@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
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Smart defaults and environment detection for wheat CLI.
3
+ * Zero dependencies — Node built-ins only.
4
+ */
5
+
6
+ export const DEFAULTS = {
7
+ audience: ["engineers"],
8
+ constraints: [],
9
+ doneCriteria: "Decision-ready brief with evidence",
10
+ };
11
+
12
+ export function isTTY() {
13
+ return Boolean(process.stdout.isTTY) && !isCI();
14
+ }
15
+
16
+ export function isCI() {
17
+ return Boolean(
18
+ process.env.CI ||
19
+ process.env.GITHUB_ACTIONS ||
20
+ process.env.GITLAB_CI ||
21
+ process.env.CIRCLECI ||
22
+ process.env.JENKINS_URL ||
23
+ process.env.BUILDKITE,
24
+ );
25
+ }
26
+
27
+ export function outputMode() {
28
+ if (process.argv.includes("--quiet")) return "quiet";
29
+ if (process.argv.includes("--json")) return "json";
30
+ if (!isTTY()) return "json";
31
+ return "tty";
32
+ }
package/lib/disconnect.js CHANGED
@@ -6,23 +6,23 @@
6
6
  * Zero npm dependencies.
7
7
  */
8
8
 
9
- import fs from 'node:fs';
10
- import path from 'node:path';
9
+ import fs from "node:fs";
10
+ import path from "node:path";
11
11
 
12
12
  // ─── Constants ─────────────────────────────────────────────────────────────
13
13
 
14
- const SETTINGS_FILENAME = '.claude/settings.local.json';
14
+ const SETTINGS_FILENAME = ".claude/settings.local.json";
15
15
 
16
16
  // ─── Argument parsing ──────────────────────────────────────────────────────
17
17
 
