@grainulation/wheat 1.0.2 → 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.
Files changed (42) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +32 -31
  3. package/bin/wheat.js +47 -36
  4. package/compiler/detect-sprints.js +126 -92
  5. package/compiler/generate-manifest.js +116 -69
  6. package/compiler/wheat-compiler.js +789 -468
  7. package/lib/compiler.js +11 -6
  8. package/lib/connect.js +273 -134
  9. package/lib/disconnect.js +61 -40
  10. package/lib/guard.js +20 -17
  11. package/lib/index.js +8 -8
  12. package/lib/init.js +217 -142
  13. package/lib/install-prompt.js +26 -26
  14. package/lib/load-claims.js +88 -0
  15. package/lib/quickstart.js +225 -111
  16. package/lib/serve-mcp.js +495 -180
  17. package/lib/server.js +198 -111
  18. package/lib/stats.js +65 -39
  19. package/lib/status.js +65 -34
  20. package/lib/update.js +13 -11
  21. package/package.json +8 -4
  22. package/templates/claude.md +31 -17
  23. package/templates/commands/blind-spot.md +9 -2
  24. package/templates/commands/brief.md +11 -1
  25. package/templates/commands/calibrate.md +3 -1
  26. package/templates/commands/challenge.md +4 -1
  27. package/templates/commands/connect.md +12 -1
  28. package/templates/commands/evaluate.md +4 -0
  29. package/templates/commands/feedback.md +3 -1
  30. package/templates/commands/handoff.md +11 -7
  31. package/templates/commands/init.md +4 -1
  32. package/templates/commands/merge.md +4 -1
  33. package/templates/commands/next.md +1 -0
  34. package/templates/commands/present.md +3 -0
  35. package/templates/commands/prototype.md +2 -0
  36. package/templates/commands/pull.md +103 -0
  37. package/templates/commands/replay.md +8 -0
  38. package/templates/commands/research.md +1 -0
  39. package/templates/commands/resolve.md +4 -1
  40. package/templates/commands/status.md +4 -0
  41. package/templates/commands/sync.md +94 -0
  42. package/templates/commands/witness.md +6 -2
@@ -13,12 +13,12 @@
13
13
  * nudging. This is an unclaimed UX innovation.
14
14
  */
15
15
 
16
- import { readFileSync, writeFileSync, mkdirSync } from 'fs';
17
- import path from 'path';
18
- import { homedir } from 'os';
16
+ import { readFileSync, writeFileSync, mkdirSync } from "fs";
17
+ import path from "path";
18
+ import { homedir } from "os";
19
19
 
20
- const USAGE_DIR = path.join(homedir(), '.grainulation');
21
- const USAGE_FILE = path.join(USAGE_DIR, 'usage.json');
20
+ const USAGE_DIR = path.join(homedir(), ".grainulation");
21
+ const USAGE_FILE = path.join(USAGE_DIR, "usage.json");
22
22
 
