lazycodex-ai 5.1.47 → 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 (438) hide show
  1. package/dist/cli/index.js +1194 -752
  2. package/dist/cli/service-auth/accounts-gate.d.ts +11 -0
  3. package/dist/cli/service-auth/commands.d.ts +2 -4
  4. package/dist/cli/service-auth/index.d.ts +1 -1
  5. package/dist/cli/service-auth/keystore.d.ts +26 -2
  6. package/dist/cli/service-auth/protocol.d.ts +7 -1
  7. package/dist/cli/service-auth/session.d.ts +6 -0
  8. package/dist/cli/service-auth/signed-out-marker.d.ts +11 -0
  9. package/dist/cli-node/index.js +1194 -752
  10. package/package.json +3 -1
  11. package/packages/lsp-core/package.json +45 -0
  12. package/packages/lsp-core/src/index.ts +30 -0
  13. package/packages/lsp-core/src/lsp/bun-command-output.ts +82 -0
  14. package/packages/lsp-core/src/lsp/bun-project-checks.test.ts +170 -0
  15. package/packages/lsp-core/src/lsp/bun-project-checks.ts +223 -0
  16. package/packages/lsp-core/src/lsp/cleanup-errors.test.ts +18 -0
  17. package/packages/lsp-core/src/lsp/cleanup-errors.ts +14 -0
  18. package/packages/lsp-core/src/lsp/client-diagnostics-concurrency.integration.test.ts +52 -0
  19. package/packages/lsp-core/src/lsp/client-diagnostics-freshness.integration.test.ts +471 -0
  20. package/packages/lsp-core/src/lsp/client-diagnostics-pull-timeout.test.ts +64 -0
  21. package/packages/lsp-core/src/lsp/client-wrapper-outside-cwd.test.ts +78 -0
  22. package/packages/lsp-core/src/lsp/client-wrapper.test.ts +213 -0
  23. package/packages/lsp-core/src/lsp/client-wrapper.ts +290 -0
  24. package/packages/lsp-core/src/lsp/client.ts +398 -0
  25. package/packages/lsp-core/src/lsp/config-loader.ts +254 -0
  26. package/packages/lsp-core/src/lsp/connection.ts +95 -0
  27. package/packages/lsp-core/src/lsp/constants.ts +17 -0
  28. package/packages/lsp-core/src/lsp/controlled-clock-test-support.ts +90 -0
  29. package/packages/lsp-core/src/lsp/directory-diagnostics.test.ts +221 -0
  30. package/packages/lsp-core/src/lsp/directory-diagnostics.ts +187 -0
  31. package/packages/lsp-core/src/lsp/effective-extension.ts +10 -0
  32. package/packages/lsp-core/src/lsp/errors.ts +89 -0
  33. package/packages/lsp-core/src/lsp/fake-bun-test-support.ts +102 -0
  34. package/packages/lsp-core/src/lsp/fixtures/diagnostics-freshness-contract-probe.ts +283 -0
  35. package/packages/lsp-core/src/lsp/fixtures/disposed-stdin-epipe-probe.ts +28 -0
  36. package/packages/lsp-core/src/lsp/fixtures/workspace-edit-contract-probe.ts +196 -0
  37. package/packages/lsp-core/src/lsp/fixtures/workspace-edit-server.mjs +234 -0
  38. package/packages/lsp-core/src/lsp/format-document.test.ts +143 -0
  39. package/packages/lsp-core/src/lsp/format-document.ts +90 -0
  40. package/packages/lsp-core/src/lsp/formatters.ts +144 -0
  41. package/packages/lsp-core/src/lsp/infer-extension.ts +66 -0
  42. package/packages/lsp-core/src/lsp/json-rpc-connection-cancellation.test.ts +97 -0
  43. package/packages/lsp-core/src/lsp/json-rpc-connection.ts +369 -0
  44. package/packages/lsp-core/src/lsp/language-mappings.ts +173 -0
  45. package/packages/lsp-core/src/lsp/manager-lifecycle.test.ts +262 -0
  46. package/packages/lsp-core/src/lsp/manager-max-clients.test.ts +151 -0
  47. package/packages/lsp-core/src/lsp/manager.ts +495 -0
  48. package/packages/lsp-core/src/lsp/outside-context-workspace.ts +33 -0
  49. package/packages/lsp-core/src/lsp/process-pipe-errors.test.ts +21 -0
  50. package/packages/lsp-core/src/lsp/process-signal-cleanup.ts +21 -0
  51. package/packages/lsp-core/src/lsp/process.ts +221 -0
  52. package/packages/lsp-core/src/lsp/server-definitions.ts +205 -0
  53. package/packages/lsp-core/src/lsp/server-install-state.ts +67 -0
  54. package/packages/lsp-core/src/lsp/server-installation.test.ts +376 -0
  55. package/packages/lsp-core/src/lsp/server-installation.ts +218 -0
  56. package/packages/lsp-core/src/lsp/server-resolution-local-binary.test.ts +170 -0
  57. package/packages/lsp-core/src/lsp/server-resolution.ts +117 -0
  58. package/packages/lsp-core/src/lsp/startup-failure.ts +1 -0
  59. package/packages/lsp-core/src/lsp/timer-provider.ts +13 -0
  60. package/packages/lsp-core/src/lsp/transport-protocol.ts +52 -0
  61. package/packages/lsp-core/src/lsp/transport-request-timeout.test.ts +73 -0
  62. package/packages/lsp-core/src/lsp/transport.ts +339 -0
  63. package/packages/lsp-core/src/lsp/types.ts +134 -0
  64. package/packages/lsp-core/src/lsp/utils.test.ts +72 -0
  65. package/packages/lsp-core/src/lsp/utils.ts +40 -0
  66. package/packages/lsp-core/src/lsp/workspace-apply-edit-failure.ts +19 -0
  67. package/packages/lsp-core/src/lsp/workspace-apply-edit-lease.integration.test.ts +218 -0
  68. package/packages/lsp-core/src/lsp/workspace-apply-edit-sync.integration.test.ts +113 -0
  69. package/packages/lsp-core/src/lsp/workspace-apply-edit-test-support.ts +194 -0
  70. package/packages/lsp-core/src/lsp/workspace-apply-edit.integration.test.ts +163 -0
  71. package/packages/lsp-core/src/lsp/workspace-document-state.test.ts +150 -0
  72. package/packages/lsp-core/src/lsp/workspace-document-state.ts +392 -0
  73. package/packages/lsp-core/src/lsp/workspace-edit-adversarial.test.ts +132 -0
  74. package/packages/lsp-core/src/lsp/workspace-edit-commit.test.ts +140 -0
  75. package/packages/lsp-core/src/lsp/workspace-edit-commit.ts +220 -0
  76. package/packages/lsp-core/src/lsp/workspace-edit-contract-evidence.test.ts +56 -0
  77. package/packages/lsp-core/src/lsp/workspace-edit-contract-evidence.ts +30 -0
  78. package/packages/lsp-core/src/lsp/workspace-edit-fingerprint.ts +44 -0
  79. package/packages/lsp-core/src/lsp/workspace-edit-options.test.ts +147 -0
  80. package/packages/lsp-core/src/lsp/workspace-edit-parse-helpers.ts +59 -0
  81. package/packages/lsp-core/src/lsp/workspace-edit-parser.ts +130 -0
  82. package/packages/lsp-core/src/lsp/workspace-edit-path.ts +98 -0
  83. package/packages/lsp-core/src/lsp/workspace-edit-plan-types.ts +60 -0
  84. package/packages/lsp-core/src/lsp/workspace-edit-plan.ts +73 -0
  85. package/packages/lsp-core/src/lsp/workspace-edit-prevalidation.test.ts +174 -0
  86. package/packages/lsp-core/src/lsp/workspace-edit-resource-parser.ts +89 -0
  87. package/packages/lsp-core/src/lsp/workspace-edit-simulation.ts +183 -0
  88. package/packages/lsp-core/src/lsp/workspace-edit-snapshot.ts +53 -0
  89. package/packages/lsp-core/src/lsp/workspace-edit-text.ts +125 -0
  90. package/packages/lsp-core/src/lsp/workspace-edit-types.ts +121 -0
  91. package/packages/lsp-core/src/lsp/workspace-edit.characterization.test.ts +95 -0
  92. package/packages/lsp-core/src/lsp/workspace-edit.ts +55 -0
  93. package/packages/lsp-core/src/lsp/workspace-markers.ts +18 -0
  94. package/packages/lsp-core/src/lsp/workspace-mutation-controller.ts +182 -0
  95. package/packages/lsp-core/src/mcp-protocol-pin.test.ts +97 -0
  96. package/packages/lsp-core/src/mcp.ts +122 -0
  97. package/packages/lsp-core/src/missing-dependency-result.test.ts +135 -0
  98. package/packages/lsp-core/src/missing-dependency-result.ts +79 -0
  99. package/packages/lsp-core/src/post-edit/file-location.ts +55 -0
  100. package/packages/lsp-core/src/post-edit/index.ts +3 -0
  101. package/packages/lsp-core/src/post-edit/not-installed-guidance.ts +36 -0
  102. package/packages/lsp-core/src/post-edit/not-installed.test.ts +224 -0
  103. package/packages/lsp-core/src/post-edit/orchestration.test.ts +157 -0
  104. package/packages/lsp-core/src/post-edit/orchestration.ts +205 -0
  105. package/packages/lsp-core/src/request-context.test.ts +200 -0
  106. package/packages/lsp-core/src/request-context.ts +235 -0
  107. package/packages/lsp-core/src/tool-surface.test.ts +173 -0
  108. package/packages/lsp-core/src/tools/bun-directory-diagnostics.test.ts +113 -0
  109. package/packages/lsp-core/src/tools/bun-directory-diagnostics.ts +75 -0
  110. package/packages/lsp-core/src/tools/definitions.ts +156 -0
  111. package/packages/lsp-core/src/tools/diagnostics.ts +133 -0
  112. package/packages/lsp-core/src/tools/format.test.ts +56 -0
  113. package/packages/lsp-core/src/tools/format.ts +56 -0
  114. package/packages/lsp-core/src/tools/index.ts +11 -0
  115. package/packages/lsp-core/src/tools/install-decision.ts +39 -0
  116. package/packages/lsp-core/src/tools/navigation.ts +88 -0
  117. package/packages/lsp-core/src/tools/parameters.test.ts +51 -0
  118. package/packages/lsp-core/src/tools/parameters.ts +49 -0
  119. package/packages/lsp-core/src/tools/rename.ts +69 -0
  120. package/packages/lsp-core/src/tools/result.ts +5 -0
  121. package/packages/lsp-core/src/tools/runtime.ts +21 -0
  122. package/packages/lsp-core/src/tools/schema.ts +9 -0
  123. package/packages/lsp-core/src/tools/status.ts +28 -0
  124. package/packages/lsp-core/src/tools/symbols.ts +99 -0
  125. package/packages/lsp-core/src/tools/types.ts +116 -0
  126. package/packages/lsp-core/src/tools.ts +1 -0
  127. package/packages/omo-codex/plugin/.codex-plugin/plugin.json +1 -1
  128. package/packages/omo-codex/plugin/README.md +1 -1
  129. package/packages/omo-codex/plugin/components/bootstrap/hooks/hooks.json +1 -1
  130. package/packages/omo-codex/plugin/components/bootstrap/package.json +1 -1
  131. package/packages/omo-codex/plugin/components/comment-checker/hooks/hooks.json +1 -1
  132. package/packages/omo-codex/plugin/components/comment-checker/package.json +1 -1
  133. package/packages/omo-codex/plugin/components/git-bash/hooks/hooks.json +2 -2
  134. package/packages/omo-codex/plugin/components/git-bash/package.json +1 -1
  135. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/hooks/hooks.json +1 -1
  136. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/package.json +1 -1
  137. package/packages/omo-codex/plugin/components/lcx/skills/lcx-contribute-bug-fix/SKILL.md +51 -55
  138. package/packages/omo-codex/plugin/components/lcx/skills/lcx-contribute-bug-fix/agents/openai.yaml +1 -1
  139. package/packages/omo-codex/plugin/components/lcx/skills/lcx-report-bug/SKILL.md +7 -7
  140. package/packages/omo-codex/plugin/components/lcx/skills/lcx-report-bug/agents/openai.yaml +1 -1
  141. package/packages/omo-codex/plugin/components/lsp/dist/.omo-runtime-manifest.json +2 -2
  142. package/packages/omo-codex/plugin/components/lsp/hooks/hooks.json +2 -2
  143. package/packages/omo-codex/plugin/components/lsp/package.json +1 -1
  144. package/packages/omo-codex/plugin/components/rules/hooks/hooks.json +4 -4
  145. package/packages/omo-codex/plugin/components/rules/package.json +1 -1
  146. package/packages/omo-codex/plugin/components/teammode/hooks/hooks.json +1 -1
  147. package/packages/omo-codex/plugin/components/teammode/package.json +2 -2
  148. package/packages/omo-codex/plugin/components/telemetry/README.md +2 -2
  149. package/packages/omo-codex/plugin/components/telemetry/dist/cli.js +52 -15
  150. package/packages/omo-codex/plugin/components/telemetry/dist/posthog.js +52 -19
  151. package/packages/omo-codex/plugin/components/telemetry/hooks/hooks.json +1 -1
  152. package/packages/omo-codex/plugin/components/telemetry/package.json +1 -1
  153. package/packages/omo-codex/plugin/components/telemetry/src/codex-hook.ts +2 -9
  154. package/packages/omo-codex/plugin/components/telemetry/src/posthog.ts +4 -13
  155. package/packages/omo-codex/plugin/components/telemetry/src/product-identity.ts +0 -2
  156. package/packages/omo-codex/plugin/components/telemetry/test/codex-hook-diagnostics.test.ts +0 -3
  157. package/packages/omo-codex/plugin/components/telemetry/test/codex-hook.test.ts +3 -8
  158. package/packages/omo-codex/plugin/components/ultrawork/dist/cli.js +3 -0
  159. package/packages/omo-codex/plugin/components/ultrawork/hooks/hooks.json +1 -1
  160. package/packages/omo-codex/plugin/components/ultrawork/package.json +1 -1
  161. package/packages/omo-codex/plugin/components/ultrawork/src/directive-content.ts +1 -1
  162. package/packages/omo-codex/plugin/components/ulw-execute-continuation/hooks/hooks.json +1 -1
  163. package/packages/omo-codex/plugin/components/ulw-execute-continuation/package.json +1 -1
  164. package/packages/omo-codex/plugin/components/ulw-loop/directive.md +3 -0
  165. package/packages/omo-codex/plugin/components/ulw-loop/hooks/hooks.json +5 -5
  166. package/packages/omo-codex/plugin/components/ulw-loop/package.json +1 -1
  167. package/packages/omo-codex/plugin/hooks/post-compact-resetting-git-bash-mcp-reminder.json +1 -1
  168. package/packages/omo-codex/plugin/hooks/post-compact-resetting-lsp-diagnostics-cache.json +1 -1
  169. package/packages/omo-codex/plugin/hooks/post-compact-resetting-project-rule-cache.json +1 -1
  170. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-comments.json +1 -1
  171. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-lsp-diagnostics.json +1 -1
  172. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-thread-title-hygiene.json +1 -1
  173. package/packages/omo-codex/plugin/hooks/post-tool-use-matching-project-rules.json +1 -1
  174. package/packages/omo-codex/plugin/hooks/post-tool-use-recording-spawn-admission.json +1 -1
  175. package/packages/omo-codex/plugin/hooks/pre-tool-use-enforcing-unlimited-goal-budget.json +1 -1
  176. package/packages/omo-codex/plugin/hooks/pre-tool-use-guarding-ulw-loop-spawns.json +1 -1
  177. package/packages/omo-codex/plugin/hooks/pre-tool-use-recommending-git-bash-mcp.json +1 -1
  178. package/packages/omo-codex/plugin/hooks/session-start-checking-auto-update.json +1 -1
  179. package/packages/omo-codex/plugin/hooks/session-start-checking-bootstrap-provisioning.json +1 -1
  180. package/packages/omo-codex/plugin/hooks/session-start-loading-project-rules.json +1 -1
  181. package/packages/omo-codex/plugin/hooks/session-start-recording-session-telemetry.json +1 -1
  182. package/packages/omo-codex/plugin/hooks/stop-checking-ulw-execute-continuation.json +1 -1
  183. package/packages/omo-codex/plugin/hooks/stop-checking-ulw-loop-resume.json +1 -1
  184. package/packages/omo-codex/plugin/hooks/subagent-stop-verifying-lazycodex-executor-evidence.json +1 -1
  185. package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ultrawork-trigger.json +1 -1
  186. package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ulw-loop-steering.json +1 -1
  187. package/packages/omo-codex/plugin/hooks/user-prompt-submit-loading-project-rules.json +1 -1
  188. package/packages/omo-codex/plugin/package-lock.json +16 -16
  189. package/packages/omo-codex/plugin/package.json +1 -1
  190. package/packages/omo-codex/plugin/shared/package.json +1 -1
  191. package/packages/omo-codex/plugin/skills/ast-grep/README.md +1 -1
  192. package/packages/omo-codex/plugin/skills/ast-grep/install.ps1 +5 -3
  193. package/packages/omo-codex/plugin/skills/ast-grep/install.sh +4 -5
  194. package/packages/omo-codex/plugin/skills/ast-grep/references/install.md +2 -2
  195. package/packages/omo-codex/plugin/skills/ast-grep/scripts/ast_grep_helper.py +13 -3
  196. package/packages/omo-codex/plugin/skills/browser/runtime/omowright/manifest.json +1 -1
  197. package/packages/omo-codex/plugin/skills/coding-agent-sessions/AGENTS.md +5 -4
  198. package/packages/omo-codex/plugin/skills/coding-agent-sessions/SKILL.md +11 -11
  199. package/packages/omo-codex/plugin/skills/coding-agent-sessions/references/all-platforms.md +2 -2
  200. package/packages/omo-codex/plugin/skills/coding-agent-sessions/references/codex.md +4 -4
  201. package/packages/omo-codex/plugin/skills/coding-agent-sessions/scripts/agent_sessions/cli.py +1 -1
  202. package/packages/omo-codex/plugin/skills/coding-agent-sessions/scripts/find-agent-sessions.py +6 -3
  203. package/packages/omo-codex/plugin/skills/frontend/ATTRIBUTION.md +1 -1
  204. package/packages/omo-codex/plugin/skills/frontend/SKILL.md +34 -12
  205. package/packages/omo-codex/plugin/skills/frontend/references/design/README.md +2 -2
  206. package/packages/omo-codex/plugin/skills/frontend/references/design/_INDEX.md +2 -1
  207. package/packages/omo-codex/plugin/skills/frontend/references/design/component-catalogs.md +14 -10
  208. package/packages/omo-codex/plugin/skills/frontend/references/design/design-system-architecture.md +19 -6
  209. package/packages/omo-codex/plugin/skills/frontend/references/design/grounding.md +29 -0
  210. package/packages/omo-codex/plugin/skills/frontend/references/ui-ux-db/scripts/design_system.py +2 -0
  211. package/packages/omo-codex/plugin/skills/frontend/references/ui-ux-db/scripts/search.py +2 -0
  212. package/packages/omo-codex/plugin/skills/lcx-contribute-bug-fix/SKILL.md +51 -55
  213. package/packages/omo-codex/plugin/skills/lcx-contribute-bug-fix/agents/openai.yaml +1 -1
  214. package/packages/omo-codex/plugin/skills/lcx-report-bug/SKILL.md +7 -7
  215. package/packages/omo-codex/plugin/skills/lcx-report-bug/agents/openai.yaml +1 -1
  216. package/packages/omo-codex/plugin/skills/ultimate-browsing/SKILL.md +5 -4
  217. package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/AGENTS.md +2 -2
  218. package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/__main__.py +9 -6
  219. package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/summary.py +1 -1
  220. package/packages/omo-codex/plugin/skills/ultimate-browsing/references/insane-search/README.md +4 -4
  221. package/packages/omo-codex/plugin/skills/ultimate-browsing/scripts/extract_cookies.py +4 -2
  222. package/packages/omo-codex/plugin/skills/ultrawork/SKILL.md +3 -0
  223. package/packages/omo-codex/plugin/skills/ulw-research/AGENTS.md +8 -4
  224. package/packages/omo-codex/plugin/skills/ulw-research/SKILL.md +7 -259
  225. package/packages/omo-codex/plugin/skills/ulw-research/references/deliverable-phase.md +68 -24
  226. package/packages/omo-codex/plugin/skills/ulw-research/references/fact-ledger-grammar.md +7 -3
  227. package/packages/omo-codex/plugin/skills/ulw-research/references/fact-ledger.md +42 -8
  228. package/packages/omo-codex/plugin/skills/ulw-research/references/fidelity-claim-kinds.md +1 -1
  229. package/packages/omo-codex/plugin/skills/ulw-research/references/markdown-blocks.md +12 -0
  230. package/packages/omo-codex/plugin/skills/ulw-research/references/report-gates.md +14 -6
  231. package/packages/omo-codex/plugin/skills/ulw-research/references/research-collection.md +195 -0
  232. package/packages/omo-codex/plugin/skills/ulw-research/references/research-delivery.md +123 -0
  233. package/packages/omo-codex/plugin/skills/ulw-research/scripts/claim-kinds.mjs +33 -5
  234. package/packages/omo-codex/plugin/skills/ulw-research/scripts/contracts.mjs +16 -15
  235. package/packages/omo-codex/plugin/skills/ulw-research/scripts/conversion-probe.mjs +229 -0
  236. package/packages/omo-codex/plugin/skills/ulw-research/scripts/css-hiding.mjs +158 -0
  237. package/packages/omo-codex/plugin/skills/ulw-research/scripts/css-lite.mjs +4 -2
  238. package/packages/omo-codex/plugin/skills/ulw-research/scripts/entities.mjs +13 -4
  239. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-commands-fill.mjs +96 -0
  240. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-commands-meaning.mjs +26 -30
  241. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-commands-polish.mjs +46 -19
  242. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-commands-verify.mjs +37 -6
  243. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-commands.mjs +40 -62
  244. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-derive.mjs +7 -2
  245. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-fill.mjs +9 -6
  246. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-ledger-kinds.mjs +6 -7
  247. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-locate.mjs +15 -9
  248. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-claims.mjs +4 -3
  249. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-csv.mjs +32 -0
  250. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-lexicon.mjs +1 -1
  251. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-originals.mjs +161 -0
  252. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-policy.mjs +183 -0
  253. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-records.mjs +81 -14
  254. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-snapshot-readability.mjs +49 -0
  255. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-sources.mjs +76 -0
  256. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-trace.mjs +25 -11
  257. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-value-unit.mjs +202 -0
  258. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-values.mjs +40 -39
  259. package/packages/omo-codex/plugin/skills/ulw-research/scripts/gates-fidelity.mjs +8 -6
  260. package/packages/omo-codex/plugin/skills/ulw-research/scripts/gates-static.mjs +10 -1
  261. package/packages/omo-codex/plugin/skills/ulw-research/scripts/html-lite.mjs +6 -2
  262. package/packages/omo-codex/plugin/skills/ulw-research/scripts/html-markup.mjs +2 -2
  263. package/packages/omo-codex/plugin/skills/ulw-research/scripts/html-text-map.mjs +140 -0
  264. package/packages/omo-codex/plugin/skills/ulw-research/scripts/markdown-closers.mjs +126 -0
  265. package/packages/omo-codex/plugin/skills/ulw-research/scripts/md-lite.mjs +1 -1
  266. package/packages/omo-codex/plugin/skills/ulw-research/scripts/number-grammar.mjs +33 -0
  267. package/packages/omo-codex/plugin/skills/ulw-research/scripts/numerals.mjs +18 -7
  268. package/packages/omo-codex/plugin/skills/ulw-research/scripts/outcome-start.mjs +66 -0
  269. package/packages/omo-codex/plugin/skills/ulw-research/scripts/outcome.mjs +11 -22
  270. package/packages/omo-codex/plugin/skills/ulw-research/scripts/placeholder.mjs +18 -6
  271. package/packages/omo-codex/plugin/skills/ulw-research/scripts/polish-apply.mjs +3 -2
  272. package/packages/omo-codex/plugin/skills/ulw-research/scripts/raw-decode.mjs +110 -0
  273. package/packages/omo-codex/plugin/skills/ulw-research/scripts/raw-locator.mjs +47 -0
  274. package/packages/omo-codex/plugin/skills/ulw-research/scripts/report-tools-commands.mjs +1 -1
  275. package/packages/omo-codex/plugin/skills/ulw-research/scripts/report-tools.mjs +7 -1
  276. package/packages/omo-codex/plugin/skills/ulw-research/scripts/source-commands.mjs +87 -29
  277. package/packages/omo-codex/plugin/skills/ulw-research/scripts/source-refresh.mjs +82 -0
  278. package/packages/omo-codex/plugin/skills/ulw-research/scripts/source-snapshot.mjs +73 -69
  279. package/packages/omo-codex/plugin/skills/ulw-research/scripts/source-view.mjs +89 -0
  280. package/packages/omo-codex/plugin/skills/ulw-research/scripts/sources-citation.mjs +203 -0
  281. package/packages/omo-codex/plugin/skills/ulw-research/scripts/units.mjs +14 -3
  282. package/packages/omo-codex/plugin/test/payload-equivalence.test.mjs +6 -4
  283. package/packages/omo-codex/plugin/test/sync-skills.test.mjs +4 -3
  284. package/packages/omo-codex/scripts/install-dist/install-local.mjs +338 -237
  285. package/packages/prompts-core/prompts/ultrawork/codex.md +3 -0
  286. package/packages/shared-skills/skill-source-filter.mjs +6 -2
  287. package/packages/shared-skills/skills/ast-grep/README.md +1 -1
  288. package/packages/shared-skills/skills/ast-grep/install.ps1 +5 -3
  289. package/packages/shared-skills/skills/ast-grep/install.sh +4 -5
  290. package/packages/shared-skills/skills/ast-grep/references/install.md +2 -2
  291. package/packages/shared-skills/skills/ast-grep/scripts/ast_grep_helper.py +13 -3
  292. package/packages/shared-skills/skills/browser/runtime/omowright/manifest.json +1 -1
  293. package/packages/shared-skills/skills/coding-agent-sessions/AGENTS.md +5 -4
  294. package/packages/shared-skills/skills/coding-agent-sessions/SKILL.md +11 -11
  295. package/packages/shared-skills/skills/coding-agent-sessions/references/all-platforms.md +2 -2
  296. package/packages/shared-skills/skills/coding-agent-sessions/references/codex.md +4 -4
  297. package/packages/shared-skills/skills/coding-agent-sessions/scripts/agent_sessions/cli.py +1 -1
  298. package/packages/shared-skills/skills/coding-agent-sessions/scripts/find-agent-sessions.py +6 -3
  299. package/packages/shared-skills/skills/frontend/ATTRIBUTION.md +1 -1
  300. package/packages/shared-skills/skills/frontend/SKILL.md +34 -12
  301. package/packages/shared-skills/skills/frontend/references/design/README.md +2 -2
  302. package/packages/shared-skills/skills/frontend/references/design/_INDEX.md +2 -1
  303. package/packages/shared-skills/skills/frontend/references/design/component-catalogs.md +14 -10
  304. package/packages/shared-skills/skills/frontend/references/design/design-system-architecture.md +19 -6
  305. package/packages/shared-skills/skills/frontend/references/design/grounding.md +29 -0
  306. package/packages/shared-skills/skills/frontend/references/ui-ux-db/scripts/design_system.py +2 -0
  307. package/packages/shared-skills/skills/frontend/references/ui-ux-db/scripts/search.py +2 -0
  308. package/packages/shared-skills/skills/ultimate-browsing/SKILL.md +5 -4
  309. package/packages/shared-skills/skills/ultimate-browsing/engine/AGENTS.md +2 -2
  310. package/packages/shared-skills/skills/ultimate-browsing/engine/__main__.py +9 -6
  311. package/packages/shared-skills/skills/ultimate-browsing/engine/summary.py +1 -1
  312. package/packages/shared-skills/skills/ultimate-browsing/references/insane-search/README.md +4 -4
  313. package/packages/shared-skills/skills/ultimate-browsing/scripts/extract_cookies.py +4 -2
  314. package/packages/shared-skills/skills/ulw-research/AGENTS.md +8 -4
  315. package/packages/shared-skills/skills/ulw-research/SKILL.md +7 -259
  316. package/packages/shared-skills/skills/ulw-research/references/deliverable-phase.md +68 -24
  317. package/packages/shared-skills/skills/ulw-research/references/fact-ledger-grammar.md +7 -3
  318. package/packages/shared-skills/skills/ulw-research/references/fact-ledger.md +42 -8
  319. package/packages/shared-skills/skills/ulw-research/references/fidelity-claim-kinds.md +1 -1
  320. package/packages/shared-skills/skills/ulw-research/references/markdown-blocks.md +12 -0
  321. package/packages/shared-skills/skills/ulw-research/references/report-gates.md +14 -6
  322. package/packages/shared-skills/skills/ulw-research/references/research-collection.md +195 -0
  323. package/packages/shared-skills/skills/ulw-research/references/research-delivery.md +123 -0
  324. package/packages/shared-skills/skills/ulw-research/scripts/claim-kinds.mjs +33 -5
  325. package/packages/shared-skills/skills/ulw-research/scripts/claim-kinds.test.ts +37 -2
  326. package/packages/shared-skills/skills/ulw-research/scripts/claim-prefix.test.ts +76 -0
  327. package/packages/shared-skills/skills/ulw-research/scripts/contracts-validation.test.ts +83 -0
  328. package/packages/shared-skills/skills/ulw-research/scripts/contracts.mjs +16 -15
  329. package/packages/shared-skills/skills/ulw-research/scripts/contracts.test.ts +6 -109
  330. package/packages/shared-skills/skills/ulw-research/scripts/conversion-altered.test.ts +154 -0
  331. package/packages/shared-skills/skills/ulw-research/scripts/conversion-originals.test.ts +297 -0
  332. package/packages/shared-skills/skills/ulw-research/scripts/conversion-probe-classes.test.ts +361 -0
  333. package/packages/shared-skills/skills/ulw-research/scripts/conversion-probe.mjs +229 -0
  334. package/packages/shared-skills/skills/ulw-research/scripts/conversion-probe.test.ts +218 -0
  335. package/packages/shared-skills/skills/ulw-research/scripts/copy-returns-fidelity-policy.test.ts +156 -0
  336. package/packages/shared-skills/skills/ulw-research/scripts/copy-returns-polish.test.ts +10 -9
  337. package/packages/shared-skills/skills/ulw-research/scripts/css-hiding.mjs +158 -0
  338. package/packages/shared-skills/skills/ulw-research/scripts/css-lite.mjs +4 -2
  339. package/packages/shared-skills/skills/ulw-research/scripts/delivery-barrier-meaning.test.ts +186 -0
  340. package/packages/shared-skills/skills/ulw-research/scripts/delivery-barrier.test.ts +40 -87
  341. package/packages/shared-skills/skills/ulw-research/scripts/entities.mjs +13 -4
  342. package/packages/shared-skills/skills/ulw-research/scripts/fact-commands-fill.mjs +96 -0
  343. package/packages/shared-skills/skills/ulw-research/scripts/fact-commands-meaning.mjs +26 -30
  344. package/packages/shared-skills/skills/ulw-research/scripts/fact-commands-polish.mjs +46 -19
  345. package/packages/shared-skills/skills/ulw-research/scripts/fact-commands-verify.mjs +37 -6
  346. package/packages/shared-skills/skills/ulw-research/scripts/fact-commands.mjs +40 -62
  347. package/packages/shared-skills/skills/ulw-research/scripts/fact-derive-2.test.ts +149 -0
  348. package/packages/shared-skills/skills/ulw-research/scripts/fact-derive.mjs +7 -2
  349. package/packages/shared-skills/skills/ulw-research/scripts/fact-derive.test.ts +7 -192
  350. package/packages/shared-skills/skills/ulw-research/scripts/fact-fill.mjs +9 -6
  351. package/packages/shared-skills/skills/ulw-research/scripts/fact-ledger-kinds.mjs +6 -7
  352. package/packages/shared-skills/skills/ulw-research/scripts/fact-ledger-kinds.test.ts +2 -1
  353. package/packages/shared-skills/skills/ulw-research/scripts/fact-ledger-legacy.test.ts +2 -1
  354. package/packages/shared-skills/skills/ulw-research/scripts/fact-locate.mjs +15 -9
  355. package/packages/shared-skills/skills/ulw-research/scripts/fact-unit-dimensions-2.test.ts +149 -0
  356. package/packages/shared-skills/skills/ulw-research/scripts/fact-unit-dimensions.test.ts +4 -168
  357. package/packages/shared-skills/skills/ulw-research/scripts/fact-unit-rules.test.ts +3 -229
  358. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-claims.mjs +4 -3
  359. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-csv.mjs +32 -0
  360. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-lexicon.mjs +1 -1
  361. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-originals.mjs +161 -0
  362. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-policy.mjs +183 -0
  363. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-records.mjs +81 -14
  364. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-snapshot-readability.mjs +49 -0
  365. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-sources.mjs +76 -0
  366. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-trace.mjs +25 -11
  367. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-value-unit.mjs +202 -0
  368. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-values.mjs +40 -39
  369. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-values.test.ts +8 -29
  370. package/packages/shared-skills/skills/ulw-research/scripts/gates-fidelity.mjs +8 -6
  371. package/packages/shared-skills/skills/ulw-research/scripts/gates-static-headings.test.ts +17 -0
  372. package/packages/shared-skills/skills/ulw-research/scripts/gates-static.mjs +10 -1
  373. package/packages/shared-skills/skills/ulw-research/scripts/html-lite.mjs +6 -2
  374. package/packages/shared-skills/skills/ulw-research/scripts/html-markup.mjs +2 -2
  375. package/packages/shared-skills/skills/ulw-research/scripts/html-text-map.mjs +140 -0
  376. package/packages/shared-skills/skills/ulw-research/scripts/html-text-map.test.ts +148 -0
  377. package/packages/shared-skills/skills/ulw-research/scripts/markdown-closers.mjs +126 -0
  378. package/packages/shared-skills/skills/ulw-research/scripts/md-lite-boundaries.test.ts +46 -0
  379. package/packages/shared-skills/skills/ulw-research/scripts/md-lite.mjs +1 -1
  380. package/packages/shared-skills/skills/ulw-research/scripts/number-grammar.mjs +33 -0
  381. package/packages/shared-skills/skills/ulw-research/scripts/number-grammar.test.ts +36 -0
  382. package/packages/shared-skills/skills/ulw-research/scripts/numerals.mjs +18 -7
  383. package/packages/shared-skills/skills/ulw-research/scripts/numerals.test.ts +3 -0
  384. package/packages/shared-skills/skills/ulw-research/scripts/outcome-start.mjs +66 -0
  385. package/packages/shared-skills/skills/ulw-research/scripts/outcome-start.test.ts +109 -0
  386. package/packages/shared-skills/skills/ulw-research/scripts/outcome.mjs +11 -22
  387. package/packages/shared-skills/skills/ulw-research/scripts/page-source-locator.test.ts +187 -0
  388. package/packages/shared-skills/skills/ulw-research/scripts/placeholder.mjs +18 -6
  389. package/packages/shared-skills/skills/ulw-research/scripts/polish-apply.mjs +3 -2
  390. package/packages/shared-skills/skills/ulw-research/scripts/polish-markup.test.ts +15 -1
  391. package/packages/shared-skills/skills/ulw-research/scripts/polish-partial.test.ts +42 -0
  392. package/packages/shared-skills/skills/ulw-research/scripts/raw-decode.mjs +110 -0
  393. package/packages/shared-skills/skills/ulw-research/scripts/raw-locator.mjs +47 -0
  394. package/packages/shared-skills/skills/ulw-research/scripts/raw-snapshot.test.ts +139 -0
  395. package/packages/shared-skills/skills/ulw-research/scripts/report-tools-2.test.ts +233 -0
  396. package/packages/shared-skills/skills/ulw-research/scripts/report-tools-3.test.ts +141 -0
  397. package/packages/shared-skills/skills/ulw-research/scripts/report-tools-4.test.ts +135 -0
  398. package/packages/shared-skills/skills/ulw-research/scripts/report-tools-5.test.ts +231 -0
  399. package/packages/shared-skills/skills/ulw-research/scripts/report-tools-commands.mjs +1 -1
  400. package/packages/shared-skills/skills/ulw-research/scripts/report-tools.mjs +7 -1
  401. package/packages/shared-skills/skills/ulw-research/scripts/report-tools.test.ts +6 -560
  402. package/packages/shared-skills/skills/ulw-research/scripts/snapshot-readability.test.ts +116 -0
  403. package/packages/shared-skills/skills/ulw-research/scripts/source-commands.mjs +87 -29
  404. package/packages/shared-skills/skills/ulw-research/scripts/source-integrity.test.ts +243 -0
  405. package/packages/shared-skills/skills/ulw-research/scripts/source-refresh.mjs +82 -0
  406. package/packages/shared-skills/skills/ulw-research/scripts/source-snapshot.mjs +73 -69
  407. package/packages/shared-skills/skills/ulw-research/scripts/source-snapshot.test.ts +7 -4
  408. package/packages/shared-skills/skills/ulw-research/scripts/source-view.mjs +89 -0
  409. package/packages/shared-skills/skills/ulw-research/scripts/sources-citation.mjs +203 -0
  410. package/packages/shared-skills/skills/ulw-research/scripts/sources-citation.test.ts +236 -0
  411. package/packages/shared-skills/skills/ulw-research/scripts/tests/contracts-support.ts +147 -0
  412. package/packages/shared-skills/skills/ulw-research/scripts/tests/delivery-barrier-session.ts +59 -0
  413. package/packages/shared-skills/skills/ulw-research/scripts/tests/derive-clock.ts +4 -0
  414. package/packages/shared-skills/skills/ulw-research/scripts/tests/fact-derive-support.ts +65 -0
  415. package/packages/shared-skills/skills/ulw-research/scripts/tests/fact-unit-dimensions-support.ts +41 -0
  416. package/packages/shared-skills/skills/ulw-research/scripts/tests/fact-unit-rules-support.ts +241 -0
  417. package/packages/shared-skills/skills/ulw-research/scripts/tests/fidelity-values-support.ts +30 -0
  418. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/conversion-probe.html +14 -0
  419. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/sources-shapes.html +20 -0
  420. package/packages/shared-skills/skills/ulw-research/scripts/tests/numeral-classes.ts +196 -0
  421. package/packages/shared-skills/skills/ulw-research/scripts/tests/report-tools-support.ts +182 -0
  422. package/packages/shared-skills/skills/ulw-research/scripts/tests/value-mode-support.ts +48 -0
  423. package/packages/shared-skills/skills/ulw-research/scripts/units.mjs +14 -3
  424. package/packages/shared-skills/skills/ulw-research/scripts/units.test.ts +10 -0
  425. package/packages/shared-skills/skills/ulw-research/scripts/value-mode-unit-classes.test.ts +255 -0
  426. package/packages/shared-skills/skills/ulw-research/scripts/value-mode-unit.test.ts +179 -0
  427. package/packages/shared-skills/skills/ulw-research/scripts/value-rules-cli.test.ts +157 -0
  428. package/packages/shared-skills/skills/ulw-research/scripts/value-rules-followups.test.ts +72 -0
  429. package/packages/shared-skills/skills/ulw-research/scripts/value-rules-priority.test.ts +80 -0
  430. package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/tests/fixtures/amp_redirect_stub.html +0 -7
  431. package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/tests/fixtures/search_interstitial.html +0 -19
  432. package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/tests/fixtures/wayback_available.json +0 -1
  433. package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/tests/fixtures/wayback_snapshot.html +0 -1128
  434. package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/tests/test_fetch_chain.py +0 -77
  435. package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/tests/test_playwright_stealth.py +0 -86
  436. package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/tests/test_playwright_templates.py +0 -279
  437. package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/tests/test_surrogate.py +0 -252
  438. package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/tests/test_surrogate_validators.py +0 -78
