roadmapsmith 0.9.28 → 0.9.30
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 +2 -2
- package/.codex-plugin/plugin.json +3 -3
- package/README.md +100 -314
- package/bin/cli.js +49 -15
- package/package.json +2 -2
- package/skills/roadmap-status/SKILL.md +7 -6
- package/skills/roadmap-sync/SKILL.md +1 -1
- package/skills/roadmap-update/SKILL.md +4 -4
- package/skills.json +13 -13
- package/src/config.js +7 -2
- package/src/host.js +96 -27
- package/src/io.js +2 -0
- package/src/parser/index.js +50 -0
- package/src/slash.js +126 -44
- package/src/sync/index.js +48 -4
- package/src/validator/index.js +614 -26
package/src/slash.js
CHANGED
|
@@ -3,65 +3,78 @@
|
|
|
3
3
|
const SLASH_ACTIONS = [
|
|
4
4
|
{
|
|
5
5
|
id: 'zero',
|
|
6
|
+
tier: 'canonical',
|
|
6
7
|
description: 'Interview the developer in terminal and generate the first roadmap for an empty or low-context repo.',
|
|
7
8
|
classicCliExample: 'roadmapsmith zero',
|
|
8
9
|
taskLabel: 'RoadmapSmith: Zero Mode'
|
|
9
10
|
},
|
|
10
11
|
{
|
|
11
12
|
id: 'maintain',
|
|
13
|
+
tier: 'canonical',
|
|
12
14
|
description: 'Preserve-first existing-repo flow: update, sync, and audit the roadmap without rebuilding substantive domain content.',
|
|
13
15
|
classicCliExample: 'roadmapsmith maintain',
|
|
14
16
|
taskLabel: 'RoadmapSmith: Maintain'
|
|
15
17
|
},
|
|
16
18
|
{
|
|
17
19
|
id: 'status',
|
|
20
|
+
tier: 'canonical',
|
|
18
21
|
description: 'Inspect CLI, roadmap, VS Code task, Codex, and Claude readiness.',
|
|
19
22
|
classicCliExample: 'roadmapsmith status --json',
|
|
20
23
|
taskLabel: 'RoadmapSmith: Status'
|
|
21
24
|
},
|
|
25
|
+
{
|
|
26
|
+
id: 'validate',
|
|
27
|
+
tier: 'canonical',
|
|
28
|
+
description: 'Inspect per-task evidence status as JSON.',
|
|
29
|
+
classicCliExample: 'roadmapsmith validate --json --project-root .',
|
|
30
|
+
taskLabel: 'RoadmapSmith: Validate'
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
id: 'update',
|
|
34
|
+
tier: 'canonical',
|
|
35
|
+
aliases: ['sync'],
|
|
36
|
+
description: 'Apply evidence-backed checklist refresh to ROADMAP.md or complete one task with verified evidence.',
|
|
37
|
+
classicCliExample: 'roadmapsmith update --project-root .',
|
|
38
|
+
taskLabel: 'RoadmapSmith: Update'
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
id: 'setup',
|
|
42
|
+
tier: 'canonical',
|
|
43
|
+
description: 'Generate visible VS Code tasks and optional Claude hook wiring.',
|
|
44
|
+
classicCliExample: 'roadmapsmith setup',
|
|
45
|
+
taskLabel: 'RoadmapSmith: Refresh Setup'
|
|
46
|
+
},
|
|
22
47
|
{
|
|
23
48
|
id: 'init',
|
|
49
|
+
tier: 'advanced',
|
|
24
50
|
description: 'Create ROADMAP.md and AGENTS.md when they are missing.',
|
|
25
51
|
classicCliExample: 'roadmapsmith init',
|
|
26
52
|
taskLabel: 'RoadmapSmith: Init'
|
|
27
53
|
},
|
|
28
54
|
{
|
|
29
55
|
id: 'generate',
|
|
56
|
+
tier: 'advanced',
|
|
30
57
|
description: 'Generate or update ROADMAP.md, refusing destructive replacement unless rerun with --full-regen.',
|
|
31
58
|
classicCliExample: 'roadmapsmith generate --project-root .',
|
|
32
59
|
taskLabel: 'RoadmapSmith: Generate'
|
|
33
60
|
},
|
|
34
|
-
{
|
|
35
|
-
id: 'validate',
|
|
36
|
-
description: 'Inspect per-task evidence status as JSON.',
|
|
37
|
-
classicCliExample: 'roadmapsmith validate --json --project-root .',
|
|
38
|
-
taskLabel: 'RoadmapSmith: Validate'
|
|
39
|
-
},
|
|
40
|
-
{
|
|
41
|
-
id: 'sync',
|
|
42
|
-
description: 'Apply evidence-backed checklist sync to ROADMAP.md.',
|
|
43
|
-
classicCliExample: 'roadmapsmith sync --project-root .',
|
|
44
|
-
taskLabel: 'RoadmapSmith: Sync'
|
|
45
|
-
},
|
|
46
61
|
{
|
|
47
62
|
id: 'audit',
|
|
63
|
+
tier: 'advanced',
|
|
48
64
|
description: 'Run sync and print the post-sync mismatch summary.',
|
|
49
65
|
classicCliExample: 'roadmapsmith sync --audit --project-root .',
|
|
50
66
|
taskLabel: 'RoadmapSmith: Sync Audit'
|
|
51
|
-
},
|
|
52
|
-
{
|
|
53
|
-
id: 'setup',
|
|
54
|
-
description: 'Generate visible VS Code tasks and optional Claude hook wiring.',
|
|
55
|
-
classicCliExample: 'roadmapsmith setup',
|
|
56
|
-
taskLabel: 'RoadmapSmith: Refresh Setup'
|
|
57
67
|
}
|
|
58
68
|
];
|
|
59
69
|
|
|
60
70
|
const SLASH_ROOT_ALIASES = new Set(['/roadmap', '/road']);
|
|
61
71
|
const LEGACY_ROUTER_ALIAS = '/roadmap-sync';
|
|
72
|
+
const CANONICAL_ACTION_IDS = Object.freeze(SLASH_ACTIONS.filter((action) => action.tier === 'canonical').map((action) => action.id));
|
|
73
|
+
const ADVANCED_ACTION_IDS = Object.freeze(SLASH_ACTIONS.filter((action) => action.tier === 'advanced').map((action) => action.id));
|
|
74
|
+
const COMPATIBILITY_ACTION_IDS = Object.freeze([]);
|
|
62
75
|
|
|
63
76
|
function getNamespacedDirectSlash(actionId) {
|
|
64
|
-
return
|
|
77
|
+
return `/roadmap-${actionId}`;
|
|
65
78
|
}
|
|
66
79
|
|
|
67
80
|
const DIRECT_HOST_NATIVE_ALIAS_TO_ACTION = Object.freeze(
|
|
@@ -69,25 +82,64 @@ const DIRECT_HOST_NATIVE_ALIAS_TO_ACTION = Object.freeze(
|
|
|
69
82
|
);
|
|
70
83
|
|
|
71
84
|
const DIRECT_DEPRECATED_CLI_ALIAS_TO_ACTION = Object.freeze(
|
|
72
|
-
Object.fromEntries(
|
|
85
|
+
Object.fromEntries(
|
|
86
|
+
SLASH_ACTIONS.flatMap((action) => {
|
|
87
|
+
const aliases = Array.isArray(action.aliases) ? action.aliases : [];
|
|
88
|
+
return [action.id, ...aliases].map((alias) => [`/${alias}`, action.id]);
|
|
89
|
+
})
|
|
90
|
+
)
|
|
73
91
|
);
|
|
74
92
|
|
|
75
|
-
|
|
93
|
+
const ACTION_ALIAS_TO_ID = Object.freeze(
|
|
94
|
+
Object.fromEntries(
|
|
95
|
+
SLASH_ACTIONS.flatMap((action) => {
|
|
96
|
+
const aliases = Array.isArray(action.aliases) ? action.aliases : [];
|
|
97
|
+
return [action.id, ...aliases].map((alias) => [alias, action.id]);
|
|
98
|
+
})
|
|
99
|
+
)
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
function mapActionIdsToSkillNames(actionIds) {
|
|
76
103
|
return [
|
|
77
104
|
'roadmap',
|
|
78
|
-
|
|
79
|
-
'roadmap-maintain',
|
|
80
|
-
'roadmap-status',
|
|
81
|
-
'roadmap-init',
|
|
82
|
-
'roadmap-generate',
|
|
83
|
-
'roadmap-validate',
|
|
84
|
-
'roadmap-update',
|
|
85
|
-
'roadmap-sync',
|
|
86
|
-
'roadmap-audit',
|
|
87
|
-
'roadmap-setup'
|
|
105
|
+
...actionIds.map((actionId) => getNamespacedDirectSlash(actionId).slice(1))
|
|
88
106
|
];
|
|
89
107
|
}
|
|
90
108
|
|
|
109
|
+
function getCanonicalHostNativeSkillNames() {
|
|
110
|
+
return mapActionIdsToSkillNames(CANONICAL_ACTION_IDS);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function getAdvancedHostNativeSkillNames() {
|
|
114
|
+
return ADVANCED_ACTION_IDS.map((actionId) => getNamespacedDirectSlash(actionId).slice(1));
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function getCompatibilityHostNativeSkillNames() {
|
|
118
|
+
return [
|
|
119
|
+
'roadmap-sync'
|
|
120
|
+
];
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function getHostNativeSkillNames() {
|
|
124
|
+
return [
|
|
125
|
+
...getCanonicalHostNativeSkillNames(),
|
|
126
|
+
...getAdvancedHostNativeSkillNames(),
|
|
127
|
+
...getCompatibilityHostNativeSkillNames()
|
|
128
|
+
];
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function getCanonicalHostNativeSlashCommands() {
|
|
132
|
+
return getCanonicalHostNativeSkillNames().map((name) => `/${name}`);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function getAdvancedHostNativeSlashCommands() {
|
|
136
|
+
return getAdvancedHostNativeSkillNames().map((name) => `/${name}`);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function getCompatibilityHostNativeSlashCommands() {
|
|
140
|
+
return getCompatibilityHostNativeSkillNames().map((name) => `/${name}`);
|
|
141
|
+
}
|
|
142
|
+
|
|
91
143
|
function getHostNativeSlashCommands() {
|
|
92
144
|
return getHostNativeSkillNames().map((name) => `/${name}`);
|
|
93
145
|
}
|
|
@@ -97,23 +149,29 @@ function normalizeActionId(value) {
|
|
|
97
149
|
if (normalized.startsWith('roadmap-')) {
|
|
98
150
|
normalized = normalized.slice('roadmap-'.length);
|
|
99
151
|
}
|
|
100
|
-
if (normalized === 'update') {
|
|
101
|
-
normalized = 'sync';
|
|
102
|
-
}
|
|
103
152
|
return normalized;
|
|
104
153
|
}
|
|
105
154
|
|
|
155
|
+
function canonicalizeActionId(value) {
|
|
156
|
+
const normalized = normalizeActionId(value);
|
|
157
|
+
return ACTION_ALIAS_TO_ID[normalized] || normalized;
|
|
158
|
+
}
|
|
159
|
+
|
|
106
160
|
function isSlashToken(value) {
|
|
107
161
|
return typeof value === 'string' && value.trim().startsWith('/');
|
|
108
162
|
}
|
|
109
163
|
|
|
110
164
|
function getSlashAction(actionId) {
|
|
111
|
-
const normalized =
|
|
165
|
+
const normalized = canonicalizeActionId(actionId);
|
|
112
166
|
return SLASH_ACTIONS.find((action) => action.id === normalized) || null;
|
|
113
167
|
}
|
|
114
168
|
|
|
115
169
|
function getLegacyRouterSlash(action) {
|
|
116
|
-
return `/roadmap-sync ${action.id
|
|
170
|
+
return `/roadmap-sync ${action.id}`;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function actionSearchTerms(action) {
|
|
174
|
+
return [action.id, ...(Array.isArray(action.aliases) ? action.aliases : [])];
|
|
117
175
|
}
|
|
118
176
|
|
|
119
177
|
function getSlashActionSpecs() {
|
|
@@ -127,13 +185,22 @@ function getSlashActionSpecs() {
|
|
|
127
185
|
|
|
128
186
|
function getSlashSuggestions(query) {
|
|
129
187
|
const normalized = normalizeActionId(query);
|
|
188
|
+
const visibleActions = SLASH_ACTIONS.filter((action) => action.tier !== 'compatibility');
|
|
130
189
|
if (!normalized) {
|
|
131
|
-
return
|
|
190
|
+
return visibleActions.map((action) => ({
|
|
191
|
+
...action,
|
|
192
|
+
directSlash: getNamespacedDirectSlash(action.id),
|
|
193
|
+
routerSlash: `/roadmap ${action.id}`,
|
|
194
|
+
legacyRouterSlash: getLegacyRouterSlash(action)
|
|
195
|
+
}));
|
|
132
196
|
}
|
|
133
197
|
|
|
134
|
-
const startsWithMatches =
|
|
135
|
-
|
|
136
|
-
|
|
198
|
+
const startsWithMatches = visibleActions.filter((action) => {
|
|
199
|
+
return actionSearchTerms(action).some((term) => term.startsWith(normalized));
|
|
200
|
+
});
|
|
201
|
+
const containsMatches = visibleActions.filter((action) => {
|
|
202
|
+
return !actionSearchTerms(action).some((term) => term.startsWith(normalized))
|
|
203
|
+
&& actionSearchTerms(action).some((term) => term.includes(normalized));
|
|
137
204
|
});
|
|
138
205
|
|
|
139
206
|
return [...startsWithMatches, ...containsMatches].map((action) => ({
|
|
@@ -175,12 +242,12 @@ function resolveSlashInvocation(command, args = []) {
|
|
|
175
242
|
const normalizedCommand = String(command).trim().toLowerCase();
|
|
176
243
|
|
|
177
244
|
if (normalizedCommand === LEGACY_ROUTER_ALIAS) {
|
|
245
|
+
const deprecationMessage = 'Legacy CLI compatibility root /roadmap-sync <action> is deprecated. Use /roadmap <action> or the direct /roadmap-* commands.';
|
|
178
246
|
if (args.length === 0) {
|
|
179
|
-
return paletteResponse(normalizedCommand, '');
|
|
247
|
+
return paletteResponse(normalizedCommand, '', true, deprecationMessage);
|
|
180
248
|
}
|
|
181
249
|
|
|
182
250
|
const queryToken = normalizeActionId(args[0]);
|
|
183
|
-
const deprecationMessage = 'Legacy CLI compatibility root /roadmap-sync <action> is deprecated. Use /roadmap <action> or the direct /roadmap-* commands.';
|
|
184
251
|
const exactAction = getSlashAction(queryToken);
|
|
185
252
|
if (exactAction) {
|
|
186
253
|
return executeResponse(normalizedCommand, exactAction.id, queryToken, true, deprecationMessage);
|
|
@@ -268,7 +335,9 @@ function renderSlashPalette(options = {}) {
|
|
|
268
335
|
suggestions.forEach((action) => {
|
|
269
336
|
lines.push(`- ${action.directSlash}: ${action.description}`);
|
|
270
337
|
lines.push(` Router form: ${action.routerSlash}`);
|
|
271
|
-
|
|
338
|
+
if (options.deprecated || source === LEGACY_ROUTER_ALIAS) {
|
|
339
|
+
lines.push(` Legacy router: ${action.legacyRouterSlash}`);
|
|
340
|
+
}
|
|
272
341
|
lines.push(` Classic CLI: ${action.classicCliExample}`);
|
|
273
342
|
lines.push(` VS Code task: ${action.taskLabel}`);
|
|
274
343
|
});
|
|
@@ -280,7 +349,9 @@ function renderSlashPalette(options = {}) {
|
|
|
280
349
|
lines.push('- roadmapsmith /roadmap maintain');
|
|
281
350
|
lines.push('- roadmapsmith /roadmap-maintain');
|
|
282
351
|
lines.push('- roadmapsmith /roadmap-update');
|
|
283
|
-
|
|
352
|
+
if (options.deprecated || source === LEGACY_ROUTER_ALIAS) {
|
|
353
|
+
lines.push('- roadmapsmith /roadmap-sync validate');
|
|
354
|
+
}
|
|
284
355
|
lines.push('');
|
|
285
356
|
lines.push('Installing the skill alone does not expose CLI behavior in VS Code. Use roadmapsmith setup for the visible task/launcher layer.');
|
|
286
357
|
|
|
@@ -288,10 +359,20 @@ function renderSlashPalette(options = {}) {
|
|
|
288
359
|
}
|
|
289
360
|
|
|
290
361
|
module.exports = {
|
|
362
|
+
ACTION_ALIAS_TO_ID,
|
|
291
363
|
DIRECT_DEPRECATED_CLI_ALIAS_TO_ACTION,
|
|
292
364
|
DIRECT_HOST_NATIVE_ALIAS_TO_ACTION,
|
|
365
|
+
ADVANCED_ACTION_IDS,
|
|
366
|
+
CANONICAL_ACTION_IDS,
|
|
367
|
+
COMPATIBILITY_ACTION_IDS,
|
|
293
368
|
LEGACY_ROUTER_ALIAS,
|
|
294
369
|
SLASH_ROOT_ALIASES,
|
|
370
|
+
getAdvancedHostNativeSkillNames,
|
|
371
|
+
getAdvancedHostNativeSlashCommands,
|
|
372
|
+
getCanonicalHostNativeSkillNames,
|
|
373
|
+
getCanonicalHostNativeSlashCommands,
|
|
374
|
+
getCompatibilityHostNativeSkillNames,
|
|
375
|
+
getCompatibilityHostNativeSlashCommands,
|
|
295
376
|
getHostNativeSkillNames,
|
|
296
377
|
getHostNativeSlashCommands,
|
|
297
378
|
getNamespacedDirectSlash,
|
|
@@ -299,6 +380,7 @@ module.exports = {
|
|
|
299
380
|
getSlashActionSpecs,
|
|
300
381
|
getSlashSuggestions,
|
|
301
382
|
isSlashToken,
|
|
383
|
+
canonicalizeActionId,
|
|
302
384
|
normalizeActionId,
|
|
303
385
|
renderSlashPalette,
|
|
304
386
|
resolveSlashInvocation
|
package/src/sync/index.js
CHANGED
|
@@ -112,6 +112,23 @@ function shouldPreserveExistingWarning(existingReason, newReason) {
|
|
|
112
112
|
return cleanNew === 'validation failed' && cleanExisting && cleanExisting !== cleanNew;
|
|
113
113
|
}
|
|
114
114
|
|
|
115
|
+
function formatVerificationRecipe(indent, recipe) {
|
|
116
|
+
return `${indent} - Verification recipe: ${recipe}`;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function formatTestEvidence(indent, evidence) {
|
|
120
|
+
return `${indent} - Test evidence: ${evidence}`;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function findVerificationRecipeIndex(lines, taskLineIndex) {
|
|
124
|
+
for (let index = taskLineIndex + 1; index < lines.length; index += 1) {
|
|
125
|
+
const line = lines[index];
|
|
126
|
+
if (!line.trim() || /^\s*#{2,}\s/.test(line) || /^\s*- \[[ xX]\]\s/.test(line)) break;
|
|
127
|
+
if (/^\s*- Verification recipe:/i.test(line)) return index;
|
|
128
|
+
}
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
|
|
115
132
|
function applySync(content, parsedTasks, results) {
|
|
116
133
|
const parsed = parseRoadmap(content);
|
|
117
134
|
const lines = [...parsed.lines];
|
|
@@ -142,6 +159,19 @@ function applySync(content, parsedTasks, results) {
|
|
|
142
159
|
lines.splice(warningIndex, 1);
|
|
143
160
|
offset -= 1;
|
|
144
161
|
}
|
|
162
|
+
const recipeIndex = findVerificationRecipeIndex(lines, lineIndex);
|
|
163
|
+
if (recipeIndex != null && recipeIndex >= 0 && recipeIndex < lines.length) {
|
|
164
|
+
lines.splice(recipeIndex, 1);
|
|
165
|
+
offset -= 1;
|
|
166
|
+
}
|
|
167
|
+
if (
|
|
168
|
+
result.generatedTestEvidence &&
|
|
169
|
+
(!Array.isArray(task.testEvidenceLines) || task.testEvidenceLines.length === 0)
|
|
170
|
+
) {
|
|
171
|
+
const insertionIndex = Math.max(lineIndex + 1, (task.lastChildLineIndex + offset) + 1);
|
|
172
|
+
lines.splice(insertionIndex, 0, formatTestEvidence(task.indent || '', result.generatedTestEvidence));
|
|
173
|
+
offset += 1;
|
|
174
|
+
}
|
|
145
175
|
if (
|
|
146
176
|
result.staleEvidenceResolved &&
|
|
147
177
|
(!Array.isArray(task.evidenceLines) || task.evidenceLines.length === 0) &&
|
|
@@ -154,10 +184,7 @@ function applySync(content, parsedTasks, results) {
|
|
|
154
184
|
lines.splice(insertionIndex, 0, `${task.indent || ''} - Evidence: ${result.discoveredEvidence}`);
|
|
155
185
|
offset += 1;
|
|
156
186
|
}
|
|
157
|
-
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
if (warningIndex != null && warningIndex >= 0 && warningIndex < lines.length) {
|
|
187
|
+
} else if (warningIndex != null && warningIndex >= 0 && warningIndex < lines.length) {
|
|
161
188
|
const existingReason = normalizeWarningReason(lines[warningIndex]);
|
|
162
189
|
const newReason = reason || 'validation failed';
|
|
163
190
|
if (!shouldPreserveExistingWarning(existingReason, newReason)) {
|
|
@@ -167,6 +194,23 @@ function applySync(content, parsedTasks, results) {
|
|
|
167
194
|
lines.splice(lastChildLineIndex + 1, 0, warningText);
|
|
168
195
|
offset += 1;
|
|
169
196
|
}
|
|
197
|
+
|
|
198
|
+
const recipeIndex = findVerificationRecipeIndex(lines, lineIndex);
|
|
199
|
+
if (result.passed) {
|
|
200
|
+
continue;
|
|
201
|
+
}
|
|
202
|
+
if (result.verificationRecipe) {
|
|
203
|
+
const recipeLine = formatVerificationRecipe(task.indent || '', result.verificationRecipe);
|
|
204
|
+
if (recipeIndex != null && recipeIndex >= 0 && recipeIndex < lines.length) {
|
|
205
|
+
lines[recipeIndex] = recipeLine;
|
|
206
|
+
} else {
|
|
207
|
+
lines.splice(lastChildLineIndex + 1, 0, recipeLine);
|
|
208
|
+
offset += 1;
|
|
209
|
+
}
|
|
210
|
+
} else if (recipeIndex != null && recipeIndex >= 0 && recipeIndex < lines.length) {
|
|
211
|
+
lines.splice(recipeIndex, 1);
|
|
212
|
+
offset -= 1;
|
|
213
|
+
}
|
|
170
214
|
}
|
|
171
215
|
|
|
172
216
|
return ensureTrailingNewline(lines.join('\n'));
|