@opengsd/gsd-core 1.14.0 → 1.16.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 +85 -5
- package/README.ja-JP.md +3 -3
- package/README.ko-KR.md +3 -3
- package/README.pt-BR.md +3 -3
- package/README.zh-CN.md +3 -3
- package/agents/gsd-code-fixer.compact.md +7 -6
- package/agents/gsd-code-fixer.md +9 -8
- package/agents/gsd-code-reviewer.compact.md +5 -3
- package/agents/gsd-code-reviewer.md +8 -6
- package/agents/gsd-debug-session-manager.compact.md +17 -2
- package/agents/gsd-debug-session-manager.md +17 -2
- package/agents/gsd-debugger.md +3 -3
- package/agents/gsd-eval-auditor.compact.md +1 -1
- package/agents/gsd-eval-auditor.md +1 -1
- package/agents/gsd-executor.md +17 -12
- package/agents/gsd-intel-updater.compact.md +1 -1
- package/agents/gsd-intel-updater.md +1 -1
- package/agents/gsd-mempalace-curator.md +2 -2
- package/agents/gsd-phase-researcher.md +19 -11
- package/agents/gsd-plan-checker.md +15 -9
- package/agents/gsd-planner.md +15 -11
- package/agents/gsd-project-researcher.compact.md +1 -1
- package/agents/gsd-project-researcher.md +1 -1
- package/agents/gsd-research-synthesizer.compact.md +1 -1
- package/agents/gsd-research-synthesizer.md +1 -1
- package/agents/gsd-ui-auditor.compact.md +21 -30
- package/agents/gsd-ui-auditor.md +166 -37
- package/agents/gsd-ui-researcher.compact.md +1 -1
- package/agents/gsd-ui-researcher.md +1 -1
- package/agents/gsd-verifier.md +37 -14
- package/bin/install.js +764 -287
- package/commands/gsd/add-tests.md +6 -1
- package/commands/gsd/ai-integration-phase.md +6 -1
- package/commands/gsd/audit-fix.md +5 -0
- package/commands/gsd/audit-milestone.md +6 -1
- package/commands/gsd/autonomous.md +7 -2
- package/commands/gsd/capture.md +9 -5
- package/commands/gsd/code-review.md +7 -2
- package/commands/gsd/complete-milestone.md +4 -0
- package/commands/gsd/config.md +7 -3
- package/commands/gsd/debug.md +11 -7
- package/commands/gsd/discuss-phase.md +7 -3
- package/commands/gsd/docs-update.md +12 -7
- package/commands/gsd/eval-review.md +6 -1
- package/commands/gsd/execute-phase.md +12 -7
- package/commands/gsd/extract-learnings.md +5 -0
- package/commands/gsd/fast.md +4 -0
- package/commands/gsd/forensics.md +5 -1
- package/commands/gsd/graphify.md +10 -6
- package/commands/gsd/health.md +5 -0
- package/commands/gsd/help.md +7 -2
- package/commands/gsd/import.md +7 -3
- package/commands/gsd/inbox.md +5 -0
- package/commands/gsd/ingest-docs.md +5 -1
- package/commands/gsd/manager.md +6 -1
- package/commands/gsd/map-codebase.md +7 -3
- package/commands/gsd/mempalace-capture.md +12 -4
- package/commands/gsd/mempalace-recall.md +5 -1
- package/commands/gsd/milestone-summary.md +5 -1
- package/commands/gsd/mvp-phase.md +8 -3
- package/commands/gsd/new-milestone.md +6 -1
- package/commands/gsd/new-project.md +5 -0
- package/commands/gsd/next.md +6 -1
- package/commands/gsd/ns-context.md +4 -0
- package/commands/gsd/ns-ideate.md +4 -0
- package/commands/gsd/ns-manage.md +4 -0
- package/commands/gsd/ns-project.md +4 -0
- package/commands/gsd/ns-review.md +4 -0
- package/commands/gsd/ns-workflow.md +4 -0
- package/commands/gsd/onboard.md +6 -1
- package/commands/gsd/pause-work.md +5 -1
- package/commands/gsd/phase.md +8 -4
- package/commands/gsd/plan-phase.md +6 -1
- package/commands/gsd/plan-review-convergence.md +11 -7
- package/commands/gsd/pr-branch.md +4 -0
- package/commands/gsd/profile-user.md +5 -1
- package/commands/gsd/progress.md +7 -2
- package/commands/gsd/quick-batch.md +21 -9
- package/commands/gsd/quick.md +12 -7
- package/commands/gsd/review.md +7 -4
- package/commands/gsd/secure-phase.md +6 -1
- package/commands/gsd/ship.md +5 -0
- package/commands/gsd/sketch.md +7 -2
- package/commands/gsd/spec-phase.md +5 -1
- package/commands/gsd/spike.md +8 -3
- package/commands/gsd/surface.md +5 -1
- package/commands/gsd/thread.md +4 -0
- package/commands/gsd/ui-phase.md +6 -1
- package/commands/gsd/ui-review.md +6 -1
- package/commands/gsd/ultraplan-phase.md +5 -1
- package/commands/gsd/undo.md +5 -1
- package/commands/gsd/update.md +6 -2
- package/commands/gsd/validate-phase.md +6 -1
- package/commands/gsd/verify-work.md +6 -1
- package/commands/gsd/workspace.md +7 -3
- package/gsd-core/bin/gsd-tools.cjs +477 -78
- package/gsd-core/bin/lib/active-workstream-store.cjs +15 -0
- package/gsd-core/bin/lib/adr-parser.cjs +3 -1
- package/gsd-core/bin/lib/agent-install-check.cjs +4 -1
- package/gsd-core/bin/lib/audit.cjs +144 -42
- package/gsd-core/bin/lib/broken-windows.cjs +13 -13
- package/gsd-core/bin/lib/capability-activation.cjs +9 -4
- package/gsd-core/bin/lib/capability-registry.cjs +197 -222
- package/gsd-core/bin/lib/capability-validator.cjs +16 -1
- package/gsd-core/bin/lib/check-auto-mode.cjs +35 -0
- package/gsd-core/bin/lib/check-command-router.cjs +164 -1625
- package/gsd-core/bin/lib/cjs-command-router-adapter.cjs +13 -2
- package/gsd-core/bin/lib/cli-exit.cjs +12 -0
- package/gsd-core/bin/lib/codex-agent-toml.cjs +32 -33
- package/gsd-core/bin/lib/command-aliases.cjs +7 -0
- package/gsd-core/bin/lib/command-routing-hub.cjs +48 -1
- package/gsd-core/bin/lib/commands.cjs +343 -207
- package/gsd-core/bin/lib/complexity-trigger.cjs +8 -7
- package/gsd-core/bin/lib/config-loader.cjs +65 -4
- package/gsd-core/bin/lib/config.cjs +76 -19
- package/gsd-core/bin/lib/core-utils.cjs +6 -1
- package/gsd-core/bin/lib/coverage.cjs +4 -8
- package/gsd-core/bin/lib/decision-coverage-support.cjs +259 -0
- package/gsd-core/bin/lib/decisions.cjs +30 -14
- package/gsd-core/bin/lib/drift.cjs +177 -42
- package/gsd-core/bin/lib/frontmatter-fence.cjs +90 -0
- package/gsd-core/bin/lib/frontmatter-splice.cjs +494 -0
- package/gsd-core/bin/lib/frontmatter.cjs +426 -234
- package/gsd-core/bin/lib/gap-checker.cjs +72 -29
- package/gsd-core/bin/lib/gate-api-coverage-verify-pre.cjs +381 -0
- package/gsd-core/bin/lib/gate-args.cjs +53 -0
- package/gsd-core/bin/lib/gate-codebase-drift.cjs +285 -0
- package/gsd-core/bin/lib/gate-config.cjs +46 -0
- package/gsd-core/bin/lib/gate-context-drift.cjs +141 -0
- package/gsd-core/bin/lib/gate-decision-coverage-plan.cjs +169 -0
- package/gsd-core/bin/lib/gate-decision-coverage-verify.cjs +126 -0
- package/gsd-core/bin/lib/gate-evaluation-scope.cjs +555 -0
- package/gsd-core/bin/lib/gate-evidence.cjs +138 -0
- package/gsd-core/bin/lib/gate-exit.cjs +27 -0
- package/gsd-core/bin/lib/gate-gap-analysis-plan-post.cjs +61 -0
- package/gsd-core/bin/lib/gate-phase-context.cjs +170 -0
- package/gsd-core/bin/lib/gate-predicate-evaluator.cjs +1 -1
- package/gsd-core/bin/lib/gate-predicate.cjs +165 -0
- package/gsd-core/bin/lib/gate-prohibition-enforcement.cjs +94 -0
- package/gsd-core/bin/lib/gate-schema-drift.cjs +165 -0
- package/gsd-core/bin/lib/gate-tdd-red-evidence.cjs +100 -0
- package/gsd-core/bin/lib/gate-tdd-review-checkpoint.cjs +182 -0
- package/gsd-core/bin/lib/gate-ui-plan.cjs +86 -0
- package/gsd-core/bin/lib/gate-ui-safety.cjs +80 -0
- package/gsd-core/bin/lib/gate-verdict.cjs +64 -0
- package/gsd-core/bin/lib/gate-verify-command-paths.cjs +78 -0
- package/gsd-core/bin/lib/gate-verify-failure-directions.cjs +41 -0
- package/gsd-core/bin/lib/graphify.cjs +10 -2
- package/gsd-core/bin/lib/health-diagnostic-rules/phase-structure.cjs +43 -47
- package/gsd-core/bin/lib/health-diagnostic.cjs +45 -8
- package/gsd-core/bin/lib/host-runtime-detection.cjs +9 -0
- package/gsd-core/bin/lib/init.cjs +364 -132
- package/gsd-core/bin/lib/install-engine.cjs +30 -33
- package/gsd-core/bin/lib/install-profiles.cjs +7 -4
- package/gsd-core/bin/lib/installer-migrations.cjs +8 -1
- package/gsd-core/bin/lib/io.cjs +122 -3
- package/gsd-core/bin/lib/loop-resolver.cjs +95 -0
- package/gsd-core/bin/lib/markdown-sectionizer.cjs +75 -1
- package/gsd-core/bin/lib/milestone.cjs +37 -6
- package/gsd-core/bin/lib/model-resolver.cjs +171 -62
- package/gsd-core/bin/lib/observability/event.cjs +1 -1
- package/gsd-core/bin/lib/observability/logger.cjs +46 -1
- package/gsd-core/bin/lib/pattern.cjs +10 -0
- package/gsd-core/bin/lib/phase-command-router.cjs +20 -5
- package/gsd-core/bin/lib/phase-estimation.cjs +5 -4
- package/gsd-core/bin/lib/phase-id-card.cjs +32 -0
- package/gsd-core/bin/lib/phase-id-display.cjs +78 -0
- package/gsd-core/bin/lib/phase-id.cjs +110 -8
- package/gsd-core/bin/lib/phase-lifecycle.cjs +9 -2
- package/gsd-core/bin/lib/phase-locator.cjs +29 -10
- package/gsd-core/bin/lib/phase-status.cjs +360 -0
- package/gsd-core/bin/lib/phase.cjs +489 -88
- package/gsd-core/bin/lib/plan-document.cjs +142 -20
- package/gsd-core/bin/lib/plan-drift-guard.cjs +5 -0
- package/gsd-core/bin/lib/planning-document.cjs +692 -0
- package/gsd-core/bin/lib/planning-inspect.cjs +60 -9
- package/gsd-core/bin/lib/planning-snapshot.cjs +18 -0
- package/gsd-core/bin/lib/planning-workspace.cjs +83 -55
- package/gsd-core/bin/lib/pr-branch-patterns.cjs +57 -0
- package/gsd-core/bin/lib/pristine-baseline.cjs +10 -0
- package/gsd-core/bin/lib/probe-core.cjs +7 -1
- package/gsd-core/bin/lib/profile-output.cjs +6 -3
- package/gsd-core/bin/lib/prohibition-enforcement.cjs +0 -55
- package/gsd-core/bin/lib/project-root.cjs +41 -2
- package/gsd-core/bin/lib/quick-batch-command-router.cjs +35 -9
- package/gsd-core/bin/lib/quick-batch-dispatch.cjs +11 -8
- package/gsd-core/bin/lib/real-home-guard.cjs +9 -1
- package/gsd-core/bin/lib/report-parser.cjs +269 -0
- package/gsd-core/bin/lib/review-lane-descriptor.cjs +10 -30
- package/gsd-core/bin/lib/review-reviewer-selection.cjs +2 -2
- package/gsd-core/bin/lib/roadmap-command-router.cjs +25 -19
- package/gsd-core/bin/lib/roadmap-parser.cjs +242 -15
- package/gsd-core/bin/lib/roadmap-upgrade.cjs +1653 -65
- package/gsd-core/bin/lib/roadmap.cjs +405 -88
- package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +373 -187
- package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +5 -2
- package/gsd-core/bin/lib/runtime-artifact-layout.cjs +57 -1
- package/gsd-core/bin/lib/runtime-homes.cjs +14 -7
- package/gsd-core/bin/lib/runtime-hooks-surface.cjs +522 -624
- package/gsd-core/bin/lib/runtime-name-policy.cjs +246 -21
- package/gsd-core/bin/lib/runtime-slash.cjs +47 -30
- package/gsd-core/bin/lib/shell-command-projection.cjs +47 -8
- package/gsd-core/bin/lib/smart-entry.cjs +19 -3
- package/gsd-core/bin/lib/stale-bake-guard.cjs +32 -48
- package/gsd-core/bin/lib/state-contract.cjs +15 -18
- package/gsd-core/bin/lib/state-document.cjs +100 -22
- package/gsd-core/bin/lib/state-transition.cjs +39 -2
- package/gsd-core/bin/lib/state.cjs +256 -105
- package/gsd-core/bin/lib/surface.cjs +19 -2
- package/gsd-core/bin/lib/tdd-red-evidence.cjs +48 -79
- package/gsd-core/bin/lib/uat-predicate.cjs +359 -38
- package/gsd-core/bin/lib/uat.cjs +432 -7
- package/gsd-core/bin/lib/ui-consideration-probe.cjs +15 -2
- package/gsd-core/bin/lib/ui-frontend-evidence.cjs +167 -40
- package/gsd-core/bin/lib/undo-commit-selection.cjs +131 -0
- package/gsd-core/bin/lib/vendor/README.md +31 -9
- package/gsd-core/bin/lib/vendor/saxes.cjs +1934 -0
- package/gsd-core/bin/lib/vendor/saxes.cjs.LICENSE.txt +92 -0
- package/gsd-core/bin/lib/vendor/tap-parser.cjs +8927 -0
- package/gsd-core/bin/lib/vendor/tap-parser.cjs.LICENSE.txt +152 -0
- package/gsd-core/bin/lib/verification-command-router.cjs +2 -1
- package/gsd-core/bin/lib/verification.cjs +1378 -274
- package/gsd-core/bin/lib/verify-command-grounding.cjs +46 -2
- package/gsd-core/bin/lib/verify-command-router.cjs +18 -7
- package/gsd-core/bin/lib/verify.cjs +357 -531
- package/gsd-core/bin/lib/workstream-inventory-builder.cjs +14 -6
- package/gsd-core/bin/lib/workstream-inventory.cjs +31 -21
- package/gsd-core/bin/lib/workstream-name-policy.cjs +31 -1
- package/gsd-core/bin/lib/workstream.cjs +11 -2
- package/gsd-core/bin/lib/worktree-base-ref.cjs +482 -73
- package/gsd-core/bin/lib/worktree-safety.cjs +784 -51
- package/gsd-core/bin/shared/config-defaults.manifest.json +8 -0
- package/gsd-core/bin/shared/config-schema.manifest.json +5 -0
- package/gsd-core/references/autonomous-smart-discuss.md +2 -1
- package/gsd-core/references/autonomous-ui-design-contract.md +3 -3
- package/gsd-core/references/checkpoints.md +5 -3
- package/gsd-core/references/edge-probe-fixtures/01-round-half-even/expected-coverage.json +28 -3
- package/gsd-core/references/edge-probe-fixtures/02-merge-intervals/expected-coverage.json +37 -4
- package/gsd-core/references/edge-probe-fixtures/03-truncate-graphemes/expected-coverage.json +28 -3
- package/gsd-core/references/edge-probe-fixtures/04-money-rounding/expected-coverage.json +28 -3
- package/gsd-core/references/edge-probe-fixtures/05-list-dedupe/expected-coverage.json +37 -4
- package/gsd-core/references/edge-probe-fixtures/06-resolved-mixed/expected-coverage.json +37 -4
- package/gsd-core/references/edge-probe.md +195 -21
- package/gsd-core/references/execute-mvp-tdd.md +5 -10
- package/gsd-core/references/execute-phase-between-wave-reset.md +10 -6
- package/gsd-core/references/execute-phase-response-language.md +1 -1
- package/gsd-core/references/execute-phase-wave-guard.md +22 -11
- package/gsd-core/references/gsd-run-resolver.md +1 -1
- package/gsd-core/references/loop-hook-dispatch.md +7 -1
- package/gsd-core/references/model-profiles.md +1 -1
- package/gsd-core/references/offer-next.md +1 -1
- package/gsd-core/references/phase-argument-parsing.md +9 -7
- package/gsd-core/references/phase-id-convention.md +28 -0
- package/gsd-core/references/planner-gap-closure.md +2 -0
- package/gsd-core/references/planner-load-graph-context.md +24 -13
- package/gsd-core/references/planner-verify-command-grounding.md +14 -0
- package/gsd-core/references/planning-config.md +12 -3
- package/gsd-core/references/spidr-splitting.md +1 -1
- package/gsd-core/references/tdd.md +37 -8
- package/gsd-core/references/ui-consideration-probe.md +10 -5
- package/gsd-core/references/verifier-phase-gates.md +5 -2
- package/gsd-core/references/verify-command-path-resolvability.md +10 -2
- package/gsd-core/references/verify-mvp-mode.md +2 -2
- package/gsd-core/references/workstream-flag.md +33 -3
- package/gsd-core/references/worktree-path-safety.md +321 -0
- package/gsd-core/templates/README.md +1 -1
- package/gsd-core/templates/UAT.md +17 -1
- package/gsd-core/templates/config.json +2 -11
- package/gsd-core/templates/verification-report.md +1 -1
- package/gsd-core/workflows/_runtime-launcher.snippet.sh +1 -1
- package/gsd-core/workflows/add-backlog.md +1 -1
- package/gsd-core/workflows/add-phase.md +8 -7
- package/gsd-core/workflows/add-tests.md +4 -3
- package/gsd-core/workflows/add-todo.md +6 -5
- package/gsd-core/workflows/ai-integration-phase.md +13 -4
- package/gsd-core/workflows/audit-fix.md +1 -1
- package/gsd-core/workflows/audit-milestone.md +4 -3
- package/gsd-core/workflows/audit-uat.md +1 -1
- package/gsd-core/workflows/autonomous/steps/converge-fail-fast.md +9 -18
- package/gsd-core/workflows/autonomous.md +43 -23
- package/gsd-core/workflows/check-todos.md +7 -6
- package/gsd-core/workflows/cleanup.md +2 -2
- package/gsd-core/workflows/code-review/steps/dispatch-fix.md +4 -3
- package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +20 -13
- package/gsd-core/workflows/code-review-fix.md +112 -25
- package/gsd-core/workflows/code-review.md +146 -135
- package/gsd-core/workflows/complete-milestone/detail/elaboration.md +4 -3
- package/gsd-core/workflows/complete-milestone.md +13 -8
- package/gsd-core/workflows/debug.md +32 -7
- package/gsd-core/workflows/diagnose-issues.md +3 -2
- package/gsd-core/workflows/discuss-phase/modes/advisor.md +1 -1
- package/gsd-core/workflows/discuss-phase/modes/chain.md +1 -1
- package/gsd-core/workflows/discuss-phase-assumptions.md +4 -3
- package/gsd-core/workflows/discuss-phase.md +4 -3
- package/gsd-core/workflows/do.md +2 -2
- package/gsd-core/workflows/docs-update.md +6 -5
- package/gsd-core/workflows/edit-phase.md +4 -3
- package/gsd-core/workflows/eval-review.md +14 -5
- package/gsd-core/workflows/execute-phase/detail/elaboration.md +2 -2
- package/gsd-core/workflows/execute-phase/steps/code-review-disposition.md +1019 -0
- package/gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md +15 -4
- package/gsd-core/workflows/execute-phase/steps/completion-reconciliation.md +10 -7
- package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +37 -3
- package/gsd-core/workflows/execute-phase/steps/gap-closure-artifacts.md +3 -1
- package/gsd-core/workflows/execute-phase/steps/partial-wave.md +2 -2
- package/gsd-core/workflows/execute-phase/steps/per-plan-executor-routing.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/post-merge-gate.md +46 -9
- package/gsd-core/workflows/execute-phase/steps/protected-branch.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/ready-wave-gate.md +37 -0
- package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/regression-gate.md +2 -0
- package/gsd-core/workflows/execute-phase/steps/tdd-applicability-resolution.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/threat-id-gate.md +28 -0
- package/gsd-core/workflows/execute-phase/steps/verify-phase-goal.md +187 -0
- package/gsd-core/workflows/execute-phase/steps/wave-post-gate-hooks.md +2 -3
- package/gsd-core/workflows/execute-phase/steps/worktree-base-check.md +25 -0
- package/gsd-core/workflows/execute-phase.md +96 -178
- package/gsd-core/workflows/execute-plan.md +18 -23
- package/gsd-core/workflows/explore.md +4 -4
- package/gsd-core/workflows/extract-learnings.md +4 -2
- package/gsd-core/workflows/fast.md +1 -1
- package/gsd-core/workflows/forensics.md +1 -1
- package/gsd-core/workflows/graduation.md +1 -1
- package/gsd-core/workflows/health.md +3 -2
- package/gsd-core/workflows/help/modes/full.compact.md +3 -3
- package/gsd-core/workflows/help/modes/full.md +5 -5
- package/gsd-core/workflows/help/modes/topic.md +15 -5
- package/gsd-core/workflows/import.md +4 -3
- package/gsd-core/workflows/inbox.md +2 -2
- package/gsd-core/workflows/ingest-docs.md +3 -3
- package/gsd-core/workflows/insert-phase.md +4 -3
- package/gsd-core/workflows/list-seeds.md +1 -1
- package/gsd-core/workflows/list-workspaces.md +1 -1
- package/gsd-core/workflows/manager.md +6 -4
- package/gsd-core/workflows/map-codebase.md +5 -4
- package/gsd-core/workflows/milestone-summary.md +3 -2
- package/gsd-core/workflows/mvp-phase.md +14 -14
- package/gsd-core/workflows/new-milestone.md +11 -11
- package/gsd-core/workflows/new-project/steps/auto-mode-config.md +3 -3
- package/gsd-core/workflows/new-project/steps/codebase-map-offer.md +1 -1
- package/gsd-core/workflows/new-project.md +7 -7
- package/gsd-core/workflows/new-workspace.md +2 -2
- package/gsd-core/workflows/next.md +1 -1
- package/gsd-core/workflows/note.md +1 -1
- package/gsd-core/workflows/onboard.md +1 -1
- package/gsd-core/workflows/pause-work.md +2 -2
- package/gsd-core/workflows/plan-phase/detail/elaboration.md +1 -1
- package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +17 -5
- package/gsd-core/workflows/plan-phase/steps/closed-phase-gate.md +1 -1
- package/gsd-core/workflows/plan-phase/steps/prd-express-path.md +1 -1
- package/gsd-core/workflows/plan-phase/steps/stall-detection-helpers.md +23 -5
- package/gsd-core/workflows/plan-phase.md +50 -24
- package/gsd-core/workflows/plan-review-convergence.md +21 -5
- package/gsd-core/workflows/plant-seed.md +62 -20
- package/gsd-core/workflows/pr-branch.md +113 -13
- package/gsd-core/workflows/profile-user.md +2 -2
- package/gsd-core/workflows/progress.md +19 -49
- package/gsd-core/workflows/quick/steps/plan-checker-loop.md +27 -0
- package/gsd-core/workflows/quick/steps/quick-verification.md +4 -4
- package/gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md +30 -11
- package/gsd-core/workflows/quick-batch/steps/batch-init.md +1 -1
- package/gsd-core/workflows/quick-batch/steps/completion.md +1 -1
- package/gsd-core/workflows/quick-batch/steps/merge-wave.md +1 -1
- package/gsd-core/workflows/quick-batch/steps/planner-wave.md +1 -1
- package/gsd-core/workflows/quick-batch/steps/research-phase.md +1 -1
- package/gsd-core/workflows/quick-batch/steps/resume-mode.md +1 -1
- package/gsd-core/workflows/quick-batch/steps/verification-wave.md +9 -3
- package/gsd-core/workflows/quick-batch/steps/worktree-dispatch.md +1 -1
- package/gsd-core/workflows/quick-batch.md +15 -10
- package/gsd-core/workflows/quick.md +62 -39
- package/gsd-core/workflows/reapply-patches.md +9 -3
- package/gsd-core/workflows/remove-phase.md +3 -2
- package/gsd-core/workflows/remove-workspace.md +2 -2
- package/gsd-core/workflows/resume-project.md +3 -2
- package/gsd-core/workflows/review.md +33 -17
- package/gsd-core/workflows/scan.md +3 -2
- package/gsd-core/workflows/secure-phase.md +13 -13
- package/gsd-core/workflows/settings-advanced.md +30 -10
- package/gsd-core/workflows/settings-integrations.md +2 -3
- package/gsd-core/workflows/settings.md +4 -4
- package/gsd-core/workflows/ship.md +14 -13
- package/gsd-core/workflows/sketch-wrap-up.md +1 -1
- package/gsd-core/workflows/sketch.md +1 -1
- package/gsd-core/workflows/smart-entry.md +2 -2
- package/gsd-core/workflows/spec-phase.md +15 -5
- package/gsd-core/workflows/spike-wrap-up.md +1 -1
- package/gsd-core/workflows/spike.md +1 -1
- package/gsd-core/workflows/stats.md +1 -1
- package/gsd-core/workflows/sync-skills.md +5 -5
- package/gsd-core/workflows/thread.md +2 -2
- package/gsd-core/workflows/transition.md +13 -23
- package/gsd-core/workflows/ui-phase.md +48 -11
- package/gsd-core/workflows/ui-review.md +21 -6
- package/gsd-core/workflows/ultraplan-phase.md +3 -2
- package/gsd-core/workflows/undo.md +339 -20
- package/gsd-core/workflows/update.md +7 -7
- package/gsd-core/workflows/validate-phase.md +12 -13
- package/gsd-core/workflows/verify-work/detail/elaboration.md +43 -3
- package/gsd-core/workflows/verify-work/steps/automated-ui-verification.md +1 -1
- package/gsd-core/workflows/verify-work/steps/mvp-uat-framing.md +5 -3
- package/gsd-core/workflows/verify-work.md +129 -55
- package/hooks/dist/gsd-agent-isolation-guard.js +32 -0
- package/hooks/dist/gsd-check-update-worker.js +8 -0
- package/hooks/dist/gsd-check-update.js +8 -0
- package/hooks/dist/gsd-context-monitor.js +31 -8
- package/hooks/dist/gsd-cursor-subagent-start.js +8 -0
- package/hooks/dist/gsd-secret-read-guard.js +161 -4
- package/hooks/dist/gsd-statusline.js +85 -20
- package/hooks/dist/gsd-update-banner.js +8 -0
- package/hooks/dist/gsd-validate-commit.sh +63 -4
- package/hooks/dist/gsd-windsurf-pre-write.js +11 -2
- package/hooks/dist/gsd-workflow-guard.js +5 -4
- package/hooks/dist/gsd-worktree-path-guard.js +6 -2
- package/hooks/dist/lib/cli-exit.js +12 -0
- package/hooks/dist/lib/git-probe.js +17 -1
- package/hooks/dist/lib/isolation-sentinel.js +2 -2
- package/hooks/gsd-agent-isolation-guard.js +32 -0
- package/hooks/gsd-check-update-worker.js +8 -0
- package/hooks/gsd-check-update.js +8 -0
- package/hooks/gsd-context-monitor.js +31 -8
- package/hooks/gsd-cursor-subagent-start.js +8 -0
- package/hooks/gsd-secret-read-guard.js +161 -4
- package/hooks/gsd-statusline.js +85 -20
- package/hooks/gsd-update-banner.js +8 -0
- package/hooks/gsd-validate-commit.sh +63 -4
- package/hooks/gsd-windsurf-pre-write.js +11 -2
- package/hooks/gsd-workflow-guard.js +5 -4
- package/hooks/gsd-worktree-path-guard.js +6 -2
- package/hooks/hooks.json +5 -5
- package/hooks/lib/cli-exit.js +12 -0
- package/hooks/lib/git-probe.js +17 -1
- package/hooks/lib/isolation-sentinel.js +2 -2
- package/package.json +22 -4
- package/scripts/build-hooks.js +15 -6
- package/scripts/changeset/parse.cjs +52 -4
- package/scripts/check-contract-drift.cjs +127 -11
- package/scripts/ci-timeout-report.cjs +770 -4
- package/scripts/command-contract-helpers.cjs +15 -8
- package/scripts/docs-guard-registry.cjs +34 -0
- package/scripts/gen-features.cjs +13 -8
- package/scripts/gen-hooks-cli-exit.cjs +12 -28
- package/scripts/gen-loop-host-contract.cjs +79 -1
- package/scripts/gen-platform-conformance-tier.cjs +187 -1
- package/scripts/gen-plugin-skills.cjs +87 -1
- package/scripts/gen-research-agents.cjs +24 -31
- package/scripts/gen-scripts-cli-exit.cjs +30 -3
- package/scripts/gen-test-timings.cjs +32 -7
- package/scripts/lib/cli-exit.cjs +12 -0
- package/scripts/lib/macos-conformance-tier.generated.cjs +34 -2
- package/scripts/lib/ndjson-reporter.cjs +31 -5
- package/scripts/lib/platform-conformance-tier.generated.cjs +45 -5
- package/scripts/lib/registration-ledger-preload.cjs +155 -0
- package/scripts/lib/vendor-bundle.cjs +59 -0
- package/scripts/lib/vendor-licenses/saxes-6.0.0.txt +64 -0
- 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-completion-predicate-drift.cjs +18 -19
- package/scripts/lint-descriptions.cjs +7 -3
- package/scripts/lint-docs-guard-registration.exempt-baseline.cjs +38 -1
- package/scripts/lint-eslint-glob-coverage.allowlist.json +20 -0
- package/scripts/lint-frontmatter-fence-drift.cjs +313 -0
- package/scripts/lint-frontmatter-scalar-broad-grep.cjs +122 -16
- package/scripts/lint-phase-arg-assignment.cjs +257 -0
- package/scripts/lint-phase-enumeration-drift.cjs +12 -8
- package/scripts/lint-phase-id-drift.cjs +319 -5
- package/scripts/lint-planning-document-positive-control.cjs +329 -0
- package/scripts/lint-pr-branch-pattern-drift.cjs +148 -0
- package/scripts/lint-response-language-coverage.cjs +3 -0
- package/scripts/lint-retired-runtime-name.cjs +619 -0
- package/scripts/lint-skill-deps.cjs +7 -3
- package/scripts/lint-state-write-path-drift.cjs +93 -0
- package/scripts/lint-test-file-count.allowlist.json +38 -9
- package/scripts/lint-test-file-count.cjs +34 -1
- package/scripts/lint-vendored-deps.cjs +41 -5
- package/scripts/lint-workflow-shellcheck-baseline.json +25 -10
- package/scripts/mutation-matrix.cjs +50 -5
- package/scripts/prompt-injection-scan.sh +4 -0
- package/scripts/release-tarball-smoke.cjs +194 -1
- package/scripts/require-issue-link-policy.cjs +6 -2
- package/scripts/sync-runtime-launcher.cjs +184 -2
- package/scripts/verify-npm-publish.cjs +76 -20
- package/skills/gsd-add-tests/SKILL.md +6 -1
- package/skills/gsd-ai-integration-phase/SKILL.md +6 -1
- package/skills/gsd-audit-fix/SKILL.md +5 -0
- package/skills/gsd-audit-milestone/SKILL.md +6 -1
- package/skills/gsd-autonomous/SKILL.md +7 -2
- package/skills/gsd-capture/SKILL.md +9 -5
- package/skills/gsd-code-review/SKILL.md +7 -2
- package/skills/gsd-complete-milestone/SKILL.md +4 -0
- package/skills/gsd-config/SKILL.md +8 -4
- package/skills/gsd-debug/SKILL.md +11 -7
- package/skills/gsd-discuss-phase/SKILL.md +7 -3
- package/skills/gsd-docs-update/SKILL.md +12 -7
- package/skills/gsd-eval-review/SKILL.md +6 -1
- package/skills/gsd-execute-phase/SKILL.md +12 -7
- package/skills/gsd-extract-learnings/SKILL.md +5 -0
- package/skills/gsd-fast/SKILL.md +4 -0
- package/skills/gsd-forensics/SKILL.md +5 -1
- package/skills/gsd-graphify/SKILL.md +10 -6
- package/skills/gsd-health/SKILL.md +5 -0
- package/skills/gsd-help/SKILL.md +7 -2
- package/skills/gsd-import/SKILL.md +7 -3
- package/skills/gsd-inbox/SKILL.md +5 -0
- package/skills/gsd-ingest-docs/SKILL.md +5 -1
- package/skills/gsd-manager/SKILL.md +6 -1
- package/skills/gsd-map-codebase/SKILL.md +7 -3
- package/skills/gsd-mempalace-capture/SKILL.md +12 -4
- package/skills/gsd-mempalace-recall/SKILL.md +5 -1
- package/skills/gsd-milestone-summary/SKILL.md +5 -1
- package/skills/gsd-mvp-phase/SKILL.md +8 -3
- package/skills/gsd-new-milestone/SKILL.md +6 -1
- package/skills/gsd-new-project/SKILL.md +5 -0
- package/skills/gsd-next/SKILL.md +6 -1
- package/skills/gsd-ns-context/SKILL.md +4 -0
- package/skills/gsd-ns-ideate/SKILL.md +4 -0
- package/skills/gsd-ns-manage/SKILL.md +4 -0
- package/skills/gsd-ns-project/SKILL.md +4 -0
- package/skills/gsd-ns-review/SKILL.md +4 -0
- package/skills/gsd-ns-workflow/SKILL.md +4 -0
- package/skills/gsd-onboard/SKILL.md +6 -1
- package/skills/gsd-pause-work/SKILL.md +5 -1
- package/skills/gsd-phase/SKILL.md +8 -4
- package/skills/gsd-plan-phase/SKILL.md +6 -1
- package/skills/gsd-plan-review-convergence/SKILL.md +10 -6
- package/skills/gsd-pr-branch/SKILL.md +4 -0
- package/skills/gsd-profile-user/SKILL.md +5 -1
- package/skills/gsd-progress/SKILL.md +7 -2
- package/skills/gsd-quick/SKILL.md +16 -10
- package/skills/gsd-quick-batch/SKILL.md +21 -9
- package/skills/gsd-review/SKILL.md +7 -4
- package/skills/gsd-review-backlog/SKILL.md +3 -2
- package/skills/gsd-secure-phase/SKILL.md +6 -1
- package/skills/gsd-ship/SKILL.md +5 -0
- package/skills/gsd-sketch/SKILL.md +7 -2
- package/skills/gsd-spec-phase/SKILL.md +5 -1
- package/skills/gsd-spike/SKILL.md +8 -3
- package/skills/gsd-surface/SKILL.md +5 -1
- package/skills/gsd-thread/SKILL.md +4 -0
- package/skills/gsd-ui-phase/SKILL.md +6 -1
- package/skills/gsd-ui-review/SKILL.md +6 -1
- package/skills/gsd-ultraplan-phase/SKILL.md +5 -1
- package/skills/gsd-undo/SKILL.md +5 -1
- package/skills/gsd-update/SKILL.md +6 -2
- package/skills/gsd-validate-phase/SKILL.md +6 -1
- package/skills/gsd-verify-work/SKILL.md +6 -1
- package/skills/gsd-workspace/SKILL.md +7 -3
- package/skills/gsd-workstreams/SKILL.md +6 -6
- package/vscode/package.json +1 -1
|
@@ -22,27 +22,18 @@ const frontmatterMod = require("./frontmatter.cjs");
|
|
|
22
22
|
const stateMod = require("./state.cjs");
|
|
23
23
|
// eslint-disable-next-line @typescript-eslint/no-require-imports -- model-profiles.cjs is an export= CommonJS module
|
|
24
24
|
const modelProfilesMod = require("./model-profiles.cjs");
|
|
25
|
-
// eslint-disable-next-line @typescript-eslint/no-require-imports -- plan-scan.cjs is an export= CommonJS module
|
|
26
|
-
const planScanMod = require("./plan-scan.cjs");
|
|
27
|
-
// eslint-disable-next-line @typescript-eslint/no-require-imports -- verification.cjs is an export= CommonJS module
|
|
28
|
-
const verificationMod = require("./verification.cjs");
|
|
29
25
|
// eslint-disable-next-line @typescript-eslint/no-require-imports -- core-utils.cjs is an export= CommonJS module
|
|
30
26
|
const coreUtilsMod = require("./core-utils.cjs");
|
|
31
27
|
const { findOrphanSummaries, findUnsummarizedPlans } = coreUtilsMod;
|
|
32
|
-
// eslint-disable-next-line @typescript-eslint/no-require-imports -- planning-scope.cjs is an export= CommonJS module
|
|
33
|
-
const planningScopeMod = require("./planning-scope.cjs");
|
|
34
|
-
const { SCOPE } = planningScopeMod;
|
|
35
|
-
// eslint-disable-next-line @typescript-eslint/no-require-imports -- worktree-safety.cjs is an export= CommonJS module
|
|
36
|
-
const worktreeSafetyMod = require("./worktree-safety.cjs");
|
|
37
|
-
// Single owner of git C-quoted-path decoding (see #4081 note at the
|
|
38
|
-
// codebase-drift --name-status parse loop).
|
|
39
|
-
const { decodeGitQuotedPath } = worktreeSafetyMod;
|
|
40
28
|
const shell_command_projection_cjs_1 = require("./shell-command-projection.cjs");
|
|
41
29
|
const security_cjs_2 = require("./security.cjs");
|
|
42
30
|
const runtime_slash_cjs_1 = require("./runtime-slash.cjs");
|
|
43
31
|
const schema_detect_cjs_1 = require("./schema-detect.cjs");
|
|
44
32
|
const markdown_sectionizer_cjs_1 = require("./markdown-sectionizer.cjs");
|
|
45
33
|
const pattern_cjs_1 = require("./pattern.cjs");
|
|
34
|
+
const gate_exit_cjs_1 = require("./gate-exit.cjs");
|
|
35
|
+
const gate_evidence_cjs_1 = require("./gate-evidence.cjs");
|
|
36
|
+
const gate_verdict_cjs_1 = require("./gate-verdict.cjs");
|
|
46
37
|
// eslint-disable-next-line @typescript-eslint/no-require-imports -- agent-install-check.cjs is an export= CommonJS module
|
|
47
38
|
const agentInstallCheck = require("./agent-install-check.cjs");
|
|
48
39
|
const { checkAgentsInstalled, checkCodexModelPosture, checkCodexSandboxPosture } = agentInstallCheck;
|
|
@@ -50,9 +41,6 @@ const { checkAgentsInstalled, checkCodexModelPosture, checkCodexSandboxPosture }
|
|
|
50
41
|
const ioMod = require("./io.cjs");
|
|
51
42
|
const { output, error } = ioMod;
|
|
52
43
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
53
|
-
const phaseIdMod = require("./phase-id.cjs");
|
|
54
|
-
const { normalizePhaseName, matchPhaseDirs } = phaseIdMod;
|
|
55
|
-
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
56
44
|
const phaseLocatorMod = require("./phase-locator.cjs");
|
|
57
45
|
const { findPhaseInternal } = phaseLocatorMod;
|
|
58
46
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
@@ -68,8 +56,7 @@ const { buildPlanningSnapshot } = planningSnapshotMod;
|
|
|
68
56
|
const onboardProjectionMod = require("./onboard-projection.cjs");
|
|
69
57
|
const { REQUIRED_CODEBASE_MAP_FILES } = onboardProjectionMod;
|
|
70
58
|
const clock_cjs_1 = require("./clock.cjs");
|
|
71
|
-
const { planningDir,
|
|
72
|
-
const { defaultPhaseCleanCommitTimesMs } = verificationMod;
|
|
59
|
+
const { planningDir, withPlanningLock } = planningWorkspace;
|
|
73
60
|
const { extractFrontmatter, parseMustHavesBlock } = frontmatterMod;
|
|
74
61
|
const { readStateHeadFreshness } = stateMod;
|
|
75
62
|
/**
|
|
@@ -273,8 +260,8 @@ function cmdVerifySummary(cwd, summaryPath, checkFileCount, raw) {
|
|
|
273
260
|
}
|
|
274
261
|
/**
|
|
275
262
|
* Issue #429 — negative-grep comment-text echo gate.
|
|
276
|
-
* A literal that an acceptance criterion negative-greps for (grep -c 'LIT' file == 0
|
|
277
|
-
* must not also appear verbatim inside an <action> body, or the executor's commit-time
|
|
263
|
+
* A literal that an acceptance criterion negative-greps for (grep -c 'LIT' file == 0, or the
|
|
264
|
+
* negated spellings `! grep -q 'LIT' file` / `if ! grep ...`, #4541) must not also appear verbatim inside an <action> body, or the executor's commit-time
|
|
278
265
|
* verify gate fails on the comment echo rather than a real regression. Conservative:
|
|
279
266
|
* errors only on a confidently-extracted QUOTED literal; ambiguous (bareword) → warning.
|
|
280
267
|
*/
|
|
@@ -339,6 +326,12 @@ function splitShellSegments(line) {
|
|
|
339
326
|
segments.push(current.trim());
|
|
340
327
|
return segments.filter((s) => s !== '');
|
|
341
328
|
}
|
|
329
|
+
/**
|
|
330
|
+
* The status-test alternation of a negated grep gate (`grep -q 'LIT' f; test $? -ne 0`): the one
|
|
331
|
+
* definition `statusNegatedTest` (the segment test) and `statusNegatedSpanRe` (the span removal)
|
|
332
|
+
* share, so the two cannot drift apart.
|
|
333
|
+
*/
|
|
334
|
+
const STATUS_NEGATED_ALTERNATION = String.raw `(?:-ne\s+0|-eq\s+1|!=\s*0|==\s*1)`;
|
|
342
335
|
function scanNegativeGrepCommentEcho(content) {
|
|
343
336
|
const errors = [];
|
|
344
337
|
const warnings = [];
|
|
@@ -376,6 +369,18 @@ function scanNegativeGrepCommentEcho(content) {
|
|
|
376
369
|
// command, not the whole line) so a pasted verify command does not self-flag
|
|
377
370
|
// while a prose echo on the same line is still caught.
|
|
378
371
|
const cmdSpanRe = /grep(?:\s+-{1,2}[A-Za-z][A-Za-z-]*)+\s+(?:'[^']*'|"[^"]*"|[^\s'"|>&;]+)[^\n]*?(?:==|-eq|=)\s*0\b/g;
|
|
372
|
+
// #4541: the other spellings of a negative gate — `! grep ...` / `if ! grep ...` (the shell
|
|
373
|
+
// negates the exit status) and `grep ...; test $? -ne 0`. A pasted command in an <action> is
|
|
374
|
+
// likewise not an echo, so these spans are removed from the scanned action text too.
|
|
375
|
+
// The `!` is a command word: it follows a line start, whitespace, `;`, `(` or the `>` closing the
|
|
376
|
+
// `<automated>` tag, and is itself followed by whitespace (so `!=` and `!==` never match).
|
|
377
|
+
const negatedGrepRe = /(?:^|[\s;(>])!\s+grep((?:\s+-{1,2}[A-Za-z][A-Za-z-]*)*)\s+(?:'([^']*)'|"([^"]*)"|([^\s'"|>&;]+))/g;
|
|
378
|
+
const negatedSpanRe = /(?:^|[\s;(>])!\s+grep(?:\s+-{1,2}[A-Za-z][A-Za-z-]*)*\s+(?:'[^']*'|"[^"]*"|[^\s'"|>&;]+)[^\n;&|<]*/g;
|
|
379
|
+
const statusNegatedTest = new RegExp(String.raw `\$\?\s*${STATUS_NEGATED_ALTERNATION}(?![\w.])`);
|
|
380
|
+
// The span between the grep literal and the status test is bounded (`{0,1000}`), like the other
|
|
381
|
+
// scans in this file, rather than a lazy unanchored run to the end of the line.
|
|
382
|
+
const statusNegatedSpanRe = new RegExp(String.raw `grep(?:\s+-{1,2}[A-Za-z][A-Za-z-]*)*\s+(?:'[^']*'|"[^"]*"|[^\s'"|>&;]+)[^\n]{0,1000}?\$\?\s*${STATUS_NEGATED_ALTERNATION}(?![\w.])`, 'g');
|
|
383
|
+
const anyGrepRe = /grep((?:\s+-{1,2}[A-Za-z][A-Za-z-]*)*)\s+(?:'([^']*)'|"([^"]*)"|([^\s'"|>&;]+))/g;
|
|
379
384
|
// Security scan: must see the FULL text up to the first </action> — including a
|
|
380
385
|
// malformed inner <action> — so a grep-echo-0 trick cannot hide behind a
|
|
381
386
|
// deliberately-unclosed tag. Use a bounded to-first-close scan (ReDoS-safe via
|
|
@@ -386,7 +391,9 @@ function scanNegativeGrepCommentEcho(content) {
|
|
|
386
391
|
let acm;
|
|
387
392
|
while ((acm = actionRe.exec(text)) !== null)
|
|
388
393
|
actionZones.push(acm[1]);
|
|
389
|
-
const scannableActionText = actionZones
|
|
394
|
+
const scannableActionText = actionZones
|
|
395
|
+
.map((zone) => zone.replace(cmdSpanRe, ' ').replace(negatedSpanRe, ' ').replace(statusNegatedSpanRe, ' '))
|
|
396
|
+
.join('\n');
|
|
390
397
|
// 3. Per shell SEGMENT (split lines on && / ||) extract count-grep literals and
|
|
391
398
|
// check echoes. Per-segment splitting keeps a positive gate (`== 1`) from
|
|
392
399
|
// poisoning a negative gate (`== 0`) sharing the same physical line.
|
|
@@ -394,28 +401,52 @@ function scanNegativeGrepCommentEcho(content) {
|
|
|
394
401
|
const seenWarn = new Set();
|
|
395
402
|
const segments = text.split('\n').flatMap(splitShellSegments);
|
|
396
403
|
for (const seg of segments) {
|
|
397
|
-
if (!/grep
|
|
404
|
+
if (!/grep\b/.test(seg))
|
|
398
405
|
continue;
|
|
399
|
-
|
|
406
|
+
const countForm = /grep(?:\s+-{1,2}[A-Za-z])/.test(seg) && zeroCmp(seg);
|
|
407
|
+
const statusForm = statusNegatedTest.test(seg);
|
|
400
408
|
const quotedLits = [];
|
|
401
409
|
const bareLits = [];
|
|
402
|
-
|
|
403
|
-
while ((m = countGrepRe.exec(seg)) !== null) {
|
|
404
|
-
if (!optsHaveCount(m[1]) || optsHaveInvert(m[1]))
|
|
405
|
-
continue; // need count, not invert (-cv is positive)
|
|
410
|
+
const harvest = (m) => {
|
|
406
411
|
if (m[2] !== undefined)
|
|
407
412
|
quotedLits.push(m[2]);
|
|
408
413
|
else if (m[3] !== undefined)
|
|
409
414
|
quotedLits.push(m[3]);
|
|
410
415
|
else if (m[4] !== undefined && plausibleBare(m[4]))
|
|
411
416
|
bareLits.push(m[4]);
|
|
417
|
+
};
|
|
418
|
+
let m;
|
|
419
|
+
if (countForm) {
|
|
420
|
+
countGrepRe.lastIndex = 0;
|
|
421
|
+
while ((m = countGrepRe.exec(seg)) !== null) {
|
|
422
|
+
if (!optsHaveCount(m[1]) || optsHaveInvert(m[1]))
|
|
423
|
+
continue; // need count, not invert (-cv is positive)
|
|
424
|
+
harvest(m);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
// `! grep -q 'LIT' f` / `if ! grep ...`: the negation is the shell's own, so any grep it
|
|
428
|
+
// negates is a negative gate whether or not it counts (an inverted grep is the positive one).
|
|
429
|
+
negatedGrepRe.lastIndex = 0;
|
|
430
|
+
while ((m = negatedGrepRe.exec(seg)) !== null) {
|
|
431
|
+
if (!optsHaveInvert(m[1]))
|
|
432
|
+
harvest(m);
|
|
433
|
+
}
|
|
434
|
+
// `grep -q 'LIT' f; test $? -ne 0`: the same gate with the status tested on the next command.
|
|
435
|
+
if (statusForm) {
|
|
436
|
+
anyGrepRe.lastIndex = 0;
|
|
437
|
+
while ((m = anyGrepRe.exec(seg)) !== null) {
|
|
438
|
+
if (!optsHaveInvert(m[1]))
|
|
439
|
+
harvest(m);
|
|
440
|
+
}
|
|
412
441
|
}
|
|
442
|
+
if (quotedLits.length === 0 && bareLits.length === 0)
|
|
443
|
+
continue;
|
|
413
444
|
for (const quoted of quotedLits) {
|
|
414
445
|
if (!quoted || allow.has(quoted) || seenErr.has(quoted))
|
|
415
446
|
continue;
|
|
416
447
|
if (scannableActionText.includes(quoted)) {
|
|
417
448
|
seenErr.add(quoted);
|
|
418
|
-
errors.push(`Plan body contains forbidden literal "${quoted}" in an <action> block, but an acceptance criterion negative-greps for it (grep -c ... == 0). Rephrase the literal by concept, remove it from the plan body, or add <!-- planner-discipline-allow: ${quoted} --> if it must legitimately appear.`);
|
|
449
|
+
errors.push(`Plan body contains forbidden literal "${quoted}" in an <action> block, but an acceptance criterion negative-greps for it (grep -c ... == 0, or a negated gate such as ! grep -q ...). Rephrase the literal by concept, remove it from the plan body, or add <!-- planner-discipline-allow: ${quoted} --> if it must legitimately appear.`);
|
|
419
450
|
}
|
|
420
451
|
}
|
|
421
452
|
if (quotedLits.length === 0) {
|
|
@@ -854,8 +885,15 @@ function scanQuantitativeCriteria(content) {
|
|
|
854
885
|
// Not a markdown table: the negated-pipe class matches a SHELL pipeline
|
|
855
886
|
// stage boundary (git before the next `|`), the same shape the
|
|
856
887
|
// #429/#968 scanners use; there is no table row to parse.
|
|
888
|
+
// #4774: the lookahead keeps `||` — a logical OR, the construct that
|
|
889
|
+
// HANDLES the failure (e.g. the `git cat-file -e <sha> || echo missing`
|
|
890
|
+
// ghost-control idiom, reachable here when prose apostrophes leave the
|
|
891
|
+
// segment's quote state unclosed) — quiet, while `|` and `|&` (a real
|
|
892
|
+
// pipeline stage, stderr-merged or not) still warn. Accepted trade-off:
|
|
893
|
+
// a QUOTED `||` literal ahead of a real pipe (``git grep 'a||b' f | wc -l``)
|
|
894
|
+
// also goes quiet — the lookahead cannot reach past the doubled pipe.
|
|
857
895
|
// allow-adhoc-markdown: shell pipeline stage boundary, not a table cell (#4024)
|
|
858
|
-
if (/\bgit\s+[a-z][^\n|]
|
|
896
|
+
if (/\bgit\s+[a-z][^\n|]*\|(?!\|)/.test(seg)) {
|
|
859
897
|
record(warnings, 'R4', '[plan-criteria R4] A fallible `git` in a non-final pipeline stage is swallowed — the pipeline reports ' +
|
|
860
898
|
'the last stage\'s status, so a broken command reads as clean. Capture the status first.', seenWarn);
|
|
861
899
|
}
|
|
@@ -926,6 +964,13 @@ function extractPlanTaskInfos(content) {
|
|
|
926
964
|
const nameArr = (0, markdown_sectionizer_cjs_1.extractTaggedBlocks)(body, 'name');
|
|
927
965
|
const hasName = nameArr.length > 0;
|
|
928
966
|
const name = hasName ? nameArr[0].trim() : '';
|
|
967
|
+
// `(?:^|\s)` (not `\b`) so a hyphenated attribute ending in `auto_select`
|
|
968
|
+
// can never be mistaken for the real attribute — the same defensive
|
|
969
|
+
// anchor as extractOptionIds' `id` match below (#4095).
|
|
970
|
+
const autoSelectMatch = attrs.match(/(?:^|\s)auto_select\s*=\s*["']([^"']*)["']/);
|
|
971
|
+
const autoSelect = autoSelectMatch ? autoSelectMatch[1] : null;
|
|
972
|
+
const optionsArr = (0, markdown_sectionizer_cjs_1.extractTaggedBlocks)(body, 'options');
|
|
973
|
+
const optionIds = optionsArr.length > 0 ? extractOptionIds(optionsArr[0]) : [];
|
|
929
974
|
infos.push({
|
|
930
975
|
name,
|
|
931
976
|
type,
|
|
@@ -943,6 +988,8 @@ function extractPlanTaskInfos(content) {
|
|
|
943
988
|
hasHowToVerify: /<how-to-verify[\s>]/.test(body),
|
|
944
989
|
hasDecision: /<decision[\s>]/.test(body),
|
|
945
990
|
hasOptions: /<options[\s>]/.test(body),
|
|
991
|
+
autoSelect,
|
|
992
|
+
optionIds,
|
|
946
993
|
hasInstructions: /<instructions[\s>]/.test(body),
|
|
947
994
|
hasVerification: /<verification[\s>]/.test(body),
|
|
948
995
|
hasResumeSignal: /<resume-signal[\s>]/.test(body),
|
|
@@ -954,6 +1001,35 @@ function extractPlanTaskInfos(content) {
|
|
|
954
1001
|
}
|
|
955
1002
|
return infos;
|
|
956
1003
|
}
|
|
1004
|
+
/**
|
|
1005
|
+
* Extract the `id` attribute of every `<option id="…">` opening tag found in
|
|
1006
|
+
* `optionsBody` (the inner text of one `<options>…</options>` block), in
|
|
1007
|
+
* document order. Bounded attribute scan (`[^>]{0,500}`), mirroring the same
|
|
1008
|
+
* ReDoS-safe idiom `extractPlanTaskInfos` uses for the `<task type="…">`
|
|
1009
|
+
* attribute string — this file's established pattern for reading an
|
|
1010
|
+
* attribute value without a general XML parser (#4095).
|
|
1011
|
+
*/
|
|
1012
|
+
function extractOptionIds(optionsBody) {
|
|
1013
|
+
const ids = [];
|
|
1014
|
+
if (typeof optionsBody !== 'string' || optionsBody.length === 0)
|
|
1015
|
+
return ids;
|
|
1016
|
+
const OPTION_OPEN_RE = /<option(\s[^>]{0,500})?>/g;
|
|
1017
|
+
let match;
|
|
1018
|
+
while ((match = OPTION_OPEN_RE.exec(optionsBody)) !== null) {
|
|
1019
|
+
const attrs = match[1] ?? '';
|
|
1020
|
+
// `(?:^|\s)` (not `\b`) so a decoy attribute like `data-id="…"` inside
|
|
1021
|
+
// the same opening tag cannot be mistaken for the real `id` — `\b`
|
|
1022
|
+
// matches at the `-`→`i` boundary too, which `.match()`'s
|
|
1023
|
+
// first-hit-wins semantics would then silently prefer (#4095).
|
|
1024
|
+
const idMatch = attrs.match(/(?:^|\s)id\s*=\s*["']([^"']{1,200})["']/);
|
|
1025
|
+
if (idMatch)
|
|
1026
|
+
ids.push(idMatch[1]);
|
|
1027
|
+
if (match.index === OPTION_OPEN_RE.lastIndex) {
|
|
1028
|
+
OPTION_OPEN_RE.lastIndex++;
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
return ids;
|
|
1032
|
+
}
|
|
957
1033
|
function isCheckpointType(type) {
|
|
958
1034
|
return type.startsWith('checkpoint:');
|
|
959
1035
|
}
|
|
@@ -996,6 +1072,15 @@ function validatePlanTaskStructure(task) {
|
|
|
996
1072
|
errors.push(`Task '${taskName}' missing <decision>`);
|
|
997
1073
|
if (!task.hasOptions)
|
|
998
1074
|
errors.push(`Task '${taskName}' missing <options>`);
|
|
1075
|
+
if (task.autoSelect !== null) {
|
|
1076
|
+
if (task.autoSelect.length === 0) {
|
|
1077
|
+
errors.push(`Task '${taskName}' auto_select is empty — name an <option id="…">`);
|
|
1078
|
+
}
|
|
1079
|
+
else if (task.hasOptions && !task.optionIds.includes(task.autoSelect)) {
|
|
1080
|
+
errors.push(`Task '${taskName}' auto_select="${task.autoSelect}" does not match any `
|
|
1081
|
+
+ `<option id="…"> (available: ${task.optionIds.join(', ') || 'none'})`);
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
999
1084
|
break;
|
|
1000
1085
|
case 'checkpoint:human-action':
|
|
1001
1086
|
if (!task.hasAction)
|
|
@@ -1023,6 +1108,45 @@ function validatePlanTaskStructure(task) {
|
|
|
1023
1108
|
}
|
|
1024
1109
|
return { errors, warnings };
|
|
1025
1110
|
}
|
|
1111
|
+
// ─── Gate verb exit status (#5170, ADR-5057 §4) ──────────────────────────────────────────────────
|
|
1112
|
+
//
|
|
1113
|
+
// A verb that returns a verdict is evaluated by callers that branch on the exit status: exit 0 and
|
|
1114
|
+
// exit 1 are verdicts (the JSON on stdout is authoritative), any other status means the verb could
|
|
1115
|
+
// not look. Each verb builds a real GateVerdict and declares its exit from `gateExitOutcome` — no
|
|
1116
|
+
// verb picks its own code.
|
|
1117
|
+
/**
|
|
1118
|
+
* A verb's target file as evidence. An absent file is `none`; an EMPTY file is `found ''` (a plan
|
|
1119
|
+
* that exists but is empty is not "File not found"); any other failure is `unreadable`.
|
|
1120
|
+
*/
|
|
1121
|
+
function readVerbFile(fullPath) {
|
|
1122
|
+
try {
|
|
1123
|
+
const content = (0, shell_command_projection_cjs_1.platformReadSync)(fullPath);
|
|
1124
|
+
return content === null ? (0, gate_evidence_cjs_1.evidenceNone)() : (0, gate_evidence_cjs_1.evidenceFound)(content);
|
|
1125
|
+
}
|
|
1126
|
+
catch (err) {
|
|
1127
|
+
return (0, gate_evidence_cjs_1.evidenceFromError)(err, fullPath);
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
1130
|
+
/** Print a verb's verdict payload, then declare its exit. Declared after output(), which rewrites the pending-outcome cell. */
|
|
1131
|
+
function emitVerbVerdict(verdict, mode, raw, rawLabel) {
|
|
1132
|
+
output(verdict.payload, raw, rawLabel);
|
|
1133
|
+
(0, gate_exit_cjs_1.declareGateExit)(verdict, mode);
|
|
1134
|
+
}
|
|
1135
|
+
/** A verb whose result is valid/invalid: invalid is a negative verdict (exit 1 in status mode). */
|
|
1136
|
+
function validityVerdict(valid, payload) {
|
|
1137
|
+
return (0, gate_verdict_cjs_1.gateVerdict)(valid ? 'pass' : 'block', !valid, payload);
|
|
1138
|
+
}
|
|
1139
|
+
/**
|
|
1140
|
+
* The "could not evaluate" verdict for a verb's target file that is not `found` (outcome `unreadable`,
|
|
1141
|
+
* exit UNAVAILABLE — an absent file and an unreadable one alike). The caller emits it through
|
|
1142
|
+
* `emitVerbVerdict` on its own return path, so every path out of a verb visibly declares its exit.
|
|
1143
|
+
*/
|
|
1144
|
+
function unreadableFileVerdict(evidence, displayPath) {
|
|
1145
|
+
const payload = evidence.kind === 'none'
|
|
1146
|
+
? { error: 'File not found', path: displayPath }
|
|
1147
|
+
: { error: 'File unreadable', path: displayPath, read_error: evidence.reason };
|
|
1148
|
+
return (0, gate_verdict_cjs_1.gateUnreadable)(false, payload);
|
|
1149
|
+
}
|
|
1026
1150
|
function cmdVerifyPlanStructure(cwd, filePath, raw) {
|
|
1027
1151
|
if (!filePath) {
|
|
1028
1152
|
error('file path required');
|
|
@@ -1031,17 +1155,18 @@ function cmdVerifyPlanStructure(cwd, filePath, raw) {
|
|
|
1031
1155
|
error('file path contains null bytes');
|
|
1032
1156
|
}
|
|
1033
1157
|
const fullPath = node_path_1.default.isAbsolute(filePath) ? filePath : node_path_1.default.join(cwd, filePath);
|
|
1034
|
-
const
|
|
1035
|
-
if (
|
|
1036
|
-
|
|
1158
|
+
const read = readVerbFile(fullPath);
|
|
1159
|
+
if (read.kind !== 'found') {
|
|
1160
|
+
emitVerbVerdict(unreadableFileVerdict(read, filePath), 'status', raw);
|
|
1037
1161
|
return;
|
|
1038
1162
|
}
|
|
1163
|
+
const content = read.value;
|
|
1039
1164
|
// #2701: fail loud on NUL/binary corruption before structure checks. A
|
|
1040
1165
|
// structurally intact-but-NUL-corrupted plan otherwise passes as valid and is
|
|
1041
1166
|
// silently skipped by recursive/binary-skipping searchers downstream.
|
|
1042
1167
|
const encErr = (0, validate_cjs_1.textEncodingError)(content, filePath);
|
|
1043
1168
|
if (encErr) {
|
|
1044
|
-
|
|
1169
|
+
emitVerbVerdict(validityVerdict(false, { valid: false, errors: [encErr] }), 'status', raw);
|
|
1045
1170
|
return;
|
|
1046
1171
|
}
|
|
1047
1172
|
const fm = extractFrontmatter(content, fullPath);
|
|
@@ -1109,14 +1234,14 @@ function cmdVerifyPlanStructure(cwd, filePath, raw) {
|
|
|
1109
1234
|
const quantScan = scanQuantitativeCriteria(content);
|
|
1110
1235
|
errors.push(...quantScan.errors);
|
|
1111
1236
|
warnings.push(...quantScan.warnings);
|
|
1112
|
-
|
|
1237
|
+
emitVerbVerdict(validityVerdict(errors.length === 0, {
|
|
1113
1238
|
valid: errors.length === 0,
|
|
1114
1239
|
errors,
|
|
1115
1240
|
warnings,
|
|
1116
1241
|
task_count: tasks.length,
|
|
1117
1242
|
tasks,
|
|
1118
1243
|
frontmatter_fields: Object.keys(fm),
|
|
1119
|
-
}, raw, errors.length === 0 ? 'valid' : 'invalid');
|
|
1244
|
+
}), 'status', raw, errors.length === 0 ? 'valid' : 'invalid');
|
|
1120
1245
|
}
|
|
1121
1246
|
function cmdVerifyPhaseCompleteness(cwd, phase, raw) {
|
|
1122
1247
|
if (!phase) {
|
|
@@ -1124,7 +1249,8 @@ function cmdVerifyPhaseCompleteness(cwd, phase, raw) {
|
|
|
1124
1249
|
}
|
|
1125
1250
|
const phaseInfoRaw = findPhaseInternal(cwd, phase);
|
|
1126
1251
|
if (!phaseInfoRaw || !phaseInfoRaw['found']) {
|
|
1127
|
-
|
|
1252
|
+
// An unresolvable phase is "could not look" (#5170): there is no phase to evaluate.
|
|
1253
|
+
emitVerbVerdict((0, gate_verdict_cjs_1.gateUnreadable)(false, { error: 'Phase not found', phase }), 'status', raw);
|
|
1128
1254
|
return;
|
|
1129
1255
|
}
|
|
1130
1256
|
const phaseInfo = phaseInfoRaw;
|
|
@@ -1140,12 +1266,14 @@ function cmdVerifyPhaseCompleteness(cwd, phase, raw) {
|
|
|
1140
1266
|
// recognized naming forms — producing false "Plans without summaries" /
|
|
1141
1267
|
// "Summaries without plans" for names it could not recognize as paired
|
|
1142
1268
|
// (the same failure class #1988/#2648 fixed for the owner's own callers).
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1269
|
+
// Only SCOPE.COMPLETE is a real answer: an existing nested plans/ that could not be read (TRUNCATED)
|
|
1270
|
+
// would hand the check a short plan set and report the unseen plans as nothing (#5170).
|
|
1271
|
+
const scan = (0, gate_evidence_cjs_1.readPlanScanEvidence)(phaseDir);
|
|
1272
|
+
if (scan.kind === 'unreadable') {
|
|
1273
|
+
emitVerbVerdict((0, gate_verdict_cjs_1.gateUnreadable)(false, { error: 'Cannot read phase directory' }), 'status', raw);
|
|
1146
1274
|
return;
|
|
1147
1275
|
}
|
|
1148
|
-
const { planFiles, summaryFiles } = scan;
|
|
1276
|
+
const { planFiles, summaryFiles } = scan.value;
|
|
1149
1277
|
const incompletePlans = findUnsummarizedPlans(planFiles, summaryFiles);
|
|
1150
1278
|
if (incompletePlans.length > 0) {
|
|
1151
1279
|
errors.push(`Plans without summaries: ${incompletePlans.join(', ')}`);
|
|
@@ -1154,7 +1282,7 @@ function cmdVerifyPhaseCompleteness(cwd, phase, raw) {
|
|
|
1154
1282
|
if (orphanSummaries.length > 0) {
|
|
1155
1283
|
warnings.push(`Summaries without plans: ${orphanSummaries.join(', ')}`);
|
|
1156
1284
|
}
|
|
1157
|
-
|
|
1285
|
+
emitVerbVerdict(validityVerdict(errors.length === 0, {
|
|
1158
1286
|
complete: errors.length === 0,
|
|
1159
1287
|
phase: phaseInfo['phase_number'],
|
|
1160
1288
|
plan_count: planFiles.length,
|
|
@@ -1163,59 +1291,78 @@ function cmdVerifyPhaseCompleteness(cwd, phase, raw) {
|
|
|
1163
1291
|
orphan_summaries: orphanSummaries,
|
|
1164
1292
|
errors,
|
|
1165
1293
|
warnings,
|
|
1166
|
-
}, raw, errors.length === 0 ? 'complete' : 'incomplete');
|
|
1294
|
+
}), 'status', raw, errors.length === 0 ? 'complete' : 'incomplete');
|
|
1295
|
+
}
|
|
1296
|
+
// #4678: citations may carry a trailing line suffix (":42", ":1-20") that
|
|
1297
|
+
// describes a location inside the file, not part of the path itself.
|
|
1298
|
+
function stripLineSuffix(ref) {
|
|
1299
|
+
return ref.replace(/:\d+(?:-\d+)?$/, '');
|
|
1167
1300
|
}
|
|
1168
1301
|
function cmdVerifyReferences(cwd, filePath, raw) {
|
|
1169
1302
|
if (!filePath) {
|
|
1170
1303
|
error('file path required');
|
|
1171
1304
|
}
|
|
1172
1305
|
const fullPath = node_path_1.default.isAbsolute(filePath) ? filePath : node_path_1.default.join(cwd, filePath);
|
|
1173
|
-
const
|
|
1174
|
-
if (
|
|
1175
|
-
|
|
1306
|
+
const read = readVerbFile(fullPath);
|
|
1307
|
+
if (read.kind !== 'found') {
|
|
1308
|
+
emitVerbVerdict(unreadableFileVerdict(read, filePath), 'status', raw);
|
|
1176
1309
|
return;
|
|
1177
1310
|
}
|
|
1311
|
+
const content = read.value;
|
|
1178
1312
|
const found = [];
|
|
1179
1313
|
const missing = [];
|
|
1314
|
+
// A reference whose path could not be examined (EACCES on a parent ...) is neither found nor missing.
|
|
1315
|
+
const unexaminable = [];
|
|
1316
|
+
const probeRef = (cleanRef, resolved) => {
|
|
1317
|
+
const probe = (0, gate_evidence_cjs_1.statEvidence)(resolved);
|
|
1318
|
+
if (probe.kind === 'found')
|
|
1319
|
+
found.push(cleanRef);
|
|
1320
|
+
else if (probe.kind === 'none')
|
|
1321
|
+
missing.push(cleanRef);
|
|
1322
|
+
else
|
|
1323
|
+
unexaminable.push({ ref: cleanRef, reason: probe.reason });
|
|
1324
|
+
};
|
|
1180
1325
|
const atRefs = content.match(/@([^\s\n,)]+\/[^\s\n,)]+)/g) || [];
|
|
1181
1326
|
for (const ref of atRefs) {
|
|
1182
1327
|
const cleanRef = ref.slice(1);
|
|
1183
|
-
const
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
}
|
|
1189
|
-
else {
|
|
1190
|
-
missing.push(cleanRef);
|
|
1191
|
-
}
|
|
1328
|
+
const fsRef = stripLineSuffix(cleanRef);
|
|
1329
|
+
const resolved = fsRef.startsWith('~/')
|
|
1330
|
+
? node_path_1.default.join(process.env['HOME'] || '', fsRef.slice(2))
|
|
1331
|
+
: node_path_1.default.join(cwd, fsRef);
|
|
1332
|
+
probeRef(cleanRef, resolved);
|
|
1192
1333
|
}
|
|
1193
|
-
const backtickRefs = content.match(/`([^`]+\/[^`]+\.[a-zA-Z]{1,10})`/g) || [];
|
|
1334
|
+
const backtickRefs = content.match(/`([^`]+\/[^`]+\.[a-zA-Z]{1,10}(?::\d+(?:-\d+)?)?)`/g) || [];
|
|
1194
1335
|
for (const ref of backtickRefs) {
|
|
1195
1336
|
const cleanRef = ref.slice(1, -1);
|
|
1196
1337
|
if (cleanRef.startsWith('http') || cleanRef.includes('${') || cleanRef.includes('{{'))
|
|
1197
1338
|
continue;
|
|
1198
|
-
if (found.includes(cleanRef) || missing.includes(cleanRef))
|
|
1339
|
+
if (found.includes(cleanRef) || missing.includes(cleanRef) || unexaminable.some((u) => u.ref === cleanRef))
|
|
1199
1340
|
continue;
|
|
1200
|
-
|
|
1201
|
-
if (node_fs_1.default.existsSync(resolved)) {
|
|
1202
|
-
found.push(cleanRef);
|
|
1203
|
-
}
|
|
1204
|
-
else {
|
|
1205
|
-
missing.push(cleanRef);
|
|
1206
|
-
}
|
|
1341
|
+
probeRef(cleanRef, node_path_1.default.join(cwd, stripLineSuffix(cleanRef)));
|
|
1207
1342
|
}
|
|
1208
|
-
|
|
1343
|
+
// Unexaminable references only matter when they could flip the verdict: with a reference already
|
|
1344
|
+
// missing the verdict is negative whatever they are; with none missing, "valid" cannot be claimed.
|
|
1345
|
+
if (missing.length === 0 && unexaminable.length > 0) {
|
|
1346
|
+
emitVerbVerdict((0, gate_verdict_cjs_1.gateUnreadable)(false, { error: 'Could not examine references', path: filePath, unreadable: unexaminable }), 'status', raw);
|
|
1347
|
+
return;
|
|
1348
|
+
}
|
|
1349
|
+
emitVerbVerdict(validityVerdict(missing.length === 0, {
|
|
1209
1350
|
valid: missing.length === 0,
|
|
1210
1351
|
found: found.length,
|
|
1211
1352
|
missing,
|
|
1212
1353
|
total: found.length + missing.length,
|
|
1213
|
-
}, raw, missing.length === 0 ? 'valid' : 'invalid');
|
|
1354
|
+
}), 'status', raw, missing.length === 0 ? 'valid' : 'invalid');
|
|
1214
1355
|
}
|
|
1215
1356
|
function cmdVerifyCommits(cwd, hashes, raw) {
|
|
1216
1357
|
if (!hashes || hashes.length === 0) {
|
|
1217
1358
|
error('At least one commit hash required');
|
|
1218
1359
|
}
|
|
1360
|
+
// A directory that is not a git work tree cannot answer "is this a commit": could not look, not "invalid".
|
|
1361
|
+
const inWorkTree = (0, shell_command_projection_cjs_1.execGit)(['rev-parse', '--is-inside-work-tree'], { cwd });
|
|
1362
|
+
if (inWorkTree.exitCode !== 0) {
|
|
1363
|
+
emitVerbVerdict((0, gate_verdict_cjs_1.gateUnreadable)(false, { error: 'Not a git repository' }), 'status', raw);
|
|
1364
|
+
return;
|
|
1365
|
+
}
|
|
1219
1366
|
const valid = [];
|
|
1220
1367
|
const invalid = [];
|
|
1221
1368
|
for (const hash of hashes) {
|
|
@@ -1227,26 +1374,30 @@ function cmdVerifyCommits(cwd, hashes, raw) {
|
|
|
1227
1374
|
invalid.push(hash);
|
|
1228
1375
|
}
|
|
1229
1376
|
}
|
|
1230
|
-
|
|
1377
|
+
emitVerbVerdict(validityVerdict(invalid.length === 0, {
|
|
1231
1378
|
all_valid: invalid.length === 0,
|
|
1232
1379
|
valid,
|
|
1233
1380
|
invalid,
|
|
1234
1381
|
total: hashes.length,
|
|
1235
|
-
}, raw, invalid.length === 0 ? 'valid' : 'invalid');
|
|
1382
|
+
}), 'status', raw, invalid.length === 0 ? 'valid' : 'invalid');
|
|
1236
1383
|
}
|
|
1237
1384
|
function cmdVerifyArtifacts(cwd, planFilePath, raw) {
|
|
1238
1385
|
if (!planFilePath) {
|
|
1239
1386
|
error('plan file path required');
|
|
1240
1387
|
}
|
|
1241
1388
|
const fullPath = node_path_1.default.isAbsolute(planFilePath) ? planFilePath : node_path_1.default.join(cwd, planFilePath);
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1389
|
+
// #5170: no plan to read is "could not look" (UNAVAILABLE), never a pass. A plan that exists but is
|
|
1390
|
+
// empty WAS read: it has no artifacts block, which is a genuinely empty scope (below).
|
|
1391
|
+
const read = readVerbFile(fullPath);
|
|
1392
|
+
if (read.kind !== 'found') {
|
|
1393
|
+
emitVerbVerdict(unreadableFileVerdict(read, planFilePath), 'status', raw);
|
|
1245
1394
|
return;
|
|
1246
1395
|
}
|
|
1396
|
+
const content = read.value;
|
|
1247
1397
|
const artifacts = parseMustHavesBlock(content, 'artifacts');
|
|
1248
1398
|
if (artifacts.length === 0) {
|
|
1249
|
-
|
|
1399
|
+
// The plan was read and declares nothing to verify: NO_INPUT (ran, zero units in scope, genuinely).
|
|
1400
|
+
emitVerbVerdict((0, gate_verdict_cjs_1.gateVerdict)('empty', false, { error: 'No must_haves.artifacts found in frontmatter', path: planFilePath }), 'status', raw);
|
|
1250
1401
|
return;
|
|
1251
1402
|
}
|
|
1252
1403
|
const results = [];
|
|
@@ -1257,27 +1408,66 @@ function cmdVerifyArtifacts(cwd, planFilePath, raw) {
|
|
|
1257
1408
|
if (!artPath)
|
|
1258
1409
|
continue;
|
|
1259
1410
|
const artFullPath = node_path_1.default.join(cwd, artPath);
|
|
1260
|
-
|
|
1411
|
+
// Typed evidence, not `fs.existsSync`: a path that cannot be examined (EACCES on a parent) is an
|
|
1412
|
+
// unreadable entry, not a missing one.
|
|
1413
|
+
const artStat = (0, gate_evidence_cjs_1.statEvidence)(artFullPath);
|
|
1414
|
+
const exists = artStat.kind === 'found';
|
|
1261
1415
|
const check = { path: artPath, exists, issues: [], passed: false };
|
|
1416
|
+
// #4685: one artifact's I/O problem is that artifact's failure, never the
|
|
1417
|
+
// whole plan's. `safeReadFile` rethrows every errno except ENOENT, so before
|
|
1418
|
+
// this an unreadable entry — a directory most commonly, but equally an EACCES
|
|
1419
|
+
// file or a dangling mount — threw out of the loop and the command reported
|
|
1420
|
+
// NOTHING: not the offending entry, and not the plan's other, perfectly good
|
|
1421
|
+
// artifacts either. A check that disappears is worse than a check that fails,
|
|
1422
|
+
// because a failure is visible.
|
|
1423
|
+
//
|
|
1424
|
+
// #5170: the stat and the read are typed evidence (`statEvidence` / `readVerbFile`), so no `try`
|
|
1425
|
+
// is needed: an artifact that could not be examined or read is that artifact's failure, recorded
|
|
1426
|
+
// with its errno (an operator seeing EACCES acts differently from one seeing EIO) and left
|
|
1427
|
+
// `passed: false` — never a pass, so the verdict is negative (exit 1).
|
|
1262
1428
|
if (exists) {
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
if (
|
|
1269
|
-
check['issues'].push(
|
|
1429
|
+
// A directory is reported as its own kind of failure, distinct from
|
|
1430
|
+
// `File not found`: the path resolved, it simply is not the thing an
|
|
1431
|
+
// artifact entry can be checked against. Verifying a directory (matching
|
|
1432
|
+
// `contains:`/`min_lines:`/`exports:` across the files inside it) is a
|
|
1433
|
+
// feature decision, deliberately not made here.
|
|
1434
|
+
if (artStat.kind === 'found' && artStat.value.isDirectory()) {
|
|
1435
|
+
check['issues'].push('Not a file: path is a directory');
|
|
1270
1436
|
}
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1437
|
+
else {
|
|
1438
|
+
// `safeReadFile` returns null on ENOENT, and `|| ''` would turn that
|
|
1439
|
+
// into an empty file — which an entry carrying only `path`/`provides`
|
|
1440
|
+
// would then PASS, having checked nothing. `statSync` just succeeded, so
|
|
1441
|
+
// `none` here means the artifact went away mid-check. Report that rather
|
|
1442
|
+
// than inheriting a pass from it.
|
|
1443
|
+
const artRead = readVerbFile(artFullPath);
|
|
1444
|
+
if (artRead.kind !== 'found') {
|
|
1445
|
+
check['issues'].push(artRead.kind === 'none' ? 'Unreadable: disappeared during check' : `Unreadable: ${artRead.reason}`);
|
|
1446
|
+
results.push(check);
|
|
1447
|
+
continue;
|
|
1278
1448
|
}
|
|
1449
|
+
const fileContent = artRead.value;
|
|
1450
|
+
const lineCount = fileContent.split('\n').length;
|
|
1451
|
+
if (artifact['min_lines'] && lineCount < artifact['min_lines']) {
|
|
1452
|
+
check['issues'].push(`Only ${lineCount} lines, need ${artifact['min_lines']}`);
|
|
1453
|
+
}
|
|
1454
|
+
if (artifact['contains'] && !fileContent.includes(artifact['contains'])) {
|
|
1455
|
+
check['issues'].push(`Missing pattern: ${artifact['contains']}`);
|
|
1456
|
+
}
|
|
1457
|
+
if (artifact['exports']) {
|
|
1458
|
+
const exports = Array.isArray(artifact['exports'])
|
|
1459
|
+
? artifact['exports']
|
|
1460
|
+
: [artifact['exports']];
|
|
1461
|
+
for (const exp of exports) {
|
|
1462
|
+
if (!fileContent.includes(exp))
|
|
1463
|
+
check['issues'].push(`Missing export: ${exp}`);
|
|
1464
|
+
}
|
|
1465
|
+
}
|
|
1466
|
+
check['passed'] = check['issues'].length === 0;
|
|
1279
1467
|
}
|
|
1280
|
-
|
|
1468
|
+
}
|
|
1469
|
+
else if (artStat.kind === 'unreadable') {
|
|
1470
|
+
check['issues'].push(`Unreadable: ${artStat.reason}`);
|
|
1281
1471
|
}
|
|
1282
1472
|
else {
|
|
1283
1473
|
check['issues'].push('File not found');
|
|
@@ -1292,12 +1482,13 @@ function cmdVerifyArtifacts(cwd, planFilePath, raw) {
|
|
|
1292
1482
|
// no-vacuous-pass rule at src/uat-predicate.cts. The fully-empty block is still
|
|
1293
1483
|
// caught earlier by the `artifacts.length === 0` guard and returns its error.
|
|
1294
1484
|
const allPassed = results.length > 0 && passed === results.length;
|
|
1295
|
-
|
|
1485
|
+
// #5170: the exit status follows the verdict — a negative verdict is exit 1 (JSON unchanged).
|
|
1486
|
+
emitVerbVerdict(validityVerdict(allPassed, {
|
|
1296
1487
|
all_passed: allPassed,
|
|
1297
1488
|
passed,
|
|
1298
1489
|
total: results.length,
|
|
1299
1490
|
artifacts: results,
|
|
1300
|
-
}, raw, allPassed ? 'valid' : 'invalid');
|
|
1491
|
+
}), 'status', raw, allPassed ? 'valid' : 'invalid');
|
|
1301
1492
|
}
|
|
1302
1493
|
/**
|
|
1303
1494
|
* Returns a Set of file paths (relative to cwd) that are promised by plans in
|
|
@@ -1308,12 +1499,23 @@ function cmdVerifyArtifacts(cwd, planFilePath, raw) {
|
|
|
1308
1499
|
*/
|
|
1309
1500
|
function collectPromisedFilesAtOrAfterWave(phaseDir, minWave) {
|
|
1310
1501
|
const promised = new Set();
|
|
1311
|
-
|
|
1312
|
-
|
|
1502
|
+
// #5170: a plan set the scan did not fully see (an existing nested plans/ that could not be read), or
|
|
1503
|
+
// a plan that cannot be read, is "could not look" — it may be the plan that promises the file — and
|
|
1504
|
+
// is reported to the caller, never treated as "no plan promises it".
|
|
1505
|
+
let unreadable = null;
|
|
1506
|
+
const planSet = (0, gate_evidence_cjs_1.readPlanSetEvidence)(phaseDir);
|
|
1507
|
+
if (planSet.kind === 'unreadable')
|
|
1508
|
+
return { files: promised, unreadable: planSet.reason };
|
|
1509
|
+
for (const planFile of planSet.value) {
|
|
1313
1510
|
const planFullPath = node_path_1.default.join(phaseDir, planFile);
|
|
1314
|
-
const
|
|
1315
|
-
if (
|
|
1511
|
+
const planRead = readVerbFile(planFullPath);
|
|
1512
|
+
if (planRead.kind === 'unreadable') {
|
|
1513
|
+
unreadable = unreadable ?? planRead.reason;
|
|
1514
|
+
continue;
|
|
1515
|
+
}
|
|
1516
|
+
if (planRead.kind === 'none' || !planRead.value)
|
|
1316
1517
|
continue;
|
|
1518
|
+
const planContent = planRead.value;
|
|
1317
1519
|
const fm = extractFrontmatter(planContent, planFullPath);
|
|
1318
1520
|
const waveRaw = fm['wave'];
|
|
1319
1521
|
const wave = typeof waveRaw === 'string' ? parseInt(waveRaw, 10) : (typeof waveRaw === 'number' ? waveRaw : NaN);
|
|
@@ -1330,21 +1532,23 @@ function collectPromisedFilesAtOrAfterWave(phaseDir, minWave) {
|
|
|
1330
1532
|
promised.add(f.trim());
|
|
1331
1533
|
}
|
|
1332
1534
|
}
|
|
1333
|
-
return promised;
|
|
1535
|
+
return { files: promised, unreadable };
|
|
1334
1536
|
}
|
|
1335
1537
|
function cmdVerifyKeyLinks(cwd, planFilePath, raw) {
|
|
1336
1538
|
if (!planFilePath) {
|
|
1337
1539
|
error('plan file path required');
|
|
1338
1540
|
}
|
|
1339
1541
|
const fullPath = node_path_1.default.isAbsolute(planFilePath) ? planFilePath : node_path_1.default.join(cwd, planFilePath);
|
|
1340
|
-
const
|
|
1341
|
-
if (
|
|
1342
|
-
|
|
1542
|
+
const read = readVerbFile(fullPath);
|
|
1543
|
+
if (read.kind !== 'found') {
|
|
1544
|
+
emitVerbVerdict(unreadableFileVerdict(read, planFilePath), 'status', raw);
|
|
1343
1545
|
return;
|
|
1344
1546
|
}
|
|
1547
|
+
const content = read.value;
|
|
1345
1548
|
const keyLinks = parseMustHavesBlock(content, 'key_links');
|
|
1346
1549
|
if (keyLinks.length === 0) {
|
|
1347
|
-
|
|
1550
|
+
// The plan was read and declares no key links: NO_INPUT, the same genuinely-empty scope as `verify artifacts`.
|
|
1551
|
+
emitVerbVerdict((0, gate_verdict_cjs_1.gateVerdict)('empty', false, { error: 'No must_haves.key_links found in frontmatter', path: planFilePath }), 'status', raw);
|
|
1348
1552
|
return;
|
|
1349
1553
|
}
|
|
1350
1554
|
// Derive the current plan's wave number and phase directory for wave-aware
|
|
@@ -1397,28 +1601,30 @@ function cmdVerifyKeyLinks(cwd, planFilePath, raw) {
|
|
|
1397
1601
|
results.push(check);
|
|
1398
1602
|
continue;
|
|
1399
1603
|
}
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
// can trigger EISDIR/EACCES, which platformReadSync re-throws for any
|
|
1406
|
-
// non-ENOENT errno). A single bad link must not abort the whole command.
|
|
1407
|
-
const code = err?.code ?? 'unknown';
|
|
1408
|
-
check['detail'] = `Source read failed: ${code}`;
|
|
1604
|
+
// Report the errno only — never the message or path (untrusted `from:` can trigger
|
|
1605
|
+
// EISDIR/EACCES). A single bad link must not abort the whole command.
|
|
1606
|
+
const sourceRead = readVerbFile(fromContained);
|
|
1607
|
+
if (sourceRead.kind === 'unreadable') {
|
|
1608
|
+
check['detail'] = `Source read failed: ${sourceRead.reason}`;
|
|
1409
1609
|
results.push(check);
|
|
1410
1610
|
continue;
|
|
1411
1611
|
}
|
|
1612
|
+
sourceContent = sourceRead.kind === 'found' ? sourceRead.value : null;
|
|
1412
1613
|
}
|
|
1413
1614
|
if (!sourceContent) {
|
|
1414
1615
|
// Check if the missing file is promised by a plan at the same or later wave.
|
|
1415
1616
|
const promised = getPromisedFiles();
|
|
1416
|
-
const isPromised = fromPath.trim() !== '' && promised.has(fromPath.trim());
|
|
1617
|
+
const isPromised = fromPath.trim() !== '' && promised.files.has(fromPath.trim());
|
|
1417
1618
|
if (isPromised) {
|
|
1418
1619
|
check['pending'] = true;
|
|
1419
1620
|
check['detail'] = 'Source file not yet created — declared in files_modified of a same-or-later-wave plan';
|
|
1420
1621
|
pendingCount++;
|
|
1421
1622
|
}
|
|
1623
|
+
else if (promised.unreadable !== null) {
|
|
1624
|
+
// The plans that could declare this file could not all be read: it is not known to be missing,
|
|
1625
|
+
// and the link is not verified — a failed link, never a pending or passing one.
|
|
1626
|
+
check['detail'] = `Source file not found, and the plans that could declare it could not be read: ${promised.unreadable}`;
|
|
1627
|
+
}
|
|
1422
1628
|
else {
|
|
1423
1629
|
check['detail'] = 'Source file not found (from: must be a relative file path; describe components/endpoints in via:)';
|
|
1424
1630
|
}
|
|
@@ -1453,46 +1659,47 @@ function cmdVerifyKeyLinks(cwd, planFilePath, raw) {
|
|
|
1453
1659
|
check['detail'] = `Pattern not verified (${reason})`;
|
|
1454
1660
|
}
|
|
1455
1661
|
else {
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
}
|
|
1477
|
-
else {
|
|
1478
|
-
targetContent = (0, shell_command_projection_cjs_1.platformReadSync)(toContained);
|
|
1479
|
-
}
|
|
1480
|
-
}
|
|
1481
|
-
if (targetContent && pat.test(targetContent)) {
|
|
1482
|
-
check['verified'] = true;
|
|
1483
|
-
check['detail'] = 'Pattern found in target';
|
|
1662
|
+
if (pat.test(sourceContent)) {
|
|
1663
|
+
check['verified'] = true;
|
|
1664
|
+
check['detail'] = 'Pattern found in source';
|
|
1665
|
+
}
|
|
1666
|
+
else {
|
|
1667
|
+
const toRaw = link['to'];
|
|
1668
|
+
const toPath = typeof toRaw === 'string' ? toRaw : '';
|
|
1669
|
+
let targetContent = null;
|
|
1670
|
+
let targetUnreadable = null;
|
|
1671
|
+
if (toPath !== '') {
|
|
1672
|
+
// An empty/missing `to:` is a malformed plan, not a
|
|
1673
|
+
// path-confinement violation — only a non-empty path that
|
|
1674
|
+
// actually resolves outside the project is path_rejected.
|
|
1675
|
+
const toContained = (0, security_cjs_2.tryWithinRoot)(toPath, cwd);
|
|
1676
|
+
if (toContained === null) {
|
|
1677
|
+
// Do not read a rejected `to:` — treat as no target content
|
|
1678
|
+
// and do not echo result.error, which embeds absolute host
|
|
1679
|
+
// paths.
|
|
1680
|
+
check['path_rejected'] = 'to';
|
|
1681
|
+
check['detail'] = `Pattern "${link['pattern']}" not found in source; target path rejected — resolves outside the project directory`;
|
|
1484
1682
|
}
|
|
1485
|
-
else
|
|
1486
|
-
|
|
1683
|
+
else {
|
|
1684
|
+
// Report the errno only — never the message, which for an untrusted `to:` like "../.."
|
|
1685
|
+
// (EISDIR) would embed an absolute filesystem path.
|
|
1686
|
+
const targetRead = readVerbFile(toContained);
|
|
1687
|
+
if (targetRead.kind === 'unreadable')
|
|
1688
|
+
targetUnreadable = targetRead.reason;
|
|
1689
|
+
else if (targetRead.kind === 'found')
|
|
1690
|
+
targetContent = targetRead.value;
|
|
1487
1691
|
}
|
|
1488
1692
|
}
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1693
|
+
if (targetUnreadable !== null) {
|
|
1694
|
+
check['detail'] = `Pattern check failed: ${targetUnreadable}`;
|
|
1695
|
+
}
|
|
1696
|
+
else if (targetContent && pat.test(targetContent)) {
|
|
1697
|
+
check['verified'] = true;
|
|
1698
|
+
check['detail'] = 'Pattern found in target';
|
|
1699
|
+
}
|
|
1700
|
+
else if (!check['path_rejected']) {
|
|
1701
|
+
check['detail'] = `Pattern "${link['pattern']}" not found in source or target`;
|
|
1702
|
+
}
|
|
1496
1703
|
}
|
|
1497
1704
|
}
|
|
1498
1705
|
}
|
|
@@ -1522,13 +1729,13 @@ function cmdVerifyKeyLinks(cwd, planFilePath, raw) {
|
|
|
1522
1729
|
// block still satisfies results.length > 0 and its #1202 non-hard-failing
|
|
1523
1730
|
// semantics are unchanged — the floor only rejects the zero-result case.
|
|
1524
1731
|
const allVerified = results.length > 0 && hardFailed === 0;
|
|
1525
|
-
|
|
1732
|
+
emitVerbVerdict(validityVerdict(allVerified, {
|
|
1526
1733
|
all_verified: allVerified,
|
|
1527
1734
|
verified,
|
|
1528
1735
|
pending: pendingCount,
|
|
1529
1736
|
total: results.length,
|
|
1530
1737
|
links: results,
|
|
1531
|
-
}, raw, allVerified ? 'valid' : 'invalid');
|
|
1738
|
+
}), 'status', raw, allVerified ? 'valid' : 'invalid');
|
|
1532
1739
|
}
|
|
1533
1740
|
/**
|
|
1534
1741
|
* Wrapper-level fix-text table for `cmdValidateHealth`'s migrated
|
|
@@ -1692,6 +1899,9 @@ function cmdValidateHealth(cwd, options, raw) {
|
|
|
1692
1899
|
// Diagnostic -> IssueEntry mapping contract this reproduces.
|
|
1693
1900
|
const snapshot = buildPlanningSnapshot(cwd);
|
|
1694
1901
|
const diagnostics = evaluateRules(snapshot);
|
|
1902
|
+
// #5118: an out-of-set verification report status is carried by the
|
|
1903
|
+
// snapshot (`verificationStatusError`) and reported as the W030 finding — a
|
|
1904
|
+
// diagnostics surface survives the defect it diagnoses, so this run exits 0.
|
|
1695
1905
|
const errors = [];
|
|
1696
1906
|
const warnings = [];
|
|
1697
1907
|
const info = [];
|
|
@@ -1815,192 +2025,13 @@ function cmdValidateAgents(cwd, raw) {
|
|
|
1815
2025
|
sandbox_posture: sandboxPosture,
|
|
1816
2026
|
}, raw);
|
|
1817
2027
|
}
|
|
1818
|
-
// ─── Context drift (#3348) ───────────────────────────────────────────────────
|
|
1819
|
-
/**
|
|
1820
|
-
* Resolve a phase directory under `phasesDir` from a user-supplied `phaseArg`,
|
|
1821
|
-
* via the canonical phase-directory matcher (phase-id.cjs::matchPhaseDirs) rather
|
|
1822
|
-
* than a naive substring test — a bare `.includes(phaseArg)` lets a non-existent
|
|
1823
|
-
* phase silently match a different phase whose directory name merely contains the
|
|
1824
|
-
* requested token (e.g. "1" matching "11-expansion"). Falls back to an exact
|
|
1825
|
-
* directory-name match. Returns null if neither resolves. (#1571, #2528)
|
|
1826
|
-
*/
|
|
1827
|
-
function resolvePhaseDirByToken(phasesDir, phaseArg) {
|
|
1828
|
-
const normalizedPhase = normalizePhaseName(phaseArg);
|
|
1829
|
-
const dirEntries = node_fs_1.default.readdirSync(phasesDir, { withFileTypes: true });
|
|
1830
|
-
const dirNames = dirEntries.filter((e) => e.isDirectory()).map((e) => e.name);
|
|
1831
|
-
const matched = matchPhaseDirs(dirNames, normalizedPhase).matches[0];
|
|
1832
|
-
if (matched)
|
|
1833
|
-
return node_path_1.default.join(phasesDir, matched);
|
|
1834
|
-
const contained = (0, security_cjs_2.tryWithinRoot)(phaseArg, phasesDir);
|
|
1835
|
-
if (contained !== null && node_fs_1.default.existsSync(contained))
|
|
1836
|
-
return contained;
|
|
1837
|
-
return null;
|
|
1838
|
-
}
|
|
1839
|
-
/**
|
|
1840
|
-
* Pure comparator: which of `entries` have an effective last-changed time
|
|
1841
|
-
* STRICTLY BEFORE `contextEffectiveMs` (CONTEXT.md's own effective time)? Strict
|
|
1842
|
-
* `<` is "stale" (matches findStaleVerificationSummary's own strict `>` convention
|
|
1843
|
-
* for "newer than" elsewhere in this codebase — an artifact committed in the SAME
|
|
1844
|
-
* commit/second as CONTEXT.md is in sync, not stale).
|
|
1845
|
-
*/
|
|
1846
|
-
function computeContextDrift(contextEffectiveMs, entries) {
|
|
1847
|
-
return entries.filter((e) => e.effectiveMs < contextEffectiveMs).map((e) => e.file);
|
|
1848
|
-
}
|
|
1849
|
-
function buildContextDriftMessage(staleArtifacts, phaseArg) {
|
|
1850
|
-
const parts = [`CONTEXT.md decisions are newer than: ${staleArtifacts.join(', ')}.`];
|
|
1851
|
-
if (staleArtifacts.some((f) => f.endsWith('-RESEARCH.md'))) {
|
|
1852
|
-
parts.push(`Regenerate research: /gsd:plan-phase ${phaseArg} --research.`);
|
|
1853
|
-
}
|
|
1854
|
-
if (staleArtifacts.some((f) => f.endsWith('-PATTERNS.md'))) {
|
|
1855
|
-
parts.push('Regenerate patterns: delete the PATTERNS.md file, then re-run /gsd:plan-phase.');
|
|
1856
|
-
}
|
|
1857
|
-
if (staleArtifacts.some((f) => f.endsWith('-VALIDATION.md') || (f.endsWith('-SPEC.md') && !f.endsWith('-AI-SPEC.md') && !f.endsWith('-UI-SPEC.md')))) {
|
|
1858
|
-
parts.push('Regenerate or manually reconcile VALIDATION.md / SPEC.md against the current decisions.');
|
|
1859
|
-
}
|
|
1860
|
-
parts.push('Do not hand-inject the newer decisions into a prompt as a substitute for regenerating — that carries the staleness forward.');
|
|
1861
|
-
return parts.join(' ');
|
|
1862
|
-
}
|
|
1863
|
-
function cmdVerifyContextDrift(cwd, phaseArg, raw) {
|
|
1864
|
-
if (!phaseArg) {
|
|
1865
|
-
error('Usage: verify context-drift <phase>');
|
|
1866
|
-
return;
|
|
1867
|
-
}
|
|
1868
|
-
const pDir = planningDir(cwd);
|
|
1869
|
-
const phasesDir = node_path_1.default.join(pDir, 'phases');
|
|
1870
|
-
const emitSkip = (reason, message = '') => {
|
|
1871
|
-
output({ block: false, skipped: true, reason, stale_artifacts: [], message }, raw);
|
|
1872
|
-
};
|
|
1873
|
-
if (!node_fs_1.default.existsSync(phasesDir)) {
|
|
1874
|
-
emitSkip('phase-not-found', `Phase directory not found: ${phaseArg}`);
|
|
1875
|
-
return;
|
|
1876
|
-
}
|
|
1877
|
-
// Same phase-directory resolution rule cmdVerifySchemaDrift uses (#1571, #2528):
|
|
1878
|
-
// matchPhaseDirs, never a naive substring test.
|
|
1879
|
-
const phaseDir = resolvePhaseDirByToken(phasesDir, phaseArg);
|
|
1880
|
-
if (!phaseDir) {
|
|
1881
|
-
emitSkip('phase-not-found', `Phase directory not found: ${phaseArg}`);
|
|
1882
|
-
return;
|
|
1883
|
-
}
|
|
1884
|
-
let phaseFiles;
|
|
1885
|
-
try {
|
|
1886
|
-
phaseFiles = node_fs_1.default.readdirSync(phaseDir).slice().sort();
|
|
1887
|
-
}
|
|
1888
|
-
catch {
|
|
1889
|
-
emitSkip('phase-not-found', `Phase directory not found: ${phaseArg}`);
|
|
1890
|
-
return;
|
|
1891
|
-
}
|
|
1892
|
-
const contextFile = phaseFiles.find((f) => f.endsWith('-CONTEXT.md'));
|
|
1893
|
-
if (!contextFile) {
|
|
1894
|
-
emitSkip('no-context-md');
|
|
1895
|
-
return;
|
|
1896
|
-
}
|
|
1897
|
-
const researchFile = phaseFiles.find((f) => f.endsWith('-RESEARCH.md'));
|
|
1898
|
-
const patternsFile = phaseFiles.find((f) => f.endsWith('-PATTERNS.md'));
|
|
1899
|
-
const validationFile = phaseFiles.find((f) => f.endsWith('-VALIDATION.md'));
|
|
1900
|
-
const specFile = phaseFiles.find((f) => f.endsWith('-SPEC.md') && !f.endsWith('-AI-SPEC.md') && !f.endsWith('-UI-SPEC.md'));
|
|
1901
|
-
const upstreamFiles = [researchFile, patternsFile, validationFile, specFile].filter((f) => !!f);
|
|
1902
|
-
if (upstreamFiles.length === 0) {
|
|
1903
|
-
emitSkip('no-upstream-artifacts');
|
|
1904
|
-
return;
|
|
1905
|
-
}
|
|
1906
|
-
const allFiles = [contextFile, ...upstreamFiles];
|
|
1907
|
-
const cleanCommitMs = defaultPhaseCleanCommitTimesMs(phaseDir, allFiles);
|
|
1908
|
-
const effectiveTimeMs = (file) => cleanCommitMs.has(file)
|
|
1909
|
-
? cleanCommitMs.get(file)
|
|
1910
|
-
: node_fs_1.default.statSync(node_path_1.default.join(phaseDir, file)).mtimeMs;
|
|
1911
|
-
const contextMs = effectiveTimeMs(contextFile);
|
|
1912
|
-
const driftEntries = upstreamFiles.map((f) => ({ file: f, effectiveMs: effectiveTimeMs(f) }));
|
|
1913
|
-
const staleArtifacts = computeContextDrift(contextMs, driftEntries);
|
|
1914
|
-
let wf;
|
|
1915
|
-
try {
|
|
1916
|
-
const rawCfg = JSON.parse(node_fs_1.default.readFileSync(node_path_1.default.join(pDir, 'config.json'), 'utf-8'));
|
|
1917
|
-
wf = rawCfg['workflow'];
|
|
1918
|
-
}
|
|
1919
|
-
catch {
|
|
1920
|
-
wf = undefined;
|
|
1921
|
-
}
|
|
1922
|
-
const action = wf?.context_drift_action === 'block' ? 'block' : 'warn';
|
|
1923
|
-
const block = staleArtifacts.length > 0 && action === 'block';
|
|
1924
|
-
const message = staleArtifacts.length > 0 ? buildContextDriftMessage(staleArtifacts, phaseArg) : '';
|
|
1925
|
-
output({
|
|
1926
|
-
block,
|
|
1927
|
-
skipped: false,
|
|
1928
|
-
stale_artifacts: staleArtifacts,
|
|
1929
|
-
action,
|
|
1930
|
-
message,
|
|
1931
|
-
}, raw);
|
|
1932
|
-
}
|
|
1933
|
-
function cmdVerifySchemaDrift(cwd, phaseArg, skipFlag, raw) {
|
|
1934
|
-
if (!phaseArg) {
|
|
1935
|
-
error('Usage: verify schema-drift <phase> [--skip]');
|
|
1936
|
-
return;
|
|
1937
|
-
}
|
|
1938
|
-
const pDir = planningDir(cwd);
|
|
1939
|
-
const phasesDir = node_path_1.default.join(pDir, 'phases');
|
|
1940
|
-
if (!node_fs_1.default.existsSync(phasesDir)) {
|
|
1941
|
-
output({ block: false, drift_detected: false, blocking: false, message: 'No phases directory' }, raw);
|
|
1942
|
-
return;
|
|
1943
|
-
}
|
|
1944
|
-
// Resolve the phase directory with the canonical phase-directory matcher
|
|
1945
|
-
// (phase-id.cjs::matchPhaseDirs), not a naive substring test. A bare
|
|
1946
|
-
// `.includes(phaseArg)` lets a non-existent phase silently match a different
|
|
1947
|
-
// phase whose directory name merely contains the requested token (e.g. "1"
|
|
1948
|
-
// matching "11-expansion"), making the drift gate inspect the wrong phase.
|
|
1949
|
-
// This shares the one selection rule with find-phase / verify
|
|
1950
|
-
// phase-completeness rather than restating it. (#1571, #2528)
|
|
1951
|
-
const phaseDir = resolvePhaseDirByToken(phasesDir, phaseArg);
|
|
1952
|
-
if (!phaseDir) {
|
|
1953
|
-
output({ block: false, drift_detected: false, blocking: false, message: `Phase directory not found: ${phaseArg}` }, raw);
|
|
1954
|
-
return;
|
|
1955
|
-
}
|
|
1956
|
-
// #3183: canonical LIVE plan/summary sets (root+nested,
|
|
1957
|
-
// status: superseded EXCLUDED) from the single owner, rather than a
|
|
1958
|
-
// root-only readdirSync filter — a superseded plan's claimed
|
|
1959
|
-
// files_modified is no longer treated as an expected drift target, and
|
|
1960
|
-
// nested (#3139 layout) plans/summaries are no longer invisible to the
|
|
1961
|
-
// drift check.
|
|
1962
|
-
const { planFiles, summaryFiles } = planScanMod.scanPhasePlans(phaseDir);
|
|
1963
|
-
const allFiles = [];
|
|
1964
|
-
for (const pf of planFiles) {
|
|
1965
|
-
const content = node_fs_1.default.readFileSync(node_path_1.default.join(phaseDir, pf), 'utf-8');
|
|
1966
|
-
const fmMatch = content.match(/files_modified:\s*\[([^\]]{0,8000})\]/);
|
|
1967
|
-
if (fmMatch) {
|
|
1968
|
-
const files = fmMatch[1].split(',').map((f) => f.trim()).filter(Boolean);
|
|
1969
|
-
allFiles.push(...files);
|
|
1970
|
-
}
|
|
1971
|
-
}
|
|
1972
|
-
let executionLog = '';
|
|
1973
|
-
for (const sf of summaryFiles) {
|
|
1974
|
-
executionLog += node_fs_1.default.readFileSync(node_path_1.default.join(phaseDir, sf), 'utf-8') + '\n';
|
|
1975
|
-
}
|
|
1976
|
-
const gitLog = (0, shell_command_projection_cjs_1.execGit)(['log', '--oneline', '--all', '-50'], { cwd });
|
|
1977
|
-
if (gitLog.exitCode === 0) {
|
|
1978
|
-
executionLog += '\n' + gitLog.stdout;
|
|
1979
|
-
}
|
|
1980
|
-
const result = (0, schema_detect_cjs_1.checkSchemaDrift)(allFiles, executionLog, { skipCheck: !!skipFlag });
|
|
1981
|
-
const isSkipped = !!result['skipped'];
|
|
1982
|
-
output({
|
|
1983
|
-
// Uniform gate contract: `block` = true means "this gate's bad condition is met".
|
|
1984
|
-
// When skipCheck is true (GSD_SKIP_SCHEMA_CHECK=true), the gate is bypassed —
|
|
1985
|
-
// block must be false regardless of whether drift was detected.
|
|
1986
|
-
// drift_detected and blocking are kept for compatibility.
|
|
1987
|
-
block: isSkipped ? false : !!result['driftDetected'],
|
|
1988
|
-
drift_detected: result['driftDetected'],
|
|
1989
|
-
blocking: result['blocking'],
|
|
1990
|
-
schema_files: result['schemaFiles'],
|
|
1991
|
-
orms: result['orms'],
|
|
1992
|
-
unpushed_orms: result['unpushedOrms'],
|
|
1993
|
-
message: result['message'],
|
|
1994
|
-
skipped: isSkipped,
|
|
1995
|
-
}, raw);
|
|
1996
|
-
}
|
|
1997
2028
|
/**
|
|
1998
2029
|
* Stamp `last_mapped_commit` (plus `last_mapped_at`) into the frontmatter of
|
|
1999
2030
|
* every codebase-map document that exists on disk, using the current HEAD sha.
|
|
2000
2031
|
*
|
|
2001
2032
|
* #3418: `drift.cjs` shipped a correct `writeMappedCommit` with no production
|
|
2002
2033
|
* caller, so no full `/gsd:map-codebase` run ever wrote the machine-readable
|
|
2003
|
-
* baseline that `
|
|
2034
|
+
* baseline that the codebase-drift gate (`gate-codebase-drift.cts`) reads. The stamp lives in CODE rather
|
|
2004
2035
|
* than in a prose instruction to the mapper agent on purpose: an agent that
|
|
2005
2036
|
* decides its work is already done skips a prose step silently, which is the
|
|
2006
2037
|
* exact class of failure the stamp exists to detect.
|
|
@@ -2098,207 +2129,6 @@ function cmdStampCodebaseMap(cwd, raw, only) {
|
|
|
2098
2129
|
emit(skip('exception: ' + (err instanceof Error ? err.message : String(err))));
|
|
2099
2130
|
}
|
|
2100
2131
|
}
|
|
2101
|
-
function cmdVerifyCodebaseDrift(cwd, raw) {
|
|
2102
|
-
// Non-hoisted: load-order matters for circular dep guard
|
|
2103
|
-
// eslint-disable-next-line @typescript-eslint/no-require-imports -- drift.cjs is an export= CommonJS module
|
|
2104
|
-
const drift = require('./drift.cjs');
|
|
2105
|
-
const emit = (payload) => output(payload, raw);
|
|
2106
|
-
try {
|
|
2107
|
-
const codebaseDir = node_path_1.default.join(planningDir(cwd), 'codebase');
|
|
2108
|
-
const structurePath = node_path_1.default.join(codebaseDir, 'STRUCTURE.md');
|
|
2109
|
-
if (!node_fs_1.default.existsSync(structurePath)) {
|
|
2110
|
-
emit({
|
|
2111
|
-
// Uniform gate contract: block = action_required (false when skipped).
|
|
2112
|
-
block: false,
|
|
2113
|
-
skipped: true,
|
|
2114
|
-
reason: 'no-structure-md',
|
|
2115
|
-
action_required: false,
|
|
2116
|
-
directive: 'none',
|
|
2117
|
-
elements: [],
|
|
2118
|
-
});
|
|
2119
|
-
return;
|
|
2120
|
-
}
|
|
2121
|
-
let structureMd;
|
|
2122
|
-
try {
|
|
2123
|
-
structureMd = node_fs_1.default.readFileSync(structurePath, 'utf-8');
|
|
2124
|
-
}
|
|
2125
|
-
catch (err) {
|
|
2126
|
-
emit({
|
|
2127
|
-
block: false,
|
|
2128
|
-
skipped: true,
|
|
2129
|
-
reason: 'cannot-read-structure-md: ' + (err instanceof Error ? err.message : String(err)),
|
|
2130
|
-
action_required: false,
|
|
2131
|
-
directive: 'none',
|
|
2132
|
-
elements: [],
|
|
2133
|
-
});
|
|
2134
|
-
return;
|
|
2135
|
-
}
|
|
2136
|
-
const lastMapped = drift['readMappedCommit'](structurePath);
|
|
2137
|
-
const revProbe = (0, shell_command_projection_cjs_1.execGit)(['rev-parse', 'HEAD'], { cwd });
|
|
2138
|
-
if (revProbe.exitCode !== 0) {
|
|
2139
|
-
emit({
|
|
2140
|
-
block: false,
|
|
2141
|
-
skipped: true,
|
|
2142
|
-
reason: 'not-a-git-repo',
|
|
2143
|
-
action_required: false,
|
|
2144
|
-
directive: 'none',
|
|
2145
|
-
elements: [],
|
|
2146
|
-
});
|
|
2147
|
-
return;
|
|
2148
|
-
}
|
|
2149
|
-
// #3418: an absent or unresolvable baseline means NO COMPARISON IS POSSIBLE.
|
|
2150
|
-
// It is neither zero drift nor total drift, and reporting it as either is a
|
|
2151
|
-
// lie the consumer cannot detect. The former fallback diffed HEAD against
|
|
2152
|
-
// the empty tree, so every tracked file read as newly added and the gate
|
|
2153
|
-
// reported maximum drift identically on every run -- which made a genuinely
|
|
2154
|
-
// stale map indistinguishable from a fresh one, and let `spawn_mapper` fire
|
|
2155
|
-
// a whole-repo remap while presenting itself as an incremental one.
|
|
2156
|
-
if (!lastMapped) {
|
|
2157
|
-
emit({
|
|
2158
|
-
block: false,
|
|
2159
|
-
skipped: true,
|
|
2160
|
-
reason: 'no-mapped-commit',
|
|
2161
|
-
action_required: false,
|
|
2162
|
-
directive: 'none',
|
|
2163
|
-
elements: [],
|
|
2164
|
-
last_mapped_commit: null,
|
|
2165
|
-
});
|
|
2166
|
-
return;
|
|
2167
|
-
}
|
|
2168
|
-
const baseProbe = (0, shell_command_projection_cjs_1.execGit)(['cat-file', '-t', lastMapped], { cwd });
|
|
2169
|
-
if (baseProbe.exitCode !== 0 || baseProbe.stdout.trim() !== 'commit') {
|
|
2170
|
-
// A stamp git cannot resolve: history rewrite, GC, or a shallow clone.
|
|
2171
|
-
// Distinct reason from 'no-mapped-commit' -- the map claims a baseline,
|
|
2172
|
-
// this repository just cannot see it, which is an operator-actionable
|
|
2173
|
-
// difference (re-map vs. unshallow). A resolvable non-commit (a tree or
|
|
2174
|
-
// blob sha, a ref name) is the same class of bad baseline: git would
|
|
2175
|
-
// happily diff against it and report drift against the wrong object.
|
|
2176
|
-
emit({
|
|
2177
|
-
block: false,
|
|
2178
|
-
skipped: true,
|
|
2179
|
-
reason: 'unresolvable-mapped-commit',
|
|
2180
|
-
action_required: false,
|
|
2181
|
-
directive: 'none',
|
|
2182
|
-
elements: [],
|
|
2183
|
-
last_mapped_commit: lastMapped,
|
|
2184
|
-
});
|
|
2185
|
-
return;
|
|
2186
|
-
}
|
|
2187
|
-
const base = lastMapped;
|
|
2188
|
-
const diff = (0, shell_command_projection_cjs_1.execGit)(['diff', '--name-status', base, 'HEAD'], { cwd });
|
|
2189
|
-
if (diff.exitCode !== 0) {
|
|
2190
|
-
emit({
|
|
2191
|
-
block: false,
|
|
2192
|
-
skipped: true,
|
|
2193
|
-
reason: 'git-diff-failed',
|
|
2194
|
-
action_required: false,
|
|
2195
|
-
directive: 'none',
|
|
2196
|
-
elements: [],
|
|
2197
|
-
});
|
|
2198
|
-
return;
|
|
2199
|
-
}
|
|
2200
|
-
// #3418: GSD's own planning artifacts are not codebase structure. A
|
|
2201
|
-
// map-codebase run commits `.planning/codebase/*.md`, so a correctly
|
|
2202
|
-
// stamped baseline would be re-poisoned by the very commit that carries
|
|
2203
|
-
// the stamp -- the next gate invocation would report the map's own seven
|
|
2204
|
-
// documents as seven new directories, back over the default threshold of
|
|
2205
|
-
// three. Derived from planningRoot() rather than a hardcoded literal so a
|
|
2206
|
-
// repoint of the planning root cannot leave this filter behind.
|
|
2207
|
-
//
|
|
2208
|
-
// `git diff --name-status` always prints repo-root-relative paths, so a cwd
|
|
2209
|
-
// below the root needs the `sub/` prefix or the filter matches nothing.
|
|
2210
|
-
// That prefix comes from git (`--show-prefix`: root-relative, forward
|
|
2211
|
-
// slashes, trailing slash, empty at the root). The rejected alternative was
|
|
2212
|
-
// path.relative(`--show-toplevel`, cwd), which mixes two path producers: on
|
|
2213
|
-
// Windows os.tmpdir() hands back the 8.3 short form while git resolves the
|
|
2214
|
-
// long one, so relative() between them yields a `../..` chain that matches
|
|
2215
|
-
// nothing. The `.planning` half below is safe to compute with relative()
|
|
2216
|
-
// because both of its sides are the same cwd string.
|
|
2217
|
-
const prefixProbe = (0, shell_command_projection_cjs_1.execGit)(['rev-parse', '--show-prefix'], { cwd });
|
|
2218
|
-
const repoPrefix = prefixProbe.exitCode === 0 ? prefixProbe.stdout.trim() : '';
|
|
2219
|
-
const planningPrefix = repoPrefix + node_path_1.default.relative(cwd, planningRoot(cwd)).split(node_path_1.default.sep).join('/') + '/';
|
|
2220
|
-
const isPlanningArtifact = (file) => file.split('\\').join('/').startsWith(planningPrefix);
|
|
2221
|
-
const added = [];
|
|
2222
|
-
const modified = [];
|
|
2223
|
-
const deleted = [];
|
|
2224
|
-
for (const line of diff.stdout.split(/\r?\n/)) {
|
|
2225
|
-
if (!line.trim())
|
|
2226
|
-
continue;
|
|
2227
|
-
const m = line.match(/^([A-Z])\d*\t(.+?)(?:\t(.+))?$/);
|
|
2228
|
-
if (!m)
|
|
2229
|
-
continue;
|
|
2230
|
-
const status = m[1];
|
|
2231
|
-
// execGit sets no core.quotepath config, so git's default `true` applies:
|
|
2232
|
-
// any path containing non-ASCII bytes (or `"`, `\`, control bytes) is
|
|
2233
|
-
// C-quoted — `"docs/\350\256\276…/overview.md"`. Capturing that verbatim
|
|
2234
|
-
// garbles affected_paths/elements and makes isPathMapped compare the
|
|
2235
|
-
// quoted prefix (`"docs`) against STRUCTURE.md, misclassifying DOCUMENTED
|
|
2236
|
-
// directories as new_dir (#4081). Decode with the single owner of the
|
|
2237
|
-
// git C-quote seam (worktree-safety.cjs); a non-quoted value — the plain
|
|
2238
|
-
// ASCII common case — passes through untouched. Both capture groups are
|
|
2239
|
-
// decoded: R/C lines carry old AND new paths, either may be quoted.
|
|
2240
|
-
const file = decodeGitQuotedPath(m[3] || m[2]);
|
|
2241
|
-
if (isPlanningArtifact(file))
|
|
2242
|
-
continue;
|
|
2243
|
-
if (status === 'A' || status === 'R' || status === 'C')
|
|
2244
|
-
added.push(file);
|
|
2245
|
-
else if (status === 'M')
|
|
2246
|
-
modified.push(file);
|
|
2247
|
-
else if (status === 'D')
|
|
2248
|
-
deleted.push(file);
|
|
2249
|
-
}
|
|
2250
|
-
// loadConfig() returns a flattened object — there is no nested `workflow`
|
|
2251
|
-
// key. Read the raw config.json directly to access workflow-scoped keys,
|
|
2252
|
-
// matching the pattern used in check-command-router.cts:readWorkflowConfig.
|
|
2253
|
-
let wf;
|
|
2254
|
-
try {
|
|
2255
|
-
const rawCfg = JSON.parse(node_fs_1.default.readFileSync(node_path_1.default.join(planningDir(cwd), 'config.json'), 'utf-8'));
|
|
2256
|
-
wf = rawCfg['workflow'];
|
|
2257
|
-
}
|
|
2258
|
-
catch {
|
|
2259
|
-
wf = undefined;
|
|
2260
|
-
}
|
|
2261
|
-
const threshold = Number.isInteger(wf?.drift_threshold) && wf?.drift_threshold >= 1
|
|
2262
|
-
? wf?.drift_threshold
|
|
2263
|
-
: 3;
|
|
2264
|
-
const action = wf?.drift_action === 'auto-remap' ? 'auto-remap' : 'warn';
|
|
2265
|
-
const driftResult = drift['detectDrift']({
|
|
2266
|
-
addedFiles: added,
|
|
2267
|
-
modifiedFiles: modified,
|
|
2268
|
-
deletedFiles: deleted,
|
|
2269
|
-
structureMd,
|
|
2270
|
-
threshold,
|
|
2271
|
-
action,
|
|
2272
|
-
runtime: (0, runtime_slash_cjs_1.resolveRuntime)(cwd),
|
|
2273
|
-
});
|
|
2274
|
-
const actionRequired = !!driftResult['actionRequired'];
|
|
2275
|
-
emit({
|
|
2276
|
-
// Uniform gate contract: block = action_required.
|
|
2277
|
-
block: actionRequired,
|
|
2278
|
-
skipped: !!driftResult['skipped'],
|
|
2279
|
-
reason: driftResult['reason'] || null,
|
|
2280
|
-
action_required: actionRequired,
|
|
2281
|
-
directive: driftResult['directive'],
|
|
2282
|
-
spawn_mapper: !!driftResult['spawnMapper'],
|
|
2283
|
-
affected_paths: driftResult['affectedPaths'] || [],
|
|
2284
|
-
elements: driftResult['elements'] || [],
|
|
2285
|
-
threshold,
|
|
2286
|
-
action,
|
|
2287
|
-
last_mapped_commit: lastMapped,
|
|
2288
|
-
message: driftResult['message'] || '',
|
|
2289
|
-
});
|
|
2290
|
-
}
|
|
2291
|
-
catch (err) {
|
|
2292
|
-
emit({
|
|
2293
|
-
block: false,
|
|
2294
|
-
skipped: true,
|
|
2295
|
-
reason: 'exception: ' + (err && err instanceof Error ? err.message : String(err)),
|
|
2296
|
-
action_required: false,
|
|
2297
|
-
directive: 'none',
|
|
2298
|
-
elements: [],
|
|
2299
|
-
});
|
|
2300
|
-
}
|
|
2301
|
-
}
|
|
2302
2132
|
module.exports = {
|
|
2303
2133
|
scanNegativeGrepCommentEcho,
|
|
2304
2134
|
scanFileWideNegativeGateConflict,
|
|
@@ -2314,10 +2144,6 @@ module.exports = {
|
|
|
2314
2144
|
cmdValidateConsistency,
|
|
2315
2145
|
cmdValidateHealth,
|
|
2316
2146
|
cmdValidateAgents,
|
|
2317
|
-
cmdVerifySchemaDrift,
|
|
2318
|
-
cmdVerifyCodebaseDrift,
|
|
2319
|
-
computeContextDrift,
|
|
2320
|
-
cmdVerifyContextDrift,
|
|
2321
2147
|
cmdStampCodebaseMap,
|
|
2322
2148
|
STATE_HEAD_ADVISORY_COMMITS,
|
|
2323
2149
|
};
|