@@ -1,23 +1,23 @@
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
1
+ // fact-commands.mjs - the `fact` subcommands of report-tools: add, quote, retract, export, derive,
2
+ // fill (fact-commands-fill.mjs), verify (fact-commands-verify.mjs), pairs and meaning-check
3
+ // (fact-commands-meaning.mjs), polish-apply and polish-diff (fact-commands-polish.mjs). Every value reaches the ledger from a source
4
4
  // snapshot through a deterministic locator or from a declared derivation; nothing here accepts a
5
5
  // typed value. add and quote print the row with 60 chars of source context on each side (JSON
6
6
  // with --json). Exit 0 ok, 1 semantic failure (fact_not_located, fill errors, a refused
7
7
  // derivation), 2 usage. node: builtins and sibling imports only.
8
8
 
9
9
  import { existsSync, readdirSync, readFileSync, writeFileSync } from "node:fs"
10
- import { dirname, extname, join, resolve } from "node:path"
10
+ import { join, resolve } from "node:path"
11
11
 
12
- import { CliError, parseArgs, sha256Of, writeJsonAtomic } from "./cli-support.mjs"
12
+ import { CliError, parseArgs, sha256Of } from "./cli-support.mjs"
13
13
  import { DEFECT_CODES } from "./contracts.mjs"
