@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 +24 -0
- package/blueprint/.claude/agents/sage.md +7 -5
- package/blueprint/.claude/rafa/contract.md +2 -2
- package/blueprint/.claude/skills/rafa-sage/SKILL.md +11 -7
- package/lib/gate/compile.mjs +4 -3
- package/lib/migrations/index.mjs +49 -2
- package/lib/reflex.mjs +38 -2
- package/lib/releases.mjs +30 -0
- package/package.json +1 -1
- package/skills-bundle/skills-manifest.json +1 -1
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
|
|
65
|
-
`.
|
|
66
|
-
`ledger.md` (the index).
|
|
67
|
-
|
|
68
|
-
|
|
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 | `.
|
|
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** —
|
|
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 `.
|
|
105
|
+
## Output — the learnings ledger (to `.rafa/learnings/`)
|
|
106
106
|
|
|
107
|
-
The ledger is a **
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
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):
|
package/lib/gate/compile.mjs
CHANGED
|
@@ -850,11 +850,12 @@ export function runCompile(argv = []) {
|
|
|
850
850
|
return count;
|
|
851
851
|
}
|
|
852
852
|
|
|
853
|
-
// sage's learnings (.
|
|
854
|
-
//
|
|
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, "
|
|
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();
|
package/lib/migrations/index.mjs
CHANGED
|
@@ -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
|
-
|
|
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> [
|
|
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
|
-
|
|
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