gsdd-cli 0.18.5 → 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 +609 -608
  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 +186 -144
  24. package/bin/lib/health-truth.mjs +181 -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 +191 -190
  29. package/bin/lib/init.mjs +17 -17
  30. package/bin/lib/lifecycle-preflight.mjs +347 -325
  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 +360 -358
  35. package/bin/lib/plan-constants.mjs +30 -30
  36. package/bin/lib/provenance.mjs +109 -106
  37. package/bin/lib/rendering.mjs +115 -83
  38. package/bin/lib/runtime-freshness.mjs +214 -214
  39. package/bin/lib/templates.mjs +225 -224
  40. package/bin/lib/workspace-root.mjs +2 -1
  41. package/distilled/DESIGN.md +2333 -2323
  42. package/distilled/EVIDENCE-INDEX.md +394 -392
  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 +80 -69
  90. package/docs/USER-GUIDE.md +394 -386
  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
@@ -1,158 +1,159 @@
1
- import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync, cpSync } from 'fs';
1
+ import { chmodSync, existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync, cpSync } from 'fs';
2
2
  import { dirname, join, isAbsolute } from 'path';
3
3
  import { buildPlanningCliHelperEntries, renderSkillContent } from './rendering.mjs';
4
4
  import { buildManifest, readManifest, writeManifest } from './manifest.mjs';
