@opengsd/gsd-core 1.11.0 → 1.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/agents/gsd-code-fixer.md +1 -1
- package/agents/gsd-debug-session-manager.md +1 -1
- package/agents/gsd-debugger.md +1 -1
- package/agents/gsd-dom-verifier.md +169 -0
- package/agents/gsd-eval-auditor.md +1 -1
- package/agents/gsd-executor.md +17 -9
- package/agents/gsd-framework-selector.md +1 -3
- package/agents/gsd-intel-updater.md +1 -1
- package/agents/gsd-mempalace-curator.md +0 -1
- package/agents/gsd-pattern-mapper.md +11 -0
- package/agents/gsd-phase-researcher.md +3 -1
- package/agents/gsd-plan-checker.md +15 -55
- package/agents/gsd-planner.md +6 -4
- package/agents/gsd-project-researcher.md +1 -1
- package/agents/gsd-research-synthesizer.md +2 -2
- package/agents/gsd-roadmapper.md +15 -11
- package/agents/gsd-ui-checker.md +63 -4
- package/agents/gsd-ui-researcher.md +41 -3
- package/agents/gsd-verifier.md +1 -1
- package/bin/install.js +609 -134
- package/commands/gsd/discuss-phase.md +1 -1
- package/commands/gsd/import.md +1 -1
- package/commands/gsd/quick.md +8 -4
- package/gsd-core/bin/gsd-tools.cjs +567 -51
- package/gsd-core/bin/lib/active-workstream-store.cjs +8 -0
- package/gsd-core/bin/lib/adr-parser.cjs +13 -7
- package/gsd-core/bin/lib/agent-install-check.cjs +162 -0
- package/gsd-core/bin/lib/api-coverage.cjs +30 -9
- package/gsd-core/bin/lib/artifacts.cjs +2 -0
- package/gsd-core/bin/lib/assumption-delta.cjs +30 -11
- package/gsd-core/bin/lib/audit.cjs +163 -41
- package/gsd-core/bin/lib/broken-windows.cjs +306 -28
- package/gsd-core/bin/lib/capability-lock.cjs +10 -4
- package/gsd-core/bin/lib/capability-registry.cjs +336 -95
- package/gsd-core/bin/lib/capability-state.cjs +18 -3
- package/gsd-core/bin/lib/capability-validator.cjs +205 -18
- package/gsd-core/bin/lib/check-command-router.cjs +145 -5
- package/gsd-core/bin/lib/cli-exit.cjs +496 -10
- package/gsd-core/bin/lib/code-review-depth.cjs +288 -0
- package/gsd-core/bin/lib/codex-agent-toml.cjs +410 -4
- package/gsd-core/bin/lib/command-arg-projection.cjs +144 -14
- package/gsd-core/bin/lib/command-routing-hub.cjs +31 -2
- package/gsd-core/bin/lib/commands.cjs +543 -44
- package/gsd-core/bin/lib/complexity-trigger.cjs +26 -6
- package/gsd-core/bin/lib/config-loader.cjs +118 -29
- package/gsd-core/bin/lib/config.cjs +92 -2
- package/gsd-core/bin/lib/configuration.cjs +129 -37
- package/gsd-core/bin/lib/core-utils.cjs +84 -7
- package/gsd-core/bin/lib/edge-probe.cjs +9 -1
- package/gsd-core/bin/lib/estimate-cli.cjs +55 -11
- package/gsd-core/bin/lib/exit-code-registry.cjs +98 -0
- package/gsd-core/bin/lib/frontmatter.cjs +840 -305
- package/gsd-core/bin/lib/gap-checker.cjs +27 -3
- package/gsd-core/bin/lib/git-base-branch.cjs +174 -39
- package/gsd-core/bin/lib/health-diagnostic-rules/consistency.cjs +7 -3
- package/gsd-core/bin/lib/health-diagnostic-rules/roadmap-disk-consistency.cjs +6 -3
- package/gsd-core/bin/lib/health-diagnostic-rules/worktree-health.cjs +22 -8
- package/gsd-core/bin/lib/health-diagnostic.cjs +23 -3
- package/gsd-core/bin/lib/host-integration.cjs +39 -6
- package/gsd-core/bin/lib/init-command-router.cjs +118 -21
- package/gsd-core/bin/lib/init.cjs +120 -41
- package/gsd-core/bin/lib/install-engine.cjs +68 -3
- package/gsd-core/bin/lib/install-model-override-resolver.cjs +33 -1
- package/gsd-core/bin/lib/install-profiles.cjs +78 -4
- package/gsd-core/bin/lib/installer-migration-report.cjs +3 -0
- package/gsd-core/bin/lib/installer-migrations/010-antigravity-retire-confighome-artifacts.cjs +169 -0
- package/gsd-core/bin/lib/installer-migrations.cjs +10 -7
- package/gsd-core/bin/lib/intel.cjs +101 -26
- package/gsd-core/bin/lib/io.cjs +160 -15
- package/gsd-core/bin/lib/learnings.cjs +85 -14
- package/gsd-core/bin/lib/legacy-cleanup.cjs +8 -2
- package/gsd-core/bin/lib/markdown-table.cjs +52 -4
- package/gsd-core/bin/lib/milestone.cjs +90 -5
- package/gsd-core/bin/lib/model-catalog.cjs +177 -19
- package/gsd-core/bin/lib/model-resolver.cjs +10 -28
- package/gsd-core/bin/lib/onboard-projection.cjs +5 -1
- package/gsd-core/bin/lib/phase-estimation.cjs +17 -8
- package/gsd-core/bin/lib/phase-id.cjs +70 -4
- package/gsd-core/bin/lib/phase-lifecycle.cjs +24 -16
- package/gsd-core/bin/lib/phase-locator.cjs +138 -17
- package/gsd-core/bin/lib/phase.cjs +405 -84
- package/gsd-core/bin/lib/plan-document.cjs +263 -0
- package/gsd-core/bin/lib/plan-scan.cjs +13 -2
- package/gsd-core/bin/lib/planning-command-router.cjs +61 -0
- package/gsd-core/bin/lib/planning-inspect.cjs +1168 -0
- package/gsd-core/bin/lib/planning-snapshot.cjs +18 -14
- package/gsd-core/bin/lib/planning-workspace.cjs +56 -0
- package/gsd-core/bin/lib/probe-core.cjs +4 -1
- package/gsd-core/bin/lib/profile-pipeline-command-router.cjs +50 -7
- package/gsd-core/bin/lib/profile-pipeline.cjs +6 -3
- package/gsd-core/bin/lib/real-home-guard.cjs +419 -0
- package/gsd-core/bin/lib/refactor-trigger-command-router.cjs +71 -45
- package/gsd-core/bin/lib/review-lane-descriptor.cjs +9 -9
- package/gsd-core/bin/lib/roadmap-command-router.cjs +45 -31
- package/gsd-core/bin/lib/roadmap-parser.cjs +79 -16
- package/gsd-core/bin/lib/roadmap.cjs +74 -19
- package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +96 -8
- package/gsd-core/bin/lib/runtime-artifact-layout.cjs +34 -1
- package/gsd-core/bin/lib/runtime-hooks-surface.cjs +287 -55
- package/gsd-core/bin/lib/runtime-identity.cjs +234 -0
- package/gsd-core/bin/lib/runtime-slash.cjs +72 -2
- package/gsd-core/bin/lib/shell-command-projection.cjs +71 -8
- package/gsd-core/bin/lib/smart-entry.cjs +12 -22
- package/gsd-core/bin/lib/spec-section.cjs +12 -7
- package/gsd-core/bin/lib/state-command-router.cjs +47 -18
- package/gsd-core/bin/lib/state-contract.cjs +359 -0
- package/gsd-core/bin/lib/state-document.cjs +186 -0
- package/gsd-core/bin/lib/state-md-schema.cjs +221 -0
- package/gsd-core/bin/lib/state-transition.cjs +517 -101
- package/gsd-core/bin/lib/state.cjs +946 -163
- package/gsd-core/bin/lib/surface.cjs +10 -2
- package/gsd-core/bin/lib/task-command-router.cjs +111 -1
- package/gsd-core/bin/lib/task-content-resolution.cjs +368 -0
- package/gsd-core/bin/lib/teams-status.cjs +4 -1
- package/gsd-core/bin/lib/uat-predicate.cjs +58 -20
- package/gsd-core/bin/lib/uat.cjs +1376 -125
- package/gsd-core/bin/lib/ui-consideration-probe.cjs +9 -1
- package/gsd-core/bin/lib/ui-safety-gate.cjs +37 -7
- package/gsd-core/bin/lib/unusable-input.cjs +13 -0
- package/gsd-core/bin/lib/validate-command-router.cjs +2 -2
- package/gsd-core/bin/lib/vendor/README.md +43 -5
- package/gsd-core/bin/lib/vendor/js-yaml.cjs +3014 -0
- package/gsd-core/bin/lib/verification.cjs +14 -1
- package/gsd-core/bin/lib/verify-command-grounding.cjs +846 -0
- package/gsd-core/bin/lib/verify.cjs +95 -40
- package/gsd-core/bin/lib/workstream-name-policy.cjs +25 -4
- package/gsd-core/bin/lib/worktree-base-ref.cjs +66 -12
- package/gsd-core/bin/lib/worktree-safety.cjs +177 -21
- package/gsd-core/bin/shared/config-defaults.manifest.json +7 -1
- package/gsd-core/bin/shared/config-schema.manifest.json +5 -0
- package/gsd-core/bin/shared/exit-codes.json +8 -0
- package/gsd-core/bin/shared/exit-codes.sh +20 -0
- package/gsd-core/bin/shared/model-catalog.json +8 -1
- package/gsd-core/references/agent-contracts.md +3 -2
- package/gsd-core/references/api-coverage.md +24 -2
- package/gsd-core/references/autonomous-smart-discuss.md +3 -3
- package/gsd-core/references/checkpoints.md +37 -19
- package/gsd-core/references/decimal-phase-calculation.md +5 -5
- package/gsd-core/references/edge-probe.md +8 -0
- package/gsd-core/references/execute-mvp-tdd.md +1 -3
- package/gsd-core/references/execute-phase-between-wave-reset.md +9 -12
- package/gsd-core/references/execute-phase-wave-guard.md +11 -9
- package/gsd-core/references/failing-direction.md +78 -0
- package/gsd-core/references/gate-prompts.md +1 -1
- package/gsd-core/references/git-integration.md +5 -5
- package/gsd-core/references/git-planning-commit.md +3 -3
- package/gsd-core/references/gsd-run-resolver.md +1 -1
- package/gsd-core/references/loop-hook-dispatch.md +22 -0
- package/gsd-core/references/model-profiles.md +1 -1
- package/gsd-core/references/nyquist-compliance.md +74 -0
- package/gsd-core/references/offer-next.md +3 -5
- package/gsd-core/references/phase-argument-parsing.md +3 -3
- package/gsd-core/references/planner-failing-direction.md +53 -0
- package/gsd-core/references/planner-human-verify-mode.md +15 -1
- package/gsd-core/references/planner-revision.md +1 -1
- package/gsd-core/references/planner-verify-command-grounding.md +17 -0
- package/gsd-core/references/planning-config.md +37 -8
- package/gsd-core/references/reviewer-instances.md +31 -0
- package/gsd-core/references/runtime-aware-dispatch.md +1 -1
- package/gsd-core/references/tdd.md +1 -3
- package/gsd-core/references/ui-brand.md +65 -21
- package/gsd-core/references/ui-consideration-probe.md +1 -1
- package/gsd-core/references/universal-anti-patterns.md +2 -2
- package/gsd-core/references/verify-command-path-resolvability.md +42 -0
- package/gsd-core/references/verify-mvp-mode.md +1 -1
- package/gsd-core/references/workstream-flag.md +11 -11
- package/gsd-core/templates/README.md +1 -1
- package/gsd-core/templates/SECURITY.md +3 -3
- package/gsd-core/templates/UI-SPEC.md +25 -3
- package/gsd-core/templates/VALIDATION.md +3 -3
- package/gsd-core/templates/phase-prompt.md +3 -0
- package/gsd-core/templates/state.md +7 -0
- package/gsd-core/workflows/_runtime-launcher.snippet.sh +1 -1
- package/gsd-core/workflows/add-backlog.md +1 -1
- package/gsd-core/workflows/add-phase.md +3 -3
- package/gsd-core/workflows/add-tests.md +3 -8
- package/gsd-core/workflows/add-todo.md +1 -1
- package/gsd-core/workflows/ai-integration-phase.md +4 -9
- package/gsd-core/workflows/audit-fix.md +12 -3
- package/gsd-core/workflows/audit-milestone.md +9 -9
- package/gsd-core/workflows/audit-uat.md +17 -2
- package/gsd-core/workflows/autonomous/steps/converge-fail-fast.md +2 -2
- package/gsd-core/workflows/autonomous.md +10 -26
- package/gsd-core/workflows/check-todos.md +1 -1
- package/gsd-core/workflows/cleanup.md +2 -2
- package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +1 -1
- package/gsd-core/workflows/code-review-fix.md +1 -1
- package/gsd-core/workflows/code-review.md +121 -40
- package/gsd-core/workflows/complete-milestone.md +15 -10
- package/gsd-core/workflows/debug.md +5 -3
- package/gsd-core/workflows/diagnose-issues.md +12 -6
- package/gsd-core/workflows/discuss-phase/modes/advisor.md +1 -1
- package/gsd-core/workflows/discuss-phase/modes/chain.md +3 -7
- package/gsd-core/workflows/discuss-phase/modes/text.md +1 -1
- package/gsd-core/workflows/discuss-phase-assumptions/steps/auto-advance-dispatch.md +1 -3
- package/gsd-core/workflows/discuss-phase-assumptions.md +2 -2
- package/gsd-core/workflows/discuss-phase.md +1 -1
- package/gsd-core/workflows/do.md +3 -6
- package/gsd-core/workflows/docs-update.md +5 -4
- package/gsd-core/workflows/edit-phase.md +1 -1
- package/gsd-core/workflows/eval-review.md +4 -9
- package/gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +113 -11
- package/gsd-core/workflows/execute-phase/steps/gap-closure-artifacts.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/partial-wave.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/per-plan-executor-routing.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +22 -4
- package/gsd-core/workflows/execute-phase/steps/post-merge-gate.md +2 -2
- package/gsd-core/workflows/execute-phase/steps/protected-branch.md +21 -0
- package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +2 -2
- package/gsd-core/workflows/execute-phase/steps/wave-post-gate-hooks.md +39 -0
- package/gsd-core/workflows/execute-phase.md +38 -54
- package/gsd-core/workflows/execute-plan.md +17 -12
- package/gsd-core/workflows/explore.md +1 -1
- package/gsd-core/workflows/extract-learnings.md +1 -1
- package/gsd-core/workflows/fast.md +2 -2
- package/gsd-core/workflows/forensics.md +1 -1
- package/gsd-core/workflows/graduation.md +5 -5
- package/gsd-core/workflows/health.md +3 -6
- package/gsd-core/workflows/import.md +14 -11
- package/gsd-core/workflows/inbox.md +4 -5
- package/gsd-core/workflows/ingest-docs.md +44 -11
- package/gsd-core/workflows/insert-phase.md +5 -5
- package/gsd-core/workflows/list-seeds.md +5 -3
- package/gsd-core/workflows/list-workspaces.md +1 -1
- package/gsd-core/workflows/manager.md +12 -23
- package/gsd-core/workflows/map-codebase.md +1 -1
- package/gsd-core/workflows/milestone-summary.md +1 -1
- package/gsd-core/workflows/mvp-phase.md +2 -2
- package/gsd-core/workflows/new-milestone.md +9 -21
- package/gsd-core/workflows/new-project/steps/auto-mode-config.md +1 -1
- package/gsd-core/workflows/new-project.md +12 -26
- package/gsd-core/workflows/new-workspace.md +1 -1
- package/gsd-core/workflows/next.md +2 -2
- package/gsd-core/workflows/pause-work.md +1 -1
- package/gsd-core/workflows/plan-phase/steps/adr-ingest-express-path.md +1 -1
- package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +1 -1
- package/gsd-core/workflows/plan-phase/steps/prd-express-path.md +2 -4
- package/gsd-core/workflows/plan-phase/steps/stall-detection-helpers.md +3 -3
- package/gsd-core/workflows/plan-phase.md +121 -42
- package/gsd-core/workflows/plan-review-convergence.md +46 -9
- package/gsd-core/workflows/plant-seed.md +2 -2
- package/gsd-core/workflows/pr-branch.md +187 -51
- package/gsd-core/workflows/profile-user.md +16 -14
- package/gsd-core/workflows/progress.md +27 -12
- package/gsd-core/workflows/quick/steps/discussion-phase.md +1 -3
- package/gsd-core/workflows/quick/steps/plan-checker-loop.md +1 -3
- package/gsd-core/workflows/quick/steps/quick-verification.md +2 -4
- package/gsd-core/workflows/quick/steps/research-phase.md +2 -4
- package/gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md +3 -3
- package/gsd-core/workflows/quick.md +20 -29
- package/gsd-core/workflows/remove-phase.md +4 -4
- package/gsd-core/workflows/remove-workspace.md +2 -2
- package/gsd-core/workflows/resume-project.md +8 -12
- package/gsd-core/workflows/review.md +193 -15
- package/gsd-core/workflows/scan.md +1 -1
- package/gsd-core/workflows/secure-phase.md +2 -2
- package/gsd-core/workflows/settings-advanced.md +7 -9
- package/gsd-core/workflows/settings-integrations.md +64 -31
- package/gsd-core/workflows/settings.md +3 -5
- package/gsd-core/workflows/ship.md +12 -6
- package/gsd-core/workflows/sketch-wrap-up.md +11 -17
- package/gsd-core/workflows/sketch.md +12 -18
- package/gsd-core/workflows/smart-entry.md +3 -5
- package/gsd-core/workflows/spec-phase.md +23 -1
- package/gsd-core/workflows/spike-wrap-up.md +7 -11
- package/gsd-core/workflows/spike.md +20 -31
- package/gsd-core/workflows/stats.md +2 -2
- package/gsd-core/workflows/sync-skills.md +1 -1
- package/gsd-core/workflows/thread.md +11 -7
- package/gsd-core/workflows/transition.md +5 -5
- package/gsd-core/workflows/ui-phase.md +10 -16
- package/gsd-core/workflows/ui-review.md +6 -10
- package/gsd-core/workflows/ultraplan-phase.md +5 -13
- package/gsd-core/workflows/undo.md +8 -16
- package/gsd-core/workflows/update.md +6 -10
- package/gsd-core/workflows/validate-phase.md +2 -2
- package/gsd-core/workflows/verify-work/steps/automated-ui-verification.md +25 -1
- package/gsd-core/workflows/verify-work/steps/mvp-uat-framing.md +1 -1
- package/gsd-core/workflows/verify-work.md +57 -18
- package/hooks/dist/gsd-agent-isolation-guard.js +77 -38
- package/hooks/dist/gsd-config-reload.js +18 -12
- package/hooks/dist/gsd-context-monitor.js +19 -10
- package/hooks/dist/gsd-cursor-post-tool.js +3 -1
- package/hooks/dist/gsd-cursor-pre-tool.js +3 -1
- package/hooks/dist/gsd-cursor-session-start.js +2 -1
- package/hooks/dist/gsd-cursor-stop.js +2 -1
- package/hooks/dist/gsd-cursor-subagent-start.js +28 -23
- package/hooks/dist/gsd-cursor-subagent-stop.js +3 -1
- package/hooks/dist/gsd-ensure-canonical-path.js +2 -1
- package/hooks/dist/gsd-graphify-update.sh +22 -18
- package/hooks/dist/gsd-node-runner.sh +76 -0
- package/hooks/dist/gsd-phase-boundary.sh +1 -0
- package/hooks/dist/gsd-prompt-guard.js +16 -7
- package/hooks/dist/gsd-read-guard.js +16 -7
- package/hooks/dist/gsd-read-injection-scanner.js +17 -8
- package/hooks/dist/gsd-session-state.sh +1 -0
- package/hooks/dist/gsd-statusline.js +215 -26
- package/hooks/dist/gsd-validate-commit.sh +80 -6
- package/hooks/dist/gsd-windsurf-pre-command.js +16 -11
- package/hooks/dist/gsd-windsurf-pre-write.js +22 -13
- package/hooks/dist/gsd-workflow-guard.js +34 -16
- package/hooks/dist/gsd-worktree-path-guard.js +36 -21
- package/hooks/dist/gsd-write-guard.js +35 -25
- package/hooks/dist/lib/cli-exit.js +560 -0
- package/hooks/dist/lib/exit-code-registry.js +98 -0
- package/hooks/dist/lib/git-probe.js +84 -0
- package/hooks/dist/lib/hook-exit.js +81 -0
- package/hooks/dist/managed-hooks-registry.cjs +3 -0
- package/hooks/gsd-agent-isolation-guard.js +77 -38
- package/hooks/gsd-config-reload.js +18 -12
- package/hooks/gsd-context-monitor.js +19 -10
- package/hooks/gsd-cursor-post-tool.js +3 -1
- package/hooks/gsd-cursor-pre-tool.js +3 -1
- package/hooks/gsd-cursor-session-start.js +2 -1
- package/hooks/gsd-cursor-stop.js +2 -1
- package/hooks/gsd-cursor-subagent-start.js +28 -23
- package/hooks/gsd-cursor-subagent-stop.js +3 -1
- package/hooks/gsd-ensure-canonical-path.js +2 -1
- package/hooks/gsd-graphify-update.sh +22 -18
- package/hooks/gsd-node-runner.sh +76 -0
- package/hooks/gsd-phase-boundary.sh +1 -0
- package/hooks/gsd-prompt-guard.js +16 -7
- package/hooks/gsd-read-guard.js +16 -7
- package/hooks/gsd-read-injection-scanner.js +17 -8
- package/hooks/gsd-session-state.sh +1 -0
- package/hooks/gsd-statusline.js +215 -26
- package/hooks/gsd-validate-commit.sh +80 -6
- package/hooks/gsd-windsurf-pre-command.js +16 -11
- package/hooks/gsd-windsurf-pre-write.js +22 -13
- package/hooks/gsd-workflow-guard.js +34 -16
- package/hooks/gsd-worktree-path-guard.js +36 -21
- package/hooks/gsd-write-guard.js +35 -25
- package/hooks/lib/cli-exit.js +560 -0
- package/hooks/lib/exit-code-registry.js +98 -0
- package/hooks/lib/git-probe.js +84 -0
- package/hooks/lib/hook-exit.js +81 -0
- package/hooks/managed-hooks-registry.cjs +3 -0
- package/package.json +12 -7
- package/scripts/base64-scan.sh +74 -12
- package/scripts/build-hooks.js +5 -0
- package/scripts/check-glossary-refs.cjs +77 -15
- package/scripts/check-mutation-score-ratchet.cjs +156 -0
- package/scripts/ci-check-job-near-cap.cjs +49 -0
- package/scripts/ci-pr-mergeability.cjs +262 -0
- package/scripts/ci-test-scope.cjs +45 -12
- package/scripts/ci-timeout-report.cjs +230 -0
- package/scripts/docs-guard-registry.cjs +396 -0
- package/scripts/gen-capability-registry.cjs +8 -6
- package/scripts/gen-exit-code-docs.cjs +318 -0
- package/scripts/gen-exit-code-registry.cjs +891 -0
- package/scripts/gen-features.cjs +836 -0
- package/scripts/gen-hooks-cli-exit.cjs +239 -0
- package/scripts/gen-install-tree-fixtures.cjs +2 -2
- package/scripts/gen-loop-host-contract.cjs +134 -1
- package/scripts/gen-scripts-cli-exit.cjs +185 -0
- package/scripts/gen-state-md-docs.cjs +727 -0
- package/scripts/{test-failure-reasons.cjs → gsd-test-gate-reasons.cjs} +6 -0
- package/scripts/lib/ci-job-timing.cjs +72 -0
- package/scripts/lib/cli-exit.cjs +546 -44
- package/scripts/lib/drift-scan.cjs +32 -2
- package/scripts/lib/exit-code-registry.cjs +98 -0
- package/scripts/lib/ndjson-reporter.cjs +119 -0
- package/scripts/lint-allow-test-rule-refs.unverified-ceiling.json +1 -1
- package/scripts/lint-docs-guard-registration.cjs +495 -0
- package/scripts/lint-docs-guard-registration.exempt-baseline.cjs +193 -0
- package/scripts/lint-eslint-glob-coverage.allowlist.json +4 -0
- package/scripts/{lint-fix-has-regression-test.cjs → lint-fix-has-regression-tests.cjs} +12 -6
- package/scripts/lint-health-diagnostic-rule-table.cjs +65 -8
- package/scripts/lint-mutation-test-derivation-drift.cjs +86 -0
- package/scripts/lint-phase-enumeration-drift.cjs +21 -8
- package/scripts/lint-planning-prompt-drift.cjs +38 -1
- package/scripts/lint-removed-but-needed.cjs +184 -16
- package/scripts/lint-seam-enforcement.cjs +182 -0
- package/scripts/lint-slug-derivation-drift.cjs +921 -0
- package/scripts/lint-source-test-name-collision.cjs +241 -0
- package/scripts/lint-state-write-path-drift.cjs +337 -432
- package/scripts/lint-test-file-count.allowlist.json +122 -4
- package/scripts/lint-test-file-count.cjs +25 -3
- package/scripts/lint-unreachable-guard-drift.cjs +51 -64
- package/scripts/lint-vendored-deps.cjs +208 -35
- package/scripts/mutation-matrix.cjs +599 -50
- package/scripts/prompt-injection-scan.sh +75 -14
- package/scripts/secret-scan.sh +75 -13
- package/scripts/select-docs-guards.cjs +56 -0
- package/scripts/sync-runtime-launcher.cjs +22 -3
- package/skills/gsd-discuss-phase/SKILL.md +1 -1
- package/skills/gsd-import/SKILL.md +1 -1
- package/skills/gsd-quick/SKILL.md +8 -4
- package/vscode/package.json +1 -1
- package/bin/lib/ui-safety-gate.cjs +0 -109
- package/scripts/lint-emitted-drift-ack.cjs +0 -344
- package/scripts/state-write-path-drift-baseline.json +0 -19
|
@@ -38,7 +38,7 @@ const commonjs_marker_cjs_1 = require("./commonjs-marker.cjs");
|
|
|
38
38
|
const imperative_hook_bus_cjs_1 = require("./host-integration-adapters/imperative-hook-bus.cjs");
|
|
39
39
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
40
40
|
const shellCmdProjection = require("./shell-command-projection.cjs");
|
|
41
|
-
const { isManagedHookBasename, isManagedHookCommand, projectLegacySettingsHookCommand, projectManagedHookCommand, projectPortableHookBaseDir, projectCodexHookTomlCommand, shellHookOmitsBashRunner, escapeTomlDoubleQuotedString, } = shellCmdProjection;
|
|
41
|
+
const { isManagedHookBasename, isManagedHookCommand, projectLegacySettingsHookCommand, projectManagedHookCommand, projectPortableHookBaseDir, projectCodexHookTomlCommand, shellHookOmitsBashRunner, escapeTomlDoubleQuotedString, escapePosixDoubleQuoted, } = shellCmdProjection;
|
|
42
42
|
// ---------------------------------------------------------------------------
|
|
43
43
|
// Terminal color constants (mirrors install.js for console output parity)
|
|
44
44
|
// ---------------------------------------------------------------------------
|
|
@@ -136,23 +136,64 @@ function _capabilityTitle(runtime) {
|
|
|
136
136
|
let __atomicWriteCounter = 0;
|
|
137
137
|
// Set<string> — absolute paths of .tmp-<pid>-<n> files this process created.
|
|
138
138
|
const __atomicWrittenTmps = new Set();
|
|
139
|
+
// Retry budget for the EEXIST (squatted temp path) branch in atomicWriteFileSync.
|
|
140
|
+
const MAX_TEMP_FILE_ATTEMPTS = 4;
|
|
139
141
|
function atomicWriteFileSync(target, data, options) {
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
142
|
+
// A pre-existing target's permission bits must survive the rewrite:
|
|
143
|
+
// rename() swaps the temp file's inode into place, so without an explicit
|
|
144
|
+
// carry a user-hardened chmod (e.g. 600 on a secrets-bearing settings.json)
|
|
145
|
+
// would silently reset to the umask default.
|
|
146
|
+
let priorMode;
|
|
143
147
|
try {
|
|
144
|
-
node_fs_1.default.
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
148
|
+
const st = node_fs_1.default.statSync(target);
|
|
149
|
+
if (st.isFile())
|
|
150
|
+
priorMode = st.mode & 0o7777;
|
|
151
|
+
}
|
|
152
|
+
catch { /* no pre-existing target: default creation mode applies */ }
|
|
153
|
+
// 'wx' (O_EXCL) refuses to follow a symlink pre-planted at the predictable
|
|
154
|
+
// temp path and refuses to reuse a foreign file already sitting there; on
|
|
155
|
+
// EEXIST the write retries under a fresh counter value.
|
|
156
|
+
const exclusiveOptions = typeof options === 'string' || options == null
|
|
157
|
+
? { encoding: options ?? null, flag: 'wx' }
|
|
158
|
+
: { ...options, flag: 'wx' };
|
|
159
|
+
for (let attempt = 0;; attempt++) {
|
|
160
|
+
__atomicWriteCounter += 1;
|
|
161
|
+
const tmp = `${target}.tmp-${process.pid}-${__atomicWriteCounter}`;
|
|
162
|
+
__atomicWrittenTmps.add(tmp);
|
|
151
163
|
try {
|
|
152
|
-
node_fs_1.default.
|
|
164
|
+
node_fs_1.default.writeFileSync(tmp, data, exclusiveOptions);
|
|
165
|
+
}
|
|
166
|
+
catch (e) {
|
|
167
|
+
if (e.code === 'EEXIST') {
|
|
168
|
+
// The file at tmp is not ours — never rmSync it.
|
|
169
|
+
if (attempt < MAX_TEMP_FILE_ATTEMPTS)
|
|
170
|
+
continue;
|
|
171
|
+
throw e;
|
|
172
|
+
}
|
|
173
|
+
try {
|
|
174
|
+
node_fs_1.default.rmSync(tmp, { force: true });
|
|
175
|
+
}
|
|
176
|
+
catch { /* ignore */ }
|
|
177
|
+
throw e;
|
|
153
178
|
}
|
|
154
|
-
|
|
155
|
-
|
|
179
|
+
try {
|
|
180
|
+
// chmod rather than options.mode: open(2) masks mode with the process
|
|
181
|
+
// umask, chmod applies the preserved bits exactly.
|
|
182
|
+
if (priorMode !== undefined)
|
|
183
|
+
node_fs_1.default.chmodSync(tmp, priorMode);
|
|
184
|
+
shellCmdProjection.retryRenameSync(tmp, target);
|
|
185
|
+
// Successful rename: the tmp path no longer exists, but leave it in the
|
|
186
|
+
// Set so _cleanTmpFiles can recognise it as installer-owned if it somehow
|
|
187
|
+
// lingers (e.g. a rename succeeded but left a stale entry on some FS).
|
|
188
|
+
}
|
|
189
|
+
catch (e) {
|
|
190
|
+
try {
|
|
191
|
+
node_fs_1.default.rmSync(tmp, { force: true });
|
|
192
|
+
}
|
|
193
|
+
catch { /* ignore */ }
|
|
194
|
+
throw e;
|
|
195
|
+
}
|
|
196
|
+
return;
|
|
156
197
|
}
|
|
157
198
|
}
|
|
158
199
|
// ---------------------------------------------------------------------------
|
|
@@ -333,20 +374,41 @@ function parseTomlValue(text, i) {
|
|
|
333
374
|
}
|
|
334
375
|
throw new Error(`parseTomlValue: unexpected character '${ch}' at position ${i}`);
|
|
335
376
|
}
|
|
377
|
+
/**
|
|
378
|
+
* Normalize a directory that will be joined with `/…` — posix separators, no
|
|
379
|
+
* trailing slash. `FNM_DIR=/custom/fnm/` would otherwise bake
|
|
380
|
+
* `/custom/fnm//aliases/default/bin/node` (#3704 review). Cosmetic — `existsSync`
|
|
381
|
+
* resolves the doubled separator and every shell collapses it — but the value is
|
|
382
|
+
* written into a user's settings.json and read by humans.
|
|
383
|
+
*
|
|
384
|
+
* Shared by both fnm branches deliberately: they build the same alias paths from
|
|
385
|
+
* different roots, and a trim applied to only one is a difference with no reason
|
|
386
|
+
* behind it.
|
|
387
|
+
*/
|
|
388
|
+
function normalizeRootDir(dir) {
|
|
389
|
+
return shellCmdProjection.posixNormalize(dir).replace(/\/+$/, '');
|
|
390
|
+
}
|
|
336
391
|
function normalizeNodePath(execPath, opts) {
|
|
337
392
|
if (!execPath)
|
|
338
393
|
return execPath;
|
|
339
394
|
const env = (opts && opts.env) || process.env;
|
|
340
395
|
const existsSync = (opts && opts.existsSync) || node_fs_1.default.existsSync;
|
|
341
396
|
const normalizedForMatch = shellCmdProjection.posixNormalize(execPath);
|
|
342
|
-
|
|
397
|
+
// #977: fnm's Windows shim IS `process.execPath` there — Windows does not
|
|
398
|
+
// realpath through it — so this branch stays. #3704 adds `(?:bin\/)?`: fnm's
|
|
399
|
+
// POSIX shim is `<multishell>/bin/node`, so the original pattern could not match
|
|
400
|
+
// it even when a caller hands one in explicitly (#3662's `execPath` option
|
|
401
|
+
// exists to do exactly that, normalizing a path from another environment).
|
|
402
|
+
if (/\/fnm_multishells\/[0-9]+_[0-9]+\/(?:bin\/)?node(\.exe)?$/i.test(normalizedForMatch)) {
|
|
343
403
|
const candidates = [];
|
|
344
404
|
if (env.FNM_DIR) {
|
|
345
|
-
|
|
346
|
-
candidates.push(`${
|
|
405
|
+
const fnmRoot = normalizeRootDir(env.FNM_DIR);
|
|
406
|
+
candidates.push(`${fnmRoot}/aliases/default/node.exe`);
|
|
407
|
+
candidates.push(`${fnmRoot}/aliases/default/bin/node`);
|
|
347
408
|
}
|
|
348
|
-
|
|
349
|
-
|
|
409
|
+
const appdata = shellCmdProjection.envGet(env, 'APPDATA');
|
|
410
|
+
if (appdata) {
|
|
411
|
+
candidates.push(`${normalizeRootDir(appdata)}/fnm/aliases/default/node.exe`);
|
|
350
412
|
}
|
|
351
413
|
for (const candidate of candidates) {
|
|
352
414
|
if (candidate && existsSync(candidate))
|
|
@@ -354,6 +416,42 @@ function normalizeNodePath(execPath, opts) {
|
|
|
354
416
|
}
|
|
355
417
|
return execPath;
|
|
356
418
|
}
|
|
419
|
+
// #3704: fnm pins a concrete version at
|
|
420
|
+
// <FNM_DIR>/node-versions/<ver>/installation/bin/node (Windows:
|
|
421
|
+
// .../installation/node.exe). On macOS/Linux this — not the shim above — is what
|
|
422
|
+
// `process.execPath` reports, because Node realpaths through
|
|
423
|
+
// `fnm_multishells`. So the shim branch above is unreachable on POSIX and the
|
|
424
|
+
// raw versioned path was baked into every managed hook: `fnm uninstall <ver>`,
|
|
425
|
+
// or fnm's own pruning, then 404s every hook — the ephemeral-path failure #977
|
|
426
|
+
// exists to prevent, and the same one #1619 (mise) and #2185 (Homebrew) fixed
|
|
427
|
+
// for their managers by matching the VERSIONED path rather than a shim.
|
|
428
|
+
//
|
|
429
|
+
// The stable alias is <FNM_DIR>/aliases/default/..., which fnm repoints on
|
|
430
|
+
// `fnm default`. Derive <FNM_DIR> from execPath first (#2185's rule — the path
|
|
431
|
+
// IS the install location, and FNM_DIR may be unset or point at a different
|
|
432
|
+
// install), keeping the env as a secondary candidate. Rewrite only when the
|
|
433
|
+
// alias exists; otherwise fall through to the raw execPath, exactly like the
|
|
434
|
+
// mise and volta branches, so a rewrite never turns a stale-but-working pin
|
|
435
|
+
// into an immediately broken one.
|
|
436
|
+
const fnmVersioned = normalizedForMatch.match(/^(.*)\/node-versions\/[^/]+\/installation\/(?:bin\/)?node(\.exe)?$/i);
|
|
437
|
+
if (fnmVersioned) {
|
|
438
|
+
const isExe = Boolean(fnmVersioned[2]);
|
|
439
|
+
const roots = [fnmVersioned[1]];
|
|
440
|
+
if (env.FNM_DIR)
|
|
441
|
+
roots.push(normalizeRootDir(env.FNM_DIR));
|
|
442
|
+
const fnmAliasCandidates = [];
|
|
443
|
+
for (const root of roots) {
|
|
444
|
+
// Probe the spelling matching the input first, then the other — a layout
|
|
445
|
+
// is one or the other, and guessing wrong would skip a real alias.
|
|
446
|
+
const leaf = isExe ? ['node.exe', 'bin/node'] : ['bin/node', 'node.exe'];
|
|
447
|
+
for (const tail of leaf)
|
|
448
|
+
fnmAliasCandidates.push(`${root}/aliases/default/${tail}`);
|
|
449
|
+
}
|
|
450
|
+
for (const candidate of fnmAliasCandidates) {
|
|
451
|
+
if (existsSync(candidate))
|
|
452
|
+
return candidate;
|
|
453
|
+
}
|
|
454
|
+
}
|
|
357
455
|
// Homebrew (macOS Intel /usr/local, Apple Silicon /opt/homebrew, Linuxbrew
|
|
358
456
|
// /home/linuxbrew/.linuxbrew, and any custom HOMEBREW_PREFIX) pins node at
|
|
359
457
|
// <prefix>/Cellar/node(<@ver>)?/<ver>/bin/node, then deletes prior versions on
|
|
@@ -399,12 +497,76 @@ function normalizeNodePath(execPath, opts) {
|
|
|
399
497
|
return execPath;
|
|
400
498
|
}
|
|
401
499
|
function resolveNodeRunner(opts) {
|
|
402
|
-
const execPath = typeof process.execPath === 'string' ? process.execPath : '';
|
|
500
|
+
const execPath = (opts && opts.execPath) || (typeof process.execPath === 'string' ? process.execPath : '');
|
|
403
501
|
if (!execPath)
|
|
404
502
|
return null;
|
|
405
503
|
const stablePath = normalizeNodePath(execPath, opts);
|
|
406
504
|
return JSON.stringify(shellCmdProjection.posixNormalize(stablePath));
|
|
407
505
|
}
|
|
506
|
+
/**
|
|
507
|
+
* #3662 — the runtime-resolving node runner token for managed JS hooks.
|
|
508
|
+
*
|
|
509
|
+
* A bake-time absolute runner (`resolveNodeRunner`) only works in the
|
|
510
|
+
* environment that ran the installer; a config root shared across
|
|
511
|
+
* environments (the `--portable-hooks` scenario, or any settings.json under a
|
|
512
|
+
* mounted `$HOME`) carries a path that 404s with exit 127 everywhere else.
|
|
513
|
+
* This token is a POSIX `sh` command substitution that resolves node at
|
|
514
|
+
* hook-fire time, trying IN ORDER:
|
|
515
|
+
*
|
|
516
|
+
* 1. the baked installer path (absolute — keeps the #2979/#3002/#3017/#3022
|
|
517
|
+
* minimal-PATH guarantee: where the baked path exists it still wins,
|
|
518
|
+
* under any PATH, GUI launch included);
|
|
519
|
+
* 2. `command -v node` (quoted — one word even with spaces in the result);
|
|
520
|
+
* 3. the well-known stable layouts (`/usr/local/bin/node`, `/usr/bin/node`).
|
|
521
|
+
*
|
|
522
|
+
* The FIRST executable candidate wins; if none resolves the substitution
|
|
523
|
+
* yields an empty word and the hook fails exactly as a stale absolute path
|
|
524
|
+
* does today — no bare `node` token is ever emitted or depended on.
|
|
525
|
+
*
|
|
526
|
+
* One shape for every platform: emitted hook commands execute via POSIX `sh`
|
|
527
|
+
* (Claude-on-win32 runs Git Bash per #166/#580; `hookCommandNeedsPowerShellCallOperator`
|
|
528
|
+
* is an unused opt-in), and the baked path is posixNormalize'd before escaping
|
|
529
|
+
* (escapePosixDoubleQuoted — the Shell Command Projection seam owns quoting).
|
|
530
|
+
* The portable resolver script (hooks/gsd-node-runner.sh) resolves through a
|
|
531
|
+
* SUPERSET of this candidate list — keep the two lists consistent.
|
|
532
|
+
*/
|
|
533
|
+
function buildNodeRunnerChainToken(opts) {
|
|
534
|
+
const execPath = (opts && opts.execPath) || (typeof process.execPath === 'string' ? process.execPath : '');
|
|
535
|
+
if (!execPath)
|
|
536
|
+
return null;
|
|
537
|
+
const stablePath = shellCmdProjection.posixNormalize(normalizeNodePath(execPath, opts));
|
|
538
|
+
const baked = escapePosixDoubleQuoted(stablePath);
|
|
539
|
+
// Absolute candidates only (leading / or a win32 drive letter): a relative
|
|
540
|
+
// `command -v node` hit (legal under a relative PATH entry) must never
|
|
541
|
+
// promote repo-cwd content into the runner slot. The gate uses parameter
|
|
542
|
+
// expansion + [ ] — deliberately NO `case` (its `)` terminates the command
|
|
543
|
+
// substitution under macOS's stock bash 3.2 /bin/sh, breaking the hook).
|
|
544
|
+
// \${…} below stays a literal shell parameter expansion, not TS interpolation.
|
|
545
|
+
return `"$(for n in "${baked}" "$(command -v node)" /usr/local/bin/node /usr/bin/node; do [ -x "$n" ] && { [ "\${n#/}" != "$n" ] || [ "\${n#?:}" != "$n" ]; } && printf '%s' "$n" && break; done)"`;
|
|
546
|
+
}
|
|
547
|
+
/**
|
|
548
|
+
* #3662 — the install-time node path as a shell-safe QUOTED token, carrying
|
|
549
|
+
* the same double-quote escaping as the chain token (`escapePosixDoubleQuoted`
|
|
550
|
+
* — $ ` " \), NOT bare JSON quoting. The portable resolver's first argument
|
|
551
|
+
* is executed by the host shell before the resolver sees argv, so a path
|
|
552
|
+
* containing shell metacharacters must arrive escaped.
|
|
553
|
+
*/
|
|
554
|
+
function buildBakedNodeToken(opts) {
|
|
555
|
+
const execPath = (opts && opts.execPath) || (typeof process.execPath === 'string' ? process.execPath : '');
|
|
556
|
+
if (!execPath)
|
|
557
|
+
return null;
|
|
558
|
+
const stablePath = shellCmdProjection.posixNormalize(normalizeNodePath(execPath, opts));
|
|
559
|
+
return `"${escapePosixDoubleQuoted(stablePath)}"`;
|
|
560
|
+
}
|
|
561
|
+
/**
|
|
562
|
+
* #3662 — basename of the portable node resolver staged into the install's
|
|
563
|
+
* hooks/ directory. Under `--portable-hooks`, managed JS hook commands route
|
|
564
|
+
* through it (`bash "<hooks>/gsd-node-runner.sh" "<baked-node>" "<script>.js"`)
|
|
565
|
+
* so the SAME staged file works for every install: the install-time node path
|
|
566
|
+
* travels as the resolver's first argument (tried first — the minimal-PATH
|
|
567
|
+
* guarantee), ahead of `command -v node` and the well-known fallback list.
|
|
568
|
+
*/
|
|
569
|
+
const NODE_RUNNER_RESOLVER_HOOK = 'gsd-node-runner.sh';
|
|
408
570
|
function resolveBashRunner(opts) {
|
|
409
571
|
const platform = (opts && opts.platform) || process.platform;
|
|
410
572
|
if (platform !== 'win32')
|
|
@@ -429,8 +591,13 @@ function resolveBashRunner(opts) {
|
|
|
429
591
|
}
|
|
430
592
|
return null;
|
|
431
593
|
}
|
|
432
|
-
|
|
433
|
-
|
|
594
|
+
// #3662 — recognize the two runtime-resolving command shapes the installer
|
|
595
|
+
// emits, so the rewriter never churns (or un-does) an entry that already
|
|
596
|
+
// works in every environment sharing the config root.
|
|
597
|
+
const CHAIN_RUNNER_COMMAND = /^"\$\(for n in [\s\S]*?printf '%s' "\$n" && break; done\)"\s+\S/;
|
|
598
|
+
const RESOLVER_RUNNER_COMMAND = /^(?:"[^"]*bash(\.exe)?"|bash)\s+"[^"]*gsd-node-runner\.sh"\s+"[^"]*"\s+\S/;
|
|
599
|
+
function rewriteLegacyManagedNodeHookCommands(settings, runnerToken, opts) {
|
|
600
|
+
if (!settings || !settings.hooks || !runnerToken)
|
|
434
601
|
return false;
|
|
435
602
|
if (!opts)
|
|
436
603
|
opts = {};
|
|
@@ -452,29 +619,31 @@ function rewriteLegacyManagedNodeHookCommands(settings, absoluteRunner, opts) {
|
|
|
452
619
|
if (hadPowerShellCallOperator) {
|
|
453
620
|
trimmed = trimmed.replace(/^&\s+/, '').trim();
|
|
454
621
|
}
|
|
622
|
+
if (CHAIN_RUNNER_COMMAND.test(trimmed) || RESOLVER_RUNNER_COMMAND.test(trimmed))
|
|
623
|
+
continue;
|
|
455
624
|
const m = trimmed.match(/^node\s+("([^"]+)"|'([^']+)'|(\S+))\s*$/) ||
|
|
456
625
|
trimmed.match(/^("([^"]+)"|'([^']+)'|(\S+))\s+("([^"]+)"|'([^']+)'|(\S+))\s*$/);
|
|
457
626
|
if (!m)
|
|
458
627
|
continue;
|
|
459
|
-
let
|
|
628
|
+
let scriptToken, scriptPath;
|
|
460
629
|
if (/^node\s+/.test(trimmed)) {
|
|
461
|
-
_runnerToken = 'node';
|
|
462
630
|
scriptToken = m[1];
|
|
463
631
|
scriptPath = m[2] || m[3] || m[4] || '';
|
|
464
632
|
}
|
|
465
633
|
else {
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
634
|
+
// #3662: the pre-fix two-token shape baked an absolute node path at
|
|
635
|
+
// install time. A foreign-but-stable runner (valid in the
|
|
636
|
+
// environment that wrote it, absent here) used to be SKIPPED — the
|
|
637
|
+
// exact mechanism behind the mixed state where no environment can
|
|
638
|
+
// run all hooks. Every two-token managed entry now re-projects onto
|
|
639
|
+
// the runtime-resolving runner, whatever environment baked it.
|
|
471
640
|
scriptToken = m[5];
|
|
472
641
|
scriptPath = m[6] || m[7] || m[8] || '';
|
|
473
642
|
}
|
|
474
643
|
if (!isManagedHookBasename(scriptPath, { surface: 'settings-json' }))
|
|
475
644
|
continue;
|
|
476
645
|
const projectedCommand = projectLegacySettingsHookCommand({
|
|
477
|
-
|
|
646
|
+
runnerToken,
|
|
478
647
|
scriptPath,
|
|
479
648
|
scriptToken,
|
|
480
649
|
runtime: opts.runtime || 'generic',
|
|
@@ -863,27 +1032,75 @@ function buildHookCommand(configDir, hookName, opts) {
|
|
|
863
1032
|
}
|
|
864
1033
|
return JSON.stringify(shellCmdProjection.posixNormalize(configDir) + '/hooks/' + hookName);
|
|
865
1034
|
}
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
1035
|
+
// .sh hooks keep the pre-#3662 shape everywhere: the bash runner resolves
|
|
1036
|
+
// at install time like today, and `bash` itself is a PATH-stable binary
|
|
1037
|
+
// (the absolute Git-Bash discovery covers win32 — #580/#3393).
|
|
1038
|
+
if (isShellHook) {
|
|
1039
|
+
const runner = resolveBashRunner(opts);
|
|
1040
|
+
if (runner === null)
|
|
1041
|
+
return null;
|
|
1042
|
+
if (opts.portableHooks) {
|
|
1043
|
+
const portableBaseDir = projectPortableHookBaseDir({
|
|
1044
|
+
configDir,
|
|
1045
|
+
homeDir: node_os_1.default.homedir(),
|
|
1046
|
+
});
|
|
1047
|
+
return projectManagedHookCommand({
|
|
1048
|
+
absoluteRunner: runner,
|
|
1049
|
+
scriptPath: `${portableBaseDir}/hooks/${hookName}`,
|
|
1050
|
+
runtime: opts.runtime || 'generic',
|
|
1051
|
+
platform,
|
|
1052
|
+
hookShell,
|
|
1053
|
+
});
|
|
1054
|
+
}
|
|
1055
|
+
const hooksPath = shellCmdProjection.posixNormalize(configDir) + '/hooks/' + hookName;
|
|
1056
|
+
return projectManagedHookCommand({
|
|
1057
|
+
absoluteRunner: runner,
|
|
1058
|
+
scriptPath: hooksPath,
|
|
1059
|
+
runtime,
|
|
1060
|
+
platform,
|
|
1061
|
+
hookShell,
|
|
1062
|
+
});
|
|
1063
|
+
}
|
|
1064
|
+
// JS hooks (#3662): the node runner is resolved at hook-fire time, never
|
|
1065
|
+
// baked as a bare absolute path — an install-environment absolute path is
|
|
1066
|
+
// exactly what breaks with exit 127 when the config root is shared across
|
|
1067
|
+
// environments with different node layouts.
|
|
870
1068
|
if (opts.portableHooks) {
|
|
1069
|
+
// Portable installs route through the staged resolver: the baked absolute
|
|
1070
|
+
// path travels as the resolver's FIRST argument (tried first, so the
|
|
1071
|
+
// minimal-PATH guarantee holds), then `command -v node`, then the
|
|
1072
|
+
// well-known list — one staged file, no per-install templating. The
|
|
1073
|
+
// token is shell-escaped like the chain (the host shell expands the
|
|
1074
|
+
// argument before bash sees argv), not merely JSON-quoted.
|
|
1075
|
+
const bakedToken = buildBakedNodeToken(opts);
|
|
1076
|
+
if (bakedToken === null)
|
|
1077
|
+
return null;
|
|
871
1078
|
const portableBaseDir = projectPortableHookBaseDir({
|
|
872
1079
|
configDir,
|
|
873
1080
|
homeDir: node_os_1.default.homedir(),
|
|
874
1081
|
});
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
1082
|
+
// Absolute Git-Bash discovery on win32 when available (#580); `bash` on
|
|
1083
|
+
// PATH otherwise — the same assumption .sh hooks already make.
|
|
1084
|
+
const resolverRunner = resolveBashRunner(opts) || 'bash';
|
|
1085
|
+
return shellCmdProjection.projectShellCommandText({
|
|
1086
|
+
runnerToken: resolverRunner,
|
|
1087
|
+
argTokens: [
|
|
1088
|
+
JSON.stringify(`${portableBaseDir}/hooks/${NODE_RUNNER_RESOLVER_HOOK}`),
|
|
1089
|
+
bakedToken,
|
|
1090
|
+
JSON.stringify(`${portableBaseDir}/hooks/${hookName}`),
|
|
1091
|
+
],
|
|
1092
|
+
runtime,
|
|
879
1093
|
platform,
|
|
880
1094
|
hookShell,
|
|
881
1095
|
});
|
|
882
1096
|
}
|
|
1097
|
+
const chainRunner = buildNodeRunnerChainToken(opts);
|
|
1098
|
+
if (chainRunner === null)
|
|
1099
|
+
return null;
|
|
883
1100
|
const hooksPath = shellCmdProjection.posixNormalize(configDir) + '/hooks/' + hookName;
|
|
884
|
-
return
|
|
885
|
-
|
|
886
|
-
|
|
1101
|
+
return shellCmdProjection.projectShellCommandText({
|
|
1102
|
+
runnerToken: chainRunner,
|
|
1103
|
+
argTokens: [JSON.stringify(hooksPath)],
|
|
887
1104
|
runtime,
|
|
888
1105
|
platform,
|
|
889
1106
|
hookShell,
|
|
@@ -1119,28 +1336,39 @@ function writeCursorHooksJson(targetDir, src, opts) {
|
|
|
1119
1336
|
installedScripts.add(script);
|
|
1120
1337
|
}
|
|
1121
1338
|
}
|
|
1122
|
-
// Stage the hooks/lib/ helpers the staged scripts require (#2587)
|
|
1123
|
-
//
|
|
1124
|
-
//
|
|
1125
|
-
//
|
|
1126
|
-
//
|
|
1127
|
-
//
|
|
1339
|
+
// Stage the hooks/lib/ helpers the staged scripts require (#2587), TRANSITIVELY
|
|
1340
|
+
// (#3911 review): a lib helper can itself require a sibling under lib/ (e.g.
|
|
1341
|
+
// hooks/lib/hook-exit.js requires './cli-exit.js', which requires
|
|
1342
|
+
// './exit-code-registry.js') — a require with NO './lib/' prefix, because from
|
|
1343
|
+
// inside lib/ the sibling is already local. The original single-pass scan only
|
|
1344
|
+
// ever matched the "./lib/…" spelling used FROM a hook script, so it staged
|
|
1345
|
+
// hook-exit.js but never walked hook-exit.js's own requires, and an installed
|
|
1346
|
+
// Cursor hook wedged on MODULE_NOT_FOUND for './cli-exit.js' at load — before
|
|
1347
|
+
// its own try/catch. This walks a worklist: hook scripts seed it with their
|
|
1348
|
+
// "./lib/X" requires, and every lib file staged is itself scanned for further
|
|
1349
|
+
// "./lib/X" OR bare "./X" (sibling-within-lib) requires, so the requirement
|
|
1350
|
+
// graph is derived to a fixed point instead of one hand-tuned level deep.
|
|
1128
1351
|
const requiredLibFiles = new Set();
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
// a miss here surfaces as MODULE_NOT_FOUND at hook load, not at install.
|
|
1134
|
-
const re = /require\(\s*['"]\.\/lib\/([A-Za-z0-9._-]+)['"]\s*\)/g;
|
|
1352
|
+
const scannedLibFiles = new Set();
|
|
1353
|
+
const libRequireRe = /require\(\s*['"]\.\/(?:lib\/)?([A-Za-z0-9._-]+)['"]\s*\)/g;
|
|
1354
|
+
function scanForLibRequires(source) {
|
|
1355
|
+
libRequireRe.lastIndex = 0;
|
|
1135
1356
|
let m;
|
|
1136
|
-
while ((m =
|
|
1357
|
+
while ((m = libRequireRe.exec(source)) !== null)
|
|
1137
1358
|
requiredLibFiles.add(m[1]);
|
|
1138
1359
|
}
|
|
1360
|
+
for (const script of installedScripts) {
|
|
1361
|
+
scanForLibRequires(node_fs_1.default.readFileSync(node_path_1.default.join(hooksDir, script), 'utf8'));
|
|
1362
|
+
}
|
|
1139
1363
|
if (requiredLibFiles.size > 0) {
|
|
1140
1364
|
const srcLibDir = node_path_1.default.join(srcHooksDir, 'lib');
|
|
1141
1365
|
const destLibDir = node_path_1.default.join(hooksDir, 'lib');
|
|
1142
1366
|
node_fs_1.default.mkdirSync(destLibDir, { recursive: true });
|
|
1143
|
-
|
|
1367
|
+
// Iterate to a fixed point: staging a lib file can add MORE required lib
|
|
1368
|
+
// files (its own requires), which must themselves be staged and scanned.
|
|
1369
|
+
let libFile = [...requiredLibFiles].find((f) => !scannedLibFiles.has(f));
|
|
1370
|
+
while (libFile !== undefined) {
|
|
1371
|
+
scannedLibFiles.add(libFile);
|
|
1144
1372
|
const libSrc = node_path_1.default.join(srcLibDir, libFile);
|
|
1145
1373
|
if (!node_fs_1.default.existsSync(libSrc)) {
|
|
1146
1374
|
// FAIL LOUD. Skipping here would ship hook scripts whose top-level
|
|
@@ -1153,6 +1381,8 @@ function writeCursorHooksJson(targetDir, src, opts) {
|
|
|
1153
1381
|
let libContent = node_fs_1.default.readFileSync(libSrc, 'utf8');
|
|
1154
1382
|
libContent = libContent.replace(/gsd:/gi, 'gsd-');
|
|
1155
1383
|
node_fs_1.default.writeFileSync(node_path_1.default.join(destLibDir, libFile), libContent);
|
|
1384
|
+
scanForLibRequires(libContent);
|
|
1385
|
+
libFile = [...requiredLibFiles].find((f) => !scannedLibFiles.has(f));
|
|
1156
1386
|
}
|
|
1157
1387
|
}
|
|
1158
1388
|
// #2717: write the CommonJS marker into hooks/ alongside the staged .js
|
|
@@ -2309,7 +2539,9 @@ module.exports = {
|
|
|
2309
2539
|
reconcileManagedShellHookCommands,
|
|
2310
2540
|
normalizeNodePath,
|
|
2311
2541
|
resolveNodeRunner,
|
|
2542
|
+
buildNodeRunnerChainToken,
|
|
2312
2543
|
resolveBashRunner,
|
|
2544
|
+
NODE_RUNNER_RESOLVER_HOOK,
|
|
2313
2545
|
// Atomic write seam (shared with bin/install.js so all writes participate
|
|
2314
2546
|
// in install.js's _cleanTmpFiles() scoped temp-cleanup).
|
|
2315
2547
|
atomicWriteFileSync,
|