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,187 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test"
|
|
2
|
+
|
|
3
|
+
import { markupParts, skeletonChange } from "./html-markup.mjs"
|
|
4
|
+
import { applyEdits, POLISH_MARKUP_REASON, textOf } from "./polish-apply.mjs"
|
|
5
|
+
import { diffPolish } from "./polish-diff.mjs"
|
|
6
|
+
|
|
7
|
+
const LEDGER = { rows: [{ fact_id: "F1", quote: "Revenue may reach 320 m." }] }
|
|
8
|
+
const FACT = '<data data-fact="F1">320 m</data>'
|
|
9
|
+
const BASE = `<p id="a" class="plain" style="color:red">Revenue may reach ${FACT} this year.</p>`
|
|
10
|
+
|
|
11
|
+
// The public polish boundary must freeze bytes, not inferred HTML/CSS semantics.
|
|
12
|
+
const MARKUP_CASES = [
|
|
13
|
+
["hidden added", BASE, BASE.replace('<p ', '<p hidden ')],
|
|
14
|
+
["id changed under existing CSS", `<style>#gone{display:none}</style>${BASE}`, `<style>#gone{display:none}</style>${BASE.replace('id="a"', 'id="gone"')}`],
|
|
15
|
+
["class changed", BASE, BASE.replace('class="plain"', 'class="gone"')],
|
|
16
|
+
["style changed", BASE, BASE.replace("color:red", "display:none")],
|
|
17
|
+
["stylesheet inserted", BASE, `<style>.plain{display:none}</style>${BASE}`],
|
|
18
|
+
["self-closing slash added", BASE, BASE.replace('color:red">', 'color:red"/>')],
|
|
19
|
+
["wrapper added", BASE, BASE.replace(FACT, `<span>${FACT}</span>`)],
|
|
20
|
+
["wrapper removed", BASE.replace(FACT, `<span>${FACT}</span>`), BASE],
|
|
21
|
+
["comment added", BASE, `${BASE}<!-- note -->`],
|
|
22
|
+
["comment changed", `${BASE}<!-- note -->`, `${BASE}<!-- notes -->`],
|
|
23
|
+
["attribute reorder", BASE, BASE.replace('id="a" class="plain"', 'class="plain" id="a"')],
|
|
24
|
+
["attribute quote changed", BASE, BASE.replace('id="a"', "id='a'")],
|
|
25
|
+
["attribute name case changed", BASE, BASE.replace('id="a"', 'ID="a"')],
|
|
26
|
+
["duplicate attribute changed", BASE.replace('<p ', '<p id="first" '), BASE.replace('<p ', '<p id="other" ')],
|
|
27
|
+
["href changed", '<a href="/a">Reference</a>', '<a href="/b">Reference</a>'],
|
|
28
|
+
["closing tag changed", BASE, BASE.replace("</p>", "</div>")],
|
|
29
|
+
["tag renamed", "<p>Results may improve.</p>", "<div>Results may improve.</div>"],
|
|
30
|
+
["tag whitespace changed", BASE, BASE.replace('<p ', '<p ')],
|
|
31
|
+
["declaration changed", `<!doctype html>${BASE}`, `<!DOCTYPE html>${BASE}`],
|
|
32
|
+
["script content changed", `${BASE}<script>let x = "note"</script>`, `${BASE}<script>let x = "notes"</script>`],
|
|
33
|
+
["style content changed", `${BASE}<style>.a{color:red}</style>`, `${BASE}<style>.a{color:tan}</style>`],
|
|
34
|
+
["template text changed", `${BASE}<template>note</template>`, `${BASE}<template>notes</template>`],
|
|
35
|
+
["nested template text changed", `${BASE}<template><template>x</template>note</template>`, `${BASE}<template><template>x</template>notes</template>`],
|
|
36
|
+
["unclosed script content changed", `${BASE}<script>note`, `${BASE}<script>notes`],
|
|
37
|
+
["unclosed template content changed", `${BASE}<template>note`, `${BASE}<template>notes`],
|
|
38
|
+
] as const
|
|
39
|
+
|
|
40
|
+
describe("polish text-node-only markup contract", () => {
|
|
41
|
+
test.each(MARKUP_CASES)("#given %s #then apply refuses markup changed and diff blocks", (_label: string, before: string, after: string) => {
|
|
42
|
+
const result = applyEdits(before, [{ before, after }], LEDGER)
|
|
43
|
+
expect(result.applied).toEqual([])
|
|
44
|
+
expect(result.out).toBe(before)
|
|
45
|
+
expect(result.rejected[0]?.reason).toContain("markup changed")
|
|
46
|
+
const defects = diffPolish(before, after, LEDGER)
|
|
47
|
+
expect(defects.find((item: { code: string }) => item.code === "polish_diff_fail")?.severity).toBe("blocker")
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
test.each([
|
|
51
|
+
["existing self-closing hidden span", `<p>Revenue ${FACT} may rise. <span hidden/> Fine.</p>`, `<p>Revenue ${FACT} rise. <span hidden/> may Fine.</p>`],
|
|
52
|
+
["existing closed dialog", `<p>Revenue ${FACT} may rise.</p><dialog>Note.</dialog>`, `<p>Revenue ${FACT} rise.</p><dialog>may Note.</dialog>`],
|
|
53
|
+
["existing duplicate hidden style", '<p>Results may improve.</p><div style="display:none" style="color:red">Note.</div>', '<p>Results improve.</p><div style="display:none" style="color:red">may Note.</div>'],
|
|
54
|
+
["aria-hidden fact text", `<p>Revenue <span aria-hidden="true">${FACT}</span> may rise.</p>`, `<p>Revenue <span aria-hidden="true">${FACT.replace("320", "321")}</span> may rise.</p>`],
|
|
55
|
+
["plain text inside aria-hidden fact", '<q data-fact="F1" aria-hidden="true">Revenue may rise.</q>', '<q data-fact="F1" aria-hidden="true">Revenue may soar.</q>'],
|
|
56
|
+
["number moved between text nodes", "<p>Revenue 320 m.</p><span hidden>Note.</span>", "<p>Revenue.</p><span hidden>320 m Note.</span>"],
|
|
57
|
+
])("#given %s #then protected text cannot migrate or change", (_label: string, before: string, after: string) => {
|
|
58
|
+
expect(applyEdits(before, [{ before, after }], LEDGER).applied).toEqual([])
|
|
59
|
+
expect(diffPolish(before, after, LEDGER).length).toBeGreaterThan(0)
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
test.each([
|
|
63
|
+
["plain prose", "<p>Results may improve steadily.</p>", "steadily", "slowly"],
|
|
64
|
+
["prose beside a fact", BASE, "this year", "next year"],
|
|
65
|
+
["quoted greater-than attribute", '<p title="a>b">Results may improve steadily.</p>', "steadily", "slowly"],
|
|
66
|
+
["literal less-than text", "<p>Growth < limits may persist steadily.</p>", "steadily", "slowly"],
|
|
67
|
+
["untouched entity beside the edit", "<p>R&D may grow steadily.</p>", "steadily", "slowly"],
|
|
68
|
+
["unchanged frozen content", "<style>.a{display:none}</style><template>note</template><p>Results may improve steadily.</p>", "steadily", "slowly"],
|
|
69
|
+
])("#given %s #then a text-only edit with protected tokens intact passes", (_label: string, html: string, from: string, to: string) => {
|
|
70
|
+
const after = html.replace(from, to)
|
|
71
|
+
expect(applyEdits(html, [{ before: from, after: to }], LEDGER).out).toBe(after)
|
|
72
|
+
expect(diffPolish(html, after, LEDGER)).toEqual([])
|
|
73
|
+
})
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
// Review #11152 round 4: an edit may never carry <, > or &, so it cannot create, alter, close or
|
|
77
|
+
// escape any tag, comment, bogus comment, CDATA section, entity or script, whatever the tokenizer models.
|
|
78
|
+
const DATA = '<p>Revenue reached <data class="fact" data-fact="F1">320 m</data> in spring.</p>'
|
|
79
|
+
const SCRIPT = "<p>Results may improve steadily.</p><script><!--<script></script>foo();</script>"
|
|
80
|
+
describe("polish edits never carry markup characters", () => {
|
|
81
|
+
test.each([
|
|
82
|
+
["bogus comment before a fact span", DATA, "Revenue reached ", "Revenue reached </ "],
|
|
83
|
+
["bogus comment hiding a sentence", "<p>Fine. The board met in spring.</p>", "The board met in spring.", "</ The board met in spring.>"],
|
|
84
|
+
["empty end tag", "<p>Results may improve steadily.</p>", "steadily", "steadily </>"],
|
|
85
|
+
["introduced entity", "<p>Results may improve steadily.</p>", "steadily", "steadily <b>"],
|
|
86
|
+
["introduced greater-than", "<p>Results may improve steadily.</p>", "steadily", "steadily >"],
|
|
87
|
+
["removed entity", "<p>R&D may grow steadily.</p>", "R&D", "RD"],
|
|
88
|
+
])("#given %s #then apply rejects it and diff blocks", (_label: string, html: string, from: string, to: string) => {
|
|
89
|
+
const result = applyEdits(html, [{ before: from, after: to }], LEDGER)
|
|
90
|
+
expect(result.applied).toEqual([])
|
|
91
|
+
expect(result.out).toBe(html)
|
|
92
|
+
expect(result.rejected[0]?.reason).toContain("edit contains markup characters (<, >, &): route this section through the full fidelity gates")
|
|
93
|
+
if (from === "R&D" || to.includes("<") || to === "steadily >") return
|
|
94
|
+
expect(diffPolish(html, html.replace(from, to), LEDGER).find((item: { code: string }) => item.code === "polish_diff_fail")?.severity).toBe("blocker")
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
test.each([
|
|
98
|
+
["words around R&D", "<p>Our R&D budget grew in 2025.</p>", "budget grew", "budget rose", true],
|
|
99
|
+
["an edit spanning R&D", "<p>Our R&D budget grew in 2025.</p>", "Our R&D budget grew", "The R&D budget rose", false],
|
|
100
|
+
["words around p < 0.05", "<p>The effect held at p < 0.05 in the trial.</p>", "in the trial", "across the trial", true],
|
|
101
|
+
["an edit spanning p < 0.05", "<p>The effect held at p < 0.05 in the trial.</p>", "held at p < 0.05", "stayed at p < 0.05", false],
|
|
102
|
+
])("#given legitimate prose with %s #then only an edit leaving the entity bytes untouched applies", (_label: string, html: string, from: string, to: string, passes: boolean) => {
|
|
103
|
+
const result = applyEdits(html, [{ before: from, after: to }], LEDGER)
|
|
104
|
+
if (passes) {
|
|
105
|
+
expect(result.out).toBe(html.replace(from, to))
|
|
106
|
+
expect(diffPolish(html, result.out, LEDGER)).toEqual([])
|
|
107
|
+
return
|
|
108
|
+
}
|
|
109
|
+
expect(result.out).toBe(html)
|
|
110
|
+
expect(result.rejected[0]?.reason).toContain(POLISH_MARKUP_REASON)
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
test("#given double-escaped script code #then the ambiguity refusal blocks a text-only edit of it", () => {
|
|
114
|
+
const result = applyEdits(SCRIPT, [{ before: "foo();", after: "bar();" }], LEDGER)
|
|
115
|
+
expect(result.applied).toEqual([])
|
|
116
|
+
expect(result.rejected[0]?.reason).toContain("ambiguous markup: <!-- inside script")
|
|
117
|
+
expect(diffPolish(SCRIPT, SCRIPT.replace("foo", "bar"), LEDGER)[0]?.message).toContain("ambiguous markup")
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
test.each([
|
|
121
|
+
["end tag open before a non-letter", "<p>a </ b> c</p>"],
|
|
122
|
+
["empty end tag", "<p>a </> c</p>"],
|
|
123
|
+
["comment opener inside script", "<script><!-- x --></script><p>c</p>"],
|
|
124
|
+
["comment opener inside style", "<style><!-- .a{} --></style><p>c</p>"],
|
|
125
|
+
["CDATA section", "<p>a <![CDATA[ b ]]> c</p>"],
|
|
126
|
+
["processing instruction", "<p>a <?x b?> c</p>"],
|
|
127
|
+
["less-than inside an attribute value", '<p title="a<b">c</p>'],
|
|
128
|
+
["comment closed by --!>", "<p>a <!-- b --!> c</p>"],
|
|
129
|
+
])("#given a document with %s #then diff refuses it as ambiguous markup", (_label: string, html: string) => {
|
|
130
|
+
const defect = diffPolish(html, html, LEDGER).find((item: { code: string }) => item.code === "polish_diff_fail")
|
|
131
|
+
expect(defect?.message).toContain("ambiguous markup")
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
test("#given an end tag open before a non-letter #then the tokenizer skips a bogus comment to the next >", () => {
|
|
135
|
+
expect(markupParts("a </ b> c </> d").markup).toEqual(["</ b>", "</>"])
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
test("#given a double-escaped script #then the script runs to the close outside the escape", () => {
|
|
139
|
+
expect(markupParts(`${SCRIPT}<p>x</p>`).markup).toEqual(["<p>", "</p>", "<script><!--<script></script>foo();</script>", "<p>", "</p>"])
|
|
140
|
+
expect(markupParts("<script><!-- a --></script><b>x</b>").markup).toEqual(["<script><!-- a --></script>", "<b>", "</b>"])
|
|
141
|
+
})
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
// Review #11152 round 5: the primary rule is a whole-document skeleton comparison. Every markup
|
|
145
|
+
// token and every character reference in text must be byte-identical; an edit's located region may
|
|
146
|
+
// not touch a character reference either, so an edit can neither change nor create one.
|
|
147
|
+
const AMPD = "<p>We fund R&D labs here.</p>"
|
|
148
|
+
const TOM = "<p>Tom & Jerry ran.</p>"
|
|
149
|
+
describe("polish keeps character references intact", () => {
|
|
150
|
+
test.each([
|
|
151
|
+
["an edit starting inside an entity", AMPD, "amp;D labs", "lt;D labs"],
|
|
152
|
+
["an edit starting right after a bare ampersand", TOM, " Jerry ran", "lt; ran"],
|
|
153
|
+
])("#given %s #then apply refuses it and diff blocks the hand edit", (_label: string, html: string, from: string, to: string) => {
|
|
154
|
+
const result = applyEdits(html, [{ before: from, after: to }], LEDGER)
|
|
155
|
+
expect(result.applied).toEqual([])
|
|
156
|
+
expect(result.out).toBe(html)
|
|
157
|
+
expect(result.rejected[0]?.reason).toContain("character reference")
|
|
158
|
+
const defects = diffPolish(html, html.replace(from, to), LEDGER)
|
|
159
|
+
expect(defects.find((item: { code: string }) => item.code === "polish_diff_fail")?.severity).toBe("blocker")
|
|
160
|
+
})
|
|
161
|
+
|
|
162
|
+
test("#given a hand edit from R&D to R<D #then diff blocks it", () => {
|
|
163
|
+
const defect = diffPolish(AMPD, AMPD.replace("&", "<"), LEDGER).find((item: { code: string }) => item.code === "polish_diff_fail")
|
|
164
|
+
expect(defect?.message).toContain("skeleton changed")
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
test.each([
|
|
168
|
+
["an entity changed", AMPD, AMPD.replace("&", "<")],
|
|
169
|
+
["an entity created", TOM, "<p>Tom < ran.</p>"],
|
|
170
|
+
["an entity removed", AMPD, AMPD.replace("R&D", "RD")],
|
|
171
|
+
["a tag changed", AMPD, AMPD.replace("<p>", "<div>")],
|
|
172
|
+
])("#given %s #then the skeleton check alone names the first differing token", (_label: string, before: string, after: string) => {
|
|
173
|
+
expect(skeletonChange(before, after)).toContain("skeleton changed at token")
|
|
174
|
+
})
|
|
175
|
+
|
|
176
|
+
test.each([
|
|
177
|
+
["words beside an entity", AMPD, AMPD.replace("labs here", "labs nearby")],
|
|
178
|
+
["words beside a bare ampersand", TOM, TOM.replace("ran", "walked")],
|
|
179
|
+
["an unchanged document", AMPD, AMPD],
|
|
180
|
+
])("#given %s #then the skeleton is unchanged", (_label: string, before: string, after: string) => {
|
|
181
|
+
expect(skeletonChange(before, after)).toBeNull()
|
|
182
|
+
})
|
|
183
|
+
|
|
184
|
+
test("#given a quoted greater-than in an attribute #then the text projection follows the scanner", () => {
|
|
185
|
+
expect(textOf('<p title="a>b">Results may improve.</p>').trim()).toBe("Results may improve.")
|
|
186
|
+
})
|
|
187
|
+
})
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
// polish-project.mjs - the protected projection of report markup that every polish check compares.
|
|
2
|
+
//
|
|
3
|
+
// project(markup) yields the ordered fact spans {id, html}, the visible text, the masked text (each
|
|
4
|
+
// fact span replaced by SLOT) and the ordered href values. Callers pass markup already reduced by
|
|
5
|
+
// html-markup.markupParts().source, with frozen script/style/template content omitted. Fact spans are the
|
|
6
|
+
// placeholder.mjs FACT_ELEMENT_TAGS elements carrying data-fact (any quoting), found by
|
|
7
|
+
// html-markup.elementSpans (a same-name nested element and a quoted ">" stay inside). Hedge and
|
|
8
|
+
// qualifier terms come from fidelity-lexicon (guardedTerms, lexicalPattern "guard" mode).
|
|
9
|
+
// No IO, no model. Plain Node ESM.
|
|
10
|
+
|
|
11
|
+
import { decodeEntities } from "./entities.mjs"
|
|
12
|
+
import { guardedTerms, lexicalPattern, QUALIFIER_FORMS } from "./fidelity-lexicon.mjs"
|
|
13
|
+
import { elementSpans, markupParts, tagAttributes } from "./html-markup.mjs"
|
|
14
|
+
import { HEDGES } from "./lexicon.mjs"
|
|
15
|
+
import { parseNumeral } from "./numerals.mjs"
|
|
16
|
+
import { FACT_ELEMENT_TAGS } from "./placeholder.mjs"
|
|
17
|
+
import { splitSentences } from "./sentence-split.mjs"
|
|
18
|
+
import { normalizeUnit } from "./units.mjs"
|
|
19
|
+
|
|
20
|
+
/** The link attributes polish_link_changed owns; a data-claim id change is a markup (fact-binding) change. */
|
|
21
|
+
export const POLISH_LINK_ATTRIBUTES = Object.freeze(["href"])
|
|
22
|
+
/** The bare digit runs compared outside fact spans when no quantity parses around them. */
|
|
23
|
+
export const POLISH_DIGITS = /\d+(?:[.,]\d+)*/g
|
|
24
|
+
/** Stands in for one fact span in masked text; a private-use char, so no numeral or word matches it. */
|
|
25
|
+
export const SLOT = "\uE000"
|
|
26
|
+
const FACT_TAGS = new Set(FACT_ELEMENT_TAGS)
|
|
27
|
+
const isFactElement = (name, attrs) => FACT_TAGS.has(name) && (attrs["data-fact"] ?? "") !== ""
|
|
28
|
+
|
|
29
|
+
/** Each fact span of the markup as {id, start, end, html}, in order; an unclosed one runs to the end. */
|
|
30
|
+
export function factSpans(markup) {
|
|
31
|
+
const src = String(markup)
|
|
32
|
+
return elementSpans(src, isFactElement).map(({ start, end, tag }) => ({ id: tag.attrs["data-fact"], start, end, html: src.slice(start, end) }))
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Visible text of an HTML fragment: the scanner's text slots joined by a space (so a quoted ">"
|
|
37
|
+
* inside a tag stays markup), entities decoded, whitespace collapsed. One tokenization is shared
|
|
38
|
+
* with the markup rules; no second tag-stripping regex.
|
|
39
|
+
*/
|
|
40
|
+
export function textOf(html) {
|
|
41
|
+
return decodeEntities(markupParts(html).text.join(" ")).replace(/\s+/g, " ")
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** The protected projection of (already visible) markup; see the header. */
|
|
45
|
+
export function project(markup) {
|
|
46
|
+
const facts = []
|
|
47
|
+
let visible = ""
|
|
48
|
+
let masked = ""
|
|
49
|
+
let at = 0
|
|
50
|
+
for (const span of factSpans(markup)) {
|
|
51
|
+
const outside = textOf(markup.slice(at, span.start))
|
|
52
|
+
facts.push({ id: span.id, html: span.html.replace(/\s+/g, " ") })
|
|
53
|
+
visible += `${outside}${textOf(span.html)}`
|
|
54
|
+
masked += `${outside} ${SLOT} `
|
|
55
|
+
at = span.end
|
|
56
|
+
}
|
|
57
|
+
const rest = textOf(markup.slice(at))
|
|
58
|
+
const links = tagAttributes(markup).flatMap(({ attrs }) =>
|
|
59
|
+
POLISH_LINK_ATTRIBUTES.filter((name) => Object.hasOwn(attrs, name)).map((name) => `${name}=${attrs[name]}`),
|
|
60
|
+
)
|
|
61
|
+
return { facts, visible: (visible + rest).replace(/\s+/g, " ").trim(), masked: (masked + rest).replace(/\s+/g, " ").trim(), links }
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const unitKey = (raw) => (raw === null ? "" : (normalizeUnit(raw) ?? raw.toLowerCase()))
|
|
65
|
+
|
|
66
|
+
/** Ordered numeral keys: a parsed quantity as "value unit" (once), any other digit run as written. */
|
|
67
|
+
export function numeralKeys(text) {
|
|
68
|
+
const quantities = []
|
|
69
|
+
for (let found = parseNumeral(text); found !== null; found = parseNumeral(text, found.span[1])) quantities.push(found)
|
|
70
|
+
const keys = []
|
|
71
|
+
let k = 0
|
|
72
|
+
for (const match of text.matchAll(POLISH_DIGITS)) {
|
|
73
|
+
while (k < quantities.length && quantities[k].span[1] <= match.index) k++
|
|
74
|
+
const quantity = quantities[k]
|
|
75
|
+
if (!quantity || quantity.span[0] > match.index) keys.push(match[0])
|
|
76
|
+
else if (!quantity.counted) {
|
|
77
|
+
quantity.counted = true
|
|
78
|
+
keys.push(`${quantity.value}${quantity.range ? `~${quantity.valueEnd}` : ""} ${unitKey(quantity.unitRaw)}`.trim())
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return keys
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** term -> occurrence count of each listed term in the text (guard mode of the lexical engine). */
|
|
85
|
+
export function termCounts(text, terms = guardedTerms()) {
|
|
86
|
+
const counts = new Map()
|
|
87
|
+
for (const term of terms) {
|
|
88
|
+
const n = [...text.matchAll(lexicalPattern(term, "guard"))].length
|
|
89
|
+
if (n > 0) counts.set(term, n)
|
|
90
|
+
}
|
|
91
|
+
return counts
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const add = (into, term, n) => into.set(term, (into.get(term) ?? 0) + n)
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* The hedge and qualifier tokens the text before a polish holds and must keep (term -> count), per
|
|
98
|
+
* sentence of the masked text: a sentence without fact spans keeps all its guarded tokens; a
|
|
99
|
+
* sentence with fact spans keeps those of its tokens that the referenced rows' quote hedges or
|
|
100
|
+
* qualifier forms name. A ledger term the sentence never held is never required.
|
|
101
|
+
*/
|
|
102
|
+
export function requiredTerms(projection, rows) {
|
|
103
|
+
const required = new Map()
|
|
104
|
+
let cursor = 0
|
|
105
|
+
for (const { text } of splitSentences(projection.masked)) {
|
|
106
|
+
const slots = text.split(SLOT).length - 1
|
|
107
|
+
const facts = projection.facts.slice(cursor, cursor + slots)
|
|
108
|
+
cursor += slots
|
|
109
|
+
const present = termCounts(text)
|
|
110
|
+
const sourced = new Set()
|
|
111
|
+
for (const { id } of facts) {
|
|
112
|
+
const row = rows.get(id)
|
|
113
|
+
for (const term of termCounts(row?.quote ?? "", HEDGES).keys()) sourced.add(term)
|
|
114
|
+
for (const form of QUALIFIER_FORMS[row?.qualifier] ?? []) sourced.add(form)
|
|
115
|
+
}
|
|
116
|
+
for (const [term, n] of present) if (slots === 0 || sourced.has(term)) add(required, term, n)
|
|
117
|
+
}
|
|
118
|
+
return required
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/** Required terms the text no longer holds as many times, as "term" names. */
|
|
122
|
+
export function missingTerms(required, text) {
|
|
123
|
+
const have = termCounts(text, [...required.keys()])
|
|
124
|
+
return [...required].filter(([term, n]) => (have.get(term) ?? 0) < n).map(([term]) => term)
|
|
125
|
+
}
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
// polish-rules.mjs - the one rule set behind polish-apply (per edit) and polish-diff (whole document).
|
|
2
|
+
//
|
|
3
|
+
// polishDefects(beforeHtml, afterHtml, ledger, {ordered}) compares the visible projections
|
|
4
|
+
// (html-markup.markupParts, then polish-project.project) and returns {code, message} per failure.
|
|
5
|
+
// Polish may change text nodes only. The primary rule is the whole-document skeleton comparison
|
|
6
|
+
// (html-markup.skeletonChange): every markup token (tags, comments, bogus comments and frozen
|
|
7
|
+
// script/style/template content) and every character reference of a text node must be byte-identical
|
|
8
|
+
// in document order; an edited span holds no skeleton token, so any difference is polish_diff_fail
|
|
9
|
+
// naming the first differing token and its offset. The remaining checks compare the projections:
|
|
10
|
+
// polish_diff_fail: fact spans (id + rendered markup), numerals outside fact spans (with units),
|
|
11
|
+
// tickers (TICKER: 1-5 capitals with an optional ".XX" class, or a 6-digit KRX code), or a
|
|
12
|
+
// table's row count changed; any tag, attribute, comment, declaration, or script/style/template
|
|
13
|
+
// content changed; protected tokens also stay within their original text-node boundaries;
|
|
14
|
+
// polish_hedging_decreased: a hedge or qualifier the before text holds and must keep is gone;
|
|
15
|
+
// polish_text_churn: more than POLISH_CHURN_LIMIT of the normalized visible prose changed;
|
|
16
|
+
// polish_link_changed: href values changed or an internal #target stopped resolving;
|
|
17
|
+
// polish_name_changed: a proper noun (POLISH_NAME) of a ledger quote, present before, is gone.
|
|
18
|
+
// A document holding a construct html-markup.ambiguousMarkup lists (</ + non-letter, <!-- inside
|
|
19
|
+
// script/style, <![CDATA[, <?, --!>, or < inside a tag) is refused first with polish_diff_fail
|
|
20
|
+
// "ambiguous markup": the byte comparison is only trusted where the tokenizer is exact.
|
|
21
|
+
// ordered (polish-apply) also refuses a reordering of fact spans, numerals, tickers and links;
|
|
22
|
+
// without it they compare as multisets. Severity lives only in contracts.DEFECT_CODES.
|
|
23
|
+
// No IO, no model. Plain Node ESM.
|
|
24
|
+
|
|
25
|
+
import { ambiguousMarkup, markupParts, skeletonChange, tagAttributes } from "./html-markup.mjs"
|
|
26
|
+
import { missingTerms, numeralKeys, project, requiredTerms, termCounts, textOf } from "./polish-project.mjs"
|
|
27
|
+
import { splitSentences } from "./sentence-split.mjs"
|
|
28
|
+
import { normalizeText } from "./text-normalize.mjs"
|
|
29
|
+
|
|
30
|
+
/** The codes polishDefects can emit, in report order. */
|
|
31
|
+
export const POLISH_CODES = Object.freeze(["polish_diff_fail", "polish_hedging_decreased", "polish_text_churn", "polish_link_changed", "polish_name_changed"])
|
|
32
|
+
export const TICKER = /(?<![\p{L}\p{N}])(?:[A-Z]{1,5}(?:\.[A-Z]{1,2})?|\d{6})(?![\p{L}\p{N}])/gu
|
|
33
|
+
/** The capitalized-token heuristic for proper nouns in ledger quotes. */
|
|
34
|
+
export const POLISH_NAME = /\p{Lu}[\p{L}\p{N}&'-]+/gu
|
|
35
|
+
/** Above this share of changed normalized prose a polish composed instead of proofreading. */
|
|
36
|
+
export const POLISH_CHURN_LIMIT = 0.5
|
|
37
|
+
/** Above this many cells a gap counts as fully rewritten instead of running the quadratic distance. */
|
|
38
|
+
const DISTANCE_CELLS = 25_000_000
|
|
39
|
+
|
|
40
|
+
const sorted = (list) => [...list].sort()
|
|
41
|
+
const same = (a, b) => a.length === b.length && a.every((item, k) => item === b[k])
|
|
42
|
+
|
|
43
|
+
/** null when unchanged; a reorder counts only when ordered. */
|
|
44
|
+
function listChange(label, before, after, ordered) {
|
|
45
|
+
if (same(before, after)) return null
|
|
46
|
+
const remaining = sorted(before)
|
|
47
|
+
const added = []
|
|
48
|
+
for (const item of after) {
|
|
49
|
+
const at = remaining.indexOf(item)
|
|
50
|
+
if (at === -1) added.push(item)
|
|
51
|
+
else remaining.splice(at, 1)
|
|
52
|
+
}
|
|
53
|
+
if (remaining.length > 0 || added.length > 0) return `${label} changed: removed [${remaining.join(", ")}], added [${added.join(", ")}]`
|
|
54
|
+
return ordered ? `${label} reordered: [${before.join(", ")}] became [${after.join(", ")}]` : null
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const tableRows = (html) => html.split(/<table\b/i).slice(1).map((part) => (part.split(/<\/table\s*>/i)[0].match(/<tr\b/gi) ?? []).length)
|
|
58
|
+
|
|
59
|
+
function levenshtein(a, b) {
|
|
60
|
+
if (a === b) return 0
|
|
61
|
+
if (a.length * b.length > DISTANCE_CELLS) return Math.max(a.length, b.length)
|
|
62
|
+
let previous = Array.from({ length: b.length + 1 }, (_, k) => k)
|
|
63
|
+
for (let i = 1; i <= a.length; i++) {
|
|
64
|
+
const current = [i]
|
|
65
|
+
for (let j = 1; j <= b.length; j++) {
|
|
66
|
+
current[j] = Math.min(previous[j] + 1, current[j - 1] + 1, previous[j - 1] + (a[i - 1] === b[j - 1] ? 0 : 1))
|
|
67
|
+
}
|
|
68
|
+
previous = current
|
|
69
|
+
}
|
|
70
|
+
return previous[b.length]
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** Changed share of normalized prose: edit distance over unmatched sentence runs / longer length. */
|
|
74
|
+
export function churnRatio(beforeText, afterText) {
|
|
75
|
+
const [a, b] = [normalizeText(beforeText), normalizeText(afterText)]
|
|
76
|
+
const longest = Math.max(a.length, b.length)
|
|
77
|
+
if (longest === 0) return 0
|
|
78
|
+
const [sa, sb] = [splitSentences(a).map((s) => s.text), splitSentences(b).map((s) => s.text)]
|
|
79
|
+
const lcs = Array.from({ length: sa.length + 1 }, () => new Array(sb.length + 1).fill(0))
|
|
80
|
+
for (let i = sa.length - 1; i >= 0; i--) {
|
|
81
|
+
for (let j = sb.length - 1; j >= 0; j--) lcs[i][j] = sa[i] === sb[j] ? lcs[i + 1][j + 1] + 1 : Math.max(lcs[i + 1][j], lcs[i][j + 1])
|
|
82
|
+
}
|
|
83
|
+
let distance = 0
|
|
84
|
+
let gap = [[], []]
|
|
85
|
+
const flush = () => {
|
|
86
|
+
distance += levenshtein(gap[0].join(" "), gap[1].join(" "))
|
|
87
|
+
gap = [[], []]
|
|
88
|
+
}
|
|
89
|
+
let [i, j] = [0, 0]
|
|
90
|
+
while (i < sa.length || j < sb.length) {
|
|
91
|
+
if (i < sa.length && j < sb.length && sa[i] === sb[j]) {
|
|
92
|
+
flush()
|
|
93
|
+
i++
|
|
94
|
+
j++
|
|
95
|
+
} else if (j >= sb.length || (i < sa.length && lcs[i + 1][j] >= lcs[i][j + 1])) gap[0].push(sa[i++])
|
|
96
|
+
else gap[1].push(sb[j++])
|
|
97
|
+
}
|
|
98
|
+
flush()
|
|
99
|
+
return distance / longest
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function linkChange(from, to, before, after, ordered) {
|
|
103
|
+
const change = listChange("href and data-claim values", from.links, to.links, ordered)
|
|
104
|
+
if (change) return change
|
|
105
|
+
const ids = (html) => new Set(tagAttributes(html).flatMap(({ attrs }) => (Object.hasOwn(attrs, "id") ? [attrs.id] : [])))
|
|
106
|
+
const [idsBefore, idsAfter] = [ids(before), ids(after)]
|
|
107
|
+
const targets = to.links.filter((link) => link.startsWith("href=#")).map((link) => link.slice(5))
|
|
108
|
+
const broken = targets.filter((href) => idsBefore.has(href.slice(1)) && !idsAfter.has(href.slice(1)))
|
|
109
|
+
return broken.length > 0 ? `internal link targets stopped resolving: [${[...new Set(broken)].join(", ")}]` : null
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** Capitalized tokens of the ledger quotes, minus each sentence's first word. */
|
|
113
|
+
function ledgerNames(rows) {
|
|
114
|
+
const names = new Set()
|
|
115
|
+
for (const row of rows) {
|
|
116
|
+
for (const { text } of splitSentences(row.quote ?? "")) {
|
|
117
|
+
const first = text.match(/^\P{L}*\p{L}+/u)?.[0].length ?? 0
|
|
118
|
+
for (const match of text.matchAll(POLISH_NAME)) if (match.index >= first) names.add(match[0])
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return names
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const hasWord = (text, word) => new RegExp(`(?<![\\p{L}\\p{N}])${word.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(?![\\p{L}\\p{N}])`, "u").test(text)
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* @param {string} beforeHtml
|
|
128
|
+
* @param {string} afterHtml
|
|
129
|
+
* @param {{ rows: object[] }} ledger
|
|
130
|
+
* @param {{ ordered?: boolean }} [options]
|
|
131
|
+
* @returns {{ code: string, message: string }[]} in POLISH_CODES order
|
|
132
|
+
*/
|
|
133
|
+
export function polishDefects(beforeHtml, afterHtml, ledger, { ordered = false } = {}) {
|
|
134
|
+
const ambiguous = [...new Set([...ambiguousMarkup(beforeHtml), ...ambiguousMarkup(afterHtml)])]
|
|
135
|
+
if (ambiguous.length > 0) return [{ code: "polish_diff_fail", message: `ambiguous markup: ${ambiguous.join(", ")}` }]
|
|
136
|
+
const [beforeParts, afterParts] = [markupParts(beforeHtml), markupParts(afterHtml)]
|
|
137
|
+
const [before, after] = [beforeParts.source, afterParts.source]
|
|
138
|
+
const [from, to] = [project(before), project(after)]
|
|
139
|
+
const rows = ledger?.rows ?? []
|
|
140
|
+
const defects = []
|
|
141
|
+
const note = (code, message) => message && defects.push({ code, message })
|
|
142
|
+
const factKey = (fact) => `${fact.id} ${fact.html}`
|
|
143
|
+
const tickers = (text) => text.match(TICKER) ?? []
|
|
144
|
+
const markupEqual = same(beforeParts.markup, afterParts.markup)
|
|
145
|
+
note("polish_diff_fail", markupEqual ? null : "markup changed")
|
|
146
|
+
note("polish_diff_fail", skeletonChange(beforeHtml, afterHtml))
|
|
147
|
+
note("polish_diff_fail", listChange("fact spans", from.facts.map(factKey), to.facts.map(factKey), ordered))
|
|
148
|
+
note("polish_diff_fail", listChange("numerals outside fact spans", numeralKeys(from.masked), numeralKeys(to.masked), ordered))
|
|
149
|
+
note("polish_diff_fail", listChange("tickers", tickers(from.masked), tickers(to.masked), ordered))
|
|
150
|
+
const [rowsBefore, rowsAfter] = [tableRows(before), tableRows(after)]
|
|
151
|
+
if (!same(rowsBefore, rowsAfter)) note("polish_diff_fail", `table row counts changed: [${rowsBefore}] became [${rowsAfter}]`)
|
|
152
|
+
const required = requiredTerms(from, new Map(rows.map((row) => [row.fact_id, row])))
|
|
153
|
+
if (markupEqual) {
|
|
154
|
+
for (const [index, raw] of beforeParts.text.entries()) {
|
|
155
|
+
const [a, b] = [raw, afterParts.text[index]].map(textOf)
|
|
156
|
+
note("polish_diff_fail", listChange("text-node numerals", numeralKeys(a), numeralKeys(b), ordered))
|
|
157
|
+
note("polish_diff_fail", listChange("text-node tickers", tickers(a), tickers(b), ordered))
|
|
158
|
+
const lost = missingTerms(termCounts(a, [...required.keys()]), b)
|
|
159
|
+
if (lost.length) note("polish_hedging_decreased", `text-node hedges removed: ${lost.join(", ")}`)
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
const missing = missingTerms(required, to.visible)
|
|
163
|
+
if (missing.length > 0) note("polish_hedging_decreased", `hedges or qualifiers removed: ${missing.map((term) => `"${term}"`).join(", ")}`)
|
|
164
|
+
const ratio = churnRatio(from.visible, to.visible)
|
|
165
|
+
if (ratio > POLISH_CHURN_LIMIT) note("polish_text_churn", `${(ratio * 100).toFixed(1)} percent of the normalized prose changed`)
|
|
166
|
+
note("polish_link_changed", linkChange(from, to, before, after, ordered))
|
|
167
|
+
const lost = [...ledgerNames(rows)].filter((name) => hasWord(from.visible, name) && !hasWord(to.visible, name))
|
|
168
|
+
if (lost.length > 0) note("polish_name_changed", `ledger-quote names missing after polish: ${lost.join(", ")}`)
|
|
169
|
+
return defects.sort((a, b) => POLISH_CODES.indexOf(a.code) - POLISH_CODES.indexOf(b.code))
|
|
170
|
+
}
|
|
@@ -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
|
+
}
|