@rafinery/cli 0.14.0 → 0.15.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.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,30 @@ The machine-readable version of this lives in [`lib/releases.mjs`](lib/releases.
4
4
  CLI reads it to tell a client, on `rafa update`, exactly what an upgrade requires (nothing,
5
5
  a re-scan, or a `rafa migrate`).
6
6
 
7
+ ## 0.15.1 — fix `rafa reflex --consume` verdict parsing
8
+
9
+ A `rafa reflex --consume <id> --reason session-only` silently recorded `banked`
10
+ instead — the parser only accepted a bare positional verdict and rejected
11
+ anything starting with `-`, then fell through to the `banked` default. That's a
12
+ silent wrong value, exactly what the platform forbids.
13
+
14
+ Now the verdict accepts a flag form (`--verdict=<v>` / `--reason=<v>`, plus the
15
+ bare positional), and an **unrecognized verdict fails loudly** instead of
16
+ defaulting — a session-only correction is recorded session-only, or the consume
17
+ errors. Regression-tested.
18
+
19
+ ## 0.15.0 — learnings are a `.rafa/` sibling
20
+
21
+ sage's learnings move out of committed `.claude/rafa/learnings/` and become a
22
+ sibling of the improvement ledger: they live in **gitignored `.rafa/learnings/`**,
23
+ stored exactly like improvements — local working copy, durable via the brain-repo
24
+ mirror + the platform DB (`agentLearnings`). Each is an OKF `type: Learning`
25
+ concept, gated by the quartet. No learning files clutter `.claude/` anymore.
26
+
27
+ `rafa update` runs a **mechanical migration** that relocates a repo's existing
28
+ committed learnings to `.rafa/learnings/` and untracks the old copy — the files
29
+ move, nothing is dropped. Commit the removal of the old `.claude/rafa/learnings/`.
30
+
7
31
  ## 0.14.0 — continuity, tombstones, clean SOPs, distill removed
8
32
 
9
33
  **Continuity is the product.** Scans and improve passes are refresh-first —
@@ -61,11 +61,13 @@ the learnings-ledger entry schema, and the procedure. Spawned by the **conductor
61
61
  or a boundary offer), context-isolated.
62
62
 
63
63
  ## Output
64
- The **learnings ledger** — a committed, human-reviewed governance artifact at
65
- `.claude/rafa/learnings/` (per the SOP): `learnings/<id>.md` (one proposed card/SOP diff each) +
66
- `ledger.md` (the index). **Never** a Convex table; **never** inside any customer `.rafa/brain/`
67
- (learnings are about OUR agents they never mix with customer knowledge). Every entry cites loop-
68
- event shapes only and passes the scrub step, or it doesn't ship.
64
+ The **learnings ledger** — a sibling of the improvement ledger (owner 2026-07-27) in gitignored
65
+ `.rafa/learnings/` (per the SOP): `learnings/<id>.md` (one proposed card/SOP diff each, an OKF
66
+ `type: Learning` concept) + `ledger.md` (the index). Storage mirrors improvements gitignored
67
+ locally, durable via the brain-repo mirror + the platform DB (`agentLearnings`, written with
68
+ `report_learning`). **Never** inside any customer `.rafa/brain/` (learnings are about OUR agents
69
+ they never mix with customer knowledge). Every entry cites loop-event shapes only and passes the
70
+ scrub step, or it doesn't ship.
69
71
 
70
72
  ## Style
71
73
  Terse, pattern-first, no nagging. Lead with the highest-leverage learning — the one card/SOP change
@@ -58,7 +58,7 @@ doesn't validate, compile fails.
58
58
  | reflex queue | `reflex.jsonl` | **generated** (local state — transport-excluded, NEVER pushed/ingested; transcript pointers are LOCAL, raw transcripts never ship) | `{id, p, t, tp}` per detected correction + append-only `{id, done, verdict, at}` markers (by the UserPromptSubmit sensor; read by `rafa reflex` + the digest) | tool |
59
59
  | agent | `.claude/agents/*.md` (code repo) | **structured** (local gate — NOT in manifest) | §10 | rafa |
