@opengsd/gsd-core 1.16.0 → 1.18.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 -2
- package/.opencode/plugins/gsd-core.js +251 -1
- package/.opencode/plugins/gsd-statusline/tui.tsx +55 -0
- package/GEMINI.md +1 -1
- package/agents/gsd-advisor-researcher.compact.md +1 -1
- package/agents/gsd-advisor-researcher.md +1 -1
- package/agents/gsd-assumptions-analyzer.compact.md +1 -1
- package/agents/gsd-assumptions-analyzer.md +1 -1
- package/agents/gsd-code-fixer.compact.md +1 -1
- package/agents/gsd-code-fixer.md +1 -1
- package/agents/gsd-code-reviewer.compact.md +1 -1
- package/agents/gsd-code-reviewer.md +1 -1
- package/agents/gsd-codebase-mapper.compact.md +1 -1
- package/agents/gsd-codebase-mapper.md +1 -1
- package/agents/gsd-debug-session-manager.compact.md +6 -0
- package/agents/gsd-debug-session-manager.md +6 -0
- package/agents/gsd-debugger.md +1 -1
- package/agents/gsd-doc-verifier.compact.md +4 -4
- package/agents/gsd-doc-verifier.md +4 -3
- package/agents/gsd-doc-writer.compact.md +23 -5
- package/agents/gsd-doc-writer.md +19 -6
- package/agents/gsd-eval-auditor.compact.md +1 -1
- package/agents/gsd-eval-auditor.md +1 -1
- package/agents/gsd-executor.md +7 -3
- package/agents/gsd-integration-checker.compact.md +1 -1
- package/agents/gsd-integration-checker.md +1 -1
- package/agents/gsd-nyquist-auditor.compact.md +1 -1
- package/agents/gsd-nyquist-auditor.md +1 -1
- package/agents/gsd-phase-researcher.md +1 -1
- package/agents/gsd-plan-checker.md +2 -1
- package/agents/gsd-planner.md +5 -4
- package/agents/gsd-project-researcher.compact.md +1 -1
- package/agents/gsd-project-researcher.md +1 -1
- package/agents/gsd-research-synthesizer.compact.md +5 -5
- package/agents/gsd-research-synthesizer.md +5 -5
- package/agents/gsd-roadmapper.compact.md +1 -1
- package/agents/gsd-roadmapper.md +1 -1
- package/agents/gsd-security-auditor.compact.md +1 -1
- package/agents/gsd-security-auditor.md +1 -1
- package/agents/gsd-ui-auditor.compact.md +1 -1
- package/agents/gsd-ui-auditor.md +1 -1
- package/agents/gsd-ui-checker.compact.md +1 -1
- package/agents/gsd-ui-checker.md +1 -1
- package/agents/gsd-ui-researcher.compact.md +13 -1
- package/agents/gsd-ui-researcher.md +28 -1
- package/agents/gsd-verifier.md +1 -1
- package/bin/gsd-mcp-server.js +3 -1
- package/bin/install.js +398 -95
- package/commands/gsd/code-review.md +8 -0
- package/commands/gsd/pause-work.md +1 -1
- package/gsd-core/bin/gsd-tools.cjs +34 -12
- package/gsd-core/bin/lib/adr-parser.cjs +37 -5
- package/gsd-core/bin/lib/capability-ledger.cjs +25 -0
- package/gsd-core/bin/lib/capability-lock.cjs +29 -7
- package/gsd-core/bin/lib/capability-registry.cjs +138 -80
- package/gsd-core/bin/lib/capability-source.cjs +3 -1
- package/gsd-core/bin/lib/capability-state.cjs +6 -3
- package/gsd-core/bin/lib/capability-validator.cjs +8 -5
- package/gsd-core/bin/lib/capability-writer.cjs +2 -1
- package/gsd-core/bin/lib/commands.cjs +33 -9
- package/gsd-core/bin/lib/config-loader.cjs +3 -3
- package/gsd-core/bin/lib/decision-coverage-support.cjs +19 -0
- package/gsd-core/bin/lib/gate-context-drift.cjs +5 -4
- package/gsd-core/bin/lib/gate-decision-coverage-plan.cjs +15 -0
- package/gsd-core/bin/lib/gate-decision-coverage-verify.cjs +13 -0
- package/gsd-core/bin/lib/gate-predicate-evaluator.cjs +23 -3
- package/gsd-core/bin/lib/gate-predicate.cjs +8 -12
- package/gsd-core/bin/lib/gate-tdd-review-checkpoint.cjs +60 -5
- package/gsd-core/bin/lib/gate-ui-safety.cjs +2 -1
- package/gsd-core/bin/lib/graphify.cjs +3 -2
- package/gsd-core/bin/lib/init.cjs +43 -16
- package/gsd-core/bin/lib/install-effort-resolver.cjs +2 -2
- package/gsd-core/bin/lib/install-engine.cjs +11 -0
- package/gsd-core/bin/lib/install-model-override-resolver.cjs +4 -3
- package/gsd-core/bin/lib/install-profiles.cjs +5 -3
- package/gsd-core/bin/lib/install-shadow-report.cjs +12 -0
- package/gsd-core/bin/lib/installer-migrations/011-opencode-mcp-no-prompts.cjs +99 -0
- package/gsd-core/bin/lib/installer-migrations.cjs +54 -1
- package/gsd-core/bin/lib/jsonc-edit.cjs +418 -0
- package/gsd-core/bin/lib/loop-host-contract.cjs +2 -1
- package/gsd-core/bin/lib/mcp-server.cjs +5 -1
- package/gsd-core/bin/lib/opencode-mcp-entry.cjs +39 -0
- package/gsd-core/bin/lib/opencode-version.cjs +486 -0
- package/gsd-core/bin/lib/phase.cjs +141 -11
- package/gsd-core/bin/lib/quick-batch-dispatch.cjs +2 -1
- package/gsd-core/bin/lib/report-parser.cjs +57 -1
- package/gsd-core/bin/lib/review-lane-invocation.cjs +40 -0
- package/gsd-core/bin/lib/review-reviewer-selection.cjs +20 -1
- package/gsd-core/bin/lib/roadmap.cjs +56 -5
- package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +142 -6
- package/gsd-core/bin/lib/runtime-homes.cjs +18 -0
- package/gsd-core/bin/lib/runtime-hooks-surface.cjs +58 -3
- package/gsd-core/bin/lib/runtime-slash.cjs +13 -0
- package/gsd-core/bin/lib/shell-command-projection.cjs +2 -0
- package/gsd-core/bin/lib/smart-entry.cjs +100 -53
- package/gsd-core/bin/lib/state-document.cjs +37 -3
- package/gsd-core/bin/lib/state-transition.cjs +98 -19
- package/gsd-core/bin/lib/state.cjs +544 -126
- package/gsd-core/bin/lib/task-command-router.cjs +2 -3
- package/gsd-core/bin/lib/tdd-red-evidence.cjs +5 -0
- package/gsd-core/bin/lib/tdd-test-files.cjs +42 -0
- package/gsd-core/bin/lib/update-context.cjs +18 -1
- package/gsd-core/bin/lib/verification.cjs +107 -17
- package/gsd-core/bin/lib/verify.cjs +36 -1
- package/gsd-core/references/agent-contracts.md +1 -1
- package/gsd-core/references/agent-skills-bootstrap.md +26 -7
- package/gsd-core/references/git-integration.md +3 -3
- package/gsd-core/references/planner-reversibility.md +18 -4
- package/gsd-core/references/planner-verify-command-grounding.md +18 -0
- package/gsd-core/references/tdd.md +4 -3
- package/gsd-core/references/verifier-phase-gates.md +8 -1
- package/gsd-core/workflows/add-backlog.md +10 -2
- package/gsd-core/workflows/add-phase.md +5 -1
- package/gsd-core/workflows/add-tests.md +5 -1
- package/gsd-core/workflows/add-todo.md +5 -1
- package/gsd-core/workflows/ai-integration-phase.md +5 -1
- package/gsd-core/workflows/audit-milestone.md +21 -9
- package/gsd-core/workflows/autonomous/templates/minimal-context.md +41 -0
- package/gsd-core/workflows/autonomous.md +52 -64
- package/gsd-core/workflows/check-todos.md +5 -1
- package/gsd-core/workflows/code-review-fix.md +5 -1
- package/gsd-core/workflows/code-review.md +21 -2
- package/gsd-core/workflows/complete-milestone/detail/elaboration.md +5 -1
- package/gsd-core/workflows/complete-milestone.md +25 -5
- package/gsd-core/workflows/debug.md +5 -1
- package/gsd-core/workflows/diagnose-issues.md +5 -1
- package/gsd-core/workflows/discuss-phase/modes/chain.md +5 -1
- package/gsd-core/workflows/discuss-phase-assumptions.md +11 -3
- package/gsd-core/workflows/discuss-phase.md +5 -1
- package/gsd-core/workflows/docs-update/detail/elaboration.md +7 -6
- package/gsd-core/workflows/docs-update.md +27 -5
- package/gsd-core/workflows/edit-phase.md +5 -1
- package/gsd-core/workflows/eval-review.md +11 -3
- package/gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md +5 -1
- package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/post-merge-gate.md +12 -6
- package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +19 -2
- package/gsd-core/workflows/execute-phase/steps/regression-gate.md +18 -1
- package/gsd-core/workflows/execute-phase/steps/verify-phase-goal.md +5 -1
- package/gsd-core/workflows/execute-phase.md +40 -7
- package/gsd-core/workflows/execute-plan.md +8 -4
- package/gsd-core/workflows/extract-learnings.md +6 -2
- package/gsd-core/workflows/forensics.md +5 -1
- package/gsd-core/workflows/import.md +5 -1
- package/gsd-core/workflows/insert-phase.md +5 -1
- package/gsd-core/workflows/manager.md +10 -2
- package/gsd-core/workflows/map-codebase.md +5 -1
- package/gsd-core/workflows/milestone-summary.md +10 -2
- package/gsd-core/workflows/new-milestone.md +13 -5
- package/gsd-core/workflows/new-project.md +9 -5
- package/gsd-core/workflows/next.md +8 -4
- package/gsd-core/workflows/pause-work.md +3 -3
- package/gsd-core/workflows/plan-phase/steps/auto-advance-check.md +70 -0
- package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +5 -1
- package/gsd-core/workflows/plan-phase/steps/closed-phase-gate.md +6 -2
- package/gsd-core/workflows/plan-phase.md +41 -73
- package/gsd-core/workflows/plan-review-convergence.md +17 -5
- package/gsd-core/workflows/plant-seed.md +8 -4
- package/gsd-core/workflows/progress.md +6 -2
- package/gsd-core/workflows/quick-batch.md +5 -1
- package/gsd-core/workflows/quick.md +22 -7
- package/gsd-core/workflows/remove-phase.md +5 -1
- package/gsd-core/workflows/resume-project.md +19 -11
- package/gsd-core/workflows/review.md +6 -2
- package/gsd-core/workflows/scan.md +5 -1
- package/gsd-core/workflows/secure-phase.md +6 -2
- package/gsd-core/workflows/settings.md +3 -3
- package/gsd-core/workflows/ship.md +13 -10
- package/gsd-core/workflows/thread.md +10 -2
- package/gsd-core/workflows/transition.md +5 -1
- package/gsd-core/workflows/ui-phase.md +5 -1
- package/gsd-core/workflows/ui-review.md +6 -2
- package/gsd-core/workflows/ultraplan-phase.md +5 -1
- package/gsd-core/workflows/update.md +5 -1
- package/gsd-core/workflows/validate-phase.md +6 -2
- package/gsd-core/workflows/verify-work/detail/elaboration.md +1 -1
- package/gsd-core/workflows/verify-work/steps/mvp-uat-framing.md +5 -1
- package/gsd-core/workflows/verify-work.md +11 -3
- package/hooks/dist/gsd-agent-isolation-guard.js +3 -1
- package/hooks/dist/gsd-check-update.js +26 -20
- package/hooks/dist/gsd-context-monitor.js +14 -5
- package/hooks/dist/gsd-cursor-subagent-start.js +2 -1
- package/hooks/dist/gsd-secret-read-guard.js +189 -15
- package/hooks/dist/gsd-statusline.js +321 -22
- package/hooks/dist/gsd-validate-commit.sh +27 -5
- package/hooks/gsd-agent-isolation-guard.js +3 -1
- package/hooks/gsd-check-update.js +26 -20
- package/hooks/gsd-context-monitor.js +14 -5
- package/hooks/gsd-cursor-subagent-start.js +2 -1
- package/hooks/gsd-secret-read-guard.js +189 -15
- package/hooks/gsd-statusline.js +321 -22
- package/hooks/gsd-validate-commit.sh +27 -5
- package/package.json +1 -1
- package/scripts/audit-workflow-script-paths.cjs +1 -1
- package/scripts/diagnose-validate-commit-in-tier.cjs +270 -0
- package/scripts/diagnose-validate-commit-phases.cjs +348 -0
- package/scripts/diagnose-windows-spawn-watcher.cjs +317 -0
- package/scripts/docs-guard-registry.cjs +2 -0
- package/scripts/gen-capability-registry.cjs +7 -1
- package/scripts/gen-loop-host-contract.cjs +4 -1
- package/scripts/lib/macos-conformance-tier.generated.cjs +13 -0
- package/scripts/lib/platform-conformance-tier.generated.cjs +26 -0
- package/scripts/lint-phase-arg-assignment.cjs +2 -2
- package/scripts/lint-workflow-shellcheck-baseline.json +16 -16
- package/scripts/mutation-matrix.cjs +3 -1
- package/scripts/release-tarball-smoke.cjs +15 -7
- package/scripts/xtrace-phase-timing.cjs +205 -0
- package/skills/gsd-code-review/SKILL.md +8 -0
- package/skills/gsd-pause-work/SKILL.md +1 -1
- package/vscode/package.json +1 -1
|
@@ -20,6 +20,8 @@ const markdown_sectionizer_cjs_1 = require("./markdown-sectionizer.cjs");
|
|
|
20
20
|
const io = require("./io.cjs");
|
|
21
21
|
// eslint-disable-next-line @typescript-eslint/no-require-imports -- config-loader.cjs is an export= CommonJS module
|
|
22
22
|
const configLoader = require("./config-loader.cjs");
|
|
23
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports -- capability-ledger.cjs is an export= CommonJS module
|
|
24
|
+
const capabilityLedger = require("./capability-ledger.cjs");
|
|
23
25
|
const project_root_cjs_1 = require("./project-root.cjs");
|
|
24
26
|
// eslint-disable-next-line @typescript-eslint/no-require-imports -- model-resolver.cjs is an export= CommonJS module
|
|
25
27
|
const modelResolver = require("./model-resolver.cjs");
|
|
@@ -157,6 +159,24 @@ function slugifyPhaseName(phaseName) {
|
|
|
157
159
|
// the same reported phase_slug.
|
|
158
160
|
return phaseName ? coreUtils.generateSlugInternal(phaseName, null) : null;
|
|
159
161
|
}
|
|
162
|
+
/**
|
|
163
|
+
* #4854: the one place that decides which name an `init` verb emits as
|
|
164
|
+
* `phase_name`. When a phase directory exists, `searchPhaseInDir` derives
|
|
165
|
+
* `phaseInfo.phase_name` from the directory-name remainder — already slugified
|
|
166
|
+
* (`phase.add` writes `${num}-${slug}` dirs), so it comes out byte-identical to
|
|
167
|
+
* `phase_slug`. The ROADMAP carries the curated display name
|
|
168
|
+
* (`### Phase N: <Name>`), so it wins; the disk-derived name is the fallback
|
|
169
|
+
* for a phase the ROADMAP does not describe, then `null`. #3171 fixed this
|
|
170
|
+
* inline for `init execute-phase` only; plan-phase (whose `phase_name` reaches
|
|
171
|
+
* STATE.md through `state planned-phase --name`), verify-work, code-review,
|
|
172
|
+
* discuss-phase-assumptions and phase-op read the raw disk value. `phase_slug`
|
|
173
|
+
* is deliberately not routed through here — it stays disk-derived.
|
|
174
|
+
*/
|
|
175
|
+
function resolvePhaseDisplayName(phaseInfo, roadmapPhase) {
|
|
176
|
+
return roadmapPhase?.['phase_name']
|
|
177
|
+
|| phaseInfo?.['phase_name']
|
|
178
|
+
|| null;
|
|
179
|
+
}
|
|
160
180
|
/**
|
|
161
181
|
* #2994 (review finding, DEFECT.GENERATIVE-FIX): shared archived/not-found
|
|
162
182
|
* fallback applied identically by `cmdInitExecutePhase`, `cmdInitPlanPhase`,
|
|
@@ -164,10 +184,11 @@ function slugifyPhaseName(phaseName) {
|
|
|
164
184
|
* `cmdInitDiscussPhaseAssumptions` — 6 call sites previously reproducing the
|
|
165
185
|
* exact same two-branch control flow verbatim (only the synthetic
|
|
166
186
|
* replacement object's field set differs per caller, supplied here via
|
|
167
|
-
* `buildFallback`). `cmdInitPhaseOp`
|
|
168
|
-
*
|
|
169
|
-
*
|
|
170
|
-
*
|
|
187
|
+
* `buildFallback`). `cmdInitPhaseOp` does not use this helper (its own fallback
|
|
188
|
+
* object differs by one field, `has_reviews` absent, so it is not a
|
|
189
|
+
* byte-identical copy), but since #4854 it resolves `roadmapPhase` once up
|
|
190
|
+
* front like the others, so `resolvePhaseDisplayName` sees it in the ordinary
|
|
191
|
+
* case too.
|
|
171
192
|
*
|
|
172
193
|
* Behavior-preserving by construction: every original call site either (a)
|
|
173
194
|
* unconditionally computed `roadmapPhase` once up front and then applied
|
|
@@ -924,7 +945,7 @@ function cmdInitExecutePhase(cwd, phase, raw, options = {}) {
|
|
|
924
945
|
// the human-curated display name (`### Phase N: <Name>`); prefer it,
|
|
925
946
|
// matching the no-disk fallback above. phase_slug stays disk-derived — it
|
|
926
947
|
// correctly feeds branch-name construction below and is unchanged here.
|
|
927
|
-
phase_name: (roadmapPhase
|
|
948
|
+
phase_name: resolvePhaseDisplayName(phaseInfo, roadmapPhase),
|
|
928
949
|
phase_slug: phaseInfo?.['phase_slug'] || null,
|
|
929
950
|
phase_req_ids,
|
|
930
951
|
plans: phaseInfo?.['plans'] || [],
|
|
@@ -1043,7 +1064,7 @@ function cmdInitPlanPhase(cwd, phase, raw, options = {}) {
|
|
|
1043
1064
|
const phase_req_ids = reqExtracted && reqExtracted !== 'TBD' ? reqExtracted : null;
|
|
1044
1065
|
const phaseDirPlan = phaseInfo?.['directory'] || null;
|
|
1045
1066
|
const phaseNumberPlan = phaseInfo?.['phase_number'] || null;
|
|
1046
|
-
const phaseNamePlan = phaseInfo
|
|
1067
|
+
const phaseNamePlan = resolvePhaseDisplayName(phaseInfo, roadmapPhase);
|
|
1047
1068
|
const rawProjectCodePlan = config.project_code || '';
|
|
1048
1069
|
let expectedPhaseDirPlan = null;
|
|
1049
1070
|
if (!phaseDirPlan && phaseNumberPlan && phaseNamePlan) {
|
|
@@ -1689,7 +1710,7 @@ function cmdInitVerifyWork(cwd, phase, raw) {
|
|
|
1689
1710
|
// itself stays relative — evaluateUatPassed above still joins it against cwd.
|
|
1690
1711
|
phase_dir: phaseDir ? toPosixPath(node_path_1.default.join(cwd, phaseDir)) : null,
|
|
1691
1712
|
phase_number: phaseInfo?.['phase_number'] || null,
|
|
1692
|
-
phase_name: phaseInfo
|
|
1713
|
+
phase_name: resolvePhaseDisplayName(phaseInfo, roadmapPhase),
|
|
1693
1714
|
// #2376: verify-work.md's plan_gap_closure step reads these instead of
|
|
1694
1715
|
// hardcoding '.planning/STATE.md' / '.planning/ROADMAP.md' literals.
|
|
1695
1716
|
state_path: toPosixPath(node_path_1.default.join(planningDir(cwd), 'STATE.md')),
|
|
@@ -1761,7 +1782,7 @@ function cmdInitCodeReview(cwd, phase, raw, options = {}) {
|
|
|
1761
1782
|
});
|
|
1762
1783
|
const phaseDir = phaseInfo?.['directory'] || null;
|
|
1763
1784
|
const phaseNumber = phaseInfo?.['phase_number'] || null;
|
|
1764
|
-
const phaseName = phaseInfo
|
|
1785
|
+
const phaseName = resolvePhaseDisplayName(phaseInfo, roadmapPhase);
|
|
1765
1786
|
const fallow = detectFallowConfig(cwd);
|
|
1766
1787
|
const result = {
|
|
1767
1788
|
commit_docs: config.commit_docs,
|
|
@@ -1890,7 +1911,7 @@ function cmdInitDiscussPhaseAssumptions(cwd, phase, raw, options = {}) {
|
|
|
1890
1911
|
});
|
|
1891
1912
|
const phaseDir = phaseInfo?.['directory'] || null;
|
|
1892
1913
|
const phaseNumber = phaseInfo?.['phase_number'] || null;
|
|
1893
|
-
const phaseName = phaseInfo
|
|
1914
|
+
const phaseName = resolvePhaseDisplayName(phaseInfo, roadmapPhase);
|
|
1894
1915
|
// #2994: mirrors discuss-phase-assumptions.md's own auto_advance step
|
|
1895
1916
|
// resolver — `--auto` flag OR the consolidated `check auto-mode --pick
|
|
1896
1917
|
// active` fact (workflow._auto_chain_active OR workflow.auto_advance).
|
|
@@ -1942,8 +1963,10 @@ function cmdInitPhaseOp(cwd, phase, raw) {
|
|
|
1942
1963
|
output(withProjectRoot(cwd, result), raw);
|
|
1943
1964
|
return;
|
|
1944
1965
|
}
|
|
1966
|
+
// #4854: resolved once, up front (a pure read — guardedGetRoadmapPhase has no side effects),
|
|
1967
|
+
// so the ordinary case (phase dir on disk, not archived) also sees the ROADMAP display name.
|
|
1968
|
+
const roadmapPhase = guardedGetRoadmapPhase(cwd, phase, config.project_code);
|
|
1945
1969
|
if (phaseInfo?.['archived']) {
|
|
1946
|
-
const roadmapPhase = guardedGetRoadmapPhase(cwd, phase, config.project_code);
|
|
1947
1970
|
if (roadmapPhase?.['found']) {
|
|
1948
1971
|
const phaseName = roadmapPhase['phase_name'];
|
|
1949
1972
|
phaseInfo = {
|
|
@@ -1966,7 +1989,6 @@ function cmdInitPhaseOp(cwd, phase, raw) {
|
|
|
1966
1989
|
}
|
|
1967
1990
|
}
|
|
1968
1991
|
if (!phaseInfo) {
|
|
1969
|
-
const roadmapPhase = guardedGetRoadmapPhase(cwd, phase, config.project_code);
|
|
1970
1992
|
if (roadmapPhase?.['found']) {
|
|
1971
1993
|
const phaseName = roadmapPhase['phase_name'];
|
|
1972
1994
|
phaseInfo = {
|
|
@@ -1990,7 +2012,7 @@ function cmdInitPhaseOp(cwd, phase, raw) {
|
|
|
1990
2012
|
}
|
|
1991
2013
|
const phaseDir = phaseInfo?.['directory'] || null;
|
|
1992
2014
|
const phaseNumber = phaseInfo?.['phase_number'] || null;
|
|
1993
|
-
const phaseName = phaseInfo
|
|
2015
|
+
const phaseName = resolvePhaseDisplayName(phaseInfo, roadmapPhase);
|
|
1994
2016
|
const rawProjectCode = config.project_code || '';
|
|
1995
2017
|
let expectedPhaseDir = null;
|
|
1996
2018
|
if (!phaseDir && phaseNumber && phaseName) {
|
|
@@ -3753,7 +3775,7 @@ function buildAgentSkillsBlock(config, agentType, projectRoot, diagnostics) {
|
|
|
3753
3775
|
}).join('\n');
|
|
3754
3776
|
return `<agent_skills>\nRead these user-configured skills:\n${lines}\n</agent_skills>`;
|
|
3755
3777
|
}
|
|
3756
|
-
function cmdAgentSkills(cwd, agentType, raw, jsonMode) {
|
|
3778
|
+
function cmdAgentSkills(cwd, agentType, raw, jsonMode, skillsOnly = false) {
|
|
3757
3779
|
if (!agentType) {
|
|
3758
3780
|
output('', raw, '');
|
|
3759
3781
|
return;
|
|
@@ -3784,8 +3806,13 @@ function cmdAgentSkills(cwd, agentType, raw, jsonMode) {
|
|
|
3784
3806
|
// (a real exit code) instead of a prose config-get gate. Compact is tried
|
|
3785
3807
|
// first when requested; a missing compact sibling falls back to canonical
|
|
3786
3808
|
// with the fallback disclosed in the payload itself, never a silent switch.
|
|
3809
|
+
//
|
|
3810
|
+
// #5122: `skillsOnly` (the CLI's `--skills-only`) is the agent's own
|
|
3811
|
+
// self-load. It wants configured skills and nothing else, so it skips the
|
|
3812
|
+
// fallback on every runtime — otherwise a non-Claude agent reads its own
|
|
3813
|
+
// persona back into itself as if it were a skills block.
|
|
3787
3814
|
let agentPayloadVariant = null;
|
|
3788
|
-
if (!block) {
|
|
3815
|
+
if (!block && !skillsOnly) {
|
|
3789
3816
|
const runtime = (config && config['runtime']) || process.env['GSD_RUNTIME'] || 'claude';
|
|
3790
3817
|
if (runtime !== 'claude') {
|
|
3791
3818
|
const agentCheck = checkAgentsInstalled(runtime, projectRoot);
|
|
@@ -4051,7 +4078,7 @@ function buildSkillManifest(cwd, skillsDir = null) {
|
|
|
4051
4078
|
if (!entry.isDirectory())
|
|
4052
4079
|
continue;
|
|
4053
4080
|
const skillMdPath = node_path_1.default.join(rootPath, entry.name, 'SKILL.md');
|
|
4054
|
-
const content =
|
|
4081
|
+
const content = capabilityLedger.readSkillFile(skillMdPath);
|
|
4055
4082
|
if (content !== null) {
|
|
4056
4083
|
if (pushSkillEntry(`${entry.name}/SKILL.md`, content, skillMdPath))
|
|
4057
4084
|
skillCount++;
|
|
@@ -4076,7 +4103,7 @@ function buildSkillManifest(cwd, skillsDir = null) {
|
|
|
4076
4103
|
if (!nested.isDirectory())
|
|
4077
4104
|
continue;
|
|
4078
4105
|
const nestedSkillMd = node_path_1.default.join(nestedSkillsDir, nested.name, 'SKILL.md');
|
|
4079
|
-
const nestedContent =
|
|
4106
|
+
const nestedContent = capabilityLedger.readSkillFile(nestedSkillMd);
|
|
4080
4107
|
if (nestedContent === null)
|
|
4081
4108
|
continue;
|
|
4082
4109
|
// Use forward-slash separator explicitly so manifest paths are posix-style
|
|
@@ -35,7 +35,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
35
35
|
*/
|
|
36
36
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
37
37
|
const node_path_1 = __importDefault(require("node:path"));
|
|
38
|
-
const
|
|
38
|
+
const runtime_homes_cjs_1 = require("./runtime-homes.cjs");
|
|
39
39
|
// eslint-disable-next-line @typescript-eslint/no-require-imports -- install-fs-adapter.cjs is an export= CommonJS module
|
|
40
40
|
const installFsAdapter = require("./install-fs-adapter.cjs");
|
|
41
41
|
const { installFs } = installFsAdapter;
|
|
@@ -154,7 +154,7 @@ function _findAncestorGsdConfigPath(targetDir) {
|
|
|
154
154
|
* @param targetDir Runtime install root (walks up to find .planning/).
|
|
155
155
|
*/
|
|
156
156
|
function readGsdEffectiveEffortConfig(targetDir = null) {
|
|
157
|
-
const homeDefaults = _readGsdConfigFile(node_path_1.default.join(
|
|
157
|
+
const homeDefaults = _readGsdConfigFile(node_path_1.default.join((0, runtime_homes_cjs_1.resolveGsdHome)(), '.gsd', 'defaults.json'), '~/.gsd/defaults.json');
|
|
158
158
|
let projectConfig = null;
|
|
159
159
|
if (targetDir) {
|
|
160
160
|
const candidate = _findAncestorGsdConfigPath(targetDir);
|
|
@@ -1741,6 +1741,17 @@ function _installNativePluginIfDeclared(runtime, configDir, behaviors, src) {
|
|
|
1741
1741
|
}
|
|
1742
1742
|
}
|
|
1743
1743
|
}
|
|
1744
|
+
// OpenCode 2.x terminal-client plugin (ADR-5331 Phase 3): a TUI entrypoint kept in its own directory under
|
|
1745
|
+
// plugins/. OpenCode 1.x scans plugins/*.js|ts only, so it never loads this directory.
|
|
1746
|
+
const cli = behaviors.nativeCliPlugin;
|
|
1747
|
+
if (cli && cli.source) {
|
|
1748
|
+
const cliSrc = node_path_1.default.join(src, cli.source);
|
|
1749
|
+
if (installFs().existsSync(cliSrc)) {
|
|
1750
|
+
const cliDest = runtimeArtifactInstallPlan.assertDestWithinConfigHome(configDir, node_path_1.default.join(cli.dir, cli.file));
|
|
1751
|
+
installFs().mkdirSync(node_path_1.default.dirname(cliDest), { recursive: true });
|
|
1752
|
+
installFs().copyFileSync(cliSrc, cliDest);
|
|
1753
|
+
}
|
|
1754
|
+
}
|
|
1744
1755
|
}
|
|
1745
1756
|
// ---------------------------------------------------------------------------
|
|
1746
1757
|
// _migrateLegacyOpencodeCommandDir
|
|
@@ -34,6 +34,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
34
34
|
*/
|
|
35
35
|
const node_path_1 = __importDefault(require("node:path"));
|
|
36
36
|
const node_os_1 = __importDefault(require("node:os"));
|
|
37
|
+
const runtime_homes_cjs_1 = require("./runtime-homes.cjs");
|
|
37
38
|
// eslint-disable-next-line @typescript-eslint/no-require-imports -- install-fs-adapter.cjs is an export= CommonJS module
|
|
38
39
|
const installFsAdapter = require("./install-fs-adapter.cjs");
|
|
39
40
|
const { installFs } = installFsAdapter;
|
|
@@ -57,7 +58,7 @@ const { resolveTierEntry: gsdResolveTierEntry, resolveModelPolicy: gsdResolveMod
|
|
|
57
58
|
*/
|
|
58
59
|
function readGsdGlobalModelOverrides(options = {}) {
|
|
59
60
|
try {
|
|
60
|
-
const home = options.homedir ? options.homedir() : node_os_1.default.homedir();
|
|
61
|
+
const home = (0, runtime_homes_cjs_1.resolveGsdHome)(options.homedir ? options.homedir() : node_os_1.default.homedir());
|
|
61
62
|
const defaultsPath = node_path_1.default.join(home, '.gsd', 'defaults.json');
|
|
62
63
|
if (!installFs().existsSync(defaultsPath))
|
|
63
64
|
return null;
|
|
@@ -139,7 +140,7 @@ function readGsdAgentTools(config) {
|
|
|
139
140
|
* model override resolver.
|
|
140
141
|
*/
|
|
141
142
|
function readGsdEffectiveAgentTools(targetDir = null, options = {}) {
|
|
142
|
-
const home = options.homedir ? options.homedir() : node_os_1.default.homedir();
|
|
143
|
+
const home = (0, runtime_homes_cjs_1.resolveGsdHome)(options.homedir ? options.homedir() : node_os_1.default.homedir());
|
|
143
144
|
const globalConfig = _readGsdConfigFile(node_path_1.default.join(home, '.gsd', 'defaults.json'), 'global defaults');
|
|
144
145
|
let projectConfig = null;
|
|
145
146
|
if (targetDir) {
|
|
@@ -170,7 +171,7 @@ function readGsdEffectiveAgentTools(targetDir = null, options = {}) {
|
|
|
170
171
|
* baked, letting the runtime's own default/session model govern).
|
|
171
172
|
*/
|
|
172
173
|
function readGsdRuntimeProfileResolver(targetDir = null) {
|
|
173
|
-
const homeDefaults = _readGsdConfigFile(node_path_1.default.join(
|
|
174
|
+
const homeDefaults = _readGsdConfigFile(node_path_1.default.join((0, runtime_homes_cjs_1.resolveGsdHome)(), '.gsd', 'defaults.json'), '~/.gsd/defaults.json');
|
|
174
175
|
// #2875 defect fix (Generative Fix Divergence): same shared walk as
|
|
175
176
|
// readGsdEffectiveModelOverrides above — see that call site's comment.
|
|
176
177
|
let projectConfig = null;
|
|
@@ -31,6 +31,8 @@ const frontmatter_fence_cjs_1 = require("./frontmatter-fence.cjs");
|
|
|
31
31
|
// #2322: reuse the existing pure path-containment seam (ADR-1239 Phase C-2)
|
|
32
32
|
// instead of hand-rolling a new traversal check for capability skill stems.
|
|
33
33
|
const external_descriptor_trust_cjs_1 = require("./external-descriptor-trust.cjs");
|
|
34
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports -- capability-ledger.cjs is an export= CommonJS module
|
|
35
|
+
const capabilityLedger = require("./capability-ledger.cjs");
|
|
34
36
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
35
37
|
const conversionModule = require("./runtime-artifact-conversion.cjs");
|
|
36
38
|
const { applyAgentPathRewrites: _applyAgentPathRewrites, processAttribution: _processAttribution, normalizeAgentBodyForRuntime: _normalizeAgentBodyForRuntime, readGsdCommandNames: _readGsdCommandNames, deriveAgentName: _deriveAgentName, applyAgentFrontmatterExtensions: _applyAgentFrontmatterExtensions, appendAgentTools: _appendAgentTools, } = conversionModule;
|
|
@@ -748,9 +750,9 @@ function readInstalledCapabilitySkill(stem, registry) {
|
|
|
748
750
|
// installed as a capability skill (epic #4636).
|
|
749
751
|
if (node_fs_1.default.lstatSync(skillPath).isSymbolicLink())
|
|
750
752
|
return null;
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
return { capId, content
|
|
753
|
+
// #5121: the bounded regular-file read — a FIFO/device/oversized SKILL.md is skipped like a missing one.
|
|
754
|
+
const content = capabilityLedger.readSkillFile(skillPath);
|
|
755
|
+
return content === null ? null : { capId, content };
|
|
754
756
|
}
|
|
755
757
|
catch {
|
|
756
758
|
return null; // missing / unreadable / corrupt entry -> skip
|
|
@@ -23,6 +23,10 @@
|
|
|
23
23
|
* (design row #5 / "Not-corruption"). `kindsDiffer` on `ShadowReport` is what
|
|
24
24
|
* lets `renderShadowReport` word the two cases correctly.
|
|
25
25
|
*
|
|
26
|
+
* Same-scope pairs are not reported: a runtime whose layout installs `commands` and `skills` side by side at one
|
|
27
|
+
* scope (OpenCode, Kilo, Augment, CodeBuddy, ZCode) writes both on purpose, and `isGenuinelyShadowed` requires the
|
|
28
|
+
* winner and the loser to sit in different scopes (#5344).
|
|
29
|
+
*
|
|
26
30
|
* ── Report, don't correct (mirrors the resolver's own law) ─────────────────
|
|
27
31
|
* `mismatches` surfaces a declared runtime/scope that disagrees with the
|
|
28
32
|
* probed one (Postel's Law, design doc: liberal in what is accepted, but the
|
|
@@ -218,6 +222,14 @@ function stemFromTrigger(trigger, prefix) {
|
|
|
218
222
|
function isGenuinelyShadowed(t, scopeRecords, prefixFor) {
|
|
219
223
|
if (t.shadowedBy === null)
|
|
220
224
|
return false;
|
|
225
|
+
// This is a CROSS-SCOPE report (see the module comment). A winner and a loser in the SAME scope are the two
|
|
226
|
+
// kinds one install deliberately wrote side by side — OpenCode, Kilo, Augment, CodeBuddy and ZCode install
|
|
227
|
+
// `commands` and `skills` for every trigger at one scope. GSD wrote both, the operator cannot act on it, and
|
|
228
|
+
// the host's own dedupe (OpenCode 1.x lists the command and drops the same-named skill; 2.x keeps them on
|
|
229
|
+
// separate surfaces) is not a loss of reachability. Reporting it printed "72 triggers shadowed" on every
|
|
230
|
+
// install of those five runtimes (#5344).
|
|
231
|
+
if (t.shadowedBy.scope === t.scope)
|
|
232
|
+
return false;
|
|
221
233
|
const winnerRecord = scopeRecords.get(t.shadowedBy.scope);
|
|
222
234
|
const shadowedRecord = scopeRecords.get(t.scope);
|
|
223
235
|
const winnerStem = stemFromTrigger(t.trigger, prefixFor(t.shadowedBy.scope, t.shadowedBy.kind));
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Installer migration: stop the GSD MCP companion from duplicating every GSD command in OpenCode
|
|
4
|
+
* (#5338; epic #5331, ADR-5331 D6).
|
|
5
|
+
*
|
|
6
|
+
* OpenCode turns each MCP prompt into a `<server>:<prompt>` slash command. The GSD companion server
|
|
7
|
+
* advertised its whole prompt catalog, so with the command files the installer already writes every GSD
|
|
8
|
+
* command appeared twice (`gsd-plan-phase` and `gsd:plan-phase`; 143 entries against 72 observed on
|
|
9
|
+
* OpenCode 2.0.27). New installs register the server with `--no-prompts`. This migration converges an
|
|
10
|
+
* existing install by adding that flag to the `gsd` MCP entry GSD itself wrote — and only that entry: a
|
|
11
|
+
* `gsd` server the user edited or defined does not match the installer's shape and is never touched.
|
|
12
|
+
*
|
|
13
|
+
* It applies to OpenCode on both generations (the entry is the same shape), covers the 1.x `mcp.gsd` and
|
|
14
|
+
* the 2.x native `mcp.servers.gsd` locations, and edits the config as text so a commented `opencode.jsonc`
|
|
15
|
+
* keeps its comments and layout (#5334).
|
|
16
|
+
*
|
|
17
|
+
* Is the action safe in non-interactive install?
|
|
18
|
+
* Yes. `rewrite-json` is non-interactive and journaled; the rewrite is a one-token append to a command
|
|
19
|
+
* array the installer wrote.
|
|
20
|
+
*
|
|
21
|
+
* See docs/installer-migrations.md#shipped-migrations and the OpenCode row of
|
|
22
|
+
* docs/installer-migrations.md#runtime-configuration-contract-registry.
|
|
23
|
+
*/
|
|
24
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
25
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
26
|
+
};
|
|
27
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
28
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
29
|
+
const jsonc_edit_cjs_1 = require("../jsonc-edit.cjs");
|
|
30
|
+
const opencode_mcp_entry_cjs_1 = require("../opencode-mcp-entry.cjs");
|
|
31
|
+
/** OpenCode reads `opencode.jsonc` in preference to `opencode.json` when both exist. */
|
|
32
|
+
const CONFIG_CANDIDATES = ['opencode.jsonc', 'opencode.json'];
|
|
33
|
+
function isPlainObject(v) {
|
|
34
|
+
return v !== null && typeof v === 'object' && !Array.isArray(v);
|
|
35
|
+
}
|
|
36
|
+
const migration = {
|
|
37
|
+
id: '2026-10-12-opencode-mcp-no-prompts',
|
|
38
|
+
title: 'Stop the GSD MCP companion duplicating GSD commands in OpenCode',
|
|
39
|
+
description: 'Add --no-prompts to the GSD-written `gsd` MCP server entry in opencode.json(c) (mcp.gsd, and the ' +
|
|
40
|
+
'OpenCode 2.x native mcp.servers.gsd) so OpenCode no longer lists every GSD command a second time as ' +
|
|
41
|
+
'a `gsd:<name>` MCP-prompt command. Only the entry the installer wrote is changed; comments and ' +
|
|
42
|
+
'layout in a JSONC config are preserved (#5338, #5334).',
|
|
43
|
+
introducedIn: '1.17.0',
|
|
44
|
+
runtimes: ['opencode'],
|
|
45
|
+
scopes: ['global', 'local'],
|
|
46
|
+
destructive: false,
|
|
47
|
+
runtimeContract: 'docs/installer-migrations.md#runtime-configuration-contract-registry OpenCode row',
|
|
48
|
+
plan: (ctx) => {
|
|
49
|
+
if (ctx.runtime !== 'opencode')
|
|
50
|
+
return [];
|
|
51
|
+
// The file OpenCode (and the installer's resolveOpencodeConfigPath) treats as THE config.
|
|
52
|
+
const relPath = CONFIG_CANDIDATES.find((name) => node_fs_1.default.existsSync(node_path_1.default.join(ctx.configDir, name)));
|
|
53
|
+
if (!relPath)
|
|
54
|
+
return [];
|
|
55
|
+
let config;
|
|
56
|
+
try {
|
|
57
|
+
config = (0, jsonc_edit_cjs_1.parseJsonc)(node_fs_1.default.readFileSync(node_path_1.default.join(ctx.configDir, relPath), 'utf8'));
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
return []; // unparseable: never touch a config we cannot read (same posture as the installer)
|
|
61
|
+
}
|
|
62
|
+
if (!isPlainObject(config) || !isPlainObject(config['mcp']))
|
|
63
|
+
return [];
|
|
64
|
+
const mcp = config['mcp'];
|
|
65
|
+
const edits = [];
|
|
66
|
+
const next = { ...config, mcp: { ...mcp } };
|
|
67
|
+
const nextMcp = next['mcp'];
|
|
68
|
+
const legacy = mcp['gsd'];
|
|
69
|
+
if ((0, opencode_mcp_entry_cjs_1.isGsdMcpEntry)(legacy) && !(0, opencode_mcp_entry_cjs_1.hasNoPromptsFlag)(legacy)) {
|
|
70
|
+
const updated = (0, opencode_mcp_entry_cjs_1.withNoPromptsFlag)(legacy);
|
|
71
|
+
nextMcp['gsd'] = updated;
|
|
72
|
+
edits.push({ path: ['mcp', 'gsd', 'command'], value: updated.command });
|
|
73
|
+
}
|
|
74
|
+
const servers = mcp['servers'];
|
|
75
|
+
if (isPlainObject(servers)) {
|
|
76
|
+
const native = servers['gsd'];
|
|
77
|
+
if ((0, opencode_mcp_entry_cjs_1.isGsdMcpEntry)(native) && !(0, opencode_mcp_entry_cjs_1.hasNoPromptsFlag)(native)) {
|
|
78
|
+
const updated = (0, opencode_mcp_entry_cjs_1.withNoPromptsFlag)(native);
|
|
79
|
+
nextMcp['servers'] = { ...servers, gsd: updated };
|
|
80
|
+
edits.push({ path: ['mcp', 'servers', 'gsd', 'command'], value: updated.command });
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
if (edits.length === 0)
|
|
84
|
+
return [];
|
|
85
|
+
return [
|
|
86
|
+
{
|
|
87
|
+
type: 'rewrite-json',
|
|
88
|
+
relPath,
|
|
89
|
+
value: next,
|
|
90
|
+
edits,
|
|
91
|
+
reason: 'the GSD MCP companion advertised its prompt catalog, which OpenCode lists as a second set of ' +
|
|
92
|
+
'`gsd:<name>` slash commands next to the installed command files',
|
|
93
|
+
ownershipEvidence: "the entry matches the exact shape configureOpencodePermissions writes: type 'local' and a command " +
|
|
94
|
+
"array of `npx … <package> gsd-mcp-server`; a user-edited or user-defined `gsd` server does not match",
|
|
95
|
+
},
|
|
96
|
+
];
|
|
97
|
+
},
|
|
98
|
+
};
|
|
99
|
+
module.exports = migration;
|
|
@@ -18,6 +18,7 @@ const shell_command_projection_cjs_1 = require("./shell-command-projection.cjs")
|
|
|
18
18
|
const clock_cjs_1 = require("./clock.cjs");
|
|
19
19
|
const install_scope_cjs_1 = require("./install-scope.cjs");
|
|
20
20
|
const security_cjs_1 = require("./security.cjs");
|
|
21
|
+
const jsonc_edit_cjs_1 = require("./jsonc-edit.cjs");
|
|
21
22
|
// #2874 (ADR-58 cleanup phase): this file is the ~1200-line migration
|
|
22
23
|
// plan/apply/rollback/lock/journal engine — almost none of it is on the
|
|
23
24
|
// installRuntimeArtifacts call tree. Only `readInstallManifest` and
|
|
@@ -293,15 +294,25 @@ function readInstallManifest(configDir) {
|
|
|
293
294
|
scope: (0, install_scope_cjs_1.isInstallScopeId)(m.scope) ? m.scope : null,
|
|
294
295
|
};
|
|
295
296
|
}
|
|
297
|
+
function normalizeHostGeneration(raw) {
|
|
298
|
+
if (!raw || typeof raw !== 'object')
|
|
299
|
+
return undefined;
|
|
300
|
+
const r = raw;
|
|
301
|
+
if (r.runtime !== 'opencode' || (r.generation !== 1 && r.generation !== 2))
|
|
302
|
+
return undefined;
|
|
303
|
+
return { runtime: 'opencode', generation: r.generation, version: typeof r.version === 'string' ? r.version : null };
|
|
304
|
+
}
|
|
296
305
|
function readInstallState(configDir) {
|
|
297
306
|
const state = readJsonIfPresent(node_path_1.default.join(configDir, INSTALL_STATE_NAME), STRICT_JSON);
|
|
298
307
|
if (!state || typeof state !== 'object') {
|
|
299
308
|
return { schemaVersion: 1, appliedMigrations: [] };
|
|
300
309
|
}
|
|
301
310
|
const s = state;
|
|
311
|
+
const hostGeneration = normalizeHostGeneration(s.hostGeneration);
|
|
302
312
|
return {
|
|
303
313
|
schemaVersion: typeof s.schemaVersion === 'number' ? s.schemaVersion : 1,
|
|
304
314
|
appliedMigrations: Array.isArray(s.appliedMigrations) ? s.appliedMigrations : [],
|
|
315
|
+
...(hostGeneration ? { hostGeneration } : {}),
|
|
305
316
|
};
|
|
306
317
|
}
|
|
307
318
|
// Strict atomic write for the install state: must never be left half-written.
|
|
@@ -654,6 +665,31 @@ function journalAction(action, status, extras = {}) {
|
|
|
654
665
|
const { value: _value, ...safeAction } = action;
|
|
655
666
|
return { ...safeAction, ...extras, status };
|
|
656
667
|
}
|
|
668
|
+
/**
|
|
669
|
+
* The text a `rewrite-json` action writes. A file with JSONC syntax (comments, trailing commas, or a
|
|
670
|
+
* `.jsonc` name) is edited in place from the action's `edits`, so the user's annotations survive (#5334);
|
|
671
|
+
* everything else, and any edit the text editor cannot place, is written as the action's whole `value`.
|
|
672
|
+
*/
|
|
673
|
+
function renderRewrittenJson(fullPath, action) {
|
|
674
|
+
if (Array.isArray(action.edits) && action.edits.length > 0) {
|
|
675
|
+
let original = null;
|
|
676
|
+
try {
|
|
677
|
+
original = node_fs_1.default.readFileSync(fullPath, 'utf8');
|
|
678
|
+
}
|
|
679
|
+
catch { /* fall through to the value rewrite */ }
|
|
680
|
+
if (original !== null && (fullPath.endsWith('.jsonc') || (0, jsonc_edit_cjs_1.hasJsoncFeatures)(original))) {
|
|
681
|
+
let text = original;
|
|
682
|
+
for (const edit of action.edits) {
|
|
683
|
+
text = (0, jsonc_edit_cjs_1.setJsoncValue)(text, edit.path, edit.value);
|
|
684
|
+
if (text === null)
|
|
685
|
+
break;
|
|
686
|
+
}
|
|
687
|
+
if (text !== null)
|
|
688
|
+
return text;
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
return JSON.stringify(action.value, null, 2) + '\n';
|
|
692
|
+
}
|
|
657
693
|
function planInstallerMigrations({ configDir, runtime = null, scope = null, migrations, baselineScan = false, now = () => new Date().toISOString(), }) {
|
|
658
694
|
if (!configDir)
|
|
659
695
|
throw new Error('configDir is required');
|
|
@@ -725,6 +761,8 @@ function planInstallerMigrations({ configDir, runtime = null, scope = null, migr
|
|
|
725
761
|
}
|
|
726
762
|
if (action.type === 'rewrite-json') {
|
|
727
763
|
action.value = rawAction.value;
|
|
764
|
+
if (Array.isArray(rawAction.edits))
|
|
765
|
+
action.edits = rawAction.edits;
|
|
728
766
|
action.deleteIfEmpty = rawAction.deleteIfEmpty === true;
|
|
729
767
|
}
|
|
730
768
|
if (rawAction.prompt)
|
|
@@ -896,7 +934,7 @@ function applyInstallerMigrationPlan({ configDir, plan, now = () => new Date().t
|
|
|
896
934
|
}));
|
|
897
935
|
}
|
|
898
936
|
else {
|
|
899
|
-
(0, shell_command_projection_cjs_1.platformWriteSync)(fullPath,
|
|
937
|
+
(0, shell_command_projection_cjs_1.platformWriteSync)(fullPath, renderRewrittenJson(fullPath, action));
|
|
900
938
|
journal.actions.push(journalAction(action, 'rewritten', {
|
|
901
939
|
rollbackRelPath: node_path_1.default.posix.join(rollbackRootRelPath, normalized),
|
|
902
940
|
}));
|
|
@@ -945,6 +983,7 @@ function applyInstallerMigrationPlan({ configDir, plan, now = () => new Date().t
|
|
|
945
983
|
writeInstallState(configDir, {
|
|
946
984
|
schemaVersion: 1,
|
|
947
985
|
appliedMigrations: nextApplied,
|
|
986
|
+
...(state.hostGeneration ? { hostGeneration: state.hostGeneration } : {}),
|
|
948
987
|
});
|
|
949
988
|
return {
|
|
950
989
|
appliedMigrationIds: journal.appliedMigrationIds,
|
|
@@ -1017,6 +1056,7 @@ function markPendingMigrationsApplied({ configDir, plan, now = () => new Date().
|
|
|
1017
1056
|
writeInstallState(configDir, {
|
|
1018
1057
|
schemaVersion: 1,
|
|
1019
1058
|
appliedMigrations: nextApplied,
|
|
1059
|
+
...(state.hostGeneration ? { hostGeneration: state.hostGeneration } : {}),
|
|
1020
1060
|
});
|
|
1021
1061
|
}
|
|
1022
1062
|
return newlyApplied;
|
|
@@ -1073,6 +1113,18 @@ function runInstallerMigrations({ configDir, runtime = null, scope = null, migra
|
|
|
1073
1113
|
// Unused but kept to satisfy eslint — sleepSync is referenced in the original
|
|
1074
1114
|
// and may be used by test code that patches this module.
|
|
1075
1115
|
void sleepSync;
|
|
1116
|
+
/**
|
|
1117
|
+
* Record which OpenCode generation this install targets (ADR-5331 D2; #5337). Idempotent and
|
|
1118
|
+
* atomic; leaves the applied-migrations journal untouched.
|
|
1119
|
+
*/
|
|
1120
|
+
function recordHostGeneration(configDir, info) {
|
|
1121
|
+
const state = readInstallState(configDir);
|
|
1122
|
+
const next = { runtime: 'opencode', generation: info.generation, version: info.version ?? null };
|
|
1123
|
+
const prev = state.hostGeneration;
|
|
1124
|
+
if (prev && prev.generation === next.generation && prev.version === next.version)
|
|
1125
|
+
return;
|
|
1126
|
+
writeInstallState(configDir, { ...state, hostGeneration: next });
|
|
1127
|
+
}
|
|
1076
1128
|
module.exports = {
|
|
1077
1129
|
DEFAULT_MIGRATIONS_DIR,
|
|
1078
1130
|
INSTALL_MIGRATION_LOCK_NAME,
|
|
@@ -1089,6 +1141,7 @@ module.exports = {
|
|
|
1089
1141
|
planInstallerMigrations,
|
|
1090
1142
|
readInstallManifest,
|
|
1091
1143
|
readInstallState,
|
|
1144
|
+
recordHostGeneration,
|
|
1092
1145
|
runInstallerMigrations,
|
|
1093
1146
|
writeInstallState,
|
|
1094
1147
|
};
|