@hanzlaa/rcode 2.7.0 → 2.7.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/cli/install.js +10 -0
- package/cli/uninstall.js +13 -1
- package/dist/rcode.js +10 -0
- package/package.json +1 -1
- package/rihal/workflows/do.md +50 -0
package/cli/install.js
CHANGED
|
@@ -1025,12 +1025,18 @@ function sweepStaleInstalledFiles(target, newPlan) {
|
|
|
1025
1025
|
const newRelsSet = new Set(newPlan.map(e => e.rel.split(path.sep).join('/')));
|
|
1026
1026
|
// Safety — never sweep these, even if they somehow landed in the manifest.
|
|
1027
1027
|
const neverSweep = /^(\.rihal\/config\.yaml|\.rihal\/state\.json|\.rihal\/state\.json\.lock|\.planning\/|\.rihal\/brain\/sources\.yaml)/;
|
|
1028
|
+
// #382 — local overrides: files matching <name>.local.md are user-managed.
|
|
1029
|
+
// The installer never touches them: not in copy, not in sweep, not even on
|
|
1030
|
+
// --force-overwrite. This gives users a stable path to customize agent
|
|
1031
|
+
// voice / examples / project-specific rules without losing them on update.
|
|
1032
|
+
const isLocalOverride = (rel) => /\.local\.(md|mdc|json|yaml|yml|toml|js|ts)$/.test(rel);
|
|
1028
1033
|
|
|
1029
1034
|
let removed = 0;
|
|
1030
1035
|
const emptyCandidateDirs = new Set();
|
|
1031
1036
|
for (const rel of oldRels) {
|
|
1032
1037
|
if (newRelsSet.has(rel)) continue;
|
|
1033
1038
|
if (neverSweep.test(rel)) continue;
|
|
1039
|
+
if (isLocalOverride(rel)) continue; // #382 — never sweep user-owned overrides
|
|
1034
1040
|
const full = path.join(target, rel);
|
|
1035
1041
|
try {
|
|
1036
1042
|
if (fs.existsSync(full)) {
|
|
@@ -1603,6 +1609,10 @@ async function install(opts) {
|
|
|
1603
1609
|
console.log(dim(' npx @hanzlaa/rcode@latest install # pull the latest rcode + brain'));
|
|
1604
1610
|
console.log(dim(` /rihal:update v${version} # pin rcode to a specific version`));
|
|
1605
1611
|
console.log('');
|
|
1612
|
+
console.log(dim(' Customize without losing changes on update:'));
|
|
1613
|
+
console.log(dim(' Create <name>.local.md siblings (e.g. .claude/agents/rihal-waleed.local.md)'));
|
|
1614
|
+
console.log(dim(' *.local.md files are NEVER touched by install / --force-overwrite / uninstall.'));
|
|
1615
|
+
console.log('');
|
|
1606
1616
|
console.log(' ' + warn('If your IDE is already open, reload the window to refresh skills/commands.'));
|
|
1607
1617
|
console.log(dim(' Claude Code / VS Code / Cursor: Cmd+Shift+P → Reload Window'));
|
|
1608
1618
|
console.log('');
|
package/cli/uninstall.js
CHANGED
|
@@ -57,14 +57,26 @@ function parseArgs(args) {
|
|
|
57
57
|
return opts;
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
+
/**
|
|
61
|
+
* #382 — Local overrides: files matching <name>.local.md (or .local.mdc /
|
|
62
|
+
* .local.json / etc.) are user-managed. The uninstaller never removes them
|
|
63
|
+
* — they survive both regular uninstall AND --purge. Users can customize
|
|
64
|
+
* an agent voice / skill / command by creating a .local.md sibling, knowing
|
|
65
|
+
* it'll persist across updates and uninstalls.
|
|
66
|
+
*/
|
|
67
|
+
function isLocalOverride(name) {
|
|
68
|
+
return /\.local\.(md|mdc|json|yaml|yml|toml|js|ts)$/.test(name);
|
|
69
|
+
}
|
|
70
|
+
|
|
60
71
|
/**
|
|
61
72
|
* Walk a directory and remove all files/subdirs whose name matches a predicate.
|
|
62
|
-
* Returns the number of entries removed.
|
|
73
|
+
* Returns the number of entries removed. Always skips local overrides (#382).
|
|
63
74
|
*/
|
|
64
75
|
function removeMatching(dir, predicate) {
|
|
65
76
|
if (!fs.existsSync(dir)) return 0;
|
|
66
77
|
let count = 0;
|
|
67
78
|
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
79
|
+
if (isLocalOverride(entry.name)) continue; // #382 — never remove user overrides
|
|
68
80
|
if (!predicate(entry.name)) continue;
|
|
69
81
|
const full = path.join(dir, entry.name);
|
|
70
82
|
fs.rmSync(full, { recursive: true, force: true });
|
package/dist/rcode.js
CHANGED
|
@@ -16530,11 +16530,13 @@ Say "plan a sprint" or run \`/rihal:sprint-planning\` to break Phase 01 into sto
|
|
|
16530
16530
|
}
|
|
16531
16531
|
const newRelsSet = new Set(newPlan.map((e) => e.rel.split(path2.sep).join("/")));
|
|
16532
16532
|
const neverSweep = /^(\.rihal\/config\.yaml|\.rihal\/state\.json|\.rihal\/state\.json\.lock|\.planning\/|\.rihal\/brain\/sources\.yaml)/;
|
|
16533
|
+
const isLocalOverride = (rel) => /\.local\.(md|mdc|json|yaml|yml|toml|js|ts)$/.test(rel);
|
|
16533
16534
|
let removed = 0;
|
|
16534
16535
|
const emptyCandidateDirs = /* @__PURE__ */ new Set();
|
|
16535
16536
|
for (const rel of oldRels) {
|
|
16536
16537
|
if (newRelsSet.has(rel)) continue;
|
|
16537
16538
|
if (neverSweep.test(rel)) continue;
|
|
16539
|
+
if (isLocalOverride(rel)) continue;
|
|
16538
16540
|
const full = path2.join(target, rel);
|
|
16539
16541
|
try {
|
|
16540
16542
|
if (fs2.existsSync(full)) {
|
|
@@ -17005,6 +17007,10 @@ Say "plan a sprint" or run \`/rihal:sprint-planning\` to break Phase 01 into sto
|
|
|
17005
17007
|
console.log(dim(" npx @hanzlaa/rcode@latest install # pull the latest rcode + brain"));
|
|
17006
17008
|
console.log(dim(` /rihal:update v${version} # pin rcode to a specific version`));
|
|
17007
17009
|
console.log("");
|
|
17010
|
+
console.log(dim(" Customize without losing changes on update:"));
|
|
17011
|
+
console.log(dim(" Create <name>.local.md siblings (e.g. .claude/agents/rihal-waleed.local.md)"));
|
|
17012
|
+
console.log(dim(" *.local.md files are NEVER touched by install / --force-overwrite / uninstall."));
|
|
17013
|
+
console.log("");
|
|
17008
17014
|
console.log(" " + warn("If your IDE is already open, reload the window to refresh skills/commands."));
|
|
17009
17015
|
console.log(dim(" Claude Code / VS Code / Cursor: Cmd+Shift+P \u2192 Reload Window"));
|
|
17010
17016
|
console.log("");
|
|
@@ -17953,10 +17959,14 @@ var require_uninstall = __commonJS({
|
|
|
17953
17959
|
}
|
|
17954
17960
|
return opts;
|
|
17955
17961
|
}
|
|
17962
|
+
function isLocalOverride(name) {
|
|
17963
|
+
return /\.local\.(md|mdc|json|yaml|yml|toml|js|ts)$/.test(name);
|
|
17964
|
+
}
|
|
17956
17965
|
function removeMatching(dir, predicate) {
|
|
17957
17966
|
if (!fs2.existsSync(dir)) return 0;
|
|
17958
17967
|
let count = 0;
|
|
17959
17968
|
for (const entry of fs2.readdirSync(dir, { withFileTypes: true })) {
|
|
17969
|
+
if (isLocalOverride(entry.name)) continue;
|
|
17960
17970
|
if (!predicate(entry.name)) continue;
|
|
17961
17971
|
const full = path2.join(dir, entry.name);
|
|
17962
17972
|
fs2.rmSync(full, { recursive: true, force: true });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hanzlaa/rcode",
|
|
3
|
-
"version": "2.7.
|
|
3
|
+
"version": "2.7.2",
|
|
4
4
|
"description": "Rihal Code (rcode) — installable context-brain for Rihalians. 43 agents, 99 slash commands, 56 skills, pullable Rihal standards. Unified install for Claude Code, Cursor, and Gemini.",
|
|
5
5
|
"main": "cli/index.js",
|
|
6
6
|
"bin": {
|
package/rihal/workflows/do.md
CHANGED
|
@@ -28,6 +28,56 @@ fi
|
|
|
28
28
|
```
|
|
29
29
|
</step>
|
|
30
30
|
|
|
31
|
+
<step name="persona_shortcut" priority="first-match">
|
|
32
|
+
**Recognize `@persona CODE` shortcuts as the deterministic API surface.**
|
|
33
|
+
|
|
34
|
+
Every Rihal persona file has a Capabilities table listing 2-3-letter codes (Waleed: ADR/RV/TS/FZ/KS · Hussain-PM: CP/VP/EP/CE/CS/IR/CC · Mariam: MR/ICP/GTM/POS/LP · Fatima: TS/RG/EC/RR/RP/FT · Hanzla: DS/IS/BF/RF/KA/CR · Sadiq: KC/OC/PT/MT/KS · Dalil: SC/MC/RF/TS · Khattat / Munaffidh / Bahith / Muhaqqiq similarly).
|
|
35
|
+
|
|
36
|
+
Match if `$QUESTION` starts with `@<persona> <CODE>` or `@<persona>:<CODE>` — case-insensitive on persona, codes uppercase. Examples:
|
|
37
|
+
|
|
38
|
+
- `@hussain CP` → dispatch to Hussain-PM with capability `CP` (Create PRD via interview)
|
|
39
|
+
- `@waleed ADR` → dispatch to Waleed with capability `ADR` (write an ADR)
|
|
40
|
+
- `@fatima RG` → dispatch to Fatima with capability `RG` (release-gate review)
|
|
41
|
+
- `@dalil SC --topic "Sentry"` → dispatch to Dalil with capability `SC` (lightweight scan, topic phrase passed)
|
|
42
|
+
|
|
43
|
+
**Persona-name aliases** (lowercased, common nicknames):
|
|
44
|
+
| Alias | Resolves to | Agent file |
|
|
45
|
+
|---|---|---|
|
|
46
|
+
| `sadiq`, `strategy`, `director` | Sadiq | rihal-sadiq |
|
|
47
|
+
| `waleed`, `cto`, `architect` | Waleed | rihal-waleed |
|
|
48
|
+
| `hussain`, `hussain-pm`, `pm` | Hussain | rihal-hussain-pm |
|
|
49
|
+
| `mariam`, `marketing` | Mariam | rihal-mariam |
|
|
50
|
+
| `fatima`, `qa` | Fatima | rihal-fatima |
|
|
51
|
+
| `hanzla`, `dev`, `engineer` | Hanzla | rihal-hanzla |
|
|
52
|
+
| `dalil`, `scout`, `mapper` | Dalil | rihal-codebase-mapper |
|
|
53
|
+
| `khattat`, `planner` | Khattat | rihal-planner |
|
|
54
|
+
| `munaffidh`, `executor` | Munaffidh | rihal-executor |
|
|
55
|
+
|
|
56
|
+
**Behavior:**
|
|
57
|
+
|
|
58
|
+
1. Parse the persona alias and CODE.
|
|
59
|
+
2. Read the persona's agent file at `.claude/agents/{agent-id}.md` (or `.claude/agents/{agent-id}.local.md` if it exists — local overrides take precedence).
|
|
60
|
+
3. Look up the CODE in the Capabilities table. If found, the table row's "Skill / workflow" column tells you which sub-command to invoke; pass the rest of `$QUESTION` (after the shortcut) as arguments.
|
|
61
|
+
4. If the CODE is not in that persona's Capabilities table, print:
|
|
62
|
+
```
|
|
63
|
+
Persona '{persona}' has no capability '{CODE}'. Available codes:
|
|
64
|
+
{list from the persona's Capabilities table}
|
|
65
|
+
```
|
|
66
|
+
And stop. Do not fall back to fuzzy intent matching — the user used the deterministic API, honour it.
|
|
67
|
+
5. Dispatch directly via the routing banner. Skip greenfield_guard / external_data_guard / explicit_intent_check / route — the user already chose the persona AND the action. The persona itself can still refuse internally if its preconditions aren't met.
|
|
68
|
+
|
|
69
|
+
**This is the deterministic API surface.** Power users (and other agents in council follow-ups) can invoke specific capabilities without re-reading triggers or risking fuzzy match. It's the cheapest way to get repeatable behaviour out of the persona system.
|
|
70
|
+
|
|
71
|
+
**Edge cases:**
|
|
72
|
+
|
|
73
|
+
- `@waleed` (no code) → dispatch to Waleed with no capability hint; persona uses its default workflow.
|
|
74
|
+
- `@nobody CP` (unknown persona) → fail loud: list known personas, exit.
|
|
75
|
+
- `CP @hussain` (code first) → reorder; do not match. The `@persona CODE` order is canonical.
|
|
76
|
+
- `@hussain CP fix the auth bug` → dispatch with `CP` and pass `fix the auth bug` as argument context.
|
|
77
|
+
|
|
78
|
+
If this step does NOT fire (no `@` prefix), continue to validate.
|
|
79
|
+
</step>
|
|
80
|
+
|
|
31
81
|
<step name="validate">
|
|
32
82
|
**Check for input.**
|
|
33
83
|
|