rcf-lite 0.14.0 → 0.16.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.
Files changed (35) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/bin/view-supervisor-child.mjs +0 -0
  3. package/blueprints/delivery-ci-workflows/README.md +4 -0
  4. package/blueprints/delivery-ci-workflows/assets/bootstrap/README.md +26 -0
  5. package/blueprints/delivery-ci-workflows/assets/bootstrap/adr-bootstrap-coverage-supersession.template.json +28 -0
  6. package/blueprints/delivery-ci-workflows/assets/ci-provider-examples/github-actions/default-branch-checks.yml +12 -6
  7. package/blueprints/delivery-ci-workflows/assets/ci-provider-examples/github-actions/pull-request-checks.yml +16 -7
  8. package/blueprints/delivery-ci-workflows/assets/ci-provider-examples/github-actions/release.yml +4 -0
  9. package/blueprints/delivery-ci-workflows/assets/ci-provider-examples/github-actions/scheduled-audit.yml +4 -0
  10. package/blueprints/delivery-ci-workflows/assets/ci-provider-examples/notes.md +5 -5
  11. package/blueprints/delivery-ci-workflows/assets/report-samples/per-gate.json +1 -1
  12. package/blueprints/delivery-ci-workflows/blueprint.json +1 -1
  13. package/blueprints/delivery-ci-workflows/contributions/adrs/adr-702-delivery-ci-workflows-strict-coverage-gate.json +2 -2
  14. package/blueprints/delivery-ci-workflows/contributions/tacs/tac-701-delivery-ci-workflows-gate-runner.json +2 -2
  15. package/blueprints/delivery-ci-workflows/contributions/tacs/tac-704-delivery-ci-workflows-workflow-materialiser.json +7 -4
  16. package/blueprints/delivery-ci-workflows/contributions/user-stories/delivery-ci-workflows-us-6111.json +2 -2
  17. package/blueprints/delivery-ci-workflows/contributions/user-stories/delivery-ci-workflows-us-6114.json +6 -6
  18. package/blueprints/delivery-ci-workflows/contributions/user-stories/delivery-ci-workflows-us-6115.json +6 -6
  19. package/blueprints/delivery-ci-workflows/guide/delivery-ci-workflows.md +39 -1
  20. package/fixtures/canary-manifest.json +101 -1
  21. package/guidance/harness-template.md +9 -0
  22. package/guidance/managed/agent-instructions-block.hash +1 -1
  23. package/guidance/managed/agent-instructions-block.md +9 -0
  24. package/package.json +13 -15
  25. package/releases/releases.yaml +21 -1
  26. package/src/blueprint/apply.js +15 -6
  27. package/src/blueprint/index.js +22 -0
  28. package/src/blueprint/library-cache.js +143 -0
  29. package/src/blueprint/library-fetcher-git.js +347 -0
  30. package/src/blueprint/library-fetcher-tarball.js +379 -0
  31. package/src/blueprint/library-loader.js +21 -0
  32. package/src/blueprint/shelf-resolver.js +100 -7
  33. package/src/blueprint/supersede.js +56 -13
  34. package/src/cli/blueprint-library.js +427 -82
  35. package/src/cli/blueprint.js +6 -1
