@hanzlaa/rcode 4.8.0 โ 4.9.1
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/AGENTS.md +1 -1
- package/CONTRIBUTING.md +3 -0
- package/README.md +7 -5
- package/cli/doctor.js +4 -2
- package/cli/github-sync.js +26 -17
- package/cli/install.js +59 -34
- package/cli/lib/namespace-migrate.cjs +62 -4
- package/cli/migrate-namespace.js +4 -0
- package/dist/rcode.js +207 -207
- package/package.json +1 -1
- package/rcode/agents/rcode-code-reviewer.md +1 -1
- package/rcode/agents/rcode-docs-auditor.md +1 -1
- package/rcode/agents/rcode-edge-case-hunter.md +1 -1
- package/rcode/agents/rcode-security-adversary.md +1 -1
- package/rcode/agents/rcode-security-auditor.md +1 -1
- package/rcode/agents/rcode-sprint-checker.md +1 -1
- package/rcode/agents/rcode-verifier.md +1 -1
- package/rcode/bin/lib/brain.cjs +16 -1
- package/rcode/bin/lib/gitignore.cjs +3 -5
- package/rcode/bin/rcode-hooks.cjs +22 -5
- package/rcode/bin/rcode-tools.cjs +166 -14
- package/rcode/data/intent-table.json +1 -1
- package/rcode/references/git-preflight.md +5 -2
- package/rcode/references/output-format.md +5 -5
- package/rcode/workflows/add-phase.md +33 -14
- package/rcode/workflows/do.md +33 -1
- package/rcode/workflows/execute-sprint.md +3 -4
- package/rcode/workflows/execute-waves.md +25 -32
- package/rcode/workflows/execute.md +80 -21
- package/rcode/workflows/init.md +28 -6
- package/rcode/workflows/plan-research-validation.md +10 -5
- package/rcode/workflows/plan-spawn-planner.md +9 -13
- package/rcode/workflows/plan.md +2 -2
- package/rcode/workflows/scaffold-skill.md +19 -1
- package/rcode/workflows/scan.md +22 -1
- package/rcode/workflows/secure-phase.md +7 -1
- package/rcode/workflows/validate-phase.md +7 -1
- package/server/lib/html/client/components/Sidebar.js +8 -5
- package/server/lib/html/client/views/PhasesView.js +1 -1
- package/server/lib/html/css.js +4 -0
- package/server/lib/scanner.js +4 -1
package/AGENTS.md
CHANGED
|
@@ -24,7 +24,7 @@ If a user says "just keep going" or "don't stop until done", that authorization
|
|
|
24
24
|
|
|
25
25
|
- Follow [Conventional Commits](https://www.conventionalcommits.org/) format: `type(scope): subject`
|
|
26
26
|
- Types allowed: `feat`, `fix`, `docs`, `style`, `refactor`, `test`, `chore`, `perf`, `revert`
|
|
27
|
-
- Scopes allowed: `agents`, `skills`, `workflows`, `templates`, `dashboard`, `docs`, `config`, `github`, `github-sync`, `commands`, `memory`, `brand`, `cli`, `ci`, `release`, `meta`, `tasks`, `migrations`, `refs`, `state`, `hooks`, `init`, `install`, `parity`, `triggers`, `dogfood`, `namespace`, `planning`, `insights`, `help`, `roadmap`, `session`, `audits`, `execute`, `executor`, `plan`, `planner`, `readme`, `rcode`, `review`, `sync`, `sprint`, `agent-exp`, `extensibility`, `lens-audit`, `tiers`, `build`, `council`, `doctor`, `postinstall`, `progress`, `security`, `tools`, `uninstall`, `update`, `test`, `changelog`, `scopes`, `phases`, `references`, `kanban`, `orchestrator`, `orchpanel`, `status`, `bin`, `brain`, `dogfeed`, `new-project`, `package`, `rcode-tools`, `rihal-tools`, `team`, `usp`, `v4`, `observability`, `audit`, `agent-rules`, `cursor`, `i18n`, `phase`, `scaffold`, `campaign`, `ship`, `getting-started`, `do-router`, `milestone-health`, `modules`, `project-types`, `roadmapper`, `token`, plus numeric phase/sprint scopes (e.g. `docs(15)`, `feat(8.3)`)
|
|
27
|
+
- Scopes allowed: `agents`, `skills`, `workflows`, `templates`, `dashboard`, `docs`, `config`, `github`, `github-sync`, `commands`, `memory`, `brand`, `cli`, `ci`, `release`, `meta`, `tasks`, `migrations`, `refs`, `state`, `hooks`, `init`, `install`, `parity`, `triggers`, `dogfood`, `namespace`, `planning`, `insights`, `help`, `roadmap`, `session`, `audits`, `execute`, `executor`, `plan`, `planner`, `readme`, `rcode`, `review`, `sync`, `sprint`, `agent-exp`, `extensibility`, `lens-audit`, `tiers`, `build`, `council`, `doctor`, `postinstall`, `progress`, `security`, `tools`, `uninstall`, `update`, `test`, `changelog`, `scopes`, `phases`, `references`, `kanban`, `orchestrator`, `orchpanel`, `status`, `bin`, `brain`, `dogfeed`, `new-project`, `package`, `rcode-tools`, `rihal-tools`, `team`, `usp`, `v4`, `observability`, `audit`, `agent-rules`, `cursor`, `i18n`, `phase`, `scaffold`, `campaign`, `ship`, `getting-started`, `do-router`, `milestone-health`, `modules`, `project-types`, `roadmapper`, `token`, `benchmarks`, `eval`, `scan`, plus numeric phase/sprint scopes (e.g. `docs(15)`, `feat(8.3)`)
|
|
28
28
|
- Subject: lowercase first letter, imperative mood, no trailing period, under 72 chars
|
|
29
29
|
- **NEVER add Claude/AI attribution to commit messages.** No "Generated with Claude Code", no "Co-Authored-By: Claude", no "๐ค Generated". The user does not want this.
|
|
30
30
|
- **NEVER use `--no-verify`** to bypass hooks. If hooks fail, fix the underlying issue.
|
package/CONTRIBUTING.md
CHANGED
|
@@ -363,6 +363,9 @@ We use [Conventional Commits](https://www.conventionalcommits.org/) format. The
|
|
|
363
363
|
- `project-types` โ project-type templates and detection
|
|
364
364
|
- `roadmapper` โ roadmap generation agent/workflow
|
|
365
365
|
- `token` โ token-budget and cost-tracking logic
|
|
366
|
+
- `benchmarks` โ `benchmarks/` scripts and benchmark reports
|
|
367
|
+
- `eval` โ `test/eval/` behavioral drift baselines and eval harness
|
|
368
|
+
- `scan` โ `/rcode-scan` workflow and existing-codebase analysis
|
|
366
369
|
- `<phase-id>` โ numeric phase scope when committing inside a phase (e.g. `docs(15)`, `feat(8.3)`)
|
|
367
370
|
- `<sprint-id>` โ numeric sprint scope inside a phase (e.g. `feat(15.1)`)
|
|
368
371
|
|
package/README.md
CHANGED
|
@@ -23,7 +23,7 @@ pnpm dlx @hanzlaa/rcode install
|
|
|
23
23
|
[](https://github.com/hanzlahabib/rcode/actions/workflows/test.yml)
|
|
24
24
|
[](LICENSE)
|
|
25
25
|
|
|
26
|
-
Status: `@hanzlaa/rcode` v4.
|
|
26
|
+
Status: `@hanzlaa/rcode` v4.9.0 on npm. 45 agents ยท 117 commands ยท 130 workflows ยท **1 runtime dependency**. Test status tracked by CI badge above. Actively dogfooded on real projects every week.
|
|
27
27
|
|
|
28
28
|
---
|
|
29
29
|
|
|
@@ -154,10 +154,10 @@ The point isn't "I beat LangChain." The point is **you don't need LangChain for
|
|
|
154
154
|
| Metric | Value | Why it's not a vanity number |
|
|
155
155
|
|---|---|---|
|
|
156
156
|
| **Portable methodology corpus** | **~105,000 lines** of markdown | A 10-person eng org's playbooks (agents + commands + workflows + skills + references) โ as files you own and grep, not a SaaS you rent. |
|
|
157
|
-
| **Automated tests** | **
|
|
157
|
+
| **Automated tests** | **598** across 74 files | The methodology is *guarded*, not vibes. Live pass/fail status is on the CI badge above; `node --test` (or `node benchmarks/facts.cjs`) reproduces locally in seconds. |
|
|
158
158
|
| **Tested CLI engine** | **~9,700 lines** (`rcode-tools.cjs` + `lib/`) | The deterministic brain โ routing, state, planning math โ is real code under test, not prompt soup. |
|
|
159
159
|
| **Runtime dependencies** | **1** (`ws`, for the dashboard socket) | The dashboard is otherwise pure Node stdlib. Almost nothing to audit, nothing to CVE-scan, nothing to break on `npm install`. |
|
|
160
|
-
| **Specialist agents / commands / workflows / skills** | **45 / 117 /
|
|
160
|
+
| **Specialist agents / commands / workflows / skills** | **45 / 117 / 130 / 96** | An entire engineering org, phrase-activated, that travels with you across Claude Code, Cursor, Codex, VS Code, Antigravity, Windsurf. |
|
|
161
161
|
| **Core-op latency** | **~60 ms**, **0 LLM tokens** (best-of-7) | Intent routing, state reads, and milestone-health are deterministic *local* compute โ a few ms over Node's own cold-start floor. The orchestration layer doesn't burn API tokens on bookkeeping the way pure-LLM agent frameworks do. |
|
|
162
162
|
|
|
163
163
|
> Counts above are produced by `node benchmarks/facts.cjs` and may drift slightly between releases โ run it for the exact current figures.
|
|
@@ -181,6 +181,8 @@ pnpm dlx @hanzlaa/rcode install
|
|
|
181
181
|
|
|
182
182
|
`/rcode-init` detects your project state (fresh / existing / returning) and routes to the right first action. For a greenfield project it auto-routes to `/rcode-new-project`.
|
|
183
183
|
|
|
184
|
+
> **Want a status primer at the start of every session?** Run `/rcode-enable-hooks` to turn on a one-line project status readout (phase, plan progress, blockers) each time Claude Code starts in this project, plus 9 other opt-in guardrails (read-before-edit checks, dangerous-command blocking, auto-formatting). All off by default โ a fresh install never surprises you.
|
|
185
|
+
|
|
184
186
|
### The full loop
|
|
185
187
|
|
|
186
188
|
Four commands cover most of a real week of work โ decide, plan, build, check in:
|
|
@@ -227,9 +229,9 @@ The non-goal: building yet another agent framework. There are enough. rcode stay
|
|
|
227
229
|
|
|
228
230
|
## Honest state of things
|
|
229
231
|
|
|
230
|
-
- **v4.
|
|
232
|
+
- **v4.9.0** is the current release โ Memory Bank now ships populated by default, brain-pull is live, and 50+ bug fixes landed from the most recent 3-project dogfeed run.
|
|
231
233
|
- **Open issues**: ~50 โ half are feature requests, the rest are backlog bugs ranked by severity.
|
|
232
|
-
- **Test suite**:
|
|
234
|
+
- **Test suite**: 598 automated tests across 74 files (run `node --test`). Live pass/fail status is on the CI badge above. Coverage is structural (compliance + artifact schema + workflow behavioral), not line-coverage.
|
|
233
235
|
- **Real users**: I run it on 4 projects daily. A handful of others run it on theirs. If you find a bug, file it โ most P1s ship within 48 hours.
|
|
234
236
|
- **Funding**: none. This is solo work. If your company wants commercial support, [email me](mailto:hanzla.dev@gmail.com).
|
|
235
237
|
|
package/cli/doctor.js
CHANGED
|
@@ -229,8 +229,10 @@ function runPreflight(cwd, packageRoot) {
|
|
|
229
229
|
status: 'warn',
|
|
230
230
|
message:
|
|
231
231
|
`${nsDup.totalCount} duplicate registration(s) โ legacy skills: ${nsDup.legacySkillCount}, ` +
|
|
232
|
-
`legacy commands: ${nsDup.legacyCommandCount},
|
|
233
|
-
`cross-scope: ${nsDup.crossScopeCount}
|
|
232
|
+
`legacy commands: ${nsDup.legacyCommandCount}, legacy agents: ${nsDup.legacyAgentCount}, ` +
|
|
233
|
+
`unprefixed: ${nsDup.unprefixedCount}, cross-scope: ${nsDup.crossScopeCount}, ` +
|
|
234
|
+
`legacy Codex commands: ${nsDup.legacyCodexCommandCount} ` +
|
|
235
|
+
`(run \`rcode migrate-namespace --yes\` or \`rcode update\`)`,
|
|
234
236
|
});
|
|
235
237
|
} else {
|
|
236
238
|
checks.push({
|
package/cli/github-sync.js
CHANGED
|
@@ -174,6 +174,15 @@ function saveSyncMap(cwd, map) {
|
|
|
174
174
|
writeJsonAtomic(mapPath, map);
|
|
175
175
|
}
|
|
176
176
|
|
|
177
|
+
// Namespace syncMap.epics/stories keys by phase/track. Sprint-track task ids
|
|
178
|
+
// (from `<task id="">`, no format enforcement) and epic-track story ids
|
|
179
|
+
// (`{epicNum}.{storyNum}`) can collapse to the same bare id across tracks โ
|
|
180
|
+
// without a phase prefix they'd silently clobber each other's issue mapping.
|
|
181
|
+
// See #1002.
|
|
182
|
+
function syncKey(phaseId, id) {
|
|
183
|
+
return `${phaseId}:${id}`;
|
|
184
|
+
}
|
|
185
|
+
|
|
177
186
|
// ---------- Main sync flow ----------
|
|
178
187
|
|
|
179
188
|
/**
|
|
@@ -369,33 +378,33 @@ async function main(args) {
|
|
|
369
378
|
] : [],
|
|
370
379
|
milestones: phases.filter((p) => !p.noMilestone && !syncMap.phases[p.id]),
|
|
371
380
|
epics: phases.flatMap((p) =>
|
|
372
|
-
p.epics.filter((e) => !syncMap.epics[e.id]).map((e) => ({ ...e, phase: p.id })),
|
|
381
|
+
p.epics.filter((e) => !syncMap.epics[syncKey(p.id, e.id)]).map((e) => ({ ...e, phase: p.id })),
|
|
373
382
|
),
|
|
374
383
|
stories: phases.flatMap((p) =>
|
|
375
|
-
p.stories.filter((s) => !syncMap.stories[s.id]).map((s) => ({ ...s, phase: p.id })),
|
|
384
|
+
p.stories.filter((s) => !syncMap.stories[syncKey(p.id, s.id)]).map((s) => ({ ...s, phase: p.id })),
|
|
376
385
|
),
|
|
377
386
|
|
|
378
387
|
// Items that already exist on GitHub โ candidates for update.
|
|
379
388
|
// Only populated when updateEnabled is true.
|
|
380
389
|
updateEpics: opts.updateEnabled
|
|
381
390
|
? phases.flatMap((p) =>
|
|
382
|
-
p.epics.filter((e) => syncMap.epics[e.id]).map((e) => ({
|
|
391
|
+
p.epics.filter((e) => syncMap.epics[syncKey(p.id, e.id)]).map((e) => ({
|
|
383
392
|
...e,
|
|
384
393
|
phase: p.id,
|
|
385
|
-
issueNumber: syncMap.epics[e.id].issue_number,
|
|
386
|
-
lastSyncedAt: syncMap.epics[e.id].synced_at,
|
|
387
|
-
lastSyncedContentHash: syncMap.epics[e.id].content_hash,
|
|
394
|
+
issueNumber: syncMap.epics[syncKey(p.id, e.id)].issue_number,
|
|
395
|
+
lastSyncedAt: syncMap.epics[syncKey(p.id, e.id)].synced_at,
|
|
396
|
+
lastSyncedContentHash: syncMap.epics[syncKey(p.id, e.id)].content_hash,
|
|
388
397
|
})),
|
|
389
398
|
)
|
|
390
399
|
: [],
|
|
391
400
|
updateStories: opts.updateEnabled
|
|
392
401
|
? phases.flatMap((p) =>
|
|
393
|
-
p.stories.filter((s) => syncMap.stories[s.id]).map((s) => ({
|
|
402
|
+
p.stories.filter((s) => syncMap.stories[syncKey(p.id, s.id)]).map((s) => ({
|
|
394
403
|
...s,
|
|
395
404
|
phase: p.id,
|
|
396
|
-
issueNumber: syncMap.stories[s.id].issue_number,
|
|
397
|
-
lastSyncedAt: syncMap.stories[s.id].synced_at,
|
|
398
|
-
lastSyncedContentHash: syncMap.stories[s.id].content_hash,
|
|
405
|
+
issueNumber: syncMap.stories[syncKey(p.id, s.id)].issue_number,
|
|
406
|
+
lastSyncedAt: syncMap.stories[syncKey(p.id, s.id)].synced_at,
|
|
407
|
+
lastSyncedContentHash: syncMap.stories[syncKey(p.id, s.id)].content_hash,
|
|
399
408
|
})),
|
|
400
409
|
)
|
|
401
410
|
: [],
|
|
@@ -544,7 +553,7 @@ async function main(args) {
|
|
|
544
553
|
if (result.error) {
|
|
545
554
|
results.errors.push(`epic ${epic.id}: ${result.error}`);
|
|
546
555
|
} else if (!result.dryRun) {
|
|
547
|
-
syncMap.epics[epic.id] = {
|
|
556
|
+
syncMap.epics[syncKey(epic.phase, epic.id)] = {
|
|
548
557
|
issue_number: result.number,
|
|
549
558
|
url: result.url,
|
|
550
559
|
phase: epic.phase,
|
|
@@ -568,7 +577,7 @@ async function main(args) {
|
|
|
568
577
|
// convention), not "any epic in this phase" โ that bug led to all
|
|
569
578
|
// stories pointing at the same epic previously.
|
|
570
579
|
const parentEpicEntry = story.parentEpic
|
|
571
|
-
? syncMap.epics[story.parentEpic]
|
|
580
|
+
? syncMap.epics[syncKey(story.phase, story.parentEpic)]
|
|
572
581
|
: null;
|
|
573
582
|
const parentRefLine = parentEpicEntry
|
|
574
583
|
? `- **Parent Epic:** #${parentEpicEntry.issue_number} (Part of this epic)`
|
|
@@ -624,7 +633,7 @@ async function main(args) {
|
|
|
624
633
|
if (result.error) {
|
|
625
634
|
results.errors.push(`story ${story.id}: ${result.error}`);
|
|
626
635
|
} else if (!result.dryRun) {
|
|
627
|
-
syncMap.stories[story.id] = {
|
|
636
|
+
syncMap.stories[syncKey(story.phase, story.id)] = {
|
|
628
637
|
issue_number: result.number,
|
|
629
638
|
url: result.url,
|
|
630
639
|
phase: story.phase,
|
|
@@ -734,8 +743,8 @@ async function main(args) {
|
|
|
734
743
|
if (result.error) {
|
|
735
744
|
results.errors.push(`update epic #${epic.issueNumber}: ${result.error}`);
|
|
736
745
|
} else if (!result.dryRun) {
|
|
737
|
-
syncMap.epics[epic.id].content_hash = newHash;
|
|
738
|
-
syncMap.epics[epic.id].updated_at = new Date().toISOString();
|
|
746
|
+
syncMap.epics[syncKey(epic.phase, epic.id)].content_hash = newHash;
|
|
747
|
+
syncMap.epics[syncKey(epic.phase, epic.id)].updated_at = new Date().toISOString();
|
|
739
748
|
console.log(` โ updated: #${epic.issueNumber} (${epic.id})`);
|
|
740
749
|
}
|
|
741
750
|
}
|
|
@@ -779,8 +788,8 @@ async function main(args) {
|
|
|
779
788
|
if (result.error) {
|
|
780
789
|
results.errors.push(`update story #${story.issueNumber}: ${result.error}`);
|
|
781
790
|
} else if (!result.dryRun) {
|
|
782
|
-
syncMap.stories[story.id].content_hash = newHash;
|
|
783
|
-
syncMap.stories[story.id].updated_at = new Date().toISOString();
|
|
791
|
+
syncMap.stories[syncKey(story.phase, story.id)].content_hash = newHash;
|
|
792
|
+
syncMap.stories[syncKey(story.phase, story.id)].updated_at = new Date().toISOString();
|
|
784
793
|
console.log(` โ updated: #${story.issueNumber} (${story.id})`);
|
|
785
794
|
}
|
|
786
795
|
}
|
package/cli/install.js
CHANGED
|
@@ -2151,11 +2151,15 @@ async function installInner(opts) {
|
|
|
2151
2151
|
|
|
2152
2152
|
// Antigravity install is experimental โ best-effort path, user may need to adjust
|
|
2153
2153
|
if (opts.ides.includes('antigravity')) {
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
//
|
|
2154
|
+
// #908/#1028: the UserPrompt hook that makes .antigravity/ files functional
|
|
2155
|
+
// is only wired on a global install. A project-local install would write
|
|
2156
|
+
// guaranteed-inert files, so skip them entirely instead of installing dead
|
|
2157
|
+
// weight โ see planIdes filtering below.
|
|
2157
2158
|
if (!opts.global) {
|
|
2158
|
-
console.log(' ' + warn('Antigravity /rcode-* slash commands need a GLOBAL install โ re-run with `--global`.
|
|
2159
|
+
console.log(' ' + warn('Antigravity /rcode-* slash commands need a GLOBAL install โ re-run with `--global`. Skipping .antigravity/ files on this project-local install (they would be inert).'));
|
|
2160
|
+
} else {
|
|
2161
|
+
console.log(' ' + warn('Antigravity install is experimental. Files land at .antigravity/rcode/{agents,commands}/.'));
|
|
2162
|
+
console.log(' ' + dim('If Antigravity expects a different path, adjust .rcode/config.yaml and re-run.'));
|
|
2159
2163
|
}
|
|
2160
2164
|
}
|
|
2161
2165
|
|
|
@@ -2179,9 +2183,23 @@ async function installInner(opts) {
|
|
|
2179
2183
|
}
|
|
2180
2184
|
}
|
|
2181
2185
|
|
|
2182
|
-
|
|
2186
|
+
// #1028: .antigravity/ is only wired up on a GLOBAL install (the hook that
|
|
2187
|
+
// makes /rcode-* slash commands work lives in ~/.gemini/antigravity/, written
|
|
2188
|
+
// by installAntigravitySlashRouterHook() only when opts.global is set โ see
|
|
2189
|
+
// the warning above). A project-local install writes guaranteed-inert files;
|
|
2190
|
+
// skip them entirely unless --global was actually passed.
|
|
2191
|
+
const planIdes = (!opts.global && Array.isArray(opts.ides))
|
|
2192
|
+
? opts.ides.filter(i => i !== 'antigravity')
|
|
2193
|
+
: opts.ides;
|
|
2194
|
+
|
|
2195
|
+
const fullPlan = buildInstallPlan(planIdes, opts.target);
|
|
2183
2196
|
const plan = filterPlanByModules(fullPlan, opts.modules);
|
|
2184
2197
|
if (plan.length === 0) {
|
|
2198
|
+
if (Array.isArray(opts.ides) && opts.ides.includes('antigravity') && !opts.global) {
|
|
2199
|
+
console.error('โ Nothing to install โ Antigravity was the only target IDE, and its files need a GLOBAL install.');
|
|
2200
|
+
console.error(' Re-run with --global, or pick another --ide.');
|
|
2201
|
+
return 1;
|
|
2202
|
+
}
|
|
2185
2203
|
console.error('โ Nothing to install โ install plan is empty.');
|
|
2186
2204
|
if (opts.modules.length > 0) console.error(` Modules requested: ${opts.modules.join(', ')}`);
|
|
2187
2205
|
return 1;
|
|
@@ -2502,21 +2520,32 @@ async function installInner(opts) {
|
|
|
2502
2520
|
fs.existsSync(path.join(projectClaudeCommands, 'rcode'))
|
|
2503
2521
|
);
|
|
2504
2522
|
if (globalHasrcode && !projectHasrcode) {
|
|
2505
|
-
// Global commands exist, project has none yet โ filter
|
|
2506
|
-
// so we don't create duplicates. Project gets .rcode/ state only.
|
|
2523
|
+
// Global commands exist, project has none yet โ filter commands out of the
|
|
2524
|
+
// plan so we don't create duplicates. Project gets .rcode/ state only.
|
|
2525
|
+
//
|
|
2526
|
+
// Issue #1022: agents are NOT deferrable to global the way commands/skills
|
|
2527
|
+
// are โ they are first-class, project-local files by design (see the
|
|
2528
|
+
// createInstallBackup comment above: "closes #381 โ without this,
|
|
2529
|
+
// customized .claude/agents/rcode-*.md ... were silently lost"). Lumping
|
|
2530
|
+
// `.claude/agents/` into this commands-dedup filter meant a project with
|
|
2531
|
+
// global rcode commands installed but no project-level commands yet would
|
|
2532
|
+
// never get its .claude/agents/*.md files written at all, leaving only
|
|
2533
|
+
// whatever pre-existing subdirectories (e.g. rules/) survived untouched.
|
|
2507
2534
|
const before = plan.length;
|
|
2508
2535
|
const filtered = plan.filter(e => {
|
|
2509
2536
|
const rel = e.rel.split(path.sep).join('/');
|
|
2510
|
-
return !rel.startsWith('.claude/commands/')
|
|
2537
|
+
return !rel.startsWith('.claude/commands/');
|
|
2511
2538
|
});
|
|
2512
2539
|
if (filtered.length < before) {
|
|
2513
2540
|
plan.length = 0;
|
|
2514
2541
|
filtered.forEach(e => plan.push(e));
|
|
2515
|
-
console.log(' ' + dim('Global rcode commands detected in ~/.claude/ โ skipping project-level
|
|
2542
|
+
console.log(' ' + dim('Global rcode commands detected in ~/.claude/ โ skipping project-level command install to avoid duplicates.'));
|
|
2516
2543
|
console.log(' ' + dim('Use --force-overwrite to install locally anyway.'));
|
|
2517
2544
|
}
|
|
2518
2545
|
} else if (globalHasrcode && projectHasrcode) {
|
|
2519
2546
|
// Both exist โ project commands are duplicates. Remove project-level ones.
|
|
2547
|
+
// Agents are left untouched (#1022) โ they are project-local by design,
|
|
2548
|
+
// never deduped against the global commands install.
|
|
2520
2549
|
try {
|
|
2521
2550
|
// Remove root-level rcode-*.md files
|
|
2522
2551
|
const projectCommandFiles = fs.readdirSync(projectClaudeCommands)
|
|
@@ -2530,19 +2559,11 @@ async function installInner(opts) {
|
|
|
2530
2559
|
if (fs.existsSync(rcodeSubdir)) {
|
|
2531
2560
|
safeRmSync(rcodeSubdir, opts.target);
|
|
2532
2561
|
}
|
|
2533
|
-
const projectAgentsDir = path.join(opts.target, '.claude', 'agents');
|
|
2534
|
-
if (fs.existsSync(projectAgentsDir)) {
|
|
2535
|
-
const agentFiles = fs.readdirSync(projectAgentsDir)
|
|
2536
|
-
.filter(f => f.startsWith('rcode-') && f.endsWith('.md'));
|
|
2537
|
-
for (const f of agentFiles) {
|
|
2538
|
-
fs.unlinkSync(path.join(projectAgentsDir, f));
|
|
2539
|
-
}
|
|
2540
|
-
}
|
|
2541
2562
|
console.log(' ' + dim('Removed duplicate project-level rcode commands (global ones in ~/.claude/ take precedence).'));
|
|
2542
2563
|
} catch { /* non-fatal */ }
|
|
2543
2564
|
const filtered = plan.filter(e => {
|
|
2544
2565
|
const rel = e.rel.split(path.sep).join('/');
|
|
2545
|
-
return !rel.startsWith('.claude/commands/')
|
|
2566
|
+
return !rel.startsWith('.claude/commands/');
|
|
2546
2567
|
});
|
|
2547
2568
|
plan.length = 0;
|
|
2548
2569
|
filtered.forEach(e => plan.push(e));
|
|
@@ -2751,23 +2772,30 @@ async function installInner(opts) {
|
|
|
2751
2772
|
const hookReport = ensureRcodePreCommitHook(opts.target, { gitHooks: opts.gitHooks });
|
|
2752
2773
|
|
|
2753
2774
|
// Pull rcode brain content (v2.0 โ issue #158).
|
|
2754
|
-
// Runs rcode-tools brain pull as a
|
|
2755
|
-
// are skipped gracefully so this does not fail a fresh install.
|
|
2775
|
+
// Runs rcode-tools brain pull as a detached background process. Placeholder
|
|
2776
|
+
// URLs are skipped gracefully so this does not fail a fresh install.
|
|
2777
|
+
//
|
|
2778
|
+
// Issue #1030: a cold pull (cache miss) clones + sparse-checks-out a real
|
|
2779
|
+
// upstream repo and live-measured ~58s for just 2 small files โ dangerously
|
|
2780
|
+
// close to the previous 60s execFileSync timeout (#706) and ~6x over the
|
|
2781
|
+
// 10s kill criterion issue #162 itself specified. Since brain pull is
|
|
2782
|
+
// already best-effort and never fails install (see catch below, historically
|
|
2783
|
+
// a timeout was just treated as a pull failure), there is no reason to block
|
|
2784
|
+
// install on it at all. Spawn it detached and let install finish immediately;
|
|
2785
|
+
// the child keeps running and warms the cache/writes content on its own.
|
|
2756
2786
|
let brainReport = null;
|
|
2787
|
+
let brainBackgrounded = false;
|
|
2757
2788
|
try {
|
|
2758
|
-
const {
|
|
2789
|
+
const { spawn } = require('child_process');
|
|
2759
2790
|
const toolsPath = path.join(opts.target, '.rcode', 'bin', 'rcode-tools.cjs');
|
|
2760
2791
|
if (fs.existsSync(toolsPath)) {
|
|
2761
|
-
|
|
2762
|
-
// entire install indefinitely. Brain pull is best-effort, so a timeout
|
|
2763
|
-
// failure is treated identically to any other pull failure (caught below).
|
|
2764
|
-
const out = execFileSync('node', [toolsPath, 'brain', 'pull'], {
|
|
2792
|
+
const child = spawn('node', [toolsPath, 'brain', 'pull'], {
|
|
2765
2793
|
cwd: opts.target,
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
timeout: 60_000,
|
|
2794
|
+
stdio: 'ignore',
|
|
2795
|
+
detached: true,
|
|
2769
2796
|
});
|
|
2770
|
-
|
|
2797
|
+
child.unref();
|
|
2798
|
+
brainBackgrounded = true;
|
|
2771
2799
|
}
|
|
2772
2800
|
} catch (e) {
|
|
2773
2801
|
// brain pull is best-effort on install โ do not fail the whole install
|
|
@@ -2780,11 +2808,8 @@ async function installInner(opts) {
|
|
|
2780
2808
|
if (opts.force && existedBefore) {
|
|
2781
2809
|
console.log(' ' + warn('config.yaml and state.json preserved (pass --reset to wipe)'));
|
|
2782
2810
|
}
|
|
2783
|
-
if (
|
|
2784
|
-
|
|
2785
|
-
const skippedCount = (brainReport.skipped || []).length;
|
|
2786
|
-
console.log(' ' + ok(`Brain: ${pulledCount} source${pulledCount === 1 ? '' : 's'} pulled` +
|
|
2787
|
-
(skippedCount ? `, ${skippedCount} skipped (placeholder URLs)` : '')));
|
|
2811
|
+
if (brainBackgrounded) {
|
|
2812
|
+
console.log(' ' + dim('Brain: pulling in background (may take up to a minute on a cold cache; run `rcode brain status` to check)'));
|
|
2788
2813
|
} else if (brainReport && brainReport.error) {
|
|
2789
2814
|
console.log(' ' + dim(`Brain: skipped (${brainReport.error})`));
|
|
2790
2815
|
}
|
|
@@ -132,6 +132,40 @@ function findCrossScopeDupes(projectClaudeDir, globalClaudeDir) {
|
|
|
132
132
|
return dupes;
|
|
133
133
|
}
|
|
134
134
|
|
|
135
|
+
/**
|
|
136
|
+
* Legacy rihal-* flat command files under ~/.rcode/slash-commands/, whose
|
|
137
|
+
* rcode-* twin already exists in the SAME dir. Read-only.
|
|
138
|
+
*
|
|
139
|
+
* This is the ONLY per-file location that's actually Codex-specific (#1024):
|
|
140
|
+
* `cli/install.js`'s `--global` install writes every command body here (a
|
|
141
|
+
* single flat dir shared by the Codex + Antigravity prompt-submit hook
|
|
142
|
+
* router โ see `installSlashRouterCommands()`), and this is where a stale
|
|
143
|
+
* rihal-* copy from before the rename would still live. The comment in
|
|
144
|
+
* install.js claiming Codex reads `~/.codex/prompts/*.md` is inaccurate โ
|
|
145
|
+
* that path is never written by this codebase; verified by reading
|
|
146
|
+
* install.js's actual writer functions rather than trusting the comment.
|
|
147
|
+
* Project-level Codex installs (agentsDir/commandsDir under `.claude/`) are
|
|
148
|
+
* already covered by findLegacyRihalArtifacts() since Codex shares that
|
|
149
|
+
* layout with claude/grok.
|
|
150
|
+
*/
|
|
151
|
+
function findLegacyCodexSlashCommands(homeDir) {
|
|
152
|
+
const dupes = [];
|
|
153
|
+
const dir = path.join(homeDir, '.rcode', 'slash-commands');
|
|
154
|
+
for (const entry of listDirSafe(dir)) {
|
|
155
|
+
if (!entry.isFile() || !entry.name.startsWith('rihal-') || !entry.name.endsWith('.md')) continue;
|
|
156
|
+
const twinName = 'rcode-' + entry.name.slice('rihal-'.length);
|
|
157
|
+
if (fs.existsSync(path.join(dir, twinName))) {
|
|
158
|
+
dupes.push({
|
|
159
|
+
name: entry.name,
|
|
160
|
+
twin: twinName,
|
|
161
|
+
srcPath: path.join(dir, entry.name),
|
|
162
|
+
kind: 'command',
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
return dupes;
|
|
167
|
+
}
|
|
168
|
+
|
|
135
169
|
/**
|
|
136
170
|
* Full duplication report across project + global scopes. Pure read โ safe
|
|
137
171
|
* to call from `rcode doctor` on every run.
|
|
@@ -145,19 +179,30 @@ function scanNamespaceDuplication(projectDir, homeDir) {
|
|
|
145
179
|
const projectUnprefixed = findUnprefixedTwinDupes(projectClaudeDir);
|
|
146
180
|
const globalUnprefixed = findUnprefixedTwinDupes(globalClaudeDir);
|
|
147
181
|
const crossScope = findCrossScopeDupes(projectClaudeDir, globalClaudeDir);
|
|
182
|
+
const codexSlashCommands = findLegacyCodexSlashCommands(homeDir);
|
|
148
183
|
|
|
149
184
|
const legacySkillCount = projectLegacy.skills.length + globalLegacy.skills.length;
|
|
150
185
|
const legacyCommandCount = projectLegacy.commands.length + globalLegacy.commands.length;
|
|
186
|
+
const legacyAgentCount = projectLegacy.agents.length + globalLegacy.agents.length;
|
|
151
187
|
const unprefixedCount = projectUnprefixed.length + globalUnprefixed.length;
|
|
152
188
|
const crossScopeCount = crossScope.length;
|
|
189
|
+
const legacyCodexCommandCount = codexSlashCommands.length;
|
|
153
190
|
|
|
154
191
|
return {
|
|
155
192
|
legacySkillCount,
|
|
156
193
|
legacyCommandCount,
|
|
194
|
+
legacyAgentCount,
|
|
157
195
|
unprefixedCount,
|
|
158
196
|
crossScopeCount,
|
|
159
|
-
|
|
160
|
-
|
|
197
|
+
legacyCodexCommandCount,
|
|
198
|
+
totalCount:
|
|
199
|
+
legacySkillCount +
|
|
200
|
+
legacyCommandCount +
|
|
201
|
+
legacyAgentCount +
|
|
202
|
+
unprefixedCount +
|
|
203
|
+
crossScopeCount +
|
|
204
|
+
legacyCodexCommandCount,
|
|
205
|
+
detail: { projectLegacy, globalLegacy, projectUnprefixed, globalUnprefixed, crossScope, codexSlashCommands },
|
|
161
206
|
};
|
|
162
207
|
}
|
|
163
208
|
|
|
@@ -169,7 +214,8 @@ function scanNamespaceDuplication(projectDir, homeDir) {
|
|
|
169
214
|
*/
|
|
170
215
|
function backupAndRemove(srcPath, backupRoot, scope, kind) {
|
|
171
216
|
if (!fs.existsSync(srcPath)) return false;
|
|
172
|
-
const
|
|
217
|
+
const destDirName = kind === 'skill' ? 'skills' : kind === 'agent' ? 'agents' : 'commands';
|
|
218
|
+
const destDir = path.join(backupRoot, scope, destDirName);
|
|
173
219
|
fs.mkdirSync(destDir, { recursive: true });
|
|
174
220
|
const dest = path.join(destDir, path.basename(srcPath));
|
|
175
221
|
fs.cpSync(srcPath, dest, { recursive: true });
|
|
@@ -194,7 +240,14 @@ function migrateNamespace(projectDir, homeDir) {
|
|
|
194
240
|
|
|
195
241
|
const summary = {
|
|
196
242
|
backupDir: null,
|
|
197
|
-
removed: {
|
|
243
|
+
removed: {
|
|
244
|
+
legacySkills: 0,
|
|
245
|
+
legacyCommands: 0,
|
|
246
|
+
legacyAgents: 0,
|
|
247
|
+
unprefixedDupes: 0,
|
|
248
|
+
crossScopeDupes: 0,
|
|
249
|
+
legacyCodexCommands: 0,
|
|
250
|
+
},
|
|
198
251
|
};
|
|
199
252
|
|
|
200
253
|
const removeAll = (items, scope, kind) => {
|
|
@@ -211,10 +264,14 @@ function migrateNamespace(projectDir, homeDir) {
|
|
|
211
264
|
summary.removed.legacyCommands =
|
|
212
265
|
removeAll(scan.detail.projectLegacy.commands, 'project', 'command') +
|
|
213
266
|
removeAll(scan.detail.globalLegacy.commands, 'global', 'command');
|
|
267
|
+
summary.removed.legacyAgents =
|
|
268
|
+
removeAll(scan.detail.projectLegacy.agents, 'project', 'agent') +
|
|
269
|
+
removeAll(scan.detail.globalLegacy.agents, 'global', 'agent');
|
|
214
270
|
summary.removed.unprefixedDupes =
|
|
215
271
|
removeAll(scan.detail.projectUnprefixed, 'project', 'command') +
|
|
216
272
|
removeAll(scan.detail.globalUnprefixed, 'global', 'command');
|
|
217
273
|
summary.removed.crossScopeDupes = removeAll(scan.detail.crossScope, 'global', 'command');
|
|
274
|
+
summary.removed.legacyCodexCommands = removeAll(scan.detail.codexSlashCommands, 'global', 'command');
|
|
218
275
|
|
|
219
276
|
const totalRemoved = Object.values(summary.removed).reduce((a, b) => a + b, 0);
|
|
220
277
|
if (totalRemoved > 0) summary.backupDir = backupRoot;
|
|
@@ -226,6 +283,7 @@ module.exports = {
|
|
|
226
283
|
findLegacyRihalArtifacts,
|
|
227
284
|
findUnprefixedTwinDupes,
|
|
228
285
|
findCrossScopeDupes,
|
|
286
|
+
findLegacyCodexSlashCommands,
|
|
229
287
|
scanNamespaceDuplication,
|
|
230
288
|
migrateNamespace,
|
|
231
289
|
};
|
package/cli/migrate-namespace.js
CHANGED
|
@@ -26,8 +26,10 @@ function printScanSummary(scan) {
|
|
|
26
26
|
console.log(` Found ${scan.totalCount} artifact(s) to migrate:`);
|
|
27
27
|
console.log(` legacy rihal-* skills: ${scan.legacySkillCount}`);
|
|
28
28
|
console.log(` legacy rihal-* commands: ${scan.legacyCommandCount}`);
|
|
29
|
+
console.log(` legacy rihal-* agents: ${scan.legacyAgentCount}`);
|
|
29
30
|
console.log(` unprefixed dupes: ${scan.unprefixedCount}`);
|
|
30
31
|
console.log(` cross-scope dupes: ${scan.crossScopeCount} (global copy shadowed by project)`);
|
|
32
|
+
console.log(` legacy Codex commands: ${scan.legacyCodexCommandCount} (~/.rcode/slash-commands/)`);
|
|
31
33
|
console.log();
|
|
32
34
|
}
|
|
33
35
|
|
|
@@ -50,8 +52,10 @@ module.exports = function migrateNamespaceCommand(args = []) {
|
|
|
50
52
|
console.log(` โ Removed ${total} artifact(s):`);
|
|
51
53
|
console.log(` legacy skills: ${summary.removed.legacySkills}`);
|
|
52
54
|
console.log(` legacy commands: ${summary.removed.legacyCommands}`);
|
|
55
|
+
console.log(` legacy agents: ${summary.removed.legacyAgents}`);
|
|
53
56
|
console.log(` unprefixed: ${summary.removed.unprefixedDupes}`);
|
|
54
57
|
console.log(` cross-scope: ${summary.removed.crossScopeDupes}`);
|
|
58
|
+
console.log(` codex commands: ${summary.removed.legacyCodexCommands}`);
|
|
55
59
|
if (summary.backupDir) {
|
|
56
60
|
console.log(` ๐พ backup: ${path.relative(home, summary.backupDir)} (under ~/.claude/.rcode-backup/)`);
|
|
57
61
|
}
|