lazycodex-ai 5.1.44 → 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,139 @@
1
+ // rational.mjs - exact rational arithmetic over BigInt numerator/denominator pairs.
2
+ //
3
+ // A rational is { num: bigint, den: bigint }, reduced, with den > 0. Numerals enter as decimal
4
+ // strings and never pass through Number(), so no value is ever rounded by floating point.
5
+ // Plain Node ESM, no imports.
6
+
7
+ const DECIMAL = /^([+-]?)(\d+)(?:\.(\d+))?$/
8
+
9
+ const abs = (n) => (n < 0n ? -n : n)
10
+
11
+ function gcd(a, b) {
12
+ let x = abs(a)
13
+ let y = abs(b)
14
+ while (y !== 0n) [x, y] = [y, x % y]
15
+ return x
16
+ }
17
+
18
+ function make(num, den) {
19
+ if (den === 0n) throw new RangeError("rational: zero denominator")
20
+ const sign = den < 0n ? -1n : 1n
21
+ const g = gcd(num, den) || 1n
22
+ return Object.freeze({ num: (sign * num) / g, den: (sign * den) / g })
23
+ }
24
+
25
+ /** Accepts a rational, a bigint, a safe integer, or a decimal string. */
26
+ function coerce(value) {
27
+ if (typeof value === "bigint") return make(value, 1n)
28
+ if (typeof value === "number") {
29
+ if (!Number.isSafeInteger(value)) throw new TypeError(`rational: ${value} is not a safe integer; pass a decimal string`)
30
+ return make(BigInt(value), 1n)
31
+ }
32
+ if (typeof value === "string") return fromDecimal(value)
33
+ if (value && typeof value.num === "bigint" && typeof value.den === "bigint") return make(value.num, value.den)
34
+ throw new TypeError("rational: expected a rational, bigint, integer, or decimal string")
35
+ }
36
+
37
+ /** "12.50" -> 25/2. Accepts an optional sign, digits, and an optional fraction; nothing else. */
38
+ export function fromDecimal(str) {
39
+ const match = DECIMAL.exec(String(str).trim())
40
+ if (!match) throw new SyntaxError(`rational: "${str}" is not a plain decimal`)
41
+ const [, sign, whole, frac = ""] = match
42
+ const num = BigInt(whole + frac) * (sign === "-" ? -1n : 1n)
43
+ return make(num, 10n ** BigInt(frac.length))
44
+ }
45
+
46
+ export function add(a, b) {
47
+ const x = coerce(a)
48
+ const y = coerce(b)
49
+ return make(x.num * y.den + y.num * x.den, x.den * y.den)
50
+ }
51
+
52
+ export function sub(a, b) {
53
+ const x = coerce(a)
54
+ const y = coerce(b)
55
+ return make(x.num * y.den - y.num * x.den, x.den * y.den)
56
+ }
57
+
58
+ export function mul(a, b) {
59
+ const x = coerce(a)
60
+ const y = coerce(b)
61
+ return make(x.num * y.num, x.den * y.den)
62
+ }
63
+
64
+ export function div(a, b) {
65
+ const x = coerce(a)
66
+ const y = coerce(b)
67
+ if (y.num === 0n) throw new RangeError("rational: division by zero")
68
+ return make(x.num * y.den, x.den * y.num)
69
+ }
70
+
71
+ export function eq(a, b) {
72
+ const x = coerce(a)
73
+ const y = coerce(b)
74
+ return x.num === y.num && x.den === y.den
75
+ }
76
+
77
+ /** Digits of a non-negative integer scaled by 10^-scale, as a decimal string. */
78
+ function placeDecimal(digits, scale) {
79
+ if (scale <= 0) return digits + "0".repeat(-scale)
80
+ const padded = digits.padStart(scale + 1, "0")
81
+ return `${padded.slice(0, padded.length - scale)}.${padded.slice(padded.length - scale)}`
82
+ }
83
+
84
+ /** Exact decimal; throws RangeError when the expansion does not terminate. */
85
+ function exactDecimal(r) {
86
+ let den = r.den
87
+ let twos = 0
88
+ let fives = 0
89
+ while (den % 2n === 0n) [den, twos] = [den / 2n, twos + 1]
90
+ while (den % 5n === 0n) [den, fives] = [den / 5n, fives + 1]
91
+ if (den !== 1n) throw new RangeError(`rational: ${r.num}/${r.den} has no finite decimal; pass sigFigs`)
92
+ const scale = Math.max(twos, fives)
93
+ const digits = (abs(r.num) * 10n ** BigInt(scale)) / r.den
94
+ return (r.num < 0n ? "-" : "") + placeDecimal(digits.toString(), scale)
95
+ }
96
+
97
+ /** Number of decimal digits in the integer part of |num/den| (0 when below 1). */
98
+ function integerDigits(num, den) {
99
+ const whole = num / den
100
+ if (whole > 0n) return whole.toString().length
101
+ let exp = 0
102
+ let scaled = num
103
+ while (scaled < den) [scaled, exp] = [scaled * 10n, exp - 1]
104
+ return exp + 1
105
+ }
106
+
107
+ /**
108
+ * Decimal string. Without sigFigs the value must terminate and is rendered exactly. With sigFigs it
109
+ * is rounded half away from zero to that many significant figures; integers are never written
110
+ * with an exponent, so 319999 at 2 figures renders "320000".
111
+ */
112
+ export function toDecimal(value, sigFigs) {
113
+ const r = coerce(value)
114
+ if (sigFigs === undefined) return exactDecimal(r)
115
+ if (!Number.isInteger(sigFigs) || sigFigs < 1) throw new RangeError("rational: sigFigs must be a positive integer")
116
+ if (r.num === 0n) return sigFigs > 1 ? `0.${"0".repeat(sigFigs - 1)}` : "0"
117
+ const num = abs(r.num)
118
+ const scale = sigFigs - integerDigits(num, r.den)
119
+ const numer = scale >= 0 ? num * 10n ** BigInt(scale) : num
120
+ const denom = scale >= 0 ? r.den : r.den * 10n ** BigInt(-scale)
121
+ let digits = numer / denom
122
+ let places = scale
123
+ if ((numer % denom) * 2n >= denom) digits += 1n
124
+ if (digits.toString().length > sigFigs) [digits, places] = [digits / 10n, places - 1]
125
+ return (r.num < 0n ? "-" : "") + placeDecimal(digits.toString(), places)
126
+ }
127
+
128
+ /**
129
+ * Significant figures of a decimal string. With a decimal point: digits from the first nonzero
130
+ * digit to the end, trailing zeros included ("3.2" -> 2, "0.050" -> 2). Without one: every digit
131
+ * after leading zeros counts ("320" -> 3, "100" -> 3, "20" -> 2).
132
+ */
133
+ export function sigFigsOf(decimalStr) {
134
+ const match = DECIMAL.exec(String(decimalStr).trim())
135
+ if (!match) throw new SyntaxError(`rational: "${decimalStr}" is not a plain decimal`)
136
+ const [, , whole, frac] = match
137
+ const digits = (whole + (frac ?? "")).replace(/^0+/, "")
138
+ return Math.max(digits.length, 1)
139
+ }
@@ -5,7 +5,7 @@ import { existsSync, readFileSync, statSync, writeFileSync } from "node:fs"
5
5
  import { dirname, join, resolve } from "node:path"
