@rafinery/cli 0.13.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/CHANGELOG.md +29 -0
- package/bin/rafa.mjs +4 -15
- package/blueprint/.claude/agents/atlas.md +27 -5
- package/blueprint/.claude/agents/bloom.md +24 -4
- package/blueprint/.claude/agents/prism.md +12 -3
- package/blueprint/.claude/commands/rafa.md +19 -8
- package/blueprint/.claude/rafa/contract.md +114 -0
- package/blueprint/.claude/skills/rafa-build/SKILL.md +5 -6
- package/blueprint/.claude/skills/rafa-improve/SKILL.md +18 -3
- package/blueprint/.claude/skills/rafa-plan/SKILL.md +17 -17
- package/blueprint/.claude/skills/rafa-scan/SKILL.md +44 -10
- package/blueprint/.claude/skills/rafa-security/SKILL.md +15 -8
- package/lib/blueprint.mjs +0 -1
- package/lib/doctor.mjs +1 -1
- package/lib/gate/compile.mjs +6 -0
- package/lib/gate/verify-citations.mjs +8 -1
- package/lib/releases.mjs +29 -0
- package/package.json +1 -1
- package/skills-bundle/skills-manifest.json +1 -1
- package/blueprint/.claude/skills/rafa-distill/SKILL.md +0 -126
- package/lib/ci-setup.mjs +0 -172
- package/lib/distill.mjs +0 -665
- package/lib/distiller/schema-ladder.mjs +0 -186
|
@@ -1,186 +0,0 @@
|
|
|
1
|
-
// The schema ladder (0.11.0 — schema-aware reconciliation, owner's 4-case
|
|
2
|
-
// doctrine, 2026-07-25). Distillation is where knowledge from two lineages
|
|
3
|
-
// meets, so it is ALSO where brain schema versions meet — the reconciler
|
|
4
|
-
// resolves the version lattice BEFORE any claim-level judging, and the merged
|
|
5
|
-
// result always lands on the newest schema the RUNNER understands:
|
|
6
|
-
//
|
|
7
|
-
// 1. target < source → REWRITE-TARGET: the whole target brain
|
|
8
|
-
// lifts to the newer schema (the incoming side already speaks it).
|
|
9
|
-
// 2. target > source → UPGRADE-SOURCE: incoming old-schema
|
|
10
|
-
// candidates lift into the target's newer schema before judging.
|
|
11
|
-
// 3. target == source < latest → REWRITE-MERGED: both sides lift; the
|
|
12
|
-
// merged target is a full rewrite onto the latest schema.
|
|
13
|
-
// 4. target == source == latest → DIFF: the happy flow, exactly today.
|
|
14
|
-
// ∅. either side > latest → ABORT LOUDLY: a newer CLI authored this;
|
|
15
|
-
// this runner cannot judge a schema it does not know — update the
|
|
16
|
-
// runner, NEVER downgrade knowledge.
|
|
17
|
-
//
|
|
18
|
-
// Every non-abort mode converges in ONE pass: toVersion is always the
|
|
19
|
-
// runner's latest (which case 1/2's higher side already equals or precedes —
|
|
20
|
-
// the runner can never know less than what it authored). Transforms are
|
|
21
|
-
// REGISTERED per step (v(N-1)→vN) in LADDER below; a missing step throws —
|
|
22
|
-
// the ladder never guesses a schema (no assumed values). v1 is the baseline
|
|
23
|
-
// and the only schema in existence today, so the registry ships empty: this
|
|
24
|
-
// module is the SEAM, proven by tests, that makes a future v2 a one-entry
|
|
25
|
-
// change instead of a migration crisis.
|
|
26
|
-
|
|
27
|
-
import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
|
|
28
|
-
import { join } from "node:path";
|
|
29
|
-
|
|
30
|
-
// Per-step, PER-OKF-TYPE transforms (owner 2026-07-26: each file class has its
|
|
31
|
-
// own shape in the contract's type registry, so each ladder step declares each
|
|
32
|
-
// class EXPLICITLY):
|
|
33
|
-
//
|
|
34
|
-
// LADDER[2] = {
|
|
35
|
-
// describe: "what v2 changes",
|
|
36
|
-
// rule: (content) => content2, // a real shape change for rules
|
|
37
|
-
// playbook: "unchanged", // EXPLICIT identity — visible, auditable
|
|
38
|
-
// improvement: (content) => …,
|
|
39
|
-
// }
|
|
40
|
-
//
|
|
41
|
-
// Only the three DURABLE knowledge classes ladder (rule · playbook ·
|
|
42
|
-
// improvement — the classes distillation judges and the brain keeps). Derived/
|
|
43
|
-
// generated files (coverage, checklist, ledger, reconciliation reports,
|
|
44
|
-
// manifest) are NEVER migrated — their producers regenerate them at the new
|
|
45
|
-
// schema. A step missing a class entry is a LOUD error, not implicit identity:
|
|
46
|
-
// "unchanged" must be declared, never assumed. Every applied step (identity
|
|
47
|
-
// included) re-stamps the note's own `schemaVersion:` line.
|
|
48
|
-
export const LADDER = {};
|
|
49
|
-
|
|
50
|
-
export const LADDER_CLASSES = ["rule", "playbook", "improvement"];
|
|
51
|
-
|
|
52
|
-
// Class resolution chain (owner 2026-07-26): DIRECTORY first — inside rafa's
|
|
53
|
-
// transport the path is governed identity (the CAS row key, the registry
|
|
54
|
-
// glob, the hydrate target; it cannot drift without becoming a different
|
|
55
|
-
// row) — then the PORTABLE filename suffix (`.rule.md` · `.playbook.md` ·
|
|
56
|
-
// `.improvement.md`), which travels WITH a file that leaves the canonical
|
|
57
|
-
// tree (foreign OKF bundles, out-of-context shares) while staying outside
|
|
58
|
-
// the corruptible content. When BOTH are present they must AGREE — a
|
|
59
|
-
// contradiction throws loudly, never a guess. Returns null for non-laddered
|
|
60
|
-
// paths (intent records, reports, …) — those pass through untouched.
|
|
61
|
-
const SUFFIX_CLASS = {
|
|
62
|
-
".rule.md": "rule",
|
|
63
|
-
".playbook.md": "playbook",
|
|
64
|
-
".improvement.md": "improvement",
|
|
65
|
-
};
|
|
66
|
-
export function classOf(path) {
|
|
67
|
-
const p = String(path).replace(/\\/g, "/");
|
|
68
|
-
const byDir = p.includes("brain/rules/")
|
|
69
|
-
? "rule"
|
|
70
|
-
: p.includes("brain/playbooks/")
|
|
71
|
-
? "playbook"
|
|
72
|
-
: p.includes("improve/improvements/")
|
|
73
|
-
? "improvement"
|
|
74
|
-
: null;
|
|
75
|
-
const bySuffix =
|
|
76
|
-
Object.entries(SUFFIX_CLASS).find(([s]) => p.endsWith(s))?.[1] ?? null;
|
|
77
|
-
if (byDir && bySuffix && byDir !== bySuffix)
|
|
78
|
-
throw new Error(
|
|
79
|
-
`schema ladder: directory class "${byDir}" contradicts filename class "${bySuffix}" (${p}) — ` +
|
|
80
|
-
"fix the file's home or its name; the ladder never guesses",
|
|
81
|
-
);
|
|
82
|
-
return byDir ?? bySuffix;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// A note's OWN declared schema (first-fence `schemaVersion:`). Default 1 — the
|
|
86
|
-
// founding schema, same convention as stampedVersions defaulting to the first
|
|
87
|
-
// release (a pre-stamp file predates versioning, it isn't unversioned).
|
|
88
|
-
export function noteSchemaVersion(content) {
|
|
89
|
-
if (typeof content !== "string" || !content.startsWith("---")) return 1;
|
|
90
|
-
const end = content.indexOf("\n---", 3);
|
|
91
|
-
const fm = end === -1 ? content : content.slice(0, end);
|
|
92
|
-
const m = fm.match(/^schemaVersion:\s*(\d+)\s*$/m);
|
|
93
|
-
return m ? Number(m[1]) : 1;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
const NOTE_DIRS = ["brain/rules", "brain/playbooks", "improve/improvements"];
|
|
97
|
-
|
|
98
|
-
export function walkBrainNotes(rafaDir) {
|
|
99
|
-
const out = [];
|
|
100
|
-
const walk = (dir) => {
|
|
101
|
-
if (!existsSync(dir)) return;
|
|
102
|
-
for (const e of readdirSync(dir)) {
|
|
103
|
-
const p = join(dir, e);
|
|
104
|
-
if (statSync(p).isDirectory()) walk(p);
|
|
105
|
-
else if (e.endsWith(".md") && !e.startsWith("_") && !e.endsWith(".theirs.md"))
|
|
106
|
-
out.push(p);
|
|
107
|
-
}
|
|
108
|
-
};
|
|
109
|
-
for (const d of NOTE_DIRS) walk(join(rafaDir, d));
|
|
110
|
-
return out;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
// The TARGET brain's schema: manifest.json first (the compiled truth), else
|
|
114
|
-
// the max over its notes' own declarations, else the founding schema.
|
|
115
|
-
export function brainSchemaVersion(rafaDir) {
|
|
116
|
-
try {
|
|
117
|
-
const m = JSON.parse(readFileSync(join(rafaDir, "manifest.json"), "utf8"));
|
|
118
|
-
if (Number.isInteger(m.schemaVersion)) return m.schemaVersion;
|
|
119
|
-
} catch {
|
|
120
|
-
/* no manifest — fall through to the notes */
|
|
121
|
-
}
|
|
122
|
-
const versions = walkBrainNotes(rafaDir).map((f) =>
|
|
123
|
-
noteSchemaVersion(readFileSync(f, "utf8")),
|
|
124
|
-
);
|
|
125
|
-
return versions.length ? Math.max(...versions) : 1;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
// The SOURCE side's schema: the max over the incoming candidates' own
|
|
129
|
-
// declarations (a working set may be mixed if it straddled an upgrade).
|
|
130
|
-
export function sourceSchemaVersion(contents) {
|
|
131
|
-
const versions = (contents ?? []).map(noteSchemaVersion);
|
|
132
|
-
return versions.length ? Math.max(...versions) : 1;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
// The lattice. Returns {mode, toVersion, rewriteTarget, upgradeSource, reason}.
|
|
136
|
-
export function resolveSchemaPlan({ target, source, latest }) {
|
|
137
|
-
if (target > latest || source > latest)
|
|
138
|
-
return {
|
|
139
|
-
mode: "abort-newer-than-runner",
|
|
140
|
-
toVersion: null,
|
|
141
|
-
rewriteTarget: false,
|
|
142
|
-
upgradeSource: false,
|
|
143
|
-
reason:
|
|
144
|
-
`brain schema v${Math.max(target, source)} is NEWER than this runner's v${latest} — ` +
|
|
145
|
-
"a newer CLI authored it. Update @rafinery/cli and re-run; knowledge is never downgraded.",
|
|
146
|
-
};
|
|
147
|
-
const base = { toVersion: latest, rewriteTarget: target < latest, upgradeSource: source < latest };
|
|
148
|
-
if (target < source)
|
|
149
|
-
return { mode: "rewrite-target", ...base, reason: `target v${target} < source v${source} — full target rewrite onto v${latest}` };
|
|
150
|
-
if (source < target)
|
|
151
|
-
return { mode: "upgrade-source", ...base, reason: `source v${source} < target v${target} — incoming candidates lift into v${latest}` };
|
|
152
|
-
if (target < latest)
|
|
153
|
-
return { mode: "rewrite-merged", ...base, reason: `both sides v${target} < runner v${latest} — merged result rewrites onto v${latest}` };
|
|
154
|
-
return { mode: "diff", ...base, reason: `both sides already v${latest} — plain diff reconcile` };
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
const stampVersion = (content, v) =>
|
|
158
|
-
content.replace(/^schemaVersion:\s*\d+\s*$/m, `schemaVersion: ${v}`);
|
|
159
|
-
|
|
160
|
-
// Lift ONE note of ONE class from its own declared version to `to`, one
|
|
161
|
-
// registered step at a time. A missing step OR a missing class entry within a
|
|
162
|
-
// step is a loud error, never a guess; the explicit "unchanged" is the only
|
|
163
|
-
// legal identity, and even it re-stamps the note's schemaVersion line.
|
|
164
|
-
export function applyLadder(content, to, cls) {
|
|
165
|
-
if (!LADDER_CLASSES.includes(cls))
|
|
166
|
-
throw new Error(
|
|
167
|
-
`schema ladder: "${cls}" is not a laddered class (${LADDER_CLASSES.join("|")}) — ` +
|
|
168
|
-
"derived/generated files regenerate at the new schema, they never migrate",
|
|
169
|
-
);
|
|
170
|
-
let from = noteSchemaVersion(content);
|
|
171
|
-
let out = content;
|
|
172
|
-
while (from < to) {
|
|
173
|
-
const step = LADDER[from + 1];
|
|
174
|
-
const t = step?.[cls];
|
|
175
|
-
if (t === undefined)
|
|
176
|
-
throw new Error(
|
|
177
|
-
`schema ladder: no registered ${cls} transform v${from}→v${from + 1} — every step ` +
|
|
178
|
-
'declares every class (a function, or the EXPLICIT "unchanged"); ' +
|
|
179
|
-
"update @rafinery/cli to a release that ships it (the ladder never guesses a schema)",
|
|
180
|
-
);
|
|
181
|
-
out = t === "unchanged" ? out : t(out);
|
|
182
|
-
out = stampVersion(out, from + 1);
|
|
183
|
-
from += 1;
|
|
184
|
-
}
|
|
185
|
-
return out;
|
|
186
|
-
}
|