@@ -1,3 +1,103 @@
1
1
  {
2
- "registerCanary": []
2
+ "registerCanary": [
3
+ {
4
+ "id": "rc-2026-09-03-001",
5
+ "createdAt": "2026-09-03T13:16:14.200Z",
6
+ "buildVersion": "0.16.0-mockdriver",
7
+ "fixturePromptId": "canary-prompt-01",
8
+ "responseWordCount": 55,
9
+ "grades": {
10
+ "internalRuleCitation": {
11
+ "verdict": "pass",
12
+ "matches": []
13
+ },
14
+ "unglossedJargon": {
15
+ "verdict": "pass",
16
+ "matches": []
17
+ },
18
+ "redundantPermissionAsk": {
19
+ "verdict": "pass",
20
+ "matches": []
21
+ },
22
+ "bypassOffer": {
23
+ "verdict": "pass",
24
+ "matches": []
25
+ },
26
+ "wordCountBudget": {
27
+ "verdict": "pass",
28
+ "target": 200,
29
+ "actual": 55,
30
+ "matches": []
31
+ }
32
+ },
33
+ "verdict": "fail",
34
+ "shipDespiteFailReason": "mock canary driver used; no real subagent was dispatched. This record verifies canary infrastructure only, not the register itself."
35
+ },
36
+ {
37
+ "id": "rc-2026-09-03-002",
38
+ "createdAt": "2026-09-03T13:16:14.200Z",
39
+ "buildVersion": "0.16.0-mockdriver",
40
+ "fixturePromptId": "canary-prompt-02",
41
+ "responseWordCount": 55,
42
+ "grades": {
43
+ "internalRuleCitation": {
44
+ "verdict": "pass",
45
+ "matches": []
46
+ },
47
+ "unglossedJargon": {
48
+ "verdict": "pass",
49
+ "matches": []
50
+ },
51
+ "redundantPermissionAsk": {
52
+ "verdict": "pass",
53
+ "matches": []
54
+ },
55
+ "bypassOffer": {
56
+ "verdict": "pass",
57
+ "matches": []
58
+ },
59
+ "wordCountBudget": {
60
+ "verdict": "pass",
61
+ "target": 200,
62
+ "actual": 55,
63
+ "matches": []
64
+ }
65
+ },
66
+ "verdict": "fail",
67
+ "shipDespiteFailReason": "mock canary driver used; no real subagent was dispatched. This record verifies canary infrastructure only, not the register itself."
68
+ },
69
+ {
70
+ "id": "rc-2026-09-03-003",
71
+ "createdAt": "2026-09-03T13:16:14.201Z",
72
+ "buildVersion": "0.16.0-mockdriver",
73
+ "fixturePromptId": "canary-prompt-03",
74
+ "responseWordCount": 55,
75
+ "grades": {
76
+ "internalRuleCitation": {
77
+ "verdict": "pass",
78
+ "matches": []
79
+ },
80
+ "unglossedJargon": {
81
+ "verdict": "pass",
82
+ "matches": []
83
+ },
84
+ "redundantPermissionAsk": {
85
+ "verdict": "pass",
86
+ "matches": []
87
+ },
88
+ "bypassOffer": {
89
+ "verdict": "pass",
90
+ "matches": []
91
+ },
92
+ "wordCountBudget": {
93
+ "verdict": "pass",
94
+ "target": 200,
95
+ "actual": 55,
96
+ "matches": []
97
+ }
98
+ },
99
+ "verdict": "fail",
100
+ "shipDespiteFailReason": "mock canary driver used; no real subagent was dispatched. This record verifies canary infrastructure only, not the register itself."
101
+ }
102
+ ]
3
103
  }
@@ -56,6 +56,15 @@ non-technical; the method must be invisible in what they read.
56
56
  - Tone: it is in hand. The operator steers; you drive. Confident
57
57
  without hedging, and plainly honest when something is genuinely
58
58
  blocked or ambiguous.
59
+ - Blueprints and libraries. When the operator asks for a starting
60
+ shape, list what is available (the packaged shelf and any libraries
61
+ registered on this project) in plain words and offer one that fits.
62
+ The operator chooses; you do not pick for them. Registering a
63
+ library is a trust decision the operator makes; when it lands,
64
+ relay it in a sentence ("added the WSD library to this project").
65
+ Library-qualified names like `wsd:auth-oauth2`, the ids each apply
66
+ stamps, and the exact CLI lines belong in files and in `rcf`
67
+ output, not in the conversation.
59
68
 
60
69
  Before / after - the same first status after project setup:
61
70
 
@@ -1 +1 @@
1
- a36dfe0d47c57bafa2ace121e7390fac95c7fff70ee362e864adf75c07be190a
1
+ 1db0aeb320810137f642e7dcdc7e8ffb4b43c9d050383ae3c9d10d10de0dd690
@@ -43,6 +43,15 @@ non-technical; the method must be invisible in what they read.
43
43
  - Tone: it is in hand. The operator steers; you drive. Confident
44
44
  without hedging, and plainly honest when something is genuinely
