oh-my-opencode 5.1.48 → 5.1.51

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 (495) hide show
  1. package/dist/agents/refusal-rule.d.ts +7 -0
  2. package/dist/cli/index.js +1194 -752
  3. package/dist/cli/service-auth/accounts-gate.d.ts +11 -0
  4. package/dist/cli/service-auth/commands.d.ts +2 -4
  5. package/dist/cli/service-auth/index.d.ts +1 -1
  6. package/dist/cli/service-auth/keystore.d.ts +26 -2
  7. package/dist/cli/service-auth/protocol.d.ts +7 -1
  8. package/dist/cli/service-auth/session.d.ts +6 -0
  9. package/dist/cli/service-auth/signed-out-marker.d.ts +11 -0
  10. package/dist/cli-node/index.js +1194 -752
  11. package/dist/config/schema/hooks.d.ts +1 -0
  12. package/dist/create-hooks.d.ts +1 -0
  13. package/dist/features/boulder-state/storage.d.ts +1 -1
  14. package/dist/features/boulder-state/types.d.ts +1 -1
  15. package/dist/hooks/atlas/idle-continuation.d.ts +2 -1
  16. package/dist/hooks/atlas/types.d.ts +9 -1
  17. package/dist/hooks/index.d.ts +1 -0
  18. package/dist/hooks/momus-review-persistence/hook.d.ts +19 -0
  19. package/dist/hooks/momus-review-persistence/index.d.ts +1 -0
  20. package/dist/hooks/momus-review-persistence/review-resolution.d.ts +20 -0
  21. package/dist/hooks/prometheus-md-only/constants.d.ts +2 -0
  22. package/dist/hooks/prometheus-md-only/fixtures/denial-log.d.ts +1 -0
  23. package/dist/hooks/prometheus-md-only/fixtures/guarded-hashline.d.ts +1 -0
  24. package/dist/hooks/prometheus-md-only/fixtures/invoke-before.d.ts +7 -0
  25. package/dist/hooks/prometheus-md-only/fixtures/opencode-patch-parser.d.ts +2 -0
  26. package/dist/hooks/prometheus-md-only/hook.d.ts +5 -1
  27. package/dist/hooks/prometheus-md-only/path-policy.d.ts +2 -9
  28. package/dist/hooks/prometheus-md-only/vendor/opencode-patch-parser.d.ts +22 -0
  29. package/dist/index.js +2322 -1045
  30. package/dist/plugin/hooks/create-core-hooks.d.ts +1 -0
  31. package/dist/plugin/hooks/create-tool-guard-hooks.d.ts +2 -1
  32. package/dist/shared/posthog-activity-state.d.ts +1 -0
  33. package/dist/shared/posthog.d.ts +1 -2
  34. package/dist/shared/resolve-file-write-path.d.ts +6 -0
  35. package/dist/skills/ast-grep/README.md +1 -1
  36. package/dist/skills/ast-grep/install.ps1 +5 -3
  37. package/dist/skills/ast-grep/install.sh +4 -5
  38. package/dist/skills/ast-grep/references/install.md +2 -2
  39. package/dist/skills/ast-grep/scripts/ast_grep_helper.py +13 -3
  40. package/dist/skills/browser/runtime/omowright/manifest.json +1 -1
  41. package/dist/skills/coding-agent-sessions/AGENTS.md +5 -4
  42. package/dist/skills/coding-agent-sessions/SKILL.md +11 -11
  43. package/dist/skills/coding-agent-sessions/references/all-platforms.md +2 -2
  44. package/dist/skills/coding-agent-sessions/references/codex.md +4 -4
  45. package/dist/skills/coding-agent-sessions/scripts/agent_sessions/cli.py +1 -1
  46. package/dist/skills/coding-agent-sessions/scripts/find-agent-sessions.py +6 -3
  47. package/dist/skills/frontend/ATTRIBUTION.md +1 -1
  48. package/dist/skills/frontend/SKILL.md +34 -12
  49. package/dist/skills/frontend/references/design/README.md +2 -2
  50. package/dist/skills/frontend/references/design/_INDEX.md +2 -1
  51. package/dist/skills/frontend/references/design/component-catalogs.md +14 -10
  52. package/dist/skills/frontend/references/design/design-system-architecture.md +19 -6
  53. package/dist/skills/frontend/references/design/grounding.md +29 -0
  54. package/dist/skills/frontend/references/ui-ux-db/scripts/design_system.py +2 -0
  55. package/dist/skills/frontend/references/ui-ux-db/scripts/search.py +2 -0
  56. package/dist/skills/ultimate-browsing/SKILL.md +5 -4
  57. package/dist/skills/ultimate-browsing/engine/AGENTS.md +2 -2
  58. package/dist/skills/ultimate-browsing/engine/__main__.py +9 -6
  59. package/dist/skills/ultimate-browsing/engine/summary.py +1 -1
  60. package/dist/skills/ultimate-browsing/references/insane-search/README.md +4 -4
  61. package/dist/skills/ultimate-browsing/scripts/extract_cookies.py +4 -2
  62. package/dist/skills/ulw-research/AGENTS.md +8 -4
  63. package/dist/skills/ulw-research/SKILL.md +7 -316
  64. package/dist/skills/ulw-research/references/deliverable-phase.md +23 -21
  65. package/dist/skills/ulw-research/references/fact-ledger-grammar.md +7 -3
  66. package/dist/skills/ulw-research/references/fact-ledger.md +42 -8
  67. package/dist/skills/ulw-research/references/fidelity-claim-kinds.md +1 -1
  68. package/dist/skills/ulw-research/references/markdown-blocks.md +12 -0
  69. package/dist/skills/ulw-research/references/report-gates.md +12 -5
  70. package/dist/skills/ulw-research/references/research-collection.md +195 -0
  71. package/dist/skills/ulw-research/references/research-delivery.md +123 -0
  72. package/dist/skills/ulw-research/scripts/claim-kinds.mjs +33 -5
  73. package/dist/skills/ulw-research/scripts/claim-kinds.test.ts +37 -2
  74. package/dist/skills/ulw-research/scripts/claim-prefix.test.ts +76 -0
  75. package/dist/skills/ulw-research/scripts/contracts-validation.test.ts +83 -0
  76. package/dist/skills/ulw-research/scripts/contracts.mjs +13 -4
  77. package/dist/skills/ulw-research/scripts/contracts.test.ts +6 -110
  78. package/dist/skills/ulw-research/scripts/conversion-altered.test.ts +154 -0
  79. package/dist/skills/ulw-research/scripts/conversion-originals.test.ts +297 -0
  80. package/dist/skills/ulw-research/scripts/conversion-probe-classes.test.ts +361 -0
  81. package/dist/skills/ulw-research/scripts/conversion-probe.mjs +229 -0
  82. package/dist/skills/ulw-research/scripts/conversion-probe.test.ts +218 -0
  83. package/dist/skills/ulw-research/scripts/copy-returns-polish.test.ts +4 -3
  84. package/dist/skills/ulw-research/scripts/css-hiding.mjs +158 -0
  85. package/dist/skills/ulw-research/scripts/css-lite.mjs +4 -2
  86. package/dist/skills/ulw-research/scripts/delivery-barrier-meaning.test.ts +186 -0
  87. package/dist/skills/ulw-research/scripts/delivery-barrier.test.ts +40 -87
  88. package/dist/skills/ulw-research/scripts/entities.mjs +13 -4
  89. package/dist/skills/ulw-research/scripts/fact-commands-fill.mjs +11 -1
  90. package/dist/skills/ulw-research/scripts/fact-commands-meaning.mjs +13 -9
  91. package/dist/skills/ulw-research/scripts/fact-commands-polish.mjs +40 -18
  92. package/dist/skills/ulw-research/scripts/fact-commands-verify.mjs +25 -8
  93. package/dist/skills/ulw-research/scripts/fact-commands.mjs +35 -16
  94. package/dist/skills/ulw-research/scripts/fact-derive-2.test.ts +149 -0
  95. package/dist/skills/ulw-research/scripts/fact-derive.mjs +7 -2
  96. package/dist/skills/ulw-research/scripts/fact-derive.test.ts +7 -192
  97. package/dist/skills/ulw-research/scripts/fact-fill.mjs +6 -4
  98. package/dist/skills/ulw-research/scripts/fact-ledger-kinds.mjs +6 -7
  99. package/dist/skills/ulw-research/scripts/fact-ledger-kinds.test.ts +2 -1
  100. package/dist/skills/ulw-research/scripts/fact-ledger-legacy.test.ts +2 -1
  101. package/dist/skills/ulw-research/scripts/fact-locate.mjs +15 -9
  102. package/dist/skills/ulw-research/scripts/fact-unit-dimensions-2.test.ts +149 -0
  103. package/dist/skills/ulw-research/scripts/fact-unit-dimensions.test.ts +4 -168
  104. package/dist/skills/ulw-research/scripts/fact-unit-rules.test.ts +3 -229
  105. package/dist/skills/ulw-research/scripts/fidelity-claims.mjs +4 -3
  106. package/dist/skills/ulw-research/scripts/fidelity-csv.mjs +32 -0
  107. package/dist/skills/ulw-research/scripts/fidelity-lexicon.mjs +1 -1
  108. package/dist/skills/ulw-research/scripts/fidelity-originals.mjs +161 -0
  109. package/dist/skills/ulw-research/scripts/fidelity-policy.mjs +4 -4
  110. package/dist/skills/ulw-research/scripts/fidelity-records.mjs +81 -14
  111. package/dist/skills/ulw-research/scripts/fidelity-snapshot-readability.mjs +49 -0
  112. package/dist/skills/ulw-research/scripts/fidelity-sources.mjs +76 -0
  113. package/dist/skills/ulw-research/scripts/fidelity-trace.mjs +25 -11
  114. package/dist/skills/ulw-research/scripts/fidelity-value-unit.mjs +202 -0
  115. package/dist/skills/ulw-research/scripts/fidelity-values.mjs +40 -39
  116. package/dist/skills/ulw-research/scripts/fidelity-values.test.ts +8 -29
  117. package/dist/skills/ulw-research/scripts/gates-fidelity.mjs +8 -6
  118. package/dist/skills/ulw-research/scripts/gates-static-headings.test.ts +17 -0
  119. package/dist/skills/ulw-research/scripts/gates-static.mjs +10 -1
  120. package/dist/skills/ulw-research/scripts/html-lite.mjs +6 -2
  121. package/dist/skills/ulw-research/scripts/html-markup.mjs +2 -2
  122. package/dist/skills/ulw-research/scripts/html-text-map.mjs +140 -0
  123. package/dist/skills/ulw-research/scripts/html-text-map.test.ts +148 -0
  124. package/dist/skills/ulw-research/scripts/markdown-closers.mjs +126 -0
  125. package/dist/skills/ulw-research/scripts/md-lite-boundaries.test.ts +46 -0
  126. package/dist/skills/ulw-research/scripts/md-lite.mjs +1 -1
  127. package/dist/skills/ulw-research/scripts/number-grammar.mjs +33 -0
  128. package/dist/skills/ulw-research/scripts/number-grammar.test.ts +36 -0
  129. package/dist/skills/ulw-research/scripts/numerals.mjs +18 -7
  130. package/dist/skills/ulw-research/scripts/numerals.test.ts +3 -0
  131. package/dist/skills/ulw-research/scripts/outcome-start.mjs +66 -0
  132. package/dist/skills/ulw-research/scripts/outcome-start.test.ts +109 -0
  133. package/dist/skills/ulw-research/scripts/outcome.mjs +11 -22
  134. package/dist/skills/ulw-research/scripts/page-source-locator.test.ts +187 -0
  135. package/dist/skills/ulw-research/scripts/placeholder.mjs +18 -6
  136. package/dist/skills/ulw-research/scripts/polish-apply.mjs +3 -2
  137. package/dist/skills/ulw-research/scripts/polish-markup.test.ts +15 -1
  138. package/dist/skills/ulw-research/scripts/polish-partial.test.ts +42 -0
  139. package/dist/skills/ulw-research/scripts/raw-decode.mjs +110 -0
  140. package/dist/skills/ulw-research/scripts/raw-locator.mjs +47 -0
  141. package/dist/skills/ulw-research/scripts/raw-snapshot.test.ts +139 -0
  142. package/dist/skills/ulw-research/scripts/report-tools-2.test.ts +233 -0
  143. package/dist/skills/ulw-research/scripts/report-tools-3.test.ts +141 -0
  144. package/dist/skills/ulw-research/scripts/report-tools-4.test.ts +135 -0
  145. package/dist/skills/ulw-research/scripts/report-tools-5.test.ts +231 -0
  146. package/dist/skills/ulw-research/scripts/report-tools-commands.mjs +1 -1
  147. package/dist/skills/ulw-research/scripts/report-tools.mjs +7 -1
  148. package/dist/skills/ulw-research/scripts/report-tools.test.ts +5 -800
  149. package/dist/skills/ulw-research/scripts/snapshot-readability.test.ts +116 -0
  150. package/dist/skills/ulw-research/scripts/source-commands.mjs +79 -26
  151. package/dist/skills/ulw-research/scripts/source-integrity.test.ts +243 -0
  152. package/dist/skills/ulw-research/scripts/source-refresh.mjs +82 -0
  153. package/dist/skills/ulw-research/scripts/source-snapshot.mjs +73 -69
  154. package/dist/skills/ulw-research/scripts/source-snapshot.test.ts +7 -4
  155. package/dist/skills/ulw-research/scripts/source-view.mjs +89 -0
  156. package/dist/skills/ulw-research/scripts/sources-citation.mjs +203 -0
  157. package/dist/skills/ulw-research/scripts/sources-citation.test.ts +236 -0
  158. package/dist/skills/ulw-research/scripts/tests/contracts-support.ts +147 -0
  159. package/dist/skills/ulw-research/scripts/tests/delivery-barrier-session.ts +59 -0
  160. package/dist/skills/ulw-research/scripts/tests/derive-clock.ts +4 -0
  161. package/dist/skills/ulw-research/scripts/tests/fact-derive-support.ts +65 -0
  162. package/dist/skills/ulw-research/scripts/tests/fact-unit-dimensions-support.ts +41 -0
  163. package/dist/skills/ulw-research/scripts/tests/fact-unit-rules-support.ts +241 -0
  164. package/dist/skills/ulw-research/scripts/tests/fidelity-values-support.ts +30 -0
  165. package/dist/skills/ulw-research/scripts/tests/fixtures/conversion-probe.html +14 -0
  166. package/dist/skills/ulw-research/scripts/tests/fixtures/sources-shapes.html +20 -0
  167. package/dist/skills/ulw-research/scripts/tests/numeral-classes.ts +196 -0
  168. package/dist/skills/ulw-research/scripts/tests/report-tools-support.ts +182 -0
  169. package/dist/skills/ulw-research/scripts/tests/value-mode-support.ts +48 -0
  170. package/dist/skills/ulw-research/scripts/units.mjs +14 -3
  171. package/dist/skills/ulw-research/scripts/units.test.ts +10 -0
  172. package/dist/skills/ulw-research/scripts/value-mode-unit-classes.test.ts +255 -0
  173. package/dist/skills/ulw-research/scripts/value-mode-unit.test.ts +179 -0
  174. package/dist/skills/ulw-research/scripts/value-rules-cli.test.ts +157 -0
  175. package/dist/skills/ulw-research/scripts/value-rules-followups.test.ts +72 -0
  176. package/dist/skills/ulw-research/scripts/value-rules-priority.test.ts +80 -0
  177. package/dist/tools/delegate-task/executor-types.d.ts +1 -0
  178. package/dist/tools/delegate-task/sync-session-existence.d.ts +4 -0
  179. package/dist/tools/delegate-task/types.d.ts +1 -0
  180. package/dist/tools/skill/description-formatter.d.ts +2 -1
  181. package/dist/tools/skill/types.d.ts +6 -0
  182. package/dist/tui.js +131 -33
  183. package/package.json +18 -15
  184. package/packages/git-bash-mcp/package.json +1 -1
  185. package/packages/omo-codex/plugin/.codex-plugin/plugin.json +1 -1
  186. package/packages/omo-codex/plugin/README.md +1 -1
  187. package/packages/omo-codex/plugin/components/bootstrap/hooks/hooks.json +1 -1
  188. package/packages/omo-codex/plugin/components/bootstrap/package.json +1 -1
  189. package/packages/omo-codex/plugin/components/comment-checker/hooks/hooks.json +1 -1
  190. package/packages/omo-codex/plugin/components/comment-checker/package.json +1 -1
  191. package/packages/omo-codex/plugin/components/git-bash/hooks/hooks.json +2 -2
  192. package/packages/omo-codex/plugin/components/git-bash/package.json +1 -1
  193. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/hooks/hooks.json +1 -1
  194. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/package.json +1 -1
  195. package/packages/omo-codex/plugin/components/lcx/skills/lcx-contribute-bug-fix/SKILL.md +51 -55
  196. package/packages/omo-codex/plugin/components/lcx/skills/lcx-contribute-bug-fix/agents/openai.yaml +1 -1
  197. package/packages/omo-codex/plugin/components/lcx/skills/lcx-report-bug/SKILL.md +7 -7
  198. package/packages/omo-codex/plugin/components/lcx/skills/lcx-report-bug/agents/openai.yaml +1 -1
  199. package/packages/omo-codex/plugin/components/lsp/dist/.omo-runtime-manifest.json +2 -2
  200. package/packages/omo-codex/plugin/components/lsp/hooks/hooks.json +2 -2
  201. package/packages/omo-codex/plugin/components/lsp/package.json +1 -1
  202. package/packages/omo-codex/plugin/components/rules/hooks/hooks.json +4 -4
  203. package/packages/omo-codex/plugin/components/rules/package.json +1 -1
  204. package/packages/omo-codex/plugin/components/teammode/hooks/hooks.json +1 -1
  205. package/packages/omo-codex/plugin/components/teammode/package.json +2 -2
  206. package/packages/omo-codex/plugin/components/telemetry/README.md +2 -2
  207. package/packages/omo-codex/plugin/components/telemetry/dist/cli.js +52 -15
  208. package/packages/omo-codex/plugin/components/telemetry/dist/posthog.js +52 -19
  209. package/packages/omo-codex/plugin/components/telemetry/hooks/hooks.json +1 -1
  210. package/packages/omo-codex/plugin/components/telemetry/package.json +1 -1
  211. package/packages/omo-codex/plugin/components/telemetry/src/codex-hook.ts +2 -9
  212. package/packages/omo-codex/plugin/components/telemetry/src/posthog.ts +4 -13
  213. package/packages/omo-codex/plugin/components/telemetry/src/product-identity.ts +0 -2
  214. package/packages/omo-codex/plugin/components/telemetry/test/codex-hook-diagnostics.test.ts +0 -3
  215. package/packages/omo-codex/plugin/components/telemetry/test/codex-hook.test.ts +3 -8
  216. package/packages/omo-codex/plugin/components/ultrawork/dist/cli.js +3 -0
  217. package/packages/omo-codex/plugin/components/ultrawork/hooks/hooks.json +1 -1
  218. package/packages/omo-codex/plugin/components/ultrawork/package.json +1 -1
  219. package/packages/omo-codex/plugin/components/ultrawork/src/directive-content.ts +1 -1
  220. package/packages/omo-codex/plugin/components/ulw-execute-continuation/hooks/hooks.json +1 -1
  221. package/packages/omo-codex/plugin/components/ulw-execute-continuation/package.json +1 -1
  222. package/packages/omo-codex/plugin/components/ulw-loop/directive.md +3 -0
  223. package/packages/omo-codex/plugin/components/ulw-loop/hooks/hooks.json +5 -5
  224. package/packages/omo-codex/plugin/components/ulw-loop/package.json +1 -1
  225. package/packages/omo-codex/plugin/hooks/post-compact-resetting-git-bash-mcp-reminder.json +1 -1
  226. package/packages/omo-codex/plugin/hooks/post-compact-resetting-lsp-diagnostics-cache.json +1 -1
  227. package/packages/omo-codex/plugin/hooks/post-compact-resetting-project-rule-cache.json +1 -1
  228. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-comments.json +1 -1
  229. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-lsp-diagnostics.json +1 -1
  230. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-thread-title-hygiene.json +1 -1
  231. package/packages/omo-codex/plugin/hooks/post-tool-use-matching-project-rules.json +1 -1
  232. package/packages/omo-codex/plugin/hooks/post-tool-use-recording-spawn-admission.json +1 -1
  233. package/packages/omo-codex/plugin/hooks/pre-tool-use-enforcing-unlimited-goal-budget.json +1 -1
  234. package/packages/omo-codex/plugin/hooks/pre-tool-use-guarding-ulw-loop-spawns.json +1 -1
  235. package/packages/omo-codex/plugin/hooks/pre-tool-use-recommending-git-bash-mcp.json +1 -1
  236. package/packages/omo-codex/plugin/hooks/session-start-checking-auto-update.json +1 -1
  237. package/packages/omo-codex/plugin/hooks/session-start-checking-bootstrap-provisioning.json +1 -1
  238. package/packages/omo-codex/plugin/hooks/session-start-loading-project-rules.json +1 -1
  239. package/packages/omo-codex/plugin/hooks/session-start-recording-session-telemetry.json +1 -1
  240. package/packages/omo-codex/plugin/hooks/stop-checking-ulw-execute-continuation.json +1 -1
  241. package/packages/omo-codex/plugin/hooks/stop-checking-ulw-loop-resume.json +1 -1
  242. package/packages/omo-codex/plugin/hooks/subagent-stop-verifying-lazycodex-executor-evidence.json +1 -1
  243. package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ultrawork-trigger.json +1 -1
  244. package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ulw-loop-steering.json +1 -1
  245. package/packages/omo-codex/plugin/hooks/user-prompt-submit-loading-project-rules.json +1 -1
  246. package/packages/omo-codex/plugin/package-lock.json +16 -16
  247. package/packages/omo-codex/plugin/package.json +1 -1
  248. package/packages/omo-codex/plugin/shared/package.json +1 -1
  249. package/packages/omo-codex/plugin/skills/ast-grep/README.md +1 -1
  250. package/packages/omo-codex/plugin/skills/ast-grep/install.ps1 +5 -3
  251. package/packages/omo-codex/plugin/skills/ast-grep/install.sh +4 -5
  252. package/packages/omo-codex/plugin/skills/ast-grep/references/install.md +2 -2
  253. package/packages/omo-codex/plugin/skills/ast-grep/scripts/ast_grep_helper.py +13 -3
  254. package/packages/omo-codex/plugin/skills/browser/runtime/omowright/manifest.json +1 -1
  255. package/packages/omo-codex/plugin/skills/coding-agent-sessions/AGENTS.md +5 -4
  256. package/packages/omo-codex/plugin/skills/coding-agent-sessions/SKILL.md +11 -11
  257. package/packages/omo-codex/plugin/skills/coding-agent-sessions/references/all-platforms.md +2 -2
  258. package/packages/omo-codex/plugin/skills/coding-agent-sessions/references/codex.md +4 -4
  259. package/packages/omo-codex/plugin/skills/coding-agent-sessions/scripts/agent_sessions/cli.py +1 -1
  260. package/packages/omo-codex/plugin/skills/coding-agent-sessions/scripts/find-agent-sessions.py +6 -3
  261. package/packages/omo-codex/plugin/skills/frontend/ATTRIBUTION.md +1 -1
  262. package/packages/omo-codex/plugin/skills/frontend/SKILL.md +34 -12
  263. package/packages/omo-codex/plugin/skills/frontend/references/design/README.md +2 -2
  264. package/packages/omo-codex/plugin/skills/frontend/references/design/_INDEX.md +2 -1
  265. package/packages/omo-codex/plugin/skills/frontend/references/design/component-catalogs.md +14 -10
  266. package/packages/omo-codex/plugin/skills/frontend/references/design/design-system-architecture.md +19 -6
  267. package/packages/omo-codex/plugin/skills/frontend/references/design/grounding.md +29 -0
  268. package/packages/omo-codex/plugin/skills/frontend/references/ui-ux-db/scripts/design_system.py +2 -0
  269. package/packages/omo-codex/plugin/skills/frontend/references/ui-ux-db/scripts/search.py +2 -0
  270. package/packages/omo-codex/plugin/skills/lcx-contribute-bug-fix/SKILL.md +51 -55
  271. package/packages/omo-codex/plugin/skills/lcx-contribute-bug-fix/agents/openai.yaml +1 -1
  272. package/packages/omo-codex/plugin/skills/lcx-report-bug/SKILL.md +7 -7
  273. package/packages/omo-codex/plugin/skills/lcx-report-bug/agents/openai.yaml +1 -1
  274. package/packages/omo-codex/plugin/skills/ultimate-browsing/SKILL.md +5 -4
  275. package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/AGENTS.md +2 -2
  276. package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/__main__.py +9 -6
  277. package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/summary.py +1 -1
  278. package/packages/omo-codex/plugin/skills/ultimate-browsing/references/insane-search/README.md +4 -4
  279. package/packages/omo-codex/plugin/skills/ultimate-browsing/scripts/extract_cookies.py +4 -2
  280. package/packages/omo-codex/plugin/skills/ultrawork/SKILL.md +3 -0
  281. package/packages/omo-codex/plugin/skills/ulw-research/AGENTS.md +8 -4
  282. package/packages/omo-codex/plugin/skills/ulw-research/SKILL.md +7 -316
  283. package/packages/omo-codex/plugin/skills/ulw-research/references/deliverable-phase.md +23 -21
  284. package/packages/omo-codex/plugin/skills/ulw-research/references/fact-ledger-grammar.md +7 -3
  285. package/packages/omo-codex/plugin/skills/ulw-research/references/fact-ledger.md +42 -8
  286. package/packages/omo-codex/plugin/skills/ulw-research/references/fidelity-claim-kinds.md +1 -1
  287. package/packages/omo-codex/plugin/skills/ulw-research/references/markdown-blocks.md +12 -0
  288. package/packages/omo-codex/plugin/skills/ulw-research/references/report-gates.md +12 -5
  289. package/packages/omo-codex/plugin/skills/ulw-research/references/research-collection.md +195 -0
  290. package/packages/omo-codex/plugin/skills/ulw-research/references/research-delivery.md +123 -0
  291. package/packages/omo-codex/plugin/skills/ulw-research/scripts/claim-kinds.mjs +33 -5
  292. package/packages/omo-codex/plugin/skills/ulw-research/scripts/contracts.mjs +13 -4
  293. package/packages/omo-codex/plugin/skills/ulw-research/scripts/conversion-probe.mjs +229 -0
  294. package/packages/omo-codex/plugin/skills/ulw-research/scripts/css-hiding.mjs +158 -0
  295. package/packages/omo-codex/plugin/skills/ulw-research/scripts/css-lite.mjs +4 -2
  296. package/packages/omo-codex/plugin/skills/ulw-research/scripts/entities.mjs +13 -4
  297. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-commands-fill.mjs +11 -1
  298. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-commands-meaning.mjs +13 -9
  299. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-commands-polish.mjs +40 -18
  300. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-commands-verify.mjs +25 -8
  301. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-commands.mjs +35 -16
  302. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-derive.mjs +7 -2
  303. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-fill.mjs +6 -4
  304. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-ledger-kinds.mjs +6 -7
  305. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-locate.mjs +15 -9
  306. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-claims.mjs +4 -3
  307. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-csv.mjs +32 -0
  308. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-lexicon.mjs +1 -1
  309. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-originals.mjs +161 -0
  310. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-policy.mjs +4 -4
  311. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-records.mjs +81 -14
  312. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-snapshot-readability.mjs +49 -0
  313. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-sources.mjs +76 -0
  314. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-trace.mjs +25 -11
  315. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-value-unit.mjs +202 -0
  316. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-values.mjs +40 -39
  317. package/packages/omo-codex/plugin/skills/ulw-research/scripts/gates-fidelity.mjs +8 -6
  318. package/packages/omo-codex/plugin/skills/ulw-research/scripts/gates-static.mjs +10 -1
  319. package/packages/omo-codex/plugin/skills/ulw-research/scripts/html-lite.mjs +6 -2
  320. package/packages/omo-codex/plugin/skills/ulw-research/scripts/html-markup.mjs +2 -2
  321. package/packages/omo-codex/plugin/skills/ulw-research/scripts/html-text-map.mjs +140 -0
  322. package/packages/omo-codex/plugin/skills/ulw-research/scripts/markdown-closers.mjs +126 -0
  323. package/packages/omo-codex/plugin/skills/ulw-research/scripts/md-lite.mjs +1 -1
  324. package/packages/omo-codex/plugin/skills/ulw-research/scripts/number-grammar.mjs +33 -0
  325. package/packages/omo-codex/plugin/skills/ulw-research/scripts/numerals.mjs +18 -7
  326. package/packages/omo-codex/plugin/skills/ulw-research/scripts/outcome-start.mjs +66 -0
  327. package/packages/omo-codex/plugin/skills/ulw-research/scripts/outcome.mjs +11 -22
  328. package/packages/omo-codex/plugin/skills/ulw-research/scripts/placeholder.mjs +18 -6
  329. package/packages/omo-codex/plugin/skills/ulw-research/scripts/polish-apply.mjs +3 -2
  330. package/packages/omo-codex/plugin/skills/ulw-research/scripts/raw-decode.mjs +110 -0
  331. package/packages/omo-codex/plugin/skills/ulw-research/scripts/raw-locator.mjs +47 -0
  332. package/packages/omo-codex/plugin/skills/ulw-research/scripts/report-tools-commands.mjs +1 -1
  333. package/packages/omo-codex/plugin/skills/ulw-research/scripts/report-tools.mjs +7 -1
  334. package/packages/omo-codex/plugin/skills/ulw-research/scripts/source-commands.mjs +79 -26
  335. package/packages/omo-codex/plugin/skills/ulw-research/scripts/source-refresh.mjs +82 -0
  336. package/packages/omo-codex/plugin/skills/ulw-research/scripts/source-snapshot.mjs +73 -69
  337. package/packages/omo-codex/plugin/skills/ulw-research/scripts/source-view.mjs +89 -0
  338. package/packages/omo-codex/plugin/skills/ulw-research/scripts/sources-citation.mjs +203 -0
  339. package/packages/omo-codex/plugin/skills/ulw-research/scripts/units.mjs +14 -3
  340. package/packages/omo-codex/plugin/test/payload-equivalence.test.mjs +6 -4
  341. package/packages/omo-codex/plugin/test/sync-skills.test.mjs +4 -3
  342. package/packages/omo-codex/scripts/install-dist/install-local.mjs +338 -237
  343. package/packages/prompts-core/prompts/ultrawork/codex.md +3 -0
  344. package/packages/shared-skills/skill-source-filter.mjs +6 -2
  345. package/packages/shared-skills/skills/ast-grep/README.md +1 -1
  346. package/packages/shared-skills/skills/ast-grep/install.ps1 +5 -3
  347. package/packages/shared-skills/skills/ast-grep/install.sh +4 -5
  348. package/packages/shared-skills/skills/ast-grep/references/install.md +2 -2
  349. package/packages/shared-skills/skills/ast-grep/scripts/ast_grep_helper.py +13 -3
  350. package/packages/shared-skills/skills/browser/runtime/omowright/manifest.json +1 -1
  351. package/packages/shared-skills/skills/coding-agent-sessions/AGENTS.md +5 -4
  352. package/packages/shared-skills/skills/coding-agent-sessions/SKILL.md +11 -11
  353. package/packages/shared-skills/skills/coding-agent-sessions/references/all-platforms.md +2 -2
  354. package/packages/shared-skills/skills/coding-agent-sessions/references/codex.md +4 -4
  355. package/packages/shared-skills/skills/coding-agent-sessions/scripts/agent_sessions/cli.py +1 -1
  356. package/packages/shared-skills/skills/coding-agent-sessions/scripts/find-agent-sessions.py +6 -3
  357. package/packages/shared-skills/skills/frontend/ATTRIBUTION.md +1 -1
  358. package/packages/shared-skills/skills/frontend/SKILL.md +34 -12
  359. package/packages/shared-skills/skills/frontend/references/design/README.md +2 -2
  360. package/packages/shared-skills/skills/frontend/references/design/_INDEX.md +2 -1
  361. package/packages/shared-skills/skills/frontend/references/design/component-catalogs.md +14 -10
  362. package/packages/shared-skills/skills/frontend/references/design/design-system-architecture.md +19 -6
  363. package/packages/shared-skills/skills/frontend/references/design/grounding.md +29 -0
  364. package/packages/shared-skills/skills/frontend/references/ui-ux-db/scripts/design_system.py +2 -0
  365. package/packages/shared-skills/skills/frontend/references/ui-ux-db/scripts/search.py +2 -0
  366. package/packages/shared-skills/skills/ultimate-browsing/SKILL.md +5 -4
  367. package/packages/shared-skills/skills/ultimate-browsing/engine/AGENTS.md +2 -2
  368. package/packages/shared-skills/skills/ultimate-browsing/engine/__main__.py +9 -6
  369. package/packages/shared-skills/skills/ultimate-browsing/engine/summary.py +1 -1
  370. package/packages/shared-skills/skills/ultimate-browsing/references/insane-search/README.md +4 -4
  371. package/packages/shared-skills/skills/ultimate-browsing/scripts/extract_cookies.py +4 -2
  372. package/packages/shared-skills/skills/ulw-research/AGENTS.md +8 -4
  373. package/packages/shared-skills/skills/ulw-research/SKILL.md +7 -316
  374. package/packages/shared-skills/skills/ulw-research/references/deliverable-phase.md +23 -21
  375. package/packages/shared-skills/skills/ulw-research/references/fact-ledger-grammar.md +7 -3
  376. package/packages/shared-skills/skills/ulw-research/references/fact-ledger.md +42 -8
  377. package/packages/shared-skills/skills/ulw-research/references/fidelity-claim-kinds.md +1 -1
  378. package/packages/shared-skills/skills/ulw-research/references/markdown-blocks.md +12 -0
  379. package/packages/shared-skills/skills/ulw-research/references/report-gates.md +12 -5
  380. package/packages/shared-skills/skills/ulw-research/references/research-collection.md +195 -0
  381. package/packages/shared-skills/skills/ulw-research/references/research-delivery.md +123 -0
  382. package/packages/shared-skills/skills/ulw-research/scripts/claim-kinds.mjs +33 -5
  383. package/packages/shared-skills/skills/ulw-research/scripts/claim-kinds.test.ts +37 -2
  384. package/packages/shared-skills/skills/ulw-research/scripts/claim-prefix.test.ts +76 -0
  385. package/packages/shared-skills/skills/ulw-research/scripts/contracts-validation.test.ts +83 -0
  386. package/packages/shared-skills/skills/ulw-research/scripts/contracts.mjs +13 -4
  387. package/packages/shared-skills/skills/ulw-research/scripts/contracts.test.ts +6 -110
  388. package/packages/shared-skills/skills/ulw-research/scripts/conversion-altered.test.ts +154 -0
  389. package/packages/shared-skills/skills/ulw-research/scripts/conversion-originals.test.ts +297 -0
  390. package/packages/shared-skills/skills/ulw-research/scripts/conversion-probe-classes.test.ts +361 -0
  391. package/packages/shared-skills/skills/ulw-research/scripts/conversion-probe.mjs +229 -0
  392. package/packages/shared-skills/skills/ulw-research/scripts/conversion-probe.test.ts +218 -0
  393. package/packages/shared-skills/skills/ulw-research/scripts/copy-returns-polish.test.ts +4 -3
  394. package/packages/shared-skills/skills/ulw-research/scripts/css-hiding.mjs +158 -0
  395. package/packages/shared-skills/skills/ulw-research/scripts/css-lite.mjs +4 -2
  396. package/packages/shared-skills/skills/ulw-research/scripts/delivery-barrier-meaning.test.ts +186 -0
  397. package/packages/shared-skills/skills/ulw-research/scripts/delivery-barrier.test.ts +40 -87
  398. package/packages/shared-skills/skills/ulw-research/scripts/entities.mjs +13 -4
  399. package/packages/shared-skills/skills/ulw-research/scripts/fact-commands-fill.mjs +11 -1
  400. package/packages/shared-skills/skills/ulw-research/scripts/fact-commands-meaning.mjs +13 -9
  401. package/packages/shared-skills/skills/ulw-research/scripts/fact-commands-polish.mjs +40 -18
  402. package/packages/shared-skills/skills/ulw-research/scripts/fact-commands-verify.mjs +25 -8
  403. package/packages/shared-skills/skills/ulw-research/scripts/fact-commands.mjs +35 -16
  404. package/packages/shared-skills/skills/ulw-research/scripts/fact-derive-2.test.ts +149 -0
  405. package/packages/shared-skills/skills/ulw-research/scripts/fact-derive.mjs +7 -2
  406. package/packages/shared-skills/skills/ulw-research/scripts/fact-derive.test.ts +7 -192
  407. package/packages/shared-skills/skills/ulw-research/scripts/fact-fill.mjs +6 -4
  408. package/packages/shared-skills/skills/ulw-research/scripts/fact-ledger-kinds.mjs +6 -7
  409. package/packages/shared-skills/skills/ulw-research/scripts/fact-ledger-kinds.test.ts +2 -1
  410. package/packages/shared-skills/skills/ulw-research/scripts/fact-ledger-legacy.test.ts +2 -1
  411. package/packages/shared-skills/skills/ulw-research/scripts/fact-locate.mjs +15 -9
  412. package/packages/shared-skills/skills/ulw-research/scripts/fact-unit-dimensions-2.test.ts +149 -0
  413. package/packages/shared-skills/skills/ulw-research/scripts/fact-unit-dimensions.test.ts +4 -168
  414. package/packages/shared-skills/skills/ulw-research/scripts/fact-unit-rules.test.ts +3 -229
  415. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-claims.mjs +4 -3
  416. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-csv.mjs +32 -0
  417. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-lexicon.mjs +1 -1
  418. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-originals.mjs +161 -0
  419. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-policy.mjs +4 -4
  420. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-records.mjs +81 -14
  421. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-snapshot-readability.mjs +49 -0
  422. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-sources.mjs +76 -0
  423. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-trace.mjs +25 -11
  424. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-value-unit.mjs +202 -0
  425. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-values.mjs +40 -39
  426. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-values.test.ts +8 -29
  427. package/packages/shared-skills/skills/ulw-research/scripts/gates-fidelity.mjs +8 -6
  428. package/packages/shared-skills/skills/ulw-research/scripts/gates-static-headings.test.ts +17 -0
  429. package/packages/shared-skills/skills/ulw-research/scripts/gates-static.mjs +10 -1
  430. package/packages/shared-skills/skills/ulw-research/scripts/html-lite.mjs +6 -2
  431. package/packages/shared-skills/skills/ulw-research/scripts/html-markup.mjs +2 -2
  432. package/packages/shared-skills/skills/ulw-research/scripts/html-text-map.mjs +140 -0
  433. package/packages/shared-skills/skills/ulw-research/scripts/html-text-map.test.ts +148 -0
  434. package/packages/shared-skills/skills/ulw-research/scripts/markdown-closers.mjs +126 -0
  435. package/packages/shared-skills/skills/ulw-research/scripts/md-lite-boundaries.test.ts +46 -0
  436. package/packages/shared-skills/skills/ulw-research/scripts/md-lite.mjs +1 -1
  437. package/packages/shared-skills/skills/ulw-research/scripts/number-grammar.mjs +33 -0
  438. package/packages/shared-skills/skills/ulw-research/scripts/number-grammar.test.ts +36 -0
  439. package/packages/shared-skills/skills/ulw-research/scripts/numerals.mjs +18 -7
  440. package/packages/shared-skills/skills/ulw-research/scripts/numerals.test.ts +3 -0
  441. package/packages/shared-skills/skills/ulw-research/scripts/outcome-start.mjs +66 -0
  442. package/packages/shared-skills/skills/ulw-research/scripts/outcome-start.test.ts +109 -0
  443. package/packages/shared-skills/skills/ulw-research/scripts/outcome.mjs +11 -22
  444. package/packages/shared-skills/skills/ulw-research/scripts/page-source-locator.test.ts +187 -0
  445. package/packages/shared-skills/skills/ulw-research/scripts/placeholder.mjs +18 -6
  446. package/packages/shared-skills/skills/ulw-research/scripts/polish-apply.mjs +3 -2
  447. package/packages/shared-skills/skills/ulw-research/scripts/polish-markup.test.ts +15 -1
  448. package/packages/shared-skills/skills/ulw-research/scripts/polish-partial.test.ts +42 -0
  449. package/packages/shared-skills/skills/ulw-research/scripts/raw-decode.mjs +110 -0
  450. package/packages/shared-skills/skills/ulw-research/scripts/raw-locator.mjs +47 -0
  451. package/packages/shared-skills/skills/ulw-research/scripts/raw-snapshot.test.ts +139 -0
  452. package/packages/shared-skills/skills/ulw-research/scripts/report-tools-2.test.ts +233 -0
  453. package/packages/shared-skills/skills/ulw-research/scripts/report-tools-3.test.ts +141 -0
  454. package/packages/shared-skills/skills/ulw-research/scripts/report-tools-4.test.ts +135 -0
  455. package/packages/shared-skills/skills/ulw-research/scripts/report-tools-5.test.ts +231 -0
  456. package/packages/shared-skills/skills/ulw-research/scripts/report-tools-commands.mjs +1 -1
  457. package/packages/shared-skills/skills/ulw-research/scripts/report-tools.mjs +7 -1
  458. package/packages/shared-skills/skills/ulw-research/scripts/report-tools.test.ts +5 -800
  459. package/packages/shared-skills/skills/ulw-research/scripts/snapshot-readability.test.ts +116 -0
  460. package/packages/shared-skills/skills/ulw-research/scripts/source-commands.mjs +79 -26
  461. package/packages/shared-skills/skills/ulw-research/scripts/source-integrity.test.ts +243 -0
  462. package/packages/shared-skills/skills/ulw-research/scripts/source-refresh.mjs +82 -0
  463. package/packages/shared-skills/skills/ulw-research/scripts/source-snapshot.mjs +73 -69
  464. package/packages/shared-skills/skills/ulw-research/scripts/source-snapshot.test.ts +7 -4
  465. package/packages/shared-skills/skills/ulw-research/scripts/source-view.mjs +89 -0
  466. package/packages/shared-skills/skills/ulw-research/scripts/sources-citation.mjs +203 -0
  467. package/packages/shared-skills/skills/ulw-research/scripts/sources-citation.test.ts +236 -0
  468. package/packages/shared-skills/skills/ulw-research/scripts/tests/contracts-support.ts +147 -0
  469. package/packages/shared-skills/skills/ulw-research/scripts/tests/delivery-barrier-session.ts +59 -0
  470. package/packages/shared-skills/skills/ulw-research/scripts/tests/derive-clock.ts +4 -0
  471. package/packages/shared-skills/skills/ulw-research/scripts/tests/fact-derive-support.ts +65 -0
  472. package/packages/shared-skills/skills/ulw-research/scripts/tests/fact-unit-dimensions-support.ts +41 -0
  473. package/packages/shared-skills/skills/ulw-research/scripts/tests/fact-unit-rules-support.ts +241 -0
  474. package/packages/shared-skills/skills/ulw-research/scripts/tests/fidelity-values-support.ts +30 -0
  475. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/conversion-probe.html +14 -0
  476. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/sources-shapes.html +20 -0
  477. package/packages/shared-skills/skills/ulw-research/scripts/tests/numeral-classes.ts +196 -0
  478. package/packages/shared-skills/skills/ulw-research/scripts/tests/report-tools-support.ts +182 -0
  479. package/packages/shared-skills/skills/ulw-research/scripts/tests/value-mode-support.ts +48 -0
  480. package/packages/shared-skills/skills/ulw-research/scripts/units.mjs +14 -3
  481. package/packages/shared-skills/skills/ulw-research/scripts/units.test.ts +10 -0
  482. package/packages/shared-skills/skills/ulw-research/scripts/value-mode-unit-classes.test.ts +255 -0
  483. package/packages/shared-skills/skills/ulw-research/scripts/value-mode-unit.test.ts +179 -0
  484. package/packages/shared-skills/skills/ulw-research/scripts/value-rules-cli.test.ts +157 -0
  485. package/packages/shared-skills/skills/ulw-research/scripts/value-rules-followups.test.ts +72 -0
  486. package/packages/shared-skills/skills/ulw-research/scripts/value-rules-priority.test.ts +80 -0
  487. package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/tests/fixtures/amp_redirect_stub.html +0 -7
  488. package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/tests/fixtures/search_interstitial.html +0 -19
  489. package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/tests/fixtures/wayback_available.json +0 -1
  490. package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/tests/fixtures/wayback_snapshot.html +0 -1128
  491. package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/tests/test_fetch_chain.py +0 -77
  492. package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/tests/test_playwright_stealth.py +0 -86
  493. package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/tests/test_playwright_templates.py +0 -279
  494. package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/tests/test_surrogate.py +0 -252
  495. package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/tests/test_surrogate_validators.py +0 -78
