@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
|
@@ -36,6 +36,7 @@ function routeVerifyCommand({ verify, args, cwd, raw, error }) {
|
|
|
36
36
|
// per ADR/PRD 3524 §3 / L160 (CJS-only by design). Routing through
|
|
37
37
|
// recursive dispatch would re-enter this router path.
|
|
38
38
|
'codebase-drift': () => verify.cmdVerifyCodebaseDrift(cwd, raw),
|
|
39
|
+
'context-drift': () => verify.cmdVerifyContextDrift(cwd, args[2], raw),
|
|
39
40
|
},
|
|
40
41
|
});
|
|
41
42
|
}
|
|
@@ -23,12 +23,19 @@ const stateMod = require("./state.cjs");
|
|
|
23
23
|
const modelProfilesMod = require("./model-profiles.cjs");
|
|
24
24
|
// eslint-disable-next-line @typescript-eslint/no-require-imports -- plan-scan.cjs is an export= CommonJS module
|
|
25
25
|
const planScanMod = require("./plan-scan.cjs");
|
|
26
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports -- verification.cjs is an export= CommonJS module
|
|
27
|
+
const verificationMod = require("./verification.cjs");
|
|
26
28
|
// eslint-disable-next-line @typescript-eslint/no-require-imports -- core-utils.cjs is an export= CommonJS module
|
|
27
29
|
const coreUtilsMod = require("./core-utils.cjs");
|
|
28
30
|
const { findOrphanSummaries, findUnsummarizedPlans } = coreUtilsMod;
|
|
29
31
|
// eslint-disable-next-line @typescript-eslint/no-require-imports -- planning-scope.cjs is an export= CommonJS module
|
|
30
32
|
const planningScopeMod = require("./planning-scope.cjs");
|
|
31
33
|
const { SCOPE } = planningScopeMod;
|
|
34
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports -- worktree-safety.cjs is an export= CommonJS module
|
|
35
|
+
const worktreeSafetyMod = require("./worktree-safety.cjs");
|
|
36
|
+
// Single owner of git C-quoted-path decoding (see #4081 note at the
|
|
37
|
+
// codebase-drift --name-status parse loop).
|
|
38
|
+
const { decodeGitQuotedPath } = worktreeSafetyMod;
|
|
32
39
|
const shell_command_projection_cjs_1 = require("./shell-command-projection.cjs");
|
|
33
40
|
const security_cjs_1 = require("./security.cjs");
|
|
34
41
|
const runtime_slash_cjs_1 = require("./runtime-slash.cjs");
|
|
@@ -37,7 +44,7 @@ const markdown_sectionizer_cjs_1 = require("./markdown-sectionizer.cjs");
|
|
|
37
44
|
const pattern_cjs_1 = require("./pattern.cjs");
|
|
38
45
|
// eslint-disable-next-line @typescript-eslint/no-require-imports -- agent-install-check.cjs is an export= CommonJS module
|
|
39
46
|
const agentInstallCheck = require("./agent-install-check.cjs");
|
|
40
|
-
const { checkAgentsInstalled, checkCodexModelPosture } = agentInstallCheck;
|
|
47
|
+
const { checkAgentsInstalled, checkCodexModelPosture, checkCodexSandboxPosture } = agentInstallCheck;
|
|
41
48
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
42
49
|
const ioMod = require("./io.cjs");
|
|
43
50
|
const { output, error } = ioMod;
|
|
@@ -56,7 +63,8 @@ const { SEVERITY: HEALTH_SEVERITY, REMEDY_ACTION, REMEDY_RISK, evaluateRules, ev
|
|
|
56
63
|
// eslint-disable-next-line @typescript-eslint/no-require-imports -- planning-snapshot.cjs is an export= CommonJS module
|
|
57
64
|
const planningSnapshotMod = require("./planning-snapshot.cjs");
|
|
58
65
|
const { buildPlanningSnapshot } = planningSnapshotMod;
|
|
59
|
-
const { planningDir
|
|
66
|
+
const { planningDir } = planningWorkspace;
|
|
67
|
+
const { defaultPhaseCleanCommitTimesMs } = verificationMod;
|
|
60
68
|
const { extractFrontmatter, parseMustHavesBlock } = frontmatterMod;
|
|
61
69
|
const { readStateHeadFreshness } = stateMod;
|
|
62
70
|
/**
|
|
@@ -262,15 +270,78 @@ function cmdVerifySummary(cwd, summaryPath, checkFileCount, raw) {
|
|
|
262
270
|
* verify gate fails on the comment echo rather than a real regression. Conservative:
|
|
263
271
|
* errors only on a confidently-extracted QUOTED literal; ambiguous (bareword) → warning.
|
|
264
272
|
*/
|
|
273
|
+
/**
|
|
274
|
+
* Decode entity-escaped ampersands (& → &) — #3611. Planners emit
|
|
275
|
+
* <automated> bodies with `&&` as the chain operator (66 occurrences
|
|
276
|
+
* vs 0 literal in the reporting repo), and the executing agent reads the
|
|
277
|
+
* decoded (rendered) form. Every downstream scan — segment split,
|
|
278
|
+
* zero-comparison, literal harvest, echo matching — must operate on the same
|
|
279
|
+
* decoded text or a negative clause (`= 0`) poisons the literals of a
|
|
280
|
+
* POSITIVE clause (-ge 3) joined to it. Shared by both plan-discipline
|
|
281
|
+
* scanners so the two gates cannot drift apart again.
|
|
282
|
+
*/
|
|
283
|
+
function decodeEntityAmps(s) {
|
|
284
|
+
return s.replace(/&/g, '&');
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* Split one shell line into &&/|| segments, QUOTE-AWARE (#3611 adversarial
|
|
288
|
+
* review): an operator inside a quoted literal (`grep -c 'a&&b'`) is part of
|
|
289
|
+
* the pattern, not a chain operator — a quote-blind split destroys the
|
|
290
|
+
* literal and silently disarms the gate for exactly the plans that spell
|
|
291
|
+
* patterns with ampersands. Backslash escapes count inside double quotes
|
|
292
|
+
* (POSIX single quotes have none, and over-staying a single-quoted span can
|
|
293
|
+
* only miss a split, never invent one).
|
|
294
|
+
*/
|
|
295
|
+
function splitShellSegments(line) {
|
|
296
|
+
const segments = [];
|
|
297
|
+
let current = '';
|
|
298
|
+
let quote = null;
|
|
299
|
+
for (let i = 0; i < line.length; i++) {
|
|
300
|
+
const ch = line[i];
|
|
301
|
+
if (quote === "'") {
|
|
302
|
+
if (ch === "'")
|
|
303
|
+
quote = null;
|
|
304
|
+
current += ch;
|
|
305
|
+
continue;
|
|
306
|
+
}
|
|
307
|
+
if (quote === '"') {
|
|
308
|
+
if (ch === '\\') {
|
|
309
|
+
current += ch + (line[i + 1] ?? '');
|
|
310
|
+
i++;
|
|
311
|
+
continue;
|
|
312
|
+
}
|
|
313
|
+
if (ch === '"')
|
|
314
|
+
quote = null;
|
|
315
|
+
current += ch;
|
|
316
|
+
continue;
|
|
317
|
+
}
|
|
318
|
+
if (ch === "'" || ch === '"') {
|
|
319
|
+
quote = ch;
|
|
320
|
+
current += ch;
|
|
321
|
+
continue;
|
|
322
|
+
}
|
|
323
|
+
if ((ch === '&' && line[i + 1] === '&') || (ch === '|' && line[i + 1] === '|')) {
|
|
324
|
+
segments.push(current.trim());
|
|
325
|
+
current = '';
|
|
326
|
+
i++;
|
|
327
|
+
continue;
|
|
328
|
+
}
|
|
329
|
+
current += ch;
|
|
330
|
+
}
|
|
331
|
+
segments.push(current.trim());
|
|
332
|
+
return segments.filter((s) => s !== '');
|
|
333
|
+
}
|
|
265
334
|
function scanNegativeGrepCommentEcho(content) {
|
|
266
335
|
const errors = [];
|
|
267
336
|
const warnings = [];
|
|
268
337
|
// Normalize newlines; join backslash line-continuations so a verify command wrapped
|
|
269
338
|
// across lines (grep ... \ <newline> == 0) is still seen as one segment.
|
|
270
|
-
|
|
339
|
+
// #3611: decode entity-escaped ampersands (see decodeEntityAmps) so every
|
|
340
|
+
// downstream scan reads the same decoded text the executing agent reads.
|
|
341
|
+
const text = decodeEntityAmps((content || '')
|
|
271
342
|
.replace(/\r\n/g, '\n')
|
|
272
343
|
.replace(/\r/g, '\n')
|
|
273
|
-
.replace(/\\\n/g, ' ');
|
|
344
|
+
.replace(/\\\n/g, ' '));
|
|
274
345
|
// 1. Allowlisted literals: <!-- planner-discipline-allow: LIT -->
|
|
275
346
|
const allow = new Set();
|
|
276
347
|
const allowRe = /<!--\s*planner-discipline-allow:\s*(.+?)\s*-->/g;
|
|
@@ -313,7 +384,7 @@ function scanNegativeGrepCommentEcho(content) {
|
|
|
313
384
|
// poisoning a negative gate (`== 0`) sharing the same physical line.
|
|
314
385
|
const seenErr = new Set();
|
|
315
386
|
const seenWarn = new Set();
|
|
316
|
-
const segments = text.split('\n').flatMap(
|
|
387
|
+
const segments = text.split('\n').flatMap(splitShellSegments);
|
|
317
388
|
for (const seg of segments) {
|
|
318
389
|
if (!/grep(?:\s+-{1,2}[A-Za-z])/.test(seg) || !zeroCmp(seg))
|
|
319
390
|
continue;
|
|
@@ -362,10 +433,13 @@ function scanNegativeGrepCommentEcho(content) {
|
|
|
362
433
|
function scanFileWideNegativeGateConflict(content) {
|
|
363
434
|
const warnings = [];
|
|
364
435
|
// Normalize newlines; join backslash line-continuations (same as #429).
|
|
365
|
-
|
|
436
|
+
// #3611: the SAME entity decode as the #429 scanner — the two gates share
|
|
437
|
+
// the caller and the input; a decode on one side only let an entity-escaped
|
|
438
|
+
// chain poison this detector's harvest exactly the same way.
|
|
439
|
+
const text = decodeEntityAmps((content || '')
|
|
366
440
|
.replace(/\r\n/g, '\n')
|
|
367
441
|
.replace(/\r/g, '\n')
|
|
368
|
-
.replace(/\\\n/g, ' ');
|
|
442
|
+
.replace(/\\\n/g, ' '));
|
|
369
443
|
// Allowlisted patterns: <!-- planner-region-allow: PAT -->
|
|
370
444
|
const allow = new Set();
|
|
371
445
|
const allowRe = /<!--\s*planner-region-allow:\s*(.+?)\s*-->/g;
|
|
@@ -499,8 +573,8 @@ function scanFileWideNegativeGateConflict(content) {
|
|
|
499
573
|
// For each task A, scan gate text for file-wide negative grep bans.
|
|
500
574
|
for (let ai = 0; ai < tasks.length; ai++) {
|
|
501
575
|
const taskA = tasks[ai];
|
|
502
|
-
// Split gate text into shell segments (
|
|
503
|
-
const segments = taskA.gateText.split('\n').flatMap(
|
|
576
|
+
// Split gate text into shell segments (quote-aware, shared with #429 — #3611).
|
|
577
|
+
const segments = taskA.gateText.split('\n').flatMap(splitShellSegments);
|
|
504
578
|
for (const seg of segments) {
|
|
505
579
|
if (!/grep/.test(seg))
|
|
506
580
|
continue;
|
|
@@ -628,6 +702,189 @@ function scanFileWideNegativeGateConflict(content) {
|
|
|
628
702
|
// This detector is warn-only: it never sets valid=false.
|
|
629
703
|
return { warnings, valid: true };
|
|
630
704
|
}
|
|
705
|
+
/**
|
|
706
|
+
* Issue #4024 — quantitative-criteria trap-shape scanner, the third plan-discipline
|
|
707
|
+
* gate in this family (after #429's comment-echo gate and #968's file-wide
|
|
708
|
+
* negative-gate conflict detector). #429/#968 judge `grep -c … == 0` shapes; this
|
|
709
|
+
* scanner judges the OTHER quantitative criterion shapes that are provably traps
|
|
710
|
+
* at HEAD, so a plan whose criteria are unsatisfiable-or-vacuous before any
|
|
711
|
+
* executor touches them no longer passes `verify plan-structure` clean.
|
|
712
|
+
*
|
|
713
|
+
* Scope: criteria text only — `<acceptance_criteria>`, `<automated>`, and
|
|
714
|
+
* `<verify>` blocks. Prose elsewhere in the plan (e.g. an explanatory
|
|
715
|
+
* `<action>`) is not judged (fail-open; see the negative-space section of the
|
|
716
|
+
* #4024 diagnosis).
|
|
717
|
+
*
|
|
718
|
+
* Ban list, each rule with a corrected arm asserted in tests (a rule that fires
|
|
719
|
+
* on its own fix is a refusal, not a rule):
|
|
720
|
+
* R1 (error) exact count out of `grep -c` with N ≥ 2 — `grep -c` counts LINES,
|
|
721
|
+
* not matches. Hedged counts (`>=`, "at least"), `== 0` (the #429/#968
|
|
722
|
+
* family) and the `== 1` presence idiom stay clean.
|
|
723
|
+
* R2 (error) bulk observed-failing claims — "all N tests … observed failing" /
|
|
724
|
+
* "tests N through M … each … observed failing". A test asserting a
|
|
725
|
+
* non-change cannot go red before the change exists. Per-test
|
|
726
|
+
* "discriminates" wording and subset claims stay clean.
|
|
727
|
+
* R3 (error) `$VAR` unquoted in command position — zsh does not word-split an
|
|
728
|
+
* unquoted expansion, the command exits 127, and any arm reading its
|
|
729
|
+
* status passes vacuously.
|
|
730
|
+
* R4 (warn) a fallible command (`git …`) in a non-final pipeline stage — the
|
|
731
|
+
* pipeline reports the LAST stage's status, so the failure is swallowed.
|
|
732
|
+
* Warn-only: the shape is ambiguous unless the criterion reads the status.
|
|
733
|
+
* R5 (error) `wc` output compared by string equality (`… | wc -l | grep -x 0`)
|
|
734
|
+
* — BSD `wc` pads its output, so the comparison never matches.
|
|
735
|
+
* R6 (error) `git diff`/`git log` with a relative `HEAD~N` anchor — it names
|
|
736
|
+
* whatever commit happened to land (another session's). Bare `git diff`
|
|
737
|
+
* with no range is a WARNING: a committed change produces no output and
|
|
738
|
+
* the check passes, but an uncommitted-tree check is a legitimate idiom.
|
|
739
|
+
*
|
|
740
|
+
* Legitimate exit: `<!-- plan-criteria-allow: R# - reason -->` (house style of
|
|
741
|
+
* `planner-discipline-allow`); the reason must be non-empty. Fail open: no
|
|
742
|
+
* criteria zones → no findings; every parse is text-only, nothing executes.
|
|
743
|
+
*/
|
|
744
|
+
function scanQuantitativeCriteria(content) {
|
|
745
|
+
const errors = [];
|
|
746
|
+
const warnings = [];
|
|
747
|
+
// Criteria zones: <acceptance_criteria>, <automated>, <verify>. A <verify>
|
|
748
|
+
// block contains its <automated> child, so the same segment may be harvested
|
|
749
|
+
// twice — dedupe findings by message below.
|
|
750
|
+
const zones = [];
|
|
751
|
+
for (const tag of ['acceptance_criteria', 'automated', 'verify']) {
|
|
752
|
+
for (const block of (0, markdown_sectionizer_cjs_1.extractTaggedBlocks)(content || '', tag)) {
|
|
753
|
+
zones.push({ text: block, prose: tag === 'acceptance_criteria' });
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
if (zones.length === 0)
|
|
757
|
+
return { errors, warnings };
|
|
758
|
+
// Allow markers may sit anywhere in the plan (same as the sibling scanners).
|
|
759
|
+
// The reason must be non-empty: `R1 - -->` is not an auditable exit.
|
|
760
|
+
const allow = new Set();
|
|
761
|
+
const allowRe = /<!--\s*plan-criteria-allow:\s*(R[1-6])\s+-\s+([^>]*\S)\s*-->/g;
|
|
762
|
+
let am;
|
|
763
|
+
while ((am = allowRe.exec(content || '')) !== null)
|
|
764
|
+
allow.add(am[1]);
|
|
765
|
+
const record = (bucket, rule, message, seen) => {
|
|
766
|
+
if (allow.has(rule) || seen.has(message))
|
|
767
|
+
return;
|
|
768
|
+
seen.add(message);
|
|
769
|
+
bucket.push(message);
|
|
770
|
+
};
|
|
771
|
+
// A count-grep invocation (grep with -c / --count), per the #429 idiom.
|
|
772
|
+
const countGrepRe = /grep((?:\s+-{1,2}[A-Za-z][A-Za-z-]*)+)\s+(?:'[^']*'|"[^"]*"|[^\s|>&;]+)/g;
|
|
773
|
+
const optsHaveCount = (opts) => /(?:^|\s)-[A-Za-z]*c[A-Za-z]*(?=\s|$)/.test(opts) || /--count\b/.test(opts);
|
|
774
|
+
const hasCountGrep = (s) => {
|
|
775
|
+
countGrepRe.lastIndex = 0;
|
|
776
|
+
let m;
|
|
777
|
+
while ((m = countGrepRe.exec(s)) !== null) {
|
|
778
|
+
if (optsHaveCount(m[1]))
|
|
779
|
+
return true;
|
|
780
|
+
}
|
|
781
|
+
return false;
|
|
782
|
+
};
|
|
783
|
+
const seenErr = new Set();
|
|
784
|
+
const seenWarn = new Set();
|
|
785
|
+
for (const zone of zones) {
|
|
786
|
+
// Same normalization as the sibling scanners (#3611): newline join,
|
|
787
|
+
// backslash continuation join, entity-amp decode.
|
|
788
|
+
const text = decodeEntityAmps(zone.text
|
|
789
|
+
.replace(/\r\n/g, '\n')
|
|
790
|
+
.replace(/\r/g, '\n')
|
|
791
|
+
.replace(/\\\n/g, ' '));
|
|
792
|
+
// R2 — bulk observed-failing prose (acceptance_criteria only). The gap
|
|
793
|
+
// [^\n.]{0,120} keeps the subject and the claim adjacent within one
|
|
794
|
+
// sentence, so the corrected arm (a subset claim, or "each test
|
|
795
|
+
// discriminates") never fires.
|
|
796
|
+
if (zone.prose) {
|
|
797
|
+
const bulkRedRes = [
|
|
798
|
+
/\ball\s+(?:\d+|one|two|three|four|five|six|seven|eight|nine|ten|eleven|twelve)\s+tests\b[^\n.]{0,120}?observed\s+fail/i,
|
|
799
|
+
/\btests\s+\d+\s+through\s+\d+\b[^\n.]{0,120}?observed\s+fail/i,
|
|
800
|
+
];
|
|
801
|
+
for (const re of bulkRedRes) {
|
|
802
|
+
if (re.test(text)) {
|
|
803
|
+
record(errors, 'R2', '[plan-criteria R2] A bulk "all N tests / tests N through M were observed failing" criterion is ' +
|
|
804
|
+
'unsatisfiable whenever any of the N tests asserts a non-change, which cannot go red before the ' +
|
|
805
|
+
'change exists. Say "each test discriminates" (fails before the change, passes after), and attach a ' +
|
|
806
|
+
'mutation record for any test that cannot be seen red.', seenErr);
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
for (const seg of text.split('\n').flatMap(splitShellSegments)) {
|
|
811
|
+
// R1 — exact count out of `grep -c` (N ≥ 2). Spaced `==`/`-eq` (the
|
|
812
|
+
// #429 zeroCmp idiom, excluding assignments and >=/<=/!=), or the prose
|
|
813
|
+
// "returns exactly N".
|
|
814
|
+
if (hasCountGrep(seg)) {
|
|
815
|
+
const exact = [];
|
|
816
|
+
let m;
|
|
817
|
+
const shellCmp = /\s==?\s*(\d+)\b/g;
|
|
818
|
+
while ((m = shellCmp.exec(seg)) !== null)
|
|
819
|
+
exact.push(parseInt(m[1], 10));
|
|
820
|
+
const eqCmp = /-eq\s+(\d+)\b/g;
|
|
821
|
+
while ((m = eqCmp.exec(seg)) !== null)
|
|
822
|
+
exact.push(parseInt(m[1], 10));
|
|
823
|
+
const proseCmp = /\breturns?\s+exactly\s+(\d+)\b/gi;
|
|
824
|
+
while ((m = proseCmp.exec(seg)) !== null)
|
|
825
|
+
exact.push(parseInt(m[1], 10));
|
|
826
|
+
if (exact.some((n) => n >= 2)) {
|
|
827
|
+
record(errors, 'R1', '[plan-criteria R1] An exact count out of `grep -c` is a trap: `grep -c` counts LINES, not matches, ' +
|
|
828
|
+
'so the criterion can be false at HEAD before any executor touches it. Use `>= 1`, `== 0`, or ' +
|
|
829
|
+
'`grep -n` and read the line numbers.', seenErr);
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
// R3 — unquoted $VAR in command position (zsh does not word-split; the
|
|
833
|
+
// command exits 127 and any arm reading its status passes vacuously).
|
|
834
|
+
// The anchor tolerates a markdown list bullet and/or an inline-code
|
|
835
|
+
// backtick before the command — criteria are prose lines like
|
|
836
|
+
// "- `$NOKEY npx tsx x.ts` exits 0" — but nothing else: the expansion
|
|
837
|
+
// must still be the FIRST token of the command itself.
|
|
838
|
+
if (/^(?:[-*]\s*)?\u0060?\$[A-Z_][A-Z0-9_]*\s/.test(seg)) {
|
|
839
|
+
record(errors, 'R3', '[plan-criteria R3] `$VAR` unquoted in command position does not word-split under zsh — the shell ' +
|
|
840
|
+
'looks for a command literally named by the whole expansion and exits 127, so any arm reading its ' +
|
|
841
|
+
'status passes vacuously. Write the prefix inline or use a shell function.', seenErr);
|
|
842
|
+
}
|
|
843
|
+
// R4 — a fallible `git …` in a non-final pipeline stage: the pipeline
|
|
844
|
+
// reports the LAST stage's status, so git's failure is swallowed.
|
|
845
|
+
// Warn-only: without a status assertion the shape is ambiguous.
|
|
846
|
+
// Not a markdown table: the negated-pipe class matches a SHELL pipeline
|
|
847
|
+
// stage boundary (git before the next `|`), the same shape the
|
|
848
|
+
// #429/#968 scanners use; there is no table row to parse.
|
|
849
|
+
// allow-adhoc-markdown: shell pipeline stage boundary, not a table cell (#4024)
|
|
850
|
+
if (/\bgit\s+[a-z][^\n|]*\|/.test(seg)) {
|
|
851
|
+
record(warnings, 'R4', '[plan-criteria R4] A fallible `git` in a non-final pipeline stage is swallowed — the pipeline reports ' +
|
|
852
|
+
'the last stage\'s status, so a broken command reads as clean. Capture the status first.', seenWarn);
|
|
853
|
+
}
|
|
854
|
+
// R5 — `wc` output compared by string equality: BSD `wc` pads its output
|
|
855
|
+
// (seven spaces then the number), so `grep -x 0` can never match.
|
|
856
|
+
if (/\bwc\b/.test(seg) && /\bgrep\s+(?:-{1,2}[A-Za-z]*x[A-Za-z]*|--line-regexp)\s+['"]?\d+['"]?/.test(seg)) {
|
|
857
|
+
record(errors, 'R5', '[plan-criteria R5] `wc` output compared by string equality is unsatisfiable on BSD `wc`, which pads ' +
|
|
858
|
+
'its output before the number. Compare numerically: `test "$n" -eq 0`, or `tr -d \' \'` first.', seenErr);
|
|
859
|
+
}
|
|
860
|
+
// R6 — relative HEAD~N anchors name whatever commit happened to land;
|
|
861
|
+
// a bare `git diff` with no range makes a committed change invisible.
|
|
862
|
+
if (/\bgit\s+(?:diff|log)\b[^\n|;&]*\bHEAD~\d+/.test(seg)) {
|
|
863
|
+
record(errors, 'R6', '[plan-criteria R6] `git diff`/`git log` with a relative `HEAD~N` anchor names whatever commit ' +
|
|
864
|
+
'happened to land last — in a repository with parallel sessions that is another session\'s commit. ' +
|
|
865
|
+
'Pin an explicit range: `<sha>^..<sha>`.', seenErr);
|
|
866
|
+
}
|
|
867
|
+
else {
|
|
868
|
+
// The argument span ends at the closing backtick of an inline-code
|
|
869
|
+
// span ("`git diff` shows ..."), at a redirection, or at a chain
|
|
870
|
+
// operator — trailing prose must never read as diff arguments.
|
|
871
|
+
const bareDiff = /\bgit\s+diff\b([^\u0060\n|;&<>]*)/.exec(seg);
|
|
872
|
+
if (bareDiff) {
|
|
873
|
+
const rest = bareDiff[1].trim();
|
|
874
|
+
// Bare or flags-only (no rev, range, or path): ambiguous between an
|
|
875
|
+
// uncommitted-tree check (legitimate) and a committed-change check
|
|
876
|
+
// (vacuously clean) — warn, never error.
|
|
877
|
+
if (rest === '' || /^-{1,2}[A-Za-z-]+$/.test(rest)) {
|
|
878
|
+
record(warnings, 'R6', '[plan-criteria R6] Bare `git diff` with no revision range produces no output for a COMMITTED ' +
|
|
879
|
+
'change, so the criterion passes vacuously. Pin an explicit range (`<sha>^..<sha>`) unless the ' +
|
|
880
|
+
'check is intentionally about the uncommitted tree.', seenWarn);
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
return { errors, warnings };
|
|
887
|
+
}
|
|
631
888
|
/**
|
|
632
889
|
* Single pass over `<task ...>…</task>` blocks. The body pattern is
|
|
633
890
|
* ReDoS-safe stop-at-next-open (mirrors `taggedBlockPattern` in
|
|
@@ -838,6 +1095,12 @@ function cmdVerifyPlanStructure(cwd, filePath, raw) {
|
|
|
838
1095
|
warnings.push(...echoScan.warnings);
|
|
839
1096
|
const conflictScan = scanFileWideNegativeGateConflict(content);
|
|
840
1097
|
warnings.push(...conflictScan.warnings);
|
|
1098
|
+
// #4024: quantitative-criteria trap shapes (exact grep -c counts, bulk
|
|
1099
|
+
// observed-failing claims, $VAR command position, swallowed pipeline
|
|
1100
|
+
// stages, wc string-equality, relative git anchors).
|
|
1101
|
+
const quantScan = scanQuantitativeCriteria(content);
|
|
1102
|
+
errors.push(...quantScan.errors);
|
|
1103
|
+
warnings.push(...quantScan.warnings);
|
|
841
1104
|
output({
|
|
842
1105
|
valid: errors.length === 0,
|
|
843
1106
|
errors,
|
|
@@ -1014,12 +1277,19 @@ function cmdVerifyArtifacts(cwd, planFilePath, raw) {
|
|
|
1014
1277
|
results.push(check);
|
|
1015
1278
|
}
|
|
1016
1279
|
const passed = results.filter((r) => r['passed']).length;
|
|
1280
|
+
// Positive-evidence floor (#3956): a non-empty artifacts block whose items are
|
|
1281
|
+
// all bare strings / path-less objects is item-by-item skipped, leaving results
|
|
1282
|
+
// empty; `passed === results.length` would then be `0 === 0` → a vacuous GREEN
|
|
1283
|
+
// over zero checks. Require at least one checked artifact, mirroring the
|
|
1284
|
+
// no-vacuous-pass rule at src/uat-predicate.cts. The fully-empty block is still
|
|
1285
|
+
// caught earlier by the `artifacts.length === 0` guard and returns its error.
|
|
1286
|
+
const allPassed = results.length > 0 && passed === results.length;
|
|
1017
1287
|
output({
|
|
1018
|
-
all_passed:
|
|
1288
|
+
all_passed: allPassed,
|
|
1019
1289
|
passed,
|
|
1020
1290
|
total: results.length,
|
|
1021
1291
|
artifacts: results,
|
|
1022
|
-
}, raw,
|
|
1292
|
+
}, raw, allPassed ? 'valid' : 'invalid');
|
|
1023
1293
|
}
|
|
1024
1294
|
/**
|
|
1025
1295
|
* Returns a Set of file paths (relative to cwd) that are promised by plans in
|
|
@@ -1233,7 +1503,17 @@ function cmdVerifyKeyLinks(cwd, planFilePath, raw) {
|
|
|
1233
1503
|
// A pending link (from: file promised by a same-or-later-wave plan) is not a
|
|
1234
1504
|
// hard failure — it should not count against the all_verified gate (#1202).
|
|
1235
1505
|
const hardFailed = results.filter((r) => !r['verified'] && !r['pending']).length;
|
|
1236
|
-
|
|
1506
|
+
// Positive-evidence floor (#3956): an all-bare-string key_links block skips
|
|
1507
|
+
// every item (only `typeof link === 'string'` items are continue-skipped
|
|
1508
|
+
// above), leaving results empty; `hardFailed === 0` would then be a vacuous
|
|
1509
|
+
// GREEN over zero checks. Require at least one checked link. (A `from:`-less
|
|
1510
|
+
// object is NOT skipped, unlike a path-less object on the artifacts side — it
|
|
1511
|
+
// falls through to a `verified: false` result and hard-fails, so it was never
|
|
1512
|
+
// part of the vacuous-pass surface; only the all-bare-string case is.) A
|
|
1513
|
+
// pending link IS pushed to results (with pending: true), so an all-pending
|
|
1514
|
+
// block still satisfies results.length > 0 and its #1202 non-hard-failing
|
|
1515
|
+
// semantics are unchanged — the floor only rejects the zero-result case.
|
|
1516
|
+
const allVerified = results.length > 0 && hardFailed === 0;
|
|
1237
1517
|
output({
|
|
1238
1518
|
all_verified: allVerified,
|
|
1239
1519
|
verified,
|
|
@@ -1242,30 +1522,6 @@ function cmdVerifyKeyLinks(cwd, planFilePath, raw) {
|
|
|
1242
1522
|
links: results,
|
|
1243
1523
|
}, raw, allVerified ? 'valid' : 'invalid');
|
|
1244
1524
|
}
|
|
1245
|
-
function listMilestoneArchiveDirs(planBase) {
|
|
1246
|
-
const milestonesDir = node_path_1.default.join(planBase, 'milestones');
|
|
1247
|
-
try {
|
|
1248
|
-
return node_fs_1.default
|
|
1249
|
-
.readdirSync(milestonesDir, { withFileTypes: true })
|
|
1250
|
-
.filter((e) => e.isDirectory() && validate_cjs_1.MILESTONE_ARCHIVE_DIR_RE.test(e.name))
|
|
1251
|
-
.map((e) => node_path_1.default.join(milestonesDir, e.name))
|
|
1252
|
-
.sort((a, b) => node_path_1.default.basename(a).localeCompare(node_path_1.default.basename(b), undefined, { numeric: true }));
|
|
1253
|
-
}
|
|
1254
|
-
catch (err) {
|
|
1255
|
-
// #1883: distinguish genuine absence from a permission/I-O failure. ENOENT
|
|
1256
|
-
// (no milestones/ dir yet) keeps the long-standing [] contract callers of
|
|
1257
|
-
// this function depend on for "no archives"; every other error (EACCES,
|
|
1258
|
-
// EIO, …) must propagate — otherwise an unreadable milestones/ dir is
|
|
1259
|
-
// silently reported as "no archives" and archived-phase resolution
|
|
1260
|
-
// misbehaves. As of Phase 12 (#3310), `cmdValidateConsistency`'s own
|
|
1261
|
-
// caller of this function (`collectPhaseRoots` -> `getActiveMilestoneArchiveDir`)
|
|
1262
|
-
// was migrated onto `buildPlanningSnapshot` and deleted; this function is
|
|
1263
|
-
// retained solely for its `_listMilestoneArchiveDirs` test seam below.
|
|
1264
|
-
if (err.code === 'ENOENT')
|
|
1265
|
-
return [];
|
|
1266
|
-
throw err;
|
|
1267
|
-
}
|
|
1268
|
-
}
|
|
1269
1525
|
/**
|
|
1270
1526
|
* Wrapper-level fix-text table for `cmdValidateHealth`'s migrated
|
|
1271
1527
|
* `HealthDiagnostic` -> `IssueEntry` mapping (Phase 11, #3309). Rules cannot
|
|
@@ -1397,8 +1653,10 @@ function cmdValidateHealth(cwd, options, raw) {
|
|
|
1397
1653
|
}, raw);
|
|
1398
1654
|
return;
|
|
1399
1655
|
}
|
|
1400
|
-
// rootBase
|
|
1401
|
-
|
|
1656
|
+
// rootBase resolves to the PROJECT-scoped planning root (`.planning[/<project>]`
|
|
1657
|
+
// — PROJECT.md, config.json live here; #3749). Workstream-free by construction:
|
|
1658
|
+
// planningDir(cwd, null) honors GSD_PROJECT and suppresses GSD_WORKSTREAM.
|
|
1659
|
+
const rootBase = planningDir(cwd, null);
|
|
1402
1660
|
const _slashRuntime = (0, runtime_slash_cjs_1.resolveRuntime)(cwd);
|
|
1403
1661
|
const slash = (name) => (0, runtime_slash_cjs_1.formatGsdSlash)(name, _slashRuntime);
|
|
1404
1662
|
// Second (and last) pre-check that stays OUTSIDE the rule table entirely
|
|
@@ -1523,6 +1781,21 @@ function cmdValidateAgents(cwd, raw) {
|
|
|
1523
1781
|
// model or an orphaned reasoning-effort pin in a Codex agent .toml), not just
|
|
1524
1782
|
// presence. checkAgentsInstalled above is untouched.
|
|
1525
1783
|
const codexPosture = checkCodexModelPosture(runtime, cwd);
|
|
1784
|
+
// #3897 rung 3 (ADR-3473 §8.3 criterion 3) — sibling posture check, additive
|
|
1785
|
+
// alongside codex_posture (never nested inside it, never replacing it): a
|
|
1786
|
+
// TOML's `sandbox_mode` disagreeing with its role's derived expectation is a
|
|
1787
|
+
// different defect class than an Anthropic-flavored model. Same short-circuits,
|
|
1788
|
+
// same read-only posture as checkCodexModelPosture above; checkAgentsInstalled
|
|
1789
|
+
// and codex_posture are both left untouched.
|
|
1790
|
+
//
|
|
1791
|
+
// Failure semantics: matches the checkCodexModelPosture precedent exactly.
|
|
1792
|
+
// Neither posture check makes `validate agents` exit non-zero on its own —
|
|
1793
|
+
// both are report-only fields inspected by the caller (or a human) via the
|
|
1794
|
+
// JSON payload. A `validate` verb whose two posture checks disagreed on
|
|
1795
|
+
// fatality would be its own defect (#3897 dispatch note); this keeps them
|
|
1796
|
+
// consistent rather than inventing new exit-code behavior for only one of
|
|
1797
|
+
// the two siblings.
|
|
1798
|
+
const sandboxPosture = checkCodexSandboxPosture(runtime, cwd);
|
|
1526
1799
|
output({
|
|
1527
1800
|
agents_dir: agentStatus.agents_dir,
|
|
1528
1801
|
agents_found: agentStatus.agents_installed,
|
|
@@ -1531,6 +1804,122 @@ function cmdValidateAgents(cwd, raw) {
|
|
|
1531
1804
|
incomplete: agentStatus.incomplete_agents,
|
|
1532
1805
|
expected,
|
|
1533
1806
|
codex_posture: codexPosture,
|
|
1807
|
+
sandbox_posture: sandboxPosture,
|
|
1808
|
+
}, raw);
|
|
1809
|
+
}
|
|
1810
|
+
// ─── Context drift (#3348) ───────────────────────────────────────────────────
|
|
1811
|
+
/**
|
|
1812
|
+
* Resolve a phase directory under `phasesDir` from a user-supplied `phaseArg`,
|
|
1813
|
+
* via the canonical phase-directory matcher (phase-id.cjs::matchPhaseDirs) rather
|
|
1814
|
+
* than a naive substring test — a bare `.includes(phaseArg)` lets a non-existent
|
|
1815
|
+
* phase silently match a different phase whose directory name merely contains the
|
|
1816
|
+
* requested token (e.g. "1" matching "11-expansion"). Falls back to an exact
|
|
1817
|
+
* directory-name match. Returns null if neither resolves. (#1571, #2528)
|
|
1818
|
+
*/
|
|
1819
|
+
function resolvePhaseDirByToken(phasesDir, phaseArg) {
|
|
1820
|
+
const normalizedPhase = normalizePhaseName(phaseArg);
|
|
1821
|
+
const dirEntries = node_fs_1.default.readdirSync(phasesDir, { withFileTypes: true });
|
|
1822
|
+
const dirNames = dirEntries.filter((e) => e.isDirectory()).map((e) => e.name);
|
|
1823
|
+
const matched = matchPhaseDirs(dirNames, normalizedPhase).matches[0];
|
|
1824
|
+
if (matched)
|
|
1825
|
+
return node_path_1.default.join(phasesDir, matched);
|
|
1826
|
+
const check = (0, security_cjs_1.validatePath)(phaseArg, phasesDir);
|
|
1827
|
+
if (check.safe && node_fs_1.default.existsSync(check.resolved))
|
|
1828
|
+
return check.resolved;
|
|
1829
|
+
return null;
|
|
1830
|
+
}
|
|
1831
|
+
/**
|
|
1832
|
+
* Pure comparator: which of `entries` have an effective last-changed time
|
|
1833
|
+
* STRICTLY BEFORE `contextEffectiveMs` (CONTEXT.md's own effective time)? Strict
|
|
1834
|
+
* `<` is "stale" (matches findStaleVerificationSummary's own strict `>` convention
|
|
1835
|
+
* for "newer than" elsewhere in this codebase — an artifact committed in the SAME
|
|
1836
|
+
* commit/second as CONTEXT.md is in sync, not stale).
|
|
1837
|
+
*/
|
|
1838
|
+
function computeContextDrift(contextEffectiveMs, entries) {
|
|
1839
|
+
return entries.filter((e) => e.effectiveMs < contextEffectiveMs).map((e) => e.file);
|
|
1840
|
+
}
|
|
1841
|
+
function buildContextDriftMessage(staleArtifacts, phaseArg) {
|
|
1842
|
+
const parts = [`CONTEXT.md decisions are newer than: ${staleArtifacts.join(', ')}.`];
|
|
1843
|
+
if (staleArtifacts.some((f) => f.endsWith('-RESEARCH.md'))) {
|
|
1844
|
+
parts.push(`Regenerate research: /gsd:plan-phase ${phaseArg} --research.`);
|
|
1845
|
+
}
|
|
1846
|
+
if (staleArtifacts.some((f) => f.endsWith('-PATTERNS.md'))) {
|
|
1847
|
+
parts.push('Regenerate patterns: delete the PATTERNS.md file, then re-run /gsd:plan-phase.');
|
|
1848
|
+
}
|
|
1849
|
+
if (staleArtifacts.some((f) => f.endsWith('-VALIDATION.md') || (f.endsWith('-SPEC.md') && !f.endsWith('-AI-SPEC.md') && !f.endsWith('-UI-SPEC.md')))) {
|
|
1850
|
+
parts.push('Regenerate or manually reconcile VALIDATION.md / SPEC.md against the current decisions.');
|
|
1851
|
+
}
|
|
1852
|
+
parts.push('Do not hand-inject the newer decisions into a prompt as a substitute for regenerating — that carries the staleness forward.');
|
|
1853
|
+
return parts.join(' ');
|
|
1854
|
+
}
|
|
1855
|
+
function cmdVerifyContextDrift(cwd, phaseArg, raw) {
|
|
1856
|
+
if (!phaseArg) {
|
|
1857
|
+
error('Usage: verify context-drift <phase>');
|
|
1858
|
+
return;
|
|
1859
|
+
}
|
|
1860
|
+
const pDir = planningDir(cwd);
|
|
1861
|
+
const phasesDir = node_path_1.default.join(pDir, 'phases');
|
|
1862
|
+
const emitSkip = (reason, message = '') => {
|
|
1863
|
+
output({ block: false, skipped: true, reason, stale_artifacts: [], message }, raw);
|
|
1864
|
+
};
|
|
1865
|
+
if (!node_fs_1.default.existsSync(phasesDir)) {
|
|
1866
|
+
emitSkip('phase-not-found', `Phase directory not found: ${phaseArg}`);
|
|
1867
|
+
return;
|
|
1868
|
+
}
|
|
1869
|
+
// Same phase-directory resolution rule cmdVerifySchemaDrift uses (#1571, #2528):
|
|
1870
|
+
// matchPhaseDirs, never a naive substring test.
|
|
1871
|
+
const phaseDir = resolvePhaseDirByToken(phasesDir, phaseArg);
|
|
1872
|
+
if (!phaseDir) {
|
|
1873
|
+
emitSkip('phase-not-found', `Phase directory not found: ${phaseArg}`);
|
|
1874
|
+
return;
|
|
1875
|
+
}
|
|
1876
|
+
let phaseFiles;
|
|
1877
|
+
try {
|
|
1878
|
+
phaseFiles = node_fs_1.default.readdirSync(phaseDir).slice().sort();
|
|
1879
|
+
}
|
|
1880
|
+
catch {
|
|
1881
|
+
emitSkip('phase-not-found', `Phase directory not found: ${phaseArg}`);
|
|
1882
|
+
return;
|
|
1883
|
+
}
|
|
1884
|
+
const contextFile = phaseFiles.find((f) => f.endsWith('-CONTEXT.md'));
|
|
1885
|
+
if (!contextFile) {
|
|
1886
|
+
emitSkip('no-context-md');
|
|
1887
|
+
return;
|
|
1888
|
+
}
|
|
1889
|
+
const researchFile = phaseFiles.find((f) => f.endsWith('-RESEARCH.md'));
|
|
1890
|
+
const patternsFile = phaseFiles.find((f) => f.endsWith('-PATTERNS.md'));
|
|
1891
|
+
const validationFile = phaseFiles.find((f) => f.endsWith('-VALIDATION.md'));
|
|
1892
|
+
const specFile = phaseFiles.find((f) => f.endsWith('-SPEC.md') && !f.endsWith('-AI-SPEC.md') && !f.endsWith('-UI-SPEC.md'));
|
|
1893
|
+
const upstreamFiles = [researchFile, patternsFile, validationFile, specFile].filter((f) => !!f);
|
|
1894
|
+
if (upstreamFiles.length === 0) {
|
|
1895
|
+
emitSkip('no-upstream-artifacts');
|
|
1896
|
+
return;
|
|
1897
|
+
}
|
|
1898
|
+
const allFiles = [contextFile, ...upstreamFiles];
|
|
1899
|
+
const cleanCommitMs = defaultPhaseCleanCommitTimesMs(phaseDir, allFiles);
|
|
1900
|
+
const effectiveTimeMs = (file) => cleanCommitMs.has(file)
|
|
1901
|
+
? cleanCommitMs.get(file)
|
|
1902
|
+
: node_fs_1.default.statSync(node_path_1.default.join(phaseDir, file)).mtimeMs;
|
|
1903
|
+
const contextMs = effectiveTimeMs(contextFile);
|
|
1904
|
+
const driftEntries = upstreamFiles.map((f) => ({ file: f, effectiveMs: effectiveTimeMs(f) }));
|
|
1905
|
+
const staleArtifacts = computeContextDrift(contextMs, driftEntries);
|
|
1906
|
+
let wf;
|
|
1907
|
+
try {
|
|
1908
|
+
const rawCfg = JSON.parse(node_fs_1.default.readFileSync(node_path_1.default.join(pDir, 'config.json'), 'utf-8'));
|
|
1909
|
+
wf = rawCfg['workflow'];
|
|
1910
|
+
}
|
|
1911
|
+
catch {
|
|
1912
|
+
wf = undefined;
|
|
1913
|
+
}
|
|
1914
|
+
const action = wf?.context_drift_action === 'block' ? 'block' : 'warn';
|
|
1915
|
+
const block = staleArtifacts.length > 0 && action === 'block';
|
|
1916
|
+
const message = staleArtifacts.length > 0 ? buildContextDriftMessage(staleArtifacts, phaseArg) : '';
|
|
1917
|
+
output({
|
|
1918
|
+
block,
|
|
1919
|
+
skipped: false,
|
|
1920
|
+
stale_artifacts: staleArtifacts,
|
|
1921
|
+
action,
|
|
1922
|
+
message,
|
|
1534
1923
|
}, raw);
|
|
1535
1924
|
}
|
|
1536
1925
|
function cmdVerifySchemaDrift(cwd, phaseArg, skipFlag, raw) {
|
|
@@ -1551,18 +1940,7 @@ function cmdVerifySchemaDrift(cwd, phaseArg, skipFlag, raw) {
|
|
|
1551
1940
|
// matching "11-expansion"), making the drift gate inspect the wrong phase.
|
|
1552
1941
|
// This shares the one selection rule with find-phase / verify
|
|
1553
1942
|
// phase-completeness rather than restating it. (#1571, #2528)
|
|
1554
|
-
|
|
1555
|
-
const normalizedPhase = normalizePhaseName(phaseArg);
|
|
1556
|
-
const entries = node_fs_1.default.readdirSync(phasesDir, { withFileTypes: true });
|
|
1557
|
-
const dirNames = entries.filter((e) => e.isDirectory()).map((e) => e.name);
|
|
1558
|
-
const drift = matchPhaseDirs(dirNames, normalizedPhase).matches[0];
|
|
1559
|
-
if (drift)
|
|
1560
|
-
phaseDir = node_path_1.default.join(phasesDir, drift);
|
|
1561
|
-
if (!phaseDir) {
|
|
1562
|
-
const exact = node_path_1.default.join(phasesDir, phaseArg);
|
|
1563
|
-
if (node_fs_1.default.existsSync(exact))
|
|
1564
|
-
phaseDir = exact;
|
|
1565
|
-
}
|
|
1943
|
+
const phaseDir = resolvePhaseDirByToken(phasesDir, phaseArg);
|
|
1566
1944
|
if (!phaseDir) {
|
|
1567
1945
|
output({ block: false, drift_detected: false, blocking: false, message: `Phase directory not found: ${phaseArg}` }, raw);
|
|
1568
1946
|
return;
|
|
@@ -1688,7 +2066,16 @@ function cmdVerifyCodebaseDrift(cwd, raw) {
|
|
|
1688
2066
|
if (!m)
|
|
1689
2067
|
continue;
|
|
1690
2068
|
const status = m[1];
|
|
1691
|
-
|
|
2069
|
+
// execGit sets no core.quotepath config, so git's default `true` applies:
|
|
2070
|
+
// any path containing non-ASCII bytes (or `"`, `\`, control bytes) is
|
|
2071
|
+
// C-quoted — `"docs/\350\256\276…/overview.md"`. Capturing that verbatim
|
|
2072
|
+
// garbles affected_paths/elements and makes isPathMapped compare the
|
|
2073
|
+
// quoted prefix (`"docs`) against STRUCTURE.md, misclassifying DOCUMENTED
|
|
2074
|
+
// directories as new_dir (#4081). Decode with the single owner of the
|
|
2075
|
+
// git C-quote seam (worktree-safety.cjs); a non-quoted value — the plain
|
|
2076
|
+
// ASCII common case — passes through untouched. Both capture groups are
|
|
2077
|
+
// decoded: R/C lines carry old AND new paths, either may be quoted.
|
|
2078
|
+
const file = decodeGitQuotedPath(m[3] || m[2]);
|
|
1692
2079
|
if (status === 'A' || status === 'R' || status === 'C')
|
|
1693
2080
|
added.push(file);
|
|
1694
2081
|
else if (status === 'M')
|
|
@@ -1751,6 +2138,7 @@ function cmdVerifyCodebaseDrift(cwd, raw) {
|
|
|
1751
2138
|
module.exports = {
|
|
1752
2139
|
scanNegativeGrepCommentEcho,
|
|
1753
2140
|
scanFileWideNegativeGateConflict,
|
|
2141
|
+
scanQuantitativeCriteria,
|
|
1754
2142
|
cmdVerifySummary,
|
|
1755
2143
|
verifySummaryCore,
|
|
1756
2144
|
cmdVerifyPlanStructure,
|
|
@@ -1764,10 +2152,7 @@ module.exports = {
|
|
|
1764
2152
|
cmdValidateAgents,
|
|
1765
2153
|
cmdVerifySchemaDrift,
|
|
1766
2154
|
cmdVerifyCodebaseDrift,
|
|
2155
|
+
computeContextDrift,
|
|
2156
|
+
cmdVerifyContextDrift,
|
|
1767
2157
|
STATE_HEAD_ADVISORY_COMMITS,
|
|
1768
|
-
// Test seam (#1883): listMilestoneArchiveDirs is private and exercised through
|
|
1769
|
-
// the validate command, which runs in a subprocess — an fs monkeypatch in the
|
|
1770
|
-
// test process cannot reach it. Exposed under a leading underscore so the
|
|
1771
|
-
// permission-error path can be unit-tested directly (no chmod 0o000).
|
|
1772
|
-
_listMilestoneArchiveDirs: listMilestoneArchiveDirs,
|
|
1773
2158
|
};
|