@rafinery/cli 0.2.2 → 0.4.0
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 +105 -0
- package/README.md +17 -7
- package/bin/rafa.mjs +78 -8
- package/blueprint/.claude/agents/atlas.md +28 -20
- package/blueprint/.claude/agents/bloom.md +15 -8
- package/blueprint/.claude/agents/compass.md +46 -0
- package/blueprint/.claude/agents/prism.md +42 -25
- package/blueprint/.claude/commands/rafa.md +190 -13
- package/blueprint/.claude/rafa/contract.md +482 -0
- package/blueprint/.claude/skills/rafa-build/SKILL.md +76 -0
- package/blueprint/.claude/skills/rafa-distill/SKILL.md +87 -0
- package/blueprint/{.rafa/capabilities/improve.md → .claude/skills/rafa-improve/SKILL.md} +10 -5
- package/blueprint/.claude/skills/rafa-insights/SKILL.md +71 -0
- package/blueprint/{.rafa/capabilities/leverage.md → .claude/skills/rafa-leverage/SKILL.md} +9 -4
- package/blueprint/.claude/skills/rafa-plan/SKILL.md +74 -0
- package/blueprint/{.rafa/capabilities/scan.md → .claude/skills/rafa-scan/SKILL.md} +14 -9
- package/blueprint/{.rafa/capabilities/validate.md → .claude/skills/rafa-validate/SKILL.md} +14 -5
- package/lib/blueprint.mjs +22 -12
- package/lib/brain-repo.mjs +100 -0
- package/lib/checkpoint.mjs +138 -0
- package/lib/ci-setup.mjs +109 -0
- package/lib/claude-config.mjs +5 -5
- package/lib/compile.mjs +6 -21
- package/lib/distill.mjs +237 -0
- package/lib/fold.mjs +53 -0
- package/lib/gate/compile.mjs +549 -0
- package/lib/gate/verify-citations.mjs +156 -0
- package/lib/hydrate.mjs +96 -0
- package/lib/init.mjs +69 -35
- package/lib/leverage/adapters/claude-code.mjs +5 -4
- package/lib/mcp-client.mjs +97 -0
- package/lib/mcp-config.mjs +93 -0
- package/lib/migrations/index.mjs +39 -3
- package/lib/pull.mjs +129 -0
- package/lib/push.mjs +93 -14
- package/lib/releases.mjs +36 -0
- package/lib/verify-citations.mjs +9 -0
- package/lib/working-set.mjs +113 -0
- package/package.json +2 -2
- package/blueprint/.rafa/bin/rafa-compile.mjs +0 -390
- package/blueprint/.rafa/bin/rafa-push.mjs +0 -75
- package/blueprint/.rafa/bin/verify-citations.mjs +0 -153
- package/blueprint/.rafa/capabilities/build.md +0 -38
- package/blueprint/.rafa/capabilities/plan.md +0 -38
- package/blueprint/.rafa/contract.md +0 -303
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: rafa-improve
|
|
3
|
+
description: "rafa SOP — bloom's continuous-improvement pass: multi-lens, cited, prioritized (P0–P3) ledger of the silent rot no gate catches; leverage-ranked, advisory, never nagging. Loaded on /rafa improve."
|
|
4
|
+
---
|
|
5
|
+
|
|
1
6
|
# improve — continuous improvement (capability #2)
|
|
2
7
|
|
|
3
8
|
rafa's second mission: **drive the codebase worst → well-managed, compounding via work
|
|
@@ -42,12 +47,12 @@ Invoke via `/rafa improve`. The brain (#1) must exist — bloom *uses* it as the
|
|
|
42
47
|
- `improvements/<id>.md` — one file per improvement (cited, prioritized, status-tracked).
|
|
43
48
|
- `ledger.md` — generated index: counts by priority/lens/status + the **health/debt trend**.
|
|
44
49
|
Its **frontmatter carries the machine-read `open` · `debt_score` · `by_priority`** (per
|
|
45
|
-
[`.rafa/contract.md`](
|
|
50
|
+
[`.claude/rafa/contract.md`](../../rafa/contract.md) §5) — the platform reads *that*, never the body tables,
|
|
46
51
|
and `rafa compile` cross-checks `open`/`by_priority` against the actual improvement rows.
|
|
47
52
|
- `manifest.json` — regenerated by `rafa compile`; the JSON the platform ingests. Never hand-edit.
|
|
48
53
|
- `citation-check.md` — generated by the checker (improvement cites resolve).
|
|
49
54
|
|
|
50
|
-
**Improvement schema.** The **strict contract is [`.rafa/contract.md`](
|
|
55
|
+
**Improvement schema.** The **strict contract is [`.claude/rafa/contract.md`](../../rafa/contract.md) §3** —
|
|
51
56
|
`rafa compile` rejects violations. `title`, `summary`, `fix` are **required frontmatter** (the
|
|
52
57
|
platform shows them; nothing is scraped from prose). Example *illustrative — from the rafa repo*:
|
|
53
58
|
```yaml
|
|
@@ -89,13 +94,13 @@ convention — see [[rsc-client-boundary]]). Compiles + runs, so no gate catches
|
|
|
89
94
|
whose code is now clean (`status: fixed`); **preserve** the dev's prior triage
|
|
90
95
|
(backlog/wontfix stay).
|
|
91
96
|
5. **Cite-check (the fidelity gate).** Run
|
|
92
|
-
`
|
|
97
|
+
`npx @rafinery/cli verify-citations --root=.rafa/improve --dirs=improvements`. Every cite
|
|
93
98
|
must resolve; **drop or fix any that don't** — a hallucinated improvement is the cardinal sin.
|
|
94
99
|
6. **Ledger + trend.** Regenerate `ledger.md`: counts by priority/lens/status + a **debt
|
|
95
100
|
score** (weighted open improvements) and its trend vs the last run — the compounding made
|
|
96
101
|
visible. Write the machine-read **frontmatter** `open` · `debt_score` · `by_priority`
|
|
97
|
-
([contract §5](
|
|
98
|
-
7. **Contract gate.** Run `
|
|
102
|
+
([contract §5](../../rafa/contract.md)) — the platform reads that.
|
|
103
|
+
7. **Contract gate.** Run `npx @rafinery/cli compile`. Fix every `path · field · rule`
|
|
99
104
|
violation (missing `title`/`summary`/`fix`, bad enum, ledger counts not matching rows) and
|
|
100
105
|
re-run until it **exits 0** and writes `manifest.json`. Validate-and-correct: a
|
|
101
106
|
schema-invalid brain never ships.
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: rafa-insights
|
|
3
|
+
description: "rafa SOP — compass's user brain: bootstrap from the dev's native /insights report, refine with consent, steer back at boundaries; account-scoped, cross-repo, private. Loaded on /rafa insights and routed capture moments."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# insights — the user brain: bootstrap, refine, steer (compass's SOP)
|
|
7
|
+
|
|
8
|
+
> Status: **active (v1 — conductor-mediated).** Invoke via `/rafa insights`
|
|
9
|
+
> (bootstrap/refresh) — capture during work is the conductor's job (rafa.md
|
|
10
|
+
> §capture routes dev-level observations here). Store: `put_dev_insight` /
|
|
11
|
+
> `list_dev_insights` / `remove_dev_insight` (account-scoped, cross-repo).
|
|
12
|
+
|
|
13
|
+
## The creed (owner, verbatim — non-negotiable)
|
|
14
|
+
**"We are not stealing their knowledge; we are working with them to refine
|
|
15
|
+
their knowledge to do bigger things in better way."** Partnership, not
|
|
16
|
+
extraction. The dev is the beneficiary AND co-author of their own refinement.
|
|
17
|
+
The measure of this work is the dev doing bigger things better — never the dev
|
|
18
|
+
becoming legible to anyone else.
|
|
19
|
+
|
|
20
|
+
## Honesty about tenancy (pre-orgs)
|
|
21
|
+
Until org accounts land, teammates' keys live under the repo owner's platform
|
|
22
|
+
account: MCP scoping keeps one KEY from reading another's insights, but the
|
|
23
|
+
ACCOUNT owner can see data stored under their account on the platform. When
|
|
24
|
+
the key isn't the account owner's, the offer says so: *"banked privately to
|
|
25
|
+
your key — note: stored under this repo owner's account until org accounts
|
|
26
|
+
ship."* Consent means informed.
|
|
27
|
+
|
|
28
|
+
## The boundary (both directions, no exceptions)
|
|
29
|
+
- Nothing **code-cited** enters the user brain (code facts → the branch working set →
|
|
30
|
+
org brain via distillation).
|
|
31
|
+
- Nothing **person-scoped** leaves it — not to teammates, not to repo activity
|
|
32
|
+
feeds, not to aggregates without explicit standing consent. This floor is
|
|
33
|
+
enforced in the platform dispatch, not just here.
|
|
34
|
+
|
|
35
|
+
## Procedure
|
|
36
|
+
|
|
37
|
+
### 1. Bootstrap (first run, or on request)
|
|
38
|
+
Read the dev's native usage report (Claude Code `/insights` — ask the dev to
|
|
39
|
+
run it if absent; its report lands at `~/.claude/usage-data/report.html`).
|
|
40
|
+
**Distill with judgment — never parse the HTML mechanically** (its shape is
|
|
41
|
+
unversioned; the model reads it like a document). Extract dev-level signals
|
|
42
|
+
only: asking/decomposition patterns, repeated instructions (each is a candidate
|
|
43
|
+
preference), friction loops, unused leverage. For each candidate: show the dev,
|
|
44
|
+
**offer** — banked only on yes (`put_dev_insight`, kind pattern|preference|
|
|
45
|
+
constraint).
|
|
46
|
+
|
|
47
|
+
### 2. Continuous refinement (every session, conductor-routed)
|
|
48
|
+
**Before ANY put: `list_dev_insights` first.** Update the matching existing
|
|
49
|
+
insight (its id) instead of creating a near-copy — and NEVER re-offer an
|
|
50
|
+
insight the dev deleted (deleted rows persist as content-erased tombstones in
|
|
51
|
+
the list precisely so you can honor the refusal: "you removed this before; not
|
|
52
|
+
re-suggesting"). **Never bank a secret**: a stated constraint quoting a
|
|
53
|
+
credential is rephrased value-free before the offer (the platform screens too —
|
|
54
|
+
don't rely on it).
|
|
55
|
+
Capture moments: a correction repeated across sessions · a stated preference ·
|
|
56
|
+
a named constraint · a friction loop. Route: dev-level → offer → bank;
|
|
57
|
+
code-level → the branch working set (not yours — rafa.md §capture routes it). Update beats duplicate: refine the
|
|
58
|
+
existing insight (`put_dev_insight` with its id) rather than accreting near-
|
|
59
|
+
copies.
|
|
60
|
+
|
|
61
|
+
### 3. Steering (the payoff — propose, the dev disposes)
|
|
62
|
+
At natural boundaries only, at most one nudge: surface the insight that changes
|
|
63
|
+
what the dev does next — "you've corrected X three times: want it banked as a
|
|
64
|
+
rule?" · "this decomposition worked before, reuse it?" · "you never use plan
|
|
65
|
+
mode on big changes; it would have caught this." Dismissal is final for the
|
|
66
|
+
session. Never rank, never compare devs, never nag.
|
|
67
|
+
|
|
68
|
+
### 4. Legibility (consent's other half)
|
|
69
|
+
On request, show everything (`list_dev_insights`), correct anything, delete
|
|
70
|
+
anything (`remove_dev_insight`) — no residue, no argument. An insight the dev
|
|
71
|
+
says is wrong IS wrong.
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: rafa-leverage
|
|
3
|
+
description: "rafa SOP — tune the dev's agent toolbox: detect missing/misconfigured/unused permissions, skills, MCP servers; on approval apply exact merges. Loaded on /rafa leverage."
|
|
4
|
+
---
|
|
5
|
+
|
|
1
6
|
# leverage — get more out of the toolbox you already have (capability #3)
|
|
2
7
|
|
|
3
8
|
rafa's third mission: **make the dev's agent toolchain pull its full weight.** rafa already
|
|
@@ -37,11 +42,11 @@ at the fix. This is platform DNA: **leverage what's present; never reinvent it.*
|
|
|
37
42
|
|
|
38
43
|
## Architecture — the ToolchainAdapter seam
|
|
39
44
|
|
|
40
|
-
The engine ([`lib/leverage/engine.mjs`](
|
|
45
|
+
The engine ([`lib/leverage/engine.mjs`](../../../packages/cli/lib/leverage/engine.mjs)) gathers
|
|
41
46
|
light, tool-agnostic repo signals (`repoContext`: stack deps, `.env` files + whether they're
|
|
42
47
|
ignored, declared MCP servers), then runs every **detected** adapter and ranks the tips.
|
|
43
48
|
|
|
44
|
-
Each adapter implements one contract ([`lib/leverage/adapters/index.mjs`](
|
|
49
|
+
Each adapter implements one contract ([`lib/leverage/adapters/index.mjs`](../../../packages/cli/lib/leverage/adapters/index.mjs)):
|
|
45
50
|
|
|
46
51
|
| Member | Shape | Job |
|
|
47
52
|
|---|---|---|
|
|
@@ -57,7 +62,7 @@ list) — the seam is real, the adapters are added when a user actually runs on
|
|
|
57
62
|
|
|
58
63
|
### What the Claude Code adapter checks today
|
|
59
64
|
- **Secrets** — `.env*` present but not gitignored → `P1` (protect the keys before a scan or a commit).
|
|
60
|
-
- **Permissions** —
|
|
65
|
+
- **Permissions** — rafa provisioned but its CLI commands are not allow-listed / no `settings.json`
|
|
61
66
|
→ `P2`, fix = `rafa init`/`update` (which *merge*, never clobber — see the CLI).
|
|
62
67
|
- **MCP** — a stack with no matching server wired (e.g. Convex repo, no `convex` MCP) → `P2`.
|
|
63
68
|
- **Skills** — no project skills captured → `P3`.
|
|
@@ -70,7 +75,7 @@ behind the same contract. The engine and the `rafa leverage` command never chang
|
|
|
70
75
|
## Boundaries
|
|
71
76
|
|
|
72
77
|
- **Committed config only.** Read the repo's `.claude/` (org config). The dev's personal
|
|
73
|
-
`~/.claude/` is private — never inspected. (Mirrors [scan](scan.md)'s privacy line.)
|
|
78
|
+
`~/.claude/` is private — never inspected. (Mirrors [scan](../rafa-scan/SKILL.md)'s privacy line.)
|
|
74
79
|
- **Secrets are off-limits.** Detect that an `.env` is unprotected; **never open it or read a
|
|
75
80
|
value.** Same hard line as the scan guardrail.
|
|
76
81
|
- **Read-only.** leverage itself mutates nothing. The only writes are `rafa init`/`update`
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: rafa-plan
|
|
3
|
+
description: "rafa SOP — brain-grounded, prism-validated decomposition of an intent into contract §7 plan files (each child with a Done-check); plan-lite for small blast radius. Loaded on /rafa plan or plan-shaped intent."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# plan — brain-grounded, prism-validated decomposition (capability #3)
|
|
7
|
+
|
|
8
|
+
> Status: **active.** The first *consumer* of the stores — where the brain starts to
|
|
9
|
+
> pay off. Depends on: brain (#1), ledger (#2). Invoke via `/rafa plan <intent>`.
|
|
10
|
+
|
|
11
|
+
Turn an intent into an approval-gated plan (contract §7 files), *grounded* in the
|
|
12
|
+
brain (don't re-derive), *aware* of the ledger (fold improvement into the work), and
|
|
13
|
+
**validated by prism before the owner ever sees it**.
|
|
14
|
+
|
|
15
|
+
## The trio at plan time
|
|
16
|
+
|
|
17
|
+
Planning is a choreography, not one agent (spec: knowledge-mcp-build-agent):
|
|
18
|
+
|
|
19
|
+
- **atlas drafts** — RECALL the brain slice for the intent's domains through the
|
|
20
|
+
knowledge MCP (`get_coverage` to navigate → `search_knowledge` → `get_rule` /
|
|
21
|
+
`get_playbook`, including non-exemplars), name the BLAST RADIUS from coverage,
|
|
22
|
+
decompose into parent + child plan files. *Recall is automatic — the SOP calls
|
|
23
|
+
the tools; the dev never asks for it. Repo not platform-connected (no `rafinery`
|
|
24
|
+
MCP in the session)? Fall back to reading `.rafa/brain/` files directly — same
|
|
25
|
+
knowledge, just unserved.*
|
|
26
|
+
- **bloom pulls** — `list_improvements` in the blast radius; surface the
|
|
27
|
+
top-leverage open items as optional *"while-you're-here"* child tasks
|
|
28
|
+
(leverage-ranked, dismissible, never blocking).
|
|
29
|
+
- **prism validates the plan itself** — before the approval gate: is every task
|
|
30
|
+
grounded in real brain/code (not hallucinated ground)? does every child carry a
|
|
31
|
+
`## Done-check` (the expected outcome prism will validate execution against)?
|
|
32
|
+
A plan whose children lack Done-checks is REJECTED here — compile never parses
|
|
33
|
+
bodies (invariant #3); this gate is prism's.
|
|
34
|
+
|
|
35
|
+
## Procedure
|
|
36
|
+
|
|
37
|
+
1. **Staleness check** — compare the platform envelope's `brainForSha` against the
|
|
38
|
+
local brain stamp; if the platform is behind, surface "run `rafa push`" (never
|
|
39
|
+
proceed silently on knowledge you know is stale — never block either).
|
|
40
|
+
2. **Recall** (atlas, via MCP) → **decompose** ADR-style: decision + rationale +
|
|
41
|
+
alternatives + risk surface + non-goals; tasks bound to domains with a
|
|
42
|
+
`## Done-check` each.
|
|
43
|
+
3. **Ledger pull** (bloom) → optional leverage tasks in the blast radius.
|
|
44
|
+
4. **Leverage-match** — recommend existing skills/tools/MCP that fit the tasks;
|
|
45
|
+
never plan to hand-roll what a capability already does.
|
|
46
|
+
5. **prism plan-validation** → REJECT/fix loop until clean.
|
|
47
|
+
6. **Approval gate** (owner). Then materialize `plans/<plan>/*.md` per contract §7
|
|
48
|
+
(parent + child-owned files, globally-unique prefixed ids) + `active.md` pointer
|
|
49
|
+
→ `rafa compile` (validate the files) → **`push_plan` + `set_active_plan`
|
|
50
|
+
immediately, no second prompt — plan approval IS the push trigger** (the
|
|
51
|
+
dedicated plans channel; plans never ride the brain manifest). The dev just
|
|
52
|
+
approved this exact content; a connected repo renders it on the platform
|
|
53
|
+
within moments, resumable from ANY session, machine, or teammate — *"list
|
|
54
|
+
plans"* shows names only; *"load plan X"* (`get_plan`) materializes the full
|
|
55
|
+
plan (bodies included) back into `.rafa/plans/`.
|
|
56
|
+
|
|
57
|
+
## plan-lite — the light path (a mode of THIS SOP, not a bypass)
|
|
58
|
+
The full choreography earns its weight on cross-cutting work; a one-file change routed
|
|
59
|
+
through five steps teaches devs to route around rafa — and a route-around is a product
|
|
60
|
+
failure. So the conductor weighs the **blast radius** (from coverage at recall time):
|
|
61
|
+
|
|
62
|
+
- **Lite** (radius ≤ 2 domains and no contract/schema surface touched): ONE parent +
|
|
63
|
+
ONE child file, recall scoped to the touched domains, bloom pull skipped, prism gate
|
|
64
|
+
collapses to the two invariants that never relax — every task grounded, the child
|
|
65
|
+
carries a `## Done-check`. Same contract §7 files, same compile, same approval.
|
|
66
|
+
- **Full** (anything wider): the complete procedure above.
|
|
67
|
+
|
|
68
|
+
The dev can force either: `/rafa plan` always offers the choice when the radius is
|
|
69
|
+
borderline; "skip" bypasses planning entirely (their call — note it, never police it).
|
|
70
|
+
|
|
71
|
+
## Deferred / open
|
|
72
|
+
- Capture-back of plan-time decisions → brain (needs the capture engine).
|
|
73
|
+
- Pivot detection (mark superseded; the path is data).
|
|
74
|
+
- Plan lifecycle + tiering (cap, archive→remote, restore).
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: rafa-scan
|
|
3
|
+
description: "rafa SOP — atlas's founding scan: map the whole codebase into cited rules/playbooks + coverage, breadth before depth, every citation mechanically verified. Loaded when the /rafa conductor runs scan or init; atlas follows it verbatim."
|
|
4
|
+
---
|
|
5
|
+
|
|
1
6
|
# Scan — capability
|
|
2
7
|
|
|
3
8
|
atlas's founding contribution to the brain: read the **whole** codebase and write its
|
|
@@ -63,7 +68,7 @@ A well-formed note with a wrong citation is worse than no note — a dev greps t
|
|
|
63
68
|
file, finds nothing, and stops trusting the brain. So fidelity is non-negotiable:
|
|
64
69
|
|
|
65
70
|
- **Every citation is mechanically checked — by script, all of them.** Write each cite as
|
|
66
|
-
`file:line :: token`, then run `
|
|
71
|
+
`file:line :: token`, then run `npx @rafinery/cli verify-citations`, which asserts
|
|
67
72
|
every cited line contains its token and exits non-zero on any miss. Fix every ✗ until it
|
|
68
73
|
exits 0. A "spot-check" does NOT satisfy this. Off-by-N or wrong-file → fix or drop.
|
|
69
74
|
- **Contract sites come from grep, not memory — and the checker enforces it.** Every
|
|
@@ -87,10 +92,10 @@ file, finds nothing, and stops trusting the brain. So fidelity is non-negotiable
|
|
|
87
92
|
- `.rafa/brain/rules/*.md` — `convention` + `contract` notes ("what not to break")
|
|
88
93
|
- `.rafa/brain/playbooks/*.md` — `flow` + `how-to` notes ("how it works / how to add X")
|
|
89
94
|
- `.rafa/brain/coverage.md` — the coverage report. **Machine-read frontmatter** per
|
|
90
|
-
[`.rafa/contract.md`](
|
|
95
|
+
[`.claude/rafa/contract.md`](../../rafa/contract.md) §6: `domains: { <domain>: mapped|thin|stubbed|empty, … }`
|
|
91
96
|
— one entry per domain from Step 1. The body keeps the per-criterion PASS/FAIL narrative.
|
|
92
97
|
|
|
93
|
-
**Note format.** The **strict contract is [`.rafa/contract.md`](
|
|
98
|
+
**Note format.** The **strict contract is [`.claude/rafa/contract.md`](../../rafa/contract.md) §2** — every
|
|
94
99
|
required field there is mandatory and `rafa compile` (Step 7) **rejects** any violation with a
|
|
95
100
|
`path · field · rule` error you must fix. Machine-read fields live in frontmatter; the body is
|
|
96
101
|
prose (never parsed). The example is *illustrative — a contract from the rafa reference repo*:
|
|
@@ -173,7 +178,7 @@ retrieval index. Bodies read like a senior engineer explaining that one concept
|
|
|
173
178
|
|
|
174
179
|
7. **Verification & synthesis** — apply the 100× filter (a note earns a file only if it
|
|
175
180
|
answers a work-time question AND eliminates a recurring tax), then write the notes. Run
|
|
176
|
-
the **deterministic citation checker** — `
|
|
181
|
+
the **deterministic citation checker** — `npx @rafinery/cli verify-citations` — which
|
|
177
182
|
verifies every `file:line :: token` cite resolves (B1) AND that every contract `anchor:`'s
|
|
178
183
|
code occurrences are all cited (B2); fix every ✗ and re-run until it **exits 0**. It
|
|
179
184
|
writes `.rafa/brain/citation-check.md` — **reference that file** from `coverage.md`,
|
|
@@ -182,8 +187,8 @@ retrieval index. Bodies read like a senior engineer explaining that one concept
|
|
|
182
187
|
Step-1 checklist — every domain has ≥1 note or is flagged thin/stubbed; no silent
|
|
183
188
|
truncation). Finalize `coverage.md` with per-criterion PASS/FAIL.
|
|
184
189
|
|
|
185
|
-
Finally, run the **contract gate** — `
|
|
186
|
-
every file against [`.rafa/contract.md`](
|
|
190
|
+
Finally, run the **contract gate** — `npx @rafinery/cli compile` — which validates
|
|
191
|
+
every file against [`.claude/rafa/contract.md`](../../rafa/contract.md) and emits `.rafa/manifest.json`
|
|
187
192
|
(the JSON the platform ingests). It fails loudly with `path · field · rule` errors; **fix
|
|
188
193
|
every one and re-run until it exits 0.** This is the validate-and-correct loop: a
|
|
189
194
|
schema-invalid brain never ships, and the platform never guesses a value. Do not hand-edit
|
|
@@ -211,7 +216,7 @@ A scan **PASSES only if every box below is true.** Any single failure → `itera
|
|
|
211
216
|
- [ ] **A4** Every `thin`/`stubbed`/`empty` domain states why. (a silent gap = FAIL.)
|
|
212
217
|
|
|
213
218
|
**B · Fidelity — verify, don't infer (hard gate)**
|
|
214
|
-
- [ ] **B1** `
|
|
219
|
+
- [ ] **B1** `npx @rafinery/cli verify-citations` **exits 0** — every `file:line :: token`
|
|
215
220
|
cite mechanically verified (all, not a sample); its table pasted in `coverage.md`.
|
|
216
221
|
A spot-check, or a PASS without the checker exiting 0, = FAIL.
|
|
217
222
|
- [ ] **B2** Every `type: contract` declares `anchor:` — a token, or `anchor: none` (with
|
|
@@ -238,9 +243,9 @@ A scan **PASSES only if every box below is true.** Any single failure → `itera
|
|
|
238
243
|
**D · Format & contract**
|
|
239
244
|
- [ ] **D1** Output is exactly `.rafa/brain/rules/` + `.rafa/brain/playbooks/` +
|
|
240
245
|
`.rafa/brain/coverage.md`. **No `graph.json`.**
|
|
241
|
-
- [ ] **D2** Every note has valid frontmatter per [`.rafa/contract.md`](
|
|
246
|
+
- [ ] **D2** Every note has valid frontmatter per [`.claude/rafa/contract.md`](../../rafa/contract.md) §2
|
|
242
247
|
(incl. required `schemaVersion`, `title`, `summary`).
|
|
243
|
-
- [ ] **D3** `
|
|
248
|
+
- [ ] **D3** `npx @rafinery/cli compile` **exits 0** and writes `.rafa/manifest.json`.
|
|
244
249
|
Any `path · field · rule` violation = FAIL — fix and re-run (validate-and-correct).
|
|
245
250
|
|
|
246
251
|
The report must state PASS/FAIL **per criterion** (not a summary verdict), so iteration
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: rafa-validate
|
|
3
|
+
description: "rafa SOP — prism's independent scan QA: re-run every check against the code, adversarial probes, verdict + score to checklist.md. Loaded when prism validates a brain; never trusts the producer's claims."
|
|
4
|
+
---
|
|
5
|
+
|
|
1
6
|
# Validate — prism's SOP (scan QA)
|
|
2
7
|
|
|
3
8
|
Standard operating procedure for **prism** to validate a atlas scan. Independent,
|
|
@@ -20,19 +25,19 @@ agent. So:
|
|
|
20
25
|
|
|
21
26
|
## Inputs / preconditions
|
|
22
27
|
- The brain: `.rafa/brain/` (`rules/`, `playbooks/`, `coverage.md`).
|
|
23
|
-
- The repo (ground truth) + the checker
|
|
28
|
+
- The repo (ground truth) + the checker ``rafa verify-citations` (in @rafinery/cli)`.
|
|
24
29
|
- The bar: `scan.md` § Acceptance criteria (A–D) + the quality rubric below.
|
|
25
30
|
- You run in **fresh context** — you have NOT seen how the scan was produced. Keep it that way.
|
|
26
31
|
|
|
27
32
|
## Procedure (run in order; ground everything in code)
|
|
28
33
|
|
|
29
|
-
1. **Re-run the checker yourself** — `
|
|
34
|
+
1. **Re-run the checker yourself** — `npx @rafinery/cli verify-citations`. Record exit
|
|
30
35
|
code + counts (resolution / completeness / policy). Exit ≠ 0 → **blocker(s)**. Never
|
|
31
36
|
trust a pasted table.
|
|
32
37
|
2. **Trust-but-verify the checker** —
|
|
33
38
|
(a) independently re-verify a sample (~10) of cites against the raw files by a *different*
|
|
34
39
|
method (hand grep / read), to confirm the checker isn't lying.
|
|
35
|
-
(b) **Mutation self-test** — run `
|
|
40
|
+
(b) **Mutation self-test** — run `npx @rafinery/cli verify-citations --selftest`. It
|
|
36
41
|
proves the checker's logic *fails on a bad cite and passes a good one* (on a throwaway
|
|
37
42
|
temp file — no brain/repo pollution, no debris). It must print PASS and **exit 0**.
|
|
38
43
|
Re-running the checker (step 1) is not proof it still works; this is.
|
|
@@ -66,7 +71,10 @@ agent. So:
|
|
|
66
71
|
- **Blocker** — hard-gate failure, broken checker, or a wrong/misleading note. Must be **0**.
|
|
67
72
|
- **Major** — a real gap: missing essence, under-covered domain, unanswerable question.
|
|
68
73
|
- **Minor** — nits (orphan, cosmetic). Logged, not looped on.
|
|
69
|
-
- **PASS** = hard gates pass · quality score ≥ 85 · 0 blockers · majors ≤ 2
|
|
74
|
+
- **PASS** = hard gates pass · quality score ≥ 85 · 0 blockers · majors ≤ 2 · **and no
|
|
75
|
+
unflagged salient-but-wrong exemplar** (an open 5b finding → **auto-ITERATE regardless
|
|
76
|
+
of score** — salience gets copied, so one unflagged exception poisons every cold agent;
|
|
77
|
+
the numeric bar alone must never pass it). Else **ITERATE**.
|
|
70
78
|
|
|
71
79
|
## Quality score (0–100) — only meaningful once hard gates (fidelity + coverage) pass
|
|
72
80
|
| Weight | Dimension | Measure |
|
|
@@ -106,7 +114,8 @@ hard-gate fail write `score: 0` (not `null` — the contract requires an int).
|
|
|
106
114
|
- **Findings ledger** — blocker / major / minor, each cited (`file:line`) + a suggested fix.
|
|
107
115
|
|
|
108
116
|
You write **`checklist.md` only**. The **conductor** owns `log.md` (the round-by-round trail).
|
|
109
|
-
PASS = hard gates pass · score ≥ 85 · 0 blockers · majors ≤ 2
|
|
117
|
+
PASS = hard gates pass · score ≥ 85 · 0 blockers · majors ≤ 2 · no unflagged salient-but-wrong
|
|
118
|
+
exemplar (5b override — auto-ITERATE).
|
|
110
119
|
|
|
111
120
|
## The ratchet
|
|
112
121
|
Any judgment finding you raise repeatedly that *could* be mechanized → recommend it become a
|
package/lib/blueprint.mjs
CHANGED
|
@@ -16,33 +16,43 @@ import { dirname, join } from "node:path";
|
|
|
16
16
|
import { fileURLToPath } from "node:url";
|
|
17
17
|
|
|
18
18
|
// Two classes of blueprint file:
|
|
19
|
-
// - owned → the client tunes these (agents,
|
|
20
|
-
//
|
|
21
|
-
// - lockstep →
|
|
19
|
+
// - owned → the client tunes these (agents, commands, skills). We NEVER clobber a
|
|
20
|
+
// modified copy; a conflict asks (shadcn-style).
|
|
21
|
+
// - lockstep → the contract. It moves with the CLI version (the CLI implements it),
|
|
22
|
+
// so it overwrites.
|
|
23
|
+
// Blueprint split (0.4.0): the gate TOOLS are not here — they live inside this
|
|
24
|
+
// package (lib/gate). Capabilities ship as committed SKILLS (.claude/skills/rafa-*)
|
|
25
|
+
// so behavior versions with the code repo, MR-reviewed and clone-complete. `.rafa/`
|
|
26
|
+
// itself is never vendored — it is lazy, gitignored knowledge+state.
|
|
22
27
|
export const BLUEPRINT = {
|
|
23
28
|
owned: [
|
|
24
29
|
".claude/agents/atlas.md",
|
|
25
30
|
".claude/agents/prism.md",
|
|
26
31
|
".claude/agents/bloom.md",
|
|
32
|
+
".claude/agents/compass.md",
|
|
27
33
|
".claude/commands/rafa.md",
|
|
28
34
|
],
|
|
29
|
-
ownedDirs: [
|
|
30
|
-
|
|
31
|
-
".rafa
|
|
32
|
-
".
|
|
33
|
-
".
|
|
34
|
-
".
|
|
35
|
+
ownedDirs: [
|
|
36
|
+
".claude/skills/rafa-scan",
|
|
37
|
+
".claude/skills/rafa-validate",
|
|
38
|
+
".claude/skills/rafa-plan",
|
|
39
|
+
".claude/skills/rafa-build",
|
|
40
|
+
".claude/skills/rafa-improve",
|
|
41
|
+
".claude/skills/rafa-distill",
|
|
42
|
+
".claude/skills/rafa-insights",
|
|
43
|
+
".claude/skills/rafa-leverage",
|
|
35
44
|
],
|
|
45
|
+
lockstep: [".claude/rafa/contract.md"],
|
|
36
46
|
};
|
|
37
47
|
|
|
38
48
|
const HERE = dirname(fileURLToPath(import.meta.url)); // …/packages/cli/lib
|
|
39
49
|
|
|
40
|
-
// Walk up to the monorepo root (the dir that holds the
|
|
50
|
+
// Walk up to the monorepo root (the dir that holds the canonical contract).
|
|
41
51
|
function findRepoRoot() {
|
|
42
52
|
let d = HERE;
|
|
43
53
|
for (let i = 0; i < 8; i++) {
|
|
44
54
|
d = join(d, "..");
|
|
45
|
-
if (existsSync(join(d, ".rafa", "contract.md"))) return d;
|
|
55
|
+
if (existsSync(join(d, ".claude", "rafa", "contract.md"))) return d;
|
|
46
56
|
}
|
|
47
57
|
return null;
|
|
48
58
|
}
|
|
@@ -52,7 +62,7 @@ function findRepoRoot() {
|
|
|
52
62
|
// - dev in the monorepo → the repo root itself
|
|
53
63
|
export function blueprintSource() {
|
|
54
64
|
const bundled = join(HERE, "..", "blueprint");
|
|
55
|
-
if (existsSync(join(bundled, ".rafa", "contract.md"))) return bundled;
|
|
65
|
+
if (existsSync(join(bundled, ".claude", "rafa", "contract.md"))) return bundled;
|
|
56
66
|
const root = findRepoRoot();
|
|
57
67
|
if (root) return root;
|
|
58
68
|
throw new Error(
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
// The lazy `.rafa/` bootstrap — the committed rafa.json is the anchor.
|
|
2
|
+
//
|
|
3
|
+
// Blueprint split (0.4.0): `.rafa/` is created lazily, gitignored, per working
|
|
4
|
+
// copy — it is NEVER vendored and never travels through the code repo. Any
|
|
5
|
+
// command that needs the brain repo (push/pull/distill) calls ensureBrainRepo(),
|
|
6
|
+
// which materializes the skeleton + git wiring from the committed rafa.json so
|
|
7
|
+
// `clone → npx rafa <cmd>` just works with no init step on the machine.
|
|
8
|
+
|
|
9
|
+
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
|
|
10
|
+
import { execSync } from "node:child_process";
|
|
11
|
+
import { join } from "node:path";
|
|
12
|
+
import { readStamp } from "./stamp.mjs";
|
|
13
|
+
|
|
14
|
+
export const die = (m) => {
|
|
15
|
+
console.error(`✗ ${m}`);
|
|
16
|
+
process.exit(1);
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export const inDir = (dir) => (cmd) =>
|
|
20
|
+
execSync(cmd, { cwd: dir, stdio: ["ignore", "pipe", "pipe"], encoding: "utf8" }).trim();
|
|
21
|
+
|
|
22
|
+
// The brain remote recorded at provision time (committed, secret-free).
|
|
23
|
+
export function brainRemote(cwd) {
|
|
24
|
+
const stamp = readStamp(cwd);
|
|
25
|
+
return typeof stamp.brainRepo === "string" && stamp.brainRepo !== ""
|
|
26
|
+
? stamp.brainRepo
|
|
27
|
+
: null;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Ensure `.rafa/` exists as its own git repo wired to the brain remote.
|
|
31
|
+
// Order of truth: an existing `.rafa/.git` origin wins (a machine wired by an
|
|
32
|
+
// older init keeps working); otherwise bootstrap from the committed rafa.json.
|
|
33
|
+
// Returns { rafaDir, remote, bootstrapped }.
|
|
34
|
+
export function ensureBrainRepo(cwd, { requireRemote = true } = {}) {
|
|
35
|
+
const rafaDir = join(cwd, ".rafa");
|
|
36
|
+
const inRafa = inDir(rafaDir);
|
|
37
|
+
let bootstrapped = false;
|
|
38
|
+
|
|
39
|
+
if (!existsSync(rafaDir)) {
|
|
40
|
+
mkdirSync(rafaDir, { recursive: true });
|
|
41
|
+
bootstrapped = true;
|
|
42
|
+
}
|
|
43
|
+
if (!existsSync(join(rafaDir, ".git"))) {
|
|
44
|
+
inDir(cwd)(`git -C ${JSON.stringify(rafaDir)} init -b main`);
|
|
45
|
+
bootstrapped = true;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
let remote = null;
|
|
49
|
+
try {
|
|
50
|
+
remote = inRafa("git remote get-url origin");
|
|
51
|
+
} catch {
|
|
52
|
+
const fromStamp = brainRemote(cwd);
|
|
53
|
+
if (fromStamp) {
|
|
54
|
+
inRafa(`git remote add origin ${fromStamp}`);
|
|
55
|
+
remote = fromStamp;
|
|
56
|
+
bootstrapped = true;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
if (requireRemote && !remote)
|
|
60
|
+
die(
|
|
61
|
+
"no brain remote — this repo's rafa.json has no `brainRepo` and `.rafa/` has no origin.\n" +
|
|
62
|
+
" Provision via `rafa init <setup-url>` (platform), or add `\"brainRepo\": \"<git url>\"` to rafa.json.",
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
// CI auth (rafa ci-setup): a workflow has no interactive git credentials —
|
|
66
|
+
// RAFA_BRAIN_TOKEN (repo secret) is injected into the https remote for THIS
|
|
67
|
+
// working copy only. Never stored on the rafinery platform, never printed.
|
|
68
|
+
const token = process.env.RAFA_BRAIN_TOKEN;
|
|
69
|
+
if (token && remote?.startsWith("https://") && !remote.includes("@")) {
|
|
70
|
+
inRafa(
|
|
71
|
+
`git remote set-url origin ${remote.replace("https://", `https://x-access-token:${token}@`)}`,
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Session/state debris never rides a brain push: the sidecar, conflict
|
|
76
|
+
// copies, and distill staging are transport-excluded here (not knowledge).
|
|
77
|
+
const ignore = join(rafaDir, ".gitignore");
|
|
78
|
+
const IGNORES = "hydration.json\n*.theirs.md\ndistill-incoming/\ndistill-verdicts.json\n";
|
|
79
|
+
if (!existsSync(ignore)) writeFileSync(ignore, IGNORES);
|
|
80
|
+
|
|
81
|
+
return { rafaDir, remote, bootstrapped };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// The brain repo's default branch (HEAD symref) — never assume "main".
|
|
85
|
+
export function remoteDefaultBranch(rafaDir, fallback = "main") {
|
|
86
|
+
try {
|
|
87
|
+
const symref = inDir(rafaDir)("git ls-remote --symref origin HEAD");
|
|
88
|
+
const m = symref.match(/ref:\s+refs\/heads\/(\S+)\s+HEAD/);
|
|
89
|
+
if (m) return m[1];
|
|
90
|
+
} catch {
|
|
91
|
+
/* unreachable remote — caller decides how loud to be */
|
|
92
|
+
}
|
|
93
|
+
return fallback;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Seed the generated active pointer if absent (state, not knowledge).
|
|
97
|
+
export function ensureActivePointer(rafaDir) {
|
|
98
|
+
const p = join(rafaDir, "active.md");
|
|
99
|
+
if (!existsSync(p)) writeFileSync(p, "# No active plan\n");
|
|
100
|
+
}
|