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,176 @@
|
|
|
1
|
+
// Test-pointer working-tree resolution (w-2026-07-28-005).
|
|
2
|
+
//
|
|
3
|
+
// This is the test-axis twin of `cn-resolve.js`. Every Test Case declares a
|
|
4
|
+
// `testPointer` (`filePath::testName`). This module checks each pointer
|
|
5
|
+
// against the CHECKED-OUT WORKING TREE: the file must exist AND a
|
|
6
|
+
// declaration-anchor regex must find the named test inside it. Coverage
|
|
7
|
+
// (`rcf coverage`) consumes the result: a TC whose pointer does not resolve
|
|
8
|
+
// is never counted as covering its AC - it surfaces as `covered-unresolved`
|
|
9
|
+
// instead. Without this, a tree full of stub TC rows reports full coverage
|
|
10
|
+
// while pointing at nothing.
|
|
11
|
+
//
|
|
12
|
+
// Determinism (same posture as cn-resolve): file existence is `fs.stat`;
|
|
13
|
+
// test presence is a fixed set of declaration-anchor regexes per language.
|
|
14
|
+
// No LLM, no test execution, no parsing beyond regex. Fully reproducible
|
|
15
|
+
// given a working tree.
|
|
16
|
+
//
|
|
17
|
+
// Language support is an anchor TABLE, keyed by file extension: adding a
|
|
18
|
+
// language is one entry (id, extensions, anchorsFor). JS/TS ships now, with
|
|
19
|
+
// anchors derived from this repo's own corpus (1022 x `test('name', ...)`
|
|
20
|
+
// plus template-literal names) widened to the common `test` / `it` /
|
|
21
|
+
// `describe` declaration forms and their `.only` / `.skip` / `.each`
|
|
22
|
+
// modifier chains, across all three quote styles (', ", `).
|
|
23
|
+
//
|
|
24
|
+
// HONEST LIMITATION (inherited from cn-resolve, same register as
|
|
25
|
+
// docs/code-nodes.md): the anchor check proves a test DECLARATION with that
|
|
26
|
+
// name exists - not that the test still asserts what the AC says. A renamed
|
|
27
|
+
// test is caught; a gutted-but-same-named test is NOT caught (semantic drift
|
|
28
|
+
// is out of reach for a deterministic check). A test name built from a
|
|
29
|
+
// template-literal interpolation cannot be anchored statically and reports
|
|
30
|
+
// unresolved unless the pointer carries the literal source text.
|
|
31
|
+
|
|
32
|
+
import { readFile, stat } from 'node:fs/promises';
|
|
33
|
+
import { extname, join } from 'node:path';
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Split a test pointer into its file part and test-name part.
|
|
37
|
+
* The separator is the FIRST `::`; test names may themselves contain `::`.
|
|
38
|
+
* @param {string} pointer e.g. "test/store/loader.test.js::loads a document"
|
|
39
|
+
* @returns {{ file: string, testName: string | null }}
|
|
40
|
+
*/
|
|
41
|
+
export function splitTestPointer(pointer) {
|
|
42
|
+
const sep = pointer.indexOf('::');
|
|
43
|
+
if (sep < 0) return { file: pointer, testName: null };
|
|
44
|
+
const testName = pointer.slice(sep + 2);
|
|
45
|
+
return { file: pointer.slice(0, sep), testName: testName.length > 0 ? testName : null };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function escapeRegExp(s) {
|
|
49
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// The anchor table. One entry per supported language; adding a language is
|
|
53
|
+
// adding an entry (nothing else changes). `anchorsFor` returns the
|
|
54
|
+
// deterministic declaration-anchor matchers for a test name; a test is
|
|
55
|
+
// considered PRESENT if any anchor matches the file text.
|
|
56
|
+
const ANCHOR_LANGUAGES = [
|
|
57
|
+
{
|
|
58
|
+
id: 'javascript',
|
|
59
|
+
extensions: ['.js', '.mjs', '.cjs', '.jsx', '.ts', '.mts', '.cts', '.tsx'],
|
|
60
|
+
anchorsFor(testName) {
|
|
61
|
+
const n = escapeRegExp(testName);
|
|
62
|
+
// `test('name'` / `it("name"` / `describe(`name`` plus modifier
|
|
63
|
+
// chains (`test.skip(`, `it.only(`, `test.each(...)(` is NOT chased -
|
|
64
|
+
// parameterised names are dynamic and out of deterministic reach).
|
|
65
|
+
const head = String.raw`\b(?:test|it|describe)(?:\.\w+)*\s*\(\s*`;
|
|
66
|
+
return [
|
|
67
|
+
new RegExp(`${head}'${n}'`),
|
|
68
|
+
new RegExp(`${head}"${n}"`),
|
|
69
|
+
new RegExp(`${head}\`${n}\``),
|
|
70
|
+
];
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
];
|
|
74
|
+
|
|
75
|
+
function languageForFile(file) {
|
|
76
|
+
const ext = extname(file).toLowerCase();
|
|
77
|
+
return ANCHOR_LANGUAGES.find((lang) => lang.extensions.includes(ext)) ?? null;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* @typedef {object} TestPointerResolution
|
|
82
|
+
* @property {boolean} resolved
|
|
83
|
+
* @property {'ok'|'missing-pointer'|'malformed-pointer'|'unsupported-file-type'|'file-missing'|'test-missing'} reason
|
|
84
|
+
* @property {string|null} testPointer - the raw pointer (null when absent)
|
|
85
|
+
* @property {string} tsId
|
|
86
|
+
* @property {string} tcId
|
|
87
|
+
*/
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Key for the per-TC resolution map. Exported so consumers (coverage) and
|
|
91
|
+
* this module agree on one spelling.
|
|
92
|
+
* @param {string} tsId
|
|
93
|
+
* @param {string} tcId
|
|
94
|
+
* @returns {string}
|
|
95
|
+
*/
|
|
96
|
+
export function testCaseKey(tsId, tcId) {
|
|
97
|
+
return `${tsId}::${tcId}`;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Resolve every Test Case's `testPointer` in the tree against the working
|
|
102
|
+
* tree. Returns a Map keyed by `testCaseKey(tsId, tcId)`; every TC in the
|
|
103
|
+
* tree gets an entry, resolved or not. A per-file read cache avoids
|
|
104
|
+
* re-reading a file that multiple TCs point into (same posture as
|
|
105
|
+
* cn-resolve's cache).
|
|
106
|
+
*
|
|
107
|
+
* @param {object} args
|
|
108
|
+
* @param {string} args.projectRoot - absolute path to project root
|
|
109
|
+
* @param {object} args.tree - walker TreeModel (carries tree.testSuites)
|
|
110
|
+
* @returns {Promise<Map<string, TestPointerResolution>>}
|
|
111
|
+
*/
|
|
112
|
+
export async function resolveTestPointers({ projectRoot, tree }) {
|
|
113
|
+
/** @type {Map<string, TestPointerResolution>} */
|
|
114
|
+
const results = new Map();
|
|
115
|
+
const fileCache = new Map();
|
|
116
|
+
|
|
117
|
+
const readFileCached = async (absFile) => {
|
|
118
|
+
if (fileCache.has(absFile)) return fileCache.get(absFile);
|
|
119
|
+
let text = null;
|
|
120
|
+
try {
|
|
121
|
+
text = await readFile(absFile, 'utf8');
|
|
122
|
+
} catch {
|
|
123
|
+
text = null;
|
|
124
|
+
}
|
|
125
|
+
fileCache.set(absFile, text);
|
|
126
|
+
return text;
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
for (const ts of tree.testSuites ?? []) {
|
|
130
|
+
for (const tc of ts.testCases ?? []) {
|
|
131
|
+
if (!tc?.id) continue;
|
|
132
|
+
const key = testCaseKey(ts.id, tc.id);
|
|
133
|
+
const record = (resolved, reason, pointer = null) => {
|
|
134
|
+
results.set(key, { resolved, reason, testPointer: pointer, tsId: ts.id, tcId: tc.id });
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
const pointer = tc.testPointer;
|
|
138
|
+
if (typeof pointer !== 'string' || pointer.length === 0) {
|
|
139
|
+
record(false, 'missing-pointer');
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
const { file, testName } = splitTestPointer(pointer);
|
|
143
|
+
if (!testName || file.length === 0) {
|
|
144
|
+
record(false, 'malformed-pointer', pointer);
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
const language = languageForFile(file);
|
|
148
|
+
if (!language) {
|
|
149
|
+
record(false, 'unsupported-file-type', pointer);
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const absFile = join(projectRoot, file);
|
|
154
|
+
let fileOk = false;
|
|
155
|
+
try {
|
|
156
|
+
const st = await stat(absFile);
|
|
157
|
+
fileOk = st.isFile();
|
|
158
|
+
} catch {
|
|
159
|
+
fileOk = false;
|
|
160
|
+
}
|
|
161
|
+
if (!fileOk) {
|
|
162
|
+
record(false, 'file-missing', pointer);
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const text = await readFileCached(absFile);
|
|
167
|
+
const present = text != null && language.anchorsFor(testName).some((re) => re.test(text));
|
|
168
|
+
if (!present) {
|
|
169
|
+
record(false, 'test-missing', pointer);
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
172
|
+
record(true, 'ok', pointer);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
return results;
|
|
176
|
+
}
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
// Schema validator. Registers the @stravica-ai/rcf-schemas@0.2.0 bundle once
|
|
2
|
+
// at start-up and exposes a single `validateDocument` entry point. Returns
|
|
3
|
+
// `null` on success or a structured `validation` error on failure.
|
|
4
|
+
//
|
|
5
|
+
// Validation runs on load (FBS-001 / AC-701-3): the published bundle is the
|
|
6
|
+
// contract, not a local copy - with ONE documented strictness overlay
|
|
7
|
+
// (`testPointer` required on every Test Case; see the overlay block below).
|
|
8
|
+
// Referential-integrity checking lives in the walker (D8); this module is
|
|
9
|
+
// schema-shape-only.
|
|
10
|
+
|
|
11
|
+
import Ajv2020 from 'ajv/dist/2020.js';
|
|
12
|
+
import addFormats from 'ajv-formats';
|
|
13
|
+
|
|
14
|
+
import commonSchema from '@stravica-ai/rcf-schemas/schemas/common.schema.json' with { type: 'json' };
|
|
15
|
+
import manifestSchema from '@stravica-ai/rcf-schemas/schemas/manifest.schema.json' with { type: 'json' };
|
|
16
|
+
import prdSchema from '@stravica-ai/rcf-schemas/schemas/prd.schema.json' with { type: 'json' };
|
|
17
|
+
import reqSchema from '@stravica-ai/rcf-schemas/schemas/req.schema.json' with { type: 'json' };
|
|
18
|
+
import userStorySchema from '@stravica-ai/rcf-schemas/schemas/user-story.schema.json' with { type: 'json' };
|
|
19
|
+
import tadSchema from '@stravica-ai/rcf-schemas/schemas/tad.schema.json' with { type: 'json' };
|
|
20
|
+
import tacSchema from '@stravica-ai/rcf-schemas/schemas/tac.schema.json' with { type: 'json' };
|
|
21
|
+
import adrSchema from '@stravica-ai/rcf-schemas/schemas/adr.schema.json' with { type: 'json' };
|
|
22
|
+
import buildSequenceSchema from '@stravica-ai/rcf-schemas/schemas/build-sequence.schema.json' with { type: 'json' };
|
|
23
|
+
import fbsSchema from '@stravica-ai/rcf-schemas/schemas/fbs.schema.json' with { type: 'json' };
|
|
24
|
+
import testSuiteSchema from '@stravica-ai/rcf-schemas/schemas/test-suite.schema.json' with { type: 'json' };
|
|
25
|
+
// Phase 10 (X2 CodeNode bridge): 11th document kind, delivered in
|
|
26
|
+
// @stravica-ai/rcf-schemas@0.3.0.
|
|
27
|
+
import cnSchema from '@stravica-ai/rcf-schemas/schemas/cn.schema.json' with { type: 'json' };
|
|
28
|
+
|
|
29
|
+
import { rcfError } from '../errors/index.js';
|
|
30
|
+
|
|
31
|
+
// w-2026-07-28-005: strictness overlay - `testPointer` is REQUIRED on every
|
|
32
|
+
// Test Case. The published bundle (0.3.1) still declares it optional; an
|
|
33
|
+
// optional pointer is how 76 stub TC rows can claim coverage while pointing
|
|
34
|
+
// at nothing, so Build Lite refuses the shape at the schema layer. This is
|
|
35
|
+
// the ONLY local divergence from the published bundle, it is a pure
|
|
36
|
+
// tightening (every document valid here is valid upstream), and it is
|
|
37
|
+
// registered under the bundle's own $id so every validation path (walker
|
|
38
|
+
// load, post-write gate, write verbs) inherits it. Durable home: making
|
|
39
|
+
// testPointer required in @stravica-ai/rcf-schemas itself; drop this block
|
|
40
|
+
// when that ships.
|
|
41
|
+
const testSuiteSchemaStrict = structuredClone(testSuiteSchema);
|
|
42
|
+
{
|
|
43
|
+
const testCase = testSuiteSchemaStrict.$defs.testCase;
|
|
44
|
+
testCase.required = [...testCase.required, 'testPointer'];
|
|
45
|
+
testCase.properties.testPointer = {
|
|
46
|
+
...testCase.properties.testPointer,
|
|
47
|
+
minLength: 1,
|
|
48
|
+
description: 'Required pointer to the executable test, format: filePath::testName.',
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* @typedef {('manifest'|'prd'|'req'|'userStory'|'tad'|'tac'|'adr'|'buildSequence'|'fbs'|'testSuite'|'codeNode')} DocKind
|
|
54
|
+
*/
|
|
55
|
+
|
|
56
|
+
const SCHEMAS = {
|
|
57
|
+
manifest: manifestSchema,
|
|
58
|
+
prd: prdSchema,
|
|
59
|
+
req: reqSchema,
|
|
60
|
+
userStory: userStorySchema,
|
|
61
|
+
tad: tadSchema,
|
|
62
|
+
tac: tacSchema,
|
|
63
|
+
adr: adrSchema,
|
|
64
|
+
buildSequence: buildSequenceSchema,
|
|
65
|
+
fbs: fbsSchema,
|
|
66
|
+
// w-2026-07-28-005 overlay: the tightened copy, not the raw import.
|
|
67
|
+
testSuite: testSuiteSchemaStrict,
|
|
68
|
+
// Phase 10: Code Node.
|
|
69
|
+
codeNode: cnSchema,
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const ID_FIELD = {
|
|
73
|
+
manifest: null,
|
|
74
|
+
prd: 'prdId',
|
|
75
|
+
req: 'reqId',
|
|
76
|
+
userStory: 'usId',
|
|
77
|
+
tad: 'tadId',
|
|
78
|
+
tac: 'tacId',
|
|
79
|
+
adr: 'adrId',
|
|
80
|
+
buildSequence: 'bsId',
|
|
81
|
+
fbs: 'fbsId',
|
|
82
|
+
// Test Suite uses the plain `id` field (see @stravica-ai/rcf-schemas@0.2.0
|
|
83
|
+
// test-suite.schema.json). No `tsId` field.
|
|
84
|
+
testSuite: 'id',
|
|
85
|
+
// Phase 10: Code Node.
|
|
86
|
+
codeNode: 'cnId',
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
let cachedAjv = null;
|
|
90
|
+
|
|
91
|
+
function getAjv() {
|
|
92
|
+
if (cachedAjv) return cachedAjv;
|
|
93
|
+
const ajv = new Ajv2020({ allErrors: true, strict: false });
|
|
94
|
+
addFormats(ajv);
|
|
95
|
+
ajv.addSchema(commonSchema);
|
|
96
|
+
for (const schema of Object.values(SCHEMAS)) {
|
|
97
|
+
ajv.addSchema(schema);
|
|
98
|
+
}
|
|
99
|
+
cachedAjv = ajv;
|
|
100
|
+
return ajv;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Returns the set of document kinds the validator knows about.
|
|
105
|
+
* @returns {DocKind[]}
|
|
106
|
+
*/
|
|
107
|
+
export function knownKinds() {
|
|
108
|
+
return /** @type {DocKind[]} */ (Object.keys(SCHEMAS));
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Returns the property name carrying the document's id, or null for manifest.
|
|
113
|
+
*
|
|
114
|
+
* @param {DocKind} kind
|
|
115
|
+
* @returns {string|null}
|
|
116
|
+
*/
|
|
117
|
+
export function idFieldFor(kind) {
|
|
118
|
+
if (!(kind in ID_FIELD)) {
|
|
119
|
+
throw new TypeError(`Unknown kind: ${kind}`);
|
|
120
|
+
}
|
|
121
|
+
return ID_FIELD[kind];
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Extract the document id, or null for manifest.
|
|
126
|
+
*
|
|
127
|
+
* @param {object} doc
|
|
128
|
+
* @param {DocKind} kind
|
|
129
|
+
* @returns {string|null}
|
|
130
|
+
*/
|
|
131
|
+
export function documentIdOf(doc, kind) {
|
|
132
|
+
const field = idFieldFor(kind);
|
|
133
|
+
if (!field) return null;
|
|
134
|
+
const value = doc?.[field];
|
|
135
|
+
return typeof value === 'string' ? value : null;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Ajv's default `enum` message is "must be equal to one of the allowed
|
|
140
|
+
* values" and never says which. The allowed set is in `params`, which
|
|
141
|
+
* we were discarding - leaving the only way to find a valid status /
|
|
142
|
+
* priority / testLevel / category / generationStrategy be reading the
|
|
143
|
+
* schema source. Append it when Ajv provides it.
|
|
144
|
+
*
|
|
145
|
+
* @param {object} e - an Ajv error object
|
|
146
|
+
* @returns {string} suffix to append to the error message ('' when none)
|
|
147
|
+
*/
|
|
148
|
+
function detailOf(e) {
|
|
149
|
+
const allowed = e?.params?.allowedValues;
|
|
150
|
+
if (!Array.isArray(allowed) || allowed.length === 0) return '';
|
|
151
|
+
return ` (allowed: ${allowed.join(', ')})`;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Validate a document against its schema.
|
|
156
|
+
*
|
|
157
|
+
* @param {object} args
|
|
158
|
+
* @param {object} args.doc - parsed JSON document
|
|
159
|
+
* @param {DocKind} args.kind
|
|
160
|
+
* @param {string} [args.filePath] - optional, recorded on validation errors
|
|
161
|
+
* @returns {import('../errors/index.js').RcfError | null}
|
|
162
|
+
*/
|
|
163
|
+
export function validateDocument({ doc, kind, filePath }) {
|
|
164
|
+
const schema = SCHEMAS[kind];
|
|
165
|
+
if (!schema) {
|
|
166
|
+
return rcfError({
|
|
167
|
+
kind: 'validation',
|
|
168
|
+
message: `Unknown document kind: ${kind}`,
|
|
169
|
+
filePath,
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
const ajv = getAjv();
|
|
173
|
+
const validate = ajv.getSchema(schema.$id) ?? ajv.compile(schema);
|
|
174
|
+
if (validate(doc)) return null;
|
|
175
|
+
const ajvErrors = validate.errors ?? [];
|
|
176
|
+
const first = ajvErrors[0] ?? {};
|
|
177
|
+
const field = first.instancePath?.replace(/^\//, '').replace(/\//g, '.') || undefined;
|
|
178
|
+
const rule = first.keyword || undefined;
|
|
179
|
+
const message = ajvErrors
|
|
180
|
+
.map((e) => `${e.instancePath || '/'} ${e.message ?? 'invalid'}${detailOf(e)}`)
|
|
181
|
+
.join('; ');
|
|
182
|
+
const documentId = documentIdOf(doc, kind) ?? undefined;
|
|
183
|
+
return rcfError({
|
|
184
|
+
kind: 'validation',
|
|
185
|
+
message,
|
|
186
|
+
documentId,
|
|
187
|
+
filePath,
|
|
188
|
+
field,
|
|
189
|
+
rule,
|
|
190
|
+
});
|
|
191
|
+
}
|