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,167 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test"
|
|
2
|
+
import { readFileSync } from "node:fs"
|
|
3
|
+
import { join } from "node:path"
|
|
4
|
+
|
|
5
|
+
import { indexClaims } from "./claims-index.mjs"
|
|
6
|
+
import { extractPairs, hasHedge, PAIR_KINDS } from "./fact-pairs.mjs"
|
|
7
|
+
import { fill } from "./fact-fill.mjs"
|
|
8
|
+
|
|
9
|
+
type Row = Record<string, unknown> & { fact_id: string }
|
|
10
|
+
type Pair = Record<string, unknown> & { pair_id: string; kind: string; sentence: string }
|
|
11
|
+
|
|
12
|
+
const FIXTURES = join(import.meta.dir, "tests/fixtures/fidelity")
|
|
13
|
+
const BASE = JSON.parse(readFileSync(join(FIXTURES, "ledger-basic.json"), "utf8")) as { schemaVersion: number; rows: Row[] }
|
|
14
|
+
const Q7 = BASE.rows.find((row) => row.fact_id === "Q7")
|
|
15
|
+
if (Q7 === undefined) throw new Error("ledger-basic.json lost Q7")
|
|
16
|
+
const Q8 = { ...Q7, fact_id: "Q8", label: "outlook", quote: "Demand may recover in 2027" }
|
|
17
|
+
const LEDGER = { ...BASE, rows: [...BASE.rows, Q8] }
|
|
18
|
+
const CLAIMS = indexClaims(readFileSync(join(FIXTURES, "claim-graph.md"), "utf8"))
|
|
19
|
+
|
|
20
|
+
const SOURCE = [
|
|
21
|
+
"<!doctype html><html lang=\"en\"><body>",
|
|
22
|
+
"<h1>Bridge report</h1>",
|
|
23
|
+
"<h2 id=\"findings\">Findings</h2>",
|
|
24
|
+
"<p>The span is {{F12:span_len:q}}, or {{F40:span_mm}} [S4].</p>",
|
|
25
|
+
"<p>The rate is {{F13:rate}} [S4].</p>",
|
|
26
|
+
"<p>The operator wrote {{Q8:outlook}}, so the recovery is certain and fully funded now.</p>",
|
|
27
|
+
"<p>The note says {{Q7:completion}}<span data-translates=\"Q7\">completion is not expected before 2027</span>.</p>",
|
|
28
|
+
"<p data-claim=\"C2\">Revenue grew 10 percent in 2025.</p>",
|
|
29
|
+
"<p data-kind=\"opinion\">This looks like a sound plan.</p>",
|
|
30
|
+
"<h2>Sources</h2>",
|
|
31
|
+
"<ol><li id=\"s4\">[S4] Bridge authority page, retrieved 2026-10-01. Its span is {{F13:rate}}.</li></ol>",
|
|
32
|
+
"</body></html>",
|
|
33
|
+
].join("\n")
|
|
34
|
+
|
|
35
|
+
function filled(): { out: string; fillMap: { fact_id: string }[] } {
|
|
36
|
+
const result = fill(SOURCE, LEDGER, { format: "html" })
|
|
37
|
+
if (result.out === null) throw new Error(`fixture failed to fill: ${JSON.stringify(result.errors)}`)
|
|
38
|
+
return { out: result.out, fillMap: result.fillMap as { fact_id: string }[] }
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function pairs(): Pair[] {
|
|
42
|
+
const { out, fillMap } = filled()
|
|
43
|
+
return extractPairs(out, LEDGER, CLAIMS, fillMap) as Pair[]
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
describe("extractPairs: a filled report", () => {
|
|
47
|
+
test("#given the filled fixture #when extracted #then pairs come in document order with P ids", () => {
|
|
48
|
+
const result = pairs()
|
|
49
|
+
expect(result.map((pair) => [pair.pair_id, pair.kind])).toEqual([
|
|
50
|
+
["P1", "quote"],
|
|
51
|
+
["P2", "quote"],
|
|
52
|
+
["P3", "quote"],
|
|
53
|
+
["P4", "quote"],
|
|
54
|
+
["P5", "translation"],
|
|
55
|
+
["P6", "claim"],
|
|
56
|
+
])
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
test("#given the fixture that produces every pair kind #when extracted #then the kinds are exactly PAIR_KINDS", () => {
|
|
60
|
+
expect([...new Set(pairs().map((pair) => pair.kind))].sort()).toEqual([...PAIR_KINDS].sort())
|
|
61
|
+
expect(Object.isFrozen(PAIR_KINDS)).toBe(true)
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
test("#given two facts sharing one source quote #when extracted #then one quote pair names both", () => {
|
|
65
|
+
const [first] = pairs()
|
|
66
|
+
expect(first).toMatchObject({
|
|
67
|
+
kind: "quote",
|
|
68
|
+
sentence_key: "findings#F12,F40",
|
|
69
|
+
fact_ids: ["F12", "F40"],
|
|
70
|
+
quote: "The bridge has a main span of up to 320 m.",
|
|
71
|
+
source_lang: "en",
|
|
72
|
+
report_lang: "en",
|
|
73
|
+
force_strong: false,
|
|
74
|
+
})
|
|
75
|
+
expect(first.sentence).toContain("up to 320 m")
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
test("#given an unhedged quote with its value kept #when extracted #then force_strong stays false", () => {
|
|
79
|
+
expect(pairs()[1]).toMatchObject({ fact_ids: ["F13"], quote: "The rate reached 15.2% in May.", force_strong: false })
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
test("#given a hedged quote whose sentence ends without a hedge #when extracted #then force_strong is true", () => {
|
|
83
|
+
expect(pairs()[2]).toMatchObject({ fact_ids: ["Q8"], quote: "Demand may recover in 2027", force_strong: true })
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
test("#given a data-translates span #when extracted #then a translation pair carries the span text", () => {
|
|
87
|
+
const result = pairs()
|
|
88
|
+
expect(result[3]).toMatchObject({ kind: "quote", fact_ids: ["Q7"] })
|
|
89
|
+
expect(result[4]).toMatchObject({
|
|
90
|
+
kind: "translation",
|
|
91
|
+
fact_ids: ["Q7"],
|
|
92
|
+
quote: "we estimate completion no earlier than 2027",
|
|
93
|
+
sentence: "completion is not expected before 2027",
|
|
94
|
+
})
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
test("#given a sentence bound only by data-claim #when extracted #then a claim pair carries the node statement", () => {
|
|
98
|
+
expect(pairs()[5]).toMatchObject({
|
|
99
|
+
kind: "claim",
|
|
100
|
+
claim_id: "C2",
|
|
101
|
+
statement: "Revenue grew about 10 percent in 2025.",
|
|
102
|
+
sentence: "Revenue grew 10 percent in 2025.",
|
|
103
|
+
force_strong: true,
|
|
104
|
+
})
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
test("#given an opinion paragraph and a sources section #when extracted #then neither yields a pair", () => {
|
|
108
|
+
const sentences = pairs().map((pair) => pair.sentence)
|
|
109
|
+
expect(sentences.some((text) => text.includes("sound plan"))).toBe(false)
|
|
110
|
+
expect(sentences.some((text) => text.includes("Bridge authority"))).toBe(false)
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
test("#given a fill map that omits a fact #when extracted #then that hand-typed fact yields no pair", () => {
|
|
114
|
+
const { out, fillMap } = filled()
|
|
115
|
+
const kept = fillMap.filter((entry) => entry.fact_id !== "F13")
|
|
116
|
+
const result = extractPairs(out, LEDGER, CLAIMS, kept) as Pair[]
|
|
117
|
+
expect(result.some((pair) => Array.isArray(pair.fact_ids) && (pair.fact_ids as string[]).includes("F13"))).toBe(false)
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
test("#given no fill map #when extracted #then every data-fact element counts as filled", () => {
|
|
121
|
+
const { out } = filled()
|
|
122
|
+
expect((extractPairs(out, LEDGER, CLAIMS) as Pair[]).length).toBe(6)
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
test("#given a Hangul quote #when extracted #then source_lang is ko", () => {
|
|
126
|
+
const html = "<html><body><h2>Findings</h2><p>HBM revenue reached {{F21:hbm_rev}} [S5].</p></body></html>"
|
|
127
|
+
const result = fill(html, LEDGER, { format: "html" }) as { out: string; fillMap: { fact_id: string }[] }
|
|
128
|
+
const [pair] = extractPairs(result.out, LEDGER, CLAIMS, result.fillMap) as Pair[]
|
|
129
|
+
expect(pair).toMatchObject({ fact_ids: ["F21"], source_lang: "ko", report_lang: "en" })
|
|
130
|
+
})
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
describe("extractPairs: a fill map over translations", () => {
|
|
134
|
+
test("#given a fill map that omits the translated fact #when extracted #then no translation pair is emitted", () => {
|
|
135
|
+
const { out, fillMap } = filled()
|
|
136
|
+
const kept = fillMap.filter((entry) => entry.fact_id !== "Q7")
|
|
137
|
+
const result = extractPairs(out, LEDGER, CLAIMS, kept) as Pair[]
|
|
138
|
+
expect(result.some((pair) => pair.kind === "translation")).toBe(false)
|
|
139
|
+
})
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
describe("hasHedge: a month is not a hedge", () => {
|
|
143
|
+
test("#given a text that starts with the month May #when checked #then it does not count as hedged", () => {
|
|
144
|
+
expect(hasHedge("May 2024 sales rose.")).toBe(false)
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
test("#given a sentence-initial modal may #when checked #then it still counts as hedged", () => {
|
|
148
|
+
expect(hasHedge("May the figure hold through 2027.")).toBe(true)
|
|
149
|
+
expect(hasHedge("Demand may recover in 2027.")).toBe(true)
|
|
150
|
+
})
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
describe("extractPairs: determinism", () => {
|
|
154
|
+
test("#given the same filled report twice #when extracted #then both results are byte-for-byte equal", () => {
|
|
155
|
+
const { out, fillMap } = filled()
|
|
156
|
+
const first = JSON.stringify(extractPairs(out, LEDGER, CLAIMS, fillMap))
|
|
157
|
+
const second = JSON.stringify(extractPairs(out, LEDGER, CLAIMS, fillMap))
|
|
158
|
+
expect(second).toBe(first)
|
|
159
|
+
})
|
|
160
|
+
})
|
|
161
|
+
|
|
162
|
+
describe("extractPairs: the no-model contract", () => {
|
|
163
|
+
test("#given the module source #when read #then it never fetches or imports a network or model client", () => {
|
|
164
|
+
const text = readFileSync(join(import.meta.dir, "fact-pairs.mjs"), "utf8")
|
|
165
|
+
expect(text).not.toMatch(/\bfetch\s*\(|node:https?|node:net|from\s+"(?!\.\/|node:)/u)
|
|
166
|
+
})
|
|
167
|
+
})
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
// Iterative postorder verifier: one snapshot and one arithmetic evaluation per reachable node.
|
|
2
|
+
import { compute } from "./fact-derive.mjs"
|
|
3
|
+
import { parseExpr } from "./fact-expr.mjs"
|
|
4
|
+
import { isLegacyDerived, kindRuleErrors, readFlaws, readRow } from "./fact-ledger-kinds.mjs"
|
|
5
|
+
import { rowErrors } from "./fact-ledger-schema.mjs"
|
|
6
|
+
import { refuse } from "./unit-algebra.mjs"
|
|
7
|
+
import { refusal } from "./unit-errors.mjs"
|
|
8
|
+
import { object, sameDeclared } from "./unit-metadata.mjs"
|
|
9
|
+
import { expectedUnit, malformed, ruleOf, unitMismatch } from "./unit-rules.mjs"
|
|
10
|
+
|
|
11
|
+
const derived = (row) => row.kind === "derived" || row.kind === "converted"
|
|
12
|
+
const fail = (detail, shape, expected) => ({ ok: false, detail, shape, expected })
|
|
13
|
+
function refuseLegacy(row) {
|
|
14
|
+
if (isLegacyDerived(row)) refuse(`${row.fact_id} was written before precision, compatibility, and context existed; regenerate it with derive`, undefined, "legacy derived row", "a derived row regenerated by derive")
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function verifyRow(row, rows) {
|
|
18
|
+
const rule = ruleOf(row.operation)
|
|
19
|
+
const ast = rule.expression ? parseExpr(row.formula) : null
|
|
20
|
+
const context = { ...row.context, rationale: row.rationale }
|
|
21
|
+
const unit = expectedUnit(row.operation, rows, { ast, context })
|
|
22
|
+
const expected = { unit, ...compute(row.operation, rows, unit, ast, context, row.formula), derived_from: rows.map((r) => r.fact_id) }
|
|
23
|
+
if (row.unit !== unit) return fail(`${row.fact_id}: ${unitMismatch(row.operation, rows, row.unit, unit)}`, "result_unit", expected)
|
|
24
|
+
for (const field of Object.keys(expected)) {
|
|
25
|
+
if (!sameDeclared(row[field], expected[field])) return fail(`${row.fact_id}: stored ${field} differs from the recomputed result`, `${field}_mismatch`, expected)
|
|
26
|
+
}
|
|
27
|
+
return { ok: true, expected }
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function verifyGraph(ledger, roots, requireActive = false, override = null) {
|
|
31
|
+
try {
|
|
32
|
+
if (!object(ledger) || !Array.isArray(ledger.rows)) refuse("ledger rows must be an array")
|
|
33
|
+
const byId = new Map()
|
|
34
|
+
for (const r of ledger.rows) {
|
|
35
|
+
if (!object(r) || typeof r.fact_id !== "string" || byId.has(r.fact_id)) refuse("ledger rows need distinct fact ids", undefined, "identity", "distinct fact ids")
|
|
36
|
+
byId.set(r.fact_id, r)
|
|
37
|
+
}
|
|
38
|
+
if (override) byId.set(override.fact_id, override)
|
|
39
|
+
const states = new Map()
|
|
40
|
+
const snapshots = new Map()
|
|
41
|
+
const results = new Map()
|
|
42
|
+
const stack = roots.map((id) => ({ id, finish: false }))
|
|
43
|
+
while (stack.length) {
|
|
44
|
+
const { id, finish } = stack.pop()
|
|
45
|
+
if (states.get(id) === "done") continue
|
|
46
|
+
if (!finish) {
|
|
47
|
+
if (states.get(id) === "open") refuse(`${id}: operands form a cycle`, undefined, "cycle", "an acyclic dependency graph")
|
|
48
|
+
const original = byId.get(id)
|
|
49
|
+
if (!original) refuse(`no ledger row ${id}`, undefined, "missing_operand", "an existing fact id")
|
|
50
|
+
const row = { ...readRow(original) }
|
|
51
|
+
snapshots.set(id, row)
|
|
52
|
+
if (requireActive && row.status !== "active") refuse(`${id} is ${row.status}`, undefined, "inactive_dependency", "active dependencies at every depth")
|
|
53
|
+
const [flaw] = readFlaws(row)
|
|
54
|
+
if (flaw) refuse(`${id}: ${flaw.detail}`, undefined, flaw.shape, flaw.expected)
|
|
55
|
+
if (derived(row)) {
|
|
56
|
+
refuseLegacy(row)
|
|
57
|
+
const shape = malformed(row)
|
|
58
|
+
if (shape) refuse(`${id}: ${shape}`, undefined, "malformed_row", "the operation's registry row shape")
|
|
59
|
+
}
|
|
60
|
+
const errors = derived(row) ? kindRuleErrors(row, id) : rowErrors(row)
|
|
61
|
+
if (errors.length) refuse(errors.join("; "), undefined, "row_schema", "a canonical row matching its kind and operation")
|
|
62
|
+
if (row.exact === null || row.value === null) refuse(`${id} has no exact value to derive from`)
|
|
63
|
+
states.set(id, "open")
|
|
64
|
+
stack.push({ id, finish: true })
|
|
65
|
+
if (derived(row)) for (const dependency of row.operands) stack.push({ id: dependency, finish: false })
|
|
66
|
+
} else {
|
|
67
|
+
const row = snapshots.get(id)
|
|
68
|
+
if (derived(row)) {
|
|
69
|
+
const result = verifyRow(row, row.operands.map((ref) => snapshots.get(ref)))
|
|
70
|
+
if (!result.ok) return result
|
|
71
|
+
const errors = rowErrors(row)
|
|
72
|
+
if (errors.length) refuse(errors.join("; "), undefined, "row_schema", "a complete canonical row")
|
|
73
|
+
results.set(id, result)
|
|
74
|
+
}
|
|
75
|
+
states.set(id, "done")
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return results.get(roots[0]) ?? { ok: true }
|
|
79
|
+
} catch (error) { return { ok: false, ...refusal(error) } }
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function recompute(ledger, row) {
|
|
83
|
+
try {
|
|
84
|
+
refuseLegacy(row)
|
|
85
|
+
const shape = malformed(row)
|
|
86
|
+
if (shape) refuse(shape, undefined, "malformed_row", "a valid derived or converted row")
|
|
87
|
+
return verifyGraph(ledger, [row.fact_id], false, row)
|
|
88
|
+
} catch (error) { return { ok: false, ...refusal(error) } }
|
|
89
|
+
}
|
|
@@ -0,0 +1,409 @@
|
|
|
1
|
+
import { expect, test } from "bun:test"
|
|
2
|
+
|
|
3
|
+
import { derive, recompute } from "./fact-derive.mjs"
|
|
4
|
+
import { addRow, emptyLedger, retractRow, validateLedger } from "./fact-ledger.mjs"
|
|
5
|
+
import { canonicalUnit } from "./unit-rules.mjs"
|
|
6
|
+
import { convertFactor } from "./units.mjs"
|
|
7
|
+
|
|
8
|
+
type Row = Record<string, unknown>
|
|
9
|
+
type Book = { schemaVersion: number; rows: Row[]; events: unknown[] }
|
|
10
|
+
const now = "2026-10-23T08:00:00Z"
|
|
11
|
+
function book(values: string[] = ["1", "3"], units: Array<string | null> = ["m", "m"], extras: Row[] = []): Book {
|
|
12
|
+
const ledger = emptyLedger() as Book
|
|
13
|
+
values.forEach((value, i) => addRow(ledger, {
|
|
14
|
+
kind: "measured", value, unit: units[i], raw: value, quote: value, source_id: "S1",
|
|
15
|
+
locator: { artifact: "sources/S1/text.txt", char_start: 0, char_end: 1 },
|
|
16
|
+
snapshot_sha256: "a".repeat(64), extracted_by: "test", observed_at: now, label: `f${i + 1}`,
|
|
17
|
+
...extras[i],
|
|
18
|
+
}))
|
|
19
|
+
return ledger
|
|
20
|
+
}
|
|
21
|
+
function row(ledger: Book, spec: Row): Row {
|
|
22
|
+
const result = derive(ledger, { ...spec, now })
|
|
23
|
+
if (!("row" in result)) throw Error(JSON.stringify(result))
|
|
24
|
+
return result.row as Row
|
|
25
|
+
}
|
|
26
|
+
const sum = { op: "sum", operands: ["F1", "F2"], unit: "m" }
|
|
27
|
+
function refused(ledger: Book, spec: unknown) {
|
|
28
|
+
const before = structuredClone(ledger)
|
|
29
|
+
const result = derive(ledger, spec)
|
|
30
|
+
expect(result).toMatchObject({ error: "incompatible_operands" })
|
|
31
|
+
expect(ledger).toEqual(before)
|
|
32
|
+
return result
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
test("dimension 01: kind operation consistency cannot disable recursive trust", () => {
|
|
36
|
+
for (const patch of [{ operation: null }, { kind: "measured" }, { operation: "convert" }]) {
|
|
37
|
+
const l = book()
|
|
38
|
+
const inner = row(l, sum)
|
|
39
|
+
const outer = row(l, { op: "convert", operands: [inner.fact_id], to: "cm" })
|
|
40
|
+
Object.assign(inner, patch, { value: "999", exact: { num: "999", den: "1" } })
|
|
41
|
+
expect(validateLedger(l).length).toBeGreaterThan(0)
|
|
42
|
+
expect(recompute(l, outer).ok).toBe(false)
|
|
43
|
+
refused(l, { op: "convert", operands: [inner.fact_id], to: "cm" })
|
|
44
|
+
}
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
test("dimension 02: direct and expression metadata propagate with identical compatibility", () => {
|
|
48
|
+
for (const field of ["population", "accounting_basis", "snapshot_window"]) {
|
|
49
|
+
const incompatible = book(["1", "3"], ["m", "m"], [{ [field]: "adults" }, { [field]: "children" }])
|
|
50
|
+
refused(incompatible, sum)
|
|
51
|
+
refused(incompatible, { op: "expr", formula: "F1 + F2", unit: "m" })
|
|
52
|
+
}
|
|
53
|
+
const meta = { population: "all", accounting_basis: "cash", snapshot_window: "2026", entity: "acme", valid_at: "2026" }
|
|
54
|
+
const l = book(["1", "3"], ["m", "m"], [meta, meta])
|
|
55
|
+
for (const spec of [sum, { op: "expr", formula: "F1 + F2", unit: "m" }]) {
|
|
56
|
+
const inner = row(l, spec)
|
|
57
|
+
expect(inner).toMatchObject(meta)
|
|
58
|
+
expect(row(l, { op: "convert", operands: [inner.fact_id], to: "cm" })).toMatchObject(meta)
|
|
59
|
+
}
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
test("dimension 03: precision survives stored intermediates and conversion round trips", () => {
|
|
63
|
+
const l = book(["1.20", "2.30", "0.04"], ["m", "m", "m"])
|
|
64
|
+
const inner = row(l, sum)
|
|
65
|
+
expect(inner.value).toBe("3.5")
|
|
66
|
+
expect(row(l, { ...sum, operands: [inner.fact_id, "F3"] }).value).toBe("3.54")
|
|
67
|
+
const cm = row(l, { op: "convert", operands: [inner.fact_id], to: "cm" })
|
|
68
|
+
const m = row(l, { op: "convert", operands: [cm.fact_id], to: "m" })
|
|
69
|
+
expect(row(l, { ...sum, operands: [m.fact_id, "F3"] }).value).toBe("3.54")
|
|
70
|
+
expect(recompute(l, m).ok).toBe(true)
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
test("dimension 04: scaled aliases preserve composite factor scale", () => {
|
|
74
|
+
for (const [unit, value, formula, expectedUnit, expected] of [
|
|
75
|
+
["bp/person", "100", "F1 * F2", "%p", "1"],
|
|
76
|
+
["person/bp", "1", "F1 / F2", "1/%p", "100"],
|
|
77
|
+
["bp^2/person", "10000", "F1 * F2", "%p^2", "1"],
|
|
78
|
+
] as const) {
|
|
79
|
+
const l = book([value, "1"], [unit, "person"])
|
|
80
|
+
expect(row(l, { op: "expr", formula, unit: expectedUnit }).exact).toEqual({ num: expected, den: "1" })
|
|
81
|
+
}
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
test("dimension 05: public refusal API is total classified and append atomic", () => {
|
|
85
|
+
for (const bad of [null, undefined, 1, [], {}, { op: "wat" }, { ...sum, operands: null }]) {
|
|
86
|
+
const result = refused(book(), bad)
|
|
87
|
+
expect(result).toMatchObject({ shape: expect.any(String), expected: expect.anything() })
|
|
88
|
+
}
|
|
89
|
+
for (const bad of [null, [], {}, { num: "NaN", den: "1" }, { num: "1", den: "0" }]) {
|
|
90
|
+
const l = book()
|
|
91
|
+
l.rows[0].exact = bad
|
|
92
|
+
refused(l, sum)
|
|
93
|
+
const result = recompute(l, l.rows[0])
|
|
94
|
+
expect(result).toMatchObject({ ok: false, shape: expect.any(String) })
|
|
95
|
+
expect(result.expected).toBeDefined()
|
|
96
|
+
}
|
|
97
|
+
for (const bad of [null, undefined, 1, [], {}]) {
|
|
98
|
+
expect(() => recompute(bad, bad)).not.toThrow()
|
|
99
|
+
expect(recompute(bad, bad)).toMatchObject({ ok: false, shape: expect.any(String) })
|
|
100
|
+
}
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
test("dimension 06: aliases normalize at input; derived rows store canonical units, source aliases read canonical (dimension 31)", () => {
|
|
104
|
+
const l = book(["1", "3"], ["metre", "m"])
|
|
105
|
+
expect(l.rows[0].unit).toBe("m")
|
|
106
|
+
const result = row(l, sum)
|
|
107
|
+
l.rows[0].unit = "metre"
|
|
108
|
+
expect(validateLedger(l)).toEqual([])
|
|
109
|
+
expect(recompute(l, result).ok).toBe(true)
|
|
110
|
+
const relabeled = { ...result, unit: "metre" }
|
|
111
|
+
l.rows[l.rows.indexOf(result)] = relabeled
|
|
112
|
+
expect(validateLedger(l).length).toBeGreaterThan(0)
|
|
113
|
+
expect(recompute(l, relabeled).ok).toBe(false)
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
test("dimension 07: exact rationals are reduced canonical integer strings; dev's integer pairs read reduced (dimension 31)", () => {
|
|
117
|
+
for (const [exact, reduced] of [[{ num: "2", den: "2" }, "1"], [{ num: "01", den: "1" }, "1"], [{ num: "-0", den: "1" }, "0"], [{ num: "-6", den: "06" }, "-1"]] as const) {
|
|
118
|
+
const l = book(["1", "3"])
|
|
119
|
+
Object.assign(l.rows[0], { kind: "assumed", rationale: "given", value: reduced, source_id: null, locator: null, snapshot_sha256: null, exact })
|
|
120
|
+
expect(validateLedger(l)).toEqual([])
|
|
121
|
+
expect(row(l, { op: "expr", formula: "F1", unit: "m" }).exact).toEqual({ num: reduced, den: "1" })
|
|
122
|
+
}
|
|
123
|
+
for (const exact of [{ num: 1, den: "1" }, { num: "1", den: "-1" }, { num: "1", den: "0" }, { num: "1.5", den: "1" }]) {
|
|
124
|
+
const l = book()
|
|
125
|
+
Object.assign(l.rows[0], { kind: "assumed", rationale: "given", exact })
|
|
126
|
+
expect(validateLedger(l).length).toBeGreaterThan(0)
|
|
127
|
+
refused(l, { op: "expr", formula: "F1", unit: "m" })
|
|
128
|
+
}
|
|
129
|
+
const l = book()
|
|
130
|
+
const made = row(l, sum)
|
|
131
|
+
made.exact = { num: "8", den: "2" }
|
|
132
|
+
expect(validateLedger(l).length).toBeGreaterThan(0)
|
|
133
|
+
expect(recompute(l, made).ok).toBe(false)
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
test("dimension 08: context schemas reject foreign keys and wrong types", () => {
|
|
137
|
+
const l = book()
|
|
138
|
+
const result = row(l, sum)
|
|
139
|
+
for (const context of [{ disjoint: "true", reference: 7 }, { disjoint: false, reference: null, target: "km" }, { disjoint: false, reference: null, extra: true }]) {
|
|
140
|
+
expect(recompute(l, { ...result, context }).ok).toBe(false)
|
|
141
|
+
}
|
|
142
|
+
refused(l, { ...sum, disjoint: "true" })
|
|
143
|
+
refused(l, { ...sum, target: "km" })
|
|
144
|
+
refused(l, { ...sum, op: "diff", disjoint: true })
|
|
145
|
+
refused(l, { op: "diff", operands: ["F1", "F2"], unit: "m", context: { target: "km" } })
|
|
146
|
+
})
|
|
147
|
+
|
|
148
|
+
test("dimension 09: redundant audit fields are rederived and compared", () => {
|
|
149
|
+
const l = book()
|
|
150
|
+
for (const spec of [sum, { op: "convert", operands: ["F1"], to: "cm" }]) {
|
|
151
|
+
const result = row(l, spec)
|
|
152
|
+
for (const patch of [{ factor: "999" }, { formula: "999" }, { derived_from: ["F2"] }, { raw: "999" }]) {
|
|
153
|
+
expect(recompute(l, { ...result, ...patch }).ok).toBe(false)
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
test("dimension 10: metadata equality ignores object insertion order but not invalid presence", () => {
|
|
159
|
+
const l = book(["1", "3"], ["m", "m"], [{ population: { age: 20, place: "x" } }, { population: { place: "x", age: 20 } }])
|
|
160
|
+
expect(row(l, sum).value).toBe("4")
|
|
161
|
+
for (const value of [undefined, 3, "", []]) {
|
|
162
|
+
const bad = book()
|
|
163
|
+
bad.rows[0].population = value
|
|
164
|
+
refused(bad, sum)
|
|
165
|
+
}
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
test("dimension 11: inactive descendants remain historically verifiable but ineligible", () => {
|
|
169
|
+
const l = book()
|
|
170
|
+
const inner = row(l, sum)
|
|
171
|
+
const outer = row(l, { op: "convert", operands: [inner.fact_id], to: "cm" })
|
|
172
|
+
retractRow(l, "F1", "wrong source")
|
|
173
|
+
expect(recompute(l, outer).ok).toBe(true)
|
|
174
|
+
refused(l, { op: "convert", operands: [outer.fact_id], to: "m" })
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
test("dimension 12: shared DAG nodes are verified once and deep expressions are stack independent", () => {
|
|
178
|
+
const l = book()
|
|
179
|
+
const first = row(l, sum)
|
|
180
|
+
let a = first
|
|
181
|
+
let b = row(l, { ...sum, operands: [first.fact_id, "F1"] })
|
|
182
|
+
for (let i = 0; i < 10; i++) [a, b] = [b, row(l, { ...sum, operands: [a.fact_id, b.fact_id] })]
|
|
183
|
+
let visits = 0
|
|
184
|
+
const exact = first.exact
|
|
185
|
+
Object.defineProperty(first, "exact", { get: () => { visits++; return exact }, configurable: true })
|
|
186
|
+
expect(recompute(l, b).ok).toBe(true)
|
|
187
|
+
expect(visits).toBe(1)
|
|
188
|
+
const deep = `${"(".repeat(12000)}F1${")".repeat(12000)}`
|
|
189
|
+
expect(row(book(), { op: "expr", formula: deep, unit: "m" }).value).toBe("1")
|
|
190
|
+
const chain = book()
|
|
191
|
+
const template = row(chain, { op: "expr", formula: "F1", unit: "m" })
|
|
192
|
+
let previous = String(template.fact_id)
|
|
193
|
+
for (let i = 4; i < 12004; i++) {
|
|
194
|
+
const fact_id = `F${i}`
|
|
195
|
+
chain.rows.push({ ...template, fact_id, label: `chain-${i}`, operands: [previous], derived_from: [previous], formula: previous })
|
|
196
|
+
previous = fact_id
|
|
197
|
+
}
|
|
198
|
+
expect(validateLedger(chain)).toEqual([])
|
|
199
|
+
expect(recompute(chain, chain.rows.at(-1)).ok).toBe(true)
|
|
200
|
+
})
|
|
201
|
+
|
|
202
|
+
test("dimension 13: composite exponents are exact bigint and normalization idempotent", () => {
|
|
203
|
+
for (const [input, expected] of [["m^9007199254740993", "m^9007199254740993"], ["m^9007199254740993/m^9007199254740992", "m"], ["m^0", null], ["m^-2", "1/m^2"], ["m/(s/kg)", "kg*m/s"]] as const) {
|
|
204
|
+
expect(canonicalUnit(input)).toBe(expected)
|
|
205
|
+
expect(canonicalUnit(expected)).toBe(expected)
|
|
206
|
+
}
|
|
207
|
+
for (const input of ["m^1e3", "m^1.5", "m^"]) expect(() => canonicalUnit(input)).toThrow()
|
|
208
|
+
})
|
|
209
|
+
|
|
210
|
+
test("dimension 14: affine and scale atoms cannot hide in composites or cancellation", () => {
|
|
211
|
+
for (const unit of ["C/s", "F*m", "C/C", "(C/s)^2"]) {
|
|
212
|
+
let l: Book
|
|
213
|
+
try { l = book(["1", "3"], [unit, unit]) } catch { continue }
|
|
214
|
+
expect(derive(l, { op: "ratio", num: "F1", den: "F2", unit: null }).error).toBeDefined()
|
|
215
|
+
}
|
|
216
|
+
for (const unit of ["million", "m/M", "bn/bn"]) expect(() => canonicalUnit(unit)).toThrow()
|
|
217
|
+
})
|
|
218
|
+
|
|
219
|
+
test("dimension 15: disjoint shares individually and jointly stay within one whole", () => {
|
|
220
|
+
const spec = { op: "sum", operands: ["F1", "F2"], unit: "%", disjoint: true, reference: "all", rationale: "disjoint" }
|
|
221
|
+
for (const values of [["60", "60"], ["-1", "50"], ["101", "0"]]) refused(book(values, ["%", "%"]), spec)
|
|
222
|
+
expect(row(book(["40", "60"], ["%", "%"]), spec).value).toBe("100")
|
|
223
|
+
refused(book(["20", "30"], ["%", "%"]), { ...spec, reference: { F1: "all", F2: "all", F99: "all" } })
|
|
224
|
+
})
|
|
225
|
+
|
|
226
|
+
test("dimension 16: FX arithmetic persists unchecked compatibility and requires a dated rate", () => {
|
|
227
|
+
const spec = { op: "expr", formula: "F1 * F2", unit: "USD" }
|
|
228
|
+
refused(book(["100", "1.10"], ["EUR", "USD/EUR"]), spec)
|
|
229
|
+
const l = book(["100", "1.10"], ["EUR", "USD/EUR"], [{ valid_at: "2026-10-23" }, { valid_at: "2026-10-23" }])
|
|
230
|
+
const result = row(l, spec)
|
|
231
|
+
expect(result.compatibility).toBe("unchecked")
|
|
232
|
+
expect(result.exact).toEqual({ num: "110", den: "1" })
|
|
233
|
+
expect(recompute(l, result).ok).toBe(true)
|
|
234
|
+
expect(recompute(l, { ...result, compatibility: "checked" }).ok).toBe(false)
|
|
235
|
+
})
|
|
236
|
+
|
|
237
|
+
test("dimension 17: symbols are case sensitive and opaque text stays opaque", () => {
|
|
238
|
+
expect(canonicalUnit("Mb")).toBe("Mb")
|
|
239
|
+
expect(canonicalUnit("MB")).toBe("MB")
|
|
240
|
+
expect(canonicalUnit("m b")).toBe("m b")
|
|
241
|
+
expect(canonicalUnit(" metric ton ")).toBe("metric ton")
|
|
242
|
+
expect(canonicalUnit("m / s")).toBe("m/s")
|
|
243
|
+
expect(canonicalUnit("\u03bcm")).not.toBe(canonicalUnit("\u00b5m"))
|
|
244
|
+
})
|
|
245
|
+
|
|
246
|
+
test("dimension 18: undefined and missing required fields never stand in for null", () => {
|
|
247
|
+
const l = book()
|
|
248
|
+
for (const unit of [undefined, ""]) refused(l, { op: "ratio", num: "F1", den: "F2", unit })
|
|
249
|
+
refused(l, { op: "ratio", num: "F1", den: "F2" })
|
|
250
|
+
expect(row(l, { op: "ratio", num: "F1", den: "F2", unit: null }).unit).toBeNull()
|
|
251
|
+
for (const patch of [{ unit: undefined }, { value: undefined }, { population: undefined }]) {
|
|
252
|
+
const bad = book()
|
|
253
|
+
Object.assign(bad.rows[0], patch)
|
|
254
|
+
refused(bad, sum)
|
|
255
|
+
}
|
|
256
|
+
})
|
|
257
|
+
|
|
258
|
+
test("dimension 19: allocation above safe integer remains exact and unique", () => {
|
|
259
|
+
const l = book()
|
|
260
|
+
l.rows[1].fact_id = "F9007199254740992"
|
|
261
|
+
const result = row(l, { ...sum, operands: ["F1", "F9007199254740992"] })
|
|
262
|
+
expect(result.fact_id).toBe("F9007199254740993")
|
|
263
|
+
expect(recompute(l, result).ok).toBe(true)
|
|
264
|
+
})
|
|
265
|
+
|
|
266
|
+
test("dimension 20: supplied expression operand set must match formula leaves", () => {
|
|
267
|
+
for (const operands of [["F2"], ["F1", "F2"], ["F1", "F1"], [], null]) refused(book(), { op: "expr", formula: "F1", unit: "m", operands })
|
|
268
|
+
expect(row(book(), { op: "expr", formula: "F1 + F2", operands: ["F2", "F1"], unit: "m" }).value).toBe("4")
|
|
269
|
+
})
|
|
270
|
+
|
|
271
|
+
test("dimension 21: expression root and literal precision remain explicit", () => {
|
|
272
|
+
const l = book(["1000", "3"], ["m", "m"])
|
|
273
|
+
expect(row(l, { op: "expr", formula: "F1 - F2", unit: "m" }).value).toBe("997")
|
|
274
|
+
expect(row(l, { op: "expr", formula: "(F1 - F2) * 1", unit: "m" }).value).toBe("1000")
|
|
275
|
+
expect(row(l, { op: "expr", formula: "(F1 - F2) * 1.00", unit: "m" }).value).toBe("1000")
|
|
276
|
+
})
|
|
277
|
+
|
|
278
|
+
test("dimension 22: cloned snapshots are deterministic and replay appends a new identity", () => {
|
|
279
|
+
const original = book()
|
|
280
|
+
const left = structuredClone(original)
|
|
281
|
+
const right = structuredClone(original)
|
|
282
|
+
expect(row(left, sum)).toEqual(row(right, sum))
|
|
283
|
+
expect(row(left, sum).fact_id).toBe("F4")
|
|
284
|
+
expect(original.rows).toHaveLength(2)
|
|
285
|
+
})
|
|
286
|
+
|
|
287
|
+
test("dimension 23: coherent trusted input rewrites are a documented arithmetic limitation", () => {
|
|
288
|
+
const l = book(["0", "3"])
|
|
289
|
+
const km = row(l, { op: "convert", operands: ["F1"], to: "km" })
|
|
290
|
+
const cm = row(l, { op: "convert", operands: ["F1"], to: "cm" })
|
|
291
|
+
const coherent = { ...cm, fact_id: km.fact_id, label: km.label }
|
|
292
|
+
expect(recompute(l, coherent).ok).toBe(true)
|
|
293
|
+
expect(recompute(l, { ...km, unit: "cm" }).ok).toBe(false)
|
|
294
|
+
})
|
|
295
|
+
|
|
296
|
+
test("dimension 24: sum permutation and ordered arithmetic keep their own semantics", () => {
|
|
297
|
+
const l = book(["2", "5"])
|
|
298
|
+
for (const operands of [["F1", "F2"], ["F2", "F1"]]) expect(row(l, { ...sum, operands }).exact).toEqual({ num: "7", den: "1" })
|
|
299
|
+
expect(row(l, { ...sum, op: "diff" }).exact).toEqual({ num: "-3", den: "1" })
|
|
300
|
+
expect(row(l, { ...sum, op: "diff", operands: ["F2", "F1"] }).exact).toEqual({ num: "3", den: "1" })
|
|
301
|
+
})
|
|
302
|
+
|
|
303
|
+
test("dimension 25: numeric domain boundaries do not forbid negative ordinary arithmetic", () => {
|
|
304
|
+
for (const values of [["6", "5"], ["-1", "5"], ["0", "0"]]) expect(derive(book(values), { op: "share", part: "F1", whole: "F2", unit: "%" }).error).toBeDefined()
|
|
305
|
+
expect(row(book(["-2", "-5"]), sum).exact).toEqual({ num: "-7", den: "1" })
|
|
306
|
+
expect(derive(book(), { op: "expr", formula: "F1 / (F2 - F2)", unit: null }).error).toBeDefined()
|
|
307
|
+
})
|
|
308
|
+
|
|
309
|
+
test("dimension 26: percent products and pct nodes preserve fraction semantics", () => {
|
|
310
|
+
const l = book(["20", "50"], ["%", "%"])
|
|
311
|
+
expect(row(l, { op: "expr", formula: "pct(F1 * F2)", unit: "%" }).exact).toEqual({ num: "10", den: "1" })
|
|
312
|
+
expect(row(l, { op: "diff", operands: ["F2", "F1"], unit: "%p" }).exact).toEqual({ num: "30", den: "1" })
|
|
313
|
+
expect(derive(l, { op: "expr", formula: "pct(F1)", unit: "%" }).error).toBeDefined()
|
|
314
|
+
})
|
|
315
|
+
|
|
316
|
+
test("dimension 27: conversion maps compose with exact independent rational arithmetic", () => {
|
|
317
|
+
const groups = [["mm", "cm", "m", "km"], ["g", "kg", "t"], ["ms", "s", "min", "h"], ["B", "KB", "MB", "GB", "TB"], ["C", "F"], ["%", "\u2030"], ["KRW", "KRW-e3", "KRW-e4", "KRW-e8", "KRW-e12"]]
|
|
318
|
+
const apply = (factor: { num: bigint; den: bigint; offset?: { num: bigint; den: bigint } } | null, x: [bigint, bigint]): [bigint, bigint] => {
|
|
319
|
+
if (!factor) throw Error("missing conversion")
|
|
320
|
+
const o = factor.offset ?? { num: 0n, den: 1n }
|
|
321
|
+
return [x[0] * factor.num * o.den + o.num * x[1] * factor.den, x[1] * factor.den * o.den]
|
|
322
|
+
}
|
|
323
|
+
for (const group of groups) for (const a of group) for (const b of group) for (const c of group) {
|
|
324
|
+
for (const value of [-3n, 0n, 7n]) {
|
|
325
|
+
const direct = apply(convertFactor(a, c), [value, 1n])
|
|
326
|
+
const chained = apply(convertFactor(b, c), apply(convertFactor(a, b), [value, 1n]))
|
|
327
|
+
expect(direct[0] * chained[1]).toBe(chained[0] * direct[1])
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
expect(derive(book(), { op: "convert", operands: ["F1"], to: "furlong" }).error).toBeDefined()
|
|
331
|
+
})
|
|
332
|
+
|
|
333
|
+
test("dimension 28: grammar rejects injection and preserves precedence", () => {
|
|
334
|
+
for (const formula of ["F1;globalThis.x=1", "-F1", "F1 ** 2", "pct F1", "(F1", "F1 F2"]) expect(derive(book(), { op: "expr", formula, unit: "m" }).error).toBeDefined()
|
|
335
|
+
expect(row(book(), { op: "expr", formula: "F1 + F2 * 2", unit: "m" }).exact).toEqual({ num: "7", den: "1" })
|
|
336
|
+
})
|
|
337
|
+
|
|
338
|
+
test("dimension 29: signed rounding ties and canonical display are exact", () => {
|
|
339
|
+
for (const [values, expected] of [[["-1.25", "-2.5"], "-3.8"], [["1.25", "2.5"], "3.8"], [["999.5", "0"], "1000"]] as const) {
|
|
340
|
+
expect(row(book([...values]), sum).value).toBe(expected)
|
|
341
|
+
}
|
|
342
|
+
const l = book(["20", "15"], ["%", "%"])
|
|
343
|
+
const delta = row(l, { op: "diff", operands: ["F1", "F2"], unit: "%p" })
|
|
344
|
+
expect(recompute(l, { ...delta, render: "5 pp" }).ok).toBe(false)
|
|
345
|
+
})
|
|
346
|
+
|
|
347
|
+
test("dimension 30: seeded factor trees have an independent arithmetic and unit oracle", () => {
|
|
348
|
+
let seed = 0x11009
|
|
349
|
+
const next = () => { seed = (Math.imul(seed, 1664525) + 1013904223) >>> 0; return seed }
|
|
350
|
+
const atoms = ["m", "s", "person"]
|
|
351
|
+
for (let i = 0; i < 100; i++) {
|
|
352
|
+
const counts = [0, 0, 0]
|
|
353
|
+
const factors = Array.from({ length: 5 }, () => {
|
|
354
|
+
const index = next() % atoms.length
|
|
355
|
+
const exponent = (next() % 7) - 3
|
|
356
|
+
counts[index] += exponent
|
|
357
|
+
return `(${atoms[index]}^${exponent})`
|
|
358
|
+
})
|
|
359
|
+
const side = (positive: boolean) => atoms.map((a, index) => ({ a, n: counts[index] })).filter(({ n }) => positive ? n > 0 : n < 0).sort((a, b) => a.a.localeCompare(b.a)).map(({ a, n }) => Math.abs(n) === 1 ? a : `${a}^${Math.abs(n)}`)
|
|
360
|
+
const top = side(true).join("*") || "1"
|
|
361
|
+
const bottom = side(false)
|
|
362
|
+
const expectedUnit = bottom.length ? `${top}/${bottom.length === 1 ? bottom[0] : `(${bottom.join("*")})`}` : top === "1" ? null : top
|
|
363
|
+
expect(canonicalUnit(factors.join("*"))).toBe(expectedUnit)
|
|
364
|
+
const a = BigInt(1 + next() % 100)
|
|
365
|
+
const b = BigInt(1 + next() % 100)
|
|
366
|
+
const l = book([String(a), String(b)], [factors.join("*"), "person"])
|
|
367
|
+
const result = row(l, { op: "expr", formula: "F1 * F2 / F2", unit: expectedUnit })
|
|
368
|
+
expect(result.exact).toEqual({ num: String(a), den: "1" })
|
|
369
|
+
expect(result.unit).toBe(expectedUnit)
|
|
370
|
+
expect(recompute(l, result).ok).toBe(true)
|
|
371
|
+
expect(recompute(l, { ...result, exact: { num: String(a + 1n), den: "1" } }).ok).toBe(false)
|
|
372
|
+
}
|
|
373
|
+
})
|
|
374
|
+
|
|
375
|
+
test("dimension 30: independent rational oracle crosses every operation with metadata and tampering", () => {
|
|
376
|
+
let state = 3917
|
|
377
|
+
const next = () => { state = (Math.imul(state, 1103515245) + 12345) >>> 0; return state }
|
|
378
|
+
const exact = (n: bigint, d = 1n) => {
|
|
379
|
+
let a = n < 0n ? -n : n
|
|
380
|
+
let b = d
|
|
381
|
+
while (b) [a, b] = [b, a % b]
|
|
382
|
+
return { num: String(n / a), den: String(d / a) }
|
|
383
|
+
}
|
|
384
|
+
for (let i = 0; i < 50; i++) {
|
|
385
|
+
const a = BigInt(1 + next() % 50)
|
|
386
|
+
const b = a + BigInt(1 + next() % 50)
|
|
387
|
+
const meta = { population: { country: "x", cohort: i }, accounting_basis: "cash", snapshot_window: "2026", entity: "acme" }
|
|
388
|
+
const specs: Array<[Row, { num: string; den: string }]> = [
|
|
389
|
+
[{ ...sum }, exact(a + b)],
|
|
390
|
+
[{ ...sum, op: "diff" }, exact(a - b)],
|
|
391
|
+
[{ op: "ratio", num: "F1", den: "F2", unit: null }, exact(a, b)],
|
|
392
|
+
[{ op: "share", part: "F1", whole: "F2", unit: "%" }, exact(100n * a, b)],
|
|
393
|
+
[{ op: "growth", from: "F1", to: "F2", unit: "%" }, exact(100n * (b - a), a)],
|
|
394
|
+
[{ op: "convert", operands: ["F1"], to: "cm" }, exact(100n * a)],
|
|
395
|
+
[{ op: "expr", formula: "F1 * 2 / F2", unit: null }, exact(2n * a, b)],
|
|
396
|
+
]
|
|
397
|
+
for (const [spec, expected] of specs) {
|
|
398
|
+
const l = book([String(a), String(b)], ["metre", "m"], [{ ...meta, valid_at: "2025" }, { ...meta, valid_at: "2026" }])
|
|
399
|
+
const result = row(l, spec)
|
|
400
|
+
expect(result.exact).toEqual(expected)
|
|
401
|
+
expect(recompute(l, result).ok).toBe(true)
|
|
402
|
+
expect(recompute(l, { ...result, unit: "person" }).ok).toBe(false)
|
|
403
|
+
expect(recompute(l, { ...result, exact: { num: String(BigInt(expected.num) + 1n), den: expected.den } }).ok).toBe(false)
|
|
404
|
+
}
|
|
405
|
+
const l = book([String(100n * a), String(b)], ["bp/person", "person"])
|
|
406
|
+
const result = row(l, { op: "expr", formula: "F1 * F2", unit: "%p" })
|
|
407
|
+
expect(result.exact).toEqual(exact(a * b))
|
|
408
|
+
}
|
|
409
|
+
})
|