oh-my-opencode 5.1.48 → 5.1.51

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 (495) hide show
  1. package/dist/agents/refusal-rule.d.ts +7 -0
  2. package/dist/cli/index.js +1194 -752
  3. package/dist/cli/service-auth/accounts-gate.d.ts +11 -0
  4. package/dist/cli/service-auth/commands.d.ts +2 -4
  5. package/dist/cli/service-auth/index.d.ts +1 -1
  6. package/dist/cli/service-auth/keystore.d.ts +26 -2
  7. package/dist/cli/service-auth/protocol.d.ts +7 -1
  8. package/dist/cli/service-auth/session.d.ts +6 -0
  9. package/dist/cli/service-auth/signed-out-marker.d.ts +11 -0
  10. package/dist/cli-node/index.js +1194 -752
  11. package/dist/config/schema/hooks.d.ts +1 -0
  12. package/dist/create-hooks.d.ts +1 -0
  13. package/dist/features/boulder-state/storage.d.ts +1 -1
  14. package/dist/features/boulder-state/types.d.ts +1 -1
  15. package/dist/hooks/atlas/idle-continuation.d.ts +2 -1
  16. package/dist/hooks/atlas/types.d.ts +9 -1
  17. package/dist/hooks/index.d.ts +1 -0
  18. package/dist/hooks/momus-review-persistence/hook.d.ts +19 -0
  19. package/dist/hooks/momus-review-persistence/index.d.ts +1 -0
  20. package/dist/hooks/momus-review-persistence/review-resolution.d.ts +20 -0
  21. package/dist/hooks/prometheus-md-only/constants.d.ts +2 -0
  22. package/dist/hooks/prometheus-md-only/fixtures/denial-log.d.ts +1 -0
  23. package/dist/hooks/prometheus-md-only/fixtures/guarded-hashline.d.ts +1 -0
  24. package/dist/hooks/prometheus-md-only/fixtures/invoke-before.d.ts +7 -0
  25. package/dist/hooks/prometheus-md-only/fixtures/opencode-patch-parser.d.ts +2 -0
  26. package/dist/hooks/prometheus-md-only/hook.d.ts +5 -1
  27. package/dist/hooks/prometheus-md-only/path-policy.d.ts +2 -9
  28. package/dist/hooks/prometheus-md-only/vendor/opencode-patch-parser.d.ts +22 -0
  29. package/dist/index.js +2322 -1045
  30. package/dist/plugin/hooks/create-core-hooks.d.ts +1 -0
  31. package/dist/plugin/hooks/create-tool-guard-hooks.d.ts +2 -1
  32. package/dist/shared/posthog-activity-state.d.ts +1 -0
  33. package/dist/shared/posthog.d.ts +1 -2
  34. package/dist/shared/resolve-file-write-path.d.ts +6 -0
  35. package/dist/skills/ast-grep/README.md +1 -1
  36. package/dist/skills/ast-grep/install.ps1 +5 -3
  37. package/dist/skills/ast-grep/install.sh +4 -5
  38. package/dist/skills/ast-grep/references/install.md +2 -2
  39. package/dist/skills/ast-grep/scripts/ast_grep_helper.py +13 -3
  40. package/dist/skills/browser/runtime/omowright/manifest.json +1 -1
  41. package/dist/skills/coding-agent-sessions/AGENTS.md +5 -4
  42. package/dist/skills/coding-agent-sessions/SKILL.md +11 -11
  43. package/dist/skills/coding-agent-sessions/references/all-platforms.md +2 -2
  44. package/dist/skills/coding-agent-sessions/references/codex.md +4 -4
  45. package/dist/skills/coding-agent-sessions/scripts/agent_sessions/cli.py +1 -1
  46. package/dist/skills/coding-agent-sessions/scripts/find-agent-sessions.py +6 -3
  47. package/dist/skills/frontend/ATTRIBUTION.md +1 -1
  48. package/dist/skills/frontend/SKILL.md +34 -12
  49. package/dist/skills/frontend/references/design/README.md +2 -2
  50. package/dist/skills/frontend/references/design/_INDEX.md +2 -1
  51. package/dist/skills/frontend/references/design/component-catalogs.md +14 -10
  52. package/dist/skills/frontend/references/design/design-system-architecture.md +19 -6
  53. package/dist/skills/frontend/references/design/grounding.md +29 -0
  54. package/dist/skills/frontend/references/ui-ux-db/scripts/design_system.py +2 -0
  55. package/dist/skills/frontend/references/ui-ux-db/scripts/search.py +2 -0
  56. package/dist/skills/ultimate-browsing/SKILL.md +5 -4
  57. package/dist/skills/ultimate-browsing/engine/AGENTS.md +2 -2
  58. package/dist/skills/ultimate-browsing/engine/__main__.py +9 -6
  59. package/dist/skills/ultimate-browsing/engine/summary.py +1 -1
  60. package/dist/skills/ultimate-browsing/references/insane-search/README.md +4 -4
  61. package/dist/skills/ultimate-browsing/scripts/extract_cookies.py +4 -2
  62. package/dist/skills/ulw-research/AGENTS.md +8 -4
  63. package/dist/skills/ulw-research/SKILL.md +7 -316
  64. package/dist/skills/ulw-research/references/deliverable-phase.md +23 -21
  65. package/dist/skills/ulw-research/references/fact-ledger-grammar.md +7 -3
  66. package/dist/skills/ulw-research/references/fact-ledger.md +42 -8
  67. package/dist/skills/ulw-research/references/fidelity-claim-kinds.md +1 -1
  68. package/dist/skills/ulw-research/references/markdown-blocks.md +12 -0
  69. package/dist/skills/ulw-research/references/report-gates.md +12 -5
  70. package/dist/skills/ulw-research/references/research-collection.md +195 -0
  71. package/dist/skills/ulw-research/references/research-delivery.md +123 -0
  72. package/dist/skills/ulw-research/scripts/claim-kinds.mjs +33 -5
  73. package/dist/skills/ulw-research/scripts/claim-kinds.test.ts +37 -2
  74. package/dist/skills/ulw-research/scripts/claim-prefix.test.ts +76 -0
  75. package/dist/skills/ulw-research/scripts/contracts-validation.test.ts +83 -0
  76. package/dist/skills/ulw-research/scripts/contracts.mjs +13 -4
  77. package/dist/skills/ulw-research/scripts/contracts.test.ts +6 -110
  78. package/dist/skills/ulw-research/scripts/conversion-altered.test.ts +154 -0
  79. package/dist/skills/ulw-research/scripts/conversion-originals.test.ts +297 -0
  80. package/dist/skills/ulw-research/scripts/conversion-probe-classes.test.ts +361 -0
  81. package/dist/skills/ulw-research/scripts/conversion-probe.mjs +229 -0
  82. package/dist/skills/ulw-research/scripts/conversion-probe.test.ts +218 -0
  83. package/dist/skills/ulw-research/scripts/copy-returns-polish.test.ts +4 -3
  84. package/dist/skills/ulw-research/scripts/css-hiding.mjs +158 -0
  85. package/dist/skills/ulw-research/scripts/css-lite.mjs +4 -2
  86. package/dist/skills/ulw-research/scripts/delivery-barrier-meaning.test.ts +186 -0
  87. package/dist/skills/ulw-research/scripts/delivery-barrier.test.ts +40 -87
  88. package/dist/skills/ulw-research/scripts/entities.mjs +13 -4
  89. package/dist/skills/ulw-research/scripts/fact-commands-fill.mjs +11 -1
  90. package/dist/skills/ulw-research/scripts/fact-commands-meaning.mjs +13 -9
  91. package/dist/skills/ulw-research/scripts/fact-commands-polish.mjs +40 -18
  92. package/dist/skills/ulw-research/scripts/fact-commands-verify.mjs +25 -8
  93. package/dist/skills/ulw-research/scripts/fact-commands.mjs +35 -16
  94. package/dist/skills/ulw-research/scripts/fact-derive-2.test.ts +149 -0
  95. package/dist/skills/ulw-research/scripts/fact-derive.mjs +7 -2
  96. package/dist/skills/ulw-research/scripts/fact-derive.test.ts +7 -192
  97. package/dist/skills/ulw-research/scripts/fact-fill.mjs +6 -4
  98. package/dist/skills/ulw-research/scripts/fact-ledger-kinds.mjs +6 -7
  99. package/dist/skills/ulw-research/scripts/fact-ledger-kinds.test.ts +2 -1
  100. package/dist/skills/ulw-research/scripts/fact-ledger-legacy.test.ts +2 -1
  101. package/dist/skills/ulw-research/scripts/fact-locate.mjs +15 -9
  102. package/dist/skills/ulw-research/scripts/fact-unit-dimensions-2.test.ts +149 -0
  103. package/dist/skills/ulw-research/scripts/fact-unit-dimensions.test.ts +4 -168
  104. package/dist/skills/ulw-research/scripts/fact-unit-rules.test.ts +3 -229
  105. package/dist/skills/ulw-research/scripts/fidelity-claims.mjs +4 -3
  106. package/dist/skills/ulw-research/scripts/fidelity-csv.mjs +32 -0
  107. package/dist/skills/ulw-research/scripts/fidelity-lexicon.mjs +1 -1
  108. package/dist/skills/ulw-research/scripts/fidelity-originals.mjs +161 -0
  109. package/dist/skills/ulw-research/scripts/fidelity-policy.mjs +4 -4
  110. package/dist/skills/ulw-research/scripts/fidelity-records.mjs +81 -14
  111. package/dist/skills/ulw-research/scripts/fidelity-snapshot-readability.mjs +49 -0
  112. package/dist/skills/ulw-research/scripts/fidelity-sources.mjs +76 -0
  113. package/dist/skills/ulw-research/scripts/fidelity-trace.mjs +25 -11
  114. package/dist/skills/ulw-research/scripts/fidelity-value-unit.mjs +202 -0
  115. package/dist/skills/ulw-research/scripts/fidelity-values.mjs +40 -39
  116. package/dist/skills/ulw-research/scripts/fidelity-values.test.ts +8 -29
  117. package/dist/skills/ulw-research/scripts/gates-fidelity.mjs +8 -6
  118. package/dist/skills/ulw-research/scripts/gates-static-headings.test.ts +17 -0
  119. package/dist/skills/ulw-research/scripts/gates-static.mjs +10 -1
  120. package/dist/skills/ulw-research/scripts/html-lite.mjs +6 -2
  121. package/dist/skills/ulw-research/scripts/html-markup.mjs +2 -2
  122. package/dist/skills/ulw-research/scripts/html-text-map.mjs +140 -0
  123. package/dist/skills/ulw-research/scripts/html-text-map.test.ts +148 -0
  124. package/dist/skills/ulw-research/scripts/markdown-closers.mjs +126 -0
  125. package/dist/skills/ulw-research/scripts/md-lite-boundaries.test.ts +46 -0
  126. package/dist/skills/ulw-research/scripts/md-lite.mjs +1 -1
  127. package/dist/skills/ulw-research/scripts/number-grammar.mjs +33 -0
  128. package/dist/skills/ulw-research/scripts/number-grammar.test.ts +36 -0
  129. package/dist/skills/ulw-research/scripts/numerals.mjs +18 -7
  130. package/dist/skills/ulw-research/scripts/numerals.test.ts +3 -0
  131. package/dist/skills/ulw-research/scripts/outcome-start.mjs +66 -0
  132. package/dist/skills/ulw-research/scripts/outcome-start.test.ts +109 -0
  133. package/dist/skills/ulw-research/scripts/outcome.mjs +11 -22
  134. package/dist/skills/ulw-research/scripts/page-source-locator.test.ts +187 -0
  135. package/dist/skills/ulw-research/scripts/placeholder.mjs +18 -6
  136. package/dist/skills/ulw-research/scripts/polish-apply.mjs +3 -2
  137. package/dist/skills/ulw-research/scripts/polish-markup.test.ts +15 -1
  138. package/dist/skills/ulw-research/scripts/polish-partial.test.ts +42 -0
  139. package/dist/skills/ulw-research/scripts/raw-decode.mjs +110 -0
  140. package/dist/skills/ulw-research/scripts/raw-locator.mjs +47 -0
  141. package/dist/skills/ulw-research/scripts/raw-snapshot.test.ts +139 -0
  142. package/dist/skills/ulw-research/scripts/report-tools-2.test.ts +233 -0
  143. package/dist/skills/ulw-research/scripts/report-tools-3.test.ts +141 -0
  144. package/dist/skills/ulw-research/scripts/report-tools-4.test.ts +135 -0
  145. package/dist/skills/ulw-research/scripts/report-tools-5.test.ts +231 -0
  146. package/dist/skills/ulw-research/scripts/report-tools-commands.mjs +1 -1
  147. package/dist/skills/ulw-research/scripts/report-tools.mjs +7 -1
  148. package/dist/skills/ulw-research/scripts/report-tools.test.ts +5 -800
  149. package/dist/skills/ulw-research/scripts/snapshot-readability.test.ts +116 -0
  150. package/dist/skills/ulw-research/scripts/source-commands.mjs +79 -26
  151. package/dist/skills/ulw-research/scripts/source-integrity.test.ts +243 -0
  152. package/dist/skills/ulw-research/scripts/source-refresh.mjs +82 -0
  153. package/dist/skills/ulw-research/scripts/source-snapshot.mjs +73 -69
  154. package/dist/skills/ulw-research/scripts/source-snapshot.test.ts +7 -4
  155. package/dist/skills/ulw-research/scripts/source-view.mjs +89 -0
  156. package/dist/skills/ulw-research/scripts/sources-citation.mjs +203 -0
  157. package/dist/skills/ulw-research/scripts/sources-citation.test.ts +236 -0
  158. package/dist/skills/ulw-research/scripts/tests/contracts-support.ts +147 -0
  159. package/dist/skills/ulw-research/scripts/tests/delivery-barrier-session.ts +59 -0
  160. package/dist/skills/ulw-research/scripts/tests/derive-clock.ts +4 -0
  161. package/dist/skills/ulw-research/scripts/tests/fact-derive-support.ts +65 -0
  162. package/dist/skills/ulw-research/scripts/tests/fact-unit-dimensions-support.ts +41 -0
  163. package/dist/skills/ulw-research/scripts/tests/fact-unit-rules-support.ts +241 -0
  164. package/dist/skills/ulw-research/scripts/tests/fidelity-values-support.ts +30 -0
  165. package/dist/skills/ulw-research/scripts/tests/fixtures/conversion-probe.html +14 -0
  166. package/dist/skills/ulw-research/scripts/tests/fixtures/sources-shapes.html +20 -0
  167. package/dist/skills/ulw-research/scripts/tests/numeral-classes.ts +196 -0
  168. package/dist/skills/ulw-research/scripts/tests/report-tools-support.ts +182 -0
  169. package/dist/skills/ulw-research/scripts/tests/value-mode-support.ts +48 -0
  170. package/dist/skills/ulw-research/scripts/units.mjs +14 -3
  171. package/dist/skills/ulw-research/scripts/units.test.ts +10 -0
  172. package/dist/skills/ulw-research/scripts/value-mode-unit-classes.test.ts +255 -0
  173. package/dist/skills/ulw-research/scripts/value-mode-unit.test.ts +179 -0
  174. package/dist/skills/ulw-research/scripts/value-rules-cli.test.ts +157 -0
  175. package/dist/skills/ulw-research/scripts/value-rules-followups.test.ts +72 -0
  176. package/dist/skills/ulw-research/scripts/value-rules-priority.test.ts +80 -0
  177. package/dist/tools/delegate-task/executor-types.d.ts +1 -0
  178. package/dist/tools/delegate-task/sync-session-existence.d.ts +4 -0
  179. package/dist/tools/delegate-task/types.d.ts +1 -0
  180. package/dist/tools/skill/description-formatter.d.ts +2 -1
  181. package/dist/tools/skill/types.d.ts +6 -0
  182. package/dist/tui.js +131 -33
  183. package/package.json +18 -15
  184. package/packages/git-bash-mcp/package.json +1 -1
  185. package/packages/omo-codex/plugin/.codex-plugin/plugin.json +1 -1
  186. package/packages/omo-codex/plugin/README.md +1 -1
  187. package/packages/omo-codex/plugin/components/bootstrap/hooks/hooks.json +1 -1
  188. package/packages/omo-codex/plugin/components/bootstrap/package.json +1 -1
  189. package/packages/omo-codex/plugin/components/comment-checker/hooks/hooks.json +1 -1
  190. package/packages/omo-codex/plugin/components/comment-checker/package.json +1 -1
  191. package/packages/omo-codex/plugin/components/git-bash/hooks/hooks.json +2 -2
  192. package/packages/omo-codex/plugin/components/git-bash/package.json +1 -1
  193. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/hooks/hooks.json +1 -1
  194. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/package.json +1 -1
  195. package/packages/omo-codex/plugin/components/lcx/skills/lcx-contribute-bug-fix/SKILL.md +51 -55
  196. package/packages/omo-codex/plugin/components/lcx/skills/lcx-contribute-bug-fix/agents/openai.yaml +1 -1
  197. package/packages/omo-codex/plugin/components/lcx/skills/lcx-report-bug/SKILL.md +7 -7
  198. package/packages/omo-codex/plugin/components/lcx/skills/lcx-report-bug/agents/openai.yaml +1 -1
  199. package/packages/omo-codex/plugin/components/lsp/dist/.omo-runtime-manifest.json +2 -2
  200. package/packages/omo-codex/plugin/components/lsp/hooks/hooks.json +2 -2
  201. package/packages/omo-codex/plugin/components/lsp/package.json +1 -1
  202. package/packages/omo-codex/plugin/components/rules/hooks/hooks.json +4 -4
  203. package/packages/omo-codex/plugin/components/rules/package.json +1 -1
  204. package/packages/omo-codex/plugin/components/teammode/hooks/hooks.json +1 -1
  205. package/packages/omo-codex/plugin/components/teammode/package.json +2 -2
  206. package/packages/omo-codex/plugin/components/telemetry/README.md +2 -2
  207. package/packages/omo-codex/plugin/components/telemetry/dist/cli.js +52 -15
  208. package/packages/omo-codex/plugin/components/telemetry/dist/posthog.js +52 -19
  209. package/packages/omo-codex/plugin/components/telemetry/hooks/hooks.json +1 -1
  210. package/packages/omo-codex/plugin/components/telemetry/package.json +1 -1
  211. package/packages/omo-codex/plugin/components/telemetry/src/codex-hook.ts +2 -9
  212. package/packages/omo-codex/plugin/components/telemetry/src/posthog.ts +4 -13
  213. package/packages/omo-codex/plugin/components/telemetry/src/product-identity.ts +0 -2
  214. package/packages/omo-codex/plugin/components/telemetry/test/codex-hook-diagnostics.test.ts +0 -3
  215. package/packages/omo-codex/plugin/components/telemetry/test/codex-hook.test.ts +3 -8
  216. package/packages/omo-codex/plugin/components/ultrawork/dist/cli.js +3 -0
  217. package/packages/omo-codex/plugin/components/ultrawork/hooks/hooks.json +1 -1
  218. package/packages/omo-codex/plugin/components/ultrawork/package.json +1 -1
  219. package/packages/omo-codex/plugin/components/ultrawork/src/directive-content.ts +1 -1
  220. package/packages/omo-codex/plugin/components/ulw-execute-continuation/hooks/hooks.json +1 -1
  221. package/packages/omo-codex/plugin/components/ulw-execute-continuation/package.json +1 -1
  222. package/packages/omo-codex/plugin/components/ulw-loop/directive.md +3 -0
  223. package/packages/omo-codex/plugin/components/ulw-loop/hooks/hooks.json +5 -5
  224. package/packages/omo-codex/plugin/components/ulw-loop/package.json +1 -1
  225. package/packages/omo-codex/plugin/hooks/post-compact-resetting-git-bash-mcp-reminder.json +1 -1
  226. package/packages/omo-codex/plugin/hooks/post-compact-resetting-lsp-diagnostics-cache.json +1 -1
  227. package/packages/omo-codex/plugin/hooks/post-compact-resetting-project-rule-cache.json +1 -1
  228. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-comments.json +1 -1
  229. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-lsp-diagnostics.json +1 -1
  230. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-thread-title-hygiene.json +1 -1
  231. package/packages/omo-codex/plugin/hooks/post-tool-use-matching-project-rules.json +1 -1
  232. package/packages/omo-codex/plugin/hooks/post-tool-use-recording-spawn-admission.json +1 -1
  233. package/packages/omo-codex/plugin/hooks/pre-tool-use-enforcing-unlimited-goal-budget.json +1 -1
  234. package/packages/omo-codex/plugin/hooks/pre-tool-use-guarding-ulw-loop-spawns.json +1 -1
  235. package/packages/omo-codex/plugin/hooks/pre-tool-use-recommending-git-bash-mcp.json +1 -1
  236. package/packages/omo-codex/plugin/hooks/session-start-checking-auto-update.json +1 -1
  237. package/packages/omo-codex/plugin/hooks/session-start-checking-bootstrap-provisioning.json +1 -1
  238. package/packages/omo-codex/plugin/hooks/session-start-loading-project-rules.json +1 -1
  239. package/packages/omo-codex/plugin/hooks/session-start-recording-session-telemetry.json +1 -1
  240. package/packages/omo-codex/plugin/hooks/stop-checking-ulw-execute-continuation.json +1 -1
  241. package/packages/omo-codex/plugin/hooks/stop-checking-ulw-loop-resume.json +1 -1
  242. package/packages/omo-codex/plugin/hooks/subagent-stop-verifying-lazycodex-executor-evidence.json +1 -1
  243. package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ultrawork-trigger.json +1 -1
  244. package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ulw-loop-steering.json +1 -1
  245. package/packages/omo-codex/plugin/hooks/user-prompt-submit-loading-project-rules.json +1 -1
  246. package/packages/omo-codex/plugin/package-lock.json +16 -16
  247. package/packages/omo-codex/plugin/package.json +1 -1
  248. package/packages/omo-codex/plugin/shared/package.json +1 -1
  249. package/packages/omo-codex/plugin/skills/ast-grep/README.md +1 -1
  250. package/packages/omo-codex/plugin/skills/ast-grep/install.ps1 +5 -3
  251. package/packages/omo-codex/plugin/skills/ast-grep/install.sh +4 -5
  252. package/packages/omo-codex/plugin/skills/ast-grep/references/install.md +2 -2
  253. package/packages/omo-codex/plugin/skills/ast-grep/scripts/ast_grep_helper.py +13 -3
  254. package/packages/omo-codex/plugin/skills/browser/runtime/omowright/manifest.json +1 -1
  255. package/packages/omo-codex/plugin/skills/coding-agent-sessions/AGENTS.md +5 -4
  256. package/packages/omo-codex/plugin/skills/coding-agent-sessions/SKILL.md +11 -11
  257. package/packages/omo-codex/plugin/skills/coding-agent-sessions/references/all-platforms.md +2 -2
  258. package/packages/omo-codex/plugin/skills/coding-agent-sessions/references/codex.md +4 -4
  259. package/packages/omo-codex/plugin/skills/coding-agent-sessions/scripts/agent_sessions/cli.py +1 -1
  260. package/packages/omo-codex/plugin/skills/coding-agent-sessions/scripts/find-agent-sessions.py +6 -3
  261. package/packages/omo-codex/plugin/skills/frontend/ATTRIBUTION.md +1 -1
  262. package/packages/omo-codex/plugin/skills/frontend/SKILL.md +34 -12
  263. package/packages/omo-codex/plugin/skills/frontend/references/design/README.md +2 -2
  264. package/packages/omo-codex/plugin/skills/frontend/references/design/_INDEX.md +2 -1
  265. package/packages/omo-codex/plugin/skills/frontend/references/design/component-catalogs.md +14 -10
  266. package/packages/omo-codex/plugin/skills/frontend/references/design/design-system-architecture.md +19 -6
  267. package/packages/omo-codex/plugin/skills/frontend/references/design/grounding.md +29 -0
  268. package/packages/omo-codex/plugin/skills/frontend/references/ui-ux-db/scripts/design_system.py +2 -0
  269. package/packages/omo-codex/plugin/skills/frontend/references/ui-ux-db/scripts/search.py +2 -0
  270. package/packages/omo-codex/plugin/skills/lcx-contribute-bug-fix/SKILL.md +51 -55
  271. package/packages/omo-codex/plugin/skills/lcx-contribute-bug-fix/agents/openai.yaml +1 -1
  272. package/packages/omo-codex/plugin/skills/lcx-report-bug/SKILL.md +7 -7
  273. package/packages/omo-codex/plugin/skills/lcx-report-bug/agents/openai.yaml +1 -1
  274. package/packages/omo-codex/plugin/skills/ultimate-browsing/SKILL.md +5 -4
  275. package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/AGENTS.md +2 -2
  276. package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/__main__.py +9 -6
  277. package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/summary.py +1 -1
  278. package/packages/omo-codex/plugin/skills/ultimate-browsing/references/insane-search/README.md +4 -4
  279. package/packages/omo-codex/plugin/skills/ultimate-browsing/scripts/extract_cookies.py +4 -2
  280. package/packages/omo-codex/plugin/skills/ultrawork/SKILL.md +3 -0
  281. package/packages/omo-codex/plugin/skills/ulw-research/AGENTS.md +8 -4
  282. package/packages/omo-codex/plugin/skills/ulw-research/SKILL.md +7 -316
  283. package/packages/omo-codex/plugin/skills/ulw-research/references/deliverable-phase.md +23 -21
  284. package/packages/omo-codex/plugin/skills/ulw-research/references/fact-ledger-grammar.md +7 -3
  285. package/packages/omo-codex/plugin/skills/ulw-research/references/fact-ledger.md +42 -8
  286. package/packages/omo-codex/plugin/skills/ulw-research/references/fidelity-claim-kinds.md +1 -1
  287. package/packages/omo-codex/plugin/skills/ulw-research/references/markdown-blocks.md +12 -0
  288. package/packages/omo-codex/plugin/skills/ulw-research/references/report-gates.md +12 -5
  289. package/packages/omo-codex/plugin/skills/ulw-research/references/research-collection.md +195 -0
  290. package/packages/omo-codex/plugin/skills/ulw-research/references/research-delivery.md +123 -0
  291. package/packages/omo-codex/plugin/skills/ulw-research/scripts/claim-kinds.mjs +33 -5
  292. package/packages/omo-codex/plugin/skills/ulw-research/scripts/contracts.mjs +13 -4
  293. package/packages/omo-codex/plugin/skills/ulw-research/scripts/conversion-probe.mjs +229 -0
  294. package/packages/omo-codex/plugin/skills/ulw-research/scripts/css-hiding.mjs +158 -0
  295. package/packages/omo-codex/plugin/skills/ulw-research/scripts/css-lite.mjs +4 -2
  296. package/packages/omo-codex/plugin/skills/ulw-research/scripts/entities.mjs +13 -4
  297. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-commands-fill.mjs +11 -1
  298. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-commands-meaning.mjs +13 -9
  299. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-commands-polish.mjs +40 -18
  300. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-commands-verify.mjs +25 -8
  301. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-commands.mjs +35 -16
  302. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-derive.mjs +7 -2
  303. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-fill.mjs +6 -4
  304. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-ledger-kinds.mjs +6 -7
  305. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-locate.mjs +15 -9
  306. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-claims.mjs +4 -3
  307. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-csv.mjs +32 -0
  308. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-lexicon.mjs +1 -1
  309. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-originals.mjs +161 -0
  310. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-policy.mjs +4 -4
  311. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-records.mjs +81 -14
  312. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-snapshot-readability.mjs +49 -0
  313. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-sources.mjs +76 -0
  314. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-trace.mjs +25 -11
  315. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-value-unit.mjs +202 -0
  316. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-values.mjs +40 -39
  317. package/packages/omo-codex/plugin/skills/ulw-research/scripts/gates-fidelity.mjs +8 -6
  318. package/packages/omo-codex/plugin/skills/ulw-research/scripts/gates-static.mjs +10 -1
  319. package/packages/omo-codex/plugin/skills/ulw-research/scripts/html-lite.mjs +6 -2
  320. package/packages/omo-codex/plugin/skills/ulw-research/scripts/html-markup.mjs +2 -2
  321. package/packages/omo-codex/plugin/skills/ulw-research/scripts/html-text-map.mjs +140 -0
  322. package/packages/omo-codex/plugin/skills/ulw-research/scripts/markdown-closers.mjs +126 -0
  323. package/packages/omo-codex/plugin/skills/ulw-research/scripts/md-lite.mjs +1 -1
  324. package/packages/omo-codex/plugin/skills/ulw-research/scripts/number-grammar.mjs +33 -0
  325. package/packages/omo-codex/plugin/skills/ulw-research/scripts/numerals.mjs +18 -7
  326. package/packages/omo-codex/plugin/skills/ulw-research/scripts/outcome-start.mjs +66 -0
  327. package/packages/omo-codex/plugin/skills/ulw-research/scripts/outcome.mjs +11 -22
  328. package/packages/omo-codex/plugin/skills/ulw-research/scripts/placeholder.mjs +18 -6
  329. package/packages/omo-codex/plugin/skills/ulw-research/scripts/polish-apply.mjs +3 -2
  330. package/packages/omo-codex/plugin/skills/ulw-research/scripts/raw-decode.mjs +110 -0
  331. package/packages/omo-codex/plugin/skills/ulw-research/scripts/raw-locator.mjs +47 -0
  332. package/packages/omo-codex/plugin/skills/ulw-research/scripts/report-tools-commands.mjs +1 -1
  333. package/packages/omo-codex/plugin/skills/ulw-research/scripts/report-tools.mjs +7 -1
  334. package/packages/omo-codex/plugin/skills/ulw-research/scripts/source-commands.mjs +79 -26
  335. package/packages/omo-codex/plugin/skills/ulw-research/scripts/source-refresh.mjs +82 -0
  336. package/packages/omo-codex/plugin/skills/ulw-research/scripts/source-snapshot.mjs +73 -69
  337. package/packages/omo-codex/plugin/skills/ulw-research/scripts/source-view.mjs +89 -0
  338. package/packages/omo-codex/plugin/skills/ulw-research/scripts/sources-citation.mjs +203 -0
  339. package/packages/omo-codex/plugin/skills/ulw-research/scripts/units.mjs +14 -3
  340. package/packages/omo-codex/plugin/test/payload-equivalence.test.mjs +6 -4
  341. package/packages/omo-codex/plugin/test/sync-skills.test.mjs +4 -3
  342. package/packages/omo-codex/scripts/install-dist/install-local.mjs +338 -237
  343. package/packages/prompts-core/prompts/ultrawork/codex.md +3 -0
  344. package/packages/shared-skills/skill-source-filter.mjs +6 -2
  345. package/packages/shared-skills/skills/ast-grep/README.md +1 -1
  346. package/packages/shared-skills/skills/ast-grep/install.ps1 +5 -3
  347. package/packages/shared-skills/skills/ast-grep/install.sh +4 -5
  348. package/packages/shared-skills/skills/ast-grep/references/install.md +2 -2
  349. package/packages/shared-skills/skills/ast-grep/scripts/ast_grep_helper.py +13 -3
  350. package/packages/shared-skills/skills/browser/runtime/omowright/manifest.json +1 -1
  351. package/packages/shared-skills/skills/coding-agent-sessions/AGENTS.md +5 -4
  352. package/packages/shared-skills/skills/coding-agent-sessions/SKILL.md +11 -11
  353. package/packages/shared-skills/skills/coding-agent-sessions/references/all-platforms.md +2 -2
  354. package/packages/shared-skills/skills/coding-agent-sessions/references/codex.md +4 -4
  355. package/packages/shared-skills/skills/coding-agent-sessions/scripts/agent_sessions/cli.py +1 -1
  356. package/packages/shared-skills/skills/coding-agent-sessions/scripts/find-agent-sessions.py +6 -3
  357. package/packages/shared-skills/skills/frontend/ATTRIBUTION.md +1 -1
  358. package/packages/shared-skills/skills/frontend/SKILL.md +34 -12
  359. package/packages/shared-skills/skills/frontend/references/design/README.md +2 -2
  360. package/packages/shared-skills/skills/frontend/references/design/_INDEX.md +2 -1
  361. package/packages/shared-skills/skills/frontend/references/design/component-catalogs.md +14 -10
  362. package/packages/shared-skills/skills/frontend/references/design/design-system-architecture.md +19 -6
  363. package/packages/shared-skills/skills/frontend/references/design/grounding.md +29 -0
  364. package/packages/shared-skills/skills/frontend/references/ui-ux-db/scripts/design_system.py +2 -0
  365. package/packages/shared-skills/skills/frontend/references/ui-ux-db/scripts/search.py +2 -0
  366. package/packages/shared-skills/skills/ultimate-browsing/SKILL.md +5 -4
  367. package/packages/shared-skills/skills/ultimate-browsing/engine/AGENTS.md +2 -2
  368. package/packages/shared-skills/skills/ultimate-browsing/engine/__main__.py +9 -6
  369. package/packages/shared-skills/skills/ultimate-browsing/engine/summary.py +1 -1
  370. package/packages/shared-skills/skills/ultimate-browsing/references/insane-search/README.md +4 -4
  371. package/packages/shared-skills/skills/ultimate-browsing/scripts/extract_cookies.py +4 -2
  372. package/packages/shared-skills/skills/ulw-research/AGENTS.md +8 -4
  373. package/packages/shared-skills/skills/ulw-research/SKILL.md +7 -316
  374. package/packages/shared-skills/skills/ulw-research/references/deliverable-phase.md +23 -21
  375. package/packages/shared-skills/skills/ulw-research/references/fact-ledger-grammar.md +7 -3
  376. package/packages/shared-skills/skills/ulw-research/references/fact-ledger.md +42 -8
  377. package/packages/shared-skills/skills/ulw-research/references/fidelity-claim-kinds.md +1 -1
  378. package/packages/shared-skills/skills/ulw-research/references/markdown-blocks.md +12 -0
  379. package/packages/shared-skills/skills/ulw-research/references/report-gates.md +12 -5
  380. package/packages/shared-skills/skills/ulw-research/references/research-collection.md +195 -0
  381. package/packages/shared-skills/skills/ulw-research/references/research-delivery.md +123 -0
  382. package/packages/shared-skills/skills/ulw-research/scripts/claim-kinds.mjs +33 -5
  383. package/packages/shared-skills/skills/ulw-research/scripts/claim-kinds.test.ts +37 -2
  384. package/packages/shared-skills/skills/ulw-research/scripts/claim-prefix.test.ts +76 -0
  385. package/packages/shared-skills/skills/ulw-research/scripts/contracts-validation.test.ts +83 -0
  386. package/packages/shared-skills/skills/ulw-research/scripts/contracts.mjs +13 -4
  387. package/packages/shared-skills/skills/ulw-research/scripts/contracts.test.ts +6 -110
  388. package/packages/shared-skills/skills/ulw-research/scripts/conversion-altered.test.ts +154 -0
  389. package/packages/shared-skills/skills/ulw-research/scripts/conversion-originals.test.ts +297 -0
  390. package/packages/shared-skills/skills/ulw-research/scripts/conversion-probe-classes.test.ts +361 -0
  391. package/packages/shared-skills/skills/ulw-research/scripts/conversion-probe.mjs +229 -0
  392. package/packages/shared-skills/skills/ulw-research/scripts/conversion-probe.test.ts +218 -0
  393. package/packages/shared-skills/skills/ulw-research/scripts/copy-returns-polish.test.ts +4 -3
  394. package/packages/shared-skills/skills/ulw-research/scripts/css-hiding.mjs +158 -0
  395. package/packages/shared-skills/skills/ulw-research/scripts/css-lite.mjs +4 -2
  396. package/packages/shared-skills/skills/ulw-research/scripts/delivery-barrier-meaning.test.ts +186 -0
  397. package/packages/shared-skills/skills/ulw-research/scripts/delivery-barrier.test.ts +40 -87
  398. package/packages/shared-skills/skills/ulw-research/scripts/entities.mjs +13 -4
  399. package/packages/shared-skills/skills/ulw-research/scripts/fact-commands-fill.mjs +11 -1
  400. package/packages/shared-skills/skills/ulw-research/scripts/fact-commands-meaning.mjs +13 -9
  401. package/packages/shared-skills/skills/ulw-research/scripts/fact-commands-polish.mjs +40 -18
  402. package/packages/shared-skills/skills/ulw-research/scripts/fact-commands-verify.mjs +25 -8
  403. package/packages/shared-skills/skills/ulw-research/scripts/fact-commands.mjs +35 -16
  404. package/packages/shared-skills/skills/ulw-research/scripts/fact-derive-2.test.ts +149 -0
  405. package/packages/shared-skills/skills/ulw-research/scripts/fact-derive.mjs +7 -2
  406. package/packages/shared-skills/skills/ulw-research/scripts/fact-derive.test.ts +7 -192
  407. package/packages/shared-skills/skills/ulw-research/scripts/fact-fill.mjs +6 -4
  408. package/packages/shared-skills/skills/ulw-research/scripts/fact-ledger-kinds.mjs +6 -7
  409. package/packages/shared-skills/skills/ulw-research/scripts/fact-ledger-kinds.test.ts +2 -1
  410. package/packages/shared-skills/skills/ulw-research/scripts/fact-ledger-legacy.test.ts +2 -1
  411. package/packages/shared-skills/skills/ulw-research/scripts/fact-locate.mjs +15 -9
  412. package/packages/shared-skills/skills/ulw-research/scripts/fact-unit-dimensions-2.test.ts +149 -0
  413. package/packages/shared-skills/skills/ulw-research/scripts/fact-unit-dimensions.test.ts +4 -168
  414. package/packages/shared-skills/skills/ulw-research/scripts/fact-unit-rules.test.ts +3 -229
  415. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-claims.mjs +4 -3
  416. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-csv.mjs +32 -0
  417. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-lexicon.mjs +1 -1
  418. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-originals.mjs +161 -0
  419. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-policy.mjs +4 -4
  420. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-records.mjs +81 -14
  421. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-snapshot-readability.mjs +49 -0
  422. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-sources.mjs +76 -0
  423. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-trace.mjs +25 -11
  424. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-value-unit.mjs +202 -0
  425. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-values.mjs +40 -39
  426. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-values.test.ts +8 -29
  427. package/packages/shared-skills/skills/ulw-research/scripts/gates-fidelity.mjs +8 -6
  428. package/packages/shared-skills/skills/ulw-research/scripts/gates-static-headings.test.ts +17 -0
  429. package/packages/shared-skills/skills/ulw-research/scripts/gates-static.mjs +10 -1
  430. package/packages/shared-skills/skills/ulw-research/scripts/html-lite.mjs +6 -2
  431. package/packages/shared-skills/skills/ulw-research/scripts/html-markup.mjs +2 -2
  432. package/packages/shared-skills/skills/ulw-research/scripts/html-text-map.mjs +140 -0
  433. package/packages/shared-skills/skills/ulw-research/scripts/html-text-map.test.ts +148 -0
  434. package/packages/shared-skills/skills/ulw-research/scripts/markdown-closers.mjs +126 -0
  435. package/packages/shared-skills/skills/ulw-research/scripts/md-lite-boundaries.test.ts +46 -0
  436. package/packages/shared-skills/skills/ulw-research/scripts/md-lite.mjs +1 -1
  437. package/packages/shared-skills/skills/ulw-research/scripts/number-grammar.mjs +33 -0
  438. package/packages/shared-skills/skills/ulw-research/scripts/number-grammar.test.ts +36 -0
  439. package/packages/shared-skills/skills/ulw-research/scripts/numerals.mjs +18 -7
  440. package/packages/shared-skills/skills/ulw-research/scripts/numerals.test.ts +3 -0
  441. package/packages/shared-skills/skills/ulw-research/scripts/outcome-start.mjs +66 -0
  442. package/packages/shared-skills/skills/ulw-research/scripts/outcome-start.test.ts +109 -0
  443. package/packages/shared-skills/skills/ulw-research/scripts/outcome.mjs +11 -22
  444. package/packages/shared-skills/skills/ulw-research/scripts/page-source-locator.test.ts +187 -0
  445. package/packages/shared-skills/skills/ulw-research/scripts/placeholder.mjs +18 -6
  446. package/packages/shared-skills/skills/ulw-research/scripts/polish-apply.mjs +3 -2
  447. package/packages/shared-skills/skills/ulw-research/scripts/polish-markup.test.ts +15 -1
  448. package/packages/shared-skills/skills/ulw-research/scripts/polish-partial.test.ts +42 -0
  449. package/packages/shared-skills/skills/ulw-research/scripts/raw-decode.mjs +110 -0
  450. package/packages/shared-skills/skills/ulw-research/scripts/raw-locator.mjs +47 -0
  451. package/packages/shared-skills/skills/ulw-research/scripts/raw-snapshot.test.ts +139 -0
  452. package/packages/shared-skills/skills/ulw-research/scripts/report-tools-2.test.ts +233 -0
  453. package/packages/shared-skills/skills/ulw-research/scripts/report-tools-3.test.ts +141 -0
  454. package/packages/shared-skills/skills/ulw-research/scripts/report-tools-4.test.ts +135 -0
  455. package/packages/shared-skills/skills/ulw-research/scripts/report-tools-5.test.ts +231 -0
  456. package/packages/shared-skills/skills/ulw-research/scripts/report-tools-commands.mjs +1 -1
  457. package/packages/shared-skills/skills/ulw-research/scripts/report-tools.mjs +7 -1
  458. package/packages/shared-skills/skills/ulw-research/scripts/report-tools.test.ts +5 -800
  459. package/packages/shared-skills/skills/ulw-research/scripts/snapshot-readability.test.ts +116 -0
  460. package/packages/shared-skills/skills/ulw-research/scripts/source-commands.mjs +79 -26
  461. package/packages/shared-skills/skills/ulw-research/scripts/source-integrity.test.ts +243 -0
  462. package/packages/shared-skills/skills/ulw-research/scripts/source-refresh.mjs +82 -0
  463. package/packages/shared-skills/skills/ulw-research/scripts/source-snapshot.mjs +73 -69
  464. package/packages/shared-skills/skills/ulw-research/scripts/source-snapshot.test.ts +7 -4
  465. package/packages/shared-skills/skills/ulw-research/scripts/source-view.mjs +89 -0
  466. package/packages/shared-skills/skills/ulw-research/scripts/sources-citation.mjs +203 -0
  467. package/packages/shared-skills/skills/ulw-research/scripts/sources-citation.test.ts +236 -0
  468. package/packages/shared-skills/skills/ulw-research/scripts/tests/contracts-support.ts +147 -0
  469. package/packages/shared-skills/skills/ulw-research/scripts/tests/delivery-barrier-session.ts +59 -0
  470. package/packages/shared-skills/skills/ulw-research/scripts/tests/derive-clock.ts +4 -0
  471. package/packages/shared-skills/skills/ulw-research/scripts/tests/fact-derive-support.ts +65 -0
  472. package/packages/shared-skills/skills/ulw-research/scripts/tests/fact-unit-dimensions-support.ts +41 -0
  473. package/packages/shared-skills/skills/ulw-research/scripts/tests/fact-unit-rules-support.ts +241 -0
  474. package/packages/shared-skills/skills/ulw-research/scripts/tests/fidelity-values-support.ts +30 -0
  475. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/conversion-probe.html +14 -0
  476. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/sources-shapes.html +20 -0
  477. package/packages/shared-skills/skills/ulw-research/scripts/tests/numeral-classes.ts +196 -0
  478. package/packages/shared-skills/skills/ulw-research/scripts/tests/report-tools-support.ts +182 -0
  479. package/packages/shared-skills/skills/ulw-research/scripts/tests/value-mode-support.ts +48 -0
  480. package/packages/shared-skills/skills/ulw-research/scripts/units.mjs +14 -3
  481. package/packages/shared-skills/skills/ulw-research/scripts/units.test.ts +10 -0
  482. package/packages/shared-skills/skills/ulw-research/scripts/value-mode-unit-classes.test.ts +255 -0
  483. package/packages/shared-skills/skills/ulw-research/scripts/value-mode-unit.test.ts +179 -0
  484. package/packages/shared-skills/skills/ulw-research/scripts/value-rules-cli.test.ts +157 -0
  485. package/packages/shared-skills/skills/ulw-research/scripts/value-rules-followups.test.ts +72 -0
  486. package/packages/shared-skills/skills/ulw-research/scripts/value-rules-priority.test.ts +80 -0
  487. package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/tests/fixtures/amp_redirect_stub.html +0 -7
  488. package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/tests/fixtures/search_interstitial.html +0 -19
  489. package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/tests/fixtures/wayback_available.json +0 -1
  490. package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/tests/fixtures/wayback_snapshot.html +0 -1128
  491. package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/tests/test_fetch_chain.py +0 -77
  492. package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/tests/test_playwright_stealth.py +0 -86
  493. package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/tests/test_playwright_templates.py +0 -279
  494. package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/tests/test_surrogate.py +0 -252
  495. package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/tests/test_surrogate_validators.py +0 -78
