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,221 @@
|
|
|
1
|
+
// fact-ledger-schema.mjs - the fact ledger's vocabulary and validation: row fields and defaults,
|
|
2
|
+
// kinds, statuses, qualifiers, and validateLedger over rows, status events, and their history.
|
|
3
|
+
// The grammar it encodes is references/fact-ledger-grammar.md; every per-kind column rule lives in
|
|
4
|
+
// KIND_RULES (fact-ledger-kinds.mjs), never here.
|
|
5
|
+
// Pure functions with no IO; fact-ledger.mjs owns reading, writing, and row changes. node: builtins only.
|
|
6
|
+
|
|
7
|
+
import { DECIMAL, isCount, isCurrentDerived, isNonEmpty, isObject, isString, kindRuleErrors, LATER_COLUMNS, readRow, storedErrors } from "./fact-ledger-kinds.mjs"
|
|
8
|
+
import { malformed } from "./unit-rules.mjs"
|
|
9
|
+
|
|
10
|
+
export { DECIMAL, isNonEmpty, isString } from "./fact-ledger-kinds.mjs"
|
|
11
|
+
|
|
12
|
+
export const LEDGER_SCHEMA_VERSION = 1
|
|
13
|
+
export const FACT_KINDS = Object.freeze(["measured", "quote", "derived", "assumed", "converted"])
|
|
14
|
+
export const FACT_STATUSES = Object.freeze(["active", "superseded", "retracted"])
|
|
15
|
+
export const OPERATIONS = Object.freeze(["sum", "diff", "growth", "ratio", "share", "convert", "expr"])
|
|
16
|
+
/** Canonical qualifier terms; surface forms in either language map onto these. */
|
|
17
|
+
export const QUALIFIERS = Object.freeze([
|
|
18
|
+
"upper_inclusive",
|
|
19
|
+
"lower_inclusive",
|
|
20
|
+
"approximate",
|
|
21
|
+
"near",
|
|
22
|
+
"lower_exclusive",
|
|
23
|
+
"upper_exclusive",
|
|
24
|
+
])
|
|
25
|
+
|
|
26
|
+
export const ROW_DEFAULTS = Object.freeze({
|
|
27
|
+
value: null,
|
|
28
|
+
unit: null,
|
|
29
|
+
value_span: null,
|
|
30
|
+
source_id: null,
|
|
31
|
+
locator: null,
|
|
32
|
+
locate_hint: null,
|
|
33
|
+
snapshot_sha256: null,
|
|
34
|
+
valid_at: null,
|
|
35
|
+
qualifier: null,
|
|
36
|
+
entity: null,
|
|
37
|
+
sentence_id: null,
|
|
38
|
+
observation_id: null,
|
|
39
|
+
fuzzy: false,
|
|
40
|
+
outside_floor: false,
|
|
41
|
+
population: null,
|
|
42
|
+
missing_rule: null,
|
|
43
|
+
accounting_basis: null,
|
|
44
|
+
snapshot_window: null,
|
|
45
|
+
revision: null,
|
|
46
|
+
exact: null,
|
|
47
|
+
render: null,
|
|
48
|
+
operation: null,
|
|
49
|
+
operands: [],
|
|
50
|
+
derived_from: [],
|
|
51
|
+
formula: null,
|
|
52
|
+
factor: null,
|
|
53
|
+
rationale: null,
|
|
54
|
+
context: null,
|
|
55
|
+
precision: null,
|
|
56
|
+
compatibility: null,
|
|
57
|
+
status: "active",
|
|
58
|
+
})
|
|
59
|
+
/** Fields added after schema version 1 shipped: written on every new row, optional when reading an older ledger. */
|
|
60
|
+
const OPTIONAL_FIELDS = new Set(LATER_COLUMNS)
|
|
61
|
+
const REQUIRED = ["fact_id", "kind", "raw", "quote", "extracted_by", "observed_at", "label"]
|
|
62
|
+
export const ROW_FIELDS = Object.freeze([...REQUIRED, ...Object.keys(ROW_DEFAULTS)])
|
|
63
|
+
|
|
64
|
+
const ISO_DATE = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$/
|
|
65
|
+
const LABEL = /^[a-z0-9_-]{1,32}$/
|
|
66
|
+
const FACT_REF = /^[FQ]\d+$/
|
|
67
|
+
|
|
68
|
+
function check(errors, condition, message) {
|
|
69
|
+
if (!condition) errors.push(message)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function nullOr(value, test) {
|
|
73
|
+
return value === null || test(value)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function validateRow(row, index, errors) {
|
|
77
|
+
const at = `rows[${index}]`
|
|
78
|
+
if (!isObject(row)) return errors.push(`${at} must be an object`)
|
|
79
|
+
for (const key of ROW_FIELDS) check(errors, OPTIONAL_FIELDS.has(key) || Object.hasOwn(row, key), `${at}.${key} is required`)
|
|
80
|
+
for (const key of Object.keys(row)) check(errors, ROW_FIELDS.includes(key), `${at}.${key} is not a ledger field`)
|
|
81
|
+
check(errors, FACT_KINDS.includes(row.kind), `${at}.kind must be one of ${FACT_KINDS.join(", ")}`)
|
|
82
|
+
// Dimension 31 wins at read time: per-kind rules see the row as readRow normalizes it.
|
|
83
|
+
errors.push(...kindRuleErrors(readRow(row), at))
|
|
84
|
+
errors.push(...storedErrors(row).map((e) => `${at}.${e}`))
|
|
85
|
+
if (isCurrentDerived(row)) {
|
|
86
|
+
const problem = malformed(row)
|
|
87
|
+
if (problem) errors.push(`${at}: ${problem}`)
|
|
88
|
+
}
|
|
89
|
+
check(errors, row.unit === undefined || nullOr(row.unit, isNonEmpty), `${at}.unit must be null or a non-empty token`)
|
|
90
|
+
for (const key of ["raw", "quote"]) check(errors, row[key] === undefined || isString(row[key]), `${at}.${key} must be a string`)
|
|
91
|
+
check(errors, row.extracted_by === undefined || isNonEmpty(row.extracted_by), `${at}.extracted_by must be a non-empty string`)
|
|
92
|
+
check(errors, row.observed_at === undefined || (isString(row.observed_at) && ISO_DATE.test(row.observed_at)), `${at}.observed_at must be an ISO timestamp`)
|
|
93
|
+
check(errors, row.label === undefined || (isString(row.label) && LABEL.test(row.label)), `${at}.label must match [a-z0-9_-]{1,32}`)
|
|
94
|
+
const span = row.value_span
|
|
95
|
+
check(errors, span === undefined || nullOr(span, (v) => Array.isArray(v) && v.length === 2 && v.every(isCount) && v[0] <= v[1]), `${at}.value_span must be null or [start, end]`)
|
|
96
|
+
for (const key of ["locate_hint", "valid_at", "entity", "observation_id", "missing_rule", "accounting_basis", "formula", "render"]) {
|
|
97
|
+
if (row[key] !== undefined) check(errors, nullOr(row[key], isString), `${at}.${key} must be null or a string`)
|
|
98
|
+
}
|
|
99
|
+
for (const key of ["population", "snapshot_window", "revision"]) {
|
|
100
|
+
if (row[key] !== undefined) check(errors, nullOr(row[key], (v) => isString(v) || isObject(v)), `${at}.${key} must be null, a string, or an object`)
|
|
101
|
+
}
|
|
102
|
+
if (row.qualifier !== undefined) check(errors, nullOr(row.qualifier, (v) => QUALIFIERS.includes(v)), `${at}.qualifier must be null or one of ${QUALIFIERS.join(", ")}`)
|
|
103
|
+
if (row.sentence_id !== undefined) check(errors, nullOr(row.sentence_id, (v) => isString(v) && /^S\d+:s\d+$/.test(v)), `${at}.sentence_id must be null or S<n>:s<k>`)
|
|
104
|
+
for (const key of ["fuzzy", "outside_floor"]) {
|
|
105
|
+
if (row[key] !== undefined) check(errors, typeof row[key] === "boolean", `${at}.${key} must be a boolean`)
|
|
106
|
+
}
|
|
107
|
+
if (row.operation !== undefined) check(errors, nullOr(row.operation, (v) => OPERATIONS.includes(v)), `${at}.operation must be null or one of ${OPERATIONS.join(", ")}`)
|
|
108
|
+
for (const key of ["operands", "derived_from"]) {
|
|
109
|
+
if (row[key] !== undefined) check(errors, Array.isArray(row[key]) && row[key].every((v) => isString(v) && FACT_REF.test(v)), `${at}.${key} must be an array of fact ids`)
|
|
110
|
+
}
|
|
111
|
+
if (row.factor !== undefined) check(errors, nullOr(row.factor, (v) => isString(v) && DECIMAL.test(v)), `${at}.factor must be null or a decimal string`)
|
|
112
|
+
check(errors, row.status === undefined || FACT_STATUSES.includes(row.status), `${at}.status must be one of ${FACT_STATUSES.join(", ")}`)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export function rowErrors(row) {
|
|
116
|
+
const errors = []
|
|
117
|
+
validateRow(row, 0, errors)
|
|
118
|
+
return errors
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// The status machine and event rule of references/fact-ledger-grammar.md. validateLedger replays
|
|
122
|
+
// events through transitionError, and every writer in fact-ledger.mjs calls it before appending.
|
|
123
|
+
export const TRANSITIONS = Object.freeze({ active: Object.freeze(["retracted", "superseded"]), retracted: Object.freeze([]), superseded: Object.freeze([]) })
|
|
124
|
+
const EVENT_FIELDS = Object.freeze(["fact_id", "from", "to", "replacement", "reason"])
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* @param {Map<string, string>} statuses each fact's status at the event's position
|
|
128
|
+
* @returns {string | null} why the event is not a legal transition, or null
|
|
129
|
+
*/
|
|
130
|
+
export function transitionError(statuses, event) {
|
|
131
|
+
if (!isObject(event)) return "an event must be an object"
|
|
132
|
+
const id = event.fact_id
|
|
133
|
+
if (!statuses.has(id)) return `no ledger row ${id}`
|
|
134
|
+
if (EVENT_FIELDS.some((key) => !Object.hasOwn(event, key)) || Object.keys(event).some((key) => !EVENT_FIELDS.includes(key))) {
|
|
135
|
+
return `${id}: an event has exactly the fields ${EVENT_FIELDS.join(", ")}`
|
|
136
|
+
}
|
|
137
|
+
const current = statuses.get(id)
|
|
138
|
+
if (event.from !== current) return `${id} is ${current}, but the event moves it from ${event.from}`
|
|
139
|
+
if (!TRANSITIONS[current].includes(event.to)) return `${id} cannot move from ${current} to ${event.to}`
|
|
140
|
+
if (event.to === "superseded") {
|
|
141
|
+
if (event.replacement === null) return `supersede ${id} needs a replacement row; use retract for a fact with no replacement`
|
|
142
|
+
if (event.replacement === id) return `${id} cannot be its own replacement`
|
|
143
|
+
if (!statuses.has(event.replacement)) return `no replacement row ${event.replacement} for ${id}`
|
|
144
|
+
if (statuses.get(event.replacement) !== "active") return `replacement row ${event.replacement} is ${statuses.get(event.replacement)} (superseding ${id})`
|
|
145
|
+
} else if (event.replacement !== null) return `${id} is ${event.to} but names replacement ${event.replacement}`
|
|
146
|
+
if (!isNonEmpty(event.reason)) return `a reason is required to mark ${id} ${event.to}`
|
|
147
|
+
return null
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/** Replay every event from all-active; each row's status must be where its event chain leaves it. */
|
|
151
|
+
function validateHistory(ledger, errors) {
|
|
152
|
+
const statuses = new Map(ledger.rows.filter(isObject).map((row) => [row.fact_id, "active"]))
|
|
153
|
+
const counts = new Map()
|
|
154
|
+
ledger.events.forEach((event, index) => {
|
|
155
|
+
const problem = transitionError(statuses, event)
|
|
156
|
+
if (problem) errors.push(`events[${index}]: ${problem}`)
|
|
157
|
+
if (!isObject(event) || !statuses.has(event.fact_id) || !FACT_STATUSES.includes(event.to)) return
|
|
158
|
+
statuses.set(event.fact_id, event.to)
|
|
159
|
+
counts.set(event.fact_id, (counts.get(event.fact_id) ?? 0) + 1)
|
|
160
|
+
})
|
|
161
|
+
ledger.rows.forEach((row, index) => {
|
|
162
|
+
if (!isObject(row) || !FACT_STATUSES.includes(row.status)) return
|
|
163
|
+
const to = statuses.get(row.fact_id)
|
|
164
|
+
if (!counts.has(row.fact_id)) check(errors, row.status === "active", `rows[${index}]: ${row.fact_id} is ${row.status} but has no status event`)
|
|
165
|
+
else check(errors, to === row.status, `rows[${index}]: ${row.fact_id} has status ${row.status} but its last event moves it to ${to}`)
|
|
166
|
+
})
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/** operands and derived_from name existing rows, never the row itself, and close no cycle. */
|
|
170
|
+
export function provenanceErrors(rows) {
|
|
171
|
+
const errors = []
|
|
172
|
+
const refs = new Map()
|
|
173
|
+
for (const row of rows) if (isObject(row)) refs.set(row.fact_id, [row.operands, row.derived_from].flatMap((list) => (Array.isArray(list) ? list : [])))
|
|
174
|
+
rows.forEach((row, index) => {
|
|
175
|
+
for (const key of ["operands", "derived_from"]) {
|
|
176
|
+
if (!isObject(row) || !Array.isArray(row[key])) continue
|
|
177
|
+
for (const ref of row[key]) {
|
|
178
|
+
if (ref === row.fact_id) errors.push(`rows[${index}].${key}: ${ref} references itself`)
|
|
179
|
+
else check(errors, refs.has(ref), `rows[${index}].${key}: ${row.fact_id} references ${ref}, which has no ledger row`)
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
})
|
|
183
|
+
const state = new Map()
|
|
184
|
+
for (const root of refs.keys()) {
|
|
185
|
+
const stack = [{ id: root, finish: false }]
|
|
186
|
+
const path = []
|
|
187
|
+
while (stack.length) {
|
|
188
|
+
const { id, finish } = stack.pop()
|
|
189
|
+
if (state.get(id) === "done") continue
|
|
190
|
+
if (finish) { state.set(id, "done"); path.pop(); continue }
|
|
191
|
+
if (state.get(id) === "open") return [...errors, `${id} derives from itself through cycle ${[...path, id].join(" -> ")}`]
|
|
192
|
+
state.set(id, "open")
|
|
193
|
+
path.push(id)
|
|
194
|
+
stack.push({ id, finish: true })
|
|
195
|
+
for (const next of refs.get(id)) if (next !== id && refs.has(next)) stack.push({ id: next, finish: false })
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
return errors
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/** @returns {string[]} every schema error; empty when the ledger is valid. */
|
|
202
|
+
export function validateLedger(ledger) {
|
|
203
|
+
const errors = []
|
|
204
|
+
if (!isObject(ledger)) return ["ledger must be an object"]
|
|
205
|
+
check(errors, ledger.schemaVersion === LEDGER_SCHEMA_VERSION, `schemaVersion must be ${LEDGER_SCHEMA_VERSION}`)
|
|
206
|
+
if (!Array.isArray(ledger.rows)) return [...errors, "rows must be an array"]
|
|
207
|
+
ledger.rows.forEach((row, index) => validateRow(row, index, errors))
|
|
208
|
+
const ids = new Set()
|
|
209
|
+
const labels = new Set()
|
|
210
|
+
ledger.rows.forEach((row, index) => {
|
|
211
|
+
if (!isObject(row)) return
|
|
212
|
+
check(errors, !ids.has(row.fact_id), `rows[${index}].fact_id ${row.fact_id} is a duplicate`)
|
|
213
|
+
check(errors, row.label === undefined || !labels.has(row.label), `rows[${index}]: ${row.fact_id} has duplicate label ${row.label}`)
|
|
214
|
+
ids.add(row.fact_id)
|
|
215
|
+
labels.add(row.label)
|
|
216
|
+
})
|
|
217
|
+
errors.push(...provenanceErrors(ledger.rows))
|
|
218
|
+
if (Array.isArray(ledger.events)) validateHistory(ledger, errors)
|
|
219
|
+
else errors.push("events must be an array")
|
|
220
|
+
return errors
|
|
221
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
// fact-ledger.mjs - the verbatim fact ledger: one row per measured, quoted, derived, assumed,
|
|
2
|
+
// or converted value, with its source locator and exact rational. Rows are never deleted:
|
|
3
|
+
// retract and supersede change status and append an event. Validation and the field vocabulary
|
|
4
|
+
// live in fact-ledger-schema.mjs and are re-exported here. node: builtins only.
|
|
5
|
+
|
|
6
|
+
import { existsSync } from "node:fs"
|
|
7
|
+
import { join } from "node:path"
|
|
8
|
+
|
|
9
|
+
import { CliError, readJsonFile, writeJsonAtomic } from "./cli-support.mjs"
|
|
10
|
+
import { DECIMAL, exactComesFromValue, exactFromDecimal, idPrefix, isString, readFlaws, readRow } from "./fact-ledger-kinds.mjs"
|
|
11
|
+
import { LEDGER_SCHEMA_VERSION, ROW_DEFAULTS, ROW_FIELDS, transitionError, validateLedger } from "./fact-ledger-schema.mjs"
|
|
12
|
+
import { normalizeInput } from "./unit-algebra.mjs"
|
|
13
|
+
|
|
14
|
+
export { exactFromDecimal } from "./fact-ledger-kinds.mjs"
|
|
15
|
+
export { FACT_KINDS, FACT_STATUSES, LEDGER_SCHEMA_VERSION, OPERATIONS, QUALIFIERS, ROW_FIELDS, TRANSITIONS, transitionError, validateLedger } from "./fact-ledger-schema.mjs"
|
|
16
|
+
|
|
17
|
+
export const LEDGER_FILENAME = "fact-ledger.json"
|
|
18
|
+
|
|
19
|
+
export function emptyLedger() {
|
|
20
|
+
return { schemaVersion: LEDGER_SCHEMA_VERSION, rows: [], events: [] }
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function assertValid(ledger) {
|
|
24
|
+
const errors = validateLedger(ledger)
|
|
25
|
+
if (errors.length > 0) throw new CliError(`invalid fact ledger: ${errors.join("; ")}`)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** The row's fields in ROW_FIELDS order, then any unknown field (which validation reports). */
|
|
29
|
+
function ordered(row) {
|
|
30
|
+
const out = Object.fromEntries(ROW_FIELDS.filter((key) => Object.hasOwn(row, key)).map((key) => [key, row[key]]))
|
|
31
|
+
for (const key of Object.keys(row)) if (!ROW_FIELDS.includes(key)) out[key] = row[key]
|
|
32
|
+
return out
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Every row comes back as readRow normalizes it (reduced exacts and decimals; source rows with canonical
|
|
37
|
+
* units and null later columns) before any strict rule runs; the file changes on the next write.
|
|
38
|
+
*/
|
|
39
|
+
export function readLedger(dir) {
|
|
40
|
+
const file = join(dir, LEDGER_FILENAME)
|
|
41
|
+
if (!existsSync(file)) return emptyLedger()
|
|
42
|
+
const ledger = readJsonFile(file)
|
|
43
|
+
assertValid(ledger)
|
|
44
|
+
ledger.rows = ledger.rows.map((row) => ordered(readRow(row)))
|
|
45
|
+
assertValid(ledger)
|
|
46
|
+
return ledger
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function writeLedger(dir, ledger) {
|
|
50
|
+
assertValid(ledger)
|
|
51
|
+
const file = join(dir, LEDGER_FILENAME)
|
|
52
|
+
writeJsonAtomic(file, ledger)
|
|
53
|
+
return file
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const ID_NUMBER = Object.freeze({ F: /^F(\d+)$/, Q: /^Q(\d+)$/ })
|
|
57
|
+
|
|
58
|
+
/** The next id with the kind's prefix from KIND_RULES (F<n> for value rows, Q<n> for quotes). */
|
|
59
|
+
export function nextId(ledger, kind) {
|
|
60
|
+
const prefix = idPrefix(kind)
|
|
61
|
+
let max = 0n
|
|
62
|
+
for (const row of ledger.rows) {
|
|
63
|
+
const match = ID_NUMBER[prefix].exec(row.fact_id ?? "")
|
|
64
|
+
if (match && BigInt(match[1]) > max) max = BigInt(match[1])
|
|
65
|
+
}
|
|
66
|
+
return `${prefix}${max + 1n}`
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Fill defaults, assign the next id, validate the whole ledger (provenance included), then append.
|
|
71
|
+
* Only kinds whose KIND_RULES exact is from_value (measured) get exact from their decimal:
|
|
72
|
+
* derived and converted rows carry the exact that derive computed from the operands and factor,
|
|
73
|
+
* never one read back from the rounded value.
|
|
74
|
+
*/
|
|
75
|
+
export function addRow(ledger, row) {
|
|
76
|
+
const filled = { ...structuredClone(ROW_DEFAULTS), ...row, fact_id: row.fact_id ?? nextId(ledger, row.kind) }
|
|
77
|
+
if (!["derived", "converted"].includes(filled.kind)) {
|
|
78
|
+
const normalized = normalizeInput(filled.unit, filled.value)
|
|
79
|
+
if (normalized.value !== filled.value && filled.exact !== null) throw new CliError("scaled input must omit exact; it is computed after normalization")
|
|
80
|
+
Object.assign(filled, readRow({ ...filled, ...normalized }))
|
|
81
|
+
}
|
|
82
|
+
if (exactComesFromValue(filled.kind) && filled.exact === null && isString(filled.value) && DECIMAL.test(filled.value)) {
|
|
83
|
+
filled.exact = exactFromDecimal(filled.value)
|
|
84
|
+
}
|
|
85
|
+
const added = ordered(filled)
|
|
86
|
+
const flaws = readFlaws(added)
|
|
87
|
+
if (flaws.length > 0) throw new CliError(`invalid fact ledger: ${flaws.map((flaw) => `${flaw.shape}: ${flaw.detail}`).join("; ")}`)
|
|
88
|
+
assertValid({ ...ledger, rows: [...ledger.rows, added] })
|
|
89
|
+
ledger.rows.push(added)
|
|
90
|
+
return added
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** Build the event from the row's current status and append it only if transitionError allows it. */
|
|
94
|
+
function changeStatus(ledger, id, to, replacement, reason) {
|
|
95
|
+
const row = ledger.rows.find((candidate) => candidate.fact_id === id)
|
|
96
|
+
const event = { fact_id: id, from: row?.status ?? null, to, replacement: replacement ?? null, reason }
|
|
97
|
+
const problem = transitionError(new Map(ledger.rows.map((candidate) => [candidate.fact_id, candidate.status])), event)
|
|
98
|
+
if (problem) throw new CliError(problem)
|
|
99
|
+
ledger.events.push(event)
|
|
100
|
+
row.status = to
|
|
101
|
+
return row
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export function retractRow(ledger, id, reason) {
|
|
105
|
+
return changeStatus(ledger, id, "retracted", null, reason)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export function supersedeRow(ledger, id, replacementId, reason) {
|
|
109
|
+
return changeStatus(ledger, id, "superseded", replacementId, reason)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function cell(value) {
|
|
113
|
+
return value === null || value === undefined ? "" : String(value).replace(/\|/g, "\\|").replace(/\n/g, " ")
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** The `--view` table: one line per row, retracted and superseded rows included. */
|
|
117
|
+
export function renderLedgerMarkdown(ledger) {
|
|
118
|
+
const lines = ["| fact_id | kind | label | value | unit | source | status | raw |", "|---|---|---|---|---|---|---|---|"]
|
|
119
|
+
for (const row of ledger.rows) {
|
|
120
|
+
lines.push(`| ${[row.fact_id, row.kind, row.label, row.value, row.unit, row.source_id, row.status, row.raw].map(cell).join(" | ")} |`)
|
|
121
|
+
}
|
|
122
|
+
return `${lines.join("\n")}\n`
|
|
123
|
+
}
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
// fact-locate.mjs - deterministic fact locators over a source snapshot.
|
|
2
|
+
//
|
|
3
|
+
// Each locator returns { value, unit, raw, quote, value_span, sentence_id, qualifier, locator } or
|
|
4
|
+
// { error: "fact_not_located" | "fact_ambiguous", candidates }; it never guesses. `quote` is the
|
|
5
|
+
// whole containing sentence, cell, or line of text.txt, `value_span` is [start, end] inside
|
|
6
|
+
// `quote` with quote.slice(...value_span) === raw, and locator.char_start/char_end place `quote`
|
|
7
|
+
// in text.txt. Every call takes `sourceId` (S<n>) so sentence ids match sentences.jsonl.
|
|
8
|
+
// Plain Node ESM, sibling imports only; no network, no OCR.
|
|
9
|
+
|
|
10
|
+
import { proseText } from "./html-lite.mjs"
|
|
11
|
+
import { QUALIFIERS } from "./lexicon.mjs"
|
|
12
|
+
import { parseNumeral } from "./numerals.mjs"
|
|
13
|
+
import { indexSentences, snapshotBlocks } from "./source-snapshot.mjs"
|
|
14
|
+
import { findNormalized, normalizeText, tokenize } from "./text-normalize.mjs"
|
|
15
|
+
import { similarity } from "./text-similarity.mjs"
|
|
16
|
+
import { normalizeUnit } from "./units.mjs"
|
|
17
|
+
|
|
18
|
+
const FIND_WINDOW = 80
|
|
19
|
+
const CONTEXT = 60
|
|
20
|
+
const FUZZY_FLOOR = 0.95
|
|
21
|
+
const PHRASES = QUALIFIERS.map((phrase) => phrase.split(" "))
|
|
22
|
+
const SINGLE_QUALIFIERS = new Set(QUALIFIERS.filter((phrase) => !phrase.includes(" ")))
|
|
23
|
+
|
|
24
|
+
const notLocated = (candidates = []) => ({ error: "fact_not_located", candidates })
|
|
25
|
+
const ambiguous = (candidates) => ({ error: "fact_ambiguous", candidates })
|
|
26
|
+
const label = (text) => normalizeText(text).toLowerCase()
|
|
27
|
+
const clean = (token) => token.replace(/^[^\p{L}\p{N}%]+|[^\p{L}\p{N}%]+$/gu, "").toLowerCase()
|
|
28
|
+
const sentenceAt = (sentences, offset) => sentences.find((row) => row.start <= offset && offset < row.end) ?? null
|
|
29
|
+
|
|
30
|
+
function requireSource(sourceId) {
|
|
31
|
+
if (!/^S[1-9]\d*$/.test(String(sourceId))) throw new TypeError(`sourceId must be S<n>, got ${sourceId}`)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Every normalized occurrence of `needle`, as raw [start, end) offsets. */
|
|
35
|
+
function findAll(text, needle) {
|
|
36
|
+
const hits = []
|
|
37
|
+
for (let from = 0; from < text.length; ) {
|
|
38
|
+
const hit = findNormalized(text.slice(from), needle)
|
|
39
|
+
if (!hit) break
|
|
40
|
+
hits.push({ start: from + hit.start, end: from + hit.end })
|
|
41
|
+
from += hit.start + 1
|
|
42
|
+
}
|
|
43
|
+
return hits
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function locatorFor(text, sourceId, start, end, extra = {}) {
|
|
47
|
+
const before = text.slice(0, start)
|
|
48
|
+
const pageStart = before.lastIndexOf("\f") + 1
|
|
49
|
+
const page = text.includes("\f") ? { page: before.split("\f").length } : {}
|
|
50
|
+
return { artifact: `${sourceId}/text.txt`, ...page, line: before.slice(pageStart).split("\n").length, char_start: start, char_end: end, ...extra }
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function candidate(text, sentences, start, end) {
|
|
54
|
+
return { sentence_id: sentenceAt(sentences, start)?.sid ?? null, char_start: start, char_end: end, context: text.slice(Math.max(0, start - CONTEXT), end + CONTEXT) }
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** A QUALIFIERS token ending within the 2 tokens before the value, or the 1 token right after it. */
|
|
58
|
+
function qualifierAround(quote, [start, end]) {
|
|
59
|
+
const before = quote.slice(0, start).split(/\s+/).filter(Boolean).map(clean)
|
|
60
|
+
for (let skip = 0; skip < 2; skip++) {
|
|
61
|
+
for (const words of PHRASES) {
|
|
62
|
+
const stop = before.length - skip
|
|
63
|
+
if (stop - words.length >= 0 && before.slice(stop - words.length, stop).join(" ") === words.join(" ")) return words.join(" ")
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
const next = /^\s*(\S+)/.exec(quote.slice(end))
|
|
67
|
+
return next && SINGLE_QUALIFIERS.has(clean(next[1])) ? clean(next[1]) : null
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** A located quantity: `holder` is the quote's { sid, start, end, text }, `numeral` is parsed from holder.text. */
|
|
71
|
+
function quantityResult(text, sourceId, holder, numeral, extra = {}, unitFallback = null) {
|
|
72
|
+
const unit = numeral.unitRaw ? (normalizeUnit(numeral.unitRaw) ?? numeral.unitRaw) : unitFallback
|
|
73
|
+
return {
|
|
74
|
+
value: numeral.value,
|
|
75
|
+
unit,
|
|
76
|
+
raw: numeral.raw,
|
|
77
|
+
quote: holder.text,
|
|
78
|
+
value_span: [numeral.span[0], numeral.span[1]],
|
|
79
|
+
sentence_id: holder.sid,
|
|
80
|
+
qualifier: qualifierAround(holder.text, numeral.span),
|
|
81
|
+
locator: locatorFor(text, sourceId, holder.start, holder.end, extra),
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** --find: one anchor occurrence, then the first numeral starting within 80 chars after it in the same sentence. */
|
|
86
|
+
export function locateFind(text, { find, unitExpected, sourceId }) {
|
|
87
|
+
requireSource(sourceId)
|
|
88
|
+
const sentences = indexSentences(text, sourceId)
|
|
89
|
+
const hits = findAll(text, find)
|
|
90
|
+
if (hits.length === 0) return notLocated()
|
|
91
|
+
if (hits.length > 1) return ambiguous(hits.map((hit) => candidate(text, sentences, hit.start, hit.end)))
|
|
92
|
+
const [hit] = hits
|
|
93
|
+
const sentence = sentenceAt(sentences, hit.start)
|
|
94
|
+
if (!sentence || hit.end > sentence.end) return notLocated()
|
|
95
|
+
const from = hit.end - sentence.start
|
|
96
|
+
const numeral = parseNumeral(sentence.text, from)
|
|
97
|
+
if (!numeral || numeral.span[0] - from > FIND_WINDOW) return notLocated([candidate(text, sentences, hit.start, hit.end)])
|
|
98
|
+
const result = quantityResult(text, sourceId, sentence, numeral)
|
|
99
|
+
if (unitExpected === undefined) return result
|
|
100
|
+
const expected = normalizeUnit(unitExpected) ?? unitExpected
|
|
101
|
+
return result.unit === expected ? result : { error: "unit_expected_mismatch", expected, found: result.unit, candidates: [result] }
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function cellsOf(row) {
|
|
105
|
+
const cells = []
|
|
106
|
+
for (const cell of row.children.filter((child) => child.tag === "td" || child.tag === "th")) {
|
|
107
|
+
const span = Math.max(1, Number.parseInt(cell.attrs.colspan ?? "1", 10) || 1)
|
|
108
|
+
for (let k = 0; k < span; k++) cells.push(cell)
|
|
109
|
+
}
|
|
110
|
+
return cells
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function rowsOf(table) {
|
|
114
|
+
const rows = []
|
|
115
|
+
const walk = (node) => {
|
|
116
|
+
for (const child of node.children) {
|
|
117
|
+
if (child.tag === "tr") rows.push(child)
|
|
118
|
+
else if (child.tag !== "table" && child.tag !== "#text") walk(child)
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
walk(table)
|
|
122
|
+
return rows
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function tablesOf(root) {
|
|
126
|
+
const tables = []
|
|
127
|
+
const walk = (node) => {
|
|
128
|
+
for (const child of node.children) {
|
|
129
|
+
if (child.tag === "table") tables.push(child)
|
|
130
|
+
if (child.tag !== "#text") walk(child)
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
walk(root)
|
|
134
|
+
return tables.map((table) => {
|
|
135
|
+
const rows = rowsOf(table)
|
|
136
|
+
const header = rows.find((row) => row.children.some((child) => child.tag === "th")) ?? rows[0]
|
|
137
|
+
const caption = table.children.find((child) => child.tag === "caption")
|
|
138
|
+
return { table, header, rows: rows.filter((row) => row !== header), caption: caption ? label(proseText(caption)) : null }
|
|
139
|
+
})
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const cellLabel = (cell) => label(proseText(cell))
|
|
143
|
+
const indexesOf = (labels, wanted) => labels.flatMap((value, index) => (value === wanted ? [index] : []))
|
|
144
|
+
|
|
145
|
+
/** --table/--row/--col: the table by id, caption, or a header label; row and column by normalized label, never by index. */
|
|
146
|
+
export function locateTable(sourceHtml, { table, row, col, sourceId }) {
|
|
147
|
+
requireSource(sourceId)
|
|
148
|
+
const { root, text, blocks } = snapshotBlocks(sourceHtml)
|
|
149
|
+
const key = label(table)
|
|
150
|
+
const tables = tablesOf(root).filter((entry) => entry.table.attrs.id === table || entry.caption === key || (entry.header && cellsOf(entry.header).map(cellLabel).includes(key)))
|
|
151
|
+
if (tables.length !== 1) return tables.length === 0 ? notLocated() : ambiguous(tables.map((entry) => ({ table: entry.table.attrs.id ?? entry.caption })))
|
|
152
|
+
const [{ header, rows }] = tables
|
|
153
|
+
const headerCells = header ? cellsOf(header) : []
|
|
154
|
+
const columns = indexesOf(headerCells.map(cellLabel), label(col))
|
|
155
|
+
const matches = rows.filter((candidateRow) => cellsOf(candidateRow).length > 0 && cellLabel(cellsOf(candidateRow)[0]) === label(row))
|
|
156
|
+
if (columns.length > 1 || matches.length > 1) return ambiguous([...columns.map((index) => ({ col: index })), ...matches.map((match) => ({ row_line: match.line }))])
|
|
157
|
+
const cell = columns.length === 1 && matches.length === 1 ? cellsOf(matches[0])[columns[0]] : null
|
|
158
|
+
const block = cell ? blocks.find((entry) => entry.node === cell) : null
|
|
159
|
+
if (!block) return notLocated()
|
|
160
|
+
const holder = { sid: sentenceAt(indexSentences(text, sourceId), block.start)?.sid ?? null, start: block.start, end: block.end, text: text.slice(block.start, block.end) }
|
|
161
|
+
const numeral = parseNumeral(holder.text)
|
|
162
|
+
if (!numeral) return notLocated()
|
|
163
|
+
const headerUnit = /\(([^()]+)\)\s*$/.exec(cellLabel(headerCells[columns[0]]))
|
|
164
|
+
return quantityResult(text, sourceId, holder, numeral, { table, row_label: row, col_label: col }, headerUnit ? normalizeUnit(headerUnit[1]) : null)
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/** --page/--line: exactly one quantity on that line of that page (pages split on a form feed). */
|
|
168
|
+
export function locatePageLine(text, { page, line, sourceId }) {
|
|
169
|
+
requireSource(sourceId)
|
|
170
|
+
const pages = text.split("\f")
|
|
171
|
+
if (!Number.isInteger(page) || page < 1 || page > pages.length) return notLocated()
|
|
172
|
+
const lines = pages[page - 1].split("\n")
|
|
173
|
+
if (!Number.isInteger(line) || line < 1 || line > lines.length) return notLocated()
|
|
174
|
+
let start = pages.slice(0, page - 1).reduce((sum, body) => sum + body.length + 1, 0)
|
|
175
|
+
for (const body of lines.slice(0, line - 1)) start += body.length + 1
|
|
176
|
+
const holder = { start, end: start + lines[line - 1].length, text: lines[line - 1] }
|
|
177
|
+
const numerals = []
|
|
178
|
+
for (let numeral = parseNumeral(holder.text); numeral; numeral = parseNumeral(holder.text, numeral.span[1])) numerals.push(numeral)
|
|
179
|
+
const sentences = indexSentences(text, sourceId)
|
|
180
|
+
if (numerals.length !== 1) return numerals.length === 0 ? notLocated() : ambiguous(numerals.map((numeral) => candidate(text, sentences, start + numeral.span[0], start + numeral.span[1])))
|
|
181
|
+
holder.sid = sentenceAt(sentences, start + numerals[0].span[0])?.sid ?? null
|
|
182
|
+
return quantityResult(text, sourceId, holder, numerals[0])
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/** Token-aligned windows of `sentence` whose similarity to `match` reaches the fuzzy floor; best first. */
|
|
186
|
+
function fuzzyWindow(sentence, match) {
|
|
187
|
+
const target = normalizeText(match).length
|
|
188
|
+
const wanted = new Set(tokenize(match))
|
|
189
|
+
const present = new Set(tokenize(sentence.text))
|
|
190
|
+
if (wanted.size === 0 || [...wanted].filter((token) => present.has(token)).length / wanted.size < 0.5) return null
|
|
191
|
+
const bounds = [...sentence.text.matchAll(/[\p{L}\p{N}]+/gu)].map((m) => [m.index, m.index + m[0].length])
|
|
192
|
+
let best = null
|
|
193
|
+
for (let i = 0; i < bounds.length; i++) {
|
|
194
|
+
for (let j = i; j < bounds.length; j++) {
|
|
195
|
+
const window = sentence.text.slice(bounds[i][0], bounds[j][1])
|
|
196
|
+
if (window.length > target * 1.06 + 2) break
|
|
197
|
+
if (window.length < target * 0.94 - 2) continue
|
|
198
|
+
const score = similarity(window, match)
|
|
199
|
+
if (score >= FUZZY_FLOOR && (!best || score > best.score)) best = { score, start: sentence.start + bounds[i][0], end: sentence.start + bounds[j][1] }
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
return best
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function quoteResult(text, sourceId, sentences, start, end, fuzzy) {
|
|
206
|
+
const first = sentenceAt(sentences, start)
|
|
207
|
+
const last = sentenceAt(sentences, end - 1) ?? first
|
|
208
|
+
return {
|
|
209
|
+
value: null,
|
|
210
|
+
unit: null,
|
|
211
|
+
raw: text.slice(start, end),
|
|
212
|
+
quote: text.slice(first.start, last.end),
|
|
213
|
+
value_span: null,
|
|
214
|
+
sentence_id: first.sid,
|
|
215
|
+
qualifier: null,
|
|
216
|
+
fuzzy,
|
|
217
|
+
locator: locatorFor(text, sourceId, first.start, last.end),
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/** --match: a normalized exact match (fuzzy false), else the one window with similarity >= 0.95 (fuzzy true). */
|
|
222
|
+
export function locateMatch(text, { match, sourceId }) {
|
|
223
|
+
requireSource(sourceId)
|
|
224
|
+
const sentences = indexSentences(text, sourceId)
|
|
225
|
+
const hits = findAll(text, match)
|
|
226
|
+
if (hits.length > 1) return ambiguous(hits.map((hit) => candidate(text, sentences, hit.start, hit.end)))
|
|
227
|
+
if (hits.length === 1) return quoteResult(text, sourceId, sentences, hits[0].start, hits[0].end, false)
|
|
228
|
+
const windows = sentences.map((sentence) => fuzzyWindow(sentence, match)).filter(Boolean)
|
|
229
|
+
if (windows.length > 1) return ambiguous(windows.map((window) => candidate(text, sentences, window.start, window.end)))
|
|
230
|
+
return windows.length === 1 ? quoteResult(text, sourceId, sentences, windows[0].start, windows[0].end, true) : notLocated()
|
|
231
|
+
}
|