oh-my-opencode 5.1.47 → 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 (497) 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 -259
  64. package/dist/skills/ulw-research/references/deliverable-phase.md +68 -24
  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 +14 -6
  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 +16 -15
  77. package/dist/skills/ulw-research/scripts/contracts.test.ts +6 -109
  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-fidelity-policy.test.ts +156 -0
  84. package/dist/skills/ulw-research/scripts/copy-returns-polish.test.ts +10 -9
  85. package/dist/skills/ulw-research/scripts/css-hiding.mjs +158 -0
  86. package/dist/skills/ulw-research/scripts/css-lite.mjs +4 -2
  87. package/dist/skills/ulw-research/scripts/delivery-barrier-meaning.test.ts +186 -0
  88. package/dist/skills/ulw-research/scripts/delivery-barrier.test.ts +40 -87
  89. package/dist/skills/ulw-research/scripts/entities.mjs +13 -4
  90. package/dist/skills/ulw-research/scripts/fact-commands-fill.mjs +96 -0
  91. package/dist/skills/ulw-research/scripts/fact-commands-meaning.mjs +26 -30
  92. package/dist/skills/ulw-research/scripts/fact-commands-polish.mjs +46 -19
  93. package/dist/skills/ulw-research/scripts/fact-commands-verify.mjs +37 -6
  94. package/dist/skills/ulw-research/scripts/fact-commands.mjs +40 -62
  95. package/dist/skills/ulw-research/scripts/fact-derive-2.test.ts +149 -0
  96. package/dist/skills/ulw-research/scripts/fact-derive.mjs +7 -2
  97. package/dist/skills/ulw-research/scripts/fact-derive.test.ts +7 -192
  98. package/dist/skills/ulw-research/scripts/fact-fill.mjs +9 -6
  99. package/dist/skills/ulw-research/scripts/fact-ledger-kinds.mjs +6 -7
  100. package/dist/skills/ulw-research/scripts/fact-ledger-kinds.test.ts +2 -1
  101. package/dist/skills/ulw-research/scripts/fact-ledger-legacy.test.ts +2 -1
  102. package/dist/skills/ulw-research/scripts/fact-locate.mjs +15 -9
  103. package/dist/skills/ulw-research/scripts/fact-unit-dimensions-2.test.ts +149 -0
  104. package/dist/skills/ulw-research/scripts/fact-unit-dimensions.test.ts +4 -168
  105. package/dist/skills/ulw-research/scripts/fact-unit-rules.test.ts +3 -229
  106. package/dist/skills/ulw-research/scripts/fidelity-claims.mjs +4 -3
  107. package/dist/skills/ulw-research/scripts/fidelity-csv.mjs +32 -0
  108. package/dist/skills/ulw-research/scripts/fidelity-lexicon.mjs +1 -1
  109. package/dist/skills/ulw-research/scripts/fidelity-originals.mjs +161 -0
  110. package/dist/skills/ulw-research/scripts/fidelity-policy.mjs +183 -0
  111. package/dist/skills/ulw-research/scripts/fidelity-records.mjs +81 -14
  112. package/dist/skills/ulw-research/scripts/fidelity-snapshot-readability.mjs +49 -0
  113. package/dist/skills/ulw-research/scripts/fidelity-sources.mjs +76 -0
  114. package/dist/skills/ulw-research/scripts/fidelity-trace.mjs +25 -11
  115. package/dist/skills/ulw-research/scripts/fidelity-value-unit.mjs +202 -0
  116. package/dist/skills/ulw-research/scripts/fidelity-values.mjs +40 -39
  117. package/dist/skills/ulw-research/scripts/fidelity-values.test.ts +8 -29
  118. package/dist/skills/ulw-research/scripts/gates-fidelity.mjs +8 -6
  119. package/dist/skills/ulw-research/scripts/gates-static-headings.test.ts +17 -0
  120. package/dist/skills/ulw-research/scripts/gates-static.mjs +10 -1
  121. package/dist/skills/ulw-research/scripts/html-lite.mjs +6 -2
  122. package/dist/skills/ulw-research/scripts/html-markup.mjs +2 -2
  123. package/dist/skills/ulw-research/scripts/html-text-map.mjs +140 -0
  124. package/dist/skills/ulw-research/scripts/html-text-map.test.ts +148 -0
  125. package/dist/skills/ulw-research/scripts/markdown-closers.mjs +126 -0
  126. package/dist/skills/ulw-research/scripts/md-lite-boundaries.test.ts +46 -0
  127. package/dist/skills/ulw-research/scripts/md-lite.mjs +1 -1
  128. package/dist/skills/ulw-research/scripts/number-grammar.mjs +33 -0
  129. package/dist/skills/ulw-research/scripts/number-grammar.test.ts +36 -0
  130. package/dist/skills/ulw-research/scripts/numerals.mjs +18 -7
  131. package/dist/skills/ulw-research/scripts/numerals.test.ts +3 -0
  132. package/dist/skills/ulw-research/scripts/outcome-start.mjs +66 -0
  133. package/dist/skills/ulw-research/scripts/outcome-start.test.ts +109 -0
  134. package/dist/skills/ulw-research/scripts/outcome.mjs +11 -22
  135. package/dist/skills/ulw-research/scripts/page-source-locator.test.ts +187 -0
  136. package/dist/skills/ulw-research/scripts/placeholder.mjs +18 -6
  137. package/dist/skills/ulw-research/scripts/polish-apply.mjs +3 -2
  138. package/dist/skills/ulw-research/scripts/polish-markup.test.ts +15 -1
  139. package/dist/skills/ulw-research/scripts/polish-partial.test.ts +42 -0
  140. package/dist/skills/ulw-research/scripts/raw-decode.mjs +110 -0
  141. package/dist/skills/ulw-research/scripts/raw-locator.mjs +47 -0
  142. package/dist/skills/ulw-research/scripts/raw-snapshot.test.ts +139 -0
  143. package/dist/skills/ulw-research/scripts/report-tools-2.test.ts +233 -0
  144. package/dist/skills/ulw-research/scripts/report-tools-3.test.ts +141 -0
  145. package/dist/skills/ulw-research/scripts/report-tools-4.test.ts +135 -0
  146. package/dist/skills/ulw-research/scripts/report-tools-5.test.ts +231 -0
  147. package/dist/skills/ulw-research/scripts/report-tools-commands.mjs +1 -1
  148. package/dist/skills/ulw-research/scripts/report-tools.mjs +7 -1
  149. package/dist/skills/ulw-research/scripts/report-tools.test.ts +6 -560
  150. package/dist/skills/ulw-research/scripts/snapshot-readability.test.ts +116 -0
  151. package/dist/skills/ulw-research/scripts/source-commands.mjs +87 -29
  152. package/dist/skills/ulw-research/scripts/source-integrity.test.ts +243 -0
  153. package/dist/skills/ulw-research/scripts/source-refresh.mjs +82 -0
  154. package/dist/skills/ulw-research/scripts/source-snapshot.mjs +73 -69
  155. package/dist/skills/ulw-research/scripts/source-snapshot.test.ts +7 -4
  156. package/dist/skills/ulw-research/scripts/source-view.mjs +89 -0
  157. package/dist/skills/ulw-research/scripts/sources-citation.mjs +203 -0
  158. package/dist/skills/ulw-research/scripts/sources-citation.test.ts +236 -0
  159. package/dist/skills/ulw-research/scripts/tests/contracts-support.ts +147 -0
  160. package/dist/skills/ulw-research/scripts/tests/delivery-barrier-session.ts +59 -0
  161. package/dist/skills/ulw-research/scripts/tests/derive-clock.ts +4 -0
  162. package/dist/skills/ulw-research/scripts/tests/fact-derive-support.ts +65 -0
  163. package/dist/skills/ulw-research/scripts/tests/fact-unit-dimensions-support.ts +41 -0
  164. package/dist/skills/ulw-research/scripts/tests/fact-unit-rules-support.ts +241 -0
  165. package/dist/skills/ulw-research/scripts/tests/fidelity-values-support.ts +30 -0
  166. package/dist/skills/ulw-research/scripts/tests/fixtures/conversion-probe.html +14 -0
  167. package/dist/skills/ulw-research/scripts/tests/fixtures/sources-shapes.html +20 -0
  168. package/dist/skills/ulw-research/scripts/tests/numeral-classes.ts +196 -0
  169. package/dist/skills/ulw-research/scripts/tests/report-tools-support.ts +182 -0
  170. package/dist/skills/ulw-research/scripts/tests/value-mode-support.ts +48 -0
  171. package/dist/skills/ulw-research/scripts/units.mjs +14 -3
  172. package/dist/skills/ulw-research/scripts/units.test.ts +10 -0
  173. package/dist/skills/ulw-research/scripts/value-mode-unit-classes.test.ts +255 -0
  174. package/dist/skills/ulw-research/scripts/value-mode-unit.test.ts +179 -0
  175. package/dist/skills/ulw-research/scripts/value-rules-cli.test.ts +157 -0
  176. package/dist/skills/ulw-research/scripts/value-rules-followups.test.ts +72 -0
  177. package/dist/skills/ulw-research/scripts/value-rules-priority.test.ts +80 -0
  178. package/dist/tools/delegate-task/executor-types.d.ts +1 -0
  179. package/dist/tools/delegate-task/sync-session-existence.d.ts +4 -0
  180. package/dist/tools/delegate-task/types.d.ts +1 -0
  181. package/dist/tools/skill/description-formatter.d.ts +2 -1
  182. package/dist/tools/skill/types.d.ts +6 -0
  183. package/dist/tui.js +131 -33
  184. package/package.json +18 -15
  185. package/packages/git-bash-mcp/package.json +1 -1
  186. package/packages/omo-codex/plugin/.codex-plugin/plugin.json +1 -1
  187. package/packages/omo-codex/plugin/README.md +1 -1
  188. package/packages/omo-codex/plugin/components/bootstrap/hooks/hooks.json +1 -1
  189. package/packages/omo-codex/plugin/components/bootstrap/package.json +1 -1
  190. package/packages/omo-codex/plugin/components/comment-checker/hooks/hooks.json +1 -1
  191. package/packages/omo-codex/plugin/components/comment-checker/package.json +1 -1
  192. package/packages/omo-codex/plugin/components/git-bash/hooks/hooks.json +2 -2
  193. package/packages/omo-codex/plugin/components/git-bash/package.json +1 -1
  194. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/hooks/hooks.json +1 -1
  195. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/package.json +1 -1
  196. package/packages/omo-codex/plugin/components/lcx/skills/lcx-contribute-bug-fix/SKILL.md +51 -55
  197. package/packages/omo-codex/plugin/components/lcx/skills/lcx-contribute-bug-fix/agents/openai.yaml +1 -1
  198. package/packages/omo-codex/plugin/components/lcx/skills/lcx-report-bug/SKILL.md +7 -7
  199. package/packages/omo-codex/plugin/components/lcx/skills/lcx-report-bug/agents/openai.yaml +1 -1
  200. package/packages/omo-codex/plugin/components/lsp/dist/.omo-runtime-manifest.json +2 -2
  201. package/packages/omo-codex/plugin/components/lsp/hooks/hooks.json +2 -2
  202. package/packages/omo-codex/plugin/components/lsp/package.json +1 -1
  203. package/packages/omo-codex/plugin/components/rules/hooks/hooks.json +4 -4
  204. package/packages/omo-codex/plugin/components/rules/package.json +1 -1
  205. package/packages/omo-codex/plugin/components/teammode/hooks/hooks.json +1 -1
  206. package/packages/omo-codex/plugin/components/teammode/package.json +2 -2
  207. package/packages/omo-codex/plugin/components/telemetry/README.md +2 -2
  208. package/packages/omo-codex/plugin/components/telemetry/dist/cli.js +52 -15
  209. package/packages/omo-codex/plugin/components/telemetry/dist/posthog.js +52 -19
  210. package/packages/omo-codex/plugin/components/telemetry/hooks/hooks.json +1 -1
  211. package/packages/omo-codex/plugin/components/telemetry/package.json +1 -1
  212. package/packages/omo-codex/plugin/components/telemetry/src/codex-hook.ts +2 -9
  213. package/packages/omo-codex/plugin/components/telemetry/src/posthog.ts +4 -13
  214. package/packages/omo-codex/plugin/components/telemetry/src/product-identity.ts +0 -2
  215. package/packages/omo-codex/plugin/components/telemetry/test/codex-hook-diagnostics.test.ts +0 -3
  216. package/packages/omo-codex/plugin/components/telemetry/test/codex-hook.test.ts +3 -8
  217. package/packages/omo-codex/plugin/components/ultrawork/dist/cli.js +3 -0
  218. package/packages/omo-codex/plugin/components/ultrawork/hooks/hooks.json +1 -1
  219. package/packages/omo-codex/plugin/components/ultrawork/package.json +1 -1
  220. package/packages/omo-codex/plugin/components/ultrawork/src/directive-content.ts +1 -1
  221. package/packages/omo-codex/plugin/components/ulw-execute-continuation/hooks/hooks.json +1 -1
  222. package/packages/omo-codex/plugin/components/ulw-execute-continuation/package.json +1 -1
  223. package/packages/omo-codex/plugin/components/ulw-loop/directive.md +3 -0
  224. package/packages/omo-codex/plugin/components/ulw-loop/hooks/hooks.json +5 -5
  225. package/packages/omo-codex/plugin/components/ulw-loop/package.json +1 -1
  226. package/packages/omo-codex/plugin/hooks/post-compact-resetting-git-bash-mcp-reminder.json +1 -1
  227. package/packages/omo-codex/plugin/hooks/post-compact-resetting-lsp-diagnostics-cache.json +1 -1
  228. package/packages/omo-codex/plugin/hooks/post-compact-resetting-project-rule-cache.json +1 -1
  229. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-comments.json +1 -1
  230. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-lsp-diagnostics.json +1 -1
  231. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-thread-title-hygiene.json +1 -1
  232. package/packages/omo-codex/plugin/hooks/post-tool-use-matching-project-rules.json +1 -1
  233. package/packages/omo-codex/plugin/hooks/post-tool-use-recording-spawn-admission.json +1 -1
  234. package/packages/omo-codex/plugin/hooks/pre-tool-use-enforcing-unlimited-goal-budget.json +1 -1
  235. package/packages/omo-codex/plugin/hooks/pre-tool-use-guarding-ulw-loop-spawns.json +1 -1
  236. package/packages/omo-codex/plugin/hooks/pre-tool-use-recommending-git-bash-mcp.json +1 -1
  237. package/packages/omo-codex/plugin/hooks/session-start-checking-auto-update.json +1 -1
  238. package/packages/omo-codex/plugin/hooks/session-start-checking-bootstrap-provisioning.json +1 -1
  239. package/packages/omo-codex/plugin/hooks/session-start-loading-project-rules.json +1 -1
  240. package/packages/omo-codex/plugin/hooks/session-start-recording-session-telemetry.json +1 -1
  241. package/packages/omo-codex/plugin/hooks/stop-checking-ulw-execute-continuation.json +1 -1
  242. package/packages/omo-codex/plugin/hooks/stop-checking-ulw-loop-resume.json +1 -1
  243. package/packages/omo-codex/plugin/hooks/subagent-stop-verifying-lazycodex-executor-evidence.json +1 -1
  244. package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ultrawork-trigger.json +1 -1
  245. package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ulw-loop-steering.json +1 -1
  246. package/packages/omo-codex/plugin/hooks/user-prompt-submit-loading-project-rules.json +1 -1
  247. package/packages/omo-codex/plugin/package-lock.json +16 -16
  248. package/packages/omo-codex/plugin/package.json +1 -1
  249. package/packages/omo-codex/plugin/shared/package.json +1 -1
  250. package/packages/omo-codex/plugin/skills/ast-grep/README.md +1 -1
  251. package/packages/omo-codex/plugin/skills/ast-grep/install.ps1 +5 -3
  252. package/packages/omo-codex/plugin/skills/ast-grep/install.sh +4 -5
  253. package/packages/omo-codex/plugin/skills/ast-grep/references/install.md +2 -2
  254. package/packages/omo-codex/plugin/skills/ast-grep/scripts/ast_grep_helper.py +13 -3
  255. package/packages/omo-codex/plugin/skills/browser/runtime/omowright/manifest.json +1 -1
  256. package/packages/omo-codex/plugin/skills/coding-agent-sessions/AGENTS.md +5 -4
  257. package/packages/omo-codex/plugin/skills/coding-agent-sessions/SKILL.md +11 -11
  258. package/packages/omo-codex/plugin/skills/coding-agent-sessions/references/all-platforms.md +2 -2
  259. package/packages/omo-codex/plugin/skills/coding-agent-sessions/references/codex.md +4 -4
  260. package/packages/omo-codex/plugin/skills/coding-agent-sessions/scripts/agent_sessions/cli.py +1 -1
  261. package/packages/omo-codex/plugin/skills/coding-agent-sessions/scripts/find-agent-sessions.py +6 -3
  262. package/packages/omo-codex/plugin/skills/frontend/ATTRIBUTION.md +1 -1
  263. package/packages/omo-codex/plugin/skills/frontend/SKILL.md +34 -12
  264. package/packages/omo-codex/plugin/skills/frontend/references/design/README.md +2 -2
  265. package/packages/omo-codex/plugin/skills/frontend/references/design/_INDEX.md +2 -1
  266. package/packages/omo-codex/plugin/skills/frontend/references/design/component-catalogs.md +14 -10
  267. package/packages/omo-codex/plugin/skills/frontend/references/design/design-system-architecture.md +19 -6
  268. package/packages/omo-codex/plugin/skills/frontend/references/design/grounding.md +29 -0
  269. package/packages/omo-codex/plugin/skills/frontend/references/ui-ux-db/scripts/design_system.py +2 -0
  270. package/packages/omo-codex/plugin/skills/frontend/references/ui-ux-db/scripts/search.py +2 -0
  271. package/packages/omo-codex/plugin/skills/lcx-contribute-bug-fix/SKILL.md +51 -55
  272. package/packages/omo-codex/plugin/skills/lcx-contribute-bug-fix/agents/openai.yaml +1 -1
  273. package/packages/omo-codex/plugin/skills/lcx-report-bug/SKILL.md +7 -7
  274. package/packages/omo-codex/plugin/skills/lcx-report-bug/agents/openai.yaml +1 -1
  275. package/packages/omo-codex/plugin/skills/ultimate-browsing/SKILL.md +5 -4
  276. package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/AGENTS.md +2 -2
  277. package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/__main__.py +9 -6
  278. package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/summary.py +1 -1
  279. package/packages/omo-codex/plugin/skills/ultimate-browsing/references/insane-search/README.md +4 -4
  280. package/packages/omo-codex/plugin/skills/ultimate-browsing/scripts/extract_cookies.py +4 -2
  281. package/packages/omo-codex/plugin/skills/ultrawork/SKILL.md +3 -0
  282. package/packages/omo-codex/plugin/skills/ulw-research/AGENTS.md +8 -4
  283. package/packages/omo-codex/plugin/skills/ulw-research/SKILL.md +7 -259
  284. package/packages/omo-codex/plugin/skills/ulw-research/references/deliverable-phase.md +68 -24
  285. package/packages/omo-codex/plugin/skills/ulw-research/references/fact-ledger-grammar.md +7 -3
  286. package/packages/omo-codex/plugin/skills/ulw-research/references/fact-ledger.md +42 -8
  287. package/packages/omo-codex/plugin/skills/ulw-research/references/fidelity-claim-kinds.md +1 -1
  288. package/packages/omo-codex/plugin/skills/ulw-research/references/markdown-blocks.md +12 -0
  289. package/packages/omo-codex/plugin/skills/ulw-research/references/report-gates.md +14 -6
  290. package/packages/omo-codex/plugin/skills/ulw-research/references/research-collection.md +195 -0
  291. package/packages/omo-codex/plugin/skills/ulw-research/references/research-delivery.md +123 -0
  292. package/packages/omo-codex/plugin/skills/ulw-research/scripts/claim-kinds.mjs +33 -5
  293. package/packages/omo-codex/plugin/skills/ulw-research/scripts/contracts.mjs +16 -15
  294. package/packages/omo-codex/plugin/skills/ulw-research/scripts/conversion-probe.mjs +229 -0
  295. package/packages/omo-codex/plugin/skills/ulw-research/scripts/css-hiding.mjs +158 -0
  296. package/packages/omo-codex/plugin/skills/ulw-research/scripts/css-lite.mjs +4 -2
  297. package/packages/omo-codex/plugin/skills/ulw-research/scripts/entities.mjs +13 -4
  298. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-commands-fill.mjs +96 -0
  299. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-commands-meaning.mjs +26 -30
  300. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-commands-polish.mjs +46 -19
  301. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-commands-verify.mjs +37 -6
  302. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-commands.mjs +40 -62
  303. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-derive.mjs +7 -2
  304. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-fill.mjs +9 -6
  305. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-ledger-kinds.mjs +6 -7
  306. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-locate.mjs +15 -9
  307. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-claims.mjs +4 -3
  308. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-csv.mjs +32 -0
  309. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-lexicon.mjs +1 -1
  310. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-originals.mjs +161 -0
  311. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-policy.mjs +183 -0
  312. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-records.mjs +81 -14
  313. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-snapshot-readability.mjs +49 -0
  314. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-sources.mjs +76 -0
  315. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-trace.mjs +25 -11
  316. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-value-unit.mjs +202 -0
  317. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-values.mjs +40 -39
  318. package/packages/omo-codex/plugin/skills/ulw-research/scripts/gates-fidelity.mjs +8 -6
  319. package/packages/omo-codex/plugin/skills/ulw-research/scripts/gates-static.mjs +10 -1
  320. package/packages/omo-codex/plugin/skills/ulw-research/scripts/html-lite.mjs +6 -2
  321. package/packages/omo-codex/plugin/skills/ulw-research/scripts/html-markup.mjs +2 -2
  322. package/packages/omo-codex/plugin/skills/ulw-research/scripts/html-text-map.mjs +140 -0
  323. package/packages/omo-codex/plugin/skills/ulw-research/scripts/markdown-closers.mjs +126 -0
  324. package/packages/omo-codex/plugin/skills/ulw-research/scripts/md-lite.mjs +1 -1
  325. package/packages/omo-codex/plugin/skills/ulw-research/scripts/number-grammar.mjs +33 -0
  326. package/packages/omo-codex/plugin/skills/ulw-research/scripts/numerals.mjs +18 -7
  327. package/packages/omo-codex/plugin/skills/ulw-research/scripts/outcome-start.mjs +66 -0
  328. package/packages/omo-codex/plugin/skills/ulw-research/scripts/outcome.mjs +11 -22
  329. package/packages/omo-codex/plugin/skills/ulw-research/scripts/placeholder.mjs +18 -6
  330. package/packages/omo-codex/plugin/skills/ulw-research/scripts/polish-apply.mjs +3 -2
  331. package/packages/omo-codex/plugin/skills/ulw-research/scripts/raw-decode.mjs +110 -0
  332. package/packages/omo-codex/plugin/skills/ulw-research/scripts/raw-locator.mjs +47 -0
  333. package/packages/omo-codex/plugin/skills/ulw-research/scripts/report-tools-commands.mjs +1 -1
  334. package/packages/omo-codex/plugin/skills/ulw-research/scripts/report-tools.mjs +7 -1
  335. package/packages/omo-codex/plugin/skills/ulw-research/scripts/source-commands.mjs +87 -29
  336. package/packages/omo-codex/plugin/skills/ulw-research/scripts/source-refresh.mjs +82 -0
  337. package/packages/omo-codex/plugin/skills/ulw-research/scripts/source-snapshot.mjs +73 -69
  338. package/packages/omo-codex/plugin/skills/ulw-research/scripts/source-view.mjs +89 -0
  339. package/packages/omo-codex/plugin/skills/ulw-research/scripts/sources-citation.mjs +203 -0
  340. package/packages/omo-codex/plugin/skills/ulw-research/scripts/units.mjs +14 -3
  341. package/packages/omo-codex/plugin/test/payload-equivalence.test.mjs +6 -4
  342. package/packages/omo-codex/plugin/test/sync-skills.test.mjs +4 -3
  343. package/packages/omo-codex/scripts/install-dist/install-local.mjs +338 -237
  344. package/packages/prompts-core/prompts/ultrawork/codex.md +3 -0
  345. package/packages/shared-skills/skill-source-filter.mjs +6 -2
  346. package/packages/shared-skills/skills/ast-grep/README.md +1 -1
  347. package/packages/shared-skills/skills/ast-grep/install.ps1 +5 -3
  348. package/packages/shared-skills/skills/ast-grep/install.sh +4 -5
  349. package/packages/shared-skills/skills/ast-grep/references/install.md +2 -2
  350. package/packages/shared-skills/skills/ast-grep/scripts/ast_grep_helper.py +13 -3
  351. package/packages/shared-skills/skills/browser/runtime/omowright/manifest.json +1 -1
  352. package/packages/shared-skills/skills/coding-agent-sessions/AGENTS.md +5 -4
  353. package/packages/shared-skills/skills/coding-agent-sessions/SKILL.md +11 -11
  354. package/packages/shared-skills/skills/coding-agent-sessions/references/all-platforms.md +2 -2
  355. package/packages/shared-skills/skills/coding-agent-sessions/references/codex.md +4 -4
  356. package/packages/shared-skills/skills/coding-agent-sessions/scripts/agent_sessions/cli.py +1 -1
  357. package/packages/shared-skills/skills/coding-agent-sessions/scripts/find-agent-sessions.py +6 -3
  358. package/packages/shared-skills/skills/frontend/ATTRIBUTION.md +1 -1
  359. package/packages/shared-skills/skills/frontend/SKILL.md +34 -12
  360. package/packages/shared-skills/skills/frontend/references/design/README.md +2 -2
  361. package/packages/shared-skills/skills/frontend/references/design/_INDEX.md +2 -1
  362. package/packages/shared-skills/skills/frontend/references/design/component-catalogs.md +14 -10
  363. package/packages/shared-skills/skills/frontend/references/design/design-system-architecture.md +19 -6
  364. package/packages/shared-skills/skills/frontend/references/design/grounding.md +29 -0
  365. package/packages/shared-skills/skills/frontend/references/ui-ux-db/scripts/design_system.py +2 -0
  366. package/packages/shared-skills/skills/frontend/references/ui-ux-db/scripts/search.py +2 -0
  367. package/packages/shared-skills/skills/ultimate-browsing/SKILL.md +5 -4
  368. package/packages/shared-skills/skills/ultimate-browsing/engine/AGENTS.md +2 -2
  369. package/packages/shared-skills/skills/ultimate-browsing/engine/__main__.py +9 -6
  370. package/packages/shared-skills/skills/ultimate-browsing/engine/summary.py +1 -1
  371. package/packages/shared-skills/skills/ultimate-browsing/references/insane-search/README.md +4 -4
  372. package/packages/shared-skills/skills/ultimate-browsing/scripts/extract_cookies.py +4 -2
  373. package/packages/shared-skills/skills/ulw-research/AGENTS.md +8 -4
  374. package/packages/shared-skills/skills/ulw-research/SKILL.md +7 -259
  375. package/packages/shared-skills/skills/ulw-research/references/deliverable-phase.md +68 -24
  376. package/packages/shared-skills/skills/ulw-research/references/fact-ledger-grammar.md +7 -3
  377. package/packages/shared-skills/skills/ulw-research/references/fact-ledger.md +42 -8
  378. package/packages/shared-skills/skills/ulw-research/references/fidelity-claim-kinds.md +1 -1
  379. package/packages/shared-skills/skills/ulw-research/references/markdown-blocks.md +12 -0
  380. package/packages/shared-skills/skills/ulw-research/references/report-gates.md +14 -6
  381. package/packages/shared-skills/skills/ulw-research/references/research-collection.md +195 -0
  382. package/packages/shared-skills/skills/ulw-research/references/research-delivery.md +123 -0
  383. package/packages/shared-skills/skills/ulw-research/scripts/claim-kinds.mjs +33 -5
  384. package/packages/shared-skills/skills/ulw-research/scripts/claim-kinds.test.ts +37 -2
  385. package/packages/shared-skills/skills/ulw-research/scripts/claim-prefix.test.ts +76 -0
  386. package/packages/shared-skills/skills/ulw-research/scripts/contracts-validation.test.ts +83 -0
  387. package/packages/shared-skills/skills/ulw-research/scripts/contracts.mjs +16 -15
  388. package/packages/shared-skills/skills/ulw-research/scripts/contracts.test.ts +6 -109
  389. package/packages/shared-skills/skills/ulw-research/scripts/conversion-altered.test.ts +154 -0
  390. package/packages/shared-skills/skills/ulw-research/scripts/conversion-originals.test.ts +297 -0
  391. package/packages/shared-skills/skills/ulw-research/scripts/conversion-probe-classes.test.ts +361 -0
  392. package/packages/shared-skills/skills/ulw-research/scripts/conversion-probe.mjs +229 -0
  393. package/packages/shared-skills/skills/ulw-research/scripts/conversion-probe.test.ts +218 -0
  394. package/packages/shared-skills/skills/ulw-research/scripts/copy-returns-fidelity-policy.test.ts +156 -0
  395. package/packages/shared-skills/skills/ulw-research/scripts/copy-returns-polish.test.ts +10 -9
  396. package/packages/shared-skills/skills/ulw-research/scripts/css-hiding.mjs +158 -0
  397. package/packages/shared-skills/skills/ulw-research/scripts/css-lite.mjs +4 -2
  398. package/packages/shared-skills/skills/ulw-research/scripts/delivery-barrier-meaning.test.ts +186 -0
  399. package/packages/shared-skills/skills/ulw-research/scripts/delivery-barrier.test.ts +40 -87
  400. package/packages/shared-skills/skills/ulw-research/scripts/entities.mjs +13 -4
  401. package/packages/shared-skills/skills/ulw-research/scripts/fact-commands-fill.mjs +96 -0
  402. package/packages/shared-skills/skills/ulw-research/scripts/fact-commands-meaning.mjs +26 -30
  403. package/packages/shared-skills/skills/ulw-research/scripts/fact-commands-polish.mjs +46 -19
  404. package/packages/shared-skills/skills/ulw-research/scripts/fact-commands-verify.mjs +37 -6
  405. package/packages/shared-skills/skills/ulw-research/scripts/fact-commands.mjs +40 -62
  406. package/packages/shared-skills/skills/ulw-research/scripts/fact-derive-2.test.ts +149 -0
  407. package/packages/shared-skills/skills/ulw-research/scripts/fact-derive.mjs +7 -2
  408. package/packages/shared-skills/skills/ulw-research/scripts/fact-derive.test.ts +7 -192
  409. package/packages/shared-skills/skills/ulw-research/scripts/fact-fill.mjs +9 -6
  410. package/packages/shared-skills/skills/ulw-research/scripts/fact-ledger-kinds.mjs +6 -7
  411. package/packages/shared-skills/skills/ulw-research/scripts/fact-ledger-kinds.test.ts +2 -1
  412. package/packages/shared-skills/skills/ulw-research/scripts/fact-ledger-legacy.test.ts +2 -1
  413. package/packages/shared-skills/skills/ulw-research/scripts/fact-locate.mjs +15 -9
  414. package/packages/shared-skills/skills/ulw-research/scripts/fact-unit-dimensions-2.test.ts +149 -0
  415. package/packages/shared-skills/skills/ulw-research/scripts/fact-unit-dimensions.test.ts +4 -168
  416. package/packages/shared-skills/skills/ulw-research/scripts/fact-unit-rules.test.ts +3 -229
  417. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-claims.mjs +4 -3
  418. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-csv.mjs +32 -0
  419. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-lexicon.mjs +1 -1
  420. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-originals.mjs +161 -0
  421. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-policy.mjs +183 -0
  422. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-records.mjs +81 -14
  423. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-snapshot-readability.mjs +49 -0
  424. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-sources.mjs +76 -0
  425. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-trace.mjs +25 -11
  426. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-value-unit.mjs +202 -0
  427. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-values.mjs +40 -39
  428. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-values.test.ts +8 -29
  429. package/packages/shared-skills/skills/ulw-research/scripts/gates-fidelity.mjs +8 -6
  430. package/packages/shared-skills/skills/ulw-research/scripts/gates-static-headings.test.ts +17 -0
  431. package/packages/shared-skills/skills/ulw-research/scripts/gates-static.mjs +10 -1
  432. package/packages/shared-skills/skills/ulw-research/scripts/html-lite.mjs +6 -2
  433. package/packages/shared-skills/skills/ulw-research/scripts/html-markup.mjs +2 -2
  434. package/packages/shared-skills/skills/ulw-research/scripts/html-text-map.mjs +140 -0
  435. package/packages/shared-skills/skills/ulw-research/scripts/html-text-map.test.ts +148 -0
  436. package/packages/shared-skills/skills/ulw-research/scripts/markdown-closers.mjs +126 -0
  437. package/packages/shared-skills/skills/ulw-research/scripts/md-lite-boundaries.test.ts +46 -0
  438. package/packages/shared-skills/skills/ulw-research/scripts/md-lite.mjs +1 -1
  439. package/packages/shared-skills/skills/ulw-research/scripts/number-grammar.mjs +33 -0
  440. package/packages/shared-skills/skills/ulw-research/scripts/number-grammar.test.ts +36 -0
  441. package/packages/shared-skills/skills/ulw-research/scripts/numerals.mjs +18 -7
  442. package/packages/shared-skills/skills/ulw-research/scripts/numerals.test.ts +3 -0
  443. package/packages/shared-skills/skills/ulw-research/scripts/outcome-start.mjs +66 -0
  444. package/packages/shared-skills/skills/ulw-research/scripts/outcome-start.test.ts +109 -0
  445. package/packages/shared-skills/skills/ulw-research/scripts/outcome.mjs +11 -22
  446. package/packages/shared-skills/skills/ulw-research/scripts/page-source-locator.test.ts +187 -0
  447. package/packages/shared-skills/skills/ulw-research/scripts/placeholder.mjs +18 -6
  448. package/packages/shared-skills/skills/ulw-research/scripts/polish-apply.mjs +3 -2
  449. package/packages/shared-skills/skills/ulw-research/scripts/polish-markup.test.ts +15 -1
  450. package/packages/shared-skills/skills/ulw-research/scripts/polish-partial.test.ts +42 -0
  451. package/packages/shared-skills/skills/ulw-research/scripts/raw-decode.mjs +110 -0
  452. package/packages/shared-skills/skills/ulw-research/scripts/raw-locator.mjs +47 -0
  453. package/packages/shared-skills/skills/ulw-research/scripts/raw-snapshot.test.ts +139 -0
  454. package/packages/shared-skills/skills/ulw-research/scripts/report-tools-2.test.ts +233 -0
  455. package/packages/shared-skills/skills/ulw-research/scripts/report-tools-3.test.ts +141 -0
  456. package/packages/shared-skills/skills/ulw-research/scripts/report-tools-4.test.ts +135 -0
  457. package/packages/shared-skills/skills/ulw-research/scripts/report-tools-5.test.ts +231 -0
  458. package/packages/shared-skills/skills/ulw-research/scripts/report-tools-commands.mjs +1 -1
  459. package/packages/shared-skills/skills/ulw-research/scripts/report-tools.mjs +7 -1
  460. package/packages/shared-skills/skills/ulw-research/scripts/report-tools.test.ts +6 -560
  461. package/packages/shared-skills/skills/ulw-research/scripts/snapshot-readability.test.ts +116 -0
  462. package/packages/shared-skills/skills/ulw-research/scripts/source-commands.mjs +87 -29
  463. package/packages/shared-skills/skills/ulw-research/scripts/source-integrity.test.ts +243 -0
  464. package/packages/shared-skills/skills/ulw-research/scripts/source-refresh.mjs +82 -0
  465. package/packages/shared-skills/skills/ulw-research/scripts/source-snapshot.mjs +73 -69
  466. package/packages/shared-skills/skills/ulw-research/scripts/source-snapshot.test.ts +7 -4
  467. package/packages/shared-skills/skills/ulw-research/scripts/source-view.mjs +89 -0
  468. package/packages/shared-skills/skills/ulw-research/scripts/sources-citation.mjs +203 -0
  469. package/packages/shared-skills/skills/ulw-research/scripts/sources-citation.test.ts +236 -0
  470. package/packages/shared-skills/skills/ulw-research/scripts/tests/contracts-support.ts +147 -0
  471. package/packages/shared-skills/skills/ulw-research/scripts/tests/delivery-barrier-session.ts +59 -0
  472. package/packages/shared-skills/skills/ulw-research/scripts/tests/derive-clock.ts +4 -0
  473. package/packages/shared-skills/skills/ulw-research/scripts/tests/fact-derive-support.ts +65 -0
  474. package/packages/shared-skills/skills/ulw-research/scripts/tests/fact-unit-dimensions-support.ts +41 -0
  475. package/packages/shared-skills/skills/ulw-research/scripts/tests/fact-unit-rules-support.ts +241 -0
  476. package/packages/shared-skills/skills/ulw-research/scripts/tests/fidelity-values-support.ts +30 -0
  477. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/conversion-probe.html +14 -0
  478. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/sources-shapes.html +20 -0
  479. package/packages/shared-skills/skills/ulw-research/scripts/tests/numeral-classes.ts +196 -0
  480. package/packages/shared-skills/skills/ulw-research/scripts/tests/report-tools-support.ts +182 -0
  481. package/packages/shared-skills/skills/ulw-research/scripts/tests/value-mode-support.ts +48 -0
  482. package/packages/shared-skills/skills/ulw-research/scripts/units.mjs +14 -3
  483. package/packages/shared-skills/skills/ulw-research/scripts/units.test.ts +10 -0
  484. package/packages/shared-skills/skills/ulw-research/scripts/value-mode-unit-classes.test.ts +255 -0
  485. package/packages/shared-skills/skills/ulw-research/scripts/value-mode-unit.test.ts +179 -0
  486. package/packages/shared-skills/skills/ulw-research/scripts/value-rules-cli.test.ts +157 -0
  487. package/packages/shared-skills/skills/ulw-research/scripts/value-rules-followups.test.ts +72 -0
  488. package/packages/shared-skills/skills/ulw-research/scripts/value-rules-priority.test.ts +80 -0
  489. package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/tests/fixtures/amp_redirect_stub.html +0 -7
  490. package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/tests/fixtures/search_interstitial.html +0 -19
  491. package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/tests/fixtures/wayback_available.json +0 -1
  492. package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/tests/fixtures/wayback_snapshot.html +0 -1128
  493. package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/tests/test_fetch_chain.py +0 -77
  494. package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/tests/test_playwright_stealth.py +0 -86
  495. package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/tests/test_playwright_templates.py +0 -279
  496. package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/tests/test_surrogate.py +0 -252
  497. package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/tests/test_surrogate_validators.py +0 -78
