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
@@ -0,0 +1,150 @@
1
+ // fidelity-brief.mjs - parse the `## Fidelity` block of brief.md and decide whether it is locked.
2
+ //
3
+ // Grammar (fable-interview.md section 3, "Parser contract"): the block starts at a line
4
+ // `## Fidelity` and ends at the next `## ` heading. It holds the seven field lines below, in any
5
+ // order, each `<field>: <value> (answered_by: user|default|request|pending)`, plus an optional
6
+ // `asked: <field>, <field>` history line. Values carry no parentheses, and `answered_by: pending`
7
+ // is legal only with the value `pending`. Trailing whitespace on a line is ignored; a second
8
+ // `## Fidelity` block is a parse error. This module parses; it never interprets a value, which
9
+ // is each consumer's job. Plain Node ESM, node: builtins only.
10
+
11
+ import { readFileSync } from "node:fs"
12
+
13
+ export const FIDELITY_FIELDS = Object.freeze([
14
+ "as of",
15
+ "meaning check",
16
+ "source floor",
17
+ "derived numbers",
18
+ "languages",
19
+ "identifier classes",
20
+ "fallback",
21
+ ])
22
+ export const FIDELITY_ANSWERED_BY = Object.freeze(["user", "default", "request", "pending"])
23
+ const LOCKED_ANSWERED_BY = new Set(["user", "default", "request"])
24
+
25
+ export const FIDELITY_INCOMPLETE = "fidelity_interview_incomplete"
26
+ const INCOMPLETE_HINT =
27
+ "A fidelity field is pending or missing in brief.md; answer the open question or record its default, then rerun."
28
+
29
+ const BLOCK_HEADING = /^## Fidelity\s*$/
30
+ const NEXT_HEADING = /^## /
31
+ const FIELD_LINE = /^([a-z][a-z ]*[a-z]): ([^()]+) \(answered_by: (user|default|request|pending)\)$/
32
+ const ASKED_LINE = /^asked:(.*)$/
33
+
34
+ function blockLines(markdown) {
35
+ const lines = String(markdown)
36
+ .split(/\r?\n/)
37
+ .map((line) => line.trimEnd())
38
+ const headings = lines.flatMap((line, index) => (BLOCK_HEADING.test(line) ? [index] : []))
39
+ if (headings.length === 0) return null
40
+ const [start, ...duplicates] = headings
41
+ const body = []
42
+ for (let index = start + 1; index < lines.length; index += 1) {
43
+ if (NEXT_HEADING.test(lines[index])) break
44
+ body.push({ text: lines[index], line: index + 1 })
45
+ }
46
+ return { body, duplicates: duplicates.map((index) => index + 1) }
47
+ }
48
+
49
+ function parseAsked(rest, line, text, errors) {
50
+ const names = rest
51
+ .split(",")
52
+ .map((name) => name.trim())
53
+ .filter(Boolean)
54
+ const unknown = names.filter((name) => !FIDELITY_FIELDS.includes(name))
55
+ if (unknown.length > 0) {
56
+ errors.push(`line ${line}: malformed asked line, unknown field ${unknown.join(", ")}: ${text}`)
57
+ return []
58
+ }
59
+ return names
60
+ }
61
+
62
+ function parseFieldLine({ text, line }, parsed, errors) {
63
+ const match = FIELD_LINE.exec(text)
64
+ if (!match) return errors.push(`line ${line}: malformed fidelity line: ${text}`)
65
+ const [, field, rawValue, answeredBy] = match
66
+ const value = rawValue.trim()
67
+ if (!FIDELITY_FIELDS.includes(field)) return errors.push(`line ${line}: malformed fidelity line, unknown field "${field}": ${text}`)
68
+ if (Object.hasOwn(parsed.fields, field)) return errors.push(`line ${line}: malformed fidelity line, duplicate field "${field}": ${text}`)
69
+ if ((answeredBy === "pending") !== (value === "pending")) {
70
+ return errors.push(`line ${line}: malformed fidelity line, value pending and answered_by: pending go together: ${text}`)
71
+ }
72
+ parsed.fields[field] = value
73
+ parsed.answered_by[field] = answeredBy
74
+ }
75
+
76
+ /**
77
+ * @param {string} briefMd the whole brief.md text
78
+ * @returns {{ present: boolean, fields: Record<string, string>, answered_by: Record<string, string>,
79
+ * asked: string[], missing: string[], pending: string[], errors: string[] }}
80
+ */
81
+ export function parseFidelityBlock(briefMd) {
82
+ const parsed = { present: false, fields: {}, answered_by: {}, asked: [], missing: [], pending: [], errors: [] }
83
+ const block = blockLines(briefMd)
84
+ if (block !== null) {
85
+ const { body, duplicates } = block
86
+ parsed.present = true
87
+ for (const line of duplicates) parsed.errors.push(`line ${line}: duplicate ## Fidelity block, only one is allowed`)
88
+ let sawAsked = false
89
+ for (const entry of body) {
90
+ if (entry.text.trim() === "") continue
91
+ const asked = ASKED_LINE.exec(entry.text)
92
+ if (!asked) {
93
+ parseFieldLine(entry, parsed, parsed.errors)
94
+ continue
95
+ }
96
+ if (sawAsked) parsed.errors.push(`line ${entry.line}: malformed asked line, duplicate: ${entry.text}`)
97
+ else parsed.asked = parseAsked(asked[1], entry.line, entry.text, parsed.errors)
98
+ sawAsked = true
99
+ }
100
+ }
101
+ parsed.missing = FIDELITY_FIELDS.filter((field) => !Object.hasOwn(parsed.fields, field))
102
+ parsed.pending = FIDELITY_FIELDS.filter((field) => parsed.answered_by[field] === "pending")
103
+ return parsed
104
+ }
105
+
106
+ /** Locked = no parse error and all seven fields present with answered_by user, default, or request. */
107
+ export function isLocked(parsed) {
108
+ if (!parsed || parsed.errors.length > 0) return false
109
+ return FIDELITY_FIELDS.every((field) => Object.hasOwn(parsed.fields, field) && LOCKED_ANSWERED_BY.has(parsed.answered_by[field]))
110
+ }
111
+
112
+ /** The brief's `meaning check` field turns the meaning verifier off only when it reads "off". */
113
+ export function meaningCheckEnabled(parsed) {
114
+ return !/^off\b/iu.test(parsed?.fields?.["meaning check"] ?? "")
115
+ }
116
+
117
+ function incompleteDefect(briefPath, parsed) {
118
+ const reasons = []
119
+ if (!parsed.present) reasons.push("no ## Fidelity block")
120
+ else if (parsed.missing.length > 0) reasons.push(`missing: ${parsed.missing.join(", ")}`)
121
+ if (parsed.pending.length > 0) reasons.push(`pending: ${parsed.pending.join(", ")}`)
122
+ return {
123
+ code: FIDELITY_INCOMPLETE,
124
+ severity: "blocker",
125
+ integrity: true,
126
+ selector: null,
127
+ line: null,
128
+ message: `${briefPath}: fidelity interview incomplete (${reasons.join("; ")})`,
129
+ hint: INCOMPLETE_HINT,
130
+ }
131
+ }
132
+
133
+ /**
134
+ * The lock check the reader-facing CLI stages run before writing anything.
135
+ * exitCode 0 = locked; 1 = pending, missing, or no brief (one `fidelity_interview_incomplete`
136
+ * defect); 2 = malformed block (usage error, `errors` name the lines, no defect).
137
+ * @param {string} briefPath
138
+ */
139
+ export function requireLockedBrief(briefPath) {
140
+ let markdown = ""
141
+ try {
142
+ markdown = readFileSync(briefPath, "utf8")
143
+ } catch (error) {
144
+ if (error?.code !== "ENOENT") throw error
145
+ }
146
+ const parsed = parseFidelityBlock(markdown)
147
+ if (parsed.errors.length > 0) return { locked: false, exitCode: 2, defects: [], errors: parsed.errors, parsed }
148
+ if (isLocked(parsed)) return { locked: true, exitCode: 0, defects: [], errors: [], parsed }
149
+ return { locked: false, exitCode: 1, defects: [incompleteDefect(briefPath, parsed)], errors: [], parsed }
150
+ }
@@ -0,0 +1,179 @@
1
+ import { describe, expect, test } from "bun:test"
2
+ import { mkdtempSync, rmSync, writeFileSync } from "node:fs"
3
+ import { tmpdir } from "node:os"
4
+ import { join } from "node:path"
5
+
6
+ import { FIDELITY_FIELDS, isLocked, parseFidelityBlock, requireLockedBrief } from "./fidelity-brief.mjs"
7
+
8
+ const fixture = (name: string) => join(import.meta.dir, "tests", "fixtures", name)
9
+ const lockedMd = await Bun.file(fixture("fidelity-brief-locked.md")).text()
10
+ const pendingMd = await Bun.file(fixture("fidelity-brief-pending.md")).text()
11
+
12
+ function withoutLine(markdown: string, prefix: string): string {
13
+ return markdown
14
+ .split("\n")
15
+ .filter((line) => !line.startsWith(prefix))
16
+ .join("\n")
17
+ }
18
+
19
+ describe("parseFidelityBlock", () => {
20
+ test("#given a complete block #when parsed #then seven fields are read and the brief is locked", () => {
21
+ // when
22
+ const parsed = parseFidelityBlock(lockedMd)
23
+ // then
24
+ expect(parsed.errors).toEqual([])
25
+ expect(Object.keys(parsed.fields).sort()).toEqual([...FIDELITY_FIELDS].sort())
26
+ expect(parsed.fields["as of"]).toBe("latest at run start, 2026-10-23")
27
+ expect(parsed.fields.languages).toBe("report ko; sources ko, en; quotes original with translation beside")
28
+ expect(parsed.answered_by["derived numbers"]).toBe("user")
29
+ expect(parsed.answered_by.fallback).toBe("request")
30
+ expect(parsed.asked).toEqual(["derived numbers"])
31
+ expect(isLocked(parsed)).toBe(true)
32
+ })
33
+
34
+ test("#given a block missing as of #when parsed #then it is not locked and the missing field is named", () => {
35
+ // when
36
+ const parsed = parseFidelityBlock(withoutLine(lockedMd, "as of:"))
37
+ // then
38
+ expect(isLocked(parsed)).toBe(false)
39
+ expect(parsed.missing).toEqual(["as of"])
40
+ expect(parsed.errors).toEqual([])
41
+ })
42
+
43
+ test("#given a field answered_by pending #when parsed #then it is not locked and not malformed", () => {
44
+ // when
45
+ const parsed = parseFidelityBlock(pendingMd)
46
+ // then
47
+ expect(parsed.errors).toEqual([])
48
+ expect(parsed.answered_by["derived numbers"]).toBe("pending")
49
+ expect(parsed.pending).toEqual(["derived numbers"])
50
+ expect(isLocked(parsed)).toBe(false)
51
+ })
52
+
53
+ test("#given a malformed field line #when parsed #then errors name the line as malformed", () => {
54
+ // given
55
+ const markdown = lockedMd.replace("meaning check: on (answered_by: default)", "meaning check: on")
56
+ // when
57
+ const parsed = parseFidelityBlock(markdown)
58
+ // then
59
+ expect(parsed.errors.length).toBe(1)
60
+ expect(parsed.errors[0]).toContain("malformed")
61
+ expect(parsed.errors[0]).toContain("meaning check: on")
62
+ expect(isLocked(parsed)).toBe(false)
63
+ })
64
+
65
+ test("#given grammar violations #when parsed #then each is a malformed error", () => {
66
+ const cases = [
67
+ lockedMd.replace("fallback: block-quote (answered_by: request)", "fallback: block-quote (answered_by: someone)"),
68
+ lockedMd.replace("meaning check: on (answered_by: default)", "meaning check: on (maybe) (answered_by: default)"),
69
+ lockedMd.replace("meaning check: on (answered_by: default)", "meaning check: on (answered_by: pending)"),
70
+ lockedMd.replace("asked: derived numbers", "asked: derived numbers\ntone: formal (answered_by: user)"),
71
+ lockedMd.replace("asked: derived numbers", "asked: derived numbers\nfallback: omit (answered_by: user)"),
72
+ lockedMd.replace("asked: derived numbers", "asked: tone"),
73
+ ]
74
+ for (const markdown of cases) expect(parseFidelityBlock(markdown).errors.length).toBe(1)
75
+ })
76
+
77
+ test("#given a brief with no Fidelity block #when parsed #then it is not locked and every field is missing", () => {
78
+ // when
79
+ const parsed = parseFidelityBlock("# Brief\n\n## Deliverable\nformat: pdf (answered_by: request)\n")
80
+ // then
81
+ expect(parsed.present).toBe(false)
82
+ expect(parsed.missing).toEqual([...FIDELITY_FIELDS])
83
+ expect(isLocked(parsed)).toBe(false)
84
+ })
85
+
86
+ test("#given field lines after the next heading #when parsed #then they are outside the block", () => {
87
+ // given
88
+ const markdown = withoutLine(lockedMd, "fallback:") + "\nfallback: omit (answered_by: user)\n"
89
+ // then
90
+ expect(parseFidelityBlock(markdown).missing).toEqual(["fallback"])
91
+ })
92
+
93
+ test("#given a second Fidelity block #when parsed #then the duplicate is an error", () => {
94
+ // given
95
+ const markdown = lockedMd + "\n## Fidelity\nas of: x (answered_by: user)\n"
96
+ // when
97
+ const parsed = parseFidelityBlock(markdown)
98
+ // then
99
+ expect(parsed.errors.length).toBe(1)
100
+ expect(parsed.errors[0]).toContain("duplicate")
101
+ expect(parsed.fields["as of"]).toBe("latest at run start, 2026-10-23")
102
+ expect(isLocked(parsed)).toBe(false)
103
+ })
104
+ })
105
+
106
+ describe("requireLockedBrief", () => {
107
+ function briefFile(contents: string | null): { path: string; cleanup: () => void } {
108
+ const dir = mkdtempSync(join(tmpdir(), "fidelity-brief-"))
109
+ const path = join(dir, "brief.md")
110
+ if (contents !== null) writeFileSync(path, contents)
111
+ return { path, cleanup: () => rmSync(dir, { recursive: true, force: true }) }
112
+ }
113
+
114
+ test("#given a locked brief #when required #then exit 0 and no defect", () => {
115
+ const brief = briefFile(lockedMd)
116
+ try {
117
+ expect(requireLockedBrief(brief.path)).toMatchObject({ locked: true, exitCode: 0, defects: [], errors: [] })
118
+ } finally {
119
+ brief.cleanup()
120
+ }
121
+ })
122
+
123
+ test("#given field lines with trailing spaces, a tab, and CRLF endings #when required #then they parse like trimmed lines", () => {
124
+ // given
125
+ const markdown = lockedMd
126
+ .replace("meaning check: on (answered_by: default)", "meaning check: on (answered_by: default) ")
127
+ .replace("fallback: block-quote (answered_by: request)", "fallback: block-quote (answered_by: request)\t")
128
+ .replace("as of: latest at run start, 2026-10-23 (answered_by: default)\n", "as of: latest at run start, 2026-10-23 (answered_by: default) \r\n")
129
+ const brief = briefFile(markdown)
130
+ try {
131
+ // when
132
+ const result = requireLockedBrief(brief.path)
133
+ // then
134
+ expect(result).toMatchObject({ locked: true, exitCode: 0, defects: [], errors: [] })
135
+ expect(result.parsed.fields.fallback).toBe("block-quote")
136
+ } finally {
137
+ brief.cleanup()
138
+ }
139
+ })
140
+
141
+ test("#given a second Fidelity block #when required #then exit 2 naming the duplicate", () => {
142
+ const brief = briefFile(lockedMd + "\n## Fidelity\nas of: x (answered_by: user)\n")
143
+ try {
144
+ const result = requireLockedBrief(brief.path)
145
+ expect(result).toMatchObject({ locked: false, exitCode: 2, defects: [] })
146
+ expect(result.errors[0]).toContain("duplicate ## Fidelity block")
147
+ } finally {
148
+ brief.cleanup()
149
+ }
150
+ })
151
+
152
+ test("#given a pending, partial, or absent brief #when required #then exit 1 with the single lock defect", () => {
153
+ for (const contents of [pendingMd, withoutLine(lockedMd, "as of:"), "# Brief\n", null]) {
154
+ const brief = briefFile(contents)
155
+ try {
156
+ const result = requireLockedBrief(brief.path)
157
+ expect(result.locked).toBe(false)
158
+ expect(result.exitCode).toBe(1)
159
+ expect(result.defects.length).toBe(1)
160
+ expect(result.defects[0]).toMatchObject({ code: "fidelity_interview_incomplete", severity: "blocker", integrity: true })
161
+ expect(typeof result.defects[0].message).toBe("string")
162
+ expect(typeof result.defects[0].hint).toBe("string")
163
+ } finally {
164
+ brief.cleanup()
165
+ }
166
+ }
167
+ })
168
+
169
+ test("#given a malformed brief #when required #then exit 2 with the parse errors and no defect", () => {
170
+ const brief = briefFile(lockedMd.replace("meaning check: on (answered_by: default)", "meaning check: on"))
171
+ try {
172
+ const result = requireLockedBrief(brief.path)
173
+ expect(result).toMatchObject({ locked: false, exitCode: 2, defects: [] })
174
+ expect(result.errors[0]).toContain("malformed")
175
+ } finally {
176
+ brief.cleanup()
177
+ }
178
+ })
179
+ })
@@ -0,0 +1,89 @@
1
+ // fidelity-claims.mjs - the claim-kind checks of the deterministic fidelity gate.
2
+ //
3
+ // A mark (the data-claim-kind attribute) binds per cited fact: a data-fact element takes the kind
4
+ // of its nearest mark (its own, else the closest marked ancestor), and only that kind is checked
5
+ // against its ledger row (claim-kinds.mjs CLAIM_KINDS); a row that fails is the kind's defect
6
+ // (unsupported_verification for `verified`). A fact under two marks of different kinds is a
7
+ // claim_kind_mismatch, and a mark that owns no fact of its own kind (every fact under it cites
8
+ // nothing or is shadowed by another kind) is its kind's defect: shadowing never satisfies a parent.
9
+ // An unmarked fact citing a row neither fact nor quote accepts (an assumed row) is a
10
+ // claim_kind_mismatch naming the mark it needs. A sentence that matches a kind's unmarked patterns warns (minor, verification_claim_unmarked)
11
+ // unless every fact it cites is marked that kind; a sentence citing no fact warns unless its block
12
+ // is marked. No model, no network.
13
+
14
+ import { CLAIM_ATTRIBUTE, CLAIM_KINDS, claimSatisfied, unmarkedKinds, unmarkedRequires } from "./claim-kinds.mjs"
15
+ import { proseSentences } from "./fidelity-binding.mjs"
16
+ import { clip } from "./fidelity-trace.mjs"
17
+ import { proseText } from "./html-lite.mjs"
18
+
19
+ const marked = (node) => node.attrs?.[CLAIM_ATTRIBUTE] !== undefined
20
+ const kindOf = (node) => node.attrs[CLAIM_ATTRIBUTE].trim()
21
+ const isFact = (node) => node.attrs["data-fact"] !== undefined
22
+
23
+ /** The marks over a node, nearest first: its own mark, then each marked ancestor. */
24
+ function marksOver(node) {
25
+ const marks = []
26
+ for (let cur = node; cur; cur = cur.parent) if (marked(cur)) marks.push(cur)
27
+ return marks
28
+ }
29
+
30
+ const nearestKind = (node) => {
31
+ const [mark] = marksOver(node)
32
+ return mark ? kindOf(mark) : null
33
+ }
34
+
35
+ /** Whether `mark` binds `fact`: every mark from the fact up to `mark` names the same kind. */
36
+ function binds(mark, fact) {
37
+ const marks = marksOver(fact)
38
+ const at = marks.indexOf(mark)
39
+ return at >= 0 && marks.slice(0, at + 1).every((cur) => kindOf(cur) === kindOf(mark))
40
+ }
41
+
42
+ /**
43
+ * @param {object} ctx buildContext(...) from gates-static.mjs
44
+ * @param {Map<string, object>} rows ledger rows by fact_id
45
+ * @param {(row: object) => string | undefined} provenanceOf meta.json kind of the snapshot holding the row's own locator artifact
46
+ * @param {(code: string, node: object, message: string) => void} flag
47
+ */
48
+ export function claimChecks(ctx, rows, provenanceOf, flag) {
49
+ for (const node of ctx.all.filter(marked)) {
50
+ const name = kindOf(node)
51
+ const entry = CLAIM_KINDS[name]
52
+ if (!entry) {
53
+ flag("claim_kind_mismatch", node, `${CLAIM_ATTRIBUTE}="${name}" is not a claim kind (${Object.keys(CLAIM_KINDS).join(", ")})`)
54
+ continue
55
+ }
56
+ const cited = [node, ...ctx.util.descendants(node)].filter(isFact)
57
+ if (cited.some((fact) => binds(node, fact))) continue
58
+ const why = cited.length === 0 ? "cites no fact" : "cites no fact of its own (a nested mark of another kind never satisfies it)"
59
+ flag(entry.defect, node, `"${clip(proseText(node))}" is marked ${name} but ${why}`)
60
+ }
61
+ for (const fact of ctx.all.filter(isFact)) {
62
+ const [nearest, ...outer] = marksOver(fact)
63
+ const id = fact.attrs["data-fact"]
64
+ if (!nearest) {
65
+ const need = unmarkedRequires(rows.get(id))
66
+ const mark = need && `{{${CLAIM_KINDS[need].prefix}:${id}}} (${CLAIM_ATTRIBUTE}="${need}")`
67
+ if (need) flag("claim_kind_mismatch", fact, `${id} is a ${rows.get(id).kind} row cited with no mark; an unmarked numeral reads as a fact, so mark it ${mark}`)
68
+ continue
69
+ }
70
+ const name = kindOf(nearest)
71
+ const conflict = outer.find((mark) => kindOf(mark) !== name)
72
+ if (conflict) flag("claim_kind_mismatch", fact, `${id} is marked ${name} inside a ${kindOf(conflict)} mark; a fact carries one claim kind`)
73
+ const entry = CLAIM_KINDS[name]
74
+ const row = rows.get(id)
75
+ if (!entry || !row || claimSatisfied(name, row, provenanceOf(row))) continue
76
+ const source = entry.sourceKinds ? ` and not one whose own locator artifact sits in a ${entry.sourceKinds.join(" or ")} snapshot` : ""
77
+ flag(entry.defect, fact, `${row.fact_id} is marked ${name}, but it is a ${row.kind} row${source}`)
78
+ }
79
+ for (const block of proseSentences(ctx)) {
80
+ if (block.kind === "sources" || block.kind === "toc") continue
81
+ for (const sentence of block.sentences) {
82
+ for (const name of unmarkedKinds(sentence.text)) {
83
+ const covered = sentence.facts.length > 0 ? sentence.facts.every((fact) => nearestKind(fact.node) === name) : nearestKind(block.owner) === name
84
+ if (covered) continue
85
+ flag(CLAIM_KINDS[name].unmarked.code, block.owner, `"${clip(sentence.text)}" reads as a ${name} claim but cites a fact with no ${CLAIM_ATTRIBUTE}="${name}" mark`)
86
+ }
87
+ }
88
+ }
89
+ }
@@ -0,0 +1,145 @@
1
+ // fidelity-lexicon.mjs - closed-list lexical checks over (source quote, report sentence) pairs.
2
+ //
3
+ // lexiconChecks compares each pair with the lexicon.mjs lists: hedge_dropped, direction_flipped,
4
+ // causality_added, qualifier_dropped (six-token window around the fact span; a :q rendering puts
5
+ // the qualifier inside the span), scope_widened_warn, and the acronym-aware name_outside_quote.
6
+ // The hedge-position heuristic never yields a code; it only sets force_strong for the meaning
7
+ // verifier, as do name_outside_quote and scope_widened_warn. Latin terms match whole words (terms
8
+ // of four or more letters also match their inflections); Korean terms match as substrings, single
9
+ // syllables as tokens. No model, no IO. Plain Node ESM; Korean strings are \u escapes.
10
+
11
+ import { CAUSAL, CONCESSIVE, DIRECTION, HEDGES, NEGATION, QUALIFIERS, SCOPE_NARROW_TO_BROAD } from "./lexicon.mjs"
12
+ import { normalizeText } from "./text-normalize.mjs"
13
+ import { normalizeUnit } from "./units.mjs"
14
+
15
+ /** Ledger qualifier terms -> their surface forms in either language (closed mapping). */
16
+ export const QUALIFIER_FORMS = Object.freeze({
17
+ upper_inclusive: ["up to", "at most", "\uCD5C\uB300", "\uC774\uD558", "\uAE4C\uC9C0"],
18
+ lower_inclusive: ["at least", "\uCD5C\uC18C", "\uC774\uC0C1"],
19
+ approximate: ["about", "approximately", "\uC57D", "\uB0B4\uC678"],
20
+ near: ["nearly"],
21
+ lower_exclusive: ["over", "more than", "\uCD08\uACFC"],
22
+ upper_exclusive: ["under", "less than", "\uBBF8\uB9CC"],
23
+ })
24
+ /** Irregular and synonym direction forms the stem match cannot reach ("rose", "reduced"). */
25
+ const DIRECTION_FORMS = { up: ["rose", "risen", "rising", "grew", "grown", "gain"], down: ["reduc", "fell", "fallen", "falling", "drop", "lower"] }
26
+ const KOREAN = /[\uAC00-\uD7A3]/
27
+ /** True when the term holds a Hangul syllable (Korean terms match by the Korean rules below). */
28
+ export const isKoreanTerm = (term) => KOREAN.test(term)
29
+ /** The guarded hedge and qualifier vocabulary: the union of HEDGES, QUALIFIERS and every QUALIFIER_FORMS surface form. */
30
+ export const guardedTerms = () => [...new Set([...HEDGES, ...QUALIFIERS, ...Object.values(QUALIFIER_FORMS).flat()])]
31
+ const ACRONYM = /(?<![\p{L}\p{N}])[A-Z]{2,6}(?![\p{L}\p{N}])/gu
32
+ const WINDOW = 6
33
+ const cache = new Map()
34
+
35
+ const escape = (text) => text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")
36
+
37
+ /**
38
+ * The lexical pattern engine. mode: "stem" (inflections, Korean one-syllable token prefix), "exact",
39
+ * "word", "suffix" (Korean attached to a value), "guard" (single Latin words of four or more letters
40
+ * take inflections, multiword terms never do; Korean terms match as plain substrings).
41
+ */
42
+ export function lexicalPattern(term, mode) {
43
+ const key = `${mode}\u0001${term}`
44
+ if (cache.has(key)) return cache.get(key)
45
+ let source
46
+ if (!KOREAN.test(term)) {
47
+ const stems = term.length >= 4 && (mode === "stem" || (mode === "guard" && !term.includes(" ")))
48
+ source = `(?<![\\p{L}\\p{N}])${escape(term).replace(/ /g, "\\s+")}${stems ? "\\p{L}*" : ""}(?![\\p{L}\\p{N}])`
49
+ } else if (mode === "guard") source = escape(term)
50
+ else if (mode === "suffix") source = `(?<=^|[\\s\\p{P}\\p{N}A-Za-z])${escape(term)}(?=$|[\\s\\p{P}])`
51
+ else if ([...term].length >= 2 || mode === "word") source = escape(term)
52
+ else source = `(?<=^|[\\s\\p{P}])${escape(term)}${mode === "stem" ? "" : "(?=$|[\\s\\p{P}])"}`
53
+ const re = new RegExp(source, "giu")
54
+ cache.set(key, re)
55
+ return re
56
+ }
57
+
58
+ function positions(text, terms, mode) {
59
+ const out = []
60
+ for (const term of terms) for (const match of text.matchAll(lexicalPattern(term, mode))) out.push({ term, at: match.index })
61
+ return out.sort((a, b) => a.at - b.at)
62
+ }
63
+
64
+ const has = (text, terms, mode) => positions(text, terms, mode).length > 0
65
+ const tokens = (text) => text.split(/\s+/).filter(Boolean)
66
+ const stripQualifiers = (text) => QUALIFIERS.reduce((rest, term) => rest.replace(lexicalPattern(term, "exact"), " "), text)
67
+
68
+ function directions(text) {
69
+ const rest = stripQualifiers(text)
70
+ return {
71
+ up: has(rest, DIRECTION.up, "stem") || has(rest, DIRECTION_FORMS.up, "stem"),
72
+ down: has(rest, DIRECTION.down, "stem") || has(rest, DIRECTION_FORMS.down, "stem"),
73
+ }
74
+ }
75
+
76
+ /** The fact span plus up to six tokens on each side, never crossing the sentence. */
77
+ function qualifierWindow(sentence, span) {
78
+ const [start, end] = span
79
+ return [...tokens(sentence.slice(0, start)).slice(-WINDOW), sentence.slice(start, end), ...tokens(sentence.slice(end)).slice(0, WINDOW)].join(" ")
80
+ }
81
+
82
+ function qualifierDropped(pair) {
83
+ if (!pair.qualifier || !Array.isArray(pair.span)) return false
84
+ const forms = QUALIFIER_FORMS[pair.qualifier] ?? [pair.qualifier]
85
+ const window = qualifierWindow(pair.sentence, pair.span)
86
+ return !forms.some((form) => has(window, [form], KOREAN.test(form) ? "suffix" : "exact"))
87
+ }
88
+
89
+ /** A hedge before the sentence's last concessive while the quote hedges its own final clause. */
90
+ function hedgeMoved(quote, sentence) {
91
+ const lastConcessive = (text) => positions(text, CONCESSIVE, "word").at(-1)?.at ?? -1
92
+ const sentenceHedge = positions(sentence, HEDGES, "stem")[0]?.at
93
+ const quoteHedge = positions(quote, HEDGES, "stem").at(-1)?.at
94
+ const sentenceConcessive = lastConcessive(sentence)
95
+ return sentenceHedge !== undefined && quoteHedge !== undefined && sentenceHedge < sentenceConcessive && quoteHedge > lastConcessive(quote)
96
+ }
97
+
98
+ function pairFindings(pair) {
99
+ const quote = normalizeText(pair.quote)
100
+ const sentence = normalizeText(pair.sentence)
101
+ const found = []
102
+ const add = (code, message) => found.push({ pair_id: pair.pair_id, code, message })
103
+ const dropped = qualifierDropped(pair)
104
+ if (dropped) add("qualifier_dropped", `the source bounds the value (${pair.qualifier}) and the sentence drops it`)
105
+ const sourceHedges = positions(quote, HEDGES, "stem")
106
+ const qualifierOnly = sourceHedges.every(({ term }) => QUALIFIERS.includes(term))
107
+ if (sourceHedges.length > 0 && !has(sentence, HEDGES, "stem") && !(dropped && qualifierOnly)) {
108
+ add("hedge_dropped", `the source hedges ("${sourceHedges[0].term}") and the sentence does not`)
109
+ }
110
+ const [from, to] = [directions(quote), directions(sentence)]
111
+ const negated = has(quote, NEGATION, "word") || has(sentence, NEGATION, "word")
112
+ if (!negated && ((from.up && !from.down && to.down && !to.up) || (from.down && !from.up && to.up && !to.down))) {
113
+ add("direction_flipped", `the source says ${from.up ? "up" : "down"} and the sentence says ${to.up ? "up" : "down"}`)
114
+ }
115
+ const causal = positions(sentence, CAUSAL, "word").find(({ term }) => !has(quote, [term], "word"))
116
+ if (causal) add("causality_added", `"${causal.term}" states a cause the source does not`)
117
+ const context = [quote, ...(pair.context ?? []).map(normalizeText)]
118
+ for (const [narrow, broad] of SCOPE_NARROW_TO_BROAD) {
119
+ if (has(quote, [narrow], "word") && has(sentence, [broad], "word") && !context.some((text) => has(text, [broad], "word"))) {
120
+ add("scope_widened_warn", `the source says "${narrow}" and the sentence says "${broad}"`)
121
+ }
122
+ }
123
+ const acronyms = pair.acronyms ?? {}
124
+ const quoteNames = new Set(quote.match(ACRONYM) ?? [])
125
+ const stray = (sentence.match(ACRONYM) ?? []).find((name) => !quoteNames.has(name) && !Object.hasOwn(acronyms, name) && !normalizeUnit(name))
126
+ if (stray) add("name_outside_quote", `"${stray}" is in neither the quote nor the source's acronym table`)
127
+ return found
128
+ }
129
+
130
+ /**
131
+ * @param {{ pair_id: string, quote: string, sentence: string, span?: [number, number] | null,
132
+ * qualifier?: string | null, acronyms?: Record<string, string>, context?: string[] }[]} pairs
133
+ * @returns {{ findings: { pair_id: string, code: string, message: string }[], forceStrong: string[] }}
134
+ */
135
+ export function lexiconChecks(pairs) {
136
+ const findings = []
137
+ const forceStrong = []
138
+ for (const pair of pairs) {
139
+ const found = pairFindings(pair)
140
+ findings.push(...found)
141
+ const routed = found.some(({ code }) => code === "name_outside_quote" || code === "scope_widened_warn")
142
+ if (routed || hedgeMoved(normalizeText(pair.quote), normalizeText(pair.sentence))) forceStrong.push(pair.pair_id)
143
+ }
144
+ return { findings, forceStrong }
145
+ }