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,113 @@
1
+ // Canonical placeholder-credential-shape detector for the deployment-gate
2
+ // class cure (spa-US-1132 / TAC-210, w-2026-08-24-006).
3
+ //
4
+ // The watchpost first production review (w-2026-08-24-005) shipped with
5
+ // RESEND_API_KEY set to a placeholder value; the only login path (magic-link
6
+ // email) was inert; sign-off still said DEPLOYED. The gap was filed as a
7
+ // "quirk" note in status.md rather than blocked at gate time. This module
8
+ // owns the one canonical placeholder shape catalogue rcf-lite ships so
9
+ // downstream projects (via TAC-210 realisations) apply the same detector
10
+ // - extending the ruleset is a rcf-lite minor bump, never a per-project
11
+ // reinvention.
12
+ //
13
+ // The catalogue is deliberately narrow: only credential-shape strings a
14
+ // developer would reasonably type as a stand-in ("YOUR_API_KEY_HERE",
15
+ // "changeme", "xxx", empty). It does NOT try to classify all bad values;
16
+ // entropy checks, provider-specific prefix rules, and format regexes live
17
+ // in project-authored probes. This catalogue is what would have refused
18
+ // the watchpost RESEND_API_KEY at gate time.
19
+
20
+ /**
21
+ * @typedef {object} PlaceholderMatch
22
+ * @property {'empty'|'single-dash'|'null-token'|'you-here'|'named-stand-in'|'repeat-char'} pattern
23
+ * The named pattern that matched. Callers surface this on their report
24
+ * so a refusal message can name why.
25
+ */
26
+
27
+ /**
28
+ * @typedef {object} PlaceholderVerdict
29
+ * @property {boolean} isPlaceholder true when the value matches a placeholder shape
30
+ * @property {PlaceholderMatch['pattern']} [pattern] the matched pattern name, when isPlaceholder is true
31
+ * @property {string} [reason] a short refusal-message-ready reason string
32
+ */
33
+
34
+ // Case-insensitive named stand-ins a developer would reasonably type. The
35
+ // set is intentionally small; adding to it is a rcf-lite minor bump, and
36
+ // the additions land in ONE place downstream projects inherit.
37
+ const NAMED_STAND_INS = new Set([
38
+ 'changeme',
39
+ 'placeholder',
40
+ 'example',
41
+ 'test',
42
+ 'dummy',
43
+ 'fake',
44
+ 'todo',
45
+ 'tbd',
46
+ 'sample',
47
+ 'default',
48
+ ]);
49
+
50
+ // `YOUR_X_HERE`, `your-key-here`, etc. Case-insensitive.
51
+ const YOU_HERE_RE = /^your[_-].+[_-]here$/i;
52
+
53
+ // Repeated single characters ("xxxx", "----", "0000") of length >= 3, or
54
+ // the literal "xxx+" family common in scaffolds.
55
+ const REPEAT_CHAR_RE = /^(.)\1{2,}$/;
56
+
57
+ // Literal null-token strings that leak in when a resolver hands back a
58
+ // nullish value stringified. `.env` files stringify unset values to the
59
+ // empty string, but a JS resolver that String()s an undefined lands
60
+ // "undefined" in the ship environment - that IS a placeholder, not a
61
+ // live credential.
62
+ const NULL_TOKENS = new Set(['null', 'undefined', 'none']);
63
+
64
+ /**
65
+ * Detect whether `value` matches a placeholder-shape a credential value
66
+ * would never legitimately hold. Deterministic; case-insensitive against
67
+ * the named-stand-in catalogue and the `YOUR_X_HERE` pattern.
68
+ *
69
+ * The intent is a hard refusal filter, not a heuristic: every match is
70
+ * something a developer typed as a stand-in and forgot to replace. False
71
+ * positives are vanishingly unlikely; the shortest name-family entry is
72
+ * six characters ("dummy" being five is the shortest, and no real credential
73
+ * is a five-character English word); the repeat-char rule requires length
74
+ * >= 3 so a two-character hash prefix does not match.
75
+ *
76
+ * @param {unknown} value the raw credential-field value at ship time
77
+ * @returns {PlaceholderVerdict}
78
+ */
79
+ export function detectPlaceholderCredentialShape(value) {
80
+ // Non-strings are not credentials the shipped runtime carries; skip.
81
+ if (typeof value !== 'string') {
82
+ return { isPlaceholder: false };
83
+ }
84
+ const raw = value;
85
+ const trimmed = raw.trim();
86
+ if (trimmed.length === 0) {
87
+ return { isPlaceholder: true, pattern: 'empty', reason: 'empty or whitespace-only credential value' };
88
+ }
89
+ if (trimmed === '-' || trimmed === '_') {
90
+ return { isPlaceholder: true, pattern: 'single-dash', reason: `credential value is the single-character stand-in "${trimmed}"` };
91
+ }
92
+ const lower = trimmed.toLowerCase();
93
+ if (NULL_TOKENS.has(lower)) {
94
+ return { isPlaceholder: true, pattern: 'null-token', reason: `credential value is the literal null-token string "${trimmed}"` };
95
+ }
96
+ if (YOU_HERE_RE.test(trimmed)) {
97
+ return { isPlaceholder: true, pattern: 'you-here', reason: `credential value matches the YOUR_X_HERE scaffold pattern: "${trimmed}"` };
98
+ }
99
+ if (NAMED_STAND_INS.has(lower)) {
100
+ return { isPlaceholder: true, pattern: 'named-stand-in', reason: `credential value is the named stand-in "${trimmed}"` };
101
+ }
102
+ if (REPEAT_CHAR_RE.test(trimmed)) {
103
+ return { isPlaceholder: true, pattern: 'repeat-char', reason: `credential value is a repeated-single-character stand-in "${trimmed}"` };
104
+ }
105
+ return { isPlaceholder: false };
106
+ }
107
+
108
+ /**
109
+ * The catalogue's stable identifier. Downstream probes surface this on
110
+ * their report so the gate-failure trail names which detector version
111
+ * refused the value. Extending the catalogue bumps this constant.
112
+ */
113
+ export const PLACEHOLDER_DETECTOR_VERSION = '1.0.0';
@@ -180,7 +180,7 @@ export async function writeMarkComplete({
180
180
  return rcfError({
181
181
  kind: 'usage',
182
182
  message: `design --mark-complete: refused - ${fbsId} designStage is missing: ${missing.join(', ')}. `
183
- + 'Author the missing artefacts first via `rcf design ' + fbsId + ' journeys add ...`, `nav set ...`, or `theme-a11y set ...`.',
183
+ + 'Author the missing artefacts first via `rcf define design ' + fbsId + ' journeys add ...`, `nav set ...`, or `theme-a11y set ...`.',
184
184
  documentId: fbsId,
185
185
  });
186
186
  }
@@ -189,8 +189,8 @@ export async function writeMarkComplete({
189
189
  return rcfError({
190
190
  kind: 'usage',
191
191
  message: `design --mark-complete: refused - ${fbsId} designStage.${disagreement.designStagePath} = ${JSON.stringify(disagreement.designValue)} conflicts with uiBaseline.defaults.${disagreement.path} = ${JSON.stringify(disagreement.baselineValue)} and there is no operatorOptOuts entry. `
192
- + 'Options: (1) change designStage to match, (2) rcf ui-baseline opt-out --field '
193
- + disagreement.path + ' --reason "..." (project override), (3) rcf update '
192
+ + 'Options: (1) change designStage to match, (2) rcf discover ui-baseline opt-out --field '
193
+ + disagreement.path + ' --reason "..." (project override), (3) rcf define update '
194
194
  + fbsId + ' --set designStage.' + disagreement.designStagePath + '=... (per-FBS override).',
195
195
  documentId: fbsId,
196
196
  });
@@ -1,36 +1,35 @@
1
- // rcf-verify install detection (spec §8.3, amendment 5 - install-together
2
- // posture). The finalise gate MUST detect whether `rcf-verify` is resolvable
3
- // and, when it is absent, prompt to install it - NEVER silently skip the ship
4
- // gate (the one behaviour §8.3 explicitly forbids).
1
+ // rcf verify install detection (spec §8.3, amendment 5 - install-together
2
+ // posture). The finalise gate MUST detect whether the `rcf` bin (which
3
+ // carries the `verify` group) is resolvable and, when it is absent,
4
+ // prompt to install rcf-lite - NEVER silently skip the ship gate (the
5
+ // one behaviour §8.3 explicitly forbids).
5
6
  //
6
- // Two detection routes, in order, matching how the CLI is actually installed:
7
- // 1. The `rcf-verify` bin on PATH - the umbrella install exposes both `rcf`
8
- // and `rcf-verify` as global bins (`npm i -g rcf-lite`).
9
- // 2. Package resolution from the project dir - the local-project install
10
- // (`npm i rcf-lite` in a repo's node_modules). Two package specifiers
11
- // are tried in order: the current umbrella `rcf-lite`, then the legacy
12
- // `@stravica-ai/rcf-verify-lite` name (deprecated at 0.7.1 but still
13
- // resolvable in lockfiles that pinned it). Either resolves to the same
14
- // `bin/rcf-verify.js` entry point.
15
- // Either route yields a concrete invocation the finalise spawn (spawn.js)
16
- // uses verbatim. A miss returns { installed:false } and the caller enters
17
- // the prompt-or-explicit-flag path.
7
+ // The 0.10.0 CLI reorganisation folded the standalone `rcf-verify` bin
8
+ // into the umbrella `rcf` CLI as the `verify` group. Detection now
9
+ // resolves the `rcf` bin exclusively; there is no legacy fallback.
10
+ //
11
+ // Two detection routes, in order:
12
+ // 1. The `rcf` bin on PATH - the umbrella install exposes `rcf` as a
13
+ // global bin (`npm i -g rcf-lite`).
14
+ // 2. Package resolution from the project dir - the local-project
15
+ // install (`npm i rcf-lite` in a repo's node_modules) resolves the
16
+ // `rcf-lite` package and its `bin/rcf.js` entry point.
17
+ // Either route yields a concrete invocation the finalise spawn
18
+ // (spawn.js) uses verbatim. A miss returns { installed:false } and the
19
+ // caller enters the prompt-or-explicit-flag path.
18
20
 
19
21
  import { access, constants } from 'node:fs/promises';
20
22
  import { createRequire } from 'node:module';
21
23
  import { delimiter, dirname, join, resolve } from 'node:path';
22
24
 
23
- // VERIFY_PACKAGE is the current install target and the name shown to
24
- // operators in absent-verify messaging. VERIFY_PACKAGE_LEGACY is only used
25
- // as a resolution fallback for lockfiles still pinning the pre-0.7.1 scoped
26
- // name; it is never displayed as an install target.
25
+ // VERIFY_PACKAGE is the install target and the name shown to operators
26
+ // in absent-verify messaging.
27
27
  const VERIFY_PACKAGE = 'rcf-lite';
28
- const VERIFY_PACKAGE_LEGACY = '@stravica-ai/rcf-verify-lite';
29
- const VERIFY_PACKAGE_CANDIDATES = [VERIFY_PACKAGE, VERIFY_PACKAGE_LEGACY];
30
- const VERIFY_BIN = 'rcf-verify';
28
+ const VERIFY_PACKAGE_CANDIDATES = [VERIFY_PACKAGE];
29
+ const VERIFY_BIN = 'rcf';
31
30
 
32
31
  /**
33
- * The concrete way to launch rcf-verify as a fresh subprocess.
32
+ * The concrete way to launch the `rcf` bin as a fresh subprocess.
34
33
  * - `{ command: '<abs-bin>', prefixArgs: [] }` for a PATH / shim bin
35
34
  * (its own shebang runs it).
36
35
  * - `{ command: process.execPath, prefixArgs: ['<abs-entry.js>'] }` for a
@@ -43,8 +42,8 @@ const VERIFY_BIN = 'rcf-verify';
43
42
  */
44
43
 
45
44
  /**
46
- * Scan PATH for an executable named `rcf-verify` (plus the Windows
47
- * `.cmd`/`.exe` shim variants). Returns the first absolute path that exists
45
+ * Scan PATH for an executable named `rcf` (plus the Windows `.cmd` /
46
+ * `.exe` shim variants). Returns the first absolute path that exists
48
47
  * and is executable, or null.
49
48
  *
50
49
  * @param {string} name
@@ -78,16 +77,11 @@ export async function findOnPath(name, { env = process.env } = {}) {
78
77
  }
79
78
 
80
79
  /**
81
- * Resolve the rcf-verify bin entry point from a starting directory, following
82
- * the normal node_modules resolution the caller's project sees. Returns the
83
- * absolute path to `bin/rcf-verify.js`, or null if no candidate package is
80
+ * Resolve the `rcf` bin entry point from a starting directory, following
81
+ * the normal node_modules resolution the caller's project sees. Returns
82
+ * the absolute path to `bin/rcf.js`, or null if the package is not
84
83
  * installed / resolvable from there.
85
84
  *
86
- * Candidates are tried in the order defined by VERIFY_PACKAGE_CANDIDATES: the
87
- * current umbrella (`rcf-lite`) first, then the deprecated scoped name
88
- * (`@stravica-ai/rcf-verify-lite`) as a fallback for lockfiles pinned to it.
89
- * The first candidate whose bin resolves and exists on disk wins.
90
- *
91
85
  * @param {string} fromDir - directory to resolve from (the project root / cwd)
92
86
  * @returns {Promise<string|null>}
93
87
  */
@@ -119,9 +113,9 @@ export async function resolvePackageBin(fromDir) {
119
113
  }
120
114
 
121
115
  /**
122
- * Detect whether rcf-verify is resolvable and, if so, how to launch it.
123
- * Deps are injectable so the finalise gate can be exercised without a real
124
- * rcf-verify on the test machine.
116
+ * Detect whether the `rcf` bin (carrying the `verify` group) is
117
+ * resolvable and, if so, how to launch it. Deps are injectable so the
118
+ * finalise gate can be exercised without a real installed rcf-lite.
125
119
  *
126
120
  * @param {object} [deps]
127
121
  * @param {string} [deps.cwd] - project dir to resolve a local install from
@@ -148,4 +142,4 @@ export async function detectVerify(deps = {}) {
148
142
  return { installed: false, invocation: null };
149
143
  }
150
144
 
151
- export { VERIFY_PACKAGE, VERIFY_PACKAGE_LEGACY, VERIFY_PACKAGE_CANDIDATES, VERIFY_BIN };
145
+ export { VERIFY_PACKAGE, VERIFY_PACKAGE_CANDIDATES, VERIFY_BIN };
@@ -11,7 +11,6 @@ export {
11
11
  findOnPath,
12
12
  resolvePackageBin,
13
13
  VERIFY_PACKAGE,
14
- VERIFY_PACKAGE_LEGACY,
15
14
  VERIFY_PACKAGE_CANDIDATES,
16
15
  VERIFY_BIN,
17
16
  } from './detect.js';
@@ -1,5 +1,6 @@
1
- // Absent-verify handling (spec §8.3, amendment 5). When `rcf-verify` is not
2
- // resolvable, the finalise gate MUST NOT silently skip - that recreates the
1
+ // Absent-verify handling (spec §8.3, amendment 5). When the `rcf` bin
2
+ // (which now carries the `verify` group post-0.10.0) is not resolvable,
3
+ // the finalise gate MUST NOT silently skip - that recreates the
3
4
  // false-confidence failure the whole programme exists to prevent. The
4
5
  // sanctioned paths are exactly two, and nothing else:
5
6
  // - interactive TTY -> prompt the operator to install now (y/N);
@@ -35,8 +36,8 @@ export function promptYesNo(question, { input = process.stdin, output = process.
35
36
  }
36
37
 
37
38
  /**
38
- * Install rcf-verify globally (matches the install-together default of two
39
- * global bins). Streams npm's output to the operator. Returns the install
39
+ * Install rcf-lite globally so the `rcf verify` group becomes
40
+ * resolvable. Streams npm's output to the operator. Returns the install
40
41
  * process exit code.
41
42
  *
42
43
  * @param {object} [deps]
@@ -77,11 +78,11 @@ export async function resolveAbsentVerify({ installFlag, isTty }, io, deps = {})
77
78
  let wantsInstall = false;
78
79
  if (installFlag) {
79
80
  // Explicit flag: the sanctioned non-interactive install path.
80
- io.stdout.write(`[finalise] rcf-verify not found; installing ${VERIFY_PACKAGE} (--install-verify)...\n`);
81
+ io.stdout.write(`[finalise] the rcf CLI (with verify) not found; installing ${VERIFY_PACKAGE} (--install-verify)...\n`);
81
82
  wantsInstall = true;
82
83
  } else if (isTty) {
83
84
  // Interactive: prompt. Declining aborts (never a silent skip).
84
- io.stderr.write(`[finalise] rcf-verify is not installed. The ship gate cannot run without it.\n`);
85
+ io.stderr.write(`[finalise] rcf-lite is not installed. The ship gate cannot run without it.\n`);
85
86
  wantsInstall = await prompt(`Install ${VERIFY_PACKAGE} now? [y/N]`, {
86
87
  input: io.input, output: io.output,
87
88
  });
@@ -89,7 +90,7 @@ export async function resolveAbsentVerify({ installFlag, isTty }, io, deps = {})
89
90
  return {
90
91
  action: 'abort',
91
92
  code: 4,
92
- reason: `finalise refused: rcf-verify is required and install was declined. `
93
+ reason: `finalise refused: rcf-lite is required and install was declined. `
93
94
  + `Install it (npm i -g ${VERIFY_PACKAGE}) or re-run with --install-verify. `
94
95
  + `The ship gate is never skipped.`,
95
96
  };
@@ -100,7 +101,7 @@ export async function resolveAbsentVerify({ installFlag, isTty }, io, deps = {})
100
101
  return {
101
102
  action: 'abort',
102
103
  code: 4,
103
- reason: `finalise refused: rcf-verify is not installed and no TTY is available to prompt. `
104
+ reason: `finalise refused: rcf-lite is not installed and no TTY is available to prompt. `
104
105
  + `Install it (npm i -g ${VERIFY_PACKAGE}) or re-run with --install-verify. `
105
106
  + `The ship gate is never silently skipped.`,
106
107
  };
@@ -1,9 +1,10 @@
1
- // Fresh-subprocess invocation of `rcf-verify run` (spec §8.2). This is the
2
- // load-bearing independence guarantee: build-lite's finalise step launches
3
- // verify as a SEPARATE OS PROCESS with the isolation env - it MUST NOT import
4
- // verify's engine in-process, because the verifier agent must start from a
5
- // cold session with zero build context (§9). The subprocess exit code is the
6
- // gate; findings flow back via the --out report file, never stdout scraping.
1
+ // Fresh-subprocess invocation of `rcf verify run` (spec §8.2). This is
2
+ // the load-bearing independence guarantee: build-lite's finalise step
3
+ // launches verify as a SEPARATE OS PROCESS with the isolation env - it
4
+ // MUST NOT import verify's engine in-process, because the verifier
5
+ // agent must start from a cold session with zero build context (§9).
6
+ // The subprocess exit code is the gate; findings flow back via the
7
+ // --out report file, never stdout scraping.
7
8
  //
8
9
  // The isolation env (core's ISOLATION_RECIPE, §7.3) is layered onto the child
9
10
  // env here so the recipe travels with the process boundary exactly as §8.2
@@ -14,9 +15,11 @@ import { spawn } from 'node:child_process';
14
15
  import { isolationEnv } from '#core/isolation';
15
16
 
16
17
  /**
17
- * Assemble the `rcf-verify run` argument vector from finalise options.
18
- * Deterministic and pure so it is directly unit-testable; the exact shape is
19
- * the §8.2 invocation contract.
18
+ * Assemble the `rcf verify run` argument vector from finalise options.
19
+ * Deterministic and pure so it is directly unit-testable; the exact
20
+ * shape is the §8.2 invocation contract. The 0.10.0 CLI reorganisation
21
+ * prepends the `verify` group token; the run-verb arguments below are
22
+ * otherwise unchanged.
20
23
  *
21
24
  * @param {object} opts
22
25
  * @param {string} opts.repo - RCF chain source (the project root)
@@ -32,6 +35,7 @@ import { isolationEnv } from '#core/isolation';
32
35
  */
33
36
  export function buildVerifyArgs(opts) {
34
37
  const args = [
38
+ 'verify',
35
39
  'run',
36
40
  '--repo', opts.repo,
37
41
  '--profile', opts.profile,
@@ -47,7 +51,7 @@ export function buildVerifyArgs(opts) {
47
51
  }
48
52
 
49
53
  /**
50
- * Spawn rcf-verify as a fresh subprocess and resolve with its exit code.
54
+ * Spawn `rcf verify run` as a fresh subprocess and resolve with its exit code.
51
55
  * stdio is inherited by default so the operator sees verify's live progress
52
56
  * and its verdict line; the report artifact is read separately by the caller.
53
57
  *
package/src/mcp/tools.js CHANGED
@@ -69,7 +69,7 @@ const ISSUE_SCHEMA = {
69
69
  // success branch stays the verbatim envelope transcription (D8/D9).
70
70
  const ERROR_PAYLOAD_SCHEMA = {
71
71
  type: 'object',
72
- description: 'Tool execution error payload (accompanies isError: true): the same issue shape rcf validate --json ships',
72
+ description: 'Tool execution error payload (accompanies isError: true): the same issue shape rcf define validate --json ships',
73
73
  properties: {
74
74
  ok: { const: false },
75
75
  errors: { type: 'array', items: ISSUE_SCHEMA },
@@ -122,7 +122,7 @@ function formatTraceTable(result) {
122
122
  lines.push(`${directionLabel}: (none)`);
123
123
  } else {
124
124
  for (const n of showList) {
125
- rows.push([String(n.depth), n.id, n.kind, cellTitle(n.id)]);
125
+ rows.push([String(n.depth), n.id, n.kind, n.title ?? '']);
126
126
  }
127
127
  lines.push(renderTable(rows));
128
128
  }
@@ -139,7 +139,7 @@ function formatBothTraceTable(result) {
139
139
  lines.push(' (none)');
140
140
  } else {
141
141
  const rows = [['Depth', 'Id', 'Kind', 'Title']];
142
- for (const n of ancestors) rows.push([String(n.depth), n.id, n.kind, '']);
142
+ for (const n of ancestors) rows.push([String(n.depth), n.id, n.kind, n.title ?? '']);
143
143
  lines.push(renderTable(rows));
144
144
  }
145
145
  lines.push('');
@@ -151,7 +151,7 @@ function formatBothTraceTable(result) {
151
151
  lines.push(' (none)');
152
152
  } else {
153
153
  const rows = [['Depth', 'Id', 'Kind', 'Title']];
154
- for (const n of descendants) rows.push([String(n.depth), n.id, n.kind, '']);
154
+ for (const n of descendants) rows.push([String(n.depth), n.id, n.kind, n.title ?? '']);
155
155
  lines.push(renderTable(rows));
156
156
  }
157
157
  return `${lines.join('\n')}\n`;
@@ -194,10 +194,7 @@ function renderTable(rows) {
194
194
  return [out[0], sep, ...out.slice(1)].join('\n');
195
195
  }
196
196
 
197
- // Placeholder for future title lookup - the pure trace result does not
198
- // carry doc bodies, so title columns render blank unless the caller
199
- // passes a title source. Left here for the trace `Title` column so the
200
- // column stays if we ever wire in a title map without a schema change.
201
- function cellTitle(_id) {
202
- return '';
203
- }
197
+ // Trace nodes carry `title` as of the paper-cut batch (previously the
198
+ // Title column was always empty). Doc-kind title source: PRD →
199
+ // productName, REQ/US/TAC/ADR/TAD/TS/FBS/CN → title, inline AC/TC →
200
+ // description. Nodes lacking a title render blank.
@@ -32,6 +32,7 @@
32
32
  * @property {string} id
33
33
  * @property {string} kind
34
34
  * @property {number} depth - 0 for pivot; positive for descendants; negative for ancestors
35
+ * @property {string} title - display title, or '' when the node has none
35
36
  */
36
37
 
37
38
  /**
@@ -71,6 +72,41 @@ export function kindOf(tree, id) {
71
72
  return null;
72
73
  }
73
74
 
75
+ /**
76
+ * Return the display title for an id, or '' when the node carries none.
77
+ * Different doc kinds carry the title on different fields (PRD →
78
+ * productName; inline AC/TC → description; everything else → title). A
79
+ * standalone doc that lacks its title field renders blank rather than
80
+ * throwing; that keeps trace output readable on a partially-authored tree.
81
+ *
82
+ * Inline lookup: AC / TC nodes live inside their parent's inline array
83
+ * (userStory.acceptanceCriteria[] / testSuite.testCases[]), not as
84
+ * standalone entries in `tree.byId`. Resolve via parentByChild.
85
+ *
86
+ * @param {object} tree - walker TreeModel
87
+ * @param {string} id
88
+ * @param {string} kind - as returned by `kindOf`
89
+ * @returns {string}
90
+ */
91
+ export function titleOf(tree, id, kind) {
92
+ if (kind === 'ac') {
93
+ const usId = tree.parentByChild.get(id);
94
+ const us = usId ? tree.byId.get(usId) : null;
95
+ const ac = us?.acceptanceCriteria?.find((a) => a?.id === id);
96
+ return ac?.description ?? '';
97
+ }
98
+ if (kind === 'tc') {
99
+ const tsId = tree.parentByChild.get(id);
100
+ const ts = tsId ? tree.byId.get(tsId) : null;
101
+ const tc = ts?.testCases?.find((t) => t?.id === id);
102
+ return tc?.description ?? '';
103
+ }
104
+ const doc = tree.byId.get(id);
105
+ if (!doc) return '';
106
+ if (kind === 'prd') return doc.productName ?? '';
107
+ return doc.title ?? '';
108
+ }
109
+
74
110
  /**
75
111
  * Compute a trace from `id` in the requested direction. Unknown pivot
76
112
  * returns `{found: false}`; the handler layer converts this to exit 2.
@@ -134,7 +170,7 @@ export function computeTrace(tree, {
134
170
  */
135
171
  function walkForward(tree, pivot, pivotKind, includeCode = false, expandFbsDependents = false) {
136
172
  /** @type {TraceNode[]} */
137
- const nodes = [{ id: pivot, kind: pivotKind, depth: 0 }];
173
+ const nodes = [{ id: pivot, kind: pivotKind, depth: 0, title: titleOf(tree, pivot, pivotKind) }];
138
174
  /** @type {TraceEdge[]} */
139
175
  const edges = [];
140
176
  const seen = new Set([pivot]);
@@ -158,7 +194,12 @@ function walkForward(tree, pivot, pivotKind, includeCode = false, expandFbsDepen
158
194
  if (!childKind) continue;
159
195
  const nextDepth = curDepth + 1;
160
196
  depthById.set(child.id, nextDepth);
161
- nodes.push({ id: child.id, kind: childKind, depth: nextDepth });
197
+ nodes.push({
198
+ id: child.id,
199
+ kind: childKind,
200
+ depth: nextDepth,
201
+ title: titleOf(tree, child.id, childKind),
202
+ });
162
203
  queue.push(child.id);
163
204
  }
164
205
  }
@@ -178,7 +219,7 @@ function walkForward(tree, pivot, pivotKind, includeCode = false, expandFbsDepen
178
219
  */
179
220
  function walkBack(tree, pivot, pivotKind) {
180
221
  /** @type {TraceNode[]} */
181
- const nodes = [{ id: pivot, kind: pivotKind, depth: 0 }];
222
+ const nodes = [{ id: pivot, kind: pivotKind, depth: 0, title: titleOf(tree, pivot, pivotKind) }];
182
223
  /** @type {TraceEdge[]} */
183
224
  const edges = [];
184
225
  const seen = new Set([pivot]);
@@ -188,7 +229,7 @@ function walkBack(tree, pivot, pivotKind) {
188
229
  const k = kindOf(tree, toId);
189
230
  if (!k) return;
190
231
  seen.add(toId);
191
- nodes.push({ id: toId, kind: k, depth });
232
+ nodes.push({ id: toId, kind: k, depth, title: titleOf(tree, toId, k) });
192
233
  edges.push({ from: fromId, to: toId, kind: edgeKind });
193
234
  };
194
235
 
@@ -73,7 +73,7 @@ export function formatStage1RefusalMessage({ fbsId, usId, reqId, openCandidates
73
73
  const short = shortDescription(c.canonicalText);
74
74
  lines.push(` ${c.baselineKey}: ${short}`);
75
75
  }
76
- lines.push(` Resolve: rcf req-baseline sweep --req ${reqId}`);
76
+ lines.push(` Resolve: rcf discover req-baseline sweep --req ${reqId}`);
77
77
  return lines.join('\n');
78
78
  }
79
79
 
@@ -210,14 +210,14 @@ export async function writeUiBaselineOptOut({
210
210
  if (!priorBaseline) {
211
211
  return rcfError({
212
212
  kind: 'usage',
213
- message: 'ui-baseline opt-out: no uiBaseline record exists yet; run \'rcf ui-baseline init\' first.',
213
+ message: 'ui-baseline opt-out: no uiBaseline record exists yet; run \'rcf discover ui-baseline init\' first.',
214
214
  });
215
215
  }
216
216
  const normalisedField = String(field).startsWith('defaults.') ? String(field).slice('defaults.'.length) : String(field);
217
217
  if (typeof isKnownField === 'function' && !isKnownField(normalisedField)) {
218
218
  return rcfError({
219
219
  kind: 'usage',
220
- message: `ui-baseline opt-out: unknown baseline field '${normalisedField}' (see 'rcf ui-baseline show' for the field list).`,
220
+ message: `ui-baseline opt-out: unknown baseline field '${normalisedField}' (see 'rcf discover ui-baseline show' for the field list).`,
221
221
  });
222
222
  }
223
223
  const isoNow = now.toISOString();
@@ -9,7 +9,7 @@ import { formatError, isRcfError } from '#core/errors';
9
9
 
10
10
  import { readProvisionFile, cleanup } from '../provision/index.js';
11
11
 
12
- export const HELP = `Usage: rcf-verify cleanup --provision <file> [--url <app-url>]
12
+ export const HELP = `Usage: rcf verify cleanup --provision <file> [--url <app-url>]
13
13
 
14
14
  Tear down artefacts recorded in the --provision file (all prefixed
15
15
  'zzverify-'). Reports what was removed and what could not be.
@@ -14,7 +14,7 @@ import { createToolRegistry } from '../mcp/tools.js';
14
14
 
15
15
  const here = dirname(fileURLToPath(import.meta.url));
16
16
 
17
- export const HELP = `Usage: rcf-verify mcp [options]
17
+ export const HELP = `Usage: rcf verify mcp [options]
18
18
 
19
19
  Serve verify over the Model Context Protocol (local stdio). Exposes one tool,
20
20
  rcf_verify_run, over the same in-process engine the CLI uses. stdout carries
@@ -11,7 +11,7 @@ import { formatError, isRcfError } from '#core/errors';
11
11
  import { readChain } from '../chain/index.js';
12
12
  import { runProvisioning } from '../provision/index.js';
13
13
 
14
- export const HELP = `Usage: rcf-verify provision --repo <path> --url <app-url> --provision <file>
14
+ export const HELP = `Usage: rcf verify provision --repo <path> --url <app-url> --provision <file>
15
15
 
16
16
  Stand up prerequisite accounts / sandboxes / seed data for a later run.
17
17
  Credentials are written to the --provision FILE only, never echoed.
@@ -11,7 +11,7 @@ import { formatError, isRcfError } from '#core/errors';
11
11
  import { parseReport } from '../report/index.js';
12
12
  import { renderReport } from '../report/renderer.js';
13
13
 
14
- export const HELP = `Usage: rcf-verify report <report-path> [--json]
14
+ export const HELP = `Usage: rcf verify report <report-path> [--json]
15
15
 
16
16
  Re-render a prior report artifact. Default output is the human-readable
17
17
  summary; --json re-emits the parsed artifact as JSON.
@@ -29,7 +29,7 @@ const OPTION_SPEC = {
29
29
  help: { type: 'boolean' },
30
30
  };
31
31
 
32
- export const HELP = `Usage: rcf-verify run [options]
32
+ export const HELP = `Usage: rcf verify run [options]
33
33
 
34
34
  Launch a fresh-context adversarial verifier against a running app and emit a
35
35
  structured verdict stamped with the runtime it ran against.
@@ -19,13 +19,13 @@ export const DEFAULT_PID_PATH = '.rcf/view-server.pid';
19
19
  */
20
20
  export const viewServerGitignoreEntry = Object.freeze({
21
21
  path: '.rcf/view-server.pid',
22
- owner: 'rcf view --detach (view-server pid file)',
22
+ owner: 'rcf audit view --detach (view-server pid file)',
23
23
  since: '0.7.0',
24
24
  });
25
25
 
26
26
  export const viewServerLogGitignoreEntry = Object.freeze({
27
27
  path: '.rcf/view-server.log',
28
- owner: 'rcf view --detach (supervisor log)',
28
+ owner: 'rcf audit view --detach (supervisor log)',
29
29
  since: '0.7.0',
30
30
  });
31
31