@hanzlaa/rcode 3.4.15 → 3.4.16

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 CHANGED
@@ -945,6 +945,26 @@ function buildInstallPlan(ide = 'claude', target = process.cwd()) {
945
945
  }
946
946
  }
947
947
  }
948
+ // When both claude and vscode are in the IDE list, vscode writes commands to
949
+ // .claude/commands/rihal/{name}.md (subdirectory) while claude writes them to
950
+ // .claude/commands/rihal-{name}.md (root). Claude Code reads the full tree
951
+ // recursively, so both sets appear as slash commands — duplicates in the UI.
952
+ // Drop the vscode-style subdir entries when claude entries already cover them.
953
+ if (ide.includes('claude') && ide.includes('vscode')) {
954
+ const claudeCommandRels = new Set(
955
+ merged
956
+ .filter(e => e.ide === 'claude' && e.rel.split(path.sep).join('/').startsWith('.claude/commands/'))
957
+ .map(e => path.basename(e.rel, '.md').replace(/^rihal-/, ''))
958
+ );
959
+ return merged.filter(e => {
960
+ const rel = e.rel.split(path.sep).join('/');
961
+ if (e.ide === 'vscode' && rel.startsWith('.claude/commands/rihal/')) {
962
+ const baseName = path.basename(e.rel, path.extname(e.rel));
963
+ return !claudeCommandRels.has(baseName);
964
+ }
965
+ return true;
966
+ });
967
+ }
948
968
  return merged;
949
969
  }
950
970
 
