roadmapsmith 0.6.0 → 0.7.1
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/bin/cli.js +35 -3
- package/package.json +11 -3
- package/src/generator/index.js +563 -562
- package/src/model.js +1 -0
- package/src/renderer/professional.js +536 -476
- package/src/validator/index.js +347 -42
|
@@ -1,476 +1,536 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const { slugify, ensureTrailingNewline } = require('../utils');
|
|
4
|
-
const { sectionHeader, checkedState, priorityLabel } = require('./helpers');
|
|
5
|
-
|
|
6
|
-
function taskLineWithPriority(task, model) {
|
|
7
|
-
const pri = task.priority ? `${priorityLabel(task.priority)} ` : '';
|
|
8
|
-
const id = task.id || `prof-task-${slugify(task.text || String(task))}`;
|
|
9
|
-
const text = task.text || String(task);
|
|
10
|
-
const checked = task.checked || checkedState(model, id);
|
|
11
|
-
return `- [${checked ? 'x' : ' '}] ${pri}${text} <!-- rs:task=${id} -->`;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
function exitLine(item, phN, stN, model) {
|
|
15
|
-
const text = typeof item === 'string' ? item : item.text;
|
|
16
|
-
const pri = (typeof item === 'object' && item.priority) ? `${priorityLabel(item.priority)} ` : '';
|
|
17
|
-
const id = `prof-ph${phN}-st${stN}-exit-${slugify(text)}`;
|
|
18
|
-
const checked = checkedState(model, id);
|
|
19
|
-
return `- [${checked ? 'x' : ' '}] ${pri}${text} <!-- rs:task=${id} -->`;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
function renderSection1NorthStar(model, lines) {
|
|
23
|
-
lines.push(sectionHeader(1, 'Product North Star'));
|
|
24
|
-
lines.push('');
|
|
25
|
-
lines.push(model.product.northStar || model.northStar);
|
|
26
|
-
lines.push('');
|
|
27
|
-
if (model.product.primaryUser) {
|
|
28
|
-
lines.push(`**Primary user:** ${model.product.primaryUser}`);
|
|
29
|
-
lines.push('');
|
|
30
|
-
}
|
|
31
|
-
if (model.product.targetOutcome) {
|
|
32
|
-
lines.push(`**Target outcome:** ${model.product.targetOutcome}`);
|
|
33
|
-
lines.push('');
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function renderSection2Positioning(model, lines) {
|
|
38
|
-
lines.push(sectionHeader(2, 'Positioning and Competitive Advantage'));
|
|
39
|
-
lines.push('');
|
|
40
|
-
if (model.product.positioning) {
|
|
41
|
-
lines.push(model.product.positioning);
|
|
42
|
-
} else {
|
|
43
|
-
lines.push('_No positioning statement configured. Add `product.positioning` to roadmap-skill.config.json._');
|
|
44
|
-
}
|
|
45
|
-
lines.push('');
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
function renderSection3CurrentState(model, lines) {
|
|
49
|
-
lines.push(sectionHeader(3, 'Explicit Current State'));
|
|
50
|
-
lines.push('');
|
|
51
|
-
|
|
52
|
-
lines.push('### Implemented');
|
|
53
|
-
lines.push('');
|
|
54
|
-
if (model.currentState.implemented && model.currentState.implemented.length > 0) {
|
|
55
|
-
for (const item of model.currentState.implemented) {
|
|
56
|
-
lines.push(`- [x] ${item} <!-- rs:task=prof-state-impl-${slugify(item)} -->`);
|
|
57
|
-
}
|
|
58
|
-
} else {
|
|
59
|
-
lines.push(`- Detected implementation surface: ${model.currentState.implementedSummary}`);
|
|
60
|
-
lines.push(`- Detected stacks: ${model.currentState.stackSummary}`);
|
|
61
|
-
}
|
|
62
|
-
lines.push('');
|
|
63
|
-
|
|
64
|
-
lines.push('### Scaffold / Partial');
|
|
65
|
-
lines.push('');
|
|
66
|
-
if (model.currentState.scaffold && model.currentState.scaffold.length > 0) {
|
|
67
|
-
for (const item of model.currentState.scaffold) {
|
|
68
|
-
const id = `prof-state-scaffold-${slugify(item)}`;
|
|
69
|
-
lines.push(`- [ ] ${item} <!-- rs:task=${id} -->`);
|
|
70
|
-
}
|
|
71
|
-
} else {
|
|
72
|
-
lines.push('_No scaffold modules detected. Improve detection by adding `product.steps` to config._');
|
|
73
|
-
}
|
|
74
|
-
lines.push('');
|
|
75
|
-
|
|
76
|
-
if (model.currentState.workspaces && model.currentState.workspaces.length > 0) {
|
|
77
|
-
lines.push('### Workspace Packages');
|
|
78
|
-
lines.push('');
|
|
79
|
-
lines.push(`- Workspace packages detected: ${model.currentState.workspaces.join(', ')}`);
|
|
80
|
-
lines.push('');
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
lines.push('### Known Limitations');
|
|
84
|
-
lines.push('');
|
|
85
|
-
if (model.currentState.knownLimitations && model.currentState.knownLimitations.length > 0) {
|
|
86
|
-
for (const item of model.currentState.knownLimitations) {
|
|
87
|
-
const id = `prof-state-limit-${slugify(item)}`;
|
|
88
|
-
lines.push(`- [ ] ${item} <!-- rs:task=${id} -->`);
|
|
89
|
-
}
|
|
90
|
-
} else {
|
|
91
|
-
lines.push(`- Code-level TODO/FIXME surface: ${model.currentState.todoSummary}`);
|
|
92
|
-
}
|
|
93
|
-
lines.push('');
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
function renderSection4PhasedExecution(model, lines) {
|
|
97
|
-
lines.push(sectionHeader(4, 'Phased Execution Roadmap'));
|
|
98
|
-
lines.push('');
|
|
99
|
-
|
|
100
|
-
const detailedPhases = [...(model.phasesDetailed || [])].sort((a, b) => a.phaseNumber - b.phaseNumber);
|
|
101
|
-
|
|
102
|
-
for (const phase of detailedPhases) {
|
|
103
|
-
lines.push(`### Phase ${phase.phaseNumber}: ${phase.title}`);
|
|
104
|
-
lines.push('');
|
|
105
|
-
lines.push(`**Phase Priority:** ${priorityLabel(phase.priority)}`);
|
|
106
|
-
lines.push('');
|
|
107
|
-
if (phase.objective) {
|
|
108
|
-
lines.push(`**Objective:** ${phase.objective}`);
|
|
109
|
-
lines.push('');
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
const steps = [...(phase.steps || [])].sort((a, b) => a.stepNumber - b.stepNumber);
|
|
113
|
-
for (const step of steps) {
|
|
114
|
-
const stepLabel = `${phase.phaseNumber}.${step.stepNumber}`;
|
|
115
|
-
lines.push(`#### Step ${stepLabel}: ${step.title}`);
|
|
116
|
-
lines.push('');
|
|
117
|
-
lines.push(`**Step Priority:** ${priorityLabel(step.priority)}`);
|
|
118
|
-
const deps = step.dependsOn && step.dependsOn.length > 0
|
|
119
|
-
? step.dependsOn.map((n) => `Phase ${n}`).join(', ')
|
|
120
|
-
: 'None';
|
|
121
|
-
lines.push(`**Depends on:** ${deps}`);
|
|
122
|
-
lines.push('');
|
|
123
|
-
if (step.objective) {
|
|
124
|
-
lines.push(`**Objective:** ${step.objective}`);
|
|
125
|
-
lines.push('');
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
if (step.tasks && step.tasks.length > 0) {
|
|
129
|
-
lines.push('**Tasks:**');
|
|
130
|
-
lines.push('');
|
|
131
|
-
for (const task of step.tasks) {
|
|
132
|
-
lines.push(taskLineWithPriority(task, model));
|
|
133
|
-
}
|
|
134
|
-
lines.push('');
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
if (step.exitCriteria && step.exitCriteria.length > 0) {
|
|
138
|
-
lines.push('**Exit Criteria:**');
|
|
139
|
-
lines.push('');
|
|
140
|
-
for (const item of step.exitCriteria) {
|
|
141
|
-
lines.push(exitLine(item, phase.phaseNumber, step.stepNumber, model));
|
|
142
|
-
}
|
|
143
|
-
lines.push('');
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
if (step.risks && step.risks.length > 0) {
|
|
147
|
-
lines.push(`**Notable Risks:** ${step.risks.join('; ')}`);
|
|
148
|
-
lines.push('');
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
function renderSection5Milestones(model, lines) {
|
|
155
|
-
lines.push(sectionHeader(5, 'Versioned Milestones'));
|
|
156
|
-
lines.push('');
|
|
157
|
-
|
|
158
|
-
for (const milestone of model.milestones) {
|
|
159
|
-
const msSlug = slugify(milestone.version);
|
|
160
|
-
lines.push(`### ${milestone.version}`);
|
|
161
|
-
lines.push('');
|
|
162
|
-
lines.push(`**Goal:** ${milestone.goal}`);
|
|
163
|
-
lines.push('');
|
|
164
|
-
|
|
165
|
-
if (milestone.mustExist && milestone.mustExist.length > 0) {
|
|
166
|
-
lines.push('**What Must Exist:**');
|
|
167
|
-
lines.push('');
|
|
168
|
-
for (const item of milestone.mustExist) {
|
|
169
|
-
const id = `prof-ms-${msSlug}-exist-${slugify(item)}`;
|
|
170
|
-
lines.push(`- [${checkedState(model, id) ? 'x' : ' '}] \`[P0]\` ${item} <!-- rs:task=${id} -->`);
|
|
171
|
-
}
|
|
172
|
-
lines.push('');
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
if (milestone.mustBeStable && milestone.mustBeStable.length > 0) {
|
|
176
|
-
lines.push('**What Must Be Stable:**');
|
|
177
|
-
lines.push('');
|
|
178
|
-
for (const item of milestone.mustBeStable) {
|
|
179
|
-
const text = typeof item === 'string' ? item : item.text;
|
|
180
|
-
const
|
|
181
|
-
const
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
const
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
const
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
lines.push(
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
lines.push('
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
lines.push('
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
lines.push('
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
{ text: '
|
|
330
|
-
{ text: '
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
lines.push('
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
{ text: '
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
lines.push('
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
lines.push('
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
lines.push('
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
{ text: '
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
lines.push('
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
lines.push('
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
const
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
lines.push('
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
lines.push(
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { slugify, ensureTrailingNewline } = require('../utils');
|
|
4
|
+
const { sectionHeader, checkedState, priorityLabel } = require('./helpers');
|
|
5
|
+
|
|
6
|
+
function taskLineWithPriority(task, model) {
|
|
7
|
+
const pri = task.priority ? `${priorityLabel(task.priority)} ` : '';
|
|
8
|
+
const id = task.id || `prof-task-${slugify(task.text || String(task))}`;
|
|
9
|
+
const text = task.text || String(task);
|
|
10
|
+
const checked = task.checked || checkedState(model, id);
|
|
11
|
+
return `- [${checked ? 'x' : ' '}] ${pri}${text} <!-- rs:task=${id} -->`;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function exitLine(item, phN, stN, model) {
|
|
15
|
+
const text = typeof item === 'string' ? item : item.text;
|
|
16
|
+
const pri = (typeof item === 'object' && item.priority) ? `${priorityLabel(item.priority)} ` : '';
|
|
17
|
+
const id = `prof-ph${phN}-st${stN}-exit-${slugify(text)}`;
|
|
18
|
+
const checked = checkedState(model, id);
|
|
19
|
+
return `- [${checked ? 'x' : ' '}] ${pri}${text} <!-- rs:task=${id} -->`;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function renderSection1NorthStar(model, lines) {
|
|
23
|
+
lines.push(sectionHeader(1, 'Product North Star'));
|
|
24
|
+
lines.push('');
|
|
25
|
+
lines.push(model.product.northStar || model.northStar);
|
|
26
|
+
lines.push('');
|
|
27
|
+
if (model.product.primaryUser) {
|
|
28
|
+
lines.push(`**Primary user:** ${model.product.primaryUser}`);
|
|
29
|
+
lines.push('');
|
|
30
|
+
}
|
|
31
|
+
if (model.product.targetOutcome) {
|
|
32
|
+
lines.push(`**Target outcome:** ${model.product.targetOutcome}`);
|
|
33
|
+
lines.push('');
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function renderSection2Positioning(model, lines) {
|
|
38
|
+
lines.push(sectionHeader(2, 'Positioning and Competitive Advantage'));
|
|
39
|
+
lines.push('');
|
|
40
|
+
if (model.product.positioning) {
|
|
41
|
+
lines.push(model.product.positioning);
|
|
42
|
+
} else {
|
|
43
|
+
lines.push('_No positioning statement configured. Add `product.positioning` to roadmap-skill.config.json._');
|
|
44
|
+
}
|
|
45
|
+
lines.push('');
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function renderSection3CurrentState(model, lines) {
|
|
49
|
+
lines.push(sectionHeader(3, 'Explicit Current State'));
|
|
50
|
+
lines.push('');
|
|
51
|
+
|
|
52
|
+
lines.push('### Implemented');
|
|
53
|
+
lines.push('');
|
|
54
|
+
if (model.currentState.implemented && model.currentState.implemented.length > 0) {
|
|
55
|
+
for (const item of model.currentState.implemented) {
|
|
56
|
+
lines.push(`- [x] ${item} <!-- rs:task=prof-state-impl-${slugify(item)} -->`);
|
|
57
|
+
}
|
|
58
|
+
} else {
|
|
59
|
+
lines.push(`- Detected implementation surface: ${model.currentState.implementedSummary}`);
|
|
60
|
+
lines.push(`- Detected stacks: ${model.currentState.stackSummary}`);
|
|
61
|
+
}
|
|
62
|
+
lines.push('');
|
|
63
|
+
|
|
64
|
+
lines.push('### Scaffold / Partial');
|
|
65
|
+
lines.push('');
|
|
66
|
+
if (model.currentState.scaffold && model.currentState.scaffold.length > 0) {
|
|
67
|
+
for (const item of model.currentState.scaffold) {
|
|
68
|
+
const id = `prof-state-scaffold-${slugify(item)}`;
|
|
69
|
+
lines.push(`- [ ] ${item} <!-- rs:task=${id} -->`);
|
|
70
|
+
}
|
|
71
|
+
} else {
|
|
72
|
+
lines.push('_No scaffold modules detected. Improve detection by adding `product.steps` to config._');
|
|
73
|
+
}
|
|
74
|
+
lines.push('');
|
|
75
|
+
|
|
76
|
+
if (model.currentState.workspaces && model.currentState.workspaces.length > 0) {
|
|
77
|
+
lines.push('### Workspace Packages');
|
|
78
|
+
lines.push('');
|
|
79
|
+
lines.push(`- Workspace packages detected: ${model.currentState.workspaces.join(', ')}`);
|
|
80
|
+
lines.push('');
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
lines.push('### Known Limitations');
|
|
84
|
+
lines.push('');
|
|
85
|
+
if (model.currentState.knownLimitations && model.currentState.knownLimitations.length > 0) {
|
|
86
|
+
for (const item of model.currentState.knownLimitations) {
|
|
87
|
+
const id = `prof-state-limit-${slugify(item)}`;
|
|
88
|
+
lines.push(`- [ ] ${item} <!-- rs:task=${id} -->`);
|
|
89
|
+
}
|
|
90
|
+
} else {
|
|
91
|
+
lines.push(`- Code-level TODO/FIXME surface: ${model.currentState.todoSummary}`);
|
|
92
|
+
}
|
|
93
|
+
lines.push('');
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function renderSection4PhasedExecution(model, lines) {
|
|
97
|
+
lines.push(sectionHeader(4, 'Phased Execution Roadmap'));
|
|
98
|
+
lines.push('');
|
|
99
|
+
|
|
100
|
+
const detailedPhases = [...(model.phasesDetailed || [])].sort((a, b) => a.phaseNumber - b.phaseNumber);
|
|
101
|
+
|
|
102
|
+
for (const phase of detailedPhases) {
|
|
103
|
+
lines.push(`### Phase ${phase.phaseNumber}: ${phase.title}`);
|
|
104
|
+
lines.push('');
|
|
105
|
+
lines.push(`**Phase Priority:** ${priorityLabel(phase.priority)}`);
|
|
106
|
+
lines.push('');
|
|
107
|
+
if (phase.objective) {
|
|
108
|
+
lines.push(`**Objective:** ${phase.objective}`);
|
|
109
|
+
lines.push('');
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const steps = [...(phase.steps || [])].sort((a, b) => a.stepNumber - b.stepNumber);
|
|
113
|
+
for (const step of steps) {
|
|
114
|
+
const stepLabel = `${phase.phaseNumber}.${step.stepNumber}`;
|
|
115
|
+
lines.push(`#### Step ${stepLabel}: ${step.title}`);
|
|
116
|
+
lines.push('');
|
|
117
|
+
lines.push(`**Step Priority:** ${priorityLabel(step.priority)}`);
|
|
118
|
+
const deps = step.dependsOn && step.dependsOn.length > 0
|
|
119
|
+
? step.dependsOn.map((n) => `Phase ${n}`).join(', ')
|
|
120
|
+
: 'None';
|
|
121
|
+
lines.push(`**Depends on:** ${deps}`);
|
|
122
|
+
lines.push('');
|
|
123
|
+
if (step.objective) {
|
|
124
|
+
lines.push(`**Objective:** ${step.objective}`);
|
|
125
|
+
lines.push('');
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (step.tasks && step.tasks.length > 0) {
|
|
129
|
+
lines.push('**Tasks:**');
|
|
130
|
+
lines.push('');
|
|
131
|
+
for (const task of step.tasks) {
|
|
132
|
+
lines.push(taskLineWithPriority(task, model));
|
|
133
|
+
}
|
|
134
|
+
lines.push('');
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (step.exitCriteria && step.exitCriteria.length > 0) {
|
|
138
|
+
lines.push('**Exit Criteria:**');
|
|
139
|
+
lines.push('');
|
|
140
|
+
for (const item of step.exitCriteria) {
|
|
141
|
+
lines.push(exitLine(item, phase.phaseNumber, step.stepNumber, model));
|
|
142
|
+
}
|
|
143
|
+
lines.push('');
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (step.risks && step.risks.length > 0) {
|
|
147
|
+
lines.push(`**Notable Risks:** ${step.risks.join('; ')}`);
|
|
148
|
+
lines.push('');
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function renderSection5Milestones(model, lines) {
|
|
155
|
+
lines.push(sectionHeader(5, 'Versioned Milestones'));
|
|
156
|
+
lines.push('');
|
|
157
|
+
|
|
158
|
+
for (const milestone of model.milestones) {
|
|
159
|
+
const msSlug = slugify(milestone.version);
|
|
160
|
+
lines.push(`### ${milestone.version}`);
|
|
161
|
+
lines.push('');
|
|
162
|
+
lines.push(`**Goal:** ${milestone.goal}`);
|
|
163
|
+
lines.push('');
|
|
164
|
+
|
|
165
|
+
if (milestone.mustExist && milestone.mustExist.length > 0) {
|
|
166
|
+
lines.push('**What Must Exist:**');
|
|
167
|
+
lines.push('');
|
|
168
|
+
for (const item of milestone.mustExist) {
|
|
169
|
+
const id = `prof-ms-${msSlug}-exist-${slugify(item)}`;
|
|
170
|
+
lines.push(`- [${checkedState(model, id) ? 'x' : ' '}] \`[P0]\` ${item} <!-- rs:task=${id} -->`);
|
|
171
|
+
}
|
|
172
|
+
lines.push('');
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (milestone.mustBeStable && milestone.mustBeStable.length > 0) {
|
|
176
|
+
lines.push('**What Must Be Stable:**');
|
|
177
|
+
lines.push('');
|
|
178
|
+
for (const item of milestone.mustBeStable) {
|
|
179
|
+
const text = typeof item === 'string' ? item : item.text;
|
|
180
|
+
const hasNote = typeof item === 'object' && Boolean(item.note);
|
|
181
|
+
const note = hasNote ? ` — _${item.note}_` : '';
|
|
182
|
+
const id = `prof-ms-${msSlug}-stable-${slugify(text)}`;
|
|
183
|
+
const isChecked = checkedState(model, id);
|
|
184
|
+
lines.push(`- [${isChecked ? 'x' : ' '}] \`[P1]\` ${text}${note} <!-- rs:task=${id} -->`);
|
|
185
|
+
}
|
|
186
|
+
lines.push('');
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (milestone.outOfScope && milestone.outOfScope.length > 0) {
|
|
190
|
+
lines.push('**Intentionally Out of Scope:**');
|
|
191
|
+
lines.push('');
|
|
192
|
+
for (const item of milestone.outOfScope) {
|
|
193
|
+
lines.push(`- ${item}`);
|
|
194
|
+
}
|
|
195
|
+
lines.push('');
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
if (!milestone.mustExist && !milestone.mustBeStable && !milestone.outOfScope) {
|
|
199
|
+
const id = `prof-ms-${msSlug}`;
|
|
200
|
+
lines.push(`- [${checkedState(model, id) ? 'x' : ' '}] \`[P0]\` ${milestone.version}: ${milestone.goal} <!-- rs:task=${id} -->`);
|
|
201
|
+
lines.push('');
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
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
|
+
config: {
|
|
242
|
+
state: 'Supports roadmapProfile, product block, milestones, phaseTemplates, plugins.',
|
|
243
|
+
tasks: [
|
|
244
|
+
{ text: 'Add JSON schema validation for roadmap-skill.config.json', priority: 'P1', id: 'prof-mat-config-json-schema-validation' }
|
|
245
|
+
]
|
|
246
|
+
},
|
|
247
|
+
io: {
|
|
248
|
+
state: 'Scans files, detects languages, test frameworks, commands, modules.',
|
|
249
|
+
tasks: [
|
|
250
|
+
{ text: 'Improve module detection for monorepo workspace layouts', priority: 'P2', id: 'prof-mat-io-monorepo-workspace-detection' }
|
|
251
|
+
]
|
|
252
|
+
}
|
|
253
|
+
};
|
|
254
|
+
|
|
255
|
+
function renderSection6MaturityPath(model, lines) {
|
|
256
|
+
lines.push(sectionHeader(6, 'Command-by-Command / Module-by-Module Maturity Path'));
|
|
257
|
+
lines.push('');
|
|
258
|
+
|
|
259
|
+
const allAreas = [...model.commandBreakdown];
|
|
260
|
+
|
|
261
|
+
if (allAreas.length === 0) {
|
|
262
|
+
const id = 'prof-mat-identify-boundaries';
|
|
263
|
+
lines.push(`- [${checkedState(model, id) ? 'x' : ' '}] \`[P1]\` Identify command/module boundaries for the next increment <!-- rs:task=${id} -->`);
|
|
264
|
+
lines.push('');
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
for (const area of allAreas) {
|
|
269
|
+
const rawName = area.replace(/^(Module:|Command:)\s*/i, '').trim();
|
|
270
|
+
const meta = MODULE_METADATA[rawName.toLowerCase()];
|
|
271
|
+
const displayName = rawName;
|
|
272
|
+
|
|
273
|
+
lines.push(`### ${displayName}`);
|
|
274
|
+
lines.push('');
|
|
275
|
+
if (meta) {
|
|
276
|
+
lines.push(`**Current state:** ${meta.state}`);
|
|
277
|
+
lines.push('');
|
|
278
|
+
for (const task of meta.tasks) {
|
|
279
|
+
lines.push(`- [${checkedState(model, task.id) ? 'x' : ' '}] ${priorityLabel(task.priority)} ${task.text} <!-- rs:task=${task.id} -->`);
|
|
280
|
+
}
|
|
281
|
+
} else {
|
|
282
|
+
const isCommand = /^Command:/i.test(area);
|
|
283
|
+
const kind = isCommand ? 'command' : 'module';
|
|
284
|
+
const nextId = `prof-mat-${slugify(rawName)}-define-maturity-criteria`;
|
|
285
|
+
lines.push(`**Current state:** ${kind} detected in scan.`);
|
|
286
|
+
lines.push('');
|
|
287
|
+
lines.push(`- [${checkedState(model, nextId) ? 'x' : ' '}] \`[P1]\` Define maturity criteria and testability gates for ${displayName} <!-- rs:task=${nextId} -->`);
|
|
288
|
+
}
|
|
289
|
+
lines.push('');
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
function renderSection7OutputContract(model, lines) {
|
|
294
|
+
lines.push(sectionHeader(7, 'Output Contract Roadmap'));
|
|
295
|
+
lines.push('');
|
|
296
|
+
|
|
297
|
+
lines.push('### Output Format');
|
|
298
|
+
lines.push('');
|
|
299
|
+
const formatItems = [
|
|
300
|
+
{ text: 'Define stable public output format (stdout, files, exit codes)', priority: 'P0' },
|
|
301
|
+
{ text: 'Version output format alongside package version', priority: 'P1' }
|
|
302
|
+
];
|
|
303
|
+
for (const item of formatItems) {
|
|
304
|
+
const id = `prof-out-${slugify(item.text)}`;
|
|
305
|
+
lines.push(`- [${checkedState(model, id) ? 'x' : ' '}] ${priorityLabel(item.priority)} ${item.text} <!-- rs:task=${id} -->`);
|
|
306
|
+
}
|
|
307
|
+
lines.push('');
|
|
308
|
+
|
|
309
|
+
lines.push('### Breaking Changes');
|
|
310
|
+
lines.push('');
|
|
311
|
+
const breakingItems = [
|
|
312
|
+
{ text: 'Document breaking vs. non-breaking output changes', priority: 'P1' },
|
|
313
|
+
{ text: 'Add output schema validation to CI', priority: 'P1' }
|
|
314
|
+
];
|
|
315
|
+
for (const item of breakingItems) {
|
|
316
|
+
const id = `prof-out-${slugify(item.text)}`;
|
|
317
|
+
lines.push(`- [${checkedState(model, id) ? 'x' : ' '}] ${priorityLabel(item.priority)} ${item.text} <!-- rs:task=${id} -->`);
|
|
318
|
+
}
|
|
319
|
+
lines.push('');
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
function renderSection8Testing(model, lines) {
|
|
323
|
+
lines.push(sectionHeader(8, 'Testing and Quality-Gate Roadmap'));
|
|
324
|
+
lines.push('');
|
|
325
|
+
|
|
326
|
+
lines.push('### Test Coverage');
|
|
327
|
+
lines.push('');
|
|
328
|
+
const coverageItems = [
|
|
329
|
+
{ text: 'Unit test coverage for all core modules', priority: 'P0' },
|
|
330
|
+
{ text: 'Integration tests covering the full generate → sync → validate pipeline', priority: 'P0' },
|
|
331
|
+
{ text: 'Regression fixtures for compact and professional profile output', priority: 'P1' },
|
|
332
|
+
{ text: 'Edge case coverage: empty repo, no config, large monorepo scan', priority: 'P1' }
|
|
333
|
+
];
|
|
334
|
+
for (const item of coverageItems) {
|
|
335
|
+
const id = `prof-test-${slugify(item.text)}`;
|
|
336
|
+
lines.push(`- [${checkedState(model, id) ? 'x' : ' '}] ${priorityLabel(item.priority)} ${item.text} <!-- rs:task=${id} -->`);
|
|
337
|
+
}
|
|
338
|
+
lines.push('');
|
|
339
|
+
|
|
340
|
+
lines.push('### Quality Gates');
|
|
341
|
+
lines.push('');
|
|
342
|
+
const gateItems = [
|
|
343
|
+
{ text: 'CI quality gate: tests must pass before merge', priority: 'P0' },
|
|
344
|
+
{ text: 'Block merge when generated roadmap loses checked state', priority: 'P0' },
|
|
345
|
+
{ text: 'Add professional renderer snapshot tests', priority: 'P1' }
|
|
346
|
+
];
|
|
347
|
+
for (const item of gateItems) {
|
|
348
|
+
const id = `prof-test-${slugify(item.text)}`;
|
|
349
|
+
lines.push(`- [${checkedState(model, id) ? 'x' : ' '}] ${priorityLabel(item.priority)} ${item.text} <!-- rs:task=${id} -->`);
|
|
350
|
+
}
|
|
351
|
+
lines.push('');
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
function renderSection9Distribution(model, lines) {
|
|
355
|
+
lines.push(sectionHeader(9, 'Distribution Roadmap'));
|
|
356
|
+
lines.push('');
|
|
357
|
+
|
|
358
|
+
lines.push('### npm Registry');
|
|
359
|
+
lines.push('');
|
|
360
|
+
const npmItems = [
|
|
361
|
+
{ text: 'Publish to npm registry with stable semver', priority: 'P0' },
|
|
362
|
+
{ text: 'Ensure CLI binary is correctly linked in package.json `bin`', priority: 'P0' }
|
|
363
|
+
];
|
|
364
|
+
for (const item of npmItems) {
|
|
365
|
+
const id = `prof-dist-${slugify(item.text)}`;
|
|
366
|
+
lines.push(`- [${checkedState(model, id) ? 'x' : ' '}] ${priorityLabel(item.priority)} ${item.text} <!-- rs:task=${id} -->`);
|
|
367
|
+
}
|
|
368
|
+
lines.push('');
|
|
369
|
+
|
|
370
|
+
lines.push('### Release Process');
|
|
371
|
+
lines.push('');
|
|
372
|
+
const releaseItems = [
|
|
373
|
+
{ text: 'Tag git releases aligned with npm publish', priority: 'P1' },
|
|
374
|
+
{ text: 'Document install instructions for npm global and npx usage', priority: 'P1' }
|
|
375
|
+
];
|
|
376
|
+
for (const item of releaseItems) {
|
|
377
|
+
const id = `prof-dist-${slugify(item.text)}`;
|
|
378
|
+
lines.push(`- [${checkedState(model, id) ? 'x' : ' '}] ${priorityLabel(item.priority)} ${item.text} <!-- rs:task=${id} -->`);
|
|
379
|
+
}
|
|
380
|
+
lines.push('');
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
function renderSection10Documentation(model, lines) {
|
|
384
|
+
lines.push(sectionHeader(10, 'Documentation Roadmap'));
|
|
385
|
+
lines.push('');
|
|
386
|
+
|
|
387
|
+
lines.push('### Core Docs');
|
|
388
|
+
lines.push('');
|
|
389
|
+
const coreItems = [
|
|
390
|
+
{ text: 'README.md covers install, commands, and profile selection', priority: 'P0' },
|
|
391
|
+
{ text: 'SKILL.md reflects current feature set and guardrails', priority: 'P0' },
|
|
392
|
+
{ text: 'CHANGELOG.md maintained for each release', priority: 'P1' }
|
|
393
|
+
];
|
|
394
|
+
for (const item of coreItems) {
|
|
395
|
+
const id = `prof-doc-${slugify(item.text)}`;
|
|
396
|
+
lines.push(`- [${checkedState(model, id) ? 'x' : ' '}] ${priorityLabel(item.priority)} ${item.text} <!-- rs:task=${id} -->`);
|
|
397
|
+
}
|
|
398
|
+
lines.push('');
|
|
399
|
+
|
|
400
|
+
lines.push('### Showcase');
|
|
401
|
+
lines.push('');
|
|
402
|
+
const showcaseItems = [
|
|
403
|
+
{ text: 'docs/ use-cases cover compact and professional profiles', priority: 'P1' },
|
|
404
|
+
{ text: 'Generated ROADMAP.md showcases professional Phase→Step→Task output', priority: 'P1' }
|
|
405
|
+
];
|
|
406
|
+
for (const item of showcaseItems) {
|
|
407
|
+
const id = `prof-doc-${slugify(item.text)}`;
|
|
408
|
+
lines.push(`- [${checkedState(model, id) ? 'x' : ' '}] ${priorityLabel(item.priority)} ${item.text} <!-- rs:task=${id} -->`);
|
|
409
|
+
}
|
|
410
|
+
lines.push('');
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
function renderSection11Risks(model, lines) {
|
|
414
|
+
lines.push(sectionHeader(11, 'Risks, Constraints, and Anti-Goals'));
|
|
415
|
+
lines.push('');
|
|
416
|
+
|
|
417
|
+
lines.push('### Risks');
|
|
418
|
+
lines.push('');
|
|
419
|
+
for (let i = 0; i < model.risks.length; i += 1) {
|
|
420
|
+
const risk = model.risks[i];
|
|
421
|
+
const pri = i === 0 ? 'P0' : 'P1';
|
|
422
|
+
const id = `prof-risk-${slugify(risk)}`;
|
|
423
|
+
lines.push(`- [${checkedState(model, id) ? 'x' : ' '}] ${priorityLabel(pri)} ${risk} <!-- rs:task=${id} -->`);
|
|
424
|
+
}
|
|
425
|
+
lines.push('');
|
|
426
|
+
|
|
427
|
+
lines.push('### Anti-Goals');
|
|
428
|
+
lines.push('');
|
|
429
|
+
for (const antiGoal of model.antiGoals) {
|
|
430
|
+
lines.push(`- ${antiGoal}`);
|
|
431
|
+
}
|
|
432
|
+
lines.push('');
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
function renderSection13CustomPhases(model, lines) {
|
|
436
|
+
const phases = model.customPhases || [];
|
|
437
|
+
if (phases.length === 0) {
|
|
438
|
+
return;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
lines.push(sectionHeader(13, 'Extended Phases'));
|
|
442
|
+
lines.push('');
|
|
443
|
+
|
|
444
|
+
const sorted = [...phases].sort((a, b) => a.phaseNumber - b.phaseNumber);
|
|
445
|
+
for (const phase of sorted) {
|
|
446
|
+
lines.push(`### Phase ${phase.phaseNumber}: ${phase.title}`);
|
|
447
|
+
lines.push('');
|
|
448
|
+
lines.push(`**Phase Priority:** ${priorityLabel(phase.priority)}`);
|
|
449
|
+
lines.push(`**Objective:** ${phase.objective}`);
|
|
450
|
+
lines.push('');
|
|
451
|
+
|
|
452
|
+
const steps = [...(phase.steps || [])].sort((a, b) => a.stepNumber - b.stepNumber);
|
|
453
|
+
for (const step of steps) {
|
|
454
|
+
const stepLabel = `${phase.phaseNumber}.${step.stepNumber}`;
|
|
455
|
+
lines.push(`#### Step ${stepLabel}: ${step.title}`);
|
|
456
|
+
lines.push('');
|
|
457
|
+
lines.push(`**Step Priority:** ${priorityLabel(step.priority)}`);
|
|
458
|
+
const deps = step.dependsOn && step.dependsOn.length > 0
|
|
459
|
+
? step.dependsOn.map((n) => `Phase ${n}`).join(', ')
|
|
460
|
+
: 'None';
|
|
461
|
+
lines.push(`**Depends on:** ${deps}`);
|
|
462
|
+
lines.push('');
|
|
463
|
+
if (step.objective) {
|
|
464
|
+
lines.push(`**Objective:** ${step.objective}`);
|
|
465
|
+
lines.push('');
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
if (step.tasks && step.tasks.length > 0) {
|
|
469
|
+
lines.push('**Tasks:**');
|
|
470
|
+
lines.push('');
|
|
471
|
+
for (const task of step.tasks) {
|
|
472
|
+
lines.push(taskLineWithPriority(task, model));
|
|
473
|
+
}
|
|
474
|
+
lines.push('');
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
if (step.exitCriteria && step.exitCriteria.length > 0) {
|
|
478
|
+
lines.push('**Exit Criteria:**');
|
|
479
|
+
lines.push('');
|
|
480
|
+
for (const item of step.exitCriteria) {
|
|
481
|
+
const text = typeof item === 'string' ? item : item.text;
|
|
482
|
+
const pri = (typeof item === 'object' && item.priority) ? `${priorityLabel(item.priority)} ` : '';
|
|
483
|
+
const id = (typeof item === 'object' && item.id) ? item.id : `mkt-ph${phase.phaseNumber}-st${step.stepNumber}-exit-${slugify(text)}`;
|
|
484
|
+
lines.push(`- [${checkedState(model, id) ? 'x' : ' '}] ${pri}${text} <!-- rs:task=${id} -->`);
|
|
485
|
+
}
|
|
486
|
+
lines.push('');
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
function renderSection12SuccessCriteria(model, lines) {
|
|
493
|
+
lines.push(sectionHeader(12, '1.0 Measurable Success Criteria'));
|
|
494
|
+
lines.push('');
|
|
495
|
+
|
|
496
|
+
const criteria = model.successCriteria && model.successCriteria.length > 0
|
|
497
|
+
? model.successCriteria
|
|
498
|
+
: [
|
|
499
|
+
'All roadmap sections render without errors for compact and professional profiles',
|
|
500
|
+
'Checked task state is preserved across regeneration',
|
|
501
|
+
'npm test passes with no failures',
|
|
502
|
+
'ROADMAP.md is generated by RoadmapSmith itself'
|
|
503
|
+
];
|
|
504
|
+
|
|
505
|
+
for (const criterion of criteria) {
|
|
506
|
+
const id = `prof-sc-${slugify(criterion)}`;
|
|
507
|
+
lines.push(`- [${checkedState(model, id) ? 'x' : ' '}] \`[P0]\` ${criterion} <!-- rs:task=${id} -->`);
|
|
508
|
+
}
|
|
509
|
+
lines.push('');
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
function renderProfessional(model) {
|
|
513
|
+
const projectName = (model.product && model.product.name) || 'Project';
|
|
514
|
+
const lines = [];
|
|
515
|
+
|
|
516
|
+
lines.push(`# ${projectName} Roadmap`);
|
|
517
|
+
lines.push('');
|
|
518
|
+
|
|
519
|
+
renderSection1NorthStar(model, lines);
|
|
520
|
+
renderSection2Positioning(model, lines);
|
|
521
|
+
renderSection3CurrentState(model, lines);
|
|
522
|
+
renderSection4PhasedExecution(model, lines);
|
|
523
|
+
renderSection5Milestones(model, lines);
|
|
524
|
+
renderSection6MaturityPath(model, lines);
|
|
525
|
+
renderSection7OutputContract(model, lines);
|
|
526
|
+
renderSection8Testing(model, lines);
|
|
527
|
+
renderSection9Distribution(model, lines);
|
|
528
|
+
renderSection10Documentation(model, lines);
|
|
529
|
+
renderSection11Risks(model, lines);
|
|
530
|
+
renderSection12SuccessCriteria(model, lines);
|
|
531
|
+
renderSection13CustomPhases(model, lines);
|
|
532
|
+
|
|
533
|
+
return ensureTrailingNewline(lines.join('\n')).trimEnd();
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
module.exports = { renderProfessional };
|