@rafinery/cli 0.2.1 → 0.2.2
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 +13 -0
- package/bin/rafa.mjs +4 -0
- package/blueprint/.claude/commands/rafa.md +30 -7
- package/lib/blueprint.mjs +54 -25
- package/lib/init.mjs +38 -6
- package/lib/prompt.mjs +21 -0
- package/lib/releases.mjs +10 -0
- package/lib/update.mjs +5 -3
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,19 @@ 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.2.2
|
|
8
|
+
|
|
9
|
+
**Adopt with:** `npx @rafinery/cli@latest update` — no re-scan, no migration.
|
|
10
|
+
|
|
11
|
+
Completes 0.2.1 (which was published before these two landed):
|
|
12
|
+
|
|
13
|
+
- **shadcn-style overwrite, no more `.new` clutter.** When a vendored file the dev owns already
|
|
14
|
+
exists and differs, `init`/`update` **ask** "overwrite? [y/N/a/q]" and act on the answer —
|
|
15
|
+
instead of silently writing `<file>.new`. Flags `--overwrite` / `--keep` skip the prompt; a
|
|
16
|
+
non-TTY (CI / agent shell) safely keeps files. Stale `.new` files from older CLIs are cleaned up.
|
|
17
|
+
- **`/rafa help`** — a full command reference for both surfaces (in-editor `/rafa` vs terminal
|
|
18
|
+
`rafa` CLI), also shown on no argument or an unrecognized command.
|
|
19
|
+
|
|
7
20
|
## 0.2.1
|
|
8
21
|
|
|
9
22
|
**Adopt with:** `npx @rafinery/cli@latest update` (or `/rafa update` in Claude Code) — no re-scan,
|
package/bin/rafa.mjs
CHANGED
|
@@ -29,6 +29,10 @@ Commands:
|
|
|
29
29
|
init [<setup-url>] Provision this repo — vendor the blueprint (agents, capabilities,
|
|
30
30
|
contract, gates) and, with a platform setup URL, wire the brain remote.
|
|
31
31
|
update Re-sync the blueprint into this repo (you own your copy; opt-in updates).
|
|
32
|
+
|
|
33
|
+
Flags (init/update): --overwrite replace changed owned files without asking
|
|
34
|
+
--keep keep all changed owned files without asking
|
|
35
|
+
(default: ask per file; in a non-TTY it keeps them)
|
|
32
36
|
compile Validate the brain against the contract → .rafa/manifest.json.
|
|
33
37
|
push Compile, then push .rafa/ to your brain remote.
|
|
34
38
|
leverage Inspect your agent toolbox (permissions, skills, MCP) and print
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
description: rafa — init · scan · improve · push · leverage · migrate · update. Usage: /rafa <init|scan|improve|push|leverage|migrate|update> [--brain-only]
|
|
2
|
+
description: rafa — init · scan · improve · push · leverage · migrate · update · help. Usage: /rafa <init|scan|improve|push|leverage|migrate|update|help> [--brain-only]
|
|
3
3
|
---
|
|
4
4
|
|
|
5
5
|
You are the **conductor** of the rafa agent loop, running in the **main session**.
|
|
@@ -100,13 +100,36 @@ understood to be rewritten correctly — need intelligence, and that's this comm
|
|
|
100
100
|
on the dev's approval, `rafa push` if this is a connected repo.
|
|
101
101
|
Never hand-edit around a migration or discard tuned files.
|
|
102
102
|
|
|
103
|
-
## (no argument, or unrecognized)
|
|
104
|
-
|
|
105
|
-
`leverage` · `migrate` · `update`.
|
|
103
|
+
## `help` (also: no argument, or an unrecognized command)
|
|
104
|
+
Print this reference verbatim and stop.
|
|
106
105
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
106
|
+
**rafa has two surfaces.** `/rafa <cmd>` runs the intelligent, in-editor passes; the terminal
|
|
107
|
+
`rafa` CLI (run via `npx @rafinery/cli@latest <cmd>`) does the deterministic plumbing. Several
|
|
108
|
+
names exist on both — the CLI does the mechanical half, `/rafa` the intelligent half.
|
|
109
|
+
|
|
110
|
+
**`/rafa` — in the editor (LLM does the work):**
|
|
111
|
+
| Command | What it does |
|
|
112
|
+
|---|---|
|
|
113
|
+
| `/rafa init` | First run: ensure structure, then run the full scan pass. |
|
|
114
|
+
| `/rafa scan [--brain-only]` | The full pass: know → verify → improve → push. `--brain-only` stops after the brain is validated (a cheap knowledge refresh). |
|
|
115
|
+
| `/rafa improve` | The improvement pass — bloom writes a cited, prioritized (P0–P3) ledger. |
|
|
116
|
+
| `/rafa push` | (Re-)push the brain to the brain remote (your git auth). |
|
|
117
|
+
| `/rafa leverage` | Tune your toolbox: reason over config and, on approval, apply fixes exactly — merge settings, wire an MCP, scaffold a skill. |
|
|
118
|
+
| `/rafa migrate` | Semantic migration — rewrite plans to a new schema preserving meaning, then compile-gate. |
|
|
119
|
+
| `/rafa update` | The brain-side of an upgrade: after the CLI syncs the blueprint, run the migrations that need intelligence (re-scan a stale brain / rewrite plans). |
|
|
120
|
+
|
|
121
|
+
**`rafa` — in the terminal (`npx @rafinery/cli@latest <cmd>`; deterministic):**
|
|
122
|
+
| Command | What it does |
|
|
123
|
+
|---|---|
|
|
124
|
+
| `init [<setup-url>]` | Vendor the blueprint into the repo (+ wire the brain remote from a platform setup URL). |
|
|
125
|
+
| `update [--overwrite\|--keep]` | Blueprint-side of an upgrade: re-sync the blueprint (asks before overwriting files you tuned), run mechanical migrations, and report what the brain needs next. |
|
|
126
|
+
| `compile` | Run the contract gate → `.rafa/manifest.json`. |
|
|
127
|
+
| `push` | Compile, then push `.rafa/` to the brain remote. |
|
|
128
|
+
| `leverage` | Detect toolbox gaps (deterministic) and print prioritized tips — the detector for `/rafa leverage`. |
|
|
129
|
+
| `migrate` | Run the mechanical (deterministic) migrations. |
|
|
130
|
+
|
|
131
|
+
If the dev typed a **terminal-only** command (`compile`) as a slash command, point them to the
|
|
132
|
+
shell (`rafa compile`) as well. Then stop.
|
|
110
133
|
|
|
111
134
|
---
|
|
112
135
|
Token discipline: glob/grep/AST before reading; scoped reads; deterministic extraction
|
package/lib/blueprint.mjs
CHANGED
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
readFileSync,
|
|
11
11
|
readdirSync,
|
|
12
12
|
statSync,
|
|
13
|
+
rmSync,
|
|
13
14
|
} from "node:fs";
|
|
14
15
|
import { dirname, join } from "node:path";
|
|
15
16
|
import { fileURLToPath } from "node:url";
|
|
@@ -97,39 +98,67 @@ function sameBytes(a, b) {
|
|
|
97
98
|
}
|
|
98
99
|
|
|
99
100
|
// Copy the blueprint from `from` into `to`.
|
|
100
|
-
// - mode "vendor" (default): non-destructive. lockstep files
|
|
101
|
-
// created if absent, left untouched if identical, and
|
|
102
|
-
//
|
|
101
|
+
// - mode "vendor" (default): non-destructive. lockstep files (tools + contract) overwrite;
|
|
102
|
+
// owned files are created if absent, left untouched if identical, and — if the dev has
|
|
103
|
+
// changed them — resolved via `onConflict(rel)` which returns "overwrite" | "keep"
|
|
104
|
+
// (shadcn-style: ask before clobbering). No `.new` files; any stale `.new` left by an older
|
|
105
|
+
// CLI is cleaned up.
|
|
103
106
|
// - mode "bundle": force-copy everything (building the package tarball into a scratch dir).
|
|
104
|
-
// Returns { created, updated, unchanged,
|
|
105
|
-
export function copyBlueprint(from, to, { mode = "vendor" } = {}) {
|
|
107
|
+
// Returns { created, updated, unchanged, kept, cleaned, errors } — arrays of relative paths.
|
|
108
|
+
export async function copyBlueprint(from, to, { mode = "vendor", onConflict } = {}) {
|
|
106
109
|
const { owned, lockstep } = blueprintFiles(from);
|
|
107
|
-
const report = { created: [], updated: [], unchanged: [],
|
|
110
|
+
const report = { created: [], updated: [], unchanged: [], kept: [], cleaned: [], errors: [] };
|
|
108
111
|
|
|
109
|
-
|
|
112
|
+
// Remove a stale `<dst>.new` written by an older CLI — it's our own debris, now obsolete.
|
|
113
|
+
const dropStaleNew = (rel, dst) => {
|
|
114
|
+
const stale = `${dst}.new`;
|
|
115
|
+
if (existsSync(stale)) {
|
|
116
|
+
try {
|
|
117
|
+
rmSync(stale);
|
|
118
|
+
report.cleaned.push(`${rel}.new`);
|
|
119
|
+
} catch {
|
|
120
|
+
/* leave it; not worth failing the update over */
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
const put = async (rel, isOwned) => {
|
|
110
126
|
const src = join(from, rel);
|
|
111
127
|
if (!existsSync(src)) return;
|
|
112
128
|
const dst = join(to, rel);
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
+
try {
|
|
130
|
+
mkdirSync(dirname(dst), { recursive: true });
|
|
131
|
+
if (!existsSync(dst)) {
|
|
132
|
+
cpSync(src, dst);
|
|
133
|
+
report.created.push(rel);
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
if (sameBytes(src, dst)) {
|
|
137
|
+
dropStaleNew(rel, dst);
|
|
138
|
+
report.unchanged.push(rel);
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
// exists AND differs
|
|
142
|
+
if (mode === "bundle" || !isOwned) {
|
|
143
|
+
cpSync(src, dst); // lockstep, or a scratch bundle: moves with the CLI
|
|
144
|
+
report.updated.push(rel);
|
|
145
|
+
} else {
|
|
146
|
+
// owned + modified: ask before clobbering the dev's tuning (default: keep).
|
|
147
|
+
const decision = onConflict ? await onConflict(rel) : "keep";
|
|
148
|
+
if (decision === "overwrite") {
|
|
149
|
+
cpSync(src, dst);
|
|
150
|
+
report.updated.push(rel);
|
|
151
|
+
} else {
|
|
152
|
+
report.kept.push(rel);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
dropStaleNew(rel, dst);
|
|
156
|
+
} catch (e) {
|
|
157
|
+
report.errors.push(`${rel}: ${e instanceof Error ? e.message : e}`);
|
|
129
158
|
}
|
|
130
159
|
};
|
|
131
160
|
|
|
132
|
-
for (const rel of lockstep) put(rel, false);
|
|
133
|
-
for (const rel of owned) put(rel, true);
|
|
161
|
+
for (const rel of lockstep) await put(rel, false);
|
|
162
|
+
for (const rel of owned) await put(rel, true);
|
|
134
163
|
return report;
|
|
135
164
|
}
|
package/lib/init.mjs
CHANGED
|
@@ -13,16 +13,46 @@ import { blueprintSource, copyBlueprint } from "./blueprint.mjs";
|
|
|
13
13
|
import { mergeClaudeSettings } from "./claude-config.mjs";
|
|
14
14
|
import { stampCurrent } from "./stamp.mjs";
|
|
15
15
|
import { notifyIfBehind } from "./version-check.mjs";
|
|
16
|
+
import { isInteractive, ask } from "./prompt.mjs";
|
|
16
17
|
|
|
17
|
-
//
|
|
18
|
+
// Decide what to do when an owned file the dev may have tuned already exists and differs.
|
|
19
|
+
// shadcn-style: ask before clobbering. Honors `--overwrite`/`-o` (yes to all) and `--keep`
|
|
20
|
+
// (no to all); in a non-TTY (CI / agent shell) it defaults to KEEP so it never blocks or clobbers.
|
|
21
|
+
export function makeConflictResolver(args = []) {
|
|
22
|
+
const force = args.includes("--overwrite") || args.includes("-o");
|
|
23
|
+
const keepAll = args.includes("--keep");
|
|
24
|
+
const interactive = isInteractive();
|
|
25
|
+
let all = false; // overwrite everything remaining
|
|
26
|
+
let quit = false; // keep everything remaining
|
|
27
|
+
return async (rel) => {
|
|
28
|
+
if (force || all) return "overwrite";
|
|
29
|
+
if (keepAll || quit || !interactive) return "keep";
|
|
30
|
+
const a = await ask(
|
|
31
|
+
` • ${rel} exists and differs — overwrite? [y = yes · N = keep · a = all · q = keep rest] `,
|
|
32
|
+
);
|
|
33
|
+
if (a === "a") {
|
|
34
|
+
all = true;
|
|
35
|
+
return "overwrite";
|
|
36
|
+
}
|
|
37
|
+
if (a === "q") {
|
|
38
|
+
quit = true;
|
|
39
|
+
return "keep";
|
|
40
|
+
}
|
|
41
|
+
return a === "y" || a === "yes" ? "overwrite" : "keep";
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// One-line summary of a copyBlueprint report + per-file notes for kept/cleaned/errored files.
|
|
18
46
|
export function reportBlueprint(r) {
|
|
19
47
|
const n = (a) => a.length;
|
|
20
48
|
console.log(
|
|
21
49
|
` ✓ blueprint: ${n(r.created)} new · ${n(r.updated)} updated · ` +
|
|
22
|
-
`${n(r.unchanged)} unchanged · ${n(r.
|
|
50
|
+
`${n(r.unchanged)} unchanged · ${n(r.kept)} kept`,
|
|
23
51
|
);
|
|
24
|
-
for (const rel of r.
|
|
25
|
-
console.log(` • kept your ${rel}
|
|
52
|
+
for (const rel of r.kept)
|
|
53
|
+
console.log(` • kept your ${rel} (re-run with --overwrite to replace, or /rafa update to merge)`);
|
|
54
|
+
for (const rel of r.cleaned) console.log(` • removed stale ${rel}`);
|
|
55
|
+
for (const err of r.errors) console.log(` ! ${err}`);
|
|
26
56
|
}
|
|
27
57
|
|
|
28
58
|
// Union rafa's required permissions into .claude/settings.json without clobbering it.
|
|
@@ -69,8 +99,10 @@ export default async function init(args) {
|
|
|
69
99
|
}
|
|
70
100
|
|
|
71
101
|
// Vendor the blueprint into .claude/ + .rafa/ (you own these copies) — non-destructive:
|
|
72
|
-
// owned files (agents, capabilities)
|
|
73
|
-
reportBlueprint(
|
|
102
|
+
// tools/contract move in lockstep; owned files (agents, capabilities) prompt before overwrite.
|
|
103
|
+
reportBlueprint(
|
|
104
|
+
await copyBlueprint(blueprintSource(), TARGET, { onConflict: makeConflictResolver(args) }),
|
|
105
|
+
);
|
|
74
106
|
if (!existsSync(join(TARGET, ".rafa", "active.md")))
|
|
75
107
|
writeFileSync(join(TARGET, ".rafa", "active.md"), "# No active plan\n");
|
|
76
108
|
// Grant rafa's vendored tools their permissions (merge, never overwrite).
|
package/lib/prompt.mjs
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// Minimal interactive prompt helpers. Used to ask before overwriting a file the dev owns —
|
|
2
|
+
// shadcn-style — instead of silently writing `.new` clutter.
|
|
3
|
+
|
|
4
|
+
import { createInterface } from "node:readline/promises";
|
|
5
|
+
import { stdin, stdout } from "node:process";
|
|
6
|
+
|
|
7
|
+
// True only when we can actually prompt (both ends are a TTY). In a pipe / CI / an
|
|
8
|
+
// agent-spawned shell we must NOT block on input — callers fall back to a safe default.
|
|
9
|
+
export function isInteractive() {
|
|
10
|
+
return Boolean(stdin.isTTY && stdout.isTTY);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// Ask a free-form question, return the trimmed lowercased answer ("" if the dev just hits enter).
|
|
14
|
+
export async function ask(question) {
|
|
15
|
+
const rl = createInterface({ input: stdin, output: stdout });
|
|
16
|
+
try {
|
|
17
|
+
return (await rl.question(question)).trim().toLowerCase();
|
|
18
|
+
} finally {
|
|
19
|
+
rl.close();
|
|
20
|
+
}
|
|
21
|
+
}
|
package/lib/releases.mjs
CHANGED
|
@@ -46,6 +46,16 @@ export const RELEASES = [
|
|
|
46
46
|
"blueprint migrations and reports what the brain needs; new `/rafa update` does the brain-side " +
|
|
47
47
|
"migrations (re-scan / rewrite). `rafa update` now prints release notes + required actions.",
|
|
48
48
|
},
|
|
49
|
+
{
|
|
50
|
+
version: "0.2.2",
|
|
51
|
+
contract: 1,
|
|
52
|
+
plans: 1,
|
|
53
|
+
requires: "update",
|
|
54
|
+
summary:
|
|
55
|
+
"shadcn-style overwrite prompt on init/update — asks before replacing files you tuned " +
|
|
56
|
+
"(no more `.new` clutter; stale `.new` cleaned up; --overwrite/--keep flags). New `/rafa help` " +
|
|
57
|
+
"with a full command reference for both surfaces. (Completes 0.2.1, published before these landed.)",
|
|
58
|
+
},
|
|
49
59
|
];
|
|
50
60
|
|
|
51
61
|
// The release this CLI build ships (last entry).
|
package/lib/update.mjs
CHANGED
|
@@ -8,13 +8,13 @@
|
|
|
8
8
|
import { existsSync } from "node:fs";
|
|
9
9
|
import { join } from "node:path";
|
|
10
10
|
import { blueprintSource, copyBlueprint } from "./blueprint.mjs";
|
|
11
|
-
import { reportBlueprint, reportSettings } from "./init.mjs";
|
|
11
|
+
import { reportBlueprint, reportSettings, makeConflictResolver } from "./init.mjs";
|
|
12
12
|
import { CURRENT, CLI_VERSION, releasesAfter } from "./releases.mjs";
|
|
13
13
|
import { stampedVersions, stampCurrent, stampCli } from "./stamp.mjs";
|
|
14
14
|
import { pendingMigrations } from "./migrations/index.mjs";
|
|
15
15
|
import { notifyIfBehind } from "./version-check.mjs";
|
|
16
16
|
|
|
17
|
-
export default async function update() {
|
|
17
|
+
export default async function update(args = []) {
|
|
18
18
|
const TARGET = process.cwd();
|
|
19
19
|
if (!existsSync(join(TARGET, ".rafa")) && !existsSync(join(TARGET, ".claude"))) {
|
|
20
20
|
console.error("✗ This repo isn't provisioned yet — run `rafa init` first.");
|
|
@@ -27,7 +27,9 @@ export default async function update() {
|
|
|
27
27
|
await notifyIfBehind();
|
|
28
28
|
|
|
29
29
|
console.log("rafa update — syncing the blueprint (non-destructive)");
|
|
30
|
-
reportBlueprint(
|
|
30
|
+
reportBlueprint(
|
|
31
|
+
await copyBlueprint(blueprintSource(), TARGET, { onConflict: makeConflictResolver(args) }),
|
|
32
|
+
);
|
|
31
33
|
reportSettings(TARGET);
|
|
32
34
|
|
|
33
35
|
// ── Blueprint-side migrations: mechanical/deterministic, run here in the CLI. ──
|
package/package.json
CHANGED