oh-my-opencode 5.1.45 → 5.1.47

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 (370) hide show
  1. package/.agents/command/publish.md +12 -13
  2. package/.agents/skills/publish/SKILL.md +12 -13
  3. package/.opencode/command/publish.md +12 -13
  4. package/README.de.md +0 -5
  5. package/README.es.md +0 -5
  6. package/README.fr.md +0 -5
  7. package/README.it.md +0 -5
  8. package/README.ja.md +0 -5
  9. package/README.ko.md +0 -5
  10. package/README.md +4 -5
  11. package/README.pt-br.md +0 -5
  12. package/README.ru.md +0 -6
  13. package/README.zh-cn.md +0 -5
  14. package/dist/agents/atlas/agent.d.ts +3 -2
  15. package/dist/agents/sisyphus/index.d.ts +1 -0
  16. package/dist/agents/sisyphus/kimi-k3-1.d.ts +9 -0
  17. package/dist/agents/sisyphus-agent-factory.d.ts +1 -1
  18. package/dist/agents/sisyphus-junior/agent.d.ts +1 -1
  19. package/dist/agents/sisyphus-junior/index.d.ts +1 -0
  20. package/dist/agents/sisyphus-junior/kimi-k3-1.d.ts +6 -0
  21. package/dist/agents/types.d.ts +2 -2
  22. package/dist/cli/index.js +91 -163
  23. package/dist/cli/service-auth/keystore.d.ts +8 -3
  24. package/dist/cli-node/index.js +91 -163
  25. package/dist/config-migration/index.d.ts +2 -2
  26. package/dist/config-migration/migration-plans.d.ts +0 -10
  27. package/dist/index.js +1784 -1204
  28. package/dist/skills/ast-grep/AGENTS.md +1 -1
  29. package/dist/skills/browser/runtime/omowright/manifest.json +1 -1
  30. package/dist/skills/frontend/ATTRIBUTION.md +2 -2
  31. package/dist/skills/ultimate-browsing/engine/AGENTS.md +1 -1
  32. package/dist/skills/ulw-research/AGENTS.md +14 -2
  33. package/dist/skills/ulw-research/SKILL.md +37 -12
  34. package/dist/skills/ulw-research/references/deliverable-phase.md +81 -14
  35. package/dist/skills/ulw-research/references/fact-ledger-grammar.md +19 -1
  36. package/dist/skills/ulw-research/references/fact-ledger.md +95 -0
  37. package/dist/skills/ulw-research/references/fidelity-claim-kinds.md +19 -0
  38. package/dist/skills/ulw-research/references/report-gates.md +21 -4
  39. package/dist/skills/ulw-research/references/report-visualization.md +2 -0
  40. package/dist/skills/ulw-research/references/unit-rules.md +157 -0
  41. package/dist/skills/ulw-research/scripts/claim-kinds.mjs +116 -0
  42. package/dist/skills/ulw-research/scripts/claim-kinds.test.ts +193 -0
  43. package/dist/skills/ulw-research/scripts/cli-support.mjs +19 -5
  44. package/dist/skills/ulw-research/scripts/cli-support.test.ts +13 -2
  45. package/dist/skills/ulw-research/scripts/contracts.mjs +27 -6
  46. package/dist/skills/ulw-research/scripts/contracts.test.ts +3 -0
  47. package/dist/skills/ulw-research/scripts/copy-returns-polish.test.ts +209 -0
  48. package/dist/skills/ulw-research/scripts/copy-returns.test.ts +183 -0
  49. package/dist/skills/ulw-research/scripts/delivery-barrier.test.ts +245 -0
  50. package/dist/skills/ulw-research/scripts/fact-commands-meaning.mjs +176 -0
  51. package/dist/skills/ulw-research/scripts/fact-commands-polish.mjs +127 -0
  52. package/dist/skills/ulw-research/scripts/fact-commands-verify.mjs +183 -0
  53. package/dist/skills/ulw-research/scripts/fact-commands.mjs +241 -0
  54. package/dist/skills/ulw-research/scripts/fact-derive.mjs +66 -0
  55. package/dist/skills/ulw-research/scripts/fact-derive.test.ts +375 -0
  56. package/dist/skills/ulw-research/scripts/fact-expr.mjs +108 -0
  57. package/dist/skills/ulw-research/scripts/fact-fill.mjs +194 -0
  58. package/dist/skills/ulw-research/scripts/fact-fill.test.ts +221 -0
  59. package/dist/skills/ulw-research/scripts/fact-ledger-grammar.test.ts +9 -2
  60. package/dist/skills/ulw-research/scripts/fact-ledger-kinds.mjs +127 -13
  61. package/dist/skills/ulw-research/scripts/fact-ledger-kinds.test.ts +18 -3
  62. package/dist/skills/ulw-research/scripts/fact-ledger-legacy.test.ts +160 -0
  63. package/dist/skills/ulw-research/scripts/fact-ledger-schema.mjs +33 -16
  64. package/dist/skills/ulw-research/scripts/fact-ledger.mjs +29 -9
  65. package/dist/skills/ulw-research/scripts/fact-ledger.test.ts +2 -0
  66. package/dist/skills/ulw-research/scripts/fact-locate.mjs +231 -0
  67. package/dist/skills/ulw-research/scripts/fact-locate.test.ts +118 -0
  68. package/dist/skills/ulw-research/scripts/fact-pairs.mjs +185 -0
  69. package/dist/skills/ulw-research/scripts/fact-pairs.test.ts +151 -0
  70. package/dist/skills/ulw-research/scripts/fact-recompute.mjs +89 -0
  71. package/dist/skills/ulw-research/scripts/fact-unit-dimensions.test.ts +409 -0
  72. package/dist/skills/ulw-research/scripts/fact-unit-rules.test.ts +388 -0
  73. package/dist/skills/ulw-research/scripts/fidelity-binding.mjs +130 -0
  74. package/dist/skills/ulw-research/scripts/fidelity-binding.test.ts +81 -0
  75. package/dist/skills/ulw-research/scripts/fidelity-brief.mjs +5 -0
  76. package/dist/skills/ulw-research/scripts/fidelity-claims.mjs +89 -0
  77. package/dist/skills/ulw-research/scripts/fidelity-lexicon.mjs +145 -0
  78. package/dist/skills/ulw-research/scripts/fidelity-lexicon.test.ts +117 -0
  79. package/dist/skills/ulw-research/scripts/fidelity-records.mjs +163 -0
  80. package/dist/skills/ulw-research/scripts/fidelity-state.mjs +2 -1
  81. package/dist/skills/ulw-research/scripts/fidelity-trace.mjs +85 -0
  82. package/dist/skills/ulw-research/scripts/fidelity-trace.test.ts +58 -0
  83. package/dist/skills/ulw-research/scripts/fidelity-values.mjs +229 -0
  84. package/dist/skills/ulw-research/scripts/fidelity-values.test.ts +270 -0
  85. package/dist/skills/ulw-research/scripts/gates-fidelity.mjs +113 -0
  86. package/dist/skills/ulw-research/scripts/gates-fidelity.test.ts +73 -0
  87. package/dist/skills/ulw-research/scripts/gates-static.mjs +28 -8
  88. package/dist/skills/ulw-research/scripts/html-lite.mjs +2 -2
  89. package/dist/skills/ulw-research/scripts/html-markup.mjs +182 -0
  90. package/dist/skills/ulw-research/scripts/md-lite.mjs +193 -0
  91. package/dist/skills/ulw-research/scripts/md-lite.test.ts +271 -0
  92. package/dist/skills/ulw-research/scripts/meaning-check.mjs +196 -0
  93. package/dist/skills/ulw-research/scripts/meaning-check.test.ts +228 -0
  94. package/dist/skills/ulw-research/scripts/outcome.mjs +6 -5
  95. package/dist/skills/ulw-research/scripts/placeholder.mjs +184 -0
  96. package/dist/skills/ulw-research/scripts/placeholder.test.ts +77 -0
  97. package/dist/skills/ulw-research/scripts/polish-apply.mjs +115 -0
  98. package/dist/skills/ulw-research/scripts/polish-apply.test.ts +363 -0
  99. package/dist/skills/ulw-research/scripts/polish-diff.mjs +31 -0
  100. package/dist/skills/ulw-research/scripts/polish-diff.test.ts +168 -0
  101. package/dist/skills/ulw-research/scripts/polish-markup.test.ts +187 -0
  102. package/dist/skills/ulw-research/scripts/polish-project.mjs +125 -0
  103. package/dist/skills/ulw-research/scripts/polish-rules.mjs +170 -0
  104. package/dist/skills/ulw-research/scripts/references-fences.test.ts +25 -0
  105. package/dist/skills/ulw-research/scripts/report-tools-commands.mjs +19 -8
  106. package/dist/skills/ulw-research/scripts/report-tools.mjs +15 -0
  107. package/dist/skills/ulw-research/scripts/report-tools.test.ts +545 -2
  108. package/dist/skills/ulw-research/scripts/source-commands.mjs +140 -0
  109. package/dist/skills/ulw-research/scripts/source-snapshot.mjs +176 -0
  110. package/dist/skills/ulw-research/scripts/source-snapshot.test.ts +113 -0
  111. package/dist/skills/ulw-research/scripts/tests/fixtures/fidelity/assets/share.csv +3 -0
  112. package/dist/skills/ulw-research/scripts/tests/fixtures/fidelity/gen-legacy-dev.mjs +89 -0
  113. package/dist/skills/ulw-research/scripts/tests/fixtures/fidelity/ledger-basic.json +223 -0
  114. package/dist/skills/ulw-research/scripts/tests/fixtures/fidelity/ledger-legacy-dev.json +859 -0
  115. package/dist/skills/ulw-research/scripts/tests/fixtures/fidelity/ledger-verify.json +420 -0
  116. package/dist/skills/ulw-research/scripts/tests/fixtures/fidelity/polish-edits.json +12 -0
  117. package/dist/skills/ulw-research/scripts/tests/fixtures/fidelity/report-bad.html +19 -0
  118. package/dist/skills/ulw-research/scripts/tests/fixtures/fidelity/report-good.html +39 -0
  119. package/dist/skills/ulw-research/scripts/tests/fixtures/fidelity/sources/S4/acronyms.json +3 -0
  120. package/dist/skills/ulw-research/scripts/tests/fixtures/fidelity/sources/S4/meta.json +9 -0
  121. package/dist/skills/ulw-research/scripts/tests/fixtures/fidelity/sources/S4/sentences.jsonl +7 -0
  122. package/dist/skills/ulw-research/scripts/tests/fixtures/fidelity/sources/S4/text.txt +7 -0
  123. package/dist/skills/ulw-research/scripts/tests/fixtures/fidelity/sources/S4.html +19 -0
  124. package/dist/skills/ulw-research/scripts/tests/fixtures/fidelity/sources/S5/acronyms.json +1 -0
  125. package/dist/skills/ulw-research/scripts/tests/fixtures/fidelity/sources/S5/meta.json +9 -0
  126. package/dist/skills/ulw-research/scripts/tests/fixtures/fidelity/sources/S5/sentences.jsonl +1 -0
  127. package/dist/skills/ulw-research/scripts/tests/fixtures/fidelity/sources/S5/text.txt +1 -0
  128. package/dist/skills/ulw-research/scripts/tests/fixtures/fidelity/sources/S9.txt +1 -0
  129. package/dist/skills/ulw-research/scripts/unit-algebra.mjs +101 -0
  130. package/dist/skills/ulw-research/scripts/unit-errors.mjs +23 -0
  131. package/dist/skills/ulw-research/scripts/unit-metadata.mjs +31 -0
  132. package/dist/skills/ulw-research/scripts/unit-pairs.mjs +72 -0
  133. package/dist/skills/ulw-research/scripts/unit-precision.mjs +44 -0
  134. package/dist/skills/ulw-research/scripts/unit-rules-shapes.mjs +64 -0
  135. package/dist/skills/ulw-research/scripts/unit-rules.mjs +173 -0
  136. package/dist/skills/ulw-research/scripts/units.mjs +4 -4
  137. package/dist/testing/module-mock-lifecycle.d.ts +1 -1
  138. package/dist/tui.js +32 -34
  139. package/package.json +13 -13
  140. package/packages/lsp-core/src/mcp-protocol-pin.test.ts +29 -0
  141. package/packages/lsp-core/src/mcp.ts +5 -2
  142. package/packages/lsp-daemon/dist/cli.js +3 -3
  143. package/packages/lsp-daemon/dist/client.js +3 -3
  144. package/packages/lsp-daemon/dist/index.js +3 -3
  145. package/packages/lsp-tools-mcp/dist/cli.js +3 -3
  146. package/packages/lsp-tools-mcp/dist/mcp.js +3 -3
  147. package/packages/omo-codex/plugin/.codex-plugin/plugin.json +1 -1
  148. package/packages/omo-codex/plugin/components/bootstrap/dist/cli.js +23 -14
  149. package/packages/omo-codex/plugin/components/bootstrap/hooks/hooks.json +1 -1
  150. package/packages/omo-codex/plugin/components/bootstrap/package.json +1 -1
  151. package/packages/omo-codex/plugin/components/comment-checker/hooks/hooks.json +1 -1
  152. package/packages/omo-codex/plugin/components/comment-checker/package.json +1 -1
  153. package/packages/omo-codex/plugin/components/git-bash/hooks/hooks.json +2 -2
  154. package/packages/omo-codex/plugin/components/git-bash/package.json +1 -1
  155. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/hooks/hooks.json +1 -1
  156. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/package.json +1 -1
  157. package/packages/omo-codex/plugin/components/lsp/dist/.omo-runtime-manifest.json +2 -2
  158. package/packages/omo-codex/plugin/components/lsp/hooks/hooks.json +2 -2
  159. package/packages/omo-codex/plugin/components/lsp/package.json +1 -1
  160. package/packages/omo-codex/plugin/components/rules/hooks/hooks.json +4 -4
  161. package/packages/omo-codex/plugin/components/rules/package.json +1 -1
  162. package/packages/omo-codex/plugin/components/teammode/hooks/hooks.json +1 -1
  163. package/packages/omo-codex/plugin/components/teammode/package.json +1 -1
  164. package/packages/omo-codex/plugin/components/telemetry/hooks/hooks.json +1 -1
  165. package/packages/omo-codex/plugin/components/telemetry/package.json +1 -1
  166. package/packages/omo-codex/plugin/components/ultrawork/hooks/hooks.json +1 -1
  167. package/packages/omo-codex/plugin/components/ultrawork/package.json +1 -1
  168. package/packages/omo-codex/plugin/components/ulw-execute-continuation/hooks/hooks.json +1 -1
  169. package/packages/omo-codex/plugin/components/ulw-execute-continuation/package.json +1 -1
  170. package/packages/omo-codex/plugin/components/ulw-loop/hooks/hooks.json +5 -5
  171. package/packages/omo-codex/plugin/components/ulw-loop/package.json +1 -1
  172. package/packages/omo-codex/plugin/hooks/post-compact-resetting-git-bash-mcp-reminder.json +1 -1
  173. package/packages/omo-codex/plugin/hooks/post-compact-resetting-lsp-diagnostics-cache.json +1 -1
  174. package/packages/omo-codex/plugin/hooks/post-compact-resetting-project-rule-cache.json +1 -1
  175. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-comments.json +1 -1
  176. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-lsp-diagnostics.json +1 -1
  177. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-thread-title-hygiene.json +1 -1
  178. package/packages/omo-codex/plugin/hooks/post-tool-use-matching-project-rules.json +1 -1
  179. package/packages/omo-codex/plugin/hooks/post-tool-use-recording-spawn-admission.json +1 -1
  180. package/packages/omo-codex/plugin/hooks/pre-tool-use-enforcing-unlimited-goal-budget.json +1 -1
  181. package/packages/omo-codex/plugin/hooks/pre-tool-use-guarding-ulw-loop-spawns.json +1 -1
  182. package/packages/omo-codex/plugin/hooks/pre-tool-use-recommending-git-bash-mcp.json +1 -1
  183. package/packages/omo-codex/plugin/hooks/session-start-checking-auto-update.json +1 -1
  184. package/packages/omo-codex/plugin/hooks/session-start-checking-bootstrap-provisioning.json +1 -1
  185. package/packages/omo-codex/plugin/hooks/session-start-loading-project-rules.json +1 -1
  186. package/packages/omo-codex/plugin/hooks/session-start-recording-session-telemetry.json +1 -1
  187. package/packages/omo-codex/plugin/hooks/stop-checking-ulw-execute-continuation.json +1 -1
  188. package/packages/omo-codex/plugin/hooks/stop-checking-ulw-loop-resume.json +1 -1
  189. package/packages/omo-codex/plugin/hooks/subagent-stop-verifying-lazycodex-executor-evidence.json +1 -1
  190. package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ultrawork-trigger.json +1 -1
  191. package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ulw-loop-steering.json +1 -1
  192. package/packages/omo-codex/plugin/hooks/user-prompt-submit-loading-project-rules.json +1 -1
  193. package/packages/omo-codex/plugin/package-lock.json +12 -12
  194. package/packages/omo-codex/plugin/package.json +1 -1
  195. package/packages/omo-codex/plugin/scripts/sync-skills.mjs +8 -3
  196. package/packages/omo-codex/plugin/shared/test/config-loader.test.ts +0 -2
  197. package/packages/omo-codex/plugin/skills/ast-grep/AGENTS.md +1 -1
  198. package/packages/omo-codex/plugin/skills/browser/runtime/omowright/manifest.json +1 -1
  199. package/packages/omo-codex/plugin/skills/frontend/ATTRIBUTION.md +2 -2
  200. package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/AGENTS.md +1 -1
  201. package/packages/omo-codex/plugin/skills/ulw-research/AGENTS.md +14 -2
  202. package/packages/omo-codex/plugin/skills/ulw-research/SKILL.md +36 -11
  203. package/packages/omo-codex/plugin/skills/ulw-research/references/deliverable-phase.md +81 -14
  204. package/packages/omo-codex/plugin/skills/ulw-research/references/fact-ledger-grammar.md +19 -1
  205. package/packages/omo-codex/plugin/skills/ulw-research/references/fact-ledger.md +95 -0
  206. package/packages/omo-codex/plugin/skills/ulw-research/references/fidelity-claim-kinds.md +19 -0
  207. package/packages/omo-codex/plugin/skills/ulw-research/references/report-gates.md +21 -4
  208. package/packages/omo-codex/plugin/skills/ulw-research/references/report-visualization.md +2 -0
  209. package/packages/omo-codex/plugin/skills/ulw-research/references/unit-rules.md +157 -0
  210. package/packages/omo-codex/plugin/skills/ulw-research/scripts/claim-kinds.mjs +116 -0
  211. package/packages/omo-codex/plugin/skills/ulw-research/scripts/cli-support.mjs +19 -5
  212. package/packages/omo-codex/plugin/skills/ulw-research/scripts/contracts.mjs +27 -6
  213. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-commands-meaning.mjs +176 -0
  214. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-commands-polish.mjs +127 -0
  215. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-commands-verify.mjs +183 -0
  216. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-commands.mjs +241 -0
  217. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-derive.mjs +66 -0
  218. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-expr.mjs +108 -0
  219. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-fill.mjs +194 -0
  220. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-ledger-kinds.mjs +127 -13
  221. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-ledger-schema.mjs +33 -16
  222. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-ledger.mjs +29 -9
  223. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-locate.mjs +231 -0
  224. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-pairs.mjs +185 -0
  225. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-recompute.mjs +89 -0
  226. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-binding.mjs +130 -0
  227. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-brief.mjs +5 -0
  228. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-claims.mjs +89 -0
  229. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-lexicon.mjs +145 -0
  230. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-records.mjs +163 -0
  231. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-state.mjs +2 -1
  232. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-trace.mjs +85 -0
  233. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-values.mjs +229 -0
  234. package/packages/omo-codex/plugin/skills/ulw-research/scripts/gates-fidelity.mjs +113 -0
  235. package/packages/omo-codex/plugin/skills/ulw-research/scripts/gates-static.mjs +28 -8
  236. package/packages/omo-codex/plugin/skills/ulw-research/scripts/html-lite.mjs +2 -2
  237. package/packages/omo-codex/plugin/skills/ulw-research/scripts/html-markup.mjs +182 -0
  238. package/packages/omo-codex/plugin/skills/ulw-research/scripts/md-lite.mjs +193 -0
  239. package/packages/omo-codex/plugin/skills/ulw-research/scripts/meaning-check.mjs +196 -0
  240. package/packages/omo-codex/plugin/skills/ulw-research/scripts/outcome.mjs +6 -5
  241. package/packages/omo-codex/plugin/skills/ulw-research/scripts/placeholder.mjs +184 -0
  242. package/packages/omo-codex/plugin/skills/ulw-research/scripts/polish-apply.mjs +115 -0
  243. package/packages/omo-codex/plugin/skills/ulw-research/scripts/polish-diff.mjs +31 -0
  244. package/packages/omo-codex/plugin/skills/ulw-research/scripts/polish-project.mjs +125 -0
  245. package/packages/omo-codex/plugin/skills/ulw-research/scripts/polish-rules.mjs +170 -0
  246. package/packages/omo-codex/plugin/skills/ulw-research/scripts/report-tools-commands.mjs +19 -8
  247. package/packages/omo-codex/plugin/skills/ulw-research/scripts/report-tools.mjs +15 -0
  248. package/packages/omo-codex/plugin/skills/ulw-research/scripts/source-commands.mjs +140 -0
  249. package/packages/omo-codex/plugin/skills/ulw-research/scripts/source-snapshot.mjs +176 -0
  250. package/packages/omo-codex/plugin/skills/ulw-research/scripts/unit-algebra.mjs +101 -0
  251. package/packages/omo-codex/plugin/skills/ulw-research/scripts/unit-errors.mjs +23 -0
  252. package/packages/omo-codex/plugin/skills/ulw-research/scripts/unit-metadata.mjs +31 -0
  253. package/packages/omo-codex/plugin/skills/ulw-research/scripts/unit-pairs.mjs +72 -0
  254. package/packages/omo-codex/plugin/skills/ulw-research/scripts/unit-precision.mjs +44 -0
  255. package/packages/omo-codex/plugin/skills/ulw-research/scripts/unit-rules-shapes.mjs +64 -0
  256. package/packages/omo-codex/plugin/skills/ulw-research/scripts/unit-rules.mjs +173 -0
  257. package/packages/omo-codex/plugin/skills/ulw-research/scripts/units.mjs +4 -4
  258. package/packages/omo-codex/plugin/test/sync-skills-codex-compatibility.test.mjs +19 -0
  259. package/packages/omo-codex/plugin/test/sync-skills-test-support.mjs +7 -3
  260. package/packages/omo-codex/scripts/install-dist/install-local.mjs +28 -19
  261. package/packages/shared-skills/skills/ast-grep/AGENTS.md +1 -1
  262. package/packages/shared-skills/skills/browser/runtime/omowright/manifest.json +1 -1
  263. package/packages/shared-skills/skills/frontend/ATTRIBUTION.md +2 -2
  264. package/packages/shared-skills/skills/ultimate-browsing/engine/AGENTS.md +1 -1
  265. package/packages/shared-skills/skills/ulw-research/AGENTS.md +14 -2
  266. package/packages/shared-skills/skills/ulw-research/SKILL.md +37 -12
  267. package/packages/shared-skills/skills/ulw-research/references/deliverable-phase.md +81 -14
  268. package/packages/shared-skills/skills/ulw-research/references/fact-ledger-grammar.md +19 -1
  269. package/packages/shared-skills/skills/ulw-research/references/fact-ledger.md +95 -0
  270. package/packages/shared-skills/skills/ulw-research/references/fidelity-claim-kinds.md +19 -0
  271. package/packages/shared-skills/skills/ulw-research/references/report-gates.md +21 -4
  272. package/packages/shared-skills/skills/ulw-research/references/report-visualization.md +2 -0
  273. package/packages/shared-skills/skills/ulw-research/references/unit-rules.md +157 -0
  274. package/packages/shared-skills/skills/ulw-research/scripts/claim-kinds.mjs +116 -0
  275. package/packages/shared-skills/skills/ulw-research/scripts/claim-kinds.test.ts +193 -0
  276. package/packages/shared-skills/skills/ulw-research/scripts/cli-support.mjs +19 -5
  277. package/packages/shared-skills/skills/ulw-research/scripts/cli-support.test.ts +13 -2
  278. package/packages/shared-skills/skills/ulw-research/scripts/contracts.mjs +27 -6
  279. package/packages/shared-skills/skills/ulw-research/scripts/contracts.test.ts +3 -0
  280. package/packages/shared-skills/skills/ulw-research/scripts/copy-returns-polish.test.ts +209 -0
  281. package/packages/shared-skills/skills/ulw-research/scripts/copy-returns.test.ts +183 -0
  282. package/packages/shared-skills/skills/ulw-research/scripts/delivery-barrier.test.ts +245 -0
  283. package/packages/shared-skills/skills/ulw-research/scripts/fact-commands-meaning.mjs +176 -0
  284. package/packages/shared-skills/skills/ulw-research/scripts/fact-commands-polish.mjs +127 -0
  285. package/packages/shared-skills/skills/ulw-research/scripts/fact-commands-verify.mjs +183 -0
  286. package/packages/shared-skills/skills/ulw-research/scripts/fact-commands.mjs +241 -0
  287. package/packages/shared-skills/skills/ulw-research/scripts/fact-derive.mjs +66 -0
  288. package/packages/shared-skills/skills/ulw-research/scripts/fact-derive.test.ts +375 -0
  289. package/packages/shared-skills/skills/ulw-research/scripts/fact-expr.mjs +108 -0
  290. package/packages/shared-skills/skills/ulw-research/scripts/fact-fill.mjs +194 -0
  291. package/packages/shared-skills/skills/ulw-research/scripts/fact-fill.test.ts +221 -0
  292. package/packages/shared-skills/skills/ulw-research/scripts/fact-ledger-grammar.test.ts +9 -2
  293. package/packages/shared-skills/skills/ulw-research/scripts/fact-ledger-kinds.mjs +127 -13
  294. package/packages/shared-skills/skills/ulw-research/scripts/fact-ledger-kinds.test.ts +18 -3
  295. package/packages/shared-skills/skills/ulw-research/scripts/fact-ledger-legacy.test.ts +160 -0
  296. package/packages/shared-skills/skills/ulw-research/scripts/fact-ledger-schema.mjs +33 -16
  297. package/packages/shared-skills/skills/ulw-research/scripts/fact-ledger.mjs +29 -9
  298. package/packages/shared-skills/skills/ulw-research/scripts/fact-ledger.test.ts +2 -0
  299. package/packages/shared-skills/skills/ulw-research/scripts/fact-locate.mjs +231 -0
  300. package/packages/shared-skills/skills/ulw-research/scripts/fact-locate.test.ts +118 -0
  301. package/packages/shared-skills/skills/ulw-research/scripts/fact-pairs.mjs +185 -0
  302. package/packages/shared-skills/skills/ulw-research/scripts/fact-pairs.test.ts +151 -0
  303. package/packages/shared-skills/skills/ulw-research/scripts/fact-recompute.mjs +89 -0
  304. package/packages/shared-skills/skills/ulw-research/scripts/fact-unit-dimensions.test.ts +409 -0
  305. package/packages/shared-skills/skills/ulw-research/scripts/fact-unit-rules.test.ts +388 -0
  306. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-binding.mjs +130 -0
  307. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-binding.test.ts +81 -0
  308. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-brief.mjs +5 -0
  309. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-claims.mjs +89 -0
  310. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-lexicon.mjs +145 -0
  311. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-lexicon.test.ts +117 -0
  312. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-records.mjs +163 -0
  313. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-state.mjs +2 -1
  314. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-trace.mjs +85 -0
  315. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-trace.test.ts +58 -0
  316. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-values.mjs +229 -0
  317. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-values.test.ts +270 -0
  318. package/packages/shared-skills/skills/ulw-research/scripts/gates-fidelity.mjs +113 -0
  319. package/packages/shared-skills/skills/ulw-research/scripts/gates-fidelity.test.ts +73 -0
  320. package/packages/shared-skills/skills/ulw-research/scripts/gates-static.mjs +28 -8
  321. package/packages/shared-skills/skills/ulw-research/scripts/html-lite.mjs +2 -2
  322. package/packages/shared-skills/skills/ulw-research/scripts/html-markup.mjs +182 -0
  323. package/packages/shared-skills/skills/ulw-research/scripts/md-lite.mjs +193 -0
  324. package/packages/shared-skills/skills/ulw-research/scripts/md-lite.test.ts +271 -0
  325. package/packages/shared-skills/skills/ulw-research/scripts/meaning-check.mjs +196 -0
  326. package/packages/shared-skills/skills/ulw-research/scripts/meaning-check.test.ts +228 -0
  327. package/packages/shared-skills/skills/ulw-research/scripts/outcome.mjs +6 -5
  328. package/packages/shared-skills/skills/ulw-research/scripts/placeholder.mjs +184 -0
  329. package/packages/shared-skills/skills/ulw-research/scripts/placeholder.test.ts +77 -0
  330. package/packages/shared-skills/skills/ulw-research/scripts/polish-apply.mjs +115 -0
  331. package/packages/shared-skills/skills/ulw-research/scripts/polish-apply.test.ts +363 -0
  332. package/packages/shared-skills/skills/ulw-research/scripts/polish-diff.mjs +31 -0
  333. package/packages/shared-skills/skills/ulw-research/scripts/polish-diff.test.ts +168 -0
  334. package/packages/shared-skills/skills/ulw-research/scripts/polish-markup.test.ts +187 -0
  335. package/packages/shared-skills/skills/ulw-research/scripts/polish-project.mjs +125 -0
  336. package/packages/shared-skills/skills/ulw-research/scripts/polish-rules.mjs +170 -0
  337. package/packages/shared-skills/skills/ulw-research/scripts/references-fences.test.ts +25 -0
  338. package/packages/shared-skills/skills/ulw-research/scripts/report-tools-commands.mjs +19 -8
  339. package/packages/shared-skills/skills/ulw-research/scripts/report-tools.mjs +15 -0
  340. package/packages/shared-skills/skills/ulw-research/scripts/report-tools.test.ts +545 -2
  341. package/packages/shared-skills/skills/ulw-research/scripts/source-commands.mjs +140 -0
  342. package/packages/shared-skills/skills/ulw-research/scripts/source-snapshot.mjs +176 -0
  343. package/packages/shared-skills/skills/ulw-research/scripts/source-snapshot.test.ts +113 -0
  344. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/assets/share.csv +3 -0
  345. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/gen-legacy-dev.mjs +89 -0
  346. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/ledger-basic.json +223 -0
  347. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/ledger-legacy-dev.json +859 -0
  348. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/ledger-verify.json +420 -0
  349. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/polish-edits.json +12 -0
  350. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/report-bad.html +19 -0
  351. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/report-good.html +39 -0
  352. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/sources/S4/acronyms.json +3 -0
  353. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/sources/S4/meta.json +9 -0
  354. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/sources/S4/sentences.jsonl +7 -0
  355. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/sources/S4/text.txt +7 -0
  356. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/sources/S4.html +19 -0
  357. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/sources/S5/acronyms.json +1 -0
  358. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/sources/S5/meta.json +9 -0
  359. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/sources/S5/sentences.jsonl +1 -0
  360. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/sources/S5/text.txt +1 -0
  361. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/sources/S9.txt +1 -0
  362. package/packages/shared-skills/skills/ulw-research/scripts/unit-algebra.mjs +101 -0
  363. package/packages/shared-skills/skills/ulw-research/scripts/unit-errors.mjs +23 -0
  364. package/packages/shared-skills/skills/ulw-research/scripts/unit-metadata.mjs +31 -0
  365. package/packages/shared-skills/skills/ulw-research/scripts/unit-pairs.mjs +72 -0
  366. package/packages/shared-skills/skills/ulw-research/scripts/unit-precision.mjs +44 -0
  367. package/packages/shared-skills/skills/ulw-research/scripts/unit-rules-shapes.mjs +64 -0
  368. package/packages/shared-skills/skills/ulw-research/scripts/unit-rules.mjs +173 -0
  369. package/packages/shared-skills/skills/ulw-research/scripts/units.mjs +4 -4
  370. package/dist/config-migration/dori-section-rename.d.ts +0 -3