14
+ import { alteredIn } from "./conversion-probe.mjs"
14
15
  import { derive } from "./fact-derive.mjs"
15
- import { fill } from "./fact-fill.mjs"
16
16
  import { addRow, readLedger, retractRow, supersedeRow, writeLedger } from "./fact-ledger.mjs"
17
17
  import { locateFind, locateMatch, locatePageLine, locateTable } from "./fact-locate.mjs"
18
18
  import { QUALIFIER_FORMS } from "./fidelity-lexicon.mjs"
19
- import { readState, writeState } from "./fidelity-state.mjs"
20
19
  import { exportRows } from "./fidelity-values.mjs"
20
+ import { rawLocated } from "./raw-locator.mjs"
21
21
  import { pointedFlag, sessionDirOf, snapshotOf } from "./source-commands.mjs"
22
22
 
23
23
  const CONTEXT = 60
@@ -33,26 +33,30 @@ function readText(path) {
33
33
  }
34
34
  }
35
35
 
36
- function nowIso(flags) {
37
- 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)
38
39
  if (Number.isNaN(date.getTime())) throw new CliError("--now requires an ISO timestamp")
39
40
  return date.toISOString()
40
41
  }
41
42
 
43
+ const nowIso = (flags) => (flags.now === undefined ? new Date().toISOString() : isoOf(flags.now))
44
+
42
45
  function failure(command, error, detail) {
43
46
  const json = { command, status: "fail", exitCode: 1, error, ...detail }
44
47
  return { json, summary: `${command}: ${error}${detail.message ? ` (${detail.message})` : ""}`, exitCode: 1 }
45
48
  }
