rcf-lite 0.9.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.
- package/CHANGELOG.md +86 -54
- package/README.md +8 -4
- package/bin/rcf.js +145 -57
- package/fixtures/canary-manifest.json +9 -9
- package/guidance/README.md +1 -1
- package/guidance/build-cycle-playbook.md +51 -51
- package/guidance/build-cycle.md +7 -7
- package/guidance/document-model.md +1 -1
- package/guidance/elicitation-playbook.md +29 -29
- package/guidance/harness-template.md +10 -10
- package/guidance/managed/README.md +1 -1
- package/guidance/managed/agent-instructions-block.hash +1 -1
- package/guidance/managed/agent-instructions-block.md +9 -9
- package/guidance/manifest.json +1 -1
- package/guidance/overview.md +4 -4
- package/package.json +4 -6
- package/rcf/adrs/adr-008.json +1 -1
- package/rcf/adrs/adr-009.json +4 -4
- package/rcf/code-nodes/cn-016.json +1 -1
- package/rcf/code-nodes/cn-019.json +1 -1
- package/rcf/code-nodes/cn-020.json +1 -1
- package/rcf/code-nodes/cn-021.json +1 -1
- package/rcf/code-nodes/cn-022.json +1 -1
- package/rcf/code-nodes/cn-049.json +1 -1
- package/rcf/code-nodes/cn-055.json +1 -1
- package/rcf/code-nodes/cn-057.json +5 -5
- package/rcf/code-nodes/cn-061.json +1 -1
- package/rcf/code-nodes/cn-066.json +1 -1
- package/rcf/code-nodes/cn-069.json +1 -1
- package/rcf/fbs/fbs-005.json +1 -1
- package/rcf/fbs/fbs-006.json +2 -2
- package/rcf/fbs/fbs-007.json +1 -1
- package/rcf/fbs/fbs-014.json +1 -1
- package/rcf/fbs/fbs-015.json +9 -8
- package/rcf/fbs/fbs-016.json +1 -1
- package/rcf/fbs/fbs-017.json +1 -1
- package/rcf/fbs/fbs-018.json +2 -2
- package/rcf/requirements/req-008.json +1 -1
- package/rcf/requirements/req-009.json +2 -2
- package/rcf/requirements/req-010.json +1 -1
- package/rcf/test-suites/PENDING.md +2 -2
- package/rcf/test-suites/ts-004.json +1 -1
- package/rcf/test-suites/ts-006.json +3 -3
- package/rcf/test-suites/ts-008.json +2 -2
- package/rcf/test-suites/ts-009.json +2 -2
- package/rcf/test-suites/ts-017.json +1 -1
- package/rcf/test-suites/ts-024.json +1 -1
- package/rcf/test-suites/ts-025.json +32 -18
- package/rcf/test-suites/ts-026.json +1 -1
- package/rcf/test-suites/ts-027.json +7 -7
- package/rcf/test-suites/ts-028.json +1 -1
- package/rcf/user-stories/us-1001.json +10 -10
- package/rcf/user-stories/us-1002.json +12 -12
- package/rcf/user-stories/us-1003.json +6 -6
- package/rcf/user-stories/us-805.json +2 -2
- package/rcf/user-stories/us-901.json +13 -13
- package/src/blueprint/conflicts.js +15 -15
- package/src/blueprint/supersede.js +6 -6
- package/src/build/bundle.js +3 -3
- package/src/build/formatters/markdown.js +9 -9
- package/src/build/mark.js +3 -3
- package/src/build/queue.js +1 -1
- package/src/cli/blueprint.js +3 -3
- package/src/cli/browser-verify.js +1 -1
- package/src/cli/build.js +139 -69
- package/src/cli/coverage.js +1 -1
- package/src/cli/create.js +3 -3
- package/src/cli/delete.js +2 -2
- package/src/cli/design.js +10 -10
- package/src/cli/fbs.js +1 -1
- package/src/cli/finalise.js +22 -20
- package/src/cli/help.js +267 -95
- package/src/cli/impact.js +1 -1
- package/src/cli/intake.js +3 -3
- package/src/cli/link.js +3 -3
- package/src/cli/preflight.js +2 -2
- package/src/cli/read.js +1 -1
- package/src/cli/req-baseline.js +2 -2
- package/src/cli/req-classify.js +3 -3
- package/src/cli/review.js +1 -1
- package/src/cli/standards.js +1 -1
- package/src/cli/test-suite.js +1 -1
- package/src/cli/trace.js +1 -1
- package/src/cli/ui-baseline.js +3 -3
- package/src/cli/ui-classify.js +4 -4
- package/src/cli/update.js +2 -2
- package/src/cli/validate.js +2 -2
- package/src/cli/view.js +12 -10
- package/src/core/store/writer.js +1 -1
- package/src/design/writer.js +3 -3
- package/src/finalise/detect.js +32 -38
- package/src/finalise/index.js +0 -1
- package/src/finalise/install.js +9 -8
- package/src/finalise/spawn.js +14 -10
- package/src/mcp/tools.js +1 -1
- package/src/req-baseline/gate.js +1 -1
- package/src/ui-baseline/manifest-writer.js +2 -2
- package/src/verify/cli/cleanup.js +1 -1
- package/src/verify/cli/mcp.js +1 -1
- package/src/verify/cli/provision.js +1 -1
- package/src/verify/cli/report.js +1 -1
- package/src/verify/cli/run.js +1 -1
- package/src/view-supervisor/manifest-writer.js +2 -2
- package/bin/rcf-verify.js +0 -122
- package/src/verify/cli/help.js +0 -56
package/src/cli/build.js
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
|
-
// `rcf build`
|
|
1
|
+
// `rcf build` group handler. 0.10.0 splits the old flag-based mode
|
|
2
|
+
// detection into three explicit sub-verbs; the internal semantics are
|
|
3
|
+
// unchanged.
|
|
2
4
|
//
|
|
3
|
-
// rcf build
|
|
4
|
-
// rcf build <fbs-id>
|
|
5
|
-
// rcf build --next
|
|
6
|
-
// rcf build <fbs-id>
|
|
5
|
+
// rcf build queue queue overview
|
|
6
|
+
// rcf build bundle <fbs-id> spec bundle for one FBS item
|
|
7
|
+
// rcf build bundle --next bundle for the next actionable item
|
|
8
|
+
// rcf build mark <fbs-id> <status> record a lifecycle transition
|
|
7
9
|
//
|
|
8
10
|
// Positional is an FBS id ONLY: the FBS is the queue unit (one US's
|
|
9
11
|
// ACs can span multiple FBS items, so US addressing is ambiguous by
|
|
10
|
-
// construction). US ids exit 2 with a pointer at `rcf trace`.
|
|
12
|
+
// construction). US ids exit 2 with a pointer at `rcf audit trace`.
|
|
11
13
|
//
|
|
12
14
|
// Deterministic-only boundary (§D13): this verb assembles what the
|
|
13
15
|
// tree says. It does not score bundle quality, detect under-specified
|
|
@@ -60,65 +62,71 @@ const OPTION_SPEC = {
|
|
|
60
62
|
reason: { type: 'string' },
|
|
61
63
|
};
|
|
62
64
|
|
|
63
|
-
export const HELP = `Usage: rcf build
|
|
65
|
+
export const HELP = `Usage: rcf build <verb> [options]
|
|
64
66
|
|
|
65
67
|
Assemble FBS spec bundles and drive the build queue (the SDD adapter).
|
|
66
|
-
|
|
68
|
+
Three sub-verbs:
|
|
67
69
|
|
|
68
|
-
rcf build
|
|
69
|
-
|
|
70
|
-
rcf build <fbs-id>
|
|
71
|
-
rcf build --next
|
|
72
|
-
rcf build <fbs-id>
|
|
70
|
+
rcf build queue Queue overview (the FBS queue as a
|
|
71
|
+
table, with parallel-safe tier groups)
|
|
72
|
+
rcf build bundle <fbs-id> Spec bundle for one FBS item
|
|
73
|
+
rcf build bundle --next Bundle for the next actionable item
|
|
74
|
+
rcf build mark <fbs-id> <status> Record a lifecycle transition
|
|
73
75
|
|
|
74
76
|
The positional is an FBS id only. For 'which FBS items implement this
|
|
75
|
-
story', use: rcf trace <us-id> --forward --format json
|
|
77
|
+
story', use: rcf audit trace <us-id> --forward --format json
|
|
76
78
|
|
|
77
79
|
Lifecycle (forward-only): notStarted -> inProgress -> complete -> verified.
|
|
78
|
-
The
|
|
79
|
-
because
|
|
80
|
-
an independent verify run. Backward transitions are also refused
|
|
81
|
-
the deliberate-correction / manual-override escape hatch is:
|
|
82
|
-
rcf update <fbs-id> --set executionStatus=<status>
|
|
80
|
+
The mark ladder caps at 'complete': 'verified' is refused (exit 4)
|
|
81
|
+
because it is written only by the finalise gate (rcf build finalise)
|
|
82
|
+
after an independent verify run. Backward transitions are also refused
|
|
83
|
+
(exit 4); the deliberate-correction / manual-override escape hatch is:
|
|
84
|
+
rcf define update <fbs-id> --set executionStatus=<status>
|
|
83
85
|
|
|
84
86
|
Bundle assembly is mechanical and deterministic: it projects what the
|
|
85
87
|
tree says. It does NOT judge whether the FBS is well-specified or the
|
|
86
88
|
bundle sufficient - that belongs to a later prompting + MCP phase.
|
|
87
89
|
|
|
88
|
-
Options:
|
|
90
|
+
Options (bundle):
|
|
89
91
|
--next Select the next actionable FBS item (lowest
|
|
90
92
|
buildOrder, notStarted, all dependencies
|
|
91
93
|
satisfied) and emit its bundle
|
|
92
|
-
--
|
|
93
|
-
|
|
94
|
-
--format <format> md (default) | json (queue + bundle modes)
|
|
95
|
-
--out <path> Write the bundle to a file (bundle modes)
|
|
94
|
+
--format <format> md (default) | json
|
|
95
|
+
--out <path> Write the bundle to a file
|
|
96
96
|
--strict Refuse (exit 4) a bundle for a blocked item;
|
|
97
97
|
no effect with --next (it never selects
|
|
98
98
|
blocked items)
|
|
99
|
-
|
|
99
|
+
|
|
100
|
+
Options (queue):
|
|
101
|
+
--format <format> md (default) | json
|
|
102
|
+
|
|
103
|
+
Options (mark):
|
|
104
|
+
--no-code-nodes With mark complete: declare this FBS as
|
|
100
105
|
genuinely producing no traceable code
|
|
101
106
|
(docs-only, config-only), recorded on the FBS
|
|
102
107
|
and exempting it from the mark-complete CN gate
|
|
103
|
-
--accept-block With
|
|
104
|
-
a browserVerification block
|
|
105
|
-
|
|
106
|
-
|
|
108
|
+
--accept-block With mark complete on a uiBearing FBS: accept
|
|
109
|
+
a browserVerification block and ship the FBS
|
|
110
|
+
at complete. Requires --reason "..." (>= 20
|
|
111
|
+
chars). Records the reason on the
|
|
107
112
|
browserVerification.operatorShipDespiteBlockReason
|
|
108
113
|
field. Track B ship-without-verified (spec §8.6).
|
|
109
114
|
--reason "..." Reason string for --accept-block.
|
|
115
|
+
|
|
116
|
+
Global:
|
|
110
117
|
--quiet Suppress non-error confirmations
|
|
111
118
|
--help Print this help
|
|
112
119
|
|
|
113
|
-
Mark-complete CN gate (D17):
|
|
114
|
-
missingCodeNodes) when any of its ACs carries no Code Node.
|
|
115
|
-
coverage first, or pass --no-code-nodes for a genuinely
|
|
120
|
+
Mark-complete CN gate (D17): 'build mark <fbs-id> complete' refuses
|
|
121
|
+
(exit 3, missingCodeNodes) when any of its ACs carries no Code Node.
|
|
122
|
+
Author CN coverage first, or pass --no-code-nodes for a genuinely
|
|
123
|
+
no-code spec.
|
|
116
124
|
`;
|
|
117
125
|
|
|
118
126
|
const VALID_FORMATS = new Set(['md', 'json']);
|
|
119
127
|
|
|
120
128
|
/**
|
|
121
|
-
* @param {string[]} argv - argv slice after `build`
|
|
129
|
+
* @param {string[]} argv - argv slice after `build` (sub-verb-first).
|
|
122
130
|
* @param {object} [deps]
|
|
123
131
|
* @returns {Promise<number>}
|
|
124
132
|
*/
|
|
@@ -127,9 +135,71 @@ export async function main(argv, deps = {}) {
|
|
|
127
135
|
const stderr = deps.stderr ?? process.stderr;
|
|
128
136
|
const cwd = deps.cwd ?? process.cwd();
|
|
129
137
|
|
|
138
|
+
// Global --help before sub-verb dispatch.
|
|
139
|
+
if (argv[0] === '--help' || argv[0] === '-h') {
|
|
140
|
+
stdout.write(HELP);
|
|
141
|
+
return 0;
|
|
142
|
+
}
|
|
143
|
+
const subverb = argv[0];
|
|
144
|
+
if (!subverb) {
|
|
145
|
+
stdout.write(HELP);
|
|
146
|
+
return 0;
|
|
147
|
+
}
|
|
148
|
+
if (!['queue', 'bundle', 'mark'].includes(subverb)) {
|
|
149
|
+
stderr.write(`[error] usage build: unknown sub-verb '${subverb}' (expected queue | bundle | mark)\n`);
|
|
150
|
+
stderr.write(HELP);
|
|
151
|
+
return 2;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Translate the sub-verb-first argv shape into the flag-based mode
|
|
155
|
+
// the internal implementation still consumes. The translation is
|
|
156
|
+
// one-way and only touches the argv layer; every internal check
|
|
157
|
+
// (positional discipline, flag-conflict rules, format validation)
|
|
158
|
+
// stays byte-identical.
|
|
159
|
+
//
|
|
160
|
+
// queue <extra-flags> -> queue mode
|
|
161
|
+
// bundle <fbs-id> <extra-flags> -> bundle mode
|
|
162
|
+
// bundle --next <extra-flags> -> next mode (bundle sub-verb)
|
|
163
|
+
// mark <fbs-id> <status> <extra> -> mark mode
|
|
164
|
+
const rest = argv.slice(1);
|
|
165
|
+
// `rcf build <sub-verb> --help` short-circuits to the shared HELP
|
|
166
|
+
// block before any sub-verb-specific positional check refuses.
|
|
167
|
+
if (rest.includes('--help') || rest.includes('-h')) {
|
|
168
|
+
stdout.write(HELP);
|
|
169
|
+
return 0;
|
|
170
|
+
}
|
|
171
|
+
let translated;
|
|
172
|
+
if (subverb === 'queue') {
|
|
173
|
+
// Refuse a stray positional so `rcf build queue <fbs-id>` is not silently
|
|
174
|
+
// treated as a bundle request.
|
|
175
|
+
if (rest.length > 0 && !rest[0].startsWith('-')) {
|
|
176
|
+
stderr.write(`[error] usage build queue: unexpected positional '${rest[0]}' (queue takes no positional)\n`);
|
|
177
|
+
return 2;
|
|
178
|
+
}
|
|
179
|
+
translated = rest;
|
|
180
|
+
} else if (subverb === 'bundle') {
|
|
181
|
+
// Either --next as the first token, or a positional fbs-id.
|
|
182
|
+
translated = rest;
|
|
183
|
+
} else { // mark
|
|
184
|
+
if (rest.length < 2) {
|
|
185
|
+
stderr.write('[error] usage build mark: requires <fbs-id> <status>\n');
|
|
186
|
+
return 2;
|
|
187
|
+
}
|
|
188
|
+
const [fbsId, status, ...tail] = rest;
|
|
189
|
+
if (fbsId.startsWith('-')) {
|
|
190
|
+
stderr.write('[error] usage build mark: first positional must be an FBS id, not a flag\n');
|
|
191
|
+
return 2;
|
|
192
|
+
}
|
|
193
|
+
if (status.startsWith('-')) {
|
|
194
|
+
stderr.write('[error] usage build mark: second positional must be a status word, not a flag\n');
|
|
195
|
+
return 2;
|
|
196
|
+
}
|
|
197
|
+
translated = [fbsId, '--mark', status, ...tail];
|
|
198
|
+
}
|
|
199
|
+
|
|
130
200
|
let parsed;
|
|
131
201
|
try {
|
|
132
|
-
parsed = parseArgs({ args:
|
|
202
|
+
parsed = parseArgs({ args: translated, options: OPTION_SPEC, allowPositionals: true, strict: true });
|
|
133
203
|
} catch (err) {
|
|
134
204
|
stderr.write(`[error] usage ${err.message}\n`);
|
|
135
205
|
stderr.write(HELP);
|
|
@@ -150,43 +220,43 @@ export async function main(argv, deps = {}) {
|
|
|
150
220
|
return 2;
|
|
151
221
|
}
|
|
152
222
|
|
|
153
|
-
//
|
|
223
|
+
// Sub-verb / flag consistency. The internal mode name below stays the
|
|
224
|
+
// same for readability; the sub-verb selects it deterministically.
|
|
154
225
|
const usage = (message) => {
|
|
155
226
|
stderr.write(`[error] usage build: ${message}\n`);
|
|
156
227
|
return 2;
|
|
157
228
|
};
|
|
158
|
-
if (
|
|
159
|
-
|
|
160
|
-
if (
|
|
161
|
-
if (flags.next) return usage('
|
|
162
|
-
|
|
229
|
+
if (subverb === 'bundle') {
|
|
230
|
+
if (flags.mark !== undefined) return usage('bundle does not take --mark (use `rcf build mark`)');
|
|
231
|
+
if (flags.next && positional) return usage('bundle --next takes no positional');
|
|
232
|
+
if (!flags.next && !positional) return usage('bundle requires <fbs-id> or --next');
|
|
233
|
+
} else if (subverb === 'queue') {
|
|
234
|
+
if (flags.mark !== undefined) return usage('queue does not take --mark (use `rcf build mark`)');
|
|
235
|
+
if (flags.next) return usage('queue does not take --next (use `rcf build bundle --next`)');
|
|
236
|
+
if (positional) return usage('queue takes no positional');
|
|
237
|
+
if (flags.out !== undefined) return usage('--out is invalid in queue mode');
|
|
238
|
+
if (flags.strict) return usage('--strict applies to bundle modes only');
|
|
239
|
+
} else if (subverb === 'mark') {
|
|
240
|
+
if (!positional) return usage('mark requires an <fbs-id>');
|
|
241
|
+
if (flags.next) return usage('mark cannot combine with --next');
|
|
242
|
+
if (flags.format !== undefined) return usage('mark cannot combine with --format');
|
|
163
243
|
if (flags.out !== undefined) return usage('--out is invalid in mark mode');
|
|
164
|
-
if (flags.strict) return usage('
|
|
244
|
+
if (flags.strict) return usage('mark cannot combine with --strict');
|
|
165
245
|
}
|
|
166
246
|
// Phase 10 (X2 CodeNode bridge, D17): --no-code-nodes only makes sense
|
|
167
247
|
// declaring a spec complete with no traceable code.
|
|
168
248
|
if (flags['no-code-nodes'] && flags.mark !== 'complete') {
|
|
169
|
-
return usage('--no-code-nodes only combines with
|
|
249
|
+
return usage('--no-code-nodes only combines with `build mark <fbs-id> complete`');
|
|
170
250
|
}
|
|
171
|
-
// Track B (§8.6): --accept-block only makes sense with
|
|
251
|
+
// Track B (§8.6): --accept-block only makes sense with mark complete
|
|
172
252
|
// and requires --reason "...".
|
|
173
253
|
if (flags['accept-block'] && flags.mark !== 'complete') {
|
|
174
|
-
return usage('--accept-block only combines with
|
|
254
|
+
return usage('--accept-block only combines with `build mark <fbs-id> complete`');
|
|
175
255
|
}
|
|
176
256
|
if (flags['accept-block'] && (typeof flags.reason !== 'string' || flags.reason.length < 20)) {
|
|
177
257
|
return usage('--accept-block requires --reason "..." with at least 20 characters');
|
|
178
258
|
}
|
|
179
|
-
const mode = flags.
|
|
180
|
-
? 'mark'
|
|
181
|
-
: flags.next
|
|
182
|
-
? 'next'
|
|
183
|
-
: positional
|
|
184
|
-
? 'bundle'
|
|
185
|
-
: 'queue';
|
|
186
|
-
if (mode === 'queue') {
|
|
187
|
-
if (flags.out !== undefined) return usage('--out is invalid in queue mode');
|
|
188
|
-
if (flags.strict) return usage('--strict applies to bundle modes only');
|
|
189
|
-
}
|
|
259
|
+
const mode = subverb === 'mark' ? 'mark' : subverb === 'queue' ? 'queue' : flags.next ? 'next' : 'bundle';
|
|
190
260
|
const format = flags.format ?? 'md';
|
|
191
261
|
if (!VALID_FORMATS.has(format)) {
|
|
192
262
|
return usage(`unknown --format ${format} (expected md | json)`);
|
|
@@ -240,7 +310,7 @@ function classifyPositional(tree, id) {
|
|
|
240
310
|
return rcfError({
|
|
241
311
|
kind: 'usage',
|
|
242
312
|
message: `build: ${id} is a user story, not an FBS id; the FBS is the queue unit. `
|
|
243
|
-
+ `To list the FBS items linked to this story: rcf trace ${id} --forward --format json`,
|
|
313
|
+
+ `To list the FBS items linked to this story: rcf audit trace ${id} --forward --format json`,
|
|
244
314
|
documentId: id,
|
|
245
315
|
});
|
|
246
316
|
}
|
|
@@ -308,7 +378,7 @@ async function emitNext({ tree, format, io }) {
|
|
|
308
378
|
const unbacked = scanUnbackedServices(tree, next.fbsId);
|
|
309
379
|
if (unbacked.length > 0) {
|
|
310
380
|
const names = unbacked.map((u) => u.serviceId).join(', ');
|
|
311
|
-
io.stderr.write(`[warn] build --next: ${next.fbsId} touches services not covered by any preFlightConfig (${names}); run 'rcf preflight' before Stage 4.\n`);
|
|
381
|
+
io.stderr.write(`[warn] build bundle --next: ${next.fbsId} touches services not covered by any preFlightConfig (${names}); run 'rcf discover preflight' before Stage 4.\n`);
|
|
312
382
|
}
|
|
313
383
|
// Track B (spec §4.4): always print the classifier verdict + signals
|
|
314
384
|
// for the selected FBS. Transparent-by-default so a `notUi` verdict
|
|
@@ -384,7 +454,7 @@ async function runMark({ tree, projectRoot, fbsId, status, io, noCodeNodes = fal
|
|
|
384
454
|
if (plan.to === 'inProgress') {
|
|
385
455
|
const fbs = tree.byId.get(plan.fbsId);
|
|
386
456
|
if (fbs?.uiBearing === true && !fbs?.designStage) {
|
|
387
|
-
io.stderr.write(`[warn] build
|
|
457
|
+
io.stderr.write(`[warn] build mark ${plan.fbsId} inProgress: uiBearing FBS with no designStage yet. Consider running 'rcf define design ${plan.fbsId}' before Stage 2 build begins. Not a refusal; the hard gate fires at 'build mark <fbs-id> complete'.\n`);
|
|
388
458
|
}
|
|
389
459
|
}
|
|
390
460
|
|
|
@@ -403,8 +473,8 @@ async function runMark({ tree, projectRoot, fbsId, status, io, noCodeNodes = fal
|
|
|
403
473
|
if (!designGate.ok) {
|
|
404
474
|
io.stderr.write(`[error] refused build: ${plan.fbsId} is uiBearing but designStageComplete is not true on the FBS record.\n`
|
|
405
475
|
+ ' Complete the Design substage first:\n'
|
|
406
|
-
+ ` rcf design ${plan.fbsId}
|
|
407
|
-
+ ` rcf design ${plan.fbsId} --mark-complete
|
|
476
|
+
+ ` rcf define design ${plan.fbsId} (dispatches the Design worker)\n`
|
|
477
|
+
+ ` rcf define design ${plan.fbsId} --mark-complete (hand-authored equivalent, once all three artefacts are present)\n`);
|
|
408
478
|
return 4;
|
|
409
479
|
}
|
|
410
480
|
|
|
@@ -430,8 +500,8 @@ async function runMark({ tree, projectRoot, fbsId, status, io, noCodeNodes = fal
|
|
|
430
500
|
io.stderr.write(`[error] refused build: ${plan.fbsId} designStage.${disagreement.designStagePath} = ${JSON.stringify(disagreement.designValue)} conflicts with uiBaseline.defaults.${disagreement.path} = ${JSON.stringify(disagreement.baselineValue)} and there is no operatorOptOuts entry.\n`
|
|
431
501
|
+ ' Options:\n'
|
|
432
502
|
+ ` 1) Change designStage.${disagreement.designStagePath} to match the baseline.\n`
|
|
433
|
-
+ ` 2) rcf ui-baseline opt-out --field ${disagreement.path} --reason "..." (project-wide override).\n`
|
|
434
|
-
+ ` 3) rcf update ${plan.fbsId} --set designStage.${disagreement.designStagePath}=... (per-FBS override; records ack).\n`);
|
|
503
|
+
+ ` 2) rcf discover ui-baseline opt-out --field ${disagreement.path} --reason "..." (project-wide override).\n`
|
|
504
|
+
+ ` 3) rcf define update ${plan.fbsId} --set designStage.${disagreement.designStagePath}=... (per-FBS override; records ack).\n`);
|
|
435
505
|
return 4;
|
|
436
506
|
}
|
|
437
507
|
}
|
|
@@ -465,19 +535,19 @@ async function runMark({ tree, projectRoot, fbsId, status, io, noCodeNodes = fal
|
|
|
465
535
|
// ratify a real record. Two-command dance: acknowledged and
|
|
466
536
|
// preferred.
|
|
467
537
|
io.stderr.write(`[error] refused build: ${plan.fbsId} is uiBearing but no browserVerification record exists on the manifest.\n`
|
|
468
|
-
+ ` Run: rcf
|
|
538
|
+
+ ` Run: rcf verify browser ${plan.fbsId}\n`
|
|
469
539
|
+ ` Then, if the resulting record is block or warn and you have decided to ship anyway, rerun with --accept-block --reason "..." on that record.\n`
|
|
470
540
|
+ ' (--accept-block requires an existing browserVerification record; it acknowledges a real verdict rather than skipping the gate.)\n');
|
|
471
541
|
return 4;
|
|
472
542
|
} else if (bvRecord.verdict === 'block' && !acceptBlock) {
|
|
473
543
|
io.stderr.write(`[error] refused build: ${plan.fbsId} browserVerification ${bvRecord.id} verdict is 'block'.\n`
|
|
474
|
-
+ ' Re-run
|
|
475
|
-
+ ` rcf build ${plan.fbsId}
|
|
544
|
+
+ ' Re-run verify browser after fixing the failures, or acknowledge with:\n'
|
|
545
|
+
+ ` rcf build mark ${plan.fbsId} complete --accept-block --reason "..."\n`);
|
|
476
546
|
return 4;
|
|
477
547
|
} else if (bvRecord.verdict === 'warn' && !bvRecord.operatorAckAt) {
|
|
478
548
|
io.stderr.write(`[error] refused build: ${plan.fbsId} browserVerification ${bvRecord.id} verdict is 'warn' but operatorAckAt is not set.\n`
|
|
479
549
|
+ ' Ack the warn verdict with:\n'
|
|
480
|
-
+ ` rcf
|
|
550
|
+
+ ` rcf verify browser ${plan.fbsId} --ack\n`);
|
|
481
551
|
return 4;
|
|
482
552
|
}
|
|
483
553
|
// Ship-without-verified: record the operator's reason on the bv
|
|
@@ -507,7 +577,7 @@ async function runMark({ tree, projectRoot, fbsId, status, io, noCodeNodes = fal
|
|
|
507
577
|
if (!gate.ok) {
|
|
508
578
|
const err = rcfError({
|
|
509
579
|
kind: 'missingCodeNodes',
|
|
510
|
-
message: `build
|
|
580
|
+
message: `build mark ${plan.fbsId} complete: refused - has AC(s) with no Code Node: ${gate.missingAcIds.join(', ')}. `
|
|
511
581
|
+ 'Author CN coverage for these ACs, or pass --no-code-nodes for a genuinely no-code (docs-only, config-only) spec.',
|
|
512
582
|
documentId: plan.fbsId,
|
|
513
583
|
field: 'acIds',
|
|
@@ -562,8 +632,8 @@ function emitClassifierSignal(io, tree, fbsId) {
|
|
|
562
632
|
: ' [not yet ratified]';
|
|
563
633
|
io.stderr.write(`[info] build: ui-classifier verdict=${block.verdict} reason=${block.reason} (${signalCount} signal(s))${suffix}\n`);
|
|
564
634
|
if (block.verdict === 'ui' && fbs.uiBearing !== true) {
|
|
565
|
-
io.stderr.write(` Classified as UI-bearing. Ratify with: rcf update ${fbsId} --set uiBearing=true\n`);
|
|
566
|
-
io.stderr.write(` Override with: rcf update ${fbsId} --set uiBearing=false (if the classifier is wrong).\n`);
|
|
635
|
+
io.stderr.write(` Classified as UI-bearing. Ratify with: rcf define update ${fbsId} --set uiBearing=true\n`);
|
|
636
|
+
io.stderr.write(` Override with: rcf define update ${fbsId} --set uiBearing=false (if the classifier is wrong).\n`);
|
|
567
637
|
}
|
|
568
638
|
}
|
|
569
639
|
|
package/src/cli/coverage.js
CHANGED
|
@@ -41,7 +41,7 @@ const OPTION_SPEC = {
|
|
|
41
41
|
'require-approved': { type: 'boolean' },
|
|
42
42
|
};
|
|
43
43
|
|
|
44
|
-
export const HELP = `Usage: rcf coverage [scope-id] [options]
|
|
44
|
+
export const HELP = `Usage: rcf audit coverage [scope-id] [options]
|
|
45
45
|
|
|
46
46
|
Report structural coverage over the REQ chain (PRD -> REQ -> US -> AC
|
|
47
47
|
-> TS -> TC). A TC counts as covering its AC only when its testPointer
|
package/src/cli/create.js
CHANGED
|
@@ -39,7 +39,7 @@ const OPTION_SPEC = {
|
|
|
39
39
|
'derive-deps': { type: 'boolean' },
|
|
40
40
|
};
|
|
41
41
|
|
|
42
|
-
export const HELP = `Usage: rcf create <kind> [options]
|
|
42
|
+
export const HELP = `Usage: rcf define create <kind> [options]
|
|
43
43
|
|
|
44
44
|
Kinds: req | us | ac | tac | adr | fbs | ts | tc | cn
|
|
45
45
|
|
|
@@ -158,7 +158,7 @@ export async function main(argv, deps = {}) {
|
|
|
158
158
|
// repairing a broken tree is possible while net-new breakage is still
|
|
159
159
|
// refused.
|
|
160
160
|
if (walkResult.errors.length > 0) {
|
|
161
|
-
stderr.write(`[warn] tree has ${walkResult.errors.length} pre-existing issue(s); proceeding - writes are validated against the post-write state (run 'rcf validate' for details)\n`);
|
|
161
|
+
stderr.write(`[warn] tree has ${walkResult.errors.length} pre-existing issue(s); proceeding - writes are validated against the post-write state (run 'rcf define validate' for details)\n`);
|
|
162
162
|
}
|
|
163
163
|
|
|
164
164
|
let fileBody = null;
|
|
@@ -304,7 +304,7 @@ export async function main(argv, deps = {}) {
|
|
|
304
304
|
if (open.length > 0 && !flags.quiet) {
|
|
305
305
|
const keys = open.map((c) => c.baselineKey).join(', ');
|
|
306
306
|
stdout.write(`${result.id} has ${open.length} open baseline candidate${open.length === 1 ? '' : 's'}: ${keys}\n`);
|
|
307
|
-
stdout.write(` Resolve: rcf req-baseline sweep --req ${usDoc.reqId}\n`);
|
|
307
|
+
stdout.write(` Resolve: rcf discover req-baseline sweep --req ${usDoc.reqId}\n`);
|
|
308
308
|
}
|
|
309
309
|
}
|
|
310
310
|
} catch (err) {
|
package/src/cli/delete.js
CHANGED
|
@@ -15,7 +15,7 @@ const OPTION_SPEC = {
|
|
|
15
15
|
help: { type: 'boolean' },
|
|
16
16
|
};
|
|
17
17
|
|
|
18
|
-
export const HELP = `Usage: rcf delete <id> [options]
|
|
18
|
+
export const HELP = `Usage: rcf define delete <id> [options]
|
|
19
19
|
|
|
20
20
|
Options:
|
|
21
21
|
--cascade Also delete dependents and drop backrefs
|
|
@@ -64,7 +64,7 @@ export async function main(argv, deps = {}) {
|
|
|
64
64
|
// the canonical repair. The writer gates on the POST-write tree state;
|
|
65
65
|
// only net-new breakage refuses.
|
|
66
66
|
if (walkResult.errors.length > 0) {
|
|
67
|
-
stderr.write(`[warn] tree has ${walkResult.errors.length} pre-existing issue(s); proceeding - writes are validated against the post-write state (run 'rcf validate' for details)\n`);
|
|
67
|
+
stderr.write(`[warn] tree has ${walkResult.errors.length} pre-existing issue(s); proceeding - writes are validated against the post-write state (run 'rcf define validate' for details)\n`);
|
|
68
68
|
}
|
|
69
69
|
|
|
70
70
|
const result = await deleteDocument({
|
package/src/cli/design.js
CHANGED
|
@@ -68,19 +68,19 @@ const OPTION_SPEC = {
|
|
|
68
68
|
help: { type: 'boolean' },
|
|
69
69
|
};
|
|
70
70
|
|
|
71
|
-
export const HELP = `Usage: rcf design <fbs-id> [<sub-verb> ...] [options]
|
|
71
|
+
export const HELP = `Usage: rcf define design <fbs-id> [<sub-verb> ...] [options]
|
|
72
72
|
|
|
73
73
|
Design substage verbs for a UI-bearing FBS. FBS positional is required
|
|
74
74
|
and must match /^FBS-\\d+$/ (any other positional in that slot is a
|
|
75
75
|
usage error, not an FBS lookup miss).
|
|
76
76
|
|
|
77
77
|
Verb shapes:
|
|
78
|
-
rcf design <fbs-id>
|
|
78
|
+
rcf define design <fbs-id> Print the current design
|
|
79
79
|
state and next-move options.
|
|
80
|
-
rcf design <fbs-id> journeys add ...
|
|
81
|
-
rcf design <fbs-id> nav set ...
|
|
82
|
-
rcf design <fbs-id> theme-a11y set ...
|
|
83
|
-
rcf design <fbs-id> --mark-complete
|
|
80
|
+
rcf define design <fbs-id> journeys add ... Append a journey.
|
|
81
|
+
rcf define design <fbs-id> nav set ... Overwrite navModel.
|
|
82
|
+
rcf define design <fbs-id> theme-a11y set ... Overwrite themeAndA11y.
|
|
83
|
+
rcf define design <fbs-id> --mark-complete Set designStageComplete: true.
|
|
84
84
|
|
|
85
85
|
journeys add options:
|
|
86
86
|
--id <slug> Lowercase slug (a-z0-9-), unique within the FBS.
|
|
@@ -177,11 +177,11 @@ export async function main(argv, deps = {}) {
|
|
|
177
177
|
return 2;
|
|
178
178
|
}
|
|
179
179
|
if (fbs.uiBearing !== true) {
|
|
180
|
-
stderr.write(`[warn] design: ${fbsId} is not uiBearing (fbs.uiBearing is ${JSON.stringify(fbs.uiBearing)}); the Design substage is a no-op for non-UI FBS. Set uiBearing=true first (see 'rcf ui-classify ${fbsId}').\n`);
|
|
180
|
+
stderr.write(`[warn] design: ${fbsId} is not uiBearing (fbs.uiBearing is ${JSON.stringify(fbs.uiBearing)}); the Design substage is a no-op for non-UI FBS. Set uiBearing=true first (see 'rcf discover ui-classify ${fbsId}').\n`);
|
|
181
181
|
}
|
|
182
182
|
// Refuse Design substage writes when the baseline is missing (§6.2).
|
|
183
183
|
if (fbs.uiBearing === true && !tree.manifest?.uiBaseline && subs.length > 0) {
|
|
184
|
-
stderr.write(`[error] refused: ${fbsId} is uiBearing but no uiBaseline record exists on the manifest. Run: rcf ui-baseline init\n`);
|
|
184
|
+
stderr.write(`[error] refused: ${fbsId} is uiBearing but no uiBaseline record exists on the manifest. Run: rcf discover ui-baseline init\n`);
|
|
185
185
|
return 4;
|
|
186
186
|
}
|
|
187
187
|
|
|
@@ -229,11 +229,11 @@ function runShow({ tree, fbs, fbsId, stdout, flags }) {
|
|
|
229
229
|
if (missing.length > 0) {
|
|
230
230
|
stdout.write(` next: author the missing artefact(s): ${missing.join(', ')}\n`);
|
|
231
231
|
} else if (fbs.designStageComplete !== true) {
|
|
232
|
-
stdout.write(' next: rcf design ' + fbsId + ' --mark-complete\n');
|
|
232
|
+
stdout.write(' next: rcf define design ' + fbsId + ' --mark-complete\n');
|
|
233
233
|
}
|
|
234
234
|
const baseline = tree.manifest?.uiBaseline;
|
|
235
235
|
if (fbs.uiBearing === true && !baseline) {
|
|
236
|
-
stdout.write(' refused (once you begin authoring): rcf ui-baseline init must run first.\n');
|
|
236
|
+
stdout.write(' refused (once you begin authoring): rcf discover ui-baseline init must run first.\n');
|
|
237
237
|
}
|
|
238
238
|
return 0;
|
|
239
239
|
}
|
package/src/cli/fbs.js
CHANGED
|
@@ -30,7 +30,7 @@ const DEPENDS_ON_OPTION_SPEC = {
|
|
|
30
30
|
help: { type: 'boolean' },
|
|
31
31
|
};
|
|
32
32
|
|
|
33
|
-
export const HELP = `Usage: rcf fbs <fbs-id> <verb> [options]
|
|
33
|
+
export const HELP = `Usage: rcf build fbs <fbs-id> <verb> [options]
|
|
34
34
|
|
|
35
35
|
Verbs:
|
|
36
36
|
depends-on Record a third-party service dependency on this FBS
|
package/src/cli/finalise.js
CHANGED
|
@@ -78,21 +78,23 @@ const OPTION_SPEC = {
|
|
|
78
78
|
help: { type: 'boolean' },
|
|
79
79
|
};
|
|
80
80
|
|
|
81
|
-
export const HELP = `Usage: rcf finalise <fbs-id> --url <deploy-url> [options]
|
|
81
|
+
export const HELP = `Usage: rcf build finalise <fbs-id> --url <deploy-url> [options]
|
|
82
82
|
|
|
83
|
-
The ship gate. Promotes an FBS at status complete to verified ONLY when
|
|
84
|
-
independent rcf
|
|
83
|
+
The ship gate. Promotes an FBS at status complete to verified ONLY when
|
|
84
|
+
an independent 'rcf verify run' passes AND carries ship authority.
|
|
85
85
|
|
|
86
|
-
|
|
87
|
-
verifier agent starts cold, with zero build context - its only
|
|
88
|
-
RCF chain (the acceptance contract) and the live URL.
|
|
89
|
-
subprocess exit code AND the report's ship authority:
|
|
90
|
-
carry SHIP authority (a 'deployed'-profile
|
|
91
|
-
--parity-env) to promote. A
|
|
92
|
-
Findings flow back via
|
|
86
|
+
The verify run is launched as a FRESH SUBPROCESS (never in-process) so
|
|
87
|
+
the verifier agent starts cold, with zero build context - its only
|
|
88
|
+
inputs are the RCF chain (the acceptance contract) and the live URL.
|
|
89
|
+
The gate is the subprocess exit code AND the report's ship authority:
|
|
90
|
+
a run must both pass and carry SHIP authority (a 'deployed'-profile
|
|
91
|
+
run, or a 'ci'/'local-dev' run with --parity-env) to promote. A
|
|
92
|
+
correctness-only PASS holds without promoting. Findings flow back via
|
|
93
|
+
the --out report file.
|
|
93
94
|
|
|
94
|
-
If rcf-
|
|
95
|
-
posture) - it never silently skips the gate. Off a TTY, pass
|
|
95
|
+
If rcf-lite is not installed, finalise PROMPTS to install it (install-
|
|
96
|
+
together posture) - it never silently skips the gate. Off a TTY, pass
|
|
97
|
+
--install-verify.
|
|
96
98
|
|
|
97
99
|
Required:
|
|
98
100
|
<fbs-id> The FBS item to finalise (must be 'complete')
|
|
@@ -110,7 +112,7 @@ Options:
|
|
|
110
112
|
(default: ${DEFAULT_REPORT})
|
|
111
113
|
--chain <PRD/ref> Which PRD/chain to verify against (default: repo's)
|
|
112
114
|
--persona <name> Adversarial persona flavour
|
|
113
|
-
--install-verify If rcf-
|
|
115
|
+
--install-verify If rcf-lite is absent, install it without
|
|
114
116
|
prompting (the sanctioned non-interactive path)
|
|
115
117
|
--ship-without-verified Acknowledge MOCK-ONLY-DECLARED /
|
|
116
118
|
BLOCKED-BY-DECLARATION per-AC verdicts on
|
|
@@ -130,7 +132,7 @@ Exit codes:
|
|
|
130
132
|
2 usage error (bad flags, unknown / non-FBS id)
|
|
131
133
|
3 schema validation or broken references (tree unreadable, or write refused)
|
|
132
134
|
4 gate NOT passed - verify blocked ship, OR verify passed but the run lacks
|
|
133
|
-
ship authority (a correctness-only HOLD), OR rcf-
|
|
135
|
+
ship authority (a correctness-only HOLD), OR rcf-lite absent and install
|
|
134
136
|
declined/unavailable - the FBS is left unchanged, findings surfaced
|
|
135
137
|
`;
|
|
136
138
|
|
|
@@ -210,7 +212,7 @@ export async function main(argv, deps = {}) {
|
|
|
210
212
|
const currentStatus = fbs.executionStatus;
|
|
211
213
|
if (!FINALISABLE_STATUSES.has(currentStatus)) {
|
|
212
214
|
stderr.write(`[error] refused finalise: ${fbsId} is '${currentStatus}'; the finalise gate promotes 'complete' -> 'verified'. `
|
|
213
|
-
+ `Mark it complete first: rcf build ${fbsId}
|
|
215
|
+
+ `Mark it complete first: rcf build mark ${fbsId} complete\n`);
|
|
214
216
|
return 4;
|
|
215
217
|
}
|
|
216
218
|
|
|
@@ -237,7 +239,7 @@ export async function main(argv, deps = {}) {
|
|
|
237
239
|
? await deps.detectVerify({ cwd: projectRoot })
|
|
238
240
|
: await detectVerify({ cwd: projectRoot });
|
|
239
241
|
if (!detection.installed) {
|
|
240
|
-
stderr.write('[error] unexpected rcf
|
|
242
|
+
stderr.write('[error] unexpected the rcf bin still not resolvable after install; install it manually and re-run.\n');
|
|
241
243
|
return 1;
|
|
242
244
|
}
|
|
243
245
|
}
|
|
@@ -257,7 +259,7 @@ export async function main(argv, deps = {}) {
|
|
|
257
259
|
});
|
|
258
260
|
|
|
259
261
|
if (!quiet) {
|
|
260
|
-
stdout.write(`[finalise] launching rcf
|
|
262
|
+
stdout.write(`[finalise] launching rcf verify run (fresh subprocess) against ${flags.url} [profile=${profile}]...\n`);
|
|
261
263
|
}
|
|
262
264
|
|
|
263
265
|
let spawnResult;
|
|
@@ -267,7 +269,7 @@ export async function main(argv, deps = {}) {
|
|
|
267
269
|
: await spawnVerify({ invocation: detection.invocation, verifyArgs, cwd: projectRoot }, deps);
|
|
268
270
|
} catch (err) {
|
|
269
271
|
writeUnexpectedFailure(
|
|
270
|
-
rcfError({ kind: 'ioFailure', message: `finalise: could not launch rcf
|
|
272
|
+
rcfError({ kind: 'ioFailure', message: `finalise: could not launch rcf verify: ${err.message}`, stack: err.stack }),
|
|
271
273
|
stderr,
|
|
272
274
|
);
|
|
273
275
|
return 1;
|
|
@@ -293,7 +295,7 @@ export async function main(argv, deps = {}) {
|
|
|
293
295
|
const passLoaded = await (deps.loadReport ? deps.loadReport(outPath, deps) : loadReport(outPath, deps));
|
|
294
296
|
const authority = passLoaded.ok ? passLoaded.report.verdictAuthority : undefined;
|
|
295
297
|
if (authority !== 'ship') {
|
|
296
|
-
stderr.write(`[finalise] HOLD: rcf
|
|
298
|
+
stderr.write(`[finalise] HOLD: rcf verify passed but this run carries '${authority ?? 'unknown'}' authority, `
|
|
297
299
|
+ `not 'ship'; ${fbsId} left '${currentStatus}'.\n`);
|
|
298
300
|
stderr.write('Only a \'deployed\'-profile run - or a \'ci\'/\'local-dev\' run with --parity-env asserting the '
|
|
299
301
|
+ 'runtime is edge-identical to prod - carries ship authority and can promote to verified. '
|
|
@@ -359,7 +361,7 @@ export async function main(argv, deps = {}) {
|
|
|
359
361
|
|
|
360
362
|
// Gate NOT passed. The FBS stays put; surface the findings from the report
|
|
361
363
|
// (the §5.4 verify -> fix -> re-verify seam), then fail non-zero.
|
|
362
|
-
stderr.write(`[finalise] gate NOT passed (rcf
|
|
364
|
+
stderr.write(`[finalise] gate NOT passed (rcf verify exit ${spawnResult.code}); ${fbsId} left '${currentStatus}'.\n`);
|
|
363
365
|
const loaded = await (deps.loadReport ? deps.loadReport(outPath, deps) : loadReport(outPath, deps));
|
|
364
366
|
if (loaded.ok) {
|
|
365
367
|
stderr.write(summariseReport(loaded.report));
|