45
45
  blocked or ambiguous.
46
+ - Blueprints and libraries. When the operator asks for a starting
47
+ shape, list what is available (the packaged shelf and any libraries
48
+ registered on this project) in plain words and offer one that fits.
49
+ The operator chooses; you do not pick for them. Registering a
50
+ library is a trust decision the operator makes; when it lands,
51
+ relay it in a sentence ("added the WSD library to this project").
52
+ Library-qualified names like `wsd:auth-oauth2`, the ids each apply
53
+ stamps, and the exact CLI lines belong in files and in `rcf`
54
+ output, not in the conversation.
46
55
 
47
56
  Before / after - the same first status after project setup:
48
57
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rcf-lite",
3
- "version": "0.14.0",
3
+ "version": "0.16.0",
4
4
  "type": "module",
5
5
  "description": "One-install tooling for the Requirements Confidence Framework (RCF): the unified `rcf` CLI grouped into the five RCF tool groups (discover, define, build, verify, audit) plus a small core set (init, doctor, guidance, mcp), an MCP server, the live tree viewer and the fresh-context adversarial ship-gate verifier. Consumes @stravica-ai/rcf-schemas.",
6
6
  "license": "Apache-2.0",
@@ -39,18 +39,6 @@
39
39
  "registry": "https://registry.npmjs.org/",
40
40
  "access": "public"
41
41
  },
42
- "scripts": {
43
- "test": "node --test --test-concurrency=1 'test/**/*.test.js'",
44
- "vendor": "node scripts/vendor-mermaid.mjs",
45
- "build:managed": "node scripts/gen-managed-artefacts.mjs",
46
- "canary:register": "node scripts/canary-register.mjs",
47
- "validate:releases": "node scripts/validate-releases.mjs",
48
- "stage:blueprints": "node scripts/stage-blueprint-shelf.mjs",
49
- "preinstall": "node scripts/preinstall-node-check.mjs",
50
- "prepack": "node scripts/stage-blueprint-shelf.mjs",
51
- "prepublishOnly": "node scripts/gen-managed-artefacts.mjs && node scripts/stage-blueprint-shelf.mjs && node scripts/validate-releases.mjs",
52
- "rcf": "node bin/rcf.js"
53
- },
54
42
  "repository": {
55
43
  "type": "git",
56
44
  "url": "git+https://github.com/Stravica/rcf-lite.git",
@@ -72,7 +60,7 @@
72
60
  "#admissibility": "./src/admissibility/index.js"
73
61
  },
74
62
  "dependencies": {
75
- "@stravica-ai/rcf-schemas": "0.5.0",
63
+ "@stravica-ai/rcf-schemas": "0.5.1",
76
64
  "ajv": "^8.20.0",
77
65
  "ajv-formats": "^3.0.1"
78
66
  },
@@ -80,5 +68,15 @@
80
68
  "@modelcontextprotocol/sdk": "^1.29.0",
81
69
  "js-yaml": "^4.1.0",
82
70
  "mermaid": "11.6.0"
71
+ },
72
+ "scripts": {
73
+ "test": "node --test --test-concurrency=1 'test/**/*.test.js'",
74
+ "vendor": "node scripts/vendor-mermaid.mjs",
75
+ "build:managed": "node scripts/gen-managed-artefacts.mjs",
76
+ "canary:register": "node scripts/canary-register.mjs",
77
+ "validate:releases": "node scripts/validate-releases.mjs",
78
+ "stage:blueprints": "node scripts/stage-blueprint-shelf.mjs",
79
+ "preinstall": "node scripts/preinstall-node-check.mjs",
80
+ "rcf": "node bin/rcf.js"
83
81
  }
84
- }
82
+ }
@@ -40,8 +40,28 @@
40
40
  # `npm install rcf-lite`.
41
41
 
42
42
  feedVersion: 1
43
- latest: "0.14.0"
43
+ latest: "0.16.0"
44
44
  releases:
