@grainulation/wheat 1.0.3 → 1.0.5

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