roadmapsmith 0.9.15 → 0.9.22

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/src/host.js CHANGED
@@ -1,12 +1,15 @@
1
1
  'use strict';
2
2
 
3
3
  const fs = require('fs');
4
+ const os = require('os');
4
5
  const path = require('path');
5
6
  const { readTextIfExists, writeText } = require('./io');
6
- const { getSlashActionSpecs } = require('./slash');
7
+ const { getHostNativeSkillNames, getHostNativeSlashCommands, getSlashActionSpecs } = require('./slash');
7
8
 
8
9
  const SUPPORTED_EDITORS = new Set(['vscode']);
9
10
  const SUPPORTED_HOSTS = new Set(['codex', 'claude']);
11
+ const PACKAGE_ROOT = path.resolve(__dirname, '..');
12
+ const REPO_ROOT = path.resolve(PACKAGE_ROOT, '..');
10
13
  const VSCODE_LAUNCHER_RELATIVE_PATH = '.vscode/roadmapsmith-launcher.js';
11
14
  const WINDOWS_TASK_WRAPPER_RELATIVE_PATH = '.vscode/roadmapsmith-task.cmd';
12
15
  const POSIX_TASK_WRAPPER_RELATIVE_PATH = '.vscode/roadmapsmith-task.sh';
@@ -25,6 +28,14 @@ const ROADMAPSMITH_TASK_LABELS = [
25
28
  ];
26
29
  const CLAUDE_HOOK_COMMAND = 'node .claude/hooks/roadmap-sync.js';
27
30
  const CLAUDE_HOOK_RELATIVE_PATH = '.claude/hooks/roadmap-sync.js';
31
+ const ROADMAPSMITH_PLUGIN_NAME = 'roadmapsmith';
32
+ const LEGACY_ROADMAP_SYNC_SKILL_NAME = 'roadmap-sync';
33
+ const EXPECTED_NATIVE_SKILL_NAMES = Object.freeze(getHostNativeSkillNames());
34
+ const EXPECTED_NATIVE_SLASH_COMMANDS = Object.freeze(getHostNativeSlashCommands());
35
+ const BUNDLE_ROOT_CANDIDATES = Object.freeze([
36
+ { kind: 'package', root: PACKAGE_ROOT },
37
+ { kind: 'repo', root: REPO_ROOT }
38
+ ]);
28
39
 