@@ -0,0 +1,140 @@
1
+ // html-text-map.mjs - the reader-visible text of a page, with a source map back into the markup.
2
+ //
3
+ // extractHtml(html) returns { root, text, blocks, starts, ends }: `text` is one line per block
4
+ // (prose blocks and table cells; whitespace collapsed, entities decoded) and text unit k came from
5
+ // html.slice(starts[k], ends[k]). Only text nodes are read: attribute values, CSS, scripts, and
6
+ // anything a reader never sees (head, script, style, svg, noscript, template, code-like elements,
7
+ // aria-hidden="true" and their descendants, and what the page's CSS hides: css-hiding.mjs)
8
+ // contribute nothing, so a locator over `text` can never land in markup. A synthetic separator
9
+ // (collapsed break, line feed) maps to an empty range at the previous character's end.
10
+ // Plain Node ESM, sibling imports only.
11
+
12
+ import { cssVisibility } from "./css-hiding.mjs"
13
+ import { decodeOne } from "./entities.mjs"
14
+ import { parseHtml, PROSE_BREAKS, PROSE_TAGS } from "./html-lite.mjs"
15
+
16
+ const SKIP = new Set(["head", "script", "style", "svg", "code", "pre", "kbd", "samp", "template", "noscript"])
17
+ const INLINE = new Set([
18
+ "a", "abbr", "b", "bdi", "bdo", "cite", "data", "del", "dfn", "em", "font", "i", "ins", "label", "mark", "q", "s",
19
+ "small", "span", "strong", "sub", "sup", "time", "u", "var",
20
+ ])
21
+ const SPACE = /\s/
22
+
23
+ /** null when a reader never sees the element or anything inside it, else whether its own text shows (css: cssVisibility). */
24
+ function shownState(node, css, parentShown) {
25
+ if (SKIP.has(node.tag) || String(node.attrs?.["aria-hidden"] ?? "").trim().toLowerCase() === "true") return null
26
+ return css(node, parentShown)
27
+ }
28
+
29
+ function newLine() {
30
+ return { node: null, text: "", starts: [], ends: [], space: null }
31
+ }
32
+
33
+ function put(line, unit, start, end) {
34
+ if (SPACE.test(unit)) {
35
+ if (line.text !== "" && line.space === null) line.space = [start, end]
36
+ return
37
+ }
38
+ if (line.space !== null) {
39
+ line.text += " "
40
+ line.starts.push(line.space[0])
41
+ line.ends.push(line.space[1])
42
+ line.space = null
43
+ }
44
+ line.text += unit
45
+ line.starts.push(start)
46
+ line.ends.push(end)
47
+ }
48
+
49
+ /** A collapsible break with no source character of its own. */
50
+ function softBreak(line) {
51
+ if (line.text !== "" && line.space === null) {
52
+ const at = line.ends[line.ends.length - 1]
53
+ line.space = [at, at]
54
+ }
55
+ }
56
+
57
+ /** Append a text node's source range, decoding entities exactly as decodeEntities does. */
58
+ function putText(line, src, from, to) {
59
+ for (let at = from; at < to; ) {
60
+ if (src[at] === "&") {
61
+ const semi = src.indexOf(";", at + 1)
62
+ const decoded = semi !== -1 && semi < to && semi - at <= 12 ? decodeOne(src.slice(at + 1, semi)) : null
63
+ if (decoded !== null) {
64
+ for (let k = 0; k < decoded.length; k++) put(line, decoded[k], at, semi + 1)
65
+ at = semi + 1
66
+ continue
67
+ }
68
+ }
69
+ put(line, src[at], at, at + 1)
70
+ at++
71
+ }
72
+ }
73
+
74
+ function putProse(line, src, node, css, shown) {
75
+ for (const child of node.children) {
76
+ if (child.tag === "#text") {
77
+ if (shown) putText(line, src, child.source_start, child.source_end)
78
+ continue
79
+ }
80
+ const state = shownState(child, css, shown)
81
+ if (state === null) continue
82
+ const breaks = PROSE_BREAKS.has(child.tag)
83
+ if (breaks) softBreak(line)
84
+ putProse(line, src, child, css, state)
85
+ if (breaks) softBreak(line)
86
+ }
87
+ }
88
+
89
+ /** See the file comment. blocks: [{ node, start, end }] with text.slice(start, end) one block line. */
90
+ export function extractHtml(html) {
91
+ const src = String(html)
92
+ const root = parseHtml(src)
93
+ const css = cssVisibility(root)
94
+ const lines = []
95
+ let line = newLine()
96
+ const flush = (node) => {
97
+ if (line.text !== "") lines.push({ ...line, node })
98
+ line = newLine()
99
+ }
100
+ const walk = (node, shown) => {
101
+ for (const child of node.children) {
102
+ if (child.tag === "#text") {
103
+ if (shown) putText(line, src, child.source_start, child.source_end)
104
+ continue
105
+ }
106
+ const state = shownState(child, css, shown)
107
+ if (state === null) continue
108
+ if (child.tag === "br") flush(node)
109
+ else if (INLINE.has(child.tag)) walk(child, state)
110
+ else if (PROSE_TAGS.has(child.tag)) {
111
+ flush(node)
112
+ putProse(line, src, child, css, state)
113
+ flush(child)
114
+ } else {
115
+ flush(node)
116
+ walk(child, state)
117
+ flush(child)
118
+ }
119
+ }
120
+ }
121
+ walk(root, true)
122
+ flush(root)
123
+ const blocks = []
124
+ const starts = []
125
+ const ends = []
126
+ let text = ""
127
+ for (const entry of lines) {
128
+ if (text !== "") {
129
+ const at = ends[ends.length - 1]
130
+ text += "\n"
131
+ starts.push(at)
132
+ ends.push(at)
133
+ }
134
+ blocks.push({ node: entry.node, start: text.length, end: text.length + entry.text.length })
135
+ text += entry.text
136
+ starts.push(...entry.starts)
137
+ ends.push(...entry.ends)
138
+ }
139
+ return { root, text, blocks, starts, ends }
140
+ }
@@ -0,0 +1,148 @@
1
+ import { describe, expect, test } from "bun:test"
2
+
3
+ import { extractHtml } from "./html-text-map.mjs"
4
+
5
+ /** {T} is the target sentence: every row runs once with English text and once with Korean text. */
6
+ const TARGET = { en: "Target length is 999 m.", ko: "\ubaa9\ud45c \uae38\uc774\ub294 999 m\uc774\ub2e4." }
7
+ const CONTROL = "Control length is 410 m."
8
+
9
+ type Row = { form: string; html: string; expected: "hidden" | "visible" }
10
+
11
+ const css = (sheet: string, element = '<p class="x">{T}</p>') => `<style>${sheet}</style>${element}`
12
+
13
+ const ROWS: Row[] = [
14
+ // conditional and grouping at-rules: only an unconditional rule hides; a conditional one never does
15
+ { form: "@media (max-width:600px)", html: css("@media (max-width:600px){.x{display:none}}"), expected: "visible" },
16
+ { form: "@media print", html: css("@media print{.x{display:none}}"), expected: "visible" },
17
+ { form: "@media screen and (min-width:0)", html: css("@media screen and (min-width: 0){.x{display:none}}"), expected: "visible" },
18
+ { form: "@media not print", html: css("@media not print{.x{display:none}}"), expected: "visible" },
19
+ { form: "@media all", html: css("@media all{.x{display:none}}"), expected: "hidden" },
20
+ { form: "@media all and (min-width:1px)", html: css("@media all and (min-width:1px){.x{display:none}}"), expected: "visible" },
21
+ { form: "@supports (display:grid)", html: css("@supports (display:grid){.x{display:none}}"), expected: "visible" },
22
+ { form: "@container (min-width:400px)", html: css("@container (min-width: 400px){.x{display:none}}"), expected: "visible" },
23
+ { form: "@scope (.card)", html: css("@scope (.card){.x{display:none}}", '<div class="card"><p class="x">{T}</p></div>'), expected: "visible" },
24
+ { form: "@starting-style", html: css("@starting-style{.x{display:none}}"), expected: "visible" },
25
+ { form: "@document url-prefix()", html: css("@-moz-document url-prefix(){.x{display:none}}"), expected: "visible" },
26
+ { form: "@layer base (block)", html: css("@layer base{.x{display:none}}"), expected: "hidden" },
27
+ { form: "@layer statement then block", html: css("@layer a, b; @layer b{.x{display:none}}"), expected: "hidden" },
28
+ { form: "@layer inside @media", html: css("@media print{@layer a{.x{display:none}}}"), expected: "visible" },
29
+ { form: "@media inside @layer", html: css("@layer a{@media print{.x{display:none}}}"), expected: "visible" },
30
+ { form: "@media inside @supports", html: css("@supports (display:grid){@media (min-width:1px){.x{display:none}}}"), expected: "visible" },
31
+ { form: "@media nested in a style rule", html: css(".x{color:red;@media print{display:none}}"), expected: "visible" },
32
+ { form: "@import before a rule", html: css('@import url("print.css") print; .x{display:none}'), expected: "hidden" },
33
+ { form: "@font-face before a rule", html: css("@font-face{font-family:F;src:url(f.woff2)} .x{display:none}"), expected: "hidden" },
34
+ { form: "@keyframes hiding steps", html: css("@keyframes k{from{visibility:hidden}to{display:none}} .x{animation:k 1s}"), expected: "visible" },
35
+ { form: "@page", html: css("@page{display:none} .y{display:none}"), expected: "visible" },
36
+ { form: "CSS nesting (combinator, not evaluated)", html: css(".card{.x{display:none}}", '<div class="card"><p class="x">{T}</p></div>'), expected: "visible" },
37
+ { form: "conditional rule after an unconditional one", html: css(".x{display:none} @media print{.x{color:red}}"), expected: "hidden" },
38
+ // comments
39
+ { form: "rule inside a comment", html: css("/* .x{display:none} */"), expected: "visible" },
40
+ { form: "comment before the declaration", html: css(".x{/* display:block */display:none}"), expected: "hidden" },
41
+ { form: "comment inside the value", html: css(".x{display:/**/none}"), expected: "hidden" },
42
+ { form: "HTML comment markers in <style>", html: css("<!-- .x{display:none} -->"), expected: "hidden" },
43
+ { form: "comment in an inline style", html: '<p style="display:/* hide */none">{T}</p>', expected: "hidden" },
44
+ // selector lists and the cascade
45
+ { form: "selector list", html: css(".a, .x {display:none}"), expected: "hidden" },
46
+ { form: "selector list with a compound part", html: css("div p, .x{display:none}"), expected: "hidden" },
47
+ { form: "compound selector alone (not evaluated)", html: css("p.x{display:none}"), expected: "visible" },
48
+ { form: "descendant selector alone (not evaluated)", html: css("div .x{display:none}", '<div><p class="x">{T}</p></div>'), expected: "visible" },
49
+ { form: "attribute selector (not evaluated)", html: css("[data-off]{display:none}", "<p data-off>{T}</p>"), expected: "visible" },
50
+ { form: "later rule shows again", html: css(".x{display:none} .x{display:block}"), expected: "visible" },
51
+ { form: "later rule hides", html: css(".x{display:block} .x{display:none}"), expected: "hidden" },
52
+ { form: "id outranks class", html: css("#y{display:block} .x{display:none}", '<p id="y" class="x">{T}</p>'), expected: "visible" },
53
+ { form: "class outranks tag", html: css(".x{display:none} p{display:block}"), expected: "hidden" },
54
+ { form: "responsive show (d-none d-md-block)", html: css(".x{display:none} @media (min-width:768px){.y{display:block}}", '<p class="x y">{T}</p>'), expected: "visible" },
55
+ { form: "important hide beats a conditional show", html: css(".x{display:none!important} @media (min-width:768px){.y{display:block}}", '<p class="x y">{T}</p>'), expected: "hidden" },
56
+ { form: "important hide and important conditional show", html: css(".x{display:none!important} @media (min-width:768px){.y{display:block!important}}", '<p class="x y">{T}</p>'), expected: "visible" },
57
+ { form: "compound show may apply", html: css(".x{display:none} div .x{display:block}", '<div><p class="x">{T}</p></div>'), expected: "visible" },
58
+ { form: "compound show that cannot match", html: css(".x{display:none} div .z{display:block}", '<div><p class="x">{T}</p></div>'), expected: "hidden" },
59
+ { form: "layered show loses to unlayered hide", html: css("@layer base{.x{display:block}} .x{display:none}"), expected: "hidden" },
60
+ { form: "unlayered show beats layered hide", html: css("@layer base{.x{display:none}} p{display:block}"), expected: "visible" },
61
+ { form: "later layer beats earlier layer", html: css("@layer a, b; @layer b{.x{display:none}} @layer a{#y{display:block}}", '<p id="y" class="x">{T}</p>'), expected: "hidden" },
62
+ // !important against inline styles
63
+ { form: "important rule beats inline show", html: css(".x{display:none !important}", '<p class="x" style="display:block">{T}</p>'), expected: "hidden" },
64
+ { form: "inline show beats a normal rule", html: css(".x{display:none}", '<p class="x" style="display:block">{T}</p>'), expected: "visible" },
65
+ { form: "important show beats inline hide", html: css(".x{display:block !important}", '<p class="x" style="display:none">{T}</p>'), expected: "visible" },
66
+ { form: "important without a space", html: css(".x{display:none!important}"), expected: "hidden" },
67
+ { form: "inline important beats important rule", html: css(".x{display:block!important}", '<p class="x" style="display:none!important">{T}</p>'), expected: "hidden" },
68
+ // which properties hide
69
+ { form: "display:none (rule)", html: css(".x{display:none}"), expected: "hidden" },
70
+ { form: "visibility:hidden (rule)", html: css(".x{visibility:hidden}"), expected: "hidden" },
71
+ { form: "visibility:collapse", html: '<p style="visibility:collapse">{T}</p>', expected: "hidden" },
72
+ { form: "visibility:hidden parent, visible child", html: '<div style="visibility:hidden"><p style="visibility:visible">{T}</p></div>', expected: "visible" },
73
+ { form: "visibility:hidden parent, plain child", html: '<div style="visibility:hidden"><p>{T}</p></div>', expected: "hidden" },
74
+ { form: "visibility:hidden parent, child by rule", html: css(".x{visibility:visible}", '<div style="visibility:hidden"><p class="x">{T}</p></div>'), expected: "visible" },
75
+ { form: "display:none parent, visible child", html: '<div style="display:none"><p style="visibility:visible;display:block">{T}</p></div>', expected: "hidden" },
76
+ { form: "content-visibility:hidden", html: '<div style="content-visibility:hidden"><p>{T}</p></div>', expected: "hidden" },
77
+ { form: "display:contents", html: '<p style="display:contents">{T}</p>', expected: "visible" },
78
+ { form: "opacity:0", html: '<p style="opacity:0">{T}</p>', expected: "visible" },
79
+ { form: "clip rect (sr-only)", html: '<p style="position:absolute;clip:rect(0 0 0 0);width:1px;height:1px;overflow:hidden">{T}</p>', expected: "visible" },
80
+ { form: "clip-path inset", html: '<p style="clip-path:inset(50%)">{T}</p>', expected: "visible" },
81
+ { form: "height:0 with overflow hidden", html: '<p style="height:0;overflow:hidden">{T}</p>', expected: "visible" },
82
+ { form: "font-size:0", html: '<p style="font-size:0">{T}</p>', expected: "visible" },
83
+ { form: "off-screen position", html: '<p style="position:absolute;left:-9999px">{T}</p>', expected: "visible" },
84
+ { form: "transparent color", html: '<p style="color:transparent">{T}</p>', expected: "visible" },
85
+ // the hidden attribute
86
+ { form: "hidden attribute", html: "<p hidden>{T}</p>", expected: "hidden" },
87
+ { form: 'hidden="hidden"', html: '<p hidden="hidden">{T}</p>', expected: "hidden" },
88
+ { form: "HIDDEN in upper case", html: "<p HIDDEN>{T}</p>", expected: "hidden" },
89
+ { form: 'hidden="until-found"', html: '<p hidden="until-found">{T}</p>', expected: "visible" },
90
+ { form: "hidden attribute under an author display rule", html: css(".x{display:block}", '<p class="x" hidden>{T}</p>'), expected: "visible" },
91
+ { form: "hidden attribute under an inline display", html: '<p hidden style="display:block">{T}</p>', expected: "visible" },
92
+ { form: "descendant of a hidden element", html: "<div hidden><p>{T}</p></div>", expected: "hidden" },
93
+ // aria-hidden
94
+ { form: 'aria-hidden="true"', html: '<p aria-hidden="true">{T}</p>', expected: "hidden" },
95
+ { form: 'aria-hidden=" TRUE "', html: '<p aria-hidden=" TRUE ">{T}</p>', expected: "hidden" },
96
+ { form: 'aria-hidden="false"', html: '<p aria-hidden="false">{T}</p>', expected: "visible" },
97
+ { form: "descendant of aria-hidden", html: '<div aria-hidden="true"><p>{T}</p></div>', expected: "hidden" },
98
+ // inline style spellings
99
+ { form: "inline display:none", html: '<p style="display:none">{T}</p>', expected: "hidden" },
100
+ { form: "inline with spaces and a trailing semicolon", html: '<p style=" display : none ; ">{T}</p>', expected: "hidden" },
101
+ { form: "inline in upper case", html: '<p style="DISPLAY:NONE">{T}</p>', expected: "hidden" },
102
+ { form: "inline after another declaration", html: '<p style="color:red;display:none">{T}</p>', expected: "hidden" },
103
+ { form: "inline: last declaration wins", html: '<p style="display:none;display:block">{T}</p>', expected: "visible" },
104
+ { form: "inline custom property", html: '<p style="--display:none">{T}</p>', expected: "visible" },
105
+ { form: "inline var() value", html: '<p style="display:var(--d)">{T}</p>', expected: "visible" },
106
+ { form: "inline display:none in a string value", html: '<p style="font-family:\'display:none\'">{T}</p>', expected: "visible" },
107
+ // case: tag names are case-insensitive, class and id names are case-sensitive
108
+ { form: ".Foo vs class=foo", html: css(".Foo{display:none}", '<p class="foo">{T}</p>'), expected: "visible" },
109
+ { form: ".foo vs class=Foo", html: css(".foo{display:none}", '<p class="Foo">{T}</p>'), expected: "visible" },
110
+ { form: ".Foo vs class=Foo", html: css(".Foo{display:none}", '<p class="Foo">{T}</p>'), expected: "hidden" },
111
+ { form: "#Gone vs id=gone", html: css("#Gone{display:none}", '<p id="gone">{T}</p>'), expected: "visible" },
112
+ { form: "#gone vs id=gone", html: css("#gone{display:none}", '<p id="gone">{T}</p>'), expected: "hidden" },
113
+ { form: "P{} vs <p>", html: css("P{display:none}", "<p>{T}</p>"), expected: "hidden" },
114
+ { form: "p{} vs <P>", html: css("p{display:none}", "<P>{T}</P>"), expected: "hidden" },
115
+ { form: "Hangul class name", html: css(".\uc228\uae40{display:none}", '<p class="\uc228\uae40">{T}</p>'), expected: "hidden" },
116
+ { form: "upper-case property and value", html: css(".x{DISPLAY:NONE}"), expected: "hidden" },
117
+ // elements whose contents a reader never sees
118
+ { form: "<noscript>", html: "<noscript><p>{T}</p></noscript>", expected: "hidden" },
119
+ { form: "<template>", html: "<template><p>{T}</p></template>", expected: "hidden" },
120
+ { form: "<script>", html: '<script>var note = "{T}"</script>', expected: "hidden" },
121
+ { form: "<style> content", html: '<style>.x::after{content:"{T}"}</style>', expected: "hidden" },
122
+ { form: "<title> in <head>", html: "<head><title>{T}</title></head>", expected: "hidden" },
123
+ { form: "<svg> text", html: "<svg><text>{T}</text></svg>", expected: "hidden" },
124
+ { form: "closed <dialog>", html: "<dialog><p>{T}</p></dialog>", expected: "hidden" },
125
+ { form: "open <dialog>", html: "<dialog open><p>{T}</p></dialog>", expected: "visible" },
126
+ { form: "closed <details> body", html: "<details><summary>More</summary><p>{T}</p></details>", expected: "visible" },
127
+ // which <style> elements apply
128
+ { form: "<style> inside <template> is inert", html: '<template><style>.x{display:none}</style></template><p class="x">{T}</p>', expected: "visible" },
129
+ { form: "<style> inside <noscript> applies only without scripts", html: '<noscript><style>.x{display:none}</style></noscript><p class="x">{T}</p>', expected: "visible" },
130
+ { form: "<style> inside a hidden element still applies", html: '<div hidden><style>.x{display:none}</style></div><p class="x">{T}</p>', expected: "hidden" },
131
+ { form: "<style> inside <svg> applies to the page", html: '<svg><style>.x{display:none}</style></svg><p class="x">{T}</p>', expected: "hidden" },
132
+ { form: '<style media="print">', html: '<style media="print">.x{display:none}</style><p class="x">{T}</p>', expected: "visible" },
133
+ { form: '<style media="all">', html: '<style media="all">.x{display:none}</style><p class="x">{T}</p>', expected: "hidden" },
134
+ { form: '<style type="text/plain">', html: '<style type="text/plain">.x{display:none}</style><p class="x">{T}</p>', expected: "visible" },
135
+ { form: "external stylesheet (not evaluated)", html: '<link rel="stylesheet" href="hide.css"><p class="d-none">{T}</p>', expected: "visible" },
136
+ ]
137
+
138
+ const CASES = ROWS.flatMap((row) =>
139
+ (["en", "ko"] as const).map((lang) => [`${row.form} [${lang}] -> ${row.expected}`, row.html.replaceAll("{T}", TARGET[lang]), TARGET[lang], row.expected] as const),
140
+ )
141
+
142
+ describe("reader-visible text: one row per hiding form, English and Korean", () => {
143
+ test.each(CASES)("#given %s #when extracted #then the target is that", (_name, html, target, expected) => {
144
+ const { text } = extractHtml(`${html}<blockquote>${CONTROL}</blockquote>`)
145
+ expect(text).toContain(CONTROL)
146
+ expect(text.includes(target) ? "visible" : "hidden").toBe(expected)
147
+ })
148
+ })
@@ -0,0 +1,126 @@
1
+ // markdown-closers.mjs - the Markdown closer runs a converter writes after a piece of text, read with
2
+ // the CommonMark inline grammar instead of a hand-written pattern. A closer run is any sequence of:
3
+ // an inline link ending `](dest "title")` (the destination either in <...> or bare with balanced
4
+ // parentheses, the optional title in "...", '...' or (...), backslash escapes inside both), a full or
5
+ // collapsed reference ending `][ref]` / `][]`, a whole image `![alt](...)` or `![alt][ref]` (its alt
6
+ // text is not context), the `[` that opened a link whose text is all markup (`[![x](i.png)](/p)`),
7
+ // an emphasis, strike or code delimiter, a bare `)`, `]` or `>`, and a backslash-escaped closer
8
+ // (`\*`, `\_`, `\]`, `\)`, `\~`, a backtick), which stands for the raw character and is skipped like
9
+ // it. An escaped opener (`\[`, `\(`) is a real character and ends the run. Shared by the conversion
10
+ // probe's sign reading and its link-syntax spans. Pure; no imports.
11
+
12
+ const SIMPLE = new Set(["*", "_", "~", "`", ")", "]", ">"])
13
+ const BREAK = "\u0001"
14
+
15
+ /** Whether the character at `index` is backslash-escaped (an odd run of backslashes before it). */
16
+ export function escapedAt(text, index) {
17
+ let count = 0
18
+ for (let i = index - 1; i >= 0 && text[i] === "\\"; i--) count++
19
+ return count % 2 === 1
20
+ }
21
+
22
+ /** The end (exclusive) of an inline link target `dest "title")` that starts right after its "(", or -1. */
23
+ export function inlineTargetEnd(text, from) {
24
+ let i = from
25
+ const space = () => {
26
+ while (text[i] === " " || text[i] === "\t") i++
27
+ }
28
+ space()
29
+ if (text[i] === "<") {
30
+ for (i++; i < text.length && text[i] !== ">"; i++) {
31
+ if (text[i] === "\\") i++
32
+ else if (text[i] === "<" || text[i] === BREAK) return -1
33
+ }
34
+ if (text[i++] !== ">") return -1
35
+ } else {
36
+ let depth = 0
37
+ for (; i < text.length; i++) {
38
+ const ch = text[i]
39
+ if (ch === "\\") i++
40
+ else if (ch === "(") depth++
41
+ else if (ch === ")" && depth === 0) break
42
+ else if (ch === ")") depth--
43
+ else if (ch === " " || ch === "\t" || ch === BREAK) break
44
+ }
45
+ if (depth !== 0) return -1
46
+ }
47
+ const destEnd = i
48
+ space()
49
+ const open = text[i]
50
+ if (i > destEnd && (open === '"' || open === "'" || open === "(")) {
51
+ const close = open === "(" ? ")" : open
52
+ for (i++; i < text.length && text[i] !== close; i++) {
53
+ if (text[i] === "\\") i++
54
+ else if (text[i] === BREAK || (open === "(" && text[i] === "(")) return -1
55
+ }
56
+ if (text[i++] !== close) return -1
57
+ space()
58
+ }
59
+ return text[i] === ")" ? i + 1 : -1
60
+ }
61
+
62
+ /** The unescaped "[" that the unescaped "]" at `close` closes (brackets balance), or -1. */
63
+ function openerOf(text, close) {
64
+ let depth = 0
65
+ for (let i = close; i >= 0 && text[i] !== BREAK; i--) {
66
+ if ((text[i] !== "[" && text[i] !== "]") || escapedAt(text, i)) continue
67
+ depth += text[i] === "]" ? 1 : -1
68
+ if (depth === 0) return i
69
+ }
70
+ return -1
71
+ }
72
+
73
+ /** The "](" positions whose inline target ends exactly at `end`, leftmost first (so a title holding "](" is not cut). */
74
+ function inlineLinkClose(text, end) {
75
+ const from = Math.max(0, text.lastIndexOf(BREAK, end - 1))
76
+ for (let k = text.indexOf("](", from); k !== -1 && k < end; k = text.indexOf("](", k + 1)) {
77
+ if (!escapedAt(text, k) && inlineTargetEnd(text, k + 2) === end) return k
78
+ }
79
+ return -1
80
+ }
81
+
82
+ /** Where the link or image tail ending at `end` starts, and whether its "[" is still open; null when none ends there. */
83
+ function linkTail(text, end) {
84
+ let close = -1
85
+ if (text[end - 1] === ")") close = inlineLinkClose(text, end)
86
+ else if (text[end - 1] === "]" && !escapedAt(text, end - 1)) {
87
+ const ref = openerOf(text, end - 1)
88
+ if (ref > 0 && text[ref - 1] === "]" && !escapedAt(text, ref - 1)) close = ref - 1
89
+ }
90
+ if (close === -1) return null
91
+ const open = openerOf(text, close)
92
+ if (open > 0 && text[open - 1] === "!" && !escapedAt(text, open - 1)) return { start: open - 1, opened: 0 }
93
+ return { start: close, opened: open === -1 ? 0 : 1 }
94
+ }
95
+
96
+ /** `text` without the Markdown closer run it ends with. */
97
+ export function stripClosers(text) {
98
+ let end = text.length
99
+ let owed = 0
100
+ for (;;) {
101
+ const tail = linkTail(text, end)
102
+ if (tail) {
103
+ end = tail.start
104
+ owed += tail.opened
105
+ continue
106
+ }
107
+ const ch = text[end - 1]
108
+ if (SIMPLE.has(ch)) end -= escapedAt(text, end - 1) ? 2 : 1
109
+ else if (ch === "[" && owed > 0 && !escapedAt(text, end - 1)) {
110
+ end--
111
+ owed--
112
+ } else return text.slice(0, end)
113
+ }
114
+ }
115
+
116
+ /** [start, end) spans of every inline link or image with its target (an image from its "!"), for link-syntax cover. */
117
+ export function inlineLinkSpans(text) {
118
+ const spans = []
119
+ for (let k = text.indexOf("]("); k !== -1; k = text.indexOf("](", k + 1)) {
120
+ const end = escapedAt(text, k) ? -1 : inlineTargetEnd(text, k + 2)
121
+ if (end === -1) continue
122
+ const open = openerOf(text, k)
123
+ spans.push([open > 0 && text[open - 1] === "!" ? open - 1 : k, end])
124
+ }
125
+ return spans
126
+ }
@@ -0,0 +1,46 @@
1
+ import { describe, expect, test } from "bun:test"
2
+
3
+ import { parseMarkdown } from "./md-lite.mjs"
4
+ import { parseNumeral } from "./numerals.mjs"
5
+
6
+ describe("Markdown list continuation boundaries", () => {
7
+ test("#given a continuation after a list item #when parsed #then a later marker stays paragraph text", () => {
8
+ const text = "1. a\ncontinued\n2. b"
9
+ const { blocks } = parseMarkdown(text)
10
+ expect(blocks.map((block: { kind: string; text: string }) => [block.kind, block.text])).toEqual([
11
+ ["list_item", "a"],
12
+ ["paragraph", "continued\n2. b"],
13
+ ])
14
+ })
15
+
16
+ test("#given numbers beside a list continuation #when parsed #then fidelity numerals retain source offsets", () => {
17
+ const text = "1. a 12%\ncontinued\n2. b 34%"
18
+ const { blocks } = parseMarkdown(text)
19
+ const found = blocks.flatMap((block: { text: string; start: number }) => {
20
+ const numerals = []
21
+ for (let numeral = parseNumeral(block.text); numeral; numeral = parseNumeral(block.text, numeral.span[1])) {
22
+ const [start, end] = numeral.span
23
+ numerals.push([numeral.value, text.slice(block.start + start, block.start + end)])
24
+ }
25
+ return numerals
26
+ })
27
+ expect(found).toEqual([["12", "12%"], ["2", "2"], ["34", "34%"]])
28
+ })
29
+ })
30
+
31
+ describe("Markdown closing fence indentation", () => {
32
+ test.each(["```", "~~~"])("#given a four-space %s closing candidate #when parsed #then the code runs to EOF", (fence: string) => {
33
+ const text = `${fence}\ncode 12\n ${fence}\nclaim 34`
34
+ const { blocks, problems } = parseMarkdown(text)
35
+ expect(blocks.map((block: { kind: string; text: string }) => [block.kind, block.text])).toEqual([
36
+ ["code", `code 12\n ${fence}\nclaim 34`],
37
+ ])
38
+ expect(problems).toEqual([{ code: "unterminated_fence", line: 1, start: 0 }])
39
+ })
40
+
41
+ test.each([0, 1, 2, 3])("#given a closing fence indented %s spaces #when parsed #then later prose is outside code", (spaces: number) => {
42
+ const { blocks, problems } = parseMarkdown(`\`\`\`\ncode\n${" ".repeat(spaces)}\`\`\`\nclaim 34`)
43
+ expect(blocks.map((block: { kind: string }) => block.kind)).toEqual(["code", "paragraph"])
44
+ expect(problems).toEqual([])
45
+ })
46
+ })
@@ -113,7 +113,7 @@ export function parseMarkdown(text) {
113
113
  const codeStart = Math.min(line.next, source.length)
114
114
  let close = lines.length
115
115
  for (let k = i + 1; k < lines.length; k++) {
116
- const candidate = lines[k].raw.trim()
116
+ const candidate = lines[k].raw.replace(/^ {0,3}/, "").trimEnd()
117
117
  if (candidate.startsWith(open[0].repeat(open.length)) && /^(`+|~+)$/.test(candidate)) {
118
118
  close = k
119
119
  break
@@ -0,0 +1,33 @@
1
+ // number-grammar.mjs - the one grouped-number grammar. A grouped number is a 1-3 digit leading
2
+ // group, then one or more 3-digit groups joined by ONE separator type, with no digit right after the
3
+ // last group, then an optional decimal part. "12,3451" is therefore not "12,345" plus "1". A period
4
+ // groups only when the reading is unambiguous: two or more period groups ("1.234.567") or one group
5
+ // before a decimal comma ("1.234,5"); a lone "1.234" is a decimal, as numerals.mjs reads it. Shared by
6
+ // numerals.mjs (comma grouping), the conversion probe (every separator), the value-mode unit check,
7
+ // and the converted-half display check. Pure; no imports.
8
+
9
+ /** Separators a thousands group may use: comma, period, space, NBSP, narrow NBSP, thin space, figure space. */
10
+ export const GROUP_SEPARATORS = Object.freeze([",", ".", " ", "\u00A0", "\u202F", "\u2009", "\u2007"])
11
+
12
+ const literal = (sep) => (sep === "." ? "\\." : sep === " " ? " " : sep === "," ? "," : `\\u${sep.charCodeAt(0).toString(16).padStart(4, "0")}`)
13
+ const decimalAfter = (sep) => (sep === "," ? "\\." : sep === "." ? "," : "[.,]")
14
+
15
+ /** Regex source of one grouped number using any of `separators` (one type per number); no flags needed. */
16
+ export function groupedNumberSource(separators = GROUP_SEPARATORS) {
17
+ const one = (sep) => sep === "."
18
+ ? "\\d{1,3}(?:(?:\\.\\d{3}(?!\\d)){2,}(?:,\\d+)?|\\.\\d{3}(?!\\d),\\d+)"
19
+ : `\\d{1,3}(?:${literal(sep)}\\d{3}(?!\\d))+(?:${decimalAfter(sep)}\\d+)?`
20
+ return `(?<!\\d)(?:${separators.map(one).join("|")})`
21
+ }
22
+
23
+ /** A grouped number or a plain one ("4.22", "12,3451" read as 12.3451), as regex source. */
24
+ export const NUMBER_SOURCE = `(?:${groupedNumberSource()}|(?<!\\d)\\d+(?:[.,]\\d+)?)`
25
+
26
+ const WHOLE = new Map(GROUP_SEPARATORS.map((sep) => [sep, new RegExp(`^${groupedNumberSource([sep])}$`)]))
27
+
28
+ /** The plain decimal a number token spells: group separators dropped, a decimal comma read as a point. */
29
+ export function plainDecimal(token) {
30
+ const sep = GROUP_SEPARATORS.find((candidate) => WHOLE.get(candidate).test(token))
31
+ const ungrouped = sep === undefined ? token : token.split(sep).join("")
32
+ return ungrouped.replace(",", ".")
33
+ }
@@ -0,0 +1,36 @@
1
+ import { describe, expect, test } from "bun:test"
2
+
3
+ import { NUMBER_SOURCE, groupedNumberSource, plainDecimal } from "./number-grammar.mjs"
4
+ import { parseNumeral } from "./numerals.mjs"
5
+
6
+ const numbers = (text: string) => [...text.matchAll(new RegExp(NUMBER_SOURCE, "g"))].map((match) => match[0])
7
+
8
+ describe("the one grouped-number grammar", () => {
9
+ for (const [text, expected] of [
10
+ ["12,3451", ["12,3451"]],
11
+ ["212,34515,678", ["212,34515", "678"]],
12
+ ["12,345.6 and 1 234 567", ["12,345.6", "1 234 567"]],
13
+ ["1\u2009234\u2009567", ["1\u2009234\u2009567"]],
14
+ ["2,500 300", ["2,500", "300"]],
15
+ ["2023 500", ["2023", "500"]],
16
+ ["1.234,5", ["1.234,5"]],
17
+ ["1.234.567 and 1.234", ["1.234.567", "1.234"]],
18
+ ] as const) {
19
+ test(`#given ${JSON.stringify(text)} #when tokenized #then ${JSON.stringify(expected)}`, () => expect(numbers(text)).toEqual([...expected]))
20
+ }
21
+
22
+ test("#given grouped and plain tokens #when normalized #then group separators drop and a decimal comma reads as a point", () => {
23
+ expect(["12,345.6", "1 234 567", "1.234,5", "12,3451", "4.22"].map(plainDecimal)).toEqual(["12345.6", "1234567", "1234.5", "12.3451", "4.22"])
24
+ })
25
+
26
+ test("#given a single period group #when normalized #then it is a decimal, as parseNumeral reads it", () => {
27
+ expect(["1.234", "1.234.567"].map(plainDecimal)).toEqual(["1.234", "1234567"])
28
+ expect(parseNumeral("1.234 units")?.value).toBe("1.234")
29
+ })
30
+
31
+ test("#given comma grouping #when parseNumeral reads it #then only a well-formed group is one value", () => {
32
+ expect(new RegExp(`^${groupedNumberSource([","])}$`).test("1,237")).toBe(true)
33
+ expect(parseNumeral("1,237 units")?.value).toBe("1237")
34
+ expect(parseNumeral("12,3451 units")?.value).not.toBe("123451")
35
+ })
36
+ })
@@ -6,9 +6,12 @@
6
6
  // English number words one to twelve, an adjacent currency symbol or unit, and an adjacent bounding
7
7
  // qualifier. No numeral is ever passed through Number(). Plain Node ESM, sibling imports only.
8
8
 
9
+ import { groupedNumberSource } from "./number-grammar.mjs"
9
10
  import { add, fromDecimal, mul, toDecimal } from "./rational.mjs"
10
11
  import { normalizeUnit } from "./units.mjs"
11
12
 
13
+ /** European digit grouping ("1.234.567"): two or more dot groups of three digits, never a version. */
14
+ export const DOT_GROUPED = "\\d{1,3}(?:\\.\\d{3}){2,}"
12
15
  const MONTH = "(?:jan|feb|mar|apr|may|jun|jul|aug|sep|sept|oct|nov|dec)[a-z]*\\.?"
13
16
  /** Stripped before claim detection: dates, versions, figure refs, times, bare years. */
14
17
  export const NOT_CLAIMS = [
@@ -19,7 +22,7 @@ export const NOT_CLAIMS = [
19
22
  /(?:19|20)\d{2}\s*\uB144/g,
20
23
  new RegExp(`\\b${MONTH}\\s+\\d{1,2}(?:st|nd|rd|th)?(?:,?\\s+\\d{4})?`, "gi"),
21
24
  new RegExp(`\\b\\d{1,2}(?:st|nd|rd|th)?\\s+${MONTH}(?:\\s+\\d{4})?`, "gi"),
22
- /\bv\d+(?:\.\d+)*\b|\b\d+(?:\.\d+){2,}\b/gi,
25
+ new RegExp(`\\bv\\d+(?:\\.\\d+)*\\b|\\b(?!${DOT_GROUPED}(?![\\d,]|\\.\\d))\\d+(?:\\.\\d+){2,}\\b`, "gi"),
23
26
  /\b(?:version|ver\.?|release)\s*\d+(?:\.\d+)*/gi,
24
27
  /\b(?:fig(?:ure)?\.?|table|chart|section|step|phase|wave|part)\s*\d+(?:\.\d+)*/gi,
25
28
  /(?:\uADF8\uB9BC|\uB3C4\uD45C|\uD45C|\uBC84\uC804)\s*\d+(?:\.\d+)*/g,
@@ -45,8 +48,8 @@ const BIG = new Map([["\uB9CC", 10000n], ["\uC5B5", 100000000n], ["\uC870", 1000
45
48
  /** A scale word that starts a longer word is not a scale: jogak (pieces), mankeum (as much as). */
46
49
  const KO_GROUP = /^(\s?)(?:(\uCC9C)?([\uB9CC\uC5B5\uC870])|(\uCC9C))(?![\uAC01\uD07C])/
47
50
  const KO_NEXT = new RegExp(`^\\s?(${NUM})(\uCC9C)?([\uB9CC\uC5B5\uC870])?`, "u")
48
- const LATIN_SCALE = /^(?:(k|K|M|bn|B)|\s(thousand|million|billion|trillion|bn))(?![\p{L}\d])/u
49
- const LATIN_FACTOR = { k: 1000n, K: 1000n, M: 1000000n, bn: 1000000000n, B: 1000000000n, thousand: 1000n, million: 1000000n, billion: 1000000000n, trillion: 1000000000000n }
51
+ const LATIN_SCALE = /^(?:(k|K|M|mn|bn|B)|\s(thousand|million|billion|trillion|mn|bn))(?![\p{L}\d])/u
52
+ const LATIN_FACTOR = { k: 1000n, K: 1000n, M: 1000000n, mn: 1000000n, bn: 1000000000n, B: 1000000000n, thousand: 1000n, million: 1000000n, billion: 1000000000n, trillion: 1000000000000n }
50
53
  const CURRENCY = "$\u20AC\u00A3\u00A5\u20A9"
51
54
  const SYMBOL_UNIT = /^\s?(%p|%|\u2030|\u00D7|\u00B0C|\u00B0F|\u2103|\u2109)/
52
55
  const LATIN_UNIT = /^\s?([A-Za-z]+)(?![\p{L}\d])/u
@@ -60,15 +63,23 @@ const RANGE_SEP = /^(?:\s?[-\u2013~]\s?|\sto\s)/
60
63
  const SIGN = "-\u2212"
61
64
  /** Bounding qualifiers: before the value (Korean and English) and right after it (Korean). */
62
65
  const QUALIFIERS_BEFORE = ["up to", "at least", "at most", "more than", "less than", "about", "approximately", "nearly", "over", "under", "\uCD5C\uB300", "\uCD5C\uC18C", "\uC57D"]
63
- const QUALIFIER_AFTER = /^\s?(\uC774\uC0C1|\uC774\uD558|\uBBF8\uB9CC|\uCD08\uACFC|\uB0B4\uC678|\uAE4C\uC9C0)/
66
+ /** Korean bounding qualifiers written right after a value (isang, iha, miman, chogwa, naeoe, kkaji). */
67
+ export const QUALIFIERS_AFTER = Object.freeze(["\uC774\uC0C1", "\uC774\uD558", "\uBBF8\uB9CC", "\uCD08\uACFC", "\uB0B4\uC678", "\uAE4C\uC9C0"])
68
+ const QUALIFIER_AFTER = new RegExp(`^\\s?(${QUALIFIERS_AFTER.join("|")})`)
64
69
 
65
- /** "1,237" -> "1237"; a malformed group ("1,23") keeps only the leading plain number. */
70
+ /** Comma grouping from the one grouped-number grammar (number-grammar.mjs). */
71
+ const COMMA_GROUPED = new RegExp(`^${groupedNumberSource([","])}$`)
72
+
73
+ /** "1,237" -> "1237"; a malformed group ("1,23", "12,3451") keeps only the leading plain number. */
66
74
  function cleanDigits(raw) {
67
- if (/^\d{1,3}(?:,\d{3})+(?:\.\d+)?$/.test(raw)) return { digits: raw.replace(/,/g, ""), length: raw.length }
75
+ if (COMMA_GROUPED.test(raw)) return { digits: raw.replace(/,/g, ""), length: raw.length }
68
76
  const plain = /^\d+(?:\.\d+)?/.exec(raw)[0]
69
77
  return { digits: plain, length: plain.length }
70
78
  }
71
79
 
80
+ /** A numeral's digits, reading a European period grouping ("1.234.567") as one integer. */
81
+ const digitsOf = (raw) => (new RegExp(`^${DOT_GROUPED}$`).test(raw) ? { digits: raw.replace(/\./g, ""), length: raw.length } : cleanDigits(raw))
82
+
72
83
  /** The bare-year and four-digit-date patterns: a numeral they cover can still be a value. */
73
84
  const YEAR_SHAPED = new Set([NOT_CLAIMS[0], NOT_CLAIMS[12]])
74
85
 
@@ -95,7 +106,7 @@ function findStart(text, from, ranges) {
95
106
  let digit = null
96
107
  for (const match of text.slice(from).matchAll(new RegExp(NUM, "gu"))) {
97
108
  const at = from + match.index
98
- const candidate = { at, ...cleanDigits(match[0]) }
109
+ const candidate = { at, ...digitsOf(match[0]) }
99
110
  const end = at + candidate.length
100
111
  const cover = covering(at)
101
112
  const lifted = cover.every(([s, e, yearShaped]) => yearShaped && s === at && e <= end)
@@ -37,6 +37,9 @@ describe("parseNumeral", () => {
37
37
  expect(parseNumeral("1.2k")?.value).toBe("1200")
38
38
  expect(parseNumeral("$3M")).toMatchObject({ value: "3000000", raw: "$3M", unitRaw: "$" })
39
39
  expect(parseNumeral("2.5bn")?.value).toBe("2500000000")
40
+ expect(parseNumeral("5mn users")?.value).toBe("5000000")
41
+ expect(parseNumeral("1.234.567 users")?.value).toBe("1234567")
42
+ expect(parseNumeral("build 0.30.14 shipped")).toBeNull()
40
43
  expect(parseNumeral("$4B")?.value).toBe("4000000000")
41
44
  expect(parseNumeral("3 million users")?.value).toBe("3000000")
42
45
  expect(parseNumeral("5 km")).toMatchObject({ value: "5", unitRaw: "km" })