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
|
@@ -6,6 +6,16 @@
|
|
|
6
6
|
|
|
7
7
|
export const SCHEMA_VERSION = 1
|
|
8
8
|
|
|
9
|
+
/** The severity vocabulary in rank order; every DEFECT_CODES entry uses one of these. */
|
|
10
|
+
export const SEVERITIES = Object.freeze(["blocker", "major", "minor"])
|
|
11
|
+
|
|
12
|
+
/** Defect counts per severity, then total. */
|
|
13
|
+
export function countDefects(defects) {
|
|
14
|
+
const counts = Object.fromEntries(SEVERITIES.map((severity) => [severity, 0]))
|
|
15
|
+
for (const defect of defects) counts[defect.severity] += 1
|
|
16
|
+
return { ...counts, total: defects.length }
|
|
17
|
+
}
|
|
18
|
+
|
|
9
19
|
function deepFreeze(value) {
|
|
10
20
|
if (value === null || typeof value !== "object") return value
|
|
11
21
|
for (const key of Object.keys(value)) deepFreeze(value[key])
|
|
@@ -48,7 +58,7 @@ export const DEFECT_CODES = deepFreeze({
|
|
|
48
58
|
pointer_label_mismatch: { severity: "blocker", integrity: true, hint: "The placeholder's label is not the row's label; check the id, then the label." },
|
|
49
59
|
quote_anchor_unresolved: { severity: "blocker", integrity: true, hint: "A from or to anchor is missing or occurs more than once in the quote; pick unique anchor words." },
|
|
50
60
|
unit_duplicated: { severity: "blocker", integrity: true, hint: "The unit after the placeholder repeats the row's unit; use :value or remove the unit." },
|
|
51
|
-
polish_diff_fail: { severity: "blocker", integrity: true, hint: "The polish pass changed numbers, tickers, or
|
|
61
|
+
polish_diff_fail: { severity: "blocker", integrity: true, hint: "The polish pass changed numbers, tickers, table rows, claim ids, markup, or hidden content; revert to the pre-polish artifact and apply the language fixes only." },
|
|
52
62
|
fidelity_interview_incomplete: { severity: "blocker", integrity: true, hint: "A fidelity field is pending or missing in brief.md; answer the open question or record its default, then rerun." },
|
|
53
63
|
derived_not_allowed: { severity: "blocker", integrity: true, hint: "The brief allows no derived or assumed numbers; cite a source row directly or change the brief with the requester." },
|
|
54
64
|
source_outside_floor: { severity: "blocker", integrity: true, hint: "The row's source is outside the source floor the brief records; use a source the brief allows or remove the fact." },
|
|
@@ -57,7 +67,8 @@ export const DEFECT_CODES = deepFreeze({
|
|
|
57
67
|
quote_unbound: { severity: "blocker", integrity: true, hint: "A direct quotation has no ledger quote row; register it with fact quote and insert it through its placeholder." },
|
|
58
68
|
edition_unverified: { severity: "blocker", integrity: true, hint: "The delivered file has no matching fact verify record; run fact verify on that edition and record it with --verified-by." },
|
|
59
69
|
untraceable_number: { severity: "major", integrity: false, hint: "Replace the typed number with a {{F<n>}} placeholder backed by a ledger row, or register it as assumed with a rationale." },
|
|
60
|
-
unsupported_verification: { severity: "major", integrity: false, hint: "
|
|
70
|
+
unsupported_verification: { severity: "major", integrity: false, hint: "A sentence marked verified (data-claim-kind=\"verified\") cites a row that is not recorded command output; cite the recorded output row or drop the verified mark." },
|
|
71
|
+
claim_kind_mismatch: { severity: "major", integrity: false, hint: "An element marked with a claim kind (data-claim-kind) cites a row that kind does not accept, or the kind is unknown; cite a row of that kind or fix the mark (references/fidelity-claim-kinds.md)." },
|
|
61
72
|
conflicting_readings: { severity: "major", integrity: false, hint: "Two ledger rows with different periods or qualifiers are cited as one fact; split the sentence or retire one row." },
|
|
62
73
|
hedge_dropped: { severity: "major", integrity: false, hint: "Restore the qualifier the source uses (estimated, about, not yet)." },
|
|
63
74
|
direction_flipped: { severity: "major", integrity: false, hint: "The source states the opposite direction; rewrite from the quote." },
|
|
@@ -65,7 +76,8 @@ export const DEFECT_CODES = deepFreeze({
|
|
|
65
76
|
qualifier_dropped: { severity: "major", integrity: false, hint: "The source bounds this value (up to, at least, about); restore the qualifier or use :q." },
|
|
66
77
|
unbound_sentence: { severity: "major", integrity: false, hint: "Bind the sentence to a fact, a quote, a citation, or a claim-graph node with data-claim." },
|
|
67
78
|
meaning_drift_confirmed: { severity: "major", integrity: false, hint: "Two independent readers found the same drift; rewrite from the quote or dispute with a reason." },
|
|
68
|
-
|
|
79
|
+
meaning_unverified: { severity: "major", integrity: false, hint: "No meaning reader judged this pair, or no strong reader judged a force_strong pair; run the missing reader on pairs.json and rerun fact meaning-check." },
|
|
80
|
+
polish_edit_rejected: { severity: "major", integrity: false, hint: "The edit changed a number, a hedge, a qualifier, a link, or a claim id, or contained <, > or &; re-propose the language fix only, narrowed to the words around any entity or tag." },
|
|
69
81
|
polish_hedging_decreased: { severity: "major", integrity: false, hint: "The polish pass removed qualifiers; restore them." },
|
|
70
82
|
polish_text_churn: { severity: "major", integrity: false, hint: "More than half the text changed during polish; the pass composed instead of proofreading." },
|
|
71
83
|
snapshot_mismatch: { severity: "major", integrity: false, hint: "The operands come from different collection windows; use rows from one window or state the windows separately." },
|
|
@@ -73,6 +85,7 @@ export const DEFECT_CODES = deepFreeze({
|
|
|
73
85
|
quote_stale_version: { severity: "major", integrity: false, hint: "The quoted source revision differs from the revision the report targets; re-snapshot the target revision or date the quote." },
|
|
74
86
|
quote_fuzzy_match: { severity: "minor", integrity: false, hint: "The source text is OCR-noisy; confirm the snapshot and prefer the raw document." },
|
|
75
87
|
name_outside_quote: { severity: "minor", integrity: false, hint: "A name in the sentence is not in the cited quote; add the source or remove the name." },
|
|
88
|
+
verification_claim_unmarked: { severity: "minor", integrity: false, hint: "The sentence reads as a check we ran but carries no verified mark; mark it {{V:F<n>:label}} on a recorded command-output row, or reword it." },
|
|
76
89
|
scope_widened_warn: { severity: "minor", integrity: false, hint: "The sentence uses a broader scope word than the quote; confirm the scope or narrow it." },
|
|
77
90
|
particle_unverified: { severity: "minor", integrity: false, hint: "The particle after this value could not be checked; read it aloud and fix it by hand if wrong." },
|
|
78
91
|
fallback_without_entity: { severity: "minor", integrity: false, hint: "The replacement quote has no entity for attribution; set one on the row." },
|
|
@@ -87,10 +100,10 @@ export const FIDELITY_CODES = Object.freeze([
|
|
|
87
100
|
"unresolved_placeholder", "value_mismatch", "unit_mismatch", "quote_not_verbatim", "claim_status_invalid",
|
|
88
101
|
"incompatible_operands", "pointer_label_mismatch", "quote_anchor_unresolved", "unit_duplicated", "polish_diff_fail",
|
|
89
102
|
"fidelity_interview_incomplete", "derived_not_allowed", "source_outside_floor", "snapshot_hash_mismatch",
|
|
90
|
-
"chart_value_untraceable", "quote_unbound", "edition_unverified", "untraceable_number", "unsupported_verification",
|
|
103
|
+
"chart_value_untraceable", "quote_unbound", "edition_unverified", "untraceable_number", "unsupported_verification", "claim_kind_mismatch",
|
|
91
104
|
"conflicting_readings", "hedge_dropped", "direction_flipped", "causality_added", "qualifier_dropped", "unbound_sentence",
|
|
92
|
-
"meaning_drift_confirmed", "polish_edit_rejected", "polish_hedging_decreased", "polish_text_churn", "snapshot_mismatch",
|
|
93
|
-
"join_integrity_fail", "quote_stale_version", "quote_fuzzy_match", "name_outside_quote", "scope_widened_warn",
|
|
105
|
+
"meaning_drift_confirmed", "meaning_unverified", "polish_edit_rejected", "polish_hedging_decreased", "polish_text_churn", "snapshot_mismatch",
|
|
106
|
+
"join_integrity_fail", "quote_stale_version", "quote_fuzzy_match", "name_outside_quote", "verification_claim_unmarked", "scope_widened_warn",
|
|
94
107
|
"particle_unverified", "fallback_without_entity", "meaning_drift_warn", "polish_edit_unlocated", "polish_link_changed",
|
|
95
108
|
"polish_name_changed",
|
|
96
109
|
])
|
|
@@ -184,6 +197,14 @@ export function validateOutcomeManifest(manifest) {
|
|
|
184
197
|
if (isObject(manifest.gates)) {
|
|
185
198
|
for (const gate of GATE_NAMES) check(errors, GATE_STATUSES.includes(manifest.gates[gate]), `gates.${gate} must be one of ${GATE_STATUSES.join(", ")}`)
|
|
186
199
|
} else errors.push("gates must be an object")
|
|
200
|
+
if (manifest.gateHistory !== undefined) {
|
|
201
|
+
if (!Array.isArray(manifest.gateHistory)) errors.push("gateHistory must be an array")
|
|
202
|
+
else for (const entry of manifest.gateHistory) {
|
|
203
|
+
check(errors, isObject(entry) && GATE_NAMES.includes(entry.gate) && GATE_STATUSES.includes(entry.status)
|
|
204
|
+
&& typeof entry.at === "string" && ISO_DATE.test(entry.at)
|
|
205
|
+
&& (entry.reason === undefined || typeof entry.reason === "string"), "gateHistory entries must have a known gate, status, timestamp, and optional reason")
|
|
206
|
+
}
|
|
207
|
+
}
|
|
187
208
|
if (Array.isArray(manifest.residualDefects)) manifest.residualDefects.forEach((defect, index) => validateDefect(defect, `residualDefects[${index}]`, errors))
|
|
188
209
|
else errors.push("residualDefects must be an array")
|
|
189
210
|
check(errors, manifest.repair === null || isObject(manifest.repair), "repair must be null or an object")
|
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
// fact-commands-polish.mjs - `fact polish-apply` and `fact polish-diff`, the script half of the
|
|
2
|
+
// proofread gate, plus the --changed-since filter of `fact pairs`.
|
|
3
|
+
//
|
|
4
|
+
// The writing lane returns polish-edits.json ([{before, after, reason}]) and never edits the
|
|
5
|
+
// report. polish-apply applies each edit through polish-apply.mjs and writes the result; a
|
|
6
|
+
// rejected (major) or unlocated (minor) edit leaves its region unchanged and is listed with its
|
|
7
|
+
// index and reason so the orchestrator can re-send it; every rejection reason is printed verbatim, one
|
|
8
|
+
// line per rejected edit (index and the first 60 characters of its before), in the summary and in
|
|
9
|
+
// data.rejected_reasons. It exits 0 whenever it wrote the output.
|
|
10
|
+
// polish-diff is the whole-document backstop (polish-diff.mjs) between the pre-polish artifact
|
|
11
|
+
// and any later copy; it exits 1 on a blocker. pairsChangedSince keeps only the pairs whose
|
|
12
|
+
// sentence text (normalized) is not in the pre-polish artifact that a fill map names, so the
|
|
13
|
+
// meaning readers re-check exactly what polish rewrote. No model, no network.
|
|
14
|
+
|
|
15
|
+
import { existsSync, readFileSync } from "node:fs"
|
|
16
|
+
import { join, resolve } from "node:path"
|
|
17
|
+
|
|
18
|
+
import { CliError, parseArgs, readJsonFile, sha256Of, writeJsonAtomic, writeTextAtomic } from "./cli-support.mjs"
|
|
19
|
+
import { countDefects, DEFECT_CODES } from "./contracts.mjs"
|
|
20
|
+
import { editionFormatOf, ledgerFrom, markdownAsHtml, sessionOf } from "./fact-commands-verify.mjs"
|
|
21
|
+
import { extractPairs } from "./fact-pairs.mjs"
|
|
22
|
+
import { applyEdits, POLISH_EDIT_FIELDS, validatePolishEdits } from "./polish-apply.mjs"
|
|
23
|
+
import { diffPolish } from "./polish-diff.mjs"
|
|
24
|
+
import { normalizeText } from "./text-normalize.mjs"
|
|
25
|
+
|
|
26
|
+
export { ledgerFrom, POLISH_EDIT_FIELDS, validatePolishEdits }
|
|
27
|
+
|
|
28
|
+
export const POLISH_RECORD = "polish-defects.json"
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* The polish-diff exit policy: only a registry blocker fails the command; majors are reported.
|
|
32
|
+
* Fail-closed like the delivery barrier: a code the registry does not know refuses outright,
|
|
33
|
+
* because a caller-claimed severity may not demote it.
|
|
34
|
+
*/
|
|
35
|
+
export const polishDiffFails = (defects) =>
|
|
36
|
+
defects.some((defect) => {
|
|
37
|
+
if (!Object.hasOwn(DEFECT_CODES, defect?.code)) throw new CliError(`unknown defect code ${defect?.code}`)
|
|
38
|
+
return DEFECT_CODES[defect.code].severity === "blocker"
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
function readText(path) {
|
|
42
|
+
try {
|
|
43
|
+
return readFileSync(path, "utf8")
|
|
44
|
+
} catch (error) {
|
|
45
|
+
throw new CliError(`cannot read ${path}: ${error.code ?? error.message}`)
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const withSeverity = (entry) => ({ ...entry, severity: DEFECT_CODES[entry.code].severity, hint: DEFECT_CODES[entry.code].hint })
|
|
50
|
+
|
|
51
|
+
/** The first 60 characters of an edit's before, on one line. */
|
|
52
|
+
const beforePreview = (before) => (typeof before === "string" ? before : "").replace(/\s+/gu, " ").slice(0, 60)
|
|
53
|
+
|
|
54
|
+
/** One line per rejected edit: its index, the start of its before, and its reason verbatim. */
|
|
55
|
+
const rejectionLine = (entry, edit) => ` rejected edit ${entry.index} ("${beforePreview(edit?.before)}"): ${entry.reason}`
|
|
56
|
+
|
|
57
|
+
function runPolishApply(args) {
|
|
58
|
+
const [report] = args.positionals
|
|
59
|
+
const { flags } = args
|
|
60
|
+
if (!report || flags.edits === undefined || flags.out === undefined) throw new CliError("usage: fact polish-apply <report.html> --edits polish-edits.json --out <report.polished.html> [--ledger F] [--session-dir D]")
|
|
61
|
+
if (editionFormatOf(report, flags.format) === "md") throw new CliError("fact polish-apply takes the filled HTML edition; polish a Markdown edition through its HTML source")
|
|
62
|
+
const html = readText(report)
|
|
63
|
+
const editsText = readText(flags.edits)
|
|
64
|
+
const edits = readJsonFile(flags.edits)
|
|
65
|
+
try {
|
|
66
|
+
validatePolishEdits(edits)
|
|
67
|
+
} catch (error) {
|
|
68
|
+
throw new CliError(`${flags.edits} must be an array of {${Object.keys(POLISH_EDIT_FIELDS).join(", ")}} objects: ${error.message}`)
|
|
69
|
+
}
|
|
70
|
+
const result = applyEdits(html, edits, ledgerFrom(flags))
|
|
71
|
+
writeTextAtomic(flags.out, result.out)
|
|
72
|
+
const defects = [...result.rejected, ...result.unlocated].sort((a, b) => a.index - b.index).map(withSeverity)
|
|
73
|
+
const counts = { applied: result.applied.length, rejected: result.rejected.length, unlocated: result.unlocated.length }
|
|
74
|
+
const data = { out: resolve(flags.out), ...counts, applied_indexes: result.applied, input_sha256: sha256Of(html), edits_sha256: sha256Of(editsText), out_sha256: sha256Of(result.out) }
|
|
75
|
+
const rejected_reasons = result.rejected.map((entry) => rejectionLine(entry, edits[entry.index]))
|
|
76
|
+
const summary = [`fact polish-apply: ${counts.applied} applied, ${counts.rejected} rejected, ${counts.unlocated} unlocated of ${edits.length} edit(s)`, ...rejected_reasons].join("\n")
|
|
77
|
+
return { json: { command: "fact polish-apply", status: "pass", exitCode: 0, defects, data: { ...data, rejected_reasons } }, summary, exitCode: 0 }
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function runPolishDiff(args) {
|
|
81
|
+
const { flags } = args
|
|
82
|
+
if (flags.before === undefined || flags.after === undefined) throw new CliError("usage: fact polish-diff --before <pre-polish.html> --after <polished.html> [--ledger F] [--out polish-defects.json] [--session-dir D]")
|
|
83
|
+
const after = readText(flags.after)
|
|
84
|
+
const defects = diffPolish(readText(flags.before), after, ledgerFrom(flags)).map((defect) => ({ ...defect, hint: DEFECT_CODES[defect.code]?.hint ?? null }))
|
|
85
|
+
const counts = countDefects(defects)
|
|
86
|
+
const status = polishDiffFails(defects) ? "fail" : "pass"
|
|
87
|
+
const exitCode = status === "fail" ? 1 : 0
|
|
88
|
+
const record = { command: "fact polish-diff", status, exitCode, before: resolve(flags.before), artifact: resolve(flags.after), artifact_sha256: sha256Of(after), defects, counts }
|
|
89
|
+
const session = sessionOf(flags)
|
|
90
|
+
const out = flags.out ?? (session === null ? null : join(session, POLISH_RECORD))
|
|
91
|
+
if (out !== null) writeJsonAtomic(resolve(out), record)
|
|
92
|
+
const summary = `fact polish-diff: ${status.toUpperCase()} - ${counts.blocker} blocker, ${counts.major} major, ${counts.minor} minor`
|
|
93
|
+
return { json: { ...record, out: out === null ? null : resolve(out) }, summary, exitCode }
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const pairKey = (pair) => [pair.kind, pair.sentence_key, pair.claim_id ?? "", normalizeText(pair.sentence)].join("\u0000")
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* The pairs whose sentence text changed since the pre-polish artifact a fill map names: a pair is
|
|
100
|
+
* kept unless the same (kind, sentence key, normalized sentence) occurs in that artifact. The map
|
|
101
|
+
* must carry the artifact's `artifact_sha256` and the bytes must still match it, so the filter is
|
|
102
|
+
* fail-closed on a hand-made or stale map.
|
|
103
|
+
* @param {object[]} pairs extractPairs output on the polished edition
|
|
104
|
+
* @param {string} mapPath the pre-polish fill map (fact fill's `<out>.fill-map.json`)
|
|
105
|
+
*/
|
|
106
|
+
export function pairsChangedSince(pairs, mapPath, ledger, claims) {
|
|
107
|
+
const map = readJsonFile(mapPath)
|
|
108
|
+
if (typeof map.artifact !== "string" || !existsSync(map.artifact)) throw new CliError(`${mapPath} names no readable pre-polish artifact`)
|
|
109
|
+
const text = readText(map.artifact)
|
|
110
|
+
if (typeof map.artifact_sha256 !== "string") throw new CliError(`${mapPath} fill map has no artifact_sha256; --changed-since needs the pre-polish hash`)
|
|
111
|
+
if (sha256Of(text) !== map.artifact_sha256) throw new CliError(`${map.artifact} changed after fill; --changed-since needs the pre-polish bytes`)
|
|
112
|
+
const html = map.format === "md" ? markdownAsHtml(text, map.entries, ledger).html : text
|
|
113
|
+
const before = new Map()
|
|
114
|
+
for (const pair of extractPairs(html, ledger, claims)) before.set(pairKey(pair), (before.get(pairKey(pair)) ?? 0) + 1)
|
|
115
|
+
return pairs.filter((pair) => {
|
|
116
|
+
const left = before.get(pairKey(pair)) ?? 0
|
|
117
|
+
if (left === 0) return true
|
|
118
|
+
before.set(pairKey(pair), left - 1)
|
|
119
|
+
return false
|
|
120
|
+
})
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** @param {string[]} argv the arguments after `fact polish-apply` or `fact polish-diff` */
|
|
124
|
+
export function runFactPolish(sub, argv) {
|
|
125
|
+
const args = parseArgs(argv)
|
|
126
|
+
return sub === "polish-apply" ? runPolishApply(args) : runPolishDiff(args)
|
|
127
|
+
}
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
// fact-commands-verify.mjs - `fact verify` and the Markdown projection it checks.
|
|
2
|
+
//
|
|
3
|
+
// fact verify runs the deterministic fidelity gate over one filled HTML or Markdown edition and
|
|
4
|
+
// writes a fidelity record (default $SESSION_DIR/fidelity-defects.json, through
|
|
5
|
+
// writeFidelityRecord) carrying the artifact's sha256. It
|
|
6
|
+
// exits 1 on any blocker or major defect (the orchestrator then records `outcome gate fidelity
|
|
7
|
+
// fail`); minors alone exit 0 and are listed. A Markdown edition is read through md-lite into an
|
|
8
|
+
// escaped HTML projection (same line numbers) and checked with its fill map, which is the only
|
|
9
|
+
// record of where its values sit; LaTeX is verified through its Markdown source.
|
|
10
|
+
//
|
|
11
|
+
// The delivery barrier that reads these records (and every other fidelity record) is
|
|
12
|
+
// fidelity-records.mjs.
|
|
13
|
+
|
|
14
|
+
import { existsSync, readFileSync } from "node:fs"
|
|
15
|
+
import { dirname, join, resolve } from "node:path"
|
|
16
|
+
|
|
17
|
+
import { CliError, readJsonFile, sha256Of } from "./cli-support.mjs"
|
|
18
|
+
import { countDefects } from "./contracts.mjs"
|
|
19
|
+
import { recompute } from "./fact-derive.mjs"
|
|
20
|
+
import { readLedger } from "./fact-ledger.mjs"
|
|
21
|
+
import { validateLedger } from "./fact-ledger-schema.mjs"
|
|
22
|
+
import { FIDELITY_RECORD, writeFidelityRecord } from "./fidelity-records.mjs"
|
|
23
|
+
import { blocksDelivery, checkFidelity } from "./gates-fidelity.mjs"
|
|
24
|
+
import { parseMarkdown } from "./md-lite.mjs"
|
|
25
|
+
import { escapeHtml, parseRenderedQuote, renderQuote, renderValueHtml } from "./placeholder.mjs"
|
|
26
|
+
import { sessionDirOf } from "./source-commands.mjs"
|
|
27
|
+
|
|
28
|
+
function readText(path) {
|
|
29
|
+
try {
|
|
30
|
+
return readFileSync(path, "utf8")
|
|
31
|
+
} catch (error) {
|
|
32
|
+
throw new CliError(`cannot read ${path}: ${error.code ?? error.message}`)
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* md-lite block kind -> its projection: the element tag (null = h<level>) and, for a run of one kind,
|
|
38
|
+
* the container that wraps the run. Code and table rows also take the specialized bodies below.
|
|
39
|
+
*/
|
|
40
|
+
export const MD_HTML_BLOCK_RULES = Object.freeze({
|
|
41
|
+
heading: Object.freeze({ tag: null }),
|
|
42
|
+
paragraph: Object.freeze({ tag: "p" }),
|
|
43
|
+
list_item: Object.freeze({ tag: "li", group: Object.freeze(["<ul>", "</ul>"]) }),
|
|
44
|
+
blockquote: Object.freeze({ tag: "blockquote" }),
|
|
45
|
+
code: Object.freeze({ tag: "code" }),
|
|
46
|
+
table_row: Object.freeze({ tag: "tr", group: Object.freeze(["<table>", "</table>"]) }),
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
/** Escaped text with inline links and code spans kept as elements, so a URL is never read as prose. */
|
|
50
|
+
export const projectMarkdownInline = (text) =>
|
|
51
|
+
escapeHtml(text)
|
|
52
|
+
.replace(/\[([^\]]+)\]\(([^)\s]+)\)/gu, '<a href="$2">$1</a>')
|
|
53
|
+
.replace(/`([^`]+)`/gu, "<code>$1</code>")
|
|
54
|
+
|
|
55
|
+
/** The element the HTML fill writes for one fill-map entry, or null when its bytes were edited since. */
|
|
56
|
+
function entryHtml(text, entry, rows) {
|
|
57
|
+
const body = text.slice(entry.start, entry.end)
|
|
58
|
+
if (body !== entry.rendered) return null
|
|
59
|
+
const row = rows.get(entry.fact_id)
|
|
60
|
+
if (entry.translates) return `<span data-translates="${escapeHtml(entry.translates)}">${projectMarkdownInline(body.replace(/^<!--[\s\S]*?-->\s*/u, ""))}</span>`
|
|
61
|
+
if (!row) return `<data data-fact="${escapeHtml(entry.fact_id)}">${escapeHtml(body)}</data>`
|
|
62
|
+
const quoted = row.kind === "quote" ? parseRenderedQuote(body) : null
|
|
63
|
+
if (quoted) return renderQuote(row, entry.selector ?? "full", quoted.text, "html")
|
|
64
|
+
return row.kind === "quote" ? null : renderValueHtml(row, body)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** [from, to) of the filled text as HTML, each intact fill-map entry rebuilt as its fill element. */
|
|
68
|
+
function spanHtml(text, from, to, entries, rows, stale) {
|
|
69
|
+
let html = ""
|
|
70
|
+
let cursor = from
|
|
71
|
+
for (const entry of entries) {
|
|
72
|
+
if (entry.start < cursor || entry.end > to || entry.start < from || entry.replaced !== undefined) continue
|
|
73
|
+
const element = entryHtml(text, entry, rows)
|
|
74
|
+
if (element === null) continue
|
|
75
|
+
stale.delete(entry)
|
|
76
|
+
html += projectMarkdownInline(text.slice(cursor, entry.start)) + element
|
|
77
|
+
cursor = entry.end
|
|
78
|
+
}
|
|
79
|
+
return html + projectMarkdownInline(text.slice(cursor, to))
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* An escaped HTML projection of a filled Markdown edition: one element per md-lite block, each on
|
|
84
|
+
* the source line it starts on, lists and table rows grouped. Never a rendering for readers.
|
|
85
|
+
* @param {string} text the filled Markdown
|
|
86
|
+
* @param {{ fact_id: string, start: number, end: number, rendered: string, replaced?: string, translates?: string }[]} entries its fill map
|
|
87
|
+
* @param {{ rows: object[] }} ledger
|
|
88
|
+
* @returns {{ html: string, stale: object[] }} stale: entries whose bytes changed after fill (checked by text)
|
|
89
|
+
*/
|
|
90
|
+
export function markdownAsHtml(text, entries, ledger) {
|
|
91
|
+
const sorted = [...(entries ?? [])].sort((a, b) => a.start - b.start)
|
|
92
|
+
const rows = new Map((ledger?.rows ?? []).map((row) => [row.fact_id, row]))
|
|
93
|
+
const stale = new Set(sorted.filter((entry) => entry.replaced === undefined))
|
|
94
|
+
const lineAt = (offset) => text.slice(0, offset).split("\n").length
|
|
95
|
+
const parsed = parseMarkdown(text)
|
|
96
|
+
if (parsed.problems.length > 0) {
|
|
97
|
+
throw new CliError(`the Markdown edition is malformed: ${parsed.problems.map((problem) => `${problem.code} at line ${problem.line}`).join(", ")}; fix it before verifying`)
|
|
98
|
+
}
|
|
99
|
+
let html = "<!doctype html><html><head><title>md</title></head><body>"
|
|
100
|
+
let line = 1
|
|
101
|
+
let open = null
|
|
102
|
+
for (const block of parsed.blocks) {
|
|
103
|
+
const rule = MD_HTML_BLOCK_RULES[block.kind]
|
|
104
|
+
if (rule === undefined) throw new CliError(`md-lite block kind ${block.kind} has no projection rule`)
|
|
105
|
+
if (open && open !== block.kind) html += MD_HTML_BLOCK_RULES[open].group[1]
|
|
106
|
+
const target = lineAt(block.start)
|
|
107
|
+
if (target > line) html += "\n".repeat(target - line)
|
|
108
|
+
line = Math.max(line, target) + (text.slice(block.start, block.end).match(/\n/gu)?.length ?? 0)
|
|
109
|
+
if (rule.group && open !== block.kind) html += rule.group[0]
|
|
110
|
+
open = rule.group ? block.kind : null
|
|
111
|
+
const inner = (from, to) => spanHtml(text, from, to, sorted, rows, stale)
|
|
112
|
+
if (block.kind === "table_row") html += `<${rule.tag}>${block.cells.map((cell) => `<td>${inner(cell.start, cell.end)}</td>`).join("")}</${rule.tag}>`
|
|
113
|
+
else if (block.kind === "code") html += `<pre><${rule.tag}>${escapeHtml(block.text)}</${rule.tag}></pre>`
|
|
114
|
+
else {
|
|
115
|
+
const tag = rule.tag ?? `h${block.level}`
|
|
116
|
+
const replaced = block.kind === "blockquote" ? sorted.find((entry) => entry.replaced !== undefined && entry.start <= block.start && entry.end >= block.end) : undefined
|
|
117
|
+
const attrs = replaced ? ` data-fact="${escapeHtml(replaced.fact_id)}" data-replaced="${escapeHtml(replaced.replaced)}"` : ""
|
|
118
|
+
html += `<${tag}${attrs}>${inner(block.start, block.end)}</${tag}>`
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return { html: `${html}${open ? MD_HTML_BLOCK_RULES[open].group[1] : ""}</body></html>\n`, stale: [...stale] }
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** The session dir from --session-dir or SESSION_DIR, or null; a bare flag names no session. */
|
|
125
|
+
export const sessionOf = (flags) => {
|
|
126
|
+
const dir = flags["session-dir"] ?? process.env.SESSION_DIR
|
|
127
|
+
return dir && dir !== true ? resolve(dir) : null
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** The ledger from --ledger (validated) or from the session dir; every fact command loads it here. */
|
|
131
|
+
export function ledgerFrom(flags, session = sessionOf(flags)) {
|
|
132
|
+
if (flags.ledger === undefined) {
|
|
133
|
+
if (session === null) throw new CliError("--ledger is required without --session-dir (or SESSION_DIR)")
|
|
134
|
+
return readLedger(session)
|
|
135
|
+
}
|
|
136
|
+
const ledger = readJsonFile(flags.ledger)
|
|
137
|
+
const errors = validateLedger(ledger)
|
|
138
|
+
if (errors.length > 0) throw new CliError(`${flags.ledger} is not a valid fact ledger: ${errors[0]}`)
|
|
139
|
+
return ledger
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const MARKDOWN_EXTENSION = /\.(md|markdown)$/iu
|
|
143
|
+
/** The edition format: the explicit flag, else md for a .md or .markdown path (any case), else html. */
|
|
144
|
+
export const editionFormatOf = (path, flag) => flag ?? (MARKDOWN_EXTENSION.test(path) ? "md" : "html")
|
|
145
|
+
|
|
146
|
+
/** The report's format (from --format or its extension) and, for Markdown, its fill map entries. */
|
|
147
|
+
export function readEdition(report, flags) {
|
|
148
|
+
const format = editionFormatOf(report, flags.format)
|
|
149
|
+
if (format !== "html" && format !== "md") throw new CliError(`--format ${format}: verify html or md (a LaTeX edition is verified through its Markdown source)`)
|
|
150
|
+
const text = readText(report)
|
|
151
|
+
if (format === "html") return { format, text, entries: flags["fill-map"] === undefined ? null : readJsonFile(flags["fill-map"]).entries }
|
|
152
|
+
const map = flags["fill-map"] ?? `${report}.fill-map.json`
|
|
153
|
+
if (!existsSync(map)) throw new CliError(`a Markdown edition needs its fill map: ${map} is missing (pass --fill-map)`)
|
|
154
|
+
return { format, text, entries: readJsonFile(map).entries }
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/** @param {{ positionals: string[], flags: Record<string, any> }} args parsed by runFact */
|
|
158
|
+
export function runFactVerify(args) {
|
|
159
|
+
const [report] = args.positionals
|
|
160
|
+
const { flags } = args
|
|
161
|
+
if (!report) throw new CliError("usage: fact verify <report> [--format html|md] [--sources D] [--claims claims.json] [--fill-map F] [--brief brief.md] [--out fidelity-defects.json]")
|
|
162
|
+
const edition = readEdition(report, flags)
|
|
163
|
+
const sessionDir = sessionDirOf(flags)
|
|
164
|
+
const claimsPath = flags.claims ?? join(sessionDir, "claims.json")
|
|
165
|
+
const briefPath = flags.brief ?? join(sessionDir, "brief.md")
|
|
166
|
+
const ledger = readLedger(sessionDir)
|
|
167
|
+
const projected = edition.format === "md" ? markdownAsHtml(edition.text, edition.entries, ledger) : null
|
|
168
|
+
const result = checkFidelity(projected?.html ?? edition.text, {
|
|
169
|
+
ledger,
|
|
170
|
+
sourcesDir: resolve(flags.sources ?? join(sessionDir, "sources")),
|
|
171
|
+
claims: flags.claims !== undefined || existsSync(claimsPath) ? readJsonFile(claimsPath) : null,
|
|
172
|
+
fillMap: projected?.stale ?? edition.entries,
|
|
173
|
+
brief: existsSync(briefPath) ? readText(briefPath) : "",
|
|
174
|
+
baseDir: dirname(resolve(report)),
|
|
175
|
+
recompute,
|
|
176
|
+
})
|
|
177
|
+
const counts = countDefects(result.defects)
|
|
178
|
+
const status = result.defects.some(blocksDelivery) ? "fail" : "pass"
|
|
179
|
+
const exitCode = status === "fail" ? 1 : 0
|
|
180
|
+
const out = resolve(flags.out ?? join(sessionDir, FIDELITY_RECORD))
|
|
181
|
+
const record = writeFidelityRecord(out, "fidelity", { command: "fact verify", status, exitCode, format: edition.format, report: resolve(report), artifact_sha256: sha256Of(edition.text), defects: result.defects, counts, gatesRun: result.gatesRun })
|
|
182
|
+
return { json: { ...record, out }, summary: `fact verify: ${status.toUpperCase()} - ${counts.blocker} blocker, ${counts.major} major, ${counts.minor} minor`, exitCode }
|
|
183
|
+
}
|