oh-my-opencode 5.1.45 → 5.1.47
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/.agents/command/publish.md +12 -13
- package/.agents/skills/publish/SKILL.md +12 -13
- package/.opencode/command/publish.md +12 -13
- package/README.de.md +0 -5
- package/README.es.md +0 -5
- package/README.fr.md +0 -5
- package/README.it.md +0 -5
- package/README.ja.md +0 -5
- package/README.ko.md +0 -5
- package/README.md +4 -5
- package/README.pt-br.md +0 -5
- package/README.ru.md +0 -6
- package/README.zh-cn.md +0 -5
- package/dist/agents/atlas/agent.d.ts +3 -2
- package/dist/agents/sisyphus/index.d.ts +1 -0
- package/dist/agents/sisyphus/kimi-k3-1.d.ts +9 -0
- package/dist/agents/sisyphus-agent-factory.d.ts +1 -1
- package/dist/agents/sisyphus-junior/agent.d.ts +1 -1
- package/dist/agents/sisyphus-junior/index.d.ts +1 -0
- package/dist/agents/sisyphus-junior/kimi-k3-1.d.ts +6 -0
- package/dist/agents/types.d.ts +2 -2
- package/dist/cli/index.js +91 -163
- package/dist/cli/service-auth/keystore.d.ts +8 -3
- package/dist/cli-node/index.js +91 -163
- package/dist/config-migration/index.d.ts +2 -2
- package/dist/config-migration/migration-plans.d.ts +0 -10
- package/dist/index.js +1784 -1204
- package/dist/skills/ast-grep/AGENTS.md +1 -1
- package/dist/skills/browser/runtime/omowright/manifest.json +1 -1
- package/dist/skills/frontend/ATTRIBUTION.md +2 -2
- package/dist/skills/ultimate-browsing/engine/AGENTS.md +1 -1
- package/dist/skills/ulw-research/AGENTS.md +14 -2
- package/dist/skills/ulw-research/SKILL.md +37 -12
- package/dist/skills/ulw-research/references/deliverable-phase.md +81 -14
- package/dist/skills/ulw-research/references/fact-ledger-grammar.md +19 -1
- package/dist/skills/ulw-research/references/fact-ledger.md +95 -0
- package/dist/skills/ulw-research/references/fidelity-claim-kinds.md +19 -0
- package/dist/skills/ulw-research/references/report-gates.md +21 -4
- package/dist/skills/ulw-research/references/report-visualization.md +2 -0
- package/dist/skills/ulw-research/references/unit-rules.md +157 -0
- package/dist/skills/ulw-research/scripts/claim-kinds.mjs +116 -0
- package/dist/skills/ulw-research/scripts/claim-kinds.test.ts +193 -0
- package/dist/skills/ulw-research/scripts/cli-support.mjs +19 -5
- package/dist/skills/ulw-research/scripts/cli-support.test.ts +13 -2
- package/dist/skills/ulw-research/scripts/contracts.mjs +27 -6
- package/dist/skills/ulw-research/scripts/contracts.test.ts +3 -0
- package/dist/skills/ulw-research/scripts/copy-returns-polish.test.ts +209 -0
- package/dist/skills/ulw-research/scripts/copy-returns.test.ts +183 -0
- package/dist/skills/ulw-research/scripts/delivery-barrier.test.ts +245 -0
- package/dist/skills/ulw-research/scripts/fact-commands-meaning.mjs +176 -0
- package/dist/skills/ulw-research/scripts/fact-commands-polish.mjs +127 -0
- package/dist/skills/ulw-research/scripts/fact-commands-verify.mjs +183 -0
- package/dist/skills/ulw-research/scripts/fact-commands.mjs +241 -0
- package/dist/skills/ulw-research/scripts/fact-derive.mjs +66 -0
- package/dist/skills/ulw-research/scripts/fact-derive.test.ts +375 -0
- package/dist/skills/ulw-research/scripts/fact-expr.mjs +108 -0
- package/dist/skills/ulw-research/scripts/fact-fill.mjs +194 -0
- package/dist/skills/ulw-research/scripts/fact-fill.test.ts +221 -0
- package/dist/skills/ulw-research/scripts/fact-ledger-grammar.test.ts +9 -2
- package/dist/skills/ulw-research/scripts/fact-ledger-kinds.mjs +127 -13
- package/dist/skills/ulw-research/scripts/fact-ledger-kinds.test.ts +18 -3
- package/dist/skills/ulw-research/scripts/fact-ledger-legacy.test.ts +160 -0
- package/dist/skills/ulw-research/scripts/fact-ledger-schema.mjs +33 -16
- package/dist/skills/ulw-research/scripts/fact-ledger.mjs +29 -9
- package/dist/skills/ulw-research/scripts/fact-ledger.test.ts +2 -0
- package/dist/skills/ulw-research/scripts/fact-locate.mjs +231 -0
- package/dist/skills/ulw-research/scripts/fact-locate.test.ts +118 -0
- package/dist/skills/ulw-research/scripts/fact-pairs.mjs +185 -0
- package/dist/skills/ulw-research/scripts/fact-pairs.test.ts +151 -0
- package/dist/skills/ulw-research/scripts/fact-recompute.mjs +89 -0
- package/dist/skills/ulw-research/scripts/fact-unit-dimensions.test.ts +409 -0
- package/dist/skills/ulw-research/scripts/fact-unit-rules.test.ts +388 -0
- package/dist/skills/ulw-research/scripts/fidelity-binding.mjs +130 -0
- package/dist/skills/ulw-research/scripts/fidelity-binding.test.ts +81 -0
- package/dist/skills/ulw-research/scripts/fidelity-brief.mjs +5 -0
- package/dist/skills/ulw-research/scripts/fidelity-claims.mjs +89 -0
- package/dist/skills/ulw-research/scripts/fidelity-lexicon.mjs +145 -0
- package/dist/skills/ulw-research/scripts/fidelity-lexicon.test.ts +117 -0
- package/dist/skills/ulw-research/scripts/fidelity-records.mjs +163 -0
- package/dist/skills/ulw-research/scripts/fidelity-state.mjs +2 -1
- package/dist/skills/ulw-research/scripts/fidelity-trace.mjs +85 -0
- package/dist/skills/ulw-research/scripts/fidelity-trace.test.ts +58 -0
- package/dist/skills/ulw-research/scripts/fidelity-values.mjs +229 -0
- package/dist/skills/ulw-research/scripts/fidelity-values.test.ts +270 -0
- package/dist/skills/ulw-research/scripts/gates-fidelity.mjs +113 -0
- package/dist/skills/ulw-research/scripts/gates-fidelity.test.ts +73 -0
- package/dist/skills/ulw-research/scripts/gates-static.mjs +28 -8
- package/dist/skills/ulw-research/scripts/html-lite.mjs +2 -2
- package/dist/skills/ulw-research/scripts/html-markup.mjs +182 -0
- package/dist/skills/ulw-research/scripts/md-lite.mjs +193 -0
- package/dist/skills/ulw-research/scripts/md-lite.test.ts +271 -0
- package/dist/skills/ulw-research/scripts/meaning-check.mjs +196 -0
- package/dist/skills/ulw-research/scripts/meaning-check.test.ts +228 -0
- package/dist/skills/ulw-research/scripts/outcome.mjs +6 -5
- package/dist/skills/ulw-research/scripts/placeholder.mjs +184 -0
- package/dist/skills/ulw-research/scripts/placeholder.test.ts +77 -0
- package/dist/skills/ulw-research/scripts/polish-apply.mjs +115 -0
- package/dist/skills/ulw-research/scripts/polish-apply.test.ts +363 -0
- package/dist/skills/ulw-research/scripts/polish-diff.mjs +31 -0
- package/dist/skills/ulw-research/scripts/polish-diff.test.ts +168 -0
- package/dist/skills/ulw-research/scripts/polish-markup.test.ts +187 -0
- package/dist/skills/ulw-research/scripts/polish-project.mjs +125 -0
- package/dist/skills/ulw-research/scripts/polish-rules.mjs +170 -0
- package/dist/skills/ulw-research/scripts/references-fences.test.ts +25 -0
- package/dist/skills/ulw-research/scripts/report-tools-commands.mjs +19 -8
- package/dist/skills/ulw-research/scripts/report-tools.mjs +15 -0
- package/dist/skills/ulw-research/scripts/report-tools.test.ts +545 -2
- package/dist/skills/ulw-research/scripts/source-commands.mjs +140 -0
- package/dist/skills/ulw-research/scripts/source-snapshot.mjs +176 -0
- package/dist/skills/ulw-research/scripts/source-snapshot.test.ts +113 -0
- package/dist/skills/ulw-research/scripts/tests/fixtures/fidelity/assets/share.csv +3 -0
- package/dist/skills/ulw-research/scripts/tests/fixtures/fidelity/gen-legacy-dev.mjs +89 -0
- package/dist/skills/ulw-research/scripts/tests/fixtures/fidelity/ledger-basic.json +223 -0
- package/dist/skills/ulw-research/scripts/tests/fixtures/fidelity/ledger-legacy-dev.json +859 -0
- package/dist/skills/ulw-research/scripts/tests/fixtures/fidelity/ledger-verify.json +420 -0
- package/dist/skills/ulw-research/scripts/tests/fixtures/fidelity/polish-edits.json +12 -0
- package/dist/skills/ulw-research/scripts/tests/fixtures/fidelity/report-bad.html +19 -0
- package/dist/skills/ulw-research/scripts/tests/fixtures/fidelity/report-good.html +39 -0
- package/dist/skills/ulw-research/scripts/tests/fixtures/fidelity/sources/S4/acronyms.json +3 -0
- package/dist/skills/ulw-research/scripts/tests/fixtures/fidelity/sources/S4/meta.json +9 -0
- package/dist/skills/ulw-research/scripts/tests/fixtures/fidelity/sources/S4/sentences.jsonl +7 -0
- package/dist/skills/ulw-research/scripts/tests/fixtures/fidelity/sources/S4/text.txt +7 -0
- package/dist/skills/ulw-research/scripts/tests/fixtures/fidelity/sources/S4.html +19 -0
- package/dist/skills/ulw-research/scripts/tests/fixtures/fidelity/sources/S5/acronyms.json +1 -0
- package/dist/skills/ulw-research/scripts/tests/fixtures/fidelity/sources/S5/meta.json +9 -0
- package/dist/skills/ulw-research/scripts/tests/fixtures/fidelity/sources/S5/sentences.jsonl +1 -0
- package/dist/skills/ulw-research/scripts/tests/fixtures/fidelity/sources/S5/text.txt +1 -0
- package/dist/skills/ulw-research/scripts/tests/fixtures/fidelity/sources/S9.txt +1 -0
- package/dist/skills/ulw-research/scripts/unit-algebra.mjs +101 -0
- package/dist/skills/ulw-research/scripts/unit-errors.mjs +23 -0
- package/dist/skills/ulw-research/scripts/unit-metadata.mjs +31 -0
- package/dist/skills/ulw-research/scripts/unit-pairs.mjs +72 -0
- package/dist/skills/ulw-research/scripts/unit-precision.mjs +44 -0
- package/dist/skills/ulw-research/scripts/unit-rules-shapes.mjs +64 -0
- package/dist/skills/ulw-research/scripts/unit-rules.mjs +173 -0
- package/dist/skills/ulw-research/scripts/units.mjs +4 -4
- package/dist/testing/module-mock-lifecycle.d.ts +1 -1
- package/dist/tui.js +32 -34
- package/package.json +13 -13
- package/packages/lsp-core/src/mcp-protocol-pin.test.ts +29 -0
- package/packages/lsp-core/src/mcp.ts +5 -2
- package/packages/lsp-daemon/dist/cli.js +3 -3
- package/packages/lsp-daemon/dist/client.js +3 -3
- package/packages/lsp-daemon/dist/index.js +3 -3
- package/packages/lsp-tools-mcp/dist/cli.js +3 -3
- package/packages/lsp-tools-mcp/dist/mcp.js +3 -3
- package/packages/omo-codex/plugin/.codex-plugin/plugin.json +1 -1
- package/packages/omo-codex/plugin/components/bootstrap/dist/cli.js +23 -14
- 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/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 +1 -1
- 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/ultrawork/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/ultrawork/package.json +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/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 +12 -12
- package/packages/omo-codex/plugin/package.json +1 -1
- package/packages/omo-codex/plugin/scripts/sync-skills.mjs +8 -3
- package/packages/omo-codex/plugin/shared/test/config-loader.test.ts +0 -2
- package/packages/omo-codex/plugin/skills/ast-grep/AGENTS.md +1 -1
- package/packages/omo-codex/plugin/skills/browser/runtime/omowright/manifest.json +1 -1
- package/packages/omo-codex/plugin/skills/frontend/ATTRIBUTION.md +2 -2
- package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/AGENTS.md +1 -1
- package/packages/omo-codex/plugin/skills/ulw-research/AGENTS.md +14 -2
- package/packages/omo-codex/plugin/skills/ulw-research/SKILL.md +36 -11
- package/packages/omo-codex/plugin/skills/ulw-research/references/deliverable-phase.md +81 -14
- package/packages/omo-codex/plugin/skills/ulw-research/references/fact-ledger-grammar.md +19 -1
- package/packages/omo-codex/plugin/skills/ulw-research/references/fact-ledger.md +95 -0
- package/packages/omo-codex/plugin/skills/ulw-research/references/fidelity-claim-kinds.md +19 -0
- package/packages/omo-codex/plugin/skills/ulw-research/references/report-gates.md +21 -4
- package/packages/omo-codex/plugin/skills/ulw-research/references/report-visualization.md +2 -0
- package/packages/omo-codex/plugin/skills/ulw-research/references/unit-rules.md +157 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/claim-kinds.mjs +116 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/cli-support.mjs +19 -5
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/contracts.mjs +27 -6
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-commands-meaning.mjs +176 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-commands-polish.mjs +127 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-commands-verify.mjs +183 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-commands.mjs +241 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-derive.mjs +66 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-expr.mjs +108 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-fill.mjs +194 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-ledger-kinds.mjs +127 -13
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-ledger-schema.mjs +33 -16
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-ledger.mjs +29 -9
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-locate.mjs +231 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-pairs.mjs +185 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-recompute.mjs +89 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-binding.mjs +130 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-brief.mjs +5 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-claims.mjs +89 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-lexicon.mjs +145 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-records.mjs +163 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-state.mjs +2 -1
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-trace.mjs +85 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-values.mjs +229 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/gates-fidelity.mjs +113 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/gates-static.mjs +28 -8
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/html-lite.mjs +2 -2
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/html-markup.mjs +182 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/md-lite.mjs +193 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/meaning-check.mjs +196 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/outcome.mjs +6 -5
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/placeholder.mjs +184 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/polish-apply.mjs +115 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/polish-diff.mjs +31 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/polish-project.mjs +125 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/polish-rules.mjs +170 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/report-tools-commands.mjs +19 -8
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/report-tools.mjs +15 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/source-commands.mjs +140 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/source-snapshot.mjs +176 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/unit-algebra.mjs +101 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/unit-errors.mjs +23 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/unit-metadata.mjs +31 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/unit-pairs.mjs +72 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/unit-precision.mjs +44 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/unit-rules-shapes.mjs +64 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/unit-rules.mjs +173 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/units.mjs +4 -4
- package/packages/omo-codex/plugin/test/sync-skills-codex-compatibility.test.mjs +19 -0
- package/packages/omo-codex/plugin/test/sync-skills-test-support.mjs +7 -3
- package/packages/omo-codex/scripts/install-dist/install-local.mjs +28 -19
- package/packages/shared-skills/skills/ast-grep/AGENTS.md +1 -1
- package/packages/shared-skills/skills/browser/runtime/omowright/manifest.json +1 -1
- package/packages/shared-skills/skills/frontend/ATTRIBUTION.md +2 -2
- package/packages/shared-skills/skills/ultimate-browsing/engine/AGENTS.md +1 -1
- package/packages/shared-skills/skills/ulw-research/AGENTS.md +14 -2
- package/packages/shared-skills/skills/ulw-research/SKILL.md +37 -12
- package/packages/shared-skills/skills/ulw-research/references/deliverable-phase.md +81 -14
- package/packages/shared-skills/skills/ulw-research/references/fact-ledger-grammar.md +19 -1
- package/packages/shared-skills/skills/ulw-research/references/fact-ledger.md +95 -0
- package/packages/shared-skills/skills/ulw-research/references/fidelity-claim-kinds.md +19 -0
- package/packages/shared-skills/skills/ulw-research/references/report-gates.md +21 -4
- package/packages/shared-skills/skills/ulw-research/references/report-visualization.md +2 -0
- package/packages/shared-skills/skills/ulw-research/references/unit-rules.md +157 -0
- package/packages/shared-skills/skills/ulw-research/scripts/claim-kinds.mjs +116 -0
- package/packages/shared-skills/skills/ulw-research/scripts/claim-kinds.test.ts +193 -0
- package/packages/shared-skills/skills/ulw-research/scripts/cli-support.mjs +19 -5
- package/packages/shared-skills/skills/ulw-research/scripts/cli-support.test.ts +13 -2
- package/packages/shared-skills/skills/ulw-research/scripts/contracts.mjs +27 -6
- package/packages/shared-skills/skills/ulw-research/scripts/contracts.test.ts +3 -0
- package/packages/shared-skills/skills/ulw-research/scripts/copy-returns-polish.test.ts +209 -0
- package/packages/shared-skills/skills/ulw-research/scripts/copy-returns.test.ts +183 -0
- package/packages/shared-skills/skills/ulw-research/scripts/delivery-barrier.test.ts +245 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fact-commands-meaning.mjs +176 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fact-commands-polish.mjs +127 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fact-commands-verify.mjs +183 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fact-commands.mjs +241 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fact-derive.mjs +66 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fact-derive.test.ts +375 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fact-expr.mjs +108 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fact-fill.mjs +194 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fact-fill.test.ts +221 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fact-ledger-grammar.test.ts +9 -2
- package/packages/shared-skills/skills/ulw-research/scripts/fact-ledger-kinds.mjs +127 -13
- package/packages/shared-skills/skills/ulw-research/scripts/fact-ledger-kinds.test.ts +18 -3
- package/packages/shared-skills/skills/ulw-research/scripts/fact-ledger-legacy.test.ts +160 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fact-ledger-schema.mjs +33 -16
- package/packages/shared-skills/skills/ulw-research/scripts/fact-ledger.mjs +29 -9
- package/packages/shared-skills/skills/ulw-research/scripts/fact-ledger.test.ts +2 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fact-locate.mjs +231 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fact-locate.test.ts +118 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fact-pairs.mjs +185 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fact-pairs.test.ts +151 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fact-recompute.mjs +89 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fact-unit-dimensions.test.ts +409 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fact-unit-rules.test.ts +388 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fidelity-binding.mjs +130 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fidelity-binding.test.ts +81 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fidelity-brief.mjs +5 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fidelity-claims.mjs +89 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fidelity-lexicon.mjs +145 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fidelity-lexicon.test.ts +117 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fidelity-records.mjs +163 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fidelity-state.mjs +2 -1
- package/packages/shared-skills/skills/ulw-research/scripts/fidelity-trace.mjs +85 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fidelity-trace.test.ts +58 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fidelity-values.mjs +229 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fidelity-values.test.ts +270 -0
- package/packages/shared-skills/skills/ulw-research/scripts/gates-fidelity.mjs +113 -0
- package/packages/shared-skills/skills/ulw-research/scripts/gates-fidelity.test.ts +73 -0
- package/packages/shared-skills/skills/ulw-research/scripts/gates-static.mjs +28 -8
- package/packages/shared-skills/skills/ulw-research/scripts/html-lite.mjs +2 -2
- package/packages/shared-skills/skills/ulw-research/scripts/html-markup.mjs +182 -0
- package/packages/shared-skills/skills/ulw-research/scripts/md-lite.mjs +193 -0
- package/packages/shared-skills/skills/ulw-research/scripts/md-lite.test.ts +271 -0
- package/packages/shared-skills/skills/ulw-research/scripts/meaning-check.mjs +196 -0
- package/packages/shared-skills/skills/ulw-research/scripts/meaning-check.test.ts +228 -0
- package/packages/shared-skills/skills/ulw-research/scripts/outcome.mjs +6 -5
- package/packages/shared-skills/skills/ulw-research/scripts/placeholder.mjs +184 -0
- package/packages/shared-skills/skills/ulw-research/scripts/placeholder.test.ts +77 -0
- package/packages/shared-skills/skills/ulw-research/scripts/polish-apply.mjs +115 -0
- package/packages/shared-skills/skills/ulw-research/scripts/polish-apply.test.ts +363 -0
- package/packages/shared-skills/skills/ulw-research/scripts/polish-diff.mjs +31 -0
- package/packages/shared-skills/skills/ulw-research/scripts/polish-diff.test.ts +168 -0
- package/packages/shared-skills/skills/ulw-research/scripts/polish-markup.test.ts +187 -0
- package/packages/shared-skills/skills/ulw-research/scripts/polish-project.mjs +125 -0
- package/packages/shared-skills/skills/ulw-research/scripts/polish-rules.mjs +170 -0
- package/packages/shared-skills/skills/ulw-research/scripts/references-fences.test.ts +25 -0
- package/packages/shared-skills/skills/ulw-research/scripts/report-tools-commands.mjs +19 -8
- package/packages/shared-skills/skills/ulw-research/scripts/report-tools.mjs +15 -0
- package/packages/shared-skills/skills/ulw-research/scripts/report-tools.test.ts +545 -2
- package/packages/shared-skills/skills/ulw-research/scripts/source-commands.mjs +140 -0
- package/packages/shared-skills/skills/ulw-research/scripts/source-snapshot.mjs +176 -0
- package/packages/shared-skills/skills/ulw-research/scripts/source-snapshot.test.ts +113 -0
- package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/assets/share.csv +3 -0
- package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/gen-legacy-dev.mjs +89 -0
- package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/ledger-basic.json +223 -0
- package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/ledger-legacy-dev.json +859 -0
- package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/ledger-verify.json +420 -0
- package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/polish-edits.json +12 -0
- package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/report-bad.html +19 -0
- package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/report-good.html +39 -0
- package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/sources/S4/acronyms.json +3 -0
- package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/sources/S4/meta.json +9 -0
- package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/sources/S4/sentences.jsonl +7 -0
- package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/sources/S4/text.txt +7 -0
- package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/sources/S4.html +19 -0
- package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/sources/S5/acronyms.json +1 -0
- package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/sources/S5/meta.json +9 -0
- package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/sources/S5/sentences.jsonl +1 -0
- package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/sources/S5/text.txt +1 -0
- package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/sources/S9.txt +1 -0
- package/packages/shared-skills/skills/ulw-research/scripts/unit-algebra.mjs +101 -0
- package/packages/shared-skills/skills/ulw-research/scripts/unit-errors.mjs +23 -0
- package/packages/shared-skills/skills/ulw-research/scripts/unit-metadata.mjs +31 -0
- package/packages/shared-skills/skills/ulw-research/scripts/unit-pairs.mjs +72 -0
- package/packages/shared-skills/skills/ulw-research/scripts/unit-precision.mjs +44 -0
- package/packages/shared-skills/skills/ulw-research/scripts/unit-rules-shapes.mjs +64 -0
- package/packages/shared-skills/skills/ulw-research/scripts/unit-rules.mjs +173 -0
- package/packages/shared-skills/skills/ulw-research/scripts/units.mjs +4 -4
- package/dist/config-migration/dori-section-rename.d.ts +0 -3
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
// Architecture contract (retention bar): each shared table, list, regex, or classifier has one owner
|
|
2
|
+
// export, so a reintroduced local copy in a consumer fails one of these tests (#11156 sweep).
|
|
3
|
+
import { describe, expect, test } from "bun:test"
|
|
4
|
+
import { readdirSync, readFileSync } from "node:fs"
|
|
5
|
+
import { join } from "node:path"
|
|
6
|
+
|
|
7
|
+
import { sha256Of } from "./cli-support.mjs"
|
|
8
|
+
import { countDefects, DEFECT_CODES, SEVERITIES } from "./contracts.mjs"
|
|
9
|
+
import { meaningStatus, PROMOTION, promotionEligibility } from "./fact-commands-meaning.mjs"
|
|
10
|
+
import { editionFormatOf, MD_HTML_BLOCK_RULES, markdownAsHtml, projectMarkdownInline } from "./fact-commands-verify.mjs"
|
|
11
|
+
import { EXEMPT_KINDS, isExemptKind } from "./fidelity-binding.mjs"
|
|
12
|
+
import { blocksDelivery } from "./gates-fidelity.mjs"
|
|
13
|
+
import { MD_BLOCK_PATTERNS, parseMarkdown } from "./md-lite.mjs"
|
|
14
|
+
import { DRIFT_KINDS, isDriftKind, MODEL_TIERS, VERDICT_SCHEMA, VERDICTS } from "./meaning-check.mjs"
|
|
15
|
+
import { parseRenderedQuote, renderQuote } from "./placeholder.mjs"
|
|
16
|
+
|
|
17
|
+
const source = (name: string) => readFileSync(join(import.meta.dir, name), "utf8")
|
|
18
|
+
|
|
19
|
+
describe("severity vocabulary comes from contracts", () => {
|
|
20
|
+
test("#given DEFECT_CODES #when its severities are collected #then they are exactly SEVERITIES", () => {
|
|
21
|
+
const used = new Set(Object.values(DEFECT_CODES).map((entry) => entry.severity))
|
|
22
|
+
expect([...used].sort()).toEqual([...SEVERITIES].sort())
|
|
23
|
+
expect(Object.isFrozen(SEVERITIES)).toBe(true)
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
test("#given defects of every severity #when counted #then the buckets are SEVERITIES plus total", () => {
|
|
27
|
+
const defects = SEVERITIES.map((severity) => ({ severity }))
|
|
28
|
+
expect(Object.keys(countDefects(defects))).toEqual([...SEVERITIES, "total"])
|
|
29
|
+
expect(countDefects(defects)).toEqual({ ...Object.fromEntries(SEVERITIES.map((severity) => [severity, 1])), total: SEVERITIES.length })
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
test("#given every registry code and an unknown code #when the meaning status is computed #then it follows blocksDelivery", () => {
|
|
33
|
+
for (const code of [...Object.keys(DEFECT_CODES), "not_a_registered_code"]) {
|
|
34
|
+
expect(meaningStatus([{ code, severity: "minor" }]), code).toBe(blocksDelivery({ code }) ? "fail" : "pass")
|
|
35
|
+
}
|
|
36
|
+
expect(meaningStatus([])).toBe("pass")
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
test("#given the command modules #when read #then none restates the severity buckets, the pair kinds, or a window literal", () => {
|
|
40
|
+
for (const name of ["fact-commands-meaning.mjs", "fact-commands-verify.mjs"]) {
|
|
41
|
+
const text = source(name)
|
|
42
|
+
expect(text, name).not.toMatch(/blocker:\s*0/u)
|
|
43
|
+
expect(text, name).not.toMatch(/\["quote",\s*"claim",\s*"translation"\]/u)
|
|
44
|
+
expect(text, name).not.toMatch(/slice\(-\d+\)/u)
|
|
45
|
+
expect(text, name).not.toMatch(/counts\.blocker \+ counts\.major/u)
|
|
46
|
+
}
|
|
47
|
+
})
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
describe("one sha256 helper", () => {
|
|
51
|
+
test("#given every runtime module #when read #then only cli-support builds a hash, so records and receipts share sha256Of", () => {
|
|
52
|
+
const modules = readdirSync(import.meta.dir).filter((name) => name.endsWith(".mjs") && name !== "cli-support.mjs")
|
|
53
|
+
expect(modules.length).toBeGreaterThan(0)
|
|
54
|
+
for (const name of modules) expect(source(name), name).not.toMatch(/createHash\(/u)
|
|
55
|
+
expect(sha256Of("abc")).toBe("ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad")
|
|
56
|
+
expect(sha256Of(Buffer.from("abc"))).toBe(sha256Of("abc"))
|
|
57
|
+
})
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
describe("calibration policy is the exported PROMOTION", () => {
|
|
61
|
+
const confirmedRun = (run: string, disputed: boolean) => ({ run_id: run, confirmed: true, disputed })
|
|
62
|
+
|
|
63
|
+
test("#given false blocks only before the window #when eligibility is computed #then they are not counted", () => {
|
|
64
|
+
const window = PROMOTION.false_block_window
|
|
65
|
+
expect(Number.isInteger(window) && window > 0).toBe(true)
|
|
66
|
+
const old = [0, 1, 2].map((n) => confirmedRun(`old${n}`, true))
|
|
67
|
+
const recent = Array.from({ length: window }, (_, n) => confirmedRun(`new${n}`, false))
|
|
68
|
+
expect(promotionEligibility([...old, ...recent]).report_false_blocks_in_window).toBe(0)
|
|
69
|
+
expect(promotionEligibility([...recent, ...old]).report_false_blocks_in_window).toBe(3)
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
test("#given false blocks at the policy maximum #when eligibility is computed #then the threshold read from PROMOTION decides", () => {
|
|
73
|
+
const limit = PROMOTION.max_report_false_blocks_in_window
|
|
74
|
+
const clean = Array.from({ length: PROMOTION.min_runs * 20 }, (_, n) => confirmedRun(`c${n}`, false))
|
|
75
|
+
const blocks = (count: number) => Array.from({ length: count }, (_, n) => confirmedRun(`b${n}`, true))
|
|
76
|
+
expect(promotionEligibility([...clean, ...blocks(limit)]).eligible).toBe(true)
|
|
77
|
+
expect(promotionEligibility([...clean, ...blocks(limit + 1)]).eligible).toBe(false)
|
|
78
|
+
})
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
describe("verdict schema comes from the meaning-check registries", () => {
|
|
82
|
+
test("#given VERDICT_SCHEMA #when read #then its vocabularies are the owner exports", () => {
|
|
83
|
+
expect(VERDICT_SCHEMA.verdict).toBe(VERDICTS)
|
|
84
|
+
expect(VERDICT_SCHEMA.model_tier).toBe(MODEL_TIERS)
|
|
85
|
+
expect(VERDICT_SCHEMA.drift_kind).toBe(DRIFT_KINDS)
|
|
86
|
+
expect(Object.isFrozen(VERDICTS)).toBe(true)
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
test("#given every drift kind and a stranger #when checked #then isDriftKind is membership in DRIFT_KINDS", () => {
|
|
90
|
+
for (const kind of DRIFT_KINDS) expect(isDriftKind(kind)).toBe(true)
|
|
91
|
+
expect(isDriftKind("not_a_kind")).toBe(false)
|
|
92
|
+
expect(source("meaning-check.mjs")).not.toMatch(/new Set\(DRIFT_KINDS\)|new Set\(\["drift"/u)
|
|
93
|
+
})
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
describe("inherited lexical tables come from their owners", () => {
|
|
97
|
+
test("#given fact-pairs #when read #then it holds no Hangul range, exempt-kind set, or hand-built hedge regex", () => {
|
|
98
|
+
const text = source("fact-pairs.mjs")
|
|
99
|
+
expect(text).not.toMatch(/\\uAC00-\\uD7A3/u)
|
|
100
|
+
expect(text).not.toMatch(/\["opinion",\s*"recommendation"\]/u)
|
|
101
|
+
expect(text).not.toMatch(/new RegExp\(/u)
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
test("#given EXEMPT_KINDS #when checked #then isExemptKind is membership in it", () => {
|
|
105
|
+
for (const kind of EXEMPT_KINDS) expect(isExemptKind(kind)).toBe(true)
|
|
106
|
+
expect(isExemptKind("finding")).toBe(false)
|
|
107
|
+
expect(isExemptKind(undefined)).toBe(false)
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
test("#given md-lite #when its block grammar is read #then MD_BLOCK_PATTERNS names every block regex", () => {
|
|
111
|
+
expect(Object.keys(MD_BLOCK_PATTERNS).sort()).toEqual(["BLOCKQUOTE", "CLOSING_HASHES", "FENCE", "HEADING", "LIST_ITEM", "TABLE_SEPARATOR", "THEMATIC_BREAK"])
|
|
112
|
+
for (const pattern of Object.values(MD_BLOCK_PATTERNS)) expect(pattern).toBeInstanceOf(RegExp)
|
|
113
|
+
expect(Object.isFrozen(MD_BLOCK_PATTERNS)).toBe(true)
|
|
114
|
+
})
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
describe("markdown projection rules", () => {
|
|
118
|
+
const EVERY_KIND = ["# Title", "", "Para text.", "", "- item", "", "> quoted", "", "| a | b |", "| - | - |", "| 1 | 2 |", "", "```", "code", "```", ""].join("\n")
|
|
119
|
+
|
|
120
|
+
test("#given every block kind md-lite emits #when projected #then each kind has a projection rule", () => {
|
|
121
|
+
const kinds = [...new Set(parseMarkdown(EVERY_KIND).blocks.map((block: { kind: string }) => block.kind))].sort()
|
|
122
|
+
expect(kinds).toEqual(["blockquote", "code", "heading", "list_item", "paragraph", "table_row"])
|
|
123
|
+
for (const kind of kinds) expect(Object.hasOwn(MD_HTML_BLOCK_RULES, kind), kind).toBe(true)
|
|
124
|
+
expect(Object.keys(MD_HTML_BLOCK_RULES).sort()).toEqual(kinds)
|
|
125
|
+
const { html } = markdownAsHtml(EVERY_KIND, [], { rows: [] })
|
|
126
|
+
expect(html).toContain("<h1>Title</h1>")
|
|
127
|
+
expect(html).toContain("<ul><li>item</li></ul>")
|
|
128
|
+
expect(html).toMatch(/<table><tr><td>a<\/td><td>b<\/td><\/tr>\s*<tr><td>1<\/td><td>2<\/td><\/tr><\/table>/u)
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
test("#given an unterminated fence #when projected #then it is refused naming the line", () => {
|
|
132
|
+
expect(() => markdownAsHtml("Intro.\n\n```\nno close\n", [], { rows: [] })).toThrow(/unterminated_fence at line 3/u)
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
test("#given inline links and code #when projected #then they stay elements and the rest is escaped", () => {
|
|
136
|
+
expect(projectMarkdownInline("a <b> [x](https://e.x) `c`")).toBe('a <b> <a href="https://e.x">x</a> <code>c</code>')
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
test("#given a fill map with quotes in translates and fact_id #when projected #then the attributes are escaped", () => {
|
|
140
|
+
const md = "Before X after.\n\n> Quoted block.\n"
|
|
141
|
+
const span = { fact_id: 'F"1', start: 7, end: 8, rendered: "X", translates: 'T"1' }
|
|
142
|
+
const block = { fact_id: 'Q"2', start: md.indexOf(">"), end: md.length - 1, rendered: "", replaced: "a" }
|
|
143
|
+
const { html } = markdownAsHtml(md, [span, block], { rows: [] })
|
|
144
|
+
expect(html).toContain('data-translates="T"1"')
|
|
145
|
+
expect(html).toContain('data-fact="Q"2"')
|
|
146
|
+
expect(markdownAsHtml("Before X after.\n", [{ ...span, translates: undefined }], { rows: [] }).html).toContain('data-fact="F"1"')
|
|
147
|
+
expect(html).not.toMatch(/="[^"]*"1"/u)
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
test("#given paths of each case #when the format is inferred #then md and markdown are Markdown and a flag wins", () => {
|
|
151
|
+
expect(["r.md", "R.MD", "r.markdown", "r.html", "r.txt"].map((path) => editionFormatOf(path, undefined))).toEqual(["md", "md", "md", "html", "html"])
|
|
152
|
+
expect(editionFormatOf("r.md", "html")).toBe("html")
|
|
153
|
+
})
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
describe("rendered quotes round-trip through the placeholder owner", () => {
|
|
157
|
+
const row = { fact_id: "Q1", kind: "quote", source_id: "S4", quote: "We estimate completion no earlier than 2027." }
|
|
158
|
+
const selections = [
|
|
159
|
+
["full", row.quote],
|
|
160
|
+
["lead=2", "We estimate"],
|
|
161
|
+
["from=completion,to=2027", "completion no earlier than 2027"],
|
|
162
|
+
]
|
|
163
|
+
|
|
164
|
+
for (const [selector, text] of selections) {
|
|
165
|
+
test(`#given a ${selector} quote rendered for md #when projected #then the quote element and marker survive`, () => {
|
|
166
|
+
const rendered = renderQuote(row, selector, text, "md")
|
|
167
|
+
expect(parseRenderedQuote(rendered)).toEqual({ text, marker: "[S4]" })
|
|
168
|
+
const md = `The operator wrote ${rendered}.\n`
|
|
169
|
+
const entry = { fact_id: "Q1", start: md.indexOf(rendered), end: md.indexOf(rendered) + rendered.length, rendered, selector }
|
|
170
|
+
const projected = markdownAsHtml(md, [entry], { rows: [row] })
|
|
171
|
+
expect(projected.html).toContain(renderQuote(row, selector, text, "html"))
|
|
172
|
+
expect(projected.stale).toEqual([])
|
|
173
|
+
const edited = md.replace(text, `${text} indeed`)
|
|
174
|
+
const stale = markdownAsHtml(edited, [{ ...entry, end: entry.end + " indeed".length }], { rows: [row] })
|
|
175
|
+
expect(stale.html).not.toContain("<q ")
|
|
176
|
+
expect(stale.stale).toHaveLength(1)
|
|
177
|
+
})
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
test("#given bytes that are not the md quote layout #when parsed #then null", () => {
|
|
181
|
+
expect(parseRenderedQuote("plain text [S4]")).toBeNull()
|
|
182
|
+
})
|
|
183
|
+
})
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
import { describe, expect, onTestFinished, test } from "bun:test"
|
|
2
|
+
import { mkdirSync, mkdtempSync, readFileSync, rmSync, unlinkSync, writeFileSync } from "node:fs"
|
|
3
|
+
import { tmpdir } from "node:os"
|
|
4
|
+
import { join } from "node:path"
|
|
5
|
+
|
|
6
|
+
import { sha256Of } from "./cli-support.mjs"
|
|
7
|
+
import { DEFECT_CODES } from "./contracts.mjs"
|
|
8
|
+
import { FIDELITY_RECORDS, MEANING_RECORD, RECORD_ENVELOPE, writeFidelityRecord } from "./fidelity-records.mjs"
|
|
9
|
+
import { blocksDelivery } from "./gates-fidelity.mjs"
|
|
10
|
+
import { run } from "./report-tools.mjs"
|
|
11
|
+
|
|
12
|
+
async function delivered(format = "html", codes: string[] | null = null) {
|
|
13
|
+
const root = mkdtempSync(join(tmpdir(), "delivery-barrier-"))
|
|
14
|
+
onTestFinished(() => rmSync(root, { recursive: true, force: true }))
|
|
15
|
+
const dir = join(root, "20260101-120000")
|
|
16
|
+
mkdirSync(dir)
|
|
17
|
+
const path = join(dir, `report.${format}`)
|
|
18
|
+
const record = join(dir, "fidelity-defects.json")
|
|
19
|
+
writeFileSync(path, "<p>Report</p>")
|
|
20
|
+
if (codes !== null) writeFidelityRecord(record, "fidelity", { artifact_sha256: sha256Of(readFileSync(path)), report: path, defects: codes.map((code) => ({ code, severity: "minor", message: code })) })
|
|
21
|
+
const invoke = (args: string[]) => run(["outcome", ...args, "--session-dir", dir])
|
|
22
|
+
await invoke(["init", "--promised", format])
|
|
23
|
+
expect((await invoke(["set", format, "delivered", "--path", path, ...(codes === null ? [] : ["--verified-by", record])])).exitCode).toBe(0)
|
|
24
|
+
return { dir, path, record, invoke }
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Enumerate the source registry, not a second list of today's fidelity codes.
|
|
28
|
+
for (const [code, entry] of Object.entries(DEFECT_CODES)) {
|
|
29
|
+
if (!entry.integrity && entry.severity === "minor") continue
|
|
30
|
+
test(`#given registry blocker ${code} #when verified and finished #then delivery refuses despite a demoted row`, async () => {
|
|
31
|
+
const { dir, invoke } = await delivered("html", [code])
|
|
32
|
+
for (const command of ["verify", "finish"]) {
|
|
33
|
+
const result = await invoke([command])
|
|
34
|
+
expect(result.exitCode).toBe(1)
|
|
35
|
+
expect(result.json.problems).toContainEqual(expect.objectContaining({ code: "fidelity_unresolved", codes: [code] }))
|
|
36
|
+
}
|
|
37
|
+
expect(JSON.parse(readFileSync(join(dir, "outcome.json"), "utf8")).finishedAt).toBeNull()
|
|
38
|
+
})
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
test("#given an unknown defect code #when verified or finished #then delivery refuses with its reason", async () => {
|
|
42
|
+
const { invoke } = await delivered("html", ["future_fidelity_defect"])
|
|
43
|
+
for (const command of ["verify", "finish"]) {
|
|
44
|
+
const result = await invoke([command])
|
|
45
|
+
expect(result.exitCode).toBe(1)
|
|
46
|
+
expect(JSON.stringify(result.json.problems)).toContain("unknown defect code future_fidelity_defect")
|
|
47
|
+
}
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
test("#given a registry non-blocker #when verified and finished #then delivery passes", async () => {
|
|
51
|
+
const entry = Object.entries(DEFECT_CODES).find(([, value]) => !value.integrity && value.severity === "minor")
|
|
52
|
+
if (!entry) throw new Error("registry must have a non-blocking entry")
|
|
53
|
+
const { invoke } = await delivered("html", [entry[0]])
|
|
54
|
+
expect((await invoke(["verify"])).exitCode).toBe(0)
|
|
55
|
+
expect((await invoke(["finish"])).exitCode).toBe(0)
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
for (const format of ["html", "pdf", "docx"]) {
|
|
59
|
+
test(`#given delivered ${format} without any ledger or gate record #when verified and finished #then both refuse`, async () => {
|
|
60
|
+
const { invoke } = await delivered(format)
|
|
61
|
+
for (const command of ["verify", "finish"]) {
|
|
62
|
+
const result = await invoke([command])
|
|
63
|
+
expect(result.exitCode).toBe(1)
|
|
64
|
+
expect(JSON.stringify(result.json.problems)).toContain("fidelity gate not run")
|
|
65
|
+
}
|
|
66
|
+
})
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
test("#given a missing gate #when explicitly skipped with a reason #then the logged choice permits delivery", async () => {
|
|
70
|
+
const { dir, invoke } = await delivered()
|
|
71
|
+
const now = "2026-01-01T12:01:00.000Z"
|
|
72
|
+
await invoke(["gate", "fidelity", "not_run", "--reason", "No factual content in this edition", "--now", now])
|
|
73
|
+
const manifest = JSON.parse(readFileSync(join(dir, "outcome.json"), "utf8"))
|
|
74
|
+
expect(manifest.gateHistory).toContainEqual({ gate: "fidelity", status: "not_run", reason: "No factual content in this edition", at: now })
|
|
75
|
+
expect((await invoke(["verify"])).exitCode).toBe(0)
|
|
76
|
+
expect((await invoke(["finish"])).exitCode).toBe(0)
|
|
77
|
+
await invoke(["gate", "fidelity", "pass"])
|
|
78
|
+
expect((await invoke(["verify"])).exitCode).toBe(1)
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
test("#given a missing gate #when skipped without a meaningful reason #then the command refuses", async () => {
|
|
82
|
+
const { invoke } = await delivered()
|
|
83
|
+
for (const reason of [[], ["--reason", " "]]) {
|
|
84
|
+
await expect(invoke(["gate", "fidelity", "not_run", ...reason])).rejects.toThrow("reason")
|
|
85
|
+
}
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
test("#given a fact ledger and an unverified edition #when the gate is skipped with a reason #then delivery still refuses", async () => {
|
|
89
|
+
const { dir, invoke } = await delivered()
|
|
90
|
+
writeFileSync(join(dir, "fact-ledger.json"), JSON.stringify({ version: 1, facts: [] }))
|
|
91
|
+
await invoke(["gate", "fidelity", "not_run", "--reason", "This harness has no verifier"])
|
|
92
|
+
for (const command of ["verify", "finish"]) {
|
|
93
|
+
const result = await invoke([command])
|
|
94
|
+
expect(result.exitCode).toBe(1)
|
|
95
|
+
expect(JSON.stringify(result.json.problems)).toContain("fidelity gate not run")
|
|
96
|
+
}
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
test("#given a stale fidelity record nobody attached #when the gate is skipped with a reason #then delivery still refuses", async () => {
|
|
100
|
+
const { dir, path, invoke } = await delivered()
|
|
101
|
+
writeFidelityRecord(join(dir, "fidelity-defects.json"), "fidelity", { artifact_sha256: "0".repeat(64), report: path, defects: [] })
|
|
102
|
+
await invoke(["gate", "fidelity", "not_run", "--reason", "This harness has no verifier"])
|
|
103
|
+
for (const command of ["verify", "finish"]) {
|
|
104
|
+
const result = await invoke([command])
|
|
105
|
+
expect(result.exitCode).toBe(1)
|
|
106
|
+
expect(JSON.stringify(result.json.problems)).toContain("fidelity gate not run")
|
|
107
|
+
}
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
test("#given an explicit skip and an existing blocker #when verified #then the skip cannot release the blocker", async () => {
|
|
111
|
+
const { invoke } = await delivered("html", ["claim_status_invalid"])
|
|
112
|
+
await invoke(["gate", "fidelity", "not_run", "--reason", "Unsupported edition"])
|
|
113
|
+
expect((await invoke(["verify"])).exitCode).toBe(1)
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
const LOCKED_BRIEF = readFileSync(join(import.meta.dir, "tests", "fixtures", "fidelity-brief-locked.md"), "utf8")
|
|
117
|
+
const BLOCKER = Object.keys(DEFECT_CODES).find((code) => blocksDelivery({ code })) as string
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* A delivered html edition whose locked brief requires every table record, each present, fresh for
|
|
121
|
+
* the delivered bytes, and clean. recordPath is where the session keeps each row's record; a table
|
|
122
|
+
* row with no entry here fails the coverage test below, so a new row brings its own cases.
|
|
123
|
+
*/
|
|
124
|
+
async function fullyRecorded(brief = LOCKED_BRIEF) {
|
|
125
|
+
const session = await delivered("html", [])
|
|
126
|
+
writeFileSync(join(session.dir, "brief.md"), brief)
|
|
127
|
+
const recordPath: Record<string, string> = { fidelity: session.record, meaning: join(session.dir, MEANING_RECORD) }
|
|
128
|
+
const write = (kind: string, fields: Record<string, unknown> = {}) =>
|
|
129
|
+
writeFidelityRecord(recordPath[kind], kind, { artifact_sha256: sha256Of(readFileSync(session.path)), report: session.path, defects: [], ...fields })
|
|
130
|
+
write("meaning")
|
|
131
|
+
const outcomes = async () => Promise.all(["verify", "finish"].map((command) => session.invoke([command])))
|
|
132
|
+
return { ...session, recordPath, write, outcomes }
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
async function expectRefused(session: Awaited<ReturnType<typeof fullyRecorded>>, problem: Record<string, unknown>) {
|
|
136
|
+
for (const result of await session.outcomes()) {
|
|
137
|
+
expect(result.exitCode).toBe(1)
|
|
138
|
+
expect(result.json.problems).toContainEqual(expect.objectContaining(problem))
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
test("#given the record table #when every row is read #then each row has every rule column and an arranged session path", async () => {
|
|
143
|
+
const { recordPath } = await fullyRecorded()
|
|
144
|
+
expect(Object.keys(recordPath).sort()).toEqual(FIDELITY_RECORDS.map((entry) => entry.kind).sort())
|
|
145
|
+
for (const entry of FIDELITY_RECORDS) {
|
|
146
|
+
for (const column of ["targets", "requiredWhen", "remedy", "blocking"] as const) expect(typeof entry[column], `${entry.kind}.${column}`).toBe("function")
|
|
147
|
+
expect(RECORD_ENVELOPE).toContain(entry.bindsTo)
|
|
148
|
+
}
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
// The table is the barrier's spec: every row gets the same five cases.
|
|
152
|
+
for (const entry of FIDELITY_RECORDS) {
|
|
153
|
+
describe(`record row ${entry.kind}`, () => {
|
|
154
|
+
test("#given the record is missing while required #when verified and finished #then delivery refuses", async () => {
|
|
155
|
+
const session = await fullyRecorded()
|
|
156
|
+
unlinkSync(session.recordPath[entry.kind])
|
|
157
|
+
await expectRefused(session, { code: "edition_unverified", path: session.recordPath[entry.kind] })
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
test("#given the record is for other bytes #when verified and finished #then delivery refuses: a different edition", async () => {
|
|
161
|
+
const session = await fullyRecorded()
|
|
162
|
+
session.write(entry.kind, { artifact_sha256: sha256Of("an earlier draft") })
|
|
163
|
+
await expectRefused(session, { code: "edition_unverified", path: session.recordPath[entry.kind], message: expect.stringContaining("is for a different edition") })
|
|
164
|
+
})
|
|
165
|
+
|
|
166
|
+
test("#given a fresh record with a blocking defect #when verified and finished #then delivery refuses", async () => {
|
|
167
|
+
const session = await fullyRecorded()
|
|
168
|
+
session.write(entry.kind, { defects: [{ code: BLOCKER, severity: "minor", message: BLOCKER }] })
|
|
169
|
+
await expectRefused(session, { code: "fidelity_unresolved", codes: [BLOCKER], path: session.recordPath[entry.kind] })
|
|
170
|
+
})
|
|
171
|
+
|
|
172
|
+
test("#given a fresh clean record #when verified and finished #then delivery passes", async () => {
|
|
173
|
+
const session = await fullyRecorded()
|
|
174
|
+
expect((await session.outcomes()).map((result) => result.exitCode)).toEqual([0, 0])
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
for (const [name, mutate] of [
|
|
178
|
+
["a missing envelope field", (record: Record<string, unknown>) => delete record.written_at],
|
|
179
|
+
["an unknown field", (record: Record<string, unknown>) => Object.assign(record, { verified_elsewhere: true })],
|
|
180
|
+
["another record kind", (record: Record<string, unknown>) => Object.assign(record, { record_kind: "not_a_record_kind" })],
|
|
181
|
+
] as const) {
|
|
182
|
+
test(`#given a record with ${name} #when verified and finished #then delivery refuses it as malformed`, async () => {
|
|
183
|
+
const session = await fullyRecorded()
|
|
184
|
+
const path = session.recordPath[entry.kind]
|
|
185
|
+
const record = JSON.parse(readFileSync(path, "utf8"))
|
|
186
|
+
mutate(record)
|
|
187
|
+
writeFileSync(path, JSON.stringify(record))
|
|
188
|
+
await expectRefused(session, { code: "edition_unverified", path, message: expect.stringContaining("is malformed") })
|
|
189
|
+
})
|
|
190
|
+
}
|
|
191
|
+
})
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
test("#given a record kind outside the table or a malformed envelope #when written #then the writer refuses", () => {
|
|
195
|
+
const dir = mkdtempSync(join(tmpdir(), "delivery-barrier-writer-"))
|
|
196
|
+
onTestFinished(() => rmSync(dir, { recursive: true, force: true }))
|
|
197
|
+
const fields = { artifact_sha256: sha256Of("x"), report: join(dir, "report.html"), defects: [] }
|
|
198
|
+
expect(() => writeFidelityRecord(join(dir, "a.json"), "not_a_record_kind", fields)).toThrow("unknown fidelity record kind")
|
|
199
|
+
expect(() => writeFidelityRecord(join(dir, "b.json"), "meaning", { ...fields, artifact_sha256: "draft" })).toThrow("malformed")
|
|
200
|
+
expect(() => writeFidelityRecord(join(dir, "c.json"), "meaning", { ...fields, gatesRun: [] })).toThrow("unknown field")
|
|
201
|
+
})
|
|
202
|
+
|
|
203
|
+
/** A delivered, fidelity-verified html edition in a session whose locked brief sets `meaning check`. */
|
|
204
|
+
async function meaningSession(meaning: "on" | "off", codes: string[] | null) {
|
|
205
|
+
const session = await fullyRecorded(LOCKED_BRIEF.replace("meaning check: on", `meaning check: ${meaning}`))
|
|
206
|
+
if (codes === null) unlinkSync(session.recordPath.meaning)
|
|
207
|
+
else session.write("meaning", { defects: codes.map((code) => ({ code, severity: "major", message: code })) })
|
|
208
|
+
return session
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
for (const code of ["meaning_drift_confirmed", "meaning_unverified"]) {
|
|
212
|
+
test(`#given a meaning record with ${code} #when verified and finished #then delivery refuses`, async () => {
|
|
213
|
+
const session = await meaningSession("on", [code])
|
|
214
|
+
await expectRefused(session, { code: "fidelity_unresolved", codes: [code], path: join(session.dir, MEANING_RECORD) })
|
|
215
|
+
})
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
test("#given the brief says meaning check on and the meaning step was skipped #when verified and finished #then delivery refuses: meaning gate not run", async () => {
|
|
219
|
+
const session = await meaningSession("on", null)
|
|
220
|
+
await expectRefused(session, { message: expect.stringContaining("meaning gate not run") })
|
|
221
|
+
})
|
|
222
|
+
|
|
223
|
+
test("#given a malformed ## Fidelity block that tries to turn meaning check off #when no meaning record exists #then delivery refuses: meaning gate not run", async () => {
|
|
224
|
+
const malformed = LOCKED_BRIEF.replace("meaning check: on (answered_by: default)", "meaning check: on (answered_by: default)\nmeaning check: off (answered_by: user)")
|
|
225
|
+
const session = await fullyRecorded(malformed)
|
|
226
|
+
unlinkSync(session.recordPath.meaning)
|
|
227
|
+
await expectRefused(session, { message: expect.stringContaining("meaning gate not run") })
|
|
228
|
+
})
|
|
229
|
+
|
|
230
|
+
test("#given the brief says meaning check off and no meaning record #when verified and finished #then delivery passes", async () => {
|
|
231
|
+
const session = await meaningSession("off", null)
|
|
232
|
+
expect((await session.outcomes()).map((result) => result.exitCode)).toEqual([0, 0])
|
|
233
|
+
})
|
|
234
|
+
|
|
235
|
+
test("#given a clean meaning record (warn only) under meaning check on #when verified #then delivery passes", async () => {
|
|
236
|
+
const { invoke } = await meaningSession("on", ["meaning_drift_warn"])
|
|
237
|
+
expect((await invoke(["verify"])).exitCode).toBe(0)
|
|
238
|
+
})
|
|
239
|
+
|
|
240
|
+
test("#given meaning check on, no fidelity evidence, and an explicit fidelity not_run #when verified #then the logged exemption covers the meaning step too", async () => {
|
|
241
|
+
const { dir, invoke } = await delivered()
|
|
242
|
+
writeFileSync(join(dir, "brief.md"), LOCKED_BRIEF)
|
|
243
|
+
await invoke(["gate", "fidelity", "not_run", "--reason", "No factual content in this edition"])
|
|
244
|
+
expect((await invoke(["verify"])).exitCode).toBe(0)
|
|
245
|
+
})
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
// fact-commands-meaning.mjs - `fact pairs` and `fact meaning-check`, the script half of the
|
|
2
|
+
// separate-session meaning verifier. fact pairs writes the (source, report sentence) pairs a
|
|
3
|
+
// reader lane gets and nothing else (with --changed-since, only the pairs whose sentence polish
|
|
4
|
+
// rewrote); it honours the brief's `meaning check` field and writes
|
|
5
|
+
// `{ skipped: true }` when it is off. fact meaning-check ingests the readers' verdict files
|
|
6
|
+
// (meaning-check.mjs), writes fidelity-meaning-defects.json (a fidelity record bound to the pairs'
|
|
7
|
+
// artifact_sha256 and report, through writeFidelityRecord), and records the run in
|
|
8
|
+
// fidelity-calibration.jsonl: a header with the promotion criterion on first write, then per run
|
|
9
|
+
// one run line plus one line per flagged pair. The run id hashes the inputs only, so a rerun or a
|
|
10
|
+
// --dispute rerun of the same inputs replaces that run's lines in place (disputes carry forward)
|
|
11
|
+
// instead of counting a second run. Neither command calls a model or the network.
|
|
12
|
+
|
|
13
|
+
import { existsSync, readFileSync } from "node:fs"
|
|
14
|
+
import { dirname, join, resolve } from "node:path"
|
|
15
|
+
|
|
16
|
+
import { CliError, parseArgs, readJsonFile, sha256Of, writeJsonAtomic, writeTextAtomic } from "./cli-support.mjs"
|
|
17
|
+
import { countDefects } from "./contracts.mjs"
|
|
18
|
+
import { ledgerFrom, markdownAsHtml, readEdition, sessionOf } from "./fact-commands-verify.mjs"
|
|
19
|
+
import { pairsChangedSince } from "./fact-commands-polish.mjs"
|
|
20
|
+
import { extractPairs, PAIR_KINDS } from "./fact-pairs.mjs"
|
|
21
|
+
import { FIDELITY_INCOMPLETE, meaningCheckEnabled, requireLockedBrief } from "./fidelity-brief.mjs"
|
|
22
|
+
import { MEANING_RECORD, writeFidelityRecord } from "./fidelity-records.mjs"
|
|
23
|
+
import { blocksDelivery } from "./gates-fidelity.mjs"
|
|
24
|
+
import { ingestVerdicts, unverifiedDefects, VERDICT_SCHEMA } from "./meaning-check.mjs"
|
|
25
|
+
|
|
26
|
+
export const PAIRS_FILE = "pairs.json"
|
|
27
|
+
export const CALIBRATION_FILE = "fidelity-calibration.jsonl"
|
|
28
|
+
/** Starting values, recorded in the calibration log header; changed only with evidence. */
|
|
29
|
+
export const PROMOTION = Object.freeze({ code: "meaning_drift_confirmed", to: "integrity", min_runs: 20, max_disputed_to_confirmed: 0.1, false_block_window: 20, max_report_false_blocks_in_window: 2 })
|
|
30
|
+
|
|
31
|
+
/** The command status: fail when any defect blocks delivery under the registry policy. */
|
|
32
|
+
export const meaningStatus = (defects) => (defects.some(blocksDelivery) ? "fail" : "pass")
|
|
33
|
+
|
|
34
|
+
const inSession = (session, name, flag) => {
|
|
35
|
+
if (session === null) throw new CliError(`${flag} is required without --session-dir (or SESSION_DIR)`)
|
|
36
|
+
return join(session, name)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function briefState(flags, session) {
|
|
40
|
+
const path = flags.brief ?? inSession(session, "brief.md", "--brief")
|
|
41
|
+
const lock = requireLockedBrief(path)
|
|
42
|
+
if (lock.exitCode === 2) throw new CliError(`${path}: malformed ## Fidelity block: ${lock.errors.join("; ")}`)
|
|
43
|
+
return { path, locked: lock.locked, off: !meaningCheckEnabled(lock.parsed) }
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function runPairs(args) {
|
|
47
|
+
const [report] = args.positionals
|
|
48
|
+
const { flags } = args
|
|
49
|
+
if (!report) throw new CliError("usage: fact pairs <report> [--format html|md] [--ledger F] [--claims claims.json] [--changed-since <fill-map.json>] [--brief brief.md] [--out pairs.json]")
|
|
50
|
+
const session = sessionOf(flags)
|
|
51
|
+
const out = resolve(flags.out ?? inSession(session, PAIRS_FILE, "--out"))
|
|
52
|
+
const brief = briefState(flags, session)
|
|
53
|
+
if (!brief.locked) {
|
|
54
|
+
const json = { command: "fact pairs", status: "fail", exitCode: 1, error: "fidelity_interview_incomplete", message: `${brief.path} has no locked ## Fidelity block` }
|
|
55
|
+
return { json, summary: `fact pairs: ${json.message}`, exitCode: 1 }
|
|
56
|
+
}
|
|
57
|
+
if (brief.off) {
|
|
58
|
+
const record = { schemaVersion: 1, skipped: true, reason: "meaning check: off", pairs: [] }
|
|
59
|
+
writeJsonAtomic(out, record)
|
|
60
|
+
return { json: { command: "fact pairs", status: "skipped", exitCode: 0, data: { out, ...record } }, summary: "fact pairs: skipped (meaning check: off)", exitCode: 0 }
|
|
61
|
+
}
|
|
62
|
+
const edition = readEdition(report, flags)
|
|
63
|
+
const claimsPath = flags.claims ?? (session === null ? null : join(session, "claims.json"))
|
|
64
|
+
const claims = claimsPath !== null && (flags.claims !== undefined || existsSync(claimsPath)) ? readJsonFile(claimsPath) : null
|
|
65
|
+
const ledger = ledgerFrom(flags, session)
|
|
66
|
+
const html = edition.format === "md" ? markdownAsHtml(edition.text, edition.entries, ledger).html : edition.text
|
|
67
|
+
const all = extractPairs(html, ledger, claims)
|
|
68
|
+
const pairs = flags["changed-since"] === undefined ? all : pairsChangedSince(all, flags["changed-since"], ledger, claims)
|
|
69
|
+
const counts = { total: pairs.length, force_strong: pairs.filter((pair) => pair.force_strong).length }
|
|
70
|
+
for (const kind of PAIR_KINDS) counts[kind] = pairs.filter((pair) => pair.kind === kind).length
|
|
71
|
+
const record = { schemaVersion: 1, skipped: false, report: resolve(report), format: edition.format, artifact_sha256: sha256Of(edition.text), pairs_sha256: sha256Of(JSON.stringify(pairs)), counts, verdict_schema: VERDICT_SCHEMA, pairs }
|
|
72
|
+
writeJsonAtomic(out, record)
|
|
73
|
+
const summary = `fact pairs: ${counts.total} pair(s), ${counts.force_strong} routed to the strong reader`
|
|
74
|
+
return { json: { command: "fact pairs", status: "pass", exitCode: 0, data: { out, ...record, pairs: undefined } }, summary, exitCode: 0 }
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** Every line of the log after the header, in file order; a corrupt line is a usage error naming path:line. */
|
|
78
|
+
function calibrationRows(path) {
|
|
79
|
+
if (!existsSync(path)) return []
|
|
80
|
+
const rows = []
|
|
81
|
+
for (const [index, line] of readFileSync(path, "utf8").split("\n").entries()) {
|
|
82
|
+
if (line.trim() === "") continue
|
|
83
|
+
try {
|
|
84
|
+
rows.push(JSON.parse(line))
|
|
85
|
+
} catch (error) {
|
|
86
|
+
throw new CliError(`${path}:${index + 1}: corrupt calibration log line (${error.message}); repair or move the file`)
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return rows.filter((row) => row.header !== true)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** The promotion criterion evaluated over the log: runs, disputed-to-confirmed, report-level false blocks. */
|
|
93
|
+
export function promotionEligibility(rows) {
|
|
94
|
+
const runs = [...new Set(rows.map((row) => row.run_id))]
|
|
95
|
+
const confirmed = rows.filter((row) => row.run !== true && row.confirmed)
|
|
96
|
+
const disputed = confirmed.filter((row) => row.disputed).length
|
|
97
|
+
const ratio = confirmed.length === 0 ? null : disputed / confirmed.length
|
|
98
|
+
const falseBlocks = runs.slice(-PROMOTION.false_block_window).filter((run) => {
|
|
99
|
+
const blocking = confirmed.filter((row) => row.run_id === run)
|
|
100
|
+
return blocking.length > 0 && blocking.every((row) => row.disputed)
|
|
101
|
+
}).length
|
|
102
|
+
const eligible = runs.length >= PROMOTION.min_runs && ratio !== null && ratio < PROMOTION.max_disputed_to_confirmed && falseBlocks <= PROMOTION.max_report_false_blocks_in_window
|
|
103
|
+
return { runs: runs.length, confirmed: confirmed.length, disputed_confirmed: disputed, disputed_to_confirmed: ratio, report_false_blocks_in_window: falseBlocks, eligible }
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Writes this run's lines: one run line (so a run with no flagged pair still counts) and one line per
|
|
108
|
+
* flagged pair. A run id already in the log is replaced in place, keeping its position in the false
|
|
109
|
+
* block window and any earlier dispute on a pair the new lines do not dispute.
|
|
110
|
+
*/
|
|
111
|
+
function recordCalibration(path, lines, runId, pairsSha) {
|
|
112
|
+
const before = calibrationRows(path)
|
|
113
|
+
const previous = before.filter((row) => row.run_id === runId)
|
|
114
|
+
const disputes = new Map(previous.filter((row) => row.disputed).map((row) => [row.pair_id, row.reason]))
|
|
115
|
+
const pairRows = lines.map((line) => {
|
|
116
|
+
const row = { ...JSON.parse(line), run_id: runId, pairs_sha256: pairsSha }
|
|
117
|
+
return !row.disputed && disputes.has(row.pair_id) ? { ...row, disputed: true, reason: disputes.get(row.pair_id) } : row
|
|
118
|
+
})
|
|
119
|
+
const rows = [{ run: true, run_id: runId, pairs_sha256: pairsSha, flagged: pairRows.length }, ...pairRows]
|
|
120
|
+
const at = previous.length === 0 ? before.length : before.indexOf(previous[0])
|
|
121
|
+
const after = [...before.slice(0, at).filter((row) => row.run_id !== runId), ...rows, ...before.slice(at).filter((row) => row.run_id !== runId)]
|
|
122
|
+
const header = { header: true, schemaVersion: 1, promotion: PROMOTION, note: "starting values; change them only with evidence" }
|
|
123
|
+
writeTextAtomic(path, `${[header, ...after].map((row) => JSON.stringify(row)).join("\n")}\n`)
|
|
124
|
+
return { written: pairRows.length, replaced: previous.length > 0, rows: after }
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/** A `{ skipped: true }` pairs file is accepted only when the locked brief turns the meaning check off. */
|
|
128
|
+
function skippedRefusal(flags, session) {
|
|
129
|
+
const brief = briefState(flags, session)
|
|
130
|
+
if (!brief.locked) return { error: FIDELITY_INCOMPLETE, message: `${brief.path} has no locked ## Fidelity block, so a skipped meaning check cannot be accepted` }
|
|
131
|
+
if (!brief.off) return { error: "meaning_check_required", message: `${flags.pairs} says skipped but ${brief.path} has meaning check on; rerun fact pairs` }
|
|
132
|
+
return null
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function runMeaningCheck(args) {
|
|
136
|
+
const { flags } = args
|
|
137
|
+
const usage = "usage: fact meaning-check --pairs pairs.json --verdicts cheap.json [--verdicts strong.json] [--dispute P<n> --reason TEXT] [--run-id ID] [--calibration F] [--out F]"
|
|
138
|
+
if (flags.pairs === undefined || !Array.isArray(flags.verdicts)) throw new CliError(usage)
|
|
139
|
+
const session = sessionOf(flags)
|
|
140
|
+
const pairsText = readFileSync(flags.pairs, "utf8")
|
|
141
|
+
const pairsDoc = readJsonFile(flags.pairs)
|
|
142
|
+
if (pairsDoc?.skipped === true) {
|
|
143
|
+
const refusal = skippedRefusal(flags, session)
|
|
144
|
+
if (refusal !== null) return { json: { command: "fact meaning-check", status: "fail", exitCode: 1, ...refusal }, summary: `fact meaning-check: ${refusal.message}`, exitCode: 1 }
|
|
145
|
+
return { json: { command: "fact meaning-check", status: "skipped", exitCode: 0, defects: [] }, summary: JSON.stringify({ command: "fact meaning-check", status: "skipped" }), exitCode: 0 }
|
|
146
|
+
}
|
|
147
|
+
if (typeof pairsDoc?.artifact_sha256 !== "string" || typeof pairsDoc.report !== "string") throw new CliError(`${flags.pairs} names no artifact_sha256 and report, so the meaning record cannot be bound to an edition; rerun fact pairs`)
|
|
148
|
+
for (const file of flags.verdicts) readJsonFile(file)
|
|
149
|
+
const verdictTexts = flags.verdicts.map((file) => readFileSync(file, "utf8"))
|
|
150
|
+
const dispute = flags.dispute === undefined ? undefined : { pair_id: flags.dispute, reason: args.required("reason") }
|
|
151
|
+
let result
|
|
152
|
+
try {
|
|
153
|
+
result = ingestVerdicts(pairsDoc, flags.verdicts, dispute ? { dispute } : {})
|
|
154
|
+
} catch (error) {
|
|
155
|
+
throw new CliError(`invalid verdict input: ${error.message}`)
|
|
156
|
+
}
|
|
157
|
+
const out = resolve(flags.out ?? inSession(session, MEANING_RECORD, "--out"))
|
|
158
|
+
const calibration = resolve(flags.calibration ?? join(session ?? dirname(out), CALIBRATION_FILE))
|
|
159
|
+
const runId = flags["run-id"] ?? sha256Of([pairsText, ...verdictTexts].join("\0")).slice(0, 16)
|
|
160
|
+
const pairsSha = pairsDoc.pairs_sha256 ?? sha256Of(pairsText)
|
|
161
|
+
const log = recordCalibration(calibration, result.calibrationLines, runId, pairsSha)
|
|
162
|
+
const defects = [...result.defects, ...unverifiedDefects(result.unverified)]
|
|
163
|
+
const counts = countDefects(defects)
|
|
164
|
+
const status = meaningStatus(defects)
|
|
165
|
+
const exitCode = status === "fail" ? 1 : 0
|
|
166
|
+
const record = writeFidelityRecord(out, "meaning", { command: "fact meaning-check", status, exitCode, artifact_sha256: pairsDoc.artifact_sha256, report: pairsDoc.report, run_id: runId, pairs_sha256: pairsSha, defects, counts, dropped: result.dropped, unverified: result.unverified, calibration_path: calibration, calibration_written: log.written, calibration_replaced: log.replaced, promotion_eligibility: promotionEligibility(log.rows) })
|
|
167
|
+
const confirmed = result.defects.filter((defect) => defect.code === "meaning_drift_confirmed").length
|
|
168
|
+
const summary = JSON.stringify({ command: "fact meaning-check", status, defects: defects.length, confirmed, warn: result.defects.length - confirmed, dropped: result.dropped.length, unverified: result.unverified.length })
|
|
169
|
+
return { json: { ...record, out }, summary, exitCode }
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/** @param {string[]} argv the arguments after `fact pairs` or `fact meaning-check` */
|
|
173
|
+
export function runFactMeaning(sub, argv) {
|
|
174
|
+
const args = parseArgs(argv, { repeatable: ["verdicts"] })
|
|
175
|
+
return sub === "pairs" ? runPairs(args) : runMeaningCheck(args)
|
|
176
|
+
}
|