@rafinery/cli 0.8.0 → 0.8.2
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 +20 -0
- package/bin/rafa-distiller.mjs +13 -0
- package/bin/rafa.mjs +4 -0
- package/blueprint/.claude/agents/atlas.md +5 -1
- package/blueprint/.claude/agents/bloom.md +5 -1
- package/blueprint/.claude/agents/compass.md +5 -1
- package/blueprint/.claude/agents/prism.md +5 -1
- package/blueprint/.claude/agents/sage.md +5 -1
- package/blueprint/.claude/commands/rafa.md +19 -1
- package/blueprint/.claude/rafa/contract.md +31 -3
- package/blueprint/.claude/rafa/hooks/brain-commit.mjs +80 -0
- package/blueprint/.claude/rafa/hooks/brain-map.mjs +64 -0
- package/blueprint/.claude/rafa/hooks/brain-switch.mjs +48 -0
- package/blueprint/.claude/rafa/hooks/post-checkout +10 -0
- package/blueprint/.claude/rafa/hooks/post-commit +11 -0
- package/blueprint/.claude/rafa/hooks/post-rewrite +10 -0
- package/blueprint/.claude/rafa/hooks/pre-push +22 -0
- package/blueprint/.claude/rafa/hooks/session-start.mjs +93 -0
- package/blueprint/.claude/skills/rafa-improve/SKILL.md +5 -0
- package/blueprint/.claude/skills/rafa-validate/SKILL.md +6 -0
- package/lib/checkpoint.mjs +42 -0
- package/lib/ci-setup.mjs +48 -3
- package/lib/dirty.mjs +37 -0
- package/lib/distill.mjs +313 -37
- package/lib/distiller/doctrine.mjs +379 -0
- package/lib/distiller/state-plane.mjs +159 -0
- package/lib/distiller.mjs +410 -0
- package/lib/doctor.mjs +110 -0
- package/lib/gate/compile.mjs +31 -0
- package/lib/gate/okf-emit.mjs +12 -0
- package/lib/gate/verify-citations.mjs +9 -2
- package/lib/githook.mjs +75 -33
- package/lib/init.mjs +14 -2
- package/lib/mcp-client.mjs +22 -5
- package/lib/okf.mjs +11 -1
- package/lib/pull.mjs +2 -2
- package/lib/push.mjs +67 -2
- package/lib/releases.mjs +26 -0
- package/lib/sensor-health.mjs +91 -0
- package/lib/status.mjs +47 -1
- package/lib/update.mjs +2 -2
- package/lib/working-set.mjs +19 -1
- package/package.json +11 -9
- package/LICENSE +0 -21
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,26 @@ 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.2 — MCP scope: derived from the key, never guessed
|
|
8
|
+
|
|
9
|
+
First real client MCP call surfaced it: agents GUESSED the `repo` arg (folder name)
|
|
10
|
+
because nothing told them where the id lives. Server-side, `repo` is now optional on
|
|
11
|
+
every tool — the per-repo key IS the scope and the platform derives it (explicit
|
|
12
|
+
mismatch still fails loudly, teaching both exits). Fleet-side, every agent card +
|
|
13
|
+
the conductor carry the convention: omit `repo`; where a value is explicitly needed
|
|
14
|
+
it is the committed `rafa.json → repoId`, never a guess. Adopt with `rafa update`.
|
|
15
|
+
|
|
16
|
+
## 0.8.1 — honest empty: absence is never conformance
|
|
17
|
+
|
|
18
|
+
`rafa okf check` over a root with zero `.md` files used to print a green "conforms" —
|
|
19
|
+
a vacuous truth over nothing, the exact silent-empty class the contract bans. Now:
|
|
20
|
+
zero files → loud failure ("nothing to check") with root guidance (a brain-repo clone
|
|
21
|
+
wants `--root=.`; a code repo's `.rafa` is lazy until `rafa pull --full`/a scan).
|
|
22
|
+
`rafa okf` (emit) on an empty root writes no scaffold and returns 1 — push logs it and
|
|
23
|
+
continues (a lazy `.rafa` is legitimate there); standalone exits loud.
|
|
24
|
+
`@rafinery/okf` 0.1.1 adds the additive `empty` flag to `validateBundle` so library
|
|
25
|
+
consumers distinguish "checked and clean" from "nothing to check."
|
|
26
|
+
|
|
7
27
|
## 0.8.0 — the OKF surface: every brain is an Open Knowledge Format bundle
|
|
8
28
|
|
|
9
29
|
**Adopt with:** `npx @rafinery/cli@latest update`, then `rafa push` — the next push
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// rafa-distiller — the distiller loop's binary (reconciliation orchestrator, spec
|
|
3
|
+
// §2.6). boot.sh's DISTILLER_ENTRY seam: the sandbox image's boot.sh wins the claim
|
|
4
|
+
// (claim-on-start, exports RAFA_ATTEMPT) and `exec rafa-distiller`. From here the
|
|
5
|
+
// metered loop owns the run: heartbeat + arbitrate against merged main + author
|
|
6
|
+
// survivors through the gates + push (never force) + reconcile_report.
|
|
7
|
+
//
|
|
8
|
+
// Thin entry only — the loop lives in ../lib/distiller.mjs so it stays importable +
|
|
9
|
+
// unit-testable (runDistiller) without a sandbox.
|
|
10
|
+
|
|
11
|
+
import distiller from "../lib/distiller.mjs";
|
|
12
|
+
|
|
13
|
+
await distiller(process.argv.slice(2));
|
package/bin/rafa.mjs
CHANGED
|
@@ -12,6 +12,8 @@
|
|
|
12
12
|
// rafa checkpoint sync this branch's working set (CAS; conflicts → .theirs.md)
|
|
13
13
|
// rafa hydrate fault knowledge into the lazy .rafa/
|
|
14
14
|
// rafa dirty [--json] staleness queue → citing notes; --consume after a refresh
|
|
15
|
+
// rafa doctor prove the capture machinery end to end (sensors ·
|
|
16
|
+
// scripts · heartbeat round-trip); runs inside init too
|
|
15
17
|
// rafa fold branch→parent mechanical fold of the working set (no LLM)
|
|
16
18
|
// rafa distill --headless CI merge-to-main distillation (org's own LLM key)
|
|
17
19
|
// rafa ci-setup write the reconcile workflow (fold + distill on PR merge)
|
|
@@ -45,8 +47,10 @@ const COMMANDS = [
|
|
|
45
47
|
"dirty",
|
|
46
48
|
"reflex",
|
|
47
49
|
"status",
|
|
50
|
+
"doctor",
|
|
48
51
|
"fold",
|
|
49
52
|
"distill",
|
|
53
|
+
"distiller",
|
|
50
54
|
"ci-setup",
|
|
51
55
|
"leverage",
|
|
52
56
|
"benchmark",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: atlas
|
|
3
|
-
version: 3.8.
|
|
3
|
+
version: 3.8.1
|
|
4
4
|
model: opus # authoring is correctness-critical — a hallucinated note poisons the brain; best model, never cheap
|
|
5
5
|
groundTruth: code-at-sha
|
|
6
6
|
description: >-
|
|
@@ -23,6 +23,10 @@ duties:
|
|
|
23
23
|
|
|
24
24
|
# atlas — senior design engineer
|
|
25
25
|
|
|
26
|
+
**MCP scope — every `mcp__rafinery` call:** OMIT `repo`; your key IS the repo
|
|
27
|
+
scope and the server derives it. Where a value is explicitly needed, it is the
|
|
28
|
+
committed `rafa.json → repoId` — NEVER a folder name or repo-name guess.
|
|
29
|
+
|
|
26
30
|
You are **atlas**, a senior design engineer for the rafa platform. A peer who
|
|
27
31
|
reasons about systems, not a code-completion assistant.
|
|
28
32
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: bloom
|
|
3
|
-
version: 0.8.
|
|
3
|
+
version: 0.8.1
|
|
4
4
|
model: opus # a false flag mutes the whole ledger — best model, never cheap
|
|
5
5
|
groundTruth: code-trend
|
|
6
6
|
description: >-
|
|
@@ -22,6 +22,10 @@ duties:
|
|
|
22
22
|
|
|
23
23
|
# bloom — continuous improvement
|
|
24
24
|
|
|
25
|
+
**MCP scope — every `mcp__rafinery` call:** OMIT `repo`; your key IS the repo
|
|
26
|
+
scope and the server derives it. Where a value is explicitly needed, it is the
|
|
27
|
+
committed `rafa.json → repoId` — NEVER a folder name or repo-name guess.
|
|
28
|
+
|
|
25
29
|
You are **bloom**, rafa's improvement engine — a patient staff engineer who raises the bar
|
|
26
30
|
a little every interaction, relentless but gentle. atlas **knows** the codebase; **prism**
|
|
27
31
|
doubts it; **you raise its floor.**
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: compass
|
|
3
|
-
version: 0.4.
|
|
3
|
+
version: 0.4.1
|
|
4
4
|
model: opus # a false note about a PERSON is worse than one about code — best model, never cheap
|
|
5
5
|
groundTruth: sessions-over-time
|
|
6
6
|
description: >-
|
|
@@ -23,6 +23,10 @@ duties:
|
|
|
23
23
|
|
|
24
24
|
# compass — the dev's coach
|
|
25
25
|
|
|
26
|
+
**MCP scope — every `mcp__rafinery` call:** OMIT `repo`; your key IS the repo
|
|
27
|
+
scope and the server derives it. Where a value is explicitly needed, it is the
|
|
28
|
+
committed `rafa.json → repoId` — NEVER a folder name or repo-name guess.
|
|
29
|
+
|
|
26
30
|
You are **compass**, the fourth agent: where atlas knows the code, prism doubts
|
|
27
31
|
the claims, and bloom raises the floor, you know — and refine — **how this
|
|
28
32
|
developer works**. Your store is the user brain: account-scoped, cross-repo,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: prism
|
|
3
|
-
version: 0.7.
|
|
3
|
+
version: 0.7.1
|
|
4
4
|
model: opus # the trust anchor — a hallucinated verdict/finding is the worst failure; best model, never cheap
|
|
5
5
|
groundTruth: code-vs-claim
|
|
6
6
|
description: >-
|
|
@@ -22,6 +22,10 @@ duties:
|
|
|
22
22
|
|
|
23
23
|
# prism — the validator
|
|
24
24
|
|
|
25
|
+
**MCP scope — every `mcp__rafinery` call:** OMIT `repo`; your key IS the repo
|
|
26
|
+
scope and the server derives it. Where a value is explicitly needed, it is the
|
|
27
|
+
committed `rafa.json → repoId` — NEVER a folder name or repo-name guess.
|
|
28
|
+
|
|
25
29
|
You are **prism**, an independent QA engineer and the trust anchor of every rafa
|
|
26
30
|
gate. Adversarial by mandate: your job is to find what's wrong, not to bless what's
|
|
27
31
|
there. You review the **artifact + the ground truth, never the producer's claims**;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: sage
|
|
3
|
-
version: 0.2.
|
|
3
|
+
version: 0.2.1
|
|
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: >-
|
|
@@ -24,6 +24,10 @@ duties:
|
|
|
24
24
|
|
|
25
25
|
# sage — the silent L5 observer
|
|
26
26
|
|
|
27
|
+
**MCP scope — every `mcp__rafinery` call:** OMIT `repo`; your key IS the repo
|
|
28
|
+
scope and the server derives it. Where a value is explicitly needed, it is the
|
|
29
|
+
committed `rafa.json → repoId` — NEVER a folder name or repo-name guess.
|
|
30
|
+
|
|
27
31
|
You are **sage**, rafa's fifth agent and its **self-improvement loop (L5: system → itself)**.
|
|
28
32
|
Where **atlas** knows the code, **prism** doubts the claims, **bloom** raises the code floor,
|
|
29
33
|
and **compass** coaches the dev — **you raise the floor of the agents themselves.** Your subject
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
version: 2.0.
|
|
2
|
+
version: 2.0.2
|
|
3
3
|
description: rafa — the repo's engineering SOP. Use whenever the dev wants to plan a feature or change, build/execute against the active plan, improve code health, or ground work in the repo's brain — intent counts, the dev does NOT need to type /rafa. Also explicit: /rafa <init|scan|improve|plan|build|push|leverage|migrate|update|help> [--brain-only]. Admin verbs (init/scan/push/migrate/update) run ONLY when explicitly invoked.
|
|
4
4
|
---
|
|
5
5
|
|
|
@@ -123,6 +123,13 @@ Dismissible as a unit; offer fatigue trains reflexive yes, which corrodes every
|
|
|
123
123
|
rung above it. Open gaps are one more line in this ONE digest — a pullable source, never
|
|
124
124
|
a serial prompt and never mid-flow.
|
|
125
125
|
|
|
126
|
+
**Exception — a `[rafa · doctor]` digest line is NOT dismissible-later.** It means the
|
|
127
|
+
capture machinery itself is broken (unwired sensor, missing/foreign git hook, a recent
|
|
128
|
+
swallowed failure): run `npx -y @rafinery/cli doctor` FIRST and present its named fixes
|
|
129
|
+
to the dev as concrete next steps before knowledge work — every task done on dead
|
|
130
|
+
sensors is knowledge silently lost. This fires on new sessions AND resumes (the
|
|
131
|
+
SessionStart hook covers both).
|
|
132
|
+
|
|
126
133
|
**The M5 sensors feed you — you never poll.** Four deterministic instruments run outside
|
|
127
134
|
the model (blueprint hooks + the git boundary): the SessionStart digest injects
|
|
128
135
|
staleness/conflicts/corrections/active-plan at session start · every Edit/Write is
|
|
@@ -199,6 +206,17 @@ Two destinations, one rule — route by what the observation is ABOUT:
|
|
|
199
206
|
> **Every verb that writes `.md` is under the OKF surface** (contract §11): files
|
|
200
207
|
> self-describe, body links are bundle-relative markdown, `rafa push` materializes
|
|
201
208
|
> the rest — protocol + the two declared exceptions live in [rafa-okf](../skills/rafa-okf/SKILL.md).
|
|
209
|
+
>
|
|
210
|
+
> **MCP scope — every `mcp__rafinery` call:** omit `repo` (the key IS the scope;
|
|
211
|
+
> the server derives it); where explicitly needed it is `rafa.json → repoId`,
|
|
212
|
+
> never a folder-name guess. Mid-session 401 after a key rotation = this session
|
|
213
|
+
> holds the OLD connect-time key — have the dev reconnect the rafinery MCP (or
|
|
214
|
+
> restart); CLI tools reload credentials per call and keep working.
|
|
215
|
+
>
|
|
216
|
+
> **Fixed-in-passing is REPORTED, always:** any ledger improvement fixed during
|
|
217
|
+
> ANY work — inside or outside a /rafa verb — gets `report_improvement_status`
|
|
218
|
+
> at the fix boundary. Hand-edits never flip ledger status; an unreported fix
|
|
219
|
+
> leaves bloom's ledger lying until the next full pass.
|
|
202
220
|
|
|
203
221
|
Each work/admin verb dispatches to its SOP. Read the skill only when the verb runs; it is
|
|
204
222
|
self-contained and ADR-shaped. **You orchestrate + push on approval; the subagent follows
|
|
@@ -47,6 +47,7 @@ doesn't validate, compile fails.
|
|
|
47
47
|
| improvement | `improve/improvements/*.md` | **structured** | §3 | bloom |
|
|
48
48
|
| ledger | `improve/ledger.md` | **structured** | §5 | bloom |
|
|
49
49
|
| plan | `plans/**/*.md` | **structured** | §7 | plan/build |
|
|
50
|
+
| reconciliation report | `brain/reconciliations/*.md` | **structured** (validated, NOT in the manifest — travels on the brain-repo transport, like plans) | `run` (merge sha) · `outcome` ∈ `succeeded`\|`needs-attention`\|`superseded` · `tier` ∈ `canonical`\|`provisional`; refutation/deletion records ride INSIDE (body sections — one class); minted via `rafa okf new reconciliation-report` | distiller |
|
|
50
51
|
| log | `brain/log.md` | **verbatim** | — (prose trail; OKF-reserved name, §11) | conductor |
|
|
51
52
|
| index | `**/index.md` | **generated** | — (OKF §6 listings, by `rafa okf`; the ROOT index alone carries frontmatter: `okf_version` + provenance) | tool |
|
|
52
53
|
| citation-check | `**/citation-check.md` | **generated** | — (by `rafa verify-citations`; self-describing frontmatter, §11) | tool |
|
|
@@ -500,7 +501,11 @@ match.
|
|
|
500
501
|
| `get_plan` | `repo`, `id` (parent or child) | parent + all children **with stored bodies** + **derived** progress; a child id resolves the whole plan with `requestedChild` set — this is what "load plan X" materializes into `.rafa/plans/` |
|
|
501
502
|
| `get_active_plan` | `repo` | resolves the channel pointer (`set_active_plan`) → `get_plan`, or "no active plan" |
|
|
502
503
|
|
|
503
|
-
`repo`
|
|
504
|
+
`repo` is OPTIONAL on every tool — the scope is DERIVED from the per-repo key
|
|
505
|
+
(the key IS the repo identity; a derivation from an authenticated fact, never an
|
|
506
|
+
assumed value). When provided it must match the registered id exactly — loud
|
|
507
|
+
mismatch, never a fallback. Sessions that pass it read the id from the committed
|
|
508
|
+
`rafa.json` `repoId`, never a folder name. `branch` is reserved (branch-keyed
|
|
504
509
|
instances land in Slice 2 — third-party clients default to the production brain,
|
|
505
510
|
i.e. the default branch's instance).
|
|
506
511
|
|
|
@@ -538,7 +543,13 @@ intent degrades to search; a wrong intent would answer the wrong thing.
|
|
|
538
543
|
silent throttle.
|
|
539
544
|
|
|
540
545
|
**Auth:** per-repo machine keys, minted on the platform, sent as
|
|
541
|
-
`Authorization: Bearer`.
|
|
546
|
+
`Authorization: Bearer`. **Rotation is zero-downtime by design:** keys COEXIST —
|
|
547
|
+
mint the replacement, update the configs (settings.local.json · credentials.json ·
|
|
548
|
+
CI secrets), then revoke the old at leisure; revoke stays an immediate kill (a
|
|
549
|
+
security act, never graced). A live session holds its connect-time key — reconnect
|
|
550
|
+
the rafinery MCP server (or restart the session) after swapping; the CLI tools
|
|
551
|
+
(checkpoint · hydrate · distill) resolve credentials fresh per invocation and are
|
|
552
|
+
never stale. Keys are stored hashed platform-side and live client-side in
|
|
542
553
|
`~/.config/rafinery/credentials.json` — never inside the code repo or this brain repo.
|
|
543
554
|
|
|
544
555
|
**Client wiring (written by `rafa init`, secret-free where committed):** a key is
|
|
@@ -551,7 +562,7 @@ minted at setup generation and delivered consume-once through the setup fetch;
|
|
|
551
562
|
### §9 addendum — state-plane tools (three-store model, working-set architecture)
|
|
552
563
|
|
|
553
564
|
The knowledge plane stays strictly read-only (org-brain writes go ONLY through
|
|
554
|
-
files → compile → push → ingest).
|
|
565
|
+
files → compile → push → ingest). Additional tools write **collaboration
|
|
555
566
|
state**, marked by `envelope.plane: "state"`:
|
|
556
567
|
|
|
557
568
|
| Tool | Store | Notes |
|
|
@@ -563,6 +574,11 @@ state**, marked by `envelope.plane: "state"`:
|
|
|
563
574
|
| `fold_working_set` | branch working set | branch→parent-branch MECHANICAL fold (no LLM, no prism — rigor only at main): absent → re-keyed · identical → merged · divergent → `needs-adjudication` on the parent row |
|
|
564
575
|
| `push_plan` / `list_plans` / `update_plan_status` / `set_active_plan` / `log_decision` | **plans** — (repo) work objects | the push-on-approval channel (§7 v2): approval pushes the whole work-item TREE (items[], bodies stored); `list_plans` = names only; statuses push back (5-value enum — blocked is derived); `log_decision` records the deliberation trail (paraphrase + pivotal quotes); the active pointer is explicit, never inferred |
|
|
565
576
|
| `report_improvement_status` | **events only** — no store | a LIVE session signal (bloom's fixed-in-passing during build). The improvement ledger row is untouched — it has ONE writer, the ingest (K1); the platform overlays the report as pending-reconciliation until the next brain push confirms it |
|
|
577
|
+
| `reconcile_claim` | **reconciliations queue** — (repo) run rows | executor claim (`plane:"state"`): ACID no-running-sibling assert → returns the attempt token; delegates to `reconciliations.claim`; stamps the actor envelope `{model, agent, runner}` (runner ∈ `sandbox`\|`ci`\|`session`, enum-checked at the boundary) on the row |
|
|
578
|
+
| `reconcile_heartbeat` | reconciliations queue | extends the wall-clock lease + sets the phase; **attempt-fenced** (a non-current attempt is rejected, nothing written); actor envelope re-stamped |
|
|
579
|
+
| `reconcile_log_append` | **reconciliationLogs** — the run log tail | batched chunks, **secret-screened PER LINE** (the same `looksLikeSecret` screen — a credential chunk is rejected by chunk/line index, nothing stored or echoed) + **attempt-fenced**; `seq`-ordered; UNLOGGED (emits its own `reconcile.log` event, counts only) |
|
|
580
|
+
| `reconcile_report` | reconciliations queue (+ knowledge node on success) | terminal outcome + node delta + meter; **attempt-fenced**; success COMPOSES the pointer advance atomically (insert node + move pointer, one mutation); refutation/deletion ride the success outcomes; actor envelope stamped |
|
|
581
|
+
| `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 |
|
|
566
582
|
|
|
567
583
|
State tools work with or without an ingested brain (a working set can exist —
|
|
568
584
|
and a plan can be pushed — before the first scan) and are exempt from the
|
|
@@ -571,6 +587,18 @@ snapshot/ingestError gates. `get_plan`/`get_active_plan` share this exemption
|
|
|
571
587
|
bucket-note tools (`stage/list/resolve_bucket_note`, pre-0.4.0) are superseded
|
|
572
588
|
by the working-set tools.
|
|
573
589
|
|
|
590
|
+
**Typed candidates (capture-engine P2, additive).** A working-set row's KIND
|
|
591
|
+
derives from its path — never from a parallel field that could drift:
|
|
592
|
+
`brain/rules|playbooks/**` = **note** (§2, judged on "does the claim hold?");
|
|
593
|
+
`improve/improvements/**` = **improvement** (§3, judged on the LIFECYCLE —
|
|
594
|
+
"is the defect actually gone / does it actually exist on merged main?"; the
|
|
595
|
+
row resolution stays the standard enum, the verdict note carries
|
|
596
|
+
`confirmed-fixed` / `still-open` / `opened`); `intent/**` = **intent record**
|
|
597
|
+
(the post-commit hook's per-commit provenance — consumed at merge, never
|
|
598
|
+
judged, never authored into the org brain). `improve/ledger.md` is DERIVED
|
|
599
|
+
and never a candidate. schemaVersion stays 1 — this section is purely
|
|
600
|
+
additive (§8).
|
|
601
|
+
|
|
574
602
|
## 10. Agent cards — `../.claude/agents/*.md` (local gate)
|
|
575
603
|
|
|
576
604
|
The shipped agents are contract-governed artifacts: every card is validated by
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
// rafa brain-commit — the post-commit worker (capture-engine P1, spec r2 §2.2).
|
|
2
|
+
// ONE brain commit per code commit, strict 1-1 (--allow-empty), on the MIRRORED
|
|
3
|
+
// brain branch — never the brain default branch (the distiller's, single
|
|
4
|
+
// writer). Carries whatever changed under .rafa/ plus the commit's INTENT
|
|
5
|
+
// RECORD (intent/<shortsha>.md); trailers code-commit/code-branch are the join
|
|
6
|
+
// keys. Standalone by design (node built-ins only, like every M5 sensor);
|
|
7
|
+
// non-blocking always — a brain problem must never block a code commit.
|
|
8
|
+
|
|
9
|
+
import { execSync } from "node:child_process";
|
|
10
|
+
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
|
|
11
|
+
import { join } from "node:path";
|
|
12
|
+
|
|
13
|
+
const ROOT = process.cwd();
|
|
14
|
+
try {
|
|
15
|
+
if (process.env.RAFA_HOOKS_DISABLED === "1") process.exit(0);
|
|
16
|
+
if (!existsSync(join(ROOT, "rafa.json"))) process.exit(0);
|
|
17
|
+
if (!existsSync(join(ROOT, ".rafa", ".git"))) process.exit(0);
|
|
18
|
+
|
|
19
|
+
const sh = (cmd, cwd = ROOT) =>
|
|
20
|
+
execSync(cmd, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
|
|
21
|
+
const rafa = join(ROOT, ".rafa");
|
|
22
|
+
const shR = (cmd) => sh(cmd, rafa);
|
|
23
|
+
|
|
24
|
+
const branch = sh("git rev-parse --abbrev-ref HEAD");
|
|
25
|
+
// Trunk commits never mirror — the brain default branch has one writer, the
|
|
26
|
+
// distiller. Conservative charset guard: a refname git allows but a shell
|
|
27
|
+
// might mangle is skipped, never quoted-and-hoped.
|
|
28
|
+
if (branch === "main" || branch === "master" || branch === "HEAD") process.exit(0);
|
|
29
|
+
if (!/^[A-Za-z0-9._/-]+$/.test(branch)) process.exit(0);
|
|
30
|
+
|
|
31
|
+
// Lockstep ensure (post-checkout's job, repeated here belt-and-braces): dirty
|
|
32
|
+
// surfaces carry to the OLD branch first — deterministic, nothing lost.
|
|
33
|
+
const cur = shR("git rev-parse --abbrev-ref HEAD");
|
|
34
|
+
if (cur !== branch) {
|
|
35
|
+
try {
|
|
36
|
+
shR("git add -A");
|
|
37
|
+
shR(`git commit -q -m "brain(switch-carryover)" -m "switch-carryover-from: ${cur}"`);
|
|
38
|
+
} catch {
|
|
39
|
+
/* nothing dirty */
|
|
40
|
+
}
|
|
41
|
+
try {
|
|
42
|
+
shR(`git rev-parse --verify -q "refs/heads/${branch}"`);
|
|
43
|
+
shR(`git checkout -q "${branch}"`);
|
|
44
|
+
} catch {
|
|
45
|
+
shR(`git checkout -q -b "${branch}"`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// The intent record — the commit's end-to-end intent, mechanically joined.
|
|
50
|
+
// Minimal here (sha · subject · files); the P3 capture worker enriches.
|
|
51
|
+
const fullSha = sh("git rev-parse HEAD");
|
|
52
|
+
const short = fullSha.slice(0, 12);
|
|
53
|
+
const clean = (s) => s.replace(/[`"$\\]/g, "'");
|
|
54
|
+
const subject = clean(sh("git log -1 --pretty=%s")).slice(0, 100);
|
|
55
|
+
const files = sh("git diff-tree --no-commit-id --name-only -r HEAD")
|
|
56
|
+
.split("\n")
|
|
57
|
+
.filter(Boolean)
|
|
58
|
+
.slice(0, 100);
|
|
59
|
+
mkdirSync(join(rafa, "intent"), { recursive: true });
|
|
60
|
+
writeFileSync(
|
|
61
|
+
join(rafa, "intent", `${short}.md`),
|
|
62
|
+
`---\n` +
|
|
63
|
+
`type: IntentRecord\n` +
|
|
64
|
+
`code-commit: ${fullSha}\n` +
|
|
65
|
+
`code-branch: ${branch}\n` +
|
|
66
|
+
`timestamp: ${new Date().toISOString()}\n` +
|
|
67
|
+
`---\n\n# ${subject}\n\n## Files\n` +
|
|
68
|
+
files.map((f) => `- ${f}`).join("\n") +
|
|
69
|
+
`\n`,
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
shR("git add -A");
|
|
73
|
+
shR(
|
|
74
|
+
`git commit --allow-empty -q -m "brain(${branch}): ${subject}" ` +
|
|
75
|
+
`-m "code-commit: ${fullSha}" -m "code-branch: ${branch}"`,
|
|
76
|
+
);
|
|
77
|
+
} catch {
|
|
78
|
+
/* silent by design — the heartbeat carries sensor health */
|
|
79
|
+
}
|
|
80
|
+
process.exit(0);
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
// rafa brain-map — the post-rewrite worker (capture-engine P1, spec r2 §2.2).
|
|
2
|
+
// Rebase/amend rewrite the CODE shas; git hands the old→new map on stdin. We
|
|
3
|
+
// APPEND it to the mirrored brain branch (maps/rewrite-<ts>.json) — brain
|
|
4
|
+
// history is never rewritten. Collection keys on the merge event's branch
|
|
5
|
+
// name; the map chain restores per-commit grounding through code-commit
|
|
6
|
+
// trailers. argv[2]: "amend" | "rebase" (git's post-rewrite kind).
|
|
7
|
+
|
|
8
|
+
import { execSync } from "node:child_process";
|
|
9
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
10
|
+
import { join } from "node:path";
|
|
11
|
+
|
|
12
|
+
const ROOT = process.cwd();
|
|
13
|
+
try {
|
|
14
|
+
if (process.env.RAFA_HOOKS_DISABLED === "1") process.exit(0);
|
|
15
|
+
if (!existsSync(join(ROOT, "rafa.json"))) process.exit(0);
|
|
16
|
+
if (!existsSync(join(ROOT, ".rafa", ".git"))) process.exit(0);
|
|
17
|
+
|
|
18
|
+
const map = readFileSync(0, "utf8")
|
|
19
|
+
.split("\n")
|
|
20
|
+
.filter(Boolean)
|
|
21
|
+
.map((l) => {
|
|
22
|
+
const [oldSha, newSha] = l.trim().split(/\s+/);
|
|
23
|
+
return { old: oldSha, new: newSha };
|
|
24
|
+
})
|
|
25
|
+
.filter((m) => /^[0-9a-f]{40,64}$/i.test(m.old ?? "") && /^[0-9a-f]{40,64}$/i.test(m.new ?? ""));
|
|
26
|
+
if (map.length === 0) process.exit(0);
|
|
27
|
+
|
|
28
|
+
const sh = (cmd, cwd = ROOT) =>
|
|
29
|
+
execSync(cmd, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
|
|
30
|
+
const rafa = join(ROOT, ".rafa");
|
|
31
|
+
const shR = (cmd) => sh(cmd, rafa);
|
|
32
|
+
|
|
33
|
+
const branch = sh("git rev-parse --abbrev-ref HEAD");
|
|
34
|
+
if (branch === "main" || branch === "master" || branch === "HEAD") process.exit(0);
|
|
35
|
+
if (!/^[A-Za-z0-9._/-]+$/.test(branch)) process.exit(0);
|
|
36
|
+
|
|
37
|
+
const cur = shR("git rev-parse --abbrev-ref HEAD");
|
|
38
|
+
if (cur !== branch) {
|
|
39
|
+
try {
|
|
40
|
+
shR("git add -A");
|
|
41
|
+
shR(`git commit -q -m "brain(switch-carryover)" -m "switch-carryover-from: ${cur}"`);
|
|
42
|
+
} catch {
|
|
43
|
+
/* nothing dirty */
|
|
44
|
+
}
|
|
45
|
+
try {
|
|
46
|
+
shR(`git rev-parse --verify -q "refs/heads/${branch}"`);
|
|
47
|
+
shR(`git checkout -q "${branch}"`);
|
|
48
|
+
} catch {
|
|
49
|
+
shR(`git checkout -q -b "${branch}"`);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const kind = process.argv[2] === "amend" ? "amend" : "rebase";
|
|
54
|
+
mkdirSync(join(rafa, "maps"), { recursive: true });
|
|
55
|
+
writeFileSync(
|
|
56
|
+
join(rafa, "maps", `rewrite-${Date.now()}.json`),
|
|
57
|
+
JSON.stringify({ kind, at: new Date().toISOString(), branch, map }, null, 2) + "\n",
|
|
58
|
+
);
|
|
59
|
+
shR("git add -A");
|
|
60
|
+
shR(`git commit -q -m "brain(rewrite-map): ${kind} · ${map.length} commit(s)" -m "code-branch: ${branch}"`);
|
|
61
|
+
} catch {
|
|
62
|
+
/* silent by design */
|
|
63
|
+
}
|
|
64
|
+
process.exit(0);
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// rafa brain-switch — the post-checkout worker (capture-engine P1, spec r2
|
|
2
|
+
// §2.2). Keeps the nested .rafa/ repo's branch in LOCKSTEP with the code
|
|
3
|
+
// branch: code branch cut/switch ⇒ brain branch cut/switch (created lazily
|
|
4
|
+
// from the current local state). Dirty brain surfaces are committed to the OLD
|
|
5
|
+
// branch first (switch-carryover) — deterministic, nothing lost, nothing
|
|
6
|
+
// blocks. argv: <prevHEAD> <newHEAD> <flag> (git's post-checkout contract;
|
|
7
|
+
// flag "1" = branch checkout, "0" = file checkout → no-op).
|
|
8
|
+
|
|
9
|
+
import { execSync } from "node:child_process";
|
|
10
|
+
import { existsSync } from "node:fs";
|
|
11
|
+
import { join } from "node:path";
|
|
12
|
+
|
|
13
|
+
const ROOT = process.cwd();
|
|
14
|
+
try {
|
|
15
|
+
if (process.env.RAFA_HOOKS_DISABLED === "1") process.exit(0);
|
|
16
|
+
if (process.argv[4] !== "1") process.exit(0);
|
|
17
|
+
if (!existsSync(join(ROOT, "rafa.json"))) process.exit(0);
|
|
18
|
+
if (!existsSync(join(ROOT, ".rafa", ".git"))) process.exit(0);
|
|
19
|
+
|
|
20
|
+
const sh = (cmd, cwd = ROOT) =>
|
|
21
|
+
execSync(cmd, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
|
|
22
|
+
const shR = (cmd) => sh(cmd, join(ROOT, ".rafa"));
|
|
23
|
+
|
|
24
|
+
const branch = sh("git rev-parse --abbrev-ref HEAD");
|
|
25
|
+
if (branch === "HEAD") process.exit(0); // detached — no mirror
|
|
26
|
+
if (!/^[A-Za-z0-9._/-]+$/.test(branch)) process.exit(0);
|
|
27
|
+
|
|
28
|
+
const cur = shR("git rev-parse --abbrev-ref HEAD");
|
|
29
|
+
if (cur === branch) process.exit(0);
|
|
30
|
+
try {
|
|
31
|
+
shR("git add -A");
|
|
32
|
+
shR(`git commit -q -m "brain(switch-carryover)" -m "switch-carryover-from: ${cur}"`);
|
|
33
|
+
} catch {
|
|
34
|
+
/* nothing dirty */
|
|
35
|
+
}
|
|
36
|
+
// Trunk mirrors to the brain repo's SAME-NAMED branch when it exists — the
|
|
37
|
+
// default branch stays the distiller's write target; the session only ever
|
|
38
|
+
// reads there. A missing trunk-named brain branch is left alone.
|
|
39
|
+
try {
|
|
40
|
+
shR(`git rev-parse --verify -q "refs/heads/${branch}"`);
|
|
41
|
+
shR(`git checkout -q "${branch}"`);
|
|
42
|
+
} catch {
|
|
43
|
+
if (branch !== "main" && branch !== "master") shR(`git checkout -q -b "${branch}"`);
|
|
44
|
+
}
|
|
45
|
+
} catch {
|
|
46
|
+
/* silent by design */
|
|
47
|
+
}
|
|
48
|
+
process.exit(0);
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
# rafa post-checkout hook v1 (installed by @rafinery/cli init/pull/update).
|
|
3
|
+
#
|
|
4
|
+
# Capture-engine P1: brain-branch LOCKSTEP — code branch cut/switch mirrors in
|
|
5
|
+
# the nested .rafa/ repo; dirty brain surfaces carry to the old branch first.
|
|
6
|
+
# Non-blocking and silent on every path.
|
|
7
|
+
[ "$RAFA_HOOKS_DISABLED" = "1" ] && exit 0
|
|
8
|
+
[ -f "rafa.json" ] || exit 0
|
|
9
|
+
node ".claude/rafa/hooks/brain-switch.mjs" "$1" "$2" "$3" >/dev/null 2>&1 || true
|
|
10
|
+
exit 0
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
# rafa post-commit hook v1 (installed by @rafinery/cli init/pull/update — safe
|
|
3
|
+
# to delete; offered again, never forced over a foreign hook).
|
|
4
|
+
#
|
|
5
|
+
# Capture-engine P1: ONE brain commit per code commit (strict 1-1) on the
|
|
6
|
+
# MIRRORED brain branch, with the commit's intent record inside. Hook-owned —
|
|
7
|
+
# the dev never maintains the brain. Non-blocking and silent on every path.
|
|
8
|
+
[ "$RAFA_HOOKS_DISABLED" = "1" ] && exit 0
|
|
9
|
+
[ -f "rafa.json" ] || exit 0
|
|
10
|
+
node ".claude/rafa/hooks/brain-commit.mjs" >/dev/null 2>&1 || true
|
|
11
|
+
exit 0
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
# rafa post-rewrite hook v1 (installed by @rafinery/cli init/pull/update).
|
|
3
|
+
#
|
|
4
|
+
# Capture-engine P1: rebase/amend safety — git's old→new sha map is APPENDED to
|
|
5
|
+
# the mirrored brain branch (never rewritten); CI resolves code-commit trailers
|
|
6
|
+
# through the chain. Non-blocking and silent on every path.
|
|
7
|
+
[ "$RAFA_HOOKS_DISABLED" = "1" ] && exit 0
|
|
8
|
+
[ -f "rafa.json" ] || exit 0
|
|
9
|
+
node ".claude/rafa/hooks/brain-map.mjs" "$1" >/dev/null 2>&1 || true
|
|
10
|
+
exit 0
|
|
@@ -19,6 +19,28 @@ npx -y @rafinery/cli checkpoint || {
|
|
|
19
19
|
echo "rafa · checkpoint CONFLICT — a teammate's newer copy landed as *.theirs.md; decide in your next session (merge/adopt/keep, re-run rafa checkpoint). Push continues."
|
|
20
20
|
else
|
|
21
21
|
echo "rafa · checkpoint skipped (exit $code) — push continues; run 'rafa checkpoint' manually when convenient."
|
|
22
|
+
# P0: a swallowed failure must still reach the heartbeat — record it so the
|
|
23
|
+
# next checkpoint's report_sensor_health carries it (best-effort, silent).
|
|
24
|
+
mkdir -p .rafa 2>/dev/null && printf '{"t":%s000,"hook":"pre-push","error":"checkpoint exit %s"}\n' "$(date +%s)" "$code" >> .rafa/sensor-errors.jsonl 2>/dev/null || true
|
|
22
25
|
fi
|
|
23
26
|
}
|
|
27
|
+
|
|
28
|
+
# Capture-engine P1 ceremony: durably push the MIRRORED brain branch so capture
|
|
29
|
+
# survives a dead laptop, a closed PR, an abandoned branch. Trunk pushes skip —
|
|
30
|
+
# the brain default branch is the distiller's; sessions never push it. A
|
|
31
|
+
# diverged remote (two clones on one branch) takes a mechanical transport
|
|
32
|
+
# replay; a real conflict is left intact for the next session, never forced.
|
|
33
|
+
BR="$(git rev-parse --abbrev-ref HEAD 2>/dev/null)"
|
|
34
|
+
if [ -n "$BR" ] && [ "$BR" != "main" ] && [ "$BR" != "master" ] && [ -d ".rafa/.git" ]; then
|
|
35
|
+
if git -C .rafa rev-parse --verify -q "refs/heads/$BR" >/dev/null 2>&1; then
|
|
36
|
+
echo "rafa · brain branch push ($BR) …"
|
|
37
|
+
git -C .rafa push -q -u origin "$BR" 2>/dev/null || {
|
|
38
|
+
git -C .rafa pull -q --rebase origin "$BR" 2>/dev/null && git -C .rafa push -q -u origin "$BR" 2>/dev/null || {
|
|
39
|
+
git -C .rafa rebase --abort >/dev/null 2>&1
|
|
40
|
+
echo "rafa · brain branch push skipped (remote diverged on the same file) — the next session reconciles; code push continues."
|
|
41
|
+
printf '{"t":%s000,"hook":"pre-push","error":"brain branch push diverged"}\n' "$(date +%s)" >> .rafa/sensor-errors.jsonl 2>/dev/null || true
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
fi
|
|
45
|
+
fi
|
|
24
46
|
exit 0
|