lazycodex-ai 5.1.45 → 5.1.46

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 (236) hide show
  1. package/README.de.md +0 -5
  2. package/README.es.md +0 -5
  3. package/README.fr.md +0 -5
  4. package/README.it.md +0 -5
  5. package/README.ja.md +0 -5
  6. package/README.ko.md +0 -5
  7. package/README.md +4 -5
  8. package/README.pt-br.md +0 -5
  9. package/README.ru.md +0 -6
  10. package/README.zh-cn.md +0 -5
  11. package/dist/cli/index.js +76 -156
  12. package/dist/cli-node/index.js +76 -156
  13. package/package.json +1 -1
  14. package/packages/lsp-daemon/dist/cli.js +3 -3
  15. package/packages/lsp-daemon/dist/client.js +3 -3
  16. package/packages/lsp-daemon/dist/index.js +3 -3
  17. package/packages/lsp-tools-mcp/dist/cli.js +3 -3
  18. package/packages/lsp-tools-mcp/dist/mcp.js +3 -3
  19. package/packages/omo-codex/plugin/.codex-plugin/plugin.json +1 -1
  20. package/packages/omo-codex/plugin/components/bootstrap/dist/cli.js +22 -12
  21. package/packages/omo-codex/plugin/components/bootstrap/hooks/hooks.json +1 -1
  22. package/packages/omo-codex/plugin/components/bootstrap/package.json +1 -1
  23. package/packages/omo-codex/plugin/components/comment-checker/hooks/hooks.json +1 -1
  24. package/packages/omo-codex/plugin/components/comment-checker/package.json +1 -1
  25. package/packages/omo-codex/plugin/components/git-bash/hooks/hooks.json +2 -2
  26. package/packages/omo-codex/plugin/components/git-bash/package.json +1 -1
  27. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/hooks/hooks.json +1 -1
  28. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/package.json +1 -1
  29. package/packages/omo-codex/plugin/components/lsp/dist/.omo-runtime-manifest.json +2 -2
  30. package/packages/omo-codex/plugin/components/lsp/hooks/hooks.json +2 -2
  31. package/packages/omo-codex/plugin/components/lsp/package.json +1 -1
  32. package/packages/omo-codex/plugin/components/rules/hooks/hooks.json +4 -4
  33. package/packages/omo-codex/plugin/components/rules/package.json +1 -1
  34. package/packages/omo-codex/plugin/components/teammode/hooks/hooks.json +1 -1
  35. package/packages/omo-codex/plugin/components/teammode/package.json +1 -1
  36. package/packages/omo-codex/plugin/components/telemetry/hooks/hooks.json +1 -1
  37. package/packages/omo-codex/plugin/components/telemetry/package.json +1 -1
  38. package/packages/omo-codex/plugin/components/ultrawork/hooks/hooks.json +1 -1
  39. package/packages/omo-codex/plugin/components/ultrawork/package.json +1 -1
  40. package/packages/omo-codex/plugin/components/ulw-execute-continuation/hooks/hooks.json +1 -1
  41. package/packages/omo-codex/plugin/components/ulw-execute-continuation/package.json +1 -1
  42. package/packages/omo-codex/plugin/components/ulw-loop/hooks/hooks.json +5 -5
  43. package/packages/omo-codex/plugin/components/ulw-loop/package.json +1 -1
  44. package/packages/omo-codex/plugin/hooks/post-compact-resetting-git-bash-mcp-reminder.json +1 -1
  45. package/packages/omo-codex/plugin/hooks/post-compact-resetting-lsp-diagnostics-cache.json +1 -1
  46. package/packages/omo-codex/plugin/hooks/post-compact-resetting-project-rule-cache.json +1 -1
  47. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-comments.json +1 -1
  48. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-lsp-diagnostics.json +1 -1
  49. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-thread-title-hygiene.json +1 -1
  50. package/packages/omo-codex/plugin/hooks/post-tool-use-matching-project-rules.json +1 -1
  51. package/packages/omo-codex/plugin/hooks/post-tool-use-recording-spawn-admission.json +1 -1
  52. package/packages/omo-codex/plugin/hooks/pre-tool-use-enforcing-unlimited-goal-budget.json +1 -1
  53. package/packages/omo-codex/plugin/hooks/pre-tool-use-guarding-ulw-loop-spawns.json +1 -1
  54. package/packages/omo-codex/plugin/hooks/pre-tool-use-recommending-git-bash-mcp.json +1 -1
  55. package/packages/omo-codex/plugin/hooks/session-start-checking-auto-update.json +1 -1
  56. package/packages/omo-codex/plugin/hooks/session-start-checking-bootstrap-provisioning.json +1 -1
  57. package/packages/omo-codex/plugin/hooks/session-start-loading-project-rules.json +1 -1
  58. package/packages/omo-codex/plugin/hooks/session-start-recording-session-telemetry.json +1 -1
  59. package/packages/omo-codex/plugin/hooks/stop-checking-ulw-execute-continuation.json +1 -1
  60. package/packages/omo-codex/plugin/hooks/stop-checking-ulw-loop-resume.json +1 -1
  61. package/packages/omo-codex/plugin/hooks/subagent-stop-verifying-lazycodex-executor-evidence.json +1 -1
  62. package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ultrawork-trigger.json +1 -1
  63. package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ulw-loop-steering.json +1 -1
  64. package/packages/omo-codex/plugin/hooks/user-prompt-submit-loading-project-rules.json +1 -1
  65. package/packages/omo-codex/plugin/package-lock.json +12 -12
  66. package/packages/omo-codex/plugin/package.json +1 -1
  67. package/packages/omo-codex/plugin/scripts/sync-skills.mjs +8 -3
  68. package/packages/omo-codex/plugin/shared/test/config-loader.test.ts +0 -2
  69. package/packages/omo-codex/plugin/skills/ast-grep/AGENTS.md +1 -1
  70. package/packages/omo-codex/plugin/skills/browser/runtime/omowright/manifest.json +1 -1
  71. package/packages/omo-codex/plugin/skills/frontend/ATTRIBUTION.md +2 -2
  72. package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/AGENTS.md +1 -1
  73. package/packages/omo-codex/plugin/skills/ulw-research/AGENTS.md +14 -2
  74. package/packages/omo-codex/plugin/skills/ulw-research/SKILL.md +36 -11
  75. package/packages/omo-codex/plugin/skills/ulw-research/references/deliverable-phase.md +73 -13
  76. package/packages/omo-codex/plugin/skills/ulw-research/references/fact-ledger-grammar.md +19 -1
  77. package/packages/omo-codex/plugin/skills/ulw-research/references/fact-ledger.md +95 -0
  78. package/packages/omo-codex/plugin/skills/ulw-research/references/fidelity-claim-kinds.md +19 -0
  79. package/packages/omo-codex/plugin/skills/ulw-research/references/report-gates.md +21 -4
  80. package/packages/omo-codex/plugin/skills/ulw-research/references/report-visualization.md +2 -0
  81. package/packages/omo-codex/plugin/skills/ulw-research/references/unit-rules.md +157 -0
  82. package/packages/omo-codex/plugin/skills/ulw-research/scripts/claim-kinds.mjs +116 -0
  83. package/packages/omo-codex/plugin/skills/ulw-research/scripts/cli-support.mjs +19 -5
  84. package/packages/omo-codex/plugin/skills/ulw-research/scripts/contracts.mjs +27 -6
  85. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-commands-meaning.mjs +191 -0
  86. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-commands-verify.mjs +164 -0
  87. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-commands.mjs +239 -0
  88. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-derive.mjs +66 -0
  89. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-expr.mjs +108 -0
  90. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-fill.mjs +194 -0
  91. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-ledger-kinds.mjs +127 -13
  92. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-ledger-schema.mjs +33 -16
  93. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-ledger.mjs +29 -9
  94. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-locate.mjs +231 -0
  95. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-pairs.mjs +187 -0
  96. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-recompute.mjs +89 -0
  97. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-binding.mjs +130 -0
  98. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-brief.mjs +5 -0
  99. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-claims.mjs +89 -0
  100. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-lexicon.mjs +145 -0
  101. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-records.mjs +163 -0
  102. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-state.mjs +2 -1
  103. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-trace.mjs +85 -0
  104. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-values.mjs +229 -0
  105. package/packages/omo-codex/plugin/skills/ulw-research/scripts/gates-fidelity.mjs +113 -0
  106. package/packages/omo-codex/plugin/skills/ulw-research/scripts/gates-static.mjs +28 -8
  107. package/packages/omo-codex/plugin/skills/ulw-research/scripts/html-lite.mjs +2 -2
  108. package/packages/omo-codex/plugin/skills/ulw-research/scripts/html-markup.mjs +182 -0
  109. package/packages/omo-codex/plugin/skills/ulw-research/scripts/md-lite.mjs +193 -0
  110. package/packages/omo-codex/plugin/skills/ulw-research/scripts/meaning-check.mjs +196 -0
  111. package/packages/omo-codex/plugin/skills/ulw-research/scripts/outcome.mjs +6 -5
  112. package/packages/omo-codex/plugin/skills/ulw-research/scripts/placeholder.mjs +184 -0
  113. package/packages/omo-codex/plugin/skills/ulw-research/scripts/polish-apply.mjs +115 -0
  114. package/packages/omo-codex/plugin/skills/ulw-research/scripts/polish-diff.mjs +31 -0
  115. package/packages/omo-codex/plugin/skills/ulw-research/scripts/polish-project.mjs +125 -0
  116. package/packages/omo-codex/plugin/skills/ulw-research/scripts/polish-rules.mjs +170 -0
  117. package/packages/omo-codex/plugin/skills/ulw-research/scripts/report-tools-commands.mjs +19 -8
  118. package/packages/omo-codex/plugin/skills/ulw-research/scripts/report-tools.mjs +15 -0
  119. package/packages/omo-codex/plugin/skills/ulw-research/scripts/source-commands.mjs +140 -0
  120. package/packages/omo-codex/plugin/skills/ulw-research/scripts/source-snapshot.mjs +176 -0
  121. package/packages/omo-codex/plugin/skills/ulw-research/scripts/unit-algebra.mjs +101 -0
  122. package/packages/omo-codex/plugin/skills/ulw-research/scripts/unit-errors.mjs +23 -0
  123. package/packages/omo-codex/plugin/skills/ulw-research/scripts/unit-metadata.mjs +31 -0
  124. package/packages/omo-codex/plugin/skills/ulw-research/scripts/unit-pairs.mjs +72 -0
  125. package/packages/omo-codex/plugin/skills/ulw-research/scripts/unit-precision.mjs +44 -0
  126. package/packages/omo-codex/plugin/skills/ulw-research/scripts/unit-rules-shapes.mjs +64 -0
  127. package/packages/omo-codex/plugin/skills/ulw-research/scripts/unit-rules.mjs +173 -0
  128. package/packages/omo-codex/plugin/skills/ulw-research/scripts/units.mjs +4 -4
  129. package/packages/omo-codex/plugin/test/sync-skills-codex-compatibility.test.mjs +19 -0
  130. package/packages/omo-codex/plugin/test/sync-skills-test-support.mjs +7 -3
  131. package/packages/omo-codex/scripts/install-dist/install-local.mjs +26 -16
  132. package/packages/shared-skills/skills/ast-grep/AGENTS.md +1 -1
  133. package/packages/shared-skills/skills/browser/runtime/omowright/manifest.json +1 -1
  134. package/packages/shared-skills/skills/frontend/ATTRIBUTION.md +2 -2
  135. package/packages/shared-skills/skills/ultimate-browsing/engine/AGENTS.md +1 -1
  136. package/packages/shared-skills/skills/ulw-research/AGENTS.md +14 -2
  137. package/packages/shared-skills/skills/ulw-research/SKILL.md +37 -12
  138. package/packages/shared-skills/skills/ulw-research/references/deliverable-phase.md +73 -13
  139. package/packages/shared-skills/skills/ulw-research/references/fact-ledger-grammar.md +19 -1
  140. package/packages/shared-skills/skills/ulw-research/references/fact-ledger.md +95 -0
  141. package/packages/shared-skills/skills/ulw-research/references/fidelity-claim-kinds.md +19 -0
  142. package/packages/shared-skills/skills/ulw-research/references/report-gates.md +21 -4
  143. package/packages/shared-skills/skills/ulw-research/references/report-visualization.md +2 -0
  144. package/packages/shared-skills/skills/ulw-research/references/unit-rules.md +157 -0
  145. package/packages/shared-skills/skills/ulw-research/scripts/claim-kinds.mjs +116 -0
  146. package/packages/shared-skills/skills/ulw-research/scripts/claim-kinds.test.ts +193 -0
  147. package/packages/shared-skills/skills/ulw-research/scripts/cli-support.mjs +19 -5
  148. package/packages/shared-skills/skills/ulw-research/scripts/cli-support.test.ts +13 -2
  149. package/packages/shared-skills/skills/ulw-research/scripts/contracts.mjs +27 -6
  150. package/packages/shared-skills/skills/ulw-research/scripts/contracts.test.ts +3 -0
  151. package/packages/shared-skills/skills/ulw-research/scripts/copy-returns.test.ts +183 -0
  152. package/packages/shared-skills/skills/ulw-research/scripts/delivery-barrier.test.ts +245 -0
  153. package/packages/shared-skills/skills/ulw-research/scripts/fact-commands-meaning.mjs +191 -0
  154. package/packages/shared-skills/skills/ulw-research/scripts/fact-commands-verify.mjs +164 -0
  155. package/packages/shared-skills/skills/ulw-research/scripts/fact-commands.mjs +239 -0
  156. package/packages/shared-skills/skills/ulw-research/scripts/fact-derive.mjs +66 -0
  157. package/packages/shared-skills/skills/ulw-research/scripts/fact-derive.test.ts +375 -0
  158. package/packages/shared-skills/skills/ulw-research/scripts/fact-expr.mjs +108 -0
  159. package/packages/shared-skills/skills/ulw-research/scripts/fact-fill.mjs +194 -0
  160. package/packages/shared-skills/skills/ulw-research/scripts/fact-fill.test.ts +221 -0
  161. package/packages/shared-skills/skills/ulw-research/scripts/fact-ledger-grammar.test.ts +9 -2
  162. package/packages/shared-skills/skills/ulw-research/scripts/fact-ledger-kinds.mjs +127 -13
  163. package/packages/shared-skills/skills/ulw-research/scripts/fact-ledger-kinds.test.ts +18 -3
  164. package/packages/shared-skills/skills/ulw-research/scripts/fact-ledger-legacy.test.ts +160 -0
  165. package/packages/shared-skills/skills/ulw-research/scripts/fact-ledger-schema.mjs +33 -16
  166. package/packages/shared-skills/skills/ulw-research/scripts/fact-ledger.mjs +29 -9
  167. package/packages/shared-skills/skills/ulw-research/scripts/fact-ledger.test.ts +2 -0
  168. package/packages/shared-skills/skills/ulw-research/scripts/fact-locate.mjs +231 -0
  169. package/packages/shared-skills/skills/ulw-research/scripts/fact-locate.test.ts +118 -0
  170. package/packages/shared-skills/skills/ulw-research/scripts/fact-pairs.mjs +187 -0
  171. package/packages/shared-skills/skills/ulw-research/scripts/fact-pairs.test.ts +167 -0
  172. package/packages/shared-skills/skills/ulw-research/scripts/fact-recompute.mjs +89 -0
  173. package/packages/shared-skills/skills/ulw-research/scripts/fact-unit-dimensions.test.ts +409 -0
  174. package/packages/shared-skills/skills/ulw-research/scripts/fact-unit-rules.test.ts +388 -0
  175. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-binding.mjs +130 -0
  176. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-binding.test.ts +81 -0
  177. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-brief.mjs +5 -0
  178. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-claims.mjs +89 -0
  179. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-lexicon.mjs +145 -0
  180. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-lexicon.test.ts +117 -0
  181. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-records.mjs +163 -0
  182. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-state.mjs +2 -1
  183. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-trace.mjs +85 -0
  184. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-trace.test.ts +58 -0
  185. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-values.mjs +229 -0
  186. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-values.test.ts +270 -0
  187. package/packages/shared-skills/skills/ulw-research/scripts/gates-fidelity.mjs +113 -0
  188. package/packages/shared-skills/skills/ulw-research/scripts/gates-fidelity.test.ts +73 -0
  189. package/packages/shared-skills/skills/ulw-research/scripts/gates-static.mjs +28 -8
  190. package/packages/shared-skills/skills/ulw-research/scripts/html-lite.mjs +2 -2
  191. package/packages/shared-skills/skills/ulw-research/scripts/html-markup.mjs +182 -0
  192. package/packages/shared-skills/skills/ulw-research/scripts/md-lite.mjs +193 -0
  193. package/packages/shared-skills/skills/ulw-research/scripts/md-lite.test.ts +271 -0
  194. package/packages/shared-skills/skills/ulw-research/scripts/meaning-check.mjs +196 -0
  195. package/packages/shared-skills/skills/ulw-research/scripts/meaning-check.test.ts +228 -0
  196. package/packages/shared-skills/skills/ulw-research/scripts/outcome.mjs +6 -5
  197. package/packages/shared-skills/skills/ulw-research/scripts/placeholder.mjs +184 -0
  198. package/packages/shared-skills/skills/ulw-research/scripts/placeholder.test.ts +77 -0
  199. package/packages/shared-skills/skills/ulw-research/scripts/polish-apply.mjs +115 -0
  200. package/packages/shared-skills/skills/ulw-research/scripts/polish-apply.test.ts +363 -0
  201. package/packages/shared-skills/skills/ulw-research/scripts/polish-diff.mjs +31 -0
  202. package/packages/shared-skills/skills/ulw-research/scripts/polish-diff.test.ts +168 -0
  203. package/packages/shared-skills/skills/ulw-research/scripts/polish-markup.test.ts +187 -0
  204. package/packages/shared-skills/skills/ulw-research/scripts/polish-project.mjs +125 -0
  205. package/packages/shared-skills/skills/ulw-research/scripts/polish-rules.mjs +170 -0
  206. package/packages/shared-skills/skills/ulw-research/scripts/report-tools-commands.mjs +19 -8
  207. package/packages/shared-skills/skills/ulw-research/scripts/report-tools.mjs +15 -0
  208. package/packages/shared-skills/skills/ulw-research/scripts/report-tools.test.ts +458 -2
  209. package/packages/shared-skills/skills/ulw-research/scripts/source-commands.mjs +140 -0
  210. package/packages/shared-skills/skills/ulw-research/scripts/source-snapshot.mjs +176 -0
  211. package/packages/shared-skills/skills/ulw-research/scripts/source-snapshot.test.ts +113 -0
  212. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/assets/share.csv +3 -0
  213. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/gen-legacy-dev.mjs +89 -0
  214. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/ledger-basic.json +223 -0
  215. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/ledger-legacy-dev.json +859 -0
  216. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/ledger-verify.json +420 -0
  217. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/report-bad.html +19 -0
  218. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/report-good.html +39 -0
  219. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/sources/S4/acronyms.json +3 -0
  220. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/sources/S4/meta.json +9 -0
  221. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/sources/S4/sentences.jsonl +7 -0
  222. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/sources/S4/text.txt +7 -0
  223. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/sources/S4.html +19 -0
  224. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/sources/S5/acronyms.json +1 -0
  225. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/sources/S5/meta.json +9 -0
  226. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/sources/S5/sentences.jsonl +1 -0
  227. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/sources/S5/text.txt +1 -0
  228. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/sources/S9.txt +1 -0
  229. package/packages/shared-skills/skills/ulw-research/scripts/unit-algebra.mjs +101 -0
  230. package/packages/shared-skills/skills/ulw-research/scripts/unit-errors.mjs +23 -0
  231. package/packages/shared-skills/skills/ulw-research/scripts/unit-metadata.mjs +31 -0
  232. package/packages/shared-skills/skills/ulw-research/scripts/unit-pairs.mjs +72 -0
  233. package/packages/shared-skills/skills/ulw-research/scripts/unit-precision.mjs +44 -0
  234. package/packages/shared-skills/skills/ulw-research/scripts/unit-rules-shapes.mjs +64 -0
  235. package/packages/shared-skills/skills/ulw-research/scripts/unit-rules.mjs +173 -0
  236. package/packages/shared-skills/skills/ulw-research/scripts/units.mjs +4 -4
