@rafinery/cli 0.8.15 → 0.10.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.
Files changed (71) hide show
  1. package/CHANGELOG.md +104 -0
  2. package/bin/rafa.mjs +11 -0
  3. package/blueprint/.claude/agents/atlas.md +21 -3
  4. package/blueprint/.claude/agents/bloom.md +2 -2
  5. package/blueprint/.claude/agents/prism.md +42 -3
  6. package/blueprint/.claude/agents/sage.md +2 -1
  7. package/blueprint/.claude/commands/rafa.md +24 -13
  8. package/blueprint/.claude/rafa/contract.md +6 -0
  9. package/blueprint/.claude/rafa/hooks/brain-commit.mjs +62 -16
  10. package/blueprint/.claude/rafa/hooks/session-start.mjs +66 -0
  11. package/blueprint/.claude/skills/rafa-build/SKILL.md +111 -20
  12. package/blueprint/.claude/skills/rafa-improve/SKILL.md +22 -0
  13. package/blueprint/.claude/skills/rafa-leverage/SKILL.md +6 -1
  14. package/blueprint/.claude/skills/rafa-plan/SKILL.md +36 -1
  15. package/blueprint/.claude/skills/rafa-review/SKILL.md +16 -2
  16. package/blueprint/.claude/skills/rafa-sage/SKILL.md +21 -4
  17. package/blueprint/.claude/skills/rafa-scan/SKILL.md +4 -1
  18. package/lib/brain-repo.mjs +1 -1
  19. package/lib/checkpoint.mjs +76 -10
  20. package/lib/distiller/doctrine.mjs +5 -16
  21. package/lib/doctor.mjs +19 -1
  22. package/lib/facts.mjs +130 -0
  23. package/lib/gate/compile.mjs +12 -0
  24. package/lib/gate/verify-citations.mjs +78 -1
  25. package/lib/hydrate.mjs +45 -0
  26. package/lib/init.mjs +9 -1
  27. package/lib/leverage/engine.mjs +32 -3
  28. package/lib/leverage.mjs +11 -2
  29. package/lib/loop-cache.mjs +67 -0
  30. package/lib/mcp-client.mjs +12 -0
  31. package/lib/push.mjs +9 -1
  32. package/lib/reflex.mjs +5 -1
  33. package/lib/releases.mjs +87 -18
  34. package/lib/review.mjs +32 -6
  35. package/lib/session-facts.mjs +108 -0
  36. package/lib/skill-deps.mjs +377 -0
  37. package/lib/update.mjs +9 -0
  38. package/package.json +3 -2
  39. package/skills-bundle/frontend-design/LICENSE.txt +177 -0
  40. package/skills-bundle/frontend-design/SKILL.md +55 -0
  41. package/{LICENSE → skills-bundle/grill-me/LICENSE} +6 -6
  42. package/skills-bundle/grill-me/SKILL.md +7 -0
  43. package/skills-bundle/grill-me/agents/openai.yaml +5 -0
  44. package/skills-bundle/grilling/SKILL.md +53 -0
  45. package/skills-bundle/improve-codebase-architecture/HTML-REPORT.md +123 -0
  46. package/skills-bundle/improve-codebase-architecture/LICENSE +21 -0
  47. package/skills-bundle/improve-codebase-architecture/SKILL.md +71 -0
  48. package/skills-bundle/improve-codebase-architecture/agents/openai.yaml +5 -0
  49. package/skills-bundle/requesting-code-review/LICENSE +21 -0
  50. package/skills-bundle/requesting-code-review/SKILL.md +95 -0
  51. package/skills-bundle/requesting-code-review/code-reviewer.md +172 -0
  52. package/skills-bundle/skills-manifest.json +72 -0
  53. package/skills-bundle/tdd/LICENSE +21 -0
  54. package/skills-bundle/tdd/SKILL.md +36 -0
  55. package/skills-bundle/tdd/agents/openai.yaml +3 -0
  56. package/skills-bundle/tdd/mocking.md +59 -0
  57. package/skills-bundle/tdd/tests.md +77 -0
  58. package/skills-bundle/vercel-composition-patterns/AGENTS.md +946 -0
  59. package/skills-bundle/vercel-composition-patterns/README.md +60 -0
  60. package/skills-bundle/vercel-composition-patterns/SKILL.md +89 -0
  61. package/skills-bundle/vercel-composition-patterns/metadata.json +11 -0
  62. package/skills-bundle/vercel-composition-patterns/rules/_sections.md +29 -0
  63. package/skills-bundle/vercel-composition-patterns/rules/_template.md +24 -0
  64. package/skills-bundle/vercel-composition-patterns/rules/architecture-avoid-boolean-props.md +100 -0
  65. package/skills-bundle/vercel-composition-patterns/rules/architecture-compound-components.md +112 -0
  66. package/skills-bundle/vercel-composition-patterns/rules/patterns-children-over-render-props.md +87 -0
  67. package/skills-bundle/vercel-composition-patterns/rules/patterns-explicit-variants.md +100 -0
  68. package/skills-bundle/vercel-composition-patterns/rules/react19-no-forwardref.md +42 -0
  69. package/skills-bundle/vercel-composition-patterns/rules/state-context-interface.md +191 -0
  70. package/skills-bundle/vercel-composition-patterns/rules/state-decouple-implementation.md +113 -0
  71. package/skills-bundle/vercel-composition-patterns/rules/state-lift-state.md +125 -0
