oh-my-opencode 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/agents/refusal-rule.d.ts +7 -0
- 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/dist/config/schema/hooks.d.ts +1 -0
- package/dist/create-hooks.d.ts +1 -0
- package/dist/features/boulder-state/storage.d.ts +1 -1
- package/dist/features/boulder-state/types.d.ts +1 -1
- package/dist/hooks/atlas/idle-continuation.d.ts +2 -1
- package/dist/hooks/atlas/types.d.ts +9 -1
- package/dist/hooks/index.d.ts +1 -0
- package/dist/hooks/momus-review-persistence/hook.d.ts +19 -0
- package/dist/hooks/momus-review-persistence/index.d.ts +1 -0
- package/dist/hooks/momus-review-persistence/review-resolution.d.ts +20 -0
- package/dist/hooks/prometheus-md-only/constants.d.ts +2 -0
- package/dist/hooks/prometheus-md-only/fixtures/denial-log.d.ts +1 -0
- package/dist/hooks/prometheus-md-only/fixtures/guarded-hashline.d.ts +1 -0
- package/dist/hooks/prometheus-md-only/fixtures/invoke-before.d.ts +7 -0
- package/dist/hooks/prometheus-md-only/fixtures/opencode-patch-parser.d.ts +2 -0
- package/dist/hooks/prometheus-md-only/hook.d.ts +5 -1
- package/dist/hooks/prometheus-md-only/path-policy.d.ts +2 -9
- package/dist/hooks/prometheus-md-only/vendor/opencode-patch-parser.d.ts +22 -0
- package/dist/index.js +2322 -1045
- package/dist/plugin/hooks/create-core-hooks.d.ts +1 -0
- package/dist/plugin/hooks/create-tool-guard-hooks.d.ts +2 -1
- package/dist/shared/posthog-activity-state.d.ts +1 -0
- package/dist/shared/posthog.d.ts +1 -2
- package/dist/shared/resolve-file-write-path.d.ts +6 -0
- package/dist/skills/ast-grep/README.md +1 -1
- package/dist/skills/ast-grep/install.ps1 +5 -3
- package/dist/skills/ast-grep/install.sh +4 -5
- package/dist/skills/ast-grep/references/install.md +2 -2
- package/dist/skills/ast-grep/scripts/ast_grep_helper.py +13 -3
- package/dist/skills/browser/runtime/omowright/manifest.json +1 -1
- package/dist/skills/coding-agent-sessions/AGENTS.md +5 -4
- package/dist/skills/coding-agent-sessions/SKILL.md +11 -11
- package/dist/skills/coding-agent-sessions/references/all-platforms.md +2 -2
- package/dist/skills/coding-agent-sessions/references/codex.md +4 -4
- package/dist/skills/coding-agent-sessions/scripts/agent_sessions/cli.py +1 -1
- package/dist/skills/coding-agent-sessions/scripts/find-agent-sessions.py +6 -3
- package/dist/skills/frontend/ATTRIBUTION.md +1 -1
- package/dist/skills/frontend/SKILL.md +34 -12
- package/dist/skills/frontend/references/design/README.md +2 -2
- package/dist/skills/frontend/references/design/_INDEX.md +2 -1
- package/dist/skills/frontend/references/design/component-catalogs.md +14 -10
- package/dist/skills/frontend/references/design/design-system-architecture.md +19 -6
- package/dist/skills/frontend/references/design/grounding.md +29 -0
- package/dist/skills/frontend/references/ui-ux-db/scripts/design_system.py +2 -0
- package/dist/skills/frontend/references/ui-ux-db/scripts/search.py +2 -0
- package/dist/skills/ultimate-browsing/SKILL.md +5 -4
- package/dist/skills/ultimate-browsing/engine/AGENTS.md +2 -2
- package/dist/skills/ultimate-browsing/engine/__main__.py +9 -6
- package/dist/skills/ultimate-browsing/engine/summary.py +1 -1
- package/dist/skills/ultimate-browsing/references/insane-search/README.md +4 -4
- package/dist/skills/ultimate-browsing/scripts/extract_cookies.py +4 -2
- package/dist/skills/ulw-research/AGENTS.md +8 -4
- package/dist/skills/ulw-research/SKILL.md +7 -316
- package/dist/skills/ulw-research/references/deliverable-phase.md +23 -21
- package/dist/skills/ulw-research/references/fact-ledger-grammar.md +7 -3
- package/dist/skills/ulw-research/references/fact-ledger.md +42 -8
- package/dist/skills/ulw-research/references/fidelity-claim-kinds.md +1 -1
- package/dist/skills/ulw-research/references/markdown-blocks.md +12 -0
- package/dist/skills/ulw-research/references/report-gates.md +12 -5
- package/dist/skills/ulw-research/references/research-collection.md +195 -0
- package/dist/skills/ulw-research/references/research-delivery.md +123 -0
- package/dist/skills/ulw-research/scripts/claim-kinds.mjs +33 -5
- package/dist/skills/ulw-research/scripts/claim-kinds.test.ts +37 -2
- package/dist/skills/ulw-research/scripts/claim-prefix.test.ts +76 -0
- package/dist/skills/ulw-research/scripts/contracts-validation.test.ts +83 -0
- package/dist/skills/ulw-research/scripts/contracts.mjs +13 -4
- package/dist/skills/ulw-research/scripts/contracts.test.ts +6 -110
- package/dist/skills/ulw-research/scripts/conversion-altered.test.ts +154 -0
- package/dist/skills/ulw-research/scripts/conversion-originals.test.ts +297 -0
- package/dist/skills/ulw-research/scripts/conversion-probe-classes.test.ts +361 -0
- package/dist/skills/ulw-research/scripts/conversion-probe.mjs +229 -0
- package/dist/skills/ulw-research/scripts/conversion-probe.test.ts +218 -0
- package/dist/skills/ulw-research/scripts/copy-returns-polish.test.ts +4 -3
- package/dist/skills/ulw-research/scripts/css-hiding.mjs +158 -0
- package/dist/skills/ulw-research/scripts/css-lite.mjs +4 -2
- package/dist/skills/ulw-research/scripts/delivery-barrier-meaning.test.ts +186 -0
- package/dist/skills/ulw-research/scripts/delivery-barrier.test.ts +40 -87
- package/dist/skills/ulw-research/scripts/entities.mjs +13 -4
- package/dist/skills/ulw-research/scripts/fact-commands-fill.mjs +11 -1
- package/dist/skills/ulw-research/scripts/fact-commands-meaning.mjs +13 -9
- package/dist/skills/ulw-research/scripts/fact-commands-polish.mjs +40 -18
- package/dist/skills/ulw-research/scripts/fact-commands-verify.mjs +25 -8
- package/dist/skills/ulw-research/scripts/fact-commands.mjs +35 -16
- package/dist/skills/ulw-research/scripts/fact-derive-2.test.ts +149 -0
- package/dist/skills/ulw-research/scripts/fact-derive.mjs +7 -2
- package/dist/skills/ulw-research/scripts/fact-derive.test.ts +7 -192
- package/dist/skills/ulw-research/scripts/fact-fill.mjs +6 -4
- package/dist/skills/ulw-research/scripts/fact-ledger-kinds.mjs +6 -7
- package/dist/skills/ulw-research/scripts/fact-ledger-kinds.test.ts +2 -1
- package/dist/skills/ulw-research/scripts/fact-ledger-legacy.test.ts +2 -1
- package/dist/skills/ulw-research/scripts/fact-locate.mjs +15 -9
- package/dist/skills/ulw-research/scripts/fact-unit-dimensions-2.test.ts +149 -0
- package/dist/skills/ulw-research/scripts/fact-unit-dimensions.test.ts +4 -168
- package/dist/skills/ulw-research/scripts/fact-unit-rules.test.ts +3 -229
- package/dist/skills/ulw-research/scripts/fidelity-claims.mjs +4 -3
- package/dist/skills/ulw-research/scripts/fidelity-csv.mjs +32 -0
- package/dist/skills/ulw-research/scripts/fidelity-lexicon.mjs +1 -1
- package/dist/skills/ulw-research/scripts/fidelity-originals.mjs +161 -0
- package/dist/skills/ulw-research/scripts/fidelity-policy.mjs +4 -4
- package/dist/skills/ulw-research/scripts/fidelity-records.mjs +81 -14
- package/dist/skills/ulw-research/scripts/fidelity-snapshot-readability.mjs +49 -0
- package/dist/skills/ulw-research/scripts/fidelity-sources.mjs +76 -0
- package/dist/skills/ulw-research/scripts/fidelity-trace.mjs +25 -11
- package/dist/skills/ulw-research/scripts/fidelity-value-unit.mjs +202 -0
- package/dist/skills/ulw-research/scripts/fidelity-values.mjs +40 -39
- package/dist/skills/ulw-research/scripts/fidelity-values.test.ts +8 -29
- package/dist/skills/ulw-research/scripts/gates-fidelity.mjs +8 -6
- package/dist/skills/ulw-research/scripts/gates-static-headings.test.ts +17 -0
- package/dist/skills/ulw-research/scripts/gates-static.mjs +10 -1
- package/dist/skills/ulw-research/scripts/html-lite.mjs +6 -2
- package/dist/skills/ulw-research/scripts/html-markup.mjs +2 -2
- package/dist/skills/ulw-research/scripts/html-text-map.mjs +140 -0
- package/dist/skills/ulw-research/scripts/html-text-map.test.ts +148 -0
- package/dist/skills/ulw-research/scripts/markdown-closers.mjs +126 -0
- package/dist/skills/ulw-research/scripts/md-lite-boundaries.test.ts +46 -0
- package/dist/skills/ulw-research/scripts/md-lite.mjs +1 -1
- package/dist/skills/ulw-research/scripts/number-grammar.mjs +33 -0
- package/dist/skills/ulw-research/scripts/number-grammar.test.ts +36 -0
- package/dist/skills/ulw-research/scripts/numerals.mjs +18 -7
- package/dist/skills/ulw-research/scripts/numerals.test.ts +3 -0
- package/dist/skills/ulw-research/scripts/outcome-start.mjs +66 -0
- package/dist/skills/ulw-research/scripts/outcome-start.test.ts +109 -0
- package/dist/skills/ulw-research/scripts/outcome.mjs +11 -22
- package/dist/skills/ulw-research/scripts/page-source-locator.test.ts +187 -0
- package/dist/skills/ulw-research/scripts/placeholder.mjs +18 -6
- package/dist/skills/ulw-research/scripts/polish-apply.mjs +3 -2
- package/dist/skills/ulw-research/scripts/polish-markup.test.ts +15 -1
- package/dist/skills/ulw-research/scripts/polish-partial.test.ts +42 -0
- package/dist/skills/ulw-research/scripts/raw-decode.mjs +110 -0
- package/dist/skills/ulw-research/scripts/raw-locator.mjs +47 -0
- package/dist/skills/ulw-research/scripts/raw-snapshot.test.ts +139 -0
- package/dist/skills/ulw-research/scripts/report-tools-2.test.ts +233 -0
- package/dist/skills/ulw-research/scripts/report-tools-3.test.ts +141 -0
- package/dist/skills/ulw-research/scripts/report-tools-4.test.ts +135 -0
- package/dist/skills/ulw-research/scripts/report-tools-5.test.ts +231 -0
- package/dist/skills/ulw-research/scripts/report-tools-commands.mjs +1 -1
- package/dist/skills/ulw-research/scripts/report-tools.mjs +7 -1
- package/dist/skills/ulw-research/scripts/report-tools.test.ts +5 -800
- package/dist/skills/ulw-research/scripts/snapshot-readability.test.ts +116 -0
- package/dist/skills/ulw-research/scripts/source-commands.mjs +79 -26
- package/dist/skills/ulw-research/scripts/source-integrity.test.ts +243 -0
- package/dist/skills/ulw-research/scripts/source-refresh.mjs +82 -0
- package/dist/skills/ulw-research/scripts/source-snapshot.mjs +73 -69
- package/dist/skills/ulw-research/scripts/source-snapshot.test.ts +7 -4
- package/dist/skills/ulw-research/scripts/source-view.mjs +89 -0
- package/dist/skills/ulw-research/scripts/sources-citation.mjs +203 -0
- package/dist/skills/ulw-research/scripts/sources-citation.test.ts +236 -0
- package/dist/skills/ulw-research/scripts/tests/contracts-support.ts +147 -0
- package/dist/skills/ulw-research/scripts/tests/delivery-barrier-session.ts +59 -0
- package/dist/skills/ulw-research/scripts/tests/derive-clock.ts +4 -0
- package/dist/skills/ulw-research/scripts/tests/fact-derive-support.ts +65 -0
- package/dist/skills/ulw-research/scripts/tests/fact-unit-dimensions-support.ts +41 -0
- package/dist/skills/ulw-research/scripts/tests/fact-unit-rules-support.ts +241 -0
- package/dist/skills/ulw-research/scripts/tests/fidelity-values-support.ts +30 -0
- package/dist/skills/ulw-research/scripts/tests/fixtures/conversion-probe.html +14 -0
- package/dist/skills/ulw-research/scripts/tests/fixtures/sources-shapes.html +20 -0
- package/dist/skills/ulw-research/scripts/tests/numeral-classes.ts +196 -0
- package/dist/skills/ulw-research/scripts/tests/report-tools-support.ts +182 -0
- package/dist/skills/ulw-research/scripts/tests/value-mode-support.ts +48 -0
- package/dist/skills/ulw-research/scripts/units.mjs +14 -3
- package/dist/skills/ulw-research/scripts/units.test.ts +10 -0
- package/dist/skills/ulw-research/scripts/value-mode-unit-classes.test.ts +255 -0
- package/dist/skills/ulw-research/scripts/value-mode-unit.test.ts +179 -0
- package/dist/skills/ulw-research/scripts/value-rules-cli.test.ts +157 -0
- package/dist/skills/ulw-research/scripts/value-rules-followups.test.ts +72 -0
- package/dist/skills/ulw-research/scripts/value-rules-priority.test.ts +80 -0
- package/dist/tools/delegate-task/executor-types.d.ts +1 -0
- package/dist/tools/delegate-task/sync-session-existence.d.ts +4 -0
- package/dist/tools/delegate-task/types.d.ts +1 -0
- package/dist/tools/skill/description-formatter.d.ts +2 -1
- package/dist/tools/skill/types.d.ts +6 -0
- package/dist/tui.js +131 -33
- package/package.json +18 -15
- package/packages/git-bash-mcp/package.json +1 -1
- 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
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { describe, expect, test } from "bun:test"
|
|
2
|
-
|
|
3
2
|
import {
|
|
4
3
|
ANSWERED_BY,
|
|
5
4
|
DEFECT_CODES,
|
|
@@ -22,120 +21,14 @@ import {
|
|
|
22
21
|
validateOutcomeManifest,
|
|
23
22
|
validateRepairState,
|
|
24
23
|
} from "./contracts.mjs"
|
|
24
|
+
import { SEVERITIES, STATIC_CODES_EXPECTED, LAYOUT_CODES_EXPECTED, FIDELITY_BLOCKERS, FIDELITY_MAJORS, FIDELITY_MINORS, FIDELITY_CODES_EXPECTED, fourGateManifest, sampleManifest } from "./tests/contracts-support"
|
|
25
25
|
|
|
26
|
-
const SEVERITIES = new Set(["blocker", "major", "minor"])
|
|
27
|
-
const STATIC_CODES_EXPECTED = [
|
|
28
|
-
"korean_no_keep_all",
|
|
29
|
-
"palette_off_token",
|
|
30
|
-
"emoji_in_prose",
|
|
31
|
-
"em_dash_in_prose",
|
|
32
|
-
"en_dash_in_prose",
|
|
33
|
-
"heading_too_long",
|
|
34
|
-
"heading_generic",
|
|
35
|
-
"unsourced_number",
|
|
36
|
-
"chart_without_figure",
|
|
37
|
-
"chart_svg_no_text",
|
|
38
|
-
"chart_svg_missing_labels",
|
|
39
|
-
"report_without_visual",
|
|
40
|
-
"image_missing_alt",
|
|
41
|
-
"korean_serif_font",
|
|
42
|
-
"section_without_citation",
|
|
43
|
-
"missing_closing_section",
|
|
44
|
-
"missing_citation_section",
|
|
45
|
-
"broken_asset_reference",
|
|
46
|
-
"missing_required_section",
|
|
47
|
-
]
|
|
48
|
-
const LAYOUT_CODES_EXPECTED = [
|
|
49
|
-
"layout_overflow",
|
|
50
|
-
"layout_text_clipped",
|
|
51
|
-
"layout_scroll_container",
|
|
52
|
-
"layout_image_distorted",
|
|
53
|
-
"layout_sibling_overlap",
|
|
54
|
-
]
|
|
55
|
-
// Fidelity vocabulary contract (#11009): severity and integrity per code are fixed here.
|
|
56
|
-
const FIDELITY_BLOCKERS = [
|
|
57
|
-
"unresolved_placeholder",
|
|
58
|
-
"value_mismatch",
|
|
59
|
-
"unit_mismatch",
|
|
60
|
-
"quote_not_verbatim",
|
|
61
|
-
"claim_status_invalid",
|
|
62
|
-
"incompatible_operands",
|
|
63
|
-
"pointer_label_mismatch",
|
|
64
|
-
"quote_anchor_unresolved",
|
|
65
|
-
"unit_duplicated",
|
|
66
|
-
"polish_diff_fail",
|
|
67
|
-
"fidelity_interview_incomplete",
|
|
68
|
-
"derived_not_allowed",
|
|
69
|
-
"source_outside_floor",
|
|
70
|
-
"brief_changed",
|
|
71
|
-
"snapshot_hash_mismatch",
|
|
72
|
-
"chart_value_untraceable",
|
|
73
|
-
"quote_unbound",
|
|
74
|
-
"edition_unverified",
|
|
75
|
-
]
|
|
76
|
-
const FIDELITY_MAJORS = [
|
|
77
|
-
"untraceable_number",
|
|
78
|
-
"unsupported_verification",
|
|
79
|
-
"claim_kind_mismatch",
|
|
80
|
-
"conflicting_readings",
|
|
81
|
-
"hedge_dropped",
|
|
82
|
-
"direction_flipped",
|
|
83
|
-
"causality_added",
|
|
84
|
-
"qualifier_dropped",
|
|
85
|
-
"unbound_sentence",
|
|
86
|
-
"meaning_drift_confirmed",
|
|
87
|
-
"meaning_unverified",
|
|
88
|
-
"polish_edit_rejected",
|
|
89
|
-
"polish_hedging_decreased",
|
|
90
|
-
"polish_text_churn",
|
|
91
|
-
"snapshot_mismatch",
|
|
92
|
-
"join_integrity_fail",
|
|
93
|
-
"quote_stale_version",
|
|
94
|
-
]
|
|
95
|
-
const FIDELITY_MINORS = [
|
|
96
|
-
"quote_fuzzy_match",
|
|
97
|
-
"name_outside_quote",
|
|
98
|
-
"verification_claim_unmarked",
|
|
99
|
-
"scope_widened_warn",
|
|
100
|
-
"particle_unverified",
|
|
101
|
-
"fallback_without_entity",
|
|
102
|
-
"meaning_drift_warn",
|
|
103
|
-
"polish_edit_unlocated",
|
|
104
|
-
"polish_link_changed",
|
|
105
|
-
"polish_name_changed",
|
|
106
|
-
]
|
|
107
|
-
const FIDELITY_CODES_EXPECTED = [...FIDELITY_BLOCKERS, ...FIDELITY_MAJORS, ...FIDELITY_MINORS]
|
|
108
|
-
|
|
109
|
-
function fourGateManifest() {
|
|
110
|
-
const manifest = sampleManifest()
|
|
111
|
-
const { fidelity: _dropped, ...gates } = manifest.gates
|
|
112
|
-
return { ...manifest, gates }
|
|
113
|
-
}
|
|
114
26
|
|
|
115
|
-
function sampleManifest() {
|
|
116
|
-
return {
|
|
117
|
-
schemaVersion: SCHEMA_VERSION,
|
|
118
|
-
sessionDir: "/tmp/session/20260101-120000",
|
|
119
|
-
startedAt: "2026-01-01T12:00:00.000Z",
|
|
120
|
-
finishedAt: null,
|
|
121
|
-
elapsedMinutes: null,
|
|
122
|
-
lane: "no-format",
|
|
123
|
-
interview: null,
|
|
124
|
-
deliverables: [
|
|
125
|
-
{ format: "pdf", status: "pending", promised: true, updatedAt: "2026-01-01T12:00:00.000Z" },
|
|
126
|
-
],
|
|
127
|
-
renders: [],
|
|
128
|
-
gates: { static: "not_run", fidelity: "not_run", layout: "not_run", visual: "not_run", proofread: "not_run" },
|
|
129
|
-
residualDefects: [],
|
|
130
|
-
repair: null,
|
|
131
|
-
sources: null,
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
27
|
|
|
135
28
|
describe("DEFECT_CODES", () => {
|
|
136
29
|
test("#given the code table #when every code is inspected #then it has a severity, a hint, and an integrity flag", () => {
|
|
137
30
|
expect(Object.keys(DEFECT_CODES).sort()).toEqual(
|
|
138
|
-
[...STATIC_CODES_EXPECTED, "missing_promised_deliverable", ...LAYOUT_CODES_EXPECTED, ...FIDELITY_CODES_EXPECTED].sort(),
|
|
31
|
+
[...STATIC_CODES_EXPECTED, "missing_promised_deliverable", ...LAYOUT_CODES_EXPECTED, ...FIDELITY_CODES_EXPECTED, "conversion_altered_number", "unit_unbound"].sort(),
|
|
139
32
|
)
|
|
140
33
|
for (const [code, entry] of Object.entries(DEFECT_CODES)) {
|
|
141
34
|
expect(SEVERITIES.has(entry.severity), `${code} severity`).toBe(true)
|
|
@@ -154,6 +47,7 @@ describe("DEFECT_CODES", () => {
|
|
|
154
47
|
"missing_promised_deliverable",
|
|
155
48
|
"missing_required_section",
|
|
156
49
|
...FIDELITY_BLOCKERS,
|
|
50
|
+
"conversion_altered_number",
|
|
157
51
|
].sort(),
|
|
158
52
|
)
|
|
159
53
|
for (const code of INTEGRITY_CODES) expect(DEFECT_CODES[code].severity).toBe("blocker")
|
|
@@ -174,8 +68,10 @@ describe("DEFECT_CODES", () => {
|
|
|
174
68
|
|
|
175
69
|
test("#given the fidelity codes #when inspected #then each carries its fixed severity and integrity flag", () => {
|
|
176
70
|
for (const code of FIDELITY_BLOCKERS) expect([code, DEFECT_CODES[code]?.severity, DEFECT_CODES[code]?.integrity]).toEqual([code, "blocker", true])
|
|
71
|
+
expect([DEFECT_CODES.conversion_altered_number?.severity, DEFECT_CODES.conversion_altered_number?.integrity]).toEqual(["blocker", true])
|
|
177
72
|
for (const code of FIDELITY_MAJORS) expect([code, DEFECT_CODES[code]?.severity, DEFECT_CODES[code]?.integrity]).toEqual([code, "major", false])
|
|
178
73
|
for (const code of FIDELITY_MINORS) expect([code, DEFECT_CODES[code]?.severity, DEFECT_CODES[code]?.integrity]).toEqual([code, "minor", false])
|
|
74
|
+
expect([DEFECT_CODES.unit_unbound?.severity, DEFECT_CODES.unit_unbound?.integrity]).toEqual(["minor", false])
|
|
179
75
|
expect(DEFECT_CODES).not.toHaveProperty("incompatible_populations")
|
|
180
76
|
})
|
|
181
77
|
})
|
|
@@ -185,7 +81,7 @@ describe("code groups", () => {
|
|
|
185
81
|
expect([...STATIC_CODES].sort()).toEqual(STATIC_CODES_EXPECTED.slice().sort())
|
|
186
82
|
expect([...LAYOUT_CODES].sort()).toEqual(LAYOUT_CODES_EXPECTED.slice().sort())
|
|
187
83
|
expect([...MANIFEST_CODES]).toEqual(["missing_promised_deliverable"])
|
|
188
|
-
expect([...FIDELITY_CODES].sort()).toEqual(FIDELITY_CODES_EXPECTED.
|
|
84
|
+
expect([...FIDELITY_CODES].sort()).toEqual([...FIDELITY_CODES_EXPECTED, "conversion_altered_number", "unit_unbound"].sort())
|
|
189
85
|
const union = [...STATIC_CODES, ...LAYOUT_CODES, ...MANIFEST_CODES, ...FIDELITY_CODES].sort()
|
|
190
86
|
expect(union).toEqual(Object.keys(DEFECT_CODES).sort())
|
|
191
87
|
expect(new Set(union).size).toBe(union.length)
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { describe, expect, onTestFinished, test } from "bun:test"
|
|
2
|
+
import { copyFileSync, existsSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs"
|
|
3
|
+
import { tmpdir } from "node:os"
|
|
4
|
+
import { join } from "node:path"
|
|
5
|
+
|
|
6
|
+
import { CliError } from "./cli-support.mjs"
|
|
7
|
+
import { DEFECT_CODES } from "./contracts.mjs"
|
|
8
|
+
import { run } from "./report-tools.mjs"
|
|
9
|
+
import { writeSnapshot } from "./source-snapshot.mjs"
|
|
10
|
+
|
|
11
|
+
type Json = Record<string, unknown> & { data?: Record<string, unknown>; error?: string }
|
|
12
|
+
const PAGE = "<p>USD 4.2<sup>2</sup> billion in revenue.</p><p>Staff grew to 1,250 people.</p>"
|
|
13
|
+
|
|
14
|
+
function session() {
|
|
15
|
+
const dir = mkdtempSync(join(tmpdir(), "conversion-altered-"))
|
|
16
|
+
onTestFinished(() => rmSync(dir, { recursive: true, force: true }))
|
|
17
|
+
return dir
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async function snapshot(dir: string, html = PAGE) {
|
|
21
|
+
const file = join(dir, "page.html")
|
|
22
|
+
writeFileSync(file, html)
|
|
23
|
+
return (await run(["source", "snapshot", "--id", "S1", "--html", file, "--session-dir", dir])) as { exitCode: number; json: Json; summary: string }
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
describe("the conversion probe result is consumed", () => {
|
|
27
|
+
test("#given a page whose conversion altered a number #when snapshotted #then the JSON and the summary report it", async () => {
|
|
28
|
+
const result = await snapshot(session())
|
|
29
|
+
expect(result.exitCode).toBe(0)
|
|
30
|
+
expect(result.json.data?.conversion_altered_numbers).toEqual(["4.22"])
|
|
31
|
+
expect(result.summary).toContain("1 altered")
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
test("#given an altered 4.22 in conversion.json #when fact add --find locates it #then it refuses with conversion_altered_number and writes no row", async () => {
|
|
35
|
+
const dir = session()
|
|
36
|
+
await snapshot(dir)
|
|
37
|
+
const result = (await run(["fact", "add", "--source", "S1", "--find", "USD", "--label", "usd_bn", "--session-dir", dir, "--json"])) as { exitCode: number; json: Json }
|
|
38
|
+
expect(result.exitCode).toBe(1)
|
|
39
|
+
expect(result.json.error).toBe("conversion_altered_number")
|
|
40
|
+
expect(existsSync(join(dir, "fact-ledger.json"))).toBe(false)
|
|
41
|
+
expect(DEFECT_CODES.conversion_altered_number).toMatchObject({ severity: "blocker", integrity: true })
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
test("#given an unaltered number in the same snapshot #when fact add --find locates it #then it registers", async () => {
|
|
45
|
+
const dir = session()
|
|
46
|
+
await snapshot(dir)
|
|
47
|
+
const result = (await run(["fact", "add", "--source", "S1", "--find", "Staff grew to", "--label", "staff", "--session-dir", dir, "--json"])) as { exitCode: number; json: Json }
|
|
48
|
+
expect(result.exitCode).toBe(0)
|
|
49
|
+
expect((result.json.data?.row as { value: string }).value).toBe("1250")
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
test("#given a non-ISO observed_at #when writeSnapshot runs #then it throws before writing anything", () => {
|
|
53
|
+
const dir = join(session(), "sources")
|
|
54
|
+
expect(() => writeSnapshot(dir, "S1", { path: "p.html", html: PAGE, extractor: "model", observed_at: "yesterday" })).toThrow(TypeError)
|
|
55
|
+
expect(existsSync(join(dir, "S1"))).toBe(false)
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
test("#given an image named chart-109.png beside a glued 10<sup>9</sup> #when fact add --find locates it #then it refuses and registers nothing", async () => {
|
|
59
|
+
const dir = session()
|
|
60
|
+
const snap = await snapshot(dir, '<p>Output reached 10<sup>9</sup> kWh</p><p><img src="chart-109.png"></p>')
|
|
61
|
+
expect(snap.json.data?.conversion_altered_numbers).toEqual(["109"])
|
|
62
|
+
const result = (await run(["fact", "add", "--source", "S1", "--find", "Output reached", "--label", "output", "--session-dir", dir, "--json"])) as { exitCode: number; json: Json }
|
|
63
|
+
expect(result.exitCode).toBe(1)
|
|
64
|
+
expect(result.json.error).toBe("conversion_altered_number")
|
|
65
|
+
expect(existsSync(join(dir, "fact-ledger.json"))).toBe(false)
|
|
66
|
+
})
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
test("#given a genuine 4.22 beside a glued 4.2<sup>2</sup> #when fact add --find locates the genuine one #then the refusal names the --replace recovery", async () => {
|
|
70
|
+
const dir = session()
|
|
71
|
+
await snapshot(dir, "<p>USD 4.2<sup>2</sup> billion in revenue.</p><p>Margin was 4.22 points.</p>")
|
|
72
|
+
const result = (await run(["fact", "add", "--source", "S1", "--find", "Margin was", "--label", "margin", "--session-dir", dir, "--json"])) as { exitCode: number; json: Json & { message?: string } }
|
|
73
|
+
expect(result.exitCode).toBe(1)
|
|
74
|
+
expect(result.json.error).toBe("conversion_altered_number")
|
|
75
|
+
expect(result.json.message).toContain("source snapshot --id S1 --text <verified.txt> --replace")
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
describe("the documented recovery: source snapshot --replace with a verified text", () => {
|
|
79
|
+
const s = (dir: string) => ["--session-dir", dir]
|
|
80
|
+
|
|
81
|
+
test("#given a refused 4.22 #when the verified text replaces S1 #then fact add registers 4.2 billion and the filled report verifies clean", async () => {
|
|
82
|
+
const dir = session()
|
|
83
|
+
await snapshot(dir)
|
|
84
|
+
const verified = join(dir, "verified.txt")
|
|
85
|
+
writeFileSync(verified, "USD 4.2 billion in revenue.\nStaff grew to 1,250 people.\n")
|
|
86
|
+
const replaced = (await run(["source", "snapshot", "--id", "S1", "--text", verified, "--replace", ...s(dir)])) as { exitCode: number; json: Json }
|
|
87
|
+
expect(replaced.exitCode).toBe(0)
|
|
88
|
+
expect(existsSync(join(dir, "sources", "S1", "conversion.json"))).toBe(false)
|
|
89
|
+
const added = (await run(["fact", "add", "--source", "S1", "--find", "USD", "--label", "usd_bn", ...s(dir), "--json"])) as { exitCode: number; json: Json }
|
|
90
|
+
expect(added.exitCode).toBe(0)
|
|
91
|
+
expect((added.json.data?.row as { value: string }).value).toBe("4200000000")
|
|
92
|
+
copyFileSync(join(import.meta.dir, "tests", "fixtures", "fidelity-brief-locked.md"), join(dir, "brief.md"))
|
|
93
|
+
writeFileSync(join(dir, "report.src.html"), [
|
|
94
|
+
'<!doctype html><html lang="en"><head><meta charset="utf-8"><title>Revenue</title></head><body><h1>Revenue</h1>',
|
|
95
|
+
"<h2>Findings</h2><p>Revenue was {{F1:usd_bn}} [S1].</p>",
|
|
96
|
+
"<h2>How this report was made</h2><p>Collected from one cited public page [S1].</p>",
|
|
97
|
+
'<h2>Sources</h2><ol><li id="s1">[S1] Revenue page, retrieved 2026-10-01.</li></ol></body></html>',
|
|
98
|
+
].join("\n"))
|
|
99
|
+
const filled = (await run(["fact", "fill", join(dir, "report.src.html"), "--out", join(dir, "report.html"), ...s(dir)])) as { exitCode: number }
|
|
100
|
+
expect(filled.exitCode).toBe(0)
|
|
101
|
+
const verifiedReport = (await run(["fact", "verify", join(dir, "report.html"), "--sources", join(dir, "sources"), "--brief", join(dir, "brief.md"), ...s(dir)])) as { exitCode: number; json: { defects?: unknown[] } }
|
|
102
|
+
expect(verifiedReport.json.defects).toEqual([])
|
|
103
|
+
expect(verifiedReport.exitCode).toBe(0)
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
test("#given an active row pinning S1 #when --replace runs #then it refuses with snapshot_pinned and keeps the old text", async () => {
|
|
107
|
+
const dir = session()
|
|
108
|
+
await snapshot(dir)
|
|
109
|
+
await run(["fact", "add", "--source", "S1", "--find", "Staff grew to", "--label", "staff", ...s(dir), "--json"])
|
|
110
|
+
const verified = join(dir, "verified.txt")
|
|
111
|
+
writeFileSync(verified, "USD 4.2 billion in revenue.\n")
|
|
112
|
+
const replaced = (await run(["source", "snapshot", "--id", "S1", "--text", verified, "--replace", ...s(dir)])) as { exitCode: number; json: Json }
|
|
113
|
+
expect(replaced.exitCode).toBe(1)
|
|
114
|
+
expect(replaced.json.error).toBe("snapshot_pinned")
|
|
115
|
+
expect(existsSync(join(dir, "sources", "S1", "conversion.json"))).toBe(true)
|
|
116
|
+
})
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
test("#given --html whose conversion altered 4.22 #when fact add is refused, the verified text replaces S1, and the row is added, filled and verified #then the replacement is declared text over the kept raw.bin and every step passes", async () => {
|
|
120
|
+
const dir = session()
|
|
121
|
+
const s = ["--session-dir", dir]
|
|
122
|
+
await snapshot(dir)
|
|
123
|
+
const refused = (await run(["fact", "add", "--source", "S1", "--find", "USD", "--label", "usd_bn", ...s, "--json"])) as { exitCode: number; json: Json }
|
|
124
|
+
expect([refused.exitCode, refused.json.error]).toEqual([1, "conversion_altered_number"])
|
|
125
|
+
const verified = join(dir, "verified.txt")
|
|
126
|
+
writeFileSync(verified, "USD 4.2 billion in revenue.\nStaff grew to 1,250 people.\n")
|
|
127
|
+
const replaced = (await run(["source", "snapshot", "--id", "S1", "--text", verified, "--replace", ...s])) as { exitCode: number; json: Json }
|
|
128
|
+
expect([replaced.exitCode, replaced.json.data?.text_source, replaced.json.data?.kind]).toEqual([0, "declared", "page"])
|
|
129
|
+
expect(existsSync(join(dir, "sources", "S1", "raw.bin"))).toBe(true)
|
|
130
|
+
const added = (await run(["fact", "add", "--source", "S1", "--find", "USD", "--label", "usd_bn", ...s, "--json"])) as { exitCode: number; json: Json & { message?: string } }
|
|
131
|
+
expect([added.exitCode, added.json.error, added.json.message]).toEqual([0, undefined, undefined])
|
|
132
|
+
const row = added.json.data?.row as { value: string; locator: { artifact: string; unit: string } }
|
|
133
|
+
expect([row.value, row.locator.artifact, row.locator.unit]).toEqual(["4200000000", "text.txt", "bytes"])
|
|
134
|
+
copyFileSync(join(import.meta.dir, "tests", "fixtures", "fidelity-brief-locked.md"), join(dir, "brief.md"))
|
|
135
|
+
writeFileSync(join(dir, "report.src.html"), [
|
|
136
|
+
'<!doctype html><html lang="en"><head><meta charset="utf-8"><title>Revenue</title></head><body><h1>Revenue</h1>',
|
|
137
|
+
"<h2>Findings</h2><p>Revenue was {{F1:usd_bn}} [S1].</p>",
|
|
138
|
+
"<h2>How this report was made</h2><p>Collected from one cited public page [S1].</p>",
|
|
139
|
+
'<h2>Sources</h2><ol><li id="s1">[S1] Revenue page, retrieved 2026-10-01.</li></ol></body></html>',
|
|
140
|
+
].join("\n"))
|
|
141
|
+
expect(((await run(["fact", "fill", join(dir, "report.src.html"), "--out", join(dir, "report.html"), ...s])) as { exitCode: number }).exitCode).toBe(0)
|
|
142
|
+
const report = (await run(["fact", "verify", join(dir, "report.html"), "--sources", join(dir, "sources"), "--brief", join(dir, "brief.md"), ...s])) as { exitCode: number; json: { defects?: unknown[] } }
|
|
143
|
+
expect([report.exitCode, report.json.defects]).toEqual([0, []])
|
|
144
|
+
expect((JSON.parse(readFileSync(join(dir, "fidelity-defects.json"), "utf8")) as { defects: { code: string }[] }).defects.map((d) => d.code)).not.toContain("snapshot_not_raw")
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
test("#given a malformed conversion.json #when fact add reads it #then it is a usage error naming the file, not a crash", async () => {
|
|
148
|
+
const dir = session()
|
|
149
|
+
await snapshot(dir)
|
|
150
|
+
writeFileSync(join(dir, "sources", "S1", "conversion.json"), "{not json")
|
|
151
|
+
const added = run(["fact", "add", "--source", "S1", "--find", "Staff grew to", "--label", "staff", "--session-dir", dir, "--json"])
|
|
152
|
+
await expect(added).rejects.toBeInstanceOf(CliError)
|
|
153
|
+
await expect(added).rejects.toThrow(/conversion\.json/)
|
|
154
|
+
})
|
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test"
|
|
2
|
+
import { readFileSync } from "node:fs"
|
|
3
|
+
import { join } from "node:path"
|
|
4
|
+
|
|
5
|
+
import { markdownAsHtml } from "./fact-commands-verify.mjs"
|
|
6
|
+
import { derive } from "./fact-derive.mjs"
|
|
7
|
+
import { fill } from "./fact-fill.mjs"
|
|
8
|
+
import { addRow, emptyLedger } from "./fact-ledger.mjs"
|
|
9
|
+
import { checkFidelity } from "./gates-fidelity.mjs"
|
|
10
|
+
import { valueChecks } from "./fidelity-values.mjs"
|
|
11
|
+
import { buildContext } from "./gates-static.mjs"
|
|
12
|
+
import { parseHtml } from "./html-lite.mjs"
|
|
13
|
+
|
|
14
|
+
/** fact derive stamps observed_at from a caller-supplied clock (#11285); these rows only need a fixed one. */
|
|
15
|
+
const NOW = "2026-10-27T00:00:00.000Z"
|
|
16
|
+
|
|
17
|
+
type Row = Record<string, unknown> & { fact_id: string }
|
|
18
|
+
type Ledger = { rows: Row[] }
|
|
19
|
+
const FIX = join(import.meta.dir, "tests", "fixtures", "fidelity")
|
|
20
|
+
const BASE: Ledger = JSON.parse(readFileSync(join(FIX, "ledger-verify.json"), "utf8"))
|
|
21
|
+
const BRIEF = readFileSync(join(FIX, "..", "fidelity-brief-locked.md"), "utf8")
|
|
22
|
+
/** F8 in the shape fact derive writes it: raw is the render (fact-derive.mjs compute: raw = render). */
|
|
23
|
+
const DERIVED: Ledger = { ...BASE, rows: BASE.rows.map((r) => (r.fact_id === "F8" ? { ...r, raw: r.render } : r)) }
|
|
24
|
+
const check = (html: string, ledger: Ledger = DERIVED, sources: string | null = join(FIX, "sources")) =>
|
|
25
|
+
valueChecks(buildContext(parseHtml(`<h2>Findings</h2>${html}`), { baseDir: FIX }), ledger, null, sources)
|
|
26
|
+
const data = (id: string, shown: string) => `<p><data data-fact="${id}">${shown}</data></p>`
|
|
27
|
+
const rowOf = (ledger: Ledger, id: string) => ledger.rows.find((r) => r.fact_id === id) as Row
|
|
28
|
+
|
|
29
|
+
function measured(id: string, value: string, unit: string, extra: Record<string, unknown> = {}) {
|
|
30
|
+
return { fact_id: id, kind: "measured", value, unit, raw: `${value} ${unit}`, quote: `The figure is ${value} ${unit}.`, source_id: "S1", locator: { artifact: "sources/S1/text.txt", char_start: 0, char_end: 5 }, snapshot_sha256: "a".repeat(64), extracted_by: "fact add --find", observed_at: "2026-10-23T08:00:00Z", label: id.toLowerCase(), ...extra }
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** A derive-produced row filled by fact fill, so the report is exactly what the tools wrote. */
|
|
34
|
+
function derivedReport(rows: Row[], spec: Record<string, unknown>) {
|
|
35
|
+
const ledger = emptyLedger() as Ledger
|
|
36
|
+
for (const row of rows) addRow(ledger, row)
|
|
37
|
+
const result = derive(ledger, { now: NOW, ...spec }) as { row?: Row; error?: string }
|
|
38
|
+
if (!result.row) throw new Error(`derive refused: ${result.error}`)
|
|
39
|
+
const filled = fill(`<h2>Findings</h2><p>The total is {{${result.row.fact_id}:${result.row.label}}}.</p>`, ledger)
|
|
40
|
+
if (filled.out === null) throw new Error("fill failed")
|
|
41
|
+
return { ledger, row: result.row, html: filled.out }
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
describe("the converted half of a derived or converted row is display(value, unit)", () => {
|
|
45
|
+
const dated = { valid_at: "2026-10-01" }
|
|
46
|
+
const cases: [string, Row[], Record<string, unknown>][] = [
|
|
47
|
+
["a grouped KRW-e8 sum", [measured("F1", "1617", "KRW-e8"), measured("F2", "1617", "KRW-e8")], { op: "sum", operands: ["F1", "F2"], unit: "KRW-e8" }],
|
|
48
|
+
["a grouped KRW-e3 sum", [measured("F1", "1617", "KRW-e3"), measured("F2", "1617", "KRW-e3")], { op: "sum", operands: ["F1", "F2"], unit: "KRW-e3" }],
|
|
49
|
+
["a grouped KRW-e4 sum", [measured("F1", "1617", "KRW-e4"), measured("F2", "1617", "KRW-e4")], { op: "sum", operands: ["F1", "F2"], unit: "KRW-e4" }],
|
|
50
|
+
["a grouped KRW-e12 sum", [measured("F1", "1617", "KRW-e12"), measured("F2", "1617", "KRW-e12")], { op: "sum", operands: ["F1", "F2"], unit: "KRW-e12" }],
|
|
51
|
+
["a km/h sum", [measured("F1", "30000", "km/h"), measured("F2", "30000", "km/h")], { op: "sum", operands: ["F1", "F2"], unit: "km/h" }],
|
|
52
|
+
["a KRW/person sum", [measured("F1", "30000", "KRW/person"), measured("F2", "30000", "KRW/person")], { op: "sum", operands: ["F1", "F2"], unit: "KRW/person" }],
|
|
53
|
+
["an FX expr in KRW-e8", [measured("F1", "12", "USD", dated), measured("F2", "1420", "KRW-e8/USD", dated)], { op: "expr", formula: "F1 * F2", unit: "KRW-e8" }],
|
|
54
|
+
]
|
|
55
|
+
for (const [name, rows, spec] of cases) {
|
|
56
|
+
test(`#given ${name} exactly as fill wrote it #when verified #then no defect`, () => {
|
|
57
|
+
const { ledger, html } = derivedReport(rows, spec)
|
|
58
|
+
expect(valueChecks(buildContext(parseHtml(html), {}), ledger, null, null)).toEqual([])
|
|
59
|
+
})
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
test("#given a grouped KRW-e8 sum whose converted half was edited #when verified #then it still blocks", () => {
|
|
63
|
+
const { ledger, row, html } = derivedReport(cases[0][1], cases[0][2])
|
|
64
|
+
const edited = html.replace(`>${String(row.raw)}<`, ">3,243 KRW-e8<")
|
|
65
|
+
expect(edited).not.toBe(html)
|
|
66
|
+
expect(valueChecks(buildContext(parseHtml(edited), {}), ledger, null, null).map((d: { code: string }) => d.code)).toContain("value_mismatch")
|
|
67
|
+
})
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
describe("conversion originals bind both halves", () => {
|
|
71
|
+
for (const [shown, code] of [["320,000 mm (320 mm)", "unit_mismatch"], ["320,000 mm (999 m)", "value_mismatch"], ["320,000 mm (three m)", "value_mismatch"], ["320,000 mm (\uFF19\uFF19\uFF19 m)", "value_mismatch"], ["320,000 mm (m)", "value_mismatch"]]) {
|
|
72
|
+
test(`#given an edited original ${shown} #when verified #then ${code} on part original`, () => {
|
|
73
|
+
expect(check(data("F8", shown))).toContainEqual(expect.objectContaining({ code, part: "original" }))
|
|
74
|
+
expect(checkFidelity(data("F8", shown), { ledger: DERIVED, brief: BRIEF }).defects).toContainEqual(expect.objectContaining({ code, part: "original" }))
|
|
75
|
+
})
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
test("#given a ledger render edited to match a tampered report #when verified #then the original stays bound to its operand", () => {
|
|
79
|
+
const ledger = structuredClone(DERIVED)
|
|
80
|
+
rowOf(ledger, "F8").render = "320,000 mm (999 m)"
|
|
81
|
+
expect(check(data("F8", "320,000 mm (999 m)"), ledger)).toContainEqual(expect.objectContaining({ code: "value_mismatch", part: "original" }))
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
test("#given an edited converted half or a number beside the original #when verified #then value_mismatch", () => {
|
|
85
|
+
expect(check(data("F8", "320,000 mm (320 m)"))).toEqual([])
|
|
86
|
+
expect(check(data("F8", "319,000 mm (320 m)"))).toContainEqual(expect.objectContaining({ code: "value_mismatch" }))
|
|
87
|
+
expect(check(data("F8", "320,000 mm (320 m) 999"))).toContainEqual(expect.objectContaining({ code: "value_mismatch", part: "original" }))
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
test("#given the fixture F8 whose raw is not its render #when filled and verified #then the shape fact derive never writes is rejected", () => {
|
|
91
|
+
expect(rowOf(BASE, "F8").raw).not.toBe(rowOf(BASE, "F8").render)
|
|
92
|
+
const filled = fill("<h2>Findings</h2><p>The span is {{F8:span_mm}}.</p>", structuredClone(BASE))
|
|
93
|
+
expect(valueChecks(buildContext(parseHtml(String(filled.out)), {}), BASE, null, null)).toContainEqual(expect.objectContaining({ code: "value_mismatch" }))
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
test("#given grouped and percent originals #when verified #then the operation display contract holds", () => {
|
|
97
|
+
const ledger = structuredClone(DERIVED)
|
|
98
|
+
rowOf(ledger, "F1").value = "1237"
|
|
99
|
+
const converted = rowOf(ledger, "F8")
|
|
100
|
+
converted.value = "1237000"
|
|
101
|
+
expect(check(data("F8", "1,237,000 mm (1,237 m)"), ledger)).toEqual([])
|
|
102
|
+
Object.assign(converted, { kind: "derived", value: "0.12", unit: null, derived_from: ["F3"] })
|
|
103
|
+
expect(check(data("F8", "0.12 (12%)"), ledger)).toEqual([])
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
test("#given a converted row filled in value and unit mode #when verified #then no original is due", () => {
|
|
107
|
+
for (const pointer of [(id: string) => `{{${id}:span_mm:value}} mm`, (id: string) => `{{${id}:span_mm:value}}{{${id}:span_mm:unit}}`]) {
|
|
108
|
+
const ledger = emptyLedger() as Ledger
|
|
109
|
+
addRow(ledger, measured("F1", "320", "m"))
|
|
110
|
+
const { row } = derive(ledger, { now: NOW, op: "convert", operands: ["F1"], to: "mm", label: "span_mm" }) as { row: Row }
|
|
111
|
+
const filled = fill(`<h2>Findings</h2><p>The span is ${pointer(row.fact_id)}.</p>`, ledger)
|
|
112
|
+
expect(valueChecks(buildContext(parseHtml(String(filled.out)), {}), ledger, null, null)).toEqual([])
|
|
113
|
+
}
|
|
114
|
+
})
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* The conversion-original class: one row per written form (EN and KO), each applied at the fill
|
|
119
|
+
* site of a real fact derive convert row ("320,000 mm (320 m)") filled by fact fill, in four
|
|
120
|
+
* renders: HTML paragraph, HTML table cell, Markdown paragraph, Markdown table cell (fill map and
|
|
121
|
+
* projection, as fact verify reads it). Every bracket group is an original; a dash is not one.
|
|
122
|
+
*/
|
|
123
|
+
describe("conversion originals: the bracket grammar class", () => {
|
|
124
|
+
const PASS = "pass"
|
|
125
|
+
const ORIGINAL = "value_mismatch/original"
|
|
126
|
+
const KO = { prefix: "\uD658\uC0B0 ", tbd: "\uBBF8\uC815", na: "\uD574\uB2F9 \uC5C6\uC74C", about: "\uC57D", words: "\uC0BC\uBC31\uC774\uC2ED" }
|
|
127
|
+
/** `after`: the input is typed right after the untouched fill site instead of replacing it. */
|
|
128
|
+
const ROWS: { form: string; input: string; expected: string; after?: true }[] = [
|
|
129
|
+
{ form: "ASCII parentheses", input: "320,000 mm (320 m)", expected: PASS },
|
|
130
|
+
{ form: "fullwidth parentheses", input: "320,000 mm\uFF08320 m\uFF09", expected: PASS },
|
|
131
|
+
{ form: "fullwidth parentheses after a space", input: "320,000 mm \uFF08320 m\uFF09", expected: PASS },
|
|
132
|
+
{ form: "square brackets", input: "320,000 mm [320 m]", expected: PASS },
|
|
133
|
+
{ form: "fullwidth square brackets", input: "320,000 mm\uFF3B320 m\uFF3D", expected: PASS },
|
|
134
|
+
{ form: "lenticular brackets", input: "320,000 mm\u3010320 m\u3011", expected: PASS },
|
|
135
|
+
{ form: "corner brackets", input: "320,000 mm\u300C320 m\u300D", expected: PASS },
|
|
136
|
+
{ form: "tortoise-shell brackets", input: "320,000 mm\u3014320 m\u3015", expected: PASS },
|
|
137
|
+
{ form: "NFKC fullwidth digits and letters throughout", input: "\uFF13\uFF12\uFF10\uFF0C\uFF10\uFF10\uFF10 \uFF4D\uFF4D\uFF08\uFF13\uFF12\uFF10 \uFF4D\uFF09", expected: PASS },
|
|
138
|
+
{ form: "NFKC fullwidth digits inside the original", input: "320,000 mm (\uFF13\uFF12\uFF10 m)", expected: PASS },
|
|
139
|
+
{ form: "spaces inside the brackets", input: "320,000 mm ( 320 m )", expected: PASS },
|
|
140
|
+
{ form: "no space before the bracket", input: "320,000 mm(320 m)", expected: PASS },
|
|
141
|
+
{ form: "EN digit-free prefix", input: "Converted: 320,000 mm (320 m)", expected: PASS },
|
|
142
|
+
{ form: "KO digit-free prefix, fullwidth", input: `${KO.prefix}320,000 mm\uFF08320 m\uFF09`, expected: PASS },
|
|
143
|
+
{ form: "ASCII parentheses, wrong original", input: "320,000 mm (999 m)", expected: ORIGINAL },
|
|
144
|
+
{ form: "fullwidth parentheses, wrong original", input: "320,000 mm\uFF08999 m\uFF09", expected: ORIGINAL },
|
|
145
|
+
{ form: "square brackets, wrong original", input: "320,000 mm [999 m]", expected: ORIGINAL },
|
|
146
|
+
{ form: "fullwidth square brackets, wrong original", input: "320,000 mm\uFF3B999 m\uFF3D", expected: ORIGINAL },
|
|
147
|
+
{ form: "lenticular brackets, wrong original", input: "320,000 mm\u3010999 m\u3011", expected: ORIGINAL },
|
|
148
|
+
{ form: "corner brackets, wrong original", input: "320,000 mm\u300C999 m\u300D", expected: ORIGINAL },
|
|
149
|
+
{ form: "tortoise-shell brackets, wrong original", input: "320,000 mm\u3014999 m\u3015", expected: ORIGINAL },
|
|
150
|
+
{ form: "NFKC fullwidth digits, wrong original", input: "320,000 mm (\uFF19\uFF19\uFF19 m)", expected: ORIGINAL },
|
|
151
|
+
{ form: "spaces inside, wrong original", input: "320,000 mm ( 999 m )", expected: ORIGINAL },
|
|
152
|
+
{ form: "hyphen separator, right number", input: "320,000 mm - 320 m", expected: ORIGINAL },
|
|
153
|
+
{ form: "en-dash separator, right number", input: "320,000 mm \u2013 320 m", expected: ORIGINAL },
|
|
154
|
+
{ form: "em-dash separator, right number", input: "320,000 mm \u2014 320 m", expected: ORIGINAL },
|
|
155
|
+
{ form: "hyphen separator, wrong number", input: "320,000 mm - 999 m", expected: ORIGINAL },
|
|
156
|
+
{ form: "en-dash separator, wrong number", input: "320,000 mm \u2013 999 m", expected: ORIGINAL },
|
|
157
|
+
{ form: "separator after the original", input: "320,000 mm (320 m) - 999 m", expected: ORIGINAL },
|
|
158
|
+
{ form: "nested parentheses", input: "320,000 mm ((320 m))", expected: ORIGINAL },
|
|
159
|
+
{ form: "square inside parentheses", input: "320,000 mm ([320 m])", expected: ORIGINAL },
|
|
160
|
+
{ form: "ASCII inside fullwidth parentheses", input: "320,000 mm\uFF08(320 m)\uFF09", expected: ORIGINAL },
|
|
161
|
+
{ form: "mismatched bracket pair", input: "320,000 mm (320 m]", expected: ORIGINAL },
|
|
162
|
+
{ form: "unclosed bracket", input: "320,000 mm (320 m", expected: ORIGINAL },
|
|
163
|
+
{ form: "EN number word", input: "320,000 mm (twenty m)", expected: ORIGINAL },
|
|
164
|
+
{ form: "EN n/a", input: "320,000 mm (n/a)", expected: ORIGINAL },
|
|
165
|
+
{ form: "EN TBD", input: "320,000 mm (TBD)", expected: ORIGINAL },
|
|
166
|
+
{ form: "EN approx. alone", input: "320,000 mm (approx.)", expected: ORIGINAL },
|
|
167
|
+
{ form: "EN approx. beside the original", input: "320,000 mm (approx.) (320 m)", expected: ORIGINAL },
|
|
168
|
+
{ form: "bare unit", input: "320,000 mm (m)", expected: ORIGINAL },
|
|
169
|
+
{ form: "KO TBD", input: `320,000 mm (${KO.tbd})`, expected: ORIGINAL },
|
|
170
|
+
{ form: "KO n/a, fullwidth", input: `320,000 mm\uFF08${KO.na}\uFF09`, expected: ORIGINAL },
|
|
171
|
+
{ form: "KO hedge inside the original", input: `320,000 mm (${KO.about} 320 m)`, expected: ORIGINAL },
|
|
172
|
+
{ form: "KO number word", input: `320,000 mm (${KO.words} m)`, expected: ORIGINAL },
|
|
173
|
+
{ form: "missing original", input: "320,000 mm", expected: ORIGINAL },
|
|
174
|
+
{ form: "EN prefix, missing original", input: "Converted: 320,000 mm", expected: ORIGINAL },
|
|
175
|
+
{ form: "KO prefix, missing original", input: `${KO.prefix}320,000 mm`, expected: ORIGINAL },
|
|
176
|
+
{ form: "original before the value", input: "(320 m) 320,000 mm", expected: ORIGINAL },
|
|
177
|
+
{ form: "fullwidth original before the value", input: "\uFF08320 m\uFF09320,000 mm", expected: ORIGINAL },
|
|
178
|
+
{ form: "two originals, both right", input: "320,000 mm (320 m) (320 m)", expected: ORIGINAL },
|
|
179
|
+
{ form: "two originals, second wrong", input: "320,000 mm (320 m)\uFF08999 m\uFF09", expected: ORIGINAL },
|
|
180
|
+
{ form: "two originals, mixed brackets", input: "320,000 mm (320 m) [320 m]", expected: ORIGINAL },
|
|
181
|
+
{ form: "second original typed after the fill site", input: " (999 m)", expected: ORIGINAL, after: true },
|
|
182
|
+
{ form: "fullwidth original typed after the fill site", input: "\uFF08320 m\uFF09", expected: ORIGINAL, after: true },
|
|
183
|
+
{ form: "square original typed after the fill site", input: " [999 m]", expected: ORIGINAL, after: true },
|
|
184
|
+
{ form: "separator original typed after the fill site", input: " - 999 m", expected: ORIGINAL, after: true },
|
|
185
|
+
{ form: "prose bracket typed after the fill site", input: " (approx.)", expected: PASS, after: true },
|
|
186
|
+
]
|
|
187
|
+
const SENTENCE = { en: ["The span is ", "."], ko: ["\uAD50\uB7C9 \uAE38\uC774\uB294 ", "\uC774\uB2E4."] }
|
|
188
|
+
type Render = { format: "html" | "md"; doc: (pointer: string, around: string[]) => string }
|
|
189
|
+
const RENDERS: Record<string, Render> = {
|
|
190
|
+
"html paragraph": { format: "html", doc: (p, [a, b]) => `<h2>Findings</h2><p>${a}${p}${b}</p>` },
|
|
191
|
+
"html table cell": { format: "html", doc: (p) => `<h2>Findings</h2><table><tr><th>span</th><td>${p}</td></tr></table>` },
|
|
192
|
+
"md paragraph": { format: "md", doc: (p, [a, b]) => `## Findings\n\n${a}${p}${b}\n` },
|
|
193
|
+
"md table cell": { format: "md", doc: (p) => `## Findings\n\n| item | value |\n| --- | --- |\n| span | ${p} |\n` },
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/** The verdict fact verify gives the form written at the fill site: pass, or the code/part of the first value defect. */
|
|
197
|
+
function verdict(input: string, render: Render, lang: "en" | "ko", after = false) {
|
|
198
|
+
const ledger = emptyLedger() as Ledger
|
|
199
|
+
addRow(ledger, measured("F1", "320", "m"))
|
|
200
|
+
const row = (derive(ledger, { now: NOW, op: "convert", operands: ["F1"], to: "mm", label: "span_mm" }) as { row: Row }).row
|
|
201
|
+
const filled = fill(render.doc(`{{${row.fact_id}:span_mm}}`, SENTENCE[lang]), ledger, { format: render.format }) as { out: string; fillMap: Parameters<typeof markdownAsHtml>[1] }
|
|
202
|
+
const site = render.format === "html" ? `>${row.render}</data>` : String(row.render)
|
|
203
|
+
const edited = filled.out.replace(site, after ? `${site}${input}` : render.format === "html" ? `>${input}</data>` : input)
|
|
204
|
+
if (!filled.out.includes(site) || (input !== row.render && edited === filled.out)) return "fill site not found"
|
|
205
|
+
const projected = render.format === "md" ? markdownAsHtml(edited, filled.fillMap, ledger) : null
|
|
206
|
+
const defects = valueChecks(buildContext(parseHtml(projected?.html ?? edited), {}), ledger, projected?.stale ?? null, null) as { code: string; part?: string }[]
|
|
207
|
+
const value = defects.find((d) => d.code === "value_mismatch" || d.code === "unit_mismatch")
|
|
208
|
+
if (value) return `${value.code}/${value.part ?? "-"}`
|
|
209
|
+
return defects.length === 0 ? PASS : defects.map((d) => d.code).join(",")
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
test(`#given ${ROWS.length} written forms x ${Object.keys(RENDERS).length} renders x EN and KO #when verified #then each verdict is the row's expected`, () => {
|
|
213
|
+
const got: string[] = []
|
|
214
|
+
const want: string[] = []
|
|
215
|
+
for (const { form, input, expected, after } of ROWS) {
|
|
216
|
+
for (const [name, render] of Object.entries(RENDERS)) {
|
|
217
|
+
for (const lang of ["en", "ko"] as const) {
|
|
218
|
+
got.push(`${form} | ${name} | ${lang} | ${verdict(input, render, lang, after)}`)
|
|
219
|
+
want.push(`${form} | ${name} | ${lang} | ${expected}`)
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
expect(got).toEqual(want)
|
|
224
|
+
})
|
|
225
|
+
})
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* What can follow a converted element: each shape is written in the report template right after
|
|
229
|
+
* the fill site of a real fact derive convert row and resolved by fact fill, so every untampered
|
|
230
|
+
* row is a report exactly as the tools write it. A following group is another original only when
|
|
231
|
+
* it is a bare numeral with a unit of the operand's dimension that is not a filled element.
|
|
232
|
+
*/
|
|
233
|
+
describe("conversion originals: what can follow a converted element", () => {
|
|
234
|
+
const [CLEAN, ORIGINAL, UNTRACED] = ["clean", "original", "untraceable_number"]
|
|
235
|
+
const BRACKETS = [["(", ")"], ["\uFF08", "\uFF09"], ["[", "]"], ["\uFF3B", "\uFF3D"], ["\u3010", "\u3011"], ["\u300C", "\u300D"], ["\u3014", "\u3015"], ["\u300E", "\u300F"], ["\u300A", "\u300B"]]
|
|
236
|
+
const SEPARATORS = ["-", "\u2013", "\u2014", "/"]
|
|
237
|
+
const ROWS: { shape: string; after: string; expected: string }[] = [
|
|
238
|
+
{ shape: "nothing (element at the end of the cell or line)", after: "", expected: CLEAN },
|
|
239
|
+
...BRACKETS.flatMap(([o, c]) => [
|
|
240
|
+
{ shape: `${o}${c} second placeholder in prose`, after: ` ${o}{{F3:rate_yoy}} longer${c}`, expected: CLEAN },
|
|
241
|
+
{ shape: `${o}${c} second placeholder alone`, after: ` ${o}{{F1:span_len}}${c}`, expected: CLEAN },
|
|
242
|
+
{ shape: `${o}${c} bare numeral, wrong`, after: ` ${o}999 m${c}`, expected: ORIGINAL },
|
|
243
|
+
{ shape: `${o}${c} bare numeral, right, other unit of the dimension`, after: ` ${o}0.32 km${c}`, expected: ORIGINAL },
|
|
244
|
+
]),
|
|
245
|
+
...SEPARATORS.flatMap((s) => [
|
|
246
|
+
{ shape: `${s} then a filled element`, after: ` ${s} {{F1:span_len}}`, expected: CLEAN },
|
|
247
|
+
{ shape: `${s} then a bare numeral`, after: ` ${s} 999 m`, expected: ORIGINAL },
|
|
248
|
+
{ shape: `${s} then prose with a count`, after: ` ${s} 12 sites`, expected: UNTRACED },
|
|
249
|
+
]),
|
|
250
|
+
{ shape: "prose with a filled percent", after: " ({{F3:rate_yoy}} longer)", expected: CLEAN },
|
|
251
|
+
{ shape: "prose with a literal percent", after: " (12 % longer)", expected: UNTRACED },
|
|
252
|
+
{ shape: "a bare unitless number (footnote)", after: " (7)", expected: UNTRACED },
|
|
253
|
+
{ shape: "a year", after: " (2024)", expected: UNTRACED },
|
|
254
|
+
{ shape: "two consecutive elements", after: " {{F1:span_len}}", expected: CLEAN },
|
|
255
|
+
{ shape: "two consecutive elements, glued", after: "{{F3:rate_yoy}}", expected: CLEAN },
|
|
256
|
+
{ shape: "prose without digits", after: " (approx.)", expected: CLEAN },
|
|
257
|
+
]
|
|
258
|
+
const SENTENCE = { en: ["The span is ", "."], ko: ["\uAD50\uB7C9 \uAE38\uC774\uB294 ", "\uC774\uB2E4."] }
|
|
259
|
+
type Render = { format: "html" | "md"; doc: (body: string, around: string[]) => string; stale?: true }
|
|
260
|
+
const RENDERS: Record<string, Render> = {
|
|
261
|
+
"html paragraph": { format: "html", doc: (b, [x, y]) => `<h2>Findings</h2><p>${x}${b}${y}</p>` },
|
|
262
|
+
"html table cell": { format: "html", doc: (b) => `<h2>Findings</h2><table><tr><th>span</th><td>${b}</td></tr></table>` },
|
|
263
|
+
"md paragraph": { format: "md", doc: (b, [x, y]) => `## Findings\n\n${x}${b}${y}\n` },
|
|
264
|
+
"md table cell": { format: "md", doc: (b) => `## Findings\n\n| item | value |\n| --- | --- |\n| span | ${b} |\n` },
|
|
265
|
+
"md paragraph, edited before the site (stale fill map)": { format: "md", doc: (b, [x, y]) => `## Findings\n\n${x}${b}${y}\n`, stale: true },
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/** derive -> fill -> verify as fact verify wires it: "original" when a defect is on part original, else the defect codes or clean. */
|
|
269
|
+
function verdict(after: string, render: Render, lang: "en" | "ko") {
|
|
270
|
+
const ledger = emptyLedger() as Ledger
|
|
271
|
+
// The fixture's own source rows: each pins S4 and quotes its sentence, as fact add writes them.
|
|
272
|
+
for (const id of ["F1", "F3"]) addRow(ledger, structuredClone(rowOf(BASE, id)))
|
|
273
|
+
const row = (derive(ledger, { now: NOW, op: "convert", operands: ["F1"], to: "mm", label: "span_mm" }) as { row: Row }).row
|
|
274
|
+
const filled = fill(render.doc(`{{${row.fact_id}:span_mm}}${after}`, SENTENCE[lang]), ledger, { format: render.format }) as { out: string | null; fillMap: Parameters<typeof markdownAsHtml>[1] }
|
|
275
|
+
if (filled.out === null) return "fill failed"
|
|
276
|
+
const report = render.stale ? filled.out.replace("## Findings", "## Findings, revised after the review") : filled.out
|
|
277
|
+
const projected = render.format === "md" ? markdownAsHtml(report, filled.fillMap, ledger) : null
|
|
278
|
+
if (render.stale && projected?.stale.length === 0) return "fill map not stale"
|
|
279
|
+
const defects = valueChecks(buildContext(parseHtml(projected?.html ?? report), {}), ledger, projected?.stale ?? null, join(FIX, "sources")) as { code: string; part?: string }[]
|
|
280
|
+
if (defects.some((d) => d.part === "original")) return ORIGINAL
|
|
281
|
+
return defects.length === 0 ? CLEAN : [...new Set(defects.map((d) => d.code))].sort().join(",")
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
test(`#given ${ROWS.length} shapes x ${Object.keys(RENDERS).length} renders x EN and KO after a filled convert row #when verified #then each verdict is the row's expected`, () => {
|
|
285
|
+
const got: string[] = []
|
|
286
|
+
const want: string[] = []
|
|
287
|
+
for (const { shape, after, expected } of ROWS) {
|
|
288
|
+
for (const [name, render] of Object.entries(RENDERS)) {
|
|
289
|
+
for (const lang of ["en", "ko"] as const) {
|
|
290
|
+
got.push(`${shape} | ${name} | ${lang} | ${verdict(after, render, lang)}`)
|
|
291
|
+
want.push(`${shape} | ${name} | ${lang} | ${expected}`)
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
expect(got).toEqual(want)
|
|
296
|
+
})
|
|
297
|
+
})
|