@nebulacomponents/citable 1.18.2 → 1.20.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 (298) hide show
  1. package/CHANGELOG.md +64 -0
  2. package/README.md +12 -1
  3. package/dist/universal/.agents/skills/citable/SKILL.md +11 -2
  4. package/dist/universal/.agents/skills/citable/VERSION +1 -1
  5. package/dist/universal/.agents/skills/citable/manifest.json +19 -13
  6. package/dist/universal/.agents/skills/citable/schemas/action-plan.schema.json +38 -1
  7. package/dist/universal/.agents/skills/citable/schemas/audit-coverage.schema.json +118 -0
  8. package/dist/universal/.agents/skills/citable/schemas/browser-evidence-plan.schema.json +186 -3
  9. package/dist/universal/.agents/skills/citable/schemas/browser-network-events.schema.json +142 -0
  10. package/dist/universal/.agents/skills/citable/schemas/browser-runtime-events.schema.json +112 -0
  11. package/dist/universal/.agents/skills/citable/schemas/browser-state-observations.schema.json +97 -0
  12. package/dist/universal/.agents/skills/citable/schemas/collection-result.schema.json +35 -0
  13. package/dist/universal/.agents/skills/citable/schemas/config.schema.json +2 -0
  14. package/dist/universal/.agents/skills/citable/schemas/cro-report.schema.json +8 -1
  15. package/dist/universal/.agents/skills/citable/schemas/finding.schema.json +105 -2
  16. package/dist/universal/.agents/skills/citable/schemas/remediation-verification.schema.json +12 -1
  17. package/dist/universal/.agents/skills/citable/schemas/run.schema.json +15 -2
  18. package/dist/universal/.agents/skills/citable/schemas/search-report.schema.json +8 -1
  19. package/dist/universal/.agents/skills/citable/schemas/summary.schema.json +41 -0
  20. package/dist/universal/.claude/agents/citable/manifest.json +2 -2
  21. package/dist/universal/.claude/skills/citable/SKILL.md +11 -2
  22. package/dist/universal/.claude/skills/citable/VERSION +1 -1
  23. package/dist/universal/.claude/skills/citable/manifest.json +19 -13
  24. package/dist/universal/.claude/skills/citable/schemas/action-plan.schema.json +38 -1
  25. package/dist/universal/.claude/skills/citable/schemas/audit-coverage.schema.json +118 -0
  26. package/dist/universal/.claude/skills/citable/schemas/browser-evidence-plan.schema.json +186 -3
  27. package/dist/universal/.claude/skills/citable/schemas/browser-network-events.schema.json +142 -0
  28. package/dist/universal/.claude/skills/citable/schemas/browser-runtime-events.schema.json +112 -0
  29. package/dist/universal/.claude/skills/citable/schemas/browser-state-observations.schema.json +97 -0
  30. package/dist/universal/.claude/skills/citable/schemas/collection-result.schema.json +35 -0
  31. package/dist/universal/.claude/skills/citable/schemas/config.schema.json +2 -0
  32. package/dist/universal/.claude/skills/citable/schemas/cro-report.schema.json +8 -1
  33. package/dist/universal/.claude/skills/citable/schemas/finding.schema.json +105 -2
  34. package/dist/universal/.claude/skills/citable/schemas/remediation-verification.schema.json +12 -1
  35. package/dist/universal/.claude/skills/citable/schemas/run.schema.json +15 -2
  36. package/dist/universal/.claude/skills/citable/schemas/search-report.schema.json +8 -1
  37. package/dist/universal/.claude/skills/citable/schemas/summary.schema.json +41 -0
  38. package/dist/universal/.cursor/skills/citable/SKILL.md +11 -2
  39. package/dist/universal/.cursor/skills/citable/VERSION +1 -1
  40. package/dist/universal/.cursor/skills/citable/manifest.json +19 -13
  41. package/dist/universal/.cursor/skills/citable/schemas/action-plan.schema.json +38 -1
  42. package/dist/universal/.cursor/skills/citable/schemas/audit-coverage.schema.json +118 -0
  43. package/dist/universal/.cursor/skills/citable/schemas/browser-evidence-plan.schema.json +186 -3
  44. package/dist/universal/.cursor/skills/citable/schemas/browser-network-events.schema.json +142 -0
  45. package/dist/universal/.cursor/skills/citable/schemas/browser-runtime-events.schema.json +112 -0
  46. package/dist/universal/.cursor/skills/citable/schemas/browser-state-observations.schema.json +97 -0
  47. package/dist/universal/.cursor/skills/citable/schemas/collection-result.schema.json +35 -0
  48. package/dist/universal/.cursor/skills/citable/schemas/config.schema.json +2 -0
  49. package/dist/universal/.cursor/skills/citable/schemas/cro-report.schema.json +8 -1
  50. package/dist/universal/.cursor/skills/citable/schemas/finding.schema.json +105 -2
  51. package/dist/universal/.cursor/skills/citable/schemas/remediation-verification.schema.json +12 -1
  52. package/dist/universal/.cursor/skills/citable/schemas/run.schema.json +15 -2
  53. package/dist/universal/.cursor/skills/citable/schemas/search-report.schema.json +8 -1
  54. package/dist/universal/.cursor/skills/citable/schemas/summary.schema.json +41 -0
  55. package/dist/universal/.gemini/skills/citable/SKILL.md +11 -2
  56. package/dist/universal/.gemini/skills/citable/VERSION +1 -1
  57. package/dist/universal/.gemini/skills/citable/manifest.json +19 -13
  58. package/dist/universal/.gemini/skills/citable/schemas/action-plan.schema.json +38 -1
  59. package/dist/universal/.gemini/skills/citable/schemas/audit-coverage.schema.json +118 -0
  60. package/dist/universal/.gemini/skills/citable/schemas/browser-evidence-plan.schema.json +186 -3
  61. package/dist/universal/.gemini/skills/citable/schemas/browser-network-events.schema.json +142 -0
  62. package/dist/universal/.gemini/skills/citable/schemas/browser-runtime-events.schema.json +112 -0
  63. package/dist/universal/.gemini/skills/citable/schemas/browser-state-observations.schema.json +97 -0
  64. package/dist/universal/.gemini/skills/citable/schemas/collection-result.schema.json +35 -0
  65. package/dist/universal/.gemini/skills/citable/schemas/config.schema.json +2 -0
  66. package/dist/universal/.gemini/skills/citable/schemas/cro-report.schema.json +8 -1
  67. package/dist/universal/.gemini/skills/citable/schemas/finding.schema.json +105 -2
  68. package/dist/universal/.gemini/skills/citable/schemas/remediation-verification.schema.json +12 -1
  69. package/dist/universal/.gemini/skills/citable/schemas/run.schema.json +15 -2
  70. package/dist/universal/.gemini/skills/citable/schemas/search-report.schema.json +8 -1
  71. package/dist/universal/.gemini/skills/citable/schemas/summary.schema.json +41 -0
  72. package/dist/universal/.github/skills/citable/SKILL.md +11 -2
  73. package/dist/universal/.github/skills/citable/VERSION +1 -1
  74. package/dist/universal/.github/skills/citable/manifest.json +19 -13
  75. package/dist/universal/.github/skills/citable/schemas/action-plan.schema.json +38 -1
  76. package/dist/universal/.github/skills/citable/schemas/audit-coverage.schema.json +118 -0
  77. package/dist/universal/.github/skills/citable/schemas/browser-evidence-plan.schema.json +186 -3
  78. package/dist/universal/.github/skills/citable/schemas/browser-network-events.schema.json +142 -0
  79. package/dist/universal/.github/skills/citable/schemas/browser-runtime-events.schema.json +112 -0
  80. package/dist/universal/.github/skills/citable/schemas/browser-state-observations.schema.json +97 -0
  81. package/dist/universal/.github/skills/citable/schemas/collection-result.schema.json +35 -0
  82. package/dist/universal/.github/skills/citable/schemas/config.schema.json +2 -0
  83. package/dist/universal/.github/skills/citable/schemas/cro-report.schema.json +8 -1
  84. package/dist/universal/.github/skills/citable/schemas/finding.schema.json +105 -2
  85. package/dist/universal/.github/skills/citable/schemas/remediation-verification.schema.json +12 -1
  86. package/dist/universal/.github/skills/citable/schemas/run.schema.json +15 -2
  87. package/dist/universal/.github/skills/citable/schemas/search-report.schema.json +8 -1
  88. package/dist/universal/.github/skills/citable/schemas/summary.schema.json +41 -0
  89. package/dist/universal/.kiro/skills/citable/SKILL.md +11 -2
  90. package/dist/universal/.kiro/skills/citable/VERSION +1 -1
  91. package/dist/universal/.kiro/skills/citable/manifest.json +19 -13
  92. package/dist/universal/.kiro/skills/citable/schemas/action-plan.schema.json +38 -1
  93. package/dist/universal/.kiro/skills/citable/schemas/audit-coverage.schema.json +118 -0
  94. package/dist/universal/.kiro/skills/citable/schemas/browser-evidence-plan.schema.json +186 -3
  95. package/dist/universal/.kiro/skills/citable/schemas/browser-network-events.schema.json +142 -0
  96. package/dist/universal/.kiro/skills/citable/schemas/browser-runtime-events.schema.json +112 -0
  97. package/dist/universal/.kiro/skills/citable/schemas/browser-state-observations.schema.json +97 -0
  98. package/dist/universal/.kiro/skills/citable/schemas/collection-result.schema.json +35 -0
  99. package/dist/universal/.kiro/skills/citable/schemas/config.schema.json +2 -0
  100. package/dist/universal/.kiro/skills/citable/schemas/cro-report.schema.json +8 -1
  101. package/dist/universal/.kiro/skills/citable/schemas/finding.schema.json +105 -2
  102. package/dist/universal/.kiro/skills/citable/schemas/remediation-verification.schema.json +12 -1
  103. package/dist/universal/.kiro/skills/citable/schemas/run.schema.json +15 -2
  104. package/dist/universal/.kiro/skills/citable/schemas/search-report.schema.json +8 -1
  105. package/dist/universal/.kiro/skills/citable/schemas/summary.schema.json +41 -0
  106. package/dist/universal/.opencode/skills/citable/SKILL.md +11 -2
  107. package/dist/universal/.opencode/skills/citable/VERSION +1 -1
  108. package/dist/universal/.opencode/skills/citable/manifest.json +19 -13
  109. package/dist/universal/.opencode/skills/citable/schemas/action-plan.schema.json +38 -1
  110. package/dist/universal/.opencode/skills/citable/schemas/audit-coverage.schema.json +118 -0
  111. package/dist/universal/.opencode/skills/citable/schemas/browser-evidence-plan.schema.json +186 -3
  112. package/dist/universal/.opencode/skills/citable/schemas/browser-network-events.schema.json +142 -0
  113. package/dist/universal/.opencode/skills/citable/schemas/browser-runtime-events.schema.json +112 -0
  114. package/dist/universal/.opencode/skills/citable/schemas/browser-state-observations.schema.json +97 -0
  115. package/dist/universal/.opencode/skills/citable/schemas/collection-result.schema.json +35 -0
  116. package/dist/universal/.opencode/skills/citable/schemas/config.schema.json +2 -0
  117. package/dist/universal/.opencode/skills/citable/schemas/cro-report.schema.json +8 -1
  118. package/dist/universal/.opencode/skills/citable/schemas/finding.schema.json +105 -2
  119. package/dist/universal/.opencode/skills/citable/schemas/remediation-verification.schema.json +12 -1
  120. package/dist/universal/.opencode/skills/citable/schemas/run.schema.json +15 -2
  121. package/dist/universal/.opencode/skills/citable/schemas/search-report.schema.json +8 -1
  122. package/dist/universal/.opencode/skills/citable/schemas/summary.schema.json +41 -0
  123. package/dist/universal/.pi/agent/skills/citable/SKILL.md +11 -2
  124. package/dist/universal/.pi/agent/skills/citable/VERSION +1 -1
  125. package/dist/universal/.pi/agent/skills/citable/manifest.json +19 -13
  126. package/dist/universal/.pi/agent/skills/citable/schemas/action-plan.schema.json +38 -1
  127. package/dist/universal/.pi/agent/skills/citable/schemas/audit-coverage.schema.json +118 -0
  128. package/dist/universal/.pi/agent/skills/citable/schemas/browser-evidence-plan.schema.json +186 -3
  129. package/dist/universal/.pi/agent/skills/citable/schemas/browser-network-events.schema.json +142 -0
  130. package/dist/universal/.pi/agent/skills/citable/schemas/browser-runtime-events.schema.json +112 -0
  131. package/dist/universal/.pi/agent/skills/citable/schemas/browser-state-observations.schema.json +97 -0
  132. package/dist/universal/.pi/agent/skills/citable/schemas/collection-result.schema.json +35 -0
  133. package/dist/universal/.pi/agent/skills/citable/schemas/config.schema.json +2 -0
  134. package/dist/universal/.pi/agent/skills/citable/schemas/cro-report.schema.json +8 -1
  135. package/dist/universal/.pi/agent/skills/citable/schemas/finding.schema.json +105 -2
  136. package/dist/universal/.pi/agent/skills/citable/schemas/remediation-verification.schema.json +12 -1
  137. package/dist/universal/.pi/agent/skills/citable/schemas/run.schema.json +15 -2
  138. package/dist/universal/.pi/agent/skills/citable/schemas/search-report.schema.json +8 -1
  139. package/dist/universal/.pi/agent/skills/citable/schemas/summary.schema.json +41 -0
  140. package/dist/universal/.qoder/skills/citable/SKILL.md +11 -2
  141. package/dist/universal/.qoder/skills/citable/VERSION +1 -1
  142. package/dist/universal/.qoder/skills/citable/manifest.json +19 -13
  143. package/dist/universal/.qoder/skills/citable/schemas/action-plan.schema.json +38 -1
  144. package/dist/universal/.qoder/skills/citable/schemas/audit-coverage.schema.json +118 -0
  145. package/dist/universal/.qoder/skills/citable/schemas/browser-evidence-plan.schema.json +186 -3
  146. package/dist/universal/.qoder/skills/citable/schemas/browser-network-events.schema.json +142 -0
  147. package/dist/universal/.qoder/skills/citable/schemas/browser-runtime-events.schema.json +112 -0
  148. package/dist/universal/.qoder/skills/citable/schemas/browser-state-observations.schema.json +97 -0
  149. package/dist/universal/.qoder/skills/citable/schemas/collection-result.schema.json +35 -0
  150. package/dist/universal/.qoder/skills/citable/schemas/config.schema.json +2 -0
  151. package/dist/universal/.qoder/skills/citable/schemas/cro-report.schema.json +8 -1
  152. package/dist/universal/.qoder/skills/citable/schemas/finding.schema.json +105 -2
  153. package/dist/universal/.qoder/skills/citable/schemas/remediation-verification.schema.json +12 -1
  154. package/dist/universal/.qoder/skills/citable/schemas/run.schema.json +15 -2
  155. package/dist/universal/.qoder/skills/citable/schemas/search-report.schema.json +8 -1
  156. package/dist/universal/.qoder/skills/citable/schemas/summary.schema.json +41 -0
  157. package/dist/universal/.rovodev/skills/citable/SKILL.md +11 -2
  158. package/dist/universal/.rovodev/skills/citable/VERSION +1 -1
  159. package/dist/universal/.rovodev/skills/citable/manifest.json +19 -13
  160. package/dist/universal/.rovodev/skills/citable/schemas/action-plan.schema.json +38 -1
  161. package/dist/universal/.rovodev/skills/citable/schemas/audit-coverage.schema.json +118 -0
  162. package/dist/universal/.rovodev/skills/citable/schemas/browser-evidence-plan.schema.json +186 -3
  163. package/dist/universal/.rovodev/skills/citable/schemas/browser-network-events.schema.json +142 -0
  164. package/dist/universal/.rovodev/skills/citable/schemas/browser-runtime-events.schema.json +112 -0
  165. package/dist/universal/.rovodev/skills/citable/schemas/browser-state-observations.schema.json +97 -0
  166. package/dist/universal/.rovodev/skills/citable/schemas/collection-result.schema.json +35 -0
  167. package/dist/universal/.rovodev/skills/citable/schemas/config.schema.json +2 -0
  168. package/dist/universal/.rovodev/skills/citable/schemas/cro-report.schema.json +8 -1
  169. package/dist/universal/.rovodev/skills/citable/schemas/finding.schema.json +105 -2
  170. package/dist/universal/.rovodev/skills/citable/schemas/remediation-verification.schema.json +12 -1
  171. package/dist/universal/.rovodev/skills/citable/schemas/run.schema.json +15 -2
  172. package/dist/universal/.rovodev/skills/citable/schemas/search-report.schema.json +8 -1
  173. package/dist/universal/.rovodev/skills/citable/schemas/summary.schema.json +41 -0
  174. package/dist/universal/.trae/skills/citable/SKILL.md +11 -2
  175. package/dist/universal/.trae/skills/citable/VERSION +1 -1
  176. package/dist/universal/.trae/skills/citable/manifest.json +19 -13
  177. package/dist/universal/.trae/skills/citable/schemas/action-plan.schema.json +38 -1
  178. package/dist/universal/.trae/skills/citable/schemas/audit-coverage.schema.json +118 -0
  179. package/dist/universal/.trae/skills/citable/schemas/browser-evidence-plan.schema.json +186 -3
  180. package/dist/universal/.trae/skills/citable/schemas/browser-network-events.schema.json +142 -0
  181. package/dist/universal/.trae/skills/citable/schemas/browser-runtime-events.schema.json +112 -0
  182. package/dist/universal/.trae/skills/citable/schemas/browser-state-observations.schema.json +97 -0
  183. package/dist/universal/.trae/skills/citable/schemas/collection-result.schema.json +35 -0
  184. package/dist/universal/.trae/skills/citable/schemas/config.schema.json +2 -0
  185. package/dist/universal/.trae/skills/citable/schemas/cro-report.schema.json +8 -1
  186. package/dist/universal/.trae/skills/citable/schemas/finding.schema.json +105 -2
  187. package/dist/universal/.trae/skills/citable/schemas/remediation-verification.schema.json +12 -1
  188. package/dist/universal/.trae/skills/citable/schemas/run.schema.json +15 -2
  189. package/dist/universal/.trae/skills/citable/schemas/search-report.schema.json +8 -1
  190. package/dist/universal/.trae/skills/citable/schemas/summary.schema.json +41 -0
  191. package/dist/universal/.trae-cn/skills/citable/SKILL.md +11 -2
  192. package/dist/universal/.trae-cn/skills/citable/VERSION +1 -1
  193. package/dist/universal/.trae-cn/skills/citable/manifest.json +19 -13
  194. package/dist/universal/.trae-cn/skills/citable/schemas/action-plan.schema.json +38 -1
  195. package/dist/universal/.trae-cn/skills/citable/schemas/audit-coverage.schema.json +118 -0
  196. package/dist/universal/.trae-cn/skills/citable/schemas/browser-evidence-plan.schema.json +186 -3
  197. package/dist/universal/.trae-cn/skills/citable/schemas/browser-network-events.schema.json +142 -0
  198. package/dist/universal/.trae-cn/skills/citable/schemas/browser-runtime-events.schema.json +112 -0
  199. package/dist/universal/.trae-cn/skills/citable/schemas/browser-state-observations.schema.json +97 -0
  200. package/dist/universal/.trae-cn/skills/citable/schemas/collection-result.schema.json +35 -0
  201. package/dist/universal/.trae-cn/skills/citable/schemas/config.schema.json +2 -0
  202. package/dist/universal/.trae-cn/skills/citable/schemas/cro-report.schema.json +8 -1
  203. package/dist/universal/.trae-cn/skills/citable/schemas/finding.schema.json +105 -2
  204. package/dist/universal/.trae-cn/skills/citable/schemas/remediation-verification.schema.json +12 -1
  205. package/dist/universal/.trae-cn/skills/citable/schemas/run.schema.json +15 -2
  206. package/dist/universal/.trae-cn/skills/citable/schemas/search-report.schema.json +8 -1
  207. package/dist/universal/.trae-cn/skills/citable/schemas/summary.schema.json +41 -0
  208. package/dist/universal/manifest.json +232 -160
  209. package/package.json +1 -1
  210. package/schemas/action-plan.schema.json +38 -1
  211. package/schemas/audit-coverage.schema.json +118 -0
  212. package/schemas/browser-evidence-plan.schema.json +186 -3
  213. package/schemas/browser-network-events.schema.json +142 -0
  214. package/schemas/browser-runtime-events.schema.json +112 -0
  215. package/schemas/browser-state-observations.schema.json +97 -0
  216. package/schemas/collection-result.schema.json +35 -0
  217. package/schemas/config.schema.json +2 -0
  218. package/schemas/cro-report.schema.json +8 -1
  219. package/schemas/finding.schema.json +105 -2
  220. package/schemas/remediation-verification.schema.json +12 -1
  221. package/schemas/run.schema.json +15 -2
  222. package/schemas/search-report.schema.json +8 -1
  223. package/schemas/summary.schema.json +41 -0
  224. package/skill/SKILL.md +11 -2
  225. package/src/acceptance/reproducibility.js +5 -25
  226. package/src/analysis/croRoadmap.js +3 -3
  227. package/src/analysis/strategicRoadmap.js +3 -3
  228. package/src/artifacts/interchange.js +2 -6
  229. package/src/cli/index.js +15 -3
  230. package/src/commands/actionPlan.js +46 -5
  231. package/src/commands/applyRemediation.js +5 -5
  232. package/src/commands/audit.js +75 -8
  233. package/src/commands/ciWorkflow.js +7 -2
  234. package/src/commands/compareSnapshots.js +155 -47
  235. package/src/commands/connect.js +24 -4
  236. package/src/commands/context.js +41 -2
  237. package/src/commands/croSuite.js +51 -13
  238. package/src/commands/delivery.js +7 -7
  239. package/src/commands/governance.js +9 -10
  240. package/src/commands/implementationKit.js +2 -1
  241. package/src/commands/init.js +8 -1
  242. package/src/commands/previewCro.js +1 -1
  243. package/src/commands/prioritize.js +3 -2
  244. package/src/commands/reviews.js +31 -5
  245. package/src/commands/roadmapCmd.js +7 -13
  246. package/src/commands/sweep.js +15 -2
  247. package/src/commands/verifyRemediation.js +94 -19
  248. package/src/connectors/collectionResult.js +80 -0
  249. package/src/connectors/ga4.js +46 -11
  250. package/src/connectors/gsc.js +41 -6
  251. package/src/connectors/index.js +1 -1
  252. package/src/connectors/webflow.js +140 -10
  253. package/src/connectors/wordpress.js +92 -8
  254. package/src/crawler/fetch.js +220 -24
  255. package/src/crawler/resourceValidity.js +119 -0
  256. package/src/crawler/sitemap.js +435 -16
  257. package/src/crawler/sitemapCollector.js +364 -0
  258. package/src/crawler/urlIdentity.js +97 -0
  259. package/src/detectors/agent.js +11 -0
  260. package/src/detectors/ans.js +16 -1
  261. package/src/detectors/arch.js +6 -0
  262. package/src/detectors/claim.js +9 -0
  263. package/src/detectors/crawl.js +7 -0
  264. package/src/detectors/cro.js +21 -0
  265. package/src/detectors/cwv.js +4 -0
  266. package/src/detectors/entity.js +7 -0
  267. package/src/detectors/evd.js +9 -0
  268. package/src/detectors/framework.js +28 -2
  269. package/src/detectors/geoReco.js +16 -0
  270. package/src/detectors/hreflang.js +3 -0
  271. package/src/detectors/lifeMeas.js +9 -0
  272. package/src/detectors/link.js +6 -1
  273. package/src/detectors/page.js +11 -0
  274. package/src/detectors/schemaData.js +16 -1
  275. package/src/detectors/tech.js +34 -3
  276. package/src/evidence/coverage.js +448 -0
  277. package/src/evidence/determination.js +157 -0
  278. package/src/evidence/downstream.js +110 -0
  279. package/src/evidence/hashes.js +109 -0
  280. package/src/evidence/run.js +68 -8
  281. package/src/extractor/page.js +29 -2
  282. package/src/extractor/site.js +298 -50
  283. package/src/observations/browser/correlation.js +83 -0
  284. package/src/observations/browser/networkCollector.js +413 -0
  285. package/src/observations/browser/runtimeEventCollector.js +337 -0
  286. package/src/observations/browser/sanitizer.js +322 -0
  287. package/src/observations/browser/stateCollector.js +386 -0
  288. package/src/observations/browserJourney.js +131 -3
  289. package/src/reporting/executiveCroReport.js +37 -14
  290. package/src/reporting/executiveExport.js +45 -16
  291. package/src/reporting/executiveSearchReport.js +57 -23
  292. package/src/reporting/report.js +56 -9
  293. package/src/shared/epistemicLanguage.js +86 -0
  294. package/src/shared/evidenceSourceResolver.js +48 -17
  295. package/src/shared/htmlEscape.js +8 -4
  296. package/src/shared/runPackageVerifier.js +99 -8
  297. package/src/shared/verifiedRunLoader.js +129 -0
  298. package/src/sow/generateSow.js +1 -1
