@rafinery/cli 0.12.0 → 0.14.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/lib/guard.mjs ADDED
@@ -0,0 +1,107 @@
1
+ // rafa guard — the capture-integrity gate (owner 2026-07-26 r5: whole-brain
2
+ // capture is MANDATORY; "we need to guard post-commit and pre-push").
3
+ //
4
+ // rafa guard --pre-push verify every OUTGOING code commit has its 1-1
5
+ // brain mirror commit; a missing mirror SELF-HEALS
6
+ // (the brain-commit worker re-runs); still missing
7
+ // → exit 1 and the push BLOCKS. Git-local — no
8
+ // network — so a failure is genuine capture loss,
9
+ // never flakiness.
10
+ //
11
+ // Exit: 0 capture intact (or nothing to guard) · 1 capture missing after
12
+ // self-heal. Trunk pushes exit 0 (the brain trunk has one writer — the
13
+ // reconciler; there is no mirror to demand).
14
+
15
+ import { execSync } from "node:child_process";
16
+ import { existsSync } from "node:fs";
17
+ import { join } from "node:path";
18
+ import { trunkBranchOf, readStamp } from "./stamp.mjs";
19
+
20
+ const sh = (cmd, cwd) =>
21
+ execSync(cmd, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
22
+
23
+ export function runGuard(args = []) {
24
+ const ROOT = process.cwd();
25
+ if (!args.includes("--pre-push")) {
26
+ console.log("usage: rafa guard --pre-push (capture-integrity gate; hook-invoked)");
27
+ return 0;
28
+ }
29
+ const rafaDir = join(ROOT, ".rafa");
30
+ if (!existsSync(join(rafaDir, ".git"))) return 0; // no brain repo here — nothing to guard
31
+
32
+ let branch = "";
33
+ try {
34
+ branch = sh("git rev-parse --abbrev-ref HEAD", ROOT);
35
+ } catch {
36
+ return 0;
37
+ }
38
+ const trunk = trunkBranchOf(ROOT);
39
+ const configured = typeof readStamp(ROOT).prodBranch === "string";
40
+ if (branch === trunk || (!configured && (branch === "main" || branch === "master"))) return 0;
41
+
42
+ // Outgoing commits: upstream..HEAD when an upstream exists, else every local
43
+ // commit on this branch not on the trunk (first push of a new branch).
44
+ let outgoing = [];
45
+ try {
46
+ outgoing = sh("git rev-list @{u}..HEAD", ROOT).split("\n").filter(Boolean);
47
+ } catch {
48
+ try {
49
+ outgoing = sh(`git rev-list ${trunk}..HEAD`, ROOT).split("\n").filter(Boolean);
50
+ } catch {
51
+ outgoing = [];
52
+ }
53
+ }
54
+ if (outgoing.length === 0) return 0;
55
+
56
+ // The brain branch's mirror trailers — `code-commit: <sha>` — are the 1-1
57
+ // capture record (capture-engine P1). Missing sha = a commit whose brain
58
+ // state was never mirrored.
59
+ const mirrored = () => {
60
+ try {
61
+ return new Set(
62
+ sh(`git log --format=%B ${branch}`, rafaDir)
63
+ .split("\n")
64
+ .map((l) => l.match(/^code-commit:\s*([0-9a-f]{7,40})/)?.[1])
65
+ .filter(Boolean),
66
+ );
67
+ } catch {
68
+ return new Set(); // brain branch missing entirely — every commit is unmirrored
69
+ }
70
+ };
71
+
72
+ const missing = (set) => outgoing.filter((sha) => ![...set].some((m) => sha.startsWith(m) || m.startsWith(sha)));
73
+
74
+ let gone = missing(mirrored());
75
+ if (gone.length > 0) {
76
+ // SELF-HEAL: the brain-commit worker is idempotent and tail-aware — one
77
+ // re-run mirrors any stray session state onto the branch.
78
+ console.log(`rafa guard · ${gone.length} outgoing commit(s) missing a brain mirror — self-healing …`);
79
+ try {
80
+ execSync(`node ${JSON.stringify(join(ROOT, ".claude", "rafa", "hooks", "brain-commit.mjs"))}`, {
81
+ cwd: ROOT,
82
+ stdio: ["ignore", "ignore", "ignore"],
83
+ });
84
+ } catch {
85
+ /* verified below — the re-check is the truth, not this exit code */
86
+ }
87
+ gone = missing(mirrored());
88
+ }
89
+
90
+ if (gone.length > 0) {
91
+ console.error(
92
+ `✗ rafa guard: capture INCOMPLETE — ${gone.length} outgoing commit(s) have no brain mirror:\n` +
93
+ gone.map((s) => ` ${s.slice(0, 12)}`).join("\n") +
94
+ "\n Whole-brain capture is mandatory (decision 2026-07-26 r5): a code commit whose brain" +
95
+ "\n state was never mirrored is capture loss the reconciler cannot recover." +
96
+ "\n Fix: `node .claude/rafa/hooks/brain-commit.mjs` (or re-commit), then push again." +
97
+ "\n Escape hatch (capture loss accepted, on the record): RAFA_HOOKS_DISABLED=1 git push",
98
+ );
99
+ return 1;
100
+ }
101
+ console.log(`rafa guard · capture intact (${outgoing.length} outgoing commit(s), all mirrored)`);
102
+ return 0;
103
+ }
104
+
105
+ export default function guard(args = []) {
106
+ process.exit(runGuard(args));
107
+ }
package/lib/releases.mjs CHANGED
@@ -409,6 +409,82 @@ export const RELEASES = [
409
409
  "origin/HEAD → main) drives the checkpoint trunk guard + CI distill " +
410
410
  "target.",
411
411
  },
412
+ {
413
+ version: "0.12.0",
414
+ contract: 1,
415
+ plans: 2,
416
+ requires: "update",
417
+ summary:
418
+ "THE SECURITY MODULE (owner-ratified 2026-07-26; spec " +
419
+ ".arohi/2026-07-26-security-module-spec.md): `rafa audit` — the " +
420
+ "SELF-CONTAINED engine (rafa.audit/v1): dep CVEs via the built-in " +
421
+ "lockfile parser + keyless OSV.dev merged with pnpm audit; secrets via " +
422
+ "the built-in ruleset (tracked files only, .env* never opened, " +
423
+ "fingerprints never bytes); SAST via semgrep IF present — a tier that " +
424
+ "didn't run says ran:false, never an empty list. NO binaries bundled " +
425
+ "or installed, ever. Improvement frontmatter `lens` RENAMED `category` " +
426
+ "(same enum; `lens` accepted as a deprecated alias — old ledgers keep " +
427
+ "compiling). New blueprint SOP rafa-security (never a dev verb): " +
428
+ "mechanical priority map critical→P0…dev-only→P3, reachability " +
429
+ "annotated from the brain, P0 security rows travel outside the blast " +
430
+ "radius; bloom 0.10.0 (security-profile duty), atlas 4.1.0 (plan-time " +
431
+ "audit transparency — clean is said out loud), scan requires the " +
432
+ "security-posture playbook. doctor gains the security-tools block.",
433
+ },
434
+ {
435
+ version: "0.13.0",
436
+ contract: 1,
437
+ plans: 2,
438
+ requires: "update",
439
+ summary:
440
+ "WHOLE-BRAIN MANDATORY CAPTURE (owner r4–r6, 2026-07-26 — 'if the " +
441
+ "brain did not capture what happened during the session, it's a " +
442
+ "failure'): `rafa guard --pre-push` — the capture-integrity gate the " +
443
+ "pre-push hook runs FIRST: every outgoing commit must carry its 1-1 " +
444
+ "brain mirror; missing → self-heal → still missing → the push BLOCKS " +
445
+ "(RAFA_HOOKS_DISABLED=1 is the on-the-record escape hatch); " +
446
+ "post-commit mirror failures go LOUD. `rafa checkpoint` auto-CONVERGES " +
447
+ "reported improvement flips into real ledger files and COMPULSORILY " +
448
+ "hydrates notes citing touched code (dirty queue ∪ git branch-vs-trunk " +
449
+ "diff — manual edits count) so affected-note edits happen DURING " +
450
+ "development. report_improvement_status returns required_next. " +
451
+ "MULTI-MANAGER lockfiles in the audit core: pnpm · npm (v3+v1, " +
452
+ "dev/direct from the lockfile) · yarn (classic+berry) · bun (text " +
453
+ "JSONC), resolved dynamically richest-first; yarn/bun are honestly " +
454
+ "packages-only (chain/dev null); binary bun.lockb detected and pointed " +
455
+ "at --save-text-lockfile; doctor FAILS a package.json repo with no " +
456
+ "lockfile and prints the per-manager generate command. review.json " +
457
+ "joins the brain-repo bootstrap ignore.",
458
+ },
459
+ {
460
+ version: "0.14.0",
461
+ contract: 1,
462
+ plans: 2,
463
+ requires: "update",
464
+ summary:
465
+ "CONTINUITY + CLEAN SOP + DISTILL REMOVED (owner 2026-07-27). CONTINUITY " +
466
+ "IS THE PRODUCT: scans/improves are REFRESH-FIRST — Step 0 `pull --full` " +
467
+ "+ brain-remote-HEAD check before any pass; founding = PLATFORM truth " +
468
+ "(zero knowledge served), never local emptiness; ids are STABLE FOREVER, " +
469
+ "refresh updates in place, triage + debt-trend never reset. The " +
470
+ "reconciler's PARALLEL-BRAIN GUARD contests a NEW note/improvement that " +
471
+ "overlaps an existing one (same domain/category + equal title OR ≥50% " +
472
+ "shared cited files vs the trunk manifest) — fold into the existing id " +
473
+ "or defend a genuinely new one; duplicates never bank silently. CLOSURES " +
474
+ "ARE TOMBSTONES, never deletions: notes gain `status: retired` + a dated " +
475
+ "`## Retired` section, improvements close `status: fixed|wontfix` + a " +
476
+ "dated evidence line — recall excludes tombstones, verify-citations " +
477
+ "skips them, the reconciler's prune WRITES the tombstone. The whole " +
478
+ "capture/continuity/security doctrine is CONSOLIDATED in contract §12 " +
479
+ "(the guarded loop: moments · signals · gates · lanes) and every " +
480
+ "agent/skill/reconciler SOP rewritten clean against it. The DISTILL " +
481
+ "surface is REMOVED (the rafa-distill skill + the `distill`/`ci-setup` " +
482
+ "commands): reconciliation is SERVER-SIDE only, the reconciler is the " +
483
+ "org brain's single writer. All guards resolve the trunk DYNAMICALLY " +
484
+ "(stamped prodBranch → origin/HEAD → main) — the configurable default " +
485
+ "branch is honored across checkpoint, guard, branch-merge audit, and " +
486
+ "reconciliation; no path hardcodes main.",
487
+ },
412
488
  ];
