roadmapsmith 0.1.0
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 +219 -0
- package/bin/cli.js +212 -0
- package/package.json +41 -0
- package/src/config.js +188 -0
- package/src/generator/index.js +436 -0
- package/src/index.js +8 -0
- package/src/io.js +228 -0
- package/src/match.js +86 -0
- package/src/model.js +28 -0
- package/src/parser/index.js +109 -0
- package/src/sync/index.js +59 -0
- package/src/templates/index.js +31 -0
- package/src/utils.js +143 -0
- package/src/validator/index.js +401 -0
- package/templates/agents.template.md +22 -0
- package/templates/roadmap.template.md +50 -0
|
@@ -0,0 +1,401 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const { walkFiles, detectTestFrameworks } = require('../io');
|
|
6
|
+
const { collectPluginContributions } = require('../config');
|
|
7
|
+
const { escapeRegExp, tokenize } = require('../utils');
|
|
8
|
+
|
|
9
|
+
const CODE_EXTENSIONS = new Set([
|
|
10
|
+
'.js', '.cjs', '.mjs', '.ts', '.tsx', '.jsx', '.py', '.go', '.rs', '.java', '.kt', '.swift', '.rb', '.php', '.cs'
|
|
11
|
+
]);
|
|
12
|
+
|
|
13
|
+
const DOC_HINTS = ['readme', 'changelog', 'docs', 'documentation', 'spec', 'diagram', 'runbook'];
|
|
14
|
+
const CODE_HINTS = ['implement', 'add', 'create', 'build', 'refactor', 'fix', 'module', 'function', 'api', 'endpoint', 'command'];
|
|
15
|
+
const GENERIC_TASK_TOKENS = new Set([
|
|
16
|
+
'implement',
|
|
17
|
+
'implementation',
|
|
18
|
+
'module',
|
|
19
|
+
'function',
|
|
20
|
+
'class',
|
|
21
|
+
'method',
|
|
22
|
+
'command',
|
|
23
|
+
'create',
|
|
24
|
+
'add',
|
|
25
|
+
'build',
|
|
26
|
+
'refactor',
|
|
27
|
+
'fix',
|
|
28
|
+
'test',
|
|
29
|
+
'tests'
|
|
30
|
+
]);
|
|
31
|
+
|
|
32
|
+
function readFileIndex(projectRoot, files) {
|
|
33
|
+
const index = [];
|
|
34
|
+
for (const relativePath of files) {
|
|
35
|
+
const absolutePath = path.resolve(projectRoot, relativePath);
|
|
36
|
+
const ext = path.extname(relativePath).toLowerCase();
|
|
37
|
+
let content = '';
|
|
38
|
+
try {
|
|
39
|
+
const buffer = fs.readFileSync(absolutePath);
|
|
40
|
+
if (buffer.length > 512 * 1024) {
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
content = buffer.toString('utf8');
|
|
44
|
+
} catch {
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
index.push({
|
|
49
|
+
relativePath,
|
|
50
|
+
absolutePath,
|
|
51
|
+
ext,
|
|
52
|
+
content,
|
|
53
|
+
isTestFile: /(^|\/)(__tests__|tests)\//.test(relativePath) || /\.test\.|\.spec\.|_test\.go$/.test(relativePath)
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
return index;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function extractExplicitPaths(text) {
|
|
60
|
+
const results = new Set();
|
|
61
|
+
const quoted = String(text).match(/`([^`]+)`/g) || [];
|
|
62
|
+
for (const token of quoted) {
|
|
63
|
+
const clean = token.slice(1, -1);
|
|
64
|
+
if (clean.includes('/') || clean.includes('\\') || clean.includes('.')) {
|
|
65
|
+
results.add(clean);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const pathTokens = String(text).match(/([A-Za-z0-9_.-]+\/[A-Za-z0-9_./-]+)/g) || [];
|
|
70
|
+
for (const token of pathTokens) {
|
|
71
|
+
results.add(token);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return Array.from(results).sort((left, right) => left.localeCompare(right));
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function extractSymbolHints(text) {
|
|
78
|
+
const symbols = new Set();
|
|
79
|
+
const patterns = [
|
|
80
|
+
/(?:function|class|method|command)\s+([A-Za-z_][A-Za-z0-9_]*)/gi,
|
|
81
|
+
/(?:function|module|class|command|method)\s+`([A-Za-z_][A-Za-z0-9_-]*)`/gi,
|
|
82
|
+
/`([A-Za-z_][A-Za-z0-9_-]*)`\s+(?:function|module|class|command|method)/gi
|
|
83
|
+
];
|
|
84
|
+
|
|
85
|
+
for (const pattern of patterns) {
|
|
86
|
+
let match = pattern.exec(text);
|
|
87
|
+
while (match) {
|
|
88
|
+
symbols.add(match[1]);
|
|
89
|
+
match = pattern.exec(text);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return Array.from(symbols).sort((left, right) => left.localeCompare(right));
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function isCodeTask(taskText) {
|
|
97
|
+
const normalized = String(taskText).toLowerCase();
|
|
98
|
+
return CODE_HINTS.some((hint) => normalized.includes(hint));
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function isDocTask(taskText) {
|
|
102
|
+
const normalized = String(taskText).toLowerCase();
|
|
103
|
+
return DOC_HINTS.some((hint) => normalized.includes(hint));
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function findFilesByPathHints(pathHints, fileIndex) {
|
|
107
|
+
const matches = [];
|
|
108
|
+
for (const hint of pathHints) {
|
|
109
|
+
const normalizedHint = hint.replace(/\\/g, '/');
|
|
110
|
+
const direct = fileIndex.find((file) => file.relativePath === normalizedHint);
|
|
111
|
+
if (direct) {
|
|
112
|
+
matches.push(direct.relativePath);
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
for (const file of fileIndex) {
|
|
117
|
+
if (file.relativePath.endsWith(normalizedHint)) {
|
|
118
|
+
matches.push(file.relativePath);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return Array.from(new Set(matches)).sort((left, right) => left.localeCompare(right));
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function findFilesBySymbols(symbolHints, fileIndex) {
|
|
126
|
+
const matches = new Set();
|
|
127
|
+
for (const symbol of symbolHints) {
|
|
128
|
+
const regex = new RegExp(`\\b${escapeRegExp(symbol)}\\b`, 'i');
|
|
129
|
+
for (const file of fileIndex) {
|
|
130
|
+
if (!CODE_EXTENSIONS.has(file.ext)) {
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
if (regex.test(file.content)) {
|
|
134
|
+
matches.add(file.relativePath);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return Array.from(matches).sort((left, right) => left.localeCompare(right));
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function findCodeEvidence(taskText, fileIndex) {
|
|
142
|
+
const tokens = tokenize(taskText)
|
|
143
|
+
.filter((token) => token.length >= 3 && !GENERIC_TASK_TOKENS.has(token))
|
|
144
|
+
.slice(0, 8);
|
|
145
|
+
if (tokens.length === 0) {
|
|
146
|
+
return [];
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const matches = [];
|
|
150
|
+
for (const file of fileIndex) {
|
|
151
|
+
if (!CODE_EXTENSIONS.has(file.ext) || file.isTestFile) {
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
let score = 0;
|
|
156
|
+
const lowered = file.content.toLowerCase();
|
|
157
|
+
for (const token of tokens) {
|
|
158
|
+
if (token.length < 3) {
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
if (lowered.includes(token)) {
|
|
162
|
+
score += 1;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const threshold = tokens.length === 1 ? 1 : 2;
|
|
167
|
+
if (score >= threshold) {
|
|
168
|
+
matches.push(file.relativePath);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return matches.slice(0, 20);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function findTestEvidence(taskText, fileIndex) {
|
|
176
|
+
const tokens = tokenize(taskText)
|
|
177
|
+
.filter((token) => token.length >= 3 && !GENERIC_TASK_TOKENS.has(token))
|
|
178
|
+
.slice(0, 8);
|
|
179
|
+
const matches = [];
|
|
180
|
+
|
|
181
|
+
for (const file of fileIndex) {
|
|
182
|
+
if (!file.isTestFile) {
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
const lowered = file.content.toLowerCase();
|
|
186
|
+
const hasMatch = tokens.some((token) => lowered.includes(token));
|
|
187
|
+
if (hasMatch) {
|
|
188
|
+
matches.push(file.relativePath);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return matches.slice(0, 20);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function findArtifactEvidence(taskText, fileIndex) {
|
|
196
|
+
const normalized = String(taskText).toLowerCase();
|
|
197
|
+
const matches = [];
|
|
198
|
+
|
|
199
|
+
if (!isDocTask(taskText) && !normalized.includes('artifact') && !normalized.includes('release')) {
|
|
200
|
+
return matches;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const artifactPatterns = [
|
|
204
|
+
/^README\.md$/i,
|
|
205
|
+
/^CHANGELOG\.md$/i,
|
|
206
|
+
/^docs\//i,
|
|
207
|
+
/^artifacts\//i,
|
|
208
|
+
/^dist\//i,
|
|
209
|
+
/^build\//i
|
|
210
|
+
];
|
|
211
|
+
|
|
212
|
+
for (const file of fileIndex) {
|
|
213
|
+
if (artifactPatterns.some((pattern) => pattern.test(file.relativePath))) {
|
|
214
|
+
matches.push(file.relativePath);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
return matches.slice(0, 20);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function evaluateRule(rule, task, context) {
|
|
222
|
+
if (!rule) {
|
|
223
|
+
return { passed: true, reasons: [], evidence: {} };
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
if (rule.when) {
|
|
227
|
+
const regexp = new RegExp(rule.when, 'i');
|
|
228
|
+
if (!regexp.test(task.text)) {
|
|
229
|
+
return { passed: true, reasons: [], evidence: {} };
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
if (typeof rule.check === 'function') {
|
|
234
|
+
const custom = rule.check(task, context);
|
|
235
|
+
if (!custom) {
|
|
236
|
+
return { passed: true, reasons: [], evidence: {} };
|
|
237
|
+
}
|
|
238
|
+
return {
|
|
239
|
+
passed: custom.passed !== false,
|
|
240
|
+
reasons: Array.isArray(custom.reasons) ? custom.reasons : [],
|
|
241
|
+
evidence: custom.evidence || {}
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const reasons = [];
|
|
246
|
+
const evidence = {};
|
|
247
|
+
|
|
248
|
+
if (rule.type === 'file-exists' && rule.path) {
|
|
249
|
+
const hit = context.fileIndex.find((file) => file.relativePath === rule.path || file.relativePath.endsWith(rule.path));
|
|
250
|
+
if (!hit) {
|
|
251
|
+
reasons.push(rule.message || `missing file: ${rule.path}`);
|
|
252
|
+
} else {
|
|
253
|
+
evidence.file = hit.relativePath;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
if (rule.type === 'symbol' && rule.pattern) {
|
|
258
|
+
const regex = new RegExp(rule.pattern, 'i');
|
|
259
|
+
const hit = context.fileIndex.find((file) => regex.test(file.content));
|
|
260
|
+
if (!hit) {
|
|
261
|
+
reasons.push(rule.message || `missing symbol pattern: ${rule.pattern}`);
|
|
262
|
+
} else {
|
|
263
|
+
evidence.symbol = hit.relativePath;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
if (rule.type === 'artifact' && rule.path) {
|
|
268
|
+
const hit = context.fileIndex.find((file) => file.relativePath.startsWith(rule.path) || file.relativePath === rule.path);
|
|
269
|
+
if (!hit) {
|
|
270
|
+
reasons.push(rule.message || `missing artifact: ${rule.path}`);
|
|
271
|
+
} else {
|
|
272
|
+
evidence.artifact = hit.relativePath;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
if (rule.type === 'test' && context.testFrameworks.length === 0) {
|
|
277
|
+
reasons.push(rule.message || 'test framework not detected');
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
return {
|
|
281
|
+
passed: reasons.length === 0,
|
|
282
|
+
reasons,
|
|
283
|
+
evidence
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
function buildValidationContext(projectRoot, config, plugins) {
|
|
288
|
+
const files = walkFiles(projectRoot);
|
|
289
|
+
const fileIndex = readFileIndex(projectRoot, files);
|
|
290
|
+
const testFrameworks = detectTestFrameworks(projectRoot, files);
|
|
291
|
+
|
|
292
|
+
return {
|
|
293
|
+
projectRoot,
|
|
294
|
+
config,
|
|
295
|
+
plugins,
|
|
296
|
+
files,
|
|
297
|
+
fileIndex,
|
|
298
|
+
testFrameworks
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
function validateTask(task, context, config, plugins) {
|
|
303
|
+
const pathHints = extractExplicitPaths(task.text);
|
|
304
|
+
const symbolHints = extractSymbolHints(task.text);
|
|
305
|
+
|
|
306
|
+
const filesFromPaths = findFilesByPathHints(pathHints, context.fileIndex);
|
|
307
|
+
const filesFromSymbols = findFilesBySymbols(symbolHints, context.fileIndex);
|
|
308
|
+
const filesFromCode = findCodeEvidence(task.text, context.fileIndex);
|
|
309
|
+
const filesFromTests = findTestEvidence(task.text, context.fileIndex);
|
|
310
|
+
const filesFromArtifacts = findArtifactEvidence(task.text, context.fileIndex);
|
|
311
|
+
|
|
312
|
+
const evidence = {
|
|
313
|
+
code: filesFromCode.length > 0 || filesFromSymbols.length > 0,
|
|
314
|
+
test: filesFromTests.length > 0,
|
|
315
|
+
artifact: filesFromArtifacts.length > 0,
|
|
316
|
+
files: filesFromPaths,
|
|
317
|
+
symbols: filesFromSymbols,
|
|
318
|
+
codeFiles: filesFromCode,
|
|
319
|
+
testFiles: filesFromTests,
|
|
320
|
+
artifactFiles: filesFromArtifacts
|
|
321
|
+
};
|
|
322
|
+
|
|
323
|
+
const reasons = [];
|
|
324
|
+
if (pathHints.length > 0 && filesFromPaths.length === 0) {
|
|
325
|
+
reasons.push(`missing referenced file(s): ${pathHints.join(', ')}`);
|
|
326
|
+
}
|
|
327
|
+
if (symbolHints.length > 0 && filesFromSymbols.length === 0) {
|
|
328
|
+
reasons.push(`missing symbol(s): ${symbolHints.join(', ')}`);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
const hasEvidence = evidence.code || evidence.test || evidence.artifact || evidence.files.length > 0;
|
|
332
|
+
if (!hasEvidence) {
|
|
333
|
+
reasons.push('no code, test, or artifact evidence found');
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
const requiresTest = context.testFrameworks.length > 0 && isCodeTask(task.text) && !isDocTask(task.text);
|
|
337
|
+
if (requiresTest && !evidence.test) {
|
|
338
|
+
reasons.push('missing test evidence');
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
const configuredRules = Array.isArray(config.validators) ? config.validators : [];
|
|
342
|
+
const pluginRules = collectPluginContributions(plugins || [], 'registerValidators', context);
|
|
343
|
+
for (const rule of [...configuredRules, ...pluginRules]) {
|
|
344
|
+
const ruleResult = evaluateRule(rule, task, context);
|
|
345
|
+
if (!ruleResult.passed) {
|
|
346
|
+
reasons.push(...ruleResult.reasons);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
const uniqueReasons = Array.from(new Set(reasons));
|
|
351
|
+
|
|
352
|
+
return {
|
|
353
|
+
taskId: task.id,
|
|
354
|
+
passed: uniqueReasons.length === 0,
|
|
355
|
+
reasons: uniqueReasons,
|
|
356
|
+
evidence,
|
|
357
|
+
requiresTest,
|
|
358
|
+
hasEvidence,
|
|
359
|
+
attempted: hasEvidence || pathHints.length > 0 || symbolHints.length > 0
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
function validateTasks(tasks, context, config, plugins) {
|
|
364
|
+
const result = {};
|
|
365
|
+
for (const task of tasks) {
|
|
366
|
+
result[task.id] = validateTask(task, context, config, plugins);
|
|
367
|
+
}
|
|
368
|
+
return result;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
function auditValidation(tasks, results) {
|
|
372
|
+
const checkedWithoutEvidence = [];
|
|
373
|
+
const readyButUnchecked = [];
|
|
374
|
+
|
|
375
|
+
for (const task of tasks) {
|
|
376
|
+
const result = results[task.id];
|
|
377
|
+
if (!result) {
|
|
378
|
+
continue;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
if (task.checked && !result.passed) {
|
|
382
|
+
checkedWithoutEvidence.push({ task, result });
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
if (!task.checked && result.passed) {
|
|
386
|
+
readyButUnchecked.push({ task, result });
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
return {
|
|
391
|
+
checkedWithoutEvidence,
|
|
392
|
+
readyButUnchecked
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
module.exports = {
|
|
397
|
+
auditValidation,
|
|
398
|
+
buildValidationContext,
|
|
399
|
+
validateTask,
|
|
400
|
+
validateTasks
|
|
401
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Agent Roadmap Sync Rules
|
|
2
|
+
|
|
3
|
+
These rules govern automated roadmap synchronization for `{{roadmapPath}}`.
|
|
4
|
+
|
|
5
|
+
## Completion Rules
|
|
6
|
+
- Locate the matching checklist item in `{{roadmapPath}}` before changing status.
|
|
7
|
+
- Mark a task `[x]` only after validation confirms one of these evidence types:
|
|
8
|
+
- code exists
|
|
9
|
+
- test exists
|
|
10
|
+
- artifact exists
|
|
11
|
+
- If the project has a detectable test framework and the task is a code task, require test evidence.
|
|
12
|
+
|
|
13
|
+
## Validation Failure Handling
|
|
14
|
+
- Do not force completion when validation fails.
|
|
15
|
+
- Keep the task unchecked and add/update a child warning:
|
|
16
|
+
- `- ⚠️ attempted but validation failed: <reason>`
|
|
17
|
+
|
|
18
|
+
## Operating Procedure
|
|
19
|
+
- Use `roadmapsmith generate` to build or refresh structured roadmap sections.
|
|
20
|
+
- Use `roadmapsmith sync` to reconcile checklist state against repository evidence.
|
|
21
|
+
- Use `roadmapsmith validate` before manual completion claims.
|
|
22
|
+
- Do not manually toggle checklist items unless validation has already passed.
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
<!-- rs:managed:start -->
|
|
2
|
+
# Project Roadmap
|
|
3
|
+
|
|
4
|
+
## Product North Star
|
|
5
|
+
Ship validated increments with transparent completion evidence and deterministic planning artifacts.
|
|
6
|
+
|
|
7
|
+
## Current State
|
|
8
|
+
- Implemented surface: 0 implementation files detected
|
|
9
|
+
- TODO surface: 0 TODO/FIXME markers detected
|
|
10
|
+
- Detected stacks: No language-specific stack detected
|
|
11
|
+
|
|
12
|
+
## Phased Roadmap
|
|
13
|
+
|
|
14
|
+
### Phase P0 (Critical)
|
|
15
|
+
- [ ] Stabilize project baseline and unblock high-risk delivery paths <!-- rs:task=p0-stabilize-project-baseline-and-unblock-high-risk-delivery-paths -->
|
|
16
|
+
- [ ] Implement critical tasks required for milestone v0.1 <!-- rs:task=p0-implement-critical-tasks-required-for-milestone-v0-1 -->
|
|
17
|
+
|
|
18
|
+
### Phase P1 (Important)
|
|
19
|
+
- [ ] Expand feature completeness and improve reliability <!-- rs:task=p1-expand-feature-completeness-and-improve-reliability -->
|
|
20
|
+
- [ ] Reduce operational risk before v0.3 <!-- rs:task=p1-reduce-operational-risk-before-v0-3 -->
|
|
21
|
+
|
|
22
|
+
### Phase P2 (Optimization)
|
|
23
|
+
- [ ] Complete final hardening and release readiness for v1.0 <!-- rs:task=p2-complete-final-hardening-and-release-readiness-for-v1-0 -->
|
|
24
|
+
- [ ] Close non-critical backlog aligned to anti-goals <!-- rs:task=p2-close-non-critical-backlog-aligned-to-anti-goals -->
|
|
25
|
+
|
|
26
|
+
## Release Milestones
|
|
27
|
+
- [ ] v0.1: Foundation baseline complete <!-- rs:task=milestone-v0-1 -->
|
|
28
|
+
- [ ] v0.2: Core feature coverage stabilized <!-- rs:task=milestone-v0-2 -->
|
|
29
|
+
- [ ] v0.3: Release candidate hardening complete <!-- rs:task=milestone-v0-3 -->
|
|
30
|
+
- [ ] v1.0: Production readiness exit criteria met <!-- rs:task=milestone-v1-0 -->
|
|
31
|
+
|
|
32
|
+
## Command/Module Breakdown
|
|
33
|
+
- [ ] Identify command/module boundaries for the next increment <!-- rs:task=identify-command-module-boundaries -->
|
|
34
|
+
|
|
35
|
+
## Exit Criteria Per Phase
|
|
36
|
+
- [ ] P0: all critical checklist items validated by code/test/artifact evidence <!-- rs:task=exit-p0-all-critical-checklist-items-validated-by-code-test-artifact-evidence -->
|
|
37
|
+
- [ ] P1: reliability and regression checks green on the mainline <!-- rs:task=exit-p1-reliability-and-regression-checks-green-on-the-mainline -->
|
|
38
|
+
- [ ] P2: release hardening and anti-goal checks completed for v1.0 <!-- rs:task=exit-p2-release-hardening-and-anti-goal-checks-completed-for-v1-0 -->
|
|
39
|
+
|
|
40
|
+
## Risks and Anti-goals
|
|
41
|
+
### Risks
|
|
42
|
+
- [ ] Roadmap drift if checklist state diverges from repository evidence <!-- rs:task=risk-roadmap-drift-if-checklist-state-diverges-from-repository-evidence -->
|
|
43
|
+
- [ ] Silent regressions when tasks are marked complete without tests <!-- rs:task=risk-silent-regressions-when-tasks-are-marked-complete-without-tests -->
|
|
44
|
+
- [ ] Scope creep that delays the v1.0 milestone path <!-- rs:task=risk-scope-creep-that-delays-the-v1-0-milestone-path -->
|
|
45
|
+
|
|
46
|
+
### Anti-goals
|
|
47
|
+
- [ ] Do not mark tasks complete without repository evidence <!-- rs:task=anti-goal-do-not-mark-tasks-complete-without-repository-evidence -->
|
|
48
|
+
- [ ] Do not introduce non-deterministic roadmap formatting <!-- rs:task=anti-goal-do-not-introduce-non-deterministic-roadmap-formatting -->
|
|
49
|
+
- [ ] Do not hide validation failures from roadmap consumers <!-- rs:task=anti-goal-do-not-hide-validation-failures-from-roadmap-consumers -->
|
|
50
|
+
<!-- rs:managed:end -->
|