rcf-lite 0.0.1 → 0.7.1

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 (342) hide show
  1. package/CHANGELOG.md +288 -0
  2. package/LICENSE +202 -0
  3. package/README.md +92 -4
  4. package/bin/rcf-verify.js +122 -0
  5. package/bin/rcf.js +174 -0
  6. package/bin/view-supervisor-child.mjs +14 -0
  7. package/fixtures/canary-manifest.json +103 -0
  8. package/guidance/README.md +25 -0
  9. package/guidance/build-cycle-playbook.md +492 -0
  10. package/guidance/build-cycle.md +40 -0
  11. package/guidance/document-model.md +45 -0
  12. package/guidance/elicitation-playbook.md +442 -0
  13. package/guidance/harness-template.md +246 -0
  14. package/guidance/managed/README.md +63 -0
  15. package/guidance/managed/agent-instructions-block.hash +1 -0
  16. package/guidance/managed/agent-instructions-block.md +220 -0
  17. package/guidance/managed/legacy-fragment-hashes.json +22 -0
  18. package/guidance/manifest.json +21 -0
  19. package/guidance/overview.md +48 -0
  20. package/guidance/persona-programme.md +44 -0
  21. package/package.json +69 -8
  22. package/rcf/adrs/adr-001.json +25 -0
  23. package/rcf/adrs/adr-002.json +25 -0
  24. package/rcf/adrs/adr-003.json +25 -0
  25. package/rcf/adrs/adr-004.json +20 -0
  26. package/rcf/adrs/adr-005.json +20 -0
  27. package/rcf/adrs/adr-006.json +25 -0
  28. package/rcf/adrs/adr-007.json +25 -0
  29. package/rcf/adrs/adr-008.json +25 -0
  30. package/rcf/adrs/adr-009.json +25 -0
  31. package/rcf/build-sequence.json +11 -0
  32. package/rcf/code-nodes/cn-001.json +14 -0
  33. package/rcf/code-nodes/cn-002.json +14 -0
  34. package/rcf/code-nodes/cn-003.json +15 -0
  35. package/rcf/code-nodes/cn-004.json +17 -0
  36. package/rcf/code-nodes/cn-005.json +19 -0
  37. package/rcf/code-nodes/cn-006.json +17 -0
  38. package/rcf/code-nodes/cn-007.json +16 -0
  39. package/rcf/code-nodes/cn-008.json +19 -0
  40. package/rcf/code-nodes/cn-009.json +16 -0
  41. package/rcf/code-nodes/cn-010.json +18 -0
  42. package/rcf/code-nodes/cn-011.json +18 -0
  43. package/rcf/code-nodes/cn-012.json +18 -0
  44. package/rcf/code-nodes/cn-013.json +19 -0
  45. package/rcf/code-nodes/cn-014.json +16 -0
  46. package/rcf/code-nodes/cn-015.json +16 -0
  47. package/rcf/code-nodes/cn-016.json +16 -0
  48. package/rcf/code-nodes/cn-017.json +16 -0
  49. package/rcf/code-nodes/cn-018.json +15 -0
  50. package/rcf/code-nodes/cn-019.json +16 -0
  51. package/rcf/code-nodes/cn-020.json +16 -0
  52. package/rcf/code-nodes/cn-021.json +16 -0
  53. package/rcf/code-nodes/cn-022.json +16 -0
  54. package/rcf/code-nodes/cn-023.json +16 -0
  55. package/rcf/code-nodes/cn-024.json +16 -0
  56. package/rcf/code-nodes/cn-025.json +16 -0
  57. package/rcf/code-nodes/cn-026.json +16 -0
  58. package/rcf/code-nodes/cn-027.json +16 -0
  59. package/rcf/code-nodes/cn-028.json +16 -0
  60. package/rcf/code-nodes/cn-029.json +19 -0
  61. package/rcf/code-nodes/cn-030.json +14 -0
  62. package/rcf/code-nodes/cn-031.json +14 -0
  63. package/rcf/code-nodes/cn-032.json +14 -0
  64. package/rcf/code-nodes/cn-033.json +14 -0
  65. package/rcf/code-nodes/cn-034.json +14 -0
  66. package/rcf/code-nodes/cn-035.json +14 -0
  67. package/rcf/code-nodes/cn-036.json +14 -0
  68. package/rcf/code-nodes/cn-037.json +14 -0
  69. package/rcf/code-nodes/cn-038.json +14 -0
  70. package/rcf/code-nodes/cn-039.json +14 -0
  71. package/rcf/code-nodes/cn-040.json +14 -0
  72. package/rcf/code-nodes/cn-041.json +14 -0
  73. package/rcf/code-nodes/cn-042.json +14 -0
  74. package/rcf/code-nodes/cn-043.json +14 -0
  75. package/rcf/code-nodes/cn-044.json +14 -0
  76. package/rcf/code-nodes/cn-045.json +14 -0
  77. package/rcf/code-nodes/cn-046.json +14 -0
  78. package/rcf/code-nodes/cn-047.json +14 -0
  79. package/rcf/code-nodes/cn-048.json +14 -0
  80. package/rcf/code-nodes/cn-049.json +14 -0
  81. package/rcf/code-nodes/cn-050.json +14 -0
  82. package/rcf/code-nodes/cn-051.json +14 -0
  83. package/rcf/code-nodes/cn-052.json +14 -0
  84. package/rcf/code-nodes/cn-053.json +14 -0
  85. package/rcf/code-nodes/cn-054.json +14 -0
  86. package/rcf/code-nodes/cn-055.json +14 -0
  87. package/rcf/code-nodes/cn-056.json +14 -0
  88. package/rcf/code-nodes/cn-057.json +14 -0
  89. package/rcf/fbs/fbs-001.json +49 -0
  90. package/rcf/fbs/fbs-002.json +42 -0
  91. package/rcf/fbs/fbs-003.json +37 -0
  92. package/rcf/fbs/fbs-004.json +39 -0
  93. package/rcf/fbs/fbs-005.json +38 -0
  94. package/rcf/fbs/fbs-006.json +48 -0
  95. package/rcf/fbs/fbs-007.json +39 -0
  96. package/rcf/fbs/fbs-008.json +40 -0
  97. package/rcf/fbs/fbs-009.json +36 -0
  98. package/rcf/fbs/fbs-010.json +41 -0
  99. package/rcf/fbs/fbs-011.json +36 -0
  100. package/rcf/fbs/fbs-012.json +46 -0
  101. package/rcf/fbs/fbs-013.json +42 -0
  102. package/rcf/fbs/fbs-014.json +49 -0
  103. package/rcf/fbs/fbs-015.json +32 -0
  104. package/rcf/manifest.json +17 -0
  105. package/rcf/prd.json +47 -0
  106. package/rcf/requirements/req-001.json +19 -0
  107. package/rcf/requirements/req-002.json +19 -0
  108. package/rcf/requirements/req-003.json +19 -0
  109. package/rcf/requirements/req-004.json +19 -0
  110. package/rcf/requirements/req-005.json +19 -0
  111. package/rcf/requirements/req-006.json +19 -0
  112. package/rcf/requirements/req-007.json +19 -0
  113. package/rcf/requirements/req-008.json +19 -0
  114. package/rcf/requirements/req-009.json +19 -0
  115. package/rcf/tacs/tac-001.json +45 -0
  116. package/rcf/tacs/tac-002.json +109 -0
  117. package/rcf/tacs/tac-003.json +40 -0
  118. package/rcf/tacs/tac-004.json +51 -0
  119. package/rcf/tacs/tac-005.json +52 -0
  120. package/rcf/tacs/tac-006.json +104 -0
  121. package/rcf/tacs/tac-007.json +38 -0
  122. package/rcf/tacs/tac-008.json +51 -0
  123. package/rcf/tad.json +51 -0
  124. package/rcf/test-suites/PENDING.md +23 -0
  125. package/rcf/test-suites/ts-001.json +38 -0
  126. package/rcf/test-suites/ts-002.json +38 -0
  127. package/rcf/test-suites/ts-003.json +43 -0
  128. package/rcf/test-suites/ts-004.json +44 -0
  129. package/rcf/test-suites/ts-005.json +30 -0
  130. package/rcf/test-suites/ts-006.json +36 -0
  131. package/rcf/test-suites/ts-007.json +43 -0
  132. package/rcf/test-suites/ts-008.json +37 -0
  133. package/rcf/test-suites/ts-009.json +38 -0
  134. package/rcf/test-suites/ts-010.json +38 -0
  135. package/rcf/test-suites/ts-011.json +44 -0
  136. package/rcf/test-suites/ts-012.json +36 -0
  137. package/rcf/test-suites/ts-013.json +38 -0
  138. package/rcf/test-suites/ts-014.json +38 -0
  139. package/rcf/test-suites/ts-015.json +38 -0
  140. package/rcf/test-suites/ts-016.json +37 -0
  141. package/rcf/test-suites/ts-017.json +38 -0
  142. package/rcf/test-suites/ts-018.json +38 -0
  143. package/rcf/test-suites/ts-019.json +37 -0
  144. package/rcf/test-suites/ts-020.json +46 -0
  145. package/rcf/test-suites/ts-021.json +46 -0
  146. package/rcf/test-suites/ts-022.json +46 -0
  147. package/rcf/test-suites/ts-023.json +46 -0
  148. package/rcf/test-suites/ts-024.json +46 -0
  149. package/rcf/test-suites/ts-025.json +52 -0
  150. package/rcf/user-stories/us-101.json +40 -0
  151. package/rcf/user-stories/us-102.json +40 -0
  152. package/rcf/user-stories/us-201.json +40 -0
  153. package/rcf/user-stories/us-202.json +40 -0
  154. package/rcf/user-stories/us-203.json +32 -0
  155. package/rcf/user-stories/us-301.json +40 -0
  156. package/rcf/user-stories/us-302.json +40 -0
  157. package/rcf/user-stories/us-303.json +40 -0
  158. package/rcf/user-stories/us-304.json +40 -0
  159. package/rcf/user-stories/us-401.json +40 -0
  160. package/rcf/user-stories/us-402.json +40 -0
  161. package/rcf/user-stories/us-403.json +40 -0
  162. package/rcf/user-stories/us-501.json +40 -0
  163. package/rcf/user-stories/us-502.json +40 -0
  164. package/rcf/user-stories/us-503.json +40 -0
  165. package/rcf/user-stories/us-601.json +40 -0
  166. package/rcf/user-stories/us-602.json +40 -0
  167. package/rcf/user-stories/us-701.json +40 -0
  168. package/rcf/user-stories/us-702.json +40 -0
  169. package/rcf/user-stories/us-801.json +49 -0
  170. package/rcf/user-stories/us-802.json +49 -0
  171. package/rcf/user-stories/us-803.json +49 -0
  172. package/rcf/user-stories/us-804.json +49 -0
  173. package/rcf/user-stories/us-805.json +49 -0
  174. package/rcf/user-stories/us-901.json +40 -0
  175. package/src/.gitkeep +0 -0
  176. package/src/browser-verify/auth-smoke.js +109 -0
  177. package/src/browser-verify/index.js +29 -0
  178. package/src/browser-verify/invariants.js +336 -0
  179. package/src/browser-verify/manifest-writer.js +189 -0
  180. package/src/browser-verify/runner.js +171 -0
  181. package/src/build/bundle.js +198 -0
  182. package/src/build/formatters/json.js +18 -0
  183. package/src/build/formatters/markdown.js +406 -0
  184. package/src/build/index.js +14 -0
  185. package/src/build/mark.js +177 -0
  186. package/src/build/queue.js +285 -0
  187. package/src/cli/browser-verify.js +231 -0
  188. package/src/cli/build.js +584 -0
  189. package/src/cli/coverage.js +219 -0
  190. package/src/cli/create.js +355 -0
  191. package/src/cli/delete.js +127 -0
  192. package/src/cli/design.js +353 -0
  193. package/src/cli/doctor.js +548 -0
  194. package/src/cli/fbs.js +171 -0
  195. package/src/cli/finalise.js +377 -0
  196. package/src/cli/guidance.js +206 -0
  197. package/src/cli/help.js +156 -0
  198. package/src/cli/impact.js +119 -0
  199. package/src/cli/init.js +282 -0
  200. package/src/cli/intake.js +153 -0
  201. package/src/cli/link.js +128 -0
  202. package/src/cli/mcp.js +160 -0
  203. package/src/cli/preflight.js +220 -0
  204. package/src/cli/read.js +162 -0
  205. package/src/cli/req-baseline.js +269 -0
  206. package/src/cli/req-classify.js +135 -0
  207. package/src/cli/review.js +295 -0
  208. package/src/cli/test-suite.js +216 -0
  209. package/src/cli/trace.js +172 -0
  210. package/src/cli/ui-baseline.js +292 -0
  211. package/src/cli/ui-classify.js +108 -0
  212. package/src/cli/update.js +197 -0
  213. package/src/cli/validate.js +168 -0
  214. package/src/cli/view.js +452 -0
  215. package/src/core/baseline-catalog/data/auth.json +42 -0
  216. package/src/core/baseline-catalog/data/http-api.json +42 -0
  217. package/src/core/baseline-catalog/data/notifications.json +33 -0
  218. package/src/core/baseline-catalog/data/persistence.json +33 -0
  219. package/src/core/baseline-catalog/data/web-ui.json +60 -0
  220. package/src/core/baseline-catalog/index.js +121 -0
  221. package/src/core/errors/index.js +167 -0
  222. package/src/core/fixtures/register-canary/canary-prompt-01.json +18 -0
  223. package/src/core/fixtures/register-canary/canary-prompt-02.json +21 -0
  224. package/src/core/fixtures/register-canary/canary-prompt-03.json +17 -0
  225. package/src/core/isolation/index.js +60 -0
  226. package/src/core/mcp/framing.js +103 -0
  227. package/src/core/mcp/index.js +8 -0
  228. package/src/core/mcp/server.js +228 -0
  229. package/src/core/patterns/register-canary.js +209 -0
  230. package/src/core/patterns/req-shapes.js +158 -0
  231. package/src/core/patterns/services.js +358 -0
  232. package/src/core/patterns/ui-shapes.js +166 -0
  233. package/src/core/store/cn-resolve.js +134 -0
  234. package/src/core/store/derive-deps.js +93 -0
  235. package/src/core/store/ids.js +78 -0
  236. package/src/core/store/index.js +20 -0
  237. package/src/core/store/init.js +255 -0
  238. package/src/core/store/loader.js +211 -0
  239. package/src/core/store/tp-resolve.js +176 -0
  240. package/src/core/store/validator.js +191 -0
  241. package/src/core/store/walker.js +898 -0
  242. package/src/core/store/writer.js +1849 -0
  243. package/src/design/index.js +11 -0
  244. package/src/design/writer.js +271 -0
  245. package/src/finalise/detect.js +129 -0
  246. package/src/finalise/index.js +17 -0
  247. package/src/finalise/ingest.js +119 -0
  248. package/src/finalise/install.js +119 -0
  249. package/src/finalise/ship-without-verified.js +131 -0
  250. package/src/finalise/spawn.js +84 -0
  251. package/src/intake/fidelity.js +105 -0
  252. package/src/intake/index.js +6 -0
  253. package/src/intake/manifest-writer.js +100 -0
  254. package/src/intake/orchestrator.js +138 -0
  255. package/src/intake/validate.js +80 -0
  256. package/src/mcp/map-errors.js +131 -0
  257. package/src/mcp/prompts.js +49 -0
  258. package/src/mcp/resources.js +244 -0
  259. package/src/mcp/tools.js +1204 -0
  260. package/src/preflight/design-shapes.js +185 -0
  261. package/src/preflight/index.js +90 -0
  262. package/src/preflight/manifest-writer.js +264 -0
  263. package/src/preflight/scanner.js +206 -0
  264. package/src/preflight/secrets.js +134 -0
  265. package/src/preflight/session.js +246 -0
  266. package/src/query/attestation.js +285 -0
  267. package/src/query/coverage.js +308 -0
  268. package/src/query/formatters/json.js +21 -0
  269. package/src/query/formatters/mermaid.js +209 -0
  270. package/src/query/formatters/table.js +203 -0
  271. package/src/query/impact.js +173 -0
  272. package/src/query/index.js +9 -0
  273. package/src/query/trace.js +345 -0
  274. package/src/register-canary/fixture-loader.js +87 -0
  275. package/src/register-canary/index.js +10 -0
  276. package/src/register-canary/record-writer.js +132 -0
  277. package/src/register-canary/runner.js +156 -0
  278. package/src/req-baseline/gate.js +86 -0
  279. package/src/req-baseline/index.js +27 -0
  280. package/src/req-baseline/open-candidates.js +143 -0
  281. package/src/req-baseline/opt-out.js +195 -0
  282. package/src/req-baseline/sweep.js +230 -0
  283. package/src/req-detection/classifier.js +181 -0
  284. package/src/req-detection/index.js +9 -0
  285. package/src/req-detection/persist.js +55 -0
  286. package/src/review/index.js +325 -0
  287. package/src/review/mutation.js +117 -0
  288. package/src/review/ui-baseline-drift.js +138 -0
  289. package/src/server/index.js +178 -0
  290. package/src/server/routes.js +110 -0
  291. package/src/server/sse.js +118 -0
  292. package/src/setup/agent-setup.js +362 -0
  293. package/src/setup/identity-seed.js +104 -0
  294. package/src/setup/knowledge-seed.js +123 -0
  295. package/src/setup/managed-block.js +193 -0
  296. package/src/setup/managed-gitignore.js +166 -0
  297. package/src/setup/managed-markers.js +49 -0
  298. package/src/ui-baseline/defaults.js +119 -0
  299. package/src/ui-baseline/index.js +25 -0
  300. package/src/ui-baseline/manifest-writer.js +282 -0
  301. package/src/ui-baseline/session.js +178 -0
  302. package/src/ui-detection/classifier.js +192 -0
  303. package/src/verify/chain/index.js +190 -0
  304. package/src/verify/cli/cleanup.js +61 -0
  305. package/src/verify/cli/help.js +56 -0
  306. package/src/verify/cli/mcp.js +98 -0
  307. package/src/verify/cli/provision.js +71 -0
  308. package/src/verify/cli/report.js +71 -0
  309. package/src/verify/cli/run.js +155 -0
  310. package/src/verify/engine/brief.js +87 -0
  311. package/src/verify/engine/index.js +177 -0
  312. package/src/verify/engine/launcher.js +307 -0
  313. package/src/verify/mcp/tools.js +107 -0
  314. package/src/verify/profile/index.js +146 -0
  315. package/src/verify/provision/index.js +256 -0
  316. package/src/verify/report/index.js +139 -0
  317. package/src/verify/report/renderer.js +118 -0
  318. package/src/verify/verdict/index.js +246 -0
  319. package/src/view/doc-renderers/adr.js +44 -0
  320. package/src/view/doc-renderers/build-sequence.js +40 -0
  321. package/src/view/doc-renderers/fbs.js +128 -0
  322. package/src/view/doc-renderers/helpers.js +159 -0
  323. package/src/view/doc-renderers/index.js +12 -0
  324. package/src/view/doc-renderers/prd.js +45 -0
  325. package/src/view/doc-renderers/req.js +43 -0
  326. package/src/view/doc-renderers/tac.js +38 -0
  327. package/src/view/doc-renderers/tad.js +74 -0
  328. package/src/view/doc-renderers/test-suite.js +45 -0
  329. package/src/view/doc-renderers/user-story.js +63 -0
  330. package/src/view/html-page.js +462 -0
  331. package/src/view/index.js +63 -0
  332. package/src/view/live-client.js +338 -0
  333. package/src/view/mermaid-diagram.js +178 -0
  334. package/src/view/style.css +735 -0
  335. package/src/view/tree-model.js +152 -0
  336. package/src/view/vendored/mermaid.min.js +2607 -0
  337. package/src/view-supervisor/index.js +26 -0
  338. package/src/view-supervisor/logs.js +32 -0
  339. package/src/view-supervisor/manifest-writer.js +178 -0
  340. package/src/view-supervisor/persist-until.js +85 -0
  341. package/src/view-supervisor/supervisor.js +276 -0
  342. package/src/watch/index.js +152 -0