46
49
 
47
- 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) {
48
52
  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 })
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 })
51
55
  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 })
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 })
54
58
  }
55
- 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 })
56
60
  throw new CliError("usage: fact add --source S<n> (--find TEXT | --table T --row R --col C | --page P --line L) --label LABEL")
57
61
  }
58
62
 
@@ -79,11 +83,17 @@ function runAdd(args, quote) {
79
83
  const id = args.required("source")
80
84
  if (quote) args.required("match")
81
85
  const sessionDir = sessionDirOf(flags)
82
- const snapshot = snapshotOf(sessionDir, id)
83
- 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)
84
93
  if (found.error === "unit_expected_mismatch") return failure(command, "unit_mismatch", { message: `expected ${found.expected}, found ${found.found}`, candidates: found.candidates })
85
94
  if (found.error) return failure(command, found.error, { message: `no single match in ${id}`, candidates: found.candidates })
86
- 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 })
87
97
  const host = URL.canParse(meta.url) ? new URL(meta.url).hostname : null
88
98
  const labelSource = flags.find ?? flags.match ?? (flags.table !== undefined ? `${flags.row}_${flags.col}` : `p${flags.page}_l${flags.line}`)
89
99
  const ledger = readLedger(sessionDir)
@@ -92,9 +102,10 @@ function runAdd(args, quote) {
92
102
  ...(quote ? {} : { value: found.value, unit: found.unit, value_span: found.value_span, qualifier: QUALIFIER_OF.get(found.qualifier) ?? null }),
93
103
  raw: found.raw,
94
104
  quote: found.quote,
105
+ context: found.context,
95
106
  source_id: id,
96
107
  locator: { ...found.locator, source_kind: meta.kind, url: meta.url, host, pointed: pointedFlag(sessionDir, meta.url) },
97
- snapshot_sha256: meta.sha256,
108
+ snapshot_sha256: sha256,
98
109
  sentence_id: found.sentence_id ?? null,
99
110
  extracted_by: meta.extractor,
100
111
  observed_at: nowIso(flags),
@@ -107,11 +118,14 @@ function runAdd(args, quote) {
107
118
  fuzzy: found.fuzzy === true,
108
119
  })
