lazycodex-ai 5.1.44 → 5.1.46

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (412) hide show
  1. package/README.de.md +0 -5
  2. package/README.es.md +0 -5
  3. package/README.fr.md +0 -5
  4. package/README.it.md +0 -5
  5. package/README.ja.md +0 -5
  6. package/README.ko.md +0 -5
  7. package/README.md +4 -5
  8. package/README.pt-br.md +0 -5
  9. package/README.ru.md +0 -6
  10. package/README.zh-cn.md +0 -5
  11. package/dist/cli/index.js +865 -413
  12. package/dist/cli/service-auth/keystore.d.ts +4 -2
  13. package/dist/cli-node/index.js +865 -413
  14. package/docs/reference/web-terminal-visual-qa.md +8 -0
  15. package/package.json +1 -1
  16. package/packages/lsp-daemon/dist/cli.js +7 -7
  17. package/packages/lsp-daemon/dist/client.js +7 -7
  18. package/packages/lsp-daemon/dist/index.js +7 -7
  19. package/packages/lsp-daemon/package.json +3 -3
  20. package/packages/lsp-tools-mcp/dist/cli.js +7 -7
  21. package/packages/lsp-tools-mcp/dist/mcp.js +7 -7
  22. package/packages/lsp-tools-mcp/dist/tools.js +4 -4
  23. package/packages/lsp-tools-mcp/package.json +3 -3
  24. package/packages/omo-codex/plugin/.codex-plugin/plugin.json +1 -1
  25. package/packages/omo-codex/plugin/components/bootstrap/dist/cli.js +77 -26
  26. package/packages/omo-codex/plugin/components/bootstrap/hooks/hooks.json +1 -1
  27. package/packages/omo-codex/plugin/components/bootstrap/package.json +1 -1
  28. package/packages/omo-codex/plugin/components/bootstrap/src/hook.ts +1 -1
  29. package/packages/omo-codex/plugin/components/comment-checker/biome.json +1 -21
  30. package/packages/omo-codex/plugin/components/comment-checker/hooks/hooks.json +1 -1
  31. package/packages/omo-codex/plugin/components/comment-checker/package.json +4 -4
  32. package/packages/omo-codex/plugin/components/git-bash/hooks/hooks.json +2 -2
  33. package/packages/omo-codex/plugin/components/git-bash/package.json +1 -1
  34. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/biome.json +1 -21
  35. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/hooks/hooks.json +1 -1
  36. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/package.json +4 -4
  37. package/packages/omo-codex/plugin/components/lsp/biome.json +1 -21
  38. package/packages/omo-codex/plugin/components/lsp/dist/.omo-runtime-manifest.json +3 -3
  39. package/packages/omo-codex/plugin/components/lsp/dist/cli.js +4 -4
  40. package/packages/omo-codex/plugin/components/lsp/hooks/hooks.json +2 -2
  41. package/packages/omo-codex/plugin/components/lsp/package.json +4 -4
  42. package/packages/omo-codex/plugin/components/lsp/scripts/build-runtime.mjs +0 -1
  43. package/packages/omo-codex/plugin/components/lsp/test/codex-hook-not-installed.test.ts +1 -1
  44. package/packages/omo-codex/plugin/components/rules/biome.json +1 -21
  45. package/packages/omo-codex/plugin/components/rules/hooks/hooks.json +4 -4
  46. package/packages/omo-codex/plugin/components/rules/package.json +4 -4
  47. package/packages/omo-codex/plugin/components/teammode/hooks/hooks.json +1 -1
  48. package/packages/omo-codex/plugin/components/teammode/package.json +1 -1
  49. package/packages/omo-codex/plugin/components/telemetry/biome.json +1 -21
  50. package/packages/omo-codex/plugin/components/telemetry/dist/cli.js +90 -1
  51. package/packages/omo-codex/plugin/components/telemetry/dist/posthog.js +91 -1
  52. package/packages/omo-codex/plugin/components/telemetry/hooks/hooks.json +1 -1
  53. package/packages/omo-codex/plugin/components/telemetry/package.json +4 -4
  54. package/packages/omo-codex/plugin/components/ultrawork/agents/lazycodex-gate-reviewer.toml +2 -2
  55. package/packages/omo-codex/plugin/components/ultrawork/biome.json +1 -21
  56. package/packages/omo-codex/plugin/components/ultrawork/hooks/hooks.json +1 -1
  57. package/packages/omo-codex/plugin/components/ultrawork/package.json +4 -4
  58. package/packages/omo-codex/plugin/components/ulw-execute-continuation/biome.json +1 -21
  59. package/packages/omo-codex/plugin/components/ulw-execute-continuation/directive.md +1 -1
  60. package/packages/omo-codex/plugin/components/ulw-execute-continuation/hooks/hooks.json +1 -1
  61. package/packages/omo-codex/plugin/components/ulw-execute-continuation/package.json +4 -4
  62. package/packages/omo-codex/plugin/components/ulw-loop/biome.json +1 -21
  63. package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint.js +1 -1
  64. package/packages/omo-codex/plugin/components/ulw-loop/dist/cli.js +7 -7
  65. package/packages/omo-codex/plugin/components/ulw-loop/dist/codex-goal-instruction.js +5 -5
  66. package/packages/omo-codex/plugin/components/ulw-loop/dist/stop-resume-hook.js +1 -1
  67. package/packages/omo-codex/plugin/components/ulw-loop/hooks/hooks.json +5 -5
  68. package/packages/omo-codex/plugin/components/ulw-loop/package.json +4 -4
  69. package/packages/omo-codex/plugin/components/ulw-loop/src/checkpoint.ts +1 -1
  70. package/packages/omo-codex/plugin/components/ulw-loop/src/codex-goal-instruction.ts +5 -5
  71. package/packages/omo-codex/plugin/components/ulw-loop/src/stop-resume-hook.ts +1 -1
  72. package/packages/omo-codex/plugin/components/ulw-loop/test/fixtures/commit-mutations.mjs +2 -0
  73. package/packages/omo-codex/plugin/hooks/post-compact-resetting-git-bash-mcp-reminder.json +1 -1
  74. package/packages/omo-codex/plugin/hooks/post-compact-resetting-lsp-diagnostics-cache.json +1 -1
  75. package/packages/omo-codex/plugin/hooks/post-compact-resetting-project-rule-cache.json +1 -1
  76. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-comments.json +1 -1
  77. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-lsp-diagnostics.json +1 -1
  78. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-thread-title-hygiene.json +1 -1
  79. package/packages/omo-codex/plugin/hooks/post-tool-use-matching-project-rules.json +1 -1
  80. package/packages/omo-codex/plugin/hooks/post-tool-use-recording-spawn-admission.json +1 -1
  81. package/packages/omo-codex/plugin/hooks/pre-tool-use-enforcing-unlimited-goal-budget.json +1 -1
  82. package/packages/omo-codex/plugin/hooks/pre-tool-use-guarding-ulw-loop-spawns.json +1 -1
  83. package/packages/omo-codex/plugin/hooks/pre-tool-use-recommending-git-bash-mcp.json +1 -1
  84. package/packages/omo-codex/plugin/hooks/session-start-checking-auto-update.json +1 -1
  85. package/packages/omo-codex/plugin/hooks/session-start-checking-bootstrap-provisioning.json +1 -1
  86. package/packages/omo-codex/plugin/hooks/session-start-loading-project-rules.json +1 -1
  87. package/packages/omo-codex/plugin/hooks/session-start-recording-session-telemetry.json +1 -1
  88. package/packages/omo-codex/plugin/hooks/stop-checking-ulw-execute-continuation.json +1 -1
  89. package/packages/omo-codex/plugin/hooks/stop-checking-ulw-loop-resume.json +1 -1
  90. package/packages/omo-codex/plugin/hooks/subagent-stop-verifying-lazycodex-executor-evidence.json +1 -1
  91. package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ultrawork-trigger.json +1 -1
  92. package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ulw-loop-steering.json +1 -1
  93. package/packages/omo-codex/plugin/hooks/user-prompt-submit-loading-project-rules.json +1 -1
  94. package/packages/omo-codex/plugin/package-lock.json +12 -12
  95. package/packages/omo-codex/plugin/package.json +1 -1
  96. package/packages/omo-codex/plugin/scripts/sync-skills.mjs +14 -9
  97. package/packages/omo-codex/plugin/skills/ast-grep/AGENTS.md +1 -1
  98. package/packages/omo-codex/plugin/skills/browser/SKILL.md +21 -22
  99. package/packages/omo-codex/plugin/skills/browser/references/commands.md +2 -9
  100. package/packages/omo-codex/plugin/skills/browser/references/install.md +1 -2
  101. package/packages/omo-codex/plugin/skills/browser/runtime/omowright/manifest.json +1 -1
  102. package/packages/omo-codex/plugin/skills/browser/scripts/browser-doctor.mjs +2 -2
  103. package/packages/omo-codex/plugin/skills/browser/scripts/browser-engine-guard.mjs +63 -487
  104. package/packages/omo-codex/plugin/skills/browser/scripts/browser-install.mjs +1 -1
  105. package/packages/omo-codex/plugin/skills/browser/scripts/omowright.mjs +1 -10
  106. package/packages/omo-codex/plugin/skills/frontend/ATTRIBUTION.md +2 -2
  107. package/packages/omo-codex/plugin/skills/frontend/SKILL.md +79 -118
  108. package/packages/omo-codex/plugin/skills/frontend/references/design/README.md +75 -224
  109. package/packages/omo-codex/plugin/skills/frontend/references/design/stylegallery.md +32 -27
  110. package/packages/omo-codex/plugin/skills/frontend/references/perfection/README.md +2 -2
  111. package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/check-accent-stripes.ts +8 -24
  112. package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/check-draft-guard.ts +40 -0
  113. package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/collect-files.ts +53 -0
  114. package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/draft-guard/findings.ts +16 -0
  115. package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/draft-guard/lint.ts +38 -0
  116. package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/draft-guard/rules.ts +69 -0
  117. package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/draft-guard/text-nodes.ts +121 -0
  118. package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/fixtures/draft-guard/negative/attr-lookalikes.tsx +10 -0
  119. package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/fixtures/draft-guard/negative/bound-values.tsx +12 -0
  120. package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/fixtures/draft-guard/negative/const-generics.tsx +3 -0
  121. package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/fixtures/draft-guard/negative/generics.tsx +7 -0
  122. package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/fixtures/draft-guard/negative/measured-stats.html +6 -0
  123. package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/fixtures/draft-guard/negative/plain-copy.html +9 -0
  124. package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/fixtures/draft-guard/negative/state-copy.html +11 -0
  125. package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/fixtures/draft-guard/positive/attribute-edges.html +7 -0
  126. package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/fixtures/draft-guard/positive/components.tsx +16 -0
  127. package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/fixtures/draft-guard/positive/decorative-badge.vue +7 -0
  128. package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/fixtures/draft-guard/positive/emoji-icons.tsx +9 -0
  129. package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/fixtures/draft-guard/positive/jsx-edges.tsx +1 -0
  130. package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/fixtures/draft-guard/positive/promo-copy.html +6 -0
  131. package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/fixtures/draft-guard/positive/promo-copy.tsx +8 -0
  132. package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/fixtures/draft-guard/positive/vanity-stats.html +5 -0
  133. package/packages/omo-codex/plugin/skills/init-deep/SKILL.md +1 -1
  134. package/packages/omo-codex/plugin/skills/programming/SKILL.md +10 -7
  135. package/packages/omo-codex/plugin/skills/programming/references/typescript/README.md +10 -7
  136. package/packages/omo-codex/plugin/skills/programming/references/typescript/bootstrap.md +9 -23
  137. package/packages/omo-codex/plugin/skills/programming/references/typescript/oxc-lint.md +130 -0
  138. package/packages/omo-codex/plugin/skills/programming/references/typescript/tsconfig-strict.md +6 -62
  139. package/packages/omo-codex/plugin/skills/programming/scripts/typescript/check-no-excuse-rules.ts +12 -12
  140. package/packages/omo-codex/plugin/skills/programming/scripts/typescript/new-project.ts +20 -81
  141. package/packages/omo-codex/plugin/skills/programming/scripts/typescript/oxc/.oxfmtrc.json +9 -0
  142. package/packages/omo-codex/plugin/skills/programming/scripts/typescript/oxc/.oxlintrc.json +46 -0
  143. package/packages/omo-codex/plugin/skills/programming/scripts/typescript/oxc/lint/oxlint-plugin/LICENSE +21 -0
  144. package/packages/omo-codex/plugin/skills/programming/scripts/typescript/oxc/lint/oxlint-plugin/index.js +10 -0
  145. package/packages/omo-codex/plugin/skills/programming/scripts/typescript/oxc/lint/oxlint-plugin/no-em-dash-copy.js +28 -0
  146. package/packages/omo-codex/plugin/skills/programming/scripts/typescript/oxc/lint/oxlint-plugin/require-suppression-reason.js +62 -0
  147. package/packages/omo-codex/plugin/skills/programming/scripts/typescript/oxc/tsconfig.json +29 -0
  148. package/packages/omo-codex/plugin/skills/refactor/SKILL.md +14 -14
  149. package/packages/omo-codex/plugin/skills/remove-ai-slops/SKILL.md +1 -1
  150. package/packages/omo-codex/plugin/skills/review-work/SKILL.md +2 -2
  151. package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/AGENTS.md +1 -1
  152. package/packages/omo-codex/plugin/skills/ulw-execute/SKILL.md +2 -2
  153. package/packages/omo-codex/plugin/skills/ulw-loop/SKILL.md +1 -1
  154. package/packages/omo-codex/plugin/skills/ulw-research/AGENTS.md +21 -3
  155. package/packages/omo-codex/plugin/skills/ulw-research/SKILL.md +37 -12
  156. package/packages/omo-codex/plugin/skills/ulw-research/references/deliverable-phase.md +77 -16
  157. package/packages/omo-codex/plugin/skills/ulw-research/references/fact-ledger-grammar.md +134 -0
  158. package/packages/omo-codex/plugin/skills/ulw-research/references/fact-ledger.md +95 -0
  159. package/packages/omo-codex/plugin/skills/ulw-research/references/fidelity-claim-kinds.md +19 -0
  160. package/packages/omo-codex/plugin/skills/ulw-research/references/report-gates.md +68 -3
  161. package/packages/omo-codex/plugin/skills/ulw-research/references/report-visualization.md +2 -0
  162. package/packages/omo-codex/plugin/skills/ulw-research/references/unit-rules.md +157 -0
  163. package/packages/omo-codex/plugin/skills/ulw-research/scripts/claim-kinds.mjs +116 -0
  164. package/packages/omo-codex/plugin/skills/ulw-research/scripts/claims-index.mjs +125 -0
  165. package/packages/omo-codex/plugin/skills/ulw-research/scripts/cli-support.mjs +19 -5
  166. package/packages/omo-codex/plugin/skills/ulw-research/scripts/contracts.mjs +89 -3
  167. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-commands-meaning.mjs +191 -0
  168. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-commands-verify.mjs +164 -0
  169. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-commands.mjs +239 -0
  170. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-derive.mjs +66 -0
  171. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-expr.mjs +108 -0
  172. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-fill.mjs +194 -0
  173. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-ledger-kinds.mjs +250 -0
  174. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-ledger-schema.mjs +221 -0
  175. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-ledger.mjs +123 -0
  176. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-locate.mjs +231 -0
  177. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-pairs.mjs +187 -0
  178. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-recompute.mjs +89 -0
  179. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-binding.mjs +130 -0
  180. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-brief.mjs +150 -0
  181. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-claims.mjs +89 -0
  182. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-lexicon.mjs +145 -0
  183. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-records.mjs +163 -0
  184. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-state.mjs +64 -0
  185. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-trace.mjs +85 -0
  186. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-values.mjs +229 -0
  187. package/packages/omo-codex/plugin/skills/ulw-research/scripts/gates-fidelity.mjs +113 -0
  188. package/packages/omo-codex/plugin/skills/ulw-research/scripts/gates-static.mjs +29 -9
  189. package/packages/omo-codex/plugin/skills/ulw-research/scripts/gates-structure.mjs +1 -1
  190. package/packages/omo-codex/plugin/skills/ulw-research/scripts/gates-text.mjs +1 -27
  191. package/packages/omo-codex/plugin/skills/ulw-research/scripts/html-lite.mjs +2 -2
  192. package/packages/omo-codex/plugin/skills/ulw-research/scripts/html-markup.mjs +182 -0
  193. package/packages/omo-codex/plugin/skills/ulw-research/scripts/lexicon.mjs +47 -0
  194. package/packages/omo-codex/plugin/skills/ulw-research/scripts/md-lite.mjs +193 -0
  195. package/packages/omo-codex/plugin/skills/ulw-research/scripts/meaning-check.mjs +196 -0
  196. package/packages/omo-codex/plugin/skills/ulw-research/scripts/numerals.mjs +226 -0
  197. package/packages/omo-codex/plugin/skills/ulw-research/scripts/outcome.mjs +8 -8
  198. package/packages/omo-codex/plugin/skills/ulw-research/scripts/particles-ko.mjs +108 -0
  199. package/packages/omo-codex/plugin/skills/ulw-research/scripts/placeholder.mjs +184 -0
  200. package/packages/omo-codex/plugin/skills/ulw-research/scripts/polish-apply.mjs +115 -0
  201. package/packages/omo-codex/plugin/skills/ulw-research/scripts/polish-diff.mjs +31 -0
  202. package/packages/omo-codex/plugin/skills/ulw-research/scripts/polish-project.mjs +125 -0
  203. package/packages/omo-codex/plugin/skills/ulw-research/scripts/polish-rules.mjs +170 -0
  204. package/packages/omo-codex/plugin/skills/ulw-research/scripts/rational.mjs +139 -0
  205. package/packages/omo-codex/plugin/skills/ulw-research/scripts/report-tools-commands.mjs +21 -10
  206. package/packages/omo-codex/plugin/skills/ulw-research/scripts/report-tools.mjs +15 -0
  207. package/packages/omo-codex/plugin/skills/ulw-research/scripts/sentence-split.mjs +77 -0
  208. package/packages/omo-codex/plugin/skills/ulw-research/scripts/source-commands.mjs +140 -0
  209. package/packages/omo-codex/plugin/skills/ulw-research/scripts/source-snapshot.mjs +176 -0
  210. package/packages/omo-codex/plugin/skills/ulw-research/scripts/text-normalize.mjs +82 -0
  211. package/packages/omo-codex/plugin/skills/ulw-research/scripts/text-similarity.mjs +28 -0
  212. package/packages/omo-codex/plugin/skills/ulw-research/scripts/unit-algebra.mjs +101 -0
  213. package/packages/omo-codex/plugin/skills/ulw-research/scripts/unit-errors.mjs +23 -0
  214. package/packages/omo-codex/plugin/skills/ulw-research/scripts/unit-metadata.mjs +31 -0
  215. package/packages/omo-codex/plugin/skills/ulw-research/scripts/unit-pairs.mjs +72 -0
  216. package/packages/omo-codex/plugin/skills/ulw-research/scripts/unit-precision.mjs +44 -0
  217. package/packages/omo-codex/plugin/skills/ulw-research/scripts/unit-rules-shapes.mjs +64 -0
  218. package/packages/omo-codex/plugin/skills/ulw-research/scripts/unit-rules.mjs +173 -0
  219. package/packages/omo-codex/plugin/skills/ulw-research/scripts/units.mjs +105 -0
  220. package/packages/omo-codex/plugin/skills/visual-qa/SKILL.md +71 -64
  221. package/packages/omo-codex/plugin/test/component-bundled-cli.test.mjs +4 -1
  222. package/packages/omo-codex/plugin/test/sync-skills-codex-compatibility.test.mjs +19 -0
  223. package/packages/omo-codex/plugin/test/sync-skills-test-support.mjs +12 -8
  224. package/packages/omo-codex/scripts/install-dist/install-local.mjs +169 -30
  225. package/packages/shared-skills/skills/ast-grep/AGENTS.md +1 -1
  226. package/packages/shared-skills/skills/browser/SKILL.md +21 -22
  227. package/packages/shared-skills/skills/browser/references/commands.md +2 -9
  228. package/packages/shared-skills/skills/browser/references/install.md +1 -2
  229. package/packages/shared-skills/skills/browser/runtime/omowright/manifest.json +1 -1
  230. package/packages/shared-skills/skills/browser/scripts/browser-doctor.mjs +2 -2
  231. package/packages/shared-skills/skills/browser/scripts/browser-engine-guard.mjs +63 -487
  232. package/packages/shared-skills/skills/browser/scripts/browser-install.mjs +1 -1
  233. package/packages/shared-skills/skills/browser/scripts/omowright.mjs +1 -10
  234. package/packages/shared-skills/skills/frontend/ATTRIBUTION.md +2 -2
  235. package/packages/shared-skills/skills/frontend/SKILL.md +79 -118
  236. package/packages/shared-skills/skills/frontend/references/design/README.md +75 -224
  237. package/packages/shared-skills/skills/frontend/references/design/stylegallery.md +32 -27
  238. package/packages/shared-skills/skills/frontend/references/perfection/README.md +2 -2
  239. package/packages/shared-skills/skills/frontend/scripts/perfection/check-accent-stripes.ts +8 -24
  240. package/packages/shared-skills/skills/frontend/scripts/perfection/check-draft-guard.test.ts +101 -0
  241. package/packages/shared-skills/skills/frontend/scripts/perfection/check-draft-guard.ts +40 -0
  242. package/packages/shared-skills/skills/frontend/scripts/perfection/collect-files.test.ts +50 -0
  243. package/packages/shared-skills/skills/frontend/scripts/perfection/collect-files.ts +53 -0
  244. package/packages/shared-skills/skills/frontend/scripts/perfection/draft-guard/findings.ts +16 -0
  245. package/packages/shared-skills/skills/frontend/scripts/perfection/draft-guard/lint.ts +38 -0
  246. package/packages/shared-skills/skills/frontend/scripts/perfection/draft-guard/rules.ts +69 -0
  247. package/packages/shared-skills/skills/frontend/scripts/perfection/draft-guard/text-nodes.ts +121 -0
  248. package/packages/shared-skills/skills/frontend/scripts/perfection/fixtures/draft-guard/negative/attr-lookalikes.tsx +10 -0
  249. package/packages/shared-skills/skills/frontend/scripts/perfection/fixtures/draft-guard/negative/bound-values.tsx +12 -0
  250. package/packages/shared-skills/skills/frontend/scripts/perfection/fixtures/draft-guard/negative/const-generics.tsx +3 -0
  251. package/packages/shared-skills/skills/frontend/scripts/perfection/fixtures/draft-guard/negative/generics.tsx +7 -0
  252. package/packages/shared-skills/skills/frontend/scripts/perfection/fixtures/draft-guard/negative/measured-stats.html +6 -0
  253. package/packages/shared-skills/skills/frontend/scripts/perfection/fixtures/draft-guard/negative/plain-copy.html +9 -0
  254. package/packages/shared-skills/skills/frontend/scripts/perfection/fixtures/draft-guard/negative/state-copy.html +11 -0
  255. package/packages/shared-skills/skills/frontend/scripts/perfection/fixtures/draft-guard/positive/attribute-edges.html +7 -0
  256. package/packages/shared-skills/skills/frontend/scripts/perfection/fixtures/draft-guard/positive/components.tsx +16 -0
  257. package/packages/shared-skills/skills/frontend/scripts/perfection/fixtures/draft-guard/positive/decorative-badge.vue +7 -0
  258. package/packages/shared-skills/skills/frontend/scripts/perfection/fixtures/draft-guard/positive/emoji-icons.tsx +9 -0
  259. package/packages/shared-skills/skills/frontend/scripts/perfection/fixtures/draft-guard/positive/jsx-edges.tsx +1 -0
  260. package/packages/shared-skills/skills/frontend/scripts/perfection/fixtures/draft-guard/positive/promo-copy.html +6 -0
  261. package/packages/shared-skills/skills/frontend/scripts/perfection/fixtures/draft-guard/positive/promo-copy.tsx +8 -0
  262. package/packages/shared-skills/skills/frontend/scripts/perfection/fixtures/draft-guard/positive/vanity-stats.html +5 -0
  263. package/packages/shared-skills/skills/programming/SKILL.md +10 -7
  264. package/packages/shared-skills/skills/programming/references/typescript/README.md +10 -7
  265. package/packages/shared-skills/skills/programming/references/typescript/bootstrap.md +9 -23
  266. package/packages/shared-skills/skills/programming/references/typescript/oxc-lint.md +130 -0
  267. package/packages/shared-skills/skills/programming/references/typescript/tsconfig-strict.md +6 -62
  268. package/packages/shared-skills/skills/programming/scripts/typescript/check-no-excuse-rules.ts +12 -12
  269. package/packages/shared-skills/skills/programming/scripts/typescript/new-project.test.ts +162 -0
  270. package/packages/shared-skills/skills/programming/scripts/typescript/new-project.ts +20 -81
  271. package/packages/shared-skills/skills/programming/scripts/typescript/oxc/.oxfmtrc.json +9 -0
  272. package/packages/shared-skills/skills/programming/scripts/typescript/oxc/.oxlintrc.json +46 -0
  273. package/packages/shared-skills/skills/programming/scripts/typescript/oxc/lint/oxlint-plugin/LICENSE +21 -0
  274. package/packages/shared-skills/skills/programming/scripts/typescript/oxc/lint/oxlint-plugin/index.js +10 -0
  275. package/packages/shared-skills/skills/programming/scripts/typescript/oxc/lint/oxlint-plugin/no-em-dash-copy.js +28 -0
  276. package/packages/shared-skills/skills/programming/scripts/typescript/oxc/lint/oxlint-plugin/require-suppression-reason.js +62 -0
  277. package/packages/shared-skills/skills/programming/scripts/typescript/oxc/tsconfig.json +29 -0
  278. package/packages/shared-skills/skills/refactor/SKILL.md +13 -13
  279. package/packages/shared-skills/skills/ultimate-browsing/engine/AGENTS.md +1 -1
  280. package/packages/shared-skills/skills/ulw-execute/SKILL.md +1 -1
  281. package/packages/shared-skills/skills/ulw-research/AGENTS.md +21 -3
  282. package/packages/shared-skills/skills/ulw-research/SKILL.md +37 -12
  283. package/packages/shared-skills/skills/ulw-research/references/deliverable-phase.md +77 -16
  284. package/packages/shared-skills/skills/ulw-research/references/fact-ledger-grammar.md +134 -0
  285. package/packages/shared-skills/skills/ulw-research/references/fact-ledger.md +95 -0
  286. package/packages/shared-skills/skills/ulw-research/references/fidelity-claim-kinds.md +19 -0
  287. package/packages/shared-skills/skills/ulw-research/references/report-gates.md +68 -3
  288. package/packages/shared-skills/skills/ulw-research/references/report-visualization.md +2 -0
  289. package/packages/shared-skills/skills/ulw-research/references/unit-rules.md +157 -0
  290. package/packages/shared-skills/skills/ulw-research/scripts/claim-kinds.mjs +116 -0
  291. package/packages/shared-skills/skills/ulw-research/scripts/claim-kinds.test.ts +193 -0
  292. package/packages/shared-skills/skills/ulw-research/scripts/claims-index.mjs +125 -0
  293. package/packages/shared-skills/skills/ulw-research/scripts/claims-index.test.ts +149 -0
  294. package/packages/shared-skills/skills/ulw-research/scripts/cli-support.mjs +19 -5
  295. package/packages/shared-skills/skills/ulw-research/scripts/cli-support.test.ts +13 -2
  296. package/packages/shared-skills/skills/ulw-research/scripts/contracts.mjs +89 -3
  297. package/packages/shared-skills/skills/ulw-research/scripts/contracts.test.ts +100 -12
  298. package/packages/shared-skills/skills/ulw-research/scripts/copy-returns.test.ts +183 -0
  299. package/packages/shared-skills/skills/ulw-research/scripts/delivery-barrier.test.ts +245 -0
  300. package/packages/shared-skills/skills/ulw-research/scripts/fact-commands-meaning.mjs +191 -0
  301. package/packages/shared-skills/skills/ulw-research/scripts/fact-commands-verify.mjs +164 -0
  302. package/packages/shared-skills/skills/ulw-research/scripts/fact-commands.mjs +239 -0
  303. package/packages/shared-skills/skills/ulw-research/scripts/fact-derive.mjs +66 -0
  304. package/packages/shared-skills/skills/ulw-research/scripts/fact-derive.test.ts +375 -0
  305. package/packages/shared-skills/skills/ulw-research/scripts/fact-expr.mjs +108 -0
  306. package/packages/shared-skills/skills/ulw-research/scripts/fact-fill.mjs +194 -0
  307. package/packages/shared-skills/skills/ulw-research/scripts/fact-fill.test.ts +221 -0
  308. package/packages/shared-skills/skills/ulw-research/scripts/fact-ledger-grammar.test.ts +197 -0
  309. package/packages/shared-skills/skills/ulw-research/scripts/fact-ledger-kinds.mjs +250 -0
  310. package/packages/shared-skills/skills/ulw-research/scripts/fact-ledger-kinds.test.ts +156 -0
  311. package/packages/shared-skills/skills/ulw-research/scripts/fact-ledger-legacy.test.ts +160 -0
  312. package/packages/shared-skills/skills/ulw-research/scripts/fact-ledger-schema.mjs +221 -0
  313. package/packages/shared-skills/skills/ulw-research/scripts/fact-ledger-schema.test.ts +43 -0
  314. package/packages/shared-skills/skills/ulw-research/scripts/fact-ledger.mjs +123 -0
  315. package/packages/shared-skills/skills/ulw-research/scripts/fact-ledger.test.ts +254 -0
  316. package/packages/shared-skills/skills/ulw-research/scripts/fact-locate.mjs +231 -0
  317. package/packages/shared-skills/skills/ulw-research/scripts/fact-locate.test.ts +118 -0
  318. package/packages/shared-skills/skills/ulw-research/scripts/fact-pairs.mjs +187 -0
  319. package/packages/shared-skills/skills/ulw-research/scripts/fact-pairs.test.ts +167 -0
  320. package/packages/shared-skills/skills/ulw-research/scripts/fact-recompute.mjs +89 -0
  321. package/packages/shared-skills/skills/ulw-research/scripts/fact-unit-dimensions.test.ts +409 -0
  322. package/packages/shared-skills/skills/ulw-research/scripts/fact-unit-rules.test.ts +388 -0
  323. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-binding.mjs +130 -0
  324. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-binding.test.ts +81 -0
  325. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-brief.mjs +150 -0
  326. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-brief.test.ts +179 -0
  327. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-claims.mjs +89 -0
  328. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-lexicon.mjs +145 -0
  329. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-lexicon.test.ts +117 -0
  330. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-records.mjs +163 -0
  331. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-state.mjs +64 -0
  332. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-state.test.ts +46 -0
  333. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-trace.mjs +85 -0
  334. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-trace.test.ts +58 -0
  335. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-values.mjs +229 -0
  336. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-values.test.ts +270 -0
  337. package/packages/shared-skills/skills/ulw-research/scripts/gates-context.test.ts +64 -0
  338. package/packages/shared-skills/skills/ulw-research/scripts/gates-fidelity.mjs +113 -0
  339. package/packages/shared-skills/skills/ulw-research/scripts/gates-fidelity.test.ts +73 -0
  340. package/packages/shared-skills/skills/ulw-research/scripts/gates-static.mjs +29 -9
  341. package/packages/shared-skills/skills/ulw-research/scripts/gates-structure.mjs +1 -1
  342. package/packages/shared-skills/skills/ulw-research/scripts/gates-text.mjs +1 -27
  343. package/packages/shared-skills/skills/ulw-research/scripts/html-lite.mjs +2 -2
  344. package/packages/shared-skills/skills/ulw-research/scripts/html-markup.mjs +182 -0
  345. package/packages/shared-skills/skills/ulw-research/scripts/lexicon.mjs +47 -0
  346. package/packages/shared-skills/skills/ulw-research/scripts/lexicon.test.ts +57 -0
  347. package/packages/shared-skills/skills/ulw-research/scripts/md-lite.mjs +193 -0
  348. package/packages/shared-skills/skills/ulw-research/scripts/md-lite.test.ts +271 -0
  349. package/packages/shared-skills/skills/ulw-research/scripts/meaning-check.mjs +196 -0
  350. package/packages/shared-skills/skills/ulw-research/scripts/meaning-check.test.ts +228 -0
  351. package/packages/shared-skills/skills/ulw-research/scripts/numerals.mjs +226 -0
  352. package/packages/shared-skills/skills/ulw-research/scripts/numerals.test.ts +120 -0
  353. package/packages/shared-skills/skills/ulw-research/scripts/outcome.mjs +8 -8
  354. package/packages/shared-skills/skills/ulw-research/scripts/outcome.test.ts +29 -0
  355. package/packages/shared-skills/skills/ulw-research/scripts/particles-ko.mjs +108 -0
  356. package/packages/shared-skills/skills/ulw-research/scripts/particles-ko.test.ts +86 -0
  357. package/packages/shared-skills/skills/ulw-research/scripts/placeholder.mjs +184 -0
  358. package/packages/shared-skills/skills/ulw-research/scripts/placeholder.test.ts +77 -0
  359. package/packages/shared-skills/skills/ulw-research/scripts/polish-apply.mjs +115 -0
  360. package/packages/shared-skills/skills/ulw-research/scripts/polish-apply.test.ts +363 -0
  361. package/packages/shared-skills/skills/ulw-research/scripts/polish-diff.mjs +31 -0
  362. package/packages/shared-skills/skills/ulw-research/scripts/polish-diff.test.ts +168 -0
  363. package/packages/shared-skills/skills/ulw-research/scripts/polish-markup.test.ts +187 -0
  364. package/packages/shared-skills/skills/ulw-research/scripts/polish-project.mjs +125 -0
  365. package/packages/shared-skills/skills/ulw-research/scripts/polish-rules.mjs +170 -0
  366. package/packages/shared-skills/skills/ulw-research/scripts/rational.mjs +139 -0
  367. package/packages/shared-skills/skills/ulw-research/scripts/rational.test.ts +46 -0
  368. package/packages/shared-skills/skills/ulw-research/scripts/report-tools-commands.mjs +21 -10
  369. package/packages/shared-skills/skills/ulw-research/scripts/report-tools.mjs +15 -0
  370. package/packages/shared-skills/skills/ulw-research/scripts/report-tools.test.ts +458 -2
  371. package/packages/shared-skills/skills/ulw-research/scripts/sentence-split.mjs +77 -0
  372. package/packages/shared-skills/skills/ulw-research/scripts/sentence-split.test.ts +109 -0
  373. package/packages/shared-skills/skills/ulw-research/scripts/source-commands.mjs +140 -0
  374. package/packages/shared-skills/skills/ulw-research/scripts/source-snapshot.mjs +176 -0
  375. package/packages/shared-skills/skills/ulw-research/scripts/source-snapshot.test.ts +113 -0
  376. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/assets/share.csv +3 -0
  377. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/claim-graph.md +13 -0
  378. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/gen-legacy-dev.mjs +89 -0
  379. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/ledger-basic.json +223 -0
  380. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/ledger-legacy-dev.json +859 -0
  381. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/ledger-verify.json +420 -0
  382. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/report-bad.html +19 -0
  383. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/report-good.html +39 -0
  384. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/sources/S4/acronyms.json +3 -0
  385. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/sources/S4/meta.json +9 -0
  386. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/sources/S4/sentences.jsonl +7 -0
  387. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/sources/S4/text.txt +7 -0
  388. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/sources/S4.html +19 -0
  389. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/sources/S5/acronyms.json +1 -0
  390. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/sources/S5/meta.json +9 -0
  391. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/sources/S5/sentences.jsonl +1 -0
  392. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/sources/S5/text.txt +1 -0
  393. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/sources/S9.txt +1 -0
  394. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity-brief-locked.md +17 -0
  395. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity-brief-pending.md +17 -0
  396. package/packages/shared-skills/skills/ulw-research/scripts/text-normalize.mjs +82 -0
  397. package/packages/shared-skills/skills/ulw-research/scripts/text-normalize.test.ts +73 -0
  398. package/packages/shared-skills/skills/ulw-research/scripts/text-similarity.mjs +28 -0
  399. package/packages/shared-skills/skills/ulw-research/scripts/text-similarity.test.ts +38 -0
  400. package/packages/shared-skills/skills/ulw-research/scripts/unit-algebra.mjs +101 -0
  401. package/packages/shared-skills/skills/ulw-research/scripts/unit-errors.mjs +23 -0
  402. package/packages/shared-skills/skills/ulw-research/scripts/unit-metadata.mjs +31 -0
  403. package/packages/shared-skills/skills/ulw-research/scripts/unit-pairs.mjs +72 -0
  404. package/packages/shared-skills/skills/ulw-research/scripts/unit-precision.mjs +44 -0
  405. package/packages/shared-skills/skills/ulw-research/scripts/unit-rules-shapes.mjs +64 -0
  406. package/packages/shared-skills/skills/ulw-research/scripts/unit-rules.mjs +173 -0
  407. package/packages/shared-skills/skills/ulw-research/scripts/units.mjs +105 -0
  408. package/packages/shared-skills/skills/ulw-research/scripts/units.test.ts +83 -0
  409. package/packages/shared-skills/skills/visual-qa/SKILL.md +70 -63
  410. package/packages/shared-skills/skills/visual-qa/scripts/skill-doc-fields.test.ts +34 -0
  411. package/script/qa/web-terminal-visual-qa.mjs +6 -2
  412. package/script/qa/xterm-live-terminal.mjs +26 -3
