cabloy 5.1.199 → 5.1.201
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 +9 -9
- package/repo-agent-governance/skills/cabloy-spec-generation/references/canonical-spec-input.md +2 -0
- package/repo-agent-governance/skills/cabloy-spec-generation/references/repo-specs-document-set.md +1 -1
- package/repo-agent-governance/skills/cabloy-spec-generation/references/traceability-and-status-rules.md +1 -1
- package/repo-agent-governance/tests/spec-audit.test.mjs +214 -0
- package/repo-agent-governance/tools/spec-audit/audit.mjs +80 -8
- package/repo-docs/assets/img/cabloy-admin-ssr-cover-en.png +0 -0
- package/repo-docs/index.md +6 -0
package/.cabloy-version
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
5.1.
|
|
1
|
+
5.1.201
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 5.1.201
|
|
4
|
+
|
|
5
|
+
### Features
|
|
6
|
+
|
|
7
|
+
- Support scoped spec traceability exceptions.
|
|
8
|
+
|
|
9
|
+
### Bug Fixes
|
|
10
|
+
|
|
11
|
+
- Fix the CabloyJS Admin SSR video.
|
|
12
|
+
|
|
13
|
+
### Improvements
|
|
14
|
+
|
|
15
|
+
- Add the CabloyJS Admin SSR video to the demonstrations.
|
|
16
|
+
- Refine the spec parser.
|
|
17
|
+
|
|
18
|
+
## 5.1.200
|
|
19
|
+
|
|
20
|
+
### Features
|
|
21
|
+
|
|
22
|
+
- Apply feature updates.
|
|
23
|
+
|
|
3
24
|
## 5.1.199
|
|
4
25
|
|
|
5
26
|
### Improvements
|
package/README.md
CHANGED
|
@@ -36,6 +36,12 @@ For Cabloy Start, clone the [Cabloy Start repository](https://github.com/cabloy/
|
|
|
36
36
|
|
|
37
37
|
See the [AI Spec-Driven Development](https://cabloy.com/ai/ai-spec-driven-development).
|
|
38
38
|
|
|
39
|
+
## Demonstrations(Videos)
|
|
40
|
+
|
|
41
|
+
### 1. Can an Admin Site Use SSR? CabloyJS in Three Practical Demos (Duration: 1:16)
|
|
42
|
+
|
|
43
|
+
[](https://youtu.be/786IQhRdr1I)
|
|
44
|
+
|
|
39
45
|
## Editions
|
|
40
46
|
|
|
41
47
|
Cabloy is available through two complete project baselines, each maintained in its own repository:
|
package/package.json
CHANGED
|
@@ -221,7 +221,7 @@
|
|
|
221
221
|
{
|
|
222
222
|
"adapter": "codex",
|
|
223
223
|
"category": "skill",
|
|
224
|
-
"sha256": "
|
|
224
|
+
"sha256": "df49ea0a8485de0ea6cf345199481836924eed0b68608abf4e6ed829f620b72e",
|
|
225
225
|
"source": "skills/cabloy-spec-generation/references/canonical-spec-input.md",
|
|
226
226
|
"target": ".agents/skills/cabloy-spec-generation/references/canonical-spec-input.md"
|
|
227
227
|
},
|
|
@@ -235,14 +235,14 @@
|
|
|
235
235
|
{
|
|
236
236
|
"adapter": "codex",
|
|
237
237
|
"category": "skill",
|
|
238
|
-
"sha256": "
|
|
238
|
+
"sha256": "95fb6ad6f1f85373a6565442e4f0f50582acafd2bef5c8f1f0056123b0996bc8",
|
|
239
239
|
"source": "skills/cabloy-spec-generation/references/repo-specs-document-set.md",
|
|
240
240
|
"target": ".agents/skills/cabloy-spec-generation/references/repo-specs-document-set.md"
|
|
241
241
|
},
|
|
242
242
|
{
|
|
243
243
|
"adapter": "codex",
|
|
244
244
|
"category": "skill",
|
|
245
|
-
"sha256": "
|
|
245
|
+
"sha256": "45b83917c079306fb56e0c2cf406a894f7c18c9fd8732f97f9902ed02cf32594",
|
|
246
246
|
"source": "skills/cabloy-spec-generation/references/traceability-and-status-rules.md",
|
|
247
247
|
"target": ".agents/skills/cabloy-spec-generation/references/traceability-and-status-rules.md"
|
|
248
248
|
},
|
|
@@ -557,7 +557,7 @@
|
|
|
557
557
|
{
|
|
558
558
|
"adapter": "claude",
|
|
559
559
|
"category": "skill",
|
|
560
|
-
"sha256": "
|
|
560
|
+
"sha256": "df49ea0a8485de0ea6cf345199481836924eed0b68608abf4e6ed829f620b72e",
|
|
561
561
|
"source": "skills/cabloy-spec-generation/references/canonical-spec-input.md",
|
|
562
562
|
"target": ".claude/skills/cabloy-spec-generation/references/canonical-spec-input.md"
|
|
563
563
|
},
|
|
@@ -571,14 +571,14 @@
|
|
|
571
571
|
{
|
|
572
572
|
"adapter": "claude",
|
|
573
573
|
"category": "skill",
|
|
574
|
-
"sha256": "
|
|
574
|
+
"sha256": "95fb6ad6f1f85373a6565442e4f0f50582acafd2bef5c8f1f0056123b0996bc8",
|
|
575
575
|
"source": "skills/cabloy-spec-generation/references/repo-specs-document-set.md",
|
|
576
576
|
"target": ".claude/skills/cabloy-spec-generation/references/repo-specs-document-set.md"
|
|
577
577
|
},
|
|
578
578
|
{
|
|
579
579
|
"adapter": "claude",
|
|
580
580
|
"category": "skill",
|
|
581
|
-
"sha256": "
|
|
581
|
+
"sha256": "45b83917c079306fb56e0c2cf406a894f7c18c9fd8732f97f9902ed02cf32594",
|
|
582
582
|
"source": "skills/cabloy-spec-generation/references/traceability-and-status-rules.md",
|
|
583
583
|
"target": ".claude/skills/cabloy-spec-generation/references/traceability-and-status-rules.md"
|
|
584
584
|
},
|
|
@@ -907,7 +907,7 @@
|
|
|
907
907
|
{
|
|
908
908
|
"adapter": "cursor",
|
|
909
909
|
"category": "skill",
|
|
910
|
-
"sha256": "
|
|
910
|
+
"sha256": "df49ea0a8485de0ea6cf345199481836924eed0b68608abf4e6ed829f620b72e",
|
|
911
911
|
"source": "skills/cabloy-spec-generation/references/canonical-spec-input.md",
|
|
912
912
|
"target": ".cursor/skills/cabloy-spec-generation/references/canonical-spec-input.md"
|
|
913
913
|
},
|
|
@@ -921,14 +921,14 @@
|
|
|
921
921
|
{
|
|
922
922
|
"adapter": "cursor",
|
|
923
923
|
"category": "skill",
|
|
924
|
-
"sha256": "
|
|
924
|
+
"sha256": "95fb6ad6f1f85373a6565442e4f0f50582acafd2bef5c8f1f0056123b0996bc8",
|
|
925
925
|
"source": "skills/cabloy-spec-generation/references/repo-specs-document-set.md",
|
|
926
926
|
"target": ".cursor/skills/cabloy-spec-generation/references/repo-specs-document-set.md"
|
|
927
927
|
},
|
|
928
928
|
{
|
|
929
929
|
"adapter": "cursor",
|
|
930
930
|
"category": "skill",
|
|
931
|
-
"sha256": "
|
|
931
|
+
"sha256": "45b83917c079306fb56e0c2cf406a894f7c18c9fd8732f97f9902ed02cf32594",
|
|
932
932
|
"source": "skills/cabloy-spec-generation/references/traceability-and-status-rules.md",
|
|
933
933
|
"target": ".cursor/skills/cabloy-spec-generation/references/traceability-and-status-rules.md"
|
|
934
934
|
},
|
package/repo-agent-governance/skills/cabloy-spec-generation/references/canonical-spec-input.md
CHANGED
|
@@ -17,6 +17,8 @@ References in matrices, related-record lists, progress, evidence, templates, wil
|
|
|
17
17
|
|
|
18
18
|
Use exact instantiated IDs in declaration-body Traceability. A wildcard or abbreviated range is an aggregate summary, not an exact association or substitute for missing definitions. Keep associations explicit even if a downstream summary matrix repeats them. The audit must derive associations from the declaration body, not nearby unrelated sections.
|
|
19
19
|
|
|
20
|
+
For a genuinely cross-cutting engineering contract without a product requirement, an atomic SRS bullet may carry `Traceability exception: technical-only — <record-specific rationale>` on its definition line. This exempts only the incoming PRD association; its outgoing WBS mapping remains required. For a documentary planning or release gate with no independent executable scenario, a WBS task may carry `Traceability exception: authority-only — <record-specific rationale>` in its declaration body. This exempts only the outgoing ATP association; its incoming SRS mapping remains required. Use exactly one well-formed field on the correct definition and explain the alternative authority or retained documentary checks. The auditor rejects malformed, empty, duplicated, or wrong-kind exceptions. Neither classification waives the underlying contract, delivery checks, applicable ATPs, revision-scoped evidence, or release approval; never use one merely to silence a missing real link.
|
|
21
|
+
|
|
20
22
|
## Minimal connected example
|
|
21
23
|
|
|
22
24
|
These are neutral **syntax examples**, not business requirements to copy into a real suite. All four IDs are exact and unique. Production bodies must describe the approved domain.
|
package/repo-agent-governance/skills/cabloy-spec-generation/references/repo-specs-document-set.md
CHANGED
|
@@ -137,7 +137,7 @@ Every WBS entry should state:
|
|
|
137
137
|
- primary source areas or ownership boundaries;
|
|
138
138
|
- bounded tasks;
|
|
139
139
|
- acceptance/completion checks;
|
|
140
|
-
- linked PRD, SRS, and ATP identifiers;
|
|
140
|
+
- linked PRD, SRS, and ATP identifiers where a real adjacent-authority association exists; a genuine documentary planning/release gate without an independent ATP uses the explicit `authority-only` classification and retains its SRS association and review evidence as specified in `canonical-spec-input.md`;
|
|
141
141
|
- whether it is planned, implemented, or awaiting evidence.
|
|
142
142
|
|
|
143
143
|
Begin with a documentation/decision implementation gate before feature work. For differing Web/Admin strategies, split shared-site integration and independent-site delivery into separate frontend tasks when their source facts, dependencies, or proof differ. An unresolved strategy or exact runtime identifier may block only the affected implementation task; preserve runnable discovery work and unaffected backend or audience work as accurately actionable. Prefer vertical, verifiable increments. Include migration and release hardening as explicit work. Keep `implementation-complete` distinct from `verified`.
|
|
@@ -47,7 +47,7 @@ Minimum cardinality for a material in-scope requirement:
|
|
|
47
47
|
- at least one ATP scenario;
|
|
48
48
|
- zero evidence records before execution, and at least one retained evidence record before `verified`.
|
|
49
49
|
|
|
50
|
-
A single ATP may prove several related requirements, but the matrices must make the relationship explicit. A WBS item may cover several contracts, but it still needs bounded completion checks.
|
|
50
|
+
A single ATP may prove several related requirements, but the matrices must make the relationship explicit. A WBS item may cover several contracts, but it still needs bounded completion checks. The full-chain minimum applies to material in-scope product requirements; a separately sourced engineering obligation or a documentary coordination gate need not invent a product parent or an executable ATP. Use the narrowly scoped declaration-local `Traceability exception` classifications in `canonical-spec-input.md` only for those genuine cases. The unaffected direction still needs an exact-ID link, and review/closure still needs applicable retained proof. An exception is not a deferred status, evidence waiver, acceptance result, or release approval.
|
|
51
51
|
|
|
52
52
|
## Authority-first updates
|
|
53
53
|
|
|
@@ -99,6 +99,176 @@ test('ordinary co-occurrence and wildcard matrices cannot manufacture traceabili
|
|
|
99
99
|
);
|
|
100
100
|
});
|
|
101
101
|
|
|
102
|
+
test('technical-only SRS exempts its PRD parent, not its WBS successor', async t => {
|
|
103
|
+
const records = {
|
|
104
|
+
...BASELINE,
|
|
105
|
+
'srs.md':
|
|
106
|
+
'# 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',
|
|
107
|
+
'prd.md': BASELINE['prd.md'].replace('`SRS-DEMO-01`', '`SRS-DEMO-02`'),
|
|
108
|
+
};
|
|
109
|
+
const result = await auditPlanning(await fixture(t, records));
|
|
110
|
+
assert.deepEqual(result.diagnostics, []);
|
|
111
|
+
const withoutException = {
|
|
112
|
+
...records,
|
|
113
|
+
'srs.md': records['srs.md'].replace(
|
|
114
|
+
' Traceability exception: technical-only — Repository fixture policy owns cleanup.',
|
|
115
|
+
'',
|
|
116
|
+
),
|
|
117
|
+
};
|
|
118
|
+
const missingParent = await auditPlanning(await fixture(t, withoutException));
|
|
119
|
+
assert.ok(
|
|
120
|
+
missingParent.diagnostics.some(
|
|
121
|
+
diagnostic => diagnostic.id === 'SRS-DEMO-01' && diagnostic.code === 'traceability-incoming',
|
|
122
|
+
),
|
|
123
|
+
);
|
|
124
|
+
const missingSuccessor = await auditPlanning(
|
|
125
|
+
await fixture(t, {
|
|
126
|
+
...records,
|
|
127
|
+
'srs.md': records['srs.md'].replace('Traceability: `WBS-DEMO-10-01`\n', ''),
|
|
128
|
+
'pdp-wbs.md': records['pdp-wbs.md'].replace('`SRS-DEMO-01`, ', ''),
|
|
129
|
+
}),
|
|
130
|
+
);
|
|
131
|
+
assert.ok(
|
|
132
|
+
missingSuccessor.diagnostics.some(
|
|
133
|
+
diagnostic => diagnostic.id === 'SRS-DEMO-01' && diagnostic.code === 'traceability-outgoing',
|
|
134
|
+
),
|
|
135
|
+
);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
test('authority-only WBS exempts its ATP successor, not its SRS parent', async t => {
|
|
139
|
+
const records = {
|
|
140
|
+
...BASELINE,
|
|
141
|
+
'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`,
|
|
142
|
+
'progress.md': `${BASELINE['progress.md']}| WBS-DEMO-10-02 | Review | not-started |\n`,
|
|
143
|
+
};
|
|
144
|
+
const result = await auditPlanning(await fixture(t, records));
|
|
145
|
+
assert.deepEqual(result.diagnostics, []);
|
|
146
|
+
const withoutException = {
|
|
147
|
+
...records,
|
|
148
|
+
'pdp-wbs.md': records['pdp-wbs.md'].replace(
|
|
149
|
+
'Traceability exception: authority-only — Retain the baseline review disposition.\n\n',
|
|
150
|
+
'',
|
|
151
|
+
),
|
|
152
|
+
};
|
|
153
|
+
const missingSuccessor = await auditPlanning(await fixture(t, withoutException));
|
|
154
|
+
assert.ok(
|
|
155
|
+
missingSuccessor.diagnostics.some(
|
|
156
|
+
diagnostic =>
|
|
157
|
+
diagnostic.id === 'WBS-DEMO-10-02' && diagnostic.code === 'traceability-outgoing',
|
|
158
|
+
),
|
|
159
|
+
);
|
|
160
|
+
const missingParent = await auditPlanning(
|
|
161
|
+
await fixture(t, {
|
|
162
|
+
...records,
|
|
163
|
+
'pdp-wbs.md': records['pdp-wbs.md'].replace('Traceability: `SRS-DEMO-01`.\n', ''),
|
|
164
|
+
}),
|
|
165
|
+
);
|
|
166
|
+
assert.ok(
|
|
167
|
+
missingParent.diagnostics.some(
|
|
168
|
+
diagnostic =>
|
|
169
|
+
diagnostic.id === 'WBS-DEMO-10-02' && diagnostic.code === 'traceability-incoming',
|
|
170
|
+
),
|
|
171
|
+
);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
test('malformed, repeated and wrong-kind exceptions fail closed', async t => {
|
|
175
|
+
const invalid = [
|
|
176
|
+
'Traceability exception: technical-only —',
|
|
177
|
+
'Traceability exception: technical-only — !',
|
|
178
|
+
'Traceability exception: unknown — Rationale.',
|
|
179
|
+
'Traceability exception: authority-only — Rationale.',
|
|
180
|
+
'Traceability exception: technical-only — Rationale. Traceability exception: technical-only — Again.',
|
|
181
|
+
];
|
|
182
|
+
for (const field of invalid) {
|
|
183
|
+
const records = {
|
|
184
|
+
...BASELINE,
|
|
185
|
+
'srs.md': BASELINE['srs.md'].replace(
|
|
186
|
+
'### SRS-DEMO-01: Contract\n',
|
|
187
|
+
`- **SRS-DEMO-01**: Contract. ${field}\n`,
|
|
188
|
+
),
|
|
189
|
+
};
|
|
190
|
+
const result = await auditPlanning(await fixture(t, records));
|
|
191
|
+
assert.ok(
|
|
192
|
+
result.diagnostics.some(diagnostic => diagnostic.code === 'traceability-exception'),
|
|
193
|
+
field,
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
const wrongWbs = {
|
|
197
|
+
...BASELINE,
|
|
198
|
+
'pdp-wbs.md': BASELINE['pdp-wbs.md'].replace(
|
|
199
|
+
'Completion checks:',
|
|
200
|
+
'Traceability exception: technical-only — Not an SRS.\n\nCompletion checks:',
|
|
201
|
+
),
|
|
202
|
+
};
|
|
203
|
+
assert.ok(
|
|
204
|
+
codes(await auditPlanning(await fixture(t, wrongWbs))).includes('traceability-exception'),
|
|
205
|
+
);
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
test('an exception outside its own declaration cannot exempt a missing link', async t => {
|
|
209
|
+
const records = {
|
|
210
|
+
...BASELINE,
|
|
211
|
+
'srs.md':
|
|
212
|
+
'# 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',
|
|
213
|
+
'pdp-wbs.md': `${BASELINE['pdp-wbs.md'].replace(
|
|
214
|
+
'Dependencies: none.',
|
|
215
|
+
'Dependencies: none.\n\nTraceability exception: authority-only — Phase preamble.',
|
|
216
|
+
)}\n#### WBS-DEMO-10-02: Unmapped task\n`,
|
|
217
|
+
'progress.md': `${BASELINE['progress.md']}| WBS-DEMO-10-02 | Unmapped | not-started |\n`,
|
|
218
|
+
};
|
|
219
|
+
const result = await auditPlanning(await fixture(t, records));
|
|
220
|
+
for (const [id, code] of [
|
|
221
|
+
['SRS-DEMO-02', 'traceability-incoming'],
|
|
222
|
+
['WBS-DEMO-10-02', 'traceability-outgoing'],
|
|
223
|
+
]) {
|
|
224
|
+
assert.ok(
|
|
225
|
+
result.diagnostics.some(diagnostic => diagnostic.id === id && diagnostic.code === code),
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
assert.ok(result.diagnostics.some(diagnostic => diagnostic.code === 'traceability-exception'));
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
test('classified gates retain reference and completed-prerequisite checks', async t => {
|
|
232
|
+
const records = {
|
|
233
|
+
...BASELINE,
|
|
234
|
+
'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`,
|
|
235
|
+
'progress.md': `${BASELINE['progress.md']}| WBS-DEMO-10-02 | Review | verified |\n`,
|
|
236
|
+
};
|
|
237
|
+
const result = await auditPlanning(await fixture(t, records));
|
|
238
|
+
assert.ok(
|
|
239
|
+
result.diagnostics.some(
|
|
240
|
+
diagnostic => diagnostic.code === 'undefined-reference' && diagnostic.id === 'SRS-MISSING-01',
|
|
241
|
+
),
|
|
242
|
+
);
|
|
243
|
+
assert.ok(
|
|
244
|
+
result.diagnostics.some(
|
|
245
|
+
diagnostic => diagnostic.code === 'dependency-state' && diagnostic.id === 'WBS-DEMO-10-02',
|
|
246
|
+
),
|
|
247
|
+
);
|
|
248
|
+
assert.ok(
|
|
249
|
+
!result.diagnostics.some(
|
|
250
|
+
diagnostic =>
|
|
251
|
+
diagnostic.code === 'traceability-outgoing' && diagnostic.id === 'WBS-DEMO-10-02',
|
|
252
|
+
),
|
|
253
|
+
);
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
test('fences and comments cannot annotate a definition', async t => {
|
|
257
|
+
const records = {
|
|
258
|
+
...BASELINE,
|
|
259
|
+
'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`,
|
|
260
|
+
'progress.md': `${BASELINE['progress.md']}| WBS-DEMO-10-02 | Review | not-started |\n`,
|
|
261
|
+
};
|
|
262
|
+
const result = await auditPlanning(await fixture(t, records));
|
|
263
|
+
assert.ok(
|
|
264
|
+
result.diagnostics.some(
|
|
265
|
+
diagnostic =>
|
|
266
|
+
diagnostic.code === 'traceability-outgoing' && diagnostic.id === 'WBS-DEMO-10-02',
|
|
267
|
+
),
|
|
268
|
+
);
|
|
269
|
+
assert.ok(!codes(result).includes('traceability-exception'));
|
|
270
|
+
});
|
|
271
|
+
|
|
102
272
|
test('lightweight records do not require absent baseline files or charts', async t => {
|
|
103
273
|
const directory = await fixture(t, {
|
|
104
274
|
'README.md': '# Lightweight\n',
|
|
@@ -208,6 +378,50 @@ test('local Markdown destinations, fragments and reference links are checked', a
|
|
|
208
378
|
);
|
|
209
379
|
});
|
|
210
380
|
|
|
381
|
+
test('link destinations are not authority references but labels and prose remain audited', async t => {
|
|
382
|
+
const records = {
|
|
383
|
+
...BASELINE,
|
|
384
|
+
'rollout.md':
|
|
385
|
+
'# Rollout\n\n[WBS-MISSING-01](./evidence/WBS-DEMO-10-01-current-source.md) and `ATP-MISSING-01`.\n\n\n\n[WBS-DEMO-10-01][proof]\n\n[proof]: ./evidence/WBS-DEMO-10-01-referenced.md\n',
|
|
386
|
+
'evidence/WBS-DEMO-10-01-current-source.md': '# Current source\n',
|
|
387
|
+
'evidence/WBS-DEMO-10-01-image.md': '# Image\n',
|
|
388
|
+
'evidence/WBS-DEMO-10-01-referenced.md': '# Referenced\n',
|
|
389
|
+
};
|
|
390
|
+
const result = await auditPlanning(await fixture(t, records));
|
|
391
|
+
assert.deepEqual(
|
|
392
|
+
result.diagnostics.map(diagnostic => [
|
|
393
|
+
diagnostic.code,
|
|
394
|
+
diagnostic.file,
|
|
395
|
+
diagnostic.line,
|
|
396
|
+
diagnostic.id,
|
|
397
|
+
]),
|
|
398
|
+
[
|
|
399
|
+
['undefined-reference', 'rollout.md', 3, 'ATP-MISSING-01'],
|
|
400
|
+
['undefined-reference', 'rollout.md', 3, 'WBS-MISSING-01'],
|
|
401
|
+
['undefined-reference', 'rollout.md', 5, 'SRS-MISSING-01'],
|
|
402
|
+
],
|
|
403
|
+
);
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
test('destination masking preserves local link and reference diagnostics', async t => {
|
|
407
|
+
const records = {
|
|
408
|
+
...BASELINE,
|
|
409
|
+
'rollout.md':
|
|
410
|
+
'# Rollout\n\n[Missing](./evidence/WBS-DEMO-10-01-missing.md)\n[Bad fragment](./evidence/WBS-DEMO-10-01-existing.md#absent)\n[Good fragment](./evidence/WBS-DEMO-10-01-existing.md#present)\n[Bad encoding](./evidence/WBS-DEMO-10-01-%ZZ.md)\n[Undefined][missing]\n\n[unused]: <./evidence/WBS-DEMO-10-01-unused.md>\n',
|
|
411
|
+
'evidence/WBS-DEMO-10-01-existing.md': '# Present\n',
|
|
412
|
+
};
|
|
413
|
+
const result = await auditPlanning(await fixture(t, records));
|
|
414
|
+
assert.deepEqual(
|
|
415
|
+
result.diagnostics.map(diagnostic => [diagnostic.code, diagnostic.line]),
|
|
416
|
+
[
|
|
417
|
+
['link-target', 3],
|
|
418
|
+
['link-fragment', 4],
|
|
419
|
+
['link-format', 6],
|
|
420
|
+
['link-reference', 7],
|
|
421
|
+
],
|
|
422
|
+
);
|
|
423
|
+
});
|
|
424
|
+
|
|
211
425
|
test('exact range endpoints do not masquerade as exact traceability', async t => {
|
|
212
426
|
const records = {
|
|
213
427
|
...BASELINE,
|
|
@@ -71,6 +71,46 @@ function addMapping(groups, edges, definitions) {
|
|
|
71
71
|
}
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
+
function traceabilityException(definition, report, annotatedLines) {
|
|
75
|
+
const fields = markdownLines(definition.body ?? '').flatMap(({ text, line }) =>
|
|
76
|
+
Array.from(text.matchAll(/\bTraceability\s+exception\b/gi), match => ({
|
|
77
|
+
text,
|
|
78
|
+
line,
|
|
79
|
+
start: match.index,
|
|
80
|
+
})),
|
|
81
|
+
);
|
|
82
|
+
if (!fields.length) return null;
|
|
83
|
+
for (const field of fields) {
|
|
84
|
+
const line = definition.line + field.line - (definition.kind === 'WBS' ? 0 : 1);
|
|
85
|
+
annotatedLines.add(`${definition.file}:${line}`);
|
|
86
|
+
}
|
|
87
|
+
const field = fields[0];
|
|
88
|
+
const value =
|
|
89
|
+
definition.kind === 'bullet' && field.line === 1
|
|
90
|
+
? field.text.slice(field.start).trim()
|
|
91
|
+
: field.text.trim();
|
|
92
|
+
const match = value.match(/^Traceability exception:\s*(technical-only|authority-only)\s+—\s+/i);
|
|
93
|
+
const exception = match?.[1].toLowerCase();
|
|
94
|
+
const rationale = match && value.slice(match[0].length);
|
|
95
|
+
const validOwner =
|
|
96
|
+
(exception === 'technical-only' &&
|
|
97
|
+
definition.id.startsWith('SRS-') &&
|
|
98
|
+
definition.kind === 'bullet' &&
|
|
99
|
+
field.line === 1) ||
|
|
100
|
+
(exception === 'authority-only' && definition.id.startsWith('WBS-'));
|
|
101
|
+
if (fields.length !== 1 || !validOwner || !/[\p{L}\p{N}]/u.test(rationale ?? '')) {
|
|
102
|
+
report(
|
|
103
|
+
'traceability-exception',
|
|
104
|
+
definition.file,
|
|
105
|
+
definition.line + field.line - (definition.kind === 'WBS' ? 0 : 1),
|
|
106
|
+
'Invalid Traceability exception: use one declaration-local technical-only SRS or authority-only WBS field with a rationale.',
|
|
107
|
+
definition.id,
|
|
108
|
+
);
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
return exception;
|
|
112
|
+
}
|
|
113
|
+
|
|
74
114
|
function collectMappings(records, definitions) {
|
|
75
115
|
const edges = new Map(Array.from(definitions.keys(), id => [id, new Set()]));
|
|
76
116
|
for (const definition of definitions.values()) {
|
|
@@ -142,17 +182,31 @@ function headingIds(markdown) {
|
|
|
142
182
|
return slugs;
|
|
143
183
|
}
|
|
144
184
|
|
|
185
|
+
const LINK_DEFINITION = /^(\s*\[([^\]]+)\]:\s*)(<[^>]+>|\S+)/;
|
|
186
|
+
const INLINE_LINK = /(!?\[[^\]]*\]\(\s*)(<[^>]+>|[^\s)]+)((?:\s+["'][^\n]*["'])?\s*\))/g;
|
|
187
|
+
|
|
188
|
+
function referenceText(text) {
|
|
189
|
+
return text
|
|
190
|
+
.replace(
|
|
191
|
+
LINK_DEFINITION,
|
|
192
|
+
(_, start, _label, destination) => `${start}${' '.repeat(destination.length)}`,
|
|
193
|
+
)
|
|
194
|
+
.replace(
|
|
195
|
+
INLINE_LINK,
|
|
196
|
+
(_, start, destination, end) => `${start}${' '.repeat(destination.length)}${end}`,
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
|
|
145
200
|
function markdownLinks(markdown) {
|
|
146
201
|
const lines = markdownLines(markdown);
|
|
147
202
|
const references = new Map();
|
|
148
203
|
const links = [];
|
|
149
204
|
for (const { text, line } of lines) {
|
|
150
|
-
const declaration = text.match(
|
|
205
|
+
const declaration = text.match(LINK_DEFINITION);
|
|
151
206
|
if (declaration)
|
|
152
|
-
references.set(declaration[
|
|
153
|
-
const
|
|
154
|
-
|
|
155
|
-
links.push({ destination: match[1].replace(/^<|>$/g, ''), line });
|
|
207
|
+
references.set(declaration[2].trim().toLowerCase(), declaration[3].replace(/^<|>$/g, ''));
|
|
208
|
+
for (const match of text.matchAll(INLINE_LINK))
|
|
209
|
+
links.push({ destination: match[2].replace(/^<|>$/g, ''), line });
|
|
156
210
|
const referenced = /!?\[([^\]]+)\]\[([^\]]*)\]/g;
|
|
157
211
|
for (const match of text.matchAll(referenced))
|
|
158
212
|
links.push({ reference: (match[2] || match[1]).trim().toLowerCase(), line });
|
|
@@ -257,10 +311,24 @@ export async function auditPlanning(
|
|
|
257
311
|
report('declaration', file, 1, error.message);
|
|
258
312
|
}
|
|
259
313
|
}
|
|
314
|
+
const exceptions = new Map();
|
|
315
|
+
const annotatedLines = new Set();
|
|
316
|
+
for (const definition of definitions.values()) {
|
|
317
|
+
const exception = traceabilityException(definition, report, annotatedLines);
|
|
318
|
+
if (exception) exceptions.set(definition.id, exception);
|
|
319
|
+
}
|
|
260
320
|
for (const [file, markdown] of records) {
|
|
261
321
|
for (const { text, line } of markdownLines(markdown)) {
|
|
322
|
+
if (/\bTraceability\s+exception\b/i.test(text) && !annotatedLines.has(`${file}:${line}`)) {
|
|
323
|
+
report(
|
|
324
|
+
'traceability-exception',
|
|
325
|
+
file,
|
|
326
|
+
line,
|
|
327
|
+
'Traceability exception must be in its own formal declaration.',
|
|
328
|
+
);
|
|
329
|
+
}
|
|
262
330
|
for (const prefix of OWNERS.keys()) {
|
|
263
|
-
for (const id of findIdentifiers(text, prefix)) {
|
|
331
|
+
for (const id of findIdentifiers(referenceText(text), prefix)) {
|
|
264
332
|
if (!definitions.has(id)) {
|
|
265
333
|
report(
|
|
266
334
|
'undefined-reference',
|
|
@@ -333,7 +401,7 @@ export async function auditPlanning(
|
|
|
333
401
|
const incoming = new Set([...edges.values()].flatMap(values => [...values]));
|
|
334
402
|
for (const [id, definition] of definitions) {
|
|
335
403
|
if (definition.status === 'deferred' || definition.deferred) continue;
|
|
336
|
-
if (!id.startsWith('PRD-') && !incoming.has(id)) {
|
|
404
|
+
if (!id.startsWith('PRD-') && !incoming.has(id) && exceptions.get(id) !== 'technical-only') {
|
|
337
405
|
report(
|
|
338
406
|
'traceability-incoming',
|
|
339
407
|
definition.file,
|
|
@@ -342,7 +410,11 @@ export async function auditPlanning(
|
|
|
342
410
|
id,
|
|
343
411
|
);
|
|
344
412
|
}
|
|
345
|
-
if (
|
|
413
|
+
if (
|
|
414
|
+
!id.startsWith('ATP-') &&
|
|
415
|
+
!edges.get(id).size &&
|
|
416
|
+
exceptions.get(id) !== 'authority-only'
|
|
417
|
+
) {
|
|
346
418
|
report(
|
|
347
419
|
'traceability-outgoing',
|
|
348
420
|
definition.file,
|
|
Binary file
|
package/repo-docs/index.md
CHANGED
|
@@ -30,6 +30,12 @@ features:
|
|
|
30
30
|
details: Organize capabilities for SSR, SPA, Web, and Admin applications with shared conventions.
|
|
31
31
|
---
|
|
32
32
|
|
|
33
|
+
## Demonstrations(Videos)
|
|
34
|
+
|
|
35
|
+
### 1. Can an Admin Site Use SSR? CabloyJS in Three Practical Demos (Duration: 1:16)
|
|
36
|
+
|
|
37
|
+
[](https://youtu.be/786IQhRdr1I)
|
|
38
|
+
|
|
33
39
|
## Choose a reading path
|
|
34
40
|
|
|
35
41
|
### Start a project
|