23
23
  const THRESHOLDS = {
24
24
  suggest_global: 3,
@@ -30,7 +30,7 @@ const THRESHOLDS = {
30
30
 
31
31
  function readUsage() {
32
32
  try {
33
- return JSON.parse(readFileSync(USAGE_FILE, 'utf8'));
33
+ return JSON.parse(readFileSync(USAGE_FILE, "utf8"));
34
34
  } catch {
35
35
  return { commands: {}, prompted: false, installed: false };
36
36
  }
@@ -39,7 +39,7 @@ function readUsage() {
39
39
  function writeUsage(data) {
40
40
  try {
41
41
  mkdirSync(USAGE_DIR, { recursive: true });
42
- writeFileSync(USAGE_FILE, JSON.stringify(data, null, 2) + '\n', 'utf8');
42
+ writeFileSync(USAGE_FILE, JSON.stringify(data, null, 2) + "\n", "utf8");
43
43
  } catch {
44
44
  // fail silently
45
45
  }
@@ -50,8 +50,8 @@ function writeUsage(data) {
50
50
  * npx stores executables in a temporary _npx cache directory.
51
51
  */
52
52
  function isNpxInvocation() {
53
- const execPath = process.argv[1] || '';
54
- return execPath.includes('_npx');
53
+ const execPath = process.argv[1] || "";
54
+ return execPath.includes("_npx");
55
55
  }
56
56
 
57
57
  /**
@@ -66,23 +66,23 @@ function isNpxInvocation() {
66
66
  * (isNpxInvocation returns false, so prompts are already suppressed).
67
67
  */
68
68
  function isInstalled() {
69
- const execPath = process.argv[1] || '';
69
+ const execPath = process.argv[1] || "";
70
70
 
71
71
  // Local install: running from node_modules
72
- if (execPath.includes('node_modules') && !execPath.includes('_npx')) {
72
+ if (execPath.includes("node_modules") && !execPath.includes("_npx")) {
73
73
  return true;
74
74
  }
75
75
 
76
76
  // Global install: binary is in a well-known global prefix
77
77
  // (not npx, not node_modules — must be a permanent install)
78
- if (!execPath.includes('_npx') && !execPath.includes('node_modules')) {
78
+ if (!execPath.includes("_npx") && !execPath.includes("node_modules")) {
79
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'),
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
86
  ];
87
87
  for (const prefix of knownGlobalDirs) {
88
88
  if (execPath.startsWith(prefix)) return true;
@@ -141,8 +141,8 @@ export function track(command) {
141
141
  */
142
142
  export function maybePrompt(command) {
143
143
  try {
144
- if (process.env.WHEAT_NO_INSTALL_PROMPT === '1') return;
145
- if (process.argv.includes('--quiet')) return;
144
+ if (process.env.WHEAT_NO_INSTALL_PROMPT === "1") return;
145
+ if (process.argv.includes("--quiet")) return;
146
146
  if (isInstalled()) return;
147
147
  if (!isNpxInvocation()) return;
148
148
  if (!command) return;
@@ -162,22 +162,22 @@ export function maybePrompt(command) {
162
162
  // 10+ invocations: more visible, but still non-blocking
163
163
  process.stderr.write(
164
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`
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
168
  );
169
169
  } else if (effective >= THRESHOLDS.suggest_local) {
170
170
  // 5-9 invocations: suggest both global and local
171
171
  process.stderr.write(
172
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`
173
+ `For instant startup: npm i -g @grainulation/wheat\n` +
174
+ ` Or add to this project: npm i -D @grainulation/wheat\n`
175
175
  );
176
176
  } else if (effective >= THRESHOLDS.suggest_global) {
177
177
  // 3-4 invocations: one-liner, suggest global only
178
178
  process.stderr.write(
179
179
  ` wheat: You've run wheat via npx ${count} times. ` +
180
- `For instant startup: npm i -g @grainulation/wheat\n`
180
+ `For instant startup: npm i -g @grainulation/wheat\n`
181
181
  );
182
182
  }
183
183
  } catch {
@@ -0,0 +1,88 @@
1
+ /**
2
+ * Shared claims.json loader with schema migration
3
+ *
4
+ * All consumers that read claims.json should use this helper to ensure
5
+ * the data is migrated to the current schema version before processing.
6
+ *
7
+ * Zero npm dependencies.
8
+ */
9
+
10
+ import fs from "fs";
11
+ import path from "path";
12
+ import { checkAndMigrateSchema } from "../compiler/wheat-compiler.js";
13
+
14
+ /**
15
+ * Load and migrate claims.json from a directory.
16
+ *
17
+ * @param {string} dir - Directory containing claims.json
18
+ * @param {object} [opts] - Options
19
+ * @param {string} [opts.filename='claims.json'] - Claims filename override
20
+ * @returns {{ data: object|null, errors: Array<{code: string, message: string}>, path: string }}
21
+ * - data: the full claims object (with schema_version, meta, claims) or null if file missing/invalid
22
+ * - errors: migration errors (empty array on success)
23
+ * - path: resolved file path
24
+ */
25
+ export function loadClaims(dir, opts = {}) {
26
+ const filename = opts.filename || "claims.json";
27
+ const claimsPath = path.join(dir, filename);
28
+
29
+ if (!fs.existsSync(claimsPath)) {
30
+ return { data: null, errors: [], path: claimsPath };
31
+ }
32
+
33
+ let raw;
34
+ try {
35
+ raw = fs.readFileSync(claimsPath, "utf8");
36
+ } catch (err) {
37
+ return {
38
+ data: null,
39
+ errors: [
40
+ {
41
+ code: "E_READ",
42
+ message: `Failed to read ${filename}: ${err.message}`,
43
+ },
44
+ ],
45
+ path: claimsPath,
46
+ };
47
+ }
48
+
49
+ let parsed;
50
+ try {
51
+ parsed = JSON.parse(raw);
52
+ } catch (err) {
53
+ return {
54
+ data: null,
55
+ errors: [
56
+ {
57
+ code: "E_PARSE",
58
+ message: `${filename} is not valid JSON: ${err.message}`,
59
+ },
60
+ ],
61
+ path: claimsPath,
62
+ };
63
+ }
64
+
65
+ // Run schema migration
66
+ const result = checkAndMigrateSchema(parsed);
67
+
68
+ // Write migrated data back to disk so downstream tools that read the file
69
+ // directly (bypassing loadClaims) see the current schema version.
70
+ // Only write if migration actually changed something (avoid unnecessary I/O).
71
+ if (result.errors.length === 0 && result.data) {
72
+ const migrated = JSON.stringify(result.data, null, 2);
73
+ if (migrated !== raw) {
74
+ try {
75
+ fs.writeFileSync(claimsPath, migrated + "\n", "utf8");
76
+ } catch (err) {
77
+ // Non-fatal: migration write-back failure should not block reads.
78
+ // The in-memory data is still correct.
79
+ }
80
+ }
81
+ }
82
+
83
+ return {
84
+ data: result.errors.length > 0 ? null : result.data,
85
+ errors: result.errors,
86
+ path: claimsPath,
87
+ };
88
+ }