rcf-lite 0.13.0 → 0.14.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 +29 -1
- package/bin/rcf.js +3 -1
- package/blueprints/application-api-rest/docs/topics.md +1 -1
- package/blueprints/application-spa/README.md +3 -3
- package/blueprints/application-spa/contributions/adrs/adr-202-application-spa-theming.json +1 -1
- package/blueprints/application-spa/contributions/adrs/adr-206-application-spa-iconography.json +1 -1
- package/blueprints/application-spa/contributions/tacs/tac-207-application-spa-token-adherence-probe.json +7 -7
- package/blueprints/application-spa/contributions/tacs/tac-208-application-spa-icon-adherence-probe.json +7 -7
- package/blueprints/application-spa/contributions/tacs/tac-209-application-spa-csp-styled-adherence-probe.json +8 -8
- package/blueprints/application-spa/contributions/tacs/tac-210-application-spa-external-dependency-provisioning-probe.json +8 -8
- package/blueprints/application-spa/contributions/tacs/tac-211-application-spa-core-flow-e2e-probe.json +8 -8
- package/blueprints/application-spa/contributions/user-stories/application-spa-us-1129.json +2 -2
- package/blueprints/application-spa/contributions/user-stories/application-spa-us-1130.json +2 -2
- package/blueprints/application-spa/contributions/user-stories/application-spa-us-1131.json +2 -2
- package/blueprints/application-spa/contributions/user-stories/application-spa-us-1132.json +2 -2
- package/blueprints/application-spa/contributions/user-stories/application-spa-us-1133.json +3 -3
- package/blueprints/application-spa/docs/topics.md +1 -1
- package/blueprints/delivery-ci-workflows/CHANGELOG.md +39 -0
- package/blueprints/delivery-ci-workflows/README.md +57 -0
- package/blueprints/delivery-ci-workflows/assets/ci-provider-examples/github-actions/default-branch-checks.yml +55 -0
- package/blueprints/delivery-ci-workflows/assets/ci-provider-examples/github-actions/pull-request-checks.yml +65 -0
- package/blueprints/delivery-ci-workflows/assets/ci-provider-examples/github-actions/release.yml +71 -0
- package/blueprints/delivery-ci-workflows/assets/ci-provider-examples/github-actions/scheduled-audit.yml +61 -0
- package/blueprints/delivery-ci-workflows/assets/ci-provider-examples/notes.md +61 -0
- package/blueprints/{ci-pipeline → delivery-ci-workflows}/assets/report-samples/per-gate.json +2 -1
- package/blueprints/delivery-ci-workflows/blueprint.json +87 -0
- package/blueprints/delivery-ci-workflows/contributions/adrs/adr-701-delivery-ci-workflows-ci-gates.json +30 -0
- package/blueprints/{ci-pipeline/contributions/adrs/adr-702-ci-pipeline-strict-coverage-gate.json → delivery-ci-workflows/contributions/adrs/adr-702-delivery-ci-workflows-strict-coverage-gate.json} +2 -2
- package/blueprints/{ci-pipeline/contributions/adrs/adr-703-ci-pipeline-node-only-runner.json → delivery-ci-workflows/contributions/adrs/adr-703-delivery-ci-workflows-node-only-runner.json} +1 -1
- package/blueprints/{ci-pipeline/contributions/adrs/adr-704-ci-pipeline-report-shape.json → delivery-ci-workflows/contributions/adrs/adr-704-delivery-ci-workflows-report-shape.json} +3 -3
- package/blueprints/delivery-ci-workflows/contributions/adrs/adr-705-delivery-ci-workflows-elicitation-surface.json +25 -0
- package/blueprints/delivery-ci-workflows/contributions/adrs/adr-706-delivery-ci-workflows-branch-model-defaults.json +25 -0
- package/blueprints/delivery-ci-workflows/contributions/adrs/adr-707-delivery-ci-workflows-release-workflow-shape.json +25 -0
- package/blueprints/delivery-ci-workflows/contributions/adrs/adr-708-delivery-ci-workflows-provider-hint-shape.json +25 -0
- package/blueprints/delivery-ci-workflows/contributions/adrs/adr-709-delivery-ci-workflows-release-artefacts.json +25 -0
- package/blueprints/delivery-ci-workflows/contributions/adrs/adr-710-delivery-ci-workflows-scheduled-audit.json +25 -0
- package/blueprints/delivery-ci-workflows/contributions/requirements/delivery-ci-workflows-req-001.json +18 -0
- package/blueprints/{ci-pipeline/contributions/requirements/ci-pipeline-req-002.json → delivery-ci-workflows/contributions/requirements/delivery-ci-workflows-req-002.json} +2 -2
- package/blueprints/{ci-pipeline/contributions/requirements/ci-pipeline-req-003.json → delivery-ci-workflows/contributions/requirements/delivery-ci-workflows-req-003.json} +2 -2
- package/blueprints/{ci-pipeline/contributions/requirements/ci-pipeline-req-004.json → delivery-ci-workflows/contributions/requirements/delivery-ci-workflows-req-004.json} +2 -2
- package/blueprints/{ci-pipeline/contributions/requirements/ci-pipeline-req-005.json → delivery-ci-workflows/contributions/requirements/delivery-ci-workflows-req-005.json} +4 -4
- package/blueprints/{ci-pipeline/contributions/requirements/ci-pipeline-req-006.json → delivery-ci-workflows/contributions/requirements/delivery-ci-workflows-req-006.json} +2 -2
- package/blueprints/{ci-pipeline/contributions/requirements/ci-pipeline-req-007.json → delivery-ci-workflows/contributions/requirements/delivery-ci-workflows-req-007.json} +2 -2
- package/blueprints/{ci-pipeline/contributions/requirements/ci-pipeline-req-008.json → delivery-ci-workflows/contributions/requirements/delivery-ci-workflows-req-008.json} +2 -2
- package/blueprints/delivery-ci-workflows/contributions/requirements/delivery-ci-workflows-req-009.json +18 -0
- package/blueprints/{ci-pipeline/contributions/requirements/ci-pipeline-req-010.json → delivery-ci-workflows/contributions/requirements/delivery-ci-workflows-req-010.json} +2 -2
- package/blueprints/delivery-ci-workflows/contributions/requirements/delivery-ci-workflows-req-011.json +18 -0
- package/blueprints/delivery-ci-workflows/contributions/requirements/delivery-ci-workflows-req-012.json +18 -0
- package/blueprints/delivery-ci-workflows/contributions/requirements/delivery-ci-workflows-req-013.json +18 -0
- package/blueprints/delivery-ci-workflows/contributions/requirements/delivery-ci-workflows-req-014.json +18 -0
- package/blueprints/delivery-ci-workflows/contributions/requirements/delivery-ci-workflows-req-015.json +18 -0
- package/blueprints/delivery-ci-workflows/contributions/requirements/delivery-ci-workflows-req-016.json +18 -0
- package/blueprints/delivery-ci-workflows/contributions/requirements/delivery-ci-workflows-req-017.json +18 -0
- package/blueprints/delivery-ci-workflows/contributions/requirements/delivery-ci-workflows-req-018.json +18 -0
- package/blueprints/delivery-ci-workflows/contributions/requirements/delivery-ci-workflows-req-019.json +18 -0
- package/blueprints/delivery-ci-workflows/contributions/requirements/delivery-ci-workflows-req-020.json +18 -0
- package/blueprints/delivery-ci-workflows/contributions/requirements/delivery-ci-workflows-req-021.json +18 -0
- package/blueprints/delivery-ci-workflows/contributions/requirements/delivery-ci-workflows-req-022.json +18 -0
- package/blueprints/delivery-ci-workflows/contributions/requirements/delivery-ci-workflows-req-023.json +18 -0
- package/blueprints/{ci-pipeline/contributions/tacs/tac-701-ci-pipeline-gate-runner.json → delivery-ci-workflows/contributions/tacs/tac-701-delivery-ci-workflows-gate-runner.json} +3 -3
- package/blueprints/{ci-pipeline/contributions/tacs/tac-702-ci-pipeline-gate-report.json → delivery-ci-workflows/contributions/tacs/tac-702-delivery-ci-workflows-gate-report.json} +1 -1
- package/blueprints/{ci-pipeline/contributions/tacs/tac-703-ci-pipeline-aggregate-report.json → delivery-ci-workflows/contributions/tacs/tac-703-delivery-ci-workflows-aggregate-report.json} +2 -2
- package/blueprints/delivery-ci-workflows/contributions/tacs/tac-704-delivery-ci-workflows-workflow-materialiser.json +64 -0
- package/blueprints/delivery-ci-workflows/contributions/tacs/tac-705-delivery-ci-workflows-release-workflow.json +51 -0
- package/blueprints/delivery-ci-workflows/contributions/tacs/tac-706-delivery-ci-workflows-scheduled-audit.json +38 -0
- package/blueprints/delivery-ci-workflows/contributions/user-stories/delivery-ci-workflows-us-6101.json +37 -0
- package/blueprints/{ci-pipeline/contributions/user-stories/ci-pipeline-us-6102.json → delivery-ci-workflows/contributions/user-stories/delivery-ci-workflows-us-6102.json} +3 -3
- package/blueprints/{ci-pipeline/contributions/user-stories/ci-pipeline-us-6103.json → delivery-ci-workflows/contributions/user-stories/delivery-ci-workflows-us-6103.json} +4 -4
- package/blueprints/{ci-pipeline/contributions/user-stories/ci-pipeline-us-6104.json → delivery-ci-workflows/contributions/user-stories/delivery-ci-workflows-us-6104.json} +4 -4
- package/blueprints/{ci-pipeline/contributions/user-stories/ci-pipeline-us-6105.json → delivery-ci-workflows/contributions/user-stories/delivery-ci-workflows-us-6105.json} +6 -6
- package/blueprints/{ci-pipeline/contributions/user-stories/ci-pipeline-us-6106.json → delivery-ci-workflows/contributions/user-stories/delivery-ci-workflows-us-6106.json} +3 -3
- package/blueprints/{ci-pipeline/contributions/user-stories/ci-pipeline-us-6107.json → delivery-ci-workflows/contributions/user-stories/delivery-ci-workflows-us-6107.json} +5 -5
- package/blueprints/{ci-pipeline/contributions/user-stories/ci-pipeline-us-6108.json → delivery-ci-workflows/contributions/user-stories/delivery-ci-workflows-us-6108.json} +4 -4
- package/blueprints/delivery-ci-workflows/contributions/user-stories/delivery-ci-workflows-us-6109.json +36 -0
- package/blueprints/{ci-pipeline/contributions/user-stories/ci-pipeline-us-6110.json → delivery-ci-workflows/contributions/user-stories/delivery-ci-workflows-us-6110.json} +3 -3
- package/blueprints/delivery-ci-workflows/contributions/user-stories/delivery-ci-workflows-us-6111.json +36 -0
- package/blueprints/delivery-ci-workflows/contributions/user-stories/delivery-ci-workflows-us-6112.json +36 -0
- package/blueprints/delivery-ci-workflows/contributions/user-stories/delivery-ci-workflows-us-6113.json +36 -0
- package/blueprints/delivery-ci-workflows/contributions/user-stories/delivery-ci-workflows-us-6114.json +46 -0
- package/blueprints/delivery-ci-workflows/contributions/user-stories/delivery-ci-workflows-us-6115.json +37 -0
- package/blueprints/delivery-ci-workflows/contributions/user-stories/delivery-ci-workflows-us-6116.json +28 -0
- package/blueprints/delivery-ci-workflows/contributions/user-stories/delivery-ci-workflows-us-6117.json +28 -0
- package/blueprints/delivery-ci-workflows/contributions/user-stories/delivery-ci-workflows-us-6118.json +37 -0
- package/blueprints/delivery-ci-workflows/contributions/user-stories/delivery-ci-workflows-us-6119.json +28 -0
- package/blueprints/delivery-ci-workflows/contributions/user-stories/delivery-ci-workflows-us-6120.json +28 -0
- package/blueprints/delivery-ci-workflows/contributions/user-stories/delivery-ci-workflows-us-6121.json +46 -0
- package/blueprints/delivery-ci-workflows/contributions/user-stories/delivery-ci-workflows-us-6122.json +46 -0
- package/blueprints/delivery-ci-workflows/contributions/user-stories/delivery-ci-workflows-us-6123.json +37 -0
- package/blueprints/delivery-ci-workflows/docs/topics.md +61 -0
- package/blueprints/delivery-ci-workflows/guide/delivery-ci-workflows.md +136 -0
- package/blueprints/deploy-cloudflare-workers/docs/topics.md +3 -3
- package/blueprints/email-smtp-resend/docs/topics.md +1 -1
- package/blueprints/observability-essentials/README.md +2 -2
- package/blueprints/observability-essentials/docs/topics.md +5 -5
- package/blueprints/observability-probe-endpoints/docs/topics.md +2 -2
- package/blueprints/persistence-data-d1/README.md +2 -2
- package/blueprints/persistence-data-d1/assets/facade-shape/facade-module-shape.md +1 -1
- package/blueprints/persistence-data-d1/contributions/tacs/tac-1403-persistence-data-d1-deploy-gate.json +1 -1
- package/blueprints/persistence-data-d1/docs/topics.md +2 -2
- package/blueprints/persistence-data-d1/guide/persistence-data-d1.md +1 -1
- package/blueprints/persistence-data-sqlite/README.md +1 -1
- package/blueprints/persistence-data-sqlite/docs/topics.md +1 -1
- package/blueprints/security-auth-clerk/README.md +5 -3
- package/blueprints/security-auth-clerk/assets/middleware/workers-fetch-shape.md +123 -0
- package/blueprints/security-auth-clerk/assets/wiring/workers-wrangler-toml-shape.md +51 -0
- package/blueprints/security-auth-clerk/blueprint.json +1 -1
- package/blueprints/security-auth-clerk/docs/topics.md +2 -2
- package/blueprints/security-auth-clerk/guide/security-auth-clerk.md +9 -0
- package/blueprints/security-auth-keycloak/docs/topics.md +2 -2
- package/blueprints/security-auth-magic-link/README.md +1 -1
- package/blueprints/security-auth-magic-link/docs/topics.md +1 -1
- package/blueprints/security-auth-oauth2/README.md +1 -1
- package/blueprints/security-auth-oauth2/docs/topics.md +2 -2
- package/blueprints/security-secrets-management/README.md +1 -1
- package/blueprints/security-secrets-management/docs/topics.md +3 -3
- package/guidance/build-cycle-playbook.md +2 -2
- package/guidance/document-model.md +1 -1
- package/guidance/harness-template.md +13 -0
- package/guidance/managed/agent-instructions-block.hash +1 -1
- package/guidance/managed/agent-instructions-block.md +13 -0
- package/package.json +5 -2
- package/rcf/adrs/adr-001.json +1 -1
- package/rcf/adrs/adr-009.json +1 -1
- package/rcf/build-sequence.json +1 -1
- package/rcf/manifest.json +2 -2
- package/rcf/prd.json +2 -2
- package/releases/releases.yaml +116 -0
- package/src/blueprint/apply.js +51 -13
- package/src/blueprint/index.js +12 -0
- package/src/blueprint/library-loader.js +271 -0
- package/src/blueprint/library-registry.js +341 -0
- package/src/blueprint/list.js +38 -4
- package/src/blueprint/shelf-resolver.js +144 -31
- package/src/cli/blueprint-library.js +419 -0
- package/src/cli/blueprint.js +46 -9
- package/src/cli/guidance.js +1 -1
- package/src/cli/help.js +27 -1
- package/src/cli/version.js +673 -0
- package/src/cli/view.js +282 -1
- package/src/server/index.js +3 -0
- package/src/server/routes.js +15 -1
- package/src/server/scope-endpoint.js +105 -0
- package/src/view/live-client.js +253 -6
- package/src/view/scope.js +231 -0
- package/src/view/style.css +42 -0
- package/blueprints/ci-pipeline/README.md +0 -49
- package/blueprints/ci-pipeline/assets/ci-provider-examples/github-actions.yml +0 -61
- package/blueprints/ci-pipeline/assets/ci-provider-examples/notes.md +0 -50
- package/blueprints/ci-pipeline/blueprint.json +0 -46
- package/blueprints/ci-pipeline/contributions/adrs/adr-701-ci-pipeline-ci-gates.json +0 -25
- package/blueprints/ci-pipeline/contributions/requirements/ci-pipeline-req-001.json +0 -18
- package/blueprints/ci-pipeline/contributions/requirements/ci-pipeline-req-009.json +0 -18
- package/blueprints/ci-pipeline/contributions/user-stories/ci-pipeline-us-6101.json +0 -37
- package/blueprints/ci-pipeline/contributions/user-stories/ci-pipeline-us-6109.json +0 -36
- package/blueprints/ci-pipeline/docs/topics.md +0 -49
- package/blueprints/ci-pipeline/guide/ci-pipeline.md +0 -79
- package/rcf/.identity/profile.md +0 -37
- package/rcf/knowledge/INDEX.md +0 -12
- package/rcf/knowledge/README.md +0 -41
- package/rcf/knowledge/docs/.gitkeep +0 -0
- package/rcf/knowledge/notes/.gitkeep +0 -0
- /package/blueprints/{ci-pipeline → delivery-ci-workflows}/assets/report-samples/pipeline.json +0 -0
package/src/cli/view.js
CHANGED
|
@@ -5,7 +5,9 @@
|
|
|
5
5
|
// exported from that bin now live here so the CLI tests can still
|
|
6
6
|
// exercise them without spawning a subprocess.
|
|
7
7
|
|
|
8
|
+
import { readFile } from 'node:fs/promises';
|
|
8
9
|
import { spawn } from 'node:child_process';
|
|
10
|
+
import { join } from 'node:path';
|
|
9
11
|
import { platform } from 'node:process';
|
|
10
12
|
|
|
11
13
|
import { formatErrors } from '#core/errors';
|
|
@@ -20,6 +22,8 @@ import {
|
|
|
20
22
|
readLogTail,
|
|
21
23
|
} from '../view-supervisor/index.js';
|
|
22
24
|
import { parsePersistUntil } from '../view-supervisor/persist-until.js';
|
|
25
|
+
// Deep-link scope resolution (w-2026-08-30-dave-020).
|
|
26
|
+
import { contributionsForBlueprint, listAppliedSlugs, treeHasId } from '../view/scope.js';
|
|
23
27
|
|
|
24
28
|
export const DEFAULT_PORT = 4373;
|
|
25
29
|
export const SHUTDOWN_BUDGET_MS = 2000;
|
|
@@ -58,6 +62,19 @@ Subverbs (spec §9.2):
|
|
|
58
62
|
record.
|
|
59
63
|
rcf audit view logs [--tail <n>]
|
|
60
64
|
Print the supervisor log tail (default 200).
|
|
65
|
+
rcf audit view open [--blueprint <slug>] [--node <id>] [--no-open] [--json]
|
|
66
|
+
Resolve a scoped deep-link into the running
|
|
67
|
+
(or default) view server and print the URL.
|
|
68
|
+
--blueprint scopes the tree to the applied
|
|
69
|
+
blueprint's namespaced contribution list;
|
|
70
|
+
--node focuses a single doc id (any kind in
|
|
71
|
+
the tree). Both compose. Prints the URL to
|
|
72
|
+
stdout every time; opens it in the default
|
|
73
|
+
browser too when the terminal is a TTY and
|
|
74
|
+
--no-open was not passed. --json emits the
|
|
75
|
+
resolution shape for scripting. Refuses (exit
|
|
76
|
+
2) if the slug is not applied or the node id
|
|
77
|
+
is not on the tree.
|
|
61
78
|
|
|
62
79
|
Options:
|
|
63
80
|
--port <n> Bind the HTTP server on the given port.
|
|
@@ -96,7 +113,7 @@ Exit codes:
|
|
|
96
113
|
130 SIGINT
|
|
97
114
|
`;
|
|
98
115
|
|
|
99
|
-
const SUBVERBS = new Set(['start', 'status', 'stop', 'logs']);
|
|
116
|
+
const SUBVERBS = new Set(['start', 'status', 'stop', 'logs', 'open']);
|
|
100
117
|
|
|
101
118
|
/**
|
|
102
119
|
* Parse the view subcommand argv. Hand-rolled so the CLI can pass
|
|
@@ -375,6 +392,10 @@ async function runSubverb({ subverb, argv, deps }) {
|
|
|
375
392
|
return 0;
|
|
376
393
|
}
|
|
377
394
|
|
|
395
|
+
if (subverb === 'open') {
|
|
396
|
+
return await runOpenSubverb({ argv, projectRoot, deps: { env, stdout, stderr } });
|
|
397
|
+
}
|
|
398
|
+
|
|
378
399
|
if (subverb === 'logs') {
|
|
379
400
|
const tailIdx = argv.indexOf('--tail');
|
|
380
401
|
const tail = tailIdx >= 0 ? Number.parseInt(argv[tailIdx + 1] ?? '', 10) : 200;
|
|
@@ -452,3 +473,263 @@ async function runSubverb({ subverb, argv, deps }) {
|
|
|
452
473
|
return 1;
|
|
453
474
|
}
|
|
454
475
|
}
|
|
476
|
+
|
|
477
|
+
// -------------------------------------------------------------------
|
|
478
|
+
// `rcf audit view open` (w-2026-08-30-dave-020).
|
|
479
|
+
//
|
|
480
|
+
// Resolves a scoped deep-link into the running (or default) view
|
|
481
|
+
// server and prints the URL. The agent-facing surface: during a
|
|
482
|
+
// blueprint integration validation gate, the agent runs this to hand
|
|
483
|
+
// the operator a URL that opens the viewer with the relevant
|
|
484
|
+
// contribution set already visible (rest dimmed) and the target node
|
|
485
|
+
// scrolled into view. Read-only: v1 does not accept approve/reject
|
|
486
|
+
// state; that is a v2 concern (state-bearing, needs its own design).
|
|
487
|
+
// -------------------------------------------------------------------
|
|
488
|
+
|
|
489
|
+
/**
|
|
490
|
+
* Parse the argv slice for `open`. Kept out of `parseArgs` because
|
|
491
|
+
* the flag set overlaps enough to conflate error messages.
|
|
492
|
+
*
|
|
493
|
+
* @param {string[]} argv
|
|
494
|
+
* @returns {{ opts: object, errors: string[] }}
|
|
495
|
+
*/
|
|
496
|
+
export function parseOpenArgs(argv) {
|
|
497
|
+
const opts = {
|
|
498
|
+
blueprint: null,
|
|
499
|
+
node: null,
|
|
500
|
+
noOpen: false,
|
|
501
|
+
json: false,
|
|
502
|
+
port: null,
|
|
503
|
+
help: false,
|
|
504
|
+
};
|
|
505
|
+
const errors = [];
|
|
506
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
507
|
+
const arg = argv[i];
|
|
508
|
+
switch (arg) {
|
|
509
|
+
case '--no-open':
|
|
510
|
+
opts.noOpen = true;
|
|
511
|
+
break;
|
|
512
|
+
case '--json':
|
|
513
|
+
opts.json = true;
|
|
514
|
+
break;
|
|
515
|
+
case '--help':
|
|
516
|
+
case '-h':
|
|
517
|
+
opts.help = true;
|
|
518
|
+
break;
|
|
519
|
+
case '--blueprint': {
|
|
520
|
+
const next = argv[i + 1];
|
|
521
|
+
if (next === undefined || next.startsWith('--')) {
|
|
522
|
+
errors.push('--blueprint requires a slug argument');
|
|
523
|
+
break;
|
|
524
|
+
}
|
|
525
|
+
opts.blueprint = next;
|
|
526
|
+
i += 1;
|
|
527
|
+
break;
|
|
528
|
+
}
|
|
529
|
+
case '--node': {
|
|
530
|
+
const next = argv[i + 1];
|
|
531
|
+
if (next === undefined || next.startsWith('--')) {
|
|
532
|
+
errors.push('--node requires a doc id argument');
|
|
533
|
+
break;
|
|
534
|
+
}
|
|
535
|
+
opts.node = next;
|
|
536
|
+
i += 1;
|
|
537
|
+
break;
|
|
538
|
+
}
|
|
539
|
+
case '--port': {
|
|
540
|
+
const next = argv[i + 1];
|
|
541
|
+
if (next === undefined || next.startsWith('--')) {
|
|
542
|
+
errors.push('--port requires a numeric argument');
|
|
543
|
+
break;
|
|
544
|
+
}
|
|
545
|
+
const n = Number(next);
|
|
546
|
+
if (!Number.isInteger(n) || n < 0 || n > 65535) {
|
|
547
|
+
errors.push(`--port expects an integer in [0, 65535], got ${next}`);
|
|
548
|
+
} else {
|
|
549
|
+
opts.port = n;
|
|
550
|
+
}
|
|
551
|
+
i += 1;
|
|
552
|
+
break;
|
|
553
|
+
}
|
|
554
|
+
default:
|
|
555
|
+
errors.push(`unknown option: ${arg}`);
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
return { opts, errors };
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
/**
|
|
562
|
+
* Read `rcf/manifest.json` off disk (bypasses the walker so the CLI
|
|
563
|
+
* can validate scope fast without a full tree walk when only a slug
|
|
564
|
+
* check is needed).
|
|
565
|
+
*
|
|
566
|
+
* @param {string} projectRoot
|
|
567
|
+
* @returns {Promise<object|null>}
|
|
568
|
+
*/
|
|
569
|
+
async function readManifestFile(projectRoot) {
|
|
570
|
+
try {
|
|
571
|
+
const raw = await readFile(join(projectRoot, 'rcf', 'manifest.json'), 'utf8');
|
|
572
|
+
return JSON.parse(raw);
|
|
573
|
+
} catch (err) {
|
|
574
|
+
if (err && err.code === 'ENOENT') return null;
|
|
575
|
+
throw err;
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
/**
|
|
580
|
+
* Compose a scoped URL from the base URL plus scope params.
|
|
581
|
+
*
|
|
582
|
+
* @param {object} args
|
|
583
|
+
* @param {string} args.baseUrl - e.g. `http://127.0.0.1:4373/`
|
|
584
|
+
* @param {string|null} args.blueprint
|
|
585
|
+
* @param {string|null} args.node
|
|
586
|
+
* @returns {string}
|
|
587
|
+
*/
|
|
588
|
+
export function composeScopeUrl({ baseUrl, blueprint, node }) {
|
|
589
|
+
// The base URL from the supervisor / --port / env / default resolver
|
|
590
|
+
// always ends with `/`. Normalise defensively so a caller-supplied
|
|
591
|
+
// base without one still composes to a valid URL.
|
|
592
|
+
const trimmed = baseUrl.endsWith('/') ? baseUrl : `${baseUrl}/`;
|
|
593
|
+
const params = [];
|
|
594
|
+
if (blueprint) params.push(`blueprint=${encodeURIComponent(blueprint)}`);
|
|
595
|
+
if (node) params.push(`node=${encodeURIComponent(node)}`);
|
|
596
|
+
const query = params.length > 0 ? `?${params.join('&')}` : '';
|
|
597
|
+
// Hash so the existing hash-router activates the tab + drills the
|
|
598
|
+
// ancestor <details> chain without waiting for the client to fetch
|
|
599
|
+
// /scope.json.
|
|
600
|
+
const hash = node ? `#${encodeURIComponent(node)}` : '';
|
|
601
|
+
return `${trimmed}${query}${hash}`;
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
/**
|
|
605
|
+
* Resolve the base URL to open at. Prefers a running detached
|
|
606
|
+
* supervisor's recorded URL (respects any non-default --port a prior
|
|
607
|
+
* start invocation bound to); otherwise the RCF_VIEW_PORT / default
|
|
608
|
+
* pair. The command still prints a URL even when no server is
|
|
609
|
+
* running, so an agent can print one to the operator alongside a
|
|
610
|
+
* hint to start the server.
|
|
611
|
+
*
|
|
612
|
+
* @param {object} args
|
|
613
|
+
* @param {string} args.projectRoot
|
|
614
|
+
* @param {number|null} args.port
|
|
615
|
+
* @param {NodeJS.ProcessEnv} args.env
|
|
616
|
+
* @returns {Promise<{ baseUrl: string, source: 'supervisor'|'flag'|'env'|'default' }>}
|
|
617
|
+
*/
|
|
618
|
+
export async function resolveBaseUrl({ projectRoot, port, env }) {
|
|
619
|
+
if (typeof port === 'number') {
|
|
620
|
+
return { baseUrl: `http://127.0.0.1:${port}/`, source: 'flag' };
|
|
621
|
+
}
|
|
622
|
+
try {
|
|
623
|
+
const status = await statusOfDetached(projectRoot);
|
|
624
|
+
if (status && status.state === 'running' && typeof status.url === 'string' && status.url.length > 0) {
|
|
625
|
+
const url = status.url.endsWith('/') ? status.url : `${status.url}/`;
|
|
626
|
+
return { baseUrl: url, source: 'supervisor' };
|
|
627
|
+
}
|
|
628
|
+
} catch { /* fall through */ }
|
|
629
|
+
const raw = env.RCF_VIEW_PORT;
|
|
630
|
+
if (raw !== undefined && raw !== '') {
|
|
631
|
+
const n = Number(raw);
|
|
632
|
+
if (Number.isInteger(n) && n >= 0 && n <= 65535) {
|
|
633
|
+
return { baseUrl: `http://127.0.0.1:${n}/`, source: 'env' };
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
return { baseUrl: `http://127.0.0.1:${DEFAULT_PORT}/`, source: 'default' };
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
/**
|
|
640
|
+
* The full open flow. Kept as a named export so the test suite can
|
|
641
|
+
* exercise it directly.
|
|
642
|
+
*
|
|
643
|
+
* @param {object} args
|
|
644
|
+
* @param {string[]} args.argv
|
|
645
|
+
* @param {string} args.projectRoot
|
|
646
|
+
* @param {{ env: NodeJS.ProcessEnv, stdout: NodeJS.WritableStream, stderr: NodeJS.WritableStream }} args.deps
|
|
647
|
+
* @returns {Promise<number>}
|
|
648
|
+
*/
|
|
649
|
+
export async function runOpenSubverb({ argv, projectRoot, deps }) {
|
|
650
|
+
const { env, stdout, stderr } = deps;
|
|
651
|
+
const { opts, errors: argErrors } = parseOpenArgs(argv);
|
|
652
|
+
if (opts.help) {
|
|
653
|
+
stdout.write(HELP);
|
|
654
|
+
return 0;
|
|
655
|
+
}
|
|
656
|
+
if (argErrors.length > 0) {
|
|
657
|
+
for (const msg of argErrors) stderr.write(`[error] usage view open: ${msg}\n`);
|
|
658
|
+
return 2;
|
|
659
|
+
}
|
|
660
|
+
if (!opts.blueprint && !opts.node) {
|
|
661
|
+
stderr.write('[error] usage view open: pass --blueprint <slug> and/or --node <id>\n');
|
|
662
|
+
return 2;
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
// Validate scope.
|
|
666
|
+
let manifest = null;
|
|
667
|
+
if (opts.blueprint) {
|
|
668
|
+
manifest = await readManifestFile(projectRoot);
|
|
669
|
+
const hit = contributionsForBlueprint(manifest, opts.blueprint);
|
|
670
|
+
if (!hit.found) {
|
|
671
|
+
const applied = listAppliedSlugs(manifest);
|
|
672
|
+
const hint = applied.length > 0
|
|
673
|
+
? ` Applied: ${applied.join(', ')}.`
|
|
674
|
+
: ' No blueprints are applied to this project.';
|
|
675
|
+
stderr.write(`[error] usage view open: blueprint '${opts.blueprint}' is not applied to this project.${hint}\n`);
|
|
676
|
+
return 2;
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
let contributionIds = [];
|
|
680
|
+
if (manifest && opts.blueprint) {
|
|
681
|
+
const hit = contributionsForBlueprint(manifest, opts.blueprint);
|
|
682
|
+
contributionIds = hit.found ? hit.contributionIds : [];
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
if (opts.node) {
|
|
686
|
+
// Walk the tree once to prove the node exists. Cheaper than
|
|
687
|
+
// guessing at grammar; also gives the user the same "unknown id"
|
|
688
|
+
// wording as define/link.
|
|
689
|
+
const { tree } = await walkTree({ projectRoot });
|
|
690
|
+
if (!treeHasId(tree, opts.node)) {
|
|
691
|
+
stderr.write(`[error] usage view open: node id '${opts.node}' is not present on the tree.\n`);
|
|
692
|
+
return 2;
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
const { baseUrl, source } = await resolveBaseUrl({ projectRoot, port: opts.port, env });
|
|
697
|
+
const url = composeScopeUrl({ baseUrl, blueprint: opts.blueprint, node: opts.node });
|
|
698
|
+
const supervisor = source === 'supervisor';
|
|
699
|
+
const serverRunningNote = supervisor
|
|
700
|
+
? null
|
|
701
|
+
: 'view server is not running; start it with `rcf audit view start --detach` for the URL to load';
|
|
702
|
+
|
|
703
|
+
if (opts.json) {
|
|
704
|
+
stdout.write(`${JSON.stringify({
|
|
705
|
+
url,
|
|
706
|
+
baseUrl,
|
|
707
|
+
urlSource: source,
|
|
708
|
+
blueprint: opts.blueprint,
|
|
709
|
+
node: opts.node,
|
|
710
|
+
contributionCount: contributionIds.length,
|
|
711
|
+
opened: false,
|
|
712
|
+
serverRunning: supervisor,
|
|
713
|
+
}, null, 2)}\n`);
|
|
714
|
+
} else {
|
|
715
|
+
stdout.write(`${url}\n`);
|
|
716
|
+
if (opts.blueprint) {
|
|
717
|
+
stdout.write(` blueprint: ${opts.blueprint} (${contributionIds.length} contribution${contributionIds.length === 1 ? '' : 's'})\n`);
|
|
718
|
+
}
|
|
719
|
+
if (opts.node) {
|
|
720
|
+
stdout.write(` node: ${opts.node}\n`);
|
|
721
|
+
}
|
|
722
|
+
if (serverRunningNote) stdout.write(` note: ${serverRunningNote}\n`);
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
if (!opts.noOpen) {
|
|
726
|
+
maybeAutoOpen({
|
|
727
|
+
target: url,
|
|
728
|
+
noOpen: opts.noOpen,
|
|
729
|
+
stream: stdout,
|
|
730
|
+
env,
|
|
731
|
+
stderr,
|
|
732
|
+
});
|
|
733
|
+
}
|
|
734
|
+
return 0;
|
|
735
|
+
}
|
package/src/server/index.js
CHANGED
|
@@ -16,6 +16,7 @@ import { join } from 'node:path';
|
|
|
16
16
|
import { LIVE_CLIENT_PATH, STYLE_CSS_PATH, VENDORED_MERMAID_PATH, renderModelToPage } from '../view/index.js';
|
|
17
17
|
import { watch as defaultWatch } from '../watch/index.js';
|
|
18
18
|
import { createRouter } from './routes.js';
|
|
19
|
+
import { createScopeHandler } from './scope-endpoint.js';
|
|
19
20
|
import { createSseHub } from './sse.js';
|
|
20
21
|
|
|
21
22
|
/**
|
|
@@ -96,12 +97,14 @@ export async function startServer(args) {
|
|
|
96
97
|
// and the first SSE connect gets a real payload.
|
|
97
98
|
await rewalk();
|
|
98
99
|
|
|
100
|
+
const scopeHandler = createScopeHandler({ projectRoot });
|
|
99
101
|
const router = createRouter({
|
|
100
102
|
currentState: () => state,
|
|
101
103
|
sse,
|
|
102
104
|
stylePath: STYLE_CSS_PATH,
|
|
103
105
|
mermaidPath: VENDORED_MERMAID_PATH,
|
|
104
106
|
liveClientPath: LIVE_CLIENT_PATH,
|
|
107
|
+
scope: scopeHandler,
|
|
105
108
|
});
|
|
106
109
|
|
|
107
110
|
const server = createServer(router);
|
package/src/server/routes.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
// HTTP route table for the live-view server.
|
|
1
|
+
// HTTP route table for the live-view server. Routes served:
|
|
2
2
|
// GET / -> rendered page (text/html)
|
|
3
3
|
// GET /events -> SSE stream (text/event-stream)
|
|
4
4
|
// GET /style.css -> shipped stylesheet
|
|
5
5
|
// GET /mermaid.min.js-> vendored mermaid runtime
|
|
6
6
|
// GET /live-client.js-> phase 3.8 live client script
|
|
7
|
+
// GET /scope.json -> deep-link scope resolver (w-2026-08-30-dave-020)
|
|
7
8
|
// Everything else -> 404 text/plain.
|
|
8
9
|
//
|
|
9
10
|
// No CORS headers, no cache headers on static assets beyond what the
|
|
@@ -25,6 +26,10 @@ const MIME = {
|
|
|
25
26
|
* @property {string} stylePath
|
|
26
27
|
* @property {string} mermaidPath
|
|
27
28
|
* @property {string} liveClientPath
|
|
29
|
+
* @property {((req: import('node:http').IncomingMessage, res: import('node:http').ServerResponse) => Promise<void>|void)} [scope]
|
|
30
|
+
* Deep-link scope handler (w-2026-08-30-dave-020). Optional so
|
|
31
|
+
* the router stays usable in tests that mount only the static
|
|
32
|
+
* routes without a project root.
|
|
28
33
|
*/
|
|
29
34
|
|
|
30
35
|
/**
|
|
@@ -74,6 +79,15 @@ export function createRouter(deps) {
|
|
|
74
79
|
serveFile(res, deps.liveClientPath, MIME.js).catch((err) => fail(res, err));
|
|
75
80
|
return;
|
|
76
81
|
}
|
|
82
|
+
if (path === '/scope.json') {
|
|
83
|
+
if (typeof deps.scope !== 'function') {
|
|
84
|
+
res.writeHead(404, { 'content-type': MIME.txt });
|
|
85
|
+
res.end('not found\n');
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
Promise.resolve(deps.scope(req, res)).catch((err) => fail(res, err));
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
77
91
|
|
|
78
92
|
res.writeHead(404, { 'content-type': MIME.txt });
|
|
79
93
|
res.end('not found\n');
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
// HTTP handler for `/scope.json`. The live-client fetches this to
|
|
2
|
+
// resolve a `?blueprint=<slug>` URL query into the applied
|
|
3
|
+
// contribution list and the customised-since-apply subset.
|
|
4
|
+
//
|
|
5
|
+
// Three shapes:
|
|
6
|
+
// GET /scope.json -> { blueprints: [{ slug, contributionCount, appliedAt, version }] }
|
|
7
|
+
// (the applied blueprint index; used by the client to know
|
|
8
|
+
// which slugs are valid before firing a scoped request)
|
|
9
|
+
// GET /scope.json?blueprint=<slug> -> { slug, appliedAt, version, contributionIds, customisedIds, missingSourceIds }
|
|
10
|
+
// GET /scope.json?blueprint=<slug> with slug not applied -> 404 { error: 'unknownBlueprint', slug }
|
|
11
|
+
//
|
|
12
|
+
// The endpoint is read-only. It reads manifest.json every call rather
|
|
13
|
+
// than piggy-backing on the cached tree state; the caller is a browser
|
|
14
|
+
// (small payload, low frequency), and a fresh read means scope answers
|
|
15
|
+
// track a re-apply immediately even if a tree-update SSE broadcast has
|
|
16
|
+
// not yet propagated.
|
|
17
|
+
|
|
18
|
+
import { readFile } from 'node:fs/promises';
|
|
19
|
+
import { join } from 'node:path';
|
|
20
|
+
|
|
21
|
+
import { contributionsForBlueprint, detectCustomisations, listAppliedSlugs } from '../view/scope.js';
|
|
22
|
+
|
|
23
|
+
const MIME_JSON = 'application/json; charset=utf-8';
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Build a handler bound to a project root. Returns a function suitable
|
|
27
|
+
* for wiring into the router as the `/scope.json` route.
|
|
28
|
+
*
|
|
29
|
+
* @param {object} args
|
|
30
|
+
* @param {string} args.projectRoot
|
|
31
|
+
* @param {(root: string) => Promise<object|null>} [args._readManifest] - test seam
|
|
32
|
+
* @returns {(req: import('node:http').IncomingMessage, res: import('node:http').ServerResponse) => Promise<void>}
|
|
33
|
+
*/
|
|
34
|
+
export function createScopeHandler({ projectRoot, _readManifest, _detectCustomisations } = {}) {
|
|
35
|
+
const readManifest = _readManifest ?? defaultReadManifest;
|
|
36
|
+
const detectImpl = _detectCustomisations ?? detectCustomisations;
|
|
37
|
+
return async function handle(req, res) {
|
|
38
|
+
try {
|
|
39
|
+
const url = new URL(req.url ?? '/', 'http://localhost');
|
|
40
|
+
const slug = normaliseString(url.searchParams.get('blueprint'));
|
|
41
|
+
const manifest = await readManifest(projectRoot);
|
|
42
|
+
if (!slug) {
|
|
43
|
+
respond(res, 200, {
|
|
44
|
+
blueprints: appliedIndex(manifest),
|
|
45
|
+
});
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
const found = contributionsForBlueprint(manifest, slug);
|
|
49
|
+
if (!found.found) {
|
|
50
|
+
respond(res, 404, { error: 'unknownBlueprint', slug });
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
const { customisedIds, missingSourceIds } = await detectImpl({
|
|
54
|
+
projectRoot,
|
|
55
|
+
record: found.record,
|
|
56
|
+
});
|
|
57
|
+
respond(res, 200, {
|
|
58
|
+
slug,
|
|
59
|
+
appliedAt: found.record.appliedAt ?? null,
|
|
60
|
+
version: found.record.version ?? null,
|
|
61
|
+
contributionIds: found.contributionIds,
|
|
62
|
+
customisedIds,
|
|
63
|
+
missingSourceIds,
|
|
64
|
+
});
|
|
65
|
+
} catch (err) {
|
|
66
|
+
respond(res, 500, { error: 'ioFailure', message: err && err.message ? err.message : 'unknown' });
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function appliedIndex(manifest) {
|
|
72
|
+
const slugs = listAppliedSlugs(manifest);
|
|
73
|
+
const list = manifest?.blueprints ?? [];
|
|
74
|
+
return slugs.map((slug) => {
|
|
75
|
+
const rec = list.find((b) => b?.slug === slug) ?? {};
|
|
76
|
+
return {
|
|
77
|
+
slug,
|
|
78
|
+
version: rec.version ?? null,
|
|
79
|
+
appliedAt: rec.appliedAt ?? null,
|
|
80
|
+
contributionCount: Array.isArray(rec.contributions) ? rec.contributions.length : 0,
|
|
81
|
+
};
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function normaliseString(v) {
|
|
86
|
+
if (typeof v !== 'string') return null;
|
|
87
|
+
const trimmed = v.trim();
|
|
88
|
+
return trimmed.length === 0 ? null : trimmed;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async function defaultReadManifest(projectRoot) {
|
|
92
|
+
try {
|
|
93
|
+
const raw = await readFile(join(projectRoot, 'rcf', 'manifest.json'), 'utf8');
|
|
94
|
+
return JSON.parse(raw);
|
|
95
|
+
} catch (err) {
|
|
96
|
+
if (err && err.code === 'ENOENT') return null;
|
|
97
|
+
throw err;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function respond(res, status, body) {
|
|
102
|
+
const buf = Buffer.from(`${JSON.stringify(body)}\n`, 'utf8');
|
|
103
|
+
res.writeHead(status, { 'content-type': MIME_JSON, 'content-length': String(buf.length) });
|
|
104
|
+
res.end(buf);
|
|
105
|
+
}
|