gsdd-cli 0.18.5 → 0.19.1

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 (101) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +610 -608
  3. package/agents/DISTILLATION.md +421 -421
  4. package/agents/README.md +62 -62
  5. package/agents/approach-explorer.md +370 -361
  6. package/agents/debugger.md +82 -82
  7. package/agents/executor.md +473 -394
  8. package/agents/integration-checker.md +318 -318
  9. package/agents/mapper.md +103 -103
  10. package/agents/planner.md +342 -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 +193 -191
  17. package/bin/adapters/codex.mjs +85 -85
  18. package/bin/adapters/index.mjs +20 -20
  19. package/bin/adapters/opencode.mjs +280 -278
  20. package/bin/gsdd.mjs +123 -116
  21. package/bin/lib/cli-utils.mjs +28 -28
  22. package/bin/lib/evidence-contract.mjs +325 -112
  23. package/bin/lib/file-ops.mjs +186 -144
  24. package/bin/lib/health-truth.mjs +196 -178
  25. package/bin/lib/health.mjs +246 -226
  26. package/bin/lib/init-flow.mjs +247 -231
  27. package/bin/lib/init-prompts.mjs +248 -247
  28. package/bin/lib/init-runtime.mjs +193 -190
  29. package/bin/lib/init.mjs +17 -17
  30. package/bin/lib/lifecycle-preflight.mjs +760 -326
  31. package/bin/lib/lifecycle-state.mjs +356 -267
  32. package/bin/lib/manifest.mjs +116 -114
  33. package/bin/lib/models.mjs +411 -411
  34. package/bin/lib/phase.mjs +365 -358
  35. package/bin/lib/plan-constants.mjs +35 -30
  36. package/bin/lib/provenance.mjs +109 -106
  37. package/bin/lib/rendering.mjs +119 -83
  38. package/bin/lib/runtime-freshness.mjs +214 -214
  39. package/bin/lib/session-fingerprint.mjs +91 -14
  40. package/bin/lib/templates.mjs +225 -224
  41. package/bin/lib/workspace-root.mjs +2 -1
  42. package/distilled/DESIGN.md +2461 -2323
  43. package/distilled/EVIDENCE-INDEX.md +418 -392
  44. package/distilled/README.md +196 -193
  45. package/distilled/SKILL.md +86 -85
  46. package/distilled/templates/agents.block.md +21 -21
  47. package/distilled/templates/agents.md +6 -6
  48. package/distilled/templates/approach.md +272 -232
  49. package/distilled/templates/auth-matrix.md +78 -78
  50. package/distilled/templates/brownfield-change/CHANGE.md +99 -0
  51. package/distilled/templates/brownfield-change/HANDOFF.md +38 -0
  52. package/distilled/templates/brownfield-change/VERIFICATION.md +56 -0
  53. package/distilled/templates/codebase/architecture.md +110 -110
  54. package/distilled/templates/codebase/concerns.md +95 -95
  55. package/distilled/templates/codebase/conventions.md +193 -193
  56. package/distilled/templates/codebase/stack.md +96 -96
  57. package/distilled/templates/delegates/approach-explorer.md +28 -25
  58. package/distilled/templates/delegates/mapper-arch.md +26 -26
  59. package/distilled/templates/delegates/mapper-concerns.md +27 -27
  60. package/distilled/templates/delegates/mapper-quality.md +28 -28
  61. package/distilled/templates/delegates/mapper-tech.md +25 -25
  62. package/distilled/templates/delegates/plan-checker.md +78 -68
  63. package/distilled/templates/delegates/researcher-architecture.md +30 -30
  64. package/distilled/templates/delegates/researcher-features.md +30 -30
  65. package/distilled/templates/delegates/researcher-pitfalls.md +30 -30
  66. package/distilled/templates/delegates/researcher-stack.md +30 -30
  67. package/distilled/templates/delegates/researcher-synthesizer.md +31 -31
  68. package/distilled/templates/research/architecture.md +57 -57
  69. package/distilled/templates/research/features.md +23 -23
  70. package/distilled/templates/research/pitfalls.md +46 -46
  71. package/distilled/templates/research/stack.md +45 -45
  72. package/distilled/templates/research/summary.md +67 -67
  73. package/distilled/templates/roadmap.md +74 -62
  74. package/distilled/templates/spec.md +110 -110
  75. package/distilled/workflows/audit-milestone.md +303 -271
  76. package/distilled/workflows/complete-milestone.md +349 -332
  77. package/distilled/workflows/execute.md +457 -450
  78. package/distilled/workflows/map-codebase.md +253 -253
  79. package/distilled/workflows/new-milestone.md +242 -238
  80. package/distilled/workflows/new-project.md +398 -398
  81. package/distilled/workflows/pause.md +160 -156
  82. package/distilled/workflows/plan-milestone-gaps.md +183 -183
  83. package/distilled/workflows/plan.md +454 -448
  84. package/distilled/workflows/progress.md +227 -223
  85. package/distilled/workflows/quick.md +351 -347
  86. package/distilled/workflows/resume.md +220 -212
  87. package/distilled/workflows/verify-work.md +260 -260
  88. package/distilled/workflows/verify.md +431 -429
  89. package/docs/BROWNFIELD-PROOF.md +95 -95
  90. package/docs/RUNTIME-SUPPORT.md +80 -69
  91. package/docs/USER-GUIDE.md +394 -386
  92. package/docs/VERIFICATION-DISCIPLINE.md +59 -59
  93. package/docs/claude/context-monitor.md +98 -98
  94. package/docs/proof/consumer-node-cli/README.md +37 -37
  95. package/docs/proof/consumer-node-cli/ROADMAP.md +14 -14
  96. package/docs/proof/consumer-node-cli/SPEC.md +17 -17
  97. package/docs/proof/consumer-node-cli/brief.md +9 -9
  98. package/docs/proof/consumer-node-cli/phases/01-foundation/01-01-PLAN.md +34 -34
  99. package/docs/proof/consumer-node-cli/phases/01-foundation/01-01-SUMMARY.md +10 -10
  100. package/docs/proof/consumer-node-cli/phases/01-foundation/01-VERIFICATION.md +30 -30
  101. package/package.json +62 -61
