@rafinery/cli 0.7.1 → 0.8.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.
@@ -38,6 +38,11 @@ import { readFileSync, writeFileSync, readdirSync, statSync, existsSync, mkdtemp
38
38
  import { execSync } from "node:child_process";
39
39
  import { join } from "node:path";
40
40
  import { tmpdir } from "node:os";
41
+ // Format utilities only (splitConcept/extractLinks/unresolvedLinks) — the five
42
+ // GATES above stay independently implemented; the LINKS lane below is a
43
+ // non-failing warn lane, so sharing the format package does not couple a gate
44
+ // to the producer it checks.
45
+ import { splitConcept, extractLinks, unresolvedLinks, OKF_RESERVED } from "@rafinery/okf";
41
46
 
42
47
  export const CHECKER_VERSION = 2; // v1 = the 3-gate era (resolution/completeness/policy)
43
48
 
@@ -60,7 +65,26 @@ function walk(dir) {
60
65
  if (!existsSync(dir)) return [];
61
66
  return readdirSync(dir).flatMap((e) => {
62
67
  const p = join(dir, e);
63
- return statSync(p).isDirectory() ? walk(p) : (e.endsWith(".md") && !e.startsWith("_")) ? [p] : []; // ignore _*.md scratch
68
+ if (statSync(p).isDirectory()) return walk(p);
69
+ // ignore _*.md scratch + OKF-reserved listings (index.md is generated, never
70
+ // a note) + *.theirs.md conflict copies (compile gates those with a typed error)
71
+ return e.endsWith(".md") &&
72
+ !e.startsWith("_") &&
73
+ !e.endsWith(".theirs.md") &&
74
+ !OKF_RESERVED.has(e)
75
+ ? [p]
76
+ : [];
77
+ });
78
+ }
79
+
80
+ // Every .md in the bundle (reserved included — they are legitimate mdlink
81
+ // targets), as "/bundle-relative" paths for the LINKS lane's resolution set.
82
+ function walkBundleMd(dir, base = dir) {
83
+ if (!existsSync(dir)) return [];
84
+ return readdirSync(dir).flatMap((e) => {
85
+ const p = join(dir, e);
86
+ if (statSync(p).isDirectory()) return e === ".git" ? [] : walkBundleMd(p, base);
87
+ return e.endsWith(".md") ? ["/" + p.slice(base.length + 1)] : [];
64
88
  });
65
89
  }
66
90
 
@@ -178,12 +202,24 @@ export function runVerifyCitations(argv = []) {
178
202
  return 0;
179
203
  }
180
204
 
205
+ // LINKS lane resolution sets — BUNDLE-wide (a note may link across dirs):
206
+ // ids = concept stems of every knowledge class; paths = every bundle .md as
207
+ // "/bundle-relative" (reserved files included — legitimate mdlink targets).
208
+ const BUNDLE_ROOT = join(ROOT, "..");
209
+ const bundleIds = new Set(
210
+ ["brain/rules", "brain/playbooks", "improve/improvements"]
211
+ .flatMap((d) => walk(join(BUNDLE_ROOT, d)))
212
+ .map((p) => p.split("/").pop().replace(/\.md$/, "")),
213
+ );
214
+ const bundlePaths = new Set(walkBundleMd(BUNDLE_ROOT));
215
+
181
216
  const resolution = []; // { note, loc, token, ok, reason }
182
217
  const completeness = []; // { note, anchor, site, ok, reason }
183
218
  const policy = []; // { note, ok, reason }
184
219
  const absence = []; // { note, token, site, ok, reason }
185
220
  const inventory = []; // { name, glob, declared, found, ok, reason, paths }
186
221
  const warns = []; // { note, phrase } — heuristic, never fails the gate
222
+ const linkWarns = []; // { note, kind, target } — dangling links (OKF §5.3), never fails the gate
187
223
 
188
224
  for (const note of notes) {
189
225
  const rel = note.replace(ROOT + "/", "");
@@ -256,6 +292,40 @@ export function runVerifyCitations(argv = []) {
256
292
  const m = (title + " · " + summary).match(ABSENCE_HEURISTIC);
257
293
  if (m) warns.push({ note: rel, phrase: m[0] });
258
294
  }
295
+
296
+ // LINKS lane (non-failing, OKF §5.3): every frontmatter `links:` id, body
297
+ // [[wikilink]], and body bundle-relative markdown link should resolve to a
298
+ // bundle concept/file. A dangling link may be not-yet-written knowledge —
299
+ // prism's worklist, never a gate failure.
300
+ {
301
+ const raw = lines.join("\n");
302
+ const split = splitConcept(raw);
303
+ const body = split ? split.body : raw;
304
+ const fmLinks = [];
305
+ let inLinks = false;
306
+ for (const l of split?.fmLines ?? []) {
307
+ const flow = l.match(/^links:\s*\[(.*)\]\s*$/);
308
+ if (flow) {
309
+ for (const item of flow[1].split(",")) {
310
+ const v = strip(item);
311
+ if (v) fmLinks.push(v);
312
+ }
313
+ inLinks = false;
314
+ continue;
315
+ }
316
+ if (/^links:\s*$/.test(l)) { inLinks = true; continue; }
317
+ if (inLinks) {
318
+ const item = l.match(/^\s*-\s+(.+?)\s*$/);
319
+ if (item) { fmLinks.push(strip(item[1])); continue; }
320
+ if (/^\S/.test(l)) inLinks = false;
321
+ }
322
+ }
323
+ const dangling = unresolvedLinks(extractLinks({ links: fmLinks }, body), {
324
+ ids: bundleIds,
325
+ paths: bundlePaths,
326
+ });
327
+ for (const d of dangling) linkWarns.push({ note: rel, kind: d.kind, target: d.target });
328
+ }
259
329
  }
260
330
 
261
331
  // INVENTORY — coverage.md declared counts vs tracked reality
@@ -309,10 +379,24 @@ export function runVerifyCitations(argv = []) {
309
379
  `## Warns (heuristic, non-failing — existence-shaped title/summary with no \`absent:\` declared): ${warns.length}`,
310
380
  ...warns.map((w) => `⚠ ${w.note} — reads as an absence claim ("${w.phrase}") — declare \`absent: <token>\` so the gate can re-grep it, or reword`),
311
381
  "",
382
+ `## Links (non-failing, OKF §5.3 — dangling cross-links, bundle-wide resolution): ${linkWarns.length}`,
383
+ ...linkWarns.map((w) => `⚠ ${w.note} — ${w.kind} → ${w.target} does not resolve in the bundle (not-yet-written knowledge, a typo, or a moved file)`),
384
+ "",
312
385
  fail ? `**${fail} FAILED.**` : "**All pass.**",
313
386
  "",
314
387
  ].join("\n");
315
- writeFileSync(join(ROOT, "citation-check.md"), md);
388
+ // The report is itself a generated concept (contract §11): it self-describes.
389
+ const at = new Date().toISOString();
390
+ const reportFm = [
391
+ "---",
392
+ 'type: "Citation Check"',
393
+ 'title: "Citation check report"',
394
+ `description: "checker v${CHECKER_VERSION} — ${fail ? `${fail} failed` : "all gates pass"} · ${warns.length + linkWarns.length} warn(s)"`,
395
+ `timestamp: ${at}`,
396
+ "---",
397
+ "",
398
+ ].join("\n");
399
+ writeFileSync(join(ROOT, "citation-check.md"), reportFm + md);
316
400
 
317
401
  // Machine record of THIS run — compile folds it into manifest.citations so the
318
402
  // platform knows the gate level a brain passed. Recorded, never assumed.
@@ -321,7 +405,7 @@ export function runVerifyCitations(argv = []) {
321
405
  JSON.stringify(
322
406
  {
323
407
  checkerVersion: CHECKER_VERSION,
324
- at: new Date().toISOString(),
408
+ at,
325
409
  pass: fail === 0,
326
410
  gates: {
327
411
  resolution: { pass: resolution.length - rFail.length, total: resolution.length },
@@ -331,6 +415,7 @@ export function runVerifyCitations(argv = []) {
331
415
  inventory: { pass: inventory.length - iFail.length, total: inventory.length },
332
416
  },
333
417
  warns: warns.length,
418
+ linkWarns: linkWarns.length, // additive optional — compile only reads { checkerVersion, pass, at }
334
419
  },
335
420
  null,
336
421
  2,
@@ -341,6 +426,7 @@ export function runVerifyCitations(argv = []) {
341
426
  console.log(
342
427
  `resolution ${resolution.length - rFail.length}/${resolution.length} · completeness ${completeness.length - cFail.length}/${completeness.length} · policy ${policy.length - pFail.length}/${policy.length} · absence ${absence.length - aFail.length}/${absence.length} · inventory ${inventory.length - iFail.length}/${inventory.length}` +
343
428
  (warns.length ? ` · ${warns.length} warn(s)` : "") +
429
+ (linkWarns.length ? ` · ${linkWarns.length} dangling link(s)` : "") +
344
430
  (fail ? ` · ${fail} FAILED` : " · all pass"),
345
431
  );
346
432
  return fail ? 1 : 0;
@@ -26,7 +26,9 @@ const readJson = (file) => {
26
26
  // The platform repo id this working copy is provisioned for.
27
27
  export function repoIdOf(cwd) {
28
28
  const stamp = readStamp(cwd);
29
- return typeof stamp.repoId === "string" && stamp.repoId !== "" ? stamp.repoId : null;
29
+ return typeof stamp.repoId === "string" && stamp.repoId !== ""
30
+ ? stamp.repoId
31
+ : null;
30
32
  }
31
33
 
32
34
  export function resolveMcp(cwd) {
@@ -66,26 +68,51 @@ let rpcId = 0;
66
68
  // message on transport or tool errors.
67
69
  export async function callTool(cwd, tool, args = {}) {
68
70
  const { repoId, key, mcpUrl } = resolveMcp(cwd);
69
- const res = await fetch(mcpUrl, {
70
- method: "POST",
71
- headers: {
72
- "content-type": "application/json",
73
- authorization: `Bearer ${key}`,
74
- },
75
- body: JSON.stringify({
76
- jsonrpc: "2.0",
77
- id: ++rpcId,
78
- method: "tools/call",
79
- params: { name: tool, arguments: { repo: repoId, ...args } },
80
- }),
81
- });
71
+ // A localhost platform is only reachable on the machine running the dev
72
+ // server — on a CI runner it is a guaranteed dead end; say so up front
73
+ // instead of a bare "fetch failed".
74
+ if (
75
+ process.env.CI &&
76
+ /\/\/(localhost|127\.0\.0\.1|0\.0\.0\.0)[:/]/.test(mcpUrl)
77
+ )
78
+ throw new Error(
79
+ `platform MCP url is ${mcpUrl} — a localhost dev server is not reachable from CI.\n` +
80
+ ` Set the RAFA_MCP_URL secret to your deployed platform's /api/mcp URL, or run the\n` +
81
+ ` session fallback instead (/rafa distill <branch> in Claude Code).`,
82
+ );
83
+ let res;
84
+ try {
85
+ res = await fetch(mcpUrl, {
86
+ method: "POST",
87
+ headers: {
88
+ "content-type": "application/json",
89
+ authorization: `Bearer ${key}`,
90
+ },
91
+ body: JSON.stringify({
92
+ jsonrpc: "2.0",
93
+ id: ++rpcId,
94
+ method: "tools/call",
95
+ params: { name: tool, arguments: { repo: repoId, ...args } },
96
+ }),
97
+ });
98
+ } catch (e) {
99
+ throw new Error(
100
+ `cannot reach the platform MCP at ${mcpUrl} (${e instanceof Error ? e.message : e}) — ` +
101
+ `check the URL (RAFA_MCP_URL / credentials.json / .mcp.json) and that the platform is deployed and reachable from here.`,
102
+ );
103
+ }
82
104
  if (res.status === 401)
83
- throw new Error("agent key rejected (401) — revoked or wrong platform; mint a new one (repo → Agent access)");
105
+ throw new Error(
106
+ "agent key rejected (401) — revoked or wrong platform; mint a new one (repo → Agent access)",
107
+ );
84
108
  if (res.status === 429)
85
109
  throw new Error("rate limited (429) — back off and retry");
86
110
  if (!res.ok) throw new Error(`MCP endpoint returned HTTP ${res.status}`);
87
111
  const body = await res.json();
88
- if (body.error) throw new Error(`MCP error: ${body.error.message ?? JSON.stringify(body.error)}`);
112
+ if (body.error)
113
+ throw new Error(
114
+ `MCP error: ${body.error.message ?? JSON.stringify(body.error)}`,
115
+ );
89
116
  const result = body.result ?? {};
90
117
  const payload = result.structuredContent ?? null;
91
118
  if (result.isError) {
package/lib/okf.mjs ADDED
@@ -0,0 +1,110 @@
1
+ // rafa okf — the OKF surface commands (contract §11). The format machinery is
2
+ // @rafinery/okf; the rafa profile decides where classes live and what stamps
3
+ // are derivable.
4
+ //
5
+ // rafa okf materialize the surface on .rafa (stamps · link
6
+ // transpile · citations sections · index tree).
7
+ // Runs automatically inside `rafa push`.
8
+ // rafa okf check OKF §9 conformance walk of the bundle — errors
9
+ // fail (exit 1), soft guidance warns. Exemptions
10
+ // (active.md) are explicit and surfaced.
11
+ // rafa okf new <class> <id> [--key=value …] [--cite="f:l :: tok" …] [--link=id …]
12
+ // mint a conformant concept skeleton in the class's
13
+ // directory. Nothing is invented: flags you omit are
14
+ // absent, and `rafa compile` fails required ones
15
+ // loudly — that is the validate-and-correct loop.
16
+
17
+ import { existsSync, writeFileSync, mkdirSync } from "node:fs";
18
+ import { join, dirname } from "node:path";
19
+ import { runOkfEmit } from "./gate/okf-emit.mjs";
20
+ import { SCHEMA_VERSION } from "./gate/compile.mjs";
21
+ import {
22
+ OKF_VERSION,
23
+ validateBundle,
24
+ newConcept,
25
+ FILE_CLASSES,
26
+ RAFA_EXEMPT,
27
+ } from "@rafinery/okf";
28
+
29
+ export default async function okf(args = []) {
30
+ const [sub, ...rest] = args[0]?.startsWith("--") ? [undefined, ...args] : args;
31
+ const ROOT =
32
+ (args.find((a) => a.startsWith("--root=")) ?? "").slice(7) || ".rafa";
33
+
34
+ if (sub === undefined || sub === "emit") {
35
+ process.exit(runOkfEmit(args.filter((a) => a.startsWith("--"))));
36
+ }
37
+
38
+ if (sub === "check") {
39
+ const { conformant, checked, errors, warnings } = validateBundle(ROOT, {
40
+ exempt: RAFA_EXEMPT,
41
+ });
42
+ for (const e of errors) console.log(`✗ ${e.path} — ${e.rule}`);
43
+ for (const w of warnings) console.log(`⚠ ${w.path} — ${w.rule}`);
44
+ console.log(
45
+ `${conformant ? "✓" : "✗"} rafa okf check: ${checked} files · ` +
46
+ `${errors.length} error(s) · ${warnings.length} warning(s) — ` +
47
+ (conformant ? `bundle conforms to OKF v${OKF_VERSION}` : "bundle is NOT conformant"),
48
+ );
49
+ process.exit(conformant ? 0 : 1);
50
+ }
51
+
52
+ if (sub === "new") {
53
+ const [cls, id] = rest.filter((a) => !a.startsWith("--"));
54
+ const registry = FILE_CLASSES[cls];
55
+ if (!registry || registry.singleton) {
56
+ console.error(
57
+ `✗ rafa okf new: <class> must be one of ${Object.entries(FILE_CLASSES)
58
+ .filter(([, c]) => !c.singleton)
59
+ .map(([k]) => k)
60
+ .join(" | ")} (singletons are authored at their fixed path)`,
61
+ );
62
+ process.exit(1);
63
+ }
64
+ if (!id) {
65
+ console.error("✗ rafa okf new: missing <id> (kebab-case, becomes the filename stem)");
66
+ process.exit(1);
67
+ }
68
+ const fields = { schemaVersion: SCHEMA_VERSION }; // never a literal — moves with the contract
69
+ const listFields = { cites: [], links: [] };
70
+ for (const a of rest.filter((a) => a.startsWith("--"))) {
71
+ const m = a.match(/^--([A-Za-z_][A-Za-z0-9_]*)=(.*)$/);
72
+ if (!m) continue;
73
+ const [, key, value] = m;
74
+ if (key === "cite") listFields.cites.push(value);
75
+ else if (key === "link") listFields.links.push(value);
76
+ else if (key !== "root") fields[key] = value;
77
+ }
78
+ // The improvement class's OKF type is constant; notes author their own
79
+ // contract enum via --type (compile owns the enum — no default here).
80
+ const type = fields.type ?? registry.okfType ?? "";
81
+ delete fields.type;
82
+ let content;
83
+ try {
84
+ content = newConcept({ type, id, fields, listFields });
85
+ } catch (e) {
86
+ console.error(`✗ rafa okf new: ${e instanceof Error ? e.message : e}`);
87
+ console.error(
88
+ cls === "improvement"
89
+ ? ""
90
+ : " notes require --type=<contract|convention|flow|how-to> (the compile enum)",
91
+ );
92
+ process.exit(1);
93
+ }
94
+ const path = join(ROOT, registry.dir, `${id}.md`);
95
+ if (existsSync(path)) {
96
+ console.error(`✗ rafa okf new: ${path} already exists — refusing to overwrite`);
97
+ process.exit(1);
98
+ }
99
+ mkdirSync(dirname(path), { recursive: true });
100
+ writeFileSync(path, content);
101
+ console.log(
102
+ `✓ rafa okf new: ${path} — now write the body + fill required fields, ` +
103
+ `then \`rafa verify-citations\` + \`rafa compile\` gate it.`,
104
+ );
105
+ process.exit(0);
106
+ }
107
+
108
+ console.error(`✗ rafa okf: unknown subcommand "${sub}" (emit | check | new)`);
109
+ process.exit(1);
110
+ }
package/lib/push.mjs CHANGED
@@ -14,6 +14,7 @@ import { existsSync, readFileSync, writeFileSync } from "node:fs";
14
14
  import { join } from "node:path";
15
15
  import { runCompile } from "./gate/compile.mjs";
16
16
  import { runVerifyCitations } from "./gate/verify-citations.mjs";
17
+ import { runOkfEmit } from "./gate/okf-emit.mjs";
17
18
  import { ensureBrainRepo, remoteDefaultBranch, inDir, die } from "./brain-repo.mjs";
18
19
  import { CLI_VERSION } from "./releases.mjs";
19
20
 
@@ -40,6 +41,38 @@ export default async function push(args = []) {
40
41
  /* no origin — manifest.repo stays "" */
41
42
  }
42
43
 
44
+ // ── stamped contract copy rides the push (before emit so the root index
45
+ // lists it from the very first push) ──
46
+ const canonical = join(ROOT, ".claude", "rafa", "contract.md");
47
+ if (existsSync(canonical)) {
48
+ // The copy is itself a generated concept (contract §11): it self-describes.
49
+ const stampFm =
50
+ `---\n` +
51
+ `type: Reference\n` +
52
+ `title: "rafa brain contract (stamped copy)"\n` +
53
+ `description: "the file protocol this bundle conforms to — canonical: .claude/rafa/contract.md in the code repo"\n` +
54
+ `timestamp: ${new Date().toISOString()}\n` +
55
+ `---\n\n`;
56
+ const stampHeader =
57
+ `<!-- STAMPED COPY — canonical: .claude/rafa/contract.md in the code repo.\n` +
58
+ ` cli: ${CLI_VERSION} · brain-for: ${codeSha} · stamped: ${new Date().toISOString()} -->\n\n`;
59
+ writeFileSync(join(rafaDir, "contract.md"), stampFm + stampHeader + readFileSync(canonical, "utf8"));
60
+ } else {
61
+ console.log(
62
+ " ! no .claude/rafa/contract.md in this repo — pushing without the stamped contract copy " +
63
+ "(run `rafa update` to vendor it)",
64
+ );
65
+ }
66
+
67
+ // ── OKF surface (in-process, contract §11) ──
68
+ // Materialize the interchange surface BEFORE the gates so everything the
69
+ // emitter writes is checked like any authored file: stamps (type/description/
70
+ // timestamp/tags where derivable), wikilink→markdown-link transpile, generated
71
+ // `# Citations` sections, and the index.md tree. A pushed brain repo is a
72
+ // conformant OKF v0.1 bundle any foreign consumer can read without rafa.
73
+ console.log("• rafa okf — materializing the OKF surface (stamps · links · citations · indexes) …");
74
+ runOkfEmit([`--repo=${repoFull}`]);
75
+
43
76
  // ── citation gate (in-process) ──
44
77
  // Re-run the checker HERE so the record that rides the push is the record of
45
78
  // THIS push — a stale/hand-stamped citation-check.json can never ship
@@ -70,23 +103,6 @@ export default async function push(args = []) {
70
103
  "authoring agent (atlas/bloom/prism) correct them, then re-push.",
71
104
  );
72
105
 
73
- // ── stamped contract copy rides the push ──
74
- // The canonical contract lives in the CODE repo (.claude/rafa/contract.md);
75
- // the brain repo carries a stamped copy so any reader of the brain repo knows
76
- // exactly which contract version this brain conforms to.
77
- const canonical = join(ROOT, ".claude", "rafa", "contract.md");
78
- if (existsSync(canonical)) {
79
- const stampHeader =
80
- `<!-- STAMPED COPY — canonical: .claude/rafa/contract.md in the code repo.\n` +
81
- ` cli: ${CLI_VERSION} · brain-for: ${codeSha} · stamped: ${new Date().toISOString()} -->\n\n`;
82
- writeFileSync(join(rafaDir, "contract.md"), stampHeader + readFileSync(canonical, "utf8"));
83
- } else {
84
- console.log(
85
- " ! no .claude/rafa/contract.md in this repo — pushing without the stamped contract copy " +
86
- "(run `rafa update` to vendor it)",
87
- );
88
- }
89
-
90
106
  inRafa("git add -A");
91
107
  try {
92
108
  inRafa(`git commit -m "brain: scan" -m "brain-for: ${codeSha}"`);
package/lib/releases.mjs CHANGED
@@ -168,6 +168,41 @@ export const RELEASES = [
168
168
  "never ship). Brain data schema unchanged; no re-scan; adopt with `rafa update` " +
169
169
  "(re-vendors hooks, merges settings + statusline, installs the pre-push boundary).",
170
170
  },
171
+ {
172
+ version: "0.8.0",
173
+ contract: 1,
174
+ plans: 2,
175
+ requires: "update",
176
+ recommends: ["push"],
177
+ summary:
178
+ "THE OKF SURFACE (contract §11): every generated .md self-describes, and a pushed " +
179
+ "brain repo is a conformant Open Knowledge Format v0.1 bundle any foreign agent can " +
180
+ "read without rafa tooling. New @rafinery/okf package (zero-dep: strict parser · " +
181
+ "concept primitives · links · citations · indexes · §9 validator · generator) — the " +
182
+ "gate now imports the parser from it. New `rafa okf` (emit — runs inside push, before " +
183
+ "the gates, so its output is gate-checked) · `rafa okf check` (conformance walk; " +
184
+ "documented exemptions surface as warnings) · `rafa okf new <class> <id>` (mint a " +
185
+ "conformant concept skeleton; the rafa-okf skill is the SOP). Emit stamps ONLY " +
186
+ "derivable values (description from summary · singleton type/title from the class " +
187
+ "registry · timestamp from authored created:/found: → brain-repo git history → the " +
188
+ "emit run; never a guess), renders `# Citations` (sha-pinned GitHub URLs when the " +
189
+ "repo is known) inside okf:citations markers, transpiles resolvable [[wikilinks]] to " +
190
+ "bundle-relative markdown links (the authored form going forward — SOPs updated), and " +
191
+ "generates the index.md tree (root index carries okf_version + repo + codeSha). " +
192
+ "Plan items carry the surface too (type: Plan Epic|Task|Subtask derived from kind; " +
193
+ "per-epic plans/index.md). " +
194
+ "Checker: non-failing LINKS warn lane (dangling link = not-yet-written knowledge, " +
195
+ "OKF §5.3 — prism's worklist; closes the 2026-06-19 [[…]]-lint ratchet) and the " +
196
+ "citation-check.md report now self-describes. index.md/log.md are reserved names " +
197
+ "(skipped by every walker). Agent cards: okf-surface duty on atlas · bloom · prism · " +
198
+ "sage; okf-awareness on compass (bundles are PORTABLE — person-scoped content never " +
199
+ "lands in one); the conductor verb map carries the protocol note. TOTALITY: "
200
+ + "*.theirs.md conflicts fail compile with a typed rule (walkers skip them); "
201
+ + "shipped SOPs (rafa-* skills + conductor) and sage learnings are compile-gated "
202
+ + "local classes beside the agent cards. WIRE UNCHANGED: " +
203
+ "manifest shape + schemaVersion 1 + checker v2 — all additive " +
204
+ "(§8); no re-scan, adopt with `rafa update`, then `rafa push` materializes the bundle.",
205
+ },
171
206
  ];
172
207
 
173
208
  // The release this CLI build ships (last entry).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rafinery/cli",
3
- "version": "0.7.1",
3
+ "version": "0.8.0",
4
4
  "description": "rafa — the AI engineer CLI. Vendors the rafa blueprint into your repo (shadcn-style) and runs the deterministic contract gates.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -15,13 +15,12 @@
15
15
  "engines": {
16
16
  "node": ">=18"
17
17
  },
18
+ "dependencies": {
19
+ "@rafinery/okf": "0.1.0"
20
+ },
18
21
  "publishConfig": {
19
22
  "access": "public"
20
23
  },
21
- "scripts": {
22
- "bundle": "node scripts/bundle-blueprint.mjs",
23
- "prepare": "node scripts/bundle-blueprint.mjs"
24
- },
25
24
  "keywords": [
26
25
  "rafa",
27
26
  "rafinery",
@@ -37,5 +36,9 @@
37
36
  "url": "git+https://github.com/rafinery-ai/rafinery.git",
38
37
  "directory": "packages/cli"
39
38
  },
40
- "homepage": "https://github.com/rafinery-ai/rafinery/tree/main/packages/cli"
41
- }
39
+ "homepage": "https://github.com/rafinery-ai/rafinery/tree/main/packages/cli",
40
+ "scripts": {
41
+ "bundle": "node scripts/bundle-blueprint.mjs",
42
+ "test": "node --test"
43
+ }
44
+ }