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,221 @@
1
+ // fact-ledger-schema.mjs - the fact ledger's vocabulary and validation: row fields and defaults,
2
+ // kinds, statuses, qualifiers, and validateLedger over rows, status events, and their history.
3
+ // The grammar it encodes is references/fact-ledger-grammar.md; every per-kind column rule lives in
4
+ // KIND_RULES (fact-ledger-kinds.mjs), never here.
5
+ // Pure functions with no IO; fact-ledger.mjs owns reading, writing, and row changes. node: builtins only.
6
+
7
+ import { DECIMAL, isCount, isCurrentDerived, isNonEmpty, isObject, isString, kindRuleErrors, LATER_COLUMNS, readRow, storedErrors } from "./fact-ledger-kinds.mjs"
8
+ import { malformed } from "./unit-rules.mjs"
9
+
10
+ export { DECIMAL, isNonEmpty, isString } from "./fact-ledger-kinds.mjs"
11
+
12
+ export const LEDGER_SCHEMA_VERSION = 1
13
+ export const FACT_KINDS = Object.freeze(["measured", "quote", "derived", "assumed", "converted"])
14
+ export const FACT_STATUSES = Object.freeze(["active", "superseded", "retracted"])
15
+ export const OPERATIONS = Object.freeze(["sum", "diff", "growth", "ratio", "share", "convert", "expr"])
16
+ /** Canonical qualifier terms; surface forms in either language map onto these. */
17
+ export const QUALIFIERS = Object.freeze([
18
+ "upper_inclusive",
19
+ "lower_inclusive",
20
+ "approximate",
21
+ "near",
22
+ "lower_exclusive",
23
+ "upper_exclusive",
24
+ ])
25
+
26
+ export const ROW_DEFAULTS = Object.freeze({
27
+ value: null,
28
+ unit: null,
29
+ value_span: null,
30
+ source_id: null,
31
+ locator: null,
32
+ locate_hint: null,
33
+ snapshot_sha256: null,
34
+ valid_at: null,
35
+ qualifier: null,
36
+ entity: null,
37
+ sentence_id: null,
38
+ observation_id: null,
39
+ fuzzy: false,
40
+ outside_floor: false,
41
+ population: null,
42
+ missing_rule: null,
43
+ accounting_basis: null,
44
+ snapshot_window: null,
45
+ revision: null,
46
+ exact: null,
47
+ render: null,
48
+ operation: null,
49
+ operands: [],
50
+ derived_from: [],
51
+ formula: null,
52
+ factor: null,
53
+ rationale: null,
54
+ context: null,
55
+ precision: null,
56
+ compatibility: null,
57
+ status: "active",
58
+ })
59
+ /** Fields added after schema version 1 shipped: written on every new row, optional when reading an older ledger. */
60
+ const OPTIONAL_FIELDS = new Set(LATER_COLUMNS)
61
+ const REQUIRED = ["fact_id", "kind", "raw", "quote", "extracted_by", "observed_at", "label"]
62
+ export const ROW_FIELDS = Object.freeze([...REQUIRED, ...Object.keys(ROW_DEFAULTS)])
63
+
64
+ const ISO_DATE = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$/
65
+ const LABEL = /^[a-z0-9_-]{1,32}$/
66
+ const FACT_REF = /^[FQ]\d+$/
67
+
68
+ function check(errors, condition, message) {
69
+ if (!condition) errors.push(message)
70
+ }
71
+
72
+ function nullOr(value, test) {
73
+ return value === null || test(value)
74
+ }
75
+
76
+ function validateRow(row, index, errors) {
77
+ const at = `rows[${index}]`
78
+ if (!isObject(row)) return errors.push(`${at} must be an object`)
79
+ for (const key of ROW_FIELDS) check(errors, OPTIONAL_FIELDS.has(key) || Object.hasOwn(row, key), `${at}.${key} is required`)
80
+ for (const key of Object.keys(row)) check(errors, ROW_FIELDS.includes(key), `${at}.${key} is not a ledger field`)
81
+ check(errors, FACT_KINDS.includes(row.kind), `${at}.kind must be one of ${FACT_KINDS.join(", ")}`)
82
+ // Dimension 31 wins at read time: per-kind rules see the row as readRow normalizes it.
83
+ errors.push(...kindRuleErrors(readRow(row), at))
84
+ errors.push(...storedErrors(row).map((e) => `${at}.${e}`))
85
+ if (isCurrentDerived(row)) {
86
+ const problem = malformed(row)
87
+ if (problem) errors.push(`${at}: ${problem}`)
88
+ }
89
+ check(errors, row.unit === undefined || nullOr(row.unit, isNonEmpty), `${at}.unit must be null or a non-empty token`)
90
+ for (const key of ["raw", "quote"]) check(errors, row[key] === undefined || isString(row[key]), `${at}.${key} must be a string`)
91
+ check(errors, row.extracted_by === undefined || isNonEmpty(row.extracted_by), `${at}.extracted_by must be a non-empty string`)
92
+ check(errors, row.observed_at === undefined || (isString(row.observed_at) && ISO_DATE.test(row.observed_at)), `${at}.observed_at must be an ISO timestamp`)
93
+ check(errors, row.label === undefined || (isString(row.label) && LABEL.test(row.label)), `${at}.label must match [a-z0-9_-]{1,32}`)
94
+ const span = row.value_span
95
+ check(errors, span === undefined || nullOr(span, (v) => Array.isArray(v) && v.length === 2 && v.every(isCount) && v[0] <= v[1]), `${at}.value_span must be null or [start, end]`)
96
+ for (const key of ["locate_hint", "valid_at", "entity", "observation_id", "missing_rule", "accounting_basis", "formula", "render"]) {
97
+ if (row[key] !== undefined) check(errors, nullOr(row[key], isString), `${at}.${key} must be null or a string`)
98
+ }
99
+ for (const key of ["population", "snapshot_window", "revision"]) {
100
+ if (row[key] !== undefined) check(errors, nullOr(row[key], (v) => isString(v) || isObject(v)), `${at}.${key} must be null, a string, or an object`)
101
+ }
102
+ if (row.qualifier !== undefined) check(errors, nullOr(row.qualifier, (v) => QUALIFIERS.includes(v)), `${at}.qualifier must be null or one of ${QUALIFIERS.join(", ")}`)
103
+ if (row.sentence_id !== undefined) check(errors, nullOr(row.sentence_id, (v) => isString(v) && /^S\d+:s\d+$/.test(v)), `${at}.sentence_id must be null or S<n>:s<k>`)
104
+ for (const key of ["fuzzy", "outside_floor"]) {
105
+ if (row[key] !== undefined) check(errors, typeof row[key] === "boolean", `${at}.${key} must be a boolean`)
106
+ }
107
+ if (row.operation !== undefined) check(errors, nullOr(row.operation, (v) => OPERATIONS.includes(v)), `${at}.operation must be null or one of ${OPERATIONS.join(", ")}`)
108
+ for (const key of ["operands", "derived_from"]) {
109
+ if (row[key] !== undefined) check(errors, Array.isArray(row[key]) && row[key].every((v) => isString(v) && FACT_REF.test(v)), `${at}.${key} must be an array of fact ids`)
110
+ }
111
+ if (row.factor !== undefined) check(errors, nullOr(row.factor, (v) => isString(v) && DECIMAL.test(v)), `${at}.factor must be null or a decimal string`)
112
+ check(errors, row.status === undefined || FACT_STATUSES.includes(row.status), `${at}.status must be one of ${FACT_STATUSES.join(", ")}`)
113
+ }
114
+
115
+ export function rowErrors(row) {
116
+ const errors = []
117
+ validateRow(row, 0, errors)
118
+ return errors
119
+ }
120
+
121
+ // The status machine and event rule of references/fact-ledger-grammar.md. validateLedger replays
122
+ // events through transitionError, and every writer in fact-ledger.mjs calls it before appending.
123
+ export const TRANSITIONS = Object.freeze({ active: Object.freeze(["retracted", "superseded"]), retracted: Object.freeze([]), superseded: Object.freeze([]) })
124
+ const EVENT_FIELDS = Object.freeze(["fact_id", "from", "to", "replacement", "reason"])
125
+
126
+ /**
127
+ * @param {Map<string, string>} statuses each fact's status at the event's position
128
+ * @returns {string | null} why the event is not a legal transition, or null
129
+ */
130
+ export function transitionError(statuses, event) {
131
+ if (!isObject(event)) return "an event must be an object"
132
+ const id = event.fact_id
133
+ if (!statuses.has(id)) return `no ledger row ${id}`
134
+ if (EVENT_FIELDS.some((key) => !Object.hasOwn(event, key)) || Object.keys(event).some((key) => !EVENT_FIELDS.includes(key))) {
135
+ return `${id}: an event has exactly the fields ${EVENT_FIELDS.join(", ")}`
136
+ }
137
+ const current = statuses.get(id)
138
+ if (event.from !== current) return `${id} is ${current}, but the event moves it from ${event.from}`
139
+ if (!TRANSITIONS[current].includes(event.to)) return `${id} cannot move from ${current} to ${event.to}`
140
+ if (event.to === "superseded") {
141
+ if (event.replacement === null) return `supersede ${id} needs a replacement row; use retract for a fact with no replacement`
142
+ if (event.replacement === id) return `${id} cannot be its own replacement`
143
+ if (!statuses.has(event.replacement)) return `no replacement row ${event.replacement} for ${id}`
144
+ if (statuses.get(event.replacement) !== "active") return `replacement row ${event.replacement} is ${statuses.get(event.replacement)} (superseding ${id})`
145
+ } else if (event.replacement !== null) return `${id} is ${event.to} but names replacement ${event.replacement}`
146
+ if (!isNonEmpty(event.reason)) return `a reason is required to mark ${id} ${event.to}`
147
+ return null
148
+ }
149
+
150
+ /** Replay every event from all-active; each row's status must be where its event chain leaves it. */
151
+ function validateHistory(ledger, errors) {
152
+ const statuses = new Map(ledger.rows.filter(isObject).map((row) => [row.fact_id, "active"]))
153
+ const counts = new Map()
154
+ ledger.events.forEach((event, index) => {
155
+ const problem = transitionError(statuses, event)
156
+ if (problem) errors.push(`events[${index}]: ${problem}`)
157
+ if (!isObject(event) || !statuses.has(event.fact_id) || !FACT_STATUSES.includes(event.to)) return
158
+ statuses.set(event.fact_id, event.to)
159
+ counts.set(event.fact_id, (counts.get(event.fact_id) ?? 0) + 1)
160
+ })
161
+ ledger.rows.forEach((row, index) => {
162
+ if (!isObject(row) || !FACT_STATUSES.includes(row.status)) return
163
+ const to = statuses.get(row.fact_id)
164
+ if (!counts.has(row.fact_id)) check(errors, row.status === "active", `rows[${index}]: ${row.fact_id} is ${row.status} but has no status event`)
165
+ else check(errors, to === row.status, `rows[${index}]: ${row.fact_id} has status ${row.status} but its last event moves it to ${to}`)
166
+ })
167
+ }
168
+
169
+ /** operands and derived_from name existing rows, never the row itself, and close no cycle. */
170
+ export function provenanceErrors(rows) {
171
+ const errors = []
172
+ const refs = new Map()
173
+ for (const row of rows) if (isObject(row)) refs.set(row.fact_id, [row.operands, row.derived_from].flatMap((list) => (Array.isArray(list) ? list : [])))
174
+ rows.forEach((row, index) => {
175
+ for (const key of ["operands", "derived_from"]) {
176
+ if (!isObject(row) || !Array.isArray(row[key])) continue
177
+ for (const ref of row[key]) {
178
+ if (ref === row.fact_id) errors.push(`rows[${index}].${key}: ${ref} references itself`)
179
+ else check(errors, refs.has(ref), `rows[${index}].${key}: ${row.fact_id} references ${ref}, which has no ledger row`)
180
+ }
181
+ }
182
+ })
183
+ const state = new Map()
184
+ for (const root of refs.keys()) {
185
+ const stack = [{ id: root, finish: false }]
186
+ const path = []
187
+ while (stack.length) {
188
+ const { id, finish } = stack.pop()
189
+ if (state.get(id) === "done") continue
190
+ if (finish) { state.set(id, "done"); path.pop(); continue }
191
+ if (state.get(id) === "open") return [...errors, `${id} derives from itself through cycle ${[...path, id].join(" -> ")}`]
192
+ state.set(id, "open")
193
+ path.push(id)
194
+ stack.push({ id, finish: true })
195
+ for (const next of refs.get(id)) if (next !== id && refs.has(next)) stack.push({ id: next, finish: false })
196
+ }
197
+ }
198
+ return errors
199
+ }
200
+
201
+ /** @returns {string[]} every schema error; empty when the ledger is valid. */
202
+ export function validateLedger(ledger) {
203
+ const errors = []
204
+ if (!isObject(ledger)) return ["ledger must be an object"]
205
+ check(errors, ledger.schemaVersion === LEDGER_SCHEMA_VERSION, `schemaVersion must be ${LEDGER_SCHEMA_VERSION}`)
206
+ if (!Array.isArray(ledger.rows)) return [...errors, "rows must be an array"]
207
+ ledger.rows.forEach((row, index) => validateRow(row, index, errors))
208
+ const ids = new Set()
209
+ const labels = new Set()
210
+ ledger.rows.forEach((row, index) => {
211
+ if (!isObject(row)) return
212
+ check(errors, !ids.has(row.fact_id), `rows[${index}].fact_id ${row.fact_id} is a duplicate`)
213
+ check(errors, row.label === undefined || !labels.has(row.label), `rows[${index}]: ${row.fact_id} has duplicate label ${row.label}`)
214
+ ids.add(row.fact_id)
215
+ labels.add(row.label)
216
+ })
217
+ errors.push(...provenanceErrors(ledger.rows))
218
+ if (Array.isArray(ledger.events)) validateHistory(ledger, errors)
219
+ else errors.push("events must be an array")
220
+ return errors
221
+ }
@@ -0,0 +1,123 @@
1
+ // fact-ledger.mjs - the verbatim fact ledger: one row per measured, quoted, derived, assumed,
2
+ // or converted value, with its source locator and exact rational. Rows are never deleted:
3
+ // retract and supersede change status and append an event. Validation and the field vocabulary
4
+ // live in fact-ledger-schema.mjs and are re-exported here. node: builtins only.
5
+
6
+ import { existsSync } from "node:fs"
7
+ import { join } from "node:path"
8
+
9
+ import { CliError, readJsonFile, writeJsonAtomic } from "./cli-support.mjs"
10
+ import { DECIMAL, exactComesFromValue, exactFromDecimal, idPrefix, isString, readFlaws, readRow } from "./fact-ledger-kinds.mjs"
11
+ import { LEDGER_SCHEMA_VERSION, ROW_DEFAULTS, ROW_FIELDS, transitionError, validateLedger } from "./fact-ledger-schema.mjs"
12
+ import { normalizeInput } from "./unit-algebra.mjs"
13
+
14
+ export { exactFromDecimal } from "./fact-ledger-kinds.mjs"
15
+ export { FACT_KINDS, FACT_STATUSES, LEDGER_SCHEMA_VERSION, OPERATIONS, QUALIFIERS, ROW_FIELDS, TRANSITIONS, transitionError, validateLedger } from "./fact-ledger-schema.mjs"
16
+
17
+ export const LEDGER_FILENAME = "fact-ledger.json"
18
+
19
+ export function emptyLedger() {
20
+ return { schemaVersion: LEDGER_SCHEMA_VERSION, rows: [], events: [] }
21
+ }
22
+
23
+ function assertValid(ledger) {
24
+ const errors = validateLedger(ledger)
25
+ if (errors.length > 0) throw new CliError(`invalid fact ledger: ${errors.join("; ")}`)
26
+ }
27
+
28
+ /** The row's fields in ROW_FIELDS order, then any unknown field (which validation reports). */
29
+ function ordered(row) {
30
+ const out = Object.fromEntries(ROW_FIELDS.filter((key) => Object.hasOwn(row, key)).map((key) => [key, row[key]]))
31
+ for (const key of Object.keys(row)) if (!ROW_FIELDS.includes(key)) out[key] = row[key]
32
+ return out
33
+ }
34
+
35
+ /**
36
+ * Every row comes back as readRow normalizes it (reduced exacts and decimals; source rows with canonical
37
+ * units and null later columns) before any strict rule runs; the file changes on the next write.
38
+ */
39
+ export function readLedger(dir) {
40
+ const file = join(dir, LEDGER_FILENAME)
41
+ if (!existsSync(file)) return emptyLedger()
42
+ const ledger = readJsonFile(file)
43
+ assertValid(ledger)
44
+ ledger.rows = ledger.rows.map((row) => ordered(readRow(row)))
45
+ assertValid(ledger)
46
+ return ledger
47
+ }
48
+
49
+ export function writeLedger(dir, ledger) {
50
+ assertValid(ledger)
51
+ const file = join(dir, LEDGER_FILENAME)
52
+ writeJsonAtomic(file, ledger)
53
+ return file
54
+ }
55
+
56
+ const ID_NUMBER = Object.freeze({ F: /^F(\d+)$/, Q: /^Q(\d+)$/ })
57
+
58
+ /** The next id with the kind's prefix from KIND_RULES (F<n> for value rows, Q<n> for quotes). */
59
+ export function nextId(ledger, kind) {
60
+ const prefix = idPrefix(kind)
61
+ let max = 0n
62
+ for (const row of ledger.rows) {
63
+ const match = ID_NUMBER[prefix].exec(row.fact_id ?? "")
64
+ if (match && BigInt(match[1]) > max) max = BigInt(match[1])
65
+ }
66
+ return `${prefix}${max + 1n}`
67
+ }
68
+
69
+ /**
70
+ * Fill defaults, assign the next id, validate the whole ledger (provenance included), then append.
71
+ * Only kinds whose KIND_RULES exact is from_value (measured) get exact from their decimal:
72
+ * derived and converted rows carry the exact that derive computed from the operands and factor,
73
+ * never one read back from the rounded value.
74
+ */
75
+ export function addRow(ledger, row) {
76
+ const filled = { ...structuredClone(ROW_DEFAULTS), ...row, fact_id: row.fact_id ?? nextId(ledger, row.kind) }
77
+ if (!["derived", "converted"].includes(filled.kind)) {
78
+ const normalized = normalizeInput(filled.unit, filled.value)
79
+ if (normalized.value !== filled.value && filled.exact !== null) throw new CliError("scaled input must omit exact; it is computed after normalization")
80
+ Object.assign(filled, readRow({ ...filled, ...normalized }))
81
+ }
82
+ if (exactComesFromValue(filled.kind) && filled.exact === null && isString(filled.value) && DECIMAL.test(filled.value)) {
83
+ filled.exact = exactFromDecimal(filled.value)
84
+ }
85
+ const added = ordered(filled)
86
+ const flaws = readFlaws(added)
87
+ if (flaws.length > 0) throw new CliError(`invalid fact ledger: ${flaws.map((flaw) => `${flaw.shape}: ${flaw.detail}`).join("; ")}`)
88
+ assertValid({ ...ledger, rows: [...ledger.rows, added] })
89
+ ledger.rows.push(added)
90
+ return added
91
+ }
92
+
93
+ /** Build the event from the row's current status and append it only if transitionError allows it. */
94
+ function changeStatus(ledger, id, to, replacement, reason) {
95
+ const row = ledger.rows.find((candidate) => candidate.fact_id === id)
96
+ const event = { fact_id: id, from: row?.status ?? null, to, replacement: replacement ?? null, reason }
97
+ const problem = transitionError(new Map(ledger.rows.map((candidate) => [candidate.fact_id, candidate.status])), event)
98
+ if (problem) throw new CliError(problem)
99
+ ledger.events.push(event)
100
+ row.status = to
101
+ return row
102
+ }
103
+
104
+ export function retractRow(ledger, id, reason) {
105
+ return changeStatus(ledger, id, "retracted", null, reason)
106
+ }
107
+
108
+ export function supersedeRow(ledger, id, replacementId, reason) {
109
+ return changeStatus(ledger, id, "superseded", replacementId, reason)
110
+ }
111
+
112
+ function cell(value) {
113
+ return value === null || value === undefined ? "" : String(value).replace(/\|/g, "\\|").replace(/\n/g, " ")
114
+ }
115
+
116
+ /** The `--view` table: one line per row, retracted and superseded rows included. */
117
+ export function renderLedgerMarkdown(ledger) {
118
+ const lines = ["| fact_id | kind | label | value | unit | source | status | raw |", "|---|---|---|---|---|---|---|---|"]
119
+ for (const row of ledger.rows) {
120
+ lines.push(`| ${[row.fact_id, row.kind, row.label, row.value, row.unit, row.source_id, row.status, row.raw].map(cell).join(" | ")} |`)
121
+ }
122
+ return `${lines.join("\n")}\n`
123
+ }
@@ -0,0 +1,231 @@
1
+ // fact-locate.mjs - deterministic fact locators over a source snapshot.
2
+ //
3
+ // Each locator returns { value, unit, raw, quote, value_span, sentence_id, qualifier, locator } or
4
+ // { error: "fact_not_located" | "fact_ambiguous", candidates }; it never guesses. `quote` is the
5
+ // whole containing sentence, cell, or line of text.txt, `value_span` is [start, end] inside
6
+ // `quote` with quote.slice(...value_span) === raw, and locator.char_start/char_end place `quote`
7
+ // in text.txt. Every call takes `sourceId` (S<n>) so sentence ids match sentences.jsonl.
8
+ // Plain Node ESM, sibling imports only; no network, no OCR.
9
+
10
+ import { proseText } from "./html-lite.mjs"
11
+ import { QUALIFIERS } from "./lexicon.mjs"
12
+ import { parseNumeral } from "./numerals.mjs"
13
+ import { indexSentences, snapshotBlocks } from "./source-snapshot.mjs"
14
+ import { findNormalized, normalizeText, tokenize } from "./text-normalize.mjs"
15
+ import { similarity } from "./text-similarity.mjs"
16
+ import { normalizeUnit } from "./units.mjs"
17
+
18
+ const FIND_WINDOW = 80
19
+ const CONTEXT = 60
20
+ const FUZZY_FLOOR = 0.95
21
+ const PHRASES = QUALIFIERS.map((phrase) => phrase.split(" "))
22
+ const SINGLE_QUALIFIERS = new Set(QUALIFIERS.filter((phrase) => !phrase.includes(" ")))
23
+
24
+ const notLocated = (candidates = []) => ({ error: "fact_not_located", candidates })
25
+ const ambiguous = (candidates) => ({ error: "fact_ambiguous", candidates })
26
+ const label = (text) => normalizeText(text).toLowerCase()
27
+ const clean = (token) => token.replace(/^[^\p{L}\p{N}%]+|[^\p{L}\p{N}%]+$/gu, "").toLowerCase()
28
+ const sentenceAt = (sentences, offset) => sentences.find((row) => row.start <= offset && offset < row.end) ?? null
29
+
30
+ function requireSource(sourceId) {
31
+ if (!/^S[1-9]\d*$/.test(String(sourceId))) throw new TypeError(`sourceId must be S<n>, got ${sourceId}`)
32
+ }
33
+
34
+ /** Every normalized occurrence of `needle`, as raw [start, end) offsets. */
35
+ function findAll(text, needle) {
36
+ const hits = []
37
+ for (let from = 0; from < text.length; ) {
38
+ const hit = findNormalized(text.slice(from), needle)
39
+ if (!hit) break
40
+ hits.push({ start: from + hit.start, end: from + hit.end })
41
+ from += hit.start + 1
42
+ }
43
+ return hits
44
+ }
45
+
46
+ function locatorFor(text, sourceId, start, end, extra = {}) {
47
+ const before = text.slice(0, start)
48
+ const pageStart = before.lastIndexOf("\f") + 1
49
+ const page = text.includes("\f") ? { page: before.split("\f").length } : {}
50
+ return { artifact: `${sourceId}/text.txt`, ...page, line: before.slice(pageStart).split("\n").length, char_start: start, char_end: end, ...extra }
51
+ }
52
+
53
+ function candidate(text, sentences, start, end) {
54
+ return { sentence_id: sentenceAt(sentences, start)?.sid ?? null, char_start: start, char_end: end, context: text.slice(Math.max(0, start - CONTEXT), end + CONTEXT) }
55
+ }
56
+
57
+ /** A QUALIFIERS token ending within the 2 tokens before the value, or the 1 token right after it. */
58
+ function qualifierAround(quote, [start, end]) {
59
+ const before = quote.slice(0, start).split(/\s+/).filter(Boolean).map(clean)
60
+ for (let skip = 0; skip < 2; skip++) {
61
+ for (const words of PHRASES) {
62
+ const stop = before.length - skip
63
+ if (stop - words.length >= 0 && before.slice(stop - words.length, stop).join(" ") === words.join(" ")) return words.join(" ")
64
+ }
65
+ }
66
+ const next = /^\s*(\S+)/.exec(quote.slice(end))
67
+ return next && SINGLE_QUALIFIERS.has(clean(next[1])) ? clean(next[1]) : null
68
+ }
69
+
70
+ /** A located quantity: `holder` is the quote's { sid, start, end, text }, `numeral` is parsed from holder.text. */
71
+ function quantityResult(text, sourceId, holder, numeral, extra = {}, unitFallback = null) {
72
+ const unit = numeral.unitRaw ? (normalizeUnit(numeral.unitRaw) ?? numeral.unitRaw) : unitFallback
73
+ return {
74
+ value: numeral.value,
75
+ unit,
76
+ raw: numeral.raw,
77
+ quote: holder.text,
78
+ value_span: [numeral.span[0], numeral.span[1]],
79
+ sentence_id: holder.sid,
80
+ qualifier: qualifierAround(holder.text, numeral.span),
81
+ locator: locatorFor(text, sourceId, holder.start, holder.end, extra),
82
+ }
83
+ }
84
+
85
+ /** --find: one anchor occurrence, then the first numeral starting within 80 chars after it in the same sentence. */
86
+ export function locateFind(text, { find, unitExpected, sourceId }) {
87
+ requireSource(sourceId)
88
+ const sentences = indexSentences(text, sourceId)
89
+ const hits = findAll(text, find)
90
+ if (hits.length === 0) return notLocated()
91
+ if (hits.length > 1) return ambiguous(hits.map((hit) => candidate(text, sentences, hit.start, hit.end)))
92
+ const [hit] = hits
93
+ const sentence = sentenceAt(sentences, hit.start)
94
+ if (!sentence || hit.end > sentence.end) return notLocated()
95
+ const from = hit.end - sentence.start
96
+ const numeral = parseNumeral(sentence.text, from)
97
+ if (!numeral || numeral.span[0] - from > FIND_WINDOW) return notLocated([candidate(text, sentences, hit.start, hit.end)])
98
+ const result = quantityResult(text, sourceId, sentence, numeral)
99
+ if (unitExpected === undefined) return result
100
+ const expected = normalizeUnit(unitExpected) ?? unitExpected
101
+ return result.unit === expected ? result : { error: "unit_expected_mismatch", expected, found: result.unit, candidates: [result] }
102
+ }
103
+
104
+ function cellsOf(row) {
105
+ const cells = []
106
+ for (const cell of row.children.filter((child) => child.tag === "td" || child.tag === "th")) {
107
+ const span = Math.max(1, Number.parseInt(cell.attrs.colspan ?? "1", 10) || 1)
108
+ for (let k = 0; k < span; k++) cells.push(cell)
109
+ }
110
+ return cells
111
+ }
112
+
113
+ function rowsOf(table) {
114
+ const rows = []
115
+ const walk = (node) => {
116
+ for (const child of node.children) {
117
+ if (child.tag === "tr") rows.push(child)
118
+ else if (child.tag !== "table" && child.tag !== "#text") walk(child)
119
+ }
120
+ }
121
+ walk(table)
122
+ return rows
123
+ }
124
+
125
+ function tablesOf(root) {
126
+ const tables = []
127
+ const walk = (node) => {
128
+ for (const child of node.children) {
129
+ if (child.tag === "table") tables.push(child)
130
+ if (child.tag !== "#text") walk(child)
131
+ }
132
+ }
133
+ walk(root)
134
+ return tables.map((table) => {
135
+ const rows = rowsOf(table)
136
+ const header = rows.find((row) => row.children.some((child) => child.tag === "th")) ?? rows[0]
137
+ const caption = table.children.find((child) => child.tag === "caption")
138
+ return { table, header, rows: rows.filter((row) => row !== header), caption: caption ? label(proseText(caption)) : null }
139
+ })
140
+ }
141
+
142
+ const cellLabel = (cell) => label(proseText(cell))
143
+ const indexesOf = (labels, wanted) => labels.flatMap((value, index) => (value === wanted ? [index] : []))
144
+
145
+ /** --table/--row/--col: the table by id, caption, or a header label; row and column by normalized label, never by index. */
146
+ export function locateTable(sourceHtml, { table, row, col, sourceId }) {
147
+ requireSource(sourceId)
148
+ const { root, text, blocks } = snapshotBlocks(sourceHtml)
149
+ const key = label(table)
150
+ const tables = tablesOf(root).filter((entry) => entry.table.attrs.id === table || entry.caption === key || (entry.header && cellsOf(entry.header).map(cellLabel).includes(key)))
151
+ if (tables.length !== 1) return tables.length === 0 ? notLocated() : ambiguous(tables.map((entry) => ({ table: entry.table.attrs.id ?? entry.caption })))
152
+ const [{ header, rows }] = tables
153
+ const headerCells = header ? cellsOf(header) : []
154
+ const columns = indexesOf(headerCells.map(cellLabel), label(col))
155
+ const matches = rows.filter((candidateRow) => cellsOf(candidateRow).length > 0 && cellLabel(cellsOf(candidateRow)[0]) === label(row))
156
+ if (columns.length > 1 || matches.length > 1) return ambiguous([...columns.map((index) => ({ col: index })), ...matches.map((match) => ({ row_line: match.line }))])
157
+ const cell = columns.length === 1 && matches.length === 1 ? cellsOf(matches[0])[columns[0]] : null
158
+ const block = cell ? blocks.find((entry) => entry.node === cell) : null
159
+ if (!block) return notLocated()
160
+ const holder = { sid: sentenceAt(indexSentences(text, sourceId), block.start)?.sid ?? null, start: block.start, end: block.end, text: text.slice(block.start, block.end) }
161
+ const numeral = parseNumeral(holder.text)
162
+ if (!numeral) return notLocated()
163
+ const headerUnit = /\(([^()]+)\)\s*$/.exec(cellLabel(headerCells[columns[0]]))
164
+ return quantityResult(text, sourceId, holder, numeral, { table, row_label: row, col_label: col }, headerUnit ? normalizeUnit(headerUnit[1]) : null)
165
+ }
166
+
167
+ /** --page/--line: exactly one quantity on that line of that page (pages split on a form feed). */
168
+ export function locatePageLine(text, { page, line, sourceId }) {
169
+ requireSource(sourceId)
170
+ const pages = text.split("\f")
171
+ if (!Number.isInteger(page) || page < 1 || page > pages.length) return notLocated()
172
+ const lines = pages[page - 1].split("\n")
173
+ if (!Number.isInteger(line) || line < 1 || line > lines.length) return notLocated()
174
+ let start = pages.slice(0, page - 1).reduce((sum, body) => sum + body.length + 1, 0)
175
+ for (const body of lines.slice(0, line - 1)) start += body.length + 1
176
+ const holder = { start, end: start + lines[line - 1].length, text: lines[line - 1] }
177
+ const numerals = []
178
+ for (let numeral = parseNumeral(holder.text); numeral; numeral = parseNumeral(holder.text, numeral.span[1])) numerals.push(numeral)
179
+ const sentences = indexSentences(text, sourceId)
180
+ if (numerals.length !== 1) return numerals.length === 0 ? notLocated() : ambiguous(numerals.map((numeral) => candidate(text, sentences, start + numeral.span[0], start + numeral.span[1])))
181
+ holder.sid = sentenceAt(sentences, start + numerals[0].span[0])?.sid ?? null
182
+ return quantityResult(text, sourceId, holder, numerals[0])
183
+ }
184
+
185
+ /** Token-aligned windows of `sentence` whose similarity to `match` reaches the fuzzy floor; best first. */
186
+ function fuzzyWindow(sentence, match) {
187
+ const target = normalizeText(match).length
188
+ const wanted = new Set(tokenize(match))
189
+ const present = new Set(tokenize(sentence.text))
190
+ if (wanted.size === 0 || [...wanted].filter((token) => present.has(token)).length / wanted.size < 0.5) return null
191
+ const bounds = [...sentence.text.matchAll(/[\p{L}\p{N}]+/gu)].map((m) => [m.index, m.index + m[0].length])
192
+ let best = null
193
+ for (let i = 0; i < bounds.length; i++) {
194
+ for (let j = i; j < bounds.length; j++) {
195
+ const window = sentence.text.slice(bounds[i][0], bounds[j][1])
196
+ if (window.length > target * 1.06 + 2) break
197
+ if (window.length < target * 0.94 - 2) continue
198
+ const score = similarity(window, match)
199
+ if (score >= FUZZY_FLOOR && (!best || score > best.score)) best = { score, start: sentence.start + bounds[i][0], end: sentence.start + bounds[j][1] }
200
+ }
201
+ }
202
+ return best
203
+ }
204
+
205
+ function quoteResult(text, sourceId, sentences, start, end, fuzzy) {
206
+ const first = sentenceAt(sentences, start)
207
+ const last = sentenceAt(sentences, end - 1) ?? first
208
+ return {
209
+ value: null,
210
+ unit: null,
211
+ raw: text.slice(start, end),
212
+ quote: text.slice(first.start, last.end),
213
+ value_span: null,
214
+ sentence_id: first.sid,
215
+ qualifier: null,
216
+ fuzzy,
217
+ locator: locatorFor(text, sourceId, first.start, last.end),
218
+ }
219
+ }
220
+
221
+ /** --match: a normalized exact match (fuzzy false), else the one window with similarity >= 0.95 (fuzzy true). */
222
+ export function locateMatch(text, { match, sourceId }) {
223
+ requireSource(sourceId)
224
+ const sentences = indexSentences(text, sourceId)
225
+ const hits = findAll(text, match)
226
+ if (hits.length > 1) return ambiguous(hits.map((hit) => candidate(text, sentences, hit.start, hit.end)))
227
+ if (hits.length === 1) return quoteResult(text, sourceId, sentences, hits[0].start, hits[0].end, false)
228
+ const windows = sentences.map((sentence) => fuzzyWindow(sentence, match)).filter(Boolean)
229
+ if (windows.length > 1) return ambiguous(windows.map((window) => candidate(text, sentences, window.start, window.end)))
230
+ return windows.length === 1 ? quoteResult(text, sourceId, sentences, windows[0].start, windows[0].end, true) : notLocated()
231
+ }