package/CHANGELOG.md CHANGED
@@ -4,6 +4,110 @@ 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.10.0 — wave 6: the capability arc (skill dependencies + agent upgrades)
8
+
9
+ rafa's agents get materially better at engineering — and the mechanism is the
10
+ owner's design: rafa **declares** third-party skill dependencies and installs
11
+ them **with your consent**, package-manager style, so the agents adopt YOUR
12
+ toolbox instead of hardcoding one.
13
+
14
+ - **The skill installer.** `rafa update`/`init` offers 7 declared skills (tdd,
15
+ frontend-design, vercel-composition-patterns, requesting-code-review,
16
+ improve-codebase-architecture, grill-me, and rafa's own grilling procedure)
17
+ with one-line reasons; on yes it installs them one at a time into the
18
+ **harness-neutral `.agents/skills/`** (one convention Claude Code, Codex,
19
+ and Cursor all read — Codex manifests ride along), verifies each
20
+ (frontmatter parses · sha256s match the tarball's own manifest · license
21
+ files present), and stamps `rafa.json`. Declining is remembered — re-offer
22
+ with `rafa update --skills`; `--yes` for CI; non-TTY without `--yes` skips
23
+ and stamps nothing. A declined set degrades gracefully, never breaks:
24
+ vendored cards never reference `.agents/` (pinned by test).
25
+ - **TDD by default** (atlas 4.0.0): eligible code tasks (tdd installed + a
26
+ harness detected) build red→green — the Done-check IS a failing test
27
+ authored first at a seam the dev approved at plan time; evidence (red
28
+ exit≠0 before green exit 0) rides the structured return; prism re-runs
29
+ green live and verifies red per validation tier. Refactor defers to review.
30
+ - **The design method**: UI tasks invoke frontend-design (token system,
31
+ signature element, critique-before-code) and consult Vercel's composition
32
+ rules (React-19-gated) — with the brain's design-system notes as the brief.
33
+ - **The grill gate** (prism 0.9.0): plans get a tier-bounded adversarial
34
+ interview before approval — grounding, unstated dependencies, VACUOUS
35
+ Done-checks (tautological tests caught), YAGNI. Devs also get `/grill-me`.
36
+ - **Severity-labeled findings** everywhere prism/review rule: Critical ·
37
+ Important · Minor — Minor never flips a verdict.
38
+ - **bloom's architecture method** (0.9.0): git-log heat scoping, the deletion
39
+ test, Confidence-lined candidates, first-slice-or-backlog — never big-bang.
40
+ - **Toolbox surfaces**: conductor 2.2.0 recalls `.agents/skills` in every
41
+ spawn slice; `rafa leverage --json` carries top-level `agentSkills` +
42
+ `skillDeps`; `rafa doctor` verifies installed skills against the bundle.
43
+
44
+ Adopt with `rafa update` — the skills prompt runs first, then the blueprint
45
+ sync. All licenses confirmed and vendored (MIT ×5, Apache-2.0 frontend-design).
46
+
47
+ ## 0.9.0 — wave 5: trust plane + transport economics
48
+
49
+ The research-canvas QA dogfood session (2026-07-25) produced a P0–P3 bottleneck
50
+ backlog; its root causes converged with sage's first two learnings on the same
51
+ seam. This release lands the whole wave:
52
+
53
+ - **Strict loop events, no legacy path.** `report_loop_event` requires the
54
+ actor envelope (`model` · `agent` · `runner ∈ sandbox|ci|session`; mechanical
55
+ CLI emits stamp `model:"mechanical"`) and `verification {method: static|live,
56
+ tool?, evidence?}` on verdict/gate categories — the static-vs-executed
57
+ distinction is structural, so a fabricated "tests passed" can't survive the
58
+ schema. `dedupeKey` makes every emit idempotent; `tier` records the
59
+ validation tier the ruling ran at. Pre-0.9.0 emits are rejected server-side
60
+ (pre-launch: old CLIs discarded). Both sage learnings flipped accepted.
61
+ - **One beat per task boundary.** `patch_plan_item` patches ONE item's fields
62
+ (structure stays `push_plan`-only); `checkpoint_task` lands the item patch +
63
+ the prism-verdict loop event + decision records in ONE transaction,
64
+ idempotent on retry. A task boundary went from ~6 platform calls + a
65
+ full-tree resend to 2 beats.
66
+ - **Self-healing citations.** `verify-citations --fix` rewrites a drifted cite
67
+ when its token still lives at exactly ONE grep site (gone/ambiguous stay
68
+ failing — the checker never guesses), then re-runs clean. `rafa hydrate`
69
+ re-bases served cites against the local checkout on arrival.
70
+ - **`rafa facts` — the session-facts plane.** Bank a verified fact once (claim
71
+ + the command/exit that proved it + dependsOn paths, sha-anchored); agents
72
+ read first and cite `SF-n unchanged`. Staleness is mechanical (dependsOn ∩
73
+ git diff since the verifying sha, working tree included).
74
+ - **The sage-due bridge.** `rafa checkpoint` caches loop-event shapes locally;
75
+ the SessionStart digest prints "sage due: N" with zero added platform calls,
76
+ and the conductor card makes honoring it a MUST — sage's trigger is
77
+ mechanical instead of discretionary.
78
+ - **`validation_tier` on plan tasks** (light|standard|full): bounds prism's
79
+ re-derivation depth only — the Done-check gate never relaxes.
80
+ - Cards/SOPs re-vendored: prism 0.8.0 (tool-authenticity rule · structured
81
+ return · bounded invariant citation) · atlas 3.9.0 (structured return ·
82
+ facts duty) · sage 0.4.0 (verification/actor/tier analysis dimensions) ·
83
+ conductor 2.1.0 (mechanical sage trigger). Review summary counts dedupe by
84
+ note id.
85
+
86
+ Adopt with `rafa update` (re-vendors hooks, cards, SOPs, contract).
87
+
88
+ ## 0.8.16 — the session tail never strands (both planes, one beat)
89
+
90
+ Live-catch (2026-07-24, research-canvas): brain notes authored AFTER the last code
91
+ commit sat uncommitted in `.rafa` while `rafa checkpoint` reported clean — the
92
+ capture plane (platform working set) was synced, the git plane (brain mirror
93
+ branch) was dirty. The hooks only fire on code commit/push, so a session tail
94
+ (verify flips · late-authored notes · regenerated manifest) had nothing to ride.
95
+
96
+ - `rafa checkpoint` now owns BOTH planes: after the working-set sync (including
97
+ the "clean" path) it fires the vendored `brain-commit.mjs` worker whenever the
98
+ brain tree is dirty — the tail commits and pushes at the same beat that
99
+ creates it, and "clean" finally means capture AND mirror.
100
+ - The worker is tail-aware: when HEAD already has its 1-1 mirror, the extra run
101
+ commits with a distinct `brain(<branch>): session tail · N file(s)` subject on
102
+ the SAME `code-commit` trailer (join intact, log legible) — and an empty tail
103
+ is a no-op, never an `--allow-empty` duplicate.
104
+ - `review.json` (the review gate's scored-ranges artifact) joins the local-state
105
+ exclusion list — machine state like `distill-verdicts.json`, never knowledge.
106
+ - Fixed: the `RELEASES` ledger tail was out of ascending order (0.8.13/0.8.14
107
+ after 0.8.15), so `CURRENT` reported 0.8.13 from a 0.8.15 build.
108
+
109
+ Adopt with `rafa update` (re-vendors the hook worker).
110
+
7
111
  ## 0.8.2 — MCP scope: derived from the key, never guessed
8
112
 
9
113
  First real client MCP call surfaced it: agents GUESSED the `repo` arg (folder name)
package/bin/rafa.mjs CHANGED
@@ -46,6 +46,7 @@ const COMMANDS = [
46
46
  "hydrate",
47
47
  "dirty",
48
48
  "reflex",
49
+ "facts",
49
50
  "status",
50
51
  "doctor",
51
52
  "fold",
@@ -73,6 +74,8 @@ Commands:
73
74
  Flags (init/update): --overwrite replace changed owned files without asking
74
75
  --keep keep all changed owned files without asking
75
76
  (default: ask per file; in a non-TTY it keeps them)
77
+ --skills re-offer declared skill dependencies (incl. declined)
78
+ --yes install all skill dependencies unprompted (CI)
76
79
  compile Validate the brain against the contract → .rafa/manifest.json.
77
80
  manifest The BRANCH manifest (lenient snapshot — WIP files land under
78
81
  drafts, never fail). You never need to run this: the post-commit
@@ -113,6 +116,14 @@ Commands:
113
116
  detected. List/--json; --consume <id> [verdict] AFTER banking it
114
117
  through the gates (or judging it session-only). Corrections are
115
118
  the highest-value knowledge signal — they must never evaporate.
119
+ facts The session-facts plane (wave 5.4 — memoized verification): bank a
120
+ VERIFIED fact once (claim + the command/exit that proved it +
121
+ dependsOn paths, sha-anchored); agents read first and cite
122
+ "SF-n unchanged" instead of re-deriving. Staleness is mechanical
123
+ (dependsOn ∩ git diff since the verifying sha) and NOT transitive —
124
+ declare the enclosing directory for subsystem-spanning facts
125
+ (breadth is the conservative move). add · invalidate ·
126
+ prune --stale · --json.
116
127
  status Where the loop stands, from local state only: active plan +
117
128
  task progress · stale count · corrections · conflicts.
118
129
  --line = the one-line ambient form (statuslines/prompts) ·
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: atlas
3
- version: 3.8.1
3
+ version: 4.0.0
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: >-
@@ -16,7 +16,7 @@ duties:
16
16
  - "scan :: .claude/skills/rafa-scan/SKILL.md :: comprehensive breadth-first cited brain · verify-citations exits 0 · coverage honest (thin/gap stated, never hidden)"
17
17
  - "repair :: .claude/skills/rafa-scan/SKILL.md :: every blocker + major in checklist.md fixed against the code · checker re-run to exit 0 · never weaken a check to pass it"
18
18
  - "plan-drafting :: .claude/skills/rafa-plan/SKILL.md :: recall-grounded decomposition (coverage → search → notes) · blast radius named · contract §7 files · every child carries a Done-check"
19
- - "build-execution :: .claude/skills/rafa-build/SKILL.md :: implement per recalled knowledge · never hand-edit brain files around the gate · return what changed, cited"
19
+ - "build-execution :: .claude/skills/rafa-build/SKILL.md :: implement per recalled knowledge · TDD-DEFAULT when the tdd skill + a harness are present (the Done-check is a failing test FIRST at the plan-named seam; red→green evidence in the return) · UI tasks run the installed design skills per SOP (brain conventions win) · read session-facts FIRST and bank new expensive verifications (rafa facts add) · never hand-edit brain files around the gate · return what changed, cited"
20
20
  - "scoped-refresh :: .claude/skills/rafa-scan/SKILL.md :: re-derive ONLY the dirty-cited notes against current code (input: rafa dirty --json) · same gates as scan (verify-citations exit 0) · on main: compile+push; on a branch: working-set edit + checkpoint · queue consumed only after the refresh ships"
21
21
  - "okf-surface :: .claude/skills/rafa-okf/SKILL.md :: authored files pass rafa okf check as written — body links are markdown (never wikilinks); emit-owned sections untouched"
22
22
  ---
@@ -62,6 +62,22 @@ whole-codebase read happens in *your* window, not the conductor's, so the main s
62
62
  stays lean. You **never spawn** other agents — subagents can't nest, so the conductor owns
63
63
  the loop. Return summaries + on-disk artifacts, not the raw reads.
64
64
 
65
+ **Structured return (wave 5).** A build-execution report ends with the machine
66
+ block the conductor assembles Logs from — never re-paraphrased prose:
67
+
68
+ ```json
69
+ { "filesChanged": ["path", "…"], "commitSha": "…",
70
+ "notes": "one-paragraph what/why",
71
+ "factsDiscovered": ["SF-n banked: <claim>", "…"] }
72
+ ```
73
+
74
+ **Session facts (wave 5 — read first, bank once).** Before re-deriving any
75
+ environment/tooling fact, read `.rafa/session-facts.json` (`rafa facts`) and
76
+ cite `SF-<n> unchanged` when it holds. When YOU verify something expensive
77
+ (a harness exists, an install works, an env constraint), bank it once:
78
+ `rafa facts add --claim="…" --command="…" --exit=N [--depends=a,b]` — the next
79
+ spawn inherits it instead of re-learning it.
80
+
65
81
  ## The brain is your index, once it exists
66
82
  Generic at provision-time, repo-aware after the scan: **if a brain exists at
67
83
  `.rafa/brain/`, consult it as your index before re-reading code** — route via its cited
@@ -91,7 +107,9 @@ Never act cold; never over-load.
91
107
  *name* is a contract; its *value* is a secret. If a note genuinely needs a value, stop and ask
92
108
  the dev — don't harvest it. (Enforced in [the scan skill](../skills/rafa-scan/SKILL.md) step 4.)
93
109
  - **Toolbox-first execution (automatic — never an offer).** Before implementing any
94
- task step, CHECK the repo's installed toolbox — committed `.claude/skills/`,
110
+ task step, CHECK the repo's installed toolbox — committed `.claude/skills/`, the
111
+ **harness-neutral `.agents/skills/`** (consent-installed skill dependencies —
112
+ tdd, frontend-design, vercel-composition-patterns are yours to INVOKE),
95
113
  `.mcp.json` servers, commands — for a capability that already does it, and INVOKE
96
114
  it (Skill tool / MCP) instead of hand-rolling. The conductor passes the matching
97
115
  inventory slice in your spawn prompt; consult it first. Only what is actually
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: bloom
3
- version: 0.8.1
3
+ version: 0.9.0
4
4
  model: opus # a false flag mutes the whole ledger — best model, never cheap
5
5
  groundTruth: code-trend
6
6
  description: >-
@@ -13,7 +13,7 @@ description: >-
13
13
  tools: Read, Grep, Glob, Bash, Skill, Write, mcp__rafinery
14
14
  color: green
15
15
  duties:
16
- - "improve-pass :: .claude/skills/rafa-improve/SKILL.md :: cited · prioritized P0–P3 · leverage-ranked · cite-checked (unresolved dropped) · ledger + debt trend regenerated"
16
+ - "improve-pass :: .claude/skills/rafa-improve/SKILL.md :: cited · prioritized P0–P3 · the architecture lens applies the deletion-test/depth method with its vocabulary (Confidence-lined candidates, first-slice-or-backlog, never big-bang) · leverage-ranked · cite-checked (unresolved dropped) · ledger + debt trend regenerated"
17
17
  - "plan-pull :: .claude/skills/rafa-plan/SKILL.md :: top-leverage OPEN improvements in the blast radius surfaced as optional child tasks — dismissible, never blocking"
18
18
  - "build-sweep :: .claude/skills/rafa-build/SKILL.md :: newly spotted → ledger files · fixed-in-passing → status fixed · at most ONE opt-in nudge per task"
19
19
  - "staleness-watch :: .claude/skills/rafa-improve/SKILL.md :: median open-item age stays minimal — every sweep re-validates or closes aged items; no improvement ages silently"
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: prism
3
- version: 0.7.1
3
+ version: 0.9.0
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: >-
@@ -14,8 +14,8 @@ tools: Read, Grep, Glob, Bash, Write, mcp__rafinery
14
14
  color: orange
15
15
  duties:
16
16
  - "scan-validation :: .claude/skills/rafa-validate/SKILL.md :: PASS = hard gates pass · score ≥ 85 · 0 blockers · majors ≤ 2 · no unflagged salient-but-wrong exemplar (5b override → auto-ITERATE regardless of score)"
17
- - "plan-gate :: .claude/skills/rafa-plan/SKILL.md :: PASS = every task grounded in brain + code (none hallucinated) · every child carries a Done-check · blast radius named from coverage; else REJECT with cited reasons"
18
- - "execution-validation :: .claude/skills/rafa-build/SKILL.md :: done only when the child's Done-check demonstrably holds — run it yourself against the working tree; FAIL returns cited reasons to the producer"
17
+ - "plan-gate :: .claude/skills/rafa-plan/SKILL.md :: PASS = every task grounded in brain + code (none hallucinated) · every child's Done-check present AND non-vacuous (grilled per validation_tier — light = invariants only · standard = bounded probes · full = the whole interview) · TDD-eligible code tasks name their seam in the Done-check · blast radius named from coverage; else REJECT with cited reasons"
18
+ - "execution-validation :: .claude/skills/rafa-build/SKILL.md :: done only when the child's Done-check demonstrably holds — run it yourself against the working tree · TDD tasks: green re-run live, red per tier (static-attested at standard, worktree re-run at full) · findings labeled Critical/Important/Minor — ITERATE iff any Critical or Important, Minor never flips the verdict; FAIL returns cited reasons to the producer"
19
19
  - "distillation-validation :: .claude/skills/rafa-distill/SKILL.md :: every working-set file judged against MERGED MAIN (never the fork point) with a confirming/refuting file:line — survivors enter the org brain only through compile; refutations go back to their author, cited"
20
20
  - "okf-surface :: .claude/skills/rafa-okf/SKILL.md :: every checker LINKS warn judged (unwritten knowledge vs typo) — never ignored, never auto-failed"
21
21
  ---
@@ -37,6 +37,45 @@ brain as a trusted index, whatever you are validating is the thing on trial —
37
37
  against the code (ground truth) and the stated acceptance criteria. Trust nothing in
38
38
  it until you've confirmed it.
39
39
 
40
+ **Tool authenticity (wave 5 — the npx-placeholder trap).** A tool result is
41
+ evidence ONLY if the tool actually ran: name the binary (path or resolved
42
+ command) and the REAL exit code. `npx <name>` can silently resolve a joke/
43
+ placeholder package and "pass" — a class that already produced one fabricated
44
+ verdict in the field. If you cannot execute the check (no keys, no deps, no
45
+ env), the claim is `method: "static"` — say so in the same breath, never
46
+ smooth it into an unqualified PASS. Fabricating a `live` result is the one
47
+ unforgivable failure: the whole trust model prices from it.
48
+
49
+ **Structured return (wave 5).** Your report ends with the machine block the
50
+ conductor assembles Logs and loop events from — never re-paraphrased prose:
51
+
52
+ ```json
53
+ { "verdict": "PASS|ITERATE",
54
+ "evidence": [{ "claim": "…", "method": "static|live", "tool": "…", "exitCode": 0 }],
55
+ "residualRisk": "what was NOT proven, named — or the explicit empty string" }
56
+ ```
57
+
58
+ The `evidence` entries feed `report_loop_event.verification` verbatim; the
59
+ static-vs-live split is structural, not prose discipline.
60
+
61
+ **Bounded invariant citation (wave 5 — no-redundant-reverification).** An
62
+ invariant YOU (or a prior prism pass this plan) verified once is citable as
63
+ `SF-<n> unchanged` **iff** `.rafa/session-facts.json` shows its `dependsOn`
64
+ untouched since its verifying sha (the session-start digest surfaces staleness;
65
+ `rafa facts` lists it). Re-deriving an unchanged, evidence-backed fact is waste,
66
+ not rigor. The bound is hard: anything NOT in the facts file — and everything
67
+ about the artifact under test itself — stays trust-nothing. **Invalidation is
68
+ NOT transitive** (dependsOn is intersected with the diff, imports are never
69
+ walked — pinned by test): trust a fact only as far as its declared breadth, and
70
+ when a fact you rely on spans a subsystem whose dependsOn names single files,
71
+ treat it as under-declared — re-verify instead of citing it.
72
+
73
+ **Validation tier (wave 5).** A task's `validation_tier` (plan §7) bounds your
74
+ RE-DERIVATION DEPTH — how much surrounding context you re-read and re-prove:
75
+ light = the Done-check itself + direct evidence · standard = + the touched
76
+ files' immediate seams · full = unbounded re-derivation. The Done-check gate
77
+ NEVER relaxes at any tier, and your emitted loop event carries the tier it ran at.
78
+
40
79
  ## Duties (each duty's bar is in the frontmatter; the SOP carries the procedure)
41
80
  1. **Scan validation** — judge the brain in `.rafa/brain/` against the repo per
42
81
  [the validate skill](../skills/rafa-validate/SKILL.md). Verdict + score + severity-
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: sage
3
- version: 0.2.1
3
+ version: 0.4.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
 
@@ -1,5 +1,5 @@
1
1
  ---
2
- version: 2.0.2
2
+ version: 2.2.0
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
 
@@ -59,10 +59,15 @@ MCP tools, checkpoints, pushes, decisions logging — is AGENT-INTERNAL machiner
59
59
  dev speaks intent; YOU run the machinery. Never instruct the dev to run a command you
60
60
  can run yourself. The only human acts: init (provision) · `npx rafa pull` (once per
61
61
  clone) · ci-setup approval · npm publish. **Toolbox is a recall surface:** at bootstrap,
62
- load the committed toolbox (`.claude/skills/`, `.mcp.json`, commands) alongside brain
63
- status; pass the matching slice into every atlas/bloom spawn prompt agents USE the fit
64
- capability instead of hand-rolling (only what's installed, never guessed; personal
65
- `~/.claude/` stays live-recommend-only). **Actor envelope:** stamp `actorMeta {model,
62
+ load the committed toolbox `.claude/skills/` (this harness) **and
63
+ `.agents/skills/` (the HARNESS-NEUTRAL third-party skillsone convention
64
+ Claude Code · Codex · Cursor all read; installed by `rafa update` with
65
+ consent, wave 6)** plus `.mcp.json` and commands, alongside brain status
66
+ (`rafa leverage --json` is the deterministic read); pass the matching slice
67
+ into every atlas/bloom spawn prompt — agents USE the fit capability instead of
68
+ hand-rolling (only what's installed, never guessed; a DECLINED skill dep means
69
+ the SOP's fallback path, never a nag; personal `~/.claude/` stays
70
+ live-recommend-only). **Actor envelope:** stamp `actorMeta {model,
66
71
  agent, runner: "session"}` on every state-plane write (checkpoints, plan pushes,
67
72
  decisions, reports) and export `RAFA_ACTOR_MODEL` before CLI calls — the platform records
68
73
  WHO/WHAT executed; "unreported" when unknown, never guessed.
@@ -240,14 +245,20 @@ explicitly typed; an ACCEPTED boundary offer counts as the explicit invocation.
240
245
 
241
246
  ## sage — the silent L5 observer (runs IMPLICITLY, zero-command; owner 2026-07-13)
242
247
 
243
- Devs never type `/rafa sage`. **Trigger:** at completion boundaries build final-verify ·
244
- distill close · bootstrap when new loop events exist since the learnings ledger's newest
245
- entry, spawn sage per [rafa-sage](../skills/rafa-sage/SKILL.md); announce ONE line, proceed;
246
- its proposals ride the NEXT bootstrap digest. Writing proposals is the just-do rung; the
247
- consent moment is ACCEPTING one (accepted = a versioned, MR-reviewed card/SOP edit proposals
248
- never self-apply). `/rafa sage` is the explicit override. Full mechanics (≥10-event threshold,
249
- `get_loop_events` shape read, scrub step, ledger schema, the person-free/asset-free creed) live
250
- in the skill.
248
+ Devs never type `/rafa sage`. **Trigger MECHANICAL as of wave 5 (owner
249
+ 2026-07-25):** the SessionStart digest computes it a `[rafa · sage] sage due:
250
+ N loop events…` line (the checkpoint-written loop-event cache vs the learnings
251
+ ledger, compared locally). **When that line is present, spawning sage at the
252
+ next completion boundary build final-verify · distill close · bootstrapis
253
+ a MUST, not a judgment call**: the line IS the trigger condition already
254
+ evaluated; a discretionary trigger is how sage never fired organically. Spawn
255
+ per [rafa-sage](../skills/rafa-sage/SKILL.md); announce ONE line, proceed; its
256
+ proposals ride the NEXT bootstrap digest. No digest line = not due (skip
257
+ silently). Writing proposals is the just-do rung; the consent moment is
258
+ ACCEPTING one (accepted = a versioned, MR-reviewed card/SOP edit — proposals
259
+ never self-apply). `/rafa sage` is the explicit override. Full mechanics
260
+ (≥10-event threshold, `get_loop_events` shape read, scrub step, ledger schema,
261
+ the person-free/asset-free creed) live in the skill.
251
262
 
252
263
  ## migrate · update — brain-side schema migration (no skill; the intelligence-only half)
253
264
 
@@ -382,6 +382,7 @@ blocked_by: [payments-c2] # optional · item ids this waits on — a dependency
382
382
  blocked_reason: "vendor sandbox access" # optional · external blocks (no dependency id to point at)
383
383
  priority: 2 # optional · 0 none · 1 urgent · 2 high · 3 medium · 4 low (Linear scale)
384
384
  estimate: 3 # optional · points (Linear estimate / Jira story points)
385
+ validation_tier: standard # optional · light|standard|full — bounds prism's re-derivation DEPTH only (wave 5); the Done-check gate itself never relaxes. Planner assigns from blast radius: contract/state/graph surface → full · types/docs/leaf-UI → light · default standard
385
386
  branch: feat/webhooks # optional · ↔ Linear branchName
386
387
  baseSha: abc123 # optional · commit it was cut from
387
388
  domains: [payments] # optional · EPIC only · the blast radius — the brain link
@@ -573,12 +574,17 @@ state**, marked by `envelope.plane: "state"`:
573
574
  | `resolve_working_file` | branch working set | `distilled` / `refuted` (+ cited note) at merge-to-main distillation; `keep-current` for adjudication decisions; CAS on status — a racing distill fails loudly |
574
575
  | `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 |
575
576
  | `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 |
577
+ | `patch_plan_item` | plans (one item) | wave 5.2 — field-level patch on ONE item (no full-tree resend): provided fields replace, absent stay; CLEARING a field and any STRUCTURE change (kind/parent/add/remove/domains/external/merged) remain `push_plan`-only, so "a vanished optional field must not survive" keeps holding at tree granularity; snapshot-before-change rides noteRevisions like a re-push |
578
+ | `checkpoint_task` | plans + loopEvents + planDecisions (**ONE transaction**) | wave 5.2 — the ONE-beat boundary write: optional item patch + the loop-event ruling + decision records land atomically (all-or-nothing; `mcp.call` is an action, so composing the separate tools can never be atomic — this is one internalMutation). Idempotent end-to-end (patch no-ops on same values · loop event dedupes on `dedupeKey` · decisions dedupe on client-stable ids). The embedded loopEvent obeys `report_loop_event`'s exact rules — one rulebook, no side door. `checkpoint_sync` stays separate: per-file CAS partial-accept is a different failure model than an atomic beat |
576
579
  | `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
580
  | `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
581
  | `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
582
  | `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
583
  | `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
584
  | `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 |
585
+ | `report_loop_event` | **loopEvents** — the loop-outcome ledger (sage's evidence) | ONE structured outcome per ruling, shapes only (enum category + per-category enum outcome + `subject` shape ref, secret-screened). Wave 5 trust plane, STRICT (no legacy path): **required actor envelope** `{model, agent, runner ∈ sandbox\|ci\|session}` (mechanical CLI emits use `model:"mechanical"`); **required `verification {method: static\|live, tool?, evidence?}`** on `prism-verdict`/`gate-result`/`review-verdict` — the static-vs-executed distinction is structural, never prose; optional `dedupeKey` (≤120) makes the emit **idempotent** (same `(repo, key)` never lands twice — response says `deduped`) and `tier ∈ light\|standard\|full` records the §7 `validation_tier` the ruling ran at |
586
+ | `get_loop_events` | loopEvents (**READ**) | newest-first shape read (optional category filter, limit ≤500): category/outcome/subject + actorMeta/verification/dedupeKey/tier + at — no bodies, no code; sage splits miss patterns by verification method, actor, and tier |
587
+ | `report_learning` | **agentLearnings** — sage's summary-row mirror | sage ONLY (wave 4.1): mirrors the SUMMARY ROW of one committed ledger entry (`.claude/rafa/learnings/<id>.md` stays the single truth, MR-gated, never self-applied). Asset-free ENFORCED server-side: enum status/leverage/categories, `.claude/`-only diff target, length caps, code-fence + secret screens; upsert by id |
582
588
 
583
589
  State tools work with or without an ingested brain (a working set can exist —
584
590
  and a plan can be pushed — before the first scan) and are exempt from the
@@ -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
- writeFileSync(
133
- join(rafa, "intent", `${short}.md`),
134
- `---\n` +
135
- `type: IntentRecord\n` +
136
- `description: "per-commit intent trail (capture-engine P2) — provenance, consumed at merge, never org-brain truth"\n` +
137
- `code-commit: ${fullSha}\n` +
138
- `code-branch: ${branch}\n` +
139
- `timestamp: ${new Date().toISOString()}\n` +
140
- `---\n\n# ${subject}\n\n## Files\n` +
141
- files.map((f) => `- ${f}`).join("\n") +
142
- `\n`,
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,13 @@ 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",
184
+ // wave 5: session-scoped machine state — memoized verification + the
185
+ // checkpoint-written loop-event cache the sage-due digest reads.
186
+ "session-facts.json",
187
+ "loop-events-tail.json",
158
188
  ];
159
189
  const gi = join(rafa, ".gitignore");
160
190
  const cur = existsSync(gi) ? readFileSync(gi, "utf8") : "";
@@ -171,10 +201,26 @@ try {
171
201
 
172
202
  shR("git add -A");
173
203
  disposeHydrations(branch);
174
- shR(
175
- `git commit --allow-empty -q -m "brain(${branch}): ${subject}" ` +
176
- `-m "code-commit: ${fullSha}" -m "code-branch: ${branch}"`,
177
- );
204
+ if (isTail) {
205
+ // Session tail: commit only when something REAL is staged (disposal may
206
+ // have unstaged everything). The 1-1 join stays mechanical — same trailer.
207
+ let staged = [];
208
+ try {
209
+ staged = shR("git diff --cached --name-only").split("\n").filter(Boolean);
210
+ } catch {
211
+ /* nothing staged */
212
+ }
213
+ if (staged.length > 0)
214
+ shR(
215
+ `git commit -q -m "brain(${branch}): session tail · ${staged.length} file(s)" ` +
216
+ `-m "code-commit: ${fullSha}" -m "code-branch: ${branch}"`,
217
+ );
218
+ } else {
219
+ shR(
220
+ `git commit --allow-empty -q -m "brain(${branch}): ${subject}" ` +
221
+ `-m "code-commit: ${fullSha}" -m "code-branch: ${branch}"`,
222
+ );
223
+ }
178
224
  // Keep the REMOTE mirror branch current (owner 2026-07-23: the branch is visible
179
225
  // in the brain repo) — best-effort, bounded; a failed push retries on the next
180
226
  // commit. Never the trunk (single writer = the reconciler).
@@ -300,6 +300,72 @@ try {
300
300
  }
301
301
  }
302
302
 
303
+ // Session facts (wave 5.4 — memoized verification): banked verified facts +
304
+ // mechanical staleness (dependsOn ∩ git diff since the verifying sha, working
305
+ // tree included). Spawned agents read the file FIRST and cite "SF-n unchanged"
306
+ // instead of re-deriving. Local reads only — no platform call.
307
+ {
308
+ const doc = readJson(join(rafaDir, "session-facts.json"));
309
+ if (doc?.schemaVersion === 1 && Array.isArray(doc.facts) && doc.facts.length) {
310
+ let stale = 0;
311
+ for (const f of doc.facts) {
312
+ try {
313
+ const changed = execSync(`git diff --name-only ${f.verifiedAtSha}`, {
314
+ cwd: root,
315
+ encoding: "utf8",
316
+ stdio: ["ignore", "pipe", "ignore"],
317
+ })
318
+ .split("\n")
319
+ .filter(Boolean);
320
+ if (
321
+ (f.dependsOn ?? []).some((p) =>
322
+ changed.some((c) => c === p || c.startsWith(p.endsWith("/") ? p : p + "/")),
323
+ )
324
+ )
325
+ stale++;
326
+ } catch {
327
+ stale++; // unknown sha — fail-honest, count it stale
328
+ }
329
+ }
330
+ lines.push(
331
+ `[rafa · facts] ${doc.facts.length} session fact(s) banked (.rafa/session-facts.json — spawned agents read it FIRST and cite "SF-n unchanged" instead of re-deriving)` +
332
+ (stale ? ` · ${stale} STALE — re-verify or \`rafa facts prune --stale\`` : ""),
333
+ );
334
+ }
335
+ }
336
+
337
+ // Sage-due bridge (wave 5.5 — mechanical detection): the checkpoint-written
338
+ // loop-event cache vs the learnings ledger, compared LOCALLY (zero platform
339
+ // calls). Sage's trigger was a discretionary end-of-session SOP step and
340
+ // never fired organically — detection now lands front-of-session where
341
+ // reminders get obeyed; only the SPAWN stays with the conductor.
342
+ {
343
+ const cache = readJson(join(rafaDir, "loop-events-tail.json"));
344
+ if (cache?.schemaVersion === 1 && Array.isArray(cache.events)) {
345
+ let ledgerAt = null;
346
+ const ledgerPath = join(root, ".claude", "rafa", "learnings", "ledger.md");
347
+ if (existsSync(ledgerPath)) {
348
+ const m = readFileSync(ledgerPath, "utf8").match(
349
+ /^-\s*Generated:\s*([0-9]{4}-[0-9]{2}-[0-9]{2}(?:[T ][0-9:.Z+-]+)?)/m,
350
+ );
351
+ if (m) {
352
+ const t = Date.parse(m[1]);
353
+ if (Number.isFinite(t)) ledgerAt = t;
354
+ }
355
+ }
356
+ const fresh =
357
+ ledgerAt == null
358
+ ? cache.events
359
+ : cache.events.filter((e) => Number.isFinite(e.at) && e.at > ledgerAt);
360
+ if (fresh.length >= 10)
361
+ lines.push(
362
+ `[rafa · sage] sage due: ${fresh.length} loop events since the last learnings pass` +
363
+ (ledgerAt == null ? " (no ledger yet — sage has never run here)" : "") +
364
+ ` — spawn sage at the next completion boundary (zero-command; this line is the trigger, not a suggestion).`,
365
+ );
366
+ }
367
+ }
368
+
303
369
  // Benchmark liveness (harness-arc: the proof engine is workflow-woven, never a
304
370
  // dev-typed command) — a scanned brain with no MEASURED benchmark is a gap the
305
371
  // scan SOP's Prove-it step closes. Absence sensor only; never a nag mid-flow.