gsdd-cli 0.18.4 → 0.19.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.
Files changed (100) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +625 -607
  3. package/agents/DISTILLATION.md +421 -421
  4. package/agents/README.md +62 -62
  5. package/agents/approach-explorer.md +361 -361
  6. package/agents/debugger.md +82 -82
  7. package/agents/executor.md +394 -394
  8. package/agents/integration-checker.md +318 -318
  9. package/agents/mapper.md +103 -103
  10. package/agents/planner.md +313 -313
  11. package/agents/researcher.md +84 -84
  12. package/agents/roadmapper.md +296 -296
  13. package/agents/synthesizer.md +236 -236
  14. package/agents/verifier.md +337 -337
  15. package/bin/adapters/agents.mjs +34 -34
  16. package/bin/adapters/claude.mjs +191 -191
  17. package/bin/adapters/codex.mjs +85 -85
  18. package/bin/adapters/index.mjs +20 -20
  19. package/bin/adapters/opencode.mjs +278 -278
  20. package/bin/gsdd.mjs +123 -116
  21. package/bin/lib/cli-utils.mjs +28 -28
  22. package/bin/lib/evidence-contract.mjs +112 -112
  23. package/bin/lib/file-ops.mjs +209 -161
  24. package/bin/lib/health-truth.mjs +181 -178
  25. package/bin/lib/health.mjs +265 -235
  26. package/bin/lib/init-flow.mjs +252 -236
  27. package/bin/lib/init-prompts.mjs +248 -247
  28. package/bin/lib/init-runtime.mjs +225 -212
  29. package/bin/lib/init.mjs +17 -17
  30. package/bin/lib/lifecycle-preflight.mjs +360 -333
  31. package/bin/lib/lifecycle-state.mjs +351 -267
  32. package/bin/lib/manifest.mjs +116 -114
  33. package/bin/lib/models.mjs +411 -411
  34. package/bin/lib/phase.mjs +397 -378
  35. package/bin/lib/plan-constants.mjs +30 -30
  36. package/bin/lib/provenance.mjs +109 -106
  37. package/bin/lib/rendering.mjs +178 -130
  38. package/bin/lib/runtime-freshness.mjs +221 -219
  39. package/bin/lib/templates.mjs +225 -224
  40. package/bin/lib/workspace-root.mjs +132 -0
  41. package/distilled/DESIGN.md +2347 -2327
  42. package/distilled/EVIDENCE-INDEX.md +397 -394
  43. package/distilled/README.md +196 -193
  44. package/distilled/SKILL.md +86 -85
  45. package/distilled/templates/agents.block.md +21 -21
  46. package/distilled/templates/agents.md +6 -6
  47. package/distilled/templates/approach.md +232 -232
  48. package/distilled/templates/auth-matrix.md +78 -78
  49. package/distilled/templates/brownfield-change/CHANGE.md +99 -0
  50. package/distilled/templates/brownfield-change/HANDOFF.md +38 -0
  51. package/distilled/templates/brownfield-change/VERIFICATION.md +56 -0
  52. package/distilled/templates/codebase/architecture.md +110 -110
  53. package/distilled/templates/codebase/concerns.md +95 -95
  54. package/distilled/templates/codebase/conventions.md +193 -193
  55. package/distilled/templates/codebase/stack.md +96 -96
  56. package/distilled/templates/delegates/approach-explorer.md +25 -25
  57. package/distilled/templates/delegates/mapper-arch.md +26 -26
  58. package/distilled/templates/delegates/mapper-concerns.md +27 -27
  59. package/distilled/templates/delegates/mapper-quality.md +28 -28
  60. package/distilled/templates/delegates/mapper-tech.md +25 -25
  61. package/distilled/templates/delegates/plan-checker.md +68 -68
  62. package/distilled/templates/delegates/researcher-architecture.md +30 -30
  63. package/distilled/templates/delegates/researcher-features.md +30 -30
  64. package/distilled/templates/delegates/researcher-pitfalls.md +30 -30
  65. package/distilled/templates/delegates/researcher-stack.md +30 -30
  66. package/distilled/templates/delegates/researcher-synthesizer.md +31 -31
  67. package/distilled/templates/research/architecture.md +57 -57
  68. package/distilled/templates/research/features.md +23 -23
  69. package/distilled/templates/research/pitfalls.md +46 -46
  70. package/distilled/templates/research/stack.md +45 -45
  71. package/distilled/templates/research/summary.md +67 -67
  72. package/distilled/templates/roadmap.md +74 -62
  73. package/distilled/templates/spec.md +110 -110
  74. package/distilled/workflows/audit-milestone.md +275 -271
  75. package/distilled/workflows/complete-milestone.md +336 -332
  76. package/distilled/workflows/execute.md +454 -449
  77. package/distilled/workflows/map-codebase.md +253 -253
  78. package/distilled/workflows/new-milestone.md +242 -238
  79. package/distilled/workflows/new-project.md +398 -398
  80. package/distilled/workflows/pause.md +160 -156
  81. package/distilled/workflows/plan-milestone-gaps.md +183 -183
  82. package/distilled/workflows/plan.md +451 -447
  83. package/distilled/workflows/progress.md +227 -223
  84. package/distilled/workflows/quick.md +351 -347
  85. package/distilled/workflows/resume.md +220 -212
  86. package/distilled/workflows/verify-work.md +260 -260
  87. package/distilled/workflows/verify.md +431 -429
  88. package/docs/BROWNFIELD-PROOF.md +95 -95
  89. package/docs/RUNTIME-SUPPORT.md +93 -75
  90. package/docs/USER-GUIDE.md +440 -399
  91. package/docs/VERIFICATION-DISCIPLINE.md +59 -59
  92. package/docs/claude/context-monitor.md +98 -98
  93. package/docs/proof/consumer-node-cli/README.md +37 -37
  94. package/docs/proof/consumer-node-cli/ROADMAP.md +14 -14
  95. package/docs/proof/consumer-node-cli/SPEC.md +17 -17
  96. package/docs/proof/consumer-node-cli/brief.md +9 -9
  97. package/docs/proof/consumer-node-cli/phases/01-foundation/01-01-PLAN.md +34 -34
  98. package/docs/proof/consumer-node-cli/phases/01-foundation/01-01-SUMMARY.md +10 -10
  99. package/docs/proof/consumer-node-cli/phases/01-foundation/01-VERIFICATION.md +30 -30
  100. package/package.json +62 -61