@@ -8,10 +8,12 @@
8
8
  // escaped HTML projection (same line numbers) and checked with its fill map, which is the only
9
9
  // record of where its values sit; LaTeX is verified through its Markdown source.
10
10
  //
11
- // The locked brief is re-judged here, not only at fill: the edition's fill map (--fill-map, else
12
- // `<report>.fill-map.json` when present) must carry the current brief's brief_sha256
13
- // (brief_changed), and policyErrors re-runs over every data-fact the edition carries, so an
14
- // edition that violates the current derived-numbers or source-floor rule fails here too.
11
+ // The locked brief is re-judged here, not only at fill: every edition (HTML or Markdown) needs its
12
+ // fill map (fillMapOf: --fill-map, else whichever of `<report>.fill-map.json` and the
13
+ // `<report>.polished.fill-map.json` polish-apply writes records the edition's bytes), and that map
14
+ // must carry the current brief's brief_sha256; no map, or another hash, is brief_changed. policyErrors
15
+ // re-runs over every data-fact the edition carries, so an edition that violates the current
16
+ // derived-numbers or source-floor rule fails here too.
15
17
  //
16
18
  // The delivery barrier that reads these records (and every other fidelity record) is
17
19
  // fidelity-records.mjs.
@@ -66,8 +68,8 @@ function entryHtml(text, entry, rows) {
66
68
  if (entry.translates) return `<span data-translates="${escapeHtml(entry.translates)}">${projectMarkdownInline(body.replace(/^<!--[\s\S]*?-->\s*/u, ""))}</span>`
67
69
  if (!row) return `<data data-fact="${escapeHtml(entry.fact_id)}">${escapeHtml(body)}</data>`
68
70
  const quoted = row.kind === "quote" ? parseRenderedQuote(body) : null
69
- if (quoted) return renderQuote(row, entry.selector ?? "full", quoted.text, "html")
70
- return row.kind === "quote" ? null : renderValueHtml(row, body)
71
+ if (quoted) return renderQuote(row, entry.selector ?? "full", quoted.text, "html", entry.claimKind)
72
+ return row.kind === "quote" ? null : renderValueHtml(row, body, { mode: entry.mode, claimKind: entry.claimKind ?? null })
71
73
  }
