@jokerized/getresearchdone 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +103 -0
- package/README.md +211 -0
- package/agents/grd-baseline-assessor.md +684 -0
- package/agents/grd-code-reviewer.md +300 -0
- package/agents/grd-codebase-mapper.md +355 -0
- package/agents/grd-critique-agent.md +119 -0
- package/agents/grd-debugger.md +519 -0
- package/agents/grd-deep-diver.md +737 -0
- package/agents/grd-eval-planner.md +913 -0
- package/agents/grd-eval-reporter.md +717 -0
- package/agents/grd-executor.md +683 -0
- package/agents/grd-feasibility-analyst.md +624 -0
- package/agents/grd-integration-checker.md +367 -0
- package/agents/grd-knowledge-miner.md +81 -0
- package/agents/grd-migrator.md +88 -0
- package/agents/grd-phase-researcher.md +697 -0
- package/agents/grd-plan-checker.md +443 -0
- package/agents/grd-planner.md +1532 -0
- package/agents/grd-product-owner.md +562 -0
- package/agents/grd-project-researcher.md +513 -0
- package/agents/grd-research-synthesizer.md +273 -0
- package/agents/grd-roadmapper.md +798 -0
- package/agents/grd-surveyor.md +566 -0
- package/agents/grd-verifier.md +893 -0
- package/bin/gd.js +4 -0
- package/bin/gd.ts +227 -0
- package/bin/grd-manifest.js +4 -0
- package/bin/grd-manifest.ts +286 -0
- package/bin/grd-mcp-server.js +4 -0
- package/bin/grd-mcp-server.ts +124 -0
- package/bin/grd-tools.js +4 -0
- package/bin/grd-tools.ts +2471 -0
- package/bin/postinstall.js +4 -0
- package/bin/postinstall.ts +80 -0
- package/commands/add-phase.md +123 -0
- package/commands/add-todo.md +87 -0
- package/commands/assess-baseline.md +289 -0
- package/commands/autopilot.md +100 -0
- package/commands/autoplan.md +55 -0
- package/commands/check-todos.md +87 -0
- package/commands/compare-methods.md +262 -0
- package/commands/complete-milestone.md +225 -0
- package/commands/debug.md +372 -0
- package/commands/deep-dive.md +288 -0
- package/commands/discover.md +281 -0
- package/commands/discuss-phase.md +188 -0
- package/commands/discuss.md +55 -0
- package/commands/eval-report.md +310 -0
- package/commands/evolve.md +79 -0
- package/commands/execute-phase.md +1017 -0
- package/commands/feasibility.md +292 -0
- package/commands/help.md +407 -0
- package/commands/init.md +1508 -0
- package/commands/insert-phase.md +113 -0
- package/commands/iterate.md +327 -0
- package/commands/list-phase-assumptions.md +217 -0
- package/commands/long-term-roadmap.md +202 -0
- package/commands/map-codebase.md +111 -0
- package/commands/migrate.md +159 -0
- package/commands/new-milestone.md +169 -0
- package/commands/pause-work.md +83 -0
- package/commands/plan-milestone-gaps.md +373 -0
- package/commands/plan-phase.md +655 -0
- package/commands/principles.md +328 -0
- package/commands/product-plan.md +319 -0
- package/commands/progress.md +481 -0
- package/commands/quick.md +167 -0
- package/commands/reapply-patches.md +154 -0
- package/commands/remove-phase.md +97 -0
- package/commands/requirement.md +96 -0
- package/commands/resume-project.md +113 -0
- package/commands/settings.md +1144 -0
- package/commands/survey.md +242 -0
- package/commands/sync.md +246 -0
- package/commands/tracker-setup.md +322 -0
- package/commands/update.md +202 -0
- package/commands/verify-phase.md +335 -0
- package/commands/verify-work.md +701 -0
- package/commands/wireup.md +29 -0
- package/dist/bin/gd.d.ts +3 -0
- package/dist/bin/gd.d.ts.map +1 -0
- package/dist/bin/gd.js +178 -0
- package/dist/bin/gd.js.map +1 -0
- package/dist/bin/grd-manifest.d.ts +3 -0
- package/dist/bin/grd-manifest.d.ts.map +1 -0
- package/dist/bin/grd-manifest.js +202 -0
- package/dist/bin/grd-manifest.js.map +1 -0
- package/dist/bin/grd-mcp-server.d.ts +3 -0
- package/dist/bin/grd-mcp-server.d.ts.map +1 -0
- package/dist/bin/grd-mcp-server.js +71 -0
- package/dist/bin/grd-mcp-server.js.map +1 -0
- package/dist/bin/grd-tools.d.ts +3 -0
- package/dist/bin/grd-tools.d.ts.map +1 -0
- package/dist/bin/grd-tools.js +1680 -0
- package/dist/bin/grd-tools.js.map +1 -0
- package/dist/bin/postinstall.d.ts +3 -0
- package/dist/bin/postinstall.d.ts.map +1 -0
- package/dist/bin/postinstall.js +61 -0
- package/dist/bin/postinstall.js.map +1 -0
- package/dist/lib/autopilot-milestone.d.ts +2 -0
- package/dist/lib/autopilot-milestone.d.ts.map +1 -0
- package/dist/lib/autopilot-milestone.js +94 -0
- package/dist/lib/autopilot-milestone.js.map +1 -0
- package/dist/lib/autopilot-pipeline.d.ts +2 -0
- package/dist/lib/autopilot-pipeline.d.ts.map +1 -0
- package/dist/lib/autopilot-pipeline.js +830 -0
- package/dist/lib/autopilot-pipeline.js.map +1 -0
- package/dist/lib/autopilot-waves.d.ts +2 -0
- package/dist/lib/autopilot-waves.d.ts.map +1 -0
- package/dist/lib/autopilot-waves.js +266 -0
- package/dist/lib/autopilot-waves.js.map +1 -0
- package/dist/lib/autopilot.d.ts +2 -0
- package/dist/lib/autopilot.d.ts.map +1 -0
- package/dist/lib/autopilot.js +1314 -0
- package/dist/lib/autopilot.js.map +1 -0
- package/dist/lib/autoplan.d.ts +2 -0
- package/dist/lib/autoplan.d.ts.map +1 -0
- package/dist/lib/autoplan.js +198 -0
- package/dist/lib/autoplan.js.map +1 -0
- package/dist/lib/autoresearch.d.ts +2 -0
- package/dist/lib/autoresearch.d.ts.map +1 -0
- package/dist/lib/autoresearch.js +626 -0
- package/dist/lib/autoresearch.js.map +1 -0
- package/dist/lib/backend.d.ts +2 -0
- package/dist/lib/backend.d.ts.map +1 -0
- package/dist/lib/backend.js +1036 -0
- package/dist/lib/backend.js.map +1 -0
- package/dist/lib/benchmark.d.ts +99 -0
- package/dist/lib/benchmark.d.ts.map +1 -0
- package/dist/lib/benchmark.js +278 -0
- package/dist/lib/benchmark.js.map +1 -0
- package/dist/lib/citations.d.ts +2 -0
- package/dist/lib/citations.d.ts.map +1 -0
- package/dist/lib/citations.js +642 -0
- package/dist/lib/citations.js.map +1 -0
- package/dist/lib/cleanup.d.ts +2 -0
- package/dist/lib/cleanup.d.ts.map +1 -0
- package/dist/lib/cleanup.js +1222 -0
- package/dist/lib/cleanup.js.map +1 -0
- package/dist/lib/cli/adapters.d.ts +10 -0
- package/dist/lib/cli/adapters.d.ts.map +1 -0
- package/dist/lib/cli/adapters.js +27 -0
- package/dist/lib/cli/adapters.js.map +1 -0
- package/dist/lib/cli/agent.d.ts +17 -0
- package/dist/lib/cli/agent.d.ts.map +1 -0
- package/dist/lib/cli/agent.js +53 -0
- package/dist/lib/cli/agent.js.map +1 -0
- package/dist/lib/cli/index.d.ts +21 -0
- package/dist/lib/cli/index.d.ts.map +1 -0
- package/dist/lib/cli/index.js +264 -0
- package/dist/lib/cli/index.js.map +1 -0
- package/dist/lib/cli/output.d.ts +20 -0
- package/dist/lib/cli/output.d.ts.map +1 -0
- package/dist/lib/cli/output.js +22 -0
- package/dist/lib/cli/output.js.map +1 -0
- package/dist/lib/cli/scan-dispatch.d.ts +9 -0
- package/dist/lib/cli/scan-dispatch.d.ts.map +1 -0
- package/dist/lib/cli/scan-dispatch.js +107 -0
- package/dist/lib/cli/scan-dispatch.js.map +1 -0
- package/dist/lib/cli/tools.d.ts +16 -0
- package/dist/lib/cli/tools.d.ts.map +1 -0
- package/dist/lib/cli/tools.js +168 -0
- package/dist/lib/cli/tools.js.map +1 -0
- package/dist/lib/commands/_dashboard-parsers.d.ts +2 -0
- package/dist/lib/commands/_dashboard-parsers.d.ts.map +1 -0
- package/dist/lib/commands/_dashboard-parsers.js +192 -0
- package/dist/lib/commands/_dashboard-parsers.js.map +1 -0
- package/dist/lib/commands/analysis.d.ts +2 -0
- package/dist/lib/commands/analysis.d.ts.map +1 -0
- package/dist/lib/commands/analysis.js +1418 -0
- package/dist/lib/commands/analysis.js.map +1 -0
- package/dist/lib/commands/assumptions.d.ts +2 -0
- package/dist/lib/commands/assumptions.d.ts.map +1 -0
- package/dist/lib/commands/assumptions.js +166 -0
- package/dist/lib/commands/assumptions.js.map +1 -0
- package/dist/lib/commands/blame.d.ts +2 -0
- package/dist/lib/commands/blame.d.ts.map +1 -0
- package/dist/lib/commands/blame.js +133 -0
- package/dist/lib/commands/blame.js.map +1 -0
- package/dist/lib/commands/budget.d.ts +2 -0
- package/dist/lib/commands/budget.d.ts.map +1 -0
- package/dist/lib/commands/budget.js +100 -0
- package/dist/lib/commands/budget.js.map +1 -0
- package/dist/lib/commands/check-plans.d.ts +2 -0
- package/dist/lib/commands/check-plans.d.ts.map +1 -0
- package/dist/lib/commands/check-plans.js +190 -0
- package/dist/lib/commands/check-plans.js.map +1 -0
- package/dist/lib/commands/config.d.ts +2 -0
- package/dist/lib/commands/config.d.ts.map +1 -0
- package/dist/lib/commands/config.js +188 -0
- package/dist/lib/commands/config.js.map +1 -0
- package/dist/lib/commands/dashboard.d.ts +2 -0
- package/dist/lib/commands/dashboard.d.ts.map +1 -0
- package/dist/lib/commands/dashboard.js +466 -0
- package/dist/lib/commands/dashboard.js.map +1 -0
- package/dist/lib/commands/estimate.d.ts +2 -0
- package/dist/lib/commands/estimate.d.ts.map +1 -0
- package/dist/lib/commands/estimate.js +148 -0
- package/dist/lib/commands/estimate.js.map +1 -0
- package/dist/lib/commands/eval-diff.d.ts +2 -0
- package/dist/lib/commands/eval-diff.d.ts.map +1 -0
- package/dist/lib/commands/eval-diff.js +213 -0
- package/dist/lib/commands/eval-diff.js.map +1 -0
- package/dist/lib/commands/freshness.d.ts +2 -0
- package/dist/lib/commands/freshness.d.ts.map +1 -0
- package/dist/lib/commands/freshness.js +163 -0
- package/dist/lib/commands/freshness.js.map +1 -0
- package/dist/lib/commands/health.d.ts +2 -0
- package/dist/lib/commands/health.d.ts.map +1 -0
- package/dist/lib/commands/health.js +435 -0
- package/dist/lib/commands/health.js.map +1 -0
- package/dist/lib/commands/index.d.ts +2 -0
- package/dist/lib/commands/index.d.ts.map +1 -0
- package/dist/lib/commands/index.js +128 -0
- package/dist/lib/commands/index.js.map +1 -0
- package/dist/lib/commands/install.d.ts +56 -0
- package/dist/lib/commands/install.d.ts.map +1 -0
- package/dist/lib/commands/install.js +214 -0
- package/dist/lib/commands/install.js.map +1 -0
- package/dist/lib/commands/knowhow-aggregator.d.ts +2 -0
- package/dist/lib/commands/knowhow-aggregator.d.ts.map +1 -0
- package/dist/lib/commands/knowhow-aggregator.js +279 -0
- package/dist/lib/commands/knowhow-aggregator.js.map +1 -0
- package/dist/lib/commands/knowledge-search.d.ts +2 -0
- package/dist/lib/commands/knowledge-search.d.ts.map +1 -0
- package/dist/lib/commands/knowledge-search.js +113 -0
- package/dist/lib/commands/knowledge-search.js.map +1 -0
- package/dist/lib/commands/long-term-roadmap.d.ts +2 -0
- package/dist/lib/commands/long-term-roadmap.d.ts.map +1 -0
- package/dist/lib/commands/long-term-roadmap.js +272 -0
- package/dist/lib/commands/long-term-roadmap.js.map +1 -0
- package/dist/lib/commands/patterns.d.ts +91 -0
- package/dist/lib/commands/patterns.d.ts.map +1 -0
- package/dist/lib/commands/patterns.js +391 -0
- package/dist/lib/commands/patterns.js.map +1 -0
- package/dist/lib/commands/phase-info.d.ts +2 -0
- package/dist/lib/commands/phase-info.d.ts.map +1 -0
- package/dist/lib/commands/phase-info.js +509 -0
- package/dist/lib/commands/phase-info.js.map +1 -0
- package/dist/lib/commands/plan-lint.d.ts +56 -0
- package/dist/lib/commands/plan-lint.d.ts.map +1 -0
- package/dist/lib/commands/plan-lint.js +481 -0
- package/dist/lib/commands/plan-lint.js.map +1 -0
- package/dist/lib/commands/plan-phase.d.ts +53 -0
- package/dist/lib/commands/plan-phase.d.ts.map +1 -0
- package/dist/lib/commands/plan-phase.js +288 -0
- package/dist/lib/commands/plan-phase.js.map +1 -0
- package/dist/lib/commands/progress.d.ts +2 -0
- package/dist/lib/commands/progress.d.ts.map +1 -0
- package/dist/lib/commands/progress.js +266 -0
- package/dist/lib/commands/progress.js.map +1 -0
- package/dist/lib/commands/quality.d.ts +2 -0
- package/dist/lib/commands/quality.d.ts.map +1 -0
- package/dist/lib/commands/quality.js +80 -0
- package/dist/lib/commands/quality.js.map +1 -0
- package/dist/lib/commands/rollback.d.ts +2 -0
- package/dist/lib/commands/rollback.d.ts.map +1 -0
- package/dist/lib/commands/rollback.js +145 -0
- package/dist/lib/commands/rollback.js.map +1 -0
- package/dist/lib/commands/scan.d.ts +25 -0
- package/dist/lib/commands/scan.d.ts.map +1 -0
- package/dist/lib/commands/scan.js +28 -0
- package/dist/lib/commands/scan.js.map +1 -0
- package/dist/lib/commands/search.d.ts +2 -0
- package/dist/lib/commands/search.d.ts.map +1 -0
- package/dist/lib/commands/search.js +212 -0
- package/dist/lib/commands/search.js.map +1 -0
- package/dist/lib/commands/select-candidate.d.ts +128 -0
- package/dist/lib/commands/select-candidate.d.ts.map +1 -0
- package/dist/lib/commands/select-candidate.js +518 -0
- package/dist/lib/commands/select-candidate.js.map +1 -0
- package/dist/lib/commands/singularity.d.ts +2 -0
- package/dist/lib/commands/singularity.d.ts.map +1 -0
- package/dist/lib/commands/singularity.js +185 -0
- package/dist/lib/commands/singularity.js.map +1 -0
- package/dist/lib/commands/slug-timestamp.d.ts +2 -0
- package/dist/lib/commands/slug-timestamp.d.ts.map +1 -0
- package/dist/lib/commands/slug-timestamp.js +54 -0
- package/dist/lib/commands/slug-timestamp.js.map +1 -0
- package/dist/lib/commands/tail.d.ts +2 -0
- package/dist/lib/commands/tail.d.ts.map +1 -0
- package/dist/lib/commands/tail.js +100 -0
- package/dist/lib/commands/tail.js.map +1 -0
- package/dist/lib/commands/todo.d.ts +2 -0
- package/dist/lib/commands/todo.d.ts.map +1 -0
- package/dist/lib/commands/todo.js +200 -0
- package/dist/lib/commands/todo.js.map +1 -0
- package/dist/lib/commands/watch.d.ts +2 -0
- package/dist/lib/commands/watch.d.ts.map +1 -0
- package/dist/lib/commands/watch.js +72 -0
- package/dist/lib/commands/watch.js.map +1 -0
- package/dist/lib/complexity.d.ts +55 -0
- package/dist/lib/complexity.d.ts.map +1 -0
- package/dist/lib/complexity.js +80 -0
- package/dist/lib/complexity.js.map +1 -0
- package/dist/lib/context/agents.d.ts +2 -0
- package/dist/lib/context/agents.d.ts.map +1 -0
- package/dist/lib/context/agents.js +344 -0
- package/dist/lib/context/agents.js.map +1 -0
- package/dist/lib/context/base.d.ts +2 -0
- package/dist/lib/context/base.d.ts.map +1 -0
- package/dist/lib/context/base.js +81 -0
- package/dist/lib/context/base.js.map +1 -0
- package/dist/lib/context/execute.d.ts +2 -0
- package/dist/lib/context/execute.d.ts.map +1 -0
- package/dist/lib/context/execute.js +753 -0
- package/dist/lib/context/execute.js.map +1 -0
- package/dist/lib/context/index.d.ts +2 -0
- package/dist/lib/context/index.d.ts.map +1 -0
- package/dist/lib/context/index.js +88 -0
- package/dist/lib/context/index.js.map +1 -0
- package/dist/lib/context/progress.d.ts +2 -0
- package/dist/lib/context/progress.d.ts.map +1 -0
- package/dist/lib/context/progress.js +178 -0
- package/dist/lib/context/progress.js.map +1 -0
- package/dist/lib/context/project.d.ts +2 -0
- package/dist/lib/context/project.d.ts.map +1 -0
- package/dist/lib/context/project.js +413 -0
- package/dist/lib/context/project.js.map +1 -0
- package/dist/lib/context/research.d.ts +2 -0
- package/dist/lib/context/research.d.ts.map +1 -0
- package/dist/lib/context/research.js +466 -0
- package/dist/lib/context/research.js.map +1 -0
- package/dist/lib/dead-ends.d.ts +28 -0
- package/dist/lib/dead-ends.d.ts.map +1 -0
- package/dist/lib/dead-ends.js +451 -0
- package/dist/lib/dead-ends.js.map +1 -0
- package/dist/lib/deps.d.ts +2 -0
- package/dist/lib/deps.d.ts.map +1 -0
- package/dist/lib/deps.js +630 -0
- package/dist/lib/deps.js.map +1 -0
- package/dist/lib/discussion.d.ts +2 -0
- package/dist/lib/discussion.d.ts.map +1 -0
- package/dist/lib/discussion.js +1041 -0
- package/dist/lib/discussion.js.map +1 -0
- package/dist/lib/drift.d.ts +36 -0
- package/dist/lib/drift.d.ts.map +1 -0
- package/dist/lib/drift.js +481 -0
- package/dist/lib/drift.js.map +1 -0
- package/dist/lib/evolve/_dimensions-features.d.ts +2 -0
- package/dist/lib/evolve/_dimensions-features.d.ts.map +1 -0
- package/dist/lib/evolve/_dimensions-features.js +369 -0
- package/dist/lib/evolve/_dimensions-features.js.map +1 -0
- package/dist/lib/evolve/_dimensions.d.ts +2 -0
- package/dist/lib/evolve/_dimensions.d.ts.map +1 -0
- package/dist/lib/evolve/_dimensions.js +358 -0
- package/dist/lib/evolve/_dimensions.js.map +1 -0
- package/dist/lib/evolve/_product-ideation.d.ts +2 -0
- package/dist/lib/evolve/_product-ideation.d.ts.map +1 -0
- package/dist/lib/evolve/_product-ideation.js +281 -0
- package/dist/lib/evolve/_product-ideation.js.map +1 -0
- package/dist/lib/evolve/_prompts.d.ts +2 -0
- package/dist/lib/evolve/_prompts.d.ts.map +1 -0
- package/dist/lib/evolve/_prompts.js +153 -0
- package/dist/lib/evolve/_prompts.js.map +1 -0
- package/dist/lib/evolve/cli.d.ts +2 -0
- package/dist/lib/evolve/cli.d.ts.map +1 -0
- package/dist/lib/evolve/cli.js +224 -0
- package/dist/lib/evolve/cli.js.map +1 -0
- package/dist/lib/evolve/discovery.d.ts +2 -0
- package/dist/lib/evolve/discovery.d.ts.map +1 -0
- package/dist/lib/evolve/discovery.js +391 -0
- package/dist/lib/evolve/discovery.js.map +1 -0
- package/dist/lib/evolve/index.d.ts +2 -0
- package/dist/lib/evolve/index.d.ts.map +1 -0
- package/dist/lib/evolve/index.js +88 -0
- package/dist/lib/evolve/index.js.map +1 -0
- package/dist/lib/evolve/orchestrator.d.ts +2 -0
- package/dist/lib/evolve/orchestrator.d.ts.map +1 -0
- package/dist/lib/evolve/orchestrator.js +851 -0
- package/dist/lib/evolve/orchestrator.js.map +1 -0
- package/dist/lib/evolve/scoring.d.ts +2 -0
- package/dist/lib/evolve/scoring.d.ts.map +1 -0
- package/dist/lib/evolve/scoring.js +118 -0
- package/dist/lib/evolve/scoring.js.map +1 -0
- package/dist/lib/evolve/state.d.ts +2 -0
- package/dist/lib/evolve/state.d.ts.map +1 -0
- package/dist/lib/evolve/state.js +264 -0
- package/dist/lib/evolve/state.js.map +1 -0
- package/dist/lib/evolve/types.d.ts +249 -0
- package/dist/lib/evolve/types.d.ts.map +1 -0
- package/dist/lib/evolve/types.js +3 -0
- package/dist/lib/evolve/types.js.map +1 -0
- package/dist/lib/frontmatter.d.ts +2 -0
- package/dist/lib/frontmatter.d.ts.map +1 -0
- package/dist/lib/frontmatter.js +513 -0
- package/dist/lib/frontmatter.js.map +1 -0
- package/dist/lib/gates.d.ts +2 -0
- package/dist/lib/gates.d.ts.map +1 -0
- package/dist/lib/gates.js +578 -0
- package/dist/lib/gates.js.map +1 -0
- package/dist/lib/genome.d.ts +10 -0
- package/dist/lib/genome.d.ts.map +1 -0
- package/dist/lib/genome.js +368 -0
- package/dist/lib/genome.js.map +1 -0
- package/dist/lib/got.d.ts +2 -0
- package/dist/lib/got.d.ts.map +1 -0
- package/dist/lib/got.js +280 -0
- package/dist/lib/got.js.map +1 -0
- package/dist/lib/invariants.d.ts +2 -0
- package/dist/lib/invariants.d.ts.map +1 -0
- package/dist/lib/invariants.js +298 -0
- package/dist/lib/invariants.js.map +1 -0
- package/dist/lib/knowledge.d.ts +2 -0
- package/dist/lib/knowledge.d.ts.map +1 -0
- package/dist/lib/knowledge.js +658 -0
- package/dist/lib/knowledge.js.map +1 -0
- package/dist/lib/long-term-roadmap.d.ts +2 -0
- package/dist/lib/long-term-roadmap.d.ts.map +1 -0
- package/dist/lib/long-term-roadmap.js +602 -0
- package/dist/lib/long-term-roadmap.js.map +1 -0
- package/dist/lib/markdown-split.d.ts +2 -0
- package/dist/lib/markdown-split.d.ts.map +1 -0
- package/dist/lib/markdown-split.js +199 -0
- package/dist/lib/markdown-split.js.map +1 -0
- package/dist/lib/mcp-server.d.ts +2 -0
- package/dist/lib/mcp-server.d.ts.map +1 -0
- package/dist/lib/mcp-server.js +2424 -0
- package/dist/lib/mcp-server.js.map +1 -0
- package/dist/lib/metrics.d.ts +16 -0
- package/dist/lib/metrics.d.ts.map +1 -0
- package/dist/lib/metrics.js +48 -0
- package/dist/lib/metrics.js.map +1 -0
- package/dist/lib/overstory.d.ts +2 -0
- package/dist/lib/overstory.d.ts.map +1 -0
- package/dist/lib/overstory.js +211 -0
- package/dist/lib/overstory.js.map +1 -0
- package/dist/lib/parallel.d.ts +2 -0
- package/dist/lib/parallel.d.ts.map +1 -0
- package/dist/lib/parallel.js +349 -0
- package/dist/lib/parallel.js.map +1 -0
- package/dist/lib/paths.d.ts +2 -0
- package/dist/lib/paths.d.ts.map +1 -0
- package/dist/lib/paths.js +254 -0
- package/dist/lib/paths.js.map +1 -0
- package/dist/lib/phase-complete-llm.d.ts +22 -0
- package/dist/lib/phase-complete-llm.d.ts.map +1 -0
- package/dist/lib/phase-complete-llm.js +331 -0
- package/dist/lib/phase-complete-llm.js.map +1 -0
- package/dist/lib/phase-complete.d.ts +46 -0
- package/dist/lib/phase-complete.d.ts.map +1 -0
- package/dist/lib/phase-complete.js +278 -0
- package/dist/lib/phase-complete.js.map +1 -0
- package/dist/lib/phase-io.d.ts +2 -0
- package/dist/lib/phase-io.d.ts.map +1 -0
- package/dist/lib/phase-io.js +126 -0
- package/dist/lib/phase-io.js.map +1 -0
- package/dist/lib/phase.d.ts +2 -0
- package/dist/lib/phase.d.ts.map +1 -0
- package/dist/lib/phase.js +1344 -0
- package/dist/lib/phase.js.map +1 -0
- package/dist/lib/plan-tournament.d.ts +63 -0
- package/dist/lib/plan-tournament.d.ts.map +1 -0
- package/dist/lib/plan-tournament.js +353 -0
- package/dist/lib/plan-tournament.js.map +1 -0
- package/dist/lib/refinement.d.ts +74 -0
- package/dist/lib/refinement.d.ts.map +1 -0
- package/dist/lib/refinement.js +283 -0
- package/dist/lib/refinement.js.map +1 -0
- package/dist/lib/requirements.d.ts +2 -0
- package/dist/lib/requirements.d.ts.map +1 -0
- package/dist/lib/requirements.js +355 -0
- package/dist/lib/requirements.js.map +1 -0
- package/dist/lib/research-bundle.d.ts +2 -0
- package/dist/lib/research-bundle.d.ts.map +1 -0
- package/dist/lib/research-bundle.js +246 -0
- package/dist/lib/research-bundle.js.map +1 -0
- package/dist/lib/roadmap.d.ts +2 -0
- package/dist/lib/roadmap.d.ts.map +1 -0
- package/dist/lib/roadmap.js +541 -0
- package/dist/lib/roadmap.js.map +1 -0
- package/dist/lib/sample.d.ts +16 -0
- package/dist/lib/sample.d.ts.map +1 -0
- package/dist/lib/sample.js +20 -0
- package/dist/lib/sample.js.map +1 -0
- package/dist/lib/scaffold.d.ts +2 -0
- package/dist/lib/scaffold.d.ts.map +1 -0
- package/dist/lib/scaffold.js +355 -0
- package/dist/lib/scaffold.js.map +1 -0
- package/dist/lib/scan/_utils.d.ts +11 -0
- package/dist/lib/scan/_utils.d.ts.map +1 -0
- package/dist/lib/scan/_utils.js +36 -0
- package/dist/lib/scan/_utils.js.map +1 -0
- package/dist/lib/scan/base64.d.ts +15 -0
- package/dist/lib/scan/base64.d.ts.map +1 -0
- package/dist/lib/scan/base64.js +66 -0
- package/dist/lib/scan/base64.js.map +1 -0
- package/dist/lib/scan/ignorefile.d.ts +30 -0
- package/dist/lib/scan/ignorefile.d.ts.map +1 -0
- package/dist/lib/scan/ignorefile.js +101 -0
- package/dist/lib/scan/ignorefile.js.map +1 -0
- package/dist/lib/scan/injection.d.ts +14 -0
- package/dist/lib/scan/injection.d.ts.map +1 -0
- package/dist/lib/scan/injection.js +39 -0
- package/dist/lib/scan/injection.js.map +1 -0
- package/dist/lib/scan/patterns.d.ts +17 -0
- package/dist/lib/scan/patterns.d.ts.map +1 -0
- package/dist/lib/scan/patterns.js +123 -0
- package/dist/lib/scan/patterns.js.map +1 -0
- package/dist/lib/scan/strip-markdown.d.ts +7 -0
- package/dist/lib/scan/strip-markdown.d.ts.map +1 -0
- package/dist/lib/scan/strip-markdown.js +38 -0
- package/dist/lib/scan/strip-markdown.js.map +1 -0
- package/dist/lib/scan/types.d.ts +23 -0
- package/dist/lib/scan/types.d.ts.map +1 -0
- package/dist/lib/scan/types.js +3 -0
- package/dist/lib/scan/types.js.map +1 -0
- package/dist/lib/scheduler-wait.d.ts +2 -0
- package/dist/lib/scheduler-wait.d.ts.map +1 -0
- package/dist/lib/scheduler-wait.js +59 -0
- package/dist/lib/scheduler-wait.js.map +1 -0
- package/dist/lib/scheduler.d.ts +254 -0
- package/dist/lib/scheduler.d.ts.map +1 -0
- package/dist/lib/scheduler.js +1147 -0
- package/dist/lib/scheduler.js.map +1 -0
- package/dist/lib/state.d.ts +2 -0
- package/dist/lib/state.d.ts.map +1 -0
- package/dist/lib/state.js +744 -0
- package/dist/lib/state.js.map +1 -0
- package/dist/lib/think.d.ts +18 -0
- package/dist/lib/think.d.ts.map +1 -0
- package/dist/lib/think.js +317 -0
- package/dist/lib/think.js.map +1 -0
- package/dist/lib/tracker.d.ts +2 -0
- package/dist/lib/tracker.d.ts.map +1 -0
- package/dist/lib/tracker.js +1121 -0
- package/dist/lib/tracker.js.map +1 -0
- package/dist/lib/types.d.ts +1514 -0
- package/dist/lib/types.d.ts.map +1 -0
- package/dist/lib/types.js +4 -0
- package/dist/lib/types.js.map +1 -0
- package/dist/lib/utils.d.ts +2 -0
- package/dist/lib/utils.d.ts.map +1 -0
- package/dist/lib/utils.js +1363 -0
- package/dist/lib/utils.js.map +1 -0
- package/dist/lib/verify.d.ts +2 -0
- package/dist/lib/verify.d.ts.map +1 -0
- package/dist/lib/verify.js +1153 -0
- package/dist/lib/verify.js.map +1 -0
- package/dist/lib/wireup/autofix.d.ts +2 -0
- package/dist/lib/wireup/autofix.d.ts.map +1 -0
- package/dist/lib/wireup/autofix.js +188 -0
- package/dist/lib/wireup/autofix.js.map +1 -0
- package/dist/lib/wireup/cli.d.ts +2 -0
- package/dist/lib/wireup/cli.d.ts.map +1 -0
- package/dist/lib/wireup/cli.js +194 -0
- package/dist/lib/wireup/cli.js.map +1 -0
- package/dist/lib/wireup/detection.d.ts +47 -0
- package/dist/lib/wireup/detection.d.ts.map +1 -0
- package/dist/lib/wireup/detection.js +410 -0
- package/dist/lib/wireup/detection.js.map +1 -0
- package/dist/lib/wireup/discovery.d.ts +2 -0
- package/dist/lib/wireup/discovery.d.ts.map +1 -0
- package/dist/lib/wireup/discovery.js +934 -0
- package/dist/lib/wireup/discovery.js.map +1 -0
- package/dist/lib/wireup/execution.d.ts +2 -0
- package/dist/lib/wireup/execution.d.ts.map +1 -0
- package/dist/lib/wireup/execution.js +573 -0
- package/dist/lib/wireup/execution.js.map +1 -0
- package/dist/lib/wireup/index.d.ts +2 -0
- package/dist/lib/wireup/index.d.ts.map +1 -0
- package/dist/lib/wireup/index.js +85 -0
- package/dist/lib/wireup/index.js.map +1 -0
- package/dist/lib/wireup/orchestrator.d.ts +2 -0
- package/dist/lib/wireup/orchestrator.d.ts.map +1 -0
- package/dist/lib/wireup/orchestrator.js +366 -0
- package/dist/lib/wireup/orchestrator.js.map +1 -0
- package/dist/lib/wireup/report.d.ts +47 -0
- package/dist/lib/wireup/report.d.ts.map +1 -0
- package/dist/lib/wireup/report.js +201 -0
- package/dist/lib/wireup/report.js.map +1 -0
- package/dist/lib/wireup/scenarios.d.ts +2 -0
- package/dist/lib/wireup/scenarios.d.ts.map +1 -0
- package/dist/lib/wireup/scenarios.js +516 -0
- package/dist/lib/wireup/scenarios.js.map +1 -0
- package/dist/lib/wireup/state.d.ts +2 -0
- package/dist/lib/wireup/state.d.ts.map +1 -0
- package/dist/lib/wireup/state.js +102 -0
- package/dist/lib/wireup/state.js.map +1 -0
- package/dist/lib/wireup/types.d.ts +376 -0
- package/dist/lib/wireup/types.d.ts.map +1 -0
- package/dist/lib/wireup/types.js +3 -0
- package/dist/lib/wireup/types.js.map +1 -0
- package/dist/lib/worktree.d.ts +2 -0
- package/dist/lib/worktree.d.ts.map +1 -0
- package/dist/lib/worktree.js +999 -0
- package/dist/lib/worktree.js.map +1 -0
- package/lib/autopilot-milestone.ts +136 -0
- package/lib/autopilot-pipeline.ts +1179 -0
- package/lib/autopilot-waves.ts +361 -0
- package/lib/autopilot.ts +1874 -0
- package/lib/autoplan.ts +280 -0
- package/lib/autoresearch.js +4 -0
- package/lib/autoresearch.ts +886 -0
- package/lib/backend.ts +1252 -0
- package/lib/benchmark.ts +341 -0
- package/lib/citations.ts +760 -0
- package/lib/cleanup.ts +1588 -0
- package/lib/cli/adapters.ts +41 -0
- package/lib/cli/agent.ts +83 -0
- package/lib/cli/index.ts +273 -0
- package/lib/cli/output.ts +33 -0
- package/lib/cli/scan-dispatch.ts +130 -0
- package/lib/cli/tools.ts +198 -0
- package/lib/commands/_dashboard-parsers.ts +275 -0
- package/lib/commands/analysis.ts +1851 -0
- package/lib/commands/assumptions.ts +232 -0
- package/lib/commands/blame.ts +174 -0
- package/lib/commands/budget.ts +148 -0
- package/lib/commands/check-plans.ts +233 -0
- package/lib/commands/config.ts +287 -0
- package/lib/commands/dashboard.ts +680 -0
- package/lib/commands/estimate.ts +204 -0
- package/lib/commands/eval-diff.ts +252 -0
- package/lib/commands/freshness.ts +213 -0
- package/lib/commands/health.ts +607 -0
- package/lib/commands/index.ts +266 -0
- package/lib/commands/install.ts +307 -0
- package/lib/commands/knowhow-aggregator.ts +345 -0
- package/lib/commands/knowledge-search.ts +153 -0
- package/lib/commands/long-term-roadmap.ts +390 -0
- package/lib/commands/patterns.ts +465 -0
- package/lib/commands/phase-info.ts +698 -0
- package/lib/commands/plan-lint.ts +546 -0
- package/lib/commands/plan-phase.ts +375 -0
- package/lib/commands/progress.ts +319 -0
- package/lib/commands/quality.ts +138 -0
- package/lib/commands/rollback.ts +195 -0
- package/lib/commands/scan.ts +72 -0
- package/lib/commands/search.ts +300 -0
- package/lib/commands/select-candidate.ts +687 -0
- package/lib/commands/singularity.ts +222 -0
- package/lib/commands/slug-timestamp.ts +74 -0
- package/lib/commands/tail.ts +129 -0
- package/lib/commands/todo.ts +273 -0
- package/lib/commands/watch.ts +80 -0
- package/lib/complexity.ts +117 -0
- package/lib/context/agents.ts +505 -0
- package/lib/context/base.ts +123 -0
- package/lib/context/execute.ts +977 -0
- package/lib/context/index.ts +110 -0
- package/lib/context/progress.ts +278 -0
- package/lib/context/project.ts +531 -0
- package/lib/context/research.ts +646 -0
- package/lib/dead-ends.ts +506 -0
- package/lib/deps.ts +773 -0
- package/lib/discussion.ts +1275 -0
- package/lib/drift.ts +519 -0
- package/lib/evolve/_dimensions-features.ts +525 -0
- package/lib/evolve/_dimensions.ts +511 -0
- package/lib/evolve/_product-ideation.ts +405 -0
- package/lib/evolve/_prompts.ts +178 -0
- package/lib/evolve/cli.ts +330 -0
- package/lib/evolve/discovery.ts +571 -0
- package/lib/evolve/index.ts +105 -0
- package/lib/evolve/orchestrator.ts +1139 -0
- package/lib/evolve/scoring.ts +167 -0
- package/lib/evolve/state.ts +330 -0
- package/lib/evolve/types.ts +290 -0
- package/lib/frontmatter.ts +615 -0
- package/lib/gates.ts +695 -0
- package/lib/genome.ts +402 -0
- package/lib/got.js +4 -0
- package/lib/got.ts +361 -0
- package/lib/invariants.ts +378 -0
- package/lib/knowledge.ts +768 -0
- package/lib/long-term-roadmap.ts +806 -0
- package/lib/markdown-split.ts +273 -0
- package/lib/mcp-server.ts +3292 -0
- package/lib/metrics.ts +49 -0
- package/lib/overstory.ts +270 -0
- package/lib/parallel.ts +570 -0
- package/lib/paths.ts +293 -0
- package/lib/phase-complete-llm.ts +376 -0
- package/lib/phase-complete.ts +366 -0
- package/lib/phase-io.ts +101 -0
- package/lib/phase.ts +1981 -0
- package/lib/plan-tournament.ts +426 -0
- package/lib/refinement.ts +349 -0
- package/lib/requirements.ts +469 -0
- package/lib/research-bundle.ts +300 -0
- package/lib/roadmap.ts +775 -0
- package/lib/scaffold.ts +480 -0
- package/lib/scan/_utils.ts +37 -0
- package/lib/scan/base64.ts +90 -0
- package/lib/scan/ignorefile.ts +109 -0
- package/lib/scan/injection.ts +67 -0
- package/lib/scan/patterns.ts +139 -0
- package/lib/scan/strip-markdown.ts +39 -0
- package/lib/scan/types.ts +28 -0
- package/lib/scheduler-wait.ts +58 -0
- package/lib/scheduler.ts +1370 -0
- package/lib/state.ts +1000 -0
- package/lib/think.ts +365 -0
- package/lib/tracker.ts +1591 -0
- package/lib/types.ts +1663 -0
- package/lib/utils.ts +1479 -0
- package/lib/verify.ts +1434 -0
- package/lib/wireup/autofix.ts +241 -0
- package/lib/wireup/cli.ts +278 -0
- package/lib/wireup/detection.ts +542 -0
- package/lib/wireup/discovery.ts +1063 -0
- package/lib/wireup/execution.ts +686 -0
- package/lib/wireup/index.ts +117 -0
- package/lib/wireup/orchestrator.ts +519 -0
- package/lib/wireup/report.ts +286 -0
- package/lib/wireup/scenarios.ts +616 -0
- package/lib/wireup/state.ts +139 -0
- package/lib/wireup/types.ts +436 -0
- package/lib/worktree.ts +1309 -0
- package/package.json +67 -0
package/lib/verify.ts
ADDED
|
@@ -0,0 +1,1434 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* GRD Verification Suite -- Plan structure, phase completeness, references, commits, artifacts, key-links
|
|
5
|
+
*
|
|
6
|
+
* Extracted from bin/grd-tools.js during Phase 03 modularization.
|
|
7
|
+
* Depends on: lib/utils.ts (safeReadFile, execGit, findPhaseInternal, validateGitRef, output, error)
|
|
8
|
+
* lib/frontmatter.ts (extractFrontmatter, parseMustHavesBlock)
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
import type { FrontmatterObject, PhaseInfo, ExecGitResult } from './types';
|
|
13
|
+
|
|
14
|
+
const fs = require('fs');
|
|
15
|
+
const path = require('path');
|
|
16
|
+
const os = require('os');
|
|
17
|
+
const {
|
|
18
|
+
safeReadFile,
|
|
19
|
+
execGit,
|
|
20
|
+
findPhaseInternal,
|
|
21
|
+
validateGitRef,
|
|
22
|
+
output,
|
|
23
|
+
error,
|
|
24
|
+
}: {
|
|
25
|
+
safeReadFile: (filePath: string) => string | null;
|
|
26
|
+
execGit: (cwd: string, args: string[], opts?: { allowBlocked?: boolean }) => ExecGitResult;
|
|
27
|
+
findPhaseInternal: (cwd: string, phase: string) => PhaseInfo | null;
|
|
28
|
+
validateGitRef: (ref: string) => string;
|
|
29
|
+
output: (result: unknown, raw: boolean, rawValue?: unknown) => never;
|
|
30
|
+
error: (message: string) => never;
|
|
31
|
+
} = require('./utils');
|
|
32
|
+
const {
|
|
33
|
+
extractFrontmatter,
|
|
34
|
+
parseMustHavesBlock,
|
|
35
|
+
}: {
|
|
36
|
+
extractFrontmatter: (content: string) => FrontmatterObject;
|
|
37
|
+
parseMustHavesBlock: (content: string, field: string) => MustHavesEntry[];
|
|
38
|
+
} = require('./frontmatter');
|
|
39
|
+
|
|
40
|
+
// ─── Domain Types ────────────────────────────────────────────────────────────
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* A must_haves artifact entry from plan frontmatter.
|
|
44
|
+
*/
|
|
45
|
+
interface MustHavesArtifact {
|
|
46
|
+
path: string;
|
|
47
|
+
provides?: string;
|
|
48
|
+
exports?: string | string[];
|
|
49
|
+
min_lines?: number;
|
|
50
|
+
contains?: string;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* A must_haves key_links entry from plan frontmatter.
|
|
55
|
+
*/
|
|
56
|
+
interface MustHavesKeyLink {
|
|
57
|
+
from: string;
|
|
58
|
+
to: string;
|
|
59
|
+
via?: string;
|
|
60
|
+
pattern?: string;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Union type for entries returned by parseMustHavesBlock.
|
|
65
|
+
*/
|
|
66
|
+
type MustHavesEntry = string | MustHavesArtifact | MustHavesKeyLink;
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Result of file creation check in summary verification.
|
|
70
|
+
*/
|
|
71
|
+
interface FilesCreatedCheck {
|
|
72
|
+
checked: number;
|
|
73
|
+
found: number;
|
|
74
|
+
missing: string[];
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Checks performed during summary verification.
|
|
79
|
+
*/
|
|
80
|
+
interface SummaryVerifyChecks {
|
|
81
|
+
summary_exists: boolean;
|
|
82
|
+
files_created: FilesCreatedCheck;
|
|
83
|
+
commits_exist: boolean;
|
|
84
|
+
self_check: string;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Result of summary verification.
|
|
89
|
+
*/
|
|
90
|
+
interface SummaryVerifyResult {
|
|
91
|
+
passed: boolean;
|
|
92
|
+
checks: SummaryVerifyChecks;
|
|
93
|
+
errors: string[];
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Task info extracted from plan structure verification.
|
|
98
|
+
*/
|
|
99
|
+
interface PlanTask {
|
|
100
|
+
name: string;
|
|
101
|
+
hasFiles: boolean;
|
|
102
|
+
hasAction: boolean;
|
|
103
|
+
hasVerify: boolean;
|
|
104
|
+
hasDone: boolean;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Result of plan structure verification.
|
|
109
|
+
*/
|
|
110
|
+
interface PlanVerifyResult {
|
|
111
|
+
valid: boolean;
|
|
112
|
+
errors: string[];
|
|
113
|
+
warnings: string[];
|
|
114
|
+
task_count: number;
|
|
115
|
+
tasks: PlanTask[];
|
|
116
|
+
frontmatter_fields: string[];
|
|
117
|
+
found_sections: string[];
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Result of phase completeness verification.
|
|
122
|
+
*/
|
|
123
|
+
interface PhaseCompletenessResult {
|
|
124
|
+
complete: boolean;
|
|
125
|
+
phase: string;
|
|
126
|
+
plan_count: number;
|
|
127
|
+
summary_count: number;
|
|
128
|
+
incomplete_plans: string[];
|
|
129
|
+
orphan_summaries: string[];
|
|
130
|
+
errors: string[];
|
|
131
|
+
warnings: string[];
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Result of reference verification.
|
|
136
|
+
*/
|
|
137
|
+
interface ReferenceVerifyResult {
|
|
138
|
+
valid: boolean;
|
|
139
|
+
found: number;
|
|
140
|
+
missing: string[];
|
|
141
|
+
total: number;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Result of commit verification.
|
|
146
|
+
*/
|
|
147
|
+
interface CommitVerifyResult {
|
|
148
|
+
all_valid: boolean;
|
|
149
|
+
valid: string[];
|
|
150
|
+
invalid: string[];
|
|
151
|
+
total: number;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Result of a single artifact check.
|
|
156
|
+
*/
|
|
157
|
+
interface ArtifactCheck {
|
|
158
|
+
path: string;
|
|
159
|
+
exists: boolean;
|
|
160
|
+
issues: string[];
|
|
161
|
+
passed: boolean;
|
|
162
|
+
plan_file: string;
|
|
163
|
+
must_haves_field: string;
|
|
164
|
+
remediation?: string;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Result of artifact verification.
|
|
169
|
+
*/
|
|
170
|
+
interface ArtifactVerifyResult {
|
|
171
|
+
all_passed: boolean;
|
|
172
|
+
passed: number;
|
|
173
|
+
total: number;
|
|
174
|
+
artifacts: ArtifactCheck[];
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Result of a single key-link check.
|
|
179
|
+
*/
|
|
180
|
+
interface KeyLinkCheck {
|
|
181
|
+
from: string;
|
|
182
|
+
to: string;
|
|
183
|
+
via: string;
|
|
184
|
+
verified: boolean;
|
|
185
|
+
detail: string;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Result of key-link verification.
|
|
190
|
+
*/
|
|
191
|
+
interface KeyLinkVerifyResult {
|
|
192
|
+
all_verified: boolean;
|
|
193
|
+
verified: number;
|
|
194
|
+
total: number;
|
|
195
|
+
links: KeyLinkCheck[];
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Module-level cache for file reads within a single process invocation.
|
|
199
|
+
// Safe for verify functions since they never write files.
|
|
200
|
+
const _fileReadCache = new Map<string, string | null>();
|
|
201
|
+
function readFileCached(fullPath: string): string | null {
|
|
202
|
+
if (!_fileReadCache.has(fullPath)) {
|
|
203
|
+
_fileReadCache.set(fullPath, safeReadFile(fullPath));
|
|
204
|
+
}
|
|
205
|
+
return _fileReadCache.get(fullPath) as string | null;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/** Clear the module-level file read cache. Call in test beforeEach to prevent stale reads across tests. */
|
|
209
|
+
function clearVerifyCache(): void {
|
|
210
|
+
_fileReadCache.clear();
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// ─── Verification Command Functions ──────────────────────────────────────────
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* CLI command: Verify SUMMARY.md structure including file existence, commit hashes, and self-check.
|
|
217
|
+
* @param cwd - Project working directory
|
|
218
|
+
* @param summaryPath - Relative path to the SUMMARY.md file
|
|
219
|
+
* @param checkFileCount - Number of mentioned files to spot-check for existence
|
|
220
|
+
* @param raw - Output raw 'passed'/'failed' instead of JSON
|
|
221
|
+
*/
|
|
222
|
+
function cmdVerifySummary(
|
|
223
|
+
cwd: string,
|
|
224
|
+
summaryPath: string,
|
|
225
|
+
checkFileCount: number,
|
|
226
|
+
raw: boolean
|
|
227
|
+
): void {
|
|
228
|
+
if (!summaryPath) {
|
|
229
|
+
error('summary-path required');
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const fullPath: string = path.join(cwd, summaryPath);
|
|
233
|
+
const checkCount: number = checkFileCount || 2;
|
|
234
|
+
|
|
235
|
+
// Check 1: Summary exists
|
|
236
|
+
let content: string;
|
|
237
|
+
try {
|
|
238
|
+
content = fs.readFileSync(fullPath, 'utf-8');
|
|
239
|
+
} catch {
|
|
240
|
+
const result: SummaryVerifyResult = {
|
|
241
|
+
passed: false,
|
|
242
|
+
checks: {
|
|
243
|
+
summary_exists: false,
|
|
244
|
+
files_created: { checked: 0, found: 0, missing: [] },
|
|
245
|
+
commits_exist: false,
|
|
246
|
+
self_check: 'not_found',
|
|
247
|
+
},
|
|
248
|
+
errors: ['SUMMARY.md not found'],
|
|
249
|
+
};
|
|
250
|
+
output(result, raw, 'failed');
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
const errors: string[] = [];
|
|
254
|
+
|
|
255
|
+
// Check 2: Spot-check files mentioned in summary
|
|
256
|
+
const mentionedFiles = new Set<string>();
|
|
257
|
+
const patterns: RegExp[] = [
|
|
258
|
+
/`([^`]+\.[a-zA-Z]+)`/g,
|
|
259
|
+
/(?:Created|Modified|Added|Updated|Edited):\s*`?([^\s`]+\.[a-zA-Z]+)`?/gi,
|
|
260
|
+
];
|
|
261
|
+
|
|
262
|
+
for (const pattern of patterns) {
|
|
263
|
+
let m: RegExpExecArray | null;
|
|
264
|
+
while ((m = pattern.exec(content)) !== null) {
|
|
265
|
+
const filePath: string = m[1];
|
|
266
|
+
if (filePath && !filePath.startsWith('http') && filePath.includes('/')) {
|
|
267
|
+
mentionedFiles.add(filePath);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
const filesToCheck: string[] = Array.from(mentionedFiles).slice(0, checkCount);
|
|
273
|
+
const missing: string[] = [];
|
|
274
|
+
for (const file of filesToCheck) {
|
|
275
|
+
if (!fs.existsSync(path.join(cwd, file))) {
|
|
276
|
+
missing.push(file);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// Check 3: Commits exist. Codex r15 P2: every referenced commit must
|
|
281
|
+
// exist (was sampling 3 and passing if any one resolved). Check the
|
|
282
|
+
// first ~10 unique hashes to keep the cost bounded but catch
|
|
283
|
+
// partial-failure cases.
|
|
284
|
+
// Codex r27 P2: a context-free hex-token regex also matches
|
|
285
|
+
// checksums, cache keys, content-hash IDs, and other non-commit
|
|
286
|
+
// hex strings. Anchor on explicit commit labels first (commit:,
|
|
287
|
+
// SHA:, ref:, hash:, parent:, `**Commit**`, hyperlinks to
|
|
288
|
+
// /commit/<sha>, conventional `(abcdef1)` parens), and only fall
|
|
289
|
+
// back to the bare hex scan if the labelled set is empty AND the
|
|
290
|
+
// SUMMARY explicitly mentions "commit" somewhere (so we keep the
|
|
291
|
+
// backward-compatible behaviour without false-failing on summaries
|
|
292
|
+
// that legitimately reference no commits).
|
|
293
|
+
// Codex r28 P2: a labelled `Commits: <a>, <b>, <c>` list contains
|
|
294
|
+
// multiple hashes. Capture the full label line (up to newline or
|
|
295
|
+
// next section) and pull every hex token from it, instead of only
|
|
296
|
+
// matching one hash per label.
|
|
297
|
+
// Codex r30 P2: only collect hex tokens that appear in commit
|
|
298
|
+
// contexts. Three kinds of context count:
|
|
299
|
+
// 1. Labelled lines: `Commit: <sha>`, `SHA: <sha>`, etc., and
|
|
300
|
+
// multi-hash lists like `Commits: <a>, <b>`. The line itself
|
|
301
|
+
// is the context; harvest every hex token on it.
|
|
302
|
+
// 2. /commit/<sha> hyperlinks.
|
|
303
|
+
// 3. A commits-block heading (`## (Task )?Commits`, `### Commits`,
|
|
304
|
+
// `# Commits`) — harvest every hex token in lines until the
|
|
305
|
+
// next heading at the same or shallower level, or the next
|
|
306
|
+
// blank line followed by a non-list line.
|
|
307
|
+
// Bare `(deadbeef)` parens and the prior whole-document scan are
|
|
308
|
+
// dropped — they re-introduced the false positives r27 was trying
|
|
309
|
+
// to fix.
|
|
310
|
+
const labelledHashes: string[] = [];
|
|
311
|
+
// Codex r32 P2: accept bold/bulleted label forms documented in
|
|
312
|
+
// agents/grd-executor.md, e.g.:
|
|
313
|
+
// - **Commits:** abc, def
|
|
314
|
+
// **Commits:**
|
|
315
|
+
// - abc
|
|
316
|
+
// Match the *label line* generically, then if it's a bare label
|
|
317
|
+
// (no hash on the same line) sweep subsequent bullet rows.
|
|
318
|
+
const linesAll = content.split('\n');
|
|
319
|
+
for (let i = 0; i < linesAll.length; i++) {
|
|
320
|
+
const line = linesAll[i];
|
|
321
|
+
// Codex r34 P2: also accept colonless forms used in real
|
|
322
|
+
// SUMMARYs:
|
|
323
|
+
// - `Commit 29c6883 exists` (label + space + hash)
|
|
324
|
+
// - `### Task 1 ... (29c6883)` (paren-suffix on a
|
|
325
|
+
// heading/task line)
|
|
326
|
+
// - `- [x] Task X completed (abc1234)` (paren-suffix on a
|
|
327
|
+
// checklist line)
|
|
328
|
+
const isColonLabel = /(?:[-*]\s+|^|\*\*)\s*(?:commit|sha|ref|hash|parent)s?\s*[:*]/i.test(line);
|
|
329
|
+
// Codex r38 P2: colonless `Commits a, b, c` should capture every
|
|
330
|
+
// hash on the line, not just the first. Detect the colonless
|
|
331
|
+
// label and then harvest *all* hex tokens from the line.
|
|
332
|
+
// Codex r39 P2: also accept backticked hashes after a colonless
|
|
333
|
+
// label, e.g. `- [x] Commit \`8880489\` exists`. Allow optional
|
|
334
|
+
// backticks between the label and the hash.
|
|
335
|
+
const hasColonlessLabel = /\b(?:commit|sha|ref|hash|parent)s?\s+`?[0-9a-f]{7,40}`?\b/i.test(line);
|
|
336
|
+
const colonlessHashes = hasColonlessLabel
|
|
337
|
+
? line.match(/\b[0-9a-f]{7,40}\b/gi) ?? []
|
|
338
|
+
: [];
|
|
339
|
+
// Codex r35 P2: paren-suffix `(<sha>)` is ambiguous — it's also
|
|
340
|
+
// used for checksums/artifact IDs. Require the line itself to
|
|
341
|
+
// mention something commit-flavored OR be a task-completion
|
|
342
|
+
// checklist marker (`- [x]`). Plain headings like
|
|
343
|
+
// `### Artifact checksum (deadbeef)` are no longer matched.
|
|
344
|
+
const isCheckedTask = /^\s*[-*]\s+\[x\]\s+/i.test(line);
|
|
345
|
+
const lineHasCommitWord = /\b(?:commit|merge|landed|shipped)\b/i.test(line);
|
|
346
|
+
// Codex r41 P2: also accept task-heading paren suffixes such as
|
|
347
|
+
// `### Task 1: add parser (deadbee)`. The heading-with-"Task"
|
|
348
|
+
// signal disambiguates from `### Artifact checksum (deadbeef)`.
|
|
349
|
+
const isTaskHeading = /^#{1,6}\s+(?:Task|Step|Plan|Phase|Subtask)\b/i.test(line);
|
|
350
|
+
const parenSuffixMatch =
|
|
351
|
+
(isCheckedTask || lineHasCommitWord || isTaskHeading)
|
|
352
|
+
? line.match(/\(([0-9a-f]{7,40})\)\s*$/i)
|
|
353
|
+
: null;
|
|
354
|
+
for (const h of colonlessHashes) {
|
|
355
|
+
labelledHashes.push(h.toLowerCase());
|
|
356
|
+
}
|
|
357
|
+
if (parenSuffixMatch) {
|
|
358
|
+
labelledHashes.push(parenSuffixMatch[1].toLowerCase());
|
|
359
|
+
}
|
|
360
|
+
if (!isColonLabel) {
|
|
361
|
+
continue;
|
|
362
|
+
}
|
|
363
|
+
// Codex r36 P2: use word boundaries so a SHA-256 artifact hash
|
|
364
|
+
// (64 hex chars) isn't truncated to its first 40 and treated as
|
|
365
|
+
// a commit. Boundary-aware match is consistent with the table
|
|
366
|
+
// and block scanners below.
|
|
367
|
+
const hashesOnLine = line.match(/\b[0-9a-f]{7,40}\b/gi) ?? [];
|
|
368
|
+
if (hashesOnLine.length > 0) {
|
|
369
|
+
for (const h of hashesOnLine) labelledHashes.push(h.toLowerCase());
|
|
370
|
+
continue;
|
|
371
|
+
}
|
|
372
|
+
// Bare label — sweep following bullet lines until indent drops
|
|
373
|
+
// back or a non-bullet line appears.
|
|
374
|
+
for (let j = i + 1; j < linesAll.length; j++) {
|
|
375
|
+
const row = linesAll[j];
|
|
376
|
+
if (/^\s*$/.test(row)) continue;
|
|
377
|
+
if (!/^\s*[-*]\s+/.test(row)) break;
|
|
378
|
+
for (const h of row.match(/\b[0-9a-f]{7,40}\b/gi) ?? []) {
|
|
379
|
+
labelledHashes.push(h.toLowerCase());
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
for (const m of content.matchAll(/\/commit\/([0-9a-f]{7,40})\b/gi)) {
|
|
384
|
+
labelledHashes.push(m[1].toLowerCase());
|
|
385
|
+
}
|
|
386
|
+
// Codex r35 P2: a `## Commits` section can contain nested `### Task N`
|
|
387
|
+
// subheadings with the actual hashes. The prior pattern stopped at
|
|
388
|
+
// any heading of any depth, missing those. Walk lines explicitly:
|
|
389
|
+
// when we hit a `## Commits` heading at depth D, harvest hashes
|
|
390
|
+
// from following lines until we see a heading at depth ≤ D
|
|
391
|
+
// (siblings or shallower).
|
|
392
|
+
const headingRe = /^(#{1,6})\s+(?:Task\s+)?Commits?\s*$/i;
|
|
393
|
+
for (let i = 0; i < linesAll.length; i++) {
|
|
394
|
+
const headingMatch = linesAll[i].match(headingRe);
|
|
395
|
+
if (!headingMatch) continue;
|
|
396
|
+
const depth = headingMatch[1].length;
|
|
397
|
+
for (let j = i + 1; j < linesAll.length; j++) {
|
|
398
|
+
const nextHeading = linesAll[j].match(/^(#{1,6})\s+/);
|
|
399
|
+
if (nextHeading && nextHeading[1].length <= depth) break;
|
|
400
|
+
for (const h of linesAll[j].match(/\b[0-9a-f]{7,40}\b/gi) ?? []) {
|
|
401
|
+
labelledHashes.push(h.toLowerCase());
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
// Codex r31 P2 / r33 P2: markdown tables with a `Commit` column
|
|
406
|
+
// header. Find the commit-column INDEX from the header row, then
|
|
407
|
+
// only harvest hex tokens from that specific cell in each row —
|
|
408
|
+
// otherwise a `| task | commit | checksum |` table would also try
|
|
409
|
+
// to validate the unrelated `checksum` column against git.
|
|
410
|
+
const lines = content.split('\n');
|
|
411
|
+
// Codex r37 P2: only drop the trailing-pipe artifact when one
|
|
412
|
+
// actually exists. Otherwise tables without a trailing `|`
|
|
413
|
+
// (`| Task | Commit` / `| 1 | deadbeef`) lose their last real
|
|
414
|
+
// cell — the Commit column — and the scanner misses every hash.
|
|
415
|
+
const splitCells = (s: string): string[] => {
|
|
416
|
+
const trimmed = s.trimEnd();
|
|
417
|
+
const hasTrailingPipe = trimmed.endsWith('|');
|
|
418
|
+
const cells = trimmed.split('|').map((c) => c.trim());
|
|
419
|
+
cells.shift(); // drop leading empty before first `|`
|
|
420
|
+
if (hasTrailingPipe) cells.pop();
|
|
421
|
+
return cells;
|
|
422
|
+
};
|
|
423
|
+
for (let i = 0; i < lines.length; i++) {
|
|
424
|
+
// Codex r40 P2: trim leading whitespace before checking for table
|
|
425
|
+
// start — indented tables (inside a list or quoted block) also
|
|
426
|
+
// need their commit column scanned.
|
|
427
|
+
const line = lines[i].trimStart();
|
|
428
|
+
if (
|
|
429
|
+
!line.startsWith('|') ||
|
|
430
|
+
!/\bcommit\b/i.test(line) ||
|
|
431
|
+
!lines[i + 1]?.match(/^\s*\|?\s*[-:]+/)
|
|
432
|
+
) {
|
|
433
|
+
continue;
|
|
434
|
+
}
|
|
435
|
+
const headerCells = splitCells(line);
|
|
436
|
+
const commitColIdx = headerCells.findIndex((c) => /\bcommit\b/i.test(c));
|
|
437
|
+
if (commitColIdx === -1) continue;
|
|
438
|
+
for (let j = i + 2; j < lines.length; j++) {
|
|
439
|
+
const row = lines[j].trimStart();
|
|
440
|
+
if (!row.startsWith('|')) break;
|
|
441
|
+
const rowCells = splitCells(row);
|
|
442
|
+
const cell = rowCells[commitColIdx] ?? '';
|
|
443
|
+
for (const h of cell.match(/\b[0-9a-f]{7,40}\b/gi) ?? []) {
|
|
444
|
+
labelledHashes.push(h.toLowerCase());
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
const hashes: string[] = Array.from(new Set(labelledHashes));
|
|
449
|
+
let commitsExist = false;
|
|
450
|
+
const invalidHashes: string[] = [];
|
|
451
|
+
if (hashes.length > 0) {
|
|
452
|
+
const uniqueHashes = Array.from(new Set(hashes)).slice(0, 10);
|
|
453
|
+
let allValid = true;
|
|
454
|
+
for (const hash of uniqueHashes) {
|
|
455
|
+
try {
|
|
456
|
+
validateGitRef(hash);
|
|
457
|
+
} catch {
|
|
458
|
+
allValid = false;
|
|
459
|
+
invalidHashes.push(hash);
|
|
460
|
+
continue;
|
|
461
|
+
}
|
|
462
|
+
const result: ExecGitResult = execGit(cwd, ['cat-file', '-t', hash]);
|
|
463
|
+
if (!(result.exitCode === 0 && result.stdout === 'commit')) {
|
|
464
|
+
allValid = false;
|
|
465
|
+
invalidHashes.push(hash);
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
commitsExist = allValid;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
// Check 4: Self-check section
|
|
472
|
+
let selfCheck = 'not_found';
|
|
473
|
+
const selfCheckPattern = /##\s*(?:Self[- ]?Check|Verification|Quality Check)/i;
|
|
474
|
+
if (selfCheckPattern.test(content)) {
|
|
475
|
+
const passPattern = /(?:all\s+)?(?:pass|✓|✅|complete|succeeded)/i;
|
|
476
|
+
const failPattern = /(?:fail|✗|❌|incomplete|blocked)/i;
|
|
477
|
+
const checkSection: string = content.slice(content.search(selfCheckPattern));
|
|
478
|
+
if (failPattern.test(checkSection)) {
|
|
479
|
+
selfCheck = 'failed';
|
|
480
|
+
} else if (passPattern.test(checkSection)) {
|
|
481
|
+
selfCheck = 'passed';
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
if (missing.length > 0) errors.push('Missing files: ' + missing.join(', '));
|
|
486
|
+
if (!commitsExist && hashes.length > 0)
|
|
487
|
+
errors.push(
|
|
488
|
+
invalidHashes.length > 0
|
|
489
|
+
? `Referenced commit hashes not found in git history: ${invalidHashes.join(', ')}`
|
|
490
|
+
: 'Referenced commit hashes not found in git history'
|
|
491
|
+
);
|
|
492
|
+
if (selfCheck === 'failed') errors.push('Self-check section indicates failure');
|
|
493
|
+
|
|
494
|
+
const checks: SummaryVerifyChecks = {
|
|
495
|
+
summary_exists: true,
|
|
496
|
+
files_created: {
|
|
497
|
+
checked: filesToCheck.length,
|
|
498
|
+
found: filesToCheck.length - missing.length,
|
|
499
|
+
missing,
|
|
500
|
+
},
|
|
501
|
+
commits_exist: commitsExist,
|
|
502
|
+
self_check: selfCheck,
|
|
503
|
+
};
|
|
504
|
+
|
|
505
|
+
const passed: boolean = missing.length === 0 && selfCheck !== 'failed' && (commitsExist || hashes.length === 0);
|
|
506
|
+
const result: SummaryVerifyResult = { passed, checks, errors };
|
|
507
|
+
output(result, raw, passed ? 'passed' : 'failed');
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
/**
|
|
511
|
+
* CLI command: Verify PLAN.md structure, frontmatter fields, and task element completeness.
|
|
512
|
+
* @param cwd - Project working directory
|
|
513
|
+
* @param filePath - Path to the PLAN.md file to validate
|
|
514
|
+
* @param raw - Output raw 'valid'/'invalid' instead of JSON
|
|
515
|
+
*/
|
|
516
|
+
function cmdVerifyPlanStructure(cwd: string, filePath: string, raw: boolean): void {
|
|
517
|
+
if (!filePath) {
|
|
518
|
+
error('file path required');
|
|
519
|
+
}
|
|
520
|
+
const fullPath: string = path.isAbsolute(filePath) ? filePath : path.join(cwd, filePath);
|
|
521
|
+
const content: string | null = readFileCached(fullPath);
|
|
522
|
+
if (!content) {
|
|
523
|
+
output({ error: 'File not found', path: filePath }, raw);
|
|
524
|
+
return;
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
const fm: FrontmatterObject = extractFrontmatter(content);
|
|
528
|
+
const errors: string[] = [];
|
|
529
|
+
const warnings: string[] = [];
|
|
530
|
+
|
|
531
|
+
// Check required frontmatter fields
|
|
532
|
+
const required: string[] = [
|
|
533
|
+
'phase',
|
|
534
|
+
'plan',
|
|
535
|
+
'type',
|
|
536
|
+
'wave',
|
|
537
|
+
'depends_on',
|
|
538
|
+
'files_modified',
|
|
539
|
+
'autonomous',
|
|
540
|
+
'must_haves',
|
|
541
|
+
];
|
|
542
|
+
const missingFields: string[] = [];
|
|
543
|
+
for (const field of required) {
|
|
544
|
+
if (fm[field] === undefined) {
|
|
545
|
+
errors.push(`Missing required frontmatter field: ${field}`);
|
|
546
|
+
missingFields.push(field);
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
// Include found frontmatter fields context when fields are missing
|
|
550
|
+
if (missingFields.length > 0) {
|
|
551
|
+
const foundFields: string[] = Object.keys(fm);
|
|
552
|
+
if (foundFields.length > 0) {
|
|
553
|
+
errors.push(`Found frontmatter fields: ${foundFields.join(', ')}`);
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
// Parse and check task elements
|
|
558
|
+
const taskPattern = /<task[^>]*>([\s\S]*?)<\/task>/g;
|
|
559
|
+
const tasks: PlanTask[] = [];
|
|
560
|
+
let taskMatch: RegExpExecArray | null;
|
|
561
|
+
while ((taskMatch = taskPattern.exec(content)) !== null) {
|
|
562
|
+
const taskContent: string = taskMatch[1];
|
|
563
|
+
const nameMatch: RegExpMatchArray | null = taskContent.match(/<name>([\s\S]*?)<\/name>/);
|
|
564
|
+
const taskName: string = nameMatch ? nameMatch[1].trim() : 'unnamed';
|
|
565
|
+
const hasFiles: boolean = /<files>/.test(taskContent);
|
|
566
|
+
const hasAction: boolean = /<action>/.test(taskContent);
|
|
567
|
+
const hasVerify: boolean = /<verify>/.test(taskContent);
|
|
568
|
+
const hasDone: boolean = /<done>/.test(taskContent);
|
|
569
|
+
|
|
570
|
+
if (!nameMatch) errors.push('Task missing <name> element');
|
|
571
|
+
if (!hasAction) errors.push(`Task '${taskName}' missing <action>`);
|
|
572
|
+
if (!hasVerify) warnings.push(`Task '${taskName}' missing <verify>`);
|
|
573
|
+
if (!hasDone) warnings.push(`Task '${taskName}' missing <done>`);
|
|
574
|
+
if (!hasFiles) warnings.push(`Task '${taskName}' missing <files>`);
|
|
575
|
+
|
|
576
|
+
tasks.push({ name: taskName, hasFiles, hasAction, hasVerify, hasDone });
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
if (tasks.length === 0) warnings.push('No <task> elements found');
|
|
580
|
+
|
|
581
|
+
// Wave/depends_on consistency
|
|
582
|
+
if (
|
|
583
|
+
fm.wave &&
|
|
584
|
+
parseInt(String(fm.wave)) > 1 &&
|
|
585
|
+
(!fm.depends_on || (Array.isArray(fm.depends_on) && fm.depends_on.length === 0))
|
|
586
|
+
) {
|
|
587
|
+
warnings.push('Wave > 1 but depends_on is empty');
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
// Autonomous/checkpoint consistency
|
|
591
|
+
const hasCheckpoints: boolean = /<task\s+type=["']?checkpoint/.test(content);
|
|
592
|
+
// fm.autonomous may arrive as boolean or string from YAML parsing -- check both
|
|
593
|
+
const autonomousVal: unknown = fm.autonomous;
|
|
594
|
+
if (hasCheckpoints && autonomousVal !== 'false' && autonomousVal !== false) {
|
|
595
|
+
errors.push('Has checkpoint tasks but autonomous is not false');
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
// Extract markdown headings for found_sections
|
|
599
|
+
const headingPattern = /^#{1,6}\s+.+$/gm;
|
|
600
|
+
const found_sections: string[] = (content.match(headingPattern) || []).map((h: string) =>
|
|
601
|
+
h.trim()
|
|
602
|
+
);
|
|
603
|
+
|
|
604
|
+
const result: PlanVerifyResult = {
|
|
605
|
+
valid: errors.length === 0,
|
|
606
|
+
errors,
|
|
607
|
+
warnings,
|
|
608
|
+
task_count: tasks.length,
|
|
609
|
+
tasks,
|
|
610
|
+
frontmatter_fields: Object.keys(fm),
|
|
611
|
+
found_sections,
|
|
612
|
+
};
|
|
613
|
+
output(result, raw, errors.length === 0 ? 'valid' : 'invalid');
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
/**
|
|
617
|
+
* CLI command: Check that all plans in a phase have corresponding summaries.
|
|
618
|
+
* @param cwd - Project working directory
|
|
619
|
+
* @param phase - Phase number to check
|
|
620
|
+
* @param raw - Output raw 'complete'/'incomplete' instead of JSON
|
|
621
|
+
*/
|
|
622
|
+
function cmdVerifyPhaseCompleteness(cwd: string, phase: string, raw: boolean): void {
|
|
623
|
+
if (!phase) {
|
|
624
|
+
error('phase required');
|
|
625
|
+
}
|
|
626
|
+
const phaseInfo: PhaseInfo | null = findPhaseInternal(cwd, phase);
|
|
627
|
+
if (!phaseInfo || !phaseInfo.found) {
|
|
628
|
+
output({ error: 'Phase not found', phase }, raw);
|
|
629
|
+
return;
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
const errors: string[] = [];
|
|
633
|
+
const warnings: string[] = [];
|
|
634
|
+
const phaseDir: string = path.join(cwd, phaseInfo.directory);
|
|
635
|
+
|
|
636
|
+
// List plans and summaries
|
|
637
|
+
let files: string[];
|
|
638
|
+
try {
|
|
639
|
+
files = fs.readdirSync(phaseDir);
|
|
640
|
+
} catch {
|
|
641
|
+
output({ error: 'Cannot read phase directory' }, raw);
|
|
642
|
+
return;
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
const plans: string[] = files.filter((f: string) => f.match(/-PLAN\.md$/i));
|
|
646
|
+
const summaries: string[] = files.filter((f: string) => f.match(/-SUMMARY\.md$/i));
|
|
647
|
+
|
|
648
|
+
// Extract plan IDs (everything before -PLAN.md)
|
|
649
|
+
const planIds = new Set<string>(plans.map((p: string) => p.replace(/-PLAN\.md$/i, '')));
|
|
650
|
+
const summaryIds = new Set<string>(summaries.map((s: string) => s.replace(/-SUMMARY\.md$/i, '')));
|
|
651
|
+
|
|
652
|
+
// Plans without summaries
|
|
653
|
+
const incompletePlans: string[] = [...planIds].filter((id) => !summaryIds.has(id));
|
|
654
|
+
if (incompletePlans.length > 0) {
|
|
655
|
+
errors.push(`Plans without summaries: ${incompletePlans.join(', ')}`);
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
// Summaries without plans (orphans)
|
|
659
|
+
const orphanSummaries: string[] = [...summaryIds].filter((id) => !planIds.has(id));
|
|
660
|
+
if (orphanSummaries.length > 0) {
|
|
661
|
+
warnings.push(`Summaries without plans: ${orphanSummaries.join(', ')}`);
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
const result: PhaseCompletenessResult = {
|
|
665
|
+
complete: errors.length === 0,
|
|
666
|
+
phase: phaseInfo.phase_number,
|
|
667
|
+
plan_count: plans.length,
|
|
668
|
+
summary_count: summaries.length,
|
|
669
|
+
incomplete_plans: incompletePlans,
|
|
670
|
+
orphan_summaries: orphanSummaries,
|
|
671
|
+
errors,
|
|
672
|
+
warnings,
|
|
673
|
+
};
|
|
674
|
+
output(result, raw, errors.length === 0 ? 'complete' : 'incomplete');
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
/**
|
|
678
|
+
* CLI command: Validate @-references and backtick file paths in a markdown file.
|
|
679
|
+
* @param cwd - Project working directory
|
|
680
|
+
* @param filePath - Path to the markdown file to check
|
|
681
|
+
* @param raw - Output raw 'valid'/'invalid' instead of JSON
|
|
682
|
+
*/
|
|
683
|
+
function cmdVerifyReferences(cwd: string, filePath: string, raw: boolean): void {
|
|
684
|
+
if (!filePath) {
|
|
685
|
+
error('file path required');
|
|
686
|
+
}
|
|
687
|
+
const fullPath: string = path.isAbsolute(filePath) ? filePath : path.join(cwd, filePath);
|
|
688
|
+
const content: string | null = readFileCached(fullPath);
|
|
689
|
+
if (!content) {
|
|
690
|
+
output({ error: 'File not found', path: filePath }, raw);
|
|
691
|
+
return;
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
const found: string[] = [];
|
|
695
|
+
const missing: string[] = [];
|
|
696
|
+
|
|
697
|
+
// Find @-references: @path/to/file (must contain / to be a file path)
|
|
698
|
+
const atRefs: string[] = content.match(/@([^\s\n,)]+\/[^\s\n,)]+)/g) || [];
|
|
699
|
+
for (const ref of atRefs) {
|
|
700
|
+
const cleanRef: string = ref.slice(1); // remove @
|
|
701
|
+
// Skip templated/dynamic refs (e.g. @${CLAUDE_PLUGIN_ROOT}/...) — same
|
|
702
|
+
// guard used by the backtick-ref branch below.
|
|
703
|
+
if (cleanRef.includes('${') || cleanRef.includes('{{')) continue;
|
|
704
|
+
const resolved: string = cleanRef.startsWith('~/')
|
|
705
|
+
? path.join(process.env.HOME || os.homedir() || '', cleanRef.slice(2))
|
|
706
|
+
: path.join(cwd, cleanRef);
|
|
707
|
+
if (fs.existsSync(resolved)) {
|
|
708
|
+
found.push(cleanRef);
|
|
709
|
+
} else {
|
|
710
|
+
missing.push(cleanRef);
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
// Find backtick file paths that look like real paths (contain / and have extension)
|
|
715
|
+
const backtickRefs: string[] = content.match(/`([^`]+\/[^`]+\.[a-zA-Z]{1,10})`/g) || [];
|
|
716
|
+
for (const ref of backtickRefs) {
|
|
717
|
+
const cleanRef: string = ref.slice(1, -1); // remove backticks
|
|
718
|
+
if (cleanRef.startsWith('http') || cleanRef.includes('${') || cleanRef.includes('{{')) continue;
|
|
719
|
+
if (found.includes(cleanRef) || missing.includes(cleanRef)) continue; // dedup
|
|
720
|
+
const resolved: string = path.join(cwd, cleanRef);
|
|
721
|
+
if (fs.existsSync(resolved)) {
|
|
722
|
+
found.push(cleanRef);
|
|
723
|
+
} else {
|
|
724
|
+
missing.push(cleanRef);
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
const result: ReferenceVerifyResult = {
|
|
729
|
+
valid: missing.length === 0,
|
|
730
|
+
found: found.length,
|
|
731
|
+
missing,
|
|
732
|
+
total: found.length + missing.length,
|
|
733
|
+
};
|
|
734
|
+
output(result, raw, missing.length === 0 ? 'valid' : 'invalid');
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
/**
|
|
738
|
+
* CLI command: Batch verify that git commit hashes exist in the repository.
|
|
739
|
+
* @param cwd - Project working directory
|
|
740
|
+
* @param hashes - Array of commit hashes to verify
|
|
741
|
+
* @param raw - Output raw 'valid'/'invalid' instead of JSON
|
|
742
|
+
*/
|
|
743
|
+
function cmdVerifyCommits(cwd: string, hashes: string[], raw: boolean): void {
|
|
744
|
+
if (!hashes || hashes.length === 0) {
|
|
745
|
+
error(
|
|
746
|
+
'At least one commit hash required. Usage: verify commits <hash1> [hash2 ...]. Run "git log --oneline" to find commit hashes'
|
|
747
|
+
);
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
const valid: string[] = [];
|
|
751
|
+
const invalid: string[] = [];
|
|
752
|
+
for (const hash of hashes) {
|
|
753
|
+
try {
|
|
754
|
+
validateGitRef(hash);
|
|
755
|
+
} catch {
|
|
756
|
+
invalid.push(hash);
|
|
757
|
+
continue;
|
|
758
|
+
}
|
|
759
|
+
const result: ExecGitResult = execGit(cwd, ['cat-file', '-t', hash]);
|
|
760
|
+
if (result.exitCode === 0 && result.stdout.trim() === 'commit') {
|
|
761
|
+
valid.push(hash);
|
|
762
|
+
} else {
|
|
763
|
+
invalid.push(hash);
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
const verifyResult: CommitVerifyResult = {
|
|
768
|
+
all_valid: invalid.length === 0,
|
|
769
|
+
valid,
|
|
770
|
+
invalid,
|
|
771
|
+
total: hashes.length,
|
|
772
|
+
};
|
|
773
|
+
output(verifyResult, raw, invalid.length === 0 ? 'valid' : 'invalid');
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
/**
|
|
777
|
+
* CLI command: Check that must_haves.artifacts from a plan exist on disk with required content.
|
|
778
|
+
* @param cwd - Project working directory
|
|
779
|
+
* @param planFilePath - Path to the PLAN.md file containing must_haves.artifacts
|
|
780
|
+
* @param raw - Output raw 'valid'/'invalid' instead of JSON
|
|
781
|
+
*/
|
|
782
|
+
function cmdVerifyArtifacts(cwd: string, planFilePath: string, raw: boolean): void {
|
|
783
|
+
if (!planFilePath) {
|
|
784
|
+
error('plan file path required');
|
|
785
|
+
}
|
|
786
|
+
const fullPath: string = path.isAbsolute(planFilePath)
|
|
787
|
+
? planFilePath
|
|
788
|
+
: path.join(cwd, planFilePath);
|
|
789
|
+
const content: string | null = readFileCached(fullPath);
|
|
790
|
+
if (!content) {
|
|
791
|
+
output({ error: 'File not found', path: planFilePath }, raw);
|
|
792
|
+
return;
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
const artifacts: MustHavesEntry[] = parseMustHavesBlock(content, 'artifacts');
|
|
796
|
+
if (artifacts.length === 0) {
|
|
797
|
+
output({ error: 'No must_haves.artifacts found in frontmatter', path: planFilePath }, raw);
|
|
798
|
+
return;
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
const results: ArtifactCheck[] = [];
|
|
802
|
+
for (const artifact of artifacts) {
|
|
803
|
+
if (typeof artifact === 'string') continue; // skip simple string items
|
|
804
|
+
const artPath: string | undefined = (artifact as MustHavesArtifact).path;
|
|
805
|
+
if (!artPath) continue;
|
|
806
|
+
|
|
807
|
+
const artFullPath: string = path.join(cwd, artPath);
|
|
808
|
+
const exists: boolean = fs.existsSync(artFullPath);
|
|
809
|
+
const check: ArtifactCheck = {
|
|
810
|
+
path: artPath,
|
|
811
|
+
exists,
|
|
812
|
+
issues: [],
|
|
813
|
+
passed: false,
|
|
814
|
+
plan_file: planFilePath,
|
|
815
|
+
must_haves_field: 'must_haves.artifacts',
|
|
816
|
+
};
|
|
817
|
+
|
|
818
|
+
if (exists) {
|
|
819
|
+
const fileContent: string = safeReadFile(artFullPath) || '';
|
|
820
|
+
const lineCount: number = fileContent.split('\n').length;
|
|
821
|
+
const artTyped = artifact as MustHavesArtifact;
|
|
822
|
+
|
|
823
|
+
if (artTyped.min_lines && lineCount < artTyped.min_lines) {
|
|
824
|
+
check.issues.push(`Only ${lineCount} lines, need ${artTyped.min_lines}`);
|
|
825
|
+
}
|
|
826
|
+
if (artTyped.contains && !fileContent.includes(artTyped.contains)) {
|
|
827
|
+
check.issues.push(`Missing pattern: ${artTyped.contains}`);
|
|
828
|
+
}
|
|
829
|
+
if (artTyped.exports) {
|
|
830
|
+
const exports: string[] = Array.isArray(artTyped.exports)
|
|
831
|
+
? artTyped.exports
|
|
832
|
+
: [artTyped.exports];
|
|
833
|
+
for (const exp of exports) {
|
|
834
|
+
if (!fileContent.includes(exp)) check.issues.push(`Missing export: ${exp}`);
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
check.passed = check.issues.length === 0;
|
|
838
|
+
} else {
|
|
839
|
+
check.issues.push('File not found');
|
|
840
|
+
check.remediation = `Create the missing file at: ${artPath}`;
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
results.push(check);
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
const passed: number = results.filter((r) => r.passed).length;
|
|
847
|
+
const verifyResult: ArtifactVerifyResult = {
|
|
848
|
+
all_passed: passed === results.length,
|
|
849
|
+
passed,
|
|
850
|
+
total: results.length,
|
|
851
|
+
artifacts: results,
|
|
852
|
+
};
|
|
853
|
+
output(verifyResult, raw, passed === results.length ? 'valid' : 'invalid');
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
/**
|
|
857
|
+
* CLI command: Validate must_haves.key_links patterns between source and target files.
|
|
858
|
+
* @param cwd - Project working directory
|
|
859
|
+
* @param planFilePath - Path to the PLAN.md file containing must_haves.key_links
|
|
860
|
+
* @param raw - Output raw 'valid'/'invalid' instead of JSON
|
|
861
|
+
*/
|
|
862
|
+
function cmdVerifyKeyLinks(cwd: string, planFilePath: string, raw: boolean): void {
|
|
863
|
+
if (!planFilePath) {
|
|
864
|
+
error('plan file path required');
|
|
865
|
+
}
|
|
866
|
+
const fullPath: string = path.isAbsolute(planFilePath)
|
|
867
|
+
? planFilePath
|
|
868
|
+
: path.join(cwd, planFilePath);
|
|
869
|
+
const content: string | null = readFileCached(fullPath);
|
|
870
|
+
if (!content) {
|
|
871
|
+
output({ error: 'File not found', path: planFilePath }, raw);
|
|
872
|
+
return;
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
const keyLinks: MustHavesEntry[] = parseMustHavesBlock(content, 'key_links');
|
|
876
|
+
if (keyLinks.length === 0) {
|
|
877
|
+
output({ error: 'No must_haves.key_links found in frontmatter', path: planFilePath }, raw);
|
|
878
|
+
return;
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
const results: KeyLinkCheck[] = [];
|
|
882
|
+
for (const link of keyLinks) {
|
|
883
|
+
if (typeof link === 'string') continue;
|
|
884
|
+
const linkTyped = link as MustHavesKeyLink;
|
|
885
|
+
const check: KeyLinkCheck = {
|
|
886
|
+
from: linkTyped.from,
|
|
887
|
+
to: linkTyped.to,
|
|
888
|
+
via: linkTyped.via || '',
|
|
889
|
+
verified: false,
|
|
890
|
+
detail: '',
|
|
891
|
+
};
|
|
892
|
+
|
|
893
|
+
const sourceContent: string | null = safeReadFile(path.join(cwd, linkTyped.from || ''));
|
|
894
|
+
if (!sourceContent) {
|
|
895
|
+
check.detail = 'Source file not found';
|
|
896
|
+
} else if (linkTyped.pattern) {
|
|
897
|
+
try {
|
|
898
|
+
const regex = new RegExp(linkTyped.pattern);
|
|
899
|
+
if (regex.test(sourceContent)) {
|
|
900
|
+
check.verified = true;
|
|
901
|
+
check.detail = 'Pattern found in source';
|
|
902
|
+
} else {
|
|
903
|
+
const targetContent: string | null = safeReadFile(path.join(cwd, linkTyped.to || ''));
|
|
904
|
+
if (targetContent && regex.test(targetContent)) {
|
|
905
|
+
check.verified = true;
|
|
906
|
+
check.detail = 'Pattern found in target';
|
|
907
|
+
} else {
|
|
908
|
+
check.detail = `Pattern "${linkTyped.pattern}" not found in source or target`;
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
} catch {
|
|
912
|
+
check.detail = `Invalid regex pattern: ${linkTyped.pattern}`;
|
|
913
|
+
}
|
|
914
|
+
} else {
|
|
915
|
+
// No pattern: just check source references target
|
|
916
|
+
if (sourceContent.includes(linkTyped.to || '')) {
|
|
917
|
+
check.verified = true;
|
|
918
|
+
check.detail = 'Target referenced in source';
|
|
919
|
+
} else {
|
|
920
|
+
check.detail = 'Target not referenced in source';
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
results.push(check);
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
const verified: number = results.filter((r) => r.verified).length;
|
|
928
|
+
const verifyResult: KeyLinkVerifyResult = {
|
|
929
|
+
all_verified: verified === results.length,
|
|
930
|
+
verified,
|
|
931
|
+
total: results.length,
|
|
932
|
+
links: results,
|
|
933
|
+
};
|
|
934
|
+
output(verifyResult, raw, verified === results.length ? 'valid' : 'invalid');
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
/**
|
|
938
|
+
* Result of a single mechanical check inside the bundle.
|
|
939
|
+
*/
|
|
940
|
+
interface MechanicalCheckResult {
|
|
941
|
+
check: 'frontmatter' | 'artifacts' | 'key_links' | 'references' | 'plan_summary_completeness';
|
|
942
|
+
scope: string;
|
|
943
|
+
passed: boolean;
|
|
944
|
+
detail: string;
|
|
945
|
+
data?: Record<string, unknown>;
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
/**
|
|
949
|
+
* Aggregated result of cmdVerifyMechanical — the "Mechanical tier" of
|
|
950
|
+
* the verifier agent's three-stage gate.
|
|
951
|
+
*/
|
|
952
|
+
interface MechanicalVerifyResult {
|
|
953
|
+
passed: boolean;
|
|
954
|
+
phase: string;
|
|
955
|
+
plan_count: number;
|
|
956
|
+
total_checks: number;
|
|
957
|
+
passed_count: number;
|
|
958
|
+
failed_count: number;
|
|
959
|
+
checks: MechanicalCheckResult[];
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
/**
|
|
963
|
+
* Bundle the four PLAN.md mechanical checks (frontmatter, artifacts,
|
|
964
|
+
* key_links, references) plus a phase-level plan/summary completeness
|
|
965
|
+
* check into a single aggregated JSON result. Reuses the same helpers
|
|
966
|
+
* as the discrete verify commands so behavior stays consistent.
|
|
967
|
+
*
|
|
968
|
+
* Required PLAN.md frontmatter fields are kept in sync with
|
|
969
|
+
* cmdVerifyPlanStructure. Frontmatter check passes if all required
|
|
970
|
+
* fields are present; artifact and key-link checks pass when every
|
|
971
|
+
* declared item resolves; reference check passes when every @-reference
|
|
972
|
+
* and backtick path resolves; completeness check passes when every
|
|
973
|
+
* PLAN has a matching SUMMARY.
|
|
974
|
+
*
|
|
975
|
+
* @param cwd - Project working directory
|
|
976
|
+
* @param phase - Phase number or name passed to findPhaseInternal
|
|
977
|
+
* @param raw - Output 'pass'/'fail' instead of JSON
|
|
978
|
+
*/
|
|
979
|
+
function cmdVerifyMechanical(cwd: string, phase: string, raw: boolean): void {
|
|
980
|
+
if (!phase) {
|
|
981
|
+
error('phase required');
|
|
982
|
+
}
|
|
983
|
+
const phaseInfo: PhaseInfo | null = findPhaseInternal(cwd, phase);
|
|
984
|
+
if (!phaseInfo || !phaseInfo.found) {
|
|
985
|
+
output({ error: 'Phase not found', phase }, raw);
|
|
986
|
+
return;
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
const phaseDir: string = path.join(cwd, phaseInfo.directory);
|
|
990
|
+
let files: string[];
|
|
991
|
+
try {
|
|
992
|
+
files = fs.readdirSync(phaseDir);
|
|
993
|
+
} catch {
|
|
994
|
+
output({ error: 'Cannot read phase directory', phase }, raw);
|
|
995
|
+
return;
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
// Accept both prefixed (`01-01-PLAN.md`) and bare (`PLAN.md`) filenames —
|
|
999
|
+
// matches the convention used across the codebase (phase.ts:336, utils.ts:1046,
|
|
1000
|
+
// gates.ts:199, knowledge.ts:230, roadmap.ts:614).
|
|
1001
|
+
const plans: string[] = files.filter(
|
|
1002
|
+
(f) => /-PLAN\.md$/i.test(f) || f === 'PLAN.md'
|
|
1003
|
+
);
|
|
1004
|
+
const summaries: string[] = files.filter(
|
|
1005
|
+
(f) => /-SUMMARY\.md$/i.test(f) || f === 'SUMMARY.md'
|
|
1006
|
+
);
|
|
1007
|
+
const checks: MechanicalCheckResult[] = [];
|
|
1008
|
+
|
|
1009
|
+
// A phase with zero PLAN.md files cannot pass the mechanical gate by
|
|
1010
|
+
// vacuously satisfying every check. Emit an explicit failing check so the
|
|
1011
|
+
// aggregate result correctly reports passed=false.
|
|
1012
|
+
checks.push({
|
|
1013
|
+
check: 'plan_summary_completeness',
|
|
1014
|
+
scope: `phase:${phaseInfo.phase_number}`,
|
|
1015
|
+
passed: plans.length > 0,
|
|
1016
|
+
detail:
|
|
1017
|
+
plans.length > 0
|
|
1018
|
+
? `Phase has ${plans.length} PLAN.md file(s)`
|
|
1019
|
+
: 'Phase has no PLAN.md files — nothing to verify',
|
|
1020
|
+
data: { plan_count: plans.length },
|
|
1021
|
+
});
|
|
1022
|
+
if (plans.length === 0) {
|
|
1023
|
+
const result: MechanicalVerifyResult = {
|
|
1024
|
+
passed: false,
|
|
1025
|
+
phase: phaseInfo.phase_number,
|
|
1026
|
+
plan_count: 0,
|
|
1027
|
+
total_checks: checks.length,
|
|
1028
|
+
passed_count: 0,
|
|
1029
|
+
failed_count: checks.length,
|
|
1030
|
+
checks,
|
|
1031
|
+
};
|
|
1032
|
+
output(result, raw, 'fail');
|
|
1033
|
+
return;
|
|
1034
|
+
}
|
|
1035
|
+
// The placeholder above will be replaced by the real plan_summary_completeness
|
|
1036
|
+
// check below; pop it so we don't double-report.
|
|
1037
|
+
checks.pop();
|
|
1038
|
+
|
|
1039
|
+
const requiredFrontmatterFields: readonly string[] = [
|
|
1040
|
+
'phase',
|
|
1041
|
+
'plan',
|
|
1042
|
+
'type',
|
|
1043
|
+
'wave',
|
|
1044
|
+
'depends_on',
|
|
1045
|
+
'files_modified',
|
|
1046
|
+
'autonomous',
|
|
1047
|
+
'must_haves',
|
|
1048
|
+
];
|
|
1049
|
+
|
|
1050
|
+
for (const planFile of plans) {
|
|
1051
|
+
const planPath: string = path.join(phaseDir, planFile);
|
|
1052
|
+
const content: string | null = readFileCached(planPath);
|
|
1053
|
+
if (!content) {
|
|
1054
|
+
checks.push({
|
|
1055
|
+
check: 'frontmatter',
|
|
1056
|
+
scope: `plan:${planFile}`,
|
|
1057
|
+
passed: false,
|
|
1058
|
+
detail: 'PLAN.md unreadable',
|
|
1059
|
+
});
|
|
1060
|
+
continue;
|
|
1061
|
+
}
|
|
1062
|
+
const fm: FrontmatterObject = extractFrontmatter(content);
|
|
1063
|
+
|
|
1064
|
+
// frontmatter check
|
|
1065
|
+
const missingFields: string[] = requiredFrontmatterFields.filter(
|
|
1066
|
+
(f) => fm[f] === undefined
|
|
1067
|
+
);
|
|
1068
|
+
checks.push({
|
|
1069
|
+
check: 'frontmatter',
|
|
1070
|
+
scope: `plan:${planFile}`,
|
|
1071
|
+
passed: missingFields.length === 0,
|
|
1072
|
+
detail:
|
|
1073
|
+
missingFields.length === 0
|
|
1074
|
+
? `All ${requiredFrontmatterFields.length} required fields present`
|
|
1075
|
+
: `Missing: ${missingFields.join(', ')}`,
|
|
1076
|
+
data: { missing: missingFields, required: [...requiredFrontmatterFields] },
|
|
1077
|
+
});
|
|
1078
|
+
|
|
1079
|
+
// artifacts check — mirror cmdVerifyArtifacts: existence AND content
|
|
1080
|
+
// constraints (min_lines / contains / exports).
|
|
1081
|
+
const artifacts: MustHavesEntry[] = parseMustHavesBlock(content, 'artifacts');
|
|
1082
|
+
if (artifacts.length > 0) {
|
|
1083
|
+
const failed: { path: string; issues: string[] }[] = [];
|
|
1084
|
+
for (const art of artifacts) {
|
|
1085
|
+
if (typeof art === 'string') continue;
|
|
1086
|
+
const artTyped = art as MustHavesArtifact;
|
|
1087
|
+
const artPath: string | undefined = artTyped.path;
|
|
1088
|
+
if (!artPath) continue;
|
|
1089
|
+
const artFullPath: string = path.join(cwd, artPath);
|
|
1090
|
+
const issues: string[] = [];
|
|
1091
|
+
if (!fs.existsSync(artFullPath)) {
|
|
1092
|
+
issues.push('File not found');
|
|
1093
|
+
} else {
|
|
1094
|
+
const fileContent: string = safeReadFile(artFullPath) || '';
|
|
1095
|
+
const lineCount: number = fileContent.split('\n').length;
|
|
1096
|
+
if (artTyped.min_lines && lineCount < artTyped.min_lines) {
|
|
1097
|
+
issues.push(`Only ${lineCount} lines, need ${artTyped.min_lines}`);
|
|
1098
|
+
}
|
|
1099
|
+
if (artTyped.contains && !fileContent.includes(artTyped.contains)) {
|
|
1100
|
+
issues.push(`Missing pattern: ${artTyped.contains}`);
|
|
1101
|
+
}
|
|
1102
|
+
if (artTyped.exports) {
|
|
1103
|
+
const exps: string[] = Array.isArray(artTyped.exports)
|
|
1104
|
+
? artTyped.exports
|
|
1105
|
+
: [artTyped.exports];
|
|
1106
|
+
for (const exp of exps) {
|
|
1107
|
+
if (!fileContent.includes(exp)) issues.push(`Missing export: ${exp}`);
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1111
|
+
if (issues.length > 0) failed.push({ path: artPath, issues });
|
|
1112
|
+
}
|
|
1113
|
+
checks.push({
|
|
1114
|
+
check: 'artifacts',
|
|
1115
|
+
scope: `plan:${planFile}`,
|
|
1116
|
+
passed: failed.length === 0,
|
|
1117
|
+
detail:
|
|
1118
|
+
failed.length === 0
|
|
1119
|
+
? `All ${artifacts.length} artifacts present and satisfy content constraints`
|
|
1120
|
+
: `Failed: ${failed.map((f) => `${f.path} (${f.issues.join('; ')})`).join('; ')}`,
|
|
1121
|
+
data: { failed },
|
|
1122
|
+
});
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1125
|
+
// key_links check
|
|
1126
|
+
const keyLinks: MustHavesEntry[] = parseMustHavesBlock(content, 'key_links');
|
|
1127
|
+
if (keyLinks.length > 0) {
|
|
1128
|
+
const failed: string[] = [];
|
|
1129
|
+
for (const link of keyLinks) {
|
|
1130
|
+
if (typeof link === 'string') continue;
|
|
1131
|
+
const linkTyped = link as MustHavesKeyLink;
|
|
1132
|
+
const fromContent: string | null = safeReadFile(path.join(cwd, linkTyped.from || ''));
|
|
1133
|
+
const toPath: string = path.join(cwd, linkTyped.to || '');
|
|
1134
|
+
let verified = false;
|
|
1135
|
+
if (fromContent) {
|
|
1136
|
+
if (linkTyped.pattern) {
|
|
1137
|
+
try {
|
|
1138
|
+
const regex = new RegExp(linkTyped.pattern);
|
|
1139
|
+
if (regex.test(fromContent)) verified = true;
|
|
1140
|
+
else {
|
|
1141
|
+
const toContent: string | null = safeReadFile(toPath);
|
|
1142
|
+
if (toContent && regex.test(toContent)) verified = true;
|
|
1143
|
+
}
|
|
1144
|
+
} catch {
|
|
1145
|
+
verified = false;
|
|
1146
|
+
}
|
|
1147
|
+
} else {
|
|
1148
|
+
verified = fromContent.includes(linkTyped.to || '');
|
|
1149
|
+
}
|
|
1150
|
+
}
|
|
1151
|
+
if (!verified) failed.push(`${linkTyped.from} → ${linkTyped.to}`);
|
|
1152
|
+
}
|
|
1153
|
+
checks.push({
|
|
1154
|
+
check: 'key_links',
|
|
1155
|
+
scope: `plan:${planFile}`,
|
|
1156
|
+
passed: failed.length === 0,
|
|
1157
|
+
detail:
|
|
1158
|
+
failed.length === 0
|
|
1159
|
+
? `All ${keyLinks.length} key links verified`
|
|
1160
|
+
: `Failed: ${failed.join('; ')}`,
|
|
1161
|
+
data: { failed },
|
|
1162
|
+
});
|
|
1163
|
+
}
|
|
1164
|
+
|
|
1165
|
+
// references check
|
|
1166
|
+
const missingRefs: string[] = [];
|
|
1167
|
+
const atRefs: string[] = content.match(/@([^\s\n,)]+\/[^\s\n,)]+)/g) || [];
|
|
1168
|
+
for (const ref of atRefs) {
|
|
1169
|
+
const cleanRef: string = ref.slice(1);
|
|
1170
|
+
// Skip templated/dynamic refs (e.g. @${CLAUDE_PLUGIN_ROOT}/...) — they
|
|
1171
|
+
// are not literal paths. Same guard the backtick branch uses below.
|
|
1172
|
+
if (cleanRef.includes('${') || cleanRef.includes('{{')) continue;
|
|
1173
|
+
const resolved: string = cleanRef.startsWith('~/')
|
|
1174
|
+
? path.join(process.env.HOME || os.homedir() || '', cleanRef.slice(2))
|
|
1175
|
+
: path.join(cwd, cleanRef);
|
|
1176
|
+
if (!fs.existsSync(resolved)) missingRefs.push(cleanRef);
|
|
1177
|
+
}
|
|
1178
|
+
const backtickRefs: string[] = content.match(/`([^`]+\/[^`]+\.[a-zA-Z]{1,10})`/g) || [];
|
|
1179
|
+
for (const ref of backtickRefs) {
|
|
1180
|
+
const cleanRef: string = ref.slice(1, -1);
|
|
1181
|
+
if (cleanRef.startsWith('http') || cleanRef.includes('${') || cleanRef.includes('{{')) continue;
|
|
1182
|
+
if (missingRefs.includes(cleanRef)) continue;
|
|
1183
|
+
if (!fs.existsSync(path.join(cwd, cleanRef))) missingRefs.push(cleanRef);
|
|
1184
|
+
}
|
|
1185
|
+
const totalRefs: number = atRefs.length + backtickRefs.length;
|
|
1186
|
+
if (totalRefs > 0) {
|
|
1187
|
+
checks.push({
|
|
1188
|
+
check: 'references',
|
|
1189
|
+
scope: `plan:${planFile}`,
|
|
1190
|
+
passed: missingRefs.length === 0,
|
|
1191
|
+
detail:
|
|
1192
|
+
missingRefs.length === 0
|
|
1193
|
+
? `All ${totalRefs} references resolve`
|
|
1194
|
+
: `Missing: ${missingRefs.join(', ')}`,
|
|
1195
|
+
data: { missing: missingRefs, total: totalRefs },
|
|
1196
|
+
});
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1200
|
+
// phase-level: plan/summary completeness — bare PLAN.md / SUMMARY.md
|
|
1201
|
+
// normalise to '' so they pair off.
|
|
1202
|
+
const stripPlanId = (n: string): string =>
|
|
1203
|
+
n === 'PLAN.md' ? '' : n.replace(/-PLAN\.md$/i, '');
|
|
1204
|
+
const stripSummaryId = (n: string): string =>
|
|
1205
|
+
n === 'SUMMARY.md' ? '' : n.replace(/-SUMMARY\.md$/i, '');
|
|
1206
|
+
const planIds = new Set<string>(plans.map(stripPlanId));
|
|
1207
|
+
const summaryIds = new Set<string>(summaries.map(stripSummaryId));
|
|
1208
|
+
const incomplete: string[] = [...planIds].filter((id) => !summaryIds.has(id));
|
|
1209
|
+
checks.push({
|
|
1210
|
+
check: 'plan_summary_completeness',
|
|
1211
|
+
scope: `phase:${phaseInfo.phase_number}`,
|
|
1212
|
+
passed: incomplete.length === 0,
|
|
1213
|
+
detail:
|
|
1214
|
+
incomplete.length === 0
|
|
1215
|
+
? `All ${plans.length} plans have summaries`
|
|
1216
|
+
: `Plans without summaries: ${incomplete.join(', ')}`,
|
|
1217
|
+
data: { incomplete, plan_count: plans.length, summary_count: summaries.length },
|
|
1218
|
+
});
|
|
1219
|
+
|
|
1220
|
+
const passedCount: number = checks.filter((c) => c.passed).length;
|
|
1221
|
+
const failedCount: number = checks.length - passedCount;
|
|
1222
|
+
const result: MechanicalVerifyResult = {
|
|
1223
|
+
passed: failedCount === 0,
|
|
1224
|
+
phase: phaseInfo.phase_number,
|
|
1225
|
+
plan_count: plans.length,
|
|
1226
|
+
total_checks: checks.length,
|
|
1227
|
+
passed_count: passedCount,
|
|
1228
|
+
failed_count: failedCount,
|
|
1229
|
+
checks,
|
|
1230
|
+
};
|
|
1231
|
+
output(result, raw, failedCount === 0 ? 'pass' : 'fail');
|
|
1232
|
+
}
|
|
1233
|
+
|
|
1234
|
+
// ─── Diagnose Phase ──────────────────────────────────────────────────────────
|
|
1235
|
+
|
|
1236
|
+
/** A single ranked root-cause finding from phase diagnosis. */
|
|
1237
|
+
interface DiagnosisEntry {
|
|
1238
|
+
rank: number;
|
|
1239
|
+
cause: string;
|
|
1240
|
+
evidence: string;
|
|
1241
|
+
suggestion: string;
|
|
1242
|
+
}
|
|
1243
|
+
|
|
1244
|
+
/** Result of phase diagnosis. */
|
|
1245
|
+
interface DiagnosisResult {
|
|
1246
|
+
phase: string;
|
|
1247
|
+
verdict: string;
|
|
1248
|
+
root_causes: DiagnosisEntry[];
|
|
1249
|
+
failed_checks: string[];
|
|
1250
|
+
git_diff_lines: number;
|
|
1251
|
+
plans_missing_summaries: number;
|
|
1252
|
+
}
|
|
1253
|
+
|
|
1254
|
+
/**
|
|
1255
|
+
* CLI command: Diagnose a failed phase by reading VERIFICATION.md, plan files,
|
|
1256
|
+
* and running git diff to produce a ranked root-cause list.
|
|
1257
|
+
*
|
|
1258
|
+
* Intended as a quick forensics tool after `gd verify-phase N` fails.
|
|
1259
|
+
* Reads:
|
|
1260
|
+
* - .planning/milestones/{m}/phases/{N}/VERIFICATION.md (verdict + failed checks)
|
|
1261
|
+
* - *-PLAN.md files in phase dir (detect plans without summaries)
|
|
1262
|
+
* - `git diff HEAD` (scope estimate of uncommitted changes)
|
|
1263
|
+
* Ranks causes by heuristic signal strength.
|
|
1264
|
+
*
|
|
1265
|
+
* @param cwd - Project working directory
|
|
1266
|
+
* @param phase - Phase number or name
|
|
1267
|
+
* @param raw - Output raw text instead of JSON
|
|
1268
|
+
*/
|
|
1269
|
+
function cmdDiagnosePhase(cwd: string, phase: string, raw: boolean): void {
|
|
1270
|
+
if (!phase) {
|
|
1271
|
+
error('phase required. Usage: gd diagnose <phase>');
|
|
1272
|
+
}
|
|
1273
|
+
|
|
1274
|
+
const phaseInfo: PhaseInfo | null = findPhaseInternal(cwd, phase);
|
|
1275
|
+
if (!phaseInfo || !phaseInfo.found) {
|
|
1276
|
+
output({ error: 'Phase not found', phase }, raw);
|
|
1277
|
+
return;
|
|
1278
|
+
}
|
|
1279
|
+
|
|
1280
|
+
const phaseDir: string = path.join(cwd, phaseInfo.directory);
|
|
1281
|
+
// Codex r20 P2: scaffold/execution flow produces prefixed names like
|
|
1282
|
+
// `75-VERIFICATION.md`. Match both forms.
|
|
1283
|
+
let verificationPath: string = path.join(phaseDir, 'VERIFICATION.md');
|
|
1284
|
+
try {
|
|
1285
|
+
const files = require('fs').readdirSync(phaseDir) as string[];
|
|
1286
|
+
const prefixed = files.find((f: string) => /-VERIFICATION\.md$/.test(f));
|
|
1287
|
+
if (prefixed && !require('fs').existsSync(verificationPath)) {
|
|
1288
|
+
verificationPath = path.join(phaseDir, prefixed);
|
|
1289
|
+
}
|
|
1290
|
+
} catch { /* fall through */ }
|
|
1291
|
+
const verificationContent: string | null = safeReadFile(verificationPath);
|
|
1292
|
+
|
|
1293
|
+
// Parse failed checks from VERIFICATION.md
|
|
1294
|
+
const failedChecks: string[] = [];
|
|
1295
|
+
let verdict = 'unknown';
|
|
1296
|
+
if (verificationContent) {
|
|
1297
|
+
const verdictMatch = verificationContent.match(/\*\*verdict\*\*[:\s]*([^\n]+)/i);
|
|
1298
|
+
if (verdictMatch) verdict = verdictMatch[1].trim();
|
|
1299
|
+
|
|
1300
|
+
// Extract lines starting with ❌ or [FAIL] or "- FAIL"
|
|
1301
|
+
const failLines = verificationContent.match(/^(?:❌|[-*]\s*\[?FAIL[^]]*]?).*$/gim) || [];
|
|
1302
|
+
for (const line of failLines) {
|
|
1303
|
+
const clean = line.replace(/^[-*\s❌✗x]+/i, '').trim();
|
|
1304
|
+
if (clean) failedChecks.push(clean);
|
|
1305
|
+
}
|
|
1306
|
+
// Also parse "check: X passed: false" patterns in JSON-like blocks
|
|
1307
|
+
const checkFailMatches = verificationContent.match(/"check"\s*:\s*"([^"]+)"[^}]*"passed"\s*:\s*false/g) || [];
|
|
1308
|
+
for (const m of checkFailMatches) {
|
|
1309
|
+
const nameMatch = m.match(/"check"\s*:\s*"([^"]+)"/);
|
|
1310
|
+
if (nameMatch && !failedChecks.includes(nameMatch[1])) {
|
|
1311
|
+
failedChecks.push(nameMatch[1]);
|
|
1312
|
+
}
|
|
1313
|
+
}
|
|
1314
|
+
}
|
|
1315
|
+
|
|
1316
|
+
// Check for plans without summaries
|
|
1317
|
+
let files: string[] = [];
|
|
1318
|
+
try {
|
|
1319
|
+
files = fs.readdirSync(phaseDir) as string[];
|
|
1320
|
+
} catch {
|
|
1321
|
+
// phase dir unreadable
|
|
1322
|
+
}
|
|
1323
|
+
const plans = files.filter((f) => /-PLAN\.md$/i.test(f) || f === 'PLAN.md');
|
|
1324
|
+
const summaries = files.filter((f) => /-SUMMARY\.md$/i.test(f) || f === 'SUMMARY.md');
|
|
1325
|
+
const planIds = plans.map((f) => (f === 'PLAN.md' ? '' : f.replace(/-PLAN\.md$/i, '')));
|
|
1326
|
+
const summaryIds = new Set(summaries.map((f) => (f === 'SUMMARY.md' ? '' : f.replace(/-SUMMARY\.md$/i, ''))));
|
|
1327
|
+
const plansMissingSummaries = planIds.filter((id) => !summaryIds.has(id)).length;
|
|
1328
|
+
|
|
1329
|
+
// Measure uncommitted git diff size
|
|
1330
|
+
let gitDiffLines = 0;
|
|
1331
|
+
try {
|
|
1332
|
+
const diffResult = execGit(cwd, ['diff', 'HEAD', '--stat'], { allowBlocked: true });
|
|
1333
|
+
if (diffResult.stdout) {
|
|
1334
|
+
gitDiffLines = (diffResult.stdout.match(/\n/g) || []).length;
|
|
1335
|
+
}
|
|
1336
|
+
} catch {
|
|
1337
|
+
// git not available or not a repo
|
|
1338
|
+
}
|
|
1339
|
+
|
|
1340
|
+
// Build ranked root causes
|
|
1341
|
+
const rootCauses: DiagnosisEntry[] = [];
|
|
1342
|
+
let rank = 1;
|
|
1343
|
+
|
|
1344
|
+
if (plansMissingSummaries > 0) {
|
|
1345
|
+
rootCauses.push({
|
|
1346
|
+
rank: rank++,
|
|
1347
|
+
cause: `${plansMissingSummaries} plan(s) missing SUMMARY.md`,
|
|
1348
|
+
evidence: `Found ${plans.length} PLAN.md files, ${summaries.length} SUMMARY.md files`,
|
|
1349
|
+
suggestion: 'Run gd execute-phase N or manually write SUMMARY.md for each incomplete plan',
|
|
1350
|
+
});
|
|
1351
|
+
}
|
|
1352
|
+
|
|
1353
|
+
if (!verificationContent) {
|
|
1354
|
+
rootCauses.push({
|
|
1355
|
+
rank: rank++,
|
|
1356
|
+
cause: 'VERIFICATION.md not found',
|
|
1357
|
+
evidence: `Expected at ${path.relative(cwd, verificationPath)}`,
|
|
1358
|
+
suggestion: 'Run gd verify-phase N to generate VERIFICATION.md before diagnosing',
|
|
1359
|
+
});
|
|
1360
|
+
} else if (verdict.toLowerCase().includes('fail') || verdict === 'unknown') {
|
|
1361
|
+
rootCauses.push({
|
|
1362
|
+
rank: rank++,
|
|
1363
|
+
cause: `Verification verdict: ${verdict}`,
|
|
1364
|
+
evidence: `${failedChecks.length} failed check(s) recorded`,
|
|
1365
|
+
suggestion: `Address failed checks: ${failedChecks.slice(0, 3).join(', ')}${failedChecks.length > 3 ? '...' : ''}`,
|
|
1366
|
+
});
|
|
1367
|
+
}
|
|
1368
|
+
|
|
1369
|
+
for (const check of failedChecks.slice(0, 5)) {
|
|
1370
|
+
const cause = `Failed check: ${check}`;
|
|
1371
|
+
if (rootCauses.some((c) => c.cause === cause)) continue;
|
|
1372
|
+
rootCauses.push({
|
|
1373
|
+
rank: rank++,
|
|
1374
|
+
cause,
|
|
1375
|
+
evidence: `Recorded in VERIFICATION.md`,
|
|
1376
|
+
suggestion: _checkSuggestion(check),
|
|
1377
|
+
});
|
|
1378
|
+
}
|
|
1379
|
+
|
|
1380
|
+
if (gitDiffLines > 10) {
|
|
1381
|
+
rootCauses.push({
|
|
1382
|
+
rank: rank,
|
|
1383
|
+
cause: `${gitDiffLines} uncommitted lines in working tree`,
|
|
1384
|
+
evidence: 'git diff HEAD --stat shows pending changes',
|
|
1385
|
+
suggestion: 'Commit or stash changes before re-running verification',
|
|
1386
|
+
});
|
|
1387
|
+
}
|
|
1388
|
+
|
|
1389
|
+
if (rootCauses.length === 0) {
|
|
1390
|
+
rootCauses.push({
|
|
1391
|
+
rank: 1,
|
|
1392
|
+
cause: 'No obvious failure signals found',
|
|
1393
|
+
evidence: 'VERIFICATION.md exists, no missing summaries, no large diffs',
|
|
1394
|
+
suggestion: 'Review VERIFICATION.md manually or re-run gd verify-phase N for details',
|
|
1395
|
+
});
|
|
1396
|
+
}
|
|
1397
|
+
|
|
1398
|
+
const result: DiagnosisResult = {
|
|
1399
|
+
phase: phaseInfo.phase_number,
|
|
1400
|
+
verdict,
|
|
1401
|
+
root_causes: rootCauses,
|
|
1402
|
+
failed_checks: failedChecks,
|
|
1403
|
+
git_diff_lines: gitDiffLines,
|
|
1404
|
+
plans_missing_summaries: plansMissingSummaries,
|
|
1405
|
+
};
|
|
1406
|
+
|
|
1407
|
+
const summary = `Phase ${phaseInfo.phase_number}: ${rootCauses.length} root cause(s) — top: ${rootCauses[0].cause}`;
|
|
1408
|
+
output(result, raw, summary);
|
|
1409
|
+
}
|
|
1410
|
+
|
|
1411
|
+
/** Map a failed check name to a human-readable next-step suggestion. */
|
|
1412
|
+
function _checkSuggestion(check: string): string {
|
|
1413
|
+
if (/frontmatter/i.test(check)) return 'Ensure PLAN.md has all required frontmatter fields (phase, plan, type, wave, depends_on, files_modified, autonomous, must_haves)';
|
|
1414
|
+
if (/artifact/i.test(check)) return 'Verify all must_haves artifacts exist at the declared paths with required content';
|
|
1415
|
+
if (/reference/i.test(check)) return 'Fix broken @file or `path` references in PLAN.md';
|
|
1416
|
+
if (/key.?link/i.test(check)) return 'Confirm all key_links pairs are wired together in source/target files';
|
|
1417
|
+
if (/summary|completeness/i.test(check)) return 'Write a SUMMARY.md for each PLAN.md in the phase directory';
|
|
1418
|
+
return `Review the "${check}" section in VERIFICATION.md for details`;
|
|
1419
|
+
}
|
|
1420
|
+
|
|
1421
|
+
// ─── Exports ─────────────────────────────────────────────────────────────────
|
|
1422
|
+
|
|
1423
|
+
module.exports = {
|
|
1424
|
+
cmdVerifySummary,
|
|
1425
|
+
cmdVerifyPlanStructure,
|
|
1426
|
+
cmdVerifyPhaseCompleteness,
|
|
1427
|
+
cmdVerifyReferences,
|
|
1428
|
+
cmdVerifyCommits,
|
|
1429
|
+
cmdVerifyArtifacts,
|
|
1430
|
+
cmdVerifyKeyLinks,
|
|
1431
|
+
cmdVerifyMechanical,
|
|
1432
|
+
cmdDiagnosePhase,
|
|
1433
|
+
clearVerifyCache,
|
|
1434
|
+
};
|