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,170 @@
1
+ // polish-rules.mjs - the one rule set behind polish-apply (per edit) and polish-diff (whole document).
2
+ //
3
+ // polishDefects(beforeHtml, afterHtml, ledger, {ordered}) compares the visible projections
4
+ // (html-markup.markupParts, then polish-project.project) and returns {code, message} per failure.
5
+ // Polish may change text nodes only. The primary rule is the whole-document skeleton comparison
6
+ // (html-markup.skeletonChange): every markup token (tags, comments, bogus comments and frozen
7
+ // script/style/template content) and every character reference of a text node must be byte-identical
8
+ // in document order; an edited span holds no skeleton token, so any difference is polish_diff_fail
9
+ // naming the first differing token and its offset. The remaining checks compare the projections:
10
+ // polish_diff_fail: fact spans (id + rendered markup), numerals outside fact spans (with units),
11
+ // tickers (TICKER: 1-5 capitals with an optional ".XX" class, or a 6-digit KRX code), or a
12
+ // table's row count changed; any tag, attribute, comment, declaration, or script/style/template
13
+ // content changed; protected tokens also stay within their original text-node boundaries;
14
+ // polish_hedging_decreased: a hedge or qualifier the before text holds and must keep is gone;
15
+ // polish_text_churn: more than POLISH_CHURN_LIMIT of the normalized visible prose changed;
16
+ // polish_link_changed: href values changed or an internal #target stopped resolving;
17
+ // polish_name_changed: a proper noun (POLISH_NAME) of a ledger quote, present before, is gone.
18
+ // A document holding a construct html-markup.ambiguousMarkup lists (</ + non-letter, <!-- inside
19
+ // script/style, <![CDATA[, <?, --!>, or < inside a tag) is refused first with polish_diff_fail
20
+ // "ambiguous markup": the byte comparison is only trusted where the tokenizer is exact.
21
+ // ordered (polish-apply) also refuses a reordering of fact spans, numerals, tickers and links;
22
+ // without it they compare as multisets. Severity lives only in contracts.DEFECT_CODES.
23
+ // No IO, no model. Plain Node ESM.
24
+
25
+ import { ambiguousMarkup, markupParts, skeletonChange, tagAttributes } from "./html-markup.mjs"
26
+ import { missingTerms, numeralKeys, project, requiredTerms, termCounts, textOf } from "./polish-project.mjs"
27
+ import { splitSentences } from "./sentence-split.mjs"
28
+ import { normalizeText } from "./text-normalize.mjs"
29
+
30
+ /** The codes polishDefects can emit, in report order. */
31
+ export const POLISH_CODES = Object.freeze(["polish_diff_fail", "polish_hedging_decreased", "polish_text_churn", "polish_link_changed", "polish_name_changed"])
32
+ export const TICKER = /(?<![\p{L}\p{N}])(?:[A-Z]{1,5}(?:\.[A-Z]{1,2})?|\d{6})(?![\p{L}\p{N}])/gu
33
+ /** The capitalized-token heuristic for proper nouns in ledger quotes. */
34
+ export const POLISH_NAME = /\p{Lu}[\p{L}\p{N}&'-]+/gu
35
+ /** Above this share of changed normalized prose a polish composed instead of proofreading. */
36
+ export const POLISH_CHURN_LIMIT = 0.5
37
+ /** Above this many cells a gap counts as fully rewritten instead of running the quadratic distance. */
38
+ const DISTANCE_CELLS = 25_000_000
39
+
40
+ const sorted = (list) => [...list].sort()
41
+ const same = (a, b) => a.length === b.length && a.every((item, k) => item === b[k])
42
+
43
+ /** null when unchanged; a reorder counts only when ordered. */
44
+ function listChange(label, before, after, ordered) {
45
+ if (same(before, after)) return null
46
+ const remaining = sorted(before)
47
+ const added = []
48
+ for (const item of after) {
49
+ const at = remaining.indexOf(item)
50
+ if (at === -1) added.push(item)
51
+ else remaining.splice(at, 1)
52
+ }
53
+ if (remaining.length > 0 || added.length > 0) return `${label} changed: removed [${remaining.join(", ")}], added [${added.join(", ")}]`
54
+ return ordered ? `${label} reordered: [${before.join(", ")}] became [${after.join(", ")}]` : null
55
+ }
56
+
57
+ const tableRows = (html) => html.split(/<table\b/i).slice(1).map((part) => (part.split(/<\/table\s*>/i)[0].match(/<tr\b/gi) ?? []).length)
58
+
59
+ function levenshtein(a, b) {
60
+ if (a === b) return 0
61
+ if (a.length * b.length > DISTANCE_CELLS) return Math.max(a.length, b.length)
62
+ let previous = Array.from({ length: b.length + 1 }, (_, k) => k)
63
+ for (let i = 1; i <= a.length; i++) {
64
+ const current = [i]
65
+ for (let j = 1; j <= b.length; j++) {
66
+ current[j] = Math.min(previous[j] + 1, current[j - 1] + 1, previous[j - 1] + (a[i - 1] === b[j - 1] ? 0 : 1))
67
+ }
68
+ previous = current
69
+ }
70
+ return previous[b.length]
71
+ }
72
+
73
+ /** Changed share of normalized prose: edit distance over unmatched sentence runs / longer length. */
74
+ export function churnRatio(beforeText, afterText) {
75
+ const [a, b] = [normalizeText(beforeText), normalizeText(afterText)]
76
+ const longest = Math.max(a.length, b.length)
77
+ if (longest === 0) return 0
78
+ const [sa, sb] = [splitSentences(a).map((s) => s.text), splitSentences(b).map((s) => s.text)]
79
+ const lcs = Array.from({ length: sa.length + 1 }, () => new Array(sb.length + 1).fill(0))
80
+ for (let i = sa.length - 1; i >= 0; i--) {
81
+ for (let j = sb.length - 1; j >= 0; j--) lcs[i][j] = sa[i] === sb[j] ? lcs[i + 1][j + 1] + 1 : Math.max(lcs[i + 1][j], lcs[i][j + 1])
82
+ }
83
+ let distance = 0
84
+ let gap = [[], []]
85
+ const flush = () => {
86
+ distance += levenshtein(gap[0].join(" "), gap[1].join(" "))
87
+ gap = [[], []]
88
+ }
89
+ let [i, j] = [0, 0]
90
+ while (i < sa.length || j < sb.length) {
91
+ if (i < sa.length && j < sb.length && sa[i] === sb[j]) {
92
+ flush()
93
+ i++
94
+ j++
95
+ } else if (j >= sb.length || (i < sa.length && lcs[i + 1][j] >= lcs[i][j + 1])) gap[0].push(sa[i++])
96
+ else gap[1].push(sb[j++])
97
+ }
98
+ flush()
99
+ return distance / longest
100
+ }
101
+
102
+ function linkChange(from, to, before, after, ordered) {
103
+ const change = listChange("href and data-claim values", from.links, to.links, ordered)
104
+ if (change) return change
105
+ const ids = (html) => new Set(tagAttributes(html).flatMap(({ attrs }) => (Object.hasOwn(attrs, "id") ? [attrs.id] : [])))
106
+ const [idsBefore, idsAfter] = [ids(before), ids(after)]
107
+ const targets = to.links.filter((link) => link.startsWith("href=#")).map((link) => link.slice(5))
108
+ const broken = targets.filter((href) => idsBefore.has(href.slice(1)) && !idsAfter.has(href.slice(1)))
109
+ return broken.length > 0 ? `internal link targets stopped resolving: [${[...new Set(broken)].join(", ")}]` : null
110
+ }
111
+
112
+ /** Capitalized tokens of the ledger quotes, minus each sentence's first word. */
113
+ function ledgerNames(rows) {
114
+ const names = new Set()
115
+ for (const row of rows) {
116
+ for (const { text } of splitSentences(row.quote ?? "")) {
117
+ const first = text.match(/^\P{L}*\p{L}+/u)?.[0].length ?? 0
118
+ for (const match of text.matchAll(POLISH_NAME)) if (match.index >= first) names.add(match[0])
119
+ }
120
+ }
121
+ return names
122
+ }
123
+
124
+ const hasWord = (text, word) => new RegExp(`(?<![\\p{L}\\p{N}])${word.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(?![\\p{L}\\p{N}])`, "u").test(text)
125
+
126
+ /**
127
+ * @param {string} beforeHtml
128
+ * @param {string} afterHtml
129
+ * @param {{ rows: object[] }} ledger
130
+ * @param {{ ordered?: boolean }} [options]
131
+ * @returns {{ code: string, message: string }[]} in POLISH_CODES order
132
+ */
133
+ export function polishDefects(beforeHtml, afterHtml, ledger, { ordered = false } = {}) {
134
+ const ambiguous = [...new Set([...ambiguousMarkup(beforeHtml), ...ambiguousMarkup(afterHtml)])]
135
+ if (ambiguous.length > 0) return [{ code: "polish_diff_fail", message: `ambiguous markup: ${ambiguous.join(", ")}` }]
136
+ const [beforeParts, afterParts] = [markupParts(beforeHtml), markupParts(afterHtml)]
137
+ const [before, after] = [beforeParts.source, afterParts.source]
138
+ const [from, to] = [project(before), project(after)]
139
+ const rows = ledger?.rows ?? []
140
+ const defects = []
141
+ const note = (code, message) => message && defects.push({ code, message })
142
+ const factKey = (fact) => `${fact.id} ${fact.html}`
143
+ const tickers = (text) => text.match(TICKER) ?? []
144
+ const markupEqual = same(beforeParts.markup, afterParts.markup)
145
+ note("polish_diff_fail", markupEqual ? null : "markup changed")
146
+ note("polish_diff_fail", skeletonChange(beforeHtml, afterHtml))
147
+ note("polish_diff_fail", listChange("fact spans", from.facts.map(factKey), to.facts.map(factKey), ordered))
148
+ note("polish_diff_fail", listChange("numerals outside fact spans", numeralKeys(from.masked), numeralKeys(to.masked), ordered))
149
+ note("polish_diff_fail", listChange("tickers", tickers(from.masked), tickers(to.masked), ordered))
150
+ const [rowsBefore, rowsAfter] = [tableRows(before), tableRows(after)]
151
+ if (!same(rowsBefore, rowsAfter)) note("polish_diff_fail", `table row counts changed: [${rowsBefore}] became [${rowsAfter}]`)
152
+ const required = requiredTerms(from, new Map(rows.map((row) => [row.fact_id, row])))
153
+ if (markupEqual) {
154
+ for (const [index, raw] of beforeParts.text.entries()) {
155
+ const [a, b] = [raw, afterParts.text[index]].map(textOf)
156
+ note("polish_diff_fail", listChange("text-node numerals", numeralKeys(a), numeralKeys(b), ordered))
157
+ note("polish_diff_fail", listChange("text-node tickers", tickers(a), tickers(b), ordered))
158
+ const lost = missingTerms(termCounts(a, [...required.keys()]), b)
159
+ if (lost.length) note("polish_hedging_decreased", `text-node hedges removed: ${lost.join(", ")}`)
160
+ }
161
+ }
162
+ const missing = missingTerms(required, to.visible)
163
+ if (missing.length > 0) note("polish_hedging_decreased", `hedges or qualifiers removed: ${missing.map((term) => `"${term}"`).join(", ")}`)
164
+ const ratio = churnRatio(from.visible, to.visible)
165
+ if (ratio > POLISH_CHURN_LIMIT) note("polish_text_churn", `${(ratio * 100).toFixed(1)} percent of the normalized prose changed`)
166
+ note("polish_link_changed", linkChange(from, to, before, after, ordered))
167
+ const lost = [...ledgerNames(rows)].filter((name) => hasWord(from.visible, name) && !hasWord(to.visible, name))
168
+ if (lost.length > 0) note("polish_name_changed", `ledger-quote names missing after polish: ${lost.join(", ")}`)
169
+ return defects.sort((a, b) => POLISH_CODES.indexOf(a.code) - POLISH_CODES.indexOf(b.code))
170
+ }
@@ -0,0 +1,139 @@
1
+ // rational.mjs - exact rational arithmetic over BigInt numerator/denominator pairs.
2
+ //
3
+ // A rational is { num: bigint, den: bigint }, reduced, with den > 0. Numerals enter as decimal
4
+ // strings and never pass through Number(), so no value is ever rounded by floating point.
5
+ // Plain Node ESM, no imports.
6
+
7
+ const DECIMAL = /^([+-]?)(\d+)(?:\.(\d+))?$/
8
+
9
+ const abs = (n) => (n < 0n ? -n : n)
10
+
11
+ function gcd(a, b) {
12
+ let x = abs(a)
13
+ let y = abs(b)
14
+ while (y !== 0n) [x, y] = [y, x % y]
15
+ return x
16
+ }
17
+
18
+ function make(num, den) {
19
+ if (den === 0n) throw new RangeError("rational: zero denominator")
20
+ const sign = den < 0n ? -1n : 1n
21
+ const g = gcd(num, den) || 1n
22
+ return Object.freeze({ num: (sign * num) / g, den: (sign * den) / g })
23
+ }
24
+
25
+ /** Accepts a rational, a bigint, a safe integer, or a decimal string. */
26
+ function coerce(value) {
27
+ if (typeof value === "bigint") return make(value, 1n)
28
+ if (typeof value === "number") {
29
+ if (!Number.isSafeInteger(value)) throw new TypeError(`rational: ${value} is not a safe integer; pass a decimal string`)
30
+ return make(BigInt(value), 1n)
31
+ }
32
+ if (typeof value === "string") return fromDecimal(value)
33
+ if (value && typeof value.num === "bigint" && typeof value.den === "bigint") return make(value.num, value.den)
34
+ throw new TypeError("rational: expected a rational, bigint, integer, or decimal string")
35
+ }
36
+
37
+ /** "12.50" -> 25/2. Accepts an optional sign, digits, and an optional fraction; nothing else. */
38
+ export function fromDecimal(str) {
39
+ const match = DECIMAL.exec(String(str).trim())
40
+ if (!match) throw new SyntaxError(`rational: "${str}" is not a plain decimal`)
41
+ const [, sign, whole, frac = ""] = match
42
+ const num = BigInt(whole + frac) * (sign === "-" ? -1n : 1n)
43
+ return make(num, 10n ** BigInt(frac.length))
44
+ }
45
+
46
+ export function add(a, b) {
47
+ const x = coerce(a)
48
+ const y = coerce(b)
49
+ return make(x.num * y.den + y.num * x.den, x.den * y.den)
50
+ }
51
+
52
+ export function sub(a, b) {
53
+ const x = coerce(a)
54
+ const y = coerce(b)
55
+ return make(x.num * y.den - y.num * x.den, x.den * y.den)
56
+ }
57
+
58
+ export function mul(a, b) {
59
+ const x = coerce(a)
60
+ const y = coerce(b)
61
+ return make(x.num * y.num, x.den * y.den)
62
+ }
63
+
64
+ export function div(a, b) {
65
+ const x = coerce(a)
66
+ const y = coerce(b)
67
+ if (y.num === 0n) throw new RangeError("rational: division by zero")
68
+ return make(x.num * y.den, x.den * y.num)
69
+ }
70
+
71
+ export function eq(a, b) {
72
+ const x = coerce(a)
73
+ const y = coerce(b)
74
+ return x.num === y.num && x.den === y.den
75
+ }
76
+
77
+ /** Digits of a non-negative integer scaled by 10^-scale, as a decimal string. */
78
+ function placeDecimal(digits, scale) {
79
+ if (scale <= 0) return digits + "0".repeat(-scale)
80
+ const padded = digits.padStart(scale + 1, "0")
81
+ return `${padded.slice(0, padded.length - scale)}.${padded.slice(padded.length - scale)}`
82
+ }
83
+
84
+ /** Exact decimal; throws RangeError when the expansion does not terminate. */
85
+ function exactDecimal(r) {
86
+ let den = r.den
87
+ let twos = 0
88
+ let fives = 0
89
+ while (den % 2n === 0n) [den, twos] = [den / 2n, twos + 1]
90
+ while (den % 5n === 0n) [den, fives] = [den / 5n, fives + 1]
91
+ if (den !== 1n) throw new RangeError(`rational: ${r.num}/${r.den} has no finite decimal; pass sigFigs`)
92
+ const scale = Math.max(twos, fives)
93
+ const digits = (abs(r.num) * 10n ** BigInt(scale)) / r.den
94
+ return (r.num < 0n ? "-" : "") + placeDecimal(digits.toString(), scale)
95
+ }
96
+
97
+ /** Number of decimal digits in the integer part of |num/den| (0 when below 1). */
98
+ function integerDigits(num, den) {
99
+ const whole = num / den
100
+ if (whole > 0n) return whole.toString().length
101
+ let exp = 0
102
+ let scaled = num
103
+ while (scaled < den) [scaled, exp] = [scaled * 10n, exp - 1]
104
+ return exp + 1
105
+ }
106
+
107
+ /**
108
+ * Decimal string. Without sigFigs the value must terminate and is rendered exactly. With sigFigs it
109
+ * is rounded half away from zero to that many significant figures; integers are never written
110
+ * with an exponent, so 319999 at 2 figures renders "320000".
111
+ */
112
+ export function toDecimal(value, sigFigs) {
113
+ const r = coerce(value)
114
+ if (sigFigs === undefined) return exactDecimal(r)
115
+ if (!Number.isInteger(sigFigs) || sigFigs < 1) throw new RangeError("rational: sigFigs must be a positive integer")
116
+ if (r.num === 0n) return sigFigs > 1 ? `0.${"0".repeat(sigFigs - 1)}` : "0"
117
+ const num = abs(r.num)
118
+ const scale = sigFigs - integerDigits(num, r.den)
119
+ const numer = scale >= 0 ? num * 10n ** BigInt(scale) : num
120
+ const denom = scale >= 0 ? r.den : r.den * 10n ** BigInt(-scale)
121
+ let digits = numer / denom
122
+ let places = scale
123
+ if ((numer % denom) * 2n >= denom) digits += 1n
124
+ if (digits.toString().length > sigFigs) [digits, places] = [digits / 10n, places - 1]
125
+ return (r.num < 0n ? "-" : "") + placeDecimal(digits.toString(), places)
126
+ }
127
+
128
+ /**
129
+ * Significant figures of a decimal string. With a decimal point: digits from the first nonzero
130
+ * digit to the end, trailing zeros included ("3.2" -> 2, "0.050" -> 2). Without one: every digit
131
+ * after leading zeros counts ("320" -> 3, "100" -> 3, "20" -> 2).
132
+ */
133
+ export function sigFigsOf(decimalStr) {
134
+ const match = DECIMAL.exec(String(decimalStr).trim())
135
+ if (!match) throw new SyntaxError(`rational: "${decimalStr}" is not a plain decimal`)
136
+ const [, , whole, frac] = match
137
+ const digits = (whole + (frac ?? "")).replace(/^0+/, "")
138
+ return Math.max(digits.length, 1)
139
+ }
@@ -5,7 +5,7 @@ import { existsSync, readFileSync, statSync, writeFileSync } from "node:fs"
5
5
  import { dirname, join, resolve } from "node:path"
6
6
 
7
7
  import { CliError, parseArgs, readJsonFile, writeJsonAtomic } from "./cli-support.mjs"
8
- import { REPAIR_DEFAULTS } from "./contracts.mjs"
8
+ import { GATE_NAMES, GATE_STATUSES, REPAIR_DEFAULTS } from "./contracts.mjs"
9
9
  import { parseLineageMode, parsePalette, renderDesignSpec } from "./design-spec.mjs"
10
10
  import { extractFromHtml, extractFromPath, fetchHtml } from "./format-extract.mjs"
11
11
  import { checkLayout } from "./gates-layout.mjs"
@@ -80,12 +80,16 @@ export function runLayoutProbe(argv) {
80
80
  }
81
81
 
82
82
  export function runRepairDecide(argv) {
83
- const args = parseArgs(argv)
83
+ const args = parseArgs(argv, { repeatable: ["defects"] })
84
84
  const statePath = args.required("state")
85
85
  const now = nowFrom(args.flags)
86
- const defectsInput = readJsonFile(args.required("defects"))
87
- const defects = Array.isArray(defectsInput) ? defectsInput : defectsInput.defects
88
- if (!Array.isArray(defects)) throw new CliError("--defects must be a JSON array or a check result with a defects array")
86
+ // Every --defects file (check result, fidelity record, or bare array) joins one defect list.
87
+ const defects = args.required("defects").flatMap((path) => {
88
+ const input = readJsonFile(path)
89
+ const list = Array.isArray(input) ? input : input.defects
90
+ if (!Array.isArray(list)) throw new CliError(`--defects ${path} must be a JSON array or a check result with a defects array`)
91
+ return list
92
+ })
89
93
  const state = existsSync(statePath)
90
94
  ? deserializeState(readText(statePath))
91
95
  : createTracker({
@@ -122,7 +126,7 @@ function manifestPath(flags) {
122
126
  return join(sessionDirFrom(flags), outcome.MANIFEST_FILENAME)
123
127
  }
124
128
 
125
- export function runOutcome(argv) {
129
+ export async function runOutcome(argv) {
126
130
  const [sub, ...rest] = argv
127
131
  const args = parseArgs(rest, { booleans: ["json"] })
128
132
  const file = sub === "state" ? null : manifestPath(args.flags)
@@ -135,7 +139,7 @@ export function runOutcome(argv) {
135
139
  }
136
140
  case "set": {
137
141
  const [format, status] = args.positionals
138
- if (!format || !status) throw new CliError("usage: outcome set <format> <status> [--path p] [--reason r] [--pages n]")
142
+ if (!format || !status) throw new CliError("usage: outcome set <format> <status> [--path p] [--reason r] [--pages n] [--verified-by fidelity-defects.json] [--rendered-from source]")
139
143
  const manifest = outcome.readManifest(file)
140
144
  const detail = {}
141
145
  if (args.flags.path !== undefined) {
@@ -144,13 +148,14 @@ export function runOutcome(argv) {
144
148
  }
145
149
  if (args.flags.reason !== undefined) detail.reason = args.flags.reason
146
150
  if (args.flags.pages !== undefined) detail.pages = args.int("pages")
151
+ for (const [flag, key] of [["verified-by", "verifiedBy"], ["rendered-from", "renderedFrom"]]) if (args.flags[flag] !== undefined) detail[key] = resolve(args.flags[flag])
147
152
  outcome.writeManifest(file, outcome.setDeliverable(manifest, format, status, { ...detail, now: nowFrom(args.flags) }))
148
153
  return { json: { command: "outcome set", format, status }, exitCode: 0 }
149
154
  }
150
155
  case "gate": {
151
156
  const [gate, status] = args.positionals
152
- if (!gate || !status) throw new CliError("usage: outcome gate <static|layout|visual|proofread> <pass|fail|not_run>")
153
- outcome.writeManifest(file, outcome.setGate(outcome.readManifest(file), gate, status))
157
+ if (!gate || !status) throw new CliError(`usage: outcome gate <${GATE_NAMES.join("|")}> <${GATE_STATUSES.join("|")}>`)
158
+ outcome.writeManifest(file, outcome.setGate(outcome.readManifest(file), gate, status, { reason: args.flags.reason, now: nowFrom(args.flags) }))
154
159
  return { json: { command: "outcome gate", gate, status }, exitCode: 0 }
155
160
  }
156
161
  case "render": {
@@ -169,11 +174,17 @@ export function runOutcome(argv) {
169
174
  return { json: { command: "outcome state", state }, summary: `state: ${state}`, exitCode: 0 }
170
175
  }
171
176
  case "verify": {
172
- const result = outcome.verifyManifest(outcome.readManifest(file))
177
+ const manifest = outcome.readManifest(file)
178
+ const base = outcome.verifyManifest(manifest)
179
+ const { fidelityProblems } = await import("./fidelity-records.mjs")
180
+ const problems = [...base.problems, ...fidelityProblems(sessionDirFrom(args.flags), manifest)]
181
+ const result = { ok: problems.length === 0, problems }
173
182
  return { json: { command: "outcome verify", ...result }, summary: `verify: ${result.ok ? "OK" : `${result.problems.length} problem(s)`}`, exitCode: result.ok ? 0 : 1 }
174
183
  }
175
184
  case "finish": {
176
185
  const manifest = outcome.readManifest(file)
186
+ const refused = (await import("./fidelity-records.mjs")).fidelityProblems(sessionDirFrom(args.flags), manifest)
187
+ if (refused.length > 0) return { json: { command: "outcome finish", refused: true, problems: refused }, summary: `finish: refused (${refused.map((p) => p.code).join(", ")})`, exitCode: 1 }
177
188
  const ledgerPath = args.flags.ledger ?? join(sessionDirFrom(args.flags), "sources-ledger.md")
178
189
  const now = nowFrom(args.flags)
179
190
  const facts = outcome.briefingFacts(manifest, existsSync(ledgerPath) ? readText(ledgerPath) : "", now)
@@ -20,6 +20,15 @@ export const COMMANDS = Object.freeze([
20
20
  "outcome finish",
21
21
  "outcome briefing",
22
22
  "format-extract",
23
+ "fact add",
24
+ "fact quote",
25
+ "fact retract",
26
+ "fact export",
27
+ "fact fill",
28
+ "fact verify",
29
+ "fact derive",
30
+ "source snapshot",
31
+ "claims index",
23
32
  ])
24
33
 
25
34
  export async function run(argv) {
@@ -36,6 +45,12 @@ export async function run(argv) {
36
45
  return runOutcome(rest)
37
46
  case "format-extract":
38
47
  return runFormatExtract(rest)
48
+ case "fact":
49
+ return (await import("./fact-commands.mjs")).runFact(rest)
50
+ case "source":
51
+ return (await import("./source-commands.mjs")).runSource(rest)
52
+ case "claims":
53
+ return (await import("./source-commands.mjs")).runClaims(rest)
39
54
  case "--help":
40
55
  case "help":
41
56
  if (rest.includes("--json")) return { json: { commands: COMMANDS }, exitCode: 0 }
@@ -0,0 +1,77 @@
1
+ // sentence-split.mjs - deterministic sentence index for snapshot text.
2
+ //
3
+ // splitSentences(text) returns [{ sid, start, end, text }] with sid "s<k>" (1-based) and
4
+ // start/end as offsets into the raw text, so text.slice(start, end) === record.text.
5
+ // Boundaries: a line break; ". ! ?" followed by whitespace or end of line (known
6
+ // abbreviations such as "e.g." excepted); and the Korean endings da. yo. nida. kka? even
7
+ // with no following space, unless closing punctuation follows (quoted speech such as
8
+ // '...da."rago' then needs whitespace like any other terminator). "no." is an abbreviation
9
+ // only before a number ("No. 5"). Plain Node ESM, no dependencies.
10
+
11
+ import { normalizeText } from "./text-normalize.mjs"
12
+
13
+ const TERMINATORS = ".!?"
14
+ const CLOSERS = "\"')]}\u201D\u2019\u00BB\u300D\u300F"
15
+ const KOREAN_PERIOD_ENDINGS = new Set(["\uB2E4", "\uC694"]) // da, yo (nida ends in da)
16
+ const KOREAN_QUESTION_ENDING = "\uAE4C" // kka
17
+ const ABBREVIATIONS = new Set([
18
+ "e.g", "i.e", "etc", "vs", "cf", "approx", "al", "fig", "no", "mr", "mrs", "ms", "dr", "prof", "inc", "ltd", "jr", "sr",
19
+ ])
20
+ const SPACE = /\s/u
21
+
22
+ function isKoreanEnding(line, at) {
23
+ const prev = line[at - 1]
24
+ if (line[at] === ".") return KOREAN_PERIOD_ENDINGS.has(prev)
25
+ return line[at] === "?" && prev === KOREAN_QUESTION_ENDING
26
+ }
27
+
28
+ function isAbbreviation(line, from, at) {
29
+ let k = at
30
+ while (k > from && !SPACE.test(line[k - 1])) k--
31
+ const word = line.slice(k, at).replace(/^[("'[]+/u, "").toLowerCase()
32
+ if (word === "no") return /^\s*\d/u.test(line.slice(at + 1))
33
+ return ABBREVIATIONS.has(word)
34
+ }
35
+
36
+ function pushSegment(line, base, from, to, out) {
37
+ let start = from
38
+ let end = to
39
+ while (start < end && SPACE.test(line[start])) start++
40
+ while (end > start && SPACE.test(line[end - 1])) end--
41
+ const text = line.slice(start, end)
42
+ if (normalizeText(text) === "") return
43
+ out.push({ sid: `s${out.length + 1}`, start: base + start, end: base + end, text })
44
+ }
45
+
46
+ function splitLine(line, base, out) {
47
+ let from = 0
48
+ for (let i = 0; i < line.length; i++) {
49
+ if (!TERMINATORS.includes(line[i])) continue
50
+ let j = i + 1
51
+ while (j < line.length && (TERMINATORS.includes(line[j]) || CLOSERS.includes(line[j]))) j++
52
+ const atBreak = j >= line.length || SPACE.test(line[j])
53
+ const closed = [...line.slice(i + 1, j)].some((c) => CLOSERS.includes(c))
54
+ const korean = !closed && isKoreanEnding(line, i)
55
+ const abbreviation = line[i] === "." && !korean && isAbbreviation(line, from, i)
56
+ if ((atBreak || korean) && !abbreviation) {
57
+ pushSegment(line, base, from, j, out)
58
+ from = j
59
+ }
60
+ i = j - 1
61
+ }
62
+ pushSegment(line, base, from, line.length, out)
63
+ }
64
+
65
+ export function splitSentences(text) {
66
+ const source = String(text ?? "")
67
+ const out = []
68
+ let base = 0
69
+ while (base <= source.length) {
70
+ const newline = source.indexOf("\n", base)
71
+ const stop = newline === -1 ? source.length : newline
72
+ splitLine(source.slice(base, stop), base, out)
73
+ if (newline === -1) break
74
+ base = newline + 1
75
+ }
76
+ return out
77
+ }
@@ -0,0 +1,140 @@
1
+ // source-commands.mjs - the `source snapshot` and `claims index` subcommands of report-tools.
2
+ // A source dir is immutable: the same id with identical text is a no-op, different text fails
3
+ // (exit 1, snapshot_exists), because ledger rows pin the old hash. `--url` fetches with plain
4
+ // fetch (30 s timeout, no browser) and records extractor tool:source-snapshot@1; `--html` and
5
+ // `--text` import a browser-lane capture and record extractor model (lower trust), or the named
6
+ // extractor for `--text --extractor NAME@VERSION`; `--command` runs an argv without a shell and
7
+ // snapshots its stdout, with the argv, cwd, exit status, and output hashes in command.json.
8
+ // node: builtins and sibling imports only.
9
+
10
+ import { spawnSync } from "node:child_process"
11
+ import { existsSync, readFileSync, writeFileSync } from "node:fs"
12
+ import { join, resolve } from "node:path"
13
+
14
+ import { indexClaims } from "./claims-index.mjs"
15
+ import { CliError, parseArgs, sha256Of, writeJsonAtomic } from "./cli-support.mjs"
16
+ import { parseFidelityBlock } from "./fidelity-brief.mjs"
17
+ import { writeSnapshot } from "./source-snapshot.mjs"
18
+
19
+ const FETCH_TIMEOUT_MS = 30_000
20
+ const TOOL_EXTRACTOR = "tool:source-snapshot@1"
21
+ const SNAPSHOT_USAGE =
22
+ "usage: source snapshot --id S<n> (--url <u> | --html <file> | --text <file> [--extractor NAME@VERSION] | --command '<argv json>' --cwd DIR) [--kind page|command|document] [--provenance primary|secondary] [--revision R] --session-dir D"
23
+
24
+ function readInput(path) {
25
+ try {
26
+ return readFileSync(path, "utf8")
27
+ } catch (error) {
28
+ throw new CliError(`cannot read ${path}: ${error.code ?? error.message}`)
29
+ }
30
+ }
31
+
32
+ /** The session dir from --session-dir or SESSION_DIR; a usage error when neither is set. */
33
+ export function sessionDirOf(flags) {
34
+ const dir = flags["session-dir"] ?? process.env.SESSION_DIR
35
+ if (!dir || dir === true) throw new CliError("--session-dir is required (or set SESSION_DIR)")
36
+ return resolve(dir)
37
+ }
38
+
39
+ /** The snapshot of one source: meta.json, text.txt, and source.html when the source was a page. */
40
+ export function snapshotOf(sessionDir, id) {
41
+ const dir = join(sessionDir, "sources", String(id))
42
+ if (!existsSync(join(dir, "meta.json"))) throw new CliError(`source ${id} has no snapshot; run source snapshot --id ${id} first`)
43
+ const html = join(dir, "source.html")
44
+ return { meta: JSON.parse(readInput(join(dir, "meta.json"))), text: readInput(join(dir, "text.txt")), html: existsSync(html) ? readInput(html) : null }
45
+ }
46
+
47
+ /** Whether the brief's source floor lists this source as pointed at (`pointed only: a, b`). */
48
+ export function pointedFlag(sessionDir, url) {
49
+ const brief = join(sessionDir, "brief.md")
50
+ const floor = existsSync(brief) ? (parseFidelityBlock(readInput(brief)).fields?.["source floor"] ?? "") : ""
51
+ const match = /^pointed only:\s*(.+)$/i.exec(floor.trim())
52
+ return match ? match[1].split(",").map((entry) => entry.trim()).includes(url) : false
53
+ }
54
+
55
+ async function fetchSource(url) {
56
+ if (!URL.canParse(url)) throw new CliError(`--url is not a URL: ${url}`)
57
+ let response
58
+ try {
59
+ response = await fetch(url, { signal: AbortSignal.timeout(FETCH_TIMEOUT_MS) })
60
+ } catch (error) {
61
+ throw new CliError(`fetch failed for ${url}: ${error.message}`)
62
+ }
63
+ if (!response.ok) {
64
+ await response.body?.cancel()
65
+ throw new CliError(`fetch failed: HTTP ${response.status} for ${url}`)
66
+ }
67
+ const type = response.headers.get("content-type") ?? ""
68
+ if (/pdf/i.test(type)) {
69
+ await response.body?.cancel()
70
+ throw new CliError(`pdf response: ${url}; extract its text and use --text <file> --extractor NAME@VERSION`)
71
+ }
72
+ const body = await response.text()
73
+ return /html|xml/i.test(type) || /^\s*</.test(body) ? { url, html: body } : { url, text: body }
74
+ }
75
+
76
+ function runCommand(argvJson, cwd) {
77
+ let argv
78
+ try {
79
+ argv = JSON.parse(argvJson)
80
+ } catch (error) {
81
+ throw new CliError(`--command must be a JSON array of strings: ${error.message}`)
82
+ }
83
+ if (!Array.isArray(argv) || argv.length === 0 || !argv.every((part) => typeof part === "string" && part.length > 0)) {
84
+ throw new CliError("--command must be a non-empty JSON array of non-empty strings")
85
+ }
86
+ if (!cwd || cwd === true) throw new CliError("--command needs --cwd DIR")
87
+ const proc = spawnSync(argv[0], argv.slice(1), { cwd: resolve(cwd), encoding: "utf8", shell: false, maxBuffer: 64 * 1024 * 1024 })
88
+ if (proc.error) throw new CliError(`--command could not start: ${proc.error.message}`)
89
+ const receipt = { argv, cwd: resolve(cwd), exit_status: proc.status, stdout_sha256: sha256Of(proc.stdout), stderr_sha256: sha256Of(proc.stderr) }
90
+ return { path: `command:${argv.join(" ")}`, text: proc.stdout, kind: "command", receipt }
91
+ }
92
+
93
+ async function acquire(flags) {
94
+ const given = ["url", "html", "text", "command"].filter((name) => flags[name] !== undefined)
95
+ if (given.length !== 1) throw new CliError(SNAPSHOT_USAGE)
96
+ if (flags.extractor !== undefined && flags.text === undefined) throw new CliError("--extractor goes with --text only")
97
+ if (flags.url !== undefined) return { ...(await fetchSource(flags.url)), extractor: TOOL_EXTRACTOR }
98
+ if (flags.html !== undefined) return { path: resolve(flags.html), html: readInput(flags.html), extractor: "model" }
99
+ if (flags.text !== undefined) return { path: resolve(flags.text), text: readInput(flags.text), extractor: flags.extractor === undefined ? "model" : `extractor:${flags.extractor}` }
100
+ return { ...runCommand(flags.command, flags.cwd), extractor: TOOL_EXTRACTOR }
101
+ }
102
+
103
+ export async function runSource(argv) {
104
+ const [sub, ...rest] = argv
105
+ if (sub !== "snapshot") throw new CliError(SNAPSHOT_USAGE)
106
+ const args = parseArgs(rest)
107
+ const id = args.required("id")
108
+ const sourcesDir = join(sessionDirOf(args.flags), "sources")
109
+ const { receipt, ...input } = await acquire(args.flags)
110
+ if (args.flags.kind !== undefined) input.kind = args.flags.kind
111
+ if (args.flags.provenance !== undefined) input.provenance = args.flags.provenance
112
+ if (args.flags.revision !== undefined) input.revision = args.flags.revision
113
+ let written
114
+ try {
115
+ written = writeSnapshot(sourcesDir, id, input)
116
+ } catch (error) {
117
+ if (String(error.message).startsWith("snapshot_exists")) {
118
+ return { json: { command: "source snapshot", status: "fail", exitCode: 1, error: "snapshot_exists", message: error.message }, summary: `source snapshot: ${error.message}`, exitCode: 1 }
119
+ }
120
+ if (error instanceof TypeError) throw new CliError(error.message)
121
+ throw error
122
+ }
123
+ if (receipt) writeFileSync(join(written.dir, "command.json"), `${JSON.stringify(receipt, null, 2)}\n`)
124
+ const meta = JSON.parse(readFileSync(join(written.dir, "meta.json"), "utf8"))
125
+ const data = { source_id: id, directory: written.dir, snapshot_sha256: written.sha256, extractor: meta.extractor, kind: meta.kind, provenance: meta.provenance }
126
+ return { json: { command: "source snapshot", status: "pass", exitCode: 0, data }, summary: `source snapshot: ${id} ${written.sha256.slice(0, 12)} (${meta.extractor})`, exitCode: 0 }
127
+ }
128
+
129
+ export function runClaims(argv) {
130
+ const [sub, ...rest] = argv
131
+ const args = parseArgs(rest)
132
+ const [graph] = args.positionals
133
+ if (sub !== "index" || !graph) throw new CliError("usage: claims index <claim-graph.md> --out claims.json")
134
+ const out = args.required("out")
135
+ const index = indexClaims(readInput(graph))
136
+ if (index.errors.length > 0) throw new CliError(`claims index ${graph}: ${index.errors.join("; ")}`)
137
+ writeJsonAtomic(out, { schemaVersion: 1, graph: resolve(graph), graph_sha256: sha256Of(readFileSync(graph)), ...index })
138
+ const verified = index.claims.filter((claim) => claim.in_verified).length
139
+ return { json: { command: "claims index", out: resolve(out), claims: index.claims.length, verified }, summary: `claims index: ${index.claims.length} claim(s), ${verified} verified`, exitCode: 0 }
140
+ }