lazycodex-ai 5.1.43 → 5.1.45

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 (273) hide show
  1. package/dist/cli/index.js +948 -416
  2. package/dist/cli/service-auth/keystore.d.ts +4 -2
  3. package/dist/cli-node/index.js +948 -416
  4. package/docs/reference/web-terminal-visual-qa.md +8 -0
  5. package/package.json +1 -1
  6. package/packages/lsp-daemon/dist/cli.js +4 -4
  7. package/packages/lsp-daemon/dist/client.js +4 -4
  8. package/packages/lsp-daemon/dist/index.js +4 -4
  9. package/packages/lsp-daemon/package.json +3 -3
  10. package/packages/lsp-tools-mcp/dist/cli.js +4 -4
  11. package/packages/lsp-tools-mcp/dist/mcp.js +4 -4
  12. package/packages/lsp-tools-mcp/dist/tools.js +4 -4
  13. package/packages/lsp-tools-mcp/package.json +3 -3
  14. package/packages/omo-codex/plugin/.codex-plugin/plugin.json +1 -1
  15. package/packages/omo-codex/plugin/components/bootstrap/dist/cli.js +62 -21
  16. package/packages/omo-codex/plugin/components/bootstrap/hooks/hooks.json +1 -1
  17. package/packages/omo-codex/plugin/components/bootstrap/package.json +1 -1
  18. package/packages/omo-codex/plugin/components/bootstrap/src/hook.ts +1 -1
  19. package/packages/omo-codex/plugin/components/comment-checker/biome.json +1 -21
  20. package/packages/omo-codex/plugin/components/comment-checker/hooks/hooks.json +1 -1
  21. package/packages/omo-codex/plugin/components/comment-checker/package.json +4 -4
  22. package/packages/omo-codex/plugin/components/git-bash/hooks/hooks.json +2 -2
  23. package/packages/omo-codex/plugin/components/git-bash/package.json +1 -1
  24. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/biome.json +1 -21
  25. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/hooks/hooks.json +1 -1
  26. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/package.json +4 -4
  27. package/packages/omo-codex/plugin/components/lsp/biome.json +1 -21
  28. package/packages/omo-codex/plugin/components/lsp/dist/.omo-runtime-manifest.json +3 -3
  29. package/packages/omo-codex/plugin/components/lsp/dist/cli.js +4 -4
  30. package/packages/omo-codex/plugin/components/lsp/hooks/hooks.json +2 -2
  31. package/packages/omo-codex/plugin/components/lsp/package.json +4 -4
  32. package/packages/omo-codex/plugin/components/lsp/scripts/build-runtime.mjs +0 -1
  33. package/packages/omo-codex/plugin/components/lsp/test/codex-hook-not-installed.test.ts +1 -1
  34. package/packages/omo-codex/plugin/components/rules/biome.json +1 -21
  35. package/packages/omo-codex/plugin/components/rules/hooks/hooks.json +4 -4
  36. package/packages/omo-codex/plugin/components/rules/package.json +4 -4
  37. package/packages/omo-codex/plugin/components/teammode/hooks/hooks.json +1 -1
  38. package/packages/omo-codex/plugin/components/teammode/package.json +1 -1
  39. package/packages/omo-codex/plugin/components/telemetry/biome.json +1 -21
  40. package/packages/omo-codex/plugin/components/telemetry/dist/cli.js +90 -1
  41. package/packages/omo-codex/plugin/components/telemetry/dist/posthog.js +91 -1
  42. package/packages/omo-codex/plugin/components/telemetry/hooks/hooks.json +1 -1
  43. package/packages/omo-codex/plugin/components/telemetry/package.json +4 -4
  44. package/packages/omo-codex/plugin/components/ultrawork/agents/lazycodex-gate-reviewer.toml +2 -2
  45. package/packages/omo-codex/plugin/components/ultrawork/biome.json +1 -21
  46. package/packages/omo-codex/plugin/components/ultrawork/hooks/hooks.json +1 -1
  47. package/packages/omo-codex/plugin/components/ultrawork/package.json +4 -4
  48. package/packages/omo-codex/plugin/components/ulw-execute-continuation/biome.json +1 -21
  49. package/packages/omo-codex/plugin/components/ulw-execute-continuation/directive.md +1 -1
  50. package/packages/omo-codex/plugin/components/ulw-execute-continuation/hooks/hooks.json +1 -1
  51. package/packages/omo-codex/plugin/components/ulw-execute-continuation/package.json +4 -4
  52. package/packages/omo-codex/plugin/components/ulw-loop/biome.json +1 -21
  53. package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint.js +1 -1
  54. package/packages/omo-codex/plugin/components/ulw-loop/dist/cli.js +7 -7
  55. package/packages/omo-codex/plugin/components/ulw-loop/dist/codex-goal-instruction.js +5 -5
  56. package/packages/omo-codex/plugin/components/ulw-loop/dist/stop-resume-hook.js +1 -1
  57. package/packages/omo-codex/plugin/components/ulw-loop/hooks/hooks.json +5 -5
  58. package/packages/omo-codex/plugin/components/ulw-loop/package.json +4 -4
  59. package/packages/omo-codex/plugin/components/ulw-loop/src/checkpoint.ts +1 -1
  60. package/packages/omo-codex/plugin/components/ulw-loop/src/codex-goal-instruction.ts +5 -5
  61. package/packages/omo-codex/plugin/components/ulw-loop/src/stop-resume-hook.ts +1 -1
  62. package/packages/omo-codex/plugin/components/ulw-loop/test/fixtures/commit-mutations.mjs +2 -0
  63. package/packages/omo-codex/plugin/hooks/post-compact-resetting-git-bash-mcp-reminder.json +1 -1
  64. package/packages/omo-codex/plugin/hooks/post-compact-resetting-lsp-diagnostics-cache.json +1 -1
  65. package/packages/omo-codex/plugin/hooks/post-compact-resetting-project-rule-cache.json +1 -1
  66. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-comments.json +1 -1
  67. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-lsp-diagnostics.json +1 -1
  68. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-thread-title-hygiene.json +1 -1
  69. package/packages/omo-codex/plugin/hooks/post-tool-use-matching-project-rules.json +1 -1
  70. package/packages/omo-codex/plugin/hooks/post-tool-use-recording-spawn-admission.json +1 -1
  71. package/packages/omo-codex/plugin/hooks/pre-tool-use-enforcing-unlimited-goal-budget.json +1 -1
  72. package/packages/omo-codex/plugin/hooks/pre-tool-use-guarding-ulw-loop-spawns.json +1 -1
  73. package/packages/omo-codex/plugin/hooks/pre-tool-use-recommending-git-bash-mcp.json +1 -1
  74. package/packages/omo-codex/plugin/hooks/session-start-checking-auto-update.json +1 -1
  75. package/packages/omo-codex/plugin/hooks/session-start-checking-bootstrap-provisioning.json +1 -1
  76. package/packages/omo-codex/plugin/hooks/session-start-loading-project-rules.json +1 -1
  77. package/packages/omo-codex/plugin/hooks/session-start-recording-session-telemetry.json +1 -1
  78. package/packages/omo-codex/plugin/hooks/stop-checking-ulw-execute-continuation.json +1 -1
  79. package/packages/omo-codex/plugin/hooks/stop-checking-ulw-loop-resume.json +1 -1
  80. package/packages/omo-codex/plugin/hooks/subagent-stop-verifying-lazycodex-executor-evidence.json +1 -1
  81. package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ultrawork-trigger.json +1 -1
  82. package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ulw-loop-steering.json +1 -1
  83. package/packages/omo-codex/plugin/hooks/user-prompt-submit-loading-project-rules.json +1 -1
  84. package/packages/omo-codex/plugin/package-lock.json +12 -12
  85. package/packages/omo-codex/plugin/package.json +1 -1
  86. package/packages/omo-codex/plugin/scripts/sync-skills.mjs +7 -7
  87. package/packages/omo-codex/plugin/shared/test/config-loader.test.ts +2 -0
  88. package/packages/omo-codex/plugin/skills/browser/SKILL.md +21 -22
  89. package/packages/omo-codex/plugin/skills/browser/references/commands.md +2 -9
  90. package/packages/omo-codex/plugin/skills/browser/references/install.md +1 -2
  91. package/packages/omo-codex/plugin/skills/browser/runtime/omowright/manifest.json +1 -1
  92. package/packages/omo-codex/plugin/skills/browser/scripts/browser-doctor.mjs +2 -2
  93. package/packages/omo-codex/plugin/skills/browser/scripts/browser-engine-guard.mjs +63 -487
  94. package/packages/omo-codex/plugin/skills/browser/scripts/browser-install.mjs +1 -1
  95. package/packages/omo-codex/plugin/skills/browser/scripts/omowright.mjs +1 -10
  96. package/packages/omo-codex/plugin/skills/frontend/SKILL.md +79 -118
  97. package/packages/omo-codex/plugin/skills/frontend/references/design/README.md +75 -224
  98. package/packages/omo-codex/plugin/skills/frontend/references/design/stylegallery.md +32 -27
  99. package/packages/omo-codex/plugin/skills/frontend/references/perfection/README.md +2 -2
  100. package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/check-accent-stripes.ts +8 -24
  101. package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/check-draft-guard.ts +40 -0
  102. package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/collect-files.ts +53 -0
  103. package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/draft-guard/findings.ts +16 -0
  104. package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/draft-guard/lint.ts +38 -0
  105. package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/draft-guard/rules.ts +69 -0
  106. package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/draft-guard/text-nodes.ts +121 -0
  107. package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/fixtures/draft-guard/negative/attr-lookalikes.tsx +10 -0
  108. package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/fixtures/draft-guard/negative/bound-values.tsx +12 -0
  109. package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/fixtures/draft-guard/negative/const-generics.tsx +3 -0
  110. package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/fixtures/draft-guard/negative/generics.tsx +7 -0
  111. package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/fixtures/draft-guard/negative/measured-stats.html +6 -0
  112. package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/fixtures/draft-guard/negative/plain-copy.html +9 -0
  113. package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/fixtures/draft-guard/negative/state-copy.html +11 -0
  114. package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/fixtures/draft-guard/positive/attribute-edges.html +7 -0
  115. package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/fixtures/draft-guard/positive/components.tsx +16 -0
  116. package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/fixtures/draft-guard/positive/decorative-badge.vue +7 -0
  117. package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/fixtures/draft-guard/positive/emoji-icons.tsx +9 -0
  118. package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/fixtures/draft-guard/positive/jsx-edges.tsx +1 -0
  119. package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/fixtures/draft-guard/positive/promo-copy.html +6 -0
  120. package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/fixtures/draft-guard/positive/promo-copy.tsx +8 -0
  121. package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/fixtures/draft-guard/positive/vanity-stats.html +5 -0
  122. package/packages/omo-codex/plugin/skills/init-deep/SKILL.md +1 -1
  123. package/packages/omo-codex/plugin/skills/programming/SKILL.md +10 -7
  124. package/packages/omo-codex/plugin/skills/programming/references/typescript/README.md +10 -7
  125. package/packages/omo-codex/plugin/skills/programming/references/typescript/bootstrap.md +9 -23
  126. package/packages/omo-codex/plugin/skills/programming/references/typescript/oxc-lint.md +130 -0
  127. package/packages/omo-codex/plugin/skills/programming/references/typescript/tsconfig-strict.md +6 -62
  128. package/packages/omo-codex/plugin/skills/programming/scripts/typescript/check-no-excuse-rules.ts +12 -12
  129. package/packages/omo-codex/plugin/skills/programming/scripts/typescript/new-project.ts +20 -81
  130. package/packages/omo-codex/plugin/skills/programming/scripts/typescript/oxc/.oxfmtrc.json +9 -0
  131. package/packages/omo-codex/plugin/skills/programming/scripts/typescript/oxc/.oxlintrc.json +46 -0
  132. package/packages/omo-codex/plugin/skills/programming/scripts/typescript/oxc/lint/oxlint-plugin/LICENSE +21 -0
  133. package/packages/omo-codex/plugin/skills/programming/scripts/typescript/oxc/lint/oxlint-plugin/index.js +10 -0
  134. package/packages/omo-codex/plugin/skills/programming/scripts/typescript/oxc/lint/oxlint-plugin/no-em-dash-copy.js +28 -0
  135. package/packages/omo-codex/plugin/skills/programming/scripts/typescript/oxc/lint/oxlint-plugin/require-suppression-reason.js +62 -0
  136. package/packages/omo-codex/plugin/skills/programming/scripts/typescript/oxc/tsconfig.json +29 -0
  137. package/packages/omo-codex/plugin/skills/refactor/SKILL.md +14 -14
  138. package/packages/omo-codex/plugin/skills/remove-ai-slops/SKILL.md +1 -1
  139. package/packages/omo-codex/plugin/skills/review-work/SKILL.md +2 -2
  140. package/packages/omo-codex/plugin/skills/ulw-execute/SKILL.md +2 -2
  141. package/packages/omo-codex/plugin/skills/ulw-loop/SKILL.md +1 -1
  142. package/packages/omo-codex/plugin/skills/ulw-research/AGENTS.md +9 -3
  143. package/packages/omo-codex/plugin/skills/ulw-research/SKILL.md +2 -2
  144. package/packages/omo-codex/plugin/skills/ulw-research/references/deliverable-phase.md +7 -6
  145. package/packages/omo-codex/plugin/skills/ulw-research/references/fact-ledger-grammar.md +116 -0
  146. package/packages/omo-codex/plugin/skills/ulw-research/references/report-gates.md +51 -3
  147. package/packages/omo-codex/plugin/skills/ulw-research/scripts/claims-index.mjs +125 -0
  148. package/packages/omo-codex/plugin/skills/ulw-research/scripts/contracts.mjs +68 -3
  149. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-ledger-kinds.mjs +136 -0
  150. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-ledger-schema.mjs +204 -0
  151. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-ledger.mjs +103 -0
  152. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-brief.mjs +145 -0
  153. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-state.mjs +63 -0
  154. package/packages/omo-codex/plugin/skills/ulw-research/scripts/gates-static.mjs +1 -1
  155. package/packages/omo-codex/plugin/skills/ulw-research/scripts/gates-structure.mjs +1 -1
  156. package/packages/omo-codex/plugin/skills/ulw-research/scripts/gates-text.mjs +1 -27
  157. package/packages/omo-codex/plugin/skills/ulw-research/scripts/lexicon.mjs +47 -0
  158. package/packages/omo-codex/plugin/skills/ulw-research/scripts/numerals.mjs +226 -0
  159. package/packages/omo-codex/plugin/skills/ulw-research/scripts/outcome.mjs +2 -3
  160. package/packages/omo-codex/plugin/skills/ulw-research/scripts/particles-ko.mjs +108 -0
  161. package/packages/omo-codex/plugin/skills/ulw-research/scripts/rational.mjs +139 -0
  162. package/packages/omo-codex/plugin/skills/ulw-research/scripts/report-tools-commands.mjs +2 -2
  163. package/packages/omo-codex/plugin/skills/ulw-research/scripts/sentence-split.mjs +77 -0
  164. package/packages/omo-codex/plugin/skills/ulw-research/scripts/text-normalize.mjs +82 -0
  165. package/packages/omo-codex/plugin/skills/ulw-research/scripts/text-similarity.mjs +28 -0
  166. package/packages/omo-codex/plugin/skills/ulw-research/scripts/units.mjs +105 -0
  167. package/packages/omo-codex/plugin/skills/visual-qa/SKILL.md +71 -64
  168. package/packages/omo-codex/plugin/test/component-bundled-cli.test.mjs +4 -1
  169. package/packages/omo-codex/plugin/test/sync-skills-test-support.mjs +6 -6
  170. package/packages/omo-codex/scripts/install-dist/install-local.mjs +152 -23
  171. package/packages/shared-skills/skills/browser/SKILL.md +21 -22
  172. package/packages/shared-skills/skills/browser/references/commands.md +2 -9
  173. package/packages/shared-skills/skills/browser/references/install.md +1 -2
  174. package/packages/shared-skills/skills/browser/runtime/omowright/manifest.json +1 -1
  175. package/packages/shared-skills/skills/browser/scripts/browser-doctor.mjs +2 -2
  176. package/packages/shared-skills/skills/browser/scripts/browser-engine-guard.mjs +63 -487
  177. package/packages/shared-skills/skills/browser/scripts/browser-install.mjs +1 -1
  178. package/packages/shared-skills/skills/browser/scripts/omowright.mjs +1 -10
  179. package/packages/shared-skills/skills/frontend/SKILL.md +79 -118
  180. package/packages/shared-skills/skills/frontend/references/design/README.md +75 -224
  181. package/packages/shared-skills/skills/frontend/references/design/stylegallery.md +32 -27
  182. package/packages/shared-skills/skills/frontend/references/perfection/README.md +2 -2
  183. package/packages/shared-skills/skills/frontend/scripts/perfection/check-accent-stripes.ts +8 -24
  184. package/packages/shared-skills/skills/frontend/scripts/perfection/check-draft-guard.test.ts +101 -0
  185. package/packages/shared-skills/skills/frontend/scripts/perfection/check-draft-guard.ts +40 -0
  186. package/packages/shared-skills/skills/frontend/scripts/perfection/collect-files.test.ts +50 -0
  187. package/packages/shared-skills/skills/frontend/scripts/perfection/collect-files.ts +53 -0
  188. package/packages/shared-skills/skills/frontend/scripts/perfection/draft-guard/findings.ts +16 -0
  189. package/packages/shared-skills/skills/frontend/scripts/perfection/draft-guard/lint.ts +38 -0
  190. package/packages/shared-skills/skills/frontend/scripts/perfection/draft-guard/rules.ts +69 -0
  191. package/packages/shared-skills/skills/frontend/scripts/perfection/draft-guard/text-nodes.ts +121 -0
  192. package/packages/shared-skills/skills/frontend/scripts/perfection/fixtures/draft-guard/negative/attr-lookalikes.tsx +10 -0
  193. package/packages/shared-skills/skills/frontend/scripts/perfection/fixtures/draft-guard/negative/bound-values.tsx +12 -0
  194. package/packages/shared-skills/skills/frontend/scripts/perfection/fixtures/draft-guard/negative/const-generics.tsx +3 -0
  195. package/packages/shared-skills/skills/frontend/scripts/perfection/fixtures/draft-guard/negative/generics.tsx +7 -0
  196. package/packages/shared-skills/skills/frontend/scripts/perfection/fixtures/draft-guard/negative/measured-stats.html +6 -0
  197. package/packages/shared-skills/skills/frontend/scripts/perfection/fixtures/draft-guard/negative/plain-copy.html +9 -0
  198. package/packages/shared-skills/skills/frontend/scripts/perfection/fixtures/draft-guard/negative/state-copy.html +11 -0
  199. package/packages/shared-skills/skills/frontend/scripts/perfection/fixtures/draft-guard/positive/attribute-edges.html +7 -0
  200. package/packages/shared-skills/skills/frontend/scripts/perfection/fixtures/draft-guard/positive/components.tsx +16 -0
  201. package/packages/shared-skills/skills/frontend/scripts/perfection/fixtures/draft-guard/positive/decorative-badge.vue +7 -0
  202. package/packages/shared-skills/skills/frontend/scripts/perfection/fixtures/draft-guard/positive/emoji-icons.tsx +9 -0
  203. package/packages/shared-skills/skills/frontend/scripts/perfection/fixtures/draft-guard/positive/jsx-edges.tsx +1 -0
  204. package/packages/shared-skills/skills/frontend/scripts/perfection/fixtures/draft-guard/positive/promo-copy.html +6 -0
  205. package/packages/shared-skills/skills/frontend/scripts/perfection/fixtures/draft-guard/positive/promo-copy.tsx +8 -0
  206. package/packages/shared-skills/skills/frontend/scripts/perfection/fixtures/draft-guard/positive/vanity-stats.html +5 -0
  207. package/packages/shared-skills/skills/programming/SKILL.md +10 -7
  208. package/packages/shared-skills/skills/programming/references/typescript/README.md +10 -7
  209. package/packages/shared-skills/skills/programming/references/typescript/bootstrap.md +9 -23
  210. package/packages/shared-skills/skills/programming/references/typescript/oxc-lint.md +130 -0
  211. package/packages/shared-skills/skills/programming/references/typescript/tsconfig-strict.md +6 -62
  212. package/packages/shared-skills/skills/programming/scripts/typescript/check-no-excuse-rules.ts +12 -12
  213. package/packages/shared-skills/skills/programming/scripts/typescript/new-project.test.ts +162 -0
  214. package/packages/shared-skills/skills/programming/scripts/typescript/new-project.ts +20 -81
  215. package/packages/shared-skills/skills/programming/scripts/typescript/oxc/.oxfmtrc.json +9 -0
  216. package/packages/shared-skills/skills/programming/scripts/typescript/oxc/.oxlintrc.json +46 -0
  217. package/packages/shared-skills/skills/programming/scripts/typescript/oxc/lint/oxlint-plugin/LICENSE +21 -0
  218. package/packages/shared-skills/skills/programming/scripts/typescript/oxc/lint/oxlint-plugin/index.js +10 -0
  219. package/packages/shared-skills/skills/programming/scripts/typescript/oxc/lint/oxlint-plugin/no-em-dash-copy.js +28 -0
  220. package/packages/shared-skills/skills/programming/scripts/typescript/oxc/lint/oxlint-plugin/require-suppression-reason.js +62 -0
  221. package/packages/shared-skills/skills/programming/scripts/typescript/oxc/tsconfig.json +29 -0
  222. package/packages/shared-skills/skills/refactor/SKILL.md +13 -13
  223. package/packages/shared-skills/skills/ulw-execute/SKILL.md +1 -1
  224. package/packages/shared-skills/skills/ulw-research/AGENTS.md +9 -3
  225. package/packages/shared-skills/skills/ulw-research/SKILL.md +2 -2
  226. package/packages/shared-skills/skills/ulw-research/references/deliverable-phase.md +7 -6
  227. package/packages/shared-skills/skills/ulw-research/references/fact-ledger-grammar.md +116 -0
  228. package/packages/shared-skills/skills/ulw-research/references/report-gates.md +51 -3
  229. package/packages/shared-skills/skills/ulw-research/scripts/claims-index.mjs +125 -0
  230. package/packages/shared-skills/skills/ulw-research/scripts/claims-index.test.ts +149 -0
  231. package/packages/shared-skills/skills/ulw-research/scripts/contracts.mjs +68 -3
  232. package/packages/shared-skills/skills/ulw-research/scripts/contracts.test.ts +97 -12
  233. package/packages/shared-skills/skills/ulw-research/scripts/fact-ledger-grammar.test.ts +190 -0
  234. package/packages/shared-skills/skills/ulw-research/scripts/fact-ledger-kinds.mjs +136 -0
  235. package/packages/shared-skills/skills/ulw-research/scripts/fact-ledger-kinds.test.ts +141 -0
  236. package/packages/shared-skills/skills/ulw-research/scripts/fact-ledger-schema.mjs +204 -0
  237. package/packages/shared-skills/skills/ulw-research/scripts/fact-ledger-schema.test.ts +43 -0
  238. package/packages/shared-skills/skills/ulw-research/scripts/fact-ledger.mjs +103 -0
  239. package/packages/shared-skills/skills/ulw-research/scripts/fact-ledger.test.ts +252 -0
  240. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-brief.mjs +145 -0
  241. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-brief.test.ts +179 -0
  242. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-state.mjs +63 -0
  243. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-state.test.ts +46 -0
  244. package/packages/shared-skills/skills/ulw-research/scripts/gates-context.test.ts +64 -0
  245. package/packages/shared-skills/skills/ulw-research/scripts/gates-static.mjs +1 -1
  246. package/packages/shared-skills/skills/ulw-research/scripts/gates-structure.mjs +1 -1
  247. package/packages/shared-skills/skills/ulw-research/scripts/gates-text.mjs +1 -27
  248. package/packages/shared-skills/skills/ulw-research/scripts/lexicon.mjs +47 -0
  249. package/packages/shared-skills/skills/ulw-research/scripts/lexicon.test.ts +57 -0
  250. package/packages/shared-skills/skills/ulw-research/scripts/numerals.mjs +226 -0
  251. package/packages/shared-skills/skills/ulw-research/scripts/numerals.test.ts +120 -0
  252. package/packages/shared-skills/skills/ulw-research/scripts/outcome.mjs +2 -3
  253. package/packages/shared-skills/skills/ulw-research/scripts/outcome.test.ts +29 -0
  254. package/packages/shared-skills/skills/ulw-research/scripts/particles-ko.mjs +108 -0
  255. package/packages/shared-skills/skills/ulw-research/scripts/particles-ko.test.ts +86 -0
  256. package/packages/shared-skills/skills/ulw-research/scripts/rational.mjs +139 -0
  257. package/packages/shared-skills/skills/ulw-research/scripts/rational.test.ts +46 -0
  258. package/packages/shared-skills/skills/ulw-research/scripts/report-tools-commands.mjs +2 -2
  259. package/packages/shared-skills/skills/ulw-research/scripts/sentence-split.mjs +77 -0
  260. package/packages/shared-skills/skills/ulw-research/scripts/sentence-split.test.ts +109 -0
  261. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/claim-graph.md +13 -0
  262. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity-brief-locked.md +17 -0
  263. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity-brief-pending.md +17 -0
  264. package/packages/shared-skills/skills/ulw-research/scripts/text-normalize.mjs +82 -0
  265. package/packages/shared-skills/skills/ulw-research/scripts/text-normalize.test.ts +73 -0
  266. package/packages/shared-skills/skills/ulw-research/scripts/text-similarity.mjs +28 -0
  267. package/packages/shared-skills/skills/ulw-research/scripts/text-similarity.test.ts +38 -0
  268. package/packages/shared-skills/skills/ulw-research/scripts/units.mjs +105 -0
  269. package/packages/shared-skills/skills/ulw-research/scripts/units.test.ts +83 -0
  270. package/packages/shared-skills/skills/visual-qa/SKILL.md +70 -63
  271. package/packages/shared-skills/skills/visual-qa/scripts/skill-doc-fields.test.ts +34 -0
  272. package/script/qa/web-terminal-visual-qa.mjs +6 -2
  273. package/script/qa/xterm-live-terminal.mjs +26 -3
