rcf-lite 0.8.0 → 0.10.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 (138) hide show
  1. package/CHANGELOG.md +124 -51
  2. package/README.md +8 -4
  3. package/bin/rcf.js +147 -53
  4. package/fixtures/canary-manifest.json +9 -9
  5. package/guidance/README.md +1 -1
  6. package/guidance/build-cycle-playbook.md +51 -51
  7. package/guidance/build-cycle.md +7 -7
  8. package/guidance/document-model.md +1 -1
  9. package/guidance/elicitation-playbook.md +29 -29
  10. package/guidance/harness-template.md +21 -10
  11. package/guidance/managed/README.md +1 -1
  12. package/guidance/managed/agent-instructions-block.hash +1 -1
  13. package/guidance/managed/agent-instructions-block.md +20 -9
  14. package/guidance/manifest.json +1 -1
  15. package/guidance/overview.md +4 -4
  16. package/package.json +5 -7
  17. package/rcf/adrs/adr-008.json +1 -1
  18. package/rcf/adrs/adr-009.json +4 -4
  19. package/rcf/adrs/adr-010.json +30 -0
  20. package/rcf/code-nodes/cn-016.json +1 -1
  21. package/rcf/code-nodes/cn-019.json +1 -1
  22. package/rcf/code-nodes/cn-020.json +1 -1
  23. package/rcf/code-nodes/cn-021.json +1 -1
  24. package/rcf/code-nodes/cn-022.json +1 -1
  25. package/rcf/code-nodes/cn-049.json +1 -1
  26. package/rcf/code-nodes/cn-055.json +1 -1
  27. package/rcf/code-nodes/cn-057.json +5 -5
  28. package/rcf/code-nodes/cn-058.json +18 -0
  29. package/rcf/code-nodes/cn-059.json +14 -0
  30. package/rcf/code-nodes/cn-060.json +14 -0
  31. package/rcf/code-nodes/cn-061.json +14 -0
  32. package/rcf/code-nodes/cn-062.json +14 -0
  33. package/rcf/code-nodes/cn-063.json +15 -0
  34. package/rcf/code-nodes/cn-064.json +15 -0
  35. package/rcf/code-nodes/cn-065.json +16 -0
  36. package/rcf/code-nodes/cn-066.json +14 -0
  37. package/rcf/code-nodes/cn-067.json +15 -0
  38. package/rcf/code-nodes/cn-068.json +15 -0
  39. package/rcf/code-nodes/cn-069.json +16 -0
  40. package/rcf/fbs/fbs-005.json +1 -1
  41. package/rcf/fbs/fbs-006.json +2 -2
  42. package/rcf/fbs/fbs-007.json +1 -1
  43. package/rcf/fbs/fbs-014.json +1 -1
  44. package/rcf/fbs/fbs-015.json +9 -8
  45. package/rcf/fbs/fbs-016.json +39 -0
  46. package/rcf/fbs/fbs-017.json +40 -0
  47. package/rcf/fbs/fbs-018.json +34 -0
  48. package/rcf/fbs/fbs-019.json +33 -0
  49. package/rcf/requirements/req-008.json +1 -1
  50. package/rcf/requirements/req-009.json +2 -2
  51. package/rcf/requirements/req-010.json +20 -0
  52. package/rcf/test-suites/PENDING.md +2 -2
  53. package/rcf/test-suites/ts-004.json +1 -1
  54. package/rcf/test-suites/ts-006.json +3 -3
  55. package/rcf/test-suites/ts-008.json +2 -2
  56. package/rcf/test-suites/ts-009.json +2 -2
  57. package/rcf/test-suites/ts-017.json +1 -1
  58. package/rcf/test-suites/ts-024.json +1 -1
  59. package/rcf/test-suites/ts-025.json +32 -18
  60. package/rcf/test-suites/ts-026.json +54 -0
  61. package/rcf/test-suites/ts-027.json +115 -0
  62. package/rcf/test-suites/ts-028.json +46 -0
  63. package/rcf/test-suites/ts-029.json +46 -0
  64. package/rcf/user-stories/us-1001.json +56 -0
  65. package/rcf/user-stories/us-1002.json +96 -0
  66. package/rcf/user-stories/us-1003.json +48 -0
  67. package/rcf/user-stories/us-1004.json +48 -0
  68. package/rcf/user-stories/us-805.json +2 -2
  69. package/rcf/user-stories/us-901.json +13 -13
  70. package/src/blueprint/apply.js +464 -0
  71. package/src/blueprint/conflicts.js +351 -0
  72. package/src/blueprint/diff.js +82 -0
  73. package/src/blueprint/index.js +12 -0
  74. package/src/blueprint/list.js +21 -0
  75. package/src/blueprint/loader.js +163 -0
  76. package/src/blueprint/manifest-writer.js +49 -0
  77. package/src/blueprint/namespace.js +145 -0
  78. package/src/blueprint/remove.js +105 -0
  79. package/src/blueprint/resolutions.js +83 -0
  80. package/src/blueprint/standards.js +148 -0
  81. package/src/blueprint/supersede.js +318 -0
  82. package/src/browser-verify/invariants.js +33 -6
  83. package/src/build/bundle.js +37 -14
  84. package/src/build/formatters/markdown.js +9 -9
  85. package/src/build/mark.js +3 -3
  86. package/src/build/queue.js +1 -1
  87. package/src/build/standards-selector.js +52 -0
  88. package/src/cli/blueprint.js +325 -0
  89. package/src/cli/browser-verify.js +1 -1
  90. package/src/cli/build.js +139 -69
  91. package/src/cli/coverage.js +1 -1
  92. package/src/cli/create.js +48 -3
  93. package/src/cli/delete.js +2 -2
  94. package/src/cli/design.js +10 -10
  95. package/src/cli/fbs.js +1 -1
  96. package/src/cli/finalise.js +22 -20
  97. package/src/cli/help.js +269 -89
  98. package/src/cli/impact.js +1 -1
  99. package/src/cli/init.js +20 -5
  100. package/src/cli/intake.js +3 -3
  101. package/src/cli/link.js +3 -3
  102. package/src/cli/preflight.js +2 -2
  103. package/src/cli/read.js +1 -1
  104. package/src/cli/req-baseline.js +2 -2
  105. package/src/cli/req-classify.js +3 -3
  106. package/src/cli/review.js +1 -1
  107. package/src/cli/standards.js +127 -0
  108. package/src/cli/test-suite.js +1 -1
  109. package/src/cli/trace.js +1 -1
  110. package/src/cli/ui-baseline.js +3 -3
  111. package/src/cli/ui-classify.js +4 -4
  112. package/src/cli/update.js +2 -2
  113. package/src/cli/validate.js +2 -2
  114. package/src/cli/view.js +12 -10
  115. package/src/core/store/ids.js +168 -18
  116. package/src/core/store/loader.js +27 -16
  117. package/src/core/store/walker.js +27 -15
  118. package/src/core/store/writer.js +1 -1
  119. package/src/deployment/index.js +13 -0
  120. package/src/deployment/placeholder-detector.js +113 -0
  121. package/src/design/writer.js +3 -3
  122. package/src/finalise/detect.js +32 -38
  123. package/src/finalise/index.js +0 -1
  124. package/src/finalise/install.js +9 -8
  125. package/src/finalise/spawn.js +14 -10
  126. package/src/mcp/tools.js +1 -1
  127. package/src/query/formatters/table.js +7 -10
  128. package/src/query/trace.js +45 -4
  129. package/src/req-baseline/gate.js +1 -1
  130. package/src/ui-baseline/manifest-writer.js +2 -2
  131. package/src/verify/cli/cleanup.js +1 -1
  132. package/src/verify/cli/mcp.js +1 -1
  133. package/src/verify/cli/provision.js +1 -1
  134. package/src/verify/cli/report.js +1 -1
  135. package/src/verify/cli/run.js +1 -1
  136. package/src/view-supervisor/manifest-writer.js +2 -2
  137. package/bin/rcf-verify.js +0 -122
  138. package/src/verify/cli/help.js +0 -56
