oh-my-opencode 5.1.45 → 5.1.47

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 (370) hide show
  1. package/.agents/command/publish.md +12 -13
  2. package/.agents/skills/publish/SKILL.md +12 -13
  3. package/.opencode/command/publish.md +12 -13
  4. package/README.de.md +0 -5
  5. package/README.es.md +0 -5
  6. package/README.fr.md +0 -5
  7. package/README.it.md +0 -5
  8. package/README.ja.md +0 -5
  9. package/README.ko.md +0 -5
  10. package/README.md +4 -5
  11. package/README.pt-br.md +0 -5
  12. package/README.ru.md +0 -6
  13. package/README.zh-cn.md +0 -5
  14. package/dist/agents/atlas/agent.d.ts +3 -2
  15. package/dist/agents/sisyphus/index.d.ts +1 -0
  16. package/dist/agents/sisyphus/kimi-k3-1.d.ts +9 -0
  17. package/dist/agents/sisyphus-agent-factory.d.ts +1 -1
  18. package/dist/agents/sisyphus-junior/agent.d.ts +1 -1
  19. package/dist/agents/sisyphus-junior/index.d.ts +1 -0
  20. package/dist/agents/sisyphus-junior/kimi-k3-1.d.ts +6 -0
  21. package/dist/agents/types.d.ts +2 -2
  22. package/dist/cli/index.js +91 -163
  23. package/dist/cli/service-auth/keystore.d.ts +8 -3
  24. package/dist/cli-node/index.js +91 -163
  25. package/dist/config-migration/index.d.ts +2 -2
  26. package/dist/config-migration/migration-plans.d.ts +0 -10
  27. package/dist/index.js +1784 -1204
  28. package/dist/skills/ast-grep/AGENTS.md +1 -1
  29. package/dist/skills/browser/runtime/omowright/manifest.json +1 -1
  30. package/dist/skills/frontend/ATTRIBUTION.md +2 -2
  31. package/dist/skills/ultimate-browsing/engine/AGENTS.md +1 -1
  32. package/dist/skills/ulw-research/AGENTS.md +14 -2
  33. package/dist/skills/ulw-research/SKILL.md +37 -12
  34. package/dist/skills/ulw-research/references/deliverable-phase.md +81 -14
  35. package/dist/skills/ulw-research/references/fact-ledger-grammar.md +19 -1
  36. package/dist/skills/ulw-research/references/fact-ledger.md +95 -0
  37. package/dist/skills/ulw-research/references/fidelity-claim-kinds.md +19 -0
  38. package/dist/skills/ulw-research/references/report-gates.md +21 -4
  39. package/dist/skills/ulw-research/references/report-visualization.md +2 -0
  40. package/dist/skills/ulw-research/references/unit-rules.md +157 -0
  41. package/dist/skills/ulw-research/scripts/claim-kinds.mjs +116 -0
  42. package/dist/skills/ulw-research/scripts/claim-kinds.test.ts +193 -0
  43. package/dist/skills/ulw-research/scripts/cli-support.mjs +19 -5
  44. package/dist/skills/ulw-research/scripts/cli-support.test.ts +13 -2
  45. package/dist/skills/ulw-research/scripts/contracts.mjs +27 -6
  46. package/dist/skills/ulw-research/scripts/contracts.test.ts +3 -0
  47. package/dist/skills/ulw-research/scripts/copy-returns-polish.test.ts +209 -0
  48. package/dist/skills/ulw-research/scripts/copy-returns.test.ts +183 -0
  49. package/dist/skills/ulw-research/scripts/delivery-barrier.test.ts +245 -0
  50. package/dist/skills/ulw-research/scripts/fact-commands-meaning.mjs +176 -0
  51. package/dist/skills/ulw-research/scripts/fact-commands-polish.mjs +127 -0
  52. package/dist/skills/ulw-research/scripts/fact-commands-verify.mjs +183 -0
  53. package/dist/skills/ulw-research/scripts/fact-commands.mjs +241 -0
  54. package/dist/skills/ulw-research/scripts/fact-derive.mjs +66 -0
  55. package/dist/skills/ulw-research/scripts/fact-derive.test.ts +375 -0
  56. package/dist/skills/ulw-research/scripts/fact-expr.mjs +108 -0
  57. package/dist/skills/ulw-research/scripts/fact-fill.mjs +194 -0
  58. package/dist/skills/ulw-research/scripts/fact-fill.test.ts +221 -0
  59. package/dist/skills/ulw-research/scripts/fact-ledger-grammar.test.ts +9 -2
  60. package/dist/skills/ulw-research/scripts/fact-ledger-kinds.mjs +127 -13
  61. package/dist/skills/ulw-research/scripts/fact-ledger-kinds.test.ts +18 -3
  62. package/dist/skills/ulw-research/scripts/fact-ledger-legacy.test.ts +160 -0
  63. package/dist/skills/ulw-research/scripts/fact-ledger-schema.mjs +33 -16
  64. package/dist/skills/ulw-research/scripts/fact-ledger.mjs +29 -9
  65. package/dist/skills/ulw-research/scripts/fact-ledger.test.ts +2 -0
  66. package/dist/skills/ulw-research/scripts/fact-locate.mjs +231 -0
  67. package/dist/skills/ulw-research/scripts/fact-locate.test.ts +118 -0
  68. package/dist/skills/ulw-research/scripts/fact-pairs.mjs +185 -0
  69. package/dist/skills/ulw-research/scripts/fact-pairs.test.ts +151 -0
  70. package/dist/skills/ulw-research/scripts/fact-recompute.mjs +89 -0
  71. package/dist/skills/ulw-research/scripts/fact-unit-dimensions.test.ts +409 -0
  72. package/dist/skills/ulw-research/scripts/fact-unit-rules.test.ts +388 -0
  73. package/dist/skills/ulw-research/scripts/fidelity-binding.mjs +130 -0
  74. package/dist/skills/ulw-research/scripts/fidelity-binding.test.ts +81 -0
  75. package/dist/skills/ulw-research/scripts/fidelity-brief.mjs +5 -0
  76. package/dist/skills/ulw-research/scripts/fidelity-claims.mjs +89 -0
  77. package/dist/skills/ulw-research/scripts/fidelity-lexicon.mjs +145 -0
  78. package/dist/skills/ulw-research/scripts/fidelity-lexicon.test.ts +117 -0
  79. package/dist/skills/ulw-research/scripts/fidelity-records.mjs +163 -0
  80. package/dist/skills/ulw-research/scripts/fidelity-state.mjs +2 -1
  81. package/dist/skills/ulw-research/scripts/fidelity-trace.mjs +85 -0
  82. package/dist/skills/ulw-research/scripts/fidelity-trace.test.ts +58 -0
  83. package/dist/skills/ulw-research/scripts/fidelity-values.mjs +229 -0
  84. package/dist/skills/ulw-research/scripts/fidelity-values.test.ts +270 -0
  85. package/dist/skills/ulw-research/scripts/gates-fidelity.mjs +113 -0
  86. package/dist/skills/ulw-research/scripts/gates-fidelity.test.ts +73 -0
  87. package/dist/skills/ulw-research/scripts/gates-static.mjs +28 -8
  88. package/dist/skills/ulw-research/scripts/html-lite.mjs +2 -2
  89. package/dist/skills/ulw-research/scripts/html-markup.mjs +182 -0
  90. package/dist/skills/ulw-research/scripts/md-lite.mjs +193 -0
  91. package/dist/skills/ulw-research/scripts/md-lite.test.ts +271 -0
  92. package/dist/skills/ulw-research/scripts/meaning-check.mjs +196 -0
  93. package/dist/skills/ulw-research/scripts/meaning-check.test.ts +228 -0
  94. package/dist/skills/ulw-research/scripts/outcome.mjs +6 -5
  95. package/dist/skills/ulw-research/scripts/placeholder.mjs +184 -0
  96. package/dist/skills/ulw-research/scripts/placeholder.test.ts +77 -0
  97. package/dist/skills/ulw-research/scripts/polish-apply.mjs +115 -0
  98. package/dist/skills/ulw-research/scripts/polish-apply.test.ts +363 -0
  99. package/dist/skills/ulw-research/scripts/polish-diff.mjs +31 -0
  100. package/dist/skills/ulw-research/scripts/polish-diff.test.ts +168 -0
  101. package/dist/skills/ulw-research/scripts/polish-markup.test.ts +187 -0
  102. package/dist/skills/ulw-research/scripts/polish-project.mjs +125 -0
  103. package/dist/skills/ulw-research/scripts/polish-rules.mjs +170 -0
  104. package/dist/skills/ulw-research/scripts/references-fences.test.ts +25 -0
  105. package/dist/skills/ulw-research/scripts/report-tools-commands.mjs +19 -8
  106. package/dist/skills/ulw-research/scripts/report-tools.mjs +15 -0
  107. package/dist/skills/ulw-research/scripts/report-tools.test.ts +545 -2
  108. package/dist/skills/ulw-research/scripts/source-commands.mjs +140 -0
  109. package/dist/skills/ulw-research/scripts/source-snapshot.mjs +176 -0
  110. package/dist/skills/ulw-research/scripts/source-snapshot.test.ts +113 -0
  111. package/dist/skills/ulw-research/scripts/tests/fixtures/fidelity/assets/share.csv +3 -0
  112. package/dist/skills/ulw-research/scripts/tests/fixtures/fidelity/gen-legacy-dev.mjs +89 -0
  113. package/dist/skills/ulw-research/scripts/tests/fixtures/fidelity/ledger-basic.json +223 -0
  114. package/dist/skills/ulw-research/scripts/tests/fixtures/fidelity/ledger-legacy-dev.json +859 -0
  115. package/dist/skills/ulw-research/scripts/tests/fixtures/fidelity/ledger-verify.json +420 -0
  116. package/dist/skills/ulw-research/scripts/tests/fixtures/fidelity/polish-edits.json +12 -0
  117. package/dist/skills/ulw-research/scripts/tests/fixtures/fidelity/report-bad.html +19 -0
  118. package/dist/skills/ulw-research/scripts/tests/fixtures/fidelity/report-good.html +39 -0
  119. package/dist/skills/ulw-research/scripts/tests/fixtures/fidelity/sources/S4/acronyms.json +3 -0
  120. package/dist/skills/ulw-research/scripts/tests/fixtures/fidelity/sources/S4/meta.json +9 -0
  121. package/dist/skills/ulw-research/scripts/tests/fixtures/fidelity/sources/S4/sentences.jsonl +7 -0
  122. package/dist/skills/ulw-research/scripts/tests/fixtures/fidelity/sources/S4/text.txt +7 -0
  123. package/dist/skills/ulw-research/scripts/tests/fixtures/fidelity/sources/S4.html +19 -0
  124. package/dist/skills/ulw-research/scripts/tests/fixtures/fidelity/sources/S5/acronyms.json +1 -0
  125. package/dist/skills/ulw-research/scripts/tests/fixtures/fidelity/sources/S5/meta.json +9 -0
  126. package/dist/skills/ulw-research/scripts/tests/fixtures/fidelity/sources/S5/sentences.jsonl +1 -0
  127. package/dist/skills/ulw-research/scripts/tests/fixtures/fidelity/sources/S5/text.txt +1 -0
  128. package/dist/skills/ulw-research/scripts/tests/fixtures/fidelity/sources/S9.txt +1 -0
  129. package/dist/skills/ulw-research/scripts/unit-algebra.mjs +101 -0
  130. package/dist/skills/ulw-research/scripts/unit-errors.mjs +23 -0
  131. package/dist/skills/ulw-research/scripts/unit-metadata.mjs +31 -0
  132. package/dist/skills/ulw-research/scripts/unit-pairs.mjs +72 -0
  133. package/dist/skills/ulw-research/scripts/unit-precision.mjs +44 -0
  134. package/dist/skills/ulw-research/scripts/unit-rules-shapes.mjs +64 -0
  135. package/dist/skills/ulw-research/scripts/unit-rules.mjs +173 -0
  136. package/dist/skills/ulw-research/scripts/units.mjs +4 -4
  137. package/dist/testing/module-mock-lifecycle.d.ts +1 -1
  138. package/dist/tui.js +32 -34
  139. package/package.json +13 -13
  140. package/packages/lsp-core/src/mcp-protocol-pin.test.ts +29 -0
  141. package/packages/lsp-core/src/mcp.ts +5 -2
  142. package/packages/lsp-daemon/dist/cli.js +3 -3
  143. package/packages/lsp-daemon/dist/client.js +3 -3
  144. package/packages/lsp-daemon/dist/index.js +3 -3
  145. package/packages/lsp-tools-mcp/dist/cli.js +3 -3
  146. package/packages/lsp-tools-mcp/dist/mcp.js +3 -3
  147. package/packages/omo-codex/plugin/.codex-plugin/plugin.json +1 -1
  148. package/packages/omo-codex/plugin/components/bootstrap/dist/cli.js +23 -14
  149. package/packages/omo-codex/plugin/components/bootstrap/hooks/hooks.json +1 -1
  150. package/packages/omo-codex/plugin/components/bootstrap/package.json +1 -1
  151. package/packages/omo-codex/plugin/components/comment-checker/hooks/hooks.json +1 -1
  152. package/packages/omo-codex/plugin/components/comment-checker/package.json +1 -1
  153. package/packages/omo-codex/plugin/components/git-bash/hooks/hooks.json +2 -2
  154. package/packages/omo-codex/plugin/components/git-bash/package.json +1 -1
  155. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/hooks/hooks.json +1 -1
  156. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/package.json +1 -1
  157. package/packages/omo-codex/plugin/components/lsp/dist/.omo-runtime-manifest.json +2 -2
  158. package/packages/omo-codex/plugin/components/lsp/hooks/hooks.json +2 -2
  159. package/packages/omo-codex/plugin/components/lsp/package.json +1 -1
  160. package/packages/omo-codex/plugin/components/rules/hooks/hooks.json +4 -4
  161. package/packages/omo-codex/plugin/components/rules/package.json +1 -1
  162. package/packages/omo-codex/plugin/components/teammode/hooks/hooks.json +1 -1
  163. package/packages/omo-codex/plugin/components/teammode/package.json +1 -1
  164. package/packages/omo-codex/plugin/components/telemetry/hooks/hooks.json +1 -1
  165. package/packages/omo-codex/plugin/components/telemetry/package.json +1 -1
  166. package/packages/omo-codex/plugin/components/ultrawork/hooks/hooks.json +1 -1
  167. package/packages/omo-codex/plugin/components/ultrawork/package.json +1 -1
  168. package/packages/omo-codex/plugin/components/ulw-execute-continuation/hooks/hooks.json +1 -1
  169. package/packages/omo-codex/plugin/components/ulw-execute-continuation/package.json +1 -1
  170. package/packages/omo-codex/plugin/components/ulw-loop/hooks/hooks.json +5 -5
  171. package/packages/omo-codex/plugin/components/ulw-loop/package.json +1 -1
  172. package/packages/omo-codex/plugin/hooks/post-compact-resetting-git-bash-mcp-reminder.json +1 -1
  173. package/packages/omo-codex/plugin/hooks/post-compact-resetting-lsp-diagnostics-cache.json +1 -1
  174. package/packages/omo-codex/plugin/hooks/post-compact-resetting-project-rule-cache.json +1 -1
  175. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-comments.json +1 -1
  176. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-lsp-diagnostics.json +1 -1
  177. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-thread-title-hygiene.json +1 -1
  178. package/packages/omo-codex/plugin/hooks/post-tool-use-matching-project-rules.json +1 -1
  179. package/packages/omo-codex/plugin/hooks/post-tool-use-recording-spawn-admission.json +1 -1
  180. package/packages/omo-codex/plugin/hooks/pre-tool-use-enforcing-unlimited-goal-budget.json +1 -1
  181. package/packages/omo-codex/plugin/hooks/pre-tool-use-guarding-ulw-loop-spawns.json +1 -1
  182. package/packages/omo-codex/plugin/hooks/pre-tool-use-recommending-git-bash-mcp.json +1 -1
  183. package/packages/omo-codex/plugin/hooks/session-start-checking-auto-update.json +1 -1
  184. package/packages/omo-codex/plugin/hooks/session-start-checking-bootstrap-provisioning.json +1 -1
  185. package/packages/omo-codex/plugin/hooks/session-start-loading-project-rules.json +1 -1
  186. package/packages/omo-codex/plugin/hooks/session-start-recording-session-telemetry.json +1 -1
  187. package/packages/omo-codex/plugin/hooks/stop-checking-ulw-execute-continuation.json +1 -1
  188. package/packages/omo-codex/plugin/hooks/stop-checking-ulw-loop-resume.json +1 -1
  189. package/packages/omo-codex/plugin/hooks/subagent-stop-verifying-lazycodex-executor-evidence.json +1 -1
  190. package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ultrawork-trigger.json +1 -1
  191. package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ulw-loop-steering.json +1 -1
  192. package/packages/omo-codex/plugin/hooks/user-prompt-submit-loading-project-rules.json +1 -1
  193. package/packages/omo-codex/plugin/package-lock.json +12 -12
  194. package/packages/omo-codex/plugin/package.json +1 -1
  195. package/packages/omo-codex/plugin/scripts/sync-skills.mjs +8 -3
  196. package/packages/omo-codex/plugin/shared/test/config-loader.test.ts +0 -2
  197. package/packages/omo-codex/plugin/skills/ast-grep/AGENTS.md +1 -1
  198. package/packages/omo-codex/plugin/skills/browser/runtime/omowright/manifest.json +1 -1
  199. package/packages/omo-codex/plugin/skills/frontend/ATTRIBUTION.md +2 -2
  200. package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/AGENTS.md +1 -1
  201. package/packages/omo-codex/plugin/skills/ulw-research/AGENTS.md +14 -2
  202. package/packages/omo-codex/plugin/skills/ulw-research/SKILL.md +36 -11
  203. package/packages/omo-codex/plugin/skills/ulw-research/references/deliverable-phase.md +81 -14
  204. package/packages/omo-codex/plugin/skills/ulw-research/references/fact-ledger-grammar.md +19 -1
  205. package/packages/omo-codex/plugin/skills/ulw-research/references/fact-ledger.md +95 -0
  206. package/packages/omo-codex/plugin/skills/ulw-research/references/fidelity-claim-kinds.md +19 -0
  207. package/packages/omo-codex/plugin/skills/ulw-research/references/report-gates.md +21 -4
  208. package/packages/omo-codex/plugin/skills/ulw-research/references/report-visualization.md +2 -0
  209. package/packages/omo-codex/plugin/skills/ulw-research/references/unit-rules.md +157 -0
  210. package/packages/omo-codex/plugin/skills/ulw-research/scripts/claim-kinds.mjs +116 -0
  211. package/packages/omo-codex/plugin/skills/ulw-research/scripts/cli-support.mjs +19 -5
  212. package/packages/omo-codex/plugin/skills/ulw-research/scripts/contracts.mjs +27 -6
  213. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-commands-meaning.mjs +176 -0
  214. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-commands-polish.mjs +127 -0
  215. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-commands-verify.mjs +183 -0
  216. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-commands.mjs +241 -0
  217. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-derive.mjs +66 -0
  218. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-expr.mjs +108 -0
  219. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-fill.mjs +194 -0
  220. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-ledger-kinds.mjs +127 -13
  221. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-ledger-schema.mjs +33 -16
  222. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-ledger.mjs +29 -9
  223. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-locate.mjs +231 -0
  224. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-pairs.mjs +185 -0
  225. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-recompute.mjs +89 -0
  226. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-binding.mjs +130 -0
  227. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-brief.mjs +5 -0
  228. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-claims.mjs +89 -0
  229. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-lexicon.mjs +145 -0
  230. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-records.mjs +163 -0
  231. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-state.mjs +2 -1
  232. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-trace.mjs +85 -0
  233. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-values.mjs +229 -0
  234. package/packages/omo-codex/plugin/skills/ulw-research/scripts/gates-fidelity.mjs +113 -0
  235. package/packages/omo-codex/plugin/skills/ulw-research/scripts/gates-static.mjs +28 -8
  236. package/packages/omo-codex/plugin/skills/ulw-research/scripts/html-lite.mjs +2 -2
  237. package/packages/omo-codex/plugin/skills/ulw-research/scripts/html-markup.mjs +182 -0
  238. package/packages/omo-codex/plugin/skills/ulw-research/scripts/md-lite.mjs +193 -0
  239. package/packages/omo-codex/plugin/skills/ulw-research/scripts/meaning-check.mjs +196 -0
  240. package/packages/omo-codex/plugin/skills/ulw-research/scripts/outcome.mjs +6 -5
  241. package/packages/omo-codex/plugin/skills/ulw-research/scripts/placeholder.mjs +184 -0
  242. package/packages/omo-codex/plugin/skills/ulw-research/scripts/polish-apply.mjs +115 -0
  243. package/packages/omo-codex/plugin/skills/ulw-research/scripts/polish-diff.mjs +31 -0
  244. package/packages/omo-codex/plugin/skills/ulw-research/scripts/polish-project.mjs +125 -0
  245. package/packages/omo-codex/plugin/skills/ulw-research/scripts/polish-rules.mjs +170 -0
  246. package/packages/omo-codex/plugin/skills/ulw-research/scripts/report-tools-commands.mjs +19 -8
  247. package/packages/omo-codex/plugin/skills/ulw-research/scripts/report-tools.mjs +15 -0
  248. package/packages/omo-codex/plugin/skills/ulw-research/scripts/source-commands.mjs +140 -0
  249. package/packages/omo-codex/plugin/skills/ulw-research/scripts/source-snapshot.mjs +176 -0
  250. package/packages/omo-codex/plugin/skills/ulw-research/scripts/unit-algebra.mjs +101 -0
  251. package/packages/omo-codex/plugin/skills/ulw-research/scripts/unit-errors.mjs +23 -0
  252. package/packages/omo-codex/plugin/skills/ulw-research/scripts/unit-metadata.mjs +31 -0
  253. package/packages/omo-codex/plugin/skills/ulw-research/scripts/unit-pairs.mjs +72 -0
  254. package/packages/omo-codex/plugin/skills/ulw-research/scripts/unit-precision.mjs +44 -0
  255. package/packages/omo-codex/plugin/skills/ulw-research/scripts/unit-rules-shapes.mjs +64 -0
  256. package/packages/omo-codex/plugin/skills/ulw-research/scripts/unit-rules.mjs +173 -0
  257. package/packages/omo-codex/plugin/skills/ulw-research/scripts/units.mjs +4 -4
  258. package/packages/omo-codex/plugin/test/sync-skills-codex-compatibility.test.mjs +19 -0
  259. package/packages/omo-codex/plugin/test/sync-skills-test-support.mjs +7 -3
  260. package/packages/omo-codex/scripts/install-dist/install-local.mjs +28 -19
  261. package/packages/shared-skills/skills/ast-grep/AGENTS.md +1 -1
  262. package/packages/shared-skills/skills/browser/runtime/omowright/manifest.json +1 -1
  263. package/packages/shared-skills/skills/frontend/ATTRIBUTION.md +2 -2
  264. package/packages/shared-skills/skills/ultimate-browsing/engine/AGENTS.md +1 -1
  265. package/packages/shared-skills/skills/ulw-research/AGENTS.md +14 -2
  266. package/packages/shared-skills/skills/ulw-research/SKILL.md +37 -12
  267. package/packages/shared-skills/skills/ulw-research/references/deliverable-phase.md +81 -14
  268. package/packages/shared-skills/skills/ulw-research/references/fact-ledger-grammar.md +19 -1
  269. package/packages/shared-skills/skills/ulw-research/references/fact-ledger.md +95 -0
  270. package/packages/shared-skills/skills/ulw-research/references/fidelity-claim-kinds.md +19 -0
  271. package/packages/shared-skills/skills/ulw-research/references/report-gates.md +21 -4
  272. package/packages/shared-skills/skills/ulw-research/references/report-visualization.md +2 -0
  273. package/packages/shared-skills/skills/ulw-research/references/unit-rules.md +157 -0
  274. package/packages/shared-skills/skills/ulw-research/scripts/claim-kinds.mjs +116 -0
  275. package/packages/shared-skills/skills/ulw-research/scripts/claim-kinds.test.ts +193 -0
  276. package/packages/shared-skills/skills/ulw-research/scripts/cli-support.mjs +19 -5
  277. package/packages/shared-skills/skills/ulw-research/scripts/cli-support.test.ts +13 -2
  278. package/packages/shared-skills/skills/ulw-research/scripts/contracts.mjs +27 -6
  279. package/packages/shared-skills/skills/ulw-research/scripts/contracts.test.ts +3 -0
  280. package/packages/shared-skills/skills/ulw-research/scripts/copy-returns-polish.test.ts +209 -0
  281. package/packages/shared-skills/skills/ulw-research/scripts/copy-returns.test.ts +183 -0
  282. package/packages/shared-skills/skills/ulw-research/scripts/delivery-barrier.test.ts +245 -0
  283. package/packages/shared-skills/skills/ulw-research/scripts/fact-commands-meaning.mjs +176 -0
  284. package/packages/shared-skills/skills/ulw-research/scripts/fact-commands-polish.mjs +127 -0
  285. package/packages/shared-skills/skills/ulw-research/scripts/fact-commands-verify.mjs +183 -0
  286. package/packages/shared-skills/skills/ulw-research/scripts/fact-commands.mjs +241 -0
  287. package/packages/shared-skills/skills/ulw-research/scripts/fact-derive.mjs +66 -0
  288. package/packages/shared-skills/skills/ulw-research/scripts/fact-derive.test.ts +375 -0
  289. package/packages/shared-skills/skills/ulw-research/scripts/fact-expr.mjs +108 -0
  290. package/packages/shared-skills/skills/ulw-research/scripts/fact-fill.mjs +194 -0
  291. package/packages/shared-skills/skills/ulw-research/scripts/fact-fill.test.ts +221 -0
  292. package/packages/shared-skills/skills/ulw-research/scripts/fact-ledger-grammar.test.ts +9 -2
  293. package/packages/shared-skills/skills/ulw-research/scripts/fact-ledger-kinds.mjs +127 -13
  294. package/packages/shared-skills/skills/ulw-research/scripts/fact-ledger-kinds.test.ts +18 -3
  295. package/packages/shared-skills/skills/ulw-research/scripts/fact-ledger-legacy.test.ts +160 -0
  296. package/packages/shared-skills/skills/ulw-research/scripts/fact-ledger-schema.mjs +33 -16
  297. package/packages/shared-skills/skills/ulw-research/scripts/fact-ledger.mjs +29 -9
  298. package/packages/shared-skills/skills/ulw-research/scripts/fact-ledger.test.ts +2 -0
  299. package/packages/shared-skills/skills/ulw-research/scripts/fact-locate.mjs +231 -0
  300. package/packages/shared-skills/skills/ulw-research/scripts/fact-locate.test.ts +118 -0
  301. package/packages/shared-skills/skills/ulw-research/scripts/fact-pairs.mjs +185 -0
  302. package/packages/shared-skills/skills/ulw-research/scripts/fact-pairs.test.ts +151 -0
  303. package/packages/shared-skills/skills/ulw-research/scripts/fact-recompute.mjs +89 -0
  304. package/packages/shared-skills/skills/ulw-research/scripts/fact-unit-dimensions.test.ts +409 -0
  305. package/packages/shared-skills/skills/ulw-research/scripts/fact-unit-rules.test.ts +388 -0
  306. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-binding.mjs +130 -0
  307. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-binding.test.ts +81 -0
  308. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-brief.mjs +5 -0
  309. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-claims.mjs +89 -0
  310. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-lexicon.mjs +145 -0
  311. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-lexicon.test.ts +117 -0
  312. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-records.mjs +163 -0
  313. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-state.mjs +2 -1
  314. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-trace.mjs +85 -0
  315. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-trace.test.ts +58 -0
  316. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-values.mjs +229 -0
  317. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-values.test.ts +270 -0
  318. package/packages/shared-skills/skills/ulw-research/scripts/gates-fidelity.mjs +113 -0
  319. package/packages/shared-skills/skills/ulw-research/scripts/gates-fidelity.test.ts +73 -0
  320. package/packages/shared-skills/skills/ulw-research/scripts/gates-static.mjs +28 -8
  321. package/packages/shared-skills/skills/ulw-research/scripts/html-lite.mjs +2 -2
  322. package/packages/shared-skills/skills/ulw-research/scripts/html-markup.mjs +182 -0
  323. package/packages/shared-skills/skills/ulw-research/scripts/md-lite.mjs +193 -0
  324. package/packages/shared-skills/skills/ulw-research/scripts/md-lite.test.ts +271 -0
  325. package/packages/shared-skills/skills/ulw-research/scripts/meaning-check.mjs +196 -0
  326. package/packages/shared-skills/skills/ulw-research/scripts/meaning-check.test.ts +228 -0
  327. package/packages/shared-skills/skills/ulw-research/scripts/outcome.mjs +6 -5
  328. package/packages/shared-skills/skills/ulw-research/scripts/placeholder.mjs +184 -0
  329. package/packages/shared-skills/skills/ulw-research/scripts/placeholder.test.ts +77 -0
  330. package/packages/shared-skills/skills/ulw-research/scripts/polish-apply.mjs +115 -0
  331. package/packages/shared-skills/skills/ulw-research/scripts/polish-apply.test.ts +363 -0
  332. package/packages/shared-skills/skills/ulw-research/scripts/polish-diff.mjs +31 -0
  333. package/packages/shared-skills/skills/ulw-research/scripts/polish-diff.test.ts +168 -0
  334. package/packages/shared-skills/skills/ulw-research/scripts/polish-markup.test.ts +187 -0
  335. package/packages/shared-skills/skills/ulw-research/scripts/polish-project.mjs +125 -0
  336. package/packages/shared-skills/skills/ulw-research/scripts/polish-rules.mjs +170 -0
  337. package/packages/shared-skills/skills/ulw-research/scripts/references-fences.test.ts +25 -0
  338. package/packages/shared-skills/skills/ulw-research/scripts/report-tools-commands.mjs +19 -8
  339. package/packages/shared-skills/skills/ulw-research/scripts/report-tools.mjs +15 -0
  340. package/packages/shared-skills/skills/ulw-research/scripts/report-tools.test.ts +545 -2
  341. package/packages/shared-skills/skills/ulw-research/scripts/source-commands.mjs +140 -0
  342. package/packages/shared-skills/skills/ulw-research/scripts/source-snapshot.mjs +176 -0
  343. package/packages/shared-skills/skills/ulw-research/scripts/source-snapshot.test.ts +113 -0
  344. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/assets/share.csv +3 -0
  345. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/gen-legacy-dev.mjs +89 -0
  346. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/ledger-basic.json +223 -0
  347. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/ledger-legacy-dev.json +859 -0
  348. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/ledger-verify.json +420 -0
  349. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/polish-edits.json +12 -0
  350. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/report-bad.html +19 -0
  351. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/report-good.html +39 -0
  352. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/sources/S4/acronyms.json +3 -0
  353. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/sources/S4/meta.json +9 -0
  354. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/sources/S4/sentences.jsonl +7 -0
  355. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/sources/S4/text.txt +7 -0
  356. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/sources/S4.html +19 -0
  357. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/sources/S5/acronyms.json +1 -0
  358. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/sources/S5/meta.json +9 -0
  359. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/sources/S5/sentences.jsonl +1 -0
  360. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/sources/S5/text.txt +1 -0
  361. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/sources/S9.txt +1 -0
  362. package/packages/shared-skills/skills/ulw-research/scripts/unit-algebra.mjs +101 -0
  363. package/packages/shared-skills/skills/ulw-research/scripts/unit-errors.mjs +23 -0
  364. package/packages/shared-skills/skills/ulw-research/scripts/unit-metadata.mjs +31 -0
  365. package/packages/shared-skills/skills/ulw-research/scripts/unit-pairs.mjs +72 -0
  366. package/packages/shared-skills/skills/ulw-research/scripts/unit-precision.mjs +44 -0
  367. package/packages/shared-skills/skills/ulw-research/scripts/unit-rules-shapes.mjs +64 -0
  368. package/packages/shared-skills/skills/ulw-research/scripts/unit-rules.mjs +173 -0
  369. package/packages/shared-skills/skills/ulw-research/scripts/units.mjs +4 -4
  370. package/dist/config-migration/dori-section-rename.d.ts +0 -3
