roadmapsmith 0.9.34 → 0.9.36

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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "roadmapsmith",
3
- "version": "0.9.34",
3
+ "version": "0.9.36",
4
4
  "description": "Evidence-backed ROADMAP.md workflows for AI coding agents, with canonical RoadmapSmith status and maintain surfaces plus advanced sync/generate tools and legacy compatibility aliases.",
5
5
  "author": {
6
6
  "name": "PapiScholz"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "roadmapsmith",
3
- "version": "0.9.34",
3
+ "version": "0.9.36",
4
4
  "description": "Evidence-backed ROADMAP.md workflows for AI coding agents, with canonical RoadmapSmith status and maintain surfaces plus advanced sync/generate tools and legacy compatibility aliases.",
5
5
  "author": {
6
6
  "name": "PapiScholz"
package/bin/cli.js CHANGED
@@ -92,6 +92,15 @@ function printAudit(audit) {
92
92
  console.log(`- [${item.task.id}] ${item.task.text}`);
93
93
  });
94
94
  }
95
+ if (Array.isArray(audit.newlyUnchecked) && audit.newlyUnchecked.length > 0) {
96
+ console.log(`Unchecked by this run (${audit.newlyUnchecked.length}): ${audit.newlyUnchecked.join(', ')}`);
97
+ }
98
+ if (Array.isArray(audit.humanVerifiedTasks) && audit.humanVerifiedTasks.length > 0) {
99
+ console.log(`Human-verified tasks (${audit.humanVerifiedTasks.length}):`);
100
+ audit.humanVerifiedTasks.forEach((item) => {
101
+ console.log(`- [${item.task.id}] ${item.task.text}`);
102
+ });
103
+ }
95
104
  }
96
105
 
