@rafinery/cli 0.1.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.
@@ -0,0 +1,303 @@
1
+ # rafa brain contract — v1
2
+
3
+ The strict protocol every brain file obeys, and the `manifest.json` the platform
4
+ ingests. This is the **single source of truth** shared by the writers (atlas, bloom,
5
+ prism) and the reader (the platform ingest). If a file violates this, the compile
6
+ step **fails loudly** and the authoring agent must correct it — the platform never
7
+ guesses, never assumes a value.
8
+
9
+ Pipeline:
10
+
11
+ ```
12
+ agents write .md (frontmatter per this contract)
13
+ → rafa compile (deterministic: parse frontmatter, VALIDATE, fail loudly)
14
+ ├ FAIL → structured error (file · field · rule) → author agent fixes → retry
15
+ └ PASS → emit manifest.json
16
+ → git push → webhook → INGEST = JSON.parse(manifest) + shape-check → Convex
17
+ → query APIs (per repo+branch) → platform renders
18
+ ```
19
+
20
+ Two enforcement points: **compile-time** (structure — this contract) and **prism**
21
+ (semantics — truth of the content). Structure is validated *before* semantics.
22
+
23
+ ---
24
+
25
+ ## File-type registry — every `.rafa` file has a declared class
26
+
27
+ No consumer ever guesses. **Every** file type is classified here; a `structured` type has
28
+ a schema (parse it), a `verbatim` type is prose (display it, never parse for data), a
29
+ `generated` type is machine-written by a named tool. If a file matching a `structured` path
30
+ doesn't validate, compile fails.
31
+
32
+ | Type | Path glob | Class | Schema | Author |
33
+ |---|---|---|---|---|
34
+ | rule | `brain/rules/*.md` | **structured** | §2 | atlas |
35
+ | playbook | `brain/playbooks/*.md` | **structured** | §2 | atlas |
36
+ | health | `brain/checklist.md` | **structured** | §4 | prism |
37
+ | coverage | `brain/coverage.md` | **structured** | §6 | atlas |
38
+ | improvement | `improve/improvements/*.md` | **structured** | §3 | bloom |
39
+ | ledger | `improve/ledger.md` | **structured** | §5 | bloom |
40
+ | plan | `plans/**/*.md` | **structured** | §7 *(wired: Slice 4)* | plan/build |
41
+ | log | `brain/log.md` | **verbatim** | — (prose trail) | conductor |
42
+ | citation-check | `**/citation-check.md` | **generated** | — (by `verify-citations`) | tool |
43
+ | active pointer | `active.md` | **generated** | — (`# <plan-id>` or "No active plan") | conductor |
44
+ | blueprint | `capabilities/*`, `bin/*`, `contract.md` | **verbatim** | — (shipped SOPs/tools) | rafa |
45
+
46
+ `structured` types are compiled into `manifest.json` (§1) — that JSON is the ONLY thing the
47
+ platform ingests. `verbatim`/`generated` files are shown as-is in the file browser (fetched
48
+ lazily), never parsed. A file under a `structured` path that isn't in this table → compile
49
+ error (no rogue unschema'd data files).
50
+
51
+ ---
52
+
53
+ ## 0. Global rules
54
+
55
+ - **Frontmatter is the only machine-read surface.** The markdown **body is prose**
56
+ and is NEVER parsed for data. Every value the platform shows comes from frontmatter
57
+ (via the compiled manifest), or from the body rendered *verbatim* (never scraped).
58
+ - **`schemaVersion` is required** on every file and in the manifest. Unknown version
59
+ → ingest reports an error, does not guess.
60
+ - **`id` is required and stable.** It MUST equal the filename stem (`foo.md` → `foo`).
61
+ Renames are safe because rows are keyed by `id`, not path.
62
+ - **Required fields are required.** Missing/empty required field = compile failure.
63
+ The validator **never fills a default for a required field** — that would be
64
+ assuming. Optional fields have documented defaults.
65
+ - **Enums are closed.** A value outside the enum = compile failure (not coerced).
66
+
67
+ ### Frontmatter grammar (strict subset — the only shapes allowed)
68
+
69
+ Frontmatter is a leading `---` … `---` block. Within it, only these forms are legal;
70
+ anything else is a compile error:
71
+
72
+ ```yaml
73
+ key: scalar # string | number | true | false
74
+ key: "quoted string" # quotes stripped
75
+ key: [a, b, c] # flow list of scalars
76
+ key: { a: x, b: y } # flow map of scalars
77
+ key: # block list …
78
+ - item
79
+ - item
80
+ ```
81
+
82
+ No nested block maps, no multiline scalars, no anchors. Deterministic to parse.
83
+
84
+ ### cites DSL
85
+
86
+ Each cite is one string: `` `<path>:<line> :: <token>` ``
87
+
88
+ - split on the **first** ` :: ` → (`left`, `token`)
89
+ - in `left`, split on the **last** `:` → (`path`, `line`)
90
+ - `line` is digits (`"42"`) or a range (`"14-18"`)
91
+
92
+ Compile parses each into `{ file, line, token }`. A cite that doesn't match → error.
93
+
94
+ ---
95
+
96
+ ## 1. manifest.json — the ingestion contract
97
+
98
+ Machine-generated by `rafa compile`, committed to the brain repo, read by ingest as
99
+ JSON. This is the exact shape the platform binds to.
100
+
101
+ ```jsonc
102
+ {
103
+ "schemaVersion": 1,
104
+ "generatedAt": "2026-07-03T10:00:00Z", // ISO-8601, stamped by compile
105
+ "repo": "owner/repo", // CODE repo full_name (identity)
106
+ "branch": "main", // code branch this brain reflects
107
+ "codeSha": "abc123…", // code commit the brain was built for
108
+
109
+ "health": { // from brain/checklist.md — or null
110
+ "verdict": "PASS", // "PASS" | "ITERATE"
111
+ "score": 95, // 0–100
112
+ "gates": { "fidelity": "pass", "coverage": "pass" },
113
+ "counts": { "blockers": 0, "majors": 0, "minors": 2 }
114
+ },
115
+
116
+ "ledger": { // from improve/ledger.md — or null
117
+ "open": 7,
118
+ "debtScore": 13,
119
+ "byPriority": { "P0": 0, "P1": 1, "P2": 3, "P3": 3 }
120
+ },
121
+
122
+ "coverage": { // from brain/coverage.md — or null
123
+ "domains": [ // one row per domain, its scan status
124
+ { "domain": "design-system", "status": "mapped" },
125
+ { "domain": "external-integrations", "status": "thin" }
126
+ ]
127
+ },
128
+
129
+ "notes": [
130
+ {
131
+ "id": "agent-name-contract",
132
+ "kind": "rule", // "rule" | "playbook" (from directory)
133
+ "type": "contract", // contract | convention | flow | how-to
134
+ "domain": "web-agent-bridge",
135
+ "title": "…",
136
+ "summary": "…",
137
+ "links": ["other-note-id"], // [] if none
138
+ "failure": "silent", // "silent" | "loud" | omitted
139
+ "cites": [{ "file": "src/x.tsx", "line": "42", "token": "research_agent" }],
140
+ "path": "brain/rules/agent-name-contract.md" // prose body (lazy fetch)
141
+ }
142
+ ],
143
+
144
+ "improvements": [
145
+ {
146
+ "id": "dead-model-options-google-crewai",
147
+ "priority": "P1", // P0 | P1 | P2 | P3
148
+ "lens": "product", // security|correctness|performance|architecture|product|ops
149
+ "status": "open", // open | backlog | fixed | wontfix
150
+ "title": "…",
151
+ "summary": "…",
152
+ "fix": "…",
153
+ "leverage": { "impact": "high", "effort": "low" }, // impact/effort ∈ low|medium|high
154
+ "blastRadius": ["web-agent-bridge"], // [] if none
155
+ "cites": [{ "file": "…", "line": "25", "token": "…" }],
156
+ "path": "improve/improvements/dead-model-options-google-crewai.md"
157
+ }
158
+ ],
159
+
160
+ "plans": [] // v1: may be empty; shape in §6
161
+ }
162
+ ```
163
+
164
+ Ingest is `JSON.parse` + a shape-check against this schema. Any mismatch (missing
165
+ key, wrong enum, wrong type, unknown `schemaVersion`) → a **surfaced ingest error**
166
+ on the platform, never a guessed value.
167
+
168
+ ---
169
+
170
+ ## 2. Note files — `brain/rules/*.md`, `brain/playbooks/*.md`
171
+
172
+ `kind` is derived from the directory (`rules/` → `rule`, `playbooks/` → `playbook`).
173
+
174
+ ```yaml
175
+ ---
176
+ schemaVersion: 1
177
+ id: agent-name-contract # required · == filename stem
178
+ type: contract # required · contract | convention | flow | how-to
179
+ domain: web-agent-bridge # required · non-empty
180
+ title: The agent name is a cross-process contract # required
181
+ summary: Only "research_agent" is fully wired end to end # required
182
+ links: [copilotkit-runtime-route-convention] # optional · default []
183
+ failure: silent # optional · silent | loud
184
+ cites: # required · ≥ 1
185
+ - src/lib/model-selector-provider.tsx:42 :: research_agent
186
+ - agents/python/main.py:20 :: research_agent
187
+ ---
188
+ (prose body — rendered verbatim, never parsed)
189
+ ```
190
+
191
+ ---
192
+
193
+ ## 3. Improvement files — `improve/improvements/*.md`
194
+
195
+ ```yaml
196
+ ---
197
+ schemaVersion: 1
198
+ id: dead-model-options-google-crewai # required · == filename stem
199
+ priority: P1 # required · P0 | P1 | P2 | P3
200
+ lens: product # required · security|correctness|performance|architecture|product|ops
201
+ status: open # required · open | backlog | fixed | wontfix
202
+ title: Two of the four model options are dead and fail silently # required
203
+ summary: google_genai and crewai route to unwired agents # required
204
+ fix: Wire both agents in main.py, or remove them from the picker # required
205
+ leverage: { impact: high, effort: low } # required · impact/effort ∈ low|medium|high
206
+ blast_radius: [web-agent-bridge, external-integrations] # optional · default []
207
+ cites: # required · ≥ 1
208
+ - src/components/ModelSelector.tsx:25 :: google_genai
209
+ found: 2026-07-02 # optional · ISO date
210
+ ---
211
+ (prose body)
212
+ ```
213
+
214
+ Note: `title`, `summary`, `fix` are now **required frontmatter** — previously scraped
215
+ from prose. That scraping is gone; these are authored values.
216
+
217
+ ---
218
+
219
+ ## 4. Health file — `brain/checklist.md`
220
+
221
+ ```yaml
222
+ ---
223
+ schemaVersion: 1
224
+ verdict: PASS # required · PASS | ITERATE
225
+ score: 95 # required · 0–100
226
+ gates: { fidelity: pass, coverage: pass } # required · each pass | fail
227
+ counts: { blockers: 0, majors: 0, minors: 2 } # required · non-negative ints
228
+ ---
229
+ (prism report body)
230
+ ```
231
+
232
+ ---
233
+
234
+ ## 5. Ledger file — `improve/ledger.md`
235
+
236
+ The debt figure is **authored in frontmatter** (bloom's number), not parsed from a
237
+ table. `open`/`by_priority` are cross-checked against the improvement rows at ingest.
238
+ Flat keys only — no nesting (compile maps `debt_score`→`debtScore`, `by_priority`→`byPriority`).
239
+
240
+ ```yaml
241
+ ---
242
+ schemaVersion: 1
243
+ open: 7
244
+ debt_score: 13
245
+ by_priority: { P0: 0, P1: 1, P2: 3, P3: 3 }
246
+ ---
247
+ (human-readable trend/tables in the body)
248
+ ```
249
+
250
+ ---
251
+
252
+ ## 6. Coverage file — `brain/coverage.md`
253
+
254
+ The scan's breadth report. `domains` is a **flow map** of `domain: status`, one entry per
255
+ domain found (compile emits it as `[{ domain, status }]` in the manifest). Status enum:
256
+ `mapped | thin | stubbed | empty`. The body holds the human per-criterion PASS/FAIL narrative.
257
+
258
+ ```yaml
259
+ ---
260
+ schemaVersion: 1
261
+ domains: { design-system: mapped, components: mapped, api: thin, external-integrations: empty }
262
+ ---
263
+ (per-criterion PASS/FAIL narrative in the body)
264
+ ```
265
+
266
+ ---
267
+
268
+ ## 7. Plan files — `plans/**/*.md` (Slice 4)
269
+
270
+ Each child owns exactly one file (so merges never conflict; parent progress is
271
+ derived by counting).
272
+
273
+ ```yaml
274
+ ---
275
+ schemaVersion: 1
276
+ id: c1-schema # required · == filename stem
277
+ plan: multitenant # required · the parent plan id
278
+ parent: null # required · parent plan id, or null for the root
279
+ kind: parent | child # required
280
+ title: … # required
281
+ status: todo | in-progress | done | blocked # required (child); parent may omit
282
+ branch: feat/mt-c1 # optional · the branch this executes on
283
+ baseSha: abc123 # optional · commit it was cut from
284
+ ---
285
+ ```
286
+
287
+ Parent progress is `count(children where status == done) / count(children)` —
288
+ derived, never stored.
289
+
290
+ ---
291
+
292
+ ## 8. Versioning & the validate-and-correct loop
293
+
294
+ - **`schemaVersion`** bumps when this contract changes incompatibly. Ingest refuses
295
+ an unknown version (surfaced error) rather than mis-reading it.
296
+ - **Validate-and-correct:** `rafa compile` validates every file. On failure it emits
297
+ a structured error — `path · field · rule` — to the **authoring agent** (atlas for
298
+ notes, bloom for improvements/ledger, prism for checklist). The agent edits the
299
+ file and compile re-runs. Bounded retries; if it can't converge, a **hard failure**
300
+ blocks the push (better than a silently-wrong brain).
301
+ - The validator **reports, never auto-fills** a required value.
302
+
303
+ This contract is the thing to test against.
@@ -0,0 +1,86 @@
1
+ // The blueprint — the exact set of files rafa vendors into a repo (shadcn-style:
2
+ // the client OWNS these copies). This one list is the source of truth for both
3
+ // `bundle-blueprint` (monorepo root → package/blueprint) and `init`/`update`
4
+ // (package/blueprint → your repo).
5
+
6
+ import {
7
+ existsSync,
8
+ mkdirSync,
9
+ cpSync,
10
+ readdirSync,
11
+ statSync,
12
+ } from "node:fs";
13
+ import { dirname, join } from "node:path";
14
+ import { fileURLToPath } from "node:url";
15
+
16
+ // Files a client owns + tunes (agents, SOPs). Tools + contract move in lockstep.
17
+ export const BLUEPRINT = {
18
+ files: [
19
+ ".claude/agents/atlas.md",
20
+ ".claude/agents/prism.md",
21
+ ".claude/agents/bloom.md",
22
+ ".claude/commands/rafa.md",
23
+ ".rafa/contract.md",
24
+ ".rafa/bin/verify-citations.mjs",
25
+ ".rafa/bin/rafa-compile.mjs",
26
+ ".rafa/bin/rafa-push.mjs",
27
+ ],
28
+ dirs: [".rafa/capabilities"],
29
+ };
30
+
31
+ const HERE = dirname(fileURLToPath(import.meta.url)); // …/packages/cli/lib
32
+
33
+ // Walk up to the monorepo root (the dir that holds the source `.rafa/contract.md`).
34
+ function findRepoRoot() {
35
+ let d = HERE;
36
+ for (let i = 0; i < 8; i++) {
37
+ d = join(d, "..");
38
+ if (existsSync(join(d, ".rafa", "contract.md"))) return d;
39
+ }
40
+ return null;
41
+ }
42
+
43
+ // Where the blueprint is read FROM when vendoring into a repo:
44
+ // - published package → <package>/blueprint (bundled by `prepare`)
45
+ // - dev in the monorepo → the repo root itself
46
+ export function blueprintSource() {
47
+ const bundled = join(HERE, "..", "blueprint");
48
+ if (existsSync(join(bundled, ".rafa", "contract.md"))) return bundled;
49
+ const root = findRepoRoot();
50
+ if (root) return root;
51
+ throw new Error(
52
+ "blueprint not found — run `npm run bundle` in packages/cli, or reinstall.",
53
+ );
54
+ }
55
+
56
+ // The monorepo root, for `bundle-blueprint` (copies root → package/blueprint).
57
+ export function repoRoot() {
58
+ const root = findRepoRoot();
59
+ if (!root) throw new Error("could not locate the monorepo root");
60
+ return root;
61
+ }
62
+
63
+ // Copy every blueprint file/dir from `from` into `to`. Returns the relative paths written.
64
+ export function copyBlueprint(from, to) {
65
+ const written = [];
66
+ for (const rel of BLUEPRINT.files) {
67
+ const src = join(from, rel);
68
+ if (!existsSync(src)) continue;
69
+ const dst = join(to, rel);
70
+ mkdirSync(dirname(dst), { recursive: true });
71
+ cpSync(src, dst);
72
+ written.push(rel);
73
+ }
74
+ for (const rel of BLUEPRINT.dirs) {
75
+ const src = join(from, rel);
76
+ if (!existsSync(src)) continue;
77
+ const dst = join(to, rel);
78
+ mkdirSync(dst, { recursive: true });
79
+ cpSync(src, dst, { recursive: true });
80
+ // list the .md files copied, for a useful summary
81
+ for (const e of readdirSync(src)) {
82
+ if (statSync(join(src, e)).isFile()) written.push(join(rel, e));
83
+ }
84
+ }
85
+ return written;
86
+ }
@@ -0,0 +1,24 @@
1
+ // rafa compile — run the vendored contract gate on this repo's brain. Thin wrapper
2
+ // over .rafa/bin/rafa-compile.mjs (the client's own copy = its contract version).
3
+
4
+ import { existsSync } from "node:fs";
5
+ import { execSync } from "node:child_process";
6
+ import { join } from "node:path";
7
+
8
+ export default async function compile(args) {
9
+ const script = join(process.cwd(), ".rafa", "bin", "rafa-compile.mjs");
10
+ if (!existsSync(script)) {
11
+ console.error(
12
+ "✗ .rafa/bin/rafa-compile.mjs not found — run `rafa init` (or `rafa update`) first.",
13
+ );
14
+ process.exit(1);
15
+ }
16
+ try {
17
+ execSync(`node ${JSON.stringify(script)} ${args.join(" ")}`.trim(), {
18
+ stdio: "inherit",
19
+ cwd: process.cwd(),
20
+ });
21
+ } catch {
22
+ process.exit(1); // compile already printed its path·field·rule errors
23
+ }
24
+ }
package/lib/init.mjs ADDED
@@ -0,0 +1,91 @@
1
+ // rafa init — provision a repo: vendor the blueprint, and (with a platform setup
2
+ // URL) wire the brain remote. Run inside your code repo's root.
3
+
4
+ import {
5
+ existsSync,
6
+ writeFileSync,
7
+ readFileSync,
8
+ appendFileSync,
9
+ } from "node:fs";
10
+ import { execSync } from "node:child_process";
11
+ import { join } from "node:path";
12
+ import { blueprintSource, copyBlueprint } from "./blueprint.mjs";
13
+
14
+ const die = (m) => {
15
+ console.error(`✗ ${m}`);
16
+ process.exit(1);
17
+ };
18
+ const run = (cmd, cwd) =>
19
+ execSync(cmd, { cwd, stdio: ["ignore", "pipe", "pipe"], encoding: "utf8" }).trim();
20
+
21
+ export default async function init(args) {
22
+ const url = args.find((a) => !a.startsWith("-"));
23
+ const TARGET = process.cwd();
24
+ if (!existsSync(join(TARGET, ".git")))
25
+ die("Not a git repo — run this inside your code repo's root.");
26
+
27
+ // Optional platform setup (short-code URL → non-secret provisioning payload).
28
+ let p = null;
29
+ if (url) {
30
+ try {
31
+ const res = await fetch(url);
32
+ p = await res.json();
33
+ if (!res.ok || p?.error)
34
+ die(`Couldn't fetch setup: ${p?.error ?? res.status} (setup URLs expire in 15 min).`);
35
+ } catch (e) {
36
+ die(`Couldn't reach the setup URL: ${e instanceof Error ? e.message : e}`);
37
+ }
38
+ if (!p?.repoId || !p?.brainRepoUrl) die("Setup is missing repo/brain details.");
39
+ console.log(`rafa init → ${p.repoName} (${p.repoId})`);
40
+ console.log(` brain remote: ${p.brainRepoUrl}`);
41
+ } else {
42
+ console.log("rafa init — vendoring the blueprint (no setup URL)");
43
+ }
44
+
45
+ // Vendor the blueprint into .claude/ + .rafa/ (you own these copies).
46
+ const written = copyBlueprint(blueprintSource(), TARGET);
47
+ if (!existsSync(join(TARGET, ".rafa", "active.md")))
48
+ writeFileSync(join(TARGET, ".rafa", "active.md"), "# No active plan\n");
49
+ console.log(` ✓ vendored ${written.length} blueprint files → .claude/ + .rafa/`);
50
+
51
+ // Keep the brain out of the code repo's diffs.
52
+ const gi = join(TARGET, ".gitignore");
53
+ const body = existsSync(gi) ? readFileSync(gi, "utf8") : "";
54
+ if (!/^\.rafa\/?\s*$/m.test(body)) {
55
+ appendFileSync(gi, `${body.endsWith("\n") || body === "" ? "" : "\n"}.rafa/\n`);
56
+ console.log(" ✓ .rafa/ → .gitignore");
57
+ }
58
+
59
+ if (p) {
60
+ writeFileSync(
61
+ join(TARGET, "rafa.json"),
62
+ JSON.stringify(
63
+ {
64
+ name: p.repoName,
65
+ repoId: p.repoId,
66
+ codeRepo: p.codeRepoUrl ?? null,
67
+ brainRepo: p.brainRepoUrl,
68
+ provisionedFor: p.owner ?? null,
69
+ },
70
+ null,
71
+ 2,
72
+ ) + "\n",
73
+ );
74
+ const rafaDir = join(TARGET, ".rafa");
75
+ if (!existsSync(join(rafaDir, ".git"))) run("git init -b main", rafaDir);
76
+ try {
77
+ run("git remote get-url origin", rafaDir);
78
+ run(`git remote set-url origin ${p.brainRepoUrl}`, rafaDir);
79
+ } catch {
80
+ run(`git remote add origin ${p.brainRepoUrl}`, rafaDir);
81
+ }
82
+ console.log(" ✓ .rafa/ is its own git repo → origin = brain remote");
83
+ }
84
+
85
+ console.log(
86
+ `\n✓ Provisioned.\n\nNext steps:\n` +
87
+ " 1. Restart Claude Code in this repo (reload the /rafa command + agents).\n" +
88
+ " 2. Run /rafa scan — build + validate the brain, then rafa push to sync it.\n" +
89
+ " 3. Open the repo on the rafinery platform — its Overview shows the brain once pushed.",
90
+ );
91
+ }
package/lib/push.mjs ADDED
@@ -0,0 +1,24 @@
1
+ // rafa push — compile (contract gate) then push .rafa/ to the brain remote. Thin
2
+ // wrapper over the vendored .rafa/bin/rafa-push.mjs (which runs compile itself).
3
+
4
+ import { existsSync } from "node:fs";
5
+ import { execSync } from "node:child_process";
6
+ import { join } from "node:path";
7
+
8
+ export default async function push(args) {
9
+ const script = join(process.cwd(), ".rafa", "bin", "rafa-push.mjs");
10
+ if (!existsSync(script)) {
11
+ console.error(
12
+ "✗ .rafa/bin/rafa-push.mjs not found — run `rafa init` (or `rafa update`) first.",
13
+ );
14
+ process.exit(1);
15
+ }
16
+ try {
17
+ execSync(`node ${JSON.stringify(script)} ${args.join(" ")}`.trim(), {
18
+ stdio: "inherit",
19
+ cwd: process.cwd(),
20
+ });
21
+ } catch {
22
+ process.exit(1);
23
+ }
24
+ }
package/lib/update.mjs ADDED
@@ -0,0 +1,22 @@
1
+ // rafa update — re-sync the blueprint into this repo. shadcn-style: you own your
2
+ // copies, so this is an opt-in refresh (run it to pull the latest agents,
3
+ // capabilities, contract, and gates after upgrading the CLI).
4
+
5
+ import { existsSync } from "node:fs";
6
+ import { join } from "node:path";
7
+ import { blueprintSource, copyBlueprint } from "./blueprint.mjs";
8
+
9
+ export default async function update() {
10
+ const TARGET = process.cwd();
11
+ if (!existsSync(join(TARGET, ".rafa")) && !existsSync(join(TARGET, ".claude"))) {
12
+ console.error("✗ This repo isn't provisioned yet — run `rafa init` first.");
13
+ process.exit(1);
14
+ }
15
+ const written = copyBlueprint(blueprintSource(), TARGET);
16
+ console.log(`✓ rafa update — synced ${written.length} blueprint files:`);
17
+ for (const f of written) console.log(` ${f}`);
18
+ console.log(
19
+ "\n Your brain data (.rafa/brain, .rafa/improve) was untouched. " +
20
+ "Restart Claude Code to reload the agents.",
21
+ );
22
+ }
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "@rafinery/cli",
3
+ "version": "0.1.0",
4
+ "description": "rafa — the AI engineer CLI. Vendors the rafa blueprint into your repo (shadcn-style) and runs the deterministic contract gates.",
5
+ "type": "module",
6
+ "bin": {
7
+ "rafa": "bin/rafa.mjs"
8
+ },
9
+ "files": [
10
+ "bin",
11
+ "lib",
12
+ "blueprint"
13
+ ],
14
+ "engines": {
15
+ "node": ">=18"
16
+ },
17
+ "publishConfig": {
18
+ "access": "public"
19
+ },
20
+ "scripts": {
21
+ "bundle": "node scripts/bundle-blueprint.mjs",
22
+ "prepare": "node scripts/bundle-blueprint.mjs"
23
+ },
24
+ "keywords": [
25
+ "rafa",
26
+ "rafinery",
27
+ "ai",
28
+ "codebase",
29
+ "brain",
30
+ "agent",
31
+ "cli"
32
+ ],
33
+ "license": "MIT",
34
+ "repository": {
35
+ "type": "git",
36
+ "url": "git+https://github.com/rafinery-ai/rafinery.git",
37
+ "directory": "packages/cli"
38
+ },
39
+ "homepage": "https://github.com/rafinery-ai/rafinery/tree/main/packages/cli"
40
+ }