@opengsd/gsd-core 1.13.0 → 1.15.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/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/README.ja-JP.md +3 -3
- package/README.ko-KR.md +3 -3
- package/README.pt-BR.md +3 -3
- package/README.zh-CN.md +3 -3
- package/agents/gsd-advisor-researcher.compact.md +85 -0
- package/agents/gsd-ai-researcher.compact.md +96 -0
- package/agents/gsd-assumptions-analyzer.compact.md +81 -0
- package/agents/gsd-code-fixer.compact.md +459 -0
- package/agents/gsd-code-fixer.md +9 -8
- package/agents/gsd-code-reviewer.compact.md +269 -0
- package/agents/gsd-code-reviewer.md +15 -3
- package/agents/gsd-codebase-mapper.compact.md +760 -0
- package/agents/gsd-debug-session-manager.compact.md +360 -0
- package/agents/gsd-debug-session-manager.md +17 -2
- package/agents/gsd-debugger.md +2 -2
- package/agents/gsd-doc-classifier.compact.md +192 -0
- package/agents/gsd-doc-synthesizer.compact.md +200 -0
- package/agents/gsd-doc-verifier.compact.md +143 -0
- package/agents/gsd-doc-writer.compact.md +440 -0
- package/agents/gsd-dom-verifier.compact.md +138 -0
- package/agents/gsd-domain-researcher.compact.md +141 -0
- package/agents/gsd-eval-auditor.compact.md +160 -0
- package/agents/gsd-eval-auditor.md +1 -1
- package/agents/gsd-eval-planner.compact.md +137 -0
- package/agents/gsd-executor.md +13 -8
- package/agents/gsd-framework-selector.compact.md +82 -0
- package/agents/gsd-integration-checker.compact.md +245 -0
- package/agents/gsd-intel-updater.compact.md +226 -0
- package/agents/gsd-intel-updater.md +1 -1
- package/agents/gsd-mempalace-curator.compact.md +45 -0
- package/agents/gsd-nyquist-auditor.compact.md +179 -0
- package/agents/gsd-pattern-mapper.compact.md +275 -0
- package/agents/gsd-phase-researcher.md +19 -11
- package/agents/gsd-plan-checker.md +8 -7
- package/agents/gsd-planner.md +12 -8
- package/agents/gsd-project-researcher.compact.md +587 -0
- package/agents/gsd-project-researcher.md +1 -1
- package/agents/gsd-research-synthesizer.compact.md +212 -0
- package/agents/gsd-research-synthesizer.md +1 -1
- package/agents/gsd-roadmapper.compact.md +454 -0
- package/agents/gsd-roadmapper.md +13 -0
- package/agents/gsd-security-auditor.compact.md +162 -0
- package/agents/gsd-ui-auditor.compact.md +404 -0
- package/agents/gsd-ui-auditor.md +155 -17
- package/agents/gsd-ui-checker.compact.md +277 -0
- package/agents/gsd-ui-researcher.compact.md +282 -0
- package/agents/gsd-ui-researcher.md +1 -1
- package/agents/gsd-user-profiler.compact.md +108 -0
- package/agents/gsd-verifier.md +10 -9
- package/bin/install.js +848 -163
- package/commands/gsd/autonomous.md +2 -2
- package/commands/gsd/capture.md +1 -1
- package/commands/gsd/cleanup.md +1 -0
- package/commands/gsd/code-review.md +2 -1
- package/commands/gsd/complete-milestone.md +1 -0
- package/commands/gsd/config.md +1 -0
- package/commands/gsd/debug.md +1 -0
- package/commands/gsd/graphify.md +1 -0
- package/commands/gsd/health.md +1 -0
- package/commands/gsd/mempalace-capture.md +8 -3
- package/commands/gsd/mempalace-recall.md +1 -0
- package/commands/gsd/new-milestone.md +1 -0
- package/commands/gsd/new-project.md +1 -0
- package/commands/gsd/next.md +1 -0
- package/commands/gsd/pause-work.md +1 -0
- package/commands/gsd/phase.md +1 -0
- package/commands/gsd/plan-review-convergence.md +6 -6
- package/commands/gsd/pr-branch.md +1 -0
- package/commands/gsd/progress.md +1 -1
- package/commands/gsd/quick-batch.md +1 -1
- package/commands/gsd/resume-work.md +1 -0
- package/commands/gsd/review-backlog.md +1 -0
- package/commands/gsd/review.md +2 -3
- package/commands/gsd/settings.md +2 -1
- package/commands/gsd/stats.md +1 -0
- package/commands/gsd/thread.md +1 -0
- package/commands/gsd/workspace.md +1 -0
- package/commands/gsd/workstreams.md +1 -0
- package/gsd-core/bin/check-latest-version.cjs +8 -3
- package/gsd-core/bin/gsd-tools.cjs +672 -146
- package/gsd-core/bin/lib/adr-parser.cjs +4 -2
- package/gsd-core/bin/lib/artifacts.cjs +2 -1
- package/gsd-core/bin/lib/audit.cjs +119 -34
- package/gsd-core/bin/lib/broken-windows.cjs +168 -49
- package/gsd-core/bin/lib/capability-lifecycle.cjs +10 -6
- package/gsd-core/bin/lib/capability-loader.cjs +135 -1
- package/gsd-core/bin/lib/capability-registry.cjs +96 -189
- package/gsd-core/bin/lib/capability-source.cjs +19 -2
- package/gsd-core/bin/lib/capability-validator.cjs +14 -2
- package/gsd-core/bin/lib/check-command-router.cjs +213 -49
- package/gsd-core/bin/lib/code-review-depth.cjs +2 -2
- package/gsd-core/bin/lib/codex-agent-toml.cjs +21 -25
- package/gsd-core/bin/lib/commands.cjs +823 -112
- package/gsd-core/bin/lib/config-loader.cjs +66 -4
- package/gsd-core/bin/lib/config.cjs +186 -45
- package/gsd-core/bin/lib/coverage.cjs +1 -1
- package/gsd-core/bin/lib/decisions.cjs +164 -45
- package/gsd-core/bin/lib/external-descriptor-trust.cjs +29 -14
- package/gsd-core/bin/lib/frontmatter.cjs +13 -0
- package/gsd-core/bin/lib/graphify.cjs +10 -2
- package/gsd-core/bin/lib/gsd2-import.cjs +1 -2
- package/gsd-core/bin/lib/health-diagnostic-rules/state-consistency.cjs +12 -1
- package/gsd-core/bin/lib/health-diagnostic-rules/worktree-health.cjs +1 -1
- package/gsd-core/bin/lib/host-runtime-detection.cjs +9 -0
- package/gsd-core/bin/lib/init.cjs +614 -86
- package/gsd-core/bin/lib/install-engine.cjs +29 -3
- package/gsd-core/bin/lib/install-profiles.cjs +14 -0
- package/gsd-core/bin/lib/installer-migrations.cjs +41 -5
- package/gsd-core/bin/lib/loop-resolver.cjs +50 -31
- package/gsd-core/bin/lib/mcp-catalog.cjs +2 -2
- package/gsd-core/bin/lib/milestone.cjs +37 -13
- package/gsd-core/bin/lib/model-resolver.cjs +253 -53
- package/gsd-core/bin/lib/phase-command-router.cjs +16 -2
- package/gsd-core/bin/lib/phase-id-card.cjs +32 -0
- package/gsd-core/bin/lib/phase-id-display.cjs +78 -0
- package/gsd-core/bin/lib/phase-id.cjs +268 -27
- package/gsd-core/bin/lib/phase-lifecycle.cjs +61 -0
- package/gsd-core/bin/lib/phase-locator.cjs +29 -10
- package/gsd-core/bin/lib/phase.cjs +393 -88
- package/gsd-core/bin/lib/plan-document.cjs +49 -1
- package/gsd-core/bin/lib/planning-document.cjs +459 -0
- package/gsd-core/bin/lib/planning-inspect.cjs +52 -19
- package/gsd-core/bin/lib/planning-snapshot.cjs +61 -12
- package/gsd-core/bin/lib/planning-workspace.cjs +57 -3
- package/gsd-core/bin/lib/pr-branch-patterns.cjs +57 -0
- package/gsd-core/bin/lib/pristine-baseline.cjs +182 -0
- package/gsd-core/bin/lib/probe-core.cjs +7 -1
- package/gsd-core/bin/lib/prohibition-enforcement.cjs +91 -4
- package/gsd-core/bin/lib/project-root.cjs +41 -2
- package/gsd-core/bin/lib/quick-batch.cjs +1 -1
- package/gsd-core/bin/lib/refactor-trigger-command-router.cjs +61 -2
- package/gsd-core/bin/lib/research-store.cjs +11 -12
- package/gsd-core/bin/lib/review-lane-descriptor.cjs +10 -30
- package/gsd-core/bin/lib/review-lane-invocation.cjs +23 -0
- package/gsd-core/bin/lib/review-reviewer-selection.cjs +2 -2
- package/gsd-core/bin/lib/reviewer-step-dispatch.cjs +337 -0
- package/gsd-core/bin/lib/roadmap-command-router.cjs +12 -4
- package/gsd-core/bin/lib/roadmap-parser.cjs +219 -18
- package/gsd-core/bin/lib/roadmap-upgrade.cjs +1539 -13
- package/gsd-core/bin/lib/roadmap.cjs +356 -42
- package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +310 -41
- package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +15 -4
- package/gsd-core/bin/lib/runtime-artifact-layout.cjs +13 -5
- package/gsd-core/bin/lib/runtime-homes.cjs +4 -0
- package/gsd-core/bin/lib/runtime-hooks-surface.cjs +408 -37
- package/gsd-core/bin/lib/runtime-name-policy.cjs +111 -1
- package/gsd-core/bin/lib/security.cjs +126 -7
- package/gsd-core/bin/lib/shell-command-projection.cjs +10 -6
- package/gsd-core/bin/lib/state-document.cjs +130 -28
- package/gsd-core/bin/lib/state-md-schema.cjs +21 -14
- package/gsd-core/bin/lib/state-transition.cjs +181 -30
- package/gsd-core/bin/lib/state.cjs +265 -27
- package/gsd-core/bin/lib/surface.cjs +77 -3
- package/gsd-core/bin/lib/task-command-router.cjs +12 -6
- package/gsd-core/bin/lib/tdd-red-evidence.cjs +78 -5
- package/gsd-core/bin/lib/uat-predicate.cjs +47 -4
- package/gsd-core/bin/lib/uat.cjs +9 -1
- package/gsd-core/bin/lib/ui-consideration-probe.cjs +15 -2
- package/gsd-core/bin/lib/ui-frontend-evidence.cjs +100 -9
- package/gsd-core/bin/lib/undo-commit-selection.cjs +131 -0
- package/gsd-core/bin/lib/update-context.cjs +30 -24
- package/gsd-core/bin/lib/vendor/js-yaml.cjs +11 -3
- package/gsd-core/bin/lib/verification.cjs +315 -30
- package/gsd-core/bin/lib/verify-command-grounding.cjs +47 -3
- package/gsd-core/bin/lib/verify.cjs +320 -48
- package/gsd-core/bin/lib/workstream-inventory.cjs +1 -0
- package/gsd-core/bin/lib/worktree-base-ref.cjs +482 -73
- package/gsd-core/bin/lib/worktree-safety.cjs +797 -58
- package/gsd-core/bin/shared/config-defaults.manifest.json +4 -0
- package/gsd-core/bin/shared/config-schema.manifest.json +6 -0
- package/gsd-core/bin/verify-reapply-patches.cjs +439 -80
- package/gsd-core/references/checkpoints.md +5 -3
- package/gsd-core/references/compact-content-gate.md +66 -0
- package/gsd-core/references/edge-probe-fixtures/01-round-half-even/expected-coverage.json +28 -3
- package/gsd-core/references/edge-probe-fixtures/02-merge-intervals/expected-coverage.json +37 -4
- package/gsd-core/references/edge-probe-fixtures/03-truncate-graphemes/expected-coverage.json +28 -3
- package/gsd-core/references/edge-probe-fixtures/04-money-rounding/expected-coverage.json +28 -3
- package/gsd-core/references/edge-probe-fixtures/05-list-dedupe/expected-coverage.json +37 -4
- package/gsd-core/references/edge-probe-fixtures/06-resolved-mixed/expected-coverage.json +37 -4
- package/gsd-core/references/edge-probe.md +195 -21
- package/gsd-core/references/execute-phase-between-wave-reset.md +7 -6
- package/gsd-core/references/execute-phase-wave-guard.md +22 -11
- package/gsd-core/references/gsd-run-resolver.md +1 -1
- package/gsd-core/references/loop-hook-dispatch.md +18 -0
- package/gsd-core/references/model-profiles.md +13 -4
- package/gsd-core/references/phase-argument-parsing.md +9 -7
- package/gsd-core/references/phase-id-convention.md +28 -0
- package/gsd-core/references/planner-gap-closure.md +2 -0
- package/gsd-core/references/planner-load-graph-context.md +24 -13
- package/gsd-core/references/planner-verify-command-grounding.md +14 -0
- package/gsd-core/references/planning-config.md +14 -2
- package/gsd-core/references/tdd.md +30 -4
- package/gsd-core/references/thinking-models-planning.md +18 -2
- package/gsd-core/references/ui-consideration-probe.md +10 -5
- package/gsd-core/references/verification-patterns.md +17 -4
- package/gsd-core/references/verify-command-path-resolvability.md +10 -2
- package/gsd-core/references/worktree-path-safety.md +433 -2
- package/gsd-core/templates/README.md +7 -1
- package/gsd-core/templates/state.md +6 -3
- package/gsd-core/templates/summary.compact.md +212 -0
- package/gsd-core/templates/user-setup.compact.md +199 -0
- package/gsd-core/templates/user-setup.md +0 -9
- package/gsd-core/templates/verification-report.md +1 -1
- package/gsd-core/workflows/_runtime-launcher.snippet.sh +1 -1
- package/gsd-core/workflows/add-backlog.md +1 -1
- package/gsd-core/workflows/add-phase.md +1 -1
- package/gsd-core/workflows/add-tests.md +2 -2
- package/gsd-core/workflows/add-todo.md +6 -5
- package/gsd-core/workflows/ai-integration-phase.md +11 -3
- package/gsd-core/workflows/audit-fix.md +1 -1
- package/gsd-core/workflows/audit-milestone.md +1 -1
- package/gsd-core/workflows/audit-uat.md +1 -1
- package/gsd-core/workflows/autonomous/steps/converge-fail-fast.md +9 -18
- package/gsd-core/workflows/autonomous.md +29 -16
- package/gsd-core/workflows/check-todos.md +6 -4
- package/gsd-core/workflows/cleanup.md +5 -3
- package/gsd-core/workflows/code-review/steps/dispatch-fix.md +4 -3
- package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +8 -1
- package/gsd-core/workflows/code-review-fix.md +108 -22
- package/gsd-core/workflows/code-review.md +216 -73
- package/gsd-core/workflows/complete-milestone/detail/elaboration.md +274 -0
- package/gsd-core/workflows/complete-milestone.md +41 -264
- package/gsd-core/workflows/debug.md +3 -3
- package/gsd-core/workflows/diagnose-issues.md +1 -1
- package/gsd-core/workflows/discuss-phase/modes/advisor.md +1 -1
- package/gsd-core/workflows/discuss-phase/modes/chain.md +1 -1
- package/gsd-core/workflows/discuss-phase-assumptions.md +1 -1
- package/gsd-core/workflows/discuss-phase.md +1 -1
- package/gsd-core/workflows/do.md +2 -2
- package/gsd-core/workflows/docs-update/detail/elaboration.md +179 -0
- package/gsd-core/workflows/docs-update.md +17 -158
- package/gsd-core/workflows/edit-phase.md +1 -1
- package/gsd-core/workflows/eval-review.md +10 -3
- package/gsd-core/workflows/execute-phase/detail/elaboration.md +124 -0
- package/gsd-core/workflows/execute-phase/steps/code-review-disposition.md +1017 -0
- package/gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md +19 -4
- package/gsd-core/workflows/execute-phase/steps/completion-reconciliation.md +56 -0
- package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +43 -4
- package/gsd-core/workflows/execute-phase/steps/executor-progress-policy.md +43 -0
- package/gsd-core/workflows/execute-phase/steps/gap-closure-artifacts.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/partial-wave.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/per-plan-executor-routing.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/post-merge-gate.md +46 -9
- package/gsd-core/workflows/execute-phase/steps/protected-branch.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/ready-wave-gate.md +37 -0
- package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/sequential-root-pin.md +35 -0
- package/gsd-core/workflows/execute-phase/steps/stale-reverification.md +24 -0
- package/gsd-core/workflows/execute-phase/steps/tdd-applicability-resolution.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/threat-id-gate.md +28 -0
- package/gsd-core/workflows/execute-phase/steps/wave-post-gate-hooks.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/worktree-base-check.md +25 -0
- package/gsd-core/workflows/execute-phase.md +83 -172
- package/gsd-core/workflows/execute-plan.md +24 -10
- package/gsd-core/workflows/explore.md +3 -3
- package/gsd-core/workflows/extract-learnings.md +2 -1
- package/gsd-core/workflows/fast.md +1 -1
- package/gsd-core/workflows/forensics.md +1 -1
- package/gsd-core/workflows/graduation.md +1 -1
- package/gsd-core/workflows/health.md +2 -2
- package/gsd-core/workflows/help/modes/full.compact.md +398 -0
- package/gsd-core/workflows/help/modes/full.md +5 -5
- package/gsd-core/workflows/help/modes/topic.md +15 -5
- package/gsd-core/workflows/help.md +1 -1
- package/gsd-core/workflows/import.md +2 -2
- package/gsd-core/workflows/inbox.md +2 -2
- package/gsd-core/workflows/ingest-docs.md +3 -3
- package/gsd-core/workflows/insert-phase.md +1 -1
- package/gsd-core/workflows/list-seeds.md +1 -1
- package/gsd-core/workflows/list-workspaces.md +1 -1
- package/gsd-core/workflows/manager.md +2 -2
- package/gsd-core/workflows/map-codebase.md +52 -5
- package/gsd-core/workflows/milestone-summary.md +1 -1
- package/gsd-core/workflows/mvp-phase.md +1 -1
- package/gsd-core/workflows/new-milestone.md +56 -14
- package/gsd-core/workflows/new-project/detail/elaboration.md +216 -0
- package/gsd-core/workflows/new-project/steps/auto-mode-config.md +3 -3
- package/gsd-core/workflows/new-project/steps/codebase-map-offer.md +1 -1
- package/gsd-core/workflows/new-project.md +39 -209
- package/gsd-core/workflows/new-workspace.md +2 -2
- package/gsd-core/workflows/next.md +1 -1
- package/gsd-core/workflows/note.md +1 -1
- package/gsd-core/workflows/onboard.md +1 -1
- package/gsd-core/workflows/pause-work.md +1 -1
- package/gsd-core/workflows/plan-phase/detail/elaboration.md +209 -0
- package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +17 -5
- package/gsd-core/workflows/plan-phase/steps/prd-express-path.md +1 -1
- package/gsd-core/workflows/plan-phase/steps/stall-detection-helpers.md +23 -5
- package/gsd-core/workflows/plan-phase.md +45 -187
- package/gsd-core/workflows/plan-review-convergence.md +21 -5
- package/gsd-core/workflows/plant-seed.md +62 -20
- package/gsd-core/workflows/pr-branch.md +132 -20
- package/gsd-core/workflows/profile-user.md +2 -2
- package/gsd-core/workflows/progress.md +1 -1
- package/gsd-core/workflows/quick/steps/plan-checker-loop.md +25 -0
- package/gsd-core/workflows/quick/steps/quick-verification.md +1 -1
- package/gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md +1 -1
- package/gsd-core/workflows/quick-batch/steps/batch-init.md +1 -1
- package/gsd-core/workflows/quick-batch/steps/completion.md +1 -1
- package/gsd-core/workflows/quick-batch/steps/merge-wave.md +1 -1
- package/gsd-core/workflows/quick-batch/steps/planner-wave.md +1 -1
- package/gsd-core/workflows/quick-batch/steps/research-phase.md +1 -1
- package/gsd-core/workflows/quick-batch/steps/resume-mode.md +1 -1
- package/gsd-core/workflows/quick-batch/steps/verification-wave.md +1 -1
- package/gsd-core/workflows/quick-batch/steps/worktree-dispatch.md +1 -1
- package/gsd-core/workflows/quick-batch.md +1 -1
- package/gsd-core/workflows/quick.md +29 -10
- package/gsd-core/workflows/reapply-patches.md +86 -6
- package/gsd-core/workflows/remove-phase.md +1 -1
- package/gsd-core/workflows/remove-workspace.md +2 -2
- package/gsd-core/workflows/resume-project.md +1 -1
- package/gsd-core/workflows/review.md +31 -16
- package/gsd-core/workflows/scan.md +1 -1
- package/gsd-core/workflows/secure-phase.md +3 -2
- package/gsd-core/workflows/settings-advanced.md +30 -10
- package/gsd-core/workflows/settings-integrations.md +2 -3
- package/gsd-core/workflows/settings.md +22 -9
- package/gsd-core/workflows/ship.md +3 -2
- package/gsd-core/workflows/sketch-wrap-up.md +1 -1
- package/gsd-core/workflows/sketch.md +1 -1
- package/gsd-core/workflows/smart-entry.md +2 -2
- package/gsd-core/workflows/spec-phase.md +15 -5
- package/gsd-core/workflows/spike-wrap-up.md +1 -1
- package/gsd-core/workflows/spike.md +1 -1
- package/gsd-core/workflows/stats.md +1 -1
- package/gsd-core/workflows/sync-skills.md +5 -5
- package/gsd-core/workflows/thread.md +1 -1
- package/gsd-core/workflows/transition.md +1 -1
- package/gsd-core/workflows/ui-phase.md +44 -8
- package/gsd-core/workflows/ui-review.md +18 -4
- package/gsd-core/workflows/ultraplan-phase.md +1 -1
- package/gsd-core/workflows/undo.md +339 -20
- package/gsd-core/workflows/update.md +14 -12
- package/gsd-core/workflows/validate-phase.md +3 -2
- package/gsd-core/workflows/verify-work/detail/elaboration.md +230 -0
- package/gsd-core/workflows/verify-work/steps/automated-ui-verification.md +1 -1
- package/gsd-core/workflows/verify-work/steps/mvp-uat-framing.md +1 -1
- package/gsd-core/workflows/verify-work.md +101 -196
- package/hooks/dist/gsd-agent-isolation-guard.js +66 -16
- package/hooks/dist/gsd-context-monitor.js +88 -15
- package/hooks/dist/gsd-cursor-subagent-start.js +34 -14
- package/hooks/dist/gsd-secret-read-guard.js +71 -19
- package/hooks/dist/gsd-statusline.js +81 -20
- package/hooks/dist/gsd-validate-commit.sh +97 -8
- package/hooks/dist/gsd-worktree-path-guard.js +25 -14
- package/hooks/dist/gsd-write-guard.js +46 -1
- package/hooks/dist/lib/dispatch-identity.js +187 -0
- package/hooks/dist/lib/filename-classification.js +64 -0
- package/hooks/dist/lib/isolation-deny-reason.js +53 -1
- package/hooks/dist/lib/isolation-sentinel.js +58 -19
- package/hooks/gsd-agent-isolation-guard.js +66 -16
- package/hooks/gsd-context-monitor.js +88 -15
- package/hooks/gsd-cursor-subagent-start.js +34 -14
- package/hooks/gsd-secret-read-guard.js +71 -19
- package/hooks/gsd-statusline.js +81 -20
- package/hooks/gsd-validate-commit.sh +97 -8
- package/hooks/gsd-worktree-path-guard.js +25 -14
- package/hooks/gsd-write-guard.js +46 -1
- package/hooks/lib/dispatch-identity.js +187 -0
- package/hooks/lib/filename-classification.js +64 -0
- package/hooks/lib/isolation-deny-reason.js +53 -1
- package/hooks/lib/isolation-sentinel.js +58 -19
- package/package.json +11 -6
- package/scripts/benchmark-compact-content-variants.cjs +298 -0
- package/scripts/benchmark-compact-content.cjs +368 -0
- package/scripts/build-hooks.js +15 -6
- package/scripts/check-contract-drift.cjs +131 -12
- package/scripts/check-env.cjs +36 -8
- package/scripts/check-glossary-refs.cjs +25 -21
- package/scripts/ci-next-health.cjs +271 -0
- package/scripts/ci-prepare-test-scope.cjs +7 -7
- package/scripts/ci-test-scope.cjs +126 -20
- package/scripts/ci-timeout-report.cjs +1 -1
- package/scripts/command-contract-helpers.cjs +3 -0
- package/scripts/diff-touches-shipped-paths.cjs +1 -1
- package/scripts/docs-guard-registry.cjs +35 -2
- package/scripts/gen-adr-index.cjs +8 -2
- package/scripts/gen-inventory-manifest.cjs +12 -0
- package/scripts/gen-loop-host-contract.cjs +69 -0
- package/scripts/gen-platform-conformance-tier.cjs +557 -0
- package/scripts/lib/drift-scan.cjs +1 -1
- package/scripts/lib/macos-conformance-tier.generated.cjs +224 -0
- package/scripts/lib/ndjson-reporter.cjs +3 -2
- package/scripts/lib/npm-version-check-diagnosis.cjs +59 -0
- package/scripts/lib/platform-conformance-tier.generated.cjs +287 -0
- package/scripts/lib/suite-detection.cjs +32 -0
- package/scripts/lint-allowed-tools-parity.cjs +221 -0
- package/scripts/lint-docs-guard-registration.exempt-baseline.cjs +47 -3
- package/scripts/lint-phase-arg-assignment.cjs +257 -0
- package/scripts/lint-phase-id-drift.cjs +623 -13
- package/scripts/lint-pr-branch-pattern-drift.cjs +148 -0
- package/scripts/lint-response-language-coverage.cjs +9 -3
- package/scripts/lint-retired-runtime-name.cjs +619 -0
- package/scripts/lint-source-test-name-collision.cjs +1 -1
- package/scripts/lint-state-write-path-drift.cjs +93 -0
- package/scripts/lint-test-file-count.allowlist.json +29 -9
- package/scripts/lint-vendored-deps.cjs +128 -17
- package/scripts/lint-workflow-shellcheck-baseline.json +100 -0
- package/scripts/prompt-injection-scan.sh +18 -0
- package/scripts/release-tarball-smoke.cjs +194 -1
- package/scripts/workflow-size.cjs +139 -0
- package/skills/gsd-autonomous/SKILL.md +2 -2
- package/skills/gsd-capture/SKILL.md +1 -1
- package/skills/gsd-cleanup/SKILL.md +1 -0
- package/skills/gsd-code-review/SKILL.md +2 -1
- package/skills/gsd-complete-milestone/SKILL.md +1 -0
- package/skills/gsd-config/SKILL.md +1 -0
- package/skills/gsd-debug/SKILL.md +1 -0
- package/skills/gsd-graphify/SKILL.md +1 -0
- package/skills/gsd-health/SKILL.md +1 -0
- package/skills/gsd-mempalace-capture/SKILL.md +8 -3
- package/skills/gsd-mempalace-recall/SKILL.md +1 -0
- package/skills/gsd-new-milestone/SKILL.md +1 -0
- package/skills/gsd-new-project/SKILL.md +1 -0
- package/skills/gsd-next/SKILL.md +1 -0
- package/skills/gsd-pause-work/SKILL.md +1 -0
- package/skills/gsd-phase/SKILL.md +1 -0
- package/skills/gsd-plan-review-convergence/SKILL.md +5 -5
- package/skills/gsd-pr-branch/SKILL.md +1 -0
- package/skills/gsd-progress/SKILL.md +1 -1
- package/skills/gsd-quick-batch/SKILL.md +1 -1
- package/skills/gsd-resume-work/SKILL.md +1 -0
- package/skills/gsd-review/SKILL.md +2 -3
- package/skills/gsd-review-backlog/SKILL.md +1 -0
- package/skills/gsd-settings/SKILL.md +2 -1
- package/skills/gsd-stats/SKILL.md +1 -0
- package/skills/gsd-thread/SKILL.md +1 -0
- package/skills/gsd-workspace/SKILL.md +1 -0
- package/skills/gsd-workstreams/SKILL.md +1 -0
- package/vscode/package.json +1 -1
- package/gsd-core/templates/claude-md.md +0 -145
- package/gsd-core/templates/codebase/concerns.md +0 -310
- package/gsd-core/templates/codebase/conventions.md +0 -307
- package/gsd-core/templates/codebase/integrations.md +0 -280
- package/gsd-core/templates/codebase/structure.md +0 -285
- package/gsd-core/templates/codebase/testing.md +0 -480
- package/gsd-core/templates/debug-subagent-prompt.md +0 -91
- package/gsd-core/templates/discovery.md +0 -146
|
@@ -127,6 +127,23 @@ function preferFirst(entries, preferred) {
|
|
|
127
127
|
const rest = entries.filter(([rt]) => rt !== preferred);
|
|
128
128
|
return [...pref, ...rest];
|
|
129
129
|
}
|
|
130
|
+
// GLOBAL probe: absolute env candidates first (in preferFirst order, first
|
|
131
|
+
// hasInstall hit wins), then $HOME-relative. Single resolver shared by the
|
|
132
|
+
// preferredConfigDir fast path's same-path dedup and the full cascade (#4197),
|
|
133
|
+
// so both compare against the global dir the resolution would actually select —
|
|
134
|
+
// an env-directed candidate, not necessarily the $HOME-relative pathname.
|
|
135
|
+
function resolveGlobalCandidate(fs, env, home, preferred) {
|
|
136
|
+
for (const [rt, absdir] of preferFirst(envRuntimeDirs({ env, home }), preferred)) {
|
|
137
|
+
if (hasInstall(fs, absdir))
|
|
138
|
+
return { runtime: rt, dir: node_path_1.default.resolve(absdir) };
|
|
139
|
+
}
|
|
140
|
+
for (const [rt, reldir] of preferFirst(exports.RUNTIME_DIRS, preferred)) {
|
|
141
|
+
const cand = node_path_1.default.resolve(home, reldir);
|
|
142
|
+
if (hasInstall(fs, cand))
|
|
143
|
+
return { runtime: rt, dir: cand };
|
|
144
|
+
}
|
|
145
|
+
return { runtime: '', dir: '' };
|
|
146
|
+
}
|
|
130
147
|
/**
|
|
131
148
|
* Pure resolver. Returns { installedVersion, scope, runtime, gsdDir }.
|
|
132
149
|
*/
|
|
@@ -137,11 +154,17 @@ function resolveUpdateContext({ home, cwd, env = {}, fs, preferredConfigDir = ''
|
|
|
137
154
|
// Fast path: a validated preferredConfigDir (custom --config-dir install).
|
|
138
155
|
if (preferredConfigDir && hasInstall(fs, preferredConfigDir)) {
|
|
139
156
|
const resolvedPref = node_path_1.default.resolve(preferredConfigDir);
|
|
157
|
+
// Same-path dedup the cascade applies (#4197): a preferred dir that IS the
|
|
158
|
+
// selected global install (an env candidate or the $HOME-relative dir) is
|
|
159
|
+
// GLOBAL even when cwd === $HOME also makes it the cwd-relative match.
|
|
160
|
+
const { dir: globalDir } = resolveGlobalCandidate(fs, env, home, preferred);
|
|
140
161
|
let scope = 'GLOBAL';
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
162
|
+
if (resolvedPref !== globalDir) {
|
|
163
|
+
for (const [, reldir] of exports.RUNTIME_DIRS) {
|
|
164
|
+
if (node_path_1.default.resolve(cwd, reldir) === resolvedPref) {
|
|
165
|
+
scope = 'LOCAL';
|
|
166
|
+
break;
|
|
167
|
+
}
|
|
145
168
|
}
|
|
146
169
|
}
|
|
147
170
|
return {
|
|
@@ -151,7 +174,6 @@ function resolveUpdateContext({ home, cwd, env = {}, fs, preferredConfigDir = ''
|
|
|
151
174
|
gsdDir: preferredConfigDir,
|
|
152
175
|
};
|
|
153
176
|
}
|
|
154
|
-
const orderedEnv = preferFirst(envRuntimeDirs({ env, home }), preferred);
|
|
155
177
|
const orderedRuntime = preferFirst(exports.RUNTIME_DIRS, preferred);
|
|
156
178
|
// LOCAL probe (relative to cwd).
|
|
157
179
|
let localRuntime = '', localDir = '';
|
|
@@ -163,25 +185,9 @@ function resolveUpdateContext({ home, cwd, env = {}, fs, preferredConfigDir = ''
|
|
|
163
185
|
break;
|
|
164
186
|
}
|
|
165
187
|
}
|
|
166
|
-
// GLOBAL probe: absolute env candidates first, then $HOME-relative
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
if (hasInstall(fs, absdir)) {
|
|
170
|
-
globalRuntime = rt;
|
|
171
|
-
globalDir = node_path_1.default.resolve(absdir);
|
|
172
|
-
break;
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
if (!globalRuntime) {
|
|
176
|
-
for (const [rt, reldir] of orderedRuntime) {
|
|
177
|
-
const cand = node_path_1.default.resolve(home, reldir);
|
|
178
|
-
if (hasInstall(fs, cand)) {
|
|
179
|
-
globalRuntime = rt;
|
|
180
|
-
globalDir = cand;
|
|
181
|
-
break;
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
}
|
|
188
|
+
// GLOBAL probe: absolute env candidates first, then $HOME-relative — the
|
|
189
|
+
// same resolver the fast path dedups against.
|
|
190
|
+
const { runtime: globalRuntime, dir: globalDir } = resolveGlobalCandidate(fs, env, home, preferred);
|
|
185
191
|
const localValid = trustedVersionAt(fs, localDir);
|
|
186
192
|
const isLocal = !!localValid && (!globalDir || localDir !== globalDir);
|
|
187
193
|
if (isLocal) {
|
|
@@ -1262,16 +1262,21 @@
|
|
|
1262
1262
|
state.result += _result;
|
|
1263
1263
|
}
|
|
1264
1264
|
}
|
|
1265
|
+
function chargeMergeWork(state) {
|
|
1266
|
+
state.totalMergeKeys++;
|
|
1267
|
+
if (state.maxTotalMergeKeys !== -1 && state.totalMergeKeys > state.maxTotalMergeKeys) {
|
|
1268
|
+
throwError(state, "merge keys exceeded maxTotalMergeKeys (" + state.maxTotalMergeKeys + ")");
|
|
1269
|
+
}
|
|
1270
|
+
}
|
|
1265
1271
|
function mergeMappings(state, destination, source, overridableKeys) {
|
|
1266
1272
|
if (!common2.isObject(source)) {
|
|
1267
1273
|
throwError(state, "cannot merge mappings; the provided source object is unacceptable");
|
|
1268
1274
|
}
|
|
1275
|
+
chargeMergeWork(state);
|
|
1269
1276
|
var sourceKeys = Object.keys(source);
|
|
1270
1277
|
for (var index = 0, quantity = sourceKeys.length; index < quantity; index += 1) {
|
|
1271
1278
|
var key = sourceKeys[index];
|
|
1272
|
-
|
|
1273
|
-
throwError(state, "merge keys exceeded maxTotalMergeKeys (" + state.maxTotalMergeKeys + ")");
|
|
1274
|
-
}
|
|
1279
|
+
chargeMergeWork(state);
|
|
1275
1280
|
if (!_hasOwnProperty.call(destination, key)) {
|
|
1276
1281
|
setProperty(destination, key, source[key]);
|
|
1277
1282
|
overridableKeys[key] = true;
|
|
@@ -1299,6 +1304,9 @@
|
|
|
1299
1304
|
}
|
|
1300
1305
|
if (keyTag === "tag:yaml.org,2002:merge") {
|
|
1301
1306
|
if (Array.isArray(valueNode)) {
|
|
1307
|
+
if (valueNode.length > 100) {
|
|
1308
|
+
throwError(state, "abnormal merge sequence size");
|
|
1309
|
+
}
|
|
1302
1310
|
for (var _index = 0, _quantity = valueNode.length; _index < _quantity; _index += 1) {
|
|
1303
1311
|
mergeMappings(state, _result, valueNode[_index], overridableKeys);
|
|
1304
1312
|
}
|
|
@@ -48,9 +48,10 @@ const coreUtilsMod = require("./core-utils.cjs");
|
|
|
48
48
|
const planningScopeMod = require("./planning-scope.cjs");
|
|
49
49
|
const shell_command_projection_cjs_1 = require("./shell-command-projection.cjs");
|
|
50
50
|
const runtime_slash_cjs_1 = require("./runtime-slash.cjs");
|
|
51
|
+
const security_cjs_1 = require("./security.cjs");
|
|
51
52
|
const { output, error } = io;
|
|
52
53
|
const { extractPhaseToken, scopeToPhase } = phaseId;
|
|
53
|
-
const { extractFrontmatter } = frontmatterMod;
|
|
54
|
+
const { extractFrontmatter, FRONTMATTER_UNPARSEABLE } = frontmatterMod;
|
|
54
55
|
const { normalizeLineEndings } = coreUtilsMod;
|
|
55
56
|
const { SCOPE } = planningScopeMod;
|
|
56
57
|
// ─── Constants ────────────────────────────────────────────────────────────────
|
|
@@ -101,8 +102,14 @@ const VERIFICATION_ROUTING_TABLE = {
|
|
|
101
102
|
},
|
|
102
103
|
stale: {
|
|
103
104
|
status: 'stale',
|
|
104
|
-
|
|
105
|
-
|
|
105
|
+
// #4682: staleness means covered source files changed after the verifier
|
|
106
|
+
// last ran — the only remedy is re-running the verifier.
|
|
107
|
+
// /gsd-verify-work never rewrites VERIFICATION.md, so advising it from
|
|
108
|
+
// here was an advice loop. execute-phase resumes at the verification
|
|
109
|
+
// gates and re-runs the verifier (its resume tree routes a stale report
|
|
110
|
+
// to re-verification), which regenerates VERIFICATION.md and its digest.
|
|
111
|
+
next_action: 'Verification is stale — covered source files changed after the verifier last ran. Re-run execute-phase for this phase: it resumes at the verification gates and re-runs the verifier, regenerating VERIFICATION.md and its digest. verify-work alone cannot refresh a stale report.',
|
|
112
|
+
next_command: 'execute-phase',
|
|
106
113
|
},
|
|
107
114
|
// INTERNAL SENTINEL: constructed when no *-VERIFICATION.md file exists or when
|
|
108
115
|
// the file has no parseable frontmatter status. Never emitted by the verifier.
|
|
@@ -111,6 +118,15 @@ const VERIFICATION_ROUTING_TABLE = {
|
|
|
111
118
|
next_action: 'No verification report found — the verify step never completed. Running execute-phase is safe here: it resumes at the verification gates and does not re-run plans that already have a SUMMARY.md (see #2868).',
|
|
112
119
|
next_command: 'execute-phase',
|
|
113
120
|
},
|
|
121
|
+
// #4806: the report EXISTS but its frontmatter is not parseable YAML —
|
|
122
|
+
// fundamentally different from "missing" (the verify step DID run; re-running
|
|
123
|
+
// execute-phase cannot fix a YAML typo). Consumers treat any non-'passed'
|
|
124
|
+
// status as blocking, so this value fails safe while telling the truth.
|
|
125
|
+
unparseable: {
|
|
126
|
+
status: 'unparseable',
|
|
127
|
+
next_action: "The *-VERIFICATION.md frontmatter is not parseable YAML — fix the syntax error in the report itself. Re-running execute-phase cannot fix a YAML typo in an existing report.",
|
|
128
|
+
next_command: '',
|
|
129
|
+
},
|
|
114
130
|
// INTERNAL SENTINEL: constructed when the file has a status value not in
|
|
115
131
|
// VERIFIER_STATUSES. Never emitted by the verifier.
|
|
116
132
|
unknown: {
|
|
@@ -173,8 +189,95 @@ function canonicalizeCoveredFiles(files) {
|
|
|
173
189
|
* Bump on any change to the digest's input shape (path list, hashing order,
|
|
174
190
|
* per-file hash algorithm) so an old stored digest can never collide with a
|
|
175
191
|
* differently-computed new one — a version mismatch is just a mismatch.
|
|
192
|
+
*
|
|
193
|
+
* Version history:
|
|
194
|
+
* v1 (#4155) — every covered path's whole bytes, uniformly.
|
|
195
|
+
* v2 (#4623) — repo-wide planning documents (`isSharedPlanningDoc`) are
|
|
196
|
+
* excluded from the hash by construction.
|
|
197
|
+
*
|
|
198
|
+
* A stored digest names its own version (`v<N>:sha256:…`), and
|
|
199
|
+
* `readVerificationStatus` recomputes under the STORED version rather than
|
|
200
|
+
* this constant — so bumping it does not flip every already-verified phase
|
|
201
|
+
* to `stale` on upgrade. A legacy v1 report keeps v1 semantics, shared
|
|
202
|
+
* documents included, until it is re-fingerprinted; only a version outside
|
|
203
|
+
* `KNOWN_FINGERPRINT_VERSIONS` is unrecomputable and fails closed.
|
|
204
|
+
*/
|
|
205
|
+
const FINGERPRINT_VERSION = 2;
|
|
206
|
+
const KNOWN_FINGERPRINT_VERSIONS = new Set([1, 2]);
|
|
207
|
+
/**
|
|
208
|
+
* #4623: the planning roots whose DIRECT children are repo-wide planning
|
|
209
|
+
* documents, as project-root-relative posix paths. Always `.planning`; plus
|
|
210
|
+
* the phase's OWN planning root when a phase directory is known — the parent
|
|
211
|
+
* of its `phases/` directory, which is how `planningDir` lays out every
|
|
212
|
+
* scope (`.planning`, `.planning/<project>`, `.planning/workstreams/<ws>`,
|
|
213
|
+
* `.planning/<project>/workstreams/<ws>`; `planning-workspace.cts`). Derived
|
|
214
|
+
* from the phase's position rather than from a list of layouts so a
|
|
215
|
+
* workstream-scoped `ROADMAP.md` is recognised without this function
|
|
216
|
+
* knowing what a workstream is, and so `.planning/research/notes.md` is
|
|
217
|
+
* NOT mistaken for one — lexically the two are indistinguishable from
|
|
218
|
+
* `.planning/<project>/ROADMAP.md`. A phase directory that does not sit
|
|
219
|
+
* under the project root (unit fixtures at a bare tmpdir) contributes no
|
|
220
|
+
* extra root.
|
|
221
|
+
*/
|
|
222
|
+
function sharedPlanningRoots(projectRoot, phaseDir) {
|
|
223
|
+
const roots = ['.planning'];
|
|
224
|
+
if (phaseDir) {
|
|
225
|
+
const phasesDir = node_path_1.default.dirname(node_path_1.default.resolve(phaseDir));
|
|
226
|
+
const planningDir = node_path_1.default.dirname(phasesDir);
|
|
227
|
+
const rel = normalizeRel(node_path_1.default.relative(node_path_1.default.resolve(projectRoot), planningDir));
|
|
228
|
+
// Two structural checks, both load-bearing: the phase dir's PARENT must be
|
|
229
|
+
// the `phases/` directory `planningDir` lays every scope out with, and the
|
|
230
|
+
// derived root must sit inside `.planning/`. Without them any accepted
|
|
231
|
+
// directory — `<root>/src/phases/01-fake` — would nominate `src` as a
|
|
232
|
+
// planning root and silently drop real implementation evidence from the
|
|
233
|
+
// digest (found by the cross-AI review of this change). A shape that fails
|
|
234
|
+
// either check contributes no extra root; `.planning` itself is already
|
|
235
|
+
// present.
|
|
236
|
+
if (node_path_1.default.basename(phasesDir) === 'phases' &&
|
|
237
|
+
rel.startsWith('.planning/') &&
|
|
238
|
+
!rel.includes('/../') &&
|
|
239
|
+
!roots.includes(rel)) {
|
|
240
|
+
roots.push(rel);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
return roots;
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* #4623: a covered path names a repo-wide planning document when it sits
|
|
247
|
+
* DIRECTLY under one of `sharedPlanningRoots` — `ROADMAP.md`,
|
|
248
|
+
* `REQUIREMENTS.md`, `STATE.md`, `PROJECT.md`, `MILESTONES.md`,
|
|
249
|
+
* `config.json`, … — as opposed to a phase's own artifacts under
|
|
250
|
+
* `<root>/phases/<phase>/` or a research note under `.planning/research/`.
|
|
251
|
+
* Every phase rewrites these as ordinary bookkeeping (a roadmap checkbox, a
|
|
252
|
+
* requirement's traceability cell, STATE.md's position), so hashing their
|
|
253
|
+
* whole bytes into one phase's digest coupled every phase's staleness to
|
|
254
|
+
* every other phase's close — and to its OWN close, since `phase.complete`
|
|
255
|
+
* and `requirements mark-complete` write them after the verifier has
|
|
256
|
+
* already run.
|
|
257
|
+
*
|
|
258
|
+
* Defined by position, not by a name list, so the set cannot drift as new
|
|
259
|
+
* top-level planning documents appear (the tree already carries a dozen).
|
|
260
|
+
* `rel` is expected posix-normalized (`canonicalizeCoveredFiles`), so a
|
|
261
|
+
* `./.planning/ROADMAP.md` spelling has already collapsed to the bare form.
|
|
262
|
+
*/
|
|
263
|
+
function isSharedPlanningDoc(rel, roots = ['.planning']) {
|
|
264
|
+
if (rel === '' || rel.endsWith('/'))
|
|
265
|
+
return false;
|
|
266
|
+
return roots.includes(node_path_1.default.posix.dirname(rel));
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* #4623: the fingerprint version a stored `covered_digest` was computed
|
|
270
|
+
* under, or `null` when the prefix is absent, malformed, or names a version
|
|
271
|
+
* this build cannot recompute (an unknown version is a mismatch by
|
|
272
|
+
* construction — the fail-closed shape `FINGERPRINT_VERSION`'s doc promises).
|
|
176
273
|
*/
|
|
177
|
-
|
|
274
|
+
function parseFingerprintVersion(digest) {
|
|
275
|
+
const m = /^v(\d+):sha256:/.exec(digest);
|
|
276
|
+
if (!m)
|
|
277
|
+
return null;
|
|
278
|
+
const version = Number(m[1]);
|
|
279
|
+
return KNOWN_FINGERPRINT_VERSIONS.has(version) ? version : null;
|
|
280
|
+
}
|
|
178
281
|
/**
|
|
179
282
|
* #4155: recompute the deterministic content fingerprint over a verifier's
|
|
180
283
|
* declared covered-input set (phase PLAN/SUMMARY, mapped requirements,
|
|
@@ -215,14 +318,23 @@ const FINGERPRINT_VERSION = 1;
|
|
|
215
318
|
* confinement work (against `projectRoot`, the correct boundary for this
|
|
216
319
|
* data), so no security property is lost by bypassing a narrower seam here.
|
|
217
320
|
*/
|
|
218
|
-
function computeCoveredDigest(projectRoot, coveredFiles) {
|
|
321
|
+
function computeCoveredDigest(projectRoot, coveredFiles, version = FINGERPRINT_VERSION, opts = {}) {
|
|
322
|
+
// #4623: `version` selects the input shape to hash under — the CURRENT
|
|
323
|
+
// one for a fresh fingerprint (the CLI verb), or the STORED one when
|
|
324
|
+
// `readVerificationStatus` recomputes against a report's own digest.
|
|
325
|
+
// `opts.phaseDir` lets v2 recognise the phase's own planning root
|
|
326
|
+
// (`sharedPlanningRoots`); without it only `.planning/` itself is shared.
|
|
327
|
+
if (!KNOWN_FINGERPRINT_VERSIONS.has(version))
|
|
328
|
+
return null;
|
|
219
329
|
const uniqueSorted = canonicalizeCoveredFiles(coveredFiles);
|
|
220
330
|
if (uniqueSorted.length === 0)
|
|
221
331
|
return null;
|
|
332
|
+
const sharedRoots = version >= 2 ? sharedPlanningRoots(projectRoot, opts.phaseDir) : [];
|
|
333
|
+
let hashed = 0;
|
|
222
334
|
// Canonicalize the root ONCE — every candidate's realpath is checked against
|
|
223
335
|
// this, not the possibly-symlinked `projectRoot` argument itself. Always via
|
|
224
336
|
// the REAL fs, never fsImpl: `projectRoot` is a trusted anchor the CALLER
|
|
225
|
-
// derived (
|
|
337
|
+
// derived (resolveProjectRoot), not attacker-influenced covered-input data —
|
|
226
338
|
// routing it through a caller-scoped containment seam (e.g. #4155's
|
|
227
339
|
// containmentEnforcingVerificationFs, confined to `.planning/`, a proper
|
|
228
340
|
// SUBSET of `projectRoot`) would reject the root itself and fail every
|
|
@@ -251,13 +363,25 @@ function computeCoveredDigest(projectRoot, coveredFiles) {
|
|
|
251
363
|
// confinement check above is not enough. realpathSync resolves the
|
|
252
364
|
// actual target; re-confining against realRoot closes that gap.
|
|
253
365
|
const real = node_fs_1.default.realpathSync(resolved);
|
|
254
|
-
|
|
255
|
-
|
|
366
|
+
// Both operands are already realpath-resolved (this fn's own realpathSync calls
|
|
367
|
+
// above), so the shared containment comparison applies directly (ADR-4650) —
|
|
368
|
+
// no re-resolution through assertWithinRoot/tryWithinRoot, which would redo work
|
|
369
|
+
// this function already owns for its exists-vs-escaped tri-state.
|
|
370
|
+
if (!(0, security_cjs_1.isContainedIn)(real, realRoot)) {
|
|
256
371
|
return null;
|
|
257
372
|
}
|
|
258
373
|
const st = node_fs_1.default.statSync(real);
|
|
259
374
|
if (!st.isFile())
|
|
260
375
|
return null;
|
|
376
|
+
// #4623 (v2+): a repo-wide planning document is VALIDATED exactly as
|
|
377
|
+
// every other covered path — confined, present, a regular file; the
|
|
378
|
+
// fail-closed contract above is unchanged — but its bytes contribute
|
|
379
|
+
// nothing to the digest. It may stay declared in `covered_files` (the
|
|
380
|
+
// verifier's instructions long said to list the mapped requirement,
|
|
381
|
+
// and every report already written does); its bookkeeping churn can
|
|
382
|
+
// no longer read as drift.
|
|
383
|
+
if (isSharedPlanningDoc(rel, sharedRoots))
|
|
384
|
+
continue;
|
|
261
385
|
bytes = node_fs_1.default.readFileSync(real);
|
|
262
386
|
}
|
|
263
387
|
catch {
|
|
@@ -265,12 +389,19 @@ function computeCoveredDigest(projectRoot, coveredFiles) {
|
|
|
265
389
|
}
|
|
266
390
|
const fileHash = node_crypto_1.default.createHash('sha256').update(bytes).digest('hex');
|
|
267
391
|
parts.push(`${rel}\n${fileHash}\n`);
|
|
392
|
+
hashed++;
|
|
268
393
|
}
|
|
394
|
+
// #4623 (v2+): a declaration made ONLY of shared planning documents has no
|
|
395
|
+
// evidence in it at all — a constant digest over the header would satisfy
|
|
396
|
+
// the fingerprint pair while grounding the verification in nothing. Fail
|
|
397
|
+
// closed, the same way an empty declaration does.
|
|
398
|
+
if (version >= 2 && hashed === 0)
|
|
399
|
+
return null;
|
|
269
400
|
const aggregate = node_crypto_1.default
|
|
270
401
|
.createHash('sha256')
|
|
271
|
-
.update(`v${
|
|
402
|
+
.update(`v${version}\n${parts.join('')}`, 'utf-8')
|
|
272
403
|
.digest('hex');
|
|
273
|
-
return `v${
|
|
404
|
+
return `v${version}:sha256:${aggregate}`;
|
|
274
405
|
}
|
|
275
406
|
/**
|
|
276
407
|
* #4155: the content fingerprint only recomputes digests for paths the
|
|
@@ -471,7 +602,7 @@ function resolvePhaseArtifactFile(entries, bareName, options = {}) {
|
|
|
471
602
|
// filtered out, and if that leaves nothing the code falls through to
|
|
472
603
|
// `allowBare`/`null` deliberately.
|
|
473
604
|
const scoped = options.phaseDirName
|
|
474
|
-
? scopeToPhase(candidates, options.phaseDirName)
|
|
605
|
+
? scopeToPhase(candidates, options.phaseDirName, options.convention)
|
|
475
606
|
: candidates;
|
|
476
607
|
if (scoped.length > 0)
|
|
477
608
|
return scoped[0];
|
|
@@ -528,7 +659,7 @@ function resolveVerificationFile(entries, options = {}) {
|
|
|
528
659
|
function resolveUatFile(entries, options = {}) {
|
|
529
660
|
return resolvePhaseArtifactFile(entries, 'UAT.md', options);
|
|
530
661
|
}
|
|
531
|
-
function findStaleVerificationSummary(phaseDir, fsImpl = defaultFsImpl, phaseCleanCommitTimesMs = defaultPhaseCleanCommitTimesMs) {
|
|
662
|
+
function findStaleVerificationSummary(phaseDir, fsImpl = defaultFsImpl, phaseCleanCommitTimesMs = defaultPhaseCleanCommitTimesMs, convention) {
|
|
532
663
|
// FS errors (TOCTOU: a SUMMARY listed by scanPhasePlans then removed before statSync;
|
|
533
664
|
// unreadable dir; broken symlink; file->dir swap) must degrade rather than throw
|
|
534
665
|
// uncaught into callers that are NOT under the planning lock (init.manager /
|
|
@@ -544,9 +675,20 @@ function findStaleVerificationSummary(phaseDir, fsImpl = defaultFsImpl, phaseCle
|
|
|
544
675
|
// or sentinel-numbered canonically-shaped file cannot outrank this
|
|
545
676
|
// phase's own (possibly non-canonical) report. #3511: phaseDirName scopes
|
|
546
677
|
// the fallback path to this same phase (see resolveVerificationFile docs).
|
|
678
|
+
// #4187: allowBare — this staleness seam must see the same report set the
|
|
679
|
+
// status reader sees, or a bare report could never read `stale` while its
|
|
680
|
+
// dashed twin could (two answers from one verb).
|
|
547
681
|
const phaseDirName = node_path_1.default.basename(phaseDir);
|
|
548
|
-
|
|
549
|
-
|
|
682
|
+
// #612: derive the token with the resolved convention so a bracket dir's
|
|
683
|
+
// own token is read behind its `{CODE}.{MM}-` prefix. #4187: keep the bare
|
|
684
|
+
// report tier aligned with the status reader.
|
|
685
|
+
const phaseToken = extractPhaseToken(phaseDirName, convention);
|
|
686
|
+
const verificationFile = resolveVerificationFile(phaseFiles, {
|
|
687
|
+
allowBare: true,
|
|
688
|
+
phaseToken,
|
|
689
|
+
phaseDirName,
|
|
690
|
+
convention,
|
|
691
|
+
});
|
|
550
692
|
if (!verificationFile)
|
|
551
693
|
return { determined: true, stale: false };
|
|
552
694
|
const summaryFiles = scanPhasePlans(phaseDir).summaryFiles
|
|
@@ -587,7 +729,9 @@ function findStaleVerificationSummary(phaseDir, fsImpl = defaultFsImpl, phaseCle
|
|
|
587
729
|
* 1. Find the phase's verification report via `resolveVerificationFile`
|
|
588
730
|
* (canonical `<phase-token>-VERIFICATION.md` preferred; falls back to the
|
|
589
731
|
* alphabetically-first `*-VERIFICATION.md` that belongs to THIS phase when
|
|
590
|
-
* none is canonical — #3357/#3511
|
|
732
|
+
* none is canonical — #3357/#3511; and, when the directory's only report
|
|
733
|
+
* is a bare `VERIFICATION.md`, that file — #4187, matching
|
|
734
|
+
* `verification.resolve-file`). If none → status 'missing'.
|
|
591
735
|
* 2. Extract `status` from FRONTMATTER ONLY via the shared extractFrontmatter
|
|
592
736
|
* parser (DEFECT.FRONTMATTER-SCALAR-BROAD-GREP fix — parser anchors at byte 0).
|
|
593
737
|
* If no frontmatter block or no `status` key → status 'missing'.
|
|
@@ -608,7 +752,12 @@ function readVerificationStatus(phaseDir, opts = {}) {
|
|
|
608
752
|
const fsImpl = opts.fs ?? defaultFsImpl;
|
|
609
753
|
const phaseCleanCommitTimesMs = opts.phaseCleanCommitTimesMs ?? defaultPhaseCleanCommitTimesMs;
|
|
610
754
|
const runtime = opts.runtime ?? 'claude';
|
|
611
|
-
// Phase token for the gaps_found command
|
|
755
|
+
// Phase token for the gaps_found command — deliberately convention-LESS
|
|
756
|
+
// even when `opts.convention` is present: the token becomes a bare COMMAND
|
|
757
|
+
// ARGUMENT below, and a bare bracket phase number is milestone-ambiguous
|
|
758
|
+
// (`02` cannot tell GSD.01-02 from GSD.02-02), so the argument keeps its
|
|
759
|
+
// pre-#612 shape. The convention-aware token is derived separately for
|
|
760
|
+
// FILE RESOLUTION only (`resolutionToken`, at the readdir below).
|
|
612
761
|
const baseName = node_path_1.default.basename(phaseDir);
|
|
613
762
|
const phaseToken = extractPhaseToken(baseName);
|
|
614
763
|
const derivedPhaseNumber = phaseToken.length > 0 ? phaseToken : baseName;
|
|
@@ -624,12 +773,21 @@ function readVerificationStatus(phaseDir, opts = {}) {
|
|
|
624
773
|
let verificationFile = null;
|
|
625
774
|
try {
|
|
626
775
|
const entries = fsImpl.readdirSync(phaseDir);
|
|
627
|
-
// #3492: pin selection to THIS phase's own token
|
|
628
|
-
//
|
|
629
|
-
//
|
|
630
|
-
//
|
|
631
|
-
//
|
|
632
|
-
|
|
776
|
+
// #3492: pin selection to THIS phase's own token so a stray cross-phase
|
|
777
|
+
// or sentinel-numbered canonically-shaped file cannot outrank this phase's
|
|
778
|
+
// own report. #612: derive a separate convention-aware RESOLUTION token
|
|
779
|
+
// for bracket directories while the routed command argument above stays
|
|
780
|
+
// convention-less and milestone-unambiguous. #4187: keep the bare report
|
|
781
|
+
// tier aligned with every other verification reader.
|
|
782
|
+
const resolutionToken = opts.convention === 'bracket'
|
|
783
|
+
? extractPhaseToken(baseName, opts.convention)
|
|
784
|
+
: phaseToken;
|
|
785
|
+
verificationFile = resolveVerificationFile(entries, {
|
|
786
|
+
allowBare: true,
|
|
787
|
+
phaseToken: resolutionToken,
|
|
788
|
+
phaseDirName: baseName,
|
|
789
|
+
convention: opts.convention,
|
|
790
|
+
});
|
|
633
791
|
}
|
|
634
792
|
catch {
|
|
635
793
|
// Directory unreadable → treat as missing
|
|
@@ -656,6 +814,16 @@ function readVerificationStatus(phaseDir, opts = {}) {
|
|
|
656
814
|
// same root cause as the false-clean class fixed elsewhere in #3707-CR.
|
|
657
815
|
const content = normalizeLineEndings(fsImpl.readFileSync(filePath, 'utf-8'));
|
|
658
816
|
fm = extractFrontmatter(content, filePath);
|
|
817
|
+
// #4806: an unparseable frontmatter block is NOT "missing" — the file
|
|
818
|
+
// exists and verification ran. Report a distinct status so the caller is
|
|
819
|
+
// sent to fix the YAML, not to re-run execute-phase.
|
|
820
|
+
if (fm[FRONTMATTER_UNPARSEABLE] === true) {
|
|
821
|
+
return {
|
|
822
|
+
status: 'unparseable',
|
|
823
|
+
next_action: "The *-VERIFICATION.md frontmatter is not parseable YAML — fix the syntax error in the report itself. Re-running execute-phase cannot fix a YAML typo in an existing report.",
|
|
824
|
+
next_command: '',
|
|
825
|
+
};
|
|
826
|
+
}
|
|
659
827
|
const statusVal = fm['status'];
|
|
660
828
|
// status is always a scalar string in a well-formed VERIFICATION.md frontmatter;
|
|
661
829
|
// only accept string values — arrays and objects are not valid status values.
|
|
@@ -710,13 +878,23 @@ function readVerificationStatus(phaseDir, opts = {}) {
|
|
|
710
878
|
// short-circuit in turn: the live-directory re-scan (for a plan/summary
|
|
711
879
|
// added AFTER verification and never declared in covered_files) only
|
|
712
880
|
// runs once the digest itself has already matched.
|
|
881
|
+
//
|
|
882
|
+
// #4623: recompute under the STORED digest's own version, not the
|
|
883
|
+
// current constant — a v1 report written before the shared-document
|
|
884
|
+
// exclusion keeps v1 semantics rather than going stale on upgrade. An
|
|
885
|
+
// unknown version parses to `null`, which `computeCoveredDigest`
|
|
886
|
+
// refuses (returns `null`), so the compare below fails closed.
|
|
887
|
+
const storedVersion = hasWellFormedFingerprint && typeof coveredDigestVal === 'string'
|
|
888
|
+
? parseFingerprintVersion(coveredDigestVal)
|
|
889
|
+
: null;
|
|
713
890
|
isStale =
|
|
714
891
|
!hasWellFormedFingerprint ||
|
|
715
|
-
|
|
892
|
+
storedVersion === null ||
|
|
893
|
+
computeCoveredDigest((0, project_root_cjs_1.resolveProjectRoot)(phaseDir), coveredFilesVal, storedVersion, { phaseDir }) !== coveredDigestVal ||
|
|
716
894
|
!allCurrentArtifactsCovered(phaseDir, coveredFilesVal);
|
|
717
895
|
}
|
|
718
896
|
else {
|
|
719
|
-
const staleCheck = findStaleVerificationSummary(phaseDir, fsImpl, phaseCleanCommitTimesMs);
|
|
897
|
+
const staleCheck = findStaleVerificationSummary(phaseDir, fsImpl, phaseCleanCommitTimesMs, opts.convention);
|
|
720
898
|
isStale = staleCheck.determined && staleCheck.stale;
|
|
721
899
|
// staleCheck is either {determined:true, stale:false} (checked; nothing
|
|
722
900
|
// stale) or {determined:false} (could not check — fs/scan/clock failure).
|
|
@@ -730,7 +908,10 @@ function readVerificationStatus(phaseDir, opts = {}) {
|
|
|
730
908
|
return {
|
|
731
909
|
status: entry.status,
|
|
732
910
|
next_action: entry.next_action,
|
|
733
|
-
|
|
911
|
+
// #4682: execute-phase resumes at the verification gates and re-runs
|
|
912
|
+
// the verifier, regenerating VERIFICATION.md and its digest — the same
|
|
913
|
+
// routing the `missing` sentinel has used since #2868.
|
|
914
|
+
next_command: projectNextCommand('execute-phase', runtime, phaseArg),
|
|
734
915
|
};
|
|
735
916
|
}
|
|
736
917
|
// 3. Route — exclude internal sentinels from raw-file lookup (they are
|
|
@@ -800,6 +981,7 @@ function isPhaseComplete(phaseDir, deps = {}) {
|
|
|
800
981
|
phaseCleanCommitTimesMs: deps.phaseCleanCommitTimesMs,
|
|
801
982
|
runtime: deps.runtime,
|
|
802
983
|
phaseNumber: deps.phaseNumber,
|
|
984
|
+
convention: deps.convention,
|
|
803
985
|
});
|
|
804
986
|
return {
|
|
805
987
|
value: {
|
|
@@ -866,6 +1048,69 @@ function cmdVerificationResolveFile(cwd, phaseDirArg, raw) {
|
|
|
866
1048
|
}
|
|
867
1049
|
output({ verification_file: verificationPath }, raw, verificationPath);
|
|
868
1050
|
}
|
|
1051
|
+
/**
|
|
1052
|
+
* #4623: parse the argv tokens after `verification.fingerprint <phaseDir>`
|
|
1053
|
+
* into a covered-file list. The router hands over a raw positional slice,
|
|
1054
|
+
* so every `--files`-style form other `gsd-tools` verbs accept (`commit
|
|
1055
|
+
* --files a b`, `docs/CLI-TOOLS.md`) used to reach `computeCoveredDigest`
|
|
1056
|
+
* with the literal token `--files` — or an unsplit `"a,b"` — as a covered
|
|
1057
|
+
* path, and the whole command failed closed with "a covered file is
|
|
1058
|
+
* missing, unreadable, or escapes the project root". On the reporting
|
|
1059
|
+
* project that message convinced two people the digest was permanently
|
|
1060
|
+
* unrecomputable.
|
|
1061
|
+
*
|
|
1062
|
+
* Accepted, all equivalent and freely mixed:
|
|
1063
|
+
* - bare positionals `a b` (the documented form, unchanged)
|
|
1064
|
+
* - a single flag `--files a`
|
|
1065
|
+
* - a comma-separated value `--files a,b` (also `--files=a,b`)
|
|
1066
|
+
* - a repeated flag `--files a --files b`
|
|
1067
|
+
*
|
|
1068
|
+
* Only a `--files` VALUE is comma-split: a bare positional keeps its bytes,
|
|
1069
|
+
* so the documented form's behaviour on a comma-bearing filename is
|
|
1070
|
+
* unchanged. Any other `--flag` is an explicit usage error, never a path —
|
|
1071
|
+
* a mis-typed flag must not fail as "file missing" again. (`--raw` never
|
|
1072
|
+
* reaches here; the CLI entry point splices it out before routing.)
|
|
1073
|
+
*/
|
|
1074
|
+
function parseFingerprintFileArgs(tokens) {
|
|
1075
|
+
const files = [];
|
|
1076
|
+
const EMPTY_VALUE = '--files requires at least one path for verification.fingerprint (a path, or a comma-separated list)';
|
|
1077
|
+
const splitList = (value) => value
|
|
1078
|
+
.split(',')
|
|
1079
|
+
.map((s) => s.trim())
|
|
1080
|
+
.filter((s) => s.length > 0);
|
|
1081
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
1082
|
+
const token = tokens[i];
|
|
1083
|
+
if (token === '--files') {
|
|
1084
|
+
const value = tokens[i + 1];
|
|
1085
|
+
if (value === undefined || value.startsWith('--')) {
|
|
1086
|
+
return { error: '--files requires a value for verification.fingerprint (a path, or a comma-separated list)' };
|
|
1087
|
+
}
|
|
1088
|
+
const list = splitList(value);
|
|
1089
|
+
// An empty or all-comma value is a usage error, never a silent no-op —
|
|
1090
|
+
// the caller would otherwise meet the generic zero-files error and go
|
|
1091
|
+
// looking for a missing path.
|
|
1092
|
+
if (list.length === 0)
|
|
1093
|
+
return { error: EMPTY_VALUE };
|
|
1094
|
+
files.push(...list);
|
|
1095
|
+
i++;
|
|
1096
|
+
}
|
|
1097
|
+
else if (token.startsWith('--files=')) {
|
|
1098
|
+
const list = splitList(token.slice('--files='.length));
|
|
1099
|
+
if (list.length === 0)
|
|
1100
|
+
return { error: EMPTY_VALUE };
|
|
1101
|
+
files.push(...list);
|
|
1102
|
+
}
|
|
1103
|
+
else if (token.startsWith('--')) {
|
|
1104
|
+
return {
|
|
1105
|
+
error: `unknown flag ${token} for verification.fingerprint (covered files are bare positionals or --files <a[,b]>, repeatable)`,
|
|
1106
|
+
};
|
|
1107
|
+
}
|
|
1108
|
+
else {
|
|
1109
|
+
files.push(token);
|
|
1110
|
+
}
|
|
1111
|
+
}
|
|
1112
|
+
return { files };
|
|
1113
|
+
}
|
|
869
1114
|
/**
|
|
870
1115
|
* CLI command handler (#4155): compute the covered-input fingerprint the
|
|
871
1116
|
* verifier embeds in VERIFICATION.md frontmatter (`covered_files`,
|
|
@@ -881,7 +1126,13 @@ function cmdVerificationResolveFile(cwd, phaseDirArg, raw) {
|
|
|
881
1126
|
* @param cwd - Current working directory.
|
|
882
1127
|
* @param phaseDirArg - Phase directory path (absolute or relative to cwd);
|
|
883
1128
|
* its project root is the base covered paths resolve against.
|
|
884
|
-
*
|
|
1129
|
+
* Must be an existing directory (#4623): with the
|
|
1130
|
+
* phase dir omitted, the first covered file used to be
|
|
1131
|
+
* taken as the phase dir and the rest hashed — a
|
|
1132
|
+
* plausible digest over the wrong set, at exit 0.
|
|
1133
|
+
* @param fileArgs - The argv tokens after the phase dir, parsed by
|
|
1134
|
+
* `parseFingerprintFileArgs`: covered-input paths
|
|
1135
|
+
* relative to the project root, bare or via `--files`.
|
|
885
1136
|
* @param raw - Whether to emit raw (non-JSON) output: just the
|
|
886
1137
|
* `covered_digest` string, so `VAR=$(gsd_run query
|
|
887
1138
|
* verification.fingerprint "$PHASE_DIR" ... --raw)` is
|
|
@@ -889,17 +1140,34 @@ function cmdVerificationResolveFile(cwd, phaseDirArg, raw) {
|
|
|
889
1140
|
* from the caller's own input list in that mode, so
|
|
890
1141
|
* only the computed digest needs a raw form.
|
|
891
1142
|
*/
|
|
892
|
-
function cmdVerificationFingerprint(cwd, phaseDirArg,
|
|
1143
|
+
function cmdVerificationFingerprint(cwd, phaseDirArg, fileArgs, raw) {
|
|
893
1144
|
if (!phaseDirArg) {
|
|
894
1145
|
error('phase directory required for verification.fingerprint');
|
|
895
1146
|
return;
|
|
896
1147
|
}
|
|
1148
|
+
const phaseDir = node_path_1.default.resolve(cwd, phaseDirArg);
|
|
1149
|
+
let phaseDirIsDir = false;
|
|
1150
|
+
try {
|
|
1151
|
+
phaseDirIsDir = node_fs_1.default.statSync(phaseDir).isDirectory();
|
|
1152
|
+
}
|
|
1153
|
+
catch {
|
|
1154
|
+
// not found → not a directory
|
|
1155
|
+
}
|
|
1156
|
+
if (!phaseDirIsDir) {
|
|
1157
|
+
error(`phase directory not found: ${phaseDirArg} — verification.fingerprint takes the phase directory first, then the covered files`);
|
|
1158
|
+
return;
|
|
1159
|
+
}
|
|
1160
|
+
const parsed = parseFingerprintFileArgs(fileArgs);
|
|
1161
|
+
if ('error' in parsed) {
|
|
1162
|
+
error(parsed.error);
|
|
1163
|
+
return;
|
|
1164
|
+
}
|
|
1165
|
+
const files = parsed.files;
|
|
897
1166
|
if (files.length === 0) {
|
|
898
1167
|
error('at least one covered file required for verification.fingerprint');
|
|
899
1168
|
return;
|
|
900
1169
|
}
|
|
901
|
-
const
|
|
902
|
-
const projectRoot = (0, project_root_cjs_1.findProjectRoot)(phaseDir);
|
|
1170
|
+
const projectRoot = (0, project_root_cjs_1.resolveProjectRoot)(phaseDir);
|
|
903
1171
|
// canonicalizeCoveredFiles here is for the emitted `covered_files` field —
|
|
904
1172
|
// computeCoveredDigest canonicalizes its own `coveredFiles` argument
|
|
905
1173
|
// internally too (it must, for callers like readVerificationStatus that
|
|
@@ -907,8 +1175,21 @@ function cmdVerificationFingerprint(cwd, phaseDirArg, files, raw) {
|
|
|
907
1175
|
// already-canonical list keeps that internal pass a cheap no-op rather
|
|
908
1176
|
// than a second meaningfully different canonicalization.
|
|
909
1177
|
const uniqueSorted = canonicalizeCoveredFiles(files);
|
|
910
|
-
const digest = computeCoveredDigest(projectRoot, uniqueSorted);
|
|
1178
|
+
const digest = computeCoveredDigest(projectRoot, uniqueSorted, FINGERPRINT_VERSION, { phaseDir });
|
|
911
1179
|
if (digest === null) {
|
|
1180
|
+
// #4623: name the one null that is NOT a bad path — a declaration made
|
|
1181
|
+
// only of shared planning documents hashes nothing under v2, and the
|
|
1182
|
+
// generic message below would send the caller looking for a missing file
|
|
1183
|
+
// that is not missing. Discriminated AFTER the v2 attempt, and only when a
|
|
1184
|
+
// v1 pass over the same list (which hashes, and therefore validates, every
|
|
1185
|
+
// path) succeeds: an all-shared list with a missing or directory member is
|
|
1186
|
+
// a bad path first, and gets the generic message.
|
|
1187
|
+
const sharedRoots = sharedPlanningRoots(projectRoot, phaseDir);
|
|
1188
|
+
if (uniqueSorted.every((f) => isSharedPlanningDoc(f, sharedRoots)) &&
|
|
1189
|
+
computeCoveredDigest(projectRoot, uniqueSorted, 1) !== null) {
|
|
1190
|
+
error(`could not compute fingerprint — every covered file is a repo-wide planning document (direct children of ${sharedRoots.join(', ')} never enter the digest); declare the phase's own artifacts and implementation files`);
|
|
1191
|
+
return;
|
|
1192
|
+
}
|
|
912
1193
|
error('could not compute fingerprint — a covered file is missing, unreadable, or escapes the project root');
|
|
913
1194
|
return;
|
|
914
1195
|
}
|
|
@@ -926,5 +1207,9 @@ module.exports = {
|
|
|
926
1207
|
cmdVerificationStatus,
|
|
927
1208
|
cmdVerificationResolveFile,
|
|
928
1209
|
computeCoveredDigest,
|
|
1210
|
+
sharedPlanningRoots,
|
|
1211
|
+
isSharedPlanningDoc,
|
|
1212
|
+
parseFingerprintVersion,
|
|
1213
|
+
parseFingerprintFileArgs,
|
|
929
1214
|
cmdVerificationFingerprint,
|
|
930
1215
|
};
|