roadmapsmith 0.9.25 → 0.9.27
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 +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/package.json +1 -1
- package/skills.json +11 -11
- package/src/validator/index.js +224 -51
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "roadmapsmith",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.27",
|
|
4
4
|
"description": "One-command evidence-backed ROADMAP.md generator and sync tool for AI coding agents, with shared RoadmapSmith plugin skills for Codex and Claude plus the roadmapsmith status readiness surface.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "PapiScholz"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "roadmapsmith",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.27",
|
|
4
4
|
"description": "One-command evidence-backed ROADMAP.md generator and sync tool for AI coding agents, with shared RoadmapSmith plugin skills for Codex and Claude plus the roadmapsmith status readiness surface.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "PapiScholz"
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "roadmapsmith",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.27",
|
|
4
4
|
"description": "One-command evidence-backed ROADMAP.md generator and sync tool for AI coding agents, with shared RoadmapSmith plugin skills for Codex and Claude plus the roadmapsmith status readiness surface.",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"bin": {
|
package/skills.json
CHANGED
|
@@ -29,67 +29,67 @@
|
|
|
29
29
|
"name": "roadmap",
|
|
30
30
|
"path": "skills/roadmap",
|
|
31
31
|
"description": "Native slash palette for RoadmapSmith commands and recommended entrypoints across supported hosts.",
|
|
32
|
-
"version": "0.9.
|
|
32
|
+
"version": "0.9.27"
|
|
33
33
|
},
|
|
34
34
|
{
|
|
35
35
|
"name": "roadmap-zero",
|
|
36
36
|
"path": "skills/roadmap-zero",
|
|
37
37
|
"description": "Native slash entrypoint for the one-command Zero Mode CLI workflow.",
|
|
38
|
-
"version": "0.9.
|
|
38
|
+
"version": "0.9.27"
|
|
39
39
|
},
|
|
40
40
|
{
|
|
41
41
|
"name": "roadmap-maintain",
|
|
42
42
|
"path": "skills/roadmap-maintain",
|
|
43
43
|
"description": "Native slash entrypoint for the preserve-first generate + sync + audit flow.",
|
|
44
|
-
"version": "0.9.
|
|
44
|
+
"version": "0.9.27"
|
|
45
45
|
},
|
|
46
46
|
{
|
|
47
47
|
"name": "roadmap-status",
|
|
48
48
|
"path": "skills/roadmap-status",
|
|
49
49
|
"description": "Native slash readiness check grounded in roadmapsmith status JSON.",
|
|
50
|
-
"version": "0.9.
|
|
50
|
+
"version": "0.9.27"
|
|
51
51
|
},
|
|
52
52
|
{
|
|
53
53
|
"name": "roadmap-init",
|
|
54
54
|
"path": "skills/roadmap-init",
|
|
55
55
|
"description": "Native slash entrypoint for creating ROADMAP.md and AGENTS.md.",
|
|
56
|
-
"version": "0.9.
|
|
56
|
+
"version": "0.9.27"
|
|
57
57
|
},
|
|
58
58
|
{
|
|
59
59
|
"name": "roadmap-generate",
|
|
60
60
|
"path": "skills/roadmap-generate",
|
|
61
61
|
"description": "Native slash entrypoint for managed roadmap updates that require --full-regen before destructive replacement.",
|
|
62
|
-
"version": "0.9.
|
|
62
|
+
"version": "0.9.27"
|
|
63
63
|
},
|
|
64
64
|
{
|
|
65
65
|
"name": "roadmap-validate",
|
|
66
66
|
"path": "skills/roadmap-validate",
|
|
67
67
|
"description": "Native slash entrypoint for evidence-backed roadmap validation.",
|
|
68
|
-
"version": "0.9.
|
|
68
|
+
"version": "0.9.27"
|
|
69
69
|
},
|
|
70
70
|
{
|
|
71
71
|
"name": "roadmap-update",
|
|
72
72
|
"path": "skills/roadmap-update",
|
|
73
73
|
"description": "Native slash entrypoint for applying evidence-backed checklist sync.",
|
|
74
|
-
"version": "0.9.
|
|
74
|
+
"version": "0.9.27"
|
|
75
75
|
},
|
|
76
76
|
{
|
|
77
77
|
"name": "roadmap-sync",
|
|
78
78
|
"path": "skills/roadmap-sync",
|
|
79
79
|
"description": "Legacy namespaced root plus policy guidance for RoadmapSmith slash workflows.",
|
|
80
|
-
"version": "0.9.
|
|
80
|
+
"version": "0.9.27"
|
|
81
81
|
},
|
|
82
82
|
{
|
|
83
83
|
"name": "roadmap-audit",
|
|
84
84
|
"path": "skills/roadmap-audit",
|
|
85
85
|
"description": "Native slash entrypoint for the current sync-plus-audit workflow.",
|
|
86
|
-
"version": "0.9.
|
|
86
|
+
"version": "0.9.27"
|
|
87
87
|
},
|
|
88
88
|
{
|
|
89
89
|
"name": "roadmap-setup",
|
|
90
90
|
"path": "skills/roadmap-setup",
|
|
91
91
|
"description": "Native slash entrypoint for generating RoadmapSmith host integration files.",
|
|
92
|
-
"version": "0.9.
|
|
92
|
+
"version": "0.9.27"
|
|
93
93
|
}
|
|
94
94
|
]
|
|
95
95
|
}
|
package/src/validator/index.js
CHANGED
|
@@ -275,15 +275,29 @@ function isAsciiAlphaNumeric(char) {
|
|
|
275
275
|
return (code >= 48 && code <= 57) || (code >= 65 && code <= 90) || (code >= 97 && code <= 122);
|
|
276
276
|
}
|
|
277
277
|
|
|
278
|
-
function isPathTokenCharacter(char) {
|
|
279
|
-
|
|
278
|
+
function isPathTokenCharacter(char, current) {
|
|
279
|
+
if (char === '~') {
|
|
280
|
+
return !current;
|
|
281
|
+
}
|
|
282
|
+
return isAsciiAlphaNumeric(char) || char === '.' || char === '_' || char === '-' || char === '/' || char === '\\' || char === ':';
|
|
280
283
|
}
|
|
281
284
|
|
|
282
285
|
function stripTrailingPathPunctuation(token) {
|
|
283
286
|
let result = String(token || '');
|
|
284
287
|
while (result.length > 0) {
|
|
285
288
|
const lastChar = result[result.length - 1];
|
|
286
|
-
if (
|
|
289
|
+
if (
|
|
290
|
+
lastChar !== '.' &&
|
|
291
|
+
lastChar !== ',' &&
|
|
292
|
+
lastChar !== ';' &&
|
|
293
|
+
lastChar !== ':' &&
|
|
294
|
+
lastChar !== '!' &&
|
|
295
|
+
lastChar !== '?' &&
|
|
296
|
+
lastChar !== ')' &&
|
|
297
|
+
lastChar !== ']' &&
|
|
298
|
+
lastChar !== '>' &&
|
|
299
|
+
lastChar !== '`'
|
|
300
|
+
) {
|
|
287
301
|
break;
|
|
288
302
|
}
|
|
289
303
|
result = result.slice(0, -1);
|
|
@@ -294,22 +308,41 @@ function stripTrailingPathPunctuation(token) {
|
|
|
294
308
|
function collectPathishTokens(text) {
|
|
295
309
|
const tokens = [];
|
|
296
310
|
let current = '';
|
|
311
|
+
let tokenStart = -1;
|
|
297
312
|
const source = String(text || '');
|
|
298
313
|
for (let index = 0; index < source.length; index += 1) {
|
|
299
314
|
const char = source[index];
|
|
300
|
-
if (isPathTokenCharacter(char)) {
|
|
315
|
+
if (isPathTokenCharacter(char, current)) {
|
|
316
|
+
if (!current) {
|
|
317
|
+
tokenStart = index;
|
|
318
|
+
}
|
|
301
319
|
current += char;
|
|
302
320
|
continue;
|
|
303
321
|
}
|
|
304
322
|
if (current) {
|
|
305
|
-
|
|
323
|
+
const value = stripTrailingPathPunctuation(current);
|
|
324
|
+
if (value) {
|
|
325
|
+
tokens.push({
|
|
326
|
+
value,
|
|
327
|
+
start: tokenStart,
|
|
328
|
+
end: tokenStart + value.length
|
|
329
|
+
});
|
|
330
|
+
}
|
|
306
331
|
current = '';
|
|
332
|
+
tokenStart = -1;
|
|
307
333
|
}
|
|
308
334
|
}
|
|
309
335
|
if (current) {
|
|
310
|
-
|
|
336
|
+
const value = stripTrailingPathPunctuation(current);
|
|
337
|
+
if (value) {
|
|
338
|
+
tokens.push({
|
|
339
|
+
value,
|
|
340
|
+
start: tokenStart,
|
|
341
|
+
end: tokenStart + value.length
|
|
342
|
+
});
|
|
343
|
+
}
|
|
311
344
|
}
|
|
312
|
-
return tokens
|
|
345
|
+
return tokens;
|
|
313
346
|
}
|
|
314
347
|
|
|
315
348
|
// LINE_REF_RE matches "path/file.ext:NN" or "path/file.ext:NN-MM" — indicates WHERE
|
|
@@ -317,56 +350,142 @@ function collectPathishTokens(text) {
|
|
|
317
350
|
// to lineReferenceHints and excluded from hasDirectReferencePass scoring.
|
|
318
351
|
const LINE_REF_RE = /^(.+?):(\d+)(?:-\d+)?$/;
|
|
319
352
|
|
|
320
|
-
function
|
|
321
|
-
const
|
|
353
|
+
function normalizePathCandidateToken(rawToken) {
|
|
354
|
+
const stripped = stripTrailingPathPunctuation(String(rawToken || '').trim());
|
|
355
|
+
if (!stripped) {
|
|
356
|
+
return '';
|
|
357
|
+
}
|
|
358
|
+
const normalized = stripped.replace(/\\/g, '/');
|
|
359
|
+
if (/^~\//.test(normalized)) {
|
|
360
|
+
return normalized;
|
|
361
|
+
}
|
|
362
|
+
return normalized.replace(/^~(?=\/)/, '~');
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
function isExternalPathToken(token) {
|
|
366
|
+
return /^~\//.test(String(token || '').trim().replace(/\\/g, '/'));
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
function classifyExplicitPathCandidate(rawToken) {
|
|
370
|
+
const clean = normalizePathCandidateToken(rawToken);
|
|
322
371
|
if (!clean || clean.includes('*') || clean.includes('?')) {
|
|
323
372
|
return null;
|
|
324
373
|
}
|
|
325
374
|
|
|
326
375
|
const lineMatch = LINE_REF_RE.exec(clean);
|
|
327
376
|
if (lineMatch) {
|
|
328
|
-
const linePath =
|
|
377
|
+
const linePath = normalizePathCandidateToken(lineMatch[1]);
|
|
378
|
+
if (isExternalPathToken(linePath)) {
|
|
379
|
+
return { path: linePath, kind: 'external', isLineReference: true };
|
|
380
|
+
}
|
|
329
381
|
if (isRealFilePath(linePath)) {
|
|
330
|
-
return { path: linePath, isLineReference: true };
|
|
382
|
+
return { path: linePath, kind: 'repo', isLineReference: true };
|
|
331
383
|
}
|
|
332
384
|
return null;
|
|
333
385
|
}
|
|
334
386
|
|
|
387
|
+
if (isExternalPathToken(clean)) {
|
|
388
|
+
return { path: clean, kind: 'external', isLineReference: false };
|
|
389
|
+
}
|
|
390
|
+
|
|
335
391
|
if (!isRealFilePath(clean)) {
|
|
336
392
|
return null;
|
|
337
393
|
}
|
|
338
394
|
|
|
339
|
-
return { path: clean, isLineReference: false };
|
|
395
|
+
return { path: clean, kind: 'repo', isLineReference: false };
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
function addClassifiedPath(classified, results, externalPaths, lineReferenceHints) {
|
|
399
|
+
if (!classified) return;
|
|
400
|
+
if (classified.kind === 'external') {
|
|
401
|
+
externalPaths.add(classified.path);
|
|
402
|
+
} else {
|
|
403
|
+
results.add(classified.path);
|
|
404
|
+
if (classified.isLineReference) {
|
|
405
|
+
lineReferenceHints.add(classified.path);
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
function findHttpRequestRouteRanges(text) {
|
|
411
|
+
const ranges = [];
|
|
412
|
+
const pattern = /\b(?:GET|POST|PUT|PATCH|DELETE)\s+(\/[^\s`]+)/gi;
|
|
413
|
+
let match = pattern.exec(String(text || ''));
|
|
414
|
+
while (match) {
|
|
415
|
+
const routeToken = match[1];
|
|
416
|
+
const routeStart = match.index + match[0].length - routeToken.length;
|
|
417
|
+
ranges.push({ start: routeStart, end: routeStart + routeToken.length });
|
|
418
|
+
match = pattern.exec(String(text || ''));
|
|
419
|
+
}
|
|
420
|
+
return ranges;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
function isTokenInsideRanges(token, ranges) {
|
|
424
|
+
return ranges.some((range) => token.start >= range.start && token.end <= range.end);
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
function addPathTokensFromPlainText(text, results, externalPaths, lineReferenceHints) {
|
|
428
|
+
const ignoredRanges = findHttpRequestRouteRanges(text);
|
|
429
|
+
for (const token of collectPathishTokens(text)) {
|
|
430
|
+
if (!token.value.includes('/') && !isExternalPathToken(token.value)) {
|
|
431
|
+
continue;
|
|
432
|
+
}
|
|
433
|
+
if (isTokenInsideRanges(token, ignoredRanges)) {
|
|
434
|
+
continue;
|
|
435
|
+
}
|
|
436
|
+
addClassifiedPath(classifyExplicitPathCandidate(token.value), results, externalPaths, lineReferenceHints);
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
function addPathTokensFromBacktickSpan(text, results, externalPaths, lineReferenceHints) {
|
|
441
|
+
const wholeSpan = classifyExplicitPathCandidate(text);
|
|
442
|
+
if (wholeSpan) {
|
|
443
|
+
addClassifiedPath(wholeSpan, results, externalPaths, lineReferenceHints);
|
|
444
|
+
return;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
if (!/[;,]/.test(text)) {
|
|
448
|
+
return;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
for (const part of text.split(/[;,]/)) {
|
|
452
|
+
addClassifiedPath(classifyExplicitPathCandidate(part), results, externalPaths, lineReferenceHints);
|
|
453
|
+
}
|
|
340
454
|
}
|
|
341
455
|
|
|
342
456
|
function extractExplicitPaths(text) {
|
|
343
457
|
const results = new Set();
|
|
458
|
+
const externalPaths = new Set();
|
|
344
459
|
const lineReferenceHints = new Set();
|
|
460
|
+
const source = String(text || '');
|
|
461
|
+
let cursor = 0;
|
|
345
462
|
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
if (normalized.isLineReference) {
|
|
352
|
-
lineReferenceHints.add(normalized.path);
|
|
463
|
+
while (cursor < source.length) {
|
|
464
|
+
const openTick = source.indexOf('`', cursor);
|
|
465
|
+
if (openTick < 0) {
|
|
466
|
+
addPathTokensFromPlainText(source.slice(cursor), results, externalPaths, lineReferenceHints);
|
|
467
|
+
break;
|
|
353
468
|
}
|
|
354
|
-
}
|
|
355
469
|
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
if (normalized.isLineReference) {
|
|
362
|
-
lineReferenceHints.add(normalized.path);
|
|
470
|
+
addPathTokensFromPlainText(source.slice(cursor, openTick), results, externalPaths, lineReferenceHints);
|
|
471
|
+
const closeTick = source.indexOf('`', openTick + 1);
|
|
472
|
+
if (closeTick < 0) {
|
|
473
|
+
addPathTokensFromPlainText(source.slice(openTick), results, externalPaths, lineReferenceHints);
|
|
474
|
+
break;
|
|
363
475
|
}
|
|
476
|
+
|
|
477
|
+
addPathTokensFromBacktickSpan(source.slice(openTick + 1, closeTick), results, externalPaths, lineReferenceHints);
|
|
478
|
+
cursor = closeTick + 1;
|
|
364
479
|
}
|
|
365
480
|
|
|
366
481
|
const paths = Array.from(results)
|
|
367
482
|
.filter((p) => !p.includes('*') && !p.includes('?'))
|
|
368
483
|
.sort((left, right) => left.localeCompare(right));
|
|
369
|
-
return {
|
|
484
|
+
return {
|
|
485
|
+
paths,
|
|
486
|
+
externalPaths: Array.from(externalPaths).sort((left, right) => left.localeCompare(right)),
|
|
487
|
+
lineReferenceHints
|
|
488
|
+
};
|
|
370
489
|
}
|
|
371
490
|
|
|
372
491
|
// Standalone filenames (no slash) mentioned in task prose — e.g. "roadmap-skill.config.json",
|
|
@@ -434,10 +553,59 @@ function isImplementationTask(taskText) {
|
|
|
434
553
|
return !isDocTask(taskText) && (isCodeTask(taskText) || taskDescribesChange(taskText));
|
|
435
554
|
}
|
|
436
555
|
|
|
437
|
-
function
|
|
556
|
+
function deriveNextAppRouteAlias(relativePath) {
|
|
557
|
+
const normalized = normalizePathForMatch(relativePath);
|
|
558
|
+
const match = normalized.match(/^(?:src\/)?app(?:\/(.*))?\/(page|route)\.(?:js|jsx|ts|tsx)$/);
|
|
559
|
+
if (!match) {
|
|
560
|
+
return null;
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
const routePath = match[1] || '';
|
|
564
|
+
const segments = routePath ? routePath.split('/').filter(Boolean) : [];
|
|
565
|
+
const visibleSegments = [];
|
|
566
|
+
for (const segment of segments) {
|
|
567
|
+
if (/^\([^)]*\)$/.test(segment)) {
|
|
568
|
+
continue;
|
|
569
|
+
}
|
|
570
|
+
if (segment.includes('(') || segment.includes(')') || segment.includes('[') || segment.includes(']') || segment.startsWith('@')) {
|
|
571
|
+
return null;
|
|
572
|
+
}
|
|
573
|
+
visibleSegments.push(segment);
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
return visibleSegments.length > 0 ? `/${visibleSegments.join('/')}` : '/';
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
function buildPathHintResolver(fileIndex) {
|
|
580
|
+
const routeAliasIndex = new Map();
|
|
581
|
+
for (const file of fileIndex) {
|
|
582
|
+
const alias = deriveNextAppRouteAlias(file.relativePath);
|
|
583
|
+
if (!alias) {
|
|
584
|
+
continue;
|
|
585
|
+
}
|
|
586
|
+
const existing = routeAliasIndex.get(alias) || [];
|
|
587
|
+
existing.push(file.relativePath);
|
|
588
|
+
routeAliasIndex.set(alias, existing);
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
for (const [alias, matches] of routeAliasIndex.entries()) {
|
|
592
|
+
routeAliasIndex.set(alias, Array.from(new Set(matches)).sort((left, right) => left.localeCompare(right)));
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
return {
|
|
596
|
+
fileIndex,
|
|
597
|
+
routeAliasIndex
|
|
598
|
+
};
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
function findFilesByPathHints(pathHints, pathHintResolver) {
|
|
602
|
+
const resolver = Array.isArray(pathHintResolver)
|
|
603
|
+
? buildPathHintResolver(pathHintResolver)
|
|
604
|
+
: pathHintResolver;
|
|
605
|
+
const fileIndex = resolver.fileIndex;
|
|
438
606
|
const matches = [];
|
|
439
607
|
for (const hint of pathHints) {
|
|
440
|
-
const normalizedHint = hint
|
|
608
|
+
const normalizedHint = normalizePathCandidateToken(hint);
|
|
441
609
|
const direct = fileIndex.find((file) => file.relativePath === normalizedHint);
|
|
442
610
|
if (direct) {
|
|
443
611
|
matches.push(direct.relativePath);
|
|
@@ -449,6 +617,11 @@ function findFilesByPathHints(pathHints, fileIndex) {
|
|
|
449
617
|
matches.push(file.relativePath);
|
|
450
618
|
}
|
|
451
619
|
}
|
|
620
|
+
|
|
621
|
+
const routeMatches = resolver.routeAliasIndex.get(normalizedHint);
|
|
622
|
+
if (routeMatches && routeMatches.length > 0) {
|
|
623
|
+
matches.push(...routeMatches);
|
|
624
|
+
}
|
|
452
625
|
}
|
|
453
626
|
return Array.from(new Set(matches)).sort((left, right) => left.localeCompare(right));
|
|
454
627
|
}
|
|
@@ -740,19 +913,12 @@ function isTestPath(relativePath) {
|
|
|
740
913
|
return /(^|\/)(__tests__|tests)\//.test(relativePath) || /\.test\.|\.spec\.|_test\.go$/.test(relativePath);
|
|
741
914
|
}
|
|
742
915
|
|
|
743
|
-
function
|
|
744
|
-
const
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
}
|
|
750
|
-
if (!hasKnownFileExtension(candidate)) {
|
|
751
|
-
continue;
|
|
752
|
-
}
|
|
753
|
-
paths.add(candidate.replace(/^\.\//, ''));
|
|
754
|
-
}
|
|
755
|
-
return Array.from(paths).sort((left, right) => left.localeCompare(right));
|
|
916
|
+
function extractReferencedPaths(text) {
|
|
917
|
+
const extracted = extractExplicitPaths(text);
|
|
918
|
+
return {
|
|
919
|
+
repoPaths: extracted.paths,
|
|
920
|
+
externalPaths: extracted.externalPaths
|
|
921
|
+
};
|
|
756
922
|
}
|
|
757
923
|
|
|
758
924
|
function evidenceLineHasPassingSummary(evidenceText) {
|
|
@@ -771,7 +937,7 @@ function evidenceSummaryImpliesTests(evidenceText) {
|
|
|
771
937
|
/\b(?:vitest|jest|npm test|pnpm test|yarn test|bun test)\b/i.test(String(evidenceText || ''));
|
|
772
938
|
}
|
|
773
939
|
|
|
774
|
-
function evaluateAuthoritativeEvidence(task,
|
|
940
|
+
function evaluateAuthoritativeEvidence(task, pathHintResolver) {
|
|
775
941
|
const evidenceLines = Array.isArray(task.evidenceLines) ? task.evidenceLines : [];
|
|
776
942
|
if (evidenceLines.length === 0) {
|
|
777
943
|
return {
|
|
@@ -786,8 +952,9 @@ function evaluateAuthoritativeEvidence(task, fileIndex) {
|
|
|
786
952
|
};
|
|
787
953
|
}
|
|
788
954
|
|
|
789
|
-
const
|
|
790
|
-
const
|
|
955
|
+
const extractedReferences = evidenceLines.map((line) => extractReferencedPaths(line.text));
|
|
956
|
+
const referencedPaths = unionArrays(...extractedReferences.map((entry) => entry.repoPaths));
|
|
957
|
+
const matchedPaths = referencedPaths.length > 0 ? findFilesByPathHints(referencedPaths, pathHintResolver) : [];
|
|
791
958
|
const summaryMatches = evidenceLines
|
|
792
959
|
.filter((line) => evidenceLineHasPassingSummary(line.text))
|
|
793
960
|
.map((line) => line.text);
|
|
@@ -1110,6 +1277,7 @@ function buildValidationContext(projectRoot, config, plugins) {
|
|
|
1110
1277
|
const files = walkFiles(projectRoot);
|
|
1111
1278
|
const fileIndex = readFileIndex(projectRoot, files, config);
|
|
1112
1279
|
const testFrameworks = detectTestFrameworks(projectRoot, files);
|
|
1280
|
+
const pathHintResolver = buildPathHintResolver(fileIndex);
|
|
1113
1281
|
|
|
1114
1282
|
return {
|
|
1115
1283
|
projectRoot,
|
|
@@ -1117,26 +1285,31 @@ function buildValidationContext(projectRoot, config, plugins) {
|
|
|
1117
1285
|
plugins,
|
|
1118
1286
|
files,
|
|
1119
1287
|
fileIndex,
|
|
1288
|
+
pathHintResolver,
|
|
1120
1289
|
testFrameworks
|
|
1121
1290
|
};
|
|
1122
1291
|
}
|
|
1123
1292
|
|
|
1124
1293
|
function validateTask(task, context, config, plugins) {
|
|
1125
|
-
const {
|
|
1294
|
+
const {
|
|
1295
|
+
paths: pathHints,
|
|
1296
|
+
externalPaths,
|
|
1297
|
+
lineReferenceHints
|
|
1298
|
+
} = extractExplicitPaths(task.text);
|
|
1126
1299
|
// Paths that are line-reference hints (file.ts:NN) indicate WHERE to implement,
|
|
1127
1300
|
// not that implementation exists. They are excluded from hasDirectReferencePass.
|
|
1128
1301
|
const purePathHints = pathHints.filter((p) => !lineReferenceHints.has(p));
|
|
1129
1302
|
const standaloneFilenames = extractStandaloneFilenames(task.text);
|
|
1130
1303
|
const symbolHints = extractSymbolHints(task.text);
|
|
1131
|
-
const authoritativeEvidence = evaluateAuthoritativeEvidence(task, context.
|
|
1304
|
+
const authoritativeEvidence = evaluateAuthoritativeEvidence(task, context.pathHintResolver);
|
|
1132
1305
|
|
|
1133
|
-
const filesFromPaths = findFilesByPathHints(pathHints, context.
|
|
1134
|
-
const filesFromPurePathHints = findFilesByPathHints(purePathHints, context.
|
|
1306
|
+
const filesFromPaths = findFilesByPathHints(pathHints, context.pathHintResolver);
|
|
1307
|
+
const filesFromPurePathHints = findFilesByPathHints(purePathHints, context.pathHintResolver);
|
|
1135
1308
|
const filesFromSymbols = findFilesBySymbols(symbolHints, context.fileIndex);
|
|
1136
1309
|
// Combine path hints AND standalone filenames for token exclusion so that tokens
|
|
1137
1310
|
// derived from any referenced filename (e.g. "roadmap-skill" from
|
|
1138
1311
|
// "roadmap-skill.config.json") are excluded from code evidence scoring.
|
|
1139
|
-
const pathDerivedTokens = extractPathDerivedTokens([...pathHints, ...standaloneFilenames]);
|
|
1312
|
+
const pathDerivedTokens = extractPathDerivedTokens([...pathHints, ...externalPaths, ...standaloneFilenames]);
|
|
1140
1313
|
const filesFromCode = findCodeEvidence(task.text, context.fileIndex, pathDerivedTokens);
|
|
1141
1314
|
const filesFromWeakPathTokens = findFilesByTaskPathTokens(task.text, context.fileIndex, pathDerivedTokens);
|
|
1142
1315
|
const weakPathContentTokens = findWeakPathContentSpecificTokens(task.text, context.fileIndex, filesFromWeakPathTokens, pathDerivedTokens);
|