@grainulation/wheat 1.0.4 → 1.0.6
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/README.md +57 -76
- package/bin/wheat.js +16 -4
- package/compiler/wheat-compiler.js +12 -0
- package/lib/defaults.js +32 -0
- package/lib/hints.js +214 -0
- package/lib/init.js +149 -9
- package/lib/update.js +4 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
</p>
|
|
4
4
|
|
|
5
5
|
<p align="center">
|
|
6
|
-
<a href="https://www.npmjs.com/package/@grainulation/wheat"><img src="https://img.shields.io/npm/v/@grainulation/wheat" alt="npm version"></a> <a href="https://www.npmjs.com/package/@grainulation/wheat"><img src="https://img.shields.io/npm/dm/@grainulation/wheat" alt="npm downloads"></a> <a href="https://github.com/grainulation/wheat/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-MIT-green" alt="license"></a> <a href="https://nodejs.org"><img src="https://img.shields.io/node/v/@grainulation/wheat" alt="node"></a> <a href="https://github.com/grainulation/wheat/actions"><img src="https://github.com/grainulation/wheat/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
|
|
6
|
+
<a href="https://www.npmjs.com/package/@grainulation/wheat"><img src="https://img.shields.io/npm/v/@grainulation/wheat?label=%40grainulation%2Fwheat" alt="npm version"></a> <a href="https://www.npmjs.com/package/@grainulation/wheat"><img src="https://img.shields.io/npm/dm/@grainulation/wheat" alt="npm downloads"></a> <a href="https://github.com/grainulation/wheat/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-MIT-green" alt="license"></a> <a href="https://nodejs.org"><img src="https://img.shields.io/node/v/@grainulation/wheat" alt="node"></a> <a href="https://github.com/grainulation/wheat/actions"><img src="https://github.com/grainulation/wheat/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
|
|
7
7
|
<a href="https://deepwiki.com/grainulation/wheat"><img src="https://deepwiki.com/badge.svg" alt="Explore on DeepWiki"></a>
|
|
8
8
|
</p>
|
|
9
9
|
|
|
@@ -15,118 +15,99 @@ The migration will take months. It will cost real money. And right now, the deci
|
|
|
15
15
|
|
|
16
16
|
You'd never ship code without tests. Why ship a decision without validated evidence?
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
## Install
|
|
18
|
+
## Quick start
|
|
21
19
|
|
|
22
20
|
```bash
|
|
23
|
-
npx @grainulation/wheat
|
|
21
|
+
npx @grainulation/wheat "Should we migrate to GraphQL?"
|
|
24
22
|
```
|
|
25
23
|
|
|
26
|
-
|
|
24
|
+
One command. Zero prompts. Sprint ready in under 3 seconds.
|
|
27
25
|
|
|
28
|
-
|
|
26
|
+
Then open your AI coding tool and start investigating:
|
|
29
27
|
|
|
30
|
-
```
|
|
31
|
-
|
|
28
|
+
```
|
|
29
|
+
/research "GraphQL performance vs REST"
|
|
30
|
+
/challenge r003
|
|
31
|
+
/blind-spot
|
|
32
|
+
/brief
|
|
32
33
|
```
|
|
33
34
|
|
|
34
|
-
|
|
35
|
+
Works with [Claude Code](https://claude.com/claude-code), [Cursor](https://cursor.com), [GitHub Copilot](https://github.com/features/copilot), or standalone via CLI.
|
|
35
36
|
|
|
36
|
-
##
|
|
37
|
+
## Full MCP integration (optional)
|
|
38
|
+
|
|
39
|
+
For native tool access in Claude Code:
|
|
37
40
|
|
|
38
41
|
```bash
|
|
39
|
-
npx @grainulation/wheat
|
|
42
|
+
claude mcp add wheat -- npx -y @grainulation/wheat mcp
|
|
40
43
|
```
|
|
41
44
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
```
|
|
45
|
-
claims.json # Typed assertions (the test suite for your decision)
|
|
46
|
-
CLAUDE.md # AI assistant configuration
|
|
47
|
-
.claude/commands/ # 18 slash commands
|
|
48
|
-
output/ # Where compiled artifacts land
|
|
49
|
-
```
|
|
45
|
+
This gives Claude direct access to wheat's claims engine — add-claim, compile, search, status — without shelling out.
|
|
50
46
|
|
|
51
|
-
|
|
47
|
+
## See it in 30 seconds
|
|
52
48
|
|
|
53
|
-
```
|
|
54
|
-
/
|
|
55
|
-
/prototype # build something testable
|
|
56
|
-
/challenge r003 # stress-test a finding
|
|
57
|
-
/blind-spot # what are we missing?
|
|
58
|
-
/brief # compile the decision document
|
|
49
|
+
```bash
|
|
50
|
+
npx @grainulation/wheat quickstart
|
|
59
51
|
```
|
|
60
52
|
|
|
53
|
+
Creates a demo sprint with pre-seeded claims, an intentional conflict, compiles everything, and opens a dashboard. The compiler flags the conflict and blocks output until it's resolved.
|
|
54
|
+
|
|
61
55
|
## How it works
|
|
62
56
|
|
|
63
|
-
Wheat is a continuous planning pipeline. Findings are validated as they come in
|
|
57
|
+
Wheat is a continuous planning pipeline. Findings are validated as they come in:
|
|
64
58
|
|
|
65
59
|
```
|
|
66
|
-
You investigate
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
60
|
+
You investigate → Claims accumulate → Compiler validates → Brief compiles
|
|
61
|
+
/research typed, evidence-graded 7-pass pipeline backed by evidence
|
|
62
|
+
/prototype
|
|
63
|
+
/challenge
|
|
70
64
|
```
|
|
71
65
|
|
|
72
|
-
**
|
|
66
|
+
**Claim types:** constraint, factual, estimate, risk, recommendation, feedback
|
|
73
67
|
|
|
74
|
-
**Evidence tiers
|
|
68
|
+
**Evidence tiers:** stated → web → documented → tested → production
|
|
75
69
|
|
|
76
|
-
The compiler catches conflicts,
|
|
70
|
+
The compiler catches conflicts, flags weak evidence, and blocks the build when issues exist. You can't ship a brief built on contradictions — same as you can't merge with failing tests.
|
|
77
71
|
|
|
78
72
|
## Commands
|
|
79
73
|
|
|
80
|
-
| Command
|
|
81
|
-
|
|
82
|
-
| `/
|
|
83
|
-
| `/
|
|
84
|
-
| `/
|
|
85
|
-
| `/
|
|
86
|
-
| `/
|
|
87
|
-
| `/
|
|
88
|
-
| `/status`
|
|
89
|
-
| `/
|
|
90
|
-
| `/
|
|
91
|
-
| `/feedback` | Incorporate stakeholder input |
|
|
92
|
-
| `/resolve` | Adjudicate conflicts between claims |
|
|
93
|
-
| `/replay` | Time-travel through sprint history |
|
|
94
|
-
| `/calibrate` | Score predictions against actual outcomes |
|
|
95
|
-
| `/handoff` | Package sprint for knowledge transfer |
|
|
96
|
-
| `/merge <path>` | Combine findings across sprints |
|
|
97
|
-
| `/connect <type>` | Link external tools (Jira, docs, etc.) |
|
|
98
|
-
| `/evaluate` | Test claims against reality, resolve conflicts |
|
|
99
|
-
| `/next` | Route next steps through Farmer (mobile feedback) |
|
|
74
|
+
| Command | What it does |
|
|
75
|
+
|---------|-------------|
|
|
76
|
+
| `/research <topic>` | Deep dive on a topic, creates claims |
|
|
77
|
+
| `/prototype` | Build something testable |
|
|
78
|
+
| `/challenge <id>` | Adversarial stress-test of a claim |
|
|
79
|
+
| `/witness <id> <url>` | External corroboration |
|
|
80
|
+
| `/blind-spot` | Find gaps in your investigation |
|
|
81
|
+
| `/brief` | Compile the decision document |
|
|
82
|
+
| `/status` | Sprint dashboard |
|
|
83
|
+
| `/present` | Generate a stakeholder presentation |
|
|
84
|
+
| `/resolve` | Adjudicate conflicts between claims |
|
|
100
85
|
|
|
101
86
|
## Guard rails
|
|
102
87
|
|
|
103
|
-
Wheat installs two guard mechanisms:
|
|
104
|
-
|
|
105
|
-
1. **Git pre-commit hook** -- prevents committing broken `claims.json`
|
|
106
|
-
2. **Claude Code guard hook** -- prevents generating output artifacts from stale or blocked compilations
|
|
107
|
-
|
|
108
|
-
Both are optional and can be removed.
|
|
109
|
-
|
|
110
|
-
## Works in any repo
|
|
88
|
+
Wheat installs two optional guard mechanisms:
|
|
111
89
|
|
|
112
|
-
|
|
90
|
+
1. **Git pre-commit hook** — prevents committing broken claims
|
|
91
|
+
2. **Claude Code guard hook** — prevents generating output from stale compilations
|
|
113
92
|
|
|
114
|
-
##
|
|
93
|
+
## Works everywhere
|
|
115
94
|
|
|
116
|
-
|
|
95
|
+
Wheat doesn't care what language you use or what AI tool you run. Your Scala project, your Python monorepo, your Flutter app — wheat works the same everywhere. Node 20+ is the only requirement. Zero npm dependencies.
|
|
117
96
|
|
|
118
97
|
## Part of the grainulation ecosystem
|
|
119
98
|
|
|
120
|
-
| Tool
|
|
121
|
-
|
|
122
|
-
| **wheat**
|
|
123
|
-
| [farmer](https://github.com/grainulation/farmer)
|
|
124
|
-
| [barn](https://github.com/grainulation/barn)
|
|
125
|
-
| [mill](https://github.com/grainulation/mill)
|
|
126
|
-
| [silo](https://github.com/grainulation/silo)
|
|
127
|
-
| [harvest](https://github.com/grainulation/harvest)
|
|
128
|
-
| [orchard](https://github.com/grainulation/orchard)
|
|
129
|
-
| [grainulation](https://github.com/grainulation/grainulation) | Unified CLI
|
|
99
|
+
| Tool | Role |
|
|
100
|
+
|------|------|
|
|
101
|
+
| **wheat** | Research engine — grow structured evidence |
|
|
102
|
+
| [farmer](https://github.com/grainulation/farmer) | Permission dashboard — approve AI actions in real time |
|
|
103
|
+
| [barn](https://github.com/grainulation/barn) | Shared tools — templates, validators, sprint detection |
|
|
104
|
+
| [mill](https://github.com/grainulation/mill) | Format conversion — export to PDF, CSV, slides |
|
|
105
|
+
| [silo](https://github.com/grainulation/silo) | Knowledge storage — reusable claim libraries |
|
|
106
|
+
| [harvest](https://github.com/grainulation/harvest) | Analytics — cross-sprint patterns and prediction scoring |
|
|
107
|
+
| [orchard](https://github.com/grainulation/orchard) | Orchestration — multi-sprint coordination |
|
|
108
|
+
| [grainulation](https://github.com/grainulation/grainulation) | Unified CLI — single entry point to the ecosystem |
|
|
109
|
+
|
|
110
|
+
**You don't need all eight.** Start with wheat. That's it.
|
|
130
111
|
|
|
131
112
|
## License
|
|
132
113
|
|
package/bin/wheat.js
CHANGED
|
@@ -67,7 +67,8 @@ if (!subcommand || subcommand === "--help" || subcommand === "-h") {
|
|
|
67
67
|
console.log(`wheat v${VERSION} — Research-driven development framework
|
|
68
68
|
|
|
69
69
|
Usage:
|
|
70
|
-
wheat
|
|
70
|
+
wheat "your question" Start a sprint instantly (recommended)
|
|
71
|
+
wheat <command> [options] Run a specific command
|
|
71
72
|
|
|
72
73
|
Commands:
|
|
73
74
|
init Bootstrap a new research sprint in this repo
|
|
@@ -90,11 +91,9 @@ Global options:
|
|
|
90
91
|
--help Show this help
|
|
91
92
|
|
|
92
93
|
Examples:
|
|
93
|
-
npx @grainulation/wheat
|
|
94
|
+
npx @grainulation/wheat "Should we migrate to Postgres?"
|
|
94
95
|
npx @grainulation/wheat init
|
|
95
96
|
npx @grainulation/wheat compile --summary
|
|
96
|
-
npx @grainulation/wheat init --question "Should we migrate to Postgres?"
|
|
97
|
-
npx @grainulation/wheat init --non-interactive --question "..." --audience "..." --done "..."
|
|
98
97
|
|
|
99
98
|
Documentation: https://github.com/grainulation/wheat`);
|
|
100
99
|
process.exit(0);
|
|
@@ -189,6 +188,19 @@ Run "wheat disconnect farmer --help" for options.`);
|
|
|
189
188
|
}
|
|
190
189
|
|
|
191
190
|
if (!commands[subcommand]) {
|
|
191
|
+
// Verb-less mode: wheat "my question" → dispatch to init with auto defaults
|
|
192
|
+
const compoundCmds = ["connect", "disconnect", "migrate"];
|
|
193
|
+
if (subcommand && !subcommand.startsWith("-") && !compoundCmds.includes(subcommand)) {
|
|
194
|
+
vlog("dispatch", `verb-less mode: treating "${subcommand}" as question`);
|
|
195
|
+
const initPath = new URL(commands.init, import.meta.url).href;
|
|
196
|
+
const initHandler = await import(initPath);
|
|
197
|
+
await initHandler.run(targetDir, ["--question", subcommand, "--auto"]).catch((err) => {
|
|
198
|
+
console.error(`\nwheat failed:`, err.message);
|
|
199
|
+
if (process.env.WHEAT_DEBUG) console.error(err.stack);
|
|
200
|
+
process.exit(1);
|
|
201
|
+
});
|
|
202
|
+
process.exit(0);
|
|
203
|
+
}
|
|
192
204
|
console.error(`wheat: unknown command: ${subcommand}\n`);
|
|
193
205
|
console.error('Run "wheat --help" for available commands.');
|
|
194
206
|
process.exit(1);
|
|
@@ -19,6 +19,7 @@ import crypto from "crypto";
|
|
|
19
19
|
import path from "path";
|
|
20
20
|
|
|
21
21
|
import { fileURLToPath } from "url";
|
|
22
|
+
import { maybeHint } from "../lib/hints.js";
|
|
22
23
|
|
|
23
24
|
// Sprint detection — git-based, no config pointer needed (p013/f001)
|
|
24
25
|
import { detectSprints } from "./detect-sprints.js";
|
|
@@ -1195,6 +1196,17 @@ Options:
|
|
|
1195
1196
|
`Certificate: ${c.compilation_certificate.input_hash.slice(0, 20)}...`
|
|
1196
1197
|
);
|
|
1197
1198
|
|
|
1199
|
+
if (!jsonFlag) {
|
|
1200
|
+
try {
|
|
1201
|
+
const hint = maybeHint(compilation, { context: "compile" });
|
|
1202
|
+
if (hint) {
|
|
1203
|
+
process.stderr.write("\n" + hint + "\n");
|
|
1204
|
+
}
|
|
1205
|
+
} catch {
|
|
1206
|
+
// hints are non-critical — fail silently
|
|
1207
|
+
}
|
|
1208
|
+
}
|
|
1209
|
+
|
|
1198
1210
|
if (jsonFlag) {
|
|
1199
1211
|
console.log(JSON.stringify(c, null, 2));
|
|
1200
1212
|
}
|
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/hints.js
ADDED
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* hints.js — Ecosystem cross-promotion hints
|
|
3
|
+
*
|
|
4
|
+
* Contextual, one-line, non-blocking hints that surface Grainulation
|
|
5
|
+
* ecosystem tools at the moment the user's workflow makes them relevant.
|
|
6
|
+
*
|
|
7
|
+
* Shows max 1 hint per invocation on stderr.
|
|
8
|
+
* Tracks shown hints in ~/.grainulation/hints.json
|
|
9
|
+
* Fails silently on any I/O error — never blocks the CLI.
|
|
10
|
+
*
|
|
11
|
+
* Zero dependencies.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { readFileSync, writeFileSync, mkdirSync } from "fs";
|
|
15
|
+
import path from "path";
|
|
16
|
+
import { homedir } from "os";
|
|
17
|
+
|
|
18
|
+
const HINTS_DIR = path.join(homedir(), ".grainulation");
|
|
19
|
+
const HINTS_FILE = path.join(HINTS_DIR, "hints.json");
|
|
20
|
+
const MAX_SHOWS = 3;
|
|
21
|
+
const COOLDOWN_MS = 24 * 60 * 60 * 1000; // 24h
|
|
22
|
+
|
|
23
|
+
// ─── State I/O (sync, fail-silent) ──────────────────────────────────────────
|
|
24
|
+
|
|
25
|
+
function readHints() {
|
|
26
|
+
try {
|
|
27
|
+
return JSON.parse(readFileSync(HINTS_FILE, "utf8"));
|
|
28
|
+
} catch {
|
|
29
|
+
return { shown: {}, dismissed: [], installed: [] };
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function writeHints(data) {
|
|
34
|
+
try {
|
|
35
|
+
mkdirSync(HINTS_DIR, { recursive: true });
|
|
36
|
+
writeFileSync(HINTS_FILE, JSON.stringify(data, null, 2) + "\n");
|
|
37
|
+
} catch {
|
|
38
|
+
// fail silently
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function shouldSuppress(product, state) {
|
|
43
|
+
if ((state.dismissed || []).includes(product)) return true;
|
|
44
|
+
if ((state.installed || []).includes(product)) return true;
|
|
45
|
+
const record = (state.shown || {})[product];
|
|
46
|
+
if (!record) return false;
|
|
47
|
+
if (record.count >= MAX_SHOWS) return true;
|
|
48
|
+
if (record.last && Date.now() - new Date(record.last).getTime() < COOLDOWN_MS) return true;
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// ─── Triggers (ordered by priority, first match wins) ────────────────────────
|
|
53
|
+
|
|
54
|
+
const TRIGGERS = [
|
|
55
|
+
// 1. >20 claims → harvest
|
|
56
|
+
function detectHarvestFromClaims(compilation) {
|
|
57
|
+
const count = compilation?.claims?.length || 0;
|
|
58
|
+
if (count > 20) {
|
|
59
|
+
return {
|
|
60
|
+
product: "harvest",
|
|
61
|
+
message: "btw: harvest can visualize claim growth across sprints (npx @grainulation/harvest)",
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
return null;
|
|
65
|
+
},
|
|
66
|
+
|
|
67
|
+
// 2. >3 topics → orchard
|
|
68
|
+
function detectOrchardFromTopics(compilation) {
|
|
69
|
+
const claims = compilation?.claims || [];
|
|
70
|
+
const topics = new Set(claims.map((c) => c.topic).filter(Boolean));
|
|
71
|
+
if (topics.size > 3) {
|
|
72
|
+
return {
|
|
73
|
+
product: "orchard",
|
|
74
|
+
message: "btw: orchard can run multiple research sprints in parallel (npx @grainulation/orchard)",
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
return null;
|
|
78
|
+
},
|
|
79
|
+
|
|
80
|
+
// 3. brief/present context → mill
|
|
81
|
+
function detectMillFromBrief(compilation, context) {
|
|
82
|
+
if (context === "brief" || context === "present") {
|
|
83
|
+
return {
|
|
84
|
+
product: "mill",
|
|
85
|
+
message: "btw: mill can export this as PDF, slides, or Confluence (npx @grainulation/mill)",
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
return null;
|
|
89
|
+
},
|
|
90
|
+
|
|
91
|
+
// 4. first ever compile → farmer
|
|
92
|
+
function detectFarmerFirstCompile(compilation, context) {
|
|
93
|
+
if (context !== "compile") return null;
|
|
94
|
+
// Check if this looks like a first compile (no prior hint state)
|
|
95
|
+
try {
|
|
96
|
+
const state = readHints();
|
|
97
|
+
const totalShows = Object.values(state.shown || {}).reduce(
|
|
98
|
+
(sum, r) => sum + (r.count || 0),
|
|
99
|
+
0
|
|
100
|
+
);
|
|
101
|
+
if (totalShows === 0) {
|
|
102
|
+
return {
|
|
103
|
+
product: "farmer",
|
|
104
|
+
message: "btw: farmer gives you a mobile dashboard for AI permissions (npx @grainulation/farmer)",
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
} catch {
|
|
108
|
+
// fail silently
|
|
109
|
+
}
|
|
110
|
+
return null;
|
|
111
|
+
},
|
|
112
|
+
|
|
113
|
+
// 5. >5 sprints → silo
|
|
114
|
+
function detectSiloFromSprints(compilation) {
|
|
115
|
+
const sprintCount = compilation?.sprints?.length || 0;
|
|
116
|
+
if (sprintCount > 5) {
|
|
117
|
+
return {
|
|
118
|
+
product: "silo",
|
|
119
|
+
message: "btw: silo stores reusable claim libraries across projects (npx @grainulation/silo)",
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
return null;
|
|
123
|
+
},
|
|
124
|
+
];
|
|
125
|
+
|
|
126
|
+
// ─── Format ──────────────────────────────────────────────────────────────────
|
|
127
|
+
|
|
128
|
+
function formatHint(message) {
|
|
129
|
+
return ` \x1b[2m${message}\x1b[0m`;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// ─── Public API ──────────────────────────────────────────────────────────────
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Evaluate trigger conditions and return a formatted hint string,
|
|
136
|
+
* or null if no hint should be shown.
|
|
137
|
+
*
|
|
138
|
+
* @param {object} compilation - The compilation.json data
|
|
139
|
+
* @param {object} opts
|
|
140
|
+
* @param {string} opts.context - "compile" | "init" | "brief" | "present"
|
|
141
|
+
* @returns {string|null} Formatted hint for stderr, or null
|
|
142
|
+
*/
|
|
143
|
+
export function maybeHint(compilation, opts = {}) {
|
|
144
|
+
try {
|
|
145
|
+
// Global suppression
|
|
146
|
+
if (process.env.WHEAT_BTW === "off") return null;
|
|
147
|
+
if (process.env.WHEAT_NO_HINTS === "1") return null;
|
|
148
|
+
if (process.env.CI) return null;
|
|
149
|
+
if (process.argv.includes("--quiet")) return null;
|
|
150
|
+
if (process.argv.includes("--json")) return null;
|
|
151
|
+
if (!process.stderr.isTTY) return null;
|
|
152
|
+
|
|
153
|
+
const state = readHints();
|
|
154
|
+
|
|
155
|
+
for (const trigger of TRIGGERS) {
|
|
156
|
+
const result = trigger(compilation, opts.context);
|
|
157
|
+
if (!result) continue;
|
|
158
|
+
if (shouldSuppress(result.product, state)) continue;
|
|
159
|
+
|
|
160
|
+
// Record the show
|
|
161
|
+
if (!state.shown) state.shown = {};
|
|
162
|
+
if (!state.shown[result.product]) state.shown[result.product] = { count: 0 };
|
|
163
|
+
state.shown[result.product].count++;
|
|
164
|
+
state.shown[result.product].last = new Date().toISOString();
|
|
165
|
+
writeHints(state);
|
|
166
|
+
|
|
167
|
+
return formatHint(result.message);
|
|
168
|
+
}
|
|
169
|
+
} catch {
|
|
170
|
+
// fail silently — never block the main output
|
|
171
|
+
}
|
|
172
|
+
return null;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Record that a product has been installed/connected.
|
|
177
|
+
* @param {string} product
|
|
178
|
+
*/
|
|
179
|
+
export function markInstalled(product) {
|
|
180
|
+
try {
|
|
181
|
+
const state = readHints();
|
|
182
|
+
if (!state.installed) state.installed = [];
|
|
183
|
+
if (!state.installed.includes(product)) {
|
|
184
|
+
state.installed.push(product);
|
|
185
|
+
writeHints(state);
|
|
186
|
+
}
|
|
187
|
+
} catch {
|
|
188
|
+
// fail silently
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Record that the user dismissed hints for a product.
|
|
194
|
+
* @param {string} product
|
|
195
|
+
*/
|
|
196
|
+
export function dismiss(product) {
|
|
197
|
+
try {
|
|
198
|
+
const state = readHints();
|
|
199
|
+
if (!state.dismissed) state.dismissed = [];
|
|
200
|
+
if (!state.dismissed.includes(product)) {
|
|
201
|
+
state.dismissed.push(product);
|
|
202
|
+
writeHints(state);
|
|
203
|
+
}
|
|
204
|
+
} catch {
|
|
205
|
+
// fail silently
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Reset all hint state (for testing).
|
|
211
|
+
*/
|
|
212
|
+
export function reset() {
|
|
213
|
+
writeHints({ shown: {}, dismissed: [], installed: [] });
|
|
214
|
+
}
|
package/lib/init.js
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* Creates in the TARGET repo:
|
|
10
10
|
* - claims.json (seeded with constraint claims)
|
|
11
11
|
* - CLAUDE.md (sprint configuration for Claude Code)
|
|
12
|
-
* - .claude/commands/*.md (slash commands)
|
|
12
|
+
* - .claude/commands/wheat/*.md (slash commands)
|
|
13
13
|
* - wheat.config.json (local config pointing back to package)
|
|
14
14
|
*
|
|
15
15
|
* Zero npm dependencies.
|
|
@@ -20,6 +20,8 @@ import path from "path";
|
|
|
20
20
|
import readline from "readline";
|
|
21
21
|
import { execFileSync } from "child_process";
|
|
22
22
|
import { fileURLToPath } from "url";
|
|
23
|
+
import { DEFAULTS, outputMode } from "./defaults.js";
|
|
24
|
+
import { maybeHint } from "./hints.js";
|
|
23
25
|
|
|
24
26
|
const __filename = fileURLToPath(import.meta.url);
|
|
25
27
|
const __dirname = path.dirname(__filename);
|
|
@@ -232,9 +234,9 @@ function buildClaudeMd(meta) {
|
|
|
232
234
|
|
|
233
235
|
function copyCommands(dir) {
|
|
234
236
|
const srcDir = path.join(packageRoot(), "templates", "commands");
|
|
235
|
-
const destDir = target(dir, ".claude", "commands");
|
|
237
|
+
const destDir = target(dir, ".claude", "commands", "wheat");
|
|
236
238
|
|
|
237
|
-
// Create .claude/commands/ if it doesn't exist
|
|
239
|
+
// Create .claude/commands/wheat/ if it doesn't exist
|
|
238
240
|
fs.mkdirSync(destDir, { recursive: true });
|
|
239
241
|
|
|
240
242
|
let copied = 0;
|
|
@@ -247,7 +249,7 @@ function copyCommands(dir) {
|
|
|
247
249
|
|
|
248
250
|
// Don't overwrite existing commands (user may have customized)
|
|
249
251
|
if (fs.existsSync(dest)) {
|
|
250
|
-
console.log(` Skipped .claude/commands/${file} (already exists)`);
|
|
252
|
+
console.log(` Skipped .claude/commands/wheat/${file} (already exists)`);
|
|
251
253
|
continue;
|
|
252
254
|
}
|
|
253
255
|
|
|
@@ -336,6 +338,86 @@ fi
|
|
|
336
338
|
}
|
|
337
339
|
}
|
|
338
340
|
|
|
341
|
+
// ─── .mcp.json & AGENTS.md ──────────────────────────────────────────────────
|
|
342
|
+
|
|
343
|
+
function writeMcpJson(dir) {
|
|
344
|
+
const mcpPath = target(dir, ".mcp.json");
|
|
345
|
+
const wheatEntry = {
|
|
346
|
+
command: "npx",
|
|
347
|
+
args: ["-y", "@grainulation/wheat", "mcp"],
|
|
348
|
+
};
|
|
349
|
+
|
|
350
|
+
let config = { mcpServers: {} };
|
|
351
|
+
if (fs.existsSync(mcpPath)) {
|
|
352
|
+
try {
|
|
353
|
+
config = JSON.parse(fs.readFileSync(mcpPath, "utf8"));
|
|
354
|
+
if (!config.mcpServers) config.mcpServers = {};
|
|
355
|
+
} catch {
|
|
356
|
+
// Corrupted file — overwrite with fresh config
|
|
357
|
+
config = { mcpServers: {} };
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
config.mcpServers.wheat = wheatEntry;
|
|
362
|
+
fs.writeFileSync(mcpPath, JSON.stringify(config, null, 2) + "\n");
|
|
363
|
+
console.log(
|
|
364
|
+
" \x1b[32m+\x1b[0m .mcp.json (Claude Code MCP auto-discovery)"
|
|
365
|
+
);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
function writeAgentsMd(dir, meta) {
|
|
369
|
+
const agentsPath = target(dir, "AGENTS.md");
|
|
370
|
+
const constraintList =
|
|
371
|
+
meta.constraints.length > 0
|
|
372
|
+
? meta.constraints.map((c) => `- ${c}`).join("\n")
|
|
373
|
+
: "- (none specified)";
|
|
374
|
+
|
|
375
|
+
const section = `# Wheat Research Sprint
|
|
376
|
+
|
|
377
|
+
**Question:** ${meta.question}
|
|
378
|
+
|
|
379
|
+
**Audience:** ${meta.audience.join(", ")}
|
|
380
|
+
|
|
381
|
+
**Constraints:**
|
|
382
|
+
${constraintList}
|
|
383
|
+
|
|
384
|
+
**Done looks like:** ${meta.doneCriteria || "A recommendation with evidence"}
|
|
385
|
+
|
|
386
|
+
## Claims System
|
|
387
|
+
|
|
388
|
+
All findings are tracked as typed claims in \`claims.json\`. Claim types: constraint, factual, estimate, risk, recommendation, feedback. Evidence tiers (low to high): stated, web, documented, tested, production.
|
|
389
|
+
|
|
390
|
+
## Key Commands
|
|
391
|
+
|
|
392
|
+
- \`wheat init\` — bootstrap a research sprint
|
|
393
|
+
- \`wheat compile\` — validate and compile claims
|
|
394
|
+
- \`wheat status\` — sprint health dashboard
|
|
395
|
+
- \`wheat search <query>\` — search claims
|
|
396
|
+
- \`wheat add-claim\` — add a new claim
|
|
397
|
+
- \`wheat resolve <id>\` — resolve a conflicting claim
|
|
398
|
+
`;
|
|
399
|
+
|
|
400
|
+
if (fs.existsSync(agentsPath)) {
|
|
401
|
+
const existing = fs.readFileSync(agentsPath, "utf8");
|
|
402
|
+
if (existing.includes("# Wheat Research Sprint")) {
|
|
403
|
+
console.log(
|
|
404
|
+
" \x1b[34m-\x1b[0m AGENTS.md (wheat section already present)"
|
|
405
|
+
);
|
|
406
|
+
return;
|
|
407
|
+
}
|
|
408
|
+
// Append wheat section
|
|
409
|
+
fs.appendFileSync(agentsPath, "\n" + section);
|
|
410
|
+
console.log(
|
|
411
|
+
" \x1b[32m+\x1b[0m AGENTS.md (appended wheat section)"
|
|
412
|
+
);
|
|
413
|
+
} else {
|
|
414
|
+
fs.writeFileSync(agentsPath, section);
|
|
415
|
+
console.log(
|
|
416
|
+
" \x1b[32m+\x1b[0m AGENTS.md (universal AI instructions)"
|
|
417
|
+
);
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
|
|
339
421
|
// ─── Main ────────────────────────────────────────────────────────────────────
|
|
340
422
|
|
|
341
423
|
export async function run(dir, args) {
|
|
@@ -357,7 +439,26 @@ export async function run(dir, args) {
|
|
|
357
439
|
|
|
358
440
|
let meta;
|
|
359
441
|
|
|
360
|
-
if (flags.
|
|
442
|
+
if (flags.auto) {
|
|
443
|
+
// ── Auto mode — question only, smart defaults for everything else ──
|
|
444
|
+
if (!flags.question) {
|
|
445
|
+
console.error(" wheat: no question provided");
|
|
446
|
+
process.exit(1);
|
|
447
|
+
}
|
|
448
|
+
meta = {
|
|
449
|
+
question: flags.question,
|
|
450
|
+
audience: flags.audience
|
|
451
|
+
? flags.audience.split(",").map((s) => s.trim())
|
|
452
|
+
: DEFAULTS.audience,
|
|
453
|
+
constraints: flags.constraints
|
|
454
|
+
? flags.constraints
|
|
455
|
+
.split(";")
|
|
456
|
+
.map((s) => s.trim())
|
|
457
|
+
.filter(Boolean)
|
|
458
|
+
: DEFAULTS.constraints,
|
|
459
|
+
doneCriteria: flags.done || DEFAULTS.doneCriteria,
|
|
460
|
+
};
|
|
461
|
+
} else if (flags.headless || flags["non-interactive"]) {
|
|
361
462
|
// ── Headless mode — all flags required ──
|
|
362
463
|
const missing = [];
|
|
363
464
|
if (!flags.question) missing.push("--question");
|
|
@@ -481,12 +582,18 @@ export async function run(dir, args) {
|
|
|
481
582
|
fs.writeFileSync(claudePath, claudeMd);
|
|
482
583
|
console.log(" \x1b[32m+\x1b[0m CLAUDE.md");
|
|
483
584
|
|
|
484
|
-
// .claude/commands/
|
|
585
|
+
// .claude/commands/wheat/
|
|
485
586
|
const copied = copyCommands(dir);
|
|
486
587
|
console.log(
|
|
487
|
-
` \x1b[32m+\x1b[0m .claude/commands/ (${copied} commands installed)`
|
|
588
|
+
` \x1b[32m+\x1b[0m .claude/commands/wheat/ (${copied} commands installed)`
|
|
488
589
|
);
|
|
489
590
|
|
|
591
|
+
// .mcp.json (Claude Code MCP auto-discovery)
|
|
592
|
+
writeMcpJson(dir);
|
|
593
|
+
|
|
594
|
+
// AGENTS.md (universal AI instructions)
|
|
595
|
+
writeAgentsMd(dir, meta);
|
|
596
|
+
|
|
490
597
|
// Create output directories
|
|
491
598
|
const dirs = ["output", "research", "prototypes", "evidence"];
|
|
492
599
|
for (const d of dirs) {
|
|
@@ -510,7 +617,7 @@ export async function run(dir, args) {
|
|
|
510
617
|
constraints: meta.constraints.length,
|
|
511
618
|
done_criteria: meta.doneCriteria || null,
|
|
512
619
|
claims_seeded: claims.claims.length,
|
|
513
|
-
files_created: ["claims.json", "CLAUDE.md", ".claude/commands/"],
|
|
620
|
+
files_created: ["claims.json", "CLAUDE.md", ".claude/commands/wheat/", ".mcp.json", "AGENTS.md"],
|
|
514
621
|
dir,
|
|
515
622
|
})
|
|
516
623
|
);
|
|
@@ -518,6 +625,33 @@ export async function run(dir, args) {
|
|
|
518
625
|
}
|
|
519
626
|
|
|
520
627
|
console.log();
|
|
628
|
+
// Auto mode: compact output, no tutorial
|
|
629
|
+
if (flags.auto) {
|
|
630
|
+
const mode = outputMode();
|
|
631
|
+
if (mode === "quiet") {
|
|
632
|
+
return;
|
|
633
|
+
} else if (mode === "json") {
|
|
634
|
+
console.log(JSON.stringify({
|
|
635
|
+
question: meta.question,
|
|
636
|
+
audience: meta.audience,
|
|
637
|
+
claims: claims.claims.length,
|
|
638
|
+
dir,
|
|
639
|
+
}));
|
|
640
|
+
return;
|
|
641
|
+
} else {
|
|
642
|
+
console.log();
|
|
643
|
+
console.log(` \x1b[1m\x1b[33mwheat\x1b[0m — sprint created`);
|
|
644
|
+
console.log(` ${meta.question}`);
|
|
645
|
+
console.log(` ${claims.claims.length} constraint(s) seeded`);
|
|
646
|
+
try {
|
|
647
|
+
const hint = maybeHint({ claims: claims.claims }, { context: "init" });
|
|
648
|
+
if (hint) process.stderr.write(hint + "\n");
|
|
649
|
+
} catch { /* non-critical */ }
|
|
650
|
+
console.log();
|
|
651
|
+
return;
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
|
|
521
655
|
console.log(" ─────────────────────────────────────────");
|
|
522
656
|
console.log(` \x1b[1m\x1b[33mSprint ready.\x1b[0m`);
|
|
523
657
|
console.log();
|
|
@@ -528,7 +662,9 @@ export async function run(dir, args) {
|
|
|
528
662
|
console.log(" Created:");
|
|
529
663
|
console.log(" claims.json Your evidence database");
|
|
530
664
|
console.log(" CLAUDE.md AI assistant configuration");
|
|
531
|
-
console.log(" .claude/commands/
|
|
665
|
+
console.log(" .claude/commands/wheat/ slash commands for Claude Code");
|
|
666
|
+
console.log(" .mcp.json Claude Code MCP auto-discovery");
|
|
667
|
+
console.log(" AGENTS.md Universal AI instructions");
|
|
532
668
|
console.log(" output/ Where compiled artifacts land");
|
|
533
669
|
console.log();
|
|
534
670
|
console.log(" Next steps:");
|
|
@@ -539,5 +675,9 @@ export async function run(dir, args) {
|
|
|
539
675
|
);
|
|
540
676
|
console.log();
|
|
541
677
|
console.log(" Trust the process. The evidence will compound.");
|
|
678
|
+
try {
|
|
679
|
+
const hint = maybeHint({ claims: claims.claims }, { context: "init" });
|
|
680
|
+
if (hint) process.stderr.write(hint + "\n");
|
|
681
|
+
} catch { /* non-critical */ }
|
|
542
682
|
console.log();
|
|
543
683
|
}
|
package/lib/update.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* wheat update — Copy/update slash commands to .claude/commands/
|
|
2
|
+
* wheat update — Copy/update slash commands to .claude/commands/wheat/
|
|
3
3
|
*
|
|
4
4
|
* Copies command templates from the installed package into the user's
|
|
5
|
-
* .claude/commands/ directory. Existing files can be overwritten with --force.
|
|
5
|
+
* .claude/commands/wheat/ directory. Existing files can be overwritten with --force.
|
|
6
6
|
*
|
|
7
7
|
* Zero npm dependencies.
|
|
8
8
|
*/
|
|
@@ -21,7 +21,7 @@ function packageRoot() {
|
|
|
21
21
|
export async function run(dir, args) {
|
|
22
22
|
const force = args.includes("--force");
|
|
23
23
|
const srcDir = path.join(packageRoot(), "templates", "commands");
|
|
24
|
-
const destDir = path.join(dir, ".claude", "commands");
|
|
24
|
+
const destDir = path.join(dir, ".claude", "commands", "wheat");
|
|
25
25
|
|
|
26
26
|
fs.mkdirSync(destDir, { recursive: true });
|
|
27
27
|
|
|
@@ -34,7 +34,7 @@ export async function run(dir, args) {
|
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
console.log();
|
|
37
|
-
console.log(` Updating .claude/commands/ (${files.length} commands)`);
|
|
37
|
+
console.log(` Updating .claude/commands/wheat/ (${files.length} commands)`);
|
|
38
38
|
console.log();
|
|
39
39
|
|
|
40
40
|
let updated = 0;
|
package/package.json
CHANGED