@@ -79,16 +79,37 @@ export async function syncConnection(root, { connectionId, startDate, endDate, a
79
79
  }
80
80
  throw error;
81
81
  }
82
+ // Connector collection may preserve a partial result even when the provider
83
+ // reports an authorization/quota state. Keep the result available to direct
84
+ // connector callers, while retaining the command's legacy state transition.
85
+ if (result.connectorError?.connectorState) {
86
+ const error = result.connectorError;
87
+ const failed = { ...loaded.registries.connections, entries: loaded.registries.connections.entries.map((item) => item.connection_id === connectionId ? { ...item, state: error.connectorState, limitations: [...(result.collection?.errors || []), 'Connector synchronization did not complete; inspect collection errors.'] } : item) };
88
+ saveRegistry(root, 'connections', failed);
89
+ throw error;
90
+ }
91
+ const collection = result.collection || {
92
+ schema_version: 1,
93
+ items: result.rows,
94
+ pagination_state: { pages_requested: 1, pages_retrieved: 1 },
95
+ provider_reported_total: null,
96
+ retrieved_total: result.rows.length,
97
+ coverage_status: 'indeterminate',
98
+ continuation_state: null,
99
+ limitations: ['Connector did not provide a collection contract.'],
100
+ errors: [],
101
+ };
102
+ const collectionLimitations = [...collection.limitations, ...collection.errors];
82
103
  const observations = result.rows.map((row) => envelope('metric', {
83
104
  metric_id: row.metric.metric_id, provider: connector.provider, external_name: row.metric.external_name,
84
105
  value: Number(row.value), unit: row.metric.unit === 'custom' ? row.metric.custom_unit : row.metric.unit,
85
106
  aggregation: row.metric.aggregation, observed_at: row.observed_at, period_start: row.observed_at,
86
107
  period_end: row.observed_at, dimensions: row.dimensions,
87
- }, { method: 'live_api', source: connector.provider, limitations: [...row.metric.limitations, ...result.limitations] }));
108
+ }, { method: 'live_api', source: connector.provider, limitations: [...row.metric.limitations, ...collectionLimitations] }));
88
109
  const run = observationRun(root, 'connect sync', loaded.connection.property_id, observations);
