@rafinery/cli 0.3.0 → 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 +79 -0
- package/README.md +17 -7
- package/bin/rafa.mjs +68 -20
- 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 +151 -26
- package/blueprint/{.rafa → .claude/rafa}/contract.md +108 -22
- 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/{.rafa/capabilities/plan.md → .claude/skills/rafa-plan/SKILL.md} +30 -3
- 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 +15 -30
- package/lib/leverage/adapters/claude-code.mjs +5 -4
- package/lib/mcp-client.mjs +97 -0
- package/lib/mcp-config.mjs +1 -1
- package/lib/migrations/index.mjs +39 -3
- package/lib/pull.mjs +129 -0
- package/lib/push.mjs +93 -14
- package/lib/releases.mjs +23 -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 -478
- 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 -39
package/lib/push.mjs
CHANGED
|
@@ -1,24 +1,103 @@
|
|
|
1
|
-
// rafa push — compile (contract gate)
|
|
2
|
-
//
|
|
1
|
+
// rafa push — compile (contract gate), stamp the contract copy, commit `.rafa/`
|
|
2
|
+
// and push it to the brain remote.
|
|
3
|
+
//
|
|
4
|
+
// Blueprint split (0.4.0): the gate runs in-process (no vendored bin), `.rafa/`
|
|
5
|
+
// bootstraps lazily from the committed rafa.json, and a STAMPED copy of the
|
|
6
|
+
// canonical contract (.claude/rafa/contract.md, code repo) rides every push so
|
|
7
|
+
// the brain repo always carries the contract version it conforms to.
|
|
8
|
+
// Uses YOUR git auth — no credential passes through rafa. Run from the code
|
|
9
|
+
// repo root. Pushing is a MAIN-branch act: the org brain describes the default
|
|
10
|
+
// branch; branch work syncs through checkpoints, not brain pushes.
|
|
3
11
|
|
|
4
|
-
import { existsSync } from "node:fs";
|
|
5
12
|
import { execSync } from "node:child_process";
|
|
13
|
+
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
6
14
|
import { join } from "node:path";
|
|
15
|
+
import { runCompile } from "./gate/compile.mjs";
|
|
16
|
+
import { ensureBrainRepo, remoteDefaultBranch, inDir, die } from "./brain-repo.mjs";
|
|
17
|
+
import { CLI_VERSION } from "./releases.mjs";
|
|
7
18
|
|
|
8
|
-
export default async function push(args) {
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
19
|
+
export default async function push(args = []) {
|
|
20
|
+
const ROOT = process.cwd();
|
|
21
|
+
const { rafaDir } = ensureBrainRepo(ROOT);
|
|
22
|
+
const inRafa = inDir(rafaDir);
|
|
23
|
+
|
|
24
|
+
// Anchor the brain to the current code HEAD.
|
|
25
|
+
let codeSha = "unknown";
|
|
26
|
+
try {
|
|
27
|
+
codeSha = execSync("git rev-parse --short HEAD", { cwd: ROOT, encoding: "utf8" }).trim();
|
|
28
|
+
} catch {
|
|
29
|
+
/* code repo may have no commits yet — fine */
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// The CODE repo's full_name — stamped into the manifest as its identity.
|
|
33
|
+
let repoFull = "";
|
|
34
|
+
try {
|
|
35
|
+
const origin = execSync("git remote get-url origin", { cwd: ROOT, encoding: "utf8" }).trim();
|
|
36
|
+
const m = origin.match(/[/:]([^/:]+)\/([^/]+?)(?:\.git)?\/?$/);
|
|
37
|
+
if (m) repoFull = `${m[1]}/${m[2]}`;
|
|
38
|
+
} catch {
|
|
39
|
+
/* no origin — manifest.repo stays "" */
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// ── contract gate (in-process) ──
|
|
43
|
+
// Compile + validate the brain. A schema-invalid brain NEVER leaves the machine —
|
|
44
|
+
// the platform ingests JSON, so what we push must already conform to the contract.
|
|
45
|
+
console.log("• rafa compile — validating against the contract …");
|
|
46
|
+
if (runCompile([`--repo=${repoFull}`]) !== 0)
|
|
47
|
+
die(
|
|
48
|
+
"brain failed the contract (see errors above). Fix the files, or have the " +
|
|
49
|
+
"authoring agent (atlas/bloom/prism) correct them, then re-push.",
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
// ── stamped contract copy rides the push ──
|
|
53
|
+
// The canonical contract lives in the CODE repo (.claude/rafa/contract.md);
|
|
54
|
+
// the brain repo carries a stamped copy so any reader of the brain repo knows
|
|
55
|
+
// exactly which contract version this brain conforms to.
|
|
56
|
+
const canonical = join(ROOT, ".claude", "rafa", "contract.md");
|
|
57
|
+
if (existsSync(canonical)) {
|
|
58
|
+
const stampHeader =
|
|
59
|
+
`<!-- STAMPED COPY — canonical: .claude/rafa/contract.md in the code repo.\n` +
|
|
60
|
+
` cli: ${CLI_VERSION} · brain-for: ${codeSha} · stamped: ${new Date().toISOString()} -->\n\n`;
|
|
61
|
+
writeFileSync(join(rafaDir, "contract.md"), stampHeader + readFileSync(canonical, "utf8"));
|
|
62
|
+
} else {
|
|
63
|
+
console.log(
|
|
64
|
+
" ! no .claude/rafa/contract.md in this repo — pushing without the stamped contract copy " +
|
|
65
|
+
"(run `rafa update` to vendor it)",
|
|
13
66
|
);
|
|
14
|
-
process.exit(1);
|
|
15
67
|
}
|
|
68
|
+
|
|
69
|
+
inRafa("git add -A");
|
|
70
|
+
try {
|
|
71
|
+
inRafa(`git commit -m "brain: scan" -m "brain-for: ${codeSha}"`);
|
|
72
|
+
} catch {
|
|
73
|
+
console.log("• nothing new to commit — pushing current state");
|
|
74
|
+
}
|
|
75
|
+
const branch = remoteDefaultBranch(rafaDir);
|
|
76
|
+
inRafa(`git branch -M ${branch}`);
|
|
16
77
|
try {
|
|
17
|
-
|
|
18
|
-
stdio: "inherit",
|
|
19
|
-
cwd: process.cwd(),
|
|
20
|
-
});
|
|
78
|
+
inRafa(`git push -u origin ${branch}`);
|
|
21
79
|
} catch {
|
|
22
|
-
|
|
80
|
+
// Someone pushed since our last pull. TRANSPORT replay (not knowledge
|
|
81
|
+
// merging): rebase our local commits onto the remote tip — child-owned plan
|
|
82
|
+
// files and improve rows are disjoint by design, so this is clean in the
|
|
83
|
+
// normal case. A real conflict aborts loudly with local work INTACT.
|
|
84
|
+
console.log("• remote is ahead — replaying local commits on top (pull --rebase) …");
|
|
85
|
+
try {
|
|
86
|
+
inRafa(`git pull --rebase origin ${branch}`);
|
|
87
|
+
inRafa(`git push -u origin ${branch}`);
|
|
88
|
+
} catch {
|
|
89
|
+
try {
|
|
90
|
+
inRafa("git rebase --abort");
|
|
91
|
+
} catch {
|
|
92
|
+
/* no rebase in progress */
|
|
93
|
+
}
|
|
94
|
+
die(
|
|
95
|
+
"push conflicts with the remote on the SAME file — local work kept intact. " +
|
|
96
|
+
"Conflicting knowledge is never hand-merged: run `rafa pull --full --force` to adopt the " +
|
|
97
|
+
"remote (your local .rafa is a cache) and re-derive, or resolve the plan-file " +
|
|
98
|
+
"conflict manually inside .rafa and re-push.",
|
|
99
|
+
);
|
|
100
|
+
}
|
|
23
101
|
}
|
|
102
|
+
console.log(`✓ Pushed .rafa/ → brain remote (${branch} · brain-for: ${codeSha})`);
|
|
24
103
|
}
|
package/lib/releases.mjs
CHANGED
|
@@ -69,6 +69,29 @@ export const RELEASES = [
|
|
|
69
69
|
"wired: compile now emits `plans[]` + `activePlanId` with global-id/parent/progress rules. " +
|
|
70
70
|
"Re-run `/rafa scan` (or push) to surface plans in your manifest; no re-scan required to adopt.",
|
|
71
71
|
},
|
|
72
|
+
{
|
|
73
|
+
version: "0.4.0",
|
|
74
|
+
contract: 1,
|
|
75
|
+
plans: 1,
|
|
76
|
+
requires: "migrate",
|
|
77
|
+
summary:
|
|
78
|
+
"The working-set blueprint. BLUEPRINT SPLIT: gate tools live INSIDE the CLI (compile/" +
|
|
79
|
+
"verify-citations/push/pull — nothing vendored under .rafa/bin); capabilities are committed " +
|
|
80
|
+
"skills (.claude/skills/rafa-*); the contract is canonical in the code repo " +
|
|
81
|
+
"(.claude/rafa/contract.md; stamped copy rides brain pushes); .rafa/ is lazy + gitignored " +
|
|
82
|
+
"and bootstraps from the committed rafa.json (clone → npx rafa pull just works). " +
|
|
83
|
+
"WORKING SET: branch capture is files under .rafa/brain/** — rafa hydrate faults notes in, " +
|
|
84
|
+
"rafa checkpoint syncs under base-version CAS at checkpoint moments (never session-end); " +
|
|
85
|
+
"conflicts are YOUR decision, never auto-resolved. PLANS CHANNEL: push-on-approval " +
|
|
86
|
+
"(push_plan, bodies stored), list = names only, load materializes; terminal statuses " +
|
|
87
|
+
"superseded/abandoned; plans never ride the manifest. CI RECONCILIATION: rafa ci-setup " +
|
|
88
|
+
"installs the reconcile workflow — mechanical fold on branch→parent merges (rafa fold), " +
|
|
89
|
+
"headless distillation on merge-to-main (rafa distill --headless, on the ORG'S OWN " +
|
|
90
|
+
"ANTHROPIC_API_KEY — never stored on the platform). Also: contract-governed agent cards " +
|
|
91
|
+
"(§10, compile-gated), intent dispatch, plan-lite, compass (4th agent), /rafa insights. " +
|
|
92
|
+
"Brain data schema UNCHANGED (no re-scan); the mechanical blueprint-split migration " +
|
|
93
|
+
"cleans the old vendored files; re-push plans once through the new channel.",
|
|
94
|
+
},
|
|
72
95
|
];
|
|
73
96
|
|
|
74
97
|
// The release this CLI build ships (last entry).
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// rafa verify-citations — the deterministic citation checker (B1 resolution ·
|
|
2
|
+
// B2 completeness · contract-anchor policy). Lives inside the CLI (blueprint
|
|
3
|
+
// split, 0.4.0). Agents run it until it exits 0; prism re-runs it blind.
|
|
4
|
+
|
|
5
|
+
import { runVerifyCitations } from "./gate/verify-citations.mjs";
|
|
6
|
+
|
|
7
|
+
export default async function verifyCitations(args = []) {
|
|
8
|
+
process.exit(runVerifyCitations(args));
|
|
9
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
// Shared working-set bookkeeping (working-set architecture, ratified 2026-07-10).
|
|
2
|
+
//
|
|
3
|
+
// `.rafa/hydration.json` is the DETERMINISTIC sidecar that makes "unedited
|
|
4
|
+
// hydrated file = disposable cache, edited/new file = working set" a hash
|
|
5
|
+
// comparison, not a judgment call:
|
|
6
|
+
//
|
|
7
|
+
// files[path] { baseBrainSha, hash } set at hydration
|
|
8
|
+
// sync[branch][path] { version, hash } set on accepted checkpoint
|
|
9
|
+
// sync[branch][path].conflictSeen server version surfaced as a
|
|
10
|
+
// conflict; the NEXT checkpoint
|
|
11
|
+
// (after the human decided)
|
|
12
|
+
// pushes against it
|
|
13
|
+
//
|
|
14
|
+
// The sidecar is state about cache-ness, never knowledge — gitignored with the
|
|
15
|
+
// rest of `.rafa/`, regenerable by re-hydrating.
|
|
16
|
+
|
|
17
|
+
import { createHash } from "node:crypto";
|
|
18
|
+
import {
|
|
19
|
+
existsSync,
|
|
20
|
+
mkdirSync,
|
|
21
|
+
readFileSync,
|
|
22
|
+
readdirSync,
|
|
23
|
+
statSync,
|
|
24
|
+
writeFileSync,
|
|
25
|
+
} from "node:fs";
|
|
26
|
+
import { dirname, join } from "node:path";
|
|
27
|
+
|
|
28
|
+
// The working-set scan roots inside .rafa/ — brain-shaped authoring surfaces.
|
|
29
|
+
export const WORKING_DIRS = ["brain/rules", "brain/playbooks"];
|
|
30
|
+
|
|
31
|
+
export const hashOf = (text) => createHash("sha256").update(text).digest("hex");
|
|
32
|
+
|
|
33
|
+
export function sidecarPath(cwd) {
|
|
34
|
+
return join(cwd, ".rafa", "hydration.json");
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function readSidecar(cwd) {
|
|
38
|
+
const p = sidecarPath(cwd);
|
|
39
|
+
if (!existsSync(p)) return { files: {}, sync: {} };
|
|
40
|
+
try {
|
|
41
|
+
const d = JSON.parse(readFileSync(p, "utf8"));
|
|
42
|
+
return { files: d.files ?? {}, sync: d.sync ?? {} };
|
|
43
|
+
} catch {
|
|
44
|
+
// A corrupt sidecar means cache-ness is unknowable — treat everything as
|
|
45
|
+
// working set (over-sync is safe; silent loss is not). Start fresh.
|
|
46
|
+
return { files: {}, sync: {} };
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function writeSidecar(cwd, data) {
|
|
51
|
+
const p = sidecarPath(cwd);
|
|
52
|
+
mkdirSync(dirname(p), { recursive: true });
|
|
53
|
+
writeFileSync(p, JSON.stringify(data, null, 2) + "\n");
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Every candidate working-set file under .rafa/ (brain-relative paths).
|
|
57
|
+
// `.theirs.md` conflict copies and `_*` scratch are never part of the set.
|
|
58
|
+
export function scanWorkingFiles(cwd) {
|
|
59
|
+
const out = [];
|
|
60
|
+
for (const dir of WORKING_DIRS) {
|
|
61
|
+
const abs = join(cwd, ".rafa", dir);
|
|
62
|
+
if (!existsSync(abs)) continue;
|
|
63
|
+
for (const e of readdirSync(abs)) {
|
|
64
|
+
const p = join(abs, e);
|
|
65
|
+
if (statSync(p).isDirectory()) continue;
|
|
66
|
+
if (!e.endsWith(".md") || e.startsWith("_") || e.endsWith(".theirs.md")) continue;
|
|
67
|
+
out.push({ path: `${dir}/${e}`, abs });
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return out;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Write a hydrated org-brain note to disk + record its cache identity.
|
|
74
|
+
// Returns the brain-relative path.
|
|
75
|
+
export function materializeNote(cwd, kind, data, brainForSha) {
|
|
76
|
+
const dir = kind === "rule" ? "brain/rules" : "brain/playbooks";
|
|
77
|
+
const rel = `${dir}/${data.id}.md`;
|
|
78
|
+
const abs = join(cwd, ".rafa", rel);
|
|
79
|
+
const content = renderNote(data);
|
|
80
|
+
mkdirSync(dirname(abs), { recursive: true });
|
|
81
|
+
writeFileSync(abs, content);
|
|
82
|
+
const sidecar = readSidecar(cwd);
|
|
83
|
+
sidecar.files[rel] = {
|
|
84
|
+
...(brainForSha ? { baseBrainSha: brainForSha } : {}),
|
|
85
|
+
hash: hashOf(content),
|
|
86
|
+
};
|
|
87
|
+
writeSidecar(cwd, sidecar);
|
|
88
|
+
return rel;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Deterministic contract-§2 frontmatter writer for hydrated notes. The MCP
|
|
92
|
+
// serves fields, not raw files, so hydration reconstructs the file; `anchor:`
|
|
93
|
+
// is not served — a contract note edited after hydration must re-declare it
|
|
94
|
+
// before distillation (the policy gate catches the omission loudly).
|
|
95
|
+
const q = (s) => `"${String(s).replace(/"/g, '\\"')}"`;
|
|
96
|
+
export function renderNote(d) {
|
|
97
|
+
const lines = [
|
|
98
|
+
"---",
|
|
99
|
+
"schemaVersion: 1",
|
|
100
|
+
`id: ${d.id}`,
|
|
101
|
+
`type: ${d.type}`,
|
|
102
|
+
`domain: ${d.domain}`,
|
|
103
|
+
`title: ${q(d.title)}`,
|
|
104
|
+
...(d.summary ? [`summary: ${q(d.summary)}`] : []),
|
|
105
|
+
`links: [${(d.links ?? []).join(", ")}]`,
|
|
106
|
+
...(d.failure ? [`failure: ${d.failure}`] : []),
|
|
107
|
+
"cites:",
|
|
108
|
+
...(d.cites ?? []).map((c) => ` - ${c.file}:${c.line} :: ${c.token}`),
|
|
109
|
+
"---",
|
|
110
|
+
"",
|
|
111
|
+
];
|
|
112
|
+
return lines.join("\n") + (d.body ?? "") + "\n";
|
|
113
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rafinery/cli",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "rafa
|
|
3
|
+
"version": "0.4.0",
|
|
4
|
+
"description": "rafa \u2014 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": {
|
|
7
7
|
"rafa": "bin/rafa.mjs"
|