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/.claude-plugin/plugin.json +34 -0
- package/.codex-plugin/plugin.json +55 -0
- package/README.md +76 -20
- package/assets/palette.png +0 -0
- package/assets/roadmapsmith-logo.png +0 -0
- package/bin/cli.js +75 -17
- package/package.json +20 -4
- package/skills/roadmap/SKILL.md +33 -0
- package/skills/roadmap-audit/SKILL.md +16 -0
- package/skills/roadmap-generate/SKILL.md +18 -0
- package/skills/roadmap-init/SKILL.md +16 -0
- package/skills/roadmap-maintain/SKILL.md +18 -0
- package/skills/roadmap-setup/SKILL.md +17 -0
- package/skills/roadmap-status/SKILL.md +17 -0
- package/skills/roadmap-sync/SKILL.md +20 -0
- package/skills/roadmap-sync/agents/openai.yaml +7 -0
- package/skills/roadmap-update/SKILL.md +17 -0
- package/skills/roadmap-validate/SKILL.md +16 -0
- package/skills/roadmap-zero/SKILL.md +17 -0
- package/skills.json +95 -0
- package/src/classifier/index.js +35 -0
- package/src/generator/index.js +219 -19
- package/src/host.js +446 -58
- package/src/io.js +45 -4
- package/src/match.js +18 -2
- package/src/parser/index.js +13 -0
- package/src/slash.js +148 -69
- package/src/utils.js +1 -0
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
|
|
227
|
-
createTask('status', 'RoadmapSmith: Status', 'Inspect readiness and learn the slash entrypoints like /
|
|
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', '
|
|
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
|
-
|
|
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([\'/
|
|
488
|
-
'const
|
|
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
|
-
'
|
|
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 : \'/
|
|
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(`-
|
|
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
|
|
573
|
-
' lines.push(\'- roadmapsmith maintain\');',
|
|
574
|
-
' lines.push(\'- roadmapsmith /
|
|
575
|
-
' lines.push(\'- roadmapsmith /
|
|
576
|
-
' lines.push(\'- roadmapsmith /roadmap-sync
|
|
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 (
|
|
588
|
-
'
|
|
589
|
-
'
|
|
590
|
-
'
|
|
591
|
-
'
|
|
592
|
-
'
|
|
593
|
-
'
|
|
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
|
|
907
|
+
' return paletteResponse(normalizedCommand, \'\', deprecated, deprecationMessage);',
|
|
600
908
|
' }',
|
|
601
|
-
' const exactAction =
|
|
909
|
+
' const exactAction = getSlashAction(queryToken);',
|
|
602
910
|
' if (exactAction) {',
|
|
603
|
-
' return
|
|
911
|
+
' return executeResponse(normalizedCommand, exactAction.id, queryToken, deprecated, deprecationMessage);',
|
|
604
912
|
' }',
|
|
605
|
-
' return
|
|
913
|
+
' return paletteResponse(normalizedCommand, queryToken, deprecated, deprecationMessage);',
|
|
606
914
|
' }',
|
|
607
|
-
'
|
|
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
|
|
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 "/
|
|
618
|
-
' console.log(\'- For existing repos, run "RoadmapSmith: Maintain" or use "/
|
|
619
|
-
' console.log(\'- Use
|
|
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: /
|
|
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 /
|
|
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
|
-
|
|
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,
|