lazycodex-ai 5.1.44 → 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 (412) 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 +865 -413
  12. package/dist/cli/service-auth/keystore.d.ts +4 -2
  13. package/dist/cli-node/index.js +865 -413
  14. package/docs/reference/web-terminal-visual-qa.md +8 -0
  15. package/package.json +1 -1
  16. package/packages/lsp-daemon/dist/cli.js +7 -7
  17. package/packages/lsp-daemon/dist/client.js +7 -7
  18. package/packages/lsp-daemon/dist/index.js +7 -7
  19. package/packages/lsp-daemon/package.json +3 -3
  20. package/packages/lsp-tools-mcp/dist/cli.js +7 -7
  21. package/packages/lsp-tools-mcp/dist/mcp.js +7 -7
  22. package/packages/lsp-tools-mcp/dist/tools.js +4 -4
  23. package/packages/lsp-tools-mcp/package.json +3 -3
  24. package/packages/omo-codex/plugin/.codex-plugin/plugin.json +1 -1
  25. package/packages/omo-codex/plugin/components/bootstrap/dist/cli.js +77 -26
  26. package/packages/omo-codex/plugin/components/bootstrap/hooks/hooks.json +1 -1
  27. package/packages/omo-codex/plugin/components/bootstrap/package.json +1 -1
  28. package/packages/omo-codex/plugin/components/bootstrap/src/hook.ts +1 -1
  29. package/packages/omo-codex/plugin/components/comment-checker/biome.json +1 -21
  30. package/packages/omo-codex/plugin/components/comment-checker/hooks/hooks.json +1 -1
  31. package/packages/omo-codex/plugin/components/comment-checker/package.json +4 -4
  32. package/packages/omo-codex/plugin/components/git-bash/hooks/hooks.json +2 -2
  33. package/packages/omo-codex/plugin/components/git-bash/package.json +1 -1
  34. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/biome.json +1 -21
  35. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/hooks/hooks.json +1 -1
  36. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/package.json +4 -4
  37. package/packages/omo-codex/plugin/components/lsp/biome.json +1 -21
  38. package/packages/omo-codex/plugin/components/lsp/dist/.omo-runtime-manifest.json +3 -3
  39. package/packages/omo-codex/plugin/components/lsp/dist/cli.js +4 -4
  40. package/packages/omo-codex/plugin/components/lsp/hooks/hooks.json +2 -2
  41. package/packages/omo-codex/plugin/components/lsp/package.json +4 -4
  42. package/packages/omo-codex/plugin/components/lsp/scripts/build-runtime.mjs +0 -1
  43. package/packages/omo-codex/plugin/components/lsp/test/codex-hook-not-installed.test.ts +1 -1
  44. package/packages/omo-codex/plugin/components/rules/biome.json +1 -21
  45. package/packages/omo-codex/plugin/components/rules/hooks/hooks.json +4 -4
  46. package/packages/omo-codex/plugin/components/rules/package.json +4 -4
  47. package/packages/omo-codex/plugin/components/teammode/hooks/hooks.json +1 -1
  48. package/packages/omo-codex/plugin/components/teammode/package.json +1 -1
  49. package/packages/omo-codex/plugin/components/telemetry/biome.json +1 -21
  50. package/packages/omo-codex/plugin/components/telemetry/dist/cli.js +90 -1
  51. package/packages/omo-codex/plugin/components/telemetry/dist/posthog.js +91 -1
  52. package/packages/omo-codex/plugin/components/telemetry/hooks/hooks.json +1 -1
  53. package/packages/omo-codex/plugin/components/telemetry/package.json +4 -4
  54. package/packages/omo-codex/plugin/components/ultrawork/agents/lazycodex-gate-reviewer.toml +2 -2
  55. package/packages/omo-codex/plugin/components/ultrawork/biome.json +1 -21
  56. package/packages/omo-codex/plugin/components/ultrawork/hooks/hooks.json +1 -1
  57. package/packages/omo-codex/plugin/components/ultrawork/package.json +4 -4
  58. package/packages/omo-codex/plugin/components/ulw-execute-continuation/biome.json +1 -21
  59. package/packages/omo-codex/plugin/components/ulw-execute-continuation/directive.md +1 -1
  60. package/packages/omo-codex/plugin/components/ulw-execute-continuation/hooks/hooks.json +1 -1
  61. package/packages/omo-codex/plugin/components/ulw-execute-continuation/package.json +4 -4
  62. package/packages/omo-codex/plugin/components/ulw-loop/biome.json +1 -21
  63. package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint.js +1 -1
  64. package/packages/omo-codex/plugin/components/ulw-loop/dist/cli.js +7 -7
  65. package/packages/omo-codex/plugin/components/ulw-loop/dist/codex-goal-instruction.js +5 -5
  66. package/packages/omo-codex/plugin/components/ulw-loop/dist/stop-resume-hook.js +1 -1
  67. package/packages/omo-codex/plugin/components/ulw-loop/hooks/hooks.json +5 -5
  68. package/packages/omo-codex/plugin/components/ulw-loop/package.json +4 -4
  69. package/packages/omo-codex/plugin/components/ulw-loop/src/checkpoint.ts +1 -1
  70. package/packages/omo-codex/plugin/components/ulw-loop/src/codex-goal-instruction.ts +5 -5
  71. package/packages/omo-codex/plugin/components/ulw-loop/src/stop-resume-hook.ts +1 -1
  72. package/packages/omo-codex/plugin/components/ulw-loop/test/fixtures/commit-mutations.mjs +2 -0
  73. package/packages/omo-codex/plugin/hooks/post-compact-resetting-git-bash-mcp-reminder.json +1 -1
  74. package/packages/omo-codex/plugin/hooks/post-compact-resetting-lsp-diagnostics-cache.json +1 -1
  75. package/packages/omo-codex/plugin/hooks/post-compact-resetting-project-rule-cache.json +1 -1
  76. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-comments.json +1 -1
  77. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-lsp-diagnostics.json +1 -1
  78. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-thread-title-hygiene.json +1 -1
  79. package/packages/omo-codex/plugin/hooks/post-tool-use-matching-project-rules.json +1 -1
  80. package/packages/omo-codex/plugin/hooks/post-tool-use-recording-spawn-admission.json +1 -1
  81. package/packages/omo-codex/plugin/hooks/pre-tool-use-enforcing-unlimited-goal-budget.json +1 -1
  82. package/packages/omo-codex/plugin/hooks/pre-tool-use-guarding-ulw-loop-spawns.json +1 -1
  83. package/packages/omo-codex/plugin/hooks/pre-tool-use-recommending-git-bash-mcp.json +1 -1
  84. package/packages/omo-codex/plugin/hooks/session-start-checking-auto-update.json +1 -1
  85. package/packages/omo-codex/plugin/hooks/session-start-checking-bootstrap-provisioning.json +1 -1
  86. package/packages/omo-codex/plugin/hooks/session-start-loading-project-rules.json +1 -1
  87. package/packages/omo-codex/plugin/hooks/session-start-recording-session-telemetry.json +1 -1
  88. package/packages/omo-codex/plugin/hooks/stop-checking-ulw-execute-continuation.json +1 -1
  89. package/packages/omo-codex/plugin/hooks/stop-checking-ulw-loop-resume.json +1 -1
  90. package/packages/omo-codex/plugin/hooks/subagent-stop-verifying-lazycodex-executor-evidence.json +1 -1
  91. package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ultrawork-trigger.json +1 -1
  92. package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ulw-loop-steering.json +1 -1
  93. package/packages/omo-codex/plugin/hooks/user-prompt-submit-loading-project-rules.json +1 -1
  94. package/packages/omo-codex/plugin/package-lock.json +12 -12
  95. package/packages/omo-codex/plugin/package.json +1 -1
  96. package/packages/omo-codex/plugin/scripts/sync-skills.mjs +14 -9
  97. package/packages/omo-codex/plugin/skills/ast-grep/AGENTS.md +1 -1
  98. package/packages/omo-codex/plugin/skills/browser/SKILL.md +21 -22
  99. package/packages/omo-codex/plugin/skills/browser/references/commands.md +2 -9
  100. package/packages/omo-codex/plugin/skills/browser/references/install.md +1 -2
  101. package/packages/omo-codex/plugin/skills/browser/runtime/omowright/manifest.json +1 -1
  102. package/packages/omo-codex/plugin/skills/browser/scripts/browser-doctor.mjs +2 -2
  103. package/packages/omo-codex/plugin/skills/browser/scripts/browser-engine-guard.mjs +63 -487
  104. package/packages/omo-codex/plugin/skills/browser/scripts/browser-install.mjs +1 -1
  105. package/packages/omo-codex/plugin/skills/browser/scripts/omowright.mjs +1 -10
  106. package/packages/omo-codex/plugin/skills/frontend/ATTRIBUTION.md +2 -2
  107. package/packages/omo-codex/plugin/skills/frontend/SKILL.md +79 -118
  108. package/packages/omo-codex/plugin/skills/frontend/references/design/README.md +75 -224
  109. package/packages/omo-codex/plugin/skills/frontend/references/design/stylegallery.md +32 -27
  110. package/packages/omo-codex/plugin/skills/frontend/references/perfection/README.md +2 -2
  111. package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/check-accent-stripes.ts +8 -24
  112. package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/check-draft-guard.ts +40 -0
  113. package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/collect-files.ts +53 -0
  114. package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/draft-guard/findings.ts +16 -0
  115. package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/draft-guard/lint.ts +38 -0
  116. package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/draft-guard/rules.ts +69 -0
  117. package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/draft-guard/text-nodes.ts +121 -0
  118. package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/fixtures/draft-guard/negative/attr-lookalikes.tsx +10 -0
  119. package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/fixtures/draft-guard/negative/bound-values.tsx +12 -0
  120. package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/fixtures/draft-guard/negative/const-generics.tsx +3 -0
  121. package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/fixtures/draft-guard/negative/generics.tsx +7 -0
  122. package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/fixtures/draft-guard/negative/measured-stats.html +6 -0
  123. package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/fixtures/draft-guard/negative/plain-copy.html +9 -0
  124. package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/fixtures/draft-guard/negative/state-copy.html +11 -0
  125. package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/fixtures/draft-guard/positive/attribute-edges.html +7 -0
  126. package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/fixtures/draft-guard/positive/components.tsx +16 -0
  127. package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/fixtures/draft-guard/positive/decorative-badge.vue +7 -0
  128. package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/fixtures/draft-guard/positive/emoji-icons.tsx +9 -0
  129. package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/fixtures/draft-guard/positive/jsx-edges.tsx +1 -0
  130. package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/fixtures/draft-guard/positive/promo-copy.html +6 -0
  131. package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/fixtures/draft-guard/positive/promo-copy.tsx +8 -0
  132. package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/fixtures/draft-guard/positive/vanity-stats.html +5 -0
  133. package/packages/omo-codex/plugin/skills/init-deep/SKILL.md +1 -1
  134. package/packages/omo-codex/plugin/skills/programming/SKILL.md +10 -7
  135. package/packages/omo-codex/plugin/skills/programming/references/typescript/README.md +10 -7
  136. package/packages/omo-codex/plugin/skills/programming/references/typescript/bootstrap.md +9 -23
  137. package/packages/omo-codex/plugin/skills/programming/references/typescript/oxc-lint.md +130 -0
  138. package/packages/omo-codex/plugin/skills/programming/references/typescript/tsconfig-strict.md +6 -62
  139. package/packages/omo-codex/plugin/skills/programming/scripts/typescript/check-no-excuse-rules.ts +12 -12
  140. package/packages/omo-codex/plugin/skills/programming/scripts/typescript/new-project.ts +20 -81
  141. package/packages/omo-codex/plugin/skills/programming/scripts/typescript/oxc/.oxfmtrc.json +9 -0
  142. package/packages/omo-codex/plugin/skills/programming/scripts/typescript/oxc/.oxlintrc.json +46 -0
  143. package/packages/omo-codex/plugin/skills/programming/scripts/typescript/oxc/lint/oxlint-plugin/LICENSE +21 -0
  144. package/packages/omo-codex/plugin/skills/programming/scripts/typescript/oxc/lint/oxlint-plugin/index.js +10 -0
  145. package/packages/omo-codex/plugin/skills/programming/scripts/typescript/oxc/lint/oxlint-plugin/no-em-dash-copy.js +28 -0
  146. package/packages/omo-codex/plugin/skills/programming/scripts/typescript/oxc/lint/oxlint-plugin/require-suppression-reason.js +62 -0
  147. package/packages/omo-codex/plugin/skills/programming/scripts/typescript/oxc/tsconfig.json +29 -0
  148. package/packages/omo-codex/plugin/skills/refactor/SKILL.md +14 -14
  149. package/packages/omo-codex/plugin/skills/remove-ai-slops/SKILL.md +1 -1
  150. package/packages/omo-codex/plugin/skills/review-work/SKILL.md +2 -2
  151. package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/AGENTS.md +1 -1
  152. package/packages/omo-codex/plugin/skills/ulw-execute/SKILL.md +2 -2
  153. package/packages/omo-codex/plugin/skills/ulw-loop/SKILL.md +1 -1
  154. package/packages/omo-codex/plugin/skills/ulw-research/AGENTS.md +21 -3
  155. package/packages/omo-codex/plugin/skills/ulw-research/SKILL.md +37 -12
  156. package/packages/omo-codex/plugin/skills/ulw-research/references/deliverable-phase.md +77 -16
  157. package/packages/omo-codex/plugin/skills/ulw-research/references/fact-ledger-grammar.md +134 -0
  158. package/packages/omo-codex/plugin/skills/ulw-research/references/fact-ledger.md +95 -0
  159. package/packages/omo-codex/plugin/skills/ulw-research/references/fidelity-claim-kinds.md +19 -0
  160. package/packages/omo-codex/plugin/skills/ulw-research/references/report-gates.md +68 -3
  161. package/packages/omo-codex/plugin/skills/ulw-research/references/report-visualization.md +2 -0
  162. package/packages/omo-codex/plugin/skills/ulw-research/references/unit-rules.md +157 -0
  163. package/packages/omo-codex/plugin/skills/ulw-research/scripts/claim-kinds.mjs +116 -0
  164. package/packages/omo-codex/plugin/skills/ulw-research/scripts/claims-index.mjs +125 -0
  165. package/packages/omo-codex/plugin/skills/ulw-research/scripts/cli-support.mjs +19 -5
  166. package/packages/omo-codex/plugin/skills/ulw-research/scripts/contracts.mjs +89 -3
  167. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-commands-meaning.mjs +191 -0
  168. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-commands-verify.mjs +164 -0
  169. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-commands.mjs +239 -0
  170. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-derive.mjs +66 -0
  171. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-expr.mjs +108 -0
  172. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-fill.mjs +194 -0
  173. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-ledger-kinds.mjs +250 -0
  174. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-ledger-schema.mjs +221 -0
  175. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-ledger.mjs +123 -0
  176. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-locate.mjs +231 -0
  177. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-pairs.mjs +187 -0
  178. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-recompute.mjs +89 -0
  179. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-binding.mjs +130 -0
  180. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-brief.mjs +150 -0
  181. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-claims.mjs +89 -0
  182. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-lexicon.mjs +145 -0
  183. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-records.mjs +163 -0
  184. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-state.mjs +64 -0
  185. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-trace.mjs +85 -0
  186. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-values.mjs +229 -0
  187. package/packages/omo-codex/plugin/skills/ulw-research/scripts/gates-fidelity.mjs +113 -0
  188. package/packages/omo-codex/plugin/skills/ulw-research/scripts/gates-static.mjs +29 -9
  189. package/packages/omo-codex/plugin/skills/ulw-research/scripts/gates-structure.mjs +1 -1
  190. package/packages/omo-codex/plugin/skills/ulw-research/scripts/gates-text.mjs +1 -27
  191. package/packages/omo-codex/plugin/skills/ulw-research/scripts/html-lite.mjs +2 -2
  192. package/packages/omo-codex/plugin/skills/ulw-research/scripts/html-markup.mjs +182 -0
  193. package/packages/omo-codex/plugin/skills/ulw-research/scripts/lexicon.mjs +47 -0
  194. package/packages/omo-codex/plugin/skills/ulw-research/scripts/md-lite.mjs +193 -0
  195. package/packages/omo-codex/plugin/skills/ulw-research/scripts/meaning-check.mjs +196 -0
  196. package/packages/omo-codex/plugin/skills/ulw-research/scripts/numerals.mjs +226 -0
  197. package/packages/omo-codex/plugin/skills/ulw-research/scripts/outcome.mjs +8 -8
  198. package/packages/omo-codex/plugin/skills/ulw-research/scripts/particles-ko.mjs +108 -0
  199. package/packages/omo-codex/plugin/skills/ulw-research/scripts/placeholder.mjs +184 -0
  200. package/packages/omo-codex/plugin/skills/ulw-research/scripts/polish-apply.mjs +115 -0
  201. package/packages/omo-codex/plugin/skills/ulw-research/scripts/polish-diff.mjs +31 -0
  202. package/packages/omo-codex/plugin/skills/ulw-research/scripts/polish-project.mjs +125 -0
  203. package/packages/omo-codex/plugin/skills/ulw-research/scripts/polish-rules.mjs +170 -0
  204. package/packages/omo-codex/plugin/skills/ulw-research/scripts/rational.mjs +139 -0
  205. package/packages/omo-codex/plugin/skills/ulw-research/scripts/report-tools-commands.mjs +21 -10
  206. package/packages/omo-codex/plugin/skills/ulw-research/scripts/report-tools.mjs +15 -0
  207. package/packages/omo-codex/plugin/skills/ulw-research/scripts/sentence-split.mjs +77 -0
  208. package/packages/omo-codex/plugin/skills/ulw-research/scripts/source-commands.mjs +140 -0
  209. package/packages/omo-codex/plugin/skills/ulw-research/scripts/source-snapshot.mjs +176 -0
  210. package/packages/omo-codex/plugin/skills/ulw-research/scripts/text-normalize.mjs +82 -0
  211. package/packages/omo-codex/plugin/skills/ulw-research/scripts/text-similarity.mjs +28 -0
  212. package/packages/omo-codex/plugin/skills/ulw-research/scripts/unit-algebra.mjs +101 -0
  213. package/packages/omo-codex/plugin/skills/ulw-research/scripts/unit-errors.mjs +23 -0
  214. package/packages/omo-codex/plugin/skills/ulw-research/scripts/unit-metadata.mjs +31 -0
  215. package/packages/omo-codex/plugin/skills/ulw-research/scripts/unit-pairs.mjs +72 -0
  216. package/packages/omo-codex/plugin/skills/ulw-research/scripts/unit-precision.mjs +44 -0
  217. package/packages/omo-codex/plugin/skills/ulw-research/scripts/unit-rules-shapes.mjs +64 -0
  218. package/packages/omo-codex/plugin/skills/ulw-research/scripts/unit-rules.mjs +173 -0
  219. package/packages/omo-codex/plugin/skills/ulw-research/scripts/units.mjs +105 -0
  220. package/packages/omo-codex/plugin/skills/visual-qa/SKILL.md +71 -64
  221. package/packages/omo-codex/plugin/test/component-bundled-cli.test.mjs +4 -1
  222. package/packages/omo-codex/plugin/test/sync-skills-codex-compatibility.test.mjs +19 -0
  223. package/packages/omo-codex/plugin/test/sync-skills-test-support.mjs +12 -8
  224. package/packages/omo-codex/scripts/install-dist/install-local.mjs +169 -30
  225. package/packages/shared-skills/skills/ast-grep/AGENTS.md +1 -1
  226. package/packages/shared-skills/skills/browser/SKILL.md +21 -22
  227. package/packages/shared-skills/skills/browser/references/commands.md +2 -9
  228. package/packages/shared-skills/skills/browser/references/install.md +1 -2
  229. package/packages/shared-skills/skills/browser/runtime/omowright/manifest.json +1 -1
  230. package/packages/shared-skills/skills/browser/scripts/browser-doctor.mjs +2 -2
  231. package/packages/shared-skills/skills/browser/scripts/browser-engine-guard.mjs +63 -487
  232. package/packages/shared-skills/skills/browser/scripts/browser-install.mjs +1 -1
  233. package/packages/shared-skills/skills/browser/scripts/omowright.mjs +1 -10
  234. package/packages/shared-skills/skills/frontend/ATTRIBUTION.md +2 -2
  235. package/packages/shared-skills/skills/frontend/SKILL.md +79 -118
  236. package/packages/shared-skills/skills/frontend/references/design/README.md +75 -224
  237. package/packages/shared-skills/skills/frontend/references/design/stylegallery.md +32 -27
  238. package/packages/shared-skills/skills/frontend/references/perfection/README.md +2 -2
  239. package/packages/shared-skills/skills/frontend/scripts/perfection/check-accent-stripes.ts +8 -24
  240. package/packages/shared-skills/skills/frontend/scripts/perfection/check-draft-guard.test.ts +101 -0
  241. package/packages/shared-skills/skills/frontend/scripts/perfection/check-draft-guard.ts +40 -0
  242. package/packages/shared-skills/skills/frontend/scripts/perfection/collect-files.test.ts +50 -0
  243. package/packages/shared-skills/skills/frontend/scripts/perfection/collect-files.ts +53 -0
  244. package/packages/shared-skills/skills/frontend/scripts/perfection/draft-guard/findings.ts +16 -0
  245. package/packages/shared-skills/skills/frontend/scripts/perfection/draft-guard/lint.ts +38 -0
  246. package/packages/shared-skills/skills/frontend/scripts/perfection/draft-guard/rules.ts +69 -0
  247. package/packages/shared-skills/skills/frontend/scripts/perfection/draft-guard/text-nodes.ts +121 -0
  248. package/packages/shared-skills/skills/frontend/scripts/perfection/fixtures/draft-guard/negative/attr-lookalikes.tsx +10 -0
  249. package/packages/shared-skills/skills/frontend/scripts/perfection/fixtures/draft-guard/negative/bound-values.tsx +12 -0
  250. package/packages/shared-skills/skills/frontend/scripts/perfection/fixtures/draft-guard/negative/const-generics.tsx +3 -0
  251. package/packages/shared-skills/skills/frontend/scripts/perfection/fixtures/draft-guard/negative/generics.tsx +7 -0
  252. package/packages/shared-skills/skills/frontend/scripts/perfection/fixtures/draft-guard/negative/measured-stats.html +6 -0
  253. package/packages/shared-skills/skills/frontend/scripts/perfection/fixtures/draft-guard/negative/plain-copy.html +9 -0
  254. package/packages/shared-skills/skills/frontend/scripts/perfection/fixtures/draft-guard/negative/state-copy.html +11 -0
  255. package/packages/shared-skills/skills/frontend/scripts/perfection/fixtures/draft-guard/positive/attribute-edges.html +7 -0
  256. package/packages/shared-skills/skills/frontend/scripts/perfection/fixtures/draft-guard/positive/components.tsx +16 -0
  257. package/packages/shared-skills/skills/frontend/scripts/perfection/fixtures/draft-guard/positive/decorative-badge.vue +7 -0
  258. package/packages/shared-skills/skills/frontend/scripts/perfection/fixtures/draft-guard/positive/emoji-icons.tsx +9 -0
  259. package/packages/shared-skills/skills/frontend/scripts/perfection/fixtures/draft-guard/positive/jsx-edges.tsx +1 -0
  260. package/packages/shared-skills/skills/frontend/scripts/perfection/fixtures/draft-guard/positive/promo-copy.html +6 -0
  261. package/packages/shared-skills/skills/frontend/scripts/perfection/fixtures/draft-guard/positive/promo-copy.tsx +8 -0
  262. package/packages/shared-skills/skills/frontend/scripts/perfection/fixtures/draft-guard/positive/vanity-stats.html +5 -0
  263. package/packages/shared-skills/skills/programming/SKILL.md +10 -7
  264. package/packages/shared-skills/skills/programming/references/typescript/README.md +10 -7
  265. package/packages/shared-skills/skills/programming/references/typescript/bootstrap.md +9 -23
  266. package/packages/shared-skills/skills/programming/references/typescript/oxc-lint.md +130 -0
  267. package/packages/shared-skills/skills/programming/references/typescript/tsconfig-strict.md +6 -62
  268. package/packages/shared-skills/skills/programming/scripts/typescript/check-no-excuse-rules.ts +12 -12
  269. package/packages/shared-skills/skills/programming/scripts/typescript/new-project.test.ts +162 -0
  270. package/packages/shared-skills/skills/programming/scripts/typescript/new-project.ts +20 -81
  271. package/packages/shared-skills/skills/programming/scripts/typescript/oxc/.oxfmtrc.json +9 -0
  272. package/packages/shared-skills/skills/programming/scripts/typescript/oxc/.oxlintrc.json +46 -0
  273. package/packages/shared-skills/skills/programming/scripts/typescript/oxc/lint/oxlint-plugin/LICENSE +21 -0
  274. package/packages/shared-skills/skills/programming/scripts/typescript/oxc/lint/oxlint-plugin/index.js +10 -0
  275. package/packages/shared-skills/skills/programming/scripts/typescript/oxc/lint/oxlint-plugin/no-em-dash-copy.js +28 -0
  276. package/packages/shared-skills/skills/programming/scripts/typescript/oxc/lint/oxlint-plugin/require-suppression-reason.js +62 -0
  277. package/packages/shared-skills/skills/programming/scripts/typescript/oxc/tsconfig.json +29 -0
  278. package/packages/shared-skills/skills/refactor/SKILL.md +13 -13
  279. package/packages/shared-skills/skills/ultimate-browsing/engine/AGENTS.md +1 -1
  280. package/packages/shared-skills/skills/ulw-execute/SKILL.md +1 -1
  281. package/packages/shared-skills/skills/ulw-research/AGENTS.md +21 -3
  282. package/packages/shared-skills/skills/ulw-research/SKILL.md +37 -12
  283. package/packages/shared-skills/skills/ulw-research/references/deliverable-phase.md +77 -16
  284. package/packages/shared-skills/skills/ulw-research/references/fact-ledger-grammar.md +134 -0
  285. package/packages/shared-skills/skills/ulw-research/references/fact-ledger.md +95 -0
  286. package/packages/shared-skills/skills/ulw-research/references/fidelity-claim-kinds.md +19 -0
  287. package/packages/shared-skills/skills/ulw-research/references/report-gates.md +68 -3
  288. package/packages/shared-skills/skills/ulw-research/references/report-visualization.md +2 -0
  289. package/packages/shared-skills/skills/ulw-research/references/unit-rules.md +157 -0
  290. package/packages/shared-skills/skills/ulw-research/scripts/claim-kinds.mjs +116 -0
  291. package/packages/shared-skills/skills/ulw-research/scripts/claim-kinds.test.ts +193 -0
  292. package/packages/shared-skills/skills/ulw-research/scripts/claims-index.mjs +125 -0
  293. package/packages/shared-skills/skills/ulw-research/scripts/claims-index.test.ts +149 -0
  294. package/packages/shared-skills/skills/ulw-research/scripts/cli-support.mjs +19 -5
  295. package/packages/shared-skills/skills/ulw-research/scripts/cli-support.test.ts +13 -2
  296. package/packages/shared-skills/skills/ulw-research/scripts/contracts.mjs +89 -3
  297. package/packages/shared-skills/skills/ulw-research/scripts/contracts.test.ts +100 -12
  298. package/packages/shared-skills/skills/ulw-research/scripts/copy-returns.test.ts +183 -0
  299. package/packages/shared-skills/skills/ulw-research/scripts/delivery-barrier.test.ts +245 -0
  300. package/packages/shared-skills/skills/ulw-research/scripts/fact-commands-meaning.mjs +191 -0
  301. package/packages/shared-skills/skills/ulw-research/scripts/fact-commands-verify.mjs +164 -0
  302. package/packages/shared-skills/skills/ulw-research/scripts/fact-commands.mjs +239 -0
  303. package/packages/shared-skills/skills/ulw-research/scripts/fact-derive.mjs +66 -0
  304. package/packages/shared-skills/skills/ulw-research/scripts/fact-derive.test.ts +375 -0
  305. package/packages/shared-skills/skills/ulw-research/scripts/fact-expr.mjs +108 -0
  306. package/packages/shared-skills/skills/ulw-research/scripts/fact-fill.mjs +194 -0
  307. package/packages/shared-skills/skills/ulw-research/scripts/fact-fill.test.ts +221 -0
  308. package/packages/shared-skills/skills/ulw-research/scripts/fact-ledger-grammar.test.ts +197 -0
  309. package/packages/shared-skills/skills/ulw-research/scripts/fact-ledger-kinds.mjs +250 -0
  310. package/packages/shared-skills/skills/ulw-research/scripts/fact-ledger-kinds.test.ts +156 -0
  311. package/packages/shared-skills/skills/ulw-research/scripts/fact-ledger-legacy.test.ts +160 -0
  312. package/packages/shared-skills/skills/ulw-research/scripts/fact-ledger-schema.mjs +221 -0
  313. package/packages/shared-skills/skills/ulw-research/scripts/fact-ledger-schema.test.ts +43 -0
  314. package/packages/shared-skills/skills/ulw-research/scripts/fact-ledger.mjs +123 -0
  315. package/packages/shared-skills/skills/ulw-research/scripts/fact-ledger.test.ts +254 -0
  316. package/packages/shared-skills/skills/ulw-research/scripts/fact-locate.mjs +231 -0
  317. package/packages/shared-skills/skills/ulw-research/scripts/fact-locate.test.ts +118 -0
  318. package/packages/shared-skills/skills/ulw-research/scripts/fact-pairs.mjs +187 -0
  319. package/packages/shared-skills/skills/ulw-research/scripts/fact-pairs.test.ts +167 -0
  320. package/packages/shared-skills/skills/ulw-research/scripts/fact-recompute.mjs +89 -0
  321. package/packages/shared-skills/skills/ulw-research/scripts/fact-unit-dimensions.test.ts +409 -0
  322. package/packages/shared-skills/skills/ulw-research/scripts/fact-unit-rules.test.ts +388 -0
  323. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-binding.mjs +130 -0
  324. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-binding.test.ts +81 -0
  325. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-brief.mjs +150 -0
  326. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-brief.test.ts +179 -0
  327. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-claims.mjs +89 -0
  328. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-lexicon.mjs +145 -0
  329. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-lexicon.test.ts +117 -0
  330. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-records.mjs +163 -0
  331. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-state.mjs +64 -0
  332. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-state.test.ts +46 -0
  333. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-trace.mjs +85 -0
  334. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-trace.test.ts +58 -0
  335. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-values.mjs +229 -0
  336. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-values.test.ts +270 -0
  337. package/packages/shared-skills/skills/ulw-research/scripts/gates-context.test.ts +64 -0
  338. package/packages/shared-skills/skills/ulw-research/scripts/gates-fidelity.mjs +113 -0
  339. package/packages/shared-skills/skills/ulw-research/scripts/gates-fidelity.test.ts +73 -0
  340. package/packages/shared-skills/skills/ulw-research/scripts/gates-static.mjs +29 -9
  341. package/packages/shared-skills/skills/ulw-research/scripts/gates-structure.mjs +1 -1
  342. package/packages/shared-skills/skills/ulw-research/scripts/gates-text.mjs +1 -27
  343. package/packages/shared-skills/skills/ulw-research/scripts/html-lite.mjs +2 -2
  344. package/packages/shared-skills/skills/ulw-research/scripts/html-markup.mjs +182 -0
  345. package/packages/shared-skills/skills/ulw-research/scripts/lexicon.mjs +47 -0
  346. package/packages/shared-skills/skills/ulw-research/scripts/lexicon.test.ts +57 -0
  347. package/packages/shared-skills/skills/ulw-research/scripts/md-lite.mjs +193 -0
  348. package/packages/shared-skills/skills/ulw-research/scripts/md-lite.test.ts +271 -0
  349. package/packages/shared-skills/skills/ulw-research/scripts/meaning-check.mjs +196 -0
  350. package/packages/shared-skills/skills/ulw-research/scripts/meaning-check.test.ts +228 -0
  351. package/packages/shared-skills/skills/ulw-research/scripts/numerals.mjs +226 -0
  352. package/packages/shared-skills/skills/ulw-research/scripts/numerals.test.ts +120 -0
  353. package/packages/shared-skills/skills/ulw-research/scripts/outcome.mjs +8 -8
  354. package/packages/shared-skills/skills/ulw-research/scripts/outcome.test.ts +29 -0
  355. package/packages/shared-skills/skills/ulw-research/scripts/particles-ko.mjs +108 -0
  356. package/packages/shared-skills/skills/ulw-research/scripts/particles-ko.test.ts +86 -0
  357. package/packages/shared-skills/skills/ulw-research/scripts/placeholder.mjs +184 -0
  358. package/packages/shared-skills/skills/ulw-research/scripts/placeholder.test.ts +77 -0
  359. package/packages/shared-skills/skills/ulw-research/scripts/polish-apply.mjs +115 -0
  360. package/packages/shared-skills/skills/ulw-research/scripts/polish-apply.test.ts +363 -0
  361. package/packages/shared-skills/skills/ulw-research/scripts/polish-diff.mjs +31 -0
  362. package/packages/shared-skills/skills/ulw-research/scripts/polish-diff.test.ts +168 -0
  363. package/packages/shared-skills/skills/ulw-research/scripts/polish-markup.test.ts +187 -0
  364. package/packages/shared-skills/skills/ulw-research/scripts/polish-project.mjs +125 -0
  365. package/packages/shared-skills/skills/ulw-research/scripts/polish-rules.mjs +170 -0
  366. package/packages/shared-skills/skills/ulw-research/scripts/rational.mjs +139 -0
  367. package/packages/shared-skills/skills/ulw-research/scripts/rational.test.ts +46 -0
  368. package/packages/shared-skills/skills/ulw-research/scripts/report-tools-commands.mjs +21 -10
  369. package/packages/shared-skills/skills/ulw-research/scripts/report-tools.mjs +15 -0
  370. package/packages/shared-skills/skills/ulw-research/scripts/report-tools.test.ts +458 -2
  371. package/packages/shared-skills/skills/ulw-research/scripts/sentence-split.mjs +77 -0
  372. package/packages/shared-skills/skills/ulw-research/scripts/sentence-split.test.ts +109 -0
  373. package/packages/shared-skills/skills/ulw-research/scripts/source-commands.mjs +140 -0
  374. package/packages/shared-skills/skills/ulw-research/scripts/source-snapshot.mjs +176 -0
  375. package/packages/shared-skills/skills/ulw-research/scripts/source-snapshot.test.ts +113 -0
  376. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/assets/share.csv +3 -0
  377. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/claim-graph.md +13 -0
  378. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/gen-legacy-dev.mjs +89 -0
  379. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/ledger-basic.json +223 -0
  380. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/ledger-legacy-dev.json +859 -0
  381. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/ledger-verify.json +420 -0
  382. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/report-bad.html +19 -0
  383. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/report-good.html +39 -0
  384. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/sources/S4/acronyms.json +3 -0
  385. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/sources/S4/meta.json +9 -0
  386. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/sources/S4/sentences.jsonl +7 -0
  387. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/sources/S4/text.txt +7 -0
  388. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/sources/S4.html +19 -0
  389. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/sources/S5/acronyms.json +1 -0
  390. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/sources/S5/meta.json +9 -0
  391. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/sources/S5/sentences.jsonl +1 -0
  392. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/sources/S5/text.txt +1 -0
  393. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/sources/S9.txt +1 -0
  394. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity-brief-locked.md +17 -0
  395. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity-brief-pending.md +17 -0
  396. package/packages/shared-skills/skills/ulw-research/scripts/text-normalize.mjs +82 -0
  397. package/packages/shared-skills/skills/ulw-research/scripts/text-normalize.test.ts +73 -0
  398. package/packages/shared-skills/skills/ulw-research/scripts/text-similarity.mjs +28 -0
  399. package/packages/shared-skills/skills/ulw-research/scripts/text-similarity.test.ts +38 -0
  400. package/packages/shared-skills/skills/ulw-research/scripts/unit-algebra.mjs +101 -0
  401. package/packages/shared-skills/skills/ulw-research/scripts/unit-errors.mjs +23 -0
  402. package/packages/shared-skills/skills/ulw-research/scripts/unit-metadata.mjs +31 -0
  403. package/packages/shared-skills/skills/ulw-research/scripts/unit-pairs.mjs +72 -0
  404. package/packages/shared-skills/skills/ulw-research/scripts/unit-precision.mjs +44 -0
  405. package/packages/shared-skills/skills/ulw-research/scripts/unit-rules-shapes.mjs +64 -0
  406. package/packages/shared-skills/skills/ulw-research/scripts/unit-rules.mjs +173 -0
  407. package/packages/shared-skills/skills/ulw-research/scripts/units.mjs +105 -0
  408. package/packages/shared-skills/skills/ulw-research/scripts/units.test.ts +83 -0
  409. package/packages/shared-skills/skills/visual-qa/SKILL.md +70 -63
  410. package/packages/shared-skills/skills/visual-qa/scripts/skill-doc-fields.test.ts +34 -0
  411. package/script/qa/web-terminal-visual-qa.mjs +6 -2
  412. package/script/qa/xterm-live-terminal.mjs +26 -3
