@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
@@ -11,21 +11,22 @@
11
11
  * r017 (topic map structure over file tree).
12
12
  */
13
13
 
14
- import fs from 'fs';
15
- import path from 'path';
14
+ import fs from "fs";
15
+ import path from "path";
16
16
 
17
- import { fileURLToPath } from 'url';
18
- import { detectSprints } from './detect-sprints.js';
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('--dir');
26
- const ROOT = _dirIdx !== -1 && process.argv[_dirIdx + 1]
27
- ? path.resolve(process.argv[_dirIdx + 1])
28
- : __dirname;
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('out', 'wheat-manifest.json'));
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, 'utf8'));
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('.') || entry.name === 'node_modules') continue;
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('prototypes/')) return 'prototype';
71
- if (normalized.startsWith('research/')) return 'research';
72
- if (normalized.startsWith('output/')) return 'output';
73
- if (normalized.startsWith('evidence/')) return 'evidence';
74
- if (normalized.startsWith('templates/')) return 'template';
75
- if (normalized.startsWith('examples/')) return 'example';
76
- if (normalized.startsWith('test/')) return 'test';
77
- if (normalized.startsWith('docs/')) return 'docs';
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('.json')) return 'config';
80
- if (relPath.endsWith('.js') || relPath.endsWith('.mjs')) return 'script';
81
- if (relPath.endsWith('.md')) return 'docs';
82
- return 'other';
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 = ['stated', 'web', 'documented', 'tested', 'production'];
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 (parsed.sprints || [])) {
107
+ for (const s of parsed.sprints || []) {
107
108
  sprints[s.name] = {
108
- question: s.question || '',
109
- phase: s.phase || 'unknown',
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 { /* fall through to live detection */ }
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 (parsed.sprints || [])) {
129
+ for (const s of parsed.sprints || []) {
127
130
  sprints[s.name] = {
128
- question: s.question || '',
129
- phase: s.phase || 'unknown',
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, 'claims.json'));
148
+ const currentClaims = loadJSON(path.join(ROOT, "claims.json"));
146
149
  if (currentClaims) {
147
- sprints['current'] = {
148
- question: currentClaims.meta?.question || '',
149
- phase: currentClaims.meta?.phase || 'unknown',
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, 'examples');
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(path.join(examplesDir, entry.name, 'claims.json'));
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 || 'unknown',
166
+ question: sprintClaims.meta?.question || "",
167
+ phase: sprintClaims.meta?.phase || "unknown",
163
168
  claims_count: sprintClaims.claims?.length || 0,
164
- path: path.join('examples', entry.name)
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, 'claims.json'));
184
- const compilation = loadJSON(path.join(rootDir, 'compilation.json'));
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] = { claims: [], files: new Set(), sprint: 'current', evidence_level: 'stated' };
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 = ['research', 'prototypes', 'output', 'evidence', 'templates', 'test', 'docs'];
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('.') || entry === 'node_modules') continue;
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 !== 'other') {
242
+ if (type !== "other") {
225
243
  allFiles[entry] = { topics: [], type };
226
244
  }
227
245
  }
228
- } catch { /* skip */ }
246
+ } catch {
247
+ /* skip */
248
+ }
229
249
  }
230
- } catch { /* skip */ }
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
- 'multi-session': ['session', 'server.mjs', 'hooks-config', 'dashboard.html', 'ws.mjs'],
235
- 'multi-sprint': ['sprint', 'examples/'],
236
- 'cartography': ['manifest', 'cartography', 'index'],
237
- 'performance': ['performance', 'evaluation'],
238
- 'compatibility': ['compat']
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 (claim.source?.artifact && filePath.includes(claim.source.artifact.replace(/^.*[/\\]prototypes[/\\]/, 'prototypes/'))) {
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 (opts.sprintsInfo.sprints || [])) {
310
+ for (const s of opts.sprintsInfo.sprints || []) {
275
311
  sprints[s.name] = {
276
- question: s.question || '',
277
- phase: s.phase || 'unknown',
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: 'generate-manifest.js',
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, 'wheat-manifest.json');
308
- fs.writeFileSync(outPath, JSON.stringify(manifest, null, 2) + '\n');
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 = process.argv[1] && fileURLToPath(import.meta.url) === path.resolve(process.argv[1]);
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('Error: claims.json not found or invalid at', path.join(ROOT, 'claims.json'));
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(` Topics: ${result.topicCount} | Files: ${result.fileCount} | Sprints: ${result.sprintCount} | Size: ${(sizeBytes / 1024).toFixed(1)}KB`);
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
  }