45
+ - version: "0.16.0"
46
+ date: "2026-09-03"
47
+ breaking: false
48
+ headlines:
49
+ - "External blueprint libraries now support git and tarball sources with an on-disk cache checked into the tree, plus a refresh command that re-resolves a library's pinned reference and refuses the update on drift."
50
+ - "Adding a blueprint from a local path inside an unregistered library now stamps the same slug and identity a registered add would, closing the author-and-test-locally loop."
51
+ - "The managed agent-instructions block now teaches the agent to offer blueprints and registered libraries in plain words so the operator picks; a new library authoring standard doc and worked-example fixture ship with the package. Rerun rcf init to refresh the block."
52
+ minAgentAction: "rerun-init"
53
+ notesUrl: "https://stravica.ai/docs/rcf/changelog/"
54
+
55
+ - version: "0.15.0"
56
+ date: "2026-08-31"
57
+ breaking: false
58
+ headlines:
59
+ - "Library-applied blueprints now carry a durable ownership stamp so re-registering a library under a different prefix no longer orphans records that came from it."
60
+ - "The review-on-add card gained a global-topics section and a prefix-check line so a library's cross-topic surface is visible before the operator commits."
61
+ - "The delivery-ci-workflows blueprint gained optional package-manager and branch-name shape fields, taught the coverage-strict bootstrap trap in the guide, and shipped a starting-point supersession ADR."
62
+ minAgentAction: null
63
+ notesUrl: "https://stravica.ai/docs/rcf/changelog/"
64
+
45
65
  - version: "0.14.0"
46
66
  date: "2026-08-31"
47
67
  breaking: true
@@ -49,6 +49,13 @@ import { nextResolutionId } from './resolutions.js';
49
49
  * record, so `rcf define blueprint remove wsd-auth-oauth2` reads back
50
50
  * cleanly. `namespaceOverride` still wins over `effectiveSlug` if both
51
51
  * are set (operator explicitly chose a different namespace).
52
+ * @param {string} [args.libraryPrefix] - the registered library prefix the
53
+ * blueprint was resolved through (spec §5.3, §7.3). When set the applied
54
+ * record carries a `libraryPrefix` field so `rcf library remove`'s
55
+ * ownership check reads the ownership fact off the record itself
56
+ * rather than string-matching `source`. Absent for shelf and path
57
+ * applies. Requires @stravica-ai/rcf-schemas 0.5.1 or later
58
+ * (`appliedBlueprintRecord.libraryPrefix`, additive optional).
52
59
  * @param {{ ac: { start: number, end: number }, suffixBlocks?: Array<{ kind: string, start: number, end: number }> }} [args.libraryBands]
53
60
  * Declared bands from the resolved library. When set, every stamped
54
61
  * contribution is band-gated before write; a contribution whose numeric
@@ -72,7 +79,7 @@ import { nextResolutionId } from './resolutions.js';
72
79
  * prove the rollback runs. Never used in production.
73
80
  * @returns {Promise<ApplyResult | import('../core/errors/index.js').RcfError>}
74
81
  */