@@ -1,8 +1,13 @@
1
1
  import { describe, expect, onTestFinished, test } from "bun:test"
2
- import { mkdtempSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs"
2
+ import { createHash } from "node:crypto"
3
+ import { copyFileSync, mkdtempSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs"
3
4
  import { tmpdir } from "node:os"
4
5
  import { delimiter, join } from "node:path"
5
6
 
7
+ import { PROMOTION } from "./fact-commands-meaning.mjs"
8
+ import { writeFidelityRecord } from "./fidelity-records.mjs"
9
+ import { PAIR_KINDS } from "./fact-pairs.mjs"
10
+ import { VERDICT_SCHEMA } from "./meaning-check.mjs"
6
11
  import { COMMANDS, run } from "./report-tools.mjs"
7
12
 
8
13
  const SCRIPT = join(import.meta.dir, "report-tools.mjs")
@@ -33,7 +38,7 @@ describe("help and usage", () => {
33
38
  test("#given --help --json #when run #then it lists exactly the command matrix", async () => {
34
39
  const result = await run(["--help", "--json"])
35
40
  expect(result.json.commands).toEqual([...COMMANDS])
36
- expect(COMMANDS).toEqual(["check", "layout-probe", "repair decide", "outcome init", "outcome set", "outcome gate", "outcome render", "outcome state", "outcome verify", "outcome finish", "outcome briefing", "format-extract"])
41
+ expect(COMMANDS).toEqual(["check", "layout-probe", "repair decide", "outcome init", "outcome set", "outcome gate", "outcome render", "outcome state", "outcome verify", "outcome finish", "outcome briefing", "format-extract", "fact add", "fact quote", "fact retract", "fact export", "fact fill", "fact verify", "fact derive", "source snapshot", "claims index"])
37
42
  })
38
43
 
39
44
  test("#given no arguments under node #when spawned #then it exits 2 with a usage line on stderr", () => {
@@ -84,6 +89,7 @@ describe("outcome", () => {
84
89
  await run(["outcome", "set", "pdf", "delivered", "--path", pdf, "--session-dir", dir])
85
90
  await run(["outcome", "set", "docx", "skipped", "--reason", "no editing audience", "--session-dir", dir])
86
91
  await run(["outcome", "gate", "static", "pass", "--session-dir", dir])
92
+ await run(["outcome", "gate", "fidelity", "not_run", "--reason", "Fixture has no factual content", "--session-dir", dir])
87
93
  const after = await run(["outcome", "verify", "--session-dir", dir])
88
94
  // then
89
95
  expect(before.exitCode).toBe(1)
@@ -185,6 +191,7 @@ describe("manifest bookkeeping", () => {
185
191
  await run(["outcome", "init", "--session-dir", dir, "--promised", "pdf"])
186
192
  await run(["outcome", "set", "pdf", "delivered", "--path", pdf, "--session-dir", dir])
187
193
  writeFileSync(join(dir, "sources-ledger.md"), ["[S1] https://a.example/x", "[S2] https://b.example/y"].join("\n"))
194
+ await run(["outcome", "gate", "fidelity", "not_run", "--reason", "Fixture has no factual content", "--session-dir", dir])
188
195
  const now = new Date(new Date(2026, 0, 1, 12, 0, 0).getTime() + 30 * 60_000).toISOString()
189
196
  const result = await run(["outcome", "finish", "--session-dir", dir, "--now", now])
190
197
  const manifest = JSON.parse(readFileSync(join(dir, "outcome.json"), "utf8"))
@@ -212,3 +219,452 @@ describe("format-extract exit codes", () => {
212
219
  expect(proc.exitCode).toBe(2)
213
220
  })
214
221
  })
222
+
223
+ const FIDELITY = join(FIXTURES, "fidelity")
224
+ const sha256 = (path: string) => createHash("sha256").update(readFileSync(path)).digest("hex")
225
+
226
+ type CliRow = { fact_id: string; kind: string; value: string | null }
227
+ type CliJson = { data: { row: CliRow; context: { match: string } }; defects: { code: string }[] }
228
+
229
+ function nodeJson(args: string[]) {
230
+ const proc = nodeWithoutBun(args)
231
+ const stdout = proc.stdout.toString()
232
+ let json = {} as CliJson
233
+ try {
234
+ json = JSON.parse(stdout) as CliJson
235
+ } catch {}
236
+ return { code: proc.exitCode, json, stdout, stderr: proc.stderr.toString() }
237
+ }
238
+
239
+ /** A session with the locked brief, S4 snapshotted, F1 (span), Q1 (completion), and F<n> (span in mm). */
240
+ function fidelitySession() {
241
+ const dir = sessionDir()
242
+ copyFileSync(join(FIXTURES, "fidelity-brief-locked.md"), join(dir, "brief.md"))
243
+ const s = ["--session-dir", dir]
244
+ const snap = nodeJson(["source", "snapshot", "--id", "S4", "--html", join(FIDELITY, "sources", "S4.html"), ...s])
245
+ const add = nodeJson(["fact", "add", "--source", "S4", "--find", "Span length", "--label", "span_len", "--json", ...s])
246
+ const quote = nodeJson(["fact", "quote", "--source", "S4", "--match", "no earlier than 2027", "--label", "completion", "--json", ...s])
247
+ const derive = nodeJson(["fact", "derive", "--op", "convert", "F1", "--to", "mm", "--label", "span_mm", "--json", ...s])
248
+ const mm = derive.json.data?.row?.fact_id
249
+ const source = [
250
+ '<!doctype html><html lang="en"><head><meta charset="utf-8"><title>Bridge</title></head><body><h1>Bridge</h1>',
251
+ "<h2>Findings</h2>",
252
+ "<p>The span length is {{F1:span_len}} [S4].</p>",
253
+ `<p>In millimetres the span is {{${mm}:span_mm}} [S4].</p>`,
254
+ "<p>The operator wrote {{Q1:completion}} [S4].</p>",
255
+ "<h2>How this report was made</h2><p>Collected from one cited public page [S4].</p>",
256
+ '<h2>Sources</h2><ol><li id="s4">[S4] Bridge page, retrieved 2026-10-01.</li></ol></body></html>',
257
+ ].join("\n")
258
+ writeFileSync(join(dir, "report.src.html"), source)
259
+ return { dir, s, snap, add, quote, derive, mm }
260
+ }
261
+
262
+ function verify(dir: string, report: string, extra: string[] = []) {
263
+ return nodeJson(["fact", "verify", join(dir, report), "--sources", join(dir, "sources"), "--brief", join(dir, "brief.md"), "--session-dir", dir, ...extra])
264
+ }
265
+
266
+ const codesOf = (json: { defects?: { code: string }[] }) => (json.defects ?? []).map((d) => d.code)
267
+
268
+ describe("fact, source, and claims under node", () => {
269
+ test("#given a fresh session #when snapshot, add, quote, derive, fill, and verify run #then the filled report verifies clean", () => {
270
+ // given
271
+ const { dir, s, snap, add, quote, derive, mm } = fidelitySession()
272
+ // when
273
+ const filled = nodeJson(["fact", "fill", join(dir, "report.src.html"), "--out", join(dir, "report.html"), ...s])
274
+ const checked = verify(dir, "report.html")
275
+ // then
276
+ expect(snap.code).toBe(0)
277
+ expect(add.code).toBe(0)
278
+ expect(add.json.data.row.value).toBe("320")
279
+ expect(add.json.data.context.match).toBe("320 m")
280
+ expect(quote.code).toBe(0)
281
+ expect(quote.json.data.row.fact_id).toBe("Q1")
282
+ expect(derive.code).toBe(0)
283
+ expect(mm).toBe("F2")
284
+ expect(filled.code).toBe(0)
285
+ expect(readFileSync(join(dir, "report.html"), "utf8")).toContain("320,000 mm (320 m)")
286
+ expect(checked.code).toBe(0)
287
+ expect(checked.json.defects).toEqual([])
288
+ const record = JSON.parse(readFileSync(join(dir, "fidelity-defects.json"), "utf8"))
289
+ expect(record.artifact_sha256).toBe(sha256(join(dir, "report.html")))
290
+ })
291
+
292
+ test("#given a filled report #when a value is hand-edited or a number typed #then verify exits 1 with the code", () => {
293
+ const { dir, s } = fidelitySession()
294
+ nodeJson(["fact", "fill", join(dir, "report.src.html"), "--out", join(dir, "report.html"), ...s])
295
+ const html = readFileSync(join(dir, "report.html"), "utf8")
296
+ writeFileSync(join(dir, "unit.html"), html.replace("320 m", "320 mm"))
297
+ writeFileSync(join(dir, "typed.html"), html.replace("<h2>How", "<p>Revenue grew 12% [S4].</p>\n<h2>How"))
298
+ const unit = verify(dir, "unit.html")
299
+ const typed = verify(dir, "typed.html")
300
+ expect(unit.code).toBe(1)
301
+ expect(codesOf(unit.json)).toContain("unit_mismatch")
302
+ expect(typed.code).toBe(1)
303
+ expect(codesOf(typed.json)).toContain("untraceable_number")
304
+ })
305
+
306
+ test("#given an anchor that is not in the source #when fact add runs #then it exits 1 with fact_not_located and writes no row", () => {
307
+ const { dir, s } = fidelitySession()
308
+ const proc = nodeJson(["fact", "add", "--source", "S4", "--find", "nope", "--label", "x", ...s])
309
+ expect(proc.code).toBe(1)
310
+ expect(proc.stdout).toContain("fact_not_located")
311
+ expect(JSON.parse(readFileSync(join(dir, "fact-ledger.json"), "utf8")).rows.map((r: { fact_id: string }) => r.fact_id)).toEqual(["F1", "Q1", "F2"])
312
+ })
313
+
314
+ test("#given a snapshotted source #when snapshotted again with the same or different content #then same is a no-op and different exits 1", () => {
315
+ const { dir, s } = fidelitySession()
316
+ writeFileSync(join(dir, "other.txt"), "Span length: 999 m\n")
317
+ expect(nodeJson(["source", "snapshot", "--id", "S4", "--html", join(FIDELITY, "sources", "S4.html"), ...s]).code).toBe(0)
318
+ expect(nodeJson(["source", "snapshot", "--id", "S4", "--text", join(dir, "other.txt"), ...s]).code).toBe(1)
319
+ })
320
+
321
+ test("#given an assumed value with and without a rationale #when added #then the row is kind assumed, and no rationale exits 2", () => {
322
+ const { s } = fidelitySession()
323
+ const added = nodeJson(["fact", "add", "--assumed", "12", "--unit", "%", "--rationale", "planning share chosen by the requester", "--label", "share_assumed", "--json", ...s])
324
+ expect(added.code).toBe(0)
325
+ expect(added.json.data.row.kind).toBe("assumed")
326
+ expect(nodeJson(["fact", "add", "--assumed", "12", "--label", "x", ...s]).code).toBe(2)
327
+ })
328
+
329
+ test("#given the fixture claim graph #when claims index runs #then claims.json lists its claims", () => {
330
+ const dir = sessionDir()
331
+ const proc = nodeJson(["claims", "index", join(FIDELITY, "claim-graph.md"), "--out", join(dir, "claims.json")])
332
+ expect(proc.code).toBe(0)
333
+ expect(JSON.parse(readFileSync(join(dir, "claims.json"), "utf8")).claims.length).toBeGreaterThan(0)
334
+ })
335
+
336
+ test("#given a ledger #when exported as csv #then rows carry fact_id and plot_value and stderr has the coverage line", () => {
337
+ const { s } = fidelitySession()
338
+ const proc = nodeJson(["fact", "export", "--csv", ...s])
339
+ expect(proc.code).toBe(0)
340
+ expect(proc.stdout.split("\n")[0]).toContain("fact_id")
341
+ expect(proc.stdout).toContain("F2")
342
+ expect(proc.stderr).toContain("1 of 1 sources snapshotted")
343
+ })
344
+ })
345
+
346
+ type Pair = { pair_id: string; kind: string; fact_ids?: string[]; sentence: string; quote?: string }
347
+
348
+ /** A filled session plus the pair artifact `fact pairs` wrote for it. */
349
+ function pairedSession() {
350
+ const session = fidelitySession()
351
+ const { dir, s } = session
352
+ nodeJson(["fact", "fill", join(dir, "report.src.html"), "--out", join(dir, "report.html"), ...s])
353
+ const pairs = nodeJson(["fact", "pairs", join(dir, "report.html"), "--ledger", join(dir, "fact-ledger.json"), "--out", join(dir, "pairs.json"), ...s])
354
+ return { ...session, pairs }
355
+ }
356
+
357
+ function verdicts(dir: string, name: string, tier: string, list: object[]) {
358
+ const file = join(dir, name)
359
+ writeFileSync(file, JSON.stringify({ model_tier: tier, verdicts: list }))
360
+ return file
361
+ }
362
+
363
+ const calibrationRows = (dir: string) =>
364
+ readFileSync(join(dir, "fidelity-calibration.jsonl"), "utf8")
365
+ .trim()
366
+ .split("\n")
367
+ .map((line) => JSON.parse(line))
368
+ .filter((row) => row.header !== true && row.run !== true)
369
+
370
+ const runLines = (dir: string) =>
371
+ readFileSync(join(dir, "fidelity-calibration.jsonl"), "utf8")
372
+ .trim()
373
+ .split("\n")
374
+ .map((line) => JSON.parse(line))
375
+ .filter((row) => row.run === true)
376
+
377
+ describe("meaning verifier under node", () => {
378
+ test("#given the filled fixture #when fact pairs runs #then pairs.json holds one quote pair per cited ledger quote", () => {
379
+ const { dir, pairs } = pairedSession()
380
+ const written = JSON.parse(readFileSync(join(dir, "pairs.json"), "utf8"))
381
+ expect(pairs.code).toBe(0)
382
+ expect(written.skipped).toBe(false)
383
+ expect(written.pairs.map((p: Pair) => [p.pair_id, p.kind, p.fact_ids])).toEqual([
384
+ ["P1", "quote", ["F1"]],
385
+ ["P2", "quote", ["Q1"]],
386
+ ])
387
+ expect(written.pairs_sha256).toMatch(/^[0-9a-f]{64}$/)
388
+ expect(Object.keys(written.counts).filter((key) => key !== "total" && key !== "force_strong")).toEqual([...PAIR_KINDS])
389
+ expect(written.verdict_schema).toEqual(JSON.parse(JSON.stringify(VERDICT_SCHEMA)))
390
+ })
391
+
392
+ test("#given cheap and strong verdicts agreeing on hedge_dropped #when meaning-check runs #then it exits 1 with one confirmed defect and one calibration line", () => {
393
+ const { dir, s } = pairedSession()
394
+ const drift = { pair_id: "P2", verdict: "drift", drift_kind: "hedge_dropped", fragment: "no earlier than 2027", source_fragment: "we estimate", explanation: "The estimate reads as a fact." }
395
+ const cheap = verdicts(dir, "cheap.json", "cheap", [drift, { pair_id: "P1", verdict: "ok" }])
396
+ const strong = verdicts(dir, "strong.json", "strong", [drift])
397
+ const checked = nodeJson(["fact", "meaning-check", "--pairs", join(dir, "pairs.json"), "--verdicts", cheap, "--verdicts", strong, ...s])
398
+ const record = JSON.parse(readFileSync(join(dir, "fidelity-meaning-defects.json"), "utf8"))
399
+ expect(checked.code).toBe(1)
400
+ expect(codesOf(checked.json)).toEqual(["meaning_drift_confirmed"])
401
+ expect(codesOf(record)).toEqual(["meaning_drift_confirmed"])
402
+ expect(calibrationRows(dir)).toHaveLength(1)
403
+ expect(calibrationRows(dir)[0]).toMatchObject({ pair_id: "P2", drift_kind: "hedge_dropped", confirmed: true, disputed: false })
404
+ const header = JSON.parse(readFileSync(join(dir, "fidelity-calibration.jsonl"), "utf8").split("\n")[0])
405
+ expect(header.promotion).toEqual({ ...PROMOTION })
406
+ const again = nodeJson(["fact", "meaning-check", "--pairs", join(dir, "pairs.json"), "--verdicts", cheap, "--verdicts", strong, ...s])
407
+ expect(again.code).toBe(1)
408
+ expect(calibrationRows(dir)).toHaveLength(1)
409
+ })
410
+
411
+ test("#given a dispute on the confirmed pair #when meaning-check runs #then the defect stays and the calibration line is disputed", () => {
412
+ const { dir, s } = pairedSession()
413
+ const drift = { pair_id: "P2", verdict: "drift", drift_kind: "hedge_dropped", fragment: "no earlier than 2027", source_fragment: "we estimate" }
414
+ const files = ["--verdicts", verdicts(dir, "cheap.json", "cheap", [drift, { pair_id: "P1", verdict: "ok" }]), "--verdicts", verdicts(dir, "strong.json", "strong", [drift])]
415
+ const checked = nodeJson(["fact", "meaning-check", "--pairs", join(dir, "pairs.json"), ...files, "--dispute", "P2", "--reason", "the operator states it", ...s])
416
+ expect(checked.code).toBe(1)
417
+ expect(codesOf(checked.json)).toEqual(["meaning_drift_confirmed"])
418
+ expect(calibrationRows(dir)[0]).toMatchObject({ pair_id: "P2", disputed: true, reason: "the operator states it" })
419
+ })
420
+
421
+ test("#given a dropped verdict and an unread pair #when meaning-check runs #then both pairs are meaning_unverified and it exits 1", () => {
422
+ const { dir, s } = pairedSession()
423
+ const cheap = verdicts(dir, "cheap.json", "cheap", [{ pair_id: "P2", verdict: "drift", drift_kind: "hedge_dropped", fragment: "certainly by 2027", source_fragment: "we estimate" }])
424
+ const checked = nodeJson(["fact", "meaning-check", "--pairs", join(dir, "pairs.json"), "--verdicts", cheap, ...s])
425
+ const summary = JSON.parse(checked.stderr.trim().split("\n").at(-1) ?? "{}")
426
+ expect(checked.code).toBe(1)
427
+ expect(checked.json.status).toBe("fail")
428
+ expect(codesOf(checked.json)).toEqual(["meaning_unverified", "meaning_unverified"])
429
+ expect(checked.json.unverified.map((gap: { pair_id: string }) => gap.pair_id)).toEqual(["P1", "P2"])
430
+ expect(summary).toMatchObject({ command: "fact meaning-check", defects: 2, dropped: 1, unverified: 2 })
431
+ })
432
+
433
+ test("#given an unreadable verdict file or a malformed pairs file #when meaning-check runs #then it exits 2", () => {
434
+ const { dir, s } = pairedSession()
435
+ const broken = join(dir, "broken.json")
436
+ writeFileSync(broken, "{ not json")
437
+ expect(nodeJson(["fact", "meaning-check", "--pairs", join(dir, "pairs.json"), "--verdicts", broken, ...s]).code).toBe(2)
438
+ const ok = verdicts(dir, "cheap.json", "cheap", [])
439
+ for (const body of ["{}", "null", '{ "pairs": "P1" }']) {
440
+ writeFileSync(join(dir, "bad-pairs.json"), body)
441
+ expect(nodeJson(["fact", "meaning-check", "--pairs", join(dir, "bad-pairs.json"), "--verdicts", ok, ...s]).code, body).toBe(2)
442
+ }
443
+ })
444
+
445
+ test("#given a confirmed run then a dispute on the same inputs #when meaning-check reruns #then the log still holds one run with the dispute", () => {
446
+ const { dir, s } = pairedSession()
447
+ const drift = { pair_id: "P2", verdict: "drift", drift_kind: "hedge_dropped", fragment: "no earlier than 2027", source_fragment: "we estimate" }
448
+ const files = ["--verdicts", verdicts(dir, "cheap.json", "cheap", [drift, { pair_id: "P1", verdict: "ok" }]), "--verdicts", verdicts(dir, "strong.json", "strong", [drift])]
449
+ const first = nodeJson(["fact", "meaning-check", "--pairs", join(dir, "pairs.json"), ...files, ...s])
450
+ const disputed = nodeJson(["fact", "meaning-check", "--pairs", join(dir, "pairs.json"), ...files, "--dispute", "P2", "--reason", "the operator states it", ...s])
451
+ expect(disputed.code).toBe(1)
452
+ expect(disputed.json.run_id).toBe(first.json.run_id)
453
+ expect(disputed.json.promotion_eligibility).toMatchObject({ runs: 1, confirmed: 1, disputed_confirmed: 1, disputed_to_confirmed: 1 })
454
+ expect(calibrationRows(dir)).toEqual([expect.objectContaining({ pair_id: "P2", disputed: true, reason: "the operator states it" })])
455
+ const plain = nodeJson(["fact", "meaning-check", "--pairs", join(dir, "pairs.json"), ...files, ...s])
456
+ expect(plain.json.promotion_eligibility).toMatchObject({ runs: 1, disputed_confirmed: 1 })
457
+ })
458
+
459
+ test("#given a run with no flagged pair #when meaning-check runs #then the run is still logged and counted", () => {
460
+ const { dir, s } = pairedSession()
461
+ const ok = (pair_id: string) => ({ pair_id, verdict: "ok" })
462
+ const files = ["--verdicts", verdicts(dir, "cheap.json", "cheap", [ok("P1"), ok("P2")]), "--verdicts", verdicts(dir, "strong.json", "strong", [ok("P1"), ok("P2")])]
463
+ const checked = nodeJson(["fact", "meaning-check", "--pairs", join(dir, "pairs.json"), ...files, ...s])
464
+ expect(checked.code).toBe(0)
465
+ expect(calibrationRows(dir)).toEqual([])
466
+ expect(runLines(dir)).toEqual([expect.objectContaining({ run_id: checked.json.run_id, flagged: 0 })])
467
+ expect(checked.json.promotion_eligibility.runs).toBe(1)
468
+ })
469
+
470
+ test("#given a corrupt calibration log #when meaning-check runs #then it exits 2 naming the file and line", () => {
471
+ const { dir, s } = pairedSession()
472
+ writeFileSync(join(dir, "fidelity-calibration.jsonl"), "{ not json\n")
473
+ const checked = nodeJson(["fact", "meaning-check", "--pairs", join(dir, "pairs.json"), "--verdicts", verdicts(dir, "cheap.json", "cheap", []), ...s])
474
+ expect(checked.code).toBe(2)
475
+ expect(checked.stderr).toContain(`${join(dir, "fidelity-calibration.jsonl")}:1`)
476
+ })
477
+
478
+ test("#given a skipped pairs file #when meaning-check runs #then it is accepted only when the locked brief says off", () => {
479
+ const { dir, s } = fidelitySession()
480
+ const skip = join(dir, "skip.json")
481
+ writeFileSync(skip, JSON.stringify({ schemaVersion: 1, skipped: true, pairs: [] }))
482
+ const check = () => nodeJson(["fact", "meaning-check", "--pairs", skip, "--verdicts", verdicts(dir, "cheap.json", "cheap", []), ...s])
483
+ const on = check()
484
+ expect(on.code).toBe(1)
485
+ expect(on.json.error).toBe("meaning_check_required")
486
+ writeFileSync(join(dir, "brief.md"), "# Brief\n")
487
+ expect(check().json.error).toBe("fidelity_interview_incomplete")
488
+ expect(check().code).toBe(1)
489
+ writeFileSync(join(dir, "brief.md"), readFileSync(join(FIXTURES, "fidelity-brief-locked.md"), "utf8").replace("meaning check: on", "meaning check: off"))
490
+ const off = check()
491
+ expect(off.code).toBe(0)
492
+ expect(off.json.status).toBe("skipped")
493
+ })
494
+
495
+ test("#given a brief with meaning check off #when fact pairs runs #then it exits 0 and writes skipped", () => {
496
+ const { dir, s } = fidelitySession()
497
+ const brief = readFileSync(join(dir, "brief.md"), "utf8").replace("meaning check: on", "meaning check: off")
498
+ writeFileSync(join(dir, "brief.md"), brief)
499
+ nodeJson(["fact", "fill", join(dir, "report.src.html"), "--out", join(dir, "report.html"), ...s])
500
+ const pairs = nodeJson(["fact", "pairs", join(dir, "report.html"), "--out", join(dir, "pairs.json"), ...s])
501
+ expect(pairs.code).toBe(0)
502
+ expect(JSON.parse(readFileSync(join(dir, "pairs.json"), "utf8"))).toMatchObject({ skipped: true })
503
+ })
504
+ })
505
+
506
+ describe("markdown editions under node", () => {
507
+ const MD = ["# Bridge", "", "## Findings", "", "The span length is {{F1:span_len}} [S4].", "", "The operator wrote {{Q1:completion}} [S4].", "", "## How this report was made", "", "Collected from one cited public page [S4].", "", "## Sources", "", "1. [S4] Bridge page, retrieved 2026-10-01.", ""].join("\n")
508
+
509
+ function mdSession() {
510
+ const session = fidelitySession()
511
+ writeFileSync(join(session.dir, "report.src.md"), MD)
512
+ const filled = nodeJson(["fact", "fill", join(session.dir, "report.src.md"), "--format", "md", "--out", join(session.dir, "report.md"), ...session.s])
513
+ return { ...session, filled }
514
+ }
515
+
516
+ test("#given a markdown source #when filled and verified as md #then verify passes and records the md bytes", () => {
517
+ const { dir, filled } = mdSession()
518
+ const checked = verify(dir, "report.md", ["--format", "md"])
519
+ expect(filled.code).toBe(0)
520
+ expect(readFileSync(join(dir, "report.md"), "utf8")).toContain("The span length is 320 m [S4].")
521
+ expect(checked.code).toBe(0)
522
+ expect(checked.json.defects).toEqual([])
523
+ expect(JSON.parse(readFileSync(join(dir, "fidelity-defects.json"), "utf8")).artifact_sha256).toBe(sha256(join(dir, "report.md")))
524
+ })
525
+
526
+ test("#given a filled markdown edition #when a value is hand-edited or a number typed #then md verify exits 1 with the code", () => {
527
+ const { dir } = mdSession()
528
+ const md = readFileSync(join(dir, "report.md"), "utf8")
529
+ writeFileSync(join(dir, "typed.md"), md.replace("## How", "Revenue grew 12% [S4].\n\n## How"))
530
+ copyFileSync(join(dir, "report.md.fill-map.json"), join(dir, "typed.md.fill-map.json"))
531
+ const typed = verify(dir, "typed.md", ["--format", "md"])
532
+ expect(typed.code).toBe(1)
533
+ expect(codesOf(typed.json)).toContain("untraceable_number")
534
+ })
535
+
536
+ test("#given a filled markdown edition with an unterminated fence #when verified or paired #then both refuse with exit 2", () => {
537
+ const { dir, s } = mdSession()
538
+ writeFileSync(join(dir, "fence.md"), `${readFileSync(join(dir, "report.md"), "utf8")}\n\`\`\`\nunclosed code\n`)
539
+ copyFileSync(join(dir, "report.md.fill-map.json"), join(dir, "fence.md.fill-map.json"))
540
+ const checked = verify(dir, "fence.md", ["--format", "md"])
541
+ expect(checked.code).toBe(2)
542
+ expect(checked.stderr).toContain("unterminated_fence")
543
+ expect(nodeJson(["fact", "pairs", join(dir, "fence.md"), "--out", join(dir, "pairs.json"), ...s]).code).toBe(2)
544
+ })
545
+
546
+ test("#given a filled markdown edition #when fact pairs runs on it #then it pairs the same quotes as the html edition", () => {
547
+ const { dir, s } = mdSession()
548
+ const pairs = nodeJson(["fact", "pairs", join(dir, "report.md"), "--out", join(dir, "pairs.json"), ...s])
549
+ expect(pairs.code).toBe(0)
550
+ expect(JSON.parse(readFileSync(join(dir, "pairs.json"), "utf8")).pairs.map((p: Pair) => p.fact_ids)).toEqual([["F1"], ["Q1"]])
551
+ })
552
+ })
553
+
554
+ describe("repair decide with several defect files", () => {
555
+ test("#given two --defects files #when decided #then the attempt signature carries both codes", async () => {
556
+ const dir = sessionDir()
557
+ const state = join(dir, "repair-state.json")
558
+ await run(["repair", "decide", "--state", state, "--defects", defectsFile(dir, "a.json", ["heading_generic"]), "--defects", defectsFile(dir, "b.json", ["untraceable_number"])])
559
+ const signature = JSON.parse(readFileSync(state, "utf8")).attempts[0].signature
560
+ expect(signature).toContain("heading_generic")
561
+ expect(signature).toContain("untraceable_number")
562
+ })
563
+ })
564
+
565
+ describe("fidelity delivery barrier", () => {
566
+ /** The locked brief has meaning check on, so a deliverable session also carries a clean meaning record. */
567
+ const meaningRan = (dir: string) => writeFidelityRecord(join(dir, "fidelity-meaning-defects.json"), "meaning", { artifact_sha256: sha256(join(dir, "report.html")), report: join(dir, "report.html"), defects: [] })
568
+
569
+ async function deliveredSession(codes: string[]) {
570
+ const { dir, s } = fidelitySession()
571
+ nodeJson(["fact", "fill", join(dir, "report.src.html"), "--out", join(dir, "report.html"), ...s])
572
+ meaningRan(dir)
573
+ const record = (list: string[]) => writeFidelityRecord(join(dir, "fidelity-defects.json"), "fidelity", { artifact_sha256: sha256(join(dir, "report.html")), report: join(dir, "report.html"), defects: list.map((code) => ({ code, message: code })) })
574
+ record(codes)
575
+ await run(["outcome", "init", "--session-dir", dir, "--promised", "html"])
576
+ await run(["outcome", "set", "html", "delivered", "--path", join(dir, "report.html"), "--verified-by", join(dir, "fidelity-defects.json"), "--session-dir", dir])
577
+ const decide = ["repair", "decide", "--state", join(dir, "repair-state.json"), "--defects", join(dir, "fidelity-defects.json"), "--artifact-bytes", "4096", "--renders", "1", "--session-dir", dir]
578
+ await run(decide)
579
+ const decision = await run(decide)
580
+ return { dir, record, decision }
581
+ }
582
+
583
+ test("#given a clean meaning record #when the report is edited and fact verify re-run #then the stale meaning record refuses, and a fresh one passes", async () => {
584
+ // given: meaning check on, readers clean, the edition verified and delivered
585
+ const { dir, s } = pairedSession()
586
+ const report = join(dir, "report.html")
587
+ const clean = [{ pair_id: "P1", verdict: "ok" }, { pair_id: "P2", verdict: "ok" }]
588
+ const readers = () => nodeJson(["fact", "meaning-check", "--pairs", join(dir, "pairs.json"), "--verdicts", verdicts(dir, "cheap.json", "cheap", clean), "--verdicts", verdicts(dir, "strong.json", "strong", clean), ...s])
589
+ expect(readers().code).toBe(0)
590
+ expect(verify(dir, "report.html").code).toBe(0)
591
+ await run(["outcome", "init", "--session-dir", dir, "--promised", "html"])
592
+ await run(["outcome", "set", "html", "delivered", "--path", report, "--verified-by", join(dir, "fidelity-defects.json"), "--session-dir", dir])
593
+ const first = await run(["outcome", "verify", "--session-dir", dir])
594
+ // when: draft 2 (a repair rewrite) is re-verified by fact verify only
595
+ writeFileSync(report, `${readFileSync(report, "utf8")}\n<!-- draft 2 -->\n`)
596
+ expect(verify(dir, "report.html").code).toBe(0)
597
+ const stale = await run(["outcome", "verify", "--session-dir", dir])
598
+ // when: the meaning readers run again on draft 2
599
+ expect(nodeJson(["fact", "pairs", report, "--out", join(dir, "pairs.json"), ...s]).code).toBe(0)
600
+ expect(readers().code).toBe(0)
601
+ const fresh = await run(["outcome", "verify", "--session-dir", dir])
602
+ // then
603
+ expect(first.exitCode).toBe(0)
604
+ expect(stale.exitCode).toBe(1)
605
+ expect(stale.json.problems).toContainEqual(expect.objectContaining({ code: "edition_unverified", path: join(dir, "fidelity-meaning-defects.json"), message: expect.stringContaining("meaning record") }))
606
+ expect(fresh.exitCode).toBe(0)
607
+ })
608
+
609
+ test("#given an unregistered appendix value #when verified #then untraceable_number and outcome verify refuses", async () => {
610
+ const { dir, s } = fidelitySession()
611
+ nodeJson(["fact", "fill", join(dir, "report.src.html"), "--out", join(dir, "report.html"), ...s])
612
+ const html = readFileSync(join(dir, "report.html"), "utf8").replace("<h2>How", "<h2>Appendix</h2>\n<p>Revenue grew 12% [S4].</p>\n<h2>How")
613
+ writeFileSync(join(dir, "report.html"), html)
614
+ const checked = verify(dir, "report.html")
615
+ await run(["outcome", "init", "--session-dir", dir, "--promised", "html"])
616
+ await run(["outcome", "set", "html", "delivered", "--path", join(dir, "report.html"), "--verified-by", join(dir, "fidelity-defects.json"), "--session-dir", dir])
617
+ const outcome = await run(["outcome", "verify", "--session-dir", dir])
618
+ expect(codesOf(checked.json)).toContain("untraceable_number")
619
+ expect(outcome.exitCode).toBe(1)
620
+ expect(outcome.json.problems.map((p: { code: string }) => p.code)).toContain("fidelity_unresolved")
621
+ })
622
+
623
+ for (const code of ["untraceable_number", "unbound_sentence"]) {
624
+ test(`#given ${code} left after repair delivers on exhaustion #when verified and finished #then both refuse, and an empty record passes`, async () => {
625
+ const { dir, record, decision } = await deliveredSession([code])
626
+ const refused = await run(["outcome", "verify", "--session-dir", dir])
627
+ const finish = await run(["outcome", "finish", "--session-dir", dir])
628
+ record([])
629
+ const passed = await run(["outcome", "verify", "--session-dir", dir])
630
+ const finished = await run(["outcome", "finish", "--session-dir", dir])
631
+ expect(decision.json.decision.action).toBe("deliver")
632
+ expect(refused.exitCode).toBe(1)
633
+ expect(JSON.stringify(refused.json.problems)).toContain(`fidelity_unresolved: ${code}`)
634
+ expect(finish.exitCode).toBe(1)
635
+ expect(passed.exitCode).toBe(0)
636
+ expect(finished.exitCode).toBe(0)
637
+ })
638
+ }
639
+
640
+ test("#given a clean main report and an inline edition #when the edition lacks or mismatches its verify record #then edition_unverified, and its own record passes", async () => {
641
+ // given
642
+ const { dir, s } = fidelitySession()
643
+ nodeJson(["fact", "fill", join(dir, "report.src.html"), "--out", join(dir, "report.html"), ...s])
644
+ expect(verify(dir, "report.html").code).toBe(0)
645
+ meaningRan(dir)
646
+ expect(nodeJson(["fact", "fill", join(dir, "report.src.html"), "--inline", "--out", join(dir, "report.inline.html"), ...s]).code).toBe(0)
647
+ await run(["outcome", "init", "--session-dir", dir, "--promised", "html,pdf"])
648
+ await run(["outcome", "set", "html", "delivered", "--path", join(dir, "report.html"), "--verified-by", join(dir, "fidelity-defects.json"), "--session-dir", dir])
649
+ const problems = async () => {
650
+ const result = await run(["outcome", "verify", "--session-dir", dir])
651
+ return { exitCode: result.exitCode, codes: result.json.problems.map((p: { code: string }) => p.code) }
652
+ }
653
+ // when: no --verified-by
654
+ await run(["outcome", "set", "pdf", "delivered", "--path", join(dir, "report.inline.html"), "--session-dir", dir])
655
+ const missing = await problems()
656
+ // when: a record for different bytes
657
+ expect(verify(dir, "report.inline.html", ["--out", join(dir, "fidelity-inline.json")]).code).toBe(0)
658
+ const inline = readFileSync(join(dir, "report.inline.html"), "utf8")
659
+ writeFileSync(join(dir, "report.inline.html"), inline.replace("320,000 mm", "330,000 mm"))
660
+ await run(["outcome", "set", "pdf", "delivered", "--path", join(dir, "report.inline.html"), "--verified-by", join(dir, "fidelity-inline.json"), "--session-dir", dir])
661
+ const altered = await problems()
662
+ // when: restored to the verified bytes
663
+ writeFileSync(join(dir, "report.inline.html"), inline)
664
+ const matched = await problems()
665
+ // then
666
+ expect(missing).toEqual({ exitCode: 1, codes: ["edition_unverified"] })
667
+ expect(altered).toEqual({ exitCode: 1, codes: ["edition_unverified"] })
668
+ expect(matched).toEqual({ exitCode: 0, codes: [] })
669
+ })
670
+ })
@@ -0,0 +1,77 @@
1
+ // sentence-split.mjs - deterministic sentence index for snapshot text.
2
+ //
3
+ // splitSentences(text) returns [{ sid, start, end, text }] with sid "s<k>" (1-based) and
4
+ // start/end as offsets into the raw text, so text.slice(start, end) === record.text.
5
+ // Boundaries: a line break; ". ! ?" followed by whitespace or end of line (known
6
+ // abbreviations such as "e.g." excepted); and the Korean endings da. yo. nida. kka? even
7
+ // with no following space, unless closing punctuation follows (quoted speech such as
8
+ // '...da."rago' then needs whitespace like any other terminator). "no." is an abbreviation
9
+ // only before a number ("No. 5"). Plain Node ESM, no dependencies.
10
+
11
+ import { normalizeText } from "./text-normalize.mjs"
12
+
13
+ const TERMINATORS = ".!?"
14
+ const CLOSERS = "\"')]}\u201D\u2019\u00BB\u300D\u300F"
15
+ const KOREAN_PERIOD_ENDINGS = new Set(["\uB2E4", "\uC694"]) // da, yo (nida ends in da)
16
+ const KOREAN_QUESTION_ENDING = "\uAE4C" // kka
17
+ const ABBREVIATIONS = new Set([
18
+ "e.g", "i.e", "etc", "vs", "cf", "approx", "al", "fig", "no", "mr", "mrs", "ms", "dr", "prof", "inc", "ltd", "jr", "sr",
19
+ ])
20
+ const SPACE = /\s/u
21
+
22
+ function isKoreanEnding(line, at) {
23
+ const prev = line[at - 1]
24
+ if (line[at] === ".") return KOREAN_PERIOD_ENDINGS.has(prev)
25
+ return line[at] === "?" && prev === KOREAN_QUESTION_ENDING
26
+ }
27
+
28
+ function isAbbreviation(line, from, at) {
29
+ let k = at
30
+ while (k > from && !SPACE.test(line[k - 1])) k--
31
+ const word = line.slice(k, at).replace(/^[("'[]+/u, "").toLowerCase()
32
+ if (word === "no") return /^\s*\d/u.test(line.slice(at + 1))
33
+ return ABBREVIATIONS.has(word)
34
+ }
35
+
36
+ function pushSegment(line, base, from, to, out) {
37
+ let start = from
38
+ let end = to
39
+ while (start < end && SPACE.test(line[start])) start++
40
+ while (end > start && SPACE.test(line[end - 1])) end--
41
+ const text = line.slice(start, end)
42
+ if (normalizeText(text) === "") return
43
+ out.push({ sid: `s${out.length + 1}`, start: base + start, end: base + end, text })
44
+ }
45
+
46
+ function splitLine(line, base, out) {
47
+ let from = 0
48
+ for (let i = 0; i < line.length; i++) {
49
+ if (!TERMINATORS.includes(line[i])) continue
50
+ let j = i + 1
51
+ while (j < line.length && (TERMINATORS.includes(line[j]) || CLOSERS.includes(line[j]))) j++
52
+ const atBreak = j >= line.length || SPACE.test(line[j])
53
+ const closed = [...line.slice(i + 1, j)].some((c) => CLOSERS.includes(c))
54
+ const korean = !closed && isKoreanEnding(line, i)
55
+ const abbreviation = line[i] === "." && !korean && isAbbreviation(line, from, i)
56
+ if ((atBreak || korean) && !abbreviation) {
57
+ pushSegment(line, base, from, j, out)
58
+ from = j
59
+ }
60
+ i = j - 1
61
+ }
62
+ pushSegment(line, base, from, line.length, out)
63
+ }
64
+
65
+ export function splitSentences(text) {
66
+ const source = String(text ?? "")
67
+ const out = []
68
+ let base = 0
69
+ while (base <= source.length) {
70
+ const newline = source.indexOf("\n", base)
71
+ const stop = newline === -1 ? source.length : newline
72
+ splitLine(source.slice(base, stop), base, out)
73
+ if (newline === -1) break
74
+ base = newline + 1
75
+ }
76
+ return out
77
+ }