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,250 @@
|
|
|
1
|
+
// fact-ledger-kinds.mjs - the per-kind column rules of the fact ledger as one table, KIND_RULES.
|
|
2
|
+
// validateLedger checks every per-kind column through kindRuleErrors, the writers read their
|
|
3
|
+
// per-kind choices (id prefix, measured exact) from it, and the per-kind table in
|
|
4
|
+
// references/fact-ledger-grammar.md is rendered from it (`node fact-ledger-kinds.mjs --render-grammar`).
|
|
5
|
+
// Also the value primitives the schema shares. Pure functions with no IO; node: builtins only.
|
|
6
|
+
|
|
7
|
+
import { isCliEntry } from "./entry-guard.mjs"
|
|
8
|
+
import { canonicalUnit, inputScale, RuleError } from "./unit-algebra.mjs"
|
|
9
|
+
import { metadataErrors } from "./unit-metadata.mjs"
|
|
10
|
+
import { mul } from "./rational.mjs"
|
|
11
|
+
import { placesOf, roundPlaces, toBig } from "./unit-precision.mjs"
|
|
12
|
+
import { OPERATION_RULES } from "./unit-rules.mjs"
|
|
13
|
+
|
|
14
|
+
export const DECIMAL = /^-?\d+(?:\.\d+)?$/
|
|
15
|
+
const INTEGER = /^-?\d+$/
|
|
16
|
+
const SHA256 = /^[0-9a-f]{64}$/
|
|
17
|
+
const SOURCE_ID = /^S\d+$/
|
|
18
|
+
|
|
19
|
+
export const isObject = (value) => value !== null && typeof value === "object" && !Array.isArray(value)
|
|
20
|
+
export const isString = (value) => typeof value === "string"
|
|
21
|
+
export const isNonEmpty = (value) => typeof value === "string" && value.length > 0
|
|
22
|
+
export const isCount = (value) => Number.isInteger(value) && value >= 0
|
|
23
|
+
const isDecimal = (value) => isString(value) && DECIMAL.test(value)
|
|
24
|
+
export function isRational(exact) {
|
|
25
|
+
if (!isObject(exact) || Object.keys(exact).sort().join(",") !== "den,num" || !isString(exact.num) || !isString(exact.den) || !INTEGER.test(exact.num) || !/^[1-9]\d*$/.test(exact.den)) return false
|
|
26
|
+
let a = BigInt(exact.num)
|
|
27
|
+
let b = BigInt(exact.den)
|
|
28
|
+
if (String(a) !== exact.num) return false
|
|
29
|
+
if (a < 0n) a = -a
|
|
30
|
+
while (b !== 0n) [a, b] = [b, a % b]
|
|
31
|
+
return a === 1n
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** The exact rational of a decimal string, reduced. */
|
|
35
|
+
export function exactFromDecimal(value) {
|
|
36
|
+
const [whole, fraction = ""] = value.replace(/^-/, "").split(".")
|
|
37
|
+
let num = BigInt(whole + fraction)
|
|
38
|
+
let den = 10n ** BigInt(fraction.length)
|
|
39
|
+
let a = num
|
|
40
|
+
let b = den
|
|
41
|
+
while (b !== 0n) [a, b] = [b, a % b]
|
|
42
|
+
if (a > 1n) [num, den] = [num / a, den / a]
|
|
43
|
+
return { num: `${value.startsWith("-") && num !== 0n ? "-" : ""}${num}`, den: `${den}` }
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** @returns {string[]} what is wrong with a locator object, each message prefixed with `at`. */
|
|
47
|
+
export function locatorErrors(locator, at) {
|
|
48
|
+
const errors = []
|
|
49
|
+
if (!isObject(locator)) return errors
|
|
50
|
+
if (!isNonEmpty(locator.artifact)) errors.push(`${at}.artifact must be a non-empty string`)
|
|
51
|
+
if (!(isCount(locator.char_start) && isCount(locator.char_end) && locator.char_start <= locator.char_end)) errors.push(`${at}.char_start/char_end must be integers with start <= end`)
|
|
52
|
+
for (const key of ["page", "line"]) {
|
|
53
|
+
if (locator[key] !== undefined && !(Number.isInteger(locator[key]) && locator[key] >= 1)) errors.push(`${at}.${key} must be an integer >= 1`)
|
|
54
|
+
}
|
|
55
|
+
for (const key of ["table", "row_label", "col_label"]) {
|
|
56
|
+
if (locator[key] !== undefined && !isString(locator[key])) errors.push(`${at}.${key} must be a string`)
|
|
57
|
+
}
|
|
58
|
+
return errors
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const isLocator = (value) => isObject(value) && locatorErrors(value, "").length === 0
|
|
62
|
+
const orNull = (test) => (value, row) => value === null || test(value, row)
|
|
63
|
+
const locatorDetail = (value, at) => locatorErrors(value, at)
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Every rule a KIND_RULES cell may name: `cell` is its text in the grammar table, `says` ends the
|
|
67
|
+
* error message, `test(value, row)` decides it, and `prefix` (id rules only) is what nextId uses.
|
|
68
|
+
*/
|
|
69
|
+
export const RULES = Object.freeze({
|
|
70
|
+
f_id: { cell: "`F<n>`", says: "F<n>", prefix: "F", test: (v) => isString(v) && /^F\d+$/.test(v) },
|
|
71
|
+
q_id: { cell: "`Q<n>`", says: "Q<n>", prefix: "Q", test: (v) => isString(v) && /^Q\d+$/.test(v) },
|
|
72
|
+
decimal: { cell: "decimal", says: "a decimal string", test: isDecimal },
|
|
73
|
+
null: { cell: "null", says: "null", test: (v) => v === null },
|
|
74
|
+
source: { cell: "`S<n>`", says: "S<n>", test: (v) => isString(v) && SOURCE_ID.test(v) },
|
|
75
|
+
source_or_null: { cell: "`S<n>` or null", says: "S<n> or null", test: orNull((v) => isString(v) && SOURCE_ID.test(v)) },
|
|
76
|
+
locator: { cell: "object", says: "a locator object", test: isLocator, detail: locatorDetail },
|
|
77
|
+
locator_or_null: { cell: "object or null", says: "a locator object or null", test: orNull(isLocator), detail: locatorDetail },
|
|
78
|
+
sha256: { cell: "sha256", says: "a sha256 hex digest", test: (v) => isString(v) && SHA256.test(v) },
|
|
79
|
+
sha256_or_null: { cell: "sha256 or null", says: "a sha256 hex digest or null", test: orNull((v) => isString(v) && SHA256.test(v)) },
|
|
80
|
+
string_or_null: { cell: "string or null", says: "null or a string", test: orNull(isString) },
|
|
81
|
+
non_empty: { cell: "non-empty", says: "a non-empty string (required for this kind)", test: isNonEmpty },
|
|
82
|
+
from_value: {
|
|
83
|
+
cell: "from `value`",
|
|
84
|
+
says: "the reduced exact rational of value",
|
|
85
|
+
test: (v, row) => isRational(v) && (!isDecimal(row.value) || (v.num === exactFromDecimal(row.value).num && v.den === exactFromDecimal(row.value).den)),
|
|
86
|
+
},
|
|
87
|
+
rational_or_null: { cell: "rational or null", says: "null or { num: integer string, den: positive integer string }", test: orNull(isRational) },
|
|
88
|
+
by_derive: { cell: "reduced rational", says: "a reduced { num: integer string, den: positive integer string }", test: isRational },
|
|
89
|
+
context_by_derive: { cell: "written by derive (object or null)", says: "null or an object", test: orNull(isObject) },
|
|
90
|
+
derived_operation: { cell: "sum/diff/growth/share/ratio/expr", says: "a derived operation", test: (v) => Object.hasOwn(OPERATION_RULES, v) && OPERATION_RULES[v].kind === "derived" },
|
|
91
|
+
convert_operation: { cell: "`convert`", says: "convert", test: (v) => Object.hasOwn(OPERATION_RULES, v) && OPERATION_RULES[v].kind === "converted" },
|
|
92
|
+
precision: { cell: "`{ places, sigfigs }`", says: "nonnegative places and positive sigfigs", test: (v) => isObject(v) && Object.keys(v).sort().join(",") === "places,sigfigs" && Number.isSafeInteger(v.places) && v.places >= 0 && Number.isSafeInteger(v.sigfigs) && v.sigfigs >= 1 },
|
|
93
|
+
compatibility: { cell: "checked or unchecked (expr)", says: "the operation's compatibility", test: (v, r) => Object.hasOwn(OPERATION_RULES, r.operation) && v === OPERATION_RULES[r.operation].compatibility },
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
/** The grammar's per-kind columns, in table order; `exact` follows `value` because it reads it. */
|
|
97
|
+
export const KIND_COLUMNS = Object.freeze(["fact_id", "value", "source_id", "locator", "snapshot_sha256", "rationale", "exact", "context", "operation", "precision", "compatibility"])
|
|
98
|
+
|
|
99
|
+
const sourced = { source_id: "source", locator: "locator", snapshot_sha256: "sha256" }
|
|
100
|
+
const unsourced = { source_id: "source_or_null", locator: "locator_or_null", snapshot_sha256: "sha256_or_null" }
|
|
101
|
+
const row = (cells) => Object.freeze(Object.fromEntries(KIND_COLUMNS.map((column) => [column, cells[column] ?? (["precision", "compatibility"].includes(column) && cells.operation !== "null" ? column : "null")])))
|
|
102
|
+
|
|
103
|
+
export const KIND_RULES = Object.freeze({
|
|
104
|
+
measured: row({ fact_id: "f_id", value: "decimal", ...sourced, rationale: "string_or_null", exact: "from_value", context: "null", operation: "null" }),
|
|
105
|
+
quote: row({ fact_id: "q_id", value: "null", ...sourced, rationale: "string_or_null", exact: "null", context: "null", operation: "null" }),
|
|
106
|
+
derived: row({ fact_id: "f_id", value: "decimal", ...unsourced, rationale: "string_or_null", exact: "by_derive", context: "context_by_derive", operation: "derived_operation" }),
|
|
107
|
+
assumed: row({ fact_id: "f_id", value: "decimal", ...unsourced, rationale: "non_empty", exact: "rational_or_null", context: "null", operation: "null" }),
|
|
108
|
+
converted: row({ fact_id: "f_id", value: "decimal", ...unsourced, rationale: "string_or_null", exact: "by_derive", context: "context_by_derive", operation: "convert_operation" }),
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
/** The id prefix nextId assigns for a kind; an unknown kind gets F and fails validation later. */
|
|
112
|
+
export function idPrefix(kind) {
|
|
113
|
+
return RULES[KIND_RULES[kind]?.fact_id ?? "f_id"].prefix
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** Whether addRow computes the row's exact from its decimal value. */
|
|
117
|
+
export function exactComesFromValue(kind) {
|
|
118
|
+
return KIND_RULES[kind]?.exact === "from_value"
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/** Columns added after schema version 1 shipped; a ledger written before them still loads (dimension 31). */
|
|
122
|
+
export const LATER_COLUMNS = Object.freeze(["context", "precision", "compatibility"])
|
|
123
|
+
const DERIVED_KINDS = Object.freeze(["derived", "converted"])
|
|
124
|
+
const PRE_REGISTRY_COLUMNS = KIND_COLUMNS.slice(0, KIND_COLUMNS.indexOf("exact") + 1)
|
|
125
|
+
|
|
126
|
+
/** A derived or converted row written before the later columns existed: loadable, never an operand. */
|
|
127
|
+
export const isLegacyDerived = (row) => isObject(row) && DERIVED_KINDS.includes(row.kind) && LATER_COLUMNS.every((column) => !Object.hasOwn(row, column))
|
|
128
|
+
/** A derived or converted row carrying this version's columns: the only rows checked strictly at read time. */
|
|
129
|
+
export const isCurrentDerived = (row) => isObject(row) && DERIVED_KINDS.includes(row.kind) && !isLegacyDerived(row)
|
|
130
|
+
|
|
131
|
+
/** An exact carrying keys beyond num and den: never normalized, never silently stripped, flagged on use. */
|
|
132
|
+
export const hasUnknownExactKeys = (exact) => isObject(exact) && isString(exact.num) && isString(exact.den) && Object.keys(exact).sort().join(",") !== "den,num"
|
|
133
|
+
/** Any integer pair dev's writer accepted, reduced: sign on num, no leading zeros, gcd 1; anything else as given. */
|
|
134
|
+
function readExact(exact) {
|
|
135
|
+
if (!isObject(exact) || hasUnknownExactKeys(exact) || !isString(exact.num) || !isString(exact.den) || !INTEGER.test(exact.num) || !/^\d+$/.test(exact.den) || /^0+$/.test(exact.den)) return exact
|
|
136
|
+
const { num, den } = mul({ num: BigInt(exact.num), den: BigInt(exact.den) }, 1n)
|
|
137
|
+
return { num: String(num), den: String(den) }
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const readDecimal = (value) => (isDecimal(value) ? value.replace(/^(-?)0+(?=\d)/, "$1") : value)
|
|
141
|
+
|
|
142
|
+
/** A source unit read through the alias table; unknown, scaled, or unparseable text stays as given. */
|
|
143
|
+
export function sourceUnit(unit) {
|
|
144
|
+
try {
|
|
145
|
+
const canonical = canonicalUnit(unit)
|
|
146
|
+
return isString(canonical) && !inputScale(unit) ? canonical : unit
|
|
147
|
+
} catch (error) {
|
|
148
|
+
if (error instanceof RuleError) return unit
|
|
149
|
+
throw error
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* A row as every reader sees it (dimension 31 wins at read time): rows not written by derive in this
|
|
155
|
+
* version get reduced exacts and decimals; source rows also get canonical units and null later columns.
|
|
156
|
+
* Current derived rows stay as stored so tampering is still detected. Idempotent.
|
|
157
|
+
*/
|
|
158
|
+
export function readRow(row) {
|
|
159
|
+
if (!isObject(row) || !Object.hasOwn(KIND_RULES, row.kind) || isCurrentDerived(row)) return row
|
|
160
|
+
const read = { ...row, value: readDecimal(row.value), exact: exactComesFromValue(row.kind) ? row.exact : readExact(row.exact) }
|
|
161
|
+
if (DERIVED_KINDS.includes(row.kind)) return read
|
|
162
|
+
read.unit = sourceUnit(row.unit)
|
|
163
|
+
for (const column of LATER_COLUMNS) if (!Object.hasOwn(read, column)) read[column] = null
|
|
164
|
+
return read
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const carriesDerivation = (row) => row.operation !== null || row.operands?.length || row.derived_from?.length || row.formula !== null || row.factor !== null
|
|
168
|
+
|
|
169
|
+
/** Dimension 32: a stored exact must equal the row's own value at the value's decimal places. */
|
|
170
|
+
function valueExactDisagreement(row) {
|
|
171
|
+
const exact = readExact(row.exact)
|
|
172
|
+
const value = readDecimal(row.value)
|
|
173
|
+
if (!isRational(exact) || !isDecimal(value) || roundPlaces(toBig(exact), placesOf(value)) === value) return null
|
|
174
|
+
return `value ${value} disagrees with exact ${exact.num}/${exact.den} at ${placesOf(value)} decimal places`
|
|
175
|
+
}
|
|
176
|
+
function unitFlaw(unit) {
|
|
177
|
+
try {
|
|
178
|
+
return isString(unit) && (canonicalUnit(unit) !== unit || inputScale(unit)) ? `unit ${unit} has no canonical stored text` : null
|
|
179
|
+
} catch (error) {
|
|
180
|
+
if (error instanceof RuleError) return `unit ${unit}: ${error.message}`
|
|
181
|
+
throw error
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* What a loadable source row (read through readRow) still breaks under the strict dimensions: dev's
|
|
187
|
+
* writer accepted it, so it loads, but derive and recompute refuse it as an operand with this shape.
|
|
188
|
+
* addRow refuses a new row that has any. Empty for derived and converted rows.
|
|
189
|
+
*/
|
|
190
|
+
export function readFlaws(row) {
|
|
191
|
+
if (!isObject(row) || DERIVED_KINDS.includes(row.kind)) return []
|
|
192
|
+
const flaws = []
|
|
193
|
+
if (carriesDerivation(row)) flaws.push({ shape: "dimension 1", detail: "a source row carries an operation or derivation fields", expected: "null operation and empty derivation fields on measured, quote, and assumed rows" })
|
|
194
|
+
const unit = unitFlaw(row.unit)
|
|
195
|
+
if (unit) flaws.push({ shape: "dimension 6", detail: unit, expected: "a registered or canonical unit without scale" })
|
|
196
|
+
for (const detail of metadataErrors(row)) flaws.push({ shape: "dimension 18", detail, expected: "present, type-valid metadata; explicit null when unknown" })
|
|
197
|
+
const unknownKeys = hasUnknownExactKeys(row.exact)
|
|
198
|
+
const disagreement = unknownKeys ? null : valueExactDisagreement(row)
|
|
199
|
+
if (unknownKeys) flaws.push({ shape: "dimension 7", detail: `exact has unknown keys: ${Object.keys(row.exact).filter((key) => key !== "num" && key !== "den").join(", ")}`, expected: "an exact with exactly num and den (the stored row is kept as written)" })
|
|
200
|
+
if (disagreement) flaws.push({ shape: "dimension 32", detail: disagreement, expected: "an exact equal to value at the value's decimal places" })
|
|
201
|
+
return flaws
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/** @returns {string[]} every per-kind column error of one row; empty for an unknown kind. */
|
|
205
|
+
export function kindRuleErrors(candidate, at) {
|
|
206
|
+
const rules = KIND_RULES[candidate.kind]
|
|
207
|
+
if (rules === undefined) return []
|
|
208
|
+
const errors = []
|
|
209
|
+
const legacy = isLegacyDerived(candidate)
|
|
210
|
+
const columns = legacy ? PRE_REGISTRY_COLUMNS : isCurrentDerived(candidate) ? KIND_COLUMNS : KIND_COLUMNS.filter((column) => column !== "operation")
|
|
211
|
+
for (const column of columns) {
|
|
212
|
+
const value = candidate[column] === undefined && LATER_COLUMNS.includes(column) ? null : candidate[column]
|
|
213
|
+
const rule = RULES[legacy && column === "exact" ? "rational_or_null" : rules[column]]
|
|
214
|
+
// An exact with unknown keys loads as written (never stripped); readFlaws refuses it at use and at write.
|
|
215
|
+
if (column === "exact" && hasUnknownExactKeys(value) && !isCurrentDerived(candidate)) continue
|
|
216
|
+
if (rule.test(value, candidate)) continue
|
|
217
|
+
errors.push(`${at}.${column} must be ${rule.says} for a ${candidate.kind} row`)
|
|
218
|
+
if (rule.detail) errors.push(...rule.detail(value, `${at}.${column}`))
|
|
219
|
+
}
|
|
220
|
+
return errors
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/** At-rest rules of current derived rows; source rows answer to readFlaws at use and at write instead. */
|
|
224
|
+
export function storedErrors(row) {
|
|
225
|
+
if (!isCurrentDerived(row)) return []
|
|
226
|
+
const errors = metadataErrors(row)
|
|
227
|
+
try {
|
|
228
|
+
if (canonicalUnit(row.unit) !== row.unit || inputScale(row.unit)) errors.push("unit must be canonical stored text")
|
|
229
|
+
} catch (error) { errors.push(error.message) }
|
|
230
|
+
return errors
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
export const KIND_TABLE_BEGIN = "<!-- kind-rules:begin (rendered from KIND_RULES; do not edit by hand) -->"
|
|
234
|
+
export const KIND_TABLE_END = "<!-- kind-rules:end -->"
|
|
235
|
+
|
|
236
|
+
/** The grammar's per-kind Markdown table, one line per column, ending in a newline. */
|
|
237
|
+
export function renderKindTable() {
|
|
238
|
+
const kinds = Object.keys(KIND_RULES)
|
|
239
|
+
const lines = [`| field | ${kinds.join(" | ")} |`, `|${"---|".repeat(kinds.length + 1)}`]
|
|
240
|
+
for (const column of KIND_COLUMNS) lines.push(`| \`${column}\` | ${kinds.map((kind) => RULES[KIND_RULES[kind][column]].cell).join(" | ")} |`)
|
|
241
|
+
return `${lines.join("\n")}\n`
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
if (isCliEntry(import.meta.url)) {
|
|
245
|
+
if (process.argv[2] === "--render-grammar" && process.argv.length === 3) process.stdout.write(renderKindTable())
|
|
246
|
+
else {
|
|
247
|
+
process.stderr.write("usage: node fact-ledger-kinds.mjs --render-grammar\n")
|
|
248
|
+
process.exitCode = 2
|
|
249
|
+
}
|
|
250
|
+
}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test"
|
|
2
|
+
import { spawnSync } from "node:child_process"
|
|
3
|
+
import { readFileSync } from "node:fs"
|
|
4
|
+
import { join } from "node:path"
|
|
5
|
+
|
|
6
|
+
import { KIND_COLUMNS, KIND_RULES as RULES_TABLE, KIND_TABLE_BEGIN, KIND_TABLE_END, exactFromDecimal, renderKindTable } from "./fact-ledger-kinds.mjs"
|
|
7
|
+
import { FACT_KINDS, LEDGER_SCHEMA_VERSION, ROW_DEFAULTS, validateLedger } from "./fact-ledger-schema.mjs"
|
|
8
|
+
|
|
9
|
+
// Property tests generated from KIND_RULES: for every kind, rows built from each column's rule
|
|
10
|
+
// validate, and breaking exactly one column's rule is rejected with an error naming that column.
|
|
11
|
+
|
|
12
|
+
/** mulberry32: a fixed seed gives the same rows on every run. */
|
|
13
|
+
function prng(seed: number) {
|
|
14
|
+
let state = seed >>> 0
|
|
15
|
+
return () => {
|
|
16
|
+
state = (state + 0x6d2b79f5) >>> 0
|
|
17
|
+
let t = state
|
|
18
|
+
t = Math.imul(t ^ (t >>> 15), t | 1)
|
|
19
|
+
t ^= t + Math.imul(t ^ (t >>> 7), t | 61)
|
|
20
|
+
return ((t ^ (t >>> 14)) >>> 0) / 4294967296
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const KIND_RULES = RULES_TABLE as Record<string, Record<string, string>>
|
|
25
|
+
|
|
26
|
+
type Random = () => number
|
|
27
|
+
const int = (random: Random, max: number) => Math.floor(random() * max)
|
|
28
|
+
const pick = <T>(random: Random, list: T[]): T => list[int(random, list.length)]
|
|
29
|
+
|
|
30
|
+
function decimal(random: Random) {
|
|
31
|
+
const whole = `${pick(random, ["-", ""])}${int(random, 5000)}`
|
|
32
|
+
return random() < 0.5 ? whole : `${whole}.${String(int(random, 1000)).padStart(pick(random, [1, 2, 3]), "0").slice(0, 3)}`
|
|
33
|
+
}
|
|
34
|
+
const rational = (random: Random) => exactFromDecimal(decimal(random))
|
|
35
|
+
const locator = (random: Random) => ({ artifact: `sources/S${1 + int(random, 9)}/text.txt`, char_start: 3, char_end: 3 + int(random, 80) })
|
|
36
|
+
const sha = (random: Random) => Array.from({ length: 64 }, () => "0123456789abcdef"[int(random, 16)]).join("")
|
|
37
|
+
|
|
38
|
+
type Row = Record<string, unknown> & { value: string }
|
|
39
|
+
type Gen = { valid: (random: Random, row: Row) => unknown; invalid: (random: Random, row: Row) => unknown }
|
|
40
|
+
|
|
41
|
+
/** One valid and one invalid generator per rule name; an unknown rule name fails the test below. */
|
|
42
|
+
const GENERATORS: Record<string, Gen> = {
|
|
43
|
+
f_id: { valid: (r) => `F${1 + int(r, 99)}`, invalid: (r) => `Q${1 + int(r, 99)}` },
|
|
44
|
+
q_id: { valid: (r) => `Q${1 + int(r, 99)}`, invalid: (r) => `F${1 + int(r, 99)}` },
|
|
45
|
+
decimal: { valid: decimal, invalid: (r) => pick(r, ["1e3", "", null, 12, "1,000"]) },
|
|
46
|
+
null: { valid: () => null, invalid: (r) => pick(r, ["0", { num: "1", den: "1" }, "S1", "why"]) },
|
|
47
|
+
source: { valid: (r) => `S${1 + int(r, 9)}`, invalid: (r) => pick(r, [null, "F1", "S"]) },
|
|
48
|
+
source_or_null: { valid: (r) => pick(r, [null, `S${1 + int(r, 9)}`]), invalid: (r) => pick(r, ["F1", "S", 3]) },
|
|
49
|
+
locator: { valid: locator, invalid: (r) => pick(r, [null, { artifact: "", char_start: 0, char_end: 1 }, { artifact: "a", char_start: 5, char_end: 1 }]) },
|
|
50
|
+
locator_or_null: { valid: (r) => pick(r, [null, locator(r)]), invalid: (r) => pick(r, ["here", { artifact: "", char_start: 0, char_end: 1 }]) },
|
|
51
|
+
sha256: { valid: sha, invalid: (r) => pick(r, [null, "abc", "A".repeat(64)]) },
|
|
52
|
+
sha256_or_null: { valid: (r) => pick(r, [null, sha(r)]), invalid: (r) => pick(r, ["abc", 7]) },
|
|
53
|
+
string_or_null: { valid: (r) => pick(r, [null, "", "a reason"]), invalid: (r) => pick(r, [5, false, {}]) },
|
|
54
|
+
non_empty: { valid: () => "assumed from the filing year", invalid: (r) => pick(r, ["", null]) },
|
|
55
|
+
from_value: {
|
|
56
|
+
valid: (_r, row) => exactFromDecimal(row.value),
|
|
57
|
+
invalid: (r, row) => {
|
|
58
|
+
const exact = exactFromDecimal(row.value)
|
|
59
|
+
return pick(r, [null, { num: `${BigInt(exact.num) + 7n}`, den: exact.den }, { num: `${BigInt(exact.num) * 2n}`, den: `${BigInt(exact.den) * 2n}` }])
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
rational_or_null: { valid: (r) => pick(r, [null, rational(r)]), invalid: (r) => pick(r, [{ num: "1", den: "0" }, { num: 1, den: "2" }, "1/2"]) },
|
|
63
|
+
by_derive: { valid: rational, invalid: (r) => pick(r, [null, { num: "1", den: "0" }, { num: "x", den: "2" }, "1/2"]) },
|
|
64
|
+
context_by_derive: { valid: (_r, row) => row.kind === "converted" ? { target: "km" } : null, invalid: (r) => pick(r, ["km", 3, ["km"], false]) },
|
|
65
|
+
derived_operation: { valid: () => "expr", invalid: (r) => pick(r, [null, "convert", "mean"]) },
|
|
66
|
+
convert_operation: { valid: () => "convert", invalid: (r) => pick(r, [null, "sum", "mean"]) },
|
|
67
|
+
precision: { valid: () => ({ places: 2, sigfigs: 3 }), invalid: (r) => pick(r, [null, { places: -1, sigfigs: 3 }, { places: 2, sigfigs: 0 }]) },
|
|
68
|
+
compatibility: { valid: (_r, row) => row.operation === "expr" ? "unchecked" : "checked", invalid: () => "invalid" },
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function baseRow(kind: string): Row {
|
|
72
|
+
const row: Record<string, unknown> = { ...structuredClone(ROW_DEFAULTS), kind, raw: "raw", quote: "quoted text", extracted_by: "fact add", observed_at: "2026-10-23T08:00:00Z", label: "x" }
|
|
73
|
+
if (kind === "derived" || kind === "converted") Object.assign(row, { operands: ["F1000"], derived_from: ["F1000"], formula: "F1000", render: "raw" })
|
|
74
|
+
return row as Row
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** A row whose every KIND_COLUMNS field follows its kind's rule, in column order (exact after value). */
|
|
78
|
+
function validRow(kind: string, random: Random) {
|
|
79
|
+
const row = baseRow(kind)
|
|
80
|
+
for (const column of KIND_COLUMNS) row[column] = GENERATORS[KIND_RULES[kind][column]].valid(random, row)
|
|
81
|
+
return row
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const ledgerOf = (row: object) => ({ schemaVersion: LEDGER_SCHEMA_VERSION, rows: [row, { ...validRow("measured", prng(42)), fact_id: "F1000", label: "dependency" }], events: [] })
|
|
85
|
+
const SEEDS = Array.from({ length: 60 }, (_, index) => index + 1)
|
|
86
|
+
|
|
87
|
+
describe("KIND_RULES covers the grammar", () => {
|
|
88
|
+
test("every kind has a rule for every column, and every rule has a generator", () => {
|
|
89
|
+
expect(Object.keys(KIND_RULES)).toEqual([...FACT_KINDS])
|
|
90
|
+
for (const kind of FACT_KINDS) {
|
|
91
|
+
expect(Object.keys(KIND_RULES[kind])).toEqual([...KIND_COLUMNS])
|
|
92
|
+
for (const column of KIND_COLUMNS) expect(Object.hasOwn(GENERATORS, KIND_RULES[kind][column])).toBe(true)
|
|
93
|
+
}
|
|
94
|
+
})
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
describe("rows generated from KIND_RULES", () => {
|
|
98
|
+
for (const kind of FACT_KINDS) {
|
|
99
|
+
test(`${kind}: seeded valid rows validate`, () => {
|
|
100
|
+
for (const seed of SEEDS) {
|
|
101
|
+
const row = validRow(kind, prng(seed))
|
|
102
|
+
expect({ seed, errors: validateLedger(ledgerOf(row)) }).toEqual({ seed, errors: [] })
|
|
103
|
+
}
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
for (const column of KIND_COLUMNS) {
|
|
107
|
+
test(`${kind}: breaking only the ${column} rule (${KIND_RULES[kind][column]}) is rejected`, () => {
|
|
108
|
+
for (const seed of SEEDS) {
|
|
109
|
+
const random = prng(seed * 7919)
|
|
110
|
+
const row = validRow(kind, random)
|
|
111
|
+
row[column] = GENERATORS[KIND_RULES[kind][column]].invalid(random, row)
|
|
112
|
+
const errors = validateLedger(ledgerOf(row))
|
|
113
|
+
expect({ seed, value: row[column], named: errors.some((error: string) => error.startsWith(`rows[0].${column}`)) }).toEqual({ seed, value: row[column], named: true })
|
|
114
|
+
}
|
|
115
|
+
})
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
test("review MEDIUM: a measured row with value -0.50 and exact 7/1 is rejected", () => {
|
|
120
|
+
const row = { ...validRow("measured", prng(1)), value: "-0.50", exact: { num: "7", den: "1" } }
|
|
121
|
+
expect(validateLedger(ledgerOf(row)).some((error: string) => error.startsWith("rows[0].exact"))).toBe(true)
|
|
122
|
+
expect(validateLedger(ledgerOf({ ...row, exact: { num: "-1", den: "2" } }))).toEqual([])
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
test("review r6 LOW 3: context is a derived or converted column; a measured, quote, or assumed row carrying one is rejected", () => {
|
|
126
|
+
for (const kind of ["measured", "quote", "assumed"]) {
|
|
127
|
+
const row = { ...validRow(kind, prng(3)), context: { target: "km" } }
|
|
128
|
+
expect({ kind, named: validateLedger(ledgerOf(row)).some((error: string) => error.startsWith("rows[0].context")) }).toEqual({ kind, named: true })
|
|
129
|
+
}
|
|
130
|
+
expect(validateLedger(ledgerOf(validRow("converted", prng(4))))).toEqual([])
|
|
131
|
+
expect(validateLedger(ledgerOf({ ...validRow("derived", prng(4)), context: { target: "km" } })).length).toBeGreaterThan(0)
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
test("review LOW 1: a quote row with a non-null exact is rejected", () => {
|
|
135
|
+
const row = { ...validRow("quote", prng(2)), exact: { num: "1", den: "1" } }
|
|
136
|
+
expect(validateLedger(ledgerOf(row)).some((error: string) => error.startsWith("rows[0].exact"))).toBe(true)
|
|
137
|
+
})
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
describe("the grammar's per-kind table is rendered from KIND_RULES", () => {
|
|
141
|
+
const grammar = readFileSync(join(import.meta.dir, "..", "references", "fact-ledger-grammar.md"), "utf8")
|
|
142
|
+
|
|
143
|
+
test("the committed table section equals renderKindTable() byte for byte", () => {
|
|
144
|
+
const begin = grammar.indexOf(KIND_TABLE_BEGIN)
|
|
145
|
+
const end = grammar.indexOf(KIND_TABLE_END)
|
|
146
|
+
expect(begin).toBeGreaterThanOrEqual(0)
|
|
147
|
+
expect(end).toBeGreaterThan(begin)
|
|
148
|
+
expect(grammar.slice(begin + KIND_TABLE_BEGIN.length + 1, end)).toBe(renderKindTable())
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
test("node fact-ledger-kinds.mjs --render-grammar prints the same table", () => {
|
|
152
|
+
const result = spawnSync("node", [join(import.meta.dir, "fact-ledger-kinds.mjs"), "--render-grammar"], { encoding: "utf8" })
|
|
153
|
+
expect(result.status).toBe(0)
|
|
154
|
+
expect(result.stdout).toBe(renderKindTable())
|
|
155
|
+
})
|
|
156
|
+
})
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { describe, expect, onTestFinished, test } from "bun:test"
|
|
2
|
+
import { copyFileSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs"
|
|
3
|
+
import { tmpdir } from "node:os"
|
|
4
|
+
import { join } from "node:path"
|
|
5
|
+
|
|
6
|
+
import { derive, recompute } from "./fact-derive.mjs"
|
|
7
|
+
import { LEDGER_FILENAME, addRow, emptyLedger, readLedger, validateLedger, writeLedger } from "./fact-ledger.mjs"
|
|
8
|
+
|
|
9
|
+
// Generated by dev's own addRow (tests/fixtures/fidelity/gen-legacy-dev.mjs pins the dev commit):
|
|
10
|
+
// no context/precision/compatibility columns, alias units, unreduced and zero-padded exacts.
|
|
11
|
+
const FIXTURE = join(import.meta.dir, "tests", "fixtures", "fidelity", "ledger-legacy-dev.json")
|
|
12
|
+
const SOURCE_IDS = ["F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "Q1", "F9", "F10", "F11", "F12", "F13", "F16", "F17", "F18"]
|
|
13
|
+
|
|
14
|
+
type Row = Record<string, unknown>
|
|
15
|
+
type Ledger = { schemaVersion: number; rows: Row[]; events: unknown[] }
|
|
16
|
+
|
|
17
|
+
function tempDir(): string {
|
|
18
|
+
const dir = mkdtempSync(join(tmpdir(), "ulw-legacy-"))
|
|
19
|
+
onTestFinished(() => rmSync(dir, { recursive: true, force: true }))
|
|
20
|
+
return dir
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const fixture = (): Ledger => JSON.parse(readFileSync(FIXTURE, "utf8"))
|
|
24
|
+
const rowOf = (ledger: Ledger, id: string): Row => ledger.rows.find((row) => row.fact_id === id) as Row
|
|
25
|
+
|
|
26
|
+
function loaded(): { dir: string; ledger: Ledger } {
|
|
27
|
+
const dir = tempDir()
|
|
28
|
+
copyFileSync(FIXTURE, join(dir, LEDGER_FILENAME))
|
|
29
|
+
return { dir, ledger: readLedger(dir) as Ledger }
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
describe("ledgers written by dev's writer (dimension 31 wins at read time)", () => {
|
|
33
|
+
test("#given dev's generated ledger #when validated #then it has no errors", () => {
|
|
34
|
+
expect(validateLedger(fixture())).toEqual([])
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
test("#given dev's ledger #when read #then exacts, decimals, and source units are normalized", () => {
|
|
38
|
+
const { ledger } = loaded()
|
|
39
|
+
expect(["F1", "F3", "F4", "F5", "F6", "F8", "F11"].map((id) => rowOf(ledger, id).unit)).toEqual(["km", "%", "%p", "person", "KRW-e3", "%", "km"])
|
|
40
|
+
const exacts = Object.fromEntries(["F10", "F11", "F12", "F13", "F14", "F15", "F18"].map((id) => [id, rowOf(ledger, id).exact]))
|
|
41
|
+
expect(exacts).toEqual({ F10: { num: "1", den: "1" }, F11: { num: "5", den: "2" }, F12: { num: "7", den: "1" }, F13: { num: "-5", den: "2" }, F14: { num: "41", den: "2" }, F15: { num: "12000", den: "1" }, F18: { num: "3", den: "1", note: "from the brief" } })
|
|
42
|
+
expect(rowOf(ledger, "F7").value).toBe("7")
|
|
43
|
+
for (const id of SOURCE_IDS) {
|
|
44
|
+
for (const column of ["context", "precision", "compatibility"]) expect(rowOf(ledger, id)[column]).toBeNull()
|
|
45
|
+
}
|
|
46
|
+
for (const id of ["F14", "F15"]) expect(Object.hasOwn(rowOf(ledger, id), "precision")).toBe(false)
|
|
47
|
+
expect(validateLedger(ledger)).toEqual([])
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
test("#given dev's ledger #when measured rows are summed #then the new row is canonical and recomputes", () => {
|
|
51
|
+
for (const ledger of [fixture(), loaded().ledger]) {
|
|
52
|
+
const result = derive(ledger, { op: "sum", operands: ["F1", "F2"], unit: "km" })
|
|
53
|
+
expect(result.error).toBeUndefined()
|
|
54
|
+
expect(result.row).toMatchObject({ kind: "derived", unit: "km", value: "21", exact: { num: "41", den: "2" }, compatibility: "checked" })
|
|
55
|
+
const padded = derive(ledger, { op: "sum", operands: ["F1", "F7"], unit: "km" })
|
|
56
|
+
expect(padded.row).toMatchObject({ value: "19", exact: { num: "19", den: "1" } })
|
|
57
|
+
expect(validateLedger(ledger)).toEqual([])
|
|
58
|
+
expect(recompute(ledger, result.row).ok).toBe(true)
|
|
59
|
+
}
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
test("#given a legacy derived or converted row #when recomputed #then it reports a legacy derived row", () => {
|
|
63
|
+
for (const ledger of [fixture(), loaded().ledger]) {
|
|
64
|
+
for (const id of ["F14", "F15"]) {
|
|
65
|
+
const result = recompute(ledger, rowOf(ledger, id))
|
|
66
|
+
expect(result.ok).toBe(false)
|
|
67
|
+
expect(result.shape).toBe("legacy derived row")
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
test("#given a legacy derived or converted row #when used as an operand #then derive refuses it", () => {
|
|
73
|
+
const ledger = fixture()
|
|
74
|
+
const before = ledger.rows.length
|
|
75
|
+
for (const id of ["F14", "F15"]) {
|
|
76
|
+
const result = derive(ledger, { op: "sum", operands: [id, "F2"], unit: "km" })
|
|
77
|
+
expect(result.error).toBe("incompatible_operands")
|
|
78
|
+
expect(result.shape).toBe("legacy derived row")
|
|
79
|
+
}
|
|
80
|
+
expect(ledger.rows.length).toBe(before)
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
test("#given a loadable row the strict dimensions reject #when used as an operand #then it is flagged by dimension", () => {
|
|
84
|
+
const ledger = loaded().ledger
|
|
85
|
+
const before = ledger.rows.length
|
|
86
|
+
for (const [id, shape] of [["F16", "dimension 18"], ["F17", "dimension 1"]]) {
|
|
87
|
+
const result = derive(ledger, { op: "sum", operands: [id, "F2"], unit: "km" })
|
|
88
|
+
expect(result.error).toBe("incompatible_operands")
|
|
89
|
+
expect(result.shape).toBe(shape)
|
|
90
|
+
}
|
|
91
|
+
expect(ledger.rows.length).toBe(before)
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
test("#given a new source row with a flaw dev accepted #when added #then this version's writer refuses it", () => {
|
|
95
|
+
const ledger = fixture()
|
|
96
|
+
const blank = { ...rowOf(ledger, "F16"), fact_id: undefined, label: "blank-again" }
|
|
97
|
+
expect(() => addRow(ledger, blank)).toThrow("dimension 18")
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
test("#given dev's ledger #when read and written twice #then the second pass changes nothing", () => {
|
|
101
|
+
const { dir, ledger } = loaded()
|
|
102
|
+
writeLedger(dir, ledger)
|
|
103
|
+
const once = readFileSync(join(dir, LEDGER_FILENAME), "utf8")
|
|
104
|
+
const written = JSON.parse(once) as Ledger
|
|
105
|
+
expect(rowOf(written, "F11").exact).toEqual({ num: "5", den: "2" })
|
|
106
|
+
expect(Object.hasOwn(rowOf(written, "F14"), "precision")).toBe(false)
|
|
107
|
+
writeLedger(dir, readLedger(dir))
|
|
108
|
+
expect(readFileSync(join(dir, LEDGER_FILENAME), "utf8")).toBe(once)
|
|
109
|
+
expect(readLedger(dir)).toEqual(written)
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
test("#given a current derived row with its later columns stripped #when validated #then it reads as legacy and never recomputes", () => {
|
|
113
|
+
const ledger = fixture()
|
|
114
|
+
const made = derive(ledger, { op: "sum", operands: ["F1", "F2"], unit: "km" }).row as Row
|
|
115
|
+
const stripped = rowOf(ledger, made.fact_id as string)
|
|
116
|
+
for (const column of ["context", "precision", "compatibility"]) delete stripped[column]
|
|
117
|
+
stripped.unit = "kilometers"
|
|
118
|
+
expect(validateLedger(ledger)).toEqual([])
|
|
119
|
+
expect(recompute(ledger, stripped).shape).toBe("legacy derived row")
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
test("#given a ledger written by this version #when read and written #then it is unchanged", () => {
|
|
123
|
+
const dir = tempDir()
|
|
124
|
+
const ledger = emptyLedger() as Ledger
|
|
125
|
+
const source = { kind: "measured", raw: "", quote: "", source_id: "S1", locator: { artifact: "sources/S1/text.txt", char_start: 0, char_end: 4 }, snapshot_sha256: "b".repeat(64), extracted_by: "fact add --find", observed_at: "2026-10-23T08:00:00Z" }
|
|
126
|
+
addRow(ledger, { ...source, value: "3", unit: "kilometers", label: "a" })
|
|
127
|
+
addRow(ledger, { ...source, value: "4", unit: "km", label: "b" })
|
|
128
|
+
expect(derive(ledger, { op: "sum", operands: ["F1", "F2"], unit: "km" }).error).toBeUndefined()
|
|
129
|
+
writeLedger(dir, ledger)
|
|
130
|
+
const first = readFileSync(join(dir, LEDGER_FILENAME), "utf8")
|
|
131
|
+
writeLedger(dir, readLedger(dir))
|
|
132
|
+
expect(readFileSync(join(dir, LEDGER_FILENAME), "utf8")).toBe(first)
|
|
133
|
+
expect(readLedger(dir)).toEqual(JSON.parse(first))
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
test("#given a legacy exact with an unknown key #when read and written #then the key survives and the row is refused as an operand", () => {
|
|
137
|
+
const { dir, ledger } = loaded()
|
|
138
|
+
writeLedger(dir, ledger)
|
|
139
|
+
const written = JSON.parse(readFileSync(join(dir, LEDGER_FILENAME), "utf8")) as Ledger
|
|
140
|
+
expect(rowOf(written, "F18").exact).toEqual({ num: "3", den: "1", note: "from the brief" })
|
|
141
|
+
const result = derive(ledger, { op: "sum", operands: ["F18", "F2"], unit: "km" })
|
|
142
|
+
expect(result.error).toBe("incompatible_operands")
|
|
143
|
+
expect(result.shape).toBe("dimension 7")
|
|
144
|
+
expect(recompute(ledger, rowOf(ledger, "F18")).ok).toBe(false)
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
test("#given an assumed row whose value and exact disagree #when read #then it loads flagged and a new row is refused (dimension 32)", () => {
|
|
148
|
+
const dir = tempDir()
|
|
149
|
+
const seed = fixture()
|
|
150
|
+
const mismatched = { ...rowOf(seed, "F11"), fact_id: "F19", label: "mismatched-exact", unit: "km", value: "1.5", exact: { num: "3", den: "1" } }
|
|
151
|
+
seed.rows.push(mismatched)
|
|
152
|
+
writeFileSync(join(dir, LEDGER_FILENAME), JSON.stringify(seed))
|
|
153
|
+
const ledger = readLedger(dir) as Ledger
|
|
154
|
+
expect(rowOf(ledger, "F19").value).toBe("1.5")
|
|
155
|
+
const result = derive(ledger, { op: "sum", operands: ["F19", "F2"], unit: "km" })
|
|
156
|
+
expect(result.error).toBe("incompatible_operands")
|
|
157
|
+
expect(result.shape).toBe("dimension 32")
|
|
158
|
+
expect(() => addRow(ledger, { ...mismatched, fact_id: undefined, label: "mismatch-new" })).toThrow("dimension 32")
|
|
159
|
+
})
|
|
160
|
+
})
|