@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.
- package/LICENSE +1 -1
- package/README.md +32 -31
- package/bin/wheat.js +63 -40
- package/compiler/detect-sprints.js +108 -66
- package/compiler/generate-manifest.js +116 -69
- package/compiler/wheat-compiler.js +763 -471
- package/lib/compiler.js +11 -6
- package/lib/connect.js +273 -134
- package/lib/defaults.js +32 -0
- package/lib/disconnect.js +61 -40
- package/lib/guard.js +20 -17
- package/lib/index.js +8 -8
- package/lib/init.js +260 -142
- package/lib/install-prompt.js +26 -26
- package/lib/load-claims.js +88 -0
- package/lib/quickstart.js +225 -111
- package/lib/serve-mcp.js +495 -180
- package/lib/server.js +198 -111
- package/lib/stats.js +65 -39
- package/lib/status.js +65 -34
- package/lib/update.js +13 -11
- package/package.json +8 -4
- package/templates/claude.md +31 -17
- package/templates/commands/blind-spot.md +9 -2
- package/templates/commands/brief.md +11 -1
- package/templates/commands/calibrate.md +3 -1
- package/templates/commands/challenge.md +4 -1
- package/templates/commands/connect.md +12 -1
- package/templates/commands/evaluate.md +4 -0
- package/templates/commands/feedback.md +3 -1
- package/templates/commands/handoff.md +11 -7
- package/templates/commands/init.md +4 -1
- package/templates/commands/merge.md +4 -1
- package/templates/commands/next.md +1 -0
- package/templates/commands/present.md +3 -0
- package/templates/commands/prototype.md +2 -0
- package/templates/commands/pull.md +103 -0
- package/templates/commands/replay.md +8 -0
- package/templates/commands/research.md +1 -0
- package/templates/commands/resolve.md +4 -1
- package/templates/commands/status.md +4 -0
- package/templates/commands/sync.md +94 -0
- 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
|
|
19
|
-
import path from
|
|
20
|
-
import readline from
|
|
21
|
-
import { execFileSync } from
|
|
22
|
-
import { fileURLToPath } from
|
|
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 (
|
|
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(
|
|
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(
|
|
78
|
-
console.log(
|
|
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(
|
|
82
|
-
|
|
83
|
-
|
|
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(
|
|
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(
|
|
97
|
-
|
|
98
|
-
|
|
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(
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
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(
|
|
116
|
-
|
|
117
|
-
|
|
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
|
|
126
|
-
|
|
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
|
|
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,
|
|
145
|
-
type:
|
|
146
|
-
topic:
|
|
159
|
+
id: `d${String(i + 1).padStart(3, "0")}`,
|
|
160
|
+
type: "constraint",
|
|
161
|
+
topic: "sprint-scope",
|
|
147
162
|
content: constraint,
|
|
148
|
-
source: { origin:
|
|
149
|
-
evidence:
|
|
150
|
-
status:
|
|
151
|
-
phase_added:
|
|
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,
|
|
163
|
-
type:
|
|
164
|
-
topic:
|
|
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:
|
|
167
|
-
evidence:
|
|
168
|
-
status:
|
|
169
|
-
phase_added:
|
|
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: [
|
|
188
|
+
tags: ["done-criteria"],
|
|
174
189
|
});
|
|
175
190
|
}
|
|
176
191
|
|
|
177
192
|
return {
|
|
178
|
-
schema_version:
|
|
193
|
+
schema_version: "1.0",
|
|
179
194
|
meta: {
|
|
180
195
|
question: meta.question,
|
|
181
|
-
initiated: new Date().toISOString().split(
|
|
196
|
+
initiated: new Date().toISOString().split("T")[0],
|
|
182
197
|
audience: meta.audience,
|
|
183
|
-
phase:
|
|
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(),
|
|
206
|
+
const templatePath = path.join(packageRoot(), "templates", "claude.md");
|
|
192
207
|
let template;
|
|
193
208
|
try {
|
|
194
|
-
template = fs.readFileSync(templatePath,
|
|
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(
|
|
198
|
-
|
|
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 =
|
|
202
|
-
|
|
203
|
-
|
|
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(
|
|
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(),
|
|
214
|
-
const destDir = target(dir,
|
|
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(
|
|
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(
|
|
250
|
-
cwd: dir,
|
|
251
|
-
|
|
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(
|
|
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,
|
|
258
|
-
const hookPath = path.join(hooksDir,
|
|
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 =
|
|
264
|
-
const escapedDir = dir.replace(/\\/g,
|
|
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,
|
|
315
|
+
const existing = fs.readFileSync(hookPath, "utf8");
|
|
290
316
|
if (existing.includes(WHEAT_MARKER)) {
|
|
291
|
-
console.log(
|
|
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,
|
|
299
|
-
|
|
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(
|
|
332
|
+
console.log(" \x1b[32m+\x1b[0m .git/hooks/pre-commit (wheat guard)");
|
|
302
333
|
} catch (err) {
|
|
303
|
-
console.log(
|
|
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,
|
|
346
|
+
const claimsPath = target(dir, "claims.json");
|
|
314
347
|
if (fs.existsSync(claimsPath) && !flags.force) {
|
|
315
348
|
console.log();
|
|
316
|
-
console.log(
|
|
317
|
-
|
|
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.
|
|
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(
|
|
328
|
-
if (!flags.audience) missing.push(
|
|
329
|
-
if (!flags.constraints) missing.push(
|
|
330
|
-
if (!flags.done) missing.push(
|
|
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(
|
|
334
|
-
|
|
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(
|
|
337
|
-
console.error(
|
|
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(
|
|
341
|
-
|
|
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(
|
|
348
|
-
constraints: flags.constraints
|
|
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(
|
|
353
|
-
console.log(
|
|
354
|
-
console.log(
|
|
355
|
-
|
|
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(
|
|
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({
|
|
361
|
-
|
|
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(
|
|
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(
|
|
371
|
-
: (await ask(
|
|
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
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
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 : [
|
|
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(
|
|
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 +
|
|
406
|
-
fs.writeFileSync(tmpClaims, JSON.stringify(claims, null, 2) +
|
|
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(
|
|
497
|
+
console.log(" \x1b[32m+\x1b[0m claims.json");
|
|
409
498
|
|
|
410
499
|
// CLAUDE.md
|
|
411
|
-
const claudePath = target(dir,
|
|
500
|
+
const claudePath = target(dir, "CLAUDE.md");
|
|
412
501
|
fs.writeFileSync(claudePath, claudeMd);
|
|
413
|
-
console.log(
|
|
502
|
+
console.log(" \x1b[32m+\x1b[0m CLAUDE.md");
|
|
414
503
|
|
|
415
504
|
// .claude/commands/
|
|
416
505
|
const copied = copyCommands(dir);
|
|
417
|
-
console.log(
|
|
506
|
+
console.log(
|
|
507
|
+
` \x1b[32m+\x1b[0m .claude/commands/ (${copied} commands installed)`
|
|
508
|
+
);
|
|
418
509
|
|
|
419
510
|
// Create output directories
|
|
420
|
-
const dirs = [
|
|
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,
|
|
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(
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
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
|
-
|
|
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(
|
|
456
|
-
console.log(
|
|
457
|
-
console.log(
|
|
458
|
-
console.log(
|
|
459
|
-
console.log(
|
|
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(
|
|
462
|
-
console.log(
|
|
463
|
-
console.log(
|
|
464
|
-
console.log(
|
|
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(
|
|
584
|
+
console.log(" Trust the process. The evidence will compound.");
|
|
467
585
|
console.log();
|
|
468
586
|
}
|