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,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
+ }
@@ -0,0 +1,108 @@
1
+ // fact-expr.mjs - the expression language of `fact derive --op expr` (references/unit-rules.md, expr row).
2
+ // Leaves are fact ids and decimal literals; nodes are + - * / and pct(x), the explicit dimensionless-to-% node.
3
+ // evaluate() is exact rational arithmetic. Under * and / a child whose inferred unit is % or %p enters as its
4
+ // fraction (20% as 0.2): that unit comes from the registry's inference over the operand rows, never from the row's
5
+ // own declared unit.
6
+
7
+ import { add, div, fromDecimal, mul, sub } from "./rational.mjs"
8
+ import { isPercentish, refuse } from "./unit-algebra.mjs"
9
+
10
+ const TOKEN = /\s*(?:(F\d+)|(\d+(?:\.\d+)?)|(pct)\b|([-+*/()]))/y
11
+ const HUNDRED = fromDecimal("100")
12
+
13
+ function tokenize(formula) {
14
+ const text = String(formula ?? "")
15
+ const tokens = []
16
+ TOKEN.lastIndex = 0
17
+ while (/\S/.test(text.slice(TOKEN.lastIndex))) {
18
+ const at = TOKEN.lastIndex
19
+ const match = TOKEN.exec(text)
20
+ if (!match) refuse(`unsupported expression text at ${at}: "${text.slice(at).trim().slice(0, 16)}"`)
21
+ tokens.push(match[1] ? { id: match[1] } : match[2] ? { literal: match[2] } : match[3] ? { fn: "pct" } : { op: match[4] })
22
+ }
23
+ if (tokens.length === 0) refuse("expr needs an expression")
24
+ return tokens
25
+ }
26
+
27
+ /** Shunting-yard parser: parentheses and AST depth do not consume the JS call stack. */
28
+ export function parseExpr(formula) {
29
+ if (typeof formula !== "string") refuse("expr needs an expression")
30
+ const tokens = tokenize(formula)
31
+ const values = []
32
+ const ops = []
33
+ const precedence = (op) => op === "*" || op === "/" ? 2 : 1
34
+ const reduce = () => {
35
+ const op = ops.pop()
36
+ if (op === "pct") values.push({ fn: op, arg: values.pop() })
37
+ else { const right = values.pop(); values.push({ op, left: values.pop(), right }) }
38
+ }
39
+ let need = true
40
+ for (let i = 0; i < tokens.length; i++) {
41
+ const token = tokens[i]
42
+ if (need) {
43
+ if (token.id || token.literal) { values.push(token); need = false }
44
+ else if (token.fn) {
45
+ if (tokens[i + 1]?.op !== "(") refuse("pct needs a parenthesized argument")
46
+ ops.push("pct")
47
+ } else if (token.op === "(") ops.push("(")
48
+ else refuse(`unexpected "${token.op}" in the expression`)
49
+ } else if (token.op === ")") {
50
+ while (ops.length && ops.at(-1) !== "(") reduce()
51
+ if (ops.pop() !== "(") refuse("unexpected closing parenthesis")
52
+ if (ops.at(-1) === "pct") reduce()
53
+ } else if (["+", "-", "*", "/"].includes(token.op)) {
54
+ while (ops.length && ops.at(-1) !== "(" && precedence(ops.at(-1)) >= precedence(token.op)) reduce()
55
+ ops.push(token.op)
56
+ need = true
57
+ } else refuse("unexpected text after the expression")
58
+ }
59
+ if (need) refuse("the expression ends early")
60
+ if (ops.includes("(")) refuse("the expression is missing a closing parenthesis")
61
+ while (ops.length) reduce()
62
+ return values[0]
63
+ }
64
+
65
+ const childrenOf = (node) => (node.op ? [node.left, node.right] : node.fn ? [node.arg] : [])
66
+
67
+ export function leaves(node, found = { ids: [], literals: [] }) {
68
+ const ids = new Set(found.ids)
69
+ const stack = [node]
70
+ while (stack.length) {
71
+ const current = stack.pop()
72
+ if (current.id) ids.add(current.id)
73
+ if (current.literal) found.literals.push(current.literal)
74
+ stack.push(...childrenOf(current).reverse())
75
+ }
76
+ found.ids = [...ids]
77
+ return found
78
+ }
79
+
80
+ export function postorder(node) {
81
+ const stack = [node]
82
+ const order = []
83
+ while (stack.length) { const current = stack.pop(); order.push(current); stack.push(...childrenOf(current)) }
84
+ return order.reverse()
85
+ }
86
+
87
+ /** Exact value of the tree; `units` maps each node to its inferred canonical unit (inferExpr in unit-rules.mjs). */
88
+ export function evaluate(node, values, units) {
89
+ const results = new Map()
90
+ for (const current of postorder(node)) {
91
+ let value
92
+ if (current.id) value = values.get(current.id)
93
+ else if (current.literal) value = fromDecimal(current.literal)
94
+ else if (current.fn === "pct") value = mul(results.get(current.arg), HUNDRED)
95
+ else {
96
+ const operand = (child) => {
97
+ const v = results.get(child)
98
+ return (current.op === "*" || current.op === "/") && isPercentish(units.get(child)) ? div(v, HUNDRED) : v
99
+ }
100
+ const left = operand(current.left)
101
+ const right = operand(current.right)
102
+ if (current.op === "/" && right.num === 0n) refuse("the expression divides by zero", undefined, "divides_by_zero", "a nonzero denominator")
103
+ value = { "+": add, "-": sub, "*": mul, "/": div }[current.op](left, right)
104
+ }
105
+ results.set(current, value)
106
+ }
107
+ return results.get(node)
108
+ }
@@ -0,0 +1,194 @@
1
+ // fact-fill.mjs - the fill step: substitute every fact pointer in a report source from the
2
+ // ledger, all or nothing. Fatal errors (pointer_label_mismatch, unresolved_placeholder,
3
+ // quote_anchor_unresolved, unit_duplicated) return out null; warnings (particle_unverified,
4
+ // fallback_without_entity) never block. A twice-failed sentence can be replaced by an attributed
5
+ // block quote of its source row (replaceFailed). No IO, but not pure: unless options.view is set,
6
+ // it records each replaced sentence in options.replaceFailed through markReplaced. node: builtins only.
7
+
8
+ import { finalReading, rewriteParticle } from "./particles-ko.mjs"
9
+ import { failureCount, markReplaced, sentenceKey } from "./fidelity-state.mjs"
10
+ import { findPlaceholders, lead, qualifiedText, renderBlockquote, renderQuote, renderValueHtml, subSpan, valueParts } from "./placeholder.mjs"
11
+ import { splitSentences } from "./sentence-split.mjs"
12
+
13
+ const FORMATS = Object.freeze(["html", "md"])
14
+ const TRANSLATION = Object.freeze({
15
+ html: /^\s*<span data-translates="(Q\d+)">[\s\S]*?<\/span>/u,
16
+ md: /^\s*<!-- translates: (Q\d+) -->[^\n]*/u,
17
+ })
18
+ const HEADING = Object.freeze({ html: /<h[1-6]\b([^>]*)>([\s\S]*?)<\/h[1-6]>/giu, md: /^#{1,6}[ \t]+(.+)$/gmu })
19
+
20
+ const lineOf = (text, at) => text.slice(0, at).split("\n").length
21
+
22
+ function resolveValue(row, parsed, format) {
23
+ const { valueText, unitText } = valueParts(row)
24
+ const text = { full: row.raw, value: valueText, unit: unitText, q: qualifiedText(row) }[parsed.mode]
25
+ if (text === null) return { code: "quote_anchor_unresolved", message: `${row.fact_id} has qualifier ${row.qualifier}, but its quote holds no qualifier beside "${row.raw}"` }
26
+ return { row, parsed, visible: text, rendered: format === "html" ? renderValueHtml(row, text) : text }
27
+ }
28
+
29
+ function resolveQuote(row, parsed, format) {
30
+ const whole = { text: row.quote, start: 0, end: row.quote.length }
31
+ const selection = parsed.mode === "lead" ? lead(row.quote, parsed.lead) : parsed.mode === "span" ? subSpan(row.quote, parsed.from, parsed.to) : whole
32
+ if (selection === null) {
33
+ return { code: "quote_anchor_unresolved", message: `from "${parsed.from}" and to "${parsed.to}" must each occur exactly once in ${row.fact_id}'s quote, in that order` }
34
+ }
35
+ const rendered = renderQuote(row, parsed.selector, selection.text, format)
36
+ return { row, parsed, visible: `[${row.source_id}]`, rendered, selector: parsed.selector, quote_span: [selection.start, selection.end] }
37
+ }
38
+
39
+ function resolve(found, rows, format) {
40
+ const { parsed } = found
41
+ if (parsed.error) return { code: "unresolved_placeholder", message: parsed.error }
42
+ const row = rows.get(parsed.id)
43
+ if (row === undefined) return { code: "unresolved_placeholder", message: `${parsed.id} is not in the fact ledger` }
44
+ if (row.status !== "active") return { code: "unresolved_placeholder", message: `${parsed.id} is ${row.status}; cite its replacement` }
45
+ if (row.label !== parsed.label) return { code: "pointer_label_mismatch", message: `${parsed.id} has label "${row.label}", not "${parsed.label}"` }
46
+ return parsed.kind === "Q" ? resolveQuote(row, parsed, format) : resolveValue(row, parsed, format)
47
+ }
48
+
49
+ /** A unit typed right after a pointer that already renders it: never absorbed, always an error. */
50
+ function unitDuplicated(text, item) {
51
+ if (item.parsed.kind !== "F" || (item.parsed.mode !== "full" && item.parsed.mode !== "q")) return false
52
+ const suffix = item.row.raw.replace(/^[\s\S]*\d/u, "").trim()
53
+ const units = [item.row.unit, valueParts(item.row).unitText, suffix].filter((unit) => typeof unit === "string" && unit !== "")
54
+ const following = text.slice(item.end).replace(/^[ \t]+/u, "")
55
+ return units.some((unit) => following.startsWith(unit) && !/[A-Za-z0-9]/u.test(following[unit.length] ?? ""))
56
+ }
57
+
58
+ /*
59
+ * Sentence-key rules (fidelity-state.mjs sentenceKey(slug, ids)); any other producer of keys must match:
60
+ * - slug source: an HTML heading's id attribute wins over its text; md headings use their text.
61
+ * - slugify: strip tags, lowercase, turn each run of non-letters/digits (\p{L}\p{N}) into "-",
62
+ * trim leading and trailing "-".
63
+ * - a sentence takes the nearest heading that starts strictly before the sentence's start offset;
64
+ * a sentence before the first heading gets slug "".
65
+ * - fact ids cited in the sentence are de-duplicated before sentenceKey sorts them.
66
+ */
67
+ function slugify(text) {
68
+ return text
69
+ .replace(/<[^>]*>/gu, "")
70
+ .toLowerCase()
71
+ .replace(/[^\p{L}\p{N}]+/gu, "-")
72
+ .replace(/^-+|-+$/gu, "")
73
+ }
74
+
75
+ function headings(text, format) {
76
+ return [...text.matchAll(HEADING[format])].map((match) => {
77
+ const id = format === "html" ? /\bid="([^"]*)"/u.exec(match[1])?.[1] : undefined
78
+ return { at: match.index, slug: slugify(id ?? match[match.length - 1]) }
79
+ })
80
+ }
81
+
82
+ /** Sentence offsets over the source with pointers, comments and (html) tags blanked to equal length. */
83
+ function sentencesOf(text, found, format) {
84
+ let masked = text
85
+ for (const item of found) masked = masked.slice(0, item.start) + "x".repeat(item.end - item.start) + masked.slice(item.end)
86
+ const blank = (part) => part.replace(/[^\n]/gu, " ")
87
+ masked = masked.replace(/<!--[\s\S]*?-->/gu, blank)
88
+ if (format === "html") masked = masked.replace(/<[^>]*>/gu, blank)
89
+ return splitSentences(masked)
90
+ }
91
+
92
+ function replacements(text, found, items, options, warnings) {
93
+ const { format, replaceFailed: state } = options
94
+ const marks = headings(text, format)
95
+ const out = []
96
+ for (const sentence of sentencesOf(text, found, format)) {
97
+ const inside = items.filter((item) => item.start >= sentence.start && item.end <= sentence.end)
98
+ if (inside.length === 0) continue
99
+ const slug = marks.filter((mark) => mark.at < sentence.start).at(-1)?.slug ?? ""
100
+ const key = sentenceKey(slug, [...new Set(inside.map((item) => item.row.fact_id))])
101
+ if (failureCount(state, key) < 2) continue
102
+ const rows = inside.map((item) => item.row)
103
+ const source = rows.find((row) => row.kind === "quote") ?? rows.find((row) => row.source_id !== null && row.quote !== "")
104
+ if (source === undefined) continue
105
+ let rendered = options.fallback === "omit" ? "" : renderBlockquote(source, key, format)
106
+ if (rendered !== "" && format === "md") {
107
+ if (sentence.start > 0 && text[sentence.start - 1] !== "\n") rendered = `\n\n${rendered}`
108
+ if (sentence.end < text.length && text[sentence.end] !== "\n") rendered = `${rendered}\n\n`
109
+ }
110
+ if (rendered !== "" && source.entity === null) {
111
+ warnings.push({ code: "fallback_without_entity", token: key, line: lineOf(text, sentence.start), message: `${source.fact_id} has no entity; the block quote cites [${source.source_id}] alone` })
112
+ }
113
+ out.push({ start: sentence.start, end: sentence.end, key, source, rendered })
114
+ }
115
+ return out
116
+ }
117
+
118
+ /** The text after a pointer: rewrite a following Korean particle, and map a translation span. */
119
+ function afterPlaceholder(segment, item, context) {
120
+ const { format, text, warnings, fillMap } = context
121
+ const probeEnd = format === "html" && segment.includes("<") ? segment.indexOf("<") : segment.length
122
+ const result = rewriteParticle(segment.slice(0, probeEnd), finalReading(item.visible, null))
123
+ let next = segment
124
+ if (result.applied) next = result.text + segment.slice(probeEnd)
125
+ else if (result.candidate) {
126
+ warnings.push({ code: "particle_unverified", token: item.token, line: lineOf(text, item.start), message: `the particle after ${item.token} was not rewritten; check it by eye` })
127
+ }
128
+ const translation = item.parsed.kind === "Q" ? TRANSLATION[format].exec(next) : null
129
+ if (translation !== null && translation[1] === item.row.fact_id) {
130
+ const span = translation[0].trimStart()
131
+ const start = context.out.length + translation[0].length - span.length
132
+ fillMap.push({ token: span, fact_id: translation[1], translates: translation[1], start, end: start + span.length, rendered: span })
133
+ }
134
+ return next
135
+ }
136
+
137
+ function assemble(text, parts, context) {
138
+ let cursor = 0
139
+ let previous = null
140
+ const flush = (until) => {
141
+ const segment = text.slice(cursor, until)
142
+ context.out += previous?.parsed ? afterPlaceholder(segment, previous, context) : segment
143
+ }
144
+ for (const part of parts) {
145
+ flush(part.start)
146
+ const start = context.out.length
147
+ if (part.parsed) {
148
+ const extra = part.selector === undefined ? {} : { selector: part.selector, quote_span: part.quote_span }
149
+ context.fillMap.push({ token: part.token, fact_id: part.row.fact_id, start, end: start + part.rendered.length, rendered: part.rendered, ...extra })
150
+ } else if (part.rendered !== "") {
151
+ context.fillMap.push({ token: null, fact_id: part.source.fact_id, replaced: part.key, start, end: start + part.rendered.length, rendered: part.rendered })
152
+ }
153
+ context.out += part.rendered
154
+ cursor = part.end
155
+ previous = part
156
+ }
157
+ flush(text.length)
158
+ return context.out
159
+ }
160
+
161
+ /**
162
+ * @param {string} sourceText the report source with {{F..}} / {{Q..}} pointers
163
+ * @param {{ rows: object[] }} ledger
164
+ * @param {{ format?: "html"|"md", replaceFailed?: object, fallback?: "quote"|"omit", view?: boolean }} [options]
165
+ * @returns {{ out: string|null, fillMap: object[], errors: object[], warnings: object[] }}
166
+ */
167
+ export function fill(sourceText, ledger, options = {}) {
168
+ const format = options.format ?? "html"
169
+ if (!FORMATS.includes(format)) throw new TypeError(`format must be one of ${FORMATS.join(", ")}`)
170
+ const rows = new Map(ledger.rows.map((row) => [row.fact_id, row]))
171
+ const found = findPlaceholders(sourceText)
172
+ const errors = []
173
+ const warnings = []
174
+ const items = []
175
+ for (const pointer of found) {
176
+ const resolved = resolve(pointer, rows, format)
177
+ const line = lineOf(sourceText, pointer.start)
178
+ if (resolved.code) errors.push({ code: resolved.code, token: pointer.token, line, message: resolved.message })
179
+ else {
180
+ const item = { ...pointer, ...resolved }
181
+ if (unitDuplicated(sourceText, item)) {
182
+ errors.push({ code: "unit_duplicated", token: pointer.token, line, message: `${pointer.token} already renders its unit; use {{${item.row.fact_id}:${item.row.label}:value}}` })
183
+ } else items.push(item)
184
+ }
185
+ }
186
+ if (errors.length > 0) return { out: null, fillMap: [], errors, warnings }
187
+ const replaced = options.replaceFailed ? replacements(sourceText, found, items, { ...options, format }, warnings) : []
188
+ const kept = items.filter((item) => !replaced.some((block) => item.start >= block.start && item.end <= block.end))
189
+ const parts = [...kept, ...replaced].sort((a, b) => a.start - b.start)
190
+ const context = { format, text: sourceText, warnings, fillMap: [], out: "" }
191
+ const out = assemble(sourceText, parts, context)
192
+ if (!options.view) for (const block of replaced) markReplaced(options.replaceFailed, block.key)
193
+ return { out, fillMap: context.fillMap, errors, warnings }
194
+ }