@hanzlaa/rcode 4.9.0 โ 4.10.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/AGENTS.md +1 -1
- package/CONTRIBUTING.md +3 -0
- package/README.md +2 -2
- package/cli/doctor.js +4 -2
- package/cli/github-sync.js +26 -17
- package/cli/install.js +43 -21
- 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/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 +60 -14
- package/rcode/data/intent-table.json +1 -1
- package/rcode/workflows/do.md +33 -1
- package/rcode/workflows/execute.md +58 -2
- package/rcode/workflows/init.md +18 -4
- package/rcode/workflows/scan.md +22 -1
- package/server/lib/html/client/components/Sidebar.js +8 -5
- package/server/lib/html/client/components/shared.js +29 -10
- package/server/lib/html/client/views/PhasesView.js +1 -1
- package/server/lib/html/css.js +4 -0
- package/server/lib/scanner.js +8 -5
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
|
|
|
@@ -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.
|
|
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},
|
|
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;
|
|
@@ -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
|
|
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 {
|
|
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
|
-
|
|
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
|
-
|
|
2770
|
-
|
|
2771
|
-
timeout: 60_000,
|
|
2794
|
+
stdio: 'ignore',
|
|
2795
|
+
detached: true,
|
|
2772
2796
|
});
|
|
2773
|
-
|
|
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 (
|
|
2787
|
-
|
|
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
|
-
|
|
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
|
}
|