cabloy 5.1.200 → 5.1.202
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/.cabloy-version +1 -1
- package/CHANGELOG.md +21 -0
- package/README.md +6 -0
- package/package.json +1 -1
- package/repo-agent-governance/managed-assets.json +21 -21
- package/repo-agent-governance/skills/cabloy-spec-execution/SKILL.md +3 -2
- package/repo-agent-governance/skills/cabloy-spec-execution/references/execution-protocol.md +1 -1
- package/repo-agent-governance/skills/cabloy-spec-execution/references/status-and-evidence.md +11 -10
- package/repo-agent-governance/skills/cabloy-spec-generation/SKILL.md +1 -1
- package/repo-agent-governance/skills/cabloy-spec-generation/references/canonical-spec-input.md +6 -2
- package/repo-agent-governance/skills/cabloy-spec-generation/references/repo-specs-document-set.md +16 -13
- package/repo-agent-governance/skills/cabloy-spec-generation/references/traceability-and-status-rules.md +19 -12
- package/repo-agent-governance/tests/spec-audit.test.mjs +493 -0
- package/repo-agent-governance/tools/spec-audit/audit.mjs +189 -5
- package/repo-agent-governance/tools/spec-charts/generate-implementation-charts.mjs +26 -7
- package/repo-agent-governance/tools/spec-charts/generate-implementation-charts.test.mjs +82 -0
- package/repo-agent-governance/tools/spec-charts/spec-parser.mjs +40 -1
- package/repo-docs/ai/ai-spec-driven-development.md +1 -0
- package/repo-docs/ai/playbook-spec-execution.md +3 -2
- package/repo-docs/ai/playbook-spec-generation.md +1 -1
- package/repo-docs/assets/img/cabloy-admin-ssr-cover-en.png +0 -0
- package/repo-docs/index.md +6 -0
|
@@ -57,6 +57,267 @@ test('complete baseline checks exact traceability without treating a Proposed AD
|
|
|
57
57
|
assert.match(result.limitations, /approval.*evidence authenticity/);
|
|
58
58
|
});
|
|
59
59
|
|
|
60
|
+
const PLANNING_GATE = {
|
|
61
|
+
...BASELINE,
|
|
62
|
+
'srs.md': BASELINE['srs.md'].replace(', `WBS-DEMO-10-01`', ''),
|
|
63
|
+
'test-plan.md': BASELINE['test-plan.md'].replace('Traceability: `WBS-DEMO-10-01`', ''),
|
|
64
|
+
'pdp-wbs.md':
|
|
65
|
+
'# Delivery\n\n### Phase 10: Baseline\n\nDependencies: none.\n\n#### WBS-DEMO-10-01: Review planning baseline\n\nTraceability mode: planning-baseline-review\n\nReview authority: [ADR](./decisions/0001-boundary.md).\n\nTasks:\n\n- Reconcile the whole requirements, contracts, work and acceptance catalogue.\n\nAcceptance checks:\n\n- Confirm the boundary ADR was accepted by its decision owner.\n',
|
|
66
|
+
'decisions/0001-boundary.md':
|
|
67
|
+
'# Boundary\n\n## Status\n\nAccepted.\n\n## Decision\n\nAccept the planning boundary.\n',
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
test('explicit planning-baseline gate omits only its WBS adjacency checks', async t => {
|
|
71
|
+
const result = await auditPlanning(await fixture(t, PLANNING_GATE));
|
|
72
|
+
assert.deepEqual(
|
|
73
|
+
result.diagnostics.map(diagnostic => [diagnostic.id, diagnostic.code]),
|
|
74
|
+
[
|
|
75
|
+
['SRS-DEMO-01', 'traceability-outgoing'],
|
|
76
|
+
['ATP-DEMO-01', 'traceability-incoming'],
|
|
77
|
+
],
|
|
78
|
+
);
|
|
79
|
+
assert.equal(result.ok, false);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
test('a planning gate coexists with a fully connected product chain', async t => {
|
|
83
|
+
const records = {
|
|
84
|
+
...BASELINE,
|
|
85
|
+
'pdp-wbs.md': `${PLANNING_GATE['pdp-wbs.md']}\n#### WBS-DEMO-10-02: Implement contract\n\nTraceability: \`SRS-DEMO-01\`, \`ATP-DEMO-01\`.\n`,
|
|
86
|
+
'srs.md': BASELINE['srs.md'].replace('WBS-DEMO-10-01', 'WBS-DEMO-10-02'),
|
|
87
|
+
'test-plan.md': BASELINE['test-plan.md'].replace('WBS-DEMO-10-01', 'WBS-DEMO-10-02'),
|
|
88
|
+
'progress.md': `${BASELINE['progress.md']}| WBS-DEMO-10-02 | Implement contract | not-started |\n`,
|
|
89
|
+
'decisions/0001-boundary.md': PLANNING_GATE['decisions/0001-boundary.md'],
|
|
90
|
+
};
|
|
91
|
+
const result = await auditPlanning(await fixture(t, records));
|
|
92
|
+
assert.deepEqual(result.diagnostics, []);
|
|
93
|
+
assert.equal(result.ok, true);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
test('planning closure and baseline traceability classification remain independent', async t => {
|
|
97
|
+
const records = {
|
|
98
|
+
...BASELINE,
|
|
99
|
+
'pdp-wbs.md': `${PLANNING_GATE['pdp-wbs.md']}\n#### WBS-DEMO-10-02: Implement contract\n\nTraceability: \`SRS-DEMO-01\`, \`ATP-DEMO-01\`.\n`,
|
|
100
|
+
'srs.md': BASELINE['srs.md'].replace('WBS-DEMO-10-01', 'WBS-DEMO-10-02'),
|
|
101
|
+
'test-plan.md': BASELINE['test-plan.md'].replace('WBS-DEMO-10-01', 'WBS-DEMO-10-02'),
|
|
102
|
+
'progress.md': `${BASELINE['progress.md'].replace('not-started |', 'planning-complete |')}| WBS-DEMO-10-02 | Implement contract | not-started |\n`,
|
|
103
|
+
'decisions/0001-boundary.md': PLANNING_GATE['decisions/0001-boundary.md'],
|
|
104
|
+
};
|
|
105
|
+
const missingMode = await auditPlanning(await fixture(t, records));
|
|
106
|
+
assert.deepEqual(codes(missingMode), ['completion-mode']);
|
|
107
|
+
const withMode = {
|
|
108
|
+
...records,
|
|
109
|
+
'pdp-wbs.md': records['pdp-wbs.md'].replace(
|
|
110
|
+
'Traceability mode: planning-baseline-review',
|
|
111
|
+
'Traceability mode: planning-baseline-review\n\nCompletion mode: planning-only.',
|
|
112
|
+
),
|
|
113
|
+
};
|
|
114
|
+
const eligible = await auditPlanning(await fixture(t, withMode));
|
|
115
|
+
assert.equal(eligible.ok, true, JSON.stringify(eligible.diagnostics));
|
|
116
|
+
const invalidGate = await auditPlanning(
|
|
117
|
+
await fixture(t, {
|
|
118
|
+
...withMode,
|
|
119
|
+
'pdp-wbs.md': withMode['pdp-wbs.md'].replace(
|
|
120
|
+
'Traceability mode: planning-baseline-review',
|
|
121
|
+
'Traceability mode: unknown',
|
|
122
|
+
),
|
|
123
|
+
}),
|
|
124
|
+
);
|
|
125
|
+
assert.ok(codes(invalidGate).includes('traceability-mode'));
|
|
126
|
+
assert.ok(!codes(invalidGate).includes('completion-mode'));
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
test('planning review accepts existing and legacy accepted ADR status formats', async t => {
|
|
130
|
+
for (const status of [
|
|
131
|
+
'## Status\n\nAccepted.\n',
|
|
132
|
+
'## Status\n\nAccepted\n',
|
|
133
|
+
'- **Status:** Accepted\n',
|
|
134
|
+
]) {
|
|
135
|
+
await t.test(status.trim().replace(/\n/g, ' '), async subtest => {
|
|
136
|
+
const records = {
|
|
137
|
+
...PLANNING_GATE,
|
|
138
|
+
'decisions/0001-boundary.md': `# Boundary\n\n${status}`,
|
|
139
|
+
};
|
|
140
|
+
const result = await auditPlanning(await fixture(subtest, records));
|
|
141
|
+
assert.ok(!codes(result).includes('traceability-mode'), JSON.stringify(result.diagnostics));
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
test('baseline review prose may refer to the work catalogue without doing delivery work', async t => {
|
|
147
|
+
const records = {
|
|
148
|
+
...PLANNING_GATE,
|
|
149
|
+
'pdp-wbs.md': PLANNING_GATE['pdp-wbs.md'].replace(
|
|
150
|
+
'- Reconcile the whole requirements, contracts, work and acceptance catalogue.',
|
|
151
|
+
'- Review the implementation plan, build risks and migration strategy as planning inputs.',
|
|
152
|
+
),
|
|
153
|
+
};
|
|
154
|
+
const result = await auditPlanning(await fixture(t, records));
|
|
155
|
+
assert.ok(!codes(result).includes('traceability-mode'), JSON.stringify(result.diagnostics));
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
test('baseline classification must be declared inside a bounded Phase 10 WBS task', async t => {
|
|
159
|
+
const cases = [
|
|
160
|
+
[
|
|
161
|
+
'unknown',
|
|
162
|
+
{ 'pdp-wbs.md': PLANNING_GATE['pdp-wbs.md'].replace('planning-baseline-review', 'skip') },
|
|
163
|
+
],
|
|
164
|
+
[
|
|
165
|
+
'duplicate',
|
|
166
|
+
{
|
|
167
|
+
'pdp-wbs.md': PLANNING_GATE['pdp-wbs.md'].replace(
|
|
168
|
+
'Tasks:',
|
|
169
|
+
'Traceability mode: planning-baseline-review\n\nTasks:',
|
|
170
|
+
),
|
|
171
|
+
},
|
|
172
|
+
],
|
|
173
|
+
['wrong phase', { 'pdp-wbs.md': PLANNING_GATE['pdp-wbs.md'].replace('Phase 10', 'Phase 20') }],
|
|
174
|
+
['no tasks', { 'pdp-wbs.md': PLANNING_GATE['pdp-wbs.md'].replace('Tasks:', 'Work:') }],
|
|
175
|
+
[
|
|
176
|
+
'no checks',
|
|
177
|
+
{ 'pdp-wbs.md': PLANNING_GATE['pdp-wbs.md'].replace('Acceptance checks:', 'Proof:') },
|
|
178
|
+
],
|
|
179
|
+
[
|
|
180
|
+
'mixed scope',
|
|
181
|
+
{
|
|
182
|
+
'pdp-wbs.md': `${PLANNING_GATE['pdp-wbs.md']}\n- Implement SRS-DEMO-01 through ATP-DEMO-01.\n`,
|
|
183
|
+
},
|
|
184
|
+
],
|
|
185
|
+
[
|
|
186
|
+
'no authority',
|
|
187
|
+
{ 'pdp-wbs.md': PLANNING_GATE['pdp-wbs.md'].replace('Review authority:', 'Reviewer:') },
|
|
188
|
+
],
|
|
189
|
+
[
|
|
190
|
+
'unaccepted authority',
|
|
191
|
+
{
|
|
192
|
+
'decisions/0001-boundary.md': PLANNING_GATE['decisions/0001-boundary.md'].replace(
|
|
193
|
+
'Accepted.',
|
|
194
|
+
'Proposed.',
|
|
195
|
+
),
|
|
196
|
+
},
|
|
197
|
+
],
|
|
198
|
+
[
|
|
199
|
+
'implementation in title',
|
|
200
|
+
{
|
|
201
|
+
'pdp-wbs.md': PLANNING_GATE['pdp-wbs.md'].replace(
|
|
202
|
+
'Review planning baseline',
|
|
203
|
+
'Review baseline and implement SRS-DEMO-01',
|
|
204
|
+
),
|
|
205
|
+
},
|
|
206
|
+
],
|
|
207
|
+
[
|
|
208
|
+
'release task',
|
|
209
|
+
{
|
|
210
|
+
'pdp-wbs.md': PLANNING_GATE['pdp-wbs.md'].replace(
|
|
211
|
+
'- Reconcile the whole requirements, contracts, work and acceptance catalogue.',
|
|
212
|
+
'- Review the baseline and release the feature.',
|
|
213
|
+
),
|
|
214
|
+
},
|
|
215
|
+
],
|
|
216
|
+
[
|
|
217
|
+
'implementation task',
|
|
218
|
+
{
|
|
219
|
+
'pdp-wbs.md': PLANNING_GATE['pdp-wbs.md'].replace(
|
|
220
|
+
'- Reconcile the whole requirements, contracts, work and acceptance catalogue.',
|
|
221
|
+
'- Review the baseline and implement the feature.',
|
|
222
|
+
),
|
|
223
|
+
},
|
|
224
|
+
],
|
|
225
|
+
[
|
|
226
|
+
'implementation acceptance check',
|
|
227
|
+
{
|
|
228
|
+
'pdp-wbs.md': PLANNING_GATE['pdp-wbs.md'].replace(
|
|
229
|
+
'- Confirm the boundary ADR was accepted by its decision owner.',
|
|
230
|
+
'- Confirm the boundary ADR was accepted and deploy the feature.',
|
|
231
|
+
),
|
|
232
|
+
},
|
|
233
|
+
],
|
|
234
|
+
...['Build the checkout backend.', 'Create a service.', 'Run a migration.'].map(instruction => [
|
|
235
|
+
instruction,
|
|
236
|
+
{
|
|
237
|
+
'pdp-wbs.md': PLANNING_GATE['pdp-wbs.md'].replace(
|
|
238
|
+
'- Reconcile the whole requirements, contracts, work and acceptance catalogue.',
|
|
239
|
+
`- ${instruction}`,
|
|
240
|
+
),
|
|
241
|
+
},
|
|
242
|
+
]),
|
|
243
|
+
[
|
|
244
|
+
'unmapped contract in title',
|
|
245
|
+
{
|
|
246
|
+
'pdp-wbs.md': PLANNING_GATE['pdp-wbs.md'].replace(
|
|
247
|
+
'Review planning baseline',
|
|
248
|
+
'Review planning baseline for SRS-DEMO-01',
|
|
249
|
+
),
|
|
250
|
+
},
|
|
251
|
+
],
|
|
252
|
+
[
|
|
253
|
+
'contradictory ADR statuses',
|
|
254
|
+
{
|
|
255
|
+
'decisions/0001-boundary.md': '## Status\n\nAccepted.\n\n- **Status:** Proposed\n',
|
|
256
|
+
},
|
|
257
|
+
],
|
|
258
|
+
[
|
|
259
|
+
'ADR status inside unrelated section',
|
|
260
|
+
{
|
|
261
|
+
'decisions/0001-boundary.md': '# Boundary\n\n## Background\n\n- **Status:** Accepted\n',
|
|
262
|
+
},
|
|
263
|
+
],
|
|
264
|
+
['mapped from SRS', { 'srs.md': BASELINE['srs.md'] }],
|
|
265
|
+
['mapped to ATP', { 'test-plan.md': BASELINE['test-plan.md'] }],
|
|
266
|
+
[
|
|
267
|
+
'misplaced',
|
|
268
|
+
{
|
|
269
|
+
'README.md': `${PLANNING_GATE['README.md']}\nTraceability mode: planning-baseline-review\n`,
|
|
270
|
+
},
|
|
271
|
+
],
|
|
272
|
+
[
|
|
273
|
+
'competing exception',
|
|
274
|
+
{
|
|
275
|
+
'pdp-wbs.md': PLANNING_GATE['pdp-wbs.md'].replace(
|
|
276
|
+
'Tasks:',
|
|
277
|
+
'Traceability exception: authority-only — Review the whole baseline.\n\nTasks:',
|
|
278
|
+
),
|
|
279
|
+
},
|
|
280
|
+
],
|
|
281
|
+
];
|
|
282
|
+
for (const [name, changed] of cases) {
|
|
283
|
+
await t.test(name, async subtest => {
|
|
284
|
+
const result = await auditPlanning(await fixture(subtest, { ...PLANNING_GATE, ...changed }));
|
|
285
|
+
assert.ok(codes(result).includes('traceability-mode'), JSON.stringify(result.diagnostics));
|
|
286
|
+
if (name !== 'misplaced' && name !== 'mapped from SRS') {
|
|
287
|
+
assert.ok(
|
|
288
|
+
result.diagnostics.some(
|
|
289
|
+
diagnostic =>
|
|
290
|
+
diagnostic.id === 'WBS-DEMO-10-01' && diagnostic.code === 'traceability-incoming',
|
|
291
|
+
),
|
|
292
|
+
);
|
|
293
|
+
}
|
|
294
|
+
if (name !== 'misplaced' && name !== 'mapped to ATP' && name !== 'competing exception') {
|
|
295
|
+
assert.ok(
|
|
296
|
+
result.diagnostics.some(
|
|
297
|
+
diagnostic =>
|
|
298
|
+
diagnostic.id === 'WBS-DEMO-10-01' && diagnostic.code === 'traceability-outgoing',
|
|
299
|
+
),
|
|
300
|
+
);
|
|
301
|
+
}
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
test('ordinary unmapped WBS retains its adjacency diagnostics beside a planning gate', async t => {
|
|
307
|
+
const records = {
|
|
308
|
+
...PLANNING_GATE,
|
|
309
|
+
'pdp-wbs.md': `${PLANNING_GATE['pdp-wbs.md']}\n#### WBS-DEMO-10-02: Implement item\n\nTasks:\n\n- Implement the item.\n`,
|
|
310
|
+
'progress.md': `${PLANNING_GATE['progress.md']}| WBS-DEMO-10-02 | Implement item | not-started |\n`,
|
|
311
|
+
};
|
|
312
|
+
const result = await auditPlanning(await fixture(t, records));
|
|
313
|
+
assert.deepEqual(
|
|
314
|
+
result.diagnostics
|
|
315
|
+
.filter(diagnostic => diagnostic.id === 'WBS-DEMO-10-02')
|
|
316
|
+
.map(diagnostic => diagnostic.code),
|
|
317
|
+
['traceability-incoming', 'traceability-outgoing'],
|
|
318
|
+
);
|
|
319
|
+
});
|
|
320
|
+
|
|
60
321
|
test('canonical atomic bullets carry their own inline traceability', async t => {
|
|
61
322
|
const records = {
|
|
62
323
|
...BASELINE,
|
|
@@ -99,6 +360,176 @@ test('ordinary co-occurrence and wildcard matrices cannot manufacture traceabili
|
|
|
99
360
|
);
|
|
100
361
|
});
|
|
101
362
|
|
|
363
|
+
test('technical-only SRS exempts its PRD parent, not its WBS successor', async t => {
|
|
364
|
+
const records = {
|
|
365
|
+
...BASELINE,
|
|
366
|
+
'srs.md':
|
|
367
|
+
'# Contracts\n\n- **SRS-DEMO-01**: Test hygiene. Traceability exception: technical-only — Repository fixture policy owns cleanup.\n\nTraceability: `WBS-DEMO-10-01`\n\n- **SRS-DEMO-02**: Product contract. Traceability: `PRD-DEMO-01`, `WBS-DEMO-10-01`.\n',
|
|
368
|
+
'prd.md': BASELINE['prd.md'].replace('`SRS-DEMO-01`', '`SRS-DEMO-02`'),
|
|
369
|
+
};
|
|
370
|
+
const result = await auditPlanning(await fixture(t, records));
|
|
371
|
+
assert.deepEqual(result.diagnostics, []);
|
|
372
|
+
const withoutException = {
|
|
373
|
+
...records,
|
|
374
|
+
'srs.md': records['srs.md'].replace(
|
|
375
|
+
' Traceability exception: technical-only — Repository fixture policy owns cleanup.',
|
|
376
|
+
'',
|
|
377
|
+
),
|
|
378
|
+
};
|
|
379
|
+
const missingParent = await auditPlanning(await fixture(t, withoutException));
|
|
380
|
+
assert.ok(
|
|
381
|
+
missingParent.diagnostics.some(
|
|
382
|
+
diagnostic => diagnostic.id === 'SRS-DEMO-01' && diagnostic.code === 'traceability-incoming',
|
|
383
|
+
),
|
|
384
|
+
);
|
|
385
|
+
const missingSuccessor = await auditPlanning(
|
|
386
|
+
await fixture(t, {
|
|
387
|
+
...records,
|
|
388
|
+
'srs.md': records['srs.md'].replace('Traceability: `WBS-DEMO-10-01`\n', ''),
|
|
389
|
+
'pdp-wbs.md': records['pdp-wbs.md'].replace('`SRS-DEMO-01`, ', ''),
|
|
390
|
+
}),
|
|
391
|
+
);
|
|
392
|
+
assert.ok(
|
|
393
|
+
missingSuccessor.diagnostics.some(
|
|
394
|
+
diagnostic => diagnostic.id === 'SRS-DEMO-01' && diagnostic.code === 'traceability-outgoing',
|
|
395
|
+
),
|
|
396
|
+
);
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
test('authority-only WBS exempts its ATP successor, not its SRS parent', async t => {
|
|
400
|
+
const records = {
|
|
401
|
+
...BASELINE,
|
|
402
|
+
'pdp-wbs.md': `${BASELINE['pdp-wbs.md']}\n#### WBS-DEMO-10-02: Review baseline\n\nTraceability exception: authority-only — Retain the baseline review disposition.\n\nTraceability: \`SRS-DEMO-01\`.\n`,
|
|
403
|
+
'progress.md': `${BASELINE['progress.md']}| WBS-DEMO-10-02 | Review | not-started |\n`,
|
|
404
|
+
};
|
|
405
|
+
const result = await auditPlanning(await fixture(t, records));
|
|
406
|
+
assert.deepEqual(result.diagnostics, []);
|
|
407
|
+
const withoutException = {
|
|
408
|
+
...records,
|
|
409
|
+
'pdp-wbs.md': records['pdp-wbs.md'].replace(
|
|
410
|
+
'Traceability exception: authority-only — Retain the baseline review disposition.\n\n',
|
|
411
|
+
'',
|
|
412
|
+
),
|
|
413
|
+
};
|
|
414
|
+
const missingSuccessor = await auditPlanning(await fixture(t, withoutException));
|
|
415
|
+
assert.ok(
|
|
416
|
+
missingSuccessor.diagnostics.some(
|
|
417
|
+
diagnostic =>
|
|
418
|
+
diagnostic.id === 'WBS-DEMO-10-02' && diagnostic.code === 'traceability-outgoing',
|
|
419
|
+
),
|
|
420
|
+
);
|
|
421
|
+
const missingParent = await auditPlanning(
|
|
422
|
+
await fixture(t, {
|
|
423
|
+
...records,
|
|
424
|
+
'pdp-wbs.md': records['pdp-wbs.md'].replace('Traceability: `SRS-DEMO-01`.\n', ''),
|
|
425
|
+
}),
|
|
426
|
+
);
|
|
427
|
+
assert.ok(
|
|
428
|
+
missingParent.diagnostics.some(
|
|
429
|
+
diagnostic =>
|
|
430
|
+
diagnostic.id === 'WBS-DEMO-10-02' && diagnostic.code === 'traceability-incoming',
|
|
431
|
+
),
|
|
432
|
+
);
|
|
433
|
+
});
|
|
434
|
+
|
|
435
|
+
test('malformed, repeated and wrong-kind exceptions fail closed', async t => {
|
|
436
|
+
const invalid = [
|
|
437
|
+
'Traceability exception: technical-only —',
|
|
438
|
+
'Traceability exception: technical-only — !',
|
|
439
|
+
'Traceability exception: unknown — Rationale.',
|
|
440
|
+
'Traceability exception: authority-only — Rationale.',
|
|
441
|
+
'Traceability exception: technical-only — Rationale. Traceability exception: technical-only — Again.',
|
|
442
|
+
];
|
|
443
|
+
for (const field of invalid) {
|
|
444
|
+
const records = {
|
|
445
|
+
...BASELINE,
|
|
446
|
+
'srs.md': BASELINE['srs.md'].replace(
|
|
447
|
+
'### SRS-DEMO-01: Contract\n',
|
|
448
|
+
`- **SRS-DEMO-01**: Contract. ${field}\n`,
|
|
449
|
+
),
|
|
450
|
+
};
|
|
451
|
+
const result = await auditPlanning(await fixture(t, records));
|
|
452
|
+
assert.ok(
|
|
453
|
+
result.diagnostics.some(diagnostic => diagnostic.code === 'traceability-exception'),
|
|
454
|
+
field,
|
|
455
|
+
);
|
|
456
|
+
}
|
|
457
|
+
const wrongWbs = {
|
|
458
|
+
...BASELINE,
|
|
459
|
+
'pdp-wbs.md': BASELINE['pdp-wbs.md'].replace(
|
|
460
|
+
'Completion checks:',
|
|
461
|
+
'Traceability exception: technical-only — Not an SRS.\n\nCompletion checks:',
|
|
462
|
+
),
|
|
463
|
+
};
|
|
464
|
+
assert.ok(
|
|
465
|
+
codes(await auditPlanning(await fixture(t, wrongWbs))).includes('traceability-exception'),
|
|
466
|
+
);
|
|
467
|
+
});
|
|
468
|
+
|
|
469
|
+
test('an exception outside its own declaration cannot exempt a missing link', async t => {
|
|
470
|
+
const records = {
|
|
471
|
+
...BASELINE,
|
|
472
|
+
'srs.md':
|
|
473
|
+
'# Contracts\n\nTraceability exception: technical-only — Preamble.\n\n- **SRS-DEMO-02**: Unmapped contract.\n- **SRS-DEMO-01**: Contract. Traceability exception: technical-only — Different record.\n\nTraceability: `PRD-DEMO-01`, `WBS-DEMO-10-01`\n',
|
|
474
|
+
'pdp-wbs.md': `${BASELINE['pdp-wbs.md'].replace(
|
|
475
|
+
'Dependencies: none.',
|
|
476
|
+
'Dependencies: none.\n\nTraceability exception: authority-only — Phase preamble.',
|
|
477
|
+
)}\n#### WBS-DEMO-10-02: Unmapped task\n`,
|
|
478
|
+
'progress.md': `${BASELINE['progress.md']}| WBS-DEMO-10-02 | Unmapped | not-started |\n`,
|
|
479
|
+
};
|
|
480
|
+
const result = await auditPlanning(await fixture(t, records));
|
|
481
|
+
for (const [id, code] of [
|
|
482
|
+
['SRS-DEMO-02', 'traceability-incoming'],
|
|
483
|
+
['WBS-DEMO-10-02', 'traceability-outgoing'],
|
|
484
|
+
]) {
|
|
485
|
+
assert.ok(
|
|
486
|
+
result.diagnostics.some(diagnostic => diagnostic.id === id && diagnostic.code === code),
|
|
487
|
+
);
|
|
488
|
+
}
|
|
489
|
+
assert.ok(result.diagnostics.some(diagnostic => diagnostic.code === 'traceability-exception'));
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
test('classified gates retain reference and completed-prerequisite checks', async t => {
|
|
493
|
+
const records = {
|
|
494
|
+
...BASELINE,
|
|
495
|
+
'pdp-wbs.md': `${BASELINE['pdp-wbs.md']}\n#### WBS-DEMO-10-02: Review gate\n\nDependencies: WBS-DEMO-10-01.\n\nTraceability exception: authority-only — Retain the review disposition.\n\nTraceability: \`SRS-DEMO-01\`.\n\nReview \`SRS-MISSING-01\`.\n`,
|
|
496
|
+
'progress.md': `${BASELINE['progress.md']}| WBS-DEMO-10-02 | Review | verified |\n`,
|
|
497
|
+
};
|
|
498
|
+
const result = await auditPlanning(await fixture(t, records));
|
|
499
|
+
assert.ok(
|
|
500
|
+
result.diagnostics.some(
|
|
501
|
+
diagnostic => diagnostic.code === 'undefined-reference' && diagnostic.id === 'SRS-MISSING-01',
|
|
502
|
+
),
|
|
503
|
+
);
|
|
504
|
+
assert.ok(
|
|
505
|
+
result.diagnostics.some(
|
|
506
|
+
diagnostic => diagnostic.code === 'dependency-state' && diagnostic.id === 'WBS-DEMO-10-02',
|
|
507
|
+
),
|
|
508
|
+
);
|
|
509
|
+
assert.ok(
|
|
510
|
+
!result.diagnostics.some(
|
|
511
|
+
diagnostic =>
|
|
512
|
+
diagnostic.code === 'traceability-outgoing' && diagnostic.id === 'WBS-DEMO-10-02',
|
|
513
|
+
),
|
|
514
|
+
);
|
|
515
|
+
});
|
|
516
|
+
|
|
517
|
+
test('fences and comments cannot annotate a definition', async t => {
|
|
518
|
+
const records = {
|
|
519
|
+
...BASELINE,
|
|
520
|
+
'pdp-wbs.md': `${BASELINE['pdp-wbs.md']}\n#### WBS-DEMO-10-02: Review gate\n\nTraceability: \`SRS-DEMO-01\`.\n\n\`\`\`md\nTraceability exception: authority-only — Example only.\n\`\`\`\n\n<!-- Traceability exception: authority-only — Not authority. -->\n`,
|
|
521
|
+
'progress.md': `${BASELINE['progress.md']}| WBS-DEMO-10-02 | Review | not-started |\n`,
|
|
522
|
+
};
|
|
523
|
+
const result = await auditPlanning(await fixture(t, records));
|
|
524
|
+
assert.ok(
|
|
525
|
+
result.diagnostics.some(
|
|
526
|
+
diagnostic =>
|
|
527
|
+
diagnostic.code === 'traceability-outgoing' && diagnostic.id === 'WBS-DEMO-10-02',
|
|
528
|
+
),
|
|
529
|
+
);
|
|
530
|
+
assert.ok(!codes(result).includes('traceability-exception'));
|
|
531
|
+
});
|
|
532
|
+
|
|
102
533
|
test('lightweight records do not require absent baseline files or charts', async t => {
|
|
103
534
|
const directory = await fixture(t, {
|
|
104
535
|
'README.md': '# Lightweight\n',
|
|
@@ -191,6 +622,68 @@ test('completed tasks cannot hide unfinished recorded prerequisites', async t =>
|
|
|
191
622
|
);
|
|
192
623
|
});
|
|
193
624
|
|
|
625
|
+
test('planning-complete requires a task-local mode in full and lightweight audits', async t => {
|
|
626
|
+
const progress = BASELINE['progress.md'].replace('not-started |', 'planning-complete |');
|
|
627
|
+
for (const lightweight of [false, true]) {
|
|
628
|
+
const rejected = await auditPlanning(
|
|
629
|
+
await fixture(t, { ...BASELINE, 'progress.md': progress }),
|
|
630
|
+
{ lightweight },
|
|
631
|
+
);
|
|
632
|
+
assert.ok(codes(rejected).includes('completion-mode'));
|
|
633
|
+
const approved = await auditPlanning(
|
|
634
|
+
await fixture(t, {
|
|
635
|
+
...BASELINE,
|
|
636
|
+
'pdp-wbs.md': BASELINE['pdp-wbs.md'].replace(
|
|
637
|
+
'Completion checks:',
|
|
638
|
+
'Completion mode: planning-only.\n\nCompletion checks:',
|
|
639
|
+
),
|
|
640
|
+
'progress.md': progress,
|
|
641
|
+
}),
|
|
642
|
+
{ lightweight },
|
|
643
|
+
);
|
|
644
|
+
assert.equal(approved.ok, true, JSON.stringify(approved.diagnostics));
|
|
645
|
+
}
|
|
646
|
+
});
|
|
647
|
+
|
|
648
|
+
test('planning-complete prerequisites allow recorded closure but never unblock successors', async t => {
|
|
649
|
+
const wbs = `${BASELINE['pdp-wbs.md'].replace('Completion checks:', 'Completion mode: planning-only.\n\nCompletion checks:')}\n#### WBS-DEMO-10-02: Dependent review\n\nDependencies: WBS-DEMO-10-01.\n\nTraceability exception: authority-only — Retain the review decision.\n\nTraceability: \`SRS-DEMO-01\`.\n`;
|
|
650
|
+
const progress = `${BASELINE['progress.md'].replace('not-started |', 'planning-complete |')}| WBS-DEMO-10-02 | Dependent | planning-complete |\n`;
|
|
651
|
+
const records = { ...BASELINE, 'pdp-wbs.md': wbs, 'progress.md': progress };
|
|
652
|
+
const completed = await auditPlanning(await fixture(t, records));
|
|
653
|
+
assert.ok(codes(completed).includes('completion-mode'));
|
|
654
|
+
assert.ok(!codes(completed).includes('dependency-state'));
|
|
655
|
+
const blocked = await auditPlanning(
|
|
656
|
+
await fixture(t, {
|
|
657
|
+
...records,
|
|
658
|
+
'progress.md': progress.replace('Dependent | planning-complete', 'Dependent | blocked'),
|
|
659
|
+
}),
|
|
660
|
+
);
|
|
661
|
+
assert.ok(!codes(blocked).includes('dependency-state'));
|
|
662
|
+
assert.ok(!codes(blocked).includes('completion-mode'));
|
|
663
|
+
const unfinished = await auditPlanning(
|
|
664
|
+
await fixture(t, {
|
|
665
|
+
...records,
|
|
666
|
+
'progress.md': progress.replace('planning-complete |', 'implementation-complete |'),
|
|
667
|
+
}),
|
|
668
|
+
);
|
|
669
|
+
assert.ok(codes(unfinished).includes('dependency-state'));
|
|
670
|
+
const waived = await auditPlanning(
|
|
671
|
+
await fixture(t, {
|
|
672
|
+
...records,
|
|
673
|
+
'progress.md': progress.replace('planning-complete |', 'waived |'),
|
|
674
|
+
}),
|
|
675
|
+
);
|
|
676
|
+
assert.ok(codes(waived).includes('dependency-state'));
|
|
677
|
+
const noMode = await auditPlanning(
|
|
678
|
+
await fixture(t, {
|
|
679
|
+
...records,
|
|
680
|
+
'pdp-wbs.md': wbs.replace('Completion mode: planning-only.\n\n', ''),
|
|
681
|
+
}),
|
|
682
|
+
);
|
|
683
|
+
assert.ok(codes(noMode).includes('completion-mode'));
|
|
684
|
+
assert.ok(codes(noMode).includes('dependency-state'));
|
|
685
|
+
});
|
|
686
|
+
|
|
194
687
|
test('local Markdown destinations, fragments and reference links are checked', async t => {
|
|
195
688
|
const records = {
|
|
196
689
|
...BASELINE,
|