@@ -3,43 +3,24 @@ import { createHash } from "node:crypto"
3
3
  import { copyFileSync, existsSync, mkdtempSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs"
4
4
  import { tmpdir } from "node:os"
5
5
  import { delimiter, join } from "node:path"
6
-
7
6
  import { PROMOTION } from "./fact-commands-meaning.mjs"
8
7
  import { writeFidelityRecord } from "./fidelity-records.mjs"
9
8
  import { PAIR_KINDS } from "./fact-pairs.mjs"
10
9
  import { VERDICT_SCHEMA } from "./meaning-check.mjs"
11
10
  import { POLISH_MARKUP_REASON } from "./polish-apply.mjs"
12
11
  import { COMMANDS, run } from "./report-tools.mjs"
12
+ import { SCRIPT, FIXTURES, sessionDir, nodeWithoutBun, defectsFile, FIDELITY, sha256, CliRow, CliJson, nodeJson, fidelitySession, verify, codesOf, Pair, pairedSession, verdicts, calibrationRows, runLines, PolishJson, polishSession, polishApply, setField, fillTemplate, textSource, ledgerRow } from "./tests/report-tools-support"
13
13
 
14
- const SCRIPT = join(import.meta.dir, "report-tools.mjs")
15
- const FIXTURES = join(import.meta.dir, "tests", "fixtures")
16
-
17
- function sessionDir() {
18
- const root = mkdtempSync(join(tmpdir(), "report-tools-cli-"))
19
- onTestFinished(() => rmSync(root, { recursive: true, force: true }))
20
- const dir = join(root, "20260101-120000")
21
- mkdirSync(dir)
22
- return dir
23
- }
24
-
25
- function nodeWithoutBun(args: string[]) {
26
- const nodePath = Bun.which("node")
27
- if (nodePath === null) throw new Error("node not found on PATH")
28
- const path = (process.env.PATH ?? "").split(delimiter).filter((entry) => !entry.includes(".bun")).join(delimiter)
29
- return Bun.spawnSync({ cmd: [nodePath, SCRIPT, ...args], env: { ...process.env, PATH: path } })
30
- }
31
14
 
32
- function defectsFile(dir: string, name: string, codes: string[]) {
33
- const file = join(dir, name)
34
- writeFileSync(file, JSON.stringify(codes.map((code) => ({ code, message: code }))))
35
- return file
36
- }
37
15
 
38
16
  describe("help and usage", () => {
39
17
  test("#given --help --json #when run #then it lists exactly the command matrix", async () => {
40
18
  const result = await run(["--help", "--json"])
41
19
  expect(result.json.commands).toEqual([...COMMANDS])
42
- expect(COMMANDS).toEqual(["check", "layout-probe", "repair decide", "outcome init", "outcome set", "outcome gate", "outcome render", "outcome state", "outcome verify", "outcome finish", "outcome briefing", "format-extract", "fact add", "fact quote", "fact retract", "fact export", "fact fill", "fact verify", "fact derive", "source snapshot", "claims index"])
20
+ expect(result.json.usage["source snapshot"]).toContain("--raw <file>")
21
+ expect(result.json.usage["source snapshot"]).toContain("converted-only")
22
+ expect(Object.keys(result.json.usage)).toEqual(["source snapshot", "source refresh"])
23
+ expect(COMMANDS).toEqual(["check", "layout-probe", "repair decide", "outcome init", "outcome set", "outcome gate", "outcome render", "outcome state", "outcome verify", "outcome finish", "outcome briefing", "format-extract", "fact add", "fact quote", "fact retract", "fact export", "fact fill", "fact verify", "fact derive", "source snapshot", "source refresh", "source probe-conversion", "claims index"])
43
24
  })
44
25
 
45
26
  test("#given no arguments under node #when spawned #then it exits 2 with a usage line on stderr", () => {
@@ -220,779 +201,3 @@ describe("format-extract exit codes", () => {
220
201
  expect(proc.exitCode).toBe(2)
221
202
  })
222
203
  })
223
-
224
- const FIDELITY = join(FIXTURES, "fidelity")
225
- const sha256 = (path: string) => createHash("sha256").update(readFileSync(path)).digest("hex")
226
-
227
- type CliRow = { fact_id: string; kind: string; value: string | null }
228
- type CliJson = { data: { row: CliRow; context: { match: string } }; defects: { code: string }[] }
229
-
230
- function nodeJson(args: string[]) {
231
- const proc = nodeWithoutBun(args)
232
- const stdout = proc.stdout.toString()
233
- let json = {} as CliJson
234
- try {
235
- json = JSON.parse(stdout) as CliJson
236
- } catch {}
237
- return { code: proc.exitCode, json, stdout, stderr: proc.stderr.toString() }
238
- }
239
-
240
- /** A session with the locked brief, S4 snapshotted, F1 (span), Q1 (completion), and F<n> (span in mm). */
241
- function fidelitySession() {
242
- const dir = sessionDir()
243
- copyFileSync(join(FIXTURES, "fidelity-brief-locked.md"), join(dir, "brief.md"))
244
- const s = ["--session-dir", dir]
245
- const snap = nodeJson(["source", "snapshot", "--id", "S4", "--html", join(FIDELITY, "sources", "S4.html"), ...s])
246
- const add = nodeJson(["fact", "add", "--source", "S4", "--find", "Span length", "--label", "span_len", "--json", ...s])
247
- const quote = nodeJson(["fact", "quote", "--source", "S4", "--match", "no earlier than 2027", "--label", "completion", "--json", ...s])
248
- const derive = nodeJson(["fact", "derive", "--op", "convert", "F1", "--to", "mm", "--label", "span_mm", "--json", ...s])
249
- const mm = derive.json.data?.row?.fact_id
250
- const source = [
251
- '<!doctype html><html lang="en"><head><meta charset="utf-8"><title>Bridge</title></head><body><h1>Bridge</h1>',
252
- "<h2>Findings</h2>",
253
- "<p>The span length is {{F1:span_len}} [S4].</p>",
254
- `<p>In millimetres the span is {{${mm}:span_mm}} [S4].</p>`,
255
- "<p>The operator wrote {{Q1:completion}} [S4].</p>",
256
- "<h2>How this report was made</h2><p>Collected from one cited public page [S4].</p>",
257
- '<h2>Sources</h2><ol><li id="s4">[S4] Bridge page, retrieved 2026-10-01.</li></ol></body></html>',
258
- ].join("\n")
259
- writeFileSync(join(dir, "report.src.html"), source)
260
- return { dir, s, snap, add, quote, derive, mm }
261
- }
262
-
263
- function verify(dir: string, report: string, extra: string[] = []) {
264
- return nodeJson(["fact", "verify", join(dir, report), "--sources", join(dir, "sources"), "--brief", join(dir, "brief.md"), "--session-dir", dir, ...extra])
265
- }
266
-
267
- const codesOf = (json: { defects?: { code: string }[] }) => (json.defects ?? []).map((d) => d.code)
268
-
269
- describe("fact, source, and claims under node", () => {
270
- test("#given a fresh session #when snapshot, add, quote, derive, fill, and verify run #then the filled report verifies clean", () => {
271
- // given
272
- const { dir, s, snap, add, quote, derive, mm } = fidelitySession()
273
- // when
274
- const filled = nodeJson(["fact", "fill", join(dir, "report.src.html"), "--out", join(dir, "report.html"), ...s])
275
- const checked = verify(dir, "report.html")
276
- // then
277
- expect(snap.code).toBe(0)
278
- expect(add.code).toBe(0)
279
- expect(add.json.data.row.value).toBe("320")
280
- expect(add.json.data.context.match).toBe("320 m")
281
- expect(quote.code).toBe(0)
282
- expect(quote.json.data.row.fact_id).toBe("Q1")
283
- expect(derive.code).toBe(0)
284
- expect(mm).toBe("F2")
285
- expect(filled.code).toBe(0)
286
- expect(readFileSync(join(dir, "report.html"), "utf8")).toContain("320,000 mm (320 m)")
287
- expect(checked.code).toBe(0)
288
- expect(checked.json.defects).toEqual([])
289
- const record = JSON.parse(readFileSync(join(dir, "fidelity-defects.json"), "utf8"))
290
- expect(record.artifact_sha256).toBe(sha256(join(dir, "report.html")))
291
- })
292
-
293
- test("#given a filled report #when a value is hand-edited or a number typed #then verify exits 1 with the code", () => {
294
- const { dir, s } = fidelitySession()
295
- nodeJson(["fact", "fill", join(dir, "report.src.html"), "--out", join(dir, "report.html"), ...s])
296
- const html = readFileSync(join(dir, "report.html"), "utf8")
297
- writeFileSync(join(dir, "unit.html"), html.replace("320 m", "320 mm"))
298
- writeFileSync(join(dir, "typed.html"), html.replace("<h2>How", "<p>Revenue grew 12% [S4].</p>\n<h2>How"))
299
- const unit = verify(dir, "unit.html")
300
- const typed = verify(dir, "typed.html")
301
- expect(unit.code).toBe(1)
302
- expect(codesOf(unit.json)).toContain("unit_mismatch")
303
- expect(typed.code).toBe(1)
304
- expect(codesOf(typed.json)).toContain("untraceable_number")
305
- })
306
-
307
- test("#given an anchor that is not in the source #when fact add runs #then it exits 1 with fact_not_located and writes no row", () => {
308
- const { dir, s } = fidelitySession()
309
- const proc = nodeJson(["fact", "add", "--source", "S4", "--find", "nope", "--label", "x", ...s])
310
- expect(proc.code).toBe(1)
311
- expect(proc.stdout).toContain("fact_not_located")
312
- expect(JSON.parse(readFileSync(join(dir, "fact-ledger.json"), "utf8")).rows.map((r: { fact_id: string }) => r.fact_id)).toEqual(["F1", "Q1", "F2"])
313
- })
314
-
315
- test("#given a snapshotted source #when snapshotted again with the same or different content #then same is a no-op and different exits 1", () => {
316
- const { dir, s } = fidelitySession()
317
- writeFileSync(join(dir, "other.txt"), "Span length: 999 m\n")
318
- expect(nodeJson(["source", "snapshot", "--id", "S4", "--html", join(FIDELITY, "sources", "S4.html"), ...s]).code).toBe(0)
319
- expect(nodeJson(["source", "snapshot", "--id", "S4", "--text", join(dir, "other.txt"), ...s]).code).toBe(1)
320
- })
321
-
322
- test("#given an assumed value with and without a rationale #when added #then the row is kind assumed, and no rationale exits 2", () => {
323
- const { s } = fidelitySession()
324
- const added = nodeJson(["fact", "add", "--assumed", "12", "--unit", "%", "--rationale", "planning share chosen by the requester", "--label", "share_assumed", "--json", ...s])
325
- expect(added.code).toBe(0)
326
- expect(added.json.data.row.kind).toBe("assumed")
327
- expect(nodeJson(["fact", "add", "--assumed", "12", "--label", "x", ...s]).code).toBe(2)
328
- })
329
-
330
- test("#given the fixture claim graph #when claims index runs #then claims.json lists its claims", () => {
331
- const dir = sessionDir()
332
- const proc = nodeJson(["claims", "index", join(FIDELITY, "claim-graph.md"), "--out", join(dir, "claims.json")])
333
- expect(proc.code).toBe(0)
334
- expect(JSON.parse(readFileSync(join(dir, "claims.json"), "utf8")).claims.length).toBeGreaterThan(0)
335
- })
336
-
337
- test("#given a ledger #when exported as csv #then rows carry fact_id and plot_value and stderr has the coverage line", () => {
338
- const { s } = fidelitySession()
339
- const proc = nodeJson(["fact", "export", "--csv", ...s])
340
- expect(proc.code).toBe(0)
341
- expect(proc.stdout.split("\n")[0]).toContain("fact_id")
342
- expect(proc.stdout).toContain("F2")
343
- expect(proc.stderr).toContain("1 of 1 sources snapshotted")
344
- })
345
- })
346
-
347
- type Pair = { pair_id: string; kind: string; fact_ids?: string[]; sentence: string; quote?: string }
348
-
349
- /** A filled session plus the pair artifact `fact pairs` wrote for it. */
350
- function pairedSession() {
351
- const session = fidelitySession()
352
- const { dir, s } = session
353
- nodeJson(["fact", "fill", join(dir, "report.src.html"), "--out", join(dir, "report.html"), ...s])
354
- const pairs = nodeJson(["fact", "pairs", join(dir, "report.html"), "--ledger", join(dir, "fact-ledger.json"), "--out", join(dir, "pairs.json"), ...s])
355
- return { ...session, pairs }
356
- }
357
-
358
- function verdicts(dir: string, name: string, tier: string, list: object[]) {
359
- const file = join(dir, name)
360
- writeFileSync(file, JSON.stringify({ model_tier: tier, verdicts: list }))
361
- return file
362
- }
363
-
364
- const calibrationRows = (dir: string) =>
365
- readFileSync(join(dir, "fidelity-calibration.jsonl"), "utf8")
366
- .trim()
367
- .split("\n")
368
- .map((line) => JSON.parse(line))
369
- .filter((row) => row.header !== true && row.run !== true)
370
-
371
- const runLines = (dir: string) =>
372
- readFileSync(join(dir, "fidelity-calibration.jsonl"), "utf8")
373
- .trim()
374
- .split("\n")
375
- .map((line) => JSON.parse(line))
376
- .filter((row) => row.run === true)
377
-
378
- describe("meaning verifier under node", () => {
379
- test("#given the filled fixture #when fact pairs runs #then pairs.json holds one quote pair per cited ledger quote", () => {
380
- const { dir, pairs } = pairedSession()
381
- const written = JSON.parse(readFileSync(join(dir, "pairs.json"), "utf8"))
382
- expect(pairs.code).toBe(0)
383
- expect(written.skipped).toBe(false)
384
- expect(written.pairs.map((p: Pair) => [p.pair_id, p.kind, p.fact_ids])).toEqual([
385
- ["P1", "quote", ["F1"]],
386
- ["P2", "quote", ["Q1"]],
387
- ])
388
- expect(written.pairs_sha256).toMatch(/^[0-9a-f]{64}$/)
389
- expect(Object.keys(written.counts).filter((key) => key !== "total" && key !== "force_strong")).toEqual([...PAIR_KINDS])
390
- expect(written.verdict_schema).toEqual(JSON.parse(JSON.stringify(VERDICT_SCHEMA)))
391
- })
392
-
393
- test("#given cheap and strong verdicts agreeing on hedge_dropped #when meaning-check runs #then it exits 1 with one confirmed defect and one calibration line", () => {
394
- const { dir, s } = pairedSession()
395
- const drift = { pair_id: "P2", verdict: "drift", drift_kind: "hedge_dropped", fragment: "no earlier than 2027", source_fragment: "we estimate", explanation: "The estimate reads as a fact." }
396
- const cheap = verdicts(dir, "cheap.json", "cheap", [drift, { pair_id: "P1", verdict: "ok" }])
397
- const strong = verdicts(dir, "strong.json", "strong", [drift])
398
- const checked = nodeJson(["fact", "meaning-check", "--pairs", join(dir, "pairs.json"), "--verdicts", cheap, "--verdicts", strong, ...s])
399
- const record = JSON.parse(readFileSync(join(dir, "fidelity-meaning-defects.json"), "utf8"))
400
- expect(checked.code).toBe(1)
401
- expect(codesOf(checked.json)).toEqual(["meaning_drift_confirmed"])
402
- expect(codesOf(record)).toEqual(["meaning_drift_confirmed"])
403
- expect(calibrationRows(dir)).toHaveLength(1)
404
- expect(calibrationRows(dir)[0]).toMatchObject({ pair_id: "P2", drift_kind: "hedge_dropped", confirmed: true, disputed: false })
405
- const header = JSON.parse(readFileSync(join(dir, "fidelity-calibration.jsonl"), "utf8").split("\n")[0])
406
- expect(header.promotion).toEqual({ ...PROMOTION })
407
- const again = nodeJson(["fact", "meaning-check", "--pairs", join(dir, "pairs.json"), "--verdicts", cheap, "--verdicts", strong, ...s])
408
- expect(again.code).toBe(1)
409
- expect(calibrationRows(dir)).toHaveLength(1)
410
- })
411
-
412
- test("#given a dispute on the confirmed pair #when meaning-check runs #then the defect stays and the calibration line is disputed", () => {
413
- const { dir, s } = pairedSession()
414
- const drift = { pair_id: "P2", verdict: "drift", drift_kind: "hedge_dropped", fragment: "no earlier than 2027", source_fragment: "we estimate" }
415
- const files = ["--verdicts", verdicts(dir, "cheap.json", "cheap", [drift, { pair_id: "P1", verdict: "ok" }]), "--verdicts", verdicts(dir, "strong.json", "strong", [drift])]
416
- const checked = nodeJson(["fact", "meaning-check", "--pairs", join(dir, "pairs.json"), ...files, "--dispute", "P2", "--reason", "the operator states it", ...s])
417
- expect(checked.code).toBe(1)
418
- expect(codesOf(checked.json)).toEqual(["meaning_drift_confirmed"])
419
- expect(calibrationRows(dir)[0]).toMatchObject({ pair_id: "P2", disputed: true, reason: "the operator states it" })
420
- })
421
-
422
- test("#given a dropped verdict and an unread pair #when meaning-check runs #then both pairs are meaning_unverified and it exits 1", () => {
423
- const { dir, s } = pairedSession()
424
- const cheap = verdicts(dir, "cheap.json", "cheap", [{ pair_id: "P2", verdict: "drift", drift_kind: "hedge_dropped", fragment: "certainly by 2027", source_fragment: "we estimate" }])
425
- const checked = nodeJson(["fact", "meaning-check", "--pairs", join(dir, "pairs.json"), "--verdicts", cheap, ...s])
426
- const summary = JSON.parse(checked.stderr.trim().split("\n").at(-1) ?? "{}")
427
- expect(checked.code).toBe(1)
428
- expect(checked.json.status).toBe("fail")
429
- expect(codesOf(checked.json)).toEqual(["meaning_unverified", "meaning_unverified"])
430
- expect(checked.json.unverified.map((gap: { pair_id: string }) => gap.pair_id)).toEqual(["P1", "P2"])
431
- expect(summary).toMatchObject({ command: "fact meaning-check", defects: 2, dropped: 1, unverified: 2 })
432
- })
433
-
434
- test("#given an unreadable verdict file or a malformed pairs file #when meaning-check runs #then it exits 2", () => {
435
- const { dir, s } = pairedSession()
436
- const broken = join(dir, "broken.json")
437
- writeFileSync(broken, "{ not json")
438
- expect(nodeJson(["fact", "meaning-check", "--pairs", join(dir, "pairs.json"), "--verdicts", broken, ...s]).code).toBe(2)
439
- const ok = verdicts(dir, "cheap.json", "cheap", [])
440
- for (const body of ["{}", "null", '{ "pairs": "P1" }']) {
441
- writeFileSync(join(dir, "bad-pairs.json"), body)
442
- expect(nodeJson(["fact", "meaning-check", "--pairs", join(dir, "bad-pairs.json"), "--verdicts", ok, ...s]).code, body).toBe(2)
443
- }
444
- })
445
-
446
- test("#given a confirmed run then a dispute on the same inputs #when meaning-check reruns #then the log still holds one run with the dispute", () => {
447
- const { dir, s } = pairedSession()
448
- const drift = { pair_id: "P2", verdict: "drift", drift_kind: "hedge_dropped", fragment: "no earlier than 2027", source_fragment: "we estimate" }
449
- const files = ["--verdicts", verdicts(dir, "cheap.json", "cheap", [drift, { pair_id: "P1", verdict: "ok" }]), "--verdicts", verdicts(dir, "strong.json", "strong", [drift])]
450
- const first = nodeJson(["fact", "meaning-check", "--pairs", join(dir, "pairs.json"), ...files, ...s])
451
- const disputed = nodeJson(["fact", "meaning-check", "--pairs", join(dir, "pairs.json"), ...files, "--dispute", "P2", "--reason", "the operator states it", ...s])
452
- expect(disputed.code).toBe(1)
453
- expect(disputed.json.run_id).toBe(first.json.run_id)
454
- expect(disputed.json.promotion_eligibility).toMatchObject({ runs: 1, confirmed: 1, disputed_confirmed: 1, disputed_to_confirmed: 1 })
455
- expect(calibrationRows(dir)).toEqual([expect.objectContaining({ pair_id: "P2", disputed: true, reason: "the operator states it" })])
456
- const plain = nodeJson(["fact", "meaning-check", "--pairs", join(dir, "pairs.json"), ...files, ...s])
457
- expect(plain.json.promotion_eligibility).toMatchObject({ runs: 1, disputed_confirmed: 1 })
458
- })
459
-
460
- test("#given a run with no flagged pair #when meaning-check runs #then the run is still logged and counted", () => {
461
- const { dir, s } = pairedSession()
462
- const ok = (pair_id: string) => ({ pair_id, verdict: "ok" })
463
- const files = ["--verdicts", verdicts(dir, "cheap.json", "cheap", [ok("P1"), ok("P2")]), "--verdicts", verdicts(dir, "strong.json", "strong", [ok("P1"), ok("P2")])]
464
- const checked = nodeJson(["fact", "meaning-check", "--pairs", join(dir, "pairs.json"), ...files, ...s])
465
- expect(checked.code).toBe(0)
466
- expect(calibrationRows(dir)).toEqual([])
467
- expect(runLines(dir)).toEqual([expect.objectContaining({ run_id: checked.json.run_id, flagged: 0 })])
468
- expect(checked.json.promotion_eligibility.runs).toBe(1)
469
- })
470
-
471
- test("#given a corrupt calibration log #when meaning-check runs #then it exits 2 naming the file and line", () => {
472
- const { dir, s } = pairedSession()
473
- writeFileSync(join(dir, "fidelity-calibration.jsonl"), "{ not json\n")
474
- const checked = nodeJson(["fact", "meaning-check", "--pairs", join(dir, "pairs.json"), "--verdicts", verdicts(dir, "cheap.json", "cheap", []), ...s])
475
- expect(checked.code).toBe(2)
476
- expect(checked.stderr).toContain(`${join(dir, "fidelity-calibration.jsonl")}:1`)
477
- })
478
-
479
- test("#given a skipped pairs file #when meaning-check runs #then it is accepted only when the locked brief says off", () => {
480
- const { dir, s } = fidelitySession()
481
- const skip = join(dir, "skip.json")
482
- writeFileSync(skip, JSON.stringify({ schemaVersion: 1, skipped: true, pairs: [] }))
483
- const check = () => nodeJson(["fact", "meaning-check", "--pairs", skip, "--verdicts", verdicts(dir, "cheap.json", "cheap", []), ...s])
484
- const on = check()
485
- expect(on.code).toBe(1)
486
- expect(on.json.error).toBe("meaning_check_required")
487
- writeFileSync(join(dir, "brief.md"), "# Brief\n")
488
- expect(check().json.error).toBe("fidelity_interview_incomplete")
489
- expect(check().code).toBe(1)
490
- writeFileSync(join(dir, "brief.md"), readFileSync(join(FIXTURES, "fidelity-brief-locked.md"), "utf8").replace("meaning check: on", "meaning check: off"))
491
- const off = check()
492
- expect(off.code).toBe(0)
493
- expect(off.json.status).toBe("skipped")
494
- })
495
-
496
- test("#given a brief with meaning check off #when fact pairs runs #then it exits 0 and writes skipped", () => {
497
- const { dir, s } = fidelitySession()
498
- const brief = readFileSync(join(dir, "brief.md"), "utf8").replace("meaning check: on", "meaning check: off")
499
- writeFileSync(join(dir, "brief.md"), brief)
500
- nodeJson(["fact", "fill", join(dir, "report.src.html"), "--out", join(dir, "report.html"), ...s])
501
- const pairs = nodeJson(["fact", "pairs", join(dir, "report.html"), "--out", join(dir, "pairs.json"), ...s])
502
- expect(pairs.code).toBe(0)
503
- expect(JSON.parse(readFileSync(join(dir, "pairs.json"), "utf8"))).toMatchObject({ skipped: true })
504
- })
505
- })
506
-
507
- type PolishJson = { data: { out: string; applied: number; rejected: number; unlocated: number }; defects: { code: string; index: number; reason?: string }[] }
508
-
509
- /** A filled session whose first finding names an acquirer and a target around F1. */
510
- function polishSession() {
511
- const session = fidelitySession()
512
- const { dir, s } = session
513
- const source = readFileSync(join(dir, "report.src.html"), "utf8").replace("<p>The span length is", "<p>Acme acquired Bolt before the span length reached")
514
- writeFileSync(join(dir, "report.src.html"), source)
515
- nodeJson(["fact", "fill", join(dir, "report.src.html"), "--out", join(dir, "report.html"), ...s])
516
- return session
517
- }
518
-
519
- function polishApply(dir: string, edits: string) {
520
- const args = ["fact", "polish-apply", join(dir, "report.html"), "--edits", edits, "--ledger", join(dir, "fact-ledger.json"), "--out", join(dir, "report.polished.html"), "--session-dir", dir]
521
- const proc = nodeJson(args)
522
- return { ...proc, json: proc.json as unknown as PolishJson }
523
- }
524
-
525
- describe("polish wiring under node", () => {
526
- test("#given the fixture edits #when fact polish-apply runs #then it exits 0, applies the wording edit, and rejects the numeral edit", () => {
527
- const { dir } = polishSession()
528
- const applied = polishApply(dir, join(FIDELITY, "polish-edits.json"))
529
- const polished = readFileSync(join(dir, "report.polished.html"), "utf8")
530
- expect(applied.code).toBe(0)
531
- expect(applied.json.data).toMatchObject({ applied: 1, rejected: 1, unlocated: 0 })
532
- expect(applied.json.defects.map((d) => [d.code, d.index])).toEqual([["polish_edit_rejected", 1]])
533
- expect(polished).toContain("Collected from a single cited public page")
534
- expect(polished).toContain("retrieved 2026-10-01")
535
- expect(polished).not.toContain("2026-10-02")
536
- })
537
-
538
- test("#given an edit that adds an attribute #when polish-apply and polish-diff run #then apply refuses it as markup changed and diff exits 1", () => {
539
- const { dir } = polishSession()
540
- const html = readFileSync(join(dir, "report.html"), "utf8")
541
- const edits = join(dir, "attribute.json")
542
- writeFileSync(edits, JSON.stringify([{ before: "<h2>Findings</h2>", after: '<h2 id="findings">Findings</h2>', reason: "anchor" }]))
543
- const applied = polishApply(dir, edits)
544
- expect(applied.code).toBe(0)
545
- expect(applied.json.data).toMatchObject({ applied: 0, rejected: 1, unlocated: 0 })
546
- expect(applied.json.defects.map((d) => [d.code, d.index])).toEqual([["polish_edit_rejected", 0]])
547
- expect(applied.json.defects[0]?.reason).toContain("markup changed")
548
- expect(readFileSync(join(dir, "report.polished.html"), "utf8")).toBe(html)
549
- writeFileSync(join(dir, "attribute.html"), html.replace("<h2>Findings</h2>", '<h2 id="findings">Findings</h2>'))
550
- const diffed = nodeJson(["fact", "polish-diff", "--before", join(dir, "report.html"), "--after", join(dir, "attribute.html"), "--ledger", join(dir, "fact-ledger.json"), "--session-dir", dir])
551
- expect(diffed.code).toBe(1)
552
- expect(codesOf(diffed.json)).toContain("polish_diff_fail")
553
- expect(JSON.stringify(diffed.json)).toContain("markup changed")
554
- })
555
-
556
- test("#given an edit whose before holds & #when fact polish-apply runs #then it prints polish-apply's own markup reason verbatim for that edit", () => {
557
- const { dir } = polishSession()
558
- const before = "Acme & Bolt"
559
- const edits = join(dir, "ampersand.json")
560
- writeFileSync(edits, JSON.stringify([{ before, after: "Acme and Bolt", reason: "ampersand" }]))
561
- const applied = polishApply(dir, edits)
562
- const line = ` rejected edit 0 ("${before}"): ${POLISH_MARKUP_REASON}`
563
- expect(applied.code).toBe(0)
564
- expect(applied.json.data).toMatchObject({ applied: 0, rejected: 1, unlocated: 0 })
565
- expect(applied.json.defects.map((d) => [d.code, d.index, d.reason])).toEqual([["polish_edit_rejected", 0, POLISH_MARKUP_REASON]])
566
- expect(applied.stderr.split("\n")).toContain(line)
567
- })
568
-
569
- test("#given a polished copy edited by hand #when fact polish-diff runs #then it exits 1 with polish_diff_fail", () => {
570
- const { dir } = polishSession()
571
- const html = readFileSync(join(dir, "report.html"), "utf8")
572
- writeFileSync(join(dir, "hand.html"), html.replace("320 m", "321 m"))
573
- const diffed = nodeJson(["fact", "polish-diff", "--before", join(dir, "report.html"), "--after", join(dir, "hand.html"), "--ledger", join(dir, "fact-ledger.json"), "--session-dir", dir])
574
- expect(diffed.code).toBe(1)
575
- expect(codesOf(diffed.json)).toContain("polish_diff_fail")
576
- })
577
-
578
- test("#given an accepted subject and object swap #when fact pairs runs with --changed-since #then only that sentence's pair is emitted, with the new text", () => {
579
- const { dir, s } = polishSession()
580
- const edits = join(dir, "swap.json")
581
- writeFileSync(edits, JSON.stringify([{ before: "Acme acquired Bolt", after: "Bolt acquired Acme", reason: "word order" }]))
582
- const applied = polishApply(dir, edits)
583
- const map = join(dir, "report.html.fill-map.json")
584
- const changed = nodeJson(["fact", "pairs", join(dir, "report.polished.html"), "--changed-since", map, "--out", join(dir, "pairs-changed.json"), ...s])
585
- const written = JSON.parse(readFileSync(join(dir, "pairs-changed.json"), "utf8"))
586
- expect(applied.json.data).toMatchObject({ applied: 1, rejected: 0 })
587
- expect(changed.code).toBe(0)
588
- expect(written.pairs.map((p: Pair) => [p.kind, p.fact_ids])).toEqual([["quote", ["F1"]]])
589
- expect(written.pairs[0].sentence).toContain("Bolt acquired Acme")
590
- })
591
- })
592
-
593
- describe("markdown editions under node", () => {
594
- const MD = ["# Bridge", "", "## Findings", "", "The span length is {{F1:span_len}} [S4].", "", "The operator wrote {{Q1:completion}} [S4].", "", "## How this report was made", "", "Collected from one cited public page [S4].", "", "## Sources", "", "1. [S4] Bridge page, retrieved 2026-10-01.", ""].join("\n")
595
-
596
- function mdSession() {
597
- const session = fidelitySession()
598
- writeFileSync(join(session.dir, "report.src.md"), MD)
599
- const filled = nodeJson(["fact", "fill", join(session.dir, "report.src.md"), "--format", "md", "--out", join(session.dir, "report.md"), ...session.s])
600
- return { ...session, filled }
601
- }
602
-
603
- test("#given a markdown source #when filled and verified as md #then verify passes and records the md bytes", () => {
604
- const { dir, filled } = mdSession()
605
- const checked = verify(dir, "report.md", ["--format", "md"])
606
- expect(filled.code).toBe(0)
607
- expect(readFileSync(join(dir, "report.md"), "utf8")).toContain("The span length is 320 m [S4].")
608
- expect(checked.code).toBe(0)
609
- expect(checked.json.defects).toEqual([])
610
- expect(JSON.parse(readFileSync(join(dir, "fidelity-defects.json"), "utf8")).artifact_sha256).toBe(sha256(join(dir, "report.md")))
611
- })
612
-
613
- test("#given a filled markdown edition #when a value is hand-edited or a number typed #then md verify exits 1 with the code", () => {
614
- const { dir } = mdSession()
615
- const md = readFileSync(join(dir, "report.md"), "utf8")
616
- writeFileSync(join(dir, "typed.md"), md.replace("## How", "Revenue grew 12% [S4].\n\n## How"))
617
- copyFileSync(join(dir, "report.md.fill-map.json"), join(dir, "typed.md.fill-map.json"))
618
- const typed = verify(dir, "typed.md", ["--format", "md"])
619
- expect(typed.code).toBe(1)
620
- expect(codesOf(typed.json)).toContain("untraceable_number")
621
- })
622
-
623
- test("#given a filled markdown edition with an unterminated fence #when verified or paired #then both refuse with exit 2", () => {
624
- const { dir, s } = mdSession()
625
- writeFileSync(join(dir, "fence.md"), `${readFileSync(join(dir, "report.md"), "utf8")}\n\`\`\`\nunclosed code\n`)
626
- copyFileSync(join(dir, "report.md.fill-map.json"), join(dir, "fence.md.fill-map.json"))
627
- const checked = verify(dir, "fence.md", ["--format", "md"])
628
- expect(checked.code).toBe(2)
629
- expect(checked.stderr).toContain("unterminated_fence")
630
- expect(nodeJson(["fact", "pairs", join(dir, "fence.md"), "--out", join(dir, "pairs.json"), ...s]).code).toBe(2)
631
- })
632
-
633
- test("#given a filled markdown edition #when fact pairs runs on it #then it pairs the same quotes as the html edition", () => {
634
- const { dir, s } = mdSession()
635
- const pairs = nodeJson(["fact", "pairs", join(dir, "report.md"), "--out", join(dir, "pairs.json"), ...s])
636
- expect(pairs.code).toBe(0)
637
- expect(JSON.parse(readFileSync(join(dir, "pairs.json"), "utf8")).pairs.map((p: Pair) => p.fact_ids)).toEqual([["F1"], ["Q1"]])
638
- })
639
- })
640
-
641
- describe("repair decide with several defect files", () => {
642
- test("#given two --defects files #when decided #then the attempt signature carries both codes", async () => {
643
- const dir = sessionDir()
644
- const state = join(dir, "repair-state.json")
645
- await run(["repair", "decide", "--state", state, "--defects", defectsFile(dir, "a.json", ["heading_generic"]), "--defects", defectsFile(dir, "b.json", ["untraceable_number"])])
646
- const signature = JSON.parse(readFileSync(state, "utf8")).attempts[0].signature
647
- expect(signature).toContain("heading_generic")
648
- expect(signature).toContain("untraceable_number")
649
- })
650
- })
651
-
652
- describe("fidelity delivery barrier", () => {
653
- /** The locked brief has meaning check on, so a deliverable session also carries a clean meaning record. */
654
- const meaningRan = (dir: string) => writeFidelityRecord(join(dir, "fidelity-meaning-defects.json"), "meaning", { artifact_sha256: sha256(join(dir, "report.html")), report: join(dir, "report.html"), defects: [] })
655
-
656
- async function deliveredSession(codes: string[]) {
657
- const { dir, s } = fidelitySession()
658
- nodeJson(["fact", "fill", join(dir, "report.src.html"), "--out", join(dir, "report.html"), ...s])
659
- meaningRan(dir)
660
- const record = (list: string[]) => writeFidelityRecord(join(dir, "fidelity-defects.json"), "fidelity", { artifact_sha256: sha256(join(dir, "report.html")), report: join(dir, "report.html"), defects: list.map((code) => ({ code, message: code })) })
661
- record(codes)
662
- await run(["outcome", "init", "--session-dir", dir, "--promised", "html"])
663
- await run(["outcome", "set", "html", "delivered", "--path", join(dir, "report.html"), "--verified-by", join(dir, "fidelity-defects.json"), "--session-dir", dir])
664
- const decide = ["repair", "decide", "--state", join(dir, "repair-state.json"), "--defects", join(dir, "fidelity-defects.json"), "--artifact-bytes", "4096", "--renders", "1", "--session-dir", dir]
665
- await run(decide)
666
- const decision = await run(decide)
667
- return { dir, record, decision }
668
- }
669
-
670
- test("#given a clean meaning record #when the report is edited and fact verify re-run #then the stale meaning record refuses, and a fresh one passes", async () => {
671
- // given: meaning check on, readers clean, the edition verified and delivered
672
- const { dir, s } = pairedSession()
673
- const report = join(dir, "report.html")
674
- const clean = [{ pair_id: "P1", verdict: "ok" }, { pair_id: "P2", verdict: "ok" }]
675
- const readers = () => nodeJson(["fact", "meaning-check", "--pairs", join(dir, "pairs.json"), "--verdicts", verdicts(dir, "cheap.json", "cheap", clean), "--verdicts", verdicts(dir, "strong.json", "strong", clean), ...s])
676
- expect(readers().code).toBe(0)
677
- expect(verify(dir, "report.html").code).toBe(0)
678
- await run(["outcome", "init", "--session-dir", dir, "--promised", "html"])
679
- await run(["outcome", "set", "html", "delivered", "--path", report, "--verified-by", join(dir, "fidelity-defects.json"), "--session-dir", dir])
680
- const first = await run(["outcome", "verify", "--session-dir", dir])
681
- // when: draft 2 (a repair rewrite) is re-verified by fact verify only
682
- writeFileSync(report, `${readFileSync(report, "utf8")}\n<!-- draft 2 -->\n`)
683
- expect(verify(dir, "report.html").code).toBe(0)
684
- const stale = await run(["outcome", "verify", "--session-dir", dir])
685
- // when: the meaning readers run again on draft 2
686
- expect(nodeJson(["fact", "pairs", report, "--out", join(dir, "pairs.json"), ...s]).code).toBe(0)
687
- expect(readers().code).toBe(0)
688
- const fresh = await run(["outcome", "verify", "--session-dir", dir])
689
- // then
690
- expect(first.exitCode).toBe(0)
691
- expect(stale.exitCode).toBe(1)
692
- expect(stale.json.problems).toContainEqual(expect.objectContaining({ code: "edition_unverified", path: join(dir, "fidelity-meaning-defects.json"), message: expect.stringContaining("meaning record") }))
693
- expect(fresh.exitCode).toBe(0)
694
- })
695
-
696
- test("#given an unregistered appendix value #when verified #then untraceable_number and outcome verify refuses", async () => {
697
- const { dir, s } = fidelitySession()
698
- nodeJson(["fact", "fill", join(dir, "report.src.html"), "--out", join(dir, "report.html"), ...s])
699
- const html = readFileSync(join(dir, "report.html"), "utf8").replace("<h2>How", "<h2>Appendix</h2>\n<p>Revenue grew 12% [S4].</p>\n<h2>How")
700
- writeFileSync(join(dir, "report.html"), html)
701
- const checked = verify(dir, "report.html")
702
- await run(["outcome", "init", "--session-dir", dir, "--promised", "html"])
703
- await run(["outcome", "set", "html", "delivered", "--path", join(dir, "report.html"), "--verified-by", join(dir, "fidelity-defects.json"), "--session-dir", dir])
704
- const outcome = await run(["outcome", "verify", "--session-dir", dir])
705
- expect(codesOf(checked.json)).toContain("untraceable_number")
706
- expect(outcome.exitCode).toBe(1)
707
- expect(outcome.json.problems.map((p: { code: string }) => p.code)).toContain("fidelity_unresolved")
708
- })
709
-
710
- for (const code of ["untraceable_number", "unbound_sentence"]) {
711
- test(`#given ${code} left after repair delivers on exhaustion #when verified and finished #then both refuse, and an empty record passes`, async () => {
712
- const { dir, record, decision } = await deliveredSession([code])
713
- const refused = await run(["outcome", "verify", "--session-dir", dir])
714
- const finish = await run(["outcome", "finish", "--session-dir", dir])
715
- record([])
716
- const passed = await run(["outcome", "verify", "--session-dir", dir])
717
- const finished = await run(["outcome", "finish", "--session-dir", dir])
718
- expect(decision.json.decision.action).toBe("deliver")
719
- expect(refused.exitCode).toBe(1)
720
- expect(JSON.stringify(refused.json.problems)).toContain(`fidelity_unresolved: ${code}`)
721
- expect(finish.exitCode).toBe(1)
722
- expect(passed.exitCode).toBe(0)
723
- expect(finished.exitCode).toBe(0)
724
- })
725
- }
726
-
727
- test("#given a clean main report and an inline edition #when the edition lacks or mismatches its verify record #then edition_unverified, and its own record passes", async () => {
728
- // given
729
- const { dir, s } = fidelitySession()
730
- nodeJson(["fact", "fill", join(dir, "report.src.html"), "--out", join(dir, "report.html"), ...s])
731
- expect(verify(dir, "report.html").code).toBe(0)
732
- meaningRan(dir)
733
- expect(nodeJson(["fact", "fill", join(dir, "report.src.html"), "--inline", "--out", join(dir, "report.inline.html"), ...s]).code).toBe(0)
734
- await run(["outcome", "init", "--session-dir", dir, "--promised", "html,pdf"])
735
- await run(["outcome", "set", "html", "delivered", "--path", join(dir, "report.html"), "--verified-by", join(dir, "fidelity-defects.json"), "--session-dir", dir])
736
- const problems = async () => {
737
- const result = await run(["outcome", "verify", "--session-dir", dir])
738
- return { exitCode: result.exitCode, codes: result.json.problems.map((p: { code: string }) => p.code) }
739
- }
740
- // when: no --verified-by
741
- await run(["outcome", "set", "pdf", "delivered", "--path", join(dir, "report.inline.html"), "--session-dir", dir])
742
- const missing = await problems()
743
- // when: a record for different bytes
744
- expect(verify(dir, "report.inline.html", ["--out", join(dir, "fidelity-inline.json")]).code).toBe(0)
745
- const inline = readFileSync(join(dir, "report.inline.html"), "utf8")
746
- writeFileSync(join(dir, "report.inline.html"), inline.replace("320,000 mm", "330,000 mm"))
747
- await run(["outcome", "set", "pdf", "delivered", "--path", join(dir, "report.inline.html"), "--verified-by", join(dir, "fidelity-inline.json"), "--session-dir", dir])
748
- const altered = await problems()
749
- // when: restored to the verified bytes
750
- writeFileSync(join(dir, "report.inline.html"), inline)
751
- const matched = await problems()
752
- // then
753
- expect(missing).toEqual({ exitCode: 1, codes: ["edition_unverified"] })
754
- expect(altered).toEqual({ exitCode: 1, codes: ["edition_unverified"] })
755
- expect(matched).toEqual({ exitCode: 0, codes: [] })
756
- })
757
- })
758
-
759
- /** Rewrite one `## Fidelity` field line of the session's brief.md. */
760
- function setField(dir: string, field: string, value: string, answeredBy = "user") {
761
- const path = join(dir, "brief.md")
762
- const line = new RegExp(`^${field}: .*$`, "mu")
763
- writeFileSync(path, readFileSync(path, "utf8").replace(line, `${field}: ${value} (answered_by: ${answeredBy})`))
764
- }
765
-
766
- function fillTemplate(dir: string, body: string, extra: string[] = []) {
767
- const source = ['<!doctype html><html lang="en"><head><meta charset="utf-8"><title>T</title></head><body><h1>T</h1>', "<h2>Findings</h2>", body, "</body></html>"].join("\n")
768
- writeFileSync(join(dir, "t.src.html"), source)
769
- return nodeJson(["fact", "fill", join(dir, "t.src.html"), "--out", join(dir, "t.html"), "--session-dir", dir, ...extra])
770
- }
771
-
772
- /** Snapshot a one-line text source under `id` with the given provenance (omitted: unknown) and register its span row. */
773
- function textSource(dir: string, id: string, provenance?: string) {
774
- writeFileSync(join(dir, `${id}.txt`), "Commentary: the span length is 320 m according to this review.\n")
775
- const prov = provenance === undefined ? [] : ["--provenance", provenance]
776
- const snap = nodeJson(["source", "snapshot", "--id", id, "--text", join(dir, `${id}.txt`), "--kind", "document", ...prov, "--session-dir", dir])
777
- const add = nodeJson(["fact", "add", "--source", id, "--find", "span length", "--label", `span_${id.toLowerCase()}`, "--json", "--session-dir", dir])
778
- return { snap, row: add.json.data?.row }
779
- }
780
-
781
- const ledgerRow = (dir: string, id: string) => JSON.parse(readFileSync(join(dir, "fact-ledger.json"), "utf8")).rows.find((r: { fact_id: string }) => r.fact_id === id)
782
-
783
- describe("fidelity brief lock and policy under node", () => {
784
- test("#given a filled session whose brief turns pending #when each reader-facing command runs #then all six exit 1 with only fidelity_interview_incomplete and write nothing", () => {
785
- // given
786
- const { dir, s } = pairedSession()
787
- copyFileSync(join(FIXTURES, "fidelity-brief-pending.md"), join(dir, "brief.md"))
788
- for (const name of ["fidelity-defects.json", "pairs.json"]) rmSync(join(dir, name), { force: true })
789
- const drift = verdicts(dir, "cheap.json", "cheap", [])
790
- writeFileSync(join(dir, "edits.json"), "[]")
791
- // when
792
- const runs = {
793
- fill: nodeJson(["fact", "fill", join(dir, "report.src.html"), "--out", join(dir, "again.html"), ...s]),
794
- verify: verify(dir, "report.html"),
795
- pairs: nodeJson(["fact", "pairs", join(dir, "report.html"), "--out", join(dir, "pairs.json"), ...s]),
796
- meaning: nodeJson(["fact", "meaning-check", "--pairs", join(dir, "absent-pairs.json"), "--verdicts", drift, ...s]),
797
- polishApply: nodeJson(["fact", "polish-apply", join(dir, "report.html"), "--edits", join(dir, "edits.json"), "--out", join(dir, "polished.html"), ...s]),
798
- polishDiff: nodeJson(["fact", "polish-diff", "--before", join(dir, "report.html"), "--after", join(dir, "report.html"), "--out", join(dir, "polish.json"), ...s]),
799
- }
800
- // then
801
- for (const [name, result] of Object.entries(runs)) {
802
- expect({ name, code: result.code, codes: codesOf(result.json) }).toEqual({ name, code: 1, codes: ["fidelity_interview_incomplete"] })
803
- }
804
- for (const name of ["again.html", "fidelity-defects.json", "pairs.json", "polished.html", "polish.json", "fidelity-meaning-defects.json"]) expect({ name, exists: existsSync(join(dir, name)) }).toEqual({ name, exists: false })
805
- })
806
-
807
- test("#given derived numbers none #when a derived or a converted placeholder is filled #then derived_not_allowed, and allowed renders both", () => {
808
- // given
809
- const { dir, s, mm } = fidelitySession()
810
- const expr = nodeJson(["fact", "derive", "--op", "expr", "F1 * 2", "--unit", "m", "--rationale", "two spans", "--label", "two_spans", "--json", ...s])
811
- const twice = expr.json.data.row.fact_id
812
- setField(dir, "derived numbers", "none")
813
- // when
814
- const derived = fillTemplate(dir, `<p>Two spans are {{${twice}:two_spans}} [S4].</p>`)
815
- const converted = fillTemplate(dir, `<p>The span is {{${mm}:span_mm}} [S4].</p>`)
816
- setField(dir, "derived numbers", "allowed")
817
- const allowed = fillTemplate(dir, `<p>Two spans are {{${twice}:two_spans}} [S4]. The span is {{${mm}:span_mm}} [S4].</p>`)
818
- // then
819
- expect(expr.code).toBe(0)
820
- expect([derived.code, codesOf(derived.json)]).toEqual([1, ["derived_not_allowed"]])
821
- expect([converted.code, codesOf(converted.json)]).toEqual([1, ["derived_not_allowed"]])
822
- expect(allowed.code).toBe(0)
823
- expect(readFileSync(join(dir, "t.html"), "utf8")).toContain("320,000 mm")
824
- })
825
-
826
- test("#given source floor primary only #when rows from secondary, unknown, and primary sources are filled #then only the primary row renders", () => {
827
- // given
828
- const { dir } = fidelitySession()
829
- const secondary = textSource(dir, "S7", "secondary")
830
- const unknown = textSource(dir, "S8")
831
- const primary = textSource(dir, "S6", "primary")
832
- setField(dir, "source floor", "primary only")
833
- // when
834
- const fills = [secondary, unknown, primary].map(({ row }) => fillTemplate(dir, `<p>The span is {{${row.fact_id}:${row.label}}} [S4].</p>`))
835
- // then
836
- expect([secondary.snap.json, unknown.snap.json, primary.snap.json].map((json) => (json as unknown as { data: { provenance: string } }).data.provenance)).toEqual(["secondary", "unknown", "primary"])
837
- expect(fills.map((f) => [f.code, codesOf(f.json)])).toEqual([
838
- [1, ["source_outside_floor"]],
839
- [1, ["source_outside_floor"]],
840
- [0, []],
841
- ])
842
- })
843
-
844
- test("#given a row registered while source floor is pending #when the floor locks to pointed only S4, then standard #then fill refuses, then renders, and outside_floor caches each result", () => {
845
- // given
846
- const { dir } = fidelitySession()
847
- setField(dir, "source floor", "pending", "pending")
848
- const { snap, row } = textSource(dir, "S9")
849
- const body = `<p>The span is {{${row.fact_id}:${row.label}}} [S9].</p>`
850
- // when
851
- setField(dir, "source floor", "pointed only: S4")
852
- const refused = fillTemplate(dir, body)
853
- const cachedRefused = ledgerRow(dir, row.fact_id).outside_floor
854
- setField(dir, "source floor", "standard")
855
- const rendered = fillTemplate(dir, body)
856
- // then
857
- expect(snap.code).toBe(0)
858
- expect(row.outside_floor).toBe(false)
859
- expect([refused.code, codesOf(refused.json)]).toEqual([1, ["source_outside_floor"]])
860
- expect(cachedRefused).toBe(true)
861
- expect(rendered.code).toBe(0)
862
- expect(ledgerRow(dir, row.fact_id).outside_floor).toBe(false)
863
- })
864
-
865
- test("#given a markdown translation comment after a quote #when languages is translation beside, then quotes original #then fact pairs emits a translation pair only for the first", () => {
866
- // given
867
- const { dir, s } = fidelitySession()
868
- const md = ["# T", "", "## Findings", "", "The operator wrote {{Q1:completion}} <!-- translates: Q1 --> completion comes in 2027 at the earliest [S4].", ""].join("\n")
869
- writeFileSync(join(dir, "t.src.md"), md)
870
- const kinds = () => {
871
- const filled = nodeJson(["fact", "fill", join(dir, "t.src.md"), "--format", "md", "--out", join(dir, "t.md"), ...s])
872
- const pairs = nodeJson(["fact", "pairs", join(dir, "t.md"), "--out", join(dir, "pairs.json"), ...s])
873
- return { fill: filled.code, pairs: pairs.code, kinds: JSON.parse(readFileSync(join(dir, "pairs.json"), "utf8")).pairs.map((p: Pair) => p.kind) }
874
- }
875
- // when
876
- const beside = kinds()
877
- setField(dir, "languages", "report en; sources en; quotes original")
878
- const original = kinds()
879
- // then
880
- expect(beside).toEqual({ fill: 0, pairs: 0, kinds: ["quote", "translation"] })
881
- expect(original).toEqual({ fill: 0, pairs: 0, kinds: ["quote"] })
882
- })
883
-
884
- test("#given a sentence that failed twice #when filled with --replace-failed #then block-quote replaces it and fallback omit removes it", () => {
885
- // given
886
- const { dir } = fidelitySession()
887
- const state = { schemaVersion: 1, failures: { "findings#F1": 2 }, replaced: [] }
888
- const body = "<p>The span length is {{F1:span_len}} [S4]. The operator wrote {{Q1:completion}} [S4].</p>"
889
- // when
890
- writeFileSync(join(dir, "fidelity-state.json"), JSON.stringify(state))
891
- const quoted = fillTemplate(dir, body, ["--replace-failed"])
892
- const quotedHtml = readFileSync(join(dir, "t.html"), "utf8")
893
- setField(dir, "fallback", "omit")
894
- writeFileSync(join(dir, "fidelity-state.json"), JSON.stringify(state))
895
- const omitted = fillTemplate(dir, body, ["--replace-failed"])
896
- const omittedHtml = readFileSync(join(dir, "t.html"), "utf8")
897
- // then
898
- expect(quoted.code).toBe(0)
899
- expect(quotedHtml).toContain("<blockquote")
900
- expect(omitted.code).toBe(0)
901
- expect(omittedHtml).not.toContain("<blockquote")
902
- expect(omittedHtml).not.toContain("The span length is")
903
- expect(omittedHtml).toContain("The operator wrote")
904
- })
905
-
906
- test("#given --calibration #when meaning-check flags a pair #then the calibration line goes to that path, not the session default", () => {
907
- const { dir, s } = pairedSession()
908
- const drift = { pair_id: "P2", verdict: "drift", drift_kind: "hedge_dropped", fragment: "no earlier than 2027", source_fragment: "we estimate" }
909
- const log = join(dir, "custom-calibration.jsonl")
910
- const checked = nodeJson(["fact", "meaning-check", "--pairs", join(dir, "pairs.json"), "--verdicts", verdicts(dir, "cheap.json", "cheap", [drift, { pair_id: "P1", verdict: "ok" }]), "--calibration", log, ...s])
911
- const rows = readFileSync(log, "utf8").trim().split("\n").map((line) => JSON.parse(line)).filter((row) => row.header !== true && row.run !== true)
912
- expect(checked.code).toBe(0)
913
- expect(rows).toHaveLength(1)
914
- expect(existsSync(join(dir, "fidelity-calibration.jsonl"))).toBe(false)
915
- })
916
- })
917
-
918
- describe("fidelity brief values, sourceless rows, and the brief binding under node", () => {
919
- test("#given derived numbers or fallback off its table #when fact fill runs #then a usage error names the field and value and nothing is written", () => {
920
- // given
921
- const { dir, s } = fidelitySession()
922
- const twice = nodeJson(["fact", "derive", "--op", "expr", "F1 * 2", "--unit", "m", "--rationale", "two spans", "--label", "two_spans", "--json", ...s]).json.data.row.fact_id
923
- // when
924
- setField(dir, "derived numbers", "nope")
925
- const derived = fillTemplate(dir, `<p>Two spans are {{${twice}:two_spans}} [S4].</p>`)
926
- setField(dir, "derived numbers", "allowed")
927
- setField(dir, "fallback", "drop")
928
- const fallback = fillTemplate(dir, "<p>The span length is {{F1:span_len}} [S4].</p>")
929
- // then
930
- expect({ code: derived.code, named: derived.stderr.includes('derived numbers "nope"') }).toEqual({ code: 2, named: true })
931
- expect({ code: fallback.code, named: fallback.stderr.includes('fallback "drop"') }).toEqual({ code: 2, named: true })
932
- expect(existsSync(join(dir, "t.html"))).toBe(false)
933
- })
934
-
935
- test("#given an assumed row #when filled under primary only, then standard #then the sourceless row is source_outside_floor, then renders", () => {
936
- // given
937
- const { dir, s } = fidelitySession()
938
- const assumed = nodeJson(["fact", "add", "--assumed", "12", "--unit", "%", "--rationale", "planning share chosen by the requester", "--label", "share_assumed", "--json", ...s]).json.data.row.fact_id
939
- const body = `<p>The planning share is {{${assumed}:share_assumed}}.</p>`
940
- // when
941
- setField(dir, "source floor", "primary only")
942
- const refused = fillTemplate(dir, body)
943
- setField(dir, "source floor", "standard")
944
- const rendered = fillTemplate(dir, body)
945
- // then
946
- expect([refused.code, codesOf(refused.json)]).toEqual([1, ["source_outside_floor"]])
947
- expect(JSON.stringify(refused.json)).toContain("no source")
948
- expect(rendered.code).toBe(0)
949
- })
950
-
951
- test("#given a derived row whose operand rests on a secondary source #when filled under primary only #then source_outside_floor names that source", () => {
952
- // given
953
- const { dir, s } = fidelitySession()
954
- const { row } = textSource(dir, "S7", "secondary")
955
- const twice = nodeJson(["fact", "derive", "--op", "expr", `${row.fact_id} * 2`, "--unit", "m", "--rationale", "two spans", "--label", "two_spans", "--json", ...s]).json.data.row.fact_id
956
- setField(dir, "source floor", "primary only")
957
- // when
958
- const refused = fillTemplate(dir, `<p>Two spans are {{${twice}:two_spans}} [S7].</p>`)
959
- // then
960
- expect([refused.code, codesOf(refused.json)]).toEqual([1, ["source_outside_floor"]])
961
- expect(JSON.stringify(refused.json)).toContain("rests on S7")
962
- })
963
-
964
- test("#given an edition filled under standard from a secondary source #when the brief tightens to primary only #then fact verify refuses with and without its fill map", () => {
965
- // given
966
- const { dir, s } = fidelitySession()
967
- const { row } = textSource(dir, "S7", "secondary")
968
- const src = readFileSync(join(dir, "report.src.html"), "utf8")
969
- .replace("<h2>How", `<p>A review gives the span as {{${row.fact_id}:${row.label}}} [S7].</p>\n<h2>How`)
970
- .replace("</ol>", '<li id="s7">[S7] Bridge review, retrieved 2026-10-01.</li></ol>')
971
- writeFileSync(join(dir, "report.src.html"), src)
972
- const filled = nodeJson(["fact", "fill", join(dir, "report.src.html"), "--out", join(dir, "report.html"), ...s])
973
- const map = join(dir, "report.html.fill-map.json")
974
- const before = verify(dir, "report.html")
975
- // when
976
- setField(dir, "source floor", "primary only")
977
- const bound = verify(dir, "report.html")
978
- const record = JSON.parse(readFileSync(join(dir, "fidelity-defects.json"), "utf8"))
979
- const kept = readFileSync(map, "utf8")
980
- rmSync(map)
981
- const unbound = verify(dir, "report.html")
982
- setField(dir, "source floor", "standard")
983
- const { brief_sha256: _dropped, ...legacy } = JSON.parse(kept)
984
- writeFileSync(map, JSON.stringify(legacy))
985
- const unstamped = verify(dir, "report.html")
986
- // then
987
- expect([filled.code, before.code, codesOf(before.json)]).toEqual([0, 0, []])
988
- expect(typeof JSON.parse(kept).brief_sha256).toBe("string")
989
- expect(bound.code).toBe(1)
990
- expect(codesOf(bound.json)).toContain("brief_changed")
991
- expect(codesOf(bound.json)).toContain("source_outside_floor")
992
- expect(record.status).toBe("fail")
993
- expect(unbound.code).toBe(1)
994
- expect(codesOf(unbound.json)).toContain("source_outside_floor")
995
- expect(codesOf(unbound.json)).not.toContain("brief_changed")
996
- expect([unstamped.code, codesOf(unstamped.json)]).toEqual([1, ["brief_changed"]])
997
- })
998
- })