@rafinery/cli 0.6.0 → 0.7.1

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.
@@ -119,6 +119,41 @@ export function runCompile(argv = []) {
119
119
  // Paths in the manifest are relative to the brain-repo root (`.rafa/` IS the root),
120
120
  // so the platform can fetch a prose body straight from the brain repo.
121
121
  const rel = (p) => (p.startsWith(ROOT + "/") ? p.slice(ROOT.length + 1) : p);
122
+ // The code repo root — cite `file`s are relative to it (same anchor the checker
123
+ // resolves against). `.rafa/` sits inside the code repo, so `..` reaches the root.
124
+ const repoRoot = join(ROOT, "..");
125
+
126
+ // ── deterministic size estimation (recall-savings, additive optional) ────────
127
+ // Feeds the platform's recall-savings estimator: a note's token cost and each
128
+ // cited file's token cost, so it can price "read the note" vs "re-read the code".
129
+ // Estimate = ceil(chars / 4) — the ~4-bytes-per-token rule of thumb for English
130
+ // source/prose. DETERMINISTIC: pure function of the file bytes, no LLM, no network.
131
+ // These are ADDITIVE OPTIONAL manifest fields (contract §1) — a note without them
132
+ // still compiles and ingest tolerates their absence; a target we cannot read is
133
+ // OMITTED, never defaulted to a guessed 0 (contract §0 / compile's own law above).
134
+ const estimateTokens = (s) => Math.ceil(s.length / 4);
135
+ // The prose body = everything after the closing `---` fence (never parsed, only sized).
136
+ function noteBody(text) {
137
+ if (!text.startsWith("---")) return text;
138
+ const end = text.indexOf("\n---", text.indexOf("\n"));
139
+ if (end === -1) return "";
140
+ const nl = text.indexOf("\n", end + 1); // newline after the closing `---` line
141
+ return nl === -1 ? "" : text.slice(nl + 1);
142
+ }
143
+ // Cited-file token estimate, cached per path (a file is cited by many notes).
144
+ // null = unreadable at compile time → the caller OMITS the field (honest absence).
145
+ const citeTokenCache = new Map();
146
+ function citeTargetTokens(file) {
147
+ if (citeTokenCache.has(file)) return citeTokenCache.get(file);
148
+ let tokens = null;
149
+ try {
150
+ tokens = estimateTokens(readFileSync(join(repoRoot, file), "utf8"));
151
+ } catch {
152
+ tokens = null; // missing/unreadable → omit, never guess
153
+ }
154
+ citeTokenCache.set(file, tokens);
155
+ return tokens;
156
+ }
122
157
 
123
158
  const errors = [];
124
159
  const fail = (path, field, rule) => errors.push({ path, field, rule });
@@ -184,7 +219,8 @@ export function runCompile(argv = []) {
184
219
  const notes = [];
185
220
  for (const path of walk(dir)) {
186
221
  const name = path.split("/").pop();
187
- const { data, error } = parseFrontmatter(read(path));
222
+ const raw = read(path);
223
+ const { data, error } = parseFrontmatter(raw);
188
224
  if (error) {
189
225
  fail(path, "frontmatter", error);
190
226
  continue;
@@ -194,12 +230,27 @@ export function runCompile(argv = []) {
194
230
  // `anchor:` / `absent:` are checker-side declarations (verify-citations gates
195
231
  // B2/B3 on them) — optional here, but if present they must be non-empty.
196
232
  for (const key of ["anchor", "absent"])
197
- if (key in data && (typeof data[key] !== "string" || data[key].trim() === ""))
233
+ if (
234
+ key in data &&
235
+ (typeof data[key] !== "string" || data[key].trim() === "")
236
+ )
198
237
  fail(path, key, "optional · non-empty token (or `none`)");
238
+ // Size stamps (additive optional): body cost + per-cite target-file cost. A
239
+ // target we can't read → the cite simply carries no `targetTokens` (omitted).
240
+ const cites = parseCites(data.cites, path);
241
+ for (const c of cites) {
242
+ const t = citeTargetTokens(c.file);
243
+ if (t !== null) c.targetTokens = t;
244
+ }
199
245
  notes.push({
200
246
  id,
201
247
  kind,
202
- type: reqEnum(data, "type", ["contract", "convention", "flow", "how-to"], path),
248
+ type: reqEnum(
249
+ data,
250
+ "type",
251
+ ["contract", "convention", "flow", "how-to"],
252
+ path,
253
+ ),
203
254
  domain: reqStr(data, "domain", path),
204
255
  title: reqStr(data, "title", path),
205
256
  summary: reqStr(data, "summary", path),
@@ -207,7 +258,8 @@ export function runCompile(argv = []) {
207
258
  ...(data.failure === "silent" || data.failure === "loud"
208
259
  ? { failure: data.failure }
209
260
  : {}),
210
- cites: parseCites(data.cites, path),
261
+ bodyTokens: estimateTokens(noteBody(raw)),
262
+ cites,
211
263
  path: rel(path),
212
264
  });
213
265
  }
@@ -231,17 +283,33 @@ export function runCompile(argv = []) {
231
283
  !isEnum(lev?.impact, ["low", "medium", "high"]) ||
232
284
  !isEnum(lev?.effort, ["low", "medium", "high"])
233
285
  )
234
- fail(path, "leverage", "required · { impact, effort } ∈ low|medium|high");
286
+ fail(
287
+ path,
288
+ "leverage",
289
+ "required · { impact, effort } ∈ low|medium|high",
290
+ );
235
291
  out.push({
236
292
  id,
237
293
  priority: reqEnum(data, "priority", ["P0", "P1", "P2", "P3"], path),
238
294
  lens: reqEnum(
239
295
  data,
240
296
  "lens",
241
- ["security", "correctness", "performance", "architecture", "product", "ops"],
297
+ [
298
+ "security",
299
+ "correctness",
300
+ "performance",
301
+ "architecture",
302
+ "product",
303
+ "ops",
304
+ ],
305
+ path,
306
+ ),
307
+ status: reqEnum(
308
+ data,
309
+ "status",
310
+ ["open", "backlog", "fixed", "wontfix"],
242
311
  path,
243
312
  ),
244
- status: reqEnum(data, "status", ["open", "backlog", "fixed", "wontfix"], path),
245
313
  title: reqStr(data, "title", path),
246
314
  summary: reqStr(data, "summary", path),
247
315
  fix: reqStr(data, "fix", path),
@@ -270,7 +338,10 @@ export function runCompile(argv = []) {
270
338
  checkVersion(data, path);
271
339
  const g = data.gates,
272
340
  c = data.counts;
273
- if (!isEnum(g?.fidelity, ["pass", "fail"]) || !isEnum(g?.coverage, ["pass", "fail"]))
341
+ if (
342
+ !isEnum(g?.fidelity, ["pass", "fail"]) ||
343
+ !isEnum(g?.coverage, ["pass", "fail"])
344
+ )
274
345
  fail(path, "gates", "required · { fidelity, coverage } ∈ pass|fail");
275
346
  if (
276
347
  typeof c?.blockers !== "number" ||
@@ -278,11 +349,15 @@ export function runCompile(argv = []) {
278
349
  typeof c?.minors !== "number"
279
350
  )
280
351
  fail(path, "counts", "required · { blockers, majors, minors } ints");
281
- if (typeof data.score !== "number") fail(path, "score", "required · 0–100 int");
352
+ if (typeof data.score !== "number")
353
+ fail(path, "score", "required · 0–100 int");
282
354
  return {
283
355
  verdict: reqEnum(data, "verdict", ["PASS", "ITERATE"], path),
284
356
  score: typeof data.score === "number" ? data.score : 0,
285
- gates: { fidelity: g?.fidelity ?? "fail", coverage: g?.coverage ?? "fail" },
357
+ gates: {
358
+ fidelity: g?.fidelity ?? "fail",
359
+ coverage: g?.coverage ?? "fail",
360
+ },
286
361
  counts: {
287
362
  blockers: c?.blockers ?? 0,
288
363
  majors: c?.majors ?? 0,
@@ -342,11 +417,19 @@ export function runCompile(argv = []) {
342
417
  // here so a malformed declaration fails loudly at the gate, never silently.
343
418
  if ("inventory" in data) {
344
419
  if (!Array.isArray(data.inventory) || data.inventory.length === 0) {
345
- fail(path, "inventory", "optional · block list of `<name> :: <glob> :: <count>`");
420
+ fail(
421
+ path,
422
+ "inventory",
423
+ "optional · block list of `<name> :: <glob> :: <count>`",
424
+ );
346
425
  } else {
347
426
  for (const entry of data.inventory)
348
427
  if (!/^.+?\s*::\s*.+?\s*::\s*\d+$/.test(String(entry)))
349
- fail(path, "inventory", `malformed entry: ${JSON.stringify(entry)} · must be \`<name> :: <glob> :: <count>\``);
428
+ fail(
429
+ path,
430
+ "inventory",
431
+ `malformed entry: ${JSON.stringify(entry)} · must be \`<name> :: <glob> :: <count>\``,
432
+ );
350
433
  }
351
434
  }
352
435
  return { domains };
@@ -366,8 +449,16 @@ export function runCompile(argv = []) {
366
449
  fail(path, "json", `unparseable: ${e instanceof Error ? e.message : e}`);
367
450
  return null;
368
451
  }
369
- if (typeof rec.checkerVersion !== "number" || typeof rec.pass !== "boolean" || typeof rec.at !== "string") {
370
- fail(path, "shape", "required · { checkerVersion: int, pass: bool, at: ISO string }");
452
+ if (
453
+ typeof rec.checkerVersion !== "number" ||
454
+ typeof rec.pass !== "boolean" ||
455
+ typeof rec.at !== "string"
456
+ ) {
457
+ fail(
458
+ path,
459
+ "shape",
460
+ "required · { checkerVersion: int, pass: bool, at: ISO string }",
461
+ );
371
462
  return null;
372
463
  }
373
464
  return { checkerVersion: rec.checkerVersion, pass: rec.pass, at: rec.at };
@@ -378,7 +469,13 @@ export function runCompile(argv = []) {
378
469
  // (unresolved blocked_by / blocked_reason), never stored. VALIDATED here
379
470
  // (files are the authoring surface — determinism holds) but plans do NOT ride
380
471
  // the manifest: they travel through the push-on-approval plans channel.
381
- const PLAN_STATUSES = ["todo", "in-progress", "done", "superseded", "abandoned"];
472
+ const PLAN_STATUSES = [
473
+ "todo",
474
+ "in-progress",
475
+ "done",
476
+ "superseded",
477
+ "abandoned",
478
+ ];
382
479
  const PLAN_KINDS = ["epic", "task", "subtask"];
383
480
  function compilePlans() {
384
481
  const plans = [];
@@ -393,46 +490,95 @@ export function runCompile(argv = []) {
393
490
  checkVersion(data, path);
394
491
  const id = checkId(data, path, name);
395
492
  if (seen.has(id))
396
- fail(path, "id", `duplicate plan id "${id}" (also ${seen.get(id)}) · ids are globally unique across plans/**`);
493
+ fail(
494
+ path,
495
+ "id",
496
+ `duplicate plan id "${id}" (also ${seen.get(id)}) · ids are globally unique across plans/**`,
497
+ );
397
498
  seen.set(id, path);
398
499
  if ("progress" in data)
399
- fail(path, "progress", "never stored · progress = done leaves / total leaves, derived");
500
+ fail(
501
+ path,
502
+ "progress",
503
+ "never stored · progress = done leaves / total leaves, derived",
504
+ );
400
505
  if (data.kind === "parent" || data.kind === "child")
401
- fail(path, "kind", `plans v1 shape ("${data.kind}") — run \`rafa migrate\` (v2: epic|task|subtask)`);
506
+ fail(
507
+ path,
508
+ "kind",
509
+ `plans v1 shape ("${data.kind}") — run \`rafa migrate\` (v2: epic|task|subtask)`,
510
+ );
402
511
  const kind = reqEnum(data, "kind", PLAN_KINDS, path);
403
512
  const plan = reqStr(data, "plan", path);
404
513
  const status = data.status;
405
514
  if (status === "blocked")
406
- fail(path, "status", "removed in v2 — blocked is DERIVED from blocked_by/blocked_reason (run `rafa migrate`)");
515
+ fail(
516
+ path,
517
+ "status",
518
+ "removed in v2 — blocked is DERIVED from blocked_by/blocked_reason (run `rafa migrate`)",
519
+ );
407
520
  if (kind === "epic") {
408
521
  if (data.parent !== null && data.parent !== "null")
409
522
  fail(path, "parent", "must be null for kind: epic (the root)");
410
- if (plan !== id) fail(path, "plan", `must equal id "${id}" for kind: epic`);
523
+ if (plan !== id)
524
+ fail(path, "plan", `must equal id "${id}" for kind: epic`);
411
525
  if (status !== undefined && !isEnum(status, PLAN_STATUSES))
412
526
  fail(path, "status", `optional (epic) · ${PLAN_STATUSES.join("|")}`);
413
527
  } else {
414
528
  if (!isEnum(status, PLAN_STATUSES))
415
- fail(path, "status", `required (${kind}) · ${PLAN_STATUSES.join("|")}`);
529
+ fail(
530
+ path,
531
+ "status",
532
+ `required (${kind}) · ${PLAN_STATUSES.join("|")}`,
533
+ );
416
534
  if (typeof data.parent !== "string" || data.parent === "")
417
535
  fail(path, "parent", `required (${kind}) · the parent item id`);
418
536
  if (data.domains !== undefined)
419
- fail(path, "domains", "epic only · the blast radius belongs to the plan, not an item");
537
+ fail(
538
+ path,
539
+ "domains",
540
+ "epic only · the blast radius belongs to the plan, not an item",
541
+ );
420
542
  }
421
543
  if (data.domains !== undefined && !Array.isArray(data.domains))
422
544
  fail(path, "domains", "optional · flow list of domain strings");
423
545
  if (data.blocked_by !== undefined && !Array.isArray(data.blocked_by))
424
- fail(path, "blocked_by", "optional · flow list of item ids this item waits on");
546
+ fail(
547
+ path,
548
+ "blocked_by",
549
+ "optional · flow list of item ids this item waits on",
550
+ );
425
551
  if (
426
552
  data.priority !== undefined &&
427
- !(Number.isInteger(data.priority) && data.priority >= 0 && data.priority <= 4)
553
+ !(
554
+ Number.isInteger(data.priority) &&
555
+ data.priority >= 0 &&
556
+ data.priority <= 4
557
+ )
558
+ )
559
+ fail(
560
+ path,
561
+ "priority",
562
+ "optional · int 0–4 (0 none · 1 urgent · 2 high · 3 medium · 4 low)",
563
+ );
564
+ if (
565
+ data.estimate !== undefined &&
566
+ !(Number.isInteger(data.estimate) && data.estimate >= 0)
428
567
  )
429
- fail(path, "priority", "optional · int 0–4 (0 none · 1 urgent · 2 high · 3 medium · 4 low)");
430
- if (data.estimate !== undefined && !(Number.isInteger(data.estimate) && data.estimate >= 0))
431
568
  fail(path, "estimate", "optional · non-negative int (points)");
432
569
  if (data.external !== undefined) {
433
570
  const ex = data.external;
434
- if (typeof ex !== "object" || Array.isArray(ex) || typeof ex.provider !== "string" || typeof ex.key !== "string")
435
- fail(path, "external", "optional · { provider, key, url? } flow map — tracker identity (read-only to sessions)");
571
+ if (
572
+ typeof ex !== "object" ||
573
+ Array.isArray(ex) ||
574
+ typeof ex.provider !== "string" ||
575
+ typeof ex.key !== "string"
576
+ )
577
+ fail(
578
+ path,
579
+ "external",
580
+ "optional · { provider, key, url? } flow map — tracker identity (read-only to sessions)",
581
+ );
436
582
  }
437
583
  plans.push({
438
584
  id,
@@ -450,8 +596,11 @@ export function runCompile(argv = []) {
450
596
  ...(typeof data.assignee === "string" && data.assignee !== ""
451
597
  ? { assignee: data.assignee }
452
598
  : {}),
453
- ...(Array.isArray(data.blocked_by) ? { blockedBy: data.blocked_by.map(String) } : {}),
454
- ...(typeof data.blocked_reason === "string" && data.blocked_reason !== ""
599
+ ...(Array.isArray(data.blocked_by)
600
+ ? { blockedBy: data.blocked_by.map(String) }
601
+ : {}),
602
+ ...(typeof data.blocked_reason === "string" &&
603
+ data.blocked_reason !== ""
455
604
  ? { blockedReason: data.blocked_reason }
456
605
  : {}),
457
606
  ...(Number.isInteger(data.priority) ? { priority: data.priority } : {}),
@@ -465,7 +614,9 @@ export function runCompile(argv = []) {
465
614
  ...(Array.isArray(data.domains) && kind === "epic"
466
615
  ? { domains: data.domains.map(String) }
467
616
  : {}),
468
- ...(typeof data.external === "object" && !Array.isArray(data.external) && data.external !== null
617
+ ...(typeof data.external === "object" &&
618
+ !Array.isArray(data.external) &&
619
+ data.external !== null
469
620
  ? { external: data.external }
470
621
  : {}),
471
622
  path: rel(path),
@@ -477,24 +628,45 @@ export function runCompile(argv = []) {
477
628
  for (const p of plans) if (p.kind === "epic") roots.set(p.id, p);
478
629
  for (const p of plans) {
479
630
  if (!roots.has(p.plan))
480
- fail(p.path, "plan", `no kind:epic "${p.plan}" in this compile (dangling item)`);
631
+ fail(
632
+ p.path,
633
+ "plan",
634
+ `no kind:epic "${p.plan}" in this compile (dangling item)`,
635
+ );
481
636
  if (p.kind === "task") {
482
637
  const parent = byId.get(p.parent);
483
638
  if (!parent || parent.kind !== "epic" || parent.id !== p.plan)
484
- fail(p.path, "parent", `a task's parent must be its epic ("${p.plan}")`);
639
+ fail(
640
+ p.path,
641
+ "parent",
642
+ `a task's parent must be its epic ("${p.plan}")`,
643
+ );
485
644
  }
486
645
  if (p.kind === "subtask") {
487
646
  const parent = byId.get(p.parent);
488
647
  if (!parent || parent.kind !== "task")
489
- fail(p.path, "parent", "a subtask's parent must be a task in the same plan");
648
+ fail(
649
+ p.path,
650
+ "parent",
651
+ "a subtask's parent must be a task in the same plan",
652
+ );
490
653
  else if (parent.plan !== p.plan)
491
- fail(p.path, "parent", `parent task belongs to plan "${parent.plan}", not "${p.plan}"`);
654
+ fail(
655
+ p.path,
656
+ "parent",
657
+ `parent task belongs to plan "${parent.plan}", not "${p.plan}"`,
658
+ );
492
659
  }
493
660
  for (const b of p.blockedBy ?? []) {
494
661
  const target = byId.get(b);
495
662
  if (!target || target.plan !== p.plan)
496
- fail(p.path, "blocked_by", `"${b}" does not resolve to an item in plan "${p.plan}"`);
497
- if (b === p.id) fail(p.path, "blocked_by", "an item cannot block itself");
663
+ fail(
664
+ p.path,
665
+ "blocked_by",
666
+ `"${b}" does not resolve to an item in plan "${p.plan}"`,
667
+ );
668
+ if (b === p.id)
669
+ fail(p.path, "blocked_by", "an item cannot block itself");
498
670
  }
499
671
  }
500
672
  // blocked_by acyclicity (per plan; DFS with colors).
@@ -512,7 +684,11 @@ export function runCompile(argv = []) {
512
684
  };
513
685
  for (const p of plans)
514
686
  if (!color.has(p.id) && cyc(p))
515
- fail(p.path, "blocked_by", "dependency cycle — blocked_by must be acyclic");
687
+ fail(
688
+ p.path,
689
+ "blocked_by",
690
+ "dependency cycle — blocked_by must be acyclic",
691
+ );
516
692
  return plans;
517
693
  }
518
694
 
@@ -525,13 +701,21 @@ export function runCompile(argv = []) {
525
701
  const first = read(path).split("\n")[0].trim();
526
702
  const m = first.match(/^#\s+(.+)$/);
527
703
  if (!m) {
528
- fail(path, "pointer", 'first line must be "# <plan-id>" or "# No active plan"');
704
+ fail(
705
+ path,
706
+ "pointer",
707
+ 'first line must be "# <plan-id>" or "# No active plan"',
708
+ );
529
709
  return null;
530
710
  }
531
711
  const val = m[1].trim();
532
712
  if (/^no active plan$/i.test(val)) return null;
533
713
  if (!plans.some((p) => p.kind === "epic" && p.id === val)) {
534
- fail(path, "pointer", `active plan "${val}" is not a kind:epic plan in this compile`);
714
+ fail(
715
+ path,
716
+ "pointer",
717
+ `active plan "${val}" is not a kind:epic plan in this compile`,
718
+ );
535
719
  return null;
536
720
  }
537
721
  return val;
@@ -555,7 +739,8 @@ export function runCompile(argv = []) {
555
739
  }
556
740
  count++;
557
741
  const stem = name.replace(/\.md$/, "");
558
- if (data.name !== stem) fail(path, "name", `must equal filename stem "${stem}"`);
742
+ if (data.name !== stem)
743
+ fail(path, "name", `must equal filename stem "${stem}"`);
559
744
  if (!/^\d+\.\d+\.\d+$/.test(String(data.version ?? "")))
560
745
  fail(path, "version", "required · semver MAJOR.MINOR.PATCH");
561
746
  reqStr(data, "model", path);
@@ -575,12 +760,20 @@ export function runCompile(argv = []) {
575
760
  for (const d of duties) {
576
761
  const m = String(d).match(DUTY_RE);
577
762
  if (!m) {
578
- fail(path, "duties", `malformed duty (want "<duty> :: <sop> :: <bar>"): ${JSON.stringify(d)}`);
763
+ fail(
764
+ path,
765
+ "duties",
766
+ `malformed duty (want "<duty> :: <sop> :: <bar>"): ${JSON.stringify(d)}`,
767
+ );
579
768
  continue;
580
769
  }
581
770
  const sop = m[2];
582
771
  if (!existsSync(join(repoRoot, sop)) && !existsSync(join(ROOT, sop)))
583
- fail(path, "duties", `sop does not resolve: ${sop} (a duty without a procedure is a claim)`);
772
+ fail(
773
+ path,
774
+ "duties",
775
+ `sop does not resolve: ${sop} (a duty without a procedure is a claim)`,
776
+ );
584
777
  }
585
778
  }
586
779
  return count;
@@ -607,7 +800,9 @@ export function runCompile(argv = []) {
607
800
  ...compileNotes("rule", join(ROOT, "brain", "rules")),
608
801
  ...compileNotes("playbook", join(ROOT, "brain", "playbooks")),
609
802
  ];
610
- const improvements = compileImprovements(join(ROOT, "improve", "improvements"));
803
+ const improvements = compileImprovements(
804
+ join(ROOT, "improve", "improvements"),
805
+ );
611
806
  const health = compileHealth();
612
807
  const ledger = compileLedger();
613
808
  const coverage = compileCoverage();
@@ -622,16 +817,27 @@ export function runCompile(argv = []) {
622
817
  const bp = { P0: 0, P1: 0, P2: 0, P3: 0 };
623
818
  for (const i of open) bp[i.priority]++;
624
819
  if (ledger.open !== open.length)
625
- fail("improve/ledger.md", "open", `says ${ledger.open}, rows have ${open.length}`);
820
+ fail(
821
+ "improve/ledger.md",
822
+ "open",
823
+ `says ${ledger.open}, rows have ${open.length}`,
824
+ );
626
825
  for (const k of ["P0", "P1", "P2", "P3"])
627
826
  if (ledger.byPriority[k] !== bp[k])
628
- fail("improve/ledger.md", `by_priority.${k}`, `says ${ledger.byPriority[k]}, rows have ${bp[k]}`);
827
+ fail(
828
+ "improve/ledger.md",
829
+ `by_priority.${k}`,
830
+ `says ${ledger.byPriority[k]}, rows have ${bp[k]}`,
831
+ );
629
832
  }
630
833
 
631
834
  if (errors.length) {
632
835
  console.error(`✗ rafa compile: ${errors.length} contract violation(s)\n`);
633
- for (const e of errors) console.error(` ${e.path} · ${e.field} · ${e.rule}`);
634
- console.error(`\nFix these files to match the contract (.claude/rafa/contract.md), then re-run compile.`);
836
+ for (const e of errors)
837
+ console.error(` ${e.path} · ${e.field} · ${e.rule}`);
838
+ console.error(
839
+ `\nFix these files to match the contract (.claude/rafa/contract.md), then re-run compile.`,
840
+ );
635
841
  return 1;
636
842
  }
637
843
 
@@ -650,7 +856,10 @@ export function runCompile(argv = []) {
650
856
  notes,
651
857
  improvements,
652
858
  };
653
- writeFileSync(join(ROOT, "manifest.json"), JSON.stringify(manifest, null, 2) + "\n");
859
+ writeFileSync(
860
+ join(ROOT, "manifest.json"),
861
+ JSON.stringify(manifest, null, 2) + "\n",
862
+ );
654
863
  console.log(
655
864
  `✓ rafa compile: ${notes.length} notes · ${improvements.length} improvements · ` +
656
865
  `${plans.length} plans validated${activePlanId ? ` (active: ${activePlanId})` : ""} (plans travel via the plans channel, not the manifest) · ` +
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rafinery/cli",
3
- "version": "0.6.0",
3
+ "version": "0.7.1",
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": {