@grainulation/wheat 1.0.3 → 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.
- package/LICENSE +1 -1
- package/README.md +32 -31
- package/bin/wheat.js +47 -36
- 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/disconnect.js +61 -40
- package/lib/guard.js +20 -17
- package/lib/index.js +8 -8
- package/lib/init.js +217 -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,11 @@
|
|
|
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
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 (
|
|
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(
|
|
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(
|
|
78
|
-
console.log(
|
|
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(
|
|
82
|
-
|
|
83
|
-
|
|
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(
|
|
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(
|
|
97
|
-
|
|
98
|
-
|
|
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(
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
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(
|
|
116
|
-
|
|
117
|
-
|
|
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
|
|
126
|
-
|
|
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
|
|
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,
|
|
145
|
-
type:
|
|
146
|
-
topic:
|
|
158
|
+
id: `d${String(i + 1).padStart(3, "0")}`,
|
|
159
|
+
type: "constraint",
|
|
160
|
+
topic: "sprint-scope",
|
|
147
161
|
content: constraint,
|
|
148
|
-
source: { origin:
|
|
149
|
-
evidence:
|
|
150
|
-
status:
|
|
151
|
-
phase_added:
|
|
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,
|
|
163
|
-
type:
|
|
164
|
-
topic:
|
|
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:
|
|
167
|
-
evidence:
|
|
168
|
-
status:
|
|
169
|
-
phase_added:
|
|
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: [
|
|
187
|
+
tags: ["done-criteria"],
|
|
174
188
|
});
|
|
175
189
|
}
|
|
176
190
|
|
|
177
191
|
return {
|
|
178
|
-
schema_version:
|
|
192
|
+
schema_version: "1.0",
|
|
179
193
|
meta: {
|
|
180
194
|
question: meta.question,
|
|
181
|
-
initiated: new Date().toISOString().split(
|
|
195
|
+
initiated: new Date().toISOString().split("T")[0],
|
|
182
196
|
audience: meta.audience,
|
|
183
|
-
phase:
|
|
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(),
|
|
205
|
+
const templatePath = path.join(packageRoot(), "templates", "claude.md");
|
|
192
206
|
let template;
|
|
193
207
|
try {
|
|
194
|
-
template = fs.readFileSync(templatePath,
|
|
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(
|
|
198
|
-
|
|
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 =
|
|
202
|
-
|
|
203
|
-
|
|
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(
|
|
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(),
|
|
214
|
-
const destDir = target(dir,
|
|
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(
|
|
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(
|
|
250
|
-
cwd: dir,
|
|
251
|
-
|
|
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(
|
|
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,
|
|
258
|
-
const hookPath = path.join(hooksDir,
|
|
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 =
|
|
264
|
-
const escapedDir = dir.replace(/\\/g,
|
|
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,
|
|
314
|
+
const existing = fs.readFileSync(hookPath, "utf8");
|
|
290
315
|
if (existing.includes(WHEAT_MARKER)) {
|
|
291
|
-
console.log(
|
|
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,
|
|
299
|
-
|
|
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(
|
|
331
|
+
console.log(" \x1b[32m+\x1b[0m .git/hooks/pre-commit (wheat guard)");
|
|
302
332
|
} catch (err) {
|
|
303
|
-
console.log(
|
|
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,
|
|
345
|
+
const claimsPath = target(dir, "claims.json");
|
|
314
346
|
if (fs.existsSync(claimsPath) && !flags.force) {
|
|
315
347
|
console.log();
|
|
316
|
-
console.log(
|
|
317
|
-
|
|
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[
|
|
360
|
+
if (flags.headless || flags["non-interactive"]) {
|
|
325
361
|
// ── Headless mode — all flags required ──
|
|
326
362
|
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(
|
|
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(
|
|
334
|
-
|
|
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(
|
|
337
|
-
console.error(
|
|
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(
|
|
341
|
-
|
|
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(
|
|
348
|
-
constraints: flags.constraints
|
|
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(
|
|
353
|
-
console.log(
|
|
354
|
-
console.log(
|
|
355
|
-
|
|
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(
|
|
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({
|
|
361
|
-
|
|
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(
|
|
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(
|
|
371
|
-
: (await ask(
|
|
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
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
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 : [
|
|
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(
|
|
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 +
|
|
406
|
-
fs.writeFileSync(tmpClaims, JSON.stringify(claims, null, 2) +
|
|
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(
|
|
477
|
+
console.log(" \x1b[32m+\x1b[0m claims.json");
|
|
409
478
|
|
|
410
479
|
// CLAUDE.md
|
|
411
|
-
const claudePath = target(dir,
|
|
480
|
+
const claudePath = target(dir, "CLAUDE.md");
|
|
412
481
|
fs.writeFileSync(claudePath, claudeMd);
|
|
413
|
-
console.log(
|
|
482
|
+
console.log(" \x1b[32m+\x1b[0m CLAUDE.md");
|
|
414
483
|
|
|
415
484
|
// .claude/commands/
|
|
416
485
|
const copied = copyCommands(dir);
|
|
417
|
-
console.log(
|
|
486
|
+
console.log(
|
|
487
|
+
` \x1b[32m+\x1b[0m .claude/commands/ (${copied} commands installed)`
|
|
488
|
+
);
|
|
418
489
|
|
|
419
490
|
// Create output directories
|
|
420
|
-
const dirs = [
|
|
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,
|
|
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(
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
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(
|
|
456
|
-
console.log(
|
|
457
|
-
console.log(
|
|
458
|
-
console.log(
|
|
459
|
-
console.log(
|
|
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(
|
|
462
|
-
console.log(
|
|
463
|
-
console.log(
|
|
464
|
-
console.log(
|
|
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(
|
|
541
|
+
console.log(" Trust the process. The evidence will compound.");
|
|
467
542
|
console.log();
|
|
468
543
|
}
|