rcf-lite 0.28.2 → 0.29.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 +317 -0
- package/bin/rcf.js +8 -0
- package/fixtures/canary-manifest.json +9 -9
- package/guidance/define-draft-shapes.md +89 -0
- package/guidance/define-intake.md +97 -0
- package/guidance/define-intent.md +107 -0
- package/guidance/discover-prototype.md +67 -0
- package/guidance/discovery-prototypes.md +69 -0
- package/guidance/harness-template.md +34 -0
- package/guidance/managed/agent-instructions-block.hash +1 -1
- package/guidance/managed/agent-instructions-block.md +34 -0
- package/guidance/manifest.json +9 -2
- package/package.json +1 -1
- package/rcf/adrs/adr-4120-canonical-json-hashing.json +30 -0
- package/rcf/adrs/adr-4121-define-sidecars.json +30 -0
- package/rcf/adrs/adr-4122-define-gates-blocking-and-optout.json +30 -0
- package/rcf/adrs/adr-4123-readiness-single-object.json +30 -0
- package/rcf/adrs/adr-4124-define-freeze-verb.json +30 -0
- package/rcf/adrs/adr-4126-persona-tagged-blockers-two-levels.json +40 -0
- package/rcf/adrs/adr-4127-readiness-tab-order-and-register.json +40 -0
- package/rcf/adrs/adr-4129-questions-harness-driven-loop.json +25 -0
- package/rcf/adrs/adr-4130-intake-harness-split-tool-scanned.json +20 -0
- package/rcf/adrs/adr-4132-prototypes-in-discovery-as-evidence.json +20 -0
- package/rcf/adrs/adr-4133-dag-dependency-depth-layout.json +40 -0
- package/rcf/adrs/adr-4134-index-json-route.json +35 -0
- package/rcf/adrs/adr-4135-readiness-po-register-and-adapter.json +35 -0
- package/rcf/adrs/adr-4136-embedding-host-fixture.json +35 -0
- package/rcf/code-nodes/cn-700.json +23 -0
- package/rcf/code-nodes/cn-701.json +14 -0
- package/rcf/code-nodes/cn-702.json +14 -0
- package/rcf/code-nodes/cn-703.json +16 -0
- package/rcf/code-nodes/cn-720.json +15 -0
- package/rcf/code-nodes/cn-721.json +15 -0
- package/rcf/code-nodes/cn-722.json +15 -0
- package/rcf/code-nodes/cn-723.json +18 -0
- package/rcf/code-nodes/cn-724.json +14 -0
- package/rcf/code-nodes/cn-725.json +37 -0
- package/rcf/code-nodes/cn-726.json +25 -0
- package/rcf/code-nodes/cn-727.json +23 -0
- package/rcf/code-nodes/cn-728.json +37 -0
- package/rcf/code-nodes/cn-729.json +26 -0
- package/rcf/code-nodes/cn-730.json +24 -0
- package/rcf/code-nodes/cn-731.json +20 -0
- package/rcf/code-nodes/cn-732.json +14 -0
- package/rcf/code-nodes/cn-733.json +14 -0
- package/rcf/code-nodes/cn-734.json +14 -0
- package/rcf/code-nodes/cn-735.json +20 -0
- package/rcf/code-nodes/cn-736.json +14 -0
- package/rcf/code-nodes/cn-737.json +14 -0
- package/rcf/code-nodes/cn-738.json +14 -0
- package/rcf/code-nodes/cn-739.json +18 -0
- package/rcf/code-nodes/cn-740.json +19 -0
- package/rcf/code-nodes/cn-741.json +16 -0
- package/rcf/code-nodes/cn-742.json +16 -0
- package/rcf/code-nodes/cn-743.json +15 -0
- package/rcf/code-nodes/cn-744.json +22 -0
- package/rcf/code-nodes/cn-745.json +16 -0
- package/rcf/code-nodes/cn-746.json +14 -0
- package/rcf/code-nodes/cn-747.json +18 -0
- package/rcf/code-nodes/cn-748.json +16 -0
- package/rcf/code-nodes/cn-749.json +16 -0
- package/rcf/code-nodes/cn-750.json +15 -0
- package/rcf/code-nodes/cn-751.json +18 -0
- package/rcf/code-nodes/cn-752.json +14 -0
- package/rcf/fbs/fbs-190.json +37 -0
- package/rcf/fbs/fbs-191.json +34 -0
- package/rcf/fbs/fbs-192.json +17 -0
- package/rcf/fbs/fbs-193.json +37 -0
- package/rcf/fbs/fbs-194.json +35 -0
- package/rcf/fbs/fbs-195.json +37 -0
- package/rcf/fbs/fbs-196.json +45 -0
- package/rcf/fbs/fbs-197.json +41 -0
- package/rcf/fbs/fbs-198.json +42 -0
- package/rcf/fbs/fbs-199.json +59 -0
- package/rcf/fbs/fbs-200.json +46 -0
- package/rcf/fbs/fbs-201.json +49 -0
- package/rcf/fbs/fbs-202.json +110 -0
- package/rcf/fbs/fbs-203.json +100 -0
- package/rcf/requirements/req-001.json +2 -1
- package/rcf/requirements/req-002.json +3 -2
- package/rcf/requirements/req-003.json +2 -1
- package/rcf/requirements/req-004.json +2 -1
- package/rcf/requirements/req-005.json +2 -1
- package/rcf/requirements/req-006.json +2 -1
- package/rcf/requirements/req-007.json +2 -1
- package/rcf/requirements/req-008.json +2 -1
- package/rcf/requirements/req-009.json +2 -1
- package/rcf/requirements/req-010.json +5 -2
- package/rcf/requirements/req-011.json +2 -1
- package/rcf/requirements/req-012.json +2 -1
- package/rcf/requirements/req-013.json +2 -1
- package/rcf/requirements/req-014.json +2 -1
- package/rcf/requirements/req-015.json +2 -1
- package/rcf/requirements/req-016.json +5 -2
- package/rcf/requirements/req-017.json +6 -2
- package/rcf/requirements/req-018.json +5 -2
- package/rcf/requirements/req-019.json +5 -2
- package/rcf/requirements/req-020.json +5 -2
- package/rcf/requirements/req-021.json +5 -2
- package/rcf/requirements/req-022.json +5 -2
- package/rcf/requirements/req-023.json +6 -4
- package/rcf/requirements/req-024.json +5 -2
- package/rcf/requirements/req-025.json +5 -2
- package/rcf/requirements/req-026.json +5 -2
- package/rcf/requirements/req-027.json +5 -2
- package/rcf/requirements/req-028.json +5 -2
- package/rcf/requirements/req-029.json +5 -2
- package/rcf/requirements/req-030.json +5 -2
- package/rcf/requirements/req-040.json +5 -2
- package/rcf/requirements/req-041.json +5 -2
- package/rcf/requirements/req-042.json +5 -2
- package/rcf/requirements/req-043.json +5 -2
- package/rcf/requirements/req-050.json +5 -2
- package/rcf/requirements/req-051.json +5 -2
- package/rcf/requirements/req-052.json +5 -2
- package/rcf/requirements/req-053.json +5 -2
- package/rcf/requirements/req-054.json +5 -2
- package/rcf/requirements/req-060.json +5 -2
- package/rcf/requirements/req-061.json +5 -2
- package/rcf/requirements/req-062.json +5 -2
- package/rcf/requirements/req-063.json +5 -2
- package/rcf/requirements/req-070.json +5 -2
- package/rcf/requirements/req-071.json +5 -2
- package/rcf/requirements/req-072.json +5 -2
- package/rcf/requirements/req-073.json +5 -2
- package/rcf/requirements/req-074.json +5 -2
- package/rcf/requirements/req-075.json +5 -2
- package/rcf/requirements/req-076.json +5 -2
- package/rcf/requirements/req-077.json +5 -2
- package/rcf/requirements/req-080.json +5 -2
- package/rcf/requirements/req-081.json +5 -2
- package/rcf/requirements/req-082.json +5 -2
- package/rcf/requirements/req-083.json +5 -2
- package/rcf/requirements/req-084.json +5 -2
- package/rcf/requirements/req-085.json +5 -2
- package/rcf/requirements/req-086.json +5 -2
- package/rcf/requirements/req-090.json +5 -2
- package/rcf/requirements/req-091.json +5 -2
- package/rcf/requirements/req-092.json +5 -2
- package/rcf/requirements/req-093.json +5 -2
- package/rcf/requirements/req-094.json +5 -2
- package/rcf/requirements/req-100.json +5 -2
- package/rcf/requirements/req-101.json +5 -2
- package/rcf/requirements/req-102.json +5 -2
- package/rcf/requirements/req-103.json +5 -2
- package/rcf/requirements/req-104.json +5 -2
- package/rcf/requirements/req-110.json +5 -2
- package/rcf/requirements/req-111.json +5 -2
- package/rcf/requirements/req-112.json +5 -2
- package/rcf/requirements/req-113.json +5 -2
- package/rcf/requirements/req-114.json +5 -2
- package/rcf/requirements/req-115.json +5 -2
- package/rcf/requirements/req-120.json +5 -2
- package/rcf/requirements/req-121.json +5 -2
- package/rcf/requirements/req-122.json +5 -2
- package/rcf/requirements/req-123.json +5 -2
- package/rcf/requirements/req-124.json +5 -2
- package/rcf/requirements/req-125.json +5 -2
- package/rcf/requirements/req-130.json +6 -2
- package/rcf/requirements/req-131.json +6 -2
- package/rcf/requirements/req-132.json +6 -2
- package/rcf/requirements/req-133.json +6 -2
- package/rcf/requirements/req-134.json +6 -2
- package/rcf/requirements/req-140.json +5 -2
- package/rcf/requirements/req-145.json +5 -2
- package/rcf/requirements/req-150.json +6 -2
- package/rcf/requirements/req-151.json +6 -2
- package/rcf/requirements/req-152.json +6 -2
- package/rcf/requirements/req-153.json +6 -2
- package/rcf/requirements/req-154.json +5 -2
- package/rcf/requirements/req-155.json +4 -1
- package/rcf/requirements/req-156.json +4 -1
- package/rcf/requirements/req-157.json +4 -1
- package/rcf/requirements/req-158.json +4 -1
- package/rcf/requirements/req-159.json +4 -1
- package/rcf/requirements/req-160.json +4 -1
- package/rcf/requirements/req-161.json +4 -1
- package/rcf/requirements/req-162.json +4 -1
- package/rcf/requirements/req-163.json +5 -2
- package/rcf/requirements/req-164.json +5 -2
- package/rcf/requirements/req-165.json +5 -2
- package/rcf/requirements/req-170.json +33 -0
- package/rcf/requirements/req-171.json +44 -0
- package/rcf/requirements/req-172.json +25 -0
- package/rcf/requirements/req-173.json +25 -0
- package/rcf/requirements/req-174.json +31 -0
- package/rcf/requirements/req-175.json +31 -0
- package/rcf/requirements/req-176.json +25 -0
- package/rcf/requirements/req-180.json +31 -0
- package/rcf/requirements/req-181.json +31 -0
- package/rcf/requirements/req-182.json +25 -0
- package/rcf/requirements/req-186.json +25 -0
- package/rcf/requirements/req-187.json +31 -0
- package/rcf/requirements/req-188.json +25 -0
- package/rcf/tacs/tac-4120-define-delta-and-freeze.json +21 -0
- package/rcf/tacs/tac-4121-define-ledgers.json +35 -0
- package/rcf/tacs/tac-4122-define-gates.json +61 -0
- package/rcf/tacs/tac-4123-define-readiness.json +61 -0
- package/rcf/tacs/tac-4124-define-freeze.json +28 -0
- package/rcf/tacs/tac-4127-readiness-view.json +49 -0
- package/rcf/tacs/tac-4128-define-guidance.json +53 -0
- package/rcf/tacs/tac-4129-define-questions.json +45 -0
- package/rcf/tacs/tac-4130-define-intake-brief.json +35 -0
- package/rcf/tacs/tac-4131-build-dag-view.json +39 -0
- package/rcf/tacs/tac-4132-id-lookup.json +42 -0
- package/rcf/tacs/tac-4133-readiness-po-layer.json +37 -0
- package/rcf/tacs/tac-4134-wespa-host-fixture.json +54 -0
- package/rcf/test-suites/ts-016.json +3 -3
- package/rcf/test-suites/ts-204.json +30 -0
- package/rcf/test-suites/ts-205.json +22 -0
- package/rcf/test-suites/ts-206.json +22 -0
- package/rcf/test-suites/ts-207.json +22 -0
- package/rcf/test-suites/ts-208.json +22 -0
- package/rcf/test-suites/ts-209.json +22 -0
- package/rcf/test-suites/ts-210.json +22 -0
- package/rcf/test-suites/ts-211.json +22 -0
- package/rcf/test-suites/ts-212.json +22 -0
- package/rcf/test-suites/ts-213.json +50 -0
- package/rcf/test-suites/ts-214.json +74 -0
- package/rcf/test-suites/ts-215.json +211 -0
- package/rcf/test-suites/ts-216.json +94 -0
- package/rcf/test-suites/ts-217.json +62 -0
- package/rcf/test-suites/ts-218.json +78 -0
- package/rcf/test-suites/ts-219.json +70 -0
- package/rcf/test-suites/ts-220.json +78 -0
- package/rcf/test-suites/ts-221.json +78 -0
- package/rcf/test-suites/ts-222.json +100 -0
- package/rcf/test-suites/ts-223.json +94 -0
- package/rcf/test-suites/ts-224.json +70 -0
- package/rcf/test-suites/ts-225.json +78 -0
- package/rcf/test-suites/ts-226.json +158 -0
- package/rcf/test-suites/ts-227.json +38 -0
- package/rcf/test-suites/ts-228.json +86 -0
- package/rcf/test-suites/ts-229.json +83 -0
- package/rcf/user-stories/us-15601.json +4 -4
- package/rcf/user-stories/us-17001.json +27 -0
- package/rcf/user-stories/us-17002.json +22 -0
- package/rcf/user-stories/us-17003.json +22 -0
- package/rcf/user-stories/us-17004.json +22 -0
- package/rcf/user-stories/us-17005.json +22 -0
- package/rcf/user-stories/us-17006.json +21 -0
- package/rcf/user-stories/us-17007.json +21 -0
- package/rcf/user-stories/us-17008.json +22 -0
- package/rcf/user-stories/us-17101.json +22 -0
- package/rcf/user-stories/us-17102.json +22 -0
- package/rcf/user-stories/us-17201.json +38 -0
- package/rcf/user-stories/us-17301.json +38 -0
- package/rcf/user-stories/us-17302.json +50 -0
- package/rcf/user-stories/us-17401.json +70 -0
- package/rcf/user-stories/us-17402.json +55 -0
- package/rcf/user-stories/us-17403.json +60 -0
- package/rcf/user-stories/us-17501.json +48 -0
- package/rcf/user-stories/us-17502.json +60 -0
- package/rcf/user-stories/us-17503.json +65 -0
- package/rcf/user-stories/us-17601.json +58 -0
- package/rcf/user-stories/us-18001.json +55 -0
- package/rcf/user-stories/us-18002.json +35 -0
- package/rcf/user-stories/us-18101.json +35 -0
- package/rcf/user-stories/us-18202.json +40 -0
- package/rcf/user-stories/us-18601.json +55 -0
- package/rcf/user-stories/us-18602.json +50 -0
- package/rcf/user-stories/us-18603.json +45 -0
- package/rcf/user-stories/us-18701.json +50 -0
- package/rcf/user-stories/us-18702.json +50 -0
- package/rcf/user-stories/us-18801.json +45 -0
- package/rcf/user-stories/us-204.json +59 -0
- package/rcf/user-stories/us-205.json +64 -0
- package/rcf/user-stories/us-206.json +54 -0
- package/releases/releases.yaml +31 -1
- package/src/cli/feedback.js +126 -10
- package/src/cli/freeze.js +730 -0
- package/src/cli/help.js +23 -0
- package/src/cli/ledger.js +615 -0
- package/src/cli/questions.js +288 -0
- package/src/cli/readiness.js +498 -0
- package/src/cli/view.js +29 -0
- package/src/core/store/writer.js +86 -6
- package/src/define/freeze-record.js +247 -0
- package/src/define/ledgers.js +712 -0
- package/src/feedback/redact.js +105 -19
- package/src/intake/index.js +1 -1
- package/src/intake/orchestrator.js +117 -0
- package/src/mcp/tools.js +354 -0
- package/src/query/delta.js +282 -0
- package/src/query/gates.js +1107 -0
- package/src/query/index.js +50 -0
- package/src/query/questions.js +438 -0
- package/src/query/readiness.js +574 -0
- package/src/server/index.js +60 -1
- package/src/server/routes.js +133 -4
- package/src/setup/identity-seed.js +17 -0
- package/src/view/build-dag.js +563 -0
- package/src/view/components/badge.js +41 -0
- package/src/view/components/diff.js +72 -0
- package/src/view/components/doc-row.js +62 -0
- package/src/view/components/empty-state.js +35 -0
- package/src/view/components/entity-selector.js +137 -0
- package/src/view/components/filter-bar.js +97 -0
- package/src/view/components/findings-list.js +49 -0
- package/src/view/components/index.js +12 -0
- package/src/view/components/pill.js +46 -0
- package/src/view/components/sub-tab-strip.js +43 -0
- package/src/view/components/toast.js +25 -0
- package/src/view/components-fixture.js +265 -0
- package/src/view/doc-renderers/architecture.js +304 -0
- package/src/view/doc-renderers/build.js +160 -0
- package/src/view/doc-renderers/helpers.js +2 -2
- package/src/view/doc-renderers/index.js +2 -0
- package/src/view/doc-renderers/prd.js +35 -6
- package/src/view/doc-renderers/req.js +10 -2
- package/src/view/doc-renderers/user-story.js +10 -5
- package/src/view/html-page.js +717 -300
- package/src/view/index.js +109 -3
- package/src/view/live-client.js +61 -8
- package/src/view/needs-work.js +79 -0
- package/src/view/page-init.js +2193 -0
- package/src/view/product-map.js +751 -0
- package/src/view/readiness/phrasebook.js +115 -0
- package/src/view/readiness/po-layer.js +288 -0
- package/src/view/readiness/question-adapter.js +329 -0
- package/src/view/readiness/stage-legend.js +71 -0
- package/src/view/readiness.js +505 -0
- package/src/view/style.css +2279 -266
- package/src/view/test-host/embed-client.js +121 -0
- package/src/view/test-host/fixture.js +183 -0
- package/src/view/test-host/lints.js +220 -0
- package/src/view/view-index.js +323 -0
package/src/view/index.js
CHANGED
|
@@ -6,16 +6,33 @@
|
|
|
6
6
|
|
|
7
7
|
import { dirname, join, resolve } from 'node:path';
|
|
8
8
|
import { fileURLToPath } from 'node:url';
|
|
9
|
-
import { stat } from 'node:fs/promises';
|
|
9
|
+
import { readFile, stat } from 'node:fs/promises';
|
|
10
10
|
|
|
11
|
-
import { walkTree } from '#core/store';
|
|
11
|
+
import { resolveTestPointers, walkTree } from '#core/store';
|
|
12
|
+
import { computeReadiness } from '../query/readiness.js';
|
|
13
|
+
import { computeQuestions } from '../query/questions.js';
|
|
14
|
+
import { loadFreezeRecord } from '../define/freeze-record.js';
|
|
15
|
+
import { loadAllLedgers } from '../define/ledgers.js';
|
|
12
16
|
import { renderContent, renderPage } from './html-page.js';
|
|
17
|
+
import { renderProductMapGrouping } from './product-map.js';
|
|
13
18
|
import { buildTreeModel } from './tree-model.js';
|
|
19
|
+
import { serialiseViewIndex } from './view-index.js';
|
|
14
20
|
|
|
15
21
|
const here = dirname(fileURLToPath(import.meta.url));
|
|
16
22
|
export const VENDORED_MERMAID_PATH = resolve(here, 'vendored', 'mermaid.min.js');
|
|
17
23
|
export const STYLE_CSS_PATH = resolve(here, 'style.css');
|
|
18
24
|
export const LIVE_CLIENT_PATH = resolve(here, 'live-client.js');
|
|
25
|
+
// Viewer UI refresh PR 1 (Dex / wespa 2026-10-02 #263): the former
|
|
26
|
+
// inline <script> block from html-page.js now lives at this path and
|
|
27
|
+
// is served over the new /page-init.js route. Release notes name it so
|
|
28
|
+
// wespa's proxy allow-list is extended before any version pin bump.
|
|
29
|
+
export const PAGE_INIT_PATH = resolve(here, 'page-init.js');
|
|
30
|
+
// Viewer UI refresh PR 9 (TAC-4134, ADR-4136): wespa host fixture
|
|
31
|
+
// client script. Served over /test-host.js only when the server is
|
|
32
|
+
// started with testHost: true (CLI --test-host or RCF_VIEW_TEST_HOST=1).
|
|
33
|
+
// Off the default route surface so wespa's proxy allow-list is
|
|
34
|
+
// unaffected.
|
|
35
|
+
export const TEST_HOST_CLIENT_PATH = resolve(here, 'test-host', 'embed-client.js');
|
|
19
36
|
|
|
20
37
|
/**
|
|
21
38
|
* Walk up from `start` looking for an ancestor directory containing a
|
|
@@ -56,8 +73,97 @@ export async function findProjectRoot(start) {
|
|
|
56
73
|
*/
|
|
57
74
|
export async function renderModelToPage({ projectRoot }) {
|
|
58
75
|
const { tree, errors } = await walkTree({ projectRoot });
|
|
76
|
+
|
|
77
|
+
// Load the readiness inputs once per rewalk; the viewer is pure
|
|
78
|
+
// (never recomputes readiness). Failures load as `null` so the tab
|
|
79
|
+
// still renders when a tree is partial.
|
|
80
|
+
const [freezeRecord, ledgers, testPointers, profileText] = await Promise.all([
|
|
81
|
+
loadFreezeRecord({ projectRoot }).catch(() => null),
|
|
82
|
+
loadAllLedgers({ projectRoot }).catch(() => ({})),
|
|
83
|
+
resolveTestPointers({ projectRoot, tree }).catch(() => undefined),
|
|
84
|
+
readProfileText(projectRoot),
|
|
85
|
+
]);
|
|
86
|
+
|
|
87
|
+
let readiness = null;
|
|
88
|
+
try {
|
|
89
|
+
readiness = computeReadiness(tree, {
|
|
90
|
+
freeze: freezeRecord,
|
|
91
|
+
ledgers,
|
|
92
|
+
profile: undefined,
|
|
93
|
+
profileText,
|
|
94
|
+
testPointers,
|
|
95
|
+
validateErrors: errors,
|
|
96
|
+
});
|
|
97
|
+
} catch (err) {
|
|
98
|
+
// A malformed tree should not block the viewer; the Readiness tab
|
|
99
|
+
// renders its "could not be computed" placeholder in that case.
|
|
100
|
+
readiness = null;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Viewer UI refresh PR 8 (TAC-4133, ADR-4135, AC-18002-1): fold the
|
|
104
|
+
// DEFINE step 3 PR 2 `computeQuestions` output onto the readiness
|
|
105
|
+
// object before the view reads it, so the PO layer's question
|
|
106
|
+
// adapter feature-detects the real shape (`questions[]`, `groups[]`,
|
|
107
|
+
// `optional[]`, `engineer.blockers`) rather than falling back to the
|
|
108
|
+
// blockers path. Pure fold; `computeQuestions` is a composer over
|
|
109
|
+
// the same readiness object and writes nothing under rcf/.
|
|
110
|
+
if (readiness) {
|
|
111
|
+
try {
|
|
112
|
+
const q = computeQuestions(readiness, {
|
|
113
|
+
tree,
|
|
114
|
+
ledgers,
|
|
115
|
+
profileText,
|
|
116
|
+
persona: 'productOwner',
|
|
117
|
+
});
|
|
118
|
+
readiness.questions = q.questions;
|
|
119
|
+
readiness.questionGroups = q.groups;
|
|
120
|
+
readiness.questionOptional = q.optional;
|
|
121
|
+
readiness.questionEngineer = q.engineer ?? null;
|
|
122
|
+
} catch {
|
|
123
|
+
// Composition failure never blocks the viewer; the adapter falls
|
|
124
|
+
// back to the blockers path when `readiness.questions` is absent.
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
59
128
|
const model = buildTreeModel({ tree, errors });
|
|
129
|
+
model.readiness = readiness;
|
|
130
|
+
model.profileText = profileText;
|
|
131
|
+
model.freezeRecord = freezeRecord;
|
|
60
132
|
const fullPageHtml = renderPage(model);
|
|
61
133
|
const contentHtml = renderContent(model);
|
|
62
|
-
|
|
134
|
+
// Pre-rendered partials for the /product-map/<group> endpoint
|
|
135
|
+
// (AC-17007-1). The full page ships only the shape grouping's REQ
|
|
136
|
+
// cards; the client fetches the other three on demand from these
|
|
137
|
+
// strings. Regenerated on every rewalk so the cache stays consistent
|
|
138
|
+
// with the tree on disk.
|
|
139
|
+
const pmPartials = {
|
|
140
|
+
shape: renderProductMapGrouping(model, 'shape'),
|
|
141
|
+
component: renderProductMapGrouping(model, 'component'),
|
|
142
|
+
trace: renderProductMapGrouping(model, 'trace'),
|
|
143
|
+
capability: renderProductMapGrouping(model, 'capability'),
|
|
144
|
+
blueprint: renderProductMapGrouping(model, 'blueprint'),
|
|
145
|
+
};
|
|
146
|
+
// Viewer UI refresh PR 7 (TAC-4132, ADR-4134): the ID lookup index
|
|
147
|
+
// is built on every rewalk from BuiltTreeModel and served from the
|
|
148
|
+
// new `./index.json` route (release-noted so wespa's proxy allow-
|
|
149
|
+
// list is extended before the version pin bump). One row per doc id
|
|
150
|
+
// plus one row per AC under each US.
|
|
151
|
+
const indexJson = serialiseViewIndex(model);
|
|
152
|
+
return { fullPageHtml, contentHtml, errors, tree, pmPartials, indexJson };
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Read `rcf/.identity/profile.md` text or return null when the file
|
|
157
|
+
* is absent (the Readiness tab orders persona groups as PO-first in
|
|
158
|
+
* the absent case).
|
|
159
|
+
*
|
|
160
|
+
* @param {string} projectRoot
|
|
161
|
+
* @returns {Promise<string | null>}
|
|
162
|
+
*/
|
|
163
|
+
async function readProfileText(projectRoot) {
|
|
164
|
+
try {
|
|
165
|
+
return await readFile(join(projectRoot, 'rcf', '.identity', 'profile.md'), 'utf8');
|
|
166
|
+
} catch (err) {
|
|
167
|
+
return null;
|
|
168
|
+
}
|
|
63
169
|
}
|
package/src/view/live-client.js
CHANGED
|
@@ -185,7 +185,10 @@
|
|
|
185
185
|
function fetchScope(slug, fetchImpl) {
|
|
186
186
|
var f = typeof fetchImpl === 'function' ? fetchImpl : (typeof fetch === 'function' ? fetch : null);
|
|
187
187
|
if (!f) return Promise.reject(new Error('no fetch available'));
|
|
188
|
-
|
|
188
|
+
// Dex / wespa 2026-10-02 (#263, PR 1): asset references are relative
|
|
189
|
+
// so the viewer works under the proxy mount (/rcf-viewer/) without
|
|
190
|
+
// path rewrites.
|
|
191
|
+
var url = './scope.json?blueprint=' + encodeURIComponent(slug);
|
|
189
192
|
return Promise.resolve(f(url)).then(function (resp) {
|
|
190
193
|
if (!resp) throw new Error('scope: no response');
|
|
191
194
|
var okFlag = resp.ok !== undefined ? resp.ok : (resp.status >= 200 && resp.status < 300);
|
|
@@ -410,7 +413,9 @@
|
|
|
410
413
|
setState('disconnected');
|
|
411
414
|
return;
|
|
412
415
|
}
|
|
413
|
-
|
|
416
|
+
// Dex / wespa 2026-10-02 (#263, PR 1): relative so the stream
|
|
417
|
+
// works under the proxy mount without a rewrite rule.
|
|
418
|
+
var source = new EventSourceCtor('./events');
|
|
414
419
|
|
|
415
420
|
source.onopen = function () {
|
|
416
421
|
lastEventAt = Date.now();
|
|
@@ -434,6 +439,17 @@
|
|
|
434
439
|
host.innerHTML = payload.contentHtml;
|
|
435
440
|
}
|
|
436
441
|
currentVersion = payload.version;
|
|
442
|
+
// The Product Map panel keeps a client-side partial cache
|
|
443
|
+
// (window.__rcfPmPartialCache is opaque to us). A tree-update
|
|
444
|
+
// invalidates every partial: the next activatePmGroup on a lazy
|
|
445
|
+
// grouping refetches instead of using the stale cache. See
|
|
446
|
+
// hydrateLazyGroup in the inline script.
|
|
447
|
+
win.__rcfPmDirty = true;
|
|
448
|
+
// Viewer UI refresh PR 7 (TAC-4132, ADR-4134): the ID lookup
|
|
449
|
+
// index is a snapshot of the tree. A tree-update invalidates
|
|
450
|
+
// whichever rows the client last fetched; wireLookup refetches
|
|
451
|
+
// on the next modal open instead of staying stale.
|
|
452
|
+
win.__rcfLookupDirty = true;
|
|
437
453
|
// Tab buttons live in the header (outside the swap wrapper) so
|
|
438
454
|
// their click handlers survive. But the swap serves every panel
|
|
439
455
|
// with the same `hidden` state Phase 3.6 renders (Overview
|
|
@@ -442,6 +458,13 @@
|
|
|
442
458
|
// Mermaid inside the selected panel because .mermaid nodes in
|
|
443
459
|
// the payload have no `data-processed` yet.
|
|
444
460
|
resyncTabsAfterSwap(doc, win);
|
|
461
|
+
// Re-invoke the page init so pm-group-btn / pm-status-select
|
|
462
|
+
// listeners (which lived inside the swapped wrapper) are attached
|
|
463
|
+
// to the fresh DOM. onReady is idempotent for the header-scoped
|
|
464
|
+
// bindings that survive the swap.
|
|
465
|
+
if (win.rcfPage && typeof win.rcfPage.init === 'function') {
|
|
466
|
+
try { win.rcfPage.init(); } catch (e) { /* soft */ }
|
|
467
|
+
}
|
|
445
468
|
restoreState(deps);
|
|
446
469
|
applyScope();
|
|
447
470
|
setState('connected');
|
|
@@ -484,8 +507,15 @@
|
|
|
484
507
|
// the panels back in line with the aria-selected button and re-run
|
|
485
508
|
// Mermaid in the visible one.
|
|
486
509
|
function resyncTabsAfterSwap(doc, win) {
|
|
487
|
-
|
|
488
|
-
|
|
510
|
+
// Phase 3.8 tabs; Phase 3.9 added product-map. Product Map MUST be
|
|
511
|
+
// in this list so an SSE swap while it is the active tab keeps the
|
|
512
|
+
// panel visible; without it, resolveActiveTab falls back to
|
|
513
|
+
// overview and the freshly-swapped pm listeners have already been
|
|
514
|
+
// rewired by the rcfPage.init() call below.
|
|
515
|
+
var TABS = ['overview', 'requirements', 'architecture', 'build', 'product-map'];
|
|
516
|
+
// Scope to the top-level nav.tabs to avoid matching pm-group-btn
|
|
517
|
+
// nodes (they also carry role="tab").
|
|
518
|
+
var selectedBtn = doc.querySelector('nav.tabs [role="tab"][aria-selected="true"]');
|
|
489
519
|
var selected = selectedBtn && selectedBtn.getAttribute ? selectedBtn.getAttribute('data-tab') : null;
|
|
490
520
|
if (!selected || TABS.indexOf(selected) === -1) selected = 'overview';
|
|
491
521
|
for (var i = 0; i < TABS.length; i += 1) {
|
|
@@ -515,8 +545,25 @@
|
|
|
515
545
|
};
|
|
516
546
|
}
|
|
517
547
|
|
|
548
|
+
// Connection pill: the shell footer renders #rcf-conn-pill (viewer UI
|
|
549
|
+
// refresh PR 1 follow-up: pill moved from a floating top-right dot
|
|
550
|
+
// into the footer). If the pill is missing (older shell, embedded
|
|
551
|
+
// snippet, no-shell contexts), fall back to injecting a floating dot
|
|
552
|
+
// so the live-status signal is never silently dropped.
|
|
553
|
+
function pillLabelFor(state) {
|
|
554
|
+
if (state === 'connected') return 'Live updates on';
|
|
555
|
+
if (state === 'reconnecting') return 'Reconnecting…';
|
|
556
|
+
return 'Disconnected';
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
function pillTitleFor(state) {
|
|
560
|
+
if (state === 'connected') return 'Connected - live updates on';
|
|
561
|
+
if (state === 'reconnecting') return 'Reconnecting to view server...';
|
|
562
|
+
return 'Disconnected from view server';
|
|
563
|
+
}
|
|
564
|
+
|
|
518
565
|
function injectConnectionDot(doc) {
|
|
519
|
-
if (doc.getElementById('rcf-conn-dot')) return;
|
|
566
|
+
if (doc.getElementById('rcf-conn-pill') || doc.getElementById('rcf-conn-dot')) return;
|
|
520
567
|
var style = doc.createElement('style');
|
|
521
568
|
style.textContent = [
|
|
522
569
|
'#rcf-conn-dot {',
|
|
@@ -540,12 +587,18 @@
|
|
|
540
587
|
}
|
|
541
588
|
|
|
542
589
|
function updateDot(doc, state) {
|
|
590
|
+
var pill = doc.getElementById('rcf-conn-pill');
|
|
591
|
+
if (pill) {
|
|
592
|
+
pill.className = 'live ' + state;
|
|
593
|
+
pill.title = pillTitleFor(state);
|
|
594
|
+
var label = pill.querySelector('.label');
|
|
595
|
+
if (label) label.textContent = pillLabelFor(state);
|
|
596
|
+
return;
|
|
597
|
+
}
|
|
543
598
|
var dot = doc.getElementById('rcf-conn-dot');
|
|
544
599
|
if (!dot) return;
|
|
545
600
|
dot.className = state;
|
|
546
|
-
|
|
547
|
-
else if (state === 'reconnecting') dot.title = 'Reconnecting to view server...';
|
|
548
|
-
else dot.title = 'Disconnected from view server';
|
|
601
|
+
dot.title = pillTitleFor(state);
|
|
549
602
|
}
|
|
550
603
|
|
|
551
604
|
// ---- module exports (for node:vm tests) --------------------------------
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
// Viewer UI refresh PR 3 (decision 3, decision 14): compute the set of
|
|
2
|
+
// REQ ids that still "need work" from the readiness result. The source
|
|
3
|
+
// is the readiness result the server already carries on `model.readiness`
|
|
4
|
+
// (built once per rewalk by `computeReadiness`), so we never re-run gates
|
|
5
|
+
// in the client and never recompute them from the tree.
|
|
6
|
+
//
|
|
7
|
+
// The design doc section 8 names the three PO-persona checks that call
|
|
8
|
+
// out specific REQ ids: `skeleton:reqIntent` (requirement needs a plain
|
|
9
|
+
// description), `stories:reqHasUs` (who uses this requirement, what do
|
|
10
|
+
// they do), and `skeleton:resolvedBy` (brief kinds that could become a
|
|
11
|
+
// REQ). The first two name REQ ids directly on the blocker's `ids`
|
|
12
|
+
// array. `skeleton:resolvedBy` names brief statement ids (SK-x), not
|
|
13
|
+
// REQ ids, so it is informational for the Requirements tab and only
|
|
14
|
+
// gets picked up when the suggestion names a REQ. Any REQ id that ends
|
|
15
|
+
// up in any of the above blocker lists is in the Needs-work set.
|
|
16
|
+
//
|
|
17
|
+
// Decision 14: Requirements is the engineer view at all times. The
|
|
18
|
+
// Needs-work chip uses engineer-plain language (no PO phrasebook text);
|
|
19
|
+
// the data behind it is still the PO blockers so a product-owner and
|
|
20
|
+
// an engineer looking at the same tree see the same "still needs work"
|
|
21
|
+
// requirements.
|
|
22
|
+
|
|
23
|
+
const NEEDS_WORK_CHECKS = new Set([
|
|
24
|
+
'skeleton:reqIntent',
|
|
25
|
+
'stories:reqHasUs',
|
|
26
|
+
'skeleton:resolvedBy',
|
|
27
|
+
]);
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* @param {import('../query/readiness.js').ReadinessResult | null | undefined} readiness
|
|
31
|
+
* @returns {Set<string>} REQ ids that still need work
|
|
32
|
+
*/
|
|
33
|
+
export function computeReqNeedsWorkIds(readiness) {
|
|
34
|
+
/** @type {Set<string>} */
|
|
35
|
+
const out = new Set();
|
|
36
|
+
if (!readiness) return out;
|
|
37
|
+
const level = readiness.levels?.intentComplete;
|
|
38
|
+
if (!level || !Array.isArray(level.blockedBy)) return out;
|
|
39
|
+
for (const blocker of level.blockedBy) {
|
|
40
|
+
if (!blocker || !NEEDS_WORK_CHECKS.has(blocker.check)) continue;
|
|
41
|
+
const ids = Array.isArray(blocker.ids) ? blocker.ids : [];
|
|
42
|
+
for (const id of ids) {
|
|
43
|
+
if (typeof id === 'string' && id.startsWith('REQ-')) out.add(id);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return out;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Return a short, engineer-plain reason for a REQ being in the needs-
|
|
51
|
+
* work set. Decision 14 keeps PO phrasebook text out of the Requirements
|
|
52
|
+
* tab, so we map the check name to a terse engineer-facing label rather
|
|
53
|
+
* than reproducing CHECK_QUESTION. Returns null when the REQ is not in
|
|
54
|
+
* any needs-work check.
|
|
55
|
+
*
|
|
56
|
+
* @param {import('../query/readiness.js').ReadinessResult | null | undefined} readiness
|
|
57
|
+
* @param {string} reqId
|
|
58
|
+
* @returns {string | null}
|
|
59
|
+
*/
|
|
60
|
+
export function needsWorkReasonFor(readiness, reqId) {
|
|
61
|
+
if (!readiness) return null;
|
|
62
|
+
const level = readiness.levels?.intentComplete;
|
|
63
|
+
if (!level || !Array.isArray(level.blockedBy)) return null;
|
|
64
|
+
const reasons = [];
|
|
65
|
+
for (const blocker of level.blockedBy) {
|
|
66
|
+
if (!blocker || !NEEDS_WORK_CHECKS.has(blocker.check)) continue;
|
|
67
|
+
const ids = Array.isArray(blocker.ids) ? blocker.ids : [];
|
|
68
|
+
if (!ids.includes(reqId)) continue;
|
|
69
|
+
reasons.push(REASON_BY_CHECK[blocker.check] ?? blocker.check);
|
|
70
|
+
}
|
|
71
|
+
if (reasons.length === 0) return null;
|
|
72
|
+
return reasons.join('; ');
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const REASON_BY_CHECK = {
|
|
76
|
+
'skeleton:reqIntent': 'missing plain description or domain',
|
|
77
|
+
'stories:reqHasUs': 'no user story yet',
|
|
78
|
+
'skeleton:resolvedBy': 'resolved-by target still open',
|
|
79
|
+
};
|