@@ -0,0 +1,221 @@
1
+ // fact-ledger-schema.mjs - the fact ledger's vocabulary and validation: row fields and defaults,
2
+ // kinds, statuses, qualifiers, and validateLedger over rows, status events, and their history.
3
+ // The grammar it encodes is references/fact-ledger-grammar.md; every per-kind column rule lives in
4
+ // KIND_RULES (fact-ledger-kinds.mjs), never here.
5
+ // Pure functions with no IO; fact-ledger.mjs owns reading, writing, and row changes. node: builtins only.
6
+
7
+ import { DECIMAL, isCount, isCurrentDerived, isNonEmpty, isObject, isString, kindRuleErrors, LATER_COLUMNS, readRow, storedErrors } from "./fact-ledger-kinds.mjs"
8
+ import { malformed } from "./unit-rules.mjs"
9
+
10
+ export { DECIMAL, isNonEmpty, isString } from "./fact-ledger-kinds.mjs"
11
+
12
+ export const LEDGER_SCHEMA_VERSION = 1
13
+ export const FACT_KINDS = Object.freeze(["measured", "quote", "derived", "assumed", "converted"])
14
+ export const FACT_STATUSES = Object.freeze(["active", "superseded", "retracted"])
15
+ export const OPERATIONS = Object.freeze(["sum", "diff", "growth", "ratio", "share", "convert", "expr"])
16
+ /** Canonical qualifier terms; surface forms in either language map onto these. */
17
+ export const QUALIFIERS = Object.freeze([
18
+ "upper_inclusive",
19
+ "lower_inclusive",
20
+ "approximate",
21
+ "near",
22
+ "lower_exclusive",
23
+ "upper_exclusive",
24
+ ])
25
+
26
+ export const ROW_DEFAULTS = Object.freeze({
27
+ value: null,
28
+ unit: null,
29
+ value_span: null,
30
+ source_id: null,
31
+ locator: null,
32
+ locate_hint: null,
33
+ snapshot_sha256: null,
34
+ valid_at: null,
35
+ qualifier: null,
36
+ entity: null,
37
+ sentence_id: null,
38
+ observation_id: null,
39
+ fuzzy: false,
40
+ outside_floor: false,
41
+ population: null,
42
+ missing_rule: null,
43
+ accounting_basis: null,
44
+ snapshot_window: null,
45
+ revision: null,
46
+ exact: null,
47
+ render: null,
48
+ operation: null,
49
+ operands: [],
50
+ derived_from: [],
51
+ formula: null,
52
+ factor: null,
53
+ rationale: null,
54
+ context: null,
55
+ precision: null,
56
+ compatibility: null,
57
+ status: "active",
58
+ })
59
+ /** Fields added after schema version 1 shipped: written on every new row, optional when reading an older ledger. */
60
+ const OPTIONAL_FIELDS = new Set(LATER_COLUMNS)
61
+ const REQUIRED = ["fact_id", "kind", "raw", "quote", "extracted_by", "observed_at", "label"]
62
+ export const ROW_FIELDS = Object.freeze([...REQUIRED, ...Object.keys(ROW_DEFAULTS)])
63
+
64
+ const ISO_DATE = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$/
65
+ const LABEL = /^[a-z0-9_-]{1,32}$/
66
+ const FACT_REF = /^[FQ]\d+$/
67
+
68
+ function check(errors, condition, message) {
69
+ if (!condition) errors.push(message)
70
+ }
71
+
72
+ function nullOr(value, test) {
73
+ return value === null || test(value)
74
+ }
75
+
76
+ function validateRow(row, index, errors) {
77
+ const at = `rows[${index}]`
78
+ if (!isObject(row)) return errors.push(`${at} must be an object`)
79
+ for (const key of ROW_FIELDS) check(errors, OPTIONAL_FIELDS.has(key) || Object.hasOwn(row, key), `${at}.${key} is required`)
80
+ for (const key of Object.keys(row)) check(errors, ROW_FIELDS.includes(key), `${at}.${key} is not a ledger field`)
81
+ check(errors, FACT_KINDS.includes(row.kind), `${at}.kind must be one of ${FACT_KINDS.join(", ")}`)
82
+ // Dimension 31 wins at read time: per-kind rules see the row as readRow normalizes it.
83
+ errors.push(...kindRuleErrors(readRow(row), at))
84
+ errors.push(...storedErrors(row).map((e) => `${at}.${e}`))
85
+ if (isCurrentDerived(row)) {
86
+ const problem = malformed(row)
87
+ if (problem) errors.push(`${at}: ${problem}`)
88
+ }
89
+ check(errors, row.unit === undefined || nullOr(row.unit, isNonEmpty), `${at}.unit must be null or a non-empty token`)
90
+ for (const key of ["raw", "quote"]) check(errors, row[key] === undefined || isString(row[key]), `${at}.${key} must be a string`)
91
+ check(errors, row.extracted_by === undefined || isNonEmpty(row.extracted_by), `${at}.extracted_by must be a non-empty string`)
92
+ check(errors, row.observed_at === undefined || (isString(row.observed_at) && ISO_DATE.test(row.observed_at)), `${at}.observed_at must be an ISO timestamp`)
93
+ check(errors, row.label === undefined || (isString(row.label) && LABEL.test(row.label)), `${at}.label must match [a-z0-9_-]{1,32}`)
94
+ const span = row.value_span
95
+ check(errors, span === undefined || nullOr(span, (v) => Array.isArray(v) && v.length === 2 && v.every(isCount) && v[0] <= v[1]), `${at}.value_span must be null or [start, end]`)
96
+ for (const key of ["locate_hint", "valid_at", "entity", "observation_id", "missing_rule", "accounting_basis", "formula", "render"]) {
97
+ if (row[key] !== undefined) check(errors, nullOr(row[key], isString), `${at}.${key} must be null or a string`)
98
+ }
99
+ for (const key of ["population", "snapshot_window", "revision"]) {
100
+ if (row[key] !== undefined) check(errors, nullOr(row[key], (v) => isString(v) || isObject(v)), `${at}.${key} must be null, a string, or an object`)
101
+ }
102
+ if (row.qualifier !== undefined) check(errors, nullOr(row.qualifier, (v) => QUALIFIERS.includes(v)), `${at}.qualifier must be null or one of ${QUALIFIERS.join(", ")}`)
103
+ if (row.sentence_id !== undefined) check(errors, nullOr(row.sentence_id, (v) => isString(v) && /^S\d+:s\d+$/.test(v)), `${at}.sentence_id must be null or S<n>:s<k>`)
104
+ for (const key of ["fuzzy", "outside_floor"]) {
105
+ if (row[key] !== undefined) check(errors, typeof row[key] === "boolean", `${at}.${key} must be a boolean`)
106
+ }
107
+ if (row.operation !== undefined) check(errors, nullOr(row.operation, (v) => OPERATIONS.includes(v)), `${at}.operation must be null or one of ${OPERATIONS.join(", ")}`)
108
+ for (const key of ["operands", "derived_from"]) {
109
+ if (row[key] !== undefined) check(errors, Array.isArray(row[key]) && row[key].every((v) => isString(v) && FACT_REF.test(v)), `${at}.${key} must be an array of fact ids`)
110
+ }
111
+ if (row.factor !== undefined) check(errors, nullOr(row.factor, (v) => isString(v) && DECIMAL.test(v)), `${at}.factor must be null or a decimal string`)
112
+ check(errors, row.status === undefined || FACT_STATUSES.includes(row.status), `${at}.status must be one of ${FACT_STATUSES.join(", ")}`)
113
+ }
114
+
115
+ export function rowErrors(row) {
116
+ const errors = []
117
+ validateRow(row, 0, errors)
118
+ return errors
119
+ }
120
+
121
+ // The status machine and event rule of references/fact-ledger-grammar.md. validateLedger replays
122
+ // events through transitionError, and every writer in fact-ledger.mjs calls it before appending.
123
+ export const TRANSITIONS = Object.freeze({ active: Object.freeze(["retracted", "superseded"]), retracted: Object.freeze([]), superseded: Object.freeze([]) })
124
+ const EVENT_FIELDS = Object.freeze(["fact_id", "from", "to", "replacement", "reason"])
125
+
126
+ /**
127
+ * @param {Map<string, string>} statuses each fact's status at the event's position
128
+ * @returns {string | null} why the event is not a legal transition, or null
129
+ */
130
+ export function transitionError(statuses, event) {
131
+ if (!isObject(event)) return "an event must be an object"
132
+ const id = event.fact_id
133
+ if (!statuses.has(id)) return `no ledger row ${id}`
134
+ if (EVENT_FIELDS.some((key) => !Object.hasOwn(event, key)) || Object.keys(event).some((key) => !EVENT_FIELDS.includes(key))) {
135
+ return `${id}: an event has exactly the fields ${EVENT_FIELDS.join(", ")}`
136
+ }
137
+ const current = statuses.get(id)
138
+ if (event.from !== current) return `${id} is ${current}, but the event moves it from ${event.from}`
139
+ if (!TRANSITIONS[current].includes(event.to)) return `${id} cannot move from ${current} to ${event.to}`
140
+ if (event.to === "superseded") {
141
+ if (event.replacement === null) return `supersede ${id} needs a replacement row; use retract for a fact with no replacement`
142
+ if (event.replacement === id) return `${id} cannot be its own replacement`
143
+ if (!statuses.has(event.replacement)) return `no replacement row ${event.replacement} for ${id}`
144
+ if (statuses.get(event.replacement) !== "active") return `replacement row ${event.replacement} is ${statuses.get(event.replacement)} (superseding ${id})`
145
+ } else if (event.replacement !== null) return `${id} is ${event.to} but names replacement ${event.replacement}`
146
+ if (!isNonEmpty(event.reason)) return `a reason is required to mark ${id} ${event.to}`
147
+ return null
148
+ }
149
+
150
+ /** Replay every event from all-active; each row's status must be where its event chain leaves it. */
151
+ function validateHistory(ledger, errors) {
152
+ const statuses = new Map(ledger.rows.filter(isObject).map((row) => [row.fact_id, "active"]))
153
+ const counts = new Map()
154
+ ledger.events.forEach((event, index) => {
155
+ const problem = transitionError(statuses, event)
156
+ if (problem) errors.push(`events[${index}]: ${problem}`)
157
+ if (!isObject(event) || !statuses.has(event.fact_id) || !FACT_STATUSES.includes(event.to)) return
158
+ statuses.set(event.fact_id, event.to)
159
+ counts.set(event.fact_id, (counts.get(event.fact_id) ?? 0) + 1)
160
+ })
161
+ ledger.rows.forEach((row, index) => {
162
+ if (!isObject(row) || !FACT_STATUSES.includes(row.status)) return
163
+ const to = statuses.get(row.fact_id)
164
+ if (!counts.has(row.fact_id)) check(errors, row.status === "active", `rows[${index}]: ${row.fact_id} is ${row.status} but has no status event`)
165
+ else check(errors, to === row.status, `rows[${index}]: ${row.fact_id} has status ${row.status} but its last event moves it to ${to}`)
166
+ })
167
+ }
168
+
169
+ /** operands and derived_from name existing rows, never the row itself, and close no cycle. */
170
+ export function provenanceErrors(rows) {
171
+ const errors = []
172
+ const refs = new Map()
173
+ for (const row of rows) if (isObject(row)) refs.set(row.fact_id, [row.operands, row.derived_from].flatMap((list) => (Array.isArray(list) ? list : [])))
174
+ rows.forEach((row, index) => {
175
+ for (const key of ["operands", "derived_from"]) {
176
+ if (!isObject(row) || !Array.isArray(row[key])) continue
177
+ for (const ref of row[key]) {
178
+ if (ref === row.fact_id) errors.push(`rows[${index}].${key}: ${ref} references itself`)
179
+ else check(errors, refs.has(ref), `rows[${index}].${key}: ${row.fact_id} references ${ref}, which has no ledger row`)
180
+ }
181
+ }
182
+ })
183
+ const state = new Map()
184
+ for (const root of refs.keys()) {
185
+ const stack = [{ id: root, finish: false }]
186
+ const path = []
187
+ while (stack.length) {
188
+ const { id, finish } = stack.pop()
189
+ if (state.get(id) === "done") continue
190
+ if (finish) { state.set(id, "done"); path.pop(); continue }
191
+ if (state.get(id) === "open") return [...errors, `${id} derives from itself through cycle ${[...path, id].join(" -> ")}`]
192
+ state.set(id, "open")
193
+ path.push(id)
194
+ stack.push({ id, finish: true })
195
+ for (const next of refs.get(id)) if (next !== id && refs.has(next)) stack.push({ id: next, finish: false })
196
+ }
197
+ }
198
+ return errors
199
+ }
200
+
201
+ /** @returns {string[]} every schema error; empty when the ledger is valid. */
202
+ export function validateLedger(ledger) {
203
+ const errors = []
204
+ if (!isObject(ledger)) return ["ledger must be an object"]
205
+ check(errors, ledger.schemaVersion === LEDGER_SCHEMA_VERSION, `schemaVersion must be ${LEDGER_SCHEMA_VERSION}`)
206
+ if (!Array.isArray(ledger.rows)) return [...errors, "rows must be an array"]
207
+ ledger.rows.forEach((row, index) => validateRow(row, index, errors))
208
+ const ids = new Set()
209
+ const labels = new Set()
210
+ ledger.rows.forEach((row, index) => {
211
+ if (!isObject(row)) return
212
+ check(errors, !ids.has(row.fact_id), `rows[${index}].fact_id ${row.fact_id} is a duplicate`)
213
+ check(errors, row.label === undefined || !labels.has(row.label), `rows[${index}]: ${row.fact_id} has duplicate label ${row.label}`)
214
+ ids.add(row.fact_id)
215
+ labels.add(row.label)
216
+ })
217
+ errors.push(...provenanceErrors(ledger.rows))
218
+ if (Array.isArray(ledger.events)) validateHistory(ledger, errors)
219
+ else errors.push("events must be an array")
220
+ return errors
221
+ }
@@ -0,0 +1,43 @@
1
+ import { describe, expect, test } from "bun:test"
2
+
3
+ import { LEDGER_SCHEMA_VERSION, ROW_DEFAULTS, validateLedger } from "./fact-ledger-schema.mjs"
4
+
5
+ function measuredRow(factId: string, label: string) {
6
+ return {
7
+ ...structuredClone(ROW_DEFAULTS),
8
+ fact_id: factId,
9
+ kind: "measured",
10
+ value: "320",
11
+ unit: "m",
12
+ raw: "320 m",
13
+ quote: "The span is 320 m.",
14
+ value_span: [12, 15],
15
+ source_id: "S1",
16
+ locator: { artifact: "sources/S1/text.txt", line: 3, char_start: 40, char_end: 62 },
17
+ snapshot_sha256: "a".repeat(64),
18
+ extracted_by: "fact add --find",
19
+ observed_at: "2026-10-23T08:00:00Z",
20
+ label,
21
+ exact: { num: "320", den: "1" },
22
+ }
23
+ }
24
+
25
+ function ledgerOf(...rows: object[]) {
26
+ return { schemaVersion: LEDGER_SCHEMA_VERSION, rows, events: [] }
27
+ }
28
+
29
+ describe("validateLedger (schema module)", () => {
30
+ test("given a complete measured row, when validated, then there are no errors", () => {
31
+ expect(validateLedger(ledgerOf(measuredRow("F1", "span")))).toEqual([])
32
+ })
33
+
34
+ test("given two rows with one label, when validated, then the duplicate label is reported", () => {
35
+ const errors = validateLedger(ledgerOf(measuredRow("F1", "span"), measuredRow("F2", "span")))
36
+ expect(errors.some((e: string) => e.includes("duplicate label"))).toBe(true)
37
+ })
38
+
39
+ test("given an assumed row without rationale, when validated, then rationale is required", () => {
40
+ const errors = validateLedger(ledgerOf({ ...measuredRow("F1", "span"), kind: "assumed" }))
41
+ expect(errors.some((e: string) => e.includes("rationale"))).toBe(true)
42
+ })
43
+ })
@@ -0,0 +1,123 @@
1
+ // fact-ledger.mjs - the verbatim fact ledger: one row per measured, quoted, derived, assumed,
2
+ // or converted value, with its source locator and exact rational. Rows are never deleted:
3
+ // retract and supersede change status and append an event. Validation and the field vocabulary
4
+ // live in fact-ledger-schema.mjs and are re-exported here. node: builtins only.
5
+
6
+ import { existsSync } from "node:fs"
7
+ import { join } from "node:path"
8
+
9
+ import { CliError, readJsonFile, writeJsonAtomic } from "./cli-support.mjs"
10
+ import { DECIMAL, exactComesFromValue, exactFromDecimal, idPrefix, isString, readFlaws, readRow } from "./fact-ledger-kinds.mjs"
11
+ import { LEDGER_SCHEMA_VERSION, ROW_DEFAULTS, ROW_FIELDS, transitionError, validateLedger } from "./fact-ledger-schema.mjs"
12
+ import { normalizeInput } from "./unit-algebra.mjs"
13
+
14
+ export { exactFromDecimal } from "./fact-ledger-kinds.mjs"
15
+ export { FACT_KINDS, FACT_STATUSES, LEDGER_SCHEMA_VERSION, OPERATIONS, QUALIFIERS, ROW_FIELDS, TRANSITIONS, transitionError, validateLedger } from "./fact-ledger-schema.mjs"
16
+
17
+ export const LEDGER_FILENAME = "fact-ledger.json"
18
+
19
+ export function emptyLedger() {
20
+ return { schemaVersion: LEDGER_SCHEMA_VERSION, rows: [], events: [] }
21
+ }
22
+
23
+ function assertValid(ledger) {
24
+ const errors = validateLedger(ledger)
25
+ if (errors.length > 0) throw new CliError(`invalid fact ledger: ${errors.join("; ")}`)
26
+ }
27
+
28
+ /** The row's fields in ROW_FIELDS order, then any unknown field (which validation reports). */
29
+ function ordered(row) {
30
+ const out = Object.fromEntries(ROW_FIELDS.filter((key) => Object.hasOwn(row, key)).map((key) => [key, row[key]]))
31
+ for (const key of Object.keys(row)) if (!ROW_FIELDS.includes(key)) out[key] = row[key]
32
+ return out
33
+ }
34
+
35
+ /**
36
+ * Every row comes back as readRow normalizes it (reduced exacts and decimals; source rows with canonical
37
+ * units and null later columns) before any strict rule runs; the file changes on the next write.
38
+ */
39
+ export function readLedger(dir) {
40
+ const file = join(dir, LEDGER_FILENAME)
41
+ if (!existsSync(file)) return emptyLedger()
42
+ const ledger = readJsonFile(file)
43
+ assertValid(ledger)
44
+ ledger.rows = ledger.rows.map((row) => ordered(readRow(row)))
45
+ assertValid(ledger)
46
+ return ledger
47
+ }
48
+
49
+ export function writeLedger(dir, ledger) {
50
+ assertValid(ledger)
51
+ const file = join(dir, LEDGER_FILENAME)
52
+ writeJsonAtomic(file, ledger)
53
+ return file
54
+ }
55
+
56
+ const ID_NUMBER = Object.freeze({ F: /^F(\d+)$/, Q: /^Q(\d+)$/ })
57
+
58
+ /** The next id with the kind's prefix from KIND_RULES (F<n> for value rows, Q<n> for quotes). */
59
+ export function nextId(ledger, kind) {
60
+ const prefix = idPrefix(kind)
61
+ let max = 0n
62
+ for (const row of ledger.rows) {
63
+ const match = ID_NUMBER[prefix].exec(row.fact_id ?? "")
64
+ if (match && BigInt(match[1]) > max) max = BigInt(match[1])
65
+ }
66
+ return `${prefix}${max + 1n}`
67
+ }
68
+
69
+ /**
70
+ * Fill defaults, assign the next id, validate the whole ledger (provenance included), then append.
71
+ * Only kinds whose KIND_RULES exact is from_value (measured) get exact from their decimal:
72
+ * derived and converted rows carry the exact that derive computed from the operands and factor,
73
+ * never one read back from the rounded value.
74
+ */
75
+ export function addRow(ledger, row) {
76
+ const filled = { ...structuredClone(ROW_DEFAULTS), ...row, fact_id: row.fact_id ?? nextId(ledger, row.kind) }
77
+ if (!["derived", "converted"].includes(filled.kind)) {
78
+ const normalized = normalizeInput(filled.unit, filled.value)
79
+ if (normalized.value !== filled.value && filled.exact !== null) throw new CliError("scaled input must omit exact; it is computed after normalization")
80
+ Object.assign(filled, readRow({ ...filled, ...normalized }))
81
+ }
82
+ if (exactComesFromValue(filled.kind) && filled.exact === null && isString(filled.value) && DECIMAL.test(filled.value)) {
83
+ filled.exact = exactFromDecimal(filled.value)
84
+ }
85
+ const added = ordered(filled)
86
+ const flaws = readFlaws(added)
87
+ if (flaws.length > 0) throw new CliError(`invalid fact ledger: ${flaws.map((flaw) => `${flaw.shape}: ${flaw.detail}`).join("; ")}`)
88
+ assertValid({ ...ledger, rows: [...ledger.rows, added] })
89
+ ledger.rows.push(added)
90
+ return added
91
+ }
92
+
93
+ /** Build the event from the row's current status and append it only if transitionError allows it. */
94
+ function changeStatus(ledger, id, to, replacement, reason) {
95
+ const row = ledger.rows.find((candidate) => candidate.fact_id === id)
96
+ const event = { fact_id: id, from: row?.status ?? null, to, replacement: replacement ?? null, reason }
97
+ const problem = transitionError(new Map(ledger.rows.map((candidate) => [candidate.fact_id, candidate.status])), event)
98
+ if (problem) throw new CliError(problem)
99
+ ledger.events.push(event)
100
+ row.status = to
101
+ return row
102
+ }
103
+
104
+ export function retractRow(ledger, id, reason) {
105
+ return changeStatus(ledger, id, "retracted", null, reason)
106
+ }
107
+
108
+ export function supersedeRow(ledger, id, replacementId, reason) {
109
+ return changeStatus(ledger, id, "superseded", replacementId, reason)
110
+ }
111
+
112
+ function cell(value) {
113
+ return value === null || value === undefined ? "" : String(value).replace(/\|/g, "\\|").replace(/\n/g, " ")
114
+ }
115
+
116
+ /** The `--view` table: one line per row, retracted and superseded rows included. */
117
+ export function renderLedgerMarkdown(ledger) {
118
+ const lines = ["| fact_id | kind | label | value | unit | source | status | raw |", "|---|---|---|---|---|---|---|---|"]
119
+ for (const row of ledger.rows) {
120
+ lines.push(`| ${[row.fact_id, row.kind, row.label, row.value, row.unit, row.source_id, row.status, row.raw].map(cell).join(" | ")} |`)
121
+ }
122
+ return `${lines.join("\n")}\n`
123
+ }
@@ -0,0 +1,254 @@
1
+ import { describe, expect, onTestFinished, test } from "bun:test"
2
+ import { mkdtempSync, readFileSync, readdirSync, rmSync } from "node:fs"
3
+ import { tmpdir } from "node:os"
4
+ import { join } from "node:path"
5
+
6
+ import {
7
+ LEDGER_FILENAME,
8
+ LEDGER_SCHEMA_VERSION,
9
+ addRow,
10
+ emptyLedger,
11
+ nextId,
12
+ readLedger,
13
+ renderLedgerMarkdown,
14
+ retractRow,
15
+ supersedeRow,
16
+ validateLedger,
17
+ writeLedger,
18
+ } from "./fact-ledger.mjs"
19
+
20
+ const HASH = "a".repeat(64)
21
+
22
+ function tempDir(): string {
23
+ const dir = mkdtempSync(join(tmpdir(), "ulw-ledger-"))
24
+ onTestFinished(() => rmSync(dir, { recursive: true, force: true }))
25
+ return dir
26
+ }
27
+
28
+ function measured(label: string, value = "320") {
29
+ return {
30
+ kind: "measured",
31
+ value,
32
+ unit: "m",
33
+ raw: `${value} m`,
34
+ quote: `The span is ${value} m.`,
35
+ value_span: [12, 12 + value.length],
36
+ source_id: "S1",
37
+ locator: { artifact: "sources/S1/text.txt", line: 3, char_start: 40, char_end: 62 },
38
+ snapshot_sha256: HASH,
39
+ extracted_by: "fact add --find",
40
+ observed_at: "2026-10-23T08:00:00Z",
41
+ label,
42
+ }
43
+ }
44
+
45
+ function quoteRow(label: string) {
46
+ return { ...measured(label), kind: "quote", value: null, unit: null, value_span: null }
47
+ }
48
+
49
+ function ledgerWith(...rows: object[]) {
50
+ const ledger = emptyLedger()
51
+ for (const row of rows) addRow(ledger, row)
52
+ return ledger
53
+ }
54
+
55
+ describe("validateLedger", () => {
56
+ test("accepts a ledger built by addRow", () => {
57
+ expect(validateLedger(ledgerWith(measured("span"), quoteRow("claim")))).toEqual([])
58
+ })
59
+
60
+ test("rejects a row missing label", () => {
61
+ const ledger = ledgerWith(measured("span"))
62
+ delete (ledger.rows[0] as Record<string, unknown>).label
63
+ expect(validateLedger(ledger).some((e: string) => e.includes("label"))).toBe(true)
64
+ })
65
+
66
+ test("rejects a duplicate label", () => {
67
+ const ledger = ledgerWith(measured("span"))
68
+ ledger.rows.push({ ...ledger.rows[0], fact_id: "F2" })
69
+ expect(validateLedger(ledger).some((e: string) => e.includes("duplicate label"))).toBe(true)
70
+ })
71
+
72
+ test("rejects an assumed row without rationale", () => {
73
+ const ledger = ledgerWith(measured("span"))
74
+ ledger.rows[0].kind = "assumed"
75
+ expect(validateLedger(ledger).some((e: string) => e.includes("rationale"))).toBe(true)
76
+ })
77
+
78
+ test("rejects a value row with a non-decimal value", () => {
79
+ const ledger = ledgerWith(measured("span"))
80
+ ledger.rows[0].value = "320m"
81
+ expect(validateLedger(ledger).some((e: string) => e.includes("value"))).toBe(true)
82
+ })
83
+
84
+ test("rejects an exact with a non-string num", () => {
85
+ const ledger = ledgerWith(measured("span"))
86
+ ledger.rows[0].exact = { num: 1, den: "3" }
87
+ expect(validateLedger(ledger).some((e: string) => e.includes("exact"))).toBe(true)
88
+ })
89
+
90
+ test("rejects operands and derived_from ids with no ledger row", () => {
91
+ const ledger = ledgerWith(measured("span"))
92
+ ledger.rows.push({ ...ledger.rows[0], fact_id: "F2", kind: "derived", label: "ratio", operation: "ratio", operands: ["F9"], derived_from: ["F1", "Q7"] })
93
+ const errors = validateLedger(ledger)
94
+ expect(errors.some((e: string) => e.includes("operands") && e.includes("F9"))).toBe(true)
95
+ expect(errors.some((e: string) => e.includes("derived_from") && e.includes("Q7"))).toBe(true)
96
+ expect(errors.some((e: string) => e.includes("F1"))).toBe(false)
97
+ })
98
+
99
+ test("a bare row with only fact_id returns at least three errors", () => {
100
+ expect(validateLedger({ rows: [{ fact_id: "F1" }] }).length).toBeGreaterThanOrEqual(3)
101
+ })
102
+ })
103
+
104
+ describe("ids and status", () => {
105
+ test("nextId sequences F1, F2 then Q1, and a derived row gets F3", () => {
106
+ const ledger = emptyLedger()
107
+ expect(addRow(ledger, measured("a")).fact_id).toBe("F1")
108
+ expect(addRow(ledger, measured("b")).fact_id).toBe("F2")
109
+ expect(addRow(ledger, quoteRow("c")).fact_id).toBe("Q1")
110
+ expect(nextId(ledger, "derived")).toBe("F3")
111
+ const derived = addRow(ledger, {
112
+ kind: "derived",
113
+ value: "0.333",
114
+ unit: null,
115
+ raw: "0.333",
116
+ quote: "",
117
+ extracted_by: "fact derive",
118
+ observed_at: "2026-10-23T08:00:00Z",
119
+ label: "ratio",
120
+ operation: "ratio",
121
+ operands: ["F1", "F2"],
122
+ derived_from: ["F1", "F2"],
123
+ exact: { num: "1", den: "3" },
124
+ formula: "F1 / F2", render: "0.333", precision: { places: 3, sigfigs: 3 }, compatibility: "checked",
125
+ })
126
+ expect(derived.fact_id).toBe("F3")
127
+ expect(nextId(ledger, "quote")).toBe("Q2")
128
+ })
129
+
130
+ test("measured rows store exact from their decimal", () => {
131
+ const ledger = ledgerWith(measured("span", "12.50"))
132
+ expect(ledger.rows[0].exact).toEqual({ num: "25", den: "2" })
133
+ })
134
+
135
+ test("retractRow keeps the row with status retracted", () => {
136
+ const ledger = ledgerWith(measured("a"), measured("b"))
137
+ retractRow(ledger, "F1", "wrong page")
138
+ expect(ledger.rows).toHaveLength(2)
139
+ expect(ledger.rows[0].status).toBe("retracted")
140
+ expect(ledger.events[0]).toMatchObject({ fact_id: "F1", to: "retracted", reason: "wrong page" })
141
+ expect(validateLedger(ledger)).toEqual([])
142
+ expect(() => retractRow(ledger, "F9", "x")).toThrow()
143
+ })
144
+
145
+ test("supersedeRow points at the replacement", () => {
146
+ const ledger = ledgerWith(measured("a"), measured("b"))
147
+ supersedeRow(ledger, "F1", "F2", "newer filing")
148
+ expect(ledger.rows[0].status).toBe("superseded")
149
+ expect(ledger.events[0]).toMatchObject({ fact_id: "F1", replacement: "F2" })
150
+ expect(validateLedger(ledger)).toEqual([])
151
+ })
152
+
153
+ test("supersedeRow refuses a replacement that is not active", () => {
154
+ const ledger = ledgerWith(measured("a"), measured("b"))
155
+ retractRow(ledger, "F2", "wrong page")
156
+ expect(() => supersedeRow(ledger, "F1", "F2", "newer filing")).toThrow(/F2 is retracted/)
157
+ expect(ledger.rows[0].status).toBe("active")
158
+ expect(ledger.events).toHaveLength(1)
159
+ })
160
+
161
+ test("supersedeRow without a replacement throws and points at retract", () => {
162
+ const ledger = ledgerWith(measured("a"))
163
+ expect(() => supersedeRow(ledger, "F1", null, "newer filing")).toThrow(/supersede F1 needs a replacement row; use retract/)
164
+ expect(ledger.rows[0].status).toBe("active")
165
+ expect(ledger.events).toHaveLength(0)
166
+ })
167
+ })
168
+
169
+ describe("read and write", () => {
170
+ test("write then read round-trips byte-identically", () => {
171
+ const dir = tempDir()
172
+ const ledger = ledgerWith(measured("span"), quoteRow("claim"))
173
+ writeLedger(dir, ledger)
174
+ const first = readFileSync(join(dir, LEDGER_FILENAME), "utf8")
175
+ const loaded = readLedger(dir)
176
+ expect(loaded).toEqual(ledger)
177
+ writeLedger(dir, loaded)
178
+ expect(readFileSync(join(dir, LEDGER_FILENAME), "utf8")).toBe(first)
179
+ expect(readdirSync(dir)).toEqual([LEDGER_FILENAME])
180
+ expect(loaded.schemaVersion).toBe(LEDGER_SCHEMA_VERSION)
181
+ })
182
+
183
+ test("a derived row with an exact rational round-trips and validates", () => {
184
+ const dir = tempDir()
185
+ const ledger = ledgerWith(measured("a"), measured("b"))
186
+ addRow(ledger, {
187
+ kind: "derived",
188
+ value: "0.333",
189
+ raw: "0.333",
190
+ quote: "",
191
+ extracted_by: "fact derive",
192
+ observed_at: "2026-10-23T08:00:00Z",
193
+ label: "third",
194
+ operation: "ratio",
195
+ operands: ["F1", "F2"],
196
+ derived_from: ["F1", "F2"],
197
+ exact: { num: "1", den: "3" },
198
+ formula: "F1 / F2", render: "0.333", precision: { places: 3, sigfigs: 3 }, compatibility: "checked",
199
+ })
200
+ writeLedger(dir, ledger)
201
+ const loaded = readLedger(dir)
202
+ expect(loaded.rows[2].exact).toEqual({ num: "1", den: "3" })
203
+ expect(loaded.rows[2].value).toBe("0.333")
204
+ expect(validateLedger(loaded)).toEqual([])
205
+ })
206
+
207
+ test("readLedger of a missing file is an empty ledger; writeLedger refuses an invalid one", () => {
208
+ const dir = tempDir()
209
+ expect(readLedger(dir)).toEqual(emptyLedger())
210
+ const ledger = ledgerWith(measured("a"))
211
+ ledger.rows[0].value = "abc"
212
+ expect(() => writeLedger(dir, ledger)).toThrow()
213
+ expect(readdirSync(dir)).toEqual([])
214
+ })
215
+
216
+ test("renderLedgerMarkdown lists every row", () => {
217
+ const text = renderLedgerMarkdown(ledgerWith(measured("span"), quoteRow("claim")))
218
+ expect(text).toContain("| F1 | measured | span |")
219
+ expect(text).toContain("| Q1 | quote | claim |")
220
+ })
221
+ })
222
+
223
+ describe("validateLedger checks events against rows", () => {
224
+ test("a retracted event that carries a replacement is an error", () => {
225
+ const ledger = ledgerWith(measured("a"), measured("b"))
226
+ retractRow(ledger, "F1", "wrong page")
227
+ ledger.events[0].replacement = "F2"
228
+ expect(validateLedger(ledger).some((e: string) => /F1 is retracted but names replacement F2/.test(e))).toBe(true)
229
+ })
230
+
231
+ test("the last event for a fact must match its row status", () => {
232
+ const ledger = ledgerWith(measured("a"), measured("b"))
233
+ retractRow(ledger, "F1", "wrong page")
234
+ ledger.rows[0].status = "active"
235
+ expect(validateLedger(ledger).some((e: string) => /F1 has status active but its last event moves it to retracted/.test(e))).toBe(true)
236
+ })
237
+
238
+ test("a non-active row with no event is an error", () => {
239
+ const ledger = ledgerWith(measured("a"))
240
+ ledger.rows[0].status = "superseded"
241
+ expect(validateLedger(ledger).some((e: string) => /F1 is superseded but has no status event/.test(e))).toBe(true)
242
+ })
243
+
244
+ test("an event with an invalid from or to status is an error naming the fact", () => {
245
+ const ledger = ledgerWith(measured("a"))
246
+ retractRow(ledger, "F1", "wrong page")
247
+ ledger.events[0].from = "gone"
248
+ const errors = validateLedger(ledger)
249
+ expect(errors.some((e: string) => /events\[0\]: F1 is active, but the event moves it from gone/.test(e))).toBe(true)
250
+ ledger.events[0].from = "active"
251
+ ledger.events[0].to = "active"
252
+ expect(validateLedger(ledger).some((e: string) => /events\[0\]: F1 cannot move from active to active/.test(e))).toBe(true)
253
+ })
254
+ })