89
- const updated = { ...loaded.registries.connections, entries: loaded.registries.connections.entries.map((item) => item.connection_id === connectionId ? { ...item, state: 'synchronized', last_synchronized_at: new Date().toISOString(), cursor: result.cursor, limitations: result.limitations } : item) };
110
+ const updated = { ...loaded.registries.connections, entries: loaded.registries.connections.entries.map((item) => item.connection_id === connectionId ? { ...item, state: ['truncated', 'indeterminate'].includes(collection.coverage_status) ? 'partial' : 'synchronized', last_synchronized_at: new Date().toISOString(), cursor: result.cursor, limitations: collectionLimitations } : item) };
90
111
  saveRegistry(root, 'connections', updated);
91
- return { ...run, connection_id: connectionId, provider: connector.provider };
112
+ return { ...run, connection_id: connectionId, provider: connector.provider, collection };
92
113
  }
93
114
 
94
115
  export async function readCmsContent(root, { connectionId, targetId, accessToken, env, fetchImpl } = {}) {
@@ -119,4 +140,3 @@ export async function applyCmsRemediation(root, { connectionId, input, write = f
119
140
 
120
141
  export { submitIndexNow, verifyHostKey, buildIndexNowPayload, normalizeHost, parseUrlsInput } from '../connectors/indexnow.js';
121
142
  export { collectMcpEvidence } from '../connectors/mcp/pilot.js';
122
-
@@ -4,8 +4,44 @@ import { readYaml, readJson, sha256, parseRefDate } from '../shared/io.js';
4
4
  import { loadRegistries } from '../registries/index.js';
5
5
  import { buildSiteFromDir, buildSiteFromUrl } from '../extractor/site.js';
6
6
 
7
+ const AUDIT_BUDGET_DEFAULTS = Object.freeze({ maxPages: 500, timeBudgetSeconds: 1800 });
8
+
9
+ function validateBudget(value, name, maximum) {
10
+ if (!Number.isInteger(value) || value < 1 || value > maximum) {
11
+ throw new Error(`${name} must be an integer from 1 to ${maximum}; received ${String(value)}`);
12
+ }
13
+ return value;
14
+ }
15
+
16
+ /** Resolve bounded network collection budgets: command options > config > safe defaults. */
17
+ export function resolveAuditBudgets(config = {}, options = {}) {
18
+ const configured = config.audit ?? {};
19
+ const maxPagesFromOptions = options.maxPages !== undefined;
20
+ const timeBudgetFromOptions = options.timeBudgetSeconds !== undefined;
21
+ const maxPages = maxPagesFromOptions
22
+ ? options.maxPages
23
+ : configured.max_pages !== undefined ? configured.max_pages : AUDIT_BUDGET_DEFAULTS.maxPages;
24
+ const timeBudgetSeconds = timeBudgetFromOptions
25
+ ? options.timeBudgetSeconds
26
+ : configured.time_budget_seconds !== undefined
27
+ ? configured.time_budget_seconds
28
+ : AUDIT_BUDGET_DEFAULTS.timeBudgetSeconds;
29
+
30
+ return {
31
+ maxPages: validateBudget(maxPages, maxPagesFromOptions ? '--max-pages' : 'audit.max_pages', 10_000),
32
+ timeBudgetSeconds: validateBudget(
33
+ timeBudgetSeconds,
34
+ timeBudgetFromOptions ? '--time-budget-seconds' : 'audit.time_budget_seconds',
35
+ 86_400,
36
+ ),
37
+ };
38
+ }
39
+
7
40
  /** Assemble the shared execution context used by audit-family commands. */
8
- export async function buildContext(root, { target, baseUrl, refDate, viewport = null } = {}) {
41
+ export async function buildContext(root, {
42
+ target, baseUrl, refDate, viewport = null,
43
+ maxPages, timeBudgetSeconds, fetcher, concurrency = 1,
44
+ } = {}) {
9
45
  const warnings = [];
10
46
  const configFile = path.join(root, '.citable', 'config.yaml');
11
47
  const config = fs.existsSync(configFile) ? readYaml(configFile) : { version: 1 };
@@ -13,10 +49,12 @@ export async function buildContext(root, { target, baseUrl, refDate, viewport =
13
49
  warnings.push(...problems);
14
50
 
15
51
  let site = null;
52
+ let auditBudgets = null;
16
53
  const resolvedBase = baseUrl ?? config.site?.base_url ?? 'https://example.test';
17
54
  if (target) {
18
55
  if (/^https?:\/\//.test(target)) {
19
- site = await buildSiteFromUrl(target);
56
+ auditBudgets = resolveAuditBudgets(config, { maxPages, timeBudgetSeconds });
57
+ site = await buildSiteFromUrl(target, { ...auditBudgets, fetcher, concurrency });
20
58
  } else if (fs.existsSync(target)) {
21
59
  site = buildSiteFromDir(target, { baseUrl: resolvedBase });
22
60
  } else {
@@ -70,6 +108,7 @@ export async function buildContext(root, { target, baseUrl, refDate, viewport =
70
108
  promptResults: promptResults.length ? promptResults : null,
71
109
  observations: observations.length ? observations : null,
72
110
  snapshots,
111
+ auditBudgets,
73
112
  refDate: parseRefDate(refDate),
74
113
  viewport,
75
114
  hashPage: (p) => sha256(p.text),
@@ -9,6 +9,7 @@ import { analyzeBehavioralTelemetry } from '../analysis/behavioral.js';
9
9
  import { generateExperimentBacklog, formatBacklogMarkdown } from './croBacklog.js';
10
10
  import { buildIceMatrix, formatIceMatrixOutput } from '../analysis/iceMatrix.js';
11
11
  import { buildCroRoadmap, formatCroRoadmapMarkdown } from '../analysis/croRoadmap.js';
12
+ import { downstreamEnvelope, completeLocalCoverage } from '../evidence/downstream.js';
12
13
 
13
14
  /**
14
15
  * Full End-to-End CRO Intelligence Suite Audit
@@ -22,6 +23,8 @@ export async function auditCroSuite(root, { target, baseUrl, refDate, input, fun
22
23
  const { findings } = runDetectors(croDetectors, ctx);
23
24
 
24
25
  const pages = indexTargets(ctx);
26
+ const coverage = ctx.coverage || (pages.length > 0 ? completeLocalCoverage(pages, ctx.site.baseUrl) : null);
27
+ const evidence = downstreamEnvelope(coverage);
25
28
 
26
29
  // 2. Above-the-fold clarity, trust, cognitive load, offer architecture per page
27
30
  const auditedPages = pages.map((page) => auditPageCro(page, ctx));
@@ -33,21 +36,29 @@ export async function auditCroSuite(root, { target, baseUrl, refDate, input, fun
33
36
  });
34
37
  const targetGroup = commercialPages.length > 0 ? commercialPages : auditedPages;
35
38
 
36
- const avgAtfClarity = targetGroup.length > 0
39
+ const avgAtfClarity = targetGroup.length > 0 && evidence.determination_status !== 'indeterminate'
37
40
  ? Math.round(targetGroup.reduce((acc, p) => acc + p.atf_clarity.score, 0) / targetGroup.length)
38
- : 0;
41
+ : null;
39
42
 
40
- const avgTrustScore = targetGroup.length > 0
43
+ const avgTrustScore = targetGroup.length > 0 && evidence.determination_status !== 'indeterminate'
41
44
  ? Math.round(targetGroup.reduce((acc, p) => acc + p.trust_and_credibility.score, 0) / targetGroup.length)
42
- : 0;
45
+ : null;
43
46
 
44
- const avgConversionReadiness = targetGroup.length > 0
47
+ const avgConversionReadiness = targetGroup.length > 0 && evidence.determination_status !== 'indeterminate'
45
48
  ? Math.round(targetGroup.reduce((acc, p) => acc + p.conversion_readiness_score, 0) / targetGroup.length)
46
- : 0;
49
+ : null;
47
50
 
48
51
  // 3. End-to-end Funnel and Conversion-Path Analysis
49
52
  const declaredFunnel = (ctx.registries?.funnels?.entries || []).find((f) => funnelId ? f.funnel_id === funnelId : true) || null;
50
53
  const funnelAnalysis = analyzeConversionFunnel(ctx.site.pages, declaredFunnel);
54
+ const qualify = (value) => evidence.determination_status === 'supported' ? value : null;
55
+ const qualifiedFunnel = {
56
+ ...funnelAnalysis,
57
+ funnel_health_score: qualify(funnelAnalysis.funnel_health_score),
58
+ determination_status: evidence.determination_status,
59
+ coverage_status: evidence.coverage_status,
60
+ limitations: evidence.limitations,
61
+ };
51
62
 
52
63
  // 4. Behavioral Evidence Analysis (if telemetry file provided)
53
64
  let behavioralAnalysis = null;
@@ -65,9 +76,26 @@ export async function auditCroSuite(root, { target, baseUrl, refDate, input, fun
65
76
 
66
77
  // 5. Experiment Backlog with Falsifiable Hypotheses
67
78
  const experimentBacklog = generateExperimentBacklog(findings);
79
+ const qualifiedExperimentBacklog = {
80
+ ...experimentBacklog,
81
+ total_experiments: qualify(experimentBacklog.total_experiments),
82
+ determination_status: evidence.determination_status,
83
+ coverage_status: evidence.coverage_status,
84
+ limitations: evidence.limitations,
85
+ };
68
86
 
69
87
  // 6. Impact / Effort / Confidence (ICE) Prioritization Matrix
70
88
  const iceMatrix = buildIceMatrix(findings, { type: 'findings' });
89
+ const qualifiedIceMatrix = {
90
+ ...iceMatrix,
91
+ determination_status: evidence.determination_status,
92
+ coverage_status: evidence.coverage_status,
93
+ limitations: evidence.limitations,
94
+ summary: {
95
+ ...iceMatrix.summary,
96
+ quick_wins_count: qualify(iceMatrix.summary.quick_wins_count),
97
+ },
98
+ };
71
99
 
72
100
  // 7. 30 / 90 / 180-Day CRO Strategic Roadmap tied to Measurable Conversion Outcomes
73
101
  const targetDomain = ctx.site.baseUrl ? new URL(ctx.site.baseUrl).hostname : 'target-site';
@@ -75,26 +103,36 @@ export async function auditCroSuite(root, { target, baseUrl, refDate, input, fun
75
103
  findings,
76
104
  targetDomain,
77
105
  });
106
+ const qualifiedRoadmap = {
107
+ ...croRoadmap,
108
+ determination_status: evidence.determination_status,
109
+ coverage_status: evidence.coverage_status,
110
+ limitations: evidence.limitations,
111
+ };
78
112
 
79
113
  const result = {
80
114
  fact_status: 'modeled_cro_evaluation',
81
115
  target: ctx.site.baseUrl || target,
82
116
  total_pages_audited: pages.length,
117
+ coverage_status: evidence.coverage_status,
118
+ determination_status: evidence.determination_status,
119
+ evidence_scope: evidence.evidence_scope,
120
+ limitations: evidence.limitations,
83
121
  summary: {
84
122
  conversion_readiness_score: avgConversionReadiness,
85
123
  atf_clarity_score: avgAtfClarity,
86
124
  trust_credibility_score: avgTrustScore,
87
- funnel_health_score: funnelAnalysis.funnel_health_score,
125
+ funnel_health_score: qualifiedFunnel.funnel_health_score,
88
126
  total_cro_findings: findings.length,
89
- planned_experiments: experimentBacklog.total_experiments,
90
- quick_wins_count: iceMatrix.summary.quick_wins_count,
127
+ planned_experiments: qualifiedExperimentBacklog.total_experiments,
128
+ quick_wins_count: qualifiedIceMatrix.summary.quick_wins_count,
91
129
  },
92
- funnel_analysis: funnelAnalysis,
130
+ funnel_analysis: qualifiedFunnel,
93
131
  pages: auditedPages,
94
132
  behavioral_telemetry: behavioralAnalysis,
95
- experiment_backlog: experimentBacklog,
96
- ice_matrix: iceMatrix,
97
- strategic_roadmap: croRoadmap,
133
+ experiment_backlog: qualifiedExperimentBacklog,
134
+ ice_matrix: qualifiedIceMatrix,
135
+ strategic_roadmap: qualifiedRoadmap,
98
136
  findings: findings.map((f) => ({
99
137
  detector_id: f.detector_id,
100
138
  severity: f.classification.severity,
@@ -6,6 +6,7 @@ import { compareSnapshots } from './compareSnapshots.js';
6
6
  import { buildAlertPayload, dispatchAlertWebhook, filterAlerts } from '../monitoring/alertDelivery.js';
7
7
  import { loadRegistries } from '../registries/index.js';
8
8
  import { readJson, sha256, writeJson, nowIso } from '../shared/io.js';
9
+ import { loadVerifiedRun } from '../shared/verifiedRunLoader.js';
9
10
 
10
11
  const PKG = readJson(path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../package.json'));
11
12
 
@@ -114,12 +115,11 @@ function annotationLevel(severity) {
114
115
  export function projectGithub(root, { runId } = {}) {
115
116
  if (!runId) throw new Error('projection requires a source run id');
116
117
  const runDir = path.join(root, '.citable', 'runs', runId);
117
- const findingsFile = path.join(runDir, 'findings.json');
118
- const manifestFile = path.join(runDir, 'manifest.json');
119
- if (!fs.existsSync(findingsFile) || !fs.existsSync(manifestFile)) throw new Error(`run ${runId} is missing findings or manifest evidence`);
120
- const findingsBytes = fs.readFileSync(findingsFile);
121
- const manifestBytes = fs.readFileSync(manifestFile);
122
- const annotations = JSON.parse(findingsBytes).map((finding) => ({
118
+ const loaded = loadVerifiedRun(runDir, { requireCompletedExecution: false, allowLegacy: true, requireCoverage: false });
119
+ const findingsHash = loaded.artifactHashes?.['findings.json'];
120
+ const manifestHash = loaded.artifactHashes?.['manifest.json'];
121
+ if (!findingsHash || !manifestHash) throw new Error(`run ${runId} has no loader-verified findings or manifest hash`);
122
+ const annotations = loaded.findings.map((finding) => ({
123
123
  level: annotationLevel(finding.classification.severity),
124
124
  title: `${finding.detector_id}: ${finding.classification.severity}`,
125
125
  message: finding.observation.summary,
@@ -127,7 +127,7 @@ export function projectGithub(root, { runId } = {}) {
127
127
  line: finding.subject.source_location ? Number(String(finding.subject.source_location).match(/\d+/)?.[0]) || null : null,
128
128
  finding_id: finding.finding_id,
129
129
  }));
130
- const projection = { source_run_id: runId, source_manifest_hash: sha256(manifestBytes), source_findings_hash: sha256(findingsBytes), generated_at: nowIso(), projection: 'github-check-annotations', authoritative: false, annotations };
130
+ const projection = { source_run_id: runId, source_manifest_hash: manifestHash, source_findings_hash: findingsHash, generated_at: nowIso(), projection: 'github-check-annotations', authoritative: false, annotations };
131
131
  const dir = path.join(root, '.citable', 'projections', 'github', runId);
132
132
  writeJson(path.join(dir, 'annotations.json'), projection);
133
133
  return { ...projection, dir };
@@ -1,8 +1,8 @@
1
- import fs from 'node:fs';
2
1
  import path from 'node:path';
2
+ import { loadVerifiedRun } from '../shared/verifiedRunLoader.js';
3
3
  import { createRun } from '../evidence/run.js';
4
4
  import { loadRegistries, checkReferentialIntegrity } from '../registries/index.js';
5
- import { readJson, sha256, parseRefDate } from '../shared/io.js';
5
+ import { sha256, parseRefDate } from '../shared/io.js';
6
6
 
7
7
  function recordHash(value) {
8
8
  return sha256(JSON.stringify(value));
@@ -79,8 +79,8 @@ export function validateGovernance(root, { refDate } = {}) {
79
79
  const evidence = new Map(registries.evidence.entries.map((item) => [item.evidence_id, item]));
80
80
  const activeByFinding = new Map();
81
81
  for (const exception of registries.exceptions.entries) {
82
- const file = path.join(root, '.citable', 'runs', exception.source_run_id, 'findings.json');
83
- const findings = new Map(fs.existsSync(file) ? readJson(file).map((item) => [item.finding_id, item]) : []);
82
+ const runDir = path.join(root, '.citable', 'runs', exception.source_run_id);
83
+ const findings = new Map(loadVerifiedRun(runDir, { requireCompletedExecution: false, allowLegacy: true, requireCoverage: false }).findings.map((item) => [item.finding_id, item]));
84
84
  const exceptionIssues = exceptionProblems(exception, policies.get(exception.policy_id), reviewers, findings, evidence, reference);
85
85
  problems.push(...exceptionIssues.map((problem) => `exceptions/${exception.exception_id}: ${problem}`));
86
86
  if (exceptionIssues.length === 0) {
@@ -98,10 +98,9 @@ export function validateGovernance(root, { refDate } = {}) {
98
98
 
99
99
  export function evaluateDispositions(root, { runId, refDate } = {}) {
100
100
  if (!runId) throw new Error('source run id is required');
101
- const findingsFile = path.join(root, '.citable', 'runs', runId, 'findings.json');
102
- if (!fs.existsSync(findingsFile)) throw new Error(`source findings not found for run ${runId}`);
103
- const sourceBytes = fs.readFileSync(findingsFile);
104
- const sourceFindings = JSON.parse(sourceBytes);
101
+ const runDir = path.join(root, '.citable', 'runs', runId);
102
+ const sourceRun = loadVerifiedRun(runDir, { requireCompletedExecution: false, allowLegacy: true, requireCoverage: false });
103
+ const sourceFindings = sourceRun.findings;
105
104
  const findings = new Map(sourceFindings.map((item) => [item.finding_id, item]));
106
105
  const { registries, problems } = loadRegistries(root);
107
106
  const integrity = checkReferentialIntegrity(registries);
@@ -130,8 +129,8 @@ export function evaluateDispositions(root, { runId, refDate } = {}) {
130
129
  problems: matches.flatMap((item) => item.problems),
131
130
  };
132
131
  });
133
- const run = createRun(root, { command: 'governance evaluate', argv: [runId], target: { kind: 'registries', location: findingsFile } });
134
- run.addInput('source-findings', sourceBytes);
132
+ const run = createRun(root, { command: 'governance evaluate', argv: [runId], target: { kind: 'registries', location: path.join(runDir, 'findings.json') } });
133
+ run.manifest.input_hashes['source-findings'] = sourceRun.artifactHashes['findings.json'];
135
134
  run.addInput('exceptions', registries.exceptions);
136
135
  run.addInput('review-policies', registries.review_policies);
137
136
  run.writeArtifact('dispositions.json', dispositions);
@@ -1,5 +1,6 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
+ import { loadVerifiedRun } from '../shared/verifiedRunLoader.js';
3
4
  import { readJson, nowIso } from '../shared/io.js';
4
5
  import { remediateCommand } from './remediate.js';
5
6
 
@@ -76,7 +77,7 @@ export async function exportImplementationKit(root, options = {}) {
76
77
  if (!runId || !fs.existsSync(path.join(runDir, 'findings.json'))) {
77
78
  throw new Error(`source run not found: .citable/runs/${runId || '(none)'}`);
78
79
  }
79
- const findings = readJson(path.join(runDir, 'findings.json'));
80
+ const findings = loadVerifiedRun(runDir, { requireCompletedExecution: false, allowLegacy: true, requireCoverage: false }).findings;
80
81
  const matches = findings.filter((f) =>
81
82
  (f.detector_id || '').toUpperCase() === String(finding || '').toUpperCase()
82
83
  && (!subject || (f.subject?.identifier || f.subject?.url) === subject));
@@ -89,7 +89,14 @@ export function init(root, { force = false, seed = null } = {}) {
89
89
  package_manager: detected.package_manager,
90
90
  build_command: detected.build_commands[0] ?? null, test_command: detected.test_commands[0] ?? null,
91
91
  },
92
- audit: { index_target_default: true, max_crawl_depth: 4, thin_content_words: 120, fetch_external: false },
92
+ audit: {
93
+ index_target_default: true,
94
+ max_pages: 500,
95
+ time_budget_seconds: 1800,
96
+ max_crawl_depth: 4,
97
+ thin_content_words: 120,
98
+ fetch_external: false,
99
+ },
93
100
  };
94
101
  const { valid, errors } = validateAgainst('config.schema.json', config);
95
102
  if (!valid) throw new Error(`generated config invalid: ${errors.join('; ')}`);
@@ -167,7 +167,7 @@ export function generateCroPreviewHtml({
167
167
  </div>
168
168
 
169
169
  <div class="findings-list">
170
- <h3>Resolved CRO Defects (${findingItems.length})</h3>
170
+ <h3>Observed CRO Findings (${findingItems.length})</h3>
171
171
  ${findingItems.map((f) => `
172
172
  <div class="finding-row">
173
173
  <span><strong>${f.detector_id}</strong>: ${f.summary}</span>
@@ -13,6 +13,7 @@
13
13
  */
14
14
  import fs from 'node:fs';
15
15
  import path from 'node:path';
16
+ import { loadVerifiedRun } from '../shared/verifiedRunLoader.js';
16
17
  import { contextDir, loadRegistryFile, registryLoadProblems } from '../registries/index.js';
17
18
  import { validateAgainst } from '../shared/schemaValidator.js';
18
19
  import { buildIceMatrix, formatIceMatrixOutput } from '../analysis/iceMatrix.js';
@@ -96,7 +97,7 @@ function initiativeMatrix(file, root, rest = []) {
96
97
  if (runId) {
97
98
  const findingsPath = path.join(root, '.citable', 'runs', runId, 'findings.json');
98
99
  if (!fs.existsSync(findingsPath)) throw new Error(`run ${runId} findings.json not found`);
99
- const findings = JSON.parse(fs.readFileSync(findingsPath, 'utf8'));
100
+ const findings = loadVerifiedRun(path.dirname(findingsPath), { requireCompletedExecution: false, allowLegacy: true, requireCoverage: false }).findings;
100
101
  const matrix = buildIceMatrix(findings, { type: 'findings' });
101
102
  return { ...matrix, formatted: formatIceMatrixOutput(matrix) };
102
103
  }
@@ -106,7 +107,7 @@ function initiativeMatrix(file, root, rest = []) {
106
107
  if (fs.existsSync(runsDir)) {
107
108
  const runs = fs.readdirSync(runsDir).filter((r) => fs.existsSync(path.join(runsDir, r, 'findings.json'))).sort().reverse();
108
109
  if (runs.length > 0 && rest.includes('--findings')) {
109
- const findings = JSON.parse(fs.readFileSync(path.join(runsDir, runs[0], 'findings.json'), 'utf8'));
110
+ const findings = loadVerifiedRun(path.join(runsDir, runs[0]), { requireCompletedExecution: false, allowLegacy: true, requireCoverage: false }).findings;
110
111
  const matrix = buildIceMatrix(findings, { type: 'findings' });
111
112
  return { ...matrix, formatted: formatIceMatrixOutput(matrix) };
112
113
  }
@@ -1,5 +1,6 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
+ import { loadVerifiedRun } from '../shared/verifiedRunLoader.js';
3
4
  import { loadRegistries, saveRegistry } from '../registries/index.js';
4
5
  import { readJson, readYaml, sha256, nowIso } from '../shared/io.js';
5
6
  import { validateAgainst } from '../shared/schemaValidator.js';
@@ -23,7 +24,7 @@ export function queueReviews(root, { runId, policyId, write = false }) {
23
24
  if (!runId || !policyId) throw new Error('reviews queue requires <run-id> <policy-id>');
24
25
  const file = path.join(root,'.citable','runs',runId,'findings.json');
25
26
  if (!fs.existsSync(file)) throw new Error(`source findings not found for run ${runId}`);
26
- const findings = readJson(file);
27
+ const findings = loadVerifiedRun(path.dirname(file), { requireCompletedExecution: false, allowLegacy: true, requireCoverage: false }).findings;
27
28
  const { registries, problems } = loadRegistries(root);
28
29
  if (problems.length) throw new Error(`registry validation failed: ${problems.join('; ')}`);
29
30
  if (!registries.review_policies.entries.some((p) => p.policy_id === policyId && p.status === 'active')) throw new Error(`active review policy not found: ${policyId}`);
@@ -40,7 +41,7 @@ export function queueReviews(root, { runId, policyId, write = false }) {
40
41
  export function prioritizeReviews(root, { write = false } = {}) {
41
42
  const { registries, problems } = loadRegistries(root); if (problems.length) throw new Error(`registry validation failed: ${problems.join('; ')}`);
42
43
  const cache = new Map();
43
- const entries = registries.review_items.entries.map((item) => { const file=path.join(root,'.citable','runs',item.source_run_id,'findings.json'); if(!cache.has(file)) cache.set(file,fs.existsSync(file)?new Map(readJson(file).map(f=>[f.finding_id,f])):new Map()); const ranked=priority(item,cache.get(file).get(item.finding_id)); return {...item,priority_score:ranked.score,priority_tier:ranked.tier,missing_inputs:ranked.missing}; });
44
+ const entries = registries.review_items.entries.map((item) => { const file=path.join(root,'.citable','runs',item.source_run_id,'findings.json'); if(!cache.has(file)) cache.set(file,fs.existsSync(file)?new Map(loadVerifiedRun(path.dirname(file), { requireCompletedExecution: false, allowLegacy: true, requireCoverage: false }).findings.map(f=>[f.finding_id,f])):new Map()); const ranked=priority(item,cache.get(file).get(item.finding_id)); return {...item,priority_score:ranked.score,priority_tier:ranked.tier,missing_inputs:ranked.missing}; });
44
45
  if(write) saveRegistry(root,'review_items',{...registries.review_items,entries});
45
46
  return { items:entries.sort((a,b)=>b.priority_score-a.priority_score), written:write };
46
47
  }
@@ -67,7 +68,32 @@ export function selectSample(root,{planId,write=false}) {
67
68
  }
68
69
 
69
70
  export function evaluateReviews(root) {
70
- const {registries,problems}=loadRegistries(root); const reviewers=new Map(registries.reviewers.entries.map(r=>[r.reviewer_id,r])); const results=[];
71
- for(const item of registries.review_items.entries){const issues=[];const binding=reviewBindingHash(item);const source=path.join(root,'.citable','runs',item.source_run_id,'findings.json');const finding=fs.existsSync(source)?readJson(source).find(f=>f.finding_id===item.finding_id):null;if(!finding||sha256(JSON.stringify(finding))!==item.finding_hash)issues.push('source finding is unavailable or changed');for(const d of item.decisions){const r=reviewers.get(d.reviewer_id);const reviewRole=r?.roles.some(role=>['technical_reviewer','subject_matter_reviewer','independent_reviewer'].includes(role));if(!r||r.status!=='active'||!reviewRole||!item.assigned_reviewer_ids.includes(d.reviewer_id))issues.push(`${d.reviewer_id} is not an active assigned semantic reviewer`);if(d.review_item_hash!==binding)issues.push(`${d.reviewer_id} decision is stale`);}const verdicts=[...new Set(item.decisions.map(d=>d.verdict))];let state=item.decisions.length?'completed':'review_required';if(verdicts.length>1){state='adjudication_required';const a=reviewers.get(item.adjudication?.reviewer_id);if(item.adjudication&&a?.status==='active'&&a.roles.includes('independent_reviewer')&&item.adjudication.review_item_hash===binding)state='completed';else issues.push('reviewer disagreement requires a current decision from an active independent adjudicator');}results.push({review_item_id:item.review_item_id,state,verdict:state==='completed'&&!issues.length?(item.adjudication?.verdict||verdicts[0]):null,issues});}
72
- return {ok:problems.length===0&&results.every(r=>!r.issues.length),problems,results};
71
+ const { registries, problems } = loadRegistries(root);
72
+ const reviewers = new Map(registries.reviewers.entries.map((r) => [r.reviewer_id, r]));
73
+ const results = [];
74
+ for (const item of registries.review_items.entries) {
75
+ const issues = [];
76
+ const binding = reviewBindingHash(item);
77
+ const source = path.join(root, '.citable', 'runs', item.source_run_id, 'findings.json');
78
+ const finding = fs.existsSync(source)
79
+ ? loadVerifiedRun(path.dirname(source), { requireCompletedExecution: false, allowLegacy: true, requireCoverage: false }).findings.find((f) => f.finding_id === item.finding_id)
80
+ : null;
81
+ if (!finding || sha256(JSON.stringify(finding)) !== item.finding_hash) issues.push('source finding is unavailable or changed');
82
+ for (const decision of item.decisions) {
83
+ const reviewer = reviewers.get(decision.reviewer_id);
84
+ const reviewRole = reviewer?.roles.some((role) => ['technical_reviewer', 'subject_matter_reviewer', 'independent_reviewer'].includes(role));
85
+ if (!reviewer || reviewer.status !== 'active' || !reviewRole || !item.assigned_reviewer_ids.includes(decision.reviewer_id)) issues.push(`${decision.reviewer_id} is not an active assigned semantic reviewer`);
86
+ if (decision.review_item_hash !== binding) issues.push(`${decision.reviewer_id} decision is stale`);
87
+ }
88
+ const verdicts = [...new Set(item.decisions.map((d) => d.verdict))];
89
+ let state = item.decisions.length ? 'completed' : 'review_required';
90
+ if (verdicts.length > 1) {
91
+ state = 'adjudication_required';
92
+ const adjudicator = reviewers.get(item.adjudication?.reviewer_id);
93
+ if (item.adjudication && adjudicator?.status === 'active' && adjudicator.roles.includes('independent_reviewer') && item.adjudication.review_item_hash === binding) state = 'completed';
94
+ else issues.push('reviewer disagreement requires a current decision from an active independent adjudicator');
95
+ }
96
+ results.push({ review_item_id: item.review_item_id, state, verdict: state === 'completed' && !issues.length ? (item.adjudication?.verdict || verdicts[0]) : null, issues });
97
+ }
98
+ return { ok: problems.length === 0 && results.every((r) => !r.issues.length), problems, results };
73
99
  }
@@ -1,8 +1,9 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import { contextDir, loadRegistryFile } from '../registries/index.js';
4
- import { readJson, writeJson } from '../shared/io.js';
4
+ import { writeJson } from '../shared/io.js';
5
5
  import { buildStrategicRoadmap, formatRoadmapMarkdown } from '../analysis/strategicRoadmap.js';
6
+ import { loadVerifiedRun } from '../shared/verifiedRunLoader.js';
6
7
 
7
8
  /**
8
9
  * `citable roadmap [generate|show]` — generate or view the 30/90/180-day strategic roadmap.
@@ -10,6 +11,7 @@ import { buildStrategicRoadmap, formatRoadmapMarkdown } from '../analysis/strate
10
11
  export async function roadmapCommand(root, { runId, target, write = true } = {}) {
11
12
  let findings = [];
12
13
  let initiatives = [];
14
+ let verifiedRun = null;
13
15
 
14
16
  // 1. Load findings from specified or latest run
15
17
  const runsDir = path.join(root, '.citable', 'runs');
@@ -20,10 +22,8 @@ export async function roadmapCommand(root, { runId, target, write = true } = {})
20
22
  }
21
23
 
22
24
  if (sourceRun) {
23
- const findingsFile = path.join(runsDir, sourceRun, 'findings.json');
24
- if (fs.existsSync(findingsFile)) {
25
- findings = JSON.parse(fs.readFileSync(findingsFile, 'utf8'));
26
- }
25
+ verifiedRun = loadVerifiedRun(path.join(runsDir, sourceRun), { requireCompletedExecution: false, allowLegacy: true, requireCoverage: false });
26
+ findings = verifiedRun.findings;
27
27
  }
28
28
 
29
29
  // 2. Load initiatives if present
@@ -39,14 +39,8 @@ export async function roadmapCommand(root, { runId, target, write = true } = {})
39
39
 
40
40
  // Target domain resolution
41
41
  let domain = target || 'nebulacomponents.com';
42
- if (sourceRun) {
43
- const manifestFile = path.join(runsDir, sourceRun, 'manifest.json');
44
- if (fs.existsSync(manifestFile)) {
45
- try {
46
- const manifest = readJson(manifestFile);
47
- if (manifest.target?.location) domain = manifest.target.location;
48
- } catch {}
49
- }
42
+ if (verifiedRun?.manifest?.target?.location) {
43
+ domain = verifiedRun.manifest.target.location;
50
44
  }
51
45
 
52
46
  const roadmap = buildStrategicRoadmap({ findings, initiatives, targetDomain: domain });
@@ -4,6 +4,7 @@ import { parse as parseHtml } from 'node-html-parser';
4
4
  import { buildContext } from './context.js';
5
5
  import { selectDetectors } from '../detectors/index.js';
6
6
  import { runDetectors, indexTargets, pageSubject, safePath } from '../detectors/framework.js';
7
+ import { downstreamEnvelope, scoreFromCoverage, completeLocalCoverage } from '../evidence/downstream.js';
7
8
 
8
9
  /**
9
10
  * CWV standard thresholds per Google / web.dev guidance
@@ -223,6 +224,9 @@ export async function sweepTechnical(root, { target, baseUrl, refDate, page: pag
223
224
  if (!pages.length) throw new Error(`page not found in audited output: ${pageFilter}`);
224
225
  }
225
226
 
227
+ const coverage = ctx.coverage || (pages.length > 0 ? completeLocalCoverage(pages, ctx.site.baseUrl) : null);
228
+ const evidence = downstreamEnvelope(coverage);
229
+
226
230
  // 3. Aggregate Core Web Vitals across audited pages
227
231
  const pageVitals = [];
228
232
  let totalLcpBlockers = 0;
@@ -269,6 +273,8 @@ export async function sweepTechnical(root, { target, baseUrl, refDate, page: pag
269
273
  } else if (highFindings.length > 0 || totalLcpBlockers > 3 || totalClsRisks > 5) {
270
274
  overallHealth = 'needs_attention';
271
275
  }
276
+ if (evidence.determination_status === 'indeterminate') overallHealth = 'not_established';
277
+ else if (evidence.determination_status === 'qualified' && overallHealth === 'optimal') overallHealth = 'qualified';
272
278
 
273
279
  const result = {
274
280
  target: ctx.site.baseUrl || target,
@@ -291,7 +297,9 @@ export async function sweepTechnical(root, { target, baseUrl, refDate, page: pag
291
297
  total_inp_risks: totalInpRisks,
292
298
  unsized_images: totalUnsizedImages,
293
299
  total_images: totalImagesCount,
294
- image_dimension_coverage_pct: totalImagesCount > 0 ? Math.round(((totalImagesCount - totalUnsizedImages) / totalImagesCount) * 100) : 100,
300
+ image_dimension_coverage_pct: evidence.determination_status === 'supported' && totalImagesCount > 0
301
+ ? scoreFromCoverage(totalImagesCount - totalUnsizedImages, { ...coverage, populations: { ...coverage.populations, evaluated: totalImagesCount } })
302
+ : null,
295
303
  },
296
304
  pages: pageVitals,
297
305
  },
@@ -310,6 +318,10 @@ export async function sweepTechnical(root, { target, baseUrl, refDate, page: pag
310
318
  summary: f.observation.summary,
311
319
  remediation: f.remediation?.preferred,
312
320
  })),
321
+ coverage_status: evidence.coverage_status,
322
+ determination_status: evidence.determination_status,
323
+ evidence_scope: evidence.evidence_scope,
324
+ limitations: evidence.limitations,
313
325
  };
314
326
 
315
327
  return result;
@@ -325,6 +337,7 @@ export function formatSweepOutput(r) {
325
337
  `Target: ${r.target}`,
326
338
  `Pages Audited: ${r.total_pages_audited}`,
327
339
  `Overall Health: ${r.overall_health.toUpperCase()}`,
340
+ `Evidence Determination: ${(r.determination_status || 'not_established').toUpperCase()} (coverage: ${r.coverage_status || 'unknown'})`,
328
341
  ``,
329
342
  `CRAWL & INDEXABILITY:`,
330
343
  ` HTTP 200 OK: ${r.crawl_and_indexability.status_200_count}/${r.total_pages_audited}`,
@@ -335,7 +348,7 @@ export function formatSweepOutput(r) {
335
348
  `CORE WEB VITALS READINESS (Deterministic Static Audit):`,
336
349
  ` LCP Readiness: ${r.core_web_vitals.summary.lcp_readiness.toUpperCase()} (${r.core_web_vitals.summary.total_lcp_blockers} potential render blocker(s))`,
337
350
  ` INP Readiness: ${r.core_web_vitals.summary.inp_readiness.toUpperCase()} (${r.core_web_vitals.summary.total_inp_risks} DOM complexity risk(s))`,
338
- ` CLS Readiness: ${r.core_web_vitals.summary.cls_readiness.toUpperCase()} (${r.core_web_vitals.summary.unsized_images}/${r.core_web_vitals.summary.total_images} images lack dimensions; ${r.core_web_vitals.summary.image_dimension_coverage_pct}% coverage)`,
351
+ ` CLS Readiness: ${r.core_web_vitals.summary.cls_readiness.toUpperCase()} (${r.core_web_vitals.summary.unsized_images}/${r.core_web_vitals.summary.total_images} images lack dimensions; ${r.core_web_vitals.summary.image_dimension_coverage_pct == null ? 'not evidenced' : `${r.core_web_vitals.summary.image_dimension_coverage_pct}%`} coverage)`,
339
352
  ``,
340
353
  `CWV Threshold Standards:`,
341
354
  ` - LCP: <= ${CWV_THRESHOLDS.lcp.good}s (Good), <= ${CWV_THRESHOLDS.lcp.needs_improvement}s (Needs Improvement)`,