@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,186 @@
1
+ /**
2
+ * install-prompt.js — Track npx usage and suggest global/local install
3
+ *
4
+ * Two exports:
5
+ * track(command) — Increment invocation count (sync, <5ms)
6
+ * maybePrompt(command) — Print install suggestion if thresholds met
7
+ *
8
+ * Usage data stored in ~/.grainulation/usage.json
9
+ * Respects --quiet flag, WHEAT_NO_INSTALL_PROMPT=1 env var.
10
+ * Fails silently on any I/O error — never blocks the CLI.
11
+ *
12
+ * Based on research claim r129: no existing CLI tool does npx-to-install
13
+ * nudging. This is an unclaimed UX innovation.
14
+ */
15
+
16
+ import { readFileSync, writeFileSync, mkdirSync } from 'fs';
17
+ import path from 'path';
18
+ import { homedir } from 'os';
19
+
20
+ const USAGE_DIR = path.join(homedir(), '.grainulation');
21
+ const USAGE_FILE = path.join(USAGE_DIR, 'usage.json');
22
+
23
+ const THRESHOLDS = {
24
+ suggest_global: 3,
25
+ suggest_local: 5,
26
+ prominent: 10,
27
+ };
28
+
29
+ // ─── Internal helpers ────────────────────────────────────────────────────────
30
+
31
+ function readUsage() {
32
+ try {
33
+ return JSON.parse(readFileSync(USAGE_FILE, 'utf8'));
34
+ } catch {
35
+ return { commands: {}, prompted: false, installed: false };
36
+ }
37
+ }
38
+
39
+ function writeUsage(data) {
40
+ try {
41
+ mkdirSync(USAGE_DIR, { recursive: true });
42
+ writeFileSync(USAGE_FILE, JSON.stringify(data, null, 2) + '\n', 'utf8');
43
+ } catch {
44
+ // fail silently
45
+ }
46
+ }
47
+
48
+ /**
49
+ * Detect whether wheat was invoked via npx.
50
+ * npx stores executables in a temporary _npx cache directory.
51
+ */
52
+ function isNpxInvocation() {
53
+ const execPath = process.argv[1] || '';
54
+ return execPath.includes('_npx');
55
+ }
56
+
57
+ /**
58
+ * Detect if wheat is installed globally or locally (not via npx).
59
+ *
60
+ * Local install: process.argv[1] contains node_modules
61
+ * Global install: process.argv[1] is in a known global bin path
62
+ * (no _npx, no node_modules — resolves to /usr/local/bin, nvm, volta, etc.)
63
+ *
64
+ * We intentionally do NOT detect "running from source" (node bin/wheat.js)
65
+ * as installed, since that's a dev workflow where the prompt is also irrelevant
66
+ * (isNpxInvocation returns false, so prompts are already suppressed).
67
+ */
68
+ function isInstalled() {
69
+ const execPath = process.argv[1] || '';
70
+
71
+ // Local install: running from node_modules
72
+ if (execPath.includes('node_modules') && !execPath.includes('_npx')) {
73
+ return true;
74
+ }
75
+
76
+ // Global install: binary is in a well-known global prefix
77
+ // (not npx, not node_modules — must be a permanent install)
78
+ if (!execPath.includes('_npx') && !execPath.includes('node_modules')) {
79
+ const knownGlobalDirs = [
80
+ '/usr/local/bin',
81
+ '/usr/bin',
82
+ path.join(homedir(), '.npm-global'),
83
+ path.join(homedir(), '.nvm'),
84
+ path.join(homedir(), '.volta'),
85
+ path.join(homedir(), '.local', 'bin'),
86
+ ];
87
+ for (const prefix of knownGlobalDirs) {
88
+ if (execPath.startsWith(prefix)) return true;
89
+ }
90
+ }
91
+
92
+ return false;
93
+ }
94
+
95
+ // ─── Public API ──────────────────────────────────────────────────────────────
96
+
97
+ /**
98
+ * Track an npx invocation of the given command.
99
+ * No-op if already installed or not running via npx.
100
+ * Designed to add <5ms overhead — all sync I/O on a small JSON file.
101
+ */
102
+ export function track(command) {
103
+ try {
104
+ if (!command) return;
105
+
106
+ // If user has permanently installed wheat, record that and stop
107
+ if (isInstalled()) {
108
+ const data = readUsage();
109
+ if (!data.installed) {
110
+ data.installed = true;
111
+ writeUsage(data);
112
+ }
113
+ return;
114
+ }
115
+
116
+ // Only count npx invocations
117
+ if (!isNpxInvocation()) return;
118
+
119
+ const data = readUsage();
120
+ if (data.installed) return;
121
+
122
+ if (!data.commands) data.commands = {};
123
+ data.commands[command] = (data.commands[command] || 0) + 1;
124
+ writeUsage(data);
125
+ } catch {
126
+ // fail silently — never interfere with the command
127
+ }
128
+ }
129
+
130
+ /**
131
+ * Print an install suggestion if usage thresholds are met.
132
+ * No-op if:
133
+ * - Already installed (globally or locally)
134
+ * - --quiet flag is present
135
+ * - WHEAT_NO_INSTALL_PROMPT=1 env var is set
136
+ * - Not running via npx
137
+ * - Below threshold
138
+ *
139
+ * Messages go to stderr so they don't pollute stdout (piped output stays clean).
140
+ * Never blocks, never prompts interactively, never forces.
141
+ */
142
+ export function maybePrompt(command) {
143
+ try {
144
+ if (process.env.WHEAT_NO_INSTALL_PROMPT === '1') return;
145
+ if (process.argv.includes('--quiet')) return;
146
+ if (isInstalled()) return;
147
+ if (!isNpxInvocation()) return;
148
+ if (!command) return;
149
+
150
+ const data = readUsage();
151
+ if (data.installed) return;
152
+
153
+ const count = (data.commands && data.commands[command]) || 0;
154
+ const total = Object.values(data.commands || {}).reduce((a, b) => a + b, 0);
155
+
156
+ // Use the higher of per-command and total count for threshold comparison.
157
+ // A user who runs 2x init + 2x compile = 4 total should start seeing prompts
158
+ // even though neither command alone hit 3.
159
+ const effective = Math.max(count, total);
160
+
161
+ if (effective >= THRESHOLDS.prominent) {
162
+ // 10+ invocations: more visible, but still non-blocking
163
+ process.stderr.write(
164
+ `\n wheat: You've used npx ${total} times (${command}: ${count}).` +
165
+ `\n Install for instant startup and offline use:` +
166
+ `\n npm i -g @grainulation/wheat (global)` +
167
+ `\n npm i -D @grainulation/wheat (project dev dep)\n\n`
168
+ );
169
+ } else if (effective >= THRESHOLDS.suggest_local) {
170
+ // 5-9 invocations: suggest both global and local
171
+ process.stderr.write(
172
+ ` wheat: You've run wheat via npx ${total} times. ` +
173
+ `For instant startup: npm i -g @grainulation/wheat\n` +
174
+ ` Or add to this project: npm i -D @grainulation/wheat\n`
175
+ );
176
+ } else if (effective >= THRESHOLDS.suggest_global) {
177
+ // 3-4 invocations: one-liner, suggest global only
178
+ process.stderr.write(
179
+ ` wheat: You've run wheat via npx ${count} times. ` +
180
+ `For instant startup: npm i -g @grainulation/wheat\n`
181
+ );
182
+ }
183
+ } catch {
184
+ // fail silently
185
+ }
186
+ }
@@ -0,0 +1,276 @@
1
+ /**
2
+ * wheat quickstart — zero-to-dashboard in under 90 seconds
3
+ *
4
+ * Creates a demo sprint with pre-seeded research claims,
5
+ * compiles them, and opens the dashboard. Designed to show
6
+ * wheat's value in the shortest possible time.
7
+ *
8
+ * Usage:
9
+ * wheat quickstart [--dir <path>] [--no-open] [--port 9092]
10
+ *
11
+ * Zero npm dependencies.
12
+ */
13
+
14
+ import fs from 'fs';
15
+ import path from 'path';
16
+ import { execFileSync, execFile } from 'child_process';
17
+ import { fileURLToPath } from 'url';
18
+
19
+ const __filename = fileURLToPath(import.meta.url);
20
+ const __dirname = path.dirname(__filename);
21
+
22
+ function target(dir, ...segments) {
23
+ return path.join(dir, ...segments);
24
+ }
25
+
26
+ function packageRoot() {
27
+ return path.resolve(__dirname, '..');
28
+ }
29
+
30
+ function now() {
31
+ return new Date().toISOString();
32
+ }
33
+
34
+ // ── Demo sprint data ────────────────────────────────────────────────────────
35
+
36
+ const DEMO_QUESTION = 'Should we migrate our REST API to GraphQL?';
37
+ const DEMO_AUDIENCE = ['backend team', 'frontend team', 'CTO'];
38
+ const DEMO_CONSTRAINTS = [
39
+ 'Must maintain backward compatibility with existing REST clients for 6 months',
40
+ 'Team has 3 backend engineers, none with production GraphQL experience',
41
+ 'Current API serves 200 req/s peak, 50ms p95 latency — cannot regress',
42
+ ];
43
+ const DEMO_DONE = 'A recommendation with evidence: migrate, don\'t migrate, or hybrid approach, with risk assessment and migration timeline.';
44
+
45
+ const DEMO_CLAIMS = [
46
+ {
47
+ id: 'd001', type: 'constraint', topic: 'backward-compatibility',
48
+ content: 'Must maintain backward compatibility with existing REST clients for 6 months.',
49
+ source: { origin: 'stakeholder', artifact: null, connector: null },
50
+ evidence: 'stated', status: 'active', phase_added: 'define',
51
+ tags: ['api', 'constraint'],
52
+ },
53
+ {
54
+ id: 'd002', type: 'constraint', topic: 'team-experience',
55
+ content: 'Team has 3 backend engineers, none with production GraphQL experience.',
56
+ source: { origin: 'stakeholder', artifact: null, connector: null },
57
+ evidence: 'stated', status: 'active', phase_added: 'define',
58
+ tags: ['team', 'constraint'],
59
+ },
60
+ {
61
+ id: 'd003', type: 'constraint', topic: 'performance-baseline',
62
+ content: 'Current API serves 200 req/s peak, 50ms p95 latency — cannot regress.',
63
+ source: { origin: 'stakeholder', artifact: null, connector: null },
64
+ evidence: 'stated', status: 'active', phase_added: 'define',
65
+ tags: ['performance', 'constraint'],
66
+ },
67
+ {
68
+ id: 'd004', type: 'constraint', topic: 'done-criteria',
69
+ content: 'Done looks like: A recommendation with evidence — migrate, don\'t migrate, or hybrid approach, with risk assessment and migration timeline.',
70
+ source: { origin: 'stakeholder', artifact: null, connector: null },
71
+ evidence: 'stated', status: 'active', phase_added: 'define',
72
+ tags: ['done-criteria'],
73
+ },
74
+ {
75
+ id: 'r001', type: 'factual', topic: 'graphql-adoption-2026',
76
+ content: 'GraphQL adoption in production backends reached 29% in 2025 (Postman State of APIs). However, 67% of teams that adopted GraphQL still maintain parallel REST endpoints for backward compatibility.',
77
+ source: { origin: 'research', artifact: 'research/graphql-landscape.md', connector: null },
78
+ evidence: 'web', status: 'active', phase_added: 'research',
79
+ tags: ['graphql', 'adoption', 'industry'],
80
+ },
81
+ {
82
+ id: 'r002', type: 'factual', topic: 'graphql-performance',
83
+ content: 'GraphQL resolvers add 2-8ms overhead per request compared to direct REST handlers in Node.js benchmarks. For nested queries with N+1 patterns, latency can spike to 200-500ms without DataLoader batching. With DataLoader, overhead drops to 5-15ms for complex queries.',
84
+ source: { origin: 'research', artifact: 'research/graphql-landscape.md', connector: null },
85
+ evidence: 'web', status: 'active', phase_added: 'research',
86
+ tags: ['graphql', 'performance', 'latency'],
87
+ },
88
+ {
89
+ id: 'r003', type: 'risk', topic: 'learning-curve',
90
+ content: 'Teams without GraphQL experience report 3-6 month ramp-up period before achieving parity with REST productivity. Schema design mistakes in the first 2 months often require breaking changes later.',
91
+ source: { origin: 'research', artifact: 'research/graphql-landscape.md', connector: null },
92
+ evidence: 'web', status: 'active', phase_added: 'research',
93
+ tags: ['graphql', 'risk', 'team'],
94
+ },
95
+ {
96
+ id: 'r004', type: 'recommendation', topic: 'hybrid-approach',
97
+ content: 'A hybrid approach (GraphQL gateway over existing REST services) provides incremental adoption without rewriting the backend. Apollo Federation and GraphQL Mesh both support this pattern. The frontend gets GraphQL benefits while the backend stays REST until individual services are ready to migrate.',
98
+ source: { origin: 'research', artifact: 'research/graphql-landscape.md', connector: null },
99
+ evidence: 'web', status: 'active', phase_added: 'research',
100
+ tags: ['graphql', 'recommendation', 'architecture'],
101
+ },
102
+ {
103
+ id: 'r005', type: 'estimate', topic: 'migration-timeline',
104
+ content: 'Estimated timeline for full GraphQL migration with 3 engineers: 2-3 months for schema design + gateway setup, 4-6 months for service-by-service migration, 2 months for REST deprecation. Total: 8-11 months. Hybrid approach can start delivering value in month 2.',
105
+ source: { origin: 'research', artifact: 'research/graphql-landscape.md', connector: null },
106
+ evidence: 'web', status: 'active', phase_added: 'research',
107
+ tags: ['graphql', 'estimate', 'timeline'],
108
+ },
109
+ {
110
+ id: 'r006', type: 'factual', topic: 'graphql-tooling',
111
+ content: 'Apollo Server v4 is the most adopted GraphQL server (62% market share among Node.js GraphQL users). Alternatives: Mercurius (Fastify-native, 30% faster in benchmarks), GraphQL Yoga (from The Guild, best DX), Pothos (code-first schema builder).',
112
+ source: { origin: 'research', artifact: 'research/graphql-landscape.md', connector: null },
113
+ evidence: 'web', status: 'active', phase_added: 'research',
114
+ tags: ['graphql', 'tooling', 'ecosystem'],
115
+ },
116
+ {
117
+ id: 'x001', type: 'factual', topic: 'graphql-performance',
118
+ content: 'The 2-8ms overhead claim (r002) understates the real-world impact. In production, GraphQL query parsing, validation, and execution planning add consistent 10-20ms overhead for medium-complexity queries. This matters when the p95 budget is 50ms.',
119
+ source: { origin: 'challenge', artifact: null, connector: null },
120
+ evidence: 'web', status: 'active', phase_added: 'research',
121
+ conflicts_with: ['r002'],
122
+ tags: ['graphql', 'performance', 'challenge'],
123
+ },
124
+ ];
125
+
126
+ // ── Main ────────────────────────────────────────────────────────────────────
127
+
128
+ export async function run(dir, args) {
129
+ const startTime = Date.now();
130
+ const flags = {};
131
+ for (let i = 0; i < args.length; i++) {
132
+ if (args[i] === '--no-open') flags.noOpen = true;
133
+ else if (args[i] === '--port' && args[i + 1]) { flags.port = args[++i]; }
134
+ }
135
+
136
+ const claimsPath = target(dir, 'claims.json');
137
+ if (fs.existsSync(claimsPath) && !args.includes('--force')) {
138
+ console.log();
139
+ console.log(' A sprint already exists here. Use --force to overwrite.');
140
+ console.log();
141
+ process.exit(1);
142
+ }
143
+
144
+ console.log();
145
+ console.log(' \x1b[1m\x1b[33mwheat quickstart\x1b[0m — zero to dashboard');
146
+ console.log(' ─────────────────────────────────────────');
147
+ console.log();
148
+
149
+ // Step 1: Create claims.json with demo data
150
+ const timestamp = now();
151
+ const claimsData = {
152
+ meta: {
153
+ question: DEMO_QUESTION,
154
+ initiated: new Date().toISOString().split('T')[0],
155
+ audience: DEMO_AUDIENCE,
156
+ phase: 'research',
157
+ connectors: [],
158
+ dismissed_blind_spots: [],
159
+ merged_from: [],
160
+ },
161
+ claims: DEMO_CLAIMS.map(c => ({
162
+ ...c,
163
+ timestamp,
164
+ conflicts_with: c.conflicts_with || [],
165
+ resolved_by: c.resolved_by || null,
166
+ })),
167
+ };
168
+
169
+ fs.writeFileSync(claimsPath, JSON.stringify(claimsData, null, 2) + '\n');
170
+ const elapsed1 = Date.now() - startTime;
171
+ console.log(` \x1b[32m+\x1b[0m claims.json (${claimsData.claims.length} claims seeded) \x1b[2m${elapsed1}ms\x1b[0m`);
172
+
173
+ // Step 2: Create CLAUDE.md
174
+ const templatePath = path.join(packageRoot(), 'templates', 'claude.md');
175
+ let claudeMd;
176
+ try {
177
+ claudeMd = fs.readFileSync(templatePath, 'utf8')
178
+ .replace(/\{\{QUESTION\}\}/g, DEMO_QUESTION)
179
+ .replace(/\{\{AUDIENCE\}\}/g, DEMO_AUDIENCE.join(', '))
180
+ .replace(/\{\{CONSTRAINTS\}\}/g, DEMO_CONSTRAINTS.map(c => `- ${c}`).join('\n'))
181
+ .replace(/\{\{DONE_CRITERIA\}\}/g, DEMO_DONE);
182
+ } catch {
183
+ claudeMd = `# Wheat Sprint\n\n**Question:** ${DEMO_QUESTION}\n`;
184
+ }
185
+ fs.writeFileSync(target(dir, 'CLAUDE.md'), claudeMd);
186
+ console.log(` \x1b[32m+\x1b[0m CLAUDE.md`);
187
+
188
+ // Step 3: Copy slash commands
189
+ const srcDir = path.join(packageRoot(), 'templates', 'commands');
190
+ const destDir = target(dir, '.claude', 'commands');
191
+ fs.mkdirSync(destDir, { recursive: true });
192
+ let copied = 0;
193
+ try {
194
+ for (const file of fs.readdirSync(srcDir)) {
195
+ if (!file.endsWith('.md')) continue;
196
+ fs.copyFileSync(path.join(srcDir, file), path.join(destDir, file));
197
+ copied++;
198
+ }
199
+ } catch { /* commands dir may not exist in dev */ }
200
+ console.log(` \x1b[32m+\x1b[0m .claude/commands/ (${copied} commands)`);
201
+
202
+ // Step 4: Create directories
203
+ for (const d of ['output', 'research', 'prototypes', 'evidence']) {
204
+ const dirPath = target(dir, d);
205
+ if (!fs.existsSync(dirPath)) {
206
+ fs.mkdirSync(dirPath, { recursive: true });
207
+ fs.writeFileSync(path.join(dirPath, '.gitkeep'), '');
208
+ }
209
+ }
210
+ console.log(' \x1b[32m+\x1b[0m output/, research/, prototypes/, evidence/');
211
+
212
+ // Step 5: Run the compiler
213
+ console.log();
214
+ console.log(' \x1b[1mCompiling...\x1b[0m');
215
+ try {
216
+ const compilerPath = path.join(packageRoot(), 'lib', 'compiler.js');
217
+ const compilerModule = await import(compilerPath);
218
+ if (typeof compilerModule.compile === 'function') {
219
+ await compilerModule.compile(dir, ['--summary']);
220
+ } else if (typeof compilerModule.run === 'function') {
221
+ await compilerModule.run(dir, ['--summary']);
222
+ }
223
+ } catch (err) {
224
+ // Fallback: try running as subprocess
225
+ try {
226
+ const output = execFileSync('node', [
227
+ path.join(packageRoot(), 'bin', 'wheat.js'), 'compile', '--summary', '--dir', dir
228
+ ], { timeout: 15000, stdio: ['ignore', 'pipe', 'pipe'] }).toString();
229
+ process.stdout.write(output);
230
+ } catch (e) {
231
+ console.log(` \x1b[33m!\x1b[0m Compilation skipped: ${e.message}`);
232
+ }
233
+ }
234
+
235
+ const elapsed5 = Date.now() - startTime;
236
+ console.log();
237
+ console.log(` \x1b[32mCompiled in ${elapsed5}ms.\x1b[0m`);
238
+
239
+ // Step 6: Start the dashboard
240
+ const port = flags.port || '9092';
241
+ console.log();
242
+ console.log(` \x1b[1mStarting dashboard on port ${port}...\x1b[0m`);
243
+
244
+ // Import and start the server
245
+ try {
246
+ const serverModule = await import(path.join(packageRoot(), 'lib', 'server.js'));
247
+ // The server module's run() starts listening — we call it and let it run
248
+ // It will keep the process alive
249
+ const serverArgs = ['--port', port, '--dir', dir];
250
+ if (flags.noOpen) serverArgs.push('--no-open');
251
+
252
+ console.log();
253
+ console.log(' ─────────────────────────────────────────');
254
+ const totalTime = ((Date.now() - startTime) / 1000).toFixed(1);
255
+ console.log(` \x1b[1m\x1b[33mQuickstart complete.\x1b[0m ${totalTime}s total`);
256
+ console.log();
257
+ console.log(` Sprint: ${DEMO_QUESTION}`);
258
+ console.log(` Claims: ${claimsData.claims.length} (${DEMO_CLAIMS.filter(c => c.id.startsWith('d')).length} constraints + ${DEMO_CLAIMS.filter(c => c.id.startsWith('r')).length} research + ${DEMO_CLAIMS.filter(c => c.id.startsWith('x')).length} challenge)`);
259
+ console.log(` Conflicts: 1 (r002 vs x001 — performance overhead)`);
260
+ console.log(` Dashboard: http://localhost:${port}`);
261
+ console.log();
262
+ console.log(' What to do now:');
263
+ console.log(' 1. Explore the dashboard — click topics, claims, see the conflict');
264
+ console.log(' 2. Open Claude Code here and try: /research "GraphQL security"');
265
+ console.log(' 3. Or start YOUR sprint: wheat init');
266
+ console.log();
267
+
268
+ // Start server (this blocks — keeps process alive)
269
+ await serverModule.run(dir, serverArgs);
270
+ } catch (err) {
271
+ // If server fails, still show the summary
272
+ console.log(` \x1b[33m!\x1b[0m Dashboard failed to start: ${err.message}`);
273
+ console.log(` Run manually: wheat serve --dir ${dir}`);
274
+ console.log();
275
+ }
276
+ }