@@ -1,6 +1,5 @@
1
- // outcome.mjs - the per-run outcome manifest (`outcome.json`) and the closing briefing.
2
- // Manifest functions are pure (they return a new manifest); the briefing reads only the
3
- // manifest, the sources-ledger text, and an injected `now`. node: builtins only.
1
+ // outcome.mjs - the per-run outcome manifest (`outcome.json`) and the closing briefing. Manifest functions are pure (they return a new manifest).
2
+ // The briefing reads only the manifest, the sources-ledger text, and an injected `now`. node: builtins only.
4
3
 
5
4
  import * as nodeFs from "node:fs"
6
5
  import { basename, dirname, join } from "node:path"
@@ -86,7 +85,7 @@ export function setDeliverable(manifest, format, status, detail = {}) {
86
85
  const next = clone(manifest)
87
86
  const index = next.deliverables.findIndex((row) => row.format === format)
88
87
  const row = { format, status, promised: index >= 0 ? next.deliverables[index].promised : false, updatedAt: stamp(detail.now) }
89
- for (const key of ["path", "reason", "pages", "bytes"]) {
88
+ for (const key of ["path", "reason", "pages", "bytes", "verifiedBy", "renderedFrom"]) {
90
89
  if (detail[key] !== undefined) row[key] = detail[key]
91
90
  }
92
91
  if (index >= 0) next.deliverables[index] = row
@@ -94,11 +93,13 @@ export function setDeliverable(manifest, format, status, detail = {}) {
94
93
  return next
95
94
  }
96
95
 
97
- export function setGate(manifest, gate, status) {
96
+ export function setGate(manifest, gate, status, detail = {}) {
98
97
  assertOneOf("unknown_gate", "gate", gate, GATE_NAMES)
99
98
  assertOneOf("unknown_gate_status", "gate status", status, GATE_STATUSES)
99
+ if (gate === "fidelity" && status === "not_run" && (typeof detail.reason !== "string" || !detail.reason.trim())) throw new OutcomeError("gate_reason_required", "fidelity not_run requires a non-empty --reason")
100
100
  const next = clone(manifest)
101
101
  next.gates[gate] = status
102
+ next.gateHistory = [...(next.gateHistory ?? []), { gate, status, ...(detail.reason !== undefined ? { reason: detail.reason.trim() } : {}), at: stamp(detail.now) }]
102
103
  return next
103
104
  }
104
105
 
@@ -0,0 +1,184 @@
1
+ // placeholder.mjs - the fact pointer grammar of the fill step and its render helpers.
2
+ // Value pointers: {{F12:label}}, {{F12:label:value}}, {{F12:label:unit}}, {{F12:label:q}}.
3
+ // Quote pointers: {{Q7:label}}, {{Q7:label:lead=18}}, {{Q7:label:from="x":to="y"}} (anchors are
4
+ // JSON strings, so they may hold a colon or an escaped quote). Labels are compared exactly by the
5
+ // caller; this module never guesses an id. Pure functions; node: builtins only.
6
+
7
+ import { parseNumeral } from "./numerals.mjs"
8
+
9
+ export const ELLIPSIS = "\u2026"
10
+
11
+ const ID = /^([FQ])\d+$/
12
+ const LABEL = /^[^\s:{}"]+$/
13
+ const VALUE_MODES = new Set(["value", "unit", "q"])
14
+ const LEAD = /^lead=([1-9]\d*)$/
15
+ const LINEAGE = Object.freeze({ measured: "MEASURED", assumed: "ASSUMED", derived: "DERIVED", converted: "DERIVED" })
16
+
17
+ /** End index (exclusive) of a JSON string starting at `at`, or -1 when it is not closed on its line. */
18
+ function jsonStringEnd(text, at) {
19
+ let i = at + 1
20
+ while (i < text.length && text[i] !== '"' && text[i] !== "\n") i += text[i] === "\\" ? 2 : 1
21
+ return text[i] === '"' ? i + 1 : -1
22
+ }
23
+
24
+ function readJsonString(text, at) {
25
+ if (text[at] !== '"') return null
26
+ const end = jsonStringEnd(text, at)
27
+ if (end === -1) return null
28
+ try {
29
+ return { value: JSON.parse(text.slice(at, end)), end }
30
+ } catch {
31
+ return null
32
+ }
33
+ }
34
+
35
+ function parseAnchors(tail) {
36
+ if (!tail.startsWith("from=")) return null
37
+ const from = readJsonString(tail, 5)
38
+ if (from === null || !tail.startsWith(":to=", from.end)) return null
39
+ const to = readJsonString(tail, from.end + 4)
40
+ if (to === null || to.end !== tail.length || from.value === "" || to.value === "") return null
41
+ return { from: from.value, to: to.value }
42
+ }
43
+
44
+ /** @returns the parsed pointer, or { error } for a malformed one. */
45
+ export function parsePlaceholder(token) {
46
+ if (!token.startsWith("{{") || !token.endsWith("}}")) return { error: "a placeholder is {{ID:label...}}" }
47
+ const body = token.slice(2, -2)
48
+ const colon = body.indexOf(":")
49
+ const id = colon === -1 ? body : body.slice(0, colon)
50
+ const idMatch = ID.exec(id)
51
+ if (idMatch === null) return { error: `"${id}" is not an F<n> or Q<n> id` }
52
+ if (colon === -1) return { error: `${id} has no label; write {{${id}:label}}` }
53
+ const rest = body.slice(colon + 1)
54
+ const second = rest.indexOf(":")
55
+ const label = second === -1 ? rest : rest.slice(0, second)
56
+ if (!LABEL.test(label)) return { error: `"${label}" is not a label` }
57
+ const tail = second === -1 ? null : rest.slice(second + 1)
58
+ const base = { id, kind: idMatch[1], label }
59
+ if (idMatch[1] === "F") {
60
+ if (tail === null) return { ...base, mode: "full" }
61
+ return VALUE_MODES.has(tail) ? { ...base, mode: tail } : { error: `"${tail}" is not a value mode (value, unit, q)` }
62
+ }
63
+ if (tail === null) return { ...base, mode: "full", selector: "full" }
64
+ const lead = LEAD.exec(tail)
65
+ if (lead !== null) return { ...base, mode: "lead", lead: Number(lead[1]), selector: `lead=${lead[1]}` }
66
+ const anchors = parseAnchors(tail)
67
+ if (anchors === null) return { error: `"${tail}" is not a quote selector (lead=N or from="..":to="..")` }
68
+ return { ...base, mode: "span", ...anchors, selector: `from=${anchors.from};to=${anchors.to}` }
69
+ }
70
+
71
+ /** End index (exclusive) of the pointer opened at `at`, or -1 when it is not closed on its line. */
72
+ function placeholderEnd(text, at) {
73
+ let i = at + 2
74
+ while (i < text.length && text[i] !== "\n") {
75
+ if (text[i] === '"') {
76
+ const end = jsonStringEnd(text, i)
77
+ if (end === -1) return -1
78
+ i = end
79
+ } else if (text.startsWith("}}", i)) return i + 2
80
+ else i++
81
+ }
82
+ return -1
83
+ }
84
+
85
+ /** Every `{{...}}` pointer in source order: [{ token, start, end, parsed }]. */
86
+ export function findPlaceholders(text) {
87
+ const found = []
88
+ let at = text.indexOf("{{")
89
+ while (at !== -1) {
90
+ const end = placeholderEnd(text, at)
91
+ if (end === -1) {
92
+ found.push({ token: text.slice(at, text.indexOf("\n", at) === -1 ? text.length : text.indexOf("\n", at)), start: at, end: at + 2, parsed: { error: "the placeholder is not closed on its line" } })
93
+ at = text.indexOf("{{", at + 2)
94
+ continue
95
+ }
96
+ const token = text.slice(at, end)
97
+ found.push({ token, start: at, end, parsed: parsePlaceholder(token) })
98
+ at = text.indexOf("{{", end)
99
+ }
100
+ return found
101
+ }
102
+
103
+ function occurrences(text, needle) {
104
+ let count = 0
105
+ for (let at = text.indexOf(needle); at !== -1; at = text.indexOf(needle, at + 1)) count++
106
+ return count
107
+ }
108
+
109
+ function cut(quote, start, end) {
110
+ return { text: `${start > 0 ? ELLIPSIS : ""}${quote.slice(start, end)}${end < quote.length ? ELLIPSIS : ""}`, start, end }
111
+ }
112
+
113
+ /** The contiguous span from `from` through `to`; each anchor must occur exactly once, in order. */
114
+ export function subSpan(quote, from, to) {
115
+ if (occurrences(quote, from) !== 1 || occurrences(quote, to) !== 1) return null
116
+ const start = quote.indexOf(from)
117
+ const toAt = quote.indexOf(to)
118
+ if (toAt < start || toAt + to.length < start + from.length) return null
119
+ return cut(quote, start, toAt + to.length)
120
+ }
121
+
122
+ /** The first `n` whitespace-delimited words, with an ellipsis when words were cut. */
123
+ export function lead(quote, n) {
124
+ const words = [...quote.matchAll(/\S+/g)]
125
+ if (words.length <= n) return { text: quote, start: 0, end: quote.length }
126
+ const last = words[n - 1]
127
+ return cut(quote, 0, last.index + last[0].length)
128
+ }
129
+
130
+ /** The value and unit parts of a row's raw text ("4.2 eok won" -> value "4.2 eok", unit "won"). */
131
+ export function valueParts(row) {
132
+ const unitText = parseNumeral(row.raw)?.unitRaw ?? row.unit
133
+ const valueText = unitText && row.raw.endsWith(unitText) ? row.raw.slice(0, -unitText.length).trimEnd() : (row.value ?? row.raw)
134
+ return { valueText, unitText: unitText ?? "" }
135
+ }
136
+
137
+ /** The quote interval holding the row's qualifier and its raw quantity, or null if absent. */
138
+ export function qualifiedText(row) {
139
+ if (row.qualifier === null) return row.raw
140
+ for (let numeral = parseNumeral(row.quote); numeral !== null; numeral = parseNumeral(row.quote, numeral.span[1])) {
141
+ if (numeral.raw !== row.raw || numeral.qualifierRaw === null) continue
142
+ const [start, end] = numeral.span
143
+ const before = row.quote.lastIndexOf(numeral.qualifierRaw, start)
144
+ const between = before === -1 ? [] : row.quote.slice(before + numeral.qualifierRaw.length, start).split(/\s+/u).filter(Boolean)
145
+ if (before !== -1 && between.length <= 1) return row.quote.slice(before, end)
146
+ const after = row.quote.indexOf(numeral.qualifierRaw, end)
147
+ if (after !== -1) return row.quote.slice(start, after + numeral.qualifierRaw.length)
148
+ }
149
+ return null
150
+ }
151
+
152
+ export function escapeHtml(text) {
153
+ return String(text).replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;")
154
+ }
155
+
156
+ /** The elements the renderers below emit as fact spans; recognizers build their patterns from this. */
157
+ export const FACT_ELEMENT_TAGS = Object.freeze(["data", "q", "blockquote"])
158
+
159
+ export function renderValueHtml(row, text) {
160
+ const unit = row.unit === null ? "" : ` data-unit="${escapeHtml(row.unit)}"`
161
+ return `<data class="fact" value="${escapeHtml(row.value)}" data-fact="${row.fact_id}"${unit} data-lineage="${LINEAGE[row.kind]}">${escapeHtml(text)}</data>`
162
+ }
163
+
164
+ export function renderQuote(row, selector, text, format) {
165
+ const marker = `[${row.source_id}]`
166
+ if (format === "md") return `"${text}" ${marker}`
167
+ return `<q data-fact="${row.fact_id}" data-quote-sel="${escapeHtml(selector)}">${escapeHtml(text)}</q> ${marker}`
168
+ }
169
+
170
+ const RENDERED_MD_QUOTE = /^"([\s\S]*)" (\[S\d+\])$/u
171
+
172
+ /** The quote text and source marker of bytes renderQuote wrote for md, or null for any other layout. */
173
+ export function parseRenderedQuote(body) {
174
+ const match = RENDERED_MD_QUOTE.exec(body)
175
+ return match ? { text: match[1], marker: match[2] } : null
176
+ }
177
+
178
+ /** The attributed block that replaces a twice-failed sentence; entity and valid_at stay outside the quote. */
179
+ export function renderBlockquote(row, key, format) {
180
+ const attribution = row.entity === null ? "" : `, on ${row.entity}${row.valid_at === null ? "" : ` (${row.valid_at})`}`
181
+ const footer = `[${row.source_id}]${attribution}`
182
+ if (format === "md") return `> "${row.quote}"\n> ${footer}`
183
+ return `<blockquote data-fact="${row.fact_id}" data-replaced="${escapeHtml(key)}">"${escapeHtml(row.quote)}" <footer>${escapeHtml(footer)}</footer></blockquote>`
184
+ }
@@ -0,0 +1,115 @@
1
+ // polish-apply.mjs - checked, per-edit application of a proofread lane's polish edits.
2
+ //
3
+ // An edit whose before or after holds <, > or & is polish_edit_rejected (POLISH_MARKUP_REASON)
4
+ // before anything else, so no edit can create, alter, close or escape a tag, comment,
5
+ // bogus comment, CDATA section, entity or script, whatever the tokenizer models; a located region
6
+ // that overlaps a character reference or opens one (it starts inside `&name`) is rejected with
7
+ // POLISH_REFERENCE_REASON, so an edit can neither change nor complete an existing reference.
8
+ // Those two guards are the early, actionable refusals; the guarantee itself is the whole-document
9
+ // skeleton comparison (html-markup.skeletonChange) that polish-rules runs in both gates: every
10
+ // markup token and every character reference stays byte-identical, and an accepted edit's span
11
+ // holds no skeleton token, so any difference refuses the edit and reports polish_diff_fail.
12
+ // applyEdits(html, edits, ledger) applies each {before, after, reason} edit in edits-file order,
13
+ // against the output of the edits accepted before it. `before` must occur exactly once (runs of
14
+ // whitespace match any whitespace run) and must not cut through a fact span; otherwise the edit is
15
+ // polish_edit_unlocated. A located edit is applied whole or not at all: it is polish_edit_rejected
16
+ // when polish-rules.polishDefects(..., {ordered: true}) finds any defect between the current
17
+ // document and the document with the edit (per edit), or between the ORIGINAL input and the
18
+ // document with the edit (whole document: the baseline polish-diff uses). Ordered is stricter than
19
+ // polish-diff's multiset comparison and the last accepted candidate is the output, so a clean run
20
+ // is always polish-diff clean: no cross-edit case is left to the backstop. The rules see the
21
+ // text-node projection and freeze every markup byte, including script/style/template content.
22
+ // validatePolishEdits and POLISH_EDIT_FIELDS are the edit
23
+ // contract every caller (the CLI included) uses. No IO, no model. Plain Node ESM.
24
+
25
+ import { CHARACTER_REFERENCE } from "./html-markup.mjs"
26
+ import { factSpans } from "./polish-project.mjs"
27
+ import { polishDefects } from "./polish-rules.mjs"
28
+
29
+ export { numeralKeys, project, SLOT, termCounts, textOf } from "./polish-project.mjs"
30
+
31
+ /** The fields of one polish edit: before and after are required strings, reason an optional string. */
32
+ export const POLISH_EDIT_FIELDS = Object.freeze({ before: "required", after: "required", reason: "optional" })
33
+
34
+ /** Throws a TypeError naming the first edit that breaks POLISH_EDIT_FIELDS; returns the edits. */
35
+ export function validatePolishEdits(edits) {
36
+ if (!Array.isArray(edits)) throw new TypeError("edits must be an array")
37
+ edits.forEach((edit, index) => {
38
+ if (edit === null || typeof edit !== "object" || Array.isArray(edit)) throw new TypeError(`edit ${index} must be an object`)
39
+ for (const [field, need] of Object.entries(POLISH_EDIT_FIELDS)) {
40
+ if (need === "optional" && edit[field] === undefined) continue
41
+ if (typeof edit[field] !== "string") throw new TypeError(`edit ${index} needs ${need === "optional" ? "a string" : "string"} ${field}`)
42
+ }
43
+ })
44
+ return edits
45
+ }
46
+
47
+ /** Characters an edit's before and after may never contain. */
48
+ export const POLISH_MARKUP_CHARACTERS = /[<>&]/
49
+ /** The actionable rejection reason for an edit holding a markup character. */
50
+ export const POLISH_MARKUP_REASON = "edit contains markup characters (<, >, &): route this section through the full fidelity gates"
51
+ /** The actionable rejection reason for a located region touching a character reference. */
52
+ export const POLISH_REFERENCE_REASON = "edit touches a character reference: route this section through the full fidelity gates"
53
+
54
+ /** True when [start, end) overlaps a character reference of html or starts inside one. */
55
+ export function touchesReference(html, start, end) {
56
+ for (const match of String(html).matchAll(CHARACTER_REFERENCE)) {
57
+ if (match.index < end && match.index + match[0].length > start) return true
58
+ }
59
+ return /&[#\w]*$/.test(html.slice(Math.max(0, start - 16), start))
60
+ }
61
+
62
+ const escape = (text) => text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")
63
+
64
+ /** The single whitespace-normalized match of `before` in html as {start, end}, or {count} otherwise. */
65
+ function locate(html, before) {
66
+ if (before.trim() === "") return { count: 0 }
67
+ const pattern = new RegExp(before.split(/\s+/).map(escape).join("\\s+"), "g")
68
+ const hits = [...html.matchAll(pattern)]
69
+ return hits.length === 1 ? { count: 1, start: hits[0].index, end: hits[0].index + hits[0][0].length } : { count: hits.length }
70
+ }
71
+
72
+ /** The id of a fact span the region [start, end) cuts through (overlaps without containing), or null. */
73
+ export function cutSpan(html, start, end) {
74
+ return factSpans(html).find((span) => span.start < end && span.end > start && !(start <= span.start && span.end <= end))?.id ?? null
75
+ }
76
+
77
+ /**
78
+ * @param {string} html the filled report
79
+ * @param {{ before: string, after: string, reason?: string }[]} edits in edits-file order
80
+ * @param {{ rows: object[] }} ledger
81
+ * @returns {{ out: string, applied: number[], rejected: { index: number, code: string, reason: string }[],
82
+ * unlocated: { index: number, code: string, reason: string }[] }}
83
+ */
84
+ export function applyEdits(html, edits, ledger) {
85
+ validatePolishEdits(edits)
86
+ const original = String(html)
87
+ const result = { out: original, applied: [], rejected: [], unlocated: [] }
88
+ const defects = (candidate) => {
89
+ const messages = [result.out, original].flatMap((base) => polishDefects(base, candidate, ledger, { ordered: true }).map((d) => d.message))
90
+ return [...new Set(messages)]
91
+ }
92
+ edits.forEach((edit, index) => {
93
+ const touches = POLISH_MARKUP_CHARACTERS.test(edit.before) || POLISH_MARKUP_CHARACTERS.test(edit.after)
94
+ const where = locate(result.out, edit.before)
95
+ if (where.count !== 1 && !touches) {
96
+ result.unlocated.push({ index, code: "polish_edit_unlocated", reason: `before matches ${where.count} times; it must match exactly once` })
97
+ return
98
+ }
99
+ const reasons = touches ? [POLISH_MARKUP_REASON] : []
100
+ const candidate = where.count === 1 ? result.out.slice(0, where.start) + edit.after + result.out.slice(where.end) : result.out
101
+ if (where.count === 1) {
102
+ const cut = cutSpan(result.out, where.start, where.end)
103
+ if (touchesReference(result.out, where.start, where.end)) reasons.push(POLISH_REFERENCE_REASON)
104
+ reasons.push(...defects(candidate))
105
+ if (cut !== null) reasons.push(`the edit cuts through fact span ${cut}`)
106
+ }
107
+ if (reasons.length > 0) {
108
+ result.rejected.push({ index, code: "polish_edit_rejected", reason: reasons.join("; ") })
109
+ return
110
+ }
111
+ result.out = candidate
112
+ result.applied.push(index)
113
+ })
114
+ return result
115
+ }
@@ -0,0 +1,31 @@
1
+ // polish-diff.mjs - whole-document backstop between a pre-polish artifact and its polished result.
2
+ //
3
+ // diffPolish(beforeHtml, afterHtml, ledger) runs the polish-rules.mjs rule set (the same rules
4
+ // polish-apply runs per edit) over the whole document, as multisets, and returns at most one defect
5
+ // per code. Severity is looked up in contracts.DEFECT_CODES for every emitted code; a code missing
6
+ // there throws. polish-apply checks every edit against this same whole-document baseline (ordered,
7
+ // so stricter), so a clean polish-apply run is clean here; this catches hand edits. No IO, no model.
8
+ // Plain Node ESM.
9
+
10
+ import { DEFECT_CODES } from "./contracts.mjs"
11
+ import { POLISH_CODES, polishDefects } from "./polish-rules.mjs"
12
+
13
+ export { churnRatio, TICKER } from "./polish-rules.mjs"
14
+
15
+ /** The DEFECT_CODES entry of a polish code; refuses a code the registry does not define. */
16
+ function registryEntry(code) {
17
+ if (!Object.hasOwn(DEFECT_CODES, code)) throw new Error(`polish code ${code} is not in DEFECT_CODES`)
18
+ return DEFECT_CODES[code]
19
+ }
20
+
21
+ /**
22
+ * @param {string} beforeHtml the pre-polish artifact
23
+ * @param {string} afterHtml the polished artifact
24
+ * @param {{ rows: object[] }} ledger
25
+ * @returns {{ code: string, severity: string, message: string }[]} at most one defect per code
26
+ */
27
+ export function diffPolish(beforeHtml, afterHtml, ledger) {
28
+ const found = new Map()
29
+ for (const { code, message } of polishDefects(beforeHtml, afterHtml, ledger)) found.set(code, [...(found.get(code) ?? []), message])
30
+ return POLISH_CODES.filter((code) => found.has(code)).map((code) => ({ code, severity: registryEntry(code).severity, message: found.get(code).join("; ") }))
31
+ }
@@ -0,0 +1,125 @@
1
+ // polish-project.mjs - the protected projection of report markup that every polish check compares.
2
+ //
3
+ // project(markup) yields the ordered fact spans {id, html}, the visible text, the masked text (each
4
+ // fact span replaced by SLOT) and the ordered href values. Callers pass markup already reduced by
5
+ // html-markup.markupParts().source, with frozen script/style/template content omitted. Fact spans are the
6
+ // placeholder.mjs FACT_ELEMENT_TAGS elements carrying data-fact (any quoting), found by
7
+ // html-markup.elementSpans (a same-name nested element and a quoted ">" stay inside). Hedge and
8
+ // qualifier terms come from fidelity-lexicon (guardedTerms, lexicalPattern "guard" mode).
9
+ // No IO, no model. Plain Node ESM.
10
+
11
+ import { decodeEntities } from "./entities.mjs"
12
+ import { guardedTerms, lexicalPattern, QUALIFIER_FORMS } from "./fidelity-lexicon.mjs"
13
+ import { elementSpans, markupParts, tagAttributes } from "./html-markup.mjs"
14
+ import { HEDGES } from "./lexicon.mjs"
15
+ import { parseNumeral } from "./numerals.mjs"
16
+ import { FACT_ELEMENT_TAGS } from "./placeholder.mjs"
17
+ import { splitSentences } from "./sentence-split.mjs"
18
+ import { normalizeUnit } from "./units.mjs"
19
+
20
+ /** The link attributes polish_link_changed owns; a data-claim id change is a markup (fact-binding) change. */
21
+ export const POLISH_LINK_ATTRIBUTES = Object.freeze(["href"])
22
+ /** The bare digit runs compared outside fact spans when no quantity parses around them. */
23
+ export const POLISH_DIGITS = /\d+(?:[.,]\d+)*/g
24
+ /** Stands in for one fact span in masked text; a private-use char, so no numeral or word matches it. */
25
+ export const SLOT = "\uE000"
26
+ const FACT_TAGS = new Set(FACT_ELEMENT_TAGS)
27
+ const isFactElement = (name, attrs) => FACT_TAGS.has(name) && (attrs["data-fact"] ?? "") !== ""
28
+
29
+ /** Each fact span of the markup as {id, start, end, html}, in order; an unclosed one runs to the end. */
30
+ export function factSpans(markup) {
31
+ const src = String(markup)
32
+ return elementSpans(src, isFactElement).map(({ start, end, tag }) => ({ id: tag.attrs["data-fact"], start, end, html: src.slice(start, end) }))
33
+ }
34
+
35
+ /**
36
+ * Visible text of an HTML fragment: the scanner's text slots joined by a space (so a quoted ">"
37
+ * inside a tag stays markup), entities decoded, whitespace collapsed. One tokenization is shared
38
+ * with the markup rules; no second tag-stripping regex.
39
+ */
40
+ export function textOf(html) {
41
+ return decodeEntities(markupParts(html).text.join(" ")).replace(/\s+/g, " ")
42
+ }
43
+
44
+ /** The protected projection of (already visible) markup; see the header. */
45
+ export function project(markup) {
46
+ const facts = []
47
+ let visible = ""
48
+ let masked = ""
49
+ let at = 0
50
+ for (const span of factSpans(markup)) {
51
+ const outside = textOf(markup.slice(at, span.start))
52
+ facts.push({ id: span.id, html: span.html.replace(/\s+/g, " ") })
53
+ visible += `${outside}${textOf(span.html)}`
54
+ masked += `${outside} ${SLOT} `
55
+ at = span.end
56
+ }
57
+ const rest = textOf(markup.slice(at))
58
+ const links = tagAttributes(markup).flatMap(({ attrs }) =>
59
+ POLISH_LINK_ATTRIBUTES.filter((name) => Object.hasOwn(attrs, name)).map((name) => `${name}=${attrs[name]}`),
60
+ )
61
+ return { facts, visible: (visible + rest).replace(/\s+/g, " ").trim(), masked: (masked + rest).replace(/\s+/g, " ").trim(), links }
62
+ }
63
+
64
+ const unitKey = (raw) => (raw === null ? "" : (normalizeUnit(raw) ?? raw.toLowerCase()))
65
+
66
+ /** Ordered numeral keys: a parsed quantity as "value unit" (once), any other digit run as written. */
67
+ export function numeralKeys(text) {
68
+ const quantities = []
69
+ for (let found = parseNumeral(text); found !== null; found = parseNumeral(text, found.span[1])) quantities.push(found)
70
+ const keys = []
71
+ let k = 0
72
+ for (const match of text.matchAll(POLISH_DIGITS)) {
73
+ while (k < quantities.length && quantities[k].span[1] <= match.index) k++
74
+ const quantity = quantities[k]
75
+ if (!quantity || quantity.span[0] > match.index) keys.push(match[0])
76
+ else if (!quantity.counted) {
77
+ quantity.counted = true
78
+ keys.push(`${quantity.value}${quantity.range ? `~${quantity.valueEnd}` : ""} ${unitKey(quantity.unitRaw)}`.trim())
79
+ }
80
+ }
81
+ return keys
82
+ }
83
+
84
+ /** term -> occurrence count of each listed term in the text (guard mode of the lexical engine). */
85
+ export function termCounts(text, terms = guardedTerms()) {
86
+ const counts = new Map()
87
+ for (const term of terms) {
88
+ const n = [...text.matchAll(lexicalPattern(term, "guard"))].length
89
+ if (n > 0) counts.set(term, n)
90
+ }
91
+ return counts
92
+ }
93
+
94
+ const add = (into, term, n) => into.set(term, (into.get(term) ?? 0) + n)
95
+
96
+ /**
97
+ * The hedge and qualifier tokens the text before a polish holds and must keep (term -> count), per
98
+ * sentence of the masked text: a sentence without fact spans keeps all its guarded tokens; a
99
+ * sentence with fact spans keeps those of its tokens that the referenced rows' quote hedges or
100
+ * qualifier forms name. A ledger term the sentence never held is never required.
101
+ */
102
+ export function requiredTerms(projection, rows) {
103
+ const required = new Map()
104
+ let cursor = 0
105
+ for (const { text } of splitSentences(projection.masked)) {
106
+ const slots = text.split(SLOT).length - 1
107
+ const facts = projection.facts.slice(cursor, cursor + slots)
108
+ cursor += slots
109
+ const present = termCounts(text)
110
+ const sourced = new Set()
111
+ for (const { id } of facts) {
112
+ const row = rows.get(id)
113
+ for (const term of termCounts(row?.quote ?? "", HEDGES).keys()) sourced.add(term)
114
+ for (const form of QUALIFIER_FORMS[row?.qualifier] ?? []) sourced.add(form)
115
+ }
116
+ for (const [term, n] of present) if (slots === 0 || sourced.has(term)) add(required, term, n)
117
+ }
118
+ return required
119
+ }
120
+
121
+ /** Required terms the text no longer holds as many times, as "term" names. */
122
+ export function missingTerms(required, text) {
123
+ const have = termCounts(text, [...required.keys()])
124
+ return [...required].filter(([term, n]) => (have.get(term) ?? 0) < n).map(([term]) => term)
125
+ }
@@ -0,0 +1,170 @@
1
+ // polish-rules.mjs - the one rule set behind polish-apply (per edit) and polish-diff (whole document).
2
+ //
3
+ // polishDefects(beforeHtml, afterHtml, ledger, {ordered}) compares the visible projections
4
+ // (html-markup.markupParts, then polish-project.project) and returns {code, message} per failure.
5
+ // Polish may change text nodes only. The primary rule is the whole-document skeleton comparison
6
+ // (html-markup.skeletonChange): every markup token (tags, comments, bogus comments and frozen
7
+ // script/style/template content) and every character reference of a text node must be byte-identical
8
+ // in document order; an edited span holds no skeleton token, so any difference is polish_diff_fail
9
+ // naming the first differing token and its offset. The remaining checks compare the projections:
10
+ // polish_diff_fail: fact spans (id + rendered markup), numerals outside fact spans (with units),
11
+ // tickers (TICKER: 1-5 capitals with an optional ".XX" class, or a 6-digit KRX code), or a
12
+ // table's row count changed; any tag, attribute, comment, declaration, or script/style/template
13
+ // content changed; protected tokens also stay within their original text-node boundaries;
14
+ // polish_hedging_decreased: a hedge or qualifier the before text holds and must keep is gone;
15
+ // polish_text_churn: more than POLISH_CHURN_LIMIT of the normalized visible prose changed;
16
+ // polish_link_changed: href values changed or an internal #target stopped resolving;
17
+ // polish_name_changed: a proper noun (POLISH_NAME) of a ledger quote, present before, is gone.
18
+ // A document holding a construct html-markup.ambiguousMarkup lists (</ + non-letter, <!-- inside
19
+ // script/style, <![CDATA[, <?, --!>, or < inside a tag) is refused first with polish_diff_fail
20
+ // "ambiguous markup": the byte comparison is only trusted where the tokenizer is exact.
21
+ // ordered (polish-apply) also refuses a reordering of fact spans, numerals, tickers and links;
22
+ // without it they compare as multisets. Severity lives only in contracts.DEFECT_CODES.
23
+ // No IO, no model. Plain Node ESM.
24
+
25
+ import { ambiguousMarkup, markupParts, skeletonChange, tagAttributes } from "./html-markup.mjs"
26
+ import { missingTerms, numeralKeys, project, requiredTerms, termCounts, textOf } from "./polish-project.mjs"
27
+ import { splitSentences } from "./sentence-split.mjs"
28
+ import { normalizeText } from "./text-normalize.mjs"
29
+
30
+ /** The codes polishDefects can emit, in report order. */
31
+ export const POLISH_CODES = Object.freeze(["polish_diff_fail", "polish_hedging_decreased", "polish_text_churn", "polish_link_changed", "polish_name_changed"])
32
+ export const TICKER = /(?<![\p{L}\p{N}])(?:[A-Z]{1,5}(?:\.[A-Z]{1,2})?|\d{6})(?![\p{L}\p{N}])/gu
33
+ /** The capitalized-token heuristic for proper nouns in ledger quotes. */
34
+ export const POLISH_NAME = /\p{Lu}[\p{L}\p{N}&'-]+/gu
35
+ /** Above this share of changed normalized prose a polish composed instead of proofreading. */
36
+ export const POLISH_CHURN_LIMIT = 0.5
37
+ /** Above this many cells a gap counts as fully rewritten instead of running the quadratic distance. */
38
+ const DISTANCE_CELLS = 25_000_000
39
+
40
+ const sorted = (list) => [...list].sort()
41
+ const same = (a, b) => a.length === b.length && a.every((item, k) => item === b[k])
42
+
43
+ /** null when unchanged; a reorder counts only when ordered. */
44
+ function listChange(label, before, after, ordered) {
45
+ if (same(before, after)) return null
46
+ const remaining = sorted(before)
47
+ const added = []
48
+ for (const item of after) {
49
+ const at = remaining.indexOf(item)
50
+ if (at === -1) added.push(item)
51
+ else remaining.splice(at, 1)
52
+ }
53
+ if (remaining.length > 0 || added.length > 0) return `${label} changed: removed [${remaining.join(", ")}], added [${added.join(", ")}]`
54
+ return ordered ? `${label} reordered: [${before.join(", ")}] became [${after.join(", ")}]` : null
55
+ }
56
+
57
+ const tableRows = (html) => html.split(/<table\b/i).slice(1).map((part) => (part.split(/<\/table\s*>/i)[0].match(/<tr\b/gi) ?? []).length)
58
+
59
+ function levenshtein(a, b) {
60
+ if (a === b) return 0
61
+ if (a.length * b.length > DISTANCE_CELLS) return Math.max(a.length, b.length)
62
+ let previous = Array.from({ length: b.length + 1 }, (_, k) => k)
63
+ for (let i = 1; i <= a.length; i++) {
64
+ const current = [i]
65
+ for (let j = 1; j <= b.length; j++) {
66
+ current[j] = Math.min(previous[j] + 1, current[j - 1] + 1, previous[j - 1] + (a[i - 1] === b[j - 1] ? 0 : 1))
67
+ }
68
+ previous = current
69
+ }
70
+ return previous[b.length]
71
+ }
72
+
73
+ /** Changed share of normalized prose: edit distance over unmatched sentence runs / longer length. */
74
+ export function churnRatio(beforeText, afterText) {
75
+ const [a, b] = [normalizeText(beforeText), normalizeText(afterText)]
76
+ const longest = Math.max(a.length, b.length)
77
+ if (longest === 0) return 0
78
+ const [sa, sb] = [splitSentences(a).map((s) => s.text), splitSentences(b).map((s) => s.text)]
79
+ const lcs = Array.from({ length: sa.length + 1 }, () => new Array(sb.length + 1).fill(0))
80
+ for (let i = sa.length - 1; i >= 0; i--) {
81
+ for (let j = sb.length - 1; j >= 0; j--) lcs[i][j] = sa[i] === sb[j] ? lcs[i + 1][j + 1] + 1 : Math.max(lcs[i + 1][j], lcs[i][j + 1])
82
+ }
83
+ let distance = 0
84
+ let gap = [[], []]
85
+ const flush = () => {
86
+ distance += levenshtein(gap[0].join(" "), gap[1].join(" "))
87
+ gap = [[], []]
88
+ }
89
+ let [i, j] = [0, 0]
90
+ while (i < sa.length || j < sb.length) {
91
+ if (i < sa.length && j < sb.length && sa[i] === sb[j]) {
92
+ flush()
93
+ i++
94
+ j++
95
+ } else if (j >= sb.length || (i < sa.length && lcs[i + 1][j] >= lcs[i][j + 1])) gap[0].push(sa[i++])
96
+ else gap[1].push(sb[j++])
97
+ }
98
+ flush()
99
+ return distance / longest
100
+ }
101
+
102
+ function linkChange(from, to, before, after, ordered) {
103
+ const change = listChange("href and data-claim values", from.links, to.links, ordered)
104
+ if (change) return change
105
+ const ids = (html) => new Set(tagAttributes(html).flatMap(({ attrs }) => (Object.hasOwn(attrs, "id") ? [attrs.id] : [])))
106
+ const [idsBefore, idsAfter] = [ids(before), ids(after)]
107
+ const targets = to.links.filter((link) => link.startsWith("href=#")).map((link) => link.slice(5))
108
+ const broken = targets.filter((href) => idsBefore.has(href.slice(1)) && !idsAfter.has(href.slice(1)))
109
+ return broken.length > 0 ? `internal link targets stopped resolving: [${[...new Set(broken)].join(", ")}]` : null
110
+ }
111
+
112
+ /** Capitalized tokens of the ledger quotes, minus each sentence's first word. */
113
+ function ledgerNames(rows) {
114
+ const names = new Set()
115
+ for (const row of rows) {
116
+ for (const { text } of splitSentences(row.quote ?? "")) {
117
+ const first = text.match(/^\P{L}*\p{L}+/u)?.[0].length ?? 0
118
+ for (const match of text.matchAll(POLISH_NAME)) if (match.index >= first) names.add(match[0])
119
+ }
120
+ }
121
+ return names
122
+ }
123
+
124
+ const hasWord = (text, word) => new RegExp(`(?<![\\p{L}\\p{N}])${word.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(?![\\p{L}\\p{N}])`, "u").test(text)
125
+
126
+ /**
127
+ * @param {string} beforeHtml
128
+ * @param {string} afterHtml
129
+ * @param {{ rows: object[] }} ledger
130
+ * @param {{ ordered?: boolean }} [options]
131
+ * @returns {{ code: string, message: string }[]} in POLISH_CODES order
132
+ */
133
+ export function polishDefects(beforeHtml, afterHtml, ledger, { ordered = false } = {}) {
134
+ const ambiguous = [...new Set([...ambiguousMarkup(beforeHtml), ...ambiguousMarkup(afterHtml)])]
135
+ if (ambiguous.length > 0) return [{ code: "polish_diff_fail", message: `ambiguous markup: ${ambiguous.join(", ")}` }]
136
+ const [beforeParts, afterParts] = [markupParts(beforeHtml), markupParts(afterHtml)]
137
+ const [before, after] = [beforeParts.source, afterParts.source]
138
+ const [from, to] = [project(before), project(after)]
139
+ const rows = ledger?.rows ?? []
140
+ const defects = []
141
+ const note = (code, message) => message && defects.push({ code, message })
142
+ const factKey = (fact) => `${fact.id} ${fact.html}`
143
+ const tickers = (text) => text.match(TICKER) ?? []
144
+ const markupEqual = same(beforeParts.markup, afterParts.markup)
145
+ note("polish_diff_fail", markupEqual ? null : "markup changed")
146
+ note("polish_diff_fail", skeletonChange(beforeHtml, afterHtml))
147
+ note("polish_diff_fail", listChange("fact spans", from.facts.map(factKey), to.facts.map(factKey), ordered))
148
+ note("polish_diff_fail", listChange("numerals outside fact spans", numeralKeys(from.masked), numeralKeys(to.masked), ordered))
149
+ note("polish_diff_fail", listChange("tickers", tickers(from.masked), tickers(to.masked), ordered))
150
+ const [rowsBefore, rowsAfter] = [tableRows(before), tableRows(after)]
151
+ if (!same(rowsBefore, rowsAfter)) note("polish_diff_fail", `table row counts changed: [${rowsBefore}] became [${rowsAfter}]`)
152
+ const required = requiredTerms(from, new Map(rows.map((row) => [row.fact_id, row])))
153
+ if (markupEqual) {
154
+ for (const [index, raw] of beforeParts.text.entries()) {
155
+ const [a, b] = [raw, afterParts.text[index]].map(textOf)
156
+ note("polish_diff_fail", listChange("text-node numerals", numeralKeys(a), numeralKeys(b), ordered))
157
+ note("polish_diff_fail", listChange("text-node tickers", tickers(a), tickers(b), ordered))
158
+ const lost = missingTerms(termCounts(a, [...required.keys()]), b)
159
+ if (lost.length) note("polish_hedging_decreased", `text-node hedges removed: ${lost.join(", ")}`)
160
+ }
161
+ }
162
+ const missing = missingTerms(required, to.visible)
163
+ if (missing.length > 0) note("polish_hedging_decreased", `hedges or qualifiers removed: ${missing.map((term) => `"${term}"`).join(", ")}`)
164
+ const ratio = churnRatio(from.visible, to.visible)
165
+ if (ratio > POLISH_CHURN_LIMIT) note("polish_text_churn", `${(ratio * 100).toFixed(1)} percent of the normalized prose changed`)
166
+ note("polish_link_changed", linkChange(from, to, before, after, ordered))
167
+ const lost = [...ledgerNames(rows)].filter((name) => hasWord(from.visible, name) && !hasWord(to.visible, name))
168
+ if (lost.length > 0) note("polish_name_changed", `ledger-quote names missing after polish: ${lost.join(", ")}`)
169
+ return defects.sort((a, b) => POLISH_CODES.indexOf(a.code) - POLISH_CODES.indexOf(b.code))
170
+ }