@opengsd/gsd-core 1.10.0 → 1.11.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/agents/gsd-debug-session-manager.md +11 -0
- package/agents/gsd-doc-synthesizer.md +2 -4
- package/agents/gsd-executor.md +5 -5
- package/agents/gsd-mempalace-curator.md +5 -2
- package/agents/gsd-phase-researcher.md +20 -1
- package/agents/gsd-plan-checker.md +37 -0
- package/agents/gsd-planner.md +44 -46
- package/agents/gsd-user-profiler.md +3 -0
- package/agents/gsd-verifier.md +12 -3
- package/bin/install.js +841 -971
- package/bin/lib/ui-safety-gate.cjs +2 -0
- package/commands/gsd/code-review.md +1 -1
- package/commands/gsd/execute-phase.md +1 -1
- package/commands/gsd/map-codebase.md +1 -1
- package/commands/gsd/mempalace-capture.md +1 -1
- package/commands/gsd/mempalace-recall.md +1 -1
- package/commands/gsd/new-milestone.md +1 -1
- package/commands/gsd/quick.md +1 -1
- package/commands/gsd/review-backlog.md +2 -1
- package/commands/gsd/verify-work.md +1 -1
- package/gsd-core/bin/gsd-tools.cjs +469 -88
- package/gsd-core/bin/lib/active-workstream-store.cjs +138 -22
- package/gsd-core/bin/lib/agent-install-check.cjs +230 -32
- package/gsd-core/bin/lib/api-coverage.cjs +3 -5
- package/gsd-core/bin/lib/artifacts.cjs +3 -0
- package/gsd-core/bin/lib/assumption-delta.cjs +2 -4
- package/gsd-core/bin/lib/audit-command-router.cjs +9 -2
- package/gsd-core/bin/lib/audit.cjs +876 -240
- package/gsd-core/bin/lib/broken-windows.cjs +1 -1
- package/gsd-core/bin/lib/capability-consent.cjs +149 -15
- package/gsd-core/bin/lib/capability-lifecycle.cjs +45 -0
- package/gsd-core/bin/lib/capability-registry.cjs +575 -101
- package/gsd-core/bin/lib/capability-source.cjs +92 -0
- package/gsd-core/bin/lib/capability-trust.cjs +444 -25
- package/gsd-core/bin/lib/capability-validator.cjs +495 -22
- package/gsd-core/bin/lib/capability-writer.cjs +3 -2
- package/gsd-core/bin/lib/check-command-router.cjs +71 -37
- package/gsd-core/bin/lib/claude-orchestration.cjs +56 -3
- package/gsd-core/bin/lib/codex-agent-toml.cjs +329 -0
- package/gsd-core/bin/lib/command-aliases.cjs +22 -0
- package/gsd-core/bin/lib/command-roster.cjs +44 -1
- package/gsd-core/bin/lib/commands.cjs +651 -86
- package/gsd-core/bin/lib/commonjs-marker.cjs +12 -6
- package/gsd-core/bin/lib/complexity-trigger.cjs +1172 -0
- package/gsd-core/bin/lib/config-loader.cjs +75 -0
- package/gsd-core/bin/lib/config.cjs +10 -1
- package/gsd-core/bin/lib/core-utils.cjs +127 -29
- package/gsd-core/bin/lib/decisions.cjs +23 -0
- package/gsd-core/bin/lib/fallow-runner.cjs +20 -44
- package/gsd-core/bin/lib/frontmatter.cjs +155 -20
- package/gsd-core/bin/lib/gap-checker.cjs +68 -7
- package/gsd-core/bin/lib/git-base-branch.cjs +102 -0
- package/gsd-core/bin/lib/gsd2-import.cjs +10 -1
- package/gsd-core/bin/lib/health-diagnostic-rules/agent-install.cjs +101 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/config-validation.cjs +348 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/consistency.cjs +145 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/install-surface-shadowing.cjs +98 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/milestone-archive-hygiene.cjs +100 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/phase-structure.cjs +222 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/roadmap-disk-consistency.cjs +265 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/root-existence.cjs +161 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/state-consistency.cjs +303 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/worktree-health.cjs +173 -0
- package/gsd-core/bin/lib/health-diagnostic-types.cjs +68 -0
- package/gsd-core/bin/lib/health-diagnostic.cjs +431 -0
- package/gsd-core/bin/lib/host-runtime-detection.cjs +134 -0
- package/gsd-core/bin/lib/init.cjs +321 -129
- package/gsd-core/bin/lib/install-effort-resolver.cjs +73 -30
- package/gsd-core/bin/lib/install-engine.cjs +745 -258
- package/gsd-core/bin/lib/install-fs-adapter.cjs +262 -0
- package/gsd-core/bin/lib/install-model-override-resolver.cjs +203 -0
- package/gsd-core/bin/lib/install-profiles.cjs +134 -57
- package/gsd-core/bin/lib/install-scope.cjs +270 -0
- package/gsd-core/bin/lib/install-shadow-report.cjs +385 -0
- package/gsd-core/bin/lib/installed-surface-resolver.cjs +381 -0
- package/gsd-core/bin/lib/installer-migrations.cjs +138 -31
- package/gsd-core/bin/lib/io.cjs +10 -0
- package/gsd-core/bin/lib/markdown-sectionizer.cjs +2 -1
- package/gsd-core/bin/lib/markdown-table.cjs +133 -20
- package/gsd-core/bin/lib/milestone-lock.cjs +248 -0
- package/gsd-core/bin/lib/milestone.cjs +754 -70
- package/gsd-core/bin/lib/model-catalog.cjs +59 -1
- package/gsd-core/bin/lib/model-resolver.cjs +183 -40
- package/gsd-core/bin/lib/normalize-test-command.cjs +1 -1
- package/gsd-core/bin/lib/pattern.cjs +122 -0
- package/gsd-core/bin/lib/phase-estimation.cjs +1 -1
- package/gsd-core/bin/lib/phase-id.cjs +444 -36
- package/gsd-core/bin/lib/phase-lifecycle.cjs +28 -3
- package/gsd-core/bin/lib/phase-locator.cjs +125 -18
- package/gsd-core/bin/lib/phase.cjs +646 -143
- package/gsd-core/bin/lib/plan-dependency-graph.cjs +72 -1
- package/gsd-core/bin/lib/plan-drift-guard.cjs +120 -0
- package/gsd-core/bin/lib/plan-scan.cjs +86 -2
- package/gsd-core/bin/lib/planning-scope.cjs +31 -0
- package/gsd-core/bin/lib/planning-snapshot.cjs +890 -0
- package/gsd-core/bin/lib/planning-workspace.cjs +56 -6
- package/gsd-core/bin/lib/probe-core.cjs +1 -1
- package/gsd-core/bin/lib/profile-output.cjs +1 -1
- package/gsd-core/bin/lib/refactor-trigger-command-router.cjs +740 -0
- package/gsd-core/bin/lib/retired-artifact-cleanup.cjs +11 -6
- package/gsd-core/bin/lib/review-lane-descriptor.cjs +13 -4
- package/gsd-core/bin/lib/review-lane-invocation.cjs +30 -0
- package/gsd-core/bin/lib/review-lane-runner.cjs +421 -66
- package/gsd-core/bin/lib/review-reviewer-selection.cjs +13 -18
- package/gsd-core/bin/lib/roadmap-command-router.cjs +34 -0
- package/gsd-core/bin/lib/roadmap-parser.cjs +943 -184
- package/gsd-core/bin/lib/roadmap-upgrade.cjs +37 -10
- package/gsd-core/bin/lib/roadmap.cjs +385 -94
- package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +608 -46
- package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +14 -2
- package/gsd-core/bin/lib/runtime-artifact-layout.cjs +426 -55
- package/gsd-core/bin/lib/runtime-config-adapter-registry.cjs +3 -2
- package/gsd-core/bin/lib/runtime-homes.cjs +69 -3
- package/gsd-core/bin/lib/runtime-hooks-surface.cjs +115 -3
- package/gsd-core/bin/lib/runtime-name-policy.cjs +3 -1
- package/gsd-core/bin/lib/runtime-slash.cjs +27 -9
- package/gsd-core/bin/lib/security.cjs +104 -5
- package/gsd-core/bin/lib/shell-command-projection.cjs +275 -3
- package/gsd-core/bin/lib/smart-entry.cjs +142 -22
- package/gsd-core/bin/lib/state-command-router.cjs +5 -1
- package/gsd-core/bin/lib/state-document.cjs +152 -8
- package/gsd-core/bin/lib/state-transition.cjs +371 -117
- package/gsd-core/bin/lib/state.cjs +1794 -357
- package/gsd-core/bin/lib/surface.cjs +23 -9
- package/gsd-core/bin/lib/text-lines.cjs +80 -0
- package/gsd-core/bin/lib/token-scanner.cjs +76 -0
- package/gsd-core/bin/lib/uat-predicate.cjs +9 -3
- package/gsd-core/bin/lib/uat.cjs +399 -56
- package/gsd-core/bin/lib/ui-frontend-evidence.cjs +157 -0
- package/gsd-core/bin/lib/ui-safety-gate.cjs +14 -5
- package/gsd-core/bin/lib/unusable-input.cjs +24 -0
- package/gsd-core/bin/lib/update-context.cjs +8 -2
- package/gsd-core/bin/lib/user-artifact-staging.cjs +705 -0
- package/gsd-core/bin/lib/validate.cjs +20 -6
- package/gsd-core/bin/lib/vendor/README.md +37 -0
- package/gsd-core/bin/lib/vendor/re2js.cjs +6480 -0
- package/gsd-core/bin/lib/vendor/re2js.d.cts +938 -0
- package/gsd-core/bin/lib/verification-command-router.cjs +2 -1
- package/gsd-core/bin/lib/verification.cjs +258 -8
- package/gsd-core/bin/lib/verify.cjs +368 -888
- package/gsd-core/bin/lib/workstream-inventory-builder.cjs +53 -32
- package/gsd-core/bin/lib/workstream-inventory.cjs +63 -10
- package/gsd-core/bin/lib/workstream.cjs +2 -2
- package/gsd-core/bin/lib/worktree-safety.cjs +176 -9
- package/gsd-core/bin/shared/config-defaults.manifest.json +1 -0
- package/gsd-core/bin/shared/config-schema.manifest.json +7 -1
- package/gsd-core/references/agent-contracts.md +43 -26
- package/gsd-core/references/checkpoints.md +2 -2
- package/gsd-core/references/context-budget.md +1 -1
- package/gsd-core/references/dispatch-isolation-gate.md +138 -0
- package/gsd-core/references/doc-conflict-engine.md +1 -1
- package/gsd-core/references/execute-mvp-tdd.md +3 -3
- package/gsd-core/references/execute-phase-between-wave-reset.md +6 -2
- package/gsd-core/references/execute-phase-context-guard.md +1 -1
- package/gsd-core/references/execute-phase-response-language.md +1 -1
- package/gsd-core/references/execute-phase-wave-guard.md +6 -2
- package/gsd-core/references/gate-prompts.md +1 -1
- package/gsd-core/references/git-planning-commit.md +2 -1
- package/gsd-core/references/loop-hook-dispatch.md +39 -2
- package/gsd-core/references/model-profiles.md +12 -4
- package/gsd-core/references/mvp-concepts.md +9 -9
- package/gsd-core/references/planner-guidance.md +3 -9
- package/gsd-core/references/planner-preconditions.md +1 -1
- package/gsd-core/references/planner-reviews.md +1 -1
- package/gsd-core/references/planning-config.md +8 -6
- package/gsd-core/references/revision-loop.md +1 -1
- package/gsd-core/references/specless-probe-fallback.md +1 -1
- package/gsd-core/references/universal-anti-patterns.md +3 -3
- package/gsd-core/references/verifier-phase-gates.md +192 -0
- package/gsd-core/references/verify-mvp-mode.md +1 -1
- package/gsd-core/references/workstream-flag.md +22 -6
- package/gsd-core/templates/discussion-log.md +1 -1
- package/gsd-core/templates/phase-prompt.md +2 -4
- package/gsd-core/templates/state.md +4 -4
- package/gsd-core/templates/verification-report.md +9 -1
- package/gsd-core/workflows/ai-integration-phase.md +9 -11
- package/gsd-core/workflows/autonomous.md +1 -1
- package/gsd-core/workflows/cleanup.md +62 -3
- package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +13 -3
- package/gsd-core/workflows/code-review-fix.md +37 -10
- package/gsd-core/workflows/code-review.md +38 -12
- package/gsd-core/workflows/complete-milestone.md +141 -18
- package/gsd-core/workflows/debug.md +7 -5
- package/gsd-core/workflows/diagnose-issues.md +35 -9
- package/gsd-core/workflows/discuss-phase/modes/chain.md +2 -1
- package/gsd-core/workflows/discuss-phase/modes/default.md +1 -1
- package/gsd-core/workflows/discuss-phase-assumptions.md +2 -1
- package/gsd-core/workflows/edit-phase.md +26 -1
- package/gsd-core/workflows/eval-review.md +3 -5
- package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +31 -6
- package/gsd-core/workflows/execute-phase/steps/per-plan-executor-routing.md +77 -0
- package/gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +2 -0
- package/gsd-core/workflows/execute-phase.md +38 -50
- package/gsd-core/workflows/execute-plan.md +36 -4
- package/gsd-core/workflows/explore.md +131 -4
- package/gsd-core/workflows/fast.md +10 -2
- package/gsd-core/workflows/health.md +73 -4
- package/gsd-core/workflows/import.md +4 -4
- package/gsd-core/workflows/ingest-docs.md +5 -5
- package/gsd-core/workflows/mvp-phase.md +6 -3
- package/gsd-core/workflows/new-milestone.md +14 -9
- package/gsd-core/workflows/new-project.md +14 -14
- package/gsd-core/workflows/next.md +12 -0
- package/gsd-core/workflows/plan-phase.md +41 -17
- package/gsd-core/workflows/plan-review-convergence.md +50 -2
- package/gsd-core/workflows/progress.md +34 -6
- package/gsd-core/workflows/quick/steps/plan-checker-loop.md +4 -4
- package/gsd-core/workflows/quick/steps/quick-verification.md +27 -6
- package/gsd-core/workflows/quick/steps/research-phase.md +2 -2
- package/gsd-core/workflows/quick.md +35 -15
- package/gsd-core/workflows/review.md +26 -5
- package/gsd-core/workflows/secure-phase.md +1 -1
- package/gsd-core/workflows/session-report.md +2 -1
- package/gsd-core/workflows/settings.md +66 -2
- package/gsd-core/workflows/ship.md +104 -44
- package/gsd-core/workflows/spec-phase.md +30 -12
- package/gsd-core/workflows/sync-skills.md +63 -8
- package/gsd-core/workflows/transition.md +46 -11
- package/gsd-core/workflows/ui-phase.md +5 -5
- package/gsd-core/workflows/ui-review.md +2 -2
- package/gsd-core/workflows/update.md +1 -1
- package/gsd-core/workflows/validate-phase.md +1 -1
- package/gsd-core/workflows/verify-work.md +9 -7
- package/hooks/dist/gsd-agent-isolation-guard.js +103 -14
- package/hooks/dist/gsd-check-update-worker.js +56 -13
- package/hooks/dist/gsd-check-update.js +19 -1
- package/hooks/dist/gsd-cursor-pre-tool.js +0 -3
- package/hooks/dist/gsd-cursor-subagent-start.js +77 -2
- package/hooks/dist/gsd-cursor-subagent-stop.js +3 -2
- package/hooks/dist/gsd-prompt-guard.js +21 -20
- package/hooks/dist/gsd-read-injection-scanner.js +38 -24
- package/hooks/dist/gsd-statusline.js +18 -0
- package/hooks/dist/gsd-update-banner.js +22 -1
- package/hooks/dist/gsd-workflow-guard.js +134 -36
- package/hooks/dist/lib/git-cmd.js +92 -59
- package/hooks/dist/lib/injection-patterns.js +45 -0
- package/hooks/dist/lib/isolation-deny-reason.js +39 -0
- package/hooks/dist/lib/isolation-sentinel.js +9 -0
- package/hooks/gsd-agent-isolation-guard.js +103 -14
- package/hooks/gsd-check-update-worker.js +56 -13
- package/hooks/gsd-check-update.js +19 -1
- package/hooks/gsd-cursor-pre-tool.js +0 -3
- package/hooks/gsd-cursor-subagent-start.js +77 -2
- package/hooks/gsd-cursor-subagent-stop.js +3 -2
- package/hooks/gsd-prompt-guard.js +21 -20
- package/hooks/gsd-read-injection-scanner.js +38 -24
- package/hooks/gsd-statusline.js +18 -0
- package/hooks/gsd-update-banner.js +22 -1
- package/hooks/gsd-workflow-guard.js +134 -36
- package/hooks/lib/git-cmd.js +92 -59
- package/hooks/lib/injection-patterns.js +45 -0
- package/hooks/lib/isolation-deny-reason.js +39 -0
- package/hooks/lib/isolation-sentinel.js +9 -0
- package/package.json +21 -9
- package/pi/gsd.cjs +19 -5
- package/scripts/baselines/planning-prompt-drift-baseline.json +4 -0
- package/scripts/baselines/planning-snapshot-bypass-baseline.json +12 -0
- package/scripts/baselines/unreachable-guard-drift-baseline.json +4 -0
- package/scripts/changeset/lint.cjs +60 -5
- package/scripts/check-alias-drift.cjs +7 -43
- package/scripts/check-contract-drift.cjs +297 -0
- package/scripts/ci-test-scope.cjs +19 -2
- package/scripts/command-contract-helpers.cjs +903 -1
- package/scripts/gen-adr-index.cjs +728 -38
- package/scripts/gen-capability-registry.cjs +3 -15
- package/scripts/gen-context-index.cjs +2 -11
- package/scripts/gen-health-docs.cjs +390 -0
- package/scripts/gen-inventory-manifest.cjs +50 -4
- package/scripts/gen-loop-host-contract.cjs +4 -24
- package/scripts/gen-registry.cjs +3 -14
- package/scripts/lib/alias-drift-families.cjs +46 -0
- package/scripts/lib/drift-scan.cjs +278 -0
- package/scripts/lint-allow-test-rule-refs.allowlist.json +1 -26
- package/scripts/lint-allow-test-rule-refs.effective-ceiling.json +4 -0
- package/scripts/lint-allow-test-rule-refs.unverified-ceiling.json +3 -0
- package/scripts/lint-canary-version-leak.cjs +73 -0
- package/scripts/lint-command-contract.cjs +96 -13
- package/scripts/lint-completion-predicate-drift.cjs +933 -0
- package/scripts/lint-completion-ratio-drift.cjs +214 -0
- package/scripts/lint-default-flip-documentation.cjs +193 -0
- package/scripts/lint-eslint-glob-coverage.allowlist.json +34 -0
- package/scripts/lint-eslint-glob-coverage.cjs +340 -0
- package/scripts/lint-frontmatter-scalar-broad-grep.cjs +237 -0
- package/scripts/lint-health-diagnostic-rule-table.cjs +404 -0
- package/scripts/lint-hooks-runtime-build-seam.cjs +262 -0
- package/scripts/lint-milestone-window-drift.cjs +468 -0
- package/scripts/lint-phase-enumeration-drift.cjs +479 -0
- package/scripts/lint-plan-count-drift.cjs +318 -0
- package/scripts/lint-planning-artifact-writer-drift.cjs +398 -0
- package/scripts/lint-planning-prompt-drift.cjs +434 -0
- package/scripts/lint-planning-snapshot-bypass-drift.cjs +544 -0
- package/scripts/lint-regression-test-names.cjs +15 -13
- package/scripts/lint-removed-but-needed.cjs +320 -0
- package/scripts/lint-state-field-drift.cjs +805 -0
- package/scripts/lint-state-write-path-drift.cjs +1045 -0
- package/scripts/lint-test-file-count.allowlist.json +21 -10
- package/scripts/lint-unreachable-guard-drift.cjs +843 -0
- package/scripts/lint-vendored-deps.cjs +124 -0
- package/scripts/pr-changed-files.cjs +63 -0
- package/scripts/pr-template-policy.cjs +14 -4
- package/scripts/prompt-injection-scan.sh +25 -0
- package/scripts/require-issue-link-policy.cjs +192 -0
- package/scripts/state-write-path-drift-baseline.json +19 -0
- package/scripts/sync-runtime-launcher.cjs +2 -4
- package/skills/gsd-autonomous/SKILL.md +0 -1
- package/skills/gsd-code-review/SKILL.md +1 -1
- package/skills/gsd-execute-phase/SKILL.md +1 -2
- package/skills/gsd-map-codebase/SKILL.md +1 -1
- package/skills/gsd-mempalace-capture/SKILL.md +1 -1
- package/skills/gsd-mempalace-recall/SKILL.md +1 -1
- package/skills/gsd-new-milestone/SKILL.md +1 -1
- package/skills/gsd-next/SKILL.md +0 -1
- package/skills/gsd-plan-phase/SKILL.md +0 -1
- package/skills/gsd-progress/SKILL.md +0 -1
- package/skills/gsd-quick/SKILL.md +1 -1
- package/skills/gsd-review-backlog/SKILL.md +2 -1
- package/skills/gsd-stats/SKILL.md +0 -1
- package/skills/gsd-verify-work/SKILL.md +1 -1
- package/vscode/package.json +1 -1
- package/gsd-core/workflows/discovery-phase.md +0 -298
- package/gsd-core/workflows/plan-milestone-gaps.md +0 -281
- package/gsd-core/workflows/verify-phase.md +0 -574
- package/scripts/affected-tests-lib.cjs +0 -554
- package/scripts/lint-allow-test-rule-refs.cjs +0 -162
- package/scripts/run-affected-tests.cjs +0 -7
- package/scripts/run-tests.cjs +0 -1051
|
@@ -20,6 +20,9 @@
|
|
|
20
20
|
* resolve-model <agent-type> Get model for agent based on profile
|
|
21
21
|
* find-phase <phase> Find phase directory by number
|
|
22
22
|
* commit <message> [--files f1 f2] [--no-verify] Commit planning docs
|
|
23
|
+
* commit-docs-guard enable|disable Opt-in .git/hooks/pre-commit guard
|
|
24
|
+
* that refuses a commit staging
|
|
25
|
+
* .planning/ when commit_docs is false
|
|
23
26
|
* commit-to-subrepo <msg> --files f1 f2 Route commits to sub-repos
|
|
24
27
|
* verify-summary <path> Verify a SUMMARY.md file
|
|
25
28
|
* generate-slug <text> Convert text to URL-safe slug
|
|
@@ -68,6 +71,14 @@
|
|
|
68
71
|
* milestone complete <version> Archive milestone, create MILESTONES.md
|
|
69
72
|
* [--name <name>]
|
|
70
73
|
* [--no-archive-phases] Skip moving phase dirs to milestones/vX.Y-phases/ (archived by default)
|
|
74
|
+
* [--archive-quick] Move .planning/quick/* dirs to milestones/vX.Y-quick/ + reset the
|
|
75
|
+
* Quick Tasks Completed table (#2142; opt-in, default OFF)
|
|
76
|
+
*
|
|
77
|
+
* milestone archive-quick <version> Move .planning/quick/* dirs to milestones/vX.Y-quick/ + reset the
|
|
78
|
+
* Quick Tasks Completed table, WITHOUT the milestone complete close-out
|
|
79
|
+
* (no ROADMAP/REQUIREMENTS/MILESTONES.md writes, no completion guards);
|
|
80
|
+
* safe against an already-completed milestone (#2142 escalation)
|
|
81
|
+
* [--dry-run] Preview what would move, mutates nothing
|
|
71
82
|
*
|
|
72
83
|
* User Story Validation:
|
|
73
84
|
* user-story validate --story "..." Validate "As a / I want to / so that" format
|
|
@@ -80,6 +91,7 @@
|
|
|
80
91
|
* drift-guard severity --status <S> Classify a symbol verdict into { severity, hardBlock }
|
|
81
92
|
* [--authority <A>] Status: VERIFIED|MISSING|AMBIGUOUS|UNCHECKABLE
|
|
82
93
|
* Authority: grep|intel|treesitter|lsp|scip (default: config-resolved)
|
|
94
|
+
* drift-guard phase-status [--phase N] Compare STATE.md vs ROADMAP.md phase status
|
|
83
95
|
*
|
|
84
96
|
* Validation:
|
|
85
97
|
* validate consistency Check phase numbering, disk/roadmap sync
|
|
@@ -259,8 +271,7 @@ try {
|
|
|
259
271
|
}
|
|
260
272
|
} catch { /* advisory — never block */ }
|
|
261
273
|
|
|
262
|
-
const {
|
|
263
|
-
const { resolveActiveWorkstream, applyResolvedWorkstreamEnv } = require('./lib/active-workstream-store.cjs');
|
|
274
|
+
const { resolveActiveWorkstream, applyResolvedWorkstreamEnv, peekActiveWorkstream } = require('./lib/active-workstream-store.cjs');
|
|
264
275
|
const state = require('./lib/state.cjs');
|
|
265
276
|
const phase = require('./lib/phase.cjs');
|
|
266
277
|
const roadmap = require('./lib/roadmap.cjs');
|
|
@@ -305,7 +316,7 @@ const { routeCheckCommand } = require('./lib/check-command-router.cjs');
|
|
|
305
316
|
const { routeTaskCommand } = require('./lib/task-command-router.cjs');
|
|
306
317
|
const { parseNamedArgs, parseMultiwordArg } = require('./lib/command-arg-projection.cjs');
|
|
307
318
|
const { cmdGitBaseBranch } = require('./lib/git-base-branch.cjs');
|
|
308
|
-
const { getEffectiveAuthority, classifyDriftSeverity } = require('./lib/plan-drift-guard.cjs');
|
|
319
|
+
const { getEffectiveAuthority, classifyDriftSeverity, comparePhaseStatus } = require('./lib/plan-drift-guard.cjs');
|
|
309
320
|
|
|
310
321
|
// ─── Bridge collapsed (Phase 4) ────────────────────────────────────────────────
|
|
311
322
|
// Non-family commands now run through their CJS handlers directly. Keep the
|
|
@@ -921,6 +932,17 @@ function dispatchOverlayCapabilityCommand({ command, args, cwd, raw, error, load
|
|
|
921
932
|
commands.cmdCheckCommit(cwd, raw);
|
|
922
933
|
}
|
|
923
934
|
|
|
935
|
+
function routeCommitDocsGuard({ args, cwd, raw, error }) {
|
|
936
|
+
const subcommand = args[1];
|
|
937
|
+
if (subcommand === 'enable') {
|
|
938
|
+
commands.cmdCommitDocsGuardEnable(cwd, raw);
|
|
939
|
+
} else if (subcommand === 'disable') {
|
|
940
|
+
commands.cmdCommitDocsGuardDisable(cwd, raw);
|
|
941
|
+
} else {
|
|
942
|
+
error('Unknown commit-docs-guard subcommand. Available: enable, disable', ERROR_REASON.SDK_UNKNOWN_COMMAND);
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
|
|
924
946
|
function routeCommitToSubrepo({ args, cwd, raw, error }) {
|
|
925
947
|
const message = args[1];
|
|
926
948
|
const filesIndex = args.indexOf('--files');
|
|
@@ -1285,30 +1307,45 @@ function dispatchOverlayCapabilityCommand({ command, args, cwd, raw, error, load
|
|
|
1285
1307
|
return;
|
|
1286
1308
|
}
|
|
1287
1309
|
|
|
1288
|
-
// Effort argv is resolved per lane by the host's own execution policy,
|
|
1289
|
-
//
|
|
1290
|
-
//
|
|
1291
|
-
//
|
|
1292
|
-
//
|
|
1293
|
-
//
|
|
1294
|
-
//
|
|
1295
|
-
//
|
|
1296
|
-
//
|
|
1310
|
+
// Effort argv is resolved per lane by the host's own execution policy, through the SAME
|
|
1311
|
+
// `resolve-execution` surface the bash legs used (`--host <slug>`), so the host's negotiated
|
|
1312
|
+
// effortSurface still decides whether an argument is emitted and the catalog still owns the
|
|
1313
|
+
// syntax (ADR-1239 #2481, ADR-443's escalation ladder). `cmdResolveExecution` writes to
|
|
1314
|
+
// stdout and exits, so it cannot be called in-process for a value — this spawns the same
|
|
1315
|
+
// bounded query the legs did, once per selected lane. A lane whose slug is not a known host
|
|
1316
|
+
// resolves to no effort argument at all.
|
|
1317
|
+
//
|
|
1318
|
+
// NOT `--raw` and NOT `--pick` (#2295). `--raw` prints only the resolved EFFORT ('low') with
|
|
1319
|
+
// no host-specific rendering at all. `--pick effort_argv_string` used to be the answer — the
|
|
1320
|
+
// rendered array re-joined into a string ('-c model_reasoning_effort=low') — but the caller
|
|
1321
|
+
// then had to `.split(/\s+/)` that string back apart to get an argv array, and re-splitting a
|
|
1322
|
+
// string the callee just joined is a lossy round trip: any argv element that legitimately
|
|
1323
|
+
// contains a space would come back split into two argv elements, corrupting the very argv it
|
|
1324
|
+
// was rendered to preserve. Reading the UNPICKED object instead gives both `effort_argv` (a
|
|
1325
|
+
// real string array, used verbatim, no re-splitting) and `effort_argv_value` (the bare level,
|
|
1326
|
+
// #2295's `plan.effort`) from the one spawn.
|
|
1327
|
+
const EMPTY_EFFORT = { argv: [], value: null };
|
|
1297
1328
|
const effortFor = (slug) => {
|
|
1298
1329
|
try {
|
|
1299
1330
|
const r = cp.spawnSync(
|
|
1300
1331
|
process.execPath,
|
|
1301
|
-
[__filename, 'query', 'resolve-execution', 'gsd-plan-checker',
|
|
1302
|
-
// NOT `--raw`: that prints the resolved EFFORT ('low'), ignoring --pick. The picked
|
|
1303
|
-
// field is what carries the host-specific syntax ('--effort low' for claude,
|
|
1304
|
-
// '-c model_reasoning_effort=low' for codex), which is the whole point of asking.
|
|
1305
|
-
'--host', slug, '--pick', 'effort_argv_string'],
|
|
1332
|
+
[__filename, 'query', 'resolve-execution', 'gsd-plan-checker', '--host', slug],
|
|
1306
1333
|
{ cwd, encoding: 'utf8', timeout: 15000, killSignal: 'SIGKILL', maxBuffer: 1024 * 1024 },
|
|
1307
1334
|
);
|
|
1308
|
-
if (r.status !== 0) return
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1335
|
+
if (r.status !== 0) return EMPTY_EFFORT;
|
|
1336
|
+
let parsed;
|
|
1337
|
+
try {
|
|
1338
|
+
parsed = JSON.parse(String(r.stdout || ''));
|
|
1339
|
+
} catch { return EMPTY_EFFORT; }
|
|
1340
|
+
if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) return EMPTY_EFFORT;
|
|
1341
|
+
const argv = Array.isArray(parsed.effort_argv)
|
|
1342
|
+
? parsed.effort_argv.filter((a) => typeof a === 'string' && a !== '')
|
|
1343
|
+
: [];
|
|
1344
|
+
const value = typeof parsed.effort_argv_value === 'string' && parsed.effort_argv_value
|
|
1345
|
+
? parsed.effort_argv_value
|
|
1346
|
+
: null;
|
|
1347
|
+
return { argv, value };
|
|
1348
|
+
} catch { return EMPTY_EFFORT; }
|
|
1312
1349
|
};
|
|
1313
1350
|
|
|
1314
1351
|
/**
|
|
@@ -1341,7 +1378,8 @@ function dispatchOverlayCapabilityCommand({ command, args, cwd, raw, error, load
|
|
|
1341
1378
|
// so losing all of them to one bad manifest is strictly worse. Belt and braces on purpose.
|
|
1342
1379
|
let r;
|
|
1343
1380
|
try {
|
|
1344
|
-
|
|
1381
|
+
const effort = effortFor(slug);
|
|
1382
|
+
r = resolveLanePlan({ lane, configGet, runDir, repoRoot, effortArgs: effort.argv, effortValue: effort.value });
|
|
1345
1383
|
} catch (e) {
|
|
1346
1384
|
return { slug, ok: false, reason: 'malformed_lane', detail: `resolver threw: ${e && e.message ? e.message : String(e)}` };
|
|
1347
1385
|
}
|
|
@@ -1385,10 +1423,23 @@ function dispatchOverlayCapabilityCommand({ command, args, cwd, raw, error, load
|
|
|
1385
1423
|
// ENOENT (CreateProcess cannot start .cmd). Apply the same #2667 shim
|
|
1386
1424
|
// gate used in runWithTimeout: detect .cmd/.bat and mediate through
|
|
1387
1425
|
// cmd.exe /d /s /c with an explicit argv array (no shell:true).
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1426
|
+
//
|
|
1427
|
+
// #3275: descriptors declare BARE names, so the gate above never saw an
|
|
1428
|
+
// extension — resolve through the shared PATH+PATHEXT resolver FIRST
|
|
1429
|
+
// (the same one `hasBinary` uses, so probe and spawn can never disagree
|
|
1430
|
+
// about what the lane's binary is). POSIX keeps the bare name: Node's own
|
|
1431
|
+
// PATH search already worked there, and the #3275 acceptance contract
|
|
1432
|
+
// holds macOS/Linux behavior unchanged. A name that resolves to nothing
|
|
1433
|
+
// falls back to the declared name so the ENOENT still surfaces (#3086).
|
|
1434
|
+
// #3411: the resolve-then-mediate pair is one seam call now. Both halves had
|
|
1435
|
+
// private copies here; `projectSpawnInvocation` owns them, so a fix to either
|
|
1436
|
+
// reaches every spawn site instead of only this one.
|
|
1437
|
+
//
|
|
1438
|
+
// Unlike execTool, this lane adopts the RESOLVED path even for a non-batch
|
|
1439
|
+
// binary: that is the behavior #3445 shipped and `deps.hasBinary` answers
|
|
1440
|
+
// from the same resolver, so probe and spawn must agree on the exact file.
|
|
1441
|
+
const { projectSpawnInvocation } = require('./lib/shell-command-projection.cjs');
|
|
1442
|
+
const { command: spawnBinary, args: spawnArgv, windowsVerbatimArguments } = projectSpawnInvocation(binary, argv);
|
|
1392
1443
|
const r = cp.spawnSync(spawnBinary, spawnArgv, {
|
|
1393
1444
|
input: opts.input,
|
|
1394
1445
|
encoding: 'utf8',
|
|
@@ -1396,6 +1447,11 @@ function dispatchOverlayCapabilityCommand({ command, args, cwd, raw, error, load
|
|
|
1396
1447
|
killSignal: 'SIGKILL',
|
|
1397
1448
|
maxBuffer: 64 * 1024 * 1024,
|
|
1398
1449
|
shell: false, // argv array only — never a shell string (no interpolation of config values)
|
|
1450
|
+
// #2483: a lane's declared env pairs merged OVER this process's environment, for this
|
|
1451
|
+
// child only. Passing a fresh object leaves `process.env` untouched, so nothing leaks
|
|
1452
|
+
// into the orchestrating session or into the next lane.
|
|
1453
|
+
...(opts.env ? { env: { ...process.env, ...opts.env } } : {}),
|
|
1454
|
+
...(windowsVerbatimArguments ? { windowsVerbatimArguments: true } : {}),
|
|
1399
1455
|
});
|
|
1400
1456
|
return {
|
|
1401
1457
|
status: r.status,
|
|
@@ -1424,24 +1480,13 @@ function dispatchOverlayCapabilityCommand({ command, args, cwd, raw, error, load
|
|
|
1424
1480
|
// all (a probe that costs a process is a probe you avoid running, which is how the original
|
|
1425
1481
|
// Kimi probe ended up unbounded), and `shell: true` with an args array is deprecated in
|
|
1426
1482
|
// Node 26 (DEP0190) because the arguments are concatenated rather than escaped.
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
for (const dir of (process.env.PATH || '').split(path.delimiter).filter(Boolean)) {
|
|
1435
|
-
for (const ext of exts) {
|
|
1436
|
-
const candidate = path.join(dir, name + ext);
|
|
1437
|
-
try {
|
|
1438
|
-
const st = fsx.statSync(candidate);
|
|
1439
|
-
if (st.isFile()) return true;
|
|
1440
|
-
} catch { /* next candidate */ }
|
|
1441
|
-
}
|
|
1442
|
-
}
|
|
1443
|
-
return false;
|
|
1444
|
-
},
|
|
1483
|
+
//
|
|
1484
|
+
// #3275: the scan lives in `resolveSpawnBinary` now, SHARED with `deps.spawn`
|
|
1485
|
+
// above. Two private copies of "what is this declared binary?" is how the
|
|
1486
|
+
// defect hid: the probe resolved WITH PATHEXT while spawn resolved WITHOUT,
|
|
1487
|
+
// so a lane reported available for a spawn that could never start. One
|
|
1488
|
+
// resolver, both seams — if one changes, the other changes with it.
|
|
1489
|
+
hasBinary: (name) => resolveSpawnBinary(name) !== null,
|
|
1445
1490
|
configGet,
|
|
1446
1491
|
homeDir: os.homedir(),
|
|
1447
1492
|
warn: (m) => process.stderr.write(`${m}\n`),
|
|
@@ -1493,12 +1538,14 @@ function dispatchOverlayCapabilityCommand({ command, args, cwd, raw, error, load
|
|
|
1493
1538
|
`model override (it declares no modelConfigKey). The review will use the CLI's own default.\n`,
|
|
1494
1539
|
);
|
|
1495
1540
|
}
|
|
1541
|
+
const instanceEffort = effortFor(entry.slug);
|
|
1496
1542
|
const overridden = resolveLanePlan({
|
|
1497
1543
|
lane,
|
|
1498
1544
|
configGet: (k) => (key && k === key ? instanceModel : configGet(k)),
|
|
1499
1545
|
runDir,
|
|
1500
1546
|
repoRoot,
|
|
1501
|
-
effortArgs:
|
|
1547
|
+
effortArgs: instanceEffort.argv,
|
|
1548
|
+
effortValue: instanceEffort.value,
|
|
1502
1549
|
});
|
|
1503
1550
|
if (overridden.ok) {
|
|
1504
1551
|
// Preserve any instance retargeting already applied above.
|
|
@@ -1575,7 +1622,8 @@ function dispatchOverlayCapabilityCommand({ command, args, cwd, raw, error, load
|
|
|
1575
1622
|
function routeDispatchShouldFlatten({ args, cwd, raw, error }) {
|
|
1576
1623
|
// #1708 / #853: typed query replacing the `RUNTIME === 'codex'` prose rule.
|
|
1577
1624
|
//
|
|
1578
|
-
// Resolves the current runtime (GSD_RUNTIME > config.runtime >
|
|
1625
|
+
// Resolves the current runtime (GSD_RUNTIME > config.runtime > per-install
|
|
1626
|
+
// .gsd-runtime marker > 'claude'),
|
|
1579
1627
|
// looks up registry.runtimes[id].runtime.hostIntegration.dispatch, and
|
|
1580
1628
|
// calls shouldFlattenDispatch(dispatch) from host-integration.cjs.
|
|
1581
1629
|
//
|
|
@@ -1629,7 +1677,8 @@ function dispatchOverlayCapabilityCommand({ command, args, cwd, raw, error, load
|
|
|
1629
1677
|
// #853) for exactly this reason — it replaced a `RUNTIME === 'codex'`
|
|
1630
1678
|
// prose rule.
|
|
1631
1679
|
//
|
|
1632
|
-
// Resolves the current runtime (GSD_RUNTIME > config.runtime >
|
|
1680
|
+
// Resolves the current runtime (GSD_RUNTIME > config.runtime > per-install
|
|
1681
|
+
// .gsd-runtime marker > 'claude'),
|
|
1633
1682
|
// reads registry.runtimes[id].runtime.hostIntegration.dispatch.isolation,
|
|
1634
1683
|
// and validates it against the closed vocabulary.
|
|
1635
1684
|
//
|
|
@@ -1664,12 +1713,83 @@ function dispatchOverlayCapabilityCommand({ command, args, cwd, raw, error, load
|
|
|
1664
1713
|
// `per-plan-worktree-gate.md`) override the naturally-resolved mode
|
|
1665
1714
|
// while still going through this single write path. Best-effort: a
|
|
1666
1715
|
// sentinel write failure here must never fail the wave.
|
|
1667
|
-
const
|
|
1716
|
+
const decision = resolveDispatchIsolationDecision({ args, cwd });
|
|
1717
|
+
const runtimeId = decision.runtimeId;
|
|
1718
|
+
let { isolation, exec, harnessFlag } = decision;
|
|
1719
|
+
|
|
1720
|
+
// `--force-isolation <mode>` overrides the naturally-resolved mode with
|
|
1721
|
+
// context this resolver has no way to see on its own (e.g. the #2474
|
|
1722
|
+
// per-plan submodule intersection). Invalid/unrecognized values are
|
|
1723
|
+
// ignored rather than erroring — this is a best-effort recording call,
|
|
1724
|
+
// not a hard usage gate. Forcing to 'none' clears harnessFlag/exec since
|
|
1725
|
+
// neither applies to sequential dispatch.
|
|
1726
|
+
const forceIdx = args.indexOf('--force-isolation');
|
|
1727
|
+
const forcedIsolation = forceIdx !== -1 ? args[forceIdx + 1] : undefined;
|
|
1728
|
+
if (forcedIsolation && DISPATCH_ISOLATION_VOCABULARY.has(forcedIsolation)) {
|
|
1729
|
+
isolation = forcedIsolation;
|
|
1730
|
+
if (isolation === 'none') {
|
|
1731
|
+
harnessFlag = null;
|
|
1732
|
+
exec = null;
|
|
1733
|
+
}
|
|
1734
|
+
}
|
|
1735
|
+
|
|
1736
|
+
const phaseIdx = args.indexOf('--phase');
|
|
1737
|
+
const phaseArg = phaseIdx !== -1 && args[phaseIdx + 1] && !args[phaseIdx + 1].startsWith('--')
|
|
1738
|
+
? args[phaseIdx + 1]
|
|
1739
|
+
: null;
|
|
1740
|
+
const planIdx = args.indexOf('--plan');
|
|
1741
|
+
const planArg = planIdx !== -1 && args[planIdx + 1] && !args[planIdx + 1].startsWith('--')
|
|
1742
|
+
? args[planIdx + 1]
|
|
1743
|
+
: null;
|
|
1744
|
+
|
|
1745
|
+
// Side-effect write (#3045 CORE REDESIGN) — see the doc comment above.
|
|
1746
|
+
// Never allowed to affect this query's own stdout contract or throw.
|
|
1747
|
+
try {
|
|
1748
|
+
writeDispatchIsolationSentinel(cwd, { isolation, harnessFlag, phase: phaseArg, plan: planArg });
|
|
1749
|
+
} catch {
|
|
1750
|
+
// writeDispatchIsolationSentinel already swallows its own errors into
|
|
1751
|
+
// a { recorded: false } result; this catch is defense in depth only.
|
|
1752
|
+
}
|
|
1753
|
+
|
|
1754
|
+
if (args.indexOf('--json') !== -1) {
|
|
1755
|
+
output({ runtime: runtimeId, isolation, exec, harnessFlag }, raw);
|
|
1756
|
+
} else {
|
|
1757
|
+
process.stdout.write(isolation);
|
|
1758
|
+
}
|
|
1759
|
+
}
|
|
1760
|
+
|
|
1761
|
+
const DISPATCH_ISOLATION_VOCABULARY = new Set(['harness-worktree', 'orchestrator-worktree', 'none']);
|
|
1762
|
+
|
|
1763
|
+
/**
|
|
1764
|
+
* Shared, side-effect-free resolution of the negotiated dispatch isolation:
|
|
1765
|
+
* runtime (GSD_RUNTIME > config.runtime > per-install .gsd-runtime marker >
|
|
1766
|
+
* 'claude') → declared
|
|
1767
|
+
* `dispatch.isolation` → harness-flag / orchestrator-exec degrade rules.
|
|
1768
|
+
* Extracted (#2486) so `routeDispatchIsolation` (the #3045 recording
|
|
1769
|
+
* dispatch path) and `routeInspectDispatchIsolation` (the read-only
|
|
1770
|
+
* inspection path) share exactly one negotiation implementation and cannot
|
|
1771
|
+
* drift apart. Resolution only — the caller decides whether the decision is
|
|
1772
|
+
* recorded to the sentinel.
|
|
1773
|
+
*/
|
|
1774
|
+
function resolveDispatchIsolationDecision({ args, cwd }) {
|
|
1668
1775
|
let isolation = 'none';
|
|
1669
1776
|
let runtimeId = null;
|
|
1670
1777
|
let exec = null;
|
|
1671
1778
|
let harnessFlag = null;
|
|
1672
1779
|
try {
|
|
1780
|
+
// Deliberately `resolveRuntime`, NOT `resolveActiveRuntime`/`loadConfig`:
|
|
1781
|
+
// loadConfig normalizes and rewrites legacy keys back to disk, and this
|
|
1782
|
+
// resolver backs the sentinel-free `inspect-dispatch-isolation` verb,
|
|
1783
|
+
// which must never write. resolveRuntime reads config.json directly.
|
|
1784
|
+
//
|
|
1785
|
+
// KNOWN LIMITATION, tracked separately: resolveRuntime stops at
|
|
1786
|
+
// GSD_RUNTIME > config.runtime > 'claude' and does not consult the
|
|
1787
|
+
// per-install `.gsd-runtime` marker, so on a non-Claude install whose
|
|
1788
|
+
// project config carries no `runtime` key this resolves 'claude'. That is
|
|
1789
|
+
// open-gsd/gsd-core#2395 — a pre-existing defect in the canonical
|
|
1790
|
+
// resolver, not introduced here, and deliberately NOT fixed in this PR
|
|
1791
|
+
// (its blast radius reaches every consumer of that resolver, so it is
|
|
1792
|
+
// being handled on its own).
|
|
1673
1793
|
const { resolveRuntime } = require('./lib/runtime-slash.cjs');
|
|
1674
1794
|
runtimeId = resolveRuntime(cwd);
|
|
1675
1795
|
|
|
@@ -1678,7 +1798,7 @@ function dispatchOverlayCapabilityCommand({ command, args, cwd, raw, error, load
|
|
|
1678
1798
|
? registry.runtimes[runtimeId]
|
|
1679
1799
|
: null;
|
|
1680
1800
|
const declared = runtimeEntry?.runtime?.hostIntegration?.dispatch?.isolation ?? null;
|
|
1681
|
-
if (typeof declared === 'string' &&
|
|
1801
|
+
if (typeof declared === 'string' && DISPATCH_ISOLATION_VOCABULARY.has(declared)) {
|
|
1682
1802
|
isolation = declared;
|
|
1683
1803
|
}
|
|
1684
1804
|
|
|
@@ -1721,41 +1841,52 @@ function dispatchOverlayCapabilityCommand({ command, args, cwd, raw, error, load
|
|
|
1721
1841
|
exec = null;
|
|
1722
1842
|
harnessFlag = null;
|
|
1723
1843
|
}
|
|
1844
|
+
return { runtimeId, isolation, exec, harnessFlag };
|
|
1845
|
+
}
|
|
1724
1846
|
|
|
1725
|
-
|
|
1726
|
-
//
|
|
1727
|
-
//
|
|
1728
|
-
//
|
|
1729
|
-
//
|
|
1730
|
-
//
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
//
|
|
1751
|
-
//
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1847
|
+
function routeInspectDispatchIsolation({ args, cwd, raw }) {
|
|
1848
|
+
// #2486: sentinel-free sibling of `dispatch-isolation` for INSPECTION
|
|
1849
|
+
// surfaces — /gsd:health's W025 check and /gsd:settings' Worktrees
|
|
1850
|
+
// branching. The dispatch verb above intentionally records its resolved
|
|
1851
|
+
// decision to the isolation sentinel as an unconditional side effect
|
|
1852
|
+
// (#3045 CORE REDESIGN): correct for executor dispatch, where the record
|
|
1853
|
+
// must be structurally unskippable — but wrong for a read-only
|
|
1854
|
+
// diagnostic. A health check that records a phase:null/plan:null
|
|
1855
|
+
// sentinel can hard-block every executor dispatch for the sentinel's
|
|
1856
|
+
// lifetime, across sessions sharing the main checkout. Inspection
|
|
1857
|
+
// surfaces call this verb instead. Two claims, both narrower than
|
|
1858
|
+
// "side-effect-free", and both exactly true (#2486 review, Majors 2 & 4):
|
|
1859
|
+
//
|
|
1860
|
+
// 1. SENTINEL-FREE, not write-free. This route writes nothing itself, and
|
|
1861
|
+
// in particular never writes .gsd/dispatch-isolation-sentinel.json —
|
|
1862
|
+
// the only write that can hard-block a later executor dispatch. It is
|
|
1863
|
+
// NOT an unconditional claim of total filesystem purity: like every
|
|
1864
|
+
// gsd-tools invocation, it runs the shared bootstrap and
|
|
1865
|
+
// active-workstream resolution first. As of #3579's root-cause fix
|
|
1866
|
+
// that bootstrap resolves via the non-mutating peekActiveWorkstream
|
|
1867
|
+
// (never unlinks); an actual stale/invalid pointer is still
|
|
1868
|
+
// self-healed, but only by whichever verb's own getActiveWorkstream
|
|
1869
|
+
// call later consumes it for real — this inspection route makes no
|
|
1870
|
+
// such call, so it is now also side-effect-free on the pointer file.
|
|
1871
|
+
//
|
|
1872
|
+
// 2. SHARED NEGOTIATION, for the arguments this verb accepts. Both verbs
|
|
1873
|
+
// call resolveDispatchIsolationDecision, so the natural resolution
|
|
1874
|
+
// cannot drift. It is NOT a claim of byte-identical output for every
|
|
1875
|
+
// argv: routeDispatchIsolation applies --force-isolation AFTER the
|
|
1876
|
+
// shared helper returns, so the same argv could otherwise yield
|
|
1877
|
+
// 'none' there and the declared capability here. Rather than let a
|
|
1878
|
+
// caller receive a silently different answer, this verb REJECTS the
|
|
1879
|
+
// recording-only knobs outright — they exist to be recorded, and a
|
|
1880
|
+
// read has nothing to record.
|
|
1881
|
+
const RECORDING_ONLY_ARGS = ['--force-isolation', '--phase', '--plan'];
|
|
1882
|
+
const rejected = RECORDING_ONLY_ARGS.filter((flag) => args.indexOf(flag) !== -1);
|
|
1883
|
+
if (rejected.length > 0) {
|
|
1884
|
+
error(
|
|
1885
|
+
`inspect-dispatch-isolation: ${rejected.join(', ')} ${rejected.length === 1 ? 'is a' : 'are'} recording-only argument${rejected.length === 1 ? '' : 's'} and cannot be used on the inspection verb — it resolves the runtime's DECLARED capability and records nothing. Use 'query dispatch-isolation' if you need the override applied and the decision recorded.`,
|
|
1886
|
+
ERROR_REASON.USAGE,
|
|
1887
|
+
);
|
|
1757
1888
|
}
|
|
1758
|
-
|
|
1889
|
+
const { runtimeId, isolation, exec, harnessFlag } = resolveDispatchIsolationDecision({ args, cwd });
|
|
1759
1890
|
if (args.indexOf('--json') !== -1) {
|
|
1760
1891
|
output({ runtime: runtimeId, isolation, exec, harnessFlag }, raw);
|
|
1761
1892
|
} else {
|
|
@@ -1906,6 +2037,50 @@ function dispatchOverlayCapabilityCommand({ command, args, cwd, raw, error, load
|
|
|
1906
2037
|
}
|
|
1907
2038
|
}
|
|
1908
2039
|
|
|
2040
|
+
function routeResolveAgent({ args, cwd, raw, error }) {
|
|
2041
|
+
// #1689: resolve a per-plan agent_hint specialist name to the subagent_type
|
|
2042
|
+
// an Agent() call should use. Returns the name unchanged when a
|
|
2043
|
+
// matching agent file exists in the active runtime's agent dir(s);
|
|
2044
|
+
// 'gsd-executor' when the name is absent, blank, or does not resolve.
|
|
2045
|
+
// Fail-closed is the fallback (gsd-executor) — never echo an
|
|
2046
|
+
// unvalidated name, which would make Agent() error and block the wave.
|
|
2047
|
+
//
|
|
2048
|
+
// Output:
|
|
2049
|
+
// --raw (default) -> prints the resolved type (the hint, or 'gsd-executor')
|
|
2050
|
+
// --json -> prints { runtime, requested, resolved, fallback }
|
|
2051
|
+
const FALLBACK = 'gsd-executor';
|
|
2052
|
+
try {
|
|
2053
|
+
const nameIdx = args.indexOf('--name');
|
|
2054
|
+
const requested = nameIdx !== -1 ? args[nameIdx + 1] : '';
|
|
2055
|
+
const { resolveRuntime } = require('./lib/runtime-slash.cjs');
|
|
2056
|
+
const runtimeId = resolveRuntime(cwd);
|
|
2057
|
+
const { resolveAgentHint } = require('./lib/agent-install-check.cjs');
|
|
2058
|
+
let resolved = FALLBACK;
|
|
2059
|
+
let resolvedOk = false; // true only when resolveAgentHint returned a hit
|
|
2060
|
+
if (requested && !requested.startsWith('-')) {
|
|
2061
|
+
const hit = resolveAgentHint(requested, runtimeId, cwd);
|
|
2062
|
+
if (hit !== null) {
|
|
2063
|
+
resolved = hit;
|
|
2064
|
+
resolvedOk = true;
|
|
2065
|
+
}
|
|
2066
|
+
}
|
|
2067
|
+
// `fallback` = we did NOT honor a resolvable hint (absent/flag-shaped name,
|
|
2068
|
+
// the named agent did not resolve, or resolution errored). Requesting
|
|
2069
|
+
// gsd-executor explicitly and resolving to it is NOT a fallback.
|
|
2070
|
+
const fellBack = !resolvedOk;
|
|
2071
|
+
const jsonIdx = args.indexOf('--json');
|
|
2072
|
+
if (jsonIdx !== -1) {
|
|
2073
|
+
output({ runtime: runtimeId, requested: requested || null, resolved, fallback: fellBack }, raw);
|
|
2074
|
+
} else {
|
|
2075
|
+
process.stdout.write(String(resolved));
|
|
2076
|
+
}
|
|
2077
|
+
} catch {
|
|
2078
|
+
// Fail-closed: degrade to the legacy executor on any error so dispatch
|
|
2079
|
+
// never blocks on resolution.
|
|
2080
|
+
process.stdout.write(FALLBACK);
|
|
2081
|
+
}
|
|
2082
|
+
}
|
|
2083
|
+
|
|
1909
2084
|
function routeAgentSkills({ args, cwd, raw, error }) {
|
|
1910
2085
|
// --json emits typed IR { agent_type, block, skills_count } for test assertions
|
|
1911
2086
|
// (#455). Default (no flag) outputs raw XML so workflow shell expansions work.
|
|
@@ -2006,9 +2181,20 @@ function dispatchOverlayCapabilityCommand({ command, args, cwd, raw, error, load
|
|
|
2006
2181
|
const force = args.includes('--force');
|
|
2007
2182
|
// #2118: --dry-run prints a preview plan without mutating.
|
|
2008
2183
|
const dryRun = args.includes('--dry-run');
|
|
2009
|
-
|
|
2184
|
+
// #2142: quick-task archival is opt-in (default OFF) — unlike
|
|
2185
|
+
// --no-archive-phases' inverted shape, absence of this flag means
|
|
2186
|
+
// "do nothing" rather than "skip a default-on behavior".
|
|
2187
|
+
const archiveQuick = args.includes('--archive-quick');
|
|
2188
|
+
milestone.cmdMilestoneComplete(cwd, args[2], { name: milestoneName, archivePhases, force, dryRun, archiveQuick }, raw);
|
|
2189
|
+
} else if (subcommand === 'archive-quick') {
|
|
2190
|
+
// #2142 escalation: narrow archival-only entry point (does NOT
|
|
2191
|
+
// touch ROADMAP/REQUIREMENTS/MILESTONES.md, runs no completion
|
|
2192
|
+
// guards) — safe to call against an already-completed milestone,
|
|
2193
|
+
// unlike `milestone complete --archive-quick`.
|
|
2194
|
+
const dryRun = args.includes('--dry-run');
|
|
2195
|
+
milestone.cmdQuickArchive(cwd, args[2], { dryRun }, raw);
|
|
2010
2196
|
} else {
|
|
2011
|
-
error('Unknown milestone subcommand. Available: complete', ERROR_REASON.SDK_UNKNOWN_COMMAND);
|
|
2197
|
+
error('Unknown milestone subcommand. Available: complete, archive-quick', ERROR_REASON.SDK_UNKNOWN_COMMAND);
|
|
2012
2198
|
}
|
|
2013
2199
|
}
|
|
2014
2200
|
|
|
@@ -3283,13 +3469,189 @@ function dispatchOverlayCapabilityCommand({ command, args, cwd, raw, error, load
|
|
|
3283
3469
|
return;
|
|
3284
3470
|
}
|
|
3285
3471
|
|
|
3472
|
+
if (subcommand === 'phase-status') {
|
|
3473
|
+
// #1956: deterministic STATE.md-vs-ROADMAP.md phase-status drift.
|
|
3474
|
+
const { planningDir } = require('./lib/planning-workspace.cjs');
|
|
3475
|
+
const { stateExtractField, stateCurrentPositionSlice } = require('./lib/state-document.cjs');
|
|
3476
|
+
const { findRoadmapProgressTable } = require('./lib/roadmap-parser.cjs');
|
|
3477
|
+
const { phaseKeyFromProse } = require('./lib/phase-id.cjs');
|
|
3478
|
+
// STATE.md's YAML frontmatter carries its own lowercase `status:`
|
|
3479
|
+
// scalar ahead of the body's `## Current Position` prose "Status:"
|
|
3480
|
+
// line; stateExtractField's non-scoped regex would otherwise match
|
|
3481
|
+
// that frontmatter line first (it comes first in the file) and
|
|
3482
|
+
// silently report the wrong value. Strip frontmatter so extraction
|
|
3483
|
+
// is scoped to the body.
|
|
3484
|
+
const { stripFrontmatter } = require('./lib/frontmatter.cjs');
|
|
3485
|
+
|
|
3486
|
+
const phaseIdx = args.indexOf('--phase');
|
|
3487
|
+
const phaseArg = (phaseIdx !== -1 && args[phaseIdx + 1] && !args[phaseIdx + 1].startsWith('--'))
|
|
3488
|
+
? args[phaseIdx + 1]
|
|
3489
|
+
: undefined;
|
|
3490
|
+
|
|
3491
|
+
const dir = planningDir(cwd);
|
|
3492
|
+
const statePath = path.join(dir, 'STATE.md');
|
|
3493
|
+
const roadmapPath = path.join(dir, 'ROADMAP.md');
|
|
3494
|
+
|
|
3495
|
+
let stateContent = null;
|
|
3496
|
+
try {
|
|
3497
|
+
stateContent = fs.readFileSync(statePath, 'utf-8');
|
|
3498
|
+
} catch {
|
|
3499
|
+
// missing_state below
|
|
3500
|
+
}
|
|
3501
|
+
if (stateContent === null) {
|
|
3502
|
+
const phase = phaseArg !== undefined ? phaseKeyFromProse(phaseArg) : null;
|
|
3503
|
+
output({
|
|
3504
|
+
verdict: 'uncheckable',
|
|
3505
|
+
reason: 'missing_state',
|
|
3506
|
+
phase,
|
|
3507
|
+
stateStatus: null,
|
|
3508
|
+
roadmapStatus: null,
|
|
3509
|
+
authority: 'STATE.md',
|
|
3510
|
+
}, raw);
|
|
3511
|
+
return;
|
|
3512
|
+
}
|
|
3513
|
+
|
|
3514
|
+
let roadmapContent = null;
|
|
3515
|
+
try {
|
|
3516
|
+
roadmapContent = fs.readFileSync(roadmapPath, 'utf-8');
|
|
3517
|
+
} catch {
|
|
3518
|
+
// missing_roadmap below
|
|
3519
|
+
}
|
|
3520
|
+
|
|
3521
|
+
const stateBody = stripFrontmatter(stateContent);
|
|
3522
|
+
// #1956 fix: scope extraction to `## Current Position` (or `###`
|
|
3523
|
+
// in the bootstrap template) so a historical `Phase:` / `Status:`
|
|
3524
|
+
// line in an archive section (e.g. `## Session Continuity
|
|
3525
|
+
// Archive`) can't shadow the real one — same #2956 scope state.cts
|
|
3526
|
+
// uses for current_phase, via the shared owner in
|
|
3527
|
+
// state-document.cjs.
|
|
3528
|
+
//
|
|
3529
|
+
// Deliberately NO whole-body fallback here. `state.cts`'s WRITE
|
|
3530
|
+
// path falls back to the whole body when no Current Position
|
|
3531
|
+
// heading is found (legacy behavior it must preserve for
|
|
3532
|
+
// backward-compatible writes) — but that fallback is wrong for a
|
|
3533
|
+
// READ that feeds a drift finding: a STATE.md with no Current
|
|
3534
|
+
// Position heading is exactly the shape that let a stray historical
|
|
3535
|
+
// `Status:` line elsewhere in the body shadow the real value and
|
|
3536
|
+
// fabricate a 'drifted' verdict. A guess is worse than an
|
|
3537
|
+
// abstention for a drift detector, so an absent Current Position
|
|
3538
|
+
// section reports 'uncheckable' instead of guessing from the whole
|
|
3539
|
+
// document.
|
|
3540
|
+
const currentPositionBody = stateCurrentPositionSlice(stateBody);
|
|
3541
|
+
if (currentPositionBody === null) {
|
|
3542
|
+
const phase = phaseArg !== undefined ? phaseKeyFromProse(phaseArg) : null;
|
|
3543
|
+
output({
|
|
3544
|
+
verdict: 'uncheckable',
|
|
3545
|
+
reason: 'no_current_position',
|
|
3546
|
+
phase,
|
|
3547
|
+
stateStatus: null,
|
|
3548
|
+
roadmapStatus: null,
|
|
3549
|
+
authority: 'STATE.md',
|
|
3550
|
+
}, raw);
|
|
3551
|
+
return;
|
|
3552
|
+
}
|
|
3553
|
+
|
|
3554
|
+
// Resolve the target phase: --phase if given, else whatever
|
|
3555
|
+
// STATE.md's Current Position reports as current.
|
|
3556
|
+
const phase = phaseArg !== undefined
|
|
3557
|
+
? phaseKeyFromProse(phaseArg)
|
|
3558
|
+
: phaseKeyFromProse(stateExtractField(currentPositionBody, 'Phase'));
|
|
3559
|
+
|
|
3560
|
+
if (roadmapContent === null) {
|
|
3561
|
+
output({
|
|
3562
|
+
verdict: 'uncheckable',
|
|
3563
|
+
reason: 'missing_roadmap',
|
|
3564
|
+
phase,
|
|
3565
|
+
stateStatus: null,
|
|
3566
|
+
roadmapStatus: null,
|
|
3567
|
+
authority: 'STATE.md',
|
|
3568
|
+
}, raw);
|
|
3569
|
+
return;
|
|
3570
|
+
}
|
|
3571
|
+
|
|
3572
|
+
const stateStatus = stateExtractField(currentPositionBody, 'Status');
|
|
3573
|
+
|
|
3574
|
+
// #1956/#2012: scoped to `## Progress` first (decoy-avoidance) —
|
|
3575
|
+
// see findRoadmapProgressTable's doc comment (roadmap-parser.cts).
|
|
3576
|
+
const table = findRoadmapProgressTable(roadmapContent);
|
|
3577
|
+
const matchedRow = table
|
|
3578
|
+
? table.rows.find((row) => phaseKeyFromProse(row.Phase) === phase && phase !== null)
|
|
3579
|
+
: undefined;
|
|
3580
|
+
|
|
3581
|
+
if (!matchedRow) {
|
|
3582
|
+
const result = comparePhaseStatus({ stateStatus, roadmapStatus: null });
|
|
3583
|
+
output({
|
|
3584
|
+
verdict: 'uncheckable',
|
|
3585
|
+
reason: 'phase_not_in_roadmap',
|
|
3586
|
+
phase,
|
|
3587
|
+
stateStatus,
|
|
3588
|
+
roadmapStatus: null,
|
|
3589
|
+
stateRank: result.stateRank,
|
|
3590
|
+
roadmapRank: result.roadmapRank,
|
|
3591
|
+
authority: 'STATE.md',
|
|
3592
|
+
}, raw);
|
|
3593
|
+
return;
|
|
3594
|
+
}
|
|
3595
|
+
|
|
3596
|
+
const roadmapStatus = matchedRow.Status;
|
|
3597
|
+
const result = comparePhaseStatus({ stateStatus, roadmapStatus });
|
|
3598
|
+
output({
|
|
3599
|
+
verdict: result.verdict,
|
|
3600
|
+
phase,
|
|
3601
|
+
stateStatus,
|
|
3602
|
+
roadmapStatus,
|
|
3603
|
+
stateRank: result.stateRank,
|
|
3604
|
+
roadmapRank: result.roadmapRank,
|
|
3605
|
+
authority: 'STATE.md',
|
|
3606
|
+
}, raw);
|
|
3607
|
+
return;
|
|
3608
|
+
}
|
|
3609
|
+
|
|
3286
3610
|
error(
|
|
3287
|
-
`Unknown drift-guard subcommand: ${subcommand || '(none)'}. Available: authority, severity`,
|
|
3611
|
+
`Unknown drift-guard subcommand: ${subcommand || '(none)'}. Available: authority, severity, phase-status`,
|
|
3288
3612
|
ERROR_REASON.SDK_UNKNOWN_COMMAND,
|
|
3289
3613
|
);
|
|
3290
3614
|
}
|
|
3291
3615
|
|
|
3292
3616
|
|
|
3617
|
+
/**
|
|
3618
|
+
* #3275: resolve a DECLARED command name to the file a spawn can actually start.
|
|
3619
|
+
*
|
|
3620
|
+
* Lane descriptors (src/review-lane-descriptor.cts) declare BARE, platform-unaware
|
|
3621
|
+
* binary names ('codex', 'gemini', 'kimi', 'agy'), and `review-lane invoke`'s
|
|
3622
|
+
* `deps.spawn` + `deps.hasBinary` both need the on-disk form of that name. Before
|
|
3623
|
+
* this helper existed they disagreed: `hasBinary` scanned PATH WITH PATHEXT (so
|
|
3624
|
+
* probes reported lanes AVAILABLE on Windows) while `spawn` received the bare name
|
|
3625
|
+
* — `CreateProcess` performs no PATHEXT resolution, so every spawn-transport lane
|
|
3626
|
+
* ENOENT'd there, and the #3086 `.cmd`/`.bat` cmd.exe mediation gate never fired
|
|
3627
|
+
* because the declared name never carried an extension. One shared resolver is the
|
|
3628
|
+
* only shape that cannot drift back apart.
|
|
3629
|
+
*
|
|
3630
|
+
* win32: tries PATHEXT entries ONLY — never the bare name. npm global installs
|
|
3631
|
+
* drop an EXTENSIONLESS POSIX sh shim (`...\npm\codex`) next to `codex.CMD`; a
|
|
3632
|
+
* bare-name-first scan resolves to it, the mediation gate sees no `.cmd`, and the
|
|
3633
|
+
* ENOENT returns unchanged (field-reported on Windows 11 — see the #3275 issue
|
|
3634
|
+
* comment pinning exactly this pitfall).
|
|
3635
|
+
*
|
|
3636
|
+
* POSIX: answers EXISTENCE only (the old `hasBinary` contract, preserved
|
|
3637
|
+
* byte-for-byte) by scanning PATH for the bare name. `deps.spawn` does NOT consult
|
|
3638
|
+
* this on POSIX — the bare name goes to spawnSync unchanged and Node's own PATH
|
|
3639
|
+
* search does the work, so macOS/Linux behavior is untouched (#3275 acceptance).
|
|
3640
|
+
*
|
|
3641
|
+
* Path-like names (any '/' or '\') bypass the PATH scan: the name is already an
|
|
3642
|
+
* address, so it passes through when the file exists and is a file.
|
|
3643
|
+
*
|
|
3644
|
+
* #3411: the scan itself now lives in the declared platform seam
|
|
3645
|
+
* (`src/shell-command-projection.cts` → `resolveExecutableBinary`). This function is
|
|
3646
|
+
* the `bin/` entry point onto it and holds no copy of the logic — `CONTEXT.md`
|
|
3647
|
+
* declares that file "All OS-facing I/O; single platform seam", and a private
|
|
3648
|
+
* duplicate here is what made it untrue.
|
|
3649
|
+
*/
|
|
3650
|
+
function resolveSpawnBinary(name, platform = process.platform, env = process.env) {
|
|
3651
|
+
const { resolveExecutableBinary } = require('./lib/shell-command-projection.cjs');
|
|
3652
|
+
return resolveExecutableBinary(name, { platform, env });
|
|
3653
|
+
}
|
|
3654
|
+
|
|
3293
3655
|
const HOST_COMMAND_ROUTERS = {
|
|
3294
3656
|
// Each entry wraps its `route*Command` router so it receives the module-scope
|
|
3295
3657
|
// lib the old `case` arm passed, plus the per-dispatch context
|
|
@@ -3339,6 +3701,7 @@ const HOST_COMMAND_ROUTERS = {
|
|
|
3339
3701
|
'find-phase': routeFindPhase,
|
|
3340
3702
|
'commit': routeCommit,
|
|
3341
3703
|
'check-commit': routeCheckCommit,
|
|
3704
|
+
'commit-docs-guard': routeCommitDocsGuard,
|
|
3342
3705
|
'commit-to-subrepo': routeCommitToSubrepo,
|
|
3343
3706
|
'pr-subrepo': routePrSubrepo,
|
|
3344
3707
|
'verify-summary': routeVerifySummary,
|
|
@@ -3357,8 +3720,10 @@ const HOST_COMMAND_ROUTERS = {
|
|
|
3357
3720
|
'normalize-test-command': routeNormalizeTestCommand,
|
|
3358
3721
|
'dispatch-should-flatten': routeDispatchShouldFlatten,
|
|
3359
3722
|
'dispatch-isolation': routeDispatchIsolation,
|
|
3723
|
+
'inspect-dispatch-isolation': routeInspectDispatchIsolation,
|
|
3360
3724
|
'record-dispatch-isolation': routeRecordDispatchIsolation,
|
|
3361
3725
|
'resolve-dispatch-type': routeResolveDispatchType,
|
|
3726
|
+
'resolve-agent': routeResolveAgent,
|
|
3362
3727
|
'agent-skills': routeAgentSkills,
|
|
3363
3728
|
'skill-manifest': routeSkillManifest,
|
|
3364
3729
|
'history-digest': routeHistoryDigest,
|
|
@@ -3604,14 +3969,14 @@ function runWithTimeout(argv) {
|
|
|
3604
3969
|
// independently hand-maintained sites and nothing previously caught them
|
|
3605
3970
|
// drifting apart when a query command was added to only one or two.
|
|
3606
3971
|
const TOP_LEVEL_USAGE = 'Usage: gsd-tools <command> [args] [--raw] [--pick <field>] [--cwd <path>] [--ws <name>] [--json-errors]\n' +
|
|
3607
|
-
'Commands: agent, agent-skills, assumption-delta, audit-open, audit-uat, check, check-commit, commit, commit-to-subrepo, pr-subrepo, ' +
|
|
3972
|
+
'Commands: agent, agent-skills, assumption-delta, audit-open, audit-uat, check, check-commit, commit, commit-docs-guard, commit-to-subrepo, pr-subrepo, ' +
|
|
3608
3973
|
'config-ensure-section, config-get, config-new-project, config-path, config-set, migrate-config, normalize-test-command, ' +
|
|
3609
3974
|
'context-predicates, current-timestamp, detect-custom-files, docs-init, drift-guard, effort, extract-messages, find-phase, ' +
|
|
3610
3975
|
'from-gsd2, frontmatter, gap-analysis, generate-claude-md, generate-claude-profile, ' +
|
|
3611
3976
|
'generate-dev-preferences, generate-slug, graphify, history-digest, init, intel, ' +
|
|
3612
3977
|
'capability, classify-confidence, git, learnings, list-seeds, list-todos, loop, milestone, package-legitimacy, phase, phase-plan-index, phases, profile-questionnaire, ' +
|
|
3613
3978
|
'profile-sample, progress, project-instruction-file, prompt-budget, quick-tasks-append, requirements, research-plan, research-store, resolve-granularity, resolve-model, restore-custom-files, roadmap, scaffold, smart-entry, state, ' +
|
|
3614
|
-
'config-set-model-profile, dispatch-isolation, dispatch-should-flatten, record-dispatch-isolation, estimate-calibrate, estimate-calibration, estimate-check, resolve-dispatch-type, ' +
|
|
3979
|
+
'config-set-model-profile, dispatch-isolation, dispatch-should-flatten, inspect-dispatch-isolation, record-dispatch-isolation, estimate-calibrate, estimate-calibration, estimate-check, resolve-agent, resolve-dispatch-type, ' +
|
|
3615
3980
|
'resolve-execution, review-lane, skill-manifest, skills-root, state-snapshot, stats, summary-extract, teams-status, todo, uat, update-context, verification, websearch, windows, ' +
|
|
3616
3981
|
'task, template, user-story, validate, verify, verify-path-exists, verify-summary, eval, workstream, worktree\n\n' +
|
|
3617
3982
|
'Global flags:\n' +
|
|
@@ -3769,8 +4134,21 @@ async function main() {
|
|
|
3769
4134
|
// Priority: --ws flag > GSD_WORKSTREAM env var > session/shared pointer > null.
|
|
3770
4135
|
let workstreamContext = null;
|
|
3771
4136
|
try {
|
|
4137
|
+
// #3579 root-cause fix: this bootstrap resolution only decides whether to
|
|
4138
|
+
// populate GSD_WORKSTREAM env for downstream routing — it is a check, not
|
|
4139
|
+
// the consuming read. Using the mutating getActiveWorkstream here
|
|
4140
|
+
// self-healed (cleared) a present-but-unresolvable pointer BEFORE the
|
|
4141
|
+
// dispatched command's own resolution/diagnostic ran, so a second read in
|
|
4142
|
+
// the same process (e.g. a subcommand's own getActiveWorkstream call, or
|
|
4143
|
+
// a fail-safe guard's diagnoseUnresolvedActiveWorkstream) observed
|
|
4144
|
+
// already-cleared state — silently falling through to a fallback marker
|
|
4145
|
+
// it should never have inherited (isolation violation), or losing the
|
|
4146
|
+
// evidence a diagnostic needed to explain why nothing resolved. peek
|
|
4147
|
+
// shares the identical resolution logic and only differs by never
|
|
4148
|
+
// calling adapter.clear(); self-heal still happens, exactly once, at
|
|
4149
|
+
// whichever call site actually consumes the workstream for real.
|
|
3772
4150
|
workstreamContext = resolveActiveWorkstream(cwd, args, process.env, {
|
|
3773
|
-
getStored:
|
|
4151
|
+
getStored: peekActiveWorkstream,
|
|
3774
4152
|
});
|
|
3775
4153
|
args = workstreamContext.args;
|
|
3776
4154
|
// Set env var so all modules (planningDir, planningPaths) auto-resolve workstream paths.
|
|
@@ -4024,5 +4402,8 @@ module.exports = {
|
|
|
4024
4402
|
TOP_LEVEL_USAGE,
|
|
4025
4403
|
skipsRootResolution,
|
|
4026
4404
|
resolveMainWorktreeCwd,
|
|
4405
|
+
// #3275: exported for tests — the shared PATH+PATHEXT resolver behind
|
|
4406
|
+
// review-lane invoke's `deps.spawn` / `deps.hasBinary` seams.
|
|
4407
|
+
resolveSpawnBinary,
|
|
4027
4408
|
};
|
|
4028
4409
|
|