109
120
  writeLedger(sessionDir, ledger)
110
- const context = contextOf(snapshot.text, row)
111
- 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 } }
112
125
  if (flags.json) return { json, exitCode: 0 }
113
126
  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 }
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 }
115
129
  }
116
130
 
117
131
  function runRetract(args) {
@@ -157,52 +171,16 @@ function runExport(args) {
157
171
  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
172
  }
159
173
 
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
174
  function runDerive(args) {
199
175
  const { flags } = args
200
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")
201
179
  const sessionDir = sessionDirOf(flags)
202
180
  const ledger = readLedger(sessionDir)
203
181
  // The registry refuses any field outside the operation's inputFields, so only the given flags go in; --unit null declares a dimensionless result.
204
182
  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]])) }
183
+ const spec = { op, now: isoOf(flags.now), ...Object.fromEntries(picked.map((key) => [key, flags[key]])) }
206
184
  if (op === "expr") spec.formula = args.positionals[0]
207
185
  else if (args.positionals.length > 0) spec.operands = args.positionals
208
186
  if (flags.disjoint === true) spec.disjoint = true
@@ -230,7 +208,7 @@ export async function runFact(argv) {
230
208
  case "export":
231
209
  return runExport(args)
232
210
  case "fill":
233
- return runFill(args)
211
+ return (await import("./fact-commands-fill.mjs")).runFactFill(args)
234
212
  case "derive":
235
213
  return runDerive(args)
236
214
  case "verify":
@@ -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
- })