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
@@ -1,5 +1,4 @@
1
1
  import { describe, expect, test } from "bun:test"
2
-
3
2
  import {
4
3
  ANSWERED_BY,
5
4
  DEFECT_CODES,
@@ -22,120 +21,14 @@ import {
22
21
  validateOutcomeManifest,
23
22
  validateRepairState,
24
23
  } from "./contracts.mjs"
24
+ import { SEVERITIES, STATIC_CODES_EXPECTED, LAYOUT_CODES_EXPECTED, FIDELITY_BLOCKERS, FIDELITY_MAJORS, FIDELITY_MINORS, FIDELITY_CODES_EXPECTED, fourGateManifest, sampleManifest } from "./tests/contracts-support"
25
25
 
26
- const SEVERITIES = new Set(["blocker", "major", "minor"])
27
- const STATIC_CODES_EXPECTED = [
28
- "korean_no_keep_all",
29
- "palette_off_token",
30
- "emoji_in_prose",
31
- "em_dash_in_prose",
32
- "en_dash_in_prose",
33
- "heading_too_long",
34
- "heading_generic",
35
- "unsourced_number",
36
- "chart_without_figure",
37
- "chart_svg_no_text",
38
- "chart_svg_missing_labels",
39
- "report_without_visual",
40
- "image_missing_alt",
41
- "korean_serif_font",
42
- "section_without_citation",
43
- "missing_closing_section",
44
- "missing_citation_section",
45
- "broken_asset_reference",
46
- "missing_required_section",
47
- ]
48
- const LAYOUT_CODES_EXPECTED = [
49
- "layout_overflow",
50
- "layout_text_clipped",
51
- "layout_scroll_container",
52
- "layout_image_distorted",
53
- "layout_sibling_overlap",
54
- ]
55
- // Fidelity vocabulary contract (#11009): severity and integrity per code are fixed here.
56
- const FIDELITY_BLOCKERS = [
57
- "unresolved_placeholder",
58
- "value_mismatch",
59
- "unit_mismatch",
60
- "quote_not_verbatim",
61
- "claim_status_invalid",
62
- "incompatible_operands",
63
- "pointer_label_mismatch",
64
- "quote_anchor_unresolved",
65
- "unit_duplicated",
66
- "polish_diff_fail",
67
- "fidelity_interview_incomplete",
68
- "derived_not_allowed",
69
- "source_outside_floor",
70
- "brief_changed",
71
- "snapshot_hash_mismatch",
72
- "chart_value_untraceable",
73
- "quote_unbound",
74
- "edition_unverified",
75
- ]
76
- const FIDELITY_MAJORS = [
77
- "untraceable_number",
78
- "unsupported_verification",
79
- "claim_kind_mismatch",
80
- "conflicting_readings",
81
- "hedge_dropped",
82
- "direction_flipped",
83
- "causality_added",
84
- "qualifier_dropped",
85
- "unbound_sentence",
86
- "meaning_drift_confirmed",
87
- "meaning_unverified",
88
- "polish_edit_rejected",
89
- "polish_hedging_decreased",
90
- "polish_text_churn",
91
- "snapshot_mismatch",
92
- "join_integrity_fail",
93
- "quote_stale_version",
94
- ]
95
- const FIDELITY_MINORS = [
96
- "quote_fuzzy_match",
97
- "name_outside_quote",
98
- "verification_claim_unmarked",
99
- "scope_widened_warn",
100
- "particle_unverified",
101
- "fallback_without_entity",
102
- "meaning_drift_warn",
103
- "polish_edit_unlocated",
104
- "polish_link_changed",
105
- "polish_name_changed",
106
- ]
107
- const FIDELITY_CODES_EXPECTED = [...FIDELITY_BLOCKERS, ...FIDELITY_MAJORS, ...FIDELITY_MINORS]
108
-
109
- function fourGateManifest() {
110
- const manifest = sampleManifest()
111
- const { fidelity: _dropped, ...gates } = manifest.gates
112
- return { ...manifest, gates }
113
- }
114
26
 
115
- function sampleManifest() {
116
- return {
117
- schemaVersion: SCHEMA_VERSION,
118
- sessionDir: "/tmp/session/20260101-120000",
119
- startedAt: "2026-01-01T12:00:00.000Z",
120
- finishedAt: null,
121
- elapsedMinutes: null,
122
- lane: "no-format",
123
- interview: null,
124
- deliverables: [
125
- { format: "pdf", status: "pending", promised: true, updatedAt: "2026-01-01T12:00:00.000Z" },
126
- ],
127
- renders: [],
128
- gates: { static: "not_run", fidelity: "not_run", layout: "not_run", visual: "not_run", proofread: "not_run" },
129
- residualDefects: [],
130
- repair: null,
131
- sources: null,
132
- }
133
- }
134
27
 
135
28
  describe("DEFECT_CODES", () => {
136
29
  test("#given the code table #when every code is inspected #then it has a severity, a hint, and an integrity flag", () => {
137
30
  expect(Object.keys(DEFECT_CODES).sort()).toEqual(
138
- [...STATIC_CODES_EXPECTED, "missing_promised_deliverable", ...LAYOUT_CODES_EXPECTED, ...FIDELITY_CODES_EXPECTED].sort(),
31
+ [...STATIC_CODES_EXPECTED, "missing_promised_deliverable", ...LAYOUT_CODES_EXPECTED, ...FIDELITY_CODES_EXPECTED, "conversion_altered_number", "unit_unbound"].sort(),
139
32
  )
140
33
  for (const [code, entry] of Object.entries(DEFECT_CODES)) {
141
34
  expect(SEVERITIES.has(entry.severity), `${code} severity`).toBe(true)
@@ -154,6 +47,7 @@ describe("DEFECT_CODES", () => {
154
47
  "missing_promised_deliverable",
155
48
  "missing_required_section",
156
49
  ...FIDELITY_BLOCKERS,
50
+ "conversion_altered_number",
157
51
  ].sort(),
158
52
  )
159
53
  for (const code of INTEGRITY_CODES) expect(DEFECT_CODES[code].severity).toBe("blocker")
@@ -174,8 +68,10 @@ describe("DEFECT_CODES", () => {
174
68
 
175
69
  test("#given the fidelity codes #when inspected #then each carries its fixed severity and integrity flag", () => {
176
70
  for (const code of FIDELITY_BLOCKERS) expect([code, DEFECT_CODES[code]?.severity, DEFECT_CODES[code]?.integrity]).toEqual([code, "blocker", true])
71
+ expect([DEFECT_CODES.conversion_altered_number?.severity, DEFECT_CODES.conversion_altered_number?.integrity]).toEqual(["blocker", true])
177
72
  for (const code of FIDELITY_MAJORS) expect([code, DEFECT_CODES[code]?.severity, DEFECT_CODES[code]?.integrity]).toEqual([code, "major", false])
178
73
  for (const code of FIDELITY_MINORS) expect([code, DEFECT_CODES[code]?.severity, DEFECT_CODES[code]?.integrity]).toEqual([code, "minor", false])
74
+ expect([DEFECT_CODES.unit_unbound?.severity, DEFECT_CODES.unit_unbound?.integrity]).toEqual(["minor", false])
179
75
  expect(DEFECT_CODES).not.toHaveProperty("incompatible_populations")
180
76
  })
181
77
  })
@@ -185,7 +81,7 @@ describe("code groups", () => {
185
81
  expect([...STATIC_CODES].sort()).toEqual(STATIC_CODES_EXPECTED.slice().sort())
186
82
  expect([...LAYOUT_CODES].sort()).toEqual(LAYOUT_CODES_EXPECTED.slice().sort())
187
83
  expect([...MANIFEST_CODES]).toEqual(["missing_promised_deliverable"])
188
- expect([...FIDELITY_CODES].sort()).toEqual(FIDELITY_CODES_EXPECTED.slice().sort())
84
+ expect([...FIDELITY_CODES].sort()).toEqual([...FIDELITY_CODES_EXPECTED, "conversion_altered_number", "unit_unbound"].sort())
189
85
  const union = [...STATIC_CODES, ...LAYOUT_CODES, ...MANIFEST_CODES, ...FIDELITY_CODES].sort()
190
86
  expect(union).toEqual(Object.keys(DEFECT_CODES).sort())
191
87
  expect(new Set(union).size).toBe(union.length)
@@ -0,0 +1,154 @@
1
+ import { describe, expect, onTestFinished, test } from "bun:test"
2
+ import { copyFileSync, existsSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs"
3
+ import { tmpdir } from "node:os"
4
+ import { join } from "node:path"
5
+
6
+ import { CliError } from "./cli-support.mjs"
7
+ import { DEFECT_CODES } from "./contracts.mjs"
8
+ import { run } from "./report-tools.mjs"
9
+ import { writeSnapshot } from "./source-snapshot.mjs"
10
+
11
+ type Json = Record<string, unknown> & { data?: Record<string, unknown>; error?: string }
12
+ const PAGE = "<p>USD 4.2<sup>2</sup> billion in revenue.</p><p>Staff grew to 1,250 people.</p>"
13
+
14
+ function session() {
15
+ const dir = mkdtempSync(join(tmpdir(), "conversion-altered-"))
16
+ onTestFinished(() => rmSync(dir, { recursive: true, force: true }))
17
+ return dir
18
+ }
19
+
20
+ async function snapshot(dir: string, html = PAGE) {
21
+ const file = join(dir, "page.html")
22
+ writeFileSync(file, html)
23
+ return (await run(["source", "snapshot", "--id", "S1", "--html", file, "--session-dir", dir])) as { exitCode: number; json: Json; summary: string }
24
+ }
25
+
26
+ describe("the conversion probe result is consumed", () => {
27
+ test("#given a page whose conversion altered a number #when snapshotted #then the JSON and the summary report it", async () => {
28
+ const result = await snapshot(session())
29
+ expect(result.exitCode).toBe(0)
30
+ expect(result.json.data?.conversion_altered_numbers).toEqual(["4.22"])
31
+ expect(result.summary).toContain("1 altered")
32
+ })
33
+
34
+ test("#given an altered 4.22 in conversion.json #when fact add --find locates it #then it refuses with conversion_altered_number and writes no row", async () => {
35
+ const dir = session()
36
+ await snapshot(dir)
37
+ const result = (await run(["fact", "add", "--source", "S1", "--find", "USD", "--label", "usd_bn", "--session-dir", dir, "--json"])) as { exitCode: number; json: Json }
38
+ expect(result.exitCode).toBe(1)
39
+ expect(result.json.error).toBe("conversion_altered_number")
40
+ expect(existsSync(join(dir, "fact-ledger.json"))).toBe(false)
41
+ expect(DEFECT_CODES.conversion_altered_number).toMatchObject({ severity: "blocker", integrity: true })
42
+ })
43
+
44
+ test("#given an unaltered number in the same snapshot #when fact add --find locates it #then it registers", async () => {
45
+ const dir = session()
46
+ await snapshot(dir)
47
+ const result = (await run(["fact", "add", "--source", "S1", "--find", "Staff grew to", "--label", "staff", "--session-dir", dir, "--json"])) as { exitCode: number; json: Json }
48
+ expect(result.exitCode).toBe(0)
49
+ expect((result.json.data?.row as { value: string }).value).toBe("1250")
50
+ })
51
+
52
+ test("#given a non-ISO observed_at #when writeSnapshot runs #then it throws before writing anything", () => {
53
+ const dir = join(session(), "sources")
54
+ expect(() => writeSnapshot(dir, "S1", { path: "p.html", html: PAGE, extractor: "model", observed_at: "yesterday" })).toThrow(TypeError)
55
+ expect(existsSync(join(dir, "S1"))).toBe(false)
56
+ })
57
+
58
+ test("#given an image named chart-109.png beside a glued 10<sup>9</sup> #when fact add --find locates it #then it refuses and registers nothing", async () => {
59
+ const dir = session()
60
+ const snap = await snapshot(dir, '<p>Output reached 10<sup>9</sup> kWh</p><p><img src="chart-109.png"></p>')
61
+ expect(snap.json.data?.conversion_altered_numbers).toEqual(["109"])
62
+ const result = (await run(["fact", "add", "--source", "S1", "--find", "Output reached", "--label", "output", "--session-dir", dir, "--json"])) as { exitCode: number; json: Json }
63
+ expect(result.exitCode).toBe(1)
64
+ expect(result.json.error).toBe("conversion_altered_number")
65
+ expect(existsSync(join(dir, "fact-ledger.json"))).toBe(false)
66
+ })
67
+ })
68
+
69
+ test("#given a genuine 4.22 beside a glued 4.2<sup>2</sup> #when fact add --find locates the genuine one #then the refusal names the --replace recovery", async () => {
70
+ const dir = session()
71
+ await snapshot(dir, "<p>USD 4.2<sup>2</sup> billion in revenue.</p><p>Margin was 4.22 points.</p>")
72
+ const result = (await run(["fact", "add", "--source", "S1", "--find", "Margin was", "--label", "margin", "--session-dir", dir, "--json"])) as { exitCode: number; json: Json & { message?: string } }
73
+ expect(result.exitCode).toBe(1)
74
+ expect(result.json.error).toBe("conversion_altered_number")
75
+ expect(result.json.message).toContain("source snapshot --id S1 --text <verified.txt> --replace")
76
+ })
77
+
78
+ describe("the documented recovery: source snapshot --replace with a verified text", () => {
79
+ const s = (dir: string) => ["--session-dir", dir]
80
+
81
+ test("#given a refused 4.22 #when the verified text replaces S1 #then fact add registers 4.2 billion and the filled report verifies clean", async () => {
82
+ const dir = session()
83
+ await snapshot(dir)
84
+ const verified = join(dir, "verified.txt")
85
+ writeFileSync(verified, "USD 4.2 billion in revenue.\nStaff grew to 1,250 people.\n")
86
+ const replaced = (await run(["source", "snapshot", "--id", "S1", "--text", verified, "--replace", ...s(dir)])) as { exitCode: number; json: Json }
87
+ expect(replaced.exitCode).toBe(0)
88
+ expect(existsSync(join(dir, "sources", "S1", "conversion.json"))).toBe(false)
89
+ const added = (await run(["fact", "add", "--source", "S1", "--find", "USD", "--label", "usd_bn", ...s(dir), "--json"])) as { exitCode: number; json: Json }
90
+ expect(added.exitCode).toBe(0)
91
+ expect((added.json.data?.row as { value: string }).value).toBe("4200000000")
92
+ copyFileSync(join(import.meta.dir, "tests", "fixtures", "fidelity-brief-locked.md"), join(dir, "brief.md"))
93
+ writeFileSync(join(dir, "report.src.html"), [
94
+ '<!doctype html><html lang="en"><head><meta charset="utf-8"><title>Revenue</title></head><body><h1>Revenue</h1>',
95
+ "<h2>Findings</h2><p>Revenue was {{F1:usd_bn}} [S1].</p>",
96
+ "<h2>How this report was made</h2><p>Collected from one cited public page [S1].</p>",
97
+ '<h2>Sources</h2><ol><li id="s1">[S1] Revenue page, retrieved 2026-10-01.</li></ol></body></html>',
98
+ ].join("\n"))
99
+ const filled = (await run(["fact", "fill", join(dir, "report.src.html"), "--out", join(dir, "report.html"), ...s(dir)])) as { exitCode: number }
100
+ expect(filled.exitCode).toBe(0)
101
+ const verifiedReport = (await run(["fact", "verify", join(dir, "report.html"), "--sources", join(dir, "sources"), "--brief", join(dir, "brief.md"), ...s(dir)])) as { exitCode: number; json: { defects?: unknown[] } }
102
+ expect(verifiedReport.json.defects).toEqual([])
103
+ expect(verifiedReport.exitCode).toBe(0)
104
+ })
105
+
106
+ test("#given an active row pinning S1 #when --replace runs #then it refuses with snapshot_pinned and keeps the old text", async () => {
107
+ const dir = session()
108
+ await snapshot(dir)
109
+ await run(["fact", "add", "--source", "S1", "--find", "Staff grew to", "--label", "staff", ...s(dir), "--json"])
110
+ const verified = join(dir, "verified.txt")
111
+ writeFileSync(verified, "USD 4.2 billion in revenue.\n")
112
+ const replaced = (await run(["source", "snapshot", "--id", "S1", "--text", verified, "--replace", ...s(dir)])) as { exitCode: number; json: Json }
113
+ expect(replaced.exitCode).toBe(1)
114
+ expect(replaced.json.error).toBe("snapshot_pinned")
115
+ expect(existsSync(join(dir, "sources", "S1", "conversion.json"))).toBe(true)
116
+ })
117
+ })
118
+
119
+ test("#given --html whose conversion altered 4.22 #when fact add is refused, the verified text replaces S1, and the row is added, filled and verified #then the replacement is declared text over the kept raw.bin and every step passes", async () => {
120
+ const dir = session()
121
+ const s = ["--session-dir", dir]
122
+ await snapshot(dir)
123
+ const refused = (await run(["fact", "add", "--source", "S1", "--find", "USD", "--label", "usd_bn", ...s, "--json"])) as { exitCode: number; json: Json }
124
+ expect([refused.exitCode, refused.json.error]).toEqual([1, "conversion_altered_number"])
125
+ const verified = join(dir, "verified.txt")
126
+ writeFileSync(verified, "USD 4.2 billion in revenue.\nStaff grew to 1,250 people.\n")
127
+ const replaced = (await run(["source", "snapshot", "--id", "S1", "--text", verified, "--replace", ...s])) as { exitCode: number; json: Json }
128
+ expect([replaced.exitCode, replaced.json.data?.text_source, replaced.json.data?.kind]).toEqual([0, "declared", "page"])
129
+ expect(existsSync(join(dir, "sources", "S1", "raw.bin"))).toBe(true)
130
+ const added = (await run(["fact", "add", "--source", "S1", "--find", "USD", "--label", "usd_bn", ...s, "--json"])) as { exitCode: number; json: Json & { message?: string } }
131
+ expect([added.exitCode, added.json.error, added.json.message]).toEqual([0, undefined, undefined])
132
+ const row = added.json.data?.row as { value: string; locator: { artifact: string; unit: string } }
133
+ expect([row.value, row.locator.artifact, row.locator.unit]).toEqual(["4200000000", "text.txt", "bytes"])
134
+ copyFileSync(join(import.meta.dir, "tests", "fixtures", "fidelity-brief-locked.md"), join(dir, "brief.md"))
135
+ writeFileSync(join(dir, "report.src.html"), [
136
+ '<!doctype html><html lang="en"><head><meta charset="utf-8"><title>Revenue</title></head><body><h1>Revenue</h1>',
137
+ "<h2>Findings</h2><p>Revenue was {{F1:usd_bn}} [S1].</p>",
138
+ "<h2>How this report was made</h2><p>Collected from one cited public page [S1].</p>",
139
+ '<h2>Sources</h2><ol><li id="s1">[S1] Revenue page, retrieved 2026-10-01.</li></ol></body></html>',
140
+ ].join("\n"))
141
+ expect(((await run(["fact", "fill", join(dir, "report.src.html"), "--out", join(dir, "report.html"), ...s])) as { exitCode: number }).exitCode).toBe(0)
142
+ const report = (await run(["fact", "verify", join(dir, "report.html"), "--sources", join(dir, "sources"), "--brief", join(dir, "brief.md"), ...s])) as { exitCode: number; json: { defects?: unknown[] } }
143
+ expect([report.exitCode, report.json.defects]).toEqual([0, []])
144
+ expect((JSON.parse(readFileSync(join(dir, "fidelity-defects.json"), "utf8")) as { defects: { code: string }[] }).defects.map((d) => d.code)).not.toContain("snapshot_not_raw")
145
+ })
146
+
147
+ test("#given a malformed conversion.json #when fact add reads it #then it is a usage error naming the file, not a crash", async () => {
148
+ const dir = session()
149
+ await snapshot(dir)
150
+ writeFileSync(join(dir, "sources", "S1", "conversion.json"), "{not json")
151
+ const added = run(["fact", "add", "--source", "S1", "--find", "Staff grew to", "--label", "staff", "--session-dir", dir, "--json"])
152
+ await expect(added).rejects.toBeInstanceOf(CliError)
153
+ await expect(added).rejects.toThrow(/conversion\.json/)
154
+ })
@@ -0,0 +1,297 @@
1
+ import { describe, expect, test } from "bun:test"
2
+ import { readFileSync } from "node:fs"
3
+ import { join } from "node:path"
4
+
5
+ import { markdownAsHtml } from "./fact-commands-verify.mjs"
6
+ import { derive } from "./fact-derive.mjs"
7
+ import { fill } from "./fact-fill.mjs"
8
+ import { addRow, emptyLedger } from "./fact-ledger.mjs"
9
+ import { checkFidelity } from "./gates-fidelity.mjs"
10
+ import { valueChecks } from "./fidelity-values.mjs"
11
+ import { buildContext } from "./gates-static.mjs"
12
+ import { parseHtml } from "./html-lite.mjs"
13
+
14
+ /** fact derive stamps observed_at from a caller-supplied clock (#11285); these rows only need a fixed one. */
15
+ const NOW = "2026-10-27T00:00:00.000Z"
16
+
17
+ type Row = Record<string, unknown> & { fact_id: string }
18
+ type Ledger = { rows: Row[] }
19
+ const FIX = join(import.meta.dir, "tests", "fixtures", "fidelity")
20
+ const BASE: Ledger = JSON.parse(readFileSync(join(FIX, "ledger-verify.json"), "utf8"))
21
+ const BRIEF = readFileSync(join(FIX, "..", "fidelity-brief-locked.md"), "utf8")
22
+ /** F8 in the shape fact derive writes it: raw is the render (fact-derive.mjs compute: raw = render). */
23
+ const DERIVED: Ledger = { ...BASE, rows: BASE.rows.map((r) => (r.fact_id === "F8" ? { ...r, raw: r.render } : r)) }
24
+ const check = (html: string, ledger: Ledger = DERIVED, sources: string | null = join(FIX, "sources")) =>
25
+ valueChecks(buildContext(parseHtml(`<h2>Findings</h2>${html}`), { baseDir: FIX }), ledger, null, sources)
26
+ const data = (id: string, shown: string) => `<p><data data-fact="${id}">${shown}</data></p>`
27
+ const rowOf = (ledger: Ledger, id: string) => ledger.rows.find((r) => r.fact_id === id) as Row
28
+
29
+ function measured(id: string, value: string, unit: string, extra: Record<string, unknown> = {}) {
30
+ return { fact_id: id, kind: "measured", value, unit, raw: `${value} ${unit}`, quote: `The figure is ${value} ${unit}.`, source_id: "S1", locator: { artifact: "sources/S1/text.txt", char_start: 0, char_end: 5 }, snapshot_sha256: "a".repeat(64), extracted_by: "fact add --find", observed_at: "2026-10-23T08:00:00Z", label: id.toLowerCase(), ...extra }
31
+ }
32
+
33
+ /** A derive-produced row filled by fact fill, so the report is exactly what the tools wrote. */
34
+ function derivedReport(rows: Row[], spec: Record<string, unknown>) {
35
+ const ledger = emptyLedger() as Ledger
36
+ for (const row of rows) addRow(ledger, row)
37
+ const result = derive(ledger, { now: NOW, ...spec }) as { row?: Row; error?: string }
38
+ if (!result.row) throw new Error(`derive refused: ${result.error}`)
39
+ const filled = fill(`<h2>Findings</h2><p>The total is {{${result.row.fact_id}:${result.row.label}}}.</p>`, ledger)
40
+ if (filled.out === null) throw new Error("fill failed")
41
+ return { ledger, row: result.row, html: filled.out }
42
+ }
43
+
44
+ describe("the converted half of a derived or converted row is display(value, unit)", () => {
45
+ const dated = { valid_at: "2026-10-01" }
46
+ const cases: [string, Row[], Record<string, unknown>][] = [
47
+ ["a grouped KRW-e8 sum", [measured("F1", "1617", "KRW-e8"), measured("F2", "1617", "KRW-e8")], { op: "sum", operands: ["F1", "F2"], unit: "KRW-e8" }],
48
+ ["a grouped KRW-e3 sum", [measured("F1", "1617", "KRW-e3"), measured("F2", "1617", "KRW-e3")], { op: "sum", operands: ["F1", "F2"], unit: "KRW-e3" }],
49
+ ["a grouped KRW-e4 sum", [measured("F1", "1617", "KRW-e4"), measured("F2", "1617", "KRW-e4")], { op: "sum", operands: ["F1", "F2"], unit: "KRW-e4" }],
50
+ ["a grouped KRW-e12 sum", [measured("F1", "1617", "KRW-e12"), measured("F2", "1617", "KRW-e12")], { op: "sum", operands: ["F1", "F2"], unit: "KRW-e12" }],
51
+ ["a km/h sum", [measured("F1", "30000", "km/h"), measured("F2", "30000", "km/h")], { op: "sum", operands: ["F1", "F2"], unit: "km/h" }],
52
+ ["a KRW/person sum", [measured("F1", "30000", "KRW/person"), measured("F2", "30000", "KRW/person")], { op: "sum", operands: ["F1", "F2"], unit: "KRW/person" }],
53
+ ["an FX expr in KRW-e8", [measured("F1", "12", "USD", dated), measured("F2", "1420", "KRW-e8/USD", dated)], { op: "expr", formula: "F1 * F2", unit: "KRW-e8" }],
54
+ ]
55
+ for (const [name, rows, spec] of cases) {
56
+ test(`#given ${name} exactly as fill wrote it #when verified #then no defect`, () => {
57
+ const { ledger, html } = derivedReport(rows, spec)
58
+ expect(valueChecks(buildContext(parseHtml(html), {}), ledger, null, null)).toEqual([])
59
+ })
60
+ }
61
+
62
+ test("#given a grouped KRW-e8 sum whose converted half was edited #when verified #then it still blocks", () => {
63
+ const { ledger, row, html } = derivedReport(cases[0][1], cases[0][2])
64
+ const edited = html.replace(`>${String(row.raw)}<`, ">3,243 KRW-e8<")
65
+ expect(edited).not.toBe(html)
66
+ expect(valueChecks(buildContext(parseHtml(edited), {}), ledger, null, null).map((d: { code: string }) => d.code)).toContain("value_mismatch")
67
+ })
68
+ })
69
+
70
+ describe("conversion originals bind both halves", () => {
71
+ for (const [shown, code] of [["320,000 mm (320 mm)", "unit_mismatch"], ["320,000 mm (999 m)", "value_mismatch"], ["320,000 mm (three m)", "value_mismatch"], ["320,000 mm (\uFF19\uFF19\uFF19 m)", "value_mismatch"], ["320,000 mm (m)", "value_mismatch"]]) {
72
+ test(`#given an edited original ${shown} #when verified #then ${code} on part original`, () => {
73
+ expect(check(data("F8", shown))).toContainEqual(expect.objectContaining({ code, part: "original" }))
74
+ expect(checkFidelity(data("F8", shown), { ledger: DERIVED, brief: BRIEF }).defects).toContainEqual(expect.objectContaining({ code, part: "original" }))
75
+ })
76
+ }
77
+
78
+ test("#given a ledger render edited to match a tampered report #when verified #then the original stays bound to its operand", () => {
79
+ const ledger = structuredClone(DERIVED)
80
+ rowOf(ledger, "F8").render = "320,000 mm (999 m)"
81
+ expect(check(data("F8", "320,000 mm (999 m)"), ledger)).toContainEqual(expect.objectContaining({ code: "value_mismatch", part: "original" }))
82
+ })
83
+
84
+ test("#given an edited converted half or a number beside the original #when verified #then value_mismatch", () => {
85
+ expect(check(data("F8", "320,000 mm (320 m)"))).toEqual([])
86
+ expect(check(data("F8", "319,000 mm (320 m)"))).toContainEqual(expect.objectContaining({ code: "value_mismatch" }))
87
+ expect(check(data("F8", "320,000 mm (320 m) 999"))).toContainEqual(expect.objectContaining({ code: "value_mismatch", part: "original" }))
88
+ })
89
+
90
+ test("#given the fixture F8 whose raw is not its render #when filled and verified #then the shape fact derive never writes is rejected", () => {
91
+ expect(rowOf(BASE, "F8").raw).not.toBe(rowOf(BASE, "F8").render)
92
+ const filled = fill("<h2>Findings</h2><p>The span is {{F8:span_mm}}.</p>", structuredClone(BASE))
93
+ expect(valueChecks(buildContext(parseHtml(String(filled.out)), {}), BASE, null, null)).toContainEqual(expect.objectContaining({ code: "value_mismatch" }))
94
+ })
95
+
96
+ test("#given grouped and percent originals #when verified #then the operation display contract holds", () => {
97
+ const ledger = structuredClone(DERIVED)
98
+ rowOf(ledger, "F1").value = "1237"
99
+ const converted = rowOf(ledger, "F8")
100
+ converted.value = "1237000"
101
+ expect(check(data("F8", "1,237,000 mm (1,237 m)"), ledger)).toEqual([])
102
+ Object.assign(converted, { kind: "derived", value: "0.12", unit: null, derived_from: ["F3"] })
103
+ expect(check(data("F8", "0.12 (12%)"), ledger)).toEqual([])
104
+ })
105
+
106
+ test("#given a converted row filled in value and unit mode #when verified #then no original is due", () => {
107
+ for (const pointer of [(id: string) => `{{${id}:span_mm:value}} mm`, (id: string) => `{{${id}:span_mm:value}}{{${id}:span_mm:unit}}`]) {
108
+ const ledger = emptyLedger() as Ledger
109
+ addRow(ledger, measured("F1", "320", "m"))
110
+ const { row } = derive(ledger, { now: NOW, op: "convert", operands: ["F1"], to: "mm", label: "span_mm" }) as { row: Row }
111
+ const filled = fill(`<h2>Findings</h2><p>The span is ${pointer(row.fact_id)}.</p>`, ledger)
112
+ expect(valueChecks(buildContext(parseHtml(String(filled.out)), {}), ledger, null, null)).toEqual([])
113
+ }
114
+ })
115
+ })
116
+
117
+ /**
118
+ * The conversion-original class: one row per written form (EN and KO), each applied at the fill
119
+ * site of a real fact derive convert row ("320,000 mm (320 m)") filled by fact fill, in four
120
+ * renders: HTML paragraph, HTML table cell, Markdown paragraph, Markdown table cell (fill map and
121
+ * projection, as fact verify reads it). Every bracket group is an original; a dash is not one.
122
+ */
123
+ describe("conversion originals: the bracket grammar class", () => {
124
+ const PASS = "pass"
125
+ const ORIGINAL = "value_mismatch/original"
126
+ const KO = { prefix: "\uD658\uC0B0 ", tbd: "\uBBF8\uC815", na: "\uD574\uB2F9 \uC5C6\uC74C", about: "\uC57D", words: "\uC0BC\uBC31\uC774\uC2ED" }
127
+ /** `after`: the input is typed right after the untouched fill site instead of replacing it. */
128
+ const ROWS: { form: string; input: string; expected: string; after?: true }[] = [
129
+ { form: "ASCII parentheses", input: "320,000 mm (320 m)", expected: PASS },
130
+ { form: "fullwidth parentheses", input: "320,000 mm\uFF08320 m\uFF09", expected: PASS },
131
+ { form: "fullwidth parentheses after a space", input: "320,000 mm \uFF08320 m\uFF09", expected: PASS },
132
+ { form: "square brackets", input: "320,000 mm [320 m]", expected: PASS },
133
+ { form: "fullwidth square brackets", input: "320,000 mm\uFF3B320 m\uFF3D", expected: PASS },
134
+ { form: "lenticular brackets", input: "320,000 mm\u3010320 m\u3011", expected: PASS },
135
+ { form: "corner brackets", input: "320,000 mm\u300C320 m\u300D", expected: PASS },
136
+ { form: "tortoise-shell brackets", input: "320,000 mm\u3014320 m\u3015", expected: PASS },
137
+ { form: "NFKC fullwidth digits and letters throughout", input: "\uFF13\uFF12\uFF10\uFF0C\uFF10\uFF10\uFF10 \uFF4D\uFF4D\uFF08\uFF13\uFF12\uFF10 \uFF4D\uFF09", expected: PASS },
138
+ { form: "NFKC fullwidth digits inside the original", input: "320,000 mm (\uFF13\uFF12\uFF10 m)", expected: PASS },
139
+ { form: "spaces inside the brackets", input: "320,000 mm ( 320 m )", expected: PASS },
140
+ { form: "no space before the bracket", input: "320,000 mm(320 m)", expected: PASS },
141
+ { form: "EN digit-free prefix", input: "Converted: 320,000 mm (320 m)", expected: PASS },
142
+ { form: "KO digit-free prefix, fullwidth", input: `${KO.prefix}320,000 mm\uFF08320 m\uFF09`, expected: PASS },
143
+ { form: "ASCII parentheses, wrong original", input: "320,000 mm (999 m)", expected: ORIGINAL },
144
+ { form: "fullwidth parentheses, wrong original", input: "320,000 mm\uFF08999 m\uFF09", expected: ORIGINAL },
145
+ { form: "square brackets, wrong original", input: "320,000 mm [999 m]", expected: ORIGINAL },
146
+ { form: "fullwidth square brackets, wrong original", input: "320,000 mm\uFF3B999 m\uFF3D", expected: ORIGINAL },
147
+ { form: "lenticular brackets, wrong original", input: "320,000 mm\u3010999 m\u3011", expected: ORIGINAL },
148
+ { form: "corner brackets, wrong original", input: "320,000 mm\u300C999 m\u300D", expected: ORIGINAL },
149
+ { form: "tortoise-shell brackets, wrong original", input: "320,000 mm\u3014999 m\u3015", expected: ORIGINAL },
150
+ { form: "NFKC fullwidth digits, wrong original", input: "320,000 mm (\uFF19\uFF19\uFF19 m)", expected: ORIGINAL },
151
+ { form: "spaces inside, wrong original", input: "320,000 mm ( 999 m )", expected: ORIGINAL },
152
+ { form: "hyphen separator, right number", input: "320,000 mm - 320 m", expected: ORIGINAL },
153
+ { form: "en-dash separator, right number", input: "320,000 mm \u2013 320 m", expected: ORIGINAL },
154
+ { form: "em-dash separator, right number", input: "320,000 mm \u2014 320 m", expected: ORIGINAL },
155
+ { form: "hyphen separator, wrong number", input: "320,000 mm - 999 m", expected: ORIGINAL },
156
+ { form: "en-dash separator, wrong number", input: "320,000 mm \u2013 999 m", expected: ORIGINAL },
157
+ { form: "separator after the original", input: "320,000 mm (320 m) - 999 m", expected: ORIGINAL },
158
+ { form: "nested parentheses", input: "320,000 mm ((320 m))", expected: ORIGINAL },
159
+ { form: "square inside parentheses", input: "320,000 mm ([320 m])", expected: ORIGINAL },
160
+ { form: "ASCII inside fullwidth parentheses", input: "320,000 mm\uFF08(320 m)\uFF09", expected: ORIGINAL },
161
+ { form: "mismatched bracket pair", input: "320,000 mm (320 m]", expected: ORIGINAL },
162
+ { form: "unclosed bracket", input: "320,000 mm (320 m", expected: ORIGINAL },
163
+ { form: "EN number word", input: "320,000 mm (twenty m)", expected: ORIGINAL },
164
+ { form: "EN n/a", input: "320,000 mm (n/a)", expected: ORIGINAL },
165
+ { form: "EN TBD", input: "320,000 mm (TBD)", expected: ORIGINAL },
166
+ { form: "EN approx. alone", input: "320,000 mm (approx.)", expected: ORIGINAL },
167
+ { form: "EN approx. beside the original", input: "320,000 mm (approx.) (320 m)", expected: ORIGINAL },
168
+ { form: "bare unit", input: "320,000 mm (m)", expected: ORIGINAL },
169
+ { form: "KO TBD", input: `320,000 mm (${KO.tbd})`, expected: ORIGINAL },
170
+ { form: "KO n/a, fullwidth", input: `320,000 mm\uFF08${KO.na}\uFF09`, expected: ORIGINAL },
171
+ { form: "KO hedge inside the original", input: `320,000 mm (${KO.about} 320 m)`, expected: ORIGINAL },
172
+ { form: "KO number word", input: `320,000 mm (${KO.words} m)`, expected: ORIGINAL },
173
+ { form: "missing original", input: "320,000 mm", expected: ORIGINAL },
174
+ { form: "EN prefix, missing original", input: "Converted: 320,000 mm", expected: ORIGINAL },
175
+ { form: "KO prefix, missing original", input: `${KO.prefix}320,000 mm`, expected: ORIGINAL },
176
+ { form: "original before the value", input: "(320 m) 320,000 mm", expected: ORIGINAL },
177
+ { form: "fullwidth original before the value", input: "\uFF08320 m\uFF09320,000 mm", expected: ORIGINAL },
178
+ { form: "two originals, both right", input: "320,000 mm (320 m) (320 m)", expected: ORIGINAL },
179
+ { form: "two originals, second wrong", input: "320,000 mm (320 m)\uFF08999 m\uFF09", expected: ORIGINAL },
180
+ { form: "two originals, mixed brackets", input: "320,000 mm (320 m) [320 m]", expected: ORIGINAL },
181
+ { form: "second original typed after the fill site", input: " (999 m)", expected: ORIGINAL, after: true },
182
+ { form: "fullwidth original typed after the fill site", input: "\uFF08320 m\uFF09", expected: ORIGINAL, after: true },
183
+ { form: "square original typed after the fill site", input: " [999 m]", expected: ORIGINAL, after: true },
184
+ { form: "separator original typed after the fill site", input: " - 999 m", expected: ORIGINAL, after: true },
185
+ { form: "prose bracket typed after the fill site", input: " (approx.)", expected: PASS, after: true },
186
+ ]
187
+ const SENTENCE = { en: ["The span is ", "."], ko: ["\uAD50\uB7C9 \uAE38\uC774\uB294 ", "\uC774\uB2E4."] }
188
+ type Render = { format: "html" | "md"; doc: (pointer: string, around: string[]) => string }
189
+ const RENDERS: Record<string, Render> = {
190
+ "html paragraph": { format: "html", doc: (p, [a, b]) => `<h2>Findings</h2><p>${a}${p}${b}</p>` },
191
+ "html table cell": { format: "html", doc: (p) => `<h2>Findings</h2><table><tr><th>span</th><td>${p}</td></tr></table>` },
192
+ "md paragraph": { format: "md", doc: (p, [a, b]) => `## Findings\n\n${a}${p}${b}\n` },
193
+ "md table cell": { format: "md", doc: (p) => `## Findings\n\n| item | value |\n| --- | --- |\n| span | ${p} |\n` },
194
+ }
195
+
196
+ /** The verdict fact verify gives the form written at the fill site: pass, or the code/part of the first value defect. */
197
+ function verdict(input: string, render: Render, lang: "en" | "ko", after = false) {
198
+ const ledger = emptyLedger() as Ledger
199
+ addRow(ledger, measured("F1", "320", "m"))
200
+ const row = (derive(ledger, { now: NOW, op: "convert", operands: ["F1"], to: "mm", label: "span_mm" }) as { row: Row }).row
201
+ const filled = fill(render.doc(`{{${row.fact_id}:span_mm}}`, SENTENCE[lang]), ledger, { format: render.format }) as { out: string; fillMap: Parameters<typeof markdownAsHtml>[1] }
202
+ const site = render.format === "html" ? `>${row.render}</data>` : String(row.render)
203
+ const edited = filled.out.replace(site, after ? `${site}${input}` : render.format === "html" ? `>${input}</data>` : input)
204
+ if (!filled.out.includes(site) || (input !== row.render && edited === filled.out)) return "fill site not found"
205
+ const projected = render.format === "md" ? markdownAsHtml(edited, filled.fillMap, ledger) : null
206
+ const defects = valueChecks(buildContext(parseHtml(projected?.html ?? edited), {}), ledger, projected?.stale ?? null, null) as { code: string; part?: string }[]
207
+ const value = defects.find((d) => d.code === "value_mismatch" || d.code === "unit_mismatch")
208
+ if (value) return `${value.code}/${value.part ?? "-"}`
209
+ return defects.length === 0 ? PASS : defects.map((d) => d.code).join(",")
210
+ }
211
+
212
+ test(`#given ${ROWS.length} written forms x ${Object.keys(RENDERS).length} renders x EN and KO #when verified #then each verdict is the row's expected`, () => {
213
+ const got: string[] = []
214
+ const want: string[] = []
215
+ for (const { form, input, expected, after } of ROWS) {
216
+ for (const [name, render] of Object.entries(RENDERS)) {
217
+ for (const lang of ["en", "ko"] as const) {
218
+ got.push(`${form} | ${name} | ${lang} | ${verdict(input, render, lang, after)}`)
219
+ want.push(`${form} | ${name} | ${lang} | ${expected}`)
220
+ }
221
+ }
222
+ }
223
+ expect(got).toEqual(want)
224
+ })
225
+ })
226
+
227
+ /**
228
+ * What can follow a converted element: each shape is written in the report template right after
229
+ * the fill site of a real fact derive convert row and resolved by fact fill, so every untampered
230
+ * row is a report exactly as the tools write it. A following group is another original only when
231
+ * it is a bare numeral with a unit of the operand's dimension that is not a filled element.
232
+ */
233
+ describe("conversion originals: what can follow a converted element", () => {
234
+ const [CLEAN, ORIGINAL, UNTRACED] = ["clean", "original", "untraceable_number"]
235
+ const BRACKETS = [["(", ")"], ["\uFF08", "\uFF09"], ["[", "]"], ["\uFF3B", "\uFF3D"], ["\u3010", "\u3011"], ["\u300C", "\u300D"], ["\u3014", "\u3015"], ["\u300E", "\u300F"], ["\u300A", "\u300B"]]
236
+ const SEPARATORS = ["-", "\u2013", "\u2014", "/"]
237
+ const ROWS: { shape: string; after: string; expected: string }[] = [
238
+ { shape: "nothing (element at the end of the cell or line)", after: "", expected: CLEAN },
239
+ ...BRACKETS.flatMap(([o, c]) => [
240
+ { shape: `${o}${c} second placeholder in prose`, after: ` ${o}{{F3:rate_yoy}} longer${c}`, expected: CLEAN },
241
+ { shape: `${o}${c} second placeholder alone`, after: ` ${o}{{F1:span_len}}${c}`, expected: CLEAN },
242
+ { shape: `${o}${c} bare numeral, wrong`, after: ` ${o}999 m${c}`, expected: ORIGINAL },
243
+ { shape: `${o}${c} bare numeral, right, other unit of the dimension`, after: ` ${o}0.32 km${c}`, expected: ORIGINAL },
244
+ ]),
245
+ ...SEPARATORS.flatMap((s) => [
246
+ { shape: `${s} then a filled element`, after: ` ${s} {{F1:span_len}}`, expected: CLEAN },
247
+ { shape: `${s} then a bare numeral`, after: ` ${s} 999 m`, expected: ORIGINAL },
248
+ { shape: `${s} then prose with a count`, after: ` ${s} 12 sites`, expected: UNTRACED },
249
+ ]),
250
+ { shape: "prose with a filled percent", after: " ({{F3:rate_yoy}} longer)", expected: CLEAN },
251
+ { shape: "prose with a literal percent", after: " (12 % longer)", expected: UNTRACED },
252
+ { shape: "a bare unitless number (footnote)", after: " (7)", expected: UNTRACED },
253
+ { shape: "a year", after: " (2024)", expected: UNTRACED },
254
+ { shape: "two consecutive elements", after: " {{F1:span_len}}", expected: CLEAN },
255
+ { shape: "two consecutive elements, glued", after: "{{F3:rate_yoy}}", expected: CLEAN },
256
+ { shape: "prose without digits", after: " (approx.)", expected: CLEAN },
257
+ ]
258
+ const SENTENCE = { en: ["The span is ", "."], ko: ["\uAD50\uB7C9 \uAE38\uC774\uB294 ", "\uC774\uB2E4."] }
259
+ type Render = { format: "html" | "md"; doc: (body: string, around: string[]) => string; stale?: true }
260
+ const RENDERS: Record<string, Render> = {
261
+ "html paragraph": { format: "html", doc: (b, [x, y]) => `<h2>Findings</h2><p>${x}${b}${y}</p>` },
262
+ "html table cell": { format: "html", doc: (b) => `<h2>Findings</h2><table><tr><th>span</th><td>${b}</td></tr></table>` },
263
+ "md paragraph": { format: "md", doc: (b, [x, y]) => `## Findings\n\n${x}${b}${y}\n` },
264
+ "md table cell": { format: "md", doc: (b) => `## Findings\n\n| item | value |\n| --- | --- |\n| span | ${b} |\n` },
265
+ "md paragraph, edited before the site (stale fill map)": { format: "md", doc: (b, [x, y]) => `## Findings\n\n${x}${b}${y}\n`, stale: true },
266
+ }
267
+
268
+ /** derive -> fill -> verify as fact verify wires it: "original" when a defect is on part original, else the defect codes or clean. */
269
+ function verdict(after: string, render: Render, lang: "en" | "ko") {
270
+ const ledger = emptyLedger() as Ledger
271
+ // The fixture's own source rows: each pins S4 and quotes its sentence, as fact add writes them.
272
+ for (const id of ["F1", "F3"]) addRow(ledger, structuredClone(rowOf(BASE, id)))
273
+ const row = (derive(ledger, { now: NOW, op: "convert", operands: ["F1"], to: "mm", label: "span_mm" }) as { row: Row }).row
274
+ const filled = fill(render.doc(`{{${row.fact_id}:span_mm}}${after}`, SENTENCE[lang]), ledger, { format: render.format }) as { out: string | null; fillMap: Parameters<typeof markdownAsHtml>[1] }
275
+ if (filled.out === null) return "fill failed"
276
+ const report = render.stale ? filled.out.replace("## Findings", "## Findings, revised after the review") : filled.out
277
+ const projected = render.format === "md" ? markdownAsHtml(report, filled.fillMap, ledger) : null
278
+ if (render.stale && projected?.stale.length === 0) return "fill map not stale"
279
+ const defects = valueChecks(buildContext(parseHtml(projected?.html ?? report), {}), ledger, projected?.stale ?? null, join(FIX, "sources")) as { code: string; part?: string }[]
280
+ if (defects.some((d) => d.part === "original")) return ORIGINAL
281
+ return defects.length === 0 ? CLEAN : [...new Set(defects.map((d) => d.code))].sort().join(",")
282
+ }
283
+
284
+ test(`#given ${ROWS.length} shapes x ${Object.keys(RENDERS).length} renders x EN and KO after a filled convert row #when verified #then each verdict is the row's expected`, () => {
285
+ const got: string[] = []
286
+ const want: string[] = []
287
+ for (const { shape, after, expected } of ROWS) {
288
+ for (const [name, render] of Object.entries(RENDERS)) {
289
+ for (const lang of ["en", "ko"] as const) {
290
+ got.push(`${shape} | ${name} | ${lang} | ${verdict(after, render, lang)}`)
291
+ want.push(`${shape} | ${name} | ${lang} | ${expected}`)
292
+ }
293
+ }
294
+ }
295
+ expect(got).toEqual(want)
296
+ })
297
+ })