@grainulation/wheat 1.0.2 → 1.0.4

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 (42) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +32 -31
  3. package/bin/wheat.js +47 -36
  4. package/compiler/detect-sprints.js +126 -92
  5. package/compiler/generate-manifest.js +116 -69
  6. package/compiler/wheat-compiler.js +789 -468
  7. package/lib/compiler.js +11 -6
  8. package/lib/connect.js +273 -134
  9. package/lib/disconnect.js +61 -40
  10. package/lib/guard.js +20 -17
  11. package/lib/index.js +8 -8
  12. package/lib/init.js +217 -142
  13. package/lib/install-prompt.js +26 -26
  14. package/lib/load-claims.js +88 -0
  15. package/lib/quickstart.js +225 -111
  16. package/lib/serve-mcp.js +495 -180
  17. package/lib/server.js +198 -111
  18. package/lib/stats.js +65 -39
  19. package/lib/status.js +65 -34
  20. package/lib/update.js +13 -11
  21. package/package.json +8 -4
  22. package/templates/claude.md +31 -17
  23. package/templates/commands/blind-spot.md +9 -2
  24. package/templates/commands/brief.md +11 -1
  25. package/templates/commands/calibrate.md +3 -1
  26. package/templates/commands/challenge.md +4 -1
  27. package/templates/commands/connect.md +12 -1
  28. package/templates/commands/evaluate.md +4 -0
  29. package/templates/commands/feedback.md +3 -1
  30. package/templates/commands/handoff.md +11 -7
  31. package/templates/commands/init.md +4 -1
  32. package/templates/commands/merge.md +4 -1
  33. package/templates/commands/next.md +1 -0
  34. package/templates/commands/present.md +3 -0
  35. package/templates/commands/prototype.md +2 -0
  36. package/templates/commands/pull.md +103 -0
  37. package/templates/commands/replay.md +8 -0
  38. package/templates/commands/research.md +1 -0
  39. package/templates/commands/resolve.md +4 -1
  40. package/templates/commands/status.md +4 -0
  41. package/templates/commands/sync.md +94 -0
  42. package/templates/commands/witness.md +6 -2
package/lib/init.js CHANGED
@@ -15,11 +15,11 @@
15
15
  * Zero npm dependencies.
16
16
  */
17
17
 
18
- import fs from 'fs';
19
- import path from 'path';
20
- import readline from 'readline';
21
- import { execFileSync } from 'child_process';
22
- import { fileURLToPath } from 'url';
18
+ import fs from "fs";
19
+ import path from "path";
20
+ import readline from "readline";
21
+ import { execFileSync } from "child_process";
22
+ import { fileURLToPath } from "url";
23
23
 
24
24
  const __filename = fileURLToPath(import.meta.url);
25
25
  const __dirname = path.dirname(__filename);
