rcf-lite 0.0.1 → 0.8.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/CHANGELOG.md +344 -0
- package/LICENSE +202 -0
- package/README.md +92 -4
- package/bin/rcf-verify.js +122 -0
- package/bin/rcf.js +174 -0
- package/bin/view-supervisor-child.mjs +14 -0
- package/fixtures/canary-manifest.json +103 -0
- package/guidance/README.md +25 -0
- package/guidance/build-cycle-playbook.md +492 -0
- package/guidance/build-cycle.md +40 -0
- package/guidance/document-model.md +45 -0
- package/guidance/elicitation-playbook.md +442 -0
- package/guidance/harness-template.md +246 -0
- package/guidance/managed/README.md +63 -0
- package/guidance/managed/agent-instructions-block.hash +1 -0
- package/guidance/managed/agent-instructions-block.md +220 -0
- package/guidance/managed/legacy-fragment-hashes.json +22 -0
- package/guidance/manifest.json +21 -0
- package/guidance/overview.md +48 -0
- package/guidance/persona-programme.md +44 -0
- package/package.json +71 -8
- package/rcf/adrs/adr-001.json +25 -0
- package/rcf/adrs/adr-002.json +25 -0
- package/rcf/adrs/adr-003.json +25 -0
- package/rcf/adrs/adr-004.json +20 -0
- package/rcf/adrs/adr-005.json +20 -0
- package/rcf/adrs/adr-006.json +25 -0
- package/rcf/adrs/adr-007.json +25 -0
- package/rcf/adrs/adr-008.json +25 -0
- package/rcf/adrs/adr-009.json +25 -0
- package/rcf/build-sequence.json +11 -0
- package/rcf/code-nodes/cn-001.json +14 -0
- package/rcf/code-nodes/cn-002.json +14 -0
- package/rcf/code-nodes/cn-003.json +15 -0
- package/rcf/code-nodes/cn-004.json +17 -0
- package/rcf/code-nodes/cn-005.json +19 -0
- package/rcf/code-nodes/cn-006.json +17 -0
- package/rcf/code-nodes/cn-007.json +16 -0
- package/rcf/code-nodes/cn-008.json +19 -0
- package/rcf/code-nodes/cn-009.json +16 -0
- package/rcf/code-nodes/cn-010.json +18 -0
- package/rcf/code-nodes/cn-011.json +18 -0
- package/rcf/code-nodes/cn-012.json +18 -0
- package/rcf/code-nodes/cn-013.json +19 -0
- package/rcf/code-nodes/cn-014.json +16 -0
- package/rcf/code-nodes/cn-015.json +16 -0
- package/rcf/code-nodes/cn-016.json +16 -0
- package/rcf/code-nodes/cn-017.json +16 -0
- package/rcf/code-nodes/cn-018.json +15 -0
- package/rcf/code-nodes/cn-019.json +16 -0
- package/rcf/code-nodes/cn-020.json +16 -0
- package/rcf/code-nodes/cn-021.json +16 -0
- package/rcf/code-nodes/cn-022.json +16 -0
- package/rcf/code-nodes/cn-023.json +16 -0
- package/rcf/code-nodes/cn-024.json +16 -0
- package/rcf/code-nodes/cn-025.json +16 -0
- package/rcf/code-nodes/cn-026.json +16 -0
- package/rcf/code-nodes/cn-027.json +16 -0
- package/rcf/code-nodes/cn-028.json +16 -0
- package/rcf/code-nodes/cn-029.json +19 -0
- package/rcf/code-nodes/cn-030.json +14 -0
- package/rcf/code-nodes/cn-031.json +14 -0
- package/rcf/code-nodes/cn-032.json +14 -0
- package/rcf/code-nodes/cn-033.json +14 -0
- package/rcf/code-nodes/cn-034.json +14 -0
- package/rcf/code-nodes/cn-035.json +14 -0
- package/rcf/code-nodes/cn-036.json +14 -0
- package/rcf/code-nodes/cn-037.json +14 -0
- package/rcf/code-nodes/cn-038.json +14 -0
- package/rcf/code-nodes/cn-039.json +14 -0
- package/rcf/code-nodes/cn-040.json +14 -0
- package/rcf/code-nodes/cn-041.json +14 -0
- package/rcf/code-nodes/cn-042.json +14 -0
- package/rcf/code-nodes/cn-043.json +14 -0
- package/rcf/code-nodes/cn-044.json +14 -0
- package/rcf/code-nodes/cn-045.json +14 -0
- package/rcf/code-nodes/cn-046.json +14 -0
- package/rcf/code-nodes/cn-047.json +14 -0
- package/rcf/code-nodes/cn-048.json +14 -0
- package/rcf/code-nodes/cn-049.json +14 -0
- package/rcf/code-nodes/cn-050.json +14 -0
- package/rcf/code-nodes/cn-051.json +14 -0
- package/rcf/code-nodes/cn-052.json +14 -0
- package/rcf/code-nodes/cn-053.json +14 -0
- package/rcf/code-nodes/cn-054.json +14 -0
- package/rcf/code-nodes/cn-055.json +14 -0
- package/rcf/code-nodes/cn-056.json +14 -0
- package/rcf/code-nodes/cn-057.json +14 -0
- package/rcf/fbs/fbs-001.json +49 -0
- package/rcf/fbs/fbs-002.json +42 -0
- package/rcf/fbs/fbs-003.json +37 -0
- package/rcf/fbs/fbs-004.json +39 -0
- package/rcf/fbs/fbs-005.json +38 -0
- package/rcf/fbs/fbs-006.json +48 -0
- package/rcf/fbs/fbs-007.json +39 -0
- package/rcf/fbs/fbs-008.json +40 -0
- package/rcf/fbs/fbs-009.json +36 -0
- package/rcf/fbs/fbs-010.json +41 -0
- package/rcf/fbs/fbs-011.json +36 -0
- package/rcf/fbs/fbs-012.json +46 -0
- package/rcf/fbs/fbs-013.json +42 -0
- package/rcf/fbs/fbs-014.json +49 -0
- package/rcf/fbs/fbs-015.json +32 -0
- package/rcf/manifest.json +17 -0
- package/rcf/prd.json +47 -0
- package/rcf/requirements/req-001.json +19 -0
- package/rcf/requirements/req-002.json +19 -0
- package/rcf/requirements/req-003.json +19 -0
- package/rcf/requirements/req-004.json +19 -0
- package/rcf/requirements/req-005.json +19 -0
- package/rcf/requirements/req-006.json +19 -0
- package/rcf/requirements/req-007.json +19 -0
- package/rcf/requirements/req-008.json +19 -0
- package/rcf/requirements/req-009.json +19 -0
- package/rcf/tacs/tac-001.json +45 -0
- package/rcf/tacs/tac-002.json +109 -0
- package/rcf/tacs/tac-003.json +40 -0
- package/rcf/tacs/tac-004.json +51 -0
- package/rcf/tacs/tac-005.json +52 -0
- package/rcf/tacs/tac-006.json +104 -0
- package/rcf/tacs/tac-007.json +38 -0
- package/rcf/tacs/tac-008.json +51 -0
- package/rcf/tad.json +51 -0
- package/rcf/test-suites/PENDING.md +23 -0
- package/rcf/test-suites/ts-001.json +38 -0
- package/rcf/test-suites/ts-002.json +38 -0
- package/rcf/test-suites/ts-003.json +43 -0
- package/rcf/test-suites/ts-004.json +44 -0
- package/rcf/test-suites/ts-005.json +30 -0
- package/rcf/test-suites/ts-006.json +36 -0
- package/rcf/test-suites/ts-007.json +43 -0
- package/rcf/test-suites/ts-008.json +37 -0
- package/rcf/test-suites/ts-009.json +38 -0
- package/rcf/test-suites/ts-010.json +38 -0
- package/rcf/test-suites/ts-011.json +44 -0
- package/rcf/test-suites/ts-012.json +36 -0
- package/rcf/test-suites/ts-013.json +38 -0
- package/rcf/test-suites/ts-014.json +38 -0
- package/rcf/test-suites/ts-015.json +38 -0
- package/rcf/test-suites/ts-016.json +37 -0
- package/rcf/test-suites/ts-017.json +38 -0
- package/rcf/test-suites/ts-018.json +38 -0
- package/rcf/test-suites/ts-019.json +37 -0
- package/rcf/test-suites/ts-020.json +46 -0
- package/rcf/test-suites/ts-021.json +46 -0
- package/rcf/test-suites/ts-022.json +46 -0
- package/rcf/test-suites/ts-023.json +46 -0
- package/rcf/test-suites/ts-024.json +46 -0
- package/rcf/test-suites/ts-025.json +52 -0
- package/rcf/user-stories/us-101.json +40 -0
- package/rcf/user-stories/us-102.json +40 -0
- package/rcf/user-stories/us-201.json +40 -0
- package/rcf/user-stories/us-202.json +40 -0
- package/rcf/user-stories/us-203.json +32 -0
- package/rcf/user-stories/us-301.json +40 -0
- package/rcf/user-stories/us-302.json +40 -0
- package/rcf/user-stories/us-303.json +40 -0
- package/rcf/user-stories/us-304.json +40 -0
- package/rcf/user-stories/us-401.json +40 -0
- package/rcf/user-stories/us-402.json +40 -0
- package/rcf/user-stories/us-403.json +40 -0
- package/rcf/user-stories/us-501.json +40 -0
- package/rcf/user-stories/us-502.json +40 -0
- package/rcf/user-stories/us-503.json +40 -0
- package/rcf/user-stories/us-601.json +40 -0
- package/rcf/user-stories/us-602.json +40 -0
- package/rcf/user-stories/us-701.json +40 -0
- package/rcf/user-stories/us-702.json +40 -0
- package/rcf/user-stories/us-801.json +49 -0
- package/rcf/user-stories/us-802.json +49 -0
- package/rcf/user-stories/us-803.json +49 -0
- package/rcf/user-stories/us-804.json +49 -0
- package/rcf/user-stories/us-805.json +49 -0
- package/rcf/user-stories/us-901.json +40 -0
- package/src/.gitkeep +0 -0
- package/src/admissibility/enforce.js +142 -0
- package/src/admissibility/index.js +8 -0
- package/src/admissibility/markers.js +104 -0
- package/src/admissibility/scope-lint.js +163 -0
- package/src/browser-verify/auth-smoke.js +109 -0
- package/src/browser-verify/index.js +29 -0
- package/src/browser-verify/invariants.js +336 -0
- package/src/browser-verify/manifest-writer.js +189 -0
- package/src/browser-verify/runner.js +171 -0
- package/src/build/bundle.js +198 -0
- package/src/build/formatters/json.js +18 -0
- package/src/build/formatters/markdown.js +406 -0
- package/src/build/index.js +14 -0
- package/src/build/mark.js +177 -0
- package/src/build/queue.js +285 -0
- package/src/cli/browser-verify.js +231 -0
- package/src/cli/build.js +584 -0
- package/src/cli/coverage.js +219 -0
- package/src/cli/create.js +358 -0
- package/src/cli/delete.js +127 -0
- package/src/cli/design.js +353 -0
- package/src/cli/doctor.js +548 -0
- package/src/cli/fbs.js +171 -0
- package/src/cli/finalise.js +377 -0
- package/src/cli/guidance.js +206 -0
- package/src/cli/help.js +156 -0
- package/src/cli/impact.js +119 -0
- package/src/cli/init.js +282 -0
- package/src/cli/intake.js +153 -0
- package/src/cli/link.js +128 -0
- package/src/cli/mcp.js +160 -0
- package/src/cli/preflight.js +220 -0
- package/src/cli/read.js +168 -0
- package/src/cli/req-baseline.js +269 -0
- package/src/cli/req-classify.js +135 -0
- package/src/cli/review.js +295 -0
- package/src/cli/test-suite.js +221 -0
- package/src/cli/trace.js +172 -0
- package/src/cli/ui-baseline.js +292 -0
- package/src/cli/ui-classify.js +108 -0
- package/src/cli/update.js +197 -0
- package/src/cli/validate.js +168 -0
- package/src/cli/view.js +452 -0
- package/src/core/baseline-catalog/data/auth.json +42 -0
- package/src/core/baseline-catalog/data/http-api.json +42 -0
- package/src/core/baseline-catalog/data/notifications.json +33 -0
- package/src/core/baseline-catalog/data/persistence.json +33 -0
- package/src/core/baseline-catalog/data/web-ui.json +60 -0
- package/src/core/baseline-catalog/index.js +121 -0
- package/src/core/errors/index.js +167 -0
- package/src/core/fixtures/register-canary/canary-prompt-01.json +18 -0
- package/src/core/fixtures/register-canary/canary-prompt-02.json +21 -0
- package/src/core/fixtures/register-canary/canary-prompt-03.json +17 -0
- package/src/core/isolation/index.js +60 -0
- package/src/core/mcp/framing.js +103 -0
- package/src/core/mcp/index.js +8 -0
- package/src/core/mcp/server.js +228 -0
- package/src/core/patterns/register-canary.js +209 -0
- package/src/core/patterns/req-shapes.js +158 -0
- package/src/core/patterns/services.js +358 -0
- package/src/core/patterns/ui-shapes.js +166 -0
- package/src/core/store/cn-resolve.js +134 -0
- package/src/core/store/derive-deps.js +93 -0
- package/src/core/store/ids.js +78 -0
- package/src/core/store/index.js +20 -0
- package/src/core/store/init.js +255 -0
- package/src/core/store/loader.js +214 -0
- package/src/core/store/tp-resolve.js +176 -0
- package/src/core/store/validator.js +191 -0
- package/src/core/store/walker.js +944 -0
- package/src/core/store/writer.js +1879 -0
- package/src/design/index.js +11 -0
- package/src/design/writer.js +271 -0
- package/src/finalise/detect.js +151 -0
- package/src/finalise/index.js +31 -0
- package/src/finalise/ingest.js +160 -0
- package/src/finalise/install.js +119 -0
- package/src/finalise/ship-without-verified.js +131 -0
- package/src/finalise/spawn.js +84 -0
- package/src/intake/fidelity.js +105 -0
- package/src/intake/index.js +6 -0
- package/src/intake/manifest-writer.js +100 -0
- package/src/intake/orchestrator.js +138 -0
- package/src/intake/validate.js +80 -0
- package/src/mcp/map-errors.js +131 -0
- package/src/mcp/prompts.js +49 -0
- package/src/mcp/resources.js +244 -0
- package/src/mcp/tools.js +1212 -0
- package/src/preflight/design-shapes.js +185 -0
- package/src/preflight/index.js +90 -0
- package/src/preflight/manifest-writer.js +264 -0
- package/src/preflight/scanner.js +206 -0
- package/src/preflight/secrets.js +134 -0
- package/src/preflight/session.js +246 -0
- package/src/query/attestation.js +285 -0
- package/src/query/coverage.js +308 -0
- package/src/query/formatters/json.js +21 -0
- package/src/query/formatters/mermaid.js +209 -0
- package/src/query/formatters/table.js +203 -0
- package/src/query/impact.js +173 -0
- package/src/query/index.js +13 -0
- package/src/query/refuse-on-admissibility.js +73 -0
- package/src/query/trace.js +345 -0
- package/src/register-canary/fixture-loader.js +87 -0
- package/src/register-canary/index.js +10 -0
- package/src/register-canary/record-writer.js +132 -0
- package/src/register-canary/runner.js +156 -0
- package/src/req-baseline/gate.js +86 -0
- package/src/req-baseline/index.js +27 -0
- package/src/req-baseline/open-candidates.js +143 -0
- package/src/req-baseline/opt-out.js +195 -0
- package/src/req-baseline/sweep.js +230 -0
- package/src/req-detection/classifier.js +181 -0
- package/src/req-detection/index.js +9 -0
- package/src/req-detection/persist.js +55 -0
- package/src/review/index.js +325 -0
- package/src/review/mutation.js +117 -0
- package/src/review/ui-baseline-drift.js +138 -0
- package/src/ruleset/index.js +140 -0
- package/src/ruleset/ruleset.json +146 -0
- package/src/server/index.js +178 -0
- package/src/server/routes.js +110 -0
- package/src/server/sse.js +118 -0
- package/src/setup/agent-setup.js +362 -0
- package/src/setup/identity-seed.js +104 -0
- package/src/setup/knowledge-seed.js +123 -0
- package/src/setup/managed-block.js +193 -0
- package/src/setup/managed-gitignore.js +166 -0
- package/src/setup/managed-markers.js +49 -0
- package/src/ui-baseline/defaults.js +119 -0
- package/src/ui-baseline/index.js +25 -0
- package/src/ui-baseline/manifest-writer.js +282 -0
- package/src/ui-baseline/session.js +178 -0
- package/src/ui-detection/classifier.js +192 -0
- package/src/verify/chain/index.js +221 -0
- package/src/verify/cli/cleanup.js +61 -0
- package/src/verify/cli/help.js +56 -0
- package/src/verify/cli/mcp.js +98 -0
- package/src/verify/cli/provision.js +71 -0
- package/src/verify/cli/report.js +71 -0
- package/src/verify/cli/run.js +155 -0
- package/src/verify/engine/brief.js +87 -0
- package/src/verify/engine/index.js +177 -0
- package/src/verify/engine/launcher.js +307 -0
- package/src/verify/mcp/tools.js +107 -0
- package/src/verify/profile/index.js +146 -0
- package/src/verify/provision/index.js +256 -0
- package/src/verify/report/index.js +139 -0
- package/src/verify/report/renderer.js +118 -0
- package/src/verify/verdict/index.js +313 -0
- package/src/view/doc-renderers/adr.js +44 -0
- package/src/view/doc-renderers/build-sequence.js +40 -0
- package/src/view/doc-renderers/fbs.js +128 -0
- package/src/view/doc-renderers/helpers.js +159 -0
- package/src/view/doc-renderers/index.js +12 -0
- package/src/view/doc-renderers/prd.js +45 -0
- package/src/view/doc-renderers/req.js +43 -0
- package/src/view/doc-renderers/tac.js +38 -0
- package/src/view/doc-renderers/tad.js +74 -0
- package/src/view/doc-renderers/test-suite.js +45 -0
- package/src/view/doc-renderers/user-story.js +63 -0
- package/src/view/html-page.js +462 -0
- package/src/view/index.js +63 -0
- package/src/view/live-client.js +338 -0
- package/src/view/mermaid-diagram.js +178 -0
- package/src/view/style.css +735 -0
- package/src/view/tree-model.js +152 -0
- package/src/view/vendored/mermaid.min.js +2607 -0
- package/src/view-supervisor/index.js +26 -0
- package/src/view-supervisor/logs.js +32 -0
- package/src/view-supervisor/manifest-writer.js +178 -0
- package/src/view-supervisor/persist-until.js +85 -0
- package/src/view-supervisor/supervisor.js +276 -0
- package/src/watch/index.js +152 -0
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
// Small helpers shared across the per-document renderers. Vanilla template
|
|
2
|
+
// strings; no template engine.
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* HTML-escape a string for safe injection between tags.
|
|
6
|
+
*
|
|
7
|
+
* @param {unknown} value
|
|
8
|
+
* @returns {string}
|
|
9
|
+
*/
|
|
10
|
+
export function escapeHtml(value) {
|
|
11
|
+
if (value === null || value === undefined) return '';
|
|
12
|
+
const s = String(value);
|
|
13
|
+
return s
|
|
14
|
+
.replace(/&/g, '&')
|
|
15
|
+
.replace(/</g, '<')
|
|
16
|
+
.replace(/>/g, '>')
|
|
17
|
+
.replace(/"/g, '"')
|
|
18
|
+
.replace(/'/g, ''');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Return the raw document id as an anchor id. Phase 3.2 unified the anchor
|
|
23
|
+
* convention around the display id (e.g. "REQ-002") so Mermaid click targets
|
|
24
|
+
* and internal doc links resolve to the same DOM node.
|
|
25
|
+
*
|
|
26
|
+
* @param {string} id
|
|
27
|
+
* @returns {string}
|
|
28
|
+
*/
|
|
29
|
+
export function anchorIdFor(id) {
|
|
30
|
+
return String(id);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Render an `<a>` link to the section of another document.
|
|
35
|
+
*
|
|
36
|
+
* @param {string} id
|
|
37
|
+
* @param {string} [label]
|
|
38
|
+
* @returns {string}
|
|
39
|
+
*/
|
|
40
|
+
export function docLink(id, label) {
|
|
41
|
+
return `<a href="#${anchorIdFor(id)}">${escapeHtml(label ?? id)}</a>`;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Render a list of ids as comma-separated doc links.
|
|
46
|
+
*
|
|
47
|
+
* @param {string[] | undefined} ids
|
|
48
|
+
* @returns {string}
|
|
49
|
+
*/
|
|
50
|
+
export function docLinkList(ids) {
|
|
51
|
+
if (!Array.isArray(ids) || ids.length === 0) return '<em>none</em>';
|
|
52
|
+
return ids.map((id) => docLink(id)).join(', ');
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Render a paragraph if value is present.
|
|
57
|
+
*
|
|
58
|
+
* @param {string} label
|
|
59
|
+
* @param {unknown} value
|
|
60
|
+
* @returns {string}
|
|
61
|
+
*/
|
|
62
|
+
export function fieldPara(label, value) {
|
|
63
|
+
if (value === undefined || value === null || value === '') return '';
|
|
64
|
+
return `<p><strong>${escapeHtml(label)}:</strong> ${escapeHtml(value)}</p>`;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Render an unordered list if items are present.
|
|
69
|
+
*
|
|
70
|
+
* @param {string} label
|
|
71
|
+
* @param {string[] | undefined} items
|
|
72
|
+
* @returns {string}
|
|
73
|
+
*/
|
|
74
|
+
export function fieldList(label, items) {
|
|
75
|
+
if (!Array.isArray(items) || items.length === 0) return '';
|
|
76
|
+
const li = items.map((s) => `<li>${escapeHtml(s)}</li>`).join('');
|
|
77
|
+
return `<section class="field-list"><h4>${escapeHtml(label)}</h4><ul>${li}</ul></section>`;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Render the "Show raw JSON" disclosure block for a document.
|
|
82
|
+
*
|
|
83
|
+
* Phase 3.8 D13b: the raw-JSON disclosure now carries a stable
|
|
84
|
+
* `data-doc-id="{parentDocId}::raw"` attribute so the live-client can
|
|
85
|
+
* persist its open state across SSE swaps and page reloads. The main
|
|
86
|
+
* doc-details already get a `data-doc-id` via `detailsWrap`; this closes
|
|
87
|
+
* the last state-persistence gap.
|
|
88
|
+
*
|
|
89
|
+
* @param {string|undefined} raw
|
|
90
|
+
* @param {object} doc
|
|
91
|
+
* @param {string} parentDocId - the enclosing doc's display id (e.g. "REQ-002")
|
|
92
|
+
* @returns {string}
|
|
93
|
+
*/
|
|
94
|
+
export function rawJsonDisclosure(raw, doc, parentDocId) {
|
|
95
|
+
const json = raw ?? JSON.stringify(doc, null, 2);
|
|
96
|
+
const rawId = `${parentDocId ?? 'doc'}::raw`;
|
|
97
|
+
return `<details class="raw-json" data-doc-id="${escapeHtml(rawId)}"><summary>Show raw JSON</summary><pre>${escapeHtml(json)}</pre></details>`;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Render a broken-document banner above the rest of a document section.
|
|
102
|
+
*
|
|
103
|
+
* @param {import('#core/errors').RcfError[]} errors
|
|
104
|
+
* @returns {string}
|
|
105
|
+
*/
|
|
106
|
+
export function brokenBanner(errors) {
|
|
107
|
+
if (!errors || errors.length === 0) return '';
|
|
108
|
+
const items = errors
|
|
109
|
+
.map((e) => `<li><code>${escapeHtml(e.kind)}</code> ${escapeHtml(e.message)}</li>`)
|
|
110
|
+
.join('');
|
|
111
|
+
return `
|
|
112
|
+
<aside class="broken" role="alert">
|
|
113
|
+
<p><strong>Broken document</strong> - schema validation or reference failure.</p>
|
|
114
|
+
<ul>${items}</ul>
|
|
115
|
+
</aside>`.trim();
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Render a broken-reference placeholder when a referenced id has no file.
|
|
120
|
+
*
|
|
121
|
+
* @param {string} id
|
|
122
|
+
* @returns {string}
|
|
123
|
+
*/
|
|
124
|
+
export function brokenReferenceSection(id) {
|
|
125
|
+
const anchor = anchorIdFor(id);
|
|
126
|
+
return `
|
|
127
|
+
<article id="${anchor}" class="doc broken-doc">
|
|
128
|
+
<h3>${escapeHtml(id)} - broken reference</h3>
|
|
129
|
+
<aside class="broken" role="alert">
|
|
130
|
+
<p>Referenced by a parent document but no file was found at the expected path.</p>
|
|
131
|
+
</aside>
|
|
132
|
+
</article>`.trim();
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Wrap a doc's rendered body in a `<details data-doc-id>` so it can be
|
|
137
|
+
* drilled-in from the tab tree. Closed by default. The `data-doc-id`
|
|
138
|
+
* attribute is what the hash-routing script targets.
|
|
139
|
+
*
|
|
140
|
+
* @param {object} args
|
|
141
|
+
* @param {string} args.id - display doc id (e.g. "REQ-001")
|
|
142
|
+
* @param {string} args.summary - short label shown in the summary line
|
|
143
|
+
* @param {string} args.className - class applied to the details wrapper
|
|
144
|
+
* @param {string} args.body - the rendered body HTML
|
|
145
|
+
* @param {string} [args.status] - optional doc status, rendered as a pill
|
|
146
|
+
* @returns {string}
|
|
147
|
+
*/
|
|
148
|
+
export function detailsWrap({
|
|
149
|
+
id, summary, className, body, status,
|
|
150
|
+
}) {
|
|
151
|
+
const statusPill = status
|
|
152
|
+
? `<span class="status ${escapeHtml(status)}">${escapeHtml(status)}</span>`
|
|
153
|
+
: '';
|
|
154
|
+
return `
|
|
155
|
+
<details class="doc-details ${className}" data-doc-id="${escapeHtml(id)}">
|
|
156
|
+
<summary><span class="summary-label">${escapeHtml(summary)}</span>${statusPill}</summary>
|
|
157
|
+
${body}
|
|
158
|
+
</details>`.trim();
|
|
159
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// Doc renderer index. Re-exports each per-type renderer so html-page.js can
|
|
2
|
+
// import them through a single module.
|
|
3
|
+
|
|
4
|
+
export { renderPrd } from './prd.js';
|
|
5
|
+
export { renderReq } from './req.js';
|
|
6
|
+
export { renderUserStory } from './user-story.js';
|
|
7
|
+
export { renderTad } from './tad.js';
|
|
8
|
+
export { renderTac } from './tac.js';
|
|
9
|
+
export { renderAdr } from './adr.js';
|
|
10
|
+
export { renderBuildSequence } from './build-sequence.js';
|
|
11
|
+
export { renderFbs } from './fbs.js';
|
|
12
|
+
export { renderTestSuite } from './test-suite.js';
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// PRD renderer. Curated fields per spec D11. Post-3.7 (D15) the child REQ
|
|
2
|
+
// list is not read from `prd.requirementIds` (removed in 0.2.0) but from
|
|
3
|
+
// the computed `childrenByParent` map on the tree model.
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
anchorIdFor,
|
|
7
|
+
brokenBanner,
|
|
8
|
+
docLinkList,
|
|
9
|
+
escapeHtml,
|
|
10
|
+
fieldList,
|
|
11
|
+
fieldPara,
|
|
12
|
+
rawJsonDisclosure,
|
|
13
|
+
} from './helpers.js';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @param {object} prd
|
|
17
|
+
* @param {object} ctx
|
|
18
|
+
* @param {string|undefined} ctx.raw
|
|
19
|
+
* @param {import('#core/errors').RcfError[]} [ctx.errors]
|
|
20
|
+
* @param {string[]} [ctx.requirementIds] - computed REQ children for this PRD
|
|
21
|
+
* @returns {string}
|
|
22
|
+
*/
|
|
23
|
+
export function renderPrd(prd, ctx) {
|
|
24
|
+
if (!prd) return '';
|
|
25
|
+
const anchor = anchorIdFor(prd.prdId ?? 'PRD');
|
|
26
|
+
const broken = ctx.errors?.length ? brokenBanner(ctx.errors) : '';
|
|
27
|
+
const requirementIds = ctx.requirementIds ?? [];
|
|
28
|
+
const sections = [
|
|
29
|
+
fieldPara('Executive summary', prd.executiveSummary),
|
|
30
|
+
fieldPara('Problem statement', prd.problemStatement),
|
|
31
|
+
fieldList('Target users', prd.targetUsers),
|
|
32
|
+
fieldList('In scope', prd.inScope),
|
|
33
|
+
fieldList('Out of scope', prd.outOfScope),
|
|
34
|
+
fieldList('Objectives', prd.objectives),
|
|
35
|
+
fieldList('Constraints', prd.constraints),
|
|
36
|
+
`<section class="field-list"><h4>Requirements</h4><p>${docLinkList(requirementIds)}</p></section>`,
|
|
37
|
+
].filter(Boolean).join('\n');
|
|
38
|
+
return `
|
|
39
|
+
<article id="${anchor}" class="doc doc-prd">
|
|
40
|
+
<h3>${escapeHtml(prd.prdId ?? 'PRD')} - ${escapeHtml(prd.productName ?? '')}</h3>
|
|
41
|
+
${broken}
|
|
42
|
+
${sections}
|
|
43
|
+
${rawJsonDisclosure(ctx.raw, prd, prd.prdId)}
|
|
44
|
+
</article>`.trim();
|
|
45
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// REQ renderer. Carries an optional per-REQ Mermaid subdiagram block when
|
|
2
|
+
// the caller passes one (the html-page module decides whether to include it).
|
|
3
|
+
// Child user stories are NOT rendered as inline links because the Phase 3.2
|
|
4
|
+
// layout nests them as `<details>` under the REQ's own `<details>` wrapper.
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
anchorIdFor,
|
|
8
|
+
brokenBanner,
|
|
9
|
+
escapeHtml,
|
|
10
|
+
fieldList,
|
|
11
|
+
fieldPara,
|
|
12
|
+
rawJsonDisclosure,
|
|
13
|
+
} from './helpers.js';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @param {object} req
|
|
17
|
+
* @param {object} ctx
|
|
18
|
+
* @param {string|undefined} ctx.raw
|
|
19
|
+
* @param {import('#core/errors').RcfError[]} [ctx.errors]
|
|
20
|
+
* @param {string|undefined} [ctx.subdiagram]
|
|
21
|
+
* @returns {string}
|
|
22
|
+
*/
|
|
23
|
+
export function renderReq(req, ctx) {
|
|
24
|
+
if (!req) return '';
|
|
25
|
+
const anchor = anchorIdFor(req.reqId ?? 'REQ');
|
|
26
|
+
const broken = ctx.errors?.length ? brokenBanner(ctx.errors) : '';
|
|
27
|
+
const subdiagram = ctx.subdiagram
|
|
28
|
+
? `<section class="subdiagram"><h4>Slice diagram</h4><pre class="mermaid">${escapeHtml(ctx.subdiagram)}</pre></section>`
|
|
29
|
+
: '';
|
|
30
|
+
return `
|
|
31
|
+
<article id="${anchor}" class="doc doc-req">
|
|
32
|
+
<h3>${escapeHtml(req.reqId ?? 'REQ')} - ${escapeHtml(req.title ?? '')}</h3>
|
|
33
|
+
${broken}
|
|
34
|
+
${fieldPara('Description', req.description)}
|
|
35
|
+
${fieldPara('Category', req.category)}
|
|
36
|
+
${fieldPara('Domain', req.domain)}
|
|
37
|
+
${fieldPara('Priority', req.priority)}
|
|
38
|
+
${fieldPara('Rationale', req.rationale)}
|
|
39
|
+
${fieldList('Tags', req.tags)}
|
|
40
|
+
${subdiagram}
|
|
41
|
+
${rawJsonDisclosure(ctx.raw, req, req.reqId)}
|
|
42
|
+
</article>`.trim();
|
|
43
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// TAC renderer.
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
anchorIdFor,
|
|
5
|
+
brokenBanner,
|
|
6
|
+
escapeHtml,
|
|
7
|
+
fieldList,
|
|
8
|
+
fieldPara,
|
|
9
|
+
rawJsonDisclosure,
|
|
10
|
+
} from './helpers.js';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* @param {object} tac
|
|
14
|
+
* @param {object} ctx
|
|
15
|
+
* @param {string|undefined} ctx.raw
|
|
16
|
+
* @param {import('#core/errors').RcfError[]} [ctx.errors]
|
|
17
|
+
* @returns {string}
|
|
18
|
+
*/
|
|
19
|
+
export function renderTac(tac, ctx) {
|
|
20
|
+
if (!tac) return '';
|
|
21
|
+
const anchor = anchorIdFor(tac.tacId ?? 'TAC');
|
|
22
|
+
const broken = ctx.errors?.length ? brokenBanner(ctx.errors) : '';
|
|
23
|
+
const interfaces = (tac.interfaces ?? []).map((i) => `<li><strong>${escapeHtml(i.name ?? '')}</strong> (${escapeHtml(i.kind ?? '')}) - ${escapeHtml(i.description ?? '')}</li>`).join('');
|
|
24
|
+
const deps = (tac.dependencies ?? []).map((d) => `<li><strong>${escapeHtml(d.name ?? '')}</strong> (${escapeHtml(d.kind ?? '')}) - ${escapeHtml(d.description ?? '')}</li>`).join('');
|
|
25
|
+
return `
|
|
26
|
+
<article id="${anchor}" class="doc doc-tac">
|
|
27
|
+
<h3>${escapeHtml(tac.tacId ?? 'TAC')} - ${escapeHtml(tac.name ?? '')}</h3>
|
|
28
|
+
${broken}
|
|
29
|
+
${fieldPara('Purpose', tac.purpose)}
|
|
30
|
+
${fieldList('Responsibilities', tac.responsibilities)}
|
|
31
|
+
${fieldPara('Internal structure', tac.internalStructure)}
|
|
32
|
+
${interfaces ? `<section class="field-list"><h4>Interfaces</h4><ul>${interfaces}</ul></section>` : ''}
|
|
33
|
+
${deps ? `<section class="field-list"><h4>Dependencies</h4><ul>${deps}</ul></section>` : ''}
|
|
34
|
+
${fieldPara('Trade-offs', tac.tradeoffs)}
|
|
35
|
+
${fieldPara('Notes', tac.notes)}
|
|
36
|
+
${rawJsonDisclosure(ctx.raw, tac, tac.tacId)}
|
|
37
|
+
</article>`.trim();
|
|
38
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
// TAD renderer. Curated fields per spec D11; iterates the architecturePrinciples[]
|
|
2
|
+
// array rather than hard-coding a count. Post-3.7 (D15) the child TAC / ADR
|
|
3
|
+
// lists are not read from removed parent fields (`componentIds`,
|
|
4
|
+
// `architecturalDecisionIds`) but from computed lists supplied via ctx.
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
anchorIdFor,
|
|
8
|
+
brokenBanner,
|
|
9
|
+
docLinkList,
|
|
10
|
+
escapeHtml,
|
|
11
|
+
fieldList,
|
|
12
|
+
fieldPara,
|
|
13
|
+
rawJsonDisclosure,
|
|
14
|
+
} from './helpers.js';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @param {object} tad
|
|
18
|
+
* @param {object} ctx
|
|
19
|
+
* @param {string|undefined} ctx.raw
|
|
20
|
+
* @param {import('#core/errors').RcfError[]} [ctx.errors]
|
|
21
|
+
* @param {string[]} [ctx.componentIds] - computed TAC children
|
|
22
|
+
* @param {string[]} [ctx.architecturalDecisionIds] - computed ADR children
|
|
23
|
+
* @returns {string}
|
|
24
|
+
*/
|
|
25
|
+
export function renderTad(tad, ctx) {
|
|
26
|
+
if (!tad) return '';
|
|
27
|
+
const anchor = anchorIdFor(tad.tadId ?? 'TAD');
|
|
28
|
+
const broken = ctx.errors?.length ? brokenBanner(ctx.errors) : '';
|
|
29
|
+
const overview = tad.systemOverview ?? {};
|
|
30
|
+
const componentIds = ctx.componentIds ?? [];
|
|
31
|
+
const architecturalDecisionIds = ctx.architecturalDecisionIds ?? [];
|
|
32
|
+
const principles = (tad.architecturePrinciples ?? []).map((p) => `
|
|
33
|
+
<li>
|
|
34
|
+
<strong>${escapeHtml(p.name)}.</strong> ${escapeHtml(p.description)}
|
|
35
|
+
<br/><em>Rationale:</em> ${escapeHtml(p.rationale)}
|
|
36
|
+
</li>`.trim()).join('\n');
|
|
37
|
+
const optionalSections = renderOptionalSections(tad);
|
|
38
|
+
return `
|
|
39
|
+
<article id="${anchor}" class="doc doc-tad">
|
|
40
|
+
<h3>${escapeHtml(tad.tadId ?? 'TAD')} - Technical Architecture Document</h3>
|
|
41
|
+
${broken}
|
|
42
|
+
${fieldPara('Executive summary', overview.executiveSummary)}
|
|
43
|
+
${fieldPara('System purpose', overview.systemPurpose)}
|
|
44
|
+
${fieldPara('Architectural approach', overview.architecturalApproach)}
|
|
45
|
+
${fieldList('Key capabilities', overview.keyCapabilities)}
|
|
46
|
+
${principles ? `<section class="field-list"><h4>Architecture principles</h4><ul>${principles}</ul></section>` : ''}
|
|
47
|
+
${optionalSections}
|
|
48
|
+
<section class="field-list"><h4>Components (TACs)</h4><p>${docLinkList(componentIds)}</p></section>
|
|
49
|
+
<section class="field-list"><h4>Architectural decisions (ADRs)</h4><p>${docLinkList(architecturalDecisionIds)}</p></section>
|
|
50
|
+
${rawJsonDisclosure(ctx.raw, tad, tad.tadId)}
|
|
51
|
+
</article>`.trim();
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function renderOptionalSections(tad) {
|
|
55
|
+
const out = [];
|
|
56
|
+
const named = {
|
|
57
|
+
dataArchitecture: 'Data architecture',
|
|
58
|
+
integrationArchitecture: 'Integration architecture',
|
|
59
|
+
securityArchitecture: 'Security architecture',
|
|
60
|
+
deploymentArchitecture: 'Deployment architecture',
|
|
61
|
+
operationalConcerns: 'Operational concerns',
|
|
62
|
+
};
|
|
63
|
+
for (const [key, label] of Object.entries(named)) {
|
|
64
|
+
const section = tad[key];
|
|
65
|
+
if (!section || typeof section !== 'object') continue;
|
|
66
|
+
const rows = Object.entries(section).map(([k, v]) => {
|
|
67
|
+
if (typeof v === 'string') return `<dt>${escapeHtml(k)}</dt><dd>${escapeHtml(v)}</dd>`;
|
|
68
|
+
if (Array.isArray(v)) return `<dt>${escapeHtml(k)}</dt><dd>${v.map((x) => `<code>${escapeHtml(typeof x === 'object' ? JSON.stringify(x) : x)}</code>`).join(', ')}</dd>`;
|
|
69
|
+
return `<dt>${escapeHtml(k)}</dt><dd><pre>${escapeHtml(JSON.stringify(v, null, 2))}</pre></dd>`;
|
|
70
|
+
}).join('\n');
|
|
71
|
+
out.push(`<section class="field-list"><h4>${escapeHtml(label)}</h4><dl>${rows}</dl></section>`);
|
|
72
|
+
}
|
|
73
|
+
return out.join('\n');
|
|
74
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// Test suite renderer. Post-3.7 the TS id field is `id` (matches the
|
|
2
|
+
// 0.2.0 test-suite schema); inline test cases carry `id` + `acId` +
|
|
3
|
+
// `description` + `status` (+ optional `testPointer`).
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
anchorIdFor,
|
|
7
|
+
brokenBanner,
|
|
8
|
+
docLink,
|
|
9
|
+
docLinkList,
|
|
10
|
+
escapeHtml,
|
|
11
|
+
fieldPara,
|
|
12
|
+
rawJsonDisclosure,
|
|
13
|
+
} from './helpers.js';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @param {object} ts
|
|
17
|
+
* @param {object} ctx
|
|
18
|
+
* @param {string|undefined} ctx.raw
|
|
19
|
+
* @param {import('#core/errors').RcfError[]} [ctx.errors]
|
|
20
|
+
* @returns {string}
|
|
21
|
+
*/
|
|
22
|
+
export function renderTestSuite(ts, ctx) {
|
|
23
|
+
if (!ts) return '';
|
|
24
|
+
const tsId = ts.id ?? 'TS';
|
|
25
|
+
const anchor = anchorIdFor(tsId);
|
|
26
|
+
const broken = ctx.errors?.length ? brokenBanner(ctx.errors) : '';
|
|
27
|
+
const tcs = (ts.testCases ?? []).map((tc) => `
|
|
28
|
+
<div class="tc" id="${escapeHtml(tc.id ?? '')}">
|
|
29
|
+
<p><strong>${escapeHtml(tc.id ?? 'TC')}</strong> - covers ${tc.acId ? docLink(tc.acId) : 'AC'} <span class="status ${escapeHtml(tc.status ?? '')}">${escapeHtml(tc.status ?? 'pending')}</span></p>
|
|
30
|
+
${tc.description ? `<p>${escapeHtml(tc.description)}</p>` : ''}
|
|
31
|
+
${tc.testPointer ? `<p><em>Test pointer:</em> <code>${escapeHtml(tc.testPointer)}</code></p>` : ''}
|
|
32
|
+
</div>`.trim()).join('\n');
|
|
33
|
+
return `
|
|
34
|
+
<article id="${anchor}" class="doc doc-ts">
|
|
35
|
+
<h3>${escapeHtml(tsId)} - ${escapeHtml(ts.title ?? 'test suite')}</h3>
|
|
36
|
+
${broken}
|
|
37
|
+
${ts.usId ? `<p><strong>Verifies user story:</strong> ${docLink(ts.usId)}</p>` : ''}
|
|
38
|
+
${fieldPara('Purpose', ts.purpose)}
|
|
39
|
+
${fieldPara('Test level', ts.testLevel)}
|
|
40
|
+
${ts.acIds?.length ? `<section class="field-list"><h4>Acceptance criteria verified</h4><p>${docLinkList(ts.acIds)}</p></section>` : ''}
|
|
41
|
+
${fieldPara('Status', ts.status)}
|
|
42
|
+
<section class="field-list"><h4>Test cases</h4>${tcs || '<em>no test cases</em>'}</section>
|
|
43
|
+
${rawJsonDisclosure(ctx.raw, ts, ts.id)}
|
|
44
|
+
</article>`.trim();
|
|
45
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
// User story renderer. ACs are rendered inline as list items so an AC can
|
|
2
|
+
// be a top-level scroll anchor (per Phase 3.2 D4) without wrapping each in
|
|
3
|
+
// its own `<section>`. Each AC line links back to the FBS or FBSs that
|
|
4
|
+
// deliver it via `fbsByAcId` (D8).
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
anchorIdFor,
|
|
8
|
+
brokenBanner,
|
|
9
|
+
docLink,
|
|
10
|
+
escapeHtml,
|
|
11
|
+
fieldPara,
|
|
12
|
+
rawJsonDisclosure,
|
|
13
|
+
} from './helpers.js';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @param {object} us
|
|
17
|
+
* @param {object} ctx
|
|
18
|
+
* @param {string|undefined} ctx.raw
|
|
19
|
+
* @param {import('#core/errors').RcfError[]} [ctx.errors]
|
|
20
|
+
* @param {Map<string, object[]>} [ctx.fbsByAcId]
|
|
21
|
+
* @returns {string}
|
|
22
|
+
*/
|
|
23
|
+
export function renderUserStory(us, ctx) {
|
|
24
|
+
if (!us) return '';
|
|
25
|
+
const anchor = anchorIdFor(us.usId ?? 'US');
|
|
26
|
+
const broken = ctx.errors?.length ? brokenBanner(ctx.errors) : '';
|
|
27
|
+
const acItems = (us.acceptanceCriteria ?? []).map((ac) => renderAcItem(ac, ctx)).join('\n');
|
|
28
|
+
return `
|
|
29
|
+
<article id="${anchor}" class="doc doc-us">
|
|
30
|
+
<h3>${escapeHtml(us.usId ?? 'US')} - ${escapeHtml(us.title ?? '')}</h3>
|
|
31
|
+
${broken}
|
|
32
|
+
<p><strong>As a:</strong> ${escapeHtml(us.asA ?? '')}</p>
|
|
33
|
+
<p><strong>I want:</strong> ${escapeHtml(us.iWant ?? '')}</p>
|
|
34
|
+
<p><strong>So that:</strong> ${escapeHtml(us.soThat ?? '')}</p>
|
|
35
|
+
${fieldPara('Description', us.description)}
|
|
36
|
+
<section class="field-list"><h4>Requirement</h4><p>${docLink(us.reqId)}</p></section>
|
|
37
|
+
<section class="acceptance-criteria">
|
|
38
|
+
<h4>Acceptance criteria</h4>
|
|
39
|
+
<ul class="ac-list">
|
|
40
|
+
${acItems}
|
|
41
|
+
</ul>
|
|
42
|
+
</section>
|
|
43
|
+
${rawJsonDisclosure(ctx.raw, us, us.usId)}
|
|
44
|
+
</article>`.trim();
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function renderAcItem(ac, ctx) {
|
|
48
|
+
const acId = ac.id ?? 'AC';
|
|
49
|
+
const acAnchor = anchorIdFor(acId);
|
|
50
|
+
const fbsList = ctx.fbsByAcId?.get(acId) ?? [];
|
|
51
|
+
const coveredBy = fbsList.length === 0
|
|
52
|
+
? '<em>not yet delivered by any FBS</em>'
|
|
53
|
+
: `Covered by ${fbsList.map((f) => docLink(f.fbsId)).join(', ')}`;
|
|
54
|
+
const parts = [];
|
|
55
|
+
parts.push(`<strong>${escapeHtml(acId)}</strong> - ${escapeHtml(ac.description ?? '')}`);
|
|
56
|
+
const gwt = [];
|
|
57
|
+
if (ac.given) gwt.push(`<em>Given</em> ${escapeHtml(ac.given)}`);
|
|
58
|
+
if (ac.when) gwt.push(`<em>When</em> ${escapeHtml(ac.when)}`);
|
|
59
|
+
if (ac.then) gwt.push(`<em>Then</em> ${escapeHtml(ac.then)}`);
|
|
60
|
+
if (gwt.length > 0) parts.push(`<div class="ac-gwt">${gwt.map((p) => `<p>${p}</p>`).join('')}</div>`);
|
|
61
|
+
parts.push(`<p class="ac-meta"><strong>Testable:</strong> ${escapeHtml(String(ac.testable ?? false))} - ${coveredBy}</p>`);
|
|
62
|
+
return `<li id="${acAnchor}" class="ac-item">${parts.join('\n')}</li>`;
|
|
63
|
+
}
|