@@ -0,0 +1,358 @@
1
+ // Third-party service seed pattern set - single source of truth for
2
+ // service-dependency detection across the 0.7.0 verification-integrity
3
+ // train.
4
+ //
5
+ // Consumed by:
6
+ // - `packages/rcf-lite/src/preflight/scanner.js` (Track A pre-flight service
7
+ // candidate scanner, verification-integrity-cluster-spec §4.3 / §8.3);
8
+ // the scanner walks PRD + TAD prose, calls `matchServiceSignals(text)`,
9
+ // and packages each hit into a `preFlightConfig.servicesInScope[]`
10
+ // candidate with per-match provenance (doc path, section anchor, the
11
+ // matched phrase and its category).
12
+ // - `packages/rcf-lite/src/verify/provision/index.js` (verify's provisioning
13
+ // heuristic, spec §5.3 / §8.4); verify's existing `SERVICE_PATTERNS`
14
+ // regex is superseded by this shared set so the "email channel" miss
15
+ // that motivated the whole cluster (d-2026-07-30-142 investigation)
16
+ // cannot recur from regex drift between packages.
17
+ //
18
+ // The pattern set is data, not a classifier: consumers own the "what field
19
+ // the signal came from" packaging and any interactive-session grouping.
20
+ // This module exposes the categorised keyword sets plus a convenience
21
+ // matcher that returns per-hit provenance in document order.
22
+ //
23
+ // Match rules per spec §4.4:
24
+ // - Case-insensitive.
25
+ // - Word-boundary anchored on the leading side (the matcher wraps every
26
+ // pattern as `\b<pattern>`, mirroring the sibling ui-shapes / req-shapes
27
+ // idiom); stem-suffixed forms (`emails`, `webhooks`, `providers`) match
28
+ // by design.
29
+ // - Any single match triggers a service candidate. False positives are
30
+ // cheap (the interactive session dismisses them); false negatives are
31
+ // the failure mode this seed set exists to close (`email channel` was
32
+ // missed by the old regex — d-142 rec 2).
33
+ // - Categorisation is non-exclusive: the same phrase (`provider`, `api`)
34
+ // legitimately fires under multiple categories, and every hit is
35
+ // recorded independently so the scanner can present them as separate
36
+ // candidates for the operator to merge or dismiss.
37
+ //
38
+ // Interpretation of the spec's "× verbs" shorthand (`storageCdn`, `llmAi`,
39
+ // `analyticsTelemetry`, `featureFlags`): where §4.4 writes "× verbs"
40
+ // without enumerating a per-category verb list, the shared
41
+ // `GENERIC_SERVICE_VERBS` set below is used — the union of every explicit
42
+ // verb spelled out by the four categories that DO enumerate (email,
43
+ // payment, sms/voice, auth, search). This is the sole interpretation call
44
+ // in transcribing §4.4; every token, vendor and explicitly-enumerated verb
45
+ // is ship-verbatim.
46
+
47
+ /**
48
+ * @typedef {'emailDelivery'|'payment'|'smsVoice'|'auth'|'storageCdn'|'llmAi'|'analyticsTelemetry'|'featureFlags'|'search'} ServiceCategory
49
+ */
50
+
51
+ /**
52
+ * @typedef {'token'|'verb'|'vendor'} ServiceSignalRole
53
+ */
54
+
55
+ /**
56
+ * @typedef {object} ServiceSignalMatch
57
+ * @property {ServiceCategory} category
58
+ * @property {ServiceSignalRole} role
59
+ * @property {string} pattern source pattern string that matched
60
+ * @property {string} match exact substring the pattern captured
61
+ * @property {number} index offset of the match inside `text`
62
+ */
63
+
64
+ // Canonical category id list. Order is spec §4.4 document order so the
65
+ // scanner's presentation to the operator stays stable across runs.
66
+ export const SERVICE_CATEGORY_KEYS = Object.freeze([
67
+ 'emailDelivery',
68
+ 'payment',
69
+ 'smsVoice',
70
+ 'auth',
71
+ 'storageCdn',
72
+ 'llmAi',
73
+ 'analyticsTelemetry',
74
+ 'featureFlags',
75
+ 'search',
76
+ ]);
77
+
78
+ // Shared verb set used by every category whose spec §4.4 entry writes
79
+ // "× verbs" without an explicit enumeration. Composed as the union of every
80
+ // verb the spec DOES enumerate, in first-appearance document order.
81
+ // Exported for tests and for consumers that want to reason about the
82
+ // interpretation call directly.
83
+ export const GENERIC_SERVICE_VERBS = Object.freeze([
84
+ 'send',
85
+ 'deliver',
86
+ 'dispatch',
87
+ 'notify',
88
+ 'route',
89
+ 'channel',
90
+ 'provider',
91
+ 'processor',
92
+ 'gateway',
93
+ 'api',
94
+ ]);
95
+
96
+ /**
97
+ * The nine-category third-party-service seed set, transcribed verbatim from
98
+ * verification-integrity-cluster-spec §4.4. Each category exposes three
99
+ * frozen arrays:
100
+ *
101
+ * - `tokens` the generic terms that name the service concept.
102
+ * - `verbs` the actions that co-occur with tokens in service-mentioning
103
+ * prose; equals `GENERIC_SERVICE_VERBS` for the four
104
+ * categories whose spec entry writes "× verbs".
105
+ * - `vendors` the explicit vendor names spec §4.4 lists separately (empty
106
+ * for `emailDelivery`, whose §4.4 entry lists vendors inline
107
+ * with tokens — this transcription preserves that spec shape
108
+ * rather than re-sorting vendors out of the token list).
109
+ *
110
+ * Every array is `Object.freeze`d so downstream classifiers cannot mutate
111
+ * the shared source.
112
+ *
113
+ * @type {Readonly<Record<ServiceCategory, {tokens: readonly string[], verbs: readonly string[], vendors: readonly string[]}>>}
114
+ */
115
+ export const SERVICE_SEED_PATTERNS_V1 = Object.freeze({
116
+ // Email delivery §4.4: `email|smtp|mailer|resend|sendgrid|mailgun|postmark|ses\b|mailtrap|sparkpost`
117
+ // × `send|deliver|dispatch|notify|route|channel|provider`
118
+ // (Vendor names live inline in the token list per §4.4; `vendors` empty
119
+ // here preserves that spec transcription.)
120
+ emailDelivery: Object.freeze({
121
+ tokens: Object.freeze([
122
+ 'email',
123
+ 'smtp',
124
+ 'mailer',
125
+ 'resend',
126
+ 'sendgrid',
127
+ 'mailgun',
128
+ 'postmark',
129
+ 'ses\\b',
130
+ 'mailtrap',
131
+ 'sparkpost',
132
+ ]),
133
+ verbs: Object.freeze([
134
+ 'send',
135
+ 'deliver',
136
+ 'dispatch',
137
+ 'notify',
138
+ 'route',
139
+ 'channel',
140
+ 'provider',
141
+ ]),
142
+ vendors: Object.freeze([]),
143
+ }),
144
+ // Payment §4.4: `payment|checkout|billing|invoice|charge|payout|refund`
145
+ // × `provider|processor|gateway|api`;
146
+ // explicit vendors `stripe|braintree|adyen|paypal|square`.
147
+ payment: Object.freeze({
148
+ tokens: Object.freeze([
149
+ 'payment',
150
+ 'checkout',
151
+ 'billing',
152
+ 'invoice',
153
+ 'charge',
154
+ 'payout',
155
+ 'refund',
156
+ ]),
157
+ verbs: Object.freeze([
158
+ 'provider',
159
+ 'processor',
160
+ 'gateway',
161
+ 'api',
162
+ ]),
163
+ vendors: Object.freeze([
164
+ 'stripe',
165
+ 'braintree',
166
+ 'adyen',
167
+ 'paypal',
168
+ 'square',
169
+ ]),
170
+ }),
171
+ // SMS/voice §4.4: `sms|voice|call|dial` × `provider|api`;
172
+ // explicit vendors `twilio|vonage|messagebird`.
173
+ smsVoice: Object.freeze({
174
+ tokens: Object.freeze([
175
+ 'sms',
176
+ 'voice',
177
+ 'call',
178
+ 'dial',
179
+ ]),
180
+ verbs: Object.freeze([
181
+ 'provider',
182
+ 'api',
183
+ ]),
184
+ vendors: Object.freeze([
185
+ 'twilio',
186
+ 'vonage',
187
+ 'messagebird',
188
+ ]),
189
+ }),
190
+ // Auth (identity provider) §4.4: `oauth|oidc|saml|sso|social login`
191
+ // × `provider`;
192
+ // explicit vendors `auth0|clerk|okta|firebase auth|cognito|supabase auth`.
193
+ auth: Object.freeze({
194
+ tokens: Object.freeze([
195
+ 'oauth',
196
+ 'oidc',
197
+ 'saml',
198
+ 'sso',
199
+ 'social login',
200
+ ]),
201
+ verbs: Object.freeze([
202
+ 'provider',
203
+ ]),
204
+ vendors: Object.freeze([
205
+ 'auth0',
206
+ 'clerk',
207
+ 'okta',
208
+ 'firebase auth',
209
+ 'cognito',
210
+ 'supabase auth',
211
+ ]),
212
+ }),
213
+ // Storage/CDN §4.4: `s3|r2|gcs|blob storage|cdn` × verbs;
214
+ // explicit vendors `cloudflare r2|aws s3|gcs|azure blob`.
215
+ storageCdn: Object.freeze({
216
+ tokens: Object.freeze([
217
+ 's3',
218
+ 'r2',
219
+ 'gcs',
220
+ 'blob storage',
221
+ 'cdn',
222
+ ]),
223
+ verbs: GENERIC_SERVICE_VERBS,
224
+ vendors: Object.freeze([
225
+ 'cloudflare r2',
226
+ 'aws s3',
227
+ 'gcs',
228
+ 'azure blob',
229
+ ]),
230
+ }),
231
+ // LLM/AI §4.4: `llm|inference|completion|embedding|model api` × verbs;
232
+ // explicit vendors `anthropic|openai|gemini|mistral|cohere|replicate`.
233
+ llmAi: Object.freeze({
234
+ tokens: Object.freeze([
235
+ 'llm',
236
+ 'inference',
237
+ 'completion',
238
+ 'embedding',
239
+ 'model api',
240
+ ]),
241
+ verbs: GENERIC_SERVICE_VERBS,
242
+ vendors: Object.freeze([
243
+ 'anthropic',
244
+ 'openai',
245
+ 'gemini',
246
+ 'mistral',
247
+ 'cohere',
248
+ 'replicate',
249
+ ]),
250
+ }),
251
+ // Analytics/telemetry §4.4: `analytics|telemetry|event tracking` × verbs;
252
+ // explicit vendors `posthog|segment|mixpanel|amplitude|datadog`.
253
+ analyticsTelemetry: Object.freeze({
254
+ tokens: Object.freeze([
255
+ 'analytics',
256
+ 'telemetry',
257
+ 'event tracking',
258
+ ]),
259
+ verbs: GENERIC_SERVICE_VERBS,
260
+ vendors: Object.freeze([
261
+ 'posthog',
262
+ 'segment',
263
+ 'mixpanel',
264
+ 'amplitude',
265
+ 'datadog',
266
+ ]),
267
+ }),
268
+ // Feature flags §4.4: `feature flag|toggle` × verbs;
269
+ // explicit vendors `launchdarkly|growthbook|flagsmith`.
270
+ featureFlags: Object.freeze({
271
+ tokens: Object.freeze([
272
+ 'feature flag',
273
+ 'toggle',
274
+ ]),
275
+ verbs: GENERIC_SERVICE_VERBS,
276
+ vendors: Object.freeze([
277
+ 'launchdarkly',
278
+ 'growthbook',
279
+ 'flagsmith',
280
+ ]),
281
+ }),
282
+ // Search §4.4: `search|index` × `api|provider`;
283
+ // explicit vendors `algolia|elasticsearch|typesense|meilisearch`.
284
+ search: Object.freeze({
285
+ tokens: Object.freeze([
286
+ 'search',
287
+ 'index',
288
+ ]),
289
+ verbs: Object.freeze([
290
+ 'api',
291
+ 'provider',
292
+ ]),
293
+ vendors: Object.freeze([
294
+ 'algolia',
295
+ 'elasticsearch',
296
+ 'typesense',
297
+ 'meilisearch',
298
+ ]),
299
+ }),
300
+ });
301
+
302
+ // Role iteration order for the matcher; consumers reading per-hit
303
+ // provenance can rely on this being stable across releases.
304
+ const ROLES = /** @type {const} */ (['tokens', 'verbs', 'vendors']);
305
+ const ROLE_TO_SINGULAR = /** @type {const} */ ({
306
+ tokens: 'token',
307
+ verbs: 'verb',
308
+ vendors: 'vendor',
309
+ });
310
+
311
+ /**
312
+ * Scan `text` against `SERVICE_SEED_PATTERNS_V1` and return every match, in
313
+ * document order, tagged with the category and role (`token` / `verb` /
314
+ * `vendor`) that produced it. A single `text` may yield many hits — bare
315
+ * `provider` legitimately fires under email, payment, sms/voice, auth and
316
+ * search — and each hit is recorded separately so the scanner can present
317
+ * them as candidates for the operator to merge or dismiss (spec §4.5).
318
+ *
319
+ * @param {string} text
320
+ * @returns {ServiceSignalMatch[]}
321
+ */
322
+ export function matchServiceSignals(text) {
323
+ if (typeof text !== 'string' || text.length === 0) return [];
324
+ /** @type {ServiceSignalMatch[]} */
325
+ const results = [];
326
+ for (const category of SERVICE_CATEGORY_KEYS) {
327
+ const bucket = SERVICE_SEED_PATTERNS_V1[category];
328
+ for (const roleKey of ROLES) {
329
+ const patterns = bucket[roleKey];
330
+ const role = ROLE_TO_SINGULAR[roleKey];
331
+ for (const pattern of patterns) {
332
+ // Leading `\b` prevents matching inside a longer word (e.g. `sms`
333
+ // inside `smsapi` would still be blocked by `\bsms`, but `smsapi`
334
+ // as a bare id has no leading word char anyway; the anchor blocks
335
+ // things like `damsms`). NO trailing `\b`, so stem-suffixed forms
336
+ // (`emails`, `providers`, `webhooks`, `deliveries`) still match.
337
+ // Spec §4.4 over-collects by design.
338
+ const re = new RegExp(`\\b${pattern}`, 'gi');
339
+ let m;
340
+ while ((m = re.exec(text)) !== null) {
341
+ results.push({
342
+ category,
343
+ role,
344
+ pattern,
345
+ match: m[0],
346
+ index: m.index,
347
+ });
348
+ if (m.index === re.lastIndex) re.lastIndex += 1; // zero-width safety
349
+ }
350
+ }
351
+ }
352
+ }
353
+ // Document-order stable sort (matches accumulate category-by-category,
354
+ // role-by-role, then pattern-by-pattern; this sort restores overall
355
+ // offset order so the scanner walks hits left-to-right through the doc).
356
+ results.sort((a, b) => a.index - b.index);
357
+ return results;
358
+ }
@@ -0,0 +1,166 @@
1
+ // UI seed pattern set - single source of truth for UI-shape detection.
2
+ //
3
+ // Consumed by:
4
+ // - `packages/rcf-lite/src/ui-detection/classifier.js` (Track B UI-bearing FBS
5
+ // classifier, ui-design-gate-0.7.0-spec §4.3).
6
+ // - `packages/rcf-lite/src/core/patterns/req-shapes.js` (Track C+D REQ-shape
7
+ // classifier's `webUi` shape, elicitation-and-playbook-hardening-0.7.0
8
+ // spec §4.3, which requires this file as its single source of truth for
9
+ // the Web UI signal).
10
+ //
11
+ // The pattern set is data, not a classifier: consumers own the "what field
12
+ // did the signal come from" packaging and the shape of the returned
13
+ // `signals[]` array on the FBS / REQ `*Classification` block. This module
14
+ // exposes the categorised keyword sets plus a convenience matcher.
15
+ //
16
+ // Match rules per spec §4.3:
17
+ // - Case-insensitive.
18
+ // - Word-boundary anchored so `nav` does not match inside `navigate` when
19
+ // the word-boundary hits (regex `\b` handles that).
20
+ // - Any single match triggers the classifier's `ui` verdict (match count
21
+ // is not a threshold; false positives are cheap, false negatives are the
22
+ // failure mode).
23
+ // - Sentences containing an excluded phrase from `UI_EXCLUDED_PHRASES`
24
+ // have their signals suppressed (avoids the API-endpoint / CLI /
25
+ // JSON-response false positive class); ambiguous cases favour `ui`.
26
+
27
+ /**
28
+ * @typedef {'explicitUiNouns'|'htmlRenderVerbs'|'responseShapeSignals'|'authFlowSignals'|'accessibilitySignals'} UiSeedCategory
29
+ */
30
+
31
+ /**
32
+ * @typedef {object} UiSignalMatch
33
+ * @property {UiSeedCategory} category
34
+ * @property {string} pattern the source pattern string that matched
35
+ * @property {string} match the exact substring the pattern captured
36
+ * @property {number} index offset of the match inside `text`
37
+ */
38
+
39
+ // Categorised keyword sets. Each entry is a regex source string (no flags,
40
+ // no anchors); the matcher below wraps them with `\b<source>\b` and the
41
+ // `gi` flags at scan time so consumers cannot accidentally use the raw
42
+ // source without word boundaries.
43
+ export const UI_SEED_PATTERNS_V1 = Object.freeze({
44
+ explicitUiNouns: Object.freeze([
45
+ 'web ui', 'browser', 'page', 'dashboard', 'admin', 'screen', 'form',
46
+ 'route', 'nav', 'navigation', 'layout', 'style', 'theme',
47
+ 'dark mode', 'light mode',
48
+ 'component', 'button', 'table', 'badge', 'header', 'footer',
49
+ 'sidebar', 'modal', 'tooltip',
50
+ ]),
51
+ htmlRenderVerbs: Object.freeze([
52
+ 'render(?:ed|s|ing)?',
53
+ 'display(?:ed|s|ing)?',
54
+ 'shows?',
55
+ 'visible',
56
+ 'see(?:s|ing)?',
57
+ 'click(?:s|ed|ing)?',
58
+ 'tap(?:s|ped|ping)?',
59
+ ]),
60
+ responseShapeSignals: Object.freeze([
61
+ 'HTML',
62
+ 'markup',
63
+ 'template',
64
+ 'view',
65
+ 'serve.*page',
66
+ 'content-type.*text/html',
67
+ ]),
68
+ authFlowSignals: Object.freeze([
69
+ 'login form',
70
+ 'sign(?:-|\\s)?in page',
71
+ 'logout button',
72
+ 'password reset page',
73
+ 'magic(?:-|\\s)?link (?:page|landing)',
74
+ ]),
75
+ accessibilitySignals: Object.freeze([
76
+ 'contrast',
77
+ 'WCAG',
78
+ 'screen reader',
79
+ 'keyboard nav',
80
+ 'focus ring',
81
+ 'aria-',
82
+ ]),
83
+ });
84
+
85
+ // Sentences containing any of these phrases have their UI signals
86
+ // suppressed. Guards against the API-endpoint / CLI / JSON-response false
87
+ // positive class named in spec §4.3.
88
+ export const UI_EXCLUDED_PHRASES = Object.freeze([
89
+ 'API endpoint',
90
+ 'CLI command',
91
+ 'shell prompt',
92
+ 'JSON response',
93
+ ]);
94
+
95
+ // Split `text` into rough sentence-shaped chunks. Boundary is `.!?` plus a
96
+ // line break; keeps the trailing terminator with the sentence so length /
97
+ // index stays useful for downstream reporting. Deliberately unaware of
98
+ // English abbreviations (`e.g.`, `i.e.`, `Dr.`); the excluded-phrase filter
99
+ // is intended as a coarse noise gate, not a linguistic parser.
100
+ function splitIntoSentences(text) {
101
+ if (typeof text !== 'string' || text.length === 0) return [];
102
+ const chunks = [];
103
+ const re = /[^.!?\n]+[.!?\n]?/g;
104
+ let match;
105
+ while ((match = re.exec(text)) !== null) {
106
+ const chunk = match[0];
107
+ const start = match.index;
108
+ const end = start + chunk.length;
109
+ const trimmed = chunk.trim();
110
+ if (trimmed.length > 0) chunks.push({ text: chunk, start, end });
111
+ }
112
+ return chunks;
113
+ }
114
+
115
+ function sentenceContainsExcluded(sentence) {
116
+ const lower = sentence.toLowerCase();
117
+ for (const phrase of UI_EXCLUDED_PHRASES) {
118
+ if (lower.includes(phrase.toLowerCase())) return true;
119
+ }
120
+ return false;
121
+ }
122
+
123
+ /**
124
+ * Scan `text` against `UI_SEED_PATTERNS_V1` and return every match, in
125
+ * document order, with the pattern that produced it. Sentences containing
126
+ * any `UI_EXCLUDED_PHRASES` phrase are skipped in full per spec §4.3.
127
+ *
128
+ * @param {string} text
129
+ * @returns {UiSignalMatch[]}
130
+ */
131
+ export function matchUiSignals(text) {
132
+ if (typeof text !== 'string' || text.length === 0) return [];
133
+ /** @type {UiSignalMatch[]} */
134
+ const results = [];
135
+ const sentences = splitIntoSentences(text);
136
+ // Fall back to the whole text as a single "sentence" when the regex
137
+ // parse produced nothing (e.g. a text without terminators).
138
+ if (sentences.length === 0) sentences.push({ text, start: 0, end: text.length });
139
+ for (const sentence of sentences) {
140
+ if (sentenceContainsExcluded(sentence.text)) continue;
141
+ for (const category of Object.keys(UI_SEED_PATTERNS_V1)) {
142
+ for (const pattern of UI_SEED_PATTERNS_V1[category]) {
143
+ // Leading `\b` prevents matching inside a longer word (e.g. `nav`
144
+ // inside `navigate`); NO trailing `\b`, so stem-suffixed forms
145
+ // (`shows`, `dashboards`, `delivers`) still match. Spec §4.3
146
+ // over-collects by design; false-positive cost is one operator
147
+ // override, false-negative cost is a dated UI shipping unnoticed.
148
+ const re = new RegExp(`\\b${pattern}`, 'gi');
149
+ let m;
150
+ while ((m = re.exec(sentence.text)) !== null) {
151
+ results.push({
152
+ category,
153
+ pattern,
154
+ match: m[0],
155
+ index: sentence.start + m.index,
156
+ });
157
+ if (m.index === re.lastIndex) re.lastIndex += 1; // safety for zero-width
158
+ }
159
+ }
160
+ }
161
+ }
162
+ // Document-order stable sort (matches happen sentence-by-sentence, then
163
+ // pattern-by-pattern; sort restores overall offset order).
164
+ results.sort((a, b) => a.index - b.index);
165
+ return results;
166
+ }
@@ -0,0 +1,134 @@
1
+ // Phase 10 (X2 CodeNode bridge): Code Node working-tree resolution.
2
+ //
3
+ // This is the STALENESS DETECTOR - X2's headline advantage over sidecar
4
+ // approaches (S5/S6; testPointer was the same shape until tp-resolve.js
5
+ // gave it its own working-tree check for the test axis, w-2026-07-28-005).
6
+ // Every CN declares a `path` (file-level
7
+ // "src/foo.js" or symbol-level "src/foo.js#symbol"). This module checks
8
+ // each path against the CHECKED-OUT WORKING TREE and reports the CNs whose
9
+ // target no longer resolves. A rename/move/deletion that a sidecar map
10
+ // would silently carry as a dead pointer surfaces here as a `staleCode`
11
+ // error at `rcf validate` time.
12
+ //
13
+ // Determinism (spec D6/D13 hold): file existence is `fs.stat`; symbol
14
+ // presence is a fixed set of declaration-anchor regexes (function / class /
15
+ // const|let|var / method-at-line-start / object-field). No LLM, no
16
+ // semantic judgement, no parsing beyond regex. Fully reproducible given a
17
+ // working tree.
18
+ //
19
+ // HONEST LIMITATION (documented in docs/ and the PoC report): the symbol
20
+ // check proves a DECLARATION with that name exists - not that it still does
21
+ // what the AC says. A symbol renamed is caught; a symbol whose body was
22
+ // gutted but whose name survived is NOT caught (semantic drift is out of
23
+ // reach for a deterministic check, out of scope per D13). File-level CNs
24
+ // cannot detect intra-file symbol moves at all. A symbol moved to another
25
+ // file while a same-file namesake declaration survives can false-clean
26
+ // (the anchor scan has no lexical scope awareness).
27
+
28
+ import { readFile, stat } from 'node:fs/promises';
29
+ import { join } from 'node:path';
30
+
31
+ import { rcfError } from '../errors/index.js';
32
+
33
+ /**
34
+ * Split a CN path into its file part and optional symbol part.
35
+ * @param {string} cnPath e.g. "src/store/validator.js#getAjv"
36
+ * @returns {{ file: string, symbol: string | null }}
37
+ */
38
+ export function splitCnPath(cnPath) {
39
+ const hash = cnPath.indexOf('#');
40
+ if (hash < 0) return { file: cnPath, symbol: null };
41
+ return { file: cnPath.slice(0, hash), symbol: cnPath.slice(hash + 1) };
42
+ }
43
+
44
+ /**
45
+ * Build the deterministic declaration-anchor matchers for a symbol name.
46
+ * A symbol is considered PRESENT if any anchor matches the file text.
47
+ * @param {string} symbol
48
+ * @returns {RegExp[]}
49
+ */
50
+ function anchorsFor(symbol) {
51
+ // Escape regex metacharacters in the symbol (identifiers won't normally
52
+ // carry any, but `$` is a legal JS identifier char and a regex metachar).
53
+ const s = symbol.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
54
+ return [
55
+ new RegExp(`\\b(?:async\\s+)?function\\s*\\*?\\s+${s}\\b`), // function decl
56
+ new RegExp(`\\bclass\\s+${s}\\b`), // class decl
57
+ new RegExp(`\\b(?:const|let|var)\\s+${s}\\b`), // binding decl
58
+ new RegExp(`^\\s*(?:async\\s+)?(?:static\\s+)?(?:get\\s+|set\\s+)?${s}\\s*\\(`, 'm'), // method/def at line start
59
+ new RegExp(`^\\s*${s}\\s*[:=]`, 'm'), // object-literal / class-field form
60
+ ];
61
+ }
62
+
63
+ /**
64
+ * Check every Code Node in the tree against the working tree. Returns a
65
+ * list of structured `staleCode` errors (empty when all CN paths resolve).
66
+ * A per-file read cache avoids re-reading a file that multiple symbol-level
67
+ * CNs anchor into (spec D8).
68
+ *
69
+ * @param {object} args
70
+ * @param {string} args.projectRoot - absolute path to project root
71
+ * @param {object} args.tree - walker TreeModel (carries tree.codeNodes)
72
+ * @returns {Promise<import('../errors/index.js').RcfError[]>}
73
+ */
74
+ export async function checkCodeNodeResolution({ projectRoot, tree }) {
75
+ /** @type {import('../errors/index.js').RcfError[]} */
76
+ const errors = [];
77
+ const fileCache = new Map();
78
+
79
+ const readFileCached = async (absFile) => {
80
+ if (fileCache.has(absFile)) return fileCache.get(absFile);
81
+ let text = null;
82
+ try {
83
+ text = await readFile(absFile, 'utf8');
84
+ } catch {
85
+ text = null;
86
+ }
87
+ fileCache.set(absFile, text);
88
+ return text;
89
+ };
90
+
91
+ for (const cn of tree.codeNodes ?? []) {
92
+ const cnPath = cn.path ?? '';
93
+ const relFile = `rcf/code-nodes/${(cn.cnId ?? '').toLowerCase()}.json`;
94
+ const { file, symbol } = splitCnPath(cnPath);
95
+ const absFile = join(projectRoot, file);
96
+
97
+ // 1. File existence.
98
+ let fileOk = false;
99
+ try {
100
+ const st = await stat(absFile);
101
+ fileOk = st.isFile();
102
+ } catch {
103
+ fileOk = false;
104
+ }
105
+ if (!fileOk) {
106
+ errors.push(rcfError({
107
+ kind: 'staleCode',
108
+ message: `CN ${cn.cnId} path is stale: file ${file} does not exist in the working tree`,
109
+ documentId: cn.cnId,
110
+ filePath: relFile,
111
+ field: 'path',
112
+ rule: 'fileResolves',
113
+ }));
114
+ continue; // no point checking the symbol in a missing file
115
+ }
116
+
117
+ // 2. Symbol presence (deterministic anchor scan).
118
+ if (symbol) {
119
+ const text = await readFileCached(absFile);
120
+ const present = text != null && anchorsFor(symbol).some((re) => re.test(text));
121
+ if (!present) {
122
+ errors.push(rcfError({
123
+ kind: 'staleCode',
124
+ message: `CN ${cn.cnId} path is stale: symbol '${symbol}' not found in ${file} (renamed, removed, or moved to another file)`,
125
+ documentId: cn.cnId,
126
+ filePath: relFile,
127
+ field: 'path',
128
+ rule: 'symbolResolves',
129
+ }));
130
+ }
131
+ }
132
+ }
133
+ return errors;
134
+ }