@therocketcode/gsd-core 1.4.0
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 +23 -0
- package/GEMINI.md +53 -0
- package/LICENSE +21 -0
- package/README.ja-JP.md +125 -0
- package/README.ko-KR.md +125 -0
- package/README.md +144 -0
- package/README.pt-BR.md +125 -0
- package/README.zh-CN.md +125 -0
- package/agents/gsd-advisor-researcher.md +108 -0
- package/agents/gsd-ai-researcher.md +114 -0
- package/agents/gsd-assumptions-analyzer.md +105 -0
- package/agents/gsd-code-fixer.md +668 -0
- package/agents/gsd-code-reviewer.md +387 -0
- package/agents/gsd-codebase-mapper.md +853 -0
- package/agents/gsd-debug-session-manager.md +314 -0
- package/agents/gsd-debugger.md +1452 -0
- package/agents/gsd-doc-classifier.md +168 -0
- package/agents/gsd-doc-synthesizer.md +204 -0
- package/agents/gsd-doc-verifier.md +217 -0
- package/agents/gsd-doc-writer.md +616 -0
- package/agents/gsd-domain-researcher.md +147 -0
- package/agents/gsd-eval-auditor.md +191 -0
- package/agents/gsd-eval-planner.md +154 -0
- package/agents/gsd-executor.md +785 -0
- package/agents/gsd-framework-selector.md +160 -0
- package/agents/gsd-integration-checker.md +470 -0
- package/agents/gsd-intel-updater.md +342 -0
- package/agents/gsd-nyquist-auditor.md +203 -0
- package/agents/gsd-pattern-mapper.md +335 -0
- package/agents/gsd-phase-researcher.md +867 -0
- package/agents/gsd-plan-checker.md +978 -0
- package/agents/gsd-planner.md +1204 -0
- package/agents/gsd-project-researcher.md +611 -0
- package/agents/gsd-research-synthesizer.md +259 -0
- package/agents/gsd-roadmapper.md +688 -0
- package/agents/gsd-security-auditor.md +155 -0
- package/agents/gsd-ui-auditor.md +495 -0
- package/agents/gsd-ui-checker.md +309 -0
- package/agents/gsd-ui-researcher.md +374 -0
- package/agents/gsd-user-profiler.md +171 -0
- package/agents/gsd-verifier.md +923 -0
- package/assets/gsd-logo-2000-transparent.png +0 -0
- package/assets/gsd-logo-2000-transparent.svg +17 -0
- package/assets/gsd-logo-2000.png +0 -0
- package/assets/gsd-logo-2000.svg +21 -0
- package/assets/terminal.svg +68 -0
- package/bin/install.js +12726 -0
- package/bin/lib/ui-safety-gate.cjs +107 -0
- package/commands/gsd/add-tests.md +42 -0
- package/commands/gsd/ai-integration-phase.md +37 -0
- package/commands/gsd/audit-fix.md +34 -0
- package/commands/gsd/audit-milestone.md +37 -0
- package/commands/gsd/audit-uat.md +24 -0
- package/commands/gsd/autonomous.md +48 -0
- package/commands/gsd/capture.md +62 -0
- package/commands/gsd/cleanup.md +24 -0
- package/commands/gsd/code-review.md +59 -0
- package/commands/gsd/complete-milestone.md +143 -0
- package/commands/gsd/config.md +56 -0
- package/commands/gsd/debug.md +52 -0
- package/commands/gsd/discover-product.md +65 -0
- package/commands/gsd/discuss-phase.md +77 -0
- package/commands/gsd/docs-update.md +49 -0
- package/commands/gsd/eval-review.md +33 -0
- package/commands/gsd/execute-phase.md +66 -0
- package/commands/gsd/explore.md +27 -0
- package/commands/gsd/extract-learnings.md +23 -0
- package/commands/gsd/fast.md +31 -0
- package/commands/gsd/forensics.md +57 -0
- package/commands/gsd/graphify.md +204 -0
- package/commands/gsd/health.md +31 -0
- package/commands/gsd/help.md +28 -0
- package/commands/gsd/import.md +45 -0
- package/commands/gsd/inbox.md +39 -0
- package/commands/gsd/ingest-docs.md +42 -0
- package/commands/gsd/manager.md +45 -0
- package/commands/gsd/map-codebase.md +83 -0
- package/commands/gsd/milestone-summary.md +51 -0
- package/commands/gsd/model-domain.md +65 -0
- package/commands/gsd/mvp-phase.md +45 -0
- package/commands/gsd/new-milestone.md +45 -0
- package/commands/gsd/new-project.md +47 -0
- package/commands/gsd/ns-context.md +23 -0
- package/commands/gsd/ns-ideate.md +24 -0
- package/commands/gsd/ns-manage.md +29 -0
- package/commands/gsd/ns-project.md +22 -0
- package/commands/gsd/ns-review.md +26 -0
- package/commands/gsd/ns-workflow.md +28 -0
- package/commands/gsd/pause-work.md +43 -0
- package/commands/gsd/phase.md +56 -0
- package/commands/gsd/plan-phase.md +64 -0
- package/commands/gsd/plan-review-convergence.md +59 -0
- package/commands/gsd/pr-branch.md +26 -0
- package/commands/gsd/profile-user.md +46 -0
- package/commands/gsd/progress.md +48 -0
- package/commands/gsd/quick.md +174 -0
- package/commands/gsd/recommend-architecture.md +64 -0
- package/commands/gsd/resume-work.md +30 -0
- package/commands/gsd/review-backlog.md +63 -0
- package/commands/gsd/review.md +42 -0
- package/commands/gsd/secure-phase.md +36 -0
- package/commands/gsd/settings.md +29 -0
- package/commands/gsd/ship.md +24 -0
- package/commands/gsd/sketch.md +60 -0
- package/commands/gsd/spec-phase.md +63 -0
- package/commands/gsd/spike.md +57 -0
- package/commands/gsd/stats.md +20 -0
- package/commands/gsd/surface.md +155 -0
- package/commands/gsd/testing-strategy.md +65 -0
- package/commands/gsd/thread.md +24 -0
- package/commands/gsd/ui-phase.md +35 -0
- package/commands/gsd/ui-review.md +33 -0
- package/commands/gsd/ultraplan-phase.md +34 -0
- package/commands/gsd/undo.md +35 -0
- package/commands/gsd/update.md +49 -0
- package/commands/gsd/validate-phase.md +36 -0
- package/commands/gsd/verify-work.md +39 -0
- package/commands/gsd/workspace.md +52 -0
- package/commands/gsd/workstreams.md +70 -0
- package/gemini-extension.json +6 -0
- package/gsd-core/bin/check-latest-version.cjs +161 -0
- package/gsd-core/bin/gsd-tools.cjs +1928 -0
- package/gsd-core/bin/lib/active-workstream-store.cjs +291 -0
- package/gsd-core/bin/lib/adr-parser.cjs +399 -0
- package/gsd-core/bin/lib/agent-command-router.cjs +68 -0
- package/gsd-core/bin/lib/artifacts.cjs +51 -0
- package/gsd-core/bin/lib/audit.cjs +743 -0
- package/gsd-core/bin/lib/check-command-router.cjs +343 -0
- package/gsd-core/bin/lib/cjs-command-router-adapter.cjs +81 -0
- package/gsd-core/bin/lib/cli-exit.cjs +42 -0
- package/gsd-core/bin/lib/clock.cjs +95 -0
- package/gsd-core/bin/lib/clusters.cjs +132 -0
- package/gsd-core/bin/lib/code-review-flags.cjs +59 -0
- package/gsd-core/bin/lib/command-aliases.cjs +809 -0
- package/gsd-core/bin/lib/command-arg-projection.cjs +55 -0
- package/gsd-core/bin/lib/command-routing-hub.cjs +300 -0
- package/gsd-core/bin/lib/commands.cjs +1203 -0
- package/gsd-core/bin/lib/config-schema.cjs +29 -0
- package/gsd-core/bin/lib/config-types.cjs +19 -0
- package/gsd-core/bin/lib/config.cjs +738 -0
- package/gsd-core/bin/lib/configuration.cjs +239 -0
- package/gsd-core/bin/lib/context-utilization.cjs +48 -0
- package/gsd-core/bin/lib/core.cjs +2051 -0
- package/gsd-core/bin/lib/decisions.cjs +118 -0
- package/gsd-core/bin/lib/docs.cjs +252 -0
- package/gsd-core/bin/lib/drift.cjs +364 -0
- package/gsd-core/bin/lib/fallow-runner.cjs +115 -0
- package/gsd-core/bin/lib/frontmatter.cjs +442 -0
- package/gsd-core/bin/lib/gap-checker.cjs +257 -0
- package/gsd-core/bin/lib/graphify.cjs +496 -0
- package/gsd-core/bin/lib/gsd2-import.cjs +456 -0
- package/gsd-core/bin/lib/init-command-router.cjs +62 -0
- package/gsd-core/bin/lib/init.cjs +1815 -0
- package/gsd-core/bin/lib/install-profiles.cjs +584 -0
- package/gsd-core/bin/lib/installer-migration-authoring.cjs +122 -0
- package/gsd-core/bin/lib/installer-migration-report.cjs +350 -0
- package/gsd-core/bin/lib/installer-migrations/000-first-time-baseline.cjs +218 -0
- package/gsd-core/bin/lib/installer-migrations/001-legacy-orphan-files.cjs +48 -0
- package/gsd-core/bin/lib/installer-migrations/002-codex-legacy-hooks-json.cjs +94 -0
- package/gsd-core/bin/lib/installer-migrations/003-rename-get-shit-done-to-gsd-core.cjs +108 -0
- package/gsd-core/bin/lib/installer-migrations.cjs +823 -0
- package/gsd-core/bin/lib/intel.cjs +590 -0
- package/gsd-core/bin/lib/learnings.cjs +270 -0
- package/gsd-core/bin/lib/legacy-cleanup.cjs +253 -0
- package/gsd-core/bin/lib/milestone.cjs +373 -0
- package/gsd-core/bin/lib/model-catalog.cjs +154 -0
- package/gsd-core/bin/lib/model-profiles.cjs +24 -0
- package/gsd-core/bin/lib/observability/event.cjs +51 -0
- package/gsd-core/bin/lib/observability/logger.cjs +146 -0
- package/gsd-core/bin/lib/observability/redaction.cjs +48 -0
- package/gsd-core/bin/lib/package-identity.cjs +35 -0
- package/gsd-core/bin/lib/package-legitimacy.cjs +368 -0
- package/gsd-core/bin/lib/phase-command-router.cjs +189 -0
- package/gsd-core/bin/lib/phase-lifecycle.cjs +74 -0
- package/gsd-core/bin/lib/phase.cjs +1307 -0
- package/gsd-core/bin/lib/phases-command-router.cjs +43 -0
- package/gsd-core/bin/lib/plan-scan.cjs +91 -0
- package/gsd-core/bin/lib/planning-workspace.cjs +245 -0
- package/gsd-core/bin/lib/profile-output.cjs +1120 -0
- package/gsd-core/bin/lib/profile-pipeline.cjs +517 -0
- package/gsd-core/bin/lib/project-root.cjs +119 -0
- package/gsd-core/bin/lib/prompt-budget.cjs +305 -0
- package/gsd-core/bin/lib/research-provider.cjs +137 -0
- package/gsd-core/bin/lib/research-store.cjs +167 -0
- package/gsd-core/bin/lib/review-reviewer-selection.cjs +121 -0
- package/gsd-core/bin/lib/roadmap-command-router.cjs +166 -0
- package/gsd-core/bin/lib/roadmap-upgrade.cjs +476 -0
- package/gsd-core/bin/lib/roadmap.cjs +600 -0
- package/gsd-core/bin/lib/runtime-artifact-layout.cjs +312 -0
- package/gsd-core/bin/lib/runtime-config-adapter-registry.cjs +56 -0
- package/gsd-core/bin/lib/runtime-homes.cjs +190 -0
- package/gsd-core/bin/lib/runtime-name-policy.cjs +96 -0
- package/gsd-core/bin/lib/runtime-slash.cjs +119 -0
- package/gsd-core/bin/lib/schema-detect.cjs +159 -0
- package/gsd-core/bin/lib/secrets.cjs +34 -0
- package/gsd-core/bin/lib/security.cjs +480 -0
- package/gsd-core/bin/lib/semver-compare.cjs +42 -0
- package/gsd-core/bin/lib/shell-command-projection.cjs +533 -0
- package/gsd-core/bin/lib/state-command-router.cjs +160 -0
- package/gsd-core/bin/lib/state-document.cjs +259 -0
- package/gsd-core/bin/lib/state.cjs +2010 -0
- package/gsd-core/bin/lib/surface.cjs +449 -0
- package/gsd-core/bin/lib/task-command-router.cjs +85 -0
- package/gsd-core/bin/lib/template.cjs +237 -0
- package/gsd-core/bin/lib/uat.cjs +297 -0
- package/gsd-core/bin/lib/ui-safety-gate.cjs +98 -0
- package/gsd-core/bin/lib/update-context.cjs +218 -0
- package/gsd-core/bin/lib/validate-command-router.cjs +91 -0
- package/gsd-core/bin/lib/validate.cjs +112 -0
- package/gsd-core/bin/lib/verification-command-router.cjs +31 -0
- package/gsd-core/bin/lib/verification.cjs +193 -0
- package/gsd-core/bin/lib/verify-command-router.cjs +44 -0
- package/gsd-core/bin/lib/verify.cjs +1451 -0
- package/gsd-core/bin/lib/workstream-inventory-builder.cjs +81 -0
- package/gsd-core/bin/lib/workstream-inventory.cjs +147 -0
- package/gsd-core/bin/lib/workstream-name-policy.cjs +91 -0
- package/gsd-core/bin/lib/workstream.cjs +380 -0
- package/gsd-core/bin/lib/worktree-base-ref.cjs +325 -0
- package/gsd-core/bin/lib/worktree-safety.cjs +943 -0
- package/gsd-core/bin/shared/config-defaults.manifest.json +98 -0
- package/gsd-core/bin/shared/config-schema.manifest.json +192 -0
- package/gsd-core/bin/shared/model-catalog.json +149 -0
- package/gsd-core/bin/shared/runtime-aliases.manifest.json +75 -0
- package/gsd-core/bin/verify-reapply-patches.cjs +349 -0
- package/gsd-core/contexts/dev.md +21 -0
- package/gsd-core/contexts/research.md +22 -0
- package/gsd-core/contexts/review.md +23 -0
- package/gsd-core/references/agent-contracts.md +79 -0
- package/gsd-core/references/ai-evals.md +156 -0
- package/gsd-core/references/ai-frameworks.md +186 -0
- package/gsd-core/references/architecture-decision.md +74 -0
- package/gsd-core/references/artifact-types.md +131 -0
- package/gsd-core/references/auth-in-tests.md +91 -0
- package/gsd-core/references/autonomous-smart-discuss.md +277 -0
- package/gsd-core/references/checkpoints.md +814 -0
- package/gsd-core/references/common-bug-patterns.md +114 -0
- package/gsd-core/references/context-budget.md +85 -0
- package/gsd-core/references/continuation-format.md +253 -0
- package/gsd-core/references/db-test-isolation.md +54 -0
- package/gsd-core/references/debugger-philosophy.md +76 -0
- package/gsd-core/references/decimal-phase-calculation.md +64 -0
- package/gsd-core/references/doc-conflict-engine.md +91 -0
- package/gsd-core/references/domain-modeling.md +80 -0
- package/gsd-core/references/domain-probes.md +125 -0
- package/gsd-core/references/e2e-tiering.md +35 -0
- package/gsd-core/references/execute-mvp-tdd.md +81 -0
- package/gsd-core/references/executor-examples.md +110 -0
- package/gsd-core/references/few-shot-examples/plan-checker.md +73 -0
- package/gsd-core/references/few-shot-examples/verifier.md +109 -0
- package/gsd-core/references/flaky-test-checklist.md +22 -0
- package/gsd-core/references/gate-prompts.md +100 -0
- package/gsd-core/references/gates.md +70 -0
- package/gsd-core/references/git-integration.md +298 -0
- package/gsd-core/references/git-planning-commit.md +40 -0
- package/gsd-core/references/ios-scaffold.md +123 -0
- package/gsd-core/references/mandatory-initial-read.md +2 -0
- package/gsd-core/references/model-profile-resolution.md +38 -0
- package/gsd-core/references/model-profiles.md +245 -0
- package/gsd-core/references/mvp-concepts.md +49 -0
- package/gsd-core/references/phase-argument-parsing.md +61 -0
- package/gsd-core/references/planner-antipatterns.md +89 -0
- package/gsd-core/references/planner-chunked.md +49 -0
- package/gsd-core/references/planner-gap-closure.md +62 -0
- package/gsd-core/references/planner-graphify-auto-update.md +67 -0
- package/gsd-core/references/planner-human-verify-mode.md +57 -0
- package/gsd-core/references/planner-interface-context.md +62 -0
- package/gsd-core/references/planner-load-graph-context.md +36 -0
- package/gsd-core/references/planner-mvp-mode.md +53 -0
- package/gsd-core/references/planner-reviews.md +39 -0
- package/gsd-core/references/planner-revision.md +87 -0
- package/gsd-core/references/planner-source-audit.md +73 -0
- package/gsd-core/references/planning-config.md +473 -0
- package/gsd-core/references/product-discovery.md +49 -0
- package/gsd-core/references/project-skills-discovery.md +19 -0
- package/gsd-core/references/questioning.md +162 -0
- package/gsd-core/references/realistic-test-data.md +44 -0
- package/gsd-core/references/research-documentation-lookup.md +29 -0
- package/gsd-core/references/research-philosophy.md +29 -0
- package/gsd-core/references/research-verification-protocol.md +27 -0
- package/gsd-core/references/revision-loop.md +97 -0
- package/gsd-core/references/scout-codebase.md +51 -0
- package/gsd-core/references/skeleton-template.md +48 -0
- package/gsd-core/references/sketch-interactivity.md +41 -0
- package/gsd-core/references/sketch-theme-system.md +94 -0
- package/gsd-core/references/sketch-tooling.md +45 -0
- package/gsd-core/references/sketch-variant-patterns.md +81 -0
- package/gsd-core/references/spidr-splitting.md +69 -0
- package/gsd-core/references/tdd.md +330 -0
- package/gsd-core/references/test-containers.md +55 -0
- package/gsd-core/references/test-strategy.md +75 -0
- package/gsd-core/references/thinking-models-debug.md +44 -0
- package/gsd-core/references/thinking-models-execution.md +50 -0
- package/gsd-core/references/thinking-models-planning.md +62 -0
- package/gsd-core/references/thinking-models-research.md +50 -0
- package/gsd-core/references/thinking-models-verification.md +55 -0
- package/gsd-core/references/thinking-partner.md +96 -0
- package/gsd-core/references/ui-brand.md +162 -0
- package/gsd-core/references/universal-anti-patterns.md +63 -0
- package/gsd-core/references/user-profiling.md +681 -0
- package/gsd-core/references/user-story-template.md +58 -0
- package/gsd-core/references/verification-overrides.md +227 -0
- package/gsd-core/references/verification-patterns.md +612 -0
- package/gsd-core/references/verify-mvp-mode.md +85 -0
- package/gsd-core/references/workstream-flag.md +111 -0
- package/gsd-core/references/worktree-branch-check.md +38 -0
- package/gsd-core/references/worktree-path-safety.md +67 -0
- package/gsd-core/templates/AI-SPEC.md +246 -0
- package/gsd-core/templates/DEBUG.md +169 -0
- package/gsd-core/templates/README.md +77 -0
- package/gsd-core/templates/SECURITY.md +61 -0
- package/gsd-core/templates/UAT.md +265 -0
- package/gsd-core/templates/UI-SPEC.md +100 -0
- package/gsd-core/templates/VALIDATION.md +76 -0
- package/gsd-core/templates/adr.md +58 -0
- package/gsd-core/templates/claude-md.md +145 -0
- package/gsd-core/templates/codebase/architecture.md +255 -0
- package/gsd-core/templates/codebase/concerns.md +310 -0
- package/gsd-core/templates/codebase/conventions.md +307 -0
- package/gsd-core/templates/codebase/integrations.md +280 -0
- package/gsd-core/templates/codebase/stack.md +186 -0
- package/gsd-core/templates/codebase/structure.md +285 -0
- package/gsd-core/templates/codebase/testing.md +480 -0
- package/gsd-core/templates/config.json +62 -0
- package/gsd-core/templates/context.md +352 -0
- package/gsd-core/templates/continue-here.md +78 -0
- package/gsd-core/templates/copilot-instructions.md +7 -0
- package/gsd-core/templates/debug-subagent-prompt.md +91 -0
- package/gsd-core/templates/dev-preferences.md +21 -0
- package/gsd-core/templates/discovery.md +146 -0
- package/gsd-core/templates/discussion-log.md +63 -0
- package/gsd-core/templates/domain-model.md +54 -0
- package/gsd-core/templates/milestone-archive.md +123 -0
- package/gsd-core/templates/milestone.md +115 -0
- package/gsd-core/templates/phase-prompt.md +610 -0
- package/gsd-core/templates/planner-subagent-prompt.md +117 -0
- package/gsd-core/templates/product-brief.md +55 -0
- package/gsd-core/templates/project.md +186 -0
- package/gsd-core/templates/requirements.md +231 -0
- package/gsd-core/templates/research-project/ARCHITECTURE.md +204 -0
- package/gsd-core/templates/research-project/FEATURES.md +147 -0
- package/gsd-core/templates/research-project/PITFALLS.md +200 -0
- package/gsd-core/templates/research-project/STACK.md +120 -0
- package/gsd-core/templates/research-project/SUMMARY.md +170 -0
- package/gsd-core/templates/research.md +592 -0
- package/gsd-core/templates/retrospective.md +54 -0
- package/gsd-core/templates/roadmap.md +202 -0
- package/gsd-core/templates/spec.md +307 -0
- package/gsd-core/templates/state.md +195 -0
- package/gsd-core/templates/summary-complex.md +59 -0
- package/gsd-core/templates/summary-minimal.md +41 -0
- package/gsd-core/templates/summary-standard.md +48 -0
- package/gsd-core/templates/summary.md +248 -0
- package/gsd-core/templates/test-strategy.md +50 -0
- package/gsd-core/templates/user-profile.md +146 -0
- package/gsd-core/templates/user-setup.md +311 -0
- package/gsd-core/templates/verification-report.md +322 -0
- package/gsd-core/workflows/_runtime-launcher.snippet.sh +1 -0
- package/gsd-core/workflows/add-backlog.md +91 -0
- package/gsd-core/workflows/add-phase.md +113 -0
- package/gsd-core/workflows/add-tests.md +355 -0
- package/gsd-core/workflows/add-todo.md +161 -0
- package/gsd-core/workflows/ai-integration-phase.md +295 -0
- package/gsd-core/workflows/analyze-dependencies.md +96 -0
- package/gsd-core/workflows/audit-fix.md +178 -0
- package/gsd-core/workflows/audit-milestone.md +360 -0
- package/gsd-core/workflows/audit-uat.md +110 -0
- package/gsd-core/workflows/autonomous.md +797 -0
- package/gsd-core/workflows/check-todos.md +180 -0
- package/gsd-core/workflows/cleanup.md +195 -0
- package/gsd-core/workflows/code-review-fix.md +502 -0
- package/gsd-core/workflows/code-review.md +658 -0
- package/gsd-core/workflows/complete-milestone.md +855 -0
- package/gsd-core/workflows/debug.md +237 -0
- package/gsd-core/workflows/diagnose-issues.md +245 -0
- package/gsd-core/workflows/discover-product.md +112 -0
- package/gsd-core/workflows/discovery-phase.md +291 -0
- package/gsd-core/workflows/discuss-phase/modes/advisor.md +176 -0
- package/gsd-core/workflows/discuss-phase/modes/all.md +28 -0
- package/gsd-core/workflows/discuss-phase/modes/analyze.md +44 -0
- package/gsd-core/workflows/discuss-phase/modes/auto.md +57 -0
- package/gsd-core/workflows/discuss-phase/modes/batch.md +52 -0
- package/gsd-core/workflows/discuss-phase/modes/chain.md +98 -0
- package/gsd-core/workflows/discuss-phase/modes/default.md +141 -0
- package/gsd-core/workflows/discuss-phase/modes/power.md +44 -0
- package/gsd-core/workflows/discuss-phase/modes/text.md +55 -0
- package/gsd-core/workflows/discuss-phase/templates/checkpoint.json +18 -0
- package/gsd-core/workflows/discuss-phase/templates/context.md +136 -0
- package/gsd-core/workflows/discuss-phase/templates/discussion-log.md +50 -0
- package/gsd-core/workflows/discuss-phase-assumptions.md +675 -0
- package/gsd-core/workflows/discuss-phase-power.md +291 -0
- package/gsd-core/workflows/discuss-phase.md +499 -0
- package/gsd-core/workflows/do.md +111 -0
- package/gsd-core/workflows/docs-update.md +1176 -0
- package/gsd-core/workflows/edit-phase.md +295 -0
- package/gsd-core/workflows/eval-review.md +156 -0
- package/gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md +95 -0
- package/gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +94 -0
- package/gsd-core/workflows/execute-phase/steps/post-merge-gate.md +117 -0
- package/gsd-core/workflows/execute-phase.md +1752 -0
- package/gsd-core/workflows/execute-plan.md +526 -0
- package/gsd-core/workflows/explore.md +146 -0
- package/gsd-core/workflows/extract-learnings.md +243 -0
- package/gsd-core/workflows/fast.md +124 -0
- package/gsd-core/workflows/forensics.md +279 -0
- package/gsd-core/workflows/graduation.md +196 -0
- package/gsd-core/workflows/health.md +224 -0
- package/gsd-core/workflows/help/modes/brief.md +22 -0
- package/gsd-core/workflows/help/modes/default.md +50 -0
- package/gsd-core/workflows/help/modes/full.md +789 -0
- package/gsd-core/workflows/help/modes/topic.md +74 -0
- package/gsd-core/workflows/help.md +24 -0
- package/gsd-core/workflows/import.md +256 -0
- package/gsd-core/workflows/inbox.md +387 -0
- package/gsd-core/workflows/ingest-docs.md +340 -0
- package/gsd-core/workflows/insert-phase.md +152 -0
- package/gsd-core/workflows/list-phase-assumptions.md +178 -0
- package/gsd-core/workflows/list-workspaces.md +57 -0
- package/gsd-core/workflows/manager.md +393 -0
- package/gsd-core/workflows/map-codebase.md +446 -0
- package/gsd-core/workflows/milestone-summary.md +224 -0
- package/gsd-core/workflows/model-domain.md +162 -0
- package/gsd-core/workflows/mvp-phase.md +222 -0
- package/gsd-core/workflows/new-milestone.md +635 -0
- package/gsd-core/workflows/new-project.md +1555 -0
- package/gsd-core/workflows/new-workspace.md +240 -0
- package/gsd-core/workflows/next.md +299 -0
- package/gsd-core/workflows/node-repair.md +92 -0
- package/gsd-core/workflows/note.md +158 -0
- package/gsd-core/workflows/pause-work.md +244 -0
- package/gsd-core/workflows/plan-milestone-gaps.md +281 -0
- package/gsd-core/workflows/plan-phase.md +1814 -0
- package/gsd-core/workflows/plan-review-convergence.md +346 -0
- package/gsd-core/workflows/plant-seed.md +230 -0
- package/gsd-core/workflows/pr-branch.md +157 -0
- package/gsd-core/workflows/profile-user.md +453 -0
- package/gsd-core/workflows/progress.md +699 -0
- package/gsd-core/workflows/quick.md +1017 -0
- package/gsd-core/workflows/reapply-patches.md +426 -0
- package/gsd-core/workflows/recommend-architecture.md +135 -0
- package/gsd-core/workflows/remove-phase.md +156 -0
- package/gsd-core/workflows/remove-workspace.md +108 -0
- package/gsd-core/workflows/resume-project.md +332 -0
- package/gsd-core/workflows/review.md +748 -0
- package/gsd-core/workflows/scan.md +107 -0
- package/gsd-core/workflows/secure-phase.md +182 -0
- package/gsd-core/workflows/session-report.md +146 -0
- package/gsd-core/workflows/settings-advanced.md +810 -0
- package/gsd-core/workflows/settings-integrations.md +312 -0
- package/gsd-core/workflows/settings.md +566 -0
- package/gsd-core/workflows/ship.md +405 -0
- package/gsd-core/workflows/sketch-wrap-up.md +286 -0
- package/gsd-core/workflows/sketch.md +361 -0
- package/gsd-core/workflows/spec-phase.md +263 -0
- package/gsd-core/workflows/spike-wrap-up.md +307 -0
- package/gsd-core/workflows/spike.md +453 -0
- package/gsd-core/workflows/stats.md +80 -0
- package/gsd-core/workflows/sync-skills.md +182 -0
- package/gsd-core/workflows/testing-strategy.md +122 -0
- package/gsd-core/workflows/thread.md +222 -0
- package/gsd-core/workflows/transition.md +694 -0
- package/gsd-core/workflows/ui-phase.md +328 -0
- package/gsd-core/workflows/ui-review.md +193 -0
- package/gsd-core/workflows/ultraplan-phase.md +199 -0
- package/gsd-core/workflows/undo.md +314 -0
- package/gsd-core/workflows/update.md +496 -0
- package/gsd-core/workflows/validate-phase.md +181 -0
- package/gsd-core/workflows/verify-phase.md +544 -0
- package/gsd-core/workflows/verify-work.md +781 -0
- package/hooks/dist/gsd-check-update-worker.js +108 -0
- package/hooks/dist/gsd-check-update.js +66 -0
- package/hooks/dist/gsd-config-reload.js +133 -0
- package/hooks/dist/gsd-context-monitor.js +195 -0
- package/hooks/dist/gsd-cursor-post-tool.js +75 -0
- package/hooks/dist/gsd-cursor-session-start.js +52 -0
- package/hooks/dist/gsd-graphify-update.sh +158 -0
- package/hooks/dist/gsd-phase-boundary.sh +47 -0
- package/hooks/dist/gsd-prompt-guard.js +97 -0
- package/hooks/dist/gsd-read-guard.js +101 -0
- package/hooks/dist/gsd-read-injection-scanner.js +203 -0
- package/hooks/dist/gsd-session-state.sh +59 -0
- package/hooks/dist/gsd-statusline.js +566 -0
- package/hooks/dist/gsd-update-banner.js +138 -0
- package/hooks/dist/gsd-validate-commit.sh +57 -0
- package/hooks/dist/gsd-workflow-guard.js +167 -0
- package/hooks/dist/gsd-worktree-path-guard.js +169 -0
- package/hooks/dist/lib/git-cmd.js +150 -0
- package/hooks/dist/lib/gsd-graphify-rebuild.sh +65 -0
- package/hooks/dist/managed-hooks-registry.cjs +38 -0
- package/hooks/gsd-check-update-worker.js +108 -0
- package/hooks/gsd-check-update.js +66 -0
- package/hooks/gsd-config-reload.js +133 -0
- package/hooks/gsd-context-monitor.js +195 -0
- package/hooks/gsd-cursor-post-tool.js +75 -0
- package/hooks/gsd-cursor-session-start.js +52 -0
- package/hooks/gsd-graphify-update.sh +158 -0
- package/hooks/gsd-phase-boundary.sh +47 -0
- package/hooks/gsd-prompt-guard.js +97 -0
- package/hooks/gsd-read-guard.js +101 -0
- package/hooks/gsd-read-injection-scanner.js +203 -0
- package/hooks/gsd-session-state.sh +59 -0
- package/hooks/gsd-statusline.js +566 -0
- package/hooks/gsd-update-banner.js +138 -0
- package/hooks/gsd-validate-commit.sh +57 -0
- package/hooks/gsd-workflow-guard.js +167 -0
- package/hooks/gsd-worktree-path-guard.js +169 -0
- package/hooks/hooks.json +69 -0
- package/hooks/lib/git-cmd.js +150 -0
- package/hooks/lib/gsd-graphify-rebuild.sh +65 -0
- package/hooks/managed-hooks-registry.cjs +38 -0
- package/package.json +115 -0
- package/scripts/affected-tests-lib.cjs +542 -0
- package/scripts/audit-workflow-script-paths.cjs +73 -0
- package/scripts/base64-scan.sh +351 -0
- package/scripts/build-hooks.js +247 -0
- package/scripts/changeset/README.md +129 -0
- package/scripts/changeset/cli.cjs +590 -0
- package/scripts/changeset/github-release-notes.cjs +199 -0
- package/scripts/changeset/lint.cjs +111 -0
- package/scripts/changeset/new.cjs +137 -0
- package/scripts/changeset/parse.cjs +114 -0
- package/scripts/changeset/render.cjs +34 -0
- package/scripts/changeset/serialize.cjs +130 -0
- package/scripts/check-alias-drift.cjs +114 -0
- package/scripts/check-env.cjs +312 -0
- package/scripts/check-npm-integrity.cjs +215 -0
- package/scripts/ci-guard-runner.cjs +22 -0
- package/scripts/ci-prepare-test-scope.cjs +51 -0
- package/scripts/ci-rebase-check.cjs +86 -0
- package/scripts/ci-test-scope.cjs +431 -0
- package/scripts/command-contract-helpers.cjs +64 -0
- package/scripts/diff-touches-shipped-paths.cjs +155 -0
- package/scripts/fix-slash-commands.cjs +147 -0
- package/scripts/gen-inventory-manifest.cjs +115 -0
- package/scripts/gen-research-agents.cjs +276 -0
- package/scripts/generate-package-identity.cjs +125 -0
- package/scripts/issue-dedupe.cjs +278 -0
- package/scripts/lib/allowlist-ratchet.cjs +136 -0
- package/scripts/lib/cli-exit.cjs +56 -0
- package/scripts/lint-command-contract.cjs +114 -0
- package/scripts/lint-descriptions.cjs +87 -0
- package/scripts/lint-docs-required.cjs +222 -0
- package/scripts/lint-legacy-dir-name.cjs +160 -0
- package/scripts/lint-package-identity-drift.cjs +141 -0
- package/scripts/lint-pr-check-project-dir.cjs +99 -0
- package/scripts/lint-shell-command-projection-drift.cjs +62 -0
- package/scripts/lint-skill-deps.cjs +185 -0
- package/scripts/lint-test-file-count.allowlist.json +135 -0
- package/scripts/lint-test-file-count.cjs +246 -0
- package/scripts/mutation-matrix.cjs +222 -0
- package/scripts/pr-template-policy.cjs +268 -0
- package/scripts/prompt-injection-scan.sh +207 -0
- package/scripts/release-notes/discord-release-summary.cjs +373 -0
- package/scripts/release-notes/format-github-release-notes.cjs +261 -0
- package/scripts/release-tarball-smoke.cjs +629 -0
- package/scripts/research-profiles.cjs +149 -0
- package/scripts/run-affected-tests.cjs +7 -0
- package/scripts/run-cross-platform-tests.cjs +67 -0
- package/scripts/run-tests.cjs +315 -0
- package/scripts/secret-scan-lint.sh +231 -0
- package/scripts/secret-scan.sh +358 -0
- package/scripts/setup-branch-protection.sh +236 -0
- package/scripts/strip-prose-atrefs.cjs +106 -0
- package/scripts/sync-manifest-versions.cjs +119 -0
- package/scripts/sync-rulesets.sh +34 -0
- package/scripts/sync-runtime-launcher.cjs +399 -0
- package/scripts/test-failure-reasons.cjs +34 -0
- package/scripts/verify-npm-publish.cjs +240 -0
- package/scripts/workflow-policy.cjs +450 -0
|
@@ -0,0 +1,1451 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Verify — Verification suite, consistency, and health validation
|
|
4
|
+
*
|
|
5
|
+
* ADR-457 build-at-publish: the hand-written bin/lib/verify.cjs collapsed to
|
|
6
|
+
* a TypeScript source of truth, compiled by tsc to a gitignored .cjs at the
|
|
7
|
+
* same require() path. Behaviour preserved byte-for-behaviour; only types are added.
|
|
8
|
+
*/
|
|
9
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
10
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
11
|
+
};
|
|
12
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
13
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
14
|
+
const node_os_1 = __importDefault(require("node:os"));
|
|
15
|
+
const validate_cjs_1 = require("./validate.cjs");
|
|
16
|
+
const validate_cjs_2 = require("./validate.cjs");
|
|
17
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports -- core.cjs is an export= CommonJS module
|
|
18
|
+
const core = require("./core.cjs");
|
|
19
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports -- planning-workspace.cjs is an export= CommonJS module
|
|
20
|
+
const planningWorkspace = require("./planning-workspace.cjs");
|
|
21
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports -- frontmatter.cjs is an export= CommonJS module
|
|
22
|
+
const frontmatterMod = require("./frontmatter.cjs");
|
|
23
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports -- state.cjs is an export= CommonJS module
|
|
24
|
+
const stateMod = require("./state.cjs");
|
|
25
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports -- model-profiles.cjs is an export= CommonJS module
|
|
26
|
+
const modelProfilesMod = require("./model-profiles.cjs");
|
|
27
|
+
const shell_command_projection_cjs_1 = require("./shell-command-projection.cjs");
|
|
28
|
+
const package_identity_cjs_1 = require("./package-identity.cjs");
|
|
29
|
+
const runtime_slash_cjs_1 = require("./runtime-slash.cjs");
|
|
30
|
+
const schema_detect_cjs_1 = require("./schema-detect.cjs");
|
|
31
|
+
const artifacts_cjs_1 = require("./artifacts.cjs");
|
|
32
|
+
const { loadConfig, normalizePhaseName, phaseTokenMatches, escapeRegex, findPhaseInternal, getMilestoneInfo, stripShippedMilestones, extractCurrentMilestone, output, error, checkAgentsInstalled, CONFIG_DEFAULTS, inspectWorktreeHealth, } = core;
|
|
33
|
+
const { planningDir } = planningWorkspace;
|
|
34
|
+
const { extractFrontmatter, parseMustHavesBlock } = frontmatterMod;
|
|
35
|
+
const { writeStateMd } = stateMod;
|
|
36
|
+
const { MODEL_PROFILES } = modelProfilesMod;
|
|
37
|
+
// Unused but imported for structural parity
|
|
38
|
+
void stripShippedMilestones;
|
|
39
|
+
void schema_detect_cjs_1.detectSchemaFiles;
|
|
40
|
+
function cmdVerifySummary(cwd, summaryPath, checkFileCount, raw) {
|
|
41
|
+
if (!summaryPath) {
|
|
42
|
+
error('summary-path required');
|
|
43
|
+
}
|
|
44
|
+
const fullPath = node_path_1.default.join(cwd, summaryPath);
|
|
45
|
+
const checkCount = checkFileCount || 2;
|
|
46
|
+
if (!node_fs_1.default.existsSync(fullPath)) {
|
|
47
|
+
const result = {
|
|
48
|
+
passed: false,
|
|
49
|
+
checks: {
|
|
50
|
+
summary_exists: false,
|
|
51
|
+
files_created: { checked: 0, found: 0, missing: [] },
|
|
52
|
+
commits_exist: false,
|
|
53
|
+
self_check: 'not_found',
|
|
54
|
+
},
|
|
55
|
+
errors: ['SUMMARY.md not found'],
|
|
56
|
+
};
|
|
57
|
+
output(result, raw, 'failed');
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
const content = node_fs_1.default.readFileSync(fullPath, 'utf-8');
|
|
61
|
+
const errors = [];
|
|
62
|
+
const mentionedFiles = new Set();
|
|
63
|
+
const patterns = [
|
|
64
|
+
/`([^`]+\.[a-zA-Z]+)`/g,
|
|
65
|
+
/(?:Created|Modified|Added|Updated|Edited):\s*`?([^\s`]+\.[a-zA-Z]+)`?/gi,
|
|
66
|
+
];
|
|
67
|
+
for (const pattern of patterns) {
|
|
68
|
+
let m;
|
|
69
|
+
while ((m = pattern.exec(content)) !== null) {
|
|
70
|
+
const filePath = m[1];
|
|
71
|
+
if (filePath && !filePath.startsWith('http') && filePath.includes('/')) {
|
|
72
|
+
mentionedFiles.add(filePath);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
const filesToCheck = Array.from(mentionedFiles).slice(0, checkCount);
|
|
77
|
+
const missing = [];
|
|
78
|
+
for (const file of filesToCheck) {
|
|
79
|
+
if (!node_fs_1.default.existsSync(node_path_1.default.join(cwd, file))) {
|
|
80
|
+
missing.push(file);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
const commitHashPattern = /\b[0-9a-f]{7,40}\b/g;
|
|
84
|
+
const hashes = content.match(commitHashPattern) || [];
|
|
85
|
+
let commitsExist = false;
|
|
86
|
+
if (hashes.length > 0) {
|
|
87
|
+
for (const hash of hashes.slice(0, 3)) {
|
|
88
|
+
const result = (0, shell_command_projection_cjs_1.execGit)(['cat-file', '-t', hash], { cwd });
|
|
89
|
+
if (result.exitCode === 0 && result.stdout.trim() === 'commit') {
|
|
90
|
+
commitsExist = true;
|
|
91
|
+
break;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
let selfCheck = 'not_found';
|
|
96
|
+
const selfCheckPattern = /##\s*(?:Self[- ]?Check|Verification|Quality Check)/i;
|
|
97
|
+
if (selfCheckPattern.test(content)) {
|
|
98
|
+
const passPattern = /(?:all\s+)?(?:pass|✓|✅|complete|succeeded)/i;
|
|
99
|
+
const failPattern = /(?:fail|✗|❌|incomplete|blocked)/i;
|
|
100
|
+
const checkSection = content.slice(content.search(selfCheckPattern));
|
|
101
|
+
if (failPattern.test(checkSection)) {
|
|
102
|
+
selfCheck = 'failed';
|
|
103
|
+
}
|
|
104
|
+
else if (passPattern.test(checkSection)) {
|
|
105
|
+
selfCheck = 'passed';
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
if (missing.length > 0)
|
|
109
|
+
errors.push('Missing files: ' + missing.join(', '));
|
|
110
|
+
if (!commitsExist && hashes.length > 0)
|
|
111
|
+
errors.push('Referenced commit hashes not found in git history');
|
|
112
|
+
if (selfCheck === 'failed')
|
|
113
|
+
errors.push('Self-check section indicates failure');
|
|
114
|
+
const checks = {
|
|
115
|
+
summary_exists: true,
|
|
116
|
+
files_created: { checked: filesToCheck.length, found: filesToCheck.length - missing.length, missing },
|
|
117
|
+
commits_exist: commitsExist,
|
|
118
|
+
self_check: selfCheck,
|
|
119
|
+
};
|
|
120
|
+
const passed = missing.length === 0 && selfCheck !== 'failed';
|
|
121
|
+
const result = { passed, checks, errors };
|
|
122
|
+
output(result, raw, passed ? 'passed' : 'failed');
|
|
123
|
+
}
|
|
124
|
+
function cmdVerifyPlanStructure(cwd, filePath, raw) {
|
|
125
|
+
if (!filePath) {
|
|
126
|
+
error('file path required');
|
|
127
|
+
}
|
|
128
|
+
const fullPath = node_path_1.default.isAbsolute(filePath) ? filePath : node_path_1.default.join(cwd, filePath);
|
|
129
|
+
const content = (0, shell_command_projection_cjs_1.platformReadSync)(fullPath);
|
|
130
|
+
if (!content) {
|
|
131
|
+
output({ error: 'File not found', path: filePath }, raw);
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
const fm = extractFrontmatter(content);
|
|
135
|
+
const errors = [];
|
|
136
|
+
const warnings = [];
|
|
137
|
+
const required = ['phase', 'plan', 'type', 'wave', 'depends_on', 'files_modified', 'autonomous', 'must_haves'];
|
|
138
|
+
for (const field of required) {
|
|
139
|
+
if (fm[field] === undefined)
|
|
140
|
+
errors.push(`Missing required frontmatter field: ${field}`);
|
|
141
|
+
}
|
|
142
|
+
const taskPattern = /<task[^>]*>([\s\S]*?)<\/task>/g;
|
|
143
|
+
const tasks = [];
|
|
144
|
+
let taskMatch;
|
|
145
|
+
while ((taskMatch = taskPattern.exec(content)) !== null) {
|
|
146
|
+
const taskContent = taskMatch[1];
|
|
147
|
+
const nameMatch = taskContent.match(/<name>([\s\S]*?)<\/name>/);
|
|
148
|
+
const taskName = nameMatch ? nameMatch[1].trim() : 'unnamed';
|
|
149
|
+
const hasFiles = /<files>/.test(taskContent);
|
|
150
|
+
const hasAction = /<action>/.test(taskContent);
|
|
151
|
+
const hasVerify = /<verify>/.test(taskContent);
|
|
152
|
+
const hasDone = /<done>/.test(taskContent);
|
|
153
|
+
if (!nameMatch)
|
|
154
|
+
errors.push('Task missing <name> element');
|
|
155
|
+
if (!hasAction)
|
|
156
|
+
errors.push(`Task '${taskName}' missing <action>`);
|
|
157
|
+
if (!hasVerify)
|
|
158
|
+
warnings.push(`Task '${taskName}' missing <verify>`);
|
|
159
|
+
if (!hasDone)
|
|
160
|
+
warnings.push(`Task '${taskName}' missing <done>`);
|
|
161
|
+
if (!hasFiles)
|
|
162
|
+
warnings.push(`Task '${taskName}' missing <files>`);
|
|
163
|
+
tasks.push({ name: taskName, hasFiles, hasAction, hasVerify, hasDone });
|
|
164
|
+
}
|
|
165
|
+
if (tasks.length === 0)
|
|
166
|
+
warnings.push('No <task> elements found');
|
|
167
|
+
if (fm['wave'] &&
|
|
168
|
+
parseInt(fm['wave']) > 1 &&
|
|
169
|
+
(!fm['depends_on'] ||
|
|
170
|
+
(Array.isArray(fm['depends_on']) && fm['depends_on'].length === 0))) {
|
|
171
|
+
warnings.push('Wave > 1 but depends_on is empty');
|
|
172
|
+
}
|
|
173
|
+
const hasCheckpoints = /<task\s+type=["']?checkpoint/.test(content);
|
|
174
|
+
// eslint-disable-next-line @typescript-eslint/no-base-to-string -- FrontmatterValue comparison
|
|
175
|
+
if (hasCheckpoints && fm['autonomous'] !== 'false' && String(fm['autonomous']) !== 'false') {
|
|
176
|
+
errors.push('Has checkpoint tasks but autonomous is not false');
|
|
177
|
+
}
|
|
178
|
+
output({
|
|
179
|
+
valid: errors.length === 0,
|
|
180
|
+
errors,
|
|
181
|
+
warnings,
|
|
182
|
+
task_count: tasks.length,
|
|
183
|
+
tasks,
|
|
184
|
+
frontmatter_fields: Object.keys(fm),
|
|
185
|
+
}, raw, errors.length === 0 ? 'valid' : 'invalid');
|
|
186
|
+
}
|
|
187
|
+
function cmdVerifyPhaseCompleteness(cwd, phase, raw) {
|
|
188
|
+
if (!phase) {
|
|
189
|
+
error('phase required');
|
|
190
|
+
}
|
|
191
|
+
const phaseInfoRaw = findPhaseInternal(cwd, phase);
|
|
192
|
+
if (!phaseInfoRaw || !phaseInfoRaw['found']) {
|
|
193
|
+
output({ error: 'Phase not found', phase }, raw);
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
const phaseInfo = phaseInfoRaw;
|
|
197
|
+
const errors = [];
|
|
198
|
+
const warnings = [];
|
|
199
|
+
const phaseDir = node_path_1.default.join(cwd, phaseInfo['directory']);
|
|
200
|
+
let files;
|
|
201
|
+
try {
|
|
202
|
+
files = node_fs_1.default.readdirSync(phaseDir);
|
|
203
|
+
}
|
|
204
|
+
catch {
|
|
205
|
+
output({ error: 'Cannot read phase directory' }, raw);
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
const plans = files.filter((f) => f.match(/-PLAN\.md$/i));
|
|
209
|
+
const summaries = files.filter((f) => f.match(/-SUMMARY\.md$/i));
|
|
210
|
+
const planIds = new Set(plans.map((p) => p.replace(/-PLAN\.md$/i, '')));
|
|
211
|
+
const summaryIds = new Set(summaries.map((s) => s.replace(/-SUMMARY\.md$/i, '')));
|
|
212
|
+
const incompletePlans = [...planIds].filter((id) => !summaryIds.has(id));
|
|
213
|
+
if (incompletePlans.length > 0) {
|
|
214
|
+
errors.push(`Plans without summaries: ${incompletePlans.join(', ')}`);
|
|
215
|
+
}
|
|
216
|
+
const orphanSummaries = [...summaryIds].filter((id) => !planIds.has(id));
|
|
217
|
+
if (orphanSummaries.length > 0) {
|
|
218
|
+
warnings.push(`Summaries without plans: ${orphanSummaries.join(', ')}`);
|
|
219
|
+
}
|
|
220
|
+
output({
|
|
221
|
+
complete: errors.length === 0,
|
|
222
|
+
phase: phaseInfo['phase_number'],
|
|
223
|
+
plan_count: plans.length,
|
|
224
|
+
summary_count: summaries.length,
|
|
225
|
+
incomplete_plans: incompletePlans,
|
|
226
|
+
orphan_summaries: orphanSummaries,
|
|
227
|
+
errors,
|
|
228
|
+
warnings,
|
|
229
|
+
}, raw, errors.length === 0 ? 'complete' : 'incomplete');
|
|
230
|
+
}
|
|
231
|
+
function cmdVerifyReferences(cwd, filePath, raw) {
|
|
232
|
+
if (!filePath) {
|
|
233
|
+
error('file path required');
|
|
234
|
+
}
|
|
235
|
+
const fullPath = node_path_1.default.isAbsolute(filePath) ? filePath : node_path_1.default.join(cwd, filePath);
|
|
236
|
+
const content = (0, shell_command_projection_cjs_1.platformReadSync)(fullPath);
|
|
237
|
+
if (!content) {
|
|
238
|
+
output({ error: 'File not found', path: filePath }, raw);
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
const found = [];
|
|
242
|
+
const missing = [];
|
|
243
|
+
const atRefs = content.match(/@([^\s\n,)]+\/[^\s\n,)]+)/g) || [];
|
|
244
|
+
for (const ref of atRefs) {
|
|
245
|
+
const cleanRef = ref.slice(1);
|
|
246
|
+
const resolved = cleanRef.startsWith('~/')
|
|
247
|
+
? node_path_1.default.join(process.env['HOME'] || '', cleanRef.slice(2))
|
|
248
|
+
: node_path_1.default.join(cwd, cleanRef);
|
|
249
|
+
if (node_fs_1.default.existsSync(resolved)) {
|
|
250
|
+
found.push(cleanRef);
|
|
251
|
+
}
|
|
252
|
+
else {
|
|
253
|
+
missing.push(cleanRef);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
const backtickRefs = content.match(/`([^`]+\/[^`]+\.[a-zA-Z]{1,10})`/g) || [];
|
|
257
|
+
for (const ref of backtickRefs) {
|
|
258
|
+
const cleanRef = ref.slice(1, -1);
|
|
259
|
+
if (cleanRef.startsWith('http') || cleanRef.includes('${') || cleanRef.includes('{{'))
|
|
260
|
+
continue;
|
|
261
|
+
if (found.includes(cleanRef) || missing.includes(cleanRef))
|
|
262
|
+
continue;
|
|
263
|
+
const resolved = node_path_1.default.join(cwd, cleanRef);
|
|
264
|
+
if (node_fs_1.default.existsSync(resolved)) {
|
|
265
|
+
found.push(cleanRef);
|
|
266
|
+
}
|
|
267
|
+
else {
|
|
268
|
+
missing.push(cleanRef);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
output({
|
|
272
|
+
valid: missing.length === 0,
|
|
273
|
+
found: found.length,
|
|
274
|
+
missing,
|
|
275
|
+
total: found.length + missing.length,
|
|
276
|
+
}, raw, missing.length === 0 ? 'valid' : 'invalid');
|
|
277
|
+
}
|
|
278
|
+
function cmdVerifyCommits(cwd, hashes, raw) {
|
|
279
|
+
if (!hashes || hashes.length === 0) {
|
|
280
|
+
error('At least one commit hash required');
|
|
281
|
+
}
|
|
282
|
+
const valid = [];
|
|
283
|
+
const invalid = [];
|
|
284
|
+
for (const hash of hashes) {
|
|
285
|
+
const result = (0, shell_command_projection_cjs_1.execGit)(['cat-file', '-t', hash], { cwd });
|
|
286
|
+
if (result.exitCode === 0 && result.stdout.trim() === 'commit') {
|
|
287
|
+
valid.push(hash);
|
|
288
|
+
}
|
|
289
|
+
else {
|
|
290
|
+
invalid.push(hash);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
output({
|
|
294
|
+
all_valid: invalid.length === 0,
|
|
295
|
+
valid,
|
|
296
|
+
invalid,
|
|
297
|
+
total: hashes.length,
|
|
298
|
+
}, raw, invalid.length === 0 ? 'valid' : 'invalid');
|
|
299
|
+
}
|
|
300
|
+
function cmdVerifyArtifacts(cwd, planFilePath, raw) {
|
|
301
|
+
if (!planFilePath) {
|
|
302
|
+
error('plan file path required');
|
|
303
|
+
}
|
|
304
|
+
const fullPath = node_path_1.default.isAbsolute(planFilePath) ? planFilePath : node_path_1.default.join(cwd, planFilePath);
|
|
305
|
+
const content = (0, shell_command_projection_cjs_1.platformReadSync)(fullPath);
|
|
306
|
+
if (!content) {
|
|
307
|
+
output({ error: 'File not found', path: planFilePath }, raw);
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
const artifacts = parseMustHavesBlock(content, 'artifacts');
|
|
311
|
+
if (artifacts.length === 0) {
|
|
312
|
+
output({ error: 'No must_haves.artifacts found in frontmatter', path: planFilePath }, raw);
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
const results = [];
|
|
316
|
+
for (const artifact of artifacts) {
|
|
317
|
+
if (typeof artifact === 'string')
|
|
318
|
+
continue;
|
|
319
|
+
const artPath = artifact['path'];
|
|
320
|
+
if (!artPath)
|
|
321
|
+
continue;
|
|
322
|
+
const artFullPath = node_path_1.default.join(cwd, artPath);
|
|
323
|
+
const exists = node_fs_1.default.existsSync(artFullPath);
|
|
324
|
+
const check = { path: artPath, exists, issues: [], passed: false };
|
|
325
|
+
if (exists) {
|
|
326
|
+
const fileContent = (0, shell_command_projection_cjs_1.platformReadSync)(artFullPath) || '';
|
|
327
|
+
const lineCount = fileContent.split('\n').length;
|
|
328
|
+
if (artifact['min_lines'] && lineCount < artifact['min_lines']) {
|
|
329
|
+
check['issues'].push(`Only ${lineCount} lines, need ${artifact['min_lines']}`);
|
|
330
|
+
}
|
|
331
|
+
if (artifact['contains'] && !fileContent.includes(artifact['contains'])) {
|
|
332
|
+
check['issues'].push(`Missing pattern: ${artifact['contains']}`);
|
|
333
|
+
}
|
|
334
|
+
if (artifact['exports']) {
|
|
335
|
+
const exports = Array.isArray(artifact['exports'])
|
|
336
|
+
? artifact['exports']
|
|
337
|
+
: [artifact['exports']];
|
|
338
|
+
for (const exp of exports) {
|
|
339
|
+
if (!fileContent.includes(exp))
|
|
340
|
+
check['issues'].push(`Missing export: ${exp}`);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
check['passed'] = check['issues'].length === 0;
|
|
344
|
+
}
|
|
345
|
+
else {
|
|
346
|
+
check['issues'].push('File not found');
|
|
347
|
+
}
|
|
348
|
+
results.push(check);
|
|
349
|
+
}
|
|
350
|
+
const passed = results.filter((r) => r['passed']).length;
|
|
351
|
+
output({
|
|
352
|
+
all_passed: passed === results.length,
|
|
353
|
+
passed,
|
|
354
|
+
total: results.length,
|
|
355
|
+
artifacts: results,
|
|
356
|
+
}, raw, passed === results.length ? 'valid' : 'invalid');
|
|
357
|
+
}
|
|
358
|
+
function cmdVerifyKeyLinks(cwd, planFilePath, raw) {
|
|
359
|
+
if (!planFilePath) {
|
|
360
|
+
error('plan file path required');
|
|
361
|
+
}
|
|
362
|
+
const fullPath = node_path_1.default.isAbsolute(planFilePath) ? planFilePath : node_path_1.default.join(cwd, planFilePath);
|
|
363
|
+
const content = (0, shell_command_projection_cjs_1.platformReadSync)(fullPath);
|
|
364
|
+
if (!content) {
|
|
365
|
+
output({ error: 'File not found', path: planFilePath }, raw);
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
const keyLinks = parseMustHavesBlock(content, 'key_links');
|
|
369
|
+
if (keyLinks.length === 0) {
|
|
370
|
+
output({ error: 'No must_haves.key_links found in frontmatter', path: planFilePath }, raw);
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
const results = [];
|
|
374
|
+
for (const link of keyLinks) {
|
|
375
|
+
if (typeof link === 'string')
|
|
376
|
+
continue;
|
|
377
|
+
const check = {
|
|
378
|
+
from: link['from'],
|
|
379
|
+
to: link['to'],
|
|
380
|
+
via: link['via'] || '',
|
|
381
|
+
verified: false,
|
|
382
|
+
detail: '',
|
|
383
|
+
};
|
|
384
|
+
const sourceContent = (0, shell_command_projection_cjs_1.platformReadSync)(node_path_1.default.join(cwd, link['from'] || ''));
|
|
385
|
+
if (!sourceContent) {
|
|
386
|
+
check['detail'] = 'Source file not found';
|
|
387
|
+
}
|
|
388
|
+
else if (link['pattern']) {
|
|
389
|
+
try {
|
|
390
|
+
const regex = new RegExp(link['pattern']);
|
|
391
|
+
if (regex.test(sourceContent)) {
|
|
392
|
+
check['verified'] = true;
|
|
393
|
+
check['detail'] = 'Pattern found in source';
|
|
394
|
+
}
|
|
395
|
+
else {
|
|
396
|
+
const targetContent = (0, shell_command_projection_cjs_1.platformReadSync)(node_path_1.default.join(cwd, link['to'] || ''));
|
|
397
|
+
if (targetContent && regex.test(targetContent)) {
|
|
398
|
+
check['verified'] = true;
|
|
399
|
+
check['detail'] = 'Pattern found in target';
|
|
400
|
+
}
|
|
401
|
+
else {
|
|
402
|
+
check['detail'] = `Pattern "${link['pattern']}" not found in source or target`;
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
catch {
|
|
407
|
+
check['detail'] = `Invalid regex pattern: ${link['pattern']}`;
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
else {
|
|
411
|
+
if (sourceContent.includes(link['to'] || '')) {
|
|
412
|
+
check['verified'] = true;
|
|
413
|
+
check['detail'] = 'Target referenced in source';
|
|
414
|
+
}
|
|
415
|
+
else {
|
|
416
|
+
check['detail'] = 'Target not referenced in source';
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
results.push(check);
|
|
420
|
+
}
|
|
421
|
+
const verified = results.filter((r) => r['verified']).length;
|
|
422
|
+
output({
|
|
423
|
+
all_verified: verified === results.length,
|
|
424
|
+
verified,
|
|
425
|
+
total: results.length,
|
|
426
|
+
links: results,
|
|
427
|
+
}, raw, verified === results.length ? 'valid' : 'invalid');
|
|
428
|
+
}
|
|
429
|
+
function listMilestoneArchiveDirs(planBase) {
|
|
430
|
+
const milestonesDir = node_path_1.default.join(planBase, 'milestones');
|
|
431
|
+
try {
|
|
432
|
+
return node_fs_1.default
|
|
433
|
+
.readdirSync(milestonesDir, { withFileTypes: true })
|
|
434
|
+
.filter((e) => e.isDirectory() && validate_cjs_2.MILESTONE_ARCHIVE_DIR_RE.test(e.name))
|
|
435
|
+
.map((e) => node_path_1.default.join(milestonesDir, e.name))
|
|
436
|
+
.sort((a, b) => node_path_1.default.basename(a).localeCompare(node_path_1.default.basename(b), undefined, { numeric: true }));
|
|
437
|
+
}
|
|
438
|
+
catch {
|
|
439
|
+
return [];
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
function forEachArchivedPhaseToken(planBase, onPhase) {
|
|
443
|
+
for (const archiveDir of listMilestoneArchiveDirs(planBase)) {
|
|
444
|
+
try {
|
|
445
|
+
const entries = node_fs_1.default.readdirSync(archiveDir, { withFileTypes: true });
|
|
446
|
+
for (const e of entries) {
|
|
447
|
+
if (!e.isDirectory())
|
|
448
|
+
continue;
|
|
449
|
+
const m = e.name.match(validate_cjs_2.PHASE_TOKEN_FROM_DIR_RE);
|
|
450
|
+
if (m)
|
|
451
|
+
onPhase(m[1]);
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
catch {
|
|
455
|
+
/* archive dir absent/unreadable */
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
function getActiveMilestoneArchiveDir(planBase) {
|
|
460
|
+
const archiveDirs = listMilestoneArchiveDirs(planBase);
|
|
461
|
+
if (archiveDirs.length === 0)
|
|
462
|
+
return null;
|
|
463
|
+
try {
|
|
464
|
+
const statePath = node_path_1.default.join(planBase, 'STATE.md');
|
|
465
|
+
if (node_fs_1.default.existsSync(statePath)) {
|
|
466
|
+
const state = node_fs_1.default.readFileSync(statePath, 'utf-8');
|
|
467
|
+
const m = state.match(/^\s*(?:\*\*)?milestone(?:\*\*)?:\s*\*{0,2}\s*([^\s*\r\n#][^\s\r\n#]*)/mi);
|
|
468
|
+
if (m && m[1]) {
|
|
469
|
+
const milestone = m[1].trim();
|
|
470
|
+
const candidate = node_path_1.default.join(planBase, 'milestones', `${milestone}-phases`);
|
|
471
|
+
return archiveDirs.includes(candidate) ? candidate : null;
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
catch {
|
|
476
|
+
/* intentionally empty — fall through to version-sort below */
|
|
477
|
+
}
|
|
478
|
+
return archiveDirs[archiveDirs.length - 1];
|
|
479
|
+
}
|
|
480
|
+
function collectPhaseRoots(planBase) {
|
|
481
|
+
const roots = [];
|
|
482
|
+
const flatPhasesDir = node_path_1.default.join(planBase, 'phases');
|
|
483
|
+
if (node_fs_1.default.existsSync(flatPhasesDir))
|
|
484
|
+
roots.push(flatPhasesDir);
|
|
485
|
+
const activeArchive = getActiveMilestoneArchiveDir(planBase);
|
|
486
|
+
if (activeArchive)
|
|
487
|
+
roots.push(activeArchive);
|
|
488
|
+
return roots;
|
|
489
|
+
}
|
|
490
|
+
function collectDiskPhases(planBase) {
|
|
491
|
+
const diskPhases = new Set();
|
|
492
|
+
const phaseRoots = collectPhaseRoots(planBase);
|
|
493
|
+
const scanDir = (dir) => {
|
|
494
|
+
try {
|
|
495
|
+
const entries = node_fs_1.default.readdirSync(dir, { withFileTypes: true });
|
|
496
|
+
for (const e of entries) {
|
|
497
|
+
if (e.isDirectory()) {
|
|
498
|
+
const m = e.name.match(validate_cjs_2.PHASE_TOKEN_FROM_DIR_RE);
|
|
499
|
+
if (m)
|
|
500
|
+
diskPhases.add(m[1]);
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
catch {
|
|
505
|
+
/* dir absent */
|
|
506
|
+
}
|
|
507
|
+
};
|
|
508
|
+
for (const root of phaseRoots)
|
|
509
|
+
scanDir(root);
|
|
510
|
+
return diskPhases;
|
|
511
|
+
}
|
|
512
|
+
function checkMilestonePrefixMismatches(roadmapContent, { getMilestoneFromPhaseId }) {
|
|
513
|
+
const mismatches = [];
|
|
514
|
+
const sections = [];
|
|
515
|
+
const sectionRx = /^#{1,3}\s+(?:\[[^\]]+\]\s*)?.*v(\d+\.\d+)/gim;
|
|
516
|
+
let m;
|
|
517
|
+
while ((m = sectionRx.exec(roadmapContent)) !== null) {
|
|
518
|
+
if (sections.length > 0)
|
|
519
|
+
sections[sections.length - 1].end = m.index;
|
|
520
|
+
sections.push({ version: `v${m[1]}`, start: m.index, end: roadmapContent.length });
|
|
521
|
+
}
|
|
522
|
+
for (const section of sections) {
|
|
523
|
+
const content = roadmapContent.slice(section.start, section.end);
|
|
524
|
+
const phaseRx = /#{2,4}\s*(?:\[[^\]]+\]\s*)?Phase\s+([\w][\w.-]*)\s*:/gi;
|
|
525
|
+
let pm;
|
|
526
|
+
while ((pm = phaseRx.exec(content)) !== null) {
|
|
527
|
+
const phaseId = pm[1];
|
|
528
|
+
const expectedMilestone = getMilestoneFromPhaseId(phaseId);
|
|
529
|
+
if (expectedMilestone !== null && expectedMilestone !== section.version) {
|
|
530
|
+
mismatches.push({
|
|
531
|
+
phaseId,
|
|
532
|
+
foundInMilestone: section.version,
|
|
533
|
+
expectedMilestone,
|
|
534
|
+
});
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
return mismatches;
|
|
539
|
+
}
|
|
540
|
+
function cmdValidateConsistency(cwd, raw) {
|
|
541
|
+
const planBase = planningDir(cwd);
|
|
542
|
+
const roadmapPath = node_path_1.default.join(planBase, 'ROADMAP.md');
|
|
543
|
+
const errors = [];
|
|
544
|
+
const warnings = [];
|
|
545
|
+
if (!node_fs_1.default.existsSync(roadmapPath)) {
|
|
546
|
+
errors.push('ROADMAP.md not found');
|
|
547
|
+
output({ passed: false, errors, warnings }, raw, 'failed');
|
|
548
|
+
return;
|
|
549
|
+
}
|
|
550
|
+
const roadmapContentRaw = node_fs_1.default.readFileSync(roadmapPath, 'utf-8');
|
|
551
|
+
const roadmapContent = extractCurrentMilestone(roadmapContentRaw, cwd);
|
|
552
|
+
const roadmapPhases = new Set();
|
|
553
|
+
const phasePattern = /#{2,4}\s*(?:\[[^\]]+\]\s*)?Phase\s+([\w][\w.-]*)\s*:/gi;
|
|
554
|
+
let m;
|
|
555
|
+
while ((m = phasePattern.exec(roadmapContent)) !== null) {
|
|
556
|
+
roadmapPhases.add(m[1]);
|
|
557
|
+
}
|
|
558
|
+
const fullRoadmapPhases = new Set();
|
|
559
|
+
const fullPhasePattern = /#{2,4}\s*(?:\[[^\]]+\]\s*)?Phase\s+([\w][\w.-]*)\s*:/gi;
|
|
560
|
+
let fm;
|
|
561
|
+
while ((fm = fullPhasePattern.exec(roadmapContentRaw)) !== null) {
|
|
562
|
+
fullRoadmapPhases.add(fm[1]);
|
|
563
|
+
}
|
|
564
|
+
const diskPhases = collectDiskPhases(planBase);
|
|
565
|
+
for (const p of roadmapPhases) {
|
|
566
|
+
if (!diskPhases.has(p) && !diskPhases.has(normalizePhaseName(p))) {
|
|
567
|
+
warnings.push(`Phase ${p} in ROADMAP.md but no directory on disk`);
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
for (const p of diskPhases) {
|
|
571
|
+
const normalized = normalizePhaseName(p);
|
|
572
|
+
const unpadded = String(parseInt(p, 10));
|
|
573
|
+
if (!fullRoadmapPhases.has(p) &&
|
|
574
|
+
!fullRoadmapPhases.has(normalized) &&
|
|
575
|
+
!fullRoadmapPhases.has(unpadded)) {
|
|
576
|
+
warnings.push(`Phase ${p} exists on disk but not in ROADMAP.md`);
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
const config = loadConfig(cwd);
|
|
580
|
+
if (config.phase_naming !== 'custom') {
|
|
581
|
+
const integerPhases = [...diskPhases]
|
|
582
|
+
.filter((p) => !p.includes('.'))
|
|
583
|
+
.map((p) => parseInt(p, 10))
|
|
584
|
+
.sort((a, b) => a - b);
|
|
585
|
+
for (let i = 1; i < integerPhases.length; i++) {
|
|
586
|
+
if (integerPhases[i] !== integerPhases[i - 1] + 1) {
|
|
587
|
+
warnings.push(`Gap in phase numbering: ${integerPhases[i - 1]} → ${integerPhases[i]}`);
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
const phaseRoots = collectPhaseRoots(planBase);
|
|
592
|
+
for (const phaseRoot of phaseRoots) {
|
|
593
|
+
try {
|
|
594
|
+
const entries = node_fs_1.default.readdirSync(phaseRoot, { withFileTypes: true });
|
|
595
|
+
const dirs = entries
|
|
596
|
+
.filter((e) => e.isDirectory())
|
|
597
|
+
.map((e) => e.name)
|
|
598
|
+
.sort();
|
|
599
|
+
for (const dir of dirs) {
|
|
600
|
+
const phasePath = node_path_1.default.join(phaseRoot, dir);
|
|
601
|
+
const phaseLabel = node_path_1.default.relative(planBase, phasePath).replace(/\\/g, '/');
|
|
602
|
+
const phaseFiles = node_fs_1.default.readdirSync(phasePath);
|
|
603
|
+
const plans = phaseFiles.filter((f) => f.endsWith('-PLAN.md')).sort();
|
|
604
|
+
const planNums = plans
|
|
605
|
+
.map((p) => {
|
|
606
|
+
const pm = p.match(/-(\d{2})-PLAN\.md$/);
|
|
607
|
+
return pm ? parseInt(pm[1], 10) : null;
|
|
608
|
+
})
|
|
609
|
+
.filter((n) => n !== null);
|
|
610
|
+
for (let i = 1; i < planNums.length; i++) {
|
|
611
|
+
if (planNums[i] !== planNums[i - 1] + 1) {
|
|
612
|
+
warnings.push(`Gap in plan numbering in ${phaseLabel}: plan ${planNums[i - 1]} → ${planNums[i]}`);
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
const summaries = phaseFiles.filter((f) => f.endsWith('-SUMMARY.md'));
|
|
616
|
+
const planIds = new Set(plans.map((p) => p.replace('-PLAN.md', '')));
|
|
617
|
+
const summaryIds = new Set(summaries.map((s) => s.replace('-SUMMARY.md', '')));
|
|
618
|
+
for (const sid of summaryIds) {
|
|
619
|
+
if (!planIds.has(sid)) {
|
|
620
|
+
warnings.push(`Summary ${sid}-SUMMARY.md in ${phaseLabel} has no matching PLAN.md`);
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
for (const plan of plans) {
|
|
624
|
+
const content = node_fs_1.default.readFileSync(node_path_1.default.join(phasePath, plan), 'utf-8');
|
|
625
|
+
const fmData = extractFrontmatter(content);
|
|
626
|
+
if (!fmData['wave']) {
|
|
627
|
+
warnings.push(`${phaseLabel}/${plan}: missing 'wave' in frontmatter`);
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
catch {
|
|
633
|
+
/* intentionally empty */
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
const passed = errors.length === 0;
|
|
637
|
+
output({ passed, errors, warnings, warning_count: warnings.length }, raw, passed ? 'passed' : 'failed');
|
|
638
|
+
}
|
|
639
|
+
function cmdValidateHealth(cwd, options, raw) {
|
|
640
|
+
const resolved = node_path_1.default.resolve(cwd);
|
|
641
|
+
if (resolved === node_os_1.default.homedir()) {
|
|
642
|
+
output({
|
|
643
|
+
status: 'error',
|
|
644
|
+
errors: [
|
|
645
|
+
{
|
|
646
|
+
code: 'E010',
|
|
647
|
+
message: `CWD is home directory (${resolved}) — health check would read the wrong .planning/ directory. Run from your project root instead.`,
|
|
648
|
+
fix: 'cd into your project directory and retry',
|
|
649
|
+
},
|
|
650
|
+
],
|
|
651
|
+
warnings: [],
|
|
652
|
+
info: [{ code: 'I010', message: `Resolved CWD: ${resolved}` }],
|
|
653
|
+
repairable_count: 0,
|
|
654
|
+
}, raw);
|
|
655
|
+
return;
|
|
656
|
+
}
|
|
657
|
+
const planBase = planningDir(cwd);
|
|
658
|
+
const projectPath = node_path_1.default.join(planBase, 'PROJECT.md');
|
|
659
|
+
const roadmapPath = node_path_1.default.join(planBase, 'ROADMAP.md');
|
|
660
|
+
const statePath = node_path_1.default.join(planBase, 'STATE.md');
|
|
661
|
+
const configPath = node_path_1.default.join(planBase, 'config.json');
|
|
662
|
+
const phasesDir = node_path_1.default.join(planBase, 'phases');
|
|
663
|
+
const _slashRuntime = (0, runtime_slash_cjs_1.resolveRuntime)(cwd);
|
|
664
|
+
const slash = (name) => (0, runtime_slash_cjs_1.formatGsdSlash)(name, _slashRuntime);
|
|
665
|
+
const errors = [];
|
|
666
|
+
const warnings = [];
|
|
667
|
+
const info = [];
|
|
668
|
+
const repairs = [];
|
|
669
|
+
const addIssue = (severity, code, message, fix, repairable = false) => {
|
|
670
|
+
const issue = { code, message, fix, repairable };
|
|
671
|
+
if (severity === 'error')
|
|
672
|
+
errors.push(issue);
|
|
673
|
+
else if (severity === 'warning')
|
|
674
|
+
warnings.push(issue);
|
|
675
|
+
else
|
|
676
|
+
info.push(issue);
|
|
677
|
+
};
|
|
678
|
+
if (!node_fs_1.default.existsSync(planBase)) {
|
|
679
|
+
addIssue('error', 'E001', '.planning/ directory not found', `Run ${slash('new-project')} to initialize`);
|
|
680
|
+
output({ status: 'broken', errors, warnings, info, repairable_count: 0 }, raw);
|
|
681
|
+
return;
|
|
682
|
+
}
|
|
683
|
+
if (!node_fs_1.default.existsSync(projectPath)) {
|
|
684
|
+
addIssue('error', 'E002', 'PROJECT.md not found', `Run ${slash('new-project')} to create`);
|
|
685
|
+
}
|
|
686
|
+
else {
|
|
687
|
+
const content = node_fs_1.default.readFileSync(projectPath, 'utf-8');
|
|
688
|
+
const requiredSections = ['## What This Is', '## Core Value', '## Requirements'];
|
|
689
|
+
for (const section of requiredSections) {
|
|
690
|
+
if (!content.includes(section)) {
|
|
691
|
+
addIssue('warning', 'W001', `PROJECT.md missing section: ${section}`, 'Add section manually');
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
if (!node_fs_1.default.existsSync(roadmapPath)) {
|
|
696
|
+
addIssue('error', 'E003', 'ROADMAP.md not found', `Run ${slash('new-milestone')} to create roadmap`);
|
|
697
|
+
}
|
|
698
|
+
if (!node_fs_1.default.existsSync(statePath)) {
|
|
699
|
+
addIssue('error', 'E004', 'STATE.md not found', `Run ${slash('health')} --repair to regenerate`, true);
|
|
700
|
+
repairs.push('regenerateState');
|
|
701
|
+
}
|
|
702
|
+
else {
|
|
703
|
+
const stateContent = node_fs_1.default.readFileSync(statePath, 'utf-8');
|
|
704
|
+
const phaseRefs = [...stateContent.matchAll(/[Pp]hase\s+(\d+[A-Z]?(?:\.\d+)*)/g)].map((m) => m[1]);
|
|
705
|
+
const validPhases = collectDiskPhases(planBase);
|
|
706
|
+
try {
|
|
707
|
+
if (node_fs_1.default.existsSync(roadmapPath)) {
|
|
708
|
+
const roadmapRaw = node_fs_1.default.readFileSync(roadmapPath, 'utf-8');
|
|
709
|
+
const all = [...roadmapRaw.matchAll(/#{2,4}\s*Phase\s+(\d+[A-Z]?(?:\.\d+)*)/gi)];
|
|
710
|
+
for (const m of all)
|
|
711
|
+
validPhases.add(m[1]);
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
catch {
|
|
715
|
+
/* intentionally empty */
|
|
716
|
+
}
|
|
717
|
+
forEachArchivedPhaseToken(planBase, (token) => validPhases.add(token));
|
|
718
|
+
const normalizedValid = new Set();
|
|
719
|
+
for (const p of validPhases) {
|
|
720
|
+
normalizedValid.add(p);
|
|
721
|
+
const dotIdx = p.indexOf('.');
|
|
722
|
+
const head = dotIdx === -1 ? p : p.slice(0, dotIdx);
|
|
723
|
+
const tail = dotIdx === -1 ? '' : p.slice(dotIdx);
|
|
724
|
+
if (/^\d+$/.test(head)) {
|
|
725
|
+
normalizedValid.add(head.padStart(2, '0') + tail);
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
for (const ref of phaseRefs) {
|
|
729
|
+
const dotIdx = ref.indexOf('.');
|
|
730
|
+
const head = dotIdx === -1 ? ref : ref.slice(0, dotIdx);
|
|
731
|
+
const tail = dotIdx === -1 ? '' : ref.slice(dotIdx);
|
|
732
|
+
const padded = /^\d+$/.test(head) ? head.padStart(2, '0') + tail : ref;
|
|
733
|
+
if (!normalizedValid.has(ref) && !normalizedValid.has(padded)) {
|
|
734
|
+
if (normalizedValid.size > 0) {
|
|
735
|
+
addIssue('warning', 'W002', `STATE.md references phase ${ref}, but only phases ${[...validPhases].sort((a, b) => a.localeCompare(b, undefined, { numeric: true })).join(', ')} are declared`, `Review STATE.md manually before changing it; ${slash('health')} --repair will not overwrite an existing STATE.md for phase mismatches`);
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
if (!node_fs_1.default.existsSync(configPath)) {
|
|
741
|
+
addIssue('warning', 'W003', 'config.json not found', `Run ${slash('health')} --repair to create with defaults`, true);
|
|
742
|
+
repairs.push('createConfig');
|
|
743
|
+
}
|
|
744
|
+
else {
|
|
745
|
+
try {
|
|
746
|
+
const rawCfg = node_fs_1.default.readFileSync(configPath, 'utf-8');
|
|
747
|
+
const parsed = JSON.parse(rawCfg);
|
|
748
|
+
const validProfiles = ['quality', 'balanced', 'budget', 'inherit'];
|
|
749
|
+
if (parsed['model_profile'] && !validProfiles.includes(parsed['model_profile'])) {
|
|
750
|
+
addIssue('warning', 'W004', `config.json: invalid model_profile "${parsed['model_profile']}"`, `Valid values: ${validProfiles.join(', ')}`);
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
catch (err) {
|
|
754
|
+
addIssue('error', 'E005', `config.json: JSON parse error - ${err instanceof Error ? err.message : String(err)}`, `Run ${slash('health')} --repair to reset to defaults`, true);
|
|
755
|
+
repairs.push('resetConfig');
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
if (node_fs_1.default.existsSync(configPath)) {
|
|
759
|
+
try {
|
|
760
|
+
const configRaw = node_fs_1.default.readFileSync(configPath, 'utf-8');
|
|
761
|
+
const configParsed = JSON.parse(configRaw);
|
|
762
|
+
const workflow = configParsed['workflow'];
|
|
763
|
+
if (workflow && workflow['nyquist_validation'] === undefined) {
|
|
764
|
+
addIssue('warning', 'W008', 'config.json: workflow.nyquist_validation absent (defaults to enabled but agents may skip)', `Run ${slash('health')} --repair to add key`, true);
|
|
765
|
+
if (!repairs.includes('addNyquistKey'))
|
|
766
|
+
repairs.push('addNyquistKey');
|
|
767
|
+
}
|
|
768
|
+
if (workflow && workflow['ai_integration_phase'] === undefined) {
|
|
769
|
+
addIssue('warning', 'W016', `config.json: workflow.ai_integration_phase absent (defaults to enabled — run ${slash('ai-integration-phase')} before planning AI system phases)`, `Run ${slash('health')} --repair to add key`, true);
|
|
770
|
+
if (!repairs.includes('addAiIntegrationPhaseKey'))
|
|
771
|
+
repairs.push('addAiIntegrationPhaseKey');
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
catch {
|
|
775
|
+
/* intentionally empty */
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
let phaseDirEntries = [];
|
|
779
|
+
const phaseDirFiles = new Map();
|
|
780
|
+
try {
|
|
781
|
+
phaseDirEntries = node_fs_1.default
|
|
782
|
+
.readdirSync(phasesDir, { withFileTypes: true })
|
|
783
|
+
.filter((e) => e.isDirectory());
|
|
784
|
+
for (const e of phaseDirEntries) {
|
|
785
|
+
try {
|
|
786
|
+
phaseDirFiles.set(e.name, node_fs_1.default.readdirSync(node_path_1.default.join(phasesDir, e.name)));
|
|
787
|
+
}
|
|
788
|
+
catch {
|
|
789
|
+
phaseDirFiles.set(e.name, []);
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
catch {
|
|
794
|
+
/* intentionally empty */
|
|
795
|
+
}
|
|
796
|
+
for (const e of phaseDirEntries) {
|
|
797
|
+
if (!e.name.match(validate_cjs_2.phaseDirNameRe)) {
|
|
798
|
+
addIssue('warning', 'W005', `Phase directory "${e.name}" doesn't follow NN-name format`, 'Rename to match pattern (e.g., 01-setup)');
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
for (const e of phaseDirEntries) {
|
|
802
|
+
const phaseFiles = phaseDirFiles.get(e.name) || [];
|
|
803
|
+
const plans = phaseFiles.filter((f) => f.endsWith('-PLAN.md') || f === 'PLAN.md');
|
|
804
|
+
const summaries = phaseFiles.filter((f) => f.endsWith('-SUMMARY.md') || f === 'SUMMARY.md');
|
|
805
|
+
const summaryBases = new Set();
|
|
806
|
+
for (const s of summaries) {
|
|
807
|
+
const summaryBase = s.replace('-SUMMARY.md', '').replace('SUMMARY.md', '');
|
|
808
|
+
summaryBases.add(summaryBase);
|
|
809
|
+
summaryBases.add((0, validate_cjs_2.canonicalPlanStem)(summaryBase));
|
|
810
|
+
}
|
|
811
|
+
for (const plan of plans) {
|
|
812
|
+
const planBase = plan.replace('-PLAN.md', '').replace('PLAN.md', '');
|
|
813
|
+
const canonicalBase = (0, validate_cjs_2.canonicalPlanStem)(planBase);
|
|
814
|
+
if (!summaryBases.has(planBase) && !summaryBases.has(canonicalBase)) {
|
|
815
|
+
addIssue('info', 'I001', `${e.name}/${plan} has no SUMMARY.md`, 'May be in progress');
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
for (const e of phaseDirEntries) {
|
|
820
|
+
const phaseFiles = phaseDirFiles.get(e.name) || [];
|
|
821
|
+
const hasResearch = phaseFiles.some((f) => f.endsWith('-RESEARCH.md'));
|
|
822
|
+
const hasValidation = phaseFiles.some((f) => f.endsWith('-VALIDATION.md'));
|
|
823
|
+
if (hasResearch && !hasValidation) {
|
|
824
|
+
const researchFile = phaseFiles.find((f) => f.endsWith('-RESEARCH.md'));
|
|
825
|
+
try {
|
|
826
|
+
const researchContent = node_fs_1.default.readFileSync(node_path_1.default.join(phasesDir, e.name, researchFile), 'utf-8');
|
|
827
|
+
if (researchContent.includes('## Validation Architecture')) {
|
|
828
|
+
addIssue('warning', 'W009', `Phase ${e.name}: has Validation Architecture in RESEARCH.md but no VALIDATION.md`, `Re-run ${slash('plan-phase')} with --research to regenerate`);
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
catch {
|
|
832
|
+
/* intentionally empty */
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
try {
|
|
837
|
+
const agentStatus = checkAgentsInstalled();
|
|
838
|
+
if (!agentStatus.agents_installed) {
|
|
839
|
+
if ((agentStatus.installed_agents).length === 0) {
|
|
840
|
+
addIssue('warning', 'W010', `No GSD agents found in ${agentStatus.agents_dir} — Task(subagent_type="gsd-*") will fall back to general-purpose`, `Run the GSD installer: npx ${package_identity_cjs_1.PACKAGE_NAME}@latest`);
|
|
841
|
+
}
|
|
842
|
+
else {
|
|
843
|
+
addIssue('warning', 'W010', `Missing ${(agentStatus.missing_agents).length} GSD agents: ${(agentStatus.missing_agents).join(', ')} — affected workflows will fall back to general-purpose`, `Run the GSD installer: npx ${package_identity_cjs_1.PACKAGE_NAME}@latest`);
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
catch {
|
|
848
|
+
/* intentionally empty — agent check is non-blocking */
|
|
849
|
+
}
|
|
850
|
+
if (node_fs_1.default.existsSync(roadmapPath)) {
|
|
851
|
+
const roadmapContentRaw = node_fs_1.default.readFileSync(roadmapPath, 'utf-8');
|
|
852
|
+
const roadmapContent = extractCurrentMilestone(roadmapContentRaw, cwd);
|
|
853
|
+
const { roadmapPhases } = (0, validate_cjs_1.buildRoadmapPhaseVariants)(roadmapContent);
|
|
854
|
+
const { roadmapPhaseVariants: fullRoadmapPhaseVariants } = (0, validate_cjs_1.buildRoadmapPhaseVariants)(roadmapContentRaw);
|
|
855
|
+
const diskPhases = collectDiskPhases(planBase);
|
|
856
|
+
forEachArchivedPhaseToken(planBase, (token) => diskPhases.add(token));
|
|
857
|
+
const activeDiskPhases = collectDiskPhases(planBase);
|
|
858
|
+
const notStartedPhases = (0, validate_cjs_1.buildNotStartedPhaseVariants)(roadmapContent);
|
|
859
|
+
for (const p of roadmapPhases) {
|
|
860
|
+
const variants = (0, validate_cjs_1.phaseVariants)(p);
|
|
861
|
+
const existsOnDisk = [...variants].some((v) => diskPhases.has(v));
|
|
862
|
+
if (!existsOnDisk) {
|
|
863
|
+
const isNotStarted = [...variants].some((v) => notStartedPhases.has(v));
|
|
864
|
+
if (isNotStarted)
|
|
865
|
+
continue;
|
|
866
|
+
addIssue('warning', 'W006', `Phase ${p} in ROADMAP.md but no directory on disk`, 'Create phase directory or remove from roadmap');
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
for (const p of activeDiskPhases) {
|
|
870
|
+
const variants = (0, validate_cjs_1.phaseVariants)(p);
|
|
871
|
+
if (![...variants].some((v) => fullRoadmapPhaseVariants.has(v))) {
|
|
872
|
+
addIssue('warning', 'W007', `Phase ${p} exists on disk but not in ROADMAP.md`, 'Add to roadmap or remove directory');
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
if (node_fs_1.default.existsSync(statePath) && node_fs_1.default.existsSync(roadmapPath)) {
|
|
877
|
+
try {
|
|
878
|
+
const stateContent = node_fs_1.default.readFileSync(statePath, 'utf-8');
|
|
879
|
+
const roadmapContentFull = node_fs_1.default.readFileSync(roadmapPath, 'utf-8');
|
|
880
|
+
const currentPhaseMatch = stateContent.match(/\*\*Current Phase:\*\*\s*(\S+)/i) ||
|
|
881
|
+
stateContent.match(/Current Phase:\s*(\S+)/i);
|
|
882
|
+
if (currentPhaseMatch) {
|
|
883
|
+
const statePhase = currentPhaseMatch[1].replace(/^0+/, '');
|
|
884
|
+
const phaseCheckboxRe = new RegExp(`-\\s*\\[x\\].*Phase\\s+0*${escapeRegex(statePhase)}[:\\s]`, 'i');
|
|
885
|
+
if (phaseCheckboxRe.test(roadmapContentFull)) {
|
|
886
|
+
const stateStatus = stateContent.match(/\*\*Status:\*\*\s*(.+)/i);
|
|
887
|
+
const statusVal = stateStatus ? stateStatus[1].trim().toLowerCase() : '';
|
|
888
|
+
if (statusVal !== 'complete' && statusVal !== 'done') {
|
|
889
|
+
addIssue('warning', 'W011', `STATE.md says current phase is ${statePhase} (status: ${statusVal || 'unknown'}) but ROADMAP.md shows it as [x] complete — state files may be out of sync`, `Run ${slash('progress')} to re-derive current position, or manually update STATE.md`);
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
catch {
|
|
895
|
+
/* intentionally empty — cross-validation is advisory */
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
if (node_fs_1.default.existsSync(configPath)) {
|
|
899
|
+
try {
|
|
900
|
+
const configRaw = node_fs_1.default.readFileSync(configPath, 'utf-8');
|
|
901
|
+
const configParsed = JSON.parse(configRaw);
|
|
902
|
+
const validStrategies = ['none', 'phase', 'milestone'];
|
|
903
|
+
if (configParsed['branching_strategy'] &&
|
|
904
|
+
!validStrategies.includes(configParsed['branching_strategy'])) {
|
|
905
|
+
addIssue('warning', 'W012', `config.json: invalid branching_strategy "${configParsed['branching_strategy']}"`, `Valid values: ${validStrategies.join(', ')}`);
|
|
906
|
+
}
|
|
907
|
+
if (configParsed['context_window'] !== undefined) {
|
|
908
|
+
const cw = configParsed['context_window'];
|
|
909
|
+
if (typeof cw !== 'number' || cw <= 0 || !Number.isInteger(cw)) {
|
|
910
|
+
addIssue('warning', 'W013', `config.json: context_window should be a positive integer, got "${cw}"`, 'Set to 200000 (default) or 1000000 (for 1M models)');
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
if (configParsed['phase_branch_template'] &&
|
|
914
|
+
!configParsed['phase_branch_template'].includes('{phase}')) {
|
|
915
|
+
addIssue('warning', 'W014', 'config.json: phase_branch_template missing {phase} placeholder', 'Template must include {phase} for phase number substitution');
|
|
916
|
+
}
|
|
917
|
+
if (configParsed['milestone_branch_template'] &&
|
|
918
|
+
!configParsed['milestone_branch_template'].includes('{milestone}')) {
|
|
919
|
+
addIssue('warning', 'W015', 'config.json: milestone_branch_template missing {milestone} placeholder', 'Template must include {milestone} for version substitution');
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
catch {
|
|
923
|
+
/* parse error already caught in Check 5 */
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
try {
|
|
927
|
+
const worktreeHealth = inspectWorktreeHealth(cwd, { staleAfterMs: 60 * 60 * 1000 }, { execGit: shell_command_projection_cjs_1.execGit, existsSync: node_fs_1.default.existsSync, statSync: node_fs_1.default.statSync });
|
|
928
|
+
if (!worktreeHealth['ok']) {
|
|
929
|
+
if (worktreeHealth['reason'] === 'git_timed_out') {
|
|
930
|
+
addIssue('warning', 'W020', 'Worktree health check degraded: git worktree list timed out after 10s — orphan/stale worktrees could not be inspected', 'Run: git worktree list --porcelain to diagnose; check for .git/index.lock or a hung git process');
|
|
931
|
+
}
|
|
932
|
+
if (worktreeHealth['reason'] === 'git_list_failed') {
|
|
933
|
+
addIssue('warning', 'W020', 'Worktree health check degraded: git worktree list failed — orphan/stale worktrees could not be inspected', 'Run: git worktree list --porcelain to diagnose; check git repository state and permissions');
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
else {
|
|
937
|
+
for (const finding of worktreeHealth['findings']) {
|
|
938
|
+
if (finding['kind'] === 'orphan') {
|
|
939
|
+
addIssue('warning', 'W017', `Orphan git worktree: ${finding['path']} (path no longer exists on disk)`, 'Run: git worktree prune');
|
|
940
|
+
continue;
|
|
941
|
+
}
|
|
942
|
+
if (finding['kind'] === 'stale') {
|
|
943
|
+
addIssue('warning', 'W017', `Stale git worktree: ${finding['path']} (last modified ${finding['ageMinutes']} minutes ago)`, `Run: git worktree remove ${finding['path']} --force`);
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
catch {
|
|
949
|
+
/* git worktree not available or not a git repo — skip silently */
|
|
950
|
+
}
|
|
951
|
+
try {
|
|
952
|
+
const phaseConvention = (() => {
|
|
953
|
+
if (!node_fs_1.default.existsSync(configPath))
|
|
954
|
+
return null;
|
|
955
|
+
try {
|
|
956
|
+
const configRaw = node_fs_1.default.readFileSync(configPath, 'utf-8');
|
|
957
|
+
const configParsed = JSON.parse(configRaw);
|
|
958
|
+
return configParsed['phase_id_convention'] || null;
|
|
959
|
+
}
|
|
960
|
+
catch {
|
|
961
|
+
return null;
|
|
962
|
+
}
|
|
963
|
+
})();
|
|
964
|
+
if (phaseConvention === 'milestone-prefixed') {
|
|
965
|
+
if (node_fs_1.default.existsSync(roadmapPath)) {
|
|
966
|
+
const roadmapContent = node_fs_1.default.readFileSync(roadmapPath, 'utf-8');
|
|
967
|
+
const { getMilestoneFromPhaseId } = core;
|
|
968
|
+
const mismatches = checkMilestonePrefixMismatches(roadmapContent, {
|
|
969
|
+
getMilestoneFromPhaseId: getMilestoneFromPhaseId,
|
|
970
|
+
});
|
|
971
|
+
for (const mm of mismatches) {
|
|
972
|
+
addIssue('warning', 'W021', `Phase ${mm.phaseId}: integer prefix implies ${mm.expectedMilestone} but listed under ${mm.foundInMilestone}`, 'Run `gsd-tools roadmap upgrade --convention milestone-prefixed` to migrate (dry-run by default)');
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
catch {
|
|
978
|
+
/* W021 check is advisory — skip on error */
|
|
979
|
+
}
|
|
980
|
+
const milestonesPath = node_path_1.default.join(planBase, 'MILESTONES.md');
|
|
981
|
+
const milestonesArchiveDir = node_path_1.default.join(planBase, 'milestones');
|
|
982
|
+
const missingFromRegistry = [];
|
|
983
|
+
try {
|
|
984
|
+
if (node_fs_1.default.existsSync(milestonesArchiveDir)) {
|
|
985
|
+
const archiveFiles = node_fs_1.default.readdirSync(milestonesArchiveDir);
|
|
986
|
+
const archivedVersions = archiveFiles
|
|
987
|
+
.map((f) => f.match(/^(v\d+\.\d+(?:\.\d+)?)-ROADMAP\.md$/))
|
|
988
|
+
.filter(Boolean)
|
|
989
|
+
.map((m) => m[1]);
|
|
990
|
+
if (archivedVersions.length > 0) {
|
|
991
|
+
const registryContent = node_fs_1.default.existsSync(milestonesPath)
|
|
992
|
+
? node_fs_1.default.readFileSync(milestonesPath, 'utf-8')
|
|
993
|
+
: '';
|
|
994
|
+
for (const ver of archivedVersions) {
|
|
995
|
+
if (!registryContent.includes(`## ${ver}`)) {
|
|
996
|
+
missingFromRegistry.push(ver);
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
if (missingFromRegistry.length > 0) {
|
|
1000
|
+
addIssue('warning', 'W018', `MILESTONES.md missing ${missingFromRegistry.length} archived milestone(s): ${missingFromRegistry.join(', ')}`, `Run ${slash('health')} --backfill to synthesize missing entries from archive snapshots`, true);
|
|
1001
|
+
repairs.push('backfillMilestones');
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
catch {
|
|
1007
|
+
/* intentionally empty — milestone sync check is advisory */
|
|
1008
|
+
}
|
|
1009
|
+
try {
|
|
1010
|
+
const entries = node_fs_1.default.readdirSync(planBase, { withFileTypes: true });
|
|
1011
|
+
for (const entry of entries) {
|
|
1012
|
+
if (!entry.isFile())
|
|
1013
|
+
continue;
|
|
1014
|
+
if (!entry.name.endsWith('.md'))
|
|
1015
|
+
continue;
|
|
1016
|
+
if (!(0, artifacts_cjs_1.isCanonicalPlanningFile)(entry.name)) {
|
|
1017
|
+
addIssue('warning', 'W019', `Unrecognized .planning/ file: ${entry.name} — not a canonical GSD artifact`, 'Move to .planning/milestones/ archive subdir or delete if stale. See templates/README.md for the canonical artifact list.', false);
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
catch {
|
|
1022
|
+
/* artifact check is advisory — skip on error */
|
|
1023
|
+
}
|
|
1024
|
+
try {
|
|
1025
|
+
if (node_fs_1.default.existsSync(statePath) && node_fs_1.default.existsSync(roadmapPath)) {
|
|
1026
|
+
const stateRaw = node_fs_1.default.readFileSync(statePath, 'utf-8');
|
|
1027
|
+
const statusMatch = stateRaw.match(/^status:\s*(.+)/im);
|
|
1028
|
+
const stateStatus = statusMatch ? statusMatch[1].trim().toLowerCase() : '';
|
|
1029
|
+
const isMarkedComplete = /milestone complete|archived/.test(stateStatus);
|
|
1030
|
+
if (isMarkedComplete) {
|
|
1031
|
+
const roadmapRaw = node_fs_1.default.readFileSync(roadmapPath, 'utf-8');
|
|
1032
|
+
const scopedContent = extractCurrentMilestone(roadmapRaw, cwd);
|
|
1033
|
+
const phasePattern = /#{2,4}\s*Phase\s+(\d+[A-Z]?(?:\.\d+)*)\s*:\s*([^\n]+)/gi;
|
|
1034
|
+
const unstarted = [];
|
|
1035
|
+
let pm;
|
|
1036
|
+
// Non-hoisted: load-order matters (circular dep guard)
|
|
1037
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports -- planning-workspace.cjs is an export= CommonJS module
|
|
1038
|
+
const planningWorkspace2 = require('./planning-workspace.cjs');
|
|
1039
|
+
const phasesDir2 = planningWorkspace2.planningPaths(cwd).phases;
|
|
1040
|
+
const phaseDirNames2 = (() => {
|
|
1041
|
+
try {
|
|
1042
|
+
return node_fs_1.default
|
|
1043
|
+
.readdirSync(phasesDir2, { withFileTypes: true })
|
|
1044
|
+
.filter((e) => e.isDirectory())
|
|
1045
|
+
.map((e) => e.name);
|
|
1046
|
+
}
|
|
1047
|
+
catch {
|
|
1048
|
+
return [];
|
|
1049
|
+
}
|
|
1050
|
+
})();
|
|
1051
|
+
while ((pm = phasePattern.exec(scopedContent)) !== null) {
|
|
1052
|
+
const phaseNum = pm[1];
|
|
1053
|
+
const normalizedPh = normalizePhaseName(phaseNum);
|
|
1054
|
+
const hasDirectory = phaseDirNames2.some((d) => phaseTokenMatches(d, normalizedPh));
|
|
1055
|
+
if (!hasDirectory) {
|
|
1056
|
+
unstarted.push(phaseNum);
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1059
|
+
if (unstarted.length > 0) {
|
|
1060
|
+
addIssue('warning', 'W021', `STATE says milestone complete but ROADMAP lists ${unstarted.length} unstarted phase(s) (e.g. Phase ${unstarted[0]})`, 'Run validate consistency or re-run complete-milestone after verifying all phases are done');
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
1063
|
+
}
|
|
1064
|
+
}
|
|
1065
|
+
catch {
|
|
1066
|
+
/* W021 check is advisory — skip on error */
|
|
1067
|
+
}
|
|
1068
|
+
// ─── Perform repairs if requested ─────────────────────────────────────────
|
|
1069
|
+
const repairActions = [];
|
|
1070
|
+
if (options['repair'] && repairs.length > 0) {
|
|
1071
|
+
for (const repair of repairs) {
|
|
1072
|
+
try {
|
|
1073
|
+
switch (repair) {
|
|
1074
|
+
case 'createConfig':
|
|
1075
|
+
case 'resetConfig': {
|
|
1076
|
+
const defaults = {
|
|
1077
|
+
model_profile: CONFIG_DEFAULTS.model_profile,
|
|
1078
|
+
commit_docs: CONFIG_DEFAULTS.commit_docs,
|
|
1079
|
+
search_gitignored: CONFIG_DEFAULTS.search_gitignored,
|
|
1080
|
+
branching_strategy: CONFIG_DEFAULTS.branching_strategy,
|
|
1081
|
+
phase_branch_template: CONFIG_DEFAULTS.phase_branch_template,
|
|
1082
|
+
milestone_branch_template: CONFIG_DEFAULTS.milestone_branch_template,
|
|
1083
|
+
quick_branch_template: CONFIG_DEFAULTS.quick_branch_template,
|
|
1084
|
+
workflow: {
|
|
1085
|
+
research: CONFIG_DEFAULTS.research,
|
|
1086
|
+
plan_check: CONFIG_DEFAULTS.plan_checker,
|
|
1087
|
+
verifier: CONFIG_DEFAULTS.verifier,
|
|
1088
|
+
nyquist_validation: CONFIG_DEFAULTS.nyquist_validation,
|
|
1089
|
+
},
|
|
1090
|
+
parallelization: CONFIG_DEFAULTS.parallelization,
|
|
1091
|
+
brave_search: CONFIG_DEFAULTS.brave_search,
|
|
1092
|
+
};
|
|
1093
|
+
(0, shell_command_projection_cjs_1.platformWriteSync)(configPath, JSON.stringify(defaults, null, 2));
|
|
1094
|
+
repairActions.push({ action: repair, success: true, path: 'config.json' });
|
|
1095
|
+
break;
|
|
1096
|
+
}
|
|
1097
|
+
case 'regenerateState': {
|
|
1098
|
+
if (node_fs_1.default.existsSync(statePath)) {
|
|
1099
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);
|
|
1100
|
+
const backupPath = `${statePath}.bak-${timestamp}`;
|
|
1101
|
+
node_fs_1.default.copyFileSync(statePath, backupPath);
|
|
1102
|
+
repairActions.push({ action: 'backupState', success: true, path: backupPath });
|
|
1103
|
+
}
|
|
1104
|
+
const milestone = getMilestoneInfo(cwd);
|
|
1105
|
+
const projectRef = node_path_1.default
|
|
1106
|
+
.relative(cwd, node_path_1.default.join(planningDir(cwd), 'PROJECT.md'))
|
|
1107
|
+
.split(node_path_1.default.sep)
|
|
1108
|
+
.join('/');
|
|
1109
|
+
let stateContent = `# Session State\n\n`;
|
|
1110
|
+
stateContent += `## Project Reference\n\n`;
|
|
1111
|
+
stateContent += `See: ${projectRef}\n\n`;
|
|
1112
|
+
stateContent += `## Position\n\n`;
|
|
1113
|
+
stateContent += `**Milestone:** ${milestone.version} ${milestone.name}\n`;
|
|
1114
|
+
stateContent += `**Current phase:** (determining...)\n`;
|
|
1115
|
+
stateContent += `**Status:** Resuming\n\n`;
|
|
1116
|
+
stateContent += `## Session Log\n\n`;
|
|
1117
|
+
stateContent += `- ${new Date().toISOString().split('T')[0]}: STATE.md regenerated by ${slash('health')} --repair\n`;
|
|
1118
|
+
writeStateMd(statePath, stateContent, cwd);
|
|
1119
|
+
repairActions.push({ action: repair, success: true, path: 'STATE.md' });
|
|
1120
|
+
break;
|
|
1121
|
+
}
|
|
1122
|
+
case 'addNyquistKey': {
|
|
1123
|
+
if (node_fs_1.default.existsSync(configPath)) {
|
|
1124
|
+
try {
|
|
1125
|
+
const configRaw = node_fs_1.default.readFileSync(configPath, 'utf-8');
|
|
1126
|
+
const configParsed = JSON.parse(configRaw);
|
|
1127
|
+
if (!configParsed['workflow'])
|
|
1128
|
+
configParsed['workflow'] = {};
|
|
1129
|
+
const wf = configParsed['workflow'];
|
|
1130
|
+
if (wf['nyquist_validation'] === undefined) {
|
|
1131
|
+
wf['nyquist_validation'] = true;
|
|
1132
|
+
(0, shell_command_projection_cjs_1.platformWriteSync)(configPath, JSON.stringify(configParsed, null, 2));
|
|
1133
|
+
}
|
|
1134
|
+
repairActions.push({ action: repair, success: true, path: 'config.json' });
|
|
1135
|
+
}
|
|
1136
|
+
catch (err) {
|
|
1137
|
+
repairActions.push({
|
|
1138
|
+
action: repair,
|
|
1139
|
+
success: false,
|
|
1140
|
+
error: err instanceof Error ? err.message : String(err),
|
|
1141
|
+
});
|
|
1142
|
+
}
|
|
1143
|
+
}
|
|
1144
|
+
break;
|
|
1145
|
+
}
|
|
1146
|
+
case 'addAiIntegrationPhaseKey': {
|
|
1147
|
+
if (node_fs_1.default.existsSync(configPath)) {
|
|
1148
|
+
try {
|
|
1149
|
+
const configRaw = node_fs_1.default.readFileSync(configPath, 'utf-8');
|
|
1150
|
+
const configParsed = JSON.parse(configRaw);
|
|
1151
|
+
if (!configParsed['workflow'])
|
|
1152
|
+
configParsed['workflow'] = {};
|
|
1153
|
+
const wf = configParsed['workflow'];
|
|
1154
|
+
if (wf['ai_integration_phase'] === undefined) {
|
|
1155
|
+
wf['ai_integration_phase'] = true;
|
|
1156
|
+
(0, shell_command_projection_cjs_1.platformWriteSync)(configPath, JSON.stringify(configParsed, null, 2));
|
|
1157
|
+
}
|
|
1158
|
+
repairActions.push({ action: repair, success: true, path: 'config.json' });
|
|
1159
|
+
}
|
|
1160
|
+
catch (err) {
|
|
1161
|
+
repairActions.push({
|
|
1162
|
+
action: repair,
|
|
1163
|
+
success: false,
|
|
1164
|
+
error: err instanceof Error ? err.message : String(err),
|
|
1165
|
+
});
|
|
1166
|
+
}
|
|
1167
|
+
}
|
|
1168
|
+
break;
|
|
1169
|
+
}
|
|
1170
|
+
case 'backfillMilestones': {
|
|
1171
|
+
if (!options['backfill'] && !options['repair'])
|
|
1172
|
+
break;
|
|
1173
|
+
const today = new Date().toISOString().split('T')[0];
|
|
1174
|
+
let backfilled = 0;
|
|
1175
|
+
for (const ver of missingFromRegistry) {
|
|
1176
|
+
try {
|
|
1177
|
+
const snapshotPath = node_path_1.default.join(milestonesArchiveDir, `${ver}-ROADMAP.md`);
|
|
1178
|
+
const snapshot = (0, shell_command_projection_cjs_1.platformReadSync)(snapshotPath);
|
|
1179
|
+
const titleMatch = snapshot && snapshot.match(/^#\s+(.+)$/m);
|
|
1180
|
+
const milestoneName = titleMatch
|
|
1181
|
+
? titleMatch[1].replace(/^Milestone\s+/i, '').replace(/^v[\d.]+\s*/, '').trim()
|
|
1182
|
+
: ver;
|
|
1183
|
+
const entry = `## ${ver}${milestoneName && milestoneName !== ver ? ` ${milestoneName}` : ''} (Backfilled: ${today})\n\n**Note:** Synthesized from archive snapshot by \`${slash('health')} --backfill\`. Original completion date unknown.\n\n---\n\n`;
|
|
1184
|
+
const milestonesContent = node_fs_1.default.existsSync(milestonesPath)
|
|
1185
|
+
? node_fs_1.default.readFileSync(milestonesPath, 'utf-8')
|
|
1186
|
+
: '';
|
|
1187
|
+
if (!milestonesContent.trim()) {
|
|
1188
|
+
(0, shell_command_projection_cjs_1.platformWriteSync)(milestonesPath, `# Milestones\n\n${entry}`);
|
|
1189
|
+
}
|
|
1190
|
+
else {
|
|
1191
|
+
const headerMatch = milestonesContent.match(/^(#{1,3}\s+[^\n]*\n\n?)/);
|
|
1192
|
+
if (headerMatch) {
|
|
1193
|
+
const header = headerMatch[1];
|
|
1194
|
+
const rest = milestonesContent.slice(header.length);
|
|
1195
|
+
(0, shell_command_projection_cjs_1.platformWriteSync)(milestonesPath, header + entry + rest);
|
|
1196
|
+
}
|
|
1197
|
+
else {
|
|
1198
|
+
(0, shell_command_projection_cjs_1.platformWriteSync)(milestonesPath, entry + milestonesContent);
|
|
1199
|
+
}
|
|
1200
|
+
}
|
|
1201
|
+
backfilled++;
|
|
1202
|
+
}
|
|
1203
|
+
catch {
|
|
1204
|
+
/* intentionally empty — partial backfill is acceptable */
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1207
|
+
repairActions.push({
|
|
1208
|
+
action: repair,
|
|
1209
|
+
success: true,
|
|
1210
|
+
detail: `Backfilled ${backfilled} milestone(s) into MILESTONES.md`,
|
|
1211
|
+
});
|
|
1212
|
+
break;
|
|
1213
|
+
}
|
|
1214
|
+
}
|
|
1215
|
+
}
|
|
1216
|
+
catch (err) {
|
|
1217
|
+
repairActions.push({
|
|
1218
|
+
action: repair,
|
|
1219
|
+
success: false,
|
|
1220
|
+
error: err instanceof Error ? err.message : String(err),
|
|
1221
|
+
});
|
|
1222
|
+
}
|
|
1223
|
+
}
|
|
1224
|
+
}
|
|
1225
|
+
let status;
|
|
1226
|
+
if (errors.length > 0) {
|
|
1227
|
+
status = 'broken';
|
|
1228
|
+
}
|
|
1229
|
+
else if (warnings.length > 0) {
|
|
1230
|
+
status = 'degraded';
|
|
1231
|
+
}
|
|
1232
|
+
else {
|
|
1233
|
+
status = 'healthy';
|
|
1234
|
+
}
|
|
1235
|
+
const repairableCount = errors.filter((e) => e.repairable).length + warnings.filter((w) => w.repairable).length;
|
|
1236
|
+
const result = {
|
|
1237
|
+
status,
|
|
1238
|
+
errors,
|
|
1239
|
+
warnings,
|
|
1240
|
+
info,
|
|
1241
|
+
repairable_count: repairableCount,
|
|
1242
|
+
repairs_performed: repairActions.length > 0 ? repairActions : undefined,
|
|
1243
|
+
};
|
|
1244
|
+
output(result, raw);
|
|
1245
|
+
return result;
|
|
1246
|
+
}
|
|
1247
|
+
function cmdValidateAgents(cwd, raw) {
|
|
1248
|
+
const agentStatus = checkAgentsInstalled();
|
|
1249
|
+
const expected = Object.keys(MODEL_PROFILES);
|
|
1250
|
+
output({
|
|
1251
|
+
agents_dir: agentStatus.agents_dir,
|
|
1252
|
+
agents_found: agentStatus.agents_installed,
|
|
1253
|
+
installed: agentStatus.installed_agents,
|
|
1254
|
+
missing: agentStatus.missing_agents,
|
|
1255
|
+
expected,
|
|
1256
|
+
}, raw);
|
|
1257
|
+
}
|
|
1258
|
+
function cmdVerifySchemaDrift(cwd, phaseArg, skipFlag, raw) {
|
|
1259
|
+
if (!phaseArg) {
|
|
1260
|
+
error('Usage: verify schema-drift <phase> [--skip]');
|
|
1261
|
+
return;
|
|
1262
|
+
}
|
|
1263
|
+
const pDir = planningDir(cwd);
|
|
1264
|
+
const phasesDir = node_path_1.default.join(pDir, 'phases');
|
|
1265
|
+
if (!node_fs_1.default.existsSync(phasesDir)) {
|
|
1266
|
+
output({ drift_detected: false, blocking: false, message: 'No phases directory' }, raw);
|
|
1267
|
+
return;
|
|
1268
|
+
}
|
|
1269
|
+
let phaseDir = null;
|
|
1270
|
+
const entries = node_fs_1.default.readdirSync(phasesDir, { withFileTypes: true });
|
|
1271
|
+
for (const entry of entries) {
|
|
1272
|
+
if (entry.isDirectory() && entry.name.includes(phaseArg)) {
|
|
1273
|
+
phaseDir = node_path_1.default.join(phasesDir, entry.name);
|
|
1274
|
+
break;
|
|
1275
|
+
}
|
|
1276
|
+
}
|
|
1277
|
+
if (!phaseDir) {
|
|
1278
|
+
const exact = node_path_1.default.join(phasesDir, phaseArg);
|
|
1279
|
+
if (node_fs_1.default.existsSync(exact))
|
|
1280
|
+
phaseDir = exact;
|
|
1281
|
+
}
|
|
1282
|
+
if (!phaseDir) {
|
|
1283
|
+
output({ drift_detected: false, blocking: false, message: `Phase directory not found: ${phaseArg}` }, raw);
|
|
1284
|
+
return;
|
|
1285
|
+
}
|
|
1286
|
+
const allFiles = [];
|
|
1287
|
+
const planFiles = node_fs_1.default.readdirSync(phaseDir).filter((f) => f.endsWith('-PLAN.md'));
|
|
1288
|
+
for (const pf of planFiles) {
|
|
1289
|
+
const content = node_fs_1.default.readFileSync(node_path_1.default.join(phaseDir, pf), 'utf-8');
|
|
1290
|
+
const fmMatch = content.match(/files_modified:\s*\[([^\]]*)\]/);
|
|
1291
|
+
if (fmMatch) {
|
|
1292
|
+
const files = fmMatch[1].split(',').map((f) => f.trim()).filter(Boolean);
|
|
1293
|
+
allFiles.push(...files);
|
|
1294
|
+
}
|
|
1295
|
+
}
|
|
1296
|
+
let executionLog = '';
|
|
1297
|
+
const summaryFiles = node_fs_1.default.readdirSync(phaseDir).filter((f) => f.endsWith('-SUMMARY.md'));
|
|
1298
|
+
for (const sf of summaryFiles) {
|
|
1299
|
+
executionLog += node_fs_1.default.readFileSync(node_path_1.default.join(phaseDir, sf), 'utf-8') + '\n';
|
|
1300
|
+
}
|
|
1301
|
+
const gitLog = (0, shell_command_projection_cjs_1.execGit)(['log', '--oneline', '--all', '-50'], { cwd });
|
|
1302
|
+
if (gitLog.exitCode === 0) {
|
|
1303
|
+
executionLog += '\n' + gitLog.stdout;
|
|
1304
|
+
}
|
|
1305
|
+
const result = (0, schema_detect_cjs_1.checkSchemaDrift)(allFiles, executionLog, { skipCheck: !!skipFlag });
|
|
1306
|
+
output({
|
|
1307
|
+
drift_detected: result['driftDetected'],
|
|
1308
|
+
blocking: result['blocking'],
|
|
1309
|
+
schema_files: result['schemaFiles'],
|
|
1310
|
+
orms: result['orms'],
|
|
1311
|
+
unpushed_orms: result['unpushedOrms'],
|
|
1312
|
+
message: result['message'],
|
|
1313
|
+
skipped: result['skipped'] || false,
|
|
1314
|
+
}, raw);
|
|
1315
|
+
}
|
|
1316
|
+
function cmdVerifyCodebaseDrift(cwd, raw) {
|
|
1317
|
+
// Non-hoisted: load-order matters for circular dep guard
|
|
1318
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports -- drift.cjs is an export= CommonJS module
|
|
1319
|
+
const drift = require('./drift.cjs');
|
|
1320
|
+
const emit = (payload) => output(payload, raw);
|
|
1321
|
+
try {
|
|
1322
|
+
const codebaseDir = node_path_1.default.join(planningDir(cwd), 'codebase');
|
|
1323
|
+
const structurePath = node_path_1.default.join(codebaseDir, 'STRUCTURE.md');
|
|
1324
|
+
if (!node_fs_1.default.existsSync(structurePath)) {
|
|
1325
|
+
emit({
|
|
1326
|
+
skipped: true,
|
|
1327
|
+
reason: 'no-structure-md',
|
|
1328
|
+
action_required: false,
|
|
1329
|
+
directive: 'none',
|
|
1330
|
+
elements: [],
|
|
1331
|
+
});
|
|
1332
|
+
return;
|
|
1333
|
+
}
|
|
1334
|
+
let structureMd;
|
|
1335
|
+
try {
|
|
1336
|
+
structureMd = node_fs_1.default.readFileSync(structurePath, 'utf-8');
|
|
1337
|
+
}
|
|
1338
|
+
catch (err) {
|
|
1339
|
+
emit({
|
|
1340
|
+
skipped: true,
|
|
1341
|
+
reason: 'cannot-read-structure-md: ' + (err instanceof Error ? err.message : String(err)),
|
|
1342
|
+
action_required: false,
|
|
1343
|
+
directive: 'none',
|
|
1344
|
+
elements: [],
|
|
1345
|
+
});
|
|
1346
|
+
return;
|
|
1347
|
+
}
|
|
1348
|
+
const lastMapped = drift['readMappedCommit'](structurePath);
|
|
1349
|
+
const revProbe = (0, shell_command_projection_cjs_1.execGit)(['rev-parse', 'HEAD'], { cwd });
|
|
1350
|
+
if (revProbe.exitCode !== 0) {
|
|
1351
|
+
emit({
|
|
1352
|
+
skipped: true,
|
|
1353
|
+
reason: 'not-a-git-repo',
|
|
1354
|
+
action_required: false,
|
|
1355
|
+
directive: 'none',
|
|
1356
|
+
elements: [],
|
|
1357
|
+
});
|
|
1358
|
+
return;
|
|
1359
|
+
}
|
|
1360
|
+
const EMPTY_TREE = '4b825dc642cb6eb9a060e54bf8d69288fbee4904';
|
|
1361
|
+
let base = lastMapped;
|
|
1362
|
+
if (!base) {
|
|
1363
|
+
base = EMPTY_TREE;
|
|
1364
|
+
}
|
|
1365
|
+
else {
|
|
1366
|
+
const verify = (0, shell_command_projection_cjs_1.execGit)(['cat-file', '-t', base], { cwd });
|
|
1367
|
+
if (verify.exitCode !== 0)
|
|
1368
|
+
base = EMPTY_TREE;
|
|
1369
|
+
}
|
|
1370
|
+
const diff = (0, shell_command_projection_cjs_1.execGit)(['diff', '--name-status', base, 'HEAD'], { cwd });
|
|
1371
|
+
if (diff.exitCode !== 0) {
|
|
1372
|
+
emit({
|
|
1373
|
+
skipped: true,
|
|
1374
|
+
reason: 'git-diff-failed',
|
|
1375
|
+
action_required: false,
|
|
1376
|
+
directive: 'none',
|
|
1377
|
+
elements: [],
|
|
1378
|
+
});
|
|
1379
|
+
return;
|
|
1380
|
+
}
|
|
1381
|
+
const added = [];
|
|
1382
|
+
const modified = [];
|
|
1383
|
+
const deleted = [];
|
|
1384
|
+
for (const line of diff.stdout.split(/\r?\n/)) {
|
|
1385
|
+
if (!line.trim())
|
|
1386
|
+
continue;
|
|
1387
|
+
const m = line.match(/^([A-Z])\d*\t(.+?)(?:\t(.+))?$/);
|
|
1388
|
+
if (!m)
|
|
1389
|
+
continue;
|
|
1390
|
+
const status = m[1];
|
|
1391
|
+
const file = m[3] || m[2];
|
|
1392
|
+
if (status === 'A' || status === 'R' || status === 'C')
|
|
1393
|
+
added.push(file);
|
|
1394
|
+
else if (status === 'M')
|
|
1395
|
+
modified.push(file);
|
|
1396
|
+
else if (status === 'D')
|
|
1397
|
+
deleted.push(file);
|
|
1398
|
+
}
|
|
1399
|
+
const config = loadConfig(cwd);
|
|
1400
|
+
const wf = config?.workflow;
|
|
1401
|
+
const threshold = Number.isInteger(wf?.drift_threshold) && wf?.drift_threshold >= 1
|
|
1402
|
+
? wf?.drift_threshold
|
|
1403
|
+
: 3;
|
|
1404
|
+
const action = wf?.drift_action === 'auto-remap' ? 'auto-remap' : 'warn';
|
|
1405
|
+
const driftResult = drift['detectDrift']({
|
|
1406
|
+
addedFiles: added,
|
|
1407
|
+
modifiedFiles: modified,
|
|
1408
|
+
deletedFiles: deleted,
|
|
1409
|
+
structureMd,
|
|
1410
|
+
threshold,
|
|
1411
|
+
action,
|
|
1412
|
+
runtime: (0, runtime_slash_cjs_1.resolveRuntime)(cwd),
|
|
1413
|
+
});
|
|
1414
|
+
emit({
|
|
1415
|
+
skipped: !!driftResult['skipped'],
|
|
1416
|
+
reason: driftResult['reason'] || null,
|
|
1417
|
+
action_required: !!driftResult['actionRequired'],
|
|
1418
|
+
directive: driftResult['directive'],
|
|
1419
|
+
spawn_mapper: !!driftResult['spawnMapper'],
|
|
1420
|
+
affected_paths: driftResult['affectedPaths'] || [],
|
|
1421
|
+
elements: driftResult['elements'] || [],
|
|
1422
|
+
threshold,
|
|
1423
|
+
action,
|
|
1424
|
+
last_mapped_commit: lastMapped,
|
|
1425
|
+
message: driftResult['message'] || '',
|
|
1426
|
+
});
|
|
1427
|
+
}
|
|
1428
|
+
catch (err) {
|
|
1429
|
+
emit({
|
|
1430
|
+
skipped: true,
|
|
1431
|
+
reason: 'exception: ' + (err && err instanceof Error ? err.message : String(err)),
|
|
1432
|
+
action_required: false,
|
|
1433
|
+
directive: 'none',
|
|
1434
|
+
elements: [],
|
|
1435
|
+
});
|
|
1436
|
+
}
|
|
1437
|
+
}
|
|
1438
|
+
module.exports = {
|
|
1439
|
+
cmdVerifySummary,
|
|
1440
|
+
cmdVerifyPlanStructure,
|
|
1441
|
+
cmdVerifyPhaseCompleteness,
|
|
1442
|
+
cmdVerifyReferences,
|
|
1443
|
+
cmdVerifyCommits,
|
|
1444
|
+
cmdVerifyArtifacts,
|
|
1445
|
+
cmdVerifyKeyLinks,
|
|
1446
|
+
cmdValidateConsistency,
|
|
1447
|
+
cmdValidateHealth,
|
|
1448
|
+
cmdValidateAgents,
|
|
1449
|
+
cmdVerifySchemaDrift,
|
|
1450
|
+
cmdVerifyCodebaseDrift,
|
|
1451
|
+
};
|