413
489
 
414
490
  // The release this CLI build ships (last entry).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rafinery/cli",
3
- "version": "0.12.0",
3
+ "version": "0.14.0",
4
4
  "description": "rafa — the AI engineer CLI. Vendors the rafa blueprint into your repo (shadcn-style) and runs the deterministic contract gates.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,5 +1,5 @@
1
1
  {
2
- "generatedAt": "2026-07-26T15:51:05.847Z",
2
+ "generatedAt": "2026-07-26T21:22:28.352Z",
3
3
  "skills": {
4
4
  "tdd": {
5
5
  "version": "1.0.0",
@@ -1,126 +0,0 @@
1
- ---
2
- name: rafa-distill
3
- description: "rafa SOP — merge-time reconciliation: validate a merged branch's WORKING SET against merged MAIN (prism), author survivors into the org brain through verify-citations + compile + push (atlas), refute loudly with citations; contested items flagged needs-adjudication, never guessed. Loaded on /rafa distill or the merge offer; CI runs the same SOP headlessly (rafa distill --headless)."
4
- ---
5
-
6
- # distill — merge-time reconciliation of a branch working set into the org brain
7
-
8
- > Status: **active.** Two runners, ONE SOP: the org's CI (`rafa distill
9
- > --headless`, installed by `rafa ci-setup`, driven by the ORG'S OWN
10
- > `ANTHROPIC_API_KEY` — never stored on the platform) and the dev session
11
- > (offer-driven at bootstrap, or explicit `/rafa distill <branch>`) as the
12
- > fallback when CI isn't wired or failed. Depends on: brain (#1), the branch
13
- > working set (synced via `rafa checkpoint`).
14
-
15
- The rigor gradient this enforces (ratified 2026-07-10): **dev↔dev = CAS + a
16
- session prompt · branch↔branch = free mechanical fold (no LLM, no prism) ·
17
- branch→main = full distillation + gates.** Cost tracks consequence. Working-set
18
- files are candidate-grade — attributed, loose, never served as org truth. The
19
- merge to main is the one moment rigor fires: what survives validation against
20
- merged main enters the org brain through the normal gates; what doesn't is
21
- refuted loudly back to its author; what can't be decided is flagged
22
- `needs-adjudication` — NEVER guessed. Knowledge propagates exactly like the
23
- code it describes.
24
-
25
- ## Trigger
26
-
27
- - **CI (normal path):** the reconcile workflow fires on the PR-merged EVENT
28
- (squash/rebase-safe — ancestry is never consulted): merge to the default
29
- branch → `rafa distill --headless <branch>` · branch→parent merge →
30
- `rafa fold --from <branch> --to <parent>` (mechanical, no LLM).
31
- - **Offer (session fallback):** at bootstrap the conductor checks
32
- `get_working_set` for active files on branches whose code reached main —
33
- *"branch <x> merged with N working-set files — distill now?"* Part of the
34
- ONE bootstrap digest. Boundary consent; accepted offer = invocation.
35
- - **Explicit:** `/rafa distill <branch>`.
36
-
37
- ## Procedure (the trio, distillation roles)
38
-
39
- 1. **Collect** — `get_working_set(repo, branch, status: active)`. Zero files →
40
- nothing to do, say so, stop. Rows already `needs-adjudication` are a HUMAN's
41
- to resolve — surface them in the digest, never fold them in silently.
42
- 1b. **Schema ladder (0.11.0 — the owner's 4-case doctrine).** Before ANY
43
- claim-level judging, resolve the version lattice: **target** = the org
44
- brain's `manifest.json → schemaVersion` (else max over its notes; a
45
- pre-manifest brain is the founding v1) · **source** = max over the
46
- collected candidates' own `schemaVersion` · **latest** = the running CLI's
47
- `SCHEMA_VERSION`. Then: **target < source → REWRITE-TARGET** (every org-
48
- brain note lifts to the newer schema and rides THIS run's gates + push —
49
- one migration+reconcile commit) · **target > source → UPGRADE-SOURCE**
50
- (incoming candidates lift into the target's schema before judging) ·
51
- **equal but < latest → REWRITE-MERGED** (both lift; the merged target is a
52
- full rewrite onto latest) · **equal and == latest → DIFF** (the happy flow,
53
- steps 2–6 exactly as written). **Either side > latest → ABORT LOUDLY** — a
54
- newer CLI authored it; update the runner, knowledge is never downgraded.
55
- Transforms are REGISTERED per step AND per OKF type
56
- (`lib/distiller/schema-ladder.mjs` — each step declares rule · playbook ·
57
- improvement explicitly: a function, or the EXPLICIT "unchanged", which
58
- still re-stamps the note's schemaVersion line); a missing step OR class is
59
- a loud error, never a guess. Only durable knowledge ladders — derived/
60
- generated files (coverage · checklist · ledger · reports · manifest)
61
- REGENERATE at the new schema; intent records pass through untouched. The
62
- headless CLI runs this mechanically; the session path applies the same
63
- lattice by hand.
64
- 2. **Validate (prism, context-isolated)** — the target is **merged MAIN as of
65
- NOW, never the fork point and never a stale checkout**: `git fetch origin`
66
- first; judge every claim against the fetched trunk. Confirm every citation
67
- resolves (the cited line contains its token — grep it yourself). A claim
68
- that can't be confirmed with a `file:line` is REFUTED (cited reason).
69
- Contested/low-confidence → `needs-adjudication`, never a guess. Files about
70
- code the merge did NOT touch are judged on their own merits, same bar.
71
- 3. **Author (atlas)** — for each survivor: write/update the org-brain file
72
- (contract §2) with real cites into main; `anchor:` on contracts (hydrated
73
- files lost it — re-declare); fold into an existing note when one covers the
74
- topic (supersede, never duplicate).
75
- 4. **Gate + ship** — `rafa verify-citations` AND `rafa compile` to exit 0 →
76
- `rafa push --verb=distill --message="reconcile(<branch>): <N> banked ·
77
- <M> refuted · <K> adjudication — <banked ids…>"`. The reconciliation
78
- commit is DESCRIPTIVE (owner 2026-07-26): its subject states what THIS run
79
- did — branch, per-verdict counts, the banked note ids — so any agent
80
- walking the brain log understands the commit without opening it (the
81
- headless CLI composes this automatically; the session path passes it
82
- explicitly). Only now has anything entered the org brain. A failed gate
83
- aborts EVERYTHING: nothing resolved, nothing pushed, working set intact.
84
- 5. **Resolve** — `resolve_working_file(path, distilled)` for survivors — authored
85
- per the OKF surface ([rafa-okf](../rafa-okf/SKILL.md); push emits the rest) —
86
- CAS: only a live row resolves; a failure means ANOTHER runner already
87
- distilled this branch — STOP and reconcile against what it shipped.
88
- `resolve_working_file(path, refuted, note)` for failures — TELL the author
89
- which files died and why (cited); refutation is feedback, not silence. At
90
- that same resolve beat emit **`report_loop_event(category:
91
- "distill-refutation", outcome: refuted|distilled, subject: <path or note
92
- id>, actorMeta: {model: <the judging model>, agent: "distiller@<ver>" (or
93
- "prism@<ver>" on the session path), runner: session|ci|sandbox — the tier
94
- this distill runs in}, dedupeKey: "<branch>·<path>·<outcome>")`** — sage's
95
- evidence that a claim met the gates or was refuted (shapes only: the
96
- outcome + a file/note reference, never the refuted content, never the
97
- cited code). Wave 5 is STRICT: an emit without the actor envelope is
98
- rejected loudly; the dedupeKey makes a re-run of the same distillation
99
- count ONCE. Checkpoint-adjacent, monotonic, never a session-end sweep.
100
- `resolve_working_file(path, needs-adjudication, note)` for the undecidable —
101
- the next session's digest offers the decision.
102
-
103
- ## Branch→branch merges (sub-feature → feature): FOLD, don't distill
104
-
105
- Distillation targets ONLY the default branch. When a sub-feature merges into
106
- its parent branch, the working set is **folded forward mechanically** —
107
- `rafa fold --from <branch> --to <parent>` (or the CI fold job): absent on the
108
- parent → re-keyed · identical content → merged silently · true same-file
109
- divergence → `needs-adjudication` on the parent row (incoming preserved,
110
- attributed). No LLM, no prism — the knowledge keeps riding with the code until
111
- the code reaches main.
112
-
113
- Sub-feature CONTEXT: a branch's working knowledge = org brain (via MCP) + its
114
- own working set + unmerged ANCESTOR branches' working sets (the conductor
115
- derives the ancestor chain from git and calls `get_working_set` per ancestor —
116
- the platform stores no lineage).
117
-
118
- ## Rules
119
-
120
- - Validation target is main at distillation time, never the fork point.
121
- - An abandoned branch's working set is never distilled — it dies with the
122
- branch (propagation mirrors code).
123
- - No file skips steps 2–4; there is no fast path into the org brain.
124
- - CI can detect and flag; only a dev session resolves a human's divergence.
125
- - Dev-level observations found among the files route to `put_dev_insight`
126
- (the author's user brain), never to the org brain.
package/lib/ci-setup.mjs DELETED
@@ -1,172 +0,0 @@
1
- // rafa ci-setup — install the reconciliation workflow (working-set
2
- // architecture, ratified 2026-07-10, rules 6–7): the org's own CI folds
3
- // branch→branch merges mechanically and DISTILLS merge-to-main with the org's
4
- // own LLM key. Writes .github/workflows/rafa-reconcile.yml and names the
5
- // secrets to configure — it configures nothing itself (secrets are the org's;
6
- // nothing sensitive ever transits rafinery).
7
-
8
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
9
- import { dirname, join } from "node:path";
10
-
11
- const WORKFLOW_PATH = ".github/workflows/rafa-reconcile.yml";
12
-
13
- const WORKFLOW = `# rafa reconcile — knowledge propagates exactly like the code it describes.
14
- #
15
- # branch → parent branch merge = MECHANICAL FOLD (no LLM, no prism)
16
- # branch → default branch merge = DISTILLATION (prism-validated vs merged main,
17
- # compile-gated, pushed to the brain repo)
18
- #
19
- # Secrets (Settings → Secrets and variables → Actions):
20
- # RAFA_MCP_KEY an agent key for this repo (platform → repo → Agent access)
21
- # ANTHROPIC_API_KEY the ORG'S OWN LLM key — used only inside this CI for
22
- # distillation; never stored on the rafinery platform
23
- # RAFA_BRAIN_TOKEN a token with push access to the BRAIN repo (distill only)
24
- #
25
- # Fires on the merge EVENT itself (PR closed+merged), so squash/rebase merges are
26
- # detected correctly. The distill job ALSO runs an ancestry sweep: any still-active
27
- # working-set row (any branch) whose capturedAtSha is reachable from merged main is
28
- # provably-merged backlog — an earlier merge whose distill never ran, or a direct
29
- # push — and folds into the same run. The sweep needs full history (fetch-depth: 0).
30
- # If distillation cannot run (missing secret, failed gate) it fails LOUD and the
31
- # branch's working set stays intact — the next dev session gets the standard
32
- # distill offer (fallback chain).
33
-
34
- name: rafa reconcile
35
-
36
- on:
37
- pull_request:
38
- types: [closed]
39
-
40
- jobs:
41
- fold:
42
- name: fold working set (branch → parent)
43
- if: github.event.pull_request.merged == true && github.event.pull_request.base.ref != github.event.repository.default_branch
44
- runs-on: ubuntu-latest
45
- steps:
46
- - uses: actions/checkout@v4
47
- - uses: actions/setup-node@v4
48
- with:
49
- node-version: 20
50
- - name: Mechanical fold (no LLM — rigor lives at main)
51
- env:
52
- RAFA_MCP_KEY: \${{ secrets.RAFA_MCP_KEY }}
53
- RAFA_MCP_URL: \${{ secrets.RAFA_MCP_URL }} # deployed platform /api/mcp — localhost never works from CI
54
- RAFA_HOOKS_DISABLED: "1" # headless — the M5 sensors are session instruments
55
- run: npx -y @rafinery/cli fold --from "\${{ github.event.pull_request.head.ref }}" --to "\${{ github.event.pull_request.base.ref }}"
56
-
57
- distill:
58
- name: distill working set (merge to main)
59
- if: github.event.pull_request.merged == true && github.event.pull_request.base.ref == github.event.repository.default_branch
60
- runs-on: ubuntu-latest
61
- steps:
62
- - uses: actions/checkout@v4
63
- with:
64
- ref: \${{ github.event.repository.default_branch }} # merged main = the validation target
65
- fetch-depth: 0 # full history — the ancestry sweep proves prior merges
66
- - uses: actions/setup-node@v4
67
- with:
68
- node-version: 20
69
- # ISOLATED install — never \`npm i\` inside the client checkout: a pnpm
70
- # workspace repo has "workspace:*" deps npm cannot parse (EUNSUPPORTEDPROTOCOL).
71
- - name: Install the Agent SDK (isolated dir; runs on the org's own key)
72
- run: |
73
- mkdir -p "\$RUNNER_TEMP/rafa-sdk" && cd "\$RUNNER_TEMP/rafa-sdk"
74
- npm init -y > /dev/null
75
- npm i @anthropic-ai/claude-agent-sdk
76
- - name: Distill into the org brain (prism-validated, compile-gated)
77
- env:
78
- ANTHROPIC_API_KEY: \${{ secrets.ANTHROPIC_API_KEY }}
79
- RAFA_MCP_KEY: \${{ secrets.RAFA_MCP_KEY }}
80
- RAFA_MCP_URL: \${{ secrets.RAFA_MCP_URL }} # deployed platform /api/mcp — localhost never works from CI
81
- RAFA_BRAIN_TOKEN: \${{ secrets.RAFA_BRAIN_TOKEN }}
82
- RAFA_AGENT_SDK_DIR: \${{ runner.temp }}/rafa-sdk
83
- RAFA_HOOKS_DISABLED: "1" # headless — the M5 sensors are session instruments
84
- RAFA_MERGE_SHA: \${{ github.event.pull_request.merge_commit_sha }} # keys the run record
85
- RAFA_TARGET_BRANCH: \${{ github.event.pull_request.base.ref }}
86
- run: npx -y @rafinery/cli distill --headless "\${{ github.event.pull_request.head.ref }}"
87
- `;
88
-
89
- // Pristine-detection for upgrades: a byte-diff alone cannot tell "user tuned
90
- // this" from "our template moved on" — and mislabeling a stale pristine
91
- // install as tuned strands it on old behavior forever (the ancestry sweep is
92
- // inert without fetch-depth: 0). Comparison ignores comments/blank lines;
93
- // PREVIOUS_NORMALIZED holds every historical pristine body, so a file that
94
- // matches one is upgraded in place and only a genuinely tuned file is left
95
- // alone. (Comment-only tuning is knowingly not preserved.)
96
- const normalize = (s) =>
97
- s
98
- .split("\n")
99
- .map((l) => l.trimEnd())
100
- .filter((l) => {
101
- const t = l.trim();
102
- return t !== "" && !t.startsWith("#");
103
- })
104
- .join("\n");
105
- const PREVIOUS_NORMALIZED = [
106
- // v1 (pre ancestry-sweep, pre run-record): identical body minus the lines
107
- // those two features added. Derived, not duplicated — v1 never shipped a
108
- // byte that differs otherwise.
109
- normalize(WORKFLOW)
110
- .split("\n")
111
- .filter(
112
- (l) =>
113
- !l.includes("fetch-depth: 0") &&
114
- !l.includes("RAFA_MERGE_SHA") &&
115
- !l.includes("RAFA_TARGET_BRANCH"),
116
- )
117
- .join("\n"),
118
- ];
119
-
120
- export default async function ciSetup(args = []) {
121
- const ROOT = process.cwd();
122
- const target = join(ROOT, WORKFLOW_PATH);
123
-
124
- console.log(
125
- "OPTIONAL — the platform reconciles merges automatically (no CI, no repo secrets).\n" +
126
- "This wires the org-CI ADAPTER instead: reconciliation compute runs in YOUR\n" +
127
- "GitHub Actions on YOUR secrets. Most teams should skip this.\n",
128
- );
129
-
130
- if (existsSync(target)) {
131
- const current = readFileSync(target, "utf8");
132
- if (current === WORKFLOW) {
133
- console.log(`✓ ${WORKFLOW_PATH} is already current`);
134
- } else if (args.includes("--overwrite")) {
135
- writeFileSync(target, WORKFLOW);
136
- console.log(`✓ ${WORKFLOW_PATH} updated (--overwrite)`);
137
- } else if (PREVIOUS_NORMALIZED.includes(normalize(current))) {
138
- writeFileSync(target, WORKFLOW);
139
- console.log(
140
- `✓ ${WORKFLOW_PATH} upgraded from an older pristine template (no local tuning detected)`,
141
- );
142
- } else {
143
- console.log(
144
- `! ${WORKFLOW_PATH} exists and differs — left untouched (yours may be tuned).\n` +
145
- " Re-run with --overwrite to replace it.",
146
- );
147
- }
148
- } else {
149
- mkdirSync(dirname(target), { recursive: true });
150
- writeFileSync(target, WORKFLOW);
151
- console.log(`✓ wrote ${WORKFLOW_PATH}`);
152
- }
153
-
154
- console.log(`
155
- Next steps (repo Settings → Secrets and variables → Actions):
156
- 1. RAFA_MCP_KEY — mint on the platform (repo → Agent access); scope: this repo.
157
- 2. RAFA_MCP_URL — your DEPLOYED platform's MCP endpoint (https://<host>/api/mcp).
158
- A localhost dev URL can never work from a CI runner.
159
- 3. ANTHROPIC_API_KEY — the ORG'S OWN LLM key. Used only inside YOUR CI for
160
- merge-to-main distillation; never stored on the rafinery
161
- platform (hard rule).
162
- 4. RAFA_BRAIN_TOKEN — a token with push access to the BRAIN repo (the distill
163
- job pushes validated knowledge there).
164
- 5. Commit the workflow via your normal MR flow.
165
- 6. Verify the whole capture chain: npx @rafinery/cli doctor (sensors, scripts,
166
- platform round-trip — exit 1 names any fix).
167
-
168
- The rigor gradient this wires: dev↔dev = CAS + session prompt · branch↔branch =
169
- free mechanical fold · branch→main = full distillation + gates. Cost tracks
170
- consequence. Without CI, nothing is lost — the next dev session's bootstrap
171
- digest offers the same reconciliation (/rafa distill).`);
172
- }