@@ -0,0 +1,108 @@
1
+ // Korean particle normalization after a filled value: read the value's final sound, then pick the
2
+ // particle form. Hangul and the particle table are \u escapes (this directory ships no Hangul).
3
+
4
+ // Unit token -> reading of its Korean name. Duplicated from the planned units.koreanReadingOf so this
5
+ // module has no dependency on a sibling not yet landed; the units module reconciles the two tables.
6
+ // Hangul unit words (won, dalleo, jo, eok, ...) need no row: they are read by code point.
7
+ const UNIT_READINGS = new Map([
8
+ ["%", "open"], // peosenteu
9
+ ["%p", "open"], // peosenteu pointeu
10
+ ["pp", "open"], // peosenteu pointeu
11
+ ["m", "open"], // miteo
12
+ ["mm", "open"], // millimiteo
13
+ ["cm", "open"], // sentimiteo
14
+ ["km", "open"], // killomiteo
15
+ ["kg", "closed"], // killogeuraem
16
+ ["g", "closed"], // geuraem
17
+ ["t", "closed"], // ton
18
+ ["s", "open"], // cho
19
+ ["ms", "open"], // millicho
20
+ ["min", "closed"], // bun
21
+ ["h", "closed"], // sigan
22
+ ["B", "open"], // baiteu
23
+ ["KB", "open"], // killobaiteu
24
+ ["MB", "open"], // megabaiteu
25
+ ["GB", "open"], // gigabaiteu
26
+ ["TB", "open"], // terabaiteu
27
+ ["C", "open"], // do
28
+ ["\u00B0C", "open"], // do
29
+ ["F", "open"], // do
30
+ ["\u00B0F", "open"], // do
31
+ ["USD", "open"], // dalleo
32
+ ["$", "open"], // dalleo
33
+ ["KRW", "closed"], // won
34
+ ["EUR", "open"], // yuro
35
+ ["JPY", "closed"], // en
36
+ ["x", "open"], // bae
37
+ ["\u00D7", "open"], // bae
38
+ ])
39
+
40
+ // Sino-Korean digit readings: yeong il i sam sa o yuk chil pal gu.
41
+ const DIGIT_READINGS = Object.freeze(["closed", "l", "open", "closed", "open", "open", "closed", "l", "l", "open"])
42
+
43
+ // Spoken ending of a power of ten beyond the units digit: sip baek cheon, then the 10^4 group markers
44
+ // man eok jo gyeong. Only jo (10^12) is open.
45
+ const TEN_POWER_READINGS = Object.freeze(["closed", "closed", "closed"])
46
+ const GROUP_READINGS = Object.freeze({ 4: "closed", 8: "closed", 12: "open", 16: "closed" })
47
+
48
+ // [particle, form after an open vowel, form after final l, form after another final consonant]
49
+ const PARTICLES = Object.freeze([
50
+ Object.freeze(["\uC73C\uB85C", "\uB85C", "\uB85C", "\uC73C\uB85C"]),
51
+ Object.freeze(["\uB85C", "\uB85C", "\uB85C", "\uC73C\uB85C"]),
52
+ Object.freeze(["\uC740", "\uB294", "\uC740", "\uC740"]),
53
+ Object.freeze(["\uB294", "\uB294", "\uC740", "\uC740"]),
54
+ Object.freeze(["\uC774", "\uAC00", "\uC774", "\uC774"]),
55
+ Object.freeze(["\uAC00", "\uAC00", "\uC774", "\uC774"]),
56
+ Object.freeze(["\uC744", "\uB97C", "\uC744", "\uC744"]),
57
+ Object.freeze(["\uB97C", "\uB97C", "\uC744", "\uC744"]),
58
+ Object.freeze(["\uC640", "\uC640", "\uACFC", "\uACFC"]),
59
+ Object.freeze(["\uACFC", "\uC640", "\uACFC", "\uACFC"]),
60
+ ])
61
+ const FORM_INDEX = Object.freeze({ open: 1, l: 2, closed: 3 })
62
+
63
+ const HANGUL_FIRST = 0xac00
64
+ const HANGUL_LAST = 0xd7a3
65
+ const BOUNDARY = /^[\s\p{P}]/u
66
+ const TRAILING_DIGITS = /([0-9][0-9,]*)(?:\.([0-9]+))?$/
67
+
68
+ function hangulReading(char) {
69
+ const code = char.codePointAt(0)
70
+ if (code === undefined || code < HANGUL_FIRST || code > HANGUL_LAST) return null
71
+ const finalConsonant = (code - HANGUL_FIRST) % 28
72
+ if (finalConsonant === 0) return "open"
73
+ return finalConsonant === 8 ? "l" : "closed"
74
+ }
75
+
76
+ function numberReading(text) {
77
+ const match = TRAILING_DIGITS.exec(text)
78
+ if (!match) return null
79
+ // Decimals read digit by digit: 6.50 ends on yeong, so a trailing 0 after the point is closed.
80
+ if (match[2] !== undefined) return DIGIT_READINGS[Number(match[2].at(-1))]
81
+ const digits = match[1].replaceAll(",", "")
82
+ const trailingZeros = digits.length - digits.replace(/0+$/, "").length
83
+ if (trailingZeros === 0 || trailingZeros === digits.length) return DIGIT_READINGS[Number(digits.at(-1))]
84
+ // From 10^4 up the spoken end is the last non-zero group word (sipjo ends on jo), not sip/baek/cheon.
85
+ if (trailingZeros >= 4) return GROUP_READINGS[trailingZeros - (trailingZeros % 4)] ?? null
86
+ return TEN_POWER_READINGS[trailingZeros - 1]
87
+ }
88
+
89
+ export function finalReading(valueText, unitToken) {
90
+ if (unitToken !== null && unitToken !== undefined) return UNIT_READINGS.get(unitToken) ?? hangulReading(unitToken.at(-1) ?? "")
91
+ const text = valueText.trimEnd()
92
+ const last = text.at(-1)
93
+ if (last === undefined) return null
94
+ const hangul = hangulReading(last)
95
+ if (hangul !== null) return hangul
96
+ if (last >= "0" && last <= "9") return numberReading(text)
97
+ const token = /[^\s0-9.,]+$/u.exec(text)?.[0] ?? ""
98
+ return UNIT_READINGS.get(token) ?? null
99
+ }
100
+
101
+ export function rewriteParticle(following, reading) {
102
+ const entry = PARTICLES.find(([particle]) => following.startsWith(particle))
103
+ if (entry === undefined) return { text: following, applied: false, candidate: false }
104
+ const rest = following.slice(entry[0].length)
105
+ const index = FORM_INDEX[reading]
106
+ if ((rest !== "" && !BOUNDARY.test(rest)) || index === undefined) return { text: following, applied: false, candidate: true }
107
+ return { text: entry[index] + rest, applied: true, candidate: true }
108
+ }
@@ -0,0 +1,86 @@
1
+ import { describe, expect, test } from "bun:test"
2
+
3
+ import { finalReading, rewriteParticle } from "./particles-ko.mjs"
4
+
5
+ describe("finalReading", () => {
6
+ test("given a value ending in a Hangul syllable when read then the final consonant decides", () => {
7
+ expect(finalReading("3조 8,000억 원", null)).toBe("closed")
8
+ expect(finalReading("5조", null)).toBe("open")
9
+ expect(finalReading("7달", null)).toBe("l")
10
+ })
11
+
12
+ test("given a value ending in a unit token when read then the unit's Korean reading decides", () => {
13
+ expect(finalReading("15.2%", null)).toBe("open")
14
+ expect(finalReading("3.1%p", null)).toBe("open")
15
+ expect(finalReading("320 m", null)).toBe("open")
16
+ expect(finalReading("12 kg", null)).toBe("closed")
17
+ })
18
+
19
+ test("given a value ending in a digit when read then the Sino-Korean reading decides", () => {
20
+ expect(finalReading("4,200", null)).toBe("closed")
21
+ expect(finalReading("1", null)).toBe("l")
22
+ expect(finalReading("2", null)).toBe("open")
23
+ expect(finalReading("3", null)).toBe("closed")
24
+ expect(finalReading("7", null)).toBe("l")
25
+ expect(finalReading("8", null)).toBe("l")
26
+ expect(finalReading("9", null)).toBe("open")
27
+ expect(finalReading("6.5", null)).toBe("open")
28
+ })
29
+
30
+ test("given an integer whose spoken end is jo when read then the magnitude decides, not the last zero", () => {
31
+ expect(finalReading("1000000000000", null)).toBe("open")
32
+ expect(finalReading("10,000", null)).toBe("closed")
33
+ })
34
+
35
+ test("given a round integer past one jo when read then the last 10^4 group word decides, not sip/baek/cheon", () => {
36
+ expect(finalReading("10000000000000", null)).toBe("open")
37
+ expect(finalReading("100000000000000", null)).toBe("open")
38
+ expect(finalReading("1000000000000000", null)).toBe("open")
39
+ expect(finalReading("100000", null)).toBe("closed")
40
+ expect(finalReading("1000000000", null)).toBe("closed")
41
+ })
42
+
43
+ test("given an explicit unit token when read then it overrides the value text", () => {
44
+ expect(finalReading("15.2", "%")).toBe("open")
45
+ expect(finalReading("15", "kg")).toBe("closed")
46
+ })
47
+
48
+ test("given an unknown trailing token when read then no reading is claimed", () => {
49
+ expect(finalReading("12 zorks", null)).toBeNull()
50
+ })
51
+ })
52
+
53
+ describe("rewriteParticle", () => {
54
+ test("given an object particle with a boundary after an open value when rewritten then it becomes the open form", () => {
55
+ expect(rewriteParticle("을 기록", "open")).toEqual({ text: "를 기록", applied: true, candidate: true })
56
+ })
57
+
58
+ test("given a bare particle at end of string when rewritten then it is applied", () => {
59
+ expect(rewriteParticle("은", "open")).toEqual({ text: "는", applied: true, candidate: true })
60
+ })
61
+
62
+ test("given a particle-like prefix with no boundary when rewritten then it stays untouched and is flagged as a candidate", () => {
63
+ expect(rewriteParticle("이상 기록", "closed")).toEqual({ text: "이상 기록", applied: false, candidate: true })
64
+ expect(rewriteParticle("이상.", "open")).toEqual({ text: "이상.", applied: false, candidate: true })
65
+ })
66
+
67
+ test("given English or a non-particle word when rewritten then nothing changes and it is not a candidate", () => {
68
+ expect(rewriteParticle(" is the span", "open")).toEqual({ text: " is the span", applied: false, candidate: false })
69
+ expect(rewriteParticle("기록했다", "open")).toEqual({ text: "기록했다", applied: false, candidate: false })
70
+ })
71
+
72
+ test("given each particle pair when rewritten then the reading picks the form", () => {
73
+ expect(rewriteParticle("이 올랐다", "open").text).toBe("가 올랐다")
74
+ expect(rewriteParticle("가 올랐다", "closed").text).toBe("이 올랐다")
75
+ expect(rewriteParticle("는,", "l").text).toBe("은,")
76
+ expect(rewriteParticle("과 함께", "open").text).toBe("와 함께")
77
+ expect(rewriteParticle("와 함께", "l").text).toBe("과 함께")
78
+ expect(rewriteParticle("로 늘었다", "closed").text).toBe("으로 늘었다")
79
+ expect(rewriteParticle("으로 늘었다", "l").text).toBe("로 늘었다")
80
+ expect(rewriteParticle("으로 늘었다", "open").text).toBe("로 늘었다")
81
+ })
82
+
83
+ test("given no reading when rewritten then a candidate is left unchanged", () => {
84
+ expect(rewriteParticle("을 기록", null)).toEqual({ text: "을 기록", applied: false, candidate: true })
85
+ })
86
+ })
@@ -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
+ }
@@ -0,0 +1,46 @@
1
+ import { describe, expect, test } from "bun:test"
2
+
3
+ import { add, div, eq, fromDecimal, mul, sigFigsOf, sub, toDecimal } from "./rational.mjs"
4
+
5
+ describe("rational", () => {
6
+ test("keeps every digit of a long decimal through multiplication", () => {
7
+ const product = mul(fromDecimal("10000000000000000.1"), fromDecimal("3"))
8
+ expect(toDecimal(product)).toBe("30000000000000000.3")
9
+ })
10
+
11
+ test("rounds a recurring quotient to the requested significant figures", () => {
12
+ expect(toDecimal(div(fromDecimal("1"), fromDecimal("3")), 3)).toBe("0.333")
13
+ expect(toDecimal(div(fromDecimal("2"), fromDecimal("3")), 2)).toBe("0.67")
14
+ expect(toDecimal(fromDecimal("319999"), 2)).toBe("320000")
15
+ expect(toDecimal(fromDecimal("-2.5"), 1)).toBe("-3")
16
+ })
17
+
18
+ test("refuses an exact decimal for a recurring value", () => {
19
+ expect(() => toDecimal(div(fromDecimal("1"), fromDecimal("3")))).toThrow(RangeError)
20
+ })
21
+
22
+ test("add, sub and eq work on reduced fractions", () => {
23
+ const third = div(fromDecimal("1"), fromDecimal("3"))
24
+ expect(eq(add(third, add(third, third)), fromDecimal("1"))).toBe(true)
25
+ expect(toDecimal(sub(fromDecimal("0.3"), fromDecimal("0.1")))).toBe("0.2")
26
+ expect(eq(fromDecimal("1.50"), fromDecimal("1.5"))).toBe(true)
27
+ expect(eq(fromDecimal("1.5"), fromDecimal("1.6"))).toBe(false)
28
+ })
29
+
30
+ test("never routes values through floating point", () => {
31
+ const r = fromDecimal("0.1")
32
+ expect(typeof r.num).toBe("bigint")
33
+ expect(typeof r.den).toBe("bigint")
34
+ expect(() => fromDecimal("1e3")).toThrow()
35
+ expect(() => div(fromDecimal("1"), fromDecimal("0"))).toThrow(RangeError)
36
+ })
37
+
38
+ test("counts significant figures: decimals from the first nonzero digit, integers every digit", () => {
39
+ expect(sigFigsOf("3.2")).toBe(2)
40
+ expect(sigFigsOf("0.050")).toBe(2)
41
+ expect(sigFigsOf("320")).toBe(3)
42
+ expect(sigFigsOf("100")).toBe(3)
43
+ expect(sigFigsOf("20")).toBe(2)
44
+ expect(sigFigsOf("-1.0")).toBe(2)
45
+ })
46
+ })
@@ -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"
@@ -149,7 +149,7 @@ export function runOutcome(argv) {
149
149
  }
