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,176 @@
1
+ // source-snapshot.mjs - the per-source snapshot that fact locators read.
2
+ //
3
+ // snapshotFromHtml turns a page into text.txt form: one line per block (prose blocks via
4
+ // proseText, one table cell per line; head, scripts, styles and code are dropped). writeSnapshot
5
+ // writes <sourcesDir>/<S-id>/ with text.txt (pages joined by a form feed), source.html when html
6
+ // was given, meta.json, sentences.jsonl (sid S<n>:s<k>) and acronyms.json. The sha256 covers
7
+ // text.txt only. Nothing here touches the network: the caller supplies the html or text.
8
+ // Plain Node ESM, node: builtins and sibling imports only.
9
+
10
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs"
11
+ import { join } from "node:path"
12
+
13
+ import { sha256Of } from "./cli-support.mjs"
14
+ import { parseHtml, PROSE_TAGS, proseText } from "./html-lite.mjs"
15
+ import { splitSentences } from "./sentence-split.mjs"
16
+
17
+ const SKIP = new Set(["head", "script", "style", "svg", "code", "pre", "kbd", "samp", "template", "noscript"])
18
+ const INLINE = new Set([
19
+ "a", "abbr", "b", "bdi", "bdo", "cite", "data", "del", "dfn", "em", "font", "i", "ins", "label", "mark", "q", "s",
20
+ "small", "span", "strong", "sub", "sup", "time", "u", "var",
21
+ ])
22
+ const SOURCE_ID = /^S[1-9]\d*$/
23
+ const EXTRACTOR = /^(?:tool:source-snapshot@1|model|extractor:[\w.@+-]+)$/
24
+ const KINDS = new Set(["page", "command", "document"])
25
+ const PROVENANCE = new Set(["primary", "secondary", "unknown"])
26
+ const ACRONYM_AFTER = /\(([A-Z]{2,6})\)/g
27
+ const ACRONYM_BEFORE = /(?<![\p{L}\p{N}])([A-Z]{2,6})\s?\(([A-Z][^()\n]{2,79})\)/gu
28
+ const NAME_WORD = /^[A-Z][\p{L}\p{N}-]*$/u
29
+ const CONNECTOR = new Set(["of", "and", "for", "the", "in", "on", "to", "&"])
30
+
31
+ /**
32
+ * Block lines of a page: { root, text, blocks: [{ node, start, end }] } where text joins the block
33
+ * texts with "\n" and text.slice(start, end) is one block. fact-locate reads cells through `node`.
34
+ */
35
+ export function snapshotBlocks(html) {
36
+ const root = parseHtml(html)
37
+ const lines = []
38
+ let buffer = ""
39
+ const flush = (node) => {
40
+ const line = buffer.replace(/\s+/g, " ").trim()
41
+ buffer = ""
42
+ if (line) lines.push({ node, text: line })
43
+ }
44
+ const walk = (node) => {
45
+ for (const child of node.children) {
46
+ if (child.tag === "#text") buffer += child.text
47
+ else if (SKIP.has(child.tag)) continue
48
+ else if (child.tag === "br") flush(node)
49
+ else if (INLINE.has(child.tag)) walk(child)
50
+ else if (PROSE_TAGS.has(child.tag)) {
51
+ flush(node)
52
+ const line = proseText(child)
53
+ if (line) lines.push({ node: child, text: line })
54
+ } else {
55
+ flush(node)
56
+ walk(child)
57
+ flush(child)
58
+ }
59
+ }
60
+ }
61
+ walk(root)
62
+ flush(root)
63
+ const blocks = []
64
+ let at = 0
65
+ for (const line of lines) {
66
+ blocks.push({ node: line.node, start: at, end: at + line.text.length })
67
+ at += line.text.length + 1
68
+ }
69
+ return { root, text: lines.map((line) => line.text).join("\n"), blocks }
70
+ }
71
+
72
+ /** text.txt form of an html page. */
73
+ export function snapshotFromHtml(html) {
74
+ return snapshotBlocks(html).text
75
+ }
76
+
77
+ /** Sentence index rows { sid: "S<n>:s<k>", start, end, text }; a form feed (page break) always ends a sentence. */
78
+ export function indexSentences(text, sourceId) {
79
+ return splitSentences(text.replace(/\f/g, "\n")).map((row) => ({
80
+ sid: `${sourceId}:${row.sid}`,
81
+ start: row.start,
82
+ end: row.end,
83
+ text: text.slice(row.start, row.end),
84
+ }))
85
+ }
86
+
87
+ const sentenceIdAt = (sentences, offset) => sentences.find((row) => row.start <= offset && offset < row.end)?.sid ?? null
88
+
89
+ /** "High Bandwidth Memory (HBM)": the capitalized words before the parenthesis, at most one per letter. */
90
+ function nameBefore(text, at, letters) {
91
+ const prefix = text.slice(Math.max(0, at - 160), at)
92
+ if (!/\S[ \t]?$/.test(prefix)) return null
93
+ const words = [...prefix.matchAll(/\S+/g)]
94
+ const picked = []
95
+ let content = 0
96
+ for (let k = words.length - 1; k >= 0 && content < letters; k--) {
97
+ const word = words[k][0]
98
+ if (NAME_WORD.test(word)) content++
99
+ else if (!CONNECTOR.has(word) || picked.length === 0) break
100
+ picked.unshift(words[k])
101
+ }
102
+ while (picked.length > 0 && CONNECTOR.has(picked[0][0])) picked.shift()
103
+ if (picked.length === 0) return null
104
+ const start = at - prefix.length + picked[0].index
105
+ const end = at - prefix.length + picked[picked.length - 1].index + picked[picked.length - 1][0].length
106
+ return { start, expansion: text.slice(start, end) }
107
+ }
108
+
109
+ /** Every `Full Name (ACR)` and `ACR (Full Name)` in the text, ordered by position. */
110
+ export function findAcronyms(text, sentences) {
111
+ const entries = []
112
+ for (const match of text.matchAll(ACRONYM_AFTER)) {
113
+ const name = nameBefore(text, match.index, match[1].length)
114
+ if (!name || name.expansion === match[1]) continue
115
+ entries.push({ acronym: match[1], expansion: name.expansion, span: [name.start, match.index + match[0].length] })
116
+ }
117
+ for (const match of text.matchAll(ACRONYM_BEFORE)) {
118
+ const expansion = match[2].trim()
119
+ if (!/\s/.test(expansion) || expansion === expansion.toUpperCase()) continue
120
+ entries.push({ acronym: match[1], expansion, span: [match.index, match.index + match[0].length] })
121
+ }
122
+ return entries
123
+ .sort((a, b) => a.span[0] - b.span[0])
124
+ .map((entry) => ({ acronym: entry.acronym, expansion: entry.expansion, sentence_id: sentenceIdAt(sentences, entry.span[0]), span: entry.span }))
125
+ }
126
+
127
+ function snapshotText(input) {
128
+ if (Array.isArray(input.pages)) return input.pages.join("\f")
129
+ if (typeof input.text === "string") return input.text
130
+ if (typeof input.html === "string") return snapshotFromHtml(input.html)
131
+ throw new TypeError("writeSnapshot needs text, pages, or html")
132
+ }
133
+
134
+ function checkInput(sourceId, input) {
135
+ if (!SOURCE_ID.test(String(sourceId))) throw new TypeError(`source id must be S<n>, got ${sourceId}`)
136
+ if (!EXTRACTOR.test(String(input.extractor))) throw new TypeError(`extractor must be tool:source-snapshot@1, model, or extractor:<name>, got ${input.extractor}`)
137
+ if (typeof (input.url ?? input.path) !== "string") throw new TypeError("writeSnapshot needs a url or a path")
138
+ if (input.kind !== undefined && !KINDS.has(input.kind)) throw new TypeError(`kind must be page, command, or document, got ${input.kind}`)
139
+ if (input.provenance !== undefined && !PROVENANCE.has(input.provenance)) throw new TypeError(`provenance must be primary, secondary, or unknown, got ${input.provenance}`)
140
+ }
141
+
142
+ /**
143
+ * Write <sourcesDir>/<sourceId>/ for one source; returns { dir, sha256 } with sha256 over text.txt.
144
+ * input: { url | path, text | pages | html, extractor, observed_at?, revision?, kind?, provenance? }.
145
+ * Re-writing the same text is a no-op change; a different text over an existing snapshot throws
146
+ * snapshot_exists, because ledger rows pin the old hash.
147
+ */
148
+ export function writeSnapshot(sourcesDir, sourceId, input) {
149
+ checkInput(sourceId, input)
150
+ const text = snapshotText(input)
151
+ const sha256 = sha256Of(Buffer.from(text, "utf8"))
152
+ const dir = join(sourcesDir, sourceId)
153
+ const textPath = join(dir, "text.txt")
154
+ if (existsSync(textPath) && sha256Of(readFileSync(textPath)) !== sha256) {
155
+ throw new Error(`snapshot_exists: ${sourceId} already holds a different text.txt`)
156
+ }
157
+ mkdirSync(dir, { recursive: true })
158
+ writeFileSync(textPath, text)
159
+ if (typeof input.html === "string") writeFileSync(join(dir, "source.html"), input.html)
160
+ const meta = {
161
+ source_id: sourceId,
162
+ url: input.url ?? input.path,
163
+ observed_at: input.observed_at ?? new Date().toISOString(),
164
+ sha256,
165
+ extractor: input.extractor,
166
+ revision: input.revision ?? null,
167
+ kind: input.kind ?? (input.url !== undefined || typeof input.html === "string" ? "page" : "document"),
168
+ provenance: input.provenance ?? "unknown",
169
+ }
170
+ writeFileSync(join(dir, "meta.json"), `${JSON.stringify(meta, null, 2)}\n`)
171
+ const sentences = indexSentences(text, sourceId)
172
+ writeFileSync(join(dir, "sentences.jsonl"), sentences.map((row) => `${JSON.stringify(row)}\n`).join(""))
173
+ const acronyms = { schemaVersion: 1, source_id: sourceId, snapshot_sha256: sha256, entries: findAcronyms(text, sentences) }
174
+ writeFileSync(join(dir, "acronyms.json"), `${JSON.stringify(acronyms, null, 2)}\n`)
175
+ return { dir, sha256 }
176
+ }
@@ -0,0 +1,82 @@
1
+ // text-normalize.mjs - text normalization and raw-offset matching for fidelity checks.
2
+ //
3
+ // normalizeText applies NFC, maps curly and CJK quote glyphs to straight quotes, collapses
4
+ // whitespace runs to one space, and trims. findNormalized matches a needle against the
5
+ // normalized haystack and returns offsets into the RAW haystack through an offset map built
6
+ // during normalization. Plain Node ESM, no dependencies.
7
+
8
+ const DOUBLE_QUOTES = new Set([
9
+ "\u201C", "\u201D", "\u201E", "\u201F", "\u00AB", "\u00BB", "\u300C", "\u300D", "\u300E", "\u300F",
10
+ "\u301D", "\u301E", "\u301F", "\uFF02", "\u2033",
11
+ ])
12
+ const SINGLE_QUOTES = new Set(["\u2018", "\u2019", "\u201A", "\u201B", "\u2039", "\u203A", "\uFF07", "\u2032"])
13
+ // Combining marks and conjoining Hangul medial/final jamo attach to the preceding code point,
14
+ // so a decomposed syllable is normalized as one unit.
15
+ const JOINS_PREVIOUS = /^[\p{M}\u1160-\u11FF\uD7B0-\uD7FF]$/u
16
+ const WHITESPACE = /^\s+$/u
17
+ const TOKEN_SEPARATOR = /[^\p{L}\p{N}\p{M}]+/u
18
+
19
+ function mapQuote(ch) {
20
+ if (DOUBLE_QUOTES.has(ch)) return '"'
21
+ if (SINGLE_QUOTES.has(ch)) return "'"
22
+ return ch
23
+ }
24
+
25
+ function clusters(raw) {
26
+ const out = []
27
+ let at = 0
28
+ for (const ch of raw) {
29
+ const last = out[out.length - 1]
30
+ if (last && JOINS_PREVIOUS.test(ch)) last[1] += ch.length
31
+ else out.push([at, at + ch.length])
32
+ at += ch.length
33
+ }
34
+ return out
35
+ }
36
+
37
+ // Returns { text, starts, ends }: text[k] came from raw.slice(starts[k], ends[k]).
38
+ function normalizeWithMap(raw) {
39
+ const source = String(raw ?? "")
40
+ let text = ""
41
+ const starts = []
42
+ const ends = []
43
+ for (const [start, end] of clusters(source)) {
44
+ const piece = Array.from(source.slice(start, end).normalize("NFC"), mapQuote).join("")
45
+ if (WHITESPACE.test(piece)) {
46
+ if (text !== "" && !text.endsWith(" ")) {
47
+ text += " "
48
+ starts.push(start)
49
+ ends.push(end)
50
+ }
51
+ continue
52
+ }
53
+ for (let k = 0; k < piece.length; k++) {
54
+ starts.push(start)
55
+ ends.push(end)
56
+ }
57
+ text += piece
58
+ }
59
+ if (text.endsWith(" ")) {
60
+ text = text.slice(0, -1)
61
+ starts.pop()
62
+ ends.pop()
63
+ }
64
+ return { text, starts, ends }
65
+ }
66
+
67
+ export function normalizeText(s) {
68
+ return normalizeWithMap(s).text
69
+ }
70
+
71
+ export function tokenize(s) {
72
+ return normalizeText(s).split(TOKEN_SEPARATOR).filter((token) => token !== "")
73
+ }
74
+
75
+ export function findNormalized(hay, needle) {
76
+ const target = normalizeText(needle)
77
+ if (target === "") return null
78
+ const map = normalizeWithMap(hay)
79
+ const at = map.text.indexOf(target)
80
+ if (at === -1) return null
81
+ return { start: map.starts[at], end: map.ends[at + target.length - 1] }
82
+ }
@@ -0,0 +1,28 @@
1
+ // text-similarity.mjs - similarity in [0, 1] from normalized Levenshtein distance.
2
+ //
3
+ // Both inputs go through normalizeText first (NFC, quote glyphs, whitespace), then the
4
+ // edit distance is computed over code points and divided by the longer length.
5
+ // Plain Node ESM, no dependencies.
6
+
7
+ import { normalizeText } from "./text-normalize.mjs"
8
+
9
+ function levenshtein(a, b) {
10
+ let previous = Array.from({ length: b.length + 1 }, (_, k) => k)
11
+ for (let i = 1; i <= a.length; i++) {
12
+ const current = [i]
13
+ for (let k = 1; k <= b.length; k++) {
14
+ const cost = a[i - 1] === b[k - 1] ? 0 : 1
15
+ current.push(Math.min(previous[k] + 1, current[k - 1] + 1, previous[k - 1] + cost))
16
+ }
17
+ previous = current
18
+ }
19
+ return previous[b.length]
20
+ }
21
+
22
+ export function similarity(a, b) {
23
+ const left = Array.from(normalizeText(a))
24
+ const right = Array.from(normalizeText(b))
25
+ const longest = Math.max(left.length, right.length)
26
+ if (longest === 0) return 1
27
+ return 1 - levenshtein(left, right) / longest
28
+ }
@@ -0,0 +1,101 @@
1
+ // Canonical factor algebra. Exponents are bigint; aliases enter only through normalizeInput.
2
+ import { fromDecimal, mul, sigFigsOf, toDecimal } from "./rational.mjs"
3
+ import { dimensionOf, normalizeUnit } from "./units.mjs"
4
+ import { refuse } from "./unit-errors.mjs"
5
+ export { refuse, RuleError } from "./unit-errors.mjs"
6
+
7
+ const SCALED = new Set(["bp", "basispoint", "basispoints"])
8
+ export const isPercentish = (unit) => unit === "%" || unit === "%p"
9
+ const scaled = (text) => SCALED.has(text.replace(/\s+/g, "").toLowerCase())
10
+
11
+ function atomOf(text) {
12
+ if (text === "1") return null
13
+ const atom = scaled(text) ? "%p" : normalizeUnit(text) ?? text
14
+ if (dimensionOf(atom) === "scale") refuse(`"${text}" is a scale word (${atom}), not a unit`, undefined, "scale_atom", "a counted unit with scale in the numeral")
15
+ return atom
16
+ }
17
+
18
+ const combine = (a, b, sign) => {
19
+ for (const [atom, power] of b) a.set(atom, (a.get(atom) ?? 0n) + sign * power)
20
+ return a
21
+ }
22
+
23
+ /** Iterative unit parser: factor maps plus the input's decimal scaling exponent. */
24
+ function parse(raw) {
25
+ if (raw === null) return { factors: new Map(), shift: 0n }
26
+ if (typeof raw !== "string" || !raw.trim()) refuse("unit must be text or explicit null", undefined, "unit_text", "canonical unit text or null")
27
+ const tokens = raw.match(/[*/()^]|[^*/()^]+/g).map((x) => x.trim()).filter(Boolean)
28
+ const frames = [{ factors: new Map(), shift: 0n, sign: 1n, need: true }]
29
+ let affine = false
30
+ const apply = (value, i) => {
31
+ if (tokens[i] === "^") {
32
+ const power = tokens[i + 1]
33
+ if (!/^[+-]?\d+$/.test(power ?? "")) refuse(`unit "${raw}" has a bad exponent`, undefined, "exponent", "an integer exponent")
34
+ const n = BigInt(power)
35
+ value = { factors: new Map([...value.factors].map(([a, p]) => [a, p * n])), shift: value.shift * n }
36
+ i += 2
37
+ }
38
+ const frame = frames.at(-1)
39
+ combine(frame.factors, value.factors, frame.sign)
40
+ frame.shift += frame.sign * value.shift
41
+ frame.need = false
42
+ return i
43
+ }
44
+ for (let i = 0; i < tokens.length;) {
45
+ const frame = frames.at(-1)
46
+ const token = tokens[i]
47
+ if (frame.need) {
48
+ if (token === "(") {
49
+ frames.push({ factors: new Map(), shift: 0n, sign: 1n, need: true })
50
+ i++
51
+ } else {
52
+ if (/^[*/()^]$/.test(token)) refuse(`unexpected "${token}" in unit "${raw}"`)
53
+ const atom = atomOf(token)
54
+ affine ||= dimensionOf(atom) === "temperature"
55
+ i = apply({ factors: new Map(atom === null ? [] : [[atom, 1n]]), shift: scaled(token) ? -2n : 0n }, i + 1)
56
+ }
57
+ } else if (token === ")" && frames.length > 1) {
58
+ const value = frames.pop()
59
+ i = apply(value, i + 1)
60
+ } else if (token === "*" || token === "/") {
61
+ frame.sign = token === "*" ? 1n : -1n
62
+ frame.need = true
63
+ i++
64
+ } else refuse(`unexpected "${token}" in unit "${raw}"`)
65
+ }
66
+ if (frames.length !== 1 || frames[0].need) refuse(`unit "${raw}" ends early or is missing a closing parenthesis`)
67
+ // Refuse affine composites before cancellation loses the atom that made them unsafe.
68
+ if (affine && (tokens.length !== 1 || frames[0].shift !== 0n)) refuse("affine (temperature) units cannot occur in composites", undefined, "affine_units", "a non-affine composite or bare temperature for convert")
69
+ return frames[0]
70
+ }
71
+
72
+ function format(factors) {
73
+ const side = (sign) => [...factors].filter(([, p]) => sign * p > 0n).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0)
74
+ .map(([a, p]) => { const n = sign * p; return n === 1n ? a : `${a}^${n}` })
75
+ const top = side(1n)
76
+ const bottom = side(-1n)
77
+ if (!top.length && !bottom.length) return null
78
+ return `${top.join("*") || "1"}${bottom.length ? `/${bottom.length === 1 ? bottom[0] : `(${bottom.join("*")})`}` : ""}`
79
+ }
80
+
81
+ export const canonicalUnit = (raw) => format(parse(raw).factors)
82
+ export function inputScale(raw) {
83
+ const { shift } = parse(raw)
84
+ if (shift === 0n) return null
85
+ if (shift > 10000n || shift < -10000n) refuse("scaled alias exponent exceeds exact input capacity", undefined, "scale_range", "an absolute decimal scale exponent <= 10000")
86
+ return shift > 0n ? `1${"0".repeat(Number(shift))}` : `0.${"0".repeat(Number(-shift - 1n))}1`
87
+ }
88
+
89
+ export function normalizeInput(unit, value) {
90
+ const canonical = canonicalUnit(unit)
91
+ const factor = inputScale(unit)
92
+ return { unit: canonical, value: factor ? toDecimal(mul(fromDecimal(value), fromDecimal(factor)), sigFigsOf(value)) : value }
93
+ }
94
+
95
+ export const unitTimes = (a, b) => format(combine(parse(a).factors, parse(b).factors, 1n))
96
+ export const unitOver = (a, b) => format(combine(parse(a).factors, parse(b).factors, -1n))
97
+ export const isAffine = (unit) => typeof unit === "string" && dimensionOf(unit) === "temperature"
98
+ export function isCurrencyRate(unit) {
99
+ const currencies = [...parse(unit).factors].filter(([a, n]) => n !== 0n && dimensionOf(a)?.startsWith("currency:"))
100
+ return currencies.some(([, n]) => n > 0n) && currencies.some(([, n]) => n < 0n)
101
+ }
@@ -0,0 +1,23 @@
1
+ // Classified refusals shared by public derivation boundaries and the registry.
2
+ export class RuleError extends Error {
3
+ constructor(detail, hint, shape = "invalid_input", expected = "a valid ledger and derivation specification") {
4
+ super(detail)
5
+ this.detail = detail
6
+ this.shape = shape
7
+ this.expected = expected
8
+ if (hint) this.hint = hint
9
+ }
10
+ }
11
+
12
+ export const refuse = (detail, hint, shape, expected) => {
13
+ throw new RuleError(detail, hint, shape, expected)
14
+ }
15
+
16
+ export function refusal(error) {
17
+ return {
18
+ shape: error instanceof RuleError ? error.shape : "invalid_input",
19
+ expected: error instanceof RuleError ? error.expected : "valid JSON fields matching the fact ledger grammar",
20
+ detail: error instanceof Error ? error.message : "invalid input",
21
+ ...(error instanceof RuleError && error.hint ? { hint: error.hint } : {}),
22
+ }
23
+ }
@@ -0,0 +1,31 @@
1
+ // Metadata contracts and order-independent JSON equality, shared by schema and algebra.
2
+ import { isDeepStrictEqual } from "node:util"
3
+ import { refuse } from "./unit-errors.mjs"
4
+
5
+ export const METADATA = ["population", "accounting_basis", "snapshot_window", "entity", "valid_at"]
6
+ export const sameDeclared = isDeepStrictEqual
7
+ export const nonEmpty = (value) => typeof value === "string" && value.trim().length > 0
8
+ export const object = (value) => value !== null && typeof value === "object" && !Array.isArray(value)
9
+
10
+ export function metadataErrors(row) {
11
+ return METADATA.filter((field) => {
12
+ const value = row[field]
13
+ return value !== null && !(nonEmpty(value) || (["population", "snapshot_window"].includes(field) && object(value)))
14
+ }).map((field) => `${field} must be explicitly null or valid non-empty metadata`)
15
+ }
16
+
17
+ export function checkMetadata(rows, fields) {
18
+ for (const row of rows) {
19
+ const errors = metadataErrors(row)
20
+ if (errors.length) refuse(`${row.fact_id}: ${errors.join("; ")}`, undefined, "metadata", "present, type-valid metadata; explicit null when unknown")
21
+ }
22
+ for (const field of fields) {
23
+ if (rows.some((row) => !sameDeclared(row[field], rows[0][field]))) {
24
+ refuse(`${field} differs across ${rows.map((row) => row.fact_id).join(", ")}`, undefined, "metadata", `equal ${field}`)
25
+ }
26
+ }
27
+ }
28
+
29
+ export function propagatedMetadata(rows) {
30
+ return Object.fromEntries(METADATA.map((field) => [field, rows.every((row) => sameDeclared(row[field], rows[0][field])) ? structuredClone(rows[0][field]) : null]))
31
+ }
@@ -0,0 +1,72 @@
1
+ // unit-pairs.mjs - the typed operand-unit rules shared by the operation registry and expr nodes (references/unit-rules.md).
2
+ // equalUnits and notAffine guard whole operations; minusUnit and plusUnit are the typed-pair rules that diff and sum
3
+ // use and that expr's `-` and `+` nodes follow; inferExpr assigns a canonical unit to every node of an expr tree.
4
+
5
+ import { isAffine, isPercentish, refuse, unitOver, unitTimes } from "./unit-algebra.mjs"
6
+ import { postorder } from "./fact-expr.mjs"
7
+ import { checkMetadata, propagatedMetadata } from "./unit-metadata.mjs"
8
+
9
+ export const show = (unit) => JSON.stringify(unit ?? null)
10
+ export const listed = (rows, units) => rows.map((row, index) => `${row.fact_id} ${units[index] ?? "null"}`).join(", ")
11
+
12
+ export function equalUnits(rows, units) {
13
+ if (units.some((unit) => unit !== units[0])) refuse(`unit differs across ${listed(rows, units)}`)
14
+ return units[0]
15
+ }
16
+
17
+ export function notAffine(op, rows, units) {
18
+ if (units.some(isAffine)) refuse(`${op} refuses affine (temperature) units: ${listed(rows, units)}`)
19
+ }
20
+
21
+ /** The typed-pair rule shared by diff and expr `-`: U-U is U; %-% is %p; %-%p is %; %p-%p is %p; %p-% is refused. */
22
+ export function minusUnit(a, b, where) {
23
+ if (isAffine(a) || isAffine(b)) refuse(`${where}: subtracting affine (temperature) values as absolute values has no meaning`)
24
+ if (a === b) return a === "%" ? "%p" : a
25
+ if (a === "%" && b === "%p") return "%"
26
+ refuse(`${where}: ${show(a)} - ${show(b)} is not a typed pair (U-U, %-%, %-%p, %p-%p)`)
27
+ }
28
+
29
+ export function plusUnit(a, b, where) {
30
+ if (isAffine(a) || isAffine(b)) refuse(`${where}: adding affine (temperature) values has no meaning`)
31
+ if (a === b && a === "%") refuse(`${where}: adding % values needs disjoint shares of one reference: use --op sum`)
32
+ if (a === b) return a
33
+ if ((a === "%" && b === "%p") || (a === "%p" && b === "%")) return "%"
34
+ refuse(`${where}: ${show(a)} + ${show(b)} has no common unit`)
35
+ }
36
+
37
+ /** Unit of every node of an expr tree; refuses when a node's unit cannot be inferred. */
38
+ export function inferExpr(ast, unitsById, rowsById = null) {
39
+ const units = new Map()
40
+ const metadata = new Map()
41
+ for (const node of postorder(ast)) {
42
+ let unit
43
+ if (node.id) unit = unitsById.get(node.id)
44
+ else if (node.literal) unit = null
45
+ else if (node.fn === "pct") {
46
+ unit = units.get(node.arg)
47
+ if (unit !== null) refuse(`pct() takes a dimensionless value, got ${show(unit)}`)
48
+ unit = "%"
49
+ } else {
50
+ const left = units.get(node.left)
51
+ const right = units.get(node.right)
52
+ if (node.op === "+") unit = plusUnit(left, right, "expr")
53
+ else if (node.op === "-") unit = minusUnit(left, right, "expr")
54
+ else {
55
+ if (isAffine(left) || isAffine(right)) refuse(`expr: ${node.op} on affine (temperature) units has no meaning`)
56
+ const fraction = (value) => (isPercentish(value) ? null : value)
57
+ unit = (node.op === "*" ? unitTimes : unitOver)(fraction(left), fraction(right))
58
+ }
59
+ }
60
+ units.set(node, unit)
61
+ if (rowsById) {
62
+ const children = node.id ? [rowsById.get(node.id)] : node.literal ? [] : node.fn ? [metadata.get(node.arg)] : [metadata.get(node.left), metadata.get(node.right)]
63
+ const rows = children.filter(Boolean)
64
+ if (rows.length) {
65
+ const fields = node.op === "+" || node.op === "-" ? ["population", "snapshot_window", "accounting_basis"] : node.op === "/" ? ["population", "snapshot_window"] : []
66
+ checkMetadata(rows, fields)
67
+ metadata.set(node, { fact_id: "expression", ...propagatedMetadata(rows) })
68
+ }
69
+ }
70
+ }
71
+ return { unit: units.get(ast), units, metadata: metadata.get(ast) }
72
+ }
@@ -0,0 +1,44 @@
1
+ // Precision is stored independently from display strings; generated fields are rederived on replay.
2
+ import { sigFigsOf, toDecimal } from "./rational.mjs"
3
+ import { refuse } from "./unit-errors.mjs"
4
+
5
+ export const toBig = (r) => ({ num: BigInt(r.num), den: BigInt(r.den) })
6
+ export const toText = (r) => ({ num: String(r.num), den: String(r.den) })
7
+ export const placesOf = (decimal) => (String(decimal).split(".")[1] ?? "").length
8
+ export function terminates(r) {
9
+ let den = r.den
10
+ for (const prime of [2n, 5n]) while (den % prime === 0n) den /= prime
11
+ return den === 1n
12
+ }
13
+ export function precisionOf(row) {
14
+ if (row.precision !== null && row.precision !== undefined) {
15
+ const p = row.precision
16
+ if (!p || typeof p !== "object" || Object.keys(p).sort().join(",") !== "places,sigfigs" || !Number.isSafeInteger(p.places) || p.places < 0 || !Number.isSafeInteger(p.sigfigs) || p.sigfigs < 1) refuse("invalid stored precision", undefined, "precision_field", "{ places: nonnegative integer, sigfigs: positive integer }")
17
+ return p
18
+ }
19
+ return { places: placesOf(row.value), sigfigs: sigFigsOf(row.value) }
20
+ }
21
+ export function resultPrecision(rows, literals, exact, mode) {
22
+ const inputs = [...rows.map(precisionOf), ...literals.map((value) => precisionOf({ value }))]
23
+ const places = Math.min(...inputs.map((p) => p.places))
24
+ const sigfigs = Math.min(...inputs.map((p) => p.sigfigs))
25
+ if (mode === "places") return { places, sigfigs: sigFigsOf(roundPlaces(exact, places)) }
26
+ const rendered = toDecimal(exact, sigfigs)
27
+ return { places: placesOf(rendered), sigfigs }
28
+ }
29
+ export function roundPlaces(r, places) {
30
+ const digits = ((r.num < 0n ? -r.num : r.num) * 10n ** BigInt(places) * 2n + r.den) / (r.den * 2n)
31
+ const text = digits.toString().padStart(places + 1, "0")
32
+ return (r.num < 0n && digits !== 0n ? "-" : "") + (places ? `${text.slice(0, -places)}.${text.slice(-places)}` : text)
33
+ }
34
+ export function renderValue(exact, precision, mode) {
35
+ const plain = terminates(exact) ? toDecimal(exact) : null
36
+ const n = precision[mode]
37
+ if (plain !== null && (mode === "places" ? placesOf(plain) <= n : sigFigsOf(plain) < n)) return plain
38
+ return mode === "places" ? roundPlaces(exact, n) : toDecimal(exact, n)
39
+ }
40
+ export function display(value, unit) {
41
+ const [whole, fraction] = value.split(".")
42
+ const grouped = whole.replace(/\B(?=(\d{3})+(?!\d))/g, ",") + (fraction === undefined ? "" : `.${fraction}`)
43
+ return unit === null ? grouped : unit === "%" ? `${grouped}%` : `${grouped} ${unit}`
44
+ }
@@ -0,0 +1,64 @@
1
+ // Structural rules used by the operation table, shared by schema and recomputation.
2
+ import { leaves, parseExpr } from "./fact-expr.mjs"
3
+ import { isRational } from "./fact-ledger-kinds.mjs"
4
+ import { canonicalUnit, RuleError } from "./unit-algebra.mjs"
5
+ import { nonEmpty, object } from "./unit-metadata.mjs"
6
+ import { show } from "./unit-pairs.mjs"
7
+
8
+ export const COMMON = ["operands null", "operands empty", "wrong arity", "missing operand id", "invalid rational", "no operation", "repeated operand", "self reference",
9
+ "kind operation", "audit fields", "precision field", "compatibility field", "metadata fields", "noncanonical operand"]
10
+ export const shapes = (...own) => Object.freeze([...COMMON, ...own])
11
+ export const needsContext = (row) => object(row.context) ? null : `${row.operation} rows carry a context object, got ${show(row.context)}`
12
+ export const noContext = (row) => row.context === null ? null : `${row.operation} rows carry no context, got ${show(row.context)}`
13
+ export const repeatedOperand = (ids) => new Set(ids).size !== ids.length ? `operand ${ids.find((id, i) => ids.indexOf(id) !== i)} is repeated: the operands of one row are distinct facts` : null
14
+
15
+ export function sumShape(row) {
16
+ const missing = needsContext(row)
17
+ if (missing) return missing
18
+ const c = row.context
19
+ if (Object.keys(c).sort().join(",") !== "disjoint,reference" || typeof c.disjoint !== "boolean" || !(c.reference === null || nonEmpty(c.reference) || object(c.reference))) return "sum context needs exactly boolean disjoint and null, text, or map reference"
20
+ if (object(c.reference) && (Object.keys(c.reference).sort().join(",") !== [...row.operands].sort().join(",") || !Object.values(c.reference).every(nonEmpty))) return "reference map must name exactly the supplied operands"
21
+ return null
22
+ }
23
+
24
+ export function convertShape(row) {
25
+ const target = object(row.context) ? row.context.target : undefined
26
+ if (!nonEmpty(target)) return needsContext(row) ?? "a convert row carries its target in context.target"
27
+ if (Object.keys(row.context).join(",") !== "target") return "convert context has exactly target"
28
+ try {
29
+ const canonical = canonicalUnit(target)
30
+ return canonical === target ? null : `context.target ${show(target)} is not canonical unit text (canonical: ${show(canonical)})`
31
+ } catch (error) {
32
+ if (error instanceof RuleError) return `context.target ${show(target)} is not unit text: ${error.detail}`
33
+ throw error
34
+ }
35
+ }
36
+
37
+ export function exprShape(row) {
38
+ try {
39
+ const ids = leaves(parseExpr(row.formula)).ids
40
+ if (ids.length !== row.operands.length || ids.some((id) => !row.operands.includes(id))) return `formula ids ${ids.join(", ")} do not match operands ${row.operands.join(", ")}`
41
+ return null
42
+ } catch (error) {
43
+ if (error instanceof RuleError) return error.detail
44
+ throw error
45
+ }
46
+ }
47
+
48
+ export function rowShape(row, rules) {
49
+ if (!object(row)) return "the row is not an object"
50
+ if (!Object.hasOwn(rules, row.operation)) return `operation ${show(row.operation)} is not one of ${Object.keys(rules).join(", ")}`
51
+ const rule = rules[row.operation]
52
+ const { operands, exact } = row
53
+ if (!Array.isArray(operands) || !operands.length || !operands.every(nonEmpty)) return `operands must be a non-empty array of fact ids, got ${show(operands)}`
54
+ const repeated = repeatedOperand(operands)
55
+ if (repeated) return repeated
56
+ if (operands.includes(row.fact_id)) return `${row.fact_id} cites itself as an operand`
57
+ if (!rule.arity(operands.length)) return `${row.operation} takes ${rule.arityText}, got ${operands.length}`
58
+ if (!isRational(exact)) return `exact must be { num, den } reduced integer strings, got ${show(exact)}`
59
+ if (row.kind !== rule.kind) return `kind ${row.kind} must be ${rule.kind} for ${row.operation}`
60
+ const own = rule.shape(row)
61
+ if (own) return own
62
+ if (!nonEmpty(row.formula) || !Array.isArray(row.derived_from) || row.derived_from.length !== operands.length) return "formula and derived_from are required audit fields"
63
+ return null
64
+ }