@@ -33,13 +33,13 @@ function target(dir, ...segments) {
33
33
 
34
34
  /** Get the package root (where templates live) */
35
35
  function packageRoot() {
36
- return path.resolve(__dirname, '..');
36
+ return path.resolve(__dirname, "..");
37
37
  }
38
38
 
39
39
  /** Ask a question and return the answer */
40
40
  function ask(rl, question) {
41
- return new Promise(resolve => {
42
- rl.question(question, answer => resolve(answer.trim()));
41
+ return new Promise((resolve) => {
42
+ rl.question(question, (answer) => resolve(answer.trim()));
43
43
  });
44
44
  }
45
45
 
@@ -52,10 +52,14 @@ function now() {
52
52
  function parseFlags(args) {
53
53
  const flags = {};
54
54
  for (let i = 0; i < args.length; i++) {
55
- if (args[i].startsWith('--') && i + 1 < args.length && !args[i + 1].startsWith('--')) {
55
+ if (
56
+ args[i].startsWith("--") &&
57
+ i + 1 < args.length &&
58
+ !args[i + 1].startsWith("--")
59
+ ) {
56
60
  flags[args[i].slice(2)] = args[i + 1];
57
61
  i++;
58
- } else if (args[i].startsWith('--')) {
62
+ } else if (args[i].startsWith("--")) {
59
63
  flags[args[i].slice(2)] = true;
60
64
  }
61
65
  }
@@ -71,21 +75,22 @@ async function conversationalInit(dir) {
71
75
  });
72
76
 
73
77
  console.log();
74
- console.log(' \x1b[1m\x1b[33mwheat\x1b[0m — let\'s set up a research sprint');
75
- console.log(' ─────────────────────────────────────────');
78
+ console.log(" \x1b[1m\x1b[33mwheat\x1b[0m — let's set up a research sprint");
79
+ console.log(" ─────────────────────────────────────────");
76
80
  console.log();
77
- console.log(' Before you commit to anything big, let\'s figure out');
78
- console.log(' what you actually need to know. Four questions.\n');
81
+ console.log(" Before you commit to anything big, let's figure out");
82
+ console.log(" what you actually need to know. Four questions.\n");
79
83
 
80
84
  // Question
81
- const question = await ask(rl,
82
- ' What question are you trying to answer?\n' +
83
- ' (The more specific, the better. "Should we migrate to X?" beats "what database?")\n\n' +
84
- ' > '
85
+ const question = await ask(
86
+ rl,
87
+ " What question are you trying to answer?\n" +
88
+ ' (The more specific, the better. "Should we migrate to X?" beats "what database?")\n\n' +
89
+ " > "
85
90
  );
86
91
 
87
92
  if (!question) {
88
- console.log('\n No question, no sprint. Come back when you have one.\n');
93
+ console.log("\n No question, no sprint. Come back when you have one.\n");
89
94
  rl.close();
90
95
  process.exit(1);
91
96
  }
@@ -93,41 +98,50 @@ async function conversationalInit(dir) {
93
98
  console.log();
94
99
 
95
100
  // Audience
96
- const audienceRaw = await ask(rl,
97
- ' Who needs the answer?\n' +
98
- ' Could be your team, a VP, a client, or just yourself.\n\n' +
99
- ' > '
101
+ const audienceRaw = await ask(
102
+ rl,
103
+ " Who needs the answer?\n" +
104
+ " Could be your team, a VP, a client, or just yourself.\n\n" +
105
+ " > "
100
106
  );
101
107
 
102
108
  console.log();
103
109
 
104
110
  // Constraints
105
- const constraintsRaw = await ask(rl,
106
- ' Any constraints?\n' +
107
- ' Budget, timeline, tech stack, team size — whatever narrows the space.\n' +
108
- ' (Leave blank if none.)\n\n' +
109
- ' > '
111
+ const constraintsRaw = await ask(
112
+ rl,
113
+ " Any constraints?\n" +
114
+ " Budget, timeline, tech stack, team size — whatever narrows the space.\n" +
115
+ " (Leave blank if none.)\n\n" +
116
+ " > "
110
117
  );
111
118
 
112
119
  console.log();
113
120
 
114
121
  // Done criteria
115
- const doneCriteria = await ask(rl,
116
- ' How will you know you\'re done?\n' +
117
- ' A recommendation? A prototype? A go/no-go? A deck for the meeting?\n\n' +
118
- ' > '
122
+ const doneCriteria = await ask(
123
+ rl,
124
+ " How will you know you're done?\n" +
125
+ " A recommendation? A prototype? A go/no-go? A deck for the meeting?\n\n" +
126
+ " > "
119
127
  );
120
128
 
121
129
  rl.close();
122
130
 
123
131
  // Parse audience into array
124
132
  const audience = audienceRaw
125
- ? audienceRaw.split(/[,;]/).map(s => s.trim()).filter(Boolean)
126
- : ['self'];
133
+ ? audienceRaw
134
+ .split(/[,;]/)
135
+ .map((s) => s.trim())
136
+ .filter(Boolean)
137
+ : ["self"];
127
138
 
128
139
  // Parse constraints into individual items
129
140
  const constraints = constraintsRaw
130
- ? constraintsRaw.split(/[.;]/).map(s => s.trim()).filter(s => s.length > 5)
141
+ ? constraintsRaw
142
+ .split(/[.;]/)
143
+ .map((s) => s.trim())
144
+ .filter((s) => s.length > 5)
131
145
  : [];
132
146
 
133
147
  return { question, audience, constraints, doneCriteria };
@@ -141,14 +155,14 @@ function buildClaims(meta, constraints) {
141
155
 
142
156
  constraints.forEach((constraint, i) => {
143
157
  claims.push({
144
- id: `d${String(i + 1).padStart(3, '0')}`,
145
- type: 'constraint',
146
- topic: 'sprint-scope',
158
+ id: `d${String(i + 1).padStart(3, "0")}`,
159
+ type: "constraint",
160
+ topic: "sprint-scope",
147
161
  content: constraint,
148
- source: { origin: 'stakeholder', artifact: null, connector: null },
149
- evidence: 'stated',
150
- status: 'active',
151
- phase_added: 'define',
162
+ source: { origin: "stakeholder", artifact: null, connector: null },
163
+ evidence: "stated",
164
+ status: "active",
165
+ phase_added: "define",
152
166
  timestamp,
153
167
  conflicts_with: [],
154
168
  resolved_by: null,
@@ -159,28 +173,28 @@ function buildClaims(meta, constraints) {
159
173
  // Add done-criteria as a constraint if provided
160
174
  if (meta.doneCriteria) {
161
175
  claims.push({
162
- id: `d${String(constraints.length + 1).padStart(3, '0')}`,
163
- type: 'constraint',
164
- topic: 'done-criteria',
176
+ id: `d${String(constraints.length + 1).padStart(3, "0")}`,
177
+ type: "constraint",
178
+ topic: "done-criteria",
165
179
  content: `Done looks like: ${meta.doneCriteria}`,
166
- source: { origin: 'stakeholder', artifact: null, connector: null },
167
- evidence: 'stated',
168
- status: 'active',
169
- phase_added: 'define',
180
+ source: { origin: "stakeholder", artifact: null, connector: null },
181
+ evidence: "stated",
182
+ status: "active",
183
+ phase_added: "define",
170
184
  timestamp,
171
185
  conflicts_with: [],
172
186
  resolved_by: null,
173
- tags: ['done-criteria'],
187
+ tags: ["done-criteria"],
174
188
  });
175
189
  }
176
190
 
177
191
  return {
178
- schema_version: '1.0',
192
+ schema_version: "1.0",
179
193
  meta: {
180
194
  question: meta.question,
181
- initiated: new Date().toISOString().split('T')[0],
195
+ initiated: new Date().toISOString().split("T")[0],
182
196
  audience: meta.audience,
183
- phase: 'define',
197
+ phase: "define",
184
198
  connectors: [],
185
199
  },
186
200
  claims,
@@ -188,30 +202,37 @@ function buildClaims(meta, constraints) {
188
202
  }
189
203
 
190
204
  function buildClaudeMd(meta) {
191
- const templatePath = path.join(packageRoot(), 'templates', 'claude.md');
205
+ const templatePath = path.join(packageRoot(), "templates", "claude.md");
192
206
  let template;
193
207
  try {
194
- template = fs.readFileSync(templatePath, 'utf8');
208
+ template = fs.readFileSync(templatePath, "utf8");
195
209
  } catch {
196
210
  // Fallback if template is missing (shouldn't happen in installed package)
197
- console.error(' Warning: CLAUDE.md template not found, using minimal template');
198
- template = '# Wheat Research Sprint\n\n## Sprint\n\n**Question:** {{QUESTION}}\n\n**Audience:** {{AUDIENCE}}\n\n**Constraints:**\n{{CONSTRAINTS}}\n\n**Done looks like:** {{DONE_CRITERIA}}\n';
211
+ console.error(
212
+ " Warning: CLAUDE.md template not found, using minimal template"
213
+ );
214
+ template =
215
+ "# Wheat — Research Sprint\n\n## Sprint\n\n**Question:** {{QUESTION}}\n\n**Audience:** {{AUDIENCE}}\n\n**Constraints:**\n{{CONSTRAINTS}}\n\n**Done looks like:** {{DONE_CRITERIA}}\n";
199
216
  }
200
217
 
201
- const constraintList = meta.constraints.length > 0
202
- ? meta.constraints.map(c => `- ${c}`).join('\n')
203
- : '- (none specified)';
218
+ const constraintList =
219
+ meta.constraints.length > 0
220
+ ? meta.constraints.map((c) => `- ${c}`).join("\n")
221
+ : "- (none specified)";
204
222
 
205
223
  return template
206
224
  .replace(/\{\{QUESTION\}\}/g, meta.question)
207
- .replace(/\{\{AUDIENCE\}\}/g, meta.audience.join(', '))
225
+ .replace(/\{\{AUDIENCE\}\}/g, meta.audience.join(", "))
208
226
  .replace(/\{\{CONSTRAINTS\}\}/g, constraintList)
209
- .replace(/\{\{DONE_CRITERIA\}\}/g, meta.doneCriteria || 'A recommendation with evidence');
227
+ .replace(
228
+ /\{\{DONE_CRITERIA\}\}/g,
229
+ meta.doneCriteria || "A recommendation with evidence"
230
+ );
210
231
  }
211
232
 
212
233
  function copyCommands(dir) {
213
- const srcDir = path.join(packageRoot(), 'templates', 'commands');
214
- const destDir = target(dir, '.claude', 'commands');
234
+ const srcDir = path.join(packageRoot(), "templates", "commands");
235
+ const destDir = target(dir, ".claude", "commands");
215
236
 
216
237
  // Create .claude/commands/ if it doesn't exist
217
238
  fs.mkdirSync(destDir, { recursive: true });
@@ -220,7 +241,7 @@ function copyCommands(dir) {
220
241
  try {
221
242
  const files = fs.readdirSync(srcDir);
222
243
  for (const file of files) {
223
- if (!file.endsWith('.md')) continue;
244
+ if (!file.endsWith(".md")) continue;
224
245
  const src = path.join(srcDir, file);
225
246
  const dest = path.join(destDir, file);
226
247
 
@@ -246,22 +267,26 @@ function installGitHook(dir) {
246
267
  // Find git root (might be different from target dir in monorepos)
247
268
  let gitRoot;
248
269
  try {
249
- gitRoot = execFileSync('git', ['rev-parse', '--show-toplevel'], {
250
- cwd: dir, timeout: 5000, stdio: ['ignore', 'pipe', 'pipe'],
251
- }).toString().trim();
270
+ gitRoot = execFileSync("git", ["rev-parse", "--show-toplevel"], {
271
+ cwd: dir,
272
+ timeout: 5000,
273
+ stdio: ["ignore", "pipe", "pipe"],
274
+ })
275
+ .toString()
276
+ .trim();
252
277
  } catch {
253
- console.log(' \x1b[33m!\x1b[0m Not a git repo — skipping pre-commit hook');
278
+ console.log(" \x1b[33m!\x1b[0m Not a git repo — skipping pre-commit hook");
254
279
  return;
255
280
  }
256
281
 
257
- const hooksDir = path.join(gitRoot, '.git', 'hooks');
258
- const hookPath = path.join(hooksDir, 'pre-commit');
282
+ const hooksDir = path.join(gitRoot, ".git", "hooks");
283
+ const hookPath = path.join(hooksDir, "pre-commit");
259
284
 
260
285
  // The hook snippet — runs wheat compile --check before allowing commits
261
286
  // Prefers local wheat binary (node_modules/.bin/wheat) over npx to avoid
262
287
  // auto-fetching from registry on every commit. Falls back to npx if not installed.
263
- const WHEAT_MARKER = '# wheat-guard';
264
- const escapedDir = dir.replace(/\\/g, '/'); // Normalize Windows backslashes for shell
288
+ const WHEAT_MARKER = "# wheat-guard";
289
+ const escapedDir = dir.replace(/\\/g, "/"); // Normalize Windows backslashes for shell
265
290
  const hookSnippet = `
266
291
  ${WHEAT_MARKER}
267
292
  # Wheat pre-commit: verify claims compile before committing
@@ -286,21 +311,28 @@ fi
286
311
 
287
312
  try {
288
313
  if (fs.existsSync(hookPath)) {
289
- const existing = fs.readFileSync(hookPath, 'utf8');
314
+ const existing = fs.readFileSync(hookPath, "utf8");
290
315
  if (existing.includes(WHEAT_MARKER)) {
291
- console.log(' \x1b[34m-\x1b[0m pre-commit hook (already installed)');
316
+ console.log(" \x1b[34m-\x1b[0m pre-commit hook (already installed)");
292
317
  return;
293
318
  }
294
319
  // Append to existing hook
295
320
  fs.appendFileSync(hookPath, hookSnippet);
296
321
  } else {
297
322
  // Create new hook
298
- fs.writeFileSync(hookPath, '#!/bin/sh\n' + hookSnippet);
299
- fs.chmodSync(hookPath, 0o755);
323
+ fs.writeFileSync(hookPath, "#!/bin/sh\n" + hookSnippet);
324
+ // chmod is a no-op on Windows but needed for Unix
325
+ try {
326
+ fs.chmodSync(hookPath, 0o755);
327
+ } catch {
328
+ /* Windows: no chmod support, git handles executable bit */
329
+ }
300
330
  }
301
- console.log(' \x1b[32m+\x1b[0m .git/hooks/pre-commit (wheat guard)');
331
+ console.log(" \x1b[32m+\x1b[0m .git/hooks/pre-commit (wheat guard)");
302
332
  } catch (err) {
303
- console.log(` \x1b[33m!\x1b[0m Could not install git hook: ${err.message}`);
333
+ console.log(
334
+ ` \x1b[33m!\x1b[0m Could not install git hook: ${err.message}`
335
+ );
304
336
  }
305
337
  }
306
338
 
@@ -310,78 +342,115 @@ export async function run(dir, args) {
310
342
  const flags = parseFlags(args);
311
343
 
312
344
  // Check if sprint already exists
313
- const claimsPath = target(dir, 'claims.json');
345
+ const claimsPath = target(dir, "claims.json");
314
346
  if (fs.existsSync(claimsPath) && !flags.force) {
315
347
  console.log();
316
- console.log(' A sprint already exists in this directory (claims.json found).');
317
- console.log(' Use --force to reinitialize, or run "wheat compile" to continue.');
348
+ console.log(
349
+ " A sprint already exists in this directory (claims.json found)."
350
+ );
351
+ console.log(
352
+ ' Use --force to reinitialize, or run "wheat compile" to continue.'
353
+ );
318
354
  console.log();
319
355
  process.exit(1);
320
356
  }
321
357
 
322
358
  let meta;
323
359
 
324
- if (flags.headless || flags['non-interactive']) {
360
+ if (flags.headless || flags["non-interactive"]) {
325
361
  // ── Headless mode — all flags required ──
326
362
  const missing = [];
327
- if (!flags.question) missing.push('--question');
328
- if (!flags.audience) missing.push('--audience');
329
- if (!flags.constraints) missing.push('--constraints');
330
- if (!flags.done) missing.push('--done');
363
+ if (!flags.question) missing.push("--question");
364
+ if (!flags.audience) missing.push("--audience");
365
+ if (!flags.constraints) missing.push("--constraints");
366
+ if (!flags.done) missing.push("--done");
331
367
  if (missing.length > 0) {
332
368
  console.error();
333
- console.error(` --headless requires all flags: --question, --audience, --constraints, --done`);
334
- console.error(` Missing: ${missing.join(', ')}`);
369
+ console.error(
370
+ ` --headless requires all flags: --question, --audience, --constraints, --done`
371
+ );
372
+ console.error(` Missing: ${missing.join(", ")}`);
335
373
  console.error();
336
- console.error(' Example:');
337
- console.error(' wheat init --headless \\');
374
+ console.error(" Example:");
375
+ console.error(" wheat init --headless \\");
338
376
  console.error(' --question "Should we migrate to Postgres?" \\');
339
377
  console.error(' --audience "Backend team" \\');
340
- console.error(' --constraints "Must support zero-downtime; Budget under 10k" \\');
341
- console.error(' --done "Migration plan with risk assessment and rollback strategy"');
378
+ console.error(
379
+ ' --constraints "Must support zero-downtime; Budget under 10k" \\'
380
+ );
381
+ console.error(
382
+ ' --done "Migration plan with risk assessment and rollback strategy"'
383
+ );
342
384
  console.error();
343
385
  process.exit(1);
344
386
  }
345
387
  meta = {
346
388
  question: flags.question,
347
- audience: flags.audience.split(',').map(s => s.trim()),
348
- constraints: flags.constraints.split(';').map(s => s.trim()).filter(Boolean),
389
+ audience: flags.audience.split(",").map((s) => s.trim()),
390
+ constraints: flags.constraints
391
+ .split(";")
392
+ .map((s) => s.trim())
393
+ .filter(Boolean),
349
394
  doneCriteria: flags.done,
350
395
  };
351
396
  console.log();
352
- console.log(' \x1b[1m\x1b[33mwheat\x1b[0m — headless sprint init');
353
- console.log(' ─────────────────────────────────────────');
354
- console.log(` Question: ${meta.question.slice(0, 70)}${meta.question.length > 70 ? '...' : ''}`);
355
- console.log(` Audience: ${meta.audience.join(', ')}`);
397
+ console.log(" \x1b[1m\x1b[33mwheat\x1b[0m — headless sprint init");
398
+ console.log(" ─────────────────────────────────────────");
399
+ console.log(
400
+ ` Question: ${meta.question.slice(0, 70)}${
401
+ meta.question.length > 70 ? "..." : ""
402
+ }`
403
+ );
404
+ console.log(` Audience: ${meta.audience.join(", ")}`);
356
405
  console.log(` Constraints: ${meta.constraints.length}`);
357
- console.log(` Done: ${meta.doneCriteria.slice(0, 70)}${meta.doneCriteria.length > 70 ? '...' : ''}`);
406
+ console.log(
407
+ ` Done: ${meta.doneCriteria.slice(0, 70)}${
408
+ meta.doneCriteria.length > 70 ? "..." : ""
409
+ }`
410
+ );
358
411
  } else if (flags.question) {
359
412
  // ── Quick mode — question pre-filled, prompt for the rest if missing ──
360
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
361
- const ask = (q) => new Promise(resolve => rl.question(q, resolve));
413
+ const rl = readline.createInterface({
414
+ input: process.stdin,
415
+ output: process.stdout,
416
+ });
417
+ const ask = (q) => new Promise((resolve) => rl.question(q, resolve));
362
418
 
363
419
  console.log();
364
- console.log(' \x1b[1m\x1b[33mwheat\x1b[0m — quick sprint init');
365
- console.log(' ─────────────────────────────────────────');
420
+ console.log(" \x1b[1m\x1b[33mwheat\x1b[0m — quick sprint init");
421
+ console.log(" ─────────────────────────────────────────");
366
422
  console.log(` Question: ${flags.question}`);
367
423
  console.log();
368
424
 
369
425
  const audience = flags.audience
370
- ? flags.audience.split(',').map(s => s.trim())
371
- : (await ask(' Who is this for? (comma-separated, default: self)\n > ')).split(',').map(s => s.trim()).filter(Boolean) || ['self'];
426
+ ? flags.audience.split(",").map((s) => s.trim())
427
+ : (await ask(" Who is this for? (comma-separated, default: self)\n > "))
428
+ .split(",")
429
+ .map((s) => s.trim())
430
+ .filter(Boolean) || ["self"];
372
431
 
373
432
  const constraints = flags.constraints
374
- ? flags.constraints.split(';').map(s => s.trim()).filter(Boolean)
375
- : (await ask(' Any constraints? (semicolon-separated, or press Enter to skip)\n > ')).split(';').map(s => s.trim()).filter(Boolean);
376
-
377
- const doneCriteria = flags.done
378
- || await ask(' What does "done" look like?\n > ');
433
+ ? flags.constraints
434
+ .split(";")
435
+ .map((s) => s.trim())
436
+ .filter(Boolean)
437
+ : (
438
+ await ask(
439
+ " Any constraints? (semicolon-separated, or press Enter to skip)\n > "
440
+ )
441
+ )
442
+ .split(";")
443
+ .map((s) => s.trim())
444
+ .filter(Boolean);
445
+
446
+ const doneCriteria =
447
+ flags.done || (await ask(' What does "done" look like?\n > '));
379
448
 
380
449
  rl.close();
381
450
 
382
451
  meta = {
383
452
  question: flags.question,
384
- audience: audience.length ? audience : ['self'],
453
+ audience: audience.length ? audience : ["self"],
385
454
  constraints,
386
455
  doneCriteria,
387
456
  };
@@ -398,31 +467,33 @@ export async function run(dir, args) {
398
467
 
399
468
  // Write files
400
469
  console.log();
401
- console.log(' \x1b[1mCreating sprint files...\x1b[0m');
470
+ console.log(" \x1b[1mCreating sprint files...\x1b[0m");
402
471
  console.log();
403
472
 
404
473
  // claims.json (atomic write-then-rename)
405
- const tmpClaims = claimsPath + '.tmp.' + process.pid;
406
- fs.writeFileSync(tmpClaims, JSON.stringify(claims, null, 2) + '\n');
474
+ const tmpClaims = claimsPath + ".tmp." + process.pid;
475
+ fs.writeFileSync(tmpClaims, JSON.stringify(claims, null, 2) + "\n");
407
476
  fs.renameSync(tmpClaims, claimsPath);
408
- console.log(' \x1b[32m+\x1b[0m claims.json');
477
+ console.log(" \x1b[32m+\x1b[0m claims.json");
409
478
 
410
479
  // CLAUDE.md
411
- const claudePath = target(dir, 'CLAUDE.md');
480
+ const claudePath = target(dir, "CLAUDE.md");
412
481
  fs.writeFileSync(claudePath, claudeMd);
413
- console.log(' \x1b[32m+\x1b[0m CLAUDE.md');
482
+ console.log(" \x1b[32m+\x1b[0m CLAUDE.md");
414
483
 
415
484
  // .claude/commands/
416
485
  const copied = copyCommands(dir);
417
- console.log(` \x1b[32m+\x1b[0m .claude/commands/ (${copied} commands installed)`);
486
+ console.log(
487
+ ` \x1b[32m+\x1b[0m .claude/commands/ (${copied} commands installed)`
488
+ );
418
489
 
419
490
  // Create output directories
420
- const dirs = ['output', 'research', 'prototypes', 'evidence'];
491
+ const dirs = ["output", "research", "prototypes", "evidence"];
421
492
  for (const d of dirs) {
422
493
  const dirPath = target(dir, d);
423
494
  if (!fs.existsSync(dirPath)) {
424
495
  fs.mkdirSync(dirPath, { recursive: true });
425
- fs.writeFileSync(path.join(dirPath, '.gitkeep'), '');
496
+ fs.writeFileSync(path.join(dirPath, ".gitkeep"), "");
426
497
  console.log(` \x1b[32m+\x1b[0m ${d}/`);
427
498
  }
428
499
  }
@@ -432,37 +503,41 @@ export async function run(dir, args) {
432
503
 
433
504
  // Summary
434
505
  if (flags.json) {
435
- console.log(JSON.stringify({
436
- question: meta.question,
437
- audience: meta.audience,
438
- constraints: meta.constraints.length,
439
- done_criteria: meta.doneCriteria || null,
440
- claims_seeded: claims.claims.length,
441
- files_created: ['claims.json', 'CLAUDE.md', '.claude/commands/'],
442
- dir,
443
- }));
506
+ console.log(
507
+ JSON.stringify({
508
+ question: meta.question,
509
+ audience: meta.audience,
510
+ constraints: meta.constraints.length,
511
+ done_criteria: meta.doneCriteria || null,
512
+ claims_seeded: claims.claims.length,
513
+ files_created: ["claims.json", "CLAUDE.md", ".claude/commands/"],
514
+ dir,
515
+ })
516
+ );
444
517
  process.exit(0);
445
518
  }
446
519
 
447
520
  console.log();
448
- console.log(' ─────────────────────────────────────────');
521
+ console.log(" ─────────────────────────────────────────");
449
522
  console.log(` \x1b[1m\x1b[33mSprint ready.\x1b[0m`);
450
523
  console.log();
451
524
  console.log(` Question: ${meta.question}`);
452
- console.log(` Audience: ${meta.audience.join(', ')}`);
525
+ console.log(` Audience: ${meta.audience.join(", ")}`);
453
526
  console.log(` Claims: ${claims.claims.length} constraint(s) seeded`);
454
527
  console.log();
455
- console.log(' Created:');
456
- console.log(' claims.json Your evidence database');
457
- console.log(' CLAUDE.md AI assistant configuration');
458
- console.log(' .claude/commands/ 18 slash commands for Claude Code');
459
- console.log(' output/ Where compiled artifacts land');
528
+ console.log(" Created:");
529
+ console.log(" claims.json Your evidence database");
530
+ console.log(" CLAUDE.md AI assistant configuration");
531
+ console.log(" .claude/commands/ 18 slash commands for Claude Code");
532
+ console.log(" output/ Where compiled artifacts land");
460
533
  console.log();
461
- console.log(' Next steps:');
462
- console.log(' 1. Open Claude Code in this directory');
463
- console.log(' 2. Run /research <topic> to start investigating');
464
- console.log(' 3. The compiler validates as you go -- run wheat status to check health');
534
+ console.log(" Next steps:");
535
+ console.log(" 1. Open Claude Code in this directory");
536
+ console.log(" 2. Run /research <topic> to start investigating");
537
+ console.log(
538
+ " 3. The compiler validates as you go -- run wheat status to check health"
539
+ );
465
540
  console.log();
466
- console.log(' Trust the process. The evidence will compound.');
541
+ console.log(" Trust the process. The evidence will compound.");
467
542
  console.log();
468
543
  }