18
18
  function parseArgs(args) {
19
19
  const flags = {};
20
20
  for (let i = 0; i < args.length; i++) {
21
- if (args[i] === '--dry-run') {
21
+ if (args[i] === "--dry-run") {
22
22
  flags.dryRun = true;
23
- } else if (args[i] === '--json') {
23
+ } else if (args[i] === "--json") {
24
24
  flags.json = true;
25
- } else if (args[i] === '--help' || args[i] === '-h') {
25
+ } else if (args[i] === "--help" || args[i] === "-h") {
26
26
  flags.help = true;
27
27
  }
28
28
  }
@@ -33,9 +33,10 @@ function parseArgs(args) {
33
33
 
34
34
  function isFarmerHookEntry(entry) {
35
35
  if (!entry.hooks || !Array.isArray(entry.hooks)) return false;
36
- return entry.hooks.some(h =>
37
- (h.type === 'command' && h.command && h.command.includes('/hooks/'))
38
- || (h.type === 'url' && h.url && h.url.includes('/hooks/'))
36
+ return entry.hooks.some(
37
+ (h) =>
38
+ (h.type === "command" && h.command && h.command.includes("/hooks/")) ||
39
+ (h.type === "url" && h.url && h.url.includes("/hooks/"))
39
40
  );
40
41
  }
41
42
 
@@ -43,16 +44,16 @@ function isFarmerHookEntry(entry) {
43
44
 
44
45
  function readSettings(settingsPath) {
45
46
  try {
46
- return JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
47
+ return JSON.parse(fs.readFileSync(settingsPath, "utf8"));
47
48
  } catch (err) {
48
- if (err.code === 'ENOENT') return null;
49
+ if (err.code === "ENOENT") return null;
49
50
  throw new Error(`Cannot parse ${settingsPath}: ${err.message}`);
50
51
  }
51
52
  }
52
53
 
53
54
  function writeSettingsAtomic(settingsPath, settings) {
54
- const tmpPath = settingsPath + '.tmp';
55
- fs.writeFileSync(tmpPath, JSON.stringify(settings, null, 2) + '\n');
55
+ const tmpPath = settingsPath + ".tmp";
56
+ fs.writeFileSync(tmpPath, JSON.stringify(settings, null, 2) + "\n");
56
57
  fs.renameSync(tmpPath, settingsPath);
57
58
  }
58
59
 
@@ -78,7 +79,7 @@ function removeFarmerHooks(settings) {
78
79
  for (const hookType of Object.keys(cleaned.hooks)) {
79
80
  if (!Array.isArray(cleaned.hooks[hookType])) continue;
80
81
  cleaned.hooks[hookType] = cleaned.hooks[hookType].filter(
81
- entry => !isFarmerHookEntry(entry)
82
+ (entry) => !isFarmerHookEntry(entry)
82
83
  );
83
84
  // Remove empty arrays to keep the file clean
84
85
  if (cleaned.hooks[hookType].length === 0) {
@@ -122,9 +123,17 @@ export async function run(dir, args) {
122
123
 
123
124
  if (!settings) {
124
125
  if (flags.json) {
125
- console.log(JSON.stringify({ success: true, removed: 0, message: 'No settings file found' }));
126
+ console.log(
127
+ JSON.stringify({
128
+ success: true,
129
+ removed: 0,
130
+ message: "No settings file found",
131
+ })
132
+ );
126
133
  } else {
127
- console.log('\n No farmer hooks found (settings file does not exist).\n');
134
+ console.log(
135
+ "\n No farmer hooks found (settings file does not exist).\n"
136
+ );
128
137
  }
129
138
  return;
130
139
  }
@@ -133,9 +142,15 @@ export async function run(dir, args) {
133
142
 
134
143
  if (farmerHooks.length === 0) {
135
144
  if (flags.json) {
136
- console.log(JSON.stringify({ success: true, removed: 0, message: 'No farmer hooks found' }));
145
+ console.log(
146
+ JSON.stringify({
147
+ success: true,
148
+ removed: 0,
149
+ message: "No farmer hooks found",
150
+ })
151
+ );
137
152
  } else {
138
- console.log('\n No farmer hooks found in ' + SETTINGS_FILENAME + '.\n');
153
+ console.log("\n No farmer hooks found in " + SETTINGS_FILENAME + ".\n");
139
154
  }
140
155
  return;
141
156
  }
@@ -143,18 +158,22 @@ export async function run(dir, args) {
143
158
  // Show what will be removed
144
159
  if (flags.dryRun) {
145
160
  if (flags.json) {
146
- console.log(JSON.stringify({
147
- success: true,
148
- dryRun: true,
149
- removed: farmerHooks.length,
150
- hookTypes: farmerHooks.map(h => h.hookType),
151
- }));
161
+ console.log(
162
+ JSON.stringify({
163
+ success: true,
164
+ dryRun: true,
165
+ removed: farmerHooks.length,
166
+ hookTypes: farmerHooks.map((h) => h.hookType),
167
+ })
168
+ );
152
169
  } else {
153
- console.log('\n Would remove ' + farmerHooks.length + ' farmer hook(s):');
170
+ console.log(
171
+ "\n Would remove " + farmerHooks.length + " farmer hook(s):"
172
+ );
154
173
  for (const { hookType } of farmerHooks) {
155
- console.log(' - ' + hookType);
174
+ console.log(" - " + hookType);
156
175
  }
157
- console.log('\n (dry run -- no files were modified)\n');
176
+ console.log("\n (dry run -- no files were modified)\n");
158
177
  }
159
178
  return;
160
179
  }
@@ -164,25 +183,27 @@ export async function run(dir, args) {
164
183
  writeSettingsAtomic(settingsPath, cleaned);
165
184
 
166
185
  if (flags.json) {
167
- console.log(JSON.stringify({
168
- success: true,
169
- removed: farmerHooks.length,
170
- hookTypes: farmerHooks.map(h => h.hookType),
171
- settingsPath,
172
- }));
186
+ console.log(
187
+ JSON.stringify({
188
+ success: true,
189
+ removed: farmerHooks.length,
190
+ hookTypes: farmerHooks.map((h) => h.hookType),
191
+ settingsPath,
192
+ })
193
+ );
173
194
  } else {
174
195
  console.log();
175
- console.log(' \x1b[32m\u2713\x1b[0m \x1b[1mFarmer disconnected\x1b[0m');
176
- console.log(' \u2500'.repeat(40));
177
- console.log(' Removed ' + farmerHooks.length + ' hook(s):');
196
+ console.log(" \x1b[32m\u2713\x1b[0m \x1b[1mFarmer disconnected\x1b[0m");
197
+ console.log(" \u2500".repeat(40));
198
+ console.log(" Removed " + farmerHooks.length + " hook(s):");
178
199
  for (const { hookType } of farmerHooks) {
179
- console.log(' - ' + hookType);
200
+ console.log(" - " + hookType);
180
201
  }
181
202
  console.log();
182
- console.log(' Settings: ' + settingsPath);
203
+ console.log(" Settings: " + settingsPath);
183
204
  console.log();
184
- console.log(' To reconnect:');
185
- console.log(' wheat connect farmer');
205
+ console.log(" To reconnect:");
206
+ console.log(" wheat connect farmer");
186
207
  console.log();
187
208
  }
188
209
  }
package/lib/guard.js CHANGED
@@ -14,19 +14,19 @@
14
14
  * Zero npm dependencies.
15
15
  */
16
16
 
17
- import fs from 'fs';
18
- import path from 'path';
17
+ import fs from "fs";
18
+ import path from "path";
19
19
 
20
20
  // ─── Config ──────────────────────────────────────────────────────────────────
21
21
 
22
22
  function loadConfig(dir) {
23
- const configPath = path.join(dir, 'wheat.config.json');
23
+ const configPath = path.join(dir, "wheat.config.json");
24
24
  const defaults = {
25
- dirs: { output: 'output' },
26
- compiler: { claims: 'claims.json', compilation: 'compilation.json' },
25
+ dirs: { output: "output" },
26
+ compiler: { claims: "claims.json", compilation: "compilation.json" },
27
27
  };
28
28
  try {
29
- const raw = fs.readFileSync(configPath, 'utf8');
29
+ const raw = fs.readFileSync(configPath, "utf8");
30
30
  const config = JSON.parse(raw);
31
31
  return {
32
32
  dirs: { ...defaults.dirs, ...(config.dirs || {}) },
@@ -50,19 +50,20 @@ export function guard(dir, toolInput) {
50
50
  return { allow: true };
51
51
  }
52
52
 
53
- const filePath = input.file_path || '';
54
- const rel = path.relative(dir, filePath).split(path.sep).join('/');
53
+ const filePath = input.file_path || "";
54
+ const rel = path.relative(dir, filePath).split(path.sep).join("/");
55
55
 
56
56
  // Guard 1: Writes to output/ require fresh compilation
57
- if (rel.startsWith(config.dirs.output + '/') && !rel.endsWith('.gitkeep')) {
57
+ if (rel.startsWith(config.dirs.output + "/") && !rel.endsWith(".gitkeep")) {
58
58
  const compilationPath = path.join(dir, config.compiler.compilation);
59
59
  const claimsPath = path.join(dir, config.compiler.claims);
60
60
 
61
61
  if (!fs.existsSync(compilationPath)) {
62
62
  return {
63
63
  allow: false,
64
- reason: `BLOCKED: No ${config.compiler.compilation} found. Run "wheat compile" before generating output artifacts.\n` +
65
- 'The Wheat pipeline requires: claims.json -> compiler -> compilation.json -> artifact',
64
+ reason:
65
+ `BLOCKED: No ${config.compiler.compilation} found. Run "wheat compile" before generating output artifacts.\n` +
66
+ "The Wheat pipeline requires: claims.json -> compiler -> compilation.json -> artifact",
66
67
  };
67
68
  }
68
69
 
@@ -84,9 +85,11 @@ export function guard(dir, toolInput) {
84
85
  }
85
86
 
86
87
  try {
87
- const compilation = JSON.parse(fs.readFileSync(compilationPath, 'utf8'));
88
- if (compilation.status === 'blocked') {
89
- const errors = (compilation.errors || []).map(e => ` - ${e.message}`).join('\n');
88
+ const compilation = JSON.parse(fs.readFileSync(compilationPath, "utf8"));
89
+ if (compilation.status === "blocked") {
90
+ const errors = (compilation.errors || [])
91
+ .map((e) => ` - ${e.message}`)
92
+ .join("\n");
90
93
  return {
91
94
  allow: false,
92
95
  reason: `BLOCKED: Compilation status is "blocked" — unresolved issues:\n${errors}\nFix these issues and recompile before generating output artifacts.`,
@@ -129,14 +132,14 @@ export function guard(dir, toolInput) {
129
132
  export async function run(dir, args) {
130
133
  // Read tool input from stdin or first arg
131
134
  let toolInput;
132
- if (args[0] && !args[0].startsWith('--')) {
135
+ if (args[0] && !args[0].startsWith("--")) {
133
136
  toolInput = args[0];
134
137
  } else {
135
138
  try {
136
139
  // /dev/stdin is Unix-only; use fd 0 which Node resolves cross-platform
137
- toolInput = fs.readFileSync(0, 'utf8');
140
+ toolInput = fs.readFileSync(0, "utf8");
138
141
  } catch {
139
- toolInput = '{}';
142
+ toolInput = "{}";
140
143
  }
141
144
  }
142
145
 
package/lib/index.js CHANGED
@@ -4,11 +4,11 @@
4
4
  * Re-exports the main library modules for programmatic use.
5
5
  */
6
6
 
7
- export { run as compile } from './compiler.js';
8
- export { guard } from './guard.js';
9
- export { run as serve } from './server.js';
10
- export { run as connect } from './connect.js';
11
- export { run as init } from './init.js';
12
- export { run as status } from './status.js';
13
- export { run as stats } from './stats.js';
14
- export { run as update } from './update.js';
7
+ export { run as compile } from "./compiler.js";
8
+ export { guard } from "./guard.js";
9
+ export { run as serve } from "./server.js";
10
+ export { run as connect } from "./connect.js";
11
+ export { run as init } from "./init.js";
12
+ export { run as status } from "./status.js";
13
+ export { run as stats } from "./stats.js";
14
+ export { run as update } from "./update.js";