@@ -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,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
+ })
@@ -0,0 +1,245 @@
1
+ import { describe, expect, onTestFinished, test } from "bun:test"
2
+ import { mkdirSync, mkdtempSync, readFileSync, rmSync, unlinkSync, writeFileSync } from "node:fs"
3
+ import { tmpdir } from "node:os"
4
+ import { join } from "node:path"
5
+
6
+ import { sha256Of } from "./cli-support.mjs"
7
+ import { DEFECT_CODES } from "./contracts.mjs"
8
+ import { FIDELITY_RECORDS, MEANING_RECORD, RECORD_ENVELOPE, writeFidelityRecord } from "./fidelity-records.mjs"
9
+ import { blocksDelivery } from "./gates-fidelity.mjs"
10
+ import { run } from "./report-tools.mjs"
11
+
12
+ async function delivered(format = "html", codes: string[] | null = null) {
13
+ const root = mkdtempSync(join(tmpdir(), "delivery-barrier-"))
14
+ onTestFinished(() => rmSync(root, { recursive: true, force: true }))
15
+ const dir = join(root, "20260101-120000")
16
+ mkdirSync(dir)
17
+ const path = join(dir, `report.${format}`)
18
+ const record = join(dir, "fidelity-defects.json")
19
+ writeFileSync(path, "<p>Report</p>")
20
+ if (codes !== null) writeFidelityRecord(record, "fidelity", { artifact_sha256: sha256Of(readFileSync(path)), report: path, defects: codes.map((code) => ({ code, severity: "minor", message: code })) })
21
+ const invoke = (args: string[]) => run(["outcome", ...args, "--session-dir", dir])
22
+ await invoke(["init", "--promised", format])
23
+ expect((await invoke(["set", format, "delivered", "--path", path, ...(codes === null ? [] : ["--verified-by", record])])).exitCode).toBe(0)
24
+ return { dir, path, record, invoke }
25
+ }
26
+
27
+ // Enumerate the source registry, not a second list of today's fidelity codes.
28
+ for (const [code, entry] of Object.entries(DEFECT_CODES)) {
29
+ if (!entry.integrity && entry.severity === "minor") continue
30
+ test(`#given registry blocker ${code} #when verified and finished #then delivery refuses despite a demoted row`, async () => {
31
+ const { dir, invoke } = await delivered("html", [code])
32
+ for (const command of ["verify", "finish"]) {
33
+ const result = await invoke([command])
34
+ expect(result.exitCode).toBe(1)
35
+ expect(result.json.problems).toContainEqual(expect.objectContaining({ code: "fidelity_unresolved", codes: [code] }))
36
+ }
37
+ expect(JSON.parse(readFileSync(join(dir, "outcome.json"), "utf8")).finishedAt).toBeNull()
38
+ })
39
+ }
40
+
41
+ test("#given an unknown defect code #when verified or finished #then delivery refuses with its reason", async () => {
42
+ const { invoke } = await delivered("html", ["future_fidelity_defect"])
43
+ for (const command of ["verify", "finish"]) {
44
+ const result = await invoke([command])
45
+ expect(result.exitCode).toBe(1)
46
+ expect(JSON.stringify(result.json.problems)).toContain("unknown defect code future_fidelity_defect")
47
+ }
48
+ })
49
+
50
+ test("#given a registry non-blocker #when verified and finished #then delivery passes", async () => {
51
+ const entry = Object.entries(DEFECT_CODES).find(([, value]) => !value.integrity && value.severity === "minor")
52
+ if (!entry) throw new Error("registry must have a non-blocking entry")
53
+ const { invoke } = await delivered("html", [entry[0]])
54
+ expect((await invoke(["verify"])).exitCode).toBe(0)
55
+ expect((await invoke(["finish"])).exitCode).toBe(0)
56
+ })
57
+
58
+ for (const format of ["html", "pdf", "docx"]) {
59
+ test(`#given delivered ${format} without any ledger or gate record #when verified and finished #then both refuse`, async () => {
60
+ const { invoke } = await delivered(format)
61
+ for (const command of ["verify", "finish"]) {
62
+ const result = await invoke([command])
63
+ expect(result.exitCode).toBe(1)
64
+ expect(JSON.stringify(result.json.problems)).toContain("fidelity gate not run")
65
+ }
66
+ })
67
+ }
68
+
69
+ test("#given a missing gate #when explicitly skipped with a reason #then the logged choice permits delivery", async () => {
70
+ const { dir, invoke } = await delivered()
71
+ const now = "2026-01-01T12:01:00.000Z"
72
+ await invoke(["gate", "fidelity", "not_run", "--reason", "No factual content in this edition", "--now", now])
73
+ const manifest = JSON.parse(readFileSync(join(dir, "outcome.json"), "utf8"))
74
+ expect(manifest.gateHistory).toContainEqual({ gate: "fidelity", status: "not_run", reason: "No factual content in this edition", at: now })
75
+ expect((await invoke(["verify"])).exitCode).toBe(0)
76
+ expect((await invoke(["finish"])).exitCode).toBe(0)
77
+ await invoke(["gate", "fidelity", "pass"])
78
+ expect((await invoke(["verify"])).exitCode).toBe(1)
79
+ })
80
+
81
+ test("#given a missing gate #when skipped without a meaningful reason #then the command refuses", async () => {
82
+ const { invoke } = await delivered()
83
+ for (const reason of [[], ["--reason", " "]]) {
84
+ await expect(invoke(["gate", "fidelity", "not_run", ...reason])).rejects.toThrow("reason")
85
+ }
86
+ })
87
+
88
+ test("#given a fact ledger and an unverified edition #when the gate is skipped with a reason #then delivery still refuses", async () => {
89
+ const { dir, invoke } = await delivered()
90
+ writeFileSync(join(dir, "fact-ledger.json"), JSON.stringify({ version: 1, facts: [] }))
91
+ await invoke(["gate", "fidelity", "not_run", "--reason", "This harness has no verifier"])
92
+ for (const command of ["verify", "finish"]) {
93
+ const result = await invoke([command])
94
+ expect(result.exitCode).toBe(1)
95
+ expect(JSON.stringify(result.json.problems)).toContain("fidelity gate not run")
96
+ }
97
+ })
98
+
99
+ test("#given a stale fidelity record nobody attached #when the gate is skipped with a reason #then delivery still refuses", async () => {
100
+ const { dir, path, invoke } = await delivered()
101
+ writeFidelityRecord(join(dir, "fidelity-defects.json"), "fidelity", { artifact_sha256: "0".repeat(64), report: path, defects: [] })
102
+ await invoke(["gate", "fidelity", "not_run", "--reason", "This harness has no verifier"])
103
+ for (const command of ["verify", "finish"]) {
104
+ const result = await invoke([command])
105
+ expect(result.exitCode).toBe(1)
106
+ expect(JSON.stringify(result.json.problems)).toContain("fidelity gate not run")
107
+ }
108
+ })
109
+
110
+ test("#given an explicit skip and an existing blocker #when verified #then the skip cannot release the blocker", async () => {
111
+ const { invoke } = await delivered("html", ["claim_status_invalid"])
112
+ await invoke(["gate", "fidelity", "not_run", "--reason", "Unsupported edition"])
113
+ expect((await invoke(["verify"])).exitCode).toBe(1)
114
+ })
115
+
116
+ const LOCKED_BRIEF = readFileSync(join(import.meta.dir, "tests", "fixtures", "fidelity-brief-locked.md"), "utf8")
117
+ const BLOCKER = Object.keys(DEFECT_CODES).find((code) => blocksDelivery({ code })) as string
118
+
119
+ /**
120
+ * A delivered html edition whose locked brief requires every table record, each present, fresh for
121
+ * the delivered bytes, and clean. recordPath is where the session keeps each row's record; a table
122
+ * row with no entry here fails the coverage test below, so a new row brings its own cases.
123
+ */
124
+ async function fullyRecorded(brief = LOCKED_BRIEF) {
125
+ const session = await delivered("html", [])
126
+ writeFileSync(join(session.dir, "brief.md"), brief)
127
+ const recordPath: Record<string, string> = { fidelity: session.record, meaning: join(session.dir, MEANING_RECORD) }
128
+ const write = (kind: string, fields: Record<string, unknown> = {}) =>
129
+ writeFidelityRecord(recordPath[kind], kind, { artifact_sha256: sha256Of(readFileSync(session.path)), report: session.path, defects: [], ...fields })
130
+ write("meaning")
131
+ const outcomes = async () => Promise.all(["verify", "finish"].map((command) => session.invoke([command])))
132
+ return { ...session, recordPath, write, outcomes }
133
+ }
134
+
135
+ async function expectRefused(session: Awaited<ReturnType<typeof fullyRecorded>>, problem: Record<string, unknown>) {
136
+ for (const result of await session.outcomes()) {
137
+ expect(result.exitCode).toBe(1)
138
+ expect(result.json.problems).toContainEqual(expect.objectContaining(problem))
139
+ }
140
+ }
141
+
142
+ test("#given the record table #when every row is read #then each row has every rule column and an arranged session path", async () => {
143
+ const { recordPath } = await fullyRecorded()
144
+ expect(Object.keys(recordPath).sort()).toEqual(FIDELITY_RECORDS.map((entry) => entry.kind).sort())
145
+ for (const entry of FIDELITY_RECORDS) {
146
+ for (const column of ["targets", "requiredWhen", "remedy", "blocking"] as const) expect(typeof entry[column], `${entry.kind}.${column}`).toBe("function")
147
+ expect(RECORD_ENVELOPE).toContain(entry.bindsTo)
148
+ }
149
+ })
150
+
151
+ // The table is the barrier's spec: every row gets the same five cases.
152
+ for (const entry of FIDELITY_RECORDS) {
153
+ describe(`record row ${entry.kind}`, () => {
154
+ test("#given the record is missing while required #when verified and finished #then delivery refuses", async () => {
155
+ const session = await fullyRecorded()
156
+ unlinkSync(session.recordPath[entry.kind])
157
+ await expectRefused(session, { code: "edition_unverified", path: session.recordPath[entry.kind] })
158
+ })
159
+
160
+ test("#given the record is for other bytes #when verified and finished #then delivery refuses: a different edition", async () => {
161
+ const session = await fullyRecorded()
162
+ session.write(entry.kind, { artifact_sha256: sha256Of("an earlier draft") })
163
+ await expectRefused(session, { code: "edition_unverified", path: session.recordPath[entry.kind], message: expect.stringContaining("is for a different edition") })
164
+ })
165
+
166
+ test("#given a fresh record with a blocking defect #when verified and finished #then delivery refuses", async () => {
167
+ const session = await fullyRecorded()
168
+ session.write(entry.kind, { defects: [{ code: BLOCKER, severity: "minor", message: BLOCKER }] })
169
+ await expectRefused(session, { code: "fidelity_unresolved", codes: [BLOCKER], path: session.recordPath[entry.kind] })
170
+ })
171
+
172
+ test("#given a fresh clean record #when verified and finished #then delivery passes", async () => {
173
+ const session = await fullyRecorded()
174
+ expect((await session.outcomes()).map((result) => result.exitCode)).toEqual([0, 0])
175
+ })
176
+
177
+ for (const [name, mutate] of [
178
+ ["a missing envelope field", (record: Record<string, unknown>) => delete record.written_at],
179
+ ["an unknown field", (record: Record<string, unknown>) => Object.assign(record, { verified_elsewhere: true })],
180
+ ["another record kind", (record: Record<string, unknown>) => Object.assign(record, { record_kind: "not_a_record_kind" })],
181
+ ] as const) {
182
+ test(`#given a record with ${name} #when verified and finished #then delivery refuses it as malformed`, async () => {
183
+ const session = await fullyRecorded()
184
+ const path = session.recordPath[entry.kind]
185
+ const record = JSON.parse(readFileSync(path, "utf8"))
186
+ mutate(record)
187
+ writeFileSync(path, JSON.stringify(record))
188
+ await expectRefused(session, { code: "edition_unverified", path, message: expect.stringContaining("is malformed") })
189
+ })
190
+ }
191
+ })
192
+ }
193
+
194
+ test("#given a record kind outside the table or a malformed envelope #when written #then the writer refuses", () => {
195
+ const dir = mkdtempSync(join(tmpdir(), "delivery-barrier-writer-"))
196
+ onTestFinished(() => rmSync(dir, { recursive: true, force: true }))
197
+ const fields = { artifact_sha256: sha256Of("x"), report: join(dir, "report.html"), defects: [] }
198
+ expect(() => writeFidelityRecord(join(dir, "a.json"), "not_a_record_kind", fields)).toThrow("unknown fidelity record kind")
199
+ expect(() => writeFidelityRecord(join(dir, "b.json"), "meaning", { ...fields, artifact_sha256: "draft" })).toThrow("malformed")
200
+ expect(() => writeFidelityRecord(join(dir, "c.json"), "meaning", { ...fields, gatesRun: [] })).toThrow("unknown field")
201
+ })
202
+
203
+ /** A delivered, fidelity-verified html edition in a session whose locked brief sets `meaning check`. */
204
+ async function meaningSession(meaning: "on" | "off", codes: string[] | null) {
205
+ const session = await fullyRecorded(LOCKED_BRIEF.replace("meaning check: on", `meaning check: ${meaning}`))
206
+ if (codes === null) unlinkSync(session.recordPath.meaning)
207
+ else session.write("meaning", { defects: codes.map((code) => ({ code, severity: "major", message: code })) })
208
+ return session
209
+ }
210
+
211
+ for (const code of ["meaning_drift_confirmed", "meaning_unverified"]) {
212
+ test(`#given a meaning record with ${code} #when verified and finished #then delivery refuses`, async () => {
213
+ const session = await meaningSession("on", [code])
214
+ await expectRefused(session, { code: "fidelity_unresolved", codes: [code], path: join(session.dir, MEANING_RECORD) })
215
+ })
216
+ }
217
+
218
+ test("#given the brief says meaning check on and the meaning step was skipped #when verified and finished #then delivery refuses: meaning gate not run", async () => {
219
+ const session = await meaningSession("on", null)
220
+ await expectRefused(session, { message: expect.stringContaining("meaning gate not run") })
221
+ })
222
+
223
+ test("#given a malformed ## Fidelity block that tries to turn meaning check off #when no meaning record exists #then delivery refuses: meaning gate not run", async () => {
224
+ const malformed = LOCKED_BRIEF.replace("meaning check: on (answered_by: default)", "meaning check: on (answered_by: default)\nmeaning check: off (answered_by: user)")
225
+ const session = await fullyRecorded(malformed)
226
+ unlinkSync(session.recordPath.meaning)
227
+ await expectRefused(session, { message: expect.stringContaining("meaning gate not run") })
228
+ })
229
+
230
+ test("#given the brief says meaning check off and no meaning record #when verified and finished #then delivery passes", async () => {
231
+ const session = await meaningSession("off", null)
232
+ expect((await session.outcomes()).map((result) => result.exitCode)).toEqual([0, 0])
233
+ })
234
+
235
+ test("#given a clean meaning record (warn only) under meaning check on #when verified #then delivery passes", async () => {
236
+ const { invoke } = await meaningSession("on", ["meaning_drift_warn"])
237
+ expect((await invoke(["verify"])).exitCode).toBe(0)
238
+ })
239
+
240
+ test("#given meaning check on, no fidelity evidence, and an explicit fidelity not_run #when verified #then the logged exemption covers the meaning step too", async () => {
241
+ const { dir, invoke } = await delivered()
242
+ writeFileSync(join(dir, "brief.md"), LOCKED_BRIEF)
243
+ await invoke(["gate", "fidelity", "not_run", "--reason", "No factual content in this edition"])
244
+ expect((await invoke(["verify"])).exitCode).toBe(0)
245
+ })