lazycodex-ai 5.1.44 → 5.1.46

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 (412) hide show
  1. package/README.de.md +0 -5
  2. package/README.es.md +0 -5
  3. package/README.fr.md +0 -5
  4. package/README.it.md +0 -5
  5. package/README.ja.md +0 -5
  6. package/README.ko.md +0 -5
  7. package/README.md +4 -5
  8. package/README.pt-br.md +0 -5
  9. package/README.ru.md +0 -6
  10. package/README.zh-cn.md +0 -5
  11. package/dist/cli/index.js +865 -413
  12. package/dist/cli/service-auth/keystore.d.ts +4 -2
  13. package/dist/cli-node/index.js +865 -413
  14. package/docs/reference/web-terminal-visual-qa.md +8 -0
  15. package/package.json +1 -1
  16. package/packages/lsp-daemon/dist/cli.js +7 -7
  17. package/packages/lsp-daemon/dist/client.js +7 -7
  18. package/packages/lsp-daemon/dist/index.js +7 -7
  19. package/packages/lsp-daemon/package.json +3 -3
  20. package/packages/lsp-tools-mcp/dist/cli.js +7 -7
  21. package/packages/lsp-tools-mcp/dist/mcp.js +7 -7
  22. package/packages/lsp-tools-mcp/dist/tools.js +4 -4
  23. package/packages/lsp-tools-mcp/package.json +3 -3
  24. package/packages/omo-codex/plugin/.codex-plugin/plugin.json +1 -1
  25. package/packages/omo-codex/plugin/components/bootstrap/dist/cli.js +77 -26
  26. package/packages/omo-codex/plugin/components/bootstrap/hooks/hooks.json +1 -1
  27. package/packages/omo-codex/plugin/components/bootstrap/package.json +1 -1
  28. package/packages/omo-codex/plugin/components/bootstrap/src/hook.ts +1 -1
  29. package/packages/omo-codex/plugin/components/comment-checker/biome.json +1 -21
  30. package/packages/omo-codex/plugin/components/comment-checker/hooks/hooks.json +1 -1
  31. package/packages/omo-codex/plugin/components/comment-checker/package.json +4 -4
  32. package/packages/omo-codex/plugin/components/git-bash/hooks/hooks.json +2 -2
  33. package/packages/omo-codex/plugin/components/git-bash/package.json +1 -1
  34. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/biome.json +1 -21
  35. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/hooks/hooks.json +1 -1
  36. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/package.json +4 -4
  37. package/packages/omo-codex/plugin/components/lsp/biome.json +1 -21
  38. package/packages/omo-codex/plugin/components/lsp/dist/.omo-runtime-manifest.json +3 -3
  39. package/packages/omo-codex/plugin/components/lsp/dist/cli.js +4 -4
  40. package/packages/omo-codex/plugin/components/lsp/hooks/hooks.json +2 -2
  41. package/packages/omo-codex/plugin/components/lsp/package.json +4 -4
  42. package/packages/omo-codex/plugin/components/lsp/scripts/build-runtime.mjs +0 -1
  43. package/packages/omo-codex/plugin/components/lsp/test/codex-hook-not-installed.test.ts +1 -1
  44. package/packages/omo-codex/plugin/components/rules/biome.json +1 -21
  45. package/packages/omo-codex/plugin/components/rules/hooks/hooks.json +4 -4
  46. package/packages/omo-codex/plugin/components/rules/package.json +4 -4
  47. package/packages/omo-codex/plugin/components/teammode/hooks/hooks.json +1 -1
  48. package/packages/omo-codex/plugin/components/teammode/package.json +1 -1
  49. package/packages/omo-codex/plugin/components/telemetry/biome.json +1 -21
  50. package/packages/omo-codex/plugin/components/telemetry/dist/cli.js +90 -1
  51. package/packages/omo-codex/plugin/components/telemetry/dist/posthog.js +91 -1
  52. package/packages/omo-codex/plugin/components/telemetry/hooks/hooks.json +1 -1
  53. package/packages/omo-codex/plugin/components/telemetry/package.json +4 -4
  54. package/packages/omo-codex/plugin/components/ultrawork/agents/lazycodex-gate-reviewer.toml +2 -2
  55. package/packages/omo-codex/plugin/components/ultrawork/biome.json +1 -21
  56. package/packages/omo-codex/plugin/components/ultrawork/hooks/hooks.json +1 -1
  57. package/packages/omo-codex/plugin/components/ultrawork/package.json +4 -4
  58. package/packages/omo-codex/plugin/components/ulw-execute-continuation/biome.json +1 -21
  59. package/packages/omo-codex/plugin/components/ulw-execute-continuation/directive.md +1 -1
  60. package/packages/omo-codex/plugin/components/ulw-execute-continuation/hooks/hooks.json +1 -1
  61. package/packages/omo-codex/plugin/components/ulw-execute-continuation/package.json +4 -4
  62. package/packages/omo-codex/plugin/components/ulw-loop/biome.json +1 -21
  63. package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint.js +1 -1
  64. package/packages/omo-codex/plugin/components/ulw-loop/dist/cli.js +7 -7
  65. package/packages/omo-codex/plugin/components/ulw-loop/dist/codex-goal-instruction.js +5 -5
  66. package/packages/omo-codex/plugin/components/ulw-loop/dist/stop-resume-hook.js +1 -1
  67. package/packages/omo-codex/plugin/components/ulw-loop/hooks/hooks.json +5 -5
  68. package/packages/omo-codex/plugin/components/ulw-loop/package.json +4 -4
  69. package/packages/omo-codex/plugin/components/ulw-loop/src/checkpoint.ts +1 -1
  70. package/packages/omo-codex/plugin/components/ulw-loop/src/codex-goal-instruction.ts +5 -5
  71. package/packages/omo-codex/plugin/components/ulw-loop/src/stop-resume-hook.ts +1 -1
  72. package/packages/omo-codex/plugin/components/ulw-loop/test/fixtures/commit-mutations.mjs +2 -0
  73. package/packages/omo-codex/plugin/hooks/post-compact-resetting-git-bash-mcp-reminder.json +1 -1
  74. package/packages/omo-codex/plugin/hooks/post-compact-resetting-lsp-diagnostics-cache.json +1 -1
  75. package/packages/omo-codex/plugin/hooks/post-compact-resetting-project-rule-cache.json +1 -1
  76. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-comments.json +1 -1
  77. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-lsp-diagnostics.json +1 -1
  78. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-thread-title-hygiene.json +1 -1
  79. package/packages/omo-codex/plugin/hooks/post-tool-use-matching-project-rules.json +1 -1
  80. package/packages/omo-codex/plugin/hooks/post-tool-use-recording-spawn-admission.json +1 -1
  81. package/packages/omo-codex/plugin/hooks/pre-tool-use-enforcing-unlimited-goal-budget.json +1 -1
  82. package/packages/omo-codex/plugin/hooks/pre-tool-use-guarding-ulw-loop-spawns.json +1 -1
  83. package/packages/omo-codex/plugin/hooks/pre-tool-use-recommending-git-bash-mcp.json +1 -1
  84. package/packages/omo-codex/plugin/hooks/session-start-checking-auto-update.json +1 -1
  85. package/packages/omo-codex/plugin/hooks/session-start-checking-bootstrap-provisioning.json +1 -1
  86. package/packages/omo-codex/plugin/hooks/session-start-loading-project-rules.json +1 -1
  87. package/packages/omo-codex/plugin/hooks/session-start-recording-session-telemetry.json +1 -1
  88. package/packages/omo-codex/plugin/hooks/stop-checking-ulw-execute-continuation.json +1 -1
  89. package/packages/omo-codex/plugin/hooks/stop-checking-ulw-loop-resume.json +1 -1
  90. package/packages/omo-codex/plugin/hooks/subagent-stop-verifying-lazycodex-executor-evidence.json +1 -1
  91. package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ultrawork-trigger.json +1 -1
  92. package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ulw-loop-steering.json +1 -1
  93. package/packages/omo-codex/plugin/hooks/user-prompt-submit-loading-project-rules.json +1 -1
  94. package/packages/omo-codex/plugin/package-lock.json +12 -12
  95. package/packages/omo-codex/plugin/package.json +1 -1
  96. package/packages/omo-codex/plugin/scripts/sync-skills.mjs +14 -9
  97. package/packages/omo-codex/plugin/skills/ast-grep/AGENTS.md +1 -1
  98. package/packages/omo-codex/plugin/skills/browser/SKILL.md +21 -22
  99. package/packages/omo-codex/plugin/skills/browser/references/commands.md +2 -9
  100. package/packages/omo-codex/plugin/skills/browser/references/install.md +1 -2
  101. package/packages/omo-codex/plugin/skills/browser/runtime/omowright/manifest.json +1 -1
  102. package/packages/omo-codex/plugin/skills/browser/scripts/browser-doctor.mjs +2 -2
  103. package/packages/omo-codex/plugin/skills/browser/scripts/browser-engine-guard.mjs +63 -487
  104. package/packages/omo-codex/plugin/skills/browser/scripts/browser-install.mjs +1 -1
  105. package/packages/omo-codex/plugin/skills/browser/scripts/omowright.mjs +1 -10
  106. package/packages/omo-codex/plugin/skills/frontend/ATTRIBUTION.md +2 -2
  107. package/packages/omo-codex/plugin/skills/frontend/SKILL.md +79 -118
  108. package/packages/omo-codex/plugin/skills/frontend/references/design/README.md +75 -224
  109. package/packages/omo-codex/plugin/skills/frontend/references/design/stylegallery.md +32 -27
  110. package/packages/omo-codex/plugin/skills/frontend/references/perfection/README.md +2 -2
  111. package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/check-accent-stripes.ts +8 -24
  112. package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/check-draft-guard.ts +40 -0
  113. package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/collect-files.ts +53 -0
  114. package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/draft-guard/findings.ts +16 -0
  115. package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/draft-guard/lint.ts +38 -0
  116. package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/draft-guard/rules.ts +69 -0
  117. package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/draft-guard/text-nodes.ts +121 -0
  118. package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/fixtures/draft-guard/negative/attr-lookalikes.tsx +10 -0
  119. package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/fixtures/draft-guard/negative/bound-values.tsx +12 -0
  120. package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/fixtures/draft-guard/negative/const-generics.tsx +3 -0
  121. package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/fixtures/draft-guard/negative/generics.tsx +7 -0
  122. package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/fixtures/draft-guard/negative/measured-stats.html +6 -0
  123. package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/fixtures/draft-guard/negative/plain-copy.html +9 -0
  124. package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/fixtures/draft-guard/negative/state-copy.html +11 -0
  125. package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/fixtures/draft-guard/positive/attribute-edges.html +7 -0
  126. package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/fixtures/draft-guard/positive/components.tsx +16 -0
  127. package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/fixtures/draft-guard/positive/decorative-badge.vue +7 -0
  128. package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/fixtures/draft-guard/positive/emoji-icons.tsx +9 -0
  129. package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/fixtures/draft-guard/positive/jsx-edges.tsx +1 -0
  130. package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/fixtures/draft-guard/positive/promo-copy.html +6 -0
  131. package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/fixtures/draft-guard/positive/promo-copy.tsx +8 -0
  132. package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/fixtures/draft-guard/positive/vanity-stats.html +5 -0
  133. package/packages/omo-codex/plugin/skills/init-deep/SKILL.md +1 -1
  134. package/packages/omo-codex/plugin/skills/programming/SKILL.md +10 -7
  135. package/packages/omo-codex/plugin/skills/programming/references/typescript/README.md +10 -7
  136. package/packages/omo-codex/plugin/skills/programming/references/typescript/bootstrap.md +9 -23
  137. package/packages/omo-codex/plugin/skills/programming/references/typescript/oxc-lint.md +130 -0
  138. package/packages/omo-codex/plugin/skills/programming/references/typescript/tsconfig-strict.md +6 -62
  139. package/packages/omo-codex/plugin/skills/programming/scripts/typescript/check-no-excuse-rules.ts +12 -12
  140. package/packages/omo-codex/plugin/skills/programming/scripts/typescript/new-project.ts +20 -81
  141. package/packages/omo-codex/plugin/skills/programming/scripts/typescript/oxc/.oxfmtrc.json +9 -0
  142. package/packages/omo-codex/plugin/skills/programming/scripts/typescript/oxc/.oxlintrc.json +46 -0
  143. package/packages/omo-codex/plugin/skills/programming/scripts/typescript/oxc/lint/oxlint-plugin/LICENSE +21 -0
  144. package/packages/omo-codex/plugin/skills/programming/scripts/typescript/oxc/lint/oxlint-plugin/index.js +10 -0
  145. package/packages/omo-codex/plugin/skills/programming/scripts/typescript/oxc/lint/oxlint-plugin/no-em-dash-copy.js +28 -0
  146. package/packages/omo-codex/plugin/skills/programming/scripts/typescript/oxc/lint/oxlint-plugin/require-suppression-reason.js +62 -0
  147. package/packages/omo-codex/plugin/skills/programming/scripts/typescript/oxc/tsconfig.json +29 -0
  148. package/packages/omo-codex/plugin/skills/refactor/SKILL.md +14 -14
  149. package/packages/omo-codex/plugin/skills/remove-ai-slops/SKILL.md +1 -1
  150. package/packages/omo-codex/plugin/skills/review-work/SKILL.md +2 -2
  151. package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/AGENTS.md +1 -1
  152. package/packages/omo-codex/plugin/skills/ulw-execute/SKILL.md +2 -2
  153. package/packages/omo-codex/plugin/skills/ulw-loop/SKILL.md +1 -1
  154. package/packages/omo-codex/plugin/skills/ulw-research/AGENTS.md +21 -3
  155. package/packages/omo-codex/plugin/skills/ulw-research/SKILL.md +37 -12
  156. package/packages/omo-codex/plugin/skills/ulw-research/references/deliverable-phase.md +77 -16
  157. package/packages/omo-codex/plugin/skills/ulw-research/references/fact-ledger-grammar.md +134 -0
  158. package/packages/omo-codex/plugin/skills/ulw-research/references/fact-ledger.md +95 -0
  159. package/packages/omo-codex/plugin/skills/ulw-research/references/fidelity-claim-kinds.md +19 -0
  160. package/packages/omo-codex/plugin/skills/ulw-research/references/report-gates.md +68 -3
  161. package/packages/omo-codex/plugin/skills/ulw-research/references/report-visualization.md +2 -0
  162. package/packages/omo-codex/plugin/skills/ulw-research/references/unit-rules.md +157 -0
  163. package/packages/omo-codex/plugin/skills/ulw-research/scripts/claim-kinds.mjs +116 -0
  164. package/packages/omo-codex/plugin/skills/ulw-research/scripts/claims-index.mjs +125 -0
  165. package/packages/omo-codex/plugin/skills/ulw-research/scripts/cli-support.mjs +19 -5
  166. package/packages/omo-codex/plugin/skills/ulw-research/scripts/contracts.mjs +89 -3
  167. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-commands-meaning.mjs +191 -0
  168. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-commands-verify.mjs +164 -0
  169. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-commands.mjs +239 -0
  170. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-derive.mjs +66 -0
  171. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-expr.mjs +108 -0
  172. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-fill.mjs +194 -0
  173. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-ledger-kinds.mjs +250 -0
  174. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-ledger-schema.mjs +221 -0
  175. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-ledger.mjs +123 -0
  176. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-locate.mjs +231 -0
  177. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-pairs.mjs +187 -0
  178. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-recompute.mjs +89 -0
  179. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-binding.mjs +130 -0
  180. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-brief.mjs +150 -0
  181. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-claims.mjs +89 -0
  182. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-lexicon.mjs +145 -0
  183. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-records.mjs +163 -0
  184. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-state.mjs +64 -0
  185. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-trace.mjs +85 -0
  186. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-values.mjs +229 -0
  187. package/packages/omo-codex/plugin/skills/ulw-research/scripts/gates-fidelity.mjs +113 -0
  188. package/packages/omo-codex/plugin/skills/ulw-research/scripts/gates-static.mjs +29 -9
  189. package/packages/omo-codex/plugin/skills/ulw-research/scripts/gates-structure.mjs +1 -1
  190. package/packages/omo-codex/plugin/skills/ulw-research/scripts/gates-text.mjs +1 -27
  191. package/packages/omo-codex/plugin/skills/ulw-research/scripts/html-lite.mjs +2 -2
  192. package/packages/omo-codex/plugin/skills/ulw-research/scripts/html-markup.mjs +182 -0
  193. package/packages/omo-codex/plugin/skills/ulw-research/scripts/lexicon.mjs +47 -0
  194. package/packages/omo-codex/plugin/skills/ulw-research/scripts/md-lite.mjs +193 -0
  195. package/packages/omo-codex/plugin/skills/ulw-research/scripts/meaning-check.mjs +196 -0
  196. package/packages/omo-codex/plugin/skills/ulw-research/scripts/numerals.mjs +226 -0
  197. package/packages/omo-codex/plugin/skills/ulw-research/scripts/outcome.mjs +8 -8
  198. package/packages/omo-codex/plugin/skills/ulw-research/scripts/particles-ko.mjs +108 -0
  199. package/packages/omo-codex/plugin/skills/ulw-research/scripts/placeholder.mjs +184 -0
  200. package/packages/omo-codex/plugin/skills/ulw-research/scripts/polish-apply.mjs +115 -0
  201. package/packages/omo-codex/plugin/skills/ulw-research/scripts/polish-diff.mjs +31 -0
  202. package/packages/omo-codex/plugin/skills/ulw-research/scripts/polish-project.mjs +125 -0
  203. package/packages/omo-codex/plugin/skills/ulw-research/scripts/polish-rules.mjs +170 -0
  204. package/packages/omo-codex/plugin/skills/ulw-research/scripts/rational.mjs +139 -0
  205. package/packages/omo-codex/plugin/skills/ulw-research/scripts/report-tools-commands.mjs +21 -10
  206. package/packages/omo-codex/plugin/skills/ulw-research/scripts/report-tools.mjs +15 -0
  207. package/packages/omo-codex/plugin/skills/ulw-research/scripts/sentence-split.mjs +77 -0
  208. package/packages/omo-codex/plugin/skills/ulw-research/scripts/source-commands.mjs +140 -0
  209. package/packages/omo-codex/plugin/skills/ulw-research/scripts/source-snapshot.mjs +176 -0
  210. package/packages/omo-codex/plugin/skills/ulw-research/scripts/text-normalize.mjs +82 -0
  211. package/packages/omo-codex/plugin/skills/ulw-research/scripts/text-similarity.mjs +28 -0
  212. package/packages/omo-codex/plugin/skills/ulw-research/scripts/unit-algebra.mjs +101 -0
  213. package/packages/omo-codex/plugin/skills/ulw-research/scripts/unit-errors.mjs +23 -0
  214. package/packages/omo-codex/plugin/skills/ulw-research/scripts/unit-metadata.mjs +31 -0
  215. package/packages/omo-codex/plugin/skills/ulw-research/scripts/unit-pairs.mjs +72 -0
  216. package/packages/omo-codex/plugin/skills/ulw-research/scripts/unit-precision.mjs +44 -0
  217. package/packages/omo-codex/plugin/skills/ulw-research/scripts/unit-rules-shapes.mjs +64 -0
  218. package/packages/omo-codex/plugin/skills/ulw-research/scripts/unit-rules.mjs +173 -0
  219. package/packages/omo-codex/plugin/skills/ulw-research/scripts/units.mjs +105 -0
  220. package/packages/omo-codex/plugin/skills/visual-qa/SKILL.md +71 -64
  221. package/packages/omo-codex/plugin/test/component-bundled-cli.test.mjs +4 -1
  222. package/packages/omo-codex/plugin/test/sync-skills-codex-compatibility.test.mjs +19 -0
  223. package/packages/omo-codex/plugin/test/sync-skills-test-support.mjs +12 -8
  224. package/packages/omo-codex/scripts/install-dist/install-local.mjs +169 -30
  225. package/packages/shared-skills/skills/ast-grep/AGENTS.md +1 -1
  226. package/packages/shared-skills/skills/browser/SKILL.md +21 -22
  227. package/packages/shared-skills/skills/browser/references/commands.md +2 -9
  228. package/packages/shared-skills/skills/browser/references/install.md +1 -2
  229. package/packages/shared-skills/skills/browser/runtime/omowright/manifest.json +1 -1
  230. package/packages/shared-skills/skills/browser/scripts/browser-doctor.mjs +2 -2
  231. package/packages/shared-skills/skills/browser/scripts/browser-engine-guard.mjs +63 -487
  232. package/packages/shared-skills/skills/browser/scripts/browser-install.mjs +1 -1
  233. package/packages/shared-skills/skills/browser/scripts/omowright.mjs +1 -10
  234. package/packages/shared-skills/skills/frontend/ATTRIBUTION.md +2 -2
  235. package/packages/shared-skills/skills/frontend/SKILL.md +79 -118
  236. package/packages/shared-skills/skills/frontend/references/design/README.md +75 -224
  237. package/packages/shared-skills/skills/frontend/references/design/stylegallery.md +32 -27
  238. package/packages/shared-skills/skills/frontend/references/perfection/README.md +2 -2
  239. package/packages/shared-skills/skills/frontend/scripts/perfection/check-accent-stripes.ts +8 -24
  240. package/packages/shared-skills/skills/frontend/scripts/perfection/check-draft-guard.test.ts +101 -0
  241. package/packages/shared-skills/skills/frontend/scripts/perfection/check-draft-guard.ts +40 -0
  242. package/packages/shared-skills/skills/frontend/scripts/perfection/collect-files.test.ts +50 -0
  243. package/packages/shared-skills/skills/frontend/scripts/perfection/collect-files.ts +53 -0
  244. package/packages/shared-skills/skills/frontend/scripts/perfection/draft-guard/findings.ts +16 -0
  245. package/packages/shared-skills/skills/frontend/scripts/perfection/draft-guard/lint.ts +38 -0
  246. package/packages/shared-skills/skills/frontend/scripts/perfection/draft-guard/rules.ts +69 -0
  247. package/packages/shared-skills/skills/frontend/scripts/perfection/draft-guard/text-nodes.ts +121 -0
  248. package/packages/shared-skills/skills/frontend/scripts/perfection/fixtures/draft-guard/negative/attr-lookalikes.tsx +10 -0
  249. package/packages/shared-skills/skills/frontend/scripts/perfection/fixtures/draft-guard/negative/bound-values.tsx +12 -0
  250. package/packages/shared-skills/skills/frontend/scripts/perfection/fixtures/draft-guard/negative/const-generics.tsx +3 -0
  251. package/packages/shared-skills/skills/frontend/scripts/perfection/fixtures/draft-guard/negative/generics.tsx +7 -0
  252. package/packages/shared-skills/skills/frontend/scripts/perfection/fixtures/draft-guard/negative/measured-stats.html +6 -0
  253. package/packages/shared-skills/skills/frontend/scripts/perfection/fixtures/draft-guard/negative/plain-copy.html +9 -0
  254. package/packages/shared-skills/skills/frontend/scripts/perfection/fixtures/draft-guard/negative/state-copy.html +11 -0
  255. package/packages/shared-skills/skills/frontend/scripts/perfection/fixtures/draft-guard/positive/attribute-edges.html +7 -0
  256. package/packages/shared-skills/skills/frontend/scripts/perfection/fixtures/draft-guard/positive/components.tsx +16 -0
  257. package/packages/shared-skills/skills/frontend/scripts/perfection/fixtures/draft-guard/positive/decorative-badge.vue +7 -0
  258. package/packages/shared-skills/skills/frontend/scripts/perfection/fixtures/draft-guard/positive/emoji-icons.tsx +9 -0
  259. package/packages/shared-skills/skills/frontend/scripts/perfection/fixtures/draft-guard/positive/jsx-edges.tsx +1 -0
  260. package/packages/shared-skills/skills/frontend/scripts/perfection/fixtures/draft-guard/positive/promo-copy.html +6 -0
  261. package/packages/shared-skills/skills/frontend/scripts/perfection/fixtures/draft-guard/positive/promo-copy.tsx +8 -0
  262. package/packages/shared-skills/skills/frontend/scripts/perfection/fixtures/draft-guard/positive/vanity-stats.html +5 -0
  263. package/packages/shared-skills/skills/programming/SKILL.md +10 -7
  264. package/packages/shared-skills/skills/programming/references/typescript/README.md +10 -7
  265. package/packages/shared-skills/skills/programming/references/typescript/bootstrap.md +9 -23
  266. package/packages/shared-skills/skills/programming/references/typescript/oxc-lint.md +130 -0
  267. package/packages/shared-skills/skills/programming/references/typescript/tsconfig-strict.md +6 -62
  268. package/packages/shared-skills/skills/programming/scripts/typescript/check-no-excuse-rules.ts +12 -12
  269. package/packages/shared-skills/skills/programming/scripts/typescript/new-project.test.ts +162 -0
  270. package/packages/shared-skills/skills/programming/scripts/typescript/new-project.ts +20 -81
  271. package/packages/shared-skills/skills/programming/scripts/typescript/oxc/.oxfmtrc.json +9 -0
  272. package/packages/shared-skills/skills/programming/scripts/typescript/oxc/.oxlintrc.json +46 -0
  273. package/packages/shared-skills/skills/programming/scripts/typescript/oxc/lint/oxlint-plugin/LICENSE +21 -0
  274. package/packages/shared-skills/skills/programming/scripts/typescript/oxc/lint/oxlint-plugin/index.js +10 -0
  275. package/packages/shared-skills/skills/programming/scripts/typescript/oxc/lint/oxlint-plugin/no-em-dash-copy.js +28 -0
  276. package/packages/shared-skills/skills/programming/scripts/typescript/oxc/lint/oxlint-plugin/require-suppression-reason.js +62 -0
  277. package/packages/shared-skills/skills/programming/scripts/typescript/oxc/tsconfig.json +29 -0
  278. package/packages/shared-skills/skills/refactor/SKILL.md +13 -13
  279. package/packages/shared-skills/skills/ultimate-browsing/engine/AGENTS.md +1 -1
  280. package/packages/shared-skills/skills/ulw-execute/SKILL.md +1 -1
  281. package/packages/shared-skills/skills/ulw-research/AGENTS.md +21 -3
  282. package/packages/shared-skills/skills/ulw-research/SKILL.md +37 -12
  283. package/packages/shared-skills/skills/ulw-research/references/deliverable-phase.md +77 -16
  284. package/packages/shared-skills/skills/ulw-research/references/fact-ledger-grammar.md +134 -0
  285. package/packages/shared-skills/skills/ulw-research/references/fact-ledger.md +95 -0
  286. package/packages/shared-skills/skills/ulw-research/references/fidelity-claim-kinds.md +19 -0
  287. package/packages/shared-skills/skills/ulw-research/references/report-gates.md +68 -3
  288. package/packages/shared-skills/skills/ulw-research/references/report-visualization.md +2 -0
  289. package/packages/shared-skills/skills/ulw-research/references/unit-rules.md +157 -0
  290. package/packages/shared-skills/skills/ulw-research/scripts/claim-kinds.mjs +116 -0
  291. package/packages/shared-skills/skills/ulw-research/scripts/claim-kinds.test.ts +193 -0
  292. package/packages/shared-skills/skills/ulw-research/scripts/claims-index.mjs +125 -0
  293. package/packages/shared-skills/skills/ulw-research/scripts/claims-index.test.ts +149 -0
  294. package/packages/shared-skills/skills/ulw-research/scripts/cli-support.mjs +19 -5
  295. package/packages/shared-skills/skills/ulw-research/scripts/cli-support.test.ts +13 -2
  296. package/packages/shared-skills/skills/ulw-research/scripts/contracts.mjs +89 -3
  297. package/packages/shared-skills/skills/ulw-research/scripts/contracts.test.ts +100 -12
  298. package/packages/shared-skills/skills/ulw-research/scripts/copy-returns.test.ts +183 -0
  299. package/packages/shared-skills/skills/ulw-research/scripts/delivery-barrier.test.ts +245 -0
  300. package/packages/shared-skills/skills/ulw-research/scripts/fact-commands-meaning.mjs +191 -0
  301. package/packages/shared-skills/skills/ulw-research/scripts/fact-commands-verify.mjs +164 -0
  302. package/packages/shared-skills/skills/ulw-research/scripts/fact-commands.mjs +239 -0
  303. package/packages/shared-skills/skills/ulw-research/scripts/fact-derive.mjs +66 -0
  304. package/packages/shared-skills/skills/ulw-research/scripts/fact-derive.test.ts +375 -0
  305. package/packages/shared-skills/skills/ulw-research/scripts/fact-expr.mjs +108 -0
  306. package/packages/shared-skills/skills/ulw-research/scripts/fact-fill.mjs +194 -0
  307. package/packages/shared-skills/skills/ulw-research/scripts/fact-fill.test.ts +221 -0
  308. package/packages/shared-skills/skills/ulw-research/scripts/fact-ledger-grammar.test.ts +197 -0
  309. package/packages/shared-skills/skills/ulw-research/scripts/fact-ledger-kinds.mjs +250 -0
  310. package/packages/shared-skills/skills/ulw-research/scripts/fact-ledger-kinds.test.ts +156 -0
  311. package/packages/shared-skills/skills/ulw-research/scripts/fact-ledger-legacy.test.ts +160 -0
  312. package/packages/shared-skills/skills/ulw-research/scripts/fact-ledger-schema.mjs +221 -0
  313. package/packages/shared-skills/skills/ulw-research/scripts/fact-ledger-schema.test.ts +43 -0
  314. package/packages/shared-skills/skills/ulw-research/scripts/fact-ledger.mjs +123 -0
  315. package/packages/shared-skills/skills/ulw-research/scripts/fact-ledger.test.ts +254 -0
  316. package/packages/shared-skills/skills/ulw-research/scripts/fact-locate.mjs +231 -0
  317. package/packages/shared-skills/skills/ulw-research/scripts/fact-locate.test.ts +118 -0
  318. package/packages/shared-skills/skills/ulw-research/scripts/fact-pairs.mjs +187 -0
  319. package/packages/shared-skills/skills/ulw-research/scripts/fact-pairs.test.ts +167 -0
  320. package/packages/shared-skills/skills/ulw-research/scripts/fact-recompute.mjs +89 -0
  321. package/packages/shared-skills/skills/ulw-research/scripts/fact-unit-dimensions.test.ts +409 -0
  322. package/packages/shared-skills/skills/ulw-research/scripts/fact-unit-rules.test.ts +388 -0
  323. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-binding.mjs +130 -0
  324. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-binding.test.ts +81 -0
  325. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-brief.mjs +150 -0
  326. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-brief.test.ts +179 -0
  327. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-claims.mjs +89 -0
  328. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-lexicon.mjs +145 -0
  329. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-lexicon.test.ts +117 -0
  330. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-records.mjs +163 -0
  331. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-state.mjs +64 -0
  332. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-state.test.ts +46 -0
  333. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-trace.mjs +85 -0
  334. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-trace.test.ts +58 -0
  335. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-values.mjs +229 -0
  336. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-values.test.ts +270 -0
  337. package/packages/shared-skills/skills/ulw-research/scripts/gates-context.test.ts +64 -0
  338. package/packages/shared-skills/skills/ulw-research/scripts/gates-fidelity.mjs +113 -0
  339. package/packages/shared-skills/skills/ulw-research/scripts/gates-fidelity.test.ts +73 -0
  340. package/packages/shared-skills/skills/ulw-research/scripts/gates-static.mjs +29 -9
  341. package/packages/shared-skills/skills/ulw-research/scripts/gates-structure.mjs +1 -1
  342. package/packages/shared-skills/skills/ulw-research/scripts/gates-text.mjs +1 -27
  343. package/packages/shared-skills/skills/ulw-research/scripts/html-lite.mjs +2 -2
  344. package/packages/shared-skills/skills/ulw-research/scripts/html-markup.mjs +182 -0
  345. package/packages/shared-skills/skills/ulw-research/scripts/lexicon.mjs +47 -0
  346. package/packages/shared-skills/skills/ulw-research/scripts/lexicon.test.ts +57 -0
  347. package/packages/shared-skills/skills/ulw-research/scripts/md-lite.mjs +193 -0
  348. package/packages/shared-skills/skills/ulw-research/scripts/md-lite.test.ts +271 -0
  349. package/packages/shared-skills/skills/ulw-research/scripts/meaning-check.mjs +196 -0
  350. package/packages/shared-skills/skills/ulw-research/scripts/meaning-check.test.ts +228 -0
  351. package/packages/shared-skills/skills/ulw-research/scripts/numerals.mjs +226 -0
  352. package/packages/shared-skills/skills/ulw-research/scripts/numerals.test.ts +120 -0
  353. package/packages/shared-skills/skills/ulw-research/scripts/outcome.mjs +8 -8
  354. package/packages/shared-skills/skills/ulw-research/scripts/outcome.test.ts +29 -0
  355. package/packages/shared-skills/skills/ulw-research/scripts/particles-ko.mjs +108 -0
  356. package/packages/shared-skills/skills/ulw-research/scripts/particles-ko.test.ts +86 -0
  357. package/packages/shared-skills/skills/ulw-research/scripts/placeholder.mjs +184 -0
  358. package/packages/shared-skills/skills/ulw-research/scripts/placeholder.test.ts +77 -0
  359. package/packages/shared-skills/skills/ulw-research/scripts/polish-apply.mjs +115 -0
  360. package/packages/shared-skills/skills/ulw-research/scripts/polish-apply.test.ts +363 -0
  361. package/packages/shared-skills/skills/ulw-research/scripts/polish-diff.mjs +31 -0
  362. package/packages/shared-skills/skills/ulw-research/scripts/polish-diff.test.ts +168 -0
  363. package/packages/shared-skills/skills/ulw-research/scripts/polish-markup.test.ts +187 -0
  364. package/packages/shared-skills/skills/ulw-research/scripts/polish-project.mjs +125 -0
  365. package/packages/shared-skills/skills/ulw-research/scripts/polish-rules.mjs +170 -0
  366. package/packages/shared-skills/skills/ulw-research/scripts/rational.mjs +139 -0
  367. package/packages/shared-skills/skills/ulw-research/scripts/rational.test.ts +46 -0
  368. package/packages/shared-skills/skills/ulw-research/scripts/report-tools-commands.mjs +21 -10
  369. package/packages/shared-skills/skills/ulw-research/scripts/report-tools.mjs +15 -0
  370. package/packages/shared-skills/skills/ulw-research/scripts/report-tools.test.ts +458 -2
  371. package/packages/shared-skills/skills/ulw-research/scripts/sentence-split.mjs +77 -0
  372. package/packages/shared-skills/skills/ulw-research/scripts/sentence-split.test.ts +109 -0
  373. package/packages/shared-skills/skills/ulw-research/scripts/source-commands.mjs +140 -0
  374. package/packages/shared-skills/skills/ulw-research/scripts/source-snapshot.mjs +176 -0
  375. package/packages/shared-skills/skills/ulw-research/scripts/source-snapshot.test.ts +113 -0
  376. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/assets/share.csv +3 -0
  377. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/claim-graph.md +13 -0
  378. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/gen-legacy-dev.mjs +89 -0
  379. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/ledger-basic.json +223 -0
  380. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/ledger-legacy-dev.json +859 -0
  381. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/ledger-verify.json +420 -0
  382. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/report-bad.html +19 -0
  383. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/report-good.html +39 -0
  384. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/sources/S4/acronyms.json +3 -0
  385. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/sources/S4/meta.json +9 -0
  386. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/sources/S4/sentences.jsonl +7 -0
  387. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/sources/S4/text.txt +7 -0
  388. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/sources/S4.html +19 -0
  389. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/sources/S5/acronyms.json +1 -0
  390. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/sources/S5/meta.json +9 -0
  391. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/sources/S5/sentences.jsonl +1 -0
  392. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/sources/S5/text.txt +1 -0
  393. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/sources/S9.txt +1 -0
  394. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity-brief-locked.md +17 -0
  395. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity-brief-pending.md +17 -0
  396. package/packages/shared-skills/skills/ulw-research/scripts/text-normalize.mjs +82 -0
  397. package/packages/shared-skills/skills/ulw-research/scripts/text-normalize.test.ts +73 -0
  398. package/packages/shared-skills/skills/ulw-research/scripts/text-similarity.mjs +28 -0
  399. package/packages/shared-skills/skills/ulw-research/scripts/text-similarity.test.ts +38 -0
  400. package/packages/shared-skills/skills/ulw-research/scripts/unit-algebra.mjs +101 -0
  401. package/packages/shared-skills/skills/ulw-research/scripts/unit-errors.mjs +23 -0
  402. package/packages/shared-skills/skills/ulw-research/scripts/unit-metadata.mjs +31 -0
  403. package/packages/shared-skills/skills/ulw-research/scripts/unit-pairs.mjs +72 -0
  404. package/packages/shared-skills/skills/ulw-research/scripts/unit-precision.mjs +44 -0
  405. package/packages/shared-skills/skills/ulw-research/scripts/unit-rules-shapes.mjs +64 -0
  406. package/packages/shared-skills/skills/ulw-research/scripts/unit-rules.mjs +173 -0
  407. package/packages/shared-skills/skills/ulw-research/scripts/units.mjs +105 -0
  408. package/packages/shared-skills/skills/ulw-research/scripts/units.test.ts +83 -0
  409. package/packages/shared-skills/skills/visual-qa/SKILL.md +70 -63
  410. package/packages/shared-skills/skills/visual-qa/scripts/skill-doc-fields.test.ts +34 -0
  411. package/script/qa/web-terminal-visual-qa.mjs +6 -2
  412. package/script/qa/xterm-live-terminal.mjs +26 -3