@@ -0,0 +1,325 @@
1
+ // `rcf blueprint <verb>` CLI landing.
2
+ //
3
+ // Verbs:
4
+ // add apply a blueprint (with optional --resolve)
5
+ // list projection over manifest.blueprints[]
6
+ // remove remove an applied blueprint
7
+ // supersede scaffold a project ADR + record a resolutions[] entry
8
+ // diff side-by-side view of applied blueprints' scope:global
9
+ // ADRs on a topic
10
+
11
+ import { parseArgs } from 'node:util';
12
+
13
+ import { isRcfError } from '#core/errors';
14
+ import { walkTree } from '#core/store';
15
+ import { findProjectRoot } from '../view/index.js';
16
+ import {
17
+ applyBlueprint,
18
+ diffBlueprintTopic,
19
+ listBlueprints,
20
+ removeBlueprint,
21
+ renderDiff,
22
+ supersedeBlueprintTopic,
23
+ } from '../blueprint/index.js';
24
+ import { conflictReportJson, renderConflictReport } from '../blueprint/conflicts.js';
25
+
26
+ export const HELP = `Usage: rcf define blueprint <verb> [options]
27
+
28
+ Verbs:
29
+ add <source> Apply a blueprint from a source directory
30
+ (Phase 1: local path; the registry / git-ref
31
+ resolver is a Phase 2 concern). Writes an entry
32
+ to manifest.blueprints[] and copies namespaced
33
+ contributions into the tree.
34
+ list List every applied blueprint (slug, version,
35
+ appliedAt, contributionCount).
36
+ remove <slug> Remove an applied blueprint. Refuses when any
37
+ project-authored doc references a contribution
38
+ id; prints the referring docs and exits 3.
39
+ supersede <topic> [--incoming <source>]
40
+ Author a project-level ADR that supersedes the
41
+ conflict pair on <topic> (one applied blueprint
42
+ ADR + one incoming blueprint ADR named via
43
+ --incoming <source>), and record a
44
+ manifest.resolutions[] entry so the conflict
45
+ detector honours the resolution when the
46
+ operator re-runs \`rcf define blueprint add <source>\`.
47
+ --incoming is required when the topic has fewer
48
+ than two applied scope:global ADRs (the
49
+ refused-add state) and is silently accepted
50
+ when already >= 2 are applied. Both blueprint
51
+ ADRs co-reside on disk as superseded history.
52
+ diff <topic> Side-by-side view of every applied blueprint's
53
+ scope:global ADR on <topic>: id, path, title,
54
+ status, decision. Read-only.
55
+
56
+ Options:
57
+ --namespace <slug> Override the blueprint's default namespace
58
+ (defaults to the blueprint's slug).
59
+ --resolve <t=project:ADR-id>
60
+ (add only) Declare a resolution on this add.
61
+ Repeatable per conflicted topic. Records a
62
+ manifest.resolutions[] entry before conflict
63
+ detection runs, so a would-be conflict on the
64
+ topic is honoured. resolvedByAdrId must be a
65
+ well-formed ADR id; the referenced ADR should
66
+ already exist on the project (this verb does
67
+ not scaffold one -- use \`supersede\` for that).
68
+ --reason <text> (supersede, add --resolve) Optional operator
69
+ note attached to the manifest.resolutions[]
70
+ record.
71
+ --json (add only) Emit the result (or conflict
72
+ report) as a machine-readable JSON object.
73
+ Exit code is unchanged (0 on apply, 3 on
74
+ conflict).
75
+ --dry-run Print intended writes without executing.
76
+ --quiet Suppress non-error stdout.
77
+ --help Print this help.
78
+
79
+ Composition and namespacing:
80
+
81
+ Blueprint-contributed doc ids are namespaced by the blueprint's slug.
82
+ REQ / US / PRD / BS / TAD / TS: slug PREFIX (spa-REQ-001).
83
+ ADR / TAC / FBS / CN: slug SUFFIX (ADR-005-spa).
84
+ Two blueprints both contributing a scope:global ADR on the same topic
85
+ is a genuine conflict: rcf define blueprint add refuses and prints both
86
+ sides, plus four resolution paths (adopt incoming, keep existing,
87
+ supersede via project ADR, or declare on the add itself via
88
+ --resolve).
89
+ `;
90
+
91
+ const OPTION_SPEC = {
92
+ namespace: { type: 'string' },
93
+ resolve: { type: 'string', multiple: true },
94
+ reason: { type: 'string' },
95
+ incoming: { type: 'string' },
96
+ json: { type: 'boolean' },
97
+ 'dry-run': { type: 'boolean' },
98
+ quiet: { type: 'boolean' },
99
+ help: { type: 'boolean' },
100
+ };
101
+
102
+ /**
103
+ * @param {string[]} argv - argv slice after `blueprint`
104
+ * @param {object} [deps]
105
+ * @returns {Promise<number>}
106
+ */
107
+ export async function main(argv, deps = {}) {
108
+ const stdout = deps.stdout ?? process.stdout;
109
+ const stderr = deps.stderr ?? process.stderr;
110
+ const cwd = deps.cwd ?? process.cwd();
111
+ const now = deps.now ?? new Date();
112
+
113
+ let parsed;
114
+ try {
115
+ parsed = parseArgs({ args: argv, options: OPTION_SPEC, allowPositionals: true, strict: true });
116
+ } catch (err) {
117
+ stderr.write(`[error] ${err.message}\n`);
118
+ stderr.write(HELP);
119
+ return 2;
120
+ }
121
+ if (parsed.values.help || parsed.positionals.length === 0) {
122
+ stdout.write(HELP);
123
+ return 0;
124
+ }
125
+ const verb = parsed.positionals[0];
126
+ const rest = parsed.positionals.slice(1);
127
+
128
+ const projectRoot = await findProjectRoot(cwd);
129
+ if (!projectRoot) {
130
+ stderr.write('[error] no rcf/ tree found in this directory or any ancestor.\n');
131
+ return 2;
132
+ }
133
+ const { tree, errors } = await walkTree({ projectRoot });
134
+ if (errors.length > 0 && verb !== 'list') {
135
+ // Tree errors are non-fatal for list; every other verb needs a clean tree.
136
+ for (const e of errors) stderr.write(`[tree] ${e.kind}: ${e.message}\n`);
137
+ return 2;
138
+ }
139
+
140
+ if (verb === 'add') {
141
+ if (rest.length === 0) {
142
+ stderr.write('[error] blueprint add: missing <source>\n');
143
+ return 2;
144
+ }
145
+ const source = rest[0];
146
+ const resolveDeclarations = parseResolveOptions(parsed.values.resolve, parsed.values.reason);
147
+ if (resolveDeclarations.error) {
148
+ stderr.write(`[error] blueprint add: ${resolveDeclarations.error}\n`);
149
+ return 2;
150
+ }
151
+ const result = await applyBlueprint({
152
+ projectRoot, tree, source,
153
+ namespaceOverride: parsed.values.namespace,
154
+ resolveDeclarations: resolveDeclarations.value,
155
+ now,
156
+ dryRun: parsed.values['dry-run'] === true,
157
+ });
158
+ // Surface any writer-side warnings (currently only
159
+ // duplicate-topic --resolve dedupe). Warnings do not change the
160
+ // exit code; they land on stderr so the human sees them alongside
161
+ // the applied line on stdout.
162
+ if (result && !isRcfError(result) && Array.isArray(result.warnings)) {
163
+ for (const w of result.warnings) {
164
+ if (w.kind === 'duplicateResolveTopic' && Array.isArray(w.topics)) {
165
+ for (const t of w.topics) {
166
+ stderr.write(`[warn] blueprint add: duplicate --resolve for topic '${t}'; keeping the first declaration only.\n`);
167
+ }
168
+ }
169
+ }
170
+ }
171
+ if (isRcfError(result)) {
172
+ if (parsed.values.json) {
173
+ stderr.write(`${JSON.stringify({ refused: true, error: { kind: result.kind, message: result.message } })}\n`);
174
+ } else {
175
+ stderr.write(`[error] blueprint add: ${result.message}\n`);
176
+ }
177
+ return 2;
178
+ }
179
+ if (result.conflicts && result.conflicts.length > 0) {
180
+ if (parsed.values.json) {
181
+ stdout.write(`${JSON.stringify(conflictReportJson(result.conflicts), null, 2)}\n`);
182
+ } else {
183
+ stderr.write(renderConflictReport(result.conflicts));
184
+ }
185
+ return 3;
186
+ }
187
+ if (parsed.values.json) {
188
+ stdout.write(`${JSON.stringify({
189
+ refused: false,
190
+ applied: result.applied === true,
191
+ alreadyApplied: result.alreadyApplied === true,
192
+ slug: result.slug,
193
+ version: result.version,
194
+ contributionCount: Array.isArray(result.contributions) ? result.contributions.length : 0,
195
+ })}\n`);
196
+ return 0;
197
+ }
198
+ if (result.alreadyApplied) {
199
+ if (!parsed.values.quiet) stdout.write(`[blueprint] '${result.slug}' already applied at ${result.version}; no changes.\n`);
200
+ return 0;
201
+ }
202
+ if (!parsed.values.quiet) {
203
+ stdout.write(`[blueprint] applied '${result.slug}' at ${result.version} (${result.contributions.length} contribution(s)).\n`);
204
+ }
205
+ return 0;
206
+ }
207
+
208
+ if (verb === 'list') {
209
+ const rows = listBlueprints(tree);
210
+ if (rows.length === 0) {
211
+ if (!parsed.values.quiet) stdout.write('[blueprint] no blueprints applied on this project.\n');
212
+ return 0;
213
+ }
214
+ for (const row of rows) {
215
+ stdout.write(`${row.slug}\t${row.version}\t${row.appliedAt}\t${row.contributionCount} contribution(s)\n`);
216
+ }
217
+ return 0;
218
+ }
219
+
220
+ if (verb === 'remove') {
221
+ if (rest.length === 0) {
222
+ stderr.write('[error] blueprint remove: missing <slug>\n');
223
+ return 2;
224
+ }
225
+ const slug = rest[0];
226
+ const result = await removeBlueprint({
227
+ projectRoot, tree, slug, dryRun: parsed.values['dry-run'] === true,
228
+ });
229
+ if (isRcfError(result)) {
230
+ stderr.write(`[error] blueprint remove: ${result.message}\n`);
231
+ return 2;
232
+ }
233
+ if (!result.removed) {
234
+ stderr.write(`[blueprint] remove refused: ${result.referringDocs.length} referring doc(s):\n`);
235
+ for (const r of result.referringDocs) {
236
+ stderr.write(` ${r.docId} references ${r.matchedId}\n`);
237
+ }
238
+ stderr.write('resolve by unbinding the references, then re-run.\n');
239
+ return 3;
240
+ }
241
+ if (!parsed.values.quiet) stdout.write(`[blueprint] removed '${result.slug}' (${result.deletedPaths.length} file(s) deleted).\n`);
242
+ return 0;
243
+ }
244
+
245
+ if (verb === 'supersede') {
246
+ if (rest.length === 0) {
247
+ stderr.write('[error] blueprint supersede: missing <topic>\n');
248
+ return 2;
249
+ }
250
+ const topic = rest[0];
251
+ const result = await supersedeBlueprintTopic({
252
+ projectRoot, tree, topic,
253
+ incomingSource: parsed.values.incoming,
254
+ now,
255
+ dryRun: parsed.values['dry-run'] === true,
256
+ reason: parsed.values.reason,
257
+ });
258
+ if (isRcfError(result)) {
259
+ stderr.write(`[error] blueprint supersede: ${result.message}\n`);
260
+ return 2;
261
+ }
262
+ if (!parsed.values.quiet) {
263
+ stdout.write(`[blueprint] superseded topic '${result.topic}' via ${result.resolvedByAdrId} at ${result.resolvedByAdrPath}.\n`);
264
+ stdout.write(`[blueprint] resolution recorded as ${result.resolutionId}; superseded: ${result.supersedes.map((s) => `${s.adrId} (blueprint ${s.slug})`).join(', ')}.\n`);
265
+ stdout.write(`[blueprint] edit ${result.resolvedByAdrPath} to fill out the operator's ruling context / decision / consequences.\n`);
266
+ }
267
+ return 0;
268
+ }
269
+
270
+ if (verb === 'diff') {
271
+ if (rest.length === 0) {
272
+ stderr.write('[error] blueprint diff: missing <topic>\n');
273
+ return 2;
274
+ }
275
+ const topic = rest[0];
276
+ const result = diffBlueprintTopic({ tree, topic });
277
+ stdout.write(renderDiff(result));
278
+ return 0;
279
+ }
280
+
281
+ stderr.write(`[error] blueprint: unknown verb '${verb}'\n`);
282
+ stderr.write(HELP);
283
+ return 2;
284
+ }
285
+
286
+ /**
287
+ * Parse `--resolve <topic>=project:<ADR-id>` occurrences into a list
288
+ * of declarations, attaching an optional shared `reason` (from the
289
+ * single `--reason` flag) to every declaration on this add. Returns
290
+ * `{ value: Array }` on success or `{ error: string }` on any
291
+ * mis-shaped input.
292
+ *
293
+ * The `--reason` flag is singular per invocation because a single
294
+ * add's resolutions typically share one operator justification
295
+ * (`--reason "Project auth model stands over both blueprint
296
+ * defaults."`); if per-topic reasons are needed later, the flag can
297
+ * grow a `--reason <topic>=<text>` shape without breaking this call
298
+ * shape.
299
+ */
300
+ function parseResolveOptions(rawList, reason) {
301
+ if (!Array.isArray(rawList) || rawList.length === 0) return { value: [] };
302
+ const trimmedReason = typeof reason === 'string' ? reason : undefined;
303
+ const out = [];
304
+ for (const raw of rawList) {
305
+ if (typeof raw !== 'string' || raw.length === 0) {
306
+ return { error: `--resolve expects <topic>=project:<ADR-id>, got '${raw}'` };
307
+ }
308
+ const eq = raw.indexOf('=');
309
+ if (eq === -1) {
310
+ return { error: `--resolve expects <topic>=project:<ADR-id>, got '${raw}' (missing '=')` };
311
+ }
312
+ const topic = raw.slice(0, eq);
313
+ const rhs = raw.slice(eq + 1);
314
+ if (topic.length === 0) return { error: `--resolve expects a topic before '=', got '${raw}'` };
315
+ if (!rhs.startsWith('project:')) {
316
+ return { error: `--resolve rhs must start with 'project:' (that is the currently supported resolution target), got '${rhs}'` };
317
+ }
318
+ const adrId = rhs.slice('project:'.length);
319
+ if (adrId.length === 0) return { error: `--resolve resolvedByAdrId is empty for topic '${topic}'` };
320
+ const decl = { topic, resolvedByAdrId: adrId };
321
+ if (trimmedReason !== undefined) decl.reason = trimmedReason;
322
+ out.push(decl);
323
+ }
324
+ return { value: out };
325
+ }
@@ -43,7 +43,7 @@ const OPTION_SPEC = {
43
43
  help: { type: 'boolean' },
44
44
  };
45
45
 
46
- export const HELP = `Usage: rcf browser-verify <fbs-id> [options]
46
+ export const HELP = `Usage: rcf verify browser <fbs-id> [options]
47
47
 
48
48
  Stage 5 browser-verification gate for a UI-bearing FBS. Writes a
49
49
  browserVerification record on the manifest; the finalise gate reads it.