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,190 @@
|
|
|
1
|
+
// Chain reader (spec §1, §5.2). Verify's ONLY structural input is the RCF
|
|
2
|
+
// chain — the acceptance contract. It reads the acceptance criteria off the
|
|
3
|
+
// chain through the SAME store code build-lite uses (core's walkTree), never
|
|
4
|
+
// the source tree, the test suite, or the builder's self-report (§9
|
|
5
|
+
// independence guarantee 2). It imports core's READ path only — never
|
|
6
|
+
// writer.js / init.js (§7.2 boundary).
|
|
7
|
+
//
|
|
8
|
+
// 0.7.0 additions (verification-integrity-cluster-spec §5.2, ui-design-gate
|
|
9
|
+
// §8.7): the flattened AC read-out is extended with two derived fields —
|
|
10
|
+
// `serviceAttestations` (aggregated from every FBS's `dependsOnServices[]`
|
|
11
|
+
// whose `acIds` include this AC) and `fbsUiBearing` (true when any of the
|
|
12
|
+
// FBSes bound to this AC has `uiBearing: true`). Both derivations live
|
|
13
|
+
// HERE, in verify's chain reader, per the tonight's clarification (Track A
|
|
14
|
+
// changelog 2026-07-31, Track B §18 N2 fold): core's walker surfaces raw
|
|
15
|
+
// document fields only, verify does the AC-level aggregation. Core 0.3.0
|
|
16
|
+
// carries a walker-guard test that asserts no such derivation leaks into
|
|
17
|
+
// core (`packages/rcf-lite/test/core/store/walker-0-7-0-fields.test.js:580`).
|
|
18
|
+
//
|
|
19
|
+
// The manifest fields verify's report and verdict pipelines need
|
|
20
|
+
// (`uiBaseline`, `browserVerification[]`) are read verbatim off `tree.manifest`
|
|
21
|
+
// and surfaced on the read result — verify's downstream code consumes them
|
|
22
|
+
// through the chain reader, not by re-opening the manifest.
|
|
23
|
+
|
|
24
|
+
import { access } from 'node:fs/promises';
|
|
25
|
+
import { dirname, join, resolve } from 'node:path';
|
|
26
|
+
|
|
27
|
+
import { rcfError } from '#core/errors';
|
|
28
|
+
import { walkTree } from '#core/store';
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Resolve a --repo path-or-ref to the project root that holds rcf/manifest.json.
|
|
32
|
+
* v1 treats --repo as a filesystem path (path refs to remote sources are
|
|
33
|
+
* deferred). Walks the given path and its ancestors, like build's finder.
|
|
34
|
+
*
|
|
35
|
+
* @param {string} startPath
|
|
36
|
+
* @returns {Promise<string | null>}
|
|
37
|
+
*/
|
|
38
|
+
export async function findProjectRoot(startPath) {
|
|
39
|
+
let dir = resolve(startPath);
|
|
40
|
+
// Walk up until we find rcf/manifest.json or hit the filesystem root.
|
|
41
|
+
// Bounded loop guard: dirname stabilises at the root.
|
|
42
|
+
for (let i = 0; i < 64; i += 1) {
|
|
43
|
+
try {
|
|
44
|
+
await access(join(dir, 'rcf', 'manifest.json'));
|
|
45
|
+
return dir;
|
|
46
|
+
} catch {
|
|
47
|
+
// not here — climb
|
|
48
|
+
}
|
|
49
|
+
const parent = dirname(dir);
|
|
50
|
+
if (parent === dir) break;
|
|
51
|
+
dir = parent;
|
|
52
|
+
}
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Aggregate FBS `dependsOnServices[]` into per-AC serviceAttestations. For
|
|
58
|
+
* each AC that an FBS binds (via `fbs.acIds`), every entry in the FBS's
|
|
59
|
+
* `dependsOnServices` whose `acIds` mention the AC produces a
|
|
60
|
+
* `{serviceId, attestationMode}` on that AC's attestation list. An AC bound
|
|
61
|
+
* by several FBSes accumulates every service dependency across all of them,
|
|
62
|
+
* so an `AC-101-2` covered by FBS-011 (`resend` attested `mocked`) and
|
|
63
|
+
* FBS-014 (`stripe` attested `live`) surfaces both attestations on that
|
|
64
|
+
* AC. Order is deterministic: FBSes visited in `fbsItems` order, services
|
|
65
|
+
* in each FBS's declared order.
|
|
66
|
+
*
|
|
67
|
+
* @param {object[]} fbsItems
|
|
68
|
+
* @param {string} acId
|
|
69
|
+
* @returns {Array<{serviceId: string, attestationMode: string}>}
|
|
70
|
+
*/
|
|
71
|
+
function serviceAttestationsFor(fbsItems, acId) {
|
|
72
|
+
const out = [];
|
|
73
|
+
for (const fbs of fbsItems ?? []) {
|
|
74
|
+
if (!Array.isArray(fbs.acIds) || !fbs.acIds.includes(acId)) continue;
|
|
75
|
+
for (const dep of fbs.dependsOnServices ?? []) {
|
|
76
|
+
if (!dep || typeof dep.id !== 'string' || typeof dep.attestationMode !== 'string') continue;
|
|
77
|
+
const acIds = Array.isArray(dep.acIds) ? dep.acIds : [];
|
|
78
|
+
if (acIds.length === 0 || acIds.includes(acId)) {
|
|
79
|
+
out.push({ serviceId: dep.id, attestationMode: dep.attestationMode });
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return out;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* True when any FBS bound to `acId` has `uiBearing: true`. Verify uses this
|
|
88
|
+
* flag to gate its UI-baseline verdict emission (§8.7) — a UI-bearing AC is
|
|
89
|
+
* exposed to `UI-BASELINE-UNMET` / `BROWSER-VERIFICATION-MISSING`, while a
|
|
90
|
+
* non-UI AC is not.
|
|
91
|
+
*
|
|
92
|
+
* @param {object[]} fbsItems
|
|
93
|
+
* @param {string} acId
|
|
94
|
+
* @returns {boolean}
|
|
95
|
+
*/
|
|
96
|
+
function fbsUiBearingFor(fbsItems, acId) {
|
|
97
|
+
for (const fbs of fbsItems ?? []) {
|
|
98
|
+
if (!Array.isArray(fbs.acIds) || !fbs.acIds.includes(acId)) continue;
|
|
99
|
+
if (fbs.uiBearing === true) return true;
|
|
100
|
+
}
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* The FBS ids bound to `acId` (via `fbs.acIds`), preserving `fbsItems`
|
|
106
|
+
* order. Verify uses this to resolve the browserVerification entries for
|
|
107
|
+
* an AC when deciding UI-BASELINE-UNMET vs BROWSER-VERIFICATION-MISSING.
|
|
108
|
+
*
|
|
109
|
+
* @param {object[]} fbsItems
|
|
110
|
+
* @param {string} acId
|
|
111
|
+
* @returns {string[]}
|
|
112
|
+
*/
|
|
113
|
+
function fbsIdsFor(fbsItems, acId) {
|
|
114
|
+
const out = [];
|
|
115
|
+
for (const fbs of fbsItems ?? []) {
|
|
116
|
+
if (!Array.isArray(fbs.acIds) || !fbs.acIds.includes(acId)) continue;
|
|
117
|
+
if (typeof fbs.fbsId === 'string') out.push(fbs.fbsId);
|
|
118
|
+
}
|
|
119
|
+
return out;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Read the acceptance contract from the chain. Returns the flattened list of
|
|
124
|
+
* acceptance criteria (each mapped back to its user story + requirement — the
|
|
125
|
+
* chain-node addressing the report carries), plus the resolved chainRef and
|
|
126
|
+
* the manifest fields verify's downstream stages consume.
|
|
127
|
+
*
|
|
128
|
+
* @param {object} opts
|
|
129
|
+
* @param {string} opts.repo - path-or-ref to the RCF chain source
|
|
130
|
+
* @param {string} [opts.chainRef] - which PRD/chain; default = the repo's PRD
|
|
131
|
+
* @returns {Promise<{ acs: Array<object>, chainRef: string, projectRoot: string, manifest: object } | import('#core/errors').RcfError>}
|
|
132
|
+
*/
|
|
133
|
+
export async function readChain({ repo, chainRef } = {}) {
|
|
134
|
+
if (typeof repo !== 'string' || repo.length === 0) {
|
|
135
|
+
return rcfError({ kind: 'usage', message: '--repo (the RCF chain source) is required', field: 'repo' });
|
|
136
|
+
}
|
|
137
|
+
const projectRoot = await findProjectRoot(repo);
|
|
138
|
+
if (!projectRoot) {
|
|
139
|
+
return rcfError({
|
|
140
|
+
kind: 'missingFile',
|
|
141
|
+
message: `no RCF chain found at or above "${repo}" (no rcf/manifest.json). Verify needs an existing RCF chain as its acceptance contract.`,
|
|
142
|
+
filePath: repo,
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const { tree, errors } = await walkTree({ projectRoot });
|
|
147
|
+
if (errors.length > 0) {
|
|
148
|
+
// A chain that does not load is not verifiable — surface the first
|
|
149
|
+
// structural error (errors-as-data; the CLI maps to a chain-load exit).
|
|
150
|
+
return rcfError({
|
|
151
|
+
kind: 'parseFailure',
|
|
152
|
+
message: `RCF chain failed to load: ${errors[0].message}`,
|
|
153
|
+
filePath: errors[0].filePath,
|
|
154
|
+
documentId: errors[0].documentId,
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const resolvedRef = chainRef ?? tree.prd?.prdId ?? 'PRD-UNKNOWN';
|
|
159
|
+
const fbsItems = tree.fbsItems ?? [];
|
|
160
|
+
const acs = [];
|
|
161
|
+
for (const us of tree.userStories ?? []) {
|
|
162
|
+
for (const ac of us.acceptanceCriteria ?? []) {
|
|
163
|
+
acs.push({
|
|
164
|
+
acId: ac.id,
|
|
165
|
+
usId: us.usId,
|
|
166
|
+
reqId: us.reqId ?? null,
|
|
167
|
+
title: us.title ?? null,
|
|
168
|
+
description: ac.description ?? '',
|
|
169
|
+
given: ac.given ?? '',
|
|
170
|
+
when: ac.when ?? '',
|
|
171
|
+
then: ac.then ?? '',
|
|
172
|
+
testable: ac.testable !== false,
|
|
173
|
+
// 0.7.0 derived fields — verify does the aggregation here per Track A
|
|
174
|
+
// changelog 2026-07-31 and Track B §18 N2 fold; core does not.
|
|
175
|
+
serviceAttestations: serviceAttestationsFor(fbsItems, ac.id),
|
|
176
|
+
fbsUiBearing: fbsUiBearingFor(fbsItems, ac.id),
|
|
177
|
+
fbsIds: fbsIdsFor(fbsItems, ac.id),
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Manifest carries the ruled UI baseline and the browser-verification
|
|
183
|
+
// record ledger; verify's report + verdict layers read them from here so
|
|
184
|
+
// downstream never re-opens the manifest. Missing manifest is not a
|
|
185
|
+
// failure at this layer (the walker would have surfaced a load error) —
|
|
186
|
+
// an older chain simply has no 0.7.0 fields, and `manifest` is `{}`.
|
|
187
|
+
const manifest = tree.manifest ?? {};
|
|
188
|
+
|
|
189
|
+
return { acs, chainRef: resolvedRef, projectRoot, manifest };
|
|
190
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// `rcf-verify cleanup` (spec §3, §6 cleanup contract). Tear down provisioned
|
|
2
|
+
// accounts / sandboxes / data recorded in the --provision file. On a
|
|
3
|
+
// persistent target this stops runs accreting `zzverify-` cruft. Reports what
|
|
4
|
+
// it removed. The concrete teardown route is app-specific (injected).
|
|
5
|
+
|
|
6
|
+
import { parseArgs } from 'node:util';
|
|
7
|
+
|
|
8
|
+
import { formatError, isRcfError } from '#core/errors';
|
|
9
|
+
|
|
10
|
+
import { readProvisionFile, cleanup } from '../provision/index.js';
|
|
11
|
+
|
|
12
|
+
export const HELP = `Usage: rcf-verify cleanup --provision <file> [--url <app-url>]
|
|
13
|
+
|
|
14
|
+
Tear down artefacts recorded in the --provision file (all prefixed
|
|
15
|
+
'zzverify-'). Reports what was removed and what could not be.
|
|
16
|
+
|
|
17
|
+
Required:
|
|
18
|
+
--provision <file> The provisioning record written by run/provision
|
|
19
|
+
|
|
20
|
+
Exit codes:
|
|
21
|
+
0 cleanup attempted; see the removed/blocked report
|
|
22
|
+
2 usage error
|
|
23
|
+
3 provision file could not be read
|
|
24
|
+
`;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* @param {string[]} argv
|
|
28
|
+
* @param {object} [deps]
|
|
29
|
+
* @returns {Promise<number>}
|
|
30
|
+
*/
|
|
31
|
+
export async function main(argv, deps = {}) {
|
|
32
|
+
const stdout = deps.stdout ?? process.stdout;
|
|
33
|
+
const stderr = deps.stderr ?? process.stderr;
|
|
34
|
+
|
|
35
|
+
let parsed;
|
|
36
|
+
try {
|
|
37
|
+
parsed = parseArgs({ args: argv, options: { provision: { type: 'string' }, url: { type: 'string' }, help: { type: 'boolean' } }, allowPositionals: false, strict: true });
|
|
38
|
+
} catch (err) {
|
|
39
|
+
stderr.write(`[error] usage ${err.message}\n`);
|
|
40
|
+
stderr.write(HELP);
|
|
41
|
+
return 2;
|
|
42
|
+
}
|
|
43
|
+
const flags = parsed.values;
|
|
44
|
+
if (flags.help) { stdout.write(HELP); return 0; }
|
|
45
|
+
if (!flags.provision) {
|
|
46
|
+
stderr.write('[error] usage cleanup requires --provision <file>\n');
|
|
47
|
+
stderr.write(HELP);
|
|
48
|
+
return 2;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const record = await (deps.readProvisionFile ?? readProvisionFile)(flags.provision);
|
|
52
|
+
if (isRcfError(record)) {
|
|
53
|
+
stderr.write(`${formatError(record, { verbose: true })}\n`);
|
|
54
|
+
return 3;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const provisioned = (record.credentials ?? []).map((c) => ({ kind: 'authAccount', ref: c.ref }));
|
|
58
|
+
const result = await cleanup({ provisioned, teardown: deps.teardown });
|
|
59
|
+
stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
|
60
|
+
return 0;
|
|
61
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// Top-level and per-subcommand help (mirrors build's cli/help.js). British
|
|
2
|
+
// English, ASCII hyphens only.
|
|
3
|
+
|
|
4
|
+
import { HELP as RUN_HELP } from './run.js';
|
|
5
|
+
import { HELP as REPORT_HELP } from './report.js';
|
|
6
|
+
import { HELP as PROVISION_HELP } from './provision.js';
|
|
7
|
+
import { HELP as CLEANUP_HELP } from './cleanup.js';
|
|
8
|
+
import { HELP as MCP_HELP } from './mcp.js';
|
|
9
|
+
|
|
10
|
+
export const TOP_LEVEL_HELP = `Usage: rcf-verify <command> [options]
|
|
11
|
+
|
|
12
|
+
A fresh-context adversarial verifier. Given an RCF chain (the acceptance
|
|
13
|
+
contract) and a running app under a declared runtime profile, it launches an
|
|
14
|
+
isolated verifier agent that walks real user journeys adversarially and emits a
|
|
15
|
+
structured verdict stamped with the runtime it ran against.
|
|
16
|
+
|
|
17
|
+
This is an independent ship-readiness signal, not a correctness guarantee.
|
|
18
|
+
|
|
19
|
+
Commands:
|
|
20
|
+
run Run adversarial verification and emit a report artifact
|
|
21
|
+
report <path> Re-render a prior report artifact
|
|
22
|
+
provision Stand up prerequisite accounts/sandboxes/data standalone
|
|
23
|
+
cleanup Tear down provisioned artefacts (all prefixed 'zzverify-')
|
|
24
|
+
mcp Serve verify over MCP (local stdio)
|
|
25
|
+
help [command] Print help for a command
|
|
26
|
+
|
|
27
|
+
Options:
|
|
28
|
+
--version Print the package version and exit
|
|
29
|
+
--help Print this help and exit
|
|
30
|
+
|
|
31
|
+
Run 'rcf-verify help <command>' for command-specific help.
|
|
32
|
+
`;
|
|
33
|
+
|
|
34
|
+
const SUB_HELP = {
|
|
35
|
+
run: RUN_HELP,
|
|
36
|
+
report: REPORT_HELP,
|
|
37
|
+
provision: PROVISION_HELP,
|
|
38
|
+
cleanup: CLEANUP_HELP,
|
|
39
|
+
mcp: MCP_HELP,
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* @param {string[]} argv
|
|
44
|
+
* @param {object} [deps]
|
|
45
|
+
* @returns {Promise<number>}
|
|
46
|
+
*/
|
|
47
|
+
export async function main(argv, deps = {}) {
|
|
48
|
+
const stdout = deps.stdout ?? process.stdout;
|
|
49
|
+
const topic = argv[0];
|
|
50
|
+
if (topic && SUB_HELP[topic]) {
|
|
51
|
+
stdout.write(SUB_HELP[topic]);
|
|
52
|
+
return 0;
|
|
53
|
+
}
|
|
54
|
+
stdout.write(TOP_LEVEL_HELP);
|
|
55
|
+
return 0;
|
|
56
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
// `rcf-verify mcp` subcommand (spec §10 MCP; mirrors build's cli/mcp.js).
|
|
2
|
+
// Wires process stdin/stdout/stderr into the core MCP protocol shell and
|
|
3
|
+
// serves the verify tool registry. stdout carries MCP messages and NOTHING
|
|
4
|
+
// else; all logging goes to stderr.
|
|
5
|
+
|
|
6
|
+
import { readFile } from 'node:fs/promises';
|
|
7
|
+
import { dirname, resolve as resolvePath } from 'node:path';
|
|
8
|
+
import { parseArgs } from 'node:util';
|
|
9
|
+
import { fileURLToPath } from 'node:url';
|
|
10
|
+
|
|
11
|
+
import { createMcpServer, serveStreams } from '#core/mcp-shell';
|
|
12
|
+
|
|
13
|
+
import { createToolRegistry } from '../mcp/tools.js';
|
|
14
|
+
|
|
15
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
16
|
+
|
|
17
|
+
export const HELP = `Usage: rcf-verify mcp [options]
|
|
18
|
+
|
|
19
|
+
Serve verify over the Model Context Protocol (local stdio). Exposes one tool,
|
|
20
|
+
rcf_verify_run, over the same in-process engine the CLI uses. stdout carries
|
|
21
|
+
protocol messages only; logging goes to stderr.
|
|
22
|
+
|
|
23
|
+
Options:
|
|
24
|
+
--verbose Per-request logging on stderr
|
|
25
|
+
--help Print this help
|
|
26
|
+
`;
|
|
27
|
+
|
|
28
|
+
const INSTRUCTIONS = 'Fresh-context adversarial verifier for RCF chains. rcf_verify_run takes an RCF '
|
|
29
|
+
+ 'chain (the acceptance contract) and a running app under a declared runtime profile, launches an '
|
|
30
|
+
+ 'isolated verifier agent that tries to DISPROVE the app against its acceptance criteria, and '
|
|
31
|
+
+ 'returns a structured verdict stamped with the runtime it ran against. Only a "deployed" profile '
|
|
32
|
+
+ '(or a declared --parity-env runtime) yields a SHIP verdict. This is an independent ship-readiness '
|
|
33
|
+
+ 'signal, not a correctness guarantee.';
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* @param {string[]} argv - argv slice after `mcp`
|
|
37
|
+
* @param {object} [deps]
|
|
38
|
+
* @returns {Promise<number>}
|
|
39
|
+
*/
|
|
40
|
+
export async function main(argv, deps = {}) {
|
|
41
|
+
const stdout = deps.stdout ?? process.stdout;
|
|
42
|
+
const stderr = deps.stderr ?? process.stderr;
|
|
43
|
+
const stdin = deps.stdin ?? process.stdin;
|
|
44
|
+
const onSignal = deps.onSignal ?? ((sig, fn) => process.on(sig, fn));
|
|
45
|
+
|
|
46
|
+
let parsed;
|
|
47
|
+
try {
|
|
48
|
+
parsed = parseArgs({ args: argv, options: { verbose: { type: 'boolean' }, help: { type: 'boolean' } }, allowPositionals: false, strict: true });
|
|
49
|
+
} catch (err) {
|
|
50
|
+
stderr.write(`[error] usage ${err.message}\n`);
|
|
51
|
+
stderr.write(HELP);
|
|
52
|
+
return 2;
|
|
53
|
+
}
|
|
54
|
+
if (parsed.values.help) {
|
|
55
|
+
stdout.write(HELP);
|
|
56
|
+
return 0;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const verbose = Boolean(parsed.values.verbose);
|
|
60
|
+
const log = {
|
|
61
|
+
info: verbose ? (line) => stderr.write(`[rcf-verify mcp] ${line}\n`) : () => {},
|
|
62
|
+
error: (line) => stderr.write(`${line}\n`),
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const tools = createToolRegistry(deps);
|
|
66
|
+
|
|
67
|
+
const server = createMcpServer({
|
|
68
|
+
serverInfo: { name: 'rcf-verify-lite', version: await readPackageVersion() },
|
|
69
|
+
instructions: INSTRUCTIONS,
|
|
70
|
+
capabilities: { tools: {} },
|
|
71
|
+
handlers: {
|
|
72
|
+
'tools/list': async () => ({ tools: tools.definitions }),
|
|
73
|
+
'tools/call': async (params) => {
|
|
74
|
+
log.info(`tools/call ${params.name}`);
|
|
75
|
+
return await tools.call(params.name, params.arguments);
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
log,
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
stderr.write('rcf-verify mcp: serving\n');
|
|
82
|
+
|
|
83
|
+
const { done, stop } = serveStreams(server, { input: stdin, output: stdout, log });
|
|
84
|
+
onSignal('SIGINT', () => { log.info('received SIGINT, shutting down'); stop(); });
|
|
85
|
+
onSignal('SIGTERM', () => { log.info('received SIGTERM, shutting down'); stop(); });
|
|
86
|
+
|
|
87
|
+
return await done;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async function readPackageVersion() {
|
|
91
|
+
try {
|
|
92
|
+
const pkgPath = resolvePath(here, '..', '..', 'package.json');
|
|
93
|
+
const pkg = JSON.parse(await readFile(pkgPath, 'utf8'));
|
|
94
|
+
return pkg.version ?? '0.0.0';
|
|
95
|
+
} catch {
|
|
96
|
+
return '0.0.0';
|
|
97
|
+
}
|
|
98
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
// `rcf-verify provision` (spec §3, §6). Stand up prerequisites standalone and
|
|
2
|
+
// write credentials to the --provision file. Derives what to provision from
|
|
3
|
+
// the chain's ACs (needs --repo). The concrete signup route is app-specific
|
|
4
|
+
// and agent-driven; where it can't be derived, this BLOCKS honestly rather
|
|
5
|
+
// than silently skipping (§6).
|
|
6
|
+
|
|
7
|
+
import { parseArgs } from 'node:util';
|
|
8
|
+
|
|
9
|
+
import { formatError, isRcfError } from '#core/errors';
|
|
10
|
+
|
|
11
|
+
import { readChain } from '../chain/index.js';
|
|
12
|
+
import { runProvisioning } from '../provision/index.js';
|
|
13
|
+
|
|
14
|
+
export const HELP = `Usage: rcf-verify provision --repo <path> --url <app-url> --provision <file>
|
|
15
|
+
|
|
16
|
+
Stand up prerequisite accounts / sandboxes / seed data for a later run.
|
|
17
|
+
Credentials are written to the --provision FILE only, never echoed.
|
|
18
|
+
|
|
19
|
+
Required:
|
|
20
|
+
--repo <path> RCF chain source (to derive what to provision)
|
|
21
|
+
--url <app-url> The running app
|
|
22
|
+
--provision <file> Where to write provisioned credentials
|
|
23
|
+
|
|
24
|
+
Exit codes:
|
|
25
|
+
0 provisioning attempted; see the report of what was provisioned/blocked
|
|
26
|
+
2 usage error
|
|
27
|
+
3 chain could not be loaded
|
|
28
|
+
`;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* @param {string[]} argv
|
|
32
|
+
* @param {object} [deps]
|
|
33
|
+
* @returns {Promise<number>}
|
|
34
|
+
*/
|
|
35
|
+
export async function main(argv, deps = {}) {
|
|
36
|
+
const stdout = deps.stdout ?? process.stdout;
|
|
37
|
+
const stderr = deps.stderr ?? process.stderr;
|
|
38
|
+
|
|
39
|
+
let parsed;
|
|
40
|
+
try {
|
|
41
|
+
parsed = parseArgs({ args: argv, options: { repo: { type: 'string' }, url: { type: 'string' }, provision: { type: 'string' }, help: { type: 'boolean' } }, allowPositionals: false, strict: true });
|
|
42
|
+
} catch (err) {
|
|
43
|
+
stderr.write(`[error] usage ${err.message}\n`);
|
|
44
|
+
stderr.write(HELP);
|
|
45
|
+
return 2;
|
|
46
|
+
}
|
|
47
|
+
const flags = parsed.values;
|
|
48
|
+
if (flags.help) { stdout.write(HELP); return 0; }
|
|
49
|
+
if (!flags.repo || !flags.url || !flags.provision) {
|
|
50
|
+
stderr.write('[error] usage provision requires --repo, --url and --provision\n');
|
|
51
|
+
stderr.write(HELP);
|
|
52
|
+
return 2;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const chain = await (deps.readChain ?? readChain)({ repo: flags.repo });
|
|
56
|
+
if (isRcfError(chain)) {
|
|
57
|
+
stderr.write(`${formatError(chain, { verbose: true })}\n`);
|
|
58
|
+
return 3;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const { provisioning } = await runProvisioning({
|
|
62
|
+
acs: chain.acs,
|
|
63
|
+
url: flags.url,
|
|
64
|
+
provisionPath: flags.provision,
|
|
65
|
+
mode: 'run',
|
|
66
|
+
signup: deps.signup,
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
stdout.write(`${JSON.stringify(provisioning, null, 2)}\n`);
|
|
70
|
+
return 0;
|
|
71
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
// `rcf-verify report <report-path>` (spec §3, parallels `rcf view`).
|
|
2
|
+
// Re-render / summarise a prior report artifact using verify's own
|
|
3
|
+
// self-contained finding-renderer (§7.2 — no core view dependency).
|
|
4
|
+
|
|
5
|
+
import { readFile } from 'node:fs/promises';
|
|
6
|
+
import { resolve as resolvePath } from 'node:path';
|
|
7
|
+
import { parseArgs } from 'node:util';
|
|
8
|
+
|
|
9
|
+
import { formatError, isRcfError } from '#core/errors';
|
|
10
|
+
|
|
11
|
+
import { parseReport } from '../report/index.js';
|
|
12
|
+
import { renderReport } from '../report/renderer.js';
|
|
13
|
+
|
|
14
|
+
export const HELP = `Usage: rcf-verify report <report-path> [--json]
|
|
15
|
+
|
|
16
|
+
Re-render a prior report artifact. Default output is the human-readable
|
|
17
|
+
summary; --json re-emits the parsed artifact as JSON.
|
|
18
|
+
|
|
19
|
+
Exit codes:
|
|
20
|
+
0 rendered
|
|
21
|
+
2 usage error (no path)
|
|
22
|
+
3 the report could not be read / parsed / is an unsupported schema
|
|
23
|
+
`;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* @param {string[]} argv - argv slice after `report`
|
|
27
|
+
* @param {object} [deps]
|
|
28
|
+
* @returns {Promise<number>}
|
|
29
|
+
*/
|
|
30
|
+
export async function main(argv, deps = {}) {
|
|
31
|
+
const stdout = deps.stdout ?? process.stdout;
|
|
32
|
+
const stderr = deps.stderr ?? process.stderr;
|
|
33
|
+
const cwd = deps.cwd ?? process.cwd();
|
|
34
|
+
|
|
35
|
+
let parsed;
|
|
36
|
+
try {
|
|
37
|
+
parsed = parseArgs({ args: argv, options: { json: { type: 'boolean' }, help: { type: 'boolean' } }, allowPositionals: true, strict: true });
|
|
38
|
+
} catch (err) {
|
|
39
|
+
stderr.write(`[error] usage ${err.message}\n`);
|
|
40
|
+
stderr.write(HELP);
|
|
41
|
+
return 2;
|
|
42
|
+
}
|
|
43
|
+
if (parsed.values.help) {
|
|
44
|
+
stdout.write(HELP);
|
|
45
|
+
return 0;
|
|
46
|
+
}
|
|
47
|
+
const target = parsed.positionals[0];
|
|
48
|
+
if (!target) {
|
|
49
|
+
stderr.write('[error] usage report: a <report-path> is required\n');
|
|
50
|
+
stderr.write(HELP);
|
|
51
|
+
return 2;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const reader = deps.readFile ?? readFile;
|
|
55
|
+
let raw;
|
|
56
|
+
try {
|
|
57
|
+
raw = await reader(resolvePath(cwd, target), 'utf8');
|
|
58
|
+
} catch (err) {
|
|
59
|
+
stderr.write(`[error] missingFile ${target}: ${err.message}\n`);
|
|
60
|
+
return 3;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const report = parseReport(raw);
|
|
64
|
+
if (isRcfError(report)) {
|
|
65
|
+
stderr.write(`${formatError(report, { verbose: true })}\n`);
|
|
66
|
+
return 3;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
stdout.write(parsed.values.json ? `${JSON.stringify(report, null, 2)}\n` : renderReport(report));
|
|
70
|
+
return 0;
|
|
71
|
+
}
|