@opengsd/gsd-core 1.14.0 → 1.15.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/README.ja-JP.md +3 -3
- package/README.ko-KR.md +3 -3
- package/README.pt-BR.md +3 -3
- package/README.zh-CN.md +3 -3
- package/agents/gsd-code-fixer.compact.md +7 -6
- package/agents/gsd-code-fixer.md +9 -8
- package/agents/gsd-debug-session-manager.compact.md +17 -2
- package/agents/gsd-debug-session-manager.md +17 -2
- package/agents/gsd-debugger.md +2 -2
- package/agents/gsd-eval-auditor.compact.md +1 -1
- package/agents/gsd-eval-auditor.md +1 -1
- package/agents/gsd-executor.md +13 -8
- package/agents/gsd-intel-updater.compact.md +1 -1
- package/agents/gsd-intel-updater.md +1 -1
- package/agents/gsd-phase-researcher.md +19 -11
- package/agents/gsd-plan-checker.md +8 -7
- package/agents/gsd-planner.md +12 -8
- package/agents/gsd-project-researcher.compact.md +1 -1
- package/agents/gsd-project-researcher.md +1 -1
- package/agents/gsd-research-synthesizer.compact.md +1 -1
- package/agents/gsd-research-synthesizer.md +1 -1
- package/agents/gsd-ui-auditor.md +155 -17
- package/agents/gsd-ui-researcher.compact.md +1 -1
- package/agents/gsd-ui-researcher.md +1 -1
- package/agents/gsd-verifier.md +10 -9
- package/bin/install.js +642 -95
- package/commands/gsd/autonomous.md +2 -2
- package/commands/gsd/capture.md +1 -1
- package/commands/gsd/mempalace-capture.md +7 -3
- package/commands/gsd/plan-review-convergence.md +6 -6
- package/commands/gsd/progress.md +1 -1
- package/commands/gsd/quick-batch.md +1 -1
- package/commands/gsd/review.md +2 -3
- package/gsd-core/bin/gsd-tools.cjs +335 -22
- package/gsd-core/bin/lib/adr-parser.cjs +3 -1
- package/gsd-core/bin/lib/audit.cjs +81 -13
- package/gsd-core/bin/lib/capability-registry.cjs +82 -187
- package/gsd-core/bin/lib/capability-validator.cjs +0 -1
- package/gsd-core/bin/lib/check-command-router.cjs +101 -14
- package/gsd-core/bin/lib/codex-agent-toml.cjs +21 -25
- package/gsd-core/bin/lib/commands.cjs +175 -42
- package/gsd-core/bin/lib/config-loader.cjs +65 -4
- package/gsd-core/bin/lib/config.cjs +33 -7
- package/gsd-core/bin/lib/decisions.cjs +30 -14
- package/gsd-core/bin/lib/frontmatter.cjs +13 -0
- package/gsd-core/bin/lib/graphify.cjs +10 -2
- package/gsd-core/bin/lib/host-runtime-detection.cjs +9 -0
- package/gsd-core/bin/lib/init.cjs +207 -41
- package/gsd-core/bin/lib/install-engine.cjs +13 -0
- package/gsd-core/bin/lib/installer-migrations.cjs +8 -1
- package/gsd-core/bin/lib/milestone.cjs +18 -5
- package/gsd-core/bin/lib/model-resolver.cjs +159 -50
- package/gsd-core/bin/lib/phase-command-router.cjs +9 -1
- package/gsd-core/bin/lib/phase-id-card.cjs +32 -0
- package/gsd-core/bin/lib/phase-id-display.cjs +78 -0
- package/gsd-core/bin/lib/phase-id.cjs +109 -7
- package/gsd-core/bin/lib/phase-locator.cjs +29 -10
- package/gsd-core/bin/lib/phase.cjs +227 -26
- package/gsd-core/bin/lib/plan-document.cjs +49 -1
- package/gsd-core/bin/lib/planning-document.cjs +459 -0
- package/gsd-core/bin/lib/planning-inspect.cjs +18 -1
- package/gsd-core/bin/lib/planning-workspace.cjs +8 -3
- package/gsd-core/bin/lib/pr-branch-patterns.cjs +57 -0
- package/gsd-core/bin/lib/probe-core.cjs +7 -1
- package/gsd-core/bin/lib/project-root.cjs +41 -2
- package/gsd-core/bin/lib/review-lane-descriptor.cjs +10 -30
- package/gsd-core/bin/lib/review-reviewer-selection.cjs +2 -2
- package/gsd-core/bin/lib/roadmap-command-router.cjs +12 -4
- package/gsd-core/bin/lib/roadmap-parser.cjs +163 -3
- package/gsd-core/bin/lib/roadmap-upgrade.cjs +1539 -13
- package/gsd-core/bin/lib/roadmap.cjs +251 -31
- package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +283 -31
- package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +3 -1
- package/gsd-core/bin/lib/runtime-homes.cjs +4 -0
- package/gsd-core/bin/lib/runtime-hooks-surface.cjs +215 -33
- package/gsd-core/bin/lib/runtime-name-policy.cjs +111 -1
- package/gsd-core/bin/lib/shell-command-projection.cjs +10 -6
- package/gsd-core/bin/lib/state-transition.cjs +39 -2
- package/gsd-core/bin/lib/state.cjs +42 -0
- package/gsd-core/bin/lib/surface.cjs +17 -1
- package/gsd-core/bin/lib/tdd-red-evidence.cjs +78 -5
- package/gsd-core/bin/lib/uat-predicate.cjs +47 -4
- package/gsd-core/bin/lib/uat.cjs +8 -0
- package/gsd-core/bin/lib/ui-consideration-probe.cjs +15 -2
- package/gsd-core/bin/lib/ui-frontend-evidence.cjs +100 -9
- package/gsd-core/bin/lib/undo-commit-selection.cjs +131 -0
- package/gsd-core/bin/lib/verification.cjs +268 -15
- package/gsd-core/bin/lib/verify-command-grounding.cjs +46 -2
- package/gsd-core/bin/lib/verify.cjs +132 -25
- package/gsd-core/bin/lib/worktree-base-ref.cjs +482 -73
- package/gsd-core/bin/lib/worktree-safety.cjs +784 -51
- package/gsd-core/bin/shared/config-defaults.manifest.json +3 -0
- package/gsd-core/bin/shared/config-schema.manifest.json +1 -0
- package/gsd-core/references/checkpoints.md +5 -3
- package/gsd-core/references/edge-probe-fixtures/01-round-half-even/expected-coverage.json +28 -3
- package/gsd-core/references/edge-probe-fixtures/02-merge-intervals/expected-coverage.json +37 -4
- package/gsd-core/references/edge-probe-fixtures/03-truncate-graphemes/expected-coverage.json +28 -3
- package/gsd-core/references/edge-probe-fixtures/04-money-rounding/expected-coverage.json +28 -3
- package/gsd-core/references/edge-probe-fixtures/05-list-dedupe/expected-coverage.json +37 -4
- package/gsd-core/references/edge-probe-fixtures/06-resolved-mixed/expected-coverage.json +37 -4
- package/gsd-core/references/edge-probe.md +195 -21
- package/gsd-core/references/execute-phase-between-wave-reset.md +7 -6
- package/gsd-core/references/execute-phase-wave-guard.md +22 -11
- package/gsd-core/references/gsd-run-resolver.md +1 -1
- package/gsd-core/references/model-profiles.md +1 -1
- package/gsd-core/references/phase-argument-parsing.md +9 -7
- package/gsd-core/references/phase-id-convention.md +28 -0
- package/gsd-core/references/planner-gap-closure.md +2 -0
- package/gsd-core/references/planner-load-graph-context.md +24 -13
- package/gsd-core/references/planner-verify-command-grounding.md +14 -0
- package/gsd-core/references/planning-config.md +11 -2
- package/gsd-core/references/tdd.md +27 -4
- package/gsd-core/references/ui-consideration-probe.md +10 -5
- package/gsd-core/references/verify-command-path-resolvability.md +10 -2
- package/gsd-core/references/worktree-path-safety.md +321 -0
- package/gsd-core/templates/verification-report.md +1 -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 +1 -1
- package/gsd-core/workflows/add-tests.md +2 -2
- package/gsd-core/workflows/add-todo.md +3 -3
- package/gsd-core/workflows/ai-integration-phase.md +11 -3
- package/gsd-core/workflows/audit-fix.md +1 -1
- package/gsd-core/workflows/audit-milestone.md +1 -1
- package/gsd-core/workflows/audit-uat.md +1 -1
- package/gsd-core/workflows/autonomous/steps/converge-fail-fast.md +9 -18
- package/gsd-core/workflows/autonomous.md +16 -6
- package/gsd-core/workflows/check-todos.md +2 -2
- package/gsd-core/workflows/cleanup.md +2 -2
- package/gsd-core/workflows/code-review/steps/dispatch-fix.md +4 -3
- package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +1 -1
- package/gsd-core/workflows/code-review-fix.md +108 -22
- package/gsd-core/workflows/code-review.md +63 -46
- package/gsd-core/workflows/complete-milestone/detail/elaboration.md +1 -1
- package/gsd-core/workflows/complete-milestone.md +2 -2
- package/gsd-core/workflows/debug.md +3 -3
- package/gsd-core/workflows/diagnose-issues.md +1 -1
- package/gsd-core/workflows/discuss-phase/modes/advisor.md +1 -1
- package/gsd-core/workflows/discuss-phase/modes/chain.md +1 -1
- package/gsd-core/workflows/discuss-phase-assumptions.md +1 -1
- package/gsd-core/workflows/discuss-phase.md +1 -1
- package/gsd-core/workflows/do.md +2 -2
- package/gsd-core/workflows/docs-update.md +3 -3
- package/gsd-core/workflows/edit-phase.md +1 -1
- package/gsd-core/workflows/eval-review.md +10 -3
- package/gsd-core/workflows/execute-phase/detail/elaboration.md +2 -2
- package/gsd-core/workflows/execute-phase/steps/code-review-disposition.md +1017 -0
- package/gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/completion-reconciliation.md +3 -3
- package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +37 -3
- 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 +1 -1
- package/gsd-core/workflows/execute-phase/steps/post-merge-gate.md +46 -9
- package/gsd-core/workflows/execute-phase/steps/protected-branch.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/ready-wave-gate.md +37 -0
- package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/stale-reverification.md +24 -0
- package/gsd-core/workflows/execute-phase/steps/tdd-applicability-resolution.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/threat-id-gate.md +28 -0
- package/gsd-core/workflows/execute-phase/steps/wave-post-gate-hooks.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/worktree-base-check.md +25 -0
- package/gsd-core/workflows/execute-phase.md +36 -26
- package/gsd-core/workflows/execute-plan.md +5 -4
- package/gsd-core/workflows/explore.md +3 -3
- package/gsd-core/workflows/extract-learnings.md +2 -1
- package/gsd-core/workflows/fast.md +1 -1
- package/gsd-core/workflows/forensics.md +1 -1
- package/gsd-core/workflows/graduation.md +1 -1
- package/gsd-core/workflows/health.md +2 -2
- package/gsd-core/workflows/help/modes/full.compact.md +3 -3
- package/gsd-core/workflows/help/modes/full.md +5 -5
- package/gsd-core/workflows/help/modes/topic.md +15 -5
- package/gsd-core/workflows/import.md +2 -2
- package/gsd-core/workflows/inbox.md +2 -2
- package/gsd-core/workflows/ingest-docs.md +3 -3
- package/gsd-core/workflows/insert-phase.md +1 -1
- package/gsd-core/workflows/list-seeds.md +1 -1
- package/gsd-core/workflows/list-workspaces.md +1 -1
- package/gsd-core/workflows/manager.md +2 -2
- package/gsd-core/workflows/map-codebase.md +2 -2
- package/gsd-core/workflows/milestone-summary.md +1 -1
- package/gsd-core/workflows/mvp-phase.md +1 -1
- package/gsd-core/workflows/new-milestone.md +2 -2
- package/gsd-core/workflows/new-project/steps/auto-mode-config.md +3 -3
- package/gsd-core/workflows/new-project/steps/codebase-map-offer.md +1 -1
- package/gsd-core/workflows/new-project.md +7 -7
- package/gsd-core/workflows/new-workspace.md +2 -2
- package/gsd-core/workflows/next.md +1 -1
- package/gsd-core/workflows/note.md +1 -1
- package/gsd-core/workflows/onboard.md +1 -1
- package/gsd-core/workflows/pause-work.md +1 -1
- package/gsd-core/workflows/plan-phase/detail/elaboration.md +1 -1
- package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +17 -5
- package/gsd-core/workflows/plan-phase/steps/prd-express-path.md +1 -1
- package/gsd-core/workflows/plan-phase/steps/stall-detection-helpers.md +23 -5
- package/gsd-core/workflows/plan-phase.md +24 -7
- package/gsd-core/workflows/plan-review-convergence.md +21 -5
- package/gsd-core/workflows/plant-seed.md +62 -20
- package/gsd-core/workflows/pr-branch.md +113 -13
- package/gsd-core/workflows/profile-user.md +2 -2
- package/gsd-core/workflows/progress.md +1 -1
- package/gsd-core/workflows/quick/steps/plan-checker-loop.md +25 -0
- package/gsd-core/workflows/quick/steps/quick-verification.md +1 -1
- package/gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md +1 -1
- package/gsd-core/workflows/quick-batch/steps/batch-init.md +1 -1
- package/gsd-core/workflows/quick-batch/steps/completion.md +1 -1
- package/gsd-core/workflows/quick-batch/steps/merge-wave.md +1 -1
- package/gsd-core/workflows/quick-batch/steps/planner-wave.md +1 -1
- package/gsd-core/workflows/quick-batch/steps/research-phase.md +1 -1
- package/gsd-core/workflows/quick-batch/steps/resume-mode.md +1 -1
- package/gsd-core/workflows/quick-batch/steps/verification-wave.md +1 -1
- package/gsd-core/workflows/quick-batch/steps/worktree-dispatch.md +1 -1
- package/gsd-core/workflows/quick-batch.md +1 -1
- package/gsd-core/workflows/quick.md +21 -9
- package/gsd-core/workflows/reapply-patches.md +9 -3
- package/gsd-core/workflows/remove-phase.md +1 -1
- package/gsd-core/workflows/remove-workspace.md +2 -2
- package/gsd-core/workflows/resume-project.md +1 -1
- package/gsd-core/workflows/review.md +31 -16
- package/gsd-core/workflows/scan.md +1 -1
- package/gsd-core/workflows/secure-phase.md +3 -2
- package/gsd-core/workflows/settings-advanced.md +30 -10
- package/gsd-core/workflows/settings-integrations.md +2 -3
- package/gsd-core/workflows/settings.md +4 -4
- package/gsd-core/workflows/ship.md +3 -2
- package/gsd-core/workflows/sketch-wrap-up.md +1 -1
- package/gsd-core/workflows/sketch.md +1 -1
- package/gsd-core/workflows/smart-entry.md +2 -2
- package/gsd-core/workflows/spec-phase.md +15 -5
- package/gsd-core/workflows/spike-wrap-up.md +1 -1
- package/gsd-core/workflows/spike.md +1 -1
- package/gsd-core/workflows/stats.md +1 -1
- package/gsd-core/workflows/sync-skills.md +5 -5
- package/gsd-core/workflows/thread.md +1 -1
- package/gsd-core/workflows/transition.md +1 -1
- package/gsd-core/workflows/ui-phase.md +44 -8
- package/gsd-core/workflows/ui-review.md +18 -4
- package/gsd-core/workflows/ultraplan-phase.md +1 -1
- package/gsd-core/workflows/undo.md +339 -20
- package/gsd-core/workflows/update.md +7 -7
- package/gsd-core/workflows/validate-phase.md +3 -2
- package/gsd-core/workflows/verify-work/steps/automated-ui-verification.md +1 -1
- package/gsd-core/workflows/verify-work/steps/mvp-uat-framing.md +1 -1
- package/gsd-core/workflows/verify-work.md +81 -16
- package/hooks/dist/gsd-agent-isolation-guard.js +24 -0
- package/hooks/dist/gsd-secret-read-guard.js +27 -1
- package/hooks/dist/gsd-statusline.js +70 -13
- package/hooks/dist/gsd-validate-commit.sh +63 -4
- package/hooks/gsd-agent-isolation-guard.js +24 -0
- package/hooks/gsd-secret-read-guard.js +27 -1
- package/hooks/gsd-statusline.js +70 -13
- package/hooks/gsd-validate-commit.sh +63 -4
- package/package.json +3 -2
- package/scripts/build-hooks.js +15 -6
- package/scripts/check-contract-drift.cjs +127 -11
- package/scripts/command-contract-helpers.cjs +3 -0
- package/scripts/docs-guard-registry.cjs +28 -0
- package/scripts/gen-loop-host-contract.cjs +69 -0
- package/scripts/lib/macos-conformance-tier.generated.cjs +14 -0
- package/scripts/lib/ndjson-reporter.cjs +3 -2
- package/scripts/lib/platform-conformance-tier.generated.cjs +11 -0
- package/scripts/lint-docs-guard-registration.exempt-baseline.cjs +28 -1
- package/scripts/lint-phase-arg-assignment.cjs +257 -0
- package/scripts/lint-phase-id-drift.cjs +290 -5
- package/scripts/lint-pr-branch-pattern-drift.cjs +148 -0
- package/scripts/lint-retired-runtime-name.cjs +619 -0
- package/scripts/lint-state-write-path-drift.cjs +93 -0
- package/scripts/lint-test-file-count.allowlist.json +28 -9
- package/scripts/lint-workflow-shellcheck-baseline.json +15 -0
- package/scripts/prompt-injection-scan.sh +4 -0
- package/scripts/release-tarball-smoke.cjs +194 -1
- package/skills/gsd-autonomous/SKILL.md +2 -2
- package/skills/gsd-capture/SKILL.md +1 -1
- package/skills/gsd-mempalace-capture/SKILL.md +7 -3
- package/skills/gsd-plan-review-convergence/SKILL.md +5 -5
- package/skills/gsd-progress/SKILL.md +1 -1
- package/skills/gsd-quick-batch/SKILL.md +1 -1
- package/skills/gsd-review/SKILL.md +2 -3
- package/vscode/package.json +1 -1
|
@@ -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, escapePosixDoubleQuoted, } = shellCmdProjection;
|
|
41
|
+
const { isManagedHookBasename, isManagedHookCommand, projectLegacySettingsHookCommand, projectManagedHookCommand, projectPortableHookBaseDir, projectCodexHookTomlCommand, shellHookOmitsBashRunner, escapeTomlDoubleQuotedString, escapePosixDoubleQuoted, resolveExecutableBinary, } = shellCmdProjection;
|
|
42
42
|
// ---------------------------------------------------------------------------
|
|
43
43
|
// Terminal color constants (mirrors install.js for console output parity)
|
|
44
44
|
// ---------------------------------------------------------------------------
|
|
@@ -576,7 +576,7 @@ function buildBakedNodeToken(opts) {
|
|
|
576
576
|
* guarantee), ahead of `command -v node` and the well-known fallback list.
|
|
577
577
|
*/
|
|
578
578
|
const NODE_RUNNER_RESOLVER_HOOK = 'gsd-node-runner.sh';
|
|
579
|
-
function
|
|
579
|
+
function resolveBashExecutable(opts) {
|
|
580
580
|
const platform = (opts && opts.platform) || process.platform;
|
|
581
581
|
if (platform !== 'win32')
|
|
582
582
|
return 'bash';
|
|
@@ -594,12 +594,19 @@ function resolveBashRunner(opts) {
|
|
|
594
594
|
candidates.push(node_path_1.default.win32.join(env.SystemDrive, 'Program Files (x86)', 'Git', 'bin', 'bash.exe'));
|
|
595
595
|
}
|
|
596
596
|
for (const candidate of candidates) {
|
|
597
|
-
if (candidate && exists(candidate))
|
|
598
|
-
return
|
|
599
|
-
}
|
|
597
|
+
if (candidate && exists(candidate))
|
|
598
|
+
return shellCmdProjection.posixNormalize(candidate);
|
|
600
599
|
}
|
|
601
600
|
return null;
|
|
602
601
|
}
|
|
602
|
+
function resolveBashRunner(opts) {
|
|
603
|
+
const executable = resolveBashExecutable(opts);
|
|
604
|
+
if (executable === null)
|
|
605
|
+
return null;
|
|
606
|
+
return ((opts && opts.platform) || process.platform) === 'win32'
|
|
607
|
+
? JSON.stringify(executable)
|
|
608
|
+
: executable;
|
|
609
|
+
}
|
|
603
610
|
// #3662 — recognize the two runtime-resolving command shapes the installer
|
|
604
611
|
// emits, so the rewriter never churns (or un-does) an entry that already
|
|
605
612
|
// works in every environment sharing the config root.
|
|
@@ -961,16 +968,18 @@ function reconcileCodexHooksJsonEvent(targetDir, eventName, opts = {}) {
|
|
|
961
968
|
function reconcileCodexHooksJsonSessionStart(targetDir, opts = {}) {
|
|
962
969
|
return reconcileCodexHooksJsonEvent(targetDir, 'SessionStart', opts);
|
|
963
970
|
}
|
|
964
|
-
function
|
|
965
|
-
if (!absoluteRunnerToken)
|
|
966
|
-
return null;
|
|
967
|
-
let interpreter;
|
|
971
|
+
function parseAbsoluteRunnerToken(absoluteRunnerToken) {
|
|
968
972
|
try {
|
|
969
|
-
|
|
973
|
+
return JSON.parse(absoluteRunnerToken);
|
|
970
974
|
}
|
|
971
975
|
catch {
|
|
972
|
-
|
|
976
|
+
return absoluteRunnerToken;
|
|
973
977
|
}
|
|
978
|
+
}
|
|
979
|
+
function buildCodexHookWindowsShimIR(scriptAbsPath, absoluteRunnerToken) {
|
|
980
|
+
if (!absoluteRunnerToken)
|
|
981
|
+
return null;
|
|
982
|
+
const interpreter = parseAbsoluteRunnerToken(absoluteRunnerToken);
|
|
974
983
|
const targetAbs = shellCmdProjection.posixNormalize(scriptAbsPath);
|
|
975
984
|
const scriptQuoted = JSON.stringify(targetAbs);
|
|
976
985
|
const cmdPath = scriptAbsPath.replace(/\.js$/, '.cmd');
|
|
@@ -995,6 +1004,7 @@ function ensureCodexHooksJsonSessionStart(targetDir, opts = {}) {
|
|
|
995
1004
|
return { changed: false, wrote: false, path: hooksJsonPath };
|
|
996
1005
|
const scriptPath = shellCmdProjection.posixNormalize(node_path_1.default.resolve(targetDir, 'hooks', 'gsd-check-update.js'));
|
|
997
1006
|
const cmdShimPath = scriptPath.replace(/\.js$/, '.cmd');
|
|
1007
|
+
const configuredEntrypoints = [];
|
|
998
1008
|
let managedCommand;
|
|
999
1009
|
if (platform === 'win32') {
|
|
1000
1010
|
const shimIR = buildCodexHookWindowsShimIR(scriptPath, absoluteRunner);
|
|
@@ -1011,6 +1021,7 @@ function ensureCodexHooksJsonSessionStart(targetDir, opts = {}) {
|
|
|
1011
1021
|
return { changed: false, wrote: false, path: hooksJsonPath };
|
|
1012
1022
|
}
|
|
1013
1023
|
managedCommand = shimIR.hookCommand;
|
|
1024
|
+
configuredEntrypoints.push({ runtime: 'codex', configPath: hooksJsonPath, scriptPath: shimIR.cmdPath, platform, selfExecutable: true }, { runtime: 'codex', configPath: hooksJsonPath, scriptPath, interpreterCandidates: [parseAbsoluteRunnerToken(absoluteRunner)], platform });
|
|
1014
1025
|
}
|
|
1015
1026
|
else {
|
|
1016
1027
|
managedCommand = projectManagedHookCommand({
|
|
@@ -1019,13 +1030,23 @@ function ensureCodexHooksJsonSessionStart(targetDir, opts = {}) {
|
|
|
1019
1030
|
runtime: 'codex',
|
|
1020
1031
|
platform,
|
|
1021
1032
|
}) ?? undefined;
|
|
1033
|
+
if (managedCommand) {
|
|
1034
|
+
configuredEntrypoints.push({
|
|
1035
|
+
runtime: 'codex',
|
|
1036
|
+
configPath: hooksJsonPath,
|
|
1037
|
+
scriptPath,
|
|
1038
|
+
interpreterCandidates: [parseAbsoluteRunnerToken(absoluteRunner)],
|
|
1039
|
+
platform,
|
|
1040
|
+
});
|
|
1041
|
+
}
|
|
1022
1042
|
}
|
|
1023
1043
|
if (!managedCommand)
|
|
1024
1044
|
return { changed: false, wrote: false, path: hooksJsonPath };
|
|
1025
1045
|
const commandWindows = platform === 'win32'
|
|
1026
1046
|
? JSON.stringify(shellCmdProjection.posixNormalize(cmdShimPath))
|
|
1027
1047
|
: undefined;
|
|
1028
|
-
|
|
1048
|
+
const result = reconcileCodexHooksJsonSessionStart(targetDir, { managedCommand, commandWindows });
|
|
1049
|
+
return { ...result, configuredEntrypoints };
|
|
1029
1050
|
}
|
|
1030
1051
|
function ensureCodexHooksJsonEvent(targetDir, eventName, opts = {}) {
|
|
1031
1052
|
const platform = opts.platform || process.platform;
|
|
@@ -1201,6 +1222,56 @@ function cleanupOrphanedCodexContextMonitorScript(targetDir) {
|
|
|
1201
1222
|
}
|
|
1202
1223
|
return result;
|
|
1203
1224
|
}
|
|
1225
|
+
function configuredEntrypointsForHook(configDir, hookName, opts) {
|
|
1226
|
+
const platform = opts.platform || process.platform;
|
|
1227
|
+
const runtime = opts.runtime || 'generic';
|
|
1228
|
+
const configPath = opts.configPath || configDir;
|
|
1229
|
+
const target = {
|
|
1230
|
+
runtime,
|
|
1231
|
+
configPath,
|
|
1232
|
+
scriptPath: node_path_1.default.join(configDir, 'hooks', hookName),
|
|
1233
|
+
platform,
|
|
1234
|
+
};
|
|
1235
|
+
const isShellHook = hookName.endsWith('.sh');
|
|
1236
|
+
if (shellHookOmitsBashRunner({ platform, runtime, isShellHook }))
|
|
1237
|
+
return [{ ...target, selfExecutable: true }];
|
|
1238
|
+
const bash = resolveBashExecutable(opts);
|
|
1239
|
+
if (isShellHook) {
|
|
1240
|
+
// An unresolved bash must still surface as an interpreterCandidates entry
|
|
1241
|
+
// (the literal token, same as the portableHooks runner below) so
|
|
1242
|
+
// validateConfiguredEntrypoints reports 'unresolved-interpreter' instead
|
|
1243
|
+
// of silently skipping the check because the field is absent.
|
|
1244
|
+
return [{ ...target, interpreterCandidates: [bash === null ? 'bash' : bash] }];
|
|
1245
|
+
}
|
|
1246
|
+
// #4249: check the SAME stable alias buildNodeRunnerChainToken bakes as its
|
|
1247
|
+
// first candidate (normalizeNodePath rewrites a version-manager shim like
|
|
1248
|
+
// fnm/nvm/mise/volta into its persistent path), not the raw, currently-
|
|
1249
|
+
// running process.execPath — which always trivially resolves regardless of
|
|
1250
|
+
// whether the alias actually baked into the persisted command still does.
|
|
1251
|
+
const nodeCandidates = [
|
|
1252
|
+
normalizeNodePath(opts.execPath || process.execPath, opts),
|
|
1253
|
+
'node',
|
|
1254
|
+
'/usr/local/bin/node',
|
|
1255
|
+
'/usr/bin/node',
|
|
1256
|
+
].filter((candidate) => Boolean(candidate));
|
|
1257
|
+
if (!opts.portableHooks) {
|
|
1258
|
+
return [{ ...target, interpreterCandidates: nodeCandidates }];
|
|
1259
|
+
}
|
|
1260
|
+
const runner = {
|
|
1261
|
+
runtime,
|
|
1262
|
+
configPath,
|
|
1263
|
+
scriptPath: node_path_1.default.join(configDir, 'hooks', NODE_RUNNER_RESOLVER_HOOK),
|
|
1264
|
+
interpreterCandidates: bash === null ? ['bash'] : [bash],
|
|
1265
|
+
platform,
|
|
1266
|
+
};
|
|
1267
|
+
return [runner, { ...target, interpreterCandidates: nodeCandidates }];
|
|
1268
|
+
}
|
|
1269
|
+
function recordConfiguredHookCommand(command, configDir, hookName, opts) {
|
|
1270
|
+
if (command && opts.configuredEntrypoints) {
|
|
1271
|
+
opts.configuredEntrypoints.push(...configuredEntrypointsForHook(configDir, hookName, opts).map(entry => ({ ...entry, command })));
|
|
1272
|
+
}
|
|
1273
|
+
return command;
|
|
1274
|
+
}
|
|
1204
1275
|
function buildHookCommand(configDir, hookName, opts) {
|
|
1205
1276
|
if (!opts)
|
|
1206
1277
|
opts = {};
|
|
@@ -1208,44 +1279,56 @@ function buildHookCommand(configDir, hookName, opts) {
|
|
|
1208
1279
|
const runtime = opts.runtime || 'generic';
|
|
1209
1280
|
const hookShell = opts.hookShell;
|
|
1210
1281
|
const isShellHook = hookName.endsWith('.sh');
|
|
1282
|
+
const track = (command) => recordConfiguredHookCommand(command, configDir, hookName, opts);
|
|
1211
1283
|
if (shellHookOmitsBashRunner({ platform, runtime, isShellHook })) {
|
|
1212
1284
|
if (opts.portableHooks) {
|
|
1213
1285
|
const portableBaseDir = projectPortableHookBaseDir({
|
|
1214
1286
|
configDir,
|
|
1215
1287
|
homeDir: node_os_1.default.homedir(),
|
|
1216
1288
|
});
|
|
1217
|
-
return JSON.stringify(`${portableBaseDir}/hooks/${hookName}`);
|
|
1289
|
+
return track(JSON.stringify(`${portableBaseDir}/hooks/${hookName}`));
|
|
1218
1290
|
}
|
|
1219
|
-
return JSON.stringify(shellCmdProjection.posixNormalize(configDir) + '/hooks/' + hookName);
|
|
1291
|
+
return track(JSON.stringify(shellCmdProjection.posixNormalize(configDir) + '/hooks/' + hookName));
|
|
1220
1292
|
}
|
|
1221
1293
|
// .sh hooks keep the pre-#3662 shape everywhere: the bash runner resolves
|
|
1222
1294
|
// at install time like today, and `bash` itself is a PATH-stable binary
|
|
1223
1295
|
// (the absolute Git-Bash discovery covers win32 — #580/#3393).
|
|
1224
1296
|
if (isShellHook) {
|
|
1225
1297
|
const runner = resolveBashRunner(opts);
|
|
1226
|
-
if (runner === null)
|
|
1298
|
+
if (runner === null) {
|
|
1299
|
+
// #4249 (antigravity review): this early return skips `track()` below,
|
|
1300
|
+
// so an unresolved bash on win32 (no Git Bash found) previously left
|
|
1301
|
+
// this hook silently unregistered with nothing for
|
|
1302
|
+
// validateConfiguredEntrypoints to reject — configuredEntrypointsForHook's
|
|
1303
|
+
// own 'unresolved bash must still surface' comment describes intent this
|
|
1304
|
+
// return never reached. Push the entry directly (no `command`, since
|
|
1305
|
+
// none was ever built) so the gate actually sees it.
|
|
1306
|
+
if (opts.configuredEntrypoints) {
|
|
1307
|
+
opts.configuredEntrypoints.push(...configuredEntrypointsForHook(configDir, hookName, opts));
|
|
1308
|
+
}
|
|
1227
1309
|
return null;
|
|
1310
|
+
}
|
|
1228
1311
|
if (opts.portableHooks) {
|
|
1229
1312
|
const portableBaseDir = projectPortableHookBaseDir({
|
|
1230
1313
|
configDir,
|
|
1231
1314
|
homeDir: node_os_1.default.homedir(),
|
|
1232
1315
|
});
|
|
1233
|
-
return projectManagedHookCommand({
|
|
1316
|
+
return track(projectManagedHookCommand({
|
|
1234
1317
|
absoluteRunner: runner,
|
|
1235
1318
|
scriptPath: `${portableBaseDir}/hooks/${hookName}`,
|
|
1236
1319
|
runtime: opts.runtime || 'generic',
|
|
1237
1320
|
platform,
|
|
1238
1321
|
hookShell,
|
|
1239
|
-
});
|
|
1322
|
+
}));
|
|
1240
1323
|
}
|
|
1241
1324
|
const hooksPath = shellCmdProjection.posixNormalize(configDir) + '/hooks/' + hookName;
|
|
1242
|
-
return projectManagedHookCommand({
|
|
1325
|
+
return track(projectManagedHookCommand({
|
|
1243
1326
|
absoluteRunner: runner,
|
|
1244
1327
|
scriptPath: hooksPath,
|
|
1245
1328
|
runtime,
|
|
1246
1329
|
platform,
|
|
1247
1330
|
hookShell,
|
|
1248
|
-
});
|
|
1331
|
+
}));
|
|
1249
1332
|
}
|
|
1250
1333
|
// JS hooks (#3662): the node runner is resolved at hook-fire time, never
|
|
1251
1334
|
// baked as a bare absolute path — an install-environment absolute path is
|
|
@@ -1268,7 +1351,7 @@ function buildHookCommand(configDir, hookName, opts) {
|
|
|
1268
1351
|
// Absolute Git-Bash discovery on win32 when available (#580); `bash` on
|
|
1269
1352
|
// PATH otherwise — the same assumption .sh hooks already make.
|
|
1270
1353
|
const resolverRunner = resolveBashRunner(opts) || 'bash';
|
|
1271
|
-
return shellCmdProjection.projectShellCommandText({
|
|
1354
|
+
return track(shellCmdProjection.projectShellCommandText({
|
|
1272
1355
|
runnerToken: resolverRunner,
|
|
1273
1356
|
argTokens: [
|
|
1274
1357
|
JSON.stringify(`${portableBaseDir}/hooks/${NODE_RUNNER_RESOLVER_HOOK}`),
|
|
@@ -1278,19 +1361,19 @@ function buildHookCommand(configDir, hookName, opts) {
|
|
|
1278
1361
|
runtime,
|
|
1279
1362
|
platform,
|
|
1280
1363
|
hookShell,
|
|
1281
|
-
});
|
|
1364
|
+
}));
|
|
1282
1365
|
}
|
|
1283
1366
|
const chainRunner = buildNodeRunnerChainToken(opts);
|
|
1284
1367
|
if (chainRunner === null)
|
|
1285
1368
|
return null;
|
|
1286
1369
|
const hooksPath = shellCmdProjection.posixNormalize(configDir) + '/hooks/' + hookName;
|
|
1287
|
-
return shellCmdProjection.projectShellCommandText({
|
|
1370
|
+
return track(shellCmdProjection.projectShellCommandText({
|
|
1288
1371
|
runnerToken: chainRunner,
|
|
1289
1372
|
argTokens: [JSON.stringify(hooksPath)],
|
|
1290
1373
|
runtime,
|
|
1291
1374
|
platform,
|
|
1292
1375
|
hookShell,
|
|
1293
|
-
});
|
|
1376
|
+
}));
|
|
1294
1377
|
}
|
|
1295
1378
|
// ---------------------------------------------------------------------------
|
|
1296
1379
|
// Cline helpers
|
|
@@ -1391,6 +1474,7 @@ function mergeGsdAgentsMd(filePath, gsdContent) {
|
|
|
1391
1474
|
// ---------------------------------------------------------------------------
|
|
1392
1475
|
function writeClineArtifacts(targetDir, isGlobalInstall) {
|
|
1393
1476
|
const written = [];
|
|
1477
|
+
const configuredEntrypoints = [];
|
|
1394
1478
|
const clinerulesDir = node_path_1.default.join(targetDir, '.clinerules');
|
|
1395
1479
|
try {
|
|
1396
1480
|
if (node_fs_1.default.existsSync(clinerulesDir)) {
|
|
@@ -1416,6 +1500,20 @@ function writeClineArtifacts(targetDir, isGlobalInstall) {
|
|
|
1416
1500
|
catch { /* Windows: hooks unsupported anyway */ }
|
|
1417
1501
|
written.push('.clinerules/hooks/PreToolUse');
|
|
1418
1502
|
console.log(` ${green}✓${reset} Wrote .clinerules/hooks/PreToolUse`);
|
|
1503
|
+
// #4249 (CodeRabbit): Cline invokes this file directly via its own
|
|
1504
|
+
// `#!/usr/bin/env node` shebang — a hybrid case. The script itself still
|
|
1505
|
+
// needs the execute bit (selfExecutable), but unlike GSD's other JS hooks
|
|
1506
|
+
// (which bake an absolute, install-time-resolved node path specifically to
|
|
1507
|
+
// avoid this) its interpreter is looked up on PATH by `env` at hook-fire
|
|
1508
|
+
// time, so `node` must also resolve or the hook can never run.
|
|
1509
|
+
configuredEntrypoints.push({
|
|
1510
|
+
runtime: 'cline',
|
|
1511
|
+
configPath: hookPath,
|
|
1512
|
+
scriptPath: hookPath,
|
|
1513
|
+
interpreterCandidates: ['node'],
|
|
1514
|
+
selfExecutable: true,
|
|
1515
|
+
platform: process.platform,
|
|
1516
|
+
});
|
|
1419
1517
|
if (isGlobalInstall) {
|
|
1420
1518
|
try {
|
|
1421
1519
|
const agentsPath = node_path_1.default.join(node_os_1.default.homedir(), '.agents', 'AGENTS.md');
|
|
@@ -1426,7 +1524,7 @@ function writeClineArtifacts(targetDir, isGlobalInstall) {
|
|
|
1426
1524
|
console.warn(` ${yellow}⚠${reset} Could not write ~/.agents/AGENTS.md: ${err.message}`);
|
|
1427
1525
|
}
|
|
1428
1526
|
}
|
|
1429
|
-
return written;
|
|
1527
|
+
return { written, configuredEntrypoints };
|
|
1430
1528
|
}
|
|
1431
1529
|
// ---------------------------------------------------------------------------
|
|
1432
1530
|
// Cursor hook functions
|
|
@@ -1677,7 +1775,13 @@ function writeCursorHooksJson(targetDir, src, opts) {
|
|
|
1677
1775
|
if (installedScripts.size > 0) {
|
|
1678
1776
|
ensureCommonJsMarker(hooksDir);
|
|
1679
1777
|
}
|
|
1680
|
-
const
|
|
1778
|
+
const configuredEntrypoints = [];
|
|
1779
|
+
const hookOpts = {
|
|
1780
|
+
runtime: 'cursor',
|
|
1781
|
+
platform: opts.platform || process.platform,
|
|
1782
|
+
configPath: node_path_1.default.join(targetDir, 'hooks.json'),
|
|
1783
|
+
configuredEntrypoints,
|
|
1784
|
+
};
|
|
1681
1785
|
const commands = {};
|
|
1682
1786
|
for (const ev of events) {
|
|
1683
1787
|
const script = imperative_hook_bus_cjs_1.CURSOR_EVENT_SCRIPT_MAP[ev];
|
|
@@ -1691,7 +1795,7 @@ function writeCursorHooksJson(targetDir, src, opts) {
|
|
|
1691
1795
|
const managedEntries = (0, imperative_hook_bus_cjs_1.buildHookBusEntries)(events, commands);
|
|
1692
1796
|
const hooksJsonPath = node_path_1.default.join(targetDir, 'hooks.json');
|
|
1693
1797
|
const result = reconcileCursorHooksJson(hooksJsonPath, managedEntries);
|
|
1694
|
-
return { hooksJsonPath, changed: result.changed };
|
|
1798
|
+
return { hooksJsonPath, changed: result.changed, configuredEntrypoints };
|
|
1695
1799
|
}
|
|
1696
1800
|
function removeCursorHooksJson(targetDir) {
|
|
1697
1801
|
const hooksJsonPath = node_path_1.default.join(targetDir, 'hooks.json');
|
|
@@ -1896,7 +2000,13 @@ function writeWindsurfHooksJson(targetDir, src, opts) {
|
|
|
1896
2000
|
if (installedScripts.size > 0) {
|
|
1897
2001
|
ensureCommonJsMarker(hooksDir);
|
|
1898
2002
|
}
|
|
1899
|
-
const
|
|
2003
|
+
const configuredEntrypoints = [];
|
|
2004
|
+
const hookOpts = {
|
|
2005
|
+
runtime: 'windsurf',
|
|
2006
|
+
platform: opts.platform || process.platform,
|
|
2007
|
+
configPath: node_path_1.default.join(targetDir, 'hooks.json'),
|
|
2008
|
+
configuredEntrypoints,
|
|
2009
|
+
};
|
|
1900
2010
|
const commands = {};
|
|
1901
2011
|
for (const ev of WINDSURF_HOOK_EVENTS) {
|
|
1902
2012
|
const script = WINDSURF_EVENT_SCRIPT_MAP[ev];
|
|
@@ -1910,7 +2020,7 @@ function writeWindsurfHooksJson(targetDir, src, opts) {
|
|
|
1910
2020
|
}
|
|
1911
2021
|
const hooksJsonPath = node_path_1.default.join(targetDir, 'hooks.json');
|
|
1912
2022
|
const result = reconcileWindsurfHooksJson(hooksJsonPath, managedEntries);
|
|
1913
|
-
return { hooksJsonPath, changed: result.changed };
|
|
2023
|
+
return { hooksJsonPath, changed: result.changed, configuredEntrypoints };
|
|
1914
2024
|
}
|
|
1915
2025
|
/**
|
|
1916
2026
|
* Remove all GSD-managed Cascade hook entries from hooks.json. User-owned
|
|
@@ -2761,13 +2871,21 @@ function stripKimiHooksTomlBlock(content) {
|
|
|
2761
2871
|
* touch the file's mtime for no reason.
|
|
2762
2872
|
*/
|
|
2763
2873
|
function writeKimiHooksToml(configPath, targetDir, opts) {
|
|
2874
|
+
const configuredEntrypoints = [];
|
|
2875
|
+
const trackedOpts = {
|
|
2876
|
+
hookOpts: {
|
|
2877
|
+
...opts.hookOpts,
|
|
2878
|
+
configPath,
|
|
2879
|
+
configuredEntrypoints,
|
|
2880
|
+
},
|
|
2881
|
+
};
|
|
2764
2882
|
const existing = node_fs_1.default.existsSync(configPath) ? node_fs_1.default.readFileSync(configPath, 'utf8') : '';
|
|
2765
2883
|
const stripped = stripKimiHooksTomlBlock(existing) ?? '';
|
|
2766
|
-
const block = buildKimiHooksTomlBlock(targetDir,
|
|
2884
|
+
const block = buildKimiHooksTomlBlock(targetDir, trackedOpts);
|
|
2767
2885
|
const entryCount = block ? (block.match(/\[\[hooks\]\]/g) || []).length : 0;
|
|
2768
2886
|
if (!block) {
|
|
2769
2887
|
if (stripped === existing)
|
|
2770
|
-
return { changed: false, path: configPath, entryCount: 0 };
|
|
2888
|
+
return { changed: false, path: configPath, entryCount: 0, configuredEntrypoints };
|
|
2771
2889
|
if (stripped.trim() === '') {
|
|
2772
2890
|
if (node_fs_1.default.existsSync(configPath))
|
|
2773
2891
|
node_fs_1.default.unlinkSync(configPath);
|
|
@@ -2776,15 +2894,15 @@ function writeKimiHooksToml(configPath, targetDir, opts) {
|
|
|
2776
2894
|
node_fs_1.default.mkdirSync(node_path_1.default.dirname(configPath), { recursive: true });
|
|
2777
2895
|
atomicWriteFileSync(configPath, stripped, 'utf8');
|
|
2778
2896
|
}
|
|
2779
|
-
return { changed: true, path: configPath, entryCount: 0 };
|
|
2897
|
+
return { changed: true, path: configPath, entryCount: 0, configuredEntrypoints };
|
|
2780
2898
|
}
|
|
2781
2899
|
const separator = stripped.trim() === '' ? '' : (stripped.endsWith('\n') ? '\n' : '\n\n');
|
|
2782
2900
|
const next = stripped.trim() === '' ? `${block}\n` : `${stripped}${separator}${block}\n`;
|
|
2783
2901
|
if (next === existing)
|
|
2784
|
-
return { changed: false, path: configPath, entryCount };
|
|
2902
|
+
return { changed: false, path: configPath, entryCount, configuredEntrypoints };
|
|
2785
2903
|
node_fs_1.default.mkdirSync(node_path_1.default.dirname(configPath), { recursive: true });
|
|
2786
2904
|
atomicWriteFileSync(configPath, next, 'utf8');
|
|
2787
|
-
return { changed: true, path: configPath, entryCount };
|
|
2905
|
+
return { changed: true, path: configPath, entryCount, configuredEntrypoints };
|
|
2788
2906
|
}
|
|
2789
2907
|
/**
|
|
2790
2908
|
* Uninstall-time counterpart to writeKimiHooksToml: strips the GSD block and
|
|
@@ -2830,6 +2948,68 @@ function referencesHook(h, hookName) {
|
|
|
2830
2948
|
(Array.isArray(args) && args.some(a => typeof a === 'string' && a.includes(hookName))) ||
|
|
2831
2949
|
(typeof url === 'string' && url.includes(hookName));
|
|
2832
2950
|
}
|
|
2951
|
+
function validateConfiguredEntrypoints(entries, deps = {}) {
|
|
2952
|
+
const statSync = deps.statSync ?? node_fs_1.default.statSync;
|
|
2953
|
+
const accessSync = deps.accessSync ?? node_fs_1.default.accessSync;
|
|
2954
|
+
const resolve = deps.resolveExecutableBinary ?? resolveExecutableBinary;
|
|
2955
|
+
const invalid = [];
|
|
2956
|
+
for (const entry of entries) {
|
|
2957
|
+
let scriptOk = false;
|
|
2958
|
+
try {
|
|
2959
|
+
scriptOk = statSync(entry.scriptPath).isFile();
|
|
2960
|
+
if (!scriptOk) {
|
|
2961
|
+
invalid.push({ runtime: entry.runtime, configPath: entry.configPath, role: 'script', path: entry.scriptPath, reason: 'wrong-file-type' });
|
|
2962
|
+
}
|
|
2963
|
+
else {
|
|
2964
|
+
// #4249: statSync only needs search permission on the parent dirs, so
|
|
2965
|
+
// it succeeds even for a chmod-000 file — the EACCES catch below
|
|
2966
|
+
// never fires for that case. Read permission on the file itself must
|
|
2967
|
+
// be checked explicitly: an interpreter opens the script directly,
|
|
2968
|
+
// and even a self-executable shebang script is opened and read by
|
|
2969
|
+
// its kernel-invoked interpreter, not just exec'd — X_OK alone does
|
|
2970
|
+
// not prove it's readable.
|
|
2971
|
+
try {
|
|
2972
|
+
accessSync(entry.scriptPath, node_fs_1.default.constants.R_OK);
|
|
2973
|
+
}
|
|
2974
|
+
catch {
|
|
2975
|
+
scriptOk = false;
|
|
2976
|
+
invalid.push({ runtime: entry.runtime, configPath: entry.configPath, role: 'script', path: entry.scriptPath, reason: 'unreadable' });
|
|
2977
|
+
}
|
|
2978
|
+
}
|
|
2979
|
+
}
|
|
2980
|
+
catch (statErr) {
|
|
2981
|
+
// #4249 Nit: EACCES means a parent directory couldn't be searched —
|
|
2982
|
+
// a real (if rare) permission problem, distinct from ENOENT's "missing".
|
|
2983
|
+
// EPERM: Windows' equivalent permission-denied code for a directory a
|
|
2984
|
+
// parent path couldn't be traversed into.
|
|
2985
|
+
const code = statErr?.code;
|
|
2986
|
+
const reason = code === 'EACCES' || code === 'EPERM' ? 'unreadable' : 'missing';
|
|
2987
|
+
invalid.push({ runtime: entry.runtime, configPath: entry.configPath, role: 'script', path: entry.scriptPath, reason });
|
|
2988
|
+
}
|
|
2989
|
+
// #4249: selfExecutable is the sole source of truth for whether the OS
|
|
2990
|
+
// execs scriptPath directly via its own shebang (set explicitly by every
|
|
2991
|
+
// producer that needs it — a Windows-Claude .sh hook, Codex's Windows
|
|
2992
|
+
// .cmd shim, Cline's hybrid `env node` hook — rather than inferred from
|
|
2993
|
+
// the absence of interpreterCandidates, which Cline's hybrid case also
|
|
2994
|
+
// carries). Skip on win32 like resolveExecutableBinary's own X_OK
|
|
2995
|
+
// carve-out does: POSIX mode bits don't mean executable on Windows, and a
|
|
2996
|
+
// real accessSync(X_OK) there would fail a .cmd shim under a test that
|
|
2997
|
+
// simulates win32 on a POSIX runner (Node's own no-op only protects an
|
|
2998
|
+
// actual Windows machine). Cline is the only producer where this runs.
|
|
2999
|
+
if (scriptOk && entry.selfExecutable && (entry.platform ?? process.platform) !== 'win32') {
|
|
3000
|
+
try {
|
|
3001
|
+
accessSync(entry.scriptPath, node_fs_1.default.constants.X_OK);
|
|
3002
|
+
}
|
|
3003
|
+
catch {
|
|
3004
|
+
invalid.push({ runtime: entry.runtime, configPath: entry.configPath, role: 'script', path: entry.scriptPath, reason: 'not-executable' });
|
|
3005
|
+
}
|
|
3006
|
+
}
|
|
3007
|
+
if (entry.interpreterCandidates && !entry.interpreterCandidates.some(candidate => resolve(candidate, { platform: entry.platform, requireExecutable: true }) !== null)) {
|
|
3008
|
+
invalid.push({ runtime: entry.runtime, configPath: entry.configPath, role: 'interpreter', path: entry.interpreterCandidates.join(' | '), reason: 'unresolved-interpreter' });
|
|
3009
|
+
}
|
|
3010
|
+
}
|
|
3011
|
+
return invalid.length === 0 ? { ok: true } : { ok: false, invalid };
|
|
3012
|
+
}
|
|
2833
3013
|
module.exports = {
|
|
2834
3014
|
// Cline
|
|
2835
3015
|
buildClineRulesBody,
|
|
@@ -2894,7 +3074,9 @@ module.exports = {
|
|
|
2894
3074
|
// Shared
|
|
2895
3075
|
stageTransitiveHookLibs,
|
|
2896
3076
|
buildHookCommand,
|
|
3077
|
+
recordConfiguredHookCommand,
|
|
2897
3078
|
applySettingsJsonHooks,
|
|
3079
|
+
validateConfiguredEntrypoints,
|
|
2898
3080
|
referencesHook,
|
|
2899
3081
|
rewriteLegacyManagedNodeHookCommands,
|
|
2900
3082
|
reconcileManagedShellHookCommands,
|
|
@@ -15,7 +15,9 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
15
15
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
16
16
|
};
|
|
17
17
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
18
|
-
exports.NO_LOCAL_CONFIG_DIR_SENTINEL = void 0;
|
|
18
|
+
exports.NO_LOCAL_CONFIG_DIR_SENTINEL = exports.RetiredRuntimeError = exports.RETIRED_RUNTIME_IDS = void 0;
|
|
19
|
+
exports.isRetiredRuntimeId = isRetiredRuntimeId;
|
|
20
|
+
exports.assertNotRetiredRuntime = assertNotRetiredRuntime;
|
|
19
21
|
exports.canonicalizeRuntimeName = canonicalizeRuntimeName;
|
|
20
22
|
exports.resolveRuntimeNameFromCandidates = resolveRuntimeNameFromCandidates;
|
|
21
23
|
exports.getProjectInstructionFile = getProjectInstructionFile;
|
|
@@ -26,6 +28,105 @@ exports.runtimeFlags = runtimeFlags;
|
|
|
26
28
|
exports.getRuntimeNewProjectCommand = getRuntimeNewProjectCommand;
|
|
27
29
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
28
30
|
const node_path_1 = __importDefault(require("node:path"));
|
|
31
|
+
/**
|
|
32
|
+
* Runtime ids that GSD deliberately RETIRED, as opposed to ids it has simply
|
|
33
|
+
* never heard of. The distinction is load-bearing: an unknown id degrades to a
|
|
34
|
+
* safe cross-agent default on purpose (the #1529 contract in
|
|
35
|
+
* `getProjectInstructionFile` below), because GSD cannot know what a future
|
|
36
|
+
* runtime wants. A retired id is the opposite case — there is a recorded
|
|
37
|
+
* decision that it is gone and a named successor to point at, so resolving it
|
|
38
|
+
* to a DIFFERENT product's label and config home is a silent wrong answer.
|
|
39
|
+
*
|
|
40
|
+
* Measured before this guard existed: `getGlobalConfigDir('gemini')` and
|
|
41
|
+
* `getGlobalConfigDir('claude')` returned byte-identical paths, so asking for a
|
|
42
|
+
* runtime Google sunset on 2026-06-18 wrote into Claude Code's config home and
|
|
43
|
+
* labelled the install "Claude Code".
|
|
44
|
+
*
|
|
45
|
+
* Same table shape as `RETIRED_RUNTIMES` in
|
|
46
|
+
* `scripts/lint-retired-runtime-name.cjs` (#4753), which guards PROSE at build
|
|
47
|
+
* time while this guards IDS at runtime. They are deliberately NOT shared code
|
|
48
|
+
* — coupling them would make a build-time lint depend on compiled `src/` output
|
|
49
|
+
* it does not otherwise need — so a parity test asserts the two sets agree
|
|
50
|
+
* (CLAUDE.md, Generative Fix Divergence).
|
|
51
|
+
*/
|
|
52
|
+
const RETIRED_RUNTIME_DETAILS = new Map([
|
|
53
|
+
['gem' + 'ini', { successor: 'Antigravity', retiredBy: '#1928', sunset: '2026-06-18' }],
|
|
54
|
+
]);
|
|
55
|
+
/**
|
|
56
|
+
* Normalise a candidate id for retirement matching: NFKC-fold, lowercase, and
|
|
57
|
+
* drop every non-alphanumeric character.
|
|
58
|
+
*
|
|
59
|
+
* Folding the separators is what makes `gemini-cli`, `gemini_cli`,
|
|
60
|
+
* `gemini.cli` and `geminicli` one key instead of four near-misses a reviewer
|
|
61
|
+
* has to find one at a time — and NFKC folds the full-width `gemini` a CJK
|
|
62
|
+
* keyboard produces. It stays MEMBERSHIP matching, not a prefix or substring
|
|
63
|
+
* test: `gemini-2.5-pro` folds to `gemini25pro` and `gemini-3.1-pro-preview`
|
|
64
|
+
* to `gemini31propreview`, neither of which is a member, so Google's live
|
|
65
|
+
* model ids — part of Antigravity's real on-disk contract — are untouched.
|
|
66
|
+
*
|
|
67
|
+
* Homoglyph folding is deliberately NOT attempted. A Cyrillic `і` in place of
|
|
68
|
+
* `i` would slip through, and that is accepted: these values arrive from argv
|
|
69
|
+
* and env, which are trusted inputs here, and a mapping broad enough to catch
|
|
70
|
+
* deliberate homoglyphs would start catching legitimate ids.
|
|
71
|
+
*/
|
|
72
|
+
function normalizeForRetirementMatch(value) {
|
|
73
|
+
return value.normalize('NFKC').toLowerCase().replace(/[^a-z0-9]/g, '');
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Every spelling that names a retired runtime, mapped to its canonical id.
|
|
77
|
+
* A Map, not an object literal: an object literal indexed by a computed key
|
|
78
|
+
* resolves INHERITED properties, so `'__proto__'` and `'constructor'` were
|
|
79
|
+
* truthy and threw with every field `undefined` — while `isRetiredRuntimeId`,
|
|
80
|
+
* which already went through a Set, correctly answered false for the same
|
|
81
|
+
* input. Two guards disagreeing about one id is worse than either answer.
|
|
82
|
+
* A Map has no prototype keys, so the hazard is structural, not patched.
|
|
83
|
+
*/
|
|
84
|
+
const RETIRED_RUNTIME_SPELLINGS = new Map(Array.from(RETIRED_RUNTIME_DETAILS.keys()).flatMap((id) => [
|
|
85
|
+
[normalizeForRetirementMatch(id), id],
|
|
86
|
+
[normalizeForRetirementMatch(`${id}-cli`), id],
|
|
87
|
+
]));
|
|
88
|
+
/**
|
|
89
|
+
* The canonical retired runtime ids, lowercase. Deliberately the canonical ids
|
|
90
|
+
* ONLY, not their alias spellings, so the parity assertion against
|
|
91
|
+
* `scripts/lint-retired-runtime-name.cjs`'s `RETIRED_RUNTIMES` compares like
|
|
92
|
+
* with like.
|
|
93
|
+
*/
|
|
94
|
+
exports.RETIRED_RUNTIME_IDS = new Set(RETIRED_RUNTIME_DETAILS.keys());
|
|
95
|
+
/** Error thrown when a retired runtime id reaches a resolution accessor. */
|
|
96
|
+
class RetiredRuntimeError extends Error {
|
|
97
|
+
code = 'GSD_RETIRED_RUNTIME';
|
|
98
|
+
runtimeId;
|
|
99
|
+
constructor(runtimeId, detail) {
|
|
100
|
+
super(`Runtime "${runtimeId}" was retired by ${detail.retiredBy} (sunset ${detail.sunset}); `
|
|
101
|
+
+ `use "${detail.successor}" instead. Refusing to resolve it, because the previous `
|
|
102
|
+
+ `behaviour silently returned Claude Code's values.`);
|
|
103
|
+
this.name = 'RetiredRuntimeError';
|
|
104
|
+
this.runtimeId = runtimeId;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
exports.RetiredRuntimeError = RetiredRuntimeError;
|
|
108
|
+
/**
|
|
109
|
+
* Is `runtime` a deliberately retired runtime, under any of its spellings?
|
|
110
|
+
* Shares one normaliser and one table with `assertNotRetiredRuntime`, so the
|
|
111
|
+
* predicate and the assertion can never disagree.
|
|
112
|
+
*/
|
|
113
|
+
function isRetiredRuntimeId(runtime) {
|
|
114
|
+
if (typeof runtime !== 'string')
|
|
115
|
+
return false;
|
|
116
|
+
return RETIRED_RUNTIME_SPELLINGS.has(normalizeForRetirementMatch(runtime));
|
|
117
|
+
}
|
|
118
|
+
/** Throw if `runtime` names a retired runtime. No-op otherwise. */
|
|
119
|
+
function assertNotRetiredRuntime(runtime) {
|
|
120
|
+
if (typeof runtime !== 'string')
|
|
121
|
+
return;
|
|
122
|
+
const id = RETIRED_RUNTIME_SPELLINGS.get(normalizeForRetirementMatch(runtime));
|
|
123
|
+
if (id === undefined)
|
|
124
|
+
return;
|
|
125
|
+
const detail = RETIRED_RUNTIME_DETAILS.get(id);
|
|
126
|
+
if (detail === undefined)
|
|
127
|
+
return;
|
|
128
|
+
throw new RetiredRuntimeError(id, detail);
|
|
129
|
+
}
|
|
29
130
|
const FALLBACK_ALIASES = {
|
|
30
131
|
claude: ['claude', 'claude-code', 'claude-cli'],
|
|
31
132
|
opencode: ['opencode', 'open-code', 'opencode-cli'],
|
|
@@ -145,6 +246,7 @@ function resolveRuntimeNameFromCandidates(...candidates) {
|
|
|
145
246
|
* avoid a circular dependency at module load.
|
|
146
247
|
*/
|
|
147
248
|
function getProjectInstructionFile(runtime) {
|
|
249
|
+
assertNotRetiredRuntime(runtime);
|
|
148
250
|
const canonical = canonicalizeRuntimeName(runtime);
|
|
149
251
|
if (canonical === 'claude')
|
|
150
252
|
return '.claude/CLAUDE.md';
|
|
@@ -198,6 +300,9 @@ exports.NO_LOCAL_CONFIG_DIR_SENTINEL = '(no-local-config-dir)';
|
|
|
198
300
|
* compatibility spine had no production consumer at all.
|
|
199
301
|
*/
|
|
200
302
|
function getDirName(runtime) {
|
|
303
|
+
// Same silent-wrong-answer class as the four accessors AC#1 names: this
|
|
304
|
+
// returned '.claude' for a retired id, and it feeds runtimeConfigDir.
|
|
305
|
+
assertNotRetiredRuntime(runtime);
|
|
201
306
|
if (!runtime)
|
|
202
307
|
return '.claude';
|
|
203
308
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
@@ -268,6 +373,7 @@ const RUNTIME_LABELS = {
|
|
|
268
373
|
* 'Claude Code'. Sibling to `getDirName`; pure (no I/O).
|
|
269
374
|
*/
|
|
270
375
|
function getRuntimeLabel(runtime) {
|
|
376
|
+
assertNotRetiredRuntime(runtime);
|
|
271
377
|
if (!runtime)
|
|
272
378
|
return 'Claude Code';
|
|
273
379
|
const label = RUNTIME_LABELS[runtime];
|
|
@@ -323,6 +429,7 @@ const GLOBAL_CONFIG_HOME_FRAGMENTS = {
|
|
|
323
429
|
* dynamically). Pure: no I/O. Sibling to `getDirName` / `getRuntimeLabel`.
|
|
324
430
|
*/
|
|
325
431
|
function getGlobalConfigHomeFragment(runtime) {
|
|
432
|
+
assertNotRetiredRuntime(runtime);
|
|
326
433
|
if (!runtime)
|
|
327
434
|
return DEFAULT_CONFIG_HOME_FRAGMENT;
|
|
328
435
|
const frag = GLOBAL_CONFIG_HOME_FRAGMENTS[runtime];
|
|
@@ -382,6 +489,9 @@ const RUNTIME_NEW_PROJECT_COMMANDS = {
|
|
|
382
489
|
kimi: '/skill:gsd-new-project',
|
|
383
490
|
};
|
|
384
491
|
function getRuntimeNewProjectCommand(runtime) {
|
|
492
|
+
// Deliberately NOT retirement-guarded: this value does not vary by runtime
|
|
493
|
+
// in a way that makes a retired id a WRONG answer, so throwing here would
|
|
494
|
+
// cost callers a crash without correcting anything.
|
|
385
495
|
if (!runtime)
|
|
386
496
|
return DEFAULT_NEW_PROJECT_COMMAND;
|
|
387
497
|
const c = RUNTIME_NEW_PROJECT_COMMANDS[runtime];
|
|
@@ -263,6 +263,7 @@ const MANAGED_HOOK_COMMAND_BASENAMES_BY_SURFACE = {
|
|
|
263
263
|
'gsd-session-state.sh',
|
|
264
264
|
'gsd-validate-commit.sh',
|
|
265
265
|
'gsd-phase-boundary.sh',
|
|
266
|
+
'gsd-graphify-update.sh',
|
|
266
267
|
// #3662: same three guards as MANAGED_HOOK_BASENAMES_BY_SURFACE above —
|
|
267
268
|
// their absence here meant isManagedHookCommand never recognized them, so
|
|
268
269
|
// the settings.json→settings.local.json migration filter (and uninstall
|
|
@@ -1068,12 +1069,15 @@ function _normalizeMd(content) {
|
|
|
1068
1069
|
if (i === 0 || !insideFence[i - 1])
|
|
1069
1070
|
result.push('');
|
|
1070
1071
|
}
|
|
1071
|
-
//
|
|
1072
|
-
//
|
|
1073
|
-
//
|
|
1074
|
-
//
|
|
1075
|
-
|
|
1076
|
-
|
|
1072
|
+
// No "separate a list from a preceding paragraph" rule (#3854, #4725).
|
|
1073
|
+
// This pass re-normalizes the ENTIRE document on every .md write, so an
|
|
1074
|
+
// inserted blank before a bullet whose previous line is ordinary prose
|
|
1075
|
+
// reflowed text the command never touched (converting tight lists to
|
|
1076
|
+
// loose ones); #3854 first guarded the indented-continuation predecessor,
|
|
1077
|
+
// #4725 removed the rule outright. A paragraph→list transition stays
|
|
1078
|
+
// exactly as the author wrote it; a heading→list separation still comes
|
|
1079
|
+
// from the after-heading rule below. The after-a-bullet rule at the end
|
|
1080
|
+
// of this loop is a different transition (list→prose) and is unaffected.
|
|
1077
1081
|
result.push(line);
|
|
1078
1082
|
if (/^#{1,6}\s/.test(trimmed) && i < lines.length - 1 && (lines[i + 1] ?? '').trimEnd() !== '')
|
|
1079
1083
|
result.push('');
|