@opengsd/gsd-core 1.10.0 → 1.11.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-debug-session-manager.md +11 -0
- package/agents/gsd-doc-synthesizer.md +2 -4
- package/agents/gsd-executor.md +5 -5
- package/agents/gsd-mempalace-curator.md +5 -2
- package/agents/gsd-phase-researcher.md +20 -1
- package/agents/gsd-plan-checker.md +37 -0
- package/agents/gsd-planner.md +44 -46
- package/agents/gsd-user-profiler.md +3 -0
- package/agents/gsd-verifier.md +12 -3
- package/bin/install.js +841 -971
- package/bin/lib/ui-safety-gate.cjs +2 -0
- package/commands/gsd/code-review.md +1 -1
- package/commands/gsd/execute-phase.md +1 -1
- package/commands/gsd/map-codebase.md +1 -1
- package/commands/gsd/mempalace-capture.md +1 -1
- package/commands/gsd/mempalace-recall.md +1 -1
- package/commands/gsd/new-milestone.md +1 -1
- package/commands/gsd/quick.md +1 -1
- package/commands/gsd/review-backlog.md +2 -1
- package/commands/gsd/verify-work.md +1 -1
- package/gsd-core/bin/gsd-tools.cjs +469 -88
- package/gsd-core/bin/lib/active-workstream-store.cjs +138 -22
- package/gsd-core/bin/lib/agent-install-check.cjs +230 -32
- package/gsd-core/bin/lib/api-coverage.cjs +3 -5
- package/gsd-core/bin/lib/artifacts.cjs +3 -0
- package/gsd-core/bin/lib/assumption-delta.cjs +2 -4
- package/gsd-core/bin/lib/audit-command-router.cjs +9 -2
- package/gsd-core/bin/lib/audit.cjs +876 -240
- package/gsd-core/bin/lib/broken-windows.cjs +1 -1
- package/gsd-core/bin/lib/capability-consent.cjs +149 -15
- package/gsd-core/bin/lib/capability-lifecycle.cjs +45 -0
- package/gsd-core/bin/lib/capability-registry.cjs +575 -101
- package/gsd-core/bin/lib/capability-source.cjs +92 -0
- package/gsd-core/bin/lib/capability-trust.cjs +444 -25
- package/gsd-core/bin/lib/capability-validator.cjs +495 -22
- package/gsd-core/bin/lib/capability-writer.cjs +3 -2
- package/gsd-core/bin/lib/check-command-router.cjs +71 -37
- package/gsd-core/bin/lib/claude-orchestration.cjs +56 -3
- package/gsd-core/bin/lib/codex-agent-toml.cjs +329 -0
- package/gsd-core/bin/lib/command-aliases.cjs +22 -0
- package/gsd-core/bin/lib/command-roster.cjs +44 -1
- package/gsd-core/bin/lib/commands.cjs +651 -86
- package/gsd-core/bin/lib/commonjs-marker.cjs +12 -6
- package/gsd-core/bin/lib/complexity-trigger.cjs +1172 -0
- package/gsd-core/bin/lib/config-loader.cjs +75 -0
- package/gsd-core/bin/lib/config.cjs +10 -1
- package/gsd-core/bin/lib/core-utils.cjs +127 -29
- package/gsd-core/bin/lib/decisions.cjs +23 -0
- package/gsd-core/bin/lib/fallow-runner.cjs +20 -44
- package/gsd-core/bin/lib/frontmatter.cjs +155 -20
- package/gsd-core/bin/lib/gap-checker.cjs +68 -7
- package/gsd-core/bin/lib/git-base-branch.cjs +102 -0
- package/gsd-core/bin/lib/gsd2-import.cjs +10 -1
- package/gsd-core/bin/lib/health-diagnostic-rules/agent-install.cjs +101 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/config-validation.cjs +348 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/consistency.cjs +145 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/install-surface-shadowing.cjs +98 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/milestone-archive-hygiene.cjs +100 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/phase-structure.cjs +222 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/roadmap-disk-consistency.cjs +265 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/root-existence.cjs +161 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/state-consistency.cjs +303 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/worktree-health.cjs +173 -0
- package/gsd-core/bin/lib/health-diagnostic-types.cjs +68 -0
- package/gsd-core/bin/lib/health-diagnostic.cjs +431 -0
- package/gsd-core/bin/lib/host-runtime-detection.cjs +134 -0
- package/gsd-core/bin/lib/init.cjs +321 -129
- package/gsd-core/bin/lib/install-effort-resolver.cjs +73 -30
- package/gsd-core/bin/lib/install-engine.cjs +745 -258
- package/gsd-core/bin/lib/install-fs-adapter.cjs +262 -0
- package/gsd-core/bin/lib/install-model-override-resolver.cjs +203 -0
- package/gsd-core/bin/lib/install-profiles.cjs +134 -57
- package/gsd-core/bin/lib/install-scope.cjs +270 -0
- package/gsd-core/bin/lib/install-shadow-report.cjs +385 -0
- package/gsd-core/bin/lib/installed-surface-resolver.cjs +381 -0
- package/gsd-core/bin/lib/installer-migrations.cjs +138 -31
- package/gsd-core/bin/lib/io.cjs +10 -0
- package/gsd-core/bin/lib/markdown-sectionizer.cjs +2 -1
- package/gsd-core/bin/lib/markdown-table.cjs +133 -20
- package/gsd-core/bin/lib/milestone-lock.cjs +248 -0
- package/gsd-core/bin/lib/milestone.cjs +754 -70
- package/gsd-core/bin/lib/model-catalog.cjs +59 -1
- package/gsd-core/bin/lib/model-resolver.cjs +183 -40
- package/gsd-core/bin/lib/normalize-test-command.cjs +1 -1
- package/gsd-core/bin/lib/pattern.cjs +122 -0
- package/gsd-core/bin/lib/phase-estimation.cjs +1 -1
- package/gsd-core/bin/lib/phase-id.cjs +444 -36
- package/gsd-core/bin/lib/phase-lifecycle.cjs +28 -3
- package/gsd-core/bin/lib/phase-locator.cjs +125 -18
- package/gsd-core/bin/lib/phase.cjs +646 -143
- package/gsd-core/bin/lib/plan-dependency-graph.cjs +72 -1
- package/gsd-core/bin/lib/plan-drift-guard.cjs +120 -0
- package/gsd-core/bin/lib/plan-scan.cjs +86 -2
- package/gsd-core/bin/lib/planning-scope.cjs +31 -0
- package/gsd-core/bin/lib/planning-snapshot.cjs +890 -0
- package/gsd-core/bin/lib/planning-workspace.cjs +56 -6
- package/gsd-core/bin/lib/probe-core.cjs +1 -1
- package/gsd-core/bin/lib/profile-output.cjs +1 -1
- package/gsd-core/bin/lib/refactor-trigger-command-router.cjs +740 -0
- package/gsd-core/bin/lib/retired-artifact-cleanup.cjs +11 -6
- package/gsd-core/bin/lib/review-lane-descriptor.cjs +13 -4
- package/gsd-core/bin/lib/review-lane-invocation.cjs +30 -0
- package/gsd-core/bin/lib/review-lane-runner.cjs +421 -66
- package/gsd-core/bin/lib/review-reviewer-selection.cjs +13 -18
- package/gsd-core/bin/lib/roadmap-command-router.cjs +34 -0
- package/gsd-core/bin/lib/roadmap-parser.cjs +943 -184
- package/gsd-core/bin/lib/roadmap-upgrade.cjs +37 -10
- package/gsd-core/bin/lib/roadmap.cjs +385 -94
- package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +608 -46
- package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +14 -2
- package/gsd-core/bin/lib/runtime-artifact-layout.cjs +426 -55
- package/gsd-core/bin/lib/runtime-config-adapter-registry.cjs +3 -2
- package/gsd-core/bin/lib/runtime-homes.cjs +69 -3
- package/gsd-core/bin/lib/runtime-hooks-surface.cjs +115 -3
- package/gsd-core/bin/lib/runtime-name-policy.cjs +3 -1
- package/gsd-core/bin/lib/runtime-slash.cjs +27 -9
- package/gsd-core/bin/lib/security.cjs +104 -5
- package/gsd-core/bin/lib/shell-command-projection.cjs +275 -3
- package/gsd-core/bin/lib/smart-entry.cjs +142 -22
- package/gsd-core/bin/lib/state-command-router.cjs +5 -1
- package/gsd-core/bin/lib/state-document.cjs +152 -8
- package/gsd-core/bin/lib/state-transition.cjs +371 -117
- package/gsd-core/bin/lib/state.cjs +1794 -357
- package/gsd-core/bin/lib/surface.cjs +23 -9
- package/gsd-core/bin/lib/text-lines.cjs +80 -0
- package/gsd-core/bin/lib/token-scanner.cjs +76 -0
- package/gsd-core/bin/lib/uat-predicate.cjs +9 -3
- package/gsd-core/bin/lib/uat.cjs +399 -56
- package/gsd-core/bin/lib/ui-frontend-evidence.cjs +157 -0
- package/gsd-core/bin/lib/ui-safety-gate.cjs +14 -5
- package/gsd-core/bin/lib/unusable-input.cjs +24 -0
- package/gsd-core/bin/lib/update-context.cjs +8 -2
- package/gsd-core/bin/lib/user-artifact-staging.cjs +705 -0
- package/gsd-core/bin/lib/validate.cjs +20 -6
- package/gsd-core/bin/lib/vendor/README.md +37 -0
- package/gsd-core/bin/lib/vendor/re2js.cjs +6480 -0
- package/gsd-core/bin/lib/vendor/re2js.d.cts +938 -0
- package/gsd-core/bin/lib/verification-command-router.cjs +2 -1
- package/gsd-core/bin/lib/verification.cjs +258 -8
- package/gsd-core/bin/lib/verify.cjs +368 -888
- package/gsd-core/bin/lib/workstream-inventory-builder.cjs +53 -32
- package/gsd-core/bin/lib/workstream-inventory.cjs +63 -10
- package/gsd-core/bin/lib/workstream.cjs +2 -2
- package/gsd-core/bin/lib/worktree-safety.cjs +176 -9
- package/gsd-core/bin/shared/config-defaults.manifest.json +1 -0
- package/gsd-core/bin/shared/config-schema.manifest.json +7 -1
- package/gsd-core/references/agent-contracts.md +43 -26
- package/gsd-core/references/checkpoints.md +2 -2
- package/gsd-core/references/context-budget.md +1 -1
- package/gsd-core/references/dispatch-isolation-gate.md +138 -0
- package/gsd-core/references/doc-conflict-engine.md +1 -1
- package/gsd-core/references/execute-mvp-tdd.md +3 -3
- package/gsd-core/references/execute-phase-between-wave-reset.md +6 -2
- package/gsd-core/references/execute-phase-context-guard.md +1 -1
- package/gsd-core/references/execute-phase-response-language.md +1 -1
- package/gsd-core/references/execute-phase-wave-guard.md +6 -2
- package/gsd-core/references/gate-prompts.md +1 -1
- package/gsd-core/references/git-planning-commit.md +2 -1
- package/gsd-core/references/loop-hook-dispatch.md +39 -2
- package/gsd-core/references/model-profiles.md +12 -4
- package/gsd-core/references/mvp-concepts.md +9 -9
- package/gsd-core/references/planner-guidance.md +3 -9
- package/gsd-core/references/planner-preconditions.md +1 -1
- package/gsd-core/references/planner-reviews.md +1 -1
- package/gsd-core/references/planning-config.md +8 -6
- package/gsd-core/references/revision-loop.md +1 -1
- package/gsd-core/references/specless-probe-fallback.md +1 -1
- package/gsd-core/references/universal-anti-patterns.md +3 -3
- package/gsd-core/references/verifier-phase-gates.md +192 -0
- package/gsd-core/references/verify-mvp-mode.md +1 -1
- package/gsd-core/references/workstream-flag.md +22 -6
- package/gsd-core/templates/discussion-log.md +1 -1
- package/gsd-core/templates/phase-prompt.md +2 -4
- package/gsd-core/templates/state.md +4 -4
- package/gsd-core/templates/verification-report.md +9 -1
- package/gsd-core/workflows/ai-integration-phase.md +9 -11
- package/gsd-core/workflows/autonomous.md +1 -1
- package/gsd-core/workflows/cleanup.md +62 -3
- package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +13 -3
- package/gsd-core/workflows/code-review-fix.md +37 -10
- package/gsd-core/workflows/code-review.md +38 -12
- package/gsd-core/workflows/complete-milestone.md +141 -18
- package/gsd-core/workflows/debug.md +7 -5
- package/gsd-core/workflows/diagnose-issues.md +35 -9
- package/gsd-core/workflows/discuss-phase/modes/chain.md +2 -1
- package/gsd-core/workflows/discuss-phase/modes/default.md +1 -1
- package/gsd-core/workflows/discuss-phase-assumptions.md +2 -1
- package/gsd-core/workflows/edit-phase.md +26 -1
- package/gsd-core/workflows/eval-review.md +3 -5
- package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +31 -6
- package/gsd-core/workflows/execute-phase/steps/per-plan-executor-routing.md +77 -0
- package/gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +2 -0
- package/gsd-core/workflows/execute-phase.md +38 -50
- package/gsd-core/workflows/execute-plan.md +36 -4
- package/gsd-core/workflows/explore.md +131 -4
- package/gsd-core/workflows/fast.md +10 -2
- package/gsd-core/workflows/health.md +73 -4
- package/gsd-core/workflows/import.md +4 -4
- package/gsd-core/workflows/ingest-docs.md +5 -5
- package/gsd-core/workflows/mvp-phase.md +6 -3
- package/gsd-core/workflows/new-milestone.md +14 -9
- package/gsd-core/workflows/new-project.md +14 -14
- package/gsd-core/workflows/next.md +12 -0
- package/gsd-core/workflows/plan-phase.md +41 -17
- package/gsd-core/workflows/plan-review-convergence.md +50 -2
- package/gsd-core/workflows/progress.md +34 -6
- package/gsd-core/workflows/quick/steps/plan-checker-loop.md +4 -4
- package/gsd-core/workflows/quick/steps/quick-verification.md +27 -6
- package/gsd-core/workflows/quick/steps/research-phase.md +2 -2
- package/gsd-core/workflows/quick.md +35 -15
- package/gsd-core/workflows/review.md +26 -5
- package/gsd-core/workflows/secure-phase.md +1 -1
- package/gsd-core/workflows/session-report.md +2 -1
- package/gsd-core/workflows/settings.md +66 -2
- package/gsd-core/workflows/ship.md +104 -44
- package/gsd-core/workflows/spec-phase.md +30 -12
- package/gsd-core/workflows/sync-skills.md +63 -8
- package/gsd-core/workflows/transition.md +46 -11
- package/gsd-core/workflows/ui-phase.md +5 -5
- package/gsd-core/workflows/ui-review.md +2 -2
- package/gsd-core/workflows/update.md +1 -1
- package/gsd-core/workflows/validate-phase.md +1 -1
- package/gsd-core/workflows/verify-work.md +9 -7
- package/hooks/dist/gsd-agent-isolation-guard.js +103 -14
- package/hooks/dist/gsd-check-update-worker.js +56 -13
- package/hooks/dist/gsd-check-update.js +19 -1
- package/hooks/dist/gsd-cursor-pre-tool.js +0 -3
- package/hooks/dist/gsd-cursor-subagent-start.js +77 -2
- package/hooks/dist/gsd-cursor-subagent-stop.js +3 -2
- package/hooks/dist/gsd-prompt-guard.js +21 -20
- package/hooks/dist/gsd-read-injection-scanner.js +38 -24
- package/hooks/dist/gsd-statusline.js +18 -0
- package/hooks/dist/gsd-update-banner.js +22 -1
- package/hooks/dist/gsd-workflow-guard.js +134 -36
- package/hooks/dist/lib/git-cmd.js +92 -59
- package/hooks/dist/lib/injection-patterns.js +45 -0
- package/hooks/dist/lib/isolation-deny-reason.js +39 -0
- package/hooks/dist/lib/isolation-sentinel.js +9 -0
- package/hooks/gsd-agent-isolation-guard.js +103 -14
- package/hooks/gsd-check-update-worker.js +56 -13
- package/hooks/gsd-check-update.js +19 -1
- package/hooks/gsd-cursor-pre-tool.js +0 -3
- package/hooks/gsd-cursor-subagent-start.js +77 -2
- package/hooks/gsd-cursor-subagent-stop.js +3 -2
- package/hooks/gsd-prompt-guard.js +21 -20
- package/hooks/gsd-read-injection-scanner.js +38 -24
- package/hooks/gsd-statusline.js +18 -0
- package/hooks/gsd-update-banner.js +22 -1
- package/hooks/gsd-workflow-guard.js +134 -36
- package/hooks/lib/git-cmd.js +92 -59
- package/hooks/lib/injection-patterns.js +45 -0
- package/hooks/lib/isolation-deny-reason.js +39 -0
- package/hooks/lib/isolation-sentinel.js +9 -0
- package/package.json +21 -9
- package/pi/gsd.cjs +19 -5
- package/scripts/baselines/planning-prompt-drift-baseline.json +4 -0
- package/scripts/baselines/planning-snapshot-bypass-baseline.json +12 -0
- package/scripts/baselines/unreachable-guard-drift-baseline.json +4 -0
- package/scripts/changeset/lint.cjs +60 -5
- package/scripts/check-alias-drift.cjs +7 -43
- package/scripts/check-contract-drift.cjs +297 -0
- package/scripts/ci-test-scope.cjs +19 -2
- package/scripts/command-contract-helpers.cjs +903 -1
- package/scripts/gen-adr-index.cjs +728 -38
- package/scripts/gen-capability-registry.cjs +3 -15
- package/scripts/gen-context-index.cjs +2 -11
- package/scripts/gen-health-docs.cjs +390 -0
- package/scripts/gen-inventory-manifest.cjs +50 -4
- package/scripts/gen-loop-host-contract.cjs +4 -24
- package/scripts/gen-registry.cjs +3 -14
- package/scripts/lib/alias-drift-families.cjs +46 -0
- package/scripts/lib/drift-scan.cjs +278 -0
- package/scripts/lint-allow-test-rule-refs.allowlist.json +1 -26
- package/scripts/lint-allow-test-rule-refs.effective-ceiling.json +4 -0
- package/scripts/lint-allow-test-rule-refs.unverified-ceiling.json +3 -0
- package/scripts/lint-canary-version-leak.cjs +73 -0
- package/scripts/lint-command-contract.cjs +96 -13
- package/scripts/lint-completion-predicate-drift.cjs +933 -0
- package/scripts/lint-completion-ratio-drift.cjs +214 -0
- package/scripts/lint-default-flip-documentation.cjs +193 -0
- package/scripts/lint-eslint-glob-coverage.allowlist.json +34 -0
- package/scripts/lint-eslint-glob-coverage.cjs +340 -0
- package/scripts/lint-frontmatter-scalar-broad-grep.cjs +237 -0
- package/scripts/lint-health-diagnostic-rule-table.cjs +404 -0
- package/scripts/lint-hooks-runtime-build-seam.cjs +262 -0
- package/scripts/lint-milestone-window-drift.cjs +468 -0
- package/scripts/lint-phase-enumeration-drift.cjs +479 -0
- package/scripts/lint-plan-count-drift.cjs +318 -0
- package/scripts/lint-planning-artifact-writer-drift.cjs +398 -0
- package/scripts/lint-planning-prompt-drift.cjs +434 -0
- package/scripts/lint-planning-snapshot-bypass-drift.cjs +544 -0
- package/scripts/lint-regression-test-names.cjs +15 -13
- package/scripts/lint-removed-but-needed.cjs +320 -0
- package/scripts/lint-state-field-drift.cjs +805 -0
- package/scripts/lint-state-write-path-drift.cjs +1045 -0
- package/scripts/lint-test-file-count.allowlist.json +21 -10
- package/scripts/lint-unreachable-guard-drift.cjs +843 -0
- package/scripts/lint-vendored-deps.cjs +124 -0
- package/scripts/pr-changed-files.cjs +63 -0
- package/scripts/pr-template-policy.cjs +14 -4
- package/scripts/prompt-injection-scan.sh +25 -0
- package/scripts/require-issue-link-policy.cjs +192 -0
- package/scripts/state-write-path-drift-baseline.json +19 -0
- package/scripts/sync-runtime-launcher.cjs +2 -4
- package/skills/gsd-autonomous/SKILL.md +0 -1
- package/skills/gsd-code-review/SKILL.md +1 -1
- package/skills/gsd-execute-phase/SKILL.md +1 -2
- package/skills/gsd-map-codebase/SKILL.md +1 -1
- package/skills/gsd-mempalace-capture/SKILL.md +1 -1
- package/skills/gsd-mempalace-recall/SKILL.md +1 -1
- package/skills/gsd-new-milestone/SKILL.md +1 -1
- package/skills/gsd-next/SKILL.md +0 -1
- package/skills/gsd-plan-phase/SKILL.md +0 -1
- package/skills/gsd-progress/SKILL.md +0 -1
- package/skills/gsd-quick/SKILL.md +1 -1
- package/skills/gsd-review-backlog/SKILL.md +2 -1
- package/skills/gsd-stats/SKILL.md +0 -1
- package/skills/gsd-verify-work/SKILL.md +1 -1
- package/vscode/package.json +1 -1
- package/gsd-core/workflows/discovery-phase.md +0 -298
- package/gsd-core/workflows/plan-milestone-gaps.md +0 -281
- package/gsd-core/workflows/verify-phase.md +0 -574
- package/scripts/affected-tests-lib.cjs +0 -554
- package/scripts/lint-allow-test-rule-refs.cjs +0 -162
- package/scripts/run-affected-tests.cjs +0 -7
- package/scripts/run-tests.cjs +0 -1051
package/bin/install.js
CHANGED
|
@@ -36,10 +36,18 @@ const {
|
|
|
36
36
|
resolveKimiHooksTomlDir,
|
|
37
37
|
isRegisteredRuntimeId,
|
|
38
38
|
} = require('../gsd-core/bin/lib/runtime-homes.cjs');
|
|
39
|
+
// #2870: the Install Scope Module — turns a bare 'global' | 'local' scope id
|
|
40
|
+
// plus a runtime into one resolved value (configHome, settingsFile,
|
|
41
|
+
// consentRequired, hostPrecedenceRank) instead of the id being re-derived
|
|
42
|
+
// and re-interpreted at each call site. See src/install-scope.cts.
|
|
43
|
+
const { resolveScope } = require('../gsd-core/bin/lib/install-scope.cjs');
|
|
39
44
|
// getDirName (runtime -> local config dir name) is relocated out of this
|
|
40
45
|
// installer to the runtime-name-policy leaf (ADR-1508 / #1510 Phase 1) so the
|
|
41
46
|
// conversion module's rewrite engine can consume it without importing
|
|
42
|
-
// bin/install.js.
|
|
47
|
+
// bin/install.js. Imported here for install.js's own internal call sites
|
|
48
|
+
// (getConfigDirFromHome and the runtime-content-rewrite loops below) — #2876
|
|
49
|
+
// retired the re-export; tests now import getDirName directly from
|
|
50
|
+
// gsd-core/bin/lib/runtime-name-policy.cjs.
|
|
43
51
|
const { getDirName, getRuntimeLabel, getGlobalConfigHomeFragment, runtimeFlags, getRuntimeNewProjectCommand } = require('../gsd-core/bin/lib/runtime-name-policy.cjs');
|
|
44
52
|
const {
|
|
45
53
|
applyWorktreeBaseRef,
|
|
@@ -54,6 +62,11 @@ const { composeWorkflow } = require('../gsd-core/bin/lib/workflow-fragments.cjs'
|
|
|
54
62
|
// MCP catalog, src/mcp-catalog.cts) — see the comment at its call site below.
|
|
55
63
|
const { shouldCompose } = require('../gsd-core/bin/lib/mcp-catalog.cjs');
|
|
56
64
|
const runtimeArtifactConversion = require('../gsd-core/bin/lib/runtime-artifact-conversion.cjs');
|
|
65
|
+
const { escapeRegex: escapeRegExp } = require('../gsd-core/bin/lib/pattern.cjs');
|
|
66
|
+
// #2873: cross-scope shadow detection — reports (never fails) when a
|
|
67
|
+
// GSD-owned scope shadows another on this machine (design doc:
|
|
68
|
+
// .gsd/phase/feat-2873-cross-scope-shadowing/40-design.md).
|
|
69
|
+
const { buildShadowReport, renderShadowReport } = require('../gsd-core/bin/lib/install-shadow-report.cjs');
|
|
57
70
|
// #2544: the CommonJS marker's single source of truth. classifyMarker() backs
|
|
58
71
|
// BOTH ensureCommonJsMarker() (install) and removeCommonJsMarker() (uninstall),
|
|
59
72
|
// so the write side can no longer clobber a package.json the remove side would
|
|
@@ -66,9 +79,14 @@ const { ensureCommonJsMarker, removeCommonJsMarker } = require('../gsd-core/bin/
|
|
|
66
79
|
const { HOOKS_TO_COPY: _HOOKS_TO_COPY } = require('../scripts/build-hooks.js');
|
|
67
80
|
const INSTALLED_HOOK_FILES = new Set(_HOOKS_TO_COPY);
|
|
68
81
|
|
|
69
|
-
// ADR-857 phase 5f-1: hook-surface writer functions extracted to a dedicated
|
|
70
|
-
//
|
|
71
|
-
// (require('../bin/install.js').writeCursorHooksJson etc.)
|
|
82
|
+
// ADR-857 phase 5f-1: hook-surface writer functions extracted to a dedicated
|
|
83
|
+
// module. install.js used to re-export the whole hooksSurface surface so
|
|
84
|
+
// existing callers (require('../bin/install.js').writeCursorHooksJson etc.)
|
|
85
|
+
// kept working — #2876 found zero production/test consumers of any of those
|
|
86
|
+
// re-exports (tests import runtime-hooks-surface.cjs directly) and retired
|
|
87
|
+
// them from module.exports. install.js still requires hooksSurface below for
|
|
88
|
+
// its own internal call sites (writeCursorHooksJson, writeClineArtifacts,
|
|
89
|
+
// resolveNodeRunner, applySettingsJsonHooks, etc.).
|
|
72
90
|
const hooksSurface = require('../gsd-core/bin/lib/runtime-hooks-surface.cjs');
|
|
73
91
|
|
|
74
92
|
/**
|
|
@@ -296,13 +314,26 @@ const GSD_COPILOT_SESSION_HOOK_PWSH =
|
|
|
296
314
|
// subagentStart → gsd-cursor-subagent-start.js (subagent context injection)
|
|
297
315
|
// subagentStop → gsd-cursor-subagent-stop.js (subagent completion reminder)
|
|
298
316
|
// Cursor docs: https://cursor.com/docs/hooks
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
//
|
|
317
|
+
//
|
|
318
|
+
// These script-name/marker constants used to be independently re-declared
|
|
319
|
+
// here with their own string literals — a second, unlinked copy of exactly
|
|
320
|
+
// the values runtime-hooks-surface.cts also defines for its own internal use
|
|
321
|
+
// (buildCursorHookEntry, writeCursorHooksJson, etc.). #2876's code review
|
|
322
|
+
// found tests reading the constant from install.js's copy while calling
|
|
323
|
+
// functions built from hooksSurface's copy, with nothing guarding the two
|
|
324
|
+
// staying equal — the same unlinked-duplicate-value hazard the ADR-1508
|
|
325
|
+
// dedup elsewhere in this file exists to prevent. Fixed at the root: these
|
|
326
|
+
// are now bare references to hooksSurface's own exports, so there is exactly
|
|
327
|
+
// one literal definition of each value, full stop.
|
|
328
|
+
const GSD_CURSOR_SESSION_HOOK_SCRIPT = hooksSurface.GSD_CURSOR_SESSION_HOOK_SCRIPT;
|
|
329
|
+
const GSD_CURSOR_POST_TOOL_HOOK_SCRIPT = hooksSurface.GSD_CURSOR_POST_TOOL_HOOK_SCRIPT;
|
|
330
|
+
const GSD_CURSOR_PRE_TOOL_HOOK_SCRIPT = hooksSurface.GSD_CURSOR_PRE_TOOL_HOOK_SCRIPT;
|
|
331
|
+
const GSD_CURSOR_STOP_HOOK_SCRIPT = hooksSurface.GSD_CURSOR_STOP_HOOK_SCRIPT;
|
|
332
|
+
const GSD_CURSOR_SUBAGENT_START_HOOK_SCRIPT = hooksSurface.GSD_CURSOR_SUBAGENT_START_HOOK_SCRIPT;
|
|
333
|
+
const GSD_CURSOR_SUBAGENT_STOP_HOOK_SCRIPT = hooksSurface.GSD_CURSOR_SUBAGENT_STOP_HOOK_SCRIPT;
|
|
334
|
+
// All GSD-managed Cursor hook scripts (used by uninstall cleanup). Not
|
|
335
|
+
// independently defined in hooksSurface — built here from the bare
|
|
336
|
+
// references above, so it can never drift from them either.
|
|
306
337
|
const GSD_CURSOR_HOOK_SCRIPTS = [
|
|
307
338
|
GSD_CURSOR_SESSION_HOOK_SCRIPT,
|
|
308
339
|
GSD_CURSOR_POST_TOOL_HOOK_SCRIPT,
|
|
@@ -312,7 +343,7 @@ const GSD_CURSOR_HOOK_SCRIPTS = [
|
|
|
312
343
|
GSD_CURSOR_SUBAGENT_STOP_HOOK_SCRIPT,
|
|
313
344
|
];
|
|
314
345
|
// Marker comment embedded in managed hook entries so GSD can find+remove them.
|
|
315
|
-
const GSD_CURSOR_HOOK_MARKER =
|
|
346
|
+
const GSD_CURSOR_HOOK_MARKER = hooksSurface.GSD_CURSOR_HOOK_MARKER;
|
|
316
347
|
|
|
317
348
|
// #2100 Stage 2 — Windsurf/Cascade lifecycle hook constants.
|
|
318
349
|
// Windsurf/Cascade reads hook configs from <project-root>/.windsurf/hooks.json
|
|
@@ -328,15 +359,17 @@ const GSD_CURSOR_HOOK_MARKER = 'gsd-managed';
|
|
|
328
359
|
// have no Windsurf counterpart and are deliberately NOT ported.
|
|
329
360
|
// Cascade hooks docs (reference): https://docs.windsurf.com/llms-full.txt ,
|
|
330
361
|
// https://docs.devin.ai/desktop/cascade/hooks
|
|
331
|
-
|
|
332
|
-
|
|
362
|
+
//
|
|
363
|
+
// Same #2876 fix as the Cursor block above: bare references to hooksSurface's
|
|
364
|
+
// own exports instead of a second, unlinked literal copy.
|
|
365
|
+
const GSD_WINDSURF_PRE_WRITE_HOOK_SCRIPT = hooksSurface.GSD_WINDSURF_PRE_WRITE_HOOK_SCRIPT;
|
|
366
|
+
const GSD_WINDSURF_PRE_COMMAND_HOOK_SCRIPT = hooksSurface.GSD_WINDSURF_PRE_COMMAND_HOOK_SCRIPT;
|
|
333
367
|
// All GSD-managed Windsurf hook scripts (used by uninstall cleanup).
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
];
|
|
368
|
+
// hooksSurface independently defines the same array — bare reference here so
|
|
369
|
+
// the two can never drift.
|
|
370
|
+
const GSD_WINDSURF_HOOK_SCRIPTS = hooksSurface.GSD_WINDSURF_HOOK_SCRIPTS;
|
|
338
371
|
|
|
339
|
-
// GSD-managed files under hooks/lib/ (helpers required by gsd-*.
|
|
372
|
+
// GSD-managed files under hooks/lib/ (helpers required by gsd-*.js hooks).
|
|
340
373
|
// git-cmd.js does not start with "gsd-" (shared classifier for #3129), gsd-graphify-rebuild.sh does.
|
|
341
374
|
// cursor-workspace.js (#2587) is required by the Cursor lifecycle hooks. Those
|
|
342
375
|
// are staged individually by writeCursorHooksJson (Cursor sets
|
|
@@ -344,7 +377,10 @@ const GSD_WINDSURF_HOOK_SCRIPTS = [
|
|
|
344
377
|
// copy below) — that function stages this helper alongside them. Listing it
|
|
345
378
|
// here keeps uninstall and the manifest managing it for every OTHER runtime
|
|
346
379
|
// that does receive hooks/lib.
|
|
347
|
-
|
|
380
|
+
// injection-patterns.js (#3504) is required by gsd-prompt-guard.js and
|
|
381
|
+
// gsd-read-injection-scanner.js — the shared prompt-injection pattern list the
|
|
382
|
+
// two guards require so their copies cannot drift.
|
|
383
|
+
const GSD_HOOK_LIB_FILES = ['git-cmd.js', 'gsd-graphify-rebuild.sh', 'cursor-workspace.js', 'injection-patterns.js'];
|
|
348
384
|
|
|
349
385
|
/**
|
|
350
386
|
* Directory name GSD stages its shared hook bundle under, inside a runtime's
|
|
@@ -361,6 +397,21 @@ const GSD_HOOK_LIB_FILES = ['git-cmd.js', 'gsd-graphify-rebuild.sh', 'cursor-wor
|
|
|
361
397
|
*/
|
|
362
398
|
const SHARED_HOOKS_DIR_DEFAULT = 'hooks';
|
|
363
399
|
|
|
400
|
+
// #3184 — GSD-managed file enumerations for scripts/changeset/ and scripts/lib/
|
|
401
|
+
// uninstall. The install-side copy of both directories is wholesale ("copy every
|
|
402
|
+
// file present"), so these enumerations MUST be kept in parity with the real
|
|
403
|
+
// directory contents or an added file ships on install and then orphans on
|
|
404
|
+
// uninstall (survives removal, keeps the dir non-empty, blocks its rmdir).
|
|
405
|
+
// Hoisted to module scope (and exported below) so tests/install.test.cjs can
|
|
406
|
+
// assert parity against fs.readdirSync(scripts/lib) / fs.readdirSync(scripts/changeset)
|
|
407
|
+
// without source-grepping this file.
|
|
408
|
+
const GSD_CHANGESET_FILES = [
|
|
409
|
+
'cli.cjs', 'parse.cjs', 'render.cjs', 'serialize.cjs',
|
|
410
|
+
'github-release-notes.cjs', 'lint.cjs', 'new.cjs',
|
|
411
|
+
'README.md', // documentation only — not user-authored
|
|
412
|
+
];
|
|
413
|
+
const GSD_SCRIPTS_LIB_FILES = ['cli-exit.cjs', 'allowlist-ratchet.cjs', 'drift-scan.cjs', 'alias-drift-families.cjs'];
|
|
414
|
+
|
|
364
415
|
/**
|
|
365
416
|
* Resolve a runtime's shared-hooks directory name from its descriptor.
|
|
366
417
|
*
|
|
@@ -450,14 +501,13 @@ const pkg = require('../package.json');
|
|
|
450
501
|
// of cwd, but keeping the require at the top makes the dependency explicit and
|
|
451
502
|
// surfaces resolution failures at process start instead of at first install call.
|
|
452
503
|
const _gsdLibDir = path.join(__dirname, '..', 'gsd-core', 'bin', 'lib');
|
|
453
|
-
const { MODEL_PROFILES: GSD_MODEL_PROFILES } = require(path.join(_gsdLibDir, 'model-profiles.cjs'));
|
|
454
504
|
const {
|
|
455
505
|
RUNTIME_PROFILE_MAP: GSD_RUNTIME_PROFILE_MAP,
|
|
506
|
+
isAnthropicFlavoredModel: gsdIsAnthropicFlavoredModel,
|
|
456
507
|
} = require(path.join(_gsdLibDir, 'model-catalog.cjs'));
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
} = require(path.join(_gsdLibDir, 'model-resolver.cjs'));
|
|
508
|
+
// #2875 Part 2: MODEL_PROFILES + resolveTierEntry are now consumed only by
|
|
509
|
+
// install-model-override-resolver.cjs's readGsdRuntimeProfileResolver
|
|
510
|
+
// (required below) — this installer no longer needs its own bindings.
|
|
461
511
|
|
|
462
512
|
// #2071 — install-time effort resolution (readGsdEffectiveEffortConfig /
|
|
463
513
|
// resolveInstallTimeEffort, plus their _getGsdEffortCatalog + _readGsdConfigFile
|
|
@@ -530,6 +580,17 @@ try {
|
|
|
530
580
|
// hardcoded string-equality branch) so behavior degrades CLOSED (safe), never open.
|
|
531
581
|
// The live descriptor (capabilities/claude/capability.json) remains the source of
|
|
532
582
|
// truth; this mirrors only the privacy-load-bearing subset. (ADR-1239 / #2086)
|
|
583
|
+
//
|
|
584
|
+
// #2870: NOT routed through the Install Scope Module (resolveScope,
|
|
585
|
+
// src/install-scope.cts) despite that module owning per-scope settings-file
|
|
586
|
+
// resolution elsewhere in this file. resolveScope's own descriptor lookup
|
|
587
|
+
// goes through the SAME capability registry require this floor exists to
|
|
588
|
+
// survive the failure of (see getRegistry() in install-scope.cts) — so on
|
|
589
|
+
// exactly the "registry failed to load" path this constant is for,
|
|
590
|
+
// resolveScope would throw too. Routing through it here would trade a
|
|
591
|
+
// graceful, documented degrade for a crash in the one case this floor was
|
|
592
|
+
// added to prevent. This hardcoded literal is the correct, honest answer,
|
|
593
|
+
// not an un-migrated leftover.
|
|
533
594
|
const FALLBACK_HOST_BEHAVIORS = Object.freeze({
|
|
534
595
|
claude: Object.freeze({
|
|
535
596
|
settingsFileByScope: Object.freeze({ local: 'settings.local.json', global: 'settings.json' }),
|
|
@@ -591,6 +652,22 @@ function _hostIntegrationDispatch(runtime) {
|
|
|
591
652
|
return dispatch || {};
|
|
592
653
|
}
|
|
593
654
|
|
|
655
|
+
/**
|
|
656
|
+
* #2870: shared install()/uninstall() scope resolution. Routes `id` through
|
|
657
|
+
* the Install Scope Module (src/install-scope.cts) and degrades to `null` on
|
|
658
|
+
* failure (unknown/non-installable runtime, broken registry bundle) instead
|
|
659
|
+
* of throwing, so each call site's own plain-id fallback keeps working
|
|
660
|
+
* exactly as it did before this migration. Both call sites previously carried
|
|
661
|
+
* their own copy of this try/catch; this is the one shared copy.
|
|
662
|
+
*/
|
|
663
|
+
function _resolveScopeSafe(id, runtime) {
|
|
664
|
+
try {
|
|
665
|
+
return resolveScope({ id, runtime });
|
|
666
|
+
} catch (_) {
|
|
667
|
+
return null;
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
|
|
594
671
|
/**
|
|
595
672
|
* Resolve the ACTUAL on-disk skills-install directory for a runtime, honoring a
|
|
596
673
|
* skills-kind `home` override (ADR-1239 upgrade 3 / #2088: e.g. Codex skills ->
|
|
@@ -625,6 +702,7 @@ function _runtimeAdapter(runtime) {
|
|
|
625
702
|
const {
|
|
626
703
|
applyInstallerMigrationPlan,
|
|
627
704
|
discoverInstallerMigrations,
|
|
705
|
+
MANIFEST_SCHEMA_VERSION,
|
|
628
706
|
runInstallerMigrations,
|
|
629
707
|
} = require(path.join(_gsdLibDir, 'installer-migrations.cjs'));
|
|
630
708
|
const {
|
|
@@ -653,29 +731,78 @@ const {
|
|
|
653
731
|
// getCommitAttribution STAYS here (impure install-time config I/O); it is injected
|
|
654
732
|
// into the engine functions via the resolveAttribution parameter at each call site.
|
|
655
733
|
const installEngine = require(path.join(_gsdLibDir, 'install-engine.cjs'));
|
|
734
|
+
// #2876: _copyStaged, convertClaudeCommandToOpencodeSkill, and
|
|
735
|
+
// convertClaudeCommandToKiloSkill used to be destructured here too — all
|
|
736
|
+
// three had no install.js internal caller and no export consumer (tests
|
|
737
|
+
// import all three directly from gsd-core/bin/lib/install-engine.cjs), so
|
|
738
|
+
// the retired bindings were dead code. applyOpencodeFamilyPathPrefix,
|
|
739
|
+
// _runLegacyInstallMigrations, _runLegacyUninstallCleanup,
|
|
740
|
+
// _removeGsdEntries, _restoreDir, and _removeHermesBareStemDirs were the
|
|
741
|
+
// same — unused local bindings that were never part of this module's export
|
|
742
|
+
// surface either — found and retired in the same sweep.
|
|
656
743
|
const {
|
|
657
744
|
installRuntimeArtifacts,
|
|
658
745
|
uninstallRuntimeArtifacts,
|
|
659
746
|
installOpencodeFamilySkills,
|
|
747
|
+
installAgentsKindStandalone,
|
|
660
748
|
_installNativePluginIfDeclared,
|
|
661
|
-
_copyStaged,
|
|
662
749
|
hasExistingSymlinkBetween,
|
|
663
750
|
isSymlinkedDestOptIn,
|
|
664
|
-
preserveUserArtifacts,
|
|
665
|
-
restoreUserArtifacts,
|
|
666
751
|
migrateLegacyDevPreferencesToSkill,
|
|
667
|
-
applyOpencodeFamilyPathPrefix,
|
|
668
|
-
convertClaudeCommandToOpencodeSkill,
|
|
669
|
-
convertClaudeCommandToKiloSkill,
|
|
670
752
|
USER_OWNED_ARTIFACTS,
|
|
671
|
-
_runLegacyInstallMigrations,
|
|
672
|
-
_runLegacyUninstallCleanup,
|
|
673
|
-
_removeGsdEntries,
|
|
674
753
|
_snapshotDir,
|
|
675
|
-
_restoreDir,
|
|
676
|
-
_removeHermesBareStemDirs,
|
|
677
754
|
} = installEngine;
|
|
678
755
|
|
|
756
|
+
// #2875 (epic #2866 Phase 6): durable on-disk staging for USER_OWNED_ARTIFACTS
|
|
757
|
+
// across the preserve -> wipe -> restore window (#1874-F19). See
|
|
758
|
+
// src/user-artifact-staging.cts's module doc.
|
|
759
|
+
const {
|
|
760
|
+
stageUserArtifacts,
|
|
761
|
+
restoreStagedUserArtifacts,
|
|
762
|
+
discardStagedUserArtifacts,
|
|
763
|
+
recoverOrphanedUserArtifacts,
|
|
764
|
+
} = require(path.join(_gsdLibDir, 'user-artifact-staging.cjs'));
|
|
765
|
+
|
|
766
|
+
/**
|
|
767
|
+
* Resolve the durable staging root for `configDir`, confined via
|
|
768
|
+
* `assertDestWithinConfigHome` and refused via `hasExistingSymlinkBetween`
|
|
769
|
+
* (test-matrix E1/E4) — mirrors install-engine.cts's
|
|
770
|
+
* `_resolveUserArtifactStagingRoot`, kept local here because bin/install.js's
|
|
771
|
+
* two call sites (uninstall's legacy-migration block, install's mainline
|
|
772
|
+
* gsd-core copy) are not inside that module.
|
|
773
|
+
*/
|
|
774
|
+
function _resolveUserArtifactStagingRoot(configDir) {
|
|
775
|
+
const stagingRoot = assertDestWithinConfigHome(configDir, path.posix.join('.gsd-staging', 'user-artifacts'));
|
|
776
|
+
if (hasExistingSymlinkBetween(path.resolve(configDir), stagingRoot, { allowOptInFollow: isSymlinkedDestOptIn() })) {
|
|
777
|
+
throw new Error(
|
|
778
|
+
`_resolveUserArtifactStagingRoot: staging root "${stagingRoot}" contains a symlink the install root "${configDir}" does not trust — refusing to stage. If this is an intentional user-owned symlink layout, re-run with GSD_ALLOW_SYMLINKED_DEST=1.`,
|
|
779
|
+
);
|
|
780
|
+
}
|
|
781
|
+
return stagingRoot;
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
/**
|
|
785
|
+
* Degrade-not-abort wrapper over `_resolveUserArtifactStagingRoot` — mirrors
|
|
786
|
+
* install-engine.cts's own `_tryResolveUserArtifactStagingRoot` (kept local
|
|
787
|
+
* here for the same reason the throwing version above is: bin/install.js's
|
|
788
|
+
* own call sites are not inside that module). A hostile/broken
|
|
789
|
+
* `.gsd-staging` path (or a symlinked configDir itself) must never brick
|
|
790
|
+
* `install()` or `uninstall()` — before this fix, `_resolveUserArtifactStagingRoot`
|
|
791
|
+
* was called UNGUARDED as the first statement of both, so
|
|
792
|
+
* `ln -s /nonexistent ~/.claude/.gsd-staging` killed both commands, including
|
|
793
|
+
* uninstall, the remedy for the first problem. Returns `null` (never throws),
|
|
794
|
+
* logging one warning; every call site MUST treat `null` as "skip the
|
|
795
|
+
* staging-dependent step for this run".
|
|
796
|
+
*/
|
|
797
|
+
function _tryResolveUserArtifactStagingRoot(configDir) {
|
|
798
|
+
try {
|
|
799
|
+
return _resolveUserArtifactStagingRoot(configDir);
|
|
800
|
+
} catch (err) {
|
|
801
|
+
console.warn(` ${yellow}!${reset} user-artifact staging unavailable for "${configDir}" (${err.message}) — proceeding without durable staging for this step.`);
|
|
802
|
+
return null;
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
|
|
679
806
|
// Parse args
|
|
680
807
|
const args = process.argv.slice(2);
|
|
681
808
|
const hasGlobal = args.includes('--global') || args.includes('-g');
|
|
@@ -861,7 +988,8 @@ Then re-run: npx ${pkg.name}@latest
|
|
|
861
988
|
}
|
|
862
989
|
|
|
863
990
|
// getDirName (runtime -> local config dir name) now lives in
|
|
864
|
-
// runtime-name-policy.cjs (ADR-1508 / #1510 Phase 1); imported
|
|
991
|
+
// runtime-name-policy.cjs (ADR-1508 / #1510 Phase 1); imported above for
|
|
992
|
+
// install.js's own internal use only — #2876 retired the re-export.
|
|
865
993
|
|
|
866
994
|
/**
|
|
867
995
|
* Get the config directory path relative to home directory for a runtime
|
|
@@ -982,17 +1110,20 @@ if (hasHelp) {
|
|
|
982
1110
|
}
|
|
983
1111
|
|
|
984
1112
|
// computePathPrefix: implementation moved to runtimeArtifactConversion._computePathPrefix
|
|
985
|
-
// (ADR-1508 / #1511 Phase 2 — single owner). The const binding above
|
|
986
|
-
//
|
|
1113
|
+
// (ADR-1508 / #1511 Phase 2 — single owner). The const binding above re-binds
|
|
1114
|
+
// it here for install.js's own internal call sites only — #2876 retired the
|
|
1115
|
+
// module.exports entry (zero consumers found; tests import
|
|
1116
|
+
// runtimeArtifactConversion._computePathPrefix directly).
|
|
987
1117
|
// Original doc: Compute the path prefix used for `@file` references in installed
|
|
988
1118
|
// command/skill markdown. For global installs under $HOME uses $HOME/... form;
|
|
989
1119
|
// OpenCode always uses the absolute path (#2376 Windows, #2831 macOS/Linux).
|
|
990
1120
|
|
|
991
|
-
//
|
|
992
|
-
//
|
|
993
|
-
//
|
|
994
|
-
//
|
|
995
|
-
|
|
1121
|
+
// resolveNodeRunner, resolveBashRunner, referencesHook are now owned by the
|
|
1122
|
+
// runtime-hooks-surface module. Import them here so install.js callers
|
|
1123
|
+
// continue to work and so there is a single implementation of these helpers.
|
|
1124
|
+
// (normalizeNodePath was re-bound here too until #2876 found bin/install.js
|
|
1125
|
+
// had no internal caller and no export consumer for it — hooksSurface owns
|
|
1126
|
+
// the single implementation now, used internally by resolveNodeRunner there.)
|
|
996
1127
|
const resolveNodeRunner = hooksSurface.resolveNodeRunner;
|
|
997
1128
|
const resolveBashRunner = hooksSurface.resolveBashRunner;
|
|
998
1129
|
// referencesHook: pure predicate over hook entry objects, shared between
|
|
@@ -1010,38 +1141,27 @@ const removeKimiHooksToml = hooksSurface.removeKimiHooksToml;
|
|
|
1010
1141
|
// callers continue to work and there is a single implementation. (All call
|
|
1011
1142
|
// sites are below this line, so the const binding has no TDZ hazard.)
|
|
1012
1143
|
const processAttribution = runtimeArtifactConversion.processAttribution;
|
|
1013
|
-
// computePathPrefix
|
|
1014
|
-
//
|
|
1015
|
-
//
|
|
1016
|
-
//
|
|
1017
|
-
//
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
//
|
|
1022
|
-
// conversion module. install.js re-binds (does not re-define) these so there
|
|
1023
|
-
// is exactly one body — the generative-drift hazard the dedup removes. The two
|
|
1024
|
-
// private helpers (getAugmentSkillAdapterHeader, convertSlashCommandsToAugmentSkillMentions)
|
|
1025
|
-
// live only in the conversion module now; they are no longer duplicated here.
|
|
1144
|
+
// computePathPrefix: implementation lives in runtimeArtifactConversion
|
|
1145
|
+
// (ADR-1508 / #1511 Phase 2 — single owner). Re-bound here so install.js call
|
|
1146
|
+
// sites continue to work. #2876 retired the sibling
|
|
1147
|
+
// applyRuntimeContentRewritesInPlace / applyRuntimeContentRewritesForCommandsInPlace
|
|
1148
|
+
// re-bindings that used to sit alongside it, and the entire #1675 Augment
|
|
1149
|
+
// converter family re-binding (convertClaudeToAugmentMarkdown /
|
|
1150
|
+
// convertClaudeCommandToAugmentSkill / convertClaudeAgentToAugmentAgent) that
|
|
1151
|
+
// used to follow — bin/install.js had no internal caller for any of them (the
|
|
1152
|
+
// descriptor pipeline in runtimeArtifactConversion calls them directly).
|
|
1026
1153
|
// (All call sites are below this line → no TDZ hazard.)
|
|
1027
|
-
const
|
|
1028
|
-
const convertClaudeCommandToAugmentSkill = runtimeArtifactConversion.convertClaudeCommandToAugmentSkill;
|
|
1029
|
-
const convertClaudeAgentToAugmentAgent = runtimeArtifactConversion.convertClaudeAgentToAugmentAgent;
|
|
1154
|
+
const computePathPrefix = runtimeArtifactConversion._computePathPrefix;
|
|
1030
1155
|
// #2931 (ADR-1508): the windsurf converter family is single-sourced in the
|
|
1031
|
-
// conversion module
|
|
1032
|
-
//
|
|
1033
|
-
// generative-drift hazard the dedup removes.
|
|
1034
|
-
//
|
|
1035
|
-
//
|
|
1036
|
-
//
|
|
1037
|
-
//
|
|
1038
|
-
// guard describe block), not tests/enh-1511-rewrite-engine-relocation.test.cjs
|
|
1039
|
-
// as the Augment comment above stated — that reference was stale.
|
|
1156
|
+
// conversion module. install.js re-binds (does not re-define) the one member
|
|
1157
|
+
// it still calls internally so there is exactly one body — the
|
|
1158
|
+
// generative-drift hazard the dedup removes. #2876 retired the sibling
|
|
1159
|
+
// convertClaudeCommandToWindsurfSkill / convertClaudeCommandToWindsurfWorkflow /
|
|
1160
|
+
// convertClaudeAgentToWindsurfAgent re-bindings — bin/install.js had no
|
|
1161
|
+
// internal caller for any of them (the descriptor pipeline in
|
|
1162
|
+
// runtimeArtifactConversion calls them directly).
|
|
1040
1163
|
// (All call sites are below this line → no TDZ hazard.)
|
|
1041
1164
|
const convertClaudeToWindsurfMarkdown = runtimeArtifactConversion.convertClaudeToWindsurfMarkdown;
|
|
1042
|
-
const convertClaudeCommandToWindsurfSkill = runtimeArtifactConversion.convertClaudeCommandToWindsurfSkill;
|
|
1043
|
-
const convertClaudeCommandToWindsurfWorkflow = runtimeArtifactConversion.convertClaudeCommandToWindsurfWorkflow;
|
|
1044
|
-
const convertClaudeAgentToWindsurfAgent = runtimeArtifactConversion.convertClaudeAgentToWindsurfAgent;
|
|
1045
1165
|
// #2931 (ADR-1508): single-sourced in the conversion module — was a second,
|
|
1046
1166
|
// unlinked verbatim copy here (used by the local Cursor/Trae/CodeBuddy/Cline
|
|
1047
1167
|
// converters below), the exact drift class this PR exists to reduce. Verified
|
|
@@ -1057,116 +1177,14 @@ function rewriteLegacyManagedNodeHookCommands(settings, absoluteRunner, opts) {
|
|
|
1057
1177
|
return hooksSurface.rewriteLegacyManagedNodeHookCommands(settings, absoluteRunner, opts);
|
|
1058
1178
|
}
|
|
1059
1179
|
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
* Returns null when `absoluteRunner` is null so callers can warn-and-skip
|
|
1069
|
-
* registration — emitting a broken bare-node hook is strictly worse than
|
|
1070
|
-
* not registering one (the user can re-run install once node is on PATH).
|
|
1071
|
-
*
|
|
1072
|
-
* @param {string} targetDir - Resolved absolute Codex config dir (e.g. ~/.codex).
|
|
1073
|
-
* @param {{ absoluteRunner: string|null, eol?: string }} opts
|
|
1074
|
-
* absoluteRunner: result of resolveNodeRunner() — a JSON-stringified
|
|
1075
|
-
* absolute node path with forward slashes (e.g. `"/usr/local/bin/node"`),
|
|
1076
|
-
* or null when process.execPath was unavailable.
|
|
1077
|
-
* eol: line ending to emit ('\n' or '\r\n') — caller passes
|
|
1078
|
-
* detectLineEnding(configContent) so existing CRLF files stay CRLF.
|
|
1079
|
-
* Defaults to '\n'.
|
|
1080
|
-
* @returns {string|null} The toml block to append, or null on missing runner.
|
|
1081
|
-
*/
|
|
1082
|
-
function buildCodexHookBlock(targetDir, opts) {
|
|
1083
|
-
return hooksSurface.buildCodexHookBlock(targetDir, opts);
|
|
1084
|
-
}
|
|
1085
|
-
|
|
1086
|
-
/**
|
|
1087
|
-
* Rewrite legacy bare-`node` managed-hook command lines in a Codex
|
|
1088
|
-
* config.toml string to use the absolute Node runner. Mirror of
|
|
1089
|
-
* rewriteLegacyManagedNodeHookCommands but for the toml surface (#3017).
|
|
1090
|
-
*
|
|
1091
|
-
* Only rewrites entries whose script basename matches CODEX_MANAGED_HOOK_BASENAMES
|
|
1092
|
-
* (basename equality, not substring containment) — user-authored bare-node
|
|
1093
|
-
* hooks pointing at scripts outside the managed allowlist are left alone.
|
|
1094
|
-
*
|
|
1095
|
-
* @param {string} content - Current config.toml contents.
|
|
1096
|
-
* @param {string|null} absoluteRunner - Result of resolveNodeRunner().
|
|
1097
|
-
* @returns {{ content: string, changed: boolean }}
|
|
1098
|
-
*/
|
|
1099
|
-
function rewriteLegacyCodexHookBlock(content, absoluteRunner, opts) {
|
|
1100
|
-
return hooksSurface.rewriteLegacyCodexHookBlock(content, absoluteRunner, opts);
|
|
1101
|
-
}
|
|
1102
|
-
|
|
1103
|
-
/**
|
|
1104
|
-
* Generic reconcile helper: ensure hooks.json contains exactly one managed GSD
|
|
1105
|
-
* hook entry for `eventName`, while preserving all user-owned entries.
|
|
1106
|
-
*
|
|
1107
|
-
* Supports both known hooks.json shapes:
|
|
1108
|
-
* 1) { "<EventName>": [...] }
|
|
1109
|
-
* 2) { "hooks": { "<EventName>": [...] } }
|
|
1110
|
-
*
|
|
1111
|
-
* @param {string} targetDir - Codex config dir (e.g. ~/.codex or <project>/.codex).
|
|
1112
|
-
* @param {string} eventName - Codex hook event name (e.g. 'SessionStart', 'Stop').
|
|
1113
|
-
* @param {{ managedCommand?: string|null, commandWindows?: string|null, matcher?: string|null, timeout?: number|null }} opts
|
|
1114
|
-
* managedCommand: POSIX hook command string to register, or null to remove.
|
|
1115
|
-
* commandWindows: Windows .cmd shim path to emit as `commandWindows` field
|
|
1116
|
-
* (#772). When provided, Codex uses this path on Windows and `managedCommand`
|
|
1117
|
-
* on POSIX without needing per-platform config regeneration.
|
|
1118
|
-
* matcher: optional Codex MatcherGroup pattern (e.g. 'Bash|Edit|Write').
|
|
1119
|
-
* timeout: optional timeout in seconds.
|
|
1120
|
-
* @returns {{ changed: boolean, wrote: boolean, path: string }}
|
|
1121
|
-
*/
|
|
1122
|
-
function reconcileCodexHooksJsonEvent(targetDir, eventName, opts = {}) {
|
|
1123
|
-
return hooksSurface.reconcileCodexHooksJsonEvent(targetDir, eventName, opts);
|
|
1124
|
-
}
|
|
1125
|
-
|
|
1126
|
-
/**
|
|
1127
|
-
* Reconcile the GSD-managed SessionStart hook entry in hooks.json.
|
|
1128
|
-
* Delegates to the generic reconcileCodexHooksJsonEvent helper.
|
|
1129
|
-
*
|
|
1130
|
-
* @param {string} targetDir
|
|
1131
|
-
* @param {{ managedCommand?: string|null, commandWindows?: string|null }} opts
|
|
1132
|
-
* @returns {{ changed: boolean, wrote: boolean, path: string }}
|
|
1133
|
-
*/
|
|
1134
|
-
function reconcileCodexHooksJsonSessionStart(targetDir, opts = {}) {
|
|
1135
|
-
return hooksSurface.reconcileCodexHooksJsonSessionStart(targetDir, opts);
|
|
1136
|
-
}
|
|
1137
|
-
|
|
1138
|
-
/**
|
|
1139
|
-
* Build a typed IR for the Codex hook .cmd shim used on Windows (#3426).
|
|
1140
|
-
*
|
|
1141
|
-
* On Windows, Codex runs hook commands from a PowerShell/cmd execution
|
|
1142
|
-
* environment. The previous command format was:
|
|
1143
|
-
*
|
|
1144
|
-
* "C:/Program Files/nodejs/node.exe" "C:/path/.codex/hooks/gsd-check-update.js"
|
|
1145
|
-
*
|
|
1146
|
-
* This caused `bash.exe: bash.exe: cannot execute binary file` because
|
|
1147
|
-
* Codex's hook dispatch shell (Git Bash / MSYS) tried to POSIX-exec node.exe
|
|
1148
|
-
* (a Windows PE binary) via execvp(), which fails with ENOEXEC on Windows PE
|
|
1149
|
-
* binaries that the MSYS layer doesn't know how to fork-exec natively.
|
|
1150
|
-
*
|
|
1151
|
-
* Fix: write a .cmd shim (using the same CRLF .cmd shim pattern) whose
|
|
1152
|
-
* content is `@ECHO OFF / @SETLOCAL / @"node.exe" "script.js" %*`.
|
|
1153
|
-
* cmd.exe executes
|
|
1154
|
-
* .cmd natively via CreateProcess — no POSIX exec layer, no MSYS shebang
|
|
1155
|
-
* walk, no PE binary fork-exec failure.
|
|
1156
|
-
*
|
|
1157
|
-
* Returns the typed IR `{ invocation, cmdPath, hookCommand, render }` so
|
|
1158
|
-
* callers can assert on the structured shape (CONTRIBUTING.md L558–L565
|
|
1159
|
-
* IR-first discipline). Returns null when absoluteRunnerToken is null so
|
|
1160
|
-
* callers can warn-and-skip instead of writing a broken hook.
|
|
1161
|
-
*
|
|
1162
|
-
* @param {string} scriptAbsPath - Absolute path to the .js hook script.
|
|
1163
|
-
* @param {string|null} absoluteRunnerToken - JSON-quoted absolute node path
|
|
1164
|
-
* (result of resolveNodeRunner()), e.g. `"C:/Program Files/nodejs/node.exe"`.
|
|
1165
|
-
* @returns {{ invocation: { interpreter: string, target: string }, cmdPath: string, hookCommand: string, render: { cmd: () => string } }|null}
|
|
1166
|
-
*/
|
|
1167
|
-
function buildCodexHookWindowsShimIR(scriptAbsPath, absoluteRunnerToken) {
|
|
1168
|
-
return hooksSurface.buildCodexHookWindowsShimIR(scriptAbsPath, absoluteRunnerToken);
|
|
1169
|
-
}
|
|
1180
|
+
// #2876: reconcileManagedShellHookCommands, buildCodexHookBlock,
|
|
1181
|
+
// rewriteLegacyCodexHookBlock, reconcileCodexHooksJsonEvent, and
|
|
1182
|
+
// reconcileCodexHooksJsonSessionStart used to be re-bound here as one-line
|
|
1183
|
+
// delegates to the equivalent hooksSurface.* implementations. None had an
|
|
1184
|
+
// install.js internal caller or an export consumer (tests import all five
|
|
1185
|
+
// directly from gsd-core/bin/lib/runtime-hooks-surface.cjs), so the retired
|
|
1186
|
+
// bindings were dead code with no reachable body — removed rather than kept
|
|
1187
|
+
// as unreachable wrappers.
|
|
1170
1188
|
|
|
1171
1189
|
/**
|
|
1172
1190
|
* Ensure Codex hooks.json contains exactly one managed SessionStart
|
|
@@ -1365,275 +1383,30 @@ function writeSettings(settingsPath, settings) {
|
|
|
1365
1383
|
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n');
|
|
1366
1384
|
}
|
|
1367
1385
|
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
*
|
|
1393
|
-
* Merges `~/.gsd/defaults.json` (global) with per-project
|
|
1394
|
-
* `<project>/.planning/config.json`. Per-project keys win on conflict so a
|
|
1395
|
-
* user can tune a single agent's model in one repo without re-setting the
|
|
1396
|
-
* global defaults for every other repo. Non-conflicting keys from both
|
|
1397
|
-
* sources are preserved.
|
|
1398
|
-
*
|
|
1399
|
-
* This is the fix for #2256: both adapters previously read only the global
|
|
1400
|
-
* file, so a per-project `model_overrides` (the common case the reporter
|
|
1401
|
-
* described — a per-project override for `gsd-codebase-mapper` in
|
|
1402
|
-
* `.planning/config.json`) was silently dropped and child agents inherited
|
|
1403
|
-
* the session default.
|
|
1404
|
-
*
|
|
1405
|
-
* `targetDir` is the consuming runtime's install root (e.g. `~/.codex` for
|
|
1406
|
-
* a global install, or `<project>/.codex` for a local install). We walk up
|
|
1407
|
-
* from there looking for `.planning/` so both cases resolve the correct
|
|
1408
|
-
* project root. When `targetDir` is null/undefined only the global file is
|
|
1409
|
-
* consulted (matches prior behavior for code paths that have no project
|
|
1410
|
-
* context).
|
|
1411
|
-
*
|
|
1412
|
-
* Returns a plain `{ agentName: modelId }` object, or `null` when neither
|
|
1413
|
-
* source defines `model_overrides`.
|
|
1414
|
-
*/
|
|
1415
|
-
function readGsdEffectiveModelOverrides(targetDir = null, options = {}) {
|
|
1416
|
-
const global = readGsdGlobalModelOverrides(options);
|
|
1417
|
-
|
|
1418
|
-
let projectOverrides = null;
|
|
1419
|
-
if (targetDir) {
|
|
1420
|
-
let probeDir = path.resolve(targetDir);
|
|
1421
|
-
for (let depth = 0; depth < 8; depth += 1) {
|
|
1422
|
-
const candidate = path.join(probeDir, '.planning', 'config.json');
|
|
1423
|
-
if (fs.existsSync(candidate)) {
|
|
1424
|
-
try {
|
|
1425
|
-
const parsed = JSON.parse(fs.readFileSync(candidate, 'utf-8'));
|
|
1426
|
-
if (parsed && typeof parsed === 'object' && parsed.model_overrides
|
|
1427
|
-
&& typeof parsed.model_overrides === 'object') {
|
|
1428
|
-
projectOverrides = parsed.model_overrides;
|
|
1429
|
-
}
|
|
1430
|
-
} catch {
|
|
1431
|
-
// Malformed config.json — fall back to global; readGsdRuntimeProfileResolver
|
|
1432
|
-
// surfaces a parse warning via _readGsdConfigFile already.
|
|
1433
|
-
}
|
|
1434
|
-
break;
|
|
1435
|
-
}
|
|
1436
|
-
const parent = path.dirname(probeDir);
|
|
1437
|
-
if (parent === probeDir) break;
|
|
1438
|
-
probeDir = parent;
|
|
1439
|
-
}
|
|
1440
|
-
}
|
|
1441
|
-
|
|
1442
|
-
if (!global && !projectOverrides) return null;
|
|
1443
|
-
// Per-project wins on conflict; preserve non-conflicting global keys.
|
|
1444
|
-
return { ...(global || {}), ...(projectOverrides || {}) };
|
|
1445
|
-
}
|
|
1446
|
-
|
|
1447
|
-
/**
|
|
1448
|
-
* #443 — Inject `effort: <value>` into YAML frontmatter of a Claude .md agent
|
|
1449
|
-
* file in a newline-agnostic way (LF and CRLF source files are both handled).
|
|
1450
|
-
*
|
|
1451
|
-
* The function:
|
|
1452
|
-
* - Detects the file's EOL (CRLF if the first `---` line ends with \r\n,
|
|
1453
|
-
* otherwise LF).
|
|
1454
|
-
* - Skips injection if an `effort:` key already exists in the frontmatter
|
|
1455
|
-
* (idempotent).
|
|
1456
|
-
* - Inserts `effort: <value>` immediately before the closing `---` delimiter,
|
|
1457
|
-
* using the same EOL as the surrounding frontmatter so the output file
|
|
1458
|
-
* stays EOL-consistent.
|
|
1459
|
-
* - Returns the original content unchanged when no YAML frontmatter is found.
|
|
1460
|
-
*
|
|
1461
|
-
* @param {string} content Raw file content (may have LF or CRLF endings).
|
|
1462
|
-
* @param {string} effortValue Rendered effort string, e.g. "xhigh".
|
|
1463
|
-
* @returns {string} Updated content with `effort:` injected, or the
|
|
1464
|
-
* original content when no frontmatter is found.
|
|
1465
|
-
*/
|
|
1466
|
-
function injectEffortFrontmatter(content, effortValue) {
|
|
1467
|
-
// Detect the dominant EOL from the first line (the opening `---`).
|
|
1468
|
-
// If the very first `---` is followed by \r\n, treat the whole file as CRLF.
|
|
1469
|
-
const eol = /^---\r\n/.test(content) ? '\r\n' : '\n';
|
|
1470
|
-
|
|
1471
|
-
// Build a frontmatter-matching regex that tolerates an optional \r before
|
|
1472
|
-
// each \n, so we handle both LF and CRLF files without needing to normalise
|
|
1473
|
-
// the whole content.
|
|
1474
|
-
//
|
|
1475
|
-
// Breakdown:
|
|
1476
|
-
// ^---\r?\n — opening delimiter (with optional \r)
|
|
1477
|
-
// ([\s\S]*?) — frontmatter body (non-greedy)
|
|
1478
|
-
// ^---\r?$ — closing delimiter line (optional \r, $ before \n in
|
|
1479
|
-
// multiline mode)
|
|
1480
|
-
// (\r?\n|$) — newline after closing --- (or end of string)
|
|
1481
|
-
//
|
|
1482
|
-
// The `m` flag makes ^ / $ match at every line boundary.
|
|
1483
|
-
const fmRe = /^---\r?\n([\s\S]*?)^---\r?$/m;
|
|
1484
|
-
const match = fmRe.exec(content);
|
|
1485
|
-
if (!match) return content; // no YAML frontmatter — leave unchanged
|
|
1486
|
-
|
|
1487
|
-
// Idempotency guard: don't insert a second effort: line.
|
|
1488
|
-
const fmBody = match[1]; // content between the two `---` lines
|
|
1489
|
-
if (/^effort:/m.test(fmBody)) return content;
|
|
1490
|
-
|
|
1491
|
-
// Locate the exact position of the closing `---` line so we can insert
|
|
1492
|
-
// before it using a simple string splice (avoids re-running the regex and
|
|
1493
|
-
// avoids any edge-cases with $ matching \r differently per engine).
|
|
1494
|
-
const closeIdx = match.index + 4 + fmBody.length; // 4 = len("---\n") (opening)
|
|
1495
|
-
// Actually compute based on the full match start + captured group length:
|
|
1496
|
-
// match[0] = full frontmatter block; match.index = start of that block.
|
|
1497
|
-
// The closing `---` starts at: match.index + ("---" + eol).length + fmBody.length
|
|
1498
|
-
const openLen = 3 + eol.length; // "---" + eol
|
|
1499
|
-
const closingStart = match.index + openLen + fmBody.length;
|
|
1500
|
-
|
|
1501
|
-
const before = content.slice(0, closingStart);
|
|
1502
|
-
const after = content.slice(closingStart);
|
|
1503
|
-
return `${before}effort: ${effortValue}${eol}${after}`;
|
|
1504
|
-
}
|
|
1505
|
-
|
|
1506
|
-
/**
|
|
1507
|
-
* #767 — Inject `disallowedTools: <value>` into the YAML frontmatter of a Claude .md agent.
|
|
1508
|
-
* Mirrors injectEffortFrontmatter: idempotent (skips if disallowedTools: already present),
|
|
1509
|
-
* inserts immediately before the closing `---`. Claude-only — never call for other runtimes,
|
|
1510
|
-
* which break on unknown frontmatter keys.
|
|
1511
|
-
*/
|
|
1512
|
-
function injectDisallowedToolsFrontmatter(content, disallowedValue) {
|
|
1513
|
-
// Detect the dominant EOL from the first line (the opening `---`).
|
|
1514
|
-
// If the very first `---` is followed by \r\n, treat the whole file as CRLF.
|
|
1515
|
-
const eol = /^---\r\n/.test(content) ? '\r\n' : '\n';
|
|
1516
|
-
|
|
1517
|
-
// Build a frontmatter-matching regex that tolerates an optional \r before
|
|
1518
|
-
// each \n, so we handle both LF and CRLF files without needing to normalise
|
|
1519
|
-
// the whole content.
|
|
1520
|
-
const fmRe = /^---\r?\n([\s\S]*?)^---\r?$/m;
|
|
1521
|
-
const match = fmRe.exec(content);
|
|
1522
|
-
if (!match) return content; // no YAML frontmatter — leave unchanged
|
|
1523
|
-
|
|
1524
|
-
// Idempotency guard: don't insert a second disallowedTools: line.
|
|
1525
|
-
const fmBody = match[1]; // content between the two `---` lines
|
|
1526
|
-
if (/^disallowedTools:/m.test(fmBody)) return content;
|
|
1527
|
-
|
|
1528
|
-
// Locate the exact position of the closing `---` line so we can insert
|
|
1529
|
-
// before it using a simple string splice.
|
|
1530
|
-
const openLen = 3 + eol.length; // "---" + eol
|
|
1531
|
-
const closingStart = match.index + openLen + fmBody.length;
|
|
1532
|
-
|
|
1533
|
-
const before = content.slice(0, closingStart);
|
|
1534
|
-
const after = content.slice(closingStart);
|
|
1535
|
-
return `${before}disallowedTools: ${disallowedValue}${eol}${after}`;
|
|
1536
|
-
}
|
|
1537
|
-
|
|
1538
|
-
// #767 — Read-only verifier/auditor agents get a Claude-Code disallowedTools deny-list.
|
|
1539
|
-
// Group A (pure read-only) deny Write,Edit,MultiEdit. Group B report-writers Write one
|
|
1540
|
-
// output file so they deny only Edit,MultiEdit. gsd-nyquist-auditor is intentionally
|
|
1541
|
-
// excluded (it legitimately uses Write AND Edit to create/patch test files).
|
|
1542
|
-
const READONLY_AGENT_DISALLOWED_TOOLS = {
|
|
1543
|
-
'gsd-plan-checker': 'Write, Edit, MultiEdit',
|
|
1544
|
-
'gsd-integration-checker': 'Write, Edit, MultiEdit',
|
|
1545
|
-
'gsd-ui-checker': 'Write, Edit, MultiEdit',
|
|
1546
|
-
'gsd-verifier': 'Edit, MultiEdit',
|
|
1547
|
-
'gsd-doc-verifier': 'Edit, MultiEdit',
|
|
1548
|
-
'gsd-eval-auditor': 'Edit, MultiEdit',
|
|
1549
|
-
'gsd-ui-auditor': 'Edit, MultiEdit',
|
|
1550
|
-
};
|
|
1551
|
-
|
|
1552
|
-
/**
|
|
1553
|
-
* #2517 — Build a runtime-aware tier resolver for the install path.
|
|
1554
|
-
*
|
|
1555
|
-
* Probes BOTH per-project `<targetDir>/.planning/config.json` AND
|
|
1556
|
-
* `~/.gsd/defaults.json`, with per-project keys winning over global. This
|
|
1557
|
-
* matches `loadConfig`'s precedence and is the only way the PR's headline claim
|
|
1558
|
-
* — "set runtime in .planning/config.json and the Codex TOML emit picks it up"
|
|
1559
|
-
* — actually holds end-to-end (review finding #1).
|
|
1560
|
-
*
|
|
1561
|
-
* `targetDir` should be the consuming runtime's install root — install code
|
|
1562
|
-
* passes `path.dirname(<runtime root>)` so `.planning/config.json` resolves
|
|
1563
|
-
* relative to the user's project. When `targetDir` is null/undefined, only the
|
|
1564
|
-
* global defaults are consulted.
|
|
1565
|
-
*
|
|
1566
|
-
* Returns null if no `runtime` is configured (preserves prior behavior — only
|
|
1567
|
-
* model_overrides is embedded, no tier/reasoning-effort inference). Returns
|
|
1568
|
-
* null when `model_profile` is `inherit` so the literal alias passes through
|
|
1569
|
-
* unchanged.
|
|
1570
|
-
*
|
|
1571
|
-
* Returns { runtime, resolve(agentName) -> { model, reasoning_effort? } | null }
|
|
1572
|
-
*/
|
|
1573
|
-
function readGsdRuntimeProfileResolver(targetDir = null) {
|
|
1574
|
-
const homeDefaults = _readGsdConfigFile(
|
|
1575
|
-
path.join(os.homedir(), '.gsd', 'defaults.json'),
|
|
1576
|
-
'~/.gsd/defaults.json'
|
|
1577
|
-
);
|
|
1578
|
-
|
|
1579
|
-
// Per-project config probe. Resolve the project root by walking up from
|
|
1580
|
-
// targetDir until we hit a `.planning/` directory; this covers both the
|
|
1581
|
-
// common case (caller passes the project root) and the case where caller
|
|
1582
|
-
// passes a nested install dir like `<root>/.codex/`.
|
|
1583
|
-
let projectConfig = null;
|
|
1584
|
-
if (targetDir) {
|
|
1585
|
-
let probeDir = path.resolve(targetDir);
|
|
1586
|
-
for (let depth = 0; depth < 8; depth += 1) {
|
|
1587
|
-
const candidate = path.join(probeDir, '.planning', 'config.json');
|
|
1588
|
-
if (fs.existsSync(candidate)) {
|
|
1589
|
-
projectConfig = _readGsdConfigFile(candidate, '.planning/config.json');
|
|
1590
|
-
break;
|
|
1591
|
-
}
|
|
1592
|
-
const parent = path.dirname(probeDir);
|
|
1593
|
-
if (parent === probeDir) break;
|
|
1594
|
-
probeDir = parent;
|
|
1595
|
-
}
|
|
1596
|
-
}
|
|
1597
|
-
|
|
1598
|
-
// Per-project wins. Only fall back to ~/.gsd/defaults.json when the project
|
|
1599
|
-
// didn't set the field. Field-level merge (not whole-object replace) so a
|
|
1600
|
-
// user can keep `runtime` global while overriding only `model_profile` per
|
|
1601
|
-
// project, and vice versa.
|
|
1602
|
-
const merged = {
|
|
1603
|
-
runtime:
|
|
1604
|
-
(projectConfig && projectConfig.runtime) ||
|
|
1605
|
-
(homeDefaults && homeDefaults.runtime) ||
|
|
1606
|
-
null,
|
|
1607
|
-
model_profile:
|
|
1608
|
-
(projectConfig && projectConfig.model_profile) ||
|
|
1609
|
-
(homeDefaults && homeDefaults.model_profile) ||
|
|
1610
|
-
'balanced',
|
|
1611
|
-
model_profile_overrides:
|
|
1612
|
-
(projectConfig && projectConfig.model_profile_overrides) ||
|
|
1613
|
-
(homeDefaults && homeDefaults.model_profile_overrides) ||
|
|
1614
|
-
null,
|
|
1615
|
-
};
|
|
1616
|
-
|
|
1617
|
-
if (!merged.runtime) return null;
|
|
1618
|
-
|
|
1619
|
-
const profile = String(merged.model_profile).toLowerCase();
|
|
1620
|
-
if (profile === 'inherit') return null;
|
|
1621
|
-
|
|
1622
|
-
return {
|
|
1623
|
-
runtime: merged.runtime,
|
|
1624
|
-
resolve(agentName) {
|
|
1625
|
-
const agentModels = GSD_MODEL_PROFILES[agentName];
|
|
1626
|
-
if (!agentModels) return null;
|
|
1627
|
-
const tier = agentModels[profile] || agentModels.balanced;
|
|
1628
|
-
if (!tier) return null;
|
|
1629
|
-
return gsdResolveTierEntry({
|
|
1630
|
-
runtime: merged.runtime,
|
|
1631
|
-
tier,
|
|
1632
|
-
overrides: merged.model_profile_overrides,
|
|
1633
|
-
});
|
|
1634
|
-
},
|
|
1635
|
-
};
|
|
1636
|
-
}
|
|
1386
|
+
// #2875 Part 2 (J8): model-override resolution (readGsdGlobalModelOverrides /
|
|
1387
|
+
// readGsdEffectiveModelOverrides / readGsdRuntimeProfileResolver, plus the
|
|
1388
|
+
// shared resolveAgentModelOverride precedence chain) was extracted into the
|
|
1389
|
+
// shipped gsd-core/bin/lib/install-model-override-resolver.cjs, mirroring
|
|
1390
|
+
// install-effort-resolver.cjs's existing #2071 precedent, so the descriptor-
|
|
1391
|
+
// driven agents pipeline (runtime-artifact-layout.cts's convertedAgentsKind)
|
|
1392
|
+
// and this installer resolve model_overrides / model_profile_overrides
|
|
1393
|
+
// through the SAME code — a single source of truth for the precedence chain
|
|
1394
|
+
// the inline agent loop below used to duplicate across ~24 lines per runtime
|
|
1395
|
+
// (kilo, opencode). See install-model-override-resolver.cts's module doc.
|
|
1396
|
+
const {
|
|
1397
|
+
readGsdEffectiveModelOverrides,
|
|
1398
|
+
readGsdRuntimeProfileResolver,
|
|
1399
|
+
resolveAgentModelOverride,
|
|
1400
|
+
} = require(path.join(_gsdLibDir, 'install-model-override-resolver.cjs'));
|
|
1401
|
+
|
|
1402
|
+
// #2875 Part 2: effort frontmatter injection moved to runtimeArtifactConversion
|
|
1403
|
+
// (single source of truth with the descriptor pipeline's
|
|
1404
|
+
// applyAgentFrontmatterExtensions step, which now also owns disallowedTools
|
|
1405
|
+
// injection + the read-only agent deny-list internally — see its module doc
|
|
1406
|
+
// in src/runtime-artifact-conversion.cts). injectEffortFrontmatter used to be
|
|
1407
|
+
// re-bound here purely to stay on this module's export surface; #2876 found
|
|
1408
|
+
// no install.js internal caller either (tests import it directly from
|
|
1409
|
+
// gsd-core/bin/lib/runtime-artifact-conversion.cjs) and retired the binding.
|
|
1637
1410
|
|
|
1638
1411
|
// Cache for attribution settings (populated once per runtime during install)
|
|
1639
1412
|
const attributionCache = new Map();
|
|
@@ -1922,10 +1695,6 @@ function buildKiloAgentPermissionBlock(claudeTools) {
|
|
|
1922
1695
|
return lines;
|
|
1923
1696
|
}
|
|
1924
1697
|
|
|
1925
|
-
function escapeRegExp(value) {
|
|
1926
|
-
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
1927
|
-
}
|
|
1928
|
-
|
|
1929
1698
|
function replaceRelativePathReference(content, fromPath, toPath) {
|
|
1930
1699
|
const escapedPath = escapeRegExp(fromPath);
|
|
1931
1700
|
return content.replace(
|
|
@@ -2046,12 +1815,6 @@ function skillFrontmatterName(skillDirName) {
|
|
|
2046
1815
|
return skillDirName;
|
|
2047
1816
|
}
|
|
2048
1817
|
|
|
2049
|
-
function normalizeClaudeSkillEffort(effort) {
|
|
2050
|
-
// #3039: `max` is rejected by Anthropic models when extended thinking is disabled.
|
|
2051
|
-
if (effort === 'xhigh' || effort === 'max') return 'high';
|
|
2052
|
-
return effort;
|
|
2053
|
-
}
|
|
2054
|
-
|
|
2055
1818
|
/**
|
|
2056
1819
|
* Qwen Code skills accept an optional numeric `priority` frontmatter field.
|
|
2057
1820
|
* Per the Qwen skills spec (qwen-code/docs/users/features/skills.md, verified
|
|
@@ -2108,10 +1871,11 @@ function convertClaudeCommandToClaudeSkill(content, skillName, runtime = null, c
|
|
|
2108
1871
|
const description = extractFrontmatterField(frontmatter, 'description') || '';
|
|
2109
1872
|
const argumentHint = extractFrontmatterField(frontmatter, 'argument-hint');
|
|
2110
1873
|
const agent = extractFrontmatterField(frontmatter, 'agent');
|
|
2111
|
-
// #769: preserve context:
|
|
2112
|
-
//
|
|
1874
|
+
// #769: preserve context: from source command files so it is emitted into
|
|
1875
|
+
// the installed SKILL.md frontmatter unchanged. (#3151: effort: is no longer
|
|
1876
|
+
// emitted into skill frontmatter — a static effort value invalidates the
|
|
1877
|
+
// caller's prompt cache at both scope boundaries.)
|
|
2113
1878
|
const context = extractFrontmatterField(frontmatter, 'context');
|
|
2114
|
-
const effort = extractFrontmatterField(frontmatter, 'effort');
|
|
2115
1879
|
|
|
2116
1880
|
// Preserve allowed-tools as YAML multiline list (Claude native format)
|
|
2117
1881
|
const toolsMatch = frontmatter.match(/^allowed-tools:\s*\n((?:\s+-\s+.+\n?)*)/m);
|
|
@@ -2145,12 +1909,13 @@ function convertClaudeCommandToClaudeSkill(content, skillName, runtime = null, c
|
|
|
2145
1909
|
}
|
|
2146
1910
|
if (argumentHint) fm += `argument-hint: ${yamlQuote(argumentHint)}\n`;
|
|
2147
1911
|
if (agent) fm += `agent: ${agent}\n`;
|
|
2148
|
-
// #769: emit context:
|
|
2149
|
-
//
|
|
2150
|
-
//
|
|
2151
|
-
//
|
|
1912
|
+
// #769: emit context: when present so the runtime can honour it natively
|
|
1913
|
+
// (context: fork = isolated subagent window). Claude-specific; unknown
|
|
1914
|
+
// frontmatter fields are silently ignored by other runtimes (backward-compatible).
|
|
1915
|
+
// (#3151: effort: is intentionally NOT emitted into skill frontmatter — a
|
|
1916
|
+
// static effort value changes output_config.effort on invocation and
|
|
1917
|
+
// invalidates the caller's prompt cache at both scope boundaries.)
|
|
2152
1918
|
if (context) fm += `context: ${context}\n`;
|
|
2153
|
-
if (effort) fm += `effort: ${normalizeClaudeSkillEffort(effort)}\n`;
|
|
2154
1919
|
if (toolsBlock) fm += toolsBlock;
|
|
2155
1920
|
fm += '---';
|
|
2156
1921
|
|
|
@@ -3964,7 +3729,7 @@ Typed mapping (agent_type-capable schema only):
|
|
|
3964
3729
|
to \`spawn_agent\` when the runtime/tool supports it. Omit missing, empty,
|
|
3965
3730
|
inherited, or unsupported values; do not invent one-off effort literals in
|
|
3966
3731
|
workflow prose.
|
|
3967
|
-
- \`fork_context: false\` by default — GSD agents load their own context via \`<
|
|
3732
|
+
- \`fork_context: false\` by default — GSD agents load their own context via \`<required_reading>\` blocks
|
|
3968
3733
|
- \`task_name\` — required by the collaboration schema; provide a descriptive name for each spawned task
|
|
3969
3734
|
- \`fork_turns\` — optional parameter controlling turn-forking depth; coexists with \`fork_context\` (not a replacement)
|
|
3970
3735
|
- \`Task(isolation="worktree")\` / \`Agent(isolation="worktree")\` → no direct \`spawn_agent\` mapping,
|
|
@@ -4059,15 +3824,19 @@ purpose: ${toSingleLine(description)}
|
|
|
4059
3824
|
/**
|
|
4060
3825
|
* #2310 — True if `model` is an Anthropic-flavored value that must never appear as a
|
|
4061
3826
|
* Codex agent `.toml` `model`. Two forms: (a) a bare Claude Agent-tool tier alias
|
|
4062
|
-
* (opus/sonnet/haiku/fable — the canonical CLAUDE_AGENT_ALIASES
|
|
4063
|
-
*
|
|
4064
|
-
*
|
|
4065
|
-
*
|
|
4066
|
-
*
|
|
4067
|
-
*
|
|
3827
|
+
* (opus/sonnet/haiku/fable — the canonical CLAUDE_AGENT_ALIASES); (b) any Claude model
|
|
3828
|
+
* id in any provider namespacing — `claude-*`, `anthropic/claude-*`, `us.anthropic.claude-*`
|
|
3829
|
+
* (the forms the catalog assigns to opencode/hermes/kilo, reachable on a Codex .toml via
|
|
3830
|
+
* the runtime-resolver path). No OpenAI/Codex model id contains "claude", so a
|
|
3831
|
+
* case-insensitive substring test is a safe, exhaustive guard for (b). Codex/ChatGPT
|
|
3832
|
+
* rejects all of these.
|
|
3833
|
+
*
|
|
3834
|
+
* #3241 — thin delegation to the shared predicate on src/model-catalog.cts (moved there
|
|
3835
|
+
* so it can't diverge across Codex-posture surfaces); kept as a local name because it
|
|
3836
|
+
* reads better at the call sites below.
|
|
4068
3837
|
*/
|
|
4069
3838
|
function _isAnthropicFlavoredModel(model) {
|
|
4070
|
-
return
|
|
3839
|
+
return gsdIsAnthropicFlavoredModel(model);
|
|
4071
3840
|
}
|
|
4072
3841
|
|
|
4073
3842
|
// #2310 — dedupe stderr warnings so repeated agent emits don't spam (mirrors the
|
|
@@ -4086,6 +3855,42 @@ function _warnCodexModelOverrideDropped(agentName, value) {
|
|
|
4086
3855
|
);
|
|
4087
3856
|
}
|
|
4088
3857
|
|
|
3858
|
+
// #3241 — one-time per-install deprecation notice: the automatic runtime-resolver
|
|
3859
|
+
// per-tier Codex model embed was removed (D1/D5, ADR-2313 passive-posture epic). When
|
|
3860
|
+
// the resolver *would have* supplied a model and nothing else ends up pinned, this
|
|
3861
|
+
// notice points the user at model_overrides as the explicit-pin replacement. Dedupes
|
|
3862
|
+
// with a module-level boolean (mirrors _codexModelOverrideDroppedWarned's Set above)
|
|
3863
|
+
// so a multi-agent install — every Codex agent hits this condition simultaneously —
|
|
3864
|
+
// emits exactly one line, not one per agent. Reset once per install() call (see
|
|
3865
|
+
// install()) so the "at most once" window is per-install, not per-process. That
|
|
3866
|
+
// reset lives ONLY inside install() (~:10116) — generateCodexAgentToml is also
|
|
3867
|
+
// exported standalone (~:13460), and a caller invoking it directly/repeatedly
|
|
3868
|
+
// outside install() gets process-lifetime dedupe instead of per-install. No
|
|
3869
|
+
// current test depends on the standalone caller's dedupe window.
|
|
3870
|
+
let _codexResolverModelOmittedWarned = false;
|
|
3871
|
+
function _warnCodexResolverModelOmitted() {
|
|
3872
|
+
if (_codexResolverModelOmittedWarned) return;
|
|
3873
|
+
_codexResolverModelOmittedWarned = true;
|
|
3874
|
+
process.stderr.write(
|
|
3875
|
+
'gsd: notice — Codex agents no longer auto-pin a per-tier model from the runtime ' +
|
|
3876
|
+
'resolver; set model_overrides for an agent if you want a specific Codex model ' +
|
|
3877
|
+
'instead of the session model.\n',
|
|
3878
|
+
);
|
|
3879
|
+
}
|
|
3880
|
+
|
|
3881
|
+
// Test seam only — bin/install.js deliberately keeps per-install warning/notice
|
|
3882
|
+
// dedupe in module scope (both the _codexModelOverrideDroppedWarned Set above and
|
|
3883
|
+
// the _codexResolverModelOmittedWarned boolean; install() resets the latter at
|
|
3884
|
+
// ~:10116). A unit test that drives generateCodexAgentToml() directly, without
|
|
3885
|
+
// going through install(), has no other way to reset either store between
|
|
3886
|
+
// assertions without busting the require.cache (which breaks module-instance
|
|
3887
|
+
// sharing with the rest of the suite). This is the single sanctioned way for a
|
|
3888
|
+
// unit test to clear both dedupe stores — exported so tests can call it instead.
|
|
3889
|
+
function _resetCodexWarningDedupeForTests() {
|
|
3890
|
+
_codexModelOverrideDroppedWarned.clear();
|
|
3891
|
+
_codexResolverModelOmittedWarned = false;
|
|
3892
|
+
}
|
|
3893
|
+
|
|
4089
3894
|
/**
|
|
4090
3895
|
* Generate a per-agent .toml config file for Codex.
|
|
4091
3896
|
* Sets required agent metadata, sandbox_mode, and developer_instructions
|
|
@@ -4118,38 +3923,58 @@ function generateCodexAgentToml(agentName, agentContent, modelOverrides = null,
|
|
|
4118
3923
|
// Embed model override when configured in ~/.gsd/defaults.json so that
|
|
4119
3924
|
// model_overrides is respected on Codex (which uses static TOML, not inline
|
|
4120
3925
|
// Task() model parameters). See #2256.
|
|
4121
|
-
// Precedence: per-agent model_overrides > runtime-aware tier resolution (#2517).
|
|
4122
3926
|
// #2310 — a Codex .toml `model` MUST be a real Codex/OpenAI model id. Codex is a
|
|
4123
3927
|
// passive/session-only model host (ADR-1239): GSD cannot reliably route per-agent
|
|
4124
3928
|
// tiers, and a bare GSD/Claude tier alias (opus/sonnet/haiku/fable) or a claude-*
|
|
4125
3929
|
// id 400s on a ChatGPT-account Codex ("The 'sonnet' model is not supported when
|
|
4126
3930
|
// using Codex with a ChatGPT account"). So: embed ONLY an explicit real-Codex
|
|
4127
3931
|
// model pin from model_overrides; omit anything Anthropic-flavored so the agent
|
|
4128
|
-
// inherits the always-available session model.
|
|
4129
|
-
//
|
|
3932
|
+
// inherits the always-available session model.
|
|
3933
|
+
// #3241 (D1/D5) — the runtime-aware tier-resolver auto-embed that used to fall
|
|
3934
|
+
// through here when model_overrides had nothing was removed: Codex is passive by
|
|
3935
|
+
// default now, and only an explicit model_overrides pin survives. See the
|
|
3936
|
+
// deprecation-notice block below for the population that used to get a pin from
|
|
3937
|
+
// the resolver and no longer does.
|
|
4130
3938
|
const rawModelOverride = modelOverrides?.[resolvedName] || modelOverrides?.[agentName];
|
|
4131
3939
|
let pinnedModel = null;
|
|
4132
3940
|
if (rawModelOverride) {
|
|
4133
|
-
|
|
4134
|
-
|
|
3941
|
+
// Trim before the truthiness test (#3241 defect fix): a whitespace-only value
|
|
3942
|
+
// (e.g. ' ') is a truthy JS string but not a model id — it must not be
|
|
3943
|
+
// embedded verbatim (`model = " "`, a live pre-fix defect) or routed to
|
|
3944
|
+
// _warnCodexModelOverrideDropped, whose "is not a valid Codex model
|
|
3945
|
+
// (Anthropic alias/id)" text would misdescribe a blank config field. It is
|
|
3946
|
+
// silently dropped, matching how '' already behaves (no pin, no warning).
|
|
3947
|
+
const trimmedOverride = typeof rawModelOverride === 'string' ? rawModelOverride.trim() : rawModelOverride;
|
|
3948
|
+
if (typeof trimmedOverride === 'string' && trimmedOverride && !_isAnthropicFlavoredModel(trimmedOverride)) {
|
|
3949
|
+
pinnedModel = trimmedOverride; // explicit real-Codex model pin → embed verbatim (#2256)
|
|
3950
|
+
} else if (typeof rawModelOverride === 'string' && trimmedOverride === '') {
|
|
3951
|
+
// whitespace-only override — no pin, no warning (#3241).
|
|
4135
3952
|
} else {
|
|
4136
3953
|
_warnCodexModelOverrideDropped(resolvedName, rawModelOverride); // alias/claude-* → omit
|
|
4137
3954
|
}
|
|
4138
3955
|
}
|
|
4139
|
-
if (!pinnedModel && runtimeResolver) {
|
|
4140
|
-
// #2517 — runtime-aware tier resolution. Embeds Codex-native model + reasoning_effort
|
|
4141
|
-
// from RUNTIME_PROFILE_MAP / model_profile_overrides for the configured tier.
|
|
4142
|
-
// (Superseded on the default path by the ADR-2310 passive-posture epic.)
|
|
4143
|
-
const entry = runtimeResolver.resolve(resolvedName) || runtimeResolver.resolve(agentName);
|
|
4144
|
-
if (entry?.model) pinnedModel = entry.model;
|
|
4145
|
-
}
|
|
4146
3956
|
// #2310 — final safety gate: never emit an Anthropic-flavored model into a Codex
|
|
4147
|
-
// .toml, even
|
|
4148
|
-
//
|
|
3957
|
+
// .toml, even one that reached here through some other path than the override
|
|
3958
|
+
// check above.
|
|
4149
3959
|
if (pinnedModel && _isAnthropicFlavoredModel(pinnedModel)) {
|
|
4150
3960
|
_warnCodexModelOverrideDropped(resolvedName, pinnedModel);
|
|
4151
3961
|
pinnedModel = null;
|
|
4152
3962
|
}
|
|
3963
|
+
// #3241 — one-time deprecation notice: if nothing ends up pinned but the
|
|
3964
|
+
// runtime resolver would have supplied a per-tier model that would actually
|
|
3965
|
+
// have been EMBEDDED (the population that loses a pin now that the
|
|
3966
|
+
// auto-embed above is gone), point the user at model_overrides. The would-be
|
|
3967
|
+
// model must also clear the #2310 Anthropic-flavored gate above — if it
|
|
3968
|
+
// wouldn't have survived that gate, the user never had that pin pre-Phase-1
|
|
3969
|
+
// either, and the notice would be false. Never fires when the resolver is
|
|
3970
|
+
// null, resolves to nothing, resolves to an Anthropic-flavored model, or an
|
|
3971
|
+
// explicit real-Codex pin survived — in all of those cases nothing was lost.
|
|
3972
|
+
if (!pinnedModel && runtimeResolver) {
|
|
3973
|
+
const wouldHavePinned = runtimeResolver.resolve(resolvedName) || runtimeResolver.resolve(agentName);
|
|
3974
|
+
if (wouldHavePinned?.model && !_isAnthropicFlavoredModel(wouldHavePinned.model)) {
|
|
3975
|
+
_warnCodexResolverModelOmitted();
|
|
3976
|
+
}
|
|
3977
|
+
}
|
|
4153
3978
|
let hasPinnedModel = false;
|
|
4154
3979
|
if (pinnedModel) {
|
|
4155
3980
|
lines.push(`model = ${JSON.stringify(pinnedModel)}`);
|
|
@@ -4169,8 +3994,12 @@ function generateCodexAgentToml(agentName, agentContent, modelOverrides = null,
|
|
|
4169
3994
|
// follows GSD. Keep those knobs coupled unless GSD also pins the model.
|
|
4170
3995
|
if (hasPinnedModel) {
|
|
4171
3996
|
const _universalEffortCodex = resolveInstallTimeEffort(effortCfg, resolvedName !== agentName ? resolvedName : agentName);
|
|
4172
|
-
|
|
4173
|
-
|
|
3997
|
+
// #3533 (10d): 'inherit' means OMIT the pin — the agent follows the host's
|
|
3998
|
+
// own effort default. Never write the literal.
|
|
3999
|
+
if (_universalEffortCodex !== 'inherit') {
|
|
4000
|
+
const _renderedEffortCodex = _getGsdEffortCatalog().renderEffortForRuntime('codex', _universalEffortCodex).value;
|
|
4001
|
+
lines.push(`model_reasoning_effort = ${JSON.stringify(_renderedEffortCodex)}`);
|
|
4002
|
+
}
|
|
4174
4003
|
}
|
|
4175
4004
|
|
|
4176
4005
|
// #774 — Emit service_tier and model_verbosity for light-tier agents.
|
|
@@ -6729,43 +6558,13 @@ function stripGsdFromCopilotInstructions(content) {
|
|
|
6729
6558
|
const GSD_AGENTS_MD_MARKER = '<!-- GSD Configuration — managed by gsd-core installer -->';
|
|
6730
6559
|
const GSD_AGENTS_MD_CLOSE_MARKER = '<!-- End GSD Configuration -->';
|
|
6731
6560
|
|
|
6732
|
-
|
|
6733
|
-
|
|
6734
|
-
|
|
6735
|
-
|
|
6736
|
-
|
|
6737
|
-
|
|
6738
|
-
|
|
6739
|
-
}
|
|
6740
|
-
|
|
6741
|
-
/** AGENTS.md body for the cross-tool global instruction target (`~/.agents/AGENTS.md`). */
|
|
6742
|
-
function buildClineAgentsMdBody() {
|
|
6743
|
-
return hooksSurface.buildClineAgentsMdBody();
|
|
6744
|
-
}
|
|
6745
|
-
|
|
6746
|
-
/**
|
|
6747
|
-
* The Cline PreToolUse hook script (issue #787).
|
|
6748
|
-
*
|
|
6749
|
-
* Cline invokes hooks as executable scripts named exactly after the event with
|
|
6750
|
-
* no extension, passing the operation context as JSON on stdin and reading a
|
|
6751
|
-
* JSON decision from stdout ({ cancel, errorMessage, contextModification }).
|
|
6752
|
-
*
|
|
6753
|
-
* This hook is a self-standing planning-artifact guard: it cancels write-class
|
|
6754
|
-
* tool calls that target `.planning/` (GSD-owned artifacts), and otherwise
|
|
6755
|
-
* allows the operation. It FAILS OPEN — any parse/IO error allows the call so a
|
|
6756
|
-
* hook bug can never wedge the user. No dependency on the #782 skills work.
|
|
6757
|
-
*/
|
|
6758
|
-
function buildClinePreToolUseHook() {
|
|
6759
|
-
return hooksSurface.buildClinePreToolUseHook();
|
|
6760
|
-
}
|
|
6761
|
-
|
|
6762
|
-
/**
|
|
6763
|
-
* Merge the GSD AGENTS.md block into an existing file (or create it), preserving
|
|
6764
|
-
* any user content. Mirrors mergeCopilotInstructions: marker-delimited, idempotent.
|
|
6765
|
-
*/
|
|
6766
|
-
function mergeGsdAgentsMd(filePath, gsdContent) {
|
|
6767
|
-
return hooksSurface.mergeGsdAgentsMd(filePath, gsdContent);
|
|
6768
|
-
}
|
|
6561
|
+
// #2876: buildClineRulesBody, buildClineAgentsMdBody, buildClinePreToolUseHook,
|
|
6562
|
+
// and mergeGsdAgentsMd used to be re-bound here as one-line delegates to the
|
|
6563
|
+
// equivalent hooksSurface.* implementations. None had an install.js internal
|
|
6564
|
+
// caller or an export consumer (tests import all four directly from
|
|
6565
|
+
// gsd-core/bin/lib/runtime-hooks-surface.cjs), so the retired bindings were
|
|
6566
|
+
// dead code with no reachable body — removed rather than kept as unreachable
|
|
6567
|
+
// wrappers.
|
|
6769
6568
|
|
|
6770
6569
|
/**
|
|
6771
6570
|
* Strip the GSD block from AGENTS.md content. Returns null if the file became
|
|
@@ -6817,47 +6616,13 @@ function writeClineArtifacts(targetDir, isGlobalInstall) {
|
|
|
6817
6616
|
//
|
|
6818
6617
|
// References: https://cursor.com/docs/hooks
|
|
6819
6618
|
|
|
6820
|
-
|
|
6821
|
-
|
|
6822
|
-
|
|
6823
|
-
|
|
6824
|
-
|
|
6825
|
-
|
|
6826
|
-
|
|
6827
|
-
return hooksSurface.buildCursorHookEntry(scriptPath);
|
|
6828
|
-
}
|
|
6829
|
-
|
|
6830
|
-
/**
|
|
6831
|
-
* Return true if a Cursor hook entry is GSD-managed.
|
|
6832
|
-
* Detection: presence of the GSD_CURSOR_HOOK_MARKER sentinel field.
|
|
6833
|
-
*
|
|
6834
|
-
* @param {object} entry - A hooks array element from hooks.json
|
|
6835
|
-
* @returns {boolean}
|
|
6836
|
-
*/
|
|
6837
|
-
function isManagedCursorHookEntry(entry) {
|
|
6838
|
-
return hooksSurface.isManagedCursorHookEntry(entry);
|
|
6839
|
-
}
|
|
6840
|
-
|
|
6841
|
-
/**
|
|
6842
|
-
* Reconcile the GSD-managed entries in a Cursor hooks.json file.
|
|
6843
|
-
*
|
|
6844
|
-
* Supports both known hooks.json shapes:
|
|
6845
|
-
* 1) { "version": 1, "hooks": { "sessionStart": [...], "postToolUse": [...] } }
|
|
6846
|
-
* 2) { "sessionStart": [...], "postToolUse": [...] } (no wrapper object)
|
|
6847
|
-
*
|
|
6848
|
-
* Managed entries (those with GSD_CURSOR_HOOK_MARKER) are removed then
|
|
6849
|
-
* re-added if managedEntries is non-null/non-empty. User-owned entries are
|
|
6850
|
-
* preserved. File is written atomically only when content changes.
|
|
6851
|
-
*
|
|
6852
|
-
* @param {string} hooksJsonPath - Absolute path to the hooks.json file
|
|
6853
|
-
* @param {{ sessionStart?: object|null, postToolUse?: object|null }|null} managedEntries
|
|
6854
|
-
* Map from event name to the new hook entry to register (or null to remove).
|
|
6855
|
-
* Pass null for the whole param to remove all managed entries.
|
|
6856
|
-
* @returns {{ changed: boolean, wrote: boolean, path: string }}
|
|
6857
|
-
*/
|
|
6858
|
-
function reconcileCursorHooksJson(hooksJsonPath, managedEntries) {
|
|
6859
|
-
return hooksSurface.reconcileCursorHooksJson(hooksJsonPath, managedEntries);
|
|
6860
|
-
}
|
|
6619
|
+
// #2876: buildCursorHookEntry, isManagedCursorHookEntry, and
|
|
6620
|
+
// reconcileCursorHooksJson used to be re-bound here as one-line delegates to
|
|
6621
|
+
// the equivalent hooksSurface.* implementations. None had an install.js
|
|
6622
|
+
// internal caller or an export consumer (tests import all three directly from
|
|
6623
|
+
// gsd-core/bin/lib/runtime-hooks-surface.cjs), so the retired bindings were
|
|
6624
|
+
// dead code with no reachable body — removed rather than kept as unreachable
|
|
6625
|
+
// wrappers.
|
|
6861
6626
|
|
|
6862
6627
|
/**
|
|
6863
6628
|
* #777 — Write GSD-managed Cursor lifecycle hooks into <targetDir>/hooks.json.
|
|
@@ -6917,23 +6682,13 @@ function removeWindsurfHooksJson(targetDir) {
|
|
|
6917
6682
|
return hooksSurface.removeWindsurfHooksJson(targetDir);
|
|
6918
6683
|
}
|
|
6919
6684
|
|
|
6920
|
-
|
|
6921
|
-
|
|
6922
|
-
|
|
6923
|
-
|
|
6924
|
-
|
|
6925
|
-
|
|
6926
|
-
|
|
6927
|
-
* run inline (no external script file), so the config can never reference a
|
|
6928
|
-
* hook script that the installer did not also install — it is self-contained
|
|
6929
|
-
* by construction. The command is advisory-only (always exits 0) and orients
|
|
6930
|
-
* the agent toward the project's GSD planning state at session start.
|
|
6931
|
-
*
|
|
6932
|
-
* @returns {object} Copilot hooks-configuration object
|
|
6933
|
-
*/
|
|
6934
|
-
function buildCopilotHookConfig() {
|
|
6935
|
-
return hooksSurface.buildCopilotHookConfig();
|
|
6936
|
-
}
|
|
6685
|
+
// #2876: buildCopilotHookConfig used to be re-bound here as a one-line
|
|
6686
|
+
// delegate to hooksSurface.buildCopilotHookConfig. It had no install.js
|
|
6687
|
+
// internal caller or export consumer (tests import it directly from
|
|
6688
|
+
// gsd-core/bin/lib/runtime-hooks-surface.cjs), so the retired binding was
|
|
6689
|
+
// dead code with no reachable body — removed rather than kept as an
|
|
6690
|
+
// unreachable wrapper. writeCopilotHookConfig below is unaffected — it still
|
|
6691
|
+
// has an internal caller (finishInstall).
|
|
6937
6692
|
|
|
6938
6693
|
/**
|
|
6939
6694
|
* #786 — Write the GSD-managed Copilot lifecycle hook config under the runtime
|
|
@@ -7462,10 +7217,14 @@ function convertClaudeToKiloFrontmatter(content, { isAgent = false, modelOverrid
|
|
|
7462
7217
|
|
|
7463
7218
|
// convertClaudeCommandToOpencodeFamilySkill, convertClaudeCommandToOpencodeSkill,
|
|
7464
7219
|
// convertClaudeCommandToKiloSkill: moved to src/install-engine.cts (ADR-1239 Phase B).
|
|
7465
|
-
//
|
|
7220
|
+
// #2876 found no install.js internal caller for the latter two (tests import
|
|
7221
|
+
// them directly from gsd-core/bin/lib/install-engine.cjs) and retired the
|
|
7222
|
+
// destructured bindings above.
|
|
7466
7223
|
|
|
7467
7224
|
// applyOpencodeFamilyPathPrefix: moved to src/install-engine.cts (ADR-1239 Phase B).
|
|
7468
|
-
//
|
|
7225
|
+
// #2876 found no install.js internal caller (never had one either — it was
|
|
7226
|
+
// never part of this module's export surface) and retired the destructured
|
|
7227
|
+
// binding above.
|
|
7469
7228
|
//
|
|
7470
7229
|
// copyFlattenedCommands (OpenCode/Kilo flattened command/ writer): moved to
|
|
7471
7230
|
// src/install-engine.cts as installOpencodeFamilyCommands (ADR-1239 / #2087).
|
|
@@ -7567,13 +7326,13 @@ function writeHermesCategoryDescription(categoryDir) {
|
|
|
7567
7326
|
* @param {boolean} isGlobal - Whether this is a global install
|
|
7568
7327
|
*/
|
|
7569
7328
|
|
|
7570
|
-
// USER_OWNED_ARTIFACTS,
|
|
7571
|
-
//
|
|
7572
|
-
//
|
|
7573
|
-
//
|
|
7574
|
-
//
|
|
7575
|
-
//
|
|
7576
|
-
//
|
|
7329
|
+
// USER_OWNED_ARTIFACTS, migrateLegacyDevPreferencesToSkill, _snapshotDir,
|
|
7330
|
+
// installRuntimeArtifacts, installOpencodeFamilySkills, uninstallRuntimeArtifacts:
|
|
7331
|
+
// ALL moved to src/install-engine.cts (ADR-1239 Phase B). Imported from
|
|
7332
|
+
// installEngine above. _copyStaged, _removeGsdEntries,
|
|
7333
|
+
// _runLegacyInstallMigrations, _runLegacyUninstallCleanup, _restoreDir, and
|
|
7334
|
+
// _removeHermesBareStemDirs moved there too but #2876 found no install.js
|
|
7335
|
+
// internal caller for any of them and retired their destructured bindings.
|
|
7577
7336
|
|
|
7578
7337
|
// ---------------------------------------------------------------------------
|
|
7579
7338
|
// Phase 2 — Layout-driven install/uninstall orchestrators (moved to engine)
|
|
@@ -7770,6 +7529,15 @@ function copyWithPathReplacement(srcDir, destDir, pathPrefix, runtime, isCommand
|
|
|
7770
7529
|
const srcPath = path.join(srcDir, entry.name);
|
|
7771
7530
|
const destPath = path.join(destDir, entry.name);
|
|
7772
7531
|
|
|
7532
|
+
// #3333: srcPath was enumerated by readdirSync above, but a filesystem is not
|
|
7533
|
+
// transactional — the file it named can vanish between listing and this read
|
|
7534
|
+
// (a concurrent process, or another test in this suite writing/cleaning up a
|
|
7535
|
+
// fixture inside this same real directory). Treat "gone by the time we get
|
|
7536
|
+
// here" as benign and skip it, never a fatal crash of the whole install.
|
|
7537
|
+
if (!entry.isDirectory() && !fs.existsSync(srcPath)) {
|
|
7538
|
+
continue;
|
|
7539
|
+
}
|
|
7540
|
+
|
|
7773
7541
|
if (entry.isDirectory()) {
|
|
7774
7542
|
copyWithPathReplacement(srcPath, destPath, pathPrefix, runtime, isCommand, isGlobal, confinementRoot);
|
|
7775
7543
|
} else if (entry.name.endsWith('.md')) {
|
|
@@ -7822,8 +7590,20 @@ function copyWithPathReplacement(srcDir, destDir, pathPrefix, runtime, isCommand
|
|
|
7822
7590
|
content = content.replace(globalClaudeRegex, pathPrefix);
|
|
7823
7591
|
content = content.replace(globalClaudeHomeRegex, pathPrefix);
|
|
7824
7592
|
content = content.replace(localClaudeRegex, `./${dirName}/`);
|
|
7825
|
-
|
|
7826
|
-
|
|
7593
|
+
// #3544 review (Finding 1 fallout): guarded with the SAME
|
|
7594
|
+
// negative-lookahead convention already used at ~:2859-2860 below
|
|
7595
|
+
// ("preserve .claude-plugin and .claudeignore"). A naive `\b` here
|
|
7596
|
+
// is satisfied by ANY non-word character, including '-' — so for a
|
|
7597
|
+
// --config-dir whose name EXTENDS '.claude' (e.g. '.claude-work',
|
|
7598
|
+
// pathPrefix '$HOME/.claude-work/'), this pass re-matched the
|
|
7599
|
+
// '$HOME/.claude' PREFIX of its own slash-form output (lines above)
|
|
7600
|
+
// and re-appended the full prefix, corrupting every emitted path to
|
|
7601
|
+
// '$HOME/.claude-work-work/...'. Harmless no-op for the literal
|
|
7602
|
+
// default '.claude' (self-replace with an identical string), which
|
|
7603
|
+
// is why this went undetected until a non-default config-dir name
|
|
7604
|
+
// was exercised.
|
|
7605
|
+
content = content.replace(/~\/\.claude(?![\w-])/g, pathPrefix.replace(/\/$/, ''));
|
|
7606
|
+
content = content.replace(/\$HOME\/\.claude(?![\w-])/g, pathPrefix.replace(/\/$/, ''));
|
|
7827
7607
|
content = content.replace(/\.\/\.claude\b/g, `./${dirName}`);
|
|
7828
7608
|
content = content.replace(/~\/\.qwen\//g, pathPrefix);
|
|
7829
7609
|
content = content.replace(/\$HOME\/\.qwen\//g, pathPrefix);
|
|
@@ -7831,6 +7611,17 @@ function copyWithPathReplacement(srcDir, destDir, pathPrefix, runtime, isCommand
|
|
|
7831
7611
|
content = content.replace(/~\/\.hermes\//g, pathPrefix);
|
|
7832
7612
|
content = content.replace(/\$HOME\/\.hermes\//g, pathPrefix);
|
|
7833
7613
|
content = content.replace(/\.\/\.hermes\//g, `./${dirName}/`);
|
|
7614
|
+
// #3544: restore @-file-reference lines to the tilde form Claude Code
|
|
7615
|
+
// actually expands — the SAME correction #3133 already applies to
|
|
7616
|
+
// skill/command bodies via _applyRuntimeRewrites's 'claude' case (see
|
|
7617
|
+
// restoreClaudeGlobalAtRefTilde's doc comment in
|
|
7618
|
+
// runtime-artifact-conversion.cts). This is the gsd-core/ spec-tree
|
|
7619
|
+
// emit path, which never had it: every @~/.claude/gsd-core/… include
|
|
7620
|
+
// in a global install's workflows/references tree silently resolved
|
|
7621
|
+
// to nothing (54 includes across 22 files on a live install).
|
|
7622
|
+
if (runtime === 'claude') {
|
|
7623
|
+
content = runtimeArtifactConversion._restoreClaudeGlobalAtRefTilde(content, pathPrefix);
|
|
7624
|
+
}
|
|
7834
7625
|
}
|
|
7835
7626
|
content = processAttribution(content, getCommitAttribution(runtime));
|
|
7836
7627
|
|
|
@@ -8109,6 +7900,23 @@ function uninstall(isGlobal, runtime = DEFAULT_RUNTIME) {
|
|
|
8109
7900
|
|
|
8110
7901
|
let removedCount = 0;
|
|
8111
7902
|
|
|
7903
|
+
// #2875 (#1874-F19 anti-inertness, test-matrix C7): recover any user
|
|
7904
|
+
// artifact orphaned by a PRIOR uninstall run that died between staging and
|
|
7905
|
+
// its own restore/discard, BEFORE this run's own preserve steps (sites 2,
|
|
7906
|
+
// 3, 5 below) stage anything new. Uninstall's own gsd-core/ removal and
|
|
7907
|
+
// legacy-commands cleanup are exactly as crash-exposed as install's —
|
|
7908
|
+
// without this, an orphan from a crashed uninstall is recoverable only if
|
|
7909
|
+
// the user later re-installs.
|
|
7910
|
+
// #2875 defect fix: DEGRADE, never abort uninstall, when the staging root
|
|
7911
|
+
// itself cannot be resolved — skip this recovery pass rather than throw
|
|
7912
|
+
// out of uninstall() before it does anything.
|
|
7913
|
+
{
|
|
7914
|
+
const _uninstallEntryStagingRoot = _tryResolveUserArtifactStagingRoot(targetDir);
|
|
7915
|
+
if (_uninstallEntryStagingRoot !== null) {
|
|
7916
|
+
recoverOrphanedUserArtifacts(_uninstallEntryStagingRoot, targetDir);
|
|
7917
|
+
}
|
|
7918
|
+
}
|
|
7919
|
+
|
|
8112
7920
|
// Remove profile marker so a clean reinstall defaults to full surface.
|
|
8113
7921
|
try {
|
|
8114
7922
|
fs.unlinkSync(path.join(targetDir, '.gsd-profile'));
|
|
@@ -8116,7 +7924,16 @@ function uninstall(isGlobal, runtime = DEFAULT_RUNTIME) {
|
|
|
8116
7924
|
} catch {}
|
|
8117
7925
|
|
|
8118
7926
|
// 1. Remove GSD commands/skills (layout-driven)
|
|
8119
|
-
|
|
7927
|
+
// #2870: scope id resolved ONCE here and reused below (was two independent
|
|
7928
|
+
// isGlobal-derived re-derivations). Routed through the Install Scope
|
|
7929
|
+
// Module (src/install-scope.cts) when the capability registry is
|
|
7930
|
+
// available; degrades to the plain id on failure (unknown/non-installable
|
|
7931
|
+
// runtime, broken bundle) so this function's scope-id uses — which never
|
|
7932
|
+
// depended on registry availability before this migration — keep working
|
|
7933
|
+
// exactly as they did pre-migration.
|
|
7934
|
+
const _uninstallScopeId = isGlobal ? 'global' : 'local';
|
|
7935
|
+
const _resolvedUninstallScope = _resolveScopeSafe(_uninstallScopeId, runtime);
|
|
7936
|
+
const scope = _resolvedUninstallScope ? _resolvedUninstallScope.id : _uninstallScopeId;
|
|
8120
7937
|
// ADR-1239 / #2086: drive uninstall through the public Host-Integration Interface.
|
|
8121
7938
|
// Fail-open to the engine directly if the composed-registry adapter can't load.
|
|
8122
7939
|
const _uninstallAdapter = _runtimeAdapter(runtime);
|
|
@@ -8443,18 +8260,42 @@ function uninstall(isGlobal, runtime = DEFAULT_RUNTIME) {
|
|
|
8443
8260
|
// Preserve user-owned dev-preferences.md if present (#1423 parity).
|
|
8444
8261
|
const legacyGsdCommandsDir = path.join(targetDir, 'commands', 'gsd');
|
|
8445
8262
|
if (fs.existsSync(legacyGsdCommandsDir)) {
|
|
8446
|
-
|
|
8447
|
-
|
|
8448
|
-
|
|
8449
|
-
|
|
8450
|
-
|
|
8451
|
-
|
|
8452
|
-
|
|
8453
|
-
|
|
8454
|
-
|
|
8455
|
-
|
|
8456
|
-
|
|
8457
|
-
|
|
8263
|
+
// Stage user-owned dev-preferences.md DURABLY before wiping (#2875 /
|
|
8264
|
+
// #1874-F19 "site 7" — found by sweeping bin/install.js for the
|
|
8265
|
+
// read-then-wipe-then-write PATTERN, not for preserveUserArtifacts'
|
|
8266
|
+
// callers; this uninstall-path block open-coded the same round-trip).
|
|
8267
|
+
// #2875 defect fix: DEGRADE, never abort uninstall, when the staging
|
|
8268
|
+
// root cannot be resolved — skip this legacy-cleanup block entirely
|
|
8269
|
+
// (leave the stale dir in place) rather than wipe without a durable
|
|
8270
|
+
// backup for dev-preferences.md.
|
|
8271
|
+
const _legacyGsdCommandsStagingRoot = _tryResolveUserArtifactStagingRoot(targetDir);
|
|
8272
|
+
if (_legacyGsdCommandsStagingRoot !== null) {
|
|
8273
|
+
const stagedDevPrefs = stageUserArtifacts(legacyGsdCommandsDir, ['dev-preferences.md'], _legacyGsdCommandsStagingRoot);
|
|
8274
|
+
// Preserve the ORIGINAL truthy-content check exactly: an existing but
|
|
8275
|
+
// EMPTY dev-preferences.md was (and still is) silently not restored.
|
|
8276
|
+
const savedDevPrefs = stagedDevPrefs.names.includes('dev-preferences.md')
|
|
8277
|
+
? fs.readFileSync(path.join(stagedDevPrefs.filesDir, 'dev-preferences.md'), 'utf8')
|
|
8278
|
+
: null;
|
|
8279
|
+
fs.rmSync(legacyGsdCommandsDir, { recursive: true });
|
|
8280
|
+
removedCount++;
|
|
8281
|
+
console.log(` ${green}✓${reset} Removed legacy commands/gsd/`);
|
|
8282
|
+
if (savedDevPrefs) {
|
|
8283
|
+
try {
|
|
8284
|
+
restoreStagedUserArtifacts(legacyGsdCommandsDir, stagedDevPrefs);
|
|
8285
|
+
discardStagedUserArtifacts(stagedDevPrefs);
|
|
8286
|
+
console.log(` ${green}✓${reset} Preserved commands/gsd/dev-preferences.md`);
|
|
8287
|
+
} catch (err) {
|
|
8288
|
+
console.error(` ${red}✗${reset} Failed to restore dev-preferences.md: ${err.message}`);
|
|
8289
|
+
}
|
|
8290
|
+
} else {
|
|
8291
|
+
// #2875 defect fix: an existing-but-EMPTY dev-preferences.md was (and
|
|
8292
|
+
// still is) never restored — the original truthy-content check is
|
|
8293
|
+
// preserved byte-for-byte above — but the staged batch was never
|
|
8294
|
+
// discarded either, leaking a <configDir>/.gsd-staging/ record
|
|
8295
|
+
// forever and re-materializing the just-deleted file on a future
|
|
8296
|
+
// install's orphan-recovery pass. Discard unconditionally when there
|
|
8297
|
+
// is nothing to restore.
|
|
8298
|
+
discardStagedUserArtifacts(stagedDevPrefs);
|
|
8458
8299
|
}
|
|
8459
8300
|
}
|
|
8460
8301
|
}
|
|
@@ -8472,21 +8313,77 @@ function uninstall(isGlobal, runtime = DEFAULT_RUNTIME) {
|
|
|
8472
8313
|
// so this is a best-effort guard.
|
|
8473
8314
|
const legacyDir = path.join(targetDir, 'commands', 'gsd');
|
|
8474
8315
|
if (fs.existsSync(legacyDir)) {
|
|
8475
|
-
|
|
8476
|
-
|
|
8477
|
-
|
|
8478
|
-
|
|
8479
|
-
|
|
8480
|
-
|
|
8481
|
-
|
|
8482
|
-
|
|
8483
|
-
|
|
8484
|
-
|
|
8485
|
-
const
|
|
8486
|
-
|
|
8487
|
-
|
|
8488
|
-
|
|
8489
|
-
|
|
8316
|
+
// #2875 (#1874-F19): staged DURABLY to disk before the wipe below,
|
|
8317
|
+
// instead of an in-memory Map only — a crash between the wipe and the
|
|
8318
|
+
// restore-on-failure branch below now survives via
|
|
8319
|
+
// recoverOrphanedUserArtifacts on the next run.
|
|
8320
|
+
// #2875 defect fix: DEGRADE, never abort uninstall, when the staging
|
|
8321
|
+
// root cannot be resolved — skip this legacy-migration block entirely
|
|
8322
|
+
// (leave the stale dir in place) rather than wipe without a durable
|
|
8323
|
+
// backup.
|
|
8324
|
+
const stagingRoot = _tryResolveUserArtifactStagingRoot(targetDir);
|
|
8325
|
+
if (stagingRoot !== null) {
|
|
8326
|
+
const stagedLegacyArtifacts = stageUserArtifacts(legacyDir, ['dev-preferences.md'], stagingRoot);
|
|
8327
|
+
fs.rmSync(legacyDir, { recursive: true });
|
|
8328
|
+
removedCount++;
|
|
8329
|
+
console.log(` ${green}✓${reset} Removed legacy commands/gsd/`);
|
|
8330
|
+
const _uninstallScope = scope;
|
|
8331
|
+
// migrateLegacyDevPreferencesToSkill's Map<string,string> contract is
|
|
8332
|
+
// unchanged — read the staged content back from disk (not an in-memory
|
|
8333
|
+
// value held across the wipe above).
|
|
8334
|
+
//
|
|
8335
|
+
// #2875 defect fix (readFileSync following a staged symlink) — matches
|
|
8336
|
+
// install-engine.cts's _runLegacyInstallMigrations call site 1 exactly:
|
|
8337
|
+
// readFileSync ALWAYS follows a symlink, so a staged artifact that is
|
|
8338
|
+
// itself a symlink (user-artifact-staging.cts's "Symlink safety": a
|
|
8339
|
+
// symlinked user artifact is recreated AS a symlink in the staging
|
|
8340
|
+
// tree, never copied by content) would have its REFERENT's bytes read
|
|
8341
|
+
// here and land in SKILL.md. Excluded from migration below and
|
|
8342
|
+
// restored to its original location unchanged instead.
|
|
8343
|
+
// #2875 defect fix (regression closed — was previously unguarded and
|
|
8344
|
+
// BRICKED uninstall, the very command that should recover from this):
|
|
8345
|
+
// legacyDir was already removed above, so stagedLegacyArtifacts is
|
|
8346
|
+
// the only surviving copy. migrateLegacyDevPreferencesToSkill
|
|
8347
|
+
// correctly THROWS when it finds a planted/dangling symlink at the
|
|
8348
|
+
// skill-file leaf (security fix); a raw `fs.lstatSync` in the loop
|
|
8349
|
+
// below can also throw on a TOCTOU-vanished staged file. Either one,
|
|
8350
|
+
// left unguarded, propagated straight out of uninstall, aborting it
|
|
8351
|
+
// WITHOUT ever reaching the restore-or-discard branch below — the
|
|
8352
|
+
// staged batch was orphaned on disk and every retry hit the same
|
|
8353
|
+
// throw again. Degrade identically to every other #2875 staging step
|
|
8354
|
+
// in this function: catch, warn once, and treat the batch as
|
|
8355
|
+
// unmigrated so the restore branch below always fires.
|
|
8356
|
+
let _legacyMigrated = false;
|
|
8357
|
+
let migratableLegacyNames = [];
|
|
8358
|
+
try {
|
|
8359
|
+
const savedLegacyArtifacts = new Map();
|
|
8360
|
+
for (const name of stagedLegacyArtifacts.names) {
|
|
8361
|
+
const stagedPath = path.join(stagedLegacyArtifacts.filesDir, name);
|
|
8362
|
+
if (fs.lstatSync(stagedPath).isSymbolicLink()) continue;
|
|
8363
|
+
savedLegacyArtifacts.set(name, fs.readFileSync(stagedPath, 'utf8'));
|
|
8364
|
+
migratableLegacyNames.push(name);
|
|
8365
|
+
}
|
|
8366
|
+
_legacyMigrated = migrateLegacyDevPreferencesToSkill(targetDir, savedLegacyArtifacts, runtime, _uninstallScope);
|
|
8367
|
+
} catch (err) {
|
|
8368
|
+
console.warn(` ${yellow}!${reset} dev-preferences.md migration skipped (${err.message}) — restoring the legacy copy instead.`);
|
|
8369
|
+
_legacyMigrated = false;
|
|
8370
|
+
migratableLegacyNames = [];
|
|
8371
|
+
}
|
|
8372
|
+
if (_legacyMigrated && migratableLegacyNames.length === stagedLegacyArtifacts.names.length) {
|
|
8373
|
+
// Compute the actual path written so the log line is accurate per-runtime
|
|
8374
|
+
const _layout = resolveRuntimeArtifactLayout(runtime, targetDir, _uninstallScope);
|
|
8375
|
+
const _sk = _layout.kinds.find((k) => k.kind === 'skills');
|
|
8376
|
+
const _stem = _sk && _sk.prefix === '' ? 'dev-preferences' : 'gsd-dev-preferences';
|
|
8377
|
+
const _skillRelPath = _sk ? `${_sk.destSubpath}/${_stem}/SKILL.md` : 'skills/gsd-dev-preferences/SKILL.md';
|
|
8378
|
+
console.log(` ${green}✓${reset} Migrated dev-preferences.md → ${_skillRelPath} (#2973)`);
|
|
8379
|
+
discardStagedUserArtifacts(stagedLegacyArtifacts);
|
|
8380
|
+
} else {
|
|
8381
|
+
// Migration failed, already exists, or a symlinked name was excluded
|
|
8382
|
+
// above — restore the WHOLE batch to the legacy location so no user
|
|
8383
|
+
// content is silently lost.
|
|
8384
|
+
restoreStagedUserArtifacts(legacyDir, stagedLegacyArtifacts);
|
|
8385
|
+
discardStagedUserArtifacts(stagedLegacyArtifacts);
|
|
8386
|
+
}
|
|
8490
8387
|
}
|
|
8491
8388
|
}
|
|
8492
8389
|
}
|
|
@@ -8494,22 +8391,49 @@ function uninstall(isGlobal, runtime = DEFAULT_RUNTIME) {
|
|
|
8494
8391
|
// 2. Remove gsd-core directory
|
|
8495
8392
|
const gsdDir = path.join(targetDir, 'gsd-core');
|
|
8496
8393
|
if (fs.existsSync(gsdDir)) {
|
|
8497
|
-
//
|
|
8498
|
-
|
|
8499
|
-
|
|
8500
|
-
|
|
8501
|
-
|
|
8502
|
-
|
|
8503
|
-
|
|
8394
|
+
// Stage user-generated files DURABLY to disk before wipe (#1423; #2875 /
|
|
8395
|
+
// #1874-F19 "site 5" — this block open-coded its own preserve/restore
|
|
8396
|
+
// instead of calling preserveUserArtifacts, which is why it was missed
|
|
8397
|
+
// by the original symbol-search measurement).
|
|
8398
|
+
// #2875 defect fix: this IS the core uninstall step (removing gsd-core/)
|
|
8399
|
+
// — unlike the optional legacy-cleanup blocks above, uninstall must
|
|
8400
|
+
// still be able to proceed and actually remove gsd-core/ even when the
|
|
8401
|
+
// staging root cannot be resolved. Degrade by skipping ONLY the
|
|
8402
|
+
// USER-PROFILE.md preserve/restore wrapper (warn), never the removal
|
|
8403
|
+
// itself.
|
|
8404
|
+
const _gsdDirStagingRoot = _tryResolveUserArtifactStagingRoot(targetDir);
|
|
8405
|
+
if (_gsdDirStagingRoot === null) {
|
|
8406
|
+
console.warn(` ${yellow}!${reset} Skipping gsd-core/USER-PROFILE.md preservation (staging unavailable) — it will be lost if present.`);
|
|
8407
|
+
fs.rmSync(gsdDir, { recursive: true });
|
|
8408
|
+
removedCount++;
|
|
8409
|
+
console.log(` ${green}✓${reset} Removed gsd-core/`);
|
|
8410
|
+
} else {
|
|
8411
|
+
const stagedProfile = stageUserArtifacts(gsdDir, USER_OWNED_ARTIFACTS, _gsdDirStagingRoot);
|
|
8412
|
+
// Preserve the ORIGINAL truthy-content check exactly: an existing but
|
|
8413
|
+
// EMPTY USER-PROFILE.md was (and still is) silently not restored —
|
|
8414
|
+
// matching prior behavior byte-for-byte rather than widening scope.
|
|
8415
|
+
const preservedProfile = stagedProfile.names.includes('USER-PROFILE.md')
|
|
8416
|
+
? fs.readFileSync(path.join(stagedProfile.filesDir, 'USER-PROFILE.md'), 'utf8')
|
|
8417
|
+
: null;
|
|
8418
|
+
|
|
8419
|
+
fs.rmSync(gsdDir, { recursive: true });
|
|
8420
|
+
removedCount++;
|
|
8421
|
+
console.log(` ${green}✓${reset} Removed gsd-core/`);
|
|
8504
8422
|
|
|
8505
|
-
|
|
8506
|
-
|
|
8507
|
-
|
|
8508
|
-
|
|
8509
|
-
|
|
8510
|
-
|
|
8511
|
-
|
|
8512
|
-
|
|
8423
|
+
// Restore user-generated files
|
|
8424
|
+
if (preservedProfile) {
|
|
8425
|
+
try {
|
|
8426
|
+
restoreStagedUserArtifacts(gsdDir, stagedProfile);
|
|
8427
|
+
discardStagedUserArtifacts(stagedProfile);
|
|
8428
|
+
console.log(` ${green}✓${reset} Preserved gsd-core/USER-PROFILE.md`);
|
|
8429
|
+
} catch (err) {
|
|
8430
|
+
console.error(` ${red}✗${reset} Failed to restore USER-PROFILE.md: ${err.message}`);
|
|
8431
|
+
}
|
|
8432
|
+
} else {
|
|
8433
|
+
// #2875 defect fix: same empty-file orphan leak as the legacy
|
|
8434
|
+
// commands/gsd/ site above — discard the staging batch regardless of
|
|
8435
|
+
// whether the staged content was truthy.
|
|
8436
|
+
discardStagedUserArtifacts(stagedProfile);
|
|
8513
8437
|
}
|
|
8514
8438
|
}
|
|
8515
8439
|
}
|
|
@@ -8642,13 +8566,8 @@ function uninstall(isGlobal, runtime = DEFAULT_RUNTIME) {
|
|
|
8642
8566
|
// Any file NOT in this set is user-owned and must survive uninstall.
|
|
8643
8567
|
// After removing GSD files, attempt to rmdir — if the directory is still
|
|
8644
8568
|
// non-empty (user has custom helpers) it stays; otherwise it goes cleanly.
|
|
8645
|
-
|
|
8646
|
-
|
|
8647
|
-
'github-release-notes.cjs', 'lint.cjs', 'new.cjs',
|
|
8648
|
-
'README.md', // documentation only — not user-authored
|
|
8649
|
-
];
|
|
8650
|
-
const GSD_SCRIPTS_LIB_FILES = ['cli-exit.cjs', 'allowlist-ratchet.cjs'];
|
|
8651
|
-
|
|
8569
|
+
// GSD_CHANGESET_FILES / GSD_SCRIPTS_LIB_FILES are module-scoped (#3184) so
|
|
8570
|
+
// tests can assert their parity against the real directory contents.
|
|
8652
8571
|
const changesetUninstallDir = path.join(targetDir, 'scripts', 'changeset');
|
|
8653
8572
|
if (fs.existsSync(changesetUninstallDir)) {
|
|
8654
8573
|
let removedChangeset = 0;
|
|
@@ -9525,23 +9444,41 @@ function writeManifest(configDir, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
9525
9444
|
// so the manifest records what's actually on disk. _resolveSkillsRootDir already
|
|
9526
9445
|
// resolves destSubpath (which includes hermes's 'skills/gsd' nesting) — do not
|
|
9527
9446
|
// re-append 'gsd' or the hermes dir gets double-nested to skills/gsd/gsd.
|
|
9528
|
-
|
|
9447
|
+
// #2872 (ADR-2866 Phase 3): the scope used to pick the skills root and the
|
|
9448
|
+
// scope RECORDED in the manifest are one value, resolved once. Two reads of
|
|
9449
|
+
// `options.scope` could drift; one cannot.
|
|
9450
|
+
const resolvedScope = options.scope === 'local' ? 'local' : 'global';
|
|
9451
|
+
const codexSkillsDir = _resolveSkillsRootDir(runtime, configDir, resolvedScope);
|
|
9529
9452
|
const codexSkillsManifestPrefix = _hostBehaviors(runtime).skillsManifestPrefix || 'skills/';
|
|
9530
9453
|
const agentsDir = path.join(configDir, 'agents');
|
|
9531
9454
|
const manifest = {
|
|
9455
|
+
// Schema version of this DOCUMENT (#2872) — distinct from `version`
|
|
9456
|
+
// below, which is the GSD package version. Absent ⇒ a pre-#2872 (v1)
|
|
9457
|
+
// manifest, which readInstallManifest still reads without error and
|
|
9458
|
+
// without requiring a reinstall. Read from the Installer Migration
|
|
9459
|
+
// Module rather than repeated as a second literal: the writer here and
|
|
9460
|
+
// the reader's normalizeManifestVersion are two surfaces over one
|
|
9461
|
+
// constant, and this repo's "generative fix divergence" class is exactly
|
|
9462
|
+
// two such literals drifting apart.
|
|
9463
|
+
manifestVersion: MANIFEST_SCHEMA_VERSION,
|
|
9532
9464
|
version: pkg.version,
|
|
9533
9465
|
timestamp: new Date().toISOString(),
|
|
9534
9466
|
mode: options.mode === 'minimal' ? 'minimal' : 'full',
|
|
9467
|
+
// Recorded so an Installed Surface Resolver can answer "which surfaces
|
|
9468
|
+
// are installed, at which scopes, for which runtimes" without re-deriving
|
|
9469
|
+
// it from the directory it happened to be found in (#2872).
|
|
9470
|
+
runtime,
|
|
9471
|
+
scope: resolvedScope,
|
|
9535
9472
|
files: {},
|
|
9536
9473
|
};
|
|
9537
9474
|
|
|
9538
9475
|
const gsdHashes = generateManifest(gsdDir);
|
|
9539
9476
|
for (const [rel, hash] of Object.entries(gsdHashes)) {
|
|
9540
|
-
// Skip user-owned artifacts (e.g. USER-PROFILE.md). They are
|
|
9541
|
-
//
|
|
9542
|
-
//
|
|
9543
|
-
// as a "local patch"
|
|
9544
|
-
// USER_OWNED_ARTIFACTS at top of file.
|
|
9477
|
+
// Skip user-owned artifacts (e.g. USER-PROFILE.md). They are staged
|
|
9478
|
+
// durably and restored across reinstalls (user-artifact-staging.cts,
|
|
9479
|
+
// #2875) and must NOT be hashed into the manifest — otherwise
|
|
9480
|
+
// saveLocalPatches() would flag every refresh as a "local patch"
|
|
9481
|
+
// (bug #2771). Single source of truth: USER_OWNED_ARTIFACTS at top of file.
|
|
9545
9482
|
if (USER_OWNED_ARTIFACTS.includes(rel)) continue;
|
|
9546
9483
|
manifest.files['gsd-core/' + rel] = hash;
|
|
9547
9484
|
}
|
|
@@ -10029,21 +9966,24 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
10029
9966
|
// below were removed, leaving isKimi unused in this function (the kimi
|
|
10030
9967
|
// local-install-deferred branch above already reads
|
|
10031
9968
|
// _hostBehaviors(runtime).localInstallDeferred instead of this flag).
|
|
10032
|
-
// #2096: isAntigravity dropped — antigravity
|
|
10033
|
-
//
|
|
10034
|
-
// (the path-rewrite skip and the converter dispatch) were
|
|
10035
|
-
// code; both were removed rather than re-gated on
|
|
10036
|
-
// #
|
|
10037
|
-
// _DESCRIPTOR_AGENTS_RUNTIMES
|
|
10038
|
-
//
|
|
9969
|
+
// #2096: isAntigravity dropped — antigravity's agents were already
|
|
9970
|
+
// descriptor-driven (installRuntimeArtifacts), so its two legacy-agent-loop
|
|
9971
|
+
// branches (the path-rewrite skip and the converter dispatch) were
|
|
9972
|
+
// unreachable dead code; both were removed rather than re-gated on
|
|
9973
|
+
// hostBehaviors. (#2875 Part 2 later deleted that legacy loop and its
|
|
9974
|
+
// `_DESCRIPTOR_AGENTS_RUNTIMES` gate entirely — EVERY runtime is now
|
|
9975
|
+
// descriptor-driven for agents, not just this subset.)
|
|
9976
|
+
// #2098: isCodebuddy dropped — codebuddy's agents were likewise already
|
|
9977
|
+
// descriptor-driven, so its legacy converter-dispatch branch (the
|
|
9978
|
+
// `isCodebuddy` arm calling convertClaudeAgentToCodebuddyAgent) was
|
|
10039
9979
|
// unreachable dead code and was removed rather than re-gated.
|
|
10040
|
-
// #2099: isCopilot dropped — copilot
|
|
10041
|
-
//
|
|
10042
|
-
// the converter dispatch, and the .agent.md destName
|
|
10043
|
-
// unreachable dead code and were removed rather than
|
|
10044
|
-
// .agent.md suffix now lives on
|
|
10045
|
-
// src/install-engine.cts, and the
|
|
10046
|
-
// longer needs `&& !isCopilot`.
|
|
9980
|
+
// #2099: isCopilot dropped — copilot's agents were likewise already
|
|
9981
|
+
// descriptor-driven, so its three legacy-agent-loop branches (the
|
|
9982
|
+
// path-rewrite skip, the converter dispatch, and the .agent.md destName
|
|
9983
|
+
// ternary) were unreachable dead code and were removed rather than
|
|
9984
|
+
// re-gated; the .agent.md suffix now lives on
|
|
9985
|
+
// hostBehaviors.agentFileExtension in src/install-engine.cts, and the
|
|
9986
|
+
// skipSharedHooksInstall check above no longer needs `&& !isCopilot`.
|
|
10047
9987
|
// #2100: isWindsurf dropped — its four former isWindsurf-gated branches
|
|
10048
9988
|
// (legacy .devin/skills/gsd-* cleanup, the #1629 command-bodies copy, the
|
|
10049
9989
|
// workflow-verification report, and the shared-hooks-install exclusion) are
|
|
@@ -10051,14 +9991,20 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
10051
9991
|
// hostBehaviors.installsCommandBodiesForWorkflowDelegation,
|
|
10052
9992
|
// hostBehaviors.verificationStyle === 'windsurf-workflows', and
|
|
10053
9993
|
// hostBehaviors.skipSharedHooksInstall respectively; its legacy-agent-loop
|
|
10054
|
-
// converter arm was likewise unreachable dead code (windsurf
|
|
10055
|
-
//
|
|
9994
|
+
// converter arm was likewise unreachable dead code (windsurf's agents were
|
|
9995
|
+
// already descriptor-driven) and was removed above.
|
|
10056
9996
|
// #2101: isZcode dropped — folded onto hostBehaviors.skipSharedHooksInstall.
|
|
10057
9997
|
const { isOpencode, isCodex, isCursor, isAugment, isTrae, isQwen, isHermes, isCline } = runtimeFlags(runtime);
|
|
10058
9998
|
const plan = resolveInstallPlan(runtime);
|
|
10059
9999
|
const dirName = getDirName(runtime);
|
|
10060
10000
|
const src = path.join(__dirname, '..');
|
|
10061
10001
|
|
|
10002
|
+
// #3241 — the Codex resolver-model-omitted notice dedupes "at most once", but
|
|
10003
|
+
// scoped per install() call rather than per process — each install() run gets
|
|
10004
|
+
// its own fresh window so a second install (e.g. a second runtime, or a test
|
|
10005
|
+
// re-running install()) can warn again if the same condition recurs.
|
|
10006
|
+
_codexResolverModelOmittedWarned = false;
|
|
10007
|
+
|
|
10062
10008
|
if (_hostBehaviors(runtime).localInstallDeferred && !isGlobal) {
|
|
10063
10009
|
console.log(` ${yellow}⚠${reset} Kimi local install is deferred for Phase 2.`);
|
|
10064
10010
|
console.log(` No .kimi-code/skills or .agents/skills project artifacts were written.`);
|
|
@@ -10076,6 +10022,19 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
10076
10022
|
};
|
|
10077
10023
|
}
|
|
10078
10024
|
|
|
10025
|
+
// #2870: scope id resolved ONCE here and reused at every use below (was 10
|
|
10026
|
+
// independent isGlobal-derived re-derivations). Placed AFTER the kimi
|
|
10027
|
+
// local-deferred early return above so that return path does no extra
|
|
10028
|
+
// work. Routed through the Install Scope Module (src/install-scope.cts)
|
|
10029
|
+
// when the capability registry is available; degrades to the plain id on
|
|
10030
|
+
// failure (unknown/non-installable runtime, broken bundle) so this
|
|
10031
|
+
// function's plain scope-id uses — which never depended on registry
|
|
10032
|
+
// availability before this migration — keep working exactly as they did
|
|
10033
|
+
// pre-migration. `_installScope` (the full resolved value, not just the
|
|
10034
|
+
// id) additionally backs the settingsFileByScope routing below.
|
|
10035
|
+
const _installScopeId = isGlobal ? 'global' : 'local';
|
|
10036
|
+
const _installScope = _resolveScopeSafe(_installScopeId, runtime);
|
|
10037
|
+
|
|
10079
10038
|
// Reusable helper to copy hooks/lib/ (git-cmd.js + gsd-graphify-rebuild.sh).
|
|
10080
10039
|
// Defined early so it is visible to both the main and Codex code paths.
|
|
10081
10040
|
// `allowlist` (when non-empty) restricts copying to the named top-level entries,
|
|
@@ -10121,6 +10080,26 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
10121
10080
|
? process.cwd()
|
|
10122
10081
|
: path.join(process.cwd(), dirName);
|
|
10123
10082
|
|
|
10083
|
+
// #2875 (#1874-F19 anti-inertness, test-matrix C7): recover any user
|
|
10084
|
+
// artifact orphaned by a PRIOR install run that died between staging and
|
|
10085
|
+
// its own restore/discard, BEFORE this run's own preserve step stages
|
|
10086
|
+
// anything new. This is the production entry point every install() call
|
|
10087
|
+
// reaches — the only place this phase's durability fix is complete rather
|
|
10088
|
+
// than merely callable (40-design.md "The inertness trap this design must
|
|
10089
|
+
// avoid" / #1879-F15). Runs for every runtime, ahead of both the
|
|
10090
|
+
// layout-driven path's _runLegacyInstallMigrations (site 1, inside
|
|
10091
|
+
// installRuntimeArtifacts) and this function's own mainline gsd-core copy
|
|
10092
|
+
// (site 4, below).
|
|
10093
|
+
// #2875 defect fix: DEGRADE, never abort install, when the staging root
|
|
10094
|
+
// itself cannot be resolved — skip this recovery pass rather than throw
|
|
10095
|
+
// out of install() before it does anything.
|
|
10096
|
+
{
|
|
10097
|
+
const _installEntryStagingRoot = _tryResolveUserArtifactStagingRoot(targetDir);
|
|
10098
|
+
if (_installEntryStagingRoot !== null) {
|
|
10099
|
+
recoverOrphanedUserArtifacts(_installEntryStagingRoot, targetDir);
|
|
10100
|
+
}
|
|
10101
|
+
}
|
|
10102
|
+
|
|
10124
10103
|
const locationLabel = isGlobal
|
|
10125
10104
|
? targetDir.replace(os.homedir(), '~')
|
|
10126
10105
|
: targetDir.replace(process.cwd(), '.');
|
|
@@ -10273,7 +10252,7 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
10273
10252
|
const codexPreInstallAgentContents = new Map();
|
|
10274
10253
|
let codexPreInstallVersionBytes = null;
|
|
10275
10254
|
if (_hostBehaviors(runtime).tomlConfigInstall && !isMinimalMode(_effectiveInstallMode)) {
|
|
10276
|
-
const _preSkillsDir = _resolveSkillsRootDir(runtime, targetDir,
|
|
10255
|
+
const _preSkillsDir = _resolveSkillsRootDir(runtime, targetDir, _installScopeId);
|
|
10277
10256
|
if (fs.existsSync(_preSkillsDir)) {
|
|
10278
10257
|
for (const entry of fs.readdirSync(_preSkillsDir, { withFileTypes: true })) {
|
|
10279
10258
|
if (entry.isDirectory() && entry.name.startsWith('gsd-')) {
|
|
@@ -10329,7 +10308,7 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
10329
10308
|
const _codexPreConfigRollback = !_hostBehaviors(runtime).tomlConfigInstall || isMinimalMode(_effectiveInstallMode) ? null : () => {
|
|
10330
10309
|
rollbackInstallerMigrations();
|
|
10331
10310
|
// skills/gsd-* — pass 1: restore snapshot entries (may be absent if deleted mid-install).
|
|
10332
|
-
const _earlySkillsDir = _resolveSkillsRootDir(runtime, targetDir,
|
|
10311
|
+
const _earlySkillsDir = _resolveSkillsRootDir(runtime, targetDir, _installScopeId);
|
|
10333
10312
|
for (const skillName of codexPreInstallSkillNames) {
|
|
10334
10313
|
const skillDirPath = path.join(_earlySkillsDir, skillName);
|
|
10335
10314
|
const fileMap = codexPreInstallSkillContents.get(skillName);
|
|
@@ -10427,7 +10406,7 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
10427
10406
|
installerMigrationResult = runInstallerMigrations({
|
|
10428
10407
|
configDir: targetDir,
|
|
10429
10408
|
runtime,
|
|
10430
|
-
scope:
|
|
10409
|
+
scope: _installScopeId,
|
|
10431
10410
|
migrations: options.installerMigrations,
|
|
10432
10411
|
baselineScan: true,
|
|
10433
10412
|
});
|
|
@@ -10512,6 +10491,17 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
10512
10491
|
// (copyWithPathReplacement + stale-skills cleanup).
|
|
10513
10492
|
const _isSkillsRuntime = (() => {
|
|
10514
10493
|
if (_hostBehaviors(runtime).localInstallStyle === 'legacy-flat' && !isGlobal) return false; // legacy flat local path (descriptor-driven; #2086)
|
|
10494
|
+
// #2875 Part 2 defect fix: a runtime whose LOCAL commands are embedded in a
|
|
10495
|
+
// rules file rather than materialized as files (hostBehaviors.localCommandsViaRules
|
|
10496
|
+
// — cline is the only declarant, capabilities/cline/capability.json) must not
|
|
10497
|
+
// flip into this skills/commands-reporting branch merely because its local
|
|
10498
|
+
// artifactLayout now also declares an `agents` kind (#2875 Part 2 cline-local
|
|
10499
|
+
// agents regression fix). That branch's own verification reporting expects a
|
|
10500
|
+
// skills/ or commands/ directory this runtime never writes locally and would
|
|
10501
|
+
// spuriously fail; the `localCommandsViaRules` branch below (unchanged
|
|
10502
|
+
// messaging) and the unconditional agents-materialization block further down
|
|
10503
|
+
// (installAgentsKindStandalone) already cover this runtime/scope correctly.
|
|
10504
|
+
if (!isGlobal && _hostBehaviors(runtime).localCommandsViaRules) return false;
|
|
10515
10505
|
const cap = _capabilityRegistry && _capabilityRegistry.runtimes && _capabilityRegistry.runtimes[runtime];
|
|
10516
10506
|
const layout = cap && cap.runtime && cap.runtime.artifactLayout;
|
|
10517
10507
|
if (!layout) return false;
|
|
@@ -10560,7 +10550,7 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
10560
10550
|
|
|
10561
10551
|
if (_isSkillsRuntime) {
|
|
10562
10552
|
// Layout-driven install for skills-based runtimes (full and minimal modes)
|
|
10563
|
-
const scope =
|
|
10553
|
+
const scope = _installScopeId;
|
|
10564
10554
|
// ADR-1239 upgrade 3 / #2088: a kind may declare an alternate install `home`
|
|
10565
10555
|
// (e.g. Codex skills -> $HOME/.agents/skills) instead of the runtime's normal
|
|
10566
10556
|
// configDir. Resolve the ACTUAL on-disk skills root here, descriptor-driven
|
|
@@ -10796,15 +10786,34 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
10796
10786
|
// that used the namespaced layout (wrote bare-name files under commands/gsd/).
|
|
10797
10787
|
const legacyGsdDir = path.join(commandsDir, 'gsd');
|
|
10798
10788
|
if (fs.existsSync(legacyGsdDir)) {
|
|
10799
|
-
//
|
|
10800
|
-
|
|
10801
|
-
|
|
10802
|
-
|
|
10803
|
-
|
|
10804
|
-
|
|
10805
|
-
|
|
10806
|
-
|
|
10807
|
-
|
|
10789
|
+
// Stage user-owned dev-preferences.md DURABLY before wiping (#2875 /
|
|
10790
|
+
// #1874-F19 "site 6" — this Claude commands-install path open-coded
|
|
10791
|
+
// its own preserve/restore instead of calling preserveUserArtifacts,
|
|
10792
|
+
// found by sweeping for the read-then-wipe-then-write PATTERN rather
|
|
10793
|
+
// than for that helper's callers).
|
|
10794
|
+
// #2875 defect fix: DEGRADE, never abort install, when the staging
|
|
10795
|
+
// root cannot be resolved — skip this legacy-migration block entirely
|
|
10796
|
+
// (leave the stale dir in place) rather than wipe without a durable
|
|
10797
|
+
// backup.
|
|
10798
|
+
const _legacyGsdStagingRoot = _tryResolveUserArtifactStagingRoot(targetDir);
|
|
10799
|
+
if (_legacyGsdStagingRoot !== null) {
|
|
10800
|
+
const stagedDevPrefs = stageUserArtifacts(legacyGsdDir, ['dev-preferences.md'], _legacyGsdStagingRoot);
|
|
10801
|
+
// Preserve the ORIGINAL truthy-content check exactly: an existing but
|
|
10802
|
+
// EMPTY dev-preferences.md was (and still is) silently not migrated —
|
|
10803
|
+
// matching prior behavior byte-for-byte rather than widening scope.
|
|
10804
|
+
const preservedDevPrefs = stagedDevPrefs.names.includes('dev-preferences.md')
|
|
10805
|
+
? fs.readFileSync(path.join(stagedDevPrefs.filesDir, 'dev-preferences.md'), 'utf8')
|
|
10806
|
+
: null;
|
|
10807
|
+
fs.rmSync(legacyGsdDir, { recursive: true });
|
|
10808
|
+
console.log(` ${green}✓${reset} Removed legacy commands/gsd/ (migrated to flat gsd-<cmd>.md layout)`);
|
|
10809
|
+
if (preservedDevPrefs) {
|
|
10810
|
+
// Migrate dev-preferences to the new flat form — a RENAME on
|
|
10811
|
+
// restore (staged as 'dev-preferences.md', restored as
|
|
10812
|
+
// 'gsd-dev-preferences.md'), not a round-trip.
|
|
10813
|
+
restoreStagedUserArtifacts(commandsDir, stagedDevPrefs, { rename: { 'dev-preferences.md': 'gsd-dev-preferences.md' } });
|
|
10814
|
+
console.log(` ${green}✓${reset} Migrated dev-preferences.md to commands/gsd-dev-preferences.md`);
|
|
10815
|
+
}
|
|
10816
|
+
discardStagedUserArtifacts(stagedDevPrefs);
|
|
10808
10817
|
}
|
|
10809
10818
|
}
|
|
10810
10819
|
|
|
@@ -10835,12 +10844,29 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
10835
10844
|
}
|
|
10836
10845
|
|
|
10837
10846
|
// Copy gsd-core skill with path replacement
|
|
10838
|
-
//
|
|
10847
|
+
// Stage user-generated files DURABLY to disk before the wipe-and-copy so
|
|
10848
|
+
// they survive re-install even if the process dies mid-copy (#2875 /
|
|
10849
|
+
// #1874-F19) — copyWithPathReplacement wipes and recursively re-copies the
|
|
10850
|
+
// entire gsd-core/ tree, the single longest operation in the install, on
|
|
10851
|
+
// the path every user takes (40-design.md "Site 4 is far worse...").
|
|
10839
10852
|
const skillSrc = path.join(src, 'gsd-core');
|
|
10840
10853
|
const skillDest = path.join(targetDir, 'gsd-core');
|
|
10841
|
-
|
|
10842
|
-
|
|
10843
|
-
|
|
10854
|
+
// #2875 defect fix: this IS the mainline install step (installing
|
|
10855
|
+
// gsd-core/ itself) — unlike the optional legacy-cleanup blocks above,
|
|
10856
|
+
// install must still be able to proceed and actually write gsd-core/ even
|
|
10857
|
+
// when the staging root cannot be resolved. Degrade by skipping ONLY the
|
|
10858
|
+
// USER_OWNED_ARTIFACTS preserve/restore wrapper around the copy (warn),
|
|
10859
|
+
// never the copy itself.
|
|
10860
|
+
const _gsdArtifactsStagingRoot = _tryResolveUserArtifactStagingRoot(targetDir);
|
|
10861
|
+
if (_gsdArtifactsStagingRoot === null) {
|
|
10862
|
+
console.warn(` ${yellow}!${reset} Skipping gsd-core/${USER_OWNED_ARTIFACTS.join(', gsd-core/')} preservation (staging unavailable) — it will be lost if present.`);
|
|
10863
|
+
copyWithPathReplacement(skillSrc, skillDest, pathPrefix, runtime, false, isGlobal, targetDir);
|
|
10864
|
+
} else {
|
|
10865
|
+
const stagedGsdArtifacts = stageUserArtifacts(skillDest, USER_OWNED_ARTIFACTS, _gsdArtifactsStagingRoot);
|
|
10866
|
+
copyWithPathReplacement(skillSrc, skillDest, pathPrefix, runtime, false, isGlobal, targetDir);
|
|
10867
|
+
restoreStagedUserArtifacts(skillDest, stagedGsdArtifacts);
|
|
10868
|
+
discardStagedUserArtifacts(stagedGsdArtifacts);
|
|
10869
|
+
}
|
|
10844
10870
|
if (verifyInstalled(skillDest, 'gsd-core')) {
|
|
10845
10871
|
console.log(` ${green}✓${reset} Installed workflow assets`);
|
|
10846
10872
|
} else {
|
|
@@ -10902,219 +10928,89 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
10902
10928
|
}
|
|
10903
10929
|
}
|
|
10904
10930
|
|
|
10905
|
-
//
|
|
10906
|
-
//
|
|
10907
|
-
//
|
|
10908
|
-
//
|
|
10909
|
-
//
|
|
10910
|
-
//
|
|
10911
|
-
|
|
10912
|
-
|
|
10913
|
-
|
|
10914
|
-
//
|
|
10915
|
-
//
|
|
10916
|
-
//
|
|
10917
|
-
//
|
|
10918
|
-
//
|
|
10919
|
-
//
|
|
10920
|
-
//
|
|
10921
|
-
//
|
|
10922
|
-
//
|
|
10923
|
-
//
|
|
10924
|
-
//
|
|
10925
|
-
//
|
|
10926
|
-
//
|
|
10927
|
-
//
|
|
10928
|
-
//
|
|
10929
|
-
//
|
|
10930
|
-
//
|
|
10931
|
-
//
|
|
10932
|
-
const _DESCRIPTOR_AGENTS_RUNTIMES = new Set(['cursor', 'windsurf', 'augment', 'trae', 'codebuddy', 'copilot', 'antigravity', 'qwen', 'kimi']);
|
|
10933
|
-
|
|
10934
|
-
// Always remove stale gsd-* agents first so re-installing with
|
|
10935
|
-
// `--minimal` actually shrinks a previously-full install.
|
|
10936
|
-
// For Codex this also covers per-agent `.toml` files alongside the `.md`
|
|
10937
|
-
// sources so a full → minimal switch doesn't leave stale registrations.
|
|
10938
|
-
// Skipped for descriptor-agent runtimes (installRuntimeArtifacts prunes) and
|
|
10939
|
-
// for pluginOnlyInstall runtimes (pi, ADR-1239 / #2102 Stage 1 — no agents/
|
|
10940
|
-
// dir is ever written for them, see the leading branch below).
|
|
10941
|
-
if (!_DESCRIPTOR_AGENTS_RUNTIMES.has(runtime) && !_hostBehaviors(runtime).pluginOnlyInstall && fs.existsSync(agentsDest)) {
|
|
10942
|
-
for (const file of fs.readdirSync(agentsDest)) {
|
|
10943
|
-
if (
|
|
10944
|
-
file.startsWith('gsd-') &&
|
|
10945
|
-
(file.endsWith('.md') || (_hostBehaviors(runtime).agentTomlFiles && file.endsWith('.toml')))
|
|
10946
|
-
) {
|
|
10947
|
-
fs.unlinkSync(path.join(agentsDest, file));
|
|
10948
|
-
}
|
|
10949
|
-
}
|
|
10950
|
-
}
|
|
10951
|
-
|
|
10931
|
+
// Agents directory materialization.
|
|
10932
|
+
// #2875 Part 2 (the agents-bypass closure): EVERY runtime is now
|
|
10933
|
+
// descriptor-driven for agents — installRuntimeArtifacts (called earlier in
|
|
10934
|
+
// this function, the `_isSkillsRuntime` branch above) already wrote
|
|
10935
|
+
// agents/ for any runtime whose capability.json declares an `agents` kind,
|
|
10936
|
+
// via convertedAgentsKind/agentsKind (generic layout loop) or
|
|
10937
|
+
// installAgentsKindStandalone (OpenCode/Kilo's combinedFamilyInstall
|
|
10938
|
+
// branch, called from within installOpencodeFamilyArtifacts) — both reuse
|
|
10939
|
+
// the SAME stageAgentsForRuntimeWithConverter pipeline (path-rewrite →
|
|
10940
|
+
// attribution → converter → frontmatter extensions → normalize) the
|
|
10941
|
+
// inline loop this replaces used to hand-roll, and both prune stale gsd-*
|
|
10942
|
+
// entries via their own _removeGsdEntries pass BEFORE copying (broader
|
|
10943
|
+
// than this loop's old extension-gated stale check — see
|
|
10944
|
+
// runtime-artifact-layout.cts's convertedAgentsKind doc comment).
|
|
10945
|
+
// Minimal-mode agent filtering is handled the SAME way it already was for
|
|
10946
|
+
// the ten runtimes cut over before this change: via resolvedProfile.agents
|
|
10947
|
+
// at staging time, not a separate branch here.
|
|
10948
|
+
//
|
|
10949
|
+
// `!_isSkillsRuntime` runtimes (claude-local's legacy-flat local path, and
|
|
10950
|
+
// pi) never reach that loop at all — installAgentsKindStandalone is called
|
|
10951
|
+
// here explicitly to cover them (install-engine.cts's own doc comment
|
|
10952
|
+
// explains why; a regression here was caught by the install-tree golden
|
|
10953
|
+
// fixture, tests/fixtures/install-tree/claude-local.json). It is a no-op
|
|
10954
|
+
// for pi: its capability.json declares an EMPTY artifactLayout for both
|
|
10955
|
+
// scopes (programmatic dispatch, no named-dispatch subagent toolkit, no
|
|
10956
|
+
// host-read markdown surface), so the resolved layout has no `agents` kind
|
|
10957
|
+
// to stage and the function returns `null` without writing anything.
|
|
10952
10958
|
if (_hostBehaviors(runtime).pluginOnlyInstall) {
|
|
10953
|
-
// pi (ADR-1239 / #2102 Stage 1): programmatic dispatch has no named-dispatch
|
|
10954
|
-
// subagent toolkit (dispatch.subagentToolkit: "undocumented", no Agent-tool
|
|
10955
|
-
// equivalent) and no host-read markdown surface — skip writing agents/ entirely.
|
|
10956
10959
|
console.log(` ${green}✓${reset} pi: no subagent files (programmatic dispatch, no named-dispatch toolkit)`);
|
|
10957
|
-
} else if (
|
|
10958
|
-
// installRuntimeArtifacts already wrote agents + handles stale-file cleanup
|
|
10959
|
-
// via its own prune pass. No further action needed.
|
|
10960
|
+
} else if (_isSkillsRuntime) {
|
|
10960
10961
|
console.log(` ${dim}↳${reset} Agents installed via descriptor-driven layout (${runtime})`);
|
|
10961
|
-
} else
|
|
10962
|
-
|
|
10963
|
-
|
|
10964
|
-
|
|
10965
|
-
|
|
10966
|
-
|
|
10967
|
-
|
|
10968
|
-
|
|
10969
|
-
|
|
10970
|
-
|
|
10971
|
-
|
|
10972
|
-
|
|
10973
|
-
}
|
|
10974
|
-
|
|
10975
|
-
|
|
10962
|
+
} else {
|
|
10963
|
+
const _standaloneAgentsResult = installAgentsKindStandalone(runtime, targetDir, _installScopeId, _resolvedProfile, pathPrefix, getCommitAttribution, _installedCapabilityRegistry);
|
|
10964
|
+
if (_standaloneAgentsResult) {
|
|
10965
|
+
// #2875 defect fix: installAgentsKindStandalone now returns `null`
|
|
10966
|
+
// (rather than a truthy result pointing at an empty destDir) whenever a
|
|
10967
|
+
// restricted (non-'*') resolvedProfile — --minimal being the common
|
|
10968
|
+
// case — legitimately stages ZERO agents, matching the pre-#2875-Part-2
|
|
10969
|
+
// inline loop's behavior of never creating agentsDest under --minimal
|
|
10970
|
+
// at all (see the deleted `isMinimalMode` branch). `destDir` is
|
|
10971
|
+
// therefore guaranteed non-empty whenever we reach this branch, so a
|
|
10972
|
+
// real staging failure still fails loudly via verifyInstalled below.
|
|
10973
|
+
if (verifyInstalled(_standaloneAgentsResult.destDir, 'agents')) {
|
|
10974
|
+
console.log(` ${green}✓${reset} Installed agents`);
|
|
10975
|
+
} else {
|
|
10976
|
+
failures.push('agents');
|
|
10976
10977
|
}
|
|
10977
|
-
}
|
|
10978
|
-
|
|
10979
|
-
} else if (fs.existsSync(agentsSrc)) {
|
|
10980
|
-
fs.mkdirSync(agentsDest, { recursive: true });
|
|
10981
|
-
|
|
10982
|
-
// Copy new agents
|
|
10983
|
-
const agentEntries = fs.readdirSync(agentsSrc, { withFileTypes: true });
|
|
10984
|
-
for (const entry of agentEntries) {
|
|
10985
|
-
if (entry.isFile() && entry.name.endsWith('.md')) {
|
|
10986
|
-
const agentSourcePath = path.join(agentsSrc, entry.name);
|
|
10987
|
-
let content = fs.readFileSync(agentSourcePath, 'utf8');
|
|
10988
|
-
// #2995 (epic #1671 Phase 6.4): strip `<!-- gsd:section -->` markers BEFORE
|
|
10989
|
-
// the path-rewrite regexes below, so a rewrite can never reach inside a
|
|
10990
|
-
// marker attribute. No-op (byte-identical) for an unmarked agent.
|
|
10991
|
-
content = composeWorkflow(content, { sourcePath: agentSourcePath });
|
|
10992
|
-
// Replace ~/.claude/ and $HOME/.claude/ as they are the source of truth in the repo
|
|
10993
|
-
const dirRegex = /~\/\.claude\//g;
|
|
10994
|
-
const homeDirRegex = /\$HOME\/\.claude\//g;
|
|
10995
|
-
const bareDirRegex = /~\/\.claude\b/g;
|
|
10996
|
-
const bareHomeDirRegex = /\$HOME\/\.claude\b/g;
|
|
10997
|
-
const normalizedPathPrefix = pathPrefix.replace(/\/$/, '');
|
|
10998
|
-
// #2096: `&& !isAntigravity` dropped — antigravity is in
|
|
10999
|
-
// _DESCRIPTOR_AGENTS_RUNTIMES above, so this whole branch is already
|
|
11000
|
-
// unreachable for it; the path-rewrite skip for antigravity now lives
|
|
11001
|
-
// in the descriptor-driven `applyAgentPathRewrites` (hostBehaviors.noPathRewrite).
|
|
11002
|
-
// #2099: `if (!isCopilot)` guard dropped — copilot is ALSO in
|
|
11003
|
-
// _DESCRIPTOR_AGENTS_RUNTIMES (line ~9564 above), so this whole
|
|
11004
|
-
// `else if (fs.existsSync(agentsSrc))` branch is unreachable for it;
|
|
11005
|
-
// isCopilot was therefore always false here, making the guard a no-op.
|
|
11006
|
-
content = content.replace(dirRegex, pathPrefix);
|
|
11007
|
-
content = content.replace(homeDirRegex, pathPrefix);
|
|
11008
|
-
content = content.replace(bareDirRegex, normalizedPathPrefix);
|
|
11009
|
-
content = content.replace(bareHomeDirRegex, normalizedPathPrefix);
|
|
11010
|
-
content = processAttribution(content, getCommitAttribution(runtime));
|
|
11011
|
-
// Convert frontmatter for runtime compatibility (agents need different handling)
|
|
11012
|
-
if (_hostBehaviors(runtime).frontmatterDialect === 'opencode') {
|
|
11013
|
-
// Resolve per-agent model for OpenCode agents.
|
|
11014
|
-
// Precedence: model_overrides[agent] > model_profile_overrides.opencode.<tier> > omit.
|
|
11015
|
-
// model_overrides (#2256): explicit per-agent override, highest precedence.
|
|
11016
|
-
// model_profile_overrides (#2794): tier-based runtime resolver, same parity as Codex.
|
|
11017
|
-
const _ocAgentName = entry.name.replace(/\.md$/, '');
|
|
11018
|
-
const _ocModelOverrides = readGsdEffectiveModelOverrides(targetDir);
|
|
11019
|
-
let _ocModelOverride = _ocModelOverrides?.[_ocAgentName] || null;
|
|
11020
|
-
if (!_ocModelOverride) {
|
|
11021
|
-
// Fall back to tier-based resolution via model_profile_overrides.opencode.<tier>.
|
|
11022
|
-
const _ocRuntimeResolver = readGsdRuntimeProfileResolver(targetDir);
|
|
11023
|
-
if (_ocRuntimeResolver) {
|
|
11024
|
-
const _ocEntry = _ocRuntimeResolver.resolve(_ocAgentName);
|
|
11025
|
-
if (_ocEntry?.model) {
|
|
11026
|
-
_ocModelOverride = _ocEntry.model;
|
|
11027
|
-
}
|
|
11028
|
-
}
|
|
11029
|
-
}
|
|
11030
|
-
content = convertClaudeToOpencodeFrontmatter(content, { isAgent: true, modelOverride: _ocModelOverride });
|
|
11031
|
-
} else if (_hostBehaviors(runtime).frontmatterDialect === 'kilo') {
|
|
11032
|
-
// Resolve per-agent model for Kilo agents (#2093 UPGRADE 2; Kilo is an
|
|
11033
|
-
// OpenCode fork with the same static-frontmatter model constraint).
|
|
11034
|
-
// Precedence: model_overrides[agent] > model_profile_overrides.kilo.<tier> > omit.
|
|
11035
|
-
// model_overrides (#2256): explicit per-agent override, highest precedence.
|
|
11036
|
-
// model_profile_overrides (#2794): tier-based runtime resolver, same parity as OpenCode.
|
|
11037
|
-
const _kiloAgentName = entry.name.replace(/\.md$/, '');
|
|
11038
|
-
const _kiloModelOverrides = readGsdEffectiveModelOverrides(targetDir);
|
|
11039
|
-
let _kiloModelOverride = _kiloModelOverrides?.[_kiloAgentName] || null;
|
|
11040
|
-
if (!_kiloModelOverride) {
|
|
11041
|
-
// Fall back to tier-based resolution via model_profile_overrides.kilo.<tier>.
|
|
11042
|
-
const _kiloRuntimeResolver = readGsdRuntimeProfileResolver(targetDir);
|
|
11043
|
-
if (_kiloRuntimeResolver) {
|
|
11044
|
-
const _kiloEntry = _kiloRuntimeResolver.resolve(_kiloAgentName);
|
|
11045
|
-
if (_kiloEntry?.model) {
|
|
11046
|
-
_kiloModelOverride = _kiloEntry.model;
|
|
11047
|
-
}
|
|
11048
|
-
}
|
|
11049
|
-
}
|
|
11050
|
-
content = convertClaudeToKiloFrontmatter(content, { isAgent: true, modelOverride: _kiloModelOverride });
|
|
11051
|
-
} else if (_hostBehaviors(runtime).frontmatterDialect === 'codex') {
|
|
11052
|
-
content = convertClaudeAgentToCodexAgent(content);
|
|
11053
|
-
// #2099: `else if (isCopilot)` arm dropped — copilot is unreachable
|
|
11054
|
-
// here (see the isCopilot-guard-drop comment above); its content
|
|
11055
|
-
// conversion is applied pre-staging via the descriptor's
|
|
11056
|
-
// artifactLayout.converter (runtime-artifact-layout.cts), independent
|
|
11057
|
-
// of this legacy loop.
|
|
11058
|
-
// #2100: `else if (isWindsurf)` arm dropped — windsurf is ALSO in
|
|
11059
|
-
// _DESCRIPTOR_AGENTS_RUNTIMES (line ~9575 above), so this whole
|
|
11060
|
-
// `else if (fs.existsSync(agentsSrc))` branch is unreachable for it;
|
|
11061
|
-
// isWindsurf was therefore always false here, making the arm dead.
|
|
11062
|
-
// Its content conversion is applied pre-staging via the descriptor's
|
|
11063
|
-
// artifactLayout.converter (convertClaudeAgentToWindsurfAgent),
|
|
11064
|
-
// independent of this legacy loop.
|
|
11065
|
-
} else if (_hostBehaviors(runtime).frontmatterDialect === 'cline') {
|
|
11066
|
-
// Descriptor-driven (ADR-1239 / #2090): folded from `isCline` into
|
|
11067
|
-
// hostBehaviors.frontmatterDialect === 'cline'.
|
|
11068
|
-
content = convertClaudeAgentToClineAgent(content);
|
|
11069
|
-
} else if (_hostBehaviors(runtime).brandingRewrites) {
|
|
11070
|
-
// Descriptor-driven (ADR-1239 / #2092): folded from separate
|
|
11071
|
-
// `isQwen` / hermes-hardcoded branches into a single read of
|
|
11072
|
-
// runtime.hostBehaviors.brandingRewrites (qwen -> QWEN.md/Qwen
|
|
11073
|
-
// Code/.qwen/, hermes -> HERMES.md/Hermes Agent/.hermes/).
|
|
11074
|
-
const _b = _hostBehaviors(runtime).brandingRewrites;
|
|
11075
|
-
content = content.replace(/CLAUDE\.md/g, _b['CLAUDE.md']);
|
|
11076
|
-
content = content.replace(/\bClaude Code\b/g, _b['Claude Code']);
|
|
11077
|
-
content = content.replace(/\.claude\//g, _b['.claude/']);
|
|
11078
|
-
}
|
|
11079
|
-
// #443 — Inject `effort:` into the Claude .md frontmatter ONLY.
|
|
11080
|
-
// OpenCode/Qwen/Hermes also produce .md files but break on
|
|
11081
|
-
// unknown frontmatter keys (the repo bans skills:/permissionMode: for
|
|
11082
|
-
// the same reason — see tests/agent-frontmatter.test.cjs).
|
|
11083
|
-
// Claude Code reads per-subagent `effort:` frontmatter (anthropics/claude-code #31536).
|
|
11084
|
-
// Injection is per-runtime at install time because the canonical source
|
|
11085
|
-
// agents/*.md must stay runtime-safe (no effort: key in source).
|
|
11086
|
-
if ((_hostBehaviors(runtime).agentFrontmatterExtensions || []).includes('effort')) {
|
|
11087
|
-
const _effortCfg = readGsdEffectiveEffortConfig(targetDir);
|
|
11088
|
-
const _agentName = entry.name.replace(/\.md$/, '');
|
|
11089
|
-
const _universalEffort = resolveInstallTimeEffort(_effortCfg, _agentName);
|
|
11090
|
-
const _renderedEffort = _getGsdEffortCatalog().renderEffortForRuntime(runtime, _universalEffort).value;
|
|
11091
|
-
content = injectEffortFrontmatter(content, _renderedEffort);
|
|
11092
|
-
const _disallowedTools = READONLY_AGENT_DISALLOWED_TOOLS[_agentName];
|
|
11093
|
-
if (_disallowedTools) content = injectDisallowedToolsFrontmatter(content, _disallowedTools);
|
|
11094
|
-
}
|
|
11095
|
-
// #3677 — normalize retired `/gsd:<cmd>` colon refs in the agent body
|
|
11096
|
-
// to the canonical hyphen form `/gsd-<cmd>` for hyphen-`name:`
|
|
11097
|
-
// runtimes (claude / qwen / hermes). Self-converting and
|
|
11098
|
-
// colon-canonical runtimes are skipped by the predicate — see
|
|
11099
|
-
// shouldNormalizeHyphenNamespaceInAgentBody above. Mirrors the
|
|
11100
|
-
// SKILL.md-body fix shipped via #3629.
|
|
11101
|
-
content = normalizeAgentBodyForRuntime(content, runtime, readGsdCommandNames());
|
|
11102
|
-
// #2099: `isCopilot ? ... : entry.name` ternary dropped — copilot is
|
|
11103
|
-
// unreachable here (see the isCopilot-guard-drop comment above), so
|
|
11104
|
-
// the ternary always evaluated to entry.name in practice; its
|
|
11105
|
-
// .agent.md suffix is applied by the descriptor-driven fold in
|
|
11106
|
-
// src/install-engine.cts (hostBehaviors.agentFileExtension).
|
|
11107
|
-
const destName = entry.name;
|
|
11108
|
-
fs.writeFileSync(path.join(agentsDest, destName), content);
|
|
11109
|
-
}
|
|
11110
|
-
}
|
|
11111
|
-
if (verifyInstalled(agentsDest, 'agents')) {
|
|
11112
|
-
console.log(` ${green}✓${reset} Installed agents`);
|
|
10978
|
+
} else if (_resolvedProfile.skills !== '*') {
|
|
10979
|
+
console.log(` ${dim}↳${reset} Skipping agents (${_resolvedProfile.name} profile excludes all agents — run \`gsd update\` with a broader profile to add them)`);
|
|
11113
10980
|
} else {
|
|
11114
|
-
|
|
10981
|
+
console.log(` ${dim}↳${reset} No agents kind declared for ${runtime} at this scope`);
|
|
11115
10982
|
}
|
|
11116
10983
|
}
|
|
11117
10984
|
|
|
10985
|
+
// Codex registers agents in `config.toml` via `[agents.gsd-*]` sections —
|
|
10986
|
+
// NOT agents-directory materialization (design doc "Deliberately not in
|
|
10987
|
+
// scope"), so this stays independent of the agents/ write above. Without
|
|
10988
|
+
// stripping these on a full → minimal reinstall, the runtime would keep
|
|
10989
|
+
// advertising the old full agent surface even though the descriptor-driven
|
|
10990
|
+
// write above already skipped writing the .md files for a minimal-tier
|
|
10991
|
+
// resolvedProfile. Reuse the same helper that powers `--uninstall`.
|
|
10992
|
+
if (isMinimalMode(_effectiveInstallMode) && _hostBehaviors(runtime).tomlConfigInstall) {
|
|
10993
|
+
const codexConfigPath = path.join(targetDir, 'config.toml');
|
|
10994
|
+
if (fs.existsSync(codexConfigPath)) {
|
|
10995
|
+
const existing = fs.readFileSync(codexConfigPath, 'utf8');
|
|
10996
|
+
const cleaned = stripGsdFromCodexConfig(existing);
|
|
10997
|
+
if (cleaned === null) {
|
|
10998
|
+
fs.unlinkSync(codexConfigPath);
|
|
10999
|
+
} else if (cleaned !== existing) {
|
|
11000
|
+
fs.writeFileSync(codexConfigPath, cleaned);
|
|
11001
|
+
}
|
|
11002
|
+
}
|
|
11003
|
+
}
|
|
11004
|
+
|
|
11005
|
+
// agentsSrc is declared as `let` before the enclosing try block (not const)
|
|
11006
|
+
// so it is accessible by installCodexConfig() in the Codex config section
|
|
11007
|
+
// below — that function reads RAW source agents/*.md (not the
|
|
11008
|
+
// descriptor-staged output above) to build Codex's per-agent config.toml
|
|
11009
|
+
// sidecar files, a separate writer this migration deliberately does not
|
|
11010
|
+
// touch (design doc: "Codex's config.toml [agents.gsd-*] strip... is not
|
|
11011
|
+
// agents-directory materialization").
|
|
11012
|
+
agentsSrc = _stageAgents(path.join(src, 'agents'));
|
|
11013
|
+
|
|
11118
11014
|
// Copy CHANGELOG.md
|
|
11119
11015
|
const changelogSrc = path.join(src, 'CHANGELOG.md');
|
|
11120
11016
|
const changelogDest = path.join(targetDir, 'gsd-core', 'CHANGELOG.md');
|
|
@@ -11490,12 +11386,35 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
11490
11386
|
}
|
|
11491
11387
|
|
|
11492
11388
|
// Write file manifest for future modification detection
|
|
11493
|
-
writeManifest(targetDir, runtime, { mode: _effectiveInstallMode, scope:
|
|
11389
|
+
writeManifest(targetDir, runtime, { mode: _effectiveInstallMode, scope: _installScopeId });
|
|
11494
11390
|
console.log(` ${green}✓${reset} Wrote file manifest (${MANIFEST_NAME})`);
|
|
11495
11391
|
|
|
11496
11392
|
// Report any backed-up local patches
|
|
11497
11393
|
reportLocalPatches(targetDir, runtime);
|
|
11498
11394
|
|
|
11395
|
+
// #2873: cross-scope shadow report. Fires ONCE per install (this is the
|
|
11396
|
+
// only writeManifest call site that gets it — the other four sites are
|
|
11397
|
+
// sub-writes within a single install, not separate installs). A shadowed
|
|
11398
|
+
// install is a warning, never a failure (ADR-2866 Consequences), so this
|
|
11399
|
+
// never touches `failures` or `process.exit`, and the whole block is
|
|
11400
|
+
// wrapped in a try/catch that swallows everything: a report failure must
|
|
11401
|
+
// never fail an otherwise-successful install (design row C5). No options
|
|
11402
|
+
// are injected into buildShadowReport — this is the production call shape,
|
|
11403
|
+
// resolving the real machine via os.homedir()/process.cwd() defaults
|
|
11404
|
+
// inside the resolver.
|
|
11405
|
+
try {
|
|
11406
|
+
const shadowReport = buildShadowReport(runtime);
|
|
11407
|
+
const shadowLines = renderShadowReport(shadowReport);
|
|
11408
|
+
if (shadowLines.length > 0) {
|
|
11409
|
+
console.warn(`\n ${yellow}⚠${reset} ${shadowLines[0]}`);
|
|
11410
|
+
for (const line of shadowLines.slice(1)) {
|
|
11411
|
+
console.warn(` ${dim}${line}${reset}`);
|
|
11412
|
+
}
|
|
11413
|
+
}
|
|
11414
|
+
} catch (_shadowReportErr) {
|
|
11415
|
+
// Never fail an install over a reporting concern — see comment above.
|
|
11416
|
+
}
|
|
11417
|
+
|
|
11499
11418
|
// Verify no leaked .claude paths in non-Claude runtimes (manifest-scoped)
|
|
11500
11419
|
if (!_hostBehaviors(runtime).ownsClaudePaths) {
|
|
11501
11420
|
const leakedPaths = [];
|
|
@@ -11641,7 +11560,7 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
11641
11560
|
// (copyCommandsAsCodexSkills removes pre-existing gsd-* dirs before re-writing)
|
|
11642
11561
|
// are restored even when they are absent from disk at rollback time (#3245 CR).
|
|
11643
11562
|
// • Dirs that did not pre-exist: remove entirely.
|
|
11644
|
-
const _rollbackSkillsDir = _resolveSkillsRootDir(runtime, targetDir,
|
|
11563
|
+
const _rollbackSkillsDir = _resolveSkillsRootDir(runtime, targetDir, _installScopeId);
|
|
11645
11564
|
// Pass 1 — restore snapshot entries (may be absent from disk if deleted mid-install).
|
|
11646
11565
|
for (const skillName of codexPreInstallSkillNames) {
|
|
11647
11566
|
const skillDirPath = path.join(_rollbackSkillsDir, skillName);
|
|
@@ -11756,7 +11675,7 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
11756
11675
|
// Re-write the manifest now that .toml agent files exist on disk.
|
|
11757
11676
|
// The initial writeManifest call (before Codex config generation) could
|
|
11758
11677
|
// not include agents/gsd-*.toml because those files did not yet exist.
|
|
11759
|
-
writeManifest(targetDir, runtime, { mode: _effectiveInstallMode, scope:
|
|
11678
|
+
writeManifest(targetDir, runtime, { mode: _effectiveInstallMode, scope: _installScopeId });
|
|
11760
11679
|
} else {
|
|
11761
11680
|
console.log(` ${dim}↳${reset} Skipping Codex agent config generation (minimal install)`);
|
|
11762
11681
|
}
|
|
@@ -12044,7 +11963,7 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
12044
11963
|
// manifest-tracked (verified) — uninstall removes them explicitly via
|
|
12045
11964
|
// removeCursorHooksJson + its script list, and reconcile is idempotent.
|
|
12046
11965
|
// The re-run is retained for parity with the settings.json install path.
|
|
12047
|
-
writeManifest(targetDir, runtime, { mode: _effectiveInstallMode, scope:
|
|
11966
|
+
writeManifest(targetDir, runtime, { mode: _effectiveInstallMode, scope: _installScopeId });
|
|
12048
11967
|
persistActiveProfileMarker();
|
|
12049
11968
|
return { settingsPath: null, settings: null, statuslineCommand: null, updateBannerCommand: null, runtime, configDir: targetDir };
|
|
12050
11969
|
}
|
|
@@ -12131,7 +12050,7 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
12131
12050
|
// explicitly via removeWindsurfHooksJson, and reconcileWindsurfHooksJson
|
|
12132
12051
|
// is idempotent on repeated installs, so manifest tracking isn't needed
|
|
12133
12052
|
// for correctness here.
|
|
12134
|
-
writeManifest(targetDir, runtime, { mode: _effectiveInstallMode, scope:
|
|
12053
|
+
writeManifest(targetDir, runtime, { mode: _effectiveInstallMode, scope: _installScopeId });
|
|
12135
12054
|
}
|
|
12136
12055
|
|
|
12137
12056
|
persistActiveProfileMarker();
|
|
@@ -12145,7 +12064,7 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
12145
12064
|
writeClineArtifacts(targetDir, isGlobal);
|
|
12146
12065
|
// Re-run the manifest pass: these artifacts are written *after* the earlier
|
|
12147
12066
|
// writeManifest() call, so a second pass is needed to hash-track them.
|
|
12148
|
-
writeManifest(targetDir, runtime, { mode: _effectiveInstallMode, scope:
|
|
12067
|
+
writeManifest(targetDir, runtime, { mode: _effectiveInstallMode, scope: _installScopeId });
|
|
12149
12068
|
persistActiveProfileMarker();
|
|
12150
12069
|
return { settingsPath: null, settings: null, statuslineCommand: null, updateBannerCommand: null, runtime, configDir: targetDir };
|
|
12151
12070
|
}
|
|
@@ -12160,10 +12079,24 @@ function install(isGlobal, runtime = DEFAULT_RUNTIME, options = {}) {
|
|
|
12160
12079
|
// #338: local Claude installs write to settings.local.json (Claude Code's per-user/gitignored slot)
|
|
12161
12080
|
// so engineer-specific absolute paths (Node binary, home dir) never land in the repo-shared
|
|
12162
12081
|
// settings.json. Global installs and all other runtimes continue to use settings.json.
|
|
12082
|
+
// #2870: the CURRENT scope's settings filename is sourced from the Install
|
|
12083
|
+
// Scope Module (_installScope.settingsFile, resolveScope's per-scope field)
|
|
12084
|
+
// instead of indexing _scopedSettings by hand. _scopedSettings itself is
|
|
12085
|
+
// retained unchanged as the #338-privacy fail-safe path: _hostBehaviors
|
|
12086
|
+
// already degrades to FALLBACK_HOST_BEHAVIORS (see that constant's comment
|
|
12087
|
+
// above) when the registry fails to load, whereas resolveScope's registry
|
|
12088
|
+
// lookup throws in that same scenario (_installScope is null when it did).
|
|
12089
|
+
// Falling back to _scopedSettings[_installScopeId] there — and keeping the
|
|
12090
|
+
// non-local-claude branch's expression untouched — means this is
|
|
12091
|
+
// byte-identical to the pre-migration computation in every case, including
|
|
12092
|
+
// the broken-registry fail-safe floor.
|
|
12163
12093
|
const _scopedSettings = _hostBehaviors(runtime).settingsFileByScope || null;
|
|
12164
|
-
const
|
|
12094
|
+
const _currentScopeSettingsFile = _installScope
|
|
12095
|
+
? _installScope.settingsFile
|
|
12096
|
+
: (_scopedSettings ? (_scopedSettings[_installScopeId] ?? null) : null);
|
|
12097
|
+
const isLocalClaude = (!isGlobal && !!_currentScopeSettingsFile);
|
|
12165
12098
|
const settingsFileName = isLocalClaude
|
|
12166
|
-
?
|
|
12099
|
+
? _currentScopeSettingsFile
|
|
12167
12100
|
: ((_scopedSettings && _scopedSettings.global) || 'settings.json');
|
|
12168
12101
|
// ADR-1239 Phase B write-confinement: the descriptor-sourced settings filename
|
|
12169
12102
|
// must resolve under targetDir (this path also drives a recursive mkdirSync).
|
|
@@ -13441,13 +13374,12 @@ module.exports = {
|
|
|
13441
13374
|
shouldNormalizeHyphenNamespaceInAgentBody,
|
|
13442
13375
|
normalizeAgentBodyForRuntime,
|
|
13443
13376
|
yamlIdentifier,
|
|
13444
|
-
computePathPrefix,
|
|
13445
|
-
applyRuntimeContentRewritesInPlace,
|
|
13446
13377
|
getCodexSkillAdapterHeader,
|
|
13447
13378
|
convertClaudeCommandToCursorSkill,
|
|
13448
13379
|
convertClaudeAgentToCursorAgent,
|
|
13449
13380
|
convertClaudeAgentToCodexAgent,
|
|
13450
13381
|
generateCodexAgentToml,
|
|
13382
|
+
_resetCodexWarningDedupeForTests,
|
|
13451
13383
|
cleanupCodexSkillMetadataSidecars,
|
|
13452
13384
|
cleanupWindsurfLegacyDevinSkills,
|
|
13453
13385
|
cleanupMovedSkillsOldLocation,
|
|
@@ -13455,7 +13387,6 @@ module.exports = {
|
|
|
13455
13387
|
_resolveSkillsRootDir,
|
|
13456
13388
|
codexBareAgentsHasOnlyKnownScalars,
|
|
13457
13389
|
extractCodexUserAgentsScalars,
|
|
13458
|
-
spliceCodexAgentsScalars,
|
|
13459
13390
|
CODEX_EXTENDED_HOOK_EVENTS,
|
|
13460
13391
|
generateCodexConfigBlock,
|
|
13461
13392
|
stripGsdFromCodexConfig,
|
|
@@ -13466,13 +13397,6 @@ module.exports = {
|
|
|
13466
13397
|
validateCodexConfigSchema,
|
|
13467
13398
|
mergeCodexConfig,
|
|
13468
13399
|
installCodexConfig,
|
|
13469
|
-
readGsdRuntimeProfileResolver,
|
|
13470
|
-
readGsdEffectiveModelOverrides,
|
|
13471
|
-
readGsdEffectiveEffortConfig,
|
|
13472
|
-
resolveInstallTimeEffort,
|
|
13473
|
-
injectEffortFrontmatter,
|
|
13474
|
-
get _GSD_EFFORT_MANIFEST_TIER_DEFAULTS() { return _getGsdEffortCatalog().EFFORT_MANIFEST_TIER_DEFAULTS; },
|
|
13475
|
-
get _GSD_EFFORT_MANIFEST_DEFAULT() { return _getGsdEffortCatalog().EFFORT_MANIFEST_DEFAULT; },
|
|
13476
13400
|
install,
|
|
13477
13401
|
installAllRuntimes,
|
|
13478
13402
|
uninstall,
|
|
@@ -13482,6 +13406,10 @@ module.exports = {
|
|
|
13482
13406
|
// #3023 — shared hook bundle directory name, descriptor-driven
|
|
13483
13407
|
SHARED_HOOKS_DIR_DEFAULT,
|
|
13484
13408
|
resolveSharedHooksDirName,
|
|
13409
|
+
// #3184 — uninstall-side GSD-managed file enumerations, exported for
|
|
13410
|
+
// parity assertions against the wholesale-copy source directories
|
|
13411
|
+
GSD_CHANGESET_FILES,
|
|
13412
|
+
GSD_SCRIPTS_LIB_FILES,
|
|
13485
13413
|
convertSlashCommandsToCodexSkillMentions,
|
|
13486
13414
|
convertClaudeCommandToCodexSkill,
|
|
13487
13415
|
convertClaudeCommandToKimiSkill,
|
|
@@ -13490,8 +13418,6 @@ module.exports = {
|
|
|
13490
13418
|
buildKimiAgentArtifacts,
|
|
13491
13419
|
convertClaudeToOpencodeFrontmatter,
|
|
13492
13420
|
convertClaudeToKiloFrontmatter,
|
|
13493
|
-
convertClaudeCommandToOpencodeSkill,
|
|
13494
|
-
convertClaudeCommandToKiloSkill,
|
|
13495
13421
|
configureOpencodePermissions,
|
|
13496
13422
|
neutralizeAgentReferences,
|
|
13497
13423
|
// #768 — Claude Code permissions pre-population
|
|
@@ -13501,7 +13427,6 @@ module.exports = {
|
|
|
13501
13427
|
GSD_CLAUDE_DENY_PERMISSIONS,
|
|
13502
13428
|
GSD_CODEX_MARKER,
|
|
13503
13429
|
CODEX_AGENT_SANDBOX,
|
|
13504
|
-
getDirName,
|
|
13505
13430
|
getGlobalDir,
|
|
13506
13431
|
getConfigDirFromHome,
|
|
13507
13432
|
resolveKiloConfigPath,
|
|
@@ -13523,20 +13448,11 @@ module.exports = {
|
|
|
13523
13448
|
mergeCopilotInstructions,
|
|
13524
13449
|
stripGsdFromCopilotInstructions,
|
|
13525
13450
|
GSD_COPILOT_HOOK_FILE,
|
|
13526
|
-
buildCopilotHookConfig,
|
|
13527
|
-
writeCopilotHookConfig,
|
|
13528
13451
|
convertClaudeToAntigravityContent,
|
|
13529
13452
|
convertClaudeCommandToAntigravitySkill,
|
|
13530
13453
|
convertClaudeAgentToAntigravityAgent,
|
|
13531
13454
|
convertClaudeCommandToClaudeSkill,
|
|
13532
13455
|
skillFrontmatterName,
|
|
13533
|
-
convertClaudeToWindsurfMarkdown,
|
|
13534
|
-
convertClaudeCommandToWindsurfSkill,
|
|
13535
|
-
convertClaudeCommandToWindsurfWorkflow,
|
|
13536
|
-
convertClaudeAgentToWindsurfAgent,
|
|
13537
|
-
convertClaudeToAugmentMarkdown,
|
|
13538
|
-
convertClaudeCommandToAugmentSkill,
|
|
13539
|
-
convertClaudeAgentToAugmentAgent,
|
|
13540
13456
|
convertClaudeToTraeMarkdown,
|
|
13541
13457
|
convertClaudeCommandToTraeSkill,
|
|
13542
13458
|
convertClaudeAgentToTraeAgent,
|
|
@@ -13547,8 +13463,6 @@ module.exports = {
|
|
|
13547
13463
|
convertClaudeToCliineMarkdown,
|
|
13548
13464
|
convertClaudeCommandToClineSkill,
|
|
13549
13465
|
convertClaudeAgentToClineAgent,
|
|
13550
|
-
// #2284(b) — cross-cutting branding protected-region helper
|
|
13551
|
-
applyClaudeCodeBrandSwap,
|
|
13552
13466
|
// #2284 — Hermes named-dispatch → delegate_task projection
|
|
13553
13467
|
convertClaudeToHermesMarkdown,
|
|
13554
13468
|
projectNamedDispatchToStructuralDelegate,
|
|
@@ -13558,30 +13472,11 @@ module.exports = {
|
|
|
13558
13472
|
maskStringLiterals,
|
|
13559
13473
|
findDispatchCallSpans,
|
|
13560
13474
|
_assertProjectionComplete,
|
|
13561
|
-
_normalizeDispatchCallSpan,
|
|
13562
|
-
buildClineRulesBody,
|
|
13563
|
-
buildClineAgentsMdBody,
|
|
13564
|
-
buildClinePreToolUseHook,
|
|
13565
|
-
writeClineArtifacts,
|
|
13566
|
-
mergeGsdAgentsMd,
|
|
13567
13475
|
GSD_CURSOR_SESSION_HOOK_SCRIPT,
|
|
13568
13476
|
GSD_CURSOR_POST_TOOL_HOOK_SCRIPT,
|
|
13569
|
-
GSD_CURSOR_PRE_TOOL_HOOK_SCRIPT,
|
|
13570
|
-
GSD_CURSOR_STOP_HOOK_SCRIPT,
|
|
13571
|
-
GSD_CURSOR_SUBAGENT_START_HOOK_SCRIPT,
|
|
13572
|
-
GSD_CURSOR_SUBAGENT_STOP_HOOK_SCRIPT,
|
|
13573
|
-
GSD_CURSOR_HOOK_SCRIPTS,
|
|
13574
13477
|
GSD_CURSOR_HOOK_MARKER,
|
|
13575
|
-
buildCursorHookEntry,
|
|
13576
|
-
isManagedCursorHookEntry,
|
|
13577
|
-
reconcileCursorHooksJson,
|
|
13578
|
-
writeCursorHooksJson,
|
|
13579
|
-
removeCursorHooksJson,
|
|
13580
13478
|
GSD_WINDSURF_PRE_WRITE_HOOK_SCRIPT,
|
|
13581
13479
|
GSD_WINDSURF_PRE_COMMAND_HOOK_SCRIPT,
|
|
13582
|
-
GSD_WINDSURF_HOOK_SCRIPTS,
|
|
13583
|
-
writeWindsurfHooksJson,
|
|
13584
|
-
removeWindsurfHooksJson,
|
|
13585
13480
|
stripGsdFromAgentsMd,
|
|
13586
13481
|
GSD_AGENTS_MD_MARKER,
|
|
13587
13482
|
GSD_AGENTS_MD_CLOSE_MARKER,
|
|
@@ -13589,11 +13484,9 @@ module.exports = {
|
|
|
13589
13484
|
saveLocalPatches,
|
|
13590
13485
|
reportLocalPatches,
|
|
13591
13486
|
validateHookFields,
|
|
13592
|
-
preserveUserArtifacts,
|
|
13593
|
-
restoreUserArtifacts,
|
|
13594
|
-
migrateLegacyDevPreferencesToSkill,
|
|
13595
13487
|
populatePristineDir,
|
|
13596
|
-
|
|
13488
|
+
_resolveUserArtifactStagingRoot,
|
|
13489
|
+
_tryResolveUserArtifactStagingRoot,
|
|
13597
13490
|
finishInstall,
|
|
13598
13491
|
homePathCoveredByRc,
|
|
13599
13492
|
homePathCoveredByFishConfig,
|
|
@@ -13608,34 +13501,11 @@ module.exports = {
|
|
|
13608
13501
|
buildUpdateBannerPromptText,
|
|
13609
13502
|
parseUpdateBannerInput,
|
|
13610
13503
|
buildUpdateBannerHookEntry,
|
|
13611
|
-
buildHookCommand,
|
|
13612
|
-
normalizeNodePath,
|
|
13613
|
-
resolveNodeRunner,
|
|
13614
|
-
referencesHook,
|
|
13615
|
-
applySettingsJsonHooks,
|
|
13616
|
-
rewriteLegacyManagedNodeHookCommands,
|
|
13617
|
-
buildCodexHookBlock,
|
|
13618
|
-
rewriteLegacyCodexHookBlock,
|
|
13619
|
-
buildCodexHookWindowsShimIR,
|
|
13620
|
-
ensureCodexHooksJsonSessionStart,
|
|
13621
|
-
ensureCodexHooksJsonEvent,
|
|
13622
|
-
removeCodexHooksJsonEvent,
|
|
13623
|
-
reconcileCodexHooksJsonEvent,
|
|
13624
|
-
readGsdCommandNames,
|
|
13625
|
-
installRuntimeArtifacts,
|
|
13626
|
-
installOpencodeFamilySkills,
|
|
13627
|
-
uninstallRuntimeArtifacts,
|
|
13628
13504
|
parseConfigDirFromArgs,
|
|
13629
13505
|
cleanupLegacyGsdCc,
|
|
13630
|
-
_applyRuntimeRewrites,
|
|
13631
13506
|
// #1191 — exported so tests exercise the REAL readSettings, not a replica
|
|
13632
13507
|
readSettings,
|
|
13633
13508
|
stripJsonComments,
|
|
13634
|
-
// Compatibility relays retained after auditing the former broad
|
|
13635
|
-
// runtimeArtifactConversion spread (#1559).
|
|
13636
|
-
processAttribution,
|
|
13637
|
-
applyRuntimeContentRewritesForCommandsInPlace,
|
|
13638
|
-
_copyStaged,
|
|
13639
13509
|
copyWithPathReplacement,
|
|
13640
13510
|
};
|
|
13641
13511
|
|