@grainulation/wheat 1.0.0

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 (40) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +136 -0
  3. package/bin/wheat.js +193 -0
  4. package/compiler/detect-sprints.js +319 -0
  5. package/compiler/generate-manifest.js +280 -0
  6. package/compiler/wheat-compiler.js +1229 -0
  7. package/lib/compiler.js +35 -0
  8. package/lib/connect.js +418 -0
  9. package/lib/disconnect.js +188 -0
  10. package/lib/guard.js +151 -0
  11. package/lib/index.js +14 -0
  12. package/lib/init.js +457 -0
  13. package/lib/install-prompt.js +186 -0
  14. package/lib/quickstart.js +276 -0
  15. package/lib/serve-mcp.js +509 -0
  16. package/lib/server.js +391 -0
  17. package/lib/stats.js +184 -0
  18. package/lib/status.js +135 -0
  19. package/lib/update.js +71 -0
  20. package/package.json +53 -0
  21. package/public/index.html +1798 -0
  22. package/templates/claude.md +122 -0
  23. package/templates/commands/blind-spot.md +47 -0
  24. package/templates/commands/brief.md +73 -0
  25. package/templates/commands/calibrate.md +39 -0
  26. package/templates/commands/challenge.md +72 -0
  27. package/templates/commands/connect.md +104 -0
  28. package/templates/commands/evaluate.md +80 -0
  29. package/templates/commands/feedback.md +60 -0
  30. package/templates/commands/handoff.md +53 -0
  31. package/templates/commands/init.md +68 -0
  32. package/templates/commands/merge.md +51 -0
  33. package/templates/commands/present.md +52 -0
  34. package/templates/commands/prototype.md +68 -0
  35. package/templates/commands/replay.md +61 -0
  36. package/templates/commands/research.md +73 -0
  37. package/templates/commands/resolve.md +42 -0
  38. package/templates/commands/status.md +56 -0
  39. package/templates/commands/witness.md +79 -0
  40. package/templates/explainer.html +343 -0