@@ -0,0 +1,127 @@
1
+ // fact-commands-polish.mjs - `fact polish-apply` and `fact polish-diff`, the script half of the
2
+ // proofread gate, plus the --changed-since filter of `fact pairs`.
3
+ //
4
+ // The writing lane returns polish-edits.json ([{before, after, reason}]) and never edits the
5
+ // report. polish-apply applies each edit through polish-apply.mjs and writes the result; a
6
+ // rejected (major) or unlocated (minor) edit leaves its region unchanged and is listed with its
7
+ // index and reason so the orchestrator can re-send it; every rejection reason is printed verbatim, one
8
+ // line per rejected edit (index and the first 60 characters of its before), in the summary and in
9
+ // data.rejected_reasons. It exits 0 whenever it wrote the output.
10
+ // polish-diff is the whole-document backstop (polish-diff.mjs) between the pre-polish artifact
11
+ // and any later copy; it exits 1 on a blocker. pairsChangedSince keeps only the pairs whose
12
+ // sentence text (normalized) is not in the pre-polish artifact that a fill map names, so the
13
+ // meaning readers re-check exactly what polish rewrote. No model, no network.
14
+
15
+ import { existsSync, readFileSync } from "node:fs"
16
+ import { join, resolve } from "node:path"
17
+
18
+ import { CliError, parseArgs, readJsonFile, sha256Of, writeJsonAtomic, writeTextAtomic } from "./cli-support.mjs"
19
+ import { countDefects, DEFECT_CODES } from "./contracts.mjs"
20
+ import { editionFormatOf, ledgerFrom, markdownAsHtml, sessionOf } from "./fact-commands-verify.mjs"
21
+ import { extractPairs } from "./fact-pairs.mjs"
22
+ import { applyEdits, POLISH_EDIT_FIELDS, validatePolishEdits } from "./polish-apply.mjs"
23
+ import { diffPolish } from "./polish-diff.mjs"
24
+ import { normalizeText } from "./text-normalize.mjs"
25
+
26
+ export { ledgerFrom, POLISH_EDIT_FIELDS, validatePolishEdits }
27
+
28
+ export const POLISH_RECORD = "polish-defects.json"
29
+
30
+ /**
31
+ * The polish-diff exit policy: only a registry blocker fails the command; majors are reported.
32
+ * Fail-closed like the delivery barrier: a code the registry does not know refuses outright,
33
+ * because a caller-claimed severity may not demote it.
34
+ */
35
+ export const polishDiffFails = (defects) =>
36
+ defects.some((defect) => {
37
+ if (!Object.hasOwn(DEFECT_CODES, defect?.code)) throw new CliError(`unknown defect code ${defect?.code}`)
38
+ return DEFECT_CODES[defect.code].severity === "blocker"
39
+ })
40
+
41
+ function readText(path) {
42
+ try {
43
+ return readFileSync(path, "utf8")
44
+ } catch (error) {
45
+ throw new CliError(`cannot read ${path}: ${error.code ?? error.message}`)
46
+ }
47
+ }
48
+
49
+ const withSeverity = (entry) => ({ ...entry, severity: DEFECT_CODES[entry.code].severity, hint: DEFECT_CODES[entry.code].hint })
50
+
51
+ /** The first 60 characters of an edit's before, on one line. */
52
+ const beforePreview = (before) => (typeof before === "string" ? before : "").replace(/\s+/gu, " ").slice(0, 60)
53
+
54
+ /** One line per rejected edit: its index, the start of its before, and its reason verbatim. */
55
+ const rejectionLine = (entry, edit) => ` rejected edit ${entry.index} ("${beforePreview(edit?.before)}"): ${entry.reason}`
56
+
57
+ function runPolishApply(args) {
58
+ const [report] = args.positionals
59
+ const { flags } = args
60
+ if (!report || flags.edits === undefined || flags.out === undefined) throw new CliError("usage: fact polish-apply <report.html> --edits polish-edits.json --out <report.polished.html> [--ledger F] [--session-dir D]")
61
+ if (editionFormatOf(report, flags.format) === "md") throw new CliError("fact polish-apply takes the filled HTML edition; polish a Markdown edition through its HTML source")
62
+ const html = readText(report)
63
+ const editsText = readText(flags.edits)
64
+ const edits = readJsonFile(flags.edits)
65
+ try {
66
+ validatePolishEdits(edits)
67
+ } catch (error) {
68
+ throw new CliError(`${flags.edits} must be an array of {${Object.keys(POLISH_EDIT_FIELDS).join(", ")}} objects: ${error.message}`)
69
+ }
70
+ const result = applyEdits(html, edits, ledgerFrom(flags))
71
+ writeTextAtomic(flags.out, result.out)
72
+ const defects = [...result.rejected, ...result.unlocated].sort((a, b) => a.index - b.index).map(withSeverity)
73
+ const counts = { applied: result.applied.length, rejected: result.rejected.length, unlocated: result.unlocated.length }
74
+ const data = { out: resolve(flags.out), ...counts, applied_indexes: result.applied, input_sha256: sha256Of(html), edits_sha256: sha256Of(editsText), out_sha256: sha256Of(result.out) }
75
+ const rejected_reasons = result.rejected.map((entry) => rejectionLine(entry, edits[entry.index]))
76
+ const summary = [`fact polish-apply: ${counts.applied} applied, ${counts.rejected} rejected, ${counts.unlocated} unlocated of ${edits.length} edit(s)`, ...rejected_reasons].join("\n")
77
+ return { json: { command: "fact polish-apply", status: "pass", exitCode: 0, defects, data: { ...data, rejected_reasons } }, summary, exitCode: 0 }
78
+ }
79
+
80
+ function runPolishDiff(args) {
81
+ const { flags } = args
82
+ if (flags.before === undefined || flags.after === undefined) throw new CliError("usage: fact polish-diff --before <pre-polish.html> --after <polished.html> [--ledger F] [--out polish-defects.json] [--session-dir D]")
83
+ const after = readText(flags.after)
84
+ const defects = diffPolish(readText(flags.before), after, ledgerFrom(flags)).map((defect) => ({ ...defect, hint: DEFECT_CODES[defect.code]?.hint ?? null }))
85
+ const counts = countDefects(defects)
86
+ const status = polishDiffFails(defects) ? "fail" : "pass"
87
+ const exitCode = status === "fail" ? 1 : 0
88
+ const record = { command: "fact polish-diff", status, exitCode, before: resolve(flags.before), artifact: resolve(flags.after), artifact_sha256: sha256Of(after), defects, counts }
89
+ const session = sessionOf(flags)
90
+ const out = flags.out ?? (session === null ? null : join(session, POLISH_RECORD))
91
+ if (out !== null) writeJsonAtomic(resolve(out), record)
92
+ const summary = `fact polish-diff: ${status.toUpperCase()} - ${counts.blocker} blocker, ${counts.major} major, ${counts.minor} minor`
93
+ return { json: { ...record, out: out === null ? null : resolve(out) }, summary, exitCode }
94
+ }
95
+
96
+ const pairKey = (pair) => [pair.kind, pair.sentence_key, pair.claim_id ?? "", normalizeText(pair.sentence)].join("\u0000")
97
+
98
+ /**
99
+ * The pairs whose sentence text changed since the pre-polish artifact a fill map names: a pair is
100
+ * kept unless the same (kind, sentence key, normalized sentence) occurs in that artifact. The map
101
+ * must carry the artifact's `artifact_sha256` and the bytes must still match it, so the filter is
102
+ * fail-closed on a hand-made or stale map.
103
+ * @param {object[]} pairs extractPairs output on the polished edition
104
+ * @param {string} mapPath the pre-polish fill map (fact fill's `<out>.fill-map.json`)
105
+ */
106
+ export function pairsChangedSince(pairs, mapPath, ledger, claims) {
107
+ const map = readJsonFile(mapPath)
108
+ if (typeof map.artifact !== "string" || !existsSync(map.artifact)) throw new CliError(`${mapPath} names no readable pre-polish artifact`)
109
+ const text = readText(map.artifact)
110
+ if (typeof map.artifact_sha256 !== "string") throw new CliError(`${mapPath} fill map has no artifact_sha256; --changed-since needs the pre-polish hash`)
111
+ if (sha256Of(text) !== map.artifact_sha256) throw new CliError(`${map.artifact} changed after fill; --changed-since needs the pre-polish bytes`)
112
+ const html = map.format === "md" ? markdownAsHtml(text, map.entries, ledger).html : text
113
+ const before = new Map()
114
+ for (const pair of extractPairs(html, ledger, claims)) before.set(pairKey(pair), (before.get(pairKey(pair)) ?? 0) + 1)
115
+ return pairs.filter((pair) => {
116
+ const left = before.get(pairKey(pair)) ?? 0
117
+ if (left === 0) return true
118
+ before.set(pairKey(pair), left - 1)
119
+ return false
120
+ })
121
+ }
122
+
123
+ /** @param {string[]} argv the arguments after `fact polish-apply` or `fact polish-diff` */
124
+ export function runFactPolish(sub, argv) {
125
+ const args = parseArgs(argv)
126
+ return sub === "polish-apply" ? runPolishApply(args) : runPolishDiff(args)
127
+ }
@@ -0,0 +1,183 @@
1
+ // fact-commands-verify.mjs - `fact verify` and the Markdown projection it checks.
2
+ //
3
+ // fact verify runs the deterministic fidelity gate over one filled HTML or Markdown edition and
4
+ // writes a fidelity record (default $SESSION_DIR/fidelity-defects.json, through
5
+ // writeFidelityRecord) carrying the artifact's sha256. It
6
+ // exits 1 on any blocker or major defect (the orchestrator then records `outcome gate fidelity
7
+ // fail`); minors alone exit 0 and are listed. A Markdown edition is read through md-lite into an
8
+ // escaped HTML projection (same line numbers) and checked with its fill map, which is the only
9
+ // record of where its values sit; LaTeX is verified through its Markdown source.
10
+ //
11
+ // The delivery barrier that reads these records (and every other fidelity record) is
12
+ // fidelity-records.mjs.
13
+
14
+ import { existsSync, readFileSync } from "node:fs"
15
+ import { dirname, join, resolve } from "node:path"
16
+
17
+ import { CliError, readJsonFile, sha256Of } from "./cli-support.mjs"
18
+ import { countDefects } from "./contracts.mjs"
19
+ import { recompute } from "./fact-derive.mjs"
20
+ import { readLedger } from "./fact-ledger.mjs"
21
+ import { validateLedger } from "./fact-ledger-schema.mjs"
22
+ import { FIDELITY_RECORD, writeFidelityRecord } from "./fidelity-records.mjs"
23
+ import { blocksDelivery, checkFidelity } from "./gates-fidelity.mjs"
24
+ import { parseMarkdown } from "./md-lite.mjs"
25
+ import { escapeHtml, parseRenderedQuote, renderQuote, renderValueHtml } from "./placeholder.mjs"
26
+ import { sessionDirOf } from "./source-commands.mjs"
27
+
28
+ function readText(path) {
29
+ try {
30
+ return readFileSync(path, "utf8")
31
+ } catch (error) {
32
+ throw new CliError(`cannot read ${path}: ${error.code ?? error.message}`)
33
+ }
34
+ }
35
+
36
+ /**
37
+ * md-lite block kind -> its projection: the element tag (null = h<level>) and, for a run of one kind,
38
+ * the container that wraps the run. Code and table rows also take the specialized bodies below.
39
+ */
40
+ export const MD_HTML_BLOCK_RULES = Object.freeze({
41
+ heading: Object.freeze({ tag: null }),
42
+ paragraph: Object.freeze({ tag: "p" }),
43
+ list_item: Object.freeze({ tag: "li", group: Object.freeze(["<ul>", "</ul>"]) }),
44
+ blockquote: Object.freeze({ tag: "blockquote" }),
45
+ code: Object.freeze({ tag: "code" }),
46
+ table_row: Object.freeze({ tag: "tr", group: Object.freeze(["<table>", "</table>"]) }),
47
+ })
48
+
49
+ /** Escaped text with inline links and code spans kept as elements, so a URL is never read as prose. */
50
+ export const projectMarkdownInline = (text) =>
51
+ escapeHtml(text)
52
+ .replace(/\[([^\]]+)\]\(([^)\s]+)\)/gu, '<a href="$2">$1</a>')
53
+ .replace(/`([^`]+)`/gu, "<code>$1</code>")
54
+
55
+ /** The element the HTML fill writes for one fill-map entry, or null when its bytes were edited since. */
56
+ function entryHtml(text, entry, rows) {
57
+ const body = text.slice(entry.start, entry.end)
58
+ if (body !== entry.rendered) return null
59
+ const row = rows.get(entry.fact_id)
60
+ if (entry.translates) return `<span data-translates="${escapeHtml(entry.translates)}">${projectMarkdownInline(body.replace(/^<!--[\s\S]*?-->\s*/u, ""))}</span>`
61
+ if (!row) return `<data data-fact="${escapeHtml(entry.fact_id)}">${escapeHtml(body)}</data>`
62
+ const quoted = row.kind === "quote" ? parseRenderedQuote(body) : null
63
+ if (quoted) return renderQuote(row, entry.selector ?? "full", quoted.text, "html")
64
+ return row.kind === "quote" ? null : renderValueHtml(row, body)
65
+ }
66
+
67
+ /** [from, to) of the filled text as HTML, each intact fill-map entry rebuilt as its fill element. */
68
+ function spanHtml(text, from, to, entries, rows, stale) {
69
+ let html = ""
70
+ let cursor = from
71
+ for (const entry of entries) {
72
+ if (entry.start < cursor || entry.end > to || entry.start < from || entry.replaced !== undefined) continue
73
+ const element = entryHtml(text, entry, rows)
74
+ if (element === null) continue
75
+ stale.delete(entry)
76
+ html += projectMarkdownInline(text.slice(cursor, entry.start)) + element
77
+ cursor = entry.end
78
+ }
79
+ return html + projectMarkdownInline(text.slice(cursor, to))
80
+ }
81
+
82
+ /**
83
+ * An escaped HTML projection of a filled Markdown edition: one element per md-lite block, each on
84
+ * the source line it starts on, lists and table rows grouped. Never a rendering for readers.
85
+ * @param {string} text the filled Markdown
86
+ * @param {{ fact_id: string, start: number, end: number, rendered: string, replaced?: string, translates?: string }[]} entries its fill map
87
+ * @param {{ rows: object[] }} ledger
88
+ * @returns {{ html: string, stale: object[] }} stale: entries whose bytes changed after fill (checked by text)
89
+ */
90
+ export function markdownAsHtml(text, entries, ledger) {
91
+ const sorted = [...(entries ?? [])].sort((a, b) => a.start - b.start)
92
+ const rows = new Map((ledger?.rows ?? []).map((row) => [row.fact_id, row]))
93
+ const stale = new Set(sorted.filter((entry) => entry.replaced === undefined))
94
+ const lineAt = (offset) => text.slice(0, offset).split("\n").length
95
+ const parsed = parseMarkdown(text)
96
+ if (parsed.problems.length > 0) {
97
+ throw new CliError(`the Markdown edition is malformed: ${parsed.problems.map((problem) => `${problem.code} at line ${problem.line}`).join(", ")}; fix it before verifying`)
98
+ }
99
+ let html = "<!doctype html><html><head><title>md</title></head><body>"
100
+ let line = 1
101
+ let open = null
102
+ for (const block of parsed.blocks) {
103
+ const rule = MD_HTML_BLOCK_RULES[block.kind]
104
+ if (rule === undefined) throw new CliError(`md-lite block kind ${block.kind} has no projection rule`)
105
+ if (open && open !== block.kind) html += MD_HTML_BLOCK_RULES[open].group[1]
106
+ const target = lineAt(block.start)
107
+ if (target > line) html += "\n".repeat(target - line)
108
+ line = Math.max(line, target) + (text.slice(block.start, block.end).match(/\n/gu)?.length ?? 0)
109
+ if (rule.group && open !== block.kind) html += rule.group[0]
110
+ open = rule.group ? block.kind : null
111
+ const inner = (from, to) => spanHtml(text, from, to, sorted, rows, stale)
112
+ if (block.kind === "table_row") html += `<${rule.tag}>${block.cells.map((cell) => `<td>${inner(cell.start, cell.end)}</td>`).join("")}</${rule.tag}>`
113
+ else if (block.kind === "code") html += `<pre><${rule.tag}>${escapeHtml(block.text)}</${rule.tag}></pre>`
114
+ else {
115
+ const tag = rule.tag ?? `h${block.level}`
116
+ const replaced = block.kind === "blockquote" ? sorted.find((entry) => entry.replaced !== undefined && entry.start <= block.start && entry.end >= block.end) : undefined
117
+ const attrs = replaced ? ` data-fact="${escapeHtml(replaced.fact_id)}" data-replaced="${escapeHtml(replaced.replaced)}"` : ""
118
+ html += `<${tag}${attrs}>${inner(block.start, block.end)}</${tag}>`
119
+ }
120
+ }
121
+ return { html: `${html}${open ? MD_HTML_BLOCK_RULES[open].group[1] : ""}</body></html>\n`, stale: [...stale] }
122
+ }
123
+
124
+ /** The session dir from --session-dir or SESSION_DIR, or null; a bare flag names no session. */
125
+ export const sessionOf = (flags) => {
126
+ const dir = flags["session-dir"] ?? process.env.SESSION_DIR
127
+ return dir && dir !== true ? resolve(dir) : null
128
+ }
129
+
130
+ /** The ledger from --ledger (validated) or from the session dir; every fact command loads it here. */
131
+ export function ledgerFrom(flags, session = sessionOf(flags)) {
132
+ if (flags.ledger === undefined) {
133
+ if (session === null) throw new CliError("--ledger is required without --session-dir (or SESSION_DIR)")
134
+ return readLedger(session)
135
+ }
136
+ const ledger = readJsonFile(flags.ledger)
137
+ const errors = validateLedger(ledger)
138
+ if (errors.length > 0) throw new CliError(`${flags.ledger} is not a valid fact ledger: ${errors[0]}`)
139
+ return ledger
140
+ }
141
+
142
+ const MARKDOWN_EXTENSION = /\.(md|markdown)$/iu
143
+ /** The edition format: the explicit flag, else md for a .md or .markdown path (any case), else html. */
144
+ export const editionFormatOf = (path, flag) => flag ?? (MARKDOWN_EXTENSION.test(path) ? "md" : "html")
145
+
146
+ /** The report's format (from --format or its extension) and, for Markdown, its fill map entries. */
147
+ export function readEdition(report, flags) {
148
+ const format = editionFormatOf(report, flags.format)
149
+ if (format !== "html" && format !== "md") throw new CliError(`--format ${format}: verify html or md (a LaTeX edition is verified through its Markdown source)`)
150
+ const text = readText(report)
151
+ if (format === "html") return { format, text, entries: flags["fill-map"] === undefined ? null : readJsonFile(flags["fill-map"]).entries }
152
+ const map = flags["fill-map"] ?? `${report}.fill-map.json`
153
+ if (!existsSync(map)) throw new CliError(`a Markdown edition needs its fill map: ${map} is missing (pass --fill-map)`)
154
+ return { format, text, entries: readJsonFile(map).entries }
155
+ }
156
+
157
+ /** @param {{ positionals: string[], flags: Record<string, any> }} args parsed by runFact */
158
+ export function runFactVerify(args) {
159
+ const [report] = args.positionals
160
+ const { flags } = args
161
+ if (!report) throw new CliError("usage: fact verify <report> [--format html|md] [--sources D] [--claims claims.json] [--fill-map F] [--brief brief.md] [--out fidelity-defects.json]")
162
+ const edition = readEdition(report, flags)
163
+ const sessionDir = sessionDirOf(flags)
164
+ const claimsPath = flags.claims ?? join(sessionDir, "claims.json")
165
+ const briefPath = flags.brief ?? join(sessionDir, "brief.md")
166
+ const ledger = readLedger(sessionDir)
167
+ const projected = edition.format === "md" ? markdownAsHtml(edition.text, edition.entries, ledger) : null
168
+ const result = checkFidelity(projected?.html ?? edition.text, {
169
+ ledger,
170
+ sourcesDir: resolve(flags.sources ?? join(sessionDir, "sources")),
171
+ claims: flags.claims !== undefined || existsSync(claimsPath) ? readJsonFile(claimsPath) : null,
172
+ fillMap: projected?.stale ?? edition.entries,
173
+ brief: existsSync(briefPath) ? readText(briefPath) : "",
174
+ baseDir: dirname(resolve(report)),
175
+ recompute,
176
+ })
177
+ const counts = countDefects(result.defects)
178
+ const status = result.defects.some(blocksDelivery) ? "fail" : "pass"
179
+ const exitCode = status === "fail" ? 1 : 0
180
+ const out = resolve(flags.out ?? join(sessionDir, FIDELITY_RECORD))
181
+ const record = writeFidelityRecord(out, "fidelity", { command: "fact verify", status, exitCode, format: edition.format, report: resolve(report), artifact_sha256: sha256Of(edition.text), defects: result.defects, counts, gatesRun: result.gatesRun })
182
+ return { json: { ...record, out }, summary: `fact verify: ${status.toUpperCase()} - ${counts.blocker} blocker, ${counts.major} major, ${counts.minor} minor`, exitCode }
183
+ }
@@ -0,0 +1,241 @@
1
+ // fact-commands.mjs - the `fact` subcommands of report-tools: add, quote, retract, export, fill,
2
+ // derive, verify (fact-commands-verify.mjs), pairs and meaning-check (fact-commands-meaning.mjs), polish-apply and
3
+ // polish-diff (fact-commands-polish.mjs). Every value reaches the ledger from a source
4
+ // snapshot through a deterministic locator or from a declared derivation; nothing here accepts a
5
+ // typed value. add and quote print the row with 60 chars of source context on each side (JSON
6
+ // with --json). Exit 0 ok, 1 semantic failure (fact_not_located, fill errors, a refused
7
+ // derivation), 2 usage. node: builtins and sibling imports only.
8
+
9
+ import { existsSync, readdirSync, readFileSync, writeFileSync } from "node:fs"
10
+ import { dirname, extname, join, resolve } from "node:path"
11
+
12
+ import { CliError, parseArgs, sha256Of, writeJsonAtomic } from "./cli-support.mjs"
13
+ import { DEFECT_CODES } from "./contracts.mjs"
14
+ import { derive } from "./fact-derive.mjs"
15
+ import { fill } from "./fact-fill.mjs"
16
+ import { addRow, readLedger, retractRow, supersedeRow, writeLedger } from "./fact-ledger.mjs"
17
+ import { locateFind, locateMatch, locatePageLine, locateTable } from "./fact-locate.mjs"
18
+ import { QUALIFIER_FORMS } from "./fidelity-lexicon.mjs"
19
+ import { readState, writeState } from "./fidelity-state.mjs"
20
+ import { exportRows } from "./fidelity-values.mjs"
21
+ import { pointedFlag, sessionDirOf, snapshotOf } from "./source-commands.mjs"
22
+
23
+ const CONTEXT = 60
24
+ const BOOLEANS = ["json", "csv", "md", "inline", "view", "replace-failed", "disjoint"]
25
+ const QUALIFIER_OF = new Map(Object.entries(QUALIFIER_FORMS).flatMap(([term, forms]) => forms.map((form) => [form, term])))
26
+ const slug = (text) => String(text).toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "").slice(0, 32) || "fact"
27
+
28
+ function readText(path) {
29
+ try {
30
+ return readFileSync(path, "utf8")
31
+ } catch (error) {
32
+ throw new CliError(`cannot read ${path}: ${error.code ?? error.message}`)
33
+ }
34
+ }
35
+
36
+ function nowIso(flags) {
37
+ const date = flags.now === undefined ? new Date() : new Date(flags.now)
38
+ if (Number.isNaN(date.getTime())) throw new CliError("--now requires an ISO timestamp")
39
+ return date.toISOString()
40
+ }
41
+
42
+ function failure(command, error, detail) {
43
+ const json = { command, status: "fail", exitCode: 1, error, ...detail }
44
+ return { json, summary: `${command}: ${error}${detail.message ? ` (${detail.message})` : ""}`, exitCode: 1 }
45
+ }
46
+
47
+ function locate(flags, snapshot, id, quote) {
48
+ const sourceId = id
49
+ if (quote) return locateMatch(snapshot.text, { match: flags.match, sourceId })
50
+ if (flags.find !== undefined) return locateFind(snapshot.text, { find: flags.find, unitExpected: flags["unit-expected"], sourceId })
51
+ if (flags.table !== undefined) {
52
+ if (snapshot.html === null) throw new CliError(`source ${id} has no source.html; --table needs a page snapshot`)
53
+ return locateTable(snapshot.html, { table: flags.table, row: flags.row, col: flags.col, sourceId })
54
+ }
55
+ if (flags.page !== undefined) return locatePageLine(snapshot.text, { page: Number(flags.page), line: Number(flags.line), sourceId })
56
+ throw new CliError("usage: fact add --source S<n> (--find TEXT | --table T --row R --col C | --page P --line L) --label LABEL")
57
+ }
58
+
59
+ function contextOf(text, row) {
60
+ const start = row.locator.char_start + (row.value_span?.[0] ?? 0)
61
+ const end = row.value_span ? row.locator.char_start + row.value_span[1] : row.locator.char_end
62
+ return { before: text.slice(Math.max(0, start - CONTEXT), start), match: text.slice(start, end), after: text.slice(end, end + CONTEXT) }
63
+ }
64
+
65
+ /** `fact add --assumed V --rationale R`: a value nobody measured, rendered ASSUMED; the rationale is required. */
66
+ function runAssumed(args) {
67
+ const sessionDir = sessionDirOf(args.flags)
68
+ const ledger = readLedger(sessionDir)
69
+ const value = String(args.flags.assumed)
70
+ const row = addRow(ledger, { kind: "assumed", value, unit: args.flags.unit ?? null, raw: value, quote: "", extracted_by: "model", observed_at: nowIso(args.flags), label: args.required("label"), rationale: args.required("rationale") })
71
+ writeLedger(sessionDir, ledger)
72
+ return { json: { command: "fact add", status: "pass", exitCode: 0, defects: [], data: { row } }, exitCode: 0 }
73
+ }
74
+
75
+ function runAdd(args, quote) {
76
+ const command = quote ? "fact quote" : "fact add"
77
+ const { flags } = args
78
+ if (!quote && flags.assumed !== undefined) return runAssumed(args)
79
+ const id = args.required("source")
80
+ if (quote) args.required("match")
81
+ const sessionDir = sessionDirOf(flags)
82
+ const snapshot = snapshotOf(sessionDir, id)
83
+ const found = locate(flags, snapshot, id, quote)
84
+ if (found.error === "unit_expected_mismatch") return failure(command, "unit_mismatch", { message: `expected ${found.expected}, found ${found.found}`, candidates: found.candidates })
85
+ if (found.error) return failure(command, found.error, { message: `no single match in ${id}`, candidates: found.candidates })
86
+ const { meta } = snapshot
87
+ const host = URL.canParse(meta.url) ? new URL(meta.url).hostname : null
88
+ const labelSource = flags.find ?? flags.match ?? (flags.table !== undefined ? `${flags.row}_${flags.col}` : `p${flags.page}_l${flags.line}`)
89
+ const ledger = readLedger(sessionDir)
90
+ const row = addRow(ledger, {
91
+ kind: quote ? "quote" : "measured",
92
+ ...(quote ? {} : { value: found.value, unit: found.unit, value_span: found.value_span, qualifier: QUALIFIER_OF.get(found.qualifier) ?? null }),
93
+ raw: found.raw,
94
+ quote: found.quote,
95
+ source_id: id,
96
+ locator: { ...found.locator, source_kind: meta.kind, url: meta.url, host, pointed: pointedFlag(sessionDir, meta.url) },
97
+ snapshot_sha256: meta.sha256,
98
+ sentence_id: found.sentence_id ?? null,
99
+ extracted_by: meta.extractor,
100
+ observed_at: nowIso(flags),
101
+ label: flags.label ?? slug(labelSource),
102
+ entity: flags.entity ?? null,
103
+ valid_at: flags["valid-at"] ?? null,
104
+ population: flags.population ?? null,
105
+ accounting_basis: flags["accounting-basis"] ?? null,
106
+ observation_id: flags.observation ?? null,
107
+ fuzzy: found.fuzzy === true,
108
+ })
109
+ writeLedger(sessionDir, ledger)
110
+ const context = contextOf(snapshot.text, row)
111
+ const json = { command, status: "pass", exitCode: 0, defects: [], data: { row, context } }
112
+ if (flags.json) return { json, exitCode: 0 }
113
+ const shown = quote ? row.quote : `${row.raw} (value ${row.value}${row.unit ? `, unit ${row.unit}` : ""})`
114
+ return { text: `${row.fact_id} ${row.label}: ${shown} [${id} ${row.sentence_id ?? "-"}]\n ...${context.before}[${context.match}]${context.after}...`, exitCode: 0 }
115
+ }
116
+
117
+ function runRetract(args) {
118
+ const [id] = args.positionals
119
+ if (!id) throw new CliError("usage: fact retract <F<n>|Q<n>> --reason TEXT [--replacement ID]")
120
+ const sessionDir = sessionDirOf(args.flags)
121
+ const ledger = readLedger(sessionDir)
122
+ const reason = args.required("reason")
123
+ const row = args.flags.replacement === undefined ? retractRow(ledger, id, reason) : supersedeRow(ledger, id, args.flags.replacement, reason)
124
+ writeLedger(sessionDir, ledger)
125
+ return { json: { command: "fact retract", status: "pass", exitCode: 0, data: { fact_id: id, status: row.status, replacement: args.flags.replacement ?? null } }, exitCode: 0 }
126
+ }
127
+
128
+ const csvCell = (value) => (/[",\n]/.test(String(value)) ? `"${String(value).replace(/"/g, '""')}"` : String(value))
129
+
130
+ /** "N of M sources snapshotted, by extractor ...": M also counts sources-ledger ids not yet snapshotted. */
131
+ function coverageLine(sessionDir) {
132
+ const dir = join(sessionDir, "sources")
133
+ const snapped = existsSync(dir) ? readdirSync(dir).filter((name) => existsSync(join(dir, name, "meta.json"))) : []
134
+ const ledgerFile = join(sessionDir, "sources-ledger.md")
135
+ const listed = existsSync(ledgerFile) ? [...readText(ledgerFile).matchAll(/^\[S(\d+)\]/gm)].map((match) => `S${match[1]}`) : []
136
+ const byExtractor = {}
137
+ for (const id of snapped) {
138
+ const extractor = JSON.parse(readText(join(dir, id, "meta.json"))).extractor
139
+ byExtractor[extractor] = (byExtractor[extractor] ?? 0) + 1
140
+ }
141
+ const total = new Set([...snapped, ...listed]).size
142
+ const parts = Object.entries(byExtractor).map(([name, count]) => `${name} ${count}`)
143
+ return `${snapped.length} of ${total} sources snapshotted, by extractor: ${parts.join(", ") || "none"}`
144
+ }
145
+
146
+ function runExport(args) {
147
+ const sessionDir = sessionDirOf(args.flags)
148
+ const ledger = readLedger(sessionDir)
149
+ const coverage = coverageLine(sessionDir)
150
+ let text
151
+ if (args.flags.csv) {
152
+ const rows = exportRows(ledger)
153
+ text = `${["fact_id,label,plot_value,unit", ...rows.map((row) => [row.fact_id, row.label, row.plot_value, row.unit].map(csvCell).join(","))].join("\n")}\n`
154
+ } else text = `${JSON.stringify({ schemaVersion: ledger.schemaVersion, rows: ledger.rows, coverage }, null, 2)}\n`
155
+ if (args.flags.out === undefined) return { text, summary: coverage, exitCode: 0 }
156
+ writeFileSync(args.flags.out, text)
157
+ return { json: { command: "fact export", status: "pass", exitCode: 0, data: { out: resolve(args.flags.out), format: args.flags.csv ? "csv" : "json", sha256: sha256Of(text), coverage } }, summary: coverage, exitCode: 0 }
158
+ }
159
+
160
+ /** The inline edition: local stylesheets and scripts inlined, relative image paths made absolute. */
161
+ function inlineAssets(html, baseDir) {
162
+ const local = (ref) => !/^[a-z][a-z0-9+.-]*:|^\/\//i.test(ref)
163
+ return html
164
+ .replace(/<link\b[^>]*\brel=["']stylesheet["'][^>]*>/gi, (tag) => {
165
+ const href = /\bhref=["']([^"']+)["']/i.exec(tag)?.[1]
166
+ return href && local(href) ? `<style>${readText(resolve(baseDir, href))}</style>` : tag
167
+ })
168
+ .replace(/<script\b([^>]*)\bsrc=["']([^"']+)["']([^>]*)><\/script>/gi, (tag, pre, src, post) => (local(src) ? `<script${pre}${post}>${readText(resolve(baseDir, src))}</script>` : tag))
169
+ .replace(/(<img\b[^>]*\bsrc=["'])([^"']+)(["'])/gi, (tag, pre, src, post) => (local(src) && !src.startsWith("/") ? `${pre}${resolve(baseDir, src)}${post}` : tag))
170
+ }
171
+
172
+ function runFill(args) {
173
+ const [source] = args.positionals
174
+ const { flags } = args
175
+ if (!source || (flags.out === undefined && !flags.view)) throw new CliError("usage: fact fill <src> (--out <path> | --view) [--format html|md|tex] [--inline] [--replace-failed]")
176
+ const format = flags.format ?? ({ ".md": "md", ".tex": "tex" }[extname(source).toLowerCase()] ?? "html")
177
+ if (!["html", "md", "tex"].includes(format)) throw new CliError("--format must be html, md, or tex")
178
+ if (flags.inline && format !== "html") throw new CliError("--inline applies to the html edition only")
179
+ const sessionDir = sessionDirOf(flags)
180
+ const state = flags["replace-failed"] ? readState(sessionDir) : null
181
+ const result = fill(readText(source), readLedger(sessionDir), { format: format === "tex" ? "md" : format, view: flags.view === true, ...(state ? { replaceFailed: state } : {}) })
182
+ const warnings = result.warnings
183
+ if (result.out === null) {
184
+ const defects = result.errors.map((error) => ({ ...error, severity: DEFECT_CODES[error.code]?.severity ?? "blocker", hint: DEFECT_CODES[error.code]?.hint ?? null }))
185
+ return { json: { command: "fact fill", status: "fail", exitCode: 1, defects, warnings }, summary: `fact fill: FAIL - ${defects.length} error(s), nothing written`, exitCode: 1 }
186
+ }
187
+ const out = flags.inline ? inlineAssets(result.out, dirname(resolve(source))) : result.out
188
+ if (flags.view) return { text: out, summary: `fact fill --view: ${result.fillMap.length} pointer(s), ${warnings.length} warning(s)`, exitCode: 0 }
189
+ writeFileSync(flags.out, out)
190
+ if (state) writeState(sessionDir, state)
191
+ const artifact = sha256Of(out)
192
+ const map = `${resolve(flags.out)}.fill-map.json`
193
+ writeJsonAtomic(map, { schemaVersion: 1, format, artifact: resolve(flags.out), artifact_sha256: artifact, entries: result.fillMap })
194
+ const data = { out: resolve(flags.out), map, artifact_sha256: artifact, occurrences: result.fillMap.length, warnings }
195
+ return { json: { command: "fact fill", status: "pass", exitCode: 0, defects: [], data }, summary: `fact fill: wrote ${flags.out} (${result.fillMap.length} pointer(s))`, exitCode: 0 }
196
+ }
197
+
198
+ function runDerive(args) {
199
+ const { flags } = args
200
+ const op = args.required("op")
201
+ const sessionDir = sessionDirOf(flags)
202
+ const ledger = readLedger(sessionDir)
203
+ // The registry refuses any field outside the operation's inputFields, so only the given flags go in; --unit null declares a dimensionless result.
204
+ const picked = ["to", "from", "num", "den", "part", "whole", "reference", "rationale", "label"].filter((key) => flags[key] !== undefined)
205
+ const spec = { op, now: nowIso(flags), ...Object.fromEntries(picked.map((key) => [key, flags[key]])) }
206
+ if (op === "expr") spec.formula = args.positionals[0]
207
+ else if (args.positionals.length > 0) spec.operands = args.positionals
208
+ if (flags.disjoint === true) spec.disjoint = true
209
+ if (flags.unit !== undefined) spec.unit = flags.unit === "null" ? null : flags.unit
210
+ const result = derive(ledger, spec)
211
+ if (result.error) return failure("fact derive", result.error, { message: result.detail, ...(result.hint ? { hint: result.hint } : {}) })
212
+ writeLedger(sessionDir, ledger)
213
+ const { row } = result
214
+ const json = { command: "fact derive", status: "pass", exitCode: 0, defects: [], data: { row, compatibility: result.compatibility } }
215
+ return flags.json ? { json, exitCode: 0 } : { text: `${row.fact_id} ${row.label}: ${row.render} (${row.operation} of ${row.operands.join(", ")}, ${result.compatibility})`, exitCode: 0 }
216
+ }
217
+
218
+ export async function runFact(argv) {
219
+ const [sub, ...rest] = argv
220
+ if (sub === "pairs" || sub === "meaning-check") return (await import("./fact-commands-meaning.mjs")).runFactMeaning(sub, rest)
221
+ if (sub === "polish-apply" || sub === "polish-diff") return (await import("./fact-commands-polish.mjs")).runFactPolish(sub, rest)
222
+ const args = parseArgs(rest, { booleans: BOOLEANS })
223
+ switch (sub) {
224
+ case "add":
225
+ return runAdd(args, false)
226
+ case "quote":
227
+ return runAdd(args, true)
228
+ case "retract":
229
+ return runRetract(args)
230
+ case "export":
231
+ return runExport(args)
232
+ case "fill":
233
+ return runFill(args)
234
+ case "derive":
235
+ return runDerive(args)
236
+ case "verify":
237
+ return (await import("./fact-commands-verify.mjs")).runFactVerify(args)
238
+ default:
239
+ throw new CliError("usage: fact <add|quote|retract|export|fill|verify|derive|pairs|meaning-check|polish-apply|polish-diff> ...")
240
+ }
241
+ }
@@ -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
+ }