@opengsd/gsd-core 1.11.0 → 1.13.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/.opencode/plugins/gsd-core.js +12 -0
- package/agents/gsd-code-fixer.md +1 -1
- package/agents/gsd-debug-session-manager.md +1 -1
- package/agents/gsd-debugger.md +1 -1
- package/agents/gsd-dom-verifier.md +169 -0
- package/agents/gsd-eval-auditor.md +1 -1
- package/agents/gsd-executor.md +78 -42
- package/agents/gsd-framework-selector.md +1 -3
- package/agents/gsd-intel-updater.md +1 -1
- package/agents/gsd-mempalace-curator.md +0 -1
- package/agents/gsd-pattern-mapper.md +11 -0
- package/agents/gsd-phase-researcher.md +3 -1
- package/agents/gsd-plan-checker.md +91 -112
- package/agents/gsd-planner.md +20 -4
- package/agents/gsd-project-researcher.md +1 -1
- package/agents/gsd-research-synthesizer.md +2 -2
- package/agents/gsd-roadmapper.md +15 -11
- package/agents/gsd-ui-checker.md +82 -7
- package/agents/gsd-ui-researcher.md +70 -3
- package/agents/gsd-verifier.md +24 -2
- package/bin/install.js +847 -200
- package/commands/gsd/discuss-phase.md +1 -1
- package/commands/gsd/execute-phase.md +1 -1
- package/commands/gsd/import.md +1 -1
- package/commands/gsd/ns-workflow.md +2 -1
- package/commands/gsd/phase.md +1 -1
- package/commands/gsd/quick-batch.md +105 -0
- package/commands/gsd/quick.md +8 -4
- package/commands/gsd/surface.md +18 -8
- package/gsd-core/bin/gsd-tools.cjs +761 -100
- package/gsd-core/bin/lib/active-workstream-store.cjs +8 -0
- package/gsd-core/bin/lib/adr-parser.cjs +13 -7
- package/gsd-core/bin/lib/agent-install-check.cjs +162 -0
- package/gsd-core/bin/lib/api-coverage.cjs +30 -9
- package/gsd-core/bin/lib/artifacts.cjs +2 -0
- package/gsd-core/bin/lib/assumption-delta.cjs +30 -11
- package/gsd-core/bin/lib/audit.cjs +163 -41
- package/gsd-core/bin/lib/broken-windows.cjs +306 -28
- package/gsd-core/bin/lib/capability-activation.cjs +27 -0
- package/gsd-core/bin/lib/capability-lock.cjs +10 -4
- package/gsd-core/bin/lib/capability-registry.cjs +785 -144
- package/gsd-core/bin/lib/capability-state.cjs +25 -4
- package/gsd-core/bin/lib/capability-validator.cjs +321 -18
- package/gsd-core/bin/lib/capability-writer.cjs +14 -4
- package/gsd-core/bin/lib/check-command-router.cjs +229 -6
- package/gsd-core/bin/lib/claude-orchestration.cjs +10 -25
- package/gsd-core/bin/lib/cli-exit.cjs +496 -10
- package/gsd-core/bin/lib/clusters.cjs +1 -0
- package/gsd-core/bin/lib/code-review-depth.cjs +288 -0
- package/gsd-core/bin/lib/codex-agent-toml.cjs +410 -4
- package/gsd-core/bin/lib/command-aliases.cjs +16 -0
- package/gsd-core/bin/lib/command-arg-projection.cjs +144 -14
- package/gsd-core/bin/lib/command-routing-hub.cjs +31 -2
- package/gsd-core/bin/lib/commands.cjs +877 -54
- package/gsd-core/bin/lib/complexity-trigger.cjs +26 -6
- package/gsd-core/bin/lib/config-loader.cjs +121 -29
- package/gsd-core/bin/lib/config.cjs +92 -2
- package/gsd-core/bin/lib/configuration.cjs +129 -37
- package/gsd-core/bin/lib/core-utils.cjs +118 -14
- package/gsd-core/bin/lib/decisions.cjs +213 -1
- package/gsd-core/bin/lib/edge-probe.cjs +23 -2
- package/gsd-core/bin/lib/estimate-cli.cjs +55 -11
- package/gsd-core/bin/lib/exit-code-registry.cjs +98 -0
- package/gsd-core/bin/lib/file-overlap-partitioner.cjs +74 -0
- package/gsd-core/bin/lib/frontmatter.cjs +975 -326
- package/gsd-core/bin/lib/gap-checker.cjs +41 -8
- package/gsd-core/bin/lib/git-base-branch.cjs +182 -39
- package/gsd-core/bin/lib/health-diagnostic-rules/consistency.cjs +7 -3
- package/gsd-core/bin/lib/health-diagnostic-rules/phase-structure.cjs +8 -2
- package/gsd-core/bin/lib/health-diagnostic-rules/roadmap-disk-consistency.cjs +60 -14
- package/gsd-core/bin/lib/health-diagnostic-rules/state-consistency.cjs +75 -22
- package/gsd-core/bin/lib/health-diagnostic-rules/worktree-health.cjs +22 -8
- package/gsd-core/bin/lib/health-diagnostic.cjs +23 -3
- package/gsd-core/bin/lib/host-integration.cjs +96 -11
- package/gsd-core/bin/lib/init-command-router.cjs +132 -21
- package/gsd-core/bin/lib/init.cjs +252 -56
- package/gsd-core/bin/lib/install-engine.cjs +252 -15
- package/gsd-core/bin/lib/install-model-override-resolver.cjs +78 -1
- package/gsd-core/bin/lib/install-profiles.cjs +100 -18
- package/gsd-core/bin/lib/installer-migration-report.cjs +4 -0
- package/gsd-core/bin/lib/installer-migrations/010-antigravity-retire-confighome-artifacts.cjs +169 -0
- package/gsd-core/bin/lib/installer-migrations.cjs +10 -7
- package/gsd-core/bin/lib/intel.cjs +101 -26
- package/gsd-core/bin/lib/io.cjs +195 -15
- package/gsd-core/bin/lib/learnings.cjs +85 -14
- package/gsd-core/bin/lib/legacy-cleanup.cjs +8 -2
- package/gsd-core/bin/lib/loop-resolver.cjs +14 -8
- package/gsd-core/bin/lib/markdown-table.cjs +175 -4
- package/gsd-core/bin/lib/milestone.cjs +112 -7
- package/gsd-core/bin/lib/model-catalog.cjs +177 -19
- package/gsd-core/bin/lib/model-resolver.cjs +10 -28
- package/gsd-core/bin/lib/onboard-projection.cjs +5 -1
- package/gsd-core/bin/lib/phase-command-router.cjs +13 -6
- package/gsd-core/bin/lib/phase-estimation.cjs +17 -8
- package/gsd-core/bin/lib/phase-id.cjs +321 -13
- package/gsd-core/bin/lib/phase-lifecycle.cjs +24 -16
- package/gsd-core/bin/lib/phase-locator.cjs +138 -17
- package/gsd-core/bin/lib/phase.cjs +1175 -115
- package/gsd-core/bin/lib/plan-document.cjs +273 -0
- package/gsd-core/bin/lib/plan-scan.cjs +13 -2
- package/gsd-core/bin/lib/planning-command-router.cjs +61 -0
- package/gsd-core/bin/lib/planning-inspect.cjs +1168 -0
- package/gsd-core/bin/lib/planning-snapshot.cjs +165 -34
- package/gsd-core/bin/lib/planning-workspace.cjs +159 -28
- package/gsd-core/bin/lib/probe-core.cjs +4 -1
- package/gsd-core/bin/lib/profile-pipeline-command-router.cjs +50 -7
- package/gsd-core/bin/lib/profile-pipeline.cjs +6 -3
- package/gsd-core/bin/lib/quick-batch-command-router.cjs +285 -0
- package/gsd-core/bin/lib/quick-batch-dispatch.cjs +250 -0
- package/gsd-core/bin/lib/quick-batch.cjs +840 -0
- package/gsd-core/bin/lib/real-home-guard.cjs +419 -0
- package/gsd-core/bin/lib/refactor-trigger-command-router.cjs +71 -45
- package/gsd-core/bin/lib/review-lane-descriptor.cjs +62 -14
- package/gsd-core/bin/lib/review-lane-invocation.cjs +73 -1
- package/gsd-core/bin/lib/review-lane-runner.cjs +136 -10
- package/gsd-core/bin/lib/roadmap-command-router.cjs +45 -31
- package/gsd-core/bin/lib/roadmap-parser.cjs +577 -41
- package/gsd-core/bin/lib/roadmap.cjs +248 -64
- package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +329 -41
- package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +16 -17
- package/gsd-core/bin/lib/runtime-artifact-layout.cjs +320 -109
- package/gsd-core/bin/lib/runtime-hooks-surface.cjs +487 -83
- package/gsd-core/bin/lib/runtime-identity.cjs +234 -0
- package/gsd-core/bin/lib/runtime-slash.cjs +72 -2
- package/gsd-core/bin/lib/shell-command-projection.cjs +75 -8
- package/gsd-core/bin/lib/smart-entry.cjs +19 -31
- package/gsd-core/bin/lib/spec-section.cjs +12 -7
- package/gsd-core/bin/lib/state-command-router.cjs +47 -18
- package/gsd-core/bin/lib/state-contract.cjs +359 -0
- package/gsd-core/bin/lib/state-document.cjs +216 -5
- package/gsd-core/bin/lib/state-md-schema.cjs +231 -0
- package/gsd-core/bin/lib/state-transition.cjs +850 -145
- package/gsd-core/bin/lib/state.cjs +1629 -287
- package/gsd-core/bin/lib/surface.cjs +33 -10
- package/gsd-core/bin/lib/task-command-router.cjs +111 -1
- package/gsd-core/bin/lib/task-content-resolution.cjs +368 -0
- package/gsd-core/bin/lib/tdd-red-evidence.cjs +133 -0
- package/gsd-core/bin/lib/teams-status.cjs +4 -1
- package/gsd-core/bin/lib/uat-predicate.cjs +58 -20
- package/gsd-core/bin/lib/uat.cjs +2542 -387
- package/gsd-core/bin/lib/ui-consideration-probe.cjs +9 -1
- package/gsd-core/bin/lib/ui-safety-gate.cjs +37 -7
- package/gsd-core/bin/lib/unusable-input.cjs +13 -0
- package/gsd-core/bin/lib/update-context.cjs +6 -2
- package/gsd-core/bin/lib/validate-command-router.cjs +2 -2
- package/gsd-core/bin/lib/validate.cjs +230 -12
- package/gsd-core/bin/lib/vendor/README.md +43 -5
- package/gsd-core/bin/lib/vendor/js-yaml.cjs +3014 -0
- package/gsd-core/bin/lib/verification-command-router.cjs +2 -1
- package/gsd-core/bin/lib/verification.cjs +287 -13
- package/gsd-core/bin/lib/verify-command-grounding.cjs +846 -0
- package/gsd-core/bin/lib/verify-command-router.cjs +1 -0
- package/gsd-core/bin/lib/verify.cjs +441 -56
- package/gsd-core/bin/lib/workstream-inventory.cjs +20 -2
- package/gsd-core/bin/lib/workstream-name-policy.cjs +25 -4
- package/gsd-core/bin/lib/worktree-base-ref.cjs +66 -12
- package/gsd-core/bin/lib/worktree-safety.cjs +185 -21
- package/gsd-core/bin/shared/config-defaults.manifest.json +7 -1
- package/gsd-core/bin/shared/config-schema.manifest.json +13 -0
- package/gsd-core/bin/shared/exit-codes.json +8 -0
- package/gsd-core/bin/shared/exit-codes.sh +20 -0
- package/gsd-core/bin/shared/model-catalog.json +8 -1
- package/gsd-core/bin/verify-reapply-patches.cjs +70 -3
- package/gsd-core/references/agent-contracts.md +6 -5
- package/gsd-core/references/api-coverage.md +24 -2
- package/gsd-core/references/autonomous-smart-discuss.md +3 -3
- package/gsd-core/references/checkpoints.md +37 -19
- package/gsd-core/references/decimal-phase-calculation.md +5 -5
- package/gsd-core/references/edge-probe.md +17 -5
- package/gsd-core/references/execute-mvp-tdd.md +18 -18
- package/gsd-core/references/execute-phase-between-wave-reset.md +9 -12
- package/gsd-core/references/execute-phase-response-language.md +6 -0
- package/gsd-core/references/execute-phase-wave-guard.md +11 -9
- package/gsd-core/references/executor-examples.md +42 -0
- package/gsd-core/references/failing-direction.md +78 -0
- package/gsd-core/references/few-shot-examples/plan-checker.md +15 -15
- package/gsd-core/references/gate-prompts.md +1 -1
- package/gsd-core/references/git-integration.md +5 -5
- package/gsd-core/references/git-planning-commit.md +3 -3
- package/gsd-core/references/gsd-run-resolver.md +1 -1
- package/gsd-core/references/loop-hook-dispatch.md +22 -0
- package/gsd-core/references/model-profiles.md +1 -1
- package/gsd-core/references/mvp-concepts.md +2 -2
- package/gsd-core/references/nyquist-compliance.md +74 -0
- package/gsd-core/references/offer-next.md +3 -5
- package/gsd-core/references/phase-argument-parsing.md +3 -3
- package/gsd-core/references/plan-checker-examples.md +41 -0
- package/gsd-core/references/planner-antipatterns.md +25 -0
- package/gsd-core/references/planner-chunked.md +5 -1
- package/gsd-core/references/planner-coupling.md +42 -0
- package/gsd-core/references/planner-failing-direction.md +53 -0
- package/gsd-core/references/planner-human-verify-mode.md +15 -1
- package/gsd-core/references/planner-quick-batch.md +71 -0
- package/gsd-core/references/planner-reviews.md +47 -0
- package/gsd-core/references/planner-revision.md +76 -3
- package/gsd-core/references/planner-verify-command-grounding.md +17 -0
- package/gsd-core/references/planning-config.md +39 -9
- package/gsd-core/references/response-language-directive.md +9 -0
- package/gsd-core/references/reviewer-instances.md +31 -0
- package/gsd-core/references/revision-loop.md +118 -11
- package/gsd-core/references/runtime-aware-dispatch.md +1 -1
- package/gsd-core/references/tdd.md +15 -12
- package/gsd-core/references/ui-brand.md +65 -21
- package/gsd-core/references/ui-consideration-probe.md +1 -1
- package/gsd-core/references/universal-anti-patterns.md +2 -2
- package/gsd-core/references/verifier-evidence-gate.md +160 -0
- package/gsd-core/references/verify-command-path-resolvability.md +42 -0
- package/gsd-core/references/verify-mvp-mode.md +1 -1
- package/gsd-core/references/workstream-flag.md +11 -11
- package/gsd-core/templates/README.md +1 -1
- package/gsd-core/templates/SECURITY.md +3 -3
- package/gsd-core/templates/UI-SPEC.md +25 -3
- package/gsd-core/templates/VALIDATION.md +3 -3
- package/gsd-core/templates/phase-prompt.md +7 -0
- package/gsd-core/templates/state.md +7 -0
- package/gsd-core/templates/verification-report.md +5 -0
- package/gsd-core/workflows/_runtime-launcher.snippet.sh +1 -1
- package/gsd-core/workflows/add-backlog.md +3 -1
- package/gsd-core/workflows/add-phase.md +5 -3
- package/gsd-core/workflows/add-tests.md +4 -9
- package/gsd-core/workflows/add-todo.md +2 -2
- package/gsd-core/workflows/ai-integration-phase.md +5 -10
- package/gsd-core/workflows/analyze-dependencies.md +2 -0
- package/gsd-core/workflows/audit-fix.md +14 -3
- package/gsd-core/workflows/audit-milestone.md +11 -9
- package/gsd-core/workflows/audit-uat.md +19 -2
- package/gsd-core/workflows/autonomous/steps/converge-fail-fast.md +2 -2
- package/gsd-core/workflows/autonomous.md +12 -26
- package/gsd-core/workflows/check-todos.md +2 -2
- package/gsd-core/workflows/cleanup.md +3 -3
- package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +16 -14
- package/gsd-core/workflows/code-review-fix.md +3 -1
- package/gsd-core/workflows/code-review.md +192 -69
- package/gsd-core/workflows/complete-milestone.md +28 -14
- package/gsd-core/workflows/debug.md +6 -4
- package/gsd-core/workflows/diagnose-issues.md +17 -7
- package/gsd-core/workflows/discuss-phase/modes/advisor.md +3 -1
- package/gsd-core/workflows/discuss-phase/modes/all.md +2 -0
- package/gsd-core/workflows/discuss-phase/modes/analyze.md +2 -0
- package/gsd-core/workflows/discuss-phase/modes/auto.md +2 -0
- package/gsd-core/workflows/discuss-phase/modes/batch.md +2 -0
- package/gsd-core/workflows/discuss-phase/modes/chain.md +5 -7
- package/gsd-core/workflows/discuss-phase/modes/default.md +2 -0
- package/gsd-core/workflows/discuss-phase/modes/power.md +2 -0
- package/gsd-core/workflows/discuss-phase/modes/text.md +3 -1
- package/gsd-core/workflows/discuss-phase/templates/context.md +2 -0
- package/gsd-core/workflows/discuss-phase/templates/discussion-log.md +2 -0
- package/gsd-core/workflows/discuss-phase-assumptions/steps/auto-advance-dispatch.md +1 -3
- package/gsd-core/workflows/discuss-phase-assumptions.md +3 -3
- package/gsd-core/workflows/discuss-phase-power.md +2 -0
- package/gsd-core/workflows/discuss-phase.md +2 -2
- package/gsd-core/workflows/do.md +46 -19
- package/gsd-core/workflows/docs-update.md +6 -5
- package/gsd-core/workflows/edit-phase.md +3 -1
- package/gsd-core/workflows/eval-review.md +5 -10
- package/gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md +3 -1
- package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +129 -11
- 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 +29 -5
- package/gsd-core/workflows/execute-phase/steps/post-merge-gate.md +2 -2
- package/gsd-core/workflows/execute-phase/steps/protected-branch.md +21 -0
- package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +4 -2
- package/gsd-core/workflows/execute-phase/steps/tdd-applicability-resolution.md +25 -0
- package/gsd-core/workflows/execute-phase/steps/wave-post-gate-hooks.md +39 -0
- package/gsd-core/workflows/execute-phase/steps/worktree-recovery-policy.md +2 -0
- package/gsd-core/workflows/execute-phase.md +68 -66
- package/gsd-core/workflows/execute-plan.md +25 -20
- package/gsd-core/workflows/explore.md +3 -1
- package/gsd-core/workflows/extract-learnings.md +3 -1
- package/gsd-core/workflows/fast.md +8 -2
- package/gsd-core/workflows/forensics.md +3 -1
- package/gsd-core/workflows/graduation.md +6 -6
- package/gsd-core/workflows/health.md +4 -7
- package/gsd-core/workflows/help/modes/brief.md +2 -0
- package/gsd-core/workflows/help/modes/default.md +2 -0
- package/gsd-core/workflows/help/modes/full.md +12 -0
- package/gsd-core/workflows/help/modes/topic.md +2 -0
- package/gsd-core/workflows/help.md +2 -0
- package/gsd-core/workflows/import.md +17 -14
- package/gsd-core/workflows/inbox.md +5 -6
- package/gsd-core/workflows/ingest-docs.md +45 -12
- package/gsd-core/workflows/insert-phase.md +7 -5
- package/gsd-core/workflows/list-phase-assumptions.md +2 -0
- package/gsd-core/workflows/list-seeds.md +7 -3
- package/gsd-core/workflows/list-workspaces.md +3 -1
- package/gsd-core/workflows/manager.md +15 -26
- package/gsd-core/workflows/map-codebase.md +3 -1
- package/gsd-core/workflows/milestone-summary.md +3 -1
- package/gsd-core/workflows/mvp-phase.md +3 -3
- package/gsd-core/workflows/new-milestone.md +10 -22
- package/gsd-core/workflows/new-project/steps/auto-mode-config.md +1 -1
- package/gsd-core/workflows/new-project.md +17 -29
- package/gsd-core/workflows/new-workspace.md +2 -2
- package/gsd-core/workflows/next.md +4 -2
- package/gsd-core/workflows/node-repair.md +2 -0
- package/gsd-core/workflows/note.md +2 -0
- package/gsd-core/workflows/onboard.md +1 -1
- package/gsd-core/workflows/pause-work.md +20 -5
- package/gsd-core/workflows/plan-phase/steps/adr-ingest-express-path.md +1 -1
- package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +100 -18
- package/gsd-core/workflows/plan-phase/steps/prd-express-path.md +4 -4
- package/gsd-core/workflows/plan-phase/steps/stall-detection-helpers.md +12 -3
- package/gsd-core/workflows/plan-phase.md +251 -54
- package/gsd-core/workflows/plan-review-convergence.md +148 -19
- package/gsd-core/workflows/plant-seed.md +3 -3
- package/gsd-core/workflows/pr-branch.md +195 -51
- package/gsd-core/workflows/profile-user.md +17 -15
- package/gsd-core/workflows/progress/steps/forensic-audit.md +1 -1
- package/gsd-core/workflows/progress.md +52 -15
- package/gsd-core/workflows/quick/steps/discussion-phase.md +1 -3
- package/gsd-core/workflows/quick/steps/plan-checker-loop.md +38 -5
- package/gsd-core/workflows/quick/steps/quick-verification.md +2 -4
- package/gsd-core/workflows/quick/steps/research-phase.md +5 -7
- package/gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md +3 -3
- package/gsd-core/workflows/quick-batch/steps/batch-init.md +55 -0
- package/gsd-core/workflows/quick-batch/steps/completion.md +65 -0
- package/gsd-core/workflows/quick-batch/steps/merge-wave.md +100 -0
- package/gsd-core/workflows/quick-batch/steps/plan-checker-loop.md +147 -0
- package/gsd-core/workflows/quick-batch/steps/planner-wave.md +158 -0
- package/gsd-core/workflows/quick-batch/steps/research-phase.md +95 -0
- package/gsd-core/workflows/quick-batch/steps/resume-mode.md +49 -0
- package/gsd-core/workflows/quick-batch/steps/verification-wave.md +73 -0
- package/gsd-core/workflows/quick-batch/steps/worktree-dispatch.md +169 -0
- package/gsd-core/workflows/quick-batch.md +203 -0
- package/gsd-core/workflows/quick.md +33 -32
- package/gsd-core/workflows/reapply-patches.md +2 -0
- package/gsd-core/workflows/remove-phase.md +6 -4
- package/gsd-core/workflows/remove-workspace.md +3 -3
- package/gsd-core/workflows/resume-project.md +14 -14
- package/gsd-core/workflows/review.md +404 -21
- package/gsd-core/workflows/scan.md +3 -1
- package/gsd-core/workflows/section-manifest.json +12 -0
- package/gsd-core/workflows/secure-phase.md +3 -3
- package/gsd-core/workflows/session-report.md +2 -0
- package/gsd-core/workflows/settings-advanced.md +9 -9
- package/gsd-core/workflows/settings-integrations.md +66 -32
- package/gsd-core/workflows/settings.md +4 -6
- package/gsd-core/workflows/ship.md +22 -16
- package/gsd-core/workflows/sketch-wrap-up.md +13 -17
- package/gsd-core/workflows/sketch.md +13 -19
- package/gsd-core/workflows/smart-entry.md +4 -6
- package/gsd-core/workflows/spec-phase.md +31 -4
- package/gsd-core/workflows/spike-wrap-up.md +9 -11
- package/gsd-core/workflows/spike.md +21 -32
- package/gsd-core/workflows/stats.md +4 -2
- package/gsd-core/workflows/sync-skills.md +13 -5
- package/gsd-core/workflows/thread.md +13 -7
- package/gsd-core/workflows/transition.md +7 -5
- package/gsd-core/workflows/ui-phase.md +36 -21
- package/gsd-core/workflows/ui-review.md +7 -11
- package/gsd-core/workflows/ultraplan-phase.md +7 -13
- package/gsd-core/workflows/undo.md +9 -17
- package/gsd-core/workflows/update.md +47 -48
- package/gsd-core/workflows/validate-phase.md +3 -3
- package/gsd-core/workflows/verify-work/steps/automated-ui-verification.md +25 -1
- package/gsd-core/workflows/verify-work/steps/mvp-uat-framing.md +1 -1
- package/gsd-core/workflows/verify-work.md +106 -21
- package/hooks/dist/gsd-agent-isolation-guard.js +77 -38
- package/hooks/dist/gsd-check-update-worker.js +19 -2
- package/hooks/dist/gsd-config-reload.js +18 -12
- package/hooks/dist/gsd-context-monitor.js +302 -22
- package/hooks/dist/gsd-cursor-post-tool.js +3 -1
- package/hooks/dist/gsd-cursor-pre-tool.js +3 -1
- package/hooks/dist/gsd-cursor-session-start.js +2 -1
- package/hooks/dist/gsd-cursor-stop.js +2 -1
- package/hooks/dist/gsd-cursor-subagent-start.js +28 -23
- package/hooks/dist/gsd-cursor-subagent-stop.js +3 -1
- package/hooks/dist/gsd-ensure-canonical-path.js +2 -1
- package/hooks/dist/gsd-graphify-update.sh +22 -18
- package/hooks/dist/gsd-node-runner.sh +77 -0
- package/hooks/dist/gsd-phase-boundary.sh +1 -0
- package/hooks/dist/gsd-prompt-guard.js +46 -12
- package/hooks/dist/gsd-read-guard.js +18 -7
- package/hooks/dist/gsd-read-injection-scanner.js +22 -13
- package/hooks/dist/gsd-secret-read-guard.js +1079 -0
- package/hooks/dist/gsd-session-state.sh +1 -0
- package/hooks/dist/gsd-statusline.js +222 -29
- package/hooks/dist/gsd-validate-commit.sh +523 -12
- package/hooks/dist/gsd-windsurf-pre-command.js +16 -11
- package/hooks/dist/gsd-windsurf-pre-write.js +22 -13
- package/hooks/dist/gsd-workflow-guard.js +36 -17
- package/hooks/dist/gsd-worktree-path-guard.js +36 -21
- package/hooks/dist/gsd-write-guard.js +35 -25
- package/hooks/dist/lib/cli-exit.js +560 -0
- package/hooks/dist/lib/exit-code-registry.js +98 -0
- package/hooks/dist/lib/git-cmd.js +210 -1
- package/hooks/dist/lib/git-probe.js +84 -0
- package/hooks/dist/lib/hook-exit.js +81 -0
- package/hooks/dist/lib/injection-patterns.js +36 -6
- package/hooks/dist/managed-hooks-registry.cjs +4 -0
- package/hooks/gsd-agent-isolation-guard.js +77 -38
- package/hooks/gsd-check-update-worker.js +19 -2
- package/hooks/gsd-config-reload.js +18 -12
- package/hooks/gsd-context-monitor.js +302 -22
- package/hooks/gsd-cursor-post-tool.js +3 -1
- package/hooks/gsd-cursor-pre-tool.js +3 -1
- package/hooks/gsd-cursor-session-start.js +2 -1
- package/hooks/gsd-cursor-stop.js +2 -1
- package/hooks/gsd-cursor-subagent-start.js +28 -23
- package/hooks/gsd-cursor-subagent-stop.js +3 -1
- package/hooks/gsd-ensure-canonical-path.js +2 -1
- package/hooks/gsd-graphify-update.sh +22 -18
- package/hooks/gsd-node-runner.sh +77 -0
- package/hooks/gsd-phase-boundary.sh +1 -0
- package/hooks/gsd-prompt-guard.js +46 -12
- package/hooks/gsd-read-guard.js +18 -7
- package/hooks/gsd-read-injection-scanner.js +22 -13
- package/hooks/gsd-secret-read-guard.js +1079 -0
- package/hooks/gsd-session-state.sh +1 -0
- package/hooks/gsd-statusline.js +222 -29
- package/hooks/gsd-validate-commit.sh +523 -12
- package/hooks/gsd-windsurf-pre-command.js +16 -11
- package/hooks/gsd-windsurf-pre-write.js +22 -13
- package/hooks/gsd-workflow-guard.js +36 -17
- package/hooks/gsd-worktree-path-guard.js +36 -21
- package/hooks/gsd-write-guard.js +35 -25
- package/hooks/hooks.json +6 -0
- package/hooks/lib/cli-exit.js +560 -0
- package/hooks/lib/exit-code-registry.js +98 -0
- package/hooks/lib/git-cmd.js +210 -1
- package/hooks/lib/git-probe.js +84 -0
- package/hooks/lib/hook-exit.js +81 -0
- package/hooks/lib/injection-patterns.js +36 -6
- package/hooks/managed-hooks-registry.cjs +4 -0
- package/package.json +14 -9
- package/scripts/base64-scan.sh +74 -12
- package/scripts/build-hooks.js +12 -0
- package/scripts/check-glossary-refs.cjs +77 -15
- package/scripts/check-mutation-score-ratchet.cjs +156 -0
- package/scripts/ci-check-job-near-cap.cjs +49 -0
- package/scripts/ci-pr-mergeability.cjs +262 -0
- package/scripts/ci-test-scope.cjs +52 -12
- package/scripts/ci-timeout-report.cjs +230 -0
- package/scripts/docs-guard-registry.cjs +406 -0
- package/scripts/gen-capability-registry.cjs +8 -6
- package/scripts/gen-exit-code-docs.cjs +318 -0
- package/scripts/gen-exit-code-registry.cjs +891 -0
- package/scripts/gen-features.cjs +836 -0
- package/scripts/gen-hooks-cli-exit.cjs +239 -0
- package/scripts/gen-install-tree-fixtures.cjs +2 -2
- package/scripts/gen-loop-host-contract.cjs +189 -4
- package/scripts/gen-scripts-cli-exit.cjs +185 -0
- package/scripts/gen-state-md-docs.cjs +727 -0
- package/scripts/{test-failure-reasons.cjs → gsd-test-gate-reasons.cjs} +6 -0
- package/scripts/lib/ci-job-timing.cjs +72 -0
- package/scripts/lib/cli-exit.cjs +546 -44
- package/scripts/lib/drift-scan.cjs +32 -2
- package/scripts/lib/exit-code-registry.cjs +98 -0
- package/scripts/lib/ndjson-reporter.cjs +119 -0
- package/scripts/lib/shellcheck-fetch.cjs +247 -0
- package/scripts/lint-allow-test-rule-refs.allowlist.json +0 -6
- package/scripts/lint-allow-test-rule-refs.effective-ceiling.json +1 -1
- package/scripts/lint-allow-test-rule-refs.unverified-ceiling.json +1 -1
- package/scripts/lint-docs-guard-registration.cjs +495 -0
- package/scripts/lint-docs-guard-registration.exempt-baseline.cjs +198 -0
- package/scripts/lint-eslint-glob-coverage.allowlist.json +4 -0
- package/scripts/{lint-fix-has-regression-test.cjs → lint-fix-has-regression-tests.cjs} +12 -6
- package/scripts/lint-health-diagnostic-rule-table.cjs +65 -8
- package/scripts/lint-mutation-test-derivation-drift.cjs +86 -0
- package/scripts/lint-phase-enumeration-drift.cjs +45 -14
- package/scripts/lint-phase-id-drift.cjs +133 -8
- package/scripts/lint-planning-prompt-drift.cjs +38 -1
- package/scripts/lint-portable-grep.cjs +176 -0
- package/scripts/lint-removed-but-needed.cjs +184 -16
- package/scripts/lint-response-language-coverage.cjs +524 -0
- package/scripts/lint-seam-enforcement.cjs +182 -0
- package/scripts/lint-slug-derivation-drift.cjs +921 -0
- package/scripts/lint-source-test-name-collision.cjs +241 -0
- package/scripts/lint-state-write-path-drift.cjs +337 -432
- package/scripts/lint-test-file-count.allowlist.json +124 -4
- package/scripts/lint-test-file-count.cjs +25 -3
- package/scripts/lint-unreachable-guard-drift.cjs +51 -64
- package/scripts/lint-vendored-deps.cjs +208 -35
- package/scripts/lint-workflow-shellcheck-baseline.json +1027 -0
- package/scripts/lint-workflow-shellcheck.cjs +614 -0
- package/scripts/mutation-matrix.cjs +599 -50
- package/scripts/npm-audit-baseline.cjs +376 -0
- package/scripts/prompt-injection-scan.sh +83 -14
- package/scripts/require-issue-link-policy.cjs +16 -1
- package/scripts/secret-scan.sh +75 -13
- package/scripts/select-docs-guards.cjs +56 -0
- package/scripts/sync-runtime-launcher.cjs +22 -3
- package/skills/gsd-discuss-phase/SKILL.md +1 -1
- package/skills/gsd-execute-phase/SKILL.md +1 -1
- package/skills/gsd-import/SKILL.md +1 -1
- package/skills/gsd-ns-workflow/SKILL.md +1 -0
- package/skills/gsd-phase/SKILL.md +1 -1
- package/skills/gsd-quick/SKILL.md +8 -4
- package/skills/gsd-quick-batch/SKILL.md +105 -0
- package/skills/gsd-surface/SKILL.md +18 -8
- package/vscode/package.json +1 -1
- package/bin/lib/ui-safety-gate.cjs +0 -109
- package/scripts/lint-emitted-drift-ack.cjs +0 -344
- package/scripts/state-write-path-drift-baseline.json +0 -19
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
const cjsCommandRouterAdapter = require("./cjs-command-router-adapter.cjs");
|
|
14
14
|
const { routeCjsCommandFamily } = cjsCommandRouterAdapter;
|
|
15
15
|
// ─── Implementation ───────────────────────────────────────────────────────────
|
|
16
|
-
const VERIFICATION_SUBCOMMANDS = ['status', 'resolve-file'];
|
|
16
|
+
const VERIFICATION_SUBCOMMANDS = ['status', 'resolve-file', 'fingerprint'];
|
|
17
17
|
function routeVerificationCommand({ verification, args, cwd, raw, error, }) {
|
|
18
18
|
routeCjsCommandFamily({
|
|
19
19
|
args,
|
|
@@ -24,6 +24,7 @@ function routeVerificationCommand({ verification, args, cwd, raw, error, }) {
|
|
|
24
24
|
handlers: {
|
|
25
25
|
status: () => verification.cmdVerificationStatus(cwd, args[2], raw),
|
|
26
26
|
'resolve-file': () => verification.cmdVerificationResolveFile(cwd, args[2], raw),
|
|
27
|
+
fingerprint: () => verification.cmdVerificationFingerprint(cwd, args[2], args.slice(3), raw),
|
|
27
28
|
},
|
|
28
29
|
});
|
|
29
30
|
}
|
|
@@ -32,6 +32,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
32
32
|
};
|
|
33
33
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
34
34
|
const node_path_1 = __importDefault(require("node:path"));
|
|
35
|
+
const node_crypto_1 = __importDefault(require("node:crypto"));
|
|
36
|
+
const project_root_cjs_1 = require("./project-root.cjs");
|
|
35
37
|
// eslint-disable-next-line @typescript-eslint/no-require-imports -- io.cjs is an export= CommonJS module
|
|
36
38
|
const io = require("./io.cjs");
|
|
37
39
|
// eslint-disable-next-line @typescript-eslint/no-require-imports -- phase-id.cjs is an export= CommonJS module
|
|
@@ -40,6 +42,8 @@ const phaseId = require("./phase-id.cjs");
|
|
|
40
42
|
const frontmatterMod = require("./frontmatter.cjs");
|
|
41
43
|
// eslint-disable-next-line @typescript-eslint/no-require-imports -- plan-scan.cjs is an export= CommonJS module
|
|
42
44
|
const scanPhasePlans = require("./plan-scan.cjs");
|
|
45
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports -- core-utils.cjs is an export= CommonJS module
|
|
46
|
+
const coreUtilsMod = require("./core-utils.cjs");
|
|
43
47
|
// eslint-disable-next-line @typescript-eslint/no-require-imports -- planning-scope.cjs is an export= CommonJS module
|
|
44
48
|
const planningScopeMod = require("./planning-scope.cjs");
|
|
45
49
|
const shell_command_projection_cjs_1 = require("./shell-command-projection.cjs");
|
|
@@ -47,6 +51,7 @@ const runtime_slash_cjs_1 = require("./runtime-slash.cjs");
|
|
|
47
51
|
const { output, error } = io;
|
|
48
52
|
const { extractPhaseToken, scopeToPhase } = phaseId;
|
|
49
53
|
const { extractFrontmatter } = frontmatterMod;
|
|
54
|
+
const { normalizeLineEndings } = coreUtilsMod;
|
|
50
55
|
const { SCOPE } = planningScopeMod;
|
|
51
56
|
// ─── Constants ────────────────────────────────────────────────────────────────
|
|
52
57
|
/** The set of status values that the gsd-verifier agent emits. */
|
|
@@ -128,10 +133,180 @@ function projectNextCommand(bare, runtime, tail = '') {
|
|
|
128
133
|
return '';
|
|
129
134
|
return `${(0, runtime_slash_cjs_1.formatGsdSlash)(bare, runtime)}${tail}`;
|
|
130
135
|
}
|
|
136
|
+
/**
|
|
137
|
+
* Real `node:fs`-backed default satisfying FsLike. Every method wraps a call
|
|
138
|
+
* to `fs.<method>` rather than capturing the function reference — existing
|
|
139
|
+
* tests mock individual `fs` methods in place (`t.mock.method(fs, 'statSync', …)`),
|
|
140
|
+
* and a captured reference taken at module-load time would be invisible to
|
|
141
|
+
* that late mock, silently un-mocking this seam's "default" path.
|
|
142
|
+
*/
|
|
143
|
+
const defaultFsImpl = {
|
|
144
|
+
readdirSync: (dir) => node_fs_1.default.readdirSync(dir),
|
|
145
|
+
readFileSync: (filePath, encoding) => node_fs_1.default.readFileSync(filePath, encoding),
|
|
146
|
+
statSync: (filePath) => node_fs_1.default.statSync(filePath),
|
|
147
|
+
};
|
|
131
148
|
/** Normalize separators to posix (git emits `/`; callers may pass `\` on Windows). */
|
|
132
149
|
function toPosix(p) {
|
|
133
150
|
return p.replace(/\\/g, '/');
|
|
134
151
|
}
|
|
152
|
+
/**
|
|
153
|
+
* #4155: canonicalize a covered-input path before it becomes either a
|
|
154
|
+
* dedup/sort/hash key or a confinement-check subject. `path.posix.normalize`
|
|
155
|
+
* collapses `./`, redundant slashes, and internal `..` segments (`a/../../b`
|
|
156
|
+
* → `../b`) — without this, two spellings of the SAME file (`src/x.cts` vs
|
|
157
|
+
* `./src/x.cts`) hash as different covered inputs (spurious `stale`, or a
|
|
158
|
+
* file double-counted into the digest under two keys), and an escape
|
|
159
|
+
* disguised by an internal `..` segment slips past a check that only looks
|
|
160
|
+
* at the string's start.
|
|
161
|
+
*/
|
|
162
|
+
function normalizeRel(p) {
|
|
163
|
+
return node_path_1.default.posix.normalize(toPosix(p));
|
|
164
|
+
}
|
|
165
|
+
/** Canonicalize a covered-files list: normalize, de-duplicate, sort — the SAME
|
|
166
|
+
* transform computeCoveredDigest and cmdVerificationFingerprint both need
|
|
167
|
+
* (the digest's own key order; the CLI's own `covered_files` JSON output). */
|
|
168
|
+
function canonicalizeCoveredFiles(files) {
|
|
169
|
+
return Array.from(new Set(files.map(normalizeRel))).sort();
|
|
170
|
+
}
|
|
171
|
+
// ─── #4155: covered-input fingerprint ──────────────────────────────────────────
|
|
172
|
+
/**
|
|
173
|
+
* Bump on any change to the digest's input shape (path list, hashing order,
|
|
174
|
+
* per-file hash algorithm) so an old stored digest can never collide with a
|
|
175
|
+
* differently-computed new one — a version mismatch is just a mismatch.
|
|
176
|
+
*/
|
|
177
|
+
const FINGERPRINT_VERSION = 1;
|
|
178
|
+
/**
|
|
179
|
+
* #4155: recompute the deterministic content fingerprint over a verifier's
|
|
180
|
+
* declared covered-input set (phase PLAN/SUMMARY, mapped requirements,
|
|
181
|
+
* implementation files in the change set) and return the versioned digest
|
|
182
|
+
* string, or `null` if the set cannot be resolved.
|
|
183
|
+
*
|
|
184
|
+
* Determinism: paths are de-duplicated and SORTED before hashing (directory
|
|
185
|
+
* enumeration order is irrelevant), each path is resolved relative to
|
|
186
|
+
* `projectRoot` (the absolute checkout path never enters the digest), and
|
|
187
|
+
* file BYTES are hashed (mtime never enters the digest).
|
|
188
|
+
*
|
|
189
|
+
* NOT normalized: line endings. Unlike the report-frontmatter read (which
|
|
190
|
+
* runs every VERIFICATION.md through `normalizeLineEndings`), covered-file
|
|
191
|
+
* bytes are hashed exactly as they sit on disk. A covered text file checked
|
|
192
|
+
* out with CRLF line endings (e.g. a Windows checkout without a `.gitattributes
|
|
193
|
+
* eol=lf` rule pinning it to LF) hashes differently than the same file on an
|
|
194
|
+
* LF checkout — a real cross-platform digest mismatch, not a bug, since GSD
|
|
195
|
+
* installs into arbitrary user projects with no guaranteed line-ending policy.
|
|
196
|
+
*
|
|
197
|
+
|
|
198
|
+
* Fail closed: a covered path that is empty, absolute, escapes
|
|
199
|
+
* `projectRoot` (`..` traversal), or cannot be read (missing, unreadable,
|
|
200
|
+
* not a regular file) makes the WHOLE fingerprint unresolvable — returns
|
|
201
|
+
* `null` — rather than silently hashing a partial set. Callers treat `null`
|
|
202
|
+
* as stale (#4155), the same fail-closed shape #3057 B3 established for the
|
|
203
|
+
* legacy mtime staleness check.
|
|
204
|
+
*
|
|
205
|
+
* Always reads through the REAL `node:fs`, never a caller-injected `FsLike`
|
|
206
|
+
* seam — same reasoning as the root canonicalization below, extended to
|
|
207
|
+
* every covered file: `covered_files` is expected to span the whole
|
|
208
|
+
* `projectRoot` (implementation files under `src/`, not just `.planning/`
|
|
209
|
+
* artifacts), so a caller-scoped containment wrapper narrower than
|
|
210
|
+
* `projectRoot` (e.g. `planning-inspect.cts`'s `containmentEnforcingVerificationFs`,
|
|
211
|
+
* confined to `.planning/`) would reject every implementation-file read and
|
|
212
|
+
* report EVERY fingerprinted phase permanently `stale` regardless of actual
|
|
213
|
+
* drift — the bug this comment now documents against regressing. The
|
|
214
|
+
* `realRel`-vs-`realRoot` re-check a few lines below already does the real
|
|
215
|
+
* confinement work (against `projectRoot`, the correct boundary for this
|
|
216
|
+
* data), so no security property is lost by bypassing a narrower seam here.
|
|
217
|
+
*/
|
|
218
|
+
function computeCoveredDigest(projectRoot, coveredFiles) {
|
|
219
|
+
const uniqueSorted = canonicalizeCoveredFiles(coveredFiles);
|
|
220
|
+
if (uniqueSorted.length === 0)
|
|
221
|
+
return null;
|
|
222
|
+
// Canonicalize the root ONCE — every candidate's realpath is checked against
|
|
223
|
+
// this, not the possibly-symlinked `projectRoot` argument itself. Always via
|
|
224
|
+
// the REAL fs, never fsImpl: `projectRoot` is a trusted anchor the CALLER
|
|
225
|
+
// derived (findProjectRoot), not attacker-influenced covered-input data —
|
|
226
|
+
// routing it through a caller-scoped containment seam (e.g. #4155's
|
|
227
|
+
// containmentEnforcingVerificationFs, confined to `.planning/`, a proper
|
|
228
|
+
// SUBSET of `projectRoot`) would reject the root itself and fail every
|
|
229
|
+
// lookup regardless of whether the covered files are legitimate.
|
|
230
|
+
let realRoot;
|
|
231
|
+
try {
|
|
232
|
+
realRoot = node_fs_1.default.realpathSync(projectRoot);
|
|
233
|
+
}
|
|
234
|
+
catch {
|
|
235
|
+
return null;
|
|
236
|
+
}
|
|
237
|
+
const parts = [];
|
|
238
|
+
for (const rel of uniqueSorted) {
|
|
239
|
+
// `normalizeRel` (already applied by `canonicalizeCoveredFiles` above)
|
|
240
|
+
// collapses internal `..` segments before `rel` ever reaches here
|
|
241
|
+
// (`a/../../b` → `../b`), so this start-of-string check is already the
|
|
242
|
+
// full lexical confinement test — no separate post-`path.resolve`
|
|
243
|
+
// re-check can observe a different answer.
|
|
244
|
+
if (rel === '' || rel === '..' || rel.startsWith('../') || node_path_1.default.isAbsolute(rel))
|
|
245
|
+
return null;
|
|
246
|
+
const resolved = node_path_1.default.resolve(projectRoot, rel);
|
|
247
|
+
let bytes;
|
|
248
|
+
try {
|
|
249
|
+
// A regular file INSIDE projectRoot can still be a symlink whose TARGET
|
|
250
|
+
// escapes it — statSync/readFileSync follow symlinks, so the lexical
|
|
251
|
+
// confinement check above is not enough. realpathSync resolves the
|
|
252
|
+
// actual target; re-confining against realRoot closes that gap.
|
|
253
|
+
const real = node_fs_1.default.realpathSync(resolved);
|
|
254
|
+
const realRel = node_path_1.default.relative(realRoot, real);
|
|
255
|
+
if (realRel === '' || realRel === '..' || realRel.startsWith(`..${node_path_1.default.sep}`) || node_path_1.default.isAbsolute(realRel)) {
|
|
256
|
+
return null;
|
|
257
|
+
}
|
|
258
|
+
const st = node_fs_1.default.statSync(real);
|
|
259
|
+
if (!st.isFile())
|
|
260
|
+
return null;
|
|
261
|
+
bytes = node_fs_1.default.readFileSync(real);
|
|
262
|
+
}
|
|
263
|
+
catch {
|
|
264
|
+
return null;
|
|
265
|
+
}
|
|
266
|
+
const fileHash = node_crypto_1.default.createHash('sha256').update(bytes).digest('hex');
|
|
267
|
+
parts.push(`${rel}\n${fileHash}\n`);
|
|
268
|
+
}
|
|
269
|
+
const aggregate = node_crypto_1.default
|
|
270
|
+
.createHash('sha256')
|
|
271
|
+
.update(`v${FINGERPRINT_VERSION}\n${parts.join('')}`, 'utf-8')
|
|
272
|
+
.digest('hex');
|
|
273
|
+
return `v${FINGERPRINT_VERSION}:sha256:${aggregate}`;
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* #4155: the content fingerprint only recomputes digests for paths the
|
|
277
|
+
* verifier actually DECLARED in `covered_files` — it has no way to notice a
|
|
278
|
+
* plan or summary added to the phase directory AFTER verification if that
|
|
279
|
+
* new file was never declared. This closes that gap the same way the
|
|
280
|
+
* legacy mtime check always did: by re-scanning the LIVE directory (not the
|
|
281
|
+
* declared list) for every current `*-PLAN.md`/`*-SUMMARY.md` and checking
|
|
282
|
+
* each is represented in `coveredFiles` — matched by suffix (mirrors
|
|
283
|
+
* `matchRequestedFile`'s convention) since `coveredFiles` holds
|
|
284
|
+
* project-root-relative paths while the scan returns phase-relative
|
|
285
|
+
* filenames. Returns `true` if every current plan/summary is covered,
|
|
286
|
+
* `false` otherwise — callers only ever branch on this pass/fail, so no
|
|
287
|
+
* caller needs which artifact was uncovered.
|
|
288
|
+
*
|
|
289
|
+
* Fails CLOSED on an incomplete scan: `scanPhasePlans` never throws on a
|
|
290
|
+
* readdir failure — it reports it via `scope` (`SCOPE.UNREADABLE` for the
|
|
291
|
+
* phase dir itself, `SCOPE.TRUNCATED` for an unreadable nested `plans/`)
|
|
292
|
+
* with whatever files it DID manage to enumerate, per `SCOPE`'s own
|
|
293
|
+
* contract (`planning-scope.cts`): zero items under a non-`COMPLETE` scope
|
|
294
|
+
* is a NON-answer, never "this phase has no plans." Branching on `scope`
|
|
295
|
+
* here (rather than a try/catch, which this scan never triggers) is what
|
|
296
|
+
* makes an unreadable `plans/` dir report `false` instead of silently
|
|
297
|
+
* treating its invisible contents as vacuously covered — the same
|
|
298
|
+
* fail-open regression #3057 B3 fixed for the legacy path.
|
|
299
|
+
*/
|
|
300
|
+
function allCurrentArtifactsCovered(phaseDir, coveredFiles) {
|
|
301
|
+
const scan = scanPhasePlans(phaseDir);
|
|
302
|
+
if (scan.scope !== SCOPE.COMPLETE)
|
|
303
|
+
return false;
|
|
304
|
+
const coveredPosix = canonicalizeCoveredFiles(coveredFiles);
|
|
305
|
+
return [...scan.allPlanFiles, ...scan.summaryFiles].every((artifact) => {
|
|
306
|
+
const artifactPosix = toPosix(artifact);
|
|
307
|
+
return coveredPosix.some((c) => c === artifactPosix || c.endsWith(`/${artifactPosix}`));
|
|
308
|
+
});
|
|
309
|
+
}
|
|
135
310
|
/**
|
|
136
311
|
* Match a git-emitted (repo-root-relative) path back to the caller's
|
|
137
312
|
* phaseDir-relative request by exact match or `/`-bounded suffix — precise
|
|
@@ -353,7 +528,7 @@ function resolveVerificationFile(entries, options = {}) {
|
|
|
353
528
|
function resolveUatFile(entries, options = {}) {
|
|
354
529
|
return resolvePhaseArtifactFile(entries, 'UAT.md', options);
|
|
355
530
|
}
|
|
356
|
-
function findStaleVerificationSummary(phaseDir, fsImpl =
|
|
531
|
+
function findStaleVerificationSummary(phaseDir, fsImpl = defaultFsImpl, phaseCleanCommitTimesMs = defaultPhaseCleanCommitTimesMs) {
|
|
357
532
|
// FS errors (TOCTOU: a SUMMARY listed by scanPhasePlans then removed before statSync;
|
|
358
533
|
// unreadable dir; broken symlink; file->dir swap) must degrade rather than throw
|
|
359
534
|
// uncaught into callers that are NOT under the planning lock (init.manager /
|
|
@@ -430,7 +605,7 @@ function findStaleVerificationSummary(phaseDir, fsImpl = node_fs_1.default, phas
|
|
|
430
605
|
* projected into (#2617).
|
|
431
606
|
*/
|
|
432
607
|
function readVerificationStatus(phaseDir, opts = {}) {
|
|
433
|
-
const fsImpl = opts.fs ??
|
|
608
|
+
const fsImpl = opts.fs ?? defaultFsImpl;
|
|
434
609
|
const phaseCleanCommitTimesMs = opts.phaseCleanCommitTimesMs ?? defaultPhaseCleanCommitTimesMs;
|
|
435
610
|
const runtime = opts.runtime ?? 'claude';
|
|
436
611
|
// Phase token for the gaps_found command
|
|
@@ -467,9 +642,20 @@ function readVerificationStatus(phaseDir, opts = {}) {
|
|
|
467
642
|
// extractFrontmatter anchors at byte 0, so body `status:` lines are ignored.
|
|
468
643
|
const filePath = node_path_1.default.join(phaseDir, verificationFile);
|
|
469
644
|
let rawStatus = null;
|
|
645
|
+
let fm = {};
|
|
470
646
|
try {
|
|
471
|
-
|
|
472
|
-
|
|
647
|
+
// #3707-CR follow-up MINOR 1: normalize line endings at this read
|
|
648
|
+
// boundary — this function's own `readFileSync` is the equivalent seam
|
|
649
|
+
// `planning.inspect`'s `buildUatRows`/`readDocument` route through for
|
|
650
|
+
// UAT/REQUIREMENTS documents, but `readVerificationStatus` had no such
|
|
651
|
+
// normalization of its own. A lone-CR VERIFICATION.md's `---\r...\r---`
|
|
652
|
+
// frontmatter fence never matched `extractFrontmatter`'s byte-0
|
|
653
|
+
// `---\n`/`---\r\n` check, so `status: passed` was read as absent and
|
|
654
|
+
// this function reported 'missing' — under-reporting a completed
|
|
655
|
+
// verification as if the step never ran, the fail-safe direction but the
|
|
656
|
+
// same root cause as the false-clean class fixed elsewhere in #3707-CR.
|
|
657
|
+
const content = normalizeLineEndings(fsImpl.readFileSync(filePath, 'utf-8'));
|
|
658
|
+
fm = extractFrontmatter(content, filePath);
|
|
473
659
|
const statusVal = fm['status'];
|
|
474
660
|
// status is always a scalar string in a well-formed VERIFICATION.md frontmatter;
|
|
475
661
|
// only accept string values — arrays and objects are not valid status values.
|
|
@@ -494,8 +680,52 @@ function readVerificationStatus(phaseDir, opts = {}) {
|
|
|
494
680
|
next_command: projectNextCommand('plan-phase', runtime, `${phaseArg} --gaps`),
|
|
495
681
|
};
|
|
496
682
|
}
|
|
497
|
-
|
|
498
|
-
|
|
683
|
+
// #4155: a report that declares a covered-input fingerprint is checked by
|
|
684
|
+
// RECOMPUTING that fingerprint over current file content — strictly
|
|
685
|
+
// content-grounded, and it REPLACES (not supplements) the legacy
|
|
686
|
+
// SUMMARY-mtime check below for that report. A report with no fingerprint
|
|
687
|
+
// metadata (every report written before #4155) keeps the exact legacy
|
|
688
|
+
// mtime-based behavior, unchanged.
|
|
689
|
+
const coveredFilesVal = fm['covered_files'];
|
|
690
|
+
const coveredDigestVal = fm['covered_digest'];
|
|
691
|
+
// A report OPTS IN to the fingerprint check by declaring EITHER field —
|
|
692
|
+
// once opted in, an incomplete or malformed pair (one field present but
|
|
693
|
+
// not the other, an empty array, a non-array, a blank digest) fails closed
|
|
694
|
+
// to `stale` rather than silently downgrading to the weaker legacy
|
|
695
|
+
// mtime-only check, which would only ever notice a newer SUMMARY.
|
|
696
|
+
const declaresFingerprint = coveredFilesVal !== undefined || coveredDigestVal !== undefined;
|
|
697
|
+
const hasWellFormedFingerprint = Array.isArray(coveredFilesVal) &&
|
|
698
|
+
coveredFilesVal.length > 0 &&
|
|
699
|
+
coveredFilesVal.every((f) => typeof f === 'string') &&
|
|
700
|
+
typeof coveredDigestVal === 'string' &&
|
|
701
|
+
coveredDigestVal.trim().length > 0;
|
|
702
|
+
let staleCheckIndeterminate = false;
|
|
703
|
+
let isStale;
|
|
704
|
+
if (declaresFingerprint) {
|
|
705
|
+
// Stated directly rather than relying on `null !== coveredDigestVal`
|
|
706
|
+
// being true whenever the pair is malformed: `!hasWellFormedFingerprint`
|
|
707
|
+
// fails closed explicitly, and its `||` short-circuit means
|
|
708
|
+
// computeCoveredDigest/allCurrentArtifactsCovered never run on a
|
|
709
|
+
// malformed (wrong-shaped) `coveredFilesVal`. The two `||`s after it
|
|
710
|
+
// short-circuit in turn: the live-directory re-scan (for a plan/summary
|
|
711
|
+
// added AFTER verification and never declared in covered_files) only
|
|
712
|
+
// runs once the digest itself has already matched.
|
|
713
|
+
isStale =
|
|
714
|
+
!hasWellFormedFingerprint ||
|
|
715
|
+
computeCoveredDigest((0, project_root_cjs_1.findProjectRoot)(phaseDir), coveredFilesVal) !== coveredDigestVal ||
|
|
716
|
+
!allCurrentArtifactsCovered(phaseDir, coveredFilesVal);
|
|
717
|
+
}
|
|
718
|
+
else {
|
|
719
|
+
const staleCheck = findStaleVerificationSummary(phaseDir, fsImpl, phaseCleanCommitTimesMs);
|
|
720
|
+
isStale = staleCheck.determined && staleCheck.stale;
|
|
721
|
+
// staleCheck is either {determined:true, stale:false} (checked; nothing
|
|
722
|
+
// stale) or {determined:false} (could not check — fs/scan/clock failure).
|
|
723
|
+
// Both fall through to normal routing below (the pre-existing no-throw
|
|
724
|
+
// fail-open contract is unchanged), but the indeterminate case is flagged
|
|
725
|
+
// on the returned result so a caller can tell the two apart (#3057 B3).
|
|
726
|
+
staleCheckIndeterminate = !staleCheck.determined;
|
|
727
|
+
}
|
|
728
|
+
if (isStale) {
|
|
499
729
|
const entry = VERIFICATION_ROUTING_TABLE['stale'];
|
|
500
730
|
return {
|
|
501
731
|
status: entry.status,
|
|
@@ -503,12 +733,6 @@ function readVerificationStatus(phaseDir, opts = {}) {
|
|
|
503
733
|
next_command: projectNextCommand('verify-work', runtime, phaseArg),
|
|
504
734
|
};
|
|
505
735
|
}
|
|
506
|
-
// staleCheck is either {determined:true, stale:false} (checked; nothing
|
|
507
|
-
// stale) or {determined:false} (could not check — fs/scan/clock failure).
|
|
508
|
-
// Both fall through to normal routing below (the pre-existing no-throw
|
|
509
|
-
// fail-open contract is unchanged), but the indeterminate case is flagged
|
|
510
|
-
// on the returned result so a caller can tell the two apart (#3057 B3).
|
|
511
|
-
const staleCheckIndeterminate = !staleCheck.determined;
|
|
512
736
|
// 3. Route — exclude internal sentinels from raw-file lookup (they are
|
|
513
737
|
// constructed internally above, never written by the verifier).
|
|
514
738
|
if (rawStatus in VERIFICATION_ROUTING_TABLE &&
|
|
@@ -563,7 +787,7 @@ function readVerificationStatus(phaseDir, opts = {}) {
|
|
|
563
787
|
* never re-derives or requires them itself.
|
|
564
788
|
*/
|
|
565
789
|
function isPhaseComplete(phaseDir, deps = {}) {
|
|
566
|
-
const fsImpl = deps.fs ??
|
|
790
|
+
const fsImpl = deps.fs ?? defaultFsImpl;
|
|
567
791
|
let readable = true;
|
|
568
792
|
try {
|
|
569
793
|
fsImpl.readdirSync(phaseDir);
|
|
@@ -642,6 +866,54 @@ function cmdVerificationResolveFile(cwd, phaseDirArg, raw) {
|
|
|
642
866
|
}
|
|
643
867
|
output({ verification_file: verificationPath }, raw, verificationPath);
|
|
644
868
|
}
|
|
869
|
+
/**
|
|
870
|
+
* CLI command handler (#4155): compute the covered-input fingerprint the
|
|
871
|
+
* verifier embeds in VERIFICATION.md frontmatter (`covered_files`,
|
|
872
|
+
* `covered_digest`). The verifier is an LLM agent, not a hashing engine —
|
|
873
|
+
* this command does the deterministic math so the agent only has to name
|
|
874
|
+
* the covered paths and copy the result into frontmatter.
|
|
875
|
+
*
|
|
876
|
+
* Emits `{ covered_files: <sorted deduped paths>, covered_digest: <digest> }`
|
|
877
|
+
* on success. A covered path that is missing, unreadable, or escapes the
|
|
878
|
+
* project root fails the WHOLE command (fail closed — a partial fingerprint
|
|
879
|
+
* would be worse than none): `error()` is called and nothing is emitted.
|
|
880
|
+
*
|
|
881
|
+
* @param cwd - Current working directory.
|
|
882
|
+
* @param phaseDirArg - Phase directory path (absolute or relative to cwd);
|
|
883
|
+
* its project root is the base covered paths resolve against.
|
|
884
|
+
* @param files - Covered-input paths, relative to the project root.
|
|
885
|
+
* @param raw - Whether to emit raw (non-JSON) output: just the
|
|
886
|
+
* `covered_digest` string, so `VAR=$(gsd_run query
|
|
887
|
+
* verification.fingerprint "$PHASE_DIR" ... --raw)` is
|
|
888
|
+
* directly assignable. `covered_files` is unambiguous
|
|
889
|
+
* from the caller's own input list in that mode, so
|
|
890
|
+
* only the computed digest needs a raw form.
|
|
891
|
+
*/
|
|
892
|
+
function cmdVerificationFingerprint(cwd, phaseDirArg, files, raw) {
|
|
893
|
+
if (!phaseDirArg) {
|
|
894
|
+
error('phase directory required for verification.fingerprint');
|
|
895
|
+
return;
|
|
896
|
+
}
|
|
897
|
+
if (files.length === 0) {
|
|
898
|
+
error('at least one covered file required for verification.fingerprint');
|
|
899
|
+
return;
|
|
900
|
+
}
|
|
901
|
+
const phaseDir = node_path_1.default.resolve(cwd, phaseDirArg);
|
|
902
|
+
const projectRoot = (0, project_root_cjs_1.findProjectRoot)(phaseDir);
|
|
903
|
+
// canonicalizeCoveredFiles here is for the emitted `covered_files` field —
|
|
904
|
+
// computeCoveredDigest canonicalizes its own `coveredFiles` argument
|
|
905
|
+
// internally too (it must, for callers like readVerificationStatus that
|
|
906
|
+
// pass raw, un-canonicalized frontmatter values), so passing an
|
|
907
|
+
// already-canonical list keeps that internal pass a cheap no-op rather
|
|
908
|
+
// than a second meaningfully different canonicalization.
|
|
909
|
+
const uniqueSorted = canonicalizeCoveredFiles(files);
|
|
910
|
+
const digest = computeCoveredDigest(projectRoot, uniqueSorted);
|
|
911
|
+
if (digest === null) {
|
|
912
|
+
error('could not compute fingerprint — a covered file is missing, unreadable, or escapes the project root');
|
|
913
|
+
return;
|
|
914
|
+
}
|
|
915
|
+
output({ covered_files: uniqueSorted, covered_digest: digest }, raw, digest);
|
|
916
|
+
}
|
|
645
917
|
module.exports = {
|
|
646
918
|
VERIFIER_STATUSES,
|
|
647
919
|
VERIFICATION_ROUTING_TABLE,
|
|
@@ -653,4 +925,6 @@ module.exports = {
|
|
|
653
925
|
isPhaseComplete,
|
|
654
926
|
cmdVerificationStatus,
|
|
655
927
|
cmdVerificationResolveFile,
|
|
928
|
+
computeCoveredDigest,
|
|
929
|
+
cmdVerificationFingerprint,
|
|
656
930
|
};
|