rcf-lite 0.15.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.
- package/CHANGELOG.md +13 -0
- package/fixtures/canary-manifest.json +101 -1
- package/guidance/harness-template.md +9 -0
- package/guidance/managed/agent-instructions-block.hash +1 -1
- package/guidance/managed/agent-instructions-block.md +9 -0
- package/package.json +1 -1
- package/releases/releases.yaml +11 -1
- package/src/blueprint/index.js +22 -0
- package/src/blueprint/library-cache.js +143 -0
- package/src/blueprint/library-fetcher-git.js +347 -0
- package/src/blueprint/library-fetcher-tarball.js +379 -0
- package/src/blueprint/shelf-resolver.js +100 -7
- package/src/cli/blueprint-library.js +397 -80
- package/src/cli/blueprint.js +1 -0
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,19 @@ All notable changes to this project are documented in this file.
|
|
|
4
4
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). Pre-1.0, breaking changes are signalled by a minor version bump.
|
|
6
6
|
|
|
7
|
+
## [Unreleased]
|
|
8
|
+
|
|
9
|
+
## [0.16.0] - 2026-09-03
|
|
10
|
+
|
|
11
|
+
External blueprint libraries complete. Phase 2c lands git and tarball fetchers with a checked-in on-disk cache so a fresh clone lists library blueprints without a re-fetch, and a refresh path that re-resolves annotated-tag or tarball digest and refuses on drift. Amendment A2 makes plain-path adds library-aware so an author's local edit loop stamps the same effective slug and identity a qualified add would. Amendment A3 adds a blueprint paragraph to the managed agent-instructions block so the agent conversationally offers blueprints and registered libraries and the operator chooses. A library-authoring standard doc plus a worked-example fixture binds the doc against the shipped tooling.
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- **External blueprint libraries phase 2c: git and tarball fetchers + on-disk cache.** `rcf define blueprint library add` now accepts `git+<url>#<annotated-tag-or-sha>` and tarball URLs (with `--sha256 <hex>` for the pin). Fetched content lands under `rcf/.blueprint-libraries/<libraryPrefix>/<libraryRef>/`, checked into git as ordinary tree content so a fresh clone can `rcf define blueprint list` without a re-fetch (spec section 4.4). Floating branches (`main`, `master`, `HEAD`, `latest`, `develop`, `trunk`) refuse categorically; lightweight tags refuse with a diagnostic pointing at annotation as the fix; a tarball digest mismatch refuses the fetch and writes nothing. `rcf define blueprint library refresh <prefix>` re-resolves the tag's peeled commit (git) or re-downloads and re-verifies the digest (tarball) and refuses on drift per spec sections 6.4 / 9.12. `rcf define blueprint library remove <prefix>` now drops the on-disk cache for network sources. Zero new runtime dependencies: the git side shells out to the ambient `git` CLI; the tarball side uses built-in `fetch()`, `zlib`, and a bundled minimal POSIX-ustar parser. Auth remains out of scope for v1 (spec section 9.4): ambient git access is the model, no-access is definitive.
|
|
16
|
+
- **Amendment A2: library-aware local path (2026-09-03).** `rcf define blueprint add <path>` now walks up from the target directory looking for `library.json`; on a hit the resolver requires the target to sit at `<library-root>/blueprints/<slug>` and stamps the same effective slug and identity a qualified `<prefix>:<slug>` add would after registration. Library-qualified addressing and plain-path addressing now produce identical slugs and stamped ids. A path with no `library.json` in any ancestor keeps the phase-1 route unchanged, byte-for-byte. The trade-off (spec: an unregistered local library bypasses review-on-add) is accepted because the alternative kills the "author now, test now" loop; the `local` provenance warning on apply is the mitigation.
|
|
17
|
+
- **Amendment A3: blueprint paragraph in the managed agent-instructions block (2026-09-03).** New paragraph inside the "How to talk to your operator" section teaches the plain-language shape: agent enumerates blueprints and registered libraries and offers one that fits; the operator chooses, the agent never picks; qualified names and command lines belong in files and `rcf` output, not in the conversation. Hash regenerated via `scripts/gen-managed-artefacts.mjs`; harness template mirrored automatically.
|
|
18
|
+
- **Library authoring standard doc.** `packages/rcf-lite/docs/library-authoring.md` documents the library shape, `library.json` field contract, prefix and band rules, the local-path authoring loop (A2), registration, refresh / drift, and interaction with core. Ships with a minimum-viable worked-example library at `test/fixtures/library-authoring-example/` (prefix `wla`, one blueprint with one REQ and one ADR) that doubles as the fixture body for `test/blueprint/library-authoring-example.test.js`; the fixture binds the doc against the shipped tooling so a drift in either trips the suite.
|
|
19
|
+
|
|
7
20
|
## [0.15.0] - 2026-08-31
|
|
8
21
|
|
|
9
22
|
Same-evening fix wave on top of 0.14.0. Four PRs closing off the WSD-handover surface: durable library-ownership stamping on applied records (paired with `@stravica-ai/rcf-schemas` 0.5.1), `blueprint supersede --incoming` accepting the same source forms as `add`, the review-on-add card gaining its section 8.1 global-topics and prefix-check lines, and the `delivery-ci-workflows` v2.1 additive shape resolving the four P1 findings from the second dogfood run.
|
|
@@ -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
|
-
|
|
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.
|
|
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",
|
package/releases/releases.yaml
CHANGED
|
@@ -40,8 +40,18 @@
|
|
|
40
40
|
# `npm install rcf-lite`.
|
|
41
41
|
|
|
42
42
|
feedVersion: 1
|
|
43
|
-
latest: "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
|
+
|
|
45
55
|
- version: "0.15.0"
|
|
46
56
|
date: "2026-08-31"
|
|
47
57
|
breaking: false
|
package/src/blueprint/index.js
CHANGED
|
@@ -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
|
+
}
|