roadmapsmith 0.9.4 → 0.9.6
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/README.md +23 -0
- package/bin/cli.js +12 -3
- package/package.json +1 -1
- package/src/parser/index.js +4 -0
- package/src/validator/index.js +41 -3
package/README.md
CHANGED
|
@@ -22,6 +22,29 @@ npx skills add PapiScholz/roadmapsmith --skill roadmap-sync
|
|
|
22
22
|
|
|
23
23
|
This adds the `roadmap-sync` agent skill. It does not install the CLI package.
|
|
24
24
|
|
|
25
|
+
## Updating
|
|
26
|
+
|
|
27
|
+
Update the CLI based on how it was installed:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
# Global npm install
|
|
31
|
+
npm install -g roadmapsmith@latest
|
|
32
|
+
|
|
33
|
+
# Project dependency
|
|
34
|
+
npm install roadmapsmith@latest
|
|
35
|
+
|
|
36
|
+
# One-off execution without installing
|
|
37
|
+
npx roadmapsmith@latest sync --audit
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
The `roadmap-sync` agent skill is separate from the CLI. Re-running the skills install updates the agent instructions, but it does not update the `roadmapsmith` npm binary:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
npx skills add PapiScholz/roadmapsmith --skill roadmap-sync
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Fixes are available through `@latest` only after a new npm package version has been published. Before publication, install from a local checkout or a packed tarball for testing.
|
|
47
|
+
|
|
25
48
|
## Operating Modes
|
|
26
49
|
|
|
27
50
|
### Zero Mode
|
package/bin/cli.js
CHANGED
|
@@ -44,6 +44,14 @@ function maybeFilterTasks(tasks, filterValue) {
|
|
|
44
44
|
});
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
+
function tasksInManagedBlock(parsedRoadmap) {
|
|
48
|
+
if (!parsedRoadmap.managedRange) {
|
|
49
|
+
return parsedRoadmap.tasks;
|
|
50
|
+
}
|
|
51
|
+
const { start, end } = parsedRoadmap.managedRange;
|
|
52
|
+
return parsedRoadmap.tasks.filter((task) => task.lineIndex > start && task.lineIndex < end);
|
|
53
|
+
}
|
|
54
|
+
|
|
47
55
|
function printAudit(audit) {
|
|
48
56
|
console.log(`Audit summary: ${audit.checkedWithoutEvidence.length} checked-without-evidence, ${audit.readyButUnchecked.length} ready-but-unchecked.`);
|
|
49
57
|
if (audit.checkedWithoutEvidence.length > 0) {
|
|
@@ -157,10 +165,11 @@ async function run() {
|
|
|
157
165
|
}
|
|
158
166
|
|
|
159
167
|
const parsedRoadmap = parseRoadmap(content);
|
|
168
|
+
const syncTasks = tasksInManagedBlock(parsedRoadmap);
|
|
160
169
|
const validationContext = buildValidationContext(projectRoot, config, loadPlugins(projectRoot, config.plugins));
|
|
161
|
-
const results = validateTasks(
|
|
170
|
+
const results = validateTasks(syncTasks, validationContext, config, validationContext.plugins);
|
|
162
171
|
applyMinimumConfidence(results, config.validation?.minimumConfidence);
|
|
163
|
-
const next = applySync(content,
|
|
172
|
+
const next = applySync(content, syncTasks, results);
|
|
164
173
|
const dryRun = isEnabled(flags['dry-run']);
|
|
165
174
|
const writeResult = writeText(roadmapFile, next, { dryRun });
|
|
166
175
|
|
|
@@ -175,7 +184,7 @@ async function run() {
|
|
|
175
184
|
}
|
|
176
185
|
|
|
177
186
|
if (isEnabled(flags.audit)) {
|
|
178
|
-
const audit = auditValidation(
|
|
187
|
+
const audit = auditValidation(syncTasks, results);
|
|
179
188
|
printAudit(audit);
|
|
180
189
|
}
|
|
181
190
|
return;
|
package/package.json
CHANGED
package/src/parser/index.js
CHANGED
|
@@ -11,6 +11,7 @@ const MANAGED_END = '<!-- rs:managed:end -->';
|
|
|
11
11
|
|
|
12
12
|
function parseRoadmap(content) {
|
|
13
13
|
const lines = String(content || '').split(/\r?\n/);
|
|
14
|
+
const managedRange = findManagedRange(lines);
|
|
14
15
|
const tasks = [];
|
|
15
16
|
let section = '';
|
|
16
17
|
|
|
@@ -61,6 +62,8 @@ function parseRoadmap(content) {
|
|
|
61
62
|
|
|
62
63
|
return {
|
|
63
64
|
lines,
|
|
65
|
+
managedRange,
|
|
66
|
+
hasManagedBlock: Boolean(managedRange),
|
|
64
67
|
tasks
|
|
65
68
|
};
|
|
66
69
|
}
|
|
@@ -105,6 +108,7 @@ function upsertManagedBlock(existingContent, managedBody) {
|
|
|
105
108
|
}
|
|
106
109
|
|
|
107
110
|
module.exports = {
|
|
111
|
+
findManagedRange,
|
|
108
112
|
parseRoadmap,
|
|
109
113
|
upsertManagedBlock
|
|
110
114
|
};
|
package/src/validator/index.js
CHANGED
|
@@ -333,6 +333,38 @@ function findFilesByTaskPathTokens(taskText, fileIndex, pathDerivedTokens = new
|
|
|
333
333
|
return Array.from(matches).sort((left, right) => left.localeCompare(right));
|
|
334
334
|
}
|
|
335
335
|
|
|
336
|
+
function extractTaskEvidenceTokens(taskText, pathDerivedTokens = new Set()) {
|
|
337
|
+
return tokenize(taskText)
|
|
338
|
+
.filter((token) => token.length >= 3 && !GENERIC_TASK_TOKENS.has(token) && !token.endsWith('/') && !pathDerivedTokens.has(token))
|
|
339
|
+
.slice(0, 8);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
function findWeakPathContentSpecificTokens(taskText, fileIndex, weakPathFiles, pathDerivedTokens = new Set()) {
|
|
343
|
+
const tokens = extractTaskEvidenceTokens(taskText, pathDerivedTokens);
|
|
344
|
+
if (tokens.length === 0 || weakPathFiles.length === 0) return [];
|
|
345
|
+
|
|
346
|
+
const weakFiles = new Set(weakPathFiles);
|
|
347
|
+
const matches = new Set();
|
|
348
|
+
for (const file of fileIndex) {
|
|
349
|
+
if (!weakFiles.has(file.relativePath) || !CODE_EXTENSIONS.has(file.ext) || file.isTestFile) {
|
|
350
|
+
continue;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
const normalizedPath = normalizePathForMatch(file.relativePath);
|
|
354
|
+
const lowered = file.content.toLowerCase();
|
|
355
|
+
for (const token of tokens) {
|
|
356
|
+
if (normalizedPath.includes(token)) {
|
|
357
|
+
continue;
|
|
358
|
+
}
|
|
359
|
+
if (lowered.includes(token)) {
|
|
360
|
+
matches.add(token);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
return Array.from(matches).sort((left, right) => left.localeCompare(right));
|
|
366
|
+
}
|
|
367
|
+
|
|
336
368
|
function mergeRuleEvidence(baseEvidence, ruleEvidence) {
|
|
337
369
|
if (!ruleEvidence || typeof ruleEvidence !== 'object') return baseEvidence;
|
|
338
370
|
const merged = { ...baseEvidence };
|
|
@@ -376,9 +408,7 @@ function extractPathDerivedTokens(pathHints) {
|
|
|
376
408
|
}
|
|
377
409
|
|
|
378
410
|
function findCodeEvidence(taskText, fileIndex, pathDerivedTokens = new Set()) {
|
|
379
|
-
const tokens =
|
|
380
|
-
.filter((token) => token.length >= 3 && !GENERIC_TASK_TOKENS.has(token) && !token.endsWith('/') && !pathDerivedTokens.has(token))
|
|
381
|
-
.slice(0, 8);
|
|
411
|
+
const tokens = extractTaskEvidenceTokens(taskText, pathDerivedTokens);
|
|
382
412
|
if (tokens.length === 0) {
|
|
383
413
|
return [];
|
|
384
414
|
}
|
|
@@ -750,6 +780,7 @@ function validateTask(task, context, config, plugins) {
|
|
|
750
780
|
const pathDerivedTokens = extractPathDerivedTokens([...pathHints, ...standaloneFilenames]);
|
|
751
781
|
const filesFromCode = findCodeEvidence(task.text, context.fileIndex, pathDerivedTokens);
|
|
752
782
|
const filesFromWeakPathTokens = findFilesByTaskPathTokens(task.text, context.fileIndex, pathDerivedTokens);
|
|
783
|
+
const weakPathContentTokens = findWeakPathContentSpecificTokens(task.text, context.fileIndex, filesFromWeakPathTokens, pathDerivedTokens);
|
|
753
784
|
const filesFromTests = findTestEvidence(task.text, context.fileIndex, [...pathHints, ...standaloneFilenames]);
|
|
754
785
|
const { files: filesFromArtifacts, heuristicArtifacts } = findArtifactEvidence(task.text, context.fileIndex);
|
|
755
786
|
|
|
@@ -763,6 +794,7 @@ function validateTask(task, context, config, plugins) {
|
|
|
763
794
|
symbols: filesFromSymbols,
|
|
764
795
|
codeFiles: filesFromCode,
|
|
765
796
|
weakPathFiles: filesFromWeakPathTokens,
|
|
797
|
+
weakPathContentTokens,
|
|
766
798
|
testFiles: filesFromTests,
|
|
767
799
|
artifactFiles: filesFromArtifacts,
|
|
768
800
|
heuristicArtifacts,
|
|
@@ -790,6 +822,12 @@ function validateTask(task, context, config, plugins) {
|
|
|
790
822
|
reasons.push('no code, test, or artifact evidence found');
|
|
791
823
|
} else if (!hasEvidence && !hasWeakEvidence && structuralCheck.applicable && structuralCheck.passed) {
|
|
792
824
|
reasons.push('no code, test, or artifact evidence found');
|
|
825
|
+
} else if (!hasEvidence && hasWeakEvidence) {
|
|
826
|
+
if (weakPathContentTokens.length === 0) {
|
|
827
|
+
reasons.push('weak path-only evidence lacks content-specific token match');
|
|
828
|
+
} else {
|
|
829
|
+
reasons.push('weak path-token evidence lacks strong code, test, or artifact evidence');
|
|
830
|
+
}
|
|
793
831
|
}
|
|
794
832
|
|
|
795
833
|
const requiresTest = !task.noTest && context.testFrameworks.length > 0 && isCodeTask(task.text) && !isDocTask(task.text);
|