@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
|
@@ -19,6 +19,10 @@
|
|
|
19
19
|
* state signal-resume Remove WAITING.json signal
|
|
20
20
|
* resolve-model <agent-type> Get model for agent based on profile
|
|
21
21
|
* find-phase <phase> Find phase directory by number
|
|
22
|
+
* select-revert-commits (--phase P | --plan NN-MM) --range R
|
|
23
|
+
* List commits within R whose DECLARED conventional-commit
|
|
24
|
+
* scope identifies P or NN-MM (exact match; phase mode also
|
|
25
|
+
* accepts a NN-MM-scoped commit). Used by /gsd:undo (#4661).
|
|
22
26
|
* commit <message> [--files f1 f2] [--no-verify] Commit planning docs
|
|
23
27
|
* commit-docs-guard enable|disable Opt-in .git/hooks/pre-commit guard
|
|
24
28
|
* that refuses a commit staging
|
|
@@ -60,7 +64,7 @@
|
|
|
60
64
|
* roadmap update-plan-progress <N> Update progress table row from disk (PLAN vs SUMMARY counts)
|
|
61
65
|
* roadmap annotate-dependencies <N> Add wave dependency notes + cross-cutting constraints to ROADMAP.md
|
|
62
66
|
* roadmap validate Validate phase ID convention compliance
|
|
63
|
-
* roadmap upgrade [--apply] --convention milestone-prefixed Migrate phase IDs
|
|
67
|
+
* roadmap upgrade [--apply] --convention <milestone-prefixed|bracket> Migrate phase IDs (dry-run by default)
|
|
64
68
|
*
|
|
65
69
|
* Requirements Operations:
|
|
66
70
|
* requirements mark-complete <ids> Mark requirement IDs as complete in REQUIREMENTS.md
|
|
@@ -219,6 +223,8 @@
|
|
|
219
223
|
* Loop Extension Point Queries (ADR-857 phase 3c):
|
|
220
224
|
* loop render-hooks <point> Resolve + render active Capability hooks at a loop point
|
|
221
225
|
* [--config-dir <path>] [--runtime <r>] [--active-cap <capId>]
|
|
226
|
+
* [--after-fingerprint <phaseDir>] (#5105: skip verify:post
|
|
227
|
+
* steps whose declared artifact already exists in phaseDir)
|
|
222
228
|
* Returns JSON envelope { point, activeHooks, rendered }
|
|
223
229
|
* Valid points: discuss:pre/post, plan:pre/post,
|
|
224
230
|
* execute:pre/wave:pre/wave:post/post, verify:pre/post, ship:pre/post
|
|
@@ -241,6 +247,14 @@
|
|
|
241
247
|
* Import a GSD-2 (.gsd/) project back to GSD v1 (.planning/) format
|
|
242
248
|
*/
|
|
243
249
|
|
|
250
|
+
// #5183: Node's on-disk compile cache, so later runs reuse V8's compiled code
|
|
251
|
+
// for unchanged modules. Only as the entry (a test require() is unaffected),
|
|
252
|
+
// skipped under V8 coverage, optional (Bun stubs it), and never fatal.
|
|
253
|
+
// NODE_DISABLE_COMPILE_CACHE (any value) turns it off.
|
|
254
|
+
if (require.main === module && !process.env.NODE_V8_COVERAGE) {
|
|
255
|
+
try { require('node:module').enableCompileCache?.(); } catch { /* optimization only */ }
|
|
256
|
+
}
|
|
257
|
+
|
|
244
258
|
const fs = require('fs');
|
|
245
259
|
const path = require('path');
|
|
246
260
|
|
|
@@ -272,7 +286,7 @@ try {
|
|
|
272
286
|
|
|
273
287
|
const { ExitError, runMain, resolveContractVersion } = require('./lib/cli-exit.cjs');
|
|
274
288
|
const io = require('./lib/io.cjs');
|
|
275
|
-
const { error, ERROR_REASON, setJsonErrorMode, output, formatDiagnosticToken } = io;
|
|
289
|
+
const { error, ERROR_REASON, setJsonErrorMode, output, formatDiagnosticToken, captureStdoutSyncWrites, resolveAtFileOutput } = io;
|
|
276
290
|
const projectRoot = require('./lib/project-root.cjs');
|
|
277
291
|
// Resolve findProjectRoot lazily at call time rather than binding it at module
|
|
278
292
|
// load. It is sourced from project-root.cjs; a call-time lookup is robust
|
|
@@ -487,6 +501,7 @@ function dispatchCapabilityCommand({ command, args, cwd, raw, error, registry, r
|
|
|
487
501
|
_result = fn({ args, cwd, raw, error });
|
|
488
502
|
} catch (e) {
|
|
489
503
|
if (e instanceof ExitError) throw e; // intentional structured error from the router (honors --json-errors) — propagate untouched
|
|
504
|
+
if (isVerificationStatusError(e)) throw e; // #5118: translated once, centrally, by main()
|
|
490
505
|
error(
|
|
491
506
|
'capability command "' + command + '" router "' + entry.router + '" in module "' + entry.module + '" threw: ' + (e && e.message ? e.message : String(e)),
|
|
492
507
|
ERROR_REASON.SDK_FAIL_FAST,
|
|
@@ -602,6 +617,7 @@ function dispatchOverlayCapabilityCommand({ command, args, cwd, raw, error, load
|
|
|
602
617
|
_result = fn({ args, cwd, raw, error });
|
|
603
618
|
} catch (e) {
|
|
604
619
|
if (e instanceof ExitError) throw e;
|
|
620
|
+
if (isVerificationStatusError(e)) throw e; // #5118: translated once, centrally, by main()
|
|
605
621
|
error(
|
|
606
622
|
'capability command "' + command + '" router "' + entry.router + '" in module "' + entry.module + '" threw: ' + (e && e.message ? e.message : String(e)),
|
|
607
623
|
ERROR_REASON.SDK_FAIL_FAST,
|
|
@@ -949,6 +965,219 @@ function dispatchOverlayCapabilityCommand({ command, args, cwd, raw, error, load
|
|
|
949
965
|
if (!handled) phase.cmdFindPhase(cwd, args[1], raw);
|
|
950
966
|
}
|
|
951
967
|
|
|
968
|
+
/**
|
|
969
|
+
* #4906 Phase 5 (issue #4661, absorbed from the original bug report):
|
|
970
|
+
* `gsd-tools.cjs query select-revert-commits --phase <id>|--plan <id>
|
|
971
|
+
* --range <git-revision-range>`.
|
|
972
|
+
*
|
|
973
|
+
* Replaces the two `git log --oneline | grep -E ...` pipelines
|
|
974
|
+
* `gsd-core/workflows/undo.md` used to run for `/gsd:undo --phase`/`--plan`
|
|
975
|
+
* selection. `--range` is the ALREADY-COMPUTED `$UNDO_RANGE` the workflow's
|
|
976
|
+
* own phase-directory anchor logic derives — this subcommand changes only
|
|
977
|
+
* the subject-matching step, never the ranging behavior.
|
|
978
|
+
*
|
|
979
|
+
* Validates `--phase`/`--range <id>` via `validatePhaseNumber`
|
|
980
|
+
* (src/security.cts) BEFORE running any git command — an invalid id is
|
|
981
|
+
* refused here, never reaching git or the selection regex (closes bug
|
|
982
|
+
* class 1 of #4661 by construction). A `--plan NN-MM` id is two
|
|
983
|
+
* phase-number-shaped segments joined by the FIRST `-`: `validatePhaseNumber`'s
|
|
984
|
+
* bracket-style alternative requires a leading letter, so a plain numeric
|
|
985
|
+
* plan id never matches it whole — each segment is validated on its own
|
|
986
|
+
* instead of forking a second grammar.
|
|
987
|
+
*
|
|
988
|
+
* Selection itself is delegated to `selectCommitsByDeclaredScope`
|
|
989
|
+
* (src/undo-commit-selection.cts), which parses each commit subject
|
|
990
|
+
* through the SAME anchored conventional-commit header regex the
|
|
991
|
+
* changelog/PR-title gate uses and compares the declared scope to the
|
|
992
|
+
* validated id by exact string equality — no regex is ever built from
|
|
993
|
+
* user input.
|
|
994
|
+
*
|
|
995
|
+
* Output: JSON `{selected, classified}` by default; with `--raw`, one
|
|
996
|
+
* `<full-sha> <subject>` line per selected commit (preserves the visual
|
|
997
|
+
* shape `git log --oneline` produced, so the workflow's downstream
|
|
998
|
+
* confirm-screen display, >50 truncation count, and `git revert
|
|
999
|
+
* --no-commit` loop over COMMITS are unchanged).
|
|
1000
|
+
*/
|
|
1001
|
+
function routeSelectRevertCommits({ args, cwd, raw, error, invokingCwd }) {
|
|
1002
|
+
// #4906/#4465 review fix: `cwd` here has already been remapped to the
|
|
1003
|
+
// MAIN worktree root by `resolveMainWorktreeCwd` (main(), applied
|
|
1004
|
+
// BLANKET to every command before dispatch — correct for a router that
|
|
1005
|
+
// reads `.planning/`, e.g. `find-phase`, but wrong for this one). This
|
|
1006
|
+
// command reads NO `.planning/` content — it only runs `git log` — and
|
|
1007
|
+
// that git operation must see the CALLER's own worktree: its HEAD, its
|
|
1008
|
+
// branch, its reachable commit range. Using the remapped `cwd` instead
|
|
1009
|
+
// silently resolves `--range` against a DIFFERENT repository checkout
|
|
1010
|
+
// (reproduced: from a linked worktree with no local `.planning/`, a
|
|
1011
|
+
// `--range HEAD~3..HEAD` that succeeds when run directly in that
|
|
1012
|
+
// worktree fails with "ambiguous argument" through this command,
|
|
1013
|
+
// because it silently ran against main's shorter HEAD instead —
|
|
1014
|
+
// tests/undo-commit-selection-4465.test.cjs, "linked worktree: the main
|
|
1015
|
+
// worktree's planning is the same repository, and is not refused").
|
|
1016
|
+
// `invokingCwd` (threaded from main() through dispatchHostCommand) is
|
|
1017
|
+
// the pre-remap value; falling back to `cwd` keeps direct callers that
|
|
1018
|
+
// omit it (e.g. `dispatchHostCommand` invoked without the new field)
|
|
1019
|
+
// working exactly as before rather than crashing on `undefined`.
|
|
1020
|
+
const gitCwd = invokingCwd || cwd;
|
|
1021
|
+
const { validatePhaseNumber } = require('./lib/security.cjs');
|
|
1022
|
+
const { normalizePhaseName } = require('./lib/phase-id.cjs');
|
|
1023
|
+
const { selectCommitsByDeclaredScope } = require('./lib/undo-commit-selection.cjs');
|
|
1024
|
+
const { execGit } = require('./lib/shell-command-projection.cjs');
|
|
1025
|
+
|
|
1026
|
+
// #4906 Phase 5 review fix: the RETIRED phase-mode grep's `0*` prefix
|
|
1027
|
+
// (`\(0*${TARGET_PHASE}(-[0-9]+)?\):`) tolerated an UNPADDED user-supplied
|
|
1028
|
+
// phase number against a zero-padded commit scope — `--phase 3` matched
|
|
1029
|
+
// `feat(03-01): ...` because `0*3` matches the literal text `03`.
|
|
1030
|
+
// `selectCommitsByDeclaredScope` compares by exact string equality, and
|
|
1031
|
+
// `gsd-core/workflows/undo.md`'s parse_arguments step passes `TARGET_PHASE`
|
|
1032
|
+
// through with NO normalization, so without this, `--phase 3` would
|
|
1033
|
+
// silently select nothing against real (zero-padded) commit scopes — a
|
|
1034
|
+
// behavior regression, not just a documented known-limit. Zero-pad via
|
|
1035
|
+
// `normalizePhaseName` (src/phase-id.cts), the canonical owner of this
|
|
1036
|
+
// exact operation (confirmed: `normalizePhaseName('3') === '03'`).
|
|
1037
|
+
//
|
|
1038
|
+
// GUARDED to digit-first ids only: `normalizePhaseName` also runs
|
|
1039
|
+
// `stripProjectCodePrefix`, which strips a leading `LETTERS-`-shaped
|
|
1040
|
+
// prefix UNCONDITIONALLY — `normalizePhaseName('PROJ-42') === '42'`,
|
|
1041
|
+
// discarding the "PROJ-" entirely (confirmed by direct call). That is
|
|
1042
|
+
// exactly the shape `validatePhaseNumber`'s bracket-style alternative
|
|
1043
|
+
// legitimately accepts as a phase id in ITS OWN right (`PROJ-42`,
|
|
1044
|
+
// `AUTH-101` — see tests/security.test.cjs). Running a letter-first id
|
|
1045
|
+
// through it would silently widen the match (a wildcard-style
|
|
1046
|
+
// over-match reintroducing bug classes 1/2). The old grep's `0*` was
|
|
1047
|
+
// never meaningful for a letter-first target anyway — `0*` before a
|
|
1048
|
+
// non-'0' character matches zero repetitions, a no-op — so a
|
|
1049
|
+
// letter-first id needs neither padding nor this normalization.
|
|
1050
|
+
// `PROJECT_CODE_PREFIX_STRIP_RE_I` (`src/phase-id.cts:33`) itself
|
|
1051
|
+
// requires `^[A-Z]`, so it can never fire on a digit-first id — the
|
|
1052
|
+
// guard below is therefore exactly the safe/unsafe boundary.
|
|
1053
|
+
const normalizePaddedPhase = (id) => (/^\d/.test(id) ? normalizePhaseName(id) : id);
|
|
1054
|
+
|
|
1055
|
+
const parsed = parseNamedArgsOrExit(
|
|
1056
|
+
args,
|
|
1057
|
+
{ valueFlags: ['phase', 'plan', 'range'], positionals: 1 },
|
|
1058
|
+
error,
|
|
1059
|
+
);
|
|
1060
|
+
const rawPhase = parsed.phase;
|
|
1061
|
+
const rawPlan = parsed.plan;
|
|
1062
|
+
const range = parsed.range;
|
|
1063
|
+
|
|
1064
|
+
if ((rawPhase && rawPlan) || (!rawPhase && !rawPlan)) {
|
|
1065
|
+
error('select-revert-commits requires exactly one of --phase <id> or --plan <id>', ERROR_REASON.USAGE);
|
|
1066
|
+
return;
|
|
1067
|
+
}
|
|
1068
|
+
if (!range) {
|
|
1069
|
+
error('select-revert-commits requires --range <git-revision-range>', ERROR_REASON.USAGE);
|
|
1070
|
+
return;
|
|
1071
|
+
}
|
|
1072
|
+
// Belt-and-suspenders alongside the --end-of-options guard below (mirrors
|
|
1073
|
+
// src/git-base-branch.cts's isSafeRevisionRef posture for the same class
|
|
1074
|
+
// of argument): refuse an option-shaped range before it ever reaches git.
|
|
1075
|
+
if (typeof range !== 'string' || range.startsWith('-')) {
|
|
1076
|
+
error(`Invalid --range: ${JSON.stringify(range)}`, ERROR_REASON.USAGE);
|
|
1077
|
+
return;
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
let mode;
|
|
1081
|
+
let normalizedId;
|
|
1082
|
+
if (rawPhase) {
|
|
1083
|
+
mode = 'phase';
|
|
1084
|
+
const check = validatePhaseNumber(rawPhase);
|
|
1085
|
+
if (!check.valid) {
|
|
1086
|
+
error(`Invalid --phase: ${check.error}`, ERROR_REASON.USAGE);
|
|
1087
|
+
return;
|
|
1088
|
+
}
|
|
1089
|
+
normalizedId = normalizePaddedPhase(check.normalized);
|
|
1090
|
+
} else {
|
|
1091
|
+
mode = 'plan';
|
|
1092
|
+
// Split on the FIRST `-` only, giving exactly two segments (phase,
|
|
1093
|
+
// plan). This is deliberately NOT a general N-segment composite-id
|
|
1094
|
+
// parser: checked against docs/reference/plan-md.md (frontmatter
|
|
1095
|
+
// `plan` field + `.planning/phases/<NN>-<slug>/<NN>-<PP>-PLAN.md`
|
|
1096
|
+
// layout) and every `--plan` usage in gsd-core/workflows/*.md, the only
|
|
1097
|
+
// plan-id shape this repo documents or emits is the plain two-segment
|
|
1098
|
+
// `NN-MM`. A THREE-segment purely-numeric shape (`NN-MM-PP`) does exist
|
|
1099
|
+
// elsewhere in the codebase, but as a MILESTONE-phase-plan composite
|
|
1100
|
+
// under the (already-deprecated-forward) `milestone-prefixed`
|
|
1101
|
+
// `phase_id_convention` — a different id space than phase-plan, which
|
|
1102
|
+
// `docs/adr/612-bracket-phase-id-convention.md:15` itself documents as
|
|
1103
|
+
// having "no deterministic parse" once a token carries both a
|
|
1104
|
+
// milestone-joined phase AND a plan (the exact ambiguity that
|
|
1105
|
+
// motivated bracket's own `[PROJECT.MM] PP-PP` grammar). No function in
|
|
1106
|
+
// `src/phase-id.cts` validates the phase-PLAN grammar end-to-end
|
|
1107
|
+
// either: `parsePhaseId` explicitly REJECTS a bare `NN-MM` token by
|
|
1108
|
+
// design (its own doc comment lists `02-04` as a rejected "ambiguous /
|
|
1109
|
+
// bare token"), and `getPhaseDirFromPhaseId`'s N-segment dash grammar
|
|
1110
|
+
// is a MILESTONE-phase(-subphase) directory-name constructor — a
|
|
1111
|
+
// structurally similar but semantically different id space (first
|
|
1112
|
+
// segment = milestone, not phase) whose return value doesn't map back
|
|
1113
|
+
// to (phase, plan) anyway. Given no suitable whole-string validator
|
|
1114
|
+
// exists and no real usage needs more than two segments, a `NN-MM-PP`
|
|
1115
|
+
// id is refused today (see
|
|
1116
|
+
// tests/undo-commit-selection.test.cjs "refuses a 3-segment plan id"),
|
|
1117
|
+
// not silently mis-parsed — extending this to N segments is a product
|
|
1118
|
+
// decision for whichever future issue actually needs it. The same
|
|
1119
|
+
// "only the plain two-segment shape is supported" standard applies to
|
|
1120
|
+
// a bracket-style PHASE segment here too: `--plan PROJ-42-01` splits
|
|
1121
|
+
// on the first `-` into phase segment `PROJ` (no internal dash) and
|
|
1122
|
+
// plan segment `42-01`, and `validatePhaseNumber`'s bracket branch
|
|
1123
|
+
// requires the internal dash on ITS side (`PROJ-42`, not bare `PROJ`),
|
|
1124
|
+
// so the phase segment fails and the whole id is refused — bracket-style
|
|
1125
|
+
// ids are supported for `--phase` only, never as a `--plan` segment (no
|
|
1126
|
+
// real usage found; see tests/undo-commit-selection.test.cjs "bracket-
|
|
1127
|
+
// style phase segment in --plan is refused").
|
|
1128
|
+
const dashIdx = rawPlan.indexOf('-');
|
|
1129
|
+
if (dashIdx === -1) {
|
|
1130
|
+
error(`Invalid --plan: "${rawPlan}" (expected NN-MM)`, ERROR_REASON.USAGE);
|
|
1131
|
+
return;
|
|
1132
|
+
}
|
|
1133
|
+
const phasePart = rawPlan.slice(0, dashIdx);
|
|
1134
|
+
const planPart = rawPlan.slice(dashIdx + 1);
|
|
1135
|
+
const phaseCheck = validatePhaseNumber(phasePart);
|
|
1136
|
+
if (!phaseCheck.valid) {
|
|
1137
|
+
error(`Invalid --plan: "${rawPlan}" (phase segment: ${phaseCheck.error})`, ERROR_REASON.USAGE);
|
|
1138
|
+
return;
|
|
1139
|
+
}
|
|
1140
|
+
const planCheck = validatePhaseNumber(planPart);
|
|
1141
|
+
if (!planCheck.valid) {
|
|
1142
|
+
error(`Invalid --plan: "${rawPlan}" (plan segment: ${planCheck.error})`, ERROR_REASON.USAGE);
|
|
1143
|
+
return;
|
|
1144
|
+
}
|
|
1145
|
+
normalizedId = `${normalizePaddedPhase(phaseCheck.normalized)}-${normalizePaddedPhase(planCheck.normalized)}`;
|
|
1146
|
+
}
|
|
1147
|
+
|
|
1148
|
+
const GIT_LOG_TIMEOUT_MS = 15000;
|
|
1149
|
+
const gitResult = execGit(
|
|
1150
|
+
['log', '--format=%H%x00%s', '--no-merges', '--no-decorate', '--end-of-options', range],
|
|
1151
|
+
{ cwd: gitCwd, timeout: GIT_LOG_TIMEOUT_MS },
|
|
1152
|
+
);
|
|
1153
|
+
if (gitResult.exitCode !== 0) {
|
|
1154
|
+
// `execGit` (src/shell-command-projection.cts) exposes `timedOut` on
|
|
1155
|
+
// its SpawnResultOutput — thread it through so a hung `git log` reads
|
|
1156
|
+
// distinctly from an ordinary non-zero exit with no stderr, which
|
|
1157
|
+
// otherwise both surfaced as the same unhelpful "(no stderr)" message.
|
|
1158
|
+
const gitFailureDetail = gitResult.timedOut
|
|
1159
|
+
? `git log timed out after ${GIT_LOG_TIMEOUT_MS}ms`
|
|
1160
|
+
: `git log failed: ${gitResult.stderr || '(no stderr)'}`;
|
|
1161
|
+
error(
|
|
1162
|
+
`select-revert-commits: ${gitFailureDetail} for range "${range}"`,
|
|
1163
|
+
ERROR_REASON.USAGE,
|
|
1164
|
+
);
|
|
1165
|
+
return;
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
const commits = (gitResult.stdout ? gitResult.stdout.split('\n') : [])
|
|
1169
|
+
.filter((line) => line.length > 0)
|
|
1170
|
+
.map((line) => {
|
|
1171
|
+
const nulIdx = line.indexOf('\0');
|
|
1172
|
+
return nulIdx === -1 ? null : { sha: line.slice(0, nulIdx), subject: line.slice(nulIdx + 1) };
|
|
1173
|
+
})
|
|
1174
|
+
.filter((c) => c !== null);
|
|
1175
|
+
|
|
1176
|
+
const result = selectCommitsByDeclaredScope(commits, normalizedId, mode);
|
|
1177
|
+
const rawLines = result.selected.map((c) => `${c.sha} ${c.subject}`).join('\n');
|
|
1178
|
+
output(result, raw, rawLines);
|
|
1179
|
+
}
|
|
1180
|
+
|
|
952
1181
|
function routeCommit({ args, cwd, raw, error }) {
|
|
953
1182
|
const amend = args.includes('--amend');
|
|
954
1183
|
const noVerify = args.includes('--no-verify');
|
|
@@ -1183,7 +1412,18 @@ function dispatchOverlayCapabilityCommand({ command, args, cwd, raw, error, load
|
|
|
1183
1412
|
// First positional that isn't a flag also works (lenient); otherwise ignore unknown flags.
|
|
1184
1413
|
if (!a.startsWith('-') && !pifRuntime) { pifRuntime = a; }
|
|
1185
1414
|
}
|
|
1186
|
-
|
|
1415
|
+
// A retired runtime id now THROWS rather than resolving (#4709 AC#1).
|
|
1416
|
+
// Map it to the same clean single-line error routeSkillsRoot emits for
|
|
1417
|
+
// an unknown runtime — a CLI must not answer a bad flag value with a
|
|
1418
|
+
// stack trace. The thrown message already names the successor and the
|
|
1419
|
+
// retiring issue, so it is surfaced verbatim.
|
|
1420
|
+
let filename;
|
|
1421
|
+
try {
|
|
1422
|
+
filename = getProjectInstructionFile(pifRuntime);
|
|
1423
|
+
} catch (err) {
|
|
1424
|
+
if (err && err.code === 'GSD_RETIRED_RUNTIME') error(err.message);
|
|
1425
|
+
throw err;
|
|
1426
|
+
}
|
|
1187
1427
|
process.stdout.write(filename + '\n');
|
|
1188
1428
|
}
|
|
1189
1429
|
|
|
@@ -1252,9 +1492,21 @@ function dispatchOverlayCapabilityCommand({ command, args, cwd, raw, error, load
|
|
|
1252
1492
|
// claims. `--directory` wins outright when given explicitly;
|
|
1253
1493
|
// otherwise a supplied `--quick-id` + `--slug` pair derives the
|
|
1254
1494
|
// canonical permalink the same way `workflows/quick.md` renders it.
|
|
1495
|
+
// #4906 Phase 3 (#4958, evidence #4736): `--status` is the OPTIONAL
|
|
1496
|
+
// widening that closes the last gap between this CLI and
|
|
1497
|
+
// `workflows/quick.md`'s own Step 7c row shapes. `appendQuickTaskRow`
|
|
1498
|
+
// (markdown-table.cjs) has always accepted `status` — the schema's
|
|
1499
|
+
// `Status` column exists precisely for `$VALIDATE_MODE` runs — but
|
|
1500
|
+
// nothing on this CLI surface could set it, so quick.md's
|
|
1501
|
+
// VALIDATE_MODE branch had no escaping-safe path and kept
|
|
1502
|
+
// interpolating `${DESCRIPTION}` into raw markdown (#4736 D1: an
|
|
1503
|
+
// unescaped `|` in a task description permanently rags the table).
|
|
1504
|
+
// Omitted, `status` stays undefined and `appendQuickTaskRow` falls
|
|
1505
|
+
// back to its own `'—'` default — unchanged for every existing
|
|
1506
|
+
// caller (fast.md, quick.md's non-VALIDATE_MODE branch).
|
|
1255
1507
|
const qtaParsed = parseNamedArgsOrExit(
|
|
1256
1508
|
qtaArgs,
|
|
1257
|
-
{ valueFlags: ['task', 'quick-id', 'slug', 'directory'], positionals: 'rest' },
|
|
1509
|
+
{ valueFlags: ['task', 'quick-id', 'slug', 'directory', 'status'], positionals: 'rest' },
|
|
1258
1510
|
error,
|
|
1259
1511
|
);
|
|
1260
1512
|
const qtaTask = qtaParsed.task || args[1];
|
|
@@ -1265,13 +1517,17 @@ function dispatchOverlayCapabilityCommand({ command, args, cwd, raw, error, load
|
|
|
1265
1517
|
const qtaSlug = qtaParsed['slug'] || undefined;
|
|
1266
1518
|
const qtaDirectory = qtaParsed['directory']
|
|
1267
1519
|
|| (qtaQuickId && qtaSlug ? `[${qtaQuickId}-${qtaSlug}](./quick/${qtaQuickId}-${qtaSlug}/)` : undefined);
|
|
1520
|
+
const qtaStatus = qtaParsed['status'] || undefined;
|
|
1268
1521
|
|
|
1269
1522
|
const statePath = path.join(cwd, '.planning', 'STATE.md');
|
|
1270
1523
|
if (!fs.existsSync(statePath)) {
|
|
1271
1524
|
error(`quick-tasks-append: STATE.md not found at ${statePath}`, ERROR_REASON.USAGE);
|
|
1272
1525
|
}
|
|
1273
1526
|
|
|
1274
|
-
|
|
1527
|
+
// #4905: an operator-facing date, so the local calendar day (#2136)
|
|
1528
|
+
// through the clock seam, which honors the GSD_NOW_MS pin (#474).
|
|
1529
|
+
const { realClock } = require('./lib/clock.cjs');
|
|
1530
|
+
const date = realClock.localToday();
|
|
1275
1531
|
const { execGit } = require('./lib/shell-command-projection.cjs');
|
|
1276
1532
|
const hashResult = execGit(['rev-parse', '--short', 'HEAD'], { cwd });
|
|
1277
1533
|
const commit = hashResult.exitCode === 0 && hashResult.stdout ? hashResult.stdout : '—';
|
|
@@ -1302,6 +1558,7 @@ function dispatchOverlayCapabilityCommand({ command, args, cwd, raw, error, load
|
|
|
1302
1558
|
description: qtaTask,
|
|
1303
1559
|
date,
|
|
1304
1560
|
commit,
|
|
1561
|
+
status: qtaStatus,
|
|
1305
1562
|
quickId: qtaQuickId,
|
|
1306
1563
|
directory: qtaDirectory,
|
|
1307
1564
|
});
|
|
@@ -2524,6 +2781,63 @@ function dispatchOverlayCapabilityCommand({ command, args, cwd, raw, error, load
|
|
|
2524
2781
|
};
|
|
2525
2782
|
try {
|
|
2526
2783
|
nodeFs.mkdirSync(sentinelDir, { recursive: true });
|
|
2784
|
+
// Never write through a project-supplied .gsd symlink. Resolve the
|
|
2785
|
+
// directory as well so an ancestor redirect cannot escape this project.
|
|
2786
|
+
if (nodeFs.lstatSync(sentinelDir).isSymbolicLink() ||
|
|
2787
|
+
nodePath.relative(nodeFs.realpathSync(cwd), nodeFs.realpathSync(sentinelDir)) !== '.gsd') {
|
|
2788
|
+
throw new Error('sentinel directory is a symlink or resolves outside the project');
|
|
2789
|
+
}
|
|
2790
|
+
// User projects do not inherit gsd-core's root .gitignore. Ignore this
|
|
2791
|
+
// tool-owned directory locally before writing the first sentinel. An
|
|
2792
|
+
// ordinary ignore-file IO failure must not prevent recording isolation.
|
|
2793
|
+
const ignorePath = nodePath.join(sentinelDir, '.gitignore');
|
|
2794
|
+
try {
|
|
2795
|
+
nodeFs.writeFileSync(ignorePath, '*\n', { flag: 'wx' });
|
|
2796
|
+
} catch (ignoreError) {
|
|
2797
|
+
if (ignoreError.code === 'EEXIST') {
|
|
2798
|
+
// Keep a pre-existing ignore file. A bare '*' already ignores the
|
|
2799
|
+
// whole directory, including this file and the sentinel temp file.
|
|
2800
|
+
// Read through an fd opened without following links where supported;
|
|
2801
|
+
// replace by rename rather than append through the path, so a link
|
|
2802
|
+
// swapped in after the check cannot redirect a write elsewhere.
|
|
2803
|
+
let fd;
|
|
2804
|
+
let ignoreTmpPath;
|
|
2805
|
+
try {
|
|
2806
|
+
if (nodeFs.lstatSync(ignorePath).isSymbolicLink()) {
|
|
2807
|
+
throw new Error('sentinel ignore file is a symlink');
|
|
2808
|
+
}
|
|
2809
|
+
fd = nodeFs.openSync(ignorePath, nodeFs.constants.O_RDONLY | (nodeFs.constants.O_NOFOLLOW || 0));
|
|
2810
|
+
if (!nodeFs.fstatSync(fd).isFile()) throw new Error('sentinel ignore file is not a regular file');
|
|
2811
|
+
const current = nodeFs.readFileSync(fd, 'utf8');
|
|
2812
|
+
nodeFs.closeSync(fd);
|
|
2813
|
+
fd = undefined;
|
|
2814
|
+
if (!current.includes('# gsd-core dispatch sentinel') && !/^\*\r?$/m.test(current)) {
|
|
2815
|
+
const additionalRules = `${current.endsWith('\n') ? '' : '\n'}# gsd-core dispatch sentinel\n` +
|
|
2816
|
+
'/dispatch-isolation-sentinel.json\n' +
|
|
2817
|
+
'/dispatch-isolation-sentinel.json.tmp-*\n' +
|
|
2818
|
+
'/.gitignore\n';
|
|
2819
|
+
ignoreTmpPath = `${ignorePath}.tmp-${process.pid}-${require('crypto').randomBytes(6).toString('hex')}`;
|
|
2820
|
+
nodeFs.writeFileSync(ignoreTmpPath, current + additionalRules, { flag: 'wx' });
|
|
2821
|
+
if (nodeFs.lstatSync(ignorePath).isSymbolicLink()) {
|
|
2822
|
+
throw new Error('sentinel ignore file is a symlink');
|
|
2823
|
+
}
|
|
2824
|
+
nodeFs.renameSync(ignoreTmpPath, ignorePath);
|
|
2825
|
+
ignoreTmpPath = null;
|
|
2826
|
+
}
|
|
2827
|
+
} catch (existingIgnoreError) {
|
|
2828
|
+
if (existingIgnoreError.code === 'ELOOP' || existingIgnoreError.message === 'sentinel ignore file is a symlink') {
|
|
2829
|
+
throw new Error('sentinel ignore file is a symlink');
|
|
2830
|
+
}
|
|
2831
|
+
// Ignore seeding is best-effort; the sentinel is still useful if
|
|
2832
|
+
// this path cannot be read or updated.
|
|
2833
|
+
} finally {
|
|
2834
|
+
if (fd !== undefined) nodeFs.closeSync(fd);
|
|
2835
|
+
if (ignoreTmpPath) {
|
|
2836
|
+
try { nodeFs.unlinkSync(ignoreTmpPath); } catch (_) { /* best-effort cleanup */ }
|
|
2837
|
+
}
|
|
2838
|
+
}
|
|
2839
|
+
}
|
|
2840
|
+
}
|
|
2527
2841
|
// Atomic write: unique temp file + rename, so a concurrent reader (a
|
|
2528
2842
|
// guard hook firing mid-write) never observes a partially-written
|
|
2529
2843
|
// sentinel. Unique per-process+time so concurrent orchestrator-worktree
|
|
@@ -2834,8 +3148,13 @@ function dispatchOverlayCapabilityCommand({ command, args, cwd, raw, error, load
|
|
|
2834
3148
|
const coverage = require('./lib/coverage.cjs');
|
|
2835
3149
|
const options = parseNamedArgsOrExit(args, { valueFlags: ['summary', 'file'], positionals: 2 }, error);
|
|
2836
3150
|
coverage.cmdClassify(cwd, options, raw);
|
|
3151
|
+
} else if (subcommand === 'complete-session') {
|
|
3152
|
+
const uat = require('./lib/uat.cjs');
|
|
3153
|
+
const uatPath = args[2];
|
|
3154
|
+
const options = parseNamedArgsOrExit(args, { valueFlags: ['message'], positionals: 3 }, error);
|
|
3155
|
+
return uat.cmdUatCompleteSession(cwd, uatPath, { message: options.message }, raw);
|
|
2837
3156
|
} else {
|
|
2838
|
-
error('Unknown uat subcommand. Available: render-checkpoint, classify-coverage', ERROR_REASON.SDK_UNKNOWN_COMMAND);
|
|
3157
|
+
error('Unknown uat subcommand. Available: render-checkpoint, classify-coverage, complete-session', ERROR_REASON.SDK_UNKNOWN_COMMAND);
|
|
2839
3158
|
}
|
|
2840
3159
|
}
|
|
2841
3160
|
|
|
@@ -2933,10 +3252,27 @@ function dispatchOverlayCapabilityCommand({ command, args, cwd, raw, error, load
|
|
|
2933
3252
|
}
|
|
2934
3253
|
loopRuntime = value;
|
|
2935
3254
|
}
|
|
3255
|
+
// --after-fingerprint <phaseDir> (#5105 R2): gate out verify:post
|
|
3256
|
+
// steps whose declared artifact already exists in phaseDir.
|
|
3257
|
+
let loopAfterFingerprint = undefined;
|
|
3258
|
+
const afterFpEqArg = args.find(arg => arg.startsWith('--after-fingerprint='));
|
|
3259
|
+
const afterFpIdx = args.indexOf('--after-fingerprint');
|
|
3260
|
+
if (afterFpEqArg) {
|
|
3261
|
+
const value = afterFpEqArg.slice('--after-fingerprint='.length).trim();
|
|
3262
|
+
if (!value) error('Missing value for --after-fingerprint', ERROR_REASON ? ERROR_REASON.USAGE : undefined);
|
|
3263
|
+
loopAfterFingerprint = value;
|
|
3264
|
+
} else if (afterFpIdx !== -1) {
|
|
3265
|
+
const value = args[afterFpIdx + 1];
|
|
3266
|
+
if (!value || value.startsWith('--')) {
|
|
3267
|
+
error('Missing value for --after-fingerprint', ERROR_REASON ? ERROR_REASON.USAGE : undefined);
|
|
3268
|
+
}
|
|
3269
|
+
loopAfterFingerprint = value;
|
|
3270
|
+
}
|
|
2936
3271
|
loopResolver.cmdLoopRenderHooks(cwd, args[2], raw, {
|
|
2937
3272
|
configDir: loopConfigDir ? path.resolve(loopConfigDir) : undefined,
|
|
2938
3273
|
activeCap: loopActiveCap,
|
|
2939
3274
|
runtime: loopRuntime,
|
|
3275
|
+
afterFingerprint: loopAfterFingerprint,
|
|
2940
3276
|
});
|
|
2941
3277
|
} else {
|
|
2942
3278
|
error(
|
|
@@ -3006,6 +3342,12 @@ function dispatchOverlayCapabilityCommand({ command, args, cwd, raw, error, load
|
|
|
3006
3342
|
worktreeSafety.cmdWorktreeRecordAgent(cwd, args.slice(2));
|
|
3007
3343
|
} else if (subcommand === 'reap-orphans') {
|
|
3008
3344
|
worktreeSafety.cmdWorktreeReapOrphans(cwd);
|
|
3345
|
+
} else if (subcommand === 'worker-record') {
|
|
3346
|
+
worktreeSafety.cmdWorktreeWorkerRecord(cwd, args.slice(2));
|
|
3347
|
+
} else if (subcommand === 'worker-status') {
|
|
3348
|
+
worktreeSafety.cmdWorktreeWorkerStatus(cwd, args.slice(2));
|
|
3349
|
+
} else if (subcommand === 'worker-complete') {
|
|
3350
|
+
worktreeSafety.cmdWorktreeWorkerComplete(cwd, args.slice(2));
|
|
3009
3351
|
} else if (subcommand === 'base-check') {
|
|
3010
3352
|
require('./lib/worktree-base-ref.cjs').cmdWorktreeBaseCheck(cwd, args.slice(2));
|
|
3011
3353
|
} else if (subcommand === 'set-baseref') {
|
|
@@ -3013,7 +3355,7 @@ function dispatchOverlayCapabilityCommand({ command, args, cwd, raw, error, load
|
|
|
3013
3355
|
} else if (subcommand === 'create') {
|
|
3014
3356
|
worktreeSafety.cmdWorktreeCreate(cwd, args.slice(2));
|
|
3015
3357
|
} else {
|
|
3016
|
-
error('Unknown worktree subcommand. Available: cleanup-wave, record-agent, reap-orphans, base-check, set-baseref, create', ERROR_REASON.SDK_UNKNOWN_COMMAND);
|
|
3358
|
+
error('Unknown worktree subcommand. Available: cleanup-wave, record-agent, reap-orphans, base-check, set-baseref, create, worker-record, worker-status, worker-complete', ERROR_REASON.SDK_UNKNOWN_COMMAND);
|
|
3017
3359
|
}
|
|
3018
3360
|
}
|
|
3019
3361
|
|
|
@@ -3472,13 +3814,14 @@ function dispatchOverlayCapabilityCommand({ command, args, cwd, raw, error, load
|
|
|
3472
3814
|
|| relPath.startsWith('agents/')
|
|
3473
3815
|
|| relPath.startsWith('commands/');
|
|
3474
3816
|
if (isFrontmatterSurface) {
|
|
3475
|
-
|
|
3817
|
+
// The block is the one the one fence owner finds (found while implementing #5105).
|
|
3818
|
+
const found = frontmatter.frontmatterRegion(content);
|
|
3476
3819
|
const missingFields = [];
|
|
3477
|
-
if (!
|
|
3820
|
+
if (!found || !found.terminated) {
|
|
3478
3821
|
missingFields.push('name', 'description');
|
|
3479
3822
|
} else {
|
|
3480
|
-
if (!/^name:\s*\S/m.test(
|
|
3481
|
-
if (!/^description:\s*\S/m.test(
|
|
3823
|
+
if (!/^name:\s*\S/m.test(found.region)) missingFields.push('name');
|
|
3824
|
+
if (!/^description:\s*\S/m.test(found.region)) missingFields.push('description');
|
|
3482
3825
|
}
|
|
3483
3826
|
if (missingFields.length > 0) {
|
|
3484
3827
|
warnings.push({
|
|
@@ -4262,7 +4605,19 @@ function dispatchOverlayCapabilityCommand({ command, args, cwd, raw, error, load
|
|
|
4262
4605
|
}
|
|
4263
4606
|
|
|
4264
4607
|
const roadmapStatus = matchedRow.Status;
|
|
4265
|
-
|
|
4608
|
+
// #5060: normalize the raw Status cell through the Phase Status
|
|
4609
|
+
// Module's `matchRoadmapStatusCell` owner before comparing ranks —
|
|
4610
|
+
// this recognizes a cell with trailing operator prose after its
|
|
4611
|
+
// leading token (e.g. "Complete — shipped") the same way the
|
|
4612
|
+
// ROADMAP writers/readers already do, rather than requiring the
|
|
4613
|
+
// WHOLE cell to equal a bare token. The raw `roadmapStatus` in the
|
|
4614
|
+
// output stays untouched — only the comparison input changes.
|
|
4615
|
+
const { matchRoadmapStatusCell } = require('./lib/phase-status.cjs');
|
|
4616
|
+
const matched = matchRoadmapStatusCell(roadmapStatus);
|
|
4617
|
+
const result = comparePhaseStatus({
|
|
4618
|
+
stateStatus,
|
|
4619
|
+
roadmapStatus: matched ? matched.token.toLowerCase() : roadmapStatus,
|
|
4620
|
+
});
|
|
4266
4621
|
output({
|
|
4267
4622
|
verdict: result.verdict,
|
|
4268
4623
|
phase,
|
|
@@ -4407,6 +4762,8 @@ const HOST_COMMAND_ROUTERS = {
|
|
|
4407
4762
|
'smart-entry': routeSmartEntry,
|
|
4408
4763
|
'check': routeCheck,
|
|
4409
4764
|
'find-phase': routeFindPhase,
|
|
4765
|
+
// #4906 Phase 5 (#4661): commit-scope selection for /gsd:undo --phase/--plan.
|
|
4766
|
+
'select-revert-commits': routeSelectRevertCommits,
|
|
4410
4767
|
'commit': routeCommit,
|
|
4411
4768
|
'check-commit': routeCheckCommit,
|
|
4412
4769
|
'commit-docs-guard': routeCommitDocsGuard,
|
|
@@ -4484,7 +4841,7 @@ const HOST_COMMAND_ROUTERS = {
|
|
|
4484
4841
|
// through. Prototype-pollution-safe: own-property lookup rejects
|
|
4485
4842
|
// `__proto__`/`constructor`/`prototype` command keys (same guard as
|
|
4486
4843
|
// dispatchCapabilityCommand).
|
|
4487
|
-
async function dispatchHostCommand({ command, args, cwd, raw, error, defaultValue, workstreamContext }) {
|
|
4844
|
+
async function dispatchHostCommand({ command, args, cwd, raw, error, defaultValue, workstreamContext, invokingCwd }) {
|
|
4488
4845
|
if (
|
|
4489
4846
|
command === '__proto__' ||
|
|
4490
4847
|
command === 'constructor' ||
|
|
@@ -4499,7 +4856,13 @@ async function dispatchHostCommand({ command, args, cwd, raw, error, defaultValu
|
|
|
4499
4856
|
if (typeof router !== 'function') return false;
|
|
4500
4857
|
// `await` so async host routers (e.g. capability's install/upgrade ops)
|
|
4501
4858
|
// complete before runCommand returns; sync routers pass through unchanged.
|
|
4502
|
-
|
|
4859
|
+
// `invokingCwd` (#4906/#4465): the pre-worktree-remap cwd, for the rare
|
|
4860
|
+
// router (currently only `select-revert-commits`) whose own git operation
|
|
4861
|
+
// must see the INVOKING worktree's HEAD, never the main-worktree remap
|
|
4862
|
+
// every other router legitimately wants via `cwd`. Optional — callers/tests
|
|
4863
|
+
// that omit it (every existing one) leave routers that don't read it
|
|
4864
|
+
// byte-unaffected.
|
|
4865
|
+
await router({ args, cwd, raw, error, defaultValue, workstreamContext, invokingCwd });
|
|
4503
4866
|
return true; // consumed — don't emit "Unknown command"
|
|
4504
4867
|
}
|
|
4505
4868
|
|
|
@@ -4532,7 +4895,7 @@ async function dispatchHostCommand({ command, args, cwd, raw, error, defaultValu
|
|
|
4532
4895
|
// keep working. No shell is spawned (argv array) — no injection surface beyond
|
|
4533
4896
|
// the old `timeout … bash -c "$CMD"`.
|
|
4534
4897
|
function runWithTimeout(argv) {
|
|
4535
|
-
const { spawn } = require('node:child_process');
|
|
4898
|
+
const { spawn, spawnSync } = require('node:child_process');
|
|
4536
4899
|
const os = require('node:os');
|
|
4537
4900
|
|
|
4538
4901
|
const USAGE = 'Usage: gsd_run run-with-timeout <seconds> [--] <command> [args...]';
|
|
@@ -4557,9 +4920,11 @@ function runWithTimeout(argv) {
|
|
|
4557
4920
|
|
|
4558
4921
|
const isWin = process.platform === 'win32';
|
|
4559
4922
|
// Detached (own process group) on POSIX so a timeout can reap the WHOLE tree —
|
|
4560
|
-
// a bare child.kill() misses grandchildren (e.g. a test runner's workers)
|
|
4561
|
-
//
|
|
4562
|
-
//
|
|
4923
|
+
// a bare child.kill() misses grandchildren (e.g. a test runner's workers).
|
|
4924
|
+
// Windows has no POSIX process groups and process.kill(-pid) is unsupported
|
|
4925
|
+
// there, so EVERY killTree attempt on Windows tree-kills via
|
|
4926
|
+
// `taskkill /PID <pid> /T /F` while the root is alive (see killTree) — by the
|
|
4927
|
+
// time the direct child exits, its descendants are already orphaned.
|
|
4563
4928
|
const detached = !isWin && secs > 0;
|
|
4564
4929
|
const spawnFailureCode = (err) =>
|
|
4565
4930
|
(err && err.code === 'ENOENT' ? 127 : err && err.code === 'EACCES' ? 126 : 125);
|
|
@@ -4583,9 +4948,26 @@ function runWithTimeout(argv) {
|
|
|
4583
4948
|
// cap's process-group kill (the wrapped child escapes reap → exit 124 never
|
|
4584
4949
|
// fires) and risks cmd.exe mis-parsing an arg like `-e "setTimeout(()=>{})"`.
|
|
4585
4950
|
// Only .cmd/.bat are the CVE-2024-27980 EINVAL cases that require mediation.
|
|
4951
|
+
// #4797: the mediation is NOT hand-rolled here — the private `/d /s /c <cmd>
|
|
4952
|
+
// ...args` copy broke on any shim path containing a space (Node quotes the
|
|
4953
|
+
// argv token; `/s` strips the FIRST and LAST quote of the /c string, so
|
|
4954
|
+
// cmd.exe took the pre-space fragment as the program). projectSpawnInvocation
|
|
4955
|
+
// (the declared single owner, #3411/#3617) wraps the WHOLE command line in
|
|
4956
|
+
// one extra quote pair with windowsVerbatimArguments — the shape that
|
|
4957
|
+
// survives spaces. One behavior delta, accepted: the seam DECLINES mediation
|
|
4958
|
+
// when the target or an arg carries CR/LF (the old block mediated anyway) —
|
|
4959
|
+
// the unmediated spawn of a .cmd then fails EINVAL, loud, at the catch below.
|
|
4586
4960
|
const winShim = isWin && /\.(cmd|bat)$/i.test(path.basename(cmd));
|
|
4587
|
-
|
|
4588
|
-
|
|
4961
|
+
let spawnCmd = cmd;
|
|
4962
|
+
let spawnArgs = cmdArgs;
|
|
4963
|
+
let spawnOpts;
|
|
4964
|
+
if (winShim) {
|
|
4965
|
+
const { projectSpawnInvocation } = require('./lib/shell-command-projection.cjs');
|
|
4966
|
+
const inv = projectSpawnInvocation(cmd, cmdArgs);
|
|
4967
|
+
spawnCmd = inv.command;
|
|
4968
|
+
spawnArgs = inv.args;
|
|
4969
|
+
if (inv.windowsVerbatimArguments) spawnOpts = { windowsVerbatimArguments: true };
|
|
4970
|
+
}
|
|
4589
4971
|
// Node's setTimeout delay is a 32-bit signed ms int; a larger value silently
|
|
4590
4972
|
// clamps to 1ms → a spurious immediate timeout. Cap the budget (~24.8 days).
|
|
4591
4973
|
const timerMs = Math.min(Math.round(secs * 1000), 2 ** 31 - 1);
|
|
@@ -4596,11 +4978,13 @@ function runWithTimeout(argv) {
|
|
|
4596
4978
|
return new Promise((resolve) => {
|
|
4597
4979
|
let child;
|
|
4598
4980
|
try {
|
|
4599
|
-
// #2667: on win32 `.cmd`/`.bat
|
|
4600
|
-
//
|
|
4601
|
-
//
|
|
4602
|
-
//
|
|
4603
|
-
|
|
4981
|
+
// #2667: on win32 `.cmd`/`.bat` shims, spawn cmd.exe with an explicit
|
|
4982
|
+
// argv ARRAY rather than the shim directly — preserves the array-only,
|
|
4983
|
+
// no-shell-string argv contract. #4797: the exact argv shape (quote
|
|
4984
|
+
// wrapping, verbatim arguments) is projected by projectSpawnInvocation —
|
|
4985
|
+
// see the block above. `detached` is always false on win32, so it never
|
|
4986
|
+
// co-occurs with the cmd.exe mediation.
|
|
4987
|
+
child = spawn(spawnCmd, spawnArgs, { stdio: 'inherit', detached, ...spawnOpts });
|
|
4604
4988
|
} catch (err) {
|
|
4605
4989
|
process.stderr.write(`run-with-timeout: ${cmd}: ${err && err.message ? err.message : 'failed to start'}\n`);
|
|
4606
4990
|
resolve(spawnFailureCode(err));
|
|
@@ -4612,6 +4996,27 @@ function runWithTimeout(argv) {
|
|
|
4612
4996
|
if (detached && child.pid) {
|
|
4613
4997
|
try { process.kill(-child.pid, signal); return; } catch { /* group already gone */ }
|
|
4614
4998
|
}
|
|
4999
|
+
if (isWin && child.pid) {
|
|
5000
|
+
// #4601: Windows has no POSIX process groups, so the tree kill rides
|
|
5001
|
+
// on `taskkill /T`, which walks the child's descendants the way
|
|
5002
|
+
// `process.kill(-pid)` reaches a POSIX group — this is what bounds
|
|
5003
|
+
// the wall clock when the direct child mediates (cmd.exe /c shim) or
|
|
5004
|
+
// spawns its own children. Deliberately NOT gated on the SIGKILL
|
|
5005
|
+
// stage: child.kill on Windows is TerminateProcess regardless of
|
|
5006
|
+
// signal, so by the time the direct child exits its descendants are
|
|
5007
|
+
// orphaned and no taskkill can reach them — the tree kill must ride
|
|
5008
|
+
// the FIRST attempt, while the root is still alive. /F is required:
|
|
5009
|
+
// without it taskkill posts WM_CLOSE, which a headless CLI never
|
|
5010
|
+
// pumps. Spawned as an argv array per the no-shell-for-argv-array
|
|
5011
|
+
// contract, and bounded — a non-zero/absent status means taskkill
|
|
5012
|
+
// lost a race with an exiting process, and we fall through to the
|
|
5013
|
+
// direct kill so the attempt is never weaker than before.
|
|
5014
|
+
const reap = spawnSync('taskkill', ['/PID', String(child.pid), '/T', '/F'], {
|
|
5015
|
+
encoding: 'utf8',
|
|
5016
|
+
timeout: 15000, // taskkill /T is sub-second in practice; bounded so a wedged taskkill can't hang the gate
|
|
5017
|
+
});
|
|
5018
|
+
if (reap.status === 0) return;
|
|
5019
|
+
}
|
|
4615
5020
|
child.kill(signal);
|
|
4616
5021
|
} catch { /* already exited */ }
|
|
4617
5022
|
};
|
|
@@ -4694,7 +5099,7 @@ const TOP_LEVEL_USAGE = 'Usage: gsd-tools <command> [args] [--raw] [--pick <fiel
|
|
|
4694
5099
|
'capability, classify-confidence, git, learnings, list-seeds, list-todos, loop, milestone, package-legitimacy, phase, phase-plan-index, phases, planning, profile-questionnaire, ' +
|
|
4695
5100
|
'profile-sample, progress, project-instruction-file, prompt-budget, quick-batch, quick-tasks-append, quick-tasks-migrate, requirements, research-plan, research-store, resolve-granularity, resolve-model, restore-custom-files, roadmap, runtime-identity, scaffold, smart-entry, state, ' +
|
|
4696
5101
|
'config-set-model-profile, dispatch-capacity, dispatch-isolation, dispatch-should-flatten, inspect-dispatch-isolation, record-dispatch-isolation, estimate-calibrate, estimate-calibration, estimate-check, resolve-agent, resolve-dispatch-type, ' +
|
|
4697
|
-
'resolve-execution, review-lane, skill-manifest, skills-root, stamp-codebase-map, state-snapshot, stats, summary-extract, teams-status, todo, uat, update-context, verification, websearch, windows, ' +
|
|
5102
|
+
'resolve-execution, review-lane, select-revert-commits, skill-manifest, skills-root, stamp-codebase-map, state-snapshot, stats, summary-extract, teams-status, todo, uat, update-context, verification, websearch, windows, ' +
|
|
4698
5103
|
'task, template, user-story, validate, verify, verify-path-exists, verify-summary, eval, workstream, worktree\n\n' +
|
|
4699
5104
|
'Global flags:\n' +
|
|
4700
5105
|
' --raw Emit raw output without post-processing\n' +
|
|
@@ -4794,6 +5199,28 @@ function resolveMainWorktreeCwd(cwd, deps = {}) {
|
|
|
4794
5199
|
return worktreeRoot;
|
|
4795
5200
|
}
|
|
4796
5201
|
|
|
5202
|
+
// ─── #5118: an out-of-set verification status thrown past a command ─────────
|
|
5203
|
+
// ADR-5057 Phase 4 closed the verification-status vocabulary: a
|
|
5204
|
+
// *-VERIFICATION.md whose frontmatter `status` is outside `passed |
|
|
5205
|
+
// gaps_found | human_needed` is a hard error (verification.cjs's
|
|
5206
|
+
// VerificationStatusError). A command that reads one report directly
|
|
5207
|
+
// (`verification status`, `phase uat-passed`, `phase complete`) lets it throw;
|
|
5208
|
+
// aggregates carry it in their own results and fail themselves. Either way
|
|
5209
|
+
// the CLI fails through the owner's `failOnVerificationStatusError` — the
|
|
5210
|
+
// error's own message and its own `.reason`; nothing here restates either.
|
|
5211
|
+
function isVerificationStatusError(err) {
|
|
5212
|
+
return err instanceof verification.VerificationStatusError;
|
|
5213
|
+
}
|
|
5214
|
+
|
|
5215
|
+
async function captureTranslatingVerificationStatus(run) {
|
|
5216
|
+
try {
|
|
5217
|
+
return await captureStdoutSyncWrites(run);
|
|
5218
|
+
} catch (err) {
|
|
5219
|
+
if (isVerificationStatusError(err)) verification.failOnVerificationStatusError(err);
|
|
5220
|
+
throw err;
|
|
5221
|
+
}
|
|
5222
|
+
}
|
|
5223
|
+
|
|
4797
5224
|
async function main() {
|
|
4798
5225
|
let args = process.argv.slice(2);
|
|
4799
5226
|
|
|
@@ -4916,10 +5343,25 @@ async function main() {
|
|
|
4916
5343
|
cwd = resolvedProjectDir;
|
|
4917
5344
|
projectDirExplicit = true;
|
|
4918
5345
|
}
|
|
5346
|
+
// #4894: verification derives its root from a phase directory, not `cwd`, so
|
|
5347
|
+
// hand it the validated explicit root. Always (re)set — `null` when the flag is
|
|
5348
|
+
// absent — so a prior in-process main() call can never leak an override.
|
|
5349
|
+
projectRoot.setExplicitProjectRoot(projectDirExplicit ? cwd : null);
|
|
4919
5350
|
|
|
4920
5351
|
// Resolve worktree root: in a linked worktree, .planning/ lives in the main worktree.
|
|
4921
5352
|
// However, in monorepo worktrees where the subdirectory itself owns .planning/,
|
|
4922
5353
|
// skip worktree resolution — the CWD is already the correct project root.
|
|
5354
|
+
//
|
|
5355
|
+
// #4906/#4465: this remap is a BLANKET rewrite applied to every command, before
|
|
5356
|
+
// `command` is even known — most commands need it (they read `.planning/`, which
|
|
5357
|
+
// in a linked worktree lives only in the main one). A command whose own git
|
|
5358
|
+
// operation must run against the INVOKING worktree's OWN HEAD/branch (never the
|
|
5359
|
+
// main worktree's) needs the PRE-remap value instead — preserved here so it can be
|
|
5360
|
+
// threaded through dispatchHostCommand as `invokingCwd` (see routeSelectRevertCommits,
|
|
5361
|
+
// the first consumer: its `git log` must see the caller's own reachable history, not
|
|
5362
|
+
// main's, or a linked-worktree revert range silently resolves against the wrong repo
|
|
5363
|
+
// state — reproduced in tests/undo-commit-selection-4465.test.cjs's linked-worktree case).
|
|
5364
|
+
const preWorktreeRemapCwd = cwd;
|
|
4923
5365
|
cwd = resolveMainWorktreeCwd(cwd);
|
|
4924
5366
|
|
|
4925
5367
|
// Optional workstream override for parallel milestone work.
|
|
@@ -5044,8 +5486,8 @@ async function main() {
|
|
|
5044
5486
|
// themselves JSON text, so resolving late would make every large result a
|
|
5045
5487
|
// false "output was not JSON" (negative space N8).
|
|
5046
5488
|
if (pickField) {
|
|
5047
|
-
const captured = await
|
|
5048
|
-
await runCommand(command, args, cwd, raw, defaultValue, originalCommand, workstreamContext);
|
|
5489
|
+
const captured = await captureTranslatingVerificationStatus(async () => {
|
|
5490
|
+
await runCommand(command, args, cwd, raw, defaultValue, originalCommand, workstreamContext, preWorktreeRemapCwd);
|
|
5049
5491
|
});
|
|
5050
5492
|
const resolved = resolveAtFileOutput(captured);
|
|
5051
5493
|
let obj;
|
|
@@ -5076,58 +5518,15 @@ async function main() {
|
|
|
5076
5518
|
// already resolves this, but the normal path wrote @file: to stdout, forcing
|
|
5077
5519
|
// every workflow to have a bash-specific `if [[ "$INIT" == @file:* ]]` check
|
|
5078
5520
|
// that breaks on PowerShell and other non-bash shells.
|
|
5079
|
-
const captured = await
|
|
5080
|
-
await runCommand(command, args, cwd, raw, defaultValue, originalCommand, workstreamContext);
|
|
5521
|
+
const captured = await captureTranslatingVerificationStatus(async () => {
|
|
5522
|
+
await runCommand(command, args, cwd, raw, defaultValue, originalCommand, workstreamContext, preWorktreeRemapCwd);
|
|
5081
5523
|
});
|
|
5082
5524
|
fs.writeSync(1, resolveAtFileOutput(captured));
|
|
5083
5525
|
}
|
|
5084
5526
|
|
|
5085
|
-
|
|
5086
|
-
|
|
5087
|
-
|
|
5088
|
-
|
|
5089
|
-
fs.writeSync = function patchedWriteSync(fd, data, ...rest) {
|
|
5090
|
-
if (fd === 1) {
|
|
5091
|
-
if (Buffer.isBuffer(data)) {
|
|
5092
|
-
captured += data.toString('utf-8');
|
|
5093
|
-
return data.length;
|
|
5094
|
-
}
|
|
5095
|
-
const text = String(data);
|
|
5096
|
-
captured += text;
|
|
5097
|
-
let encoding = 'utf-8';
|
|
5098
|
-
if (typeof rest[1] === 'string') encoding = rest[1];
|
|
5099
|
-
return Buffer.byteLength(text, encoding);
|
|
5100
|
-
}
|
|
5101
|
-
return originalWriteSync.call(fs, fd, data, ...rest);
|
|
5102
|
-
};
|
|
5103
|
-
|
|
5104
|
-
const restore = () => {
|
|
5105
|
-
fs.writeSync = originalWriteSync;
|
|
5106
|
-
};
|
|
5107
|
-
|
|
5108
|
-
return Promise.resolve()
|
|
5109
|
-
.then(() => run())
|
|
5110
|
-
.then(() => {
|
|
5111
|
-
restore();
|
|
5112
|
-
return captured;
|
|
5113
|
-
}, (err) => {
|
|
5114
|
-
restore();
|
|
5115
|
-
// The wrapped command may have written to stdout BEFORE it threw — e.g. a --raw
|
|
5116
|
-
// command that emits a JSON result/error envelope and THEN throws ExitError to set a
|
|
5117
|
-
// non-zero exit code (capability set/disable on an unknown id). Without this flush that
|
|
5118
|
-
// captured output is silently discarded (the success-path flush at the call site never
|
|
5119
|
-
// runs on a throw). Emit it now; the error still propagates so the exit code is preserved.
|
|
5120
|
-
if (captured) {
|
|
5121
|
-
try { originalWriteSync.call(fs, 1, resolveAtFileOutput(captured)); } catch { /* best-effort flush */ }
|
|
5122
|
-
}
|
|
5123
|
-
throw err;
|
|
5124
|
-
});
|
|
5125
|
-
}
|
|
5126
|
-
|
|
5127
|
-
function resolveAtFileOutput(captured) {
|
|
5128
|
-
if (!captured.startsWith('@file:')) return captured;
|
|
5129
|
-
return fs.readFileSync(captured.slice(6), 'utf-8');
|
|
5130
|
-
}
|
|
5527
|
+
// captureStdoutSyncWrites and resolveAtFileOutput moved to src/io.cts
|
|
5528
|
+
// (gsd-core/bin/lib/io.cjs) — the ONE shared pair (#5105 S9, review finding
|
|
5529
|
+
// 7), also used by uat.cts's cmdUatCompleteSession.
|
|
5131
5530
|
|
|
5132
5531
|
// A plain object root/intermediate value — everything else (null, an array,
|
|
5133
5532
|
// a number, a string, a boolean) is treated as non-object for NAMED-key
|
|
@@ -5193,7 +5592,7 @@ function extractField(obj, fieldPath) {
|
|
|
5193
5592
|
return { found: true, value: current };
|
|
5194
5593
|
}
|
|
5195
5594
|
|
|
5196
|
-
async function runCommand(command, args, cwd, raw, defaultValue, originalCommand, workstreamContext = null) {
|
|
5595
|
+
async function runCommand(command, args, cwd, raw, defaultValue, originalCommand, workstreamContext = null, invokingCwd) {
|
|
5197
5596
|
switch (command) {
|
|
5198
5597
|
|
|
5199
5598
|
default: {
|
|
@@ -5214,7 +5613,7 @@ async function runCommand(command, args, cwd, raw, defaultValue, originalCommand
|
|
|
5214
5613
|
// commands (state, …) routed via their `route*Command` router instead of
|
|
5215
5614
|
// a hardcoded `case` arm. Tried after capability/overlay dispatch and
|
|
5216
5615
|
// before the unknown-command error.
|
|
5217
|
-
if (await dispatchHostCommand({ command, args, cwd, raw, error, defaultValue, workstreamContext })) break;
|
|
5616
|
+
if (await dispatchHostCommand({ command, args, cwd, raw, error, defaultValue, workstreamContext, invokingCwd })) break;
|
|
5218
5617
|
|
|
5219
5618
|
// #3243: if the caller passed a dotted form (e.g. "foo.bar"), the shim
|
|
5220
5619
|
// above split it so `command` here is the head ("foo"). Use
|