@opengsd/gsd-core 1.11.0 → 1.12.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-code-fixer.md +1 -1
- package/agents/gsd-debug-session-manager.md +1 -1
- package/agents/gsd-debugger.md +1 -1
- package/agents/gsd-dom-verifier.md +169 -0
- package/agents/gsd-eval-auditor.md +1 -1
- package/agents/gsd-executor.md +17 -9
- package/agents/gsd-framework-selector.md +1 -3
- package/agents/gsd-intel-updater.md +1 -1
- package/agents/gsd-mempalace-curator.md +0 -1
- package/agents/gsd-pattern-mapper.md +11 -0
- package/agents/gsd-phase-researcher.md +3 -1
- package/agents/gsd-plan-checker.md +15 -55
- package/agents/gsd-planner.md +6 -4
- package/agents/gsd-project-researcher.md +1 -1
- package/agents/gsd-research-synthesizer.md +2 -2
- package/agents/gsd-roadmapper.md +15 -11
- package/agents/gsd-ui-checker.md +63 -4
- package/agents/gsd-ui-researcher.md +41 -3
- package/agents/gsd-verifier.md +1 -1
- package/bin/install.js +609 -134
- package/commands/gsd/discuss-phase.md +1 -1
- package/commands/gsd/import.md +1 -1
- package/commands/gsd/quick.md +8 -4
- package/gsd-core/bin/gsd-tools.cjs +567 -51
- package/gsd-core/bin/lib/active-workstream-store.cjs +8 -0
- package/gsd-core/bin/lib/adr-parser.cjs +13 -7
- package/gsd-core/bin/lib/agent-install-check.cjs +162 -0
- package/gsd-core/bin/lib/api-coverage.cjs +30 -9
- package/gsd-core/bin/lib/artifacts.cjs +2 -0
- package/gsd-core/bin/lib/assumption-delta.cjs +30 -11
- package/gsd-core/bin/lib/audit.cjs +163 -41
- package/gsd-core/bin/lib/broken-windows.cjs +306 -28
- package/gsd-core/bin/lib/capability-lock.cjs +10 -4
- package/gsd-core/bin/lib/capability-registry.cjs +336 -95
- package/gsd-core/bin/lib/capability-state.cjs +18 -3
- package/gsd-core/bin/lib/capability-validator.cjs +205 -18
- package/gsd-core/bin/lib/check-command-router.cjs +145 -5
- package/gsd-core/bin/lib/cli-exit.cjs +496 -10
- package/gsd-core/bin/lib/code-review-depth.cjs +288 -0
- package/gsd-core/bin/lib/codex-agent-toml.cjs +410 -4
- package/gsd-core/bin/lib/command-arg-projection.cjs +144 -14
- package/gsd-core/bin/lib/command-routing-hub.cjs +31 -2
- package/gsd-core/bin/lib/commands.cjs +543 -44
- package/gsd-core/bin/lib/complexity-trigger.cjs +26 -6
- package/gsd-core/bin/lib/config-loader.cjs +118 -29
- package/gsd-core/bin/lib/config.cjs +92 -2
- package/gsd-core/bin/lib/configuration.cjs +129 -37
- package/gsd-core/bin/lib/core-utils.cjs +84 -7
- package/gsd-core/bin/lib/edge-probe.cjs +9 -1
- package/gsd-core/bin/lib/estimate-cli.cjs +55 -11
- package/gsd-core/bin/lib/exit-code-registry.cjs +98 -0
- package/gsd-core/bin/lib/frontmatter.cjs +840 -305
- package/gsd-core/bin/lib/gap-checker.cjs +27 -3
- package/gsd-core/bin/lib/git-base-branch.cjs +174 -39
- package/gsd-core/bin/lib/health-diagnostic-rules/consistency.cjs +7 -3
- package/gsd-core/bin/lib/health-diagnostic-rules/roadmap-disk-consistency.cjs +6 -3
- package/gsd-core/bin/lib/health-diagnostic-rules/worktree-health.cjs +22 -8
- package/gsd-core/bin/lib/health-diagnostic.cjs +23 -3
- package/gsd-core/bin/lib/host-integration.cjs +39 -6
- package/gsd-core/bin/lib/init-command-router.cjs +118 -21
- package/gsd-core/bin/lib/init.cjs +120 -41
- package/gsd-core/bin/lib/install-engine.cjs +68 -3
- package/gsd-core/bin/lib/install-model-override-resolver.cjs +33 -1
- package/gsd-core/bin/lib/install-profiles.cjs +78 -4
- package/gsd-core/bin/lib/installer-migration-report.cjs +3 -0
- package/gsd-core/bin/lib/installer-migrations/010-antigravity-retire-confighome-artifacts.cjs +169 -0
- package/gsd-core/bin/lib/installer-migrations.cjs +10 -7
- package/gsd-core/bin/lib/intel.cjs +101 -26
- package/gsd-core/bin/lib/io.cjs +160 -15
- package/gsd-core/bin/lib/learnings.cjs +85 -14
- package/gsd-core/bin/lib/legacy-cleanup.cjs +8 -2
- package/gsd-core/bin/lib/markdown-table.cjs +52 -4
- package/gsd-core/bin/lib/milestone.cjs +90 -5
- package/gsd-core/bin/lib/model-catalog.cjs +177 -19
- package/gsd-core/bin/lib/model-resolver.cjs +10 -28
- package/gsd-core/bin/lib/onboard-projection.cjs +5 -1
- package/gsd-core/bin/lib/phase-estimation.cjs +17 -8
- package/gsd-core/bin/lib/phase-id.cjs +70 -4
- package/gsd-core/bin/lib/phase-lifecycle.cjs +24 -16
- package/gsd-core/bin/lib/phase-locator.cjs +138 -17
- package/gsd-core/bin/lib/phase.cjs +405 -84
- package/gsd-core/bin/lib/plan-document.cjs +263 -0
- package/gsd-core/bin/lib/plan-scan.cjs +13 -2
- package/gsd-core/bin/lib/planning-command-router.cjs +61 -0
- package/gsd-core/bin/lib/planning-inspect.cjs +1168 -0
- package/gsd-core/bin/lib/planning-snapshot.cjs +18 -14
- package/gsd-core/bin/lib/planning-workspace.cjs +56 -0
- package/gsd-core/bin/lib/probe-core.cjs +4 -1
- package/gsd-core/bin/lib/profile-pipeline-command-router.cjs +50 -7
- package/gsd-core/bin/lib/profile-pipeline.cjs +6 -3
- package/gsd-core/bin/lib/real-home-guard.cjs +419 -0
- package/gsd-core/bin/lib/refactor-trigger-command-router.cjs +71 -45
- package/gsd-core/bin/lib/review-lane-descriptor.cjs +9 -9
- package/gsd-core/bin/lib/roadmap-command-router.cjs +45 -31
- package/gsd-core/bin/lib/roadmap-parser.cjs +79 -16
- package/gsd-core/bin/lib/roadmap.cjs +74 -19
- package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +96 -8
- package/gsd-core/bin/lib/runtime-artifact-layout.cjs +34 -1
- package/gsd-core/bin/lib/runtime-hooks-surface.cjs +287 -55
- package/gsd-core/bin/lib/runtime-identity.cjs +234 -0
- package/gsd-core/bin/lib/runtime-slash.cjs +72 -2
- package/gsd-core/bin/lib/shell-command-projection.cjs +71 -8
- package/gsd-core/bin/lib/smart-entry.cjs +12 -22
- package/gsd-core/bin/lib/spec-section.cjs +12 -7
- package/gsd-core/bin/lib/state-command-router.cjs +47 -18
- package/gsd-core/bin/lib/state-contract.cjs +359 -0
- package/gsd-core/bin/lib/state-document.cjs +186 -0
- package/gsd-core/bin/lib/state-md-schema.cjs +221 -0
- package/gsd-core/bin/lib/state-transition.cjs +517 -101
- package/gsd-core/bin/lib/state.cjs +946 -163
- package/gsd-core/bin/lib/surface.cjs +10 -2
- package/gsd-core/bin/lib/task-command-router.cjs +111 -1
- package/gsd-core/bin/lib/task-content-resolution.cjs +368 -0
- package/gsd-core/bin/lib/teams-status.cjs +4 -1
- package/gsd-core/bin/lib/uat-predicate.cjs +58 -20
- package/gsd-core/bin/lib/uat.cjs +1376 -125
- package/gsd-core/bin/lib/ui-consideration-probe.cjs +9 -1
- package/gsd-core/bin/lib/ui-safety-gate.cjs +37 -7
- package/gsd-core/bin/lib/unusable-input.cjs +13 -0
- package/gsd-core/bin/lib/validate-command-router.cjs +2 -2
- package/gsd-core/bin/lib/vendor/README.md +43 -5
- package/gsd-core/bin/lib/vendor/js-yaml.cjs +3014 -0
- package/gsd-core/bin/lib/verification.cjs +14 -1
- package/gsd-core/bin/lib/verify-command-grounding.cjs +846 -0
- package/gsd-core/bin/lib/verify.cjs +95 -40
- package/gsd-core/bin/lib/workstream-name-policy.cjs +25 -4
- package/gsd-core/bin/lib/worktree-base-ref.cjs +66 -12
- package/gsd-core/bin/lib/worktree-safety.cjs +177 -21
- package/gsd-core/bin/shared/config-defaults.manifest.json +7 -1
- package/gsd-core/bin/shared/config-schema.manifest.json +5 -0
- package/gsd-core/bin/shared/exit-codes.json +8 -0
- package/gsd-core/bin/shared/exit-codes.sh +20 -0
- package/gsd-core/bin/shared/model-catalog.json +8 -1
- package/gsd-core/references/agent-contracts.md +3 -2
- package/gsd-core/references/api-coverage.md +24 -2
- package/gsd-core/references/autonomous-smart-discuss.md +3 -3
- package/gsd-core/references/checkpoints.md +37 -19
- package/gsd-core/references/decimal-phase-calculation.md +5 -5
- package/gsd-core/references/edge-probe.md +8 -0
- package/gsd-core/references/execute-mvp-tdd.md +1 -3
- package/gsd-core/references/execute-phase-between-wave-reset.md +9 -12
- package/gsd-core/references/execute-phase-wave-guard.md +11 -9
- package/gsd-core/references/failing-direction.md +78 -0
- package/gsd-core/references/gate-prompts.md +1 -1
- package/gsd-core/references/git-integration.md +5 -5
- package/gsd-core/references/git-planning-commit.md +3 -3
- package/gsd-core/references/gsd-run-resolver.md +1 -1
- package/gsd-core/references/loop-hook-dispatch.md +22 -0
- package/gsd-core/references/model-profiles.md +1 -1
- package/gsd-core/references/nyquist-compliance.md +74 -0
- package/gsd-core/references/offer-next.md +3 -5
- package/gsd-core/references/phase-argument-parsing.md +3 -3
- package/gsd-core/references/planner-failing-direction.md +53 -0
- package/gsd-core/references/planner-human-verify-mode.md +15 -1
- package/gsd-core/references/planner-revision.md +1 -1
- package/gsd-core/references/planner-verify-command-grounding.md +17 -0
- package/gsd-core/references/planning-config.md +37 -8
- package/gsd-core/references/reviewer-instances.md +31 -0
- package/gsd-core/references/runtime-aware-dispatch.md +1 -1
- package/gsd-core/references/tdd.md +1 -3
- package/gsd-core/references/ui-brand.md +65 -21
- package/gsd-core/references/ui-consideration-probe.md +1 -1
- package/gsd-core/references/universal-anti-patterns.md +2 -2
- package/gsd-core/references/verify-command-path-resolvability.md +42 -0
- package/gsd-core/references/verify-mvp-mode.md +1 -1
- package/gsd-core/references/workstream-flag.md +11 -11
- package/gsd-core/templates/README.md +1 -1
- package/gsd-core/templates/SECURITY.md +3 -3
- package/gsd-core/templates/UI-SPEC.md +25 -3
- package/gsd-core/templates/VALIDATION.md +3 -3
- package/gsd-core/templates/phase-prompt.md +3 -0
- package/gsd-core/templates/state.md +7 -0
- package/gsd-core/workflows/_runtime-launcher.snippet.sh +1 -1
- package/gsd-core/workflows/add-backlog.md +1 -1
- package/gsd-core/workflows/add-phase.md +3 -3
- package/gsd-core/workflows/add-tests.md +3 -8
- package/gsd-core/workflows/add-todo.md +1 -1
- package/gsd-core/workflows/ai-integration-phase.md +4 -9
- package/gsd-core/workflows/audit-fix.md +12 -3
- package/gsd-core/workflows/audit-milestone.md +9 -9
- package/gsd-core/workflows/audit-uat.md +17 -2
- package/gsd-core/workflows/autonomous/steps/converge-fail-fast.md +2 -2
- package/gsd-core/workflows/autonomous.md +10 -26
- package/gsd-core/workflows/check-todos.md +1 -1
- package/gsd-core/workflows/cleanup.md +2 -2
- package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +1 -1
- package/gsd-core/workflows/code-review-fix.md +1 -1
- package/gsd-core/workflows/code-review.md +121 -40
- package/gsd-core/workflows/complete-milestone.md +15 -10
- package/gsd-core/workflows/debug.md +5 -3
- package/gsd-core/workflows/diagnose-issues.md +12 -6
- package/gsd-core/workflows/discuss-phase/modes/advisor.md +1 -1
- package/gsd-core/workflows/discuss-phase/modes/chain.md +3 -7
- package/gsd-core/workflows/discuss-phase/modes/text.md +1 -1
- package/gsd-core/workflows/discuss-phase-assumptions/steps/auto-advance-dispatch.md +1 -3
- package/gsd-core/workflows/discuss-phase-assumptions.md +2 -2
- package/gsd-core/workflows/discuss-phase.md +1 -1
- package/gsd-core/workflows/do.md +3 -6
- package/gsd-core/workflows/docs-update.md +5 -4
- package/gsd-core/workflows/edit-phase.md +1 -1
- package/gsd-core/workflows/eval-review.md +4 -9
- package/gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +113 -11
- package/gsd-core/workflows/execute-phase/steps/gap-closure-artifacts.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/partial-wave.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/per-plan-executor-routing.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +22 -4
- package/gsd-core/workflows/execute-phase/steps/post-merge-gate.md +2 -2
- package/gsd-core/workflows/execute-phase/steps/protected-branch.md +21 -0
- package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +2 -2
- package/gsd-core/workflows/execute-phase/steps/wave-post-gate-hooks.md +39 -0
- package/gsd-core/workflows/execute-phase.md +38 -54
- package/gsd-core/workflows/execute-plan.md +17 -12
- package/gsd-core/workflows/explore.md +1 -1
- package/gsd-core/workflows/extract-learnings.md +1 -1
- package/gsd-core/workflows/fast.md +2 -2
- package/gsd-core/workflows/forensics.md +1 -1
- package/gsd-core/workflows/graduation.md +5 -5
- package/gsd-core/workflows/health.md +3 -6
- package/gsd-core/workflows/import.md +14 -11
- package/gsd-core/workflows/inbox.md +4 -5
- package/gsd-core/workflows/ingest-docs.md +44 -11
- package/gsd-core/workflows/insert-phase.md +5 -5
- package/gsd-core/workflows/list-seeds.md +5 -3
- package/gsd-core/workflows/list-workspaces.md +1 -1
- package/gsd-core/workflows/manager.md +12 -23
- package/gsd-core/workflows/map-codebase.md +1 -1
- package/gsd-core/workflows/milestone-summary.md +1 -1
- package/gsd-core/workflows/mvp-phase.md +2 -2
- package/gsd-core/workflows/new-milestone.md +9 -21
- package/gsd-core/workflows/new-project/steps/auto-mode-config.md +1 -1
- package/gsd-core/workflows/new-project.md +12 -26
- package/gsd-core/workflows/new-workspace.md +1 -1
- package/gsd-core/workflows/next.md +2 -2
- package/gsd-core/workflows/pause-work.md +1 -1
- package/gsd-core/workflows/plan-phase/steps/adr-ingest-express-path.md +1 -1
- package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +1 -1
- package/gsd-core/workflows/plan-phase/steps/prd-express-path.md +2 -4
- package/gsd-core/workflows/plan-phase/steps/stall-detection-helpers.md +3 -3
- package/gsd-core/workflows/plan-phase.md +121 -42
- package/gsd-core/workflows/plan-review-convergence.md +46 -9
- package/gsd-core/workflows/plant-seed.md +2 -2
- package/gsd-core/workflows/pr-branch.md +187 -51
- package/gsd-core/workflows/profile-user.md +16 -14
- package/gsd-core/workflows/progress.md +27 -12
- package/gsd-core/workflows/quick/steps/discussion-phase.md +1 -3
- package/gsd-core/workflows/quick/steps/plan-checker-loop.md +1 -3
- package/gsd-core/workflows/quick/steps/quick-verification.md +2 -4
- package/gsd-core/workflows/quick/steps/research-phase.md +2 -4
- package/gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md +3 -3
- package/gsd-core/workflows/quick.md +20 -29
- package/gsd-core/workflows/remove-phase.md +4 -4
- package/gsd-core/workflows/remove-workspace.md +2 -2
- package/gsd-core/workflows/resume-project.md +8 -12
- package/gsd-core/workflows/review.md +193 -15
- package/gsd-core/workflows/scan.md +1 -1
- package/gsd-core/workflows/secure-phase.md +2 -2
- package/gsd-core/workflows/settings-advanced.md +7 -9
- package/gsd-core/workflows/settings-integrations.md +64 -31
- package/gsd-core/workflows/settings.md +3 -5
- package/gsd-core/workflows/ship.md +12 -6
- package/gsd-core/workflows/sketch-wrap-up.md +11 -17
- package/gsd-core/workflows/sketch.md +12 -18
- package/gsd-core/workflows/smart-entry.md +3 -5
- package/gsd-core/workflows/spec-phase.md +23 -1
- package/gsd-core/workflows/spike-wrap-up.md +7 -11
- package/gsd-core/workflows/spike.md +20 -31
- package/gsd-core/workflows/stats.md +2 -2
- package/gsd-core/workflows/sync-skills.md +1 -1
- package/gsd-core/workflows/thread.md +11 -7
- package/gsd-core/workflows/transition.md +5 -5
- package/gsd-core/workflows/ui-phase.md +10 -16
- package/gsd-core/workflows/ui-review.md +6 -10
- package/gsd-core/workflows/ultraplan-phase.md +5 -13
- package/gsd-core/workflows/undo.md +8 -16
- package/gsd-core/workflows/update.md +6 -10
- package/gsd-core/workflows/validate-phase.md +2 -2
- package/gsd-core/workflows/verify-work/steps/automated-ui-verification.md +25 -1
- package/gsd-core/workflows/verify-work/steps/mvp-uat-framing.md +1 -1
- package/gsd-core/workflows/verify-work.md +57 -18
- package/hooks/dist/gsd-agent-isolation-guard.js +77 -38
- package/hooks/dist/gsd-config-reload.js +18 -12
- package/hooks/dist/gsd-context-monitor.js +19 -10
- package/hooks/dist/gsd-cursor-post-tool.js +3 -1
- package/hooks/dist/gsd-cursor-pre-tool.js +3 -1
- package/hooks/dist/gsd-cursor-session-start.js +2 -1
- package/hooks/dist/gsd-cursor-stop.js +2 -1
- package/hooks/dist/gsd-cursor-subagent-start.js +28 -23
- package/hooks/dist/gsd-cursor-subagent-stop.js +3 -1
- package/hooks/dist/gsd-ensure-canonical-path.js +2 -1
- package/hooks/dist/gsd-graphify-update.sh +22 -18
- package/hooks/dist/gsd-node-runner.sh +76 -0
- package/hooks/dist/gsd-phase-boundary.sh +1 -0
- package/hooks/dist/gsd-prompt-guard.js +16 -7
- package/hooks/dist/gsd-read-guard.js +16 -7
- package/hooks/dist/gsd-read-injection-scanner.js +17 -8
- package/hooks/dist/gsd-session-state.sh +1 -0
- package/hooks/dist/gsd-statusline.js +215 -26
- package/hooks/dist/gsd-validate-commit.sh +80 -6
- package/hooks/dist/gsd-windsurf-pre-command.js +16 -11
- package/hooks/dist/gsd-windsurf-pre-write.js +22 -13
- package/hooks/dist/gsd-workflow-guard.js +34 -16
- package/hooks/dist/gsd-worktree-path-guard.js +36 -21
- package/hooks/dist/gsd-write-guard.js +35 -25
- package/hooks/dist/lib/cli-exit.js +560 -0
- package/hooks/dist/lib/exit-code-registry.js +98 -0
- package/hooks/dist/lib/git-probe.js +84 -0
- package/hooks/dist/lib/hook-exit.js +81 -0
- package/hooks/dist/managed-hooks-registry.cjs +3 -0
- package/hooks/gsd-agent-isolation-guard.js +77 -38
- package/hooks/gsd-config-reload.js +18 -12
- package/hooks/gsd-context-monitor.js +19 -10
- package/hooks/gsd-cursor-post-tool.js +3 -1
- package/hooks/gsd-cursor-pre-tool.js +3 -1
- package/hooks/gsd-cursor-session-start.js +2 -1
- package/hooks/gsd-cursor-stop.js +2 -1
- package/hooks/gsd-cursor-subagent-start.js +28 -23
- package/hooks/gsd-cursor-subagent-stop.js +3 -1
- package/hooks/gsd-ensure-canonical-path.js +2 -1
- package/hooks/gsd-graphify-update.sh +22 -18
- package/hooks/gsd-node-runner.sh +76 -0
- package/hooks/gsd-phase-boundary.sh +1 -0
- package/hooks/gsd-prompt-guard.js +16 -7
- package/hooks/gsd-read-guard.js +16 -7
- package/hooks/gsd-read-injection-scanner.js +17 -8
- package/hooks/gsd-session-state.sh +1 -0
- package/hooks/gsd-statusline.js +215 -26
- package/hooks/gsd-validate-commit.sh +80 -6
- package/hooks/gsd-windsurf-pre-command.js +16 -11
- package/hooks/gsd-windsurf-pre-write.js +22 -13
- package/hooks/gsd-workflow-guard.js +34 -16
- package/hooks/gsd-worktree-path-guard.js +36 -21
- package/hooks/gsd-write-guard.js +35 -25
- package/hooks/lib/cli-exit.js +560 -0
- package/hooks/lib/exit-code-registry.js +98 -0
- package/hooks/lib/git-probe.js +84 -0
- package/hooks/lib/hook-exit.js +81 -0
- package/hooks/managed-hooks-registry.cjs +3 -0
- package/package.json +12 -7
- package/scripts/base64-scan.sh +74 -12
- package/scripts/build-hooks.js +5 -0
- package/scripts/check-glossary-refs.cjs +77 -15
- package/scripts/check-mutation-score-ratchet.cjs +156 -0
- package/scripts/ci-check-job-near-cap.cjs +49 -0
- package/scripts/ci-pr-mergeability.cjs +262 -0
- package/scripts/ci-test-scope.cjs +45 -12
- package/scripts/ci-timeout-report.cjs +230 -0
- package/scripts/docs-guard-registry.cjs +396 -0
- package/scripts/gen-capability-registry.cjs +8 -6
- package/scripts/gen-exit-code-docs.cjs +318 -0
- package/scripts/gen-exit-code-registry.cjs +891 -0
- package/scripts/gen-features.cjs +836 -0
- package/scripts/gen-hooks-cli-exit.cjs +239 -0
- package/scripts/gen-install-tree-fixtures.cjs +2 -2
- package/scripts/gen-loop-host-contract.cjs +134 -1
- package/scripts/gen-scripts-cli-exit.cjs +185 -0
- package/scripts/gen-state-md-docs.cjs +727 -0
- package/scripts/{test-failure-reasons.cjs → gsd-test-gate-reasons.cjs} +6 -0
- package/scripts/lib/ci-job-timing.cjs +72 -0
- package/scripts/lib/cli-exit.cjs +546 -44
- package/scripts/lib/drift-scan.cjs +32 -2
- package/scripts/lib/exit-code-registry.cjs +98 -0
- package/scripts/lib/ndjson-reporter.cjs +119 -0
- package/scripts/lint-allow-test-rule-refs.unverified-ceiling.json +1 -1
- package/scripts/lint-docs-guard-registration.cjs +495 -0
- package/scripts/lint-docs-guard-registration.exempt-baseline.cjs +193 -0
- package/scripts/lint-eslint-glob-coverage.allowlist.json +4 -0
- package/scripts/{lint-fix-has-regression-test.cjs → lint-fix-has-regression-tests.cjs} +12 -6
- package/scripts/lint-health-diagnostic-rule-table.cjs +65 -8
- package/scripts/lint-mutation-test-derivation-drift.cjs +86 -0
- package/scripts/lint-phase-enumeration-drift.cjs +21 -8
- package/scripts/lint-planning-prompt-drift.cjs +38 -1
- package/scripts/lint-removed-but-needed.cjs +184 -16
- package/scripts/lint-seam-enforcement.cjs +182 -0
- package/scripts/lint-slug-derivation-drift.cjs +921 -0
- package/scripts/lint-source-test-name-collision.cjs +241 -0
- package/scripts/lint-state-write-path-drift.cjs +337 -432
- package/scripts/lint-test-file-count.allowlist.json +122 -4
- package/scripts/lint-test-file-count.cjs +25 -3
- package/scripts/lint-unreachable-guard-drift.cjs +51 -64
- package/scripts/lint-vendored-deps.cjs +208 -35
- package/scripts/mutation-matrix.cjs +599 -50
- package/scripts/prompt-injection-scan.sh +75 -14
- package/scripts/secret-scan.sh +75 -13
- package/scripts/select-docs-guards.cjs +56 -0
- package/scripts/sync-runtime-launcher.cjs +22 -3
- package/skills/gsd-discuss-phase/SKILL.md +1 -1
- package/skills/gsd-import/SKILL.md +1 -1
- package/skills/gsd-quick/SKILL.md +8 -4
- package/vscode/package.json +1 -1
- package/bin/lib/ui-safety-gate.cjs +0 -109
- package/scripts/lint-emitted-drift-ack.cjs +0 -344
- package/scripts/state-write-path-drift-baseline.json +0 -19
|
@@ -23,7 +23,7 @@ const node_fs_1 = __importDefault(require("node:fs"));
|
|
|
23
23
|
const node_path_1 = __importDefault(require("node:path"));
|
|
24
24
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
25
25
|
const io = require("./io.cjs");
|
|
26
|
-
const { output, error } = io;
|
|
26
|
+
const { output, error, formatDiagnosticToken } = io;
|
|
27
27
|
const pattern_cjs_1 = require("./pattern.cjs");
|
|
28
28
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
29
29
|
const planningWorkspace = require("./planning-workspace.cjs");
|
|
@@ -54,6 +54,17 @@ function parseRequirements(reqMd) {
|
|
|
54
54
|
// The **ID** is extracted from the bullet text caller-side — the seam provides
|
|
55
55
|
// the raw text; we parse the bold-ID prefix from it here.
|
|
56
56
|
const boldIdRe = new RegExp(`^\\*\\*(${ID_PATTERN})\\*\\*\\s*(.*)$`);
|
|
57
|
+
// The shipped template (gsd-core/templates/requirements.md) writes
|
|
58
|
+
// `- [ ] **AUTH-01**: User can sign up` — a single separator delimiter
|
|
59
|
+
// between the bold ID and the description. Strip AT MOST ONE leading
|
|
60
|
+
// delimiter (plus its surrounding whitespace) before the final `.trim()`,
|
|
61
|
+
// mirroring roadmap-parser.cts's `stripLeadingDelimiter` delimiter set
|
|
62
|
+
// (em dash, en dash, colon, hyphen) — that helper is not exported, and its
|
|
63
|
+
// own `+`-quantified strip removes an entire delimiter RUN, which would
|
|
64
|
+
// also eat a second, meaningful marker (`**X-01**: -- weird` must keep the
|
|
65
|
+
// `--`), so the set is mirrored here with a single-occurrence match instead
|
|
66
|
+
// of reused verbatim.
|
|
67
|
+
const ONE_LEADING_DELIMITER_RE = /^\s*[—–:-]\s*/;
|
|
57
68
|
for (const bullet of (0, markdown_sectionizer_cjs_1.iterateBullets)(reqMd)) {
|
|
58
69
|
if (bullet.marker !== 'checkbox-unchecked' && bullet.marker !== 'checkbox-checked')
|
|
59
70
|
continue;
|
|
@@ -65,7 +76,8 @@ function parseRequirements(reqMd) {
|
|
|
65
76
|
continue;
|
|
66
77
|
if (!seen.has(id)) {
|
|
67
78
|
seen.add(id);
|
|
68
|
-
|
|
79
|
+
const rawText = m[2] || '';
|
|
80
|
+
out.push({ id, text: rawText.replace(ONE_LEADING_DELIMITER_RE, '').trim() });
|
|
69
81
|
}
|
|
70
82
|
}
|
|
71
83
|
// Pipe-table-row path and separator-row skip stay caller-side
|
|
@@ -290,6 +302,7 @@ function runGapAnalysis(cwd, phaseDir, options = {}) {
|
|
|
290
302
|
table: '',
|
|
291
303
|
summary: 'workflow.post_planning_gaps disabled — skipping post-planning gap analysis',
|
|
292
304
|
counts: { total: 0, covered: 0, uncovered: 0 },
|
|
305
|
+
phase_dir_read_error: null,
|
|
293
306
|
};
|
|
294
307
|
}
|
|
295
308
|
const absPhaseDir = node_path_1.default.isAbsolute(phaseDir) ? phaseDir : node_path_1.default.join(cwd, phaseDir);
|
|
@@ -313,11 +326,18 @@ function runGapAnalysis(cwd, phaseDir, options = {}) {
|
|
|
313
326
|
// Read the phase directory once; reuse the listing for both context detection
|
|
314
327
|
// and plan-file enumeration (avoids redundant readdirSync calls).
|
|
315
328
|
let phaseDirFiles = [];
|
|
329
|
+
// #3885 (ADR-3473 §8.5): the existsSync guard above already means a catch
|
|
330
|
+
// here is NEVER "genuinely absent" (ENOENT) — this directory exists, so any
|
|
331
|
+
// failure to list it is a real read error (EACCES/EIO/...) and must be
|
|
332
|
+
// named, not folded into the same `[]` an absent directory produces.
|
|
333
|
+
let phaseDirReadError = null;
|
|
316
334
|
try {
|
|
317
335
|
if (node_fs_1.default.existsSync(absPhaseDir))
|
|
318
336
|
phaseDirFiles = node_fs_1.default.readdirSync(absPhaseDir);
|
|
319
337
|
}
|
|
320
|
-
catch {
|
|
338
|
+
catch (err) {
|
|
339
|
+
phaseDirReadError = `Could not read phase directory ${formatDiagnosticToken(absPhaseDir)}: ${formatDiagnosticToken(err?.message ?? String(err))}`;
|
|
340
|
+
}
|
|
321
341
|
// #3511-class: scope the raw listing to this phase dir before the
|
|
322
342
|
// phase-numbered -CONTEXT.md predicate. `phaseDirFiles` itself stays raw —
|
|
323
343
|
// it is also reused below only as a `.length > 0` guard ahead of
|
|
@@ -384,6 +404,7 @@ function runGapAnalysis(cwd, phaseDir, options = {}) {
|
|
|
384
404
|
table: formatGapTable(rows) + '\n' + coverageSummary + '\n\n' + mismatchMsg,
|
|
385
405
|
summary: coverageSummary + '; extracted 0 of N — possible format mismatch',
|
|
386
406
|
counts: { total: rows.length, covered, uncovered },
|
|
407
|
+
phase_dir_read_error: phaseDirReadError,
|
|
387
408
|
};
|
|
388
409
|
}
|
|
389
410
|
return {
|
|
@@ -392,6 +413,7 @@ function runGapAnalysis(cwd, phaseDir, options = {}) {
|
|
|
392
413
|
table: mismatchMsg,
|
|
393
414
|
summary: 'extracted 0 of N — possible format mismatch',
|
|
394
415
|
counts: { total: 0, covered: 0, uncovered: 0 },
|
|
416
|
+
phase_dir_read_error: phaseDirReadError,
|
|
395
417
|
};
|
|
396
418
|
}
|
|
397
419
|
// #1365: if no items at all, surface a clean no-check message.
|
|
@@ -408,6 +430,7 @@ function runGapAnalysis(cwd, phaseDir, options = {}) {
|
|
|
408
430
|
table: '## Post-Planning Gap Analysis\n\nNo requirements or decisions to check.\n',
|
|
409
431
|
summary: 'no requirements or decisions to check',
|
|
410
432
|
counts: { total: 0, covered: 0, uncovered: 0 },
|
|
433
|
+
phase_dir_read_error: phaseDirReadError,
|
|
411
434
|
};
|
|
412
435
|
}
|
|
413
436
|
const rows = sortRows([
|
|
@@ -425,6 +448,7 @@ function runGapAnalysis(cwd, phaseDir, options = {}) {
|
|
|
425
448
|
table: formatGapTable(rows) + '\n' + summary + '\n',
|
|
426
449
|
summary,
|
|
427
450
|
counts: { total: rows.length, covered, uncovered },
|
|
451
|
+
phase_dir_read_error: phaseDirReadError,
|
|
428
452
|
};
|
|
429
453
|
}
|
|
430
454
|
function cmdGapAnalysis(cwd, args, raw) {
|
|
@@ -34,57 +34,134 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
34
34
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
35
35
|
};
|
|
36
36
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
37
|
-
exports.
|
|
37
|
+
exports._readGitNested = _readGitNested;
|
|
38
|
+
exports._readGitKey = _readGitKey;
|
|
38
39
|
exports.trySymbolicRef = trySymbolicRef;
|
|
39
40
|
exports.tryRemoteShow = tryRemoteShow;
|
|
40
41
|
exports.tryLocalBranch = tryLocalBranch;
|
|
41
42
|
exports.resolveBaseBranchDiagnostics = resolveBaseBranchDiagnostics;
|
|
42
43
|
exports.resolveBaseBranch = resolveBaseBranch;
|
|
44
|
+
exports.resolveProtectedBranchStatus = resolveProtectedBranchStatus;
|
|
43
45
|
exports.gitWorktreeInfoInternal = gitWorktreeInfoInternal;
|
|
44
46
|
exports.phaseStartCommit = phaseStartCommit;
|
|
45
47
|
exports.changedFilesSince = changedFilesSince;
|
|
46
48
|
exports.cmdGitBaseBranch = cmdGitBaseBranch;
|
|
47
|
-
const node_fs_1 = __importDefault(require("node:fs"));
|
|
48
49
|
const node_path_1 = __importDefault(require("node:path"));
|
|
50
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
51
|
+
const configLoader = require("./config-loader.cjs");
|
|
52
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
53
|
+
const io = require("./io.cjs");
|
|
54
|
+
const configuration_cjs_1 = require("./configuration.cjs");
|
|
55
|
+
const security_cjs_1 = require("./security.cjs");
|
|
49
56
|
const shell_command_projection_cjs_1 = require("./shell-command-projection.cjs");
|
|
50
|
-
|
|
57
|
+
const { error, ERROR_REASON } = io;
|
|
58
|
+
const { loadConfig: loadConfigSeam } = configLoader;
|
|
59
|
+
/** Render a rejected config value for a diagnostic without throwing on exotic input. */
|
|
60
|
+
function renderRejected(value) {
|
|
61
|
+
try {
|
|
62
|
+
return (0, security_cjs_1.sanitizeLabel)(JSON.stringify(value) ?? String(value));
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
return (0, security_cjs_1.sanitizeLabel)(String(value));
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
/** Nested-only read of `git.<field>`, mirroring `loadConfigResolved`'s `getNested`. */
|
|
69
|
+
function _readGitNested(config, field) {
|
|
70
|
+
const git = config['git'];
|
|
71
|
+
if (git !== null && typeof git === 'object' && !Array.isArray(git)) {
|
|
72
|
+
return git[field];
|
|
73
|
+
}
|
|
74
|
+
return undefined;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Flat-then-nested read, mirroring `loadConfigResolved`'s own `get()`.
|
|
78
|
+
*
|
|
79
|
+
* Used for `base_branch` ONLY, and only because it HAS a legacy flat spelling:
|
|
80
|
+
* `normalizeLegacyKeys` normally hoists it away, so a flat key that SURVIVED
|
|
81
|
+
* normalization is one the migration refused (a non-object `git` section,
|
|
82
|
+
* #3760) and is the user's last remaining expression of intent.
|
|
83
|
+
*
|
|
84
|
+
* `protected_branches` deliberately does NOT use this. It is new in #3552 with
|
|
85
|
+
* no legacy form, so honouring a top-level spelling would invent an
|
|
86
|
+
* undocumented alias that silently outranks the canonical nested key
|
|
87
|
+
* (round-4 external review).
|
|
88
|
+
*/
|
|
89
|
+
function _readGitKey(config, field) {
|
|
90
|
+
if (config[field] !== undefined)
|
|
91
|
+
return config[field];
|
|
92
|
+
return _readGitNested(config, field);
|
|
93
|
+
}
|
|
51
94
|
/**
|
|
52
|
-
*
|
|
53
|
-
*
|
|
95
|
+
* Read the effective root/workstream configuration once for branch policy.
|
|
96
|
+
*
|
|
97
|
+
* Production takes the `loadConfig` branch, with `persist: false` — this is a
|
|
98
|
+
* PREDICATE, invoked on every `execute-phase` and every `ship` run, and a
|
|
99
|
+
* question must not rewrite the file it is asking about. Without it, any project
|
|
100
|
+
* carrying a legacy flat key (`base_branch`, `branching_strategy`, `depth`, …)
|
|
101
|
+
* has `.planning/config.json` silently normalized and rewritten by a call whose
|
|
102
|
+
* entire contract is to answer a boolean (#3648 review Blocker 1).
|
|
103
|
+
*
|
|
104
|
+
* The `readFile` branch is a unit-test seam, NOT a second production path, and
|
|
105
|
+
* it is deliberately narrower than `loadConfig`. It covers exactly two of
|
|
106
|
+
* production's steps — `normalizeLegacyKeys`, then the flat-then-nested lookup
|
|
107
|
+
* — over a single `<cwd>/.planning/config.json`. It does NOT apply
|
|
108
|
+
* root/workstream `_deepMergeConfig`, builtin or `~/.gsd/defaults.json`
|
|
109
|
+
* defaults, or `mergeFederatedConfig`. Tests that assert on any of those must
|
|
110
|
+
* drive `loadConfig` instead; the seam's own tests are scoped to normalization
|
|
111
|
+
* and shape validation, which is all it reproduces (#3648 review Major 3).
|
|
54
112
|
*/
|
|
55
|
-
function
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
113
|
+
function readEffectiveGitConfig(cwd, deps) {
|
|
114
|
+
let config = {};
|
|
115
|
+
if (deps?.readFile && !deps.loadConfig) {
|
|
116
|
+
const raw = deps.readFile(node_path_1.default.join(cwd, '.planning', 'config.json'));
|
|
117
|
+
if (raw) {
|
|
118
|
+
try {
|
|
119
|
+
const parsed = JSON.parse(raw);
|
|
120
|
+
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
|
|
121
|
+
const { parsed: normalized } = (0, configuration_cjs_1.normalizeLegacyKeys)(parsed);
|
|
122
|
+
config = {
|
|
123
|
+
base_branch: _readGitKey(normalized, 'base_branch'),
|
|
124
|
+
protected_branches: _readGitNested(normalized, 'protected_branches'),
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
catch { /* malformed direct edit contributes no policy values */ }
|
|
59
129
|
}
|
|
60
|
-
catch {
|
|
61
|
-
return null;
|
|
62
|
-
} });
|
|
63
|
-
const configPath = node_path_1.default.join(planningDir, 'config.json');
|
|
64
|
-
const raw = readFile(configPath);
|
|
65
|
-
if (!raw)
|
|
66
|
-
return null;
|
|
67
|
-
let cfg;
|
|
68
|
-
try {
|
|
69
|
-
cfg = JSON.parse(raw);
|
|
70
130
|
}
|
|
71
|
-
|
|
72
|
-
|
|
131
|
+
else {
|
|
132
|
+
try {
|
|
133
|
+
config = (deps?.loadConfig ?? loadConfigSeam)(cwd, { persist: false });
|
|
134
|
+
}
|
|
135
|
+
catch { /* configuration loading is fail-soft for branch resolution */ }
|
|
73
136
|
}
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
137
|
+
const rawBaseBranch = config.base_branch;
|
|
138
|
+
const baseBranch = typeof rawBaseBranch === 'string' && rawBaseBranch.trim()
|
|
139
|
+
? rawBaseBranch.trim()
|
|
140
|
+
: null;
|
|
141
|
+
// A protection predicate must not fail OPEN. `config-set` validation is
|
|
142
|
+
// bypassable by a direct edit of .planning/config.json, so one bad element
|
|
143
|
+
// discarding the whole list would silently answer "not protected" for names
|
|
144
|
+
// the user believes are protected — the exact failure #3552 exists to close,
|
|
145
|
+
// reintroduced through a different door (#3648 review Blocker 3). Drop only
|
|
146
|
+
// the bad elements, and report every rejection so it cannot pass unnoticed.
|
|
147
|
+
const rawProtectedBranches = config.protected_branches;
|
|
148
|
+
const protectedBranches = [];
|
|
149
|
+
const rejectedProtectedBranches = [];
|
|
150
|
+
if (Array.isArray(rawProtectedBranches)) {
|
|
151
|
+
for (const branch of rawProtectedBranches) {
|
|
152
|
+
if (typeof branch === 'string' && branch.trim() !== '') {
|
|
153
|
+
protectedBranches.push(branch.trim());
|
|
154
|
+
}
|
|
155
|
+
else {
|
|
156
|
+
rejectedProtectedBranches.push(renderRejected(branch));
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
else if (rawProtectedBranches !== undefined && rawProtectedBranches !== null) {
|
|
161
|
+
// Not a list at all — contributes no names, but is still a misconfiguration.
|
|
162
|
+
rejectedProtectedBranches.push(renderRejected(rawProtectedBranches));
|
|
83
163
|
}
|
|
84
|
-
|
|
85
|
-
if (typeof flat === 'string' && flat.trim())
|
|
86
|
-
return flat.trim();
|
|
87
|
-
return null;
|
|
164
|
+
return { baseBranch, protectedBranches, rejectedProtectedBranches };
|
|
88
165
|
}
|
|
89
166
|
/**
|
|
90
167
|
* Try `git symbolic-ref --short refs/remotes/origin/HEAD` (no network).
|
|
@@ -170,7 +247,7 @@ function tryLocalBranch(cwd, execGit) {
|
|
|
170
247
|
* Consults the full precedence ladder and always returns a non-empty string.
|
|
171
248
|
* Never throws.
|
|
172
249
|
*/
|
|
173
|
-
function
|
|
250
|
+
function resolveBaseBranchDiagnosticsWithConfig(cwd, configured, deps) {
|
|
174
251
|
const rawExecGit = deps?.execGit ?? shell_command_projection_cjs_1.execGit;
|
|
175
252
|
// A genuine execGit failure (timeout, or the call could not even spawn —
|
|
176
253
|
// e.g. git missing, surfaced as exitCode 127 with `error` set) is distinct
|
|
@@ -186,10 +263,7 @@ function resolveBaseBranchDiagnostics(cwd, deps) {
|
|
|
186
263
|
anyGitFailure = true;
|
|
187
264
|
return r;
|
|
188
265
|
};
|
|
189
|
-
// Derive .planning dir relative to cwd (mirrors planningDir() in planning-workspace.cjs)
|
|
190
|
-
const planningDir = node_path_1.default.join(cwd, '.planning');
|
|
191
266
|
// 1. Config override
|
|
192
|
-
const configured = readConfigBaseBranch(planningDir, deps);
|
|
193
267
|
if (configured)
|
|
194
268
|
return { branch: configured, verified: true };
|
|
195
269
|
// 2. symbolic-ref (fast, no network)
|
|
@@ -210,6 +284,10 @@ function resolveBaseBranchDiagnostics(cwd, deps) {
|
|
|
210
284
|
// not answer (#3057 B4).
|
|
211
285
|
return { branch: 'main', verified: !anyGitFailure };
|
|
212
286
|
}
|
|
287
|
+
function resolveBaseBranchDiagnostics(cwd, deps) {
|
|
288
|
+
const { baseBranch } = readEffectiveGitConfig(cwd, deps);
|
|
289
|
+
return resolveBaseBranchDiagnosticsWithConfig(cwd, baseBranch, deps);
|
|
290
|
+
}
|
|
213
291
|
/**
|
|
214
292
|
* Resolve the default/base branch for the repository at `cwd`.
|
|
215
293
|
*
|
|
@@ -220,6 +298,19 @@ function resolveBaseBranchDiagnostics(cwd, deps) {
|
|
|
220
298
|
function resolveBaseBranch(cwd, deps) {
|
|
221
299
|
return resolveBaseBranchDiagnostics(cwd, deps).branch;
|
|
222
300
|
}
|
|
301
|
+
/** Resolve the base branch plus configured protected-branch extensions. */
|
|
302
|
+
function resolveProtectedBranchStatus(cwd, currentBranch, deps) {
|
|
303
|
+
const effectiveConfig = readEffectiveGitConfig(cwd, deps);
|
|
304
|
+
const { branch: baseBranch, verified } = resolveBaseBranchDiagnosticsWithConfig(cwd, effectiveConfig.baseBranch, deps);
|
|
305
|
+
const protectedBranches = [...new Set([baseBranch, ...effectiveConfig.protectedBranches])];
|
|
306
|
+
return {
|
|
307
|
+
baseBranch,
|
|
308
|
+
protectedBranches,
|
|
309
|
+
rejectedProtectedBranches: effectiveConfig.rejectedProtectedBranches,
|
|
310
|
+
isProtected: protectedBranches.includes(currentBranch),
|
|
311
|
+
verified,
|
|
312
|
+
};
|
|
313
|
+
}
|
|
223
314
|
/**
|
|
224
315
|
* Detect whether `cwd` sits inside a git worktree, and if so, return the
|
|
225
316
|
* absolute path of the worktree root.
|
|
@@ -352,7 +443,51 @@ function changedFilesSince(cwd, sinceRef, execGit) {
|
|
|
352
443
|
* Resolves the default branch and writes it to stdout (raw string, newline-terminated).
|
|
353
444
|
* Called by workflows via `gsd_run query git.base-branch`.
|
|
354
445
|
*/
|
|
355
|
-
function cmdGitBaseBranch(cwd,
|
|
446
|
+
function cmdGitBaseBranch(cwd, args, deps) {
|
|
447
|
+
if (args[0] === '--is-protected') {
|
|
448
|
+
if (args.length > 2) {
|
|
449
|
+
error('Usage: git base-branch --is-protected [<branch>]', ERROR_REASON.USAGE);
|
|
450
|
+
}
|
|
451
|
+
const writeDiagnostic = deps?.writeDiagnostic ?? ((s) => process.stderr.write(s));
|
|
452
|
+
// `git branch --show-current` prints nothing on a detached HEAD, so the
|
|
453
|
+
// call sites legitimately pass an explicit empty string: no protected
|
|
454
|
+
// branch is named '', the answer is false, and that is not a fault worth
|
|
455
|
+
// reporting. The flag with NO argument is a different thing — a caller bug
|
|
456
|
+
// that `args[1] ?? ''` used to collapse into the detached-HEAD case. Same
|
|
457
|
+
// handling, but said out loud so the two can be told apart.
|
|
458
|
+
//
|
|
459
|
+
// This diagnostic deliberately does NOT state the answer. The empty branch
|
|
460
|
+
// matches no protected name, but the fail-closed guard below still renders
|
|
461
|
+
// `true` when the base branch could not be verified — so promising "false"
|
|
462
|
+
// here would contradict what this same call prints on stdout (#3648 review).
|
|
463
|
+
if (args.length < 2) {
|
|
464
|
+
writeDiagnostic(`⚠ git-base-branch: --is-protected was called without a branch argument; ` +
|
|
465
|
+
`treating it as an empty branch name. Pass the branch to test, ` +
|
|
466
|
+
`e.g. --is-protected "$CURRENT_BRANCH".\n`);
|
|
467
|
+
}
|
|
468
|
+
const status = resolveProtectedBranchStatus(cwd, args[1] ?? '', deps);
|
|
469
|
+
if (status.rejectedProtectedBranches.length > 0) {
|
|
470
|
+
writeDiagnostic(`⚠ git-base-branch: ignoring ${status.rejectedProtectedBranches.length} unusable ` +
|
|
471
|
+
`git.protected_branches entr${status.rejectedProtectedBranches.length === 1 ? 'y' : 'ies'} ` +
|
|
472
|
+
`(${status.rejectedProtectedBranches.join(', ')}) — each must be a non-empty branch name. ` +
|
|
473
|
+
`The remaining names are still enforced. See #3552.\n`);
|
|
474
|
+
}
|
|
475
|
+
// A protection guard must fail closed: if the base branch could not be
|
|
476
|
+
// verified against this repository (a git query timed out or failed to
|
|
477
|
+
// run — #3057 B4), report "protected" rather than silently trusting an
|
|
478
|
+
// unverified guess that might happen to not match the current branch.
|
|
479
|
+
const rendered = String(status.verified ? status.isProtected : true);
|
|
480
|
+
if (!status.verified) {
|
|
481
|
+
writeDiagnostic(`⚠ git-base-branch: --is-protected could not verify repository branch metadata; ` +
|
|
482
|
+
`defaulting to protected (fail-closed). See #3057.\n`);
|
|
483
|
+
}
|
|
484
|
+
const write = deps?.write ?? ((s) => process.stdout.write(s));
|
|
485
|
+
write(rendered + '\n');
|
|
486
|
+
return rendered;
|
|
487
|
+
}
|
|
488
|
+
if (args.length > 0) {
|
|
489
|
+
error(`Unknown flag for git.base-branch: ${args[0]}`, ERROR_REASON.USAGE);
|
|
490
|
+
}
|
|
356
491
|
const { branch, verified } = resolveBaseBranchDiagnostics(cwd, deps);
|
|
357
492
|
if (!verified) {
|
|
358
493
|
const writeDiagnostic = deps?.writeDiagnostic ?? ((s) => process.stderr.write(s));
|
|
@@ -35,7 +35,7 @@ const healthDiagnosticMod = require("../health-diagnostic-types.cjs");
|
|
|
35
35
|
const { SEVERITY, adviseRemedy } = healthDiagnosticMod;
|
|
36
36
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
37
37
|
const phaseIdMod = require("../phase-id.cjs");
|
|
38
|
-
const {
|
|
38
|
+
const { isSentinelPhaseDir } = phaseIdMod;
|
|
39
39
|
// ─── C001 — gap in disk phase numbering (integer sequence) ────────────────
|
|
40
40
|
// (verify.cts:1504-1519)
|
|
41
41
|
function checkC001(snapshot) {
|
|
@@ -47,8 +47,12 @@ function checkC001(snapshot) {
|
|
|
47
47
|
return [];
|
|
48
48
|
const integerPhases = snapshot.allPhaseDirNames.value
|
|
49
49
|
// #3225: exclude sentinel phase ids (999.x/0.x) — never part of the
|
|
50
|
-
// sequential numbering, mirrors verify.cts:1510 verbatim.
|
|
51
|
-
|
|
50
|
+
// sequential numbering, mirrors verify.cts:1510 verbatim. The dot filter
|
|
51
|
+
// already drops every code-prefixed (bracket) name before the sentinel
|
|
52
|
+
// test, so the dir-aware recognizer below is defense-in-depth for the
|
|
53
|
+
// dotless legacy forms — kept so this guard cannot regress if the dot
|
|
54
|
+
// filter is ever loosened (#3639).
|
|
55
|
+
.filter((p) => !p.includes('.') && !isSentinelPhaseDir(p))
|
|
52
56
|
.map((p) => parseInt(p, 10))
|
|
53
57
|
.filter((n) => !Number.isNaN(n))
|
|
54
58
|
.sort((a, b) => a - b);
|
|
@@ -104,7 +104,7 @@ const planningScopeMod = require("../planning-scope.cjs");
|
|
|
104
104
|
const { SCOPE } = planningScopeMod;
|
|
105
105
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
106
106
|
const phaseIdMod = require("../phase-id.cjs");
|
|
107
|
-
const { matchPhaseDirs, normalizePhaseName, extractPhaseToken, isSentinelPhaseId } = phaseIdMod;
|
|
107
|
+
const { matchPhaseDirs, normalizePhaseName, extractPhaseToken, isSentinelPhaseId, isSentinelPhaseDir } = phaseIdMod;
|
|
108
108
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
109
109
|
const validateMod = require("../validate.cjs");
|
|
110
110
|
const { phaseVariants } = validateMod;
|
|
@@ -231,8 +231,11 @@ function checkW007(snapshot) {
|
|
|
231
231
|
// `verify.cts:1373-1397`) — same token, relocated read, not reinvented.
|
|
232
232
|
const token = extractPhaseToken(dirName);
|
|
233
233
|
// #3225: a sentinel dir on disk (999-interim, 0-drafts) is defined as
|
|
234
|
-
// never-on-roadmap; it must not trigger W007.
|
|
235
|
-
|
|
234
|
+
// never-on-roadmap; it must not trigger W007. #3639: judged on the DIR
|
|
235
|
+
// NAME via the dir-aware recognizer — the extracted token is
|
|
236
|
+
// milestone-stripped, so a bracket sentinel (GSD.999-07-icebox) was
|
|
237
|
+
// invisible to the id predicate and false-fired as an orphan.
|
|
238
|
+
if (isSentinelPhaseDir(dirName))
|
|
236
239
|
continue;
|
|
237
240
|
if (claimedDirs.has(dirName))
|
|
238
241
|
continue;
|
|
@@ -35,10 +35,8 @@
|
|
|
35
35
|
* src/health-diagnostic-rules/worktree-health.cts, compiled to
|
|
36
36
|
* gsd-core/bin/lib/health-diagnostic-rules/worktree-health.cjs (gitignored).
|
|
37
37
|
*/
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
};
|
|
41
|
-
const node_path_1 = __importDefault(require("node:path"));
|
|
38
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
39
|
+
const shellCmdProjection = require("../shell-command-projection.cjs");
|
|
42
40
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
43
41
|
const healthDiagnosticMod = require("../health-diagnostic-types.cjs");
|
|
44
42
|
const { SEVERITY, adviseRemedy } = healthDiagnosticMod;
|
|
@@ -121,15 +119,31 @@ function checkW017(snapshot) {
|
|
|
121
119
|
// against `process.cwd()`. Per this batch's brief: the interpolated command
|
|
122
120
|
// (with the real path) lives in `message`; `remedy.args.command` stays a
|
|
123
121
|
// static `<path>` template, mirroring the split the brief specifies.
|
|
122
|
+
// ─── #3663 — path-comparison provenance helper ─────────────────────────────
|
|
123
|
+
//
|
|
124
|
+
// snapshot.cwd is raw process-cwd-derived — path.resolve() normalizes
|
|
125
|
+
// separators and relative segments but NOT casing, and a process launched via
|
|
126
|
+
// a differently-cased path echoes that spelling back. finding.path is
|
|
127
|
+
// `git worktree list`-derived, which self-normalizes to the canonical
|
|
128
|
+
// on-disk casing (forward slashes). Comparing those two spellings strictly
|
|
129
|
+
// misclassifies the ACTIVE worktree as stale on win32 — so the comparison
|
|
130
|
+
// folds case ONLY on win32 (case-insensitive filesystem, via the Shell
|
|
131
|
+
// Command Projection seam's toComparablePathKey — the platform-conditional
|
|
132
|
+
// fold policy lives there, not per call site) and stays case-sensitive on
|
|
133
|
+
// POSIX, where differently-cased paths are genuinely different directories.
|
|
134
|
+
// No realpath resolution — that would change symlink matching behavior.
|
|
135
|
+
function isActiveWorktreePath(activeCwd, worktreePath, platform = process.platform) {
|
|
136
|
+
const active = shellCmdProjection.toComparablePathKey(activeCwd, platform);
|
|
137
|
+
const worktree = shellCmdProjection.toComparablePathKey(worktreePath, platform);
|
|
138
|
+
return active === worktree || active.startsWith(worktree + '/');
|
|
139
|
+
}
|
|
124
140
|
function checkW027(snapshot) {
|
|
125
141
|
const diagnostics = [];
|
|
126
142
|
const activeCwd = snapshot.cwd;
|
|
127
143
|
for (const finding of snapshot.worktreeHealth.value) {
|
|
128
144
|
if (finding.kind !== 'stale')
|
|
129
145
|
continue;
|
|
130
|
-
|
|
131
|
-
const isActiveWorktree = activeCwd === normalizedWorktree || activeCwd.startsWith(normalizedWorktree + node_path_1.default.sep);
|
|
132
|
-
if (isActiveWorktree)
|
|
146
|
+
if (isActiveWorktreePath(activeCwd, finding.path))
|
|
133
147
|
continue;
|
|
134
148
|
diagnostics.push({
|
|
135
149
|
code: 'W027',
|
|
@@ -170,4 +184,4 @@ const RULES = [
|
|
|
170
184
|
check: checkW027,
|
|
171
185
|
},
|
|
172
186
|
];
|
|
173
|
-
module.exports = { RULES };
|
|
187
|
+
module.exports = { RULES, isActiveWorktreePath };
|
|
@@ -103,7 +103,7 @@ const CONSISTENCY_RULES = [
|
|
|
103
103
|
// (`verify.cts:2405-2553`) — ported verbatim, not reinvented.
|
|
104
104
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
105
105
|
const planningWorkspaceMod = require("./planning-workspace.cjs");
|
|
106
|
-
const {
|
|
106
|
+
const { planningDir } = planningWorkspaceMod;
|
|
107
107
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
108
108
|
const configLoaderMod = require("./config-loader.cjs");
|
|
109
109
|
const { CONFIG_DEFAULTS } = configLoaderMod;
|
|
@@ -113,6 +113,12 @@ const { getMilestoneInfo } = roadmapParserMod;
|
|
|
113
113
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
114
114
|
const stateMod = require("./state.cjs");
|
|
115
115
|
const { writeStateMd } = stateMod;
|
|
116
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
117
|
+
const frontmatter = require("./frontmatter.cjs");
|
|
118
|
+
const { extractFrontmatter } = frontmatter;
|
|
119
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
120
|
+
const stateTransitionMod = require("./state-transition.cjs");
|
|
121
|
+
const { rebuildStateTransaction } = stateTransitionMod;
|
|
116
122
|
const clock_cjs_1 = require("./clock.cjs");
|
|
117
123
|
const shell_command_projection_cjs_1 = require("./shell-command-projection.cjs");
|
|
118
124
|
const runtime_slash_cjs_1 = require("./runtime-slash.cjs");
|
|
@@ -160,7 +166,13 @@ function evaluateConsistencyRules(snapshot) {
|
|
|
160
166
|
* document for the read side.
|
|
161
167
|
*/
|
|
162
168
|
function repairPaths(cwd) {
|
|
163
|
-
|
|
169
|
+
// #3749: the project root is PROJECT-aware but deliberately NOT workstream-
|
|
170
|
+
// scoped — under GSD_PROJECT, config.json/MILESTONES.md/milestones belong to
|
|
171
|
+
// `.planning/<project>/` (the same base init.new-project's config_path
|
|
172
|
+
// names); under GSD_WORKSTREAM they stay at the workstream PARENT, keeping
|
|
173
|
+
// the documented root-vs-workstream split. planningDir(cwd, null) is exactly
|
|
174
|
+
// that base: project env honored, ws env suppressed.
|
|
175
|
+
const rootBase = planningDir(cwd, null);
|
|
164
176
|
const wsBase = planningDir(cwd);
|
|
165
177
|
return {
|
|
166
178
|
rootBase,
|
|
@@ -268,7 +280,15 @@ function runRepairAction(cwd, action, paths) {
|
|
|
268
280
|
stateContent += `**Status:** Resuming\n\n`;
|
|
269
281
|
stateContent += `## Session Log\n\n`;
|
|
270
282
|
stateContent += `- ${clock_cjs_1.realClock.localToday()}: STATE.md regenerated by ${slash('health')} --repair\n`;
|
|
271
|
-
|
|
283
|
+
// ADR-3473 §8.6: `/gsd-health --repair` is a factory reset, so
|
|
284
|
+
// preservation must not run; the snapshot of the file being replaced is
|
|
285
|
+
// still captured, and `{}` is the correct, legal snapshot of a
|
|
286
|
+
// STATE.md that had no parseable frontmatter — which is the usual
|
|
287
|
+
// reason this repair fires.
|
|
288
|
+
const priorState = node_fs_1.default.existsSync(statePath) ? ((0, shell_command_projection_cjs_1.platformReadSync)(statePath) ?? '') : '';
|
|
289
|
+
writeStateMd(statePath, stateContent, rebuildStateTransaction({
|
|
290
|
+
snapshot: extractFrontmatter(priorState, statePath),
|
|
291
|
+
}), cwd);
|
|
272
292
|
return { success: true, path: 'STATE.md', extraDetails };
|
|
273
293
|
}
|
|
274
294
|
case REMEDY_ACTION.ADD_NYQUIST_KEY:
|
|
@@ -653,10 +653,25 @@ function extensionEventSurfaceFor(extensionEvents) {
|
|
|
653
653
|
* per-host branch ADR-1239 exists to remove. Omit `prompt` entirely and the
|
|
654
654
|
* resolution is byte-identical to Phase 2's (the unconsumed-resolver shape).
|
|
655
655
|
*
|
|
656
|
-
* Argv order is base args → cwd flag → prompt, so the prompt
|
|
657
|
-
* positional token for the hosts that read it that way.
|
|
656
|
+
* Argv order is base args → model flag → cwd flag → prompt, so the prompt
|
|
657
|
+
* stays the final positional token for the hosts that read it that way. The
|
|
658
|
+
* model flag is placed BEFORE the cwd flag (not after, and not appended at
|
|
659
|
+
* the very end) purely to keep it clear of that trailing positional — the
|
|
660
|
+
* model value itself is never the prompt-adjacent token a host might scan
|
|
661
|
+
* for last.
|
|
662
|
+
*
|
|
663
|
+
* `model` (Phase 4, #3714) is optional, descriptor-gated exactly like prompt:
|
|
664
|
+
* a `modelFlag` string on the descriptor names the flag that pins the
|
|
665
|
+
* spawned executor's model (codex: `--model`); `null`/absent means the host
|
|
666
|
+
* exposes no such override on this exec path, and `[modelFlag, model]` is
|
|
667
|
+
* appended ONLY when both the descriptor's `modelFlag` and the caller's
|
|
668
|
+
* `model` are non-empty strings. Omitting `model` entirely (or passing it to
|
|
669
|
+
* a host with no `modelFlag`) is byte-identical to the resolver's behavior
|
|
670
|
+
* before this parameter existed. This function decides no policy about WHICH
|
|
671
|
+
* model to pass or what 'inherit' means — that is entirely the caller's job;
|
|
672
|
+
* this seam only shapes descriptor + values into argv.
|
|
658
673
|
*/
|
|
659
|
-
function resolveOrchestratorExec(orchestratorExec, cwd, prompt) {
|
|
674
|
+
function resolveOrchestratorExec(orchestratorExec, cwd, prompt, model) {
|
|
660
675
|
if (!orchestratorExec || typeof orchestratorExec !== 'object' || Array.isArray(orchestratorExec)) {
|
|
661
676
|
return { ok: false, reason: 'missing_command' };
|
|
662
677
|
}
|
|
@@ -676,11 +691,22 @@ function resolveOrchestratorExec(orchestratorExec, cwd, prompt) {
|
|
|
676
691
|
if (oe.promptFlag !== undefined && oe.promptFlag !== null && typeof oe.promptFlag !== 'string') {
|
|
677
692
|
return { ok: false, reason: 'invalid_prompt_flag' };
|
|
678
693
|
}
|
|
694
|
+
if (oe.modelFlag !== undefined && oe.modelFlag !== null && typeof oe.modelFlag !== 'string') {
|
|
695
|
+
return { ok: false, reason: 'invalid_model_flag' };
|
|
696
|
+
}
|
|
679
697
|
// An executor spawned with no instruction is a hang, not a degraded run —
|
|
680
698
|
// fail closed rather than launching a prompt-less process.
|
|
681
699
|
if (prompt !== undefined && (typeof prompt !== 'string' || prompt.length === 0)) {
|
|
682
700
|
return { ok: false, reason: 'invalid_prompt' };
|
|
683
701
|
}
|
|
702
|
+
// Unlike `prompt` — where empty is a hang, not a degraded run, hence the
|
|
703
|
+
// fail-closed check above — an absent/null/empty model is simply "use the
|
|
704
|
+
// host default", the same benign degradation `cwdFlag: null` already
|
|
705
|
+
// expresses. Only a present-but-non-string value (number/bool/array/object)
|
|
706
|
+
// is a caller error; null/undefined/'' fall through to "omit the flag".
|
|
707
|
+
if (model !== undefined && model !== null && typeof model !== 'string') {
|
|
708
|
+
return { ok: false, reason: 'invalid_model' };
|
|
709
|
+
}
|
|
684
710
|
// Leading-dash guard, mirroring worktree-safety.cts's `unsafe_leading_dash`
|
|
685
711
|
// check on git arguments. A positional prompt (or a cwd) beginning with '-'
|
|
686
712
|
// is parsed by the spawned CLI as a FLAG, not a value — the same failure the
|
|
@@ -695,10 +721,17 @@ function resolveOrchestratorExec(orchestratorExec, cwd, prompt) {
|
|
|
695
721
|
if (cwd.startsWith('-')) {
|
|
696
722
|
return { ok: false, reason: 'unsafe_leading_dash_cwd' };
|
|
697
723
|
}
|
|
724
|
+
if (typeof model === 'string' && model.startsWith('-')) {
|
|
725
|
+
return { ok: false, reason: 'unsafe_leading_dash_model' };
|
|
726
|
+
}
|
|
698
727
|
const baseArgs = Array.isArray(oe.args) ? [...oe.args] : [];
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
728
|
+
let args = baseArgs;
|
|
729
|
+
if (typeof oe.modelFlag === 'string' && oe.modelFlag.length > 0 && typeof model === 'string' && model.length > 0) {
|
|
730
|
+
args = [...args, oe.modelFlag, model];
|
|
731
|
+
}
|
|
732
|
+
args = typeof oe.cwdFlag === 'string' && oe.cwdFlag.length > 0
|
|
733
|
+
? [...args, oe.cwdFlag, cwd]
|
|
734
|
+
: args;
|
|
702
735
|
if (typeof prompt === 'string') {
|
|
703
736
|
if (typeof oe.promptFlag === 'string' && oe.promptFlag.length > 0) {
|
|
704
737
|
args.push(oe.promptFlag, prompt);
|