lazycodex-ai 5.1.43 → 5.1.45
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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,86 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test"
|
|
2
|
+
|
|
3
|
+
import { finalReading, rewriteParticle } from "./particles-ko.mjs"
|
|
4
|
+
|
|
5
|
+
describe("finalReading", () => {
|
|
6
|
+
test("given a value ending in a Hangul syllable when read then the final consonant decides", () => {
|
|
7
|
+
expect(finalReading("3조 8,000억 원", null)).toBe("closed")
|
|
8
|
+
expect(finalReading("5조", null)).toBe("open")
|
|
9
|
+
expect(finalReading("7달", null)).toBe("l")
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
test("given a value ending in a unit token when read then the unit's Korean reading decides", () => {
|
|
13
|
+
expect(finalReading("15.2%", null)).toBe("open")
|
|
14
|
+
expect(finalReading("3.1%p", null)).toBe("open")
|
|
15
|
+
expect(finalReading("320 m", null)).toBe("open")
|
|
16
|
+
expect(finalReading("12 kg", null)).toBe("closed")
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
test("given a value ending in a digit when read then the Sino-Korean reading decides", () => {
|
|
20
|
+
expect(finalReading("4,200", null)).toBe("closed")
|
|
21
|
+
expect(finalReading("1", null)).toBe("l")
|
|
22
|
+
expect(finalReading("2", null)).toBe("open")
|
|
23
|
+
expect(finalReading("3", null)).toBe("closed")
|
|
24
|
+
expect(finalReading("7", null)).toBe("l")
|
|
25
|
+
expect(finalReading("8", null)).toBe("l")
|
|
26
|
+
expect(finalReading("9", null)).toBe("open")
|
|
27
|
+
expect(finalReading("6.5", null)).toBe("open")
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
test("given an integer whose spoken end is jo when read then the magnitude decides, not the last zero", () => {
|
|
31
|
+
expect(finalReading("1000000000000", null)).toBe("open")
|
|
32
|
+
expect(finalReading("10,000", null)).toBe("closed")
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
test("given a round integer past one jo when read then the last 10^4 group word decides, not sip/baek/cheon", () => {
|
|
36
|
+
expect(finalReading("10000000000000", null)).toBe("open")
|
|
37
|
+
expect(finalReading("100000000000000", null)).toBe("open")
|
|
38
|
+
expect(finalReading("1000000000000000", null)).toBe("open")
|
|
39
|
+
expect(finalReading("100000", null)).toBe("closed")
|
|
40
|
+
expect(finalReading("1000000000", null)).toBe("closed")
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
test("given an explicit unit token when read then it overrides the value text", () => {
|
|
44
|
+
expect(finalReading("15.2", "%")).toBe("open")
|
|
45
|
+
expect(finalReading("15", "kg")).toBe("closed")
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
test("given an unknown trailing token when read then no reading is claimed", () => {
|
|
49
|
+
expect(finalReading("12 zorks", null)).toBeNull()
|
|
50
|
+
})
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
describe("rewriteParticle", () => {
|
|
54
|
+
test("given an object particle with a boundary after an open value when rewritten then it becomes the open form", () => {
|
|
55
|
+
expect(rewriteParticle("을 기록", "open")).toEqual({ text: "를 기록", applied: true, candidate: true })
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
test("given a bare particle at end of string when rewritten then it is applied", () => {
|
|
59
|
+
expect(rewriteParticle("은", "open")).toEqual({ text: "는", applied: true, candidate: true })
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
test("given a particle-like prefix with no boundary when rewritten then it stays untouched and is flagged as a candidate", () => {
|
|
63
|
+
expect(rewriteParticle("이상 기록", "closed")).toEqual({ text: "이상 기록", applied: false, candidate: true })
|
|
64
|
+
expect(rewriteParticle("이상.", "open")).toEqual({ text: "이상.", applied: false, candidate: true })
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
test("given English or a non-particle word when rewritten then nothing changes and it is not a candidate", () => {
|
|
68
|
+
expect(rewriteParticle(" is the span", "open")).toEqual({ text: " is the span", applied: false, candidate: false })
|
|
69
|
+
expect(rewriteParticle("기록했다", "open")).toEqual({ text: "기록했다", applied: false, candidate: false })
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
test("given each particle pair when rewritten then the reading picks the form", () => {
|
|
73
|
+
expect(rewriteParticle("이 올랐다", "open").text).toBe("가 올랐다")
|
|
74
|
+
expect(rewriteParticle("가 올랐다", "closed").text).toBe("이 올랐다")
|
|
75
|
+
expect(rewriteParticle("는,", "l").text).toBe("은,")
|
|
76
|
+
expect(rewriteParticle("과 함께", "open").text).toBe("와 함께")
|
|
77
|
+
expect(rewriteParticle("와 함께", "l").text).toBe("과 함께")
|
|
78
|
+
expect(rewriteParticle("로 늘었다", "closed").text).toBe("으로 늘었다")
|
|
79
|
+
expect(rewriteParticle("으로 늘었다", "l").text).toBe("로 늘었다")
|
|
80
|
+
expect(rewriteParticle("으로 늘었다", "open").text).toBe("로 늘었다")
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
test("given no reading when rewritten then a candidate is left unchanged", () => {
|
|
84
|
+
expect(rewriteParticle("을 기록", null)).toEqual({ text: "을 기록", applied: false, candidate: true })
|
|
85
|
+
})
|
|
86
|
+
})
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
// rational.mjs - exact rational arithmetic over BigInt numerator/denominator pairs.
|
|
2
|
+
//
|
|
3
|
+
// A rational is { num: bigint, den: bigint }, reduced, with den > 0. Numerals enter as decimal
|
|
4
|
+
// strings and never pass through Number(), so no value is ever rounded by floating point.
|
|
5
|
+
// Plain Node ESM, no imports.
|
|
6
|
+
|
|
7
|
+
const DECIMAL = /^([+-]?)(\d+)(?:\.(\d+))?$/
|
|
8
|
+
|
|
9
|
+
const abs = (n) => (n < 0n ? -n : n)
|
|
10
|
+
|
|
11
|
+
function gcd(a, b) {
|
|
12
|
+
let x = abs(a)
|
|
13
|
+
let y = abs(b)
|
|
14
|
+
while (y !== 0n) [x, y] = [y, x % y]
|
|
15
|
+
return x
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function make(num, den) {
|
|
19
|
+
if (den === 0n) throw new RangeError("rational: zero denominator")
|
|
20
|
+
const sign = den < 0n ? -1n : 1n
|
|
21
|
+
const g = gcd(num, den) || 1n
|
|
22
|
+
return Object.freeze({ num: (sign * num) / g, den: (sign * den) / g })
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Accepts a rational, a bigint, a safe integer, or a decimal string. */
|
|
26
|
+
function coerce(value) {
|
|
27
|
+
if (typeof value === "bigint") return make(value, 1n)
|
|
28
|
+
if (typeof value === "number") {
|
|
29
|
+
if (!Number.isSafeInteger(value)) throw new TypeError(`rational: ${value} is not a safe integer; pass a decimal string`)
|
|
30
|
+
return make(BigInt(value), 1n)
|
|
31
|
+
}
|
|
32
|
+
if (typeof value === "string") return fromDecimal(value)
|
|
33
|
+
if (value && typeof value.num === "bigint" && typeof value.den === "bigint") return make(value.num, value.den)
|
|
34
|
+
throw new TypeError("rational: expected a rational, bigint, integer, or decimal string")
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** "12.50" -> 25/2. Accepts an optional sign, digits, and an optional fraction; nothing else. */
|
|
38
|
+
export function fromDecimal(str) {
|
|
39
|
+
const match = DECIMAL.exec(String(str).trim())
|
|
40
|
+
if (!match) throw new SyntaxError(`rational: "${str}" is not a plain decimal`)
|
|
41
|
+
const [, sign, whole, frac = ""] = match
|
|
42
|
+
const num = BigInt(whole + frac) * (sign === "-" ? -1n : 1n)
|
|
43
|
+
return make(num, 10n ** BigInt(frac.length))
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function add(a, b) {
|
|
47
|
+
const x = coerce(a)
|
|
48
|
+
const y = coerce(b)
|
|
49
|
+
return make(x.num * y.den + y.num * x.den, x.den * y.den)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function sub(a, b) {
|
|
53
|
+
const x = coerce(a)
|
|
54
|
+
const y = coerce(b)
|
|
55
|
+
return make(x.num * y.den - y.num * x.den, x.den * y.den)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function mul(a, b) {
|
|
59
|
+
const x = coerce(a)
|
|
60
|
+
const y = coerce(b)
|
|
61
|
+
return make(x.num * y.num, x.den * y.den)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function div(a, b) {
|
|
65
|
+
const x = coerce(a)
|
|
66
|
+
const y = coerce(b)
|
|
67
|
+
if (y.num === 0n) throw new RangeError("rational: division by zero")
|
|
68
|
+
return make(x.num * y.den, x.den * y.num)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function eq(a, b) {
|
|
72
|
+
const x = coerce(a)
|
|
73
|
+
const y = coerce(b)
|
|
74
|
+
return x.num === y.num && x.den === y.den
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** Digits of a non-negative integer scaled by 10^-scale, as a decimal string. */
|
|
78
|
+
function placeDecimal(digits, scale) {
|
|
79
|
+
if (scale <= 0) return digits + "0".repeat(-scale)
|
|
80
|
+
const padded = digits.padStart(scale + 1, "0")
|
|
81
|
+
return `${padded.slice(0, padded.length - scale)}.${padded.slice(padded.length - scale)}`
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** Exact decimal; throws RangeError when the expansion does not terminate. */
|
|
85
|
+
function exactDecimal(r) {
|
|
86
|
+
let den = r.den
|
|
87
|
+
let twos = 0
|
|
88
|
+
let fives = 0
|
|
89
|
+
while (den % 2n === 0n) [den, twos] = [den / 2n, twos + 1]
|
|
90
|
+
while (den % 5n === 0n) [den, fives] = [den / 5n, fives + 1]
|
|
91
|
+
if (den !== 1n) throw new RangeError(`rational: ${r.num}/${r.den} has no finite decimal; pass sigFigs`)
|
|
92
|
+
const scale = Math.max(twos, fives)
|
|
93
|
+
const digits = (abs(r.num) * 10n ** BigInt(scale)) / r.den
|
|
94
|
+
return (r.num < 0n ? "-" : "") + placeDecimal(digits.toString(), scale)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** Number of decimal digits in the integer part of |num/den| (0 when below 1). */
|
|
98
|
+
function integerDigits(num, den) {
|
|
99
|
+
const whole = num / den
|
|
100
|
+
if (whole > 0n) return whole.toString().length
|
|
101
|
+
let exp = 0
|
|
102
|
+
let scaled = num
|
|
103
|
+
while (scaled < den) [scaled, exp] = [scaled * 10n, exp - 1]
|
|
104
|
+
return exp + 1
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Decimal string. Without sigFigs the value must terminate and is rendered exactly. With sigFigs it
|
|
109
|
+
* is rounded half away from zero to that many significant figures; integers are never written
|
|
110
|
+
* with an exponent, so 319999 at 2 figures renders "320000".
|
|
111
|
+
*/
|
|
112
|
+
export function toDecimal(value, sigFigs) {
|
|
113
|
+
const r = coerce(value)
|
|
114
|
+
if (sigFigs === undefined) return exactDecimal(r)
|
|
115
|
+
if (!Number.isInteger(sigFigs) || sigFigs < 1) throw new RangeError("rational: sigFigs must be a positive integer")
|
|
116
|
+
if (r.num === 0n) return sigFigs > 1 ? `0.${"0".repeat(sigFigs - 1)}` : "0"
|
|
117
|
+
const num = abs(r.num)
|
|
118
|
+
const scale = sigFigs - integerDigits(num, r.den)
|
|
119
|
+
const numer = scale >= 0 ? num * 10n ** BigInt(scale) : num
|
|
120
|
+
const denom = scale >= 0 ? r.den : r.den * 10n ** BigInt(-scale)
|
|
121
|
+
let digits = numer / denom
|
|
122
|
+
let places = scale
|
|
123
|
+
if ((numer % denom) * 2n >= denom) digits += 1n
|
|
124
|
+
if (digits.toString().length > sigFigs) [digits, places] = [digits / 10n, places - 1]
|
|
125
|
+
return (r.num < 0n ? "-" : "") + placeDecimal(digits.toString(), places)
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Significant figures of a decimal string. With a decimal point: digits from the first nonzero
|
|
130
|
+
* digit to the end, trailing zeros included ("3.2" -> 2, "0.050" -> 2). Without one: every digit
|
|
131
|
+
* after leading zeros counts ("320" -> 3, "100" -> 3, "20" -> 2).
|
|
132
|
+
*/
|
|
133
|
+
export function sigFigsOf(decimalStr) {
|
|
134
|
+
const match = DECIMAL.exec(String(decimalStr).trim())
|
|
135
|
+
if (!match) throw new SyntaxError(`rational: "${decimalStr}" is not a plain decimal`)
|
|
136
|
+
const [, , whole, frac] = match
|
|
137
|
+
const digits = (whole + (frac ?? "")).replace(/^0+/, "")
|
|
138
|
+
return Math.max(digits.length, 1)
|
|
139
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test"
|
|
2
|
+
|
|
3
|
+
import { add, div, eq, fromDecimal, mul, sigFigsOf, sub, toDecimal } from "./rational.mjs"
|
|
4
|
+
|
|
5
|
+
describe("rational", () => {
|
|
6
|
+
test("keeps every digit of a long decimal through multiplication", () => {
|
|
7
|
+
const product = mul(fromDecimal("10000000000000000.1"), fromDecimal("3"))
|
|
8
|
+
expect(toDecimal(product)).toBe("30000000000000000.3")
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
test("rounds a recurring quotient to the requested significant figures", () => {
|
|
12
|
+
expect(toDecimal(div(fromDecimal("1"), fromDecimal("3")), 3)).toBe("0.333")
|
|
13
|
+
expect(toDecimal(div(fromDecimal("2"), fromDecimal("3")), 2)).toBe("0.67")
|
|
14
|
+
expect(toDecimal(fromDecimal("319999"), 2)).toBe("320000")
|
|
15
|
+
expect(toDecimal(fromDecimal("-2.5"), 1)).toBe("-3")
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
test("refuses an exact decimal for a recurring value", () => {
|
|
19
|
+
expect(() => toDecimal(div(fromDecimal("1"), fromDecimal("3")))).toThrow(RangeError)
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
test("add, sub and eq work on reduced fractions", () => {
|
|
23
|
+
const third = div(fromDecimal("1"), fromDecimal("3"))
|
|
24
|
+
expect(eq(add(third, add(third, third)), fromDecimal("1"))).toBe(true)
|
|
25
|
+
expect(toDecimal(sub(fromDecimal("0.3"), fromDecimal("0.1")))).toBe("0.2")
|
|
26
|
+
expect(eq(fromDecimal("1.50"), fromDecimal("1.5"))).toBe(true)
|
|
27
|
+
expect(eq(fromDecimal("1.5"), fromDecimal("1.6"))).toBe(false)
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
test("never routes values through floating point", () => {
|
|
31
|
+
const r = fromDecimal("0.1")
|
|
32
|
+
expect(typeof r.num).toBe("bigint")
|
|
33
|
+
expect(typeof r.den).toBe("bigint")
|
|
34
|
+
expect(() => fromDecimal("1e3")).toThrow()
|
|
35
|
+
expect(() => div(fromDecimal("1"), fromDecimal("0"))).toThrow(RangeError)
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
test("counts significant figures: decimals from the first nonzero digit, integers every digit", () => {
|
|
39
|
+
expect(sigFigsOf("3.2")).toBe(2)
|
|
40
|
+
expect(sigFigsOf("0.050")).toBe(2)
|
|
41
|
+
expect(sigFigsOf("320")).toBe(3)
|
|
42
|
+
expect(sigFigsOf("100")).toBe(3)
|
|
43
|
+
expect(sigFigsOf("20")).toBe(2)
|
|
44
|
+
expect(sigFigsOf("-1.0")).toBe(2)
|
|
45
|
+
})
|
|
46
|
+
})
|
|
@@ -5,7 +5,7 @@ import { existsSync, readFileSync, statSync, writeFileSync } from "node:fs"
|
|
|
5
5
|
import { dirname, join, resolve } from "node:path"
|
|
6
6
|
|
|
7
7
|
import { CliError, parseArgs, readJsonFile, writeJsonAtomic } from "./cli-support.mjs"
|
|
8
|
-
import { REPAIR_DEFAULTS } from "./contracts.mjs"
|
|
8
|
+
import { GATE_NAMES, GATE_STATUSES, REPAIR_DEFAULTS } from "./contracts.mjs"
|
|
9
9
|
import { parseLineageMode, parsePalette, renderDesignSpec } from "./design-spec.mjs"
|
|
10
10
|
import { extractFromHtml, extractFromPath, fetchHtml } from "./format-extract.mjs"
|
|
11
11
|
import { checkLayout } from "./gates-layout.mjs"
|
|
@@ -149,7 +149,7 @@ export function runOutcome(argv) {
|
|
|
149
149
|
}
|
|
150
150
|
case "gate": {
|
|
151
151
|
const [gate, status] = args.positionals
|
|
152
|
-
if (!gate || !status) throw new CliError(
|
|
152
|
+
if (!gate || !status) throw new CliError(`usage: outcome gate <${GATE_NAMES.join("|")}> <${GATE_STATUSES.join("|")}>`)
|
|
153
153
|
outcome.writeManifest(file, outcome.setGate(outcome.readManifest(file), gate, status))
|
|
154
154
|
return { json: { command: "outcome gate", gate, status }, exitCode: 0 }
|
|
155
155
|
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
// sentence-split.mjs - deterministic sentence index for snapshot text.
|
|
2
|
+
//
|
|
3
|
+
// splitSentences(text) returns [{ sid, start, end, text }] with sid "s<k>" (1-based) and
|
|
4
|
+
// start/end as offsets into the raw text, so text.slice(start, end) === record.text.
|
|
5
|
+
// Boundaries: a line break; ". ! ?" followed by whitespace or end of line (known
|
|
6
|
+
// abbreviations such as "e.g." excepted); and the Korean endings da. yo. nida. kka? even
|
|
7
|
+
// with no following space, unless closing punctuation follows (quoted speech such as
|
|
8
|
+
// '...da."rago' then needs whitespace like any other terminator). "no." is an abbreviation
|
|
9
|
+
// only before a number ("No. 5"). Plain Node ESM, no dependencies.
|
|
10
|
+
|
|
11
|
+
import { normalizeText } from "./text-normalize.mjs"
|
|
12
|
+
|
|
13
|
+
const TERMINATORS = ".!?"
|
|
14
|
+
const CLOSERS = "\"')]}\u201D\u2019\u00BB\u300D\u300F"
|
|
15
|
+
const KOREAN_PERIOD_ENDINGS = new Set(["\uB2E4", "\uC694"]) // da, yo (nida ends in da)
|
|
16
|
+
const KOREAN_QUESTION_ENDING = "\uAE4C" // kka
|
|
17
|
+
const ABBREVIATIONS = new Set([
|
|
18
|
+
"e.g", "i.e", "etc", "vs", "cf", "approx", "al", "fig", "no", "mr", "mrs", "ms", "dr", "prof", "inc", "ltd", "jr", "sr",
|
|
19
|
+
])
|
|
20
|
+
const SPACE = /\s/u
|
|
21
|
+
|
|
22
|
+
function isKoreanEnding(line, at) {
|
|
23
|
+
const prev = line[at - 1]
|
|
24
|
+
if (line[at] === ".") return KOREAN_PERIOD_ENDINGS.has(prev)
|
|
25
|
+
return line[at] === "?" && prev === KOREAN_QUESTION_ENDING
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function isAbbreviation(line, from, at) {
|
|
29
|
+
let k = at
|
|
30
|
+
while (k > from && !SPACE.test(line[k - 1])) k--
|
|
31
|
+
const word = line.slice(k, at).replace(/^[("'[]+/u, "").toLowerCase()
|
|
32
|
+
if (word === "no") return /^\s*\d/u.test(line.slice(at + 1))
|
|
33
|
+
return ABBREVIATIONS.has(word)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function pushSegment(line, base, from, to, out) {
|
|
37
|
+
let start = from
|
|
38
|
+
let end = to
|
|
39
|
+
while (start < end && SPACE.test(line[start])) start++
|
|
40
|
+
while (end > start && SPACE.test(line[end - 1])) end--
|
|
41
|
+
const text = line.slice(start, end)
|
|
42
|
+
if (normalizeText(text) === "") return
|
|
43
|
+
out.push({ sid: `s${out.length + 1}`, start: base + start, end: base + end, text })
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function splitLine(line, base, out) {
|
|
47
|
+
let from = 0
|
|
48
|
+
for (let i = 0; i < line.length; i++) {
|
|
49
|
+
if (!TERMINATORS.includes(line[i])) continue
|
|
50
|
+
let j = i + 1
|
|
51
|
+
while (j < line.length && (TERMINATORS.includes(line[j]) || CLOSERS.includes(line[j]))) j++
|
|
52
|
+
const atBreak = j >= line.length || SPACE.test(line[j])
|
|
53
|
+
const closed = [...line.slice(i + 1, j)].some((c) => CLOSERS.includes(c))
|
|
54
|
+
const korean = !closed && isKoreanEnding(line, i)
|
|
55
|
+
const abbreviation = line[i] === "." && !korean && isAbbreviation(line, from, i)
|
|
56
|
+
if ((atBreak || korean) && !abbreviation) {
|
|
57
|
+
pushSegment(line, base, from, j, out)
|
|
58
|
+
from = j
|
|
59
|
+
}
|
|
60
|
+
i = j - 1
|
|
61
|
+
}
|
|
62
|
+
pushSegment(line, base, from, line.length, out)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function splitSentences(text) {
|
|
66
|
+
const source = String(text ?? "")
|
|
67
|
+
const out = []
|
|
68
|
+
let base = 0
|
|
69
|
+
while (base <= source.length) {
|
|
70
|
+
const newline = source.indexOf("\n", base)
|
|
71
|
+
const stop = newline === -1 ? source.length : newline
|
|
72
|
+
splitLine(source.slice(base, stop), base, out)
|
|
73
|
+
if (newline === -1) break
|
|
74
|
+
base = newline + 1
|
|
75
|
+
}
|
|
76
|
+
return out
|
|
77
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test"
|
|
2
|
+
|
|
3
|
+
import { splitSentences } from "./sentence-split.mjs"
|
|
4
|
+
|
|
5
|
+
type Sentence = { sid: string; start: number; end: number; text: string }
|
|
6
|
+
|
|
7
|
+
function at(records: Sentence[], index: number): Sentence {
|
|
8
|
+
const record = records[index]
|
|
9
|
+
if (record === undefined) throw new Error(`no sentence at index ${index}`)
|
|
10
|
+
return record
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function expectSlices(text: string, records: Sentence[]): void {
|
|
14
|
+
for (const record of records) expect(text.slice(record.start, record.end)).toBe(record.text)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
describe("splitSentences", () => {
|
|
18
|
+
test("#given a Korean paragraph with da/yo/nida endings #when split #then sids and offsets match each sentence", () => {
|
|
19
|
+
// given
|
|
20
|
+
const text = "매출은 100억 원입니다. 비용도 늘었어요. 이익은 줄었다.다만 회복이 기대된다."
|
|
21
|
+
// when
|
|
22
|
+
const records = splitSentences(text) as Sentence[]
|
|
23
|
+
// then
|
|
24
|
+
expect(records.map((r) => r.sid)).toEqual(["s1", "s2", "s3", "s4"])
|
|
25
|
+
expect(records.map((r) => r.text)).toEqual([
|
|
26
|
+
"매출은 100억 원입니다.",
|
|
27
|
+
"비용도 늘었어요.",
|
|
28
|
+
"이익은 줄었다.",
|
|
29
|
+
"다만 회복이 기대된다.",
|
|
30
|
+
])
|
|
31
|
+
expect(at(records, 1).start).toBe(text.indexOf("비용도"))
|
|
32
|
+
expectSlices(text, records)
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
test("#given an English paragraph with abbreviations and decimals #when split #then e.g. and 3.2 do not end a sentence", () => {
|
|
36
|
+
// given
|
|
37
|
+
const text = "Use units, e.g. 3.2 m per bar. The next one follows! Is it fine? Yes."
|
|
38
|
+
// when
|
|
39
|
+
const records = splitSentences(text) as Sentence[]
|
|
40
|
+
// then
|
|
41
|
+
expect(records.map((r) => r.sid)).toEqual(["s1", "s2", "s3", "s4"])
|
|
42
|
+
expect(records.map((r) => r.text)).toEqual([
|
|
43
|
+
"Use units, e.g. 3.2 m per bar.",
|
|
44
|
+
"The next one follows!",
|
|
45
|
+
"Is it fine?",
|
|
46
|
+
"Yes.",
|
|
47
|
+
])
|
|
48
|
+
expect(at(records, 3).start).toBe(text.indexOf("Yes."))
|
|
49
|
+
expect(at(records, 3).end).toBe(text.length)
|
|
50
|
+
expectSlices(text, records)
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
test("#given line breaks and blank lines #when split #then each non-empty line yields its own record", () => {
|
|
54
|
+
// given
|
|
55
|
+
const text = "매출은 100억 원이다. 다만 아직 확정되지 않았다.\n\n Second line without stop\nThird?"
|
|
56
|
+
// when
|
|
57
|
+
const records = splitSentences(text) as Sentence[]
|
|
58
|
+
// then
|
|
59
|
+
expect(records.map((r) => r.text)).toEqual([
|
|
60
|
+
"매출은 100억 원이다.",
|
|
61
|
+
"다만 아직 확정되지 않았다.",
|
|
62
|
+
"Second line without stop",
|
|
63
|
+
"Third?",
|
|
64
|
+
])
|
|
65
|
+
expect(records.map((r) => r.sid)).toEqual(["s1", "s2", "s3", "s4"])
|
|
66
|
+
expectSlices(text, records)
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
test("#given Korean quoted speech closed right after a da ending #when split #then the quote stays inside one sentence", () => {
|
|
70
|
+
// given
|
|
71
|
+
const single = '그는 "매출이 늘었다."라고 말했다.'
|
|
72
|
+
const pair = '그는 "매출이 늘었다."라고 말했다. 다음 날 떨어졌다.'
|
|
73
|
+
const bracket = "그는 「좋아요.」라고 답했다."
|
|
74
|
+
// when
|
|
75
|
+
const singleRecords = splitSentences(single) as Sentence[]
|
|
76
|
+
const pairRecords = splitSentences(pair) as Sentence[]
|
|
77
|
+
const bracketRecords = splitSentences(bracket) as Sentence[]
|
|
78
|
+
// then
|
|
79
|
+
expect(singleRecords.map((r) => r.text)).toEqual([single])
|
|
80
|
+
expect(pairRecords.map((r) => r.text)).toEqual(['그는 "매출이 늘었다."라고 말했다.', "다음 날 떨어졌다."])
|
|
81
|
+
expect(pairRecords.map((r) => r.sid)).toEqual(["s1", "s2"])
|
|
82
|
+
expect(at(pairRecords, 0).start).toBe(0)
|
|
83
|
+
expect(at(pairRecords, 1).start).toBe(pair.indexOf("다음 날"))
|
|
84
|
+
expect(at(pairRecords, 1).end).toBe(pair.length)
|
|
85
|
+
expect(bracketRecords.map((r) => r.text)).toEqual([bracket])
|
|
86
|
+
expectSlices(pair, pairRecords)
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
test("#given the word no #when split #then it is an abbreviation only before a number", () => {
|
|
90
|
+
// given
|
|
91
|
+
const answer = "The answer was no. Then we left."
|
|
92
|
+
const numbered = "See No. 5 for details. Then stop."
|
|
93
|
+
// when
|
|
94
|
+
const answerRecords = splitSentences(answer) as Sentence[]
|
|
95
|
+
const numberedRecords = splitSentences(numbered) as Sentence[]
|
|
96
|
+
// then
|
|
97
|
+
expect(answerRecords.map((r) => r.text)).toEqual(["The answer was no.", "Then we left."])
|
|
98
|
+
expect(at(answerRecords, 1).start).toBe(answer.indexOf("Then"))
|
|
99
|
+
expect(numberedRecords.map((r) => r.text)).toEqual(["See No. 5 for details.", "Then stop."])
|
|
100
|
+
expectSlices(answer, answerRecords)
|
|
101
|
+
expectSlices(numbered, numberedRecords)
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
test("#given empty or whitespace-only text #when split #then no records are produced", () => {
|
|
105
|
+
// when / then
|
|
106
|
+
expect(splitSentences("")).toEqual([])
|
|
107
|
+
expect(splitSentences(" \n\t\n")).toEqual([])
|
|
108
|
+
})
|
|
109
|
+
})
|
package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/claim-graph.md
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Claim graph
|
|
2
|
+
|
|
3
|
+
## verified-claims
|
|
4
|
+
|
|
5
|
+
- C1: The bridge span is up to 320 m.
|
|
6
|
+
|
|
7
|
+
## Claims
|
|
8
|
+
|
|
9
|
+
| claim_id | statement | claim type | risk tier | status | final synthesis location |
|
|
10
|
+
|---|---|---|---|---|---|
|
|
11
|
+
| C1 | The bridge span is up to 320 m. | factual | high | supported | findings |
|
|
12
|
+
| C2 | Revenue grew about 10 percent in 2025. | factual | normal | partial | findings |
|
|
13
|
+
| C3 | The project finished ahead of schedule. | factual | high | refuted | gaps |
|
package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity-brief-locked.md
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Brief
|
|
2
|
+
|
|
3
|
+
## Deliverable
|
|
4
|
+
format: pdf (answered_by: request)
|
|
5
|
+
|
|
6
|
+
## Fidelity
|
|
7
|
+
as of: latest at run start, 2026-10-23 (answered_by: default)
|
|
8
|
+
meaning check: on (answered_by: default)
|
|
9
|
+
source floor: standard (answered_by: default)
|
|
10
|
+
derived numbers: allowed (answered_by: user)
|
|
11
|
+
languages: report ko; sources ko, en; quotes original with translation beside (answered_by: default)
|
|
12
|
+
identifier classes: version strings, package names, commit ids (answered_by: default)
|
|
13
|
+
fallback: block-quote (answered_by: request)
|
|
14
|
+
asked: derived numbers
|
|
15
|
+
|
|
16
|
+
## Notes
|
|
17
|
+
free text that is not part of the block
|
package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity-brief-pending.md
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Brief
|
|
2
|
+
|
|
3
|
+
## Deliverable
|
|
4
|
+
format: pdf (answered_by: request)
|
|
5
|
+
|
|
6
|
+
## Fidelity
|
|
7
|
+
as of: latest at run start, 2026-10-23 (answered_by: default)
|
|
8
|
+
meaning check: on (answered_by: default)
|
|
9
|
+
source floor: standard (answered_by: default)
|
|
10
|
+
derived numbers: pending (answered_by: pending)
|
|
11
|
+
languages: report ko; sources ko, en; quotes original with translation beside (answered_by: default)
|
|
12
|
+
identifier classes: version strings, package names, commit ids (answered_by: default)
|
|
13
|
+
fallback: block-quote (answered_by: request)
|
|
14
|
+
asked: derived numbers
|
|
15
|
+
|
|
16
|
+
## Notes
|
|
17
|
+
free text that is not part of the block
|