5
- import { parseFlagValue, parseToolsFlag, parseAutoFlag } from './cli-utils.mjs';
6
- import { buildDefaultConfig, COST_PROFILES, RIGOR_PROFILES } from './models.mjs';
7
- import { installProjectTemplates, refreshTemplates } from './templates.mjs';
8
- import {
9
- detectPlatforms,
10
- getAdaptersToUpdate,
11
- getPostInitRoutingLines,
12
- normalizeRequestedTools,
13
- resolveAdapters,
14
- resolveInteractiveInitSession,
15
- } from './init-runtime.mjs';
16
- import { createInitPromptApi } from './init-prompts.mjs';
17
-
18
- function validateKindContract(adapter, cwd) {
19
- if (!adapter.subagentFiles) return;
20
- if (adapter.kind === 'native_capable') {
21
- const missing = adapter.subagentFiles
22
- .map(f => join(cwd, f))
23
- .filter(p => !existsSync(p));
24
- if (missing.length > 0) {
25
- console.warn(
26
- `[WARN] ${adapter.name} adapter (kind=native_capable) missing expected subagent files:\n` +
27
- missing.map(p => ` - ${p}`).join('\n')
28
- );
29
- }
30
- } else if (adapter.kind === 'governance_only') {
31
- const unexpected = adapter.subagentFiles
32
- .map(f => join(cwd, f))
33
- .filter(p => existsSync(p));
34
- if (unexpected.length > 0) {
35
- console.warn(
36
- `[WARN] ${adapter.name} adapter (kind=governance_only) unexpectedly generated subagent files:\n` +
37
- unexpected.map(p => ` - ${p}`).join('\n')
38
- );
39
- }
40
- }
41
- }
42
-
43
- export function createCmdInit(ctx) {
44
- return async function cmdInit(...initArgs) {
45
- console.log('gsdd init - setting up GSDD workflow\n');
46
-
47
- const isAuto = parseAutoFlag(initArgs);
48
- const toolsFlag = parseFlagValue(initArgs, '--tools');
49
- const briefFlag = parseFlagValue(initArgs, '--brief');
50
- let briefSource = null;
51
-
52
- if (toolsFlag.invalid) {
53
- console.error('ERROR: --tools requires a value. Example: gsdd init --tools claude');
54
- process.exitCode = 1;
55
- return;
56
- }
57
-
58
- if (briefFlag.invalid) {
59
- console.error('ERROR: --brief requires a file path. Example: gsdd init --brief project-idea.md');
60
- process.exitCode = 1;
61
- return;
62
- }
63
-
64
- if (briefFlag.value) {
65
- briefSource = isAbsolute(briefFlag.value) ? briefFlag.value : join(ctx.cwd, briefFlag.value);
66
- if (!existsSync(briefSource)) {
67
- console.error(`ERROR: Brief file not found: ${briefFlag.value}`);
68
- process.exitCode = 1;
69
- return;
70
- }
71
- }
72
-
73
- const parsedTools = parseToolsFlag(initArgs);
74
- if (isAuto && parsedTools.length === 0) {
75
- console.error('ERROR: --auto requires --tools <platform>. Example: gsdd init --auto --tools claude');
76
- process.exitCode = 1;
77
- return;
78
- }
79
-
80
- const promptApi = ctx.initPromptApi || createInitPromptApi();
81
- const interactiveSession = await resolveInteractiveInitSession({
82
- ctx,
83
- promptApi,
84
- parsedTools,
85
- isAuto,
86
- });
87
-
88
- const existed = existsSync(ctx.planningDir);
89
- mkdirSync(join(ctx.planningDir, 'phases'), { recursive: true });
90
- mkdirSync(join(ctx.planningDir, 'research'), { recursive: true });
91
- console.log(existed
92
- ? ' - .planning/ already exists (ensured subdirectories)'
93
- : ' - created .planning/ directory structure');
94
-
95
- installProjectTemplates(ctx);
96
- await ensureConfig({
97
- cwd: ctx.cwd,
98
- planningDir: ctx.planningDir,
99
- isAuto,
100
- promptApi,
101
- preselectedConfig: interactiveSession.config,
102
- });
103
-
104
- if (briefSource) {
105
- cpSync(briefSource, join(ctx.planningDir, 'PROJECT_BRIEF.md'));
106
- console.log(' - copied project brief to .planning/PROJECT_BRIEF.md');
107
- }
108
-
5
+ import { parseFlagValue, parseToolsFlag, parseAutoFlag } from './cli-utils.mjs';
6
+ import { buildDefaultConfig, COST_PROFILES, RIGOR_PROFILES } from './models.mjs';
7
+ import { installProjectTemplates, refreshTemplates } from './templates.mjs';
8
+ import {
9
+ detectPlatforms,
10
+ getAdaptersToUpdate,
11
+ getPostInitRoutingLines,
12
+ normalizeRequestedTools,
13
+ resolveAdapters,
14
+ resolveInteractiveInitSession,
15
+ } from './init-runtime.mjs';
16
+ import { createInitPromptApi } from './init-prompts.mjs';
17
+
18
+ function validateKindContract(adapter, cwd) {
19
+ if (!adapter.subagentFiles) return;
20
+ if (adapter.kind === 'native_capable') {
21
+ const missing = adapter.subagentFiles
22
+ .map(f => join(cwd, f))
23
+ .filter(p => !existsSync(p));
24
+ if (missing.length > 0) {
25
+ console.warn(
26
+ `[WARN] ${adapter.name} adapter (kind=native_capable) missing expected subagent files:\n` +
27
+ missing.map(p => ` - ${p}`).join('\n')
28
+ );
29
+ }
30
+ } else if (adapter.kind === 'governance_only') {
31
+ const unexpected = adapter.subagentFiles
32
+ .map(f => join(cwd, f))
33
+ .filter(p => existsSync(p));
34
+ if (unexpected.length > 0) {
35
+ console.warn(
36
+ `[WARN] ${adapter.name} adapter (kind=governance_only) unexpectedly generated subagent files:\n` +
37
+ unexpected.map(p => ` - ${p}`).join('\n')
38
+ );
39
+ }
40
+ }
41
+ }
42
+
43
+ export function createCmdInit(ctx) {
44
+ return async function cmdInit(...initArgs) {
45
+ console.log('gsdd init - setting up GSDD workflow\n');
46
+
47
+ const isAuto = parseAutoFlag(initArgs);
48
+ const toolsFlag = parseFlagValue(initArgs, '--tools');
49
+ const briefFlag = parseFlagValue(initArgs, '--brief');
50
+ let briefSource = null;
51
+
52
+ if (toolsFlag.invalid) {
53
+ console.error('ERROR: --tools requires a value. Example: npx -y gsdd-cli init --tools claude');
54
+ process.exitCode = 1;
55
+ return;
56
+ }
57
+
58
+ if (briefFlag.invalid) {
59
+ console.error('ERROR: --brief requires a file path. Example: npx -y gsdd-cli init --brief project-idea.md');
60
+ process.exitCode = 1;
61
+ return;
62
+ }
63
+
64
+ if (briefFlag.value) {
65
+ briefSource = isAbsolute(briefFlag.value) ? briefFlag.value : join(ctx.cwd, briefFlag.value);
66
+ if (!existsSync(briefSource)) {
67
+ console.error(`ERROR: Brief file not found: ${briefFlag.value}`);
68
+ process.exitCode = 1;
69
+ return;
70
+ }
71
+ }
72
+
73
+ const parsedTools = parseToolsFlag(initArgs);
74
+ if (isAuto && parsedTools.length === 0) {
75
+ console.error('ERROR: --auto requires --tools <platform>. Example: npx -y gsdd-cli init --auto --tools claude');
76
+ process.exitCode = 1;
77
+ return;
78
+ }
79
+
80
+ const promptApi = ctx.initPromptApi || createInitPromptApi();
81
+ const interactiveSession = await resolveInteractiveInitSession({
82
+ ctx,
83
+ promptApi,
84
+ parsedTools,
85
+ isAuto,
86
+ });
87
+
88
+ const existed = existsSync(ctx.planningDir);
89
+ mkdirSync(join(ctx.planningDir, 'phases'), { recursive: true });
90
+ mkdirSync(join(ctx.planningDir, 'research'), { recursive: true });
91
+ console.log(existed
92
+ ? ' - .planning/ already exists (ensured subdirectories)'
93
+ : ' - created .planning/ directory structure');
94
+
95
+ installProjectTemplates(ctx);
96
+ await ensureConfig({
97
+ cwd: ctx.cwd,
98
+ planningDir: ctx.planningDir,
99
+ isAuto,
100
+ promptApi,
101
+ preselectedConfig: interactiveSession.config,
102
+ });
103
+
104
+ if (briefSource) {
105
+ cpSync(briefSource, join(ctx.planningDir, 'PROJECT_BRIEF.md'));
106
+ console.log(' - copied project brief to .planning/PROJECT_BRIEF.md');
107
+ }
108
+
109
109
  generateOpenStandardSkills(ctx.cwd, ctx.workflows);
110
110
  console.log(' - generated open-standard skills (.agents/skills/gsdd-*)');
111
111
 
112
112
  generatePlanningCliHelpers(ctx);
113
113
  console.log(' - generated local workflow helpers (.planning/bin/gsdd*)');
114
-
115
- for (const adapter of resolveAdapters(ctx.adapters, interactiveSession.adapterTargets)) {
116
- adapter.generate();
117
- validateKindContract(adapter, ctx.cwd);
118
- console.log(` - ${adapter.summary('generated')}`);
119
- }
120
-
121
- const manifest = buildManifest({ planningDir: ctx.planningDir, frameworkVersion: ctx.frameworkVersion });
122
- writeManifest(ctx.planningDir, manifest);
123
- console.log(' - wrote generation manifest');
124
-
125
- console.log('\nGSDD initialized.');
114
+
115
+ for (const adapter of resolveAdapters(ctx.adapters, interactiveSession.adapterTargets)) {
116
+ adapter.generate();
117
+ validateKindContract(adapter, ctx.cwd);
118
+ console.log(` - ${adapter.summary('generated')}`);
119
+ }
120
+
121
+ const manifest = buildManifest({ planningDir: ctx.planningDir, frameworkVersion: ctx.frameworkVersion });
122
+ writeManifest(ctx.planningDir, manifest);
123
+ console.log(' - wrote generation manifest');
124
+
125
+ console.log('\n\x1B[1m\x1B[32m✓ GSDD initialized.\x1B[0m');
126
126
  printInitSummary(interactiveSession.config ?? buildDefaultConfig({ autoAdvance: isAuto }));
127
127
  console.log('Next: choose the starting lane that fits your repo and current scope:\n');
128
128
  printPostInitRouting(interactiveSession.selectedRuntimes);
129
+ console.log('\nSetup complete — this session will now exit.');
129
130
  };
130
131
  }
131
-
132
- export function createCmdUpdate(ctx) {
133
- return function cmdUpdate(...updateArgs) {
134
- const isDry = updateArgs.includes('--dry');
135
- const doTemplates = updateArgs.includes('--templates');
136
-
137
- console.log(`gsdd update - regenerating adapter files${isDry ? ' (dry run)' : ''}\n`);
138
-
139
- const parsedTools = parseToolsFlag(updateArgs);
140
- const requested = normalizeRequestedTools(parsedTools);
141
- const platforms = parsedTools.length > 0 ? requested.adapterTargets : detectPlatforms(ctx.adapters);
142
-
143
- let updated = false;
144
-
145
- if (doTemplates) {
146
- refreshTemplates({ ...ctx, isDry });
147
- updated = true;
148
- }
149
-
150
- if (platforms.length > 0 || existsSync(join(ctx.cwd, '.agents', 'skills'))) {
132
+
133
+ export function createCmdUpdate(ctx) {
134
+ return function cmdUpdate(...updateArgs) {
135
+ const isDry = updateArgs.includes('--dry');
136
+ const doTemplates = updateArgs.includes('--templates');
137
+
138
+ console.log(`gsdd update - regenerating adapter files${isDry ? ' (dry run)' : ''}\n`);
139
+
140
+ const parsedTools = parseToolsFlag(updateArgs);
141
+ const requested = normalizeRequestedTools(parsedTools);
142
+ const platforms = parsedTools.length > 0 ? requested.adapterTargets : detectPlatforms(ctx.adapters);
143
+
144
+ let updated = false;
145
+
146
+ if (doTemplates) {
147
+ refreshTemplates({ ...ctx, isDry });
148
+ updated = true;
149
+ }
150
+
151
+ if (platforms.length > 0 || existsSync(ctx.planningDir) || hasGeneratedOpenStandardSkills(ctx.cwd)) {
151
152
  if (isDry) {
152
153
  console.log(' - would update open-standard skills (.agents/skills/gsdd-*)');
153
154
  } else {
154
155
  generateOpenStandardSkills(ctx.cwd, ctx.workflows);
155
- console.log(' - updated open-standard skills (.agents/skills/gsdd-*)');
156
+ console.log(' - updated open-standard skills (.agents/skills/gsdd-*)');
156
157
  }
157
158
  updated = true;
158
159
  }
@@ -166,20 +167,20 @@ export function createCmdUpdate(ctx) {
166
167
  }
167
168
  updated = true;
168
169
  }
169
-
170
- for (const adapter of getAdaptersToUpdate(ctx.adapters, platforms)) {
171
- if (isDry) {
172
- console.log(` - would update ${adapter.name} adapter`);
173
- } else {
174
- adapter.generate();
175
- validateKindContract(adapter, ctx.cwd);
176
- console.log(` - ${adapter.summary('updated')}`);
177
- }
178
- updated = true;
179
- }
180
-
181
- if (!updated) {
182
- console.log(' - no adapters found to update (run `gsdd init` first)');
170
+
171
+ for (const adapter of getAdaptersToUpdate(ctx.adapters, platforms)) {
172
+ if (isDry) {
173
+ console.log(` - would update ${adapter.name} adapter`);
174
+ } else {
175
+ adapter.generate();
176
+ validateKindContract(adapter, ctx.cwd);
177
+ console.log(` - ${adapter.summary('updated')}`);
178
+ }
179
+ updated = true;
180
+ }
181
+
182
+ if (!updated) {
183
+ console.log(' - no adapters found to update (run `npx -y gsdd-cli init` first; bare `gsdd init` is equivalent only when globally installed)');
183
184
  } else if (isDry) {
184
185
  console.log('\nDry run complete. No files were written.\n');
185
186
  } else {
@@ -198,7 +199,22 @@ export function createCmdUpdate(ctx) {
198
199
  }
199
200
  };
200
201
  }
201
-
202
+
203
+ function hasGeneratedOpenStandardSkills(cwd) {
204
+ const skillsDir = join(cwd, '.agents', 'skills');
205
+ if (!existsSync(skillsDir)) return false;
206
+
207
+ try {
208
+ return readdirSync(skillsDir, { withFileTypes: true }).some((entry) =>
209
+ entry.isDirectory() &&
210
+ entry.name.startsWith('gsdd-') &&
211
+ existsSync(join(skillsDir, entry.name, 'SKILL.md'))
212
+ );
213
+ } catch {
214
+ return false;
215
+ }
216
+ }
217
+
202
218
  function generateOpenStandardSkills(cwd, workflows) {
203
219
  for (const workflow of workflows) {
204
220
  const dir = join(cwd, '.agents', 'skills', workflow.name);
@@ -246,81 +262,81 @@ function stripManifestTimestamp(manifest) {
246
262
  const { generatedAt, ...rest } = manifest;
247
263
  return rest;
248
264
  }
249
-
250
- async function ensureConfig({ cwd, planningDir, isAuto, promptApi, preselectedConfig = null }) {
251
- const configFile = join(planningDir, 'config.json');
252
- if (existsSync(configFile)) {
253
- console.log(' - .planning/config.json already exists');
254
- return;
255
- }
256
-
257
- if (preselectedConfig) {
258
- writeFileSync(configFile, JSON.stringify(preselectedConfig, null, 2));
259
- console.log(' - saved .planning/config.json (guided wizard)\n');
260
- if (!preselectedConfig.commitDocs) ensureGitignoreEntry(cwd);
261
- return;
262
- }
263
-
264
- if (isAuto) {
265
- console.log(' - auto mode: writing config.json with defaults');
266
- writeFileSync(configFile, JSON.stringify(buildDefaultConfig({ autoAdvance: true }), null, 2));
267
- console.log(' - saved .planning/config.json (auto mode - autoAdvance enabled)\n');
268
- return;
269
- }
270
-
271
- if (!process.stdin.isTTY) {
272
- console.log(' - non-interactive mode detected: writing config.json with defaults');
273
- writeFileSync(configFile, JSON.stringify(buildDefaultConfig(), null, 2));
274
- console.log(' - saved .planning/config.json (defaults - re-run gsdd init in a terminal to customize)\n');
275
- return;
276
- }
277
-
278
- const config = await promptApi.promptForConfig(cwd);
279
- writeFileSync(configFile, JSON.stringify(config, null, 2));
280
- console.log(' - saved .planning/config.json (guided wizard)\n');
281
-
282
- if (!config.commitDocs) ensureGitignoreEntry(cwd);
283
- }
284
-
285
- function ensureGitignoreEntry(cwd) {
286
- const gitignorePath = join(cwd, '.gitignore');
287
- const ignoreEntry = '\n# GSDD planning docs (local only)\n.planning/\n';
288
-
289
- if (existsSync(gitignorePath)) {
290
- const existing = readFileSync(gitignorePath, 'utf-8');
291
- if (!existing.includes('.planning/')) {
292
- writeFileSync(gitignorePath, existing + ignoreEntry);
293
- console.log(' - added .planning/ to .gitignore');
294
- }
295
- return;
296
- }
297
-
298
- writeFileSync(gitignorePath, ignoreEntry.trimStart());
299
- console.log(' - created .gitignore with .planning/ entry');
300
- }
301
-
302
- function printPostInitRouting(selectedRuntimes) {
303
- for (const line of getPostInitRoutingLines(selectedRuntimes)) {
304
- console.log(line);
305
- }
306
- console.log('');
307
- }
308
-
309
- function printInitSummary(config) {
310
- const rigor = Object.entries(RIGOR_PROFILES).find(([, profile]) => (
311
- profile.researchDepth === config.researchDepth
312
- && profile.workflow.research === config.workflow?.research
313
- && profile.workflow.discuss === config.workflow?.discuss
314
- && profile.workflow.planCheck === config.workflow?.planCheck
315
- && profile.workflow.verifier === config.workflow?.verifier
316
- ))?.[0] ?? 'balanced';
317
- const cost = Object.entries(COST_PROFILES).find(([, profile]) => (
318
- profile.modelProfile === config.modelProfile
319
- && profile.parallelization === config.parallelization
320
- ))?.[0] ?? 'balanced';
321
-
322
- console.log(`Rigor: ${rigor} Cost: ${cost} Track .planning/ in git: ${config.commitDocs ? 'yes' : 'no'}`);
323
- console.log('Workflows: new-project → plan → execute → verify → progress');
324
- console.log('Edit .planning/config.json to fine-tune (verifier, gitProtocol, individual workflow flags).');
325
- console.log('');
326
- }
265
+
266
+ async function ensureConfig({ cwd, planningDir, isAuto, promptApi, preselectedConfig = null }) {
267
+ const configFile = join(planningDir, 'config.json');
268
+ if (existsSync(configFile)) {
269
+ console.log(' - .planning/config.json already exists');
270
+ return;
271
+ }
272
+
273
+ if (preselectedConfig) {
274
+ writeFileSync(configFile, JSON.stringify(preselectedConfig, null, 2));
275
+ console.log(' - saved .planning/config.json (guided wizard)\n');
276
+ if (!preselectedConfig.commitDocs) ensureGitignoreEntry(cwd);
277
+ return;
278
+ }
279
+
280
+ if (isAuto) {
281
+ const config = buildDefaultConfig({ autoAdvance: true });
282
+ writeFileSync(configFile, JSON.stringify(config, null, 2));
283
+ console.log(' - wrote .planning/config.json (auto defaults)\n');
284
+ if (!config.commitDocs) ensureGitignoreEntry(cwd);
285
+ return;
286
+ }
287
+
288
+ if (!process.stdin.isTTY) {
289
+ const config = buildDefaultConfig({ autoAdvance: false });
290
+ writeFileSync(configFile, JSON.stringify(config, null, 2));
291
+ console.log(' - wrote .planning/config.json (non-interactive defaults)\n');
292
+ if (!config.commitDocs) ensureGitignoreEntry(cwd);
293
+ return;
294
+ }
295
+
296
+ const selected = typeof promptApi.promptForConfig === 'function'
297
+ ? await promptApi.promptForConfig(cwd)
298
+ : buildDefaultConfig({ autoAdvance: false });
299
+
300
+ if (!selected) {
301
+ throw new Error('Initialization cancelled');
302
+ }
303
+
304
+ writeFileSync(configFile, JSON.stringify(selected, null, 2));
305
+ console.log(' - saved .planning/config.json (guided wizard)\n');
306
+ if (!selected.commitDocs) ensureGitignoreEntry(cwd);
307
+ }
308
+
309
+ function ensureGitignoreEntry(cwd) {
310
+ const gitignorePath = join(cwd, '.gitignore');
311
+ const entry = '.planning/';
312
+ const hasGitignore = existsSync(gitignorePath);
313
+ const current = hasGitignore ? readFileSync(gitignorePath, 'utf-8') : '';
314
+ if (!current.split(/\r?\n/).includes(entry)) {
315
+ const next = current.trimEnd() ? `${current.trimEnd()}\n${entry}\n` : `${entry}\n`;
316
+ writeFileSync(gitignorePath, next);
317
+ console.log(' - ensured .planning/ is gitignored');
318
+ }
319
+ }
320
+
321
+ function printInitSummary(config) {
322
+ console.log('Config summary:');
323
+ console.log(` - researchDepth: ${config.researchDepth}`);
324
+ console.log(` - parallelization: ${config.parallelization}`);
325
+ console.log(` - commitDocs: ${config.commitDocs}`);
326
+ console.log(` - modelProfile: ${config.modelProfile}`);
327
+ if (typeof config.autoAdvance === 'boolean') console.log(` - autoAdvance: ${config.autoAdvance}`);
328
+ if (config.workflow) {
329
+ console.log(` - workflow.research: ${config.workflow.research}`);
330
+ console.log(` - workflow.discuss: ${config.workflow.discuss}`);
331
+ console.log(` - workflow.planCheck: ${config.workflow.planCheck}`);
332
+ console.log(` - workflow.verifier: ${config.workflow.verifier}`);
333
+ }
334
+ console.log('');
335
+ }
336
+
337
+ function printPostInitRouting(selectedRuntimes = []) {
338
+ for (const line of getPostInitRoutingLines(selectedRuntimes)) {
339
+ console.log(line);
340
+ }
341
+ console.log('');
342
+ }