lazycodex-ai 5.1.48 → 5.1.51
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/dist/cli/index.js +1194 -752
- package/dist/cli/service-auth/accounts-gate.d.ts +11 -0
- package/dist/cli/service-auth/commands.d.ts +2 -4
- package/dist/cli/service-auth/index.d.ts +1 -1
- package/dist/cli/service-auth/keystore.d.ts +26 -2
- package/dist/cli/service-auth/protocol.d.ts +7 -1
- package/dist/cli/service-auth/session.d.ts +6 -0
- package/dist/cli/service-auth/signed-out-marker.d.ts +11 -0
- package/dist/cli-node/index.js +1194 -752
- package/package.json +3 -1
- package/packages/lsp-core/package.json +45 -0
- package/packages/lsp-core/src/index.ts +30 -0
- package/packages/lsp-core/src/lsp/bun-command-output.ts +82 -0
- package/packages/lsp-core/src/lsp/bun-project-checks.test.ts +170 -0
- package/packages/lsp-core/src/lsp/bun-project-checks.ts +223 -0
- package/packages/lsp-core/src/lsp/cleanup-errors.test.ts +18 -0
- package/packages/lsp-core/src/lsp/cleanup-errors.ts +14 -0
- package/packages/lsp-core/src/lsp/client-diagnostics-concurrency.integration.test.ts +52 -0
- package/packages/lsp-core/src/lsp/client-diagnostics-freshness.integration.test.ts +471 -0
- package/packages/lsp-core/src/lsp/client-diagnostics-pull-timeout.test.ts +64 -0
- package/packages/lsp-core/src/lsp/client-wrapper-outside-cwd.test.ts +78 -0
- package/packages/lsp-core/src/lsp/client-wrapper.test.ts +213 -0
- package/packages/lsp-core/src/lsp/client-wrapper.ts +290 -0
- package/packages/lsp-core/src/lsp/client.ts +398 -0
- package/packages/lsp-core/src/lsp/config-loader.ts +254 -0
- package/packages/lsp-core/src/lsp/connection.ts +95 -0
- package/packages/lsp-core/src/lsp/constants.ts +17 -0
- package/packages/lsp-core/src/lsp/controlled-clock-test-support.ts +90 -0
- package/packages/lsp-core/src/lsp/directory-diagnostics.test.ts +221 -0
- package/packages/lsp-core/src/lsp/directory-diagnostics.ts +187 -0
- package/packages/lsp-core/src/lsp/effective-extension.ts +10 -0
- package/packages/lsp-core/src/lsp/errors.ts +89 -0
- package/packages/lsp-core/src/lsp/fake-bun-test-support.ts +102 -0
- package/packages/lsp-core/src/lsp/fixtures/diagnostics-freshness-contract-probe.ts +283 -0
- package/packages/lsp-core/src/lsp/fixtures/disposed-stdin-epipe-probe.ts +28 -0
- package/packages/lsp-core/src/lsp/fixtures/workspace-edit-contract-probe.ts +196 -0
- package/packages/lsp-core/src/lsp/fixtures/workspace-edit-server.mjs +234 -0
- package/packages/lsp-core/src/lsp/format-document.test.ts +143 -0
- package/packages/lsp-core/src/lsp/format-document.ts +90 -0
- package/packages/lsp-core/src/lsp/formatters.ts +144 -0
- package/packages/lsp-core/src/lsp/infer-extension.ts +66 -0
- package/packages/lsp-core/src/lsp/json-rpc-connection-cancellation.test.ts +97 -0
- package/packages/lsp-core/src/lsp/json-rpc-connection.ts +369 -0
- package/packages/lsp-core/src/lsp/language-mappings.ts +173 -0
- package/packages/lsp-core/src/lsp/manager-lifecycle.test.ts +262 -0
- package/packages/lsp-core/src/lsp/manager-max-clients.test.ts +151 -0
- package/packages/lsp-core/src/lsp/manager.ts +495 -0
- package/packages/lsp-core/src/lsp/outside-context-workspace.ts +33 -0
- package/packages/lsp-core/src/lsp/process-pipe-errors.test.ts +21 -0
- package/packages/lsp-core/src/lsp/process-signal-cleanup.ts +21 -0
- package/packages/lsp-core/src/lsp/process.ts +221 -0
- package/packages/lsp-core/src/lsp/server-definitions.ts +205 -0
- package/packages/lsp-core/src/lsp/server-install-state.ts +67 -0
- package/packages/lsp-core/src/lsp/server-installation.test.ts +376 -0
- package/packages/lsp-core/src/lsp/server-installation.ts +218 -0
- package/packages/lsp-core/src/lsp/server-resolution-local-binary.test.ts +170 -0
- package/packages/lsp-core/src/lsp/server-resolution.ts +117 -0
- package/packages/lsp-core/src/lsp/startup-failure.ts +1 -0
- package/packages/lsp-core/src/lsp/timer-provider.ts +13 -0
- package/packages/lsp-core/src/lsp/transport-protocol.ts +52 -0
- package/packages/lsp-core/src/lsp/transport-request-timeout.test.ts +73 -0
- package/packages/lsp-core/src/lsp/transport.ts +339 -0
- package/packages/lsp-core/src/lsp/types.ts +134 -0
- package/packages/lsp-core/src/lsp/utils.test.ts +72 -0
- package/packages/lsp-core/src/lsp/utils.ts +40 -0
- package/packages/lsp-core/src/lsp/workspace-apply-edit-failure.ts +19 -0
- package/packages/lsp-core/src/lsp/workspace-apply-edit-lease.integration.test.ts +218 -0
- package/packages/lsp-core/src/lsp/workspace-apply-edit-sync.integration.test.ts +113 -0
- package/packages/lsp-core/src/lsp/workspace-apply-edit-test-support.ts +194 -0
- package/packages/lsp-core/src/lsp/workspace-apply-edit.integration.test.ts +163 -0
- package/packages/lsp-core/src/lsp/workspace-document-state.test.ts +150 -0
- package/packages/lsp-core/src/lsp/workspace-document-state.ts +392 -0
- package/packages/lsp-core/src/lsp/workspace-edit-adversarial.test.ts +132 -0
- package/packages/lsp-core/src/lsp/workspace-edit-commit.test.ts +140 -0
- package/packages/lsp-core/src/lsp/workspace-edit-commit.ts +220 -0
- package/packages/lsp-core/src/lsp/workspace-edit-contract-evidence.test.ts +56 -0
- package/packages/lsp-core/src/lsp/workspace-edit-contract-evidence.ts +30 -0
- package/packages/lsp-core/src/lsp/workspace-edit-fingerprint.ts +44 -0
- package/packages/lsp-core/src/lsp/workspace-edit-options.test.ts +147 -0
- package/packages/lsp-core/src/lsp/workspace-edit-parse-helpers.ts +59 -0
- package/packages/lsp-core/src/lsp/workspace-edit-parser.ts +130 -0
- package/packages/lsp-core/src/lsp/workspace-edit-path.ts +98 -0
- package/packages/lsp-core/src/lsp/workspace-edit-plan-types.ts +60 -0
- package/packages/lsp-core/src/lsp/workspace-edit-plan.ts +73 -0
- package/packages/lsp-core/src/lsp/workspace-edit-prevalidation.test.ts +174 -0
- package/packages/lsp-core/src/lsp/workspace-edit-resource-parser.ts +89 -0
- package/packages/lsp-core/src/lsp/workspace-edit-simulation.ts +183 -0
- package/packages/lsp-core/src/lsp/workspace-edit-snapshot.ts +53 -0
- package/packages/lsp-core/src/lsp/workspace-edit-text.ts +125 -0
- package/packages/lsp-core/src/lsp/workspace-edit-types.ts +121 -0
- package/packages/lsp-core/src/lsp/workspace-edit.characterization.test.ts +95 -0
- package/packages/lsp-core/src/lsp/workspace-edit.ts +55 -0
- package/packages/lsp-core/src/lsp/workspace-markers.ts +18 -0
- package/packages/lsp-core/src/lsp/workspace-mutation-controller.ts +182 -0
- package/packages/lsp-core/src/mcp-protocol-pin.test.ts +97 -0
- package/packages/lsp-core/src/mcp.ts +122 -0
- package/packages/lsp-core/src/missing-dependency-result.test.ts +135 -0
- package/packages/lsp-core/src/missing-dependency-result.ts +79 -0
- package/packages/lsp-core/src/post-edit/file-location.ts +55 -0
- package/packages/lsp-core/src/post-edit/index.ts +3 -0
- package/packages/lsp-core/src/post-edit/not-installed-guidance.ts +36 -0
- package/packages/lsp-core/src/post-edit/not-installed.test.ts +224 -0
- package/packages/lsp-core/src/post-edit/orchestration.test.ts +157 -0
- package/packages/lsp-core/src/post-edit/orchestration.ts +205 -0
- package/packages/lsp-core/src/request-context.test.ts +200 -0
- package/packages/lsp-core/src/request-context.ts +235 -0
- package/packages/lsp-core/src/tool-surface.test.ts +173 -0
- package/packages/lsp-core/src/tools/bun-directory-diagnostics.test.ts +113 -0
- package/packages/lsp-core/src/tools/bun-directory-diagnostics.ts +75 -0
- package/packages/lsp-core/src/tools/definitions.ts +156 -0
- package/packages/lsp-core/src/tools/diagnostics.ts +133 -0
- package/packages/lsp-core/src/tools/format.test.ts +56 -0
- package/packages/lsp-core/src/tools/format.ts +56 -0
- package/packages/lsp-core/src/tools/index.ts +11 -0
- package/packages/lsp-core/src/tools/install-decision.ts +39 -0
- package/packages/lsp-core/src/tools/navigation.ts +88 -0
- package/packages/lsp-core/src/tools/parameters.test.ts +51 -0
- package/packages/lsp-core/src/tools/parameters.ts +49 -0
- package/packages/lsp-core/src/tools/rename.ts +69 -0
- package/packages/lsp-core/src/tools/result.ts +5 -0
- package/packages/lsp-core/src/tools/runtime.ts +21 -0
- package/packages/lsp-core/src/tools/schema.ts +9 -0
- package/packages/lsp-core/src/tools/status.ts +28 -0
- package/packages/lsp-core/src/tools/symbols.ts +99 -0
- package/packages/lsp-core/src/tools/types.ts +116 -0
- package/packages/lsp-core/src/tools.ts +1 -0
- package/packages/omo-codex/plugin/.codex-plugin/plugin.json +1 -1
- package/packages/omo-codex/plugin/README.md +1 -1
- 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/comment-checker/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/comment-checker/package.json +1 -1
- 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/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/lazycodex-executor-verify/package.json +1 -1
- package/packages/omo-codex/plugin/components/lcx/skills/lcx-contribute-bug-fix/SKILL.md +51 -55
- package/packages/omo-codex/plugin/components/lcx/skills/lcx-contribute-bug-fix/agents/openai.yaml +1 -1
- package/packages/omo-codex/plugin/components/lcx/skills/lcx-report-bug/SKILL.md +7 -7
- package/packages/omo-codex/plugin/components/lcx/skills/lcx-report-bug/agents/openai.yaml +1 -1
- package/packages/omo-codex/plugin/components/lsp/dist/.omo-runtime-manifest.json +2 -2
- package/packages/omo-codex/plugin/components/lsp/hooks/hooks.json +2 -2
- package/packages/omo-codex/plugin/components/lsp/package.json +1 -1
- package/packages/omo-codex/plugin/components/rules/hooks/hooks.json +4 -4
- package/packages/omo-codex/plugin/components/rules/package.json +1 -1
- package/packages/omo-codex/plugin/components/teammode/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/teammode/package.json +2 -2
- package/packages/omo-codex/plugin/components/telemetry/README.md +2 -2
- package/packages/omo-codex/plugin/components/telemetry/dist/cli.js +52 -15
- package/packages/omo-codex/plugin/components/telemetry/dist/posthog.js +52 -19
- package/packages/omo-codex/plugin/components/telemetry/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/telemetry/package.json +1 -1
- package/packages/omo-codex/plugin/components/telemetry/src/codex-hook.ts +2 -9
- package/packages/omo-codex/plugin/components/telemetry/src/posthog.ts +4 -13
- package/packages/omo-codex/plugin/components/telemetry/src/product-identity.ts +0 -2
- package/packages/omo-codex/plugin/components/telemetry/test/codex-hook-diagnostics.test.ts +0 -3
- package/packages/omo-codex/plugin/components/telemetry/test/codex-hook.test.ts +3 -8
- package/packages/omo-codex/plugin/components/ultrawork/dist/cli.js +3 -0
- package/packages/omo-codex/plugin/components/ultrawork/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/ultrawork/package.json +1 -1
- package/packages/omo-codex/plugin/components/ultrawork/src/directive-content.ts +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 +1 -1
- package/packages/omo-codex/plugin/components/ulw-loop/directive.md +3 -0
- package/packages/omo-codex/plugin/components/ulw-loop/hooks/hooks.json +5 -5
- package/packages/omo-codex/plugin/components/ulw-loop/package.json +1 -1
- 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 +16 -16
- package/packages/omo-codex/plugin/package.json +1 -1
- package/packages/omo-codex/plugin/shared/package.json +1 -1
- package/packages/omo-codex/plugin/skills/ast-grep/README.md +1 -1
- package/packages/omo-codex/plugin/skills/ast-grep/install.ps1 +5 -3
- package/packages/omo-codex/plugin/skills/ast-grep/install.sh +4 -5
- package/packages/omo-codex/plugin/skills/ast-grep/references/install.md +2 -2
- package/packages/omo-codex/plugin/skills/ast-grep/scripts/ast_grep_helper.py +13 -3
- package/packages/omo-codex/plugin/skills/browser/runtime/omowright/manifest.json +1 -1
- package/packages/omo-codex/plugin/skills/coding-agent-sessions/AGENTS.md +5 -4
- package/packages/omo-codex/plugin/skills/coding-agent-sessions/SKILL.md +11 -11
- package/packages/omo-codex/plugin/skills/coding-agent-sessions/references/all-platforms.md +2 -2
- package/packages/omo-codex/plugin/skills/coding-agent-sessions/references/codex.md +4 -4
- package/packages/omo-codex/plugin/skills/coding-agent-sessions/scripts/agent_sessions/cli.py +1 -1
- package/packages/omo-codex/plugin/skills/coding-agent-sessions/scripts/find-agent-sessions.py +6 -3
- package/packages/omo-codex/plugin/skills/frontend/ATTRIBUTION.md +1 -1
- package/packages/omo-codex/plugin/skills/frontend/SKILL.md +34 -12
- package/packages/omo-codex/plugin/skills/frontend/references/design/README.md +2 -2
- package/packages/omo-codex/plugin/skills/frontend/references/design/_INDEX.md +2 -1
- package/packages/omo-codex/plugin/skills/frontend/references/design/component-catalogs.md +14 -10
- package/packages/omo-codex/plugin/skills/frontend/references/design/design-system-architecture.md +19 -6
- package/packages/omo-codex/plugin/skills/frontend/references/design/grounding.md +29 -0
- package/packages/omo-codex/plugin/skills/frontend/references/ui-ux-db/scripts/design_system.py +2 -0
- package/packages/omo-codex/plugin/skills/frontend/references/ui-ux-db/scripts/search.py +2 -0
- package/packages/omo-codex/plugin/skills/lcx-contribute-bug-fix/SKILL.md +51 -55
- package/packages/omo-codex/plugin/skills/lcx-contribute-bug-fix/agents/openai.yaml +1 -1
- package/packages/omo-codex/plugin/skills/lcx-report-bug/SKILL.md +7 -7
- package/packages/omo-codex/plugin/skills/lcx-report-bug/agents/openai.yaml +1 -1
- package/packages/omo-codex/plugin/skills/ultimate-browsing/SKILL.md +5 -4
- package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/AGENTS.md +2 -2
- package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/__main__.py +9 -6
- package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/summary.py +1 -1
- package/packages/omo-codex/plugin/skills/ultimate-browsing/references/insane-search/README.md +4 -4
- package/packages/omo-codex/plugin/skills/ultimate-browsing/scripts/extract_cookies.py +4 -2
- package/packages/omo-codex/plugin/skills/ultrawork/SKILL.md +3 -0
- package/packages/omo-codex/plugin/skills/ulw-research/AGENTS.md +8 -4
- package/packages/omo-codex/plugin/skills/ulw-research/SKILL.md +7 -316
- package/packages/omo-codex/plugin/skills/ulw-research/references/deliverable-phase.md +23 -21
- package/packages/omo-codex/plugin/skills/ulw-research/references/fact-ledger-grammar.md +7 -3
- package/packages/omo-codex/plugin/skills/ulw-research/references/fact-ledger.md +42 -8
- package/packages/omo-codex/plugin/skills/ulw-research/references/fidelity-claim-kinds.md +1 -1
- package/packages/omo-codex/plugin/skills/ulw-research/references/markdown-blocks.md +12 -0
- package/packages/omo-codex/plugin/skills/ulw-research/references/report-gates.md +12 -5
- package/packages/omo-codex/plugin/skills/ulw-research/references/research-collection.md +195 -0
- package/packages/omo-codex/plugin/skills/ulw-research/references/research-delivery.md +123 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/claim-kinds.mjs +33 -5
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/contracts.mjs +13 -4
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/conversion-probe.mjs +229 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/css-hiding.mjs +158 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/css-lite.mjs +4 -2
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/entities.mjs +13 -4
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-commands-fill.mjs +11 -1
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-commands-meaning.mjs +13 -9
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-commands-polish.mjs +40 -18
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-commands-verify.mjs +25 -8
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-commands.mjs +35 -16
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-derive.mjs +7 -2
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-fill.mjs +6 -4
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-ledger-kinds.mjs +6 -7
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-locate.mjs +15 -9
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-claims.mjs +4 -3
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-csv.mjs +32 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-lexicon.mjs +1 -1
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-originals.mjs +161 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-policy.mjs +4 -4
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-records.mjs +81 -14
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-snapshot-readability.mjs +49 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-sources.mjs +76 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-trace.mjs +25 -11
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-value-unit.mjs +202 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-values.mjs +40 -39
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/gates-fidelity.mjs +8 -6
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/gates-static.mjs +10 -1
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/html-lite.mjs +6 -2
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/html-markup.mjs +2 -2
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/html-text-map.mjs +140 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/markdown-closers.mjs +126 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/md-lite.mjs +1 -1
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/number-grammar.mjs +33 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/numerals.mjs +18 -7
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/outcome-start.mjs +66 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/outcome.mjs +11 -22
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/placeholder.mjs +18 -6
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/polish-apply.mjs +3 -2
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/raw-decode.mjs +110 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/raw-locator.mjs +47 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/report-tools-commands.mjs +1 -1
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/report-tools.mjs +7 -1
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/source-commands.mjs +79 -26
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/source-refresh.mjs +82 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/source-snapshot.mjs +73 -69
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/source-view.mjs +89 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/sources-citation.mjs +203 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/units.mjs +14 -3
- package/packages/omo-codex/plugin/test/payload-equivalence.test.mjs +6 -4
- package/packages/omo-codex/plugin/test/sync-skills.test.mjs +4 -3
- package/packages/omo-codex/scripts/install-dist/install-local.mjs +338 -237
- package/packages/prompts-core/prompts/ultrawork/codex.md +3 -0
- package/packages/shared-skills/skill-source-filter.mjs +6 -2
- package/packages/shared-skills/skills/ast-grep/README.md +1 -1
- package/packages/shared-skills/skills/ast-grep/install.ps1 +5 -3
- package/packages/shared-skills/skills/ast-grep/install.sh +4 -5
- package/packages/shared-skills/skills/ast-grep/references/install.md +2 -2
- package/packages/shared-skills/skills/ast-grep/scripts/ast_grep_helper.py +13 -3
- package/packages/shared-skills/skills/browser/runtime/omowright/manifest.json +1 -1
- package/packages/shared-skills/skills/coding-agent-sessions/AGENTS.md +5 -4
- package/packages/shared-skills/skills/coding-agent-sessions/SKILL.md +11 -11
- package/packages/shared-skills/skills/coding-agent-sessions/references/all-platforms.md +2 -2
- package/packages/shared-skills/skills/coding-agent-sessions/references/codex.md +4 -4
- package/packages/shared-skills/skills/coding-agent-sessions/scripts/agent_sessions/cli.py +1 -1
- package/packages/shared-skills/skills/coding-agent-sessions/scripts/find-agent-sessions.py +6 -3
- package/packages/shared-skills/skills/frontend/ATTRIBUTION.md +1 -1
- package/packages/shared-skills/skills/frontend/SKILL.md +34 -12
- package/packages/shared-skills/skills/frontend/references/design/README.md +2 -2
- package/packages/shared-skills/skills/frontend/references/design/_INDEX.md +2 -1
- package/packages/shared-skills/skills/frontend/references/design/component-catalogs.md +14 -10
- package/packages/shared-skills/skills/frontend/references/design/design-system-architecture.md +19 -6
- package/packages/shared-skills/skills/frontend/references/design/grounding.md +29 -0
- package/packages/shared-skills/skills/frontend/references/ui-ux-db/scripts/design_system.py +2 -0
- package/packages/shared-skills/skills/frontend/references/ui-ux-db/scripts/search.py +2 -0
- package/packages/shared-skills/skills/ultimate-browsing/SKILL.md +5 -4
- package/packages/shared-skills/skills/ultimate-browsing/engine/AGENTS.md +2 -2
- package/packages/shared-skills/skills/ultimate-browsing/engine/__main__.py +9 -6
- package/packages/shared-skills/skills/ultimate-browsing/engine/summary.py +1 -1
- package/packages/shared-skills/skills/ultimate-browsing/references/insane-search/README.md +4 -4
- package/packages/shared-skills/skills/ultimate-browsing/scripts/extract_cookies.py +4 -2
- package/packages/shared-skills/skills/ulw-research/AGENTS.md +8 -4
- package/packages/shared-skills/skills/ulw-research/SKILL.md +7 -316
- package/packages/shared-skills/skills/ulw-research/references/deliverable-phase.md +23 -21
- package/packages/shared-skills/skills/ulw-research/references/fact-ledger-grammar.md +7 -3
- package/packages/shared-skills/skills/ulw-research/references/fact-ledger.md +42 -8
- package/packages/shared-skills/skills/ulw-research/references/fidelity-claim-kinds.md +1 -1
- package/packages/shared-skills/skills/ulw-research/references/markdown-blocks.md +12 -0
- package/packages/shared-skills/skills/ulw-research/references/report-gates.md +12 -5
- package/packages/shared-skills/skills/ulw-research/references/research-collection.md +195 -0
- package/packages/shared-skills/skills/ulw-research/references/research-delivery.md +123 -0
- package/packages/shared-skills/skills/ulw-research/scripts/claim-kinds.mjs +33 -5
- package/packages/shared-skills/skills/ulw-research/scripts/claim-kinds.test.ts +37 -2
- package/packages/shared-skills/skills/ulw-research/scripts/claim-prefix.test.ts +76 -0
- package/packages/shared-skills/skills/ulw-research/scripts/contracts-validation.test.ts +83 -0
- package/packages/shared-skills/skills/ulw-research/scripts/contracts.mjs +13 -4
- package/packages/shared-skills/skills/ulw-research/scripts/contracts.test.ts +6 -110
- package/packages/shared-skills/skills/ulw-research/scripts/conversion-altered.test.ts +154 -0
- package/packages/shared-skills/skills/ulw-research/scripts/conversion-originals.test.ts +297 -0
- package/packages/shared-skills/skills/ulw-research/scripts/conversion-probe-classes.test.ts +361 -0
- package/packages/shared-skills/skills/ulw-research/scripts/conversion-probe.mjs +229 -0
- package/packages/shared-skills/skills/ulw-research/scripts/conversion-probe.test.ts +218 -0
- package/packages/shared-skills/skills/ulw-research/scripts/copy-returns-polish.test.ts +4 -3
- package/packages/shared-skills/skills/ulw-research/scripts/css-hiding.mjs +158 -0
- package/packages/shared-skills/skills/ulw-research/scripts/css-lite.mjs +4 -2
- package/packages/shared-skills/skills/ulw-research/scripts/delivery-barrier-meaning.test.ts +186 -0
- package/packages/shared-skills/skills/ulw-research/scripts/delivery-barrier.test.ts +40 -87
- package/packages/shared-skills/skills/ulw-research/scripts/entities.mjs +13 -4
- package/packages/shared-skills/skills/ulw-research/scripts/fact-commands-fill.mjs +11 -1
- package/packages/shared-skills/skills/ulw-research/scripts/fact-commands-meaning.mjs +13 -9
- package/packages/shared-skills/skills/ulw-research/scripts/fact-commands-polish.mjs +40 -18
- package/packages/shared-skills/skills/ulw-research/scripts/fact-commands-verify.mjs +25 -8
- package/packages/shared-skills/skills/ulw-research/scripts/fact-commands.mjs +35 -16
- package/packages/shared-skills/skills/ulw-research/scripts/fact-derive-2.test.ts +149 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fact-derive.mjs +7 -2
- package/packages/shared-skills/skills/ulw-research/scripts/fact-derive.test.ts +7 -192
- package/packages/shared-skills/skills/ulw-research/scripts/fact-fill.mjs +6 -4
- package/packages/shared-skills/skills/ulw-research/scripts/fact-ledger-kinds.mjs +6 -7
- package/packages/shared-skills/skills/ulw-research/scripts/fact-ledger-kinds.test.ts +2 -1
- package/packages/shared-skills/skills/ulw-research/scripts/fact-ledger-legacy.test.ts +2 -1
- package/packages/shared-skills/skills/ulw-research/scripts/fact-locate.mjs +15 -9
- package/packages/shared-skills/skills/ulw-research/scripts/fact-unit-dimensions-2.test.ts +149 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fact-unit-dimensions.test.ts +4 -168
- package/packages/shared-skills/skills/ulw-research/scripts/fact-unit-rules.test.ts +3 -229
- package/packages/shared-skills/skills/ulw-research/scripts/fidelity-claims.mjs +4 -3
- package/packages/shared-skills/skills/ulw-research/scripts/fidelity-csv.mjs +32 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fidelity-lexicon.mjs +1 -1
- package/packages/shared-skills/skills/ulw-research/scripts/fidelity-originals.mjs +161 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fidelity-policy.mjs +4 -4
- package/packages/shared-skills/skills/ulw-research/scripts/fidelity-records.mjs +81 -14
- package/packages/shared-skills/skills/ulw-research/scripts/fidelity-snapshot-readability.mjs +49 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fidelity-sources.mjs +76 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fidelity-trace.mjs +25 -11
- package/packages/shared-skills/skills/ulw-research/scripts/fidelity-value-unit.mjs +202 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fidelity-values.mjs +40 -39
- package/packages/shared-skills/skills/ulw-research/scripts/fidelity-values.test.ts +8 -29
- package/packages/shared-skills/skills/ulw-research/scripts/gates-fidelity.mjs +8 -6
- package/packages/shared-skills/skills/ulw-research/scripts/gates-static-headings.test.ts +17 -0
- package/packages/shared-skills/skills/ulw-research/scripts/gates-static.mjs +10 -1
- package/packages/shared-skills/skills/ulw-research/scripts/html-lite.mjs +6 -2
- package/packages/shared-skills/skills/ulw-research/scripts/html-markup.mjs +2 -2
- package/packages/shared-skills/skills/ulw-research/scripts/html-text-map.mjs +140 -0
- package/packages/shared-skills/skills/ulw-research/scripts/html-text-map.test.ts +148 -0
- package/packages/shared-skills/skills/ulw-research/scripts/markdown-closers.mjs +126 -0
- package/packages/shared-skills/skills/ulw-research/scripts/md-lite-boundaries.test.ts +46 -0
- package/packages/shared-skills/skills/ulw-research/scripts/md-lite.mjs +1 -1
- package/packages/shared-skills/skills/ulw-research/scripts/number-grammar.mjs +33 -0
- package/packages/shared-skills/skills/ulw-research/scripts/number-grammar.test.ts +36 -0
- package/packages/shared-skills/skills/ulw-research/scripts/numerals.mjs +18 -7
- package/packages/shared-skills/skills/ulw-research/scripts/numerals.test.ts +3 -0
- package/packages/shared-skills/skills/ulw-research/scripts/outcome-start.mjs +66 -0
- package/packages/shared-skills/skills/ulw-research/scripts/outcome-start.test.ts +109 -0
- package/packages/shared-skills/skills/ulw-research/scripts/outcome.mjs +11 -22
- package/packages/shared-skills/skills/ulw-research/scripts/page-source-locator.test.ts +187 -0
- package/packages/shared-skills/skills/ulw-research/scripts/placeholder.mjs +18 -6
- package/packages/shared-skills/skills/ulw-research/scripts/polish-apply.mjs +3 -2
- package/packages/shared-skills/skills/ulw-research/scripts/polish-markup.test.ts +15 -1
- package/packages/shared-skills/skills/ulw-research/scripts/polish-partial.test.ts +42 -0
- package/packages/shared-skills/skills/ulw-research/scripts/raw-decode.mjs +110 -0
- package/packages/shared-skills/skills/ulw-research/scripts/raw-locator.mjs +47 -0
- package/packages/shared-skills/skills/ulw-research/scripts/raw-snapshot.test.ts +139 -0
- package/packages/shared-skills/skills/ulw-research/scripts/report-tools-2.test.ts +233 -0
- package/packages/shared-skills/skills/ulw-research/scripts/report-tools-3.test.ts +141 -0
- package/packages/shared-skills/skills/ulw-research/scripts/report-tools-4.test.ts +135 -0
- package/packages/shared-skills/skills/ulw-research/scripts/report-tools-5.test.ts +231 -0
- package/packages/shared-skills/skills/ulw-research/scripts/report-tools-commands.mjs +1 -1
- package/packages/shared-skills/skills/ulw-research/scripts/report-tools.mjs +7 -1
- package/packages/shared-skills/skills/ulw-research/scripts/report-tools.test.ts +5 -800
- package/packages/shared-skills/skills/ulw-research/scripts/snapshot-readability.test.ts +116 -0
- package/packages/shared-skills/skills/ulw-research/scripts/source-commands.mjs +79 -26
- package/packages/shared-skills/skills/ulw-research/scripts/source-integrity.test.ts +243 -0
- package/packages/shared-skills/skills/ulw-research/scripts/source-refresh.mjs +82 -0
- package/packages/shared-skills/skills/ulw-research/scripts/source-snapshot.mjs +73 -69
- package/packages/shared-skills/skills/ulw-research/scripts/source-snapshot.test.ts +7 -4
- package/packages/shared-skills/skills/ulw-research/scripts/source-view.mjs +89 -0
- package/packages/shared-skills/skills/ulw-research/scripts/sources-citation.mjs +203 -0
- package/packages/shared-skills/skills/ulw-research/scripts/sources-citation.test.ts +236 -0
- package/packages/shared-skills/skills/ulw-research/scripts/tests/contracts-support.ts +147 -0
- package/packages/shared-skills/skills/ulw-research/scripts/tests/delivery-barrier-session.ts +59 -0
- package/packages/shared-skills/skills/ulw-research/scripts/tests/derive-clock.ts +4 -0
- package/packages/shared-skills/skills/ulw-research/scripts/tests/fact-derive-support.ts +65 -0
- package/packages/shared-skills/skills/ulw-research/scripts/tests/fact-unit-dimensions-support.ts +41 -0
- package/packages/shared-skills/skills/ulw-research/scripts/tests/fact-unit-rules-support.ts +241 -0
- package/packages/shared-skills/skills/ulw-research/scripts/tests/fidelity-values-support.ts +30 -0
- package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/conversion-probe.html +14 -0
- package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/sources-shapes.html +20 -0
- package/packages/shared-skills/skills/ulw-research/scripts/tests/numeral-classes.ts +196 -0
- package/packages/shared-skills/skills/ulw-research/scripts/tests/report-tools-support.ts +182 -0
- package/packages/shared-skills/skills/ulw-research/scripts/tests/value-mode-support.ts +48 -0
- package/packages/shared-skills/skills/ulw-research/scripts/units.mjs +14 -3
- package/packages/shared-skills/skills/ulw-research/scripts/units.test.ts +10 -0
- package/packages/shared-skills/skills/ulw-research/scripts/value-mode-unit-classes.test.ts +255 -0
- package/packages/shared-skills/skills/ulw-research/scripts/value-mode-unit.test.ts +179 -0
- package/packages/shared-skills/skills/ulw-research/scripts/value-rules-cli.test.ts +157 -0
- package/packages/shared-skills/skills/ulw-research/scripts/value-rules-followups.test.ts +72 -0
- package/packages/shared-skills/skills/ulw-research/scripts/value-rules-priority.test.ts +80 -0
- package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/tests/fixtures/amp_redirect_stub.html +0 -7
- package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/tests/fixtures/search_interstitial.html +0 -19
- package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/tests/fixtures/wayback_available.json +0 -1
- package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/tests/fixtures/wayback_snapshot.html +0 -1128
- package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/tests/test_fetch_chain.py +0 -77
- package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/tests/test_playwright_stealth.py +0 -86
- package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/tests/test_playwright_templates.py +0 -279
- package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/tests/test_surrogate.py +0 -252
- package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/tests/test_surrogate_validators.py +0 -78
|
@@ -8,10 +8,12 @@
|
|
|
8
8
|
// escaped HTML projection (same line numbers) and checked with its fill map, which is the only
|
|
9
9
|
// record of where its values sit; LaTeX is verified through its Markdown source.
|
|
10
10
|
//
|
|
11
|
-
// The locked brief is re-judged here, not only at fill:
|
|
12
|
-
// `<report>.fill-map.json`
|
|
13
|
-
//
|
|
14
|
-
//
|
|
11
|
+
// The locked brief is re-judged here, not only at fill: every edition (HTML or Markdown) needs its
|
|
12
|
+
// fill map (fillMapOf: --fill-map, else whichever of `<report>.fill-map.json` and the
|
|
13
|
+
// `<report>.polished.fill-map.json` polish-apply writes records the edition's bytes), and that map
|
|
14
|
+
// must carry the current brief's brief_sha256; no map, or another hash, is brief_changed. policyErrors
|
|
15
|
+
// re-runs over every data-fact the edition carries, so an edition that violates the current
|
|
16
|
+
// derived-numbers or source-floor rule fails here too.
|
|
15
17
|
//
|
|
16
18
|
// The delivery barrier that reads these records (and every other fidelity record) is
|
|
17
19
|
// fidelity-records.mjs.
|
|
@@ -66,8 +68,8 @@ function entryHtml(text, entry, rows) {
|
|
|
66
68
|
if (entry.translates) return `<span data-translates="${escapeHtml(entry.translates)}">${projectMarkdownInline(body.replace(/^<!--[\s\S]*?-->\s*/u, ""))}</span>`
|
|
67
69
|
if (!row) return `<data data-fact="${escapeHtml(entry.fact_id)}">${escapeHtml(body)}</data>`
|
|
68
70
|
const quoted = row.kind === "quote" ? parseRenderedQuote(body) : null
|
|
69
|
-
if (quoted) return renderQuote(row, entry.selector ?? "full", quoted.text, "html")
|
|
70
|
-
return row.kind === "quote" ? null : renderValueHtml(row, body)
|
|
71
|
+
if (quoted) return renderQuote(row, entry.selector ?? "full", quoted.text, "html", entry.claimKind)
|
|
72
|
+
return row.kind === "quote" ? null : renderValueHtml(row, body, { mode: entry.mode, claimKind: entry.claimKind ?? null })
|
|
71
73
|
}
|
|
72
74
|
|
|
73
75
|
/** [from, to) of the filled text as HTML, each intact fill-map entry rebuilt as its fill element. */
|
|
@@ -149,6 +151,17 @@ const MARKDOWN_EXTENSION = /\.(md|markdown)$/iu
|
|
|
149
151
|
/** The edition format: the explicit flag, else md for a .md or .markdown path (any case), else html. */
|
|
150
152
|
export const editionFormatOf = (path, flag) => flag ?? (MARKDOWN_EXTENSION.test(path) ? "md" : "html")
|
|
151
153
|
|
|
154
|
+
/**
|
|
155
|
+
* An edition's fill map path: --fill-map, else whichever of fact fill's `<report>.fill-map.json` and
|
|
156
|
+
* polish-apply's `<report>.polished.fill-map.json` records these bytes as its artifact_sha256, else the
|
|
157
|
+
* first of them that exists; null when neither exists.
|
|
158
|
+
*/
|
|
159
|
+
export function fillMapOf(report, text, flag) {
|
|
160
|
+
if (flag !== undefined) return flag
|
|
161
|
+
const found = [`${report}.fill-map.json`, `${report}.polished.fill-map.json`].filter((path) => existsSync(path))
|
|
162
|
+
return found.find((path) => readJsonFile(path).artifact_sha256 === sha256Of(text)) ?? found[0] ?? null
|
|
163
|
+
}
|
|
164
|
+
|
|
152
165
|
/** The report's format (from --format or its extension) and, for Markdown, its fill map entries. */
|
|
153
166
|
export function readEdition(report, flags) {
|
|
154
167
|
const format = editionFormatOf(report, flags.format)
|
|
@@ -174,19 +187,23 @@ export function runFactVerify(args) {
|
|
|
174
187
|
const briefPath = flags.brief ?? join(sessionDir, "brief.md")
|
|
175
188
|
const ledger = readLedger(sessionDir)
|
|
176
189
|
const projected = edition.format === "md" ? markdownAsHtml(edition.text, edition.entries, ledger) : null
|
|
190
|
+
// The entries whose element is in the checked HTML: an HTML edition's own map, or the projection's intact entries.
|
|
191
|
+
const ownMap = flags["fill-map"] ?? `${report}.fill-map.json`
|
|
192
|
+
const fillEntries = projected ? edition.entries.filter((entry) => !projected.stale.includes(entry)) : existsSync(ownMap) ? readJsonFile(ownMap).entries : null
|
|
177
193
|
const result = checkFidelity(projected?.html ?? edition.text, {
|
|
178
194
|
ledger,
|
|
179
195
|
sourcesDir: resolve(flags.sources ?? join(sessionDir, "sources")),
|
|
180
196
|
claims: flags.claims !== undefined || existsSync(claimsPath) ? readJsonFile(claimsPath) : null,
|
|
181
197
|
fillMap: projected?.stale ?? edition.entries,
|
|
198
|
+
fillEntries,
|
|
182
199
|
brief: existsSync(briefPath) ? readText(briefPath) : "",
|
|
183
200
|
baseDir: dirname(resolve(report)),
|
|
184
201
|
recompute,
|
|
185
202
|
})
|
|
186
203
|
const checked = projected?.html ?? edition.text
|
|
187
204
|
const pointers = [...checked.matchAll(/\bdata-fact="([^"]+)"/gu)].map((match) => ({ token: match[0], start: match.index, parsed: { id: match[1] } }))
|
|
188
|
-
const mapPath = flags["fill-map"]
|
|
189
|
-
const bound =
|
|
205
|
+
const mapPath = fillMapOf(report, edition.text, flags["fill-map"])
|
|
206
|
+
const bound = briefBindingErrors(mapPath === null ? null : readJsonFile(mapPath), mapPath ?? `${report}.fill-map.json`, parsed)
|
|
190
207
|
const defects = [...bound, ...policyErrors(checked, pointers, ledger, sessionDir, policy).errors, ...result.defects]
|
|
191
208
|
const counts = countDefects(defects)
|
|
192
209
|
const status = defects.some(blocksDelivery) ? "fail" : "pass"
|
|
@@ -10,11 +10,14 @@ import { existsSync, readdirSync, readFileSync, writeFileSync } from "node:fs"
|
|
|
10
10
|
import { join, resolve } from "node:path"
|
|
11
11
|
|
|
12
12
|
import { CliError, parseArgs, sha256Of } from "./cli-support.mjs"
|
|
13
|
+
import { DEFECT_CODES } from "./contracts.mjs"
|
|
14
|
+
import { alteredIn } from "./conversion-probe.mjs"
|
|
13
15
|
import { derive } from "./fact-derive.mjs"
|
|
14
16
|
import { addRow, readLedger, retractRow, supersedeRow, writeLedger } from "./fact-ledger.mjs"
|
|
15
17
|
import { locateFind, locateMatch, locatePageLine, locateTable } from "./fact-locate.mjs"
|
|
16
18
|
import { QUALIFIER_FORMS } from "./fidelity-lexicon.mjs"
|
|
17
19
|
import { exportRows } from "./fidelity-values.mjs"
|
|
20
|
+
import { rawLocated } from "./raw-locator.mjs"
|
|
18
21
|
import { pointedFlag, sessionDirOf, snapshotOf } from "./source-commands.mjs"
|
|
19
22
|
|
|
20
23
|
const CONTEXT = 60
|
|
@@ -30,26 +33,30 @@ function readText(path) {
|
|
|
30
33
|
}
|
|
31
34
|
}
|
|
32
35
|
|
|
33
|
-
|
|
34
|
-
|
|
36
|
+
/** A caller-supplied --now value as ISO; parsing only, never the clock. */
|
|
37
|
+
function isoOf(text) {
|
|
38
|
+
const date = new Date(text)
|
|
35
39
|
if (Number.isNaN(date.getTime())) throw new CliError("--now requires an ISO timestamp")
|
|
36
40
|
return date.toISOString()
|
|
37
41
|
}
|
|
38
42
|
|
|
43
|
+
const nowIso = (flags) => (flags.now === undefined ? new Date().toISOString() : isoOf(flags.now))
|
|
44
|
+
|
|
39
45
|
function failure(command, error, detail) {
|
|
40
46
|
const json = { command, status: "fail", exitCode: 1, error, ...detail }
|
|
41
47
|
return { json, summary: `${command}: ${error}${detail.message ? ` (${detail.message})` : ""}`, exitCode: 1 }
|
|
42
48
|
}
|
|
43
49
|
|
|
44
|
-
|
|
50
|
+
/** Every locator reads the view text (visible text nodes for a page), never raw markup. */
|
|
51
|
+
function locate(flags, view, id, quote) {
|
|
45
52
|
const sourceId = id
|
|
46
|
-
if (quote) return locateMatch(
|
|
47
|
-
if (flags.find !== undefined) return locateFind(
|
|
53
|
+
if (quote) return locateMatch(view.text, { match: flags.match, sourceId })
|
|
54
|
+
if (flags.find !== undefined) return locateFind(view.text, { find: flags.find, unitExpected: flags["unit-expected"], sourceId })
|
|
48
55
|
if (flags.table !== undefined) {
|
|
49
|
-
if (
|
|
50
|
-
return locateTable(
|
|
56
|
+
if (view.root === null) throw new CliError(`source ${id} is not an HTML page snapshot; --table needs one`)
|
|
57
|
+
return locateTable(view, { table: flags.table, row: flags.row, col: flags.col, sourceId })
|
|
51
58
|
}
|
|
52
|
-
if (flags.page !== undefined) return locatePageLine(
|
|
59
|
+
if (flags.page !== undefined) return locatePageLine(view.text, { page: Number(flags.page), line: Number(flags.line), sourceId })
|
|
53
60
|
throw new CliError("usage: fact add --source S<n> (--find TEXT | --table T --row R --col C | --page P --line L) --label LABEL")
|
|
54
61
|
}
|
|
55
62
|
|
|
@@ -76,11 +83,17 @@ function runAdd(args, quote) {
|
|
|
76
83
|
const id = args.required("source")
|
|
77
84
|
if (quote) args.required("match")
|
|
78
85
|
const sessionDir = sessionDirOf(flags)
|
|
79
|
-
const
|
|
80
|
-
const
|
|
86
|
+
const { meta, view, sha256, drift } = snapshotOf(sessionDir, id)
|
|
87
|
+
const converted = view.mode === "converted"
|
|
88
|
+
if (drift.length > 0) return failure(command, "snapshot_hash_mismatch", { message: `${id}: ${drift.join("; ")}` })
|
|
89
|
+
if (view.mode === "legacy") return failure(command, "snapshot_not_raw", { message: `${id} is a legacy text.txt snapshot without raw.bin; snapshot the source again under a new id` })
|
|
90
|
+
if (converted && !quote) return failure(command, "snapshot_not_raw", { message: `${id} is converted-only (no raw bytes); measured rows need source snapshot --raw <original> --text <extraction>` })
|
|
91
|
+
const located = locate(flags, view, id, quote)
|
|
92
|
+
const found = rawLocated(view, located)
|
|
81
93
|
if (found.error === "unit_expected_mismatch") return failure(command, "unit_mismatch", { message: `expected ${found.expected}, found ${found.found}`, candidates: found.candidates })
|
|
82
94
|
if (found.error) return failure(command, found.error, { message: `no single match in ${id}`, candidates: found.candidates })
|
|
83
|
-
const
|
|
95
|
+
const altered = alteredIn(join(sessionDir, "sources", id), [found.raw, found.quote])
|
|
96
|
+
if (altered.length > 0) return failure(command, "conversion_altered_number", { message: `${id}'s conversion altered ${[...new Set(altered)].join(", ")} (sources/${id}/conversion.json); the converted text cannot back this number, even where the source also shows it unaltered: write a verified text of the source and run \`source snapshot --id ${id} --text <verified.txt> --replace\`, then add the fact again`, tokens: altered, hint: DEFECT_CODES.conversion_altered_number.hint })
|
|
84
97
|
const host = URL.canParse(meta.url) ? new URL(meta.url).hostname : null
|
|
85
98
|
const labelSource = flags.find ?? flags.match ?? (flags.table !== undefined ? `${flags.row}_${flags.col}` : `p${flags.page}_l${flags.line}`)
|
|
86
99
|
const ledger = readLedger(sessionDir)
|
|
@@ -89,9 +102,10 @@ function runAdd(args, quote) {
|
|
|
89
102
|
...(quote ? {} : { value: found.value, unit: found.unit, value_span: found.value_span, qualifier: QUALIFIER_OF.get(found.qualifier) ?? null }),
|
|
90
103
|
raw: found.raw,
|
|
91
104
|
quote: found.quote,
|
|
105
|
+
context: found.context,
|
|
92
106
|
source_id: id,
|
|
93
107
|
locator: { ...found.locator, source_kind: meta.kind, url: meta.url, host, pointed: pointedFlag(sessionDir, meta.url) },
|
|
94
|
-
snapshot_sha256:
|
|
108
|
+
snapshot_sha256: sha256,
|
|
95
109
|
sentence_id: found.sentence_id ?? null,
|
|
96
110
|
extracted_by: meta.extractor,
|
|
97
111
|
observed_at: nowIso(flags),
|
|
@@ -104,11 +118,14 @@ function runAdd(args, quote) {
|
|
|
104
118
|
fuzzy: found.fuzzy === true,
|
|
105
119
|
})
|
|
106
120
|
writeLedger(sessionDir, ledger)
|
|
107
|
-
const context = contextOf(
|
|
108
|
-
const
|
|
121
|
+
const context = contextOf(view.text, located)
|
|
122
|
+
const { severity, integrity } = DEFECT_CODES.quote_converted_only
|
|
123
|
+
const defects = converted ? [{ code: "quote_converted_only", severity, integrity, message: `${id} is converted-only; the quote is not bound to the original fetched bytes` }] : []
|
|
124
|
+
const json = { command, status: "pass", exitCode: 0, defects, data: { row, context } }
|
|
109
125
|
if (flags.json) return { json, exitCode: 0 }
|
|
110
126
|
const shown = quote ? row.quote : `${row.raw} (value ${row.value}${row.unit ? `, unit ${row.unit}` : ""})`
|
|
111
|
-
|
|
127
|
+
const warnings = defects.map((defect) => `\n warning ${defect.code}: ${defect.message}`).join("")
|
|
128
|
+
return { text: `${row.fact_id} ${row.label}: ${shown} [${id} ${row.sentence_id ?? "-"}]\n ...${context.before}[${context.match}]${context.after}...${warnings}`, exitCode: 0 }
|
|
112
129
|
}
|
|
113
130
|
|
|
114
131
|
function runRetract(args) {
|
|
@@ -157,11 +174,13 @@ function runExport(args) {
|
|
|
157
174
|
function runDerive(args) {
|
|
158
175
|
const { flags } = args
|
|
159
176
|
const op = args.required("op")
|
|
177
|
+
// Time is the caller's: without --now the CLI refuses with the library's missing_now code (exit 2) and never reads the clock.
|
|
178
|
+
if (flags.now === undefined) throw new CliError("missing_now: fact derive needs --now <ISO timestamp> (the row's observed_at); no row written")
|
|
160
179
|
const sessionDir = sessionDirOf(flags)
|
|
161
180
|
const ledger = readLedger(sessionDir)
|
|
162
181
|
// The registry refuses any field outside the operation's inputFields, so only the given flags go in; --unit null declares a dimensionless result.
|
|
163
182
|
const picked = ["to", "from", "num", "den", "part", "whole", "reference", "rationale", "label"].filter((key) => flags[key] !== undefined)
|
|
164
|
-
const spec = { op, now:
|
|
183
|
+
const spec = { op, now: isoOf(flags.now), ...Object.fromEntries(picked.map((key) => [key, flags[key]])) }
|
|
165
184
|
if (op === "expr") spec.formula = args.positionals[0]
|
|
166
185
|
else if (args.positionals.length > 0) spec.operands = args.positionals
|
|
167
186
|
if (flags.disjoint === true) spec.disjoint = true
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { describe, expect, onTestFinished, test } from "bun:test"
|
|
2
|
+
import { mkdtempSync, rmSync } from "node:fs"
|
|
3
|
+
import { tmpdir } from "node:os"
|
|
4
|
+
import { join } from "node:path"
|
|
5
|
+
import { recompute } from "./fact-derive.mjs"
|
|
6
|
+
import { derive } from "./tests/derive-clock"
|
|
7
|
+
import { addRow, emptyLedger, readLedger, writeLedger } from "./fact-ledger.mjs"
|
|
8
|
+
import { HASH, EOK_WON, Row, Ledger, measured, ledgerOf, fixture, rowOf } from "./tests/fact-derive-support"
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
describe("unit rules: the design review's counterexamples", () => {
|
|
13
|
+
const book = () =>
|
|
14
|
+
ledgerOf([
|
|
15
|
+
measured("F1", "20", "%"),
|
|
16
|
+
measured("F2", "10", "%"),
|
|
17
|
+
measured("F3", "5", "pp"),
|
|
18
|
+
measured("F4", "25", "C", { valid_at: "2025", entity: "x" }),
|
|
19
|
+
measured("F5", "30", "C", { valid_at: "2026", entity: "x" }),
|
|
20
|
+
measured("F6", "200", "person"),
|
|
21
|
+
measured("F7", "100", "person"),
|
|
22
|
+
measured("F8", "100", "USD"),
|
|
23
|
+
measured("F9", "20", "USD/person"),
|
|
24
|
+
measured("F10", "0", "m"),
|
|
25
|
+
])
|
|
26
|
+
|
|
27
|
+
test("a sum of % operands without one shared reference is refused", () => {
|
|
28
|
+
const ledger = book()
|
|
29
|
+
const base = { op: "sum", operands: ["F1", "F2"], unit: "%", disjoint: true, rationale: "disjoint shares" }
|
|
30
|
+
expect(derive(ledger, base).error).toBe("incompatible_operands")
|
|
31
|
+
expect(derive(ledger, { ...base, reference: { F1: "voters", F2: "residents" } }).error).toBe("incompatible_operands")
|
|
32
|
+
expect(rowOf(derive(ledger, { ...base, reference: { F1: "voters", F2: "voters" } }))).toMatchObject({ value: "30", unit: "%" })
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
test("20% - 5pp is 15%", () => {
|
|
36
|
+
const row = rowOf(derive(book(), { op: "diff", operands: ["F1", "F3"], unit: "%" }))
|
|
37
|
+
expect(row).toMatchObject({ value: "15", unit: "%", render: "15%" })
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
test("growth on a Celsius scale is refused", () => {
|
|
41
|
+
const result = derive(book(), { op: "growth", from: "F4", to: "F5", unit: "%" })
|
|
42
|
+
expect(result.error).toBe("incompatible_operands")
|
|
43
|
+
expect(result.detail).toContain("affine")
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
test("temperatures are not summed or differenced as absolute values; ratio refuses them; convert C to F is allowed", () => {
|
|
47
|
+
const ledger = book()
|
|
48
|
+
const refused = [
|
|
49
|
+
{ op: "sum", operands: ["F4", "F5"], unit: "C" },
|
|
50
|
+
{ op: "diff", operands: ["F5", "F4"], unit: "C" },
|
|
51
|
+
{ op: "expr", formula: "F4 + F5", unit: "C" },
|
|
52
|
+
{ op: "expr", formula: "F5 - F4", unit: "C" },
|
|
53
|
+
{ op: "ratio", num: "F5", den: "F4", unit: null },
|
|
54
|
+
]
|
|
55
|
+
for (const spec of refused) {
|
|
56
|
+
const result = derive(ledger, spec)
|
|
57
|
+
expect({ spec, error: result.error, detail: result.detail }).toEqual({ spec, error: "incompatible_operands", detail: expect.stringContaining("affine") })
|
|
58
|
+
}
|
|
59
|
+
const converted = rowOf(derive(ledger, { op: "convert", operands: ["F4"], to: "F" }))
|
|
60
|
+
expect(converted).toMatchObject({ value: "77", unit: "F" })
|
|
61
|
+
expect(recompute(ledger, converted).ok).toBe(true)
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
test("a convert row relabeled to an alias in both unit and context.target is detected", () => {
|
|
65
|
+
const ledger = book()
|
|
66
|
+
const row = rowOf(derive(ledger, { op: "convert", operands: ["F10"], to: "km" }))
|
|
67
|
+
const relabeled = recompute(ledger, { ...row, unit: "kilometre", raw: "0 kilometre (0 m)", render: "0 kilometre (0 m)", context: { target: "kilometre" } })
|
|
68
|
+
expect(relabeled.ok).toBe(false)
|
|
69
|
+
expect(relabeled.detail).toContain('context.target "kilometre" is not canonical')
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
test("a share of 200 out of 100 is refused", () => {
|
|
73
|
+
expect(derive(book(), { op: "share", part: "F6", whole: "F7", unit: "%" }).error).toBe("incompatible_operands")
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
test("100 USD / (20 USD/person) is 5 person", () => {
|
|
77
|
+
const ledger = book()
|
|
78
|
+
const row = rowOf(derive(ledger, { op: "ratio", num: "F8", den: "F9", unit: "person" }))
|
|
79
|
+
expect(row).toMatchObject({ value: "5", unit: "person", exact: { num: "5", den: "1" } })
|
|
80
|
+
expect(recompute(ledger, row).ok).toBe(true)
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
test("convert 0 m to km relabeled cm is detected though the value is unchanged", () => {
|
|
84
|
+
const ledger = book()
|
|
85
|
+
const row = rowOf(derive(ledger, { op: "convert", operands: ["F10"], to: "km" }))
|
|
86
|
+
expect(recompute(ledger, row).ok).toBe(true)
|
|
87
|
+
const relabeled = recompute(ledger, { ...row, unit: "cm", raw: "0 cm (0 m)", render: "0 cm (0 m)" })
|
|
88
|
+
expect(relabeled.ok).toBe(false)
|
|
89
|
+
expect(relabeled.detail).toContain("km")
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
test("expr % + %p is %, in either order; a ratio over unequal % and %p is refused", () => {
|
|
93
|
+
const ledger = book()
|
|
94
|
+
for (const formula of ["F1 + F3", "F3 + F1"]) {
|
|
95
|
+
const row = rowOf(derive(ledger, { op: "expr", formula, unit: "%" }))
|
|
96
|
+
expect(row).toMatchObject({ value: "25", unit: "%" })
|
|
97
|
+
expect(recompute(ledger, row).ok).toBe(true)
|
|
98
|
+
}
|
|
99
|
+
const ratio = derive(ledger, { op: "ratio", num: "F1", den: "F3", unit: null })
|
|
100
|
+
expect(ratio.error).toBe("incompatible_operands")
|
|
101
|
+
expect(ratio.detail).toContain("needs equal units")
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
test("a stored context on an operation that has none is malformed", () => {
|
|
105
|
+
const ledger = book()
|
|
106
|
+
const diff = rowOf(derive(ledger, { op: "diff", operands: ["F1", "F2"], unit: "%p" }))
|
|
107
|
+
const expr = rowOf(derive(ledger, { op: "expr", formula: "F1 - F2", unit: "%p" }))
|
|
108
|
+
for (const [row, context] of [[diff, "x"], [expr, { target: "km" }]] as const) {
|
|
109
|
+
const result = recompute(ledger, { ...row, context })
|
|
110
|
+
expect(result.ok).toBe(false)
|
|
111
|
+
expect(result.detail).toContain("rows carry no context")
|
|
112
|
+
}
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
test("expr 20% - 10% is %p, never %", () => {
|
|
116
|
+
const ledger = book()
|
|
117
|
+
expect(derive(ledger, { op: "expr", formula: "F1 - F2", unit: "%" }).error).toBe("incompatible_operands")
|
|
118
|
+
const row = rowOf(derive(ledger, { op: "expr", formula: "F1 - F2", unit: "%p" }))
|
|
119
|
+
expect(row).toMatchObject({ value: "10", unit: "%p" })
|
|
120
|
+
expect(recompute(ledger, row).ok).toBe(true)
|
|
121
|
+
})
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
describe("render precision of expr results (invariant 5)", () => {
|
|
125
|
+
const book = () => ledgerOf([measured("F1", "20.25", "%"), measured("F2", "15.5", "%"), measured("F3", "1000", "m"), measured("F4", "3", "m"), measured("F5", "12.34", "m")])
|
|
126
|
+
|
|
127
|
+
test("an expr difference giving %p renders by decimal places, as diff does", () => {
|
|
128
|
+
const ledger = book()
|
|
129
|
+
const viaDiff = rowOf(derive(ledger, { op: "diff", operands: ["F1", "F2"], unit: "%p" }))
|
|
130
|
+
const viaExpr = rowOf(derive(ledger, { op: "expr", formula: "F1 - F2", unit: "%p" }))
|
|
131
|
+
expect(viaDiff).toMatchObject({ value: "4.8", render: "4.8 %p" })
|
|
132
|
+
expect(viaExpr).toMatchObject({ value: "4.8", render: "4.8 %p" })
|
|
133
|
+
expect(recompute(ledger, viaExpr).ok).toBe(true)
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
test("an expr with a - root renders 1000 m - 3 m as 997 m", () => {
|
|
137
|
+
const ledger = book()
|
|
138
|
+
const row = rowOf(derive(ledger, { op: "expr", formula: "F3 - F4", unit: "m" }))
|
|
139
|
+
expect(row).toMatchObject({ value: "997", render: "997 m", exact: { num: "997", den: "1" } })
|
|
140
|
+
expect(recompute(ledger, row).ok).toBe(true)
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
test("an expr with a * root keeps significant figures", () => {
|
|
144
|
+
const ledger = book()
|
|
145
|
+
const row = rowOf(derive(ledger, { op: "expr", formula: "F5 * 1.5", unit: "m" }))
|
|
146
|
+
expect(row).toMatchObject({ value: "19", render: "19 m" })
|
|
147
|
+
expect(recompute(ledger, row).ok).toBe(true)
|
|
148
|
+
})
|
|
149
|
+
})
|
|
@@ -3,7 +3,7 @@ import { leaves, parseExpr } from "./fact-expr.mjs"
|
|
|
3
3
|
import { readRow } from "./fact-ledger-kinds.mjs"
|
|
4
4
|
import { addRow, nextId, validateLedger } from "./fact-ledger.mjs"
|
|
5
5
|
import { recompute, verifyGraph } from "./fact-recompute.mjs"
|
|
6
|
-
import { canonicalUnit, inputScale, refuse } from "./unit-algebra.mjs"
|
|
6
|
+
import { canonicalUnit, inputScale, refuse, RuleError } from "./unit-algebra.mjs"
|
|
7
7
|
import { refusal } from "./unit-errors.mjs"
|
|
8
8
|
import { object, sameDeclared } from "./unit-metadata.mjs"
|
|
9
9
|
import { display, renderValue, resultPrecision, toText } from "./unit-precision.mjs"
|
|
@@ -29,7 +29,11 @@ function declaredUnit(spec, rule) {
|
|
|
29
29
|
return canonicalUnit(declared)
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
+
/** Refused before any work, under its own code: time is the caller's (spec.now), never the clock. */
|
|
33
|
+
const MISSING_NOW = Object.freeze({ error: "missing_now", shape: "missing_now", expected: "an ISO timestamp in spec.now", detail: "derive needs a caller-supplied now timestamp" })
|
|
34
|
+
|
|
32
35
|
export function derive(ledger, spec) {
|
|
36
|
+
if (object(spec) && typeof spec.now !== "string") return { ...MISSING_NOW }
|
|
33
37
|
try {
|
|
34
38
|
if (!object(spec)) refuse("spec must be an object")
|
|
35
39
|
const errors = validateLedger(ledger)
|
|
@@ -54,13 +58,14 @@ export function derive(ledger, spec) {
|
|
|
54
58
|
const result = compute(spec.op, rows, expected, ast, context, spec.formula)
|
|
55
59
|
const row = addRow(ledger, {
|
|
56
60
|
kind: rule.kind, unit: expected, quote: "", extracted_by: "fact-derive",
|
|
57
|
-
observed_at: spec.now
|
|
61
|
+
observed_at: spec.now,
|
|
58
62
|
label: spec.label ?? `${spec.op}-${nextId(ledger, rule.kind).toLowerCase()}`,
|
|
59
63
|
...result, operation: spec.op, operands: ids, derived_from: ids,
|
|
60
64
|
rationale: spec.rationale ?? null, context,
|
|
61
65
|
})
|
|
62
66
|
return { row, compatibility: rule.compatibility }
|
|
63
67
|
} catch (error) {
|
|
68
|
+
if (!(error instanceof RuleError)) throw error
|
|
64
69
|
return { error: "incompatible_operands", ...refusal(error) }
|
|
65
70
|
}
|
|
66
71
|
}
|
|
@@ -2,58 +2,12 @@ import { describe, expect, onTestFinished, test } from "bun:test"
|
|
|
2
2
|
import { mkdtempSync, rmSync } from "node:fs"
|
|
3
3
|
import { tmpdir } from "node:os"
|
|
4
4
|
import { join } from "node:path"
|
|
5
|
-
|
|
6
|
-
import { derive
|
|
5
|
+
import { recompute } from "./fact-derive.mjs"
|
|
6
|
+
import { derive } from "./tests/derive-clock"
|
|
7
7
|
import { addRow, emptyLedger, readLedger, writeLedger } from "./fact-ledger.mjs"
|
|
8
|
+
import { HASH, EOK_WON, Row, Ledger, measured, ledgerOf, fixture, rowOf } from "./tests/fact-derive-support"
|
|
8
9
|
|
|
9
|
-
const HASH = "a".repeat(64)
|
|
10
|
-
const EOK_WON = "\uC5B5\uC6D0"
|
|
11
|
-
|
|
12
|
-
type Row = Record<string, unknown>
|
|
13
|
-
type Ledger = { schemaVersion: number; rows: Row[]; events: unknown[] }
|
|
14
|
-
|
|
15
|
-
function measured(factId: string, value: string, unit: string, extra: Row = {}): Row {
|
|
16
|
-
return {
|
|
17
|
-
fact_id: factId,
|
|
18
|
-
kind: "measured",
|
|
19
|
-
value,
|
|
20
|
-
unit,
|
|
21
|
-
raw: `${value} ${unit}`,
|
|
22
|
-
quote: `The figure is ${value} ${unit}.`,
|
|
23
|
-
source_id: "S1",
|
|
24
|
-
locator: { artifact: "sources/S1/text.txt", char_start: 0, char_end: 20 },
|
|
25
|
-
snapshot_sha256: HASH,
|
|
26
|
-
extracted_by: "fact add --find",
|
|
27
|
-
observed_at: "2026-10-23T08:00:00Z",
|
|
28
|
-
label: factId.toLowerCase(),
|
|
29
|
-
...extra,
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
function ledgerOf(rows: Row[]): Ledger {
|
|
34
|
-
const ledger = emptyLedger() as Ledger
|
|
35
|
-
for (const row of rows) addRow(ledger, row)
|
|
36
|
-
return ledger
|
|
37
|
-
}
|
|
38
10
|
|
|
39
|
-
function fixture(): Ledger {
|
|
40
|
-
return ledgerOf([
|
|
41
|
-
measured("F5", "100", EOK_WON, { population: "all" }),
|
|
42
|
-
measured("F6", "200", EOK_WON, { population: "all" }),
|
|
43
|
-
measured("F7", "200", EOK_WON, { population: "subset" }),
|
|
44
|
-
measured("F8", "15", "%"),
|
|
45
|
-
measured("F9", "20", "%"),
|
|
46
|
-
measured("F12", "320", "m"),
|
|
47
|
-
measured("F19", "100", "case", { valid_at: "2025-Q3", entity: "acme" }),
|
|
48
|
-
measured("F20", "112", "case", { valid_at: "2026-Q3", entity: "acme" }),
|
|
49
|
-
measured("F30", "3.2", "m"),
|
|
50
|
-
])
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
function rowOf(result: { row?: Row; error?: string; detail?: string }): Row {
|
|
54
|
-
if (!result.row) throw new Error(`expected a row, got ${result.error}: ${result.detail}`)
|
|
55
|
-
return result.row
|
|
56
|
-
}
|
|
57
11
|
|
|
58
12
|
describe("derive: checked operations", () => {
|
|
59
13
|
test("sum of compatible rows stores the exact sum and appends a derived row", () => {
|
|
@@ -99,7 +53,7 @@ describe("derive: checked operations", () => {
|
|
|
99
53
|
|
|
100
54
|
test("an unknown or missing operand is refused", () => {
|
|
101
55
|
const ledger = fixture()
|
|
102
|
-
expect(derive(ledger, { op: "sum", operands: ["F5", "F99"], unit: EOK_WON }).error).
|
|
56
|
+
expect(derive(ledger, { op: "sum", operands: ["F5", "F99"], unit: EOK_WON }).error).toBe("incompatible_operands")
|
|
103
57
|
expect(ledger.rows).toHaveLength(9)
|
|
104
58
|
})
|
|
105
59
|
})
|
|
@@ -158,8 +112,8 @@ describe("derive: expr and exact chaining", () => {
|
|
|
158
112
|
// unit-rules.md: expr units are inferred with no rationale escape, and an omitted unit is an error, not null.
|
|
159
113
|
test("expr needs a declared unit that follows from its operands", () => {
|
|
160
114
|
const ledger = fixture()
|
|
161
|
-
expect(derive(ledger, { op: "expr", formula: "F12 / F30", rationale: "span over sample length" }).error).
|
|
162
|
-
expect(derive(ledger, { op: "expr", formula: "F12 / F30", unit: "m" }).error).
|
|
115
|
+
expect(derive(ledger, { op: "expr", formula: "F12 / F30", rationale: "span over sample length" }).error).toBe("incompatible_operands")
|
|
116
|
+
expect(derive(ledger, { op: "expr", formula: "F12 / F30", unit: "m" }).error).toBe("incompatible_operands")
|
|
163
117
|
const result = derive(ledger, { op: "expr", formula: "F12 / F30", unit: null })
|
|
164
118
|
expect(result.compatibility).toBe("unchecked")
|
|
165
119
|
expect(rowOf(result)).toMatchObject({ value: "100", formula: "F12 / F30", operands: ["F12", "F30"] })
|
|
@@ -168,7 +122,7 @@ describe("derive: expr and exact chaining", () => {
|
|
|
168
122
|
test("expr refuses unsupported tokens and absent ids", () => {
|
|
169
123
|
const ledger = fixture()
|
|
170
124
|
for (const formula of ["F12 ** 2", "Math.max(F12)", "F12 / F404", "F12 / 0", "(F12"]) {
|
|
171
|
-
expect(derive(ledger, { op: "expr", formula, unit: null }).error).
|
|
125
|
+
expect(derive(ledger, { op: "expr", formula, unit: null }).error).toBe("incompatible_operands")
|
|
172
126
|
}
|
|
173
127
|
expect(ledger.rows).toHaveLength(9)
|
|
174
128
|
})
|
|
@@ -234,142 +188,3 @@ describe("recompute", () => {
|
|
|
234
188
|
expect(result.detail).toContain("F2")
|
|
235
189
|
})
|
|
236
190
|
})
|
|
237
|
-
|
|
238
|
-
describe("unit rules: the design review's counterexamples", () => {
|
|
239
|
-
const book = () =>
|
|
240
|
-
ledgerOf([
|
|
241
|
-
measured("F1", "20", "%"),
|
|
242
|
-
measured("F2", "10", "%"),
|
|
243
|
-
measured("F3", "5", "pp"),
|
|
244
|
-
measured("F4", "25", "C", { valid_at: "2025", entity: "x" }),
|
|
245
|
-
measured("F5", "30", "C", { valid_at: "2026", entity: "x" }),
|
|
246
|
-
measured("F6", "200", "person"),
|
|
247
|
-
measured("F7", "100", "person"),
|
|
248
|
-
measured("F8", "100", "USD"),
|
|
249
|
-
measured("F9", "20", "USD/person"),
|
|
250
|
-
measured("F10", "0", "m"),
|
|
251
|
-
])
|
|
252
|
-
|
|
253
|
-
test("a sum of % operands without one shared reference is refused", () => {
|
|
254
|
-
const ledger = book()
|
|
255
|
-
const base = { op: "sum", operands: ["F1", "F2"], unit: "%", disjoint: true, rationale: "disjoint shares" }
|
|
256
|
-
expect(derive(ledger, base).error).toBe("incompatible_operands")
|
|
257
|
-
expect(derive(ledger, { ...base, reference: { F1: "voters", F2: "residents" } }).error).toBe("incompatible_operands")
|
|
258
|
-
expect(rowOf(derive(ledger, { ...base, reference: { F1: "voters", F2: "voters" } }))).toMatchObject({ value: "30", unit: "%" })
|
|
259
|
-
})
|
|
260
|
-
|
|
261
|
-
test("20% - 5pp is 15%", () => {
|
|
262
|
-
const row = rowOf(derive(book(), { op: "diff", operands: ["F1", "F3"], unit: "%" }))
|
|
263
|
-
expect(row).toMatchObject({ value: "15", unit: "%", render: "15%" })
|
|
264
|
-
})
|
|
265
|
-
|
|
266
|
-
test("growth on a Celsius scale is refused", () => {
|
|
267
|
-
const result = derive(book(), { op: "growth", from: "F4", to: "F5", unit: "%" })
|
|
268
|
-
expect(result.error).toBe("incompatible_operands")
|
|
269
|
-
expect(result.detail).toContain("affine")
|
|
270
|
-
})
|
|
271
|
-
|
|
272
|
-
test("temperatures are not summed or differenced as absolute values; ratio refuses them; convert C to F is allowed", () => {
|
|
273
|
-
const ledger = book()
|
|
274
|
-
const refused = [
|
|
275
|
-
{ op: "sum", operands: ["F4", "F5"], unit: "C" },
|
|
276
|
-
{ op: "diff", operands: ["F5", "F4"], unit: "C" },
|
|
277
|
-
{ op: "expr", formula: "F4 + F5", unit: "C" },
|
|
278
|
-
{ op: "expr", formula: "F5 - F4", unit: "C" },
|
|
279
|
-
{ op: "ratio", num: "F5", den: "F4", unit: null },
|
|
280
|
-
]
|
|
281
|
-
for (const spec of refused) {
|
|
282
|
-
const result = derive(ledger, spec)
|
|
283
|
-
expect({ spec, error: result.error, detail: result.detail }).toEqual({ spec, error: "incompatible_operands", detail: expect.stringContaining("affine") })
|
|
284
|
-
}
|
|
285
|
-
const converted = rowOf(derive(ledger, { op: "convert", operands: ["F4"], to: "F" }))
|
|
286
|
-
expect(converted).toMatchObject({ value: "77", unit: "F" })
|
|
287
|
-
expect(recompute(ledger, converted).ok).toBe(true)
|
|
288
|
-
})
|
|
289
|
-
|
|
290
|
-
test("a convert row relabeled to an alias in both unit and context.target is detected", () => {
|
|
291
|
-
const ledger = book()
|
|
292
|
-
const row = rowOf(derive(ledger, { op: "convert", operands: ["F10"], to: "km" }))
|
|
293
|
-
const relabeled = recompute(ledger, { ...row, unit: "kilometre", raw: "0 kilometre (0 m)", render: "0 kilometre (0 m)", context: { target: "kilometre" } })
|
|
294
|
-
expect(relabeled.ok).toBe(false)
|
|
295
|
-
expect(relabeled.detail).toContain('context.target "kilometre" is not canonical')
|
|
296
|
-
})
|
|
297
|
-
|
|
298
|
-
test("a share of 200 out of 100 is refused", () => {
|
|
299
|
-
expect(derive(book(), { op: "share", part: "F6", whole: "F7", unit: "%" }).error).toBe("incompatible_operands")
|
|
300
|
-
})
|
|
301
|
-
|
|
302
|
-
test("100 USD / (20 USD/person) is 5 person", () => {
|
|
303
|
-
const ledger = book()
|
|
304
|
-
const row = rowOf(derive(ledger, { op: "ratio", num: "F8", den: "F9", unit: "person" }))
|
|
305
|
-
expect(row).toMatchObject({ value: "5", unit: "person", exact: { num: "5", den: "1" } })
|
|
306
|
-
expect(recompute(ledger, row).ok).toBe(true)
|
|
307
|
-
})
|
|
308
|
-
|
|
309
|
-
test("convert 0 m to km relabeled cm is detected though the value is unchanged", () => {
|
|
310
|
-
const ledger = book()
|
|
311
|
-
const row = rowOf(derive(ledger, { op: "convert", operands: ["F10"], to: "km" }))
|
|
312
|
-
expect(recompute(ledger, row).ok).toBe(true)
|
|
313
|
-
const relabeled = recompute(ledger, { ...row, unit: "cm", raw: "0 cm (0 m)", render: "0 cm (0 m)" })
|
|
314
|
-
expect(relabeled.ok).toBe(false)
|
|
315
|
-
expect(relabeled.detail).toContain("km")
|
|
316
|
-
})
|
|
317
|
-
|
|
318
|
-
test("expr % + %p is %, in either order; a ratio over unequal % and %p is refused", () => {
|
|
319
|
-
const ledger = book()
|
|
320
|
-
for (const formula of ["F1 + F3", "F3 + F1"]) {
|
|
321
|
-
const row = rowOf(derive(ledger, { op: "expr", formula, unit: "%" }))
|
|
322
|
-
expect(row).toMatchObject({ value: "25", unit: "%" })
|
|
323
|
-
expect(recompute(ledger, row).ok).toBe(true)
|
|
324
|
-
}
|
|
325
|
-
const ratio = derive(ledger, { op: "ratio", num: "F1", den: "F3", unit: null })
|
|
326
|
-
expect(ratio.error).toBe("incompatible_operands")
|
|
327
|
-
expect(ratio.detail).toContain("needs equal units")
|
|
328
|
-
})
|
|
329
|
-
|
|
330
|
-
test("a stored context on an operation that has none is malformed", () => {
|
|
331
|
-
const ledger = book()
|
|
332
|
-
const diff = rowOf(derive(ledger, { op: "diff", operands: ["F1", "F2"], unit: "%p" }))
|
|
333
|
-
const expr = rowOf(derive(ledger, { op: "expr", formula: "F1 - F2", unit: "%p" }))
|
|
334
|
-
for (const [row, context] of [[diff, "x"], [expr, { target: "km" }]] as const) {
|
|
335
|
-
const result = recompute(ledger, { ...row, context })
|
|
336
|
-
expect(result.ok).toBe(false)
|
|
337
|
-
expect(result.detail).toContain("rows carry no context")
|
|
338
|
-
}
|
|
339
|
-
})
|
|
340
|
-
|
|
341
|
-
test("expr 20% - 10% is %p, never %", () => {
|
|
342
|
-
const ledger = book()
|
|
343
|
-
expect(derive(ledger, { op: "expr", formula: "F1 - F2", unit: "%" }).error).toBe("incompatible_operands")
|
|
344
|
-
const row = rowOf(derive(ledger, { op: "expr", formula: "F1 - F2", unit: "%p" }))
|
|
345
|
-
expect(row).toMatchObject({ value: "10", unit: "%p" })
|
|
346
|
-
expect(recompute(ledger, row).ok).toBe(true)
|
|
347
|
-
})
|
|
348
|
-
})
|
|
349
|
-
|
|
350
|
-
describe("render precision of expr results (invariant 5)", () => {
|
|
351
|
-
const book = () => ledgerOf([measured("F1", "20.25", "%"), measured("F2", "15.5", "%"), measured("F3", "1000", "m"), measured("F4", "3", "m"), measured("F5", "12.34", "m")])
|
|
352
|
-
|
|
353
|
-
test("an expr difference giving %p renders by decimal places, as diff does", () => {
|
|
354
|
-
const ledger = book()
|
|
355
|
-
const viaDiff = rowOf(derive(ledger, { op: "diff", operands: ["F1", "F2"], unit: "%p" }))
|
|
356
|
-
const viaExpr = rowOf(derive(ledger, { op: "expr", formula: "F1 - F2", unit: "%p" }))
|
|
357
|
-
expect(viaDiff).toMatchObject({ value: "4.8", render: "4.8 %p" })
|
|
358
|
-
expect(viaExpr).toMatchObject({ value: "4.8", render: "4.8 %p" })
|
|
359
|
-
expect(recompute(ledger, viaExpr).ok).toBe(true)
|
|
360
|
-
})
|
|
361
|
-
|
|
362
|
-
test("an expr with a - root renders 1000 m - 3 m as 997 m", () => {
|
|
363
|
-
const ledger = book()
|
|
364
|
-
const row = rowOf(derive(ledger, { op: "expr", formula: "F3 - F4", unit: "m" }))
|
|
365
|
-
expect(row).toMatchObject({ value: "997", render: "997 m", exact: { num: "997", den: "1" } })
|
|
366
|
-
expect(recompute(ledger, row).ok).toBe(true)
|
|
367
|
-
})
|
|
368
|
-
|
|
369
|
-
test("an expr with a * root keeps significant figures", () => {
|
|
370
|
-
const ledger = book()
|
|
371
|
-
const row = rowOf(derive(ledger, { op: "expr", formula: "F5 * 1.5", unit: "m" }))
|
|
372
|
-
expect(row).toMatchObject({ value: "19", render: "19 m" })
|
|
373
|
-
expect(recompute(ledger, row).ok).toBe(true)
|
|
374
|
-
})
|
|
375
|
-
})
|