@@ -8,22 +8,22 @@ import {
8
8
  renderOpenCodeCommandContent,
9
9
  renderSkillContent,
10
10
  } from './rendering.mjs';
11
- import {
12
- CLAUDE_MODEL_PROFILES,
13
- renderClaudeApproachExplorer,
14
- renderClaudePlanChecker,
15
- renderClaudePlanCommand,
16
- renderClaudePlanSkill,
17
- } from '../adapters/claude.mjs';
18
- import {
19
- renderOpenCodeApproachExplorer,
20
- renderOpenCodePlanChecker,
21
- renderOpenCodePlanCommand,
22
- } from '../adapters/opencode.mjs';
23
- import {
24
- renderCodexApproachExplorer,
25
- renderCodexPlanChecker,
26
- } from '../adapters/codex.mjs';
11
+ import {
12
+ CLAUDE_MODEL_PROFILES,
13
+ renderClaudeApproachExplorer,
14
+ renderClaudePlanChecker,
15
+ renderClaudePlanCommand,
16
+ renderClaudePlanSkill,
17
+ } from '../adapters/claude.mjs';
18
+ import {
19
+ renderOpenCodeApproachExplorer,
20
+ renderOpenCodePlanChecker,
21
+ renderOpenCodePlanCommand,
22
+ } from '../adapters/opencode.mjs';
23
+ import {
24
+ renderCodexApproachExplorer,
25
+ renderCodexPlanChecker,
26
+ } from '../adapters/codex.mjs';
27
27
  import {
28
28
  getRuntimeModelOverride,
29
29
  loadProjectModelConfig,
@@ -33,121 +33,121 @@ import {
33
33
  const __filename = fileURLToPath(import.meta.url);
34
34
  const __dirname = dirname(__filename);
35
35
  const PACKAGE_JSON = JSON.parse(readFileSync(join(__dirname, '..', '..', 'package.json'), 'utf-8'));
36
-
37
- function normalizeContent(content) {
38
- return String(content).replace(/\r\n/g, '\n');
39
- }
40
-
41
- function compareGeneratedFile({ cwd, runtime, relativePath, expectedContent, repairCommand }) {
42
- const absolutePath = join(cwd, relativePath);
43
- if (!existsSync(absolutePath)) {
44
- return {
45
- runtime,
46
- relativePath,
47
- status: 'missing',
48
- repairCommand,
49
- };
50
- }
51
-
52
- const actualContent = normalizeContent(readFileSync(absolutePath, 'utf-8'));
53
- const expected = normalizeContent(expectedContent);
54
- if (actualContent === expected) {
55
- return {
56
- runtime,
57
- relativePath,
58
- status: 'clean',
59
- repairCommand,
60
- };
61
- }
62
-
63
- return {
64
- runtime,
65
- relativePath,
66
- status: 'stale',
67
- repairCommand,
68
- };
69
- }
70
-
71
- function buildClaudeEntries({ cwd, workflows }) {
72
- const checkerModelAlias = resolveRuntimeAgentModel({
73
- cwd,
74
- runtime: 'claude',
75
- agentId: 'plan-checker',
76
- profileMap: CLAUDE_MODEL_PROFILES,
77
- });
78
- const explorerModelAlias = resolveRuntimeAgentModel({
79
- cwd,
80
- runtime: 'claude',
81
- agentId: 'approach-explorer',
82
- profileMap: CLAUDE_MODEL_PROFILES,
83
- });
84
-
85
- const entries = workflows.map((workflow) => ({
86
- relativePath: `.claude/skills/${workflow.name}/SKILL.md`,
87
- expectedContent: workflow.name === 'gsdd-plan'
88
- ? renderClaudePlanSkill()
89
- : renderSkillContent(workflow),
90
- }));
91
-
92
- entries.push(
93
- {
94
- relativePath: '.claude/commands/gsdd-plan.md',
95
- expectedContent: renderClaudePlanCommand(),
96
- },
97
- {
98
- relativePath: '.claude/agents/gsdd-plan-checker.md',
99
- expectedContent: renderClaudePlanChecker(getDelegateContent('plan-checker.md'), checkerModelAlias),
100
- },
101
- {
102
- relativePath: '.claude/agents/gsdd-approach-explorer.md',
103
- expectedContent: renderClaudeApproachExplorer(getDelegateContent('approach-explorer.md'), explorerModelAlias),
104
- }
105
- );
106
-
107
- return entries;
108
- }
109
-
110
- function buildOpenCodeEntries({ cwd, workflows }) {
111
- const config = loadProjectModelConfig(cwd);
112
- const checkerModelId = getRuntimeModelOverride(config, 'opencode', 'plan-checker');
113
- const explorerModelId = getRuntimeModelOverride(config, 'opencode', 'approach-explorer');
114
-
115
- const entries = workflows.map((workflow) => ({
116
- relativePath: `.opencode/commands/${workflow.name}.md`,
117
- expectedContent: workflow.name === 'gsdd-plan'
118
- ? renderOpenCodePlanCommand()
119
- : renderOpenCodeCommandContent(workflow),
120
- }));
121
-
122
- entries.push(
123
- {
124
- relativePath: '.opencode/agents/gsdd-plan-checker.md',
125
- expectedContent: renderOpenCodePlanChecker(getDelegateContent('plan-checker.md'), checkerModelId),
126
- },
127
- {
128
- relativePath: '.opencode/agents/gsdd-approach-explorer.md',
129
- expectedContent: renderOpenCodeApproachExplorer(getDelegateContent('approach-explorer.md'), explorerModelId),
130
- }
131
- );
132
-
133
- return entries;
134
- }
135
-
36
+
37
+ function normalizeContent(content) {
38
+ return String(content).replace(/\r\n/g, '\n');
39
+ }
40
+
41
+ function compareGeneratedFile({ cwd, runtime, relativePath, expectedContent, repairCommand }) {
42
+ const absolutePath = join(cwd, relativePath);
43
+ if (!existsSync(absolutePath)) {
44
+ return {
45
+ runtime,
46
+ relativePath,
47
+ status: 'missing',
48
+ repairCommand,
49
+ };
50
+ }
51
+
52
+ const actualContent = normalizeContent(readFileSync(absolutePath, 'utf-8'));
53
+ const expected = normalizeContent(expectedContent);
54
+ if (actualContent === expected) {
55
+ return {
56
+ runtime,
57
+ relativePath,
58
+ status: 'clean',
59
+ repairCommand,
60
+ };
61
+ }
62
+
63
+ return {
64
+ runtime,
65
+ relativePath,
66
+ status: 'stale',
67
+ repairCommand,
68
+ };
69
+ }
70
+
71
+ function buildClaudeEntries({ cwd, workflows }) {
72
+ const checkerModelAlias = resolveRuntimeAgentModel({
73
+ cwd,
74
+ runtime: 'claude',
75
+ agentId: 'plan-checker',
76
+ profileMap: CLAUDE_MODEL_PROFILES,
77
+ });
78
+ const explorerModelAlias = resolveRuntimeAgentModel({
79
+ cwd,
80
+ runtime: 'claude',
81
+ agentId: 'approach-explorer',
82
+ profileMap: CLAUDE_MODEL_PROFILES,
83
+ });
84
+
85
+ const entries = workflows.map((workflow) => ({
86
+ relativePath: `.claude/skills/${workflow.name}/SKILL.md`,
87
+ expectedContent: workflow.name === 'gsdd-plan'
88
+ ? renderClaudePlanSkill()
89
+ : renderSkillContent(workflow),
90
+ }));
91
+
92
+ entries.push(
93
+ {
94
+ relativePath: '.claude/commands/gsdd-plan.md',
95
+ expectedContent: renderClaudePlanCommand(),
96
+ },
97
+ {
98
+ relativePath: '.claude/agents/gsdd-plan-checker.md',
99
+ expectedContent: renderClaudePlanChecker(getDelegateContent('plan-checker.md'), checkerModelAlias),
100
+ },
101
+ {
102
+ relativePath: '.claude/agents/gsdd-approach-explorer.md',
103
+ expectedContent: renderClaudeApproachExplorer(getDelegateContent('approach-explorer.md'), explorerModelAlias),
104
+ }
105
+ );
106
+
107
+ return entries;
108
+ }
109
+
110
+ function buildOpenCodeEntries({ cwd, workflows }) {
111
+ const config = loadProjectModelConfig(cwd);
112
+ const checkerModelId = getRuntimeModelOverride(config, 'opencode', 'plan-checker');
113
+ const explorerModelId = getRuntimeModelOverride(config, 'opencode', 'approach-explorer');
114
+
115
+ const entries = workflows.map((workflow) => ({
116
+ relativePath: `.opencode/commands/${workflow.name}.md`,
117
+ expectedContent: workflow.name === 'gsdd-plan'
118
+ ? renderOpenCodePlanCommand()
119
+ : renderOpenCodeCommandContent(workflow),
120
+ }));
121
+
122
+ entries.push(
123
+ {
124
+ relativePath: '.opencode/agents/gsdd-plan-checker.md',
125
+ expectedContent: renderOpenCodePlanChecker(getDelegateContent('plan-checker.md'), checkerModelId),
126
+ },
127
+ {
128
+ relativePath: '.opencode/agents/gsdd-approach-explorer.md',
129
+ expectedContent: renderOpenCodeApproachExplorer(getDelegateContent('approach-explorer.md'), explorerModelId),
130
+ }
131
+ );
132
+
133
+ return entries;
134
+ }
135
+
136
136
  function buildCodexEntries({ cwd }) {
137
- const config = loadProjectModelConfig(cwd);
138
- const checkerModelId = getRuntimeModelOverride(config, 'codex', 'plan-checker');
139
- const explorerModelId = getRuntimeModelOverride(config, 'codex', 'approach-explorer');
140
-
141
- return [
142
- {
143
- relativePath: '.codex/agents/gsdd-plan-checker.toml',
144
- expectedContent: renderCodexPlanChecker(getDelegateContent('plan-checker.md'), checkerModelId),
145
- },
146
- {
147
- relativePath: '.codex/agents/gsdd-approach-explorer.toml',
148
- expectedContent: renderCodexApproachExplorer(getDelegateContent('approach-explorer.md'), explorerModelId),
149
- },
150
- ];
137
+ const config = loadProjectModelConfig(cwd);
138
+ const checkerModelId = getRuntimeModelOverride(config, 'codex', 'plan-checker');
139
+ const explorerModelId = getRuntimeModelOverride(config, 'codex', 'approach-explorer');
140
+
141
+ return [
142
+ {
143
+ relativePath: '.codex/agents/gsdd-plan-checker.toml',
144
+ expectedContent: renderCodexPlanChecker(getDelegateContent('plan-checker.md'), checkerModelId),
145
+ },
146
+ {
147
+ relativePath: '.codex/agents/gsdd-approach-explorer.toml',
148
+ expectedContent: renderCodexApproachExplorer(getDelegateContent('approach-explorer.md'), explorerModelId),
149
+ },
150
+ ];
151
151
  }
152
152
 
153
153
  function buildWorkspaceHelperEntries() {
@@ -166,97 +166,99 @@ export function collectExpectedRuntimeSurfaceGroups({ cwd = process.cwd(), workf
166
166
  runtime: 'workspace-helper',
167
167
  label: 'workspace workflow helper',
168
168
  root: '.planning/bin',
169
- repairCommand: 'gsdd update',
169
+ repairCommand: 'npx -y gsdd-cli update',
170
170
  entries: buildWorkspaceHelperEntries(),
171
171
  },
172
172
  {
173
173
  runtime: 'portable',
174
174
  label: 'portable skills',
175
- root: '.agents/skills',
176
- repairCommand: 'gsdd update',
177
- entries: buildPortableSkillEntries(workflows).map((entry) => ({
178
- relativePath: entry.relativePath,
179
- expectedContent: entry.content,
180
- })),
181
- },
182
- {
183
- runtime: 'claude',
184
- label: 'Claude Code native surfaces',
185
- root: '.claude',
186
- repairCommand: 'gsdd update --tools claude',
187
- entries: buildClaudeEntries({ cwd, workflows }),
188
- },
189
- {
190
- runtime: 'opencode',
191
- label: 'OpenCode native surfaces',
192
- root: '.opencode',
193
- repairCommand: 'gsdd update --tools opencode',
194
- entries: buildOpenCodeEntries({ cwd, workflows }),
195
- },
196
- {
197
- runtime: 'codex',
198
- label: 'Codex CLI native agents',
199
- root: '.codex',
200
- repairCommand: 'gsdd update --tools codex',
201
- entries: buildCodexEntries({ cwd }),
202
- },
203
- ];
204
- }
205
-
206
- export function evaluateRuntimeFreshness({ cwd = process.cwd(), workflows = [] }) {
207
- const groups = collectExpectedRuntimeSurfaceGroups({ cwd, workflows }).map((group) => {
208
- const installed = existsSync(join(cwd, group.root));
209
- const comparisons = installed
210
- ? group.entries.map((entry) => compareGeneratedFile({
211
- cwd,
212
- runtime: group.runtime,
213
- relativePath: entry.relativePath,
214
- expectedContent: entry.expectedContent,
215
- repairCommand: group.repairCommand,
216
- }))
217
- : [];
218
-
219
- const stale = comparisons.filter((entry) => entry.status === 'stale');
220
- const missing = comparisons.filter((entry) => entry.status === 'missing');
221
-
222
- return {
223
- ...group,
224
- installed,
225
- comparisons,
226
- stale,
227
- missing,
228
- issueCount: stale.length + missing.length,
229
- };
230
- });
231
-
232
- const checkedGroups = groups.filter((group) => group.installed);
233
- const issues = checkedGroups.flatMap((group) => group.comparisons.filter((entry) => entry.status !== 'clean'));
234
-
235
- return {
236
- groups,
237
- checkedGroups: checkedGroups.map((group) => group.runtime),
238
- hasInstalledRuntimeSurfaces: checkedGroups.length > 0,
239
- issueCount: issues.length,
240
- staleCount: issues.filter((entry) => entry.status === 'stale').length,
241
- missingCount: issues.filter((entry) => entry.status === 'missing').length,
242
- issues,
243
- };
244
- }
245
-
246
- export function summarizeRuntimeFreshnessIssues(report, limit = 4) {
247
- if (!report || report.issueCount === 0) return '';
248
- const listed = report.issues
249
- .slice(0, limit)
250
- .map((entry) => `${entry.relativePath} [${entry.status}]`);
251
- const remainder = report.issueCount - listed.length;
252
- return remainder > 0 ? `${listed.join(', ')} (+${remainder} more)` : listed.join(', ');
253
- }
254
-
255
- export function getRuntimeFreshnessRepairGuidance(report) {
256
- if (!report || report.issueCount === 0) return 'Run `gsdd update` to regenerate installed runtime surfaces.';
257
- const commands = [...new Set(report.issues.map((entry) => entry.repairCommand))];
258
- if (commands.length === 1) {
259
- return `Run \`${commands[0]}\` to regenerate the installed runtime surfaces.`;
260
- }
261
- return `Run \`gsdd update\` to regenerate all installed runtime surfaces, or target the affected adapters individually: ${commands.map((command) => `\`${command}\``).join(', ')}.`;
262
- }
175
+ root: '.agents/skills',
176
+ repairCommand: 'npx -y gsdd-cli update',
177
+ entries: buildPortableSkillEntries(workflows).map((entry) => ({
178
+ relativePath: entry.relativePath,
179
+ expectedContent: entry.content,
180
+ })),
181
+ },
182
+ {
183
+ runtime: 'claude',
184
+ label: 'Claude Code native surfaces',
185
+ root: '.claude',
186
+ repairCommand: 'npx -y gsdd-cli update --tools claude',
187
+ entries: buildClaudeEntries({ cwd, workflows }),
188
+ },
189
+ {
190
+ runtime: 'opencode',
191
+ label: 'OpenCode native surfaces',
192
+ root: '.opencode',
193
+ repairCommand: 'npx -y gsdd-cli update --tools opencode',
194
+ entries: buildOpenCodeEntries({ cwd, workflows }),
195
+ },
196
+ {
197
+ runtime: 'codex',
198
+ label: 'Codex CLI native agents',
199
+ root: '.codex',
200
+ repairCommand: 'npx -y gsdd-cli update --tools codex',
201
+ entries: buildCodexEntries({ cwd }),
202
+ },
203
+ ];
204
+ }
205
+
206
+ export function evaluateRuntimeFreshness({ cwd = process.cwd(), workflows = [] }) {
207
+ const groups = collectExpectedRuntimeSurfaceGroups({ cwd, workflows }).map((group) => {
208
+ const installed = group.runtime === 'workspace-helper'
209
+ ? existsSync(join(cwd, '.planning'))
210
+ : existsSync(join(cwd, group.root));
211
+ const comparisons = installed
212
+ ? group.entries.map((entry) => compareGeneratedFile({
213
+ cwd,
214
+ runtime: group.runtime,
215
+ relativePath: entry.relativePath,
216
+ expectedContent: entry.expectedContent,
217
+ repairCommand: group.repairCommand,
218
+ }))
219
+ : [];
220
+
221
+ const stale = comparisons.filter((entry) => entry.status === 'stale');
222
+ const missing = comparisons.filter((entry) => entry.status === 'missing');
223
+
224
+ return {
225
+ ...group,
226
+ installed,
227
+ comparisons,
228
+ stale,
229
+ missing,
230
+ issueCount: stale.length + missing.length,
231
+ };
232
+ });
233
+
234
+ const checkedGroups = groups.filter((group) => group.installed);
235
+ const issues = checkedGroups.flatMap((group) => group.comparisons.filter((entry) => entry.status !== 'clean'));
236
+
237
+ return {
238
+ groups,
239
+ checkedGroups: checkedGroups.map((group) => group.runtime),
240
+ hasInstalledRuntimeSurfaces: checkedGroups.length > 0,
241
+ issueCount: issues.length,
242
+ staleCount: issues.filter((entry) => entry.status === 'stale').length,
243
+ missingCount: issues.filter((entry) => entry.status === 'missing').length,
244
+ issues,
245
+ };
246
+ }
247
+
248
+ export function summarizeRuntimeFreshnessIssues(report, limit = 4) {
249
+ if (!report || report.issueCount === 0) return '';
250
+ const listed = report.issues
251
+ .slice(0, limit)
252
+ .map((entry) => `${entry.relativePath} [${entry.status}]`);
253
+ const remainder = report.issueCount - listed.length;
254
+ return remainder > 0 ? `${listed.join(', ')} (+${remainder} more)` : listed.join(', ');
255
+ }
256
+
257
+ export function getRuntimeFreshnessRepairGuidance(report) {
258
+ if (!report || report.issueCount === 0) return 'Run `npx -y gsdd-cli update` to regenerate installed runtime surfaces.';
259
+ const commands = [...new Set(report.issues.map((entry) => entry.repairCommand))];
260
+ if (commands.length === 1) {
261
+ return `Run \`${commands[0]}\` to regenerate the installed runtime surfaces.`;
262
+ }
263
+ return `Run \`npx -y gsdd-cli update\` to regenerate all installed runtime surfaces, or target the affected adapters individually: ${commands.map((command) => `\`${command}\``).join(', ')}.`;
264
+ }