roadmapsmith 0.9.37 → 0.9.39
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/bin/cli.js +69 -49
- package/package.json +1 -1
- package/skills.json +11 -11
- package/src/generator/index.js +94 -16
- package/src/host.js +4 -1
- package/src/model.js +2 -1
- package/src/parser/index.js +2 -0
- package/src/renderer/compact.js +4 -4
- package/src/renderer/helpers.js +8 -3
- package/src/renderer/professional.js +18 -72
- package/src/utils.js +1 -2
- package/src/validator/index.js +17 -5
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "roadmapsmith",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.39",
|
|
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.
|
|
3
|
+
"version": "0.9.39",
|
|
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
|
@@ -29,8 +29,8 @@ function printHelp() {
|
|
|
29
29
|
' Canonical commands:',
|
|
30
30
|
' roadmapsmith zero [--project-root <path>] [--config <path>] [--product-name <text>] [--primary-user <text>] [--problem-statement <text>] [--target-outcome <text>] [--anti-goal <text> ...] [--preferred-stack <text>] [--constraint <text> ...] [--done-criterion <text> ...]',
|
|
31
31
|
' roadmapsmith maintain [--project-root <path>] [--config <path>] [--roadmap-file <path>] [--dry-run] [--full-regen] [--refresh-annotations]',
|
|
32
|
-
' roadmapsmith status [--roadmap-file <path>] [--project-root <path>] [--config <path>] [--json]',
|
|
33
|
-
' roadmapsmith validate [--roadmap-file <path>] [--project-root <path>] [--config <path>] [--task <id|text>] [--json] [--strict]',
|
|
32
|
+
' roadmapsmith status [--roadmap-file <path>] [--project-root <path>] [--config <path>] [--json] [--no-vscode]',
|
|
33
|
+
' roadmapsmith validate [--roadmap-file <path>] [--project-root <path>] [--config <path>] [--task <id|text>] [--json] [--strict] [--hide-planned]',
|
|
34
34
|
' roadmapsmith update [--task <stable-id> --evidence <text>] [--roadmap-file <path>] [--project-root <path>] [--config <path>] [--dry-run]',
|
|
35
35
|
' roadmapsmith setup [--project-root <path>] [--config <path>] [--editor vscode] [--hosts <codex,claude>] [--dry-run]',
|
|
36
36
|
'',
|
|
@@ -58,6 +58,9 @@ function isEnabled(value) {
|
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
function formatResultLine(task, result) {
|
|
61
|
+
if (result.planned) {
|
|
62
|
+
return `PLAN [${task.id}] ${task.text}`;
|
|
63
|
+
}
|
|
61
64
|
const diagnostics = Array.isArray(result.diagnostics) ? result.diagnostics : [];
|
|
62
65
|
const primaryError = diagnostics.find((item) => item.severity === 'error');
|
|
63
66
|
const warnings = diagnostics.filter((item) => item.severity === 'warning');
|
|
@@ -322,6 +325,8 @@ function runSyncCommand(projectRoot, config, flags, options = {}) {
|
|
|
322
325
|
console.log(writeResult.changed ? `Updated ${roadmapFile}` : `No changes for ${roadmapFile}`);
|
|
323
326
|
}
|
|
324
327
|
|
|
328
|
+
// options.audit (distinct from flags.audit early return above): used only by
|
|
329
|
+
// runMaintainCommand to print audit summary AFTER mutating ROADMAP.md.
|
|
325
330
|
if (options.audit) {
|
|
326
331
|
const audit = auditValidation(syncTasks, results, changes);
|
|
327
332
|
printAudit(audit);
|
|
@@ -412,19 +417,21 @@ function printHumanStatus(payload) {
|
|
|
412
417
|
console.log(`CLI resolution: ${payload.cli.kind}${payload.cli.path ? ` (${payload.cli.path})` : ''}${payload.cli.ready ? '' : ' [missing]'}`);
|
|
413
418
|
console.log(`Roadmap file: ${payload.roadmap.exists ? 'ready' : 'missing'} (${payload.roadmap.path})`);
|
|
414
419
|
console.log(`Agent rules: ${payload.agents.exists ? 'ready' : 'missing'} (${payload.agents.path})`);
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
420
|
+
if (!payload.vscode.skipped) {
|
|
421
|
+
console.log(`VS Code launcher: ${payload.vscode.launcher.exists ? 'ready' : 'missing'} (${payload.vscode.launcher.path})`);
|
|
422
|
+
console.log(`VS Code task wrappers: ${payload.vscode.wrappers.ready ? 'ready' : 'incomplete'} (${payload.vscode.wrappers.presentCount}/${payload.vscode.wrappers.expectedCount} files)`);
|
|
423
|
+
console.log(`VS Code tasks: ${payload.vscode.tasks.ready ? 'ready' : 'incomplete'} (${payload.vscode.tasks.presentLabels.length}/${payload.vscode.tasks.expectedLabels.length} tasks)`);
|
|
424
|
+
if (!payload.vscode.tasks.ready && payload.vscode.tasks.missingLabels.length > 0) {
|
|
425
|
+
console.log(`Missing VS Code tasks: ${payload.vscode.tasks.missingLabels.join(', ')}`);
|
|
426
|
+
}
|
|
427
|
+
if (Array.isArray(payload.vscode.tasks.missingAdvancedLabels) && payload.vscode.tasks.missingAdvancedLabels.length > 0) {
|
|
428
|
+
console.log(`Missing advanced VS Code tasks: ${payload.vscode.tasks.missingAdvancedLabels.join(', ')}`);
|
|
429
|
+
}
|
|
430
|
+
if (!payload.vscode.wrappers.ready) {
|
|
431
|
+
console.log(`Missing task wrapper files: ${payload.vscode.wrappers.missingPaths.join(', ')}`);
|
|
432
|
+
}
|
|
427
433
|
}
|
|
434
|
+
console.log(`Node runtime: ${payload.runtime.ready ? `ready (${payload.runtime.kind}${payload.runtime.path ? `: ${payload.runtime.path}` : ''})` : 'missing'}`);
|
|
428
435
|
console.log(`Codex readiness: ${payload.hosts.codex.ready ? 'ready' : 'needs setup'} (${payload.hosts.codex.message})`);
|
|
429
436
|
console.log(`Claude readiness: ${payload.hosts.claude.ready ? 'ready' : 'needs setup'} (${payload.hosts.claude.message})`);
|
|
430
437
|
printReadinessSummary(payload.summary);
|
|
@@ -434,15 +441,20 @@ function printHumanStatus(payload) {
|
|
|
434
441
|
if (!payload.cli.ready) {
|
|
435
442
|
console.log('\nInstalling the skill alone does not expose the CLI in VS Code. Install the CLI and rerun roadmapsmith setup.');
|
|
436
443
|
}
|
|
437
|
-
if (!payload.runtime.ready) {
|
|
444
|
+
if (!payload.vscode.skipped && !payload.runtime.ready) {
|
|
438
445
|
console.log('\nThe VS Code task runtime is missing. Install Node.js or set ROADMAPSMITH_NODE, then rerun RoadmapSmith: Status.');
|
|
439
446
|
}
|
|
440
447
|
}
|
|
441
448
|
|
|
449
|
+
function shouldSkipVscode(projectRoot, flags) {
|
|
450
|
+
return isEnabled(flags['no-vscode']) || !fs.existsSync(path.join(projectRoot, '.vscode'));
|
|
451
|
+
}
|
|
452
|
+
|
|
442
453
|
function runStatusCommand(projectRoot, config, flags, options = {}) {
|
|
454
|
+
const skipVscode = shouldSkipVscode(projectRoot, flags);
|
|
443
455
|
const roadmapFile = resolveRoadmapFile(projectRoot, config, flags['roadmap-file']);
|
|
444
456
|
const agentsFile = resolveAgentsFile(projectRoot, config, flags['agents-file']);
|
|
445
|
-
const payload = inspectHostSetup(projectRoot, { roadmapFile, agentsFile, currentCliPath: __filename });
|
|
457
|
+
const payload = inspectHostSetup(projectRoot, { roadmapFile, agentsFile, currentCliPath: __filename, skipVscode });
|
|
446
458
|
|
|
447
459
|
if (options.json) {
|
|
448
460
|
process.stdout.write(JSON.stringify(payload, null, 2) + '\n');
|
|
@@ -450,7 +462,7 @@ function runStatusCommand(projectRoot, config, flags, options = {}) {
|
|
|
450
462
|
printHumanStatus(payload);
|
|
451
463
|
}
|
|
452
464
|
|
|
453
|
-
const ready = payload.cli.ready && payload.roadmap.exists && payload.agents.exists && payload.vscode.tasks.ready && payload.runtime.ready && payload.claude.ready;
|
|
465
|
+
const ready = payload.cli.ready && payload.roadmap.exists && payload.agents.exists && (payload.vscode.skipped || payload.vscode.tasks.ready) && payload.runtime.ready && payload.claude.ready;
|
|
454
466
|
if (!ready) {
|
|
455
467
|
process.exitCode = 1;
|
|
456
468
|
}
|
|
@@ -661,7 +673,12 @@ async function run() {
|
|
|
661
673
|
const results = validateTasks(tasks, validationContext, config, validationContext.plugins);
|
|
662
674
|
|
|
663
675
|
const minRank = CONFIDENCE_RANK[config.validation && config.validation.minimumConfidence] ?? 0;
|
|
664
|
-
const
|
|
676
|
+
const hidePlanned = isEnabled(flags['hide-planned']);
|
|
677
|
+
const visibleTasks = tasks.filter((task) => {
|
|
678
|
+
if ((CONFIDENCE_RANK[results[task.id].confidence] ?? 0) < minRank) return false;
|
|
679
|
+
if (hidePlanned && results[task.id].planned) return false;
|
|
680
|
+
return true;
|
|
681
|
+
});
|
|
665
682
|
|
|
666
683
|
if (isEnabled(flags.json)) {
|
|
667
684
|
const payload = visibleTasks.map((task) => ({ task, result: results[task.id] }));
|
|
@@ -672,7 +689,7 @@ async function run() {
|
|
|
672
689
|
});
|
|
673
690
|
}
|
|
674
691
|
|
|
675
|
-
const failed = visibleTasks.some((task) => !results[task.id].passed);
|
|
692
|
+
const failed = visibleTasks.some((task) => !results[task.id].passed && !results[task.id].planned);
|
|
676
693
|
if (failed) {
|
|
677
694
|
process.exitCode = 1;
|
|
678
695
|
}
|
|
@@ -681,6 +698,7 @@ async function run() {
|
|
|
681
698
|
|
|
682
699
|
if (effectiveCommand === 'status' || effectiveCommand === 'doctor') {
|
|
683
700
|
const projectRoot = path.resolve(String(flags['project-root'] || process.cwd()));
|
|
701
|
+
const skipVscode = shouldSkipVscode(projectRoot, flags);
|
|
684
702
|
let ok = true;
|
|
685
703
|
const jsonMode = isEnabled(flags.json);
|
|
686
704
|
const log = jsonMode ? () => {} : console.log;
|
|
@@ -718,7 +736,7 @@ async function run() {
|
|
|
718
736
|
let hostStatus = null;
|
|
719
737
|
if (config) {
|
|
720
738
|
try {
|
|
721
|
-
hostStatus = inspectHostSetup(projectRoot, { roadmapFile, agentsFile });
|
|
739
|
+
hostStatus = inspectHostSetup(projectRoot, { roadmapFile, agentsFile, skipVscode });
|
|
722
740
|
} catch (error) {
|
|
723
741
|
logError(`[fail] Host integration error: ${error.message}`);
|
|
724
742
|
ok = false;
|
|
@@ -733,35 +751,37 @@ async function run() {
|
|
|
733
751
|
ok = false;
|
|
734
752
|
}
|
|
735
753
|
|
|
736
|
-
if (
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
754
|
+
if (!skipVscode) {
|
|
755
|
+
if (hostStatus.vscode.launcher.exists) {
|
|
756
|
+
log(`[ok] VS Code launcher found: ${hostStatus.vscode.launcher.path}`);
|
|
757
|
+
} else {
|
|
758
|
+
logError(`[fail] VS Code launcher missing: ${hostStatus.vscode.launcher.path}`);
|
|
759
|
+
ok = false;
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
if (hostStatus.vscode.wrappers.ready) {
|
|
763
|
+
log(`[ok] VS Code task wrappers ready: ${hostStatus.vscode.wrappers.presentCount}/${hostStatus.vscode.wrappers.expectedCount} files`);
|
|
764
|
+
} else {
|
|
765
|
+
logError(`[fail] VS Code task wrappers incomplete: missing ${hostStatus.vscode.wrappers.missingPaths.join(', ') || 'wrapper files'}`);
|
|
766
|
+
ok = false;
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
if (hostStatus.vscode.tasks.ready) {
|
|
770
|
+
log(`[ok] VS Code tasks ready: ${hostStatus.vscode.tasks.presentLabels.length}/${hostStatus.vscode.tasks.expectedLabels.length} tasks`);
|
|
771
|
+
} else {
|
|
772
|
+
logError(`[fail] VS Code tasks incomplete: missing ${hostStatus.vscode.tasks.missingLabels.join(', ') || 'managed labels'}`);
|
|
773
|
+
ok = false;
|
|
774
|
+
}
|
|
775
|
+
if (Array.isArray(hostStatus.vscode.tasks.missingAdvancedLabels) && hostStatus.vscode.tasks.missingAdvancedLabels.length > 0) {
|
|
776
|
+
log(`[warn] Advanced VS Code tasks missing: ${hostStatus.vscode.tasks.missingAdvancedLabels.join(', ')}`);
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
if (hostStatus.runtime.ready) {
|
|
780
|
+
log(`[ok] Node runtime: ${hostStatus.runtime.kind}${hostStatus.runtime.path ? ` (${hostStatus.runtime.path})` : ''}`);
|
|
781
|
+
} else {
|
|
782
|
+
logError('[fail] Node runtime missing for VS Code task execution');
|
|
783
|
+
ok = false;
|
|
784
|
+
}
|
|
765
785
|
}
|
|
766
786
|
|
|
767
787
|
if (hostStatus.claude.ready) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "roadmapsmith",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.39",
|
|
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.
|
|
31
|
+
"version": "0.9.39"
|
|
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.
|
|
37
|
+
"version": "0.9.39"
|
|
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.
|
|
43
|
+
"version": "0.9.39"
|
|
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.
|
|
49
|
+
"version": "0.9.39"
|
|
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.
|
|
55
|
+
"version": "0.9.39"
|
|
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.
|
|
61
|
+
"version": "0.9.39"
|
|
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.
|
|
67
|
+
"version": "0.9.39"
|
|
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.
|
|
73
|
+
"version": "0.9.39"
|
|
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.
|
|
79
|
+
"version": "0.9.39"
|
|
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.
|
|
85
|
+
"version": "0.9.39"
|
|
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.
|
|
91
|
+
"version": "0.9.39"
|
|
92
92
|
}
|
|
93
93
|
]
|
|
94
94
|
}
|
package/src/generator/index.js
CHANGED
|
@@ -61,10 +61,17 @@ function detectModules(files) {
|
|
|
61
61
|
}
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
+
for (const file of files) {
|
|
65
|
+
const parts = file.split('/');
|
|
66
|
+
if (parts.length === 2 && parts[1] === '__init__.py' && parts[0] && !GENERIC_MODULE_NAMES.has(parts[0])) {
|
|
67
|
+
modules.add(parts[0]);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
64
71
|
return Array.from(modules).sort((left, right) => left.localeCompare(right));
|
|
65
72
|
}
|
|
66
73
|
|
|
67
|
-
function detectCommands(files) {
|
|
74
|
+
function detectCommands(files, projectRoot) {
|
|
68
75
|
const commands = new Set();
|
|
69
76
|
for (const file of files) {
|
|
70
77
|
if (file.startsWith('bin/')) {
|
|
@@ -74,6 +81,44 @@ function detectCommands(files) {
|
|
|
74
81
|
commands.add(file.split('/')[1] || file);
|
|
75
82
|
}
|
|
76
83
|
}
|
|
84
|
+
if (projectRoot) {
|
|
85
|
+
let pkgContent = null;
|
|
86
|
+
try {
|
|
87
|
+
pkgContent = fs.readFileSync(path.join(projectRoot, 'package.json'), 'utf8');
|
|
88
|
+
} catch (err) {
|
|
89
|
+
if (err.code !== 'ENOENT') {
|
|
90
|
+
process.stderr.write(`roadmapsmith: failed to read package.json: ${err.message}\n`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
if (pkgContent) {
|
|
94
|
+
try {
|
|
95
|
+
const pkg = JSON.parse(pkgContent);
|
|
96
|
+
if (pkg.bin) {
|
|
97
|
+
const binNames = typeof pkg.bin === 'string'
|
|
98
|
+
? [path.basename(projectRoot)]
|
|
99
|
+
: Object.keys(pkg.bin);
|
|
100
|
+
for (const name of binNames) commands.add(name);
|
|
101
|
+
}
|
|
102
|
+
} catch (err) {
|
|
103
|
+
process.stderr.write(`roadmapsmith: failed to parse package.json: ${err.message}\n`);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
let tomlContent = null;
|
|
107
|
+
try {
|
|
108
|
+
tomlContent = fs.readFileSync(path.join(projectRoot, 'pyproject.toml'), 'utf8');
|
|
109
|
+
} catch (err) {
|
|
110
|
+
if (err.code !== 'ENOENT') {
|
|
111
|
+
process.stderr.write(`roadmapsmith: failed to read pyproject.toml: ${err.message}\n`);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
if (tomlContent) {
|
|
115
|
+
for (const sectionMatch of tomlContent.matchAll(/\[(?:project\.scripts|tool\.poetry\.scripts)\]([\s\S]*?)(?:\n\[|$)/g)) {
|
|
116
|
+
for (const entryMatch of sectionMatch[1].matchAll(/^([\w-]+)\s*=/gm)) {
|
|
117
|
+
commands.add(entryMatch[1]);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
77
122
|
return Array.from(commands).sort((left, right) => left.localeCompare(right));
|
|
78
123
|
}
|
|
79
124
|
|
|
@@ -144,7 +189,7 @@ function scanProject(projectRoot) {
|
|
|
144
189
|
const languages = detectLanguages(files);
|
|
145
190
|
const testFrameworks = detectTestFrameworks(projectRoot, files);
|
|
146
191
|
const modules = detectModules(files);
|
|
147
|
-
const commands = detectCommands(files);
|
|
192
|
+
const commands = detectCommands(files, projectRoot);
|
|
148
193
|
const todos = collectTodoHints(projectRoot, files);
|
|
149
194
|
const codeTodos = collectCodeTodoHints(projectRoot, files);
|
|
150
195
|
const workspaces = detectWorkspaces(projectRoot, files);
|
|
@@ -183,6 +228,25 @@ function toCandidate(text, phase, priority, source = 'default') {
|
|
|
183
228
|
};
|
|
184
229
|
}
|
|
185
230
|
|
|
231
|
+
function buildDoneCriteriaCandidates(zeroModeConfig) {
|
|
232
|
+
if (!Array.isArray(zeroModeConfig.doneCriteria) || zeroModeConfig.doneCriteria.length === 0) {
|
|
233
|
+
return [];
|
|
234
|
+
}
|
|
235
|
+
return zeroModeConfig.doneCriteria.map((criterion) => {
|
|
236
|
+
const phaseMatch = String(criterion).match(/^\s*\[(P[0-2])\]\s*/i);
|
|
237
|
+
const phase = phaseMatch ? phaseMatch[1].toUpperCase() : 'P0';
|
|
238
|
+
const text = phaseMatch ? criterion.slice(phaseMatch[0].length).trim() : criterion;
|
|
239
|
+
return {
|
|
240
|
+
id: slugify(`zm-${text}`),
|
|
241
|
+
text,
|
|
242
|
+
phase,
|
|
243
|
+
priority: phase,
|
|
244
|
+
checked: false,
|
|
245
|
+
source: 'doneCriteria'
|
|
246
|
+
};
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
|
|
186
250
|
function hasSubstantiveManagedBlock(parsedRoadmap) {
|
|
187
251
|
if (!parsedRoadmap || !parsedRoadmap.managedRange) {
|
|
188
252
|
return false;
|
|
@@ -203,8 +267,9 @@ function stripTrailingBlankLines(lines) {
|
|
|
203
267
|
return next;
|
|
204
268
|
}
|
|
205
269
|
|
|
206
|
-
function renderAdditionTask(task) {
|
|
207
|
-
|
|
270
|
+
function renderAdditionTask(task, planned = false) {
|
|
271
|
+
const flag = planned ? ' planned' : '';
|
|
272
|
+
return `- [ ] ${task.text} <!-- rs:task=${task.id}${flag} -->`;
|
|
208
273
|
}
|
|
209
274
|
|
|
210
275
|
function isGenericPreserveModeCandidate(candidate) {
|
|
@@ -215,6 +280,7 @@ function buildManagedAdditionsLines(tasks, options = {}) {
|
|
|
215
280
|
const groups = groupByPhase(tasks);
|
|
216
281
|
const lines = [];
|
|
217
282
|
const includeSectionHeading = options.includeSectionHeading !== false;
|
|
283
|
+
const plannedById = options.plannedById || {};
|
|
218
284
|
|
|
219
285
|
if (includeSectionHeading) {
|
|
220
286
|
lines.push(`## ${ADDITIONS_SECTION_TITLE}`);
|
|
@@ -227,7 +293,7 @@ function buildManagedAdditionsLines(tasks, options = {}) {
|
|
|
227
293
|
}
|
|
228
294
|
lines.push(`### Phase ${phase}`);
|
|
229
295
|
for (const task of groups[phase]) {
|
|
230
|
-
lines.push(renderAdditionTask(task));
|
|
296
|
+
lines.push(renderAdditionTask(task, Boolean(plannedById[task.id])));
|
|
231
297
|
}
|
|
232
298
|
lines.push('');
|
|
233
299
|
}
|
|
@@ -400,12 +466,13 @@ function findPhaseSectionRange(lines, managedRange, phase) {
|
|
|
400
466
|
};
|
|
401
467
|
}
|
|
402
468
|
|
|
403
|
-
function buildPreserveModeInsertions(parsedRoadmap, tasks) {
|
|
469
|
+
function buildPreserveModeInsertions(parsedRoadmap, tasks, plannedById = {}) {
|
|
404
470
|
const managedTasks = sortTasksByPhaseAndText(tasksInManagedBlock(parsedRoadmap));
|
|
405
471
|
const groups = groupByPhase(tasks);
|
|
406
472
|
const lines = parsedRoadmap.lines;
|
|
407
473
|
const insertions = [];
|
|
408
474
|
const fallbackTasks = [];
|
|
475
|
+
const renderTask = (t) => renderAdditionTask(t, Boolean(plannedById[t.id]));
|
|
409
476
|
|
|
410
477
|
for (const phase of PHASE_ORDER) {
|
|
411
478
|
const phaseTasks = sortTasksByPhaseAndText(groups[phase] || []);
|
|
@@ -423,7 +490,7 @@ function buildPreserveModeInsertions(parsedRoadmap, tasks) {
|
|
|
423
490
|
}, null);
|
|
424
491
|
insertions.push({
|
|
425
492
|
index: anchor.lastChildLineIndex + 1,
|
|
426
|
-
lines: phaseTasks.map(
|
|
493
|
+
lines: phaseTasks.map(renderTask)
|
|
427
494
|
});
|
|
428
495
|
continue;
|
|
429
496
|
}
|
|
@@ -436,7 +503,7 @@ function buildPreserveModeInsertions(parsedRoadmap, tasks) {
|
|
|
436
503
|
}
|
|
437
504
|
insertions.push({
|
|
438
505
|
index: insertionIndex,
|
|
439
|
-
lines: phaseTasks.map(
|
|
506
|
+
lines: phaseTasks.map(renderTask)
|
|
440
507
|
});
|
|
441
508
|
continue;
|
|
442
509
|
}
|
|
@@ -445,7 +512,7 @@ function buildPreserveModeInsertions(parsedRoadmap, tasks) {
|
|
|
445
512
|
}
|
|
446
513
|
|
|
447
514
|
if (fallbackTasks.length > 0) {
|
|
448
|
-
const fallbackLines = buildManagedAdditionsLines(fallbackTasks, { includeSectionHeading: true });
|
|
515
|
+
const fallbackLines = buildManagedAdditionsLines(fallbackTasks, { includeSectionHeading: true, plannedById });
|
|
449
516
|
insertions.push({
|
|
450
517
|
index: parsedRoadmap.managedRange.end,
|
|
451
518
|
lines: ['', ...fallbackLines]
|
|
@@ -455,13 +522,13 @@ function buildPreserveModeInsertions(parsedRoadmap, tasks) {
|
|
|
455
522
|
return insertions.sort((left, right) => right.index - left.index);
|
|
456
523
|
}
|
|
457
524
|
|
|
458
|
-
function insertPreserveModeTasks(existingContent, parsedRoadmap, tasks) {
|
|
525
|
+
function insertPreserveModeTasks(existingContent, parsedRoadmap, tasks, plannedById = {}) {
|
|
459
526
|
if (!parsedRoadmap || !parsedRoadmap.managedRange || tasks.length === 0) {
|
|
460
527
|
return existingContent;
|
|
461
528
|
}
|
|
462
529
|
|
|
463
530
|
const nextLines = parsedRoadmap.lines.slice();
|
|
464
|
-
const insertions = buildPreserveModeInsertions(parsedRoadmap, tasks);
|
|
531
|
+
const insertions = buildPreserveModeInsertions(parsedRoadmap, tasks, plannedById);
|
|
465
532
|
for (const insertion of insertions) {
|
|
466
533
|
nextLines.splice(insertion.index, 0, ...insertion.lines);
|
|
467
534
|
}
|
|
@@ -637,7 +704,7 @@ function buildSteps(phases, config) {
|
|
|
637
704
|
}));
|
|
638
705
|
}
|
|
639
706
|
|
|
640
|
-
function createModel(scan, tasks, config, customSections, checkedById) {
|
|
707
|
+
function createModel(scan, tasks, config, customSections, checkedById, plannedById = {}) {
|
|
641
708
|
const phases = groupByPhase(tasks);
|
|
642
709
|
|
|
643
710
|
const implemented = [
|
|
@@ -728,7 +795,9 @@ function createModel(scan, tasks, config, customSections, checkedById) {
|
|
|
728
795
|
successCriteria,
|
|
729
796
|
customSections,
|
|
730
797
|
customPhases: config.customPhases || [],
|
|
731
|
-
|
|
798
|
+
moduleMetadata: config.moduleMetadata || {},
|
|
799
|
+
checkedById,
|
|
800
|
+
plannedById
|
|
732
801
|
});
|
|
733
802
|
}
|
|
734
803
|
|
|
@@ -757,8 +826,10 @@ function generateRoadmapDocument(options) {
|
|
|
757
826
|
const scan = scanProject(projectRoot);
|
|
758
827
|
const existing = parseRoadmap(existingContent);
|
|
759
828
|
const existingCheckedById = {};
|
|
829
|
+
const existingPlannedById = {};
|
|
760
830
|
for (const task of existing.tasks) {
|
|
761
831
|
existingCheckedById[task.id] = task.checked;
|
|
832
|
+
if (task.planned) existingPlannedById[task.id] = true;
|
|
762
833
|
}
|
|
763
834
|
const existingManagedTasks = tasksInManagedBlock(existing);
|
|
764
835
|
|
|
@@ -814,7 +885,14 @@ function generateRoadmapDocument(options) {
|
|
|
814
885
|
|
|
815
886
|
const baseCandidates = buildDefaultCandidates(scan, config);
|
|
816
887
|
const matcherCandidates = applyTaskMatchers(scan, config);
|
|
817
|
-
const
|
|
888
|
+
const doneCriteriaCandidates = buildDoneCriteriaCandidates(zeroModeConfig);
|
|
889
|
+
const allCandidates = dedupeTasks([...doneCriteriaCandidates, ...baseCandidates, ...matcherCandidates, ...pluginTaskCandidates]);
|
|
890
|
+
|
|
891
|
+
const plannedById = { ...existingPlannedById };
|
|
892
|
+
for (const candidate of doneCriteriaCandidates) {
|
|
893
|
+
const isNew = !findBestTaskMatch(candidate, existingManagedTasks, { allowFuzzy: true });
|
|
894
|
+
if (isNew) plannedById[candidate.id] = true;
|
|
895
|
+
}
|
|
818
896
|
|
|
819
897
|
if (hasSubstantiveManagedBlock(existing) && preserveManagedBlock && !forceFullRegenerate) {
|
|
820
898
|
const unmatchedCandidates = allCandidates.filter((candidate) => {
|
|
@@ -829,7 +907,7 @@ function generateRoadmapDocument(options) {
|
|
|
829
907
|
return existingContent;
|
|
830
908
|
}
|
|
831
909
|
|
|
832
|
-
return insertPreserveModeTasks(existingContent, existing, preserveModeCandidates);
|
|
910
|
+
return insertPreserveModeTasks(existingContent, existing, preserveModeCandidates, plannedById);
|
|
833
911
|
}
|
|
834
912
|
|
|
835
913
|
if (hasSubstantiveManagedBlock(existing) && !forceFullRegenerate) {
|
|
@@ -840,7 +918,7 @@ function generateRoadmapDocument(options) {
|
|
|
840
918
|
allowFuzzy: true,
|
|
841
919
|
includeUnmatchedExisting: false
|
|
842
920
|
});
|
|
843
|
-
const model = createModel(scan, merged, config, [profileSection, ...generatedZeroModeSection, ...configSections, ...pluginSections], existingCheckedById);
|
|
921
|
+
const model = createModel(scan, merged, config, [profileSection, ...generatedZeroModeSection, ...configSections, ...pluginSections], existingCheckedById, plannedById);
|
|
844
922
|
const profile = config.roadmapProfile || 'compact';
|
|
845
923
|
const managedBody = renderBody(model, profile);
|
|
846
924
|
|
package/src/host.js
CHANGED
|
@@ -1318,7 +1318,10 @@ function inspectHostSetup(projectRoot, options = {}) {
|
|
|
1318
1318
|
const agentsFile = options.agentsFile;
|
|
1319
1319
|
const runtime = detectNodeRuntime(options.env || process.env);
|
|
1320
1320
|
const cli = detectCliResolution(projectRoot, { currentCliPath: options.currentCliPath });
|
|
1321
|
-
const
|
|
1321
|
+
const skipVscode = Boolean(options.skipVscode);
|
|
1322
|
+
const vscode = skipVscode
|
|
1323
|
+
? { skipped: true, launcher: { path: null, exists: false }, wrappers: { expectedCount: 2, presentCount: 0, ready: false, windows: { path: null, exists: false }, posix: { path: null, exists: false }, missingPaths: [] }, tasks: { path: null, exists: false, ready: false, expectedLabels: [], advancedLabels: [], presentLabels: [], missingLabels: [], missingAdvancedLabels: [] } }
|
|
1324
|
+
: inspectVsCodeTasks(projectRoot);
|
|
1322
1325
|
const claude = inspectClaudeSetup(projectRoot);
|
|
1323
1326
|
const bundle = inspectSharedBundleSurface();
|
|
1324
1327
|
const codexNative = inspectCodexPluginState(projectRoot, options);
|
package/src/model.js
CHANGED
|
@@ -23,7 +23,8 @@ function createRoadmapModel(input) {
|
|
|
23
23
|
successCriteria: input.successCriteria || [],
|
|
24
24
|
customSections: input.customSections || [],
|
|
25
25
|
customPhases: input.customPhases || [],
|
|
26
|
-
checkedById: input.checkedById || {}
|
|
26
|
+
checkedById: input.checkedById || {},
|
|
27
|
+
plannedById: input.plannedById || {}
|
|
27
28
|
};
|
|
28
29
|
}
|
|
29
30
|
|
package/src/parser/index.js
CHANGED
|
@@ -147,6 +147,7 @@ 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 isPlanned = /\bplanned\b/i.test(markerFlags);
|
|
150
151
|
const kindMatch = markerFlags.match(/\brs:kind=(\S+)/i);
|
|
151
152
|
const taskKind = kindMatch ? kindMatch[1].toLowerCase() : null;
|
|
152
153
|
const verifiedByMatch = markerFlags.match(/\brs:verified-by=(\S+)/i);
|
|
@@ -247,6 +248,7 @@ function parseRoadmap(content) {
|
|
|
247
248
|
blockedByIds,
|
|
248
249
|
markerId,
|
|
249
250
|
noTest,
|
|
251
|
+
planned: isPlanned,
|
|
250
252
|
kind: taskKind,
|
|
251
253
|
verifiedBy: taskVerifiedBy,
|
|
252
254
|
indent,
|
package/src/renderer/compact.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const { slugify, ensureTrailingNewline } = require('../utils');
|
|
4
|
-
const { taskLine, checkedState } = require('./helpers');
|
|
4
|
+
const { taskLine, checkedState, plannedState } = require('./helpers');
|
|
5
5
|
|
|
6
6
|
function renderCompact(model) {
|
|
7
7
|
const lines = [];
|
|
@@ -22,17 +22,17 @@ function renderCompact(model) {
|
|
|
22
22
|
lines.push('');
|
|
23
23
|
lines.push('### Phase P0 (Critical)');
|
|
24
24
|
for (const task of model.phases.P0) {
|
|
25
|
-
lines.push(taskLine(task));
|
|
25
|
+
lines.push(taskLine(task, plannedState(model, task.id)));
|
|
26
26
|
}
|
|
27
27
|
lines.push('');
|
|
28
28
|
lines.push('### Phase P1 (Important)');
|
|
29
29
|
for (const task of model.phases.P1) {
|
|
30
|
-
lines.push(taskLine(task));
|
|
30
|
+
lines.push(taskLine(task, plannedState(model, task.id)));
|
|
31
31
|
}
|
|
32
32
|
lines.push('');
|
|
33
33
|
lines.push('### Phase P2 (Optimization)');
|
|
34
34
|
for (const task of model.phases.P2) {
|
|
35
|
-
lines.push(taskLine(task));
|
|
35
|
+
lines.push(taskLine(task, plannedState(model, task.id)));
|
|
36
36
|
}
|
|
37
37
|
lines.push('');
|
|
38
38
|
|
package/src/renderer/helpers.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
function taskLine(task) {
|
|
4
|
-
|
|
3
|
+
function taskLine(task, planned = false) {
|
|
4
|
+
const flag = planned ? ' planned' : '';
|
|
5
|
+
return `- [${task.checked ? 'x' : ' '}] ${task.text} <!-- rs:task=${task.id}${flag} -->`;
|
|
5
6
|
}
|
|
6
7
|
|
|
7
8
|
function sectionHeader(n, title) {
|
|
@@ -12,8 +13,12 @@ function checkedState(model, id) {
|
|
|
12
13
|
return Boolean(model.checkedById && model.checkedById[id]);
|
|
13
14
|
}
|
|
14
15
|
|
|
16
|
+
function plannedState(model, id) {
|
|
17
|
+
return Boolean(model.plannedById && model.plannedById[id]);
|
|
18
|
+
}
|
|
19
|
+
|
|
15
20
|
function priorityLabel(priority) {
|
|
16
21
|
return priority ? `\`[${priority}]\`` : '';
|
|
17
22
|
}
|
|
18
23
|
|
|
19
|
-
module.exports = { taskLine, sectionHeader, checkedState, priorityLabel };
|
|
24
|
+
module.exports = { taskLine, sectionHeader, checkedState, plannedState, priorityLabel };
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const { slugify, ensureTrailingNewline } = require('../utils');
|
|
4
|
-
const { sectionHeader, checkedState, priorityLabel } = require('./helpers');
|
|
4
|
+
const { sectionHeader, checkedState, plannedState, priorityLabel } = require('./helpers');
|
|
5
5
|
|
|
6
6
|
function taskLineWithPriority(task, model) {
|
|
7
7
|
const pri = task.priority ? `${priorityLabel(task.priority)} ` : '';
|
|
8
8
|
const id = task.id || `prof-task-${slugify(task.text || String(task))}`;
|
|
9
9
|
const text = task.text || String(task);
|
|
10
10
|
const checked = task.checked || checkedState(model, id);
|
|
11
|
-
|
|
11
|
+
const plannedFlag = plannedState(model, id) ? ' planned' : '';
|
|
12
|
+
return `- [${checked ? 'x' : ' '}] ${pri}${text} <!-- rs:task=${id}${plannedFlag} -->`;
|
|
12
13
|
}
|
|
13
14
|
|
|
14
15
|
function exitLine(item, phN, stN, model) {
|
|
@@ -203,70 +204,6 @@ function renderSection5Milestones(model, lines) {
|
|
|
203
204
|
}
|
|
204
205
|
}
|
|
205
206
|
|
|
206
|
-
const MODULE_METADATA = {
|
|
207
|
-
generator: {
|
|
208
|
-
state: 'Compact and professional profiles supported; Phase→Step→Task model implemented.',
|
|
209
|
-
tasks: [
|
|
210
|
-
{ text: 'Improve Phase→Step→Task model inference quality', priority: 'P0', id: 'prof-mat-generator-improve-phase-step-task-inference' },
|
|
211
|
-
{ text: 'Add scan-driven task suggestions per detected module', priority: 'P1', id: 'prof-mat-generator-scan-driven-task-suggestions' }
|
|
212
|
-
]
|
|
213
|
-
},
|
|
214
|
-
parser: {
|
|
215
|
-
state: 'Parses managed blocks, rs:task IDs, and checked state.',
|
|
216
|
-
tasks: [
|
|
217
|
-
{ text: 'Add parser validation for Phase→Step hierarchy markers', priority: 'P1', id: 'prof-mat-parser-phase-hierarchy-validation' },
|
|
218
|
-
{ text: 'Improve section boundary detection for professional format', priority: 'P1', id: 'prof-mat-parser-professional-section-detection' }
|
|
219
|
-
]
|
|
220
|
-
},
|
|
221
|
-
renderer: {
|
|
222
|
-
state: 'Dispatcher supports compact, professional, and enterprise (error) profiles.',
|
|
223
|
-
tasks: [
|
|
224
|
-
{ text: 'Add snapshot regression fixtures for compact and professional', priority: 'P0', id: 'prof-mat-renderer-snapshot-regression-fixtures' },
|
|
225
|
-
{ text: 'Harden priority label rendering for edge cases', priority: 'P1', id: 'prof-mat-renderer-priority-label-edge-cases' }
|
|
226
|
-
]
|
|
227
|
-
},
|
|
228
|
-
validator: {
|
|
229
|
-
state: 'Evidence-based validation against file, symbol, and test presence.',
|
|
230
|
-
tasks: [
|
|
231
|
-
{ text: 'Extend validator to verify Phase→Step→Task IDs survive sync', priority: 'P1', id: 'prof-mat-validator-phase-step-task-id-sync' },
|
|
232
|
-
{ text: 'Add validation coverage for professional profile task IDs', priority: 'P1', id: 'prof-mat-validator-professional-task-id-coverage' }
|
|
233
|
-
]
|
|
234
|
-
},
|
|
235
|
-
match: {
|
|
236
|
-
state: 'Task similarity matching with edit-distance threshold.',
|
|
237
|
-
tasks: [
|
|
238
|
-
{ text: 'Tune similarity threshold to reduce false-positive merges', priority: 'P0', id: 'prof-mat-match-tune-similarity-threshold' }
|
|
239
|
-
]
|
|
240
|
-
},
|
|
241
|
-
sync: {
|
|
242
|
-
state: 'Applies validation outcomes to ROADMAP.md and can append warning lines for failed attempts.',
|
|
243
|
-
tasks: [
|
|
244
|
-
{ text: 'Define explicit contract for sync, sync --audit, and future promote-only flows', priority: 'P0', id: 'prof-mat-sync-define-command-contract' },
|
|
245
|
-
{ text: 'Separate mutating sync behavior from future read-only audit mode', priority: 'P0', id: 'prof-mat-sync-separate-mutation-from-read-only-audit' },
|
|
246
|
-
{ text: 'Expose weak-evidence, documentation-only, and structural-mismatch findings in audit output', priority: 'P1', id: 'prof-mat-sync-expose-rich-audit-findings' },
|
|
247
|
-
{ text: 'Claude PostToolUse hook must invoke the CLI without relying on bare "node" in PATH', priority: 'P0', id: 'prof-mat-sync-claude-hook-avoid-bare-node-path' },
|
|
248
|
-
{ text: 'Claude PostToolUse hook must fail visibly when sync execution fails', priority: 'P0', id: 'prof-mat-sync-claude-hook-fail-visibly-on-sync-error' },
|
|
249
|
-
{ text: 'Claude PostToolUse hook must keep lock-file cleanup on both success and failure', priority: 'P1', id: 'prof-mat-sync-claude-hook-cleanup-lockfile-on-both-paths' },
|
|
250
|
-
{ text: 'Differentiate write-time hook sync from commit-time pre-commit sync in the command contract', priority: 'P1', id: 'prof-mat-sync-differentiate-write-time-and-pre-commit-sync' }
|
|
251
|
-
]
|
|
252
|
-
},
|
|
253
|
-
config: {
|
|
254
|
-
state: 'Supports roadmapProfile, product block, milestones, phaseTemplates, plugins.',
|
|
255
|
-
tasks: [
|
|
256
|
-
{ text: 'Add JSON schema validation for roadmap-skill.config.json', priority: 'P1', id: 'prof-mat-config-json-schema-validation' },
|
|
257
|
-
{ text: 'Add init --professional or init --with-config bootstrap flow', priority: 'P0', id: 'prof-mat-config-add-init-with-config-bootstrap-flow' },
|
|
258
|
-
{ text: 'Honor versioned roadmap config instead of regenerating from defaults', priority: 'P1', id: 'prof-mat-config-honor-versioned-config-before-defaults' },
|
|
259
|
-
{ text: 'Define manual-to-managed migration flow and drift warnings between skill and CLI guidance', priority: 'P1', id: 'prof-mat-config-define-manual-to-managed-migration-and-drift-warnings' }
|
|
260
|
-
]
|
|
261
|
-
},
|
|
262
|
-
io: {
|
|
263
|
-
state: 'Scans files, detects languages, test frameworks, commands, modules.',
|
|
264
|
-
tasks: [
|
|
265
|
-
{ text: 'Improve module detection for monorepo workspace layouts', priority: 'P2', id: 'prof-mat-io-monorepo-workspace-detection' }
|
|
266
|
-
]
|
|
267
|
-
}
|
|
268
|
-
};
|
|
269
|
-
|
|
270
207
|
function renderSection6MaturityPath(model, lines) {
|
|
271
208
|
lines.push(sectionHeader(6, 'Command-by-Command / Module-by-Module Maturity Path'));
|
|
272
209
|
lines.push('');
|
|
@@ -275,31 +212,40 @@ function renderSection6MaturityPath(model, lines) {
|
|
|
275
212
|
|
|
276
213
|
if (allAreas.length === 0) {
|
|
277
214
|
const id = 'prof-mat-identify-boundaries';
|
|
278
|
-
|
|
215
|
+
const implSummary = (model.currentState && model.currentState.implementedSummary) || '';
|
|
216
|
+
const hasDetectedFiles = /^[1-9]/.test(implSummary);
|
|
217
|
+
const taskText = hasDetectedFiles
|
|
218
|
+
? `Define module boundaries (scanner detected files but no top-level structure)`
|
|
219
|
+
: `Identify command/module boundaries for the next increment`;
|
|
220
|
+
lines.push(`- [${checkedState(model, id) ? 'x' : ' '}] \`[P1]\` ${taskText} <!-- rs:task=${id} -->`);
|
|
279
221
|
lines.push('');
|
|
280
222
|
return;
|
|
281
223
|
}
|
|
282
224
|
|
|
225
|
+
const moduleMetadata = (model.moduleMetadata && typeof model.moduleMetadata === 'object') ? model.moduleMetadata : {};
|
|
226
|
+
|
|
283
227
|
for (const area of allAreas) {
|
|
284
228
|
const rawName = area.replace(/^(Module:|Command:)\s*/i, '').trim();
|
|
285
|
-
const meta =
|
|
229
|
+
const meta = moduleMetadata[rawName.toLowerCase()];
|
|
286
230
|
const displayName = rawName;
|
|
287
231
|
|
|
288
232
|
lines.push(`### ${displayName}`);
|
|
289
233
|
lines.push('');
|
|
290
|
-
if (meta) {
|
|
234
|
+
if (meta && typeof meta === 'object') {
|
|
291
235
|
lines.push(`**Current state:** ${meta.state}`);
|
|
292
236
|
lines.push('');
|
|
293
|
-
for (const task of meta.tasks) {
|
|
237
|
+
for (const task of (Array.isArray(meta.tasks) ? meta.tasks : [])) {
|
|
294
238
|
lines.push(`- [${checkedState(model, task.id) ? 'x' : ' '}] ${priorityLabel(task.priority)} ${task.text} <!-- rs:task=${task.id} -->`);
|
|
295
239
|
}
|
|
296
240
|
} else {
|
|
297
241
|
const isCommand = /^Command:/i.test(area);
|
|
298
242
|
const kind = isCommand ? 'command' : 'module';
|
|
299
|
-
const
|
|
243
|
+
const docId = `prof-mat-${slugify(rawName)}-document-api`;
|
|
244
|
+
const testId = `prof-mat-${slugify(rawName)}-add-test-coverage`;
|
|
300
245
|
lines.push(`**Current state:** ${kind} detected in scan.`);
|
|
301
246
|
lines.push('');
|
|
302
|
-
lines.push(`- [${checkedState(model,
|
|
247
|
+
lines.push(`- [${checkedState(model, docId) ? 'x' : ' '}] \`[P1]\` Document ${displayName} public API <!-- rs:task=${docId} -->`);
|
|
248
|
+
lines.push(`- [${checkedState(model, testId) ? 'x' : ' '}] \`[P1]\` Add test coverage for ${displayName} <!-- rs:task=${testId} -->`);
|
|
303
249
|
}
|
|
304
250
|
lines.push('');
|
|
305
251
|
}
|
package/src/utils.js
CHANGED
package/src/validator/index.js
CHANGED
|
@@ -450,11 +450,7 @@ function normalizePathCandidateToken(rawToken) {
|
|
|
450
450
|
if (!stripped) {
|
|
451
451
|
return '';
|
|
452
452
|
}
|
|
453
|
-
|
|
454
|
-
if (/^~\//.test(normalized)) {
|
|
455
|
-
return normalized;
|
|
456
|
-
}
|
|
457
|
-
return normalized.replace(/^~(?=\/)/, '~');
|
|
453
|
+
return stripped.replace(/\\/g, '/');
|
|
458
454
|
}
|
|
459
455
|
|
|
460
456
|
function isExternalPathToken(token) {
|
|
@@ -1860,6 +1856,22 @@ function buildDiscoveredEvidenceLine(evidence) {
|
|
|
1860
1856
|
}
|
|
1861
1857
|
|
|
1862
1858
|
function validateTask(task, context, config, plugins) {
|
|
1859
|
+
if (task.planned) {
|
|
1860
|
+
return {
|
|
1861
|
+
passed: true,
|
|
1862
|
+
planned: true,
|
|
1863
|
+
confidence: 'low',
|
|
1864
|
+
attempted: false,
|
|
1865
|
+
reasons: [],
|
|
1866
|
+
evidence: { code: false, test: false, artifact: false, files: [], codeFiles: [], testFiles: [], weakPathFiles: [], weakPathContentTokens: [], artifactFiles: [], heuristicArtifacts: [], symbols: [], structuralEvidence: null, authoritative: false, authoritativeFiles: [], authoritativeSummaries: [] },
|
|
1867
|
+
diagnostics: [],
|
|
1868
|
+
verificationRecipe: null,
|
|
1869
|
+
staleEvidenceDetected: false,
|
|
1870
|
+
staleEvidenceResolved: false,
|
|
1871
|
+
generatedTestEvidence: null
|
|
1872
|
+
};
|
|
1873
|
+
}
|
|
1874
|
+
|
|
1863
1875
|
if (task.verifiedBy === 'human') {
|
|
1864
1876
|
const hasEvidenceLine = Array.isArray(task.evidenceLines) &&
|
|
1865
1877
|
task.evidenceLines.some((e) => e.text && e.text.trim().length > 0);
|