29
40
  function removeJsonComments(content) {
30
41
  let result = '';
@@ -156,6 +167,64 @@ function stringifyJson(value) {
156
167
  return JSON.stringify(value, null, 2);
157
168
  }
158
169
 
170
+ function readJson(filePath) {
171
+ return JSON.parse(fs.readFileSync(filePath, 'utf8'));
172
+ }
173
+
174
+ function readJsonIfExists(filePath) {
175
+ return fs.existsSync(filePath) ? readJson(filePath) : null;
176
+ }
177
+
178
+ function normalizeComparablePath(filePath) {
179
+ if (!filePath) {
180
+ return null;
181
+ }
182
+ const normalized = path.resolve(String(filePath));
183
+ return process.platform === 'win32' ? normalized.toLowerCase() : normalized;
184
+ }
185
+
186
+ function getHomeDirectory(options = {}, env = process.env) {
187
+ return options.homeDir
188
+ || env.ROADMAPSMITH_HOME
189
+ || env.HOME
190
+ || env.USERPROFILE
191
+ || os.homedir();
192
+ }
193
+
194
+ function findCommandPaths(commandName, env = process.env) {
195
+ const probe = process.platform === 'win32'
196
+ ? require('child_process').spawnSync('where', [commandName], { encoding: 'utf8', env })
197
+ : require('child_process').spawnSync('which', [commandName], { encoding: 'utf8', env });
198
+ if (probe.status !== 0 || !probe.stdout) {
199
+ return [];
200
+ }
201
+
202
+ return probe.stdout
203
+ .split(/\r?\n/)
204
+ .map((line) => line.trim())
205
+ .filter(Boolean);
206
+ }
207
+
208
+ function pickPreferredCommandPath(paths) {
209
+ if (!Array.isArray(paths) || paths.length === 0) {
210
+ return null;
211
+ }
212
+
213
+ if (process.platform !== 'win32') {
214
+ return paths[0];
215
+ }
216
+
217
+ const priorities = ['.exe', '.cmd', '.bat', '.ps1'];
218
+ for (const extension of priorities) {
219
+ const match = paths.find((candidate) => candidate.toLowerCase().endsWith(extension));
220
+ if (match) {
221
+ return match;
222
+ }
223
+ }
224
+
225
+ return paths[0];
226
+ }
227
+
159
228
  function normalizeHostValue(host) {
160
229
  return String(host || '').trim().toLowerCase();
161
230
  }
@@ -223,11 +292,11 @@ function createTask(action, label, detail) {
223
292
  function createManagedTasks() {
224
293
  return [
225
294
  createTask('zero', 'RoadmapSmith: Zero Mode', 'Run the Zero Mode interview and generate the first roadmap in one command.'),
226
- createTask('maintain', 'RoadmapSmith: Maintain', 'Run the default existing-repo flow: generate, sync, and audit in one command.'),
227
- createTask('status', 'RoadmapSmith: Status', 'Inspect readiness and learn the slash entrypoints like /road and /roadmap-sync <action>.'),
295
+ createTask('maintain', 'RoadmapSmith: Maintain', 'Run the preserve-first existing-repo flow: generate, sync, and audit in one command.'),
296
+ createTask('status', 'RoadmapSmith: Status', 'Inspect readiness and learn the slash entrypoints like /roadmap, /roadmap-update, and legacy /roadmap-sync <action>.'),
228
297
  createTask('explain', 'RoadmapSmith: Explain Workflow', 'Explain how zero, maintain, the skill, setup, slash routing, and VS Code tasks work together.'),
229
298
  createTask('init', 'RoadmapSmith: Init', 'Create ROADMAP.md and AGENTS.md when they are missing.'),
230
- createTask('generate', 'RoadmapSmith: Generate', 'Rebuild the managed roadmap block from repository context.'),
299
+ createTask('generate', 'RoadmapSmith: Generate', 'Update ROADMAP.md and refuse destructive replacement unless rerun with --full-regen.'),
231
300
  createTask('validate', 'RoadmapSmith: Validate', 'Inspect per-task evidence status as JSON.'),
232
301
  createTask('sync', 'RoadmapSmith: Sync', 'Apply evidence-backed checklist sync to ROADMAP.md.'),
233
302
  createTask('sync-dry-run', 'RoadmapSmith: Sync Dry Run', 'Preview the next roadmap sync without writing files.'),
@@ -422,13 +491,7 @@ function renderPosixTaskWrapper() {
422
491
  }
423
492
 
424
493
  function findCommandPath(commandName, env = process.env) {
425
- const probe = process.platform === 'win32'
426
- ? require('child_process').spawnSync('where', [commandName], { encoding: 'utf8', env })
427
- : require('child_process').spawnSync('which', [commandName], { encoding: 'utf8', env });
428
- if (probe.status !== 0 || !probe.stdout) {
429
- return null;
430
- }
431
- return probe.stdout.split(/\r?\n/).map((line) => line.trim()).find(Boolean) || null;
494
+ return pickPreferredCommandPath(findCommandPaths(commandName, env));
432
495
  }
433
496
 
434
497
  function detectNodeRuntime(env = process.env) {
@@ -470,6 +533,207 @@ function detectNodeRuntime(env = process.env) {
470
533
  };
471
534
  }
472
535
 
536
+ function resolveBundleSurface() {
537
+ const candidates = BUNDLE_ROOT_CANDIDATES.map(({ kind, root }) => {
538
+ const skillsManifestPath = path.join(root, 'skills.json');
539
+ const claudePluginManifestPath = path.join(root, '.claude-plugin', 'plugin.json');
540
+ const codexPluginManifestPath = path.join(root, '.codex-plugin', 'plugin.json');
541
+ return {
542
+ kind,
543
+ root,
544
+ skillsManifestPath,
545
+ claudePluginManifestPath,
546
+ codexPluginManifestPath,
547
+ hasSkillsManifest: fs.existsSync(skillsManifestPath),
548
+ hasClaudePluginManifest: fs.existsSync(claudePluginManifestPath),
549
+ hasCodexPluginManifest: fs.existsSync(codexPluginManifestPath)
550
+ };
551
+ });
552
+
553
+ return candidates.find((candidate) => {
554
+ return candidate.hasSkillsManifest
555
+ && candidate.hasClaudePluginManifest
556
+ && candidate.hasCodexPluginManifest;
557
+ }) || candidates.find((candidate) => {
558
+ return candidate.hasSkillsManifest
559
+ || candidate.hasClaudePluginManifest
560
+ || candidate.hasCodexPluginManifest;
561
+ }) || candidates[0];
562
+ }
563
+
564
+ function inspectSharedBundleSurface() {
565
+ const bundleSurface = resolveBundleSurface();
566
+ const skillsManifest = readJsonIfExists(bundleSurface.skillsManifestPath);
567
+ const declaredSkillNames = Array.isArray(skillsManifest && skillsManifest.skills)
568
+ ? skillsManifest.skills.map((skill) => skill && skill.name).filter(Boolean)
569
+ : [];
570
+ const availableCommands = declaredSkillNames.map((name) => `/${name}`);
571
+ const missingCommands = EXPECTED_NATIVE_SLASH_COMMANDS.filter((command) => !availableCommands.includes(command));
572
+
573
+ return {
574
+ kind: bundleSurface.kind,
575
+ root: bundleSurface.root,
576
+ ready: bundleSurface.hasSkillsManifest
577
+ && bundleSurface.hasClaudePluginManifest
578
+ && bundleSurface.hasCodexPluginManifest
579
+ && missingCommands.length === 0,
580
+ files: {
581
+ skillsManifestPath: bundleSurface.skillsManifestPath,
582
+ hasSkillsManifest: bundleSurface.hasSkillsManifest,
583
+ claudePluginManifestPath: bundleSurface.claudePluginManifestPath,
584
+ hasClaudePluginManifest: bundleSurface.hasClaudePluginManifest,
585
+ codexPluginManifestPath: bundleSurface.codexPluginManifestPath,
586
+ hasCodexPluginManifest: bundleSurface.hasCodexPluginManifest
587
+ },
588
+ declaredSkillNames,
589
+ expectedCommands: EXPECTED_NATIVE_SLASH_COMMANDS.slice(),
590
+ availableCommands,
591
+ missingCommands
592
+ };
593
+ }
594
+
595
+ function inspectLegacyRoadmapSyncSkill(options = {}, env = process.env) {
596
+ const homeDir = getHomeDirectory(options, env);
597
+ const skillDir = path.join(homeDir, '.agents', 'skills', LEGACY_ROADMAP_SYNC_SKILL_NAME);
598
+ const skillPath = path.join(skillDir, 'SKILL.md');
599
+ return {
600
+ exists: fs.existsSync(skillPath),
601
+ path: skillPath
602
+ };
603
+ }
604
+
605
+ function runJsonCommand(commandPath, args, env = process.env) {
606
+ const result = require('child_process').spawnSync(commandPath, args, {
607
+ encoding: 'utf8',
608
+ env
609
+ });
610
+
611
+ if (result.status !== 0) {
612
+ return {
613
+ ok: false,
614
+ error: result.stderr || result.stdout || `Command exited with code ${result.status}`,
615
+ status: result.status
616
+ };
617
+ }
618
+
619
+ try {
620
+ return {
621
+ ok: true,
622
+ payload: JSON.parse(result.stdout)
623
+ };
624
+ } catch (error) {
625
+ return {
626
+ ok: false,
627
+ error: `Invalid JSON from ${path.basename(commandPath)}: ${error.message}`
628
+ };
629
+ }
630
+ }
631
+
632
+ function inspectCodexPluginState(projectRoot, options = {}) {
633
+ const env = options.env || process.env;
634
+ const codexCommandPath = Object.prototype.hasOwnProperty.call(options, 'codexCommandPath')
635
+ ? options.codexCommandPath
636
+ : findCommandPath('codex', env);
637
+ const legacySkill = inspectLegacyRoadmapSyncSkill(options, env);
638
+ const expectedCommands = EXPECTED_NATIVE_SLASH_COMMANDS.slice();
639
+ const missingCommands = expectedCommands.slice();
640
+ const baseState = {
641
+ commandPath: codexCommandPath,
642
+ plugin: null,
643
+ marketplace: null,
644
+ expectedCommands,
645
+ availableCommands: [],
646
+ missingCommands,
647
+ duplicates: [],
648
+ ready: false,
649
+ source: codexCommandPath
650
+ ? 'Codex command detected but RoadmapSmith plugin is not installed'
651
+ : 'Codex command not found on this machine',
652
+ message: codexCommandPath
653
+ ? 'Install and enable the RoadmapSmith Codex plugin, then verify the host surfaces manually.'
654
+ : 'Install Codex locally before expecting native Codex GUI or CLI slash surfaces.'
655
+ };
656
+
657
+ if (!codexCommandPath) {
658
+ return baseState;
659
+ }
660
+
661
+ const pluginListResult = Object.prototype.hasOwnProperty.call(options, 'codexPluginList')
662
+ ? { ok: true, payload: options.codexPluginList }
663
+ : runJsonCommand(codexCommandPath, ['plugin', 'list', '--json'], env);
664
+ const marketplaceListResult = Object.prototype.hasOwnProperty.call(options, 'codexMarketplaceList')
665
+ ? { ok: true, payload: options.codexMarketplaceList }
666
+ : runJsonCommand(codexCommandPath, ['plugin', 'marketplace', 'list', '--json'], env);
667
+
668
+ if (!pluginListResult.ok) {
669
+ return {
670
+ ...baseState,
671
+ source: 'Codex command detected',
672
+ message: `Codex is installed, but plugin inspection failed: ${pluginListResult.error}`
673
+ };
674
+ }
675
+
676
+ const installedPlugins = Array.isArray(pluginListResult.payload && pluginListResult.payload.installed)
677
+ ? pluginListResult.payload.installed
678
+ : [];
679
+ const plugin = installedPlugins.find((entry) => entry && entry.name === ROADMAPSMITH_PLUGIN_NAME);
680
+
681
+ if (!plugin || !plugin.installed || !plugin.enabled) {
682
+ return {
683
+ ...baseState,
684
+ plugin: plugin || null,
685
+ source: plugin
686
+ ? `RoadmapSmith plugin is installed but not enabled (${plugin.pluginId || plugin.name})`
687
+ : 'Codex command detected',
688
+ message: plugin
689
+ ? 'Enable the installed RoadmapSmith Codex plugin to expose the native slash bundle.'
690
+ : 'Install and enable the RoadmapSmith Codex plugin to expose the native slash bundle.'
691
+ };
692
+ }
693
+
694
+ const marketplaces = Array.isArray(marketplaceListResult.payload && marketplaceListResult.payload.marketplaces)
695
+ ? marketplaceListResult.payload.marketplaces
696
+ : [];
697
+ const marketplace = marketplaces.find((entry) => entry && entry.name === plugin.marketplaceName) || null;
698
+ const duplicates = legacySkill.exists
699
+ ? [{
700
+ command: '/roadmap-sync',
701
+ reason: 'legacy ~/.agents/skills install and RoadmapSmith plugin both register the same skill name',
702
+ sources: [legacySkill.path, plugin.source && plugin.source.path].filter(Boolean)
703
+ }]
704
+ : [];
705
+
706
+ return {
707
+ commandPath: codexCommandPath,
708
+ plugin,
709
+ marketplace,
710
+ expectedCommands,
711
+ availableCommands: expectedCommands,
712
+ missingCommands: [],
713
+ duplicates,
714
+ ready: duplicates.length === 0,
715
+ source: `${plugin.pluginId || plugin.name}${plugin.marketplaceName ? ` (${plugin.marketplaceName})` : ''}`,
716
+ message: duplicates.length > 0
717
+ ? 'RoadmapSmith is installed in Codex, but /roadmap-sync is duplicated by the legacy ~/.agents/skills install.'
718
+ : 'RoadmapSmith is installed and enabled in Codex. Interactive slash-menu visibility still needs manual host verification.',
719
+ legacySkill
720
+ };
721
+ }
722
+
723
+ function createSurfaceStatus({ name, source, message, expectedCommands, availableCommands, missingCommands, duplicates, ready, verification }) {
724
+ return {
725
+ name,
726
+ source,
727
+ message,
728
+ expectedCommands,
729
+ availableCommands,
730
+ missingCommands,
731
+ duplicates,
732
+ ready,
733
+ verification
734
+ };
735
+ }
736
+
473
737
  function renderVsCodeLauncher() {
474
738
  const slashSpecsJson = JSON.stringify(getSlashActionSpecs());
475
739
  return [
@@ -484,18 +748,8 @@ function renderVsCodeLauncher() {
484
748
  'const RAW_ARGS = process.argv.slice(2);',
485
749
  'const ACTION = RAW_ARGS[0] || \'explain\';',
486
750
  `const SLASH_ACTIONS = ${slashSpecsJson};`,
487
- 'const SLASH_ROOT_ALIASES = new Set([\'/road\', \'/roadmap-sync\']);',
488
- 'const DIRECT_SLASH_ALIAS_TO_ACTION = {',
489
- ' \'/zero\': \'zero\',',
490
- ' \'/maintain\': \'maintain\',',
491
- ' \'/status\': \'status\',',
492
- ' \'/init\': \'init\',',
493
- ' \'/generate\': \'generate\',',
494
- ' \'/validate\': \'validate\',',
495
- ' \'/sync\': \'sync\',',
496
- ' \'/audit\': \'audit\',',
497
- ' \'/setup\': \'setup\'',
498
- '};',
751
+ 'const SLASH_ROOT_ALIASES = new Set([\'/roadmap\', \'/road\']);',
752
+ 'const LEGACY_ROUTER_ALIAS = \'/roadmap-sync\';',
499
753
  'const LOCAL_DEV_CLI = path.join(PROJECT_ROOT, \'roadmap-skill\', \'bin\', \'cli.js\');',
500
754
  'const LOCAL_PACKAGE_CLI = path.join(PROJECT_ROOT, \'node_modules\', \'roadmapsmith\', \'bin\', \'cli.js\');',
501
755
  '',
@@ -528,8 +782,26 @@ function renderVsCodeLauncher() {
528
782
  ' return null;',
529
783
  '}',
530
784
  '',
785
+ 'function getNamespacedDirectSlash(actionId) {',
786
+ ' return actionId === \'sync\' ? \'/roadmap-update\' : `/roadmap-${actionId}`;',
787
+ '}',
788
+ '',
789
+ 'const DIRECT_HOST_NATIVE_ALIAS_TO_ACTION = Object.fromEntries(',
790
+ ' SLASH_ACTIONS.map((action) => [getNamespacedDirectSlash(action.id), action.id])',
791
+ ');',
792
+ 'const DIRECT_DEPRECATED_CLI_ALIAS_TO_ACTION = Object.fromEntries(',
793
+ ' SLASH_ACTIONS.map((action) => [`/${action.id}`, action.id])',
794
+ ');',
795
+ '',
531
796
  'function normalizeActionId(value) {',
532
- ' return String(value || \'\').trim().toLowerCase().replace(/^\\/+/g, \'\');',
797
+ ' let normalized = String(value || \'\').trim().toLowerCase().replace(/^\\/+/g, \'\');',
798
+ ' if (normalized.startsWith(\'roadmap-\')) {',
799
+ ' normalized = normalized.slice(\'roadmap-\'.length);',
800
+ ' }',
801
+ ' if (normalized === \'update\') {',
802
+ ' normalized = \'sync\';',
803
+ ' }',
804
+ ' return normalized;',
533
805
  '}',
534
806
  '',
535
807
  'function getSlashSuggestions(query) {',
@@ -543,12 +815,16 @@ function renderVsCodeLauncher() {
543
815
  '}',
544
816
  '',
545
817
  'function renderSlashPalette(route) {',
546
- ' const source = route && route.source ? route.source : \'/road\';',
818
+ ' const source = route && route.source ? route.source : \'/roadmap\';',
547
819
  ' const query = normalizeActionId(route && route.query);',
548
820
  ' const suggestions = route && Array.isArray(route.suggestions) ? route.suggestions : getSlashSuggestions(query);',
549
821
  ' const lines = [];',
550
822
  ' lines.push(\'RoadmapSmith slash palette\');',
551
823
  ' lines.push(\'\');',
824
+ ' if (route && route.deprecated && route.deprecationMessage) {',
825
+ ' lines.push(`Deprecated alias: ${route.deprecationMessage}`);',
826
+ ' lines.push(\'\');',
827
+ ' }',
552
828
  ' if (query) {',
553
829
  ' lines.push(`Input: ${source} ${query}`);',
554
830
  ' lines.push(suggestions.length > 0 ? \'No exact slash match was executed. Related actions:\' : \'No exact slash match was executed.\');',
@@ -561,62 +837,97 @@ function renderVsCodeLauncher() {
561
837
  ' lines.push(\'No related slash actions found.\');',
562
838
  ' } else {',
563
839
  ' suggestions.forEach((action) => {',
564
- ' lines.push(`- /${action.id}: ${action.description}`);',
840
+ ' lines.push(`- ${action.directSlash}: ${action.description}`);',
841
+ ' lines.push(` Router form: ${action.routerSlash}`);',
842
+ ' lines.push(` Legacy router: ${action.legacyRouterSlash}`);',
565
843
  ' lines.push(` Classic CLI: ${action.classicCliExample}`);',
566
- ' lines.push(` Skill form: /roadmap-sync ${action.id}`);',
567
844
  ' lines.push(` VS Code task: ${action.taskLabel}`);',
568
845
  ' });',
569
846
  ' }',
570
847
  ' lines.push(\'\');',
571
848
  ' lines.push(\'Examples:\');',
572
- ' lines.push(\'- roadmapsmith zero\');',
573
- ' lines.push(\'- roadmapsmith maintain\');',
574
- ' lines.push(\'- roadmapsmith /road\');',
575
- ' lines.push(\'- roadmapsmith /maintain\');',
576
- ' lines.push(\'- roadmapsmith /roadmap-sync maintain\');',
849
+ ' lines.push(\'- roadmapsmith /roadmap\');',
850
+ ' lines.push(\'- roadmapsmith /roadmap maintain\');',
851
+ ' lines.push(\'- roadmapsmith /roadmap-maintain\');',
852
+ ' lines.push(\'- roadmapsmith /roadmap-update\');',
853
+ ' lines.push(\'- roadmapsmith /roadmap-sync validate\');',
577
854
  ' lines.push(\'\');',
578
855
  ' lines.push(\'Installing the skill alone does not expose CLI behavior in VS Code. Use roadmapsmith setup for the visible task/launcher layer.\');',
579
856
  ' return lines.join(\'\\n\');',
580
857
  '}',
581
858
  '',
859
+ 'function getSlashAction(actionId) {',
860
+ ' const normalized = normalizeActionId(actionId);',
861
+ ' return SLASH_ACTIONS.find((action) => action.id === normalized) || null;',
862
+ '}',
863
+ '',
864
+ 'function paletteResponse(source, query, deprecated = false, deprecationMessage = \'\') {',
865
+ ' return { kind: \'palette\', query, source, suggestions: getSlashSuggestions(query), deprecated, deprecationMessage };',
866
+ '}',
867
+ '',
868
+ 'function executeResponse(source, actionId, query, deprecated = false, deprecationMessage = \'\') {',
869
+ ' return { kind: \'execute\', actionId, query, source, suggestions: getSlashSuggestions(query), deprecated, deprecationMessage };',
870
+ '}',
871
+ '',
582
872
  'function resolveSlashInvocation(command, args) {',
583
873
  ' if (typeof command !== \'string\' || !command.trim().startsWith(\'/\')) {',
584
874
  ' return null;',
585
875
  ' }',
586
876
  ' const normalizedCommand = command.trim().toLowerCase();',
587
- ' if (Object.prototype.hasOwnProperty.call(DIRECT_SLASH_ALIAS_TO_ACTION, normalizedCommand)) {',
588
- ' return {',
589
- ' kind: \'execute\',',
590
- ' actionId: DIRECT_SLASH_ALIAS_TO_ACTION[normalizedCommand],',
591
- ' query: normalizeActionId(normalizedCommand),',
592
- ' source: normalizedCommand,',
593
- ' suggestions: getSlashSuggestions(normalizedCommand)',
594
- ' };',
877
+ ' if (normalizedCommand === LEGACY_ROUTER_ALIAS) {',
878
+ ' if (args.length === 0) {',
879
+ ' return paletteResponse(normalizedCommand, \'\');',
880
+ ' }',
881
+ ' const queryToken = normalizeActionId(args[0]);',
882
+ ' const deprecationMessage = \'Legacy CLI compatibility root /roadmap-sync <action> is deprecated. Use /roadmap <action> or the direct /roadmap-* commands.\';',
883
+ ' const exactAction = getSlashAction(queryToken);',
884
+ ' if (exactAction) {',
885
+ ' return executeResponse(normalizedCommand, exactAction.id, queryToken, true, deprecationMessage);',
886
+ ' }',
887
+ ' return paletteResponse(normalizedCommand, queryToken, true, deprecationMessage);',
888
+ ' }',
889
+ ' if (Object.prototype.hasOwnProperty.call(DIRECT_HOST_NATIVE_ALIAS_TO_ACTION, normalizedCommand)) {',
890
+ ' return executeResponse(normalizedCommand, DIRECT_HOST_NATIVE_ALIAS_TO_ACTION[normalizedCommand], normalizeActionId(normalizedCommand));',
891
+ ' }',
892
+ ' if (Object.prototype.hasOwnProperty.call(DIRECT_DEPRECATED_CLI_ALIAS_TO_ACTION, normalizedCommand)) {',
893
+ ' const actionId = DIRECT_DEPRECATED_CLI_ALIAS_TO_ACTION[normalizedCommand];',
894
+ ' return executeResponse(',
895
+ ' normalizedCommand,',
896
+ ' actionId,',
897
+ ' normalizeActionId(normalizedCommand),',
898
+ ' true,',
899
+ ' `CLI compatibility alias ${normalizedCommand} is deprecated. Use ${getNamespacedDirectSlash(actionId)} or /roadmap ${actionId}.`',
900
+ ' );',
595
901
  ' }',
596
902
  ' if (SLASH_ROOT_ALIASES.has(normalizedCommand)) {',
597
903
  ' const queryToken = args.length > 0 ? normalizeActionId(args[0]) : \'\';',
904
+ ' const deprecated = normalizedCommand === \'/road\';',
905
+ ' const deprecationMessage = deprecated ? \'CLI compatibility alias /road is deprecated. Use /roadmap.\' : \'\';',
598
906
  ' if (!queryToken) {',
599
- ' return { kind: \'palette\', query: \'\', source: normalizedCommand, suggestions: getSlashSuggestions(\'\') };',
907
+ ' return paletteResponse(normalizedCommand, \'\', deprecated, deprecationMessage);',
600
908
  ' }',
601
- ' const exactAction = SLASH_ACTIONS.find((action) => action.id === queryToken);',
909
+ ' const exactAction = getSlashAction(queryToken);',
602
910
  ' if (exactAction) {',
603
- ' return { kind: \'execute\', actionId: exactAction.id, query: queryToken, source: normalizedCommand, suggestions: getSlashSuggestions(queryToken) };',
911
+ ' return executeResponse(normalizedCommand, exactAction.id, queryToken, deprecated, deprecationMessage);',
604
912
  ' }',
605
- ' return { kind: \'palette\', query: queryToken, source: normalizedCommand, suggestions: getSlashSuggestions(queryToken) };',
913
+ ' return paletteResponse(normalizedCommand, queryToken, deprecated, deprecationMessage);',
606
914
  ' }',
607
- ' return { kind: \'palette\', query: normalizeActionId(normalizedCommand), source: normalizedCommand, suggestions: getSlashSuggestions(normalizedCommand) };',
915
+ ' if (normalizedCommand.startsWith(\'/roadmap-\')) {',
916
+ ' return paletteResponse(normalizedCommand, normalizeActionId(normalizedCommand));',
917
+ ' }',
918
+ ' return paletteResponse(normalizedCommand, normalizeActionId(normalizedCommand));',
608
919
  '}',
609
920
  '',
610
921
  'function explain() {',
611
922
  ' console.log(\'RoadmapSmith layers:\\n\');',
612
923
  ' console.log(\'1. The roadmap-sync skill guides the agent. It does not add VS Code buttons or install the CLI.\');',
613
- ' console.log(\'2. The roadmapsmith CLI executes zero/maintain plus the lower-level init/generate/validate/sync/setup/doctor commands.\');',
924
+ ' console.log(\'2. The roadmapsmith CLI executes zero/maintain plus init/generate/validate/sync/setup/doctor, with --full-regen reserved for destructive replacement.\');',
614
925
  ' console.log(\'3. roadmapsmith setup makes the CLI visible in VS Code through tasks and optional Claude hook wiring.\\n\');',
615
926
  ' console.log(\'Typical VS Code workflow:\');',
616
927
  ' console.log(\'- Run "RoadmapSmith: Status" to inspect readiness.\');',
617
- ' console.log(\'- For empty repos, run "RoadmapSmith: Zero Mode" or use "/road zero".\');',
618
- ' console.log(\'- For existing repos, run "RoadmapSmith: Maintain" or use "/road maintain".\');',
619
- ' console.log(\'- Use the lower-level Init, Generate, Validate, and Sync tasks only when you want manual control.\\n\');',
928
+ ' console.log(\'- For empty repos, run "RoadmapSmith: Zero Mode" or use "/roadmap zero".\');',
929
+ ' console.log(\'- For existing repos, run "RoadmapSmith: Maintain" or use "/roadmap maintain".\');',
930
+ ' console.log(\'- Use Init, Generate, Validate, and Sync when you want manual control.\\n\');',
620
931
  ' console.log(\'If you installed only the skill, install the CLI as well and then run "RoadmapSmith: Refresh Setup".\');',
621
932
  '}',
622
933
  '',
@@ -643,6 +954,21 @@ function renderVsCodeLauncher() {
643
954
  ' }',
644
955
  ' console.log(`Codex readiness: ${payload.hosts.codex.ready ? \'ready\' : \'needs setup\'} (${payload.hosts.codex.message})`);',
645
956
  ' console.log(`Claude readiness: ${payload.hosts.claude.ready ? \'ready\' : \'needs setup\'} (${payload.hosts.claude.message})`);',
957
+ ' if (payload.surfaces && typeof payload.surfaces === \'object\') {',
958
+ ' console.log(\'\\nNative slash surfaces:\');',
959
+ ' Object.entries(payload.surfaces).forEach(([surfaceKey, surface]) => {',
960
+ ' const label = surfaceKey.replace(/([A-Z])/g, \' $1\').replace(/^./, (character) => character.toUpperCase());',
961
+ ' console.log(`- ${label}: ${surface.ready ? \'ready\' : \'needs attention\'} (${surface.message})`);',
962
+ ' console.log(` Source: ${surface.source}`);',
963
+ ' console.log(` Verification: ${surface.verification}`);',
964
+ ' if (Array.isArray(surface.missingCommands) && surface.missingCommands.length > 0) {',
965
+ ' console.log(` Missing commands: ${surface.missingCommands.join(\', \')}`);',
966
+ ' }',
967
+ ' if (Array.isArray(surface.duplicates) && surface.duplicates.length > 0) {',
968
+ ' console.log(` Duplicates: ${surface.duplicates.map((duplicate) => duplicate.command).join(\', \')}`);',
969
+ ' }',
970
+ ' });',
971
+ ' }',
646
972
  ' if (!payload.cli.ready) {',
647
973
  ' console.log(\'\\nThe CLI is missing. Installing the skill alone does not expose RoadmapSmith actions in VS Code.\');',
648
974
  ' console.log(\'Install the CLI, then run "RoadmapSmith: Refresh Setup".\');',
@@ -651,7 +977,7 @@ function renderVsCodeLauncher() {
651
977
  ' console.log(\'\\nThe VS Code task runtime is missing. Install Node.js or set ROADMAPSMITH_NODE, then rerun "RoadmapSmith: Status".\');',
652
978
  ' }',
653
979
  ' console.log(\'\\nRecommended entrypoints: roadmapsmith zero, roadmapsmith maintain\');',
654
- ' console.log(\'Slash entrypoints: /road, /zero, /maintain, /status, /generate, /validate, /sync, /audit, /setup, /roadmap-sync <action>\');',
980
+ ' console.log(\'Slash entrypoints: /roadmap, /roadmap-zero, /roadmap-maintain, /roadmap-status, /roadmap-init, /roadmap-generate, /roadmap-validate, /roadmap-update, /roadmap-audit, /roadmap-setup, plus legacy /roadmap-sync <action>.\');',
655
981
  '}',
656
982
  '',
657
983
  'function printMissingCliStatus() {',
@@ -662,7 +988,7 @@ function renderVsCodeLauncher() {
662
988
  ' console.log(\'Installing the skill alone does not expose the CLI in VS Code.\');',
663
989
  ' console.log(\'Install the roadmapsmith package, then run "RoadmapSmith: Refresh Setup".\');',
664
990
  ' console.log(\'The launcher looks for, in order: workspace dev copy, workspace dependency, global command.\');',
665
- ' console.log(\'Slash discovery still works here: try /road for the local palette.\');',
991
+ ' console.log(\'Slash discovery still works here: try /roadmap for the local palette.\');',
666
992
  '}',
667
993
  '',
668
994
  'function runCli(args, options = {}) {',
@@ -729,8 +1055,14 @@ function renderVsCodeLauncher() {
729
1055
  ' if (slashInvocation.kind === \'palette\') {',
730
1056
  ' console.log(renderSlashPalette(slashInvocation));',
731
1057
  ' } else if (slashInvocation.actionId === \'status\') {',
1058
+ ' if (slashInvocation.deprecated && slashInvocation.deprecationMessage) {',
1059
+ ' console.error(slashInvocation.deprecationMessage);',
1060
+ ' }',
732
1061
  ' status();',
733
1062
  ' } else if (Object.prototype.hasOwnProperty.call(actionToCliArgs, slashInvocation.actionId)) {',
1063
+ ' if (slashInvocation.deprecated && slashInvocation.deprecationMessage) {',
1064
+ ' console.error(slashInvocation.deprecationMessage);',
1065
+ ' }',
734
1066
  ' const result = runCli(actionToCliArgs[slashInvocation.actionId]);',
735
1067
  ' forwardResult(result);',
736
1068
  ' } else {',
@@ -808,13 +1140,7 @@ function applySetupFiles(setupPlan, options = {}) {
808
1140
  }
809
1141
 
810
1142
  function findGlobalRoadmapsmith() {
811
- const probe = process.platform === 'win32'
812
- ? require('child_process').spawnSync('where', ['roadmapsmith'], { encoding: 'utf8' })
813
- : require('child_process').spawnSync('which', ['roadmapsmith'], { encoding: 'utf8' });
814
- if (probe.status !== 0 || !probe.stdout) {
815
- return null;
816
- }
817
- return probe.stdout.split(/\r?\n/).map((line) => line.trim()).find(Boolean) || null;
1143
+ return findCommandPath('roadmapsmith');
818
1144
  }
819
1145
 
820
1146
  function detectCliResolution(projectRoot) {
@@ -921,6 +1247,8 @@ function inspectHostSetup(projectRoot, options = {}) {
921
1247
  const cli = detectCliResolution(projectRoot);
922
1248
  const vscode = inspectVsCodeTasks(projectRoot);
923
1249
  const claude = inspectClaudeSetup(projectRoot);
1250
+ const bundle = inspectSharedBundleSurface();
1251
+ const codexNative = inspectCodexPluginState(projectRoot, options);
924
1252
  const codexReady = cli.ready && vscode.tasks.ready && runtime.ready;
925
1253
  let codexMessage = 'VS Code tasks are ready for Codex/manual host workflows';
926
1254
  if (!cli.ready) {
@@ -931,10 +1259,20 @@ function inspectHostSetup(projectRoot, options = {}) {
931
1259
  codexMessage = 'Install Node.js or set ROADMAPSMITH_NODE so VS Code tasks can launch the wrapper';
932
1260
  }
933
1261
 
1262
+ const claudeBundleMessage = bundle.ready
1263
+ ? 'The full Claude slash bundle is present in this repo/package. Install or update it in Claude Code, then reload skills to verify host registration.'
1264
+ : 'The shared RoadmapSmith bundle is incomplete, so Claude cannot expose the full native slash set.';
1265
+ const codexCliMessage = codexNative.commandPath
1266
+ ? (codexNative.ready
1267
+ ? 'Codex can resolve the installed RoadmapSmith plugin. Bare slash execution should still be manually verified in the interactive host.'
1268
+ : codexNative.message)
1269
+ : 'Codex CLI is not installed on this machine, so native Codex CLI slash verification is unavailable.';
1270
+
934
1271
  return {
935
1272
  projectRoot,
936
1273
  cli,
937
1274
  runtime,
1275
+ bundle,
938
1276
  roadmap: {
939
1277
  path: roadmapFile,
940
1278
  exists: roadmapFile ? fs.existsSync(roadmapFile) : false
@@ -945,6 +1283,52 @@ function inspectHostSetup(projectRoot, options = {}) {
945
1283
  },
946
1284
  vscode,
947
1285
  claude,
1286
+ surfaces: {
1287
+ claudeGui: createSurfaceStatus({
1288
+ name: 'claudeGui',
1289
+ source: `${bundle.kind}-bundle`,
1290
+ message: claudeBundleMessage,
1291
+ expectedCommands: bundle.expectedCommands,
1292
+ availableCommands: bundle.availableCommands,
1293
+ missingCommands: bundle.missingCommands,
1294
+ duplicates: [],
1295
+ ready: bundle.ready,
1296
+ verification: 'bundle-declared'
1297
+ }),
1298
+ claudeCli: createSurfaceStatus({
1299
+ name: 'claudeCli',
1300
+ source: `${bundle.kind}-bundle`,
1301
+ message: 'Claude CLI uses the same shared bundle contract as Claude GUI. Host registration still needs manual verification in the real Claude environment.',
1302
+ expectedCommands: bundle.expectedCommands,
1303
+ availableCommands: bundle.availableCommands,
1304
+ missingCommands: bundle.missingCommands,
1305
+ duplicates: [],
1306
+ ready: bundle.ready,
1307
+ verification: 'bundle-declared'
1308
+ }),
1309
+ codexGui: createSurfaceStatus({
1310
+ name: 'codexGui',
1311
+ source: codexNative.source,
1312
+ message: codexNative.message,
1313
+ expectedCommands: codexNative.expectedCommands,
1314
+ availableCommands: codexNative.availableCommands,
1315
+ missingCommands: codexNative.missingCommands,
1316
+ duplicates: codexNative.duplicates,
1317
+ ready: codexNative.ready,
1318
+ verification: 'host-install-detected'
1319
+ }),
1320
+ codexCli: createSurfaceStatus({
1321
+ name: 'codexCli',
1322
+ source: codexNative.source,
1323
+ message: codexCliMessage,
1324
+ expectedCommands: codexNative.expectedCommands,
1325
+ availableCommands: codexNative.availableCommands,
1326
+ missingCommands: codexNative.missingCommands,
1327
+ duplicates: codexNative.duplicates,
1328
+ ready: codexNative.ready && Boolean(codexNative.commandPath),
1329
+ verification: 'host-install-detected'
1330
+ })
1331
+ },
948
1332
  hosts: {
949
1333
  codex: {
950
1334
  ready: codexReady,
@@ -962,13 +1346,17 @@ function inspectHostSetup(projectRoot, options = {}) {
962
1346
 
963
1347
  module.exports = {
964
1348
  CLAUDE_HOOK_COMMAND,
1349
+ EXPECTED_NATIVE_SKILL_NAMES,
1350
+ EXPECTED_NATIVE_SLASH_COMMANDS,
965
1351
  ROADMAPSMITH_TASK_LABELS,
966
1352
  applySetupFiles,
967
1353
  assertSupportedEditor,
968
1354
  buildSetupFiles,
969
1355
  detectNodeRuntime,
970
1356
  detectCliResolution,
1357
+ inspectCodexPluginState,
971
1358
  inspectHostSetup,
1359
+ inspectSharedBundleSurface,
972
1360
  mergeClaudeSettings,
973
1361
  mergeVsCodeTasks,
974
1362
  parseHosts,