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.
- package/dist/cli/index.js +948 -416
- package/dist/cli/service-auth/keystore.d.ts +4 -2
- package/dist/cli-node/index.js +948 -416
- package/docs/reference/web-terminal-visual-qa.md +8 -0
- package/package.json +1 -1
- package/packages/lsp-daemon/dist/cli.js +4 -4
- package/packages/lsp-daemon/dist/client.js +4 -4
- package/packages/lsp-daemon/dist/index.js +4 -4
- package/packages/lsp-daemon/package.json +3 -3
- package/packages/lsp-tools-mcp/dist/cli.js +4 -4
- package/packages/lsp-tools-mcp/dist/mcp.js +4 -4
- package/packages/lsp-tools-mcp/dist/tools.js +4 -4
- package/packages/lsp-tools-mcp/package.json +3 -3
- package/packages/omo-codex/plugin/.codex-plugin/plugin.json +1 -1
- package/packages/omo-codex/plugin/components/bootstrap/dist/cli.js +62 -21
- package/packages/omo-codex/plugin/components/bootstrap/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/bootstrap/package.json +1 -1
- package/packages/omo-codex/plugin/components/bootstrap/src/hook.ts +1 -1
- package/packages/omo-codex/plugin/components/comment-checker/biome.json +1 -21
- package/packages/omo-codex/plugin/components/comment-checker/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/comment-checker/package.json +4 -4
- package/packages/omo-codex/plugin/components/git-bash/hooks/hooks.json +2 -2
- package/packages/omo-codex/plugin/components/git-bash/package.json +1 -1
- package/packages/omo-codex/plugin/components/lazycodex-executor-verify/biome.json +1 -21
- package/packages/omo-codex/plugin/components/lazycodex-executor-verify/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/lazycodex-executor-verify/package.json +4 -4
- package/packages/omo-codex/plugin/components/lsp/biome.json +1 -21
- package/packages/omo-codex/plugin/components/lsp/dist/.omo-runtime-manifest.json +3 -3
- package/packages/omo-codex/plugin/components/lsp/dist/cli.js +4 -4
- package/packages/omo-codex/plugin/components/lsp/hooks/hooks.json +2 -2
- package/packages/omo-codex/plugin/components/lsp/package.json +4 -4
- package/packages/omo-codex/plugin/components/lsp/scripts/build-runtime.mjs +0 -1
- package/packages/omo-codex/plugin/components/lsp/test/codex-hook-not-installed.test.ts +1 -1
- package/packages/omo-codex/plugin/components/rules/biome.json +1 -21
- package/packages/omo-codex/plugin/components/rules/hooks/hooks.json +4 -4
- package/packages/omo-codex/plugin/components/rules/package.json +4 -4
- package/packages/omo-codex/plugin/components/teammode/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/teammode/package.json +1 -1
- package/packages/omo-codex/plugin/components/telemetry/biome.json +1 -21
- package/packages/omo-codex/plugin/components/telemetry/dist/cli.js +90 -1
- package/packages/omo-codex/plugin/components/telemetry/dist/posthog.js +91 -1
- package/packages/omo-codex/plugin/components/telemetry/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/telemetry/package.json +4 -4
- package/packages/omo-codex/plugin/components/ultrawork/agents/lazycodex-gate-reviewer.toml +2 -2
- package/packages/omo-codex/plugin/components/ultrawork/biome.json +1 -21
- package/packages/omo-codex/plugin/components/ultrawork/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/ultrawork/package.json +4 -4
- package/packages/omo-codex/plugin/components/ulw-execute-continuation/biome.json +1 -21
- package/packages/omo-codex/plugin/components/ulw-execute-continuation/directive.md +1 -1
- package/packages/omo-codex/plugin/components/ulw-execute-continuation/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/ulw-execute-continuation/package.json +4 -4
- package/packages/omo-codex/plugin/components/ulw-loop/biome.json +1 -21
- package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint.js +1 -1
- package/packages/omo-codex/plugin/components/ulw-loop/dist/cli.js +7 -7
- package/packages/omo-codex/plugin/components/ulw-loop/dist/codex-goal-instruction.js +5 -5
- package/packages/omo-codex/plugin/components/ulw-loop/dist/stop-resume-hook.js +1 -1
- package/packages/omo-codex/plugin/components/ulw-loop/hooks/hooks.json +5 -5
- package/packages/omo-codex/plugin/components/ulw-loop/package.json +4 -4
- package/packages/omo-codex/plugin/components/ulw-loop/src/checkpoint.ts +1 -1
- package/packages/omo-codex/plugin/components/ulw-loop/src/codex-goal-instruction.ts +5 -5
- package/packages/omo-codex/plugin/components/ulw-loop/src/stop-resume-hook.ts +1 -1
- package/packages/omo-codex/plugin/components/ulw-loop/test/fixtures/commit-mutations.mjs +2 -0
- package/packages/omo-codex/plugin/hooks/post-compact-resetting-git-bash-mcp-reminder.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-compact-resetting-lsp-diagnostics-cache.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-compact-resetting-project-rule-cache.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-checking-comments.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-checking-lsp-diagnostics.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-checking-thread-title-hygiene.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-matching-project-rules.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-recording-spawn-admission.json +1 -1
- package/packages/omo-codex/plugin/hooks/pre-tool-use-enforcing-unlimited-goal-budget.json +1 -1
- package/packages/omo-codex/plugin/hooks/pre-tool-use-guarding-ulw-loop-spawns.json +1 -1
- package/packages/omo-codex/plugin/hooks/pre-tool-use-recommending-git-bash-mcp.json +1 -1
- package/packages/omo-codex/plugin/hooks/session-start-checking-auto-update.json +1 -1
- package/packages/omo-codex/plugin/hooks/session-start-checking-bootstrap-provisioning.json +1 -1
- package/packages/omo-codex/plugin/hooks/session-start-loading-project-rules.json +1 -1
- package/packages/omo-codex/plugin/hooks/session-start-recording-session-telemetry.json +1 -1
- package/packages/omo-codex/plugin/hooks/stop-checking-ulw-execute-continuation.json +1 -1
- package/packages/omo-codex/plugin/hooks/stop-checking-ulw-loop-resume.json +1 -1
- package/packages/omo-codex/plugin/hooks/subagent-stop-verifying-lazycodex-executor-evidence.json +1 -1
- package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ultrawork-trigger.json +1 -1
- package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ulw-loop-steering.json +1 -1
- package/packages/omo-codex/plugin/hooks/user-prompt-submit-loading-project-rules.json +1 -1
- package/packages/omo-codex/plugin/package-lock.json +12 -12
- package/packages/omo-codex/plugin/package.json +1 -1
- package/packages/omo-codex/plugin/scripts/sync-skills.mjs +7 -7
- package/packages/omo-codex/plugin/shared/test/config-loader.test.ts +2 -0
- package/packages/omo-codex/plugin/skills/browser/SKILL.md +21 -22
- package/packages/omo-codex/plugin/skills/browser/references/commands.md +2 -9
- package/packages/omo-codex/plugin/skills/browser/references/install.md +1 -2
- package/packages/omo-codex/plugin/skills/browser/runtime/omowright/manifest.json +1 -1
- package/packages/omo-codex/plugin/skills/browser/scripts/browser-doctor.mjs +2 -2
- package/packages/omo-codex/plugin/skills/browser/scripts/browser-engine-guard.mjs +63 -487
- package/packages/omo-codex/plugin/skills/browser/scripts/browser-install.mjs +1 -1
- package/packages/omo-codex/plugin/skills/browser/scripts/omowright.mjs +1 -10
- package/packages/omo-codex/plugin/skills/frontend/SKILL.md +79 -118
- package/packages/omo-codex/plugin/skills/frontend/references/design/README.md +75 -224
- package/packages/omo-codex/plugin/skills/frontend/references/design/stylegallery.md +32 -27
- package/packages/omo-codex/plugin/skills/frontend/references/perfection/README.md +2 -2
- package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/check-accent-stripes.ts +8 -24
- package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/check-draft-guard.ts +40 -0
- package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/collect-files.ts +53 -0
- package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/draft-guard/findings.ts +16 -0
- package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/draft-guard/lint.ts +38 -0
- package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/draft-guard/rules.ts +69 -0
- package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/draft-guard/text-nodes.ts +121 -0
- package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/fixtures/draft-guard/negative/attr-lookalikes.tsx +10 -0
- package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/fixtures/draft-guard/negative/bound-values.tsx +12 -0
- package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/fixtures/draft-guard/negative/const-generics.tsx +3 -0
- package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/fixtures/draft-guard/negative/generics.tsx +7 -0
- package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/fixtures/draft-guard/negative/measured-stats.html +6 -0
- package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/fixtures/draft-guard/negative/plain-copy.html +9 -0
- package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/fixtures/draft-guard/negative/state-copy.html +11 -0
- package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/fixtures/draft-guard/positive/attribute-edges.html +7 -0
- package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/fixtures/draft-guard/positive/components.tsx +16 -0
- package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/fixtures/draft-guard/positive/decorative-badge.vue +7 -0
- package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/fixtures/draft-guard/positive/emoji-icons.tsx +9 -0
- package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/fixtures/draft-guard/positive/jsx-edges.tsx +1 -0
- package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/fixtures/draft-guard/positive/promo-copy.html +6 -0
- package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/fixtures/draft-guard/positive/promo-copy.tsx +8 -0
- package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/fixtures/draft-guard/positive/vanity-stats.html +5 -0
- package/packages/omo-codex/plugin/skills/init-deep/SKILL.md +1 -1
- package/packages/omo-codex/plugin/skills/programming/SKILL.md +10 -7
- package/packages/omo-codex/plugin/skills/programming/references/typescript/README.md +10 -7
- package/packages/omo-codex/plugin/skills/programming/references/typescript/bootstrap.md +9 -23
- package/packages/omo-codex/plugin/skills/programming/references/typescript/oxc-lint.md +130 -0
- package/packages/omo-codex/plugin/skills/programming/references/typescript/tsconfig-strict.md +6 -62
- package/packages/omo-codex/plugin/skills/programming/scripts/typescript/check-no-excuse-rules.ts +12 -12
- package/packages/omo-codex/plugin/skills/programming/scripts/typescript/new-project.ts +20 -81
- package/packages/omo-codex/plugin/skills/programming/scripts/typescript/oxc/.oxfmtrc.json +9 -0
- package/packages/omo-codex/plugin/skills/programming/scripts/typescript/oxc/.oxlintrc.json +46 -0
- package/packages/omo-codex/plugin/skills/programming/scripts/typescript/oxc/lint/oxlint-plugin/LICENSE +21 -0
- package/packages/omo-codex/plugin/skills/programming/scripts/typescript/oxc/lint/oxlint-plugin/index.js +10 -0
- package/packages/omo-codex/plugin/skills/programming/scripts/typescript/oxc/lint/oxlint-plugin/no-em-dash-copy.js +28 -0
- package/packages/omo-codex/plugin/skills/programming/scripts/typescript/oxc/lint/oxlint-plugin/require-suppression-reason.js +62 -0
- package/packages/omo-codex/plugin/skills/programming/scripts/typescript/oxc/tsconfig.json +29 -0
- package/packages/omo-codex/plugin/skills/refactor/SKILL.md +14 -14
- package/packages/omo-codex/plugin/skills/remove-ai-slops/SKILL.md +1 -1
- package/packages/omo-codex/plugin/skills/review-work/SKILL.md +2 -2
- package/packages/omo-codex/plugin/skills/ulw-execute/SKILL.md +2 -2
- package/packages/omo-codex/plugin/skills/ulw-loop/SKILL.md +1 -1
- package/packages/omo-codex/plugin/skills/ulw-research/AGENTS.md +9 -3
- package/packages/omo-codex/plugin/skills/ulw-research/SKILL.md +2 -2
- package/packages/omo-codex/plugin/skills/ulw-research/references/deliverable-phase.md +7 -6
- package/packages/omo-codex/plugin/skills/ulw-research/references/fact-ledger-grammar.md +116 -0
- package/packages/omo-codex/plugin/skills/ulw-research/references/report-gates.md +51 -3
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/claims-index.mjs +125 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/contracts.mjs +68 -3
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-ledger-kinds.mjs +136 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-ledger-schema.mjs +204 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-ledger.mjs +103 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-brief.mjs +145 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-state.mjs +63 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/gates-static.mjs +1 -1
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/gates-structure.mjs +1 -1
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/gates-text.mjs +1 -27
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/lexicon.mjs +47 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/numerals.mjs +226 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/outcome.mjs +2 -3
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/particles-ko.mjs +108 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/rational.mjs +139 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/report-tools-commands.mjs +2 -2
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/sentence-split.mjs +77 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/text-normalize.mjs +82 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/text-similarity.mjs +28 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/units.mjs +105 -0
- package/packages/omo-codex/plugin/skills/visual-qa/SKILL.md +71 -64
- package/packages/omo-codex/plugin/test/component-bundled-cli.test.mjs +4 -1
- package/packages/omo-codex/plugin/test/sync-skills-test-support.mjs +6 -6
- package/packages/omo-codex/scripts/install-dist/install-local.mjs +152 -23
- package/packages/shared-skills/skills/browser/SKILL.md +21 -22
- package/packages/shared-skills/skills/browser/references/commands.md +2 -9
- package/packages/shared-skills/skills/browser/references/install.md +1 -2
- package/packages/shared-skills/skills/browser/runtime/omowright/manifest.json +1 -1
- package/packages/shared-skills/skills/browser/scripts/browser-doctor.mjs +2 -2
- package/packages/shared-skills/skills/browser/scripts/browser-engine-guard.mjs +63 -487
- package/packages/shared-skills/skills/browser/scripts/browser-install.mjs +1 -1
- package/packages/shared-skills/skills/browser/scripts/omowright.mjs +1 -10
- package/packages/shared-skills/skills/frontend/SKILL.md +79 -118
- package/packages/shared-skills/skills/frontend/references/design/README.md +75 -224
- package/packages/shared-skills/skills/frontend/references/design/stylegallery.md +32 -27
- package/packages/shared-skills/skills/frontend/references/perfection/README.md +2 -2
- package/packages/shared-skills/skills/frontend/scripts/perfection/check-accent-stripes.ts +8 -24
- package/packages/shared-skills/skills/frontend/scripts/perfection/check-draft-guard.test.ts +101 -0
- package/packages/shared-skills/skills/frontend/scripts/perfection/check-draft-guard.ts +40 -0
- package/packages/shared-skills/skills/frontend/scripts/perfection/collect-files.test.ts +50 -0
- package/packages/shared-skills/skills/frontend/scripts/perfection/collect-files.ts +53 -0
- package/packages/shared-skills/skills/frontend/scripts/perfection/draft-guard/findings.ts +16 -0
- package/packages/shared-skills/skills/frontend/scripts/perfection/draft-guard/lint.ts +38 -0
- package/packages/shared-skills/skills/frontend/scripts/perfection/draft-guard/rules.ts +69 -0
- package/packages/shared-skills/skills/frontend/scripts/perfection/draft-guard/text-nodes.ts +121 -0
- package/packages/shared-skills/skills/frontend/scripts/perfection/fixtures/draft-guard/negative/attr-lookalikes.tsx +10 -0
- package/packages/shared-skills/skills/frontend/scripts/perfection/fixtures/draft-guard/negative/bound-values.tsx +12 -0
- package/packages/shared-skills/skills/frontend/scripts/perfection/fixtures/draft-guard/negative/const-generics.tsx +3 -0
- package/packages/shared-skills/skills/frontend/scripts/perfection/fixtures/draft-guard/negative/generics.tsx +7 -0
- package/packages/shared-skills/skills/frontend/scripts/perfection/fixtures/draft-guard/negative/measured-stats.html +6 -0
- package/packages/shared-skills/skills/frontend/scripts/perfection/fixtures/draft-guard/negative/plain-copy.html +9 -0
- package/packages/shared-skills/skills/frontend/scripts/perfection/fixtures/draft-guard/negative/state-copy.html +11 -0
- package/packages/shared-skills/skills/frontend/scripts/perfection/fixtures/draft-guard/positive/attribute-edges.html +7 -0
- package/packages/shared-skills/skills/frontend/scripts/perfection/fixtures/draft-guard/positive/components.tsx +16 -0
- package/packages/shared-skills/skills/frontend/scripts/perfection/fixtures/draft-guard/positive/decorative-badge.vue +7 -0
- package/packages/shared-skills/skills/frontend/scripts/perfection/fixtures/draft-guard/positive/emoji-icons.tsx +9 -0
- package/packages/shared-skills/skills/frontend/scripts/perfection/fixtures/draft-guard/positive/jsx-edges.tsx +1 -0
- package/packages/shared-skills/skills/frontend/scripts/perfection/fixtures/draft-guard/positive/promo-copy.html +6 -0
- package/packages/shared-skills/skills/frontend/scripts/perfection/fixtures/draft-guard/positive/promo-copy.tsx +8 -0
- package/packages/shared-skills/skills/frontend/scripts/perfection/fixtures/draft-guard/positive/vanity-stats.html +5 -0
- package/packages/shared-skills/skills/programming/SKILL.md +10 -7
- package/packages/shared-skills/skills/programming/references/typescript/README.md +10 -7
- package/packages/shared-skills/skills/programming/references/typescript/bootstrap.md +9 -23
- package/packages/shared-skills/skills/programming/references/typescript/oxc-lint.md +130 -0
- package/packages/shared-skills/skills/programming/references/typescript/tsconfig-strict.md +6 -62
- package/packages/shared-skills/skills/programming/scripts/typescript/check-no-excuse-rules.ts +12 -12
- package/packages/shared-skills/skills/programming/scripts/typescript/new-project.test.ts +162 -0
- package/packages/shared-skills/skills/programming/scripts/typescript/new-project.ts +20 -81
- package/packages/shared-skills/skills/programming/scripts/typescript/oxc/.oxfmtrc.json +9 -0
- package/packages/shared-skills/skills/programming/scripts/typescript/oxc/.oxlintrc.json +46 -0
- package/packages/shared-skills/skills/programming/scripts/typescript/oxc/lint/oxlint-plugin/LICENSE +21 -0
- package/packages/shared-skills/skills/programming/scripts/typescript/oxc/lint/oxlint-plugin/index.js +10 -0
- package/packages/shared-skills/skills/programming/scripts/typescript/oxc/lint/oxlint-plugin/no-em-dash-copy.js +28 -0
- package/packages/shared-skills/skills/programming/scripts/typescript/oxc/lint/oxlint-plugin/require-suppression-reason.js +62 -0
- package/packages/shared-skills/skills/programming/scripts/typescript/oxc/tsconfig.json +29 -0
- package/packages/shared-skills/skills/refactor/SKILL.md +13 -13
- package/packages/shared-skills/skills/ulw-execute/SKILL.md +1 -1
- package/packages/shared-skills/skills/ulw-research/AGENTS.md +9 -3
- package/packages/shared-skills/skills/ulw-research/SKILL.md +2 -2
- package/packages/shared-skills/skills/ulw-research/references/deliverable-phase.md +7 -6
- package/packages/shared-skills/skills/ulw-research/references/fact-ledger-grammar.md +116 -0
- package/packages/shared-skills/skills/ulw-research/references/report-gates.md +51 -3
- package/packages/shared-skills/skills/ulw-research/scripts/claims-index.mjs +125 -0
- package/packages/shared-skills/skills/ulw-research/scripts/claims-index.test.ts +149 -0
- package/packages/shared-skills/skills/ulw-research/scripts/contracts.mjs +68 -3
- package/packages/shared-skills/skills/ulw-research/scripts/contracts.test.ts +97 -12
- package/packages/shared-skills/skills/ulw-research/scripts/fact-ledger-grammar.test.ts +190 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fact-ledger-kinds.mjs +136 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fact-ledger-kinds.test.ts +141 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fact-ledger-schema.mjs +204 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fact-ledger-schema.test.ts +43 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fact-ledger.mjs +103 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fact-ledger.test.ts +252 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fidelity-brief.mjs +145 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fidelity-brief.test.ts +179 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fidelity-state.mjs +63 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fidelity-state.test.ts +46 -0
- package/packages/shared-skills/skills/ulw-research/scripts/gates-context.test.ts +64 -0
- package/packages/shared-skills/skills/ulw-research/scripts/gates-static.mjs +1 -1
- package/packages/shared-skills/skills/ulw-research/scripts/gates-structure.mjs +1 -1
- package/packages/shared-skills/skills/ulw-research/scripts/gates-text.mjs +1 -27
- package/packages/shared-skills/skills/ulw-research/scripts/lexicon.mjs +47 -0
- package/packages/shared-skills/skills/ulw-research/scripts/lexicon.test.ts +57 -0
- package/packages/shared-skills/skills/ulw-research/scripts/numerals.mjs +226 -0
- package/packages/shared-skills/skills/ulw-research/scripts/numerals.test.ts +120 -0
- package/packages/shared-skills/skills/ulw-research/scripts/outcome.mjs +2 -3
- package/packages/shared-skills/skills/ulw-research/scripts/outcome.test.ts +29 -0
- package/packages/shared-skills/skills/ulw-research/scripts/particles-ko.mjs +108 -0
- package/packages/shared-skills/skills/ulw-research/scripts/particles-ko.test.ts +86 -0
- package/packages/shared-skills/skills/ulw-research/scripts/rational.mjs +139 -0
- package/packages/shared-skills/skills/ulw-research/scripts/rational.test.ts +46 -0
- package/packages/shared-skills/skills/ulw-research/scripts/report-tools-commands.mjs +2 -2
- package/packages/shared-skills/skills/ulw-research/scripts/sentence-split.mjs +77 -0
- package/packages/shared-skills/skills/ulw-research/scripts/sentence-split.test.ts +109 -0
- package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/claim-graph.md +13 -0
- package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity-brief-locked.md +17 -0
- package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity-brief-pending.md +17 -0
- package/packages/shared-skills/skills/ulw-research/scripts/text-normalize.mjs +82 -0
- package/packages/shared-skills/skills/ulw-research/scripts/text-normalize.test.ts +73 -0
- package/packages/shared-skills/skills/ulw-research/scripts/text-similarity.mjs +28 -0
- package/packages/shared-skills/skills/ulw-research/scripts/text-similarity.test.ts +38 -0
- package/packages/shared-skills/skills/ulw-research/scripts/units.mjs +105 -0
- package/packages/shared-skills/skills/ulw-research/scripts/units.test.ts +83 -0
- package/packages/shared-skills/skills/visual-qa/SKILL.md +70 -63
- package/packages/shared-skills/skills/visual-qa/scripts/skill-doc-fields.test.ts +34 -0
- package/script/qa/web-terminal-visual-qa.mjs +6 -2
- 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(
|
|
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
|
+
}
|