README.md
# ln-310 Multi-Agent Validator — Architecture Reference
Quick-reference for understanding how the validator works at runtime. For implementation details, see SKILL.md and reference files.
## Modes
| | mode=story | mode=plan_review |
|---|-----------|-----------|
| **Input** | Story ID (Backlog) | Plan file (auto-detect) |
| **Phases** | 0 → 1 → 2 → 3 → 4 → 5 → 6 → 7 → 8 | 0 → 1 → 2 → 3 → 5 → 6 → 8 |
| **Phase 3 work** | 30-criteria audit + display | MCP Ref research |
| **Agents** | Registry-configured external agents (Codex by default) | Registry-configured external agents (Codex by default) |
| **Output** | GO/NO-GO, Story → Todo | Advisory corrections |
| **Prompt template** | `modes/story.md` | `modes/plan_review.md` |
## Phase Flow
### mode=story
```
Phase 0 Phase 1 Phase 2 Phase 3
┌──────────┐ ┌─────────────┐ ┌──────────────────┐ ┌──────────────┐
│ Load │ │ Resolve │ │ Health Check │ │ Research & │
│ tools_ │──→│ Story + │─→│ Build prompt │──→│ Audit │
│ config │ │ Task meta │ │ Launch agents ◄──┼───┼── PARALLEL │
└──────────┘ └─────────────┘ └──────────────────┘ │ Display pts │
│ agents in background│ + Fix Plan │
▼ └──────┬───────┘
Phase 4 Phase 5 Phase 6 Phase 7 │Phase 8
┌──────────────┐ ┌────────────────────────┐ ┌──────────────┐ ┌──────────────┐ ┌─────────────┐
│ Auto-Fix │ │ Wait for agents │ │ Iterative │ │ Story → Todo │ │ Self-Check │
│ 11 groups │→│ Parse + Merge + Dedup │→│ Refinement │→│ Kanban update│→│ All [ ] must│
│ 30 criteria │ │ REJECT if disagree │ │ Codex loop │ │ Summary post │ │ be [x] │
└──────────────┘ └────────────────────────┘ └──────────────┘ └──────────────┘ └─────────────┘
```
### mode=plan_review
```
Phase 0 Phase 1 Phase 2 Phase 3 Phase 5
┌──────────┐ ┌─────────────┐ ┌──────────────────┐ ┌──────────────┐ ┌────────────────┐
│ Load │ │ Resolve │ │ Health Check │ │ MCP Ref │ │ Wait agents │
│ tools_ │──→│ input + │─→│ Build prompt │──→│ Research │─→│ Merge + Verify │
│ config │ │ metadata │ │ Launch agents ◄──┼───┼── PARALLEL │ │ Apply accepted │
└──────────┘ └─────────────┘ └──────────────────┘ │ Compare & │ └───────┬────────┘
│ Correct │ │
└──────────────┘ Phase 6
┌──────────────┐
│ Iterative │
│ Refinement │
└──────┬───────┘
│
Phase 8
┌──────────────┐
│ Self-Check │
│ Advisory out │
└──────────────┘
```
## Parallel Architecture
The key design: agents run in background while Claude works foreground. No idle waiting.
```
Timeline ─────────────────────────────────────────────────────────────────────→
Phase 2 Phases 3-4 (foreground) Phase 5
┌────────────────────┐ ┌───────────────────────────────────┐ ┌─────────────┐
│ agent_runner.mjs │ │ │ │ Process-as- │
│ --health-check │ │ mode=story: │ │ arrive: │
│ │ │ Research + 30 criteria audit │ │ │
│ Build prompt from │ │ Display penalty points │ │ 1st agent → │
│ review_base.md + │ │ Auto-fix 11 groups │ │ verify │
│ modes/{mode}.md │ │ │ │ │
│ │ │ mode=plan_review: │ │ 2nd agent → │
│ Launch: │ │ MCP Ref research (3-5 topics) │ │ merge │
│ ├─ Codex CLI ─────┼──┼──── runs in background ──────────┼──┼→ parse │
│ └─ Extra agent* ──┼──┼──── runs in background ──────────┼──┼→ parse │
└────────────────────┘ └───────────────────────────────────┘ │ │
│ Dedup vs │
│ own + hist │
│ │
│ AGREE → │
│ apply │
│ REJECT → │
│ skip │
└─────────────┘
```
*Optional, only when configured in the review registry.
## Agent Review Lifecycle
```
Claude Codex CLI Extra Agent*
│ │ │
├─ agent_runner.mjs ─────────────→│ LAUNCHED │
├─ agent_runner.mjs ─────────────→│ │ LAUNCHED
│ │ │
│ ◄── foreground work ──► │ reviewing... │ reviewing...
│ │ │
│ (process-as-arrive) │ │
│◄────────────────────────────────┤ DONE: suggestions[] │
├─ verify + evaluate │ │
│ │ │
│◄────────────────────────────────┼─────────────────────────────┤ DONE
├─ merge + dedup │ │
│ │ │
├─ AGREE → apply fix │ │
├─ REJECT → skip │ │
│ │ │
├─ Iterative Refinement loop: │ │
│ ├─ Send artifact to Codex ───→│ review │
│ ├─ Parse suggestions ◄────────┤ suggestions[] │
│ ├─ AGREE/REJECT each │ │
│ └─ Repeat until APPROVED │ │
│ │ │
├─ Save review_history.md │ │
└─ Display summary │ │
```
## 30 Criteria at a Glance
| # | Criterion | Severity | Group |
|---|-----------|----------|-------|
| 1 | Story Structure | LOW (1) | Structural |
| 2 | Tasks Structure | LOW (1) | Structural |
| 3 | Story Statement | LOW (1) | Structural |
| 4 | Acceptance Criteria | MEDIUM (3) | Structural |
| 5 | Standards Compliance | CRITICAL (10) | Standards |
| 6 | Library & Version | HIGH (5) | Solution |
| 7 | Test Strategy | LOW (1) | Workflow |
| 8 | Documentation Integration | MEDIUM (3) | Workflow |
| 9 | Story Size | MEDIUM (3) | Workflow |
| 10 | Test Task Cleanup | MEDIUM (3) | Workflow |
| 11 | YAGNI | MEDIUM (3) | Workflow |
| 12 | KISS | MEDIUM (3) | Workflow |
| 13 | Task Order | MEDIUM (3) | Workflow |
| 14 | Documentation Complete | HIGH (5) | Quality |
| 15 | Code Quality Basics | MEDIUM (3) | Quality |
| 16 | Story-Task Alignment | MEDIUM (3) | Traceability |
| 17 | AC-Task Coverage | MEDIUM (3) | Traceability |
| 17b | AC Invocability | HIGH (5)* | Traceability |
| 17c | Scenario Completeness | HIGH (5)* | Traceability |
| 18 | Story Dependencies | CRITICAL (10) | Dependencies |
| 19 | Task Dependencies | MEDIUM (3) | Dependencies |
| 20 | Risk Analysis | HIGH (5)* | Risk |
| 21 | Alternative Solutions | MEDIUM (3) | Solution |
| 22 | AC Verify Methods | MEDIUM (3) | Verification |
| 23 | Architecture Considerations | MEDIUM (3) | AI-Readiness |
| 24 | Assumption Registry | MEDIUM (3) | Structural |
| 25 | AC Cross-Story Overlap | MEDIUM (3) / CRITICAL (10) | Cross-Reference |
| 26 | Task Cross-Story Duplication | LOW (1) | Cross-Reference |
| 27 | Pre-mortem Analysis | MEDIUM (3) | Pre-mortem |
| 28 | Library Feature Utilization | MEDIUM (3) | Solution |
*#20 capped at 15 points (3 risks max). #25 max 1 CRITICAL = 10. #17b and #17c are HIGH per AC, uncapped. Maximum total: 123+ points.
## Penalty Points Scoring
```
Severity: CRITICAL = 10 HIGH = 5 MEDIUM = 3 LOW = 1
Readiness Score = 10 - (Penalty / 5)
GO: Penalty After = 0 AND no FLAGGED items
NO-GO: Penalty After > 0 OR any FLAGGED items
AC Coverage: 100% = pass 80-99% = -3 penalty <80% = -5, NO-GO
```
## File Map
| File | Purpose | Read in |
|------|---------|---------|
| `SKILL.md` | Full workflow spec (phases 0-9) | Entry point |
| **Validation criteria** | | |
| `references/phase2_research_audit.md` | 30 criteria + auto-fix actions table | Phase 3 |
| `references/penalty_points.md` | Calculation rules, caps, report format | Phase 3 |
| **Validation checklists** | | |
| `references/structural_validation.md` | #1-4: template, statement, AC | Phase 4 group 1 |
| `references/standards_validation.md` | #5: RFC/OWASP compliance | Phase 4 group 2 |
| `references/solution_validation.md` | #6, #21, #28: libraries, alternatives, feature utilization | Phase 4 group 3 |
| `references/workflow_validation.md` | #7-13: test, docs, size, YAGNI, KISS | Phase 4 group 4 |
| `references/quality_validation.md` | #14-15: documentation, hardcoded values | Phase 4 group 5 |
| `references/dependency_validation.md` | #18-19: forward deps, DAG, parallel | Phase 4 group 6 |
| `references/cross_reference_validation.md` | #25-26: AC overlap, task duplication | Phase 4 group 7 |
| `references/risk_validation.md` | #20: impact x probability matrix | Phase 4 group 8 |
| `references/premortem_validation.md` | #27: Tiger/Paper Tiger/Elephant | Phase 4 group 9 |
| `references/traceability_validation.md` | #16-17, #17b-17c, #22: alignment, coverage, invocability, scenario completeness, verify | Phase 4 groups 10-11 |
| **Research** | | |
| `references/plan_review_pipeline.md` | MCP Ref research pipeline | Phase 3 |
| `references/domain_patterns.md` | Pattern registry for domain extraction | Phase 3 |
| `references/templates/mcp_ref_findings_template.md` | Output template for MCP findings | Phase 3 |
| **Shared** | | |
| `references/agent_review_workflow.md` | Agent launch, merge, refinement protocol | Phase 2, 5, 6 |
| `references/agent_delegation_pattern.md` | Inline agent review architecture | Phase 2 |
| `references/agent_review_memory.md` | Review history dedup | Phase 5 |
| `references/agents/prompt_templates/review_base.md` | Base prompt for all agent modes | Phase 2 |
| `references/agents/prompt_templates/modes/code.md, references/agents/prompt_templates/modes/context.md, references/agents/prompt_templates/modes/plan_review.md, references/agents/prompt_templates/modes/story.md` | Mode-specific prompt parts | Phase 2 |
| `references/research_tool_fallback.md` | MCP Ref → Context7 → WebSearch chain | Phase 3 |
---
**Version:** 2.0.0
**Last Updated:** 2026-03-22
references/agent_delegation_pattern.md
<!-- SOURCE-OF-TRUTH: shared/references/agent_delegation_pattern.md. Edit ONLY here; run `node tools/marketplace/shared.mjs sync` -->
# Agent Delegation Pattern
Core pattern for launching a non-host external CLI advisor through `references/agents/agent_runner.mjs`. Use it only when independent model review or long-running isolated analysis is worth the overhead.
## Use When
- A second model can catch plan, review, or validation gaps.
- Work can run independently while the host continues local evidence collection.
- The host remains the decision maker and verifies every advisor claim.
Do not launch advisors for trivial checks, purely mechanical edits, or work the host can verify directly with tests/tools.
## Invocation
Use prompt files for non-trivial context and metadata files for deterministic runtime bookkeeping.
```bash
node references/agents/agent_runner.mjs --agent {advisor_agent} --prompt-file prompt.md --output-file result.md --metadata-file result.meta.json --cwd /project
node references/agents/agent_runner.mjs --health-check --json --host-agent {claude|codex}
node references/agents/agent_runner.mjs --agent {advisor_agent} --resume-session {session_id} --prompt-file followup.md --output-file result.md --cwd /project
```
## Output Contract
Stdout is JSON with at least:
```json
{
"success": true,
"agent": "advisor",
"response": "...",
"session_id": "optional",
"pid": 12345,
"log_file": "...log",
"output_file": "...result.md",
"exit_code": 0,
"error": null
}
```
When `--output-file` is used, the runner wraps the result with `AGENT_REVIEW_RESULT` metadata markers. Skills read the result file and metadata; they must not rewrite runner-owned result files.
## Prompt Rules
- State the exact review goal and required output shape.
- Keep scope narrow: one review task per call.
- Pass file paths, URLs, or artifacts; let the advisor read needed source material.
- Include confidence/filtering rules so the host can reject unsupported claims.
- Require markdown findings plus a structured JSON block when the result is consumed programmatically.
## Fallback Rules
- Health check fails or no advisor available -> record skipped reason and use host self-review when that is acceptable for the skill.
- Advisor crashes, times out, or returns transport/auth/tool errors -> treat as transport evidence, not as a domain finding.
- Advisor claims require host verification before merge, repair, approval, or verdict changes.
- Long-running lifecycle, liveness, retry, and refinement rules live in `references/agent_review_workflow.md`; load that file only for skills that actually run an agent review loop.
---
**Version:** 2.0.0
**Last Updated:** 2026-03-26
references/agent_review_workflow.md
<!-- SOURCE-OF-TRUTH: shared/references/agent_review_workflow.md. Edit ONLY here; run `node tools/marketplace/shared.mjs sync` -->
# Agent Review Workflow
Lifecycle rules for skills that actually run external advisor review or refinement loops. Validators that only launch agents through the evaluation runtime should prefer the compact policy in their own `SKILL.md` plus `agent_delegation_pattern.md`.
## Health Check
1. Read `.hex-skills/environment_state.json` when present and skip disabled advisors.
2. Probe available non-host advisors:
```bash
node references/agents/agent_runner.mjs --health-check --json --host-agent {claude|codex}
```
3. If no advisor is available, record a skipped reason and continue only when the skill allows host self-review fallback.
## Prompt and Launch
- Build one narrow prompt per advisor or refinement perspective.
- Use prompt files, output files, and metadata files.
- Materialize any external context files under `.hex-skills/agent-review/context/` before referencing them in advisor prompts.
- Register launched agents with the active coordinator runtime when one exists.
```bash
node references/agents/agent_runner.mjs --agent {agent} \
--prompt-file {prompt.md} \
--output-file {result.md} \
--metadata-file {metadata.json} \
--cwd {project_dir}
```
## Wait and Liveness
- Resolve agents through the active runtime `sync-agent` command before merge gates.
- Claude hosts may use `Monitor` for observability; it is not the source of truth.
- Do not use sleep loops or ad-hoc polling as the primary wait mechanism.
- Before declaring an advisor failed, check log mtime, recent log content, and process liveness via `agent_runner.mjs --verify-dead {pid}`.
- Slow is not failed. The runner hard timeout is the time boundary.
## Verification
The host verifies every advisor claim before accepting it:
- transport/auth/tool failures are not domain findings
- unsupported suggestions are rejected
- accepted suggestions must cite code, docs, tests, or runtime evidence
- project mutations happen only after host verification
## Iterative Refinement
Use only for skills that require a bounded refinement loop:
- Stage 1: independent advisor sessions for configured perspectives.
- Stage 2: final sweep after Stage 1 accepted fixes.
- Record result paths, failures, accepted suggestions, and cleanup evidence.
- Valid exits: `COMPLETED`, `PARTIAL_ERROR`, `ERROR`, `SKIPPED`.
## Cleanup
- Result files are runner-owned; skills read them but do not rewrite them.
- Kill or verify-dead all launched advisor processes before final completion.
- Record skipped/failure reasons separately from domain verdict.
---
**Version:** 3.0.0
**Last Updated:** 2026-03-26
references/agent_skill_roots_contract.md
<!-- SOURCE-OF-TRUTH: shared/references/agent_skill_roots_contract.md. Edit ONLY here; run `node tools/marketplace/shared.mjs sync` -->
# Agent Skill Roots Contract
Shared contract for active skill discovery roots versus cache roots across supported agents.
Use this contract when a skill:
- audits agent setup health
- repairs marketplace/plugin alignment
- diagnoses duplicate skills
- writes Codex skill-root metadata into environment state
## Rules
- Discovery roots may contain only active install surfaces and system skills.
- Cache snapshots must never live under a discovery root.
- Duplicate detection is by skill directory name under the discovery root.
- A discovery violation is any cache path, stale snapshot, or foreign install location exposed inside the discovery root.
## Root Model by Agent
| Agent | Discovery Root | Active Install Surface | Cache Root | Discovery Rule |
|-------|----------------|------------------------|------------|----------------|
| Claude Code | `~/.claude/plugins/marketplaces/{marketplace}` | Active marketplace/plugin install under `marketplaces/` | `~/.claude/plugins/cache/{marketplace}/{family}/{snapshot}` | Cache is not an active install surface |
| Codex CLI | `~/.codex/skills` | `.system` plus `marketplaces/{marketplace}` under the Codex root | `~/.codex/skill-cache/{marketplace}` or another path outside `~/.codex/skills` | Cache under `~/.codex/skills/cache/**` is invalid |
## Codex-Specific Rules
- `~/.codex/skills` is the Codex discovery root. Do not map this root to `~/.claude/plugins` or any other foreign plugin tree.
- `~/.codex/skills/marketplaces/{marketplace}` is the active marketplace surface. Use one active copy per marketplace.
- `~/.codex/skills/known_marketplaces.json` must point `installLocation` to the Codex active install surface, not to `~/.claude/plugins/...`.
- `~/.codex/skills/cache/**` is a discovery violation even if the cache was created by a previous alignment run.
- If duplicate skill names remain after cache relocation and install-location repair, treat the Codex mapping as drifted and not healthy.
## Environment State Fields
Record Codex skill-root health under `agents.codex`:
- `active_skill_roots`
- `cache_roots`
- `duplicate_skill_names`
- `discovery_violation`
## Verification Checklist
- Codex discovery root contains no `cache/**`
- Active marketplace path exists under `~/.codex/skills/marketplaces/...`
- `known_marketplaces.json` points to the active Codex install path
- Duplicate skill-name scan under `~/.codex/skills` returns only active copies
references/agents/agent_registry.json
{
"version": "5.0.0",
"agents": {
"codex": {
"name": "Codex CLI",
"family": "codex",
"command": "codex",
"args": ["exec", "--full-auto", "--color", "never", "-C", "{cwd}", "-o", "{output_file}"],
"resume_args": ["exec", "resume", "{session_id}", "--full-auto", "-o", "{output_file}"],
"resume_prompt_delivery": "positional",
"session_id_capture": {
"strategy": "from_log",
"pattern": "session.id:\\s*([0-9a-f]+-[0-9a-f]+-[0-9a-f]+-[0-9a-f]+-[0-9a-f]+)"
},
"env_override": {},
"hard_timeout_seconds": 1800,
"skill_groups": ["200", "300", "310", "510"],
"health_check": "codex --version",
"focus_hint": "Primary focus: correctness bugs, schema feasibility, data integrity, error handling, code-level edge cases"
},
"claude": {
"name": "Claude Code CLI",
"family": "claude",
"command": "claude",
"args": ["-p", "--output-format", "json", "--dangerously-skip-permissions", "--max-turns", "30"],
"resume_args": ["-p", "--resume", "{session_id}", "--output-format", "json", "--dangerously-skip-permissions", "--max-turns", "30"],
"resume_prompt_delivery": "positional",
"hard_timeout_seconds": 1800,
"normal_prompt_delivery": "positional",
"response_capture": {
"strategy": "from_json_field",
"field_path": "result"
},
"session_id_capture": {
"strategy": "from_json_field",
"field_path": "session_id"
},
"skill_groups": ["310", "510", "813", "840"],
"health_check": "claude --version",
"focus_hint": "Primary focus: architecture consistency, implementation simplicity, maintainability, and review judgment"
}
}
}
references/agents/agent_registry.json.SOURCE.md
<!-- SOURCE-OF-TRUTH: shared/agents/agent_registry.json. Edit ONLY at the .json above; run `node tools/marketplace/shared.mjs sync` -->
# Distribution metadata
Source: `shared/agents/agent_registry.json`
Distributed targets:
- `plugins/agile-workflow/skills/ln-300-task-coordinator/references/agents/agent_registry.json`
- `plugins/agile-workflow/skills/ln-310-multi-agent-validator/references/agents/agent_registry.json`
- `plugins/agile-workflow/skills/ln-315-review-merge-worker/references/agents/agent_registry.json`
- `plugins/agile-workflow/skills/ln-316-review-refinement-worker/references/agents/agent_registry.json`
- `plugins/agile-workflow/skills/ln-510-quality-coordinator/references/agents/agent_registry.json`
- `plugins/codebase-audit-suite/skills/ln-644-dependency-topology-auditor/references/agents/agent_registry.json`
- `plugins/documentation-pipeline/skills/ln-162-skill-reviewer/references/agents/agent_registry.json`
- `plugins/optimization-suite/skills/ln-813-optimization-plan-validator/references/agents/agent_registry.json`
references/agents/agent_result_classifier.mjs
// SOURCE-OF-TRUTH: shared/agents/agent_result_classifier.mjs. Edit ONLY here; run `node tools/marketplace/shared.mjs sync`
export const AGENT_FAILURE_CLASSES = Object.freeze({
NONE: "none",
TIMEOUT_IDLE: "timeout_idle",
TIMEOUT_PRODUCTIVE: "timeout_productive",
PERMISSION_DENIAL: "permission_denial",
TOOL_MISSING: "tool_missing",
AUTH_MISSING: "auth_missing",
RATE_LIMITED: "rate_limited",
ASKED_QUESTION: "asked_question",
AGENT_ERROR: "agent_error",
UNKNOWN: "unknown",
});
function compactText(value) {
return String(value || "").toLowerCase();
}
function classifyText(text) {
if (/\b(rate limit|too many requests|quota exceeded|429|retry after)\b/.test(text)) {
return AGENT_FAILURE_CLASSES.RATE_LIMITED;
}
if (/\b(permission denied|access denied|operation not permitted|not allowed|blocked by permissions)\b/.test(text)) {
return AGENT_FAILURE_CLASSES.PERMISSION_DENIAL;
}
if (/\b(command .* not found|not found in path|enoent|is not recognized|required tool|tool missing)\b/.test(text)) {
return AGENT_FAILURE_CLASSES.TOOL_MISSING;
}
if (/\b(authentication|unauthorized|login required|not logged in|api key|token missing|credentials)\b/.test(text)) {
return AGENT_FAILURE_CLASSES.AUTH_MISSING;
}
if (/\?\s*$|should i|do you want|please confirm|need clarification/.test(text)) {
return AGENT_FAILURE_CLASSES.ASKED_QUESTION;
}
return null;
}
export function classifyAgentResult({
success = false,
timedOut = false,
exitCode = null,
error = null,
response = null,
rawStdout = "",
rawStderr = "",
logContent = "",
outputWritten = false,
sessionId = null,
} = {}) {
const progressSignals = {
output_written: Boolean(outputWritten || response),
log_written: Boolean(String(logContent || rawStdout || rawStderr).trim()),
session_captured: Boolean(sessionId),
};
const hasProgress = Object.values(progressSignals).some(Boolean);
let failureClass = AGENT_FAILURE_CLASSES.NONE;
if (timedOut) {
failureClass = hasProgress
? AGENT_FAILURE_CLASSES.TIMEOUT_PRODUCTIVE
: AGENT_FAILURE_CLASSES.TIMEOUT_IDLE;
} else if (!success) {
const textClass = classifyText(compactText([error, response, rawStdout, rawStderr, logContent].join("\n")));
failureClass = textClass || (exitCode === -1
? AGENT_FAILURE_CLASSES.TOOL_MISSING
: AGENT_FAILURE_CLASSES.AGENT_ERROR);
}
return {
failure_class: failureClass,
progress_signals: progressSignals,
session_usable: Boolean(success && failureClass === AGENT_FAILURE_CLASSES.NONE && sessionId),
};
}
references/agents/agent_runner.mjs
#!/usr/bin/env node
// SOURCE-OF-TRUTH: shared/agents/agent_runner.mjs. Edit ONLY here; run `node tools/marketplace/shared.mjs sync`
/**
* Universal Agent Runner for Multi-Model Orchestration (Node.js ESM port).
*
* Calls external CLI AI agents (Codex) via subprocess
* and returns structured JSON to stdout for Claude Code consumption.
*
* Streams agent stdout to a log file for real-time visibility.
*
* Supports session resume for multi-turn debate (challenge/follow-up rounds).
*
* Exit codes: 0 = success, 1 = agent error, 2 = agent not found/unavailable
*
* Usage:
* node agent_runner.mjs --agent codex --prompt "Analyze scope..."
* node agent_runner.mjs --agent codex --prompt-file /tmp/prompt.md --cwd /project
* node agent_runner.mjs --agent claude --prompt-file prompt.md --output-file result.md --cwd /project
* node agent_runner.mjs --agent codex --resume-session abc-123 --prompt-file challenge.md --output-file result.md --cwd /project
* node agent_runner.mjs --health-check
* node agent_runner.mjs --health-check --json
* node agent_runner.mjs --list-agents
*/
import { spawn, execSync } from "node:child_process";
import fs from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { parseArgs } from "node:util";
import { REVIEW_AGENT_STATUSES } from "../scripts/coordinator-runtime/lib/runtime-constants.mjs";
import { classifyAgentResult } from "./agent_result_classifier.mjs";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const SCRIPT_DIR = __dirname;
const REGISTRY_PATH = path.join(SCRIPT_DIR, "agent_registry.json");
const IS_WINDOWS = process.platform === "win32";
const DEFAULT_HARD_TIMEOUT = 1800; // 30 minutes
const UUID_PATTERN = /[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/i;
function loadRegistry() {
return JSON.parse(fs.readFileSync(REGISTRY_PATH, "utf-8"));
}
function parseAdvisorDepth(value) {
const parsed = parseInt(value || "0", 10);
return Number.isFinite(parsed) && parsed > 0 ? parsed : 0;
}
function buildEnv(agentCfg, agentName) {
const env = Object.assign({}, process.env);
const childHost = agentCfg.family || agentName;
if (childHost) {
env.SKILLS_HOST_AGENT = childHost;
}
env.SKILLS_ADVISOR_DEPTH = String(
parseAdvisorDepth(process.env.SKILLS_ADVISOR_DEPTH) + 1
);
const overrides = agentCfg.env_override || {};
for (const [key, val] of Object.entries(overrides)) {
env[key] = val;
}
return env;
}
const WINDOWS_PERFORMANCE_HINT =
"\n## Platform Note (Windows) — MANDATORY\n" +
"Shell commands use PowerShell (5-15 seconds EACH). You MUST minimize shell usage.\n" +
"- **USE MCP tools for file operations**: `read_file`, `edit_file`, `grep_search`, `outline` " +
"are instant. NEVER use shell for reading files (`cat`, `type`, `Get-Content`) " +
"or searching (`grep`, `rg`, `findstr`).\n" +
"- **`outline` first**: before reading large files, use `outline` to see structure (10 lines vs 500).\n" +
"- **USE your built-in file read/write tools** — they are instant, shell is not.\n" +
"- **BATCH unavoidable shell ops**: combine into ONE command " +
"(e.g., `git log --oneline -10 && git diff --stat`).\n" +
"- **Shell budget**: MAX 3-5 shell calls for the entire task.\n\n";
function preparePrompt(prompt) {
if (IS_WINDOWS) {
return WINDOWS_PERFORMANCE_HINT + prompt;
}
return prompt;
}
/**
* Synchronous `which` replacement.
* On Windows: search PATH for cmd with PATHEXT extensions.
* On Unix: use `which` command.
* Returns absolute path or null.
*/
function whichSync(cmd) {
if (!cmd) return null;
// If cmd is already an absolute path and exists, return it
if (path.isAbsolute(cmd) && fs.existsSync(cmd)) {
return cmd;
}
if (IS_WINDOWS) {
const pathDirs = (process.env.PATH || "").split(path.delimiter);
const pathExts = (process.env.PATHEXT || ".COM;.EXE;.BAT;.CMD").split(";");
for (const dir of pathDirs) {
// PATHEXT extensions first — bare name may be a POSIX shell shim
for (const ext of pathExts) {
const fullExt = path.join(dir, cmd + ext);
if (fs.existsSync(fullExt)) return fullExt;
}
const full = path.join(dir, cmd);
if (fs.existsSync(full)) return full;
}
return null;
}
// Unix
try {
const result = execSync("which " + cmd, {
encoding: "utf-8",
stdio: ["pipe", "pipe", "pipe"],
timeout: 5000,
});
const resolved = result.trim();
return resolved || null;
} catch {
return null;
}
}
/**
* Replace {cwd}, {output_file}, {session_id} placeholders in args.
*
* If a placeholder value is empty/None, removes the flag AND its value.
* E.g., args=["-C", "{cwd}", "-o", "{output_file}"] with output_file=""
* becomes ["-C", "/project"] (removes -o and {output_file}).
*/
function resolveArgPlaceholders(args, context) {
const resolved = [];
let skipNext = false;
for (let i = 0; i < args.length; i++) {
if (skipNext) {
skipNext = false;
continue;
}
const arg = args[i];
const hasPlaceholder = arg.includes("{") && arg.includes("}");
if (hasPlaceholder) {
let value = arg;
for (const [key, val] of Object.entries(context)) {
value = value.replace(
new RegExp("\\{" + key + "\\}", "g"),
val ? String(val) : ""
);
}
if (!value) {
if (resolved.length > 0 && resolved[resolved.length - 1].startsWith("-")) {
resolved.pop();
}
continue;
}
resolved.push(value);
} else {
if (i + 1 < args.length) {
const nextArg = args[i + 1];
if (nextArg.includes("{") && nextArg.includes("}")) {
let nextVal = nextArg;
for (const [key, val] of Object.entries(context)) {
nextVal = nextVal.replace(
new RegExp("\\{" + key + "\\}", "g"),
val ? String(val) : ""
);
}
if (!nextVal) {
skipNext = true;
continue;
}
}
}
resolved.push(arg);
}
}
return resolved;
}
function buildCommand(agentCfg, resolvedArgs) {
const cmdPath = whichSync(agentCfg.command) || agentCfg.command;
if (IS_WINDOWS && /\.(cmd|bat)$/i.test(cmdPath)) {
return ["cmd", "/c", cmdPath, ...resolvedArgs];
}
return [cmdPath, ...resolvedArgs];
}
/**
* Extract session ID from agent output based on capture strategy.
* Returns session_id string or null if not captured.
*/
function captureSessionId(agentCfg, rawOutput) {
const captureCfg = agentCfg.session_id_capture;
if (!captureCfg) return null;
const strategy = captureCfg.strategy;
if (strategy === "from_log") {
const pattern = captureCfg.pattern;
if (pattern) {
const re = new RegExp(pattern, "i");
const match = re.exec(rawOutput);
if (match && match[1]) {
return match[1];
}
}
// Fallback: first UUID in output
const uuidMatch = UUID_PATTERN.exec(rawOutput);
return uuidMatch ? uuidMatch[0] : null;
}
if (strategy === "from_jsonl_field") {
const fieldPath = captureCfg.field_path || "session_id";
const lines = rawOutput.trim().split("\n");
for (const rawLine of lines) {
const line = rawLine.trim();
if (!line) continue;
try {
let event = JSON.parse(line);
let value = event;
for (const part of fieldPath.split(".")) {
if (value && typeof value === "object" && !Array.isArray(value)) {
value = value[part];
} else {
value = null;
break;
}
}
if (value && typeof value === "string") {
return value;
}
} catch {
continue;
}
}
const uuidMatch = UUID_PATTERN.exec(rawOutput);
return uuidMatch ? uuidMatch[0] : null;
}
if (strategy === "from_json_field") {
const fieldPath = captureCfg.field_path || "session_id";
const value = extractJsonField(rawOutput, fieldPath);
if (value && typeof value === "string") {
return value;
}
const uuidMatch = UUID_PATTERN.exec(rawOutput);
return uuidMatch ? uuidMatch[0] : null;
}
if (strategy === "from_list_command") {
const listCmd = captureCfg.command || "";
if (!listCmd) return null;
try {
const parts = listCmd.split(/\s+/);
const cmdPath = whichSync(parts[0]);
if (!cmdPath) return null;
let execParts;
if (IS_WINDOWS && /\.(cmd|bat)$/i.test(cmdPath)) {
execParts = ["cmd", "/c", cmdPath, ...parts.slice(1)];
} else {
execParts = [cmdPath, ...parts.slice(1)];
}
const result = execSync(execParts.join(" "), {
encoding: "utf-8",
timeout: 15000,
stdio: ["pipe", "pipe", "pipe"],
});
const uuidMatch = UUID_PATTERN.exec(result);
return uuidMatch ? uuidMatch[0] : null;
} catch {
return null;
}
}
return null;
}
function extractJsonField(rawOutput, fieldPath) {
if (!rawOutput) return null;
try {
let value = JSON.parse(rawOutput.trim());
for (const part of fieldPath.split(".")) {
if (value && typeof value === "object" && !Array.isArray(value)) {
value = value[part];
} else {
return null;
}
}
return value;
} catch {
return null;
}
}
function normalizeAgentResponse(agentCfg, rawResponse) {
const captureCfg = agentCfg.response_capture;
if (!captureCfg || !rawResponse) return rawResponse;
if (captureCfg.strategy !== "from_json_field") return rawResponse;
const fieldPath = captureCfg.field_path || "result";
const value = extractJsonField(rawResponse, fieldPath);
if (typeof value === "string") {
return value.trim();
}
return rawResponse;
}
function checkAgentHealth(agentName, registry) {
const agentCfg = registry.agents[agentName];
if (!agentCfg) {
return { ok: false, info: "Agent not found in registry" };
}
const cmdPath = whichSync(agentCfg.command);
if (!cmdPath) {
return { ok: false, info: "Command not found in PATH" };
}
try {
const healthCmd = agentCfg.health_check.split(/\s+/);
let execCmd;
if (IS_WINDOWS) {
const hcPath = whichSync(healthCmd[0]);
if (hcPath && /\.(cmd|bat)$/i.test(hcPath)) {
execCmd = ["cmd", "/c", hcPath, ...healthCmd.slice(1)];
} else {
execCmd = healthCmd;
}
} else {
execCmd = healthCmd;
}
const result = execSync(execCmd.join(" "), {
encoding: "utf-8",
timeout: 15000,
env: buildEnv(agentCfg),
stdio: ["pipe", "pipe", "pipe"],
});
const version = (result || "").trim();
return { ok: true, info: version.split("\n")[0].slice(0, 80) };
} catch (e) {
// Try stderr from the error object
const stderr = e.stderr ? e.stderr.toString().trim() : "";
const info = stderr.split("\n")[0].slice(0, 80) || String(e.message || e);
return { ok: false, info: info };
}
}
function buildHealthCheckReport(registry, hostAgent) {
const agents = [];
let availableCount = 0;
let skippedCount = 0;
let unavailableCount = 0;
const advisorDepth = parseAdvisorDepth(process.env.SKILLS_ADVISOR_DEPTH);
const nestedBlocked = (
advisorDepth > 0
&& process.env.SKILLS_ALLOW_NESTED_ADVISORS !== "1"
);
for (const name of Object.keys(registry.agents)) {
const cfg = registry.agents[name];
if (nestedBlocked) {
skippedCount++;
agents.push({
name,
status: "SKIPPED",
info: "nested advisor disabled",
});
continue;
}
if (hostAgent && (cfg.family === hostAgent || name === hostAgent)) {
skippedCount++;
agents.push({ name, status: "SKIPPED", info: "same as host agent" });
continue;
}
const { ok, info } = checkAgentHealth(name, registry);
const status = ok ? "OK" : "UNAVAILABLE";
agents.push({ name, status, info });
if (ok) availableCount++;
if (!ok) unavailableCount++;
}
return {
ok: availableCount > 0,
available_count: availableCount,
unavailable_count: unavailableCount,
skipped_count: skippedCount,
host_agent: hostAgent || null,
advisor_depth: advisorDepth,
agents,
};
}
function writeResultFile(outputFile, agentName, response, duration, exitCode,
sessionId) {
const timestamp = new Date().toISOString().replace(/\.\d{3}Z$/, "Z");
let header =
"<!-- AGENT_REVIEW_RESULT -->\n" +
"<!-- agent: " + agentName + " -->\n" +
"<!-- timestamp: " + timestamp + " -->\n" +
"<!-- duration_seconds: " + duration.toFixed(2) + " -->\n" +
"<!-- exit_code: " + exitCode + " -->\n";
if (sessionId) {
header += "<!-- session_id: " + sessionId + " -->\n";
}
header += "\n";
const footer = "\n\n<!-- END_AGENT_REVIEW_RESULT -->\n";
fs.mkdirSync(path.dirname(path.resolve(outputFile)), { recursive: true });
fs.writeFileSync(outputFile, header + (response || "") + footer, "utf-8");
}
function writeMetadataFile(metadataFile, metadata) {
if (!metadataFile) return;
fs.mkdirSync(path.dirname(path.resolve(metadataFile)), { recursive: true });
fs.writeFileSync(metadataFile, JSON.stringify(metadata, null, 2) + "\n", "utf-8");
}
// ---------------------------------------------------------------------------
// Streaming execution
// ---------------------------------------------------------------------------
function getLogPath(outputFile) {
if (!outputFile) return null;
if (outputFile.endsWith("_result.md")) {
return outputFile.slice(0, -"_result.md".length) + ".log";
}
const parsed = path.parse(outputFile);
return path.join(parsed.dir, parsed.name + ".log");
}
function killProcessTree(pid) {
if (IS_WINDOWS) {
try {
execSync("taskkill /T /F /PID " + pid, {
timeout: 10000,
stdio: ["pipe", "pipe", "pipe"],
});
} catch {
// best-effort
}
} else {
try {
process.kill(-pid, "SIGKILL");
} catch {
// best-effort
}
}
}
function isProcessAlive(pid) {
if (IS_WINDOWS) {
try {
const result = execSync(
"tasklist /FI \"PID eq " + pid + "\" /NH",
{ encoding: "utf-8", timeout: 5000, stdio: ["pipe", "pipe", "pipe"] }
);
return result.includes(String(pid));
} catch {
return false;
}
} else {
try {
process.kill(pid, 0);
return true;
} catch {
return false;
}
}
}
function utcTimestamp() {
return new Date().toISOString().replace(/\.\d{3}Z$/, "Z");
}
/**
* Run agent subprocess with streaming stdout to log file.
*
* Returns a Promise that resolves to the result object.
* Monitor agent progress via log file: stat for liveness, tail for stage.
*/
function executeAgent(agentCfg, cmd, stdinPrompt, hardTimeout,
subprocessCwd, env,
outputFile, logPath, metadataFile, agentName) {
return new Promise((resolve) => {
const startTime = Date.now();
const startedAt = utcTimestamp();
let logFh = null;
let timedOut = false;
let rawStdout = "";
let rawStderr = "";
let childExited = false;
let childExitCode = null;
let hardTimeoutTimer = null;
// Cleanup helper
function cleanup() {
if (hardTimeoutTimer) {
clearTimeout(hardTimeoutTimer);
hardTimeoutTimer = null;
}
}
function buildResult(fields) {
const classification = classifyAgentResult(fields);
const {
rawStdout,
rawStderr,
logContent,
outputWritten,
timedOut,
...publicFields
} = fields;
return {
...publicFields,
failure_class: classification.failure_class,
progress_signals: classification.progress_signals,
session_usable: classification.session_usable,
};
}
// Open log file
if (logPath) {
try {
fs.mkdirSync(path.dirname(path.resolve(logPath)), { recursive: true });
logFh = fs.createWriteStream(logPath, { encoding: "utf-8" });
} catch {
logFh = null;
}
}
// Spawn options
const spawnOpts = {
cwd: subprocessCwd || undefined,
env: env,
stdio: [
stdinPrompt ? "pipe" : "ignore", // stdin
logFh ? "pipe" : "pipe", // stdout (always pipe, we route manually)
"pipe", // stderr (logged separately from stdout capture)
],
};
// Unix: new session so process.kill(-pid) can kill the entire tree
if (!IS_WINDOWS) {
spawnOpts.detached = true;
}
let child;
try {
child = spawn(cmd[0], cmd.slice(1), spawnOpts);
} catch (e) {
cleanup();
if (logFh) logFh.end();
writeMetadataFile(metadataFile, {
agent: agentName,
status: REVIEW_AGENT_STATUSES.FAILED,
pid: null,
error: "Command '" + agentCfg.command + "' not found",
success: false,
});
resolve(buildResult({
success: false,
agent: agentName,
response: null,
duration_seconds: 0,
error: "Command '" + agentCfg.command + "' not found",
session_id: null,
pid: null,
log_file: logPath,
output_file: outputFile,
started_at: startedAt,
finished_at: utcTimestamp(),
exit_code: -1,
}));
return;
}
writeMetadataFile(metadataFile, {
agent: agentName,
status: REVIEW_AGENT_STATUSES.LAUNCHED,
pid: child.pid,
error: null,
success: null,
});
// Handle spawn error (e.g., ENOENT)
child.on("error", (err) => {
if (childExited) return;
childExited = true;
childExitCode = -1;
cleanup();
if (logFh) logFh.end();
writeMetadataFile(metadataFile, {
agent: agentName,
status: REVIEW_AGENT_STATUSES.FAILED,
pid: child.pid || null,
error: "Command '" + agentCfg.command + "' not found: " + err.message,
success: false,
});
resolve(buildResult({
success: false,
agent: agentName,
response: null,
duration_seconds: 0,
error: "Command '" + agentCfg.command + "' not found: " + err.message,
session_id: null,
pid: child.pid || null,
log_file: logPath,
output_file: outputFile,
started_at: startedAt,
finished_at: utcTimestamp(),
exit_code: -1,
rawStderr: err.message,
}));
});
// Send prompt via stdin
if (stdinPrompt && child.stdin) {
try {
child.stdin.write(stdinPrompt);
child.stdin.end();
} catch {
// ignore broken pipe
}
}
// Route stdout
if (child.stdout) {
child.stdout.on("data", (chunk) => {
const text = chunk.toString("utf-8");
rawStdout += text;
if (logFh) logFh.write(text);
});
}
// Route stderr to the log without contaminating machine-readable stdout.
if (child.stderr) {
child.stderr.on("data", (chunk) => {
const text = chunk.toString("utf-8");
rawStderr += text;
if (logFh) logFh.write(text);
});
}
// Hard timeout
hardTimeoutTimer = setTimeout(() => {
if (!childExited) {
timedOut = true;
killProcessTree(child.pid);
// Give it a moment then force kill the child directly
setTimeout(() => {
if (!childExited) {
try {
child.kill("SIGKILL");
} catch {
// ignore
}
}
}, 5000);
}
}, hardTimeout * 1000);
// On exit
child.on("close", (code) => {
if (childExited) return;
childExited = true;
childExitCode = code;
cleanup();
// Close log file handle
if (logFh) {
logFh.end();
}
const duration = Math.round((Date.now() - startTime) / 10) / 100;
const finishedAt = utcTimestamp();
// Clean up orphaned child processes after normal exit
killProcessTree(child.pid);
// Read log content
let logContent = "";
if (logPath && fs.existsSync(logPath)) {
try {
logContent = fs.readFileSync(logPath, "utf-8");
} catch {
// ignore
}
}
// Keep stdout as the machine-readable channel. logContent is for liveness only.
if (timedOut) {
writeMetadataFile(metadataFile, {
agent: agentName,
status: REVIEW_AGENT_STATUSES.FAILED,
pid: child.pid || null,
error: "Hard timeout after " + hardTimeout + " seconds",
success: false,
});
let outputWritten = false;
if (outputFile) {
try {
outputWritten = fs.statSync(outputFile).size > 0;
} catch {
outputWritten = false;
}
}
resolve(buildResult({
success: false,
agent: agentName,
response: null,
duration_seconds: duration,
error: "Hard timeout after " + hardTimeout + " seconds",
session_id: null,
pid: child.pid || null,
log_file: logPath,
output_file: outputFile,
started_at: startedAt,
finished_at: finishedAt,
exit_code: code,
timedOut: true,
rawStdout,
rawStderr,
logContent,
outputWritten,
}));
return;
}
// Capture session ID
const sessionId = captureSessionId(agentCfg, rawStdout);
// Parse response
let agentWroteFile = false;
if (outputFile) {
try {
const stat = fs.statSync(outputFile);
agentWroteFile = stat.size > 0;
} catch {
agentWroteFile = false;
}
}
let response;
if (agentWroteFile) {
response = normalizeAgentResponse(
agentCfg,
fs.readFileSync(outputFile, "utf-8").trim()
);
writeResultFile(outputFile, agentName, response,
duration, code, sessionId);
} else {
response = rawStdout
? normalizeAgentResponse(agentCfg, rawStdout.trim())
: null;
if (outputFile && response) {
writeResultFile(outputFile, agentName, response,
duration, code, sessionId);
}
}
// Only write exit metadata if no result file was created.
// When result file exists, sync-agent determines RESULT_READY
// from file existence; exit_code is in result file headers.
const hasResultFile = outputFile && fs.existsSync(outputFile);
if (!hasResultFile) {
writeMetadataFile(metadataFile, {
agent: agentName,
status: code === 0
? REVIEW_AGENT_STATUSES.RESULT_READY
: REVIEW_AGENT_STATUSES.FAILED,
pid: child.pid || null,
error: code !== 0 ? "Exit code " + code : null,
success: code === 0,
});
}
resolve(buildResult({
success: code === 0,
agent: agentName,
response: response || null,
duration_seconds: duration,
error: code !== 0
? ("Exit code " + code + (rawStderr ? ": " + rawStderr.trim() : ""))
: null,
session_id: sessionId,
pid: child.pid || null,
log_file: logPath,
output_file: outputFile,
started_at: startedAt,
finished_at: finishedAt,
exit_code: code,
rawStdout,
rawStderr,
logContent,
outputWritten: agentWroteFile || Boolean(outputFile && response),
}));
});
});
}
// ---------------------------------------------------------------------------
// Orchestration
// ---------------------------------------------------------------------------
async function runAgent(agentName, prompt, cwd, timeout, registry,
outputFile, resumeSession, logFile, metadataFile) {
const agentCfg = registry.agents[agentName];
if (!agentCfg) {
return {
success: false, agent: agentName,
response: null, duration_seconds: 0,
error: "Agent '" + agentName + "' not found in registry",
session_id: null, session_resumed: false,
pid: null, log_file: logFile || getLogPath(outputFile),
output_file: outputFile || null,
started_at: null, finished_at: null, exit_code: -1,
failure_class: "tool_missing",
progress_signals: { output_written: false, log_written: false, session_captured: false },
session_usable: false,
};
}
const cmdPath = whichSync(agentCfg.command);
if (!cmdPath) {
return {
success: false, agent: agentName,
response: null, duration_seconds: 0,
error: "Command '" + agentCfg.command + "' not found in PATH",
session_id: null, session_resumed: false,
pid: null, log_file: logFile || getLogPath(outputFile),
output_file: outputFile || null,
started_at: null, finished_at: null, exit_code: -1,
failure_class: "tool_missing",
progress_signals: { output_written: false, log_written: false, session_captured: false },
session_usable: false,
};
}
const context = {
cwd: cwd || process.cwd(),
output_file: outputFile || "",
session_id: resumeSession || "",
};
// Determine hard timeout
const cfgTimeout = agentCfg.hard_timeout_seconds != null
? agentCfg.hard_timeout_seconds
: (agentCfg.timeout_seconds != null
? agentCfg.timeout_seconds
: DEFAULT_HARD_TIMEOUT);
let hardTimeout;
if (timeout) {
hardTimeout = timeout;
} else if (cfgTimeout === 0) {
hardTimeout = DEFAULT_HARD_TIMEOUT;
} else {
hardTimeout = cfgTimeout;
}
const logPath = logFile || getLogPath(outputFile);
const env = buildEnv(agentCfg, agentName);
// Try resume mode if session ID provided and agent supports it
const useResume = resumeSession && agentCfg.resume_args;
if (useResume) {
const resumeArgsTemplate = agentCfg.resume_args;
const resolvedArgs = resolveArgPlaceholders(resumeArgsTemplate, context);
// Prompt delivery: positional (append to args) or flag/stdin
const delivery = agentCfg.resume_prompt_delivery || "flag";
let stdinPrompt;
if (delivery === "positional") {
resolvedArgs.push(prompt);
stdinPrompt = null;
} else {
stdinPrompt = prompt;
}
const subprocessCwd = resolvedArgs.includes("-C") ? null : cwd;
const cmd = buildCommand(agentCfg, resolvedArgs);
let result = await executeAgent(
agentCfg, cmd, stdinPrompt, hardTimeout,
subprocessCwd, env,
outputFile, logPath, metadataFile, agentName
);
// Check if resume actually worked
const errorText = (
(result.error || "") + " " + (result.response || "")
).toLowerCase();
const resumeFailed = (
!result.success
&& errorText.trim()
&& (errorText.includes("session")
|| errorText.includes("not found")
|| errorText.includes("expired")
|| errorText.includes("unexpected argument")
|| errorText.includes("unrecognized")
|| errorText.includes("invalid option")
|| errorText.includes("unknown flag"))
);
if (resumeFailed) {
process.stderr.write(
"WARNING: Session resume failed for " + agentName +
" (session=" + resumeSession + "), " +
"falling back to stateless. Error: " + result.error + "\n"
);
if (outputFile) {
try {
fs.unlinkSync(outputFile);
} catch {
// ignore
}
}
} else {
result.session_resumed = true;
return result;
}
}
// Normal (stateless) execution
const resolvedArgs = resolveArgPlaceholders(
agentCfg.args || [], context
);
// Support positional prompt delivery (e.g. claude -p "prompt")
const delivery = agentCfg.normal_prompt_delivery || "stdin";
let stdinPrompt;
if (delivery === "positional") {
resolvedArgs.push(prompt);
stdinPrompt = null;
} else {
stdinPrompt = prompt;
}
const subprocessCwd = resolvedArgs.includes("-C") ? null : cwd;
const cmd = buildCommand(agentCfg, resolvedArgs);
const result = await executeAgent(
agentCfg, cmd, stdinPrompt, hardTimeout,
subprocessCwd, env,
outputFile, logPath, metadataFile, agentName
);
result.session_resumed = false;
return result;
}
// ---------------------------------------------------------------------------
// CLI argument parsing
// ---------------------------------------------------------------------------
function printUsageAndExit(msg) {
process.stderr.write("Error: " + msg + "\n");
process.stderr.write(
"Usage: node agent_runner.mjs --agent NAME --prompt TEXT\n" +
" node agent_runner.mjs --health-check\n" +
" node agent_runner.mjs --health-check --json\n" +
" node agent_runner.mjs --list-agents\n" +
" node agent_runner.mjs --verify-dead PID\n"
);
process.exit(2);
}
async function main() {
let parsed;
try {
parsed = parseArgs({
options: {
agent: { type: "string" },
prompt: { type: "string" },
"prompt-file": { type: "string" },
"output-file": { type: "string" },
"log-file": { type: "string" },
"metadata-file": { type: "string" },
cwd: { type: "string" },
timeout: { type: "string" },
"resume-session": { type: "string" },
"host-agent": { type: "string" },
"health-check": { type: "boolean", default: false },
json: { type: "boolean", default: false },
"list-agents": { type: "boolean", default: false },
"verify-dead": { type: "string" },
},
strict: true,
allowPositionals: false,
});
} catch (e) {
printUsageAndExit(e.message);
return; // unreachable, satisfies linter
}
const opts = parsed.values;
const registry = loadRegistry();
// --list-agents
if (opts["list-agents"]) {
for (const [name, cfg] of Object.entries(registry.agents)) {
const groups = (cfg.skill_groups || []).join(", ") || "none";
process.stdout.write(name + ": " + cfg.name + " (groups: " + groups + ")\n");
}
process.exit(0);
}
// --verify-dead PID
if (opts["verify-dead"] != null) {
const pid = parseInt(opts["verify-dead"], 10);
if (isNaN(pid)) {
printUsageAndExit("--verify-dead requires a numeric PID");
}
let alive = isProcessAlive(pid);
if (alive) {
process.stderr.write("PID " + pid + " still alive, attempting tree kill\n");
killProcessTree(pid);
// Brief wait for kill to take effect
await new Promise((r) => setTimeout(r, 1000));
alive = isProcessAlive(pid);
}
const status = alive ? "ALIVE" : "DEAD";
process.stdout.write(JSON.stringify({ pid: pid, status: status }) + "\n");
process.exit(alive ? 1 : 0);
}
// --health-check
if (opts["health-check"]) {
const report = buildHealthCheckReport(registry, opts["host-agent"] || process.env.SKILLS_HOST_AGENT || null);
if (opts.json) {
process.stdout.write(JSON.stringify(report) + "\n");
} else {
for (const agent of report.agents) {
process.stdout.write(agent.name + ": " + agent.status + " -- " + agent.info + "\n");
}
}
process.exit(report.ok ? 0 : 1);
}
// --agent required for execution
if (!opts.agent) {
printUsageAndExit("--agent is required (or use --health-check / --list-agents)");
}
// Resolve prompt
let prompt = opts.prompt || null;
if (opts["prompt-file"]) {
prompt = fs.readFileSync(opts["prompt-file"], "utf-8");
}
if (!prompt) {
printUsageAndExit("--prompt or --prompt-file is required");
}
prompt = preparePrompt(prompt);
const timeoutVal = opts.timeout ? parseInt(opts.timeout, 10) : null;
const result = await runAgent(
opts.agent, prompt, opts.cwd || null, timeoutVal, registry,
opts["output-file"] || null,
opts["resume-session"] || null,
opts["log-file"] || null,
opts["metadata-file"] || null
);
process.stdout.write(JSON.stringify(result) + "\n");
const exitCode = result.success
? 0
: ((result.error || "").includes("not found") ? 2 : 1);
process.exit(exitCode);
}
main();
references/agents/prompt_templates/modes/context.md
<!-- SOURCE-OF-TRUTH: shared/agents/prompt_templates/modes/context.md. Edit ONLY here; run `node tools/marketplace/shared.mjs sync` -->
## header
# Task: Review Context
You are reviewing the provided context against feasibility, internal consistency, best practices, and risk factors. This is an independent review with fresh perspective.
## constraints
- You HAVE internet access — use it for web research and accessing URLs
- Do NOT use task management tools (Linear, Jira, etc.) — this review analyzes only local files and web research
## body
## Review Title
{review_title}
## Context Files
{context_refs}
## Review Goal
{review_goal}
{focus_hint}
Given these goals, articulate in your report's Goal section what is the REAL risk YOU will prioritize and why — this is your refinement of the caller's goals, not a replacement. Focus your analysis on the areas most relevant to your primary focus while still covering the review goal.
## Instructions
1. Read ALL referenced files from the working directory — they contain the full context for review
2. Examine the surrounding codebase in your working directory for additional context
3. Search the web for current best practices relevant to the domain
4. Focus on analysis — avoid modifying project files unless a fix is trivial and obvious.
## Focus Areas
{focus_areas}
Default areas (when no focus filter applied):
- **logic** — Is the reasoning sound? Are there logical gaps or contradictions?
- **feasibility** — Is this achievable given constraints (time, tech, team)?
- **completeness** — Are there missing considerations, edge cases, steps?
- **consistency** — Alignment with existing decisions/patterns? Side-effects contained? Interfaces honest (no hidden writes in read-named functions)?
- **best_practices** — Industry best practices (2025-2026)? Flat orchestration (no deep service chains)? Modules as sinks (self-contained) not pipes (cascading side-effects)? No backward-compat shims — replaced code must be deleted, not wrapped.
- **risk** — What could go wrong? Failure modes, dependencies, unknowns?
## alt_title
Approaches
## alt_extra
Use area `consistency` for design alternatives, `best_practices` for implementation alternatives. Only suggest if genuinely confident alternative is better.
## schema
verdict: CONTEXT_ACCEPTABLE | SUGGESTIONS
areas: logic | feasibility | completeness | consistency | best_practices | risk
suggestion_desc: Specific actionable change
reason_desc: Why this improves quality
verdict_question: is the context acceptable or are there suggestions?
references/agents/prompt_templates/modes/story.md
<!-- SOURCE-OF-TRUTH: shared/agents/prompt_templates/modes/story.md. Edit ONLY here; run `node tools/marketplace/shared.mjs sync` -->
## header
# Task: Review Story and Tasks
You are reviewing a validated Story and its implementation Tasks against the actual codebase and industry best practices. This is an independent review with fresh context.
## constraints
- You HAVE internet access — use it for Linear and web research
## body
## Story
{story_ref}
## Tasks
{task_refs}
## Review Goal
{review_goal}
{focus_hint}
Given these goals, articulate in your report's Goal section what specific risk YOU will prioritize and why — this is your refinement of the caller's goals, not a replacement. Focus your analysis on the areas most relevant to your primary focus while still covering the review goal.
## Research Focus Areas
When reviewing technical decisions, validate against:
- Industry standards (RFC, OWASP, OpenAPI) — Story references specific standard numbers?
- Library versions — pinned and current (LTS preferred)?
- Architecture patterns — matches domain-standard patterns?
- Source quality: official docs > vendor docs > community. Avoid blog posts > 2 years old.
## Instructions
1. Access the Story and Tasks using the references above (Linear URLs or local file paths)
2. If you cannot access Linear — use local alternatives: check `docs/tasks/` directory, `git log`, `git diff`, README.md. Produce your review based on available information. Note what you could not access in your output.
3. Examine the actual codebase in your working directory
4. Search the web for current best practices relevant to the technical domains
5. Compare Story/Tasks against:
- Current code structure and patterns
- Industry best practices (2025-2026)
- Technical feasibility of proposed implementation
6. Focus on analysis — avoid modifying project files unless a fix is trivial and obvious.
## Internal Reuse Check
Before evaluating external alternatives, search the codebase for:
- Utilities, helpers, or shared modules that already solve what the Tasks propose to build
- Patterns established elsewhere in the project that Tasks should follow
- Existing abstractions (base classes, middleware, hooks) Tasks could extend rather than duplicate
If found, report under area `duplication` with file paths and function/class names.
## Focus Areas
- Are Tasks achievable given the current codebase?
- Do Tasks reference correct files/modules/patterns from the code?
- Are alternative approaches considered? (see Alternative Solutions section below)
- Missing considerations (security, performance, edge cases)?
- **Library utilization:** Do Tasks plan to build something the project's existing dependencies already provide? Check manifest files (package.json, requirements.txt, etc.) against Task Implementation Plans.
- **Clean code:** Do Tasks include cleanup of replaced code? No backward-compat shims, no legacy wrappers left behind. See `references/clean_code_checklist.md` Replacement Rule.
## Risk Analysis
Evaluate implementation risks that could cause production incidents:
- **Breaking changes:** API contracts, DB schema, client compatibility
- **Data loss:** Destructive operations without safeguards (soft-delete, backups)
- **Failure modes:** What happens when dependencies fail (timeout, unavailable, corrupt response)
- **Rollback difficulty:** Can deployment be reverted safely? Irreversible migrations?
- **Dependency risks:** Single points of failure, version pinning, unsupported libraries
- **Production edge cases:** Concurrency, race conditions, resource exhaustion, unexpected input
## alt_title
Solutions
## alt_extra
Use area `architecture` for design alternatives, `best_practices` for implementation alternatives. Only suggest if genuinely confident alternative is better.
## schema
verdict: STORY_ACCEPTABLE | SUGGESTIONS
areas: security | performance | architecture | feasibility | best_practices | risk_analysis
suggestion_desc: Specific change to Story or Tasks
reason_desc: Why this improves execution quality
verdict_question: is the story acceptable or are there suggestions?
references/clean_code_checklist.md
<!-- SOURCE-OF-TRUTH: shared/references/clean_code_checklist.md. Edit ONLY here; run `node tools/marketplace/shared.mjs sync` -->
# Clean Code Checklist
Universal patterns for detecting dead code, backward-compat shims, and legacy remnants.
## 4 Dead Code Categories
### 1. Unreachable Code
Code after `return`/`throw`/`break`; dead branches (always-true/false conditions).
**Severity:** MEDIUM
### 2. Unused Code
| What | Severity |
|------|----------|
| Unused functions, classes, methods | MEDIUM |
| Unused exports (exported but never imported) | MEDIUM |
| Unused imports, variables, parameters | LOW |
### 3. Commented-Out Code
Large code blocks in comments (>5 lines with code syntax). Git preserves history — delete, don't comment.
**Severity:** LOW
### 4. Backward-Compat & Legacy
| Pattern | Example | Severity |
|---------|---------|----------|
| Old aliases | `const oldName = newName` | medium |
| Wrapper functions | `function oldFunc() { return newFunc() }` | medium |
| Unsupported re-exports | `export { newModule as oldModule }` | medium |
| Migration shims/adapters | `LegacyAdapter`, `*Compat`, `*Shim` | high if critical path |
| Version conditionals | `if (isOldVersion) { oldFunc() }` | medium |
| Legacy naming | `_old*`, `_legacy*`, `_compat*`, `_unsupported*` | medium |
| Legacy markers in comments | `// backward compat`, `// unsupported`, `// TODO: remove in v` | low |
## Replacement Rule
When implementation replaces old code:
1. Delete old implementation entirely
2. Update ALL callers to use new API
3. Remove old signatures (don't alias)
4. Remove re-exports of old names
5. Delete adapter/shim files
**Anti-pattern:** keeping old code "just in case" — git history preserves it.
## Exceptions
| Exception | When OK |
|-----------|---------|
| Published packages (npm/NuGet) | Unsupported API cycle required (major version bump) |
| Active migration (<3 months) | Clear removal timeline documented |
| External API contract | Consumers not yet migrated (tracked as tech debt) |
## Quick Checklist
```
[] No unused imports/variables/functions
[] No commented-out code blocks (>5 lines)
[] No backward-compat wrappers or aliases
[] No unsupported re-exports
[] No _old/_legacy/_compat naming
[] All callers updated to new API
[] Old files deleted (not just emptied)
```
---
**Version:** 1.0.0
**Last Updated:** 2026-02-08
references/cross_reference_validation.md
<!-- SOURCE-OF-TRUTH: plugins/agile-workflow/shared/references/cross_reference_validation.md. Edit ONLY here; run `node tools/marketplace/shared.mjs sync` -->
# Cross-Reference Validation (Criteria #25-#26)
<!-- SCOPE: Cross-Story overlap and duplication criteria #25-#26 ONLY. Contains AC overlap detection, task duplication checks. -->
<!-- DO NOT add here: Story dependencies -> dependency_validation.md, risk -> risk_validation.md -->
Detailed rules for cross-Story overlap detection and task deduplication within an Epic.
---
## Criterion #25: AC Cross-Story Overlap
**Check:** Story AC doesn't overlap or conflict with active sibling Stories in same Epic
**Penalty:** MEDIUM (3 points) for overlap / CRITICAL (10 points) for conflict
**Cap:** Max 1 CRITICAL = 10 points (report all conflicts, score only worst). Skip when Epic has only 1 Story, all siblings Done/Canceled, or Story not part of any Epic.
### Sibling Scanning Algorithm
**Step 1: Load Sibling Stories**
```
siblings = list_issues(project=Epic.id, label="user-story")
.filter(status IN [Backlog, Todo, In Progress])
.filter(id != current_story.id)
IF siblings.count == 0 -> PASS (skip check)
```
**Step 2: Structured Traceability (Primary — scored)**
| Signal | Method | Match = Overlap |
|--------|--------|-----------------|
| AC IDs | Extract AC identifiers (AC1, AC2...) from both Stories | Same AC ID in both |
| Affected Components | Parse `## Affected Components` from Tasks | Same file path/component |
| Dependency targets | Parse `## Dependencies` sections | Both depend on/block same Story |
| Implementation file paths | Extract file paths from Implementation Plan | Same file modified by both |
Overlap detected: >=2 structural signals match -> MEDIUM (3 points), add overlap note.
**Step 3: Conflict Detection (scored)**
```
FOR EACH overlapping_ac_pair:
IF same Given + same When + DIFFERENT Then:
-> CRITICAL (10 points), FLAG for human resolution
```
Example: Story A says "GET /users -> paginated list", Story B says "GET /users -> full list with cache" = same precondition + action, conflicting outcomes = CRITICAL.
**Step 4: Keyword Overlap (Fallback — advisory only, NOT scored)**
```
FOR EACH sibling:
overlap_ratio = keyword_intersection / min(keyword_count_a, keyword_count_b)
IF overlap_ratio > 0.70:
-> WARNING note (no penalty): "Advisory: high keyword similarity with Story {id}"
```
Advisory-only because keyword overlap produces false positives on formulaic text.
### Auto-fix Actions #25
1. **Overlap (MEDIUM):** Add note: `> [!NOTE] Cross-Reference: Overlapping scope with Story {id} — shared components: {list}`
2. **Conflict (CRITICAL):** Flag only (human resolution): `> [!WARNING] CRITICAL: AC Conflict with Story {id} — same Given/When, different Then`
---
## Criterion #26: Task Cross-Story Duplication
**Check:** Tasks don't duplicate sibling Stories' tasks
**Penalty:** LOW (1 point per duplication, max 3). Skip when Epic has only 1 Story, all sibling Stories have no tasks, or Story not part of any Epic.
### Detection Algorithm
**Step 1: Load Sibling Task Metadata**
```
FOR EACH active sibling Story:
sibling_tasks = list_issues(parentId=sibling.id)
Extract: title, Affected Components (file paths)
```
**Step 2: Structured Match (Primary — scored)**
| Signal | Method | Match = Duplication |
|--------|--------|---------------------|
| Affected Components | Parse `## Affected Components` from each task | Same file paths |
| Implementation targets | Extract modified files from Implementation Plan | Same files modified |
Duplication detected: >=2 file paths shared between tasks across Stories -> LOW (1 point per match, max 3).
**Step 3: Title Keyword Overlap (Fallback — advisory only)**
```
IF title keyword overlap > 0.80:
-> WARNING note (no penalty): "Advisory: task similar to '{title}' in Story {id}"
```
Human decides: duplication warnings are informational, no auto-delete.
### Auto-fix Actions #26
Add advisory note: `> [!NOTE] DRY Warning (Cross-Story): Similar task in Story {id} — shared files: {list}`
---
**Version:** 1.0.0
**Last Updated:** 2026-03-08
references/dependency_validation.md
<!-- SOURCE-OF-TRUTH: plugins/agile-workflow/shared/references/dependency_validation.md. Edit ONLY here; run `node tools/marketplace/shared.mjs sync` -->
# Dependency Validation (Criteria #18-#19)
<!-- SCOPE: Story/Task dependency validation criteria #18-#19 ONLY. Contains forward dependency detection, sequential completability checks. -->
<!-- DO NOT add here: Structural validation → structural_validation.md, workflow → workflow_validation.md -->
Detailed rules for Story and Task independence validation (no forward dependencies).
---
## Criterion #18: Story Dependencies (Within-Epic)
**Check:** No Story depends on FUTURE Stories (only previous Stories allowed)
**Penalty:** CRITICAL (10 points)
**Rule:** Story N may reference only Stories 1..N-1. Forward references (N+1, N+2...) violate sequential executability and INVEST Independence.
---
### Auto-fix Actions #18
1. Load all Stories in Epic, sort by number (US001, US002...)
2. Parse each Story's "Depends On" field, normalize refs to numbers
3. Detect forward deps:
```
FOR Story N: IF any dep D.number > N.number → CRITICAL violation (+10 pts)
```
4. Build dependency graph:
```
Story 1.1 → (none)
Story 1.2 → 1.1 ✅
Story 1.3 → 1.5 ❌ FORWARD
```
5. Suggest fixes:
- **Option A:** Reorder Stories (move depended Story before dependent)
- **Option B:** Split Epic (move dependent Stories to separate Epic)
- **Option C:** Remove dependency (make Stories independent)
6. Update Linear: comment on Story + Epic about detected forward deps
---
## Criterion #19: Task Dependencies (Within-Story)
**Check:** No Task depends on FUTURE Tasks (only previous Tasks allowed)
**Penalty:** MEDIUM (3 points)
**Rule:** Task N may reference only Tasks 1..N-1. Tasks follow Foundation-First order (DB -> Service -> API -> UI). Skip check if Story has only 1 task.
---
### Auto-fix Actions #19
1. Load all implementation Tasks in Story, sort by number
2. Parse deps from description: keywords "requires", "depends on", "needs", "uses output from"
3. Detect forward deps:
```
FOR Task N: IF any dep refs Task M where M > N → MEDIUM violation (+3 pts)
```
4. Check Foundation-First order: DB tasks before Service before API before UI
5. Suggest fixes:
- **Option A:** Reorder Tasks (move depended Task before dependent)
- **Option B:** Remove dependency (refactor to use only previous Tasks)
- **Option C:** Split Task (extract dependent part to new Task after depended)
6. Build corrected order:
```
Before: 1.API(→T3) 2.Repo 3.Service(→T2)
After: 1.Repo 2.Service(→T1) 3.API(→T2)
```
---
## Criterion #19b: Parallel Group Validity (Within-Story)
**Check:** Parallel Groups assigned correctly (no intra-group dependencies, sequential numbering)
**Penalty:** MEDIUM (3 points)
**What it checks:**
- Tasks in same Parallel Group do NOT reference each other
- All deps of group N tasks point to groups 1..N-1 only
- Group numbers are sequential (1, 2, 3...) with no gaps
- Every task has `**Parallel Group:**` field (or all lack it — backward compatible)
Skip if no tasks have `**Parallel Group:**` field or Story has only 1 task.
### DAG Detection Algorithm
Valid parallel groups form a DAG where edges go only from lower to higher groups:
```
GOOD:
T1(G1): DB migration — no deps
T2(G2): UserRepo — dep T1 ✅ (G1 < G2)
T3(G2): ProductRepo — dep T1 ✅ (G1 < G2)
T4(G3): UserService — dep T2 ✅ (G2 < G3)
BAD:
T2(G2): UserRepo — dep T1 ✅
T3(G2): ProductRepo — dep T2 ❌ (same group = mutual dependency!)
```
### Auto-fix Actions #19b
1. Parse `**Parallel Group:**` from each task
2. Build group->tasks mapping
3. For each group: verify no task refs another task in same group
4. Verify all deps point to earlier groups
5. If violation: reassign task to next group (increment)
6. If gaps in numbering: renumber sequentially
---
## Dependency Detection Patterns
**Story Dependencies (Criterion #18):**
Search in Story "Dependencies" section:
```
## Dependencies
**Depends On:**
- Story 1.3: Token validation → extract "1.3"
- US005: User profile → extract "005" → Story 1.5
```
Keywords for implicit deps:
`requires Story N` | `depends on Story N` | `needs Story N` | `blocked by Story N` | `waits for Story N`
**Task Dependencies (Criterion #19):**
Search ALL sections of Task description:
```
## Context
Requires Task 3 to generate tokens → FORWARD if current < 3
## Implementation Plan
Uses output from Task 4 → FORWARD if current < 4
## Technical Approach
Depends on validation middleware from Task 5 → FORWARD if current < 5
```
Keywords: `requires Task N` | `depends on Task N` | `needs Task N output` | `uses Task N result` | `waits for Task N`
---
**Version:** 1.0.0
**Last Updated:** 2026-02-03
references/destructive_operation_safety.md
<!-- SOURCE-OF-TRUTH: shared/references/destructive_operation_safety.md. Edit ONLY here; run `node tools/marketplace/shared.mjs sync` -->
# Destructive Operation Safety
Single source of truth for detecting, classifying, and guarding against destructive operations across the pipeline.
## Destructive Operation Keywords
Detection keywords (used by task creators, validators, executors, reviewers, quality checkers):
`DROP`, `TRUNCATE`, `DELETE` (without WHERE), `ALTER...DROP COLUMN`, `rm -rf`, `rmdir`, `unlink` (dynamic path), `terraform destroy`, `kubectl delete`, `docker volume rm`, `migrate` (schema), `purge`, `wipe`, `--force` on destructive command, `git push --force`, `git reset --hard`
## Required Safety Measures
When destructive ops detected, ALL 5 must be documented:
| # | Measure | What to document |
|---|---------|-----------------|
| 1 | **Backup plan** | What to backup, how to verify backup completeness |
| 2 | **Rollback plan** | Undo procedure, tested in non-production |
| 3 | **Blast radius** | Affected resources + estimated scope + downtime |
| 4 | **Environment guard** | Gated by env check or admin confirmation |
| 5 | **Preview / dry-run** | what-if output, SQL diff, `terraform plan`, `SELECT COUNT(*)` before DELETE |
## Severity Classification
| Severity | Examples |
|----------|----------|
| **CRITICAL** | Unguarded DELETE-all / DROP / TRUNCATE on user data tables |
| **HIGH** | Migration without DOWN, `rm -rf` with variable path, force flags on destructive cmds |
| **MEDIUM** | Schema migration without explicit rollback test, cascade delete without scope docs |
## Code-Level Guards
Centralized table — used by task reviewers (BLOCKER/CONCERN) and quality checkers (SEC-DESTR-{ID}):
| ID | Guard | What to detect | Severity |
|----|-------|---------------|----------|
| DB | Database destruction | DROP/TRUNCATE/DELETE without WHERE, no confirmation gate | CRITICAL |
| FS | File system destruction | rm/unlink with user-controlled or unbounded path | HIGH |
| MIG | Migration safety | Migration without DOWN, DROP COLUMN without backup | MEDIUM |
| ENV | Environment guard | Destructive op reachable in production without explicit flag | HIGH |
| FORCE | Force flag abuse | `--force`/`--no-verify` without justification in comments | MEDIUM |
**Skill mapping:**
- **Task reviewer:** CRITICAL/HIGH severity -> `BLOCKER: SEC-DESTR-{ID}`. MEDIUM -> `CONCERN: SEC-DESTR-{ID}`.
- **Quality checker:** `SEC-DESTR-{ID}` prefix with severity from this table.
## Template Section
Conditional — included by task creators/replanners when destructive ops detected in Implementation Plan. Remove if N/A.
```markdown
### Destructive Operation Safety
> **MANDATORY READ:** `references/destructive_operation_safety.md`
**Operations:** [list each destructive operation]
**Severity:** [CRITICAL / HIGH / MEDIUM per shared reference classification]
**Backup plan:** [what + how to verify]
**Rollback plan:** [undo procedure + tested where]
**Blast radius:** [resources + scope + downtime]
**Environment guard:** [env check or admin confirmation]
**Preview / dry-run:** [what-if output, SQL diff, terraform plan — attach or reference]
```
## Recommended Permission Patterns
Claude Code `settings.json` permission wildcards for destructive operation control.
### Allow (safe operations)
```json
"allow": [
"Edit(*)", "Write(*)", "NotebookEdit(*)",
"Bash", "WebFetch(domain:*)", "WebSearch",
"mcp__*"
]
```
### Ask (destructive — require confirmation)
```json
"ask": [
"Bash(rm *)", "Bash(rmdir *)", "Bash(shred *)", "Bash(unlink *)",
"Bash(dd *)", "Bash(mkfs *)", "Bash(fdisk *)",
"Bash(chmod *)", "Bash(chown *)",
"Bash(git *)", "Bash(gh *)",
"Bash(npm *)", "Bash(pip *)", "Bash(pip3 *)",
"Bash(yarn *)", "Bash(pnpm *)",
"Bash(docker *)", "Bash(kubectl *)",
"Bash(curl *)", "Bash(wget *)",
"Bash(kill *)", "Bash(killall *)", "Bash(pkill *)"
]
```
### Pattern syntax
| Pattern | Matches |
|---------|---------|
| `Bash(rm *)` | Any Bash command starting with `rm` |
| `Edit(*)` | Edit any file |
| `Edit(/docs/**)` | Edit only files under `/docs/` |
| `Bash(npm run *)` | Only `npm run` subcommands |
| `mcp__memory__.*` | All tools from `memory` MCP server |
| `WebFetch(domain:api.example.com)` | Fetch from specific domain only |
Prefer wildcard syntax over `dangerously-skip-permissions`. Use `/permissions` to configure interactively.
references/documentation_creation.md
<!-- SOURCE-OF-TRUTH: shared/references/documentation_creation.md. Edit ONLY here; run `node tools/marketplace/shared.mjs sync` -->
# Documentation Creation
<!-- SCOPE: Standardized documentation creation workflow. Doc types, templates, naming, format rules, output contract. -->
<!-- DO NOT add here: research methodology → research_methodology.md, tool fallback → research_tool_fallback.md -->
Unified documentation creation rules for inline doc generation.
---
## Stack Detection
| Indicator | Stack | Query Prefix | Official Docs |
|-----------|-------|--------------|---------------|
| `*.csproj`, `*.sln` | .NET | "C# ASP.NET Core" | Microsoft docs |
| `package.json` + `tsconfig.json` | Node.js | "TypeScript Node.js" | MDN, npm docs |
| `requirements.txt`, `pyproject.toml` | Python | "Python" | Python docs, PyPI |
| `go.mod` | Go | "Go Golang" | Go docs |
| `Cargo.toml` | Rust | "Rust" | Rust docs |
| `build.gradle`, `pom.xml` | Java | "Java" | Oracle docs, Maven |
---
## Doc Type Workflow
| doc_type | Purpose | Template | Output Path | Naming | Words |
|----------|---------|----------|-------------|--------|-------|
| **guide** | Pattern with Do/Don't/When table | `references/templates/guide_template.md` | `docs/guides/` | `NN-[slug].md` | 300-500 |
| **manual** | API/library reference | `references/templates/manual_template.md` | `docs/manuals/` | `[pkg]-[ver].md` | 300-500 |
| **adr** | Architecture decision | `references/templates/adr_template.md` | `docs/adrs/` | `adr-NNN-[slug].md` | 300-500 |
| **research** | Investigation answering question | `references/templates/research_template.md` | `docs/research/` | `rsh-NNN-[slug].md` | 300-700 |
**Workflow:** Detect number (scan target dir) -> Research -> Generate from template -> Validate -> Save -> Return path
---
## Section Requirements by doc_type
| doc_type | Required Sections |
|----------|-------------------|
| **guide** | Principle, Our Implementation, Patterns table, Sources, Related |
| **manual** | Package info, Overview, Methods table, Config table, Limitations |
| **adr** | Context, Decision, Rationale, Alternatives table, Consequences, Related |
| **research** | Question, Context, Methodology, Findings (tables!), Conclusions, Next Steps, Sources |
---
## Validation Specifics
| doc_type | Validation |
|----------|------------|
| **guide** | Patterns table present |
| **manual** | Version in filename |
| **adr** | ISO date, status field |
| **all** | Sources <=1 year old |
---
## ADR Dialog (5 Questions)
Answer internally before generating ADR content:
1. **Q1:** Title?
2. **Q2:** Category (Strategic / Technical)?
3. **Q3:** Context?
4. **Q4:** Decision + Rationale?
5. **Q5:** Alternatives (2 with pros/cons)?
---
## Mandatory File Creation
- ALL documentation creation MUST end with file creation
- Create target directory if missing (`docs/guides/`, `docs/manuals/`, `docs/adrs/`, `docs/research/`)
- No exceptions — file creation is required for ALL invocations
---
## NO_CODE Rule
| Forbidden | Allowed |
|-----------|---------|
| Code snippets | Tables (params, config, alternatives) |
| Implementation examples | ASCII diagrams, Mermaid diagrams |
| Code blocks >1 line | Method signatures (1 line inline) |
| | Links to official docs |
---
## Format Priority (STRICT)
| Content Type | Format |
|--------------|--------|
| Parameters | Table: Name / Type / Required / Default |
| Configuration | Table: Option / Type / Default / Description |
| Alternatives | Table: Alt / Pros / Cons / Why Rejected |
| Patterns | Table: Do / Don't / When |
| Workflow | ASCII diagram: `A -> B -> C` |
---
## Other Rules
- Research ONCE per invocation; reuse results
- Cite sources with versions/dates (<=1 year old)
- One pattern per guide; one decision per ADR; one package per manual
- Preserve language (EN/RU) from story_context
- Link to stack-appropriate docs (Microsoft for .NET, MDN for JS, etc.)
---
## Output Contract
Each inline documentation creation MUST return:
- `doc_path`: full path to created file
- `doc_type`: guide|manual|adr|research
- `status`: created|existing (skip if doc already exists at target path)
- `numbering_basis`: next sequential number (scan target dir for existing files)
Dedup rule: Glob target dir BEFORE creating. If file with matching slug exists, status=existing, add link only.
---
**Version:** 1.0.0
**Last Updated:** 2026-03-20
references/domain_patterns.md
<!-- SOURCE-OF-TRUTH: plugins/agile-workflow/shared/references/domain_patterns.md. Edit ONLY here; run `node tools/marketplace/shared.mjs sync` -->
# Domain Patterns Registry
<!-- SCOPE: Domain pattern → doc type mapping for inline documentation creation. Contains trigger keywords, doc_type output. -->
<!-- DO NOT add here: Validation logic → ln-310-multi-agent-validator SKILL.md, documentation rules → references/documentation_creation.md -->
Mapping Story patterns to documentation types for inline documentation creation during ln-310 validation.
---
## Purpose
This registry defines WHEN to create documentation inline and WHAT type of document to create.
**Usage in ln-310 Phase 3:**
1. Load domain_patterns.md
2. Scan Story title + Technical Notes for trigger keywords
3. IF keywords match → Create doc inline per references/documentation_creation.md
4. Add created doc links to Story Technical Notes
---
## Pattern Registry
| Pattern | Doc Type | Topic | Trigger Keywords | Example Output |
|---------|----------|-------|------------------|----------------|
| **OAuth/OIDC** | Manual + ADR | [library] + "Auth Strategy" | auth, oauth, oidc, token, JWT, bearer | `docs/manuals/oauth2-v7.md` + `docs/adrs/NNN-auth.md` |
| **REST API** | Guide | RESTful API Patterns | endpoint, route, controller, REST, resource | `docs/guides/NN-rest-api-patterns.md` |
| **Rate Limiting** | Guide | API Rate Limiting | rate, throttle, quota, limit | `docs/guides/NN-rate-limiting.md` |
| **Error Handling** | Guide | Error Patterns (RFC 7807) | error, exception, status code, 4xx, 5xx | `docs/guides/NN-error-handling.md` |
| **Logging** | Guide | Structured Logging | log, trace, audit, winston, pino | `docs/guides/NN-logging.md` |
| **WebSocket** | Guide | WebSocket Patterns | websocket, real-time, streaming, SSE | `docs/guides/NN-websocket.md` |
| **Pagination** | Guide | Pagination Patterns | page, offset, cursor, pagination | `docs/guides/NN-pagination.md` |
| **Caching** | Manual | [library] (redis, memcached) | cache, redis, memcached, TTL | `docs/manuals/redis-N.md` |
| **Database** | Manual | [ORM/library] | database, ORM, prisma, sequelize | `docs/manuals/prisma-N.md` |
| **Validation** | Guide | Input Validation | validate, sanitize, schema, joi, zod | `docs/guides/NN-validation.md` |
| **File Upload** | Guide | File Upload & Storage | upload, multer, file, storage, s3 | `docs/guides/NN-file-upload.md` |
| **Email** | Manual | [library] (nodemailer, sendgrid) | email, mail, smtp, sendgrid | `docs/manuals/nodemailer-N.md` |
---
## Detection Logic
### 1. Keyword Matching
```
IF Story.title OR Story.context OR Story.technical_notes contains trigger_keyword:
→ Pattern detected
```
**Example:**
- Story title: "Implement OAuth 2.0 authentication"
- Keywords detected: "oauth", "authentication"
- Pattern matched: **OAuth/OIDC**
- Action: Create Manual + ADR
### 2. Multiple Patterns
```
IF multiple patterns detected:
→ Create ALL applicable docs
```
**Example:**
- Story: "Add rate limiting to REST API"
- Patterns: **REST API** + **Rate Limiting**
- Action: Create Guide for REST + Guide for Rate Limiting
### 3. Library Detection (for Manuals)
```
IF doc_type = Manual:
→ Extract library name from Technical Notes
→ Pass library name to manual template
```
**Example:**
- Story mentions: "Using oauth2-proxy v7.6.0"
- Action: Create `docs/manuals/oauth2-proxy-v7.md`
---
## Inline Creation Example (multi-pattern)
Story: "Add rate-limited REST API with Redis caching"
→ Keywords: `REST`, `API`, `rate`, `redis`, `caching`
→ Patterns: **REST API** + **Rate Limiting** + **Caching**
```
1. Glob docs/guides/*rest*.md → not found
2. Load references/templates/guide_template.md
3. Research "RESTful API Patterns" per research_methodology.md
4. Generate guide (NO CODE, tables first, 300-500 words)
5. Save → docs/guides/NN-rest-api-patterns.md
1. Glob docs/guides/*rate-limit*.md → not found
2. Load references/templates/guide_template.md
3. Research "API Rate Limiting Pattern" per research_methodology.md
4. Generate guide
5. Save → docs/guides/NN-api-rate-limiting.md
1. Glob docs/manuals/*redis*.md → not found
2. Load references/templates/manual_template.md
3. Research "Redis v7.2" via Context7
4. Generate manual
5. Save → docs/manuals/redis-v7.md
```
---
## Usage Guidelines
### When to Delegate
✅ **DO delegate when:**
- Pattern clearly detected (keywords match)
- Documentation does NOT already exist
- Story is in Backlog/Todo (not Done/Canceled)
❌ **DON'T delegate when:**
- Documentation already exists (just add reference)
- Story in Done/Canceled status
- Pattern ambiguous (use MCP Ref fallback instead)
### Fallback Strategy
**IF no pattern matched BUT technical aspect missing:**
- Query MCP Ref directly for standards
- Add inline references to Technical Notes
- Log in Linear comment: "No pattern matched - used MCP Ref fallback"
---
**Version:** 2.0.0
**Last Updated:** 2025-01-07
references/environment_state_contract.md
<!-- SOURCE-OF-TRUTH: shared/references/environment_state_contract.md. Edit ONLY here; run `node tools/marketplace/shared.mjs sync` -->
# Environment State Contract
Project-scoped runtime contract for `.hex-skills/environment_state.json`. Load only when reading or writing agent availability, provider routing, research fallback, setup health, hook mode, or Codex skill-root health.
## Location
```text
{project_root}/.hex-skills/environment_state.json
```
Rules:
- Never read or write environment state outside the current target project root.
- Missing file means default-enabled environment with `task_management.provider = "file"`.
- Malformed JSON is a deterministic contract error.
- Schema details are writer/runtime-validator assets owned by environment setup skills; routine readers use only this contract.
## Reader Fields
Routine readers need only:
```json
{
"agents": {
"claude": { "available": true, "disabled": false },
"codex": { "available": true, "disabled": false }
},
"task_management": {
"provider": "file",
"status": "active",
"fallback": "file",
"linear": {},
"github": {},
"fallback_metadata": {}
},
"research": {
"provider": "web_search",
"fallback_chain": []
}
}
```
## Hard Rules
- `agents.{name}.disabled=true` means the agent must not be probed or launched.
- `task_management.provider` selects provider operations; use `references/storage_mode_detection.md` after reading it.
- On provider auth/rate-limit/transport/tool failure, set `provider="file"`, keep `status="active"`, and record `fallback_metadata`.
- Tool failures must not become domain findings unless there is independent domain evidence.
- Codex skill-root health follows `references/agent_skill_roots_contract.md` only when the skill audits or repairs Codex discovery.
## Writer Fields
Writers may populate only relevant sections: `agents`, `task_management`, `research`, `claude_md`, `assessment`, `hooks`, `ide_extension`.
## Reader Pattern
1. Read `.hex-skills/environment_state.json`.
2. If missing, default to `task_provider="file"` and all agents enabled.
3. If malformed, fail with a contract error.
4. Extract `task_provider = task_management.provider || "file"`.
5. Use `storage_mode_detection.md` for provider operations.
**Version:** 3.1.0
**Last Updated:** 2026-04-07
references/epistemic_protocol.md
<!-- SOURCE-OF-TRUTH: shared/references/epistemic_protocol.md. Edit ONLY here; run `node tools/marketplace/shared.mjs sync` -->
# Epistemic Protocol
<!-- SCOPE: Source attribution and anti-hallucination rules for ALL fact-sensitive skill outputs. Covers versions, APIs, standards, market data, performance claims. -->
Universal source marking protocol for research outputs. When producing factual claims, mark provenance so consumers can assess trust level.
## A. Source Hierarchy
| Priority | Source | Trust | Mark Format |
|----------|--------|-------|-------------|
| 1 | Project files (package.json, lockfiles, *.csproj) | Authoritative | `(project: {file})` |
| 2 | MCP tools (Ref, Context7) | High | `(verified via Ref/Context7)` |
| 3 | WebSearch / WebFetch results | Medium | `(web: {date})` |
| 4 | Training data | Low | `(from training, verify)` |
| 5 | No source available | None | `(UNCERTAIN)` |
Higher priority overrides lower. If project manifest says `v5.2.0` and Context7 says `v5.3.0`, trust the manifest (Priority 1).
## B. Mandatory Verification Triggers
Do NOT rely on training data alone for these claim types. Use MCP tools or flag explicitly.
| Trigger | Examples | Required Action |
|---------|----------|-----------------|
| Version numbers | "v5.2.0", "latest stable" | Context7 or project manifest |
| API signatures | Method names, parameters, return types | Ref or Context7 docs |
| Unsupported API claims | "X unsupported", "Y replaced Z" | MCP Ref for current status |
| Existence claims | "Library X exists", "Library X has method Y" | Context7 docs or WebSearch |
| Security standards | OWASP rule numbers, CVE IDs | MCP Ref |
| Market data | Market size, shares, trends | WebSearch required |
| Performance characteristics | "Handles N ops/sec", "O(1) lookup" | Benchmark source or docs |
## C. Anti-Hallucination Rules
1. Do NOT "correct" modern code syntax to older patterns familiar from training
2. Do NOT claim "X does not exist" without MCP tool verification
3. Do NOT fabricate version numbers or metrics -- use `(UNCERTAIN)` if tools unavailable
4. Do NOT mix verified and unverified data without marking each claim
5. Do NOT substitute factual data (market, performance) with "reasonable assumptions"
6. PERMITTED: say `(UNCERTAIN -- tool verification needed)` instead of guessing
## D. Output Marking Convention
Where to place source marks depending on output context:
| Context | Mark Placement |
|---------|---------------|
| Library Research table | Source column: `Context7` / `Ref` / `training` |
| Technical Notes inline | Parenthetical: `v5.2.0 (verified via Context7)` |
| Research documents (rsh-NNN) | Methodology section + per-finding source |
| Market/competitor claims | `(WebSearch: {date})` or `(UNCERTAIN)` |
| Performance claims | `(benchmark: {source})` or `(static analysis only)` |
| Standards compliance | `RFC 7231 (verified via Ref)` |
## E. Fallback Behavior
Extends Level 5 of `research_tool_fallback.md`.
`(UNCERTAIN)` is valid ONLY after exhausting the ENTIRE fallback chain (Ref -> Context7 -> WebSearch -> WebFetch). If WebSearch is available but was not attempted, use it before marking `(UNCERTAIN)`.
When ALL tools in the chain are unavailable or returned no results:
- Mark each claim: `(from training, verify)`
- If claim matches a trigger from Section B: append `-- VERIFY before implementation`
- Do NOT present training-sourced claims as verified fact
---
**Version:** 1.0.0
**Last Updated:** 2026-03-18
references/evaluation_coordinator_runtime_contract.md
<!-- SOURCE-OF-TRUTH: shared/references/evaluation_coordinator_runtime_contract.md. Edit ONLY here; run `node tools/marketplace/shared.mjs sync` -->
# Evaluation Coordinator Runtime Contract
Runtime contract for skills that actually run an evaluation loop. Routing-only skills should not mandatory-load it.
## Envelope
Evaluation coordinators own resumable state, worker orchestration, summary aggregation, cleanup evidence, and final decision recording.
Hard requirements:
- checkpoint state before phase transitions
- plan every worker with lane, dependencies, join group, and expected summary artifact
- run read-only evidence lanes in parallel only; mutation, repair, merge, approval, and status changes stay sequential
- record worker summaries before aggregation
- emit a machine-readable coordinator summary and a human report path
- for audit coordinators, keep only the final coordinator markdown report after cleanup; worker markdown reports are temporary evidence inputs
## State And Manifest
Minimum state: `phase_order`, `phase_data`, `worker_plan`, `worker_results`, `child_runs`, `inflight_workers`, `agents`, `aggregation_summary`, `report_written`, `results_log_appended`, `self_check_passed`, `summary_recorded`, `final_result`.
When background/refinement processes run, also track `background_agent_cleanup`, `refinement_cleanup`, and `cleanup_verified`. Use optional `loop_health` only when repeated attempts or advisor usefulness must be judged.
Required manifest fields: `skill`, `identifier`, `project_root`, `phase_order`, `report_path`, `created_at`. Optional: `mode`, `results_log_path`, `phase_policy`, `expected_agents`, `required_research`, `research_freshness_hours`.
## Runtime CLI
The evaluation runtime CLI must support start/status/checkpoint, worker-result recording, summary recording, agent registration/sync, loop-health recording, phase advance/pause, decision setting, and completion. `SKILL.md` files that invoke the CLI must reference the script path directly; this contract does not distribute executable assets by itself.
## Worker Plan And Transitions
Each `worker_plan` entry includes `worker`, `identifier`, `lane`, `join_group`, `depends_on`, and `mode`. Parallel lanes are read-only only. Mutating workers require non-empty `depends_on`.
Block transition when the current phase lacks a checkpoint, planned workers lack summaries, workers are inflight at aggregation, required agents are unresolved across a barrier, cleanup is incomplete, self-check fails, or the coordinator summary is missing.
Research evidence must be recorded. Load the detailed research contract only for research planning or evidence freshness checks.
Agent/tool failures are transport evidence, not validation findings. Classify permission, auth, missing-tool, rate-limit, timeout, question, agent error, and unknown separately from domain verdicts; repeated identical failures without new evidence require loop-health handling.
## Output
Coordinators emit an `evaluation-coordinator` summary with status, final result, report path, worker count, issue totals, severity counts, warnings, and cleanup verification. Workers emit `evaluation-worker` or a family-specific evaluation summary.
For audit coordinators, `report_path` is the durable final audit report. Worker `report_path` values are temporary markdown evidence paths used during aggregation and remediation planning; cleanup verification must confirm those markdown files were removed after the final report was written.
Detailed parallelism, research, refinement trace, cleanup evidence, and loop-health refs are conditional: load only when that behavior is active.
**Version:** 1.0.0
**Last Updated:** 2026-04-10
references/evaluation_parallelism_policy.md
<!-- SOURCE-OF-TRUTH: plugins/agile-workflow/shared/references/evaluation_parallelism_policy.md. Edit ONLY here; run `node tools/marketplace/shared.mjs sync` -->
# Evaluation Parallelism Policy
Canonical parallelism policy for the evaluation platform.
## Default
Sequential is the default.
Parallelism is allowed only for independent read-only branches.
## Allowed Parallel Lanes
Parallel work may overlap when branches:
- do not mutate shared artifacts
- do not depend on each other's outputs
- do not gate later ordering-sensitive phases individually
Typical allowed overlap:
- external background agents
- research worker
- local findings worker
- domain discovery or inventory reads
## Disallowed Parallel Lanes
Do not parallelize:
- docs generation that mutates shared outputs
- repair/autofix phases
- merge/application phases
- iterative refinement
- approval/status mutation
- final self-check
## Join Barrier Rule
Before aggregation or synthesis:
- all planned workers in the same join group must have recorded summaries
- no worker in that join group may remain inflight
## Required Worker Plan Fields
Every parallelized worker plan entry must define:
- `lane`
- `join_group`
- `depends_on`
Interpretation:
- `lane` describes concurrent execution bucket
- `join_group` describes the barrier to wait on
- `depends_on` must be empty for read-only parallel branches
## Agent Overlap Rule
Background agents may overlap with research and local read-only analysis.
They do not remove the need for join barriers:
- merge may not start until required agents are resolved
- completion may not happen while any required agent or worker is unresolved
**Version:** 1.0.0
**Last Updated:** 2026-04-10
references/evaluation_research_contract.md
<!-- SOURCE-OF-TRUTH: shared/references/evaluation_research_contract.md. Edit ONLY here; run `node tools/marketplace/shared.mjs sync` -->
# Evaluation Research Contract
Canonical research contract for every evaluation and audit run.
## Mandatory Rule
Research is mandatory for every evaluation/audit run.
No evaluator or auditor may:
- skip research because the artifact looks strong
- disable MCP Ref in fast-track mode
- treat official-doc lookup as optional
## Required Source Order
Each run must collect evidence from:
1. official documentation and standards
2. MCP Ref
3. Context7 when a concrete library/framework is involved
4. web search for current best practices
The coordinator may overlap these lookups with external-agent review, but may not omit them.
## Minimal Completed Research
If the stack is small or the claim surface is narrow, produce a minimal completed research set:
- at least one official-doc or standard source
- at least one MCP Ref lookup
- at least one best-practice web lookup
Status stays `completed_minimal`, not `skipped`.
## Required Output Shape
Research workers should return compact evidence cards with:
- `topic`
- `source_type`
- `source_ref`
- `claim`
- `verdict`
- `impact`
- `actionability`
- `confidence_tier` — one of:
- `tier_1` — official documentation, language spec, RFC (max confidence)
- `tier_2` — established best-practice guide, reputable blog, conference talk
- `tier_3` — community discussion, Stack Overflow, AI-generated content
## Actionability Gate
Before converting research into a finding or edit, ask:
- what concrete defect or risk in the current artifact does this source-backed claim address?
If none, keep it informational and do not inflate issue severity.
## Token Discipline
- gather research once per run when possible
- share normalized evidence to workers through context artifacts
- keep summaries compact and structured
- avoid duplicating long quotations or long prose dumps
**Version:** 1.0.0
**Last Updated:** 2026-04-10
references/evaluation_summary_contract.md
<!-- SOURCE-OF-TRUTH: shared/references/evaluation_summary_contract.md. Edit ONLY here; run `node tools/marketplace/shared.mjs sync` -->
# Evaluation Summary Contract
Machine-readable summary rules for the evaluation platform.
## Envelope
Every summary uses this JSON envelope:
```json
{
"schema_version": "1.0.0",
"summary_kind": "evaluation-worker",
"run_id": "run-id",
"identifier": "story-or-task-id",
"producer_skill": "ln-xxx",
"produced_at": "2026-04-10T10:00:00Z",
"payload": {}
}
```
Allowed coordinator kind: `evaluation-coordinator`. Allowed worker kinds: `evaluation-worker`, `review-research`, `review-findings`, `review-docs`, `review-repair`, `review-merge`, `review-refinement`.
## Worker Payload
Required: `worker`, `status`, `operation`, `warnings`. Optional: `verdict`, `metrics`, `decisions`, `findings`, `artifact_path`, `report_path`, `metadata`, `evidence_basis_counts`.
Findings should be normalized structured objects. Large human-readable reports live in separate artifacts. Research-oriented workers point to source-backed evidence through metrics, findings, or artifact paths instead of duplicating long evidence text.
## Coordinator Payload
Required: `status`, `final_result`, `report_path`, `worker_count`, `issues_total`, `severity_counts`, `warnings`, `cleanup_verified`. Optional: `results_log_path`, `overall_score`, `artifact_path`, `metadata`.
## Paths And Freshness
Managed worker summaries are written under `.hex-skills/runtime-artifacts/runs/{parent_run_id}/evaluation-worker/`. Coordinator summaries are written under `.hex-skills/runtime-artifacts/runs/{run_id}/evaluation-coordinator/`.
At merge time, compare research summary `produced_at` values against `research_freshness_hours` when configured. Stale research adds a warning; it does not auto-invalidate the run.
**Version:** 1.0.0
**Last Updated:** 2026-04-10
references/input_resolution_pattern.md
<!-- SOURCE-OF-TRUTH: shared/references/input_resolution_pattern.md. Edit ONLY here; run `node tools/marketplace/shared.mjs sync` -->
# Input Resolution Pattern
Hard contract for resolving Epic, Story, and Task identifiers when a skill is invoked without complete args.
## Core Rule
Explicit args always win. Auto-detection is only a fallback and must not silently choose from multiple candidates.
## Resolution Chain
For each entity type:
1. **Args:** use the first explicit `epicId`, `storyId`, or `taskId`.
2. **Git branch:** parse current branch name.
3. **Recent commits:** parse `git log --oneline -5`.
4. **Changed files:** match staged/unstaged paths against task docs only if branch/commit produced nothing.
5. **Kanban/provider:** list candidates using the skill's status filter.
6. **Ask user:** required when there are zero or multiple safe candidates.
## ID Patterns
| Pattern | Entity |
|---|---|
| `{TEAM_KEY}-{N}` | Linear issue |
| `US{NNN}` | file-mode Story |
| `T{NNN}` | file-mode Task |
| `epic-{N}` or `Epic-{N}` | file-mode Epic |
## Candidate Rules
- A single candidate from args or exact git ID is safe.
- A single status-filtered kanban candidate may be suggested, but the user must confirm before mutation.
- Multiple candidates must be shown as choices grouped by parent entity.
- A changed-file match is advisory unless exactly one task doc references the changed files.
## Required Evidence
Record the source of the resolved ID in the summary or checkpoint:
```json
{
"resolved_id": "T003",
"entity_type": "task",
"source": "args|branch|commit|changed_files|kanban|user",
"confidence": "exact|suggested|confirmed"
}
```
## Ask Shape
Use concise choices:
- Story: `US001: User Login` with Epic/status in description.
- Task: `T001: DB Schema` with Story/status in description.
- Epic: `Epic 1: Authentication` with status in description.
**Version:** 1.1.0
**Last Updated:** 2026-03-06
references/loop_health_contract.md
<!-- SOURCE-OF-TRUTH: shared/references/loop_health_contract.md. Edit ONLY here; run `node tools/marketplace/shared.mjs sync` -->
# Loop Health Contract
**Version:** 1.0.0
**Last Updated:** 2026-04-25
> **Paths:** All paths are relative to the skills repository root.
## Purpose
Loop health is retry-usefulness evidence. It does not replace lifecycle status, checkpoints, artifacts, task board status, or domain verdicts.
Use it when a procedural skill can repeat a task, worker, stage, scenario segment, advisor session, or quality cycle.
## Model
| Field | Meaning | Owner |
|-------|---------|-------|
| `status` | workflow location now | existing runtime state |
| `artifact` / `checkpoint` | completion evidence | runtime artifact/checkpoint layer |
| `loop_health` | whether another retry can add value | coordinator/orchestrator runtime |
## Signal Classes
| Class | Retry action |
|-------|--------------|
| `none` | continue normally |
| `timeout_idle` | retry only while loop health allows |
| `timeout_productive` | verify/review before retry |
| `permission_denial` | pause immediately |
| `tool_missing` | pause immediately |
| `auth_missing` | pause immediately |
| `rate_limited` | pause/defer; not a domain failure |
| `asked_question` | pause for decision or refine prompt |
| `agent_error` | retry only while loop health allows |
| `unknown` | retry only while loop health allows |
## Default Policy
| Policy | Value |
|--------|-------|
| `no_progress_limit` | `3` |
| `same_error_limit` | `3` |
| immediate pause | `permission_denial`, `tool_missing`, `auth_missing` |
| progress reset | any confirmed objective progress |
Valid progress evidence includes new/changed artifacts, checkpoint deltas, task/status transitions, changed finding sets, accepted repairs, changed metrics, new bottlenecks, or benchmark improvements.
Do not treat repeated prose, identical findings, or unchanged failed assertions as progress.
## Pause Output
When loop health pauses a flow, include scope, reason, evidence key/error signature, and the next operator decision or missing dependency.
## Runtime Requirements
- `loop_health` remains optional so existing runtime state stays readable.
- Runtime history records loop-health updates as `LOOP_HEALTH_RECORDED`.
- Domain counters remain domain counters; loop health measures whether another retry is useful.
- Transport/agent failures must not become domain verdicts without domain evidence.
references/meta_analysis_protocol.md
<!-- SOURCE-OF-TRUTH: shared/references/meta_analysis_protocol.md. Edit ONLY here; run `node tools/marketplace/shared.mjs sync` -->
# Meta-Analysis Protocol
Optional post-run self-audit for skills that need protocol-formatted reflection. Do not load this file by default; load it only when the user asks for meta-analysis, when a command explicitly requires a run retrospective, or when debugging repeated skill failures.
## Use When Requested
Produce only actionable findings from the current run:
- deliverable gaps against the user's goal
- failed, wasted, or repeated tool/agent steps
- worker or subagent failures that changed the result
- concrete skill or command improvements tied to observed evidence
Skip generic SDLC commentary. If there are no findings, write: `Meta-analysis: clean run.`
## Output
```markdown
### Meta-Analysis: {Skill Name}
#### Improvements
| # | Finding | Target | Fix |
|---|---------|--------|-----|
| 1 | {observed issue} | {skill/phase/file} | {specific change} |
#### Session Errors
| Problem Type | Count | Examples |
|--------------|-------|----------|
| {type} | {N} | {brief examples} |
```
Omit empty sections. For subagents, add a separate `#### Subagent Errors: {Agent Name}` table only when that agent had material failures.
## Issue Suggestion Trigger
If the same failure pattern is reproducible across multiple runs, suggest creating an issue with the affected skill, evidence, and expected fix.
---
**Version:** 4.2.0
**Last Updated:** 2026-03-21
references/penalty_points.md
<!-- SOURCE-OF-TRUTH: plugins/agile-workflow/shared/references/penalty_points.md. Edit ONLY here; run `node tools/marketplace/shared.mjs sync` -->
# Penalty Points System
<!-- SCOPE: Calculation rules, report format, and edge cases ONLY. -->
Details that expand on SKILL.md: multiple violations, report format, edge cases.
For severity levels and 30-criteria mapping, see [SKILL.md §Phase 4](../SKILL.md#phase-4-auto-fix) and [phase2_research_audit.md](phase2_research_audit.md).
---
## Calculation Rules
### Multiple Violations per Criterion
Some criteria can have multiple violations (points multiply):
| Criterion | Multiple Violations | Calculation |
|-----------|---------------------|-------------|
| #2 Tasks Structure | Per Task | 1 point * violated_tasks_count |
| #4 Acceptance Criteria | Per missing AC | 3 points * missing_ac_count (max 3x = 9) |
| #9 Story Size | Per issue | 3 points * size_issues_count |
| #16 Story-Task Alignment | Per misaligned Task | 3 points * misaligned_tasks_count (max 3x = 9) |
| #17 AC-Task Coverage | Per uncovered AC | 3 points * uncovered_ac_count (max 3x = 9) |
| #17b AC Invocability | Per invocable AC without concrete mechanism | 5 points * violating_ac_count (uncapped) |
| #17c Scenario Completeness | Per invocable AC with incomplete segments | 5 points * incomplete_ac_count (uncapped) |
| #18 Story Dependencies | Per forward dep | 10 points * forward_dep_count |
| #19 Task Dependencies | Per forward dep | 3 points * forward_dep_count (max 3x = 9) |
| #20 Risk Analysis | Per unmitigated risk | 5 points * risk_count (Priority >= 15) or 3 points (Priority 9-14), max 15 |
| #24 Assumption Registry | Single | 3 points (includes assumption sync sub-check) |
| #25 AC Cross-Story Overlap | Per overlap + cap | 3 points (overlap) or 10 points (conflict); max 1 CRITICAL = 10 points total |
| #26 Task Cross-Story Duplication | Per duplication | 1 point * duplication_count (max 3) |
| #27 Pre-mortem Analysis | Single | 3 points if skipped for complex Story |
| #28 Library Feature Utilization | Single | 3 points (all findings combined into one advisory) |
| Others | Single | Fixed points per criterion |
**Examples:**
- Story has 5 Tasks, 2 violate structure → 1 * 2 = 2 points
- AC missing 2 edge cases → 3 * 2 = 6 points (capped at 9)
- 2 Tasks don't align with Story → 3 * 2 = 6 points (capped at 9)
- 1 Story has forward dependency → 10 * 1 = 10 points
---
## Report Format
### Phase 3 Output (Audit Results)
```
PENALTY POINTS AUDIT
====================
| # | Criterion | Severity | Points | Issue |
|---|-------------------------|----------|--------|--------------------------------|
| 4 | Acceptance Criteria | MEDIUM | 3 | Missing edge case for empty |
| 5 | Standards Compliance | CRITICAL | 10 | No RFC 7231/OWASP compliance |
| 6 | Library & Version | HIGH | 5 | Express v4.17 -> v4.19 |
|17 | AC-Task Coverage | MEDIUM | 3 | AC "Error 401" has no Task |
TOTAL: 21 penalty points
FIX PLAN:
- #4: Add Given/When/Then for empty input case
- #5: Add RFC 7231 error response + OWASP checklist
- #6: Update Express version in Technical Notes
- #17: Add TODO for missing token validation Task
```
### Phase 6 Output (Final Report)
```
VALIDATION COMPLETE
===================
PENALTY POINTS: 21 -> 0
| # | Criterion | Before | After | Fixed |
|---|-------------------------|--------|-------|-------|
| 4 | Acceptance Criteria | 3 | 0 | Yes |
| 5 | Standards Compliance | 10 | 0 | Yes |
| 6 | Library & Version | 5 | 0 | Yes |
|17 | AC-Task Coverage | 3 | 0 | Yes |
TOTAL: 21 -> 0 (100% fixed)
Story approved: Backlog -> Todo
```
---
## Edge Cases
### Zero Violations
```
PENALTY POINTS AUDIT
====================
No violations detected.
TOTAL: 0 penalty points
Story approved: Backlog -> Todo
```
### Maximum Violations
If total > 30 points (max possible: 123+ with all 30 criteria; #17b and #17c are HIGH per AC, uncapped; #20 capped at 15, #25 max 1 CRITICAL = 10), add warning:
```
WARNING: High violation count (42 points)
Consider Story scope review before approval.
```
---
**Version:** 3.0.0
**Last Updated:** 2026-02-07
references/phase2_research_audit.md
<!-- SOURCE-OF-TRUTH: plugins/agile-workflow/shared/references/phase2_research_audit.md. Edit ONLY here; run `node tools/marketplace/shared.mjs sync` -->
# Phase 3: Research & Audit
**Always execute — no exceptions.** Steps 1-5 are mode=story only. Steps 3-4 and criteria #5, #6, #21, #28 apply to ALL modes (story, plan_review).
> **Note:** Domain Extraction and Inline Documentation Creation have moved to Phase 4 (Documentation). This file covers Research & Audit only.
## Step 1: Research via MCP (ALL MODES)
**MANDATORY READ:** Load `references/research_methodology.md`
- Query MCP Ref for industry standards: `ref_search_documentation(query="[topic] RFC OWASP best practices {current_year}")`
- Query Context7 for library versions: `resolve-library-id` + `query-docs`
- Extract: standards (RFC numbers, OWASP rules), library versions, patterns
- **mode=plan_review:** pipeline entry via `references/plan_review_pipeline.md` (Applicability Check → Stack Detection → this step)
## Step 2: Anti-Hallucination Verification (ALL MODES)
**MANDATORY READ:** Load `references/epistemic_protocol.md`
- Scan artifact (Story/Tasks, plan, or reviewed documents) for factual claims across ALL trigger categories (per epistemic protocol Section B):
- Version numbers, API signatures, deprecation claims
- Standards/RFC references, security severity levels
- Market/competitor data, performance characteristics
- For each claim, check evidence from Step 1 research results:
- Has MCP Ref/Context7/WebSearch evidence → mark `VERIFIED`
- No tool evidence but claim is plausible → mark `FROM TRAINING` + add to fix list
- Contradicts tool evidence → mark `FLAGGED` (CRITICAL)
- Note: Step 2 VERIFIES claims against existing research. It does NOT run new searches — new tool queries happen in auto-fix (#6 for story, Compare & Correct for plan_review).
- Status: VERIFIED (all sourced) | FLAGGED (list unverified with trigger category)
## Step 3: Pre-mortem Analysis
**MANDATORY READ:** Load `references/premortem_validation.md`
- Execute for Stories with complexity >= Medium (3+ tasks, external deps, or unfamiliar tech)
- Skip for trivial Stories (1-2 tasks, no external deps, known tech)
- Tigers (evidence-based risks) → feed Risk criterion #20 (add to risk table BEFORE penalty calc)
- Elephants (unstated assumptions) → feed Assumptions criterion #24 (add with [pre-mortem] tag, Confidence=LOW)
- Paper Tigers (fears without evidence) → document and dismiss
- Include pre-mortem table in Phase 3 audit report
## Step 4: Cross-Reference Analysis
**MANDATORY READ:** Load `references/cross_reference_validation.md`
- Skip if Epic has only 1 Story or all siblings Done/Canceled
- Load sibling Stories via `list_issues(project=Epic.id)`
- Check AC overlap (#25): structured traceability first (AC IDs, Affected Components, file paths), keyword fallback advisory-only
- Check task duplication (#26): structured match (Affected Components, file paths) primary
- Include cross-reference findings in Phase 3 audit report
## Step 5: Penalty Points Calculation
- Evaluate all 30 criteria against Story/Tasks (see Auto-Fix Actions Reference below)
- Assign penalty points per violation (CRITICAL=10, HIGH=5, MEDIUM=3, LOW=1)
- Calculate total penalty points
- Build fix plan for each violation
# Auto-Fix Actions Reference
Detailed criteria table for Phase 4 auto-fix execution and Phase 3 penalty calculation.
## Structural (#1-#4, #24)
| # | Criterion | What it checks | Penalty | Auto-fix actions |
|---|-----------|----------------|---------|------------------|
| 1 | Story Structure | 9 sections per template | LOW (1) | Add/reorder sections with TODO placeholders; update Linear |
| 2 | Tasks Structure | Each Task has 7 sections | LOW (1) | Load each Task; add/reorder sections; update Linear |
| 3 | Story Statement | As a/I want/So that clarity | LOW (1) | Rewrite using persona/capability/value; update Linear |
| 4 | Acceptance Criteria | Given/When/Then, 3-5 items | MEDIUM (3) | Normalize to G/W/T; add edge cases; update Linear |
| 24 | Assumption Registry | Assumptions section with >=1 typed entry; each has Category, Confidence, Invalidation Impact; LOW confidence entries have validation plan in Tasks; Inherited Assumptions in child Tasks match parent Story registry (ID exists + text matches) | MEDIUM (3) | Scan Technical Notes for implicit assumptions (keywords: "assumes", "expects", "requires", "available"); populate table; verify assumption sync in Tasks |
## Standards (#5) — ALL MODES
| # | Criterion | What it checks | Penalty (story) | Auto-fix: story | Auto-fix: plan_review |
|---|-----------|----------------|-----------------|-----------------|------------------------|
| 5 | Standards Compliance | Each technical decision references specific RFC/OWASP/REST standard by number | CRITICAL (10) | Query MCP Ref; update Technical Notes with compliant approach | Query MCP Ref; add inline `"(per {RFC}: ...)"` to artifact |
## Solution (#6, #21, #28) — ALL MODES
| # | Criterion | What it checks | Penalty (story) | Auto-fix: story | Auto-fix: plan_review |
|---|-----------|----------------|-----------------|-----------------|------------------------|
| 6 | Library & Version | Libraries are latest stable | HIGH (5) | Query Context7; update to recommended versions | Query Context7; correct version in artifact + deprecation note |
| 21 | Alternative Solutions | Chosen approach optimal vs modern alternatives; cross-ref ln-645 audit if `docs/project/.audit/ln-640/*/645-open-source-replacer*.md` available (glob across dates, take latest) | MEDIUM (3) | Search MCP Ref + web; add "Alternative Considered" note to Technical Notes. If ln-645 + HIGH-confidence → advisory note | Search MCP Ref; add "Alternative Considered" note to artifact if better option found |
| 28 | Library Feature Utilization | Custom code duplicates features of declared dependencies | MEDIUM (3) | Read manifest + Context7 (max 3); add advisory to Task Technical Approach | Read manifest + Context7; add advisory "built-in {feature} available" to artifact |
> **Mode differences:** In mode=story, violations accumulate penalty points (Phase 3 Step 7) and are fixed in Phase 4. In mode=plan_review, no penalty points — corrections are applied directly per `references/plan_review_pipeline.md` Compare & Correct Safety Rules (max 5 corrections).
## Workflow (#7-#13)
| # | Criterion | What it checks | Penalty | Auto-fix actions |
|---|-----------|----------------|---------|------------------|
| 7 | Test Strategy | Section exists but empty | LOW (1) | Ensure section present; leave empty (testing handled separately) |
| 8 | Documentation Integration | No standalone doc tasks | MEDIUM (3) | Remove doc-only tasks; fold into implementation DoD |
| 9 | Story Size | 1-8 tasks (3-5 optimal); 3-5h each | MEDIUM (3) | If >8, add TODO; flag task size issues |
| 10 | Test Task Cleanup | No premature test tasks | MEDIUM (3) | Remove test tasks before final; testing appears later |
| 11 | YAGNI | Each Task maps to ≥1 Story AC; no tasks without AC justification | MEDIUM (3) | Move speculative items to Out of Scope unless standards require |
| 12 | KISS | No task requires >3 new abstractions; if >3 → split or simplify | MEDIUM (3) | Simplify unless standards require complexity |
| 13 | Task Order | DB→Service→API→UI | MEDIUM (3) | Reorder Tasks foundation-first |
## Quality (#14-#15)
| # | Criterion | What it checks | Penalty | Auto-fix actions |
|---|-----------|----------------|---------|------------------|
| 14 | Documentation Complete | Pattern docs exist + referenced | HIGH (5) | Create inline per documentation_creation.md; add all doc links to Technical Notes |
| 15 | Code Quality Basics | No hardcoded values | MEDIUM (3) | Add TODOs for constants/config/env |
## Traceability (#16-#17, #17b-#17c)
| # | Criterion | What it checks | Penalty | Auto-fix actions |
|---|-----------|----------------|---------|------------------|
| 16 | Story-Task Alignment | Each Task title contains keyword from Story AC; grep verification | MEDIUM (3) | Add TODO to misaligned Tasks; warn user |
| 17 | AC-Task Coverage | Coverage matrix: each AC row has ≥1 Task; no empty rows | MEDIUM (3) | Add TODO for uncovered ACs; suggest missing Tasks |
| 17b | AC Invocability | Every AC where an actor must invoke/consume a mechanism has a covering Task whose Implementation Plan names a concrete mechanism (MCP tool, API endpoint, CLI command, UI component, chat handler, config file, system prompt section, cron handler). Infrastructure-only tasks do NOT satisfy ACs requiring something to *use* that infrastructure. Vague mechanism = violation | HIGH (5) per AC | For each violating AC — identify missing mechanism, either (a) add to existing task's Implementation Plan with explicit section, or (b) flag that a new task is needed for the consuming layer. Update Linear |
| 17c | Scenario Completeness | For each AC where an actor must invoke/consume a mechanism, covering task(s) must collectively address all 5 segments: (1) Actor trigger, (2) Entry point (named mechanism from #17b), (3) Discovery (how actor's system finds/loads mechanism at runtime), (4) Usage context (what actor's system needs to correctly invoke mechanism), (5) Observable outcome. Missing segment = violation | HIGH (5) per AC | For each violating AC — identify missing segment(s), either (a) add to existing task's Implementation Plan, or (b) flag that covering task needs a "Scenario Integration" section. Update Linear |
## Dependencies (#18-#19)
| # | Criterion | What it checks | Penalty | Auto-fix actions |
|---|-----------|----------------|---------|------------------|
| 18 | Story Dependencies | No forward Story dependencies | CRITICAL (10) | Flag forward dependencies; suggest reorder |
| 19 | Task Dependencies | No forward Task dependencies | MEDIUM (3) | Flag forward dependencies; reorder Tasks |
## Cross-Reference (#25-#26)
| # | Criterion | What it checks | Penalty | Auto-fix actions |
|---|-----------|----------------|---------|------------------|
| 25 | AC Cross-Story Overlap | Story AC doesn't overlap/conflict with active sibling Stories in same Epic | MEDIUM (3) / CRITICAL (10), max 1 CRITICAL | Structured traceability first (AC IDs, Affected Components, file paths); keyword overlap as advisory fallback; conflict (same Given/When + different Then) → CRITICAL |
| 26 | Task Cross-Story Duplication | Tasks don't duplicate sibling Stories' tasks | LOW (1), max 3 | Structured match (Affected Components, file paths) primary; title keyword overlap advisory; human decides |
## Risk (#20)
| # | Criterion | What it checks | Penalty | Auto-fix actions |
|---|-----------|----------------|---------|------------------|
| 20 | Risk Analysis | Unmitigated implementation risks (architecture, errors, scalability, data integrity, integration, SPOF) | HIGH (5) per risk, max 15 | Score via Impact x Probability matrix; add TODO sections for Priority 15-19; FLAG for human review at Priority >= 20; skip at Priority <= 8 |
## Verification Methods (#22)
| # | Criterion | What it checks | Penalty | Auto-fix actions |
|---|-----------|----------------|---------|------------------|
| 22 | AC Verify Methods | Every task AC has `verify:` method (test/command/inspect); at least 1 non-inspect per task | MEDIUM (3) | Generate `verify:` methods based on AC content: HTTP endpoints → command, DB operations → inspect, business logic → test; update Linear |
## AI-Readiness (#23)
| # | Criterion | What it checks | Penalty | Auto-fix actions |
|---|-----------|----------------|---------|------------------|
| 23 | Architecture Considerations Complete | Story has: layers affected, side-effect boundary, orchestration depth | MEDIUM (3) | Add Architecture Considerations section from story_template.md with placeholder fields; update Linear |
## Pre-mortem (#27)
| # | Criterion | What it checks | Penalty | Auto-fix actions |
|---|-----------|----------------|---------|------------------|
| 27 | Pre-mortem Analysis | Pre-mortem with Tiger/Paper Tiger/Elephant classification (complex Stories) | MEDIUM (3) | Execute algorithm from premortem_validation.md; Tigers → risk #20; Elephants → Assumptions #24 [pre-mortem] |
**Maximum Penalty:** 123+ points (sum of all 30 criteria; #17b and #17c are HIGH per AC, uncapped; #20 capped at 15; #25 max 1 CRITICAL = 10)
---
**Version:** 1.0.0
**Last Updated:** 2026-02-14
references/plan_review_pipeline.md
<!-- SOURCE-OF-TRUTH: plugins/agile-workflow/shared/references/plan_review_pipeline.md. Edit ONLY here; run `node tools/marketplace/shared.mjs sync` -->
# Plan Review Pipeline (mode=plan_review)
Pipeline orchestration for MCP Ref research. Runs in parallel with agent background tasks.
Criteria definitions: `references/phase2_research_audit.md` (criteria #5, #6, #21, #28 + Anti-Hallucination — sections marked "ALL MODES").
## Applicability Check
Scan plan content for technology decision signals. No signals → skip MCP Ref research, proceed to Phase 5.
| Signal Type | Examples |
|-------------|---------|
| Infrastructure choice | Redis, PostgreSQL, K8s, Docker, RabbitMQ |
| API/protocol decision | REST vs GraphQL, WebSocket, gRPC, OAuth 2.0 |
| Security mechanism | JWT, PKCE, CORS, rate limiting, OWASP |
| Library/framework choice | FastAPI, Polly, SQLAlchemy, Pydantic |
| Architectural pattern | CQRS, event sourcing, middleware chain, DI |
| Configuration/tooling | ESLint, Prettier, CI config |
## Stack Detection
Priority order for `query_prefix`:
1. Plan content (technology mentions) → use directly
2. `.hex-skills/environment_state.json` research section → extract stack hints
3. Glob for indicator files:
| Indicator | Stack | Query Prefix |
|-----------|-------|--------------|
| `*.csproj`, `*.sln` | .NET | `"C# ASP.NET Core"` |
| `package.json` + `tsconfig.json` | Node.js | `"TypeScript Node.js"` |
| `requirements.txt`, `pyproject.toml` | Python | `"Python"` |
| `go.mod` | Go | `"Go Golang"` |
| `Cargo.toml` | Rust | `"Rust"` |
| `build.gradle`, `pom.xml` | Java | `"Java"` |
4. Parse plan references for technology mentions (fallback heuristic)
## Research Execution
Apply criteria #5, #6, #21, #28 from `references/phase2_research_audit.md` (see "Auto-fix: plan" column):
1. For each extracted topic, run queries per criterion
2. Anti-Hallucination (Step 4 from phase2_research_audit.md) — verify factual claims in artifact
3. Each finding → CORRECTED / VALIDATED / REVIEW NEEDED
## Compare & Correct Safety Rules
1. **Max 5 corrections** per run
2. **Must cite** specific RFC/standard/doc for each correction
3. **Only correct** when official docs **directly contradict** plan statement (high confidence)
4. Each correction = surgical Edit with inline rationale `"(per {RFC/standard}: ...)"`
5. Ambiguous findings → record as `"REVIEW NEEDED"` (not auto-corrected)
references/premortem_validation.md
<!-- SOURCE-OF-TRUTH: plugins/agile-workflow/shared/references/premortem_validation.md. Edit ONLY here; run `node tools/marketplace/shared.mjs sync` -->
# Pre-mortem Validation (Criterion #27)
<!-- SCOPE: Pre-mortem analysis criterion #27 ONLY. Contains Tiger/Paper Tiger/Elephant classification, evidence test, actions. -->
<!-- DO NOT add here: Risk categories → risk_validation.md, assumptions → structural_validation.md (#24) -->
Detailed rules for pre-mortem analysis of Story risks and unstated assumptions.
---
## Criterion #27: Pre-mortem Analysis
**Check:** Story has been analyzed for hidden risks (Tigers) and unstated assumptions (Elephants)
**Penalty:** MEDIUM (3 points)
**Skip When:**
- Story complexity < Medium (1-2 tasks, no external deps, known tech)
- Story in Done/Canceled status
---
## Pre-mortem Algorithm
### Step 1: Read Story (all 9 sections)
Load complete Story description including Technical Notes, Dependencies, AC, and Assumptions.
### Step 2: Failure Imagination
Prompt: **"Imagine this Story failed completely during implementation. What went wrong?"**
Systematically check 4 domains for unstated assumptions:
| Domain | Questions |
|--------|-----------|
| **Infrastructure** | Does the required infra exist? Is it configured? Do we have access? |
| **Data** | Is data in expected format? Volume within limits? Quality sufficient? |
| **External** | Will external APIs behave as expected? SLAs hold? Auth work? |
| **Scope** | Is anything implicitly excluded that should be explicit? Hidden requirements? |
### Step 3: Evidence Test & Classification
For each identified concern, apply the evidence test:
| Type | Definition | Evidence Test | Action |
|------|-----------|---------------|--------|
| **Tiger** | Real risk with concrete evidence | Specific technical constraint, known limitation, documented issue | → Add to Risk criterion #20 as new risk item |
| **Paper Tiger** | Fear without evidence | "What if..." without data, hypothetical scenario, no concrete constraint | → Document in pre-mortem table and dismiss |
| **Elephant** | Unstated assumption everyone relies on | "We assumed this would...", implicit dependency, unspoken prerequisite | → Add to Assumptions #24 with `[pre-mortem]` tag, Confidence=LOW |
### Step 4: Output
Generate pre-mortem table for Phase 3 audit report:
```markdown
## Pre-mortem Analysis
| # | Concern | Type | Evidence | Action |
|---|---------|------|----------|--------|
| 1 | Redis not available in staging | Tiger | Ops confirmed no Redis in staging env | → Risk #20: add staging env risk |
| 2 | "What if API rate limit changes?" | Paper Tiger | No indication of change; current limit documented | Dismissed |
| 3 | Assumes PostgreSQL supports JSONB | Elephant | Not verified in current DB version | → Assumption A3 [pre-mortem], LOW |
```
---
## Scoring
- Pre-mortem executed AND table present → PASS (0 points)
- Pre-mortem skipped for complex Story (≥3 tasks, external deps, unfamiliar tech) → MEDIUM (3 points)
- Pre-mortem not needed (simple Story) → PASS (skip, 0 points)
---
## Feed-forward Rules
Pre-mortem runs in **Step 5** (before Penalty Calculation in Step 7), so its outputs feed into scoring:
| Output | Target | How |
|--------|--------|-----|
| Tigers | Risk #20 | Add as new risk with Impact x Probability scoring |
| Elephants | Assumptions #24 | Add with `[pre-mortem]` tag, Category from domain, Confidence=LOW |
| Paper Tigers | Audit report only | Document for transparency, no penalty impact |
**Important:** Tigers and Elephants discovered here are scored by their target criteria (#20, #24) in Step 7. The pre-mortem criterion #27 itself only checks whether the analysis was performed.
---
## Auto-fix Actions
1. **Missing pre-mortem (complex Story):**
- Execute pre-mortem algorithm (Steps 1-4)
- Generate pre-mortem table
- Feed Tigers → #20, Elephants → #24
- Add table to Story audit report
2. **Pre-mortem already present:**
- Verify classifications are correct (evidence test)
- Verify feed-forward actions were taken (Tigers in #20, Elephants in #24)
---
## Examples
### Example 1: API Integration Story (Complex)
```markdown
## Pre-mortem Analysis
| # | Concern | Type | Evidence | Action |
|---|---------|------|----------|--------|
| 1 | Stripe API v2 deprecation in Q3 | Tiger | Stripe changelog announces v2 sunset | → Risk #20: migration timeline risk |
| 2 | "What if payment fails mid-checkout?" | Paper Tiger | Already handled by AC error scenarios | Dismissed |
| 3 | Assumes webhook endpoint is publicly accessible | Elephant | No infra verification done | → A4 DEPENDENCY [pre-mortem], LOW |
| 4 | Assumes test Stripe keys available | Elephant | Dev env config not checked | → A5 FEASIBILITY [pre-mortem], LOW |
```
### Example 2: Simple CRUD Story (Skip)
Story: "Add user profile edit form" — 2 tasks, no external deps, known React patterns.
→ Pre-mortem skipped (complexity < Medium). PASS.
---
## Execution Order
**Step 5 in Phase 3** (before Penalty Calculation):
```
Step 1: Domain Extraction
Step 2: Documentation Delegation
Step 3: Research via MCP
Step 4: Anti-Hallucination Verification
→ Step 5: Pre-mortem Analysis
- Classify Tigers/Paper Tigers/Elephants
- Feed Tigers → Risk #20
- Feed Elephants → Assumptions #24
Step 6: Cross-Reference Analysis (#25-#26)
→ Step 7: Penalty Calculation (includes #20 with Tigers, #24 with Elephants)
```
**Rationale:** Pre-mortem must run before penalty calculation so that discovered risks and assumptions are included in the scoring pass. Running after would create a back-edge requiring a second scoring pass.
---
## Integration with Other Criteria
**Criterion #20 (Risk Analysis):**
- #20 checks documented risks in Technical Notes
- #27 discovers NEW risks via pre-mortem imagination
- Tigers from #27 are added to #20's risk inventory before scoring
**Criterion #24 (Assumption Registry):**
- #24 checks Assumptions section completeness
- #27 discovers UNSTATED assumptions (Elephants)
- Elephants from #27 are added to #24's table with [pre-mortem] tag
**Criterion #5 (Standards Compliance):**
- #5 checks RFC/OWASP references
- #27 may discover that compliance was assumed but not verified
- Complementary: #27 surfaces assumptions, #5 verifies them
---
**Version:** 1.0.0
**Last Updated:** 2026-03-08
references/quality_validation.md
<!-- SOURCE-OF-TRUTH: plugins/agile-workflow/shared/references/quality_validation.md. Edit ONLY here; run `node tools/marketplace/shared.mjs sync` -->
# Quality Validation (Criteria #14-#15)
<!-- SCOPE: Documentation and code quality criteria #14-#15 ONLY. Contains completeness checks, hardcoded values rules. -->
<!-- DO NOT add here: Other criteria → structural_validation.md, workflow_validation.md, penalty system → penalty_points.md -->
Detailed rules for documentation completeness and code quality (no hardcoded values).
---
## Criterion #14: Documentation Complete
**Check:** All relevant docs from Phase 3 research are referenced in Story
**Penalty:** HIGH (5 points)
**What it checks:**
- Guides/Manuals/ADRs created in Phase 3 are linked in Technical Notes
- Pattern-specific documentation exists and referenced
- Technical Notes contain links to relevant docs
**GOOD:**
```markdown
## Technical Notes
### Architecture Considerations
- **REST API:** Resource-based URLs (see [Guide-05: RESTful API Patterns](docs/guides/05-rest-api-patterns.md))
- **Rate Limiting:** 100 req/min per IP (see [Guide-06: API Rate Limiting](docs/guides/06-api-rate-limiting.md))
### Integration Points
- **OAuth 2.0:** oauth2-proxy v7.6.0 (see [Manual: oauth2-proxy v7](docs/manuals/oauth2-proxy-v7.md))
- **Architecture Decision:** See [ADR-003: Authentication Strategy](docs/adrs/003-auth-strategy.md)
### Related Documentation
| Document | Path |
|----------|------|
| oauth2-proxy Manual | docs/manuals/oauth2-proxy-v7.md |
| REST API Guide | docs/guides/05-rest-api-patterns.md |
| Auth Strategy ADR | docs/adrs/003-auth-strategy.md |
```
**BAD:**
```markdown
## Technical Notes
We'll add OAuth authentication and REST API endpoints.
(No references to guides/manuals/ADRs/research created in Phase 3)
```
**Auto-fix actions:**
1. Check if documentation exists from Phase 3 (created inline)
2. For EACH keyword in Technical Notes (auth, database, api, error, logging):
- IF doc exists -> Add reference to Technical Notes
- IF doc missing -> Create per documentation_creation.md or add MCP Ref reference
3. Add "Related Documentation" subsection with all doc links
4. Update Linear issue via `save_issue`
5. Add comment: "Documentation references added - [list of docs]"
**Pattern Examples:**
- **OAuth/Auth:** Manual (library v[version]) + ADR (Authentication Strategy)
- **REST API:** Guide (RESTful API Patterns)
- **Database:** Manual (ORM/library version)
- **Error Handling:** Guide (Error Response Patterns RFC 7807)
**Skip Fix When:**
- No relevant guides exist yet
- All docs already referenced in Technical Notes
- Story in Done/Canceled status
---
## Criterion #15: No Hardcoded Values (TODO Placeholders)
**Check:** No hardcoded credentials, API keys, or config values in Story
**Penalty:** MEDIUM (3 points)
**GOOD:**
```markdown
## Technical Notes
### Configuration
- Database URL: `_TODO: Add DATABASE_URL to .env_`
- OAuth Client ID: `_TODO: Register app, add OAUTH_CLIENT_ID to .env_`
- API Key: `_TODO: Generate key, add API_KEY to .env_`
```
**BAD:**
```markdown
## Technical Notes
### Configuration
- Database URL: `postgresql://localhost:5432/mydb` <- Hardcoded
- OAuth Client ID: `abc123xyz` <- Hardcoded
- API Key: `sk_test_4eC39HqLyjWDarjtT1zdp7dc` <- Hardcoded
```
**Auto-fix actions:**
1. Grep Technical Notes for patterns: URLs, keys, credentials
- Database URLs: `postgresql://`, `mysql://`, `mongodb://`
- API keys: `sk_`, `pk_`, `Bearer`, `token=`
- Credentials: `password=`, `secret=`
2. IF hardcoded value found -> Replace with `_TODO: Add [NAME] to .env_`
3. Add Security section if missing:
```markdown
### Security
All sensitive values stored in environment variables (never committed to git)
```
4. Update Linear issue via `save_issue`
5. Add comment: "Hardcoded values replaced with .env placeholders"
**Skip Fix When:**
- Values are examples/placeholders (clearly marked as `example.com`, `<YOUR_KEY>`)
- Story in Done/Canceled status
---
## Execution Notes
**Sequential Dependency:**
- Criteria #14-#15 depend on #1-#13 being completed first
- Cannot add doc references (#14) until structure exists (#1)
- Cannot check hardcoded values (#15) until Technical Notes exist
**Research Integration:**
- Phase 3 creates documentation inline per references/documentation_creation.md
- Criteria #14-#15 read from Phase 3 docs
- All research completed BEFORE Phase 4 auto-fix begins
**Linear Updates:**
- Each criterion auto-fix updates Linear issue once
- Add single comment summarizing ALL fixes in this category
---
**Version:** 3.0.0
**Last Updated:** 2025-01-07
references/research_methodology.md
<!-- SOURCE-OF-TRUTH: shared/references/research_methodology.md. Edit ONLY here; run `node tools/marketplace/shared.mjs sync` -->
# Research Methodology
<!-- SCOPE: Standardized research methodology for MCP Ref/Context7 research. Source credibility, domain standards, quality criteria. -->
<!-- DO NOT add here: documentation creation → documentation_creation.md, tool fallback chain → research_tool_fallback.md -->
Unified research methodology for all skills performing MCP Ref / Context7 research.
---
## Source Credibility Hierarchy
| Priority | Source Type | Example | When to Use |
|----------|-------------|---------|-------------|
| **1** | Official documentation | Python.org, FastAPI docs, RFC specifications | ALWAYS prefer official docs |
| **2** | Industry standards | RFC 6749 (OAuth), OpenAPI 3.0 spec, OWASP guidelines | For protocol/standard compliance |
| **3** | Vendor documentation | AWS docs, Redis docs, PostgreSQL docs | For specific vendor implementations |
| **4** | Community standards | PEP (Python), JSR (Java), WCAG (accessibility) | For language/platform best practices |
| **5** | Authoritative blogs | Real Python, DigitalOcean tutorials, vendor blogs | For complex integration examples |
| **6** | Stack Overflow | Accepted answers with high votes (500+) | LAST RESORT - verify info elsewhere |
**Red Flags (avoid):**
- Blog posts > 2 years old (outdated patterns)
- Personal blogs without credentials
- Medium posts without verification
- Reddit/forum posts (use for direction only)
---
## Standards Compliance by Domain
| Domain | Relevant Standards |
|--------|-------------------|
| **Authentication** | OAuth 2.0 (RFC 6749), OpenID Connect, JWT (RFC 7519) |
| **REST API** | OpenAPI 3.0, REST principles (RFC 7231), HATEOAS |
| **Security** | OWASP Top 10, NIST guidelines, CSP (Content Security Policy) |
| **Data formats** | JSON Schema, Protocol Buffers, Avro |
| **Protocols** | HTTP/2 (RFC 7540), WebSocket (RFC 6455), gRPC |
| **Accessibility** | WCAG 2.1, ARIA, Section 508 |
---
## Version Selection Guidelines
| Scenario | Preferred Version | Rationale |
|----------|-------------------|-----------|
| **Production projects** | Latest LTS (Long Term Support) | Stability + security updates |
| **New features** | Latest stable release | Modern APIs, avoid beta/RC |
| **Legacy projects** | Match existing version (upgrade path in separate Story) | Avoid breaking changes |
| **Experimental** | Latest (including RC) | ONLY if Epic explicitly requests bleeding edge |
**Version notation:** Use `"v3.12.1 (LTS)"` or `"v2.5.0 (stable)"`. Never `"latest"` or `"v3.x"`.
**Unsupported API check:** If library has unsupported methods, list in "Key constraints". If library is end-of-life, suggest alternatives.
---
## Key APIs Extraction
**Focus on 2-5 MOST RELEVANT methods for the Story domain.**
**Extraction rules:**
1. Include method signature (parameters, return type if critical)
2. Explain WHEN to use (not just WHAT it does)
3. Prioritize methods for Story domain (not all library methods)
4. If >5 methods, group by category (CRUD, validation, utilities)
---
## Constraints & Limitations
**MUST document:** async/sync support, storage backends, multi-process caveats, platform limitations, performance limitations.
**Format:**
```
**Key constraints:**
- [Limitation]: [Brief explanation] - [Workaround or solution]
```
---
## Research Methodology by Type
| Type | Focus | Primary Sources | Key Questions |
|------|-------|-----------------|---------------|
| **Technical** | Solution comparison | Docs, benchmarks, RFCs | "Which solution fits our use-case?" |
| **Market** | Industry landscape | Reports, blogs, articles | "What's the market size/trend?" |
| **Competitor** | How others solve it | Product pages, reviews, demos | "What features do competitors offer?" |
| **Requirements** | User needs | Feedback, support tickets, forums | "What do customers complain about?" |
| **Feasibility** | Can we build it? | PoC, prototypes, local tests | "Is it technically possible?" |
| **Feature Demand** | Feature viability | Competitor features + blogs/socials + user complaints | "Is this feature worth building?" |
---
## Research Summary Template
```markdown
## Library Research
**Primary libraries:**
| Library | Version | Purpose | Docs |
|---------|---------|---------|------|
| [name] | v[X.Y.Z] ([stable/LTS]) | [purpose] | [URL] |
**Key APIs:**
- `method(params)` - [when to use]
**Key constraints:**
- [Limitation] - [Workaround]
**Standards compliance:**
- [Standard/RFC]: [How to comply]
**Existing guides:**
- [path] - [description]
```
---
## Quality Checklist
Before returning Research Summary, verify:
- [ ] All libraries have specific versions (not "latest")
- [ ] Key APIs (2-5 methods) include when to use (not just what)
- [ ] Constraints list workarounds or solutions
- [ ] Standards compliance includes HOW to comply (not just standard name)
- [ ] Official docs URLs are valid (not broken links)
- [ ] Research Summary is <=500 words (concise, actionable)
---
## Time Management
**Time-box: 10-15 minutes maximum per Epic**
| Phase | Time |
|-------|------|
| Identify | 1-2 minutes |
| Context7 | 3-5 minutes (parallel calls) |
| MCP Ref | 3-5 minutes (parallel calls) |
| Guides | 1-2 minutes |
| Summary | 2-3 minutes |
**If time exceeds:** Reduce library count (focus on 2-3 primary), skip fallback WebSearch, use cached Ref results from previous Epics.
---
**Version:** 1.0.0
**Last Updated:** 2026-03-20
references/research_tool_fallback.md
<!-- SOURCE-OF-TRUTH: shared/references/research_tool_fallback.md. Edit ONLY here; run `node tools/marketplace/shared.mjs sync` -->
# Research Tool Fallback
<!-- SCOPE: Runtime fallback chain for documentation/standards research. -->
## Fallback Chain
Read `.hex-skills/environment_state.json` -> `research`, execute configured tools in priority order, and stop at the first successful result:
| Priority | Tool | Condition | Trust |
|----------|------|-----------|-------|
| 1 | `mcp__Ref__ref_search_documentation` | provider includes `ref` | High |
| 2 | `mcp__context7` | provider includes `context7` and query is library-specific | High |
| 3 | `WebSearch` | current or broad web research needed | Medium |
| 4 | `WebFetch` | specific URL known | Medium |
| 5 | Built-in knowledge | all tools fail | Low; say it may be outdated |
**MANDATORY READ:** Load `references/epistemic_protocol.md` for source marking.
## Runtime Rules
1. Try configured tools in priority order.
2. On tool error, warn once, mark that tool unavailable for the session, and continue.
3. If every tool fails, use built-in knowledge only with an explicit freshness disclaimer.
---
**Version:** 2.0.0
**Last Updated:** 2026-04-05
references/researchgraph_mcp_usage.md
<!-- SOURCE-OF-TRUTH: shared/references/researchgraph_mcp_usage.md. Edit ONLY here; run `node tools/marketplace/shared.mjs sync` -->
# Research Graph MCP Usage
<!-- SCOPE: Routing policy for using hex-research MCP against hypothesis, goal, evidence, and benchmark graphs. -->
## Applicability
Use `hex-research` only when project evidence can change planning, validation, readiness, scope, or priority and the project has at least one canonical graph path: `docs/hypotheses/*.md`, `docs/goals/*.md`, or `benchmark/runs/*/manifest.yaml`.
Do not use it for code symbol identity, references, architecture, or edit blast radius; those remain `hex-graph` concerns.
## Safety Rules
- Prefer read-only query/audit tools first.
- Start with `verify_index({ path })`.
- Run `index_hypotheses({ path })` only when the index is missing, stale, explicitly requested, or current graph state is required.
- Treat `STALE` as graph debt, not tool failure.
- Treat `INVALID` from `verify_index` as diagnostic state; `INVALID` from `index_hypotheses` means rebuild failed.
- Goal `metrics_current` is derived from explicit comprehensive run manifests; manual `metrics_current` in goal frontmatter is drift.
- Source quality is derived from `sources`, optional `docs/sources/lib.yaml`, source type inference, and weighted `evidence_depth`.
- Run `export_canvas` and `export_research_map` with `dry_run: true` before writing.
- If MCP is unavailable, read split markdown/manifests manually and mark confidence degraded.
## Intent Routing
| Intent | Tool |
|--------|------|
| freshness | `verify_index` |
| rebuild/first index | `index_hypotheses` |
| hypothesis search/status | `find_hypotheses` |
| inspect `H##` / `G##` | `inspect_hypothesis` / `inspect_goal` |
| evidence, citations, benchmark runs | `find_evidence`, `find_runs` |
| lineage, graph shape, goal tree | `trace_lineage`, `analyze_topology`, `trace_goal_tree` |
| gaps, drift, readiness | `audit_orphans`, `audit_goal_alignment`, `analyze_progress`, `analyze_proposed` |
| goal metric drift / missing comprehensive metrics | `inspect_goal`, `audit_goal_alignment` |
| source quality / evidence depth | `inspect_hypothesis`, `find_evidence`, `analyze_proposed` |
| field-level diff | `analyze_progress` |
| visual map / generated research-map.md | `export_canvas`, `export_research_map` |
## Integration Rules
- Planning/review skills use graph evidence only when H/G IDs, benchmark runs, readiness, or changed researchgraph files affect the decision.
- External-doc research still prioritizes official/current sources; graph evidence is local preflight only.
- Task planning keeps `hex-graph` for code modules and uses `hex-research` only for hypothesis/task readiness and proposal status.
- Manual fallback reads frontmatter in hypotheses/goals, narrowed benchmark manifests, and wiki links by direct file lookup; label lineage/topology/drift as manual approximations.
---
**Version:** 0.1.0
**Last Updated:** 2026-05-08
references/risk_based_testing_guide.md
<!-- SOURCE-OF-TRUTH: shared/references/risk_based_testing_guide.md. Edit ONLY here; run `node tools/marketplace/shared.mjs sync` -->
# Risk-Based Testing Contract
Small mandatory contract for deciding which tests are worth creating or auditing. Load `risk_based_testing_methodology.md` only when a skill needs full planning methodology or anti-pattern examples.
## Principle
Write tests for business risk and production confidence, not coverage targets or branch counts.
Baseline for a story:
- E2E positive scenario for the main user value.
- E2E negative scenario for the critical error path.
- Add integration/unit tests only when they cover risk not already proven by E2E.
## Priority Formula
```text
Priority = Business Impact (1-5) * Probability of Failure (1-5)
```
| Priority | Action |
|---|---|
| `15-25` | must test or explicitly justify no automation |
| `9-14` | should test when not covered by existing E2E/manual evidence |
| `1-8` | skip automation; manual evidence is sufficient |
Impact scoring:
- `5`: money loss, security breach, data corruption, legal/legal-compliance risk.
- `4`: core business flow broken.
- `3`: feature partially broken.
- `2`: minor UX or non-critical behavior.
- `1`: cosmetic/trivial.
Probability scoring:
- `5`: complex algorithm, external API, new technology, no tests.
- `4`: multiple dependencies, concurrency, state management.
- `3`: standard CRUD or common integration.
- `2`: simple logic or established library path.
- `1`: trivial assignment or generated/framework behavior.
## Usefulness Gate
Every additional test beyond baseline must pass all checks:
| Check | Required answer |
|---|---|
| Risk | priority is at least `15`, or `9-14` with clear uncovered value |
| Business logic | tests our behavior, not framework/database/library behavior |
| Non-duplicative | not already covered by E2E, integration, or manual evidence |
| Predictive | passing test increases production confidence |
| Specific | failure points to a clear cause |
| Maintainable | confidence value exceeds maintenance cost |
If any answer fails, skip the test or record manual validation instead.
## Test Level Selection
- Use E2E for observable user value and story acceptance.
- Use integration only when cross-component behavior is not covered by E2E.
- Use unit only for complex custom business algorithms.
- Do not create unit tests for simple CRUD, wrappers, getters, framework hooks, ORM calls, library calls, or trivial conditionals.
## Strictness Rules
- Prefer exact assertions when expected values are known.
- Use non-default configurable values in tests: ports, timeouts, limits, feature flags, base URLs.
- A new failing test is specification evidence; investigate product code before weakening assertions.
## Output Evidence
When planning or auditing tests, record:
```json
{
"scenario": "checkout payment failure",
"impact": 5,
"probability": 4,
"priority": 20,
"decision": "e2e|integration|unit|manual|skip",
"justification": "uncovered money-loss path"
}
```
**Version:** 2.1.0
**Last Updated:** 2026-01-15
references/risk_validation.md
<!-- SOURCE-OF-TRUTH: plugins/agile-workflow/shared/references/risk_validation.md. Edit ONLY here; run `node tools/marketplace/shared.mjs sync` -->
# Risk Validation (Criterion #20)
<!-- SCOPE: Implementation risk analysis criterion #20 ONLY. Contains risk categories, Impact x Probability scoring, auto-fix rules. -->
<!-- DO NOT add here: Testing risks -> risk_based_testing_guide.md, dependencies -> dependency_validation.md, security standards -> standards_validation.md -->
Detailed rules for implementation risk analysis in Story/Tasks.
---
## Criterion #20: Risk Analysis
**Check:** Story/Tasks identify and mitigate implementation risks with Priority >= 9
**Penalty:** HIGH (5 points) per unmitigated risk with Priority >= 15; MEDIUM (3 points) for Priority 9-14
**Cap:** Max 15 points (3 violations maximum counted)
**Uses:** Impact x Probability matrix from `references/risk_based_testing_guide.md`
**Skip fix when:** Story has explicit Risk Assessment section, Story/Task in Done/Canceled, scope is trivial (1-2 Tasks, no external deps/DB/arch decisions), or all detected risks already mitigated in Technical Notes.
---
## Risk Categories
| Cat | Rule | Keywords | Auto-fix |
|-----|------|----------|----------|
| R1: Architecture | Non-trivial choices need rationale or ADR ref | architecture, pattern, CQRS, saga, event-driven, microservice, monolith, event sourcing, message queue | FLAG → create ADR per references/templates/adr_template.md |
| R2: Error Handling | External calls need timeout, retry, fallback, circuit breaker | error, exception, retry, fallback, timeout, circuit breaker, dead letter, compensation | TODO: Define error handling for [op] |
| R3: Scalability | Data ops need bounds (pagination, limits, batch). Unbounded = risk | scale, concurrent, batch, pagination, limit, all records, full scan, load all, fetch all, no limit | TODO: Define pagination/batch limits |
| R4: Data Integrity | Multi-step data ops need transactions with rollback | transaction, rollback, constraint, cascade, delete, drop, truncate, migrate, atomic, consistency | TODO: Wrap [op] in transaction with rollback |
| R5: Integration | External APIs need SLA, timeout, retry, mock, webhook idempotency | API, external, third-party, webhook, integration, service, provider, vendor, OAuth, SSO | TODO: Define timeout/retry/fallback + idempotency |
| R6: SPOF | Critical-path needs degradation or redundancy plan | single, central, only one, depends entirely, critical path, no alternative, sole provider | FLAG → degradation strategy |
---
### R4b: Destructive Operation Safety
> SSOT: `references/destructive_operation_safety.md`
**Check:** Tasks with destructive operations have "Destructive Operation Safety" section with all 5 required fields filled (backup, rollback, blast radius, env guard, preview/dry-run) + severity
**Keywords:** `DROP, TRUNCATE, DELETE (without WHERE), ALTER...DROP COLUMN, rm -rf, rmdir, unlink, terraform destroy, kubectl delete, docker volume rm, migrate, purge, wipe, --force, git push --force, git reset --hard`
**GOOD example** (all 5 fields concrete):
```markdown
### Destructive Operation Safety
**Operations:** DROP TABLE legacy_sessions; rm -rf /tmp/build-cache
**Severity:** HIGH
**Backup plan:** pg_dump legacy_sessions before DROP; verify row count matches
**Rollback plan:** pg_restore from dump; tested on staging 2024-01-15
**Blast radius:** legacy_sessions table (0 active users, read-only since 2023-06); /tmp/build-cache (ephemeral, recreated on build)
**Environment guard:** DROP gated by IS_MIGRATION_APPROVED=true env var; rm -rf only in CI cleanup stage
**Preview / dry-run:** SELECT COUNT(*) FROM legacy_sessions = 0 active rows; ls -la /tmp/build-cache shows only stale artifacts
```
**Scoring:** Impact 5, Probability 4 = Priority 20 (HIGH)
- All fields concrete (non-placeholder) → PASS (0 points)
- Any field is TODO/placeholder/empty → FLAGGED (5 points) + NO-GO (human must fill)
- Destructive keywords found but NO safety section → FLAGGED (5 points) + NO-GO (human review mandatory)
**Auto-fix:** Insert section skeleton from shared reference template. If ANY field remains TODO/placeholder/empty → criterion stays FLAGGED, story stays NO-GO. Not auto-fixable to PASS.
**Skip when:** No destructive keywords detected in Story or Tasks.
---
## Scoring Algorithm
```
FOR EACH risk category R1-R4, R4b, R5-R6:
1. SCAN Story (Technical Notes, Dependencies) + Tasks (Implementation Plan, Technical Approach)
2. DETECT risk indicators via keywords
3. IF risk indicator found:
a. CHECK if mitigation documented (retry, fallback, transaction, ADR ref, timeout, degradation)
b. IF mitigated -> PASS (0 points)
c. IF NOT mitigated:
- Assign Impact (1-5) and Probability (1-5) per risk_based_testing_guide.md
- Calculate Priority = Impact x Probability
- IF Priority >= 15 -> HIGH (5 points)
- IF Priority 9-14 -> MEDIUM (3 points)
- IF Priority <= 8 -> SKIP (0 points)
4. IF NO risk indicators for category -> PASS (0 points)
TOTAL = sum of all penalties (cap at 15 points)
```
**Default Impact x Probability by category:**
| Category | Impact | Probability | Priority | Notes |
|----------|--------|-------------|----------|-------|
| R1: Architectural Decisions | 4 | 3 | 12 (MEDIUM) | Raise to 5x4=20 if system-wide pattern |
| R2: Error Handling | 4 | 4 | 16 (HIGH) | External calls almost always need handling |
| R3: Scalability | 3 | 3 | 9 (MEDIUM) | Raise if user-facing or data-heavy |
| R4: Data Integrity | 5 | 4 | 20 (HIGH) | Data loss = highest business impact |
| R5: Integration | 4 | 4 | 16 (HIGH) | External systems are inherently unreliable |
| R6: SPOF | 5 | 2 | 10 (MEDIUM) | Low probability but catastrophic impact |
Override defaults when Story context indicates higher/lower risk (e.g., internal tool vs public API).
---
## Auto-fix vs Human Review
| Priority Range | Action | Rationale |
|----------------|--------|-----------|
| >= 20 | FLAG only (human review mandatory) | Too high-impact for automated TODO |
| 15-19 | Add TODO placeholder + FLAG | Actionable but needs human verification |
| 9-14 | Add TODO placeholder (silent) | Important but lower urgency |
| <= 8 | SKIP | Risk too low to warrant Story-level documentation |
**Auto-fixable:** R2 (error handling), R3 (limits), R4 (transactions), R5 (integration points)
**Human review only:** R1 (architectural decisions), R6 (SPOF at design level), any risk with Priority >= 20
---
**Version:** 1.0.0
**Last Updated:** 2026-02-11
references/scripts/coordinator-runtime/lib/artifacts.mjs
// SOURCE-OF-TRUTH: shared/scripts/coordinator-runtime/lib/artifacts.mjs. Edit ONLY here; run `node tools/marketplace/shared.mjs sync`
import { mkdirSync, writeFileSync } from "node:fs";
import { dirname, join, resolve } from "node:path";
function safeSegment(value) {
return String(value || "default")
.trim()
.replace(/[^a-zA-Z0-9._-]+/g, "-")
.replace(/^-+|-+$/g, "")
.toLowerCase() || "default";
}
export function runtimeArtifactDir(projectRoot, runId, summaryKind) {
return join(
resolve(projectRoot || process.cwd()),
".hex-skills",
"runtime-artifacts",
"runs",
safeSegment(runId),
safeSegment(summaryKind),
);
}
export function runtimeArtifactPath(projectRoot, runId, summaryKind, identifier) {
return join(runtimeArtifactDir(projectRoot, runId, summaryKind), `${safeSegment(identifier)}.json`);
}
export function runtimeArtifactPathForFile(projectRoot, runId, summaryKind, fileName) {
return join(runtimeArtifactDir(projectRoot, runId, summaryKind), fileName);
}
export function resolveArtifactWritePath(projectRoot, artifactPath) {
const resolvedProjectRoot = resolve(projectRoot || process.cwd());
const resolvedArtifactPath = resolve(resolvedProjectRoot, artifactPath);
if (dirname(resolvedArtifactPath) === resolvedProjectRoot) {
throw new Error("Runtime artifacts must not be written to the project root");
}
return resolvedArtifactPath;
}
export function writeRuntimeArtifactJsonToPath(projectRoot, artifactPath, payload) {
const resolvedArtifactPath = resolveArtifactWritePath(projectRoot, artifactPath);
mkdirSync(dirname(resolvedArtifactPath), { recursive: true });
writeFileSync(resolvedArtifactPath, JSON.stringify(payload, null, 2) + "\n", "utf8");
return resolvedArtifactPath;
}
export function writeRuntimeArtifactJson(projectRoot, runId, summaryKind, identifier, payload) {
const artifactPath = runtimeArtifactPath(projectRoot, runId, summaryKind, identifier);
const nextPayload = payload && typeof payload === "object" && payload.payload && typeof payload.payload === "object"
? {
...payload,
payload: {
...payload.payload,
artifact_path: payload.payload.artifact_path || artifactPath,
},
}
: payload;
return writeRuntimeArtifactJsonToPath(projectRoot, artifactPath, nextPayload);
}
references/scripts/coordinator-runtime/lib/cli-helpers.mjs
// SOURCE-OF-TRUTH: shared/scripts/coordinator-runtime/lib/cli-helpers.mjs. Edit ONLY here; run `node tools/marketplace/shared.mjs sync`
export function outputJson(data) {
process.stdout.write(JSON.stringify(data, null, 2) + "\n");
}
export function failJson(message, code = 2) {
process.stderr.write(JSON.stringify({ ok: false, error: message }) + "\n");
process.exit(code);
}
export function failResult(result, code = 2) {
if (typeof result === "string") {
failJson(result, code);
}
process.stderr.write(JSON.stringify({ ok: false, ...result }) + "\n");
process.exit(code);
}
function windowsTmpHint(filePath) {
if (process.platform !== "win32") return "";
if (typeof filePath !== "string") return "";
// Detect both raw Unix-style (/tmp/, /var/, /home/, /root/) and Git Bash
// MSYS-translated Windows temp paths (AppData/Local/Temp, /Temp/, Temp\).
const unixStyle = /^\/(tmp|var|home|root)\//.test(filePath);
const winTemp = /AppData[\\/]Local[\\/]Temp|[\\/]Temp[\\/]/i.test(filePath);
if (!unixStyle && !winTemp) return "";
return ` Hint: path "${filePath}" looks like a temp path on Windows. Git Bash resolves /tmp/ to a location that Node.js CLIs cannot always read (MSYS vs native path mismatch). Use a project-relative path (e.g. .hex-skills/runtime/) instead.`;
}
export function readPayload(values, readJsonFile) {
if (values["payload-file"]) {
const payload = readJsonFile(values["payload-file"]);
if (payload == null) {
failJson(`Unable to read payload file: ${values["payload-file"]}.${windowsTmpHint(values["payload-file"])}`);
}
return payload;
}
if (!values.payload) {
return {};
}
try {
return JSON.parse(values.payload);
} catch (error) {
failJson(`Invalid JSON payload: ${error.message}`);
}
}
export function readManifestOrFail(values, readJsonFile, flagName = "manifest-file") {
const filePath = values[flagName];
const manifest = readJsonFile(filePath);
if (manifest == null) {
failJson(`Manifest file not found or invalid: ${filePath}.${windowsTmpHint(filePath)}`);
}
return manifest;
}
function buildRuntimeMeta(run, stateOverride = null) {
const state = stateOverride || run.state;
return {
skill: run.manifest.skill,
identifier: run.manifest.identifier,
run_id: state.run_id,
phase: state.phase,
complete: state.complete,
};
}
export function outputInactiveRuntime(output) {
output({
ok: true,
active: false,
runtime: null,
});
}
export function outputRuntimeStatus(output, projectRoot, run, runtimePaths, computeResumeAction) {
output({
ok: true,
active: !run.state.complete,
runtime: buildRuntimeMeta(run),
manifest: run.manifest,
state: run.state,
checkpoints: run.checkpoints,
paths: runtimePaths(projectRoot, run.state.run_id, run.manifest.skill, run.manifest.identifier),
resume_action: computeResumeAction(run.manifest, run.state, run.checkpoints),
});
}
export function outputRuntimeState(output, run, state, extra = {}) {
output({
ok: true,
runtime: buildRuntimeMeta(run, state),
state,
...extra,
});
}
export function outputGuardFailure(output, guard) {
output({
ok: false,
error: guard.error || "Transition blocked",
validation_errors: guard.details || [],
guard,
});
process.exit(1);
}
references/scripts/coordinator-runtime/lib/core.mjs
// SOURCE-OF-TRUTH: shared/scripts/coordinator-runtime/lib/core.mjs. Edit ONLY here; run `node tools/marketplace/shared.mjs sync`
import { randomUUID } from "node:crypto";
import {
appendFileSync,
existsSync,
mkdirSync,
readdirSync,
readFileSync,
renameSync,
unlinkSync,
writeFileSync,
} from "node:fs";
import { dirname, join, resolve } from "node:path";
import {
activePointerSchema,
buildRuntimeStateSchema,
runtimeCheckpointEntrySchema,
runtimeCheckpointHistorySchema,
runtimeHistoryEventSchema,
} from "./schemas.mjs";
import { assertSchema } from "./validate.mjs";
import { updateLoopHealthMap } from "./loop-health.mjs";
const LOCK_FILE = ".lock";
const HISTORY_FILE = "history.jsonl";
function resolveParts(projectRoot, parts) {
return join(resolve(projectRoot || process.cwd()), ...(parts || []));
}
function safeReadJson(filePath) {
try {
return JSON.parse(readFileSync(filePath, "utf8"));
} catch {
return null;
}
}
function safeIdentifier(value) {
return String(value || "default")
.trim()
.replace(/[^a-zA-Z0-9_-]+/g, "-")
.replace(/^-+|-+$/g, "")
.toLowerCase() || "default";
}
function atomicWrite(filePath, data) {
mkdirSync(dirname(filePath), { recursive: true });
const tmpPath = `${filePath}.tmp-${process.pid}`;
try {
writeFileSync(tmpPath, JSON.stringify(data, null, 2) + "\n", "utf8");
renameSync(tmpPath, filePath);
} catch (error) {
try {
unlinkSync(tmpPath);
} catch {
// Best-effort cleanup only.
}
throw error;
}
}
function defaultRunId(skill, identifier) {
const safeSkill = safeIdentifier(skill || "runtime");
const safeRunIdentifier = safeIdentifier(identifier || "run");
return `${safeSkill}-${safeRunIdentifier}-${Date.now()}-${randomUUID().slice(0, 8)}`;
}
function normalizeCheckpoints(raw) {
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
return { _history: [], _next_sequence: 1 };
}
if (Array.isArray(raw._history)) {
return {
...raw,
_history: raw._history,
_next_sequence: Number(raw._next_sequence || (raw._history.length + 1)),
};
}
const history = Object.entries(raw)
.filter(([key, value]) => !key.startsWith("_") && value && typeof value === "object")
.map(([phase, entry], index) => ({
sequence: Number(entry.sequence || (index + 1)),
phase,
created_at: entry.created_at || new Date().toISOString(),
payload: entry.payload || {},
}));
return {
...raw,
_history: history,
_next_sequence: history.length + 1,
};
}
function appendCheckpoint(checkpoints, phase, payload) {
const next = normalizeCheckpoints(checkpoints);
const entry = {
sequence: next._next_sequence,
phase,
created_at: new Date().toISOString(),
payload: payload || {},
};
const entryValidation = assertSchema(runtimeCheckpointEntrySchema, entry, "checkpoint entry");
if (!entryValidation.ok) {
throw new Error(entryValidation.error);
}
const nextCheckpoints = {
...next,
[phase]: entry,
_history: [...next._history, entry],
_next_sequence: entry.sequence + 1,
};
const checkpointsValidation = assertSchema(runtimeCheckpointHistorySchema, nextCheckpoints, "checkpoint history");
if (!checkpointsValidation.ok) {
throw new Error(checkpointsValidation.error);
}
return nextCheckpoints;
}
function acquireLock(dir) {
const lockPath = join(dir, LOCK_FILE);
mkdirSync(dir, { recursive: true });
if (existsSync(lockPath)) {
const existing = safeReadJson(lockPath);
if (existing?.pid) {
try {
process.kill(existing.pid, 0);
return { ok: false, error: `Runtime already running (PID ${existing.pid})` };
} catch {
// Stale lock.
}
}
}
atomicWrite(lockPath, { pid: process.pid, started_at: new Date().toISOString() });
return { ok: true };
}
function releaseLock(dir) {
try {
unlinkSync(join(dir, LOCK_FILE));
} catch {
// Best-effort cleanup only.
}
}
function withLock(dir, action) {
const lock = acquireLock(dir);
if (!lock.ok) {
return lock;
}
try {
return action();
} finally {
releaseLock(dir);
}
}
function readHistory(filePath) {
if (!existsSync(filePath)) {
return [];
}
const content = readFileSync(filePath, "utf8");
return content
.split(/\r?\n/u)
.map(line => line.trim())
.filter(Boolean)
.map(line => {
try {
return JSON.parse(line);
} catch {
return null;
}
})
.filter(Boolean);
}
function appendHistoryEvent(filePath, eventType, payload) {
mkdirSync(dirname(filePath), { recursive: true });
const events = readHistory(filePath);
const nextEvent = {
sequence: events.length + 1,
event_type: eventType,
created_at: new Date().toISOString(),
...payload,
};
const validation = assertSchema(runtimeHistoryEventSchema, nextEvent, "runtime history event");
if (!validation.ok) {
throw new Error(validation.error);
}
appendFileSync(filePath, `${JSON.stringify(nextEvent)}\n`, "utf8");
return nextEvent;
}
function rebuildFromHistory(events, fallback) {
let manifest = fallback?.manifest || null;
let state = fallback?.state || null;
let checkpoints = normalizeCheckpoints(fallback?.checkpoints || {});
for (const event of events) {
switch (event.event_type) {
case "RUN_STARTED":
manifest = event.manifest || manifest;
state = event.state || state;
checkpoints = normalizeCheckpoints(event.checkpoints || checkpoints);
break;
case "STATE_SAVED":
case "RUN_PAUSED":
case "RUN_COMPLETED":
case "LOOP_HEALTH_RECORDED":
state = event.state || state;
break;
case "CHECKPOINT_RECORDED":
checkpoints = appendCheckpoint(checkpoints, event.phase, event.payload || {});
break;
default:
break;
}
}
if (!manifest || !state) {
return null;
}
return { manifest, state, checkpoints, history: events };
}
export function readJsonFile(filePath) {
return safeReadJson(filePath);
}
export function resolveTrackedPath(projectRoot, filePath) {
if (!filePath) {
return null;
}
return resolve(projectRoot || process.cwd(), filePath);
}
export function fileExists(filePath) {
return existsSync(filePath);
}
export function createRuntimeStore(config) {
const baseRootParts = config.baseRootParts;
const activeRootParts = config.activeRootParts || baseRootParts;
const stateSchema = config.stateSchema || buildRuntimeStateSchema();
const runsSubdir = Object.prototype.hasOwnProperty.call(config, "runsSubdir")
? config.runsSubdir
: "runs";
function baseRoot(projectRoot) {
return resolveParts(projectRoot, baseRootParts);
}
function activeRoot(projectRoot) {
return resolveParts(projectRoot, activeRootParts);
}
function runsDir(projectRoot) {
return runsSubdir ? join(baseRoot(projectRoot), runsSubdir) : baseRoot(projectRoot);
}
function runDir(projectRoot, runId) {
return join(runsDir(projectRoot), runId);
}
function manifestPath(projectRoot, runId) {
return join(runDir(projectRoot, runId), "manifest.json");
}
function statePath(projectRoot, runId) {
return join(runDir(projectRoot, runId), "state.json");
}
function checkpointsPath(projectRoot, runId) {
return join(runDir(projectRoot, runId), "checkpoints.json");
}
function historyPath(projectRoot, runId) {
return join(runDir(projectRoot, runId), HISTORY_FILE);
}
function activeDir(projectRoot, skill) {
return join(activeRoot(projectRoot), "active", safeIdentifier(skill));
}
function activePath(projectRoot, skill, identifier) {
return join(activeDir(projectRoot, skill), `${safeIdentifier(identifier)}.json`);
}
function listActiveRuns(projectRoot, skill) {
const dir = activeDir(projectRoot, skill);
if (!existsSync(dir)) {
return [];
}
return readdirSync(dir)
.filter(name => name.endsWith(".json"))
.map(name => safeReadJson(join(dir, name)))
.filter(pointer => pointer?.run_id);
}
function loadRun(projectRoot, runId) {
const manifest = safeReadJson(manifestPath(projectRoot, runId));
const state = safeReadJson(statePath(projectRoot, runId));
const checkpoints = normalizeCheckpoints(safeReadJson(checkpointsPath(projectRoot, runId)));
const history = readHistory(historyPath(projectRoot, runId));
if (history.length > 0) {
return rebuildFromHistory(history, { manifest, state, checkpoints });
}
if (!manifest || !state) {
return null;
}
return { manifest, state, checkpoints, history: [] };
}
function loadActiveRun(projectRoot, skill, identifier) {
const pointer = identifier
? safeReadJson(activePath(projectRoot, skill, identifier))
: (() => {
const activeRuns = listActiveRuns(projectRoot, skill);
return activeRuns.length === 1 ? activeRuns[0] : null;
})();
if (!pointer?.run_id) {
return null;
}
return loadRun(projectRoot, pointer.run_id);
}
function saveActive(projectRoot, skill, identifier, runId) {
const pointer = {
skill,
identifier,
run_id: runId,
updated_at: new Date().toISOString(),
};
const validation = assertSchema(activePointerSchema, pointer, "active runtime pointer");
if (!validation.ok) {
throw new Error(validation.error);
}
atomicWrite(activePath(projectRoot, skill, identifier), pointer);
}
function clearActive(projectRoot, skill, identifier, runId) {
const filePath = activePath(projectRoot, skill, identifier);
const pointer = safeReadJson(filePath);
if (pointer?.run_id && pointer.run_id !== runId) {
return;
}
try {
unlinkSync(filePath);
} catch {
// Best-effort cleanup only.
}
}
function startRun(projectRoot, manifestInput) {
const manifest = config.normalizeManifest(manifestInput, projectRoot);
if (!manifest.skill || !manifest.identifier) {
return { ok: false, error: "Manifest normalization must set skill and identifier" };
}
if (config.manifestSchema) {
const validation = assertSchema(config.manifestSchema, manifest, `${manifest.skill} manifest`);
if (!validation.ok) {
return validation;
}
}
const activeRun = loadActiveRun(projectRoot, manifest.skill, manifest.identifier);
if (activeRun && !activeRun.state.complete) {
return { ok: false, recovery: true, run: activeRun };
}
const runId = (config.buildRunId || defaultRunId)(manifest.skill, manifest.identifier, manifestInput);
const state = config.defaultState(manifest, runId);
const stateValidation = assertSchema(stateSchema, state, `${manifest.skill} state`);
if (!stateValidation.ok) {
return stateValidation;
}
const checkpoints = normalizeCheckpoints({});
const checkpointsValidation = assertSchema(runtimeCheckpointHistorySchema, checkpoints, `${manifest.skill} checkpoints`);
if (!checkpointsValidation.ok) {
return checkpointsValidation;
}
const result = withLock(baseRoot(projectRoot), () => {
atomicWrite(manifestPath(projectRoot, runId), manifest);
atomicWrite(statePath(projectRoot, runId), state);
atomicWrite(checkpointsPath(projectRoot, runId), checkpoints);
appendHistoryEvent(historyPath(projectRoot, runId), "RUN_STARTED", {
run_id: runId,
manifest,
state,
checkpoints,
});
saveActive(projectRoot, manifest.skill, manifest.identifier, runId);
return { ok: true, run_id: runId, manifest, state, checkpoints };
});
return result;
}
function saveState(projectRoot, runId, state, eventType = "STATE_SAVED") {
const nextState = {
...state,
updated_at: new Date().toISOString(),
};
const validation = assertSchema(stateSchema, nextState, "runtime state");
if (!validation.ok) {
return validation;
}
const result = withLock(baseRoot(projectRoot), () => {
const run = loadRun(projectRoot, runId);
if (!run) {
return { ok: false, error: "Run not found" };
}
atomicWrite(statePath(projectRoot, runId), nextState);
appendHistoryEvent(historyPath(projectRoot, runId), eventType, {
run_id: runId,
state: nextState,
});
if (!nextState.complete) {
saveActive(projectRoot, nextState.skill, nextState.identifier, runId);
}
return { ok: true, state: nextState };
});
return result.ok === false ? result : result.state;
}
function checkpointPhase(projectRoot, runId, phase, payload) {
const run = loadRun(projectRoot, runId);
if (!run) {
return { ok: false, error: "Run not found" };
}
const nextCheckpoints = appendCheckpoint(run.checkpoints, phase, payload);
return withLock(baseRoot(projectRoot), () => {
atomicWrite(checkpointsPath(projectRoot, runId), nextCheckpoints);
appendHistoryEvent(historyPath(projectRoot, runId), "CHECKPOINT_RECORDED", {
run_id: runId,
phase,
payload: payload || {},
});
return { ok: true, checkpoints: nextCheckpoints };
});
}
function updateState(projectRoot, runId, updater, options = {}) {
const run = loadRun(projectRoot, runId);
if (!run) {
return { ok: false, error: "Run not found" };
}
const nextState = typeof updater === "function" ? updater(run.state, run) : updater;
const saved = saveState(projectRoot, runId, nextState, options.eventType || "STATE_SAVED");
if (saved?.ok === false) {
return saved;
}
return { ok: true, state: saved };
}
function pauseRun(projectRoot, runId, reason) {
return updateState(projectRoot, runId, state => ({
...state,
phase: "PAUSED",
paused_reason: reason || "Paused",
pending_decision: null,
}), { eventType: "RUN_PAUSED" });
}
function recordLoopHealth(projectRoot, runId, scopeKey, signal, options = {}) {
let recordedEntry = null;
let pauseRecommendation = null;
const result = updateState(projectRoot, runId, state => {
const updated = updateLoopHealthMap(state.loop_health || {}, scopeKey, signal, options.policy || {});
recordedEntry = updated.entry;
pauseRecommendation = updated.pause;
const pauseState = options.pauseOnRecommendation !== false && updated.pause.pause
? {
phase: "PAUSED",
paused_reason: updated.pause.reason || "Loop health pause",
pending_decision: null,
}
: {};
return {
...state,
...pauseState,
loop_health: updated.map,
};
}, { eventType: "LOOP_HEALTH_RECORDED" });
if (!result.ok) {
return result;
}
return {
ok: true,
state: result.state,
loop_health: recordedEntry,
pause: pauseRecommendation,
};
}
function completeRun(projectRoot, runId) {
const run = loadRun(projectRoot, runId);
if (!run) {
return { ok: false, error: "Run not found" };
}
const result = updateState(projectRoot, runId, state => ({
...state,
phase: "DONE",
complete: true,
paused_reason: null,
pending_decision: null,
}), { eventType: "RUN_COMPLETED" });
if (!result.ok) {
return result;
}
clearActive(projectRoot, run.state.skill, run.state.identifier, runId);
return result;
}
function resolveRunId(projectRoot, skill, runId, identifier) {
if (runId) {
return runId;
}
if (identifier) {
const pointer = safeReadJson(activePath(projectRoot, skill, identifier));
return pointer?.run_id || null;
}
const activeRuns = listActiveRuns(projectRoot, skill);
if (activeRuns.length !== 1) {
return null;
}
return activeRuns[0].run_id;
}
function runtimePaths(projectRoot, runId, skill, identifier) {
const resolvedRunId = resolveRunId(projectRoot, skill, runId, identifier);
if (!resolvedRunId) {
return null;
}
return {
root: baseRoot(projectRoot),
run_dir: runDir(projectRoot, resolvedRunId),
manifest: manifestPath(projectRoot, resolvedRunId),
state: statePath(projectRoot, resolvedRunId),
checkpoints: checkpointsPath(projectRoot, resolvedRunId),
history: historyPath(projectRoot, resolvedRunId),
active: identifier ? activePath(projectRoot, skill, identifier) : activeDir(projectRoot, skill),
};
}
return {
baseRoot,
runtimePaths,
loadRun,
loadActiveRun,
listActiveRuns,
startRun,
saveState,
checkpointPhase,
updateState,
recordLoopHealth,
pauseRun,
completeRun,
clearActiveRun(projectRoot, skill, identifier, runId) {
clearActive(projectRoot, skill, identifier, runId);
},
resolveRunId,
readHistory(projectRoot, runId) {
return readHistory(historyPath(projectRoot, runId));
},
};
}
references/scripts/coordinator-runtime/lib/loop-health.mjs
// SOURCE-OF-TRUTH: shared/scripts/coordinator-runtime/lib/loop-health.mjs. Edit ONLY here; run `node tools/marketplace/shared.mjs sync`
export const LOOP_HEALTH_FAILURE_CLASSES = Object.freeze({
NONE: "none",
TIMEOUT_IDLE: "timeout_idle",
TIMEOUT_PRODUCTIVE: "timeout_productive",
PERMISSION_DENIAL: "permission_denial",
TOOL_MISSING: "tool_missing",
AUTH_MISSING: "auth_missing",
RATE_LIMITED: "rate_limited",
ASKED_QUESTION: "asked_question",
AGENT_ERROR: "agent_error",
UNKNOWN: "unknown",
});
export const DEFAULT_LOOP_HEALTH_POLICY = Object.freeze({
no_progress_limit: 3,
same_error_limit: 3,
immediate_pause_failure_classes: Object.freeze([
LOOP_HEALTH_FAILURE_CLASSES.PERMISSION_DENIAL,
LOOP_HEALTH_FAILURE_CLASSES.TOOL_MISSING,
LOOP_HEALTH_FAILURE_CLASSES.AUTH_MISSING,
]),
defer_failure_classes: Object.freeze([
LOOP_HEALTH_FAILURE_CLASSES.RATE_LIMITED,
]),
});
const FAILURE_CLASS_SET = new Set(Object.values(LOOP_HEALTH_FAILURE_CLASSES));
function asText(value) {
if (value == null) return "";
if (typeof value === "string") return value;
try {
return JSON.stringify(value);
} catch {
return String(value);
}
}
export function normalizeErrorSignature(text) {
return asText(text)
.toLowerCase()
.replace(/\r\n/g, "\n")
.replace(/[a-z]:[\\/][^\s)'"`]+/gi, "<path>")
.replace(/\/(?:[^/\s)'"`]+\/)+[^/\s)'"`]+/g, "<path>")
.replace(/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/gi, "<uuid>")
.replace(/\b\d{4}-\d{2}-\d{2}t\d{2}:\d{2}:\d{2}(?:\.\d+)?z\b/g, "<timestamp>")
.replace(/\b\d+\b/g, "<num>")
.replace(/\s+/g, " ")
.trim()
.slice(0, 240);
}
function hasProgressSignal(input = {}) {
const signals = input.progress_signals || {};
return Boolean(
input.progress_detected
|| input.artifact_delta
|| input.checkpoint_delta
|| input.status_delta
|| input.files_changed_delta
|| input.scenario_improved
|| input.metric_improved
|| input.new_evidence
|| signals.output_written
|| signals.log_written
|| signals.session_captured
);
}
function classifyFromText(text) {
const normalized = normalizeErrorSignature(text);
if (!normalized) return LOOP_HEALTH_FAILURE_CLASSES.NONE;
if (/\b(rate limit|rate_limited|too many requests|quota|429)\b/.test(normalized)) {
return LOOP_HEALTH_FAILURE_CLASSES.RATE_LIMITED;
}
if (/\b(permission denied|not allowed|access denied|operation not permitted|blocked by permissions)\b/.test(normalized)) {
return LOOP_HEALTH_FAILURE_CLASSES.PERMISSION_DENIAL;
}
if (/\b(command not found|not found in path|enoent|tool missing|required tool)\b/.test(normalized)) {
return LOOP_HEALTH_FAILURE_CLASSES.TOOL_MISSING;
}
if (/\b(auth|authentication|unauthorized|login required|api key|token missing|credentials)\b/.test(normalized)) {
return LOOP_HEALTH_FAILURE_CLASSES.AUTH_MISSING;
}
if (/\?\s*$|should i|do you want|please confirm|need clarification/.test(normalized)) {
return LOOP_HEALTH_FAILURE_CLASSES.ASKED_QUESTION;
}
return LOOP_HEALTH_FAILURE_CLASSES.UNKNOWN;
}
export function classifyLoopSignal(input = {}) {
const combinedText = [
input.error,
input.stderr,
input.stdout,
input.response,
input.message,
input.evidence_key,
].map(asText).filter(Boolean).join("\n");
let failureClass = input.failure_class || null;
if (!FAILURE_CLASS_SET.has(failureClass)) {
failureClass = classifyFromText(combinedText);
}
const progressDetected = hasProgressSignal(input);
const errorSignature = normalizeErrorSignature(input.error_signature || combinedText || failureClass);
return {
failure_class: failureClass,
progress_detected: progressDetected,
error_signature: errorSignature || null,
evidence_key: input.evidence_key || errorSignature || failureClass,
reason: input.reason || null,
progress_signals: {
output_written: Boolean(input.progress_signals?.output_written),
log_written: Boolean(input.progress_signals?.log_written),
session_captured: Boolean(input.progress_signals?.session_captured),
},
recorded_at: new Date().toISOString(),
};
}
export function shouldPause(loopHealth = {}) {
return {
pause: Boolean(loopHealth.pause_recommended),
reason: loopHealth.pause_reason || null,
category: loopHealth.pause_category || null,
};
}
export function updateLoopHealth(previous = {}, signalInput = {}, policyInput = {}) {
const policy = {
...DEFAULT_LOOP_HEALTH_POLICY,
...policyInput,
};
const signal = signalInput.failure_class && signalInput.error_signature !== undefined
? signalInput
: classifyLoopSignal(signalInput);
const immediateSet = new Set(policy.immediate_pause_failure_classes || []);
const deferSet = new Set(policy.defer_failure_classes || []);
const previousSignature = previous.last_error_signature || null;
const sameError = Boolean(signal.error_signature && signal.error_signature === previousSignature);
const noProgressCount = signal.progress_detected
? 0
: Number(previous.no_progress_count || 0) + 1;
const sameErrorCount = signal.progress_detected
? 0
: (sameError ? Number(previous.same_error_count || 0) + 1 : 1);
let pauseRecommended = false;
let pauseCategory = null;
let pauseReason = null;
if (immediateSet.has(signal.failure_class)) {
pauseRecommended = true;
pauseCategory = signal.failure_class;
pauseReason = `Immediate blocker: ${signal.failure_class}`;
} else if (deferSet.has(signal.failure_class)) {
pauseRecommended = true;
pauseCategory = signal.failure_class;
pauseReason = "Rate limited; defer retry without counting as domain failure";
} else if (sameErrorCount >= policy.same_error_limit) {
pauseRecommended = true;
pauseCategory = "same_error";
pauseReason = `Same error repeated ${sameErrorCount} times without progress`;
} else if (noProgressCount >= policy.no_progress_limit) {
pauseRecommended = true;
pauseCategory = "no_progress";
pauseReason = `No progress recorded for ${noProgressCount} attempts`;
}
return {
attempts: Number(previous.attempts || 0) + 1,
no_progress_count: noProgressCount,
same_error_count: sameErrorCount,
last_failure_class: signal.failure_class,
last_error_signature: signal.error_signature,
last_evidence_key: signal.evidence_key || null,
last_progress_detected: signal.progress_detected,
last_signal_at: signal.recorded_at || new Date().toISOString(),
pause_recommended: pauseRecommended,
pause_category: pauseCategory,
pause_reason: pauseReason,
history: [
...(previous.history || []).slice(-9),
signal,
],
};
}
export function updateLoopHealthMap(previousMap = {}, scopeKey, signalInput = {}, policyInput = {}) {
const key = String(scopeKey || "default");
const nextEntry = updateLoopHealth(previousMap[key] || {}, signalInput, policyInput);
return {
map: {
...previousMap,
[key]: nextEntry,
},
entry: nextEntry,
pause: shouldPause(nextEntry),
};
}
references/scripts/coordinator-runtime/lib/runtime-constants.mjs
// SOURCE-OF-TRUTH: shared/scripts/coordinator-runtime/lib/runtime-constants.mjs. Edit ONLY here; run `node tools/marketplace/shared.mjs sync`
export const TERMINAL_RUNTIME_PHASES = Object.freeze({
PAUSED: "PAUSED",
DONE: "DONE",
});
export const RUNTIME_HISTORY_EVENT_TYPES = Object.freeze({
RUN_STARTED: "RUN_STARTED",
STATE_SAVED: "STATE_SAVED",
RUN_PAUSED: "RUN_PAUSED",
RUN_COMPLETED: "RUN_COMPLETED",
CHECKPOINT_RECORDED: "CHECKPOINT_RECORDED",
LOOP_HEALTH_RECORDED: "LOOP_HEALTH_RECORDED",
});
export const RUNTIME_HISTORY_EVENT_TYPE_LIST = Object.freeze(Object.values(RUNTIME_HISTORY_EVENT_TYPES));
export const WORKER_SUMMARY_STATUSES = Object.freeze({
COMPLETED: "completed",
SKIPPED: "skipped",
ERROR: "error",
});
export const WORKER_SUMMARY_STATUS_LIST = Object.freeze(Object.values(WORKER_SUMMARY_STATUSES));
export const REVIEW_AGENT_STATUSES = Object.freeze({
SKIPPED: "skipped",
LAUNCHED: "launched",
RESULT_READY: "result_ready",
DEAD: "dead",
FAILED: "failed",
});
export const REVIEW_AGENT_STATUS_LIST = Object.freeze(Object.values(REVIEW_AGENT_STATUSES));
export const REVIEW_RESOLVED_AGENT_STATUS_LIST = Object.freeze([
REVIEW_AGENT_STATUSES.RESULT_READY,
REVIEW_AGENT_STATUSES.DEAD,
REVIEW_AGENT_STATUSES.FAILED,
REVIEW_AGENT_STATUSES.SKIPPED,
]);
export const REVIEW_RESOLVED_AGENT_STATUS_SET = new Set(REVIEW_RESOLVED_AGENT_STATUS_LIST);
export const PLANNING_PROGRESS_STATUSES = Object.freeze({
COMPLETED: "completed",
});
export const OPTIMIZATION_GATE_VERDICTS = Object.freeze({
PROCEED: "PROCEED",
CONCERNS: "CONCERNS",
WAIVED: "WAIVED",
BLOCK: "BLOCK",
});
export const OPTIMIZATION_GATE_VERDICT_LIST = Object.freeze(Object.values(OPTIMIZATION_GATE_VERDICTS));
export const OPTIMIZATION_VALIDATION_VERDICTS = Object.freeze({
GO: "GO",
GO_WITH_CONCERNS: "GO_WITH_CONCERNS",
WAIVED: "WAIVED",
NO_GO: "NO_GO",
});
export const OPTIMIZATION_EXECUTION_ALLOWED_VERDICT_LIST = Object.freeze([
OPTIMIZATION_VALIDATION_VERDICTS.GO,
OPTIMIZATION_VALIDATION_VERDICTS.GO_WITH_CONCERNS,
OPTIMIZATION_VALIDATION_VERDICTS.WAIVED,
]);
export const OPTIMIZATION_CHECKPOINT_STATUSES = Object.freeze({
COMPLETED: "completed",
SKIPPED_BY_MODE: "skipped_by_mode",
});
export const OPTIMIZATION_CYCLE_STATUSES = Object.freeze({
COMPLETED: "completed",
});
export const OPTIMIZATION_CYCLE_STATUS_LIST = Object.freeze(Object.values(OPTIMIZATION_CYCLE_STATUSES));
export const STORY_GATE_VERDICTS = Object.freeze({
PASS: "PASS",
CONCERNS: "CONCERNS",
WAIVED: "WAIVED",
FAIL: "FAIL",
});
export const STORY_GATE_VERDICT_LIST = Object.freeze(Object.values(STORY_GATE_VERDICTS));
export const STORY_GATE_FINALIZATION_STATUSES = Object.freeze({
SKIPPED_BY_VERDICT: "skipped_by_verdict",
});
export const TASK_BOARD_STATUSES = Object.freeze({
BACKLOG: "Backlog",
TODO: "Todo",
IN_PROGRESS: "In Progress",
TO_REVIEW: "To Review",
TO_REWORK: "To Rework",
DONE: "Done",
SKIPPED: "SKIPPED",
VERIFIED: "VERIFIED",
});
export const STORY_GATE_COMPLETED_TEST_STATUS_LIST = Object.freeze([
TASK_BOARD_STATUSES.DONE,
TASK_BOARD_STATUSES.SKIPPED,
TASK_BOARD_STATUSES.VERIFIED,
]);
export const STORY_GATE_PRE_VERIFICATION_ALLOWED_TEST_STATUS_LIST = Object.freeze([
TASK_BOARD_STATUSES.DONE,
TASK_BOARD_STATUSES.SKIPPED,
]);
export const STORY_EXECUTION_GROUP_STATUSES = Object.freeze({
COMPLETED: "completed",
});
export const STORY_EXECUTION_GROUP_STATUS_LIST = Object.freeze(Object.values(STORY_EXECUTION_GROUP_STATUSES));
export const ENVIRONMENT_SETUP_FINAL_RESULTS = Object.freeze({
READY: "READY",
DRY_RUN_PLAN: "DRY_RUN_PLAN",
});
export const STORY_EXECUTION_FINAL_RESULTS = Object.freeze({
READY_FOR_GATE: "READY_FOR_GATE",
});
references/scripts/coordinator-runtime/lib/schemas.mjs
// SOURCE-OF-TRUTH: shared/scripts/coordinator-runtime/lib/schemas.mjs. Edit ONLY here; run `node tools/marketplace/shared.mjs sync`
import {
OPTIMIZATION_CYCLE_STATUS_LIST,
REVIEW_AGENT_STATUS_LIST,
RUNTIME_HISTORY_EVENT_TYPE_LIST,
STORY_GATE_VERDICT_LIST,
STORY_EXECUTION_GROUP_STATUS_LIST,
WORKER_SUMMARY_STATUS_LIST,
} from "./runtime-constants.mjs";
function stringArraySchema() {
return {
type: "array",
items: { type: "string" },
};
}
function nullableStringSchema() {
return { type: ["string", "null"] };
}
function nonNegativeIntegerSchema() {
return { type: "integer", minimum: 0 };
}
function dateTimeSchema() {
return { type: "string", format: "date-time" };
}
function baseDecisionRecordSchema() {
return {
type: "object",
required: ["kind", "selected_choice", "answered_at"],
additionalProperties: false,
properties: {
kind: { type: "string", minLength: 1 },
selected_choice: { type: "string", minLength: 1 },
answered_at: dateTimeSchema(),
context: { type: "object" },
},
};
}
export function buildRuntimeStateSchema(extraProperties = {}, extraRequired = []) {
return {
type: "object",
required: [
"run_id",
"skill",
"identifier",
"phase",
"complete",
"paused_reason",
"pending_decision",
"decisions",
"final_result",
"created_at",
"updated_at",
...extraRequired,
],
properties: {
run_id: { type: "string", minLength: 1 },
skill: { type: "string", minLength: 1 },
mode: { type: ["string", "null"] },
identifier: { type: "string", minLength: 1 },
phase: { type: "string", minLength: 1 },
complete: { type: "boolean" },
paused_reason: nullableStringSchema(),
pending_decision: { type: ["object", "null"] },
decisions: {
type: "array",
items: baseDecisionRecordSchema(),
},
final_result: { type: ["string", "null"] },
created_at: dateTimeSchema(),
updated_at: dateTimeSchema(),
...extraProperties,
},
};
}
export const activePointerSchema = {
type: "object",
required: ["skill", "identifier", "run_id", "updated_at"],
additionalProperties: false,
properties: {
skill: { type: "string", minLength: 1 },
identifier: { type: "string", minLength: 1 },
run_id: { type: "string", minLength: 1 },
updated_at: dateTimeSchema(),
},
};
export const runtimeCheckpointEntrySchema = {
type: "object",
required: ["sequence", "phase", "created_at", "payload"],
additionalProperties: false,
properties: {
sequence: { type: "integer", minimum: 1 },
phase: { type: "string", minLength: 1 },
created_at: dateTimeSchema(),
payload: { type: "object" },
},
};
export const runtimeCheckpointHistorySchema = {
type: "object",
required: ["_history", "_next_sequence"],
properties: {
_history: {
type: "array",
items: runtimeCheckpointEntrySchema,
},
_next_sequence: { type: "integer", minimum: 1 },
},
};
export const runtimeHistoryEventSchema = {
type: "object",
required: ["sequence", "event_type", "created_at"],
properties: {
sequence: { type: "integer", minimum: 1 },
event_type: {
type: "string",
enum: RUNTIME_HISTORY_EVENT_TYPE_LIST,
},
created_at: dateTimeSchema(),
run_id: { type: "string" },
},
};
export const runtimeStatusResponseSchema = {
type: "object",
required: ["ok", "active", "runtime"],
properties: {
ok: { type: "boolean" },
active: { type: "boolean" },
runtime: {
type: ["object", "null"],
properties: {
skill: { type: "string" },
identifier: { type: "string" },
run_id: { type: "string" },
phase: { type: "string" },
complete: { type: "boolean" },
},
},
manifest: { type: "object" },
state: { type: "object" },
checkpoints: { type: "object" },
paths: { type: "object" },
resume_action: { type: ["string", "null"] },
error: { type: "string" },
validation_errors: { type: "array" },
},
};
export function buildSummaryEnvelopeSchema(payloadSchema) {
return {
type: "object",
required: ["schema_version", "summary_kind", "run_id", "identifier", "producer_skill", "produced_at", "payload"],
additionalProperties: false,
properties: {
schema_version: { type: "string", minLength: 1 },
summary_kind: { type: "string", minLength: 1 },
run_id: { type: "string", minLength: 1 },
identifier: { type: "string", minLength: 1 },
producer_skill: { type: "string", minLength: 1 },
produced_at: dateTimeSchema(),
payload: payloadSchema || { type: "object" },
},
};
}
export const pendingDecisionSchema = {
type: "object",
required: ["kind", "question", "choices", "default_choice", "resume_to_phase", "blocking"],
additionalProperties: false,
properties: {
kind: { type: "string", minLength: 1 },
question: { type: "string", minLength: 1 },
choices: {
...stringArraySchema(),
minItems: 1,
},
default_choice: { type: "string", minLength: 1 },
context: { type: "object" },
resume_to_phase: { type: "string", minLength: 1 },
blocking: { type: "boolean" },
},
};
export const environmentWorkerPayloadSchema = {
type: "object",
required: ["status"],
additionalProperties: false,
properties: {
status: { type: "string", enum: WORKER_SUMMARY_STATUS_LIST },
targets: stringArraySchema(),
changes: stringArraySchema(),
warnings: stringArraySchema(),
detail: { type: "string" },
},
};
export const opportunityDiscoveryWorkerPayloadSchema = {
type: "object",
required: ["input_mode", "ideas_analyzed", "survivors_count", "killed_count", "warnings"],
additionalProperties: false,
properties: {
input_mode: { type: "string", minLength: 1 },
ideas_analyzed: { type: "integer" },
generated_ideas: { type: "integer" },
survivors_count: { type: "integer" },
killed_count: { type: "integer" },
top_recommendation: nullableStringSchema(),
report_path: nullableStringSchema(),
warnings: stringArraySchema(),
artifact_path: nullableStringSchema(),
},
};
export const storyPlanWorkerPayloadSchema = {
type: "object",
required: ["mode", "epic_id", "stories_created", "stories_updated", "stories_canceled", "story_urls", "warnings", "kanban_updated"],
additionalProperties: false,
properties: {
mode: { type: "string" },
epic_id: { type: "string" },
stories_planned: { type: "integer" },
stories_created: { type: "integer" },
stories_updated: { type: "integer" },
stories_canceled: { type: "integer" },
story_urls: stringArraySchema(),
warnings: stringArraySchema(),
kanban_updated: { type: "boolean" },
research_path_used: { type: "string" },
},
};
export const storyPlanCoordinatorPayloadSchema = {
type: "object",
required: ["mode", "epic_id", "stories_created", "stories_updated", "stories_canceled", "story_urls", "warnings", "kanban_updated"],
additionalProperties: false,
properties: {
mode: { type: "string" },
epic_id: { type: "string" },
stories_planned: { type: "integer" },
stories_created: { type: "integer" },
stories_updated: { type: "integer" },
stories_canceled: { type: "integer" },
story_urls: stringArraySchema(),
warnings: stringArraySchema(),
kanban_updated: { type: "boolean" },
research_path_used: { type: "string" },
worker_runs_completed: { type: "integer" },
artifact_path: nullableStringSchema(),
},
};
export const taskPlanWorkerPayloadSchema = {
type: "object",
required: ["mode", "story_id", "task_type", "tasks_created", "tasks_updated", "tasks_canceled", "task_urls", "warnings", "kanban_updated"],
additionalProperties: false,
properties: {
mode: { type: "string" },
story_id: { type: "string" },
task_type: { type: "string" },
tasks_created: { type: "integer" },
tasks_updated: { type: "integer" },
tasks_canceled: { type: "integer" },
task_urls: stringArraySchema(),
dry_warnings_count: { type: "integer" },
warnings: stringArraySchema(),
kanban_updated: { type: "boolean" },
},
};
export const qualityWorkerPayloadSchema = {
type: "object",
required: ["worker", "status", "verdict", "issues", "warnings"],
additionalProperties: false,
properties: {
worker: { type: "string", minLength: 1 },
status: { type: "string", enum: WORKER_SUMMARY_STATUS_LIST },
verdict: { type: "string", minLength: 1 },
score: { type: "number" },
issues: stringArraySchema(),
warnings: stringArraySchema(),
artifact_path: nullableStringSchema(),
metadata: {
type: "object",
additionalProperties: true,
},
},
};
export const taskStatusWorkerPayloadSchema = {
type: "object",
required: ["worker", "status", "from_status", "to_status", "warnings"],
additionalProperties: false,
properties: {
worker: { type: "string", minLength: 1 },
status: { type: "string", enum: WORKER_SUMMARY_STATUS_LIST },
from_status: { type: "string", minLength: 1 },
to_status: { type: "string", minLength: 1 },
result: nullableStringSchema(),
tests_run: stringArraySchema(),
files_changed: stringArraySchema(),
issues: stringArraySchema(),
score: { type: ["number", "null"] },
comment_path: nullableStringSchema(),
error: nullableStringSchema(),
warnings: stringArraySchema(),
artifact_path: nullableStringSchema(),
metadata: {
type: "object",
additionalProperties: true,
},
},
};
export const testPlanningWorkerPayloadSchema = {
type: "object",
required: ["worker", "status", "warnings"],
additionalProperties: false,
properties: {
worker: { type: "string", minLength: 1 },
status: { type: "string", enum: WORKER_SUMMARY_STATUS_LIST },
warnings: stringArraySchema(),
research_comment_path: nullableStringSchema(),
manual_result_path: nullableStringSchema(),
test_task_id: nullableStringSchema(),
test_task_url: nullableStringSchema(),
coverage_summary: nullableStringSchema(),
planned_scenarios: stringArraySchema(),
metadata: {
type: "object",
additionalProperties: true,
},
},
};
export const storyPrioritizationWorkerPayloadSchema = {
type: "object",
required: ["epic_id", "stories_analyzed", "priority_distribution", "prioritization_path", "warnings"],
additionalProperties: false,
properties: {
epic_id: { type: "string", minLength: 1 },
depth: { type: "string" },
stories_analyzed: { type: "integer" },
priority_distribution: {
type: "object",
required: ["p0", "p1", "p2", "p3"],
additionalProperties: false,
properties: {
p0: nonNegativeIntegerSchema(),
p1: nonNegativeIntegerSchema(),
p2: nonNegativeIntegerSchema(),
p3: nonNegativeIntegerSchema(),
},
},
top_story_ids: stringArraySchema(),
prioritization_path: { type: "string", minLength: 1 },
warnings: stringArraySchema(),
artifact_path: nullableStringSchema(),
},
};
export const docsGenerationWorkerPayloadSchema = {
type: "object",
required: ["worker", "status", "created_files", "skipped_files", "quality_inputs", "validation_status", "warnings"],
additionalProperties: false,
properties: {
worker: { type: "string", minLength: 1 },
status: { type: "string", enum: WORKER_SUMMARY_STATUS_LIST },
created_files: stringArraySchema(),
skipped_files: stringArraySchema(),
quality_inputs: {
type: "object",
additionalProperties: true,
},
validation_status: { type: "string", minLength: 1 },
warnings: stringArraySchema(),
metadata: {
type: "object",
additionalProperties: true,
},
},
};
export const epicPlanCoordinatorPayloadSchema = {
type: "object",
required: ["mode", "scope_identifier", "epics_created", "epics_updated", "epics_canceled", "epic_urls", "warnings", "kanban_updated"],
additionalProperties: false,
properties: {
mode: { type: "string" },
scope_identifier: { type: "string", minLength: 1 },
epics_created: { type: "integer" },
epics_updated: { type: "integer" },
epics_canceled: { type: "integer" },
epic_urls: stringArraySchema(),
warnings: stringArraySchema(),
kanban_updated: { type: "boolean" },
infrastructure_epic_included: { type: "boolean" },
artifact_path: nullableStringSchema(),
},
};
export const scopeDecompositionPayloadSchema = {
type: "object",
required: ["scope_identifier", "epic_runs_completed", "story_runs_completed", "warnings"],
additionalProperties: false,
properties: {
scope_identifier: { type: "string", minLength: 1 },
epic_runs_completed: { type: "integer" },
story_runs_completed: { type: "integer" },
prioritization_runs_completed: { type: "integer" },
warnings: stringArraySchema(),
final_result: { type: "string" },
artifact_path: nullableStringSchema(),
},
};
export const pipelineStageCoordinatorPayloadSchema = {
type: "object",
required: ["stage", "story_id", "status", "final_result", "story_status", "warnings"],
additionalProperties: false,
properties: {
stage: { type: "integer", minimum: 0, maximum: 3 },
story_id: { type: "string", minLength: 1 },
status: { type: "string", enum: WORKER_SUMMARY_STATUS_LIST },
final_result: { type: "string", minLength: 1 },
story_status: { type: "string", minLength: 1 },
verdict: nullableStringSchema(),
readiness_score: { type: ["number", "null"] },
quality_score: { type: ["number", "null"] },
warnings: stringArraySchema(),
artifact_path: nullableStringSchema(),
metadata: {
type: "object",
additionalProperties: true,
},
},
};
export const auditSeverityCountsSchema = {
type: "object",
required: ["critical", "high", "medium", "low"],
additionalProperties: false,
properties: {
critical: nonNegativeIntegerSchema(),
high: nonNegativeIntegerSchema(),
medium: nonNegativeIntegerSchema(),
low: nonNegativeIntegerSchema(),
},
};
export const auditWorkerPayloadSchema = {
type: "object",
required: ["status", "category", "report_path", "score", "issues_total", "severity_counts", "warnings"],
additionalProperties: false,
properties: {
status: { type: "string", enum: WORKER_SUMMARY_STATUS_LIST },
category: { type: "string", minLength: 1 },
report_path: { type: "string", minLength: 1 },
score: { type: "number" },
issues_total: nonNegativeIntegerSchema(),
severity_counts: auditSeverityCountsSchema,
warnings: stringArraySchema(),
diagnostic_scores: {
type: "object",
additionalProperties: { type: "number" },
},
domain_name: nullableStringSchema(),
scan_scope: nullableStringSchema(),
metadata: {
type: "object",
additionalProperties: true,
},
},
};
export const auditCoordinatorPayloadSchema = {
type: "object",
required: ["status", "final_result", "report_path", "worker_count", "issues_total", "severity_counts", "warnings"],
additionalProperties: false,
properties: {
status: { type: "string", enum: WORKER_SUMMARY_STATUS_LIST },
final_result: { type: "string", minLength: 1 },
report_path: { type: "string", minLength: 1 },
results_log_path: nullableStringSchema(),
overall_score: { type: ["number", "null"] },
worker_count: nonNegativeIntegerSchema(),
issues_total: nonNegativeIntegerSchema(),
severity_counts: auditSeverityCountsSchema,
warnings: stringArraySchema(),
artifact_path: nullableStringSchema(),
metadata: {
type: "object",
additionalProperties: true,
},
},
};
export const evaluationWorkerPayloadSchema = {
type: "object",
required: ["status", "worker", "operation", "warnings"],
additionalProperties: false,
properties: {
status: { type: "string", enum: WORKER_SUMMARY_STATUS_LIST },
worker: { type: "string", minLength: 1 },
operation: { type: "string", minLength: 1 },
verdict: nullableStringSchema(),
findings: {
type: "array",
items: {
type: "object",
additionalProperties: true,
},
},
metrics: {
type: "object",
additionalProperties: true,
},
decisions: {
type: "array",
items: {
type: "object",
additionalProperties: true,
},
},
report_path: nullableStringSchema(),
artifact_path: nullableStringSchema(),
warnings: stringArraySchema(),
metadata: {
type: "object",
additionalProperties: true,
},
},
};
export const evaluationCoordinatorPayloadSchema = {
type: "object",
required: ["status", "final_result", "report_path", "worker_count", "issues_total", "severity_counts", "warnings", "cleanup_verified"],
additionalProperties: false,
properties: {
status: { type: "string", enum: WORKER_SUMMARY_STATUS_LIST },
final_result: { type: "string", minLength: 1 },
report_path: { type: "string", minLength: 1 },
results_log_path: nullableStringSchema(),
overall_score: { type: ["number", "null"] },
worker_count: nonNegativeIntegerSchema(),
agent_count: nonNegativeIntegerSchema(),
issues_total: nonNegativeIntegerSchema(),
severity_counts: auditSeverityCountsSchema,
warnings: stringArraySchema(),
cleanup_verified: { type: "boolean" },
research_completed: { type: "boolean" },
artifact_path: nullableStringSchema(),
metadata: {
type: "object",
additionalProperties: true,
},
},
};
export const optimizationWorkerPayloadSchema = {
type: "object",
required: ["status", "worker"],
additionalProperties: false,
properties: {
status: { type: "string", enum: WORKER_SUMMARY_STATUS_LIST },
worker: { type: "string", minLength: 1 },
cycle: { type: "integer", minimum: 1 },
phase_context: nullableStringSchema(),
artifact_path: nullableStringSchema(),
branch: { type: "string" },
baseline: { type: "object" },
performance_map: { type: "object" },
wrong_tool_indicators: stringArraySchema(),
e2e_test: { type: "object" },
instrumented_files: stringArraySchema(),
industry_benchmark: { type: "object" },
target_metrics: { type: "object" },
hypotheses: stringArraySchema(),
local_codebase_findings: stringArraySchema(),
verdict: { type: "string" },
corrections_applied: stringArraySchema(),
concerns: stringArraySchema(),
final: { type: "object" },
total_improvement_pct: { type: "number" },
target_met: { type: "boolean" },
strike_result: { type: "string" },
hypotheses_applied: stringArraySchema(),
hypotheses_removed: stringArraySchema(),
recorded_at: dateTimeSchema(),
},
};
export const optimizationCoordinatorPayloadSchema = {
type: "object",
required: ["status", "final_result", "cycle_count", "report_ready", "execution_mode"],
additionalProperties: false,
properties: {
status: { type: "string", enum: WORKER_SUMMARY_STATUS_LIST },
final_result: { type: "string", minLength: 1 },
cycle_count: nonNegativeIntegerSchema(),
stop_reason: nullableStringSchema(),
report_ready: { type: "boolean" },
execution_mode: { type: "string", minLength: 1 },
target_metric: { type: ["object", "null"] },
total_improvement_pct: { type: ["number", "null"] },
target_met: { type: ["boolean", "null"] },
summary_artifact_path: nullableStringSchema(),
report_path: nullableStringSchema(),
},
};
export const dependencyWorkerPayloadSchema = {
type: "object",
required: ["status", "worker", "package_manager"],
additionalProperties: false,
properties: {
status: { type: "string", enum: WORKER_SUMMARY_STATUS_LIST },
worker: { type: "string", minLength: 1 },
package_manager: { type: "string", minLength: 1 },
branch: nullableStringSchema(),
upgrades: {
type: "array",
items: {
type: "object",
required: ["package", "from", "to"],
additionalProperties: false,
properties: {
package: { type: "string", minLength: 1 },
from: { type: "string", minLength: 1 },
to: { type: "string", minLength: 1 },
breaking: { type: "boolean" },
},
},
},
warnings: stringArraySchema(),
errors: stringArraySchema(),
tests_passed: { type: "boolean" },
build_passed: { type: "boolean" },
artifact_path: nullableStringSchema(),
},
};
export const dependencyCoordinatorPayloadSchema = {
type: "object",
required: ["status", "final_result", "worker_count", "upgraded_packages", "verification_passed", "report_ready"],
additionalProperties: false,
properties: {
status: { type: "string", enum: WORKER_SUMMARY_STATUS_LIST },
final_result: { type: "string", minLength: 1 },
worker_count: nonNegativeIntegerSchema(),
upgraded_packages: nonNegativeIntegerSchema(),
failed_workers: nonNegativeIntegerSchema(),
verification_passed: { type: "boolean" },
report_ready: { type: "boolean" },
report_path: nullableStringSchema(),
artifact_path: nullableStringSchema(),
},
};
export const modernizationWorkerPayloadSchema = {
type: "object",
required: ["status", "worker"],
additionalProperties: false,
properties: {
status: { type: "string", enum: WORKER_SUMMARY_STATUS_LIST },
worker: { type: "string", minLength: 1 },
branch: nullableStringSchema(),
changes_applied: nonNegativeIntegerSchema(),
changes_discarded: nonNegativeIntegerSchema(),
tests_passed: { type: "boolean" },
build_passed: { type: "boolean" },
modules_replaced: nonNegativeIntegerSchema(),
loc_removed: nonNegativeIntegerSchema(),
bundle_reduction_bytes: nonNegativeIntegerSchema(),
warnings: stringArraySchema(),
errors: stringArraySchema(),
artifact_path: nullableStringSchema(),
},
};
export const modernizationCoordinatorPayloadSchema = {
type: "object",
required: ["status", "final_result", "worker_count", "verification_passed", "report_ready"],
additionalProperties: false,
properties: {
status: { type: "string", enum: WORKER_SUMMARY_STATUS_LIST },
final_result: { type: "string", minLength: 1 },
worker_count: nonNegativeIntegerSchema(),
verification_passed: { type: "boolean" },
report_ready: { type: "boolean" },
modules_replaced: nonNegativeIntegerSchema(),
loc_removed: nonNegativeIntegerSchema(),
bundle_reduction_bytes: nonNegativeIntegerSchema(),
report_path: nullableStringSchema(),
artifact_path: nullableStringSchema(),
},
};
export const benchmarkWorkerPayloadSchema = {
type: "object",
required: ["status", "worker", "scenarios_total", "scenarios_passed", "scenarios_failed", "activation_valid", "validity_verdict", "warnings"],
additionalProperties: false,
properties: {
status: { type: "string", enum: WORKER_SUMMARY_STATUS_LIST },
worker: { type: "string", minLength: 1 },
scenarios_total: nonNegativeIntegerSchema(),
scenarios_passed: nonNegativeIntegerSchema(),
scenarios_failed: nonNegativeIntegerSchema(),
activation_valid: { type: "boolean" },
validity_verdict: { type: "string", minLength: 1 },
report_path: nullableStringSchema(),
artifact_path: nullableStringSchema(),
scenario_ids: stringArraySchema(),
warnings: stringArraySchema(),
metrics: {
type: "object",
additionalProperties: true,
},
metadata: {
type: "object",
additionalProperties: true,
},
},
};
export const environmentWorkerSummarySchema = buildSummaryEnvelopeSchema(environmentWorkerPayloadSchema);
export const opportunityDiscoveryWorkerSummarySchema = buildSummaryEnvelopeSchema(opportunityDiscoveryWorkerPayloadSchema);
export const storyPlanWorkerSummarySchema = buildSummaryEnvelopeSchema(storyPlanWorkerPayloadSchema);
export const storyPlanCoordinatorSummarySchema = buildSummaryEnvelopeSchema(storyPlanCoordinatorPayloadSchema);
export const taskPlanWorkerSummarySchema = buildSummaryEnvelopeSchema(taskPlanWorkerPayloadSchema);
export const qualityWorkerSummarySchema = buildSummaryEnvelopeSchema(qualityWorkerPayloadSchema);
export const taskStatusWorkerSummarySchema = buildSummaryEnvelopeSchema(taskStatusWorkerPayloadSchema);
export const testPlanningWorkerSummarySchema = buildSummaryEnvelopeSchema(testPlanningWorkerPayloadSchema);
export const storyPrioritizationWorkerSummarySchema = buildSummaryEnvelopeSchema(storyPrioritizationWorkerPayloadSchema);
export const docsGenerationWorkerSummarySchema = buildSummaryEnvelopeSchema(docsGenerationWorkerPayloadSchema);
export const epicPlanCoordinatorSummarySchema = buildSummaryEnvelopeSchema(epicPlanCoordinatorPayloadSchema);
export const scopeDecompositionSummarySchema = buildSummaryEnvelopeSchema(scopeDecompositionPayloadSchema);
export const auditWorkerSummarySchema = buildSummaryEnvelopeSchema(auditWorkerPayloadSchema);
export const auditCoordinatorSummarySchema = buildSummaryEnvelopeSchema(auditCoordinatorPayloadSchema);
export const evaluationWorkerSummarySchema = buildSummaryEnvelopeSchema(evaluationWorkerPayloadSchema);
export const evaluationCoordinatorSummarySchema = buildSummaryEnvelopeSchema(evaluationCoordinatorPayloadSchema);
export const pipelineStageCoordinatorSummarySchema = buildSummaryEnvelopeSchema(pipelineStageCoordinatorPayloadSchema);
export const optimizationWorkerSummarySchema = buildSummaryEnvelopeSchema(optimizationWorkerPayloadSchema);
export const optimizationCoordinatorSummarySchema = buildSummaryEnvelopeSchema(optimizationCoordinatorPayloadSchema);
export const dependencyWorkerSummarySchema = buildSummaryEnvelopeSchema(dependencyWorkerPayloadSchema);
export const dependencyCoordinatorSummarySchema = buildSummaryEnvelopeSchema(dependencyCoordinatorPayloadSchema);
export const modernizationWorkerSummarySchema = buildSummaryEnvelopeSchema(modernizationWorkerPayloadSchema);
export const modernizationCoordinatorSummarySchema = buildSummaryEnvelopeSchema(modernizationCoordinatorPayloadSchema);
export const benchmarkWorkerSummarySchema = buildSummaryEnvelopeSchema(benchmarkWorkerPayloadSchema);
export const environmentStateSchema = {
type: "object",
required: ["scanned_at", "agents"],
properties: {
scanned_at: { type: "string", format: "date-time" },
agents: {
type: "object",
required: ["claude", "codex"],
properties: {
claude: {
type: "object",
required: ["available"],
properties: {
available: { type: "boolean" },
disabled: { type: "boolean" },
version: { type: "string" },
detail: { type: "string" },
},
},
codex: {
type: "object",
required: ["available"],
properties: {
available: { type: "boolean" },
disabled: { type: "boolean" },
version: { type: "string" },
config_aligned: { type: "boolean" },
servers_aligned: { type: "integer" },
marketplace_plugins: stringArraySchema(),
alignment_actions: stringArraySchema(),
detail: { type: "string" },
},
}
},
},
task_management: {
type: "object",
properties: {
provider: { type: "string", enum: ["linear", "file", "github"] },
status: { type: "string" },
fallback: { type: "string", enum: ["file"] },
linear: {
type: "object",
properties: {
team_id: { type: "string" },
},
},
github: {
type: "object",
properties: {
repository: { type: "string" },
project_number: { type: "integer" },
},
},
},
},
research: {
type: "object",
properties: {
provider: { type: "string" },
fallback_chain: stringArraySchema(),
status: { type: "string" },
},
},
claude_md: {
type: "object",
properties: {
exists: { type: "boolean" },
has_compact_instructions: { type: "boolean" },
has_mcp_preferences: { type: "boolean" },
has_date_stamp: { type: "boolean" },
line_count: { type: "integer" },
has_timestamps: { type: "boolean" },
},
},
assessment: {
type: "object",
properties: {
assessed_at: { type: "string", format: "date-time" },
all_green: { type: "boolean" },
score: { type: "string" },
warnings: stringArraySchema(),
info: stringArraySchema(),
workers_run: stringArraySchema(),
workers_skipped: stringArraySchema(),
},
},
hooks: {
type: "object",
properties: {
mode: {
type: "string",
enum: ["blocking", "advisory"],
},
},
},
ide_extension: {
type: "object",
properties: {
cursor: ideExtensionEntrySchema(),
vscode: ideExtensionEntrySchema(),
},
},
},
};
function ideExtensionEntrySchema() {
return {
type: "object",
properties: {
installed: { type: "boolean" },
extension_version: { type: "string" },
settings_path: { type: "string" },
initial_permission_mode: {
type: "string",
enum: ["default", "acceptEdits", "plan", "bypassPermissions"],
},
allow_dangerously_skip_permissions: { type: "boolean" },
effective_state: {
type: "string",
enum: [
"default-prompt",
"accept-edits",
"plan-only",
"bypass-active",
"bypass-blocked",
"no-ide",
],
},
conflict_with_project_default_mode: {
type: "string",
enum: ["aligned", "override", "n/a"],
},
last_modified_by_skill: { type: "string" },
},
};
}
export const reviewAgentRecordSchema = {
type: "object",
required: ["name"],
additionalProperties: false,
properties: {
name: { type: "string", minLength: 1 },
status: { type: "string", enum: REVIEW_AGENT_STATUS_LIST },
prompt_file: nullableStringSchema(),
result_file: nullableStringSchema(),
log_file: nullableStringSchema(),
metadata_file: nullableStringSchema(),
pid: { type: ["integer", "null"] },
session_id: nullableStringSchema(),
started_at: { type: ["string", "null"], format: "date-time" },
finished_at: { type: ["string", "null"], format: "date-time" },
exit_code: { type: ["integer", "null"] },
error: nullableStringSchema(),
},
};
export const storyGroupRecordSchema = {
type: "object",
required: ["group_id"],
additionalProperties: false,
properties: {
group_id: { type: "string", minLength: 1 },
task_ids: stringArraySchema(),
status: { type: "string", enum: STORY_EXECUTION_GROUP_STATUS_LIST },
result: { type: "string" },
completed_at: dateTimeSchema(),
inflight_workers: { type: "object" },
},
};
export const qualitySummarySchema = {
type: "object",
required: ["story_id", "verdict"],
additionalProperties: false,
properties: {
story_id: { type: "string", minLength: 1 },
verdict: { type: "string", enum: STORY_GATE_VERDICT_LIST },
quality_score: { type: "number" },
issues: stringArraySchema(),
fast_track: { type: "boolean" },
agent_review_summary: { type: "object" },
regression_status: { type: "string" },
},
};
export const testSummarySchema = {
type: "object",
required: ["story_id", "status"],
additionalProperties: false,
properties: {
story_id: { type: "string", minLength: 1 },
mode: { type: "string" },
test_task_id: { type: "string" },
status: { type: "string", minLength: 1 },
planned_scenarios: stringArraySchema(),
coverage_summary: { type: "string" },
planner_invoked: { type: "boolean" },
error: { type: "string" },
},
};
export const optimizationWorkerResultSchema = optimizationWorkerPayloadSchema;
export const optimizationCycleSchema = {
type: "object",
required: ["cycle"],
additionalProperties: false,
properties: {
cycle: { type: "integer", minimum: 1 },
status: { type: "string", enum: OPTIMIZATION_CYCLE_STATUS_LIST },
next_cycle: { type: "integer" },
stop_reason: { type: "string" },
final_result: { type: "string" },
recorded_at: dateTimeSchema(),
},
};
// ── Blueprint verification (ln-401 task executor) ──────────────────────────
export const blueprintCheckpointPayloadSchema = {
type: "object",
required: ["blueprint"],
properties: {
blueprint: {
type: "object",
required: ["change_order"],
properties: {
change_order: {
type: "array",
items: {
type: "object",
required: ["file", "action"],
properties: {
file: { type: "string", minLength: 1 },
action: { type: "string", enum: ["create", "modify"] },
reason: { type: "string" },
},
},
minItems: 1,
},
},
},
},
};
export const blueprintStatusSchema = {
type: "object",
required: ["planned_count", "completed", "skipped", "added", "completion_pct"],
properties: {
planned_count: { type: "integer", minimum: 0 },
completed: stringArraySchema(),
skipped: {
type: "array",
items: {
type: "object",
required: ["file", "justification"],
properties: {
file: { type: "string", minLength: 1 },
justification: { type: "string", minLength: 1 },
},
},
},
added: {
type: "array",
items: {
type: "object",
required: ["file", "justification"],
properties: {
file: { type: "string", minLength: 1 },
justification: { type: "string", minLength: 1 },
},
},
},
completion_pct: { type: "number", minimum: 0, maximum: 100 },
},
};
references/scripts/coordinator-runtime/lib/validate.mjs
// SOURCE-OF-TRUTH: shared/scripts/coordinator-runtime/lib/validate.mjs. Edit ONLY here; run `node tools/marketplace/shared.mjs sync`
function isPlainObject(value) {
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
}
function matchesType(expectedType, value) {
if (Array.isArray(expectedType)) {
return expectedType.some(type => matchesType(type, value));
}
switch (expectedType) {
case "object":
return isPlainObject(value);
case "array":
return Array.isArray(value);
case "string":
return typeof value === "string";
case "integer":
return typeof value === "number" && Number.isInteger(value);
case "number":
return typeof value === "number" && Number.isFinite(value);
case "boolean":
return typeof value === "boolean";
case "null":
return value === null;
default:
return true;
}
}
function validateNode(schema, value, path, errors) {
if (!schema || typeof schema !== "object") {
return;
}
if (schema.type && !matchesType(schema.type, value)) {
errors.push({
instancePath: path,
message: `must be ${schema.type}`,
});
return;
}
if (schema.enum && !schema.enum.includes(value)) {
errors.push({
instancePath: path,
message: `must be one of: ${schema.enum.join(", ")}`,
});
}
if (schema.type === "string" && schema.format === "date-time" && typeof value === "string") {
if (Number.isNaN(Date.parse(value))) {
errors.push({
instancePath: path,
message: "must be a valid date-time string",
});
}
}
if (schema.type === "string" && typeof value === "string" && typeof schema.minLength === "number") {
if (value.length < schema.minLength) {
errors.push({
instancePath: path,
message: `must have length >= ${schema.minLength}`,
});
}
}
if (schema.type === "array" && Array.isArray(value) && typeof schema.minItems === "number") {
if (value.length < schema.minItems) {
errors.push({
instancePath: path,
message: `must contain at least ${schema.minItems} item(s)`,
});
}
}
if ((schema.type === "integer" || schema.type === "number") && typeof value === "number" && typeof schema.minimum === "number") {
if (value < schema.minimum) {
errors.push({
instancePath: path,
message: `must be >= ${schema.minimum}`,
});
}
}
if (schema.type === "object" && isPlainObject(value)) {
const properties = schema.properties || {};
const required = Array.isArray(schema.required) ? schema.required : [];
for (const key of required) {
if (!Object.prototype.hasOwnProperty.call(value, key)) {
errors.push({
instancePath: path,
message: `missing required property: ${key}`,
});
}
}
if (schema.additionalProperties === false) {
for (const key of Object.keys(value)) {
if (!Object.prototype.hasOwnProperty.call(properties, key)) {
errors.push({
instancePath: path ? `${path}/${key}` : `/${key}`,
message: "additional property is not allowed",
});
}
}
}
for (const [key, propertySchema] of Object.entries(properties)) {
if (!Object.prototype.hasOwnProperty.call(value, key)) {
continue;
}
const nextPath = path ? `${path}/${key}` : `/${key}`;
validateNode(propertySchema, value[key], nextPath, errors);
}
}
if (schema.type === "array" && Array.isArray(value) && schema.items) {
for (let index = 0; index < value.length; index += 1) {
validateNode(schema.items, value[index], `${path}/${index}`, errors);
}
}
}
export function validateSchema(schema, data) {
const errors = [];
validateNode(schema, data, "", errors);
return {
ok: errors.length === 0,
errors,
};
}
export function formatValidationErrors(errors) {
return (errors || [])
.map(error => `${error.instancePath || "/"} ${error.message}`.trim())
.join("; ");
}
export function assertSchema(schema, data, label = "payload") {
const result = validateSchema(schema, data);
if (result.ok) {
return { ok: true };
}
return {
ok: false,
error: `Invalid ${label}: ${formatValidationErrors(result.errors)}`,
details: result.errors,
};
}
references/scripts/evaluation-runtime/cli.mjs
#!/usr/bin/env node
// SOURCE-OF-TRUTH: shared/scripts/evaluation-runtime/cli.mjs. Edit ONLY here; run `node tools/marketplace/shared.mjs sync`
import { existsSync } from "node:fs";
import { parseArgs } from "node:util";
import {
checkpointPhase,
completeRun,
fileExists,
listActiveRuns,
loadActiveRun,
loadRun,
pauseRun,
readJsonFile,
recordDecision,
recordSummary,
recordWorkerResult,
registerAgent,
resolveRunId,
resolveTrackedPath,
runtimePaths,
saveState,
setPendingDecision,
startRun,
} from "./lib/store.mjs";
import {
failJson as fail,
failResult,
outputJson as output,
outputGuardFailure,
outputInactiveRuntime,
outputRuntimeState,
outputRuntimeStatus,
readManifestOrFail,
readPayload,
} from "../coordinator-runtime/lib/cli-helpers.mjs";
import {
computeResumeAction,
validateTransition,
} from "./lib/guards.mjs";
import { REVIEW_AGENT_STATUSES, REVIEW_RESOLVED_AGENT_STATUS_SET } from "../coordinator-runtime/lib/runtime-constants.mjs";
const { values, positionals } = parseArgs({
allowPositionals: true,
options: {
skill: { type: "string" },
identifier: { type: "string" },
"run-id": { type: "string" },
"project-root": { type: "string", default: process.cwd() },
"manifest-file": { type: "string" },
phase: { type: "string" },
to: { type: "string" },
payload: { type: "string" },
"payload-file": { type: "string" },
agent: { type: "string" },
"prompt-file": { type: "string" },
"result-file": { type: "string" },
"log-file": { type: "string" },
"metadata-file": { type: "string" },
reason: { type: "string" },
},
});
function resolveRun(projectRoot) {
const runId = resolveRunId(projectRoot, values.skill, values["run-id"], values.identifier);
if (!runId) {
const activeRuns = values.skill ? listActiveRuns(projectRoot, values.skill) : [];
if (activeRuns.length > 1 && !values.identifier) {
fail("Multiple active runs found. Pass --identifier or --run-id.");
}
fail("No active run found. Pass --run-id, or --skill with --identifier.");
}
const run = loadRun(projectRoot, runId);
if (!run) {
fail(`Run not found: ${runId}`);
}
return { runId, run };
}
function mergeObjectMap(current, incoming) {
if (!incoming || typeof incoming !== "object") {
return current;
}
return {
...(current || {}),
...incoming,
};
}
function applyCheckpointToState(run, phase, payload) {
const nextState = {
...run.state,
phase_data: {
...(run.state.phase_data || {}),
[phase]: payload || {},
},
};
const policy = run.manifest.phase_policy || {};
if (Array.isArray(payload.worker_plan)) {
nextState.worker_plan = payload.worker_plan;
}
if (payload.child_run && typeof payload.child_run === "object") {
const childKey = `${payload.child_run.worker}--${payload.child_run.identifier}`;
nextState.child_runs = {
...(run.state.child_runs || {}),
[childKey]: payload.child_run,
};
}
if (payload.child_runs && typeof payload.child_runs === "object") {
nextState.child_runs = mergeObjectMap(run.state.child_runs, payload.child_runs);
}
if (payload.inflight_worker && typeof payload.inflight_worker === "object") {
const inflightKey = `${payload.inflight_worker.worker}--${payload.inflight_worker.identifier}`;
nextState.inflight_workers = {
...(run.state.inflight_workers || {}),
[inflightKey]: payload.inflight_worker,
};
}
if (payload.inflight_workers && typeof payload.inflight_workers === "object") {
nextState.inflight_workers = mergeObjectMap(run.state.inflight_workers, payload.inflight_workers);
}
if (payload.resolved_workers && Array.isArray(payload.resolved_workers)) {
const inflight = { ...(nextState.inflight_workers || {}) };
for (const workerKey of payload.resolved_workers) {
delete inflight[workerKey];
}
nextState.inflight_workers = inflight;
}
if (payload.final_result) {
nextState.final_result = payload.final_result;
}
if (payload.report_path) {
nextState.report_path = payload.report_path;
}
if (payload.results_log_path) {
nextState.results_log_path = payload.results_log_path;
}
if (payload.research_completed === true || Array.isArray(payload.research_sources) || Array.isArray(payload.research_artifacts)) {
nextState.research_completed = true;
}
if (payload.background_agent_cleanup && typeof payload.background_agent_cleanup === "object") {
nextState.background_agent_cleanup = mergeObjectMap(run.state.background_agent_cleanup, payload.background_agent_cleanup);
}
if (payload.refinement_cleanup && typeof payload.refinement_cleanup === "object") {
nextState.refinement_cleanup = mergeObjectMap(run.state.refinement_cleanup, payload.refinement_cleanup);
}
if (typeof payload.cleanup_verified === "boolean") {
nextState.cleanup_verified = payload.cleanup_verified;
}
if (phase === policy.aggregate_phase) {
nextState.aggregation_summary = payload.aggregation_summary || payload.summary || payload;
}
if (phase === policy.report_phase) {
nextState.report_written = payload.report_written === true || Boolean(payload.report_path || nextState.report_path);
}
if (phase === policy.results_log_phase) {
nextState.results_log_appended = payload.results_log_appended === true || payload.appended === true || Boolean(payload.log_row);
}
if (phase === policy.cleanup_phase) {
nextState.cleanup_verified = payload.cleanup_verified === true;
}
if (phase === policy.self_check_phase) {
nextState.self_check_passed = payload.pass === true;
nextState.final_result = payload.final_result || nextState.final_result;
}
return nextState;
}
function isProcessAlive(pid) {
if (!pid) {
return false;
}
try {
process.kill(pid, 0);
return true;
} catch {
return false;
}
}
function syncOneAgent(projectRoot, agentState) {
const nextAgent = { ...agentState };
const metadataPath = resolveTrackedPath(projectRoot, nextAgent.metadata_file);
const resultPath = resolveTrackedPath(projectRoot, nextAgent.result_file);
const metadata = metadataPath && existsSync(metadataPath) ? readJsonFile(metadataPath) : null;
if (metadata) {
nextAgent.pid = metadata.pid ?? nextAgent.pid ?? null;
nextAgent.error = metadata.error ?? nextAgent.error ?? null;
nextAgent.exit_code = metadata.exit_code ?? nextAgent.exit_code ?? null;
nextAgent.finished_at = metadata.finished_at ?? nextAgent.finished_at ?? null;
}
if (resultPath && fileExists(resultPath)) {
nextAgent.status = REVIEW_AGENT_STATUSES.RESULT_READY;
} else if (metadata && (metadata.success === false || metadata.status === REVIEW_AGENT_STATUSES.FAILED)) {
nextAgent.status = REVIEW_AGENT_STATUSES.FAILED;
} else if (nextAgent.pid && !isProcessAlive(nextAgent.pid)) {
nextAgent.status = REVIEW_AGENT_STATUSES.DEAD;
} else if (!REVIEW_RESOLVED_AGENT_STATUS_SET.has(nextAgent.status)) {
nextAgent.status = REVIEW_AGENT_STATUSES.LAUNCHED;
}
return nextAgent;
}
async function main() {
const command = positionals[0];
const projectRoot = values["project-root"];
if (command === "start") {
if (!values.skill || !values.identifier || !values["manifest-file"]) {
fail("start requires --skill, --identifier, and --manifest-file");
}
const manifest = readManifestOrFail(values, readJsonFile);
const result = startRun(projectRoot, {
...manifest,
skill: values.skill,
identifier: values.identifier,
});
output(result);
process.exit(result.ok ? 0 : 1);
}
if (command === "status") {
if (!values["run-id"] && values.skill && !values.identifier) {
const activeRuns = listActiveRuns(projectRoot, values.skill);
if (activeRuns.length > 1) {
output({ ok: false, error: "Multiple active runs found. Pass --identifier or --run-id." });
process.exit(1);
}
}
const run = values["run-id"]
? loadRun(projectRoot, values["run-id"])
: (values.skill ? loadActiveRun(projectRoot, values.skill, values.identifier) : null);
if (!run) {
outputInactiveRuntime(output);
return;
}
outputRuntimeStatus(output, projectRoot, run, runtimePaths, computeResumeAction);
return;
}
if (command === "advance") {
if (!values.to) {
fail("advance requires --to");
}
const { runId, run } = resolveRun(projectRoot);
const guard = validateTransition(run.manifest, run.state, run.checkpoints, values.to);
if (!guard.ok) {
outputGuardFailure(output, guard);
}
const nextState = saveState(projectRoot, runId, {
...run.state,
phase: values.to,
complete: values.to === "DONE" ? true : run.state.complete,
paused_reason: null,
});
if (nextState?.ok === false) {
outputGuardFailure(output, nextState);
}
outputRuntimeState(output, run, nextState);
return;
}
if (command === "checkpoint") {
if (!values.phase) {
fail("checkpoint requires --phase");
}
const payload = readPayload(values, readJsonFile);
const { runId, run } = resolveRun(projectRoot);
const result = checkpointPhase(projectRoot, runId, values.phase, payload);
if (!result.ok) {
fail(result.error);
}
const nextState = saveState(projectRoot, runId, applyCheckpointToState(run, values.phase, payload));
if (nextState?.ok === false) {
failResult(nextState);
}
outputRuntimeState(output, run, nextState, {
checkpoint: result.checkpoints[values.phase],
});
return;
}
if (command === "record-worker-result") {
const payload = readPayload(values, readJsonFile);
const { runId } = resolveRun(projectRoot);
const result = recordWorkerResult(projectRoot, runId, payload);
if (!result.ok) {
failResult(result);
}
output(result);
return;
}
if (command === "record-summary") {
const payload = readPayload(values, readJsonFile);
const { runId } = resolveRun(projectRoot);
const result = recordSummary(projectRoot, runId, payload);
if (!result.ok) {
failResult(result);
}
output(result);
return;
}
if (command === "register-agent") {
if (!values.agent) {
fail("register-agent requires --agent");
}
const { runId } = resolveRun(projectRoot);
const result = registerAgent(projectRoot, runId, {
name: values.agent,
prompt_file: values["prompt-file"] || null,
result_file: values["result-file"] || null,
log_file: values["log-file"] || null,
metadata_file: values["metadata-file"] || null,
status: REVIEW_AGENT_STATUSES.LAUNCHED,
});
if (!result.ok) {
failResult(result);
}
output(result);
return;
}
if (command === "sync-agent") {
const { runId, run } = resolveRun(projectRoot);
const agentNames = values.agent ? [values.agent] : Object.keys(run.state.agents || {});
if (agentNames.length === 0) {
output({ ok: true, agents: {}, resolved: true });
return;
}
const nextAgents = { ...run.state.agents };
for (const agentName of agentNames) {
if (!nextAgents[agentName]) {
fail(`Agent not registered: ${agentName}`);
}
nextAgents[agentName] = syncOneAgent(projectRoot, nextAgents[agentName]);
}
const nextState = saveState(projectRoot, runId, { ...run.state, agents: nextAgents });
if (nextState?.ok === false) {
failResult(nextState);
}
output({
ok: true,
agents: agentNames.reduce((acc, name) => {
acc[name] = nextState.agents[name];
return acc;
}, {}),
resolved: Object.values(nextState.agents).every(agent => REVIEW_RESOLVED_AGENT_STATUS_SET.has(agent.status)),
});
return;
}
if (command === "pause") {
const payload = readPayload(values, readJsonFile);
const { runId } = resolveRun(projectRoot);
const result = Object.keys(payload).length > 0
? setPendingDecision(projectRoot, runId, payload, values.reason || "Decision required")
: pauseRun(projectRoot, runId, values.reason || "Paused");
if (!result.ok) {
failResult(result);
}
output(result);
return;
}
if (command === "set-decision") {
const payload = readPayload(values, readJsonFile);
const { runId } = resolveRun(projectRoot);
const result = recordDecision(projectRoot, runId, payload);
if (!result.ok) {
failResult(result);
}
output(result);
return;
}
if (command === "complete") {
const { runId, run } = resolveRun(projectRoot);
const guard = validateTransition(run.manifest, run.state, run.checkpoints, "DONE");
if (!guard.ok) {
outputGuardFailure(output, guard);
}
const result = completeRun(projectRoot, runId);
if (!result.ok) {
failResult(result);
}
output(result);
return;
}
fail("Unknown command. Use: start, status, checkpoint, record-worker-result, record-summary, register-agent, sync-agent, advance, pause, set-decision, complete");
}
main().catch(error => fail(error.message));
references/scripts/evaluation-runtime/lib/guards.mjs
// SOURCE-OF-TRUTH: shared/scripts/evaluation-runtime/lib/guards.mjs. Edit ONLY here; run `node tools/marketplace/shared.mjs sync`
import { REVIEW_RESOLVED_AGENT_STATUS_SET } from "../../coordinator-runtime/lib/runtime-constants.mjs";
function latestPayload(checkpoints, phase) {
return checkpoints?.[phase]?.payload || {};
}
function configuredPhases(manifest) {
return Array.isArray(manifest.phase_order) ? manifest.phase_order : [];
}
function nextConfiguredPhase(manifest, currentPhase) {
const phases = configuredPhases(manifest);
const index = phases.indexOf(currentPhase);
if (index === -1) {
return null;
}
if (index === phases.length - 1) {
return "DONE";
}
return phases[index + 1];
}
function hasCheckpoint(checkpoints, phase) {
return Boolean(checkpoints?.[phase]);
}
function workerResultsCount(state) {
return Object.keys(state.worker_results || {}).length;
}
function inflightWorkersCount(state) {
return Object.keys(state.inflight_workers || {}).length;
}
function firstMissingWorker(state) {
const workerPlan = Array.isArray(state.worker_plan) ? state.worker_plan : [];
const workerResults = state.worker_results || {};
return workerPlan.find(worker => {
const workerKey = typeof worker === "string"
? worker
: `${worker.worker}--${worker.identifier}`;
return !workerResults[workerKey];
}) || null;
}
function skippedCheckpoint(checkpoints, phase) {
const payload = latestPayload(checkpoints, phase);
return payload.skipped_by_mode === true || payload.skipped === true;
}
function allAgentsResolved(state, barrierAgents = []) {
const agents = state.agents || {};
const agentNames = barrierAgents.length > 0 ? barrierAgents : Object.keys(agents);
return agentNames.every(agentName => {
const agent = agents[agentName];
if (!agent) {
return false;
}
return REVIEW_RESOLVED_AGENT_STATUS_SET.has(agent.status);
});
}
function pendingAgentBarrier(manifest, toPhase) {
const barriers = manifest.phase_policy?.agent_resolve_before || [];
return barriers.includes(toPhase);
}
export function validateTransition(manifest, state, checkpoints, toPhase) {
if (state.phase === "DONE" || state.phase === "PAUSED") {
return { ok: false, error: `Invalid transition: ${state.phase} -> ${toPhase}` };
}
const expectedNext = nextConfiguredPhase(manifest, state.phase);
if (!expectedNext || expectedNext !== toPhase) {
return { ok: false, error: `Invalid transition: ${state.phase} -> ${toPhase}` };
}
if (!hasCheckpoint(checkpoints, state.phase)) {
return { ok: false, error: `Checkpoint missing for ${state.phase}` };
}
const policy = manifest.phase_policy || {};
const isDelegatePhase = (policy.delegate_phases || []).includes(state.phase);
if (isDelegatePhase && Array.isArray(state.worker_plan) && state.worker_plan.length > 0
&& workerResultsCount(state) === 0 && !skippedCheckpoint(checkpoints, state.phase)) {
return { ok: false, error: `Worker summaries missing for ${state.phase}` };
}
if (toPhase === policy.aggregate_phase) {
if (Array.isArray(state.worker_plan) && state.worker_plan.length > 0 && workerResultsCount(state) < state.worker_plan.length) {
return { ok: false, error: `Not all planned workers produced summaries (${workerResultsCount(state)}/${state.worker_plan.length})` };
}
if (inflightWorkersCount(state) > 0) {
return { ok: false, error: `Aggregate phase blocked by inflight workers (${inflightWorkersCount(state)})` };
}
}
if (pendingAgentBarrier(manifest, toPhase) && !allAgentsResolved(state, manifest.expected_agents || [])) {
return { ok: false, error: `Required agents are unresolved before ${toPhase}` };
}
if (state.phase === policy.aggregate_phase && !state.aggregation_summary) {
return { ok: false, error: `Aggregation summary missing for ${state.phase}` };
}
if (state.phase === policy.report_phase && !state.report_written) {
return { ok: false, error: `Report checkpoint missing for ${state.phase}` };
}
if (state.phase === policy.results_log_phase && !state.results_log_appended) {
return { ok: false, error: `Results log checkpoint missing for ${state.phase}` };
}
if (state.phase === policy.cleanup_phase && !state.cleanup_verified) {
return { ok: false, error: `Cleanup verification missing for ${state.phase}` };
}
if (toPhase === "DONE") {
if (manifest.required_research !== false && !state.research_completed) {
return { ok: false, error: "Research evidence must be recorded before completion" };
}
if (!state.cleanup_verified) {
return { ok: false, error: "Cleanup must be verified before completion" };
}
if (!state.self_check_passed) {
return { ok: false, error: "Self-check must pass before completion" };
}
if (!state.report_written) {
return { ok: false, error: "Public report must be written before completion" };
}
if (policy.results_log_phase && !state.results_log_appended) {
return { ok: false, error: "Results log must be appended before completion" };
}
if (!state.final_result) {
return { ok: false, error: "Final result not recorded" };
}
if (!state.summary_recorded) {
return { ok: false, error: "Evaluation coordinator summary must be recorded before completion" };
}
}
return { ok: true };
}
export function computeResumeAction(manifest, state, checkpoints) {
if (state.complete || state.phase === "DONE") {
return "Run complete";
}
if (state.phase === "PAUSED") {
if (state.pending_decision?.resume_to_phase) {
return `Resolve pending decision and resume ${state.pending_decision.resume_to_phase}`;
}
return `Paused: ${state.paused_reason || "manual intervention required"}`;
}
if (!hasCheckpoint(checkpoints, state.phase)) {
return `Complete ${state.phase} and write its checkpoint`;
}
const policy = manifest.phase_policy || {};
const isDelegatePhase = (policy.delegate_phases || []).includes(state.phase);
if (isDelegatePhase && Array.isArray(state.worker_plan) && state.worker_plan.length > 0
&& workerResultsCount(state) === 0 && !skippedCheckpoint(checkpoints, state.phase)) {
return `Record worker summaries before advancing from ${state.phase}`;
}
if (Array.isArray(state.worker_plan) && state.worker_plan.length > 0 && workerResultsCount(state) < state.worker_plan.length) {
const missingWorker = firstMissingWorker(state);
const missingKey = typeof missingWorker === "string"
? missingWorker
: `${missingWorker?.worker}--${missingWorker?.identifier}`;
const childRun = missingKey ? state.child_runs?.[missingKey] : null;
if (childRun?.run_id) {
return `Record ${missingKey} summary from child run ${childRun.run_id} before advancing`;
}
return `Record remaining worker summaries (${workerResultsCount(state)}/${state.worker_plan.length}) before advancing`;
}
if (inflightWorkersCount(state) > 0) {
return `Wait for inflight workers to resolve (${inflightWorkersCount(state)} remaining)`;
}
if (state.phase === policy.aggregate_phase && !state.aggregation_summary) {
return `Checkpoint ${state.phase} with aggregation_summary`;
}
if (state.phase === policy.report_phase && !state.report_written) {
return `Checkpoint ${state.phase} with report_written=true`;
}
if (state.phase === policy.results_log_phase && !state.results_log_appended) {
return `Checkpoint ${state.phase} with results_log_appended=true`;
}
if (state.phase === policy.cleanup_phase && !state.cleanup_verified) {
return `Checkpoint ${state.phase} with cleanup_verified=true after evidence review`;
}
if (state.phase === policy.self_check_phase && !state.self_check_passed) {
return `Fix self-check failures, then checkpoint ${state.phase} with pass=true`;
}
if (manifest.required_research !== false && !state.research_completed) {
return "Record mandatory research evidence before final completion";
}
if (pendingAgentBarrier(manifest, nextConfiguredPhase(manifest, state.phase)) && !allAgentsResolved(state, manifest.expected_agents || [])) {
return "Sync agents until every required agent is resolved";
}
if (state.phase === policy.self_check_phase && !state.summary_recorded) {
return "Record evaluation coordinator summary before completion";
}
const nextPhase = nextConfiguredPhase(manifest, state.phase);
return nextPhase ? `Advance to ${nextPhase}` : "No automatic resume action available";
}
references/scripts/evaluation-runtime/lib/store.mjs
// SOURCE-OF-TRUTH: shared/scripts/evaluation-runtime/lib/store.mjs. Edit ONLY here; run `node tools/marketplace/shared.mjs sync`
import { resolve } from "node:path";
import {
createRuntimeStore,
fileExists,
readJsonFile,
resolveTrackedPath,
} from "../../coordinator-runtime/lib/core.mjs";
import {
buildRuntimeStateSchema,
evaluationCoordinatorSummarySchema,
pendingDecisionSchema,
reviewAgentRecordSchema,
} from "../../coordinator-runtime/lib/schemas.mjs";
import { writeRuntimeArtifactJson } from "../../coordinator-runtime/lib/artifacts.mjs";
import { REVIEW_AGENT_STATUSES } from "../../coordinator-runtime/lib/runtime-constants.mjs";
import { assertSchema } from "../../coordinator-runtime/lib/validate.mjs";
const phasePolicySchema = {
type: "object",
additionalProperties: false,
properties: {
delegate_phases: {
type: "array",
items: { type: "string" },
},
aggregate_phase: { type: "string" },
report_phase: { type: "string" },
results_log_phase: { type: "string" },
cleanup_phase: { type: "string" },
self_check_phase: { type: "string" },
agent_resolve_before: {
type: "array",
items: { type: "string" },
},
},
};
const evaluationManifestSchema = {
type: "object",
required: ["skill", "identifier", "project_root", "phase_order", "report_path", "created_at"],
additionalProperties: false,
properties: {
skill: { type: "string", minLength: 1 },
mode: { type: "string" },
identifier: { type: "string", minLength: 1 },
project_root: { type: "string", minLength: 1 },
phase_order: {
type: "array",
minItems: 1,
items: { type: "string", minLength: 1 },
},
phase_policy: phasePolicySchema,
report_path: { type: "string", minLength: 1 },
results_log_path: { type: "string" },
expected_agents: {
type: "array",
items: { type: "string" },
},
required_research: { type: "boolean" },
created_at: { type: "string", format: "date-time" },
},
};
const evaluationStateSchema = buildRuntimeStateSchema({
phase_order: {
type: "array",
minItems: 1,
items: { type: "string", minLength: 1 },
},
phase_data: {
type: "object",
additionalProperties: { type: "object" },
},
worker_plan: {
type: "array",
},
worker_results: {
type: "object",
additionalProperties: { type: "object" },
},
child_runs: {
type: "object",
additionalProperties: { type: "object" },
},
inflight_workers: {
type: "object",
additionalProperties: { type: "object" },
},
agents: {
type: "object",
additionalProperties: reviewAgentRecordSchema,
},
background_agent_cleanup: {
type: "object",
additionalProperties: { type: "object" },
},
refinement_cleanup: {
type: "object",
additionalProperties: { type: "object" },
},
cleanup_verified: { type: "boolean" },
research_completed: { type: "boolean" },
aggregation_summary: {
type: ["object", "null"],
},
report_written: { type: "boolean" },
report_path: { type: ["string", "null"] },
results_log_appended: { type: "boolean" },
results_log_path: { type: ["string", "null"] },
self_check_passed: { type: "boolean" },
summary_recorded: { type: "boolean" },
summary_artifact_path: { type: ["string", "null"] },
summary: { type: ["object", "null"] },
}, [
"phase_order",
"phase_data",
"worker_plan",
"worker_results",
"child_runs",
"inflight_workers",
"agents",
"background_agent_cleanup",
"refinement_cleanup",
"cleanup_verified",
"research_completed",
"aggregation_summary",
"report_written",
"report_path",
"results_log_appended",
"results_log_path",
"self_check_passed",
"summary_recorded",
"summary_artifact_path",
"summary",
]);
function normalizePhaseOrder(phaseOrder) {
const seen = new Set();
return (phaseOrder || []).filter(phase => {
if (!phase || seen.has(phase)) {
return false;
}
seen.add(phase);
return true;
});
}
const evaluationStore = createRuntimeStore({
baseRootParts: [".hex-skills", "evaluation", "runtime"],
manifestSchema: evaluationManifestSchema,
stateSchema: evaluationStateSchema,
normalizeManifest(manifestInput, projectRoot) {
const phaseOrder = normalizePhaseOrder(manifestInput.phase_order);
return {
skill: manifestInput.skill,
mode: manifestInput.mode || "evaluation",
identifier: manifestInput.identifier,
project_root: resolve(projectRoot || process.cwd()),
phase_order: phaseOrder,
phase_policy: manifestInput.phase_policy || {},
report_path: manifestInput.report_path,
results_log_path: manifestInput.results_log_path || "docs/project/.evaluation/results_log.md",
expected_agents: manifestInput.expected_agents || [],
required_research: manifestInput.required_research !== false,
created_at: new Date().toISOString(),
};
},
defaultState(manifest, runId) {
return {
run_id: runId,
skill: manifest.skill,
mode: manifest.mode,
identifier: manifest.identifier,
phase: manifest.phase_order[0],
complete: false,
paused_reason: null,
pending_decision: null,
decisions: [],
final_result: null,
phase_order: manifest.phase_order,
phase_data: {},
worker_plan: [],
worker_results: {},
child_runs: {},
inflight_workers: {},
agents: {},
background_agent_cleanup: {},
refinement_cleanup: {},
cleanup_verified: false,
research_completed: manifest.required_research === false,
aggregation_summary: null,
report_written: false,
report_path: manifest.report_path,
results_log_appended: false,
results_log_path: manifest.results_log_path || null,
self_check_passed: false,
summary_recorded: false,
summary_artifact_path: null,
summary: null,
created_at: new Date().toISOString(),
updated_at: new Date().toISOString(),
};
},
});
export const {
checkpointPhase,
completeRun,
loadActiveRun,
listActiveRuns,
loadRun,
pauseRun,
resolveRunId,
runtimePaths,
saveState,
startRun,
updateState,
} = evaluationStore;
export function setPendingDecision(projectRoot, runId, pendingDecision, reason = "Decision required") {
const run = loadRun(projectRoot, runId);
if (!run) {
return { ok: false, error: "Run not found" };
}
const validation = assertSchema(pendingDecisionSchema, pendingDecision, "pending decision");
if (!validation.ok) {
return validation;
}
if (pendingDecision.resume_to_phase === "PAUSED" || pendingDecision.resume_to_phase === "DONE") {
return { ok: false, error: `Invalid resume_to_phase: ${pendingDecision.resume_to_phase}` };
}
return updateState(projectRoot, runId, state => ({
...state,
phase: "PAUSED",
paused_reason: reason,
pending_decision: pendingDecision,
}), { eventType: "RUN_PAUSED" });
}
export function recordDecision(projectRoot, runId, decision) {
const run = loadRun(projectRoot, runId);
if (!run) {
return { ok: false, error: "Run not found" };
}
if (!run.state.pending_decision) {
return { ok: false, error: "No pending decision recorded" };
}
if (run.state.pending_decision.resume_to_phase === "PAUSED" || run.state.pending_decision.resume_to_phase === "DONE") {
return { ok: false, error: `Invalid resume_to_phase: ${run.state.pending_decision.resume_to_phase}` };
}
const choices = run.state.pending_decision.choices || [];
if (choices.length > 0 && !choices.includes(decision.selected_choice)) {
return { ok: false, error: `Invalid selected_choice: ${decision.selected_choice}. Valid: ${choices.join(", ")}` };
}
const nextDecision = {
kind: run.state.pending_decision.kind,
selected_choice: decision.selected_choice,
answered_at: new Date().toISOString(),
context: decision.context || {},
};
return updateState(projectRoot, runId, state => ({
...state,
phase: state.pending_decision.resume_to_phase,
paused_reason: null,
pending_decision: null,
decisions: [...(state.decisions || []), nextDecision],
}));
}
export function registerAgent(projectRoot, runId, agentRecord) {
const run = loadRun(projectRoot, runId);
if (!run) {
return { ok: false, error: "Run not found" };
}
const validation = assertSchema(reviewAgentRecordSchema, agentRecord, "evaluation agent record");
if (!validation.ok) {
return validation;
}
return updateState(projectRoot, runId, state => ({
...state,
agents: {
...state.agents,
[agentRecord.name]: {
name: agentRecord.name,
status: agentRecord.status || REVIEW_AGENT_STATUSES.LAUNCHED,
prompt_file: agentRecord.prompt_file || null,
result_file: agentRecord.result_file || null,
log_file: agentRecord.log_file || null,
metadata_file: agentRecord.metadata_file || null,
pid: agentRecord.pid || null,
session_id: agentRecord.session_id || null,
started_at: agentRecord.started_at || null,
finished_at: agentRecord.finished_at || null,
exit_code: agentRecord.exit_code ?? null,
error: agentRecord.error || null,
},
},
}));
}
export function recordWorkerResult(projectRoot, runId, summary) {
const run = loadRun(projectRoot, runId);
if (!run) {
return { ok: false, error: "Run not found" };
}
const hasEnvelope = summary
&& typeof summary === "object"
&& typeof summary.schema_version === "string"
&& typeof summary.summary_kind === "string"
&& typeof summary.run_id === "string"
&& typeof summary.identifier === "string"
&& typeof summary.producer_skill === "string"
&& typeof summary.produced_at === "string"
&& summary.payload
&& typeof summary.payload === "object";
if (!hasEnvelope) {
return { ok: false, error: "Worker summary must use the shared summary envelope" };
}
const workerResultKey = `${summary.producer_skill}--${summary.identifier}`;
return updateState(projectRoot, runId, state => {
const nextInflightWorkers = { ...(state.inflight_workers || {}) };
delete nextInflightWorkers[workerResultKey];
return {
...state,
worker_results: {
...state.worker_results,
[workerResultKey]: summary,
},
inflight_workers: nextInflightWorkers,
};
});
}
export function recordSummary(projectRoot, runId, summary) {
const run = loadRun(projectRoot, runId);
if (!run) {
return { ok: false, error: "Run not found" };
}
if (summary?.run_id !== runId) {
return { ok: false, error: `Evaluation coordinator summary run_id must match runtime run_id (${runId})` };
}
const validation = assertSchema(evaluationCoordinatorSummarySchema, summary, "evaluation coordinator summary");
if (!validation.ok) {
return validation;
}
return updateState(projectRoot, runId, state => {
const artifactIdentifier = `${summary.producer_skill}--${summary.identifier}`;
const artifactPath = writeRuntimeArtifactJson(projectRoot, runId, summary.summary_kind, artifactIdentifier, summary);
return {
...state,
summary_recorded: true,
summary_artifact_path: artifactPath,
summary: {
...summary,
payload: {
...summary.payload,
artifact_path: artifactPath,
},
},
};
});
}
export {
fileExists,
readJsonFile,
resolveTrackedPath,
};
references/solution_validation.md
<!-- SOURCE-OF-TRUTH: plugins/agile-workflow/shared/references/solution_validation.md. Edit ONLY here; run `node tools/marketplace/shared.mjs sync` -->
# Solution Validation (Criteria #6, #21)
<!-- SCOPE: Library version (#6) and alternative solutions (#21). Contains version verification, alternatives analysis. -->
<!-- DO NOT add here: Standards validation → standards_validation.md, other criteria → structural_validation.md -->
Detailed rules for library version verification and alternative solutions analysis.
---
## Criterion #6: Library & Version
**Check:** Libraries are latest stable versions
**Penalty:** HIGH (5 points)
✅ **GOOD:**
- "Using express v4.19.2 (latest stable as of 2025-01)"
- "Prisma v5.8.1 (current stable, verified via npm)"
- "OAuth2-proxy v7.6.0 (latest release)"
❌ **BAD:**
- "Using express v3.x" (outdated, v4.x available)
- "Any JWT library" (no specific version)
- "Latest version" (no verification)
**Auto-fix actions:**
1. Check if manuals exist from Phase 3 research (created inline)
2. IF manuals exist:
- Read recommended version from manual (e.g., Manual: oauth2-proxy v7.6.0)
- Compare with Story Technical Notes current version
- IF outdated or unspecified → Update with version from manual
- Add manual reference: "See [Manual: library-vX](docs/manuals/library-vX.md)"
3. IF no manuals exist (fallback to Context7):
- Query `mcp__context7__resolve-library-id(libraryName="[library]")`
- Query `mcp__context7__query-docs(libraryId="...", query="latest version")`
- Extract latest stable version from docs
- Add inline reference: "Library v[version] (verified via Context7)"
3a. IF Context7 also fails (per `references/epistemic_protocol.md`):
- Mark version as `(from training, verify before implementation)` instead of asserting
- Add to Library References table: Source = `training (unverified)`
- DO NOT present training-sourced version as current fact
- Phase 6 summary comment: report as `FROM TRAINING` (not as "verified")
4. Update Linear issue via `save_issue`
5. Add comment: "Library versions verified and updated"
**Example transformation:**
**Before:**
```markdown
## Technical Notes
### Integration Points
- Use Passport.js for authentication
- PostgreSQL database
```
**After (with manuals from Phase 3):**
```markdown
## Technical Notes
### Integration Points
- Passport.js v0.7.0 (latest stable, see [Manual: Passport v0.7](docs/manuals/passport-v0.7.md))
- PostgreSQL v16.1 (compatible with Prisma v5.8.1, see [Manual: Prisma v5](docs/manuals/prisma-v5.md))
### Library References
| Library | Version | Source |
|---------|---------|--------|
| passport | v0.7.0 | docs/manuals/passport-v0.7.md |
| @prisma/client | v5.8.1 | docs/manuals/prisma-v5.md |
| postgresql | v16.1 | Context7 verified |
```
**Skip Fix When:**
- All libraries have specific versions with sources
- Story in Done/Canceled status
---
## Criterion #21: Alternative Solutions
**Check:** Story approach is optimal vs modern alternatives
**Penalty:** MEDIUM (3 points)
**Rule:** Verify the chosen approach against current alternatives. Cross-reference ln-645 audit if available.
**Auto-fix actions:**
1. Search MCP Ref + web for alternatives to primary libraries/patterns in Technical Notes
2. Check for ln-645 audit: `Glob("docs/project/.audit/ln-640/*/645-open-source-replacer*.md")` — take latest by date
3. IF ln-645 report exists AND HIGH-confidence replacement touches Story's affected files:
- Add advisory note to Technical Notes: package name + migration effort
- IF Effort=L → recommend creating separate [REFACTOR] Story instead of blocking current implementation
4. IF better alternative found (without ln-645): add "Alternative Considered" note to Technical Notes
5. Update Linear issue + add comment
**Skip when:** Story in Done/Canceled, no libraries in Technical Notes, or all alternatives already documented.
---
## Criterion #28: Library Feature Utilization
**Check:** Planned custom implementations don't duplicate features of already-declared project dependencies
**Penalty:** MEDIUM (3 points)
**Rule:** Cross-reference Task Implementation Plans against project manifest + Story Library Research. Flag when a Task plans to build something a declared dependency already provides.
✅ **GOOD:**
- "Use `prisma.user.createMany()` for batch insert" (uses existing Prisma method)
- "Retry via Polly's `WaitAndRetryAsync` policy" (uses declared .NET library)
- "Format dates with `date-fns/format`" (uses installed package)
❌ **BAD:**
- "Implement custom retry with exponential backoff" (project has Polly in *.csproj)
- "Write date formatting utility" (date-fns already in package.json)
- "Build manual SQL batch insert loop" (Prisma supports createMany)
**Detection algorithm:**
1. **Dependency Extraction:**
- Read project manifest: `package.json`, `requirements.txt`, `pyproject.toml`, `*.csproj`, `go.mod`, `Cargo.toml`, `build.gradle`, `pom.xml` (Glob up to 2 levels deep; if Story Affected Components point to a subdirectory, check that directory's manifest first)
- Read Story Library Research table (populated by #6)
- Build library set: `{name, version, domain}` for top dependencies
2. **Intent Extraction from Tasks:**
- Scan each Task's Implementation Plan + Technical Approach for custom-build signals:
- Keywords: `"implement custom"`, `"write from scratch"`, `"build manually"`, `"create utility/helper"`, `"hand-roll"`, `"hand-code"`, `"add [X] function/method/class"`
- Co-occurrence required: custom-build keyword + functional noun (`parser`, `validator`, `formatter`, `serializer`, `retry`, `cache`, `scheduler`, `HTTP client`, `logger`, `queue`, `date`, `sort`, `auth`, `crypto`)
- Single keyword matches without functional noun → skip (too vague)
3. **Context7 Cross-Reference (max 3 queries per Story):**
- For top matches (by confidence: custom-build signal strength + library domain overlap):
- `resolve-library-id(libraryName="{library}")` → `query-docs(libraryId="...", query="{extracted_intent} built-in method API")`
- Batch intents per library: if 3 Tasks reference same library → 1 combined query
- Reuse #6's Context7 responses if same library already queried
- Fallback: Context7 → built-in knowledge. No WebSearch for #28
**Confidence levels:**
- **HIGH:** Task says "implement custom X" AND library docs show `library.X()` method exists → **penalty + advisory**
- **MEDIUM:** Task describes behavior that resembles a library feature but uses different terminology → **advisory note only, no penalty**
- **LOW:** Vague overlap → **skip, do not flag**
**Auto-fix actions:**
1. For each HIGH-confidence finding, add advisory note to Task Technical Approach:
```
> **Library Feature Available:** [library] v[version] provides `[method]` for [purpose].
> Consider using instead of custom implementation. [Context7/docs reference]
```
2. If Story has Library Research table: add "Key APIs (underutilized)" subsection with method signatures mapped to Task intents
3. Update Linear issue / file via appropriate provider
4. Add comment: "Library feature utilization check: N findings (advisory)"
**Skip when:**
- No manifest files found in project (no dependencies to check)
- Task has no Implementation Plan section
- Library method already documented in Technical Approach as being used for this purpose
- Story/Task in Done/Canceled status
- Story has 0 external dependencies (pure internal refactoring)
---
## Execution Notes
**Sequential Dependency:**
- Criteria #6, #21, #28 depend on #1-#5 being completed first
- Cannot verify libraries until Technical Notes exist (#1)
- Cannot verify libraries until Standards checked (#5)
- #28 depends on #6 completing first (needs verified Library Research table)
- Group 3 execution order: #6 → #21 → #28
**Research Integration:**
- Phase 3 creates documentation inline
- Criterion #6 reads from Phase 3 docs, fallback to Context7 if needed
- Criterion #28 reuses #6's Context7 responses when querying the same library
- All research completed BEFORE Phase 4 auto-fix begins
**Token Efficiency (#28):**
- Max 3 Context7 queries per Story (not per Task)
- Only query DECLARED dependencies (suggesting new ones is #21's job)
- Batch intents per library into single query
- Reuse #6's cached Context7 responses
**Linear Updates:**
- Criterion auto-fix updates Linear issue once per criterion
- Add single comment summarizing library version updates (#6) and feature findings (#28)
---
**Version:** 4.0.0
**Last Updated:** 2025-01-07
references/standards_validation.md
<!-- SOURCE-OF-TRUTH: plugins/agile-workflow/shared/references/standards_validation.md. Edit ONLY here; run `node tools/marketplace/shared.mjs sync` -->
# Standards Validation (Criterion #5)
<!-- SCOPE: RFC/OWASP/REST/Security compliance criterion #5 ONLY. Contains standard types, compliance checks. -->
<!-- DO NOT add here: Library versions → solution_validation.md, other criteria → structural_validation.md -->
Detailed rules for RFC/OWASP/REST/Security compliance verification.
---
## Criterion #5: Standards Compliance
**Check:** Solution follows industry standards (RFC, OWASP, REST, Security)
**Penalty:** CRITICAL (10 points)
**CRITICAL:** This criterion checked BEFORE KISS/YAGNI (#11-#12). Standards override simplicity.
---
## Common Standards
| Standard | Applies When | Documentation |
|----------|--------------|---------------|
| RFC 6749 (OAuth 2.0) | Auth/tokens | Guide or Manual |
| RFC 7807 (Problem Details) | Error responses | Guide |
| RFC 7231 (HTTP Semantics) | API endpoints | Guide |
| OWASP Top 10 | All apps | Security guide |
| REST Principles | APIs | REST guide |
| OpenAPI 3.x | Public APIs | API guide |
---
## Examples
**GOOD (Compliant):**
```markdown
## Technical Notes
### Standards Compliance
- **OAuth 2.0 (RFC 6749):** Using authorization code flow with PKCE
- **Error Handling (RFC 7807):** Problem Details for HTTP APIs format
- **REST:** Resource-based URLs, proper HTTP methods (GET/POST/PUT/DELETE)
- **Security:** OWASP Top 10 compliance (Helmet.js, input validation, HTTPS)
### Architecture Considerations
OAuth 2.0 compliant authentication flow:
1. Client sends POST /token with { grant_type, username, password }
2. Server validates credentials
3. Returns { access_token, token_type, expires_in, refresh_token }
```
**BAD (Non-Compliant):**
```markdown
## Technical Notes
We'll create custom login endpoint `/do-login` that accepts username/password
and returns a session cookie.
(Violates OAuth RFC 6749 if API requires stateless auth)
```
---
## Auto-fix Actions
1. Read docs created in Phase 3 (guides/manuals/ADRs/research)
2. Query MCP Ref for additional standards:
```
ref_search_documentation(query="[domain] RFC OWASP best practices {current_year}")
```
3. Extract standards/patterns (RFC numbers, OWASP rules, do/don't patterns)
4. Compare Story Technical Notes with standards
5. IF Story violates standard:
- Rewrite Technical Notes with compliant approach
- Add reference to guide/RFC (e.g., "See [Guide-05](docs/guides/05-rest-api-patterns.md)")
6. Add Standards Compliance subsection if missing
7. Update Linear issue via `save_issue`
8. Add comment: "Solution updated to comply with [Standards list]"
---
## Example Transformation
**Before:**
```markdown
## Technical Notes
We'll create custom login endpoint `/do-login` that accepts username/password
and returns a session cookie.
```
**After (Phase 3 findings from oauth2-proxy Manual + Auth ADR):**
```markdown
## Technical Notes
### Standards Compliance
- OAuth 2.0 (RFC 6749): Resource Owner Password Credentials Grant
- Token endpoint: POST /token with grant_type=password
- See [Manual: oauth2-proxy v7](docs/manuals/oauth2-proxy-v7.md) for implementation details
- Architecture decision: [ADR-003: Authentication Strategy](docs/adrs/003-auth-strategy.md)
### Architecture Considerations
OAuth 2.0 compliant authentication flow:
1. Client sends POST /token with { grant_type, username, password }
2. Server validates credentials
3. Returns { access_token, token_type, expires_in, refresh_token }
4. Client uses Bearer token for subsequent requests
```
---
## Standards Override KISS/YAGNI
**Decision Matrix:**
| Proposed Simplification | Standard Check | Decision |
|-------------------------|----------------|----------|
| "Skip refresh tokens" | RFC 6749 requires | REJECT - Keep refresh tokens |
| "Use GET for mutations" | REST violates | REJECT - Use POST/PUT/DELETE |
| "Skip CORS headers" | Security standard | REJECT - Keep CORS |
| "Return only 200/500" | RFC 7231 defines codes | REJECT - Use proper HTTP codes |
| "Custom auth for simplicity" | OAuth RFC 6749 | REJECT - Use OAuth |
**Decision Flow:**
```
Does solution violate Industry Standard (RFC, OWASP, REST)?
-> YES: Keep complex solution, add standard justification
-> NO: Continue to KISS/YAGNI check
```
---
## Skip Fix When
- Solution already references specific RFC/standard
- Story in Done/Canceled status
- Standard not applicable (e.g., OpenAPI for internal-only API)
---
## Execution Notes
**Sequential Dependency:**
- Criterion #5 depends on #1-#4 being completed first
- Cannot verify standards until Story structure is correct (#1-#2)
- Must be checked BEFORE KISS/YAGNI (#11-#12)
**Research Integration:**
- Phase 3 creates documentation inline
- Criterion #5 reads from Phase 3 docs, fallback to MCP Ref if needed
- All research completed BEFORE Phase 4 auto-fix begins
**Linear Updates:**
- Criterion auto-fix updates Linear issue once
- Add comment: "Standards compliance verified - [list of RFCs/standards]"
---
**Version:** 1.0.0
**Last Updated:** 2025-01-07
references/storage_mode_detection.md
<!-- SOURCE-OF-TRUTH: shared/references/storage_mode_detection.md. Edit ONLY here; run `node tools/marketplace/shared.mjs sync` -->
# Storage Mode Operations
Provider routing table for Agile task storage. Provider selection comes from `.hex-skills/environment_state.json -> task_management.provider`; this file does not detect providers.
## Mode Selection
| Provider | Source of truth | ID format |
|---|---|---|
| `linear` | Linear API | `PROJ-123` / UUID |
| `file` | Markdown files + `kanban_board.md` | `Epic N`, `US001`, `T001` |
| `github` | GitHub Issues + Projects v2 | issue `#N` |
Rules:
- Missing environment state defaults to `file`.
- Unknown provider is a contract error unless the skill explicitly falls back to `file`.
- Load only the selected provider transport reference for operation details; do not preload all provider docs.
## Operation Map
| Operation | Linear | File | GitHub |
|---|---|---|---|
| list epics | list projects | glob `epics/*/epic.md` | issues labeled `epic` |
| create epic | save project | write `epic.md` | create issue labeled `epic` |
| list stories | list project issues | glob `stories/*/story.md` | sub-issues |
| create story | save issue | write `story.md` | create issue/sub-issue |
| list tasks | list child issues | glob `tasks/*.md` | sub-issues |
| create task | save child issue | write `T{NNN}.md` | create issue/sub-issue |
| update status | save issue state | edit `**Status:**` | edit Project v2 status |
| add comment | Linear comment | write `comments/{ts}.md` | issue comment |
## Status Map
| Abstract | Linear/File/GitHub value |
|---|---|
| new | `Backlog` |
| ready | `Todo` |
| working | `In Progress` |
| review | `To Review` |
| rework | `To Rework` |
| complete | `Done` |
| removed | `Canceled` |
## Fallback
On Linear/GitHub auth, rate limit, timeout, tool-missing, or server failure:
1. Preserve partial remote evidence when available.
2. Update environment state fallback metadata.
3. Continue in file mode.
**Version:** 4.0.0
**Last Updated:** 2026-04-05
references/structural_validation.md
<!-- SOURCE-OF-TRUTH: plugins/agile-workflow/shared/references/structural_validation.md. Edit ONLY here; run `node tools/marketplace/shared.mjs sync` -->
# Structural Validation (Criteria #1-#4, #23-#24)
<!-- SCOPE: Structure and template compliance criteria #1-#4, Architecture Considerations #23, Assumption Registry #24. -->
<!-- DO NOT add here: Workflow criteria -> workflow_validation.md, standards -> standards_validation.md -->
Detailed rules for Story/Tasks structure, Story statement, Acceptance Criteria, Architecture Considerations, and Assumption Registry validation.
---
## Criterion #1: Story Structure (Template Compliance)
**Check:** Story description follows template structure with 9 sections in order
**Penalty:** LOW (1 point). Skip when Story is Done/Canceled or older than 30 days.
Request FULL Story description from Linear (not truncated) to validate all 9 sections.
**Required Sections (in order):**
1. **Story** (As a / I want / So that)
2. **Context** (Current Situation + Desired Outcome)
3. **Acceptance Criteria** (Given-When-Then: Main Scenarios + Edge Cases + Error Handling)
4. **Implementation Tasks** (List with links)
5. **Test Strategy** (empty placeholder, testing planned separately)
6. **Technical Notes** (Architecture Considerations + Integration Points + Performance & Security)
7. **Definition of Done** (Functionality + Testing + Code Quality)
8. **Dependencies** (Depends On + Blocks)
9. **Assumptions** (typed table with categories)
**Pass:** All 9 sections present in correct order, each non-empty (except Test Strategy — must be empty), required subsections present.
**Fail:** Missing sections -> add with `_TODO: Fill this section_`. Out of order -> reorder. Empty -> add placeholder.
**Auto-fix actions:**
1. Parse current description, identify missing/misplaced sections
2. Add missing sections with TODO placeholders, reorder to match template, add missing subsections
3. Update Linear issue + add comment explaining changes
---
## Criterion #2: Tasks Structure (Template Compliance — EVERY Task)
**Check:** All child Task descriptions follow template structure
**Penalty:** LOW (1 point per Task). Skip when Task is Done/Canceled or older than 30 days.
Request FULL Task description from Linear (not truncated) for EACH Task.
**Required Sections (in order for EACH Task):**
1. **Context** (Current State + Desired State)
2. **Implementation Plan** (Phase 1-3 with checkboxes)
3. **Technical Approach** (Recommended + Why + Patterns + Alternatives)
4. **Acceptance Criteria** (Given-When-Then with checkboxes)
5. **Affected Components** (Implementation + Documentation)
6. **Existing Code Impact** (Refactoring + Tests to Update + Documentation to Update)
7. **Definition of Done** (Checklist)
> [!NOTE]
> Test Strategy removed from Tasks — all tests in Story's final task
**Pass/Fail:** Same rules as #1 applied to EVERY Task individually.
**Auto-fix actions:** Same as #1 but per-Task. Update each Task individually.
**Template Reference:** `references/templates/task_template_implementation.md`
---
## Criterion #3: Story Statement (User-Focused)
**Check:** Clear, specific, user-focused (As a / I want / So that)
**Penalty:** LOW (1 point)
**Rule:** Statement must have persona, capability, and value. "Improve authentication" (vague, no user context) fails.
**Auto-fix actions:**
1. Extract persona from Context, capability from Technical Notes, value from Desired Outcome
2. Rewrite: `As a [persona] I want to [capability] So that [value]`
3. Update Linear issue + add comment
---
## Criterion #4: Acceptance Criteria (Testable, GWT Format)
**Check:** Specific, testable, Given/When/Then format covering Story goal
**Penalty:** MEDIUM (3 points)
**Requirements:** 3-5 ACs in Given/When/Then format.
**Completeness Check (3 scenario types required):**
1. **Happy Path** (1-2 AC) — main success scenarios
2. **Error Handling** (1-2 AC) — invalid inputs, auth failures, system errors
3. **Edge Cases** (1 AC) — boundary conditions, special states, race conditions
**Specificity Check (measurable outcomes required):**
- HTTP status codes (200, 201, 400, 401, 403, 404, 500)
- Response times (<200ms, <1s, <5s)
- Exact error messages ("Invalid credentials", "Token expired")
- Quantifiable metrics (99% uptime, 1000 req/sec)
**Auto-fix actions:**
1. Parse existing AC, convert to Given/When/Then format
2. Add missing scenarios: no error handling -> add 401/403/404/500 ACs; no edge cases -> add boundary ACs
3. Fix specificity: vague terms ("fast", "secure") -> measurable criteria; missing HTTP codes -> suggest specific codes; missing error messages -> add exact text; performance claims -> add timing
4. Update Linear issue + add comment
**Example transformation:**
- Before: "User can login", "Login fails with wrong password"
- After:
1. "Given valid credentials, When user submits login form, Then authenticated and redirected to dashboard" (happy path)
2. "Given invalid password, When user submits, Then 401 with 'Invalid credentials'" (error handling)
3. "Given account locked, When user submits, Then 403 with 'Account locked'" (edge case)
---
## Criterion #23: Architecture Considerations
**Check:** Story Technical Notes has Architecture Considerations subsection with: layers affected, side-effect boundary, orchestration depth
**Penalty:** MEDIUM (3 points)
**Required fields:**
| Field | Description |
|-------|-------------|
| Layers affected | Which architectural layers this Story touches (DB, Service, API, UI) |
| Side-effect boundary | What external state changes (DB writes, API calls, file system, cache) |
| Orchestration depth | How many services/components coordinate (1 = simple, 3+ = complex) |
**Auto-fix actions:**
1. Check if Architecture Considerations subsection exists in Technical Notes
2. IF missing: add section from `story_template.md` with placeholder fields
3. IF present but incomplete: add missing fields with `_TODO:_` placeholders
4. Update Linear issue + add comment
**Skip when:** Story in Done/Canceled status, or Story has no Technical Notes section.
---
## Criterion #24: Assumption Registry
**Check:** Assumptions section with >=1 typed entry; each has Category, Confidence, Invalidation Impact; LOW confidence entries have validation plan in Tasks; child Tasks inherit parent Story assumptions
**Penalty:** MEDIUM (3 points)
**Required table columns:**
| Column | Values |
|--------|--------|
| ID | A1, A2, ... |
| Assumption | Description text |
| Category | TECHNICAL, DEPENDENCY, FEASIBILITY, TIMELINE |
| Confidence | HIGH, MEDIUM, LOW |
| Invalidation Impact | What happens if wrong |
**Rules:**
- >=1 assumption required (every Story has implicit assumptions)
- LOW confidence → must have validation plan in at least one Task
- Child Tasks with "Inherited Assumptions" section must match parent Story IDs + text
**Detection keywords** (scan Technical Notes for implicit assumptions): `assumes`, `expects`, `requires`, `should be available`, `will be`, `must have`, `depends on`
**Auto-fix actions:**
1. Scan Story Technical Notes + Dependencies for implicit assumptions via keywords
2. Create Assumptions table with detected entries, assign Category/Confidence
3. For LOW confidence: add `_TODO: Validate assumption [ID] before implementation_` to relevant Task
4. Verify child Task "Inherited Assumptions" sync with parent Story
5. Update Linear issue + add comment
**Skip when:** Story in Done/Canceled status.
---
**Version:** 3.0.0
**Last Updated:** 2026-02-03
references/templates/adr_template.md
<!-- SOURCE-OF-TRUTH: shared/templates/adr_template.md. Edit ONLY here; run `node tools/marketplace/shared.mjs sync` -->
# ADR-{{NUMBER}}: {{TITLE}}
**Date:** {{DATE}} | **Status:** {{STATUS}} | **Category:** {{CATEGORY}} | **Decision Makers:** {{DECISION_MAKERS}}
<!-- SCOPE: Architecture Decision Record for ONE specific technical decision ONLY. Contains context, decision, rationale, consequences, alternatives (2 with pros/cons). -->
<!-- DO NOT add here: Implementation code -> Task descriptions, Requirements -> Requirements.md, Multiple decisions -> Create separate ADRs, Architecture diagrams -> Architecture.md -->
<!-- DOC_KIND: record -->
<!-- DOC_ROLE: canonical -->
<!-- READ_WHEN: Read when you need the decision context, chosen option, and trade-offs for one technical choice. -->
<!-- SKIP_WHEN: Skip when you only need the current system overview without decision history. -->
<!-- PRIMARY_SOURCES: docs/project/architecture.md, docs/project/tech_stack.md, docs/reference/README.md -->
## Quick Navigation
- [Reference Hub](../README.md)
- [Architecture](../../project/architecture.md)
- [Tech Stack](../../project/tech_stack.md)
## Agent Entry
| Signal | Value |
|--------|-------|
| Purpose | Records one technical decision, the alternatives considered, and the resulting consequences. |
| Read When | You need rationale or history behind a specific architectural choice. |
| Skip When | You only need the current state without decision history. |
| Canonical | Yes |
| Next Docs | [Architecture](../../project/architecture.md), [Tech Stack](../../project/tech_stack.md), [Reference Hub](../README.md) |
| Primary Sources | `docs/project/architecture.md`, `docs/project/tech_stack.md`, `docs/reference/README.md` |
---
## Context
{{CONTEXT}}
(2-3 sentences: background, problem, constraints, forces driving this decision)
---
## Decision
{{DECISION}}
(1-2 sentences: clear statement of what we decided, including version/constraints if applicable)
---
## Rationale
{{RATIONALE}}
(2-3 key reasons WHY we chose this solution)
---
## Consequences
**Positive:**
{{POSITIVE_CONSEQUENCES}}
(2-4 bullets: benefits, advantages)
**Negative:**
{{NEGATIVE_CONSEQUENCES}}
(2-4 bullets: trade-offs, costs, technical debt)
---
## Alternatives Considered
| Alternative | Pros | Cons | Why Rejected |
|-------------|------|------|--------------|
| {{ALT_1_NAME}} | {{ALT_1_PROS}} | {{ALT_1_CONS}} | {{ALT_1_REJECTION}} |
| {{ALT_2_NAME}} | {{ALT_2_PROS}} | {{ALT_2_CONS}} | {{ALT_2_REJECTION}} |
---
## Related Decisions
{{RELATED_DECISIONS}}
(Optional: ADR-001, ADR-003)
---
## Maintenance
**Last Updated:** {{DATE}}
**Update Triggers:**
- Decision status changes
- Consequences change materially
- Related ADR references change
**Verification:**
- [ ] Decision still reflects the accepted choice
- [ ] Alternatives and consequences still match current understanding
- [ ] Related ADR links resolve
references/templates/guide_template.md
<!-- SOURCE-OF-TRUTH: shared/templates/guide_template.md. Edit ONLY here; run `node tools/marketplace/shared.mjs sync` -->
# [Pattern Name]
<!-- SCOPE: Pattern documentation using TABLES and brief descriptions ONLY.
Contains: principle (industry standard), implementation (project-specific), Do/Don't/When patterns, sources. -->
<!-- DO NOT add here: Architectural decisions -> ADR, Multiple patterns -> Separate guides, Requirements -> Requirements.md, API specs -> api_spec.md -->
<!-- NO_CODE_EXAMPLES: Guides document PATTERNS, not implementations.
FORBIDDEN: Full function implementations, class definitions, code blocks > 5 lines
ALLOWED: Do/Don't/When tables, method signatures (1 line), pseudocode (1-3 lines max)
INSTEAD OF CODE: Reference real code location, e.g., "See src/hooks/usePlan.ts:15-30"
CORRECT table content example:
| Call `invalidateQueries()` | Manually update cache | After POST/PUT/DELETE |
WRONG (too much code):
| useQuery({ queryKey: [...], queryFn: () => fetch(...) }) | fetch() in useEffect | ... | -->
<!-- DOC_KIND: reference -->
<!-- DOC_ROLE: canonical -->
<!-- READ_WHEN: Read when you need reusable project patterns, dos and don'ts, or project-specific guidance around one topic. -->
<!-- SKIP_WHEN: Skip when you only need a historical decision or generic vendor API reference. -->
<!-- PRIMARY_SOURCES: docs/project/architecture.md, docs/reference/adrs/, src/ -->
## Quick Navigation
- [Reference Hub](../README.md)
- [Architecture](../../project/architecture.md)
- [ADRs](../adrs/)
## Agent Entry
| Signal | Value |
|--------|-------|
| Purpose | Captures reusable project-specific patterns around one focused topic. |
| Read When | You need conventions, dos and don'ts, or project-specific implementation guidance. |
| Skip When | You only need a one-off decision record or external package API reference. |
| Canonical | Yes |
| Next Docs | [Architecture](../../project/architecture.md), [ADRs](../adrs/), [Reference Hub](../README.md) |
| Primary Sources | `docs/project/architecture.md`, `docs/reference/adrs/`, `src/` |
## Principle
{{PRINCIPLE}}
(1-2 sentences describing the core industry best practice with version/date citation)
## Our Implementation
{{OUR_IMPLEMENTATION}}
(1 paragraph: how we apply this pattern in our project context, which layers/components affected, key integration points)
## Patterns
| Do This | Don't Do This | When to Use |
|-----------|------------------|-------------|
| {{PATTERN_1_DO}} | {{PATTERN_1_DONT}} | {{PATTERN_1_WHEN}} |
| {{PATTERN_2_DO}} | {{PATTERN_2_DONT}} | {{PATTERN_2_WHEN}} |
| {{PATTERN_3_DO}} | {{PATTERN_3_DONT}} | {{PATTERN_3_WHEN}} |
## Sources
- {{SOURCE_1}}
- {{SOURCE_2}}
- Internal: [Architecture.md](../project/architecture.md)
## Related
**ADRs:** {{RELATED_ADRS}}
**Guides:** {{RELATED_GUIDES}}
## Maintenance
**Last Updated:** {{DATE}}
**Update Triggers:**
- Pattern guidance changes
- Related ADRs change
- Source locations move materially
**Verification:**
- [ ] Do/Don't/When rows still match current project practice
- [ ] Related links resolve
- [ ] Guidance still references current architecture
references/templates/manual_template.md
<!-- SOURCE-OF-TRUTH: shared/templates/manual_template.md. Edit ONLY here; run `node tools/marketplace/shared.mjs sync` -->
# {{PACKAGE_NAME}} v{{VERSION}} - Usage Manual
<!-- SCOPE: API/Method reference ONLY. Contains technical descriptions, parameters, return types. -->
<!-- DO NOT add: How-to instructions -> Guide, Decision rationale -> ADR -->
<!-- NO_CODE_EXAMPLES: Manuals document APIs, not implementations.
FORBIDDEN: Code blocks, implementation snippets, code examples
ALLOWED: Method signatures (1 line inline), parameter tables, ASCII diagrams
INSTEAD OF CODE: Link to official documentation or real project file
CORRECT: "See [Official docs: CreateClient()](https://docs.example.com/CreateClient)"
CORRECT: "See [src/Services/RateLimiter.cs:42](src/Services/RateLimiter.cs#L42)"
WRONG: Full code block with usage example -->
<!-- DOC_KIND: reference -->
<!-- DOC_ROLE: canonical -->
<!-- READ_WHEN: Read when you need package-specific API facts, methods, or version notes used by the project. -->
<!-- SKIP_WHEN: Skip when you only need project patterns or architectural decisions. -->
<!-- PRIMARY_SOURCES: docs/reference/README.md, official docs, package manifests, src/ -->
## Quick Navigation
- [Reference Hub](../README.md)
- [Architecture](../../project/architecture.md)
- [Tech Stack](../../project/tech_stack.md)
## Agent Entry
| Signal | Value |
|--------|-------|
| Purpose | Summarizes the external package API surface actually relevant to the project. |
| Read When | You need package methods, parameters, return types, or version-specific notes. |
| Skip When | You only need project-specific patterns or architectural rationale. |
| Canonical | Yes |
| Next Docs | [Tech Stack](../../project/tech_stack.md), [Architecture](../../project/architecture.md), [Reference Hub](../README.md) |
| Primary Sources | `docs/reference/README.md`, official docs, package manifests, `src/` |
## Package Information
**Package:** {{PACKAGE_NAME}}
**Version:** {{VERSION}}
**Installation:** `{{INSTALL_COMMAND}}`
**Documentation:** {{OFFICIAL_DOCS_URL}}
## Overview
{{PACKAGE_DESCRIPTION}}
## Methods We Use
---
### {{METHOD_1_NAME}}
**Signature:** `{{METHOD_1_SIGNATURE}}`
**Description:** {{METHOD_1_DESCRIPTION}}
**Parameters:**
| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| {{PARAM_1_NAME}} | {{PARAM_1_TYPE}} | {{PARAM_1_REQUIRED}} | {{PARAM_1_DEFAULT}} | {{PARAM_1_DESCRIPTION}} |
| {{PARAM_2_NAME}} | {{PARAM_2_TYPE}} | {{PARAM_2_REQUIRED}} | {{PARAM_2_DEFAULT}} | {{PARAM_2_DESCRIPTION}} |
**Returns:**
{{RETURN_TYPE}} - {{RETURN_DESCRIPTION}}
**Raises:**
| Exception | Condition |
|-----------|-----------|
| `{{EXCEPTION_1}}` | {{EXCEPTION_1_CONDITION}} |
| `{{EXCEPTION_2}}` | {{EXCEPTION_2_CONDITION}} |
**Documentation:** [Official docs: {{METHOD_1_NAME}}]({{METHOD_1_DOCS_URL}})
**Project usage:** See [{{PROJECT_FILE_PATH}}]({{PROJECT_FILE_PATH}}) (if exists)
{{METHOD_1_WARNINGS}}
---
### {{METHOD_2_NAME}}
**Signature:** `{{METHOD_2_SIGNATURE}}`
**Description:** {{METHOD_2_DESCRIPTION}}
**Parameters:**
| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| {{PARAM_1_NAME}} | {{PARAM_1_TYPE}} | {{PARAM_1_REQUIRED}} | {{PARAM_1_DEFAULT}} | {{PARAM_1_DESCRIPTION}} |
**Returns:** {{RETURN_TYPE}} - {{RETURN_DESCRIPTION}}
**Raises:**
| Exception | Condition |
|-----------|-----------|
| `{{EXCEPTION_1}}` | {{EXCEPTION_1_CONDITION}} |
**Documentation:** [Official docs: {{METHOD_2_NAME}}]({{METHOD_2_DOCS_URL}})
---
## Configuration
<!-- TABLE-FIRST: Configuration MUST be in table format, not code -->
{{CONFIGURATION_SECTION}}
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| {{CONFIG_1_NAME}} | {{CONFIG_1_TYPE}} | {{CONFIG_1_DEFAULT}} | {{CONFIG_1_DESCRIPTION}} |
| {{CONFIG_2_NAME}} | {{CONFIG_2_TYPE}} | {{CONFIG_2_DEFAULT}} | {{CONFIG_2_DESCRIPTION}} |
## Known Limitations
{{LIMITATIONS}}
* {{LIMITATION_1}}
* {{LIMITATION_2}}
## Version-Specific Notes
{{VERSION_NOTES}}
## Related Resources
* **Official Documentation:** {{OFFICIAL_DOCS_LINK}}
* **GitHub Repository:** {{GITHUB_URL}}
* **Related Guides:** {{RELATED_GUIDES}}
* **Related ADRs:** {{RELATED_ADRS}}
## Maintenance
**Last Updated:** {{DATE}}
**Update Triggers:**
- Package version changes
- Relevant methods or configuration usage change
- Official documentation changes materially
**Verification:**
- [ ] Version matches the project dependency
- [ ] Method signatures still match current package usage
- [ ] Official documentation links resolve
references/templates/mcp_ref_findings_template.md
# MCP Ref Validation Findings
**Identifier:** {identifier}
**Stack:** {detected_stack}
**Date:** {date}
**Queries:** {query_count}
## Corrections Applied
| # | Criterion | File | Line | Topic | Plan Statement | Official Docs Say | Action |
|---|-----------|------|------|-------|----------------|-------------------|--------|
| 1 | #5 | {file} | {line} | {topic} | {before} | {after} (per {source}) | CORRECTED |
## Validated (No Issues)
| Criterion | Topic | Plan Statement | Confirmed By |
|-----------|-------|----------------|-------------|
| #6 | {topic} | {statement} | {source}: "{query}" |
## Review Needed (Ambiguous)
| Topic | Plan Statement | Finding | Reason |
|-------|----------------|---------|--------|
| {topic} | {statement} | {finding} | Ambiguous -- agents decide |
## Research Sources
| Topic | Query | Tool | Result |
|-------|-------|------|--------|
| {topic} | "{query}" | ref_search_documentation | Found / Not found |
references/templates/research_template.md
<!-- SOURCE-OF-TRUTH: shared/templates/research_template.md. Edit ONLY here; run `node tools/marketplace/shared.mjs sync` -->
# RSH-{{NUMBER}}: {{QUESTION}}
**Date:** {{DATE}} | **Status:** {{STATUS}}
**Type:** {{TYPE}} | **Timebox:** {{TIMEBOX}}
<!-- SCOPE: Research document answering ONE specific question.
Types: Technical, Market, Competitor, Requirements, Feasibility, Other
Status: In Progress, Completed, Superseded -->
<!-- DO NOT add here: Decisions -> ADR, Patterns -> Guide, API reference -> Manual -->
<!-- DOC_KIND: reference -->
<!-- DOC_ROLE: canonical -->
<!-- READ_WHEN: Read when you need the investigation record for one focused question. -->
<!-- SKIP_WHEN: Skip when the question has already been converted into a final ADR, guide, or manual. -->
<!-- PRIMARY_SOURCES: docs/reference/README.md, cited sources, project artifacts -->
## Quick Navigation
- [Reference Hub](../README.md)
- [Research Directory](./)
- [Architecture](../../project/architecture.md)
## Agent Entry
| Signal | Value |
|--------|-------|
| Purpose | Captures evidence, findings, and next actions for one investigation question. |
| Read When | You need the research trail or source-backed findings for one topic. |
| Skip When | The knowledge has already been promoted into an ADR, guide, or manual. |
| Canonical | Yes |
| Next Docs | [Reference Hub](../README.md), [Architecture](../../project/architecture.md) |
| Primary Sources | `docs/reference/README.md`, cited sources, project artifacts |
---
## Question
{{QUESTION_FULL}}
(Clear question formulation. Examples:
- "Which WebSocket framework best fits our use-case?"
- "What solutions do competitors use for real-time notifications?"
- "What is the B2B SaaS market landscape?")
---
## Context
{{CONTEXT}}
(Why is this research needed? What problem/uncertainty? 2-4 sentences)
---
## Methodology
{{METHODOLOGY}}
(How was research conducted? Approaches by Type:
- **Technical:** Documentation, benchmarks, RFCs, PoC
- **Market:** Industry reports, blogs, trend articles
- **Competitor:** Product pages, reviews, feature comparisons, demos
- **Requirements:** User feedback, support tickets, forums, interviews
- **Feasibility:** Prototypes, local tests, expert consultations
- **Feature Demand:** Competitor features + blogs/socials + user complaints)
---
## Findings
{{FINDINGS}}
(What was found? Preferred formats:
- Comparison tables
- Facts lists with sources
- Metrics and numbers)
---
## Conclusions
{{CONCLUSIONS}}
(Brief answer to the question + recommendation if any)
---
## Next Steps
{{NEXT_STEPS}}
(What's next? Examples:
- "Create ADR for selected solution"
- "Conduct PoC with Option A"
- "Additional research on X required"
- "No action required")
---
## Sources
{{SOURCES}}
## Maintenance
**Last Updated:** {{DATE}}
**Update Triggers:**
- Research status changes
- Findings are superseded by newer evidence
- Follow-up actions or linked docs change
**Verification:**
- [ ] Sources still resolve
- [ ] Findings still match cited evidence
- [ ] Next steps reflect current project state
references/templates/task_template_implementation.md
<!-- SOURCE-OF-TRUTH: shared/templates/task_template_implementation.md. Edit ONLY here; run `node tools/marketplace/shared.mjs sync` -->
# Task Title
<!-- Task Size Guideline: Optimal 3-5 hours development time (atomic, testable unit). Too small < 3h -> combine with related work. Too large > 8h -> decompose further. -->
<!-- SCOPE: Implementation tasks ONLY. DO NOT create new tests in this task.
New tests (E2E/Integration/Unit) are created separately by test planner after manual testing passes.
This task may update existing tests if implementation changes break them. -->
**Epic:** [Epic N - Epic Name](link) *(optional)*
**User Story:** [USXXX Story Name](link-or-path) *(parent story)*
**Related:** T001, T002
**Parallel Group:** {N}
---
## Context
### Current State
- What exists now?
- What's the problem or limitation?
### Desired State
- What should exist after completion?
- What benefits will this bring?
### Inherited Assumptions
- **A1 (FEASIBILITY):** {{relevant assumption from parent Story}}
---
## Implementation Plan
### Phase 1: [Description]
- [ ] Step 1
- [ ] Step 2
### Phase 2: [Description]
- [ ] Step 1
- [ ] Step 2
### Phase 3: [Description]
- [ ] Step 1
- [ ] Step 2
---
## Technical Approach
### Recommended Solution
**Library/Framework:** [name] v[version] ([stability: LTS/stable/beta])
**Documentation:** [official docs URL]
**Standards compliance:** [RFC/spec if applicable, e.g., RFC 6749 for OAuth 2.0]
### Key APIs
**Primary methods:**
- `[method_signature]` - [purpose and when to use]
- `[method_signature]` - [purpose and when to use]
- `[method_signature]` - [purpose and when to use]
**Configuration:**
- `[parameter]`: [value/type] - [purpose and impact]
- `[parameter]`: [value/type] - [purpose and impact]
### Implementation Pattern
**Core logic:**
```pseudocode
[High-level pseudocode showing main integration flow]
[Focus on HOW to integrate library/API, not full business logic]
[5-10 lines maximum - this is a guide, not implementation]
```
**Integration points:**
- **Where:** [file/module path where integration happens]
- **How:** [dependency injection / direct import / middleware / decorator / etc.]
- **When:** [startup / request handler / background task / etc.]
<!-- Include when this task covers ACs where an actor must invoke/consume a mechanism (criterion #17c). Remove if task is pure infrastructure with no consumer. -->
### Scenario Integration
> For each AC this task covers where an actor (user, bot, scheduler, handler, pipeline) must invoke or consume a mechanism, trace all 5 segments.
| AC | Actor Trigger | Entry Point | Discovery | Usage Context | Observable Outcome |
|----|--------------|-------------|-----------|---------------|-------------------|
| [AC ref] | [What initiates: user message, timer fire, webhook arrival, event enqueue] | [Named mechanism: tool/endpoint/command/component/config/prompt section] | [How the actor's system finds it: config registration, route mount, plugin load, system prompt, env var] | [What the actor's system needs to invoke it: instructions, prompts, schemas, param guidance, docs] | [Verifiable result: response, state change, log entry, notification] |
### Why This Approach
- [Reason 1: Standards compliance or industry best practice reference]
- [Reason 2: Performance/Security/Maintainability/Team familiarity benefit]
### Patterns Used
- [Pattern 1] - [purpose in this context]
- [Pattern 2] - [purpose in this context]
### Known Limitations
- [Limitation 1: e.g., no async support, memory constraints] - [workaround or mitigation if any]
- [Limitation 2: e.g., compatibility issue, unsupported feature] - [impact on implementation]
### Error Handling Strategy
**Expected errors (this task):**
| Error Type | HTTP Status | When Occurs | User Message |
|------------|-------------|-------------|--------------|
| [ValidationError] | 400 | [Invalid input] | [Friendly message] |
| [AuthError] | 401/403 | [Token expired/No permission] | [Friendly message] |
| [NotFoundError] | 404 | [Resource missing] | [Friendly message] |
**Retry logic:**
- Retryable: [List transient errors: 503, timeout, connection reset]
- Backoff: [exponential with jitter, max 3 retries, initial 1s]
**Validation approach:**
- Input validation: [Pydantic/Zod schema, fail-fast]
- Error response: [Match Story Error Handling Strategy format]
### Logging Requirements
**Log events (this task):**
| Event | Level | Data Fields | Purpose |
|-------|-------|-------------|---------|
| [request_received] | INFO | [correlation_id, user_id, endpoint] | [Audit] |
| [validation_failed] | WARN | [correlation_id, field, error] | [Debug] |
| [operation_completed] | INFO | [correlation_id, duration_ms] | [Metrics] |
| [unexpected_error] | ERROR | [correlation_id, stack_trace] | [Alerting] |
**Audit trail:**
- Track: [Who, What, When, Outcome for sensitive operations]
**Performance logging:**
- Threshold: [Log WARN if operation > 500ms]
<!-- Include when task has destructive ops per references/destructive_operation_safety.md. Remove if N/A. -->
### Destructive Operation Safety
> **MANDATORY READ:** `references/destructive_operation_safety.md`
**Operations:** [list each destructive operation]
**Severity:** [CRITICAL / HIGH / MEDIUM per shared reference classification]
**Backup plan:** [what + how to verify]
**Rollback plan:** [undo procedure + tested where]
**Blast radius:** [resources + scope + downtime]
**Environment guard:** [env check or admin confirmation]
**Preview / dry-run:** [what-if output, SQL diff, terraform plan — attach or reference]
### Alternatives Considered
- **Alternative 1:** [name] - [why rejected: outdated/over-engineered/non-standard/lacking feature]
- **Alternative 2:** [name] - [why rejected: performance/complexity/compatibility]
---
**SCOPE NOTE:** This Technical Approach should be 200-300 words max. Focus on KEY APIs (2-5 methods) and integration points, NOT exhaustive API documentation. This specification guides implementation without prescribing every detail. Executor discovers full implementation specifics during execution.
---
## Acceptance Criteria
- [ ] **Given** [context] **When** [action] **Then** [result]
- [ ] **Given** [context] **When** [action] **Then** [result]
- [ ] **Given** [context] **When** [action] **Then** [result]
---
## Affected Components
### Implementation
- `path/to/file` - Changes
- Side-effects introduced: [DB writes, notifications, events, HTTP calls]
- Side-effect depth: [1-2 = flat, 3+ = document justification]
### Documentation (REQUIRED in this task)
- `README.md` - Feature documentation
- `{{DOCS_PATH}}/api.md` - API updates
---
## Existing Code Impact
### Refactoring Required
- `path/to/file` - What needs refactoring and why
### Tests to Update (ONLY Existing Tests Affected by This Task)
**SCOPE:** ONLY list existing tests that break due to implementation changes (refactoring, logic updates).
DO NOT create new tests here. New tests are created by test planner after manual testing.
**Examples of valid updates:**
- Mock/stub changes when function signatures change
- Assertion updates when return values change
- Test data updates when validation logic changes
- `tests/path/test_file` - Why this existing test needs updates
### Documentation to Update
- `{{DOCS_PATH}}/file.md` - Existing docs to update
---
## Definition of Done
- [ ] All acceptance criteria met
- [ ] All existing code refactored (no backward compatibility / legacy code left)
- [ ] All existing tests updated (if any were affected by implementation changes)
- [ ] NO new tests created (new tests are in Story's final test task by test planner)
- [ ] Documentation updated
- [ ] Code reviewed
---
## Template Placeholders
When copying this template to a project, replace these placeholders:
| Placeholder | Source | Example |
|-------------|--------|---------|
| `{{TEAM_ID}}` | docs/tasks/kanban_board.md | "API" |
| `{{DOCS_PATH}}` | Standard path | "docs" |
---
**Template Version:** 8.0.0 (Moved to references/templates/, added placeholders, removed skill-specific references)
**Last Updated:** 2025-01-07
references/traceability_validation.md
<!-- SOURCE-OF-TRUTH: plugins/agile-workflow/shared/references/traceability_validation.md. Edit ONLY here; run `node tools/marketplace/shared.mjs sync` -->
# Traceability & Verification (Criteria #16-#17, #17b-#17c, #22)
<!-- SCOPE: Story-Task alignment (#16), AC coverage (#17), AC invocability (#17b), scenario completeness (#17c), and AC verify methods (#22). -->
<!-- DO NOT add here: Structural validation -> structural_validation.md, quality -> quality_validation.md -->
Detailed rules for Story-Task alignment, AC-Task coverage, AC invocability, scenario completeness, and AC verification methods.
---
## Criterion #16: Story-Task Alignment
**Check:** Each Task implements part of Story statement (no orphan Tasks)
**Penalty:** MEDIUM (3 points)
**Rule:** Every Task must contribute to the Story goal. Tasks unrelated to Story statement are orphans. Skip when Story has no Tasks, or Story is Done/Canceled.
**Auto-fix actions:**
1. Extract Story Statement keywords (user, OAuth, log in, protected resources)
2. For EACH Task, check if title/description relates to Story keywords
3. IF Task misaligned: add TODO `_TODO: Verify this Task belongs to Story scope_` + warn user
4. IF multiple misaligned Tasks -> suggest splitting Story
5. Update Linear issue, add comment: "Story-Task alignment verified - [N] aligned, [M] warnings"
---
## Criterion #17: AC-Task Coverage
**Check:** Each Acceptance Criterion (AC) has at least one implementing Task
**Penalty:** MEDIUM (3 points)
**Rule:** Every AC must map to at least one Task. No ACs left without implementation. Skip when Story has no Tasks, or Story is Done/Canceled, or all ACs already have coverage notes.
### AC-Task Mapping Algorithm
1. Parse all Acceptance Criteria from Story
2. For EACH AC, find implementing Task(s) by keyword matching
3. Build coverage matrix:
```markdown
## AC-Task Coverage Matrix
| AC | Task | Status |
|----|------|--------|
| 1 | T1 | Covered |
| 2 | T2 | Covered |
| 3 | - | MISSING |
```
4. **Coverage Quality Check** — for each AC->Task mapping, extract AC requirements:
- **HTTP codes** (200, 201, 400, 401, 403, 404, 500)
- **Error messages** ("Invalid token", "User not found", "Access denied")
- **Performance criteria** (<200ms, <1s, 1000 req/sec)
- **Timing constraints** (token expires in 1h, session timeout 30min)
5. **Scoring:**
- **STRONG:** Task mentions all AC requirements (HTTP code + message + timing)
- **WEAK:** Task exists but missing specific requirements
- **MISSING:** No Task for AC
6. **Auto-fix:**
- MISSING AC: add TODO to Story `_TODO: Add Task for AC #[N]: "[AC text]"_`
- WEAK coverage: add TODO to Task `_TODO: Ensure AC requirement: [specific requirement]_`
7. Update coverage matrix with quality indicators:
```markdown
| AC | Task | Status |
|----|------|--------|
| 1: Valid credentials -> 200 | T1 | STRONG (mentions 200, success flow) |
| 2: Invalid token -> 401 | T2 | WEAK (mentions validation, no 401/message) |
| 3: Timeout <200ms | - | MISSING |
```
8. Update Linear issue, add comment: "AC coverage - [N]/[M] ACs ([K] STRONG, [L] WEAK, [P] MISSING)"
---
## Criterion #17b: AC Invocability
**Check:** Every AC where an actor (user, bot, scheduler, handler, pipeline) must invoke or consume a mechanism has a covering Task whose Implementation Plan names a concrete mechanism
**Penalty:** HIGH (5 points per AC)
**Rule:** For each AC that describes an actor invoking or consuming a mechanism, at least one covering Task must have an Implementation Plan that names a concrete mechanism (MCP tool, API endpoint, CLI command, UI component, chat handler, config file, system prompt section, cron handler). Infrastructure-only tasks (queue, registry, store) do NOT satisfy ACs that require something to *use* that infrastructure. Vague mechanism references ("via X or Y", "direct function access") = violation. Skip when Story has no Tasks, or Story is Done/Canceled.
### Invocability Check Algorithm
1. Parse all Acceptance Criteria from Story
2. For EACH AC, determine if an actor must invoke or consume a mechanism:
- Look for action verbs: "invokes", "calls", "triggers", "sends", "receives", "configures", "uses", "consumes", "publishes", "subscribes"
- Look for actor keywords: "user", "bot", "scheduler", "handler", "pipeline", "admin", "system", "agent", "service"
3. For EACH invocable AC, find covering Task(s) and inspect their Implementation Plan:
- **CONCRETE:** Implementation Plan names a specific mechanism type (e.g., "MCP tool `ref_search_documentation`", "POST /api/tasks endpoint", "CLI command `npm run validate`", "`TaskDialog` UI component", "cron handler in `scheduler.ts`")
- **INFRASTRUCTURE-ONLY:** Task only creates infrastructure (queue, registry, store, schema, table) without a consuming layer — does NOT satisfy the AC
- **VAGUE:** Mechanism described ambiguously ("via X or Y", "direct function access", "through the system") — violation
4. Build invocability matrix:
```markdown
## AC Invocability Matrix
| AC | Actor | Mechanism Required | Covering Task | Mechanism Named | Status |
|----|-------|--------------------|---------------|-----------------|--------|
| 1 | user | login endpoint | T1 | POST /auth/login | CONCRETE |
| 2 | scheduler | cleanup trigger | T3 | cron handler cleanup.ts | CONCRETE |
| 3 | bot | notification send | T2 | (queue only) | INFRASTRUCTURE-ONLY |
| 4 | user | config update | - | - | MISSING |
```
5. **Auto-fix:**
- INFRASTRUCTURE-ONLY: identify missing consuming mechanism, add to existing task's Implementation Plan with explicit "Invocation Mechanism" section, or flag that a new task is needed
- MISSING: add TODO to Story `_TODO: AC #[N] requires a task with concrete mechanism for: "[AC text]"_`
- VAGUE: replace vague reference with specific mechanism in task's Implementation Plan
6. Update Linear issue, add comment: "AC Invocability — [N]/[M] invocable ACs ([K] CONCRETE, [L] violations)"
---
## Criterion #17c: Scenario Completeness
**Check:** For each invocable AC, covering task(s) must collectively address all 5 scenario segments
**Penalty:** HIGH (5 points per AC)
**Rule:** For each AC where an actor must invoke or consume a mechanism (as identified by #17b), the covering task(s) must collectively address all 5 segments: (1) Actor trigger — what initiates the scenario; (2) Entry point — the named mechanism from #17b; (3) Discovery — how the actor's system finds/loads the mechanism at runtime; (4) Usage context — what the actor's system needs to correctly invoke the mechanism; (5) Observable outcome — the verifiable result. Missing segment = violation. Skip when Story has no Tasks, or Story is Done/Canceled, or AC is not invocable per #17b.
### Scenario Completeness Check Algorithm
1. Use the invocability matrix from #17b (only ACs with CONCRETE status)
2. For EACH invocable AC with a concrete mechanism, check all 5 segments across covering task(s):
- **(1) Actor trigger:** What initiates the scenario? (e.g., "user clicks Submit", "cron fires at midnight", "webhook receives POST", "pipeline stage completes")
- **(2) Entry point:** The named mechanism from #17b (e.g., "POST /api/tasks", "MCP tool `ref_search_documentation`", "CLI `npm run validate`")
- **(3) Discovery:** How does the actor's system find/load the mechanism at runtime? (e.g., "registered in MCP manifest", "route registered in Express app", "command registered in package.json scripts", "component imported in App.tsx")
- **(4) Usage context:** What does the actor's system need to correctly invoke the mechanism? (e.g., "auth token in header", "config loaded from .env", "schema validated before send", "permissions checked")
- **(5) Observable outcome:** The verifiable result (e.g., "returns 200 with created entity", "log entry written", "status transitions to Done", "notification sent to channel")
3. Build scenario completeness matrix:
```markdown
## Scenario Completeness Matrix
| AC | Mechanism | Trigger | Entry | Discovery | Context | Outcome | Missing |
|----|-----------|---------|-------|-----------|---------|---------|---------|
| 1 | POST /auth/login | user clicks Login | YES | YES | YES | YES | - |
| 2 | cron cleanup.ts | midnight schedule | YES | NO | YES | YES | Discovery |
| 3 | MCP tool search | agent invokes | YES | YES | NO | YES | Context |
```
4. **Scoring:**
- **COMPLETE:** All 5 segments present across covering task(s)
- **INCOMPLETE:** 1+ segments missing — violation (5 points per AC)
5. **Auto-fix:**
- For each missing segment, either:
(a) Add the missing segment to an existing covering task's Implementation Plan under a "Scenario Integration" section
(b) Flag that a new section is needed if no existing task can logically own the segment
- Template for Scenario Integration section:
```markdown
### Scenario Integration
- **Actor trigger:** [what initiates]
- **Entry point:** [mechanism name]
- **Discovery:** [how found/loaded at runtime]
- **Usage context:** [prerequisites for correct invocation]
- **Observable outcome:** [verifiable result]
```
6. Update Linear issue, add comment: "Scenario Completeness — [N]/[M] invocable ACs ([K] COMPLETE, [L] INCOMPLETE with [P] missing segments)"
---
## Criterion #22: AC Verify Methods
**Check:** Every Task AC has a `verify:` method; at least 1 non-inspect method per Task
**Penalty:** MEDIUM (3 points)
**Rule:** Each Task AC must specify how to verify it. Three method types:
| Type | When to use | Examples |
|------|------------|---------|
| `test` | Business logic, data transforms | Unit test, integration test |
| `command` | HTTP endpoints, CLI operations | `curl`, `npm run`, DB query |
| `inspect` | Config, docs, code structure | Code review, log check |
At least 1 AC per Task must use `test` or `command` (not all `inspect`). Skip when Task is Done/Canceled.
**Mapping heuristic:**
- HTTP endpoints, API routes → `command`
- DB operations, file I/O → `inspect`
- Business logic, calculations, transforms → `test`
**Auto-fix actions:**
1. For EACH Task, scan ACs for missing `verify:` line
2. Generate `verify:` method per mapping heuristic above
3. IF all ACs are `inspect` → convert the most testable one to `test` or `command`
4. Update Linear issue + add comment
---
**Version:** 2.0.0
**Last Updated:** 2026-02-03
references/workflow_validation.md
<!-- SOURCE-OF-TRUTH: plugins/agile-workflow/shared/references/workflow_validation.md. Edit ONLY here; run `node tools/marketplace/shared.mjs sync` -->
# Workflow Validation (Criteria #7-#13)
<!-- SCOPE: Workflow validation criteria #7-#13 ONLY. Contains test strategy, KISS/YAGNI, task order, Story size rules. -->
<!-- DO NOT add here: Structural validation → structural_validation.md, traceability → traceability_validation.md -->
Detailed rules for test strategy, documentation integration, Story size, test cleanup, YAGNI, KISS, and task order.
---
## Criterion #7: Test Strategy Section (Empty Placeholder)
**Check:** Test Strategy section exists but is EMPTY (testing planned separately).
**Penalty:** LOW (1 point)
**Rule:** Section `## Test Strategy` must exist with placeholder text only. Any actual test content (unit tests, test cases) is a violation.
**Auto-fix:** If missing — add empty section with placeholder. If contains content — clear, add placeholder. Update Linear issue.
**Rationale:** Test planner analyzes ALL implementation Tasks to create Risk-Based Test Plan. Premature test planning = incomplete coverage.
---
## Criterion #8: Documentation Integration (No Standalone Doc Tasks)
**Check:** No separate Tasks for documentation — docs integrated into implementation Tasks.
**Penalty:** MEDIUM (3 points)
**Rule:** Standalone doc Tasks (keywords: "Write docs", "Update README", "Document API") must be merged into related implementation Task's Definition of Done as a doc checkbox.
**Auto-fix:** Identify standalone doc Tasks, remove them, add doc requirement to related Task's DoD. Update Linear issue.
**Rationale:** Documentation should be created WITH implementation, not after.
---
## Criterion #9: Story Size (1-8 Tasks)
**Check:** Story has 1-8 implementation Tasks (3-5 optimal).
**Penalty:** MEDIUM (3 points)
**Task Count by Complexity:**
| Complexity | Task Count | Example |
|------------|------------|---------|
| Trivial | 1-2 | Add health check, config endpoint |
| Simple | 3-4 | Add single endpoint with validation |
| Medium | 5-6 | Integrate external service (OAuth, Stripe) |
| Complex | 7-8 | Implement multi-step workflow |
**Database Creation Principle (Incremental Schema Evolution):**
Each Story creates ONLY the tables it needs. Big-bang "Setup Database" Stories that create all tables violate incremental delivery and vertical slicing.
**Auto-fix:**
1. Count implementation Tasks (exclude final test Task). If >8 — consolidate related Tasks
2. Scan first Story in Epic for database setup indicators (keywords: "Setup Database", "Create all tables", "Database schema"). If found and creates >5 tables — flag violation, suggest moving table creation to Stories that first use them
3. Update Linear issue
---
## Criterion #10: Test Cleanup (No Premature Test Tasks)
**Check:** No separate test Tasks BEFORE final Task (testing handled separately).
**Penalty:** MEDIUM (3 points)
**Rule:** Test Tasks (keywords: "test", "spec", "e2e") are only allowed as the final Task. Mid-Story test Tasks must be removed — add testing note to related Task's DoD instead.
**Auto-fix:** Find test Tasks before final Task, remove them, add testing note to related Task's DoD. Update Linear issue.
---
## Criterion #11: YAGNI (You Aren't Gonna Need It)
**Check:** Story scope limited to current requirements (no speculative features).
**Penalty:** MEDIUM (3 points)
**CRITICAL:** YAGNI applies UNLESS Industry Standards (#5) require it. Standards override YAGNI.
**YAGNI Hierarchy:**
```
Level 1: Industry Standards (RFC, OWASP) -> CANNOT remove
Level 2: Security Standards -> CANNOT remove
Level 3: YAGNI -> Apply ONLY if no conflict with Level 1-2
```
**GOOD (Standards Override YAGNI):**
- OAuth includes refresh tokens (RFC 6749 requires, even if "not needed yet")
- Error handling includes all HTTP codes (RFC 7231 defines them)
**GOOD (YAGNI Applies):**
- Login does NOT include social auth if not required now
- API does NOT include GraphQL if REST sufficient
**BAD (Violates Standards):**
- "Skip refresh tokens for simplicity" (violates RFC 6749)
**Auto-fix:**
1. Identify speculative features (keywords: "future-proof", "might need", "prepare for")
2. If required by Standard — keep, add justification. If not — remove, add TODO comment
3. Update Linear issue
---
## Criterion #12: KISS (Keep It Simple, Stupid)
**Check:** Solution uses simplest approach that meets requirements.
**Penalty:** MEDIUM (3 points)
**CRITICAL:** KISS applies UNLESS Industry Standards (#5) require complexity. Standards override KISS.
**KISS Hierarchy:**
```
Level 1: Industry Standards -> CANNOT simplify
Level 2: Security Standards -> CANNOT simplify
Level 3: KISS -> Apply ONLY if no conflict with Level 1-2
```
**GOOD (Standards Override KISS):**
- OAuth 2.0 with all required parameters (RFC 6749 requires)
- Helmet.js with security headers (OWASP requires)
**GOOD (KISS Applies):**
- Monolith instead of microservices (for small apps)
- SQLite instead of PostgreSQL (for dev/small apps)
**BAD (Over-engineered):**
- "Microservices for 3-endpoint API" (no scale requirement)
- "Kubernetes for single server" (Docker Compose sufficient)
**Auto-fix:**
1. Identify over-engineered solutions (keywords: "microservice", "kubernetes", "distributed")
2. If justified by Standard — keep, add justification. If not — simplify, suggest alternative
3. Update Linear issue
---
## Criterion #13: Foundation-First Task Order
**Check:** Tasks ordered bottom-up (Database -> Service -> API -> UI).
**Penalty:** MEDIUM (3 points)
**Correct Layer Order:** Database/schema -> Repository/data access -> Service/business logic -> API/routes -> Middleware -> UI/Frontend -> Tests (final).
**Task Independence Check:** Can Task N be completed using only Tasks 1..N-1? Forward dependencies (Task 2 requires Task 3 output) are violations. Detailed forward dependency detection handled by Criterion #19 in [dependency_validation.md](dependency_validation.md); this criterion focuses on LAYER ordering.
**Auto-fix:**
1. Identify layer for each Task (keywords: "schema", "repository", "service", "route")
2. If out of order — reorder Tasks
3. Parse Task descriptions for dependency keywords ("requires", "depends on", "needs"). If forward dependency found — flag as MEDIUM violation, suggest reordering
4. Update Linear issue
---
## Auto-Fix Hierarchy (CRITICAL)
**Check order:** Industry Standards (#5) first -> Security Standards second -> KISS/YAGNI (#11-#12) last.
**Decision:** If solution violates Industry Standard or compromises security — keep complex solution, add justification. Otherwise — apply KISS/YAGNI simplification.
| Proposed Simplification | Standard Check | Decision |
|-------------------------|----------------|----------|
| "Skip refresh tokens" | RFC 6749 requires | REJECT |
| "Use GET for mutations" | REST violates | REJECT |
| "Remove Redis caching" | No standard | ACCEPT |
| "Remove microservices" | No standard | ACCEPT |
---
**Version:** 3.0.0
**Last Updated:** 2025-01-07
SKILL.md
---
name: ln-310-multi-agent-validator
description: "Use when validating Stories, plans, or tasks through the evaluation platform with mandatory research, parallel evidence lanes, sequential merge, and bounded refinement. Modes: story | plan_review."
license: MIT
---
> **Paths:** File paths (`references/`, `../ln-*`) are relative to this skill directory.
**Type:** L2 Coordinator
**Category:** 3XX Planning
# Multi-Agent Validator
Evaluation-platform coordinator for:
- `mode=story`
- `mode=plan_review`
This skill uses the evaluation platform for:
- mandatory official-doc, MCP Ref, Context7, and current-web research
- parallel read-only evidence lanes
- sequential documentation, repair, merge, refinement, and approval
- runtime-backed worker plans, worker summaries, agent sync, and cleanup verification
## Inputs
| Input | Required | Source | Description |
|-------|----------|--------|-------------|
| `storyId` | `mode=story` | args, git branch, kanban, user | Story to validate |
| `plan {file}` | `mode=plan_review` | args or auto | Plan file to validate |
Mode detection:
- `plan` or `plan {file}` -> `mode=plan_review`
- otherwise -> `mode=story`
## Mandatory Read
**MANDATORY READ:** Load `references/environment_state_contract.md`, `references/storage_mode_detection.md`, `references/input_resolution_pattern.md`
**MANDATORY READ:** Load `references/evaluation_coordinator_runtime_contract.md`, `references/evaluation_summary_contract.md`, `references/evaluation_parallelism_policy.md`, `references/evaluation_research_contract.md`
**MANDATORY READ:** Load `references/agent_delegation_pattern.md`
**MANDATORY READ:** Load `references/penalty_points.md`
**MANDATORY READ:** Load `references/researchgraph_mcp_usage.md` when researchgraph files changed or the target claims hypothesis, goal, benchmark, or proposal readiness.
Conditional read: load `references/phase2_research_audit.md` only when the coordinator performs inline criteria mapping instead of consuming ln-312 findings summaries.
Agent review policy: run health check, record skipped reason when no advisor is available, verify every advisor claim before merge, and treat transport/auth/tool failures as operator evidence rather than domain findings. Load `references/agent_review_workflow.md` only when debugging lifecycle/liveness details outside the evaluation runtime.
## Worker Set
The coordinator uses these evaluation workers:
- `ln-311-review-research-worker`
- `ln-312-review-findings-worker`
- `ln-313-review-docs-worker`
- `ln-314-review-repair-worker`
- `ln-315-review-merge-worker`
- `ln-316-review-refinement-worker`
## Worker Invocation (MANDATORY)
**Host Skill Invocation:** `Skill(skill: "...", args: "...")` is mandatory delegation.
- Claude: call the Skill tool exactly as shown.
- Codex: if no Skill tool exists, locate the named skill in available skills, read its `SKILL.md`, treat `args` as `$ARGUMENTS`, execute that skill workflow, then return here with its result/artifact.
- Do not inline worker logic or mark the worker complete without executing the target skill.
Use the Skill tool for delegated workers. Do not inline worker logic inside the coordinator.
TodoWrite format (mandatory):
- `Resolve target and build runtime manifest`
- `Load target artifacts and metadata`
- `Launch external agents and verify health`
- `Run research and findings workers in parallel`
- `Generate documentation updates`
- `Apply accepted low-risk repairs`
- `Sync agents and merge all evidence`
- `Run refinement (MANDATORY in ALL modes when advisor available — do NOT skip)`
- `Compute verdict and write review output`
- `Verify runtime cleanup and self-check`
Representative invocations:
```text
Skill(skill: "ln-311-review-research-worker", args: "{identifier} research")
Skill(skill: "ln-312-review-findings-worker", args: "{identifier} findings")
Skill(skill: "ln-313-review-docs-worker", args: "{identifier} docs")
Skill(skill: "ln-314-review-repair-worker", args: "{identifier} repair")
Skill(skill: "ln-315-review-merge-worker", args: "{identifier} merge")
Skill(skill: "ln-316-review-refinement-worker", args: "{identifier} refinement")
```
## Runtime Contract
**MANDATORY READ:** Load `references/loop_health_contract.md`
Runtime family:
- `evaluation-runtime`
Identifier:
- `story-{storyId}` for story mode
- `plan-{slug}` for plan review
Phase order:
1. `PHASE_0_CONFIG`
2. `PHASE_1_DISCOVERY`
3. `PHASE_2_AGENT_LAUNCH`
4. `PHASE_3_EVIDENCE_LANES`
5. `PHASE_4_DOCS`
6. `PHASE_5_REPAIR`
7. `PHASE_6_MERGE`
8. `PHASE_7_REFINEMENT`
9. `PHASE_8_APPROVAL`
10. `PHASE_9_SELF_CHECK`
Phase policy:
- `delegate_phases = [PHASE_3_EVIDENCE_LANES, PHASE_4_DOCS, PHASE_5_REPAIR, PHASE_6_MERGE, PHASE_7_REFINEMENT]`
- `aggregate_phase = PHASE_6_MERGE`
- `report_phase = PHASE_8_APPROVAL`
- `cleanup_phase = PHASE_9_SELF_CHECK`
- `self_check_phase = PHASE_9_SELF_CHECK`
- `agent_resolve_before = [PHASE_6_MERGE]`
- `required_phases_when_advisor_available = [PHASE_7_REFINEMENT]`
## Parallelism Rules
Allowed overlap:
- external agents
- `ln-311`
- `ln-312`
- local repo inspection and evidence gathering
Sequential only:
- `ln-313`
- `ln-314`
- `ln-315`
- `ln-316`
- approval and status mutation
## Workflow
### Phase 0: Config
1. Resolve `mode`, identifier, and storage mode.
2. Resolve story or plan target.
3. Build evaluation runtime manifest with:
- `expected_agents`
- `required_research=true`
- exact `phase_order`
- `phase_policy`
- report path
4. Start runtime:
```bash
node references/scripts/evaluation-runtime/cli.mjs start \
--skill ln-310 \
--identifier {identifier} \
--manifest-file .hex-skills/evaluation/{identifier}_manifest.json
```
5. Checkpoint Phase 0.
### Phase 1: Discovery
1. Materialize the exact target artifact.
2. Load only the metadata needed for the current mode.
3. In `mode=story`, resolve Story and child tasks.
4. In `mode=plan_review`, resolve the plan file.
5. If researchgraph files changed or the target cites `H##`, `G##`, run IDs, benchmark manifests, or readiness claims, run read-only researchgraph verification/audits and attach the result as validation evidence.
6. Checkpoint Phase 1 with resolved refs.
### Phase 2: Agent Launch
1. Run agent health check.
2. Exclude disabled agents from `.hex-skills/environment_state.json`.
3. If no agents are available:
- record `agents_skipped_reason`
- checkpoint Phase 2
- continue
4. Otherwise:
- build per-agent prompts
- launch each available agent
- register each launched agent:
```bash
node references/scripts/evaluation-runtime/cli.mjs register-agent \
--skill ln-310 \
--identifier {identifier} \
--agent {name} \
--prompt-file {promptPath} \
--result-file {resultPath} \
--metadata-file {metadataPath}
```
5. Checkpoint Phase 2 with `health_check_done`, `agents_available`, `agents_required`, and optional `agents_skipped_reason`.
6. Classify each external agent result before domain verdict:
- `rate_limited`, `tool_missing`, `auth_missing`, `permission_denial`, and `asked_question` are transport/operator states.
- Do not convert them into `NO-GO` without domain evidence from artifacts or findings.
- Record loop health for repeated advisor/session failures and pause when retry usefulness is exhausted.
### Phase 3: Evidence Lanes
This phase is the mandatory parallel evidence barrier.
1. Build `worker_plan` with:
- `ln-311` lane `research` (mandatory)
- `ln-312` lane `findings` (mandatory)
2. Launch all planned workers in parallel.
3. While those workers run, continue local repo inspection and collect additional evidence.
4. Sync agents opportunistically, but do not block on them until merge.
5. Record each worker summary with:
```bash
node references/scripts/evaluation-runtime/cli.mjs record-worker-result \
--skill ln-310 \
--identifier {identifier} \
--payload-file {childSummaryArtifactPath}
```
Research is mandatory in every mode:
- official documentation or standards
- MCP Ref
- Context7 when a library or framework is involved
- current web best-practice research
For `mode=story`, findings must still produce penalty-point evidence and coverage analysis.
### Phase 4: Docs
1. In `mode=story`, run `ln-313-review-docs-worker` when documentation changes are required.
2. In `mode=plan_review`, skip only when there is no documentation delta to create.
3. Record the worker summary or explicit skip rationale.
### Phase 5: Repair
1. Apply accepted low-risk repairs through `ln-314-review-repair-worker`.
2. Do not merge repair logic into research or findings lanes.
3. Record summary and any cleanup evidence.
### Phase 6: Merge
Preconditions:
- all planned evidence workers resolved
- all required agents resolved or explicitly skipped
Steps:
1. Sync agents once at the merge barrier:
```bash
node references/scripts/evaluation-runtime/cli.mjs sync-agent --skill ln-310 --identifier {identifier}
```
2. Run `ln-315-review-merge-worker`.
3. Deduplicate:
- local findings
- worker findings
- agent findings
- prior review history
4. Reject unsupported claims.
5. Apply only verified accepted changes.
6. Checkpoint Phase 6 with `aggregation_summary`.
### Phase 7: Refinement
> **NEVER SKIP THIS PHASE.** Phase 7 applies to ALL modes: `story`, `plan_review`.
> The ONLY valid skip reason is no advisor available in health check.
> Mode is NOT a skip reason. Complexity is NOT a skip reason. Time is NOT a skip reason.
> If you are about to checkpoint Phase 7 without running ln-316 while an advisor is available — STOP. You are making an error.
| Mode | Phase 7 required? | Skip allowed? |
|------|-------------------|---------------|
| `story` | YES | NO (only if no advisor available) |
| `plan_review` | YES | NO (only if no advisor available) |
Phase 7 is MANDATORY when an advisor is available. The coordinator MUST NOT checkpoint Phase 7 without a recorded `review-refinement` worker summary from ln-316. The runtime `advance` command will reject the transition if an advisor was available in health check but no refinement summary exists.
Run `ln-316-review-refinement-worker`. Refinement uses a 2-stage state machine:
- Stage 1: 3 parallel advisor sessions (dry_run_executor, new_dev_tester, adversarial_reviewer)
- Stage 2: 1 sequential advisor session (final_sweep) after merging Stage 1 results
Rules:
- all 4 perspectives are mandatory
- Stage 1 runs in parallel, Stage 2 runs after Stage 1 merge
- each perspective = independent advisor process via `agent_runner.mjs` (NOT host-native sub-agents)
- every launched process requires cleanup evidence
- advisor session failures use `failure_class`, `progress_signals`, and `session_usable` from `agent_runner.mjs`; classified transport failures pause/defer instead of becoming domain findings
- refinement trace is mandatory
- wait for advisor results via runtime `sync-agent`; Claude hosts may use `Monitor` for observability
### Phase 8: Approval
Story mode:
1. Compute final gate from post-merge and post-refinement state.
2. Final Assessment Model:
| Metric | Before | After | Meaning |
|--------|--------|-------|---------|
| Penalty Points | from ln-312 | from ln-314 | 0 = all fixed |
| Readiness Score | `clamp(1,10,10-floor(before/5))` | `clamp(1,10,10-floor(after/5))` | Quality (1-10) |
| Anti-Hallucination | — | from ln-311 | VERIFIED/FLAGGED |
| AC Coverage | — | N/N | 100% = pass |
| Gate | — | GO/NO_GO | Final verdict |
3. Gate rules:
- `GO` = `penalty_after=0` AND no `FLAGGED` items AND `ac_coverage=100%`
- `NO_GO` = otherwise
- Coverage: 80-99% = +3 penalty and forced `NO_GO`
- Coverage: <80% = +5 penalty and forced `NO_GO`
4. On `GO`: mutate Story status to `Todo`; update `kanban_board.md` to `APPROVED`.
5. Retry status transition once; if failure → `NO_GO`.
6. Write user-facing review output with per-criterion penalty before/after breakdown.
Plan mode:
- write final review output without workflow mutation
Write coordinator summary:
```bash
node references/scripts/evaluation-runtime/cli.mjs record-summary \
--skill ln-310 \
--identifier {identifier} \
--payload '{...evaluation-coordinator summary...}'
```
### Phase 9: Self-Check
Required checks:
- [ ] runtime started
- [ ] discovery checkpoint exists
- [ ] agent health recorded
- [ ] mandatory research completed
- [ ] all required worker summaries recorded
- [ ] all required agents resolved before merge
- [ ] merge summary exists
- [ ] refinement trace exists when an advisor was available
- [ ] background cleanup evidence recorded
- [ ] cleanup verified
- [ ] coordinator summary recorded
- [ ] final result recorded
Then:
```bash
node references/scripts/evaluation-runtime/cli.mjs complete --skill ln-310 --identifier {identifier}
```
## Summary Contract
Coordinator summary kind:
- `evaluation-coordinator`
Recommended payload fields:
- `status`
- `final_result`
- `report_path`
- `worker_count`
- `agent_count`
- `issues_total`
- `severity_counts`
- `warnings`
- `cleanup_verified`
- `research_completed`
- `penalty_before`
- `penalty_after`
- `readiness_score`
- `ac_coverage`
- `gate` (GO/NO_GO)
- `flagged_items`
## Definition of Done
- [ ] Evaluation runtime started
- [ ] Mandatory research completed and recorded
- [ ] Read-only evidence lanes executed in parallel
- [ ] Docs, repair, merge, refinement, and approval executed sequentially
- [ ] All required worker summaries recorded
- [ ] All required agents resolved before merge
- [ ] Refinement executed when advisor available; SKIPPED only when no advisor available in health check
- [ ] Cleanup evidence recorded and verified
- [ ] `evaluation-coordinator` summary written
- [ ] Runtime completed successfully
## Meta-Analysis
Optional reference: load `references/meta_analysis_protocol.md` only when the user asks for post-run meta-analysis or protocol-formatted run reflection.
When requested after the coordinator run, analyze the session per protocol section 7 and include the protocol-formatted output with the final review result.
## References
- Runtime: `references/evaluation_coordinator_runtime_contract.md`, `references/evaluation_summary_contract.md`
- Research: `references/evaluation_research_contract.md`, `references/research_tool_fallback.md`, `references/plan_review_pipeline.md`
- Parallelism: `references/evaluation_parallelism_policy.md`
- Workers: `../ln-311-review-research-worker/SKILL.md`, `../ln-312-review-findings-worker/SKILL.md`, `../ln-313-review-docs-worker/SKILL.md`, `../ln-314-review-repair-worker/SKILL.md`, `../ln-315-review-merge-worker/SKILL.md`, `../ln-316-review-refinement-worker/SKILL.md`
- Validation criteria: `references/phase2_research_audit.md`, `references/penalty_points.md`
- Supporting validator refs: `references/cross_reference_validation.md`, `references/dependency_validation.md`, `references/domain_patterns.md`, `references/templates/mcp_ref_findings_template.md`, `references/premortem_validation.md`, `references/quality_validation.md`, `references/risk_validation.md`, `references/solution_validation.md`, `references/standards_validation.md`, `references/structural_validation.md`, `references/traceability_validation.md`, `references/workflow_validation.md`
---
**Version:** 8.0.0
**Last Updated:** 2026-03-22