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,109 @@
1
+ import { describe, expect, test } from "bun:test"
2
+
3
+ import { splitSentences } from "./sentence-split.mjs"
4
+
5
+ type Sentence = { sid: string; start: number; end: number; text: string }
6
+
7
+ function at(records: Sentence[], index: number): Sentence {
8
+ const record = records[index]
9
+ if (record === undefined) throw new Error(`no sentence at index ${index}`)
10
+ return record
11
+ }
12
+
13
+ function expectSlices(text: string, records: Sentence[]): void {
14
+ for (const record of records) expect(text.slice(record.start, record.end)).toBe(record.text)
15
+ }
16
+
17
+ describe("splitSentences", () => {
18
+ test("#given a Korean paragraph with da/yo/nida endings #when split #then sids and offsets match each sentence", () => {
19
+ // given
20
+ const text = "매출은 100억 원입니다. 비용도 늘었어요. 이익은 줄었다.다만 회복이 기대된다."
21
+ // when
22
+ const records = splitSentences(text) as Sentence[]
23
+ // then
24
+ expect(records.map((r) => r.sid)).toEqual(["s1", "s2", "s3", "s4"])
25
+ expect(records.map((r) => r.text)).toEqual([
26
+ "매출은 100억 원입니다.",
27
+ "비용도 늘었어요.",
28
+ "이익은 줄었다.",
29
+ "다만 회복이 기대된다.",
30
+ ])
31
+ expect(at(records, 1).start).toBe(text.indexOf("비용도"))
32
+ expectSlices(text, records)
33
+ })
34
+
35
+ test("#given an English paragraph with abbreviations and decimals #when split #then e.g. and 3.2 do not end a sentence", () => {
36
+ // given
37
+ const text = "Use units, e.g. 3.2 m per bar. The next one follows! Is it fine? Yes."
38
+ // when
39
+ const records = splitSentences(text) as Sentence[]
40
+ // then
41
+ expect(records.map((r) => r.sid)).toEqual(["s1", "s2", "s3", "s4"])
42
+ expect(records.map((r) => r.text)).toEqual([
43
+ "Use units, e.g. 3.2 m per bar.",
44
+ "The next one follows!",
45
+ "Is it fine?",
46
+ "Yes.",
47
+ ])
48
+ expect(at(records, 3).start).toBe(text.indexOf("Yes."))
49
+ expect(at(records, 3).end).toBe(text.length)
50
+ expectSlices(text, records)
51
+ })
52
+
53
+ test("#given line breaks and blank lines #when split #then each non-empty line yields its own record", () => {
54
+ // given
55
+ const text = "매출은 100억 원이다. 다만 아직 확정되지 않았다.\n\n Second line without stop\nThird?"
56
+ // when
57
+ const records = splitSentences(text) as Sentence[]
58
+ // then
59
+ expect(records.map((r) => r.text)).toEqual([
60
+ "매출은 100억 원이다.",
61
+ "다만 아직 확정되지 않았다.",
62
+ "Second line without stop",
63
+ "Third?",
64
+ ])
65
+ expect(records.map((r) => r.sid)).toEqual(["s1", "s2", "s3", "s4"])
66
+ expectSlices(text, records)
67
+ })
68
+
69
+ test("#given Korean quoted speech closed right after a da ending #when split #then the quote stays inside one sentence", () => {
70
+ // given
71
+ const single = '그는 "매출이 늘었다."라고 말했다.'
72
+ const pair = '그는 "매출이 늘었다."라고 말했다. 다음 날 떨어졌다.'
73
+ const bracket = "그는 「좋아요.」라고 답했다."
74
+ // when
75
+ const singleRecords = splitSentences(single) as Sentence[]
76
+ const pairRecords = splitSentences(pair) as Sentence[]
77
+ const bracketRecords = splitSentences(bracket) as Sentence[]
78
+ // then
79
+ expect(singleRecords.map((r) => r.text)).toEqual([single])
80
+ expect(pairRecords.map((r) => r.text)).toEqual(['그는 "매출이 늘었다."라고 말했다.', "다음 날 떨어졌다."])
81
+ expect(pairRecords.map((r) => r.sid)).toEqual(["s1", "s2"])
82
+ expect(at(pairRecords, 0).start).toBe(0)
83
+ expect(at(pairRecords, 1).start).toBe(pair.indexOf("다음 날"))
84
+ expect(at(pairRecords, 1).end).toBe(pair.length)
85
+ expect(bracketRecords.map((r) => r.text)).toEqual([bracket])
86
+ expectSlices(pair, pairRecords)
87
+ })
88
+
89
+ test("#given the word no #when split #then it is an abbreviation only before a number", () => {
90
+ // given
91
+ const answer = "The answer was no. Then we left."
92
+ const numbered = "See No. 5 for details. Then stop."
93
+ // when
94
+ const answerRecords = splitSentences(answer) as Sentence[]
95
+ const numberedRecords = splitSentences(numbered) as Sentence[]
96
+ // then
97
+ expect(answerRecords.map((r) => r.text)).toEqual(["The answer was no.", "Then we left."])
98
+ expect(at(answerRecords, 1).start).toBe(answer.indexOf("Then"))
99
+ expect(numberedRecords.map((r) => r.text)).toEqual(["See No. 5 for details.", "Then stop."])
100
+ expectSlices(answer, answerRecords)
101
+ expectSlices(numbered, numberedRecords)
102
+ })
103
+
104
+ test("#given empty or whitespace-only text #when split #then no records are produced", () => {
105
+ // when / then
106
+ expect(splitSentences("")).toEqual([])
107
+ expect(splitSentences(" \n\t\n")).toEqual([])
108
+ })
109
+ })
@@ -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
+ }
@@ -0,0 +1,176 @@
1
+ // source-snapshot.mjs - the per-source snapshot that fact locators read.
2
+ //
3
+ // snapshotFromHtml turns a page into text.txt form: one line per block (prose blocks via
4
+ // proseText, one table cell per line; head, scripts, styles and code are dropped). writeSnapshot
5
+ // writes <sourcesDir>/<S-id>/ with text.txt (pages joined by a form feed), source.html when html
6
+ // was given, meta.json, sentences.jsonl (sid S<n>:s<k>) and acronyms.json. The sha256 covers
7
+ // text.txt only. Nothing here touches the network: the caller supplies the html or text.
8
+ // Plain Node ESM, node: builtins and sibling imports only.
9
+
10
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs"
11
+ import { join } from "node:path"
12
+
13
+ import { sha256Of } from "./cli-support.mjs"
14
+ import { parseHtml, PROSE_TAGS, proseText } from "./html-lite.mjs"
15
+ import { splitSentences } from "./sentence-split.mjs"
16
+
17
+ const SKIP = new Set(["head", "script", "style", "svg", "code", "pre", "kbd", "samp", "template", "noscript"])
18
+ const INLINE = new Set([
19
+ "a", "abbr", "b", "bdi", "bdo", "cite", "data", "del", "dfn", "em", "font", "i", "ins", "label", "mark", "q", "s",
20
+ "small", "span", "strong", "sub", "sup", "time", "u", "var",
21
+ ])
22
+ const SOURCE_ID = /^S[1-9]\d*$/
23
+ const EXTRACTOR = /^(?:tool:source-snapshot@1|model|extractor:[\w.@+-]+)$/
24
+ const KINDS = new Set(["page", "command", "document"])
25
+ const PROVENANCE = new Set(["primary", "secondary", "unknown"])
26
+ const ACRONYM_AFTER = /\(([A-Z]{2,6})\)/g
27
+ const ACRONYM_BEFORE = /(?<![\p{L}\p{N}])([A-Z]{2,6})\s?\(([A-Z][^()\n]{2,79})\)/gu
28
+ const NAME_WORD = /^[A-Z][\p{L}\p{N}-]*$/u
29
+ const CONNECTOR = new Set(["of", "and", "for", "the", "in", "on", "to", "&"])
30
+
31
+ /**
32
+ * Block lines of a page: { root, text, blocks: [{ node, start, end }] } where text joins the block
33
+ * texts with "\n" and text.slice(start, end) is one block. fact-locate reads cells through `node`.
34
+ */
35
+ export function snapshotBlocks(html) {
36
+ const root = parseHtml(html)
37
+ const lines = []
38
+ let buffer = ""
39
+ const flush = (node) => {
40
+ const line = buffer.replace(/\s+/g, " ").trim()
41
+ buffer = ""
42
+ if (line) lines.push({ node, text: line })
43
+ }
44
+ const walk = (node) => {
45
+ for (const child of node.children) {
46
+ if (child.tag === "#text") buffer += child.text
47
+ else if (SKIP.has(child.tag)) continue
48
+ else if (child.tag === "br") flush(node)
49
+ else if (INLINE.has(child.tag)) walk(child)
50
+ else if (PROSE_TAGS.has(child.tag)) {
51
+ flush(node)
52
+ const line = proseText(child)
53
+ if (line) lines.push({ node: child, text: line })
54
+ } else {
55
+ flush(node)
56
+ walk(child)
57
+ flush(child)
58
+ }
59
+ }
60
+ }
61
+ walk(root)
62
+ flush(root)
63
+ const blocks = []
64
+ let at = 0
65
+ for (const line of lines) {
66
+ blocks.push({ node: line.node, start: at, end: at + line.text.length })
67
+ at += line.text.length + 1
68
+ }
69
+ return { root, text: lines.map((line) => line.text).join("\n"), blocks }
70
+ }
71
+
72
+ /** text.txt form of an html page. */
73
+ export function snapshotFromHtml(html) {
74
+ return snapshotBlocks(html).text
75
+ }
76
+
77
+ /** Sentence index rows { sid: "S<n>:s<k>", start, end, text }; a form feed (page break) always ends a sentence. */
78
+ export function indexSentences(text, sourceId) {
79
+ return splitSentences(text.replace(/\f/g, "\n")).map((row) => ({
80
+ sid: `${sourceId}:${row.sid}`,
81
+ start: row.start,
82
+ end: row.end,
83
+ text: text.slice(row.start, row.end),
84
+ }))
85
+ }
86
+
87
+ const sentenceIdAt = (sentences, offset) => sentences.find((row) => row.start <= offset && offset < row.end)?.sid ?? null
88
+
89
+ /** "High Bandwidth Memory (HBM)": the capitalized words before the parenthesis, at most one per letter. */
90
+ function nameBefore(text, at, letters) {
91
+ const prefix = text.slice(Math.max(0, at - 160), at)
92
+ if (!/\S[ \t]?$/.test(prefix)) return null
93
+ const words = [...prefix.matchAll(/\S+/g)]
94
+ const picked = []
95
+ let content = 0
96
+ for (let k = words.length - 1; k >= 0 && content < letters; k--) {
97
+ const word = words[k][0]
98
+ if (NAME_WORD.test(word)) content++
99
+ else if (!CONNECTOR.has(word) || picked.length === 0) break
100
+ picked.unshift(words[k])
101
+ }
102
+ while (picked.length > 0 && CONNECTOR.has(picked[0][0])) picked.shift()
103
+ if (picked.length === 0) return null
104
+ const start = at - prefix.length + picked[0].index
105
+ const end = at - prefix.length + picked[picked.length - 1].index + picked[picked.length - 1][0].length
106
+ return { start, expansion: text.slice(start, end) }
107
+ }
108
+
109
+ /** Every `Full Name (ACR)` and `ACR (Full Name)` in the text, ordered by position. */
110
+ export function findAcronyms(text, sentences) {
111
+ const entries = []
112
+ for (const match of text.matchAll(ACRONYM_AFTER)) {
113
+ const name = nameBefore(text, match.index, match[1].length)
114
+ if (!name || name.expansion === match[1]) continue
115
+ entries.push({ acronym: match[1], expansion: name.expansion, span: [name.start, match.index + match[0].length] })
116
+ }
117
+ for (const match of text.matchAll(ACRONYM_BEFORE)) {
118
+ const expansion = match[2].trim()
119
+ if (!/\s/.test(expansion) || expansion === expansion.toUpperCase()) continue
120
+ entries.push({ acronym: match[1], expansion, span: [match.index, match.index + match[0].length] })
121
+ }
122
+ return entries
123
+ .sort((a, b) => a.span[0] - b.span[0])
124
+ .map((entry) => ({ acronym: entry.acronym, expansion: entry.expansion, sentence_id: sentenceIdAt(sentences, entry.span[0]), span: entry.span }))
125
+ }
126
+
127
+ function snapshotText(input) {
128
+ if (Array.isArray(input.pages)) return input.pages.join("\f")
129
+ if (typeof input.text === "string") return input.text
130
+ if (typeof input.html === "string") return snapshotFromHtml(input.html)
131
+ throw new TypeError("writeSnapshot needs text, pages, or html")
132
+ }
133
+
134
+ function checkInput(sourceId, input) {
135
+ if (!SOURCE_ID.test(String(sourceId))) throw new TypeError(`source id must be S<n>, got ${sourceId}`)
136
+ if (!EXTRACTOR.test(String(input.extractor))) throw new TypeError(`extractor must be tool:source-snapshot@1, model, or extractor:<name>, got ${input.extractor}`)
137
+ if (typeof (input.url ?? input.path) !== "string") throw new TypeError("writeSnapshot needs a url or a path")
138
+ if (input.kind !== undefined && !KINDS.has(input.kind)) throw new TypeError(`kind must be page, command, or document, got ${input.kind}`)
139
+ if (input.provenance !== undefined && !PROVENANCE.has(input.provenance)) throw new TypeError(`provenance must be primary, secondary, or unknown, got ${input.provenance}`)
140
+ }
141
+
142
+ /**
143
+ * Write <sourcesDir>/<sourceId>/ for one source; returns { dir, sha256 } with sha256 over text.txt.
144
+ * input: { url | path, text | pages | html, extractor, observed_at?, revision?, kind?, provenance? }.
145
+ * Re-writing the same text is a no-op change; a different text over an existing snapshot throws
146
+ * snapshot_exists, because ledger rows pin the old hash.
147
+ */
148
+ export function writeSnapshot(sourcesDir, sourceId, input) {
149
+ checkInput(sourceId, input)
150
+ const text = snapshotText(input)
151
+ const sha256 = sha256Of(Buffer.from(text, "utf8"))
152
+ const dir = join(sourcesDir, sourceId)
153
+ const textPath = join(dir, "text.txt")
154
+ if (existsSync(textPath) && sha256Of(readFileSync(textPath)) !== sha256) {
155
+ throw new Error(`snapshot_exists: ${sourceId} already holds a different text.txt`)
156
+ }
157
+ mkdirSync(dir, { recursive: true })
158
+ writeFileSync(textPath, text)
159
+ if (typeof input.html === "string") writeFileSync(join(dir, "source.html"), input.html)
160
+ const meta = {
161
+ source_id: sourceId,
162
+ url: input.url ?? input.path,
163
+ observed_at: input.observed_at ?? new Date().toISOString(),
164
+ sha256,
165
+ extractor: input.extractor,
166
+ revision: input.revision ?? null,
167
+ kind: input.kind ?? (input.url !== undefined || typeof input.html === "string" ? "page" : "document"),
168
+ provenance: input.provenance ?? "unknown",
169
+ }
170
+ writeFileSync(join(dir, "meta.json"), `${JSON.stringify(meta, null, 2)}\n`)
171
+ const sentences = indexSentences(text, sourceId)
172
+ writeFileSync(join(dir, "sentences.jsonl"), sentences.map((row) => `${JSON.stringify(row)}\n`).join(""))
173
+ const acronyms = { schemaVersion: 1, source_id: sourceId, snapshot_sha256: sha256, entries: findAcronyms(text, sentences) }
174
+ writeFileSync(join(dir, "acronyms.json"), `${JSON.stringify(acronyms, null, 2)}\n`)
175
+ return { dir, sha256 }
176
+ }
@@ -0,0 +1,113 @@
1
+ import { describe, expect, onTestFinished, test } from "bun:test"
2
+ import { createHash } from "node:crypto"
3
+ import { existsSync, mkdtempSync, readFileSync, rmSync } from "node:fs"
4
+ import { tmpdir } from "node:os"
5
+ import { join } from "node:path"
6
+
7
+ import { snapshotFromHtml, writeSnapshot } from "./source-snapshot.mjs"
8
+
9
+ const FIXTURES = join(import.meta.dir, "tests", "fixtures", "fidelity", "sources")
10
+ const S4_HTML = readFileSync(join(FIXTURES, "S4.html"), "utf8")
11
+ const S9_TEXT = readFileSync(join(FIXTURES, "S9.txt"), "utf8")
12
+
13
+ function tempDir(): string {
14
+ const dir = mkdtempSync(join(tmpdir(), "ulw-snapshot-"))
15
+ onTestFinished(() => rmSync(dir, { recursive: true, force: true }))
16
+ return dir
17
+ }
18
+
19
+ type Sentence = { sid: string; start: number; end: number; text: string }
20
+
21
+ function readSentences(dir: string): Sentence[] {
22
+ return readFileSync(join(dir, "sentences.jsonl"), "utf8")
23
+ .split("\n")
24
+ .filter((line) => line !== "")
25
+ .map((line) => JSON.parse(line))
26
+ }
27
+
28
+ describe("snapshotFromHtml", () => {
29
+ test("#given a page with prose, a table, a script and a comment #when snapshotted #then blocks are lines and each table cell is its own line", () => {
30
+ const lines = snapshotFromHtml(S4_HTML).split("\n")
31
+ expect(lines).toContain("Span length: 320 m")
32
+ expect(lines).toContain("After the delay, we estimate completion no earlier than 2027.")
33
+ const header = lines.indexOf("Metric")
34
+ expect(lines.slice(header, header + 4)).toEqual(["Metric", "Length", "Span", "320 m"])
35
+ const text = lines.join("\n")
36
+ expect(text).not.toContain("not prose")
37
+ expect(text).not.toContain("internal draft comment")
38
+ expect(text).not.toContain("p { margin")
39
+ })
40
+ })
41
+
42
+ describe("writeSnapshot", () => {
43
+ test("#given html #when written #then text.txt, source.html, meta.json, sentences.jsonl and acronyms.json exist and sha256 covers text.txt", () => {
44
+ const sources = tempDir()
45
+ const result = writeSnapshot(sources, "S4", { url: "https://example.test/bridge", html: S4_HTML, extractor: "tool:source-snapshot@1", observed_at: "2026-10-23T00:00:00Z" })
46
+ expect(result.dir).toBe(join(sources, "S4"))
47
+ for (const name of ["text.txt", "source.html", "meta.json", "sentences.jsonl", "acronyms.json"]) expect(existsSync(join(result.dir, name))).toBe(true)
48
+ const text = readFileSync(join(result.dir, "text.txt"))
49
+ expect(result.sha256).toBe(createHash("sha256").update(text).digest("hex"))
50
+ expect(readFileSync(join(result.dir, "source.html"), "utf8")).toBe(S4_HTML)
51
+ const meta = JSON.parse(readFileSync(join(result.dir, "meta.json"), "utf8"))
52
+ expect(meta).toEqual({
53
+ source_id: "S4",
54
+ url: "https://example.test/bridge",
55
+ observed_at: "2026-10-23T00:00:00Z",
56
+ sha256: result.sha256,
57
+ extractor: "tool:source-snapshot@1",
58
+ revision: null,
59
+ kind: "page",
60
+ provenance: "unknown",
61
+ })
62
+ })
63
+
64
+ test("#given a written snapshot #when sentences.jsonl is read #then every row slices text.txt and carries an S<n>:s<k> id", () => {
65
+ const sources = tempDir()
66
+ const { dir } = writeSnapshot(sources, "S4", { url: "https://example.test/bridge", html: S4_HTML, extractor: "tool:source-snapshot@1" })
67
+ const text = readFileSync(join(dir, "text.txt"), "utf8")
68
+ const sentences = readSentences(dir)
69
+ expect(sentences.length).toBeGreaterThanOrEqual(4)
70
+ sentences.forEach((row, index) => {
71
+ expect(row.sid).toBe(`S4:s${index + 1}`)
72
+ expect(text.slice(row.start, row.end)).toBe(row.text)
73
+ })
74
+ })
75
+
76
+ test("#given both acronym forms #when written #then acronyms.json lists each expansion with its sentence and span", () => {
77
+ const sources = tempDir()
78
+ const text = "The deck uses High Bandwidth Memory (HBM) sensors.\nA second note cites TSV (Through Silicon Via) bonding."
79
+ const { dir, sha256 } = writeSnapshot(sources, "S2", { path: "notes/memo.txt", text, extractor: "model" })
80
+ const acronyms = JSON.parse(readFileSync(join(dir, "acronyms.json"), "utf8"))
81
+ expect(acronyms.source_id).toBe("S2")
82
+ expect(acronyms.snapshot_sha256).toBe(sha256)
83
+ const entries = acronyms.entries.map((entry: { acronym: string; expansion: string; sentence_id: string; span: number[] }) => [entry.acronym, entry.expansion, entry.sentence_id, text.slice(entry.span[0], entry.span[1])])
84
+ expect(entries).toEqual([
85
+ ["HBM", "High Bandwidth Memory", "S2:s1", "High Bandwidth Memory (HBM)"],
86
+ ["TSV", "Through Silicon Via", "S2:s2", "TSV (Through Silicon Via)"],
87
+ ])
88
+ expect(JSON.parse(readFileSync(join(dir, "meta.json"), "utf8")).kind).toBe("document")
89
+ expect(existsSync(join(dir, "source.html"))).toBe(false)
90
+ })
91
+
92
+ test("#given pages #when written #then pages are joined by a form feed and no sentence crosses a page", () => {
93
+ const sources = tempDir()
94
+ const { dir } = writeSnapshot(sources, "S7", { path: "report.pdf", pages: ["First page ends here", "second page starts here."], extractor: "extractor:pdftotext" })
95
+ expect(readFileSync(join(dir, "text.txt"), "utf8")).toBe("First page ends here\fsecond page starts here.")
96
+ expect(readSentences(dir).map((row) => row.text)).toEqual(["First page ends here", "second page starts here."])
97
+ })
98
+
99
+ test("#given Korean text #when written #then the sentence index covers it", () => {
100
+ const sources = tempDir()
101
+ const { dir } = writeSnapshot(sources, "S9", { path: "S9.txt", text: S9_TEXT, extractor: "tool:source-snapshot@1" })
102
+ expect(readSentences(dir).map((row) => row.sid)).toEqual(["S9:s1"])
103
+ })
104
+
105
+ test("#given an invalid extractor, source id, or a different text over an existing snapshot #when written #then it throws", () => {
106
+ const sources = tempDir()
107
+ expect(() => writeSnapshot(sources, "S1", { path: "a.txt", text: "x", extractor: "guess" })).toThrow()
108
+ expect(() => writeSnapshot(sources, "source1", { path: "a.txt", text: "x", extractor: "model" })).toThrow()
109
+ writeSnapshot(sources, "S1", { path: "a.txt", text: "first", extractor: "model" })
110
+ expect(() => writeSnapshot(sources, "S1", { path: "a.txt", text: "first", extractor: "model" })).not.toThrow()
111
+ expect(() => writeSnapshot(sources, "S1", { path: "a.txt", text: "second", extractor: "model" })).toThrow(/snapshot_exists/)
112
+ })
113
+ })
@@ -0,0 +1,3 @@
1
+ fact_id,plot_value,unit
2
+ F5,12,%
3
+ F6,27,%
@@ -0,0 +1,13 @@
1
+ # Claim graph
2
+
3
+ ## verified-claims
4
+
5
+ - C1: The bridge span is up to 320 m.
6
+
7
+ ## Claims
8
+
9
+ | claim_id | statement | claim type | risk tier | status | final synthesis location |
10
+ |---|---|---|---|---|---|
11
+ | C1 | The bridge span is up to 320 m. | factual | high | supported | findings |
12
+ | C2 | Revenue grew about 10 percent in 2025. | factual | normal | partial | findings |
13
+ | C3 | The project finished ahead of schedule. | factual | high | refuted | gaps |
@@ -0,0 +1,89 @@
1
+ // gen-legacy-dev.mjs - regenerates ledger-legacy-dev.json with dev's own pre-registry writer, so the
2
+ // dimension 31 tests load every value shape dev could write, not hand-picked rows.
3
+ // Pinned dev commit: 99220c4e95f9ef3a667834486f2caa03873deeab (origin/dev when the fixture was generated).
4
+ // Usage: node gen-legacy-dev.mjs [dev-sha]. Needs git and the commit in the local clone; node: builtins only.
5
+
6
+ import { execFileSync } from "node:child_process"
7
+ import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs"
8
+ import { tmpdir } from "node:os"
9
+ import { dirname, join } from "node:path"
10
+ import { fileURLToPath, pathToFileURL } from "node:url"
11
+
12
+ const DEV_SHA = process.argv[2] ?? "99220c4e95f9ef3a667834486f2caa03873deeab"
13
+ const SCRIPTS = "packages/shared-skills/skills/ulw-research/scripts"
14
+ // fact-ledger.mjs and every module it imports at the pinned commit.
15
+ const WRITER_FILES = ["fact-ledger.mjs", "fact-ledger-kinds.mjs", "fact-ledger-schema.mjs", "cli-support.mjs", "entry-guard.mjs"]
16
+ const HERE = dirname(fileURLToPath(import.meta.url))
17
+ const OUT = join(HERE, "ledger-legacy-dev.json")
18
+
19
+ const at = { artifact: "sources/S1/text.txt", line: 2, char_start: 0, char_end: 30 }
20
+ const sha = "a".repeat(64)
21
+ const base = { extracted_by: "fact add --find", observed_at: "2026-10-20T08:00:00Z" }
22
+
23
+ function measured(label, value, unit) {
24
+ const raw = `${value} ${unit}`
25
+ return { ...base, kind: "measured", label, value, unit, raw, quote: `The figure is ${raw}.`, value_span: [14, 14 + value.length], source_id: "S1", locator: at, snapshot_sha256: sha }
26
+ }
27
+
28
+ function assumed(label, value, unit, exact) {
29
+ return { ...base, kind: "assumed", label, value, unit, exact, raw: "", quote: "", extracted_by: "fact add --assumed", rationale: "planning assumption from the brief" }
30
+ }
31
+
32
+ function legacy(kind, label, fields) {
33
+ return { ...base, kind, label, raw: "", quote: "", extracted_by: "fact derive", ...fields }
34
+ }
35
+
36
+ // Every kind and every value shape dev's addRow accepts: alias units (English and Korean count and
37
+ // currency words, written as escapes so this file stays ASCII), leading zeros, negative values,
38
+ // assumed rows with unreduced or zero-padded exacts, and a legacy derived and converted row. The
39
+ // last three are shapes dev accepted that the strict dimensions do not (empty metadata, an operation
40
+ // on a source row, an extra key in exact): they must load and be refused only as operands.
41
+ const SEEDS = [
42
+ measured("north-span", "12", "kilometers"),
43
+ measured("south-span", "8.5", "kilometers"),
44
+ measured("share-online", "41.2", "per cent"),
45
+ measured("share-gap", "1.5", "pp"),
46
+ measured("staff-count", "320", "\uBA85"),
47
+ measured("unit-price", "750", "\uCC9C\uC6D0"),
48
+ measured("padded-span", "007", "km"),
49
+ measured("net-change", "-2.5", "per cent"),
50
+ { ...base, kind: "quote", label: "quote-claim", raw: "grew fast", quote: "The market grew fast.", value_span: [11, 20], source_id: "S1", locator: at, snapshot_sha256: sha },
51
+ assumed("growth-assumption", "3", "%", null),
52
+ assumed("whole-ratio", "1", "%", { num: "2", den: "2" }),
53
+ assumed("quarter-ratio", "2.5", "kilometers", { num: "10", den: "4" }),
54
+ assumed("padded-exact", "7", "km", { num: "007", den: "01" }),
55
+ assumed("negative-exact", "-2.5", "per cent", { num: "-10", den: "04" }),
56
+ legacy("derived", "total-span", { value: "20.5", unit: "kilometers", exact: { num: "82", den: "4" }, operation: "sum", operands: ["F1", "F2"], derived_from: ["F1", "F2"], formula: "F1 + F2" }),
57
+ legacy("converted", "span-metres", { value: "12000", unit: "m", exact: { num: "24000", den: "2" }, operation: "convert", operands: ["F1"], derived_from: ["F1"], formula: "F1 * 1000", factor: "1000" }),
58
+ { ...measured("blank-entity", "5", "km"), entity: "" },
59
+ { ...measured("tagged-sum", "6", "km"), operation: "sum" },
60
+ assumed("keyed-exact", "3", "km", { num: "3", den: "1", note: "from the brief" }),
61
+ ]
62
+
63
+ /** JSON with every non-ASCII character as a \u escape, tab-indented like the other fixtures. */
64
+ function asciiJson(value) {
65
+ return `${JSON.stringify(value, null, "\t").replace(/[\u0080-\uffff]/g, (c) => `\\u${c.charCodeAt(0).toString(16).padStart(4, "0")}`)}\n`
66
+ }
67
+
68
+ async function main() {
69
+ const root = execFileSync("git", ["rev-parse", "--show-toplevel"], { cwd: HERE, encoding: "utf8" }).trim()
70
+ const work = mkdtempSync(join(tmpdir(), "gen-legacy-dev-"))
71
+ try {
72
+ mkdirSync(join(work, "scripts"))
73
+ for (const file of WRITER_FILES) {
74
+ const source = execFileSync("git", ["show", `${DEV_SHA}:${SCRIPTS}/${file}`], { cwd: root, encoding: "utf8" })
75
+ writeFileSync(join(work, "scripts", file), source)
76
+ }
77
+ const dev = await import(pathToFileURL(join(work, "scripts", "fact-ledger.mjs")).href)
78
+ const ledger = dev.emptyLedger()
79
+ for (const seed of SEEDS) dev.addRow(ledger, seed)
80
+ const errors = dev.validateLedger(ledger)
81
+ if (errors.length > 0) throw new Error(`dev's validateLedger rejected the generated ledger: ${errors.join("; ")}`)
82
+ writeFileSync(OUT, asciiJson(ledger))
83
+ process.stdout.write(`wrote ${OUT} (${ledger.rows.length} rows, dev ${DEV_SHA})\n`)
84
+ } finally {
85
+ rmSync(work, { recursive: true, force: true })
86
+ }
87
+ }
88
+
89
+ await main()