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,176 @@
|
|
|
1
|
+
// source-snapshot.mjs - the per-source snapshot that fact locators read.
|
|
2
|
+
//
|
|
3
|
+
// snapshotFromHtml turns a page into text.txt form: one line per block (prose blocks via
|
|
4
|
+
// proseText, one table cell per line; head, scripts, styles and code are dropped). writeSnapshot
|
|
5
|
+
// writes <sourcesDir>/<S-id>/ with text.txt (pages joined by a form feed), source.html when html
|
|
6
|
+
// was given, meta.json, sentences.jsonl (sid S<n>:s<k>) and acronyms.json. The sha256 covers
|
|
7
|
+
// text.txt only. Nothing here touches the network: the caller supplies the html or text.
|
|
8
|
+
// Plain Node ESM, node: builtins and sibling imports only.
|
|
9
|
+
|
|
10
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs"
|
|
11
|
+
import { join } from "node:path"
|
|
12
|
+
|
|
13
|
+
import { sha256Of } from "./cli-support.mjs"
|
|
14
|
+
import { parseHtml, PROSE_TAGS, proseText } from "./html-lite.mjs"
|
|
15
|
+
import { splitSentences } from "./sentence-split.mjs"
|
|
16
|
+
|
|
17
|
+
const SKIP = new Set(["head", "script", "style", "svg", "code", "pre", "kbd", "samp", "template", "noscript"])
|
|
18
|
+
const INLINE = new Set([
|
|
19
|
+
"a", "abbr", "b", "bdi", "bdo", "cite", "data", "del", "dfn", "em", "font", "i", "ins", "label", "mark", "q", "s",
|
|
20
|
+
"small", "span", "strong", "sub", "sup", "time", "u", "var",
|
|
21
|
+
])
|
|
22
|
+
const SOURCE_ID = /^S[1-9]\d*$/
|
|
23
|
+
const EXTRACTOR = /^(?:tool:source-snapshot@1|model|extractor:[\w.@+-]+)$/
|
|
24
|
+
const KINDS = new Set(["page", "command", "document"])
|
|
25
|
+
const PROVENANCE = new Set(["primary", "secondary", "unknown"])
|
|
26
|
+
const ACRONYM_AFTER = /\(([A-Z]{2,6})\)/g
|
|
27
|
+
const ACRONYM_BEFORE = /(?<![\p{L}\p{N}])([A-Z]{2,6})\s?\(([A-Z][^()\n]{2,79})\)/gu
|
|
28
|
+
const NAME_WORD = /^[A-Z][\p{L}\p{N}-]*$/u
|
|
29
|
+
const CONNECTOR = new Set(["of", "and", "for", "the", "in", "on", "to", "&"])
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Block lines of a page: { root, text, blocks: [{ node, start, end }] } where text joins the block
|
|
33
|
+
* texts with "\n" and text.slice(start, end) is one block. fact-locate reads cells through `node`.
|
|
34
|
+
*/
|
|
35
|
+
export function snapshotBlocks(html) {
|
|
36
|
+
const root = parseHtml(html)
|
|
37
|
+
const lines = []
|
|
38
|
+
let buffer = ""
|
|
39
|
+
const flush = (node) => {
|
|
40
|
+
const line = buffer.replace(/\s+/g, " ").trim()
|
|
41
|
+
buffer = ""
|
|
42
|
+
if (line) lines.push({ node, text: line })
|
|
43
|
+
}
|
|
44
|
+
const walk = (node) => {
|
|
45
|
+
for (const child of node.children) {
|
|
46
|
+
if (child.tag === "#text") buffer += child.text
|
|
47
|
+
else if (SKIP.has(child.tag)) continue
|
|
48
|
+
else if (child.tag === "br") flush(node)
|
|
49
|
+
else if (INLINE.has(child.tag)) walk(child)
|
|
50
|
+
else if (PROSE_TAGS.has(child.tag)) {
|
|
51
|
+
flush(node)
|
|
52
|
+
const line = proseText(child)
|
|
53
|
+
if (line) lines.push({ node: child, text: line })
|
|
54
|
+
} else {
|
|
55
|
+
flush(node)
|
|
56
|
+
walk(child)
|
|
57
|
+
flush(child)
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
walk(root)
|
|
62
|
+
flush(root)
|
|
63
|
+
const blocks = []
|
|
64
|
+
let at = 0
|
|
65
|
+
for (const line of lines) {
|
|
66
|
+
blocks.push({ node: line.node, start: at, end: at + line.text.length })
|
|
67
|
+
at += line.text.length + 1
|
|
68
|
+
}
|
|
69
|
+
return { root, text: lines.map((line) => line.text).join("\n"), blocks }
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** text.txt form of an html page. */
|
|
73
|
+
export function snapshotFromHtml(html) {
|
|
74
|
+
return snapshotBlocks(html).text
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** Sentence index rows { sid: "S<n>:s<k>", start, end, text }; a form feed (page break) always ends a sentence. */
|
|
78
|
+
export function indexSentences(text, sourceId) {
|
|
79
|
+
return splitSentences(text.replace(/\f/g, "\n")).map((row) => ({
|
|
80
|
+
sid: `${sourceId}:${row.sid}`,
|
|
81
|
+
start: row.start,
|
|
82
|
+
end: row.end,
|
|
83
|
+
text: text.slice(row.start, row.end),
|
|
84
|
+
}))
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const sentenceIdAt = (sentences, offset) => sentences.find((row) => row.start <= offset && offset < row.end)?.sid ?? null
|
|
88
|
+
|
|
89
|
+
/** "High Bandwidth Memory (HBM)": the capitalized words before the parenthesis, at most one per letter. */
|
|
90
|
+
function nameBefore(text, at, letters) {
|
|
91
|
+
const prefix = text.slice(Math.max(0, at - 160), at)
|
|
92
|
+
if (!/\S[ \t]?$/.test(prefix)) return null
|
|
93
|
+
const words = [...prefix.matchAll(/\S+/g)]
|
|
94
|
+
const picked = []
|
|
95
|
+
let content = 0
|
|
96
|
+
for (let k = words.length - 1; k >= 0 && content < letters; k--) {
|
|
97
|
+
const word = words[k][0]
|
|
98
|
+
if (NAME_WORD.test(word)) content++
|
|
99
|
+
else if (!CONNECTOR.has(word) || picked.length === 0) break
|
|
100
|
+
picked.unshift(words[k])
|
|
101
|
+
}
|
|
102
|
+
while (picked.length > 0 && CONNECTOR.has(picked[0][0])) picked.shift()
|
|
103
|
+
if (picked.length === 0) return null
|
|
104
|
+
const start = at - prefix.length + picked[0].index
|
|
105
|
+
const end = at - prefix.length + picked[picked.length - 1].index + picked[picked.length - 1][0].length
|
|
106
|
+
return { start, expansion: text.slice(start, end) }
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** Every `Full Name (ACR)` and `ACR (Full Name)` in the text, ordered by position. */
|
|
110
|
+
export function findAcronyms(text, sentences) {
|
|
111
|
+
const entries = []
|
|
112
|
+
for (const match of text.matchAll(ACRONYM_AFTER)) {
|
|
113
|
+
const name = nameBefore(text, match.index, match[1].length)
|
|
114
|
+
if (!name || name.expansion === match[1]) continue
|
|
115
|
+
entries.push({ acronym: match[1], expansion: name.expansion, span: [name.start, match.index + match[0].length] })
|
|
116
|
+
}
|
|
117
|
+
for (const match of text.matchAll(ACRONYM_BEFORE)) {
|
|
118
|
+
const expansion = match[2].trim()
|
|
119
|
+
if (!/\s/.test(expansion) || expansion === expansion.toUpperCase()) continue
|
|
120
|
+
entries.push({ acronym: match[1], expansion, span: [match.index, match.index + match[0].length] })
|
|
121
|
+
}
|
|
122
|
+
return entries
|
|
123
|
+
.sort((a, b) => a.span[0] - b.span[0])
|
|
124
|
+
.map((entry) => ({ acronym: entry.acronym, expansion: entry.expansion, sentence_id: sentenceIdAt(sentences, entry.span[0]), span: entry.span }))
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function snapshotText(input) {
|
|
128
|
+
if (Array.isArray(input.pages)) return input.pages.join("\f")
|
|
129
|
+
if (typeof input.text === "string") return input.text
|
|
130
|
+
if (typeof input.html === "string") return snapshotFromHtml(input.html)
|
|
131
|
+
throw new TypeError("writeSnapshot needs text, pages, or html")
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function checkInput(sourceId, input) {
|
|
135
|
+
if (!SOURCE_ID.test(String(sourceId))) throw new TypeError(`source id must be S<n>, got ${sourceId}`)
|
|
136
|
+
if (!EXTRACTOR.test(String(input.extractor))) throw new TypeError(`extractor must be tool:source-snapshot@1, model, or extractor:<name>, got ${input.extractor}`)
|
|
137
|
+
if (typeof (input.url ?? input.path) !== "string") throw new TypeError("writeSnapshot needs a url or a path")
|
|
138
|
+
if (input.kind !== undefined && !KINDS.has(input.kind)) throw new TypeError(`kind must be page, command, or document, got ${input.kind}`)
|
|
139
|
+
if (input.provenance !== undefined && !PROVENANCE.has(input.provenance)) throw new TypeError(`provenance must be primary, secondary, or unknown, got ${input.provenance}`)
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Write <sourcesDir>/<sourceId>/ for one source; returns { dir, sha256 } with sha256 over text.txt.
|
|
144
|
+
* input: { url | path, text | pages | html, extractor, observed_at?, revision?, kind?, provenance? }.
|
|
145
|
+
* Re-writing the same text is a no-op change; a different text over an existing snapshot throws
|
|
146
|
+
* snapshot_exists, because ledger rows pin the old hash.
|
|
147
|
+
*/
|
|
148
|
+
export function writeSnapshot(sourcesDir, sourceId, input) {
|
|
149
|
+
checkInput(sourceId, input)
|
|
150
|
+
const text = snapshotText(input)
|
|
151
|
+
const sha256 = sha256Of(Buffer.from(text, "utf8"))
|
|
152
|
+
const dir = join(sourcesDir, sourceId)
|
|
153
|
+
const textPath = join(dir, "text.txt")
|
|
154
|
+
if (existsSync(textPath) && sha256Of(readFileSync(textPath)) !== sha256) {
|
|
155
|
+
throw new Error(`snapshot_exists: ${sourceId} already holds a different text.txt`)
|
|
156
|
+
}
|
|
157
|
+
mkdirSync(dir, { recursive: true })
|
|
158
|
+
writeFileSync(textPath, text)
|
|
159
|
+
if (typeof input.html === "string") writeFileSync(join(dir, "source.html"), input.html)
|
|
160
|
+
const meta = {
|
|
161
|
+
source_id: sourceId,
|
|
162
|
+
url: input.url ?? input.path,
|
|
163
|
+
observed_at: input.observed_at ?? new Date().toISOString(),
|
|
164
|
+
sha256,
|
|
165
|
+
extractor: input.extractor,
|
|
166
|
+
revision: input.revision ?? null,
|
|
167
|
+
kind: input.kind ?? (input.url !== undefined || typeof input.html === "string" ? "page" : "document"),
|
|
168
|
+
provenance: input.provenance ?? "unknown",
|
|
169
|
+
}
|
|
170
|
+
writeFileSync(join(dir, "meta.json"), `${JSON.stringify(meta, null, 2)}\n`)
|
|
171
|
+
const sentences = indexSentences(text, sourceId)
|
|
172
|
+
writeFileSync(join(dir, "sentences.jsonl"), sentences.map((row) => `${JSON.stringify(row)}\n`).join(""))
|
|
173
|
+
const acronyms = { schemaVersion: 1, source_id: sourceId, snapshot_sha256: sha256, entries: findAcronyms(text, sentences) }
|
|
174
|
+
writeFileSync(join(dir, "acronyms.json"), `${JSON.stringify(acronyms, null, 2)}\n`)
|
|
175
|
+
return { dir, sha256 }
|
|
176
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
// text-normalize.mjs - text normalization and raw-offset matching for fidelity checks.
|
|
2
|
+
//
|
|
3
|
+
// normalizeText applies NFC, maps curly and CJK quote glyphs to straight quotes, collapses
|
|
4
|
+
// whitespace runs to one space, and trims. findNormalized matches a needle against the
|
|
5
|
+
// normalized haystack and returns offsets into the RAW haystack through an offset map built
|
|
6
|
+
// during normalization. Plain Node ESM, no dependencies.
|
|
7
|
+
|
|
8
|
+
const DOUBLE_QUOTES = new Set([
|
|
9
|
+
"\u201C", "\u201D", "\u201E", "\u201F", "\u00AB", "\u00BB", "\u300C", "\u300D", "\u300E", "\u300F",
|
|
10
|
+
"\u301D", "\u301E", "\u301F", "\uFF02", "\u2033",
|
|
11
|
+
])
|
|
12
|
+
const SINGLE_QUOTES = new Set(["\u2018", "\u2019", "\u201A", "\u201B", "\u2039", "\u203A", "\uFF07", "\u2032"])
|
|
13
|
+
// Combining marks and conjoining Hangul medial/final jamo attach to the preceding code point,
|
|
14
|
+
// so a decomposed syllable is normalized as one unit.
|
|
15
|
+
const JOINS_PREVIOUS = /^[\p{M}\u1160-\u11FF\uD7B0-\uD7FF]$/u
|
|
16
|
+
const WHITESPACE = /^\s+$/u
|
|
17
|
+
const TOKEN_SEPARATOR = /[^\p{L}\p{N}\p{M}]+/u
|
|
18
|
+
|
|
19
|
+
function mapQuote(ch) {
|
|
20
|
+
if (DOUBLE_QUOTES.has(ch)) return '"'
|
|
21
|
+
if (SINGLE_QUOTES.has(ch)) return "'"
|
|
22
|
+
return ch
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function clusters(raw) {
|
|
26
|
+
const out = []
|
|
27
|
+
let at = 0
|
|
28
|
+
for (const ch of raw) {
|
|
29
|
+
const last = out[out.length - 1]
|
|
30
|
+
if (last && JOINS_PREVIOUS.test(ch)) last[1] += ch.length
|
|
31
|
+
else out.push([at, at + ch.length])
|
|
32
|
+
at += ch.length
|
|
33
|
+
}
|
|
34
|
+
return out
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Returns { text, starts, ends }: text[k] came from raw.slice(starts[k], ends[k]).
|
|
38
|
+
function normalizeWithMap(raw) {
|
|
39
|
+
const source = String(raw ?? "")
|
|
40
|
+
let text = ""
|
|
41
|
+
const starts = []
|
|
42
|
+
const ends = []
|
|
43
|
+
for (const [start, end] of clusters(source)) {
|
|
44
|
+
const piece = Array.from(source.slice(start, end).normalize("NFC"), mapQuote).join("")
|
|
45
|
+
if (WHITESPACE.test(piece)) {
|
|
46
|
+
if (text !== "" && !text.endsWith(" ")) {
|
|
47
|
+
text += " "
|
|
48
|
+
starts.push(start)
|
|
49
|
+
ends.push(end)
|
|
50
|
+
}
|
|
51
|
+
continue
|
|
52
|
+
}
|
|
53
|
+
for (let k = 0; k < piece.length; k++) {
|
|
54
|
+
starts.push(start)
|
|
55
|
+
ends.push(end)
|
|
56
|
+
}
|
|
57
|
+
text += piece
|
|
58
|
+
}
|
|
59
|
+
if (text.endsWith(" ")) {
|
|
60
|
+
text = text.slice(0, -1)
|
|
61
|
+
starts.pop()
|
|
62
|
+
ends.pop()
|
|
63
|
+
}
|
|
64
|
+
return { text, starts, ends }
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function normalizeText(s) {
|
|
68
|
+
return normalizeWithMap(s).text
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function tokenize(s) {
|
|
72
|
+
return normalizeText(s).split(TOKEN_SEPARATOR).filter((token) => token !== "")
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function findNormalized(hay, needle) {
|
|
76
|
+
const target = normalizeText(needle)
|
|
77
|
+
if (target === "") return null
|
|
78
|
+
const map = normalizeWithMap(hay)
|
|
79
|
+
const at = map.text.indexOf(target)
|
|
80
|
+
if (at === -1) return null
|
|
81
|
+
return { start: map.starts[at], end: map.ends[at + target.length - 1] }
|
|
82
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// text-similarity.mjs - similarity in [0, 1] from normalized Levenshtein distance.
|
|
2
|
+
//
|
|
3
|
+
// Both inputs go through normalizeText first (NFC, quote glyphs, whitespace), then the
|
|
4
|
+
// edit distance is computed over code points and divided by the longer length.
|
|
5
|
+
// Plain Node ESM, no dependencies.
|
|
6
|
+
|
|
7
|
+
import { normalizeText } from "./text-normalize.mjs"
|
|
8
|
+
|
|
9
|
+
function levenshtein(a, b) {
|
|
10
|
+
let previous = Array.from({ length: b.length + 1 }, (_, k) => k)
|
|
11
|
+
for (let i = 1; i <= a.length; i++) {
|
|
12
|
+
const current = [i]
|
|
13
|
+
for (let k = 1; k <= b.length; k++) {
|
|
14
|
+
const cost = a[i - 1] === b[k - 1] ? 0 : 1
|
|
15
|
+
current.push(Math.min(previous[k] + 1, current[k - 1] + 1, previous[k - 1] + cost))
|
|
16
|
+
}
|
|
17
|
+
previous = current
|
|
18
|
+
}
|
|
19
|
+
return previous[b.length]
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function similarity(a, b) {
|
|
23
|
+
const left = Array.from(normalizeText(a))
|
|
24
|
+
const right = Array.from(normalizeText(b))
|
|
25
|
+
const longest = Math.max(left.length, right.length)
|
|
26
|
+
if (longest === 0) return 1
|
|
27
|
+
return 1 - levenshtein(left, right) / longest
|
|
28
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
// Canonical factor algebra. Exponents are bigint; aliases enter only through normalizeInput.
|
|
2
|
+
import { fromDecimal, mul, sigFigsOf, toDecimal } from "./rational.mjs"
|
|
3
|
+
import { dimensionOf, normalizeUnit } from "./units.mjs"
|
|
4
|
+
import { refuse } from "./unit-errors.mjs"
|
|
5
|
+
export { refuse, RuleError } from "./unit-errors.mjs"
|
|
6
|
+
|
|
7
|
+
const SCALED = new Set(["bp", "basispoint", "basispoints"])
|
|
8
|
+
export const isPercentish = (unit) => unit === "%" || unit === "%p"
|
|
9
|
+
const scaled = (text) => SCALED.has(text.replace(/\s+/g, "").toLowerCase())
|
|
10
|
+
|
|
11
|
+
function atomOf(text) {
|
|
12
|
+
if (text === "1") return null
|
|
13
|
+
const atom = scaled(text) ? "%p" : normalizeUnit(text) ?? text
|
|
14
|
+
if (dimensionOf(atom) === "scale") refuse(`"${text}" is a scale word (${atom}), not a unit`, undefined, "scale_atom", "a counted unit with scale in the numeral")
|
|
15
|
+
return atom
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const combine = (a, b, sign) => {
|
|
19
|
+
for (const [atom, power] of b) a.set(atom, (a.get(atom) ?? 0n) + sign * power)
|
|
20
|
+
return a
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Iterative unit parser: factor maps plus the input's decimal scaling exponent. */
|
|
24
|
+
function parse(raw) {
|
|
25
|
+
if (raw === null) return { factors: new Map(), shift: 0n }
|
|
26
|
+
if (typeof raw !== "string" || !raw.trim()) refuse("unit must be text or explicit null", undefined, "unit_text", "canonical unit text or null")
|
|
27
|
+
const tokens = raw.match(/[*/()^]|[^*/()^]+/g).map((x) => x.trim()).filter(Boolean)
|
|
28
|
+
const frames = [{ factors: new Map(), shift: 0n, sign: 1n, need: true }]
|
|
29
|
+
let affine = false
|
|
30
|
+
const apply = (value, i) => {
|
|
31
|
+
if (tokens[i] === "^") {
|
|
32
|
+
const power = tokens[i + 1]
|
|
33
|
+
if (!/^[+-]?\d+$/.test(power ?? "")) refuse(`unit "${raw}" has a bad exponent`, undefined, "exponent", "an integer exponent")
|
|
34
|
+
const n = BigInt(power)
|
|
35
|
+
value = { factors: new Map([...value.factors].map(([a, p]) => [a, p * n])), shift: value.shift * n }
|
|
36
|
+
i += 2
|
|
37
|
+
}
|
|
38
|
+
const frame = frames.at(-1)
|
|
39
|
+
combine(frame.factors, value.factors, frame.sign)
|
|
40
|
+
frame.shift += frame.sign * value.shift
|
|
41
|
+
frame.need = false
|
|
42
|
+
return i
|
|
43
|
+
}
|
|
44
|
+
for (let i = 0; i < tokens.length;) {
|
|
45
|
+
const frame = frames.at(-1)
|
|
46
|
+
const token = tokens[i]
|
|
47
|
+
if (frame.need) {
|
|
48
|
+
if (token === "(") {
|
|
49
|
+
frames.push({ factors: new Map(), shift: 0n, sign: 1n, need: true })
|
|
50
|
+
i++
|
|
51
|
+
} else {
|
|
52
|
+
if (/^[*/()^]$/.test(token)) refuse(`unexpected "${token}" in unit "${raw}"`)
|
|
53
|
+
const atom = atomOf(token)
|
|
54
|
+
affine ||= dimensionOf(atom) === "temperature"
|
|
55
|
+
i = apply({ factors: new Map(atom === null ? [] : [[atom, 1n]]), shift: scaled(token) ? -2n : 0n }, i + 1)
|
|
56
|
+
}
|
|
57
|
+
} else if (token === ")" && frames.length > 1) {
|
|
58
|
+
const value = frames.pop()
|
|
59
|
+
i = apply(value, i + 1)
|
|
60
|
+
} else if (token === "*" || token === "/") {
|
|
61
|
+
frame.sign = token === "*" ? 1n : -1n
|
|
62
|
+
frame.need = true
|
|
63
|
+
i++
|
|
64
|
+
} else refuse(`unexpected "${token}" in unit "${raw}"`)
|
|
65
|
+
}
|
|
66
|
+
if (frames.length !== 1 || frames[0].need) refuse(`unit "${raw}" ends early or is missing a closing parenthesis`)
|
|
67
|
+
// Refuse affine composites before cancellation loses the atom that made them unsafe.
|
|
68
|
+
if (affine && (tokens.length !== 1 || frames[0].shift !== 0n)) refuse("affine (temperature) units cannot occur in composites", undefined, "affine_units", "a non-affine composite or bare temperature for convert")
|
|
69
|
+
return frames[0]
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function format(factors) {
|
|
73
|
+
const side = (sign) => [...factors].filter(([, p]) => sign * p > 0n).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0)
|
|
74
|
+
.map(([a, p]) => { const n = sign * p; return n === 1n ? a : `${a}^${n}` })
|
|
75
|
+
const top = side(1n)
|
|
76
|
+
const bottom = side(-1n)
|
|
77
|
+
if (!top.length && !bottom.length) return null
|
|
78
|
+
return `${top.join("*") || "1"}${bottom.length ? `/${bottom.length === 1 ? bottom[0] : `(${bottom.join("*")})`}` : ""}`
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export const canonicalUnit = (raw) => format(parse(raw).factors)
|
|
82
|
+
export function inputScale(raw) {
|
|
83
|
+
const { shift } = parse(raw)
|
|
84
|
+
if (shift === 0n) return null
|
|
85
|
+
if (shift > 10000n || shift < -10000n) refuse("scaled alias exponent exceeds exact input capacity", undefined, "scale_range", "an absolute decimal scale exponent <= 10000")
|
|
86
|
+
return shift > 0n ? `1${"0".repeat(Number(shift))}` : `0.${"0".repeat(Number(-shift - 1n))}1`
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function normalizeInput(unit, value) {
|
|
90
|
+
const canonical = canonicalUnit(unit)
|
|
91
|
+
const factor = inputScale(unit)
|
|
92
|
+
return { unit: canonical, value: factor ? toDecimal(mul(fromDecimal(value), fromDecimal(factor)), sigFigsOf(value)) : value }
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export const unitTimes = (a, b) => format(combine(parse(a).factors, parse(b).factors, 1n))
|
|
96
|
+
export const unitOver = (a, b) => format(combine(parse(a).factors, parse(b).factors, -1n))
|
|
97
|
+
export const isAffine = (unit) => typeof unit === "string" && dimensionOf(unit) === "temperature"
|
|
98
|
+
export function isCurrencyRate(unit) {
|
|
99
|
+
const currencies = [...parse(unit).factors].filter(([a, n]) => n !== 0n && dimensionOf(a)?.startsWith("currency:"))
|
|
100
|
+
return currencies.some(([, n]) => n > 0n) && currencies.some(([, n]) => n < 0n)
|
|
101
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// Classified refusals shared by public derivation boundaries and the registry.
|
|
2
|
+
export class RuleError extends Error {
|
|
3
|
+
constructor(detail, hint, shape = "invalid_input", expected = "a valid ledger and derivation specification") {
|
|
4
|
+
super(detail)
|
|
5
|
+
this.detail = detail
|
|
6
|
+
this.shape = shape
|
|
7
|
+
this.expected = expected
|
|
8
|
+
if (hint) this.hint = hint
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const refuse = (detail, hint, shape, expected) => {
|
|
13
|
+
throw new RuleError(detail, hint, shape, expected)
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function refusal(error) {
|
|
17
|
+
return {
|
|
18
|
+
shape: error instanceof RuleError ? error.shape : "invalid_input",
|
|
19
|
+
expected: error instanceof RuleError ? error.expected : "valid JSON fields matching the fact ledger grammar",
|
|
20
|
+
detail: error instanceof Error ? error.message : "invalid input",
|
|
21
|
+
...(error instanceof RuleError && error.hint ? { hint: error.hint } : {}),
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// Metadata contracts and order-independent JSON equality, shared by schema and algebra.
|
|
2
|
+
import { isDeepStrictEqual } from "node:util"
|
|
3
|
+
import { refuse } from "./unit-errors.mjs"
|
|
4
|
+
|
|
5
|
+
export const METADATA = ["population", "accounting_basis", "snapshot_window", "entity", "valid_at"]
|
|
6
|
+
export const sameDeclared = isDeepStrictEqual
|
|
7
|
+
export const nonEmpty = (value) => typeof value === "string" && value.trim().length > 0
|
|
8
|
+
export const object = (value) => value !== null && typeof value === "object" && !Array.isArray(value)
|
|
9
|
+
|
|
10
|
+
export function metadataErrors(row) {
|
|
11
|
+
return METADATA.filter((field) => {
|
|
12
|
+
const value = row[field]
|
|
13
|
+
return value !== null && !(nonEmpty(value) || (["population", "snapshot_window"].includes(field) && object(value)))
|
|
14
|
+
}).map((field) => `${field} must be explicitly null or valid non-empty metadata`)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function checkMetadata(rows, fields) {
|
|
18
|
+
for (const row of rows) {
|
|
19
|
+
const errors = metadataErrors(row)
|
|
20
|
+
if (errors.length) refuse(`${row.fact_id}: ${errors.join("; ")}`, undefined, "metadata", "present, type-valid metadata; explicit null when unknown")
|
|
21
|
+
}
|
|
22
|
+
for (const field of fields) {
|
|
23
|
+
if (rows.some((row) => !sameDeclared(row[field], rows[0][field]))) {
|
|
24
|
+
refuse(`${field} differs across ${rows.map((row) => row.fact_id).join(", ")}`, undefined, "metadata", `equal ${field}`)
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function propagatedMetadata(rows) {
|
|
30
|
+
return Object.fromEntries(METADATA.map((field) => [field, rows.every((row) => sameDeclared(row[field], rows[0][field])) ? structuredClone(rows[0][field]) : null]))
|
|
31
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
// unit-pairs.mjs - the typed operand-unit rules shared by the operation registry and expr nodes (references/unit-rules.md).
|
|
2
|
+
// equalUnits and notAffine guard whole operations; minusUnit and plusUnit are the typed-pair rules that diff and sum
|
|
3
|
+
// use and that expr's `-` and `+` nodes follow; inferExpr assigns a canonical unit to every node of an expr tree.
|
|
4
|
+
|
|
5
|
+
import { isAffine, isPercentish, refuse, unitOver, unitTimes } from "./unit-algebra.mjs"
|
|
6
|
+
import { postorder } from "./fact-expr.mjs"
|
|
7
|
+
import { checkMetadata, propagatedMetadata } from "./unit-metadata.mjs"
|
|
8
|
+
|
|
9
|
+
export const show = (unit) => JSON.stringify(unit ?? null)
|
|
10
|
+
export const listed = (rows, units) => rows.map((row, index) => `${row.fact_id} ${units[index] ?? "null"}`).join(", ")
|
|
11
|
+
|
|
12
|
+
export function equalUnits(rows, units) {
|
|
13
|
+
if (units.some((unit) => unit !== units[0])) refuse(`unit differs across ${listed(rows, units)}`)
|
|
14
|
+
return units[0]
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function notAffine(op, rows, units) {
|
|
18
|
+
if (units.some(isAffine)) refuse(`${op} refuses affine (temperature) units: ${listed(rows, units)}`)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** The typed-pair rule shared by diff and expr `-`: U-U is U; %-% is %p; %-%p is %; %p-%p is %p; %p-% is refused. */
|
|
22
|
+
export function minusUnit(a, b, where) {
|
|
23
|
+
if (isAffine(a) || isAffine(b)) refuse(`${where}: subtracting affine (temperature) values as absolute values has no meaning`)
|
|
24
|
+
if (a === b) return a === "%" ? "%p" : a
|
|
25
|
+
if (a === "%" && b === "%p") return "%"
|
|
26
|
+
refuse(`${where}: ${show(a)} - ${show(b)} is not a typed pair (U-U, %-%, %-%p, %p-%p)`)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function plusUnit(a, b, where) {
|
|
30
|
+
if (isAffine(a) || isAffine(b)) refuse(`${where}: adding affine (temperature) values has no meaning`)
|
|
31
|
+
if (a === b && a === "%") refuse(`${where}: adding % values needs disjoint shares of one reference: use --op sum`)
|
|
32
|
+
if (a === b) return a
|
|
33
|
+
if ((a === "%" && b === "%p") || (a === "%p" && b === "%")) return "%"
|
|
34
|
+
refuse(`${where}: ${show(a)} + ${show(b)} has no common unit`)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Unit of every node of an expr tree; refuses when a node's unit cannot be inferred. */
|
|
38
|
+
export function inferExpr(ast, unitsById, rowsById = null) {
|
|
39
|
+
const units = new Map()
|
|
40
|
+
const metadata = new Map()
|
|
41
|
+
for (const node of postorder(ast)) {
|
|
42
|
+
let unit
|
|
43
|
+
if (node.id) unit = unitsById.get(node.id)
|
|
44
|
+
else if (node.literal) unit = null
|
|
45
|
+
else if (node.fn === "pct") {
|
|
46
|
+
unit = units.get(node.arg)
|
|
47
|
+
if (unit !== null) refuse(`pct() takes a dimensionless value, got ${show(unit)}`)
|
|
48
|
+
unit = "%"
|
|
49
|
+
} else {
|
|
50
|
+
const left = units.get(node.left)
|
|
51
|
+
const right = units.get(node.right)
|
|
52
|
+
if (node.op === "+") unit = plusUnit(left, right, "expr")
|
|
53
|
+
else if (node.op === "-") unit = minusUnit(left, right, "expr")
|
|
54
|
+
else {
|
|
55
|
+
if (isAffine(left) || isAffine(right)) refuse(`expr: ${node.op} on affine (temperature) units has no meaning`)
|
|
56
|
+
const fraction = (value) => (isPercentish(value) ? null : value)
|
|
57
|
+
unit = (node.op === "*" ? unitTimes : unitOver)(fraction(left), fraction(right))
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
units.set(node, unit)
|
|
61
|
+
if (rowsById) {
|
|
62
|
+
const children = node.id ? [rowsById.get(node.id)] : node.literal ? [] : node.fn ? [metadata.get(node.arg)] : [metadata.get(node.left), metadata.get(node.right)]
|
|
63
|
+
const rows = children.filter(Boolean)
|
|
64
|
+
if (rows.length) {
|
|
65
|
+
const fields = node.op === "+" || node.op === "-" ? ["population", "snapshot_window", "accounting_basis"] : node.op === "/" ? ["population", "snapshot_window"] : []
|
|
66
|
+
checkMetadata(rows, fields)
|
|
67
|
+
metadata.set(node, { fact_id: "expression", ...propagatedMetadata(rows) })
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return { unit: units.get(ast), units, metadata: metadata.get(ast) }
|
|
72
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// Precision is stored independently from display strings; generated fields are rederived on replay.
|
|
2
|
+
import { sigFigsOf, toDecimal } from "./rational.mjs"
|
|
3
|
+
import { refuse } from "./unit-errors.mjs"
|
|
4
|
+
|
|
5
|
+
export const toBig = (r) => ({ num: BigInt(r.num), den: BigInt(r.den) })
|
|
6
|
+
export const toText = (r) => ({ num: String(r.num), den: String(r.den) })
|
|
7
|
+
export const placesOf = (decimal) => (String(decimal).split(".")[1] ?? "").length
|
|
8
|
+
export function terminates(r) {
|
|
9
|
+
let den = r.den
|
|
10
|
+
for (const prime of [2n, 5n]) while (den % prime === 0n) den /= prime
|
|
11
|
+
return den === 1n
|
|
12
|
+
}
|
|
13
|
+
export function precisionOf(row) {
|
|
14
|
+
if (row.precision !== null && row.precision !== undefined) {
|
|
15
|
+
const p = row.precision
|
|
16
|
+
if (!p || typeof p !== "object" || Object.keys(p).sort().join(",") !== "places,sigfigs" || !Number.isSafeInteger(p.places) || p.places < 0 || !Number.isSafeInteger(p.sigfigs) || p.sigfigs < 1) refuse("invalid stored precision", undefined, "precision_field", "{ places: nonnegative integer, sigfigs: positive integer }")
|
|
17
|
+
return p
|
|
18
|
+
}
|
|
19
|
+
return { places: placesOf(row.value), sigfigs: sigFigsOf(row.value) }
|
|
20
|
+
}
|
|
21
|
+
export function resultPrecision(rows, literals, exact, mode) {
|
|
22
|
+
const inputs = [...rows.map(precisionOf), ...literals.map((value) => precisionOf({ value }))]
|
|
23
|
+
const places = Math.min(...inputs.map((p) => p.places))
|
|
24
|
+
const sigfigs = Math.min(...inputs.map((p) => p.sigfigs))
|
|
25
|
+
if (mode === "places") return { places, sigfigs: sigFigsOf(roundPlaces(exact, places)) }
|
|
26
|
+
const rendered = toDecimal(exact, sigfigs)
|
|
27
|
+
return { places: placesOf(rendered), sigfigs }
|
|
28
|
+
}
|
|
29
|
+
export function roundPlaces(r, places) {
|
|
30
|
+
const digits = ((r.num < 0n ? -r.num : r.num) * 10n ** BigInt(places) * 2n + r.den) / (r.den * 2n)
|
|
31
|
+
const text = digits.toString().padStart(places + 1, "0")
|
|
32
|
+
return (r.num < 0n && digits !== 0n ? "-" : "") + (places ? `${text.slice(0, -places)}.${text.slice(-places)}` : text)
|
|
33
|
+
}
|
|
34
|
+
export function renderValue(exact, precision, mode) {
|
|
35
|
+
const plain = terminates(exact) ? toDecimal(exact) : null
|
|
36
|
+
const n = precision[mode]
|
|
37
|
+
if (plain !== null && (mode === "places" ? placesOf(plain) <= n : sigFigsOf(plain) < n)) return plain
|
|
38
|
+
return mode === "places" ? roundPlaces(exact, n) : toDecimal(exact, n)
|
|
39
|
+
}
|
|
40
|
+
export function display(value, unit) {
|
|
41
|
+
const [whole, fraction] = value.split(".")
|
|
42
|
+
const grouped = whole.replace(/\B(?=(\d{3})+(?!\d))/g, ",") + (fraction === undefined ? "" : `.${fraction}`)
|
|
43
|
+
return unit === null ? grouped : unit === "%" ? `${grouped}%` : `${grouped} ${unit}`
|
|
44
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
// Structural rules used by the operation table, shared by schema and recomputation.
|
|
2
|
+
import { leaves, parseExpr } from "./fact-expr.mjs"
|
|
3
|
+
import { isRational } from "./fact-ledger-kinds.mjs"
|
|
4
|
+
import { canonicalUnit, RuleError } from "./unit-algebra.mjs"
|
|
5
|
+
import { nonEmpty, object } from "./unit-metadata.mjs"
|
|
6
|
+
import { show } from "./unit-pairs.mjs"
|
|
7
|
+
|
|
8
|
+
export const COMMON = ["operands null", "operands empty", "wrong arity", "missing operand id", "invalid rational", "no operation", "repeated operand", "self reference",
|
|
9
|
+
"kind operation", "audit fields", "precision field", "compatibility field", "metadata fields", "noncanonical operand"]
|
|
10
|
+
export const shapes = (...own) => Object.freeze([...COMMON, ...own])
|
|
11
|
+
export const needsContext = (row) => object(row.context) ? null : `${row.operation} rows carry a context object, got ${show(row.context)}`
|
|
12
|
+
export const noContext = (row) => row.context === null ? null : `${row.operation} rows carry no context, got ${show(row.context)}`
|
|
13
|
+
export const repeatedOperand = (ids) => new Set(ids).size !== ids.length ? `operand ${ids.find((id, i) => ids.indexOf(id) !== i)} is repeated: the operands of one row are distinct facts` : null
|
|
14
|
+
|
|
15
|
+
export function sumShape(row) {
|
|
16
|
+
const missing = needsContext(row)
|
|
17
|
+
if (missing) return missing
|
|
18
|
+
const c = row.context
|
|
19
|
+
if (Object.keys(c).sort().join(",") !== "disjoint,reference" || typeof c.disjoint !== "boolean" || !(c.reference === null || nonEmpty(c.reference) || object(c.reference))) return "sum context needs exactly boolean disjoint and null, text, or map reference"
|
|
20
|
+
if (object(c.reference) && (Object.keys(c.reference).sort().join(",") !== [...row.operands].sort().join(",") || !Object.values(c.reference).every(nonEmpty))) return "reference map must name exactly the supplied operands"
|
|
21
|
+
return null
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function convertShape(row) {
|
|
25
|
+
const target = object(row.context) ? row.context.target : undefined
|
|
26
|
+
if (!nonEmpty(target)) return needsContext(row) ?? "a convert row carries its target in context.target"
|
|
27
|
+
if (Object.keys(row.context).join(",") !== "target") return "convert context has exactly target"
|
|
28
|
+
try {
|
|
29
|
+
const canonical = canonicalUnit(target)
|
|
30
|
+
return canonical === target ? null : `context.target ${show(target)} is not canonical unit text (canonical: ${show(canonical)})`
|
|
31
|
+
} catch (error) {
|
|
32
|
+
if (error instanceof RuleError) return `context.target ${show(target)} is not unit text: ${error.detail}`
|
|
33
|
+
throw error
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function exprShape(row) {
|
|
38
|
+
try {
|
|
39
|
+
const ids = leaves(parseExpr(row.formula)).ids
|
|
40
|
+
if (ids.length !== row.operands.length || ids.some((id) => !row.operands.includes(id))) return `formula ids ${ids.join(", ")} do not match operands ${row.operands.join(", ")}`
|
|
41
|
+
return null
|
|
42
|
+
} catch (error) {
|
|
43
|
+
if (error instanceof RuleError) return error.detail
|
|
44
|
+
throw error
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function rowShape(row, rules) {
|
|
49
|
+
if (!object(row)) return "the row is not an object"
|
|
50
|
+
if (!Object.hasOwn(rules, row.operation)) return `operation ${show(row.operation)} is not one of ${Object.keys(rules).join(", ")}`
|
|
51
|
+
const rule = rules[row.operation]
|
|
52
|
+
const { operands, exact } = row
|
|
53
|
+
if (!Array.isArray(operands) || !operands.length || !operands.every(nonEmpty)) return `operands must be a non-empty array of fact ids, got ${show(operands)}`
|
|
54
|
+
const repeated = repeatedOperand(operands)
|
|
55
|
+
if (repeated) return repeated
|
|
56
|
+
if (operands.includes(row.fact_id)) return `${row.fact_id} cites itself as an operand`
|
|
57
|
+
if (!rule.arity(operands.length)) return `${row.operation} takes ${rule.arityText}, got ${operands.length}`
|
|
58
|
+
if (!isRational(exact)) return `exact must be { num, den } reduced integer strings, got ${show(exact)}`
|
|
59
|
+
if (row.kind !== rule.kind) return `kind ${row.kind} must be ${rule.kind} for ${row.operation}`
|
|
60
|
+
const own = rule.shape(row)
|
|
61
|
+
if (own) return own
|
|
62
|
+
if (!nonEmpty(row.formula) || !Array.isArray(row.derived_from) || row.derived_from.length !== operands.length) return "formula and derived_from are required audit fields"
|
|
63
|
+
return null
|
|
64
|
+
}
|