97
106
  function printReadinessSummary(summary) {
@@ -276,8 +285,18 @@ function runSyncCommand(projectRoot, config, flags, options = {}) {
276
285
  const validationContext = buildValidationContext(projectRoot, config, loadPlugins(projectRoot, config.plugins));
277
286
  const results = validateTasks(syncTasks, validationContext, config, validationContext.plugins);
278
287
  applyMinimumConfidence(results, config.validation?.minimumConfidence);
288
+ if (isEnabled(flags.audit)) {
289
+ const audit = auditValidation(syncTasks, results);
290
+ printAudit(audit);
291
+ const hasMismatch = audit.checkedWithoutEvidence.length > 0 || audit.readyButUnchecked.length > 0;
292
+ if (hasMismatch) {
293
+ process.exitCode = 2;
294
+ }
295
+ return;
296
+ }
297
+
279
298
  const forceRefresh = isEnabled(flags['refresh-annotations']);
280
- const next = applySync(content, syncTasks, results, { forceRefresh });
299
+ const { content: next, changes } = applySync(content, syncTasks, results, { forceRefresh });
281
300
  const dryRun = isEnabled(flags['dry-run']);
282
301
  emitPreWriteWarning(roadmapFile, {
283
302
  commandName: options.commandName || 'roadmapsmith sync',
@@ -294,11 +313,17 @@ function runSyncCommand(projectRoot, config, flags, options = {}) {
294
313
  console.log(`No changes for ${roadmapFile}`);
295
314
  }
296
315
  } else {
316
+ if (changes.newlyUnchecked.length > 0) {
317
+ console.log(`Unchecked ${changes.newlyUnchecked.length} task(s): ${changes.newlyUnchecked.join(', ')}`);
318
+ }
319
+ if (changes.newlyChecked.length > 0) {
320
+ console.log(`Checked ${changes.newlyChecked.length} task(s): ${changes.newlyChecked.join(', ')}`);
321
+ }
297
322
  console.log(writeResult.changed ? `Updated ${roadmapFile}` : `No changes for ${roadmapFile}`);
298
323
  }
299
324
 
300
- if (options.audit || isEnabled(flags.audit)) {
301
- const audit = auditValidation(syncTasks, results);
325
+ if (options.audit) {
326
+ const audit = auditValidation(syncTasks, results, changes);
302
327
  printAudit(audit);
303
328
  }
304
329
  }
@@ -354,13 +379,16 @@ function runUpdateCommand(projectRoot, config, flags) {
354
379
  const validationContext = buildValidationContext(projectRoot, config, loadPlugins(projectRoot, config.plugins));
355
380
  const result = validateTasks([draftTask], validationContext, config, validationContext.plugins)[taskId];
356
381
  const errors = (result.diagnostics || []).filter((item) => item.severity === 'error');
382
+ const isEscapeHatch = draftTask.verifiedBy === 'human' || draftTask.kind === 'docs';
357
383
  const suppliedEvidenceResolved = result.evidence.authoritative && result.evidence.authoritativeFiles.length > 0;
358
- if (!suppliedEvidenceResolved || !result.passed || result.confidence !== 'high' || errors.length > 0) {
384
+ const evidenceAccepted = isEscapeHatch ? result.passed : (suppliedEvidenceResolved && result.passed && result.confidence === 'high');
385
+ if (!evidenceAccepted || errors.length > 0) {
359
386
  const reasons = result.reasons.length > 0 ? `: ${result.reasons.join('; ')}` : '';
360
- throw new Error(`Task ${taskId} was not updated; supplied evidence must resolve in the repository and validate at high confidence${reasons}`);
387
+ const hint = isEscapeHatch ? '; escape-hatch task requires an Evidence: child line' : '; supplied evidence must resolve in the repository and validate at high confidence';
388
+ throw new Error(`Task ${taskId} was not updated${hint}${reasons}`);
361
389
  }
362
390
 
363
- const next = applySync(draft, [draftTask], { [taskId]: result });
391
+ const { content: next } = applySync(draft, [draftTask], { [taskId]: result });
364
392
  const dryRun = isEnabled(flags['dry-run']);
365
393
  emitPreWriteWarning(roadmapFile, {
366
394
  commandName: 'roadmapsmith update',
@@ -489,7 +517,7 @@ function runMaintainCommand(projectRoot, flags) {
489
517
  forceFullRegenerate: fullRegen,
490
518
  warningState
491
519
  });
492
- runSyncCommand(projectRoot, config, { ...flags, audit: true }, {
520
+ runSyncCommand(projectRoot, config, { ...flags, audit: undefined }, {
493
521
  audit: true,
494
522
  commandName: 'roadmapsmith maintain',
495
523
  warningState
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "roadmapsmith",
3
- "version": "0.9.34",
3
+ "version": "0.9.36",
4
4
  "description": "Evidence-backed ROADMAP.md workflows for AI coding agents, with canonical RoadmapSmith status and maintain surfaces plus advanced sync/generate tools and legacy compatibility aliases.",
5
5
  "main": "src/index.js",
6
6
  "bin": {
package/skills.json CHANGED
@@ -28,67 +28,67 @@
28
28
  "name": "roadmap",
29
29
  "path": "skills/roadmap",
30
30
  "description": "Native slash palette for RoadmapSmith commands and recommended entrypoints across supported hosts.",
31
- "version": "0.9.34"
31
+ "version": "0.9.36"
32
32
  },
33
33
  {
34
34
  "name": "roadmap-zero",
35
35
  "path": "skills/roadmap-zero",
36
36
  "description": "Native slash entrypoint for Zero Mode, including non-interactive config-plus-flag discovery.",
37
- "version": "0.9.34"
37
+ "version": "0.9.36"
38
38
  },
39
39
  {
40
40
  "name": "roadmap-maintain",
41
41
  "path": "skills/roadmap-maintain",
42
42
  "description": "Native slash entrypoint for conservative managed-block maintenance plus sync and audit output.",
43
- "version": "0.9.34"
43
+ "version": "0.9.36"
44
44
  },
45
45
  {
46
46
  "name": "roadmap-status",
47
47
  "path": "skills/roadmap-status",
48
48
  "description": "Native slash readiness check grounded in roadmapsmith status JSON.",
49
- "version": "0.9.34"
49
+ "version": "0.9.36"
50
50
  },
51
51
  {
52
52
  "name": "roadmap-init",
53
53
  "path": "skills/roadmap-init",
54
54
  "description": "Native slash entrypoint for creating ROADMAP.md and AGENTS.md.",
55
- "version": "0.9.34"
55
+ "version": "0.9.36"
56
56
  },
57
57
  {
58
58
  "name": "roadmap-generate",
59
59
  "path": "skills/roadmap-generate",
60
60
  "description": "Native slash entrypoint for managed roadmap updates that require --full-regen before destructive replacement.",
61
- "version": "0.9.34"
61
+ "version": "0.9.36"
62
62
  },
63
63
  {
64
64
  "name": "roadmap-validate",
65
65
  "path": "skills/roadmap-validate",
66
66
  "description": "Native slash entrypoint for evidence-backed roadmap validation.",
67
- "version": "0.9.34"
67
+ "version": "0.9.36"
68
68
  },
69
69
  {
70
70
  "name": "roadmap-update",
71
71
  "path": "skills/roadmap-update",
72
72
  "description": "Native slash entrypoint for evidence-backed inline annotation refresh and verified single-task completion.",
73
- "version": "0.9.34"
73
+ "version": "0.9.36"
74
74
  },
75
75
  {
76
76
  "name": "roadmap-sync",
77
77
  "path": "skills/roadmap-sync",
78
78
  "description": "DEPRECATED legacy compatibility root; use roadmap-maintain or roadmap-update.",
79
- "version": "0.9.34"
79
+ "version": "0.9.36"
80
80
  },
81
81
  {
82
82
  "name": "roadmap-audit",
83
83
  "path": "skills/roadmap-audit",
84
84
  "description": "Native slash entrypoint for the advanced sync-plus-audit mutating summary workflow.",
85
- "version": "0.9.34"
85
+ "version": "0.9.36"
86
86
  },
87
87
  {
88
88
  "name": "roadmap-setup",
89
89
  "path": "skills/roadmap-setup",
90
90
  "description": "Native slash entrypoint for generating RoadmapSmith host integration files.",
91
- "version": "0.9.34"
91
+ "version": "0.9.36"
92
92
  }
93
93
  ]
94
94
  }
package/src/config.js CHANGED
@@ -30,6 +30,9 @@ const DEFAULT_CONFIG = {
30
30
  constraints: [],
31
31
  doneCriteria: []
32
32
  },
33
+ scan: {
34
+ excludeDirs: []
35
+ },
33
36
  validation: {
34
37
  minimumConfidence: 'low',
35
38
  testReports: [],
@@ -95,6 +98,11 @@ function mergeConfig(userConfig) {
95
98
  ? userConfig.zeroMode.doneCriteria
96
99
  : DEFAULT_CONFIG.zeroMode.doneCriteria
97
100
  },
101
+ scan: {
102
+ excludeDirs: Array.isArray(userConfig && userConfig.scan && userConfig.scan.excludeDirs)
103
+ ? userConfig.scan.excludeDirs
104
+ : DEFAULT_CONFIG.scan.excludeDirs
105
+ },
98
106
  validation: {
99
107
  ...DEFAULT_CONFIG.validation,
100
108
  ...((userConfig && userConfig.validation) || {}),
package/src/io.js CHANGED
@@ -12,6 +12,16 @@ const DEFAULT_IGNORED_DIRS = new Set([
12
12
  '.nuxt',
13
13
  '.turbo',
14
14
  '.cache',
15
+ '.open-next',
16
+ '.vercel',
17
+ '.svelte-kit',
18
+ '.parcel-cache',
19
+ '.angular',
20
+ '.expo',
21
+ '.serverless',
22
+ '.wrangler',
23
+ '.tmp',
24
+ 'tmp',
15
25
  'dist',
16
26
  'dist-electron',
17
27
  'build',
@@ -68,7 +78,10 @@ function writeText(filePath, content, options = {}) {
68
78
  }
69
79
 
70
80
  function walkFiles(rootPath, options = {}) {
71
- const ignoredDirs = options.ignoredDirs || DEFAULT_IGNORED_DIRS;
81
+ let ignoredDirs = options.ignoredDirs || DEFAULT_IGNORED_DIRS;
82
+ if (Array.isArray(options.extraIgnoredDirs) && options.extraIgnoredDirs.length > 0) {
83
+ ignoredDirs = new Set([...ignoredDirs, ...options.extraIgnoredDirs]);
84
+ }
72
85
  const result = [];
73
86
 
74
87
  function walk(current) {
@@ -147,6 +147,10 @@ function parseRoadmap(content) {
147
147
 
148
148
  const { indent, checked, text, markerId, markerFlags } = taskLine;
149
149
  const noTest = /\brs:no-test\b/i.test(markerFlags);
150
+ const kindMatch = markerFlags.match(/\brs:kind=(\S+)/i);
151
+ const taskKind = kindMatch ? kindMatch[1].toLowerCase() : null;
152
+ const verifiedByMatch = markerFlags.match(/\brs:verified-by=(\S+)/i);
153
+ const taskVerifiedBy = verifiedByMatch ? verifiedByMatch[1].toLowerCase() : null;
150
154
  const taskIndentWidth = getIndentWidth(indent);
151
155
 
152
156
  let warningLineIndex = null;
@@ -243,6 +247,8 @@ function parseRoadmap(content) {
243
247
  blockedByIds,
244
248
  markerId,
245
249
  noTest,
250
+ kind: taskKind,
251
+ verifiedBy: taskVerifiedBy,
246
252
  indent,
247
253
  section
248
254
  });
package/src/sync/index.js CHANGED
@@ -139,6 +139,13 @@ function applySync(content, parsedTasks, results, options) {
139
139
  const lines = [...parsed.lines];
140
140
  const tasks = parsedTasks || parsed.tasks;
141
141
 
142
+ const changes = {
143
+ newlyUnchecked: [],
144
+ newlyChecked: [],
145
+ warningsAdded: [],
146
+ warningsRemoved: []
147
+ };
148
+
142
149
  let offset = 0;
143
150
  for (const task of tasks) {
144
151
  const result = results[task.id];
@@ -151,7 +158,13 @@ function applySync(content, parsedTasks, results, options) {
151
158
  continue;
152
159
  }
153
160
 
161
+ const wasChecked = task.checked;
154
162
  lines[lineIndex] = setChecklistState(lines[lineIndex], result.passed);
163
+ if (wasChecked && !result.passed) {
164
+ changes.newlyUnchecked.push(task.id);
165
+ } else if (!wasChecked && result.passed) {
166
+ changes.newlyChecked.push(task.id);
167
+ }
155
168
 
156
169
  const reason = normalizeWarningReasons(result.reasons).join('; ');
157
170
  const warningText = formatWarning(task.indent || '', reason || 'validation failed', result.attempted);
@@ -198,6 +211,7 @@ function applySync(content, parsedTasks, results, options) {
198
211
  } else {
199
212
  lines.splice(lastChildLineIndex + 1, 0, warningText);
200
213
  offset += 1;
214
+ changes.warningsAdded.push(task.id);
201
215
  }
202
216
 
203
217
  const recipeIndex = findVerificationRecipeIndex(lines, lineIndex);
@@ -218,7 +232,7 @@ function applySync(content, parsedTasks, results, options) {
218
232
  }
219
233
  }
220
234
 
221
- return ensureTrailingNewline(lines.join('\n'));
235
+ return { content: ensureTrailingNewline(lines.join('\n')), changes };
222
236
  }
223
237
 
224
238
  module.exports = {
@@ -13,7 +13,11 @@ const CODE_EXTENSIONS = new Set([
13
13
  ]);
14
14
  const TRANSLATION_DIR_SEGMENTS = ['locale', 'locales', 'i18n', 'translations'];
15
15
  const DEFAULT_EXCLUDED_PATH_PREFIXES = ['.claude/', '.agent/', 'roadmap-skill/'];
16
- const GENERATED_OUTPUT_PREFIXES = ['dist-electron/', 'dist/', 'build/', 'out/', '.next/', 'coverage/'];
16
+ const GENERATED_OUTPUT_PREFIXES = [
17
+ 'dist-electron/', 'dist/', 'build/', 'out/', '.next/', 'coverage/',
18
+ '.open-next/', '.vercel/', '.svelte-kit/', '.parcel-cache/', '.angular/',
19
+ '.expo/', '.serverless/', '.wrangler/', '.tmp/', 'tmp/'
20
+ ];
17
21
  const AUXILIARY_HEURISTIC_PATH_SEGMENTS = new Set(['scripts', 'tools', 'tooling', 'demo', 'demos']);
18
22
 
19
23
  // "docs" omitted from DOC_HINTS — it is a path prefix in scan tasks, not a doc-authoring keyword.
@@ -246,6 +250,7 @@ function readFileIndex(projectRoot, files, config) {
246
250
  if (SELF_REFERENTIAL_FILES.has(relativePath)) continue;
247
251
  if (isFixturePath(relativePath)) continue;
248
252
  if (shouldExcludeByDefaultPath(relativePath, config)) continue;
253
+ if (isGeneratedOutputPath(relativePath)) continue;
249
254
 
250
255
  const absolutePath = path.resolve(projectRoot, relativePath);
251
256
  const ext = path.extname(relativePath).toLowerCase();
@@ -1774,7 +1779,10 @@ function evaluateDeterministicVerification(task, context) {
1774
1779
  }
1775
1780
 
1776
1781
  function buildValidationContext(projectRoot, config, plugins, options = {}) {
1777
- const files = walkFiles(projectRoot);
1782
+ const userExcludeDirs = Array.isArray(config && config.scan && config.scan.excludeDirs)
1783
+ ? config.scan.excludeDirs
1784
+ : [];
1785
+ const files = walkFiles(projectRoot, { extraIgnoredDirs: userExcludeDirs });
1778
1786
  const fileIndex = readFileIndex(projectRoot, files, config);
1779
1787
  const testFrameworks = detectTestFrameworks(projectRoot, files);
1780
1788
  const pathHintResolver = buildPathHintResolver(fileIndex);
@@ -1852,6 +1860,39 @@ function buildDiscoveredEvidenceLine(evidence) {
1852
1860
  }
1853
1861
 
1854
1862
  function validateTask(task, context, config, plugins) {
1863
+ if (task.verifiedBy === 'human') {
1864
+ const hasEvidenceLine = Array.isArray(task.evidenceLines) &&
1865
+ task.evidenceLines.some((e) => e.text && e.text.trim().length > 0);
1866
+ if (!hasEvidenceLine) {
1867
+ return {
1868
+ passed: false,
1869
+ confidence: 'low',
1870
+ attempted: false,
1871
+ reasons: ['human-verified tasks require an Evidence: child line'],
1872
+ evidence: { code: false, test: false, artifact: false, files: [], codeFiles: [], testFiles: [], weakPathFiles: [], weakPathContentTokens: [], artifactFiles: [], heuristicArtifacts: [], symbols: [], structuralEvidence: null, authoritative: false, authoritativeFiles: [], authoritativeSummaries: [] },
1873
+ diagnostics: [],
1874
+ verificationRecipe: null,
1875
+ staleEvidenceDetected: false,
1876
+ staleEvidenceResolved: false,
1877
+ generatedTestEvidence: null
1878
+ };
1879
+ }
1880
+ const evidenceText = task.evidenceLines[0].text;
1881
+ return {
1882
+ passed: true,
1883
+ confidence: 'medium',
1884
+ attempted: true,
1885
+ reasons: [],
1886
+ evidence: { code: false, test: false, artifact: false, files: [], codeFiles: [], testFiles: [], weakPathFiles: [], weakPathContentTokens: [], artifactFiles: [], heuristicArtifacts: [], symbols: [], structuralEvidence: null, authoritative: true, authoritativeFiles: [], authoritativeSummaries: [evidenceText] },
1887
+ diagnostics: [],
1888
+ verificationRecipe: null,
1889
+ staleEvidenceDetected: false,
1890
+ staleEvidenceResolved: false,
1891
+ generatedTestEvidence: null,
1892
+ humanVerified: true
1893
+ };
1894
+ }
1895
+
1855
1896
  const {
1856
1897
  paths: pathHints,
1857
1898
  externalPaths,
@@ -1943,6 +1984,7 @@ function validateTask(task, context, config, plugins) {
1943
1984
 
1944
1985
  const requiresTest =
1945
1986
  !task.noTest &&
1987
+ task.kind !== 'docs' &&
1946
1988
  context.testFrameworks.length > 0 &&
1947
1989
  isCodeTask(task.text) &&
1948
1990
  !isDocTask(task.text) &&
@@ -2010,6 +2052,7 @@ function validateTask(task, context, config, plugins) {
2010
2052
  // Only pure path hints (not line-reference hints like file.ts:169) count as direct evidence.
2011
2053
  const hasDirectReferencePass = filesFromPurePathHints.length > 0 || filesFromSymbols.length > 0;
2012
2054
  const hasArtifactTaskPass = evidence.artifact && (
2055
+ task.kind === 'docs' ||
2013
2056
  isDocTask(task.text) ||
2014
2057
  evidence.heuristicArtifacts.length > 0 ||
2015
2058
  filesFromPaths.some((relativePath) => !CODE_EXTENSIONS.has(path.extname(relativePath).toLowerCase()))
@@ -2226,17 +2269,23 @@ function validateTasks(tasks, context, config, plugins) {
2226
2269
  return result;
2227
2270
  }
2228
2271
 
2229
- function auditValidation(tasks, results) {
2272
+ function auditValidation(tasks, results, changes) {
2230
2273
  const checkedWithoutEvidence = [];
2231
2274
  const readyButUnchecked = [];
2232
2275
  const checkedWithWeakEvidence = [];
2233
2276
  const documentationOnlyEvidenceForImplementation = [];
2234
2277
  const checkedWithNoStructuralEvidence = [];
2278
+ const humanVerifiedTasks = [];
2279
+ const newlyUnchecked = Array.isArray(changes && changes.newlyUnchecked) ? changes.newlyUnchecked : [];
2235
2280
 
2236
2281
  for (const task of tasks) {
2237
2282
  const result = results[task.id];
2238
2283
  if (!result) continue;
2239
2284
 
2285
+ if (result.humanVerified) {
2286
+ humanVerifiedTasks.push({ task, result });
2287
+ }
2288
+
2240
2289
  if (task.checked && !result.passed) {
2241
2290
  checkedWithoutEvidence.push({ task, result });
2242
2291
  }
@@ -2265,6 +2314,8 @@ function auditValidation(tasks, results) {
2265
2314
  checkedWithWeakEvidence,
2266
2315
  documentationOnlyEvidenceForImplementation,
2267
2316
  checkedWithNoStructuralEvidence,
2317
+ humanVerifiedTasks,
2318
+ newlyUnchecked
2268
2319
  };
2269
2320
  }
2270
2321