@@ -0,0 +1,280 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * wheat-manifest.json generator
4
+ *
5
+ * Reads claims.json, compilation.json, and scans the repo directory structure
6
+ * to produce a topic-map manifest. Zero npm dependencies.
7
+ *
8
+ * Usage: node generate-manifest.js [--out wheat-manifest.json] [--dir <path>]
9
+ *
10
+ * Based on research claims r011 (single machine-readable manifest) and
11
+ * r017 (topic map structure over file tree).
12
+ */
13
+
14
+ import fs from 'fs';
15
+ import path from 'path';
16
+ import { execFileSync } from 'child_process';
17
+ import { fileURLToPath } from 'url';
18
+ import { detectSprints } from './detect-sprints.js';
19
+
20
+ const __filename = fileURLToPath(import.meta.url);
21
+ const __dirname = path.dirname(__filename);
22
+
23
+ // ─── Target directory ────────────────────────────────────────────────────────
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;
29
+
30
+ // --- CLI args ---
31
+ const args = process.argv.slice(2);
32
+ function arg(name, fallback) {
33
+ const i = args.indexOf(`--${name}`);
34
+ return i !== -1 && args[i + 1] ? args[i + 1] : fallback;
35
+ }
36
+ const OUT_PATH = path.join(ROOT, arg('out', 'wheat-manifest.json'));
37
+
38
+ // --- Helpers ---
39
+
40
+ /** Safely parse JSON from a file path; returns null on failure. */
41
+ export function loadJSON(filePath) {
42
+ try {
43
+ return JSON.parse(fs.readFileSync(filePath, 'utf8'));
44
+ } catch {
45
+ return null;
46
+ }
47
+ }
48
+
49
+ /** Recursively list files under dir, returning paths relative to ROOT. */
50
+ export function walk(dir, filter) {
51
+ const results = [];
52
+ if (!fs.existsSync(dir)) return results;
53
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
54
+ const full = path.join(dir, entry.name);
55
+ if (entry.isDirectory()) {
56
+ // skip hidden dirs and node_modules
57
+ if (entry.name.startsWith('.') || entry.name === 'node_modules') continue;
58
+ results.push(...walk(full, filter));
59
+ } else {
60
+ const rel = path.relative(ROOT, full).split(path.sep).join('/');
61
+ if (!filter || filter(rel, entry.name)) results.push(rel);
62
+ }
63
+ }
64
+ return results;
65
+ }
66
+
67
+ /** Determine file type from its path. */
68
+ 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';
78
+ // 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';
83
+ }
84
+
85
+ /** Compute highest evidence tier from a list of claims. */
86
+ export function highestEvidence(claims) {
87
+ const tiers = ['stated', 'web', 'documented', 'tested', 'production'];
88
+ let max = 0;
89
+ for (const c of claims) {
90
+ const idx = tiers.indexOf(c.evidence);
91
+ if (idx > max) max = idx;
92
+ }
93
+ return tiers[max];
94
+ }
95
+
96
+ /**
97
+ * Detect sprints using detect-sprints.js (git-based, no config pointer).
98
+ * Falls back to a minimal scan if detect-sprints.js is unavailable.
99
+ */
100
+ function detectSprintsForManifest() {
101
+ // Try to use the exported function directly
102
+ try {
103
+ const parsed = detectSprints(ROOT);
104
+ const sprints = {};
105
+ for (const s of (parsed.sprints || [])) {
106
+ sprints[s.name] = {
107
+ question: s.question || '',
108
+ phase: s.phase || 'unknown',
109
+ claims_count: s.claims_count || 0,
110
+ active_claims: s.active_claims || 0,
111
+ path: s.path,
112
+ status: s.status,
113
+ last_git_activity: s.last_git_activity,
114
+ git_commit_count: s.git_commit_count,
115
+ };
116
+ }
117
+ return sprints;
118
+ } catch {
119
+ // Fall through to naive fallback
120
+ }
121
+
122
+ // Fallback: minimal scan without git info
123
+ const sprints = {};
124
+ const currentClaims = loadJSON(path.join(ROOT, 'claims.json'));
125
+ if (currentClaims) {
126
+ sprints['current'] = {
127
+ question: currentClaims.meta?.question || '',
128
+ phase: currentClaims.meta?.phase || 'unknown',
129
+ claims_count: currentClaims.claims?.length || 0,
130
+ path: '.'
131
+ };
132
+ }
133
+ const examplesDir = path.join(ROOT, 'examples');
134
+ if (fs.existsSync(examplesDir)) {
135
+ for (const entry of fs.readdirSync(examplesDir, { withFileTypes: true })) {
136
+ if (!entry.isDirectory()) continue;
137
+ const sprintClaims = loadJSON(path.join(examplesDir, entry.name, 'claims.json'));
138
+ if (sprintClaims) {
139
+ sprints[entry.name] = {
140
+ question: sprintClaims.meta?.question || '',
141
+ phase: sprintClaims.meta?.phase || 'unknown',
142
+ claims_count: sprintClaims.claims?.length || 0,
143
+ path: path.join('examples', entry.name)
144
+ };
145
+ }
146
+ }
147
+ }
148
+ return sprints;
149
+ }
150
+
151
+ // --- Main (only when run directly) ---
152
+
153
+ const isMain = process.argv[1] && fileURLToPath(import.meta.url) === path.resolve(process.argv[1]);
154
+
155
+ if (isMain) {
156
+ const t0 = performance.now();
157
+
158
+ const claims = loadJSON(path.join(ROOT, 'claims.json'));
159
+ const compilation = loadJSON(path.join(ROOT, 'compilation.json'));
160
+
161
+ if (!claims) {
162
+ console.error('Error: claims.json not found or invalid at', path.join(ROOT, 'claims.json'));
163
+ process.exit(1);
164
+ }
165
+
166
+ // 1. Build topic map from claims
167
+ const topicMap = {};
168
+ for (const claim of claims.claims) {
169
+ const topic = claim.topic;
170
+ if (!topicMap[topic]) {
171
+ topicMap[topic] = { claims: [], files: new Set(), sprint: 'current', evidence_level: 'stated' };
172
+ }
173
+ topicMap[topic].claims.push(claim.id);
174
+ }
175
+
176
+ // Compute evidence levels per topic
177
+ for (const topic of Object.keys(topicMap)) {
178
+ const topicClaims = claims.claims.filter(c => c.topic === topic);
179
+ topicMap[topic].evidence_level = highestEvidence(topicClaims);
180
+ }
181
+
182
+ // 2. Scan current sprint directories for files
183
+ const scanDirs = ['research', 'prototypes', 'output', 'evidence', 'templates', 'test', 'docs'];
184
+ const allFiles = {};
185
+
186
+ for (const dir of scanDirs) {
187
+ const files = walk(path.join(ROOT, dir));
188
+ for (const f of files) {
189
+ const type = classifyFile(f);
190
+ allFiles[f] = { topics: [], type };
191
+ }
192
+ }
193
+
194
+ // Also include root-level scripts/configs
195
+ for (const entry of fs.readdirSync(ROOT)) {
196
+ if (entry.startsWith('.') || entry === 'node_modules') continue;
197
+ const full = path.join(ROOT, entry);
198
+ try {
199
+ if (fs.statSync(full).isFile()) {
200
+ const type = classifyFile(entry);
201
+ if (type !== 'other') {
202
+ allFiles[entry] = { topics: [], type };
203
+ }
204
+ }
205
+ } catch { /* skip */ }
206
+ }
207
+
208
+ // 3. Map files to topics using claim source artifacts and keyword heuristics
209
+ const topicKeywords = {
210
+ 'multi-session': ['session', 'server.mjs', 'hooks-config', 'dashboard.html', 'ws.mjs'],
211
+ 'multi-sprint': ['sprint', 'examples/'],
212
+ 'cartography': ['manifest', 'cartography', 'index'],
213
+ 'performance': ['performance', 'evaluation'],
214
+ 'compatibility': ['compat']
215
+ };
216
+
217
+ for (const [filePath, fileInfo] of Object.entries(allFiles)) {
218
+ const lower = filePath.toLowerCase();
219
+
220
+ // Heuristic: match file paths to topics via keywords
221
+ for (const [topic, keywords] of Object.entries(topicKeywords)) {
222
+ if (keywords.some(kw => lower.includes(kw))) {
223
+ if (!fileInfo.topics.includes(topic)) fileInfo.topics.push(topic);
224
+ }
225
+ }
226
+
227
+ // Claims that reference files as artifacts
228
+ for (const claim of claims.claims) {
229
+ if (claim.source?.artifact && filePath.includes(claim.source.artifact.replace(/^.*[/\\]prototypes[/\\]/, 'prototypes/'))) {
230
+ if (!fileInfo.topics.includes(claim.topic)) {
231
+ fileInfo.topics.push(claim.topic);
232
+ }
233
+ }
234
+ }
235
+
236
+ // Add files to topic map
237
+ for (const topic of fileInfo.topics) {
238
+ if (topicMap[topic]) {
239
+ topicMap[topic].files.add(filePath);
240
+ }
241
+ }
242
+ }
243
+
244
+ // 4. Convert Sets to arrays for JSON serialization
245
+ for (const topic of Object.keys(topicMap)) {
246
+ topicMap[topic].files = [...topicMap[topic].files].sort();
247
+ }
248
+
249
+ // 5. Detect sprints
250
+ const sprints = detectSprintsForManifest();
251
+
252
+ // 6. Build final manifest
253
+ const topicFiles = {};
254
+ for (const [filePath, info] of Object.entries(allFiles)) {
255
+ if (info.topics.length > 0) {
256
+ topicFiles[filePath] = info;
257
+ }
258
+ }
259
+
260
+ const manifest = {
261
+ generated: new Date().toISOString(),
262
+ generator: 'generate-manifest.js',
263
+ claims_hash: compilation?.claims_hash || null,
264
+ topics: topicMap,
265
+ sprints,
266
+ files: topicFiles
267
+ };
268
+
269
+ fs.writeFileSync(OUT_PATH, JSON.stringify(manifest, null, 2) + '\n');
270
+ const elapsed = (performance.now() - t0).toFixed(1);
271
+
272
+ // Summary
273
+ const topicCount = Object.keys(topicMap).length;
274
+ const fileCount = Object.keys(topicFiles).length;
275
+ const sprintCount = Object.keys(sprints).length;
276
+ const sizeBytes = Buffer.byteLength(JSON.stringify(manifest, null, 2));
277
+
278
+ console.log(`wheat-manifest.json generated in ${elapsed}ms`);
279
+ console.log(` Topics: ${topicCount} | Files: ${fileCount} | Sprints: ${sprintCount} | Size: ${(sizeBytes / 1024).toFixed(1)}KB`);
280
+ }