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,229 @@
1
+ // fidelity-values.mjs - value, quote, and chart checks of the deterministic fidelity gate.
2
+ //
3
+ // Each data-fact element must match its ledger row, and each Q element the script's
4
+ // reconstruction of its selection (report text never gets a fuzzy pass). Cited rows, table rows,
5
+ // snapshots, and chart CSVs are rechecked; the numeral and quotation trace is fidelity-trace.mjs,
6
+ // and the claim-kind checks (data-claim-kind, unsupported_verification) are fidelity-claims.mjs.
7
+ // No model, no network.
8
+
9
+ import { existsSync, readFileSync } from "node:fs"
10
+ import { join, resolve, sep } from "node:path"
11
+
12
+ import { sha256Of } from "./cli-support.mjs"
13
+ import { sameValue, unitKey } from "./fidelity-binding.mjs"
14
+ import { claimChecks } from "./fidelity-claims.mjs"
15
+ import { STRUCTURAL, clip, traceChecks } from "./fidelity-trace.mjs"
16
+ import { parseHtml, proseText } from "./html-lite.mjs"
17
+ import { parseNumeral } from "./numerals.mjs"
18
+ import { div, fromDecimal, sub } from "./rational.mjs"
19
+ import { normalizeText } from "./text-normalize.mjs"
20
+
21
+ const DECIMAL = /^-?\d+(?:\.\d+)?$/
22
+
23
+ const bare = (text) => normalizeText(String(text ?? "").replace(/\s*\[(?:S|Source\s*)\d+\]\s*$/i, "")).replace(/\u2026/g, "...").replace(/\s*\.\.\.\s*/g, "...").replace(/^["']+|["']+$/g, "").trim()
24
+
25
+ /** The text fill renders for a quote selection: full, lead=<n>, or from=<a>;to=<b> (placeholder form accepted). */
26
+ export function selectQuote(quote, selector) {
27
+ const text = String(quote ?? "").trim()
28
+ if (selector === "full") return text
29
+ const lead = /^lead=(\d+)$/.exec(selector)
30
+ const words = text.split(/\s+/)
31
+ if (lead) return words.length <= Number(lead[1]) ? text : `${words.slice(0, Number(lead[1])).join(" ")}...`
32
+ const range = /^from=(.+);to=(.+)$/.exec(selector) ?? /^from="(.+)":to="(.+)"$/.exec(selector)
33
+ if (!range) return null
34
+ const once = (anchor) => text.indexOf(anchor) !== -1 && text.indexOf(anchor) === text.lastIndexOf(anchor)
35
+ if (!once(range[1]) || !once(range[2])) return null
36
+ const start = text.indexOf(range[1])
37
+ const end = text.indexOf(range[2]) + range[2].length
38
+ if (end <= start) return null
39
+ return `${start > 0 ? "..." : ""}${text.slice(start, end)}${end < text.length ? "..." : ""}`
40
+ }
41
+
42
+ function quoteProblem(row, shown, selector) {
43
+ const want = selectQuote(row.quote, selector)
44
+ if (want === null) return ["quote_not_verbatim", `selection "${selector}" does not resolve in ${row.fact_id}'s quote`]
45
+ return bare(shown) === bare(want) ? null : ["quote_not_verbatim", `"${clip(shown)}" is not the ${selector} selection of ${row.fact_id}: "${clip(want)}"`]
46
+ }
47
+
48
+ function factProblem(row, shown, attrs = {}) {
49
+ if (attrs["data-unit"] !== undefined && unitKey(attrs["data-unit"]) !== unitKey(row.unit)) return ["unit_mismatch", `data-unit "${attrs["data-unit"]}" but ${row.fact_id} is in "${row.unit}"`]
50
+ if (attrs.value !== undefined && !sameValue(attrs.value, row.value)) return ["value_mismatch", `value="${attrs.value}" but ${row.fact_id} is ${row.value}`]
51
+ const text = normalizeText(shown)
52
+ const forms = [row.raw, row.render, row.value, row.unit && `${row.value} ${row.unit}`].filter(Boolean).map(normalizeText)
53
+ if (forms.some((form) => text === form || (text.endsWith(` ${form}`) && !/\d/.test(text.slice(0, -form.length))))) return null
54
+ const parsed = parseNumeral(text)
55
+ if (!parsed) return unitKey(text) === unitKey(row.unit) ? null : ["value_mismatch", `"${clip(shown)}" shows no value of ${row.fact_id} (${row.value})`]
56
+ if (!sameValue(parsed.value, row.value)) return ["value_mismatch", `"${clip(shown)}" reads ${parsed.value}, but ${row.fact_id} is ${row.value}`]
57
+ if (parsed.unitRaw && unitKey(parsed.unitRaw) !== unitKey(row.unit)) return ["unit_mismatch", `"${clip(shown)}" is in ${parsed.unitRaw}, but ${row.fact_id} is in ${row.unit}`]
58
+ return null
59
+ }
60
+
61
+ /** Whether a candidate fill site reads as what fill wrote there or as the entry's row ("1,237 million" is neither 1,237 nor F2). */
62
+ const fillSiteFits = (rows) => (entry, tail) => {
63
+ const [row, site, own] = [rows.get(entry.fact_id), parseNumeral(tail, 0), parseNumeral(entry.rendered, 0)]
64
+ if (!row || row.kind === "quote" || !site || !own) return true
65
+ if (sameValue(site.value, own.value) && unitKey(site.unitRaw ?? "") === unitKey(own.unitRaw ?? "")) return true
66
+ return factProblem(row, tail.slice(0, Math.max(entry.rendered.length, site.span[1]))) === null
67
+ }
68
+
69
+ const withoutFooter = (node) => proseText({ children: node.children.filter((child) => child.tag !== "footer") })
70
+
71
+ /** One row per active value row, as `fact export --csv` writes it. */
72
+ export function exportRows(ledger) {
73
+ return (ledger?.rows ?? []).filter((row) => row.status === "active" && row.kind !== "quote" && row.value !== null).map((row) => ({ fact_id: row.fact_id, label: row.label, plot_value: row.value, unit: row.unit ?? "" }))
74
+ }
75
+
76
+ function readCsv(path) {
77
+ if (!existsSync(path)) return null
78
+ const lines = readFileSync(path, "utf8").split(/\r?\n/).filter((line) => line.trim() !== "")
79
+ const cells = (line) => line.split(",").map((cell) => cell.trim().replace(/^"(.*)"$/, "$1"))
80
+ const head = cells(lines[0] ?? "")
81
+ const [id, value, unit] = ["fact_id", "plot_value", "unit"].map((name) => head.indexOf(name))
82
+ if (id < 0 || value < 0 || unit < 0 || lines.length < 2) return null
83
+ return lines.slice(1).map((line) => cells(line)).map((c) => ({ fact_id: c[id], plot_value: c[value], unit: c[unit] ?? "" }))
84
+ }
85
+
86
+ const walk = (node) => node.children.filter((child) => child.tag !== "#text").flatMap((child) => [child, ...walk(child)])
87
+
88
+ /** SVG text nodes of a chart: data labels, or ticks with their declared grid. */
89
+ function svgLabels(raw) {
90
+ return walk(parseHtml(raw)).filter((node) => node.tag === "text").map((node) => {
91
+ let tick = null
92
+ for (let cur = node.parent; cur && !tick; cur = cur.parent) if (cur.tag === "g" && /(?:^|\s)ticks(?:\s|$)/.test(cur.attrs.class ?? "")) tick = cur.attrs
93
+ return { text: proseText(node), tick }
94
+ })
95
+ }
96
+
97
+ function onGrid(value, tick) {
98
+ const [step, min, max] = [tick["data-tick-step"], tick["data-tick-min"], tick["data-tick-max"]]
99
+ if (![value, step, min, max].every((v) => DECIMAL.test(String(v ?? ""))) || /^-?0+(?:\.0+)?$/.test(step)) return false
100
+ const offset = sub(fromDecimal(value), fromDecimal(min))
101
+ return offset.num >= 0n && sub(fromDecimal(max), fromDecimal(value)).num >= 0n && div(offset, fromDecimal(step)).den === 1n
102
+ }
103
+
104
+ function numerals(text) {
105
+ const out = []
106
+ for (let at = 0, n = parseNumeral(text, 0); n; at = n.span[1], n = parseNumeral(text, at)) out.push(n.value)
107
+ return out
108
+ }
109
+
110
+ function chartFigures(ctx) {
111
+ const charts = new Map()
112
+ for (const fig of ctx.all.filter((node) => node.tag === "figure")) {
113
+ const inner = ctx.util.descendants(fig)
114
+ const labels = inner.filter((node) => node.tag === "svg").flatMap((svg) => svgLabels(svg.raw ?? ""))
115
+ const marked = fig.attrs["data-chart-source"] !== undefined || ctx.util.classTokens(fig).includes("chart")
116
+ if (marked || inner.some((node) => node.tag === "img" && ctx.util.classTokens(node).includes("chart")) || labels.some((label) => /\d/.test(label.text))) charts.set(fig, labels)
117
+ }
118
+ return charts
119
+ }
120
+
121
+ function chartProblem(ctx, fig, labels, ledger) {
122
+ const source = fig.attrs["data-chart-source"]
123
+ if (!source) return "chart figure has no data-chart-source"
124
+ const csv = readCsv(resolve(ctx.baseDir ?? ".", source))
125
+ if (!csv) return `chart data ${source} is missing or lacks fact_id, plot_value, unit columns`
126
+ const exported = new Map(exportRows(ledger).map((row) => [row.fact_id, row]))
127
+ const drift = csv.find((row) => exported.get(row.fact_id)?.plot_value !== row.plot_value || exported.get(row.fact_id)?.unit !== row.unit)
128
+ if (drift) return `chart data row ${drift.fact_id} differs from the ledger export`
129
+ const plotted = (value) => csv.some((row) => sameValue(row.plot_value, value))
130
+ for (const label of labels) {
131
+ const bad = numerals(label.text).find((value) => (label.tick ? !onGrid(value, label.tick) : !plotted(value)))
132
+ if (bad !== undefined) return `${label.tick ? "tick" : "label"} "${label.text}" is not ${label.tick ? "on the declared tick grid" : `a plot_value of ${source}`}`
133
+ }
134
+ const caption = ctx.util.descendants(fig).find((node) => node.tag === "figcaption")
135
+ const stray = numerals(caption ? STRUCTURAL.reduce((text, re) => text.replace(re, " "), proseText(caption)) : "").find((value) => !plotted(value))
136
+ return stray === undefined ? null : `caption numeral ${stray} is not a plot_value of ${source}`
137
+ }
138
+
139
+ /**
140
+ * @param {object} ctx buildContext(...) from gates-static.mjs
141
+ * @param {{ rows: object[] }} ledger
142
+ * @param {{ fact_id: string, rendered: string, selector?: string }[] | null} fillMap Markdown fill map, or null
143
+ * @param {string | null} sourcesDir directory of <S-id>/{text.txt, meta.json} snapshots
144
+ * @param {{ recompute?: (ledger: object, row: object) => { ok: boolean }, asOf?: string | null }} options
145
+ */
146
+ export function valueChecks(ctx, ledger, fillMap, sourcesDir, options = {}) {
147
+ const out = []
148
+ const flag = (code, node, message) => out.push(ctx.defect(code, node, message))
149
+ const rows = new Map((ledger?.rows ?? []).map((row) => [row.fact_id, row]))
150
+ const cited = new Map()
151
+ const check = (id, node, problemOf) => {
152
+ const row = rows.get(id)
153
+ if (!row) return flag("unresolved_placeholder", node, `${id} names no ledger row`)
154
+ if (!cited.has(id)) cited.set(id, node)
155
+ const problem = problemOf(row)
156
+ if (problem) flag(problem[0], node, problem[1])
157
+ }
158
+ for (const node of ctx.all.filter((cur) => cur.attrs["data-fact"] !== undefined)) {
159
+ const selector = node.attrs["data-quote-sel"] ?? "full"
160
+ check(node.attrs["data-fact"], node, (row) => (row.kind === "quote" ? quoteProblem(row, withoutFooter(node), selector) : factProblem(row, proseText(node), node.attrs)))
161
+ }
162
+ const docText = bare(proseText(ctx.body))
163
+ for (const entry of fillMap ?? []) {
164
+ check(entry.fact_id, ctx.body, (row) => {
165
+ const problem = row.kind === "quote" ? quoteProblem(row, entry.rendered, entry.selector ?? "full") : factProblem(row, entry.rendered)
166
+ if (problem || docText.includes(bare(entry.rendered))) return problem
167
+ return [row.kind === "quote" ? "quote_not_verbatim" : "value_mismatch", `filled text "${clip(entry.rendered)}" of ${row.fact_id} is no longer in the report`]
168
+ })
169
+ }
170
+ rowChecks(ctx, ledger, rows, cited, options, flag)
171
+ for (const tr of ctx.all.filter((node) => node.tag === "tr" && node.attrs["data-row-kind"] !== "comparison")) {
172
+ const groups = new Set(ctx.util.descendants(tr).map((node) => rows.get(node.attrs["data-fact"])?.observation_id).filter(Boolean))
173
+ if (groups.size > 1) flag("join_integrity_fail", tr, `one table row joins observations ${[...groups].join(", ")}`)
174
+ }
175
+ const meta = sourceChecks(ctx, rows, cited, sourcesDir, flag)
176
+ claimChecks(ctx, rows, (row) => provenance(sourcesDir, meta, row), flag)
177
+ const charts = chartFigures(ctx)
178
+ for (const { node, scope } of ctx.elements) {
179
+ const kind = ctx.kindOf(scope)
180
+ if ((node.tag === "q" || node.tag === "blockquote") && node.attrs["data-fact"] === undefined && kind !== "sources" && kind !== "toc") flag("quote_unbound", node, `<${node.tag}> with no Q row: "${clip(proseText(node))}"`)
181
+ }
182
+ traceChecks(ctx, charts, fillMap, flag, fillSiteFits(rows))
183
+ for (const [fig, labels] of charts) {
184
+ const problem = chartProblem(ctx, fig, labels, ledger)
185
+ if (problem) flag("chart_value_untraceable", fig, problem)
186
+ }
187
+ return out
188
+ }
189
+
190
+ function rowChecks(ctx, ledger, rows, cited, options, flag) {
191
+ // An as-of of "latest ..." or "... per snapshot" names no fixed revision, so quote_stale_version is skipped.
192
+ const asOf = options.asOf && !/^latest\b|per snapshot/i.test(options.asOf) ? options.asOf : null
193
+ for (const [id, node] of cited) {
194
+ const row = rows.get(id)
195
+ if (row.fuzzy === true) flag("quote_fuzzy_match", node, `${id} was registered from a similarity match on an OCR-noisy snapshot`)
196
+ const revision = typeof row.revision === "string" ? row.revision : (row.revision?.id ?? row.revision?.tag ?? null)
197
+ if (asOf && revision && !asOf.includes(revision)) flag("quote_stale_version", node, `${id} is from revision ${revision}; the brief targets "${asOf}"`)
198
+ if ((row.kind === "derived" || row.kind === "converted") && options.recompute?.(ledger, row)?.ok === false) flag("value_mismatch", node, `${id} does not recompute from its operands`)
199
+ const window = row.snapshot_window
200
+ if (window && typeof window === "object" && window.start && window.end) {
201
+ const [start, end] = [Date.parse(window.start), Date.parse(window.end)]
202
+ const outside = (row.operands ?? []).find((op) => !(Date.parse(rows.get(op)?.observed_at) >= start && Date.parse(rows.get(op)?.observed_at) < end))
203
+ if (outside) flag("snapshot_mismatch", node, `operand ${outside} of ${id} was observed outside ${window.start} to ${window.end}`)
204
+ }
205
+ }
206
+ }
207
+
208
+ /** meta.json kind of the snapshot holding the row's own locator artifact; undefined when the artifact is absent or escapes sources/<source_id>/. */
209
+ function provenance(sourcesDir, meta, row) {
210
+ const artifact = row.locator?.artifact
211
+ if (!sourcesDir || typeof row.source_id !== "string" || typeof artifact !== "string") return undefined
212
+ const dir = resolve(sourcesDir, row.source_id)
213
+ const path = resolve(dir, artifact)
214
+ return path.startsWith(`${dir}${sep}`) && existsSync(path) ? meta.get(row.source_id)?.kind : undefined
215
+ }
216
+
217
+ function sourceChecks(ctx, rows, cited, sourcesDir, flag) {
218
+ const ids = new Set([...cited.keys()].map((id) => rows.get(id)?.source_id).filter(Boolean))
219
+ for (const match of proseText(ctx.body).matchAll(/\[S(\d+)\]/g)) ids.add(`S${match[1]}`)
220
+ const meta = new Map()
221
+ for (const id of sourcesDir ? ids : []) {
222
+ const [metaPath, textPath] = [join(sourcesDir, id, "meta.json"), join(sourcesDir, id, "text.txt")]
223
+ if (!existsSync(metaPath) || !existsSync(textPath)) continue
224
+ meta.set(id, JSON.parse(readFileSync(metaPath, "utf8")))
225
+ const actual = sha256Of(readFileSync(textPath))
226
+ if (meta.get(id).sha256 !== actual) flag("snapshot_hash_mismatch", ctx.body, `${id}/text.txt changed after its snapshot was pinned`)
227
+ }
228
+ return meta
229
+ }
@@ -0,0 +1,113 @@
1
+ // gates-fidelity.mjs - the deterministic fidelity gate over a filled HTML report.
2
+ //
3
+ // checkFidelity refuses to judge a report whose brief has no locked `## Fidelity` block (one
4
+ // fidelity_interview_incomplete defect). Otherwise it builds the static-gate document context
5
+ // once and runs the value, binding, and lexicon checks over it, returning defects in the shape
6
+ // `check` emits plus integrity, sorted blocker, major, minor, then line, selector, code. It never
7
+ // calls a model or reads the network; the meaning verifier consumes its pairs separately.
8
+ // Plain Node ESM, sibling imports only.
9
+
10
+ import { existsSync, readFileSync } from "node:fs"
11
+ import { join } from "node:path"
12
+
13
+ import { DEFECT_CODES } from "./contracts.mjs"
14
+ import { bindingChecks, proseSentences } from "./fidelity-binding.mjs"
15
+ import { isLocked, parseFidelityBlock } from "./fidelity-brief.mjs"
16
+ import { lexiconChecks } from "./fidelity-lexicon.mjs"
17
+ import { valueChecks } from "./fidelity-values.mjs"
18
+ import { buildContext } from "./gates-static.mjs"
19
+ import { parseHtml } from "./html-lite.mjs"
20
+
21
+ /** Every code this gate can raise. */
22
+ export const FIDELITY_GATE_CODES = Object.freeze([
23
+ "fidelity_interview_incomplete", "unresolved_placeholder", "value_mismatch", "unit_mismatch", "quote_not_verbatim",
24
+ "quote_unbound", "claim_status_invalid", "snapshot_hash_mismatch", "chart_value_untraceable", "untraceable_number",
25
+ "unsupported_verification", "conflicting_readings", "hedge_dropped", "direction_flipped", "causality_added",
26
+ "qualifier_dropped", "unbound_sentence", "snapshot_mismatch", "join_integrity_fail", "quote_stale_version",
27
+ "quote_fuzzy_match", "name_outside_quote", "scope_widened_warn", "claim_kind_mismatch", "verification_claim_unmarked",
28
+ ])
29
+ const RANK = { blocker: 0, major: 1, minor: 2 }
30
+
31
+ /** The gate and delivery barrier share registry policy; caller severities cannot demote it. */
32
+ export function blocksDelivery(defect) {
33
+ if (!Object.hasOwn(DEFECT_CODES, defect?.code)) return true
34
+ const entry = DEFECT_CODES[defect.code]
35
+ return entry.integrity || entry.severity !== "minor"
36
+ }
37
+
38
+ export function deliveryBlockReason(defect) {
39
+ return Object.hasOwn(DEFECT_CODES, defect?.code) ? `fidelity_unresolved: ${defect.code}` : `unknown defect code ${defect?.code}`
40
+ }
41
+
42
+ function withIntegrity(defect) {
43
+ const { code, severity, selector, line, message, hint } = defect
44
+ return { code, severity, integrity: DEFECT_CODES[code].integrity, selector, line, message, hint }
45
+ }
46
+
47
+ function compareDefects(a, b) {
48
+ const text = (x, y) => (x < y ? -1 : x > y ? 1 : 0)
49
+ return RANK[a.severity] - RANK[b.severity] || (a.line ?? 0) - (b.line ?? 0) || text(a.selector ?? "", b.selector ?? "") || text(a.code, b.code)
50
+ }
51
+
52
+ function readAcronyms(sourcesDir, sourceId, cache) {
53
+ if (!sourcesDir || !sourceId) return {}
54
+ if (!cache.has(sourceId)) {
55
+ const path = join(sourcesDir, sourceId, "acronyms.json")
56
+ cache.set(sourceId, existsSync(path) ? JSON.parse(readFileSync(path, "utf8")) : {})
57
+ }
58
+ return cache.get(sourceId)
59
+ }
60
+
61
+ /** One (row quote, report sentence) pair per fact cited in a prose sentence outside sources and toc. */
62
+ export function fidelityPairs(ctx, ledger, sourcesDir) {
63
+ const rows = new Map((ledger?.rows ?? []).map((row) => [row.fact_id, row]))
64
+ const cache = new Map()
65
+ const pairs = []
66
+ for (const block of proseSentences(ctx)) {
67
+ if (block.kind === "sources" || block.kind === "toc") continue
68
+ block.sentences.forEach((sentence, index) => {
69
+ for (const fact of sentence.facts) {
70
+ const row = rows.get(fact.id)
71
+ if (!row?.quote) continue
72
+ pairs.push({
73
+ pair_id: `${fact.id}@${block.owner.line}:${index + 1}`,
74
+ fact_id: fact.id,
75
+ quote: row.quote,
76
+ sentence: sentence.text,
77
+ span: [fact.start - sentence.start, fact.end - sentence.start],
78
+ qualifier: row.kind === "quote" ? null : row.qualifier,
79
+ acronyms: readAcronyms(sourcesDir, row.source_id, cache),
80
+ node: block.owner,
81
+ })
82
+ }
83
+ })
84
+ }
85
+ return pairs
86
+ }
87
+
88
+ function incomplete(parsed) {
89
+ const reasons = !parsed?.present ? "no ## Fidelity block" : [...(parsed.errors ?? []), ...parsed.missing.map((f) => `missing: ${f}`), ...parsed.pending.map((f) => `pending: ${f}`)].join("; ")
90
+ const entry = DEFECT_CODES.fidelity_interview_incomplete
91
+ return { code: "fidelity_interview_incomplete", severity: entry.severity, integrity: entry.integrity, selector: null, line: null, message: `fidelity interview incomplete (${reasons})`, hint: entry.hint }
92
+ }
93
+
94
+ /**
95
+ * @param {string} html the filled report
96
+ * @param {{ ledger: { rows: object[] }, sourcesDir?: string | null, claims?: object | null, fillMap?: object[] | null,
97
+ * brief: string | object, baseDir?: string, recompute?: (ledger: object, row: object) => { ok: boolean } }} options
98
+ * `brief` is the brief.md text or parseFidelityBlock output; `baseDir` resolves data-chart-source paths.
99
+ * @returns {{ defects: object[], gatesRun: string[] }}
100
+ */
101
+ export function checkFidelity(html, { ledger, sourcesDir = null, claims = null, fillMap = null, brief, baseDir, recompute } = {}) {
102
+ const parsed = brief && typeof brief === "object" ? brief : parseFidelityBlock(String(brief ?? ""))
103
+ if (!isLocked(parsed)) return { defects: [incomplete(parsed)], gatesRun: ["fidelity_interview_incomplete"] }
104
+ const ctx = buildContext(parseHtml(html), { baseDir })
105
+ const defects = [
106
+ ...valueChecks(ctx, ledger, fillMap, sourcesDir, { recompute, asOf: parsed.fields["as of"] }),
107
+ ...bindingChecks(ctx, claims, ledger),
108
+ ]
109
+ const pairs = fidelityPairs(ctx, ledger, sourcesDir)
110
+ const byId = new Map(pairs.map((pair) => [pair.pair_id, pair]))
111
+ for (const finding of lexiconChecks(pairs).findings) defects.push(ctx.defect(finding.code, byId.get(finding.pair_id).node, `${finding.pair_id}: ${finding.message}`))
112
+ return { defects: defects.map(withIntegrity).sort(compareDefects), gatesRun: [...FIDELITY_GATE_CODES] }
113
+ }
@@ -18,14 +18,19 @@ import { PROSE_TAGS, proseText, selectorPath } from "./html-lite.mjs"
18
18
  const CITATION_MARKER = /\[S\d+\]|\[Source\s*\d+\]|\[\uCD9C\uCC98[^\]]*\]|\[\^\d+\]/i
19
19
  const ANCHOR_HREF = /^#(?:s|src|source|ref|fn)/i
20
20
  const ANCHOR_TEXT = /^\[(?:\uCD9C\uCC98|Source\s*\d+|S\d+)\]$/i
21
- /** Closing ("how this report was made") heading; Korean alternates cover the same register. */
22
- export const CLOSING_HEADING = /\b(?:method(?:ology)?|how)\b|\uB4B7\uBA74|\uBC29\uBC95|\uC5B4\uB5BB\uAC8C|\uB9CC\uB4E4\uC5B4/i
21
+ /**
22
+ * Closing ("how this report was made") heading; Korean alternates cover the same register. A
23
+ * heading that is exactly "Closing" (or the Korean word for it) also counts; a heading that only
24
+ * starts with it ("Closing the gap") does not.
25
+ */
26
+ export const CLOSING_HEADING = /\b(?:method(?:ology)?|how)\b|\uB4B7\uBA74|\uBC29\uBC95|\uC5B4\uB5BB\uAC8C|\uB9CC\uB4E4\uC5B4|^\s*(?:closing|\uB9C8\uBB34\uB9AC)\s*$/i
23
27
  export const SOURCES_HEADING = /references|sources|bibliography|\uCD9C\uCC98|\uCC38\uACE0\s*(?:\uBB38\uD5CC|\uC790\uB8CC)/i
24
- const OTHER_KINDS = [
28
+ /** Section kinds after sources and closing, in match order; HTML and Markdown share this table. */
29
+ export const OTHER_KINDS = Object.freeze([
25
30
  ["toc", /\b(?:contents|toc)\b|\uBAA9\uCC28/i],
26
31
  ["appendix", /appendix|\uBD80\uB85D/i],
27
32
  ["glossary", /glossary|\uC6A9\uC5B4/i],
28
- ]
33
+ ])
29
34
  const TEXT_SKIP = new Set(["code", "pre", "kbd", "samp", "script", "style", "svg", "template", "noscript"])
30
35
  const HEADING_TAG = /^h[1-6]$/
31
36
  /** The one sanctioned severity override: section-level lineage demotes unsourced_number. */
@@ -75,9 +80,25 @@ function findBody(doc) {
75
80
  return doc
76
81
  }
77
82
 
78
- function sectionKind(text, closingTest) {
83
+ /**
84
+ * Closing-heading test for both editions: CLOSING_HEADING plus the spec's optional pattern.
85
+ * @param {RegExp|string|null} [pattern]
86
+ * @returns {(text: string) => boolean}
87
+ */
88
+ export function closingTest(pattern) {
89
+ const extra = pattern ? toRegExp(pattern) : null
90
+ return (text) => CLOSING_HEADING.test(text) || Boolean(extra?.test(text))
91
+ }
92
+
93
+ /**
94
+ * Section kind of an h2 heading text: sources, closing, toc, appendix, glossary, or null.
95
+ * @param {string} text
96
+ * @param {(text: string) => boolean} isClosing
97
+ * @returns {string|null}
98
+ */
99
+ export function sectionKind(text, isClosing) {
79
100
  if (SOURCES_HEADING.test(text)) return "sources"
80
- if (closingTest(text)) return "closing"
101
+ if (isClosing(text)) return "closing"
81
102
  return OTHER_KINDS.find(([, re]) => re.test(text))?.[0] ?? null
82
103
  }
83
104
 
@@ -108,10 +129,9 @@ function walkBody(ctx, node, state) {
108
129
  }
109
130
  }
110
131
 
111
- function buildContext(doc, options) {
132
+ export function buildContext(doc, options) {
112
133
  const lineageMode = options.lineageMode ?? "inline"
113
134
  if (!LINEAGE_MODES.includes(lineageMode)) throw new TypeError(`lineageMode must be one of ${LINEAGE_MODES.join(", ")}`)
114
- const closingPattern = options.closingPattern ? toRegExp(options.closingPattern) : null
115
135
  const all = descendants(doc)
116
136
  const styleBlocks = all.filter((node) => node.tag === "style").map((node) => ({ node, css: node.raw ?? "" }))
117
137
  const styleAttrs = all.filter((node) => node.attrs.style).map((node) => ({ node, css: node.attrs.style }))
@@ -130,7 +150,7 @@ function buildContext(doc, options) {
130
150
  requireSections: (options.requireSections ?? []).map(toRegExp),
131
151
  baseDir: options.baseDir ?? null,
132
152
  fs: options.fs ?? nodeFs,
133
- isClosing: (text) => CLOSING_HEADING.test(text) || Boolean(closingPattern?.test(text)),
153
+ isClosing: closingTest(options.closingPattern),
134
154
  util: { classTokens, closest, descendants, hasCitation, citationMarker: CITATION_MARKER },
135
155
  kindOf: (scope) => (scope.closingH3 ? "closing" : (scope.section?.kind ?? null)),
136
156
  defect(code, node, message) {
@@ -9,7 +9,7 @@ import { join } from "node:path"
9
9
  import { proseText } from "./html-lite.mjs"
10
10
 
11
11
  const MIN_SECTION_PROSE = 120
12
- const UNCHECKED_KINDS = new Set(["closing", "sources", "toc", "appendix", "glossary"])
12
+ export const UNCHECKED_KINDS = new Set(["closing", "sources", "toc", "appendix", "glossary"])
13
13
  const DEFINITION_TAGS = new Set(["p", "li", "dd", "dt", "td"])
14
14
  const DEFINITION_LINE = /^\[(S|Source\s*)(\d+)\]/i
15
15
  const MIN_DEFINITIONS = 3
@@ -4,6 +4,7 @@
4
4
  // Plain Node ESM, sibling imports only.
5
5
 
6
6
  import { proseText } from "./html-lite.mjs"
7
+ import { CLAIMS, NOT_CLAIMS } from "./numerals.mjs"
7
8
 
8
9
  const HANGUL = /[\uAC00-\uD7A3]/g
9
10
  const KEEP_ALL = /word-break\s*:\s*keep-all/i
@@ -19,33 +20,6 @@ const MAX_HEADING_WORDS = 12
19
20
  const LINEAGE_TOKEN = /\b(?:MEASURED|ASSUMED|DERIVED)\b/
20
21
  const NUMBER_CONTAINERS = new Set(["figure", "table", "nav", "header", "footer"])
21
22
  const MAX_UNSOURCED = 50
22
- const MONTH = "(?:jan|feb|mar|apr|may|jun|jul|aug|sep|sept|oct|nov|dec)[a-z]*\\.?"
23
- /** Stripped before claim detection: dates, versions, figure refs, times, bare years. */
24
- const NOT_CLAIMS = [
25
- /\b\d{4}[-./]\d{1,2}(?:[-./]\d{1,2})?\b/g,
26
- /\b\d{2}\.\d{2}\.\d{2}\b/g,
27
- /\d{2,4}\s*\uB144\s*\d{1,2}\s*\uC6D4(?:\s*\d{1,2}\s*\uC77C)?/g,
28
- /\d{1,2}\s*\uC6D4(?:\s*\d{1,2}\s*\uC77C)?/g,
29
- /(?:19|20)\d{2}\s*\uB144/g,
30
- new RegExp(`\\b${MONTH}\\s+\\d{1,2}(?:st|nd|rd|th)?(?:,?\\s+\\d{4})?`, "gi"),
31
- new RegExp(`\\b\\d{1,2}(?:st|nd|rd|th)?\\s+${MONTH}(?:\\s+\\d{4})?`, "gi"),
32
- /\bv\d+(?:\.\d+)*\b|\b\d+(?:\.\d+){2,}\b/gi,
33
- /\b(?:version|ver\.?|release)\s*\d+(?:\.\d+)*/gi,
34
- /\b(?:fig(?:ure)?\.?|table|chart|section|step|phase|wave|part)\s*\d+(?:\.\d+)*/gi,
35
- /(?:\uADF8\uB9BC|\uB3C4\uD45C|\uD45C|\uBC84\uC804)\s*\d+(?:\.\d+)*/g,
36
- /\b\d{1,2}:\d{2}\b/g,
37
- /\b(?:19|20)\d{2}s?\b/g,
38
- ]
39
- const NUM = "(?<![\\p{L}\\d.,])\\d+(?:[.,]\\d+)*"
40
- const UNIT =
41
- "(?:%|\\u2030|\\u00D7|\\uD37C\\uC13C\\uD2B8|\\uB2EC\\uB7EC|\\uC720\\uB85C|\\uC5D4|\\uC6D0|\\uB9CC|\\uC5B5|\\uC870|\\uCC9C|\\uCD08|\\uBD84|\\uC2DC\\uAC04|\\uC77C|\\uC8FC|\\uAC1C\\uC6D4|\\uB144|\\uBA85|\\uAC74|\\uAC1C|\\uD68C|\\uBC88|\\uBC30" +
42
- "|(?:percent|pp|bps?|x|k|m|b|bn|million|billion|thousand|trillion|usd|krw|eur|ms|s|secs?|seconds?|mins?|minutes?|h|hrs?|hours?|days?|weeks?|months?|years?|kb|mb|gb|tb|users?|people|employees|engineers|tokens?|requests?|stars?|downloads?|customers?)(?![\\p{L}\\d]))"
43
- const CLAIMS = [
44
- new RegExp(`[$\\u20AC\\u00A3\\u00A5\\u20A9]\\s?${NUM}`, "u"),
45
- new RegExp(`${NUM}\\s?${UNIT}`, "iu"),
46
- /(?<![\d.,])\d{1,3}(?:,\d{3})+(?![\d,])/,
47
- /(?<![\d.])\d+\.\d+(?![\d.])/,
48
- ]
49
23
 
50
24
  function snippet(text, index, span = 24) {
51
25
  return text.slice(Math.max(0, index - span), index + span).trim()
@@ -54,8 +54,8 @@ function skipSpaces(source, at) {
54
54
  return at
55
55
  }
56
56
 
57
- /** Read `<name attrs...>` starting just after `<`. */
58
- function readTag(source, start) {
57
+ /** Read `<name attrs...>` starting just after `<`; the one attribute parser (any quoting). */
58
+ export function readTag(source, start) {
59
59
  const end = source.length
60
60
  let at = start
61
61
  while (at < end && !isSpace(source[at]) && source[at] !== ">" && source[at] !== "/") at++
@@ -0,0 +1,182 @@
1
+ // Byte-preserving HTML boundaries for polish. No CSS or visibility inference.
2
+ import { readTag } from "./html-lite.mjs"
3
+
4
+ const RAW = new Set(["script", "style"])
5
+ const FROZEN = new Set([...RAW, "template"])
6
+ const VOID = new Set(["area", "base", "br", "col", "embed", "hr", "img", "input", "link", "meta", "param", "source", "track", "wbr"])
7
+ const isLetter = (ch) => /[a-z]/i.test(ch ?? "")
8
+
9
+ function scan(src, at, visit) {
10
+ while (at < src.length) {
11
+ const start = src.indexOf("<", at)
12
+ if (start === -1) return
13
+ const next = src[start + 1]
14
+ let kind
15
+ let end
16
+ let tag = null
17
+ if (src.startsWith("<!--", start)) {
18
+ // From start + 2 so the abrupt closes <!--> and <!---> end where the HTML tokenizer ends them.
19
+ kind = "skip"
20
+ const stop = src.indexOf("-->", start + 2)
21
+ end = stop === -1 ? src.length : stop + 3
22
+ } else if (next === "!" || next === "?" || (next === "/" && !isLetter(src[start + 2]))) {
23
+ // Declarations, <?...> and </ + non-letter (</> included) are bogus comments up to the next ">".
24
+ kind = "skip"
25
+ const stop = src.indexOf(">", start + 2)
26
+ end = stop === -1 ? src.length : stop + 1
27
+ } else if (isLetter(next) || (next === "/" && isLetter(src[start + 2]))) {
28
+ kind = next === "/" ? "close" : "open"
29
+ tag = readTag(src, start + (kind === "close" ? 2 : 1))
30
+ end = tag.end
31
+ } else {
32
+ at = start + 1
33
+ continue
34
+ }
35
+ const resume = visit(kind, start, end, tag)
36
+ if (resume === Infinity) return
37
+ at = resume ?? (kind === "open" && RAW.has(tag.name) ? closeOf(src, end, tag.name) : end)
38
+ }
39
+ }
40
+
41
+ // Script data with its escaped and double-escaped states: a </script> inside <!--<script>...-->
42
+ // does not end the script. Returns the end of the closing tag, or the end of input.
43
+ const SCRIPT_MARKS = { data: /<!--|<\/script(?=[\s/>])/gi, escaped: /-->|<\/?script(?=[\s/>])/gi, double: /-->|<\/script(?=[\s/>])/gi }
44
+ function scriptEnd(src, from) {
45
+ let state = "data"
46
+ let at = from
47
+ for (;;) {
48
+ const marks = SCRIPT_MARKS[state]
49
+ marks.lastIndex = at
50
+ const match = marks.exec(src)
51
+ if (!match) return src.length
52
+ const token = match[0].toLowerCase()
53
+ if (token === "-->") [state, at] = ["data", match.index + 3]
54
+ else if (token === "<!--") [state, at] = ["escaped", match.index + 2]
55
+ else if (token === "<script") [state, at] = ["double", match.index + 7]
56
+ else if (state === "double") [state, at] = ["escaped", match.index + 8]
57
+ else return readTag(src, match.index + 2).end
58
+ }
59
+ }
60
+
61
+ function closeOf(src, from, name) {
62
+ if (name === "script") return scriptEnd(src, from)
63
+ if (RAW.has(name)) {
64
+ const close = new RegExp(`</${name}(?=[\\s/>])[^>]*>`, "gi")
65
+ close.lastIndex = from
66
+ const match = close.exec(src)
67
+ return match ? match.index + match[0].length : src.length
68
+ }
69
+ let depth = 0
70
+ let stop = src.length
71
+ scan(src, from, (kind, _start, end, tag) => {
72
+ if (tag?.name !== name) return undefined
73
+ if (kind === "open") depth++
74
+ else if (depth === 0) {
75
+ stop = end
76
+ return Infinity
77
+ } else depth--
78
+ return undefined
79
+ })
80
+ return stop
81
+ }
82
+
83
+ const endOf = (src, end, tag) => VOID.has(tag.name) ? end : closeOf(src, end, tag.name)
84
+
85
+ /** Raw markup sequence, corresponding text slots, and markup with frozen elements omitted. */
86
+ export function markupParts(html) {
87
+ const src = String(html)
88
+ const markup = []
89
+ const text = []
90
+ let source = ""
91
+ let kept = 0
92
+ scan(src, 0, (kind, start, end, tag) => {
93
+ const frozen = kind === "open" && FROZEN.has(tag.name)
94
+ const stop = frozen ? endOf(src, end, tag) : end
95
+ text.push(src.slice(kept, start))
96
+ markup.push(src.slice(start, stop))
97
+ source += src.slice(kept, start) + (frozen || kind === "skip" ? " " : src.slice(start, stop))
98
+ kept = stop
99
+ return stop
100
+ })
101
+ text.push(src.slice(kept))
102
+ source += src.slice(kept)
103
+ return { markup, text, source }
104
+ }
105
+
106
+ /** Whole elements accepted by the caller; nested matches stay within the outer match. */
107
+ export function elementSpans(html, accept) {
108
+ const src = String(html)
109
+ const spans = []
110
+ scan(src, 0, (kind, start, end, tag) => {
111
+ if (kind !== "open" || !accept(tag.name, tag.attrs)) return undefined
112
+ const stop = endOf(src, end, tag)
113
+ spans.push({ start, end: stop, tag })
114
+ return stop
115
+ })
116
+ return spans
117
+ }
118
+
119
+ export function tagAttributes(html) {
120
+ const tags = []
121
+ scan(String(html), 0, (kind, _start, _end, tag) => {
122
+ if (kind === "open") tags.push({ name: tag.name, attrs: tag.attrs })
123
+ return undefined
124
+ })
125
+ return tags
126
+ }
127
+
128
+ /**
129
+ * Constructs this tokenizer does not model exactly; renderer output never contains them, so polish
130
+ * refuses a document holding any of them instead of trusting the markup comparison.
131
+ */
132
+ export function ambiguousMarkup(html) {
133
+ const src = String(html)
134
+ const found = new Set()
135
+ if (/<\/(?![a-z])/i.test(src)) found.add("</ before a non-letter")
136
+ if (/<!\[CDATA\[/i.test(src)) found.add("<![CDATA[")
137
+ if (src.includes("<?")) found.add("<?")
138
+ if (src.includes("--!>")) found.add("--!>")
139
+ scan(src, 0, (kind, start, end, tag) => {
140
+ if (kind !== "skip" && src.slice(start + 1, end).includes("<")) found.add("< inside a tag or attribute value")
141
+ if (kind === "open" && RAW.has(tag.name) && src.slice(end, closeOf(src, end, tag.name)).includes("<!--")) found.add(`<!-- inside ${tag.name}`)
142
+ return undefined
143
+ })
144
+ return [...found]
145
+ }
146
+
147
+ /** One character reference token of a text node: & up to the first ; or non-name character. */
148
+ export const CHARACTER_REFERENCE = /&[^;\s<&]*;?/g
149
+
150
+ /**
151
+ * The skeleton of a document: every markup token (tags, comments, bogus comments and the frozen
152
+ * content of script/style/template) and every character reference of a text node, in document
153
+ * order, each with its byte offset. Polish may change text node characters only, and an edit can
154
+ * carry neither <, > nor & and may not touch a character reference, so an edited span holds no
155
+ * skeleton token: a polished document keeps this sequence byte-identical.
156
+ */
157
+ export function skeletonTokens(html) {
158
+ const { markup, text } = markupParts(html)
159
+ const tokens = []
160
+ let at = 0
161
+ text.forEach((slot, index) => {
162
+ for (const match of slot.matchAll(CHARACTER_REFERENCE)) tokens.push({ kind: "reference", value: match[0], offset: at + match.index })
163
+ at += slot.length
164
+ if (index < markup.length) {
165
+ tokens.push({ kind: "markup", value: markup[index], offset: at })
166
+ at += markup[index].length
167
+ }
168
+ })
169
+ return tokens
170
+ }
171
+
172
+ const describeToken = (token) => (token ? `${token.kind} ${JSON.stringify(token.value)} at offset ${token.offset}` : "end of document")
173
+
174
+ /** The first skeleton token two documents disagree on, as a message, or null when they agree. */
175
+ export function skeletonChange(beforeHtml, afterHtml) {
176
+ const [before, after] = [skeletonTokens(beforeHtml), skeletonTokens(afterHtml)]
177
+ for (let index = 0; index < Math.max(before.length, after.length); index++) {
178
+ if (before[index]?.value === after[index]?.value) continue
179
+ return `skeleton changed at token ${index}: ${describeToken(before[index])} became ${describeToken(after[index])}`
180
+ }
181
+ return null
182
+ }