@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.
- package/LICENSE +1 -1
- package/README.md +32 -31
- package/bin/wheat.js +63 -40
- package/compiler/detect-sprints.js +108 -66
- package/compiler/generate-manifest.js +116 -69
- package/compiler/wheat-compiler.js +763 -471
- package/lib/compiler.js +11 -6
- package/lib/connect.js +273 -134
- package/lib/defaults.js +32 -0
- package/lib/disconnect.js +61 -40
- package/lib/guard.js +20 -17
- package/lib/index.js +8 -8
- package/lib/init.js +260 -142
- package/lib/install-prompt.js +26 -26
- package/lib/load-claims.js +88 -0
- package/lib/quickstart.js +225 -111
- package/lib/serve-mcp.js +495 -180
- package/lib/server.js +198 -111
- package/lib/stats.js +65 -39
- package/lib/status.js +65 -34
- package/lib/update.js +13 -11
- package/package.json +8 -4
- package/templates/claude.md +31 -17
- package/templates/commands/blind-spot.md +9 -2
- package/templates/commands/brief.md +11 -1
- package/templates/commands/calibrate.md +3 -1
- package/templates/commands/challenge.md +4 -1
- package/templates/commands/connect.md +12 -1
- package/templates/commands/evaluate.md +4 -0
- package/templates/commands/feedback.md +3 -1
- package/templates/commands/handoff.md +11 -7
- package/templates/commands/init.md +4 -1
- package/templates/commands/merge.md +4 -1
- package/templates/commands/next.md +1 -0
- package/templates/commands/present.md +3 -0
- package/templates/commands/prototype.md +2 -0
- package/templates/commands/pull.md +103 -0
- package/templates/commands/replay.md +8 -0
- package/templates/commands/research.md +1 -0
- package/templates/commands/resolve.md +4 -1
- package/templates/commands/status.md +4 -0
- package/templates/commands/sync.md +94 -0
- package/templates/commands/witness.md +6 -2
package/lib/defaults.js
ADDED
|
@@ -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
|
|
10
|
-
import path from
|
|
9
|
+
import fs from "node:fs";
|
|
10
|
+
import path from "node:path";
|
|
11
11
|
|
|
12
12
|
// ─── Constants ─────────────────────────────────────────────────────────────
|
|
13
13
|
|
|
14
|
-
const SETTINGS_FILENAME =
|
|
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] ===
|
|
21
|
+
if (args[i] === "--dry-run") {
|
|
22
22
|
flags.dryRun = true;
|
|
23
|
-
} else if (args[i] ===
|
|
23
|
+
} else if (args[i] === "--json") {
|
|
24
24
|
flags.json = true;
|
|
25
|
-
} else if (args[i] ===
|
|
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(
|
|
37
|
-
(h
|
|
38
|
-
|
|
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,
|
|
47
|
+
return JSON.parse(fs.readFileSync(settingsPath, "utf8"));
|
|
47
48
|
} catch (err) {
|
|
48
|
-
if (err.code ===
|
|
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 +
|
|
55
|
-
fs.writeFileSync(tmpPath, JSON.stringify(settings, null, 2) +
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
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(
|
|
170
|
+
console.log(
|
|
171
|
+
"\n Would remove " + farmerHooks.length + " farmer hook(s):"
|
|
172
|
+
);
|
|
154
173
|
for (const { hookType } of farmerHooks) {
|
|
155
|
-
console.log(
|
|
174
|
+
console.log(" - " + hookType);
|
|
156
175
|
}
|
|
157
|
-
console.log(
|
|
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(
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
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(
|
|
176
|
-
console.log(
|
|
177
|
-
console.log(
|
|
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(
|
|
200
|
+
console.log(" - " + hookType);
|
|
180
201
|
}
|
|
181
202
|
console.log();
|
|
182
|
-
console.log(
|
|
203
|
+
console.log(" Settings: " + settingsPath);
|
|
183
204
|
console.log();
|
|
184
|
-
console.log(
|
|
185
|
-
console.log(
|
|
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
|
|
18
|
-
import path from
|
|
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,
|
|
23
|
+
const configPath = path.join(dir, "wheat.config.json");
|
|
24
24
|
const defaults = {
|
|
25
|
-
dirs: { output:
|
|
26
|
-
compiler: { claims:
|
|
25
|
+
dirs: { output: "output" },
|
|
26
|
+
compiler: { claims: "claims.json", compilation: "compilation.json" },
|
|
27
27
|
};
|
|
28
28
|
try {
|
|
29
|
-
const raw = fs.readFileSync(configPath,
|
|
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 +
|
|
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:
|
|
65
|
-
|
|
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,
|
|
88
|
-
if (compilation.status ===
|
|
89
|
-
const errors = (compilation.errors || [])
|
|
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,
|
|
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
|
|
8
|
-
export { guard } from
|
|
9
|
-
export { run as serve } from
|
|
10
|
-
export { run as connect } from
|
|
11
|
-
export { run as init } from
|
|
12
|
-
export { run as status } from
|
|
13
|
-
export { run as stats } from
|
|
14
|
-
export { run as update } from
|
|
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";
|