@@ -1,20 +1,20 @@
1
- import { createRootAgentsAdapter } from './agents.mjs';
2
- import { createClaudeAdapter } from './claude.mjs';
3
- import { createCodexAdapter } from './codex.mjs';
4
- import { createOpenCodeAdapter } from './opencode.mjs';
5
-
6
- function createAdapterRegistry(context) {
7
- const agentsAdapter = createRootAgentsAdapter(context, 'agents');
8
-
9
- return {
10
- claude: createClaudeAdapter(context),
11
- opencode: createOpenCodeAdapter(context),
12
- codex: createCodexAdapter(context),
13
- agents: agentsAdapter,
14
- cursor: createRootAgentsAdapter(context, 'cursor'),
15
- copilot: createRootAgentsAdapter(context, 'copilot'),
16
- gemini: createRootAgentsAdapter(context, 'gemini'),
17
- };
18
- }
19
-
20
- export { createAdapterRegistry };
1
+ import { createRootAgentsAdapter } from './agents.mjs';
2
+ import { createClaudeAdapter } from './claude.mjs';
3
+ import { createCodexAdapter } from './codex.mjs';
4
+ import { createOpenCodeAdapter } from './opencode.mjs';
5
+
6
+ function createAdapterRegistry(context) {
7
+ const agentsAdapter = createRootAgentsAdapter(context, 'agents');
8
+
9
+ return {
10
+ claude: createClaudeAdapter(context),
11
+ opencode: createOpenCodeAdapter(context),
12
+ codex: createCodexAdapter(context),
13
+ agents: agentsAdapter,
14
+ cursor: createRootAgentsAdapter(context, 'cursor'),
15
+ copilot: createRootAgentsAdapter(context, 'copilot'),
16
+ gemini: createRootAgentsAdapter(context, 'gemini'),
17
+ };
18
+ }
19
+
20
+ export { createAdapterRegistry };
@@ -1,278 +1,280 @@
1
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
2
- import os from 'os';
3
- import { join } from 'path';
4
- import {
5
- PLAN_CHECK_DIMENSIONS,
6
- MAX_CHECKER_CYCLES,
7
- CHECKER_STATUSES,
8
- } from '../lib/plan-constants.mjs';
9
-
10
- function expandHome(filePath) {
11
- if (!filePath) return filePath;
12
- if (filePath.startsWith('~/')) {
13
- return join(os.homedir(), filePath.slice(2));
14
- }
15
- return filePath;
16
- }
17
-
18
- function parseJsonc(content) {
19
- if (!content) return {};
20
- if (content.charCodeAt(0) === 0xFEFF) {
21
- content = content.slice(1);
22
- }
23
-
24
- let result = '';
25
- let inString = false;
26
- let i = 0;
27
-
28
- while (i < content.length) {
29
- const char = content[i];
30
- const next = content[i + 1];
31
-
32
- if (inString) {
33
- result += char;
34
- if (char === '\\' && i + 1 < content.length) {
35
- result += next;
36
- i += 2;
37
- continue;
38
- }
39
- if (char === '"') {
40
- inString = false;
41
- }
42
- i++;
43
- continue;
44
- }
45
-
46
- if (char === '"') {
47
- inString = true;
48
- result += char;
49
- i++;
50
- continue;
51
- }
52
-
53
- if (char === '/' && next === '/') {
54
- while (i < content.length && content[i] !== '\n') {
55
- i++;
56
- }
57
- continue;
58
- }
59
-
60
- if (char === '/' && next === '*') {
61
- i += 2;
62
- while (i < content.length - 1 && !(content[i] === '*' && content[i + 1] === '/')) {
63
- i++;
64
- }
65
- if (i < content.length - 1) i += 2;
66
- continue;
67
- }
68
-
69
- result += char;
70
- i++;
71
- }
72
-
73
- return JSON.parse(result.replace(/,(\s*[}\]])/g, '$1'));
74
- }
75
-
76
- function readOpenCodeConfig(configPath) {
77
- if (!configPath || !existsSync(configPath)) return null;
78
- try {
79
- return parseJsonc(readFileSync(configPath, 'utf-8'));
80
- } catch {
81
- return null;
82
- }
83
- }
84
-
85
- function getOpenCodeConfigPaths(cwd) {
86
- const globalConfig = join(os.homedir(), '.config', 'opencode', 'opencode.json');
87
- const customConfig = process.env.OPENCODE_CONFIG
88
- ? expandHome(process.env.OPENCODE_CONFIG)
89
- : null;
90
- const projectConfig = join(cwd, 'opencode.json');
91
-
92
- return [globalConfig, customConfig, projectConfig].filter(Boolean);
93
- }
94
-
95
- function detectOpenCodeConfiguredModel(cwd) {
96
- let configuredModel = null;
97
-
98
- for (const configPath of getOpenCodeConfigPaths(cwd)) {
99
- const config = readOpenCodeConfig(configPath);
100
- if (config && typeof config.model === 'string' && config.model.includes('/')) {
101
- configuredModel = config.model.trim();
102
- }
103
- }
104
-
105
- if (process.env.OPENCODE_CONFIG_CONTENT) {
106
- try {
107
- const inlineConfig = parseJsonc(process.env.OPENCODE_CONFIG_CONTENT);
108
- if (typeof inlineConfig.model === 'string' && inlineConfig.model.includes('/')) {
109
- configuredModel = inlineConfig.model.trim();
110
- }
111
- } catch {
112
- // Ignore malformed inline config and keep the best file-based result.
113
- }
114
- }
115
-
116
- return configuredModel;
117
- }
118
-
119
- function renderOpenCodeApproachExplorer(delegateContent, modelId = null) {
120
- const modelLine = modelId ? `model: ${modelId}\n` : '';
121
- return `---
122
- description: Explores implementation approaches for a phase and aligns with the user through structured questioning before planning begins.
123
- mode: subagent
124
- ${modelLine}tools:
125
- bash: false
126
- ---
127
-
128
- ${delegateContent.trim()}
129
- `;
130
- }
131
-
132
- function renderOpenCodePlanChecker(delegateContent, modelId = null) {
133
- const modelLine = modelId ? `model: ${modelId}\n` : '';
134
- return `---
135
- description: Fresh-context plan checker for GSDD plan drafts. Review-only; never edits plans directly.
136
- mode: subagent
137
- hidden: true
138
- ${modelLine}tools:
139
- write: false
140
- edit: false
141
- bash: false
142
- ---
143
-
144
- ${delegateContent.trim()}
145
- `;
146
- }
147
-
148
- function renderOpenCodePlanCommand() {
149
- return `---
150
- description: OpenCode-native phase planning with fresh-context plan checking for GSDD
151
- subtask: false
152
- ---
153
-
154
- You are the OpenCode-native \`/gsdd-plan\` command for GSDD phase planning.
155
-
156
- Portable contract:
157
- - Read \`.agents/skills/gsdd-plan/SKILL.md\` first. That file remains the canonical vendor-agnostic plan contract.
158
- - Keep the portable contract honest: it defines the workflow, but it does not by itself prove fresh-context checker orchestration across runtimes.
159
- - If the portable skill says plan is still a stub, treat that as a portability-status warning for the generic surface, not as a stop signal for this OpenCode-native adapter path.
160
-
161
- Native OpenCode adapter rule:
162
- - This command is the canonical OpenCode-native entry surface for \`/gsdd-plan\`.
163
- - Stay in the primary conversation context for orchestration so the checker can run as its own fresh-context subagent.
164
- - Use the native \`gsdd-plan-checker\` subagent for review-only checking.
165
- - Do NOT claim that other runtimes have the same behavior unless their own adapters explicitly implement and prove it.
166
-
167
- Execution flow:
168
- 1. Read \`.planning/SPEC.md\`, \`.planning/ROADMAP.md\`, \`.planning/config.json\`, relevant phase research, and any existing phase plan files.
169
- 2. Resolve the target phase from the command arguments. If no phase is provided, choose the first roadmap phase that is not complete.
170
- 3. **Approach exploration** (before planning):
171
- a. Check \`.planning/config.json\` for \`workflow.discuss\`. If \`false\` or missing, skip to step 4 and report \`reduced_alignment\` in the summary.
172
- b. Check if \`{phase_dir}/{padded_phase}-APPROACH.md\` exists. If it does, offer the user: "Use existing" / "Update it" / "View it". If "Use existing", load decisions and skip to step 4.
173
- c. If no APPROACH.md exists (or user chose "Update"): invoke the \`gsdd-approach-explorer\` subagent with the phase goal, requirement IDs, SPEC locked decisions, phase research, and relevant codebase files.
174
- d. The explorer runs a GSD-style interactive conversation with the user (gray areas, research, deep-dive questions, assumptions) and writes APPROACH.md.
175
- e. Load APPROACH.md decisions as locked constraints alongside SPEC.md decisions.
176
- 4. Produce the initial phase plan according to \`.agents/skills/gsdd-plan/SKILL.md\`. Pass APPROACH.md decisions (if any) as locked constraints to the planner.
177
- 5. If \`.planning/config.json\` has \`workflow.planCheck: false\`, stop after planner self-check and explicitly report reduced assurance.
178
- 6. If \`workflow.planCheck: true\`, invoke the hidden \`gsdd-plan-checker\` subagent with fresh context.
179
- 7. Pass only explicit inputs to the checker:
180
- - target phase goal and requirement IDs
181
- - relevant locked decisions / deferred items from \`.planning/SPEC.md\`
182
- - approach decisions from \`.planning/phases/*-APPROACH.md\` (if exists)
183
- - relevant phase research file(s)
184
- - produced \`.planning/phases/*-PLAN.md\` file(s)
185
- 8. Require the checker to return a single JSON object with this shape:
186
- {
187
- "status": "passed",
188
- "summary": "One sentence overall assessment",
189
- "issues": [
190
- {
191
- "dimension": "${PLAN_CHECK_DIMENSIONS.join(' | ')}",
192
- "severity": "blocker | warning",
193
- "description": "What is wrong",
194
- "plan": "01-PLAN",
195
- "task": "1-02",
196
- "fix_hint": "Specific revision instruction"
197
- }
198
- ]
199
- }
200
- Status must be either "${CHECKER_STATUSES[0]}" or "${CHECKER_STATUSES[1]}".
201
- 9. If the checker returns \`passed\`, finish and summarize.
202
- 10. If the checker returns \`issues_found\`, revise the existing plan files only where needed, then run the checker again.
203
- 11. Maximum ${MAX_CHECKER_CYCLES} checker cycles total. If blockers remain after cycle ${MAX_CHECKER_CYCLES}, stop and escalate to the user instead of pretending the plan is ready.
204
-
205
- Return a concise orchestration summary:
206
- - target phase
207
- - whether approach exploration ran (and alignment level: full | reduced_alignment | skipped)
208
- - whether native plan checking ran
209
- - checker cycle count
210
- - final result: passed | reduced_assurance | escalated
211
-
212
- Never return raw checker JSON without summarizing it.
213
- `;
214
- }
215
-
216
- function createOpenCodeAdapter({
217
- cwd,
218
- workflows,
219
- renderOpenCodeCommandContent,
220
- getDelegateContent,
221
- getRuntimeModelOverride,
222
- loadProjectModelConfig,
223
- }) {
224
- const commandsDir = join(cwd, '.opencode', 'commands');
225
- const agentsDir = join(cwd, '.opencode', 'agents');
226
-
227
- return {
228
- id: 'opencode',
229
- name: 'opencode',
230
- kind: 'native_capable',
231
- subagentFiles: [
232
- '.opencode/agents/gsdd-plan-checker.md',
233
- '.opencode/agents/gsdd-approach-explorer.md',
234
- ],
235
- detect() {
236
- return existsSync(join(cwd, '.opencode'));
237
- },
238
- isInstalled() {
239
- return existsSync(commandsDir) || existsSync(agentsDir);
240
- },
241
- generate() {
242
- const config = loadProjectModelConfig(cwd);
243
- const checkerModelId = getRuntimeModelOverride(config, 'opencode', 'plan-checker');
244
- const explorerModelId = getRuntimeModelOverride(config, 'opencode', 'approach-explorer');
245
- mkdirSync(commandsDir, { recursive: true });
246
- for (const workflow of workflows) {
247
- const content = workflow.name === 'gsdd-plan'
248
- ? renderOpenCodePlanCommand()
249
- : renderOpenCodeCommandContent(workflow);
250
- writeFileSync(
251
- join(commandsDir, `${workflow.name}.md`),
252
- content
253
- );
254
- }
255
-
256
- mkdirSync(agentsDir, { recursive: true });
257
- writeFileSync(
258
- join(agentsDir, 'gsdd-plan-checker.md'),
259
- renderOpenCodePlanChecker(getDelegateContent('plan-checker.md'), checkerModelId)
260
- );
261
- writeFileSync(
262
- join(agentsDir, 'gsdd-approach-explorer.md'),
263
- renderOpenCodeApproachExplorer(getDelegateContent('approach-explorer.md'), explorerModelId)
264
- );
265
- },
266
- summary(action) {
267
- return `${action} OpenCode slash commands (.opencode/commands/gsdd-*.md) and native agents (.opencode/agents/gsdd-*.md)`;
268
- },
269
- };
270
- }
271
-
272
- export {
273
- createOpenCodeAdapter,
274
- detectOpenCodeConfiguredModel,
275
- renderOpenCodeApproachExplorer,
276
- renderOpenCodePlanChecker,
277
- renderOpenCodePlanCommand,
278
- };
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
2
+ import os from 'os';
3
+ import { join } from 'path';
4
+ import {
5
+ PLAN_CHECK_DIMENSIONS,
6
+ MAX_CHECKER_CYCLES,
7
+ CHECKER_STATUSES,
8
+ } from '../lib/plan-constants.mjs';
9
+
10
+ function expandHome(filePath) {
11
+ if (!filePath) return filePath;
12
+ if (filePath.startsWith('~/')) {
13
+ return join(os.homedir(), filePath.slice(2));
14
+ }
15
+ return filePath;
16
+ }
17
+
18
+ function parseJsonc(content) {
19
+ if (!content) return {};
20
+ if (content.charCodeAt(0) === 0xFEFF) {
21
+ content = content.slice(1);
22
+ }
23
+
24
+ let result = '';
25
+ let inString = false;
26
+ let i = 0;
27
+
28
+ while (i < content.length) {
29
+ const char = content[i];
30
+ const next = content[i + 1];
31
+
32
+ if (inString) {
33
+ result += char;
34
+ if (char === '\\' && i + 1 < content.length) {
35
+ result += next;
36
+ i += 2;
37
+ continue;
38
+ }
39
+ if (char === '"') {
40
+ inString = false;
41
+ }
42
+ i++;
43
+ continue;
44
+ }
45
+
46
+ if (char === '"') {
47
+ inString = true;
48
+ result += char;
49
+ i++;
50
+ continue;
51
+ }
52
+
53
+ if (char === '/' && next === '/') {
54
+ while (i < content.length && content[i] !== '\n') {
55
+ i++;
56
+ }
57
+ continue;
58
+ }
59
+
60
+ if (char === '/' && next === '*') {
61
+ i += 2;
62
+ while (i < content.length - 1 && !(content[i] === '*' && content[i + 1] === '/')) {
63
+ i++;
64
+ }
65
+ if (i < content.length - 1) i += 2;
66
+ continue;
67
+ }
68
+
69
+ result += char;
70
+ i++;
71
+ }
72
+
73
+ return JSON.parse(result.replace(/,(\s*[}\]])/g, '$1'));
74
+ }
75
+
76
+ function readOpenCodeConfig(configPath) {
77
+ if (!configPath || !existsSync(configPath)) return null;
78
+ try {
79
+ return parseJsonc(readFileSync(configPath, 'utf-8'));
80
+ } catch {
81
+ return null;
82
+ }
83
+ }
84
+
85
+ function getOpenCodeConfigPaths(cwd) {
86
+ const globalConfig = join(os.homedir(), '.config', 'opencode', 'opencode.json');
87
+ const customConfig = process.env.OPENCODE_CONFIG
88
+ ? expandHome(process.env.OPENCODE_CONFIG)
89
+ : null;
90
+ const projectConfig = join(cwd, 'opencode.json');
91
+
92
+ return [globalConfig, customConfig, projectConfig].filter(Boolean);
93
+ }
94
+
95
+ function detectOpenCodeConfiguredModel(cwd) {
96
+ let configuredModel = null;
97
+
98
+ for (const configPath of getOpenCodeConfigPaths(cwd)) {
99
+ const config = readOpenCodeConfig(configPath);
100
+ if (config && typeof config.model === 'string' && config.model.includes('/')) {
101
+ configuredModel = config.model.trim();
102
+ }
103
+ }
104
+
105
+ if (process.env.OPENCODE_CONFIG_CONTENT) {
106
+ try {
107
+ const inlineConfig = parseJsonc(process.env.OPENCODE_CONFIG_CONTENT);
108
+ if (typeof inlineConfig.model === 'string' && inlineConfig.model.includes('/')) {
109
+ configuredModel = inlineConfig.model.trim();
110
+ }
111
+ } catch {
112
+ // Ignore malformed inline config and keep the best file-based result.
113
+ }
114
+ }
115
+
116
+ return configuredModel;
117
+ }
118
+
119
+ function renderOpenCodeApproachExplorer(delegateContent, modelId = null) {
120
+ const modelLine = modelId ? `model: ${modelId}\n` : '';
121
+ return `---
122
+ description: Explores implementation approaches for a phase and aligns with the user through structured questioning before planning begins.
123
+ mode: subagent
124
+ ${modelLine}tools:
125
+ bash: false
126
+ ---
127
+
128
+ ${delegateContent.trim()}
129
+ `;
130
+ }
131
+
132
+ function renderOpenCodePlanChecker(delegateContent, modelId = null) {
133
+ const modelLine = modelId ? `model: ${modelId}\n` : '';
134
+ return `---
135
+ description: Fresh-context plan checker for GSDD plan drafts. Review-only; never edits plans directly.
136
+ mode: subagent
137
+ hidden: true
138
+ ${modelLine}tools:
139
+ write: false
140
+ edit: false
141
+ bash: false
142
+ ---
143
+
144
+ ${delegateContent.trim()}
145
+ `;
146
+ }
147
+
148
+ function renderOpenCodePlanCommand() {
149
+ return `---
150
+ description: OpenCode-native phase planning with fresh-context plan checking for GSDD
151
+ subtask: false
152
+ ---
153
+
154
+ You are the OpenCode-native \`/gsdd-plan\` command for GSDD phase planning.
155
+
156
+ Portable contract:
157
+ - Read \`.agents/skills/gsdd-plan/SKILL.md\` first. That file remains the canonical vendor-agnostic plan contract.
158
+ - Keep the portable contract honest: it defines the workflow, but it does not by itself prove fresh-context checker orchestration across runtimes.
159
+ - If the portable skill says plan is still a stub, treat that as a portability-status warning for the generic surface, not as a stop signal for this OpenCode-native adapter path.
160
+
161
+ Native OpenCode adapter rule:
162
+ - This command is the canonical OpenCode-native entry surface for \`/gsdd-plan\`.
163
+ - Stay in the primary conversation context for orchestration so the checker can run as its own fresh-context subagent.
164
+ - Use the native \`gsdd-plan-checker\` subagent for review-only checking.
165
+ - Do NOT claim that other runtimes have the same behavior unless their own adapters explicitly implement and prove it.
166
+
167
+ Execution flow:
168
+ 1. Read \`.planning/SPEC.md\`, \`.planning/ROADMAP.md\`, \`.planning/config.json\`, relevant phase research, and any existing phase plan files.
169
+ 2. Resolve the target phase from the command arguments. If no phase is provided, choose the first roadmap phase that is not complete.
170
+ 3. **Approach exploration** (before planning):
171
+ a. Check \`.planning/config.json\` for \`workflow.discuss\`. If \`false\` or missing, skip to step 4 and report \`reduced_alignment\` in the summary.
172
+ b. Check if \`{phase_dir}/{padded_phase}-APPROACH.md\` exists. If it does, offer the user: "Use existing" / "Update it" / "View it". If "Use existing", load decisions, then validate the alignment proof before step 4; proofless or invalid existing APPROACH.md must be updated, not silently trusted.
173
+ c. If no APPROACH.md exists (or user chose "Update"): invoke the \`gsdd-approach-explorer\` subagent with the phase goal, requirement IDs, project config from \`.planning/config.json\` (especially \`workflow.discuss\`), SPEC locked decisions, phase research, and relevant codebase files.
174
+ d. The explorer runs a GSD-style interactive conversation with the user (gray areas, research, deep-dive questions, assumptions) and writes APPROACH.md.
175
+ e. Before planning, confirm APPROACH.md records all canonical proof fields: \`alignment_status\`, \`alignment_method\`, \`user_confirmed_at\`, \`explicit_skip_approved\`, \`skip_scope\`, \`skip_rationale\`, and \`confirmed_decisions\`. For \`alignment_status: user_confirmed\`, \`confirmed_decisions\` must name the locked decisions and skip fields may be \`false\`/\`N/A\`; for \`alignment_status: approved_skip\`, \`explicit_skip_approved: true\`, \`skip_scope\`, and \`skip_rationale\` must be substantive. Agent-only "No questions needed" is not valid proof under \`workflow.discuss: true\`.
176
+ f. Load APPROACH.md decisions as locked constraints alongside SPEC.md decisions.
177
+ 4. Produce the initial phase plan according to \`.agents/skills/gsdd-plan/SKILL.md\`. Pass APPROACH.md decisions (if any) as locked constraints to the planner.
178
+ 5. If \`.planning/config.json\` has \`workflow.planCheck: false\`, stop after planner self-check and explicitly report reduced assurance. This only skips the independent checker; it does not skip the step 3 alignment-proof gate when \`workflow.discuss: true\`.
179
+ 6. If \`workflow.planCheck: true\`, invoke the hidden \`gsdd-plan-checker\` subagent with fresh context.
180
+ 7. Pass only explicit inputs to the checker:
181
+ - target phase goal and requirement IDs
182
+ - relevant locked decisions / deferred items from \`.planning/SPEC.md\`
183
+ - project config from \`.planning/config.json\`, especially \`workflow.discuss\` and \`workflow.planCheck\`
184
+ - approach decisions from \`.planning/phases/*-APPROACH.md\` (if exists)
185
+ - relevant phase research file(s)
186
+ - produced \`.planning/phases/*-PLAN.md\` file(s)
187
+ 8. Require the checker to return a single JSON object with this shape:
188
+ {
189
+ "status": "issues_found",
190
+ "summary": "One sentence overall assessment",
191
+ "issues": [
192
+ {
193
+ "dimension": "${PLAN_CHECK_DIMENSIONS.join(' | ')}",
194
+ "severity": "blocker | warning",
195
+ "description": "What is wrong",
196
+ "plan": "01-PLAN",
197
+ "task": "1-02",
198
+ "fix_hint": "Specific revision instruction"
199
+ }
200
+ ]
201
+ }
202
+ Status must be either "${CHECKER_STATUSES[0]}" or "${CHECKER_STATUSES[1]}". Use "passed" only when "issues": []; any blocker or warning must use "issues_found".
203
+ 9. If the checker returns \`passed\`, finish and summarize.
204
+ 10. If the checker returns \`issues_found\`, revise the existing plan files only where needed, then run the checker again.
205
+ 11. Maximum ${MAX_CHECKER_CYCLES} checker cycles total. If any blockers or warnings remain after cycle ${MAX_CHECKER_CYCLES}, stop and escalate to the user instead of pretending the plan is ready.
206
+
207
+ Return a concise orchestration summary:
208
+ - target phase
209
+ - whether approach exploration ran (and alignment level: full | reduced_alignment | skipped)
210
+ - whether native plan checking ran
211
+ - checker cycle count
212
+ - final result: passed | reduced_assurance | escalated
213
+
214
+ Never return raw checker JSON without summarizing it.
215
+ `;
216
+ }
217
+
218
+ function createOpenCodeAdapter({
219
+ cwd,
220
+ workflows,
221
+ renderOpenCodeCommandContent,
222
+ getDelegateContent,
223
+ getRuntimeModelOverride,
224
+ loadProjectModelConfig,
225
+ }) {
226
+ const commandsDir = join(cwd, '.opencode', 'commands');
227
+ const agentsDir = join(cwd, '.opencode', 'agents');
228
+
229
+ return {
230
+ id: 'opencode',
231
+ name: 'opencode',
232
+ kind: 'native_capable',
233
+ subagentFiles: [
234
+ '.opencode/agents/gsdd-plan-checker.md',
235
+ '.opencode/agents/gsdd-approach-explorer.md',
236
+ ],
237
+ detect() {
238
+ return existsSync(join(cwd, '.opencode'));
239
+ },
240
+ isInstalled() {
241
+ return existsSync(commandsDir) || existsSync(agentsDir);
242
+ },
243
+ generate() {
244
+ const config = loadProjectModelConfig(cwd);
245
+ const checkerModelId = getRuntimeModelOverride(config, 'opencode', 'plan-checker');
246
+ const explorerModelId = getRuntimeModelOverride(config, 'opencode', 'approach-explorer');
247
+ mkdirSync(commandsDir, { recursive: true });
248
+ for (const workflow of workflows) {
249
+ const content = workflow.name === 'gsdd-plan'
250
+ ? renderOpenCodePlanCommand()
251
+ : renderOpenCodeCommandContent(workflow);
252
+ writeFileSync(
253
+ join(commandsDir, `${workflow.name}.md`),
254
+ content
255
+ );
256
+ }
257
+
258
+ mkdirSync(agentsDir, { recursive: true });
259
+ writeFileSync(
260
+ join(agentsDir, 'gsdd-plan-checker.md'),
261
+ renderOpenCodePlanChecker(getDelegateContent('plan-checker.md'), checkerModelId)
262
+ );
263
+ writeFileSync(
264
+ join(agentsDir, 'gsdd-approach-explorer.md'),
265
+ renderOpenCodeApproachExplorer(getDelegateContent('approach-explorer.md'), explorerModelId)
266
+ );
267
+ },
268
+ summary(action) {
269
+ return `${action} OpenCode slash commands (.opencode/commands/gsdd-*.md) and native agents (.opencode/agents/gsdd-*.md)`;
270
+ },
271
+ };
272
+ }
273
+
274
+ export {
275
+ createOpenCodeAdapter,
276
+ detectOpenCodeConfiguredModel,
277
+ renderOpenCodeApproachExplorer,
278
+ renderOpenCodePlanChecker,
279
+ renderOpenCodePlanCommand,
280
+ };