60
60
  | skill SOP | `.claude/skills/rafa-*/SKILL.md` + `.claude/commands/rafa.md` (code repo) | **structured** (local gate — NOT in manifest) | frontmatter parses · `name` == skill dir (conductor: semver `version`) · non-empty `description` — a workforce whose procedures don't parse doesn't ship | rafa |
61
- | learning | `.claude/rafa/learnings/*.md` (code repo) | **structured** (local gate — NOT in manifest) | OKF quartet-lite: `id` == stem · non-empty `type`/`title`/`description` (§11 outside the bundle; `learnings/ledger.md` is sage's generated index, skipped) | sage |
61
+ | learning | `.rafa/learnings/*.md` (gitignored `.rafa/` sibling of the improvement ledger; durable via the brain-repo mirror + the platform DB) | **structured** (local gate — NOT in the manifest; stored via `report_learning` → `agentLearnings`) | OKF quartet-lite: `id` == stem · non-empty `type`/`title`/`description` (§11; `learnings/ledger.md` is sage's generated index, skipped) | sage |
62
62
  | conflict copy | `**/*.theirs.md` | **generated** (transient — a pending HUMAN decision) | none — compile FAILS with the typed rule "unresolved checkpoint conflict" while one exists; every walker (emit · checker · validator) skips them | tool |
63
63
 
64
64
  The `agent` type is the one structured type outside `.rafa/`: the shipped agent cards
@@ -618,7 +618,7 @@ state**, marked by `envelope.plane: "state"`:
618
618
  | `list_working_sets` | branch working set (**READ**) | enumerates branches with LIVE candidate rows + counts only (no bodies) — closes the 07-14 sensor gap (`get_working_set` needs a branch arg; nothing listed them); feeds the SessionStart digest pending line |
619
619
  | `report_loop_event` | **loopEvents** — the loop-outcome ledger (sage's evidence) | ONE structured outcome per ruling, shapes only (enum category + per-category enum outcome + `subject` shape ref, secret-screened). Wave 5 trust plane, STRICT (no legacy path): **required actor envelope** `{model, agent, runner ∈ sandbox\|ci\|session}` (mechanical CLI emits use `model:"mechanical"`); **required `verification {method: static\|live, tool?, evidence?}`** on `prism-verdict`/`gate-result`/`review-verdict` — the static-vs-executed distinction is structural, never prose; optional `dedupeKey` (≤120) makes the emit **idempotent** (same `(repo, key)` never lands twice — response says `deduped`) and `tier ∈ light\|standard\|full` records the §7 `validation_tier` the ruling ran at |
620
620
  | `get_loop_events` | loopEvents (**READ**) | newest-first shape read (optional category filter, limit ≤500): category/outcome/subject + actorMeta/verification/dedupeKey/tier + at — no bodies, no code; sage splits miss patterns by verification method, actor, and tier |
621
- | `report_learning` | **agentLearnings** — sage's summary-row mirror | sage ONLY (wave 4.1): mirrors the SUMMARY ROW of one committed ledger entry (`.claude/rafa/learnings/<id>.md` stays the single truth, MR-gated, never self-applied). Asset-free ENFORCED server-side: enum status/leverage/categories, `.claude/`-only diff target, length caps, code-fence + secret screens; upsert by id |
621
+ | `report_learning` | **agentLearnings** — the learnings STORE | sage ONLY: stores one learning (local copy in `.rafa/learnings/<id>.md`, the gitignored ledger sibling; the DB row is the durable truth the platform renders). Asset-free ENFORCED server-side: enum status/leverage/categories, `.claude/`-only diff TARGET (the card/SOP a learning proposes to change), length caps, code-fence + secret screens; upsert by id |
622
622
 
623
623
  State tools work with or without an ingested brain (a working set can exist —
624
624
  and a plan can be pushed — before the first scan) and are exempt from the
@@ -102,16 +102,20 @@ human.
102
102
 
103
103
  ---
104
104
 
105
- ## Output — the learnings ledger (to `.claude/rafa/learnings/`)
105
+ ## Output — the learnings ledger (to `.rafa/learnings/`)
106
106
 
107
- The ledger is a **committed, human-reviewed governance artifact** diffs for MR review. It is
108
- **NOT** a Convex table and **NOT** inside any customer `.rafa/brain/` (asset-free: learnings are
109
- about OUR agents; they never mix with customer knowledge). It lives beside the contract at
110
- `.claude/rafa/learnings/` (governance, versioned with the blueprint, MR-reviewed). The platform's
111
- "agent learnings" card renders a **projection** of it (summary rows via `report_learning`, wave
112
- 4.1, owner-ratified 2026-07-24) a display overlay, never a second truth and never the store:
107
+ The ledger is a **sibling of the improvement ledger** (owner 2026-07-27): it lives in gitignored
108
+ `.rafa/learnings/` a `.rafa/` artifact like `.rafa/improve/`, NOT committed under `.claude/`,
109
+ NOT inside any customer `.rafa/brain/` (asset-free: learnings are about OUR agents; they never mix
110
+ with customer knowledge). Storage mirrors improvements exactly: gitignored locally, durable via
111
+ the brain-repo mirror + the platform DB. The DB (`agentLearnings`) is the store; sage writes each
112
+ entry via `report_learning`, and the platform's "agent learnings" card renders it. The local
113
+ `.rafa/learnings/` files are sage's working copy (regenerated per pass, like the brain):
113
114
 
114
115
  - `learnings/<id>.md` — one file per learning (one proposed card/SOP diff, cited to event shapes).
116
+ Each is an **OKF concept** (`type: Learning`, self-describing like every `.rafa/` file —
117
+ contract §11): stable `id` == filename stem, non-empty `type`/`title`/`description`. `rafa
118
+ compile` gates the quartet from `.rafa/learnings/`.
115
119
  - `ledger.md` — generated index: counts by category / target / status, and the top-leverage few.
116
120
 
117
121
  **Learnings-ledger ENTRY SCHEMA** (defined here; deliberately has no code-content-capable field):
@@ -850,11 +850,12 @@ export function runCompile(argv = []) {
850
850
  return count;
851
851
  }
852
852
 
853
- // sage's learnings (.claude/rafa/learnings/*.md) — the same self-describing
854
- // protocol outside the bundle (§11), mechanically gated: OKF quartet + stable
853
+ // sage's learnings (.rafa/learnings/*.md) — a gitignored `.rafa/` sibling of
854
+ // the improvement ledger (2026-07-27); durable via the brain-repo mirror + the
855
+ // platform DB. Self-describing (§11), mechanically gated: OKF quartet + stable
855
856
  // id. ledger.md there is sage's generated index (skipped like a listing).
856
857
  function compileLearnings() {
857
- const dir = join(ROOT, "..", ".claude", "rafa", "learnings");
858
+ const dir = join(ROOT, "learnings");
858
859
  let count = 0;
859
860
  for (const path of walk(dir)) {
860
861
  const name = path.split("/").pop();
@@ -28,7 +28,8 @@
28
28
  // run: (cwd) => backfillPlanField(cwd, "owner", ownerOf(cwd)),
29
29
  // };
30
30
 
31
- import { existsSync, readFileSync, readdirSync, rmSync, statSync, writeFileSync } from "node:fs";
31
+ import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, statSync, writeFileSync } from "node:fs";
32
+ import { execSync } from "node:child_process";
32
33
  import { join } from "node:path";
33
34
  import { cmpSemver } from "../releases.mjs";
34
35
 
@@ -121,7 +122,53 @@ const plans_v1_to_v2 = {
121
122
  },
122
123
  };
123
124
 
124
- export const MIGRATIONS = [blueprint_split_0_4_0, plans_v1_to_v2];
125
+ // 0.15.0: learnings become a `.rafa/` sibling of the improvement ledger (owner
126
+ // 2026-07-27). They used to be COMMITTED under `.claude/rafa/learnings/` (MR-
127
+ // gated governance); the new model stores them like improvements — gitignored
128
+ // `.rafa/learnings/`, durable via the brain-repo mirror + the platform DB. This
129
+ // relocates a consumer's existing committed learnings and untracks the old copy
130
+ // (git rm --cached; the files themselves MOVE, nothing is dropped).
131
+ const learnings_to_rafa_0_15_0 = {
132
+ id: "learnings-to-rafa-0.15.0",
133
+ kind: "config",
134
+ mode: "mechanical",
135
+ summary:
136
+ "learnings move `.claude/rafa/learnings/` → `.rafa/learnings/` (a gitignored " +
137
+ "sibling of the improvement ledger; durable via the brain repo + the platform DB)",
138
+ appliesTo: (from, to) =>
139
+ cmpSemver(from.cli, "0.15.0") < 0 && cmpSemver(to.version, "0.15.0") >= 0,
140
+ run: (cwd) => {
141
+ const src = join(cwd, ".claude", "rafa", "learnings");
142
+ const dst = join(cwd, ".rafa", "learnings");
143
+ if (!existsSync(src)) return { changed: [], notes: ["no committed learnings — nothing to migrate"] };
144
+ const changed = [];
145
+ mkdirSync(dst, { recursive: true });
146
+ for (const name of readdirSync(src)) {
147
+ const s = join(src, name);
148
+ if (!statSync(s).isFile()) continue;
149
+ const d = join(dst, name);
150
+ if (!existsSync(d)) writeFileSync(d, readFileSync(s, "utf8")); // MOVE the content, never drop it
151
+ }
152
+ // Untrack the old committed copies (best-effort — a non-git checkout still
153
+ // gets the physical move above) and remove the emptied directory.
154
+ try {
155
+ execSync(`git rm -r -q --cached --ignore-unmatch ${JSON.stringify(join(".claude", "rafa", "learnings"))}`, { cwd, stdio: "ignore" });
156
+ } catch {
157
+ /* not a git repo / already untracked — the filesystem move is what matters */
158
+ }
159
+ rmSync(src, { recursive: true, force: true });
160
+ changed.push(".claude/rafa/learnings/ → .rafa/learnings/");
161
+ return {
162
+ changed,
163
+ notes: [
164
+ "learnings are now a gitignored `.rafa/` sibling of the improvement ledger — " +
165
+ "durable via the brain-repo mirror + the platform DB; commit the removal of the old `.claude/rafa/learnings/`.",
166
+ ],
167
+ };
168
+ },
169
+ };
170
+
171
+ export const MIGRATIONS = [blueprint_split_0_4_0, plans_v1_to_v2, learnings_to_rafa_0_15_0];
125
172
 
126
173
  // The migrations that apply to a given move (stamped `from` → shipped `to`), in order.
127
174
  export function pendingMigrations(from, to) {
package/lib/reflex.mjs CHANGED
@@ -3,7 +3,10 @@
3
3
  //
4
4
  // rafa reflex list unprocessed corrections
5
5
  // rafa reflex --json machine shape (bootstrap digest / spawn prompts)
6
- // rafa reflex --consume <id> [reason] mark one processed AFTER it was banked
6
+ // rafa reflex --consume <id> [verdict] mark one processed. verdict
7
+ // banked | session-only | refuted — a bare word, or --verdict=<v> /
8
+ // --reason=<v>. Omitted ⇒ banked (the SOP default). An UNRECOGNIZED
9
+ // verdict fails loudly — never a silent wrong outcome (no assumed values).
7
10
  // through the gates, or judged session-only
8
11
  //
9
12
  // Consume APPENDS a done-marker line ({id, done:true, verdict, at}) — the queue
@@ -50,7 +53,40 @@ export default async function reflex(args = []) {
50
53
  console.error(`✗ no unprocessed correction with id ${id} (see \`rafa reflex\`)`);
51
54
  process.exit(1);
52
55
  }
53
- const verdict = args[ci + 2] && !args[ci + 2].startsWith("-") ? args[ci + 2] : "banked";
56
+ // The verdict from a bare positional (`--consume <id> session-only`) OR a
57
+ // flag (`--verdict=X` · `--verdict X` · `--reason=X` · `--reason X`; the
58
+ // human reaches for `--reason`, so accept it). NO ASSUMED VALUES: default to
59
+ // `banked` ONLY when no verdict was supplied at all; an unrecognized verdict
60
+ // — or a flag whose value is unrecognized — fails LOUDLY rather than
61
+ // silently recording the wrong outcome (the 2026-07-27 mislabel: a
62
+ // `--reason session-only` fell through to a false `banked`).
63
+ const VERDICTS = ["banked", "session-only", "refuted"];
64
+ const rest = args.slice(ci + 2);
65
+ let verdict = null;
66
+ let supplied = false;
67
+ for (let i = 0; i < rest.length; i++) {
68
+ const a = rest[i];
69
+ const m = a.match(/^--(?:verdict|reason)(?:=(.+))?$/);
70
+ if (m) {
71
+ supplied = true;
72
+ verdict = m[1] ?? rest[++i] ?? null; // `=value` or the next token
73
+ } else if (!a.startsWith("-")) {
74
+ supplied = true;
75
+ verdict = a; // bare positional verdict
76
+ } else {
77
+ continue; // an unrelated flag (e.g. a future --json) — skip, don't consume it as a verdict
78
+ }
79
+ break;
80
+ }
81
+ if (!supplied) verdict = "banked"; // bare `--consume <id>` — the SOP's default
82
+ else if (!VERDICTS.includes(verdict)) {
83
+ console.error(
84
+ `✗ unrecognized verdict ${verdict === null ? "(none given after the flag)" : `"${verdict}"`} — ` +
85
+ `expected one of: ${VERDICTS.join(" | ")}.\n` +
86
+ ` usage: rafa reflex --consume <id> [${VERDICTS.join("|")}] (bare word, or --verdict=<v> / --reason=<v>)`,
87
+ );
88
+ process.exit(1);
89
+ }
54
90
  appendFileSync(
55
91
  file,
56
92
  JSON.stringify({ id, done: true, verdict, at: new Date().toISOString() }) + "\n",
package/lib/releases.mjs CHANGED
@@ -485,6 +485,36 @@ export const RELEASES = [
485
485
  "branch is honored across checkpoint, guard, branch-merge audit, and " +
486
486
  "reconciliation; no path hardcodes main.",
487
487
  },
488
+ {
489
+ version: "0.15.0",
490
+ contract: 1,
491
+ plans: 2,
492
+ requires: "update",
493
+ summary:
494
+ "LEARNINGS ARE A `.rafa/` SIBLING (owner 2026-07-27): sage's learnings " +
495
+ "move `.claude/rafa/learnings/` → `.rafa/learnings/` — stored like the " +
496
+ "improvement ledger (gitignored locally, durable via the brain-repo " +
497
+ "mirror + the platform DB `agentLearnings`), each an OKF `type: Learning` " +
498
+ "concept gated by the quartet. `rafa update` runs a MECHANICAL migration " +
499
+ "that relocates a repo's existing committed learnings and untracks the " +
500
+ "old copy (files MOVE, nothing dropped). The committed-`.claude/`-" +
501
+ "governance framing is retired; the DB is the store, `.rafa/learnings/` " +
502
+ "the local working copy — no learning files clutter `.claude/`.",
503
+ },
504
+ {
505
+ version: "0.15.1",
506
+ contract: 1,
507
+ plans: 2,
508
+ requires: "update",
509
+ summary:
510
+ "FIX `rafa reflex --consume`: the verdict now accepts a flag form " +
511
+ "(`--verdict=<v>` / `--reason=<v>`, since the human reaches for " +
512
+ "`--reason`) as well as the bare positional, and an UNRECOGNIZED verdict " +
513
+ "fails loudly instead of silently defaulting to `banked` (the 2026-07-27 " +
514
+ "mislabel: `--reason session-only` fell through to a false `banked` in " +
515
+ "reflex.jsonl + the loop event). No assumed values — a session-only " +
516
+ "correction is recorded session-only, or the consume errors.",
517
+ },
488
518
  ];
489
519
 
490
520
  // 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.14.0",
3
+ "version": "0.15.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": {
@@ -1,5 +1,5 @@
1
1
  {
2
- "generatedAt": "2026-07-26T21:22:28.352Z",
2
+ "generatedAt": "2026-07-26T22:59:22.774Z",
3
3
  "skills": {
4
4
  "tdd": {
5
5
  "version": "1.0.0",