@hanzlaa/rcode 4.9.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 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
  [![CI](https://github.com/hanzlahabib/rcode/actions/workflows/test.yml/badge.svg)](https://github.com/hanzlahabib/rcode/actions/workflows/test.yml)
24
24
  [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
25
25
 
26
- Status: `@hanzlaa/rcode` v4.8.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.
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
 
@@ -229,7 +229,7 @@ The non-goal: building yet another agent framework. There are enough. rcode stay
229
229
 
230
230
  ## Honest state of things
231
231
 
232
- - **v4.8.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.
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.
233
233
  - **Open issues**: ~50 โ€” half are feature requests, the rest are backlog bugs ranked by severity.
234
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.
235
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.
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}, unprefixed: ${nsDup.unprefixedCount}, ` +
233
- `cross-scope: ${nsDup.crossScopeCount} (run \`rcode migrate-namespace --yes\` or \`rcode update\`)`,
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({
@@ -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
- console.log(' ' + warn('Antigravity install is experimental. Files land at .antigravity/rcode/{agents,commands}/.'));
2155
- console.log(' ' + dim('If Antigravity expects a different path, adjust .rcode/config.yaml and re-run.'));
2156
- // #908: same as Codex โ€” the UserPrompt hook is only wired on a global install.
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`. This project-local install does NOT wire the hook.'));
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
- const fullPlan = buildInstallPlan(opts.ides, opts.target);
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;
@@ -2754,23 +2772,30 @@ async function installInner(opts) {
2754
2772
  const hookReport = ensureRcodePreCommitHook(opts.target, { gitHooks: opts.gitHooks });
2755
2773
 
2756
2774
  // Pull rcode brain content (v2.0 โ€” issue #158).
2757
- // Runs rcode-tools brain pull as a child process. Placeholder URLs
2758
- // 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.
2759
2786
  let brainReport = null;
2787
+ let brainBackgrounded = false;
2760
2788
  try {
2761
- const { execFileSync } = require('child_process');
2789
+ const { spawn } = require('child_process');
2762
2790
  const toolsPath = path.join(opts.target, '.rcode', 'bin', 'rcode-tools.cjs');
2763
2791
  if (fs.existsSync(toolsPath)) {
2764
- // Issue #706: 60s timeout โ€” without it, a slow upstream URL hangs the
2765
- // entire install indefinitely. Brain pull is best-effort, so a timeout
2766
- // failure is treated identically to any other pull failure (caught below).
2767
- const out = execFileSync('node', [toolsPath, 'brain', 'pull'], {
2792
+ const child = spawn('node', [toolsPath, 'brain', 'pull'], {
2768
2793
  cwd: opts.target,
2769
- encoding: 'utf8',
2770
- stdio: ['ignore', 'pipe', 'pipe'],
2771
- timeout: 60_000,
2794
+ stdio: 'ignore',
2795
+ detached: true,
2772
2796
  });
2773
- try { brainReport = JSON.parse(out); } catch {}
2797
+ child.unref();
2798
+ brainBackgrounded = true;
2774
2799
  }
2775
2800
  } catch (e) {
2776
2801
  // brain pull is best-effort on install โ€” do not fail the whole install
@@ -2783,11 +2808,8 @@ async function installInner(opts) {
2783
2808
  if (opts.force && existedBefore) {
2784
2809
  console.log(' ' + warn('config.yaml and state.json preserved (pass --reset to wipe)'));
2785
2810
  }
2786
- if (brainReport && brainReport.ok) {
2787
- const pulledCount = (brainReport.pulled || []).length;
2788
- const skippedCount = (brainReport.skipped || []).length;
2789
- console.log(' ' + ok(`Brain: ${pulledCount} source${pulledCount === 1 ? '' : 's'} pulled` +
2790
- (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)'));
2791
2813
  } else if (brainReport && brainReport.error) {
2792
2814
  console.log(' ' + dim(`Brain: skipped (${brainReport.error})`));
2793
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
- totalCount: legacySkillCount + legacyCommandCount + unprefixedCount + crossScopeCount,
160
- detail: { projectLegacy, globalLegacy, projectUnprefixed, globalUnprefixed, crossScope },
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 destDir = path.join(backupRoot, scope, kind === 'skill' ? 'skills' : 'commands');
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: { legacySkills: 0, legacyCommands: 0, unprefixedDupes: 0, crossScopeDupes: 0 },
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
  };
@@ -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
  }