rcf-lite 0.0.1 → 0.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +288 -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 +69 -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/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 +355 -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 +162 -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 +216 -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 +211 -0
- package/src/core/store/tp-resolve.js +176 -0
- package/src/core/store/validator.js +191 -0
- package/src/core/store/walker.js +898 -0
- package/src/core/store/writer.js +1849 -0
- package/src/design/index.js +11 -0
- package/src/design/writer.js +271 -0
- package/src/finalise/detect.js +129 -0
- package/src/finalise/index.js +17 -0
- package/src/finalise/ingest.js +119 -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 +1204 -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 +9 -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/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 +190 -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 +246 -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,338 @@
|
|
|
1
|
+
// Live-view client. Loaded by the server-rendered page as
|
|
2
|
+
// `<script src="/live-client.js" defer>`. Handles the SSE `/events`
|
|
3
|
+
// stream, in-place `innerHTML` swaps into `#rcf-live-content`, and
|
|
4
|
+
// persists `<details>` open-set + main scrollTop to `localStorage`
|
|
5
|
+
// across every swap and every full reload.
|
|
6
|
+
//
|
|
7
|
+
// Vanilla classic script (no ESM syntax) so the base HTML script tag
|
|
8
|
+
// stays plain. The IIFE exposes its internals as `window.__rcfLiveClient`
|
|
9
|
+
// so `test/view/live-client.test.js` can drive them via `node:vm` without
|
|
10
|
+
// jsdom.
|
|
11
|
+
//
|
|
12
|
+
// Spec: projects/rcf-build-lite/specs/phase-3.8-live-view.md D12, D13a,
|
|
13
|
+
// D13b, D14.
|
|
14
|
+
|
|
15
|
+
(function () {
|
|
16
|
+
var STORAGE_NS = 'rcf-view:v1:';
|
|
17
|
+
var STORAGE_OPEN = STORAGE_NS + 'openDetails';
|
|
18
|
+
var STORAGE_SCROLL = STORAGE_NS + 'scrollTop';
|
|
19
|
+
var HEARTBEAT_STALE_MS = 45000;
|
|
20
|
+
|
|
21
|
+
// ---- state persistence ------------------------------------------------
|
|
22
|
+
|
|
23
|
+
function safeGetItem(storage, key) {
|
|
24
|
+
try { return storage.getItem(key); } catch (e) { return null; }
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function safeSetItem(storage, key, value) {
|
|
28
|
+
try { storage.setItem(key, value); return true; } catch (e) { return false; }
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function snapshotState(deps) {
|
|
32
|
+
var doc = deps.document;
|
|
33
|
+
var storage = deps.storage;
|
|
34
|
+
if (!doc || !storage) return;
|
|
35
|
+
try {
|
|
36
|
+
var open = [];
|
|
37
|
+
var nodes = doc.querySelectorAll('details[data-doc-id]');
|
|
38
|
+
for (var i = 0; i < nodes.length; i += 1) {
|
|
39
|
+
var el = nodes[i];
|
|
40
|
+
if (el.open) open.push(el.getAttribute('data-doc-id'));
|
|
41
|
+
}
|
|
42
|
+
safeSetItem(storage, STORAGE_OPEN, JSON.stringify(open));
|
|
43
|
+
} catch (e) { /* soft */ }
|
|
44
|
+
try {
|
|
45
|
+
var main = doc.querySelector('main');
|
|
46
|
+
var scrollTop = 0;
|
|
47
|
+
if (main && typeof main.scrollTop === 'number') scrollTop = main.scrollTop;
|
|
48
|
+
else if (deps.window && typeof deps.window.scrollY === 'number') scrollTop = deps.window.scrollY;
|
|
49
|
+
safeSetItem(storage, STORAGE_SCROLL, String(scrollTop));
|
|
50
|
+
} catch (e) { /* soft */ }
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function loadOpenSet(storage) {
|
|
54
|
+
var raw = safeGetItem(storage, STORAGE_OPEN);
|
|
55
|
+
if (!raw) return [];
|
|
56
|
+
try {
|
|
57
|
+
var parsed = JSON.parse(raw);
|
|
58
|
+
if (Array.isArray(parsed)) return parsed.filter(function (id) { return typeof id === 'string'; });
|
|
59
|
+
} catch (e) { /* soft */ }
|
|
60
|
+
return [];
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function cssEscape(id) {
|
|
64
|
+
return String(id).replace(/["\\]/g, function (c) { return '\\' + c; });
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function restoreState(deps) {
|
|
68
|
+
var doc = deps.document;
|
|
69
|
+
var storage = deps.storage;
|
|
70
|
+
if (!doc || !storage) return { openedCount: 0, droppedIds: [], scrollTop: null };
|
|
71
|
+
var wanted = loadOpenSet(storage);
|
|
72
|
+
var opened = 0;
|
|
73
|
+
var dropped = [];
|
|
74
|
+
for (var i = 0; i < wanted.length; i += 1) {
|
|
75
|
+
var id = wanted[i];
|
|
76
|
+
var sel = 'details[data-doc-id="' + cssEscape(id) + '"]';
|
|
77
|
+
var el = null;
|
|
78
|
+
try { el = doc.querySelector(sel); } catch (e) { el = null; }
|
|
79
|
+
if (el) {
|
|
80
|
+
el.open = true;
|
|
81
|
+
opened += 1;
|
|
82
|
+
} else {
|
|
83
|
+
dropped.push(id);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
if (dropped.length > 0) {
|
|
87
|
+
// Stale-key hygiene: rewrite the persisted set with only the ids
|
|
88
|
+
// that still exist. Silent - no console output for expected drift.
|
|
89
|
+
var kept = wanted.filter(function (id) { return dropped.indexOf(id) === -1; });
|
|
90
|
+
safeSetItem(storage, STORAGE_OPEN, JSON.stringify(kept));
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
var scrollTop = null;
|
|
94
|
+
var raw = safeGetItem(storage, STORAGE_SCROLL);
|
|
95
|
+
if (raw != null) {
|
|
96
|
+
var n = Number(raw);
|
|
97
|
+
if (isFinite(n)) {
|
|
98
|
+
scrollTop = n;
|
|
99
|
+
var main = doc.querySelector('main');
|
|
100
|
+
try {
|
|
101
|
+
if (main) main.scrollTop = n;
|
|
102
|
+
else if (deps.window && typeof deps.window.scrollTo === 'function') deps.window.scrollTo(0, n);
|
|
103
|
+
} catch (e) { /* soft */ }
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return { openedCount: opened, droppedIds: dropped, scrollTop: scrollTop };
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// ---- connection state machine -----------------------------------------
|
|
110
|
+
|
|
111
|
+
// Inputs: readyState of EventSource (CONNECTING=0, OPEN=1, CLOSED=2),
|
|
112
|
+
// ms since last heartbeat (any type of event resets this). Output is
|
|
113
|
+
// one of 'connected' | 'reconnecting' | 'disconnected'.
|
|
114
|
+
function classifyConnection(state) {
|
|
115
|
+
var rs = state.readyState;
|
|
116
|
+
var since = state.msSinceLastEvent;
|
|
117
|
+
if (rs === 2) return 'disconnected';
|
|
118
|
+
if (rs === 0) return 'reconnecting';
|
|
119
|
+
if (typeof since === 'number' && since > HEARTBEAT_STALE_MS) return 'reconnecting';
|
|
120
|
+
return 'connected';
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// ---- version de-dup ---------------------------------------------------
|
|
124
|
+
|
|
125
|
+
function shouldApplyUpdate(currentVersion, nextVersion) {
|
|
126
|
+
if (typeof nextVersion !== 'number' || !isFinite(nextVersion)) return true;
|
|
127
|
+
if (currentVersion === null || currentVersion === undefined) return true;
|
|
128
|
+
return nextVersion > currentVersion;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// ---- browser boot ------------------------------------------------------
|
|
132
|
+
|
|
133
|
+
function bootBrowser(win) {
|
|
134
|
+
var doc = win.document;
|
|
135
|
+
var storage;
|
|
136
|
+
try { storage = win.localStorage; } catch (e) { storage = null; }
|
|
137
|
+
var deps = { document: doc, storage: storage || memoryStorage(), window: win };
|
|
138
|
+
|
|
139
|
+
// Inject the connection-status dot into the header. Kept out of the
|
|
140
|
+
// server-rendered HTML so the layout-regression baseline stays clean
|
|
141
|
+
// (only the wrapper + script tag + raw-json data-doc-id are the
|
|
142
|
+
// whitelisted deltas from Phase 3.6).
|
|
143
|
+
injectConnectionDot(doc);
|
|
144
|
+
|
|
145
|
+
// Restore prior state on initial load.
|
|
146
|
+
if (doc.readyState === 'loading') {
|
|
147
|
+
doc.addEventListener('DOMContentLoaded', function () { restoreState(deps); });
|
|
148
|
+
} else {
|
|
149
|
+
restoreState(deps);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
var currentVersion = null;
|
|
153
|
+
var lastEventAt = Date.now();
|
|
154
|
+
var connState = 'reconnecting';
|
|
155
|
+
|
|
156
|
+
function setState(next) {
|
|
157
|
+
if (next === connState) return;
|
|
158
|
+
connState = next;
|
|
159
|
+
updateDot(doc, next);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Snapshot on navigate-away / hide.
|
|
163
|
+
win.addEventListener('beforeunload', function () { snapshotState(deps); });
|
|
164
|
+
doc.addEventListener('visibilitychange', function () {
|
|
165
|
+
if (doc.visibilityState === 'hidden') snapshotState(deps);
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
var EventSourceCtor = win.EventSource;
|
|
169
|
+
if (typeof EventSourceCtor !== 'function') {
|
|
170
|
+
setState('disconnected');
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
var source = new EventSourceCtor('/events');
|
|
174
|
+
|
|
175
|
+
source.onopen = function () {
|
|
176
|
+
lastEventAt = Date.now();
|
|
177
|
+
setState('connected');
|
|
178
|
+
};
|
|
179
|
+
source.onerror = function () {
|
|
180
|
+
// EventSource sets readyState internally. Native reconnect keeps
|
|
181
|
+
// trying while readyState is CONNECTING.
|
|
182
|
+
var rs = typeof source.readyState === 'number' ? source.readyState : 2;
|
|
183
|
+
setState(classifyConnection({ readyState: rs, msSinceLastEvent: Date.now() - lastEventAt }));
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
source.addEventListener('tree-update', function (ev) {
|
|
187
|
+
lastEventAt = Date.now();
|
|
188
|
+
var payload;
|
|
189
|
+
try { payload = JSON.parse(ev.data); } catch (e) { return; }
|
|
190
|
+
if (!shouldApplyUpdate(currentVersion, payload.version)) return;
|
|
191
|
+
snapshotState(deps);
|
|
192
|
+
var host = doc.querySelector('#rcf-live-content');
|
|
193
|
+
if (host && typeof payload.contentHtml === 'string') {
|
|
194
|
+
host.innerHTML = payload.contentHtml;
|
|
195
|
+
}
|
|
196
|
+
currentVersion = payload.version;
|
|
197
|
+
// Tab buttons live in the header (outside the swap wrapper) so
|
|
198
|
+
// their click handlers survive. But the swap serves every panel
|
|
199
|
+
// with the same `hidden` state Phase 3.6 renders (Overview
|
|
200
|
+
// visible, others hidden), so we resync visibility with the
|
|
201
|
+
// currently-selected tab and re-run Mermaid in it. Also re-run
|
|
202
|
+
// Mermaid inside the selected panel because .mermaid nodes in
|
|
203
|
+
// the payload have no `data-processed` yet.
|
|
204
|
+
resyncTabsAfterSwap(doc, win);
|
|
205
|
+
restoreState(deps);
|
|
206
|
+
setState('connected');
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
source.addEventListener('heartbeat', function () {
|
|
210
|
+
lastEventAt = Date.now();
|
|
211
|
+
setState('connected');
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
source.addEventListener('walker-error', function (ev) {
|
|
215
|
+
lastEventAt = Date.now();
|
|
216
|
+
// Errors also render inline via the next tree-update; nothing
|
|
217
|
+
// else to do client-side for now.
|
|
218
|
+
try {
|
|
219
|
+
var payload = JSON.parse(ev.data);
|
|
220
|
+
if (win.rcfPage) win.rcfPage._lastWalkerErrors = payload.errors;
|
|
221
|
+
} catch (e) { /* soft */ }
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
source.addEventListener('shutdown', function () {
|
|
225
|
+
setState('disconnected');
|
|
226
|
+
try { source.close(); } catch (e) { /* soft */ }
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
// Idle liveness check: if no event in the heartbeat-stale window,
|
|
230
|
+
// demote the dot to reconnecting even if the socket has not thrown
|
|
231
|
+
// an error yet.
|
|
232
|
+
var pollId = win.setInterval(function () {
|
|
233
|
+
var rs = typeof source.readyState === 'number' ? source.readyState : 1;
|
|
234
|
+
setState(classifyConnection({ readyState: rs, msSinceLastEvent: Date.now() - lastEventAt }));
|
|
235
|
+
}, 5000);
|
|
236
|
+
if (pollId && typeof pollId.unref === 'function') pollId.unref();
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// After an SSE innerHTML swap, the tab bar in the header still shows
|
|
240
|
+
// the tab the user was on (its click handlers survived because the
|
|
241
|
+
// header was not replaced) but every freshly-inserted panel has the
|
|
242
|
+
// Overview-visible default from the server-rendered content. Bring
|
|
243
|
+
// the panels back in line with the aria-selected button and re-run
|
|
244
|
+
// Mermaid in the visible one.
|
|
245
|
+
function resyncTabsAfterSwap(doc, win) {
|
|
246
|
+
var TABS = ['overview', 'requirements', 'architecture', 'build'];
|
|
247
|
+
var selectedBtn = doc.querySelector('[role="tab"][aria-selected="true"]');
|
|
248
|
+
var selected = selectedBtn && selectedBtn.getAttribute ? selectedBtn.getAttribute('data-tab') : null;
|
|
249
|
+
if (!selected || TABS.indexOf(selected) === -1) selected = 'overview';
|
|
250
|
+
for (var i = 0; i < TABS.length; i += 1) {
|
|
251
|
+
var name = TABS[i];
|
|
252
|
+
var panel = doc.getElementById('tab-' + name);
|
|
253
|
+
if (!panel) continue;
|
|
254
|
+
if (name === selected) panel.removeAttribute('hidden');
|
|
255
|
+
else panel.setAttribute('hidden', '');
|
|
256
|
+
}
|
|
257
|
+
var visible = doc.getElementById('tab-' + selected);
|
|
258
|
+
try {
|
|
259
|
+
if (visible && win && win.mermaid && typeof win.mermaid.run === 'function') {
|
|
260
|
+
var pending = visible.querySelectorAll('.mermaid:not([data-processed="true"])');
|
|
261
|
+
if (pending.length > 0) {
|
|
262
|
+
win.mermaid.run({ nodes: Array.prototype.slice.call(pending) });
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
} catch (e) { /* soft */ }
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function memoryStorage() {
|
|
269
|
+
var m = Object.create(null);
|
|
270
|
+
return {
|
|
271
|
+
getItem: function (k) { return Object.prototype.hasOwnProperty.call(m, k) ? m[k] : null; },
|
|
272
|
+
setItem: function (k, v) { m[k] = String(v); },
|
|
273
|
+
removeItem: function (k) { delete m[k]; },
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
function injectConnectionDot(doc) {
|
|
278
|
+
if (doc.getElementById('rcf-conn-dot')) return;
|
|
279
|
+
var style = doc.createElement('style');
|
|
280
|
+
style.textContent = [
|
|
281
|
+
'#rcf-conn-dot {',
|
|
282
|
+
' position: fixed; top: 12px; right: 12px;',
|
|
283
|
+
' width: 10px; height: 10px; border-radius: 50%;',
|
|
284
|
+
' background: #999; opacity: 0.65; z-index: 9999;',
|
|
285
|
+
' transition: background 120ms ease-in-out;',
|
|
286
|
+
'}',
|
|
287
|
+
'#rcf-conn-dot.connected { background: #4c9a2a; }',
|
|
288
|
+
'#rcf-conn-dot.reconnecting { background: #d68f00; }',
|
|
289
|
+
'#rcf-conn-dot.disconnected { background: #b03a2e; }',
|
|
290
|
+
].join('\n');
|
|
291
|
+
doc.head.appendChild(style);
|
|
292
|
+
var dot = doc.createElement('span');
|
|
293
|
+
dot.id = 'rcf-conn-dot';
|
|
294
|
+
dot.className = 'reconnecting';
|
|
295
|
+
dot.setAttribute('role', 'status');
|
|
296
|
+
dot.setAttribute('aria-live', 'polite');
|
|
297
|
+
dot.title = 'Connecting to view server...';
|
|
298
|
+
doc.body.appendChild(dot);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
function updateDot(doc, state) {
|
|
302
|
+
var dot = doc.getElementById('rcf-conn-dot');
|
|
303
|
+
if (!dot) return;
|
|
304
|
+
dot.className = state;
|
|
305
|
+
if (state === 'connected') dot.title = 'Connected - live updates on';
|
|
306
|
+
else if (state === 'reconnecting') dot.title = 'Reconnecting to view server...';
|
|
307
|
+
else dot.title = 'Disconnected from view server';
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// ---- module exports (for node:vm tests) --------------------------------
|
|
311
|
+
|
|
312
|
+
var api = {
|
|
313
|
+
snapshotState: snapshotState,
|
|
314
|
+
restoreState: restoreState,
|
|
315
|
+
classifyConnection: classifyConnection,
|
|
316
|
+
shouldApplyUpdate: shouldApplyUpdate,
|
|
317
|
+
loadOpenSet: loadOpenSet,
|
|
318
|
+
memoryStorage: memoryStorage,
|
|
319
|
+
injectConnectionDot: injectConnectionDot,
|
|
320
|
+
updateDot: updateDot,
|
|
321
|
+
resyncTabsAfterSwap: resyncTabsAfterSwap,
|
|
322
|
+
STORAGE_OPEN: STORAGE_OPEN,
|
|
323
|
+
STORAGE_SCROLL: STORAGE_SCROLL,
|
|
324
|
+
HEARTBEAT_STALE_MS: HEARTBEAT_STALE_MS,
|
|
325
|
+
bootBrowser: bootBrowser,
|
|
326
|
+
};
|
|
327
|
+
|
|
328
|
+
if (typeof window !== 'undefined' && typeof document !== 'undefined') {
|
|
329
|
+
window.__rcfLiveClient = api;
|
|
330
|
+
try { bootBrowser(window); } catch (e) {
|
|
331
|
+
// Never let a client-side boot fault break the page.
|
|
332
|
+
try { window.console && window.console.error && window.console.error('[rcf-live-client]', e); } catch (_e) { /* soft */ }
|
|
333
|
+
}
|
|
334
|
+
} else if (typeof globalThis !== 'undefined') {
|
|
335
|
+
// Non-browser context (e.g. node:vm test harness with no window/document).
|
|
336
|
+
globalThis.__rcfLiveClient = api;
|
|
337
|
+
}
|
|
338
|
+
})();
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
// Mermaid diagram emission. Phase 3.6 removed the top-of-Overview-tab
|
|
2
|
+
// diagram: at ~7 REQs it was already stacking vertically and the PRD
|
|
3
|
+
// body already lists the requirement links inline. Per-requirement
|
|
4
|
+
// subdiagrams (REQ -> US -> AC + FBS delivery) remain - they add
|
|
5
|
+
// actual structural information per REQ. Click bindings anchor into
|
|
6
|
+
// the hash-routed doc DOM (D5, D7).
|
|
7
|
+
//
|
|
8
|
+
// Node classes match the CSS palette in style.css. Broken nodes carry the
|
|
9
|
+
// `broken` class (dashed border, distinct colour). FBS-to-AC edges are
|
|
10
|
+
// dashed; FBS dependencies are dotted; FBS context (TAC / ADR) edges are
|
|
11
|
+
// dotted-thick.
|
|
12
|
+
|
|
13
|
+
const NODE_LABEL = {
|
|
14
|
+
prd: 'PRD',
|
|
15
|
+
req: 'REQ',
|
|
16
|
+
us: 'US',
|
|
17
|
+
ac: 'AC',
|
|
18
|
+
tad: 'TAD',
|
|
19
|
+
tac: 'TAC',
|
|
20
|
+
adr: 'ADR',
|
|
21
|
+
bs: 'BS',
|
|
22
|
+
fbs: 'FBS',
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Mermaid is strict about id syntax: `-` is fine but parentheses, quotes
|
|
27
|
+
* and unicode chars are not. Document ids are already safe (e.g. "REQ-002");
|
|
28
|
+
* we just wrap labels in quotes.
|
|
29
|
+
*
|
|
30
|
+
* @param {string} id
|
|
31
|
+
* @returns {string}
|
|
32
|
+
*/
|
|
33
|
+
function nodeId(id) {
|
|
34
|
+
return id;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function nodeLabel(id, model) {
|
|
38
|
+
const doc = model.byId.get(id);
|
|
39
|
+
const title = pickTitle(doc, id);
|
|
40
|
+
if (!title) return `"${id}"`;
|
|
41
|
+
const safe = title.replace(/"/g, "'").replace(/\n/g, ' ');
|
|
42
|
+
const trimmed = safe.length > 48 ? `${safe.slice(0, 45)}...` : safe;
|
|
43
|
+
return `"${id}<br/>${trimmed}"`;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function pickTitle(doc, id) {
|
|
47
|
+
if (!doc) return null;
|
|
48
|
+
if (typeof doc.title === 'string') return doc.title;
|
|
49
|
+
if (typeof doc.name === 'string') return doc.name;
|
|
50
|
+
if (typeof doc.productName === 'string') return doc.productName;
|
|
51
|
+
// Phase 10 (X2 CodeNode bridge): CN documents often carry no title, but
|
|
52
|
+
// `path` is the identity that matters visually - show it in preference to
|
|
53
|
+
// falling straight back to the bare id.
|
|
54
|
+
if (typeof doc.path === 'string') return doc.path;
|
|
55
|
+
if (typeof doc.description === 'string') return doc.description;
|
|
56
|
+
return id;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function classForId(id) {
|
|
60
|
+
if (id.startsWith('PRD-')) return 'prd';
|
|
61
|
+
if (id.startsWith('REQ-')) return 'req';
|
|
62
|
+
if (id.startsWith('US-')) return 'us';
|
|
63
|
+
if (id.startsWith('AC-')) return 'ac';
|
|
64
|
+
if (id.startsWith('TAD-')) return 'tad';
|
|
65
|
+
if (id.startsWith('TAC-')) return 'tac';
|
|
66
|
+
if (id.startsWith('ADR-')) return 'adr';
|
|
67
|
+
if (id.startsWith('BS-')) return 'bs';
|
|
68
|
+
if (id.startsWith('FBS-')) return 'fbs';
|
|
69
|
+
if (id.startsWith('TS-')) return 'ts';
|
|
70
|
+
if (id.startsWith('TC-')) return 'tc';
|
|
71
|
+
// Phase 10 (X2 CodeNode bridge, spec D12): CN cosmetic class.
|
|
72
|
+
if (id.startsWith('CN-')) return 'cn';
|
|
73
|
+
return 'unknown';
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const CLASS_DEFS = `
|
|
77
|
+
classDef prd fill:#fde68a,stroke:#92400e,color:#1f2937;
|
|
78
|
+
classDef req fill:#bbf7d0,stroke:#065f46,color:#1f2937;
|
|
79
|
+
classDef us fill:#bae6fd,stroke:#0c4a6e,color:#1f2937;
|
|
80
|
+
classDef ac fill:#e0e7ff,stroke:#3730a3,color:#1f2937;
|
|
81
|
+
classDef tad fill:#fecaca,stroke:#991b1b,color:#1f2937;
|
|
82
|
+
classDef tac fill:#fed7aa,stroke:#9a3412,color:#1f2937;
|
|
83
|
+
classDef adr fill:#fbcfe8,stroke:#9d174d,color:#1f2937;
|
|
84
|
+
classDef bs fill:#ddd6fe,stroke:#5b21b6,color:#1f2937;
|
|
85
|
+
classDef fbs fill:#c7d2fe,stroke:#3730a3,color:#1f2937;
|
|
86
|
+
classDef ts fill:#99f6e4,stroke:#115e59,color:#1f2937;
|
|
87
|
+
classDef tc fill:#d9f99d,stroke:#3f6212,color:#1f2937;
|
|
88
|
+
classDef cn fill:#a5b4fc,stroke:#312e81,color:#1f2937;
|
|
89
|
+
classDef broken stroke:#dc2626,stroke-width:2px,stroke-dasharray:5 5,color:#7f1d1d;
|
|
90
|
+
`.trim();
|
|
91
|
+
|
|
92
|
+
function emitClick(id) {
|
|
93
|
+
return ` click ${nodeId(id)} "#${id}";`;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function emitClassAssignments(ids, model) {
|
|
97
|
+
const lines = [];
|
|
98
|
+
for (const id of ids) {
|
|
99
|
+
const cls = classForId(id);
|
|
100
|
+
if (cls !== 'unknown') {
|
|
101
|
+
lines.push(` class ${nodeId(id)} ${cls};`);
|
|
102
|
+
}
|
|
103
|
+
if (model.brokenIds.has(id)) {
|
|
104
|
+
lines.push(` class ${nodeId(id)} broken;`);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return lines;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Build a per-REQ subdiagram (`flowchart LR`).
|
|
112
|
+
*
|
|
113
|
+
* @param {import('./tree-model.js').BuiltTreeModel} model
|
|
114
|
+
* @param {object} req - the requirement document
|
|
115
|
+
* @returns {string}
|
|
116
|
+
*/
|
|
117
|
+
export function requirementSubdiagram(model, req) {
|
|
118
|
+
if (!req?.reqId) return 'flowchart LR\n empty[No requirement provided]';
|
|
119
|
+
/** @type {string[]} */
|
|
120
|
+
const lines = ['flowchart LR'];
|
|
121
|
+
/** @type {Set<string>} */
|
|
122
|
+
const declared = new Set();
|
|
123
|
+
/** @type {Set<string>} */
|
|
124
|
+
const allIds = new Set();
|
|
125
|
+
const declare = (id) => {
|
|
126
|
+
if (declared.has(id)) return;
|
|
127
|
+
declared.add(id);
|
|
128
|
+
allIds.add(id);
|
|
129
|
+
lines.push(` ${nodeId(id)}[${nodeLabel(id, model)}]`);
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
declare(req.reqId);
|
|
133
|
+
const stories = model.storiesByReqId.get(req.reqId) ?? [];
|
|
134
|
+
for (const us of stories) {
|
|
135
|
+
declare(us.usId);
|
|
136
|
+
lines.push(` ${nodeId(req.reqId)} --> ${nodeId(us.usId)}`);
|
|
137
|
+
for (const ac of us.acceptanceCriteria ?? []) {
|
|
138
|
+
declare(ac.id);
|
|
139
|
+
lines.push(` ${nodeId(us.usId)} --> ${nodeId(ac.id)}`);
|
|
140
|
+
const fbsList = model.fbsByAcId.get(ac.id) ?? [];
|
|
141
|
+
for (const f of fbsList) {
|
|
142
|
+
declare(f.fbsId);
|
|
143
|
+
// Edge direction reversed so LR layout places FBS to the right of
|
|
144
|
+
// its AC (reads "AC ... delivered by ... FBS"). Same relationship
|
|
145
|
+
// as the old FBS->AC edge; just laid out more naturally.
|
|
146
|
+
lines.push(` ${nodeId(ac.id)} -.->|delivered by| ${nodeId(f.fbsId)}`);
|
|
147
|
+
}
|
|
148
|
+
// Phase 10 (X2 CodeNode bridge, spec D12): Code Nodes implementing
|
|
149
|
+
// this AC. Empty when no code-nodes/ dir exists, so a spec-only tree
|
|
150
|
+
// renders byte-identical to pre-Phase-10 output.
|
|
151
|
+
const cnList = model.cnByAcId?.get(ac.id) ?? [];
|
|
152
|
+
for (const cnId of cnList) {
|
|
153
|
+
declare(cnId);
|
|
154
|
+
lines.push(` ${nodeId(ac.id)} -.->|implemented by| ${nodeId(cnId)}`);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
for (const id of allIds) lines.push(emitClick(id));
|
|
160
|
+
lines.push(...emitClassAssignments([...allIds], model));
|
|
161
|
+
lines.push(CLASS_DEFS);
|
|
162
|
+
return lines.join('\n');
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Build every per-REQ subdiagram, keyed by reqId.
|
|
167
|
+
*
|
|
168
|
+
* @param {import('./tree-model.js').BuiltTreeModel} model
|
|
169
|
+
* @returns {Map<string, string>}
|
|
170
|
+
*/
|
|
171
|
+
export function allRequirementSubdiagrams(model) {
|
|
172
|
+
const out = new Map();
|
|
173
|
+
for (const req of model.requirements) {
|
|
174
|
+
if (!req?.reqId) continue;
|
|
175
|
+
out.set(req.reqId, requirementSubdiagram(model, req));
|
|
176
|
+
}
|
|
177
|
+
return out;
|
|
178
|
+
}
|