@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
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
/**
|
|
6
|
+
* Install Fs Adapter — #2874 (epic #2866 Phase 5), governed by ADR-58.
|
|
7
|
+
*
|
|
8
|
+
* Narrow, enumerated fs seam for the install/staging call tree rooted at
|
|
9
|
+
* `installRuntimeArtifacts` (install-engine.cts): layout source-root
|
|
10
|
+
* resolution (runtime-artifact-layout.cts), profile staging
|
|
11
|
+
* (install-profiles.cts), content-rewrite passes
|
|
12
|
+
* (runtime-artifact-conversion.cts), the CommonJS module-type marker
|
|
13
|
+
* (commonjs-marker.cts), and the two installer-migrations entry points this
|
|
14
|
+
* call tree reaches — `readInstallManifest` / `classifyArtifact`
|
|
15
|
+
* (installer-migrations.cts). Extends the `deps` bag precedent already
|
|
16
|
+
* established by `createRuntimeArtifactInstallPlan`
|
|
17
|
+
* (runtime-artifact-install-plan.cts:155) — this is NOT a general-purpose
|
|
18
|
+
* `node:fs` wrapper (40-design.md "Rejected" #1): only the operations this
|
|
19
|
+
* call tree actually performs are enumerated below. installer-migrations.cts
|
|
20
|
+
* is ~1200 lines covering migration planning/apply/rollback/locking/journal
|
|
21
|
+
* machinery unrelated to `installRuntimeArtifacts` — only its two reachable
|
|
22
|
+
* entry points (and `sha256File`, their shared hashing helper — still raw-fd
|
|
23
|
+
* streaming via `openSync`/`readSync`/`closeSync`, now routed through this
|
|
24
|
+
* seam instead of calling `node:fs` directly, so large-file hashing never
|
|
25
|
+
* buffers a whole file through the injected adapter either) are routed; the
|
|
26
|
+
* rest of that file is untouched, deliberately, because it is off this call
|
|
27
|
+
* tree.
|
|
28
|
+
*
|
|
29
|
+
* ─────────────────────────────────────────────────────────────────────────
|
|
30
|
+
* DELIVERY MECHANISM — ambient, not threaded (read this before adding a call
|
|
31
|
+
* site that needs a different adapter mid-call)
|
|
32
|
+
* ─────────────────────────────────────────────────────────────────────────
|
|
33
|
+
*
|
|
34
|
+
* A single mutable "current adapter" (`current`, below) is swapped for the
|
|
35
|
+
* duration of one synchronous `installRuntimeArtifacts` call via
|
|
36
|
+
* `withInstallFs`, rather than a `deps` parameter threaded through every
|
|
37
|
+
* function on the call tree. The rejected alternative was threading: it
|
|
38
|
+
* would touch signatures across `install-profiles.cts` (5 staging
|
|
39
|
+
* functions), the 3000+-line `runtime-artifact-conversion.cts` (rewrite
|
|
40
|
+
* passes), `commonjs-marker.cts`, and `installer-migrations.cts` — a dozen+
|
|
41
|
+
* unrelated call sites — for no behavioral gain over an ambient swap, and
|
|
42
|
+
* `createRuntimeArtifactInstallPlan`'s own `deps` bag (the precedent this
|
|
43
|
+
* seam extends) does not reach that deep either. Every fs-touching site on
|
|
44
|
+
* the call tree reads the active adapter via `installFs()` instead of
|
|
45
|
+
* importing `node:fs` directly.
|
|
46
|
+
*
|
|
47
|
+
* SYNCHRONOUS-ONLY / RE-ENTRANCY ASSUMPTION (load-bearing, not incidental):
|
|
48
|
+
* every method on this seam is `*Sync`, and `installRuntimeArtifacts` never
|
|
49
|
+
* awaits mid-call — the whole call tree from the top-level `withInstallFs`
|
|
50
|
+
* wrap down to the last `fs` touch runs on one turn of the event loop with
|
|
51
|
+
* no interleaving. That is what makes a single ambient variable safe instead
|
|
52
|
+
* of a race: nothing else can observe or mutate `current` while it is set.
|
|
53
|
+
* This assumption BREAKS if `installRuntimeArtifacts` (or anything it calls)
|
|
54
|
+
* ever becomes `async`, or if two installs run concurrently in the same
|
|
55
|
+
* process (the second `withInstallFs` call would clobber the first's
|
|
56
|
+
* adapter mid-flight) — neither is true today, but a future change that
|
|
57
|
+
* introduces either must revisit this module before trusting it.
|
|
58
|
+
*
|
|
59
|
+
* DEFERRED CLEANUP ACROSS THE RESTORE (#2874 leak-fix): staged directories
|
|
60
|
+
* outlive one `withInstallFs` call — `install-profiles.cts`'s
|
|
61
|
+
* `cleanupStagedSkills` runs later, from a `process.on('exit'/'SIGINT'/…)`
|
|
62
|
+
* handler, by which time `withInstallFs`'s `finally` has already restored
|
|
63
|
+
* `current` back to whatever was active before (real fs, in the top-level
|
|
64
|
+
* case). A cleanup handler that resolved "which adapter do I use" via
|
|
65
|
+
* `installFs()` at CLEANUP time would therefore always see the real adapter,
|
|
66
|
+
* even for a directory that was staged entirely inside a fake-adapter call —
|
|
67
|
+
* performing real filesystem IO on a path that only ever existed in the
|
|
68
|
+
* fake's in-memory store. `install-profiles.cts` avoids this by capturing
|
|
69
|
+
* the adapter OBJECT `installFs()` returns at STAGING (registration) time,
|
|
70
|
+
* keyed by path, in its own `STAGED_DIR_ADAPTERS` map, and replaying that
|
|
71
|
+
* captured object — not the ambient `current` — at cleanup time. A real
|
|
72
|
+
* install's dirs were staged with the real adapter object, so their cleanup
|
|
73
|
+
* is byte-identical to before this fix.
|
|
74
|
+
*
|
|
75
|
+
* `withInstallFs` ALWAYS restores the previous adapter in a `finally`, even
|
|
76
|
+
* on throw — a failed install (or a test that intentionally throws to prove
|
|
77
|
+
* a guard) must never leak a fake adapter into whatever runs next in the
|
|
78
|
+
* same process (e.g. the next `node:test` in a shared worker).
|
|
79
|
+
*
|
|
80
|
+
* ⚠️ PARTIAL-ADAPTER TRAP (#2875 defect fix — CLOSED, not merely documented):
|
|
81
|
+
* `withInstallFs` used to merge the injected `partial` OVER the real adapter
|
|
82
|
+
* (`{ ...REAL_ADAPTER, ...partial }`), so any method the partial did not
|
|
83
|
+
* define silently resolved to REAL `node:fs`. An incomplete fake was not a
|
|
84
|
+
* smaller fake adapter; for the methods it omitted, it WAS the real
|
|
85
|
+
* filesystem — a genuine production hazard, not only a test-authoring
|
|
86
|
+
* footgun: `user-artifact-staging.cts`'s `stageUserArtifacts` calls
|
|
87
|
+
* `installFs().rmSync(entryDir)` unconditionally as its entryDir-clearing
|
|
88
|
+
* step, and a `deps.fs` that omits `rmSync` (an easy oversight — nothing in
|
|
89
|
+
* the type system catches a plain-JS test object missing a key at runtime)
|
|
90
|
+
* silently deleted the real `<configDir>/.gsd-staging/<key>` on disk instead
|
|
91
|
+
* of failing loudly or staying confined to the fake's own store.
|
|
92
|
+
*
|
|
93
|
+
* `withInstallFs` now builds a GUARDED adapter instead: every method the
|
|
94
|
+
* partial does not define — except `realpathSync`, the one method this
|
|
95
|
+
* module's own contract documents as intentionally degrading to real fs
|
|
96
|
+
* (see its own doc comment below) — throws immediately IF CALLED, naming the
|
|
97
|
+
* missing method, instead of silently delegating to real fs. This only
|
|
98
|
+
* changes behavior for the injected-partial path (`withInstallFs(partial,
|
|
99
|
+
* fn)` with a defined `partial`); the no-adapter-injected default (AC4)
|
|
100
|
+
* still resolves to `REAL_ADAPTER` untouched, and any partial fake that
|
|
101
|
+
* genuinely implements every method its code path touches — the documented,
|
|
102
|
+
* intended usage — is byte-identical to before. A test asserting "no real IO
|
|
103
|
+
* happened" against a partial fake can now rely on this guard directly
|
|
104
|
+
* instead of having to hand-poison real `fs` methods itself (see
|
|
105
|
+
* `tests/executed-plan.test.cjs`'s F2 test, which still poisons real `fs` as
|
|
106
|
+
* defense-in-depth belt-and-suspenders, not because this guard is
|
|
107
|
+
* insufficient on its own).
|
|
108
|
+
*
|
|
109
|
+
* ─────────────────────────────────────────────────────────────────────────
|
|
110
|
+
* SECURITY NOTE (40-design.md rows 6/7, H1-H5)
|
|
111
|
+
* ─────────────────────────────────────────────────────────────────────────
|
|
112
|
+
* This module carries NO policy. `hasExistingSymlinkBetween` and
|
|
113
|
+
* `assertDestWithinConfigHome` keep their REFUSAL DECISIONS outside this
|
|
114
|
+
* seam — only their probe calls (existsSync/lstatSync/realpathSync) are
|
|
115
|
+
* routed through it. Injecting a fake adapter can only change what those
|
|
116
|
+
* probes observe for paths that were never real to begin with; it cannot
|
|
117
|
+
* flip the decision logic itself.
|
|
118
|
+
*
|
|
119
|
+
* ─────────────────────────────────────────────────────────────────────────
|
|
120
|
+
* DELIBERATELY NOT ROUTED (see the call sites themselves for the full
|
|
121
|
+
* reasoning — this is the index)
|
|
122
|
+
* ─────────────────────────────────────────────────────────────────────────
|
|
123
|
+
* `findInstallSourceRoot` / `findAgentsSourceRoot`'s walk-up-from-__dirname
|
|
124
|
+
* step (runtime-artifact-layout.cts) locates THIS PACKAGE'S OWN source tree
|
|
125
|
+
* (`commands/gsd/`, `agents/`) — not the install destination — so it uses
|
|
126
|
+
* `fs.statSync` directly, unrouted. A fake destination adapter's store
|
|
127
|
+
* starts empty and is never seeded with real repo paths; routing this walk
|
|
128
|
+
* through it would make every fake-adapter install throw
|
|
129
|
+
* "could not locate commands/gsd", not gracefully stage nothing. See the
|
|
130
|
+
* comment at each function's Step 2 for the full argument.
|
|
131
|
+
*
|
|
132
|
+
* RULE (40-design.md "Known limits"): this seam makes *destination* IO
|
|
133
|
+
* fake-able; package-source IO (this section) stays real by design — the F2
|
|
134
|
+
* test's poison list should be derived FROM that rule, not the reverse, or a
|
|
135
|
+
* future "complete the poison list" edit that adds `statSync` will break a
|
|
136
|
+
* correct `findInstallSourceRoot` for the wrong reason.
|
|
137
|
+
*/
|
|
138
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
139
|
+
const node_os_1 = __importDefault(require("node:os"));
|
|
140
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
141
|
+
const node_crypto_1 = __importDefault(require("node:crypto"));
|
|
142
|
+
const REAL_ADAPTER = {
|
|
143
|
+
existsSync: (p) => node_fs_1.default.existsSync(p),
|
|
144
|
+
mkdirSync: (p, opts) => node_fs_1.default.mkdirSync(p, opts),
|
|
145
|
+
rmSync: (p, opts) => node_fs_1.default.rmSync(p, opts),
|
|
146
|
+
readdirSync: ((p, opts) => (opts ? node_fs_1.default.readdirSync(p, opts) : node_fs_1.default.readdirSync(p))),
|
|
147
|
+
readFileSync: ((p, encoding) => (encoding ? node_fs_1.default.readFileSync(p, encoding) : node_fs_1.default.readFileSync(p))),
|
|
148
|
+
writeFileSync: (p, data, opts) => node_fs_1.default.writeFileSync(p, data, opts),
|
|
149
|
+
copyFileSync: (src, dest) => node_fs_1.default.copyFileSync(src, dest),
|
|
150
|
+
cpSync: (src, dest, opts) => node_fs_1.default.cpSync(src, dest, opts),
|
|
151
|
+
lstatSync: (p) => node_fs_1.default.lstatSync(p),
|
|
152
|
+
realpathSync: (p) => node_fs_1.default.realpathSync(p),
|
|
153
|
+
unlinkSync: (p) => node_fs_1.default.unlinkSync(p),
|
|
154
|
+
rmdirSync: (p) => node_fs_1.default.rmdirSync(p),
|
|
155
|
+
symlinkSync: (target, p) => node_fs_1.default.symlinkSync(target, p),
|
|
156
|
+
readlinkSync: (p) => node_fs_1.default.readlinkSync(p),
|
|
157
|
+
openSync: (p, flags) => node_fs_1.default.openSync(p, flags),
|
|
158
|
+
readSync: (fd, buffer, offset, length, position) => node_fs_1.default.readSync(fd, buffer, offset, length, position),
|
|
159
|
+
closeSync: (fd) => node_fs_1.default.closeSync(fd),
|
|
160
|
+
};
|
|
161
|
+
let current = REAL_ADAPTER;
|
|
162
|
+
/**
|
|
163
|
+
* Returns the fs adapter active for the currently-running install call —
|
|
164
|
+
* the real adapter when no `deps.fs` was injected, or the injected partial
|
|
165
|
+
* adapter merged over the real one (any method it did not override still
|
|
166
|
+
* resolves to real fs — see the module doc's "PARTIAL-ADAPTER TRAP").
|
|
167
|
+
*/
|
|
168
|
+
function installFs() {
|
|
169
|
+
return current;
|
|
170
|
+
}
|
|
171
|
+
// Methods allowed to silently degrade to real fs when a partial injected
|
|
172
|
+
// adapter omits them — see the module doc's "PARTIAL-ADAPTER TRAP". This is
|
|
173
|
+
// deliberately a single, narrow, documented exception: `realpathSync`'s own
|
|
174
|
+
// interface doc comment already promises graceful real-fs fallback (the
|
|
175
|
+
// symlink guard treats a `realpathSync` failure as "fall back to the lexical
|
|
176
|
+
// form" anyway, so an absent method degrading the same way changes nothing
|
|
177
|
+
// observable). Every other method is REQUIRED by `InstallFsAdapter`'s own
|
|
178
|
+
// type (no `?`) and now enforces that at runtime too.
|
|
179
|
+
const OPTIONAL_ADAPTER_METHODS = new Set(['realpathSync']);
|
|
180
|
+
/**
|
|
181
|
+
* Build a merged adapter for an injected `partial`: every method `partial`
|
|
182
|
+
* defines is used as-is; `realpathSync` (only) falls back to real fs when
|
|
183
|
+
* absent; every other omitted method becomes a stub that THROWS if actually
|
|
184
|
+
* called, naming the missing method — turning a silent real-fs fall-through
|
|
185
|
+
* into an immediate, diagnosable failure (module doc "PARTIAL-ADAPTER TRAP").
|
|
186
|
+
* A method never invoked by the exercised code path never throws, so this is
|
|
187
|
+
* safe for any existing partial fake that only implements what it touches.
|
|
188
|
+
*/
|
|
189
|
+
function buildGuardedAdapter(partial) {
|
|
190
|
+
const guarded = {};
|
|
191
|
+
for (const key of Object.keys(REAL_ADAPTER)) {
|
|
192
|
+
if (Object.prototype.hasOwnProperty.call(partial, key)) {
|
|
193
|
+
guarded[key] = partial[key];
|
|
194
|
+
}
|
|
195
|
+
else if (OPTIONAL_ADAPTER_METHODS.has(key)) {
|
|
196
|
+
guarded[key] = REAL_ADAPTER[key];
|
|
197
|
+
}
|
|
198
|
+
else {
|
|
199
|
+
guarded[key] = (..._args) => {
|
|
200
|
+
throw new Error(`installFs().${key}(...) was called but the injected partial adapter does not implement it. ` +
|
|
201
|
+
`A fake adapter must implement every method its code path touches (install-fs-adapter.cts's ` +
|
|
202
|
+
`PARTIAL-ADAPTER TRAP) — falling through to real fs is no longer allowed for this method.`);
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
return guarded;
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Run `fn` with a GUARDED merge of `partial` over the real adapter as the
|
|
210
|
+
* active install-fs adapter, restoring the previous adapter afterward — even
|
|
211
|
+
* on throw (see the module doc's re-entrancy/synchronous-only assumption for
|
|
212
|
+
* why a bare module-level variable is safe here, and why it would not be
|
|
213
|
+
* under async interleaving or concurrent installs). `partial` undefined is
|
|
214
|
+
* a no-op: `fn` runs against whatever adapter was already active (real fs
|
|
215
|
+
* by default) — this is what keeps every existing `deps`-less call site
|
|
216
|
+
* (AC4) byte-identical. See `buildGuardedAdapter` for what "guarded" means.
|
|
217
|
+
*/
|
|
218
|
+
function withInstallFs(partial, fn) {
|
|
219
|
+
if (!partial)
|
|
220
|
+
return fn();
|
|
221
|
+
const previous = current;
|
|
222
|
+
current = buildGuardedAdapter(partial);
|
|
223
|
+
try {
|
|
224
|
+
return fn();
|
|
225
|
+
}
|
|
226
|
+
finally {
|
|
227
|
+
current = previous;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Create a fresh, uniquely-named temp directory.
|
|
232
|
+
*
|
|
233
|
+
* AC4 requires the no-adapter-injected path to stay byte-identical to the
|
|
234
|
+
* pre-#2874 code, which called real `fs.mkdtempSync` directly — several
|
|
235
|
+
* existing tests (e.g. tests/install-runtime-artifacts.test.cjs's "rmSync is
|
|
236
|
+
* called on the tempDir when readFileSync throws") monkeypatch real
|
|
237
|
+
* `fs.mkdtempSync` to capture the exact directory a call under test creates,
|
|
238
|
+
* and stop working if that real syscall is no longer made. So: when NO fake
|
|
239
|
+
* adapter is active (`current === REAL_ADAPTER`, the exact top-level-install
|
|
240
|
+
* default), this calls `nodeFs.mkdtempSync` directly — the same real call
|
|
241
|
+
* the old code made, still visible to a monkeypatch applied after import
|
|
242
|
+
* because it is a live property lookup on the `node:fs` module object, not a
|
|
243
|
+
* captured reference.
|
|
244
|
+
*
|
|
245
|
+
* When a fake adapter IS active (any `withInstallFs(partial, …)` call, even
|
|
246
|
+
* a partial one — see `current`'s assignment in `withInstallFs`, which
|
|
247
|
+
* always produces a NEW merged object, never `=== REAL_ADAPTER`), this falls
|
|
248
|
+
* back to synthesizing a unique name and creating it via
|
|
249
|
+
* `installFs().mkdirSync`, exactly as before: the injected adapter contract
|
|
250
|
+
* still does not require `mkdtempSync`, so a fake (which only needs to
|
|
251
|
+
* implement `mkdirSync`) can drive the full staging pipeline without ever
|
|
252
|
+
* touching real fs.
|
|
253
|
+
*/
|
|
254
|
+
function mkInstallTempDir(prefix) {
|
|
255
|
+
if (current === REAL_ADAPTER) {
|
|
256
|
+
return node_fs_1.default.mkdtempSync(node_path_1.default.join(node_os_1.default.tmpdir(), prefix));
|
|
257
|
+
}
|
|
258
|
+
const dir = node_path_1.default.join(node_os_1.default.tmpdir(), `${prefix}${node_crypto_1.default.randomBytes(8).toString('hex')}`);
|
|
259
|
+
installFs().mkdirSync(dir, { recursive: true });
|
|
260
|
+
return dir;
|
|
261
|
+
}
|
|
262
|
+
module.exports = { installFs, withInstallFs, mkInstallTempDir };
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
/**
|
|
6
|
+
* install-model-override-resolver — install-time per-agent model-override
|
|
7
|
+
* resolution (#2256 / #2794), extracted from the package-root `bin/install.js`
|
|
8
|
+
* (#2875 Part 2 / J8).
|
|
9
|
+
*
|
|
10
|
+
* bin/install.js's inline agent-staging loop duplicated this EXACT precedence
|
|
11
|
+
* chain across two runtime branches (OpenCode ~24 lines, Kilo ~24 lines) —
|
|
12
|
+
* `model_overrides[agent]` > `model_profile_overrides.<runtime>.<tier>` > omit.
|
|
13
|
+
* Extracted here — a `src/*.cts` module compiled into the shipped
|
|
14
|
+
* `gsd-core/bin/lib/` tree, mirroring `install-effort-resolver.cts`'s existing
|
|
15
|
+
* precedent (#2071) — so the descriptor-driven agents pipeline
|
|
16
|
+
* (`convertedAgentsKind` / `stageAgentsForRuntimeWithConverter`) and
|
|
17
|
+
* bin/install.js's own callers resolve through the SAME code. A single source
|
|
18
|
+
* of truth makes the two paths diverge only if this module changes, not
|
|
19
|
+
* silently across two hand-maintained copies (the Generative Fix Divergence
|
|
20
|
+
* class CLAUDE.md's "Known Defects" section warns about).
|
|
21
|
+
*
|
|
22
|
+
* `readGsdGlobalModelOverrides` / `readGsdEffectiveModelOverrides` /
|
|
23
|
+
* `readGsdRuntimeProfileResolver` are impure (config-file reads);
|
|
24
|
+
* `resolveAgentModelOverride` is pure given their pre-resolved outputs.
|
|
25
|
+
*
|
|
26
|
+
* #2875 defect fix: this module is on the `installRuntimeArtifacts` call tree
|
|
27
|
+
* (reached from `runtime-artifact-layout.cts`'s agents-kind `stage()` for the
|
|
28
|
+
* opencode/kilo converters) — every fs touch below routes through
|
|
29
|
+
* `installFs()` (install-fs-adapter.cts), matching `retired-artifact-cleanup.cts`
|
|
30
|
+
* / `user-artifact-staging.cts`'s existing precedent, instead of calling
|
|
31
|
+
* `node:fs` directly. A raw `fs` call here silently bypassed a fake adapter
|
|
32
|
+
* injected via `withInstallFs`/`installRuntimeArtifacts(..., { fs })`,
|
|
33
|
+
* exactly the class of bug those two modules were fixed for.
|
|
34
|
+
*/
|
|
35
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
36
|
+
const node_os_1 = __importDefault(require("node:os"));
|
|
37
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports -- install-fs-adapter.cjs is an export= CommonJS module
|
|
38
|
+
const installFsAdapter = require("./install-fs-adapter.cjs");
|
|
39
|
+
const { installFs } = installFsAdapter;
|
|
40
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports -- install-effort-resolver.cjs is an export= CommonJS module
|
|
41
|
+
const installEffortResolver = require("./install-effort-resolver.cjs");
|
|
42
|
+
const { _readGsdConfigFile, _findAncestorGsdConfigPath } = installEffortResolver;
|
|
43
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports -- model-catalog.cjs is an export= CommonJS module
|
|
44
|
+
const modelCatalog = require("./model-catalog.cjs");
|
|
45
|
+
const { MODEL_PROFILES: GSD_MODEL_PROFILES } = modelCatalog;
|
|
46
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports -- model-resolver.cjs is an export= CommonJS module
|
|
47
|
+
const modelResolverModule = require("./model-resolver.cjs");
|
|
48
|
+
const { resolveTierEntry: gsdResolveTierEntry } = modelResolverModule;
|
|
49
|
+
/**
|
|
50
|
+
* Read `model_overrides` from `~/.gsd/defaults.json` at install time.
|
|
51
|
+
* Returns an object mapping agent names to model IDs, or null if the file
|
|
52
|
+
* doesn't exist or has no `model_overrides` entry.
|
|
53
|
+
*
|
|
54
|
+
* Mirrors bin/install.js's prior local `readGsdGlobalModelOverrides` exactly
|
|
55
|
+
* (silent try/catch — no stderr warning on malformed JSON, unlike
|
|
56
|
+
* `_readGsdConfigFile`'s effort-config sibling — preserved for byte-parity).
|
|
57
|
+
*/
|
|
58
|
+
function readGsdGlobalModelOverrides(options = {}) {
|
|
59
|
+
try {
|
|
60
|
+
const home = options.homedir ? options.homedir() : node_os_1.default.homedir();
|
|
61
|
+
const defaultsPath = node_path_1.default.join(home, '.gsd', 'defaults.json');
|
|
62
|
+
if (!installFs().existsSync(defaultsPath))
|
|
63
|
+
return null;
|
|
64
|
+
const raw = installFs().readFileSync(defaultsPath, 'utf-8');
|
|
65
|
+
const parsed = JSON.parse(raw);
|
|
66
|
+
const overrides = parsed.model_overrides;
|
|
67
|
+
if (!overrides || typeof overrides !== 'object')
|
|
68
|
+
return null;
|
|
69
|
+
return overrides;
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Effective per-agent `model_overrides` for the install path.
|
|
77
|
+
*
|
|
78
|
+
* Merges `~/.gsd/defaults.json` (global) with per-project
|
|
79
|
+
* `<project>/.planning/config.json`. Per-project keys win on conflict; keys
|
|
80
|
+
* present in only one source are preserved from that source (#2256).
|
|
81
|
+
*
|
|
82
|
+
* `targetDir` is the consuming runtime's install root (walks up to find
|
|
83
|
+
* `.planning/`). When `targetDir` is null/undefined only the global file is
|
|
84
|
+
* consulted.
|
|
85
|
+
*
|
|
86
|
+
* Returns a plain `{ agentName: modelId }` object, or `null` when neither
|
|
87
|
+
* source defines `model_overrides`.
|
|
88
|
+
*/
|
|
89
|
+
function readGsdEffectiveModelOverrides(targetDir = null, options = {}) {
|
|
90
|
+
const global = readGsdGlobalModelOverrides(options);
|
|
91
|
+
let projectOverrides = null;
|
|
92
|
+
if (targetDir) {
|
|
93
|
+
// #2875 defect fix (Generative Fix Divergence): the 8-deep upward walk to
|
|
94
|
+
// `.planning/config.json` is single-sourced in install-effort-resolver.cts
|
|
95
|
+
// (`_findAncestorGsdConfigPath`) — this module was itself extracted FROM
|
|
96
|
+
// that one to stop duplicating shared install-time config logic, so a
|
|
97
|
+
// second hand-rolled copy of the walk here defeated the point.
|
|
98
|
+
const candidate = _findAncestorGsdConfigPath(targetDir);
|
|
99
|
+
if (candidate) {
|
|
100
|
+
try {
|
|
101
|
+
const parsed = JSON.parse(installFs().readFileSync(candidate, 'utf-8'));
|
|
102
|
+
if (parsed && typeof parsed === 'object' && parsed.model_overrides && typeof parsed.model_overrides === 'object') {
|
|
103
|
+
projectOverrides = parsed.model_overrides;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
// Malformed config.json — fall back to global; readGsdRuntimeProfileResolver
|
|
108
|
+
// surfaces a parse warning via _readGsdConfigFile already.
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
if (!global && !projectOverrides)
|
|
113
|
+
return null;
|
|
114
|
+
// Per-project wins on conflict; preserve non-conflicting global keys.
|
|
115
|
+
return { ...(global || {}), ...(projectOverrides || {}) };
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Build a runtime-aware tier resolver for the install path (#2517).
|
|
119
|
+
*
|
|
120
|
+
* Probes BOTH per-project `<targetDir>/.planning/config.json` AND
|
|
121
|
+
* `~/.gsd/defaults.json`, with per-project keys winning over global.
|
|
122
|
+
*
|
|
123
|
+
* Returns null if no `runtime` is configured, if `model_profile` is
|
|
124
|
+
* `inherit`, or if no project config is reachable AND `~/.gsd/defaults.json`
|
|
125
|
+
* declares no `model_profile` (#3543 — an unverifiable profile is never
|
|
126
|
+
* baked, letting the runtime's own default/session model govern).
|
|
127
|
+
*/
|
|
128
|
+
function readGsdRuntimeProfileResolver(targetDir = null) {
|
|
129
|
+
const homeDefaults = _readGsdConfigFile(node_path_1.default.join(node_os_1.default.homedir(), '.gsd', 'defaults.json'), '~/.gsd/defaults.json');
|
|
130
|
+
// #2875 defect fix (Generative Fix Divergence): same shared walk as
|
|
131
|
+
// readGsdEffectiveModelOverrides above — see that call site's comment.
|
|
132
|
+
let projectConfig = null;
|
|
133
|
+
if (targetDir) {
|
|
134
|
+
const candidate = _findAncestorGsdConfigPath(targetDir);
|
|
135
|
+
if (candidate)
|
|
136
|
+
projectConfig = _readGsdConfigFile(candidate, '.planning/config.json');
|
|
137
|
+
}
|
|
138
|
+
const merged = {
|
|
139
|
+
runtime: (projectConfig && projectConfig.runtime) ||
|
|
140
|
+
(homeDefaults && homeDefaults.runtime) ||
|
|
141
|
+
null,
|
|
142
|
+
model_profile: (projectConfig && projectConfig.model_profile) ||
|
|
143
|
+
(homeDefaults && homeDefaults.model_profile) ||
|
|
144
|
+
'balanced',
|
|
145
|
+
model_profile_overrides: (projectConfig && projectConfig.model_profile_overrides) ||
|
|
146
|
+
(homeDefaults && homeDefaults.model_profile_overrides) ||
|
|
147
|
+
null,
|
|
148
|
+
};
|
|
149
|
+
if (!merged.runtime)
|
|
150
|
+
return null;
|
|
151
|
+
if (!projectConfig && !(homeDefaults && homeDefaults.model_profile)) {
|
|
152
|
+
return null;
|
|
153
|
+
}
|
|
154
|
+
const profile = String(merged.model_profile).toLowerCase();
|
|
155
|
+
if (profile === 'inherit')
|
|
156
|
+
return null;
|
|
157
|
+
const runtime = merged.runtime;
|
|
158
|
+
return {
|
|
159
|
+
runtime,
|
|
160
|
+
resolve(agentName) {
|
|
161
|
+
const agentModels = GSD_MODEL_PROFILES[agentName];
|
|
162
|
+
if (!agentModels)
|
|
163
|
+
return null;
|
|
164
|
+
const tier = agentModels[profile] || agentModels.balanced;
|
|
165
|
+
if (!tier)
|
|
166
|
+
return null;
|
|
167
|
+
return gsdResolveTierEntry({
|
|
168
|
+
runtime,
|
|
169
|
+
tier,
|
|
170
|
+
overrides: merged.model_profile_overrides,
|
|
171
|
+
});
|
|
172
|
+
},
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Resolve the effective model override for a single agent, given a
|
|
177
|
+
* pre-resolved `modelOverrides` map and `runtimeResolver` (both from the
|
|
178
|
+
* functions above). Pure — no filesystem access.
|
|
179
|
+
*
|
|
180
|
+
* Precedence (J8 — identical for kilo and opencode, resolved through this ONE
|
|
181
|
+
* shared function so the two runtimes can never diverge):
|
|
182
|
+
* 1. modelOverrides[agentName] (#2256 — explicit per-agent override)
|
|
183
|
+
* 2. runtimeResolver.resolve(agentName)?.model
|
|
184
|
+
* (#2794 — tier-based model_profile_overrides.<runtime>.<tier>)
|
|
185
|
+
* 3. null (omit — J7: the frontmatter key must not appear, not `null`/`""`)
|
|
186
|
+
*/
|
|
187
|
+
function resolveAgentModelOverride(agentName, modelOverrides, runtimeResolver) {
|
|
188
|
+
const explicit = modelOverrides ? modelOverrides[agentName] : undefined;
|
|
189
|
+
if (explicit)
|
|
190
|
+
return explicit;
|
|
191
|
+
if (runtimeResolver) {
|
|
192
|
+
const entry = runtimeResolver.resolve(agentName);
|
|
193
|
+
if (entry && entry.model)
|
|
194
|
+
return entry.model;
|
|
195
|
+
}
|
|
196
|
+
return null;
|
|
197
|
+
}
|
|
198
|
+
module.exports = {
|
|
199
|
+
readGsdGlobalModelOverrides,
|
|
200
|
+
readGsdEffectiveModelOverrides,
|
|
201
|
+
readGsdRuntimeProfileResolver,
|
|
202
|
+
resolveAgentModelOverride,
|
|
203
|
+
};
|