@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
|
@@ -19,6 +19,7 @@ exports.PATH_ACTION_REASON = void 0;
|
|
|
19
19
|
exports.toPosixPath = toPosixPath;
|
|
20
20
|
exports.toNativePath = toNativePath;
|
|
21
21
|
exports.posixNormalize = posixNormalize;
|
|
22
|
+
exports.toComparablePathKey = toComparablePathKey;
|
|
22
23
|
exports.hookCommandNeedsPowerShellCallOperator = hookCommandNeedsPowerShellCallOperator;
|
|
23
24
|
exports.formatHookCommandForRuntime = formatHookCommandForRuntime;
|
|
24
25
|
exports.shellHookOmitsBashRunner = shellHookOmitsBashRunner;
|
|
@@ -43,11 +44,15 @@ exports.projectPersistentPathExportActions = projectPersistentPathExportActions;
|
|
|
43
44
|
exports.isSpawnTimeout = isSpawnTimeout;
|
|
44
45
|
exports.execGit = execGit;
|
|
45
46
|
exports.execNpm = execNpm;
|
|
47
|
+
exports.envGet = envGet;
|
|
48
|
+
exports.resolveExecutableBinary = resolveExecutableBinary;
|
|
49
|
+
exports.projectSpawnInvocation = projectSpawnInvocation;
|
|
46
50
|
exports.execTool = execTool;
|
|
47
51
|
exports.resolveGsdToolsPath = resolveGsdToolsPath;
|
|
48
52
|
exports.dispatchGsdCommand = dispatchGsdCommand;
|
|
49
53
|
exports.probeTty = probeTty;
|
|
50
54
|
exports.normalizeContent = normalizeContent;
|
|
55
|
+
exports.contentChangedAfterNormalize = contentChangedAfterNormalize;
|
|
51
56
|
exports.retryRenameSync = retryRenameSync;
|
|
52
57
|
exports.platformWriteSync = platformWriteSync;
|
|
53
58
|
exports.platformReadSync = platformReadSync;
|
|
@@ -58,6 +63,7 @@ const node_fs_1 = __importDefault(require("node:fs"));
|
|
|
58
63
|
// can intercept calls from this seam — destructured imports capture references
|
|
59
64
|
// at load time and become un-mockable.
|
|
60
65
|
const node_child_process_1 = __importDefault(require("node:child_process"));
|
|
66
|
+
const pattern_cjs_1 = require("./pattern.cjs");
|
|
61
67
|
/**
|
|
62
68
|
* Convert a filesystem path to POSIX form (forward slashes) by translating the
|
|
63
69
|
* platform-native separator. Single seam for native→POSIX conversion.
|
|
@@ -92,6 +98,20 @@ function toNativePath(p) {
|
|
|
92
98
|
function posixNormalize(p) {
|
|
93
99
|
return p.replace(/\\/g, '/');
|
|
94
100
|
}
|
|
101
|
+
/**
|
|
102
|
+
* #3663 — comparison key for a path that must equal another path regardless
|
|
103
|
+
* of spelling: separator form (forward slashes via posixNormalize), relative
|
|
104
|
+
* segments and trailing separators (path.resolve + strip), and — ONLY on
|
|
105
|
+
* win32's case-insensitive filesystem — letter casing. POSIX stays
|
|
106
|
+
* case-sensitive: differently-cased paths are genuinely different
|
|
107
|
+
* directories there. This module owns the platform-conditional fold so the
|
|
108
|
+
* policy lives at the seam instead of accreting per-call-site copies (the
|
|
109
|
+
* class init.cts's normalizeForCompare/toComparableRaw predate).
|
|
110
|
+
*/
|
|
111
|
+
function toComparablePathKey(p, platform = process.platform) {
|
|
112
|
+
const normalized = posixNormalize(node_path_1.default.resolve(p)).replace(/\/+$/g, '');
|
|
113
|
+
return platform === 'win32' ? normalized.toLowerCase() : normalized;
|
|
114
|
+
}
|
|
95
115
|
/**
|
|
96
116
|
* Return true when a managed hook command must be prefixed with PowerShell's
|
|
97
117
|
* call operator so a quoted executable token is invokable by the target
|
|
@@ -215,6 +235,13 @@ const MANAGED_HOOK_BASENAMES_BY_SURFACE = {
|
|
|
215
235
|
'gsd-read-injection-scanner.js',
|
|
216
236
|
'gsd-update-banner.js',
|
|
217
237
|
'gsd-workflow-guard.js',
|
|
238
|
+
// #3662: the three PreToolUse guards are GSD-managed JS hooks (registered
|
|
239
|
+
// only-if-absent by applySettingsJsonHooks) but sat outside this set, so
|
|
240
|
+
// the #2979 runner rewriter never reached them — the issue's reported
|
|
241
|
+
// mixed state lived on exactly these hooks.
|
|
242
|
+
'gsd-write-guard.js',
|
|
243
|
+
'gsd-agent-isolation-guard.js',
|
|
244
|
+
'gsd-worktree-path-guard.js',
|
|
218
245
|
]),
|
|
219
246
|
'codex-toml': new Set([
|
|
220
247
|
'gsd-check-update.js',
|
|
@@ -234,6 +261,14 @@ const MANAGED_HOOK_COMMAND_BASENAMES_BY_SURFACE = {
|
|
|
234
261
|
'gsd-session-state.sh',
|
|
235
262
|
'gsd-validate-commit.sh',
|
|
236
263
|
'gsd-phase-boundary.sh',
|
|
264
|
+
// #3662: same three guards as MANAGED_HOOK_BASENAMES_BY_SURFACE above —
|
|
265
|
+
// their absence here meant isManagedHookCommand never recognized them, so
|
|
266
|
+
// the settings.json→settings.local.json migration filter (and uninstall
|
|
267
|
+
// cleanup) skipped GSD's own guard entries. Folded-in fix, surfaced by the
|
|
268
|
+
// #3662 issue thread.
|
|
269
|
+
'gsd-write-guard.js',
|
|
270
|
+
'gsd-agent-isolation-guard.js',
|
|
271
|
+
'gsd-worktree-path-guard.js',
|
|
237
272
|
]),
|
|
238
273
|
'codex-toml': new Set([
|
|
239
274
|
'gsd-check-update.js',
|
|
@@ -306,7 +341,7 @@ function isManagedHookCommand(commandText, opts = {}) {
|
|
|
306
341
|
return false;
|
|
307
342
|
}
|
|
308
343
|
for (const basename of managedBasenames) {
|
|
309
|
-
const escapedBasename =
|
|
344
|
+
const escapedBasename = (0, pattern_cjs_1.escapeRegex)(basename);
|
|
310
345
|
const pattern = new RegExp(`(^|[\\\\/\\s"'` + '`' + `])${escapedBasename}(?=$|[\\s"'` + '`' + `])`);
|
|
311
346
|
if (pattern.test(normalizedCommand))
|
|
312
347
|
return true;
|
|
@@ -327,9 +362,15 @@ const ANCHORED_HOOK_SCRIPT_TOKEN = /^"\$[A-Za-z_][A-Za-z0-9_]*"\//;
|
|
|
327
362
|
* Non-Windows keeps the original script token shape when provided (single
|
|
328
363
|
* quote / bareword / quoted), while Windows normalizes to double-quoted
|
|
329
364
|
* forward-slash path tokens for stable cross-shell behavior.
|
|
365
|
+
*
|
|
366
|
+
* #3662: `runnerToken` is the runner the current install would emit — since
|
|
367
|
+
* #3662 that is the runtime-resolving chain token (see
|
|
368
|
+
* buildNodeRunnerChainToken), not a bare absolute path, so re-projected
|
|
369
|
+
* entries converge onto a runner that resolves in every environment sharing
|
|
370
|
+
* the config root.
|
|
330
371
|
*/
|
|
331
|
-
function projectLegacySettingsHookCommand({
|
|
332
|
-
if (!
|
|
372
|
+
function projectLegacySettingsHookCommand({ runnerToken, scriptPath, scriptToken, runtime = 'generic', platform = process.platform, }) {
|
|
373
|
+
if (!runnerToken || !scriptPath)
|
|
333
374
|
return null;
|
|
334
375
|
const normalizedScriptPath = platform === 'win32' ? posixNormalize(scriptPath) : scriptPath;
|
|
335
376
|
// #1693: a script path already carrying a `"$CLAUDE_PROJECT_DIR"`-anchored
|
|
@@ -348,7 +389,7 @@ function projectLegacySettingsHookCommand({ absoluteRunner, scriptPath, scriptTo
|
|
|
348
389
|
: JSON.stringify(normalizedScriptPath))
|
|
349
390
|
: (scriptToken || JSON.stringify(normalizedScriptPath));
|
|
350
391
|
return projectShellCommandText({
|
|
351
|
-
runnerToken
|
|
392
|
+
runnerToken,
|
|
352
393
|
argTokens: [commandScriptToken],
|
|
353
394
|
runtime,
|
|
354
395
|
platform,
|
|
@@ -589,15 +630,287 @@ function execNpm(args, opts = {}) {
|
|
|
589
630
|
});
|
|
590
631
|
return _spawnResult(result, 'npm');
|
|
591
632
|
}
|
|
633
|
+
/**
|
|
634
|
+
* Default PATHEXT when Windows does not supply one. Matches the value
|
|
635
|
+
* `gsd-tools.cjs`'s `resolveSpawnBinary` shipped in #3275; kept identical so the
|
|
636
|
+
* delegation is a behavior-preserving move rather than a redefinition.
|
|
637
|
+
*/
|
|
638
|
+
const DEFAULT_PATHEXT = '.EXE;.CMD;.BAT;.COM';
|
|
639
|
+
/** Windows extensions that must be mediated through cmd.exe rather than spawned. */
|
|
640
|
+
const CMD_MEDIATED_EXT = /\.(cmd|bat)$/i;
|
|
641
|
+
function _isFile(candidate, requireExecutable = false, platform = process.platform) {
|
|
642
|
+
try {
|
|
643
|
+
if (!node_fs_1.default.statSync(candidate).isFile())
|
|
644
|
+
return false;
|
|
645
|
+
if (requireExecutable && platform !== 'win32')
|
|
646
|
+
node_fs_1.default.accessSync(candidate, node_fs_1.default.constants.X_OK);
|
|
647
|
+
return true;
|
|
648
|
+
}
|
|
649
|
+
catch {
|
|
650
|
+
// Missing, unreadable (EACCES), a broken link, or (when requireExecutable
|
|
651
|
+
// is set) not executable — all mean "not this one".
|
|
652
|
+
return false;
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
/**
|
|
656
|
+
* Read an environment variable by name, case-insensitively.
|
|
657
|
+
*
|
|
658
|
+
* Windows environment variable names are case-insensitive and conventionally
|
|
659
|
+
* cased `Path` / `ComSpec`, and `process.env` is a case-insensitive proxy that
|
|
660
|
+
* hides the difference. Spreading it (`{ ...process.env, ...opts.env }`, which
|
|
661
|
+
* `execTool` does whenever a caller supplies `opts.env`) produces a PLAIN object
|
|
662
|
+
* that keeps the OS's actual casing and loses the proxy — so an exact-case
|
|
663
|
+
* `env['PATH']` lookup returns undefined there and the PATH scan silently sees
|
|
664
|
+
* nothing. Caught by the Windows CI lane on #3617; the #3445 tests never hit it
|
|
665
|
+
* because they pass uppercase keys explicitly.
|
|
666
|
+
*
|
|
667
|
+
* Exact match wins when present, so a caller who sets the canonical name pays
|
|
668
|
+
* no scan.
|
|
669
|
+
*
|
|
670
|
+
* Exported so callers outside this seam can route an exact-case-sensitive env
|
|
671
|
+
* read through it instead of accessing a non-`process.env` object directly
|
|
672
|
+
* (see `local/no-exact-case-env-access`).
|
|
673
|
+
*/
|
|
674
|
+
function envGet(env, name) {
|
|
675
|
+
const exact = env[name];
|
|
676
|
+
if (exact !== undefined)
|
|
677
|
+
return exact;
|
|
678
|
+
const lower = name.toLowerCase();
|
|
679
|
+
for (const key of Object.keys(env)) {
|
|
680
|
+
if (key.toLowerCase() === lower)
|
|
681
|
+
return env[key];
|
|
682
|
+
}
|
|
683
|
+
return undefined;
|
|
684
|
+
}
|
|
685
|
+
/** cmd.exe's own quoting rule inside a `/c` string: a literal quote is doubled. */
|
|
686
|
+
function _cmdQuoteToken(token) {
|
|
687
|
+
return `"${token.replace(/"/g, '""')}"`;
|
|
688
|
+
}
|
|
689
|
+
/**
|
|
690
|
+
* Build a single verbatim command-line string for `cmd.exe /c` where every
|
|
691
|
+
* token is force-quoted, then wrap the whole thing in one more outer pair.
|
|
692
|
+
* cmd.exe strips exactly one outer quote pair when the string begins with a
|
|
693
|
+
* quote and contains at least two — so the outer wrap disappears and what's
|
|
694
|
+
* left is a sequence of individually-quoted tokens. Force-quoting is the
|
|
695
|
+
* point: an unquoted `a&b` is split by cmd's own metacharacter parsing, but a
|
|
696
|
+
* quoted `"a&b"` is one literal argument.
|
|
697
|
+
*/
|
|
698
|
+
function _buildVerbatimCmdLine(target, args) {
|
|
699
|
+
return `"${[target, ...args].map(_cmdQuoteToken).join(' ')}"`;
|
|
700
|
+
}
|
|
701
|
+
/**
|
|
702
|
+
* #3411: resolve a DECLARED command name to the file a spawn can actually start.
|
|
703
|
+
* The single canonical answer to "where is this binary?" for the whole tree.
|
|
704
|
+
*
|
|
705
|
+
* This is the seam `CONTEXT.md` declares as "All OS-facing I/O; single platform
|
|
706
|
+
* seam". Four divergent implementations of this logic existed (#3411): `execNpm`'s
|
|
707
|
+
* `shell:true`, `execTool`'s absence of any handling, `gsd-tools.cjs`'s private
|
|
708
|
+
* scan, and `fallow-runner.cts`'s own candidate array. #3275 folded two of them
|
|
709
|
+
* together in `bin/`; this lifts that resolver into the seam so `bin/` delegates
|
|
710
|
+
* instead of owning a copy.
|
|
711
|
+
*
|
|
712
|
+
* **win32** tries PATHEXT entries ONLY — never the bare name. npm global installs
|
|
713
|
+
* drop an EXTENSIONLESS POSIX sh shim (`...\npm\codex`) next to `codex.CMD`; a
|
|
714
|
+
* bare-name-first scan resolves to it, the cmd.exe mediation gate sees no `.cmd`,
|
|
715
|
+
* and the ENOENT returns unchanged (field-reported on Windows 11 — see #3275).
|
|
716
|
+
* A name that ALREADY carries a PATHEXT-listed extension is tried as-is first, so
|
|
717
|
+
* `foo.exe` resolves to `foo.exe` rather than being probed as `foo.exe.EXE`; a
|
|
718
|
+
* suffix that is not in PATHEXT (`foo.txt`) is not an extension and only feeds the
|
|
719
|
+
* append loop.
|
|
720
|
+
*
|
|
721
|
+
* **POSIX** answers EXISTENCE by scanning PATH for the bare name. `execTool` does
|
|
722
|
+
* NOT consult this on POSIX — the bare name goes to spawnSync unchanged and Node's
|
|
723
|
+
* own PATH search does the work, so macOS/Linux behavior is untouched (#3275
|
|
724
|
+
* acceptance contract).
|
|
725
|
+
*
|
|
726
|
+
* Path-like names (any `/` or `\`) bypass the PATH scan: the name is already an
|
|
727
|
+
* address, so it passes through when it names an existing file.
|
|
728
|
+
*
|
|
729
|
+
* **`opts.prependPaths`** — directories searched BEFORE `env.PATH`, in array
|
|
730
|
+
* order (e.g. a project-local `node_modules/.bin`). Defaults to `[]`, so
|
|
731
|
+
* Phase 1's callers (`execTool`, and `gsd-tools.cjs`'s `resolveSpawnBinary` /
|
|
732
|
+
* `deps.spawn` / `hasBinary`), which set neither new option, are byte-identical
|
|
733
|
+
* to today. The existing per-directory candidate logic (win32 as-is-then-append-
|
|
734
|
+
* PATHEXT; POSIX bare name) applies to prepended directories exactly as it does
|
|
735
|
+
* to `PATH` segments — there is no special-casing.
|
|
736
|
+
*
|
|
737
|
+
* **`opts.requireExecutable`** — when `true` and the platform is not `win32`,
|
|
738
|
+
* a candidate must additionally pass `fs.accessSync(candidate, fs.constants.X_OK)`
|
|
739
|
+
* to count as a match. On `win32` this is a no-op (mode bits do not mean
|
|
740
|
+
* execute on Windows — the same carve-out `fallow-runner`'s prior private
|
|
741
|
+
* resolver already had). Defaults to `false`, so `accessSync` is never called
|
|
742
|
+
* unless a caller opts in. It is opt-in rather than the default because making
|
|
743
|
+
* `X_OK` unconditional would break #3445's suite: those tests stage candidates
|
|
744
|
+
* with plain `fs.writeFileSync` and never set an exec bit (the repo bans
|
|
745
|
+
* `chmod` in tests), so every one of them would resolve to `null` on POSIX.
|
|
746
|
+
*
|
|
747
|
+
* **`opts.pathOverride`** — "search THIS PATH, but read everything else —
|
|
748
|
+
* PATHEXT included — from the ambient environment." When set (`!== undefined`),
|
|
749
|
+
* the PATH search segments come from splitting `pathOverride` on `path.delimiter`
|
|
750
|
+
* instead of from `env.PATH`; `pathOverride: ''` means an explicit EMPTY search
|
|
751
|
+
* path (zero segments), never a fallback to `env.PATH` — use `!== undefined`,
|
|
752
|
+
* not truthiness, to tell "caller supplied a PATH string" apart from "caller
|
|
753
|
+
* supplied nothing". `opts.prependPaths` still comes first. PATHEXT resolution
|
|
754
|
+
* is UNAFFECTED by this option — it still reads from `env` (which defaults to
|
|
755
|
+
* `process.env`) exactly as it does when `pathOverride` is omitted. This exists
|
|
756
|
+
* so a caller that already has its own search path in hand (e.g.
|
|
757
|
+
* `resolveFallowBinary`'s `envPath`) does not have to hand-thread PATHEXT
|
|
758
|
+
* alongside it — a private PATHEXT read is the very shape
|
|
759
|
+
* `local/no-private-binary-resolution` forbids outside this seam.
|
|
760
|
+
*
|
|
761
|
+
* @returns the resolved path, or `null` when nothing matched. Callers fall back to
|
|
762
|
+
* the declared name on `null` so a genuine ENOENT still surfaces (#3086).
|
|
763
|
+
*/
|
|
764
|
+
function resolveExecutableBinary(name, opts = {}) {
|
|
765
|
+
if (!name)
|
|
766
|
+
return null;
|
|
767
|
+
const requireExecutable = opts.requireExecutable ?? false;
|
|
768
|
+
const platform = opts.platform ?? process.platform;
|
|
769
|
+
if (name.includes('/') || name.includes('\\')) {
|
|
770
|
+
return _isFile(name, requireExecutable, platform) ? name : null;
|
|
771
|
+
}
|
|
772
|
+
const env = opts.env ?? process.env;
|
|
773
|
+
const rawPath = opts.pathOverride !== undefined ? opts.pathOverride : (envGet(env, 'PATH') || '');
|
|
774
|
+
const pathSegments = String(rawPath).split(node_path_1.default.delimiter).filter(Boolean);
|
|
775
|
+
const segments = [...(opts.prependPaths ?? []), ...pathSegments];
|
|
776
|
+
if (platform !== 'win32') {
|
|
777
|
+
for (const dir of segments) {
|
|
778
|
+
const candidate = node_path_1.default.join(dir, name);
|
|
779
|
+
if (_isFile(candidate, requireExecutable, platform))
|
|
780
|
+
return candidate;
|
|
781
|
+
}
|
|
782
|
+
return null;
|
|
783
|
+
}
|
|
784
|
+
const exts = String(envGet(env, 'PATHEXT') || DEFAULT_PATHEXT).split(';').filter(Boolean);
|
|
785
|
+
// A name already ending in a PATHEXT-listed extension is an address, not a stem:
|
|
786
|
+
// probing `foo.exe` as `foo.exe.EXE` would miss the file sitting right there.
|
|
787
|
+
// Compared case-insensitively because PATHEXT casing is not guaranteed.
|
|
788
|
+
const lower = name.toLowerCase();
|
|
789
|
+
const carriesKnownExt = exts.some((ext) => lower.endsWith(ext.toLowerCase()));
|
|
790
|
+
for (const dir of segments) {
|
|
791
|
+
if (carriesKnownExt) {
|
|
792
|
+
const asIs = node_path_1.default.join(dir, name);
|
|
793
|
+
if (_isFile(asIs, requireExecutable, platform))
|
|
794
|
+
return asIs;
|
|
795
|
+
}
|
|
796
|
+
for (const ext of exts) {
|
|
797
|
+
const candidate = node_path_1.default.join(dir, name + ext);
|
|
798
|
+
if (_isFile(candidate, requireExecutable, platform))
|
|
799
|
+
return candidate;
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
return null;
|
|
803
|
+
}
|
|
804
|
+
/**
|
|
805
|
+
* #3411: project a declared `(command, args)` into the pair `spawnSync` can
|
|
806
|
+
* actually execute on this platform.
|
|
807
|
+
*
|
|
808
|
+
* Resolution alone does not make Windows work: `CreateProcess` cannot execute a
|
|
809
|
+
* `.cmd`/`.bat` at all, so the cmd.exe mediation is inseparable from the lookup.
|
|
810
|
+
* Exporting only the resolver would leave every caller to re-derive that half —
|
|
811
|
+
* which is precisely how #3411's four copies accumulated.
|
|
812
|
+
*
|
|
813
|
+
* cmd.exe is invoked as an ordinary program with an EXPLICIT argv array, never via
|
|
814
|
+
* `shell: true`. `shell:true` on Windows is the mechanism behind CVE-2024-27980
|
|
815
|
+
* (argument injection through `.bat`/`.cmd`), and Node 26 deprecates it with an
|
|
816
|
+
* args array (DEP0190) because arguments are concatenated rather than escaped.
|
|
817
|
+
*
|
|
818
|
+
* Mediation fires when the target — the resolved path, or the declared name when
|
|
819
|
+
* resolution found nothing — carries a `.cmd`/`.bat` extension. A BARE name that
|
|
820
|
+
* resolved to nothing is passed through verbatim so the spawn fails with ENOENT;
|
|
821
|
+
* mediating it would turn `{exitCode:127, 'foo: not found'}` into cmd.exe's exit
|
|
822
|
+
* 9009 and silently change the not-found contract callers depend on.
|
|
823
|
+
*
|
|
824
|
+
* POSIX is a strict no-op: the declared command is returned unchanged and the
|
|
825
|
+
* environment is never consulted.
|
|
826
|
+
*
|
|
827
|
+
* The mediated command line is built VERBATIM rather than left to libuv: libuv's
|
|
828
|
+
* `quote_cmd_arg` only force-quotes an argument that contains a space, tab, or
|
|
829
|
+
* quote — it does not know about cmd.exe metacharacters (`&`, `|`, `>`, `<`,
|
|
830
|
+
* `^`, ...) at all, so an argument like `a&calc` reaches cmd.exe unquoted and
|
|
831
|
+
* gets re-parsed as two commands (the CVE-2024-27980 argument-injection class).
|
|
832
|
+
* Node's own CVE-2024-27980 escaping does not help here because it only fires
|
|
833
|
+
* when the spawned FILE itself is a `.bat`/`.cmd` — in this seam the spawned
|
|
834
|
+
* file is `cmd.exe`, not the target. Building the line ourselves and passing
|
|
835
|
+
* `windowsVerbatimArguments: true` (the shape Rust's std uses for the sibling
|
|
836
|
+
* CVE-2024-24576) means every token is force-quoted inside one outer pair, so
|
|
837
|
+
* a metacharacter inside a quoted token can never split the command line.
|
|
838
|
+
*
|
|
839
|
+
* KNOWN LIMIT: `%VAR%` still expands inside a cmd `/c` string, and there is no
|
|
840
|
+
* escape for `%` outside a batch file — an argument containing `%FOO%` is
|
|
841
|
+
* substituted with the environment value regardless of quoting. That is an
|
|
842
|
+
* information-disclosure limit, not arbitrary execution, and it's the same
|
|
843
|
+
* limit Rust's std documents for its own `CommandExt::raw_arg` escape hatch.
|
|
844
|
+
*
|
|
845
|
+
* CALLER CHOICE: this function's return value carries two independently
|
|
846
|
+
* adoptable pieces of information, and a caller may take either, both, or
|
|
847
|
+
* neither. `windowsVerbatimArguments: true` marks the cases where mediation
|
|
848
|
+
* was REQUIRED — the caller MUST adopt `command`+`args` together, since a
|
|
849
|
+
* `.cmd`/`.bat` genuinely cannot be spawned any other way. A merely-resolved
|
|
850
|
+
* `.exe` path (no mediation flag set) is only an OFFER: a caller may decline
|
|
851
|
+
* it and keep spawning the declared name instead, to hold its own observable
|
|
852
|
+
* contract stable. `execTool` (this file, below) is exactly such a caller —
|
|
853
|
+
* it adopts the mediated pair when `windowsVerbatimArguments` is set, but
|
|
854
|
+
* otherwise passes the declared `program`/`args` through untouched.
|
|
855
|
+
*/
|
|
856
|
+
function projectSpawnInvocation(command, args = [], opts = {}) {
|
|
857
|
+
const platform = opts.platform ?? process.platform;
|
|
858
|
+
if (platform !== 'win32')
|
|
859
|
+
return { command, args, resolved: null };
|
|
860
|
+
const env = opts.env ?? process.env;
|
|
861
|
+
const resolved = resolveExecutableBinary(command, { platform, env });
|
|
862
|
+
// Mediate against the resolved path when we have one, else against the declared
|
|
863
|
+
// name. An unresolved name is mediated ONLY when it already declares .cmd/.bat:
|
|
864
|
+
// PATH-only resolution misses a batch file sitting in the current directory,
|
|
865
|
+
// which `cmd.exe /c` still finds — the behavior gsd-tools.cjs shipped before
|
|
866
|
+
// this consolidation, preserved here rather than silently narrowed.
|
|
867
|
+
const target = resolved ?? command;
|
|
868
|
+
if (!CMD_MEDIATED_EXT.test(node_path_1.default.basename(target))) {
|
|
869
|
+
// A BARE name that resolved to nothing is passed through untouched so the
|
|
870
|
+
// spawn fails with ENOENT. Mediating it would turn {exitCode:127,
|
|
871
|
+
// '<name>: not found'} into cmd.exe's exit 9009 and silently change the
|
|
872
|
+
// not-found contract `_spawnResult` and its 53 dependent files rely on.
|
|
873
|
+
return resolved ? { command: resolved, args, resolved } : { command, args, resolved: null };
|
|
874
|
+
}
|
|
875
|
+
// A CR/LF cannot be represented inside a Windows command line at all — cmd.exe
|
|
876
|
+
// treats it as a line terminator, so mediating it would silently truncate the
|
|
877
|
+
// argument rather than pass it through. Fail visibly instead: fall back to the
|
|
878
|
+
// unmediated shape so the spawn either fails with ENOENT (bare unresolved name)
|
|
879
|
+
// or hands the raw string to CreateProcess, whichever the caller was already
|
|
880
|
+
// prepared to see for a non-.cmd/.bat target.
|
|
881
|
+
if (/[\r\n]/.test(target) || args.some((a) => /[\r\n]/.test(a))) {
|
|
882
|
+
return resolved ? { command: resolved, args, resolved } : { command, args, resolved: null };
|
|
883
|
+
}
|
|
884
|
+
return {
|
|
885
|
+
command: String(envGet(env, 'ComSpec') || 'cmd.exe'),
|
|
886
|
+
args: ['/d', '/s', '/c', _buildVerbatimCmdLine(target, args)],
|
|
887
|
+
resolved,
|
|
888
|
+
windowsVerbatimArguments: true,
|
|
889
|
+
};
|
|
890
|
+
}
|
|
592
891
|
function execTool(program, args, opts = {}) {
|
|
593
|
-
|
|
892
|
+
// #3411: Windows cannot spawn a .cmd/.bat at all — CreateProcess refuses it —
|
|
893
|
+
// so those are mediated through cmd.exe. Everything else keeps the DECLARED
|
|
894
|
+
// program name: libuv's CreateProcess path already performs PATH + PATHEXT
|
|
895
|
+
// search, so resolving a .exe here would buy nothing and would change what
|
|
896
|
+
// this seam's 167 dependents observe being spawned. `tests/graphify.test.cjs`
|
|
897
|
+
// pins that contract by spying on spawnSync's first argument. POSIX never
|
|
898
|
+
// reaches the mediation branch at all.
|
|
899
|
+
const spawnEnv = opts.env ? { ...process.env, ...opts.env } : undefined;
|
|
900
|
+
const invocation = projectSpawnInvocation(program, args, { env: spawnEnv ?? process.env });
|
|
901
|
+
const mediated = invocation.windowsVerbatimArguments === true;
|
|
902
|
+
const result = node_child_process_1.default.spawnSync(mediated ? invocation.command : program, mediated ? invocation.args : args, {
|
|
594
903
|
cwd: opts.cwd,
|
|
595
|
-
env:
|
|
904
|
+
env: spawnEnv,
|
|
596
905
|
encoding: 'utf-8',
|
|
597
906
|
stdio: 'pipe',
|
|
598
907
|
timeout: opts.timeout ?? 30_000,
|
|
599
908
|
windowsHide: true,
|
|
909
|
+
...(mediated ? { windowsVerbatimArguments: true } : {}),
|
|
600
910
|
});
|
|
911
|
+
// Stamp the DECLARED name, never the resolved path: `_spawnResult` renders
|
|
912
|
+
// `${program}: not found`, and callers across 53 files match on the string they
|
|
913
|
+
// passed. Resolution must not leak an absolute path into that message.
|
|
601
914
|
return _spawnResult(result, program);
|
|
602
915
|
}
|
|
603
916
|
/**
|
|
@@ -704,6 +1017,7 @@ function probeTty(opts = {}) {
|
|
|
704
1017
|
const ttyPath = node_child_process_1.default.execFileSync('tty', [], {
|
|
705
1018
|
encoding: 'utf-8',
|
|
706
1019
|
stdio: ['inherit', 'pipe', 'ignore'],
|
|
1020
|
+
timeout: 5_000,
|
|
707
1021
|
}).trim();
|
|
708
1022
|
if (!ttyPath || ttyPath === 'not a tty')
|
|
709
1023
|
return null;
|
|
@@ -750,7 +1064,11 @@ function _normalizeMd(content) {
|
|
|
750
1064
|
if (i === 0 || !insideFence[i - 1])
|
|
751
1065
|
result.push('');
|
|
752
1066
|
}
|
|
753
|
-
|
|
1067
|
+
// #3854: the `!/^\s/.test(prev)` guard mirrors the after-a-bullet rule below —
|
|
1068
|
+
// an indented non-bullet line is a CONTINUATION of the previous list item, not a
|
|
1069
|
+
// preceding paragraph, so no separating blank may be injected before this bullet
|
|
1070
|
+
// (that injection converted every tight multi-line list to a loose one on write).
|
|
1071
|
+
if (/^(\s*[-*+]\s|\s*\d+\.\s)/.test(line) && i > 0 && prevTrimmed !== '' && !/^(\s*[-*+]\s|\s*\d+\.\s)/.test(prev) && !/^\s/.test(prev) && prevTrimmed !== '---')
|
|
754
1072
|
result.push('');
|
|
755
1073
|
result.push(line);
|
|
756
1074
|
if (/^#{1,6}\s/.test(trimmed) && i < lines.length - 1 && (lines[i + 1] ?? '').trimEnd() !== '')
|
|
@@ -780,6 +1098,23 @@ function normalizeContent(filePath, content, opts = {}) {
|
|
|
780
1098
|
}
|
|
781
1099
|
return { content: normalized, encoding };
|
|
782
1100
|
}
|
|
1101
|
+
/**
|
|
1102
|
+
* True iff persisting `after` via `platformWriteSync` would land different
|
|
1103
|
+
* on-disk bytes than `before` already has (or would have, normalized the
|
|
1104
|
+
* same way). `platformWriteSync` runs Markdown normalization (CRLF strip,
|
|
1105
|
+
* blank-line-run collapse, single trailing newline) before writing, so a
|
|
1106
|
+
* caller comparing raw pre-normalize strings (`after !== before`) can report
|
|
1107
|
+
* `true` even when the persisted bytes are byte-identical — e.g. a
|
|
1108
|
+
* transform that regenerates a section fresh on every call, including a
|
|
1109
|
+
* genuine no-op re-run, in a different-but-equivalent raw shape than the
|
|
1110
|
+
* already-normalized on-disk original (#3685 / #3691). Any "did this write
|
|
1111
|
+
* change the file?" flag MUST go through this seam (or an equivalent
|
|
1112
|
+
* post-write re-read of the actual on-disk bytes) instead of a raw `!==` —
|
|
1113
|
+
* do not simplify this back to a direct string comparison.
|
|
1114
|
+
*/
|
|
1115
|
+
function contentChangedAfterNormalize(filePath, before, after) {
|
|
1116
|
+
return normalizeContent(filePath, after).content !== normalizeContent(filePath, before).content;
|
|
1117
|
+
}
|
|
783
1118
|
// Rename errnos that are transient on Windows: a concurrent reader (or an AV
|
|
784
1119
|
// scanner / indexer) holding the target open makes renameSync fail briefly.
|
|
785
1120
|
// Same idiom as capability-ledger.cts / capability-consent.cts.
|