72
74
 
73
75
  /** [from, to) of the filled text as HTML, each intact fill-map entry rebuilt as its fill element. */
@@ -149,6 +151,17 @@ const MARKDOWN_EXTENSION = /\.(md|markdown)$/iu
149
151
  /** The edition format: the explicit flag, else md for a .md or .markdown path (any case), else html. */
150
152
  export const editionFormatOf = (path, flag) => flag ?? (MARKDOWN_EXTENSION.test(path) ? "md" : "html")
151
153
 
154
+ /**
155
+ * An edition's fill map path: --fill-map, else whichever of fact fill's `<report>.fill-map.json` and
156
+ * polish-apply's `<report>.polished.fill-map.json` records these bytes as its artifact_sha256, else the
157
+ * first of them that exists; null when neither exists.
158
+ */
159
+ export function fillMapOf(report, text, flag) {
160
+ if (flag !== undefined) return flag
161
+ const found = [`${report}.fill-map.json`, `${report}.polished.fill-map.json`].filter((path) => existsSync(path))
162
+ return found.find((path) => readJsonFile(path).artifact_sha256 === sha256Of(text)) ?? found[0] ?? null
163
+ }
164
+
152
165
  /** The report's format (from --format or its extension) and, for Markdown, its fill map entries. */
153
166
  export function readEdition(report, flags) {
154
167
  const format = editionFormatOf(report, flags.format)
@@ -174,19 +187,23 @@ export function runFactVerify(args) {
174
187
  const briefPath = flags.brief ?? join(sessionDir, "brief.md")
175
188
  const ledger = readLedger(sessionDir)
176
189
  const projected = edition.format === "md" ? markdownAsHtml(edition.text, edition.entries, ledger) : null
190
+ // The entries whose element is in the checked HTML: an HTML edition's own map, or the projection's intact entries.
191
+ const ownMap = flags["fill-map"] ?? `${report}.fill-map.json`
192
+ const fillEntries = projected ? edition.entries.filter((entry) => !projected.stale.includes(entry)) : existsSync(ownMap) ? readJsonFile(ownMap).entries : null
177
193
  const result = checkFidelity(projected?.html ?? edition.text, {
178
194
  ledger,
179
195
  sourcesDir: resolve(flags.sources ?? join(sessionDir, "sources")),
180
196
  claims: flags.claims !== undefined || existsSync(claimsPath) ? readJsonFile(claimsPath) : null,
181
197
  fillMap: projected?.stale ?? edition.entries,
198
+ fillEntries,
182
199
  brief: existsSync(briefPath) ? readText(briefPath) : "",
183
200
  baseDir: dirname(resolve(report)),
184
201
  recompute,
185
202
  })
186
203
  const checked = projected?.html ?? edition.text
187
204
  const pointers = [...checked.matchAll(/\bdata-fact="([^"]+)"/gu)].map((match) => ({ token: match[0], start: match.index, parsed: { id: match[1] } }))
188
- const mapPath = flags["fill-map"] ?? `${report}.fill-map.json`
189
- const bound = existsSync(mapPath) ? briefBindingErrors(readJsonFile(mapPath), mapPath, parsed) : []
205
+ const mapPath = fillMapOf(report, edition.text, flags["fill-map"])
206
+ const bound = briefBindingErrors(mapPath === null ? null : readJsonFile(mapPath), mapPath ?? `${report}.fill-map.json`, parsed)
190
207
  const defects = [...bound, ...policyErrors(checked, pointers, ledger, sessionDir, policy).errors, ...result.defects]
191
208
  const counts = countDefects(defects)
192
209
  const status = defects.some(blocksDelivery) ? "fail" : "pass"
@@ -10,11 +10,14 @@ import { existsSync, readdirSync, readFileSync, writeFileSync } from "node:fs"
10
10
  import { join, resolve } from "node:path"
11
11
 
12
12
  import { CliError, parseArgs, sha256Of } from "./cli-support.mjs"
13
+ import { DEFECT_CODES } from "./contracts.mjs"
14
+ import { alteredIn } from "./conversion-probe.mjs"
13
15
  import { derive } from "./fact-derive.mjs"
14
16
  import { addRow, readLedger, retractRow, supersedeRow, writeLedger } from "./fact-ledger.mjs"
15
17
  import { locateFind, locateMatch, locatePageLine, locateTable } from "./fact-locate.mjs"
16
18
  import { QUALIFIER_FORMS } from "./fidelity-lexicon.mjs"
17
19
  import { exportRows } from "./fidelity-values.mjs"
20
+ import { rawLocated } from "./raw-locator.mjs"
18
21
  import { pointedFlag, sessionDirOf, snapshotOf } from "./source-commands.mjs"
19
22
 
20
23
  const CONTEXT = 60
@@ -30,26 +33,30 @@ function readText(path) {
30
33
  }
31
34
  }
32
35
 
33
- function nowIso(flags) {
34
- const date = flags.now === undefined ? new Date() : new Date(flags.now)
36
+ /** A caller-supplied --now value as ISO; parsing only, never the clock. */
37
+ function isoOf(text) {
38
+ const date = new Date(text)
35
39
  if (Number.isNaN(date.getTime())) throw new CliError("--now requires an ISO timestamp")
36
40
  return date.toISOString()
37
41
  }
38
42
 
43
+ const nowIso = (flags) => (flags.now === undefined ? new Date().toISOString() : isoOf(flags.now))
44
+
39
45
  function failure(command, error, detail) {
40
46
  const json = { command, status: "fail", exitCode: 1, error, ...detail }
41
47
  return { json, summary: `${command}: ${error}${detail.message ? ` (${detail.message})` : ""}`, exitCode: 1 }
42
48
  }
43
49
 
44
- function locate(flags, snapshot, id, quote) {
50
+ /** Every locator reads the view text (visible text nodes for a page), never raw markup. */
51
+ function locate(flags, view, id, quote) {
45
52
  const sourceId = id
46
- if (quote) return locateMatch(snapshot.text, { match: flags.match, sourceId })
47
- if (flags.find !== undefined) return locateFind(snapshot.text, { find: flags.find, unitExpected: flags["unit-expected"], sourceId })
53
+ if (quote) return locateMatch(view.text, { match: flags.match, sourceId })
54
+ if (flags.find !== undefined) return locateFind(view.text, { find: flags.find, unitExpected: flags["unit-expected"], sourceId })
48
55
  if (flags.table !== undefined) {
49
- if (snapshot.html === null) throw new CliError(`source ${id} has no source.html; --table needs a page snapshot`)
50
- return locateTable(snapshot.html, { table: flags.table, row: flags.row, col: flags.col, sourceId })
56
+ if (view.root === null) throw new CliError(`source ${id} is not an HTML page snapshot; --table needs one`)
57
+ return locateTable(view, { table: flags.table, row: flags.row, col: flags.col, sourceId })
51
58
  }
52
- if (flags.page !== undefined) return locatePageLine(snapshot.text, { page: Number(flags.page), line: Number(flags.line), sourceId })
59
+ if (flags.page !== undefined) return locatePageLine(view.text, { page: Number(flags.page), line: Number(flags.line), sourceId })
53
60
  throw new CliError("usage: fact add --source S<n> (--find TEXT | --table T --row R --col C | --page P --line L) --label LABEL")
54
61
  }
55
62
 
@@ -76,11 +83,17 @@ function runAdd(args, quote) {
76
83
  const id = args.required("source")
77
84
  if (quote) args.required("match")
78
85
  const sessionDir = sessionDirOf(flags)
79
- const snapshot = snapshotOf(sessionDir, id)
80
- const found = locate(flags, snapshot, id, quote)
86
+ const { meta, view, sha256, drift } = snapshotOf(sessionDir, id)
87
+ const converted = view.mode === "converted"
88
+ if (drift.length > 0) return failure(command, "snapshot_hash_mismatch", { message: `${id}: ${drift.join("; ")}` })
89
+ if (view.mode === "legacy") return failure(command, "snapshot_not_raw", { message: `${id} is a legacy text.txt snapshot without raw.bin; snapshot the source again under a new id` })
90
+ if (converted && !quote) return failure(command, "snapshot_not_raw", { message: `${id} is converted-only (no raw bytes); measured rows need source snapshot --raw <original> --text <extraction>` })
91
+ const located = locate(flags, view, id, quote)
92
+ const found = rawLocated(view, located)
81
93
  if (found.error === "unit_expected_mismatch") return failure(command, "unit_mismatch", { message: `expected ${found.expected}, found ${found.found}`, candidates: found.candidates })
82
94
  if (found.error) return failure(command, found.error, { message: `no single match in ${id}`, candidates: found.candidates })
83
- const { meta } = snapshot
95
+ const altered = alteredIn(join(sessionDir, "sources", id), [found.raw, found.quote])
96
+ if (altered.length > 0) return failure(command, "conversion_altered_number", { message: `${id}'s conversion altered ${[...new Set(altered)].join(", ")} (sources/${id}/conversion.json); the converted text cannot back this number, even where the source also shows it unaltered: write a verified text of the source and run \`source snapshot --id ${id} --text <verified.txt> --replace\`, then add the fact again`, tokens: altered, hint: DEFECT_CODES.conversion_altered_number.hint })
84
97
  const host = URL.canParse(meta.url) ? new URL(meta.url).hostname : null
85
98
  const labelSource = flags.find ?? flags.match ?? (flags.table !== undefined ? `${flags.row}_${flags.col}` : `p${flags.page}_l${flags.line}`)
86
99
  const ledger = readLedger(sessionDir)
@@ -89,9 +102,10 @@ function runAdd(args, quote) {
89
102
  ...(quote ? {} : { value: found.value, unit: found.unit, value_span: found.value_span, qualifier: QUALIFIER_OF.get(found.qualifier) ?? null }),
90
103
  raw: found.raw,
91
104
  quote: found.quote,
105
+ context: found.context,
92
106
  source_id: id,
93
107
  locator: { ...found.locator, source_kind: meta.kind, url: meta.url, host, pointed: pointedFlag(sessionDir, meta.url) },
94
- snapshot_sha256: meta.sha256,
108
+ snapshot_sha256: sha256,
95
109
  sentence_id: found.sentence_id ?? null,
96
110
  extracted_by: meta.extractor,
97
111
  observed_at: nowIso(flags),
@@ -104,11 +118,14 @@ function runAdd(args, quote) {
104
118
  fuzzy: found.fuzzy === true,
105
119
  })
106
120
  writeLedger(sessionDir, ledger)
107
- const context = contextOf(snapshot.text, row)
108
- const json = { command, status: "pass", exitCode: 0, defects: [], data: { row, context } }
121
+ const context = contextOf(view.text, located)
122
+ const { severity, integrity } = DEFECT_CODES.quote_converted_only
123
+ const defects = converted ? [{ code: "quote_converted_only", severity, integrity, message: `${id} is converted-only; the quote is not bound to the original fetched bytes` }] : []
124
+ const json = { command, status: "pass", exitCode: 0, defects, data: { row, context } }
109
125
  if (flags.json) return { json, exitCode: 0 }
110
126
  const shown = quote ? row.quote : `${row.raw} (value ${row.value}${row.unit ? `, unit ${row.unit}` : ""})`
111
- return { text: `${row.fact_id} ${row.label}: ${shown} [${id} ${row.sentence_id ?? "-"}]\n ...${context.before}[${context.match}]${context.after}...`, exitCode: 0 }
127
+ const warnings = defects.map((defect) => `\n warning ${defect.code}: ${defect.message}`).join("")
128
+ return { text: `${row.fact_id} ${row.label}: ${shown} [${id} ${row.sentence_id ?? "-"}]\n ...${context.before}[${context.match}]${context.after}...${warnings}`, exitCode: 0 }
112
129
  }
113
130
 
114
131
  function runRetract(args) {
@@ -157,11 +174,13 @@ function runExport(args) {
157
174
  function runDerive(args) {
158
175
  const { flags } = args
159
176
  const op = args.required("op")
177
+ // Time is the caller's: without --now the CLI refuses with the library's missing_now code (exit 2) and never reads the clock.
178
+ if (flags.now === undefined) throw new CliError("missing_now: fact derive needs --now <ISO timestamp> (the row's observed_at); no row written")
160
179
  const sessionDir = sessionDirOf(flags)
161
180
  const ledger = readLedger(sessionDir)
162
181
  // The registry refuses any field outside the operation's inputFields, so only the given flags go in; --unit null declares a dimensionless result.
163
182
  const picked = ["to", "from", "num", "den", "part", "whole", "reference", "rationale", "label"].filter((key) => flags[key] !== undefined)
164
- const spec = { op, now: nowIso(flags), ...Object.fromEntries(picked.map((key) => [key, flags[key]])) }
183
+ const spec = { op, now: isoOf(flags.now), ...Object.fromEntries(picked.map((key) => [key, flags[key]])) }
165
184
  if (op === "expr") spec.formula = args.positionals[0]
166
185
  else if (args.positionals.length > 0) spec.operands = args.positionals
167
186
  if (flags.disjoint === true) spec.disjoint = true
@@ -0,0 +1,149 @@
1
+ import { describe, expect, onTestFinished, test } from "bun:test"
2
+ import { mkdtempSync, rmSync } from "node:fs"
3
+ import { tmpdir } from "node:os"
4
+ import { join } from "node:path"
5
+ import { recompute } from "./fact-derive.mjs"
6
+ import { derive } from "./tests/derive-clock"
7
+ import { addRow, emptyLedger, readLedger, writeLedger } from "./fact-ledger.mjs"
8
+ import { HASH, EOK_WON, Row, Ledger, measured, ledgerOf, fixture, rowOf } from "./tests/fact-derive-support"
9
+
10
+
11
+
12
+ describe("unit rules: the design review's counterexamples", () => {
13
+ const book = () =>
14
+ ledgerOf([
15
+ measured("F1", "20", "%"),
16
+ measured("F2", "10", "%"),
17
+ measured("F3", "5", "pp"),
18
+ measured("F4", "25", "C", { valid_at: "2025", entity: "x" }),
19
+ measured("F5", "30", "C", { valid_at: "2026", entity: "x" }),
20
+ measured("F6", "200", "person"),
21
+ measured("F7", "100", "person"),
22
+ measured("F8", "100", "USD"),
23
+ measured("F9", "20", "USD/person"),
24
+ measured("F10", "0", "m"),
25
+ ])
26
+
27
+ test("a sum of % operands without one shared reference is refused", () => {
28
+ const ledger = book()
29
+ const base = { op: "sum", operands: ["F1", "F2"], unit: "%", disjoint: true, rationale: "disjoint shares" }
30
+ expect(derive(ledger, base).error).toBe("incompatible_operands")
31
+ expect(derive(ledger, { ...base, reference: { F1: "voters", F2: "residents" } }).error).toBe("incompatible_operands")
32
+ expect(rowOf(derive(ledger, { ...base, reference: { F1: "voters", F2: "voters" } }))).toMatchObject({ value: "30", unit: "%" })
33
+ })
34
+
35
+ test("20% - 5pp is 15%", () => {
36
+ const row = rowOf(derive(book(), { op: "diff", operands: ["F1", "F3"], unit: "%" }))
37
+ expect(row).toMatchObject({ value: "15", unit: "%", render: "15%" })
38
+ })
39
+
40
+ test("growth on a Celsius scale is refused", () => {
41
+ const result = derive(book(), { op: "growth", from: "F4", to: "F5", unit: "%" })
42
+ expect(result.error).toBe("incompatible_operands")
43
+ expect(result.detail).toContain("affine")
44
+ })
45
+
46
+ test("temperatures are not summed or differenced as absolute values; ratio refuses them; convert C to F is allowed", () => {
47
+ const ledger = book()
48
+ const refused = [
49
+ { op: "sum", operands: ["F4", "F5"], unit: "C" },
50
+ { op: "diff", operands: ["F5", "F4"], unit: "C" },
51
+ { op: "expr", formula: "F4 + F5", unit: "C" },
52
+ { op: "expr", formula: "F5 - F4", unit: "C" },
53
+ { op: "ratio", num: "F5", den: "F4", unit: null },
54
+ ]
55
+ for (const spec of refused) {
56
+ const result = derive(ledger, spec)
57
+ expect({ spec, error: result.error, detail: result.detail }).toEqual({ spec, error: "incompatible_operands", detail: expect.stringContaining("affine") })
58
+ }
59
+ const converted = rowOf(derive(ledger, { op: "convert", operands: ["F4"], to: "F" }))
60
+ expect(converted).toMatchObject({ value: "77", unit: "F" })
61
+ expect(recompute(ledger, converted).ok).toBe(true)
62
+ })
63
+
64
+ test("a convert row relabeled to an alias in both unit and context.target is detected", () => {
65
+ const ledger = book()
66
+ const row = rowOf(derive(ledger, { op: "convert", operands: ["F10"], to: "km" }))
67
+ const relabeled = recompute(ledger, { ...row, unit: "kilometre", raw: "0 kilometre (0 m)", render: "0 kilometre (0 m)", context: { target: "kilometre" } })
68
+ expect(relabeled.ok).toBe(false)
69
+ expect(relabeled.detail).toContain('context.target "kilometre" is not canonical')
70
+ })
71
+
72
+ test("a share of 200 out of 100 is refused", () => {
73
+ expect(derive(book(), { op: "share", part: "F6", whole: "F7", unit: "%" }).error).toBe("incompatible_operands")
74
+ })
75
+
76
+ test("100 USD / (20 USD/person) is 5 person", () => {
77
+ const ledger = book()
78
+ const row = rowOf(derive(ledger, { op: "ratio", num: "F8", den: "F9", unit: "person" }))
79
+ expect(row).toMatchObject({ value: "5", unit: "person", exact: { num: "5", den: "1" } })
80
+ expect(recompute(ledger, row).ok).toBe(true)
81
+ })
82
+
83
+ test("convert 0 m to km relabeled cm is detected though the value is unchanged", () => {
84
+ const ledger = book()
85
+ const row = rowOf(derive(ledger, { op: "convert", operands: ["F10"], to: "km" }))
86
+ expect(recompute(ledger, row).ok).toBe(true)
87
+ const relabeled = recompute(ledger, { ...row, unit: "cm", raw: "0 cm (0 m)", render: "0 cm (0 m)" })
88
+ expect(relabeled.ok).toBe(false)
89
+ expect(relabeled.detail).toContain("km")
90
+ })
91
+
92
+ test("expr % + %p is %, in either order; a ratio over unequal % and %p is refused", () => {
93
+ const ledger = book()
94
+ for (const formula of ["F1 + F3", "F3 + F1"]) {
95
+ const row = rowOf(derive(ledger, { op: "expr", formula, unit: "%" }))
96
+ expect(row).toMatchObject({ value: "25", unit: "%" })
97
+ expect(recompute(ledger, row).ok).toBe(true)
98
+ }
99
+ const ratio = derive(ledger, { op: "ratio", num: "F1", den: "F3", unit: null })
100
+ expect(ratio.error).toBe("incompatible_operands")
101
+ expect(ratio.detail).toContain("needs equal units")
102
+ })
103
+
104
+ test("a stored context on an operation that has none is malformed", () => {
105
+ const ledger = book()
106
+ const diff = rowOf(derive(ledger, { op: "diff", operands: ["F1", "F2"], unit: "%p" }))
107
+ const expr = rowOf(derive(ledger, { op: "expr", formula: "F1 - F2", unit: "%p" }))
108
+ for (const [row, context] of [[diff, "x"], [expr, { target: "km" }]] as const) {
109
+ const result = recompute(ledger, { ...row, context })
110
+ expect(result.ok).toBe(false)
111
+ expect(result.detail).toContain("rows carry no context")
112
+ }
113
+ })
114
+
115
+ test("expr 20% - 10% is %p, never %", () => {
116
+ const ledger = book()
117
+ expect(derive(ledger, { op: "expr", formula: "F1 - F2", unit: "%" }).error).toBe("incompatible_operands")
118
+ const row = rowOf(derive(ledger, { op: "expr", formula: "F1 - F2", unit: "%p" }))
119
+ expect(row).toMatchObject({ value: "10", unit: "%p" })
120
+ expect(recompute(ledger, row).ok).toBe(true)
121
+ })
122
+ })
123
+
124
+ describe("render precision of expr results (invariant 5)", () => {
125
+ const book = () => ledgerOf([measured("F1", "20.25", "%"), measured("F2", "15.5", "%"), measured("F3", "1000", "m"), measured("F4", "3", "m"), measured("F5", "12.34", "m")])
126
+
127
+ test("an expr difference giving %p renders by decimal places, as diff does", () => {
128
+ const ledger = book()
129
+ const viaDiff = rowOf(derive(ledger, { op: "diff", operands: ["F1", "F2"], unit: "%p" }))
130
+ const viaExpr = rowOf(derive(ledger, { op: "expr", formula: "F1 - F2", unit: "%p" }))
131
+ expect(viaDiff).toMatchObject({ value: "4.8", render: "4.8 %p" })
132
+ expect(viaExpr).toMatchObject({ value: "4.8", render: "4.8 %p" })
133
+ expect(recompute(ledger, viaExpr).ok).toBe(true)
134
+ })
135
+
136
+ test("an expr with a - root renders 1000 m - 3 m as 997 m", () => {
137
+ const ledger = book()
138
+ const row = rowOf(derive(ledger, { op: "expr", formula: "F3 - F4", unit: "m" }))
139
+ expect(row).toMatchObject({ value: "997", render: "997 m", exact: { num: "997", den: "1" } })
140
+ expect(recompute(ledger, row).ok).toBe(true)
141
+ })
142
+
143
+ test("an expr with a * root keeps significant figures", () => {
144
+ const ledger = book()
145
+ const row = rowOf(derive(ledger, { op: "expr", formula: "F5 * 1.5", unit: "m" }))
146
+ expect(row).toMatchObject({ value: "19", render: "19 m" })
147
+ expect(recompute(ledger, row).ok).toBe(true)
148
+ })
149
+ })
@@ -3,7 +3,7 @@ import { leaves, parseExpr } from "./fact-expr.mjs"
3
3
  import { readRow } from "./fact-ledger-kinds.mjs"
4
4
  import { addRow, nextId, validateLedger } from "./fact-ledger.mjs"
5
5
  import { recompute, verifyGraph } from "./fact-recompute.mjs"
6
- import { canonicalUnit, inputScale, refuse } from "./unit-algebra.mjs"
6
+ import { canonicalUnit, inputScale, refuse, RuleError } from "./unit-algebra.mjs"
7
7
  import { refusal } from "./unit-errors.mjs"
8
8
  import { object, sameDeclared } from "./unit-metadata.mjs"
9
9
  import { display, renderValue, resultPrecision, toText } from "./unit-precision.mjs"
@@ -29,7 +29,11 @@ function declaredUnit(spec, rule) {
29
29
  return canonicalUnit(declared)
30
30
  }
31
31
 
32
+ /** Refused before any work, under its own code: time is the caller's (spec.now), never the clock. */
33
+ const MISSING_NOW = Object.freeze({ error: "missing_now", shape: "missing_now", expected: "an ISO timestamp in spec.now", detail: "derive needs a caller-supplied now timestamp" })
34
+
32
35
  export function derive(ledger, spec) {
36
+ if (object(spec) && typeof spec.now !== "string") return { ...MISSING_NOW }
33
37
  try {
34
38
  if (!object(spec)) refuse("spec must be an object")
35
39
  const errors = validateLedger(ledger)
@@ -54,13 +58,14 @@ export function derive(ledger, spec) {
54
58
  const result = compute(spec.op, rows, expected, ast, context, spec.formula)
55
59
  const row = addRow(ledger, {
56
60
  kind: rule.kind, unit: expected, quote: "", extracted_by: "fact-derive",
57
- observed_at: spec.now ?? new Date().toISOString(),
61
+ observed_at: spec.now,
58
62
  label: spec.label ?? `${spec.op}-${nextId(ledger, rule.kind).toLowerCase()}`,
59
63
  ...result, operation: spec.op, operands: ids, derived_from: ids,
60
64
  rationale: spec.rationale ?? null, context,
61
65
  })
62
66
  return { row, compatibility: rule.compatibility }
63
67
  } catch (error) {
68
+ if (!(error instanceof RuleError)) throw error
64
69
  return { error: "incompatible_operands", ...refusal(error) }
65
70
  }
66
71
  }
@@ -2,58 +2,12 @@ import { describe, expect, onTestFinished, test } from "bun:test"
2
2
  import { mkdtempSync, rmSync } from "node:fs"
3
3
  import { tmpdir } from "node:os"
4
4
  import { join } from "node:path"
5
-
6
- import { derive, recompute } from "./fact-derive.mjs"
5
+ import { recompute } from "./fact-derive.mjs"
6
+ import { derive } from "./tests/derive-clock"
7
7
  import { addRow, emptyLedger, readLedger, writeLedger } from "./fact-ledger.mjs"
8
+ import { HASH, EOK_WON, Row, Ledger, measured, ledgerOf, fixture, rowOf } from "./tests/fact-derive-support"
8
9
 
9
- const HASH = "a".repeat(64)
10
- const EOK_WON = "\uC5B5\uC6D0"
11
-
12
- type Row = Record<string, unknown>
13
- type Ledger = { schemaVersion: number; rows: Row[]; events: unknown[] }
14
-
15
- function measured(factId: string, value: string, unit: string, extra: Row = {}): Row {
16
- return {
17
- fact_id: factId,
18
- kind: "measured",
19
- value,
20
- unit,
21
- raw: `${value} ${unit}`,
22
- quote: `The figure is ${value} ${unit}.`,
23
- source_id: "S1",
24
- locator: { artifact: "sources/S1/text.txt", char_start: 0, char_end: 20 },
25
- snapshot_sha256: HASH,
26
- extracted_by: "fact add --find",
27
- observed_at: "2026-10-23T08:00:00Z",
28
- label: factId.toLowerCase(),
29
- ...extra,
30
- }
31
- }
32
-
33
- function ledgerOf(rows: Row[]): Ledger {
34
- const ledger = emptyLedger() as Ledger
35
- for (const row of rows) addRow(ledger, row)
36
- return ledger
37
- }
38
10
 
39
- function fixture(): Ledger {
40
- return ledgerOf([
41
- measured("F5", "100", EOK_WON, { population: "all" }),
42
- measured("F6", "200", EOK_WON, { population: "all" }),
43
- measured("F7", "200", EOK_WON, { population: "subset" }),
44
- measured("F8", "15", "%"),
45
- measured("F9", "20", "%"),
46
- measured("F12", "320", "m"),
47
- measured("F19", "100", "case", { valid_at: "2025-Q3", entity: "acme" }),
48
- measured("F20", "112", "case", { valid_at: "2026-Q3", entity: "acme" }),
49
- measured("F30", "3.2", "m"),
50
- ])
51
- }
52
-
53
- function rowOf(result: { row?: Row; error?: string; detail?: string }): Row {
54
- if (!result.row) throw new Error(`expected a row, got ${result.error}: ${result.detail}`)
55
- return result.row
56
- }
57
11
 
58
12
  describe("derive: checked operations", () => {
59
13
  test("sum of compatible rows stores the exact sum and appends a derived row", () => {
@@ -99,7 +53,7 @@ describe("derive: checked operations", () => {
99
53
 
100
54
  test("an unknown or missing operand is refused", () => {
101
55
  const ledger = fixture()
102
- expect(derive(ledger, { op: "sum", operands: ["F5", "F99"], unit: EOK_WON }).error).toBeDefined()
56
+ expect(derive(ledger, { op: "sum", operands: ["F5", "F99"], unit: EOK_WON }).error).toBe("incompatible_operands")
103
57
  expect(ledger.rows).toHaveLength(9)
104
58
  })
105
59
  })
@@ -158,8 +112,8 @@ describe("derive: expr and exact chaining", () => {
158
112
  // unit-rules.md: expr units are inferred with no rationale escape, and an omitted unit is an error, not null.
159
113
  test("expr needs a declared unit that follows from its operands", () => {
160
114
  const ledger = fixture()
161
- expect(derive(ledger, { op: "expr", formula: "F12 / F30", rationale: "span over sample length" }).error).toBeDefined()
162
- expect(derive(ledger, { op: "expr", formula: "F12 / F30", unit: "m" }).error).toBeDefined()
115
+ expect(derive(ledger, { op: "expr", formula: "F12 / F30", rationale: "span over sample length" }).error).toBe("incompatible_operands")
116
+ expect(derive(ledger, { op: "expr", formula: "F12 / F30", unit: "m" }).error).toBe("incompatible_operands")
163
117
  const result = derive(ledger, { op: "expr", formula: "F12 / F30", unit: null })
164
118
  expect(result.compatibility).toBe("unchecked")
165
119
  expect(rowOf(result)).toMatchObject({ value: "100", formula: "F12 / F30", operands: ["F12", "F30"] })
@@ -168,7 +122,7 @@ describe("derive: expr and exact chaining", () => {
168
122
  test("expr refuses unsupported tokens and absent ids", () => {
169
123
  const ledger = fixture()
170
124
  for (const formula of ["F12 ** 2", "Math.max(F12)", "F12 / F404", "F12 / 0", "(F12"]) {
171
- expect(derive(ledger, { op: "expr", formula, unit: null }).error).toBeDefined()
125
+ expect(derive(ledger, { op: "expr", formula, unit: null }).error).toBe("incompatible_operands")
172
126
  }
173
127
  expect(ledger.rows).toHaveLength(9)
174
128
  })
@@ -234,142 +188,3 @@ describe("recompute", () => {
234
188
  expect(result.detail).toContain("F2")
235
189
  })
236
190
  })
237
-
238
- describe("unit rules: the design review's counterexamples", () => {
239
- const book = () =>
240
- ledgerOf([
241
- measured("F1", "20", "%"),
242
- measured("F2", "10", "%"),
243
- measured("F3", "5", "pp"),
244
- measured("F4", "25", "C", { valid_at: "2025", entity: "x" }),
245
- measured("F5", "30", "C", { valid_at: "2026", entity: "x" }),
246
- measured("F6", "200", "person"),
247
- measured("F7", "100", "person"),
248
- measured("F8", "100", "USD"),
249
- measured("F9", "20", "USD/person"),
250
- measured("F10", "0", "m"),
251
- ])
252
-
253
- test("a sum of % operands without one shared reference is refused", () => {
254
- const ledger = book()
255
- const base = { op: "sum", operands: ["F1", "F2"], unit: "%", disjoint: true, rationale: "disjoint shares" }
256
- expect(derive(ledger, base).error).toBe("incompatible_operands")
257
- expect(derive(ledger, { ...base, reference: { F1: "voters", F2: "residents" } }).error).toBe("incompatible_operands")
258
- expect(rowOf(derive(ledger, { ...base, reference: { F1: "voters", F2: "voters" } }))).toMatchObject({ value: "30", unit: "%" })
259
- })
260
-
261
- test("20% - 5pp is 15%", () => {
262
- const row = rowOf(derive(book(), { op: "diff", operands: ["F1", "F3"], unit: "%" }))
263
- expect(row).toMatchObject({ value: "15", unit: "%", render: "15%" })
264
- })
265
-
266
- test("growth on a Celsius scale is refused", () => {
267
- const result = derive(book(), { op: "growth", from: "F4", to: "F5", unit: "%" })
268
- expect(result.error).toBe("incompatible_operands")
269
- expect(result.detail).toContain("affine")
270
- })
271
-
272
- test("temperatures are not summed or differenced as absolute values; ratio refuses them; convert C to F is allowed", () => {
273
- const ledger = book()
274
- const refused = [
275
- { op: "sum", operands: ["F4", "F5"], unit: "C" },
276
- { op: "diff", operands: ["F5", "F4"], unit: "C" },
277
- { op: "expr", formula: "F4 + F5", unit: "C" },
278
- { op: "expr", formula: "F5 - F4", unit: "C" },
279
- { op: "ratio", num: "F5", den: "F4", unit: null },
280
- ]
281
- for (const spec of refused) {
282
- const result = derive(ledger, spec)
283
- expect({ spec, error: result.error, detail: result.detail }).toEqual({ spec, error: "incompatible_operands", detail: expect.stringContaining("affine") })
284
- }
285
- const converted = rowOf(derive(ledger, { op: "convert", operands: ["F4"], to: "F" }))
286
- expect(converted).toMatchObject({ value: "77", unit: "F" })
287
- expect(recompute(ledger, converted).ok).toBe(true)
288
- })
289
-
290
- test("a convert row relabeled to an alias in both unit and context.target is detected", () => {
291
- const ledger = book()
292
- const row = rowOf(derive(ledger, { op: "convert", operands: ["F10"], to: "km" }))
293
- const relabeled = recompute(ledger, { ...row, unit: "kilometre", raw: "0 kilometre (0 m)", render: "0 kilometre (0 m)", context: { target: "kilometre" } })
294
- expect(relabeled.ok).toBe(false)
295
- expect(relabeled.detail).toContain('context.target "kilometre" is not canonical')
296
- })
297
-
298
- test("a share of 200 out of 100 is refused", () => {
299
- expect(derive(book(), { op: "share", part: "F6", whole: "F7", unit: "%" }).error).toBe("incompatible_operands")
300
- })
301
-
302
- test("100 USD / (20 USD/person) is 5 person", () => {
303
- const ledger = book()
304
- const row = rowOf(derive(ledger, { op: "ratio", num: "F8", den: "F9", unit: "person" }))
305
- expect(row).toMatchObject({ value: "5", unit: "person", exact: { num: "5", den: "1" } })
306
- expect(recompute(ledger, row).ok).toBe(true)
307
- })
308
-
309
- test("convert 0 m to km relabeled cm is detected though the value is unchanged", () => {
310
- const ledger = book()
311
- const row = rowOf(derive(ledger, { op: "convert", operands: ["F10"], to: "km" }))
312
- expect(recompute(ledger, row).ok).toBe(true)
313
- const relabeled = recompute(ledger, { ...row, unit: "cm", raw: "0 cm (0 m)", render: "0 cm (0 m)" })
314
- expect(relabeled.ok).toBe(false)
315
- expect(relabeled.detail).toContain("km")
316
- })
317
-
318
- test("expr % + %p is %, in either order; a ratio over unequal % and %p is refused", () => {
319
- const ledger = book()
320
- for (const formula of ["F1 + F3", "F3 + F1"]) {
321
- const row = rowOf(derive(ledger, { op: "expr", formula, unit: "%" }))
322
- expect(row).toMatchObject({ value: "25", unit: "%" })
323
- expect(recompute(ledger, row).ok).toBe(true)
324
- }
325
- const ratio = derive(ledger, { op: "ratio", num: "F1", den: "F3", unit: null })
326
- expect(ratio.error).toBe("incompatible_operands")
327
- expect(ratio.detail).toContain("needs equal units")
328
- })
329
-
330
- test("a stored context on an operation that has none is malformed", () => {
331
- const ledger = book()
332
- const diff = rowOf(derive(ledger, { op: "diff", operands: ["F1", "F2"], unit: "%p" }))
333
- const expr = rowOf(derive(ledger, { op: "expr", formula: "F1 - F2", unit: "%p" }))
334
- for (const [row, context] of [[diff, "x"], [expr, { target: "km" }]] as const) {
335
- const result = recompute(ledger, { ...row, context })
336
- expect(result.ok).toBe(false)
337
- expect(result.detail).toContain("rows carry no context")
338
- }
339
- })
340
-
341
- test("expr 20% - 10% is %p, never %", () => {
342
- const ledger = book()
343
- expect(derive(ledger, { op: "expr", formula: "F1 - F2", unit: "%" }).error).toBe("incompatible_operands")
344
- const row = rowOf(derive(ledger, { op: "expr", formula: "F1 - F2", unit: "%p" }))
345
- expect(row).toMatchObject({ value: "10", unit: "%p" })
346
- expect(recompute(ledger, row).ok).toBe(true)
347
- })
348
- })
349
-
350
- describe("render precision of expr results (invariant 5)", () => {
351
- const book = () => ledgerOf([measured("F1", "20.25", "%"), measured("F2", "15.5", "%"), measured("F3", "1000", "m"), measured("F4", "3", "m"), measured("F5", "12.34", "m")])
352
-
353
- test("an expr difference giving %p renders by decimal places, as diff does", () => {
354
- const ledger = book()
355
- const viaDiff = rowOf(derive(ledger, { op: "diff", operands: ["F1", "F2"], unit: "%p" }))
356
- const viaExpr = rowOf(derive(ledger, { op: "expr", formula: "F1 - F2", unit: "%p" }))
357
- expect(viaDiff).toMatchObject({ value: "4.8", render: "4.8 %p" })
358
- expect(viaExpr).toMatchObject({ value: "4.8", render: "4.8 %p" })
359
- expect(recompute(ledger, viaExpr).ok).toBe(true)
360
- })
361
-
362
- test("an expr with a - root renders 1000 m - 3 m as 997 m", () => {
363
- const ledger = book()
364
- const row = rowOf(derive(ledger, { op: "expr", formula: "F3 - F4", unit: "m" }))
365
- expect(row).toMatchObject({ value: "997", render: "997 m", exact: { num: "997", den: "1" } })
366
- expect(recompute(ledger, row).ok).toBe(true)
367
- })
368
-
369
- test("an expr with a * root keeps significant figures", () => {
370
- const ledger = book()
371
- const row = rowOf(derive(ledger, { op: "expr", formula: "F5 * 1.5", unit: "m" }))
372
- expect(row).toMatchObject({ value: "19", render: "19 m" })
373
- expect(recompute(ledger, row).ok).toBe(true)
374
- })
375
- })