150
150
  case "gate": {
151
151
  const [gate, status] = args.positionals
152
- if (!gate || !status) throw new CliError("usage: outcome gate <static|layout|visual|proofread> <pass|fail|not_run>")
152
+ if (!gate || !status) throw new CliError(`usage: outcome gate <${GATE_NAMES.join("|")}> <${GATE_STATUSES.join("|")}>`)
153
153
  outcome.writeManifest(file, outcome.setGate(outcome.readManifest(file), gate, status))
154
154
  return { json: { command: "outcome gate", gate, status }, exitCode: 0 }
155
155
  }
@@ -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,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,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,17 @@
1
+ # Brief
2
+
3
+ ## Deliverable
4
+ format: pdf (answered_by: request)
5
+
6
+ ## Fidelity
7
+ as of: latest at run start, 2026-10-23 (answered_by: default)
8
+ meaning check: on (answered_by: default)
9
+ source floor: standard (answered_by: default)
10
+ derived numbers: allowed (answered_by: user)
11
+ languages: report ko; sources ko, en; quotes original with translation beside (answered_by: default)
12
+ identifier classes: version strings, package names, commit ids (answered_by: default)
13
+ fallback: block-quote (answered_by: request)
14
+ asked: derived numbers
15
+
16
+ ## Notes
17
+ free text that is not part of the block
@@ -0,0 +1,17 @@
1
+ # Brief
2
+
3
+ ## Deliverable
4
+ format: pdf (answered_by: request)
5
+
6
+ ## Fidelity
7
+ as of: latest at run start, 2026-10-23 (answered_by: default)
8
+ meaning check: on (answered_by: default)
9
+ source floor: standard (answered_by: default)
10
+ derived numbers: pending (answered_by: pending)
11
+ languages: report ko; sources ko, en; quotes original with translation beside (answered_by: default)
12
+ identifier classes: version strings, package names, commit ids (answered_by: default)
13
+ fallback: block-quote (answered_by: request)
14
+ asked: derived numbers
15
+
16
+ ## Notes
17
+ free text that is not part of the block