@@ -1731,12 +1751,18 @@ async function install(opts) {
1731
1751
  const globalClaudeCommands = path.join(os.homedir(), '.claude', 'commands');
1732
1752
  const projectClaudeCommands = path.join(opts.target, '.claude', 'commands');
1733
1753
  const isProjectInstall = opts.target !== os.homedir();
1734
- if (isProjectInstall && !opts.force && !opts.forceOverwrite) {
1754
+ // Run dedup even when force:true only forceOverwrite skips it.
1755
+ if (isProjectInstall && !opts.forceOverwrite) {
1735
1756
  try {
1736
- const globalHasRihal = fs.existsSync(globalClaudeCommands) &&
1737
- fs.readdirSync(globalClaudeCommands).some(f => f.startsWith('rihal-') && f.endsWith('.md'));
1738
- const projectHasRihal = fs.existsSync(projectClaudeCommands) &&
1739
- fs.readdirSync(projectClaudeCommands).some(f => f.startsWith('rihal-') && f.endsWith('.md'));
1757
+ // Check both root-level rihal-*.md AND the rihal/ subdirectory (vscode-style).
1758
+ const globalHasRihal = fs.existsSync(globalClaudeCommands) && (
1759
+ fs.readdirSync(globalClaudeCommands).some(f => f.startsWith('rihal-') && f.endsWith('.md')) ||
1760
+ fs.existsSync(path.join(globalClaudeCommands, 'rihal'))
1761
+ );
1762
+ const projectHasRihal = fs.existsSync(projectClaudeCommands) && (
1763
+ fs.readdirSync(projectClaudeCommands).some(f => f.startsWith('rihal-') && f.endsWith('.md')) ||
1764
+ fs.existsSync(path.join(projectClaudeCommands, 'rihal'))
1765
+ );
1740
1766
  if (globalHasRihal && !projectHasRihal) {
1741
1767
  // Global commands exist, project has none yet — filter them out of the plan
1742
1768
  // so we don't create duplicates. Project gets .rihal/ state only.
@@ -1754,11 +1780,17 @@ async function install(opts) {
1754
1780
  } else if (globalHasRihal && projectHasRihal) {
1755
1781
  // Both exist — project commands are duplicates. Remove project-level ones.
1756
1782
  try {
1783
+ // Remove root-level rihal-*.md files
1757
1784
  const projectCommandFiles = fs.readdirSync(projectClaudeCommands)
1758
1785
  .filter(f => f.startsWith('rihal-') && f.endsWith('.md'));
1759
1786
  for (const f of projectCommandFiles) {
1760
1787
  fs.unlinkSync(path.join(projectClaudeCommands, f));
1761
1788
  }
1789
+ // Remove rihal/ subdirectory (vscode-style commands)
1790
+ const rihalSubdir = path.join(projectClaudeCommands, 'rihal');
1791
+ if (fs.existsSync(rihalSubdir)) {
1792
+ fs.rmSync(rihalSubdir, { recursive: true, force: true });
1793
+ }
1762
1794
  const projectAgentsDir = path.join(opts.target, '.claude', 'agents');
1763
1795
  if (fs.existsSync(projectAgentsDir)) {
1764
1796
  const agentFiles = fs.readdirSync(projectAgentsDir)
package/cli/uninstall.js CHANGED
@@ -138,9 +138,17 @@ function buildPlan(cwd, editors) {
138
138
  .readdirSync(skillsDir)
139
139
  .filter((name) => name.startsWith('rihal-') || isKnownSkillName(name));
140
140
  }
141
- const commandsDir = path.join(cwd, '.claude/commands/rihal');
142
- if (fs.existsSync(commandsDir)) {
143
- plan.claude.commands = fs.readdirSync(commandsDir);
141
+ // Collect commands from vscode-style subdir (.claude/commands/rihal/) and
142
+ // claude-style root-level files (.claude/commands/rihal-*.md).
143
+ const commandsSubdir = path.join(cwd, '.claude/commands/rihal');
144
+ if (fs.existsSync(commandsSubdir)) {
145
+ plan.claude.commands = fs.readdirSync(commandsSubdir);
146
+ }
147
+ const commandsRoot = path.join(cwd, '.claude/commands');
148
+ if (fs.existsSync(commandsRoot)) {
149
+ const rootFiles = fs.readdirSync(commandsRoot)
150
+ .filter(f => f.startsWith('rihal-') && (f.endsWith('.md') || f.endsWith('.mdc')));
151
+ plan.claude.commands = [...plan.claude.commands, ...rootFiles];
144
152
  }
145
153
  // v2 installs agents to .claude/agents/rihal-*.md — scan for them
146
154
  const agentsDir = path.join(cwd, '.claude/agents');
@@ -499,11 +507,26 @@ async function runUninstall(args) {
499
507
  removed += n;
500
508
  if (n > 0) console.log(` ✓ removed ${n} Claude skills`);
501
509
 
510
+ // Remove vscode-style subdir .claude/commands/rihal/
502
511
  const commandsDir = path.join(cwd, '.claude/commands/rihal');
503
512
  if (fs.existsSync(commandsDir)) {
504
513
  fs.rmSync(commandsDir, { recursive: true, force: true });
505
- removed += plan.claude.commands.length;
506
- console.log(` ✓ removed .claude/commands/rihal/ (${plan.claude.commands.length} slash commands)`);
514
+ }
515
+ // Remove claude-style root-level rihal-*.md files
516
+ const commandsRoot = path.join(cwd, '.claude/commands');
517
+ let commandsRemoved = 0;
518
+ if (fs.existsSync(commandsRoot)) {
519
+ for (const f of fs.readdirSync(commandsRoot)) {
520
+ if (f.startsWith('rihal-') && (f.endsWith('.md') || f.endsWith('.mdc'))) {
521
+ fs.unlinkSync(path.join(commandsRoot, f));
522
+ commandsRemoved++;
523
+ }
524
+ }
525
+ }
526
+ const totalCommandsRemoved = plan.claude.commands.length;
527
+ removed += totalCommandsRemoved;
528
+ if (totalCommandsRemoved > 0) {
529
+ console.log(` ✓ removed ${totalCommandsRemoved} slash commands from .claude/commands/`);
507
530
  }
508
531
 
509
532
  // v2: .claude/agents/rihal-*.md
package/cli/update.js CHANGED
@@ -142,9 +142,19 @@ function removeOldSkillFiles(cwd, editors) {
142
142
  }
143
143
  }
144
144
  }
145
- const commandsDir = path.join(cwd, '.claude/commands/rihal');
146
- if (fs.existsSync(commandsDir)) {
147
- fs.rmSync(commandsDir, { recursive: true, force: true });
145
+ // Remove vscode-style subdir .claude/commands/rihal/
146
+ const commandsSubdir = path.join(cwd, '.claude/commands/rihal');
147
+ if (fs.existsSync(commandsSubdir)) {
148
+ fs.rmSync(commandsSubdir, { recursive: true, force: true });
149
+ }
150
+ // Remove claude-style root-level .claude/commands/rihal-*.md files
151
+ const commandsRoot = path.join(cwd, '.claude/commands');
152
+ if (fs.existsSync(commandsRoot)) {
153
+ for (const f of fs.readdirSync(commandsRoot)) {
154
+ if (f.startsWith('rihal-') && (f.endsWith('.md') || f.endsWith('.mdc'))) {
155
+ fs.unlinkSync(path.join(commandsRoot, f));
156
+ }
157
+ }
148
158
  }
149
159
  }
150
160
 
@@ -322,10 +332,15 @@ async function runUpdate(args, { packageRoot, packageJson }) {
322
332
  // ------ Re-run unified installer ------
323
333
  // Delegates to cli/install.js which handles all IDE-specific file shipping
324
334
  // (agents, commands, skills, workflows, references, bin). install.js is
325
- // the single source of truth — update.js reuses it with --force.
335
+ // the single source of truth — call once with the full IDE array so that
336
+ // buildInstallPlan can deduplicate across IDEs (e.g. claude+vscode).
326
337
  console.log();
327
- for (const ide of editors) {
328
- if (!['claude', 'cursor', 'gemini'].includes(ide)) continue;
338
+ const supportedIdes = editors.filter(ide => ['claude', 'cursor', 'gemini'].includes(ide));
339
+ if (supportedIdes.length > 0) {
340
+ if (editors.some(ide => !['claude', 'cursor', 'gemini'].includes(ide))) {
341
+ const unsupported = editors.filter(ide => !['claude', 'cursor', 'gemini'].includes(ide));
342
+ console.log(` ⚠ ${unsupported.join(', ')} refresh not yet supported by installer — skipping`);
343
+ }
329
344
  install.install({
330
345
  target: cwd,
331
346
  force: true,
@@ -334,11 +349,11 @@ async function runUpdate(args, { packageRoot, packageJson }) {
334
349
  projectName: config.project_name || require('path').basename(cwd),
335
350
  language: config.language || 'English',
336
351
  mode: config.mode || 'guided',
337
- ide,
352
+ ides: supportedIdes,
338
353
  modules: [],
339
354
  help: false,
340
355
  });
341
- console.log(` ✓ ${ide} → refreshed via install.js`);
356
+ console.log(` ✓ [${supportedIdes.join(', ')}] → refreshed via install.js`);
342
357
  }
343
358
 
344
359
  // ------ Update installed_version in config.json (atomic) ------
package/dist/rcode.js CHANGED
@@ -15650,6 +15650,19 @@ ${BLOCK}`);
15650
15650
  }
15651
15651
  }
15652
15652
  }
15653
+ if (ide.includes("claude") && ide.includes("vscode")) {
15654
+ const claudeCommandRels = new Set(
15655
+ merged.filter((e) => e.ide === "claude" && e.rel.split(path2.sep).join("/").startsWith(".claude/commands/")).map((e) => path2.basename(e.rel, ".md").replace(/^rihal-/, ""))
15656
+ );
15657
+ return merged.filter((e) => {
15658
+ const rel = e.rel.split(path2.sep).join("/");
15659
+ if (e.ide === "vscode" && rel.startsWith(".claude/commands/rihal/")) {
15660
+ const baseName = path2.basename(e.rel, path2.extname(e.rel));
15661
+ return !claudeCommandRels.has(baseName);
15662
+ }
15663
+ return true;
15664
+ });
15665
+ }
15653
15666
  return merged;
15654
15667
  }
15655
15668
  const plan = [];
@@ -16272,10 +16285,10 @@ ${BLOCK}`);
16272
16285
  const globalClaudeCommands = path2.join(os.homedir(), ".claude", "commands");
16273
16286
  const projectClaudeCommands = path2.join(opts.target, ".claude", "commands");
16274
16287
  const isProjectInstall = opts.target !== os.homedir();
16275
- if (isProjectInstall && !opts.force && !opts.forceOverwrite) {
16288
+ if (isProjectInstall && !opts.forceOverwrite) {
16276
16289
  try {
16277
- const globalHasRihal = fs2.existsSync(globalClaudeCommands) && fs2.readdirSync(globalClaudeCommands).some((f) => f.startsWith("rihal-") && f.endsWith(".md"));
16278
- const projectHasRihal = fs2.existsSync(projectClaudeCommands) && fs2.readdirSync(projectClaudeCommands).some((f) => f.startsWith("rihal-") && f.endsWith(".md"));
16290
+ const globalHasRihal = fs2.existsSync(globalClaudeCommands) && (fs2.readdirSync(globalClaudeCommands).some((f) => f.startsWith("rihal-") && f.endsWith(".md")) || fs2.existsSync(path2.join(globalClaudeCommands, "rihal")));
16291
+ const projectHasRihal = fs2.existsSync(projectClaudeCommands) && (fs2.readdirSync(projectClaudeCommands).some((f) => f.startsWith("rihal-") && f.endsWith(".md")) || fs2.existsSync(path2.join(projectClaudeCommands, "rihal")));
16279
16292
  if (globalHasRihal && !projectHasRihal) {
16280
16293
  const before = plan.length;
16281
16294
  const filtered = plan.filter((e) => {
@@ -16294,6 +16307,10 @@ ${BLOCK}`);
16294
16307
  for (const f of projectCommandFiles) {
16295
16308
  fs2.unlinkSync(path2.join(projectClaudeCommands, f));
16296
16309
  }
16310
+ const rihalSubdir = path2.join(projectClaudeCommands, "rihal");
16311
+ if (fs2.existsSync(rihalSubdir)) {
16312
+ fs2.rmSync(rihalSubdir, { recursive: true, force: true });
16313
+ }
16297
16314
  const projectAgentsDir = path2.join(opts.target, ".claude", "agents");
16298
16315
  if (fs2.existsSync(projectAgentsDir)) {
16299
16316
  const agentFiles = fs2.readdirSync(projectAgentsDir).filter((f) => f.startsWith("rihal-") && f.endsWith(".md"));
@@ -17222,9 +17239,17 @@ var require_update = __commonJS({
17222
17239
  }
17223
17240
  }
17224
17241
  }
17225
- const commandsDir = path2.join(cwd, ".claude/commands/rihal");
17226
- if (fs2.existsSync(commandsDir)) {
17227
- fs2.rmSync(commandsDir, { recursive: true, force: true });
17242
+ const commandsSubdir = path2.join(cwd, ".claude/commands/rihal");
17243
+ if (fs2.existsSync(commandsSubdir)) {
17244
+ fs2.rmSync(commandsSubdir, { recursive: true, force: true });
17245
+ }
17246
+ const commandsRoot = path2.join(cwd, ".claude/commands");
17247
+ if (fs2.existsSync(commandsRoot)) {
17248
+ for (const f of fs2.readdirSync(commandsRoot)) {
17249
+ if (f.startsWith("rihal-") && (f.endsWith(".md") || f.endsWith(".mdc"))) {
17250
+ fs2.unlinkSync(path2.join(commandsRoot, f));
17251
+ }
17252
+ }
17228
17253
  }
17229
17254
  }
17230
17255
  if (editors.includes("cursor")) {
@@ -17380,8 +17405,12 @@ var require_update = __commonJS({
17380
17405
  console.log(` \u{1F9F9} cleaned ${totalRemoved} old skill files`);
17381
17406
  stripAgentsMdSection(cwd);
17382
17407
  console.log();
17383
- for (const ide of editors) {
17384
- if (!["claude", "cursor", "gemini"].includes(ide)) continue;
17408
+ const supportedIdes = editors.filter((ide) => ["claude", "cursor", "gemini"].includes(ide));
17409
+ if (supportedIdes.length > 0) {
17410
+ if (editors.some((ide) => !["claude", "cursor", "gemini"].includes(ide))) {
17411
+ const unsupported = editors.filter((ide) => !["claude", "cursor", "gemini"].includes(ide));
17412
+ console.log(` \u26A0 ${unsupported.join(", ")} refresh not yet supported by installer \u2014 skipping`);
17413
+ }
17385
17414
  install.install({
17386
17415
  target: cwd,
17387
17416
  force: true,
@@ -17390,11 +17419,11 @@ var require_update = __commonJS({
17390
17419
  projectName: config.project_name || require("path").basename(cwd),
17391
17420
  language: config.language || "English",
17392
17421
  mode: config.mode || "guided",
17393
- ide,
17422
+ ides: supportedIdes,
17394
17423
  modules: [],
17395
17424
  help: false
17396
17425
  });
17397
- console.log(` \u2713 ${ide} \u2192 refreshed via install.js`);
17426
+ console.log(` \u2713 [${supportedIdes.join(", ")}] \u2192 refreshed via install.js`);
17398
17427
  }
17399
17428
  config.installed_version = packageVersion;
17400
17429
  writeJsonAtomic(configPath, config);
@@ -17507,9 +17536,14 @@ var require_uninstall = __commonJS({
17507
17536
  if (fs2.existsSync(skillsDir)) {
17508
17537
  plan.claude.skills = fs2.readdirSync(skillsDir).filter((name) => name.startsWith("rihal-") || isKnownSkillName(name));
17509
17538
  }
17510
- const commandsDir = path2.join(cwd, ".claude/commands/rihal");
17511
- if (fs2.existsSync(commandsDir)) {
17512
- plan.claude.commands = fs2.readdirSync(commandsDir);
17539
+ const commandsSubdir = path2.join(cwd, ".claude/commands/rihal");
17540
+ if (fs2.existsSync(commandsSubdir)) {
17541
+ plan.claude.commands = fs2.readdirSync(commandsSubdir);
17542
+ }
17543
+ const commandsRoot = path2.join(cwd, ".claude/commands");
17544
+ if (fs2.existsSync(commandsRoot)) {
17545
+ const rootFiles = fs2.readdirSync(commandsRoot).filter((f) => f.startsWith("rihal-") && (f.endsWith(".md") || f.endsWith(".mdc")));
17546
+ plan.claude.commands = [...plan.claude.commands, ...rootFiles];
17513
17547
  }
17514
17548
  const agentsDir = path2.join(cwd, ".claude/agents");
17515
17549
  if (fs2.existsSync(agentsDir)) {
@@ -17782,8 +17816,21 @@ var require_uninstall = __commonJS({
17782
17816
  const commandsDir = path2.join(cwd, ".claude/commands/rihal");
17783
17817
  if (fs2.existsSync(commandsDir)) {
17784
17818
  fs2.rmSync(commandsDir, { recursive: true, force: true });
17785
- removed += plan.claude.commands.length;
17786
- console.log(` \u2713 removed .claude/commands/rihal/ (${plan.claude.commands.length} slash commands)`);
17819
+ }
17820
+ const commandsRoot = path2.join(cwd, ".claude/commands");
17821
+ let commandsRemoved = 0;
17822
+ if (fs2.existsSync(commandsRoot)) {
17823
+ for (const f of fs2.readdirSync(commandsRoot)) {
17824
+ if (f.startsWith("rihal-") && (f.endsWith(".md") || f.endsWith(".mdc"))) {
17825
+ fs2.unlinkSync(path2.join(commandsRoot, f));
17826
+ commandsRemoved++;
17827
+ }
17828
+ }
17829
+ }
17830
+ const totalCommandsRemoved = plan.claude.commands.length;
17831
+ removed += totalCommandsRemoved;
17832
+ if (totalCommandsRemoved > 0) {
17833
+ console.log(` \u2713 removed ${totalCommandsRemoved} slash commands from .claude/commands/`);
17787
17834
  }
17788
17835
  const agentsDir = path2.join(cwd, ".claude/agents");
17789
17836
  const nAgents = removeMatching(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hanzlaa/rcode",
3
- "version": "3.4.15",
3
+ "version": "3.4.16",
4
4
  "description": "rcode — the memory bank for AI-driven SaaS teams. Persistent project context, distinctive engineering personas, and phase-based workflows. Built by Rihal. Works in Claude Code, Cursor, Gemini, VS Code, and Antigravity.",
5
5
  "main": "cli/index.js",
6
6
  "bin": {