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
@@ -0,0 +1,361 @@
1
+ import { describe, expect, test } from "bun:test"
2
+
3
+ import { probeConversion } from "./conversion-probe.mjs"
4
+ import { snapshotFromHtml } from "./source-snapshot.mjs"
5
+
6
+ // One table per class of conversion artifact. Each row is one form: the raw source, the converted
7
+ // text (BUILTIN = the built-in snapshot of the raw HTML), and the exact `altered` and `lost` lists.
8
+ // `attributes: false` is the built-in snapshot path, which writes no attribute text and no syntax.
9
+ type Row = { form: string; raw: string; converted: string; altered: string[]; lost: string[]; attributes?: boolean }
10
+ const BUILTIN = "\u0000builtin"
11
+
12
+ function check(row: Row) {
13
+ const builtin = row.converted === BUILTIN
14
+ const converted = builtin ? snapshotFromHtml(row.raw) : row.converted
15
+ const result = probeConversion(row.raw, converted, { attributes: builtin ? false : (row.attributes ?? true) })
16
+ expect({ altered: result.altered, lost: result.lost }).toEqual({ altered: row.altered, lost: row.lost })
17
+ }
18
+
19
+ const rows = (table: Row[]) => table.map((row) => [row.form, row] as const)
20
+
21
+ const OL = "<ol><li>Alpha</li><li>Beta</li></ol>"
22
+ const PANDOC_NOTE = '<p>Margin 12%<a href="#fn1" id="fnref1"><sup>1</sup></a></p><section><ol><li id="fn1"><p>Filing.<a href="#fnref1">back</a></p></li></ol></section>'
23
+
24
+ const CONVERTER_DIGITS: Row[] = [
25
+ { form: "ol -> '1. '", raw: OL, converted: "1. Alpha\n2. Beta", altered: [], lost: [] },
26
+ { form: "ol -> '1) '", raw: OL, converted: "1) Alpha\n2) Beta", altered: [], lost: [] },
27
+ { form: "ol -> '(1) '", raw: OL, converted: "(1) Alpha\n(2) Beta", altered: [], lost: [] },
28
+ { form: "KO ol with numbers in the items", raw: "<ol><li>\uB9E4\uCD9C 3\uC870 \uC6D0</li><li>\uC601\uC5C5\uC774\uC775 1,250\uC5B5 \uC6D0</li></ol>", converted: "1. \uB9E4\uCD9C 3\uC870 \uC6D0\n2. \uC601\uC5C5\uC774\uC775 1,250\uC5B5 \uC6D0", altered: [], lost: [] },
29
+ { form: "indented markers (spaces, tab)", raw: OL, converted: " 1. Alpha\n\t2. Beta", altered: [], lost: [] },
30
+ { form: "ol start=3", raw: '<ol start="3"><li>x</li><li>y</li></ol>', converted: "3. x\n4. y", altered: [], lost: [] },
31
+ { form: "ol start=3 renumbered from 1 by the converter", raw: '<ol start="3"><li>x</li></ol>', converted: "1. x", altered: ["1"], lost: [] },
32
+ { form: "nested ol", raw: "<ol><li>A<ol><li>B</li><li>C</li></ol></li><li>D</li></ol>", converted: "1. A\n 1. B\n 2. C\n2. D", altered: [], lost: [] },
33
+ { form: "ol inside a blockquote", raw: "<blockquote><ol><li>A</li></ol></blockquote>", converted: "> 1. A", altered: [], lost: [] },
34
+ { form: "footnote list (references ol)", raw: '<p>Revenue rose 4%.<sup><a href="#cite_note-1">[1]</a></sup></p><ol class="references"><li id="cite_note-1">Annual report.</li></ol>', converted: "Revenue rose 4%.[[1]](#cite_note-1)\n\n1. Annual report.", altered: [], lost: [] },
35
+ { form: "pandoc footnote -> [^1] and [^1]: definition", raw: PANDOC_NOTE, converted: "Margin 12%[^1]\n\n[^1]: Filing.", altered: [], lost: [] },
36
+ { form: "pandoc footnote -> markdownify list", raw: PANDOC_NOTE, converted: "Margin 12%[1](#fn1)\n\n1. Filing.[back](#fnref1)", altered: [], lost: [] },
37
+ { form: "KO footnote list", raw: '<p>\uB9E4\uCD9C 3,234\uC5B5 \uC6D0<sup><a href="#n1">1</a></sup></p><ol><li id="n1">\uC0AC\uC5C5\uBCF4\uACE0\uC11C</li></ol>', converted: "\uB9E4\uCD9C 3,234\uC5B5 \uC6D0[1](#n1)\n\n1. \uC0AC\uC5C5\uBCF4\uACE0\uC11C", altered: [], lost: [] },
38
+ { form: "table of contents as nested ol", raw: '<ol><li><a href="#intro">Intro</a><ol><li><a href="#scope">Scope</a></li></ol></li><li><a href="#method">Method</a></li></ol>', converted: "1. [Intro](#intro)\n 1. [Scope](#scope)\n2. [Method](#method)", altered: [], lost: [] },
39
+ { form: "table of contents numbers written in the source", raw: '<ul><li><a href="#s1"><span>1</span> Intro</a><ul><li><a href="#s1-1"><span>1.1</span> Scope</a></li></ul></li></ul>', converted: "- [1 Intro](#s1)\n - [1.1 Scope](#s1-1)", altered: [], lost: [] },
40
+ { form: "table of contents number the converter invented", raw: '<ul><li><a href="#scope">Scope</a></li></ul>', converted: "- [1.1 Scope](#scope)", altered: ["1.1"], lost: [] },
41
+ { form: "heading number written in the source", raw: "<h2>3. Results</h2>", converted: "## 3. Results", altered: [], lost: [] },
42
+ { form: "KO heading number written in the source", raw: "<h2>2\uC7A5 \uACB0\uACFC</h2>", converted: "## 2\uC7A5 \uACB0\uACFC", altered: [], lost: [] },
43
+ { form: "heading number the converter invented", raw: "<h2>Results</h2>", converted: "## 3 Results", altered: ["3"], lost: [] },
44
+ { form: "setext heading '2. Method' beside an ol", raw: "<h2>2. Method</h2><ol><li>x</li><li>y</li></ol>", converted: "2. Method\n---------\n\n1. x\n2. y", altered: [], lost: [] },
45
+ { form: "numbered paragraph, escaped by the converter", raw: "<p>1. Alpha</p>", converted: "1\\. Alpha", altered: [], lost: [] },
46
+ { form: "numbered paragraph, unescaped", raw: "<p>1. Alpha</p>", converted: "1. Alpha", altered: [], lost: [] },
47
+ { form: "bare '1. ' with no ol in the source", raw: "<p>Alpha</p>", converted: "1. Alpha", altered: ["1"], lost: [] },
48
+ { form: "ul source written as '1. '", raw: "<ul><li>Alpha</li></ul>", converted: "1. Alpha", altered: ["1"], lost: [] },
49
+ { form: "KO bare '1. ' with no ol", raw: "<p>\uB9E4\uCD9C \uC99D\uAC00</p>", converted: "1. \uB9E4\uCD9C \uC99D\uAC00", altered: ["1"], lost: [] },
50
+ { form: "more markers than ol items", raw: "<ol><li>Alpha</li></ol>", converted: "1. Alpha\n2. Beta", altered: ["2"], lost: [] },
51
+ { form: "lazy numbering '1. 1. 1.' over a three-item ol", raw: "<ol><li>a</li><li>b</li><li>c</li></ol>", converted: "1. a\n1. b\n1. c", altered: [], lost: [] },
52
+ { form: "lazy numbering at start=3", raw: '<ol start="3"><li>x</li><li>y</li></ol>', converted: "3. x\n3. y", altered: [], lost: [] },
53
+ { form: "KO lazy numbering", raw: "<ol><li>\uB9E4\uCD9C</li><li>\uBE44\uC6A9</li></ol>", converted: "1. \uB9E4\uCD9C\n1. \uBE44\uC6A9", altered: [], lost: [] },
54
+ { form: "lazy numbering with more markers than ol items", raw: "<ol><li>a</li><li>b</li></ol>", converted: "1. a\n1. b\n1. c", altered: ["1"], lost: [] },
55
+ { form: "built-in path: no marker exemption", raw: "<ol><li>Alpha</li></ol>", converted: "1. Alpha", altered: ["1"], lost: [], attributes: false },
56
+ { form: "inline link target digits", raw: '<p><a href="https://example.com/2024/03/report-4">Report</a></p>', converted: "[Report](https://example.com/2024/03/report-4)", altered: [], lost: [] },
57
+ { form: "image src, alt and title digits", raw: '<p><img src="chart-640x480.png" alt="Chart 2023" title="Fig 4"></p>', converted: '![Chart 2023](chart-640x480.png "Fig 4")', altered: [], lost: [] },
58
+ { form: "autolink", raw: '<p><a href="https://example.com/2024">https://example.com/2024</a></p>', converted: "<https://example.com/2024>", altered: [], lost: [] },
59
+ { form: "reference-style links with numeric labels", raw: '<p><a href="https://example.com/r/7">report</a> and <a href="https://example.com/r/9">data</a></p>', converted: "[report][1] and [data][2]\n\n[1]: https://example.com/r/7\n[2]: https://example.com/r/9", altered: [], lost: [] },
60
+ { form: "KO reference-style link", raw: '<p><a href="https://example.kr/b/2024">\uBCF4\uACE0\uC11C</a></p>', converted: "[\uBCF4\uACE0\uC11C][1]\n\n[1]: https://example.kr/b/2024", altered: [], lost: [] },
61
+ { form: "reference label with no definition", raw: "<p>report</p>", converted: "[report][1]", altered: ["1"], lost: [] },
62
+ { form: "reference definition with a title", raw: '<p><a href="https://example.com/r/7" title="Fig 4">report</a></p>', converted: '[report][1]\n\n[1]: https://example.com/r/7 "Fig 4"', altered: [], lost: [] },
63
+ { form: "footnote definition text is prose, not link syntax", raw: '<p>Margin<sup><a href="#note">1</a></sup> per <a href="https://s.example/4.22">source</a>.</p><ol><li id="note">Rate 4.2<sup>2</sup>.</li></ol>', converted: "Margin[^1] per source.\n\n[^1]: Rate 4.22.", altered: ["4.22"], lost: ["4.2", "2"] },
64
+ { form: "KO footnote definition text is prose", raw: '<p>\uB9E4\uCD9C<sup><a href="#n">1</a></sup> <a href="https://s.example/3,234">\uCD9C\uCC98</a></p><ol><li id="n">\uB9E4\uCD9C 3,23<sup>4</sup>\uC5B5 \uC6D0</li></ol>', converted: "\uB9E4\uCD9C[^1] \uCD9C\uCC98\n\n[^1]: \uB9E4\uCD9C 3,234\uC5B5 \uC6D0", altered: ["3,234"], lost: ["3,23", "4"] },
65
+ { form: "footnote definition with no use keeps its label a candidate", raw: "<p>Filing.</p>", converted: "[^1]: Filing.", altered: ["1"], lost: [] },
66
+ { form: "attribute digit written into prose", raw: '<p><a href="/p/42">page</a></p>', converted: "page 42", altered: ["42"], lost: [] },
67
+ { form: "built-in path: attribute digit in link syntax", raw: '<p><a href="/p/42">page</a></p>', converted: "[page](/p/42)", altered: ["42"], lost: [], attributes: false },
68
+ { form: "href page number beside a glued footnote", raw: '<p>Output reached 10<sup>9</sup> kWh</p><p><a href="https://b.example/b?pg=PA109">book</a></p>', converted: "Output reached 109 kWh\n\n[book](https://b.example/b?pg=PA109)", altered: ["109"], lost: ["10", "9"] },
69
+ ]
70
+
71
+ const SIGNS: Row[] = [
72
+ { form: "U+2212 written as a hyphen", raw: "fell \u22123.5 points", converted: "fell -3.5 points", altered: [], lost: [] },
73
+ { form: "U+2212 dropped", raw: "fell \u22123.5 points", converted: "fell 3.5 points", altered: ["3.5"], lost: ["\u22123.5"] },
74
+ { form: "hyphen-minus dropped", raw: "fell -3.5", converted: "fell 3.5", altered: ["3.5"], lost: ["-3.5"] },
75
+ { form: "en dash minus after a space dropped", raw: "fell \u20135%", converted: "fell 5%", altered: ["5"], lost: ["\u20135"] },
76
+ { form: "en dash minus written as a hyphen", raw: "fell \u20135%", converted: "fell -5%", altered: [], lost: [] },
77
+ { form: "hyphen after a space written as U+2212", raw: "change -4.2", converted: "change \u22124.2", altered: [], lost: [] },
78
+ { form: "U+2212 inside parentheses dropped", raw: "(\u22125)", converted: "(5)", altered: ["5"], lost: ["\u22125"] },
79
+ { form: "U+2212 after one letter dropped", raw: "\u0394\u22125 points", converted: "\u03945 points", altered: ["5"], lost: ["\u22125"] },
80
+ { form: "hyphen after one letter dropped", raw: "x-5", converted: "x5", altered: ["5"], lost: ["-5"] },
81
+ { form: "U+2212 after one letter written as a hyphen", raw: "\u0394\u22125", converted: "\u0394-5", altered: [], lost: [] },
82
+ { form: "en dash after one letter is a connector", raw: "A\u20135", converted: "A5", altered: [], lost: [] },
83
+ { form: "range after a digit with U+2212", raw: "2019\u22122023", converted: "2019-2023", altered: [], lost: [] },
84
+ { form: "range after a digit with an en dash", raw: "2019\u20132023", converted: "2019-2023", altered: [], lost: [] },
85
+ { form: "ISO date", raw: "2026-10-26", converted: "2026-10-26", altered: [], lost: [] },
86
+ { form: "hyphen after Hangul dropped", raw: "\uAC10\uC18C\uC728-3.5%", converted: "\uAC10\uC18C\uC728 3.5%", altered: ["3.5"], lost: ["-3.5"] },
87
+ { form: "U+2212 after Hangul dropped", raw: "\uC804\uB144 \uB300\uBE44\u22122.1%", converted: "\uC804\uB144 \uB300\uBE44 2.1%", altered: ["2.1"], lost: ["\u22122.1"] },
88
+ { form: "U+2212 after Hangul written as a hyphen", raw: "\uC99D\uAC10\uB960\u22122.1%", converted: "\uC99D\uAC10\uB960-2.1%", altered: [], lost: [] },
89
+ { form: "identifier cite_note-7", raw: "cite_note-7", converted: "cite_note7", altered: [], lost: [] },
90
+ { form: "identifier ISO-8601", raw: "ISO-8601", converted: "ISO 8601", altered: [], lost: [] },
91
+ { form: "identifier UTF-8", raw: "UTF-8 text", converted: "UTF8 text", altered: [], lost: [] },
92
+ { form: "identifier COVID-19", raw: "COVID-19 cases", converted: "COVID 19 cases", altered: [], lost: [] },
93
+ { form: "two-letter code AB-12", raw: "AB-12", converted: "AB12", altered: [], lost: [] },
94
+ { form: "digit-letter code 3x-5", raw: "3x-5", converted: "3x5", altered: [], lost: [] },
95
+ { form: "exponent minus dropped", raw: "1e-5", converted: "1e5", altered: ["5"], lost: ["-5"] },
96
+ { form: "exponent U+2212 written as a hyphen", raw: "2.5E\u22123", converted: "2.5E-3", altered: [], lost: [] },
97
+ { form: "superscript exponent flattened onto its base", raw: "<p>10<sup>\u22125</sup></p>", converted: "10-5", altered: ["5"], lost: ["\u22125"] },
98
+ { form: "superscript exponent written with a caret", raw: "<p>10<sup>\u22125</sup></p>", converted: "10^-5", altered: [], lost: [] },
99
+ { form: "negative cells kept", raw: "<table><tr><td>Q4</td><td>-5</td><td>\u22123.2</td></tr></table>", converted: "| Q4 | -5 | -3.2 |", altered: [], lost: [] },
100
+ { form: "negative cells lose their sign", raw: "<table><tr><td>Q4</td><td>-5</td><td>\u22123.2</td></tr></table>", converted: "| Q4 | 5 | 3.2 |", altered: ["5", "3.2"], lost: ["-5", "\u22123.2"] },
101
+ { form: "KO negative cell", raw: "<table><tr><th>\uC99D\uAC10</th><td>\u22122.1%</td></tr></table>", converted: "| \uC99D\uAC10 | -2.1% |", altered: [], lost: [] },
102
+ { form: "built-in path: negative cell", raw: "<table><tr><td>Q4</td><td>\u22123.2</td></tr></table>", converted: BUILTIN, altered: [], lost: [] },
103
+ { form: "plus sign carries no sign", raw: "+7", converted: "7", altered: [], lost: [] },
104
+ { form: "minus with a space after it is not a sign", raw: "change - 5", converted: "change 5", altered: [], lost: [] },
105
+ { form: "Markdown bullet before a number", raw: "<ul><li>5 sites</li></ul>", converted: "- 5 sites", altered: [], lost: [] },
106
+ { form: "Markdown bullet before a negative number", raw: "<ul><li>-5 C</li></ul>", converted: "- -5 C", altered: [], lost: [] },
107
+ ]
108
+
109
+ const GROUPING: Row[] = [
110
+ { form: "td | td | td", raw: "<table><tr><td>3</td><td>250</td><td>100</td></tr></table>", converted: "| 3 | 250 | 100 |", altered: [], lost: [] },
111
+ { form: "th | td", raw: "<table><tr><th>12</th><td>345</td></tr></table>", converted: "| 12 | 345 |", altered: [], lost: [] },
112
+ { form: "tr / tr", raw: "<table><tr><td>12</td></tr><tr><td>345</td></tr></table>", converted: "| 12 |\n| 345 |", altered: [], lost: [] },
113
+ { form: "ul li / li", raw: "<ul><li>3</li><li>250</li></ul>", converted: "- 3\n- 250", altered: [], lost: [] },
114
+ { form: "ol li / li", raw: "<ol><li>3</li><li>250</li></ol>", converted: "1. 3\n2. 250", altered: [], lost: [] },
115
+ { form: "p / p", raw: "<p>3</p><p>250</p>", converted: "3\n\n250", altered: [], lost: [] },
116
+ { form: "div / div", raw: "<div>12</div><div>345</div>", converted: "12\n345", altered: [], lost: [] },
117
+ { form: "h2 / p", raw: "<h2>12</h2><p>345</p>", converted: "## 12\n\n345", altered: [], lost: [] },
118
+ { form: "dt / dd", raw: "<dl><dt>12</dt><dd>345</dd></dl>", converted: "12\n: 345", altered: [], lost: [] },
119
+ { form: "blockquote / p", raw: "<blockquote>12</blockquote><p>345</p>", converted: "> 12\n\n345", altered: [], lost: [] },
120
+ { form: "br -> line break", raw: "<p>12<br>345</p>", converted: "12\n345", altered: [], lost: [] },
121
+ { form: "br kept as <br> in a Markdown cell", raw: "<table><tr><td>2,500<br>300</td></tr></table>", converted: "| 2,500<br>300 |", altered: [], lost: [] },
122
+ { form: "br flattened to a space (real artifact)", raw: "<p>12<br>345</p>", converted: "12 345", altered: ["12 345"], lost: ["12", "345"] },
123
+ { form: "li holding two paragraphs, flattened", raw: "<ul><li><p>in 2023</p><p>500 sites</p></li></ul>", converted: "in 2023 500 sites", altered: [], lost: [] },
124
+ { form: "comma group then a space, one paragraph", raw: "<p>1,250\n300</p>", converted: "1,250 300", altered: [], lost: [] },
125
+ { form: "comma group | next cell", raw: "<table><tr><td>1,250</td><td>300</td></tr></table>", converted: "| 1,250 | 300 |", altered: [], lost: [] },
126
+ { form: "period decimal | next cell", raw: "<table><tr><td>1.234</td><td>567</td></tr></table>", converted: "| 1.234 | 567 |", altered: [], lost: [] },
127
+ { form: "KO td | td | td", raw: "<table><tr><td>\uB9E4\uCD9C</td><td>3,234</td><td>\uC5B5\uC6D0</td></tr></table>", converted: "| \uB9E4\uCD9C | 3,234 | \uC5B5\uC6D0 |", altered: [], lost: [] },
128
+ { form: "KO li / li", raw: "<ul><li>12</li><li>345\uBA85</li></ul>", converted: "- 12\n- 345\uBA85", altered: [], lost: [] },
129
+ { form: "KO p / p", raw: "<p>12</p><p>345\uC5B5 \uC6D0</p>", converted: "12\n\n345\uC5B5 \uC6D0", altered: [], lost: [] },
130
+ { form: "spaces inside one cell group", raw: "<table><tr><td>1 234 567</td><td>8</td></tr></table>", converted: "| 1 234 567 | 8 |", altered: [], lost: [] },
131
+ { form: "NBSP groups", raw: "1\u00a0234\u00a0567", converted: "1 234 567", altered: [], lost: [] },
132
+ { form: "narrow NBSP groups", raw: "1\u202f234\u202f567", converted: "1 234 567", altered: [], lost: [] },
133
+ { form: "thin space entity groups", raw: "<p>1&#8201;234&#8201;567</p>", converted: "1 234 567", altered: [], lost: [] },
134
+ { form: "figure space entity groups", raw: "<p>1&#8199;234</p>", converted: "1 234", altered: [], lost: [] },
135
+ { form: "span-split groups", raw: "<p><span>1</span> <span>234</span> <span>567</span></p>", converted: "1 234 567", altered: [], lost: [] },
136
+ { form: "source newline between spans is a space", raw: "<p><span>1</span>\n<span>234</span></p>", converted: "1 234", altered: [], lost: [] },
137
+ { form: "KO space group with a unit", raw: "<p>1 234 567\uC6D0</p>", converted: "1 234 567\uC6D0", altered: [], lost: [] },
138
+ { form: "KO comma group in a cell", raw: "<table><tr><td>3,234\uC5B5 \uC6D0</td></tr></table>", converted: "| 3,234\uC5B5 \uC6D0 |", altered: [], lost: [] },
139
+ { form: "converter hard wrap inside a space group", raw: "<p>Population 1 234 567 people</p>", converted: "Population 1 234\n567 people", altered: [], lost: [] },
140
+ { form: "KO hard wrap inside a space group", raw: "<p>\uC778\uAD6C 1 234 567\uBA85</p>", converted: "\uC778\uAD6C 1 234\n567\uBA85", altered: [], lost: [] },
141
+ { form: "hard wrap between two separate numbers", raw: "<p>in 2023 500 sites</p>", converted: "in 2023\n500 sites", altered: [], lost: [] },
142
+ { form: "a wrap is not rejoined when the raw side holds both halves too", raw: "<p>12 345 and 12 and 345</p>", converted: "12\n345 and 12 and 345", altered: ["12", "345"], lost: ["12 345"] },
143
+ { form: "a blank line never rejoins a group", raw: "<p>Population 1 234 567 people</p>", converted: "Population 1 234\n\n567 people", altered: ["1 234", "567"], lost: ["1 234 567"] },
144
+ { form: "footnote glued onto a space group", raw: "<p>1 234<sup>5</sup></p>", converted: "1 2345", altered: ["1", "2345"], lost: ["1 234", "5"] },
145
+ { form: "built-in path: td | td | td", raw: "<table><tr><td>3</td><td>250</td><td>100</td></tr></table>", converted: BUILTIN, altered: [], lost: [] },
146
+ { form: "built-in path: li / li", raw: "<ul><li>12</li><li>345</li></ul>", converted: BUILTIN, altered: [], lost: [] },
147
+ { form: "built-in path: br inside a paragraph is joined by the snapshot, so it is flagged", raw: "<p>12<br>345</p>", converted: BUILTIN, altered: ["12 345"], lost: ["12", "345"] },
148
+ { form: "built-in path: br between cells' lines", raw: "<table><tr><td>12</td></tr><tr><td>345</td></tr></table>", converted: BUILTIN, altered: [], lost: [] },
149
+ { form: "built-in path: KO space group in a cell", raw: "<table><tr><td>1 234 567\uC6D0</td><td>8</td></tr></table>", converted: BUILTIN, altered: [], lost: [] },
150
+ ]
151
+
152
+ // Markdown markup a converter writes beside a sign must not change how the sign reads: the text the
153
+ // markup closes (or opens) decides, as it does in the raw HTML. One row per markup x dash x context.
154
+ type Shape = { form: string; html: (c: string) => string; md: (c: string) => string; opaque?: boolean }
155
+ const MARKUP: Shape[] = [
156
+ { form: "**", html: (c) => `<b>${c}</b>`, md: (c) => `**${c}**` },
157
+ { form: "*", html: (c) => `<i>${c}</i>`, md: (c) => `*${c}*` },
158
+ { form: "_", html: (c) => `<em>${c}</em>`, md: (c) => `_${c}_` },
159
+ { form: "__", html: (c) => `<strong>${c}</strong>`, md: (c) => `__${c}__` },
160
+ { form: "~~", html: (c) => `<del>${c}</del>`, md: (c) => `~~${c}~~` },
161
+ { form: "`", html: (c) => `<code>${c}</code>`, md: (c) => `\`${c}\`` },
162
+ { form: "``", html: (c) => `<code>${c}</code>`, md: (c) => `\`\`${c}\`\`` },
163
+ { form: "[](url)", html: (c) => `<a href="/c">${c}</a>`, md: (c) => `[${c}](/c)` },
164
+ { form: "[][ref]", html: (c) => `<a href="/c">${c}</a>`, md: (c) => `[${c}][c]` },
165
+ { form: "[] shortcut", html: (c) => `<a href="/c">${c}</a>`, md: (c) => `[${c}]` },
166
+ ]
167
+ const DASHES = [["hyphen", "-"], ["en dash", "\u2013"], ["U+2212", "\u2212"]] as const
168
+ const CLOSED = [
169
+ { form: "a digit", text: "2019", gap: "", after: "2023", signed: (_dash: string) => false },
170
+ { form: "a Latin word", text: "COVID", gap: "", after: "19", signed: (_dash: string) => false },
171
+ { form: "one Latin letter", text: "x", gap: "", after: "5", signed: (dash: string) => dash !== "\u2013" },
172
+ { form: "KO Hangul", text: "\uAC10\uC18C", gap: "", after: "3", signed: (_dash: string) => true },
173
+ { form: "a space", text: "fell", gap: " ", after: "5", signed: (_dash: string) => true },
174
+ ]
175
+ // Every closer shape a CommonMark converter writes after text: inline links and images with a
176
+ // "title", 'title' or (title), a target with balanced parentheses or in <...>, collapsed and full
177
+ // reference forms, a linked image, and backslash escapes. An escaped closer is skipped like the raw
178
+ // character it stands for; an escaped opener (`opaque`) is a real character, so every dash after it is a sign.
179
+ const IMG = '<img src="i.png" alt="x"'
180
+ const CLOSER_SHAPES: Shape[] = [
181
+ { form: '[](url "title")', html: (c) => `<a href="/c" title="t">${c}</a>`, md: (c) => `[${c}](/c "t")` },
182
+ { form: "[](url 'title')", html: (c) => `<a href="/c" title="t">${c}</a>`, md: (c) => `[${c}](/c 't')` },
183
+ { form: "[](url (title))", html: (c) => `<a href="/c" title="t">${c}</a>`, md: (c) => `[${c}](/c (t))` },
184
+ { form: '[](url "title with \\" and )")', html: (c) => `<a href="/c" title='a " )'>${c}</a>`, md: (c) => `[${c}](/c "a \\" )")` },
185
+ { form: "[](url_(x))", html: (c) => `<a href="/F_(x)">${c}</a>`, md: (c) => `[${c}](/F_(x))` },
186
+ { form: '[](url_(x) "title")', html: (c) => `<a href="/F_(x)" title="t">${c}</a>`, md: (c) => `[${c}](/F_(x) "t")` },
187
+ { form: "[](<url with a space>)", html: (c) => `<a href="/a b">${c}</a>`, md: (c) => `[${c}](</a b>)` },
188
+ { form: "[][] collapsed", html: (c) => `<a href="/c">${c}</a>`, md: (c) => `[${c}][]` },
189
+ { form: '[**text**](url "title")', html: (c) => `<a href="/c" title="t"><b>${c}</b></a>`, md: (c) => `[**${c}**](/c "t")` },
190
+ { form: '**[text](url "title")**', html: (c) => `<b><a href="/c" title="t">${c}</a></b>`, md: (c) => `**[${c}](/c "t")**` },
191
+ { form: "![](src)", html: (c) => `${c}${IMG}>`, md: (c) => `${c}![x](i.png)` },
192
+ { form: '![](src "title")', html: (c) => `${c}${IMG} title="t">`, md: (c) => `${c}![x](i.png "t")` },
193
+ { form: "![](src 'title')", html: (c) => `${c}${IMG} title="t">`, md: (c) => `${c}![x](i.png 't')` },
194
+ { form: "![](a_(b).png)", html: (c) => `${c}<img src="a_(b).png" alt="x">`, md: (c) => `${c}![x](a_(b).png)` },
195
+ { form: '![](a_(b).png "title")', html: (c) => `${c}<img src="a_(b).png" alt="x" title="t">`, md: (c) => `${c}![x](a_(b).png "t")` },
196
+ { form: "![](<src with a space>)", html: (c) => `${c}<img src="a b.png" alt="x">`, md: (c) => `${c}![x](<a b.png>)` },
197
+ { form: "![][ref]", html: (c) => `${c}${IMG}>`, md: (c) => `${c}![x][i]` },
198
+ { form: "[![](src)](url)", html: (c) => `${c}<a href="/p">${IMG}></a>`, md: (c) => `${c}[![x](i.png)](/p)` },
199
+ { form: "**![](src)**", html: (c) => `${c}<b>${IMG}></b>`, md: (c) => `${c}**![x](i.png)**` },
200
+ ...["*", "_", "]", ")", "~", "`"].map((x): Shape => ({ form: `escaped \\${x}`, html: (c) => `${c}${x}`, md: (c) => `${c}\\${x}` })),
201
+ ...["[", "("].map((x): Shape => ({ form: `escaped \\${x} (an opener)`, html: (c) => `${c}${x}`, md: (c) => `${c}\\${x}`, opaque: true })),
202
+ ]
203
+ const closerRows = (shapes: Shape[]): Row[] => shapes.flatMap((m) => DASHES.flatMap(([dash, d]) => CLOSED.flatMap((c) => {
204
+ const raw = `<p>${m.html(c.text)}${c.gap}${d}${c.after}</p>`
205
+ const kept: Row = { form: `${m.form} closing ${c.form}, then a ${dash}`, raw, converted: `${m.md(c.text)}${c.gap}${d}${c.after}`, altered: [], lost: [] }
206
+ if (!(m.opaque || c.signed(d))) return [kept]
207
+ return [kept, { form: `${m.form} closing ${c.form}, ${dash} dropped`, raw, converted: `${m.md(c.text)}${c.gap}${c.after}`, altered: [c.after], lost: [`${d}${c.after}`] }]
208
+ })))
209
+ const SIGN_MARKUP: Row[] = [
210
+ ...closerRows(MARKUP),
211
+ ...closerRows(CLOSER_SHAPES),
212
+ ...MARKUP.flatMap((m) => [
213
+ ...DASHES.flatMap(([dash, d]) => [
214
+ { form: `${m.form} around a ${dash} number after a space`, raw: `<p>fell ${m.html(`${d}5`)}</p>`, converted: `fell ${m.md(`${d}5`)}`, altered: [], lost: [] },
215
+ { form: `${m.form} around a ${dash} number at line start`, raw: `<p>${m.html(`${d}5`)} pts</p>`, converted: `${m.md(`${d}5`)} pts`, altered: [], lost: [] },
216
+ { form: `${m.form} opened between a ${dash} and its digits`, raw: `<p>fell ${d}${m.html("5")}</p>`, converted: `fell ${d}${m.md("5")}`, altered: [], lost: [] },
217
+ ]),
218
+ { form: `${m.form} around a number whose U+2212 was dropped`, raw: `<p>fell ${m.html("\u22125")}</p>`, converted: `fell ${m.md("5")}`, altered: ["5"], lost: ["\u22125"] },
219
+ ]),
220
+ { form: "reviewer: **2019**-2023", raw: "<p><b>2019</b>-2023</p>", converted: "**2019**-2023", altered: [], lost: [] },
221
+ { form: "reviewer: *UTF*-8", raw: "<p><i>UTF</i>-8</p>", converted: "*UTF*-8", altered: [], lost: [] },
222
+ { form: "reviewer: `v1`-2", raw: "<p><code>v1</code>-2</p>", converted: "`v1`-2", altered: [], lost: [] },
223
+ { form: "image before a minus: the alt text is not the context", raw: '<p>Q <img src="i.png" alt="chart">\u22125</p>', converted: "Q ![chart](i.png)-5", altered: [], lost: [] },
224
+ { form: "KO image before a minus", raw: '<p>\uC99D\uAC10 <img src="i.png" alt="\uADF8\uB9BC">\u22122.1%</p>', converted: "\uC99D\uAC10 ![\uADF8\uB9BC](i.png)-2.1%", altered: [], lost: [] },
225
+ { form: "autolink closer before one letter's minus", raw: '<p><a href="https://x.example/a">https://x.example/a</a>\u22125</p>', converted: "<https://x.example/a>-5", altered: [], lost: [] },
226
+ { form: "identifier kept through markup: **cite_note**-7", raw: "<p><b>cite_note</b>-7</p>", converted: "**cite_note**-7", altered: [], lost: [] },
227
+ { form: "two letters split by markup stay an identifier", raw: "<p>a<b>b</b>\u22125</p>", converted: "a**b**-5", altered: [], lost: [] },
228
+ { form: "reviewer r5: titled link before an en dash range", raw: 'From <a href="/wiki/1990" title="1990">1990</a>\u20132000', converted: 'From [1990](/wiki/1990 "1990")\u20132000', altered: [], lost: [] },
229
+ { form: "reviewer r5: titled link before a hyphen range", raw: 'From <a href="/wiki/1990" title="1990">1990</a>-2000', converted: 'From [1990](/wiki/1990 "1990")-2000', altered: [], lost: [] },
230
+ { form: "reviewer r5: titled link on a word", raw: '<p><a href="/x" title="t">ab</a>-5</p>', converted: '[ab](/x "t")-5', altered: [], lost: [] },
231
+ { form: "reviewer r5: titled image after a word", raw: '<p>ab<img alt="x" title="t" src="i.png">-5</p>', converted: 'ab![x](i.png "t")-5', altered: [], lost: [] },
232
+ { form: "reviewer r5: image target with parentheses after a space", raw: '<p>fell <img src="a_(b).png" alt="x">-5</p>', converted: "fell ![x](a_(b).png)-5", altered: [], lost: [] },
233
+ { form: "reviewer r5: escaped brackets", raw: "x[3]-5", converted: "x\\[3\\]-5", altered: [], lost: [] },
234
+ { form: "reviewer r5: escaped asterisk", raw: "foo*-5", converted: "foo\\*-5", altered: [], lost: [] },
235
+ { form: "reviewer r5: escaped underscore", raw: "ab_-5", converted: "ab\\_-5", altered: [], lost: [] },
236
+ { form: "link target digits after balanced parentheses are link syntax", raw: '<p><a href="/wiki/File_(2019)_v9.png">pic</a></p>', converted: "[pic](/wiki/File_(2019)_v9.png)", altered: [], lost: [] },
237
+ { form: "titled link target digits are link syntax", raw: '<p><a href="/r/7" title="Fig (4)">pic</a></p>', converted: '[pic](/r/7 "Fig (4)")', altered: [], lost: [] },
238
+ ]
239
+
240
+ // Raw inline HTML a converter leaves inside its Markdown (pandoc's gfm writer keeps <sup>, <sub>,
241
+ // sized <img>, <span> and <br>): read with the html-lite tag reader, a sup, sub or break tag is a
242
+ // boundary as on the raw side (`opaque`: every dash after it is a sign), any other tag or comment
243
+ // is dropped with its attributes, like a Markdown closer or opener.
244
+ const HTML_SHAPES: Shape[] = [
245
+ { form: "<sup>", html: (c) => `<sup>${c}</sup>`, md: (c) => `<sup>${c}</sup>`, opaque: true },
246
+ { form: "<sub>", html: (c) => `<sub>${c}</sub>`, md: (c) => `<sub>${c}</sub>`, opaque: true },
247
+ { form: "<br> after the text", html: (c) => `${c}<br>`, md: (c) => `${c}<br>`, opaque: true },
248
+ { form: "<br /> after the text", html: (c) => `${c}<br>`, md: (c) => `${c}<br />`, opaque: true },
249
+ { form: "<span>", html: (c) => `<span>${c}</span>`, md: (c) => `<span>${c}</span>` },
250
+ { form: '<span class="x">', html: (c) => `<span class="x">${c}</span>`, md: (c) => `<span class="x">${c}</span>` },
251
+ { form: "<em> written for <i>", html: (c) => `<i>${c}</i>`, md: (c) => `<em>${c}</em>` },
252
+ { form: '<a href="/wiki/1990">', html: (c) => `<a href="/wiki/1990">${c}</a>`, md: (c) => `<a href="/wiki/1990">${c}</a>` },
253
+ { form: '<a href title="Fig (4)">', html: (c) => `<a href="/c" title="Fig (4)">${c}</a>`, md: (c) => `<a href="/c" title="Fig (4)">${c}</a>` },
254
+ { form: '<img width="320"> after the text', html: (c) => `${c}<img src="i.png" width="320">`, md: (c) => `${c}<img src="i.png" width="320">` },
255
+ { form: "<img src=a2019.png /> after the text", html: (c) => `${c}<img src="a2019.png" alt="x">`, md: (c) => `${c}<img src=a2019.png alt=x />` },
256
+ { form: "<!-- comment 7 --> after the text", html: (c) => `${c}<!-- comment 7 -->`, md: (c) => `${c}<!-- comment 7 -->` },
257
+ { form: "**<span>**", html: (c) => `<b><span>${c}</span></b>`, md: (c) => `**<span>${c}</span>**` },
258
+ { form: "<span>[link](url)</span>", html: (c) => `<span><a href="/c">${c}</a></span>`, md: (c) => `<span>[${c}](/c)</span>` },
259
+ ]
260
+ const RAW_HTML: Row[] = [
261
+ ...closerRows(HTML_SHAPES),
262
+ { form: "reviewer r6: pandoc 10<sup>-5</sup>", raw: "<p>10<sup>-5</sup> m</p>", converted: "10<sup>-5</sup> m", altered: [], lost: [] },
263
+ { form: "reviewer r6: pandoc 10<sup>U+2212 5</sup>", raw: "<p>10<sup>\u22125</sup> m</p>", converted: "10<sup>\u22125</sup> m", altered: [], lost: [] },
264
+ { form: "reviewer r6: pandoc 10<sup>9</sup>", raw: "<p>Output reached 10<sup>9</sup> kWh</p>", converted: "Output reached 10<sup>9</sup> kWh", altered: [], lost: [] },
265
+ { form: "reviewer r6: <span>a</span>-5", raw: "<p><span>a</span>-5</p>", converted: "<span>a</span>-5", altered: [], lost: [] },
266
+ { form: 'reviewer r6: <img src="a2019.png">', raw: '<p><img src="a2019.png"></p>', converted: '<img src="a2019.png">', altered: [], lost: [] },
267
+ { form: "reviewer r6: <img src=x width=320> before a number", raw: "<p><img src=x width=320> 5 apples</p>", converted: "<img src=x width=320> 5 apples", altered: [], lost: [] },
268
+ { form: "KO <img width=320> before a number", raw: "<p><img src=x width=320> \uC0AC\uACFC 5\uAC1C</p>", converted: "<img src=x width=320> \uC0AC\uACFC 5\uAC1C", altered: [], lost: [] },
269
+ { form: "reviewer r6: 2019<sup>a</sup>-2023", raw: "<p>2019<sup>a</sup>-2023</p>", converted: "2019<sup>a</sup>-2023", altered: [], lost: [] },
270
+ { form: "KO 3,234<sup>1</sup>\uC5B5 kept as raw HTML", raw: "<p>\uB9E4\uCD9C 3,234<sup>1</sup>\uC5B5 \uC6D0</p>", converted: "\uB9E4\uCD9C 3,234<sup>1</sup>\uC5B5 \uC6D0", altered: [], lost: [] },
271
+ { form: "a superscript the converter flattened is still altered", raw: "<p>10<sup>-5</sup> m</p>", converted: "10-5 m", altered: ["5"], lost: ["-5"] },
272
+ { form: "a glued footnote is still altered next to raw HTML", raw: "<p>4.2<sup>2</sup> <span>bn</span></p>", converted: "4.22 <span>bn</span>", altered: ["4.22"], lost: ["4.2", "2"] },
273
+ { form: "an img width written into prose is still altered", raw: "<p><img src=x width=320> 5</p>", converted: "320 5", altered: ["320"], lost: [] },
274
+ { form: "an autolink is not a tag", raw: '<p><a href="https://x.example/2024">https://x.example/2024</a></p>', converted: "<https://x.example/2024>", altered: [], lost: [] },
275
+ { form: "an escaped \\< is text, not a tag", raw: "<p>a&lt;b&gt;7</p>", converted: "a\\<b>7", altered: [], lost: [] },
276
+ { form: "a < with no tag end stays text", raw: "<p>a &lt;sup 5</p>", converted: "a <sup 5", altered: [], lost: [] },
277
+ { form: "a tag never spans a blank line", raw: "<p>a &lt;span</p><p>5 x&gt;</p>", converted: "a <span\n\n5 x>", altered: [], lost: [] },
278
+ ]
279
+
280
+ // A reference definition's title may sit alone on the next line (CommonMark): its digits are link
281
+ // syntax there too. Only the title is read across the line break; text after it makes it prose.
282
+ const REF_TITLES: Row[] = [
283
+ ...[['"Fig 2019"', "double quotes"], ["'Fig 2019'", "single quotes"], ["(Fig 2019)", "parentheses"]].flatMap(([title, form]) => [
284
+ { form: `title in ${form} on the next line, indented`, raw: '<p><a href="/x" title="Fig 2019">fig</a></p>', converted: `[fig][1]\n\n[1]: /x\n ${title}`, altered: [], lost: [] },
285
+ { form: `title in ${form} on the next line, not indented`, raw: '<p><a href="/x" title="Fig 2019">fig</a></p>', converted: `[fig][1]\n\n[1]: /x\n${title}\n`, altered: [], lost: [] },
286
+ ]),
287
+ { form: "KO title on the next line", raw: '<p><a href="/x" title="\uADF8\uB9BC 2019">\uADF8\uB9BC</a></p>', converted: '[\uADF8\uB9BC][1]\n\n[1]: /x\n "\uADF8\uB9BC 2019"', altered: [], lost: [] },
288
+ { form: "two definitions, the first with its title on the next line", raw: '<p><a href="/x" title="Fig 2019">a</a> <a href="/y/7">b</a></p>', converted: '[a][1] [b][2]\n\n[1]: /x\n "Fig 2019"\n[2]: /y/7', altered: [], lost: [] },
289
+ { form: "a quoted line with text after it is prose, not a title", raw: '<p><a href="/x" title="Fig 2019">fig</a></p>', converted: '[fig][1]\n\n[1]: /x\n"Fig 2019" was cited', altered: ["2019"], lost: [] },
290
+ { form: "a next-line title digit the source does not hold is altered", raw: '<p><a href="/x" title="Fig">fig</a></p>', converted: '[fig][1]\n\n[1]: /x\n "Fig 2019"', altered: ["2019"], lost: [] },
291
+ ]
292
+
293
+ // Declared out of scope (references/fact-ledger.md): constructs outside the CommonMark inline grammar,
294
+ // the html-lite tag set and the named-entity table. Each row ASSERTS the safe-direction report, so
295
+ // the boundary is tested: the numbers are altered and `source snapshot --replace` recovers.
296
+ const OUT_OF_SCOPE: Row[] = [
297
+ { form: "custom converter syntax: a Hugo figure shortcode", raw: '<figure><img src="chart-2019.png" width="640"></figure>', converted: '{{< figure src="chart-2019.png" width="640" >}}', altered: ["2019", "640"], lost: [] },
298
+ { form: "custom converter syntax: pandoc image attributes {width=640}", raw: '<p><img src="i.png" alt="x" width="640"></p>', converted: "![x](i.png){width=640}", altered: ["640"], lost: [] },
299
+ { form: "LaTeX: \\frac{3}{4} for a vulgar fraction", raw: "<p>\u00BE of sites</p>", converted: "$\\frac{3}{4}$ of sites", altered: ["3", "4"], lost: [] },
300
+ { form: "KO LaTeX: \\frac{3}{4}", raw: "<p>\uC0AC\uC774\uD2B8\uC758 \u00BE</p>", converted: "\uC0AC\uC774\uD2B8\uC758 $\\frac{3}{4}$", altered: ["3", "4"], lost: [] },
301
+ { form: "MathJax: \\sqrt[3]{8} for a cube root", raw: "<p>\u221B8</p>", converted: "\\(\\sqrt[3]{8}\\)", altered: ["3"], lost: [] },
302
+ { form: "non-CommonMark table: AsciiDoc cols", raw: "<table><tr><td>A</td><td>B</td></tr></table>", converted: '[cols="1,3"]\n|===\n| A | B\n|===', altered: ["1,3"], lost: [] },
303
+ { form: "non-CommonMark table: a colspan cell repeated per column", raw: '<table><tr><td colspan="2">5</td></tr></table>', converted: "| 5 | 5 |", altered: ["5"], lost: [] },
304
+ { form: "footnote extension beyond pandoc's: a numbered back-reference title", raw: '<p>Margin 12%<sup><a href="#fn-a">a</a></sup></p><p id="fn-a">Filing.</p>', converted: 'Margin 12%[^a]\n\n[^a]: Filing. [\u21A9](#fnref-a "Jump back to footnote 2 in the text")', altered: ["2"], lost: [] },
305
+ { form: "reference definition with its destination on the next line", raw: '<p><a href="/x/2019">fig</a></p>', converted: "[fig][1]\n\n[1]:\n /x/2019", altered: ["2019"], lost: [] },
306
+ { form: "an unescaped & whose text spells a reference (turndown)", raw: "<p>Type &amp;minus;5 to negate</p>", converted: "Type &minus;5 to negate", altered: ["\u22125"], lost: ["5"] },
307
+ { form: "a legacy reference without its semicolon (&nbsp)", raw: "<p>1&nbsp234 567</p>", converted: "1\u00A0234 567", altered: ["1 234 567"], lost: ["1", "234 567"] },
308
+ { form: "a numeric reference padded past 6 hex digits", raw: "<p>&#x00002212;5</p>", converted: "\u22125", altered: ["\u22125"], lost: ["00002212", "5"] },
309
+ ]
310
+
311
+ // Named character references whose decoded character the probe reads as a sign or a separator (or
312
+ // as markup), decoded on the raw side as a converter decodes them: each in a sign position and a
313
+ // separator position, EN and KO, kept as the decoded character, plus the sign position with the
314
+ // character dropped (a sign is lost; any other character changes nothing).
315
+ const ENTITIES: [string, string][] = [
316
+ ["minus", "\u2212"], ["ndash", "\u2013"], ["mdash", "\u2014"], ["hyphen", "\u2010"], ["dash", "\u2010"],
317
+ ["thinsp", "\u2009"], ["ThinSpace", "\u2009"], ["numsp", "\u2007"], ["puncsp", "\u2008"], ["hairsp", "\u200A"],
318
+ ["VeryThinSpace", "\u200A"], ["ensp", "\u2002"], ["emsp", "\u2003"], ["emsp13", "\u2004"], ["emsp14", "\u2005"],
319
+ ["MediumSpace", "\u205F"], ["nbsp", "\u00A0"], ["NonBreakingSpace", "\u00A0"], ["comma", ","], ["period", "."],
320
+ ["plus", "+"], ["lowbar", "_"], ["ast", "*"], ["midast", "*"], ["grave", "`"], ["DiacriticalGrave", "`"],
321
+ ["lpar", "("], ["rpar", ")"], ["lsqb", "["], ["lbrack", "["], ["rsqb", "]"], ["rbrack", "]"],
322
+ ]
323
+ const SIGN_CHARS = new Set(["\u2212", "\u2013"])
324
+ const ENTITY_ROWS: Row[] = ENTITIES.flatMap(([name, ch]) => {
325
+ const e = `&${name};`
326
+ return [
327
+ { form: `&${name}; in a sign position`, raw: `<p>fell ${e}5 pts</p>`, converted: `fell ${ch}5 pts`, altered: [], lost: [] },
328
+ { form: `KO &${name}; in a sign position`, raw: `<p>\uAC10\uC18C${e}3%</p>`, converted: `\uAC10\uC18C${ch}3%`, altered: [], lost: [] },
329
+ { form: `&${name}; as a separator`, raw: `<p>1${e}234${e}567 people</p>`, converted: `1${ch}234${ch}567 people`, altered: [], lost: [] },
330
+ { form: `KO &${name}; as a separator`, raw: `<p>\uC778\uAD6C 1${e}234${e}567\uBA85</p>`, converted: `\uC778\uAD6C 1${ch}234${ch}567\uBA85`, altered: [], lost: [] },
331
+ { form: `&${name}; in a sign position, dropped`, raw: `<p>fell ${e}5 pts</p>`, converted: "fell 5 pts", altered: SIGN_CHARS.has(ch) ? ["5"] : [], lost: SIGN_CHARS.has(ch) ? [`${ch}5`] : [] },
332
+ { form: `KO &${name}; in a sign position, dropped`, raw: `<p>\uAC10\uC18C${e}3%</p>`, converted: "\uAC10\uC18C3%", altered: SIGN_CHARS.has(ch) ? ["3"] : [], lost: SIGN_CHARS.has(ch) ? [`${ch}3`] : [] },
333
+ { form: `&${name}; kept as the reference in a sign position`, raw: `<p>fell ${e}5 pts</p>`, converted: `fell ${e}5 pts`, altered: [], lost: [] },
334
+ { form: `KO &${name}; kept as the reference in a sign position`, raw: `<p>\uAC10\uC18C${e}3%</p>`, converted: `\uAC10\uC18C${e}3%`, altered: [], lost: [] },
335
+ { form: `&${name}; kept as the reference as a separator`, raw: `<p>1${e}234${e}567 people</p>`, converted: `1${e}234${e}567 people`, altered: [], lost: [] },
336
+ { form: `KO &${name}; kept as the reference as a separator`, raw: `<p>\uC778\uAD6C 1${e}234${e}567\uBA85</p>`, converted: `\uC778\uAD6C 1${e}234${e}567\uBA85`, altered: [], lost: [] },
337
+ { form: `&${name}; in a verbatim plain-text copy (sign position)`, raw: `fell ${e}5 pts`, converted: `fell ${e}5 pts`, altered: [], lost: [] },
338
+ { form: `&${name}; in a verbatim plain-text copy (separator)`, raw: `1${e}234${e}567 people`, converted: `1${e}234${e}567 people`, altered: [], lost: [] },
339
+ ]
340
+ })
341
+ ENTITY_ROWS.push(
342
+ { form: "reviewer r5: &nbsp;&minus; in a text with no tags", raw: "x&nbsp;&minus;3.5%", converted: "x \u22123.5%", altered: [], lost: [] },
343
+ { form: "reviewer r5: &thinsp; groups in a text with no tags", raw: "1&thinsp;234&thinsp;567", converted: "1\u2009234\u2009567", altered: [], lost: [] },
344
+ { form: "reviewer r5: &nbsp;&minus; in a paragraph", raw: "<p>x&nbsp;&minus;3.5%</p>", converted: "x\u00A0\u22123.5%", altered: [], lost: [] },
345
+ { form: "an unterminated reference in a text with no tags stays literal", raw: "a < b &amp c 5", converted: "a < b &amp c 5", altered: [], lost: [] },
346
+ { form: "reviewer r6: 'Rate &minus;5 and 1&thinsp;234' copied verbatim", raw: "Rate &minus;5 and 1&thinsp;234", converted: "Rate &minus;5 and 1&thinsp;234", altered: [], lost: [] },
347
+ { form: "KO verbatim copy with &minus; and &thinsp;", raw: "\uC99D\uAC10 &minus;5%, \uC778\uAD6C 1&thinsp;234\uBA85", converted: "\uC99D\uAC10 &minus;5%, \uC778\uAD6C 1&thinsp;234\uBA85", altered: [], lost: [] },
348
+ { form: "a sign the converted side spells as another reference is still dropped", raw: "<p>fell &minus;5 pts</p>", converted: "fell &nbsp;5 pts", altered: ["5"], lost: ["\u22125"] },
349
+ { form: "a separator spelled as a reference on the converted side still joins two numbers", raw: "<p>12</p><p>345</p>", converted: "12&thinsp;345", altered: ["12 345"], lost: ["12", "345"] },
350
+ )
351
+
352
+ describe("conversion probe classes (one row per form, EN and KO)", () => {
353
+ test.each(rows(CONVERTER_DIGITS))("converter-emitted digits: %s", (_form, row) => check(row))
354
+ test.each(rows(SIGNS))("signs and separators around numbers: %s", (_form, row) => check(row))
355
+ test.each(rows(GROUPING))("grouping boundaries: %s", (_form, row) => check(row))
356
+ test.each(rows(SIGN_MARKUP))("sign context through Markdown markup: %s", (_form, row) => check(row))
357
+ test.each(rows(ENTITY_ROWS))("named character references on both sides: %s", (_form, row) => check(row))
358
+ test.each(rows(RAW_HTML))("raw inline HTML in the converted Markdown: %s", (_form, row) => check(row))
359
+ test.each(rows(REF_TITLES))("reference definition titles on the next line: %s", (_form, row) => check(row))
360
+ test.each(rows(OUT_OF_SCOPE))("declared out of scope (reported as altered): %s", (_form, row) => check(row))
361
+ })