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,66 @@
1
+ // Public append boundary: dispatch through the operation table, verify transitive trust, append only on success.
2
+ import { leaves, parseExpr } from "./fact-expr.mjs"
3
+ import { readRow } from "./fact-ledger-kinds.mjs"
4
+ import { addRow, nextId, validateLedger } from "./fact-ledger.mjs"
5
+ import { recompute, verifyGraph } from "./fact-recompute.mjs"
6
+ import { canonicalUnit, inputScale, refuse } from "./unit-algebra.mjs"
7
+ import { refusal } from "./unit-errors.mjs"
8
+ import { object, sameDeclared } from "./unit-metadata.mjs"
9
+ import { display, renderValue, resultPrecision, toText } from "./unit-precision.mjs"
10
+ import { expectedUnit, renderPrecision, ruleOf, unitMismatch } from "./unit-rules.mjs"
11
+ export { recompute, display }
12
+
13
+ /** Arithmetic and generated fields are chosen exclusively by the registry. */
14
+ export function compute(op, rows, unit, ast, context, formula = null) {
15
+ const rule = ruleOf(op)
16
+ const out = rule.calculate(rows, context, ast, formula)
17
+ const mode = renderPrecision(op, unit, ast)
18
+ const precision = resultPrecision(rows, ast ? leaves(ast).literals : [], out.exact, mode)
19
+ const value = renderValue(out.exact, precision, mode)
20
+ const render = rule.render(display(value, unit), rows)
21
+ return { exact: toText(out.exact), value, render, raw: render, formula: out.formula, factor: out.factor, precision, compatibility: rule.compatibility, ...rule.metadataResult(rows) }
22
+ }
23
+
24
+ function declaredUnit(spec, rule) {
25
+ const own = Object.hasOwn(spec, "unit")
26
+ if ((!own && !rule.defaultUnit) || (own && spec.unit === undefined)) refuse("declare the result unit: pass unit (null for a dimensionless result)", undefined, "missing_unit", "an explicit unit or null")
27
+ const declared = own ? spec.unit : rule.defaultUnit(spec)
28
+ if (inputScale(declared)) refuse(`${declared} needs rescaling: declare %p and scale the value`)
29
+ return canonicalUnit(declared)
30
+ }
31
+
32
+ export function derive(ledger, spec) {
33
+ try {
34
+ if (!object(spec)) refuse("spec must be an object")
35
+ const errors = validateLedger(ledger)
36
+ if (errors.length) refuse(errors.join("; "), undefined, "ledger_schema", "a ledger conforming to KIND_RULES and canonical storage")
37
+ const rule = ruleOf(spec.op)
38
+ const allowed = ["op", "unit", "rationale", "label", "now", "context", ...rule.inputFields]
39
+ if (Object.keys(spec).some((key) => !allowed.includes(key))) refuse("spec carries fields outside its operation", undefined, "spec_fields", allowed)
40
+ const ast = rule.expression ? parseExpr(spec.formula) : null
41
+ const ids = rule.operandIds(spec, ast)
42
+ if (!Array.isArray(ids) || !ids.every((id) => typeof id === "string")) refuse(`${spec.op} needs its operand fact ids`, undefined, "operands", "an array of existing fact ids")
43
+ const verified = verifyGraph(ledger, ids, true)
44
+ if (!verified.ok) return { error: "incompatible_operands", ...verified }
45
+ const byId = new Map(ledger.rows.map((r) => [r.fact_id, r]))
46
+ const rows = ids.map((id) => readRow(byId.get(id)))
47
+ const context = rule.context(spec)
48
+ if (Object.hasOwn(spec, "context") && !sameDeclared(spec.context, context)) refuse("supplied context differs from the operation's input fields", undefined, "context", context ?? "explicit null")
49
+ const shape = rule.shape({ operation: spec.op, operands: ids, formula: spec.formula, context })
50
+ if (shape) refuse(shape, undefined, "context_or_formula", "the operation's declared context and matching formula operand set")
51
+ const expected = expectedUnit(spec.op, rows, { ast, context: { ...context, rationale: spec.rationale ?? null } })
52
+ const declared = declaredUnit(spec, rule)
53
+ if (declared !== expected) refuse(unitMismatch(spec.op, rows, declared, expected), undefined, "result_unit", { unit: expected })
54
+ const result = compute(spec.op, rows, expected, ast, context, spec.formula)
55
+ const row = addRow(ledger, {
56
+ kind: rule.kind, unit: expected, quote: "", extracted_by: "fact-derive",
57
+ observed_at: spec.now ?? new Date().toISOString(),
58
+ label: spec.label ?? `${spec.op}-${nextId(ledger, rule.kind).toLowerCase()}`,
59
+ ...result, operation: spec.op, operands: ids, derived_from: ids,
60
+ rationale: spec.rationale ?? null, context,
61
+ })
62
+ return { row, compatibility: rule.compatibility }
63
+ } catch (error) {
64
+ return { error: "incompatible_operands", ...refusal(error) }
65
+ }
66
+ }
@@ -0,0 +1,108 @@
1
+ // fact-expr.mjs - the expression language of `fact derive --op expr` (references/unit-rules.md, expr row).
2
+ // Leaves are fact ids and decimal literals; nodes are + - * / and pct(x), the explicit dimensionless-to-% node.
3
+ // evaluate() is exact rational arithmetic. Under * and / a child whose inferred unit is % or %p enters as its
4
+ // fraction (20% as 0.2): that unit comes from the registry's inference over the operand rows, never from the row's
5
+ // own declared unit.
6
+
7
+ import { add, div, fromDecimal, mul, sub } from "./rational.mjs"
8
+ import { isPercentish, refuse } from "./unit-algebra.mjs"
9
+
10
+ const TOKEN = /\s*(?:(F\d+)|(\d+(?:\.\d+)?)|(pct)\b|([-+*/()]))/y
11
+ const HUNDRED = fromDecimal("100")
12
+
13
+ function tokenize(formula) {
14
+ const text = String(formula ?? "")
15
+ const tokens = []
16
+ TOKEN.lastIndex = 0
17
+ while (/\S/.test(text.slice(TOKEN.lastIndex))) {
18
+ const at = TOKEN.lastIndex
19
+ const match = TOKEN.exec(text)
20
+ if (!match) refuse(`unsupported expression text at ${at}: "${text.slice(at).trim().slice(0, 16)}"`)
21
+ tokens.push(match[1] ? { id: match[1] } : match[2] ? { literal: match[2] } : match[3] ? { fn: "pct" } : { op: match[4] })
22
+ }
23
+ if (tokens.length === 0) refuse("expr needs an expression")
24
+ return tokens
25
+ }
26
+
27
+ /** Shunting-yard parser: parentheses and AST depth do not consume the JS call stack. */
28
+ export function parseExpr(formula) {
29
+ if (typeof formula !== "string") refuse("expr needs an expression")
30
+ const tokens = tokenize(formula)
31
+ const values = []
32
+ const ops = []
33
+ const precedence = (op) => op === "*" || op === "/" ? 2 : 1
34
+ const reduce = () => {
35
+ const op = ops.pop()
36
+ if (op === "pct") values.push({ fn: op, arg: values.pop() })
37
+ else { const right = values.pop(); values.push({ op, left: values.pop(), right }) }
38
+ }
39
+ let need = true
40
+ for (let i = 0; i < tokens.length; i++) {
41
+ const token = tokens[i]
42
+ if (need) {
43
+ if (token.id || token.literal) { values.push(token); need = false }
44
+ else if (token.fn) {
45
+ if (tokens[i + 1]?.op !== "(") refuse("pct needs a parenthesized argument")
46
+ ops.push("pct")
47
+ } else if (token.op === "(") ops.push("(")
48
+ else refuse(`unexpected "${token.op}" in the expression`)
49
+ } else if (token.op === ")") {
50
+ while (ops.length && ops.at(-1) !== "(") reduce()
51
+ if (ops.pop() !== "(") refuse("unexpected closing parenthesis")
52
+ if (ops.at(-1) === "pct") reduce()
53
+ } else if (["+", "-", "*", "/"].includes(token.op)) {
54
+ while (ops.length && ops.at(-1) !== "(" && precedence(ops.at(-1)) >= precedence(token.op)) reduce()
55
+ ops.push(token.op)
56
+ need = true
57
+ } else refuse("unexpected text after the expression")
58
+ }
59
+ if (need) refuse("the expression ends early")
60
+ if (ops.includes("(")) refuse("the expression is missing a closing parenthesis")
61
+ while (ops.length) reduce()
62
+ return values[0]
63
+ }
64
+
65
+ const childrenOf = (node) => (node.op ? [node.left, node.right] : node.fn ? [node.arg] : [])
66
+
67
+ export function leaves(node, found = { ids: [], literals: [] }) {
68
+ const ids = new Set(found.ids)
69
+ const stack = [node]
70
+ while (stack.length) {
71
+ const current = stack.pop()
72
+ if (current.id) ids.add(current.id)
73
+ if (current.literal) found.literals.push(current.literal)
74
+ stack.push(...childrenOf(current).reverse())
75
+ }
76
+ found.ids = [...ids]
77
+ return found
78
+ }
79
+
80
+ export function postorder(node) {
81
+ const stack = [node]
82
+ const order = []
83
+ while (stack.length) { const current = stack.pop(); order.push(current); stack.push(...childrenOf(current)) }
84
+ return order.reverse()
85
+ }
86
+
87
+ /** Exact value of the tree; `units` maps each node to its inferred canonical unit (inferExpr in unit-rules.mjs). */
88
+ export function evaluate(node, values, units) {
89
+ const results = new Map()
90
+ for (const current of postorder(node)) {
91
+ let value
92
+ if (current.id) value = values.get(current.id)
93
+ else if (current.literal) value = fromDecimal(current.literal)
94
+ else if (current.fn === "pct") value = mul(results.get(current.arg), HUNDRED)
95
+ else {
96
+ const operand = (child) => {
97
+ const v = results.get(child)
98
+ return (current.op === "*" || current.op === "/") && isPercentish(units.get(child)) ? div(v, HUNDRED) : v
99
+ }
100
+ const left = operand(current.left)
101
+ const right = operand(current.right)
102
+ if (current.op === "/" && right.num === 0n) refuse("the expression divides by zero", undefined, "divides_by_zero", "a nonzero denominator")
103
+ value = { "+": add, "-": sub, "*": mul, "/": div }[current.op](left, right)
104
+ }
105
+ results.set(current, value)
106
+ }
107
+ return results.get(node)
108
+ }
@@ -0,0 +1,194 @@
1
+ // fact-fill.mjs - the fill step: substitute every fact pointer in a report source from the
2
+ // ledger, all or nothing. Fatal errors (pointer_label_mismatch, unresolved_placeholder,
3
+ // quote_anchor_unresolved, unit_duplicated) return out null; warnings (particle_unverified,
4
+ // fallback_without_entity) never block. A twice-failed sentence can be replaced by an attributed
5
+ // block quote of its source row (replaceFailed). No IO, but not pure: unless options.view is set,
6
+ // it records each replaced sentence in options.replaceFailed through markReplaced. node: builtins only.
7
+
8
+ import { finalReading, rewriteParticle } from "./particles-ko.mjs"
9
+ import { failureCount, markReplaced, sentenceKey } from "./fidelity-state.mjs"
10
+ import { findPlaceholders, lead, qualifiedText, renderBlockquote, renderQuote, renderValueHtml, subSpan, valueParts } from "./placeholder.mjs"
11
+ import { splitSentences } from "./sentence-split.mjs"
12
+
13
+ const FORMATS = Object.freeze(["html", "md"])
14
+ const TRANSLATION = Object.freeze({
15
+ html: /^\s*<span data-translates="(Q\d+)">[\s\S]*?<\/span>/u,
16
+ md: /^\s*<!-- translates: (Q\d+) -->[^\n]*/u,
17
+ })
18
+ const HEADING = Object.freeze({ html: /<h[1-6]\b([^>]*)>([\s\S]*?)<\/h[1-6]>/giu, md: /^#{1,6}[ \t]+(.+)$/gmu })
19
+
20
+ const lineOf = (text, at) => text.slice(0, at).split("\n").length
21
+
22
+ function resolveValue(row, parsed, format) {
23
+ const { valueText, unitText } = valueParts(row)
24
+ const text = { full: row.raw, value: valueText, unit: unitText, q: qualifiedText(row) }[parsed.mode]
25
+ if (text === null) return { code: "quote_anchor_unresolved", message: `${row.fact_id} has qualifier ${row.qualifier}, but its quote holds no qualifier beside "${row.raw}"` }
26
+ return { row, parsed, visible: text, rendered: format === "html" ? renderValueHtml(row, text) : text }
27
+ }
28
+
29
+ function resolveQuote(row, parsed, format) {
30
+ const whole = { text: row.quote, start: 0, end: row.quote.length }
31
+ const selection = parsed.mode === "lead" ? lead(row.quote, parsed.lead) : parsed.mode === "span" ? subSpan(row.quote, parsed.from, parsed.to) : whole
32
+ if (selection === null) {
33
+ return { code: "quote_anchor_unresolved", message: `from "${parsed.from}" and to "${parsed.to}" must each occur exactly once in ${row.fact_id}'s quote, in that order` }
34
+ }
35
+ const rendered = renderQuote(row, parsed.selector, selection.text, format)
36
+ return { row, parsed, visible: `[${row.source_id}]`, rendered, selector: parsed.selector, quote_span: [selection.start, selection.end] }
37
+ }
38
+
39
+ function resolve(found, rows, format) {
40
+ const { parsed } = found
41
+ if (parsed.error) return { code: "unresolved_placeholder", message: parsed.error }
42
+ const row = rows.get(parsed.id)
43
+ if (row === undefined) return { code: "unresolved_placeholder", message: `${parsed.id} is not in the fact ledger` }
44
+ if (row.status !== "active") return { code: "unresolved_placeholder", message: `${parsed.id} is ${row.status}; cite its replacement` }
45
+ if (row.label !== parsed.label) return { code: "pointer_label_mismatch", message: `${parsed.id} has label "${row.label}", not "${parsed.label}"` }
46
+ return parsed.kind === "Q" ? resolveQuote(row, parsed, format) : resolveValue(row, parsed, format)
47
+ }
48
+
49
+ /** A unit typed right after a pointer that already renders it: never absorbed, always an error. */
50
+ function unitDuplicated(text, item) {
51
+ if (item.parsed.kind !== "F" || (item.parsed.mode !== "full" && item.parsed.mode !== "q")) return false
52
+ const suffix = item.row.raw.replace(/^[\s\S]*\d/u, "").trim()
53
+ const units = [item.row.unit, valueParts(item.row).unitText, suffix].filter((unit) => typeof unit === "string" && unit !== "")
54
+ const following = text.slice(item.end).replace(/^[ \t]+/u, "")
55
+ return units.some((unit) => following.startsWith(unit) && !/[A-Za-z0-9]/u.test(following[unit.length] ?? ""))
56
+ }
57
+
58
+ /*
59
+ * Sentence-key rules (fidelity-state.mjs sentenceKey(slug, ids)); any other producer of keys must match:
60
+ * - slug source: an HTML heading's id attribute wins over its text; md headings use their text.
61
+ * - slugify: strip tags, lowercase, turn each run of non-letters/digits (\p{L}\p{N}) into "-",
62
+ * trim leading and trailing "-".
63
+ * - a sentence takes the nearest heading that starts strictly before the sentence's start offset;
64
+ * a sentence before the first heading gets slug "".
65
+ * - fact ids cited in the sentence are de-duplicated before sentenceKey sorts them.
66
+ */
67
+ function slugify(text) {
68
+ return text
69
+ .replace(/<[^>]*>/gu, "")
70
+ .toLowerCase()
71
+ .replace(/[^\p{L}\p{N}]+/gu, "-")
72
+ .replace(/^-+|-+$/gu, "")
73
+ }
74
+
75
+ function headings(text, format) {
76
+ return [...text.matchAll(HEADING[format])].map((match) => {
77
+ const id = format === "html" ? /\bid="([^"]*)"/u.exec(match[1])?.[1] : undefined
78
+ return { at: match.index, slug: slugify(id ?? match[match.length - 1]) }
79
+ })
80
+ }
81
+
82
+ /** Sentence offsets over the source with pointers, comments and (html) tags blanked to equal length. */
83
+ function sentencesOf(text, found, format) {
84
+ let masked = text
85
+ for (const item of found) masked = masked.slice(0, item.start) + "x".repeat(item.end - item.start) + masked.slice(item.end)
86
+ const blank = (part) => part.replace(/[^\n]/gu, " ")
87
+ masked = masked.replace(/<!--[\s\S]*?-->/gu, blank)
88
+ if (format === "html") masked = masked.replace(/<[^>]*>/gu, blank)
89
+ return splitSentences(masked)
90
+ }
91
+
92
+ function replacements(text, found, items, options, warnings) {
93
+ const { format, replaceFailed: state } = options
94
+ const marks = headings(text, format)
95
+ const out = []
96
+ for (const sentence of sentencesOf(text, found, format)) {
97
+ const inside = items.filter((item) => item.start >= sentence.start && item.end <= sentence.end)
98
+ if (inside.length === 0) continue
99
+ const slug = marks.filter((mark) => mark.at < sentence.start).at(-1)?.slug ?? ""
100
+ const key = sentenceKey(slug, [...new Set(inside.map((item) => item.row.fact_id))])
101
+ if (failureCount(state, key) < 2) continue
102
+ const rows = inside.map((item) => item.row)
103
+ const source = rows.find((row) => row.kind === "quote") ?? rows.find((row) => row.source_id !== null && row.quote !== "")
104
+ if (source === undefined) continue
105
+ let rendered = options.fallback === "omit" ? "" : renderBlockquote(source, key, format)
106
+ if (rendered !== "" && format === "md") {
107
+ if (sentence.start > 0 && text[sentence.start - 1] !== "\n") rendered = `\n\n${rendered}`
108
+ if (sentence.end < text.length && text[sentence.end] !== "\n") rendered = `${rendered}\n\n`
109
+ }
110
+ if (rendered !== "" && source.entity === null) {
111
+ warnings.push({ code: "fallback_without_entity", token: key, line: lineOf(text, sentence.start), message: `${source.fact_id} has no entity; the block quote cites [${source.source_id}] alone` })
112
+ }
113
+ out.push({ start: sentence.start, end: sentence.end, key, source, rendered })
114
+ }
115
+ return out
116
+ }
117
+
118
+ /** The text after a pointer: rewrite a following Korean particle, and map a translation span. */
119
+ function afterPlaceholder(segment, item, context) {
120
+ const { format, text, warnings, fillMap } = context
121
+ const probeEnd = format === "html" && segment.includes("<") ? segment.indexOf("<") : segment.length
122
+ const result = rewriteParticle(segment.slice(0, probeEnd), finalReading(item.visible, null))
123
+ let next = segment
124
+ if (result.applied) next = result.text + segment.slice(probeEnd)
125
+ else if (result.candidate) {
126
+ warnings.push({ code: "particle_unverified", token: item.token, line: lineOf(text, item.start), message: `the particle after ${item.token} was not rewritten; check it by eye` })
127
+ }
128
+ const translation = item.parsed.kind === "Q" ? TRANSLATION[format].exec(next) : null
129
+ if (translation !== null && translation[1] === item.row.fact_id) {
130
+ const span = translation[0].trimStart()
131
+ const start = context.out.length + translation[0].length - span.length
132
+ fillMap.push({ token: span, fact_id: translation[1], translates: translation[1], start, end: start + span.length, rendered: span })
133
+ }
134
+ return next
135
+ }
136
+
137
+ function assemble(text, parts, context) {
138
+ let cursor = 0
139
+ let previous = null
140
+ const flush = (until) => {
141
+ const segment = text.slice(cursor, until)
142
+ context.out += previous?.parsed ? afterPlaceholder(segment, previous, context) : segment
143
+ }
144
+ for (const part of parts) {
145
+ flush(part.start)
146
+ const start = context.out.length
147
+ if (part.parsed) {
148
+ const extra = part.selector === undefined ? {} : { selector: part.selector, quote_span: part.quote_span }
149
+ context.fillMap.push({ token: part.token, fact_id: part.row.fact_id, start, end: start + part.rendered.length, rendered: part.rendered, ...extra })
150
+ } else if (part.rendered !== "") {
151
+ context.fillMap.push({ token: null, fact_id: part.source.fact_id, replaced: part.key, start, end: start + part.rendered.length, rendered: part.rendered })
152
+ }
153
+ context.out += part.rendered
154
+ cursor = part.end
155
+ previous = part
156
+ }
157
+ flush(text.length)
158
+ return context.out
159
+ }
160
+
161
+ /**
162
+ * @param {string} sourceText the report source with {{F..}} / {{Q..}} pointers
163
+ * @param {{ rows: object[] }} ledger
164
+ * @param {{ format?: "html"|"md", replaceFailed?: object, fallback?: "quote"|"omit", view?: boolean }} [options]
165
+ * @returns {{ out: string|null, fillMap: object[], errors: object[], warnings: object[] }}
166
+ */
167
+ export function fill(sourceText, ledger, options = {}) {
168
+ const format = options.format ?? "html"
169
+ if (!FORMATS.includes(format)) throw new TypeError(`format must be one of ${FORMATS.join(", ")}`)
170
+ const rows = new Map(ledger.rows.map((row) => [row.fact_id, row]))
171
+ const found = findPlaceholders(sourceText)
172
+ const errors = []
173
+ const warnings = []
174
+ const items = []
175
+ for (const pointer of found) {
176
+ const resolved = resolve(pointer, rows, format)
177
+ const line = lineOf(sourceText, pointer.start)
178
+ if (resolved.code) errors.push({ code: resolved.code, token: pointer.token, line, message: resolved.message })
179
+ else {
180
+ const item = { ...pointer, ...resolved }
181
+ if (unitDuplicated(sourceText, item)) {
182
+ errors.push({ code: "unit_duplicated", token: pointer.token, line, message: `${pointer.token} already renders its unit; use {{${item.row.fact_id}:${item.row.label}:value}}` })
183
+ } else items.push(item)
184
+ }
185
+ }
186
+ if (errors.length > 0) return { out: null, fillMap: [], errors, warnings }
187
+ const replaced = options.replaceFailed ? replacements(sourceText, found, items, { ...options, format }, warnings) : []
188
+ const kept = items.filter((item) => !replaced.some((block) => item.start >= block.start && item.end <= block.end))
189
+ const parts = [...kept, ...replaced].sort((a, b) => a.start - b.start)
190
+ const context = { format, text: sourceText, warnings, fillMap: [], out: "" }
191
+ const out = assemble(sourceText, parts, context)
192
+ if (!options.view) for (const block of replaced) markReplaced(options.replaceFailed, block.key)
193
+ return { out, fillMap: context.fillMap, errors, warnings }
194
+ }
@@ -0,0 +1,250 @@
1
+ // fact-ledger-kinds.mjs - the per-kind column rules of the fact ledger as one table, KIND_RULES.
2
+ // validateLedger checks every per-kind column through kindRuleErrors, the writers read their
3
+ // per-kind choices (id prefix, measured exact) from it, and the per-kind table in
4
+ // references/fact-ledger-grammar.md is rendered from it (`node fact-ledger-kinds.mjs --render-grammar`).
5
+ // Also the value primitives the schema shares. Pure functions with no IO; node: builtins only.
6
+
7
+ import { isCliEntry } from "./entry-guard.mjs"
8
+ import { canonicalUnit, inputScale, RuleError } from "./unit-algebra.mjs"
9
+ import { metadataErrors } from "./unit-metadata.mjs"
10
+ import { mul } from "./rational.mjs"
11
+ import { placesOf, roundPlaces, toBig } from "./unit-precision.mjs"
12
+ import { OPERATION_RULES } from "./unit-rules.mjs"
13
+
14
+ export const DECIMAL = /^-?\d+(?:\.\d+)?$/
15
+ const INTEGER = /^-?\d+$/
16
+ const SHA256 = /^[0-9a-f]{64}$/
17
+ const SOURCE_ID = /^S\d+$/
18
+
19
+ export const isObject = (value) => value !== null && typeof value === "object" && !Array.isArray(value)
20
+ export const isString = (value) => typeof value === "string"
21
+ export const isNonEmpty = (value) => typeof value === "string" && value.length > 0
22
+ export const isCount = (value) => Number.isInteger(value) && value >= 0
23
+ const isDecimal = (value) => isString(value) && DECIMAL.test(value)
24
+ export function isRational(exact) {
25
+ if (!isObject(exact) || Object.keys(exact).sort().join(",") !== "den,num" || !isString(exact.num) || !isString(exact.den) || !INTEGER.test(exact.num) || !/^[1-9]\d*$/.test(exact.den)) return false
26
+ let a = BigInt(exact.num)
27
+ let b = BigInt(exact.den)
28
+ if (String(a) !== exact.num) return false
29
+ if (a < 0n) a = -a
30
+ while (b !== 0n) [a, b] = [b, a % b]
31
+ return a === 1n
32
+ }
33
+
34
+ /** The exact rational of a decimal string, reduced. */
35
+ export function exactFromDecimal(value) {
36
+ const [whole, fraction = ""] = value.replace(/^-/, "").split(".")
37
+ let num = BigInt(whole + fraction)
38
+ let den = 10n ** BigInt(fraction.length)
39
+ let a = num
40
+ let b = den
41
+ while (b !== 0n) [a, b] = [b, a % b]
42
+ if (a > 1n) [num, den] = [num / a, den / a]
43
+ return { num: `${value.startsWith("-") && num !== 0n ? "-" : ""}${num}`, den: `${den}` }
44
+ }
45
+
46
+ /** @returns {string[]} what is wrong with a locator object, each message prefixed with `at`. */
47
+ export function locatorErrors(locator, at) {
48
+ const errors = []
49
+ if (!isObject(locator)) return errors
50
+ if (!isNonEmpty(locator.artifact)) errors.push(`${at}.artifact must be a non-empty string`)
51
+ if (!(isCount(locator.char_start) && isCount(locator.char_end) && locator.char_start <= locator.char_end)) errors.push(`${at}.char_start/char_end must be integers with start <= end`)
52
+ for (const key of ["page", "line"]) {
53
+ if (locator[key] !== undefined && !(Number.isInteger(locator[key]) && locator[key] >= 1)) errors.push(`${at}.${key} must be an integer >= 1`)
54
+ }
55
+ for (const key of ["table", "row_label", "col_label"]) {
56
+ if (locator[key] !== undefined && !isString(locator[key])) errors.push(`${at}.${key} must be a string`)
57
+ }
58
+ return errors
59
+ }
60
+
61
+ const isLocator = (value) => isObject(value) && locatorErrors(value, "").length === 0
62
+ const orNull = (test) => (value, row) => value === null || test(value, row)
63
+ const locatorDetail = (value, at) => locatorErrors(value, at)
64
+
65
+ /**
66
+ * Every rule a KIND_RULES cell may name: `cell` is its text in the grammar table, `says` ends the
67
+ * error message, `test(value, row)` decides it, and `prefix` (id rules only) is what nextId uses.
68
+ */
69
+ export const RULES = Object.freeze({
70
+ f_id: { cell: "`F<n>`", says: "F<n>", prefix: "F", test: (v) => isString(v) && /^F\d+$/.test(v) },
71
+ q_id: { cell: "`Q<n>`", says: "Q<n>", prefix: "Q", test: (v) => isString(v) && /^Q\d+$/.test(v) },
72
+ decimal: { cell: "decimal", says: "a decimal string", test: isDecimal },
73
+ null: { cell: "null", says: "null", test: (v) => v === null },
74
+ source: { cell: "`S<n>`", says: "S<n>", test: (v) => isString(v) && SOURCE_ID.test(v) },
75
+ source_or_null: { cell: "`S<n>` or null", says: "S<n> or null", test: orNull((v) => isString(v) && SOURCE_ID.test(v)) },
76
+ locator: { cell: "object", says: "a locator object", test: isLocator, detail: locatorDetail },
77
+ locator_or_null: { cell: "object or null", says: "a locator object or null", test: orNull(isLocator), detail: locatorDetail },
78
+ sha256: { cell: "sha256", says: "a sha256 hex digest", test: (v) => isString(v) && SHA256.test(v) },
79
+ sha256_or_null: { cell: "sha256 or null", says: "a sha256 hex digest or null", test: orNull((v) => isString(v) && SHA256.test(v)) },
80
+ string_or_null: { cell: "string or null", says: "null or a string", test: orNull(isString) },
81
+ non_empty: { cell: "non-empty", says: "a non-empty string (required for this kind)", test: isNonEmpty },
82
+ from_value: {
83
+ cell: "from `value`",
84
+ says: "the reduced exact rational of value",
85
+ test: (v, row) => isRational(v) && (!isDecimal(row.value) || (v.num === exactFromDecimal(row.value).num && v.den === exactFromDecimal(row.value).den)),
86
+ },
87
+ rational_or_null: { cell: "rational or null", says: "null or { num: integer string, den: positive integer string }", test: orNull(isRational) },
88
+ by_derive: { cell: "reduced rational", says: "a reduced { num: integer string, den: positive integer string }", test: isRational },
89
+ context_by_derive: { cell: "written by derive (object or null)", says: "null or an object", test: orNull(isObject) },
90
+ derived_operation: { cell: "sum/diff/growth/share/ratio/expr", says: "a derived operation", test: (v) => Object.hasOwn(OPERATION_RULES, v) && OPERATION_RULES[v].kind === "derived" },
91
+ convert_operation: { cell: "`convert`", says: "convert", test: (v) => Object.hasOwn(OPERATION_RULES, v) && OPERATION_RULES[v].kind === "converted" },
92
+ precision: { cell: "`{ places, sigfigs }`", says: "nonnegative places and positive sigfigs", test: (v) => isObject(v) && Object.keys(v).sort().join(",") === "places,sigfigs" && Number.isSafeInteger(v.places) && v.places >= 0 && Number.isSafeInteger(v.sigfigs) && v.sigfigs >= 1 },
93
+ compatibility: { cell: "checked or unchecked (expr)", says: "the operation's compatibility", test: (v, r) => Object.hasOwn(OPERATION_RULES, r.operation) && v === OPERATION_RULES[r.operation].compatibility },
94
+ })
95
+
96
+ /** The grammar's per-kind columns, in table order; `exact` follows `value` because it reads it. */
97
+ export const KIND_COLUMNS = Object.freeze(["fact_id", "value", "source_id", "locator", "snapshot_sha256", "rationale", "exact", "context", "operation", "precision", "compatibility"])
98
+
99
+ const sourced = { source_id: "source", locator: "locator", snapshot_sha256: "sha256" }
100
+ const unsourced = { source_id: "source_or_null", locator: "locator_or_null", snapshot_sha256: "sha256_or_null" }
101
+ const row = (cells) => Object.freeze(Object.fromEntries(KIND_COLUMNS.map((column) => [column, cells[column] ?? (["precision", "compatibility"].includes(column) && cells.operation !== "null" ? column : "null")])))
102
+
103
+ export const KIND_RULES = Object.freeze({
104
+ measured: row({ fact_id: "f_id", value: "decimal", ...sourced, rationale: "string_or_null", exact: "from_value", context: "null", operation: "null" }),
105
+ quote: row({ fact_id: "q_id", value: "null", ...sourced, rationale: "string_or_null", exact: "null", context: "null", operation: "null" }),
106
+ derived: row({ fact_id: "f_id", value: "decimal", ...unsourced, rationale: "string_or_null", exact: "by_derive", context: "context_by_derive", operation: "derived_operation" }),
107
+ assumed: row({ fact_id: "f_id", value: "decimal", ...unsourced, rationale: "non_empty", exact: "rational_or_null", context: "null", operation: "null" }),
108
+ converted: row({ fact_id: "f_id", value: "decimal", ...unsourced, rationale: "string_or_null", exact: "by_derive", context: "context_by_derive", operation: "convert_operation" }),
109
+ })
110
+
111
+ /** The id prefix nextId assigns for a kind; an unknown kind gets F and fails validation later. */
112
+ export function idPrefix(kind) {
113
+ return RULES[KIND_RULES[kind]?.fact_id ?? "f_id"].prefix
114
+ }
115
+
116
+ /** Whether addRow computes the row's exact from its decimal value. */
117
+ export function exactComesFromValue(kind) {
118
+ return KIND_RULES[kind]?.exact === "from_value"
119
+ }
120
+
121
+ /** Columns added after schema version 1 shipped; a ledger written before them still loads (dimension 31). */
122
+ export const LATER_COLUMNS = Object.freeze(["context", "precision", "compatibility"])
123
+ const DERIVED_KINDS = Object.freeze(["derived", "converted"])
124
+ const PRE_REGISTRY_COLUMNS = KIND_COLUMNS.slice(0, KIND_COLUMNS.indexOf("exact") + 1)
125
+
126
+ /** A derived or converted row written before the later columns existed: loadable, never an operand. */
127
+ export const isLegacyDerived = (row) => isObject(row) && DERIVED_KINDS.includes(row.kind) && LATER_COLUMNS.every((column) => !Object.hasOwn(row, column))
128
+ /** A derived or converted row carrying this version's columns: the only rows checked strictly at read time. */
129
+ export const isCurrentDerived = (row) => isObject(row) && DERIVED_KINDS.includes(row.kind) && !isLegacyDerived(row)
130
+
131
+ /** An exact carrying keys beyond num and den: never normalized, never silently stripped, flagged on use. */
132
+ export const hasUnknownExactKeys = (exact) => isObject(exact) && isString(exact.num) && isString(exact.den) && Object.keys(exact).sort().join(",") !== "den,num"
133
+ /** Any integer pair dev's writer accepted, reduced: sign on num, no leading zeros, gcd 1; anything else as given. */
134
+ function readExact(exact) {
135
+ if (!isObject(exact) || hasUnknownExactKeys(exact) || !isString(exact.num) || !isString(exact.den) || !INTEGER.test(exact.num) || !/^\d+$/.test(exact.den) || /^0+$/.test(exact.den)) return exact
136
+ const { num, den } = mul({ num: BigInt(exact.num), den: BigInt(exact.den) }, 1n)
137
+ return { num: String(num), den: String(den) }
138
+ }
139
+
140
+ const readDecimal = (value) => (isDecimal(value) ? value.replace(/^(-?)0+(?=\d)/, "$1") : value)
141
+
142
+ /** A source unit read through the alias table; unknown, scaled, or unparseable text stays as given. */
143
+ export function sourceUnit(unit) {
144
+ try {
145
+ const canonical = canonicalUnit(unit)
146
+ return isString(canonical) && !inputScale(unit) ? canonical : unit
147
+ } catch (error) {
148
+ if (error instanceof RuleError) return unit
149
+ throw error
150
+ }
151
+ }
152
+
153
+ /**
154
+ * A row as every reader sees it (dimension 31 wins at read time): rows not written by derive in this
155
+ * version get reduced exacts and decimals; source rows also get canonical units and null later columns.
156
+ * Current derived rows stay as stored so tampering is still detected. Idempotent.
157
+ */
158
+ export function readRow(row) {
159
+ if (!isObject(row) || !Object.hasOwn(KIND_RULES, row.kind) || isCurrentDerived(row)) return row
160
+ const read = { ...row, value: readDecimal(row.value), exact: exactComesFromValue(row.kind) ? row.exact : readExact(row.exact) }
161
+ if (DERIVED_KINDS.includes(row.kind)) return read
162
+ read.unit = sourceUnit(row.unit)
163
+ for (const column of LATER_COLUMNS) if (!Object.hasOwn(read, column)) read[column] = null
164
+ return read
165
+ }
166
+
167
+ const carriesDerivation = (row) => row.operation !== null || row.operands?.length || row.derived_from?.length || row.formula !== null || row.factor !== null
168
+
169
+ /** Dimension 32: a stored exact must equal the row's own value at the value's decimal places. */
170
+ function valueExactDisagreement(row) {
171
+ const exact = readExact(row.exact)
172
+ const value = readDecimal(row.value)
173
+ if (!isRational(exact) || !isDecimal(value) || roundPlaces(toBig(exact), placesOf(value)) === value) return null
174
+ return `value ${value} disagrees with exact ${exact.num}/${exact.den} at ${placesOf(value)} decimal places`
175
+ }
176
+ function unitFlaw(unit) {
177
+ try {
178
+ return isString(unit) && (canonicalUnit(unit) !== unit || inputScale(unit)) ? `unit ${unit} has no canonical stored text` : null
179
+ } catch (error) {
180
+ if (error instanceof RuleError) return `unit ${unit}: ${error.message}`
181
+ throw error
182
+ }
183
+ }
184
+
185
+ /**
186
+ * What a loadable source row (read through readRow) still breaks under the strict dimensions: dev's
187
+ * writer accepted it, so it loads, but derive and recompute refuse it as an operand with this shape.
188
+ * addRow refuses a new row that has any. Empty for derived and converted rows.
189
+ */
190
+ export function readFlaws(row) {
191
+ if (!isObject(row) || DERIVED_KINDS.includes(row.kind)) return []
192
+ const flaws = []
193
+ if (carriesDerivation(row)) flaws.push({ shape: "dimension 1", detail: "a source row carries an operation or derivation fields", expected: "null operation and empty derivation fields on measured, quote, and assumed rows" })
194
+ const unit = unitFlaw(row.unit)
195
+ if (unit) flaws.push({ shape: "dimension 6", detail: unit, expected: "a registered or canonical unit without scale" })
196
+ for (const detail of metadataErrors(row)) flaws.push({ shape: "dimension 18", detail, expected: "present, type-valid metadata; explicit null when unknown" })
197
+ const unknownKeys = hasUnknownExactKeys(row.exact)
198
+ const disagreement = unknownKeys ? null : valueExactDisagreement(row)
199
+ if (unknownKeys) flaws.push({ shape: "dimension 7", detail: `exact has unknown keys: ${Object.keys(row.exact).filter((key) => key !== "num" && key !== "den").join(", ")}`, expected: "an exact with exactly num and den (the stored row is kept as written)" })
200
+ if (disagreement) flaws.push({ shape: "dimension 32", detail: disagreement, expected: "an exact equal to value at the value's decimal places" })
201
+ return flaws
202
+ }
203
+
204
+ /** @returns {string[]} every per-kind column error of one row; empty for an unknown kind. */
205
+ export function kindRuleErrors(candidate, at) {
206
+ const rules = KIND_RULES[candidate.kind]
207
+ if (rules === undefined) return []
208
+ const errors = []
209
+ const legacy = isLegacyDerived(candidate)
210
+ const columns = legacy ? PRE_REGISTRY_COLUMNS : isCurrentDerived(candidate) ? KIND_COLUMNS : KIND_COLUMNS.filter((column) => column !== "operation")
211
+ for (const column of columns) {
212
+ const value = candidate[column] === undefined && LATER_COLUMNS.includes(column) ? null : candidate[column]
213
+ const rule = RULES[legacy && column === "exact" ? "rational_or_null" : rules[column]]
214
+ // An exact with unknown keys loads as written (never stripped); readFlaws refuses it at use and at write.
215
+ if (column === "exact" && hasUnknownExactKeys(value) && !isCurrentDerived(candidate)) continue
216
+ if (rule.test(value, candidate)) continue
217
+ errors.push(`${at}.${column} must be ${rule.says} for a ${candidate.kind} row`)
218
+ if (rule.detail) errors.push(...rule.detail(value, `${at}.${column}`))
219
+ }
220
+ return errors
221
+ }
222
+
223
+ /** At-rest rules of current derived rows; source rows answer to readFlaws at use and at write instead. */
224
+ export function storedErrors(row) {
225
+ if (!isCurrentDerived(row)) return []
226
+ const errors = metadataErrors(row)
227
+ try {
228
+ if (canonicalUnit(row.unit) !== row.unit || inputScale(row.unit)) errors.push("unit must be canonical stored text")
229
+ } catch (error) { errors.push(error.message) }
230
+ return errors
231
+ }
232
+
233
+ export const KIND_TABLE_BEGIN = "<!-- kind-rules:begin (rendered from KIND_RULES; do not edit by hand) -->"
234
+ export const KIND_TABLE_END = "<!-- kind-rules:end -->"
235
+
236
+ /** The grammar's per-kind Markdown table, one line per column, ending in a newline. */
237
+ export function renderKindTable() {
238
+ const kinds = Object.keys(KIND_RULES)
239
+ const lines = [`| field | ${kinds.join(" | ")} |`, `|${"---|".repeat(kinds.length + 1)}`]
240
+ for (const column of KIND_COLUMNS) lines.push(`| \`${column}\` | ${kinds.map((kind) => RULES[KIND_RULES[kind][column]].cell).join(" | ")} |`)
241
+ return `${lines.join("\n")}\n`
242
+ }
243
+
244
+ if (isCliEntry(import.meta.url)) {
245
+ if (process.argv[2] === "--render-grammar" && process.argv.length === 3) process.stdout.write(renderKindTable())
246
+ else {
247
+ process.stderr.write("usage: node fact-ledger-kinds.mjs --render-grammar\n")
248
+ process.exitCode = 2
249
+ }
250
+ }