roadmapsmith 0.9.29 → 0.9.31
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 -326
- package/bin/cli.js +50 -17
- package/package.json +2 -2
- package/skills/roadmap-maintain/SKILL.md +1 -0
- package/skills/roadmap-status/SKILL.md +7 -6
- package/skills/roadmap-update/SKILL.md +4 -4
- package/skills.json +13 -13
- package/src/host.js +96 -27
- package/src/reasons.js +28 -0
- package/src/slash.js +126 -44
- package/src/sync/index.js +9 -4
- package/src/validator/index.js +268 -37
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
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const { parseRoadmap } = require('../parser');
|
|
4
4
|
const { ensureTrailingNewline } = require('../utils');
|
|
5
|
+
const { isLowSpecificityReason } = require('../reasons');
|
|
5
6
|
|
|
6
7
|
const ATTEMPTED_WARNING_REASON_PREFIX = 'attempted but validation failed:';
|
|
7
8
|
const NO_EVIDENCE_WARNING_REASON_PREFIX = 'no implementation evidence found yet:';
|
|
@@ -106,10 +107,14 @@ function normalizeWarningReasons(reasons) {
|
|
|
106
107
|
return normalized;
|
|
107
108
|
}
|
|
108
109
|
|
|
109
|
-
function shouldPreserveExistingWarning(existingReason, newReason) {
|
|
110
|
+
function shouldPreserveExistingWarning(existingReason, newReason, options) {
|
|
111
|
+
if (options && options.forceRefresh) return false;
|
|
110
112
|
const cleanExisting = normalizeWarningReason(existingReason);
|
|
111
113
|
const cleanNew = normalizeWarningReason(newReason) || 'validation failed';
|
|
112
|
-
|
|
114
|
+
// Preserve when the freshly computed reason is a low-specificity policy message (no
|
|
115
|
+
// file- or symbol-specific information) and the existing annotation carries more
|
|
116
|
+
// diagnostic value (is not itself a low-specificity message).
|
|
117
|
+
return isLowSpecificityReason(cleanNew) && Boolean(cleanExisting) && !isLowSpecificityReason(cleanExisting);
|
|
113
118
|
}
|
|
114
119
|
|
|
115
120
|
function formatVerificationRecipe(indent, recipe) {
|
|
@@ -129,7 +134,7 @@ function findVerificationRecipeIndex(lines, taskLineIndex) {
|
|
|
129
134
|
return null;
|
|
130
135
|
}
|
|
131
136
|
|
|
132
|
-
function applySync(content, parsedTasks, results) {
|
|
137
|
+
function applySync(content, parsedTasks, results, options) {
|
|
133
138
|
const parsed = parseRoadmap(content);
|
|
134
139
|
const lines = [...parsed.lines];
|
|
135
140
|
const tasks = parsedTasks || parsed.tasks;
|
|
@@ -187,7 +192,7 @@ function applySync(content, parsedTasks, results) {
|
|
|
187
192
|
} else if (warningIndex != null && warningIndex >= 0 && warningIndex < lines.length) {
|
|
188
193
|
const existingReason = normalizeWarningReason(lines[warningIndex]);
|
|
189
194
|
const newReason = reason || 'validation failed';
|
|
190
|
-
if (!shouldPreserveExistingWarning(existingReason, newReason)) {
|
|
195
|
+
if (!shouldPreserveExistingWarning(existingReason, newReason, options)) {
|
|
191
196
|
lines[warningIndex] = warningText;
|
|
192
197
|
}
|
|
193
198
|
} else {
|