6
6
 
7
7
  import { CliError, parseArgs, readJsonFile, writeJsonAtomic } from "./cli-support.mjs"
8
- import { REPAIR_DEFAULTS } from "./contracts.mjs"
8
+ import { GATE_NAMES, GATE_STATUSES, REPAIR_DEFAULTS } from "./contracts.mjs"
9
9
  import { parseLineageMode, parsePalette, renderDesignSpec } from "./design-spec.mjs"
10
10
  import { extractFromHtml, extractFromPath, fetchHtml } from "./format-extract.mjs"
11
11
  import { checkLayout } from "./gates-layout.mjs"
@@ -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,82 @@
1
+ // text-normalize.mjs - text normalization and raw-offset matching for fidelity checks.
2
+ //
3
+ // normalizeText applies NFC, maps curly and CJK quote glyphs to straight quotes, collapses
4
+ // whitespace runs to one space, and trims. findNormalized matches a needle against the
5
+ // normalized haystack and returns offsets into the RAW haystack through an offset map built
6
+ // during normalization. Plain Node ESM, no dependencies.
7
+
8
+ const DOUBLE_QUOTES = new Set([
9
+ "\u201C", "\u201D", "\u201E", "\u201F", "\u00AB", "\u00BB", "\u300C", "\u300D", "\u300E", "\u300F",
10
+ "\u301D", "\u301E", "\u301F", "\uFF02", "\u2033",
11
+ ])
12
+ const SINGLE_QUOTES = new Set(["\u2018", "\u2019", "\u201A", "\u201B", "\u2039", "\u203A", "\uFF07", "\u2032"])
13
+ // Combining marks and conjoining Hangul medial/final jamo attach to the preceding code point,
14
+ // so a decomposed syllable is normalized as one unit.
15
+ const JOINS_PREVIOUS = /^[\p{M}\u1160-\u11FF\uD7B0-\uD7FF]$/u
16
+ const WHITESPACE = /^\s+$/u
17
+ const TOKEN_SEPARATOR = /[^\p{L}\p{N}\p{M}]+/u
18
+
19
+ function mapQuote(ch) {
20
+ if (DOUBLE_QUOTES.has(ch)) return '"'
21
+ if (SINGLE_QUOTES.has(ch)) return "'"
22
+ return ch
23
+ }
24
+
25
+ function clusters(raw) {
26
+ const out = []
27
+ let at = 0
28
+ for (const ch of raw) {
29
+ const last = out[out.length - 1]
30
+ if (last && JOINS_PREVIOUS.test(ch)) last[1] += ch.length
31
+ else out.push([at, at + ch.length])
32
+ at += ch.length
33
+ }
34
+ return out
35
+ }
36
+
37
+ // Returns { text, starts, ends }: text[k] came from raw.slice(starts[k], ends[k]).
38
+ function normalizeWithMap(raw) {
39
+ const source = String(raw ?? "")
40
+ let text = ""
41
+ const starts = []
42
+ const ends = []
43
+ for (const [start, end] of clusters(source)) {
44
+ const piece = Array.from(source.slice(start, end).normalize("NFC"), mapQuote).join("")
45
+ if (WHITESPACE.test(piece)) {
46
+ if (text !== "" && !text.endsWith(" ")) {
47
+ text += " "
48
+ starts.push(start)
49
+ ends.push(end)
50
+ }
51
+ continue
52
+ }
53
+ for (let k = 0; k < piece.length; k++) {
54
+ starts.push(start)
55
+ ends.push(end)
56
+ }
57
+ text += piece
58
+ }
59
+ if (text.endsWith(" ")) {
60
+ text = text.slice(0, -1)
61
+ starts.pop()
62
+ ends.pop()
63
+ }
64
+ return { text, starts, ends }
65
+ }
66
+
67
+ export function normalizeText(s) {
68
+ return normalizeWithMap(s).text
69
+ }
70
+
71
+ export function tokenize(s) {
72
+ return normalizeText(s).split(TOKEN_SEPARATOR).filter((token) => token !== "")
73
+ }
74
+
75
+ export function findNormalized(hay, needle) {
76
+ const target = normalizeText(needle)
77
+ if (target === "") return null
78
+ const map = normalizeWithMap(hay)
79
+ const at = map.text.indexOf(target)
80
+ if (at === -1) return null
81
+ return { start: map.starts[at], end: map.ends[at + target.length - 1] }
82
+ }
@@ -0,0 +1,28 @@
1
+ // text-similarity.mjs - similarity in [0, 1] from normalized Levenshtein distance.
2
+ //
3
+ // Both inputs go through normalizeText first (NFC, quote glyphs, whitespace), then the
4
+ // edit distance is computed over code points and divided by the longer length.
5
+ // Plain Node ESM, no dependencies.
6
+
7
+ import { normalizeText } from "./text-normalize.mjs"
8
+
9
+ function levenshtein(a, b) {
10
+ let previous = Array.from({ length: b.length + 1 }, (_, k) => k)
11
+ for (let i = 1; i <= a.length; i++) {
12
+ const current = [i]
13
+ for (let k = 1; k <= b.length; k++) {
14
+ const cost = a[i - 1] === b[k - 1] ? 0 : 1
15
+ current.push(Math.min(previous[k] + 1, current[k - 1] + 1, previous[k - 1] + cost))
16
+ }
17
+ previous = current
18
+ }
19
+ return previous[b.length]
20
+ }
21
+
22
+ export function similarity(a, b) {
23
+ const left = Array.from(normalizeText(a))
24
+ const right = Array.from(normalizeText(b))
25
+ const longest = Math.max(left.length, right.length)
26
+ if (longest === 0) return 1
27
+ return 1 - levenshtein(left, right) / longest
28
+ }
@@ -0,0 +1,105 @@
1
+ // units.mjs - unit table: normalization, dimensions, exact conversion factors, Korean readings.
2
+ //
3
+ // normalizeUnit maps a raw unit (any spacing or spelling variant) to one canonical token. Each token
4
+ // has a dimension, an exact factor to the dimension's base unit (base = value * factor + offset), and
5
+ // the final-sound class of its Korean reading for the particle rule: "open" (no final consonant),
6
+ // "l" (final rieul), or "closed". Currency is its own dimension per currency, so no exchange rate is
7
+ // ever implied. Data units are decimal (1 KB = 1000 B). Plain Node ESM, sibling imports only.
8
+
9
+ import { div, fromDecimal, sub } from "./rational.mjs"
10
+
11
+ /** [token, dimension, factor, reading, aliases, offset?]; the Korean reading is noted per row. */
12
+ const TABLE = [
13
+ ["mm", "length", "0.001", "open", ["millimeter", "millimeters", "\uBC00\uB9AC\uBBF8\uD130"]], // millimiteo
14
+ ["cm", "length", "0.01", "open", ["centimeter", "centimeters", "\uC13C\uD2F0\uBBF8\uD130"]], // sentimiteo
15
+ ["m", "length", "1", "open", ["meter", "meters", "metre", "metres", "\uBBF8\uD130"]], // miteo
16
+ ["km", "length", "1000", "open", ["kilometer", "kilometers", "\uD0AC\uB85C\uBBF8\uD130"]], // killomiteo
17
+ ["g", "mass", "1", "closed", ["gram", "grams", "\uADF8\uB7A8"]], // geuraem
18
+ ["kg", "mass", "1000", "closed", ["kilogram", "kilograms", "\uD0AC\uB85C\uADF8\uB7A8"]], // killogeuraem
19
+ ["t", "mass", "1000000", "closed", ["ton", "tons", "tonne", "tonnes", "\uD1A4"]], // ton
20
+ ["ms", "time", "0.001", "open", ["millisecond", "milliseconds", "\uBC00\uB9AC\uCD08"]], // millicho
21
+ ["s", "time", "1", "open", ["sec", "secs", "second", "seconds", "\uCD08"]], // cho
22
+ ["min", "time", "60", "closed", ["mins", "minute", "minutes", "\uBD84"]], // bun
23
+ ["h", "time", "3600", "closed", ["hr", "hrs", "hour", "hours", "\uC2DC\uAC04"]], // sigan
24
+ ["B", "data", "1", "open", ["byte", "bytes", "\uBC14\uC774\uD2B8"]], // baiteu
25
+ ["KB", "data", "1000", "open", ["\uD0AC\uB85C\uBC14\uC774\uD2B8"]],
26
+ ["MB", "data", "1000000", "open", ["\uBA54\uAC00\uBC14\uC774\uD2B8"]],
27
+ ["GB", "data", "1000000000", "open", ["\uAE30\uAC00\uBC14\uC774\uD2B8"]],
28
+ ["TB", "data", "1000000000000", "open", ["\uD14C\uB77C\uBC14\uC774\uD2B8"]],
29
+ ["C", "temperature", "1", "open", ["\u00B0C", "\u2103", "celsius", "\uC12D\uC528"], "0"], // do
30
+ ["F", "temperature", "5/9", "open", ["\u00B0F", "\u2109", "fahrenheit", "\uD654\uC528"], "-160/9"], // do
31
+ ["%", "percent", "1", "open", ["percent", "\uD37C\uC13C\uD2B8", "\uD504\uB85C"]], // peosenteu
32
+ ["\u2030", "percent", "0.1", "l", ["permille", "\uD37C\uBC00"]], // peomil
33
+ ["%p", "percentage-point", "1", "open", ["pp", "%pt", "percentage point", "percentage points", "\uD37C\uC13C\uD2B8\uD3EC\uC778\uD2B8", "%\uD3EC\uC778\uD2B8"]],
34
+ ["x", "multiple", "1", "open", ["\u00D7", "times", "\uBC30"]], // bae
35
+ ["e3", "scale", "1000", "closed", ["k", "thousand", "\uCC9C"]], // cheon
36
+ ["e4", "scale", "10000", "closed", ["\uB9CC"]], // man
37
+ ["e6", "scale", "1000000", "closed", ["M", "million", "\uBC31\uB9CC"]], // baengman
38
+ ["e8", "scale", "100000000", "closed", ["\uC5B5"]], // eok
39
+ ["e9", "scale", "1000000000", "closed", ["bn", "billion", "\uC2ED\uC5B5"]], // sibeok
40
+ ["e12", "scale", "1000000000000", "open", ["trillion", "\uC870"]], // jo
41
+ ["person", "count:person", "1", "closed", ["persons", "people", "\uBA85"]], // myeong
42
+ ["case", "count:case", "1", "closed", ["cases", "\uAC74"]], // geon
43
+ ["KRW", "currency:KRW", "1", "closed", ["\uC6D0", "\u20A9", "won"]], // won
44
+ ["KRW-e3", "currency:KRW", "1000", "closed", ["\uCC9C\uC6D0"]],
45
+ ["KRW-e4", "currency:KRW", "10000", "closed", ["\uB9CC\uC6D0"]],
46
+ ["KRW-e8", "currency:KRW", "100000000", "closed", ["\uC5B5\uC6D0"]],
47
+ ["KRW-e12", "currency:KRW", "1000000000000", "closed", ["\uC870\uC6D0"]],
48
+ ["USD", "currency:USD", "1", "open", ["$", "US$", "dollar", "dollars", "\uB2EC\uB7EC"]], // dalleo
49
+ ["EUR", "currency:EUR", "1", "open", ["\u20AC", "euro", "euros", "\uC720\uB85C"]], // yuro
50
+ ["JPY", "currency:JPY", "1", "closed", ["\u00A5", "yen", "\uC5D4"]], // en
51
+ ["GBP", "currency:GBP", "1", "open", ["\u00A3", "pound", "pounds", "\uD30C\uC6B4\uB4DC"]], // paundeu
52
+ ]
53
+
54
+ const parse = (text) => (text.includes("/") ? div(fromDecimal(text.split("/")[0]), fromDecimal(text.split("/")[1])) : fromDecimal(text))
55
+ const squash = (raw) => raw.replace(/\s+/g, "")
56
+
57
+ const ENTRIES = new Map()
58
+ /** Exact-case keys: every token and alias with whitespace removed. */
59
+ const EXACT = new Map()
60
+ /** Case-insensitive keys: multi-letter ASCII aliases only, so m/M and B/b never collide. */
61
+ const WORD = new Map()
62
+ for (const [token, dimension, factor, reading, aliases, offset] of TABLE) {
63
+ const entry = Object.freeze({ token, dimension, factor: parse(factor), offset: parse(offset ?? "0"), reading })
64
+ ENTRIES.set(token, entry)
65
+ for (const alias of [token, ...aliases]) {
66
+ const key = squash(alias)
67
+ EXACT.set(key, entry)
68
+ if (key.length > 1 && /^[A-Za-z]+$/.test(key) && !WORD.has(key.toLowerCase())) WORD.set(key.toLowerCase(), entry)
69
+ }
70
+ }
71
+
72
+ function entryOf(raw) {
73
+ if (typeof raw !== "string") return null
74
+ const key = squash(raw)
75
+ if (!key) return null
76
+ return EXACT.get(key) ?? WORD.get(key.toLowerCase()) ?? null
77
+ }
78
+
79
+ /** Canonical token for a raw unit, or null when the unit is not in the table. */
80
+ export function normalizeUnit(raw) {
81
+ return entryOf(raw)?.token ?? null
82
+ }
83
+
84
+ /** Dimension of a unit ("length", "currency:KRW", ...), or null. */
85
+ export function dimensionOf(token) {
86
+ return entryOf(token)?.dimension ?? null
87
+ }
88
+
89
+ /**
90
+ * Exact factor from one unit to another of the same dimension: to = from * num / den (+ offset for
91
+ * affine scales). Null across dimensions, across currencies, or for an unknown unit.
92
+ */
93
+ export function convertFactor(from, to) {
94
+ const a = entryOf(from)
95
+ const b = entryOf(to)
96
+ if (!a || !b || a.dimension !== b.dimension) return null
97
+ const { num, den } = div(a.factor, b.factor)
98
+ const offset = div(sub(a.offset, b.offset), b.factor)
99
+ return offset.num === 0n ? { num, den } : { num, den, offset: { num: offset.num, den: offset.den } }
100
+ }
101
+
102
+ /** Final-sound class of the unit's Korean reading: "open", "closed", or "l"; null when unknown. */
103
+ export function koreanReadingOf(token) {
104
+ return entryOf(token)?.reading ?? null
105
+ }