@opengsd/gsd-core 1.9.1 → 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 +2 -3
- package/.opencode/plugins/gsd-core.js +8 -1
- package/agents/gsd-code-fixer.md +27 -3
- package/agents/gsd-debug-session-manager.md +11 -0
- package/agents/gsd-debugger.md +12 -246
- package/agents/gsd-doc-synthesizer.md +2 -4
- package/agents/gsd-executor.md +12 -10
- package/agents/gsd-integration-checker.md +3 -0
- package/agents/gsd-mempalace-curator.md +5 -2
- package/agents/gsd-phase-researcher.md +20 -1
- package/agents/gsd-plan-checker.md +46 -0
- package/agents/gsd-planner.md +49 -54
- package/agents/gsd-roadmapper.md +21 -3
- package/agents/gsd-user-profiler.md +3 -0
- package/agents/gsd-verifier.md +26 -73
- package/bin/install.js +1272 -1238
- 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 +2 -2
- package/commands/gsd/mempalace-recall.md +1 -1
- package/commands/gsd/new-milestone.md +2 -2
- package/commands/gsd/plan-phase.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 +1009 -115
- package/gsd-core/bin/lib/active-workstream-store.cjs +153 -12
- package/gsd-core/bin/lib/agent-install-check.cjs +268 -38
- package/gsd-core/bin/lib/api-coverage.cjs +123 -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 +926 -202
- package/gsd-core/bin/lib/broken-windows.cjs +36 -6
- 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 +608 -148
- 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 +507 -24
- package/gsd-core/bin/lib/capability-writer.cjs +3 -2
- package/gsd-core/bin/lib/check-command-router.cjs +114 -38
- 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 +94 -0
- package/gsd-core/bin/lib/command-roster.cjs +44 -1
- package/gsd-core/bin/lib/commands.cjs +665 -99
- package/gsd-core/bin/lib/commonjs-marker.cjs +142 -0
- package/gsd-core/bin/lib/complexity-trigger.cjs +1172 -0
- package/gsd-core/bin/lib/config-loader.cjs +76 -0
- package/gsd-core/bin/lib/config.cjs +22 -2
- package/gsd-core/bin/lib/context-composer.cjs +278 -0
- package/gsd-core/bin/lib/context-predicates.cjs +506 -0
- package/gsd-core/bin/lib/core-utils.cjs +217 -40
- package/gsd-core/bin/lib/decisions.cjs +23 -0
- package/gsd-core/bin/lib/docs.cjs +3 -2
- package/gsd-core/bin/lib/external-job.cjs +19 -4
- package/gsd-core/bin/lib/fallow-runner.cjs +20 -44
- package/gsd-core/bin/lib/frontmatter.cjs +239 -32
- package/gsd-core/bin/lib/gap-checker.cjs +68 -7
- package/gsd-core/bin/lib/gate-predicate-evaluator.cjs +57 -6
- package/gsd-core/bin/lib/git-base-branch.cjs +160 -15
- package/gsd-core/bin/lib/graphify.cjs +142 -27
- package/gsd-core/bin/lib/gsd2-import.cjs +37 -5
- 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-integration.cjs +13 -1
- package/gsd-core/bin/lib/host-runtime-detection.cjs +134 -0
- package/gsd-core/bin/lib/init-command-router.cjs +83 -8
- package/gsd-core/bin/lib/init.cjs +1325 -169
- package/gsd-core/bin/lib/install-effort-resolver.cjs +73 -30
- package/gsd-core/bin/lib/install-engine.cjs +805 -264
- 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 +160 -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-migration-authoring.cjs +3 -1
- package/gsd-core/bin/lib/installer-migration-report.cjs +4 -0
- package/gsd-core/bin/lib/installer-migrations/007-retire-config-root-commonjs-marker.cjs +149 -0
- package/gsd-core/bin/lib/installer-migrations/008-cursor-retire-commands-surface.cjs +55 -0
- package/gsd-core/bin/lib/installer-migrations/009-pi-retire-reserved-hooks-dir.cjs +199 -0
- package/gsd-core/bin/lib/installer-migrations.cjs +206 -13
- package/gsd-core/bin/lib/io.cjs +38 -3
- package/gsd-core/bin/lib/markdown-sectionizer.cjs +8 -1
- package/gsd-core/bin/lib/markdown-table.cjs +133 -20
- package/gsd-core/bin/lib/mcp-catalog.cjs +518 -0
- package/gsd-core/bin/lib/mcp-server.cjs +135 -3
- package/gsd-core/bin/lib/milestone-lock.cjs +248 -0
- package/gsd-core/bin/lib/milestone.cjs +821 -109
- 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 +507 -36
- package/gsd-core/bin/lib/phase-lifecycle.cjs +28 -3
- package/gsd-core/bin/lib/phase-locator.cjs +258 -58
- package/gsd-core/bin/lib/phase.cjs +891 -156
- package/gsd-core/bin/lib/plan-dependency-graph.cjs +303 -0
- 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 +60 -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/prompt-budget.cjs +128 -165
- package/gsd-core/bin/lib/refactor-trigger-command-router.cjs +740 -0
- package/gsd-core/bin/lib/retired-artifact-cleanup.cjs +85 -0
- package/gsd-core/bin/lib/review-lane-descriptor.cjs +108 -0
- package/gsd-core/bin/lib/review-lane-invocation.cjs +30 -0
- package/gsd-core/bin/lib/review-lane-runner.cjs +447 -68
- package/gsd-core/bin/lib/review-reviewer-selection.cjs +13 -18
- package/gsd-core/bin/lib/roadmap-command-router.cjs +76 -9
- package/gsd-core/bin/lib/roadmap-parser.cjs +1035 -194
- package/gsd-core/bin/lib/roadmap-upgrade.cjs +37 -10
- package/gsd-core/bin/lib/roadmap.cjs +405 -84
- package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +795 -100
- package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +14 -2
- package/gsd-core/bin/lib/runtime-artifact-layout.cjs +440 -57
- package/gsd-core/bin/lib/runtime-config-adapter-registry.cjs +3 -2
- package/gsd-core/bin/lib/runtime-homes.cjs +220 -41
- package/gsd-core/bin/lib/runtime-hooks-surface.cjs +220 -44
- 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/section-manifest.cjs +209 -0
- package/gsd-core/bin/lib/security.cjs +104 -5
- package/gsd-core/bin/lib/shell-command-projection.cjs +388 -30
- package/gsd-core/bin/lib/smart-entry.cjs +154 -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 +424 -105
- package/gsd-core/bin/lib/state.cjs +1927 -401
- package/gsd-core/bin/lib/surface.cjs +35 -10
- 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 +20 -4
- package/gsd-core/bin/lib/uat.cjs +706 -64
- 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 +33 -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 +287 -20
- package/gsd-core/bin/lib/verify.cjs +368 -880
- package/gsd-core/bin/lib/workflow-fragments.cjs +557 -0
- package/gsd-core/bin/lib/workstream-inventory-builder.cjs +203 -19
- package/gsd-core/bin/lib/workstream-inventory.cjs +576 -31
- package/gsd-core/bin/lib/workstream.cjs +8 -2
- package/gsd-core/bin/lib/worktree-base-ref.cjs +50 -6
- package/gsd-core/bin/lib/worktree-safety.cjs +450 -125
- package/gsd-core/bin/shared/config-defaults.manifest.json +1 -0
- package/gsd-core/bin/shared/config-schema.manifest.json +9 -1
- package/gsd-core/references/agent-contracts.md +43 -26
- package/gsd-core/references/artifact-types.md +10 -3
- package/gsd-core/references/autonomous-ui-design-contract.md +42 -0
- package/gsd-core/references/checkpoints.md +2 -2
- package/gsd-core/references/context-budget.md +1 -1
- package/gsd-core/references/debugger-techniques.md +255 -0
- 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/research-documentation-lookup.md +5 -3
- package/gsd-core/references/revision-loop.md +1 -1
- package/gsd-core/references/specless-probe-fallback.md +8 -7
- package/gsd-core/references/universal-anti-patterns.md +3 -3
- package/gsd-core/references/verifier-phase-gates.md +192 -0
- package/gsd-core/references/verifier-wiring-patterns.md +100 -0
- package/gsd-core/references/verify-mvp-mode.md +1 -1
- package/gsd-core/references/workstream-flag.md +22 -6
- package/gsd-core/references/worktree-branch-check.md +2 -2
- 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/summary-complex.md +2 -0
- package/gsd-core/templates/summary-minimal.md +2 -0
- package/gsd-core/templates/summary-standard.md +2 -0
- package/gsd-core/templates/summary.md +2 -0
- package/gsd-core/templates/verification-report.md +9 -1
- package/gsd-core/workflows/ai-integration-phase.md +9 -11
- package/gsd-core/workflows/audit-milestone.md +3 -0
- package/gsd-core/workflows/autonomous/steps/converge-banner.md +1 -0
- package/gsd-core/workflows/autonomous/steps/converge-dispatch-bg.md +11 -0
- package/gsd-core/workflows/autonomous/steps/converge-dispatch-inline.md +7 -0
- package/gsd-core/workflows/autonomous/steps/converge-fail-fast.md +21 -0
- package/gsd-core/workflows/autonomous/steps/converge-loop.md +7 -0
- package/gsd-core/workflows/autonomous.md +33 -70
- package/gsd-core/workflows/cleanup.md +62 -3
- package/gsd-core/workflows/code-review/steps/dispatch-fix.md +39 -0
- package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +93 -0
- package/gsd-core/workflows/code-review-fix.md +37 -10
- package/gsd-core/workflows/code-review.md +74 -166
- package/gsd-core/workflows/complete-milestone/steps/git-tag.md +29 -0
- package/gsd-core/workflows/complete-milestone.md +160 -95
- package/gsd-core/workflows/debug.md +16 -17
- package/gsd-core/workflows/diagnose-issues.md +56 -8
- 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/steps/auto-advance-dispatch.md +15 -0
- package/gsd-core/workflows/discuss-phase-assumptions.md +7 -17
- package/gsd-core/workflows/docs-update/steps/dispatch-monorepo-packages.md +51 -0
- package/gsd-core/workflows/docs-update.md +8 -51
- 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 +64 -7
- package/gsd-core/workflows/execute-phase/steps/gap-closure-artifacts.md +50 -0
- package/gsd-core/workflows/execute-phase/steps/partial-wave.md +31 -0
- 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 +21 -0
- package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +42 -0
- package/gsd-core/workflows/execute-phase/steps/regression-gate.md +43 -37
- package/gsd-core/workflows/execute-phase.md +103 -187
- 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/help/modes/full.md +6 -1
- package/gsd-core/workflows/import.md +4 -4
- package/gsd-core/workflows/ingest-docs.md +7 -6
- package/gsd-core/workflows/mvp-phase.md +6 -3
- package/gsd-core/workflows/new-milestone/steps/project-md-milestone-write.md +16 -0
- package/gsd-core/workflows/new-milestone/steps/reset-phase-safety.md +19 -0
- package/gsd-core/workflows/new-milestone.md +35 -47
- package/gsd-core/workflows/new-project/steps/auto-mode-config.md +176 -0
- package/gsd-core/workflows/new-project/steps/auto-mode-detection.md +32 -0
- package/gsd-core/workflows/new-project/steps/codebase-map-offer.md +18 -0
- package/gsd-core/workflows/new-project.md +27 -240
- package/gsd-core/workflows/next.md +12 -0
- package/gsd-core/workflows/plan-phase/steps/adr-ingest-express-path.md +15 -0
- package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +110 -0
- package/gsd-core/workflows/plan-phase/steps/prd-express-gate.md +8 -0
- package/gsd-core/workflows/plan-phase/steps/research-only-early-exit.md +17 -0
- package/gsd-core/workflows/plan-phase/steps/research-only-modifiers.md +16 -0
- package/gsd-core/workflows/plan-phase/steps/reviews-prerequisite.md +17 -0
- package/gsd-core/workflows/plan-phase/steps/stall-detection-helpers.md +149 -0
- package/gsd-core/workflows/plan-phase.md +89 -209
- package/gsd-core/workflows/plan-review-convergence.md +50 -2
- package/gsd-core/workflows/progress/steps/forensic-audit.md +125 -0
- package/gsd-core/workflows/progress/steps/mvp-display.md +18 -0
- package/gsd-core/workflows/progress.md +45 -159
- package/gsd-core/workflows/quick/steps/discussion-phase.md +124 -0
- package/gsd-core/workflows/quick/steps/plan-checker-loop.md +111 -0
- package/gsd-core/workflows/quick/steps/quick-verification.md +67 -0
- package/gsd-core/workflows/quick/steps/research-phase.md +72 -0
- package/gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md +37 -0
- package/gsd-core/workflows/quick.md +55 -405
- package/gsd-core/workflows/resume-project.md +3 -0
- package/gsd-core/workflows/review/steps/reviewer-instances-note-1.md +4 -0
- package/gsd-core/workflows/review/steps/reviewer-instances-note-2.md +3 -0
- package/gsd-core/workflows/review.md +41 -13
- package/gsd-core/workflows/section-manifest.json +219 -0
- 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/sketch.md +1 -1
- package/gsd-core/workflows/spec-phase.md +41 -20
- package/gsd-core/workflows/spike-wrap-up.md +20 -5
- package/gsd-core/workflows/spike.md +50 -16
- package/gsd-core/workflows/sync-skills.md +106 -13
- package/gsd-core/workflows/transition/steps/workstream-collision-check.md +17 -0
- package/gsd-core/workflows/transition.md +53 -31
- package/gsd-core/workflows/ui-phase.md +13 -12
- package/gsd-core/workflows/ui-review.md +2 -2
- package/gsd-core/workflows/update/steps/channel-banner.md +7 -0
- package/gsd-core/workflows/update.md +19 -8
- package/gsd-core/workflows/validate-phase.md +1 -1
- package/gsd-core/workflows/verify-work/steps/automated-ui-verification.md +36 -0
- package/gsd-core/workflows/verify-work/steps/mvp-uat-framing.md +21 -0
- package/gsd-core/workflows/verify-work.md +17 -65
- package/hooks/dist/gsd-agent-isolation-guard.js +517 -0
- package/hooks/dist/gsd-check-update-worker.js +64 -12
- 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 +607 -26
- 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 +45 -24
- package/hooks/dist/gsd-statusline.js +90 -6
- package/hooks/dist/gsd-update-banner.js +22 -1
- package/hooks/dist/gsd-workflow-guard.js +134 -36
- package/hooks/dist/gsd-worktree-path-guard.js +2 -1
- package/hooks/dist/gsd-write-guard.js +359 -0
- 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 +277 -0
- package/hooks/dist/managed-hooks-registry.cjs +2 -0
- package/hooks/gsd-agent-isolation-guard.js +517 -0
- package/hooks/gsd-check-update-worker.js +64 -12
- package/hooks/gsd-check-update.js +19 -1
- package/hooks/gsd-cursor-pre-tool.js +0 -3
- package/hooks/gsd-cursor-subagent-start.js +607 -26
- package/hooks/gsd-cursor-subagent-stop.js +3 -2
- package/hooks/gsd-prompt-guard.js +21 -20
- package/hooks/gsd-read-injection-scanner.js +45 -24
- package/hooks/gsd-statusline.js +90 -6
- package/hooks/gsd-update-banner.js +22 -1
- package/hooks/gsd-workflow-guard.js +134 -36
- package/hooks/gsd-worktree-path-guard.js +2 -1
- package/hooks/gsd-write-guard.js +359 -0
- package/hooks/hooks.json +12 -0
- 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 +277 -0
- package/hooks/managed-hooks-registry.cjs +2 -0
- package/package.json +31 -10
- package/pi/gsd.cjs +71 -12
- 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/build-hooks.js +9 -0
- package/scripts/changeset/lint.cjs +68 -6
- package/scripts/changeset/serialize.cjs +5 -1
- 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-matrix.cjs +1 -1
- package/scripts/gen-capability-registry.cjs +3 -15
- package/scripts/gen-context-index.cjs +439 -0
- package/scripts/gen-health-docs.cjs +390 -0
- package/scripts/gen-inventory-manifest.cjs +150 -4
- package/scripts/gen-loop-host-contract.cjs +4 -24
- package/scripts/gen-prompt-budget-parity-corpus.cjs +645 -0
- package/scripts/gen-registry.cjs +3 -14
- package/scripts/gen-section-manifest.cjs +638 -0
- package/scripts/generate-package-identity.cjs +4 -2
- 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 +15 -54
- 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-compiled-artifact-sync.cjs +6 -1
- 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-docs-command-form.cjs +195 -0
- package/scripts/lint-docs-required.cjs +9 -1
- package/scripts/lint-emitted-drift-ack.cjs +215 -20
- package/scripts/lint-eslint-glob-coverage.allowlist.json +34 -0
- package/scripts/lint-eslint-glob-coverage.cjs +340 -0
- package/scripts/lint-example-parser-parity.cjs +395 -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 +40 -3
- package/scripts/lint-unreachable-guard-drift.cjs +843 -0
- package/scripts/lint-vendored-deps.cjs +124 -0
- package/scripts/mutation-matrix.cjs +13 -0
- package/scripts/pr-changed-files.cjs +63 -0
- package/scripts/pr-template-policy.cjs +14 -4
- package/scripts/prompt-injection-scan.sh +52 -6
- 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 +2 -2
- package/skills/gsd-mempalace-recall/SKILL.md +1 -1
- package/skills/gsd-new-milestone/SKILL.md +2 -2
- package/skills/gsd-next/SKILL.md +0 -1
- package/skills/gsd-plan-phase/SKILL.md +1 -2
- 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 -577
- package/scripts/affected-tests-lib.cjs +0 -554
- package/scripts/gen-emitted-baseline.cjs +0 -145
- package/scripts/lint-allow-test-rule-refs.cjs +0 -162
- package/scripts/run-affected-tests.cjs +0 -7
- package/scripts/run-tests.cjs +0 -1050
|
@@ -27,6 +27,9 @@
|
|
|
27
27
|
* the additive, importable server surface a host (or the bin shim) drives.
|
|
28
28
|
*/
|
|
29
29
|
'use strict';
|
|
30
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
31
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
32
|
+
};
|
|
30
33
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
31
34
|
exports.SERVER_NAME = exports.PROTOCOL_VERSION = void 0;
|
|
32
35
|
exports.handleMessage = handleMessage;
|
|
@@ -36,9 +39,62 @@ const stateIo = require("./state-io.cjs");
|
|
|
36
39
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
37
40
|
const shellCommandProjection = require("./shell-command-projection.cjs");
|
|
38
41
|
const { dispatchGsdCommand } = shellCommandProjection;
|
|
42
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
43
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
44
|
+
const mcp_catalog_cjs_1 = require("./mcp-catalog.cjs");
|
|
39
45
|
exports.PROTOCOL_VERSION = '2024-11-05';
|
|
40
46
|
exports.SERVER_NAME = 'gsd-core';
|
|
41
|
-
|
|
47
|
+
// #3072: resolve SERVER_VERSION from package.json (source tree) or the
|
|
48
|
+
// installed gsd-core/VERSION marker, WITHOUT a top-level `require(...)` —
|
|
49
|
+
// mirrors the established, already-precedented resolver in
|
|
50
|
+
// `runtime-artifact-conversion.cts`'s `gsdVersion()` (#1383): a top-level
|
|
51
|
+
// require would throw on any runtime whose root has no package.json/VERSION,
|
|
52
|
+
// and `scripts/sync-manifest-versions.cjs` is not a fit here — its
|
|
53
|
+
// `VERSIONED_MANIFESTS` list stamps a `version` FIELD into hand-authored JSON
|
|
54
|
+
// manifests (plugin.json, marketplace.json, vscode/package.json); a `.cts`
|
|
55
|
+
// source constant is not a JSON document that script can round-trip through
|
|
56
|
+
// `readJson`/`setByPath`, and teaching it to text-edit TypeScript source
|
|
57
|
+
// would be a much larger footprint than this lazy, cached, defensive read.
|
|
58
|
+
// Resolved once per process (never per-request) and cached; a failed lookup
|
|
59
|
+
// degrades to '0.0.0' (never `undefined`, never a crash) rather than making
|
|
60
|
+
// `initialize`'s `serverInfo.version` field absent or malformed.
|
|
61
|
+
const SEMVER_PREFIX = /^\d+\.\d+\.\d+/;
|
|
62
|
+
let cachedServerVersion;
|
|
63
|
+
function resolveServerVersion() {
|
|
64
|
+
if (cachedServerVersion !== undefined)
|
|
65
|
+
return cachedServerVersion;
|
|
66
|
+
let version = '0.0.0';
|
|
67
|
+
try {
|
|
68
|
+
const v = node_fs_1.default.readFileSync(node_path_1.default.join(__dirname, '..', '..', 'VERSION'), 'utf8').trim();
|
|
69
|
+
if (SEMVER_PREFIX.test(v))
|
|
70
|
+
version = v;
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
/* not an installed tree (no gsd-core/VERSION) */
|
|
74
|
+
}
|
|
75
|
+
if (version === '0.0.0') {
|
|
76
|
+
try {
|
|
77
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports -- lazy, defensive: a top-level require would throw on a runtime root with no package.json.
|
|
78
|
+
const pkg = require(node_path_1.default.join(__dirname, '..', '..', '..', 'package.json'));
|
|
79
|
+
if (pkg && typeof pkg.version === 'string' && SEMVER_PREFIX.test(pkg.version))
|
|
80
|
+
version = pkg.version;
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
/* runtime root has no package.json */
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
cachedServerVersion = version;
|
|
87
|
+
return version;
|
|
88
|
+
}
|
|
89
|
+
// Catalog is built once per process, lazily, and cached (design "Shape" /
|
|
90
|
+
// Gall's Law — no watching, no invalidation; Known limits: the package is
|
|
91
|
+
// immutable in every install mode we ship).
|
|
92
|
+
let cachedCatalog;
|
|
93
|
+
function getCatalog() {
|
|
94
|
+
if (cachedCatalog === undefined)
|
|
95
|
+
cachedCatalog = (0, mcp_catalog_cjs_1.buildCatalog)();
|
|
96
|
+
return cachedCatalog;
|
|
97
|
+
}
|
|
42
98
|
// JSON-RPC 2.0 error codes.
|
|
43
99
|
const PARSE_ERROR = -32700;
|
|
44
100
|
const INVALID_REQUEST = -32600;
|
|
@@ -93,6 +149,32 @@ function okResponse(id, result) {
|
|
|
93
149
|
function asString(v) {
|
|
94
150
|
return typeof v === 'string' ? v : null;
|
|
95
151
|
}
|
|
152
|
+
/** Extract the {@link REASON} carried by a `CatalogError`, if any (see `mcp-catalog.cts`). */
|
|
153
|
+
function catalogErrorReason(err) {
|
|
154
|
+
const reason = err && typeof err === 'object' ? err.reason : undefined;
|
|
155
|
+
return typeof reason === 'string' ? reason : undefined;
|
|
156
|
+
}
|
|
157
|
+
function catalogErrorMessage(err) {
|
|
158
|
+
return err instanceof Error ? err.message : String(err);
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Map a `mcp-catalog.cts` `CatalogError.reason` to a JSON-RPC error code.
|
|
162
|
+
* `READ_FAILED` is a server-side IO/compose failure (`INTERNAL_ERROR`);
|
|
163
|
+
* every other reason (unknown uri/prompt/cursor/root, malformed/traversal
|
|
164
|
+
* uri, wrong-typed param) is a client-supplied-value problem (`INVALID_PARAMS`)
|
|
165
|
+
* — deliberately never `METHOD_NOT_FOUND`, so a refusal is never
|
|
166
|
+
* indistinguishable from the method simply not existing (test-matrix rows
|
|
167
|
+
* 33/35).
|
|
168
|
+
*/
|
|
169
|
+
function catalogErrorCode(err) {
|
|
170
|
+
return catalogErrorReason(err) === mcp_catalog_cjs_1.REASON.READ_FAILED ? INTERNAL_ERROR : INVALID_PARAMS;
|
|
171
|
+
}
|
|
172
|
+
function wireResource(entry) {
|
|
173
|
+
return { uri: entry.uri, name: entry.name, title: entry.title, description: entry.description, mimeType: entry.mimeType };
|
|
174
|
+
}
|
|
175
|
+
function wirePrompt(entry) {
|
|
176
|
+
return { name: entry.name, title: entry.title, description: entry.description };
|
|
177
|
+
}
|
|
96
178
|
function callTool(name, args, ctx) {
|
|
97
179
|
const a = (args && typeof args === 'object' ? args : {});
|
|
98
180
|
const cwd = asString(ctx.cwd) || process.cwd();
|
|
@@ -148,8 +230,12 @@ function handleMessage(request, ctx = {}) {
|
|
|
148
230
|
case 'initialize':
|
|
149
231
|
result = {
|
|
150
232
|
protocolVersion: exports.PROTOCOL_VERSION,
|
|
151
|
-
|
|
152
|
-
|
|
233
|
+
// #3072: resources/prompts declared alongside tools. Deliberately NO
|
|
234
|
+
// subscribe/listChanged — nothing ever sends those notifications
|
|
235
|
+
// (design row 2 / Hyrum's Law: advertising an unimplemented
|
|
236
|
+
// notification is a lie a host would act on).
|
|
237
|
+
capabilities: { tools: {}, resources: {}, prompts: {} },
|
|
238
|
+
serverInfo: { name: exports.SERVER_NAME, version: resolveServerVersion() },
|
|
153
239
|
};
|
|
154
240
|
break;
|
|
155
241
|
case 'tools/list':
|
|
@@ -163,6 +249,52 @@ function handleMessage(request, ctx = {}) {
|
|
|
163
249
|
result = callTool(toolName, params.arguments, ctx);
|
|
164
250
|
break;
|
|
165
251
|
}
|
|
252
|
+
case 'resources/list': {
|
|
253
|
+
const params = (request.params && typeof request.params === 'object' ? request.params : {});
|
|
254
|
+
try {
|
|
255
|
+
const cursor = typeof params.cursor === 'string' ? params.cursor : undefined;
|
|
256
|
+
const pageSize = typeof params.pageSize === 'number' ? params.pageSize : undefined;
|
|
257
|
+
const page = (0, mcp_catalog_cjs_1.listResources)(getCatalog(), { cursor, pageSize });
|
|
258
|
+
result = page.nextCursor === undefined
|
|
259
|
+
? { resources: page.resources.map(wireResource) }
|
|
260
|
+
: { resources: page.resources.map(wireResource), nextCursor: page.nextCursor };
|
|
261
|
+
}
|
|
262
|
+
catch (e) {
|
|
263
|
+
return errorResponse(id, catalogErrorCode(e), catalogErrorMessage(e));
|
|
264
|
+
}
|
|
265
|
+
break;
|
|
266
|
+
}
|
|
267
|
+
case 'resources/read': {
|
|
268
|
+
const params = (request.params && typeof request.params === 'object' ? request.params : {});
|
|
269
|
+
try {
|
|
270
|
+
const read = (0, mcp_catalog_cjs_1.readResource)(getCatalog(), params.uri);
|
|
271
|
+
result = { contents: [{ uri: read.uri, mimeType: read.mimeType, text: read.text }] };
|
|
272
|
+
}
|
|
273
|
+
catch (e) {
|
|
274
|
+
return errorResponse(id, catalogErrorCode(e), catalogErrorMessage(e));
|
|
275
|
+
}
|
|
276
|
+
break;
|
|
277
|
+
}
|
|
278
|
+
case 'prompts/list':
|
|
279
|
+
result = {
|
|
280
|
+
prompts: [...getCatalog().prompts.values()]
|
|
281
|
+
.map(wirePrompt)
|
|
282
|
+
.sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0)),
|
|
283
|
+
};
|
|
284
|
+
break;
|
|
285
|
+
case 'prompts/get': {
|
|
286
|
+
const params = (request.params && typeof request.params === 'object' ? request.params : {});
|
|
287
|
+
const name = asString(params.name);
|
|
288
|
+
if (!name)
|
|
289
|
+
return errorResponse(id, INVALID_PARAMS, 'prompts/get requires string "name".');
|
|
290
|
+
try {
|
|
291
|
+
result = (0, mcp_catalog_cjs_1.getPrompt)(getCatalog(), name, params.arguments);
|
|
292
|
+
}
|
|
293
|
+
catch (e) {
|
|
294
|
+
return errorResponse(id, catalogErrorCode(e), catalogErrorMessage(e));
|
|
295
|
+
}
|
|
296
|
+
break;
|
|
297
|
+
}
|
|
166
298
|
default:
|
|
167
299
|
if (isNotification)
|
|
168
300
|
return null;
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Milestone Lock — advisory (phase, session) claim over the single Current
|
|
4
|
+
* Position slot in STATE.md (#3311).
|
|
5
|
+
*
|
|
6
|
+
* Problem it solves: two sessions running DIFFERENT phases in the same working
|
|
7
|
+
* tree both read-modify-write `## Current Position`. The byte-level STATE.md
|
|
8
|
+
* lock (#464) already serializes those writes, so no write is lost — but the
|
|
9
|
+
* SEMANTIC clobber is silent: `state.advance-plan` takes no phase argument and
|
|
10
|
+
* advances whatever plan the (possibly just clobbered) Current Position names,
|
|
11
|
+
* and nothing ever surfaces that two sessions claimed two different phases
|
|
12
|
+
* against the one single-slot field.
|
|
13
|
+
*
|
|
14
|
+
* This module is the maintainer-chosen fix (issue #3311 comment): a milestone
|
|
15
|
+
* lock keyed by phase + session id, with a second session WARNED instead of
|
|
16
|
+
* silently overwriting. It is an advisory claim file, not a mutex:
|
|
17
|
+
*
|
|
18
|
+
* - `.planning/milestone.lock` (workstream-scoped via planningDir) holds
|
|
19
|
+
* `{ phase, session, pid, updated_at }`.
|
|
20
|
+
* - Session identity reuses `getWorkstreamSessionKey()`
|
|
21
|
+
* (active-workstream-store.cjs): env-first (GSD_SESSION_KEY,
|
|
22
|
+
* CLAUDE_SESSION_ID, CODEX_THREAD_ID, …), then the controlling TTY. Two
|
|
23
|
+
* agent sessions in different terminals/runtimes resolve different keys;
|
|
24
|
+
* headless runs resolve to null.
|
|
25
|
+
* - Liveness is age-based only: a claim is live while
|
|
26
|
+
* `now - updated_at < MILESTONE_LOCK_TTL_MS`. There is deliberately NO
|
|
27
|
+
* pid-liveness gate — CLI invocations are short-lived, so the recorded pid
|
|
28
|
+
* is dead by the next invocation regardless of whether the agent SESSION is
|
|
29
|
+
* still active; pid-death would make every claim instantly stale. Active
|
|
30
|
+
* sessions heartbeat their claim (advance-plan on a matching position
|
|
31
|
+
* refreshes updated_at); an abandoned claim self-expires after the TTL.
|
|
32
|
+
* - Conflict rule: a live claim for a DIFFERENT phase held by a DIFFERENT
|
|
33
|
+
* session. Same phase never conflicts (the lock is keyed by phase — two
|
|
34
|
+
* sessions on one phase are doing the same work). A non-null session key
|
|
35
|
+
* equal to the caller's never conflicts (one orchestrating session may
|
|
36
|
+
* re-target its own claim). A null session key never counts as "same" as
|
|
37
|
+
* anything, so headless parallel phases are still detected. The one
|
|
38
|
+
* exception is advance-plan (checkMilestonePosition), which reports ANY
|
|
39
|
+
* live claim/position phase mismatch regardless of session — a session's
|
|
40
|
+
* own legitimate re-targeting goes through begin-phase, which keeps the
|
|
41
|
+
* claim in sync, so a same-session mismatch is just as anomalous.
|
|
42
|
+
* - On conflict the existing claim is LEFT INTACT (not stolen): as long as two
|
|
43
|
+
* phases are concurrently active, every Current Position mutation keeps
|
|
44
|
+
* reporting the conflict. The conflicting command still proceeds (warn, not
|
|
45
|
+
* block — a stale-but-live claim must not brick later sessions).
|
|
46
|
+
*/
|
|
47
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
48
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
49
|
+
};
|
|
50
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
51
|
+
exports.MILESTONE_LOCK_TTL_MS = exports.MILESTONE_LOCK_FILENAME = void 0;
|
|
52
|
+
exports.warnMilestoneConflict = warnMilestoneConflict;
|
|
53
|
+
exports.claimMilestonePhase = claimMilestonePhase;
|
|
54
|
+
exports.checkMilestonePosition = checkMilestonePosition;
|
|
55
|
+
exports.checkMilestoneConflictForPhase = checkMilestoneConflictForPhase;
|
|
56
|
+
exports.releaseMilestonePhase = releaseMilestonePhase;
|
|
57
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
58
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
59
|
+
const clock_cjs_1 = require("./clock.cjs");
|
|
60
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports -- planning-workspace.cjs is an export= CommonJS module
|
|
61
|
+
const planningWorkspace = require("./planning-workspace.cjs");
|
|
62
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports -- active-workstream-store.cjs is an export= CommonJS module
|
|
63
|
+
const activeWorkstreamStore = require("./active-workstream-store.cjs");
|
|
64
|
+
exports.MILESTONE_LOCK_FILENAME = 'milestone.lock';
|
|
65
|
+
/**
|
|
66
|
+
* How long a claim stays live without a heartbeat. Active sessions heartbeat
|
|
67
|
+
* on every advance-plan against their claimed phase; a phase's planning stage
|
|
68
|
+
* (begin-phase → first completed plan) can legitimately run for hours, so the
|
|
69
|
+
* floor must comfortably exceed the longest expected gap between heartbeats
|
|
70
|
+
* while still expiring an abandoned claim within a working day.
|
|
71
|
+
*/
|
|
72
|
+
exports.MILESTONE_LOCK_TTL_MS = 4 * 60 * 60 * 1000;
|
|
73
|
+
function milestoneLockPath(cwd) {
|
|
74
|
+
return node_path_1.default.join(planningWorkspace.planningDir(cwd), exports.MILESTONE_LOCK_FILENAME);
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Normalize a phase token for claim comparison: trim, and collapse an integer
|
|
78
|
+
* spelling to its canonical form so "01", "1" and " 1 " compare equal while
|
|
79
|
+
* decimals ("2.5") compare by their trimmed text.
|
|
80
|
+
*/
|
|
81
|
+
function normalizePhaseToken(value) {
|
|
82
|
+
const trimmed = String(value).trim();
|
|
83
|
+
if (/^\d+$/.test(trimmed))
|
|
84
|
+
return String(parseInt(trimmed, 10));
|
|
85
|
+
return trimmed;
|
|
86
|
+
}
|
|
87
|
+
function sameSession(a, b) {
|
|
88
|
+
// A null key is "unknown", not "equal" — two headless sessions must still
|
|
89
|
+
// conflict across phases (the CI/cron shape of #3311).
|
|
90
|
+
if (a === null || b === null)
|
|
91
|
+
return false;
|
|
92
|
+
return a === b;
|
|
93
|
+
}
|
|
94
|
+
function parseClaim(raw) {
|
|
95
|
+
if (!raw)
|
|
96
|
+
return null;
|
|
97
|
+
try {
|
|
98
|
+
const parsed = JSON.parse(raw);
|
|
99
|
+
if (typeof parsed.phase !== 'string' || !parsed.phase.trim())
|
|
100
|
+
return null;
|
|
101
|
+
return {
|
|
102
|
+
phase: parsed.phase,
|
|
103
|
+
session: typeof parsed.session === 'string' ? parsed.session : null,
|
|
104
|
+
pid: typeof parsed.pid === 'number' ? parsed.pid : 0,
|
|
105
|
+
updated_at: typeof parsed.updated_at === 'number' ? parsed.updated_at : 0,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
// Corrupt body (concurrent partial write, hand edit) — treat as no claim.
|
|
110
|
+
// The next writer replaces the file; never crash a state command over an
|
|
111
|
+
// advisory sidecar.
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
function readClaim(cwd) {
|
|
116
|
+
try {
|
|
117
|
+
return parseClaim(node_fs_1.default.readFileSync(milestoneLockPath(cwd), 'utf-8'));
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
return null; // absent or unreadable — same posture as parseClaim
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
function writeClaim(cwd, claim) {
|
|
124
|
+
ensureClaimDir(cwd);
|
|
125
|
+
node_fs_1.default.writeFileSync(milestoneLockPath(cwd), JSON.stringify(claim, null, 2) + '\n');
|
|
126
|
+
}
|
|
127
|
+
// planningWorkspace.planningDir targets .planning (or the workstream-scoped
|
|
128
|
+
// variant); the parent is created by every GSD command that gets this far, but
|
|
129
|
+
// the claim write must not throw if it is somehow missing.
|
|
130
|
+
function ensureClaimDir(cwd) {
|
|
131
|
+
try {
|
|
132
|
+
node_fs_1.default.mkdirSync(node_path_1.default.dirname(milestoneLockPath(cwd)), { recursive: true });
|
|
133
|
+
}
|
|
134
|
+
catch {
|
|
135
|
+
/* best-effort — see writeClaim callers' advisory posture */
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
function isClaimLive(claim, nowMs) {
|
|
139
|
+
return nowMs - claim.updated_at < exports.MILESTONE_LOCK_TTL_MS;
|
|
140
|
+
}
|
|
141
|
+
function currentSessionKey() {
|
|
142
|
+
return activeWorkstreamStore.getWorkstreamSessionKey();
|
|
143
|
+
}
|
|
144
|
+
function toConflict(claim, phase, session) {
|
|
145
|
+
return {
|
|
146
|
+
locked_phase: claim.phase,
|
|
147
|
+
locked_session: claim.session,
|
|
148
|
+
phase,
|
|
149
|
+
session,
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Human-visible warning for a conflict. Emitted on stderr unconditionally —
|
|
154
|
+
* visibility is the entire point of this lock (#3311's "silent last-write-wins").
|
|
155
|
+
*/
|
|
156
|
+
function warnMilestoneConflict(conflict, action) {
|
|
157
|
+
const holder = conflict.locked_session ?? 'an unknown (headless) session';
|
|
158
|
+
const actor = conflict.session ?? 'an unknown (headless) session';
|
|
159
|
+
process.stderr.write(`[gsd-tools] WARNING: milestone lock conflict (#3311): ${holder} holds the milestone claim for phase ` +
|
|
160
|
+
`${conflict.locked_phase}, but ${actor} is running ${action} for phase ${conflict.phase}. ` +
|
|
161
|
+
`STATE.md's ## Current Position is a single slot — concurrent phases overwrite each other's ` +
|
|
162
|
+
`position. Verify Current Position before trusting it.\n`);
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* begin-phase entry point: claim `phase` for this session.
|
|
166
|
+
*
|
|
167
|
+
* Returns a conflict descriptor (and leaves the existing claim intact) when a
|
|
168
|
+
* live claim for a DIFFERENT phase is held by a DIFFERENT session; otherwise
|
|
169
|
+
* takes/refreshes the claim and returns null.
|
|
170
|
+
*/
|
|
171
|
+
function claimMilestonePhase(cwd, phase, nowMs) {
|
|
172
|
+
const now = nowMs ?? clock_cjs_1.realClock.now();
|
|
173
|
+
const session = currentSessionKey();
|
|
174
|
+
const existing = readClaim(cwd);
|
|
175
|
+
if (existing !== null &&
|
|
176
|
+
isClaimLive(existing, now) &&
|
|
177
|
+
normalizePhaseToken(existing.phase) !== normalizePhaseToken(phase) &&
|
|
178
|
+
!sameSession(existing.session, session)) {
|
|
179
|
+
return toConflict(existing, phase, session);
|
|
180
|
+
}
|
|
181
|
+
writeClaim(cwd, { phase: String(phase).trim(), session, pid: process.pid, updated_at: now });
|
|
182
|
+
return null;
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* advance-plan entry point: `positionPhase` is the phase STATE.md's Current
|
|
186
|
+
* Position currently names.
|
|
187
|
+
*
|
|
188
|
+
* A live claim naming a DIFFERENT phase means the position was moved away from
|
|
189
|
+
* the claimed phase without a matching begin-phase — the #3311 flip — and is
|
|
190
|
+
* returned as a conflict REGARDLESS of session: this session's own legitimate
|
|
191
|
+
* re-targeting goes through begin-phase, which keeps the claim in sync, so a
|
|
192
|
+
* same-session mismatch is exactly as anomalous as a cross-session one. A live
|
|
193
|
+
* claim matching the position phase is heartbeat-refreshed (this session is
|
|
194
|
+
* actively working that phase, so the claim stays live). No claim is ever
|
|
195
|
+
* created here: advance-plan has no phase argument of its own, so it can only
|
|
196
|
+
* corroborate or contradict an existing claim, not originate one.
|
|
197
|
+
*/
|
|
198
|
+
function checkMilestonePosition(cwd, positionPhase, nowMs) {
|
|
199
|
+
const now = nowMs ?? clock_cjs_1.realClock.now();
|
|
200
|
+
const existing = readClaim(cwd);
|
|
201
|
+
if (existing === null || !isClaimLive(existing, now))
|
|
202
|
+
return null;
|
|
203
|
+
const session = currentSessionKey();
|
|
204
|
+
if (normalizePhaseToken(existing.phase) === normalizePhaseToken(positionPhase)) {
|
|
205
|
+
// Heartbeat — keep the claim's liveness anchored to actual activity.
|
|
206
|
+
writeClaim(cwd, { phase: existing.phase, session: existing.session, pid: process.pid, updated_at: now });
|
|
207
|
+
return null;
|
|
208
|
+
}
|
|
209
|
+
return toConflict(existing, positionPhase, session);
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* phase.complete entry point (read-only check): a live claim for a DIFFERENT
|
|
213
|
+
* phase held by a DIFFERENT session is a conflict. phase.complete never claims
|
|
214
|
+
* — it ends work on a phase rather than starting it.
|
|
215
|
+
*/
|
|
216
|
+
function checkMilestoneConflictForPhase(cwd, phase, nowMs) {
|
|
217
|
+
const now = nowMs ?? clock_cjs_1.realClock.now();
|
|
218
|
+
const existing = readClaim(cwd);
|
|
219
|
+
if (existing === null || !isClaimLive(existing, now))
|
|
220
|
+
return null;
|
|
221
|
+
if (normalizePhaseToken(existing.phase) === normalizePhaseToken(phase))
|
|
222
|
+
return null;
|
|
223
|
+
const session = currentSessionKey();
|
|
224
|
+
if (!sameSession(existing.session, session)) {
|
|
225
|
+
return toConflict(existing, phase, session);
|
|
226
|
+
}
|
|
227
|
+
return null;
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Release the claim when the phase it names completes — regardless of which
|
|
231
|
+
* session completes it (an orchestrator cleaning up after a dead session must
|
|
232
|
+
* not be blocked by the dead session's own claim). Returns whether a matching
|
|
233
|
+
* claim was removed.
|
|
234
|
+
*/
|
|
235
|
+
function releaseMilestonePhase(cwd, phase) {
|
|
236
|
+
const existing = readClaim(cwd);
|
|
237
|
+
if (existing === null)
|
|
238
|
+
return false;
|
|
239
|
+
if (normalizePhaseToken(existing.phase) !== normalizePhaseToken(phase))
|
|
240
|
+
return false;
|
|
241
|
+
try {
|
|
242
|
+
node_fs_1.default.unlinkSync(milestoneLockPath(cwd));
|
|
243
|
+
return true;
|
|
244
|
+
}
|
|
245
|
+
catch {
|
|
246
|
+
return false; // already gone — same outcome for the caller
|
|
247
|
+
}
|
|
248
|
+
}
|