@@ -0,0 +1,363 @@
1
+ import { describe, expect, test } from "bun:test"
2
+
3
+ import { FACT_ELEMENT_TAGS, renderBlockquote, renderQuote, renderValueHtml } from "./placeholder.mjs"
4
+ import { applyEdits, cutSpan, POLISH_EDIT_FIELDS, project, validatePolishEdits } from "./polish-apply.mjs"
5
+ import { diffPolish } from "./polish-diff.mjs"
6
+
7
+ type Row = { fact_id: string; kind: string; quote: string; raw: string; value: string; unit: string | null; qualifier: string | null }
8
+ const row = (fact_id: string, quote: string, raw: string, value: string, unit: string | null, qualifier: string | null = null): Row => ({
9
+ fact_id,
10
+ kind: "measured",
11
+ quote,
12
+ raw,
13
+ value,
14
+ unit,
15
+ qualifier,
16
+ })
17
+ const REVENUE = row("F1", "Revenue was about 10 million USD in 2025.", "10 million USD", "10000000", "USD", "approximate")
18
+ const SPAN = row("F2", "The bridge has a main span of 320 m.", "320 m", "320", "m")
19
+ const USERS = row("F3", "Usage reached 5,000 users.", "5,000 users", "5000", "users")
20
+ const BOUND = row("F4", "The bridge has a main span of up to 320 m.", "320 m", "320", "m", "upper_inclusive")
21
+ const LEDGER = { schemaVersion: 1, rows: [REVENUE, SPAN, USERS, BOUND], events: [] }
22
+ const f = (r: Row) => renderValueHtml(r, r.raw)
23
+ const doc = (body: string) => `<html><body>\n${body}\n</body></html>`
24
+ const edit = (before: string, after: string, reason = "polish") => ({ before, after, reason })
25
+ const codes = (list: { code: string }[]) => list.map((item) => item.code)
26
+
27
+ describe("applyEdits: hedges and qualifiers", () => {
28
+ test("removing 'about' that the row quote carries is rejected and leaves the paragraph unchanged", () => {
29
+ const html = doc(`<p>Revenue was about ${f(REVENUE)}.</p>`)
30
+ const result = applyEdits(html, [edit(`Revenue was about ${f(REVENUE)}.`, `Revenue was ${f(REVENUE)}.`)], LEDGER)
31
+ expect(result.applied).toEqual([])
32
+ expect(codes(result.rejected)).toEqual(["polish_edit_rejected"])
33
+ expect(result.rejected[0].reason).toContain("about")
34
+ expect(result.out).toBe(html)
35
+ })
36
+
37
+ test("removing a hedge the row quote lacks is applied", () => {
38
+ const html = doc(`<p>The span was about ${f(SPAN)}.</p>`)
39
+ const result = applyEdits(html, [edit("The span was about ", "The span was ")], LEDGER)
40
+ expect(result.applied).toEqual([0])
41
+ expect(result.rejected).toEqual([])
42
+ expect(result.out).toBe(doc(`<p>The span was ${f(SPAN)}.</p>`))
43
+ })
44
+
45
+ test("dropping the row's qualifier ('up to') is rejected", () => {
46
+ const html = doc(`<p>The main span is up to ${f(BOUND)}.</p>`)
47
+ const result = applyEdits(html, [edit(`The main span is up to ${f(BOUND)}.`, `The main span is ${f(BOUND)}.`)], LEDGER)
48
+ expect(codes(result.rejected)).toEqual(["polish_edit_rejected"])
49
+ expect(result.rejected[0].reason).toContain("up to")
50
+ })
51
+
52
+ test("a claim-only sentence keeps the hedges of before", () => {
53
+ const html = doc("<p>Results may improve [S1].</p>")
54
+ expect(codes(applyEdits(html, [edit("Results may improve [S1].", "Results improve [S1].")], LEDGER).rejected)).toEqual(["polish_edit_rejected"])
55
+ })
56
+ })
57
+
58
+ describe("applyEdits: fact spans and numerals", () => {
59
+ test("splitting a sentence with its fact spans intact is applied", () => {
60
+ const html = doc(`<p>The span is ${f(SPAN)} and usage is ${f(USERS)}.</p>`)
61
+ const result = applyEdits(html, [edit(" and usage is ", ". Usage is ")], LEDGER)
62
+ expect(result.applied).toEqual([0])
63
+ expect(result.out).toBe(doc(`<p>The span is ${f(SPAN)}. Usage is ${f(USERS)}.</p>`))
64
+ })
65
+
66
+ test("swapping two numerals is rejected", () => {
67
+ const html = doc("<p>See Figure 1 and Figure 2 [S1].</p>")
68
+ const result = applyEdits(html, [edit("See Figure 1 and Figure 2 [S1].", "See Figure 2 and Figure 1 [S1].")], LEDGER)
69
+ expect(codes(result.rejected)).toEqual(["polish_edit_rejected"])
70
+ expect(result.rejected[0].reason).toContain("numeral")
71
+ })
72
+
73
+ test("moving a decimal point is rejected", () => {
74
+ const html = doc("<p>Throughput grew 3.5 times [S1].</p>")
75
+ expect(codes(applyEdits(html, [edit("grew 3.5 times", "grew 35 times")], LEDGER).rejected)).toEqual(["polish_edit_rejected"])
76
+ })
77
+
78
+ test("changing a plain unit ('320 m' to '320 mm') is rejected with a reason", () => {
79
+ const html = doc("<p>The span is 320 m [S1].</p>")
80
+ const result = applyEdits(html, [edit("The span is 320 m [S1].", "The span is 320 mm [S1].")], LEDGER)
81
+ expect(codes(result.rejected)).toEqual(["polish_edit_rejected"])
82
+ expect(result.rejected[0].reason).toContain("numeral")
83
+ })
84
+
85
+ test("swapping the order of two fact spans is rejected", () => {
86
+ const html = doc(`<p>The span is ${f(SPAN)} and usage is ${f(USERS)}.</p>`)
87
+ const before = `The span is ${f(SPAN)} and usage is ${f(USERS)}.`
88
+ const after = `Usage is ${f(USERS)} and the span is ${f(SPAN)}.`
89
+ expect(codes(applyEdits(html, [edit(before, after)], LEDGER).rejected)).toEqual(["polish_edit_rejected"])
90
+ })
91
+
92
+ test("editing the rendered value inside a fact span is rejected", () => {
93
+ const html = doc(`<p>The span is ${f(SPAN)}.</p>`)
94
+ const changed = f(SPAN).replace(">320 m<", ">321 m<")
95
+ expect(codes(applyEdits(html, [edit(f(SPAN), changed)], LEDGER).rejected)).toEqual(["polish_edit_rejected"])
96
+ })
97
+ })
98
+
99
+ describe("applyEdits: links and location", () => {
100
+ test("changing a link is rejected", () => {
101
+ const html = doc('<p><a href="https://example.invalid/a">Reference</a> [S1].</p>')
102
+ const before = '<a href="https://example.invalid/a">Reference</a>'
103
+ const result = applyEdits(html, [edit(before, before.replace("/a", "/b"))], LEDGER)
104
+ expect(codes(result.rejected)).toEqual(["polish_edit_rejected"])
105
+ expect(result.rejected[0].reason).toContain("href")
106
+ })
107
+
108
+ test("changing a data-claim id is rejected", () => {
109
+ const html = doc('<p data-claim="C1">Results improve [S1].</p>')
110
+ const result = applyEdits(html, [edit('data-claim="C1"', 'data-claim="C2"')], LEDGER)
111
+ expect(codes(result.rejected)).toEqual(["polish_edit_rejected"])
112
+ expect(result.rejected[0].reason).toContain("markup changed")
113
+ })
114
+
115
+ test("a before that is not found is unlocated", () => {
116
+ const result = applyEdits(doc("<p>Text [S1].</p>"), [edit("ABSENT", "replacement")], LEDGER)
117
+ expect(codes(result.unlocated)).toEqual(["polish_edit_unlocated"])
118
+ expect(result.applied).toEqual([])
119
+ })
120
+
121
+ test("a before that occurs twice is unlocated", () => {
122
+ const result = applyEdits(doc("<p>The term and term appear [S1].</p>"), [edit("term", "word")], LEDGER)
123
+ expect(codes(result.unlocated)).toEqual(["polish_edit_unlocated"])
124
+ })
125
+
126
+ test("before matches across normalized whitespace", () => {
127
+ const html = doc("<p>Results\n improve [S1].</p>")
128
+ const result = applyEdits(html, [edit("Results improve [S1].", "Results clearly improve [S1].")], LEDGER)
129
+ expect(result.applied).toEqual([0])
130
+ expect(result.out).toBe(doc("<p>Results clearly improve [S1].</p>"))
131
+ })
132
+
133
+ test("edits apply in order; a later edit whose before is gone is unlocated", () => {
134
+ const html = doc("<p>Results improve [S1].</p>")
135
+ const result = applyEdits(
136
+ html,
137
+ [edit("Results improve [S1].", "Results improve steadily [S1]."), edit("Results improve [S1].", "Results improve rapidly [S1].")],
138
+ LEDGER,
139
+ )
140
+ expect(result.applied).toEqual([0])
141
+ expect(result.unlocated.map((item: { index: number }) => item.index)).toEqual([1])
142
+ expect(result.out).toBe(doc("<p>Results improve steadily [S1].</p>"))
143
+ })
144
+
145
+ test("a rejected edit does not block a later good edit", () => {
146
+ const html = doc(`<p>Revenue was about ${f(REVENUE)}.</p><p>Results improve [S1].</p>`)
147
+ const result = applyEdits(
148
+ html,
149
+ [edit(`Revenue was about ${f(REVENUE)}.`, `Revenue was ${f(REVENUE)}.`), edit("Results improve [S1].", "Results clearly improve [S1].")],
150
+ LEDGER,
151
+ )
152
+ expect(result.applied).toEqual([1])
153
+ expect(result.rejected.map((item: { index: number }) => item.index)).toEqual([0])
154
+ expect(result.out).toBe(doc(`<p>Revenue was about ${f(REVENUE)}.</p><p>Results clearly improve [S1].</p>`))
155
+ })
156
+ })
157
+
158
+ describe("applyEdits: rendered visibility (review #11152 round 1)", () => {
159
+ const html = doc(`<p>The span is ${f(SPAN)} [S1].</p>`)
160
+ const hide = (after: string) => applyEdits(html, [edit(f(SPAN), after)], LEDGER)
161
+ test.each([
162
+ ["an HTML comment", `<!-- ${f(SPAN)} -->`],
163
+ ["a hidden attribute", `<span hidden>${f(SPAN)}</span>`],
164
+ ["display:none", `<span style="display: none">${f(SPAN)}</span>`],
165
+ ["visibility:hidden", `<span style="visibility:hidden">${f(SPAN)}</span>`],
166
+ ["font-size:0", `<span style="font-size:0px">${f(SPAN)}</span>`],
167
+ ["a template", `<template>${f(SPAN)}</template>`],
168
+ ["an unclosed hidden wrapper", `<div hidden>${f(SPAN)}`],
169
+ ])("a fact span wrapped in %s is rejected and leaves the document unchanged", (_label, after) => {
170
+ const result = hide(after)
171
+ expect(codes(result.rejected)).toEqual(["polish_edit_rejected"])
172
+ expect(result.rejected[0].reason).toContain("markup changed")
173
+ expect(result.out).toBe(html)
174
+ })
175
+
176
+ test("a data-claim span moved into a comment is rejected", () => {
177
+ const claim = doc('<p><span data-claim="C1">Results improve</span> [S1].</p>')
178
+ const before = '<span data-claim="C1">Results improve</span>'
179
+ expect(codes(applyEdits(claim, [edit(before, `<!-- ${before} -->Results improve`)], LEDGER).rejected)).toEqual(["polish_edit_rejected"])
180
+ })
181
+
182
+ test("a hedge hidden with a hidden attribute is rejected", () => {
183
+ const claim = doc("<p>Results may improve [S1].</p>")
184
+ const result = applyEdits(claim, [edit("Results may improve", "Results <span hidden>may</span> improve")], LEDGER)
185
+ expect(codes(result.rejected)).toEqual(["polish_edit_rejected"])
186
+ expect(result.rejected[0].reason).toContain("markup changed")
187
+ })
188
+ })
189
+
190
+ describe("applyEdits: the same rules as diffPolish (review #11152 round 1)", () => {
191
+ test("adding a ticker is rejected", () => {
192
+ const result = applyEdits(doc("<p>The firm reported growth [S1].</p>"), [edit("The firm reported", "AAPL reported")], LEDGER)
193
+ expect(codes(result.rejected)).toEqual(["polish_edit_rejected"])
194
+ expect(result.rejected[0].reason).toContain("tickers")
195
+ })
196
+
197
+ test("renaming an id that an internal link targets is rejected", () => {
198
+ const html = doc('<h2 id="s1">Method</h2><p><a href="#s1">Method</a> [S1].</p>')
199
+ const result = applyEdits(html, [edit('id="s1"', 'id="s2"')], LEDGER)
200
+ expect(codes(result.rejected)).toEqual(["polish_edit_rejected"])
201
+ expect(result.rejected[0].reason).toContain("#s1")
202
+ })
203
+
204
+ test("dropping a table row is rejected", () => {
205
+ const html = doc("<table><tr><td>A</td></tr><tr><td>B</td></tr></table>")
206
+ expect(codes(applyEdits(html, [edit("<tr><td>B</td></tr>", "")], LEDGER).rejected)).toEqual(["polish_edit_rejected"])
207
+ })
208
+
209
+ test("cumulative churn is caught at the edit that crosses the limit, against the original document", () => {
210
+ const html = doc("<p>Alpha beta gamma. Delta epsilon zeta. Eta theta iota. Kappa lambda mu.</p>")
211
+ const edits = [edit("Alpha beta gamma.", "Nu xi omicron."), edit("Delta epsilon zeta.", "Rho sigma tau."), edit("Eta theta iota.", "Phi chi psi.")]
212
+ const result = applyEdits(html, edits, LEDGER)
213
+ expect(result.rejected.length).toBeGreaterThan(0)
214
+ expect(result.rejected.at(-1)?.reason).toContain("percent of the normalized prose")
215
+ expect(diffPolish(html, result.out, LEDGER)).toEqual([])
216
+ })
217
+ })
218
+
219
+ describe("applyEdits: one baseline with diffPolish (review #11152 round 2, M1)", () => {
220
+ const TOWER = row("F7", "The tower is 320 m tall.", "320 m", "320", "m")
221
+ const ledger = { ...LEDGER, rows: [...LEDGER.rows, TOWER] }
222
+ const html = doc(`<p>The tower is ${f(TOWER)} tall. It may be the tallest.</p>`)
223
+ const pair = [edit(" tall. It may be", " tall and may be"), edit("and may be the tallest", "and is the tallest")]
224
+
225
+ test("each edit of the hedge-stripping pair alone keeps the hedge", () => {
226
+ expect(applyEdits(html, [pair[0]], ledger).applied).toEqual([0])
227
+ const merged = applyEdits(html, [pair[0]], ledger).out
228
+ expect(diffPolish(merged, applyEdits(merged, [pair[1]], ledger).out, ledger)).toEqual([])
229
+ })
230
+
231
+ test("the pair together is refused at the second edit, which strips the source document's hedge", () => {
232
+ const result = applyEdits(html, pair, ledger)
233
+ expect(result.applied).toEqual([0])
234
+ expect(codes(result.rejected)).toEqual(["polish_edit_rejected"])
235
+ expect(result.rejected[0].reason).toContain("may")
236
+ expect(diffPolish(html, result.out, ledger)).toEqual([])
237
+ })
238
+
239
+ test("no cross-edit case remains: every applyEdits output is diffPolish-clean against the input", () => {
240
+ const cases: [string, { before: string; after: string; reason: string }[]][] = [
241
+ [html, pair],
242
+ [doc("<p>Alpha beta gamma. Delta epsilon zeta. Eta theta iota. Kappa lambda mu.</p>"), [edit("Alpha beta gamma.", "Nu xi omicron."), edit("Delta epsilon zeta.", "Rho sigma tau."), edit("Eta theta iota.", "Phi chi psi.")]],
243
+ [doc(`<p>The span is ${f(SPAN)} and usage is ${f(USERS)}.</p>`), [edit(" and usage is", ". Usage is"), edit("The span is", "Usage is"), edit(". Usage is", " and the span is")]],
244
+ ]
245
+ for (const [input, edits] of cases) expect(diffPolish(input, applyEdits(input, edits, ledger).out, ledger)).toEqual([])
246
+ })
247
+ })
248
+
249
+ describe("applyEdits: hidden content keeps its bytes and its class (review #11152 round 2, H1 and M2)", () => {
250
+ const refused = (html: string, edits: { before: string; after: string; reason: string }[], word: string) => {
251
+ const result = applyEdits(html, edits, LEDGER)
252
+ expect(result.applied).toEqual([])
253
+ expect(result.out).toBe(html)
254
+ expect(result.rejected.map((item: { reason: string }) => item.reason).join(" | ")).toContain(word)
255
+ }
256
+ const flagged = (before: string, after: string) => expect(codes(diffPolish(before, after, LEDGER))).toContain("polish_diff_fail")
257
+
258
+ test("probe 1: a number under aria-hidden renders, so editing it is refused and flagged", () => {
259
+ const html = doc('<p>Growth was <span aria-hidden="true">3.2% in 2024</span> overall.</p>')
260
+ refused(html, [edit("3.2% in 2024", "9.9% in 2031")], "numerals")
261
+ flagged(html, html.replace("3.2% in 2024", "9.9% in 2031"))
262
+ })
263
+
264
+ test("probe 2: a number after an unclosed <p hidden> is inside the hidden region, whose bytes may not change", () => {
265
+ const html = doc("<p hidden>note<div>Revenue was 5 billion.</div>")
266
+ refused(html, [edit("5 billion", "50 billion")], "numerals")
267
+ flagged(html, html.replace("5 billion", "50 billion"))
268
+ })
269
+
270
+ test("a legitimately hidden region left byte-identical does not block a visible edit", () => {
271
+ const html = doc("<p hidden>internal note 42</p><p>Results improve [S1].</p><!-- build 7 -->")
272
+ const result = applyEdits(html, [edit("Results improve [S1].", "Results clearly improve [S1].")], LEDGER)
273
+ expect(result.applied).toEqual([0])
274
+ expect(diffPolish(html, result.out, LEDGER)).toEqual([])
275
+ })
276
+
277
+ const shown = doc(`<p>The span is <span>${f(SPAN)}</span> [S1].</p>`)
278
+ const changed = f(SPAN).replace(">320 m<", ">321 m<")
279
+ test("relabelling a visible fact span as hidden while changing its number, in one edit, is refused and flagged", () => {
280
+ refused(shown, [edit(`<span>${f(SPAN)}</span>`, `<span hidden>${changed}</span>`)], "markup changed")
281
+ flagged(shown, shown.replace(`<span>${f(SPAN)}</span>`, `<span hidden>${changed}</span>`))
282
+ })
283
+
284
+ test("the two-edit variant (hide, then change) is refused at both edits and flagged", () => {
285
+ const edits = [edit(`<span>${f(SPAN)}`, `<span style="display:none">${f(SPAN)}`), edit(">320 m<", ">321 m<")]
286
+ const result = applyEdits(shown, edits, LEDGER)
287
+ expect(result.applied).toEqual([])
288
+ expect(result.rejected[0].reason).toContain("markup changed")
289
+ flagged(shown, shown.replace(`<span>${f(SPAN)}</span>`, `<span style="display:none">${changed}</span>`))
290
+ })
291
+
292
+ test("unhiding a hidden fact span with a changed value is refused and flagged", () => {
293
+ const hidden = doc(`<p>The span is <span hidden>${f(SPAN)}</span> [S1].</p>`)
294
+ refused(hidden, [edit(`<span hidden>${f(SPAN)}</span>`, `<span>${changed}</span>`)], "markup changed")
295
+ flagged(hidden, hidden.replace(`<span hidden>${f(SPAN)}</span>`, `<span>${changed}</span>`))
296
+ })
297
+
298
+ test("a pure change of a fact or claim span's visibility class is flagged in both directions", () => {
299
+ const claim = doc('<p><span data-claim="C1">Results improve</span> [S1].</p>')
300
+ const hiddenClaim = claim.replace('<span data-claim="C1">', '<span hidden data-claim="C1">')
301
+ for (const [before, after] of [[claim, hiddenClaim], [hiddenClaim, claim]]) {
302
+ expect(diffPolish(before, after, LEDGER).find((d: { code: string }) => d.code === "polish_diff_fail")?.message).toContain("markup changed")
303
+ }
304
+ })
305
+
306
+ test.each([
307
+ ["color:transparent"],
308
+ ["max-height:0;overflow:hidden"],
309
+ ["position:absolute;left:-9999px"],
310
+ ["clip-path:inset(50%)"],
311
+ ["transform:scale(0)"],
312
+ ])("M2: wrapping a fact span in style=%s changes the markup and is refused and flagged", (style) => {
313
+ const html = doc(`<p>The span is ${f(SPAN)} [S1].</p>`)
314
+ refused(html, [edit(f(SPAN), `<span style="${style}">${f(SPAN)}</span>`)], "markup")
315
+ flagged(html, html.replace(f(SPAN), `<span style="${style}">${f(SPAN)}</span>`))
316
+ })
317
+
318
+ test("M2: inserting a stylesheet is refused and flagged", () => {
319
+ const html = doc(`<p>The span is ${f(SPAN)} [S1].</p>`)
320
+ refused(html, [edit("The span is", "<style>.x{display:none}</style>The span is")], "markup")
321
+ flagged(html, html.replace("The span is", "<style>.x{display:none}</style>The span is"))
322
+ })
323
+ })
324
+
325
+ describe("polish fact spans come from the placeholder renderers (duplication sweep)", () => {
326
+ const source = { ...SPAN, source_id: "S1", entity: null, valid_at: null }
327
+ const rendered = [renderValueHtml(source, source.raw), renderQuote(source, "p:nth-of-type(1)", source.quote, "html"), renderBlockquote(source, "k1", "html")]
328
+
329
+ test("the renderers emit exactly the FACT_ELEMENT_TAGS elements", () => {
330
+ expect(rendered.map((html) => html.match(/^<([a-z]+)/)?.[1]).sort()).toEqual([...FACT_ELEMENT_TAGS].sort())
331
+ })
332
+
333
+ test.each(rendered.map((html) => [html.match(/^<([a-z]+)/)?.[1], html]))("project and cutSpan recognize a rendered <%s>", (_tag, html) => {
334
+ const markup = `<p>Before ${html} after.</p>`
335
+ expect(project(markup).facts.map((fact: { id: string }) => fact.id)).toEqual(["F2"])
336
+ const at = markup.indexOf(html)
337
+ expect(cutSpan(markup, at + 3, at + html.length + 2)).toBe("F2")
338
+ expect(cutSpan(markup, at, at + html.length)).toBeNull()
339
+ })
340
+ })
341
+
342
+ describe("validatePolishEdits: the edit contract (duplication sweep)", () => {
343
+ test("the contract names before and after as required and reason as optional", () => {
344
+ expect(POLISH_EDIT_FIELDS).toEqual({ before: "required", after: "required", reason: "optional" })
345
+ })
346
+
347
+ test.each([
348
+ ["a non-array", { before: "a", after: "b" }, "array"],
349
+ ["a null entry", [null], "edit 0"],
350
+ ["a non-string before", [{ before: 1, after: "b" }], "before"],
351
+ ["a missing after", [{ before: "a" }], "after"],
352
+ ["a non-string reason", [{ before: "a", after: "b", reason: 3 }], "reason"],
353
+ ])("%s is refused with a TypeError, by the validator and by applyEdits alike", (_label, edits, word) => {
354
+ expect(() => validatePolishEdits(edits)).toThrow(TypeError)
355
+ expect(() => validatePolishEdits(edits)).toThrow(word)
356
+ expect(() => applyEdits("<p>a</p>", edits as never, LEDGER)).toThrow(word)
357
+ })
358
+
359
+ test("an edit without a reason is valid", () => {
360
+ const edits = [{ before: "a", after: "b" }]
361
+ expect(validatePolishEdits(edits)).toBe(edits)
362
+ })
363
+ })
@@ -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,168 @@
1
+ import { describe, expect, test } from "bun:test"
2
+ import { readFileSync } from "node:fs"
3
+ import { join } from "node:path"
4
+
5
+ import { renderValueHtml } from "./placeholder.mjs"
6
+ import { DEFECT_CODES } from "./contracts.mjs"
7
+ import * as polishDiff from "./polish-diff.mjs"
8
+ import * as polishProject from "./polish-project.mjs"
9
+ import * as polishRules from "./polish-rules.mjs"
10
+
11
+ const { diffPolish } = polishDiff
12
+
13
+ const SPAN = { fact_id: "F2", kind: "measured", quote: "The bridge has a main span of 320 m.", raw: "320 m", value: "320", unit: "m", qualifier: null }
14
+ const MAKER = { fact_id: "F5", kind: "measured", quote: "In May, Samsung Electronics shipped 12 units.", raw: "12 units", value: "12", unit: "units", qualifier: null }
15
+ const SALES = { fact_id: "F6", kind: "measured", quote: "Sales were approximately 7 units.", raw: "7 units", value: "7", unit: "units", qualifier: null }
16
+ const LEDGER = { schemaVersion: 1, rows: [SPAN, MAKER, SALES], events: [] }
17
+ const span = renderValueHtml(SPAN, SPAN.raw)
18
+ const doc = (body: string) => `<html><body>\n${body}\n</body></html>`
19
+ const codes = (list: { code: string }[]) => list.map((item) => item.code)
20
+
21
+ describe("diffPolish: protected content", () => {
22
+ test("identical documents have no defects", () => {
23
+ const html = doc(`<p>The span was about ${span} [S1].</p><table><tr><td>A</td></tr></table>`)
24
+ expect(diffPolish(html, html, LEDGER)).toEqual([])
25
+ })
26
+
27
+ test("one numeral that differs inside a fact span is polish_diff_fail", () => {
28
+ const before = doc(`<p>The span is ${span} [S1].</p>`)
29
+ const after = doc(`<p>The span is ${span.replace(">320 m<", ">321 m<")} [S1].</p>`)
30
+ expect(codes(diffPolish(before, after, LEDGER))).toEqual(["polish_diff_fail"])
31
+ })
32
+
33
+ test("one numeral that differs outside fact spans is polish_diff_fail", () => {
34
+ const before = doc("<p>Throughput grew 3.5 times in the pilot [S1].</p>")
35
+ const after = doc("<p>Throughput grew 35 times in the pilot [S1].</p>")
36
+ expect(codes(diffPolish(before, after, LEDGER))).toEqual(["polish_diff_fail"])
37
+ })
38
+
39
+ test("a dropped table row is polish_diff_fail", () => {
40
+ const before = doc("<table><tr><td>A</td></tr><tr><td>B</td></tr></table>")
41
+ const after = doc("<table><tr><td>A</td></tr></table>")
42
+ expect(codes(diffPolish(before, after, LEDGER))).toContain("polish_diff_fail")
43
+ })
44
+
45
+ test("a changed ticker or six-digit exchange code is polish_diff_fail", () => {
46
+ expect(codes(diffPolish(doc("<p>AAPL [S1].</p>"), doc("<p>MSFT [S1].</p>"), LEDGER))).toEqual(["polish_diff_fail"])
47
+ expect(codes(diffPolish(doc("<p>Code 005930 [S1].</p>"), doc("<p>Code 000660 [S1].</p>"), LEDGER))).toEqual(["polish_diff_fail"])
48
+ })
49
+ })
50
+
51
+ describe("diffPolish: hedges, churn, links and names", () => {
52
+ test("two hedges removed from claim-only text is polish_hedging_decreased", () => {
53
+ const before = doc("<p>Results may improve and are provisional. The study covered the pilot only.</p>")
54
+ const after = doc("<p>Results improve and are final. The study covered the pilot only.</p>")
55
+ expect(codes(diffPolish(before, after, LEDGER))).toEqual(["polish_hedging_decreased"])
56
+ })
57
+
58
+ test("deleting a writer-added hedge the source lacks is not a hedge loss", () => {
59
+ const before = doc(`<p>The span was about ${span} [S1].</p>`)
60
+ const after = doc(`<p>The span was ${span} [S1].</p>`)
61
+ expect(diffPolish(before, after, LEDGER)).toEqual([])
62
+ })
63
+
64
+ test("a 60 percent rewrite is polish_text_churn and 50 percent is not", () => {
65
+ expect(codes(diffPolish(doc("<p>abcdefghij</p>"), doc("<p>klmnopghij</p>"), LEDGER))).toEqual(["polish_text_churn"])
66
+ expect(diffPolish(doc("<p>abcdefghij</p>"), doc("<p>klmnofghij</p>"), LEDGER)).toEqual([])
67
+ })
68
+
69
+ test("a changed link target or an internal link that stops resolving is polish_link_changed", () => {
70
+ const link = (href: string) => doc(`<p id="top"><a href="${href}">Reference</a> [S1].</p>`)
71
+ expect(codes(diffPolish(link("https://example.invalid/a"), link("https://example.invalid/b"), LEDGER))).toEqual(["polish_diff_fail", "polish_link_changed"])
72
+ const before = doc('<h2 id="m">Method</h2><p><a href="#m">Method</a> [S1].</p>')
73
+ const after = doc('<h2 id="method">Method</h2><p><a href="#m">Method</a> [S1].</p>')
74
+ expect(codes(diffPolish(before, after, LEDGER))).toEqual(["polish_diff_fail", "polish_link_changed"])
75
+ })
76
+
77
+ test("a ledger-quote name present before and absent after is polish_name_changed", () => {
78
+ const before = doc("<p>Samsung Electronics shipped more units in the quarter [S1].</p>")
79
+ const after = doc("<p>The company shipped more units in the quarter [S1].</p>")
80
+ expect(codes(diffPolish(before, after, LEDGER))).toEqual(["polish_name_changed"])
81
+ })
82
+
83
+ test("every defect carries its fixed severity", () => {
84
+ const before = doc("<p>Results may improve and are provisional [S1].</p><table><tr><td>A</td></tr><tr><td>B</td></tr></table>")
85
+ const after = doc("<p>Results improve and are final [S1].</p><table><tr><td>A</td></tr></table>")
86
+ const defects = diffPolish(before, after, LEDGER)
87
+ expect(codes(defects)).toEqual(["polish_diff_fail", "polish_hedging_decreased"])
88
+ for (const defect of defects) expect(defect.severity).toBe(DEFECT_CODES[defect.code].severity)
89
+ })
90
+ })
91
+
92
+ describe("diffPolish: severities come from DEFECT_CODES (review #11152 round 1)", () => {
93
+ test("no polish module exports a local severity table", () => {
94
+ for (const mod of [polishDiff, polishRules]) expect(Object.keys(mod).filter((name) => /severity/i.test(name))).toEqual([])
95
+ for (const file of ["polish-diff.mjs", "polish-rules.mjs", "polish-apply.mjs", "polish-project.mjs"]) {
96
+ const source = readFileSync(join(import.meta.dir, file), "utf8")
97
+ expect(source.match(/["'](?:blocker|major|minor)["']/g) ?? [], file).toEqual([])
98
+ }
99
+ })
100
+
101
+ test("every polish code resolves through DEFECT_CODES and each defect carries that severity", () => {
102
+ for (const code of polishRules.POLISH_CODES) expect(Object.hasOwn(DEFECT_CODES, code), code).toBe(true)
103
+ const before = doc('<p>Samsung Electronics may improve [S1].</p><p><a href="/a">x</a> abcdefghij</p><table><tr><td>A</td></tr><tr><td>B</td></tr></table>')
104
+ const after = doc('<p>The firm improves [S1].</p><p><a href="/b">x</a> klmnopqrstuvwxyz</p><table><tr><td>A</td></tr></table>')
105
+ const defects = diffPolish(before, after, LEDGER)
106
+ expect(codes(defects)).toEqual([...polishRules.POLISH_CODES])
107
+ for (const defect of defects) expect(defect.severity, defect.code).toBe(DEFECT_CODES[defect.code].severity)
108
+ })
109
+ })
110
+
111
+ describe("diffPolish: rendered visibility and hedge comparison (review #11152 round 1)", () => {
112
+ test.each([
113
+ ["a hidden attribute", `<span hidden>${span}</span>`],
114
+ ["display:none", `<span style="display:none">${span}</span>`],
115
+ ["a template", `<template>${span}</template>`],
116
+ ])("a fact span hidden by %s is polish_diff_fail", (_label, hidden) => {
117
+ expect(codes(diffPolish(doc(`<p>The span is ${span} [S1].</p>`), doc(`<p>The span is ${hidden} [S1].</p>`), LEDGER))).toContain("polish_diff_fail")
118
+ })
119
+
120
+ test("wrapping a hedge changes markup and is blocked before full fidelity verification", () => {
121
+ const after = doc("<p>Results <span hidden>may</span> improve [S1].</p>")
122
+ expect(codes(diffPolish(doc("<p>Results may improve [S1].</p>"), after, LEDGER))).toEqual(["polish_diff_fail"])
123
+ })
124
+
125
+ test("a ledger hedge the sentence never held is not a decrease", () => {
126
+ const sales = renderValueHtml(SALES, SALES.raw)
127
+ const before = doc(`<p>The share of revenue was ${sales} [S1].</p>`)
128
+ const after = doc(`<p>The share in revenue was ${sales} [S1].</p>`)
129
+ expect(diffPolish(before, after, LEDGER)).toEqual([])
130
+ })
131
+
132
+ test("single-quoted and unquoted href changes are polish_link_changed", () => {
133
+ expect(codes(diffPolish(doc("<p><a href='/a'>x</a></p>"), doc("<p><a href='/b'>x</a></p>"), LEDGER))).toEqual(["polish_diff_fail", "polish_link_changed"])
134
+ expect(codes(diffPolish(doc("<p><a href=/a>x</a></p>"), doc("<p><a href=/b>x</a></p>"), LEDGER))).toEqual(["polish_diff_fail", "polish_link_changed"])
135
+ })
136
+
137
+ test("the same input gives byte-identical output", () => {
138
+ const before = doc("<p>Results may improve and are provisional [S1].</p>")
139
+ const after = doc("<p>Results improve and are final [S1].</p>")
140
+ expect(JSON.stringify(diffPolish(before, after, LEDGER))).toBe(JSON.stringify(diffPolish(before, after, LEDGER)))
141
+ })
142
+ })
143
+
144
+ describe("claim ids and fact-span scanning (review #11152 round 2, L1 and L2)", () => {
145
+ test("L1: a changed data-claim id is a blocking polish_diff_fail, as apply treats it, not a minor link change", () => {
146
+ const defects = diffPolish(doc('<p data-claim="C1">Results improve [S1].</p>'), doc('<p data-claim="C2">Results improve [S1].</p>'), LEDGER)
147
+ expect(codes(defects)).toEqual(["polish_diff_fail"])
148
+ expect(defects[0].severity).toBe(DEFECT_CODES.polish_diff_fail.severity)
149
+ expect(defects[0].message).toContain("markup changed")
150
+ })
151
+
152
+ test("L2: a nested same-name element and a quoted '>' stay inside one fact span", () => {
153
+ const nested = '<p><q data-fact="F1">a <q>b</q> c</q> d</p>'
154
+ expect(polishProject.factSpans(nested).map((s: { html: string }) => s.html)).toEqual(['<q data-fact="F1">a <q>b</q> c</q>'])
155
+ const quoted = '<p><data title="a>b" data-fact="F2">320 m</data> d</p>'
156
+ expect(polishProject.factSpans(quoted).map((s: { id: string; html: string }) => [s.id, s.html])).toEqual([["F2", '<data title="a>b" data-fact="F2">320 m</data>']])
157
+ })
158
+ })
159
+
160
+ describe("polish policies have one owner (duplication sweep)", () => {
161
+ test("the named policies are exported once, from their owners", () => {
162
+ expect(polishRules.POLISH_CHURN_LIMIT).toBe(0.5)
163
+ expect("Samsung Electronics".match(polishRules.POLISH_NAME)).toEqual(["Samsung", "Electronics"])
164
+ expect(polishDiff.TICKER).toBe(polishRules.TICKER)
165
+ expect(polishProject.POLISH_LINK_ATTRIBUTES).toEqual(["href"])
166
+ expect("v1.25 and 3,000".match(polishProject.POLISH_DIGITS)).toEqual(["1.25", "3,000"])
167
+ })
168
+ })