roadmapsmith 0.9.9 → 0.9.10

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "roadmapsmith",
3
- "version": "0.9.9",
3
+ "version": "0.9.10",
4
4
  "description": "Evidence-backed ROADMAP.md generator and sync tool for AI coding agents.",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -178,7 +178,11 @@ function hasFileExtension(token) {
178
178
  }
179
179
 
180
180
  function isLikelyPath(token) {
181
- if (/^\.{1,2}\/|^\//.test(token)) return true;
181
+ if (token.includes('*') || token.includes('?')) return false; // glob/wildcard
182
+ if (/^\.{1,2}\/|^\//.test(token)) {
183
+ // Bare "/" or "./" with nothing after is not a real path (e.g. "API / ESC-POS" → "/")
184
+ return /[A-Za-z0-9_]/.test(token);
185
+ }
182
186
  if (hasFileExtension(token)) return true;
183
187
  if (KNOWN_PATH_ROOTS.some((root) => token.startsWith(root))) return true;
184
188
  // The ">= 2 slashes" rule was intentionally removed: it caused conceptual slash phrases
@@ -262,14 +266,16 @@ function extractExplicitPaths(text) {
262
266
  for (const token of quoted) {
263
267
  const clean = token.slice(1, -1);
264
268
  if (clean.includes('*') || clean.includes('?')) continue; // glob
265
- if (clean.includes('/') || clean.includes('\\') || clean.includes('.')) {
266
- const lineMatch = LINE_REF_RE.exec(clean);
267
- if (lineMatch && hasKnownFileExtension(lineMatch[1])) {
268
- lineReferenceHints.add(lineMatch[1]);
269
- results.add(lineMatch[1]);
270
- } else {
271
- results.add(clean);
272
- }
269
+ const hasSlash = clean.includes('/') || clean.includes('\\');
270
+ // Require a slash or a known file extension — rejects property access like err.message,
271
+ // fs.readFileSync, error.stack (whose extensions are not in KNOWN_FILE_EXTENSIONS).
272
+ if (!hasSlash && !hasKnownFileExtension(clean)) continue;
273
+ const lineMatch = LINE_REF_RE.exec(clean);
274
+ if (lineMatch && hasKnownFileExtension(lineMatch[1])) {
275
+ lineReferenceHints.add(lineMatch[1]);
276
+ results.add(lineMatch[1]);
277
+ } else {
278
+ results.add(clean);
273
279
  }
274
280
  }
275
281
 
@@ -288,7 +294,9 @@ function extractExplicitPaths(text) {
288
294
  }
289
295
  }
290
296
 
291
- const paths = Array.from(results).sort((left, right) => left.localeCompare(right));
297
+ const paths = Array.from(results)
298
+ .filter((p) => !p.includes('*') && !p.includes('?'))
299
+ .sort((left, right) => left.localeCompare(right));
292
300
  return { paths, lineReferenceHints };
293
301
  }
294
302
 
@@ -1060,7 +1068,9 @@ function validateTask(task, context, config, plugins) {
1060
1068
  code: filesFromCode.length > 0 || filesFromSymbols.length > 0,
1061
1069
  test: filesFromTests.length > 0,
1062
1070
  artifact: filesFromArtifacts.length > 0,
1063
- files: filesFromPaths,
1071
+ // Use only pure path hints (not line-reference hints) so that "file.ts:169" style hints
1072
+ // — which indicate WHERE to implement — do not contribute to feature-surface scoring.
1073
+ files: filesFromPurePathHints,
1064
1074
  symbols: filesFromSymbols,
1065
1075
  codeFiles: filesFromCode,
1066
1076
  weakPathFiles: filesFromWeakPathTokens,
@@ -1077,7 +1087,9 @@ function validateTask(task, context, config, plugins) {
1077
1087
  applyAuthoritativeEvidence(evidence, authoritativeEvidence, context.fileIndex);
1078
1088
 
1079
1089
  const reasons = [];
1080
- if (pathHints.length > 0 && filesFromPaths.length === 0) {
1090
+ // Suppress path hint failures when authoritative evidence already confirms the task —
1091
+ // a bad path hint (typo, moved file) should not override a passing Evidence line.
1092
+ if (pathHints.length > 0 && filesFromPaths.length === 0 && !authoritativeEvidence.passed) {
1081
1093
  reasons.push(`missing referenced file(s): ${pathHints.join(', ')}`);
1082
1094
  }
1083
1095
  if (Array.isArray(authoritativeEvidence.reasons) && authoritativeEvidence.reasons.length > 0) {
@@ -1182,7 +1194,19 @@ function validateTask(task, context, config, plugins) {
1182
1194
  uniqueReasons = Array.from(new Set(uniqueReasons));
1183
1195
  }
1184
1196
 
1185
- let passed = authoritativeEvidence.passed || hasDirectReferencePass || hasArtifactTaskPass || hasTrustedRuleEvidencePass || meetsStrongThreshold;
1197
+ // hasDirectReferencePass is intentionally excluded: a path hint in task text indicates
1198
+ // WHERE to implement, not that implementation is done. Unchecked tasks need authoritative
1199
+ // evidence, artifact evidence, or strong code+test threshold to pass.
1200
+ // Already-checked tasks with found path hints are preserved via shouldPreserveCheckedTask.
1201
+ let passed = authoritativeEvidence.passed || hasArtifactTaskPass || hasTrustedRuleEvidencePass || meetsStrongThreshold;
1202
+
1203
+ if (!passed && !task.checked && hasDirectReferencePass) {
1204
+ const locationReason = 'file reference shows implementation location, not confirmed completion';
1205
+ if (!uniqueReasons.includes(locationReason)) {
1206
+ uniqueReasons.push(locationReason);
1207
+ }
1208
+ }
1209
+
1186
1210
  if (task.warningText && passed && !authoritativeEvidence.passed && !meetsStrongThreshold) {
1187
1211
  passed = false;
1188
1212
  uniqueReasons.push(task.warningText);
@@ -1192,15 +1216,20 @@ function validateTask(task, context, config, plugins) {
1192
1216
  passed = false;
1193
1217
  }
1194
1218
 
1219
+ // Preserve already-checked tasks when the validator can't confirm implementation but also
1220
+ // can't find strong evidence against it. Two cases:
1221
+ // 1. No path/symbol hints at all — no machine-readable claims to evaluate.
1222
+ // 2. Path hints resolve to existing files but code/test evidence is absent —
1223
+ // file presence is not implementation; don't uncheck on that alone.
1224
+ // Symbol hints are NOT preserved: a missing symbol is a concrete falsifiable claim.
1195
1225
  const shouldPreserveCheckedTask =
1196
1226
  task.checked &&
1197
1227
  !passed &&
1198
1228
  !authoritativeEvidence.active &&
1199
- purePathHints.length === 0 &&
1200
1229
  symbolHints.length === 0 &&
1201
- !hasDirectReferencePass &&
1230
+ negativeSignalMatches.length === 0 &&
1202
1231
  evidence.structuralEvidence !== false &&
1203
- negativeSignalMatches.length === 0;
1232
+ (hasDirectReferencePass || purePathHints.length === 0);
1204
1233
  let preservedCheckedState = false;
1205
1234
  if (shouldPreserveCheckedTask) {
1206
1235
  passed = true;