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,191 @@
1
+ // fact-commands-meaning.mjs - `fact pairs` and `fact meaning-check`, the script half of the
2
+ // separate-session meaning verifier. fact pairs writes the (source, report sentence) pairs a
3
+ // reader lane gets and nothing else; it honours the brief's `meaning check` field and writes
4
+ // `{ skipped: true }` when it is off. fact meaning-check ingests the readers' verdict files
5
+ // (meaning-check.mjs), writes fidelity-meaning-defects.json (a fidelity record bound to the pairs'
6
+ // artifact_sha256 and report, through writeFidelityRecord), and records the run in
7
+ // fidelity-calibration.jsonl: a header with the promotion criterion on first write, then per run
8
+ // one run line plus one line per flagged pair. The run id hashes the inputs only, so a rerun or a
9
+ // --dispute rerun of the same inputs replaces that run's lines in place (disputes carry forward)
10
+ // instead of counting a second run. Neither command calls a model or the network.
11
+
12
+ import { existsSync, readFileSync } from "node:fs"
13
+ import { dirname, join, resolve } from "node:path"
14
+
15
+ import { CliError, parseArgs, readJsonFile, sha256Of, writeJsonAtomic, writeTextAtomic } from "./cli-support.mjs"
16
+ import { countDefects } from "./contracts.mjs"
17
+ import { markdownAsHtml, readEdition } from "./fact-commands-verify.mjs"
18
+ import { readLedger } from "./fact-ledger.mjs"
19
+ import { validateLedger } from "./fact-ledger-schema.mjs"
20
+ import { extractPairs, PAIR_KINDS } from "./fact-pairs.mjs"
21
+ import { FIDELITY_INCOMPLETE, meaningCheckEnabled, requireLockedBrief } from "./fidelity-brief.mjs"
22
+ import { MEANING_RECORD, writeFidelityRecord } from "./fidelity-records.mjs"
23
+ import { blocksDelivery } from "./gates-fidelity.mjs"
24
+ import { ingestVerdicts, unverifiedDefects, VERDICT_SCHEMA } from "./meaning-check.mjs"
25
+
26
+ export const PAIRS_FILE = "pairs.json"
27
+ export const CALIBRATION_FILE = "fidelity-calibration.jsonl"
28
+ /** Starting values, recorded in the calibration log header; changed only with evidence. */
29
+ export const PROMOTION = Object.freeze({ code: "meaning_drift_confirmed", to: "integrity", min_runs: 20, max_disputed_to_confirmed: 0.1, false_block_window: 20, max_report_false_blocks_in_window: 2 })
30
+
31
+ /** The command status: fail when any defect blocks delivery under the registry policy. */
32
+ export const meaningStatus = (defects) => (defects.some(blocksDelivery) ? "fail" : "pass")
33
+
34
+ const sessionOf = (flags) => {
35
+ const dir = flags["session-dir"] ?? process.env.SESSION_DIR
36
+ return dir && dir !== true ? resolve(dir) : null
37
+ }
38
+ const inSession = (session, name, flag) => {
39
+ if (session === null) throw new CliError(`${flag} is required without --session-dir (or SESSION_DIR)`)
40
+ return join(session, name)
41
+ }
42
+
43
+ function readLedgerFrom(flags, session) {
44
+ if (flags.ledger === undefined) {
45
+ if (session === null) throw new CliError("--ledger is required without --session-dir (or SESSION_DIR)")
46
+ return readLedger(session)
47
+ }
48
+ const ledger = readJsonFile(flags.ledger)
49
+ const errors = validateLedger(ledger)
50
+ if (errors.length > 0) throw new CliError(`${flags.ledger} is not a valid fact ledger: ${errors[0]}`)
51
+ return ledger
52
+ }
53
+
54
+ function briefState(flags, session) {
55
+ const path = flags.brief ?? inSession(session, "brief.md", "--brief")
56
+ const lock = requireLockedBrief(path)
57
+ if (lock.exitCode === 2) throw new CliError(`${path}: malformed ## Fidelity block: ${lock.errors.join("; ")}`)
58
+ return { path, locked: lock.locked, off: !meaningCheckEnabled(lock.parsed) }
59
+ }
60
+
61
+ function runPairs(args) {
62
+ const [report] = args.positionals
63
+ const { flags } = args
64
+ if (!report) throw new CliError("usage: fact pairs <report> [--format html|md] [--ledger F] [--claims claims.json] [--changed-since <fill-map.json>] [--brief brief.md] [--out pairs.json]")
65
+ const session = sessionOf(flags)
66
+ const out = resolve(flags.out ?? inSession(session, PAIRS_FILE, "--out"))
67
+ const brief = briefState(flags, session)
68
+ if (!brief.locked) {
69
+ const json = { command: "fact pairs", status: "fail", exitCode: 1, error: "fidelity_interview_incomplete", message: `${brief.path} has no locked ## Fidelity block` }
70
+ return { json, summary: `fact pairs: ${json.message}`, exitCode: 1 }
71
+ }
72
+ if (brief.off) {
73
+ const record = { schemaVersion: 1, skipped: true, reason: "meaning check: off", pairs: [] }
74
+ writeJsonAtomic(out, record)
75
+ return { json: { command: "fact pairs", status: "skipped", exitCode: 0, data: { out, ...record } }, summary: "fact pairs: skipped (meaning check: off)", exitCode: 0 }
76
+ }
77
+ const edition = readEdition(report, flags)
78
+ const claimsPath = flags.claims ?? (session === null ? null : join(session, "claims.json"))
79
+ const claims = claimsPath !== null && (flags.claims !== undefined || existsSync(claimsPath)) ? readJsonFile(claimsPath) : null
80
+ const changed = flags["changed-since"] === undefined ? undefined : readJsonFile(flags["changed-since"]).entries
81
+ const ledger = readLedgerFrom(flags, session)
82
+ const html = edition.format === "md" ? markdownAsHtml(edition.text, edition.entries, ledger).html : edition.text
83
+ const pairs = extractPairs(html, ledger, claims, changed)
84
+ const counts = { total: pairs.length, force_strong: pairs.filter((pair) => pair.force_strong).length }
85
+ for (const kind of PAIR_KINDS) counts[kind] = pairs.filter((pair) => pair.kind === kind).length
86
+ const record = { schemaVersion: 1, skipped: false, report: resolve(report), format: edition.format, artifact_sha256: sha256Of(edition.text), pairs_sha256: sha256Of(JSON.stringify(pairs)), counts, verdict_schema: VERDICT_SCHEMA, pairs }
87
+ writeJsonAtomic(out, record)
88
+ const summary = `fact pairs: ${counts.total} pair(s), ${counts.force_strong} routed to the strong reader`
89
+ return { json: { command: "fact pairs", status: "pass", exitCode: 0, data: { out, ...record, pairs: undefined } }, summary, exitCode: 0 }
90
+ }
91
+
92
+ /** Every line of the log after the header, in file order; a corrupt line is a usage error naming path:line. */
93
+ function calibrationRows(path) {
94
+ if (!existsSync(path)) return []
95
+ const rows = []
96
+ for (const [index, line] of readFileSync(path, "utf8").split("\n").entries()) {
97
+ if (line.trim() === "") continue
98
+ try {
99
+ rows.push(JSON.parse(line))
100
+ } catch (error) {
101
+ throw new CliError(`${path}:${index + 1}: corrupt calibration log line (${error.message}); repair or move the file`)
102
+ }
103
+ }
104
+ return rows.filter((row) => row.header !== true)
105
+ }
106
+
107
+ /** The promotion criterion evaluated over the log: runs, disputed-to-confirmed, report-level false blocks. */
108
+ export function promotionEligibility(rows) {
109
+ const runs = [...new Set(rows.map((row) => row.run_id))]
110
+ const confirmed = rows.filter((row) => row.run !== true && row.confirmed)
111
+ const disputed = confirmed.filter((row) => row.disputed).length
112
+ const ratio = confirmed.length === 0 ? null : disputed / confirmed.length
113
+ const falseBlocks = runs.slice(-PROMOTION.false_block_window).filter((run) => {
114
+ const blocking = confirmed.filter((row) => row.run_id === run)
115
+ return blocking.length > 0 && blocking.every((row) => row.disputed)
116
+ }).length
117
+ const eligible = runs.length >= PROMOTION.min_runs && ratio !== null && ratio < PROMOTION.max_disputed_to_confirmed && falseBlocks <= PROMOTION.max_report_false_blocks_in_window
118
+ return { runs: runs.length, confirmed: confirmed.length, disputed_confirmed: disputed, disputed_to_confirmed: ratio, report_false_blocks_in_window: falseBlocks, eligible }
119
+ }
120
+
121
+ /**
122
+ * Writes this run's lines: one run line (so a run with no flagged pair still counts) and one line per
123
+ * flagged pair. A run id already in the log is replaced in place, keeping its position in the false
124
+ * block window and any earlier dispute on a pair the new lines do not dispute.
125
+ */
126
+ function recordCalibration(path, lines, runId, pairsSha) {
127
+ const before = calibrationRows(path)
128
+ const previous = before.filter((row) => row.run_id === runId)
129
+ const disputes = new Map(previous.filter((row) => row.disputed).map((row) => [row.pair_id, row.reason]))
130
+ const pairRows = lines.map((line) => {
131
+ const row = { ...JSON.parse(line), run_id: runId, pairs_sha256: pairsSha }
132
+ return !row.disputed && disputes.has(row.pair_id) ? { ...row, disputed: true, reason: disputes.get(row.pair_id) } : row
133
+ })
134
+ const rows = [{ run: true, run_id: runId, pairs_sha256: pairsSha, flagged: pairRows.length }, ...pairRows]
135
+ const at = previous.length === 0 ? before.length : before.indexOf(previous[0])
136
+ const after = [...before.slice(0, at).filter((row) => row.run_id !== runId), ...rows, ...before.slice(at).filter((row) => row.run_id !== runId)]
137
+ const header = { header: true, schemaVersion: 1, promotion: PROMOTION, note: "starting values; change them only with evidence" }
138
+ writeTextAtomic(path, `${[header, ...after].map((row) => JSON.stringify(row)).join("\n")}\n`)
139
+ return { written: pairRows.length, replaced: previous.length > 0, rows: after }
140
+ }
141
+
142
+ /** A `{ skipped: true }` pairs file is accepted only when the locked brief turns the meaning check off. */
143
+ function skippedRefusal(flags, session) {
144
+ const brief = briefState(flags, session)
145
+ if (!brief.locked) return { error: FIDELITY_INCOMPLETE, message: `${brief.path} has no locked ## Fidelity block, so a skipped meaning check cannot be accepted` }
146
+ if (!brief.off) return { error: "meaning_check_required", message: `${flags.pairs} says skipped but ${brief.path} has meaning check on; rerun fact pairs` }
147
+ return null
148
+ }
149
+
150
+ function runMeaningCheck(args) {
151
+ const { flags } = args
152
+ const usage = "usage: fact meaning-check --pairs pairs.json --verdicts cheap.json [--verdicts strong.json] [--dispute P<n> --reason TEXT] [--run-id ID] [--calibration F] [--out F]"
153
+ if (flags.pairs === undefined || !Array.isArray(flags.verdicts)) throw new CliError(usage)
154
+ const session = sessionOf(flags)
155
+ const pairsText = readFileSync(flags.pairs, "utf8")
156
+ const pairsDoc = readJsonFile(flags.pairs)
157
+ if (pairsDoc?.skipped === true) {
158
+ const refusal = skippedRefusal(flags, session)
159
+ if (refusal !== null) return { json: { command: "fact meaning-check", status: "fail", exitCode: 1, ...refusal }, summary: `fact meaning-check: ${refusal.message}`, exitCode: 1 }
160
+ return { json: { command: "fact meaning-check", status: "skipped", exitCode: 0, defects: [] }, summary: JSON.stringify({ command: "fact meaning-check", status: "skipped" }), exitCode: 0 }
161
+ }
162
+ if (typeof pairsDoc?.artifact_sha256 !== "string" || typeof pairsDoc.report !== "string") throw new CliError(`${flags.pairs} names no artifact_sha256 and report, so the meaning record cannot be bound to an edition; rerun fact pairs`)
163
+ for (const file of flags.verdicts) readJsonFile(file)
164
+ const verdictTexts = flags.verdicts.map((file) => readFileSync(file, "utf8"))
165
+ const dispute = flags.dispute === undefined ? undefined : { pair_id: flags.dispute, reason: args.required("reason") }
166
+ let result
167
+ try {
168
+ result = ingestVerdicts(pairsDoc, flags.verdicts, dispute ? { dispute } : {})
169
+ } catch (error) {
170
+ throw new CliError(`invalid verdict input: ${error.message}`)
171
+ }
172
+ const out = resolve(flags.out ?? inSession(session, MEANING_RECORD, "--out"))
173
+ const calibration = resolve(flags.calibration ?? join(session ?? dirname(out), CALIBRATION_FILE))
174
+ const runId = flags["run-id"] ?? sha256Of([pairsText, ...verdictTexts].join("\0")).slice(0, 16)
175
+ const pairsSha = pairsDoc.pairs_sha256 ?? sha256Of(pairsText)
176
+ const log = recordCalibration(calibration, result.calibrationLines, runId, pairsSha)
177
+ const defects = [...result.defects, ...unverifiedDefects(result.unverified)]
178
+ const counts = countDefects(defects)
179
+ const status = meaningStatus(defects)
180
+ const exitCode = status === "fail" ? 1 : 0
181
+ const record = writeFidelityRecord(out, "meaning", { command: "fact meaning-check", status, exitCode, artifact_sha256: pairsDoc.artifact_sha256, report: pairsDoc.report, run_id: runId, pairs_sha256: pairsSha, defects, counts, dropped: result.dropped, unverified: result.unverified, calibration_path: calibration, calibration_written: log.written, calibration_replaced: log.replaced, promotion_eligibility: promotionEligibility(log.rows) })
182
+ const confirmed = result.defects.filter((defect) => defect.code === "meaning_drift_confirmed").length
183
+ const summary = JSON.stringify({ command: "fact meaning-check", status, defects: defects.length, confirmed, warn: result.defects.length - confirmed, dropped: result.dropped.length, unverified: result.unverified.length })
184
+ return { json: { ...record, out }, summary, exitCode }
185
+ }
186
+
187
+ /** @param {string[]} argv the arguments after `fact pairs` or `fact meaning-check` */
188
+ export function runFactMeaning(sub, argv) {
189
+ const args = parseArgs(argv, { repeatable: ["verdicts"] })
190
+ return sub === "pairs" ? runPairs(args) : runMeaningCheck(args)
191
+ }
@@ -0,0 +1,164 @@
1
+ // fact-commands-verify.mjs - `fact verify` and the Markdown projection it checks.
2
+ //
3
+ // fact verify runs the deterministic fidelity gate over one filled HTML or Markdown edition and
4
+ // writes a fidelity record (default $SESSION_DIR/fidelity-defects.json, through
5
+ // writeFidelityRecord) carrying the artifact's sha256. It
6
+ // exits 1 on any blocker or major defect (the orchestrator then records `outcome gate fidelity
7
+ // fail`); minors alone exit 0 and are listed. A Markdown edition is read through md-lite into an
8
+ // escaped HTML projection (same line numbers) and checked with its fill map, which is the only
9
+ // record of where its values sit; LaTeX is verified through its Markdown source.
10
+ //
11
+ // The delivery barrier that reads these records (and every other fidelity record) is
12
+ // fidelity-records.mjs.
13
+
14
+ import { existsSync, readFileSync } from "node:fs"
15
+ import { dirname, join, resolve } from "node:path"
16
+
17
+ import { CliError, readJsonFile, sha256Of } from "./cli-support.mjs"
18
+ import { countDefects } from "./contracts.mjs"
19
+ import { recompute } from "./fact-derive.mjs"
20
+ import { readLedger } from "./fact-ledger.mjs"
21
+ import { FIDELITY_RECORD, writeFidelityRecord } from "./fidelity-records.mjs"
22
+ import { blocksDelivery, checkFidelity } from "./gates-fidelity.mjs"
23
+ import { parseMarkdown } from "./md-lite.mjs"
24
+ import { escapeHtml, parseRenderedQuote, renderQuote, renderValueHtml } from "./placeholder.mjs"
25
+ import { sessionDirOf } from "./source-commands.mjs"
26
+
27
+ function readText(path) {
28
+ try {
29
+ return readFileSync(path, "utf8")
30
+ } catch (error) {
31
+ throw new CliError(`cannot read ${path}: ${error.code ?? error.message}`)
32
+ }
33
+ }
34
+
35
+ /**
36
+ * md-lite block kind -> its projection: the element tag (null = h<level>) and, for a run of one kind,
37
+ * the container that wraps the run. Code and table rows also take the specialized bodies below.
38
+ */
39
+ export const MD_HTML_BLOCK_RULES = Object.freeze({
40
+ heading: Object.freeze({ tag: null }),
41
+ paragraph: Object.freeze({ tag: "p" }),
42
+ list_item: Object.freeze({ tag: "li", group: Object.freeze(["<ul>", "</ul>"]) }),
43
+ blockquote: Object.freeze({ tag: "blockquote" }),
44
+ code: Object.freeze({ tag: "code" }),
45
+ table_row: Object.freeze({ tag: "tr", group: Object.freeze(["<table>", "</table>"]) }),
46
+ })
47
+
48
+ /** Escaped text with inline links and code spans kept as elements, so a URL is never read as prose. */
49
+ export const projectMarkdownInline = (text) =>
50
+ escapeHtml(text)
51
+ .replace(/\[([^\]]+)\]\(([^)\s]+)\)/gu, '<a href="$2">$1</a>')
52
+ .replace(/`([^`]+)`/gu, "<code>$1</code>")
53
+
54
+ /** The element the HTML fill writes for one fill-map entry, or null when its bytes were edited since. */
55
+ function entryHtml(text, entry, rows) {
56
+ const body = text.slice(entry.start, entry.end)
57
+ if (body !== entry.rendered) return null
58
+ const row = rows.get(entry.fact_id)
59
+ if (entry.translates) return `<span data-translates="${escapeHtml(entry.translates)}">${projectMarkdownInline(body.replace(/^<!--[\s\S]*?-->\s*/u, ""))}</span>`
60
+ if (!row) return `<data data-fact="${escapeHtml(entry.fact_id)}">${escapeHtml(body)}</data>`
61
+ const quoted = row.kind === "quote" ? parseRenderedQuote(body) : null
62
+ if (quoted) return renderQuote(row, entry.selector ?? "full", quoted.text, "html")
63
+ return row.kind === "quote" ? null : renderValueHtml(row, body)
64
+ }
65
+
66
+ /** [from, to) of the filled text as HTML, each intact fill-map entry rebuilt as its fill element. */
67
+ function spanHtml(text, from, to, entries, rows, stale) {
68
+ let html = ""
69
+ let cursor = from
70
+ for (const entry of entries) {
71
+ if (entry.start < cursor || entry.end > to || entry.start < from || entry.replaced !== undefined) continue
72
+ const element = entryHtml(text, entry, rows)
73
+ if (element === null) continue
74
+ stale.delete(entry)
75
+ html += projectMarkdownInline(text.slice(cursor, entry.start)) + element
76
+ cursor = entry.end
77
+ }
78
+ return html + projectMarkdownInline(text.slice(cursor, to))
79
+ }
80
+
81
+ /**
82
+ * An escaped HTML projection of a filled Markdown edition: one element per md-lite block, each on
83
+ * the source line it starts on, lists and table rows grouped. Never a rendering for readers.
84
+ * @param {string} text the filled Markdown
85
+ * @param {{ fact_id: string, start: number, end: number, rendered: string, replaced?: string, translates?: string }[]} entries its fill map
86
+ * @param {{ rows: object[] }} ledger
87
+ * @returns {{ html: string, stale: object[] }} stale: entries whose bytes changed after fill (checked by text)
88
+ */
89
+ export function markdownAsHtml(text, entries, ledger) {
90
+ const sorted = [...(entries ?? [])].sort((a, b) => a.start - b.start)
91
+ const rows = new Map((ledger?.rows ?? []).map((row) => [row.fact_id, row]))
92
+ const stale = new Set(sorted.filter((entry) => entry.replaced === undefined))
93
+ const lineAt = (offset) => text.slice(0, offset).split("\n").length
94
+ const parsed = parseMarkdown(text)
95
+ if (parsed.problems.length > 0) {
96
+ throw new CliError(`the Markdown edition is malformed: ${parsed.problems.map((problem) => `${problem.code} at line ${problem.line}`).join(", ")}; fix it before verifying`)
97
+ }
98
+ let html = "<!doctype html><html><head><title>md</title></head><body>"
99
+ let line = 1
100
+ let open = null
101
+ for (const block of parsed.blocks) {
102
+ const rule = MD_HTML_BLOCK_RULES[block.kind]
103
+ if (rule === undefined) throw new CliError(`md-lite block kind ${block.kind} has no projection rule`)
104
+ if (open && open !== block.kind) html += MD_HTML_BLOCK_RULES[open].group[1]
105
+ const target = lineAt(block.start)
106
+ if (target > line) html += "\n".repeat(target - line)
107
+ line = Math.max(line, target) + (text.slice(block.start, block.end).match(/\n/gu)?.length ?? 0)
108
+ if (rule.group && open !== block.kind) html += rule.group[0]
109
+ open = rule.group ? block.kind : null
110
+ const inner = (from, to) => spanHtml(text, from, to, sorted, rows, stale)
111
+ if (block.kind === "table_row") html += `<${rule.tag}>${block.cells.map((cell) => `<td>${inner(cell.start, cell.end)}</td>`).join("")}</${rule.tag}>`
112
+ else if (block.kind === "code") html += `<pre><${rule.tag}>${escapeHtml(block.text)}</${rule.tag}></pre>`
113
+ else {
114
+ const tag = rule.tag ?? `h${block.level}`
115
+ const replaced = block.kind === "blockquote" ? sorted.find((entry) => entry.replaced !== undefined && entry.start <= block.start && entry.end >= block.end) : undefined
116
+ const attrs = replaced ? ` data-fact="${escapeHtml(replaced.fact_id)}" data-replaced="${escapeHtml(replaced.replaced)}"` : ""
117
+ html += `<${tag}${attrs}>${inner(block.start, block.end)}</${tag}>`
118
+ }
119
+ }
120
+ return { html: `${html}${open ? MD_HTML_BLOCK_RULES[open].group[1] : ""}</body></html>\n`, stale: [...stale] }
121
+ }
122
+
123
+ const MARKDOWN_EXTENSION = /\.(md|markdown)$/iu
124
+ /** The edition format: the explicit flag, else md for a .md or .markdown path (any case), else html. */
125
+ export const editionFormatOf = (path, flag) => flag ?? (MARKDOWN_EXTENSION.test(path) ? "md" : "html")
126
+
127
+ /** The report's format (from --format or its extension) and, for Markdown, its fill map entries. */
128
+ export function readEdition(report, flags) {
129
+ const format = editionFormatOf(report, flags.format)
130
+ if (format !== "html" && format !== "md") throw new CliError(`--format ${format}: verify html or md (a LaTeX edition is verified through its Markdown source)`)
131
+ const text = readText(report)
132
+ if (format === "html") return { format, text, entries: flags["fill-map"] === undefined ? null : readJsonFile(flags["fill-map"]).entries }
133
+ const map = flags["fill-map"] ?? `${report}.fill-map.json`
134
+ if (!existsSync(map)) throw new CliError(`a Markdown edition needs its fill map: ${map} is missing (pass --fill-map)`)
135
+ return { format, text, entries: readJsonFile(map).entries }
136
+ }
137
+
138
+ /** @param {{ positionals: string[], flags: Record<string, any> }} args parsed by runFact */
139
+ export function runFactVerify(args) {
140
+ const [report] = args.positionals
141
+ const { flags } = args
142
+ if (!report) throw new CliError("usage: fact verify <report> [--format html|md] [--sources D] [--claims claims.json] [--fill-map F] [--brief brief.md] [--out fidelity-defects.json]")
143
+ const edition = readEdition(report, flags)
144
+ const sessionDir = sessionDirOf(flags)
145
+ const claimsPath = flags.claims ?? join(sessionDir, "claims.json")
146
+ const briefPath = flags.brief ?? join(sessionDir, "brief.md")
147
+ const ledger = readLedger(sessionDir)
148
+ const projected = edition.format === "md" ? markdownAsHtml(edition.text, edition.entries, ledger) : null
149
+ const result = checkFidelity(projected?.html ?? edition.text, {
150
+ ledger,
151
+ sourcesDir: resolve(flags.sources ?? join(sessionDir, "sources")),
152
+ claims: flags.claims !== undefined || existsSync(claimsPath) ? readJsonFile(claimsPath) : null,
153
+ fillMap: projected?.stale ?? edition.entries,
154
+ brief: existsSync(briefPath) ? readText(briefPath) : "",
155
+ baseDir: dirname(resolve(report)),
156
+ recompute,
157
+ })
158
+ const counts = countDefects(result.defects)
159
+ const status = result.defects.some(blocksDelivery) ? "fail" : "pass"
160
+ const exitCode = status === "fail" ? 1 : 0
161
+ const out = resolve(flags.out ?? join(sessionDir, FIDELITY_RECORD))
162
+ const record = writeFidelityRecord(out, "fidelity", { command: "fact verify", status, exitCode, format: edition.format, report: resolve(report), artifact_sha256: sha256Of(edition.text), defects: result.defects, counts, gatesRun: result.gatesRun })
163
+ return { json: { ...record, out }, summary: `fact verify: ${status.toUpperCase()} - ${counts.blocker} blocker, ${counts.major} major, ${counts.minor} minor`, exitCode }
164
+ }
@@ -0,0 +1,239 @@
1
+ // fact-commands.mjs - the `fact` subcommands of report-tools: add, quote, retract, export, fill,
2
+ // derive, verify (fact-commands-verify.mjs), pairs and meaning-check (fact-commands-meaning.mjs). Every value reaches the ledger from a source
3
+ // snapshot through a deterministic locator or from a declared derivation; nothing here accepts a
4
+ // typed value. add and quote print the row with 60 chars of source context on each side (JSON
5
+ // with --json). Exit 0 ok, 1 semantic failure (fact_not_located, fill errors, a refused
6
+ // derivation), 2 usage. node: builtins and sibling imports only.
7
+
8
+ import { existsSync, readdirSync, readFileSync, writeFileSync } from "node:fs"
9
+ import { dirname, extname, join, resolve } from "node:path"
10
+
11
+ import { CliError, parseArgs, sha256Of, writeJsonAtomic } from "./cli-support.mjs"
12
+ import { DEFECT_CODES } from "./contracts.mjs"
13
+ import { derive } from "./fact-derive.mjs"
14
+ import { fill } from "./fact-fill.mjs"
15
+ import { addRow, readLedger, retractRow, supersedeRow, writeLedger } from "./fact-ledger.mjs"
16
+ import { locateFind, locateMatch, locatePageLine, locateTable } from "./fact-locate.mjs"
17
+ import { QUALIFIER_FORMS } from "./fidelity-lexicon.mjs"
18
+ import { readState, writeState } from "./fidelity-state.mjs"
19
+ import { exportRows } from "./fidelity-values.mjs"
20
+ import { pointedFlag, sessionDirOf, snapshotOf } from "./source-commands.mjs"
21
+
22
+ const CONTEXT = 60
23
+ const BOOLEANS = ["json", "csv", "md", "inline", "view", "replace-failed", "disjoint"]
24
+ const QUALIFIER_OF = new Map(Object.entries(QUALIFIER_FORMS).flatMap(([term, forms]) => forms.map((form) => [form, term])))
25
+ const slug = (text) => String(text).toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "").slice(0, 32) || "fact"
26
+
27
+ function readText(path) {
28
+ try {
29
+ return readFileSync(path, "utf8")
30
+ } catch (error) {
31
+ throw new CliError(`cannot read ${path}: ${error.code ?? error.message}`)
32
+ }
33
+ }
34
+
35
+ function nowIso(flags) {
36
+ const date = flags.now === undefined ? new Date() : new Date(flags.now)
37
+ if (Number.isNaN(date.getTime())) throw new CliError("--now requires an ISO timestamp")
38
+ return date.toISOString()
39
+ }
40
+
41
+ function failure(command, error, detail) {
42
+ const json = { command, status: "fail", exitCode: 1, error, ...detail }
43
+ return { json, summary: `${command}: ${error}${detail.message ? ` (${detail.message})` : ""}`, exitCode: 1 }
44
+ }
45
+
46
+ function locate(flags, snapshot, id, quote) {
47
+ const sourceId = id
48
+ if (quote) return locateMatch(snapshot.text, { match: flags.match, sourceId })
49
+ if (flags.find !== undefined) return locateFind(snapshot.text, { find: flags.find, unitExpected: flags["unit-expected"], sourceId })
50
+ if (flags.table !== undefined) {
51
+ if (snapshot.html === null) throw new CliError(`source ${id} has no source.html; --table needs a page snapshot`)
52
+ return locateTable(snapshot.html, { table: flags.table, row: flags.row, col: flags.col, sourceId })
53
+ }
54
+ if (flags.page !== undefined) return locatePageLine(snapshot.text, { page: Number(flags.page), line: Number(flags.line), sourceId })
55
+ throw new CliError("usage: fact add --source S<n> (--find TEXT | --table T --row R --col C | --page P --line L) --label LABEL")
56
+ }
57
+
58
+ function contextOf(text, row) {
59
+ const start = row.locator.char_start + (row.value_span?.[0] ?? 0)
60
+ const end = row.value_span ? row.locator.char_start + row.value_span[1] : row.locator.char_end
61
+ return { before: text.slice(Math.max(0, start - CONTEXT), start), match: text.slice(start, end), after: text.slice(end, end + CONTEXT) }
62
+ }
63
+
64
+ /** `fact add --assumed V --rationale R`: a value nobody measured, rendered ASSUMED; the rationale is required. */
65
+ function runAssumed(args) {
66
+ const sessionDir = sessionDirOf(args.flags)
67
+ const ledger = readLedger(sessionDir)
68
+ const value = String(args.flags.assumed)
69
+ const row = addRow(ledger, { kind: "assumed", value, unit: args.flags.unit ?? null, raw: value, quote: "", extracted_by: "model", observed_at: nowIso(args.flags), label: args.required("label"), rationale: args.required("rationale") })
70
+ writeLedger(sessionDir, ledger)
71
+ return { json: { command: "fact add", status: "pass", exitCode: 0, defects: [], data: { row } }, exitCode: 0 }
72
+ }
73
+
74
+ function runAdd(args, quote) {
75
+ const command = quote ? "fact quote" : "fact add"
76
+ const { flags } = args
77
+ if (!quote && flags.assumed !== undefined) return runAssumed(args)
78
+ const id = args.required("source")
79
+ if (quote) args.required("match")
80
+ const sessionDir = sessionDirOf(flags)
81
+ const snapshot = snapshotOf(sessionDir, id)
82
+ const found = locate(flags, snapshot, id, quote)
83
+ if (found.error === "unit_expected_mismatch") return failure(command, "unit_mismatch", { message: `expected ${found.expected}, found ${found.found}`, candidates: found.candidates })
84
+ if (found.error) return failure(command, found.error, { message: `no single match in ${id}`, candidates: found.candidates })
85
+ const { meta } = snapshot
86
+ const host = URL.canParse(meta.url) ? new URL(meta.url).hostname : null
87
+ const labelSource = flags.find ?? flags.match ?? (flags.table !== undefined ? `${flags.row}_${flags.col}` : `p${flags.page}_l${flags.line}`)
88
+ const ledger = readLedger(sessionDir)
89
+ const row = addRow(ledger, {
90
+ kind: quote ? "quote" : "measured",
91
+ ...(quote ? {} : { value: found.value, unit: found.unit, value_span: found.value_span, qualifier: QUALIFIER_OF.get(found.qualifier) ?? null }),
92
+ raw: found.raw,
93
+ quote: found.quote,
94
+ source_id: id,
95
+ locator: { ...found.locator, source_kind: meta.kind, url: meta.url, host, pointed: pointedFlag(sessionDir, meta.url) },
96
+ snapshot_sha256: meta.sha256,
97
+ sentence_id: found.sentence_id ?? null,
98
+ extracted_by: meta.extractor,
99
+ observed_at: nowIso(flags),
100
+ label: flags.label ?? slug(labelSource),
101
+ entity: flags.entity ?? null,
102
+ valid_at: flags["valid-at"] ?? null,
103
+ population: flags.population ?? null,
104
+ accounting_basis: flags["accounting-basis"] ?? null,
105
+ observation_id: flags.observation ?? null,
106
+ fuzzy: found.fuzzy === true,
107
+ })
108
+ writeLedger(sessionDir, ledger)
109
+ const context = contextOf(snapshot.text, row)
110
+ const json = { command, status: "pass", exitCode: 0, defects: [], data: { row, context } }
111
+ if (flags.json) return { json, exitCode: 0 }
112
+ const shown = quote ? row.quote : `${row.raw} (value ${row.value}${row.unit ? `, unit ${row.unit}` : ""})`
113
+ return { text: `${row.fact_id} ${row.label}: ${shown} [${id} ${row.sentence_id ?? "-"}]\n ...${context.before}[${context.match}]${context.after}...`, exitCode: 0 }
114
+ }
115
+
116
+ function runRetract(args) {
117
+ const [id] = args.positionals
118
+ if (!id) throw new CliError("usage: fact retract <F<n>|Q<n>> --reason TEXT [--replacement ID]")
119
+ const sessionDir = sessionDirOf(args.flags)
120
+ const ledger = readLedger(sessionDir)
121
+ const reason = args.required("reason")
122
+ const row = args.flags.replacement === undefined ? retractRow(ledger, id, reason) : supersedeRow(ledger, id, args.flags.replacement, reason)
123
+ writeLedger(sessionDir, ledger)
124
+ return { json: { command: "fact retract", status: "pass", exitCode: 0, data: { fact_id: id, status: row.status, replacement: args.flags.replacement ?? null } }, exitCode: 0 }
125
+ }
126
+
127
+ const csvCell = (value) => (/[",\n]/.test(String(value)) ? `"${String(value).replace(/"/g, '""')}"` : String(value))
128
+
129
+ /** "N of M sources snapshotted, by extractor ...": M also counts sources-ledger ids not yet snapshotted. */
130
+ function coverageLine(sessionDir) {
131
+ const dir = join(sessionDir, "sources")
132
+ const snapped = existsSync(dir) ? readdirSync(dir).filter((name) => existsSync(join(dir, name, "meta.json"))) : []
133
+ const ledgerFile = join(sessionDir, "sources-ledger.md")
134
+ const listed = existsSync(ledgerFile) ? [...readText(ledgerFile).matchAll(/^\[S(\d+)\]/gm)].map((match) => `S${match[1]}`) : []
135
+ const byExtractor = {}
136
+ for (const id of snapped) {
137
+ const extractor = JSON.parse(readText(join(dir, id, "meta.json"))).extractor
138
+ byExtractor[extractor] = (byExtractor[extractor] ?? 0) + 1
139
+ }
140
+ const total = new Set([...snapped, ...listed]).size
141
+ const parts = Object.entries(byExtractor).map(([name, count]) => `${name} ${count}`)
142
+ return `${snapped.length} of ${total} sources snapshotted, by extractor: ${parts.join(", ") || "none"}`
143
+ }
144
+
145
+ function runExport(args) {
146
+ const sessionDir = sessionDirOf(args.flags)
147
+ const ledger = readLedger(sessionDir)
148
+ const coverage = coverageLine(sessionDir)
149
+ let text
150
+ if (args.flags.csv) {
151
+ const rows = exportRows(ledger)
152
+ text = `${["fact_id,label,plot_value,unit", ...rows.map((row) => [row.fact_id, row.label, row.plot_value, row.unit].map(csvCell).join(","))].join("\n")}\n`
153
+ } else text = `${JSON.stringify({ schemaVersion: ledger.schemaVersion, rows: ledger.rows, coverage }, null, 2)}\n`
154
+ if (args.flags.out === undefined) return { text, summary: coverage, exitCode: 0 }
155
+ writeFileSync(args.flags.out, text)
156
+ return { json: { command: "fact export", status: "pass", exitCode: 0, data: { out: resolve(args.flags.out), format: args.flags.csv ? "csv" : "json", sha256: sha256Of(text), coverage } }, summary: coverage, exitCode: 0 }
157
+ }
158
+
159
+ /** The inline edition: local stylesheets and scripts inlined, relative image paths made absolute. */
160
+ function inlineAssets(html, baseDir) {
161
+ const local = (ref) => !/^[a-z][a-z0-9+.-]*:|^\/\//i.test(ref)
162
+ return html
163
+ .replace(/<link\b[^>]*\brel=["']stylesheet["'][^>]*>/gi, (tag) => {
164
+ const href = /\bhref=["']([^"']+)["']/i.exec(tag)?.[1]
165
+ return href && local(href) ? `<style>${readText(resolve(baseDir, href))}</style>` : tag
166
+ })
167
+ .replace(/<script\b([^>]*)\bsrc=["']([^"']+)["']([^>]*)><\/script>/gi, (tag, pre, src, post) => (local(src) ? `<script${pre}${post}>${readText(resolve(baseDir, src))}</script>` : tag))
168
+ .replace(/(<img\b[^>]*\bsrc=["'])([^"']+)(["'])/gi, (tag, pre, src, post) => (local(src) && !src.startsWith("/") ? `${pre}${resolve(baseDir, src)}${post}` : tag))
169
+ }
170
+
171
+ function runFill(args) {
172
+ const [source] = args.positionals
173
+ const { flags } = args
174
+ if (!source || (flags.out === undefined && !flags.view)) throw new CliError("usage: fact fill <src> (--out <path> | --view) [--format html|md|tex] [--inline] [--replace-failed]")
175
+ const format = flags.format ?? ({ ".md": "md", ".tex": "tex" }[extname(source).toLowerCase()] ?? "html")
176
+ if (!["html", "md", "tex"].includes(format)) throw new CliError("--format must be html, md, or tex")
177
+ if (flags.inline && format !== "html") throw new CliError("--inline applies to the html edition only")
178
+ const sessionDir = sessionDirOf(flags)
179
+ const state = flags["replace-failed"] ? readState(sessionDir) : null
180
+ const result = fill(readText(source), readLedger(sessionDir), { format: format === "tex" ? "md" : format, view: flags.view === true, ...(state ? { replaceFailed: state } : {}) })
181
+ const warnings = result.warnings
182
+ if (result.out === null) {
183
+ const defects = result.errors.map((error) => ({ ...error, severity: DEFECT_CODES[error.code]?.severity ?? "blocker", hint: DEFECT_CODES[error.code]?.hint ?? null }))
184
+ return { json: { command: "fact fill", status: "fail", exitCode: 1, defects, warnings }, summary: `fact fill: FAIL - ${defects.length} error(s), nothing written`, exitCode: 1 }
185
+ }
186
+ const out = flags.inline ? inlineAssets(result.out, dirname(resolve(source))) : result.out
187
+ if (flags.view) return { text: out, summary: `fact fill --view: ${result.fillMap.length} pointer(s), ${warnings.length} warning(s)`, exitCode: 0 }
188
+ writeFileSync(flags.out, out)
189
+ if (state) writeState(sessionDir, state)
190
+ const artifact = sha256Of(out)
191
+ const map = `${resolve(flags.out)}.fill-map.json`
192
+ writeJsonAtomic(map, { schemaVersion: 1, format, artifact: resolve(flags.out), artifact_sha256: artifact, entries: result.fillMap })
193
+ const data = { out: resolve(flags.out), map, artifact_sha256: artifact, occurrences: result.fillMap.length, warnings }
194
+ return { json: { command: "fact fill", status: "pass", exitCode: 0, defects: [], data }, summary: `fact fill: wrote ${flags.out} (${result.fillMap.length} pointer(s))`, exitCode: 0 }
195
+ }
196
+
197
+ function runDerive(args) {
198
+ const { flags } = args
199
+ const op = args.required("op")
200
+ const sessionDir = sessionDirOf(flags)
201
+ const ledger = readLedger(sessionDir)
202
+ // The registry refuses any field outside the operation's inputFields, so only the given flags go in; --unit null declares a dimensionless result.
203
+ const picked = ["to", "from", "num", "den", "part", "whole", "reference", "rationale", "label"].filter((key) => flags[key] !== undefined)
204
+ const spec = { op, now: nowIso(flags), ...Object.fromEntries(picked.map((key) => [key, flags[key]])) }
205
+ if (op === "expr") spec.formula = args.positionals[0]
206
+ else if (args.positionals.length > 0) spec.operands = args.positionals
207
+ if (flags.disjoint === true) spec.disjoint = true
208
+ if (flags.unit !== undefined) spec.unit = flags.unit === "null" ? null : flags.unit
209
+ const result = derive(ledger, spec)
210
+ if (result.error) return failure("fact derive", result.error, { message: result.detail, ...(result.hint ? { hint: result.hint } : {}) })
211
+ writeLedger(sessionDir, ledger)
212
+ const { row } = result
213
+ const json = { command: "fact derive", status: "pass", exitCode: 0, defects: [], data: { row, compatibility: result.compatibility } }
214
+ return flags.json ? { json, exitCode: 0 } : { text: `${row.fact_id} ${row.label}: ${row.render} (${row.operation} of ${row.operands.join(", ")}, ${result.compatibility})`, exitCode: 0 }
215
+ }
216
+
217
+ export async function runFact(argv) {
218
+ const [sub, ...rest] = argv
219
+ if (sub === "pairs" || sub === "meaning-check") return (await import("./fact-commands-meaning.mjs")).runFactMeaning(sub, rest)
220
+ const args = parseArgs(rest, { booleans: BOOLEANS })
221
+ switch (sub) {
222
+ case "add":
223
+ return runAdd(args, false)
224
+ case "quote":
225
+ return runAdd(args, true)
226
+ case "retract":
227
+ return runRetract(args)
228
+ case "export":
229
+ return runExport(args)
230
+ case "fill":
231
+ return runFill(args)
232
+ case "derive":
233
+ return runDerive(args)
234
+ case "verify":
235
+ return (await import("./fact-commands-verify.mjs")).runFactVerify(args)
236
+ default:
237
+ throw new CliError("usage: fact <add|quote|retract|export|fill|verify|derive|pairs|meaning-check> ...")
238
+ }
239
+ }