oh-my-opencode 5.1.45 → 5.1.47

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (370) hide show
  1. package/.agents/command/publish.md +12 -13
  2. package/.agents/skills/publish/SKILL.md +12 -13
  3. package/.opencode/command/publish.md +12 -13
  4. package/README.de.md +0 -5
  5. package/README.es.md +0 -5
  6. package/README.fr.md +0 -5
  7. package/README.it.md +0 -5
  8. package/README.ja.md +0 -5
  9. package/README.ko.md +0 -5
  10. package/README.md +4 -5
  11. package/README.pt-br.md +0 -5
  12. package/README.ru.md +0 -6
  13. package/README.zh-cn.md +0 -5
  14. package/dist/agents/atlas/agent.d.ts +3 -2
  15. package/dist/agents/sisyphus/index.d.ts +1 -0
  16. package/dist/agents/sisyphus/kimi-k3-1.d.ts +9 -0
  17. package/dist/agents/sisyphus-agent-factory.d.ts +1 -1
  18. package/dist/agents/sisyphus-junior/agent.d.ts +1 -1
  19. package/dist/agents/sisyphus-junior/index.d.ts +1 -0
  20. package/dist/agents/sisyphus-junior/kimi-k3-1.d.ts +6 -0
  21. package/dist/agents/types.d.ts +2 -2
  22. package/dist/cli/index.js +91 -163
  23. package/dist/cli/service-auth/keystore.d.ts +8 -3
  24. package/dist/cli-node/index.js +91 -163
  25. package/dist/config-migration/index.d.ts +2 -2
  26. package/dist/config-migration/migration-plans.d.ts +0 -10
  27. package/dist/index.js +1784 -1204
  28. package/dist/skills/ast-grep/AGENTS.md +1 -1
  29. package/dist/skills/browser/runtime/omowright/manifest.json +1 -1
  30. package/dist/skills/frontend/ATTRIBUTION.md +2 -2
  31. package/dist/skills/ultimate-browsing/engine/AGENTS.md +1 -1
  32. package/dist/skills/ulw-research/AGENTS.md +14 -2
  33. package/dist/skills/ulw-research/SKILL.md +37 -12
  34. package/dist/skills/ulw-research/references/deliverable-phase.md +81 -14
  35. package/dist/skills/ulw-research/references/fact-ledger-grammar.md +19 -1
  36. package/dist/skills/ulw-research/references/fact-ledger.md +95 -0
  37. package/dist/skills/ulw-research/references/fidelity-claim-kinds.md +19 -0
  38. package/dist/skills/ulw-research/references/report-gates.md +21 -4
  39. package/dist/skills/ulw-research/references/report-visualization.md +2 -0
  40. package/dist/skills/ulw-research/references/unit-rules.md +157 -0
  41. package/dist/skills/ulw-research/scripts/claim-kinds.mjs +116 -0
  42. package/dist/skills/ulw-research/scripts/claim-kinds.test.ts +193 -0
  43. package/dist/skills/ulw-research/scripts/cli-support.mjs +19 -5
  44. package/dist/skills/ulw-research/scripts/cli-support.test.ts +13 -2
  45. package/dist/skills/ulw-research/scripts/contracts.mjs +27 -6
  46. package/dist/skills/ulw-research/scripts/contracts.test.ts +3 -0
  47. package/dist/skills/ulw-research/scripts/copy-returns-polish.test.ts +209 -0
  48. package/dist/skills/ulw-research/scripts/copy-returns.test.ts +183 -0
  49. package/dist/skills/ulw-research/scripts/delivery-barrier.test.ts +245 -0
  50. package/dist/skills/ulw-research/scripts/fact-commands-meaning.mjs +176 -0
  51. package/dist/skills/ulw-research/scripts/fact-commands-polish.mjs +127 -0
  52. package/dist/skills/ulw-research/scripts/fact-commands-verify.mjs +183 -0
  53. package/dist/skills/ulw-research/scripts/fact-commands.mjs +241 -0
  54. package/dist/skills/ulw-research/scripts/fact-derive.mjs +66 -0
  55. package/dist/skills/ulw-research/scripts/fact-derive.test.ts +375 -0
  56. package/dist/skills/ulw-research/scripts/fact-expr.mjs +108 -0
  57. package/dist/skills/ulw-research/scripts/fact-fill.mjs +194 -0
  58. package/dist/skills/ulw-research/scripts/fact-fill.test.ts +221 -0
  59. package/dist/skills/ulw-research/scripts/fact-ledger-grammar.test.ts +9 -2
  60. package/dist/skills/ulw-research/scripts/fact-ledger-kinds.mjs +127 -13
  61. package/dist/skills/ulw-research/scripts/fact-ledger-kinds.test.ts +18 -3
  62. package/dist/skills/ulw-research/scripts/fact-ledger-legacy.test.ts +160 -0
  63. package/dist/skills/ulw-research/scripts/fact-ledger-schema.mjs +33 -16
  64. package/dist/skills/ulw-research/scripts/fact-ledger.mjs +29 -9
  65. package/dist/skills/ulw-research/scripts/fact-ledger.test.ts +2 -0
  66. package/dist/skills/ulw-research/scripts/fact-locate.mjs +231 -0
  67. package/dist/skills/ulw-research/scripts/fact-locate.test.ts +118 -0
  68. package/dist/skills/ulw-research/scripts/fact-pairs.mjs +185 -0
  69. package/dist/skills/ulw-research/scripts/fact-pairs.test.ts +151 -0
  70. package/dist/skills/ulw-research/scripts/fact-recompute.mjs +89 -0
  71. package/dist/skills/ulw-research/scripts/fact-unit-dimensions.test.ts +409 -0
  72. package/dist/skills/ulw-research/scripts/fact-unit-rules.test.ts +388 -0
  73. package/dist/skills/ulw-research/scripts/fidelity-binding.mjs +130 -0
  74. package/dist/skills/ulw-research/scripts/fidelity-binding.test.ts +81 -0
  75. package/dist/skills/ulw-research/scripts/fidelity-brief.mjs +5 -0
  76. package/dist/skills/ulw-research/scripts/fidelity-claims.mjs +89 -0
  77. package/dist/skills/ulw-research/scripts/fidelity-lexicon.mjs +145 -0
  78. package/dist/skills/ulw-research/scripts/fidelity-lexicon.test.ts +117 -0
  79. package/dist/skills/ulw-research/scripts/fidelity-records.mjs +163 -0
  80. package/dist/skills/ulw-research/scripts/fidelity-state.mjs +2 -1
  81. package/dist/skills/ulw-research/scripts/fidelity-trace.mjs +85 -0
  82. package/dist/skills/ulw-research/scripts/fidelity-trace.test.ts +58 -0
  83. package/dist/skills/ulw-research/scripts/fidelity-values.mjs +229 -0
  84. package/dist/skills/ulw-research/scripts/fidelity-values.test.ts +270 -0
  85. package/dist/skills/ulw-research/scripts/gates-fidelity.mjs +113 -0
  86. package/dist/skills/ulw-research/scripts/gates-fidelity.test.ts +73 -0
  87. package/dist/skills/ulw-research/scripts/gates-static.mjs +28 -8
  88. package/dist/skills/ulw-research/scripts/html-lite.mjs +2 -2
  89. package/dist/skills/ulw-research/scripts/html-markup.mjs +182 -0
  90. package/dist/skills/ulw-research/scripts/md-lite.mjs +193 -0
  91. package/dist/skills/ulw-research/scripts/md-lite.test.ts +271 -0
  92. package/dist/skills/ulw-research/scripts/meaning-check.mjs +196 -0
  93. package/dist/skills/ulw-research/scripts/meaning-check.test.ts +228 -0
  94. package/dist/skills/ulw-research/scripts/outcome.mjs +6 -5
  95. package/dist/skills/ulw-research/scripts/placeholder.mjs +184 -0
  96. package/dist/skills/ulw-research/scripts/placeholder.test.ts +77 -0
  97. package/dist/skills/ulw-research/scripts/polish-apply.mjs +115 -0
  98. package/dist/skills/ulw-research/scripts/polish-apply.test.ts +363 -0
  99. package/dist/skills/ulw-research/scripts/polish-diff.mjs +31 -0
  100. package/dist/skills/ulw-research/scripts/polish-diff.test.ts +168 -0
  101. package/dist/skills/ulw-research/scripts/polish-markup.test.ts +187 -0
  102. package/dist/skills/ulw-research/scripts/polish-project.mjs +125 -0
  103. package/dist/skills/ulw-research/scripts/polish-rules.mjs +170 -0
  104. package/dist/skills/ulw-research/scripts/references-fences.test.ts +25 -0
  105. package/dist/skills/ulw-research/scripts/report-tools-commands.mjs +19 -8
  106. package/dist/skills/ulw-research/scripts/report-tools.mjs +15 -0
  107. package/dist/skills/ulw-research/scripts/report-tools.test.ts +545 -2
  108. package/dist/skills/ulw-research/scripts/source-commands.mjs +140 -0
  109. package/dist/skills/ulw-research/scripts/source-snapshot.mjs +176 -0
  110. package/dist/skills/ulw-research/scripts/source-snapshot.test.ts +113 -0
  111. package/dist/skills/ulw-research/scripts/tests/fixtures/fidelity/assets/share.csv +3 -0
  112. package/dist/skills/ulw-research/scripts/tests/fixtures/fidelity/gen-legacy-dev.mjs +89 -0
  113. package/dist/skills/ulw-research/scripts/tests/fixtures/fidelity/ledger-basic.json +223 -0
  114. package/dist/skills/ulw-research/scripts/tests/fixtures/fidelity/ledger-legacy-dev.json +859 -0
  115. package/dist/skills/ulw-research/scripts/tests/fixtures/fidelity/ledger-verify.json +420 -0
  116. package/dist/skills/ulw-research/scripts/tests/fixtures/fidelity/polish-edits.json +12 -0
  117. package/dist/skills/ulw-research/scripts/tests/fixtures/fidelity/report-bad.html +19 -0
  118. package/dist/skills/ulw-research/scripts/tests/fixtures/fidelity/report-good.html +39 -0
  119. package/dist/skills/ulw-research/scripts/tests/fixtures/fidelity/sources/S4/acronyms.json +3 -0
  120. package/dist/skills/ulw-research/scripts/tests/fixtures/fidelity/sources/S4/meta.json +9 -0
  121. package/dist/skills/ulw-research/scripts/tests/fixtures/fidelity/sources/S4/sentences.jsonl +7 -0
  122. package/dist/skills/ulw-research/scripts/tests/fixtures/fidelity/sources/S4/text.txt +7 -0
  123. package/dist/skills/ulw-research/scripts/tests/fixtures/fidelity/sources/S4.html +19 -0
  124. package/dist/skills/ulw-research/scripts/tests/fixtures/fidelity/sources/S5/acronyms.json +1 -0
  125. package/dist/skills/ulw-research/scripts/tests/fixtures/fidelity/sources/S5/meta.json +9 -0
  126. package/dist/skills/ulw-research/scripts/tests/fixtures/fidelity/sources/S5/sentences.jsonl +1 -0
  127. package/dist/skills/ulw-research/scripts/tests/fixtures/fidelity/sources/S5/text.txt +1 -0
  128. package/dist/skills/ulw-research/scripts/tests/fixtures/fidelity/sources/S9.txt +1 -0
  129. package/dist/skills/ulw-research/scripts/unit-algebra.mjs +101 -0
  130. package/dist/skills/ulw-research/scripts/unit-errors.mjs +23 -0
  131. package/dist/skills/ulw-research/scripts/unit-metadata.mjs +31 -0
  132. package/dist/skills/ulw-research/scripts/unit-pairs.mjs +72 -0
  133. package/dist/skills/ulw-research/scripts/unit-precision.mjs +44 -0
  134. package/dist/skills/ulw-research/scripts/unit-rules-shapes.mjs +64 -0
  135. package/dist/skills/ulw-research/scripts/unit-rules.mjs +173 -0
  136. package/dist/skills/ulw-research/scripts/units.mjs +4 -4
  137. package/dist/testing/module-mock-lifecycle.d.ts +1 -1
  138. package/dist/tui.js +32 -34
  139. package/package.json +13 -13
  140. package/packages/lsp-core/src/mcp-protocol-pin.test.ts +29 -0
  141. package/packages/lsp-core/src/mcp.ts +5 -2
  142. package/packages/lsp-daemon/dist/cli.js +3 -3
  143. package/packages/lsp-daemon/dist/client.js +3 -3
  144. package/packages/lsp-daemon/dist/index.js +3 -3
  145. package/packages/lsp-tools-mcp/dist/cli.js +3 -3
  146. package/packages/lsp-tools-mcp/dist/mcp.js +3 -3
  147. package/packages/omo-codex/plugin/.codex-plugin/plugin.json +1 -1
  148. package/packages/omo-codex/plugin/components/bootstrap/dist/cli.js +23 -14
  149. package/packages/omo-codex/plugin/components/bootstrap/hooks/hooks.json +1 -1
  150. package/packages/omo-codex/plugin/components/bootstrap/package.json +1 -1
  151. package/packages/omo-codex/plugin/components/comment-checker/hooks/hooks.json +1 -1
  152. package/packages/omo-codex/plugin/components/comment-checker/package.json +1 -1
  153. package/packages/omo-codex/plugin/components/git-bash/hooks/hooks.json +2 -2
  154. package/packages/omo-codex/plugin/components/git-bash/package.json +1 -1
  155. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/hooks/hooks.json +1 -1
  156. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/package.json +1 -1
  157. package/packages/omo-codex/plugin/components/lsp/dist/.omo-runtime-manifest.json +2 -2
  158. package/packages/omo-codex/plugin/components/lsp/hooks/hooks.json +2 -2
  159. package/packages/omo-codex/plugin/components/lsp/package.json +1 -1
  160. package/packages/omo-codex/plugin/components/rules/hooks/hooks.json +4 -4
  161. package/packages/omo-codex/plugin/components/rules/package.json +1 -1
  162. package/packages/omo-codex/plugin/components/teammode/hooks/hooks.json +1 -1
  163. package/packages/omo-codex/plugin/components/teammode/package.json +1 -1
  164. package/packages/omo-codex/plugin/components/telemetry/hooks/hooks.json +1 -1
  165. package/packages/omo-codex/plugin/components/telemetry/package.json +1 -1
  166. package/packages/omo-codex/plugin/components/ultrawork/hooks/hooks.json +1 -1
  167. package/packages/omo-codex/plugin/components/ultrawork/package.json +1 -1
  168. package/packages/omo-codex/plugin/components/ulw-execute-continuation/hooks/hooks.json +1 -1
  169. package/packages/omo-codex/plugin/components/ulw-execute-continuation/package.json +1 -1
  170. package/packages/omo-codex/plugin/components/ulw-loop/hooks/hooks.json +5 -5
  171. package/packages/omo-codex/plugin/components/ulw-loop/package.json +1 -1
  172. package/packages/omo-codex/plugin/hooks/post-compact-resetting-git-bash-mcp-reminder.json +1 -1
  173. package/packages/omo-codex/plugin/hooks/post-compact-resetting-lsp-diagnostics-cache.json +1 -1
  174. package/packages/omo-codex/plugin/hooks/post-compact-resetting-project-rule-cache.json +1 -1
  175. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-comments.json +1 -1
  176. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-lsp-diagnostics.json +1 -1
  177. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-thread-title-hygiene.json +1 -1
  178. package/packages/omo-codex/plugin/hooks/post-tool-use-matching-project-rules.json +1 -1
  179. package/packages/omo-codex/plugin/hooks/post-tool-use-recording-spawn-admission.json +1 -1
  180. package/packages/omo-codex/plugin/hooks/pre-tool-use-enforcing-unlimited-goal-budget.json +1 -1
  181. package/packages/omo-codex/plugin/hooks/pre-tool-use-guarding-ulw-loop-spawns.json +1 -1
  182. package/packages/omo-codex/plugin/hooks/pre-tool-use-recommending-git-bash-mcp.json +1 -1
  183. package/packages/omo-codex/plugin/hooks/session-start-checking-auto-update.json +1 -1
  184. package/packages/omo-codex/plugin/hooks/session-start-checking-bootstrap-provisioning.json +1 -1
  185. package/packages/omo-codex/plugin/hooks/session-start-loading-project-rules.json +1 -1
  186. package/packages/omo-codex/plugin/hooks/session-start-recording-session-telemetry.json +1 -1
  187. package/packages/omo-codex/plugin/hooks/stop-checking-ulw-execute-continuation.json +1 -1
  188. package/packages/omo-codex/plugin/hooks/stop-checking-ulw-loop-resume.json +1 -1
  189. package/packages/omo-codex/plugin/hooks/subagent-stop-verifying-lazycodex-executor-evidence.json +1 -1
  190. package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ultrawork-trigger.json +1 -1
  191. package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ulw-loop-steering.json +1 -1
  192. package/packages/omo-codex/plugin/hooks/user-prompt-submit-loading-project-rules.json +1 -1
  193. package/packages/omo-codex/plugin/package-lock.json +12 -12
  194. package/packages/omo-codex/plugin/package.json +1 -1
  195. package/packages/omo-codex/plugin/scripts/sync-skills.mjs +8 -3
  196. package/packages/omo-codex/plugin/shared/test/config-loader.test.ts +0 -2
  197. package/packages/omo-codex/plugin/skills/ast-grep/AGENTS.md +1 -1
  198. package/packages/omo-codex/plugin/skills/browser/runtime/omowright/manifest.json +1 -1
  199. package/packages/omo-codex/plugin/skills/frontend/ATTRIBUTION.md +2 -2
  200. package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/AGENTS.md +1 -1
  201. package/packages/omo-codex/plugin/skills/ulw-research/AGENTS.md +14 -2
  202. package/packages/omo-codex/plugin/skills/ulw-research/SKILL.md +36 -11
  203. package/packages/omo-codex/plugin/skills/ulw-research/references/deliverable-phase.md +81 -14
  204. package/packages/omo-codex/plugin/skills/ulw-research/references/fact-ledger-grammar.md +19 -1
  205. package/packages/omo-codex/plugin/skills/ulw-research/references/fact-ledger.md +95 -0
  206. package/packages/omo-codex/plugin/skills/ulw-research/references/fidelity-claim-kinds.md +19 -0
  207. package/packages/omo-codex/plugin/skills/ulw-research/references/report-gates.md +21 -4
  208. package/packages/omo-codex/plugin/skills/ulw-research/references/report-visualization.md +2 -0
  209. package/packages/omo-codex/plugin/skills/ulw-research/references/unit-rules.md +157 -0
  210. package/packages/omo-codex/plugin/skills/ulw-research/scripts/claim-kinds.mjs +116 -0
  211. package/packages/omo-codex/plugin/skills/ulw-research/scripts/cli-support.mjs +19 -5
  212. package/packages/omo-codex/plugin/skills/ulw-research/scripts/contracts.mjs +27 -6
  213. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-commands-meaning.mjs +176 -0
  214. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-commands-polish.mjs +127 -0
  215. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-commands-verify.mjs +183 -0
  216. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-commands.mjs +241 -0
  217. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-derive.mjs +66 -0
  218. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-expr.mjs +108 -0
  219. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-fill.mjs +194 -0
  220. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-ledger-kinds.mjs +127 -13
  221. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-ledger-schema.mjs +33 -16
  222. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-ledger.mjs +29 -9
  223. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-locate.mjs +231 -0
  224. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-pairs.mjs +185 -0
  225. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-recompute.mjs +89 -0
  226. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-binding.mjs +130 -0
  227. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-brief.mjs +5 -0
  228. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-claims.mjs +89 -0
  229. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-lexicon.mjs +145 -0
  230. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-records.mjs +163 -0
  231. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-state.mjs +2 -1
  232. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-trace.mjs +85 -0
  233. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-values.mjs +229 -0
  234. package/packages/omo-codex/plugin/skills/ulw-research/scripts/gates-fidelity.mjs +113 -0
  235. package/packages/omo-codex/plugin/skills/ulw-research/scripts/gates-static.mjs +28 -8
  236. package/packages/omo-codex/plugin/skills/ulw-research/scripts/html-lite.mjs +2 -2
  237. package/packages/omo-codex/plugin/skills/ulw-research/scripts/html-markup.mjs +182 -0
  238. package/packages/omo-codex/plugin/skills/ulw-research/scripts/md-lite.mjs +193 -0
  239. package/packages/omo-codex/plugin/skills/ulw-research/scripts/meaning-check.mjs +196 -0
  240. package/packages/omo-codex/plugin/skills/ulw-research/scripts/outcome.mjs +6 -5
  241. package/packages/omo-codex/plugin/skills/ulw-research/scripts/placeholder.mjs +184 -0
  242. package/packages/omo-codex/plugin/skills/ulw-research/scripts/polish-apply.mjs +115 -0
  243. package/packages/omo-codex/plugin/skills/ulw-research/scripts/polish-diff.mjs +31 -0
  244. package/packages/omo-codex/plugin/skills/ulw-research/scripts/polish-project.mjs +125 -0
  245. package/packages/omo-codex/plugin/skills/ulw-research/scripts/polish-rules.mjs +170 -0
  246. package/packages/omo-codex/plugin/skills/ulw-research/scripts/report-tools-commands.mjs +19 -8
  247. package/packages/omo-codex/plugin/skills/ulw-research/scripts/report-tools.mjs +15 -0
  248. package/packages/omo-codex/plugin/skills/ulw-research/scripts/source-commands.mjs +140 -0
  249. package/packages/omo-codex/plugin/skills/ulw-research/scripts/source-snapshot.mjs +176 -0
  250. package/packages/omo-codex/plugin/skills/ulw-research/scripts/unit-algebra.mjs +101 -0
  251. package/packages/omo-codex/plugin/skills/ulw-research/scripts/unit-errors.mjs +23 -0
  252. package/packages/omo-codex/plugin/skills/ulw-research/scripts/unit-metadata.mjs +31 -0
  253. package/packages/omo-codex/plugin/skills/ulw-research/scripts/unit-pairs.mjs +72 -0
  254. package/packages/omo-codex/plugin/skills/ulw-research/scripts/unit-precision.mjs +44 -0
  255. package/packages/omo-codex/plugin/skills/ulw-research/scripts/unit-rules-shapes.mjs +64 -0
  256. package/packages/omo-codex/plugin/skills/ulw-research/scripts/unit-rules.mjs +173 -0
  257. package/packages/omo-codex/plugin/skills/ulw-research/scripts/units.mjs +4 -4
  258. package/packages/omo-codex/plugin/test/sync-skills-codex-compatibility.test.mjs +19 -0
  259. package/packages/omo-codex/plugin/test/sync-skills-test-support.mjs +7 -3
  260. package/packages/omo-codex/scripts/install-dist/install-local.mjs +28 -19
  261. package/packages/shared-skills/skills/ast-grep/AGENTS.md +1 -1
  262. package/packages/shared-skills/skills/browser/runtime/omowright/manifest.json +1 -1
  263. package/packages/shared-skills/skills/frontend/ATTRIBUTION.md +2 -2
  264. package/packages/shared-skills/skills/ultimate-browsing/engine/AGENTS.md +1 -1
  265. package/packages/shared-skills/skills/ulw-research/AGENTS.md +14 -2
  266. package/packages/shared-skills/skills/ulw-research/SKILL.md +37 -12
  267. package/packages/shared-skills/skills/ulw-research/references/deliverable-phase.md +81 -14
  268. package/packages/shared-skills/skills/ulw-research/references/fact-ledger-grammar.md +19 -1
  269. package/packages/shared-skills/skills/ulw-research/references/fact-ledger.md +95 -0
  270. package/packages/shared-skills/skills/ulw-research/references/fidelity-claim-kinds.md +19 -0
  271. package/packages/shared-skills/skills/ulw-research/references/report-gates.md +21 -4
  272. package/packages/shared-skills/skills/ulw-research/references/report-visualization.md +2 -0
  273. package/packages/shared-skills/skills/ulw-research/references/unit-rules.md +157 -0
  274. package/packages/shared-skills/skills/ulw-research/scripts/claim-kinds.mjs +116 -0
  275. package/packages/shared-skills/skills/ulw-research/scripts/claim-kinds.test.ts +193 -0
  276. package/packages/shared-skills/skills/ulw-research/scripts/cli-support.mjs +19 -5
  277. package/packages/shared-skills/skills/ulw-research/scripts/cli-support.test.ts +13 -2
  278. package/packages/shared-skills/skills/ulw-research/scripts/contracts.mjs +27 -6
  279. package/packages/shared-skills/skills/ulw-research/scripts/contracts.test.ts +3 -0
  280. package/packages/shared-skills/skills/ulw-research/scripts/copy-returns-polish.test.ts +209 -0
  281. package/packages/shared-skills/skills/ulw-research/scripts/copy-returns.test.ts +183 -0
  282. package/packages/shared-skills/skills/ulw-research/scripts/delivery-barrier.test.ts +245 -0
  283. package/packages/shared-skills/skills/ulw-research/scripts/fact-commands-meaning.mjs +176 -0
  284. package/packages/shared-skills/skills/ulw-research/scripts/fact-commands-polish.mjs +127 -0
  285. package/packages/shared-skills/skills/ulw-research/scripts/fact-commands-verify.mjs +183 -0
  286. package/packages/shared-skills/skills/ulw-research/scripts/fact-commands.mjs +241 -0
  287. package/packages/shared-skills/skills/ulw-research/scripts/fact-derive.mjs +66 -0
  288. package/packages/shared-skills/skills/ulw-research/scripts/fact-derive.test.ts +375 -0
  289. package/packages/shared-skills/skills/ulw-research/scripts/fact-expr.mjs +108 -0
  290. package/packages/shared-skills/skills/ulw-research/scripts/fact-fill.mjs +194 -0
  291. package/packages/shared-skills/skills/ulw-research/scripts/fact-fill.test.ts +221 -0
  292. package/packages/shared-skills/skills/ulw-research/scripts/fact-ledger-grammar.test.ts +9 -2
  293. package/packages/shared-skills/skills/ulw-research/scripts/fact-ledger-kinds.mjs +127 -13
  294. package/packages/shared-skills/skills/ulw-research/scripts/fact-ledger-kinds.test.ts +18 -3
  295. package/packages/shared-skills/skills/ulw-research/scripts/fact-ledger-legacy.test.ts +160 -0
  296. package/packages/shared-skills/skills/ulw-research/scripts/fact-ledger-schema.mjs +33 -16
  297. package/packages/shared-skills/skills/ulw-research/scripts/fact-ledger.mjs +29 -9
  298. package/packages/shared-skills/skills/ulw-research/scripts/fact-ledger.test.ts +2 -0
  299. package/packages/shared-skills/skills/ulw-research/scripts/fact-locate.mjs +231 -0
  300. package/packages/shared-skills/skills/ulw-research/scripts/fact-locate.test.ts +118 -0
  301. package/packages/shared-skills/skills/ulw-research/scripts/fact-pairs.mjs +185 -0
  302. package/packages/shared-skills/skills/ulw-research/scripts/fact-pairs.test.ts +151 -0
  303. package/packages/shared-skills/skills/ulw-research/scripts/fact-recompute.mjs +89 -0
  304. package/packages/shared-skills/skills/ulw-research/scripts/fact-unit-dimensions.test.ts +409 -0
  305. package/packages/shared-skills/skills/ulw-research/scripts/fact-unit-rules.test.ts +388 -0
  306. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-binding.mjs +130 -0
  307. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-binding.test.ts +81 -0
  308. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-brief.mjs +5 -0
  309. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-claims.mjs +89 -0
  310. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-lexicon.mjs +145 -0
  311. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-lexicon.test.ts +117 -0
  312. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-records.mjs +163 -0
  313. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-state.mjs +2 -1
  314. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-trace.mjs +85 -0
  315. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-trace.test.ts +58 -0
  316. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-values.mjs +229 -0
  317. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-values.test.ts +270 -0
  318. package/packages/shared-skills/skills/ulw-research/scripts/gates-fidelity.mjs +113 -0
  319. package/packages/shared-skills/skills/ulw-research/scripts/gates-fidelity.test.ts +73 -0
  320. package/packages/shared-skills/skills/ulw-research/scripts/gates-static.mjs +28 -8
  321. package/packages/shared-skills/skills/ulw-research/scripts/html-lite.mjs +2 -2
  322. package/packages/shared-skills/skills/ulw-research/scripts/html-markup.mjs +182 -0
  323. package/packages/shared-skills/skills/ulw-research/scripts/md-lite.mjs +193 -0
  324. package/packages/shared-skills/skills/ulw-research/scripts/md-lite.test.ts +271 -0
  325. package/packages/shared-skills/skills/ulw-research/scripts/meaning-check.mjs +196 -0
  326. package/packages/shared-skills/skills/ulw-research/scripts/meaning-check.test.ts +228 -0
  327. package/packages/shared-skills/skills/ulw-research/scripts/outcome.mjs +6 -5
  328. package/packages/shared-skills/skills/ulw-research/scripts/placeholder.mjs +184 -0
  329. package/packages/shared-skills/skills/ulw-research/scripts/placeholder.test.ts +77 -0
  330. package/packages/shared-skills/skills/ulw-research/scripts/polish-apply.mjs +115 -0
  331. package/packages/shared-skills/skills/ulw-research/scripts/polish-apply.test.ts +363 -0
  332. package/packages/shared-skills/skills/ulw-research/scripts/polish-diff.mjs +31 -0
  333. package/packages/shared-skills/skills/ulw-research/scripts/polish-diff.test.ts +168 -0
  334. package/packages/shared-skills/skills/ulw-research/scripts/polish-markup.test.ts +187 -0
  335. package/packages/shared-skills/skills/ulw-research/scripts/polish-project.mjs +125 -0
  336. package/packages/shared-skills/skills/ulw-research/scripts/polish-rules.mjs +170 -0
  337. package/packages/shared-skills/skills/ulw-research/scripts/references-fences.test.ts +25 -0
  338. package/packages/shared-skills/skills/ulw-research/scripts/report-tools-commands.mjs +19 -8
  339. package/packages/shared-skills/skills/ulw-research/scripts/report-tools.mjs +15 -0
  340. package/packages/shared-skills/skills/ulw-research/scripts/report-tools.test.ts +545 -2
  341. package/packages/shared-skills/skills/ulw-research/scripts/source-commands.mjs +140 -0
  342. package/packages/shared-skills/skills/ulw-research/scripts/source-snapshot.mjs +176 -0
  343. package/packages/shared-skills/skills/ulw-research/scripts/source-snapshot.test.ts +113 -0
  344. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/assets/share.csv +3 -0
  345. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/gen-legacy-dev.mjs +89 -0
  346. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/ledger-basic.json +223 -0
  347. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/ledger-legacy-dev.json +859 -0
  348. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/ledger-verify.json +420 -0
  349. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/polish-edits.json +12 -0
  350. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/report-bad.html +19 -0
  351. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/report-good.html +39 -0
  352. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/sources/S4/acronyms.json +3 -0
  353. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/sources/S4/meta.json +9 -0
  354. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/sources/S4/sentences.jsonl +7 -0
  355. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/sources/S4/text.txt +7 -0
  356. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/sources/S4.html +19 -0
  357. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/sources/S5/acronyms.json +1 -0
  358. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/sources/S5/meta.json +9 -0
  359. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/sources/S5/sentences.jsonl +1 -0
  360. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/sources/S5/text.txt +1 -0
  361. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/sources/S9.txt +1 -0
  362. package/packages/shared-skills/skills/ulw-research/scripts/unit-algebra.mjs +101 -0
  363. package/packages/shared-skills/skills/ulw-research/scripts/unit-errors.mjs +23 -0
  364. package/packages/shared-skills/skills/ulw-research/scripts/unit-metadata.mjs +31 -0
  365. package/packages/shared-skills/skills/ulw-research/scripts/unit-pairs.mjs +72 -0
  366. package/packages/shared-skills/skills/ulw-research/scripts/unit-precision.mjs +44 -0
  367. package/packages/shared-skills/skills/ulw-research/scripts/unit-rules-shapes.mjs +64 -0
  368. package/packages/shared-skills/skills/ulw-research/scripts/unit-rules.mjs +173 -0
  369. package/packages/shared-skills/skills/ulw-research/scripts/units.mjs +4 -4
  370. package/dist/config-migration/dori-section-rename.d.ts +0 -3
