@opengsd/gsd-core 1.10.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 +12 -1
- package/agents/gsd-debugger.md +1 -1
- package/agents/gsd-doc-synthesizer.md +2 -4
- package/agents/gsd-dom-verifier.md +169 -0
- package/agents/gsd-eval-auditor.md +1 -1
- package/agents/gsd-executor.md +22 -14
- package/agents/gsd-framework-selector.md +1 -3
- package/agents/gsd-intel-updater.md +1 -1
- package/agents/gsd-mempalace-curator.md +5 -3
- package/agents/gsd-pattern-mapper.md +11 -0
- package/agents/gsd-phase-researcher.md +23 -2
- package/agents/gsd-plan-checker.md +50 -53
- package/agents/gsd-planner.md +50 -50
- 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-user-profiler.md +3 -0
- package/agents/gsd-verifier.md +13 -4
- package/bin/install.js +1448 -1103
- package/commands/gsd/code-review.md +1 -1
- package/commands/gsd/discuss-phase.md +1 -1
- package/commands/gsd/execute-phase.md +1 -1
- package/commands/gsd/import.md +1 -1
- package/commands/gsd/map-codebase.md +1 -1
- package/commands/gsd/mempalace-capture.md +1 -1
- package/commands/gsd/mempalace-recall.md +1 -1
- package/commands/gsd/new-milestone.md +1 -1
- package/commands/gsd/quick.md +9 -5
- package/commands/gsd/review-backlog.md +2 -1
- package/commands/gsd/verify-work.md +1 -1
- package/gsd-core/bin/gsd-tools.cjs +1035 -138
- package/gsd-core/bin/lib/active-workstream-store.cjs +146 -22
- package/gsd-core/bin/lib/adr-parser.cjs +13 -7
- package/gsd-core/bin/lib/agent-install-check.cjs +392 -32
- package/gsd-core/bin/lib/api-coverage.cjs +33 -14
- package/gsd-core/bin/lib/artifacts.cjs +5 -0
- package/gsd-core/bin/lib/assumption-delta.cjs +32 -15
- package/gsd-core/bin/lib/audit-command-router.cjs +9 -2
- package/gsd-core/bin/lib/audit.cjs +1026 -268
- package/gsd-core/bin/lib/broken-windows.cjs +306 -28
- 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-lock.cjs +10 -4
- package/gsd-core/bin/lib/capability-registry.cjs +845 -130
- package/gsd-core/bin/lib/capability-source.cjs +92 -0
- package/gsd-core/bin/lib/capability-state.cjs +18 -3
- package/gsd-core/bin/lib/capability-trust.cjs +444 -25
- package/gsd-core/bin/lib/capability-validator.cjs +700 -40
- package/gsd-core/bin/lib/capability-writer.cjs +3 -2
- package/gsd-core/bin/lib/check-command-router.cjs +216 -42
- package/gsd-core/bin/lib/claude-orchestration.cjs +56 -3
- 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 +735 -0
- package/gsd-core/bin/lib/command-aliases.cjs +22 -0
- package/gsd-core/bin/lib/command-arg-projection.cjs +144 -14
- package/gsd-core/bin/lib/command-roster.cjs +44 -1
- package/gsd-core/bin/lib/command-routing-hub.cjs +31 -2
- package/gsd-core/bin/lib/commands.cjs +1172 -108
- package/gsd-core/bin/lib/commonjs-marker.cjs +12 -6
- package/gsd-core/bin/lib/complexity-trigger.cjs +1192 -0
- package/gsd-core/bin/lib/config-loader.cjs +187 -23
- package/gsd-core/bin/lib/config.cjs +102 -3
- package/gsd-core/bin/lib/configuration.cjs +129 -37
- package/gsd-core/bin/lib/core-utils.cjs +208 -33
- package/gsd-core/bin/lib/decisions.cjs +23 -0
- 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/fallow-runner.cjs +20 -44
- package/gsd-core/bin/lib/frontmatter.cjs +899 -229
- package/gsd-core/bin/lib/gap-checker.cjs +95 -10
- package/gsd-core/bin/lib/git-base-branch.cjs +276 -39
- package/gsd-core/bin/lib/gsd2-import.cjs +10 -1
- package/gsd-core/bin/lib/health-diagnostic-rules/agent-install.cjs +101 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/config-validation.cjs +348 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/consistency.cjs +149 -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 +268 -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 +187 -0
- package/gsd-core/bin/lib/health-diagnostic-types.cjs +68 -0
- package/gsd-core/bin/lib/health-diagnostic.cjs +451 -0
- package/gsd-core/bin/lib/host-integration.cjs +39 -6
- package/gsd-core/bin/lib/host-runtime-detection.cjs +134 -0
- package/gsd-core/bin/lib/init-command-router.cjs +118 -21
- package/gsd-core/bin/lib/init.cjs +439 -168
- package/gsd-core/bin/lib/install-effort-resolver.cjs +73 -30
- package/gsd-core/bin/lib/install-engine.cjs +811 -259
- package/gsd-core/bin/lib/install-fs-adapter.cjs +262 -0
- package/gsd-core/bin/lib/install-model-override-resolver.cjs +235 -0
- package/gsd-core/bin/lib/install-profiles.cjs +212 -61
- 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-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 +148 -38
- package/gsd-core/bin/lib/intel.cjs +101 -26
- package/gsd-core/bin/lib/io.cjs +170 -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-sectionizer.cjs +2 -1
- package/gsd-core/bin/lib/markdown-table.cjs +183 -22
- package/gsd-core/bin/lib/milestone-lock.cjs +248 -0
- package/gsd-core/bin/lib/milestone.cjs +842 -73
- package/gsd-core/bin/lib/model-catalog.cjs +232 -16
- package/gsd-core/bin/lib/model-resolver.cjs +193 -68
- package/gsd-core/bin/lib/normalize-test-command.cjs +1 -1
- package/gsd-core/bin/lib/onboard-projection.cjs +5 -1
- package/gsd-core/bin/lib/pattern.cjs +122 -0
- package/gsd-core/bin/lib/phase-estimation.cjs +18 -9
- package/gsd-core/bin/lib/phase-id.cjs +514 -40
- package/gsd-core/bin/lib/phase-lifecycle.cjs +52 -19
- package/gsd-core/bin/lib/phase-locator.cjs +262 -34
- package/gsd-core/bin/lib/phase.cjs +1038 -214
- package/gsd-core/bin/lib/plan-dependency-graph.cjs +72 -1
- package/gsd-core/bin/lib/plan-document.cjs +263 -0
- package/gsd-core/bin/lib/plan-drift-guard.cjs +120 -0
- package/gsd-core/bin/lib/plan-scan.cjs +98 -3
- 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-scope.cjs +31 -0
- package/gsd-core/bin/lib/planning-snapshot.cjs +894 -0
- package/gsd-core/bin/lib/planning-workspace.cjs +112 -6
- package/gsd-core/bin/lib/probe-core.cjs +5 -2
- package/gsd-core/bin/lib/profile-output.cjs +1 -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 +766 -0
- package/gsd-core/bin/lib/retired-artifact-cleanup.cjs +11 -6
- package/gsd-core/bin/lib/review-lane-descriptor.cjs +22 -13
- package/gsd-core/bin/lib/review-lane-invocation.cjs +30 -0
- package/gsd-core/bin/lib/review-lane-runner.cjs +421 -66
- package/gsd-core/bin/lib/review-reviewer-selection.cjs +13 -18
- package/gsd-core/bin/lib/roadmap-command-router.cjs +59 -11
- package/gsd-core/bin/lib/roadmap-parser.cjs +1006 -184
- package/gsd-core/bin/lib/roadmap-upgrade.cjs +37 -10
- package/gsd-core/bin/lib/roadmap.cjs +442 -96
- package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +702 -52
- package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +14 -2
- package/gsd-core/bin/lib/runtime-artifact-layout.cjs +459 -55
- package/gsd-core/bin/lib/runtime-config-adapter-registry.cjs +3 -2
- package/gsd-core/bin/lib/runtime-homes.cjs +69 -3
- package/gsd-core/bin/lib/runtime-hooks-surface.cjs +402 -58
- package/gsd-core/bin/lib/runtime-identity.cjs +234 -0
- package/gsd-core/bin/lib/runtime-name-policy.cjs +3 -1
- package/gsd-core/bin/lib/runtime-slash.cjs +96 -8
- package/gsd-core/bin/lib/security.cjs +104 -5
- package/gsd-core/bin/lib/shell-command-projection.cjs +342 -7
- package/gsd-core/bin/lib/smart-entry.cjs +133 -23
- package/gsd-core/bin/lib/spec-section.cjs +12 -7
- package/gsd-core/bin/lib/state-command-router.cjs +52 -19
- package/gsd-core/bin/lib/state-contract.cjs +359 -0
- package/gsd-core/bin/lib/state-document.cjs +338 -8
- package/gsd-core/bin/lib/state-md-schema.cjs +221 -0
- package/gsd-core/bin/lib/state-transition.cjs +846 -176
- package/gsd-core/bin/lib/state.cjs +2589 -369
- package/gsd-core/bin/lib/surface.cjs +33 -11
- 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/text-lines.cjs +80 -0
- package/gsd-core/bin/lib/token-scanner.cjs +76 -0
- package/gsd-core/bin/lib/uat-predicate.cjs +67 -23
- package/gsd-core/bin/lib/uat.cjs +1761 -167
- package/gsd-core/bin/lib/ui-consideration-probe.cjs +9 -1
- package/gsd-core/bin/lib/ui-frontend-evidence.cjs +157 -0
- package/gsd-core/bin/lib/ui-safety-gate.cjs +51 -12
- package/gsd-core/bin/lib/unusable-input.cjs +37 -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-command-router.cjs +2 -2
- package/gsd-core/bin/lib/validate.cjs +20 -6
- package/gsd-core/bin/lib/vendor/README.md +75 -0
- package/gsd-core/bin/lib/vendor/js-yaml.cjs +3014 -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 +272 -9
- package/gsd-core/bin/lib/verify-command-grounding.cjs +846 -0
- package/gsd-core/bin/lib/verify.cjs +453 -918
- package/gsd-core/bin/lib/workstream-inventory-builder.cjs +53 -32
- package/gsd-core/bin/lib/workstream-inventory.cjs +63 -10
- package/gsd-core/bin/lib/workstream-name-policy.cjs +25 -4
- package/gsd-core/bin/lib/workstream.cjs +2 -2
- package/gsd-core/bin/lib/worktree-base-ref.cjs +66 -12
- package/gsd-core/bin/lib/worktree-safety.cjs +341 -18
- package/gsd-core/bin/shared/config-defaults.manifest.json +8 -1
- package/gsd-core/bin/shared/config-schema.manifest.json +12 -1
- 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 +44 -26
- 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 +39 -21
- package/gsd-core/references/context-budget.md +1 -1
- package/gsd-core/references/decimal-phase-calculation.md +5 -5
- package/gsd-core/references/dispatch-isolation-gate.md +138 -0
- package/gsd-core/references/doc-conflict-engine.md +1 -1
- package/gsd-core/references/edge-probe.md +8 -0
- package/gsd-core/references/execute-mvp-tdd.md +4 -6
- package/gsd-core/references/execute-phase-between-wave-reset.md +15 -14
- 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 +17 -11
- 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 +5 -4
- package/gsd-core/references/gsd-run-resolver.md +1 -1
- package/gsd-core/references/loop-hook-dispatch.md +61 -2
- package/gsd-core/references/model-profiles.md +12 -4
- package/gsd-core/references/mvp-concepts.md +9 -9
- 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-guidance.md +3 -9
- package/gsd-core/references/planner-human-verify-mode.md +15 -1
- package/gsd-core/references/planner-preconditions.md +1 -1
- package/gsd-core/references/planner-reviews.md +1 -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 +44 -13
- package/gsd-core/references/reviewer-instances.md +31 -0
- package/gsd-core/references/revision-loop.md +1 -1
- package/gsd-core/references/runtime-aware-dispatch.md +1 -1
- package/gsd-core/references/specless-probe-fallback.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 +5 -5
- package/gsd-core/references/verifier-phase-gates.md +192 -0
- package/gsd-core/references/verify-command-path-resolvability.md +42 -0
- package/gsd-core/references/verify-mvp-mode.md +2 -2
- package/gsd-core/references/workstream-flag.md +33 -17
- 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/discussion-log.md +1 -1
- package/gsd-core/templates/phase-prompt.md +5 -4
- package/gsd-core/templates/state.md +11 -4
- package/gsd-core/templates/verification-report.md +9 -1
- package/gsd-core/workflows/_runtime-launcher.snippet.sh +1 -1
- package/gsd-core/workflows/add-backlog.md +1 -1
- package/gsd-core/workflows/add-phase.md +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 +13 -20
- 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 +11 -27
- package/gsd-core/workflows/check-todos.md +1 -1
- package/gsd-core/workflows/cleanup.md +64 -5
- package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +14 -4
- package/gsd-core/workflows/code-review-fix.md +38 -11
- package/gsd-core/workflows/code-review.md +159 -52
- package/gsd-core/workflows/complete-milestone.md +151 -23
- package/gsd-core/workflows/debug.md +12 -8
- package/gsd-core/workflows/diagnose-issues.md +47 -15
- package/gsd-core/workflows/discuss-phase/modes/advisor.md +1 -1
- package/gsd-core/workflows/discuss-phase/modes/chain.md +5 -8
- package/gsd-core/workflows/discuss-phase/modes/default.md +1 -1
- 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 +4 -3
- 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 +27 -2
- package/gsd-core/workflows/eval-review.md +7 -14
- package/gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +142 -15
- 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 +77 -0
- package/gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +24 -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 +72 -100
- package/gsd-core/workflows/execute-plan.md +52 -15
- package/gsd-core/workflows/explore.md +131 -4
- package/gsd-core/workflows/extract-learnings.md +1 -1
- package/gsd-core/workflows/fast.md +10 -2
- package/gsd-core/workflows/forensics.md +1 -1
- package/gsd-core/workflows/graduation.md +5 -5
- package/gsd-core/workflows/health.md +76 -10
- package/gsd-core/workflows/import.md +18 -15
- package/gsd-core/workflows/inbox.md +4 -5
- package/gsd-core/workflows/ingest-docs.md +49 -16
- 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 +8 -5
- package/gsd-core/workflows/new-milestone.md +22 -29
- package/gsd-core/workflows/new-project/steps/auto-mode-config.md +1 -1
- package/gsd-core/workflows/new-project.md +26 -40
- package/gsd-core/workflows/new-workspace.md +1 -1
- package/gsd-core/workflows/next.md +14 -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 +162 -59
- package/gsd-core/workflows/plan-review-convergence.md +96 -11
- 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 +61 -18
- package/gsd-core/workflows/quick/steps/discussion-phase.md +1 -3
- package/gsd-core/workflows/quick/steps/plan-checker-loop.md +5 -7
- package/gsd-core/workflows/quick/steps/quick-verification.md +28 -9
- package/gsd-core/workflows/quick/steps/research-phase.md +4 -6
- package/gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md +3 -3
- package/gsd-core/workflows/quick.md +55 -44
- 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 +219 -20
- package/gsd-core/workflows/scan.md +1 -1
- package/gsd-core/workflows/secure-phase.md +3 -3
- package/gsd-core/workflows/session-report.md +2 -1
- package/gsd-core/workflows/settings-advanced.md +7 -9
- package/gsd-core/workflows/settings-integrations.md +64 -31
- package/gsd-core/workflows/settings.md +69 -7
- package/gsd-core/workflows/ship.md +116 -50
- 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 +53 -13
- 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 +64 -9
- package/gsd-core/workflows/thread.md +11 -7
- package/gsd-core/workflows/transition.md +49 -14
- package/gsd-core/workflows/ui-phase.md +15 -21
- package/gsd-core/workflows/ui-review.md +8 -12
- package/gsd-core/workflows/ultraplan-phase.md +5 -13
- package/gsd-core/workflows/undo.md +8 -16
- package/gsd-core/workflows/update.md +7 -11
- package/gsd-core/workflows/validate-phase.md +3 -3
- 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 +66 -25
- package/hooks/dist/gsd-agent-isolation-guard.js +158 -30
- package/hooks/dist/gsd-check-update-worker.js +56 -13
- package/hooks/dist/gsd-check-update.js +19 -1
- 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 +2 -3
- 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 +83 -3
- package/hooks/dist/gsd-cursor-subagent-stop.js +6 -3
- 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 +37 -27
- package/hooks/dist/gsd-read-guard.js +16 -7
- package/hooks/dist/gsd-read-injection-scanner.js +55 -32
- package/hooks/dist/gsd-session-state.sh +1 -0
- package/hooks/dist/gsd-statusline.js +231 -24
- package/hooks/dist/gsd-update-banner.js +22 -1
- 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 +162 -46
- 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-cmd.js +92 -59
- package/hooks/dist/lib/git-probe.js +84 -0
- package/hooks/dist/lib/hook-exit.js +81 -0
- package/hooks/dist/lib/injection-patterns.js +45 -0
- package/hooks/dist/lib/isolation-deny-reason.js +39 -0
- package/hooks/dist/lib/isolation-sentinel.js +9 -0
- package/hooks/dist/managed-hooks-registry.cjs +3 -0
- package/hooks/gsd-agent-isolation-guard.js +158 -30
- package/hooks/gsd-check-update-worker.js +56 -13
- package/hooks/gsd-check-update.js +19 -1
- 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 +2 -3
- package/hooks/gsd-cursor-session-start.js +2 -1
- package/hooks/gsd-cursor-stop.js +2 -1
- package/hooks/gsd-cursor-subagent-start.js +83 -3
- package/hooks/gsd-cursor-subagent-stop.js +6 -3
- 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 +37 -27
- package/hooks/gsd-read-guard.js +16 -7
- package/hooks/gsd-read-injection-scanner.js +55 -32
- package/hooks/gsd-session-state.sh +1 -0
- package/hooks/gsd-statusline.js +231 -24
- package/hooks/gsd-update-banner.js +22 -1
- 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 +162 -46
- 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-cmd.js +92 -59
- package/hooks/lib/git-probe.js +84 -0
- package/hooks/lib/hook-exit.js +81 -0
- package/hooks/lib/injection-patterns.js +45 -0
- package/hooks/lib/isolation-deny-reason.js +39 -0
- package/hooks/lib/isolation-sentinel.js +9 -0
- package/hooks/managed-hooks-registry.cjs +3 -0
- package/package.json +28 -11
- package/pi/gsd.cjs +19 -5
- package/scripts/base64-scan.sh +74 -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 +5 -0
- package/scripts/changeset/lint.cjs +60 -5
- package/scripts/check-alias-drift.cjs +7 -43
- package/scripts/check-contract-drift.cjs +297 -0
- package/scripts/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 +64 -14
- package/scripts/ci-timeout-report.cjs +230 -0
- package/scripts/command-contract-helpers.cjs +903 -1
- package/scripts/docs-guard-registry.cjs +396 -0
- package/scripts/gen-adr-index.cjs +728 -38
- package/scripts/gen-capability-registry.cjs +11 -21
- package/scripts/gen-context-index.cjs +2 -11
- 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-health-docs.cjs +390 -0
- package/scripts/gen-hooks-cli-exit.cjs +239 -0
- package/scripts/gen-install-tree-fixtures.cjs +2 -2
- package/scripts/gen-inventory-manifest.cjs +50 -4
- package/scripts/gen-loop-host-contract.cjs +138 -25
- package/scripts/gen-registry.cjs +3 -14
- 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/alias-drift-families.cjs +46 -0
- package/scripts/lib/ci-job-timing.cjs +72 -0
- package/scripts/lib/cli-exit.cjs +546 -44
- package/scripts/lib/drift-scan.cjs +308 -0
- package/scripts/lib/exit-code-registry.cjs +98 -0
- package/scripts/lib/ndjson-reporter.cjs +119 -0
- package/scripts/lint-allow-test-rule-refs.allowlist.json +1 -26
- package/scripts/lint-allow-test-rule-refs.effective-ceiling.json +4 -0
- package/scripts/lint-allow-test-rule-refs.unverified-ceiling.json +3 -0
- package/scripts/lint-canary-version-leak.cjs +73 -0
- package/scripts/lint-command-contract.cjs +96 -13
- package/scripts/lint-completion-predicate-drift.cjs +933 -0
- package/scripts/lint-completion-ratio-drift.cjs +214 -0
- package/scripts/lint-default-flip-documentation.cjs +193 -0
- package/scripts/lint-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 +38 -0
- package/scripts/lint-eslint-glob-coverage.cjs +340 -0
- package/scripts/{lint-fix-has-regression-test.cjs → lint-fix-has-regression-tests.cjs} +12 -6
- package/scripts/lint-frontmatter-scalar-broad-grep.cjs +237 -0
- package/scripts/lint-health-diagnostic-rule-table.cjs +461 -0
- package/scripts/lint-hooks-runtime-build-seam.cjs +262 -0
- package/scripts/lint-milestone-window-drift.cjs +468 -0
- package/scripts/lint-mutation-test-derivation-drift.cjs +86 -0
- package/scripts/lint-phase-enumeration-drift.cjs +492 -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 +471 -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 +488 -0
- 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-field-drift.cjs +805 -0
- package/scripts/lint-state-write-path-drift.cjs +950 -0
- package/scripts/lint-test-file-count.allowlist.json +137 -8
- package/scripts/lint-test-file-count.cjs +25 -3
- package/scripts/lint-unreachable-guard-drift.cjs +830 -0
- package/scripts/lint-vendored-deps.cjs +297 -0
- package/scripts/mutation-matrix.cjs +599 -50
- package/scripts/pr-changed-files.cjs +63 -0
- package/scripts/pr-template-policy.cjs +14 -4
- package/scripts/prompt-injection-scan.sh +100 -14
- package/scripts/require-issue-link-policy.cjs +192 -0
- package/scripts/secret-scan.sh +75 -13
- package/scripts/select-docs-guards.cjs +56 -0
- package/scripts/sync-runtime-launcher.cjs +24 -7
- package/skills/gsd-autonomous/SKILL.md +0 -1
- package/skills/gsd-code-review/SKILL.md +1 -1
- package/skills/gsd-discuss-phase/SKILL.md +1 -1
- package/skills/gsd-execute-phase/SKILL.md +1 -2
- package/skills/gsd-import/SKILL.md +1 -1
- package/skills/gsd-map-codebase/SKILL.md +1 -1
- package/skills/gsd-mempalace-capture/SKILL.md +1 -1
- package/skills/gsd-mempalace-recall/SKILL.md +1 -1
- package/skills/gsd-new-milestone/SKILL.md +1 -1
- package/skills/gsd-next/SKILL.md +0 -1
- package/skills/gsd-plan-phase/SKILL.md +0 -1
- package/skills/gsd-progress/SKILL.md +0 -1
- package/skills/gsd-quick/SKILL.md +9 -5
- 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/bin/lib/ui-safety-gate.cjs +0 -107
- package/gsd-core/workflows/discovery-phase.md +0 -298
- package/gsd-core/workflows/plan-milestone-gaps.md +0 -281
- package/gsd-core/workflows/verify-phase.md +0 -574
- package/scripts/affected-tests-lib.cjs +0 -554
- package/scripts/lint-allow-test-rule-refs.cjs +0 -162
- package/scripts/lint-emitted-drift-ack.cjs +0 -344
- package/scripts/run-affected-tests.cjs +0 -7
- package/scripts/run-tests.cjs +0 -1051
|
@@ -23,15 +23,19 @@ 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;
|
|
27
|
-
|
|
28
|
-
const phaseId = require("./phase-id.cjs");
|
|
29
|
-
const { escapeRegex } = phaseId;
|
|
26
|
+
const { output, error, formatDiagnosticToken } = io;
|
|
27
|
+
const pattern_cjs_1 = require("./pattern.cjs");
|
|
30
28
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
31
29
|
const planningWorkspace = require("./planning-workspace.cjs");
|
|
32
30
|
const { planningPaths, planningDir, findContextMdIn } = planningWorkspace;
|
|
33
31
|
const decisions_cjs_1 = require("./decisions.cjs");
|
|
34
32
|
const markdown_sectionizer_cjs_1 = require("./markdown-sectionizer.cjs");
|
|
33
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
34
|
+
const planScanMod = require("./plan-scan.cjs");
|
|
35
|
+
const { scanPhasePlans } = planScanMod;
|
|
36
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
37
|
+
const phaseIdMod = require("./phase-id.cjs");
|
|
38
|
+
const { scopeToPhase } = phaseIdMod;
|
|
35
39
|
/**
|
|
36
40
|
* Parse REQ-IDs from REQUIREMENTS.md content.
|
|
37
41
|
*
|
|
@@ -50,6 +54,17 @@ function parseRequirements(reqMd) {
|
|
|
50
54
|
// The **ID** is extracted from the bullet text caller-side — the seam provides
|
|
51
55
|
// the raw text; we parse the bold-ID prefix from it here.
|
|
52
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*/;
|
|
53
68
|
for (const bullet of (0, markdown_sectionizer_cjs_1.iterateBullets)(reqMd)) {
|
|
54
69
|
if (bullet.marker !== 'checkbox-unchecked' && bullet.marker !== 'checkbox-checked')
|
|
55
70
|
continue;
|
|
@@ -61,7 +76,8 @@ function parseRequirements(reqMd) {
|
|
|
61
76
|
continue;
|
|
62
77
|
if (!seen.has(id)) {
|
|
63
78
|
seen.add(id);
|
|
64
|
-
|
|
79
|
+
const rawText = m[2] || '';
|
|
80
|
+
out.push({ id, text: rawText.replace(ONE_LEADING_DELIMITER_RE, '').trim() });
|
|
65
81
|
}
|
|
66
82
|
}
|
|
67
83
|
// Pipe-table-row path and separator-row skip stay caller-side
|
|
@@ -91,7 +107,7 @@ function parseRequirements(reqMd) {
|
|
|
91
107
|
}
|
|
92
108
|
function detectCoverage(items, planText) {
|
|
93
109
|
return items.map(it => {
|
|
94
|
-
const re = new RegExp('\\b' + escapeRegex(it.id) + '\\b');
|
|
110
|
+
const re = new RegExp('\\b' + (0, pattern_cjs_1.escapeRegex)(it.id) + '\\b');
|
|
95
111
|
return {
|
|
96
112
|
source: it.source,
|
|
97
113
|
item: it.id,
|
|
@@ -152,6 +168,38 @@ const PHASE_REQ_RANGE_RE = /^(.+-)(\d+)\.\.(.+-)(\d+)$/;
|
|
|
152
168
|
* against pathological input like `X-1..X-100000` ballooning the comparison set.
|
|
153
169
|
*/
|
|
154
170
|
const MAX_PHASE_REQ_RANGE = 1000;
|
|
171
|
+
/**
|
|
172
|
+
* Shape filter applied to `--phase-req-ids` tokens AFTER range expansion
|
|
173
|
+
* (#3189). ROADMAP `**Requirements:**` lines routinely carry prose trailing
|
|
174
|
+
* the real ID list — locked-decision annotations, ambiguity scores,
|
|
175
|
+
* prohibitions, dates — and the function's own contract says callers may pass
|
|
176
|
+
* that roadmap value through verbatim. Without a shape filter every prose word
|
|
177
|
+
* survives the whitespace split and is reported as an individually-missing
|
|
178
|
+
* requirement, drowning the real coverage signal.
|
|
179
|
+
*
|
|
180
|
+
* The filter is intentionally wider than `ID_PATTERN` (which mandates a hyphen
|
|
181
|
+
* and so would reject the hyphen-less `R1`..`R8` family real roadmaps use).
|
|
182
|
+
* Three load-bearing details (#3189):
|
|
183
|
+
*
|
|
184
|
+
* 1. The digit lookahead `(?=.*\d)` matters. Without it ALL-CAPS prose tokens
|
|
185
|
+
* that appear in annotations (`LOCKED`, `TBD`-as-prose, `NONE`-as-prose)
|
|
186
|
+
* would pass and still reach the report as fake IDs. Every real
|
|
187
|
+
* requirement ID carries a digit (`R1`, `SEL-01`, `P1-P3`).
|
|
188
|
+
* 2. It MUST run after `expandPhaseReqIdToken`. Filtering before would
|
|
189
|
+
* discard the range tokens themselves (`SEL-01..SEL-03` matches no
|
|
190
|
+
* single-ID shape), silently undoing the #1269 / #1419 range expansion.
|
|
191
|
+
* 3. It is NOT reusable as `ID_PATTERN`. `ID_PATTERN` mandates a hyphen,
|
|
192
|
+
* so it rejects `R1`..`R8` and would leave only range-shaped tokens
|
|
193
|
+
* standing — a silently-empty report, which is worse.
|
|
194
|
+
*
|
|
195
|
+
* Accepts both hyphen-less digit-bearing IDs (`R1`, `R8`) and prefix-hyphen
|
|
196
|
+
* IDs (`REQ-01`, `SEL-01`, `BACK-07`), including multi-segment prefixes
|
|
197
|
+
* (`REQ2-01`). Rejects prose (`LOCKED`), punctuation (`—`, `##`, `+`),
|
|
198
|
+
* dates, version-likes (`0.12;`), backtick fragments, and any token still
|
|
199
|
+
* containing `.` (so invalid range tokens returned literal by
|
|
200
|
+
* `expandPhaseReqIdToken` are dropped rather than surfaced as fake IDs).
|
|
201
|
+
*/
|
|
202
|
+
const PHASE_REQ_ID_SHAPE_RE = /^(?=.*\d)[A-Z][A-Z0-9]*(?:[-_][A-Za-z0-9]+)*$/;
|
|
155
203
|
/**
|
|
156
204
|
* Expand a single `--phase-req-ids` token in place. If it is a valid ascending
|
|
157
205
|
* same-prefix numeric range (`<PREFIX>-NN..<PREFIX>-MM`, identical prefix both
|
|
@@ -212,6 +260,15 @@ function expandPhaseReqIdToken(token) {
|
|
|
212
260
|
* spanning more than MAX_PHASE_REQ_RANGE IDs) stays literal — no partial
|
|
213
261
|
* expansion, no guessing.
|
|
214
262
|
*
|
|
263
|
+
* ID-shape filter (#3189): STRICTLY AFTER range expansion, every token is
|
|
264
|
+
* filtered through `PHASE_REQ_ID_SHAPE_RE`. ROADMAP `**Requirements:**` lines
|
|
265
|
+
* routinely carry prose trailing the real ID list (locked-decision annotations,
|
|
266
|
+
* ambiguity scores, prohibitions, dates); without the filter every prose word
|
|
267
|
+
* would be reported as an individually-missing requirement. Tokens that cannot
|
|
268
|
+
* be requirement IDs (prose, punctuation, dates, invalid range tokens left
|
|
269
|
+
* literal by `expandPhaseReqIdToken`) are dropped; an input whose every token
|
|
270
|
+
* is dropped collapses to `null` (skip semantics).
|
|
271
|
+
*
|
|
215
272
|
* Tolerates JSON-array-ish input (`["REQ-01","REQ-02"]`) since callers may pass
|
|
216
273
|
* the roadmap value through verbatim.
|
|
217
274
|
*/
|
|
@@ -229,7 +286,12 @@ function normalizePhaseReqIds(rawVal) {
|
|
|
229
286
|
const ids = v.split(/[\s,]+/).map(s => s.trim()).filter(Boolean);
|
|
230
287
|
// Expand range tokens (#1269) per-token AFTER the split, preserving input order.
|
|
231
288
|
const expanded = ids.flatMap(expandPhaseReqIdToken);
|
|
232
|
-
|
|
289
|
+
// #3189: drop tokens that cannot be requirement IDs (prose, punctuation,
|
|
290
|
+
// dates, invalid range tokens left literal by expandPhaseReqIdToken). MUST
|
|
291
|
+
// run after expand so valid ranges still expand (filtering before would
|
|
292
|
+
// discard `SEL-01..SEL-03` itself, undoing #1269 / #1419).
|
|
293
|
+
const filtered = expanded.filter(t => PHASE_REQ_ID_SHAPE_RE.test(t));
|
|
294
|
+
return filtered.length === 0 ? null : filtered;
|
|
233
295
|
}
|
|
234
296
|
function runGapAnalysis(cwd, phaseDir, options = {}) {
|
|
235
297
|
const phaseReqIds = normalizePhaseReqIds(options.phaseReqIds);
|
|
@@ -240,6 +302,7 @@ function runGapAnalysis(cwd, phaseDir, options = {}) {
|
|
|
240
302
|
table: '',
|
|
241
303
|
summary: 'workflow.post_planning_gaps disabled — skipping post-planning gap analysis',
|
|
242
304
|
counts: { total: 0, covered: 0, uncovered: 0 },
|
|
305
|
+
phase_dir_read_error: null,
|
|
243
306
|
};
|
|
244
307
|
}
|
|
245
308
|
const absPhaseDir = node_path_1.default.isAbsolute(phaseDir) ? phaseDir : node_path_1.default.join(cwd, phaseDir);
|
|
@@ -263,12 +326,25 @@ function runGapAnalysis(cwd, phaseDir, options = {}) {
|
|
|
263
326
|
// Read the phase directory once; reuse the listing for both context detection
|
|
264
327
|
// and plan-file enumeration (avoids redundant readdirSync calls).
|
|
265
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;
|
|
266
334
|
try {
|
|
267
335
|
if (node_fs_1.default.existsSync(absPhaseDir))
|
|
268
336
|
phaseDirFiles = node_fs_1.default.readdirSync(absPhaseDir);
|
|
269
337
|
}
|
|
270
|
-
catch {
|
|
271
|
-
|
|
338
|
+
catch (err) {
|
|
339
|
+
phaseDirReadError = `Could not read phase directory ${formatDiagnosticToken(absPhaseDir)}: ${formatDiagnosticToken(err?.message ?? String(err))}`;
|
|
340
|
+
}
|
|
341
|
+
// #3511-class: scope the raw listing to this phase dir before the
|
|
342
|
+
// phase-numbered -CONTEXT.md predicate. `phaseDirFiles` itself stays raw —
|
|
343
|
+
// it is also reused below only as a `.length > 0` guard ahead of
|
|
344
|
+
// scanPhasePlans's own plan-sequence-numbered enumeration, a different
|
|
345
|
+
// grammar that must not be scoped by phase number.
|
|
346
|
+
const scopedPhaseDirFiles = scopeToPhase(phaseDirFiles, node_path_1.default.basename(absPhaseDir));
|
|
347
|
+
const ctxFile = findContextMdIn(scopedPhaseDirFiles);
|
|
272
348
|
const ctxPath = ctxFile ? node_path_1.default.join(absPhaseDir, ctxFile) : null;
|
|
273
349
|
const ctxMd = ctxPath ? node_fs_1.default.readFileSync(ctxPath, 'utf-8') : '';
|
|
274
350
|
// Use extractDecisions so gap-checker can distinguish could-not-parse from none-present.
|
|
@@ -278,7 +354,12 @@ function runGapAnalysis(cwd, phaseDir, options = {}) {
|
|
|
278
354
|
let planText = '';
|
|
279
355
|
try {
|
|
280
356
|
if (phaseDirFiles.length > 0) {
|
|
281
|
-
|
|
357
|
+
// #3183 (lint-plan-count-drift): source the live plan-file list from
|
|
358
|
+
// the single owner (scanPhasePlans) instead of a local `-PLAN\.md$`
|
|
359
|
+
// filter on the already-read listing — picks up bare PLAN.md, nested
|
|
360
|
+
// plans/, and excludes superseded plans, none of which the prior
|
|
361
|
+
// root-only exact-suffix filter did.
|
|
362
|
+
const files = scanPhasePlans(absPhaseDir).planFiles;
|
|
282
363
|
planText = files.map(f => {
|
|
283
364
|
try {
|
|
284
365
|
return node_fs_1.default.readFileSync(node_path_1.default.join(absPhaseDir, f), 'utf-8');
|
|
@@ -323,6 +404,7 @@ function runGapAnalysis(cwd, phaseDir, options = {}) {
|
|
|
323
404
|
table: formatGapTable(rows) + '\n' + coverageSummary + '\n\n' + mismatchMsg,
|
|
324
405
|
summary: coverageSummary + '; extracted 0 of N — possible format mismatch',
|
|
325
406
|
counts: { total: rows.length, covered, uncovered },
|
|
407
|
+
phase_dir_read_error: phaseDirReadError,
|
|
326
408
|
};
|
|
327
409
|
}
|
|
328
410
|
return {
|
|
@@ -331,6 +413,7 @@ function runGapAnalysis(cwd, phaseDir, options = {}) {
|
|
|
331
413
|
table: mismatchMsg,
|
|
332
414
|
summary: 'extracted 0 of N — possible format mismatch',
|
|
333
415
|
counts: { total: 0, covered: 0, uncovered: 0 },
|
|
416
|
+
phase_dir_read_error: phaseDirReadError,
|
|
334
417
|
};
|
|
335
418
|
}
|
|
336
419
|
// #1365: if no items at all, surface a clean no-check message.
|
|
@@ -347,6 +430,7 @@ function runGapAnalysis(cwd, phaseDir, options = {}) {
|
|
|
347
430
|
table: '## Post-Planning Gap Analysis\n\nNo requirements or decisions to check.\n',
|
|
348
431
|
summary: 'no requirements or decisions to check',
|
|
349
432
|
counts: { total: 0, covered: 0, uncovered: 0 },
|
|
433
|
+
phase_dir_read_error: phaseDirReadError,
|
|
350
434
|
};
|
|
351
435
|
}
|
|
352
436
|
const rows = sortRows([
|
|
@@ -364,6 +448,7 @@ function runGapAnalysis(cwd, phaseDir, options = {}) {
|
|
|
364
448
|
table: formatGapTable(rows) + '\n' + summary + '\n',
|
|
365
449
|
summary,
|
|
366
450
|
counts: { total: rows.length, covered, uncovered },
|
|
451
|
+
phase_dir_read_error: phaseDirReadError,
|
|
367
452
|
};
|
|
368
453
|
}
|
|
369
454
|
function cmdGapAnalysis(cwd, args, raw) {
|
|
@@ -34,55 +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;
|
|
46
|
+
exports.phaseStartCommit = phaseStartCommit;
|
|
47
|
+
exports.changedFilesSince = changedFilesSince;
|
|
44
48
|
exports.cmdGitBaseBranch = cmdGitBaseBranch;
|
|
45
|
-
const node_fs_1 = __importDefault(require("node:fs"));
|
|
46
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");
|
|
47
56
|
const shell_command_projection_cjs_1 = require("./shell-command-projection.cjs");
|
|
48
|
-
|
|
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
|
+
}
|
|
49
94
|
/**
|
|
50
|
-
*
|
|
51
|
-
*
|
|
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).
|
|
52
112
|
*/
|
|
53
|
-
function
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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 */ }
|
|
57
129
|
}
|
|
58
|
-
catch {
|
|
59
|
-
return null;
|
|
60
|
-
} });
|
|
61
|
-
const configPath = node_path_1.default.join(planningDir, 'config.json');
|
|
62
|
-
const raw = readFile(configPath);
|
|
63
|
-
if (!raw)
|
|
64
|
-
return null;
|
|
65
|
-
let cfg;
|
|
66
|
-
try {
|
|
67
|
-
cfg = JSON.parse(raw);
|
|
68
130
|
}
|
|
69
|
-
|
|
70
|
-
|
|
131
|
+
else {
|
|
132
|
+
try {
|
|
133
|
+
config = (deps?.loadConfig ?? loadConfigSeam)(cwd, { persist: false });
|
|
134
|
+
}
|
|
135
|
+
catch { /* configuration loading is fail-soft for branch resolution */ }
|
|
71
136
|
}
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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
|
+
}
|
|
81
159
|
}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
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));
|
|
163
|
+
}
|
|
164
|
+
return { baseBranch, protectedBranches, rejectedProtectedBranches };
|
|
86
165
|
}
|
|
87
166
|
/**
|
|
88
167
|
* Try `git symbolic-ref --short refs/remotes/origin/HEAD` (no network).
|
|
@@ -168,7 +247,7 @@ function tryLocalBranch(cwd, execGit) {
|
|
|
168
247
|
* Consults the full precedence ladder and always returns a non-empty string.
|
|
169
248
|
* Never throws.
|
|
170
249
|
*/
|
|
171
|
-
function
|
|
250
|
+
function resolveBaseBranchDiagnosticsWithConfig(cwd, configured, deps) {
|
|
172
251
|
const rawExecGit = deps?.execGit ?? shell_command_projection_cjs_1.execGit;
|
|
173
252
|
// A genuine execGit failure (timeout, or the call could not even spawn —
|
|
174
253
|
// e.g. git missing, surfaced as exitCode 127 with `error` set) is distinct
|
|
@@ -184,10 +263,7 @@ function resolveBaseBranchDiagnostics(cwd, deps) {
|
|
|
184
263
|
anyGitFailure = true;
|
|
185
264
|
return r;
|
|
186
265
|
};
|
|
187
|
-
// Derive .planning dir relative to cwd (mirrors planningDir() in planning-workspace.cjs)
|
|
188
|
-
const planningDir = node_path_1.default.join(cwd, '.planning');
|
|
189
266
|
// 1. Config override
|
|
190
|
-
const configured = readConfigBaseBranch(planningDir, deps);
|
|
191
267
|
if (configured)
|
|
192
268
|
return { branch: configured, verified: true };
|
|
193
269
|
// 2. symbolic-ref (fast, no network)
|
|
@@ -208,6 +284,10 @@ function resolveBaseBranchDiagnostics(cwd, deps) {
|
|
|
208
284
|
// not answer (#3057 B4).
|
|
209
285
|
return { branch: 'main', verified: !anyGitFailure };
|
|
210
286
|
}
|
|
287
|
+
function resolveBaseBranchDiagnostics(cwd, deps) {
|
|
288
|
+
const { baseBranch } = readEffectiveGitConfig(cwd, deps);
|
|
289
|
+
return resolveBaseBranchDiagnosticsWithConfig(cwd, baseBranch, deps);
|
|
290
|
+
}
|
|
211
291
|
/**
|
|
212
292
|
* Resolve the default/base branch for the repository at `cwd`.
|
|
213
293
|
*
|
|
@@ -218,6 +298,19 @@ function resolveBaseBranchDiagnostics(cwd, deps) {
|
|
|
218
298
|
function resolveBaseBranch(cwd, deps) {
|
|
219
299
|
return resolveBaseBranchDiagnostics(cwd, deps).branch;
|
|
220
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
|
+
}
|
|
221
314
|
/**
|
|
222
315
|
* Detect whether `cwd` sits inside a git worktree, and if so, return the
|
|
223
316
|
* absolute path of the worktree root.
|
|
@@ -244,13 +337,157 @@ function gitWorktreeInfoInternal(cwd, deps) {
|
|
|
244
337
|
return { inside: false, worktreeRoot: null };
|
|
245
338
|
}
|
|
246
339
|
}
|
|
340
|
+
// ─── Adapter 3: phase-start anchor + touched-file listing (issue #1953) ───────
|
|
341
|
+
/**
|
|
342
|
+
* Resolve the commit that ADDED `<phaseDir>/*-PLAN.md` — the anchor commit
|
|
343
|
+
* marking when the phase began (see `.gsd/phase/feat-1953-complexity-triggered-
|
|
344
|
+
* refactor/42-router-contract.md`, "Touched-file anchor"). `phaseDir` is a
|
|
345
|
+
* project-relative path (backslashes are normalized unconditionally before
|
|
346
|
+
* building the pathspec, never via `path.sep` — matches the repo's
|
|
347
|
+
* cross-platform path-normalization convention).
|
|
348
|
+
*
|
|
349
|
+
* Bounded (`timeout: 15_000`), degrades to `null` on any failure or when no
|
|
350
|
+
* such commit exists (a phase never planned through git, a shallow clone).
|
|
351
|
+
* Never throws.
|
|
352
|
+
*/
|
|
353
|
+
function phaseStartCommit(cwd, phaseDir, execGit) {
|
|
354
|
+
const git = execGit ?? shell_command_projection_cjs_1.execGit;
|
|
355
|
+
try {
|
|
356
|
+
const normalizedPhaseDir = phaseDir.replace(/\\/g, '/');
|
|
357
|
+
const pathspec = `${normalizedPhaseDir}/*-PLAN.md`;
|
|
358
|
+
const r = git(['log', '--format=%H', '--diff-filter=A', '-1', '--', pathspec], { cwd, timeout: 15_000 });
|
|
359
|
+
if (r.exitCode !== 0 || !r.stdout)
|
|
360
|
+
return null;
|
|
361
|
+
const sha = r.stdout.trim();
|
|
362
|
+
return sha || null;
|
|
363
|
+
}
|
|
364
|
+
catch {
|
|
365
|
+
return null;
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
/**
|
|
369
|
+
* Reject characters/sequences that have no legitimate use in a git revision
|
|
370
|
+
* expression reaching this module (a ref name, SHA, or `<ref>~N` / `<ref>^N`
|
|
371
|
+
* / `<ref>@{...}` navigation) but that a hostile `--since` value could use to
|
|
372
|
+
* confuse either the shell-out or a downstream reader: whitespace, ASCII
|
|
373
|
+
* control characters, and the option-shaped `?`, `*`, `[`, `\` characters
|
|
374
|
+
* that `git check-ref-format` also disallows in ref *names*. A leading `-`
|
|
375
|
+
* is rejected outright — that is the actual option-injection vector `--end-
|
|
376
|
+
* of-options` (below) already neutralizes, so this is belt-and-suspenders
|
|
377
|
+
* for older git. `..` is rejected because `sinceRef` is a single revision
|
|
378
|
+
* that this function itself turns into a range (`sinceRef..HEAD`); a
|
|
379
|
+
* `sinceRef` that already contains `..` can only produce a malformed or
|
|
380
|
+
* misleading range. A trailing `.lock` is rejected per `check-ref-format`.
|
|
381
|
+
*
|
|
382
|
+
* Deliberately NOT rejected: `~`, `^`, `:`, `@`, `{`, `}` — `check-ref-
|
|
383
|
+
* format` disallows these in a bare ref *name*, but this value is a git
|
|
384
|
+
* *revision expression*, and rejecting them would break entirely ordinary
|
|
385
|
+
* user input such as `HEAD~1`, `HEAD^`, or `main@{yesterday}`. None of
|
|
386
|
+
* these characters can reintroduce option parsing once `--end-of-options`
|
|
387
|
+
* is in effect, so allowing them costs nothing security-wise.
|
|
388
|
+
*/
|
|
389
|
+
function isSafeRevisionRef(ref) {
|
|
390
|
+
if (ref === '')
|
|
391
|
+
return false;
|
|
392
|
+
if (ref.startsWith('-'))
|
|
393
|
+
return false;
|
|
394
|
+
if (/[\x00-\x1f\x7f ?*[\\]/.test(ref))
|
|
395
|
+
return false;
|
|
396
|
+
if (ref.includes('..'))
|
|
397
|
+
return false;
|
|
398
|
+
if (ref.endsWith('.lock'))
|
|
399
|
+
return false;
|
|
400
|
+
return true;
|
|
401
|
+
}
|
|
402
|
+
/**
|
|
403
|
+
* List files changed between `sinceRef` and `HEAD`, NUL-delimited and
|
|
404
|
+
* quotepath-safe. Load-bearing details (see the router contract):
|
|
405
|
+
* - `-z` and `-c core.quotepath=false` avoid git's lossy quote-and-escape
|
|
406
|
+
* round-trip for non-ASCII paths;
|
|
407
|
+
* - splitting on `NUL` (never `\n`) tolerates a filename containing a real
|
|
408
|
+
* newline (git permits it);
|
|
409
|
+
* - `sinceRef` is validated by `isSafeRevisionRef` AND the revision-range
|
|
410
|
+
* argument is preceded by `--end-of-options`. A trailing `--` alone does
|
|
411
|
+
* NOT stop git from option-parsing an argument that appears BEFORE it —
|
|
412
|
+
* it only stops PATHSPEC interpretation of arguments AFTER it — so
|
|
413
|
+
* `--since '--output=/tmp/pwn'` would otherwise become the argument
|
|
414
|
+
* `--output=/tmp/pwn..HEAD`, which git accepts as an option and uses to
|
|
415
|
+
* redirect diff output to an attacker-chosen path. `--end-of-options`
|
|
416
|
+
* (git >= 2.24) is the correct fix: everything after it is parsed as a
|
|
417
|
+
* revision or path, never as an option, regardless of leading `-`.
|
|
418
|
+
*
|
|
419
|
+
* Bounded (`timeout: 15_000`), degrades to `null` when `sinceRef` fails
|
|
420
|
+
* validation or the underlying git call fails (non-zero exit, timeout, or
|
|
421
|
+
* spawn error) — never throws. An empty result set (no files changed
|
|
422
|
+
* between the two revisions) is a valid, non-null answer: `[]`.
|
|
423
|
+
*/
|
|
424
|
+
function changedFilesSince(cwd, sinceRef, execGit) {
|
|
425
|
+
if (!isSafeRevisionRef(sinceRef))
|
|
426
|
+
return null;
|
|
427
|
+
const git = execGit ?? shell_command_projection_cjs_1.execGit;
|
|
428
|
+
try {
|
|
429
|
+
const r = git(['-c', 'core.quotepath=false', 'diff', '--name-only', '-z', '--end-of-options', `${sinceRef}..HEAD`, '--'], { cwd, timeout: 15_000 });
|
|
430
|
+
if (r.exitCode !== 0)
|
|
431
|
+
return null;
|
|
432
|
+
if (!r.stdout)
|
|
433
|
+
return [];
|
|
434
|
+
return r.stdout.split('\0').filter((f) => f.length > 0);
|
|
435
|
+
}
|
|
436
|
+
catch {
|
|
437
|
+
return null;
|
|
438
|
+
}
|
|
439
|
+
}
|
|
247
440
|
// ─── CLI entry point ──────────────────────────────────────────────────────────
|
|
248
441
|
/**
|
|
249
442
|
* CLI command: `gsd-tools git base-branch`
|
|
250
443
|
* Resolves the default branch and writes it to stdout (raw string, newline-terminated).
|
|
251
444
|
* Called by workflows via `gsd_run query git.base-branch`.
|
|
252
445
|
*/
|
|
253
|
-
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
|
+
}
|
|
254
491
|
const { branch, verified } = resolveBaseBranchDiagnostics(cwd, deps);
|
|
255
492
|
if (!verified) {
|
|
256
493
|
const writeDiagnostic = deps?.writeDiagnostic ?? ((s) => process.stderr.write(s));
|
|
@@ -27,6 +27,7 @@ const node_path_1 = __importDefault(require("node:path"));
|
|
|
27
27
|
const shell_command_projection_cjs_1 = require("./shell-command-projection.cjs");
|
|
28
28
|
const runtime_slash_cjs_1 = require("./runtime-slash.cjs");
|
|
29
29
|
const clock_cjs_1 = require("./clock.cjs");
|
|
30
|
+
const phase_lifecycle_cjs_1 = require("./phase-lifecycle.cjs");
|
|
30
31
|
// eslint-disable-next-line @typescript-eslint/no-require-imports -- core-utils.cjs is an export= CommonJS module
|
|
31
32
|
const coreUtilsMod = require("./core-utils.cjs");
|
|
32
33
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
@@ -126,6 +127,12 @@ function parseTaskMustHaves(content) {
|
|
|
126
127
|
/**
|
|
127
128
|
* Read all task plan files from a GSD-2 tasks/ directory.
|
|
128
129
|
*/
|
|
130
|
+
// #3183 (ADR-3180 Decision 4(a) — bucket B, out of scope for the
|
|
131
|
+
// scanPhasePlans migration): this reads a FOREIGN GSD-2 legacy project's own
|
|
132
|
+
// `tasks/` directory convention (`T##-PLAN.md`) during a one-time import —
|
|
133
|
+
// it is not this project's `.planning/phases/<phase>/` layout at all, has no
|
|
134
|
+
// nested-plans/superseded-status concept, and scanPhasePlans's grammar
|
|
135
|
+
// (which is scoped to GSD's OWN phase directories) does not apply here.
|
|
129
136
|
function readTasksDir(tasksDir) {
|
|
130
137
|
if (!node_fs_1.default.existsSync(tasksDir))
|
|
131
138
|
return [];
|
|
@@ -294,7 +301,9 @@ function buildStateMd(phaseMap) {
|
|
|
294
301
|
const currentEntry = phaseMap.find(p => !p.slice.done);
|
|
295
302
|
const totalPhases = phaseMap.length;
|
|
296
303
|
const donePhases = phaseMap.filter(p => p.slice.done).length;
|
|
297
|
-
|
|
304
|
+
// ADR-3180 D7: one owner for completion percent. clampPercent's 100 ceiling is
|
|
305
|
+
// unreachable here (donePhases is a subset of totalPhases) — the value is unchanged.
|
|
306
|
+
const pct = (0, phase_lifecycle_cjs_1.clampPercent)(donePhases, totalPhases);
|
|
298
307
|
const currentPhaseNum = currentEntry ? zeroPad(currentEntry.phaseNum) : zeroPad(totalPhases);
|
|
299
308
|
const currentSlug = currentEntry ? slugify(currentEntry.slice.title) : 'complete';
|
|
300
309
|
const status = currentEntry ? 'Ready to plan' : 'All phases complete';
|