@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
|
@@ -11,21 +11,22 @@
|
|
|
11
11
|
* r017 (topic map structure over file tree).
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
-
import fs from
|
|
15
|
-
import path from
|
|
14
|
+
import fs from "fs";
|
|
15
|
+
import path from "path";
|
|
16
16
|
|
|
17
|
-
import { fileURLToPath } from
|
|
18
|
-
import { detectSprints } from
|
|
17
|
+
import { fileURLToPath } from "url";
|
|
18
|
+
import { detectSprints } from "./detect-sprints.js";
|
|
19
19
|
|
|
20
20
|
const __filename = fileURLToPath(import.meta.url);
|
|
21
21
|
const __dirname = path.dirname(__filename);
|
|
22
22
|
|
|
23
23
|
// ─── Target directory ────────────────────────────────────────────────────────
|
|
24
24
|
|
|
25
|
-
const _dirIdx = process.argv.indexOf(
|
|
26
|
-
const ROOT =
|
|
27
|
-
|
|
28
|
-
|
|
25
|
+
const _dirIdx = process.argv.indexOf("--dir");
|
|
26
|
+
const ROOT =
|
|
27
|
+
_dirIdx !== -1 && process.argv[_dirIdx + 1]
|
|
28
|
+
? path.resolve(process.argv[_dirIdx + 1])
|
|
29
|
+
: __dirname;
|
|
29
30
|
|
|
30
31
|
// --- CLI args ---
|
|
31
32
|
const args = process.argv.slice(2);
|
|
@@ -33,14 +34,14 @@ function arg(name, fallback) {
|
|
|
33
34
|
const i = args.indexOf(`--${name}`);
|
|
34
35
|
return i !== -1 && args[i + 1] ? args[i + 1] : fallback;
|
|
35
36
|
}
|
|
36
|
-
const OUT_PATH = path.join(ROOT, arg(
|
|
37
|
+
const OUT_PATH = path.join(ROOT, arg("out", "wheat-manifest.json"));
|
|
37
38
|
|
|
38
39
|
// --- Helpers ---
|
|
39
40
|
|
|
40
41
|
/** Safely parse JSON from a file path; returns null on failure. */
|
|
41
42
|
export function loadJSON(filePath) {
|
|
42
43
|
try {
|
|
43
|
-
return JSON.parse(fs.readFileSync(filePath,
|
|
44
|
+
return JSON.parse(fs.readFileSync(filePath, "utf8"));
|
|
44
45
|
} catch {
|
|
45
46
|
return null;
|
|
46
47
|
}
|
|
@@ -54,10 +55,10 @@ export function walk(dir, filter) {
|
|
|
54
55
|
const full = path.join(dir, entry.name);
|
|
55
56
|
if (entry.isDirectory()) {
|
|
56
57
|
// skip hidden dirs and node_modules
|
|
57
|
-
if (entry.name.startsWith(
|
|
58
|
+
if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
|
|
58
59
|
results.push(...walk(full, filter));
|
|
59
60
|
} else {
|
|
60
|
-
const rel = path.relative(ROOT, full).split(path.sep).join(
|
|
61
|
+
const rel = path.relative(ROOT, full).split(path.sep).join("/");
|
|
61
62
|
if (!filter || filter(rel, entry.name)) results.push(rel);
|
|
62
63
|
}
|
|
63
64
|
}
|
|
@@ -66,25 +67,25 @@ export function walk(dir, filter) {
|
|
|
66
67
|
|
|
67
68
|
/** Determine file type from its path. */
|
|
68
69
|
export function classifyFile(relPath) {
|
|
69
|
-
const normalized = relPath.split(path.sep).join(
|
|
70
|
-
if (normalized.startsWith(
|
|
71
|
-
if (normalized.startsWith(
|
|
72
|
-
if (normalized.startsWith(
|
|
73
|
-
if (normalized.startsWith(
|
|
74
|
-
if (normalized.startsWith(
|
|
75
|
-
if (normalized.startsWith(
|
|
76
|
-
if (normalized.startsWith(
|
|
77
|
-
if (normalized.startsWith(
|
|
70
|
+
const normalized = relPath.split(path.sep).join("/");
|
|
71
|
+
if (normalized.startsWith("prototypes/")) return "prototype";
|
|
72
|
+
if (normalized.startsWith("research/")) return "research";
|
|
73
|
+
if (normalized.startsWith("output/")) return "output";
|
|
74
|
+
if (normalized.startsWith("evidence/")) return "evidence";
|
|
75
|
+
if (normalized.startsWith("templates/")) return "template";
|
|
76
|
+
if (normalized.startsWith("examples/")) return "example";
|
|
77
|
+
if (normalized.startsWith("test/")) return "test";
|
|
78
|
+
if (normalized.startsWith("docs/")) return "docs";
|
|
78
79
|
// root-level files
|
|
79
|
-
if (relPath.endsWith(
|
|
80
|
-
if (relPath.endsWith(
|
|
81
|
-
if (relPath.endsWith(
|
|
82
|
-
return
|
|
80
|
+
if (relPath.endsWith(".json")) return "config";
|
|
81
|
+
if (relPath.endsWith(".js") || relPath.endsWith(".mjs")) return "script";
|
|
82
|
+
if (relPath.endsWith(".md")) return "docs";
|
|
83
|
+
return "other";
|
|
83
84
|
}
|
|
84
85
|
|
|
85
86
|
/** Compute highest evidence tier from a list of claims. */
|
|
86
87
|
export function highestEvidence(claims) {
|
|
87
|
-
const tiers = [
|
|
88
|
+
const tiers = ["stated", "web", "documented", "tested", "production"];
|
|
88
89
|
let max = 0;
|
|
89
90
|
for (const c of claims) {
|
|
90
91
|
const idx = tiers.indexOf(c.evidence);
|
|
@@ -103,10 +104,10 @@ function detectSprintsForManifest() {
|
|
|
103
104
|
try {
|
|
104
105
|
const parsed = JSON.parse(process.env.WHEAT_SPRINTS_CACHE);
|
|
105
106
|
const sprints = {};
|
|
106
|
-
for (const s of
|
|
107
|
+
for (const s of parsed.sprints || []) {
|
|
107
108
|
sprints[s.name] = {
|
|
108
|
-
question: s.question ||
|
|
109
|
-
phase: s.phase ||
|
|
109
|
+
question: s.question || "",
|
|
110
|
+
phase: s.phase || "unknown",
|
|
110
111
|
claims_count: s.claims_count || 0,
|
|
111
112
|
active_claims: s.active_claims || 0,
|
|
112
113
|
path: s.path,
|
|
@@ -116,17 +117,19 @@ function detectSprintsForManifest() {
|
|
|
116
117
|
};
|
|
117
118
|
}
|
|
118
119
|
return sprints;
|
|
119
|
-
} catch {
|
|
120
|
+
} catch {
|
|
121
|
+
/* fall through to live detection */
|
|
122
|
+
}
|
|
120
123
|
}
|
|
121
124
|
|
|
122
125
|
// Try to use the exported function directly
|
|
123
126
|
try {
|
|
124
127
|
const parsed = detectSprints(ROOT);
|
|
125
128
|
const sprints = {};
|
|
126
|
-
for (const s of
|
|
129
|
+
for (const s of parsed.sprints || []) {
|
|
127
130
|
sprints[s.name] = {
|
|
128
|
-
question: s.question ||
|
|
129
|
-
phase: s.phase ||
|
|
131
|
+
question: s.question || "",
|
|
132
|
+
phase: s.phase || "unknown",
|
|
130
133
|
claims_count: s.claims_count || 0,
|
|
131
134
|
active_claims: s.active_claims || 0,
|
|
132
135
|
path: s.path,
|
|
@@ -142,26 +145,28 @@ function detectSprintsForManifest() {
|
|
|
142
145
|
|
|
143
146
|
// Fallback: minimal scan without git info
|
|
144
147
|
const sprints = {};
|
|
145
|
-
const currentClaims = loadJSON(path.join(ROOT,
|
|
148
|
+
const currentClaims = loadJSON(path.join(ROOT, "claims.json"));
|
|
146
149
|
if (currentClaims) {
|
|
147
|
-
sprints[
|
|
148
|
-
question: currentClaims.meta?.question ||
|
|
149
|
-
phase: currentClaims.meta?.phase ||
|
|
150
|
+
sprints["current"] = {
|
|
151
|
+
question: currentClaims.meta?.question || "",
|
|
152
|
+
phase: currentClaims.meta?.phase || "unknown",
|
|
150
153
|
claims_count: currentClaims.claims?.length || 0,
|
|
151
|
-
path:
|
|
154
|
+
path: ".",
|
|
152
155
|
};
|
|
153
156
|
}
|
|
154
|
-
const examplesDir = path.join(ROOT,
|
|
157
|
+
const examplesDir = path.join(ROOT, "examples");
|
|
155
158
|
if (fs.existsSync(examplesDir)) {
|
|
156
159
|
for (const entry of fs.readdirSync(examplesDir, { withFileTypes: true })) {
|
|
157
160
|
if (!entry.isDirectory()) continue;
|
|
158
|
-
const sprintClaims = loadJSON(
|
|
161
|
+
const sprintClaims = loadJSON(
|
|
162
|
+
path.join(examplesDir, entry.name, "claims.json")
|
|
163
|
+
);
|
|
159
164
|
if (sprintClaims) {
|
|
160
165
|
sprints[entry.name] = {
|
|
161
|
-
question: sprintClaims.meta?.question ||
|
|
162
|
-
phase: sprintClaims.meta?.phase ||
|
|
166
|
+
question: sprintClaims.meta?.question || "",
|
|
167
|
+
phase: sprintClaims.meta?.phase || "unknown",
|
|
163
168
|
claims_count: sprintClaims.claims?.length || 0,
|
|
164
|
-
path: path.join(
|
|
169
|
+
path: path.join("examples", entry.name),
|
|
165
170
|
};
|
|
166
171
|
}
|
|
167
172
|
}
|
|
@@ -180,8 +185,8 @@ function detectSprintsForManifest() {
|
|
|
180
185
|
*/
|
|
181
186
|
export function buildManifest(dir, opts = {}) {
|
|
182
187
|
const rootDir = dir || ROOT;
|
|
183
|
-
const claims = loadJSON(path.join(rootDir,
|
|
184
|
-
const compilation = loadJSON(path.join(rootDir,
|
|
188
|
+
const claims = loadJSON(path.join(rootDir, "claims.json"));
|
|
189
|
+
const compilation = loadJSON(path.join(rootDir, "compilation.json"));
|
|
185
190
|
|
|
186
191
|
if (!claims) return null;
|
|
187
192
|
|
|
@@ -190,19 +195,32 @@ export function buildManifest(dir, opts = {}) {
|
|
|
190
195
|
for (const claim of claims.claims) {
|
|
191
196
|
const topic = claim.topic;
|
|
192
197
|
if (!topicMap[topic]) {
|
|
193
|
-
topicMap[topic] = {
|
|
198
|
+
topicMap[topic] = {
|
|
199
|
+
claims: [],
|
|
200
|
+
files: new Set(),
|
|
201
|
+
sprint: "current",
|
|
202
|
+
evidence_level: "stated",
|
|
203
|
+
};
|
|
194
204
|
}
|
|
195
205
|
topicMap[topic].claims.push(claim.id);
|
|
196
206
|
}
|
|
197
207
|
|
|
198
208
|
// Compute evidence levels per topic
|
|
199
209
|
for (const topic of Object.keys(topicMap)) {
|
|
200
|
-
const topicClaims = claims.claims.filter(c => c.topic === topic);
|
|
210
|
+
const topicClaims = claims.claims.filter((c) => c.topic === topic);
|
|
201
211
|
topicMap[topic].evidence_level = highestEvidence(topicClaims);
|
|
202
212
|
}
|
|
203
213
|
|
|
204
214
|
// 2. Scan current sprint directories for files
|
|
205
|
-
const scanDirs = [
|
|
215
|
+
const scanDirs = [
|
|
216
|
+
"research",
|
|
217
|
+
"prototypes",
|
|
218
|
+
"output",
|
|
219
|
+
"evidence",
|
|
220
|
+
"templates",
|
|
221
|
+
"test",
|
|
222
|
+
"docs",
|
|
223
|
+
];
|
|
206
224
|
const allFiles = {};
|
|
207
225
|
|
|
208
226
|
for (const d of scanDirs) {
|
|
@@ -216,39 +234,57 @@ export function buildManifest(dir, opts = {}) {
|
|
|
216
234
|
// Also include root-level scripts/configs
|
|
217
235
|
try {
|
|
218
236
|
for (const entry of fs.readdirSync(rootDir)) {
|
|
219
|
-
if (entry.startsWith(
|
|
237
|
+
if (entry.startsWith(".") || entry === "node_modules") continue;
|
|
220
238
|
const full = path.join(rootDir, entry);
|
|
221
239
|
try {
|
|
222
240
|
if (fs.statSync(full).isFile()) {
|
|
223
241
|
const type = classifyFile(entry);
|
|
224
|
-
if (type !==
|
|
242
|
+
if (type !== "other") {
|
|
225
243
|
allFiles[entry] = { topics: [], type };
|
|
226
244
|
}
|
|
227
245
|
}
|
|
228
|
-
} catch {
|
|
246
|
+
} catch {
|
|
247
|
+
/* skip */
|
|
248
|
+
}
|
|
229
249
|
}
|
|
230
|
-
} catch {
|
|
250
|
+
} catch {
|
|
251
|
+
/* skip */
|
|
252
|
+
}
|
|
231
253
|
|
|
232
254
|
// 3. Map files to topics using claim source artifacts and keyword heuristics
|
|
233
255
|
const topicKeywords = {
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
256
|
+
"multi-session": [
|
|
257
|
+
"session",
|
|
258
|
+
"server.mjs",
|
|
259
|
+
"hooks-config",
|
|
260
|
+
"dashboard.html",
|
|
261
|
+
"ws.mjs",
|
|
262
|
+
],
|
|
263
|
+
"multi-sprint": ["sprint", "examples/"],
|
|
264
|
+
cartography: ["manifest", "cartography", "index"],
|
|
265
|
+
performance: ["performance", "evaluation"],
|
|
266
|
+
compatibility: ["compat"],
|
|
239
267
|
};
|
|
240
268
|
|
|
241
269
|
for (const [filePath, fileInfo] of Object.entries(allFiles)) {
|
|
242
270
|
const lower = filePath.toLowerCase();
|
|
243
271
|
|
|
244
272
|
for (const [topic, keywords] of Object.entries(topicKeywords)) {
|
|
245
|
-
if (keywords.some(kw => lower.includes(kw))) {
|
|
273
|
+
if (keywords.some((kw) => lower.includes(kw))) {
|
|
246
274
|
if (!fileInfo.topics.includes(topic)) fileInfo.topics.push(topic);
|
|
247
275
|
}
|
|
248
276
|
}
|
|
249
277
|
|
|
250
278
|
for (const claim of claims.claims) {
|
|
251
|
-
if (
|
|
279
|
+
if (
|
|
280
|
+
claim.source?.artifact &&
|
|
281
|
+
filePath.includes(
|
|
282
|
+
claim.source.artifact.replace(
|
|
283
|
+
/^.*[/\\]prototypes[/\\]/,
|
|
284
|
+
"prototypes/"
|
|
285
|
+
)
|
|
286
|
+
)
|
|
287
|
+
) {
|
|
252
288
|
if (!fileInfo.topics.includes(claim.topic)) {
|
|
253
289
|
fileInfo.topics.push(claim.topic);
|
|
254
290
|
}
|
|
@@ -271,10 +307,10 @@ export function buildManifest(dir, opts = {}) {
|
|
|
271
307
|
let sprints;
|
|
272
308
|
if (opts.sprintsInfo) {
|
|
273
309
|
sprints = {};
|
|
274
|
-
for (const s of
|
|
310
|
+
for (const s of opts.sprintsInfo.sprints || []) {
|
|
275
311
|
sprints[s.name] = {
|
|
276
|
-
question: s.question ||
|
|
277
|
-
phase: s.phase ||
|
|
312
|
+
question: s.question || "",
|
|
313
|
+
phase: s.phase || "unknown",
|
|
278
314
|
claims_count: s.claims_count || 0,
|
|
279
315
|
active_claims: s.active_claims || 0,
|
|
280
316
|
path: s.path,
|
|
@@ -297,15 +333,15 @@ export function buildManifest(dir, opts = {}) {
|
|
|
297
333
|
|
|
298
334
|
const manifest = {
|
|
299
335
|
generated: new Date().toISOString(),
|
|
300
|
-
generator:
|
|
336
|
+
generator: "generate-manifest.js",
|
|
301
337
|
claims_hash: compilation?.claims_hash || null,
|
|
302
338
|
topics: topicMap,
|
|
303
339
|
sprints,
|
|
304
|
-
files: topicFiles
|
|
340
|
+
files: topicFiles,
|
|
305
341
|
};
|
|
306
342
|
|
|
307
|
-
const outPath = path.join(rootDir,
|
|
308
|
-
fs.writeFileSync(outPath, JSON.stringify(manifest, null, 2) +
|
|
343
|
+
const outPath = path.join(rootDir, "wheat-manifest.json");
|
|
344
|
+
fs.writeFileSync(outPath, JSON.stringify(manifest, null, 2) + "\n");
|
|
309
345
|
|
|
310
346
|
return {
|
|
311
347
|
manifest,
|
|
@@ -317,19 +353,30 @@ export function buildManifest(dir, opts = {}) {
|
|
|
317
353
|
|
|
318
354
|
// --- Main (only when run directly) ---
|
|
319
355
|
|
|
320
|
-
const isMain =
|
|
356
|
+
const isMain =
|
|
357
|
+
process.argv[1] &&
|
|
358
|
+
fileURLToPath(import.meta.url) === path.resolve(process.argv[1]);
|
|
321
359
|
|
|
322
360
|
if (isMain) {
|
|
323
361
|
const t0 = performance.now();
|
|
324
362
|
|
|
325
363
|
const result = buildManifest(ROOT);
|
|
326
364
|
if (!result) {
|
|
327
|
-
console.error(
|
|
365
|
+
console.error(
|
|
366
|
+
"Error: claims.json not found or invalid at",
|
|
367
|
+
path.join(ROOT, "claims.json")
|
|
368
|
+
);
|
|
328
369
|
process.exit(1);
|
|
329
370
|
}
|
|
330
371
|
|
|
331
372
|
const elapsed = (performance.now() - t0).toFixed(1);
|
|
332
373
|
const sizeBytes = Buffer.byteLength(JSON.stringify(result.manifest, null, 2));
|
|
333
374
|
console.log(`wheat-manifest.json generated in ${elapsed}ms`);
|
|
334
|
-
console.log(
|
|
375
|
+
console.log(
|
|
376
|
+
` Topics: ${result.topicCount} | Files: ${
|
|
377
|
+
result.fileCount
|
|
378
|
+
} | Sprints: ${result.sprintCount} | Size: ${(sizeBytes / 1024).toFixed(
|
|
379
|
+
1
|
|
380
|
+
)}KB`
|
|
381
|
+
);
|
|
335
382
|
}
|