75
- export async function applyBlueprint({ projectRoot, tree, source, displaySource, namespaceOverride, effectiveSlug, libraryBands, resolveDeclarations, now = new Date(), dryRun = false, _copyFileForTest }) {
82
+ export async function applyBlueprint({ projectRoot, tree, source, displaySource, namespaceOverride, effectiveSlug, libraryPrefix, libraryBands, resolveDeclarations, now = new Date(), dryRun = false, _copyFileForTest }) {
76
83
  const hintSource = typeof displaySource === 'string' && displaySource.length > 0 ? displaySource : source;
77
84
  const blueprint = await loadBlueprint(source);
78
85
  if (blueprint.kind) return blueprint; // RcfError
@@ -256,11 +263,12 @@ export async function applyBlueprint({ projectRoot, tree, source, displaySource,
256
263
  // carries the qualified typed ref for library-resolved blueprints
257
264
  // (`wsd:auth-oauth2`) so `rcf define blueprint upgrade` reads back
258
265
  // cleanly (spec §5.3); local-path applies carry the absolute path as
259
- // today. The optional `libraryPrefix` field named in spec §5.3 is a
260
- // schema-additive change on `appliedBlueprintRecord` (the record's
261
- // schema is `additionalProperties: false`); it is deferred and
262
- // tracked in the PR description as a follow-up so the shape lands as
263
- // a coordinated schemas + rcf-lite bump.
266
+ // today. When the apply resolved through a registered external
267
+ // library the record additionally carries `libraryPrefix`: the
268
+ // ownership fact for the library-registered ownership check in
269
+ // `rcf library remove`, so the registry may be edited (renamed,
270
+ // re-pointed, unregistered) without orphaning previously applied
271
+ // records. Shelf and path applies carry no `libraryPrefix`.
264
272
  const recordSource = typeof displaySource === 'string' && displaySource.length > 0 && displaySource !== source
265
273
  ? displaySource
266
274
  : source;
@@ -270,6 +278,7 @@ export async function applyBlueprint({ projectRoot, tree, source, displaySource,
270
278
  appliedAt: now.toISOString(),
271
279
  source: recordSource,
272
280
  ...(namespaceOverride ? { namespace: namespaceOverride } : {}),
281
+ ...(typeof libraryPrefix === 'string' && libraryPrefix.length > 0 ? { libraryPrefix } : {}),
273
282
  ...(writtenContributions.length > 0 ? { contributions: writtenContributions } : {}),
274
283
  };
275
284
  const manifestResult = await updateManifest({
@@ -23,3 +23,25 @@ export {
23
23
  detectContributionsOutOfBand,
24
24
  detectPrefixCollision,
25
25
  } from './library-registry.js';
26
+ export {
27
+ CACHE_ROOT,
28
+ absoluteCachePath,
29
+ ensureEmptyCache,
30
+ relativeCachePath,
31
+ removeCache,
32
+ resolveCachePath,
33
+ sanitiseRef,
34
+ } from './library-cache.js';
35
+ export {
36
+ fetchGitLibrary,
37
+ parseGitRef,
38
+ resolveRemoteSha,
39
+ isFullSha,
40
+ refusedRefs,
41
+ } from './library-fetcher-git.js';
42
+ export {
43
+ createUstarBuffer,
44
+ fetchTarballLibrary,
45
+ parseUstar,
46
+ sha256Hex,
47
+ } from './library-fetcher-tarball.js';
@@ -0,0 +1,143 @@
1
+ // External-library on-disk cache helpers (spec §4.4).
2
+ //
3
+ // Fetched library content sits under
4
+ // `rcf/.blueprint-libraries/<libraryPrefix>/<libraryRef>/`, checked
5
+ // into git as ordinary tree content so a fresh clone can `rcf define
6
+ // blueprint list` without a re-fetch. The cache is the working root
7
+ // the resolver reads through; the `cachePath` field on every registry
8
+ // entry points at it.
9
+ //
10
+ // This module owns path computation and a small set of directory
11
+ // primitives shared by the git and tarball fetchers (Phase 2c). It
12
+ // deliberately does NOT know about git or tar; the fetchers layer on
13
+ // top and land their extracted content at the paths computed here.
14
+
15
+ import { existsSync } from 'node:fs';
16
+ import { mkdir, rm, stat } from 'node:fs/promises';
17
+ import { isAbsolute, join, resolve } from 'node:path';
18
+
19
+ import { rcfError } from '../core/errors/index.js';
20
+
21
+ export const CACHE_ROOT = 'rcf/.blueprint-libraries';
22
+
23
+ /**
24
+ * Repo-relative cache path for a library at a given ref. Repo-relative
25
+ * because that is what the registry stores (`entry.cachePath`); the
26
+ * resolver joins it against `projectRoot` at read time.
27
+ *
28
+ * @param {string} libraryPrefix
29
+ * @param {string} libraryRef
30
+ * @returns {string}
31
+ */
32
+ export function relativeCachePath(libraryPrefix, libraryRef) {
33
+ return `${CACHE_ROOT}/${libraryPrefix}/${sanitiseRef(libraryRef)}`;
34
+ }
35
+
36
+ /**
37
+ * Absolute cache path for a library at a given ref, joined against a
38
+ * project root.
39
+ *
40
+ * @param {string} projectRoot
41
+ * @param {string} libraryPrefix
42
+ * @param {string} libraryRef
43
+ * @returns {string}
44
+ */
45
+ export function absoluteCachePath(projectRoot, libraryPrefix, libraryRef) {
46
+ const rel = relativeCachePath(libraryPrefix, libraryRef);
47
+ return isAbsolute(rel) ? rel : join(projectRoot, rel);
48
+ }
49
+
50
+ /**
51
+ * Prepare an empty cache directory. Refuses when a non-empty cache
52
+ * already exists at the target (the caller must remove it first via
53
+ * `removeCache`, or fail fast and prompt the operator). This keeps
54
+ * fetches deterministic: content lands on a clean slate every time.
55
+ *
56
+ * @param {string} absPath
57
+ * @param {object} [opts]
58
+ * @param {boolean} [opts.replace=false] - when true, remove any
59
+ * pre-existing content at absPath before creating the fresh dir.
60
+ * @returns {Promise<null | import('../core/errors/index.js').RcfError>}
61
+ */
62
+ export async function ensureEmptyCache(absPath, opts = {}) {
63
+ try {
64
+ if (existsSync(absPath)) {
65
+ if (opts.replace === true) {
66
+ await rm(absPath, { recursive: true, force: true });
67
+ } else {
68
+ const s = await stat(absPath);
69
+ if (s.isDirectory()) {
70
+ return rcfError({
71
+ kind: 'usage',
72
+ message: `library cache: path already exists at ${absPath}. Remove it or run 'library refresh' to re-fetch.`,
73
+ filePath: absPath,
74
+ });
75
+ }
76
+ return rcfError({
77
+ kind: 'usage',
78
+ message: `library cache: non-directory blocks cache path ${absPath}.`,
79
+ filePath: absPath,
80
+ });
81
+ }
82
+ }
83
+ await mkdir(absPath, { recursive: true });
84
+ return null;
85
+ } catch (err) {
86
+ return rcfError({
87
+ kind: 'ioFailure',
88
+ message: `library cache: could not prepare ${absPath}: ${err.message}`,
89
+ filePath: absPath,
90
+ stack: err.stack,
91
+ });
92
+ }
93
+ }
94
+
95
+ /**
96
+ * Recursive remove of a cache directory. No-op when the path does not
97
+ * exist. Used by `library remove` and by the fetchers' rollback path.
98
+ *
99
+ * @param {string} absPath
100
+ * @returns {Promise<null | import('../core/errors/index.js').RcfError>}
101
+ */
102
+ export async function removeCache(absPath) {
103
+ try {
104
+ await rm(absPath, { recursive: true, force: true });
105
+ return null;
106
+ } catch (err) {
107
+ return rcfError({
108
+ kind: 'ioFailure',
109
+ message: `library cache: could not remove ${absPath}: ${err.message}`,
110
+ filePath: absPath,
111
+ stack: err.stack,
112
+ });
113
+ }
114
+ }
115
+
116
+ /**
117
+ * Resolve an absolute cache path from either an absolute
118
+ * `entry.cachePath` (legacy local-source entries in phase 2b stored
119
+ * the library root path here) or a repo-relative one (phase 2c network
120
+ * fetches store the relative form; the resolver joins with the project
121
+ * root at read time).
122
+ *
123
+ * @param {string} projectRoot
124
+ * @param {string} cachePath
125
+ * @returns {string}
126
+ */
127
+ export function resolveCachePath(projectRoot, cachePath) {
128
+ return isAbsolute(cachePath) ? cachePath : resolve(projectRoot, cachePath);
129
+ }
130
+
131
+ /**
132
+ * Replace path-hostile characters in a libraryRef so it can be used as
133
+ * a directory segment. Refs are already semver-ish or short tag names
134
+ * in practice; this guards against a publisher who ships a ref like
135
+ * `1.2.0/rc1` or an operator who hand-types one. Slashes and colons are
136
+ * mapped to a single `-` so the on-disk layout stays flat.
137
+ *
138
+ * @param {string} ref
139
+ * @returns {string}
140
+ */
141
+ export function sanitiseRef(ref) {
142
+ return String(ref).replace(/[\\/:*?"<>|]+/g, '-');
143
+ }