lazycodex-ai 5.1.44 → 5.1.46
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.de.md +0 -5
- package/README.es.md +0 -5
- package/README.fr.md +0 -5
- package/README.it.md +0 -5
- package/README.ja.md +0 -5
- package/README.ko.md +0 -5
- package/README.md +4 -5
- package/README.pt-br.md +0 -5
- package/README.ru.md +0 -6
- package/README.zh-cn.md +0 -5
- package/dist/cli/index.js +865 -413
- package/dist/cli/service-auth/keystore.d.ts +4 -2
- package/dist/cli-node/index.js +865 -413
- package/docs/reference/web-terminal-visual-qa.md +8 -0
- package/package.json +1 -1
- package/packages/lsp-daemon/dist/cli.js +7 -7
- package/packages/lsp-daemon/dist/client.js +7 -7
- package/packages/lsp-daemon/dist/index.js +7 -7
- package/packages/lsp-daemon/package.json +3 -3
- package/packages/lsp-tools-mcp/dist/cli.js +7 -7
- package/packages/lsp-tools-mcp/dist/mcp.js +7 -7
- 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 +77 -26
- 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 +14 -9
- package/packages/omo-codex/plugin/skills/ast-grep/AGENTS.md +1 -1
- 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/ATTRIBUTION.md +2 -2
- 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/ultimate-browsing/engine/AGENTS.md +1 -1
- 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 +21 -3
- package/packages/omo-codex/plugin/skills/ulw-research/SKILL.md +37 -12
- package/packages/omo-codex/plugin/skills/ulw-research/references/deliverable-phase.md +77 -16
- package/packages/omo-codex/plugin/skills/ulw-research/references/fact-ledger-grammar.md +134 -0
- package/packages/omo-codex/plugin/skills/ulw-research/references/fact-ledger.md +95 -0
- package/packages/omo-codex/plugin/skills/ulw-research/references/fidelity-claim-kinds.md +19 -0
- package/packages/omo-codex/plugin/skills/ulw-research/references/report-gates.md +68 -3
- package/packages/omo-codex/plugin/skills/ulw-research/references/report-visualization.md +2 -0
- package/packages/omo-codex/plugin/skills/ulw-research/references/unit-rules.md +157 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/claim-kinds.mjs +116 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/claims-index.mjs +125 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/cli-support.mjs +19 -5
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/contracts.mjs +89 -3
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-commands-meaning.mjs +191 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-commands-verify.mjs +164 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-commands.mjs +239 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-derive.mjs +66 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-expr.mjs +108 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-fill.mjs +194 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-ledger-kinds.mjs +250 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-ledger-schema.mjs +221 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-ledger.mjs +123 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-locate.mjs +231 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-pairs.mjs +187 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-recompute.mjs +89 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-binding.mjs +130 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-brief.mjs +150 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-claims.mjs +89 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-lexicon.mjs +145 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-records.mjs +163 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-state.mjs +64 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-trace.mjs +85 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-values.mjs +229 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/gates-fidelity.mjs +113 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/gates-static.mjs +29 -9
- 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/html-lite.mjs +2 -2
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/html-markup.mjs +182 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/lexicon.mjs +47 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/md-lite.mjs +193 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/meaning-check.mjs +196 -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 +8 -8
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/particles-ko.mjs +108 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/placeholder.mjs +184 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/polish-apply.mjs +115 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/polish-diff.mjs +31 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/polish-project.mjs +125 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/polish-rules.mjs +170 -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 +21 -10
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/report-tools.mjs +15 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/sentence-split.mjs +77 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/source-commands.mjs +140 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/source-snapshot.mjs +176 -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/unit-algebra.mjs +101 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/unit-errors.mjs +23 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/unit-metadata.mjs +31 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/unit-pairs.mjs +72 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/unit-precision.mjs +44 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/unit-rules-shapes.mjs +64 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/unit-rules.mjs +173 -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-codex-compatibility.test.mjs +19 -0
- package/packages/omo-codex/plugin/test/sync-skills-test-support.mjs +12 -8
- package/packages/omo-codex/scripts/install-dist/install-local.mjs +169 -30
- package/packages/shared-skills/skills/ast-grep/AGENTS.md +1 -1
- 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/ATTRIBUTION.md +2 -2
- 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/ultimate-browsing/engine/AGENTS.md +1 -1
- package/packages/shared-skills/skills/ulw-execute/SKILL.md +1 -1
- package/packages/shared-skills/skills/ulw-research/AGENTS.md +21 -3
- package/packages/shared-skills/skills/ulw-research/SKILL.md +37 -12
- package/packages/shared-skills/skills/ulw-research/references/deliverable-phase.md +77 -16
- package/packages/shared-skills/skills/ulw-research/references/fact-ledger-grammar.md +134 -0
- package/packages/shared-skills/skills/ulw-research/references/fact-ledger.md +95 -0
- package/packages/shared-skills/skills/ulw-research/references/fidelity-claim-kinds.md +19 -0
- package/packages/shared-skills/skills/ulw-research/references/report-gates.md +68 -3
- package/packages/shared-skills/skills/ulw-research/references/report-visualization.md +2 -0
- package/packages/shared-skills/skills/ulw-research/references/unit-rules.md +157 -0
- package/packages/shared-skills/skills/ulw-research/scripts/claim-kinds.mjs +116 -0
- package/packages/shared-skills/skills/ulw-research/scripts/claim-kinds.test.ts +193 -0
- 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/cli-support.mjs +19 -5
- package/packages/shared-skills/skills/ulw-research/scripts/cli-support.test.ts +13 -2
- package/packages/shared-skills/skills/ulw-research/scripts/contracts.mjs +89 -3
- package/packages/shared-skills/skills/ulw-research/scripts/contracts.test.ts +100 -12
- package/packages/shared-skills/skills/ulw-research/scripts/copy-returns.test.ts +183 -0
- package/packages/shared-skills/skills/ulw-research/scripts/delivery-barrier.test.ts +245 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fact-commands-meaning.mjs +191 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fact-commands-verify.mjs +164 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fact-commands.mjs +239 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fact-derive.mjs +66 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fact-derive.test.ts +375 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fact-expr.mjs +108 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fact-fill.mjs +194 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fact-fill.test.ts +221 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fact-ledger-grammar.test.ts +197 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fact-ledger-kinds.mjs +250 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fact-ledger-kinds.test.ts +156 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fact-ledger-legacy.test.ts +160 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fact-ledger-schema.mjs +221 -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 +123 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fact-ledger.test.ts +254 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fact-locate.mjs +231 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fact-locate.test.ts +118 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fact-pairs.mjs +187 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fact-pairs.test.ts +167 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fact-recompute.mjs +89 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fact-unit-dimensions.test.ts +409 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fact-unit-rules.test.ts +388 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fidelity-binding.mjs +130 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fidelity-binding.test.ts +81 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fidelity-brief.mjs +150 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fidelity-brief.test.ts +179 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fidelity-claims.mjs +89 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fidelity-lexicon.mjs +145 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fidelity-lexicon.test.ts +117 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fidelity-records.mjs +163 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fidelity-state.mjs +64 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fidelity-state.test.ts +46 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fidelity-trace.mjs +85 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fidelity-trace.test.ts +58 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fidelity-values.mjs +229 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fidelity-values.test.ts +270 -0
- package/packages/shared-skills/skills/ulw-research/scripts/gates-context.test.ts +64 -0
- package/packages/shared-skills/skills/ulw-research/scripts/gates-fidelity.mjs +113 -0
- package/packages/shared-skills/skills/ulw-research/scripts/gates-fidelity.test.ts +73 -0
- package/packages/shared-skills/skills/ulw-research/scripts/gates-static.mjs +29 -9
- 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/html-lite.mjs +2 -2
- package/packages/shared-skills/skills/ulw-research/scripts/html-markup.mjs +182 -0
- 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/md-lite.mjs +193 -0
- package/packages/shared-skills/skills/ulw-research/scripts/md-lite.test.ts +271 -0
- package/packages/shared-skills/skills/ulw-research/scripts/meaning-check.mjs +196 -0
- package/packages/shared-skills/skills/ulw-research/scripts/meaning-check.test.ts +228 -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 +8 -8
- 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/placeholder.mjs +184 -0
- package/packages/shared-skills/skills/ulw-research/scripts/placeholder.test.ts +77 -0
- package/packages/shared-skills/skills/ulw-research/scripts/polish-apply.mjs +115 -0
- package/packages/shared-skills/skills/ulw-research/scripts/polish-apply.test.ts +363 -0
- package/packages/shared-skills/skills/ulw-research/scripts/polish-diff.mjs +31 -0
- package/packages/shared-skills/skills/ulw-research/scripts/polish-diff.test.ts +168 -0
- package/packages/shared-skills/skills/ulw-research/scripts/polish-markup.test.ts +187 -0
- package/packages/shared-skills/skills/ulw-research/scripts/polish-project.mjs +125 -0
- package/packages/shared-skills/skills/ulw-research/scripts/polish-rules.mjs +170 -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 +21 -10
- package/packages/shared-skills/skills/ulw-research/scripts/report-tools.mjs +15 -0
- package/packages/shared-skills/skills/ulw-research/scripts/report-tools.test.ts +458 -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/source-commands.mjs +140 -0
- package/packages/shared-skills/skills/ulw-research/scripts/source-snapshot.mjs +176 -0
- package/packages/shared-skills/skills/ulw-research/scripts/source-snapshot.test.ts +113 -0
- package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/assets/share.csv +3 -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/gen-legacy-dev.mjs +89 -0
- package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/ledger-basic.json +223 -0
- package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/ledger-legacy-dev.json +859 -0
- package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/ledger-verify.json +420 -0
- package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/report-bad.html +19 -0
- package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/report-good.html +39 -0
- package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/sources/S4/acronyms.json +3 -0
- package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/sources/S4/meta.json +9 -0
- package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/sources/S4/sentences.jsonl +7 -0
- package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/sources/S4/text.txt +7 -0
- package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/sources/S4.html +19 -0
- package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/sources/S5/acronyms.json +1 -0
- package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/sources/S5/meta.json +9 -0
- package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/sources/S5/sentences.jsonl +1 -0
- package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/sources/S5/text.txt +1 -0
- package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/sources/S9.txt +1 -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/unit-algebra.mjs +101 -0
- package/packages/shared-skills/skills/ulw-research/scripts/unit-errors.mjs +23 -0
- package/packages/shared-skills/skills/ulw-research/scripts/unit-metadata.mjs +31 -0
- package/packages/shared-skills/skills/ulw-research/scripts/unit-pairs.mjs +72 -0
- package/packages/shared-skills/skills/ulw-research/scripts/unit-precision.mjs +44 -0
- package/packages/shared-skills/skills/ulw-research/scripts/unit-rules-shapes.mjs +64 -0
- package/packages/shared-skills/skills/ulw-research/scripts/unit-rules.mjs +173 -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,184 @@
|
|
|
1
|
+
// placeholder.mjs - the fact pointer grammar of the fill step and its render helpers.
|
|
2
|
+
// Value pointers: {{F12:label}}, {{F12:label:value}}, {{F12:label:unit}}, {{F12:label:q}}.
|
|
3
|
+
// Quote pointers: {{Q7:label}}, {{Q7:label:lead=18}}, {{Q7:label:from="x":to="y"}} (anchors are
|
|
4
|
+
// JSON strings, so they may hold a colon or an escaped quote). Labels are compared exactly by the
|
|
5
|
+
// caller; this module never guesses an id. Pure functions; node: builtins only.
|
|
6
|
+
|
|
7
|
+
import { parseNumeral } from "./numerals.mjs"
|
|
8
|
+
|
|
9
|
+
export const ELLIPSIS = "\u2026"
|
|
10
|
+
|
|
11
|
+
const ID = /^([FQ])\d+$/
|
|
12
|
+
const LABEL = /^[^\s:{}"]+$/
|
|
13
|
+
const VALUE_MODES = new Set(["value", "unit", "q"])
|
|
14
|
+
const LEAD = /^lead=([1-9]\d*)$/
|
|
15
|
+
const LINEAGE = Object.freeze({ measured: "MEASURED", assumed: "ASSUMED", derived: "DERIVED", converted: "DERIVED" })
|
|
16
|
+
|
|
17
|
+
/** End index (exclusive) of a JSON string starting at `at`, or -1 when it is not closed on its line. */
|
|
18
|
+
function jsonStringEnd(text, at) {
|
|
19
|
+
let i = at + 1
|
|
20
|
+
while (i < text.length && text[i] !== '"' && text[i] !== "\n") i += text[i] === "\\" ? 2 : 1
|
|
21
|
+
return text[i] === '"' ? i + 1 : -1
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function readJsonString(text, at) {
|
|
25
|
+
if (text[at] !== '"') return null
|
|
26
|
+
const end = jsonStringEnd(text, at)
|
|
27
|
+
if (end === -1) return null
|
|
28
|
+
try {
|
|
29
|
+
return { value: JSON.parse(text.slice(at, end)), end }
|
|
30
|
+
} catch {
|
|
31
|
+
return null
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function parseAnchors(tail) {
|
|
36
|
+
if (!tail.startsWith("from=")) return null
|
|
37
|
+
const from = readJsonString(tail, 5)
|
|
38
|
+
if (from === null || !tail.startsWith(":to=", from.end)) return null
|
|
39
|
+
const to = readJsonString(tail, from.end + 4)
|
|
40
|
+
if (to === null || to.end !== tail.length || from.value === "" || to.value === "") return null
|
|
41
|
+
return { from: from.value, to: to.value }
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** @returns the parsed pointer, or { error } for a malformed one. */
|
|
45
|
+
export function parsePlaceholder(token) {
|
|
46
|
+
if (!token.startsWith("{{") || !token.endsWith("}}")) return { error: "a placeholder is {{ID:label...}}" }
|
|
47
|
+
const body = token.slice(2, -2)
|
|
48
|
+
const colon = body.indexOf(":")
|
|
49
|
+
const id = colon === -1 ? body : body.slice(0, colon)
|
|
50
|
+
const idMatch = ID.exec(id)
|
|
51
|
+
if (idMatch === null) return { error: `"${id}" is not an F<n> or Q<n> id` }
|
|
52
|
+
if (colon === -1) return { error: `${id} has no label; write {{${id}:label}}` }
|
|
53
|
+
const rest = body.slice(colon + 1)
|
|
54
|
+
const second = rest.indexOf(":")
|
|
55
|
+
const label = second === -1 ? rest : rest.slice(0, second)
|
|
56
|
+
if (!LABEL.test(label)) return { error: `"${label}" is not a label` }
|
|
57
|
+
const tail = second === -1 ? null : rest.slice(second + 1)
|
|
58
|
+
const base = { id, kind: idMatch[1], label }
|
|
59
|
+
if (idMatch[1] === "F") {
|
|
60
|
+
if (tail === null) return { ...base, mode: "full" }
|
|
61
|
+
return VALUE_MODES.has(tail) ? { ...base, mode: tail } : { error: `"${tail}" is not a value mode (value, unit, q)` }
|
|
62
|
+
}
|
|
63
|
+
if (tail === null) return { ...base, mode: "full", selector: "full" }
|
|
64
|
+
const lead = LEAD.exec(tail)
|
|
65
|
+
if (lead !== null) return { ...base, mode: "lead", lead: Number(lead[1]), selector: `lead=${lead[1]}` }
|
|
66
|
+
const anchors = parseAnchors(tail)
|
|
67
|
+
if (anchors === null) return { error: `"${tail}" is not a quote selector (lead=N or from="..":to="..")` }
|
|
68
|
+
return { ...base, mode: "span", ...anchors, selector: `from=${anchors.from};to=${anchors.to}` }
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** End index (exclusive) of the pointer opened at `at`, or -1 when it is not closed on its line. */
|
|
72
|
+
function placeholderEnd(text, at) {
|
|
73
|
+
let i = at + 2
|
|
74
|
+
while (i < text.length && text[i] !== "\n") {
|
|
75
|
+
if (text[i] === '"') {
|
|
76
|
+
const end = jsonStringEnd(text, i)
|
|
77
|
+
if (end === -1) return -1
|
|
78
|
+
i = end
|
|
79
|
+
} else if (text.startsWith("}}", i)) return i + 2
|
|
80
|
+
else i++
|
|
81
|
+
}
|
|
82
|
+
return -1
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** Every `{{...}}` pointer in source order: [{ token, start, end, parsed }]. */
|
|
86
|
+
export function findPlaceholders(text) {
|
|
87
|
+
const found = []
|
|
88
|
+
let at = text.indexOf("{{")
|
|
89
|
+
while (at !== -1) {
|
|
90
|
+
const end = placeholderEnd(text, at)
|
|
91
|
+
if (end === -1) {
|
|
92
|
+
found.push({ token: text.slice(at, text.indexOf("\n", at) === -1 ? text.length : text.indexOf("\n", at)), start: at, end: at + 2, parsed: { error: "the placeholder is not closed on its line" } })
|
|
93
|
+
at = text.indexOf("{{", at + 2)
|
|
94
|
+
continue
|
|
95
|
+
}
|
|
96
|
+
const token = text.slice(at, end)
|
|
97
|
+
found.push({ token, start: at, end, parsed: parsePlaceholder(token) })
|
|
98
|
+
at = text.indexOf("{{", end)
|
|
99
|
+
}
|
|
100
|
+
return found
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function occurrences(text, needle) {
|
|
104
|
+
let count = 0
|
|
105
|
+
for (let at = text.indexOf(needle); at !== -1; at = text.indexOf(needle, at + 1)) count++
|
|
106
|
+
return count
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function cut(quote, start, end) {
|
|
110
|
+
return { text: `${start > 0 ? ELLIPSIS : ""}${quote.slice(start, end)}${end < quote.length ? ELLIPSIS : ""}`, start, end }
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** The contiguous span from `from` through `to`; each anchor must occur exactly once, in order. */
|
|
114
|
+
export function subSpan(quote, from, to) {
|
|
115
|
+
if (occurrences(quote, from) !== 1 || occurrences(quote, to) !== 1) return null
|
|
116
|
+
const start = quote.indexOf(from)
|
|
117
|
+
const toAt = quote.indexOf(to)
|
|
118
|
+
if (toAt < start || toAt + to.length < start + from.length) return null
|
|
119
|
+
return cut(quote, start, toAt + to.length)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** The first `n` whitespace-delimited words, with an ellipsis when words were cut. */
|
|
123
|
+
export function lead(quote, n) {
|
|
124
|
+
const words = [...quote.matchAll(/\S+/g)]
|
|
125
|
+
if (words.length <= n) return { text: quote, start: 0, end: quote.length }
|
|
126
|
+
const last = words[n - 1]
|
|
127
|
+
return cut(quote, 0, last.index + last[0].length)
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** The value and unit parts of a row's raw text ("4.2 eok won" -> value "4.2 eok", unit "won"). */
|
|
131
|
+
export function valueParts(row) {
|
|
132
|
+
const unitText = parseNumeral(row.raw)?.unitRaw ?? row.unit
|
|
133
|
+
const valueText = unitText && row.raw.endsWith(unitText) ? row.raw.slice(0, -unitText.length).trimEnd() : (row.value ?? row.raw)
|
|
134
|
+
return { valueText, unitText: unitText ?? "" }
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/** The quote interval holding the row's qualifier and its raw quantity, or null if absent. */
|
|
138
|
+
export function qualifiedText(row) {
|
|
139
|
+
if (row.qualifier === null) return row.raw
|
|
140
|
+
for (let numeral = parseNumeral(row.quote); numeral !== null; numeral = parseNumeral(row.quote, numeral.span[1])) {
|
|
141
|
+
if (numeral.raw !== row.raw || numeral.qualifierRaw === null) continue
|
|
142
|
+
const [start, end] = numeral.span
|
|
143
|
+
const before = row.quote.lastIndexOf(numeral.qualifierRaw, start)
|
|
144
|
+
const between = before === -1 ? [] : row.quote.slice(before + numeral.qualifierRaw.length, start).split(/\s+/u).filter(Boolean)
|
|
145
|
+
if (before !== -1 && between.length <= 1) return row.quote.slice(before, end)
|
|
146
|
+
const after = row.quote.indexOf(numeral.qualifierRaw, end)
|
|
147
|
+
if (after !== -1) return row.quote.slice(start, after + numeral.qualifierRaw.length)
|
|
148
|
+
}
|
|
149
|
+
return null
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export function escapeHtml(text) {
|
|
153
|
+
return String(text).replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """)
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/** The elements the renderers below emit as fact spans; recognizers build their patterns from this. */
|
|
157
|
+
export const FACT_ELEMENT_TAGS = Object.freeze(["data", "q", "blockquote"])
|
|
158
|
+
|
|
159
|
+
export function renderValueHtml(row, text) {
|
|
160
|
+
const unit = row.unit === null ? "" : ` data-unit="${escapeHtml(row.unit)}"`
|
|
161
|
+
return `<data class="fact" value="${escapeHtml(row.value)}" data-fact="${row.fact_id}"${unit} data-lineage="${LINEAGE[row.kind]}">${escapeHtml(text)}</data>`
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export function renderQuote(row, selector, text, format) {
|
|
165
|
+
const marker = `[${row.source_id}]`
|
|
166
|
+
if (format === "md") return `"${text}" ${marker}`
|
|
167
|
+
return `<q data-fact="${row.fact_id}" data-quote-sel="${escapeHtml(selector)}">${escapeHtml(text)}</q> ${marker}`
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const RENDERED_MD_QUOTE = /^"([\s\S]*)" (\[S\d+\])$/u
|
|
171
|
+
|
|
172
|
+
/** The quote text and source marker of bytes renderQuote wrote for md, or null for any other layout. */
|
|
173
|
+
export function parseRenderedQuote(body) {
|
|
174
|
+
const match = RENDERED_MD_QUOTE.exec(body)
|
|
175
|
+
return match ? { text: match[1], marker: match[2] } : null
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/** The attributed block that replaces a twice-failed sentence; entity and valid_at stay outside the quote. */
|
|
179
|
+
export function renderBlockquote(row, key, format) {
|
|
180
|
+
const attribution = row.entity === null ? "" : `, on ${row.entity}${row.valid_at === null ? "" : ` (${row.valid_at})`}`
|
|
181
|
+
const footer = `[${row.source_id}]${attribution}`
|
|
182
|
+
if (format === "md") return `> "${row.quote}"\n> ${footer}`
|
|
183
|
+
return `<blockquote data-fact="${row.fact_id}" data-replaced="${escapeHtml(key)}">"${escapeHtml(row.quote)}" <footer>${escapeHtml(footer)}</footer></blockquote>`
|
|
184
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test"
|
|
2
|
+
|
|
3
|
+
import { findPlaceholders, lead, parsePlaceholder, subSpan } from "./placeholder.mjs"
|
|
4
|
+
|
|
5
|
+
const QUOTE = "we estimate completion no earlier than 2027"
|
|
6
|
+
|
|
7
|
+
describe("parsePlaceholder", () => {
|
|
8
|
+
test("value pointers: full, value, unit, q", () => {
|
|
9
|
+
expect(parsePlaceholder("{{F12:span_len}}")).toEqual({ id: "F12", kind: "F", label: "span_len", mode: "full" })
|
|
10
|
+
expect(parsePlaceholder("{{F12:span_len:value}}")).toEqual({ id: "F12", kind: "F", label: "span_len", mode: "value" })
|
|
11
|
+
expect(parsePlaceholder("{{F12:span_len:unit}}")).toEqual({ id: "F12", kind: "F", label: "span_len", mode: "unit" })
|
|
12
|
+
expect(parsePlaceholder("{{F12:span_len:q}}")).toEqual({ id: "F12", kind: "F", label: "span_len", mode: "q" })
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
test("quote pointers: full, lead, from/to with JSON anchors", () => {
|
|
16
|
+
expect(parsePlaceholder("{{Q7:completion}}")).toEqual({ id: "Q7", kind: "Q", label: "completion", mode: "full", selector: "full" })
|
|
17
|
+
expect(parsePlaceholder("{{Q7:completion:lead=18}}")).toEqual({ id: "Q7", kind: "Q", label: "completion", mode: "lead", lead: 18, selector: "lead=18" })
|
|
18
|
+
expect(parsePlaceholder('{{Q7:completion:from="no earlier":to="2027"}}')).toEqual({
|
|
19
|
+
id: "Q7",
|
|
20
|
+
kind: "Q",
|
|
21
|
+
label: "completion",
|
|
22
|
+
mode: "span",
|
|
23
|
+
from: "no earlier",
|
|
24
|
+
to: "2027",
|
|
25
|
+
selector: "from=no earlier;to=2027",
|
|
26
|
+
})
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
test("an anchor may contain a colon or an escaped quote", () => {
|
|
30
|
+
const parsed = parsePlaceholder('{{Q7:c:from="a:b":to="say \\"x\\""}}')
|
|
31
|
+
expect(parsed).toMatchObject({ mode: "span", from: "a:b", to: 'say "x"' })
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
test("malformed pointers carry an error and no id guess", () => {
|
|
35
|
+
for (const token of ["{{F12}}", "{{X1:a}}", "{{F12:a:lead=3}}", "{{Q7:a:value}}", "{{Q7:a:lead=0}}", '{{Q7:a:from="":to="b"}}', '{{Q7:a:from="a"}}', "{{F12:a:b:c}}"]) {
|
|
36
|
+
expect(parsePlaceholder(token)).toHaveProperty("error")
|
|
37
|
+
}
|
|
38
|
+
})
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
describe("findPlaceholders", () => {
|
|
42
|
+
test("returns every token with offsets that slice back to it", () => {
|
|
43
|
+
const text = 'Span {{F12:span_len}} and {{Q7:completion:from="a}}b":to="c"}} end'
|
|
44
|
+
const found = findPlaceholders(text)
|
|
45
|
+
expect(found.map((item) => item.token)).toEqual(["{{F12:span_len}}", '{{Q7:completion:from="a}}b":to="c"}}'])
|
|
46
|
+
for (const item of found) expect(text.slice(item.start, item.end)).toBe(item.token)
|
|
47
|
+
expect(found[1].parsed).toMatchObject({ from: "a}}b", to: "c" })
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
test("an unterminated pointer is reported, not skipped", () => {
|
|
51
|
+
const found = findPlaceholders("a {{F12:span_len\nb")
|
|
52
|
+
expect(found).toHaveLength(1)
|
|
53
|
+
expect(found[0].parsed).toHaveProperty("error")
|
|
54
|
+
})
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
describe("subSpan", () => {
|
|
58
|
+
test("inclusive sub-span with an ellipsis only at cut ends", () => {
|
|
59
|
+
expect(subSpan(QUOTE, "no earlier", "2027")).toEqual({ text: "\u2026no earlier than 2027", start: 23, end: 43 })
|
|
60
|
+
expect(subSpan(QUOTE, "we", "completion")).toEqual({ text: "we estimate completion\u2026", start: 0, end: 22 })
|
|
61
|
+
expect(subSpan(QUOTE, "estimate", "earlier").text).toBe("\u2026estimate completion no earlier\u2026")
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
test("an anchor that is missing, repeated, or reversed is null", () => {
|
|
65
|
+
expect(subSpan(QUOTE, "missing", "2027")).toBeNull()
|
|
66
|
+
expect(subSpan(QUOTE, "ti", "2027")).toBeNull()
|
|
67
|
+
expect(subSpan(QUOTE, "2027", "estimate")).toBeNull()
|
|
68
|
+
})
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
describe("lead", () => {
|
|
72
|
+
test("first n words plus an ellipsis, never cutting a word", () => {
|
|
73
|
+
expect(lead(QUOTE, 3)).toEqual({ text: "we estimate completion\u2026", start: 0, end: 22 })
|
|
74
|
+
expect(lead(QUOTE, 7)).toEqual({ text: QUOTE, start: 0, end: QUOTE.length })
|
|
75
|
+
expect(lead(QUOTE, 40)).toEqual({ text: QUOTE, start: 0, end: QUOTE.length })
|
|
76
|
+
})
|
|
77
|
+
})
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
// polish-apply.mjs - checked, per-edit application of a proofread lane's polish edits.
|
|
2
|
+
//
|
|
3
|
+
// An edit whose before or after holds <, > or & is polish_edit_rejected (POLISH_MARKUP_REASON)
|
|
4
|
+
// before anything else, so no edit can create, alter, close or escape a tag, comment,
|
|
5
|
+
// bogus comment, CDATA section, entity or script, whatever the tokenizer models; a located region
|
|
6
|
+
// that overlaps a character reference or opens one (it starts inside `&name`) is rejected with
|
|
7
|
+
// POLISH_REFERENCE_REASON, so an edit can neither change nor complete an existing reference.
|
|
8
|
+
// Those two guards are the early, actionable refusals; the guarantee itself is the whole-document
|
|
9
|
+
// skeleton comparison (html-markup.skeletonChange) that polish-rules runs in both gates: every
|
|
10
|
+
// markup token and every character reference stays byte-identical, and an accepted edit's span
|
|
11
|
+
// holds no skeleton token, so any difference refuses the edit and reports polish_diff_fail.
|
|
12
|
+
// applyEdits(html, edits, ledger) applies each {before, after, reason} edit in edits-file order,
|
|
13
|
+
// against the output of the edits accepted before it. `before` must occur exactly once (runs of
|
|
14
|
+
// whitespace match any whitespace run) and must not cut through a fact span; otherwise the edit is
|
|
15
|
+
// polish_edit_unlocated. A located edit is applied whole or not at all: it is polish_edit_rejected
|
|
16
|
+
// when polish-rules.polishDefects(..., {ordered: true}) finds any defect between the current
|
|
17
|
+
// document and the document with the edit (per edit), or between the ORIGINAL input and the
|
|
18
|
+
// document with the edit (whole document: the baseline polish-diff uses). Ordered is stricter than
|
|
19
|
+
// polish-diff's multiset comparison and the last accepted candidate is the output, so a clean run
|
|
20
|
+
// is always polish-diff clean: no cross-edit case is left to the backstop. The rules see the
|
|
21
|
+
// text-node projection and freeze every markup byte, including script/style/template content.
|
|
22
|
+
// validatePolishEdits and POLISH_EDIT_FIELDS are the edit
|
|
23
|
+
// contract every caller (the CLI included) uses. No IO, no model. Plain Node ESM.
|
|
24
|
+
|
|
25
|
+
import { CHARACTER_REFERENCE } from "./html-markup.mjs"
|
|
26
|
+
import { factSpans } from "./polish-project.mjs"
|
|
27
|
+
import { polishDefects } from "./polish-rules.mjs"
|
|
28
|
+
|
|
29
|
+
export { numeralKeys, project, SLOT, termCounts, textOf } from "./polish-project.mjs"
|
|
30
|
+
|
|
31
|
+
/** The fields of one polish edit: before and after are required strings, reason an optional string. */
|
|
32
|
+
export const POLISH_EDIT_FIELDS = Object.freeze({ before: "required", after: "required", reason: "optional" })
|
|
33
|
+
|
|
34
|
+
/** Throws a TypeError naming the first edit that breaks POLISH_EDIT_FIELDS; returns the edits. */
|
|
35
|
+
export function validatePolishEdits(edits) {
|
|
36
|
+
if (!Array.isArray(edits)) throw new TypeError("edits must be an array")
|
|
37
|
+
edits.forEach((edit, index) => {
|
|
38
|
+
if (edit === null || typeof edit !== "object" || Array.isArray(edit)) throw new TypeError(`edit ${index} must be an object`)
|
|
39
|
+
for (const [field, need] of Object.entries(POLISH_EDIT_FIELDS)) {
|
|
40
|
+
if (need === "optional" && edit[field] === undefined) continue
|
|
41
|
+
if (typeof edit[field] !== "string") throw new TypeError(`edit ${index} needs ${need === "optional" ? "a string" : "string"} ${field}`)
|
|
42
|
+
}
|
|
43
|
+
})
|
|
44
|
+
return edits
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Characters an edit's before and after may never contain. */
|
|
48
|
+
export const POLISH_MARKUP_CHARACTERS = /[<>&]/
|
|
49
|
+
/** The actionable rejection reason for an edit holding a markup character. */
|
|
50
|
+
export const POLISH_MARKUP_REASON = "edit contains markup characters (<, >, &): route this section through the full fidelity gates"
|
|
51
|
+
/** The actionable rejection reason for a located region touching a character reference. */
|
|
52
|
+
export const POLISH_REFERENCE_REASON = "edit touches a character reference: route this section through the full fidelity gates"
|
|
53
|
+
|
|
54
|
+
/** True when [start, end) overlaps a character reference of html or starts inside one. */
|
|
55
|
+
export function touchesReference(html, start, end) {
|
|
56
|
+
for (const match of String(html).matchAll(CHARACTER_REFERENCE)) {
|
|
57
|
+
if (match.index < end && match.index + match[0].length > start) return true
|
|
58
|
+
}
|
|
59
|
+
return /&[#\w]*$/.test(html.slice(Math.max(0, start - 16), start))
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const escape = (text) => text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")
|
|
63
|
+
|
|
64
|
+
/** The single whitespace-normalized match of `before` in html as {start, end}, or {count} otherwise. */
|
|
65
|
+
function locate(html, before) {
|
|
66
|
+
if (before.trim() === "") return { count: 0 }
|
|
67
|
+
const pattern = new RegExp(before.split(/\s+/).map(escape).join("\\s+"), "g")
|
|
68
|
+
const hits = [...html.matchAll(pattern)]
|
|
69
|
+
return hits.length === 1 ? { count: 1, start: hits[0].index, end: hits[0].index + hits[0][0].length } : { count: hits.length }
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** The id of a fact span the region [start, end) cuts through (overlaps without containing), or null. */
|
|
73
|
+
export function cutSpan(html, start, end) {
|
|
74
|
+
return factSpans(html).find((span) => span.start < end && span.end > start && !(start <= span.start && span.end <= end))?.id ?? null
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* @param {string} html the filled report
|
|
79
|
+
* @param {{ before: string, after: string, reason?: string }[]} edits in edits-file order
|
|
80
|
+
* @param {{ rows: object[] }} ledger
|
|
81
|
+
* @returns {{ out: string, applied: number[], rejected: { index: number, code: string, reason: string }[],
|
|
82
|
+
* unlocated: { index: number, code: string, reason: string }[] }}
|
|
83
|
+
*/
|
|
84
|
+
export function applyEdits(html, edits, ledger) {
|
|
85
|
+
validatePolishEdits(edits)
|
|
86
|
+
const original = String(html)
|
|
87
|
+
const result = { out: original, applied: [], rejected: [], unlocated: [] }
|
|
88
|
+
const defects = (candidate) => {
|
|
89
|
+
const messages = [result.out, original].flatMap((base) => polishDefects(base, candidate, ledger, { ordered: true }).map((d) => d.message))
|
|
90
|
+
return [...new Set(messages)]
|
|
91
|
+
}
|
|
92
|
+
edits.forEach((edit, index) => {
|
|
93
|
+
const touches = POLISH_MARKUP_CHARACTERS.test(edit.before) || POLISH_MARKUP_CHARACTERS.test(edit.after)
|
|
94
|
+
const where = locate(result.out, edit.before)
|
|
95
|
+
if (where.count !== 1 && !touches) {
|
|
96
|
+
result.unlocated.push({ index, code: "polish_edit_unlocated", reason: `before matches ${where.count} times; it must match exactly once` })
|
|
97
|
+
return
|
|
98
|
+
}
|
|
99
|
+
const reasons = touches ? [POLISH_MARKUP_REASON] : []
|
|
100
|
+
const candidate = where.count === 1 ? result.out.slice(0, where.start) + edit.after + result.out.slice(where.end) : result.out
|
|
101
|
+
if (where.count === 1) {
|
|
102
|
+
const cut = cutSpan(result.out, where.start, where.end)
|
|
103
|
+
if (touchesReference(result.out, where.start, where.end)) reasons.push(POLISH_REFERENCE_REASON)
|
|
104
|
+
reasons.push(...defects(candidate))
|
|
105
|
+
if (cut !== null) reasons.push(`the edit cuts through fact span ${cut}`)
|
|
106
|
+
}
|
|
107
|
+
if (reasons.length > 0) {
|
|
108
|
+
result.rejected.push({ index, code: "polish_edit_rejected", reason: reasons.join("; ") })
|
|
109
|
+
return
|
|
110
|
+
}
|
|
111
|
+
result.out = candidate
|
|
112
|
+
result.applied.push(index)
|
|
113
|
+
})
|
|
114
|
+
return result
|
|
115
|
+
}
|