@@ -1,7 +1,8 @@
1
1
  // cli-support.mjs - argument parsing, typed usage errors, exit-code mapping, and JSON file IO
2
2
  // shared by every report-tools subcommand. node: builtins only.
3
3
 
4
- import { readFileSync, renameSync, writeFileSync } from "node:fs"
4
+ import { createHash } from "node:crypto"
5
+ import { readFileSync, renameSync, rmSync, writeFileSync } from "node:fs"
5
6
  import { basename, dirname, join } from "node:path"
6
7
 
7
8
  /** A usage or input error: always exit code 2. */
@@ -87,11 +88,24 @@ export function readJsonFile(path) {
87
88
  }
88
89
  }
89
90
 
90
- /** Write JSON through a sibling temp file + rename, so a crash never leaves a half-written file. */
91
- export function writeJsonAtomic(path, value) {
91
+ /** The sha256 hex digest of a string or bytes: the one hash every record and receipt carries. */
92
+ export const sha256Of = (content) => createHash("sha256").update(content).digest("hex")
93
+
94
+ /** Write text through a sibling temp file + rename; a failed write removes the temp file. */
95
+ export function writeTextAtomic(path, text) {
92
96
  const temp = join(dirname(path), `.${basename(path)}.${process.pid}.${Date.now()}.tmp`)
93
- writeFileSync(temp, `${JSON.stringify(value, null, 2)}\n`)
94
- renameSync(temp, path)
97
+ try {
98
+ writeFileSync(temp, text)
99
+ renameSync(temp, path)
100
+ } catch (error) {
101
+ rmSync(temp, { force: true })
102
+ throw error
103
+ }
104
+ }
105
+
106
+ /** Write JSON through writeTextAtomic, so a crash never leaves a half-written file. */
107
+ export function writeJsonAtomic(path, value) {
108
+ writeTextAtomic(path, `${JSON.stringify(value, null, 2)}\n`)
95
109
  }
96
110
 
97
111
  /** Print a result: JSON on stdout, one summary line on stderr. */
@@ -1,10 +1,10 @@
1
1
  import { describe, expect, onTestFinished, test } from "bun:test"
2
- import { mkdtempSync, readdirSync, readFileSync, rmSync, writeFileSync } from "node:fs"
2
+ import { mkdirSync, mkdtempSync, readdirSync, readFileSync, rmSync, writeFileSync } from "node:fs"
3
3
  import { tmpdir } from "node:os"
4
4
  import { join } from "node:path"
5
5
  import { pathToFileURL } from "node:url"
6
6
 
7
- import { CliError, exitCodeFor, parseArgs, readJsonFile, writeJsonAtomic } from "./cli-support.mjs"
7
+ import { CliError, exitCodeFor, parseArgs, readJsonFile, writeJsonAtomic, writeTextAtomic } from "./cli-support.mjs"
8
8
  import { isCliEntry } from "./entry-guard.mjs"
9
9
 
10
10
  function tempDir() {
@@ -65,6 +65,17 @@ describe("JSON files", () => {
65
65
  expect(leftovers).toEqual([])
66
66
  })
67
67
 
68
+ test("#given a target the rename cannot replace #when written atomically #then the write throws and no temp file is left", () => {
69
+ // given: a non-empty directory where the file should go
70
+ const dir = tempDir()
71
+ const target = join(dir, "calibration.jsonl")
72
+ mkdirSync(target)
73
+ writeFileSync(join(target, "keep"), "x")
74
+ // when / then
75
+ expect(() => writeTextAtomic(target, "line\n")).toThrow()
76
+ expect(readdirSync(dir)).toEqual(["calibration.jsonl"])
77
+ })
78
+
68
79
  test("#given a missing or malformed file #when read #then a CliError names the path", () => {
69
80
  const dir = tempDir()
70
81
  expect(() => readJsonFile(join(dir, "missing.json"))).toThrow(CliError)
@@ -6,6 +6,16 @@
6
6
 
7
7
  export const SCHEMA_VERSION = 1
8
8
 
9
+ /** The severity vocabulary in rank order; every DEFECT_CODES entry uses one of these. */
10
+ export const SEVERITIES = Object.freeze(["blocker", "major", "minor"])
11
+
12
+ /** Defect counts per severity, then total. */
13
+ export function countDefects(defects) {
14
+ const counts = Object.fromEntries(SEVERITIES.map((severity) => [severity, 0]))
15
+ for (const defect of defects) counts[defect.severity] += 1
16
+ return { ...counts, total: defects.length }
17
+ }
18
+
9
19
  function deepFreeze(value) {
10
20
  if (value === null || typeof value !== "object") return value
11
21
  for (const key of Object.keys(value)) deepFreeze(value[key])
@@ -48,7 +58,7 @@ export const DEFECT_CODES = deepFreeze({
48
58
  pointer_label_mismatch: { severity: "blocker", integrity: true, hint: "The placeholder's label is not the row's label; check the id, then the label." },
49
59
  quote_anchor_unresolved: { severity: "blocker", integrity: true, hint: "A from or to anchor is missing or occurs more than once in the quote; pick unique anchor words." },
50
60
  unit_duplicated: { severity: "blocker", integrity: true, hint: "The unit after the placeholder repeats the row's unit; use :value or remove the unit." },
51
- polish_diff_fail: { severity: "blocker", integrity: true, hint: "The polish pass changed numbers, tickers, or table rows; revert to the pre-polish artifact and apply the language fixes only." },
61
+ polish_diff_fail: { severity: "blocker", integrity: true, hint: "The polish pass changed numbers, tickers, table rows, claim ids, markup, or hidden content; revert to the pre-polish artifact and apply the language fixes only." },
52
62
  fidelity_interview_incomplete: { severity: "blocker", integrity: true, hint: "A fidelity field is pending or missing in brief.md; answer the open question or record its default, then rerun." },
53
63
  derived_not_allowed: { severity: "blocker", integrity: true, hint: "The brief allows no derived or assumed numbers; cite a source row directly or change the brief with the requester." },
54
64
  source_outside_floor: { severity: "blocker", integrity: true, hint: "The row's source is outside the source floor the brief records; use a source the brief allows or remove the fact." },
@@ -57,7 +67,8 @@ export const DEFECT_CODES = deepFreeze({
57
67
  quote_unbound: { severity: "blocker", integrity: true, hint: "A direct quotation has no ledger quote row; register it with fact quote and insert it through its placeholder." },
58
68
  edition_unverified: { severity: "blocker", integrity: true, hint: "The delivered file has no matching fact verify record; run fact verify on that edition and record it with --verified-by." },
59
69
  untraceable_number: { severity: "major", integrity: false, hint: "Replace the typed number with a {{F<n>}} placeholder backed by a ledger row, or register it as assumed with a rationale." },
60
- unsupported_verification: { severity: "major", integrity: false, hint: "The sentence claims a tool check that no recorded command output backs; link the recorded output row or remove the claim." },
70
+ unsupported_verification: { severity: "major", integrity: false, hint: "A sentence marked verified (data-claim-kind=\"verified\") cites a row that is not recorded command output; cite the recorded output row or drop the verified mark." },
71
+ claim_kind_mismatch: { severity: "major", integrity: false, hint: "An element marked with a claim kind (data-claim-kind) cites a row that kind does not accept, or the kind is unknown; cite a row of that kind or fix the mark (references/fidelity-claim-kinds.md)." },
61
72
  conflicting_readings: { severity: "major", integrity: false, hint: "Two ledger rows with different periods or qualifiers are cited as one fact; split the sentence or retire one row." },
62
73
  hedge_dropped: { severity: "major", integrity: false, hint: "Restore the qualifier the source uses (estimated, about, not yet)." },
63
74
  direction_flipped: { severity: "major", integrity: false, hint: "The source states the opposite direction; rewrite from the quote." },
@@ -65,7 +76,8 @@ export const DEFECT_CODES = deepFreeze({
65
76
  qualifier_dropped: { severity: "major", integrity: false, hint: "The source bounds this value (up to, at least, about); restore the qualifier or use :q." },
66
77
  unbound_sentence: { severity: "major", integrity: false, hint: "Bind the sentence to a fact, a quote, a citation, or a claim-graph node with data-claim." },
67
78
  meaning_drift_confirmed: { severity: "major", integrity: false, hint: "Two independent readers found the same drift; rewrite from the quote or dispute with a reason." },
68
- polish_edit_rejected: { severity: "major", integrity: false, hint: "The edit changed a number, a hedge, a qualifier, a link, or a claim id; re-propose the language fix only." },
79
+ meaning_unverified: { severity: "major", integrity: false, hint: "No meaning reader judged this pair, or no strong reader judged a force_strong pair; run the missing reader on pairs.json and rerun fact meaning-check." },
80
+ polish_edit_rejected: { severity: "major", integrity: false, hint: "The edit changed a number, a hedge, a qualifier, a link, or a claim id, or contained <, > or &; re-propose the language fix only, narrowed to the words around any entity or tag." },
69
81
  polish_hedging_decreased: { severity: "major", integrity: false, hint: "The polish pass removed qualifiers; restore them." },
70
82
  polish_text_churn: { severity: "major", integrity: false, hint: "More than half the text changed during polish; the pass composed instead of proofreading." },
71
83
  snapshot_mismatch: { severity: "major", integrity: false, hint: "The operands come from different collection windows; use rows from one window or state the windows separately." },
@@ -73,6 +85,7 @@ export const DEFECT_CODES = deepFreeze({
73
85
  quote_stale_version: { severity: "major", integrity: false, hint: "The quoted source revision differs from the revision the report targets; re-snapshot the target revision or date the quote." },
74
86
  quote_fuzzy_match: { severity: "minor", integrity: false, hint: "The source text is OCR-noisy; confirm the snapshot and prefer the raw document." },
75
87
  name_outside_quote: { severity: "minor", integrity: false, hint: "A name in the sentence is not in the cited quote; add the source or remove the name." },
88
+ verification_claim_unmarked: { severity: "minor", integrity: false, hint: "The sentence reads as a check we ran but carries no verified mark; mark it {{V:F<n>:label}} on a recorded command-output row, or reword it." },
76
89
  scope_widened_warn: { severity: "minor", integrity: false, hint: "The sentence uses a broader scope word than the quote; confirm the scope or narrow it." },
77
90
  particle_unverified: { severity: "minor", integrity: false, hint: "The particle after this value could not be checked; read it aloud and fix it by hand if wrong." },
78
91
  fallback_without_entity: { severity: "minor", integrity: false, hint: "The replacement quote has no entity for attribution; set one on the row." },
@@ -87,10 +100,10 @@ export const FIDELITY_CODES = Object.freeze([
87
100
  "unresolved_placeholder", "value_mismatch", "unit_mismatch", "quote_not_verbatim", "claim_status_invalid",
88
101
  "incompatible_operands", "pointer_label_mismatch", "quote_anchor_unresolved", "unit_duplicated", "polish_diff_fail",
89
102
  "fidelity_interview_incomplete", "derived_not_allowed", "source_outside_floor", "snapshot_hash_mismatch",
90
- "chart_value_untraceable", "quote_unbound", "edition_unverified", "untraceable_number", "unsupported_verification",
103
+ "chart_value_untraceable", "quote_unbound", "edition_unverified", "untraceable_number", "unsupported_verification", "claim_kind_mismatch",
91
104
  "conflicting_readings", "hedge_dropped", "direction_flipped", "causality_added", "qualifier_dropped", "unbound_sentence",
92
- "meaning_drift_confirmed", "polish_edit_rejected", "polish_hedging_decreased", "polish_text_churn", "snapshot_mismatch",
93
- "join_integrity_fail", "quote_stale_version", "quote_fuzzy_match", "name_outside_quote", "scope_widened_warn",
105
+ "meaning_drift_confirmed", "meaning_unverified", "polish_edit_rejected", "polish_hedging_decreased", "polish_text_churn", "snapshot_mismatch",
106
+ "join_integrity_fail", "quote_stale_version", "quote_fuzzy_match", "name_outside_quote", "verification_claim_unmarked", "scope_widened_warn",
94
107
  "particle_unverified", "fallback_without_entity", "meaning_drift_warn", "polish_edit_unlocated", "polish_link_changed",
95
108
  "polish_name_changed",
96
109
  ])
@@ -184,6 +197,14 @@ export function validateOutcomeManifest(manifest) {
184
197
  if (isObject(manifest.gates)) {
185
198
  for (const gate of GATE_NAMES) check(errors, GATE_STATUSES.includes(manifest.gates[gate]), `gates.${gate} must be one of ${GATE_STATUSES.join(", ")}`)
186
199
  } else errors.push("gates must be an object")
200
+ if (manifest.gateHistory !== undefined) {
201
+ if (!Array.isArray(manifest.gateHistory)) errors.push("gateHistory must be an array")
202
+ else for (const entry of manifest.gateHistory) {
203
+ check(errors, isObject(entry) && GATE_NAMES.includes(entry.gate) && GATE_STATUSES.includes(entry.status)
204
+ && typeof entry.at === "string" && ISO_DATE.test(entry.at)
205
+ && (entry.reason === undefined || typeof entry.reason === "string"), "gateHistory entries must have a known gate, status, timestamp, and optional reason")
206
+ }
207
+ }
187
208
  if (Array.isArray(manifest.residualDefects)) manifest.residualDefects.forEach((defect, index) => validateDefect(defect, `residualDefects[${index}]`, errors))
188
209
  else errors.push("residualDefects must be an array")
189
210
  check(errors, manifest.repair === null || isObject(manifest.repair), "repair must be null or an object")
@@ -75,6 +75,7 @@ const FIDELITY_BLOCKERS = [
75
75
  const FIDELITY_MAJORS = [
76
76
  "untraceable_number",
77
77
  "unsupported_verification",
78
+ "claim_kind_mismatch",
78
79
  "conflicting_readings",
79
80
  "hedge_dropped",
80
81
  "direction_flipped",
@@ -82,6 +83,7 @@ const FIDELITY_MAJORS = [
82
83
  "qualifier_dropped",
83
84
  "unbound_sentence",
84
85
  "meaning_drift_confirmed",
86
+ "meaning_unverified",
85
87
  "polish_edit_rejected",
86
88
  "polish_hedging_decreased",
87
89
  "polish_text_churn",
@@ -92,6 +94,7 @@ const FIDELITY_MAJORS = [
92
94
  const FIDELITY_MINORS = [
93
95
  "quote_fuzzy_match",
94
96
  "name_outside_quote",
97
+ "verification_claim_unmarked",
95
98
  "scope_widened_warn",
96
99
  "particle_unverified",
97
100
  "fallback_without_entity",
@@ -0,0 +1,209 @@
1
+ // Architecture contract for the polish command (#11160 sweep): the edit contract, the ledger
2
+ // loader, the edition-format grammar, and the severity buckets each have one owner export, so a
3
+ // reintroduced local copy in fact-commands-polish.mjs fails one of these tests.
4
+ import { afterAll, afterEach, beforeEach, describe, expect, test } from "bun:test"
5
+ import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs"
6
+ import { tmpdir } from "node:os"
7
+ import { join } from "node:path"
8
+
9
+ import { CliError } from "./cli-support.mjs"
10
+ import { countDefects, DEFECT_CODES, SEVERITIES } from "./contracts.mjs"
11
+ import { runFactMeaning } from "./fact-commands-meaning.mjs"
12
+ import * as polishCommand from "./fact-commands-polish.mjs"
13
+ import { editionFormatOf, ledgerFrom, sessionOf } from "./fact-commands-verify.mjs"
14
+ import { applyEdits, POLISH_EDIT_FIELDS, POLISH_MARKUP_REASON, validatePolishEdits } from "./polish-apply.mjs"
15
+
16
+ const { polishDiffFails, runFactPolish } = polishCommand
17
+ const source = (name: string) => readFileSync(join(import.meta.dir, name), "utf8")
18
+ const LEDGER = { schemaVersion: 1, rows: [], events: [] }
19
+ const doc = (body: string) => `<html><body>\n${body}\n</body></html>`
20
+ const BRIEF = join(import.meta.dir, "tests", "fixtures", "fidelity-brief-locked.md")
21
+
22
+ const dir = mkdtempSync(join(tmpdir(), "polish-copy-returns-"))
23
+ const file = (name: string, content: string) => {
24
+ const path = join(dir, name)
25
+ writeFileSync(path, content)
26
+ return path
27
+ }
28
+ const ledgerPath = file("ledger.json", JSON.stringify(LEDGER))
29
+ const badLedgerPath = file("bad-ledger.json", JSON.stringify({ schemaVersion: 1, rows: "nope" }))
30
+ const reportPath = file("report.html", doc("<p>The span is 320 m [S1].</p>"))
31
+ const savedSession = process.env.SESSION_DIR
32
+ beforeEach(() => {
33
+ delete process.env.SESSION_DIR
34
+ })
35
+ afterEach(() => {
36
+ if (savedSession === undefined) delete process.env.SESSION_DIR
37
+ else process.env.SESSION_DIR = savedSession
38
+ })
39
+ afterAll(() => rmSync(dir, { recursive: true, force: true }))
40
+
41
+ function outcome(run: () => unknown) {
42
+ try {
43
+ run()
44
+ return "ok"
45
+ } catch (error) {
46
+ if (!(error instanceof CliError)) throw error
47
+ return error.message
48
+ }
49
+ }
50
+
51
+ describe("the polish edit contract is polish-apply's", () => {
52
+ const SHAPES = [
53
+ ["not an array", { before: "a", after: "b" }],
54
+ ["a null edit", [null]],
55
+ ["an array edit", [["a", "b"]]],
56
+ ["a missing after", [{ before: "span" }]],
57
+ ["a numeric before", [{ before: 3, after: "b" }]],
58
+ ["a numeric reason", [{ before: "span", after: "span", reason: 5 }]],
59
+ ["a valid edit", [{ before: "The span is", after: "The span is", reason: "none" }]],
60
+ ["a valid edit without reason", [{ before: "The span is", after: "The span is" }]],
61
+ ] as const
62
+
63
+ test("#given the command module #when its edit contract is read #then it is the polish-apply export", () => {
64
+ expect(polishCommand.validatePolishEdits).toBe(validatePolishEdits)
65
+ expect(polishCommand.POLISH_EDIT_FIELDS).toBe(POLISH_EDIT_FIELDS)
66
+ expect(source("fact-commands-polish.mjs")).not.toMatch(/typeof edit\?\.(before|after)/u)
67
+ })
68
+
69
+ for (const [name, edits] of SHAPES) {
70
+ test(`#given ${name} #when applied directly and through the CLI #then both accept or both refuse`, () => {
71
+ const editsPath = file(`edits-${name.replaceAll(" ", "-")}.json`, JSON.stringify(edits))
72
+ const direct = (() => {
73
+ try {
74
+ applyEdits(readFileSync(reportPath, "utf8"), edits as never, LEDGER)
75
+ return "ok"
76
+ } catch (error) {
77
+ expect(error).toBeInstanceOf(TypeError)
78
+ return "refused"
79
+ }
80
+ })()
81
+ const cli = outcome(() => runFactPolish("polish-apply", [reportPath, "--edits", editsPath, "--ledger", ledgerPath, "--out", join(dir, "out.html")]))
82
+ expect(cli === "ok" ? "ok" : "refused").toBe(direct)
83
+ if (cli !== "ok") expect(cli).toContain(editsPath)
84
+ })
85
+ }
86
+ })
87
+
88
+ describe("the ledger loader is one shared helper", () => {
89
+ const pairs = (extra: string[]) => () => runFactMeaning("pairs", [reportPath, "--brief", BRIEF, "--out", join(dir, "pairs.json"), ...extra])
90
+ const diff = (extra: string[]) => () => runFactPolish("polish-diff", ["--before", reportPath, "--after", reportPath, ...extra])
91
+
92
+ test("#given the command modules #when read #then neither defines its own ledger loader or session resolver", () => {
93
+ expect(polishCommand.ledgerFrom).toBe(ledgerFrom)
94
+ for (const name of ["fact-commands-meaning.mjs", "fact-commands-polish.mjs"]) {
95
+ const text = source(name)
96
+ expect(text, name).not.toMatch(/function (readLedgerFrom|ledgerFrom)\b|validateLedger\(/u)
97
+ expect(text, name).not.toMatch(/const sessionOf\s*=/u)
98
+ }
99
+ })
100
+
101
+ test("#given the same valid, invalid, and missing ledgers #when fact pairs and fact polish-diff load them #then both give the same outcome", () => {
102
+ for (const extra of [["--ledger", ledgerPath], ["--ledger", badLedgerPath], []]) {
103
+ const expected = outcome(() => ledgerFrom({ ...(extra.length > 0 ? { ledger: extra[1] } : {}) }))
104
+ expect(outcome(pairs(extra)), extra.join(" ")).toBe(expected)
105
+ expect(outcome(diff(extra)), extra.join(" ")).toBe(expected)
106
+ }
107
+ expect(outcome(() => ledgerFrom({ ledger: badLedgerPath }))).toContain("is not a valid fact ledger")
108
+ expect(outcome(() => ledgerFrom({}))).toContain("--ledger is required")
109
+ })
110
+
111
+ test("#given a session dir flag or SESSION_DIR #when resolved #then the flag wins and a bare flag is no session", () => {
112
+ expect(sessionOf({ "session-dir": dir })).toBe(dir)
113
+ expect(sessionOf({ "session-dir": true })).toBeNull()
114
+ process.env.SESSION_DIR = dir
115
+ expect(sessionOf({})).toBe(dir)
116
+ })
117
+ })
118
+
119
+ describe("the edition format comes from verify's grammar", () => {
120
+ test("#given report paths of each case #when polish-apply reads them #then it refuses exactly the Markdown classifications", () => {
121
+ const editsPath = file("edits-ok.json", "[]")
122
+ expect(source("fact-commands-polish.mjs")).not.toMatch(/md\|markdown/u)
123
+ for (const name of ["r.md", "R.MD", "r.markdown", "R.Markdown", "r.html", "r.HTML"]) {
124
+ const path = file(name, readFileSync(reportPath, "utf8"))
125
+ const result = outcome(() => runFactPolish("polish-apply", [path, "--edits", editsPath, "--ledger", ledgerPath, "--out", join(dir, "out.html")]))
126
+ expect(result.includes("takes the filled HTML edition"), name).toBe(editionFormatOf(path, undefined) === "md")
127
+ }
128
+ })
129
+ })
130
+
131
+ describe("polish-diff counts with contracts and exits on blockers only", () => {
132
+ type DiffRun = { exitCode: number; json: { counts: Record<string, number>; defects: { severity: string }[] } }
133
+ const run = (before: string, after: string) => runFactPolish("polish-diff", ["--before", file("b.html", doc(before)), "--after", file("a.html", doc(after)), "--ledger", ledgerPath]) as unknown as DiffRun
134
+
135
+ test("#given every registry code #when polishDiffFails classifies it #then only registry blockers fail", () => {
136
+ for (const [code, entry] of Object.entries(DEFECT_CODES) as [string, { severity: string }][]) expect(polishDiffFails([{ code, severity: "minor" }]), code).toBe(entry.severity === "blocker")
137
+ expect(polishDiffFails([])).toBe(false)
138
+ expect(source("fact-commands-polish.mjs")).not.toMatch(/blocker:\s*0|counts\.blocker > 0/u)
139
+ })
140
+
141
+ test("#given a major-only polish diff #when run #then it is reported with contract buckets and exits 0", () => {
142
+ const result = run("<p>The span may be 320 m [S1].</p>", "<p>The span is 320 m [S1].</p>")
143
+ const { defects } = result.json
144
+ expect(defects.length).toBeGreaterThan(0)
145
+ expect(defects.every((defect) => defect.severity === "major")).toBe(true)
146
+ expect(result.json.counts).toEqual(countDefects(defects))
147
+ expect(Object.keys(result.json.counts)).toEqual([...SEVERITIES, "total"])
148
+ expect(result.exitCode).toBe(0)
149
+ })
150
+
151
+ test("#given a changed numeral #when run #then the blocker exits 1", () => {
152
+ const result = run("<p>Throughput grew 3.5 times [S1].</p>", "<p>Throughput grew 35 times [S1].</p>")
153
+ expect(result.json.counts).toEqual(countDefects(result.json.defects))
154
+ expect(result.exitCode).toBe(1)
155
+ })
156
+ })
157
+
158
+ describe("fact polish-apply prints every rejection reason verbatim", () => {
159
+ type ApplyRun = { summary: string; exitCode: number; json: { defects: { index: number; code: string; reason: string }[]; data: { rejected: number; rejected_reasons: string[] } } }
160
+
161
+ test("#given an edit holding & #when fact polish-apply runs #then the printed line carries polish-apply's own markup reason", () => {
162
+ const before = "Revenue & profit rose across the year"
163
+ const editsPath = file("edits-markup-ampersand.json", JSON.stringify([{ before, after: "Revenue and profit rose across the year", reason: "wording" }]))
164
+ const run = runFactPolish("polish-apply", [reportPath, "--edits", editsPath, "--ledger", ledgerPath, "--out", join(dir, "ampersand.html")]) as unknown as ApplyRun
165
+
166
+ expect(run.exitCode).toBe(0)
167
+ expect(run.json.data.rejected).toBe(1)
168
+ expect(run.json.defects.map((defect) => [defect.index, defect.code, defect.reason])).toEqual([[0, "polish_edit_rejected", POLISH_MARKUP_REASON]])
169
+ const line = ` rejected edit 0 ("${before}"): ${POLISH_MARKUP_REASON}`
170
+ expect(run.json.data.rejected_reasons).toEqual([line])
171
+ expect(run.summary.split("\n")[1]).toBe(line)
172
+ })
173
+ })
174
+
175
+ describe("--changed-since stays fail-closed", () => {
176
+ const { pairsChangedSince } = polishCommand as unknown as { pairsChangedSince: (pairs: unknown[], mapPath: string, ledger: unknown, claims: unknown) => unknown[] }
177
+ const artifact = file("pre-polish.html", doc("<p>The span is 320 m [S1].</p>"))
178
+
179
+ test("#given a fill map without artifact_sha256 #when --changed-since reads it #then it refuses", () => {
180
+ const mapPath = file("fill-map-no-sha.json", JSON.stringify({ artifact, format: "html", entries: [] }))
181
+ expect(outcome(() => pairsChangedSince([], mapPath, LEDGER, null))).toContain("fill map has no artifact_sha256")
182
+ })
183
+
184
+ test("#given a fill map whose artifact changed since fill #when read #then it still refuses", () => {
185
+ const mapPath = file("fill-map-stale.json", JSON.stringify({ artifact, artifact_sha256: "0".repeat(64), format: "html", entries: [] }))
186
+ expect(outcome(() => pairsChangedSince([], mapPath, LEDGER, null))).toContain("changed after fill")
187
+ })
188
+ })
189
+
190
+ describe("polish-diff refuses an unknown defect code", () => {
191
+ test("#given a defect whose code is not in the registry #when classified #then it refuses instead of reading its severity", () => {
192
+ expect(outcome(() => polishDiffFails([{ code: "not_a_real_code", severity: "minor" }]))).toContain("unknown defect code not_a_real_code")
193
+ expect(source("fact-commands-polish.mjs")).not.toMatch(/\?\?\s*defect\.severity/u)
194
+ })
195
+ })
196
+
197
+ describe("polish-apply writes atomically and honours --format", () => {
198
+ test("#given the command module #when read #then it writes the edition through writeTextAtomic", () => {
199
+ const text = source("fact-commands-polish.mjs")
200
+ expect(text).not.toMatch(/writeFileSync\s*\(/u)
201
+ expect(text).toMatch(/writeTextAtomic\(flags\.out/u)
202
+ })
203
+
204
+ test("#given --format md on an .html report #when polish-apply runs #then it refuses the Markdown edition", () => {
205
+ const editsPath = file("edits-format.json", "[]")
206
+ const result = outcome(() => runFactPolish("polish-apply", [reportPath, "--edits", editsPath, "--ledger", ledgerPath, "--out", join(dir, "fmt.html"), "--format", "md"]))
207
+ expect(result).toContain("takes the filled HTML edition")
208
+ })
209
+ })
@@ -0,0 +1,183 @@
1
+ // Architecture contract (retention bar): each shared table, list, regex, or classifier has one owner
2
+ // export, so a reintroduced local copy in a consumer fails one of these tests (#11156 sweep).
3
+ import { describe, expect, test } from "bun:test"
4
+ import { readdirSync, readFileSync } from "node:fs"
5
+ import { join } from "node:path"
6
+
7
+ import { sha256Of } from "./cli-support.mjs"
8
+ import { countDefects, DEFECT_CODES, SEVERITIES } from "./contracts.mjs"
9
+ import { meaningStatus, PROMOTION, promotionEligibility } from "./fact-commands-meaning.mjs"
10
+ import { editionFormatOf, MD_HTML_BLOCK_RULES, markdownAsHtml, projectMarkdownInline } from "./fact-commands-verify.mjs"
11
+ import { EXEMPT_KINDS, isExemptKind } from "./fidelity-binding.mjs"
12
+ import { blocksDelivery } from "./gates-fidelity.mjs"
13
+ import { MD_BLOCK_PATTERNS, parseMarkdown } from "./md-lite.mjs"
14
+ import { DRIFT_KINDS, isDriftKind, MODEL_TIERS, VERDICT_SCHEMA, VERDICTS } from "./meaning-check.mjs"
15
+ import { parseRenderedQuote, renderQuote } from "./placeholder.mjs"
16
+
17
+ const source = (name: string) => readFileSync(join(import.meta.dir, name), "utf8")
18
+
19
+ describe("severity vocabulary comes from contracts", () => {
20
+ test("#given DEFECT_CODES #when its severities are collected #then they are exactly SEVERITIES", () => {
21
+ const used = new Set(Object.values(DEFECT_CODES).map((entry) => entry.severity))
22
+ expect([...used].sort()).toEqual([...SEVERITIES].sort())
23
+ expect(Object.isFrozen(SEVERITIES)).toBe(true)
24
+ })
25
+
26
+ test("#given defects of every severity #when counted #then the buckets are SEVERITIES plus total", () => {
27
+ const defects = SEVERITIES.map((severity) => ({ severity }))
28
+ expect(Object.keys(countDefects(defects))).toEqual([...SEVERITIES, "total"])
29
+ expect(countDefects(defects)).toEqual({ ...Object.fromEntries(SEVERITIES.map((severity) => [severity, 1])), total: SEVERITIES.length })
30
+ })
31
+
32
+ test("#given every registry code and an unknown code #when the meaning status is computed #then it follows blocksDelivery", () => {
33
+ for (const code of [...Object.keys(DEFECT_CODES), "not_a_registered_code"]) {
34
+ expect(meaningStatus([{ code, severity: "minor" }]), code).toBe(blocksDelivery({ code }) ? "fail" : "pass")
35
+ }
36
+ expect(meaningStatus([])).toBe("pass")
37
+ })
38
+
39
+ test("#given the command modules #when read #then none restates the severity buckets, the pair kinds, or a window literal", () => {
40
+ for (const name of ["fact-commands-meaning.mjs", "fact-commands-verify.mjs"]) {
41
+ const text = source(name)
42
+ expect(text, name).not.toMatch(/blocker:\s*0/u)
43
+ expect(text, name).not.toMatch(/\["quote",\s*"claim",\s*"translation"\]/u)
44
+ expect(text, name).not.toMatch(/slice\(-\d+\)/u)
45
+ expect(text, name).not.toMatch(/counts\.blocker \+ counts\.major/u)
46
+ }
47
+ })
48
+ })
49
+
50
+ describe("one sha256 helper", () => {
51
+ test("#given every runtime module #when read #then only cli-support builds a hash, so records and receipts share sha256Of", () => {
52
+ const modules = readdirSync(import.meta.dir).filter((name) => name.endsWith(".mjs") && name !== "cli-support.mjs")
53
+ expect(modules.length).toBeGreaterThan(0)
54
+ for (const name of modules) expect(source(name), name).not.toMatch(/createHash\(/u)
55
+ expect(sha256Of("abc")).toBe("ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad")
56
+ expect(sha256Of(Buffer.from("abc"))).toBe(sha256Of("abc"))
57
+ })
58
+ })
59
+
60
+ describe("calibration policy is the exported PROMOTION", () => {
61
+ const confirmedRun = (run: string, disputed: boolean) => ({ run_id: run, confirmed: true, disputed })
62
+
63
+ test("#given false blocks only before the window #when eligibility is computed #then they are not counted", () => {
64
+ const window = PROMOTION.false_block_window
65
+ expect(Number.isInteger(window) && window > 0).toBe(true)
66
+ const old = [0, 1, 2].map((n) => confirmedRun(`old${n}`, true))
67
+ const recent = Array.from({ length: window }, (_, n) => confirmedRun(`new${n}`, false))
68
+ expect(promotionEligibility([...old, ...recent]).report_false_blocks_in_window).toBe(0)
69
+ expect(promotionEligibility([...recent, ...old]).report_false_blocks_in_window).toBe(3)
70
+ })
71
+
72
+ test("#given false blocks at the policy maximum #when eligibility is computed #then the threshold read from PROMOTION decides", () => {
73
+ const limit = PROMOTION.max_report_false_blocks_in_window
74
+ const clean = Array.from({ length: PROMOTION.min_runs * 20 }, (_, n) => confirmedRun(`c${n}`, false))
75
+ const blocks = (count: number) => Array.from({ length: count }, (_, n) => confirmedRun(`b${n}`, true))
76
+ expect(promotionEligibility([...clean, ...blocks(limit)]).eligible).toBe(true)
77
+ expect(promotionEligibility([...clean, ...blocks(limit + 1)]).eligible).toBe(false)
78
+ })
79
+ })
80
+
81
+ describe("verdict schema comes from the meaning-check registries", () => {
82
+ test("#given VERDICT_SCHEMA #when read #then its vocabularies are the owner exports", () => {
83
+ expect(VERDICT_SCHEMA.verdict).toBe(VERDICTS)
84
+ expect(VERDICT_SCHEMA.model_tier).toBe(MODEL_TIERS)
85
+ expect(VERDICT_SCHEMA.drift_kind).toBe(DRIFT_KINDS)
86
+ expect(Object.isFrozen(VERDICTS)).toBe(true)
87
+ })
88
+
89
+ test("#given every drift kind and a stranger #when checked #then isDriftKind is membership in DRIFT_KINDS", () => {
90
+ for (const kind of DRIFT_KINDS) expect(isDriftKind(kind)).toBe(true)
91
+ expect(isDriftKind("not_a_kind")).toBe(false)
92
+ expect(source("meaning-check.mjs")).not.toMatch(/new Set\(DRIFT_KINDS\)|new Set\(\["drift"/u)
93
+ })
94
+ })
95
+
96
+ describe("inherited lexical tables come from their owners", () => {
97
+ test("#given fact-pairs #when read #then it holds no Hangul range, exempt-kind set, or hand-built hedge regex", () => {
98
+ const text = source("fact-pairs.mjs")
99
+ expect(text).not.toMatch(/\\uAC00-\\uD7A3/u)
100
+ expect(text).not.toMatch(/\["opinion",\s*"recommendation"\]/u)
101
+ expect(text).not.toMatch(/new RegExp\(/u)
102
+ })
103
+
104
+ test("#given EXEMPT_KINDS #when checked #then isExemptKind is membership in it", () => {
105
+ for (const kind of EXEMPT_KINDS) expect(isExemptKind(kind)).toBe(true)
106
+ expect(isExemptKind("finding")).toBe(false)
107
+ expect(isExemptKind(undefined)).toBe(false)
108
+ })
109
+
110
+ test("#given md-lite #when its block grammar is read #then MD_BLOCK_PATTERNS names every block regex", () => {
111
+ expect(Object.keys(MD_BLOCK_PATTERNS).sort()).toEqual(["BLOCKQUOTE", "CLOSING_HASHES", "FENCE", "HEADING", "LIST_ITEM", "TABLE_SEPARATOR", "THEMATIC_BREAK"])
112
+ for (const pattern of Object.values(MD_BLOCK_PATTERNS)) expect(pattern).toBeInstanceOf(RegExp)
113
+ expect(Object.isFrozen(MD_BLOCK_PATTERNS)).toBe(true)
114
+ })
115
+ })
116
+
117
+ describe("markdown projection rules", () => {
118
+ const EVERY_KIND = ["# Title", "", "Para text.", "", "- item", "", "> quoted", "", "| a | b |", "| - | - |", "| 1 | 2 |", "", "```", "code", "```", ""].join("\n")
119
+
120
+ test("#given every block kind md-lite emits #when projected #then each kind has a projection rule", () => {
121
+ const kinds = [...new Set(parseMarkdown(EVERY_KIND).blocks.map((block: { kind: string }) => block.kind))].sort()
122
+ expect(kinds).toEqual(["blockquote", "code", "heading", "list_item", "paragraph", "table_row"])
123
+ for (const kind of kinds) expect(Object.hasOwn(MD_HTML_BLOCK_RULES, kind), kind).toBe(true)
124
+ expect(Object.keys(MD_HTML_BLOCK_RULES).sort()).toEqual(kinds)
125
+ const { html } = markdownAsHtml(EVERY_KIND, [], { rows: [] })
126
+ expect(html).toContain("<h1>Title</h1>")
127
+ expect(html).toContain("<ul><li>item</li></ul>")
128
+ expect(html).toMatch(/<table><tr><td>a<\/td><td>b<\/td><\/tr>\s*<tr><td>1<\/td><td>2<\/td><\/tr><\/table>/u)
129
+ })
130
+
131
+ test("#given an unterminated fence #when projected #then it is refused naming the line", () => {
132
+ expect(() => markdownAsHtml("Intro.\n\n```\nno close\n", [], { rows: [] })).toThrow(/unterminated_fence at line 3/u)
133
+ })
134
+
135
+ test("#given inline links and code #when projected #then they stay elements and the rest is escaped", () => {
136
+ expect(projectMarkdownInline("a <b> [x](https://e.x) `c`")).toBe('a &lt;b&gt; <a href="https://e.x">x</a> <code>c</code>')
137
+ })
138
+
139
+ test("#given a fill map with quotes in translates and fact_id #when projected #then the attributes are escaped", () => {
140
+ const md = "Before X after.\n\n> Quoted block.\n"
141
+ const span = { fact_id: 'F"1', start: 7, end: 8, rendered: "X", translates: 'T"1' }
142
+ const block = { fact_id: 'Q"2', start: md.indexOf(">"), end: md.length - 1, rendered: "", replaced: "a" }
143
+ const { html } = markdownAsHtml(md, [span, block], { rows: [] })
144
+ expect(html).toContain('data-translates="T&quot;1"')
145
+ expect(html).toContain('data-fact="Q&quot;2"')
146
+ expect(markdownAsHtml("Before X after.\n", [{ ...span, translates: undefined }], { rows: [] }).html).toContain('data-fact="F&quot;1"')
147
+ expect(html).not.toMatch(/="[^"]*"1"/u)
148
+ })
149
+
150
+ test("#given paths of each case #when the format is inferred #then md and markdown are Markdown and a flag wins", () => {
151
+ expect(["r.md", "R.MD", "r.markdown", "r.html", "r.txt"].map((path) => editionFormatOf(path, undefined))).toEqual(["md", "md", "md", "html", "html"])
152
+ expect(editionFormatOf("r.md", "html")).toBe("html")
153
+ })
154
+ })
155
+
156
+ describe("rendered quotes round-trip through the placeholder owner", () => {
157
+ const row = { fact_id: "Q1", kind: "quote", source_id: "S4", quote: "We estimate completion no earlier than 2027." }
158
+ const selections = [
159
+ ["full", row.quote],
160
+ ["lead=2", "We estimate"],
161
+ ["from=completion,to=2027", "completion no earlier than 2027"],
162
+ ]
163
+
164
+ for (const [selector, text] of selections) {
165
+ test(`#given a ${selector} quote rendered for md #when projected #then the quote element and marker survive`, () => {
166
+ const rendered = renderQuote(row, selector, text, "md")
167
+ expect(parseRenderedQuote(rendered)).toEqual({ text, marker: "[S4]" })
168
+ const md = `The operator wrote ${rendered}.\n`
169
+ const entry = { fact_id: "Q1", start: md.indexOf(rendered), end: md.indexOf(rendered) + rendered.length, rendered, selector }
170
+ const projected = markdownAsHtml(md, [entry], { rows: [row] })
171
+ expect(projected.html).toContain(renderQuote(row, selector, text, "html"))
172
+ expect(projected.stale).toEqual([])
173
+ const edited = md.replace(text, `${text} indeed`)
174
+ const stale = markdownAsHtml(edited, [{ ...entry, end: entry.end + " indeed".length }], { rows: [row] })
175
+ expect(stale.html).not.toContain("<q ")
176
+ expect(stale.stale).toHaveLength(1)
177
+ })
178
+ }
179
+
180
+ test("#given bytes that are not the md quote layout #when parsed #then null", () => {
181
+ expect(parseRenderedQuote("plain text [S4]")).toBeNull()
182
+ })
183
+ })