@rafinery/cli 0.8.14 → 0.8.16
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 +23 -0
- package/bin/rafa.mjs +6 -4
- package/blueprint/.claude/agents/sage.md +2 -1
- package/blueprint/.claude/rafa/hooks/brain-commit.mjs +58 -16
- package/blueprint/.claude/skills/rafa-review/SKILL.md +77 -14
- package/blueprint/.claude/skills/rafa-sage/SKILL.md +13 -2
- package/lib/checkpoint.mjs +64 -0
- package/lib/releases.mjs +53 -14
- package/lib/review.mjs +105 -21
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,29 @@ 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.8.16 — the session tail never strands (both planes, one beat)
|
|
8
|
+
|
|
9
|
+
Live-catch (2026-07-24, research-canvas): brain notes authored AFTER the last code
|
|
10
|
+
commit sat uncommitted in `.rafa` while `rafa checkpoint` reported clean — the
|
|
11
|
+
capture plane (platform working set) was synced, the git plane (brain mirror
|
|
12
|
+
branch) was dirty. The hooks only fire on code commit/push, so a session tail
|
|
13
|
+
(verify flips · late-authored notes · regenerated manifest) had nothing to ride.
|
|
14
|
+
|
|
15
|
+
- `rafa checkpoint` now owns BOTH planes: after the working-set sync (including
|
|
16
|
+
the "clean" path) it fires the vendored `brain-commit.mjs` worker whenever the
|
|
17
|
+
brain tree is dirty — the tail commits and pushes at the same beat that
|
|
18
|
+
creates it, and "clean" finally means capture AND mirror.
|
|
19
|
+
- The worker is tail-aware: when HEAD already has its 1-1 mirror, the extra run
|
|
20
|
+
commits with a distinct `brain(<branch>): session tail · N file(s)` subject on
|
|
21
|
+
the SAME `code-commit` trailer (join intact, log legible) — and an empty tail
|
|
22
|
+
is a no-op, never an `--allow-empty` duplicate.
|
|
23
|
+
- `review.json` (the review gate's scored-ranges artifact) joins the local-state
|
|
24
|
+
exclusion list — machine state like `distill-verdicts.json`, never knowledge.
|
|
25
|
+
- Fixed: the `RELEASES` ledger tail was out of ascending order (0.8.13/0.8.14
|
|
26
|
+
after 0.8.15), so `CURRENT` reported 0.8.13 from a 0.8.15 build.
|
|
27
|
+
|
|
28
|
+
Adopt with `rafa update` (re-vendors the hook worker).
|
|
29
|
+
|
|
7
30
|
## 0.8.2 — MCP scope: derived from the key, never guessed
|
|
8
31
|
|
|
9
32
|
First real client MCP call surfaced it: agents GUESSED the `repo` arg (folder name)
|
package/bin/rafa.mjs
CHANGED
|
@@ -135,10 +135,12 @@ Commands:
|
|
|
135
135
|
exercises the scaffold from fixture counts; --help documents the
|
|
136
136
|
agent-driven run procedure. Measured — never the estimated baseline.
|
|
137
137
|
review Brain-grounded review pre-pass (deterministic, zero LLM): the
|
|
138
|
-
diff's changed lines ∩ the brain's cite graph → the
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
138
|
+
diff's changed lines ∩ the brain's cite graph → everything the
|
|
139
|
+
change touches — rules AND playbooks (flow-still-true), open
|
|
140
|
+
improvements, stale-cite risk flags, open knowledge gaps,
|
|
141
|
+
related plans (.rafa/review.json + summary). The rafa-review
|
|
142
|
+
SOP's judge rules on precisely that list — generic "review
|
|
143
|
+
this PR" never happens. Advisory; never blocks.
|
|
142
144
|
migrate Bring this repo's structured files (plans, config) up to the schema
|
|
143
145
|
the installed CLI ships. Idempotent; review the diff after.
|
|
144
146
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: sage
|
|
3
|
-
version: 0.
|
|
3
|
+
version: 0.3.0
|
|
4
4
|
model: opus # a wrong learning re-shapes an agent for every repo — best model, never cheap
|
|
5
5
|
groundTruth: sessions-over-time
|
|
6
6
|
description: >-
|
|
@@ -19,6 +19,7 @@ duties:
|
|
|
19
19
|
- "scrub :: .claude/skills/rafa-sage/SKILL.md :: every entry passes the SCRUB STEP before write — anything asset-shaped (code content, snippets, repo-specific facts, repo-identifying detail) abstracted to the pattern or DROPPED; the ledger entry schema has NO code-content-capable field"
|
|
20
20
|
- "route-person-shaped :: .claude/skills/rafa-sage/SKILL.md :: a person-shaped observation is NEVER a learning — it routes to compass's consent path (rafa-insights, user brain), never sage's ledger"
|
|
21
21
|
- "propose-only :: .claude/skills/rafa-sage/SKILL.md :: output is PROPOSED diffs to agent cards/SOPs — applying a change is a separate human/MR-reviewed act; sage never self-applies and never edits an agent card or SOP"
|
|
22
|
+
- "mirror-summary :: .claude/skills/rafa-sage/SKILL.md :: after scrub + file write, each entry's SUMMARY row is mirrored via report_learning for the platform card — a projection, never a second truth; the committed .md stays the ledger"
|
|
22
23
|
- "okf-surface :: .claude/skills/rafa-okf/SKILL.md :: learnings pass the compile learning gate (id · type · title · description) — the same protocol outside the bundle"
|
|
23
24
|
---
|
|
24
25
|
|
|
@@ -128,19 +128,42 @@ try {
|
|
|
128
128
|
.split("\n")
|
|
129
129
|
.filter(Boolean)
|
|
130
130
|
.slice(0, 100);
|
|
131
|
+
|
|
132
|
+
// TAIL RUN detection (0.8.16, live-catch 2026-07-24): if HEAD already has its
|
|
133
|
+
// 1-1 mirror commit (its code-commit trailer is in the brain log), THIS
|
|
134
|
+
// invocation carries the SESSION TAIL — brain deltas written AFTER the last
|
|
135
|
+
// code commit (verify flips · late-authored notes · regenerated manifest)
|
|
136
|
+
// that would otherwise strand uncommitted until the next code commit. The
|
|
137
|
+
// checkpoint beat fires this worker for exactly that case. A tail commit is
|
|
138
|
+
// additive provenance on the SAME join key (trailer unchanged); its subject
|
|
139
|
+
// is distinct so the log never shows two look-alike mirrors, and an empty
|
|
140
|
+
// tail is a NO-OP — never an --allow-empty duplicate.
|
|
141
|
+
let isTail = false;
|
|
142
|
+
try {
|
|
143
|
+
isTail = shR(`git log --grep "code-commit: ${fullSha}" --format=%H -n 1`) !== "";
|
|
144
|
+
} catch {
|
|
145
|
+
/* fresh mirror / unborn HEAD — normal 1-1 path */
|
|
146
|
+
}
|
|
147
|
+
|
|
131
148
|
mkdirSync(join(rafa, "intent"), { recursive: true });
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
149
|
+
// A tail run never rewrites an existing intent record — the 1-1 mirror wrote
|
|
150
|
+
// it, and a fresh `timestamp:` would make every tail beat a phantom delta
|
|
151
|
+
// (the no-op contract would never hold).
|
|
152
|
+
const intentPath = join(rafa, "intent", `${short}.md`);
|
|
153
|
+
if (!isTail || !existsSync(intentPath)) {
|
|
154
|
+
writeFileSync(
|
|
155
|
+
intentPath,
|
|
156
|
+
`---\n` +
|
|
157
|
+
`type: IntentRecord\n` +
|
|
158
|
+
`description: "per-commit intent trail (capture-engine P2) — provenance, consumed at merge, never org-brain truth"\n` +
|
|
159
|
+
`code-commit: ${fullSha}\n` +
|
|
160
|
+
`code-branch: ${branch}\n` +
|
|
161
|
+
`timestamp: ${new Date().toISOString()}\n` +
|
|
162
|
+
`---\n\n# ${subject}\n\n## Files\n` +
|
|
163
|
+
files.map((f) => `- ${f}`).join("\n") +
|
|
164
|
+
`\n`,
|
|
165
|
+
);
|
|
166
|
+
}
|
|
144
167
|
|
|
145
168
|
// Local-state exclusion (owner 2026-07-24): the hydration sidecar + sensor
|
|
146
169
|
// queues are MACHINE state, never knowledge — ensure the ignore entries and
|
|
@@ -155,6 +178,9 @@ try {
|
|
|
155
178
|
"sensor-errors.jsonl",
|
|
156
179
|
"distill-verdicts.json",
|
|
157
180
|
"benchmark.demo.json",
|
|
181
|
+
// rafa review's scored-ranges artifact (wave 2.3) — regenerated per
|
|
182
|
+
// review, machine state like distill-verdicts; never brain knowledge.
|
|
183
|
+
"review.json",
|
|
158
184
|
];
|
|
159
185
|
const gi = join(rafa, ".gitignore");
|
|
160
186
|
const cur = existsSync(gi) ? readFileSync(gi, "utf8") : "";
|
|
@@ -171,10 +197,26 @@ try {
|
|
|
171
197
|
|
|
172
198
|
shR("git add -A");
|
|
173
199
|
disposeHydrations(branch);
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
200
|
+
if (isTail) {
|
|
201
|
+
// Session tail: commit only when something REAL is staged (disposal may
|
|
202
|
+
// have unstaged everything). The 1-1 join stays mechanical — same trailer.
|
|
203
|
+
let staged = [];
|
|
204
|
+
try {
|
|
205
|
+
staged = shR("git diff --cached --name-only").split("\n").filter(Boolean);
|
|
206
|
+
} catch {
|
|
207
|
+
/* nothing staged */
|
|
208
|
+
}
|
|
209
|
+
if (staged.length > 0)
|
|
210
|
+
shR(
|
|
211
|
+
`git commit -q -m "brain(${branch}): session tail · ${staged.length} file(s)" ` +
|
|
212
|
+
`-m "code-commit: ${fullSha}" -m "code-branch: ${branch}"`,
|
|
213
|
+
);
|
|
214
|
+
} else {
|
|
215
|
+
shR(
|
|
216
|
+
`git commit --allow-empty -q -m "brain(${branch}): ${subject}" ` +
|
|
217
|
+
`-m "code-commit: ${fullSha}" -m "code-branch: ${branch}"`,
|
|
218
|
+
);
|
|
219
|
+
}
|
|
178
220
|
// Keep the REMOTE mirror branch current (owner 2026-07-23: the branch is visible
|
|
179
221
|
// in the brain repo) — best-effort, bounded; a failed push retries on the next
|
|
180
222
|
// commit. Never the trunk (single writer = the reconciler).
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: rafa-review
|
|
3
|
-
description: "rafa SOP — the brain-grounded review gate: `rafa review` computes
|
|
3
|
+
description: "rafa SOP — the brain-grounded review gate: `rafa review` computes exactly what a diff touches (cite-intersection, zero LLM): rules AND playbooks, open improvements, stale-cite risk, open gaps, related plans' decisions; a prism-style judge rules ONLY on that list + the active task's Done-check; findings feed atlas; the verdict emits a review-verdict loop event. Loaded before push/merge or on request."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# review — the brain-grounded gate (harness-arc wave 2)
|
|
@@ -16,34 +16,97 @@ split:
|
|
|
16
16
|
## Pass 1 — mechanical (the CLI, zero LLM)
|
|
17
17
|
|
|
18
18
|
`rafa review [--json]` — diff vs merge-base(origin default) → changed line
|
|
19
|
-
ranges ∩ the brain's cite graph (`get_code_context` per file):
|
|
19
|
+
ranges ∩ the brain's cite graph (`get_code_context` per file), BOTH note kinds:
|
|
20
20
|
|
|
21
|
-
- **◆ direct** — a changed line falls inside a
|
|
22
|
-
code the
|
|
21
|
+
- **◆ direct** — a changed line falls inside a note's cited anchor range: the
|
|
22
|
+
code the note stands on MOVED. Highest priority; also a staleness signal
|
|
23
23
|
(the note may need a scoped refresh — the dirty queue already tracks it).
|
|
24
|
-
- **· file** — the file changed elsewhere but the
|
|
24
|
+
- **· file** — the file changed elsewhere but the note governs it.
|
|
25
|
+
- **⚠ stale cites** — the freshness sweep already flagged this note's anchors
|
|
26
|
+
as drifted: a RISK marker (verify the note against code before relying on
|
|
27
|
+
it in judgment — it may describe a world that no longer exists).
|
|
25
28
|
- **! open improvements** citing a touched file — is this change making the
|
|
26
29
|
debt better, worse, or is it the fix itself (then bloom's close flow)?
|
|
30
|
+
- **open knowledge gaps** (top-missed queries) — live demand near the change.
|
|
27
31
|
|
|
28
32
|
Output: `.rafa/review.json` + summary. A bounded pass SAYS what it dropped
|
|
29
33
|
(never a silent cap); a mid-pass transport failure marks the list PARTIAL.
|
|
30
34
|
|
|
35
|
+
## Token economy — BINDING (the gate must cost less than the bugs it catches)
|
|
36
|
+
|
|
37
|
+
The judge's spend is proportional to the DIFF, never to the brain. Owner rule
|
|
38
|
+
(2026-07-24): review checks only the diff; hydrated fields serve the judgment;
|
|
39
|
+
querying files wholesale is a waste. Concretely:
|
|
40
|
+
|
|
41
|
+
1. **The pre-pass is free AND selective** — zero LLM; every note carries a
|
|
42
|
+
deterministic RELEVANCE score (direct-hit +3 · failure:silent +2 · cite
|
|
43
|
+
anchor token appears in the diff's added lines +2 · title/summary keyword
|
|
44
|
+
overlap +1 each, capped) and an **`engage` tier**:
|
|
45
|
+
- `deep` — body-worthy: hydrate/fetch and judge fully;
|
|
46
|
+
- `triage` — summary-only: rule in/out from metadata + diff, fetch a body
|
|
47
|
+
ONLY if that makes a violation plausible;
|
|
48
|
+
- `skip` — governs the file but nothing in the change relates: NEVER
|
|
49
|
+
judged, never fetched — listed in review.json so nothing drops silently.
|
|
50
|
+
(Semantic relevance stays contract-reserved via `matchKind` — keywords +
|
|
51
|
+
citations are the v1 scorer; vectors arrive on evidence, never blindly.)
|
|
52
|
+
2. **Honor the tiers.** The judge never upgrades a `skip`, and upgrades a
|
|
53
|
+
`triage` to a body fetch only with a stated reason (the finding cites it).
|
|
54
|
+
3. **Local-first, never re-serve.** A note already hydrated under
|
|
55
|
+
`.rafa/brain/**` (plan/build recall put it there) is read FROM DISK —
|
|
56
|
+
no MCP round-trip, no double-serving. Fetch via `get_rule`/`get_playbook`
|
|
57
|
+
only for shortlist notes absent locally; `rafa hydrate <kind> <id>` them
|
|
58
|
+
so a re-judge after atlas's fix re-reads the disk copy (disposable-cache
|
|
59
|
+
semantics keep them out of commits).
|
|
60
|
+
**Frontmatter-first (the OKF leverage):** every file type has a strict
|
|
61
|
+
format (contract §11), so the MACHINE fields — `cites` · `anchor` ·
|
|
62
|
+
`failure` · `links` · `type` — parse without touching the prose body.
|
|
63
|
+
Judge from frontmatter + diff first; read the body only when they can't
|
|
64
|
+
settle it. A `contract` note's check is half-mechanical before any prose:
|
|
65
|
+
does the diff touch its `anchor` token anywhere without updating every
|
|
66
|
+
cited site (the B2 completeness question)?
|
|
67
|
+
4. **Deep-judge cap: 10 notes**, leverage-ranked (◆ direct > failure:silent >
|
|
68
|
+
⚠ stale-and-direct > · file). Anything beyond the cap is ruled on metadata
|
|
69
|
+
only and the verdict SAYS SO ("N notes judged shallow") — an honest bound,
|
|
70
|
+
never a silent one.
|
|
71
|
+
5. **Metadata-only lanes stay metadata-only**: gaps, decisions, relatedPlans,
|
|
72
|
+
improvements are judged from what the pre-pass/`get_plan` already carries —
|
|
73
|
+
no body fetches, no extra searches. NEVER load the whole brain, never
|
|
74
|
+
fetch a note the pre-pass didn't flag.
|
|
75
|
+
|
|
31
76
|
## Pass 2 — the judge (prism-style, scoped)
|
|
32
77
|
|
|
33
|
-
For the work list ONLY — never the whole diff, never taste
|
|
78
|
+
For the work list ONLY — never the whole diff, never taste. Each data kind has
|
|
79
|
+
its OWN question:
|
|
34
80
|
|
|
35
|
-
1. Per **rule hit
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
81
|
+
1. Per **rule hit** — the OKF `type` names the violation shape:
|
|
82
|
+
- `convention` → does the new code FOLLOW the local pattern the rule names?
|
|
83
|
+
- `contract` → the cross-file class: does the diff change any occurrence of
|
|
84
|
+
the rule's `anchor` without updating EVERY cited site (B2 completeness —
|
|
85
|
+
half-mechanical from frontmatter, see the economy rules)? `failure:
|
|
86
|
+
silent` contracts get the deepest scrutiny — that class never announces
|
|
87
|
+
itself.
|
|
88
|
+
Judge against code + rule, never against the author's claims.
|
|
89
|
+
2. Per **playbook hit** (`flow` / `how-to` types): is the documented
|
|
90
|
+
FLOW/how-to still TRUE after this change (steps, ordering, file routes)?
|
|
91
|
+
A diff that silently invalidates a playbook ships a lie to the next dev —
|
|
92
|
+
that's an ITERATE finding (or a working-set edit updating the playbook,
|
|
93
|
+
authored the branch way).
|
|
94
|
+
3. Per **⚠ stale-cite note**: verify the note against current code BEFORE
|
|
95
|
+
using it in judgment; if the diff is the thing that staled it further,
|
|
96
|
+
surface the scoped-refresh offer.
|
|
97
|
+
4. Per **open improvement**: touched for better or worse? Fixed-in-passing →
|
|
40
98
|
bloom's close flow (hydrate first, `status: fixed`, report).
|
|
41
|
-
|
|
99
|
+
5. **Related plans' decisions** (`get_plan` for each `relatedPlans` entry):
|
|
100
|
+
does the diff contradict a recorded decision? Contradicting one is the
|
|
101
|
+
owner's call to reopen — an ITERATE finding, never silently overridden.
|
|
102
|
+
6. **Open gaps**: does this change ANSWER one (author the note, close the
|
|
103
|
+
gap) — or deepen it?
|
|
104
|
+
7. **The active task's Done-check** (from `get_active_plan`): does the diff
|
|
42
105
|
satisfy what the plan said done means?
|
|
43
|
-
|
|
106
|
+
8. Verdict: **PASS** or **ITERATE** with per-finding citations (note id +
|
|
44
107
|
file:line). Findings feed atlas (validate-and-correct — atlas fixes, the
|
|
45
108
|
judge re-checks; the reviewer never edits).
|
|
46
|
-
|
|
109
|
+
9. Emit **`report_loop_event(category: "review-verdict", outcome:
|
|
47
110
|
PASS|ITERATE, subject: <task id or branch>)`** at the ruling — sage's
|
|
48
111
|
evidence, monotonic, never a session-end sweep.
|
|
49
112
|
|
|
@@ -101,7 +101,9 @@ human.
|
|
|
101
101
|
The ledger is a **committed, human-reviewed governance artifact** — diffs for MR review. It is
|
|
102
102
|
**NOT** a Convex table and **NOT** inside any customer `.rafa/brain/` (asset-free: learnings are
|
|
103
103
|
about OUR agents; they never mix with customer knowledge). It lives beside the contract at
|
|
104
|
-
`.claude/rafa/learnings/` (governance, versioned with the blueprint, MR-reviewed)
|
|
104
|
+
`.claude/rafa/learnings/` (governance, versioned with the blueprint, MR-reviewed). The platform's
|
|
105
|
+
"agent learnings" card renders a **projection** of it (summary rows via `report_learning`, wave
|
|
106
|
+
4.1, owner-ratified 2026-07-24) — a display overlay, never a second truth and never the store:
|
|
105
107
|
|
|
106
108
|
- `learnings/<id>.md` — one file per learning (one proposed card/SOP diff, cited to event shapes).
|
|
107
109
|
- `ledger.md` — generated index: counts by category / target / status, and the top-leverage few.
|
|
@@ -169,7 +171,16 @@ enforced by sage and by MR review.
|
|
|
169
171
|
7. **Ledger + leverage.** Regenerate `ledger.md`: counts by category / target / status, and the
|
|
170
172
|
**top-leverage few** (impact × ease) — the single card/SOP change that would cover the widest
|
|
171
173
|
class of misses, first.
|
|
172
|
-
8. **
|
|
174
|
+
8. **Mirror the summary rows (wave 4.1 — the platform "agent learnings" card).** For each entry
|
|
175
|
+
written or status-changed in step 6, call `report_learning` with the entry's SUMMARY fields
|
|
176
|
+
(`id · pattern · categories · evidence_shape · proposed_diff_target · proposed_change ·
|
|
177
|
+
status · leverage`) — AFTER the scrub step and the file write, never before. This is a
|
|
178
|
+
**projection, not a second truth** (owner 2026-07-24): the committed `.md` stays the single
|
|
179
|
+
truth; the rows only render the Activity card. Upsert by id — re-runs refresh `status`,
|
|
180
|
+
never duplicate. The server re-screens every field (enum checks, `.claude/`-only target,
|
|
181
|
+
length caps, code-fence/secret rejection) — a rejection means the entry failed the asset-free
|
|
182
|
+
gate: fix the ENTRY, don't re-word past the screen.
|
|
183
|
+
9. **Present, don't nag, don't apply.** Surface the top few high-leverage learnings succinctly as
|
|
173
184
|
PROPOSALS. sage never applies a diff; a human reviews the ledger, and any accepted change lands
|
|
174
185
|
as a versioned, MR-reviewed card/SOP edit (bump `version:`, record the why). Silent otherwise.
|
|
175
186
|
|
package/lib/checkpoint.mjs
CHANGED
|
@@ -27,6 +27,62 @@ const die = (m) => {
|
|
|
27
27
|
process.exit(1);
|
|
28
28
|
};
|
|
29
29
|
|
|
30
|
+
// The GIT-PLANE tail beat (0.8.16, live-catch 2026-07-24): brain deltas written
|
|
31
|
+
// AFTER the last code commit (verify flips · late-authored notes · regenerated
|
|
32
|
+
// manifest) have no post-commit mirror to ride, and the pre-push ceremony may
|
|
33
|
+
// already have run — they strand uncommitted until the next code commit. The
|
|
34
|
+
// checkpoint beat is the boundary that CREATES that tail, so it fires the SAME
|
|
35
|
+
// vendored worker the git hooks run (single implementation, tail-aware since
|
|
36
|
+
// 0.8.16) whenever the brain tree is dirty. One beat, both planes: "clean"
|
|
37
|
+
// from this command means capture AND mirror. Best-effort — a mirror problem
|
|
38
|
+
// never fails a checkpoint.
|
|
39
|
+
function mirrorTail(ROOT, branch) {
|
|
40
|
+
const rafa = join(ROOT, ".rafa");
|
|
41
|
+
const worker = join(ROOT, ".claude", "rafa", "hooks", "brain-commit.mjs");
|
|
42
|
+
if (!existsSync(join(rafa, ".git")) || !existsSync(worker)) return;
|
|
43
|
+
const shR = (cmd) =>
|
|
44
|
+
execSync(cmd, { cwd: rafa, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
|
|
45
|
+
let dirty = "";
|
|
46
|
+
try {
|
|
47
|
+
dirty = shR("git status --porcelain");
|
|
48
|
+
} catch {
|
|
49
|
+
return; // unreadable brain repo — the hooks' own paths will surface it
|
|
50
|
+
}
|
|
51
|
+
if (dirty === "") return; // git plane already clean — nothing stranded
|
|
52
|
+
let before = "";
|
|
53
|
+
try {
|
|
54
|
+
before = shR("git rev-parse HEAD");
|
|
55
|
+
} catch {
|
|
56
|
+
/* unborn HEAD — the worker handles it */
|
|
57
|
+
}
|
|
58
|
+
try {
|
|
59
|
+
execSync(`node "${worker}"`, { cwd: ROOT, stdio: ["ignore", "ignore", "ignore"], timeout: 60000 });
|
|
60
|
+
} catch {
|
|
61
|
+
/* worker is silent + non-blocking by design; fall through to the report */
|
|
62
|
+
}
|
|
63
|
+
try {
|
|
64
|
+
const after = shR("git rev-parse HEAD");
|
|
65
|
+
if (after !== before) {
|
|
66
|
+
let pushed = false;
|
|
67
|
+
try {
|
|
68
|
+
pushed = shR(`git rev-parse "origin/${branch}"`) === after;
|
|
69
|
+
} catch {
|
|
70
|
+
/* no remote tracking yet */
|
|
71
|
+
}
|
|
72
|
+
console.log(
|
|
73
|
+
`✓ brain mirror: session tail committed (${after.slice(0, 12)})` +
|
|
74
|
+
(pushed ? " + pushed" : " — push rides the next pre-push ceremony"),
|
|
75
|
+
);
|
|
76
|
+
} else {
|
|
77
|
+
console.log(
|
|
78
|
+
"• brain tree dirt is disposable hydration cache — nothing real to mirror",
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
} catch {
|
|
82
|
+
/* report is best-effort */
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
30
86
|
export default async function checkpoint() {
|
|
31
87
|
const ROOT = process.cwd();
|
|
32
88
|
|
|
@@ -101,6 +157,9 @@ export default async function checkpoint() {
|
|
|
101
157
|
|
|
102
158
|
if (candidates.length === 0) {
|
|
103
159
|
console.log(`✓ working set clean on ${branch} — nothing to checkpoint`);
|
|
160
|
+
// A clean CAPTURE plane can still hide a dirty GIT plane (the live catch:
|
|
161
|
+
// "checkpoint said clean" while the session tail sat uncommitted).
|
|
162
|
+
mirrorTail(ROOT, branch);
|
|
104
163
|
// Knowledge-absence nudge (live-catch 2026-07-17: a session can do real
|
|
105
164
|
// work and bank ZERO knowledge, silently — no sensor fires on absence).
|
|
106
165
|
// Deterministic signal only: code edits recorded, no brain delta on this
|
|
@@ -184,6 +243,11 @@ export default async function checkpoint() {
|
|
|
184
243
|
(conflicts ? ` · ${conflicts} conflict(s) need YOUR decision (see above)` : ""),
|
|
185
244
|
);
|
|
186
245
|
|
|
246
|
+
// Both planes, one beat: the capture sync above, the brain mirror here.
|
|
247
|
+
// (Runs before the conflict exit — a contested working set is still worth a
|
|
248
|
+
// durable mirror of the dev's current copies; .theirs.md files are ignored.)
|
|
249
|
+
mirrorTail(ROOT, branch);
|
|
250
|
+
|
|
187
251
|
// prism-verdict backstop (harness-arc wave 0): gate-result/reflex-outcome are
|
|
188
252
|
// emitted mechanically by the CLI, but prism verdicts are LLM judgments only
|
|
189
253
|
// the session can report. The checkpoint beat — the SOP's own emit moment —
|
package/lib/releases.mjs
CHANGED
|
@@ -264,20 +264,6 @@ export const RELEASES = [
|
|
|
264
264
|
"first branch checkout (bounded; a failure never breaks the checkout). " +
|
|
265
265
|
"`rafa update` re-vendors the post-checkout hook.",
|
|
266
266
|
},
|
|
267
|
-
{
|
|
268
|
-
version: "0.8.14",
|
|
269
|
-
contract: 1,
|
|
270
|
-
plans: 2,
|
|
271
|
-
requires: "update",
|
|
272
|
-
summary:
|
|
273
|
-
"LOCAL-STATE EXCLUSION (zero-leak mirrors): the hydration sidecar + sensor " +
|
|
274
|
-
"queues (hydration.json · dirty/reflex/sensor-errors.jsonl · benchmark.demo.json " +
|
|
275
|
-
"· distill staging · *.theirs.md) are machine state, never knowledge — the " +
|
|
276
|
-
"post-commit brain hook now ENSURES the .rafa/.gitignore entries AND untracks " +
|
|
277
|
-
"anything an earlier window let git track (gitignore alone never untracks), so " +
|
|
278
|
-
"the next mirror commit cleans the branch. ensureBrainRepo's ignore list gains " +
|
|
279
|
-
"sensor-errors.jsonl + benchmark.demo.json. `rafa update` re-vendors the hook.",
|
|
280
|
-
},
|
|
281
267
|
{
|
|
282
268
|
version: "0.8.13",
|
|
283
269
|
contract: 1,
|
|
@@ -292,6 +278,59 @@ export const RELEASES = [
|
|
|
292
278
|
"Platform fixes ride server-side: set_active_plan now resolves v2 epics " +
|
|
293
279
|
"(was v1 parent-only — the not_found bug), get_improvement serves summary.",
|
|
294
280
|
},
|
|
281
|
+
{
|
|
282
|
+
version: "0.8.14",
|
|
283
|
+
contract: 1,
|
|
284
|
+
plans: 2,
|
|
285
|
+
requires: "update",
|
|
286
|
+
summary:
|
|
287
|
+
"THE PROVENANCE SPINE (published 2026-07-24). Branch manifest: `rafa manifest` " +
|
|
288
|
+
"(zero-command — the post-commit brain hook regenerates it per mirrored commit): " +
|
|
289
|
+
"lenient snapshot with mode:branch · base.brainSha · per-note provenance " +
|
|
290
|
+
"{baseBrainSha, commits[], tasks[]} — the reconciler's three-way handoff doc. " +
|
|
291
|
+
"rafa-commit skill: subjects carry the active task id ([task-id] type: subject) " +
|
|
292
|
+
"and the manifest lifts it into per-note task lineage. Deterministic loop " +
|
|
293
|
+
"events: push gates emit gate-result from real exit codes; reflex --consume " +
|
|
294
|
+
"emits reflex-outcome. Benchmark is workflow-woven (scan's Prove-it step + " +
|
|
295
|
+
"session-start absence sensor) — never a dev-typed command.",
|
|
296
|
+
},
|
|
297
|
+
{
|
|
298
|
+
version: "0.8.15",
|
|
299
|
+
contract: 1,
|
|
300
|
+
plans: 2,
|
|
301
|
+
requires: "update",
|
|
302
|
+
summary:
|
|
303
|
+
"THE REVIEW GATE + ZERO-LEAK MIRRORS. `rafa review`: deterministic brain-" +
|
|
304
|
+
"grounded review pre-pass — diff's changed lines ∩ the cite graph → rules AND " +
|
|
305
|
+
"playbooks + open improvements + stale-cite risk + open gaps, each note " +
|
|
306
|
+
"relevance-scored (OKF type-aware: contract class weighs heavier) into " +
|
|
307
|
+
"deep/triage/skip engage tiers so the judge's tokens scale with the DIFF, " +
|
|
308
|
+
"never the brain (rafa-review SOP: local-first + frontmatter-first reads, " +
|
|
309
|
+
"10-note deep cap, skips listed never judged). LOCAL-STATE EXCLUSION: the " +
|
|
310
|
+
"post-commit hook now ensures .rafa/.gitignore AND untracks previously " +
|
|
311
|
+
"tracked machine state (hydration.json · sensor queues · demo benchmark · " +
|
|
312
|
+
"distill staging · *.theirs.md) — the next mirror commit cleans the branch. " +
|
|
313
|
+
"Plus wave-2 SOPs: delta briefing + decision recall at plan time, branch-" +
|
|
314
|
+
"aware recall (tier-labeled), compass sitback, gap lifecycle beats.",
|
|
315
|
+
},
|
|
316
|
+
{
|
|
317
|
+
version: "0.8.16",
|
|
318
|
+
contract: 1,
|
|
319
|
+
plans: 2,
|
|
320
|
+
requires: "update",
|
|
321
|
+
summary:
|
|
322
|
+
"THE SESSION TAIL NEVER STRANDS (live-catch 2026-07-24: brain notes " +
|
|
323
|
+
"authored AFTER the last code commit sat uncommitted while `rafa " +
|
|
324
|
+
"checkpoint` said clean — capture plane synced, git plane dirty). " +
|
|
325
|
+
"`rafa checkpoint` now owns BOTH planes: after the working-set sync it " +
|
|
326
|
+
"fires the vendored brain-commit worker whenever the brain tree is " +
|
|
327
|
+
"dirty, so the tail commits + pushes at the same beat that creates it. " +
|
|
328
|
+
"The worker is tail-aware: HEAD already mirrored ⇒ a distinct " +
|
|
329
|
+
"`session tail · N file(s)` subject on the same code-commit trailer " +
|
|
330
|
+
"(never a look-alike duplicate), and an empty tail is a no-op. " +
|
|
331
|
+
"review.json (the review gate's scored-ranges artifact) joins the " +
|
|
332
|
+
"local-state exclusion — machine state, never brain knowledge.",
|
|
333
|
+
},
|
|
295
334
|
];
|
|
296
335
|
|
|
297
336
|
// The release this CLI build ships (last entry).
|
package/lib/review.mjs
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
// rafa review — the brain-grounded review gate's DETERMINISTIC pre-pass
|
|
2
2
|
// (harness-arc wave 2). Zero LLM here: the diff's changed line ranges are
|
|
3
3
|
// intersected with the brain's cite graph (get_code_context — file → the
|
|
4
|
-
//
|
|
5
|
-
// scoped work list the rafa-review SOP's judge then rules on. Generic
|
|
6
|
-
// "review this PR" never happens — the judge only ever sees THESE
|
|
7
|
-
// against THESE lines.
|
|
4
|
+
// RULES *and* PLAYBOOKS and improvements that know about it), producing the
|
|
5
|
+
// exact, scoped work list the rafa-review SOP's judge then rules on. Generic
|
|
6
|
+
// "review this PR" never happens — the judge only ever sees THESE notes
|
|
7
|
+
// against THESE lines. Also surfaced: per-note stale-cite RISK flags (the
|
|
8
|
+
// freshness sweep's signal) and the repo's open knowledge gaps — the judge
|
|
9
|
+
// weighs whether this change answers or worsens one.
|
|
8
10
|
//
|
|
9
11
|
// rafa review [--base <ref>] [--json]
|
|
10
12
|
//
|
|
@@ -71,6 +73,50 @@ export function citeHits(citeLine, ranges) {
|
|
|
71
73
|
return ranges.some((r) => a <= r.end && b >= r.start);
|
|
72
74
|
}
|
|
73
75
|
|
|
76
|
+
// Identifier tokens from the diff's ADDED lines, per file — the relevance
|
|
77
|
+
// vocabulary a note must intersect to earn the judge's attention.
|
|
78
|
+
export function addedTokens(diffText) {
|
|
79
|
+
const byFile = new Map();
|
|
80
|
+
let current = null;
|
|
81
|
+
for (const line of diffText.split("\n")) {
|
|
82
|
+
const f = line.match(/^\+\+\+ b\/(.+)$/);
|
|
83
|
+
if (f) {
|
|
84
|
+
current = f[1];
|
|
85
|
+
if (!byFile.has(current)) byFile.set(current, new Set());
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
if (current && line.startsWith("+") && !line.startsWith("+++"))
|
|
89
|
+
for (const t of line.slice(1).toLowerCase().match(/[a-z_][a-z0-9_-]{2,}/g) ?? [])
|
|
90
|
+
byFile.get(current).add(t);
|
|
91
|
+
}
|
|
92
|
+
return byFile;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Deterministic relevance (owner 2026-07-24: never hydrate/judge blindly —
|
|
96
|
+
// keywords + citations decide whether a note ADDS VALUE to this diff; the OKF
|
|
97
|
+
// type registry is leveraged: every type has a format, so every type has a
|
|
98
|
+
// DEFINED violation shape and a deterministic weight):
|
|
99
|
+
// direct anchor hit +3 (the cited code itself moved)
|
|
100
|
+
// type: contract +2 (the cross-file class — breaks span files)
|
|
101
|
+
// failure: silent +2 (the class that never announces itself)
|
|
102
|
+
// cite anchor token in diff +2 (the rule's own token appears in new code)
|
|
103
|
+
// title/summary keyword +1 each, capped at +3
|
|
104
|
+
// → engage: "deep" (≥3: body-worthy) · "triage" (1–2: summary-only) ·
|
|
105
|
+
// "skip" (0: governs the file but nothing relates — listed, never judged).
|
|
106
|
+
export function relevanceOf(note, diffTokens) {
|
|
107
|
+
let score = 0;
|
|
108
|
+
if (note.hit === "direct") score += 3;
|
|
109
|
+
if (note.type === "contract") score += 2;
|
|
110
|
+
if (note.failure === "silent") score += 2;
|
|
111
|
+
const anchor = String(note.token ?? "").toLowerCase();
|
|
112
|
+
if (anchor && [...diffTokens].some((t) => anchor.includes(t) || t.includes(anchor))) score += 2;
|
|
113
|
+
let kw = 0;
|
|
114
|
+
const text = `${note.title ?? ""} ${note.summary ?? ""}`.toLowerCase();
|
|
115
|
+
for (const t of diffTokens) if (text.includes(t)) kw += 1;
|
|
116
|
+
score += Math.min(kw, 3);
|
|
117
|
+
return { score, engage: score >= 3 ? "deep" : score >= 1 ? "triage" : "skip" };
|
|
118
|
+
}
|
|
119
|
+
|
|
74
120
|
export default async function review(args = []) {
|
|
75
121
|
const ROOT = process.cwd();
|
|
76
122
|
if (!existsSync(join(ROOT, "rafa.json"))) {
|
|
@@ -88,6 +134,7 @@ export default async function review(args = []) {
|
|
|
88
134
|
|
|
89
135
|
const diff = sh(`git diff -U0 ${base}`, ROOT);
|
|
90
136
|
const ranges = changedRanges(diff);
|
|
137
|
+
const tokensByFile = addedTokens(diff);
|
|
91
138
|
// Code files only — the brain plane reviews itself at reconcile, not here.
|
|
92
139
|
const files = [...ranges.keys()].filter(
|
|
93
140
|
(p) => !p.startsWith(".rafa/") && !p.startsWith(".claude/") && p !== "rafa.json",
|
|
@@ -106,18 +153,43 @@ export default async function review(args = []) {
|
|
|
106
153
|
break; // one transport failure fails them all — say so, don't half-report
|
|
107
154
|
}
|
|
108
155
|
const fileRanges = ranges.get(path) ?? [];
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
156
|
+
// BOTH note kinds engage: a rule hit asks "does the diff still honor the
|
|
157
|
+
// convention/contract"; a playbook hit asks "is the documented flow/how-to
|
|
158
|
+
// still true after this change". staleCites = freshness RISK (the note may
|
|
159
|
+
// already describe drifted code — verify before relying on it).
|
|
160
|
+
const fileTokens = tokensByFile.get(path) ?? new Set();
|
|
161
|
+
const notes = (ctx.notes ?? []).map((n) => {
|
|
162
|
+
const base = {
|
|
163
|
+
id: n.id,
|
|
164
|
+
kind: n.kind,
|
|
165
|
+
...(n.type ? { type: n.type } : {}),
|
|
166
|
+
title: n.title,
|
|
167
|
+
domain: n.domain,
|
|
168
|
+
line: n.line,
|
|
169
|
+
token: n.token,
|
|
170
|
+
...(n.failure ? { failure: n.failure } : {}),
|
|
171
|
+
// The one-line summary IS the triage surface — the judge rules most
|
|
172
|
+
// notes in/out from this alone, fetching bodies only for the shortlist.
|
|
173
|
+
...(n.summary ? { summary: n.summary } : {}),
|
|
174
|
+
...(n.staleCites ? { staleCites: n.staleCites } : {}),
|
|
175
|
+
hit: citeHits(n.line, fileRanges) ? "direct" : "file",
|
|
176
|
+
};
|
|
177
|
+
const { score, engage } = relevanceOf(base, fileTokens);
|
|
178
|
+
return { ...base, relevance: score, engage };
|
|
179
|
+
});
|
|
118
180
|
const improvements = (ctx.improvements ?? []).filter((i) => i.status === "open");
|
|
119
|
-
if (
|
|
120
|
-
out.push({ path, ranges: fileRanges,
|
|
181
|
+
if (notes.length > 0 || improvements.length > 0)
|
|
182
|
+
out.push({ path, ranges: fileRanges, notes, improvements, relatedPlans: ctx.relatedPlans ?? [] });
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Open knowledge gaps — live demand near this change (queries + counts only).
|
|
186
|
+
// The judge weighs: does this diff ANSWER one (close it) or deepen it?
|
|
187
|
+
let gaps = [];
|
|
188
|
+
try {
|
|
189
|
+
const g = await callTool(ROOT, "get_knowledge_gaps", { status: "open", limit: 5 });
|
|
190
|
+
gaps = (g.gaps ?? []).map((x) => ({ q: x.q, misses: x.misses }));
|
|
191
|
+
} catch {
|
|
192
|
+
/* gaps are context, not a gate — absence never fails the pass */
|
|
121
193
|
}
|
|
122
194
|
|
|
123
195
|
const report = {
|
|
@@ -128,6 +200,7 @@ export default async function review(args = []) {
|
|
|
128
200
|
...(dropped > 0 ? { dropped } : {}),
|
|
129
201
|
...(unreachable ? { unreachable } : {}),
|
|
130
202
|
files: out,
|
|
203
|
+
...(gaps.length > 0 ? { openGaps: gaps } : {}),
|
|
131
204
|
};
|
|
132
205
|
writeFileSync(join(ROOT, ".rafa", "review.json"), JSON.stringify(report, null, 2) + "\n");
|
|
133
206
|
|
|
@@ -135,9 +208,12 @@ export default async function review(args = []) {
|
|
|
135
208
|
console.log(JSON.stringify(report, null, 2));
|
|
136
209
|
return;
|
|
137
210
|
}
|
|
138
|
-
const direct = out.flatMap((f) => f.
|
|
139
|
-
const fileHits = out.flatMap((f) => f.
|
|
211
|
+
const direct = out.flatMap((f) => f.notes.filter((r) => r.hit === "direct"));
|
|
212
|
+
const fileHits = out.flatMap((f) => f.notes.filter((r) => r.hit === "file"));
|
|
140
213
|
const imps = out.flatMap((f) => f.improvements);
|
|
214
|
+
const stale = out.flatMap((f) => f.notes.filter((r) => r.staleCites));
|
|
215
|
+
const engaged = out.flatMap((f) => f.notes.filter((r) => r.engage !== "skip"));
|
|
216
|
+
const skipped = out.flatMap((f) => f.notes.filter((r) => r.engage === "skip"));
|
|
141
217
|
console.log(
|
|
142
218
|
`✓ review pre-pass vs ${base.slice(0, 7)}: ${files.length} changed file(s)` +
|
|
143
219
|
(dropped > 0 ? ` (${dropped} beyond the ${MAX_FILES}-file bound NOT reviewed)` : ""),
|
|
@@ -149,12 +225,20 @@ export default async function review(args = []) {
|
|
|
149
225
|
}
|
|
150
226
|
for (const f of out) {
|
|
151
227
|
console.log(` ${f.path}`);
|
|
152
|
-
for (const r of f.
|
|
153
|
-
console.log(
|
|
228
|
+
for (const r of f.notes.filter((n) => n.engage !== "skip"))
|
|
229
|
+
console.log(
|
|
230
|
+
` ${r.hit === "direct" ? "◆" : "·"} [${r.engage}] ${r.kind} ${r.id} [${r.domain}] ${r.title}` +
|
|
231
|
+
`${r.failure ? ` (failure: ${r.failure})` : ""}${r.staleCites ? ` ⚠ ${r.staleCites} stale cite(s)` : ""}`,
|
|
232
|
+
);
|
|
154
233
|
for (const i of f.improvements) console.log(` ! open ${i.priority}: ${i.id} ${i.title}`);
|
|
155
234
|
}
|
|
235
|
+
if (gaps.length)
|
|
236
|
+
console.log(` open knowledge gaps (does this change answer one?): ${gaps.map((g) => `"${g.q}"×${g.misses}`).join(" · ")}`);
|
|
156
237
|
console.log(
|
|
157
|
-
`→ ${
|
|
158
|
-
|
|
238
|
+
`→ engage ${engaged.length}/${engaged.length + skipped.length} note(s) (${direct.length} direct · ${fileHits.length} same-file` +
|
|
239
|
+
(skipped.length ? ` · ${skipped.length} governs-the-file-but-unrelated SKIPPED, listed in review.json` : "") +
|
|
240
|
+
`) · ${imps.length} open improvement(s)` +
|
|
241
|
+
(stale.length ? ` · ⚠ ${stale.length} stale-cite note(s) — verify before relying` : "") +
|
|
242
|
+
`\n The judge rules on exactly the engaged set (rafa-review SOP): deep = body-worthy, triage = summary-only.`,
|
|
159
243
|
);
|
|
160
244
|
}
|
package/package.json
CHANGED