oh-my-opencode 5.1.47 → 5.1.51
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agents/refusal-rule.d.ts +7 -0
- package/dist/cli/index.js +1194 -752
- package/dist/cli/service-auth/accounts-gate.d.ts +11 -0
- package/dist/cli/service-auth/commands.d.ts +2 -4
- package/dist/cli/service-auth/index.d.ts +1 -1
- package/dist/cli/service-auth/keystore.d.ts +26 -2
- package/dist/cli/service-auth/protocol.d.ts +7 -1
- package/dist/cli/service-auth/session.d.ts +6 -0
- package/dist/cli/service-auth/signed-out-marker.d.ts +11 -0
- package/dist/cli-node/index.js +1194 -752
- package/dist/config/schema/hooks.d.ts +1 -0
- package/dist/create-hooks.d.ts +1 -0
- package/dist/features/boulder-state/storage.d.ts +1 -1
- package/dist/features/boulder-state/types.d.ts +1 -1
- package/dist/hooks/atlas/idle-continuation.d.ts +2 -1
- package/dist/hooks/atlas/types.d.ts +9 -1
- package/dist/hooks/index.d.ts +1 -0
- package/dist/hooks/momus-review-persistence/hook.d.ts +19 -0
- package/dist/hooks/momus-review-persistence/index.d.ts +1 -0
- package/dist/hooks/momus-review-persistence/review-resolution.d.ts +20 -0
- package/dist/hooks/prometheus-md-only/constants.d.ts +2 -0
- package/dist/hooks/prometheus-md-only/fixtures/denial-log.d.ts +1 -0
- package/dist/hooks/prometheus-md-only/fixtures/guarded-hashline.d.ts +1 -0
- package/dist/hooks/prometheus-md-only/fixtures/invoke-before.d.ts +7 -0
- package/dist/hooks/prometheus-md-only/fixtures/opencode-patch-parser.d.ts +2 -0
- package/dist/hooks/prometheus-md-only/hook.d.ts +5 -1
- package/dist/hooks/prometheus-md-only/path-policy.d.ts +2 -9
- package/dist/hooks/prometheus-md-only/vendor/opencode-patch-parser.d.ts +22 -0
- package/dist/index.js +2322 -1045
- package/dist/plugin/hooks/create-core-hooks.d.ts +1 -0
- package/dist/plugin/hooks/create-tool-guard-hooks.d.ts +2 -1
- package/dist/shared/posthog-activity-state.d.ts +1 -0
- package/dist/shared/posthog.d.ts +1 -2
- package/dist/shared/resolve-file-write-path.d.ts +6 -0
- package/dist/skills/ast-grep/README.md +1 -1
- package/dist/skills/ast-grep/install.ps1 +5 -3
- package/dist/skills/ast-grep/install.sh +4 -5
- package/dist/skills/ast-grep/references/install.md +2 -2
- package/dist/skills/ast-grep/scripts/ast_grep_helper.py +13 -3
- package/dist/skills/browser/runtime/omowright/manifest.json +1 -1
- package/dist/skills/coding-agent-sessions/AGENTS.md +5 -4
- package/dist/skills/coding-agent-sessions/SKILL.md +11 -11
- package/dist/skills/coding-agent-sessions/references/all-platforms.md +2 -2
- package/dist/skills/coding-agent-sessions/references/codex.md +4 -4
- package/dist/skills/coding-agent-sessions/scripts/agent_sessions/cli.py +1 -1
- package/dist/skills/coding-agent-sessions/scripts/find-agent-sessions.py +6 -3
- package/dist/skills/frontend/ATTRIBUTION.md +1 -1
- package/dist/skills/frontend/SKILL.md +34 -12
- package/dist/skills/frontend/references/design/README.md +2 -2
- package/dist/skills/frontend/references/design/_INDEX.md +2 -1
- package/dist/skills/frontend/references/design/component-catalogs.md +14 -10
- package/dist/skills/frontend/references/design/design-system-architecture.md +19 -6
- package/dist/skills/frontend/references/design/grounding.md +29 -0
- package/dist/skills/frontend/references/ui-ux-db/scripts/design_system.py +2 -0
- package/dist/skills/frontend/references/ui-ux-db/scripts/search.py +2 -0
- package/dist/skills/ultimate-browsing/SKILL.md +5 -4
- package/dist/skills/ultimate-browsing/engine/AGENTS.md +2 -2
- package/dist/skills/ultimate-browsing/engine/__main__.py +9 -6
- package/dist/skills/ultimate-browsing/engine/summary.py +1 -1
- package/dist/skills/ultimate-browsing/references/insane-search/README.md +4 -4
- package/dist/skills/ultimate-browsing/scripts/extract_cookies.py +4 -2
- package/dist/skills/ulw-research/AGENTS.md +8 -4
- package/dist/skills/ulw-research/SKILL.md +7 -259
- package/dist/skills/ulw-research/references/deliverable-phase.md +68 -24
- package/dist/skills/ulw-research/references/fact-ledger-grammar.md +7 -3
- package/dist/skills/ulw-research/references/fact-ledger.md +42 -8
- package/dist/skills/ulw-research/references/fidelity-claim-kinds.md +1 -1
- package/dist/skills/ulw-research/references/markdown-blocks.md +12 -0
- package/dist/skills/ulw-research/references/report-gates.md +14 -6
- package/dist/skills/ulw-research/references/research-collection.md +195 -0
- package/dist/skills/ulw-research/references/research-delivery.md +123 -0
- package/dist/skills/ulw-research/scripts/claim-kinds.mjs +33 -5
- package/dist/skills/ulw-research/scripts/claim-kinds.test.ts +37 -2
- package/dist/skills/ulw-research/scripts/claim-prefix.test.ts +76 -0
- package/dist/skills/ulw-research/scripts/contracts-validation.test.ts +83 -0
- package/dist/skills/ulw-research/scripts/contracts.mjs +16 -15
- package/dist/skills/ulw-research/scripts/contracts.test.ts +6 -109
- package/dist/skills/ulw-research/scripts/conversion-altered.test.ts +154 -0
- package/dist/skills/ulw-research/scripts/conversion-originals.test.ts +297 -0
- package/dist/skills/ulw-research/scripts/conversion-probe-classes.test.ts +361 -0
- package/dist/skills/ulw-research/scripts/conversion-probe.mjs +229 -0
- package/dist/skills/ulw-research/scripts/conversion-probe.test.ts +218 -0
- package/dist/skills/ulw-research/scripts/copy-returns-fidelity-policy.test.ts +156 -0
- package/dist/skills/ulw-research/scripts/copy-returns-polish.test.ts +10 -9
- package/dist/skills/ulw-research/scripts/css-hiding.mjs +158 -0
- package/dist/skills/ulw-research/scripts/css-lite.mjs +4 -2
- package/dist/skills/ulw-research/scripts/delivery-barrier-meaning.test.ts +186 -0
- package/dist/skills/ulw-research/scripts/delivery-barrier.test.ts +40 -87
- package/dist/skills/ulw-research/scripts/entities.mjs +13 -4
- package/dist/skills/ulw-research/scripts/fact-commands-fill.mjs +96 -0
- package/dist/skills/ulw-research/scripts/fact-commands-meaning.mjs +26 -30
- package/dist/skills/ulw-research/scripts/fact-commands-polish.mjs +46 -19
- package/dist/skills/ulw-research/scripts/fact-commands-verify.mjs +37 -6
- package/dist/skills/ulw-research/scripts/fact-commands.mjs +40 -62
- package/dist/skills/ulw-research/scripts/fact-derive-2.test.ts +149 -0
- package/dist/skills/ulw-research/scripts/fact-derive.mjs +7 -2
- package/dist/skills/ulw-research/scripts/fact-derive.test.ts +7 -192
- package/dist/skills/ulw-research/scripts/fact-fill.mjs +9 -6
- package/dist/skills/ulw-research/scripts/fact-ledger-kinds.mjs +6 -7
- package/dist/skills/ulw-research/scripts/fact-ledger-kinds.test.ts +2 -1
- package/dist/skills/ulw-research/scripts/fact-ledger-legacy.test.ts +2 -1
- package/dist/skills/ulw-research/scripts/fact-locate.mjs +15 -9
- package/dist/skills/ulw-research/scripts/fact-unit-dimensions-2.test.ts +149 -0
- package/dist/skills/ulw-research/scripts/fact-unit-dimensions.test.ts +4 -168
- package/dist/skills/ulw-research/scripts/fact-unit-rules.test.ts +3 -229
- package/dist/skills/ulw-research/scripts/fidelity-claims.mjs +4 -3
- package/dist/skills/ulw-research/scripts/fidelity-csv.mjs +32 -0
- package/dist/skills/ulw-research/scripts/fidelity-lexicon.mjs +1 -1
- package/dist/skills/ulw-research/scripts/fidelity-originals.mjs +161 -0
- package/dist/skills/ulw-research/scripts/fidelity-policy.mjs +183 -0
- package/dist/skills/ulw-research/scripts/fidelity-records.mjs +81 -14
- package/dist/skills/ulw-research/scripts/fidelity-snapshot-readability.mjs +49 -0
- package/dist/skills/ulw-research/scripts/fidelity-sources.mjs +76 -0
- package/dist/skills/ulw-research/scripts/fidelity-trace.mjs +25 -11
- package/dist/skills/ulw-research/scripts/fidelity-value-unit.mjs +202 -0
- package/dist/skills/ulw-research/scripts/fidelity-values.mjs +40 -39
- package/dist/skills/ulw-research/scripts/fidelity-values.test.ts +8 -29
- package/dist/skills/ulw-research/scripts/gates-fidelity.mjs +8 -6
- package/dist/skills/ulw-research/scripts/gates-static-headings.test.ts +17 -0
- package/dist/skills/ulw-research/scripts/gates-static.mjs +10 -1
- package/dist/skills/ulw-research/scripts/html-lite.mjs +6 -2
- package/dist/skills/ulw-research/scripts/html-markup.mjs +2 -2
- package/dist/skills/ulw-research/scripts/html-text-map.mjs +140 -0
- package/dist/skills/ulw-research/scripts/html-text-map.test.ts +148 -0
- package/dist/skills/ulw-research/scripts/markdown-closers.mjs +126 -0
- package/dist/skills/ulw-research/scripts/md-lite-boundaries.test.ts +46 -0
- package/dist/skills/ulw-research/scripts/md-lite.mjs +1 -1
- package/dist/skills/ulw-research/scripts/number-grammar.mjs +33 -0
- package/dist/skills/ulw-research/scripts/number-grammar.test.ts +36 -0
- package/dist/skills/ulw-research/scripts/numerals.mjs +18 -7
- package/dist/skills/ulw-research/scripts/numerals.test.ts +3 -0
- package/dist/skills/ulw-research/scripts/outcome-start.mjs +66 -0
- package/dist/skills/ulw-research/scripts/outcome-start.test.ts +109 -0
- package/dist/skills/ulw-research/scripts/outcome.mjs +11 -22
- package/dist/skills/ulw-research/scripts/page-source-locator.test.ts +187 -0
- package/dist/skills/ulw-research/scripts/placeholder.mjs +18 -6
- package/dist/skills/ulw-research/scripts/polish-apply.mjs +3 -2
- package/dist/skills/ulw-research/scripts/polish-markup.test.ts +15 -1
- package/dist/skills/ulw-research/scripts/polish-partial.test.ts +42 -0
- package/dist/skills/ulw-research/scripts/raw-decode.mjs +110 -0
- package/dist/skills/ulw-research/scripts/raw-locator.mjs +47 -0
- package/dist/skills/ulw-research/scripts/raw-snapshot.test.ts +139 -0
- package/dist/skills/ulw-research/scripts/report-tools-2.test.ts +233 -0
- package/dist/skills/ulw-research/scripts/report-tools-3.test.ts +141 -0
- package/dist/skills/ulw-research/scripts/report-tools-4.test.ts +135 -0
- package/dist/skills/ulw-research/scripts/report-tools-5.test.ts +231 -0
- package/dist/skills/ulw-research/scripts/report-tools-commands.mjs +1 -1
- package/dist/skills/ulw-research/scripts/report-tools.mjs +7 -1
- package/dist/skills/ulw-research/scripts/report-tools.test.ts +6 -560
- package/dist/skills/ulw-research/scripts/snapshot-readability.test.ts +116 -0
- package/dist/skills/ulw-research/scripts/source-commands.mjs +87 -29
- package/dist/skills/ulw-research/scripts/source-integrity.test.ts +243 -0
- package/dist/skills/ulw-research/scripts/source-refresh.mjs +82 -0
- package/dist/skills/ulw-research/scripts/source-snapshot.mjs +73 -69
- package/dist/skills/ulw-research/scripts/source-snapshot.test.ts +7 -4
- package/dist/skills/ulw-research/scripts/source-view.mjs +89 -0
- package/dist/skills/ulw-research/scripts/sources-citation.mjs +203 -0
- package/dist/skills/ulw-research/scripts/sources-citation.test.ts +236 -0
- package/dist/skills/ulw-research/scripts/tests/contracts-support.ts +147 -0
- package/dist/skills/ulw-research/scripts/tests/delivery-barrier-session.ts +59 -0
- package/dist/skills/ulw-research/scripts/tests/derive-clock.ts +4 -0
- package/dist/skills/ulw-research/scripts/tests/fact-derive-support.ts +65 -0
- package/dist/skills/ulw-research/scripts/tests/fact-unit-dimensions-support.ts +41 -0
- package/dist/skills/ulw-research/scripts/tests/fact-unit-rules-support.ts +241 -0
- package/dist/skills/ulw-research/scripts/tests/fidelity-values-support.ts +30 -0
- package/dist/skills/ulw-research/scripts/tests/fixtures/conversion-probe.html +14 -0
- package/dist/skills/ulw-research/scripts/tests/fixtures/sources-shapes.html +20 -0
- package/dist/skills/ulw-research/scripts/tests/numeral-classes.ts +196 -0
- package/dist/skills/ulw-research/scripts/tests/report-tools-support.ts +182 -0
- package/dist/skills/ulw-research/scripts/tests/value-mode-support.ts +48 -0
- package/dist/skills/ulw-research/scripts/units.mjs +14 -3
- package/dist/skills/ulw-research/scripts/units.test.ts +10 -0
- package/dist/skills/ulw-research/scripts/value-mode-unit-classes.test.ts +255 -0
- package/dist/skills/ulw-research/scripts/value-mode-unit.test.ts +179 -0
- package/dist/skills/ulw-research/scripts/value-rules-cli.test.ts +157 -0
- package/dist/skills/ulw-research/scripts/value-rules-followups.test.ts +72 -0
- package/dist/skills/ulw-research/scripts/value-rules-priority.test.ts +80 -0
- package/dist/tools/delegate-task/executor-types.d.ts +1 -0
- package/dist/tools/delegate-task/sync-session-existence.d.ts +4 -0
- package/dist/tools/delegate-task/types.d.ts +1 -0
- package/dist/tools/skill/description-formatter.d.ts +2 -1
- package/dist/tools/skill/types.d.ts +6 -0
- package/dist/tui.js +131 -33
- package/package.json +18 -15
- package/packages/git-bash-mcp/package.json +1 -1
- package/packages/omo-codex/plugin/.codex-plugin/plugin.json +1 -1
- package/packages/omo-codex/plugin/README.md +1 -1
- package/packages/omo-codex/plugin/components/bootstrap/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/bootstrap/package.json +1 -1
- package/packages/omo-codex/plugin/components/comment-checker/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/comment-checker/package.json +1 -1
- package/packages/omo-codex/plugin/components/git-bash/hooks/hooks.json +2 -2
- package/packages/omo-codex/plugin/components/git-bash/package.json +1 -1
- package/packages/omo-codex/plugin/components/lazycodex-executor-verify/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/lazycodex-executor-verify/package.json +1 -1
- package/packages/omo-codex/plugin/components/lcx/skills/lcx-contribute-bug-fix/SKILL.md +51 -55
- package/packages/omo-codex/plugin/components/lcx/skills/lcx-contribute-bug-fix/agents/openai.yaml +1 -1
- package/packages/omo-codex/plugin/components/lcx/skills/lcx-report-bug/SKILL.md +7 -7
- package/packages/omo-codex/plugin/components/lcx/skills/lcx-report-bug/agents/openai.yaml +1 -1
- package/packages/omo-codex/plugin/components/lsp/dist/.omo-runtime-manifest.json +2 -2
- package/packages/omo-codex/plugin/components/lsp/hooks/hooks.json +2 -2
- package/packages/omo-codex/plugin/components/lsp/package.json +1 -1
- package/packages/omo-codex/plugin/components/rules/hooks/hooks.json +4 -4
- package/packages/omo-codex/plugin/components/rules/package.json +1 -1
- package/packages/omo-codex/plugin/components/teammode/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/teammode/package.json +2 -2
- package/packages/omo-codex/plugin/components/telemetry/README.md +2 -2
- package/packages/omo-codex/plugin/components/telemetry/dist/cli.js +52 -15
- package/packages/omo-codex/plugin/components/telemetry/dist/posthog.js +52 -19
- package/packages/omo-codex/plugin/components/telemetry/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/telemetry/package.json +1 -1
- package/packages/omo-codex/plugin/components/telemetry/src/codex-hook.ts +2 -9
- package/packages/omo-codex/plugin/components/telemetry/src/posthog.ts +4 -13
- package/packages/omo-codex/plugin/components/telemetry/src/product-identity.ts +0 -2
- package/packages/omo-codex/plugin/components/telemetry/test/codex-hook-diagnostics.test.ts +0 -3
- package/packages/omo-codex/plugin/components/telemetry/test/codex-hook.test.ts +3 -8
- package/packages/omo-codex/plugin/components/ultrawork/dist/cli.js +3 -0
- package/packages/omo-codex/plugin/components/ultrawork/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/ultrawork/package.json +1 -1
- package/packages/omo-codex/plugin/components/ultrawork/src/directive-content.ts +1 -1
- package/packages/omo-codex/plugin/components/ulw-execute-continuation/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/ulw-execute-continuation/package.json +1 -1
- package/packages/omo-codex/plugin/components/ulw-loop/directive.md +3 -0
- package/packages/omo-codex/plugin/components/ulw-loop/hooks/hooks.json +5 -5
- package/packages/omo-codex/plugin/components/ulw-loop/package.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-compact-resetting-git-bash-mcp-reminder.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-compact-resetting-lsp-diagnostics-cache.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-compact-resetting-project-rule-cache.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-checking-comments.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-checking-lsp-diagnostics.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-checking-thread-title-hygiene.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-matching-project-rules.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-recording-spawn-admission.json +1 -1
- package/packages/omo-codex/plugin/hooks/pre-tool-use-enforcing-unlimited-goal-budget.json +1 -1
- package/packages/omo-codex/plugin/hooks/pre-tool-use-guarding-ulw-loop-spawns.json +1 -1
- package/packages/omo-codex/plugin/hooks/pre-tool-use-recommending-git-bash-mcp.json +1 -1
- package/packages/omo-codex/plugin/hooks/session-start-checking-auto-update.json +1 -1
- package/packages/omo-codex/plugin/hooks/session-start-checking-bootstrap-provisioning.json +1 -1
- package/packages/omo-codex/plugin/hooks/session-start-loading-project-rules.json +1 -1
- package/packages/omo-codex/plugin/hooks/session-start-recording-session-telemetry.json +1 -1
- package/packages/omo-codex/plugin/hooks/stop-checking-ulw-execute-continuation.json +1 -1
- package/packages/omo-codex/plugin/hooks/stop-checking-ulw-loop-resume.json +1 -1
- package/packages/omo-codex/plugin/hooks/subagent-stop-verifying-lazycodex-executor-evidence.json +1 -1
- package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ultrawork-trigger.json +1 -1
- package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ulw-loop-steering.json +1 -1
- package/packages/omo-codex/plugin/hooks/user-prompt-submit-loading-project-rules.json +1 -1
- package/packages/omo-codex/plugin/package-lock.json +16 -16
- package/packages/omo-codex/plugin/package.json +1 -1
- package/packages/omo-codex/plugin/shared/package.json +1 -1
- package/packages/omo-codex/plugin/skills/ast-grep/README.md +1 -1
- package/packages/omo-codex/plugin/skills/ast-grep/install.ps1 +5 -3
- package/packages/omo-codex/plugin/skills/ast-grep/install.sh +4 -5
- package/packages/omo-codex/plugin/skills/ast-grep/references/install.md +2 -2
- package/packages/omo-codex/plugin/skills/ast-grep/scripts/ast_grep_helper.py +13 -3
- package/packages/omo-codex/plugin/skills/browser/runtime/omowright/manifest.json +1 -1
- package/packages/omo-codex/plugin/skills/coding-agent-sessions/AGENTS.md +5 -4
- package/packages/omo-codex/plugin/skills/coding-agent-sessions/SKILL.md +11 -11
- package/packages/omo-codex/plugin/skills/coding-agent-sessions/references/all-platforms.md +2 -2
- package/packages/omo-codex/plugin/skills/coding-agent-sessions/references/codex.md +4 -4
- package/packages/omo-codex/plugin/skills/coding-agent-sessions/scripts/agent_sessions/cli.py +1 -1
- package/packages/omo-codex/plugin/skills/coding-agent-sessions/scripts/find-agent-sessions.py +6 -3
- package/packages/omo-codex/plugin/skills/frontend/ATTRIBUTION.md +1 -1
- package/packages/omo-codex/plugin/skills/frontend/SKILL.md +34 -12
- package/packages/omo-codex/plugin/skills/frontend/references/design/README.md +2 -2
- package/packages/omo-codex/plugin/skills/frontend/references/design/_INDEX.md +2 -1
- package/packages/omo-codex/plugin/skills/frontend/references/design/component-catalogs.md +14 -10
- package/packages/omo-codex/plugin/skills/frontend/references/design/design-system-architecture.md +19 -6
- package/packages/omo-codex/plugin/skills/frontend/references/design/grounding.md +29 -0
- package/packages/omo-codex/plugin/skills/frontend/references/ui-ux-db/scripts/design_system.py +2 -0
- package/packages/omo-codex/plugin/skills/frontend/references/ui-ux-db/scripts/search.py +2 -0
- package/packages/omo-codex/plugin/skills/lcx-contribute-bug-fix/SKILL.md +51 -55
- package/packages/omo-codex/plugin/skills/lcx-contribute-bug-fix/agents/openai.yaml +1 -1
- package/packages/omo-codex/plugin/skills/lcx-report-bug/SKILL.md +7 -7
- package/packages/omo-codex/plugin/skills/lcx-report-bug/agents/openai.yaml +1 -1
- package/packages/omo-codex/plugin/skills/ultimate-browsing/SKILL.md +5 -4
- package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/AGENTS.md +2 -2
- package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/__main__.py +9 -6
- package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/summary.py +1 -1
- package/packages/omo-codex/plugin/skills/ultimate-browsing/references/insane-search/README.md +4 -4
- package/packages/omo-codex/plugin/skills/ultimate-browsing/scripts/extract_cookies.py +4 -2
- package/packages/omo-codex/plugin/skills/ultrawork/SKILL.md +3 -0
- package/packages/omo-codex/plugin/skills/ulw-research/AGENTS.md +8 -4
- package/packages/omo-codex/plugin/skills/ulw-research/SKILL.md +7 -259
- package/packages/omo-codex/plugin/skills/ulw-research/references/deliverable-phase.md +68 -24
- package/packages/omo-codex/plugin/skills/ulw-research/references/fact-ledger-grammar.md +7 -3
- package/packages/omo-codex/plugin/skills/ulw-research/references/fact-ledger.md +42 -8
- package/packages/omo-codex/plugin/skills/ulw-research/references/fidelity-claim-kinds.md +1 -1
- package/packages/omo-codex/plugin/skills/ulw-research/references/markdown-blocks.md +12 -0
- package/packages/omo-codex/plugin/skills/ulw-research/references/report-gates.md +14 -6
- package/packages/omo-codex/plugin/skills/ulw-research/references/research-collection.md +195 -0
- package/packages/omo-codex/plugin/skills/ulw-research/references/research-delivery.md +123 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/claim-kinds.mjs +33 -5
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/contracts.mjs +16 -15
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/conversion-probe.mjs +229 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/css-hiding.mjs +158 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/css-lite.mjs +4 -2
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/entities.mjs +13 -4
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-commands-fill.mjs +96 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-commands-meaning.mjs +26 -30
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-commands-polish.mjs +46 -19
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-commands-verify.mjs +37 -6
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-commands.mjs +40 -62
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-derive.mjs +7 -2
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-fill.mjs +9 -6
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-ledger-kinds.mjs +6 -7
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-locate.mjs +15 -9
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-claims.mjs +4 -3
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-csv.mjs +32 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-lexicon.mjs +1 -1
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-originals.mjs +161 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-policy.mjs +183 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-records.mjs +81 -14
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-snapshot-readability.mjs +49 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-sources.mjs +76 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-trace.mjs +25 -11
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-value-unit.mjs +202 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-values.mjs +40 -39
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/gates-fidelity.mjs +8 -6
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/gates-static.mjs +10 -1
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/html-lite.mjs +6 -2
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/html-markup.mjs +2 -2
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/html-text-map.mjs +140 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/markdown-closers.mjs +126 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/md-lite.mjs +1 -1
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/number-grammar.mjs +33 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/numerals.mjs +18 -7
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/outcome-start.mjs +66 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/outcome.mjs +11 -22
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/placeholder.mjs +18 -6
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/polish-apply.mjs +3 -2
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/raw-decode.mjs +110 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/raw-locator.mjs +47 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/report-tools-commands.mjs +1 -1
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/report-tools.mjs +7 -1
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/source-commands.mjs +87 -29
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/source-refresh.mjs +82 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/source-snapshot.mjs +73 -69
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/source-view.mjs +89 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/sources-citation.mjs +203 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/units.mjs +14 -3
- package/packages/omo-codex/plugin/test/payload-equivalence.test.mjs +6 -4
- package/packages/omo-codex/plugin/test/sync-skills.test.mjs +4 -3
- package/packages/omo-codex/scripts/install-dist/install-local.mjs +338 -237
- package/packages/prompts-core/prompts/ultrawork/codex.md +3 -0
- package/packages/shared-skills/skill-source-filter.mjs +6 -2
- package/packages/shared-skills/skills/ast-grep/README.md +1 -1
- package/packages/shared-skills/skills/ast-grep/install.ps1 +5 -3
- package/packages/shared-skills/skills/ast-grep/install.sh +4 -5
- package/packages/shared-skills/skills/ast-grep/references/install.md +2 -2
- package/packages/shared-skills/skills/ast-grep/scripts/ast_grep_helper.py +13 -3
- package/packages/shared-skills/skills/browser/runtime/omowright/manifest.json +1 -1
- package/packages/shared-skills/skills/coding-agent-sessions/AGENTS.md +5 -4
- package/packages/shared-skills/skills/coding-agent-sessions/SKILL.md +11 -11
- package/packages/shared-skills/skills/coding-agent-sessions/references/all-platforms.md +2 -2
- package/packages/shared-skills/skills/coding-agent-sessions/references/codex.md +4 -4
- package/packages/shared-skills/skills/coding-agent-sessions/scripts/agent_sessions/cli.py +1 -1
- package/packages/shared-skills/skills/coding-agent-sessions/scripts/find-agent-sessions.py +6 -3
- package/packages/shared-skills/skills/frontend/ATTRIBUTION.md +1 -1
- package/packages/shared-skills/skills/frontend/SKILL.md +34 -12
- package/packages/shared-skills/skills/frontend/references/design/README.md +2 -2
- package/packages/shared-skills/skills/frontend/references/design/_INDEX.md +2 -1
- package/packages/shared-skills/skills/frontend/references/design/component-catalogs.md +14 -10
- package/packages/shared-skills/skills/frontend/references/design/design-system-architecture.md +19 -6
- package/packages/shared-skills/skills/frontend/references/design/grounding.md +29 -0
- package/packages/shared-skills/skills/frontend/references/ui-ux-db/scripts/design_system.py +2 -0
- package/packages/shared-skills/skills/frontend/references/ui-ux-db/scripts/search.py +2 -0
- package/packages/shared-skills/skills/ultimate-browsing/SKILL.md +5 -4
- package/packages/shared-skills/skills/ultimate-browsing/engine/AGENTS.md +2 -2
- package/packages/shared-skills/skills/ultimate-browsing/engine/__main__.py +9 -6
- package/packages/shared-skills/skills/ultimate-browsing/engine/summary.py +1 -1
- package/packages/shared-skills/skills/ultimate-browsing/references/insane-search/README.md +4 -4
- package/packages/shared-skills/skills/ultimate-browsing/scripts/extract_cookies.py +4 -2
- package/packages/shared-skills/skills/ulw-research/AGENTS.md +8 -4
- package/packages/shared-skills/skills/ulw-research/SKILL.md +7 -259
- package/packages/shared-skills/skills/ulw-research/references/deliverable-phase.md +68 -24
- package/packages/shared-skills/skills/ulw-research/references/fact-ledger-grammar.md +7 -3
- package/packages/shared-skills/skills/ulw-research/references/fact-ledger.md +42 -8
- package/packages/shared-skills/skills/ulw-research/references/fidelity-claim-kinds.md +1 -1
- package/packages/shared-skills/skills/ulw-research/references/markdown-blocks.md +12 -0
- package/packages/shared-skills/skills/ulw-research/references/report-gates.md +14 -6
- package/packages/shared-skills/skills/ulw-research/references/research-collection.md +195 -0
- package/packages/shared-skills/skills/ulw-research/references/research-delivery.md +123 -0
- package/packages/shared-skills/skills/ulw-research/scripts/claim-kinds.mjs +33 -5
- package/packages/shared-skills/skills/ulw-research/scripts/claim-kinds.test.ts +37 -2
- package/packages/shared-skills/skills/ulw-research/scripts/claim-prefix.test.ts +76 -0
- package/packages/shared-skills/skills/ulw-research/scripts/contracts-validation.test.ts +83 -0
- package/packages/shared-skills/skills/ulw-research/scripts/contracts.mjs +16 -15
- package/packages/shared-skills/skills/ulw-research/scripts/contracts.test.ts +6 -109
- package/packages/shared-skills/skills/ulw-research/scripts/conversion-altered.test.ts +154 -0
- package/packages/shared-skills/skills/ulw-research/scripts/conversion-originals.test.ts +297 -0
- package/packages/shared-skills/skills/ulw-research/scripts/conversion-probe-classes.test.ts +361 -0
- package/packages/shared-skills/skills/ulw-research/scripts/conversion-probe.mjs +229 -0
- package/packages/shared-skills/skills/ulw-research/scripts/conversion-probe.test.ts +218 -0
- package/packages/shared-skills/skills/ulw-research/scripts/copy-returns-fidelity-policy.test.ts +156 -0
- package/packages/shared-skills/skills/ulw-research/scripts/copy-returns-polish.test.ts +10 -9
- package/packages/shared-skills/skills/ulw-research/scripts/css-hiding.mjs +158 -0
- package/packages/shared-skills/skills/ulw-research/scripts/css-lite.mjs +4 -2
- package/packages/shared-skills/skills/ulw-research/scripts/delivery-barrier-meaning.test.ts +186 -0
- package/packages/shared-skills/skills/ulw-research/scripts/delivery-barrier.test.ts +40 -87
- package/packages/shared-skills/skills/ulw-research/scripts/entities.mjs +13 -4
- package/packages/shared-skills/skills/ulw-research/scripts/fact-commands-fill.mjs +96 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fact-commands-meaning.mjs +26 -30
- package/packages/shared-skills/skills/ulw-research/scripts/fact-commands-polish.mjs +46 -19
- package/packages/shared-skills/skills/ulw-research/scripts/fact-commands-verify.mjs +37 -6
- package/packages/shared-skills/skills/ulw-research/scripts/fact-commands.mjs +40 -62
- package/packages/shared-skills/skills/ulw-research/scripts/fact-derive-2.test.ts +149 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fact-derive.mjs +7 -2
- package/packages/shared-skills/skills/ulw-research/scripts/fact-derive.test.ts +7 -192
- package/packages/shared-skills/skills/ulw-research/scripts/fact-fill.mjs +9 -6
- package/packages/shared-skills/skills/ulw-research/scripts/fact-ledger-kinds.mjs +6 -7
- package/packages/shared-skills/skills/ulw-research/scripts/fact-ledger-kinds.test.ts +2 -1
- package/packages/shared-skills/skills/ulw-research/scripts/fact-ledger-legacy.test.ts +2 -1
- package/packages/shared-skills/skills/ulw-research/scripts/fact-locate.mjs +15 -9
- package/packages/shared-skills/skills/ulw-research/scripts/fact-unit-dimensions-2.test.ts +149 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fact-unit-dimensions.test.ts +4 -168
- package/packages/shared-skills/skills/ulw-research/scripts/fact-unit-rules.test.ts +3 -229
- package/packages/shared-skills/skills/ulw-research/scripts/fidelity-claims.mjs +4 -3
- package/packages/shared-skills/skills/ulw-research/scripts/fidelity-csv.mjs +32 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fidelity-lexicon.mjs +1 -1
- package/packages/shared-skills/skills/ulw-research/scripts/fidelity-originals.mjs +161 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fidelity-policy.mjs +183 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fidelity-records.mjs +81 -14
- package/packages/shared-skills/skills/ulw-research/scripts/fidelity-snapshot-readability.mjs +49 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fidelity-sources.mjs +76 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fidelity-trace.mjs +25 -11
- package/packages/shared-skills/skills/ulw-research/scripts/fidelity-value-unit.mjs +202 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fidelity-values.mjs +40 -39
- package/packages/shared-skills/skills/ulw-research/scripts/fidelity-values.test.ts +8 -29
- package/packages/shared-skills/skills/ulw-research/scripts/gates-fidelity.mjs +8 -6
- package/packages/shared-skills/skills/ulw-research/scripts/gates-static-headings.test.ts +17 -0
- package/packages/shared-skills/skills/ulw-research/scripts/gates-static.mjs +10 -1
- package/packages/shared-skills/skills/ulw-research/scripts/html-lite.mjs +6 -2
- package/packages/shared-skills/skills/ulw-research/scripts/html-markup.mjs +2 -2
- package/packages/shared-skills/skills/ulw-research/scripts/html-text-map.mjs +140 -0
- package/packages/shared-skills/skills/ulw-research/scripts/html-text-map.test.ts +148 -0
- package/packages/shared-skills/skills/ulw-research/scripts/markdown-closers.mjs +126 -0
- package/packages/shared-skills/skills/ulw-research/scripts/md-lite-boundaries.test.ts +46 -0
- package/packages/shared-skills/skills/ulw-research/scripts/md-lite.mjs +1 -1
- package/packages/shared-skills/skills/ulw-research/scripts/number-grammar.mjs +33 -0
- package/packages/shared-skills/skills/ulw-research/scripts/number-grammar.test.ts +36 -0
- package/packages/shared-skills/skills/ulw-research/scripts/numerals.mjs +18 -7
- package/packages/shared-skills/skills/ulw-research/scripts/numerals.test.ts +3 -0
- package/packages/shared-skills/skills/ulw-research/scripts/outcome-start.mjs +66 -0
- package/packages/shared-skills/skills/ulw-research/scripts/outcome-start.test.ts +109 -0
- package/packages/shared-skills/skills/ulw-research/scripts/outcome.mjs +11 -22
- package/packages/shared-skills/skills/ulw-research/scripts/page-source-locator.test.ts +187 -0
- package/packages/shared-skills/skills/ulw-research/scripts/placeholder.mjs +18 -6
- package/packages/shared-skills/skills/ulw-research/scripts/polish-apply.mjs +3 -2
- package/packages/shared-skills/skills/ulw-research/scripts/polish-markup.test.ts +15 -1
- package/packages/shared-skills/skills/ulw-research/scripts/polish-partial.test.ts +42 -0
- package/packages/shared-skills/skills/ulw-research/scripts/raw-decode.mjs +110 -0
- package/packages/shared-skills/skills/ulw-research/scripts/raw-locator.mjs +47 -0
- package/packages/shared-skills/skills/ulw-research/scripts/raw-snapshot.test.ts +139 -0
- package/packages/shared-skills/skills/ulw-research/scripts/report-tools-2.test.ts +233 -0
- package/packages/shared-skills/skills/ulw-research/scripts/report-tools-3.test.ts +141 -0
- package/packages/shared-skills/skills/ulw-research/scripts/report-tools-4.test.ts +135 -0
- package/packages/shared-skills/skills/ulw-research/scripts/report-tools-5.test.ts +231 -0
- package/packages/shared-skills/skills/ulw-research/scripts/report-tools-commands.mjs +1 -1
- package/packages/shared-skills/skills/ulw-research/scripts/report-tools.mjs +7 -1
- package/packages/shared-skills/skills/ulw-research/scripts/report-tools.test.ts +6 -560
- package/packages/shared-skills/skills/ulw-research/scripts/snapshot-readability.test.ts +116 -0
- package/packages/shared-skills/skills/ulw-research/scripts/source-commands.mjs +87 -29
- package/packages/shared-skills/skills/ulw-research/scripts/source-integrity.test.ts +243 -0
- package/packages/shared-skills/skills/ulw-research/scripts/source-refresh.mjs +82 -0
- package/packages/shared-skills/skills/ulw-research/scripts/source-snapshot.mjs +73 -69
- package/packages/shared-skills/skills/ulw-research/scripts/source-snapshot.test.ts +7 -4
- package/packages/shared-skills/skills/ulw-research/scripts/source-view.mjs +89 -0
- package/packages/shared-skills/skills/ulw-research/scripts/sources-citation.mjs +203 -0
- package/packages/shared-skills/skills/ulw-research/scripts/sources-citation.test.ts +236 -0
- package/packages/shared-skills/skills/ulw-research/scripts/tests/contracts-support.ts +147 -0
- package/packages/shared-skills/skills/ulw-research/scripts/tests/delivery-barrier-session.ts +59 -0
- package/packages/shared-skills/skills/ulw-research/scripts/tests/derive-clock.ts +4 -0
- package/packages/shared-skills/skills/ulw-research/scripts/tests/fact-derive-support.ts +65 -0
- package/packages/shared-skills/skills/ulw-research/scripts/tests/fact-unit-dimensions-support.ts +41 -0
- package/packages/shared-skills/skills/ulw-research/scripts/tests/fact-unit-rules-support.ts +241 -0
- package/packages/shared-skills/skills/ulw-research/scripts/tests/fidelity-values-support.ts +30 -0
- package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/conversion-probe.html +14 -0
- package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/sources-shapes.html +20 -0
- package/packages/shared-skills/skills/ulw-research/scripts/tests/numeral-classes.ts +196 -0
- package/packages/shared-skills/skills/ulw-research/scripts/tests/report-tools-support.ts +182 -0
- package/packages/shared-skills/skills/ulw-research/scripts/tests/value-mode-support.ts +48 -0
- package/packages/shared-skills/skills/ulw-research/scripts/units.mjs +14 -3
- package/packages/shared-skills/skills/ulw-research/scripts/units.test.ts +10 -0
- package/packages/shared-skills/skills/ulw-research/scripts/value-mode-unit-classes.test.ts +255 -0
- package/packages/shared-skills/skills/ulw-research/scripts/value-mode-unit.test.ts +179 -0
- package/packages/shared-skills/skills/ulw-research/scripts/value-rules-cli.test.ts +157 -0
- package/packages/shared-skills/skills/ulw-research/scripts/value-rules-followups.test.ts +72 -0
- package/packages/shared-skills/skills/ulw-research/scripts/value-rules-priority.test.ts +80 -0
- package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/tests/fixtures/amp_redirect_stub.html +0 -7
- package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/tests/fixtures/search_interstitial.html +0 -19
- package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/tests/fixtures/wayback_available.json +0 -1
- package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/tests/fixtures/wayback_snapshot.html +0 -1128
- package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/tests/test_fetch_chain.py +0 -77
- package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/tests/test_playwright_stealth.py +0 -86
- package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/tests/test_playwright_templates.py +0 -279
- package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/tests/test_surrogate.py +0 -252
- package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/tests/test_surrogate_validators.py +0 -78
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { describe, expect, onTestFinished, test } from "bun:test"
|
|
2
|
+
import { cpSync, mkdirSync, mkdtempSync, readFileSync, rmSync, 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 { runFactVerify } from "./fact-commands-verify.mjs"
|
|
9
|
+
import { valueChecks } from "./fidelity-values.mjs"
|
|
10
|
+
import { unreadableSnapshots } from "./fidelity-snapshot-readability.mjs"
|
|
11
|
+
import { buildContext } from "./gates-static.mjs"
|
|
12
|
+
import { parseHtml } from "./html-lite.mjs"
|
|
13
|
+
|
|
14
|
+
type Row = Record<string, unknown> & { fact_id: string; source_id?: string; locator?: Record<string, unknown> }
|
|
15
|
+
type Defect = { code: string; fact_id?: string; message: string }
|
|
16
|
+
const FIX = join(import.meta.dir, "tests", "fixtures", "fidelity")
|
|
17
|
+
const BASE: { rows: Row[] } = JSON.parse(readFileSync(join(FIX, "ledger-verify.json"), "utf8"))
|
|
18
|
+
const check = (html: string, ledger = BASE, sources = join(FIX, "sources")) =>
|
|
19
|
+
valueChecks(buildContext(parseHtml(`<h2>Findings</h2>${html}`), { baseDir: FIX }), ledger, null, sources) as Defect[]
|
|
20
|
+
const data = (id: string, shown: string) => `<p><data data-fact="${id}">${shown}</data></p>`
|
|
21
|
+
const verified = '<p>The suite reported <data data-fact="F7" data-claim-kind="verified">42 passed</data> [S5].</p>'
|
|
22
|
+
const blocked = (defects: Defect[]) => defects.filter((d) => d.code === "snapshot_hash_mismatch")
|
|
23
|
+
|
|
24
|
+
function sourcesCopy() {
|
|
25
|
+
const dir = mkdtempSync(join(tmpdir(), "snapshot-readability-"))
|
|
26
|
+
onTestFinished(() => rmSync(dir, { recursive: true, force: true }))
|
|
27
|
+
cpSync(join(FIX, "sources"), dir, { recursive: true })
|
|
28
|
+
return dir
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
describe("an unreadable snapshot blocks only the rows it backs", () => {
|
|
32
|
+
for (const damage of ["directory", "meta.json", "text.txt", "invalid metadata", "unreadable body", "raw.bin"]) {
|
|
33
|
+
test(`#given S4 with a damaged ${damage} #when verified #then every S4-backed row is blocked`, () => {
|
|
34
|
+
const dir = sourcesCopy()
|
|
35
|
+
const ledger = structuredClone(BASE)
|
|
36
|
+
if (damage === "directory") rmSync(dir, { recursive: true })
|
|
37
|
+
else if (damage === "invalid metadata") writeFileSync(join(dir, "S4/meta.json"), "{")
|
|
38
|
+
else if (damage === "unreadable body") {
|
|
39
|
+
rmSync(join(dir, "S4/text.txt"))
|
|
40
|
+
mkdirSync(join(dir, "S4/text.txt"))
|
|
41
|
+
} else if (damage === "raw.bin") {
|
|
42
|
+
for (const row of ledger.rows.filter((r) => r.source_id === "S4")) row.locator = { ...row.locator, artifact: "raw.bin", unit: "utf8-bytes" }
|
|
43
|
+
} else rmSync(join(dir, "S4", damage))
|
|
44
|
+
const defects = check(data("F1", "up to 320 m") + data("F2", "1,237"), ledger, dir)
|
|
45
|
+
expect(blocked(defects).map((d) => d.fact_id).sort()).toEqual(["F1", "F2"])
|
|
46
|
+
expect(DEFECT_CODES.snapshot_hash_mismatch).toMatchObject({ severity: "blocker", integrity: true })
|
|
47
|
+
})
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
test("#given S4's meta.json deleted #when a verified fact on the healthy command snapshot S5 is checked #then it stays clean", () => {
|
|
51
|
+
const dir = sourcesCopy()
|
|
52
|
+
rmSync(join(dir, "S4", "meta.json"))
|
|
53
|
+
const defects = check(data("F1", "up to 320 m") + verified, BASE, dir)
|
|
54
|
+
expect(blocked(defects).map((d) => d.fact_id)).toEqual(["F1"])
|
|
55
|
+
expect(defects.filter((d) => d.code !== "snapshot_hash_mismatch")).toEqual([])
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
test("#given a text edit and a deleted meta.json #when verified #then the same code names different causes", () => {
|
|
59
|
+
const edited = sourcesCopy()
|
|
60
|
+
writeFileSync(join(edited, "S4/text.txt"), `${readFileSync(join(edited, "S4/text.txt"), "utf8")}edited`)
|
|
61
|
+
const missing = sourcesCopy()
|
|
62
|
+
rmSync(join(missing, "S4", "meta.json"))
|
|
63
|
+
const [a] = blocked(check(data("F1", "up to 320 m"), BASE, edited))
|
|
64
|
+
const [b] = blocked(check(data("F1", "up to 320 m"), BASE, missing))
|
|
65
|
+
expect(a?.message).toContain("text.txt changed after its snapshot was pinned")
|
|
66
|
+
expect(b?.message).toContain("meta.json is missing")
|
|
67
|
+
expect(a?.message).not.toBe(b?.message)
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
test("#given an edited snapshot behind a row and one named only in prose #when verified #then both carry one wording of the cause", () => {
|
|
71
|
+
const dir = sourcesCopy()
|
|
72
|
+
for (const id of ["S4", "S5"]) writeFileSync(join(dir, id, "text.txt"), `${readFileSync(join(dir, id, "text.txt"), "utf8")}edited`)
|
|
73
|
+
const defects = blocked(check(`${data("F1", "up to 320 m")}<p>The suite ran [S5].</p>`, BASE, dir))
|
|
74
|
+
const cause = (id: string) => defects.find((d) => d.message.includes(`snapshot ${id} `))?.message.split(`snapshot ${id} `)[1]
|
|
75
|
+
expect(defects).toHaveLength(2)
|
|
76
|
+
expect(cause("S5")).toBe(cause("S4") as string)
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
test("#given source-free assumptions and intact snapshots #when verified #then nothing is blocked", () => {
|
|
80
|
+
expect(check(data("F2", "1,237"))).toEqual([])
|
|
81
|
+
const assumed = { ...BASE.rows.find((r) => r.fact_id === "F8"), fact_id: "F8", kind: "assumed", raw: "15%", value: "15", unit: "%", derived_from: [] }
|
|
82
|
+
expect(check('<data data-fact="F8" data-claim-kind="assumed">15%</data>', { ...BASE, rows: [assumed] }, "nonexistent")).toEqual([])
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
test("#given raw-body and explicit converted-only snapshots #when read #then both keep their source contract", () => {
|
|
86
|
+
const dir = mkdtempSync(join(tmpdir(), "snapshot-raw-"))
|
|
87
|
+
onTestFinished(() => rmSync(dir, { recursive: true, force: true }))
|
|
88
|
+
mkdirSync(join(dir, "S1"))
|
|
89
|
+
const ctx = buildContext(parseHtml(data("F1", "42")), { baseDir: FIX })
|
|
90
|
+
const cited = new Map([["F1", ctx.body]])
|
|
91
|
+
for (const kind of ["command", "converted-only"]) {
|
|
92
|
+
const body = kind === "command" ? "raw command body" : "converted quote"
|
|
93
|
+
const file = kind === "command" ? "raw.bin" : "text.txt"
|
|
94
|
+
writeFileSync(join(dir, "S1", file), body)
|
|
95
|
+
writeFileSync(join(dir, "S1/meta.json"), JSON.stringify({ kind, sha256: sha256Of(body) }))
|
|
96
|
+
const rows = new Map([["F1", { fact_id: "F1", source_id: "S1", kind: "quote", locator: { unit: "utf8-bytes" } }]])
|
|
97
|
+
const defects: object[] = []
|
|
98
|
+
expect([...unreadableSnapshots(ctx, rows, cited, dir, defects)]).toEqual([])
|
|
99
|
+
expect(defects).toEqual([])
|
|
100
|
+
rmSync(join(dir, "S1", file))
|
|
101
|
+
}
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
test("#given a missing sources directory #when fact verify runs #then exit 1 with a blocker per source-backed row", () => {
|
|
105
|
+
const dir = mkdtempSync(join(tmpdir(), "snapshot-verify-"))
|
|
106
|
+
onTestFinished(() => rmSync(dir, { recursive: true, force: true }))
|
|
107
|
+
writeFileSync(join(dir, "fact-ledger.json"), JSON.stringify(BASE))
|
|
108
|
+
cpSync(join(FIX, "..", "fidelity-brief-locked.md"), join(dir, "brief.md"))
|
|
109
|
+
const report = join(dir, "report.html")
|
|
110
|
+
writeFileSync(report, data("F1", "up to 320 m") + data("F2", "1,237"))
|
|
111
|
+
const result = runFactVerify({ positionals: [report], flags: { "session-dir": dir, sources: join(dir, "nonexistent") } })
|
|
112
|
+
expect(result.exitCode).toBe(1)
|
|
113
|
+
const defects = ("defects" in result.json ? result.json.defects : []) as Defect[]
|
|
114
|
+
expect(blocked(defects).map((d) => d.fact_id).sort()).toEqual(["F1", "F2"])
|
|
115
|
+
})
|
|
116
|
+
})
|
|
@@ -1,9 +1,13 @@
|
|
|
1
|
-
// source-commands.mjs - the `source snapshot` and `claims index` subcommands of
|
|
2
|
-
// A source dir is immutable: the same id with identical
|
|
3
|
-
// (exit 1, snapshot_exists), because ledger rows pin the old hash
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
1
|
+
// source-commands.mjs - the `source snapshot`, `source refresh` and `claims index` subcommands of
|
|
2
|
+
// report-tools. A source dir is immutable: the same id with identical bytes is a no-op, different
|
|
3
|
+
// bytes fail (exit 1, snapshot_exists), because ledger rows pin the old hash; `source refresh`
|
|
4
|
+
// (source-refresh.mjs) is the one explicit repin. `--replace` swaps in a corrected capture under the
|
|
5
|
+
// same id (the conversion_altered_number recovery) only while no active ledger row cites the id
|
|
6
|
+
// (exit 1, snapshot_pinned, listing them). `--url` fetches with plain fetch (30 s timeout,
|
|
7
|
+
// no browser) and records extractor tool:source-snapshot@1; `--html` imports a browser-lane page
|
|
8
|
+
// capture and `--raw` alone a saved file's bytes (extractor model). `--text` alone registers a converted-only source (no raw bytes:
|
|
9
|
+
// quotes only); `--raw <bytes> --text <extraction> --extractor NAME@VERSION` keeps the original
|
|
10
|
+
// bytes and declares the text their extraction. `--command` runs an argv without a shell and
|
|
7
11
|
// snapshots its stdout, with the argv, cwd, exit status, and output hashes in command.json.
|
|
8
12
|
// node: builtins and sibling imports only.
|
|
9
13
|
|
|
@@ -13,13 +17,18 @@ import { join, resolve } from "node:path"
|
|
|
13
17
|
|
|
14
18
|
import { indexClaims } from "./claims-index.mjs"
|
|
15
19
|
import { CliError, parseArgs, sha256Of, writeJsonAtomic } from "./cli-support.mjs"
|
|
20
|
+
import { runConversionProbe } from "./conversion-probe.mjs"
|
|
21
|
+
import { readLedger } from "./fact-ledger.mjs"
|
|
16
22
|
import { parseFidelityBlock } from "./fidelity-brief.mjs"
|
|
23
|
+
import { parseSourceFloor } from "./fidelity-policy.mjs"
|
|
24
|
+
import { encodingOf, headerCharset } from "./raw-decode.mjs"
|
|
17
25
|
import { writeSnapshot } from "./source-snapshot.mjs"
|
|
26
|
+
import { readSnapshot } from "./source-view.mjs"
|
|
18
27
|
|
|
19
28
|
const FETCH_TIMEOUT_MS = 30_000
|
|
20
29
|
const TOOL_EXTRACTOR = "tool:source-snapshot@1"
|
|
21
|
-
const SNAPSHOT_USAGE =
|
|
22
|
-
"usage: source snapshot --id S<n> (--url <u> | --html <file> | --
|
|
30
|
+
export const SNAPSHOT_USAGE =
|
|
31
|
+
"usage: source probe-conversion <raw-file> <converted-file> | source snapshot --id S<n> (--url <u> | --html <file> | --raw <file> [--text <file> --extractor NAME@VERSION] | --text <file> (converted-only) | --command '<argv json>' --cwd DIR) [--charset NAME] [--kind page|command|document|converted-only] [--provenance primary|secondary] [--revision R] [--replace] --session-dir D"
|
|
23
32
|
|
|
24
33
|
function readInput(path) {
|
|
25
34
|
try {
|
|
@@ -36,20 +45,27 @@ export function sessionDirOf(flags) {
|
|
|
36
45
|
return resolve(dir)
|
|
37
46
|
}
|
|
38
47
|
|
|
39
|
-
/** The snapshot
|
|
48
|
+
/** The snapshot as source-view.mjs reads it ({ meta, raw, sha256, drift, view }); unreadable is a usage error. */
|
|
40
49
|
export function snapshotOf(sessionDir, id) {
|
|
41
50
|
const dir = join(sessionDir, "sources", String(id))
|
|
42
51
|
if (!existsSync(join(dir, "meta.json"))) throw new CliError(`source ${id} has no snapshot; run source snapshot --id ${id} first`)
|
|
43
|
-
|
|
44
|
-
|
|
52
|
+
try {
|
|
53
|
+
return readSnapshot(dir)
|
|
54
|
+
} catch (error) {
|
|
55
|
+
throw new CliError(`source ${id}: ${error.message}`)
|
|
56
|
+
}
|
|
45
57
|
}
|
|
46
58
|
|
|
47
|
-
/**
|
|
59
|
+
/**
|
|
60
|
+
* Whether the brief's source floor lists this source as pointed at (`pointed only: a, b`). An absent
|
|
61
|
+
* or pending floor points at nothing; a malformed floor is a usage error (parseSourceFloor's CliError).
|
|
62
|
+
*/
|
|
48
63
|
export function pointedFlag(sessionDir, url) {
|
|
49
64
|
const brief = join(sessionDir, "brief.md")
|
|
50
65
|
const floor = existsSync(brief) ? (parseFidelityBlock(readInput(brief)).fields?.["source floor"] ?? "") : ""
|
|
51
|
-
|
|
52
|
-
|
|
66
|
+
if (floor === "" || floor === "pending") return false
|
|
67
|
+
const rule = parseSourceFloor(floor)
|
|
68
|
+
return rule?.kind === "pointed" && rule.list.includes(url)
|
|
53
69
|
}
|
|
54
70
|
|
|
55
71
|
async function fetchSource(url) {
|
|
@@ -67,10 +83,10 @@ async function fetchSource(url) {
|
|
|
67
83
|
const type = response.headers.get("content-type") ?? ""
|
|
68
84
|
if (/pdf/i.test(type)) {
|
|
69
85
|
await response.body?.cancel()
|
|
70
|
-
throw new CliError(`pdf response: ${url};
|
|
86
|
+
throw new CliError(`pdf response: ${url}; save the bytes and use --raw <file.pdf> --text <extracted.txt> --extractor NAME@VERSION`)
|
|
71
87
|
}
|
|
72
|
-
const
|
|
73
|
-
return /html|xml/i.test(type) || /^\s*</.test(
|
|
88
|
+
const raw = Buffer.from(await response.arrayBuffer())
|
|
89
|
+
return { url, raw, charset: headerCharset(type), markup: /html|xml/i.test(type) || /^\s*</.test(raw.subarray(0, 1024).toString("latin1")) }
|
|
74
90
|
}
|
|
75
91
|
|
|
76
92
|
function runCommand(argvJson, cwd) {
|
|
@@ -84,35 +100,76 @@ function runCommand(argvJson, cwd) {
|
|
|
84
100
|
throw new CliError("--command must be a non-empty JSON array of non-empty strings")
|
|
85
101
|
}
|
|
86
102
|
if (!cwd || cwd === true) throw new CliError("--command needs --cwd DIR")
|
|
87
|
-
const proc = spawnSync(argv[0], argv.slice(1), { cwd: resolve(cwd),
|
|
103
|
+
const proc = spawnSync(argv[0], argv.slice(1), { cwd: resolve(cwd), shell: false, maxBuffer: 64 * 1024 * 1024 })
|
|
88
104
|
if (proc.error) throw new CliError(`--command could not start: ${proc.error.message}`)
|
|
89
105
|
const receipt = { argv, cwd: resolve(cwd), exit_status: proc.status, stdout_sha256: sha256Of(proc.stdout), stderr_sha256: sha256Of(proc.stderr) }
|
|
90
|
-
return { path: `command:${argv.join(" ")}`,
|
|
106
|
+
return { path: `command:${argv.join(" ")}`, raw: proc.stdout, kind: "command", receipt }
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function readBytes(path) {
|
|
110
|
+
try {
|
|
111
|
+
return readFileSync(path)
|
|
112
|
+
} catch (error) {
|
|
113
|
+
throw new CliError(`cannot read ${path}: ${error.code ?? error.message}`)
|
|
114
|
+
}
|
|
91
115
|
}
|
|
92
116
|
|
|
93
|
-
|
|
117
|
+
/** The snapshot input the flags describe; shared by `source snapshot` and `source refresh`. */
|
|
118
|
+
export async function acquire(flags) {
|
|
94
119
|
const given = ["url", "html", "text", "command"].filter((name) => flags[name] !== undefined)
|
|
95
|
-
if (given.
|
|
120
|
+
if (flags.raw !== undefined && given.some((name) => name !== "text")) throw new CliError("--raw goes alone or with --text (--url, --html and --command already keep the raw bytes)")
|
|
121
|
+
if (given.length + (flags.raw !== undefined && flags.text === undefined ? 1 : 0) !== 1) throw new CliError(SNAPSHOT_USAGE)
|
|
96
122
|
if (flags.extractor !== undefined && flags.text === undefined) throw new CliError("--extractor goes with --text only")
|
|
97
|
-
if (flags.
|
|
98
|
-
|
|
99
|
-
if (flags.
|
|
123
|
+
if (flags.charset !== undefined && encodingOf(flags.charset) === null) throw new CliError(`--charset ${flags.charset} is not a known encoding`)
|
|
124
|
+
const charset = flags.charset === undefined ? {} : { charset: encodingOf(flags.charset) }
|
|
125
|
+
if (flags.url !== undefined) return { ...(await fetchSource(flags.url)), ...charset, extractor: TOOL_EXTRACTOR }
|
|
126
|
+
if (flags.html !== undefined) return { path: resolve(flags.html), raw: readBytes(flags.html), markup: true, ...charset, extractor: "model" }
|
|
127
|
+
if (flags.raw !== undefined && flags.text === undefined) return { path: resolve(flags.raw), raw: readBytes(flags.raw), ...charset, extractor: "model" }
|
|
128
|
+
if (flags.text !== undefined) {
|
|
129
|
+
const extractor = flags.extractor === undefined ? "model" : `extractor:${flags.extractor}`
|
|
130
|
+
if (flags.raw !== undefined) return { path: resolve(flags.raw), raw: readBytes(flags.raw), text: readInput(flags.text), ...charset, extractor }
|
|
131
|
+
if (flags.kind !== undefined && flags.kind !== "converted-only") throw new CliError("--text without --raw registers a converted-only source; pass --raw <original bytes> to keep a measurable snapshot")
|
|
132
|
+
return { path: resolve(flags.text), text: readInput(flags.text), kind: "converted-only", extractor }
|
|
133
|
+
}
|
|
100
134
|
return { ...runCommand(flags.command, flags.cwd), extractor: TOOL_EXTRACTOR }
|
|
101
135
|
}
|
|
102
136
|
|
|
137
|
+
/**
|
|
138
|
+
* `--replace` with `--text` alone (the conversion_altered_number recovery) keeps the source's raw.bin and
|
|
139
|
+
* declares the verified text its extraction, so value rows locate in text.txt while the original bytes stay
|
|
140
|
+
* pinned; plain `--text` without `--replace`, or over an id with no raw.bin, stays converted-only.
|
|
141
|
+
*/
|
|
142
|
+
function declareOverKeptRaw(sourcesDir, id, input) {
|
|
143
|
+
const dir = join(sourcesDir, String(id))
|
|
144
|
+
if (input.kind !== "converted-only" || !existsSync(join(dir, "raw.bin")) || !existsSync(join(dir, "meta.json"))) return
|
|
145
|
+
const before = JSON.parse(readFileSync(join(dir, "meta.json"), "utf8"))
|
|
146
|
+
Object.assign(input, { url: before.url, raw: readFileSync(join(dir, "raw.bin")), kind: before.kind, declared: true })
|
|
147
|
+
delete input.path
|
|
148
|
+
}
|
|
149
|
+
|
|
103
150
|
export async function runSource(argv) {
|
|
104
151
|
const [sub, ...rest] = argv
|
|
152
|
+
if (sub === "refresh") return (await import("./source-refresh.mjs")).runRefresh(rest)
|
|
153
|
+
if (sub === "probe-conversion") return runConversionProbe(rest)
|
|
105
154
|
if (sub !== "snapshot") throw new CliError(SNAPSHOT_USAGE)
|
|
106
|
-
const args = parseArgs(rest)
|
|
155
|
+
const args = parseArgs(rest, { booleans: ["replace"] })
|
|
107
156
|
const id = args.required("id")
|
|
108
|
-
const
|
|
157
|
+
const sessionDir = sessionDirOf(args.flags)
|
|
158
|
+
const sourcesDir = join(sessionDir, "sources")
|
|
159
|
+
const replace = args.flags.replace === true
|
|
160
|
+
const pinned = replace ? readLedger(sessionDir).rows.filter((row) => row.source_id === id && (row.status ?? "active") === "active").map((row) => row.fact_id) : []
|
|
161
|
+
if (pinned.length > 0) {
|
|
162
|
+
const message = `${id} is cited by active row(s) ${pinned.join(", ")}; retract them first, or snapshot the corrected text under a new id`
|
|
163
|
+
return { json: { command: "source snapshot", status: "fail", exitCode: 1, error: "snapshot_pinned", message, rows: pinned }, summary: `source snapshot: ${message}`, exitCode: 1 }
|
|
164
|
+
}
|
|
109
165
|
const { receipt, ...input } = await acquire(args.flags)
|
|
166
|
+
if (replace) declareOverKeptRaw(sourcesDir, id, input)
|
|
110
167
|
if (args.flags.kind !== undefined) input.kind = args.flags.kind
|
|
111
168
|
if (args.flags.provenance !== undefined) input.provenance = args.flags.provenance
|
|
112
169
|
if (args.flags.revision !== undefined) input.revision = args.flags.revision
|
|
113
170
|
let written
|
|
114
171
|
try {
|
|
115
|
-
written = writeSnapshot(sourcesDir, id, input)
|
|
172
|
+
written = writeSnapshot(sourcesDir, id, input, { replace })
|
|
116
173
|
} catch (error) {
|
|
117
174
|
if (String(error.message).startsWith("snapshot_exists")) {
|
|
118
175
|
return { json: { command: "source snapshot", status: "fail", exitCode: 1, error: "snapshot_exists", message: error.message }, summary: `source snapshot: ${error.message}`, exitCode: 1 }
|
|
@@ -122,8 +179,9 @@ export async function runSource(argv) {
|
|
|
122
179
|
}
|
|
123
180
|
if (receipt) writeFileSync(join(written.dir, "command.json"), `${JSON.stringify(receipt, null, 2)}\n`)
|
|
124
181
|
const meta = JSON.parse(readFileSync(join(written.dir, "meta.json"), "utf8"))
|
|
125
|
-
const data = { source_id: id, directory: written.dir, snapshot_sha256: written.sha256, extractor: meta.extractor, kind: meta.kind, provenance: meta.provenance }
|
|
126
|
-
|
|
182
|
+
const data = { source_id: id, directory: written.dir, snapshot_sha256: written.sha256, extractor: meta.extractor, kind: meta.kind, provenance: meta.provenance, text_source: meta.text_source, ...(written.altered ? { conversion_altered_numbers: written.altered } : {}) }
|
|
183
|
+
const probe = written.altered ? `; conversion ${written.altered.length} altered${written.altered.length ? ` (${written.altered.join(", ")}): fact add refuses those numbers` : ""}` : ""
|
|
184
|
+
return { json: { command: "source snapshot", status: "pass", exitCode: 0, data }, summary: `source snapshot: ${id} ${written.sha256.slice(0, 12)} (${meta.extractor}, ${meta.text_source})${probe}`, exitCode: 0 }
|
|
127
185
|
}
|
|
128
186
|
|
|
129
187
|
export function runClaims(argv) {
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
import { describe, expect, onTestFinished, test } from "bun:test"
|
|
2
|
+
import { chmodSync, existsSync, 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 { addRow, readLedger, writeLedger } from "./fact-ledger.mjs"
|
|
8
|
+
import { run } from "./report-tools.mjs"
|
|
9
|
+
|
|
10
|
+
const BRIEF = readFileSync(join(import.meta.dir, "tests/fixtures/fidelity-brief-locked.md"), "utf8")
|
|
11
|
+
const PAGE = '<html><body><p>The main deck spans <span title="9 m">320</span> m in total.</p><table id="sizes"><tr><th>Name</th><th>Length</th></tr><tr><td>Deck</td><td>320 m</td></tr></table></body></html>'
|
|
12
|
+
|
|
13
|
+
// Plain markup (no inline spans) so the pre-fold locator can build the same session: the fail-closed and refresh cases then fail for their own reason.
|
|
14
|
+
const PLAIN = "<html><body><p>The main deck spans 320 m in total.</p></body></html>"
|
|
15
|
+
|
|
16
|
+
type Defect = { code: string; message: string }
|
|
17
|
+
|
|
18
|
+
/** {{F1}} -> {{F1:<its ledger label>}}: fill requires the label. */
|
|
19
|
+
const labeled = (dir: string) => {
|
|
20
|
+
const labels = new Map(readLedger(dir).rows.map((row: { fact_id: string; label: string }) => [row.fact_id, row.label]))
|
|
21
|
+
return (placeholder: string) => placeholder.replace(/\{\{(\w+)\}\}/, (_, id) => `{{${id}:${labels.get(id)}}}`)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function session() {
|
|
25
|
+
const dir = mkdtempSync(join(tmpdir(), "source-integrity-"))
|
|
26
|
+
onTestFinished(() => {
|
|
27
|
+
for (const name of ["S1", "S2"]) if (existsSync(join(dir, "sources", name, "raw.bin"))) chmodSync(join(dir, "sources", name, "raw.bin"), 0o644)
|
|
28
|
+
rmSync(dir, { recursive: true, force: true })
|
|
29
|
+
})
|
|
30
|
+
writeFileSync(join(dir, "brief.md"), BRIEF)
|
|
31
|
+
return dir
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function file(dir: string, name: string, body: string | Buffer) {
|
|
35
|
+
writeFileSync(join(dir, name), body)
|
|
36
|
+
return join(dir, name)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const snapshot = (dir: string, id: string, ...flags: string[]) => run(["source", "snapshot", "--id", id, ...flags, "--session-dir", dir])
|
|
40
|
+
const fact = (dir: string, ...args: string[]) => run(["fact", ...args, "--session-dir", dir])
|
|
41
|
+
|
|
42
|
+
async function report(dir: string, placeholders: string[], extra: string[] = []) {
|
|
43
|
+
const cite = (placeholder: string) => (placeholder.includes("S2") ? "S2" : "S1")
|
|
44
|
+
const body = [
|
|
45
|
+
'<!doctype html><html lang="en"><head><meta charset="utf-8"><title>Bridge</title></head><body><h1>Bridge</h1><h2>Findings</h2>',
|
|
46
|
+
...placeholders.map((placeholder) => `<p>The source states ${labeled(dir)(placeholder.replace(/:S\d\}\}$/, "}}"))} [${cite(placeholder)}].</p>`),
|
|
47
|
+
"<h2>How this report was made</h2><p>Collected from cited public pages [S1].</p>",
|
|
48
|
+
'<h2>Sources</h2><ol><li id="s1">[S1] Bridge page, retrieved 2026-10-01.</li><li id="s2">[S2] Second page, retrieved 2026-10-01.</li></ol></body></html>',
|
|
49
|
+
].join("\n")
|
|
50
|
+
file(dir, "report.src.html", body)
|
|
51
|
+
expect((await fact(dir, "fill", join(dir, "report.src.html"), "--out", join(dir, "report.html"))).exitCode).toBe(0)
|
|
52
|
+
return fact(dir, "verify", join(dir, "report.html"), ...extra)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** A legacy text.txt snapshot S1 (no raw.bin, no byte locator) holding "<text>" with row F1, filled and verified clean. */
|
|
56
|
+
async function legacySession(text = "Span is 320 m.") {
|
|
57
|
+
const dir = session()
|
|
58
|
+
const sources = join(dir, "sources/S1")
|
|
59
|
+
mkdirSync(sources, { recursive: true })
|
|
60
|
+
writeLegacy(sources, text)
|
|
61
|
+
const ledger = readLedger(dir)
|
|
62
|
+
addRow(ledger, { kind: "measured", value: "320", unit: "m", value_span: [8, 11], raw: "320", quote: text, source_id: "S1", locator: { artifact: "S1/text.txt", line: 1, char_start: 0, char_end: 14 }, snapshot_sha256: sha256Of(text), sentence_id: "S1:s1", extracted_by: "model", observed_at: "2026-01-01T00:00:00.000Z", label: "span" })
|
|
63
|
+
writeLedger(dir, ledger)
|
|
64
|
+
expect((await report(dir, ["{{F1}}"])).exitCode).toBe(0)
|
|
65
|
+
return dir
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function writeLegacy(sources: string, text: string) {
|
|
69
|
+
writeFileSync(join(sources, "text.txt"), text)
|
|
70
|
+
writeFileSync(join(sources, "meta.json"), JSON.stringify({ source_id: "S1", url: "https://example.invalid/", observed_at: "2026-01-01T00:00:00.000Z", sha256: sha256Of(text), extractor: "model", revision: null, kind: "page", provenance: "unknown" }))
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const reverify = (dir: string, ...extra: string[]) => fact(dir, "verify", join(dir, "report.html"), ...extra)
|
|
74
|
+
const blockers = (json: { defects: Defect[] }) => json.defects.filter((defect) => defect.code === "snapshot_hash_mismatch")
|
|
75
|
+
|
|
76
|
+
/** A page source S1 with F1 (--find) and Q1 (--match), filled and verified clean. */
|
|
77
|
+
async function pageSession() {
|
|
78
|
+
const dir = session()
|
|
79
|
+
expect((await snapshot(dir, "S1", "--html", file(dir, "page.html", PLAIN))).exitCode).toBe(0)
|
|
80
|
+
expect((await fact(dir, "add", "--source", "S1", "--find", "main deck spans", "--json")).exitCode).toBe(0)
|
|
81
|
+
expect((await fact(dir, "quote", "--source", "S1", "--match", "main deck spans 320 m", "--json")).exitCode).toBe(0)
|
|
82
|
+
expect((await report(dir, ["{{F1}}", "{{Q1}}"])).exitCode).toBe(0)
|
|
83
|
+
return dir
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
describe("converted text is never a raw snapshot", () => {
|
|
87
|
+
for (const [name, extra] of [["extracted.txt", ["--extractor", "pdftotext@24.02"]], ["page.md", []]] as const) {
|
|
88
|
+
test(`#given --text ${name} without --raw #when snapshotted #then it is converted-only and refuses measured rows`, async () => {
|
|
89
|
+
const dir = session()
|
|
90
|
+
const snap = await snapshot(dir, "S1", "--text", file(dir, name, "The span is 320 m."), ...extra)
|
|
91
|
+
expect(snap.json.data).toMatchObject({ kind: "converted-only", text_source: "converted" })
|
|
92
|
+
expect(existsSync(join(dir, "sources/S1/raw.bin"))).toBe(false)
|
|
93
|
+
expect((await fact(dir, "add", "--source", "S1", "--find", "span is", "--json")).json.error).toBe("snapshot_not_raw")
|
|
94
|
+
const quoted = await fact(dir, "quote", "--source", "S1", "--match", "The span is 320 m.", "--json")
|
|
95
|
+
expect(quoted.json.defects.map((defect: Defect) => defect.code)).toEqual(["quote_converted_only"])
|
|
96
|
+
expect(quoted.json.data.row.locator).toMatchObject({ artifact: "text.txt", unit: "bytes", char_start: 0, char_end: 18 })
|
|
97
|
+
const text = await fact(dir, "quote", "--source", "S1", "--match", "span is 320")
|
|
98
|
+
expect(text.text).toContain("warning quote_converted_only")
|
|
99
|
+
})
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
test("#given --raw page bytes with --text #when --find and --table run #then they use the page's text nodes", async () => {
|
|
103
|
+
const dir = session()
|
|
104
|
+
const snap = await snapshot(dir, "S1", "--raw", file(dir, "page.html", PAGE), "--text", file(dir, "aid.txt", "aid"), "--extractor", "reader@1")
|
|
105
|
+
expect(snap.json.data.text_source).toBe("html")
|
|
106
|
+
expect((await fact(dir, "add", "--source", "S1", "--find", "main deck spans", "--json")).json.data.row.value).toBe("320")
|
|
107
|
+
const cell = await fact(dir, "add", "--source", "S1", "--table", "sizes", "--row", "Deck", "--col", "Length", "--json")
|
|
108
|
+
expect([cell.json.data.row.value, cell.json.data.row.quote]).toEqual(["320", "320 m"])
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
test("#given --raw non-HTML bytes with a declared --text extraction #when added, verified, then the extraction edited #then verify blocks", async () => {
|
|
112
|
+
const dir = session()
|
|
113
|
+
const snap = await snapshot(dir, "S1", "--raw", file(dir, "doc.pdf", Buffer.from([0x25, 0x50, 0x44, 0x46, 0x00, 0xff])), "--text", file(dir, "doc.txt", "The span is 320 m."), "--extractor", "pdftotext@24.02")
|
|
114
|
+
expect(snap.json.data.text_source).toBe("declared")
|
|
115
|
+
const added = await fact(dir, "add", "--source", "S1", "--find", "span is", "--json")
|
|
116
|
+
expect(added.json.data.row.locator).toMatchObject({ artifact: "text.txt", unit: "bytes" })
|
|
117
|
+
expect((await report(dir, ["{{F1}}"])).exitCode).toBe(0)
|
|
118
|
+
writeFileSync(join(dir, "sources/S1/text.txt"), "The span is 330 m.")
|
|
119
|
+
expect(blockers((await reverify(dir)).json).length).toBeGreaterThan(0)
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
test("#given --raw with --html or --url #when snapshotted #then it is a usage error, not silently ignored", async () => {
|
|
123
|
+
const dir = session()
|
|
124
|
+
const page = file(dir, "page.html", PAGE)
|
|
125
|
+
await expect(snapshot(dir, "S1", "--html", page, "--raw", page)).rejects.toThrow("--raw goes alone or with --text")
|
|
126
|
+
await expect(snapshot(dir, "S1", "--url", "https://example.invalid/", "--raw", page)).rejects.toThrow("--raw goes alone or with --text")
|
|
127
|
+
})
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
// chmod 000 does not stop the owner reading a file on Windows (no POSIX mode bits), so there the same
|
|
131
|
+
// fail-closed condition is produced the way an unreadable path occurs on win32: raw.bin is replaced by
|
|
132
|
+
// a directory, and reading it fails (EISDIR). Both paths must make verify exit 1 with the cause.
|
|
133
|
+
const unreadableOn = (platform: string) => (dir: string) => {
|
|
134
|
+
const raw = join(dir, "sources/S1/raw.bin")
|
|
135
|
+
if (platform !== "win32") return chmodSync(raw, 0o000)
|
|
136
|
+
unlinkSync(raw)
|
|
137
|
+
mkdirSync(raw)
|
|
138
|
+
}
|
|
139
|
+
const unreadable = unreadableOn(process.platform)
|
|
140
|
+
const UNREADABLE_CAUSE = process.platform === "win32" ? "raw.bin is not a readable file" : "raw.bin is unreadable (EACCES)"
|
|
141
|
+
|
|
142
|
+
describe("fact verify fails closed when a cited snapshot cannot be read", () => {
|
|
143
|
+
const cases: [string, (dir: string) => void, string[], string][] = [
|
|
144
|
+
["the sources dir is removed", (dir) => rmSync(join(dir, "sources"), { recursive: true }), [], "meta.json is missing"],
|
|
145
|
+
["meta.json is removed", (dir) => unlinkSync(join(dir, "sources/S1/meta.json")), [], "meta.json is missing"],
|
|
146
|
+
["--sources names a missing dir", () => {}, ["--sources", "/nonexistent/sources"], "meta.json is missing"],
|
|
147
|
+
["raw.bin is unreadable", unreadable, [], UNREADABLE_CAUSE],
|
|
148
|
+
]
|
|
149
|
+
for (const [name, damage, extra, cause] of cases) {
|
|
150
|
+
test(`#given ${name} #when verified #then every cited row gets the blocker with the cause and a record is written`, async () => {
|
|
151
|
+
const dir = await pageSession()
|
|
152
|
+
rmSync(join(dir, "fidelity-defects.json"))
|
|
153
|
+
damage(dir)
|
|
154
|
+
const result = await reverify(dir, ...extra)
|
|
155
|
+
expect(result.exitCode).toBe(1)
|
|
156
|
+
expect(blockers(result.json).map((defect) => defect.message)).toEqual([expect.stringContaining(cause), expect.stringContaining(cause)])
|
|
157
|
+
expect(existsSync(join(dir, "fidelity-defects.json"))).toBe(true)
|
|
158
|
+
})
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
test("#given the win32 emulation of an unreadable raw.bin on any platform #when verified #then every cited row gets the blocker", async () => {
|
|
162
|
+
const dir = await pageSession()
|
|
163
|
+
unreadableOn("win32")(dir)
|
|
164
|
+
const result = await reverify(dir)
|
|
165
|
+
expect(result.exitCode).toBe(1)
|
|
166
|
+
expect(blockers(result.json).map((defect) => defect.message)).toEqual([expect.stringContaining("raw.bin is not a readable file"), expect.stringContaining("raw.bin is not a readable file")])
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
test("#given one of two sources unreadable #when verified #then only its rows are flagged and the healthy row stays clean", async () => {
|
|
170
|
+
const dir = await pageSession()
|
|
171
|
+
expect((await snapshot(dir, "S2", "--html", file(dir, "two.html", "<p>The side deck spans 210 m.</p>"))).exitCode).toBe(0)
|
|
172
|
+
expect((await fact(dir, "add", "--source", "S2", "--find", "side deck spans", "--json")).exitCode).toBe(0)
|
|
173
|
+
expect((await report(dir, ["{{F1}}", "{{F2:S2}}"])).exitCode).toBe(0)
|
|
174
|
+
unlinkSync(join(dir, "sources/S1/meta.json"))
|
|
175
|
+
const result = await reverify(dir)
|
|
176
|
+
expect(result.json.defects.map((defect: Defect) => defect.message.split(":")[0])).toEqual(["F1"])
|
|
177
|
+
})
|
|
178
|
+
|
|
179
|
+
test("#given a legacy text.txt row whose text.txt is deleted #when verified #then it blocks", async () => {
|
|
180
|
+
const dir = await legacySession()
|
|
181
|
+
const sources = join(dir, "sources/S1")
|
|
182
|
+
unlinkSync(join(sources, "text.txt"))
|
|
183
|
+
expect(blockers((await reverify(dir)).json)[0]?.message).toContain("text.txt is missing")
|
|
184
|
+
})
|
|
185
|
+
|
|
186
|
+
test("#given a byte-located row whose raw.bin is deleted #when verified #then it blocks naming raw.bin", async () => {
|
|
187
|
+
const dir = await pageSession()
|
|
188
|
+
unlinkSync(join(dir, "sources/S1/raw.bin"))
|
|
189
|
+
expect(blockers((await reverify(dir)).json).map((defect) => defect.message)).toEqual([expect.stringContaining("raw.bin is missing"), expect.stringContaining("raw.bin is missing")])
|
|
190
|
+
})
|
|
191
|
+
|
|
192
|
+
test("#given a converted-only quote whose text.txt is deleted #when verified #then it blocks", async () => {
|
|
193
|
+
const dir = session()
|
|
194
|
+
await snapshot(dir, "S1", "--text", file(dir, "aid.txt", "The span is 320 m."))
|
|
195
|
+
await fact(dir, "quote", "--source", "S1", "--match", "The span is 320 m.", "--json")
|
|
196
|
+
const clean = await report(dir, ["{{Q1}}"])
|
|
197
|
+
expect([clean.exitCode, clean.json.defects.map((defect: Defect) => defect.code)]).toEqual([0, ["quote_converted_only"]])
|
|
198
|
+
unlinkSync(join(dir, "sources/S1/text.txt"))
|
|
199
|
+
expect(blockers((await reverify(dir)).json)[0]?.message).toContain("text.txt is missing")
|
|
200
|
+
})
|
|
201
|
+
})
|
|
202
|
+
|
|
203
|
+
describe("source refresh is the one explicit repin", () => {
|
|
204
|
+
test("#given a re-captured page with the quote moved #when source refresh runs #then verify reports relocated with old and new offsets", async () => {
|
|
205
|
+
const dir = await pageSession()
|
|
206
|
+
const moved = PLAIN.replace("<body>", "<body><p>A new preface paragraph.</p>")
|
|
207
|
+
expect((await snapshot(dir, "S1", "--html", file(dir, "page2.html", moved))).json.error).toBe("snapshot_exists")
|
|
208
|
+
const refreshed = await run(["source", "refresh", "S1", "--html", join(dir, "page2.html"), "--session-dir", dir])
|
|
209
|
+
expect(refreshed.exitCode).toBe(0)
|
|
210
|
+
expect(refreshed.json.data.rows.map((row: { status: string }) => row.status)).toEqual(["relocated", "relocated"])
|
|
211
|
+
const result = await reverify(dir)
|
|
212
|
+
expect(result.exitCode).toBe(0)
|
|
213
|
+
const offset = Buffer.byteLength("<p>A new preface paragraph.</p>")
|
|
214
|
+
const before = readLedger(dir).rows[0].locator
|
|
215
|
+
expect(result.json.defects).toContainEqual(expect.objectContaining({ code: "relocated", severity: "minor", message: expect.stringContaining(`${before.char_start}:${before.char_end} -> ${before.char_start + offset}:${before.char_end + offset}`) }))
|
|
216
|
+
})
|
|
217
|
+
|
|
218
|
+
test("#given a re-captured page whose value changed #when source refresh runs #then the row is not re-anchored and verify blocks", async () => {
|
|
219
|
+
const dir = await pageSession()
|
|
220
|
+
const refreshed = await run(["source", "refresh", "S1", "--html", file(dir, "page3.html", PLAIN.replace("320", "330")), "--session-dir", dir])
|
|
221
|
+
expect(refreshed.exitCode).toBe(1)
|
|
222
|
+
expect(refreshed.json.defects.map((defect: Defect) => defect.code)).toEqual(["quote_not_verbatim", "quote_not_verbatim"])
|
|
223
|
+
expect(blockers((await reverify(dir)).json).length).toBe(2)
|
|
224
|
+
})
|
|
225
|
+
|
|
226
|
+
test("#given a legacy row whose source is refreshed to a page that no longer says it #when refresh then verify run #then both exit 1 naming F1", async () => {
|
|
227
|
+
const dir = await legacySession()
|
|
228
|
+
const refreshed = await run(["source", "refresh", "S1", "--html", file(dir, "page999.html", "<p>Span is 999 m.</p>"), "--session-dir", dir])
|
|
229
|
+
expect(refreshed.exitCode).toBe(1)
|
|
230
|
+
expect(refreshed.json.data.rows).toEqual([{ fact_id: "F1", status: "legacy_not_reanchored" }])
|
|
231
|
+
const result = await reverify(dir)
|
|
232
|
+
expect(result.exitCode).toBe(1)
|
|
233
|
+
expect(blockers(result.json).map((defect) => [defect.message.split(":")[0], defect.message.includes("no longer the legacy text.txt snapshot")])).toEqual([["F1", true]])
|
|
234
|
+
})
|
|
235
|
+
|
|
236
|
+
test("#given a legacy row whose legacy snapshot is replaced by another consistent one #when verified #then the pin mismatch blocks F1", async () => {
|
|
237
|
+
const dir = await legacySession()
|
|
238
|
+
writeLegacy(join(dir, "sources/S1"), "Span is 999 m.")
|
|
239
|
+
const result = await reverify(dir)
|
|
240
|
+
expect(result.exitCode).toBe(1)
|
|
241
|
+
expect(blockers(result.json).map((defect) => defect.message)).toEqual([expect.stringMatching(/^F1: snapshot S1 holds \w+ but the legacy row pins \w+/)])
|
|
242
|
+
})
|
|
243
|
+
})
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
// source-refresh.mjs - `source refresh <S<n>>`: the one explicit repin of a source snapshot.
|
|
2
|
+
//
|
|
3
|
+
// Re-fetches meta.url (or reads the new capture given with the source snapshot input flags),
|
|
4
|
+
// replaces the snapshot, and re-anchors every active byte-located ledger row on that source by its
|
|
5
|
+
// exact quote plus its stored context (raw-locator.mjs reanchor): a row found once gets the new
|
|
6
|
+
// snapshot_sha256 and keeps its old locator, so `fact verify` then reports minor `relocated` with
|
|
7
|
+
// the old and new byte offsets; a row whose quote is gone keeps its old pin, is reported as
|
|
8
|
+
// quote_not_verbatim here, and stays a snapshot_hash_mismatch blocker until it is retracted or
|
|
9
|
+
// re-added. A legacy text.txt row on the source (no byte locator) cannot be re-anchored: it is
|
|
10
|
+
// listed as legacy_not_reanchored and stays a blocker. Exit 0 when every row re-anchored, 1 otherwise. node: builtins and sibling imports only.
|
|
11
|
+
|
|
12
|
+
import { writeFileSync } from "node:fs"
|
|
13
|
+
import { join } from "node:path"
|
|
14
|
+
|
|
15
|
+
import { CliError, parseArgs } from "./cli-support.mjs"
|
|
16
|
+
import { DEFECT_CODES } from "./contracts.mjs"
|
|
17
|
+
import { readLedger, writeLedger } from "./fact-ledger.mjs"
|
|
18
|
+
import { reanchor } from "./raw-locator.mjs"
|
|
19
|
+
import { acquire, sessionDirOf, snapshotOf } from "./source-commands.mjs"
|
|
20
|
+
import { writeSnapshot } from "./source-snapshot.mjs"
|
|
21
|
+
|
|
22
|
+
export const REFRESH_USAGE =
|
|
23
|
+
"usage: source refresh S<n> [--url <u> | --html <file> | --raw <file> [--text <file> --extractor NAME@VERSION] | --text <file> | --command '<argv json>' --cwd DIR] [--charset NAME] --session-dir D (no input flag re-fetches the source's http(s) url)"
|
|
24
|
+
|
|
25
|
+
const INPUTS = ["url", "html", "raw", "text", "command"]
|
|
26
|
+
|
|
27
|
+
const defect = (code, message) => ({ code, severity: DEFECT_CODES[code].severity, integrity: DEFECT_CODES[code].integrity, message })
|
|
28
|
+
|
|
29
|
+
export async function runRefresh(argv) {
|
|
30
|
+
const args = parseArgs(argv)
|
|
31
|
+
const id = args.positionals[0] ?? args.flags.id
|
|
32
|
+
if (!id || id === true) throw new CliError(REFRESH_USAGE)
|
|
33
|
+
const sessionDir = sessionDirOf(args.flags)
|
|
34
|
+
const before = snapshotOf(sessionDir, id)
|
|
35
|
+
const flags = { ...args.flags }
|
|
36
|
+
if (!INPUTS.some((name) => flags[name] !== undefined)) {
|
|
37
|
+
if (!/^https?:\/\//i.test(String(before.meta.url))) throw new CliError(`${id} was not fetched from an http(s) url; pass its new capture. ${REFRESH_USAGE}`)
|
|
38
|
+
flags.url = before.meta.url
|
|
39
|
+
}
|
|
40
|
+
const { receipt, ...input } = await acquire(flags)
|
|
41
|
+
input.kind ??= flags.kind ?? before.meta.kind
|
|
42
|
+
input.provenance = flags.provenance ?? before.meta.provenance
|
|
43
|
+
input.revision = flags.revision ?? before.meta.revision ?? undefined
|
|
44
|
+
let written
|
|
45
|
+
try {
|
|
46
|
+
written = writeSnapshot(join(sessionDir, "sources"), id, input, { replace: true })
|
|
47
|
+
} catch (error) {
|
|
48
|
+
if (error instanceof TypeError) throw new CliError(error.message)
|
|
49
|
+
throw error
|
|
50
|
+
}
|
|
51
|
+
if (receipt) writeFileSync(join(written.dir, "command.json"), `${JSON.stringify(receipt, null, 2)}\n`)
|
|
52
|
+
const after = snapshotOf(sessionDir, id)
|
|
53
|
+
const ledger = readLedger(sessionDir)
|
|
54
|
+
const rows = []
|
|
55
|
+
const defects = []
|
|
56
|
+
for (const row of ledger.rows) {
|
|
57
|
+
if (row.source_id !== id || row.status !== "active") continue
|
|
58
|
+
if (row.locator?.unit === undefined) {
|
|
59
|
+
rows.push({ fact_id: row.fact_id, status: "legacy_not_reanchored" })
|
|
60
|
+
defects.push(defect("snapshot_hash_mismatch", `${row.fact_id}: a legacy text.txt row has no byte locator to re-anchor on refreshed ${id}; add it again from the new snapshot or retract it`))
|
|
61
|
+
continue
|
|
62
|
+
}
|
|
63
|
+
if (row.locator.unit !== "bytes") continue
|
|
64
|
+
const anchor = row.locator.artifact === after.view.artifact ? reanchor(after.view, row) : null
|
|
65
|
+
const span = `${row.locator.char_start}:${row.locator.char_end}`
|
|
66
|
+
if (!anchor) {
|
|
67
|
+
rows.push({ fact_id: row.fact_id, status: "not_found", from: span })
|
|
68
|
+
defects.push(defect("quote_not_verbatim", `${row.fact_id}: its quote and context are not found once in refreshed ${id}; retract it or add it again from the new snapshot`))
|
|
69
|
+
continue
|
|
70
|
+
}
|
|
71
|
+
row.snapshot_sha256 = after.sha256
|
|
72
|
+
rows.push({ fact_id: row.fact_id, status: anchor.relocated ? "relocated" : "unchanged", from: span, to: `${anchor.start}:${anchor.end}` })
|
|
73
|
+
if (anchor.relocated) defects.push(defect("relocated", `${row.fact_id}: ${span} -> ${anchor.start}:${anchor.end} (bytes of ${id}/${after.view.artifact})`))
|
|
74
|
+
}
|
|
75
|
+
writeLedger(sessionDir, ledger)
|
|
76
|
+
const failed = rows.filter((row) => row.status === "not_found" || row.status === "legacy_not_reanchored").length
|
|
77
|
+
const exitCode = failed > 0 ? 1 : 0
|
|
78
|
+
const data = { source_id: id, previous_sha256: before.sha256, snapshot_sha256: after.sha256, text_source: after.view.mode, rows }
|
|
79
|
+
const json = { command: "source refresh", status: exitCode === 0 ? "pass" : "fail", exitCode, defects, data }
|
|
80
|
+
const moved = rows.filter((row) => row.status === "relocated").length
|
|
81
|
+
return { json, summary: `source refresh: ${id} ${before.sha256.slice(0, 12)} -> ${after.sha256.slice(0, 12)}; ${rows.length - failed} row(s) re-anchored (${moved} relocated), ${failed} not re-anchored`, exitCode }
|
|
82
|
+
}
|