oh-my-opencode 5.1.48 → 5.1.51
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agents/refusal-rule.d.ts +7 -0
- package/dist/cli/index.js +1194 -752
- package/dist/cli/service-auth/accounts-gate.d.ts +11 -0
- package/dist/cli/service-auth/commands.d.ts +2 -4
- package/dist/cli/service-auth/index.d.ts +1 -1
- package/dist/cli/service-auth/keystore.d.ts +26 -2
- package/dist/cli/service-auth/protocol.d.ts +7 -1
- package/dist/cli/service-auth/session.d.ts +6 -0
- package/dist/cli/service-auth/signed-out-marker.d.ts +11 -0
- package/dist/cli-node/index.js +1194 -752
- package/dist/config/schema/hooks.d.ts +1 -0
- package/dist/create-hooks.d.ts +1 -0
- package/dist/features/boulder-state/storage.d.ts +1 -1
- package/dist/features/boulder-state/types.d.ts +1 -1
- package/dist/hooks/atlas/idle-continuation.d.ts +2 -1
- package/dist/hooks/atlas/types.d.ts +9 -1
- package/dist/hooks/index.d.ts +1 -0
- package/dist/hooks/momus-review-persistence/hook.d.ts +19 -0
- package/dist/hooks/momus-review-persistence/index.d.ts +1 -0
- package/dist/hooks/momus-review-persistence/review-resolution.d.ts +20 -0
- package/dist/hooks/prometheus-md-only/constants.d.ts +2 -0
- package/dist/hooks/prometheus-md-only/fixtures/denial-log.d.ts +1 -0
- package/dist/hooks/prometheus-md-only/fixtures/guarded-hashline.d.ts +1 -0
- package/dist/hooks/prometheus-md-only/fixtures/invoke-before.d.ts +7 -0
- package/dist/hooks/prometheus-md-only/fixtures/opencode-patch-parser.d.ts +2 -0
- package/dist/hooks/prometheus-md-only/hook.d.ts +5 -1
- package/dist/hooks/prometheus-md-only/path-policy.d.ts +2 -9
- package/dist/hooks/prometheus-md-only/vendor/opencode-patch-parser.d.ts +22 -0
- package/dist/index.js +2322 -1045
- package/dist/plugin/hooks/create-core-hooks.d.ts +1 -0
- package/dist/plugin/hooks/create-tool-guard-hooks.d.ts +2 -1
- package/dist/shared/posthog-activity-state.d.ts +1 -0
- package/dist/shared/posthog.d.ts +1 -2
- package/dist/shared/resolve-file-write-path.d.ts +6 -0
- package/dist/skills/ast-grep/README.md +1 -1
- package/dist/skills/ast-grep/install.ps1 +5 -3
- package/dist/skills/ast-grep/install.sh +4 -5
- package/dist/skills/ast-grep/references/install.md +2 -2
- package/dist/skills/ast-grep/scripts/ast_grep_helper.py +13 -3
- package/dist/skills/browser/runtime/omowright/manifest.json +1 -1
- package/dist/skills/coding-agent-sessions/AGENTS.md +5 -4
- package/dist/skills/coding-agent-sessions/SKILL.md +11 -11
- package/dist/skills/coding-agent-sessions/references/all-platforms.md +2 -2
- package/dist/skills/coding-agent-sessions/references/codex.md +4 -4
- package/dist/skills/coding-agent-sessions/scripts/agent_sessions/cli.py +1 -1
- package/dist/skills/coding-agent-sessions/scripts/find-agent-sessions.py +6 -3
- package/dist/skills/frontend/ATTRIBUTION.md +1 -1
- package/dist/skills/frontend/SKILL.md +34 -12
- package/dist/skills/frontend/references/design/README.md +2 -2
- package/dist/skills/frontend/references/design/_INDEX.md +2 -1
- package/dist/skills/frontend/references/design/component-catalogs.md +14 -10
- package/dist/skills/frontend/references/design/design-system-architecture.md +19 -6
- package/dist/skills/frontend/references/design/grounding.md +29 -0
- package/dist/skills/frontend/references/ui-ux-db/scripts/design_system.py +2 -0
- package/dist/skills/frontend/references/ui-ux-db/scripts/search.py +2 -0
- package/dist/skills/ultimate-browsing/SKILL.md +5 -4
- package/dist/skills/ultimate-browsing/engine/AGENTS.md +2 -2
- package/dist/skills/ultimate-browsing/engine/__main__.py +9 -6
- package/dist/skills/ultimate-browsing/engine/summary.py +1 -1
- package/dist/skills/ultimate-browsing/references/insane-search/README.md +4 -4
- package/dist/skills/ultimate-browsing/scripts/extract_cookies.py +4 -2
- package/dist/skills/ulw-research/AGENTS.md +8 -4
- package/dist/skills/ulw-research/SKILL.md +7 -316
- package/dist/skills/ulw-research/references/deliverable-phase.md +23 -21
- package/dist/skills/ulw-research/references/fact-ledger-grammar.md +7 -3
- package/dist/skills/ulw-research/references/fact-ledger.md +42 -8
- package/dist/skills/ulw-research/references/fidelity-claim-kinds.md +1 -1
- package/dist/skills/ulw-research/references/markdown-blocks.md +12 -0
- package/dist/skills/ulw-research/references/report-gates.md +12 -5
- package/dist/skills/ulw-research/references/research-collection.md +195 -0
- package/dist/skills/ulw-research/references/research-delivery.md +123 -0
- package/dist/skills/ulw-research/scripts/claim-kinds.mjs +33 -5
- package/dist/skills/ulw-research/scripts/claim-kinds.test.ts +37 -2
- package/dist/skills/ulw-research/scripts/claim-prefix.test.ts +76 -0
- package/dist/skills/ulw-research/scripts/contracts-validation.test.ts +83 -0
- package/dist/skills/ulw-research/scripts/contracts.mjs +13 -4
- package/dist/skills/ulw-research/scripts/contracts.test.ts +6 -110
- package/dist/skills/ulw-research/scripts/conversion-altered.test.ts +154 -0
- package/dist/skills/ulw-research/scripts/conversion-originals.test.ts +297 -0
- package/dist/skills/ulw-research/scripts/conversion-probe-classes.test.ts +361 -0
- package/dist/skills/ulw-research/scripts/conversion-probe.mjs +229 -0
- package/dist/skills/ulw-research/scripts/conversion-probe.test.ts +218 -0
- package/dist/skills/ulw-research/scripts/copy-returns-polish.test.ts +4 -3
- package/dist/skills/ulw-research/scripts/css-hiding.mjs +158 -0
- package/dist/skills/ulw-research/scripts/css-lite.mjs +4 -2
- package/dist/skills/ulw-research/scripts/delivery-barrier-meaning.test.ts +186 -0
- package/dist/skills/ulw-research/scripts/delivery-barrier.test.ts +40 -87
- package/dist/skills/ulw-research/scripts/entities.mjs +13 -4
- package/dist/skills/ulw-research/scripts/fact-commands-fill.mjs +11 -1
- package/dist/skills/ulw-research/scripts/fact-commands-meaning.mjs +13 -9
- package/dist/skills/ulw-research/scripts/fact-commands-polish.mjs +40 -18
- package/dist/skills/ulw-research/scripts/fact-commands-verify.mjs +25 -8
- package/dist/skills/ulw-research/scripts/fact-commands.mjs +35 -16
- package/dist/skills/ulw-research/scripts/fact-derive-2.test.ts +149 -0
- package/dist/skills/ulw-research/scripts/fact-derive.mjs +7 -2
- package/dist/skills/ulw-research/scripts/fact-derive.test.ts +7 -192
- package/dist/skills/ulw-research/scripts/fact-fill.mjs +6 -4
- package/dist/skills/ulw-research/scripts/fact-ledger-kinds.mjs +6 -7
- package/dist/skills/ulw-research/scripts/fact-ledger-kinds.test.ts +2 -1
- package/dist/skills/ulw-research/scripts/fact-ledger-legacy.test.ts +2 -1
- package/dist/skills/ulw-research/scripts/fact-locate.mjs +15 -9
- package/dist/skills/ulw-research/scripts/fact-unit-dimensions-2.test.ts +149 -0
- package/dist/skills/ulw-research/scripts/fact-unit-dimensions.test.ts +4 -168
- package/dist/skills/ulw-research/scripts/fact-unit-rules.test.ts +3 -229
- package/dist/skills/ulw-research/scripts/fidelity-claims.mjs +4 -3
- package/dist/skills/ulw-research/scripts/fidelity-csv.mjs +32 -0
- package/dist/skills/ulw-research/scripts/fidelity-lexicon.mjs +1 -1
- package/dist/skills/ulw-research/scripts/fidelity-originals.mjs +161 -0
- package/dist/skills/ulw-research/scripts/fidelity-policy.mjs +4 -4
- package/dist/skills/ulw-research/scripts/fidelity-records.mjs +81 -14
- package/dist/skills/ulw-research/scripts/fidelity-snapshot-readability.mjs +49 -0
- package/dist/skills/ulw-research/scripts/fidelity-sources.mjs +76 -0
- package/dist/skills/ulw-research/scripts/fidelity-trace.mjs +25 -11
- package/dist/skills/ulw-research/scripts/fidelity-value-unit.mjs +202 -0
- package/dist/skills/ulw-research/scripts/fidelity-values.mjs +40 -39
- package/dist/skills/ulw-research/scripts/fidelity-values.test.ts +8 -29
- package/dist/skills/ulw-research/scripts/gates-fidelity.mjs +8 -6
- package/dist/skills/ulw-research/scripts/gates-static-headings.test.ts +17 -0
- package/dist/skills/ulw-research/scripts/gates-static.mjs +10 -1
- package/dist/skills/ulw-research/scripts/html-lite.mjs +6 -2
- package/dist/skills/ulw-research/scripts/html-markup.mjs +2 -2
- package/dist/skills/ulw-research/scripts/html-text-map.mjs +140 -0
- package/dist/skills/ulw-research/scripts/html-text-map.test.ts +148 -0
- package/dist/skills/ulw-research/scripts/markdown-closers.mjs +126 -0
- package/dist/skills/ulw-research/scripts/md-lite-boundaries.test.ts +46 -0
- package/dist/skills/ulw-research/scripts/md-lite.mjs +1 -1
- package/dist/skills/ulw-research/scripts/number-grammar.mjs +33 -0
- package/dist/skills/ulw-research/scripts/number-grammar.test.ts +36 -0
- package/dist/skills/ulw-research/scripts/numerals.mjs +18 -7
- package/dist/skills/ulw-research/scripts/numerals.test.ts +3 -0
- package/dist/skills/ulw-research/scripts/outcome-start.mjs +66 -0
- package/dist/skills/ulw-research/scripts/outcome-start.test.ts +109 -0
- package/dist/skills/ulw-research/scripts/outcome.mjs +11 -22
- package/dist/skills/ulw-research/scripts/page-source-locator.test.ts +187 -0
- package/dist/skills/ulw-research/scripts/placeholder.mjs +18 -6
- package/dist/skills/ulw-research/scripts/polish-apply.mjs +3 -2
- package/dist/skills/ulw-research/scripts/polish-markup.test.ts +15 -1
- package/dist/skills/ulw-research/scripts/polish-partial.test.ts +42 -0
- package/dist/skills/ulw-research/scripts/raw-decode.mjs +110 -0
- package/dist/skills/ulw-research/scripts/raw-locator.mjs +47 -0
- package/dist/skills/ulw-research/scripts/raw-snapshot.test.ts +139 -0
- package/dist/skills/ulw-research/scripts/report-tools-2.test.ts +233 -0
- package/dist/skills/ulw-research/scripts/report-tools-3.test.ts +141 -0
- package/dist/skills/ulw-research/scripts/report-tools-4.test.ts +135 -0
- package/dist/skills/ulw-research/scripts/report-tools-5.test.ts +231 -0
- package/dist/skills/ulw-research/scripts/report-tools-commands.mjs +1 -1
- package/dist/skills/ulw-research/scripts/report-tools.mjs +7 -1
- package/dist/skills/ulw-research/scripts/report-tools.test.ts +5 -800
- package/dist/skills/ulw-research/scripts/snapshot-readability.test.ts +116 -0
- package/dist/skills/ulw-research/scripts/source-commands.mjs +79 -26
- package/dist/skills/ulw-research/scripts/source-integrity.test.ts +243 -0
- package/dist/skills/ulw-research/scripts/source-refresh.mjs +82 -0
- package/dist/skills/ulw-research/scripts/source-snapshot.mjs +73 -69
- package/dist/skills/ulw-research/scripts/source-snapshot.test.ts +7 -4
- package/dist/skills/ulw-research/scripts/source-view.mjs +89 -0
- package/dist/skills/ulw-research/scripts/sources-citation.mjs +203 -0
- package/dist/skills/ulw-research/scripts/sources-citation.test.ts +236 -0
- package/dist/skills/ulw-research/scripts/tests/contracts-support.ts +147 -0
- package/dist/skills/ulw-research/scripts/tests/delivery-barrier-session.ts +59 -0
- package/dist/skills/ulw-research/scripts/tests/derive-clock.ts +4 -0
- package/dist/skills/ulw-research/scripts/tests/fact-derive-support.ts +65 -0
- package/dist/skills/ulw-research/scripts/tests/fact-unit-dimensions-support.ts +41 -0
- package/dist/skills/ulw-research/scripts/tests/fact-unit-rules-support.ts +241 -0
- package/dist/skills/ulw-research/scripts/tests/fidelity-values-support.ts +30 -0
- package/dist/skills/ulw-research/scripts/tests/fixtures/conversion-probe.html +14 -0
- package/dist/skills/ulw-research/scripts/tests/fixtures/sources-shapes.html +20 -0
- package/dist/skills/ulw-research/scripts/tests/numeral-classes.ts +196 -0
- package/dist/skills/ulw-research/scripts/tests/report-tools-support.ts +182 -0
- package/dist/skills/ulw-research/scripts/tests/value-mode-support.ts +48 -0
- package/dist/skills/ulw-research/scripts/units.mjs +14 -3
- package/dist/skills/ulw-research/scripts/units.test.ts +10 -0
- package/dist/skills/ulw-research/scripts/value-mode-unit-classes.test.ts +255 -0
- package/dist/skills/ulw-research/scripts/value-mode-unit.test.ts +179 -0
- package/dist/skills/ulw-research/scripts/value-rules-cli.test.ts +157 -0
- package/dist/skills/ulw-research/scripts/value-rules-followups.test.ts +72 -0
- package/dist/skills/ulw-research/scripts/value-rules-priority.test.ts +80 -0
- package/dist/tools/delegate-task/executor-types.d.ts +1 -0
- package/dist/tools/delegate-task/sync-session-existence.d.ts +4 -0
- package/dist/tools/delegate-task/types.d.ts +1 -0
- package/dist/tools/skill/description-formatter.d.ts +2 -1
- package/dist/tools/skill/types.d.ts +6 -0
- package/dist/tui.js +131 -33
- package/package.json +18 -15
- package/packages/git-bash-mcp/package.json +1 -1
- package/packages/omo-codex/plugin/.codex-plugin/plugin.json +1 -1
- package/packages/omo-codex/plugin/README.md +1 -1
- package/packages/omo-codex/plugin/components/bootstrap/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/bootstrap/package.json +1 -1
- package/packages/omo-codex/plugin/components/comment-checker/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/comment-checker/package.json +1 -1
- package/packages/omo-codex/plugin/components/git-bash/hooks/hooks.json +2 -2
- package/packages/omo-codex/plugin/components/git-bash/package.json +1 -1
- package/packages/omo-codex/plugin/components/lazycodex-executor-verify/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/lazycodex-executor-verify/package.json +1 -1
- package/packages/omo-codex/plugin/components/lcx/skills/lcx-contribute-bug-fix/SKILL.md +51 -55
- package/packages/omo-codex/plugin/components/lcx/skills/lcx-contribute-bug-fix/agents/openai.yaml +1 -1
- package/packages/omo-codex/plugin/components/lcx/skills/lcx-report-bug/SKILL.md +7 -7
- package/packages/omo-codex/plugin/components/lcx/skills/lcx-report-bug/agents/openai.yaml +1 -1
- package/packages/omo-codex/plugin/components/lsp/dist/.omo-runtime-manifest.json +2 -2
- package/packages/omo-codex/plugin/components/lsp/hooks/hooks.json +2 -2
- package/packages/omo-codex/plugin/components/lsp/package.json +1 -1
- package/packages/omo-codex/plugin/components/rules/hooks/hooks.json +4 -4
- package/packages/omo-codex/plugin/components/rules/package.json +1 -1
- package/packages/omo-codex/plugin/components/teammode/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/teammode/package.json +2 -2
- package/packages/omo-codex/plugin/components/telemetry/README.md +2 -2
- package/packages/omo-codex/plugin/components/telemetry/dist/cli.js +52 -15
- package/packages/omo-codex/plugin/components/telemetry/dist/posthog.js +52 -19
- package/packages/omo-codex/plugin/components/telemetry/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/telemetry/package.json +1 -1
- package/packages/omo-codex/plugin/components/telemetry/src/codex-hook.ts +2 -9
- package/packages/omo-codex/plugin/components/telemetry/src/posthog.ts +4 -13
- package/packages/omo-codex/plugin/components/telemetry/src/product-identity.ts +0 -2
- package/packages/omo-codex/plugin/components/telemetry/test/codex-hook-diagnostics.test.ts +0 -3
- package/packages/omo-codex/plugin/components/telemetry/test/codex-hook.test.ts +3 -8
- package/packages/omo-codex/plugin/components/ultrawork/dist/cli.js +3 -0
- package/packages/omo-codex/plugin/components/ultrawork/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/ultrawork/package.json +1 -1
- package/packages/omo-codex/plugin/components/ultrawork/src/directive-content.ts +1 -1
- package/packages/omo-codex/plugin/components/ulw-execute-continuation/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/ulw-execute-continuation/package.json +1 -1
- package/packages/omo-codex/plugin/components/ulw-loop/directive.md +3 -0
- package/packages/omo-codex/plugin/components/ulw-loop/hooks/hooks.json +5 -5
- package/packages/omo-codex/plugin/components/ulw-loop/package.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-compact-resetting-git-bash-mcp-reminder.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-compact-resetting-lsp-diagnostics-cache.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-compact-resetting-project-rule-cache.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-checking-comments.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-checking-lsp-diagnostics.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-checking-thread-title-hygiene.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-matching-project-rules.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-recording-spawn-admission.json +1 -1
- package/packages/omo-codex/plugin/hooks/pre-tool-use-enforcing-unlimited-goal-budget.json +1 -1
- package/packages/omo-codex/plugin/hooks/pre-tool-use-guarding-ulw-loop-spawns.json +1 -1
- package/packages/omo-codex/plugin/hooks/pre-tool-use-recommending-git-bash-mcp.json +1 -1
- package/packages/omo-codex/plugin/hooks/session-start-checking-auto-update.json +1 -1
- package/packages/omo-codex/plugin/hooks/session-start-checking-bootstrap-provisioning.json +1 -1
- package/packages/omo-codex/plugin/hooks/session-start-loading-project-rules.json +1 -1
- package/packages/omo-codex/plugin/hooks/session-start-recording-session-telemetry.json +1 -1
- package/packages/omo-codex/plugin/hooks/stop-checking-ulw-execute-continuation.json +1 -1
- package/packages/omo-codex/plugin/hooks/stop-checking-ulw-loop-resume.json +1 -1
- package/packages/omo-codex/plugin/hooks/subagent-stop-verifying-lazycodex-executor-evidence.json +1 -1
- package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ultrawork-trigger.json +1 -1
- package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ulw-loop-steering.json +1 -1
- package/packages/omo-codex/plugin/hooks/user-prompt-submit-loading-project-rules.json +1 -1
- package/packages/omo-codex/plugin/package-lock.json +16 -16
- package/packages/omo-codex/plugin/package.json +1 -1
- package/packages/omo-codex/plugin/shared/package.json +1 -1
- package/packages/omo-codex/plugin/skills/ast-grep/README.md +1 -1
- package/packages/omo-codex/plugin/skills/ast-grep/install.ps1 +5 -3
- package/packages/omo-codex/plugin/skills/ast-grep/install.sh +4 -5
- package/packages/omo-codex/plugin/skills/ast-grep/references/install.md +2 -2
- package/packages/omo-codex/plugin/skills/ast-grep/scripts/ast_grep_helper.py +13 -3
- package/packages/omo-codex/plugin/skills/browser/runtime/omowright/manifest.json +1 -1
- package/packages/omo-codex/plugin/skills/coding-agent-sessions/AGENTS.md +5 -4
- package/packages/omo-codex/plugin/skills/coding-agent-sessions/SKILL.md +11 -11
- package/packages/omo-codex/plugin/skills/coding-agent-sessions/references/all-platforms.md +2 -2
- package/packages/omo-codex/plugin/skills/coding-agent-sessions/references/codex.md +4 -4
- package/packages/omo-codex/plugin/skills/coding-agent-sessions/scripts/agent_sessions/cli.py +1 -1
- package/packages/omo-codex/plugin/skills/coding-agent-sessions/scripts/find-agent-sessions.py +6 -3
- package/packages/omo-codex/plugin/skills/frontend/ATTRIBUTION.md +1 -1
- package/packages/omo-codex/plugin/skills/frontend/SKILL.md +34 -12
- package/packages/omo-codex/plugin/skills/frontend/references/design/README.md +2 -2
- package/packages/omo-codex/plugin/skills/frontend/references/design/_INDEX.md +2 -1
- package/packages/omo-codex/plugin/skills/frontend/references/design/component-catalogs.md +14 -10
- package/packages/omo-codex/plugin/skills/frontend/references/design/design-system-architecture.md +19 -6
- package/packages/omo-codex/plugin/skills/frontend/references/design/grounding.md +29 -0
- package/packages/omo-codex/plugin/skills/frontend/references/ui-ux-db/scripts/design_system.py +2 -0
- package/packages/omo-codex/plugin/skills/frontend/references/ui-ux-db/scripts/search.py +2 -0
- package/packages/omo-codex/plugin/skills/lcx-contribute-bug-fix/SKILL.md +51 -55
- package/packages/omo-codex/plugin/skills/lcx-contribute-bug-fix/agents/openai.yaml +1 -1
- package/packages/omo-codex/plugin/skills/lcx-report-bug/SKILL.md +7 -7
- package/packages/omo-codex/plugin/skills/lcx-report-bug/agents/openai.yaml +1 -1
- package/packages/omo-codex/plugin/skills/ultimate-browsing/SKILL.md +5 -4
- package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/AGENTS.md +2 -2
- package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/__main__.py +9 -6
- package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/summary.py +1 -1
- package/packages/omo-codex/plugin/skills/ultimate-browsing/references/insane-search/README.md +4 -4
- package/packages/omo-codex/plugin/skills/ultimate-browsing/scripts/extract_cookies.py +4 -2
- package/packages/omo-codex/plugin/skills/ultrawork/SKILL.md +3 -0
- package/packages/omo-codex/plugin/skills/ulw-research/AGENTS.md +8 -4
- package/packages/omo-codex/plugin/skills/ulw-research/SKILL.md +7 -316
- package/packages/omo-codex/plugin/skills/ulw-research/references/deliverable-phase.md +23 -21
- package/packages/omo-codex/plugin/skills/ulw-research/references/fact-ledger-grammar.md +7 -3
- package/packages/omo-codex/plugin/skills/ulw-research/references/fact-ledger.md +42 -8
- package/packages/omo-codex/plugin/skills/ulw-research/references/fidelity-claim-kinds.md +1 -1
- package/packages/omo-codex/plugin/skills/ulw-research/references/markdown-blocks.md +12 -0
- package/packages/omo-codex/plugin/skills/ulw-research/references/report-gates.md +12 -5
- package/packages/omo-codex/plugin/skills/ulw-research/references/research-collection.md +195 -0
- package/packages/omo-codex/plugin/skills/ulw-research/references/research-delivery.md +123 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/claim-kinds.mjs +33 -5
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/contracts.mjs +13 -4
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/conversion-probe.mjs +229 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/css-hiding.mjs +158 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/css-lite.mjs +4 -2
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/entities.mjs +13 -4
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-commands-fill.mjs +11 -1
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-commands-meaning.mjs +13 -9
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-commands-polish.mjs +40 -18
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-commands-verify.mjs +25 -8
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-commands.mjs +35 -16
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-derive.mjs +7 -2
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-fill.mjs +6 -4
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-ledger-kinds.mjs +6 -7
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-locate.mjs +15 -9
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-claims.mjs +4 -3
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-csv.mjs +32 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-lexicon.mjs +1 -1
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-originals.mjs +161 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-policy.mjs +4 -4
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-records.mjs +81 -14
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-snapshot-readability.mjs +49 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-sources.mjs +76 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-trace.mjs +25 -11
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-value-unit.mjs +202 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-values.mjs +40 -39
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/gates-fidelity.mjs +8 -6
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/gates-static.mjs +10 -1
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/html-lite.mjs +6 -2
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/html-markup.mjs +2 -2
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/html-text-map.mjs +140 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/markdown-closers.mjs +126 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/md-lite.mjs +1 -1
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/number-grammar.mjs +33 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/numerals.mjs +18 -7
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/outcome-start.mjs +66 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/outcome.mjs +11 -22
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/placeholder.mjs +18 -6
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/polish-apply.mjs +3 -2
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/raw-decode.mjs +110 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/raw-locator.mjs +47 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/report-tools-commands.mjs +1 -1
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/report-tools.mjs +7 -1
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/source-commands.mjs +79 -26
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/source-refresh.mjs +82 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/source-snapshot.mjs +73 -69
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/source-view.mjs +89 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/sources-citation.mjs +203 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/units.mjs +14 -3
- package/packages/omo-codex/plugin/test/payload-equivalence.test.mjs +6 -4
- package/packages/omo-codex/plugin/test/sync-skills.test.mjs +4 -3
- package/packages/omo-codex/scripts/install-dist/install-local.mjs +338 -237
- package/packages/prompts-core/prompts/ultrawork/codex.md +3 -0
- package/packages/shared-skills/skill-source-filter.mjs +6 -2
- package/packages/shared-skills/skills/ast-grep/README.md +1 -1
- package/packages/shared-skills/skills/ast-grep/install.ps1 +5 -3
- package/packages/shared-skills/skills/ast-grep/install.sh +4 -5
- package/packages/shared-skills/skills/ast-grep/references/install.md +2 -2
- package/packages/shared-skills/skills/ast-grep/scripts/ast_grep_helper.py +13 -3
- package/packages/shared-skills/skills/browser/runtime/omowright/manifest.json +1 -1
- package/packages/shared-skills/skills/coding-agent-sessions/AGENTS.md +5 -4
- package/packages/shared-skills/skills/coding-agent-sessions/SKILL.md +11 -11
- package/packages/shared-skills/skills/coding-agent-sessions/references/all-platforms.md +2 -2
- package/packages/shared-skills/skills/coding-agent-sessions/references/codex.md +4 -4
- package/packages/shared-skills/skills/coding-agent-sessions/scripts/agent_sessions/cli.py +1 -1
- package/packages/shared-skills/skills/coding-agent-sessions/scripts/find-agent-sessions.py +6 -3
- package/packages/shared-skills/skills/frontend/ATTRIBUTION.md +1 -1
- package/packages/shared-skills/skills/frontend/SKILL.md +34 -12
- package/packages/shared-skills/skills/frontend/references/design/README.md +2 -2
- package/packages/shared-skills/skills/frontend/references/design/_INDEX.md +2 -1
- package/packages/shared-skills/skills/frontend/references/design/component-catalogs.md +14 -10
- package/packages/shared-skills/skills/frontend/references/design/design-system-architecture.md +19 -6
- package/packages/shared-skills/skills/frontend/references/design/grounding.md +29 -0
- package/packages/shared-skills/skills/frontend/references/ui-ux-db/scripts/design_system.py +2 -0
- package/packages/shared-skills/skills/frontend/references/ui-ux-db/scripts/search.py +2 -0
- package/packages/shared-skills/skills/ultimate-browsing/SKILL.md +5 -4
- package/packages/shared-skills/skills/ultimate-browsing/engine/AGENTS.md +2 -2
- package/packages/shared-skills/skills/ultimate-browsing/engine/__main__.py +9 -6
- package/packages/shared-skills/skills/ultimate-browsing/engine/summary.py +1 -1
- package/packages/shared-skills/skills/ultimate-browsing/references/insane-search/README.md +4 -4
- package/packages/shared-skills/skills/ultimate-browsing/scripts/extract_cookies.py +4 -2
- package/packages/shared-skills/skills/ulw-research/AGENTS.md +8 -4
- package/packages/shared-skills/skills/ulw-research/SKILL.md +7 -316
- package/packages/shared-skills/skills/ulw-research/references/deliverable-phase.md +23 -21
- package/packages/shared-skills/skills/ulw-research/references/fact-ledger-grammar.md +7 -3
- package/packages/shared-skills/skills/ulw-research/references/fact-ledger.md +42 -8
- package/packages/shared-skills/skills/ulw-research/references/fidelity-claim-kinds.md +1 -1
- package/packages/shared-skills/skills/ulw-research/references/markdown-blocks.md +12 -0
- package/packages/shared-skills/skills/ulw-research/references/report-gates.md +12 -5
- package/packages/shared-skills/skills/ulw-research/references/research-collection.md +195 -0
- package/packages/shared-skills/skills/ulw-research/references/research-delivery.md +123 -0
- package/packages/shared-skills/skills/ulw-research/scripts/claim-kinds.mjs +33 -5
- package/packages/shared-skills/skills/ulw-research/scripts/claim-kinds.test.ts +37 -2
- package/packages/shared-skills/skills/ulw-research/scripts/claim-prefix.test.ts +76 -0
- package/packages/shared-skills/skills/ulw-research/scripts/contracts-validation.test.ts +83 -0
- package/packages/shared-skills/skills/ulw-research/scripts/contracts.mjs +13 -4
- package/packages/shared-skills/skills/ulw-research/scripts/contracts.test.ts +6 -110
- package/packages/shared-skills/skills/ulw-research/scripts/conversion-altered.test.ts +154 -0
- package/packages/shared-skills/skills/ulw-research/scripts/conversion-originals.test.ts +297 -0
- package/packages/shared-skills/skills/ulw-research/scripts/conversion-probe-classes.test.ts +361 -0
- package/packages/shared-skills/skills/ulw-research/scripts/conversion-probe.mjs +229 -0
- package/packages/shared-skills/skills/ulw-research/scripts/conversion-probe.test.ts +218 -0
- package/packages/shared-skills/skills/ulw-research/scripts/copy-returns-polish.test.ts +4 -3
- package/packages/shared-skills/skills/ulw-research/scripts/css-hiding.mjs +158 -0
- package/packages/shared-skills/skills/ulw-research/scripts/css-lite.mjs +4 -2
- package/packages/shared-skills/skills/ulw-research/scripts/delivery-barrier-meaning.test.ts +186 -0
- package/packages/shared-skills/skills/ulw-research/scripts/delivery-barrier.test.ts +40 -87
- package/packages/shared-skills/skills/ulw-research/scripts/entities.mjs +13 -4
- package/packages/shared-skills/skills/ulw-research/scripts/fact-commands-fill.mjs +11 -1
- package/packages/shared-skills/skills/ulw-research/scripts/fact-commands-meaning.mjs +13 -9
- package/packages/shared-skills/skills/ulw-research/scripts/fact-commands-polish.mjs +40 -18
- package/packages/shared-skills/skills/ulw-research/scripts/fact-commands-verify.mjs +25 -8
- package/packages/shared-skills/skills/ulw-research/scripts/fact-commands.mjs +35 -16
- package/packages/shared-skills/skills/ulw-research/scripts/fact-derive-2.test.ts +149 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fact-derive.mjs +7 -2
- package/packages/shared-skills/skills/ulw-research/scripts/fact-derive.test.ts +7 -192
- package/packages/shared-skills/skills/ulw-research/scripts/fact-fill.mjs +6 -4
- package/packages/shared-skills/skills/ulw-research/scripts/fact-ledger-kinds.mjs +6 -7
- package/packages/shared-skills/skills/ulw-research/scripts/fact-ledger-kinds.test.ts +2 -1
- package/packages/shared-skills/skills/ulw-research/scripts/fact-ledger-legacy.test.ts +2 -1
- package/packages/shared-skills/skills/ulw-research/scripts/fact-locate.mjs +15 -9
- package/packages/shared-skills/skills/ulw-research/scripts/fact-unit-dimensions-2.test.ts +149 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fact-unit-dimensions.test.ts +4 -168
- package/packages/shared-skills/skills/ulw-research/scripts/fact-unit-rules.test.ts +3 -229
- package/packages/shared-skills/skills/ulw-research/scripts/fidelity-claims.mjs +4 -3
- package/packages/shared-skills/skills/ulw-research/scripts/fidelity-csv.mjs +32 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fidelity-lexicon.mjs +1 -1
- package/packages/shared-skills/skills/ulw-research/scripts/fidelity-originals.mjs +161 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fidelity-policy.mjs +4 -4
- package/packages/shared-skills/skills/ulw-research/scripts/fidelity-records.mjs +81 -14
- package/packages/shared-skills/skills/ulw-research/scripts/fidelity-snapshot-readability.mjs +49 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fidelity-sources.mjs +76 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fidelity-trace.mjs +25 -11
- package/packages/shared-skills/skills/ulw-research/scripts/fidelity-value-unit.mjs +202 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fidelity-values.mjs +40 -39
- package/packages/shared-skills/skills/ulw-research/scripts/fidelity-values.test.ts +8 -29
- package/packages/shared-skills/skills/ulw-research/scripts/gates-fidelity.mjs +8 -6
- package/packages/shared-skills/skills/ulw-research/scripts/gates-static-headings.test.ts +17 -0
- package/packages/shared-skills/skills/ulw-research/scripts/gates-static.mjs +10 -1
- package/packages/shared-skills/skills/ulw-research/scripts/html-lite.mjs +6 -2
- package/packages/shared-skills/skills/ulw-research/scripts/html-markup.mjs +2 -2
- package/packages/shared-skills/skills/ulw-research/scripts/html-text-map.mjs +140 -0
- package/packages/shared-skills/skills/ulw-research/scripts/html-text-map.test.ts +148 -0
- package/packages/shared-skills/skills/ulw-research/scripts/markdown-closers.mjs +126 -0
- package/packages/shared-skills/skills/ulw-research/scripts/md-lite-boundaries.test.ts +46 -0
- package/packages/shared-skills/skills/ulw-research/scripts/md-lite.mjs +1 -1
- package/packages/shared-skills/skills/ulw-research/scripts/number-grammar.mjs +33 -0
- package/packages/shared-skills/skills/ulw-research/scripts/number-grammar.test.ts +36 -0
- package/packages/shared-skills/skills/ulw-research/scripts/numerals.mjs +18 -7
- package/packages/shared-skills/skills/ulw-research/scripts/numerals.test.ts +3 -0
- package/packages/shared-skills/skills/ulw-research/scripts/outcome-start.mjs +66 -0
- package/packages/shared-skills/skills/ulw-research/scripts/outcome-start.test.ts +109 -0
- package/packages/shared-skills/skills/ulw-research/scripts/outcome.mjs +11 -22
- package/packages/shared-skills/skills/ulw-research/scripts/page-source-locator.test.ts +187 -0
- package/packages/shared-skills/skills/ulw-research/scripts/placeholder.mjs +18 -6
- package/packages/shared-skills/skills/ulw-research/scripts/polish-apply.mjs +3 -2
- package/packages/shared-skills/skills/ulw-research/scripts/polish-markup.test.ts +15 -1
- package/packages/shared-skills/skills/ulw-research/scripts/polish-partial.test.ts +42 -0
- package/packages/shared-skills/skills/ulw-research/scripts/raw-decode.mjs +110 -0
- package/packages/shared-skills/skills/ulw-research/scripts/raw-locator.mjs +47 -0
- package/packages/shared-skills/skills/ulw-research/scripts/raw-snapshot.test.ts +139 -0
- package/packages/shared-skills/skills/ulw-research/scripts/report-tools-2.test.ts +233 -0
- package/packages/shared-skills/skills/ulw-research/scripts/report-tools-3.test.ts +141 -0
- package/packages/shared-skills/skills/ulw-research/scripts/report-tools-4.test.ts +135 -0
- package/packages/shared-skills/skills/ulw-research/scripts/report-tools-5.test.ts +231 -0
- package/packages/shared-skills/skills/ulw-research/scripts/report-tools-commands.mjs +1 -1
- package/packages/shared-skills/skills/ulw-research/scripts/report-tools.mjs +7 -1
- package/packages/shared-skills/skills/ulw-research/scripts/report-tools.test.ts +5 -800
- package/packages/shared-skills/skills/ulw-research/scripts/snapshot-readability.test.ts +116 -0
- package/packages/shared-skills/skills/ulw-research/scripts/source-commands.mjs +79 -26
- package/packages/shared-skills/skills/ulw-research/scripts/source-integrity.test.ts +243 -0
- package/packages/shared-skills/skills/ulw-research/scripts/source-refresh.mjs +82 -0
- package/packages/shared-skills/skills/ulw-research/scripts/source-snapshot.mjs +73 -69
- package/packages/shared-skills/skills/ulw-research/scripts/source-snapshot.test.ts +7 -4
- package/packages/shared-skills/skills/ulw-research/scripts/source-view.mjs +89 -0
- package/packages/shared-skills/skills/ulw-research/scripts/sources-citation.mjs +203 -0
- package/packages/shared-skills/skills/ulw-research/scripts/sources-citation.test.ts +236 -0
- package/packages/shared-skills/skills/ulw-research/scripts/tests/contracts-support.ts +147 -0
- package/packages/shared-skills/skills/ulw-research/scripts/tests/delivery-barrier-session.ts +59 -0
- package/packages/shared-skills/skills/ulw-research/scripts/tests/derive-clock.ts +4 -0
- package/packages/shared-skills/skills/ulw-research/scripts/tests/fact-derive-support.ts +65 -0
- package/packages/shared-skills/skills/ulw-research/scripts/tests/fact-unit-dimensions-support.ts +41 -0
- package/packages/shared-skills/skills/ulw-research/scripts/tests/fact-unit-rules-support.ts +241 -0
- package/packages/shared-skills/skills/ulw-research/scripts/tests/fidelity-values-support.ts +30 -0
- package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/conversion-probe.html +14 -0
- package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/sources-shapes.html +20 -0
- package/packages/shared-skills/skills/ulw-research/scripts/tests/numeral-classes.ts +196 -0
- package/packages/shared-skills/skills/ulw-research/scripts/tests/report-tools-support.ts +182 -0
- package/packages/shared-skills/skills/ulw-research/scripts/tests/value-mode-support.ts +48 -0
- package/packages/shared-skills/skills/ulw-research/scripts/units.mjs +14 -3
- package/packages/shared-skills/skills/ulw-research/scripts/units.test.ts +10 -0
- package/packages/shared-skills/skills/ulw-research/scripts/value-mode-unit-classes.test.ts +255 -0
- package/packages/shared-skills/skills/ulw-research/scripts/value-mode-unit.test.ts +179 -0
- package/packages/shared-skills/skills/ulw-research/scripts/value-rules-cli.test.ts +157 -0
- package/packages/shared-skills/skills/ulw-research/scripts/value-rules-followups.test.ts +72 -0
- package/packages/shared-skills/skills/ulw-research/scripts/value-rules-priority.test.ts +80 -0
- package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/tests/fixtures/amp_redirect_stub.html +0 -7
- package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/tests/fixtures/search_interstitial.html +0 -19
- package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/tests/fixtures/wayback_available.json +0 -1
- package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/tests/fixtures/wayback_snapshot.html +0 -1128
- package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/tests/test_fetch_chain.py +0 -77
- package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/tests/test_playwright_stealth.py +0 -86
- package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/tests/test_playwright_templates.py +0 -279
- package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/tests/test_surrogate.py +0 -252
- package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/tests/test_surrogate_validators.py +0 -78
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
// fidelity record exists, the barrier refuses the unverified edition regardless of it, and the same
|
|
16
16
|
// exemption is the only way a required record may be absent.
|
|
17
17
|
|
|
18
|
-
import { existsSync, readFileSync } from "node:fs"
|
|
18
|
+
import { existsSync, readdirSync, readFileSync, realpathSync } from "node:fs"
|
|
19
19
|
import { join } from "node:path"
|
|
20
20
|
|
|
21
21
|
import { CliError, readJsonFile, sha256Of, writeJsonAtomic } from "./cli-support.mjs"
|
|
@@ -24,7 +24,10 @@ import { meaningCheckEnabled, requireLockedBrief } from "./fidelity-brief.mjs"
|
|
|
24
24
|
import { blocksDelivery, deliveryBlockReason } from "./gates-fidelity.mjs"
|
|
25
25
|
|
|
26
26
|
export const FIDELITY_RECORD = "fidelity-defects.json"
|
|
27
|
+
/** The legacy session meaning record: a candidate for every edition whose bytes it claims, like any edition record (newest run wins). */
|
|
27
28
|
export const MEANING_RECORD = "fidelity-meaning-defects.json"
|
|
29
|
+
/** The meaning record of one delivered edition, named by its outcome format (`fact meaning-check --edition`). */
|
|
30
|
+
export const meaningRecordName = (format) => `fidelity-meaning-defects.${format}.json`
|
|
28
31
|
|
|
29
32
|
/** The fields every fidelity record carries, whatever its kind. */
|
|
30
33
|
export const RECORD_ENVELOPE = Object.freeze(["record_kind", "artifact_sha256", "report", "written_at", "defects"])
|
|
@@ -39,13 +42,67 @@ function meaningRequired({ sessionDir }) {
|
|
|
39
42
|
}
|
|
40
43
|
|
|
41
44
|
const subjectOf = (row) => row.renderedFrom ?? row.path ?? null
|
|
45
|
+
const hashOf = (path) => (existsSync(path) ? sha256Of(readFileSync(path)) : null)
|
|
42
46
|
|
|
43
47
|
/** One record per delivered edition: the file its row names with --verified-by, bound to that edition's bytes. */
|
|
44
48
|
const perEdition = (sessionDir, delivered) =>
|
|
45
49
|
delivered.map((row) => ({ path: row.verifiedBy ?? null, declared: row.verifiedBy !== undefined, format: row.format, at: `${row.format}: `, subjects: [subjectOf(row)].filter((subject) => subject !== null) }))
|
|
46
50
|
|
|
47
|
-
|
|
48
|
-
|
|
51
|
+
const sessionTarget = (sessionDir, file) => ({ path: join(sessionDir, file), declared: false, format: null, at: "", subjects: [] })
|
|
52
|
+
|
|
53
|
+
/** Include every existing session fidelity record, even when no delivered row attaches it. */
|
|
54
|
+
const perEditionAndSession = (sessionDir, delivered) => [
|
|
55
|
+
...perEdition(sessionDir, delivered),
|
|
56
|
+
...(existsSync(join(sessionDir, FIDELITY_RECORD)) ? [sessionTarget(sessionDir, FIDELITY_RECORD)] : []),
|
|
57
|
+
]
|
|
58
|
+
|
|
59
|
+
/** Edition meaning records on disk (fidelity-meaning-defects.<format>.json). */
|
|
60
|
+
const EDITION_MEANING = /^fidelity-meaning-defects\.[^.]+\.json$/u
|
|
61
|
+
|
|
62
|
+
/** Verdict severity for equal times: a confirmed drift, then any other blocking defect (unverified), then clean. */
|
|
63
|
+
const severityOf = (defects) =>
|
|
64
|
+
defects.some((defect) => defect?.code === "meaning_drift_confirmed") ? 2 : defects.some((defect) => typeof defect?.code === "string" && blocksDelivery(defect)) ? 1 : 0
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* What selection reads from a meaning record: the bytes it claims, when it was written, how severe it is.
|
|
68
|
+
* An unreadable record matches no bytes (its own row still reads and refuses it); a record with no
|
|
69
|
+
* readable written_at sorts newest, so the barrier reads it and refuses it as malformed.
|
|
70
|
+
*/
|
|
71
|
+
function claimOf(path) {
|
|
72
|
+
let record
|
|
73
|
+
try {
|
|
74
|
+
record = readJsonFile(path)
|
|
75
|
+
} catch {
|
|
76
|
+
return null
|
|
77
|
+
}
|
|
78
|
+
const at = Date.parse(record?.written_at)
|
|
79
|
+
return { path, hash: record?.artifact_sha256 ?? null, at: Number.isNaN(at) ? Infinity : at, severity: severityOf(Array.isArray(record?.defects) ? record.defects : []) }
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** Newest written_at first; on equal times the more severe verdict first. File names never decide. */
|
|
83
|
+
const newestFirst = (a, b) => (a.at === b.at ? b.severity - a.severity : b.at > a.at ? 1 : -1)
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* One meaning record per delivered edition, bound to that edition's bytes. Every record whose
|
|
87
|
+
* artifact_sha256 equals the edition's current bytes (a PDF counts its --rendered-from bytes) is a
|
|
88
|
+
* candidate: its own fidelity-meaning-defects.<format>.json, another edition's record (a
|
|
89
|
+
* byte-identical copy, or the html edition a PDF renders), and the legacy session record. The
|
|
90
|
+
* newest reader run wins; on equal times the more severe verdict wins. When no record claims these
|
|
91
|
+
* bytes the target is its own record (refused as for a different edition), else the legacy record,
|
|
92
|
+
* else its own name, so the refusal names the file to write (meaning gate not run).
|
|
93
|
+
*/
|
|
94
|
+
function perEditionMeaning(sessionDir, delivered) {
|
|
95
|
+
if (delivered.length === 0) return [sessionTarget(sessionDir, MEANING_RECORD)]
|
|
96
|
+
const legacy = join(sessionDir, MEANING_RECORD)
|
|
97
|
+
const editions = readdirSync(sessionDir).filter((name) => EDITION_MEANING.test(name)).map((name) => join(sessionDir, name))
|
|
98
|
+
const claims = [...editions, ...(existsSync(legacy) ? [legacy] : [])].map(claimOf).filter((claim) => claim !== null)
|
|
99
|
+
return perEdition(sessionDir, delivered).map((target) => {
|
|
100
|
+
const own = join(sessionDir, meaningRecordName(target.format))
|
|
101
|
+
const bytes = target.subjects.length > 0 ? hashOf(target.subjects[0]) : null
|
|
102
|
+
const [newest] = bytes === null ? [] : claims.filter((claim) => claim.hash === bytes).sort(newestFirst)
|
|
103
|
+
return { ...target, declared: false, path: newest?.path ?? (existsSync(own) ? own : existsSync(legacy) ? legacy : own) }
|
|
104
|
+
})
|
|
105
|
+
}
|
|
49
106
|
|
|
50
107
|
/** Every fidelity record the barrier reads, in check order; each column is one rule the barrier applies. */
|
|
51
108
|
export const FIDELITY_RECORDS = Object.freeze([
|
|
@@ -53,7 +110,7 @@ export const FIDELITY_RECORDS = Object.freeze([
|
|
|
53
110
|
kind: "fidelity",
|
|
54
111
|
file: FIDELITY_RECORD,
|
|
55
112
|
step: "fidelity gate",
|
|
56
|
-
targets:
|
|
113
|
+
targets: perEditionAndSession,
|
|
57
114
|
requiredWhen: ({ hasFidelity }, target) => hasFidelity || GATED_FORMATS.includes(target.format),
|
|
58
115
|
remedy: (target) => `run fact verify on ${target.subjects[0] ?? "the edition"} and set it with --verified-by`,
|
|
59
116
|
bindsTo: "artifact_sha256",
|
|
@@ -64,17 +121,25 @@ export const FIDELITY_RECORDS = Object.freeze([
|
|
|
64
121
|
kind: "meaning",
|
|
65
122
|
file: MEANING_RECORD,
|
|
66
123
|
step: "meaning gate",
|
|
67
|
-
targets:
|
|
124
|
+
targets: perEditionMeaning,
|
|
68
125
|
requiredWhen: meaningRequired,
|
|
69
|
-
remedy: () =>
|
|
126
|
+
remedy: (target) => `run fact pairs and fact meaning-check --edition ${target.format} on ${target.subjects[0] ?? "the edition"} before delivery`,
|
|
70
127
|
bindsTo: "artifact_sha256",
|
|
71
128
|
blocking: blocksDelivery,
|
|
72
129
|
payload: Object.freeze(["command", "status", "exitCode", "run_id", "pairs_sha256", "counts", "dropped", "unverified", "calibration_path", "calibration_written", "calibration_replaced", "promotion_eligibility"]),
|
|
73
130
|
}),
|
|
74
131
|
])
|
|
75
132
|
|
|
133
|
+
// Collection warnings use the same envelope, but are not delivery-gate records.
|
|
134
|
+
const SOURCE_RECORDS = Object.freeze([
|
|
135
|
+
Object.freeze({
|
|
136
|
+
kind: "source_conversion",
|
|
137
|
+
payload: Object.freeze(["conversion_altered_numbers", "lost", "raw_count", "converted_count"]),
|
|
138
|
+
}),
|
|
139
|
+
])
|
|
140
|
+
|
|
76
141
|
const entryOf = (kind) => {
|
|
77
|
-
const entry = FIDELITY_RECORDS.find((row) => row.kind === kind)
|
|
142
|
+
const entry = [...FIDELITY_RECORDS, ...SOURCE_RECORDS].find((row) => row.kind === kind)
|
|
78
143
|
if (entry === undefined) throw new CliError(`unknown fidelity record kind ${kind}`)
|
|
79
144
|
return entry
|
|
80
145
|
}
|
|
@@ -124,19 +189,20 @@ export function readFidelityRecord(path, kind) {
|
|
|
124
189
|
}
|
|
125
190
|
|
|
126
191
|
const unresolved = (format, codes, path) => ({ code: "fidelity_unresolved", format, codes, message: codes.map((code) => deliveryBlockReason({ code })).join("; "), path })
|
|
127
|
-
const hashOf = (path) => (existsSync(path) ? sha256Of(readFileSync(path)) : null)
|
|
128
192
|
|
|
129
193
|
/** Barrier problems for `outcome verify` and `outcome finish`; empty when nothing blocks delivery. */
|
|
130
194
|
export function fidelityProblems(sessionDir, manifest) {
|
|
131
195
|
const delivered = manifest.deliverables.filter((row) => row.status === "delivered")
|
|
132
|
-
const
|
|
196
|
+
const targets = FIDELITY_RECORDS.map((entry) => [entry, entry.targets(sessionDir, delivered)])
|
|
197
|
+
// Any session record a row did not name (a session fidelity record, a meaning record) is fidelity evidence.
|
|
198
|
+
const hasFidelity = existsSync(join(sessionDir, LEDGER_FILENAME)) || targets.some(([, list]) => list.some((target) => !target.declared && target.path !== null && existsSync(target.path)))
|
|
133
199
|
const lastFidelity = manifest.gateHistory?.findLast((entry) => entry.gate === "fidelity")
|
|
134
200
|
const exempt = !hasFidelity && manifest.gates.fidelity === "not_run" && lastFidelity?.status === "not_run" && Boolean(lastFidelity.reason?.trim())
|
|
135
201
|
const context = { sessionDir, hasFidelity }
|
|
136
202
|
const problems = []
|
|
137
203
|
const counted = new Set()
|
|
138
|
-
for (const entry of
|
|
139
|
-
for (const target of
|
|
204
|
+
for (const [entry, list] of targets) {
|
|
205
|
+
for (const target of list) {
|
|
140
206
|
const refuse = (message, path = target.path) => problems.push({ code: "edition_unverified", format: target.format, message: `${target.at}${message}`, path })
|
|
141
207
|
if (target.path === null || !existsSync(target.path)) {
|
|
142
208
|
if (target.declared) refuse(`${entry.kind} record ${target.path} is missing`)
|
|
@@ -151,10 +217,11 @@ export function fidelityProblems(sessionDir, manifest) {
|
|
|
151
217
|
continue
|
|
152
218
|
}
|
|
153
219
|
if (target.subjects.length > 0 && !target.subjects.some((subject) => hashOf(subject) === record[entry.bindsTo])) {
|
|
154
|
-
refuse(`${entry.kind} record ${target.path} is for a different edition than ${target.subjects.join(", ")}`)
|
|
220
|
+
refuse(`${entry.kind} record ${target.path} is for a different edition than ${target.subjects.join(", ")}; ${entry.remedy(target)}`)
|
|
155
221
|
}
|
|
156
|
-
|
|
157
|
-
counted.
|
|
222
|
+
const path = realpathSync.native(target.path)
|
|
223
|
+
if (counted.has(path)) continue
|
|
224
|
+
counted.add(path)
|
|
158
225
|
const codes = [...new Set(record.defects.filter(entry.blocking).map((defect) => defect.code))].sort()
|
|
159
226
|
if (codes.length > 0) problems.push(unresolved(target.format, codes, target.path))
|
|
160
227
|
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// Availability is checked before hash/locator checks, including the raw-byte snapshot contract.
|
|
2
|
+
// One code (snapshot_hash_mismatch) for every unreadable or changed snapshot; the message names the cause.
|
|
3
|
+
import { existsSync, readFileSync } from "node:fs"
|
|
4
|
+
import { join } from "node:path"
|
|
5
|
+
import { sha256Of } from "./cli-support.mjs"
|
|
6
|
+
|
|
7
|
+
function read(dir, file) {
|
|
8
|
+
try {
|
|
9
|
+
return readFileSync(join(dir, file))
|
|
10
|
+
} catch (error) {
|
|
11
|
+
if (error.code === "ENOENT") throw new Error(`${file} is missing`)
|
|
12
|
+
if (error.code === "EISDIR") throw new Error(`${file} is not a readable file`)
|
|
13
|
+
throw new Error(`${file} is unreadable (${error.code ?? error.message})`)
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** Why the snapshot behind `row` cannot be trusted, or null when it reads and matches its pinned hash. */
|
|
18
|
+
export function snapshotCause(sourcesDir, row) {
|
|
19
|
+
if (!sourcesDir) return "has no sources directory"
|
|
20
|
+
const dir = join(sourcesDir, row.source_id)
|
|
21
|
+
try {
|
|
22
|
+
let meta
|
|
23
|
+
try {
|
|
24
|
+
meta = JSON.parse(read(dir, "meta.json").toString("utf8"))
|
|
25
|
+
} catch (error) {
|
|
26
|
+
throw error instanceof SyntaxError ? new Error("meta.json is not valid JSON") : error
|
|
27
|
+
}
|
|
28
|
+
if (!meta || typeof meta.sha256 !== "string") return "has invalid snapshot metadata (meta.json has no sha256)"
|
|
29
|
+
const needsRaw = ["bytes", "utf8-bytes"].includes(row.locator?.unit) && meta.kind !== "converted-only"
|
|
30
|
+
const file = existsSync(join(dir, "raw.bin")) || needsRaw ? "raw.bin" : "text.txt"
|
|
31
|
+
return sha256Of(read(dir, file)) === meta.sha256 ? null : `hash mismatch: ${file} changed after its snapshot was pinned`
|
|
32
|
+
} catch (error) {
|
|
33
|
+
return error.message
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Flags each cited row whose own snapshot is unreadable; returns those source ids so healthy sources keep their metadata. */
|
|
38
|
+
export function unreadableSnapshots(ctx, rows, cited, sourcesDir, out) {
|
|
39
|
+
const unreadable = new Set()
|
|
40
|
+
for (const [id, node] of cited) {
|
|
41
|
+
const row = rows.get(id)
|
|
42
|
+
if (!row?.source_id) continue
|
|
43
|
+
const cause = snapshotCause(sourcesDir, row)
|
|
44
|
+
if (cause === null) continue
|
|
45
|
+
unreadable.add(row.source_id)
|
|
46
|
+
out.push({ ...ctx.defect("snapshot_hash_mismatch", node, `${id}: snapshot ${row.source_id} ${cause}`), fact_id: id })
|
|
47
|
+
}
|
|
48
|
+
return unreadable
|
|
49
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
// Source integrity and raw quote anchoring, shared by the fidelity value checks. Fails closed:
|
|
2
|
+
// a cited source-backed row whose sources dir, meta.json, raw.bin, or text.txt cannot be read, or
|
|
3
|
+
// whose snapshot no longer matches its pin, gets snapshot_hash_mismatch (with fact_id) naming the
|
|
4
|
+
// cause; healthy sources keep their metadata and checks. flag is (code, node, message, extra).
|
|
5
|
+
import { join } from "node:path"
|
|
6
|
+
|
|
7
|
+
import { proseText } from "./html-lite.mjs"
|
|
8
|
+
import { reanchor } from "./raw-locator.mjs"
|
|
9
|
+
import { readSnapshot } from "./source-view.mjs"
|
|
10
|
+
|
|
11
|
+
const short = (hash) => String(hash ?? "none").slice(0, 12)
|
|
12
|
+
|
|
13
|
+
function snapshotOf(sourcesDir, id) {
|
|
14
|
+
if (!sourcesDir) throw new Error("has no sources directory")
|
|
15
|
+
return readSnapshot(join(sourcesDir, id))
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const blocked = (flag, cited, row, id, cause) => flag("snapshot_hash_mismatch", cited.get(row.fact_id), `${row.fact_id}: snapshot ${id} ${cause}`, { fact_id: row.fact_id })
|
|
19
|
+
|
|
20
|
+
/** One source's checks for the rows that cite it; byte-located rows are re-anchored on the view. */
|
|
21
|
+
function rowChecks(snap, id, referenced, cited, flag) {
|
|
22
|
+
const converted = snap.view.mode === "converted"
|
|
23
|
+
for (const row of referenced) {
|
|
24
|
+
const node = cited.get(row.fact_id)
|
|
25
|
+
// No locator unit marks a legacy text.txt row: it holds only on the legacy snapshot it pins.
|
|
26
|
+
if (row.locator?.unit === undefined) {
|
|
27
|
+
if (snap.view.mode !== "legacy") blocked(flag, cited, row, id, `is no longer the legacy text.txt snapshot the row was located on (now ${snap.view.mode}); add the row again from the new snapshot`)
|
|
28
|
+
else if (row.snapshot_sha256 !== snap.sha256) blocked(flag, cited, row, id, `holds ${short(snap.sha256)} but the legacy row pins ${short(row.snapshot_sha256)}`)
|
|
29
|
+
continue
|
|
30
|
+
}
|
|
31
|
+
if (snap.view.mode === "legacy") {
|
|
32
|
+
blocked(flag, cited, row, id, "raw.bin is missing (the row is byte-located)")
|
|
33
|
+
continue
|
|
34
|
+
}
|
|
35
|
+
if (converted && row.kind !== "quote") {
|
|
36
|
+
flag("snapshot_not_raw", node, `${row.fact_id} cannot measure converted-only source ${id}`)
|
|
37
|
+
continue
|
|
38
|
+
}
|
|
39
|
+
if (row.snapshot_sha256 !== snap.sha256) {
|
|
40
|
+
blocked(flag, cited, row, id, `holds ${short(snap.sha256)} but the row pins ${short(row.snapshot_sha256)}; run source refresh ${id} to re-anchor`)
|
|
41
|
+
continue
|
|
42
|
+
}
|
|
43
|
+
const anchor = row.locator.artifact === snap.view.artifact ? reanchor(snap.view, row) : null
|
|
44
|
+
if (!anchor) flag("quote_not_verbatim", node, `${row.fact_id}: no unique exact quote and context in ${id}/${snap.view.artifact}`)
|
|
45
|
+
else if (anchor.relocated) flag("relocated", node, `${row.fact_id}: ${row.locator.char_start}:${row.locator.char_end} -> ${anchor.start}:${anchor.end} (bytes of ${id}/${snap.view.artifact})`)
|
|
46
|
+
if (converted && row.kind === "quote") flag("quote_converted_only", node, `${row.fact_id} quotes converted-only source ${id}, not the original fetched bytes`)
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function sourceChecks(ctx, rows, cited, sourcesDir, flag, unreadable = new Set()) {
|
|
51
|
+
const referenced = new Map()
|
|
52
|
+
for (const key of cited.keys()) {
|
|
53
|
+
const row = rows.get(key)
|
|
54
|
+
if (row?.source_id) referenced.set(row.source_id, [...(referenced.get(row.source_id) ?? []), row])
|
|
55
|
+
}
|
|
56
|
+
const ids = new Set(referenced.keys())
|
|
57
|
+
for (const match of proseText(ctx.body).matchAll(/\[S(\d+)\]/g)) ids.add(`S${match[1]}`)
|
|
58
|
+
const meta = new Map()
|
|
59
|
+
for (const id of ids) {
|
|
60
|
+
if (unreadable.has(id)) continue
|
|
61
|
+
const backing = referenced.get(id) ?? []
|
|
62
|
+
let snap
|
|
63
|
+
try {
|
|
64
|
+
snap = snapshotOf(sourcesDir, id)
|
|
65
|
+
} catch (error) {
|
|
66
|
+
// A source only named in prose ([S<n>]) backs no row: an absent snapshot is not a finding.
|
|
67
|
+
for (const row of backing) blocked(flag, cited, row, id, error.message)
|
|
68
|
+
continue
|
|
69
|
+
}
|
|
70
|
+
meta.set(id, snap.meta)
|
|
71
|
+
if (snap.drift.length === 0) rowChecks(snap, id, backing, cited, flag)
|
|
72
|
+
else if (backing.length === 0) flag("snapshot_hash_mismatch", ctx.body, `snapshot ${id} ${snap.drift.join("; ")}`)
|
|
73
|
+
else for (const row of backing) blocked(flag, cited, row, id, snap.drift.join("; "))
|
|
74
|
+
}
|
|
75
|
+
return meta
|
|
76
|
+
}
|
|
@@ -13,9 +13,12 @@
|
|
|
13
13
|
// so each entry, in array order, is matched to the next untraced occurrence of its rendered text in
|
|
14
14
|
// document order that is a whole numeral and reads as the entry's row there (fits: "1,237 million"
|
|
15
15
|
// is not F2's 1,237), and strips that one occurrence only. A copy of the same text that no entry
|
|
16
|
-
// accounts for stays in the scan; an entry whose text is gone strips nothing.
|
|
16
|
+
// accounts for stays in the scan; an entry whose text is gone strips nothing. A derived or
|
|
17
|
+
// converted entry is located under the bracket grammar of fidelity-originals.mjs (renderLocator).
|
|
17
18
|
|
|
19
|
+
import { PROSE_TAGS } from "./html-lite.mjs"
|
|
18
20
|
import { NOT_CLAIMS } from "./numerals.mjs"
|
|
21
|
+
import { sourcesClaims, textClaims } from "./sources-citation.mjs"
|
|
19
22
|
|
|
20
23
|
/** Structural numerals: versions, figure/table/section references, times (NOT_CLAIMS 7-11), pages, citations, placeholders. */
|
|
21
24
|
export const STRUCTURAL = [...NOT_CLAIMS.slice(7, 12), /\b(?:p|pp|page|pages)\.?\s*\d+(?:\s*[-\u2013]\s*\d+)?/gi, /\[(?:S|Source\s*)\d+\]|\[\^\d+\]/gi, /\{\{[^}]*\}\}/g]
|
|
@@ -29,7 +32,12 @@ function ownerTexts(ctx, charts) {
|
|
|
29
32
|
const owners = new Map()
|
|
30
33
|
for (const entry of ctx.texts) {
|
|
31
34
|
const kind = ctx.kindOf(entry.scope)
|
|
32
|
-
if (kind === "
|
|
35
|
+
if (kind === "toc") continue
|
|
36
|
+
if (kind === "sources") {
|
|
37
|
+
if (!PROSE_TAGS.has(entry.owner.tag)) owners.set(entry.owner, `${owners.get(entry.owner) ?? ""} ${textClaims(entry.node.text).join(" ")}`)
|
|
38
|
+
else if (!owners.has(entry.owner)) owners.set(entry.owner, sourcesClaims(entry.owner).join(" "))
|
|
39
|
+
continue
|
|
40
|
+
}
|
|
33
41
|
if (charts.has(ctx.util.closest(entry.node, (cur) => cur.tag === "figure"))) continue
|
|
34
42
|
const hidden = ctx.util.closest(entry.node, (cur) => cur.attrs["data-fact"] !== undefined || cur.attrs["data-translates"] !== undefined || cur.tag === "q" || cur.tag === "blockquote")
|
|
35
43
|
owners.set(entry.owner, (owners.get(entry.owner) ?? "") + (hidden ? ` ${TRACED} ` : entry.node.text))
|
|
@@ -52,8 +60,13 @@ function scanOwner(ctx, owner, raw, flag) {
|
|
|
52
60
|
for (const match of rest.matchAll(NUMERAL)) flag("untraceable_number", owner, `number "${match[0]}" has no ledger row: "${clip(rest.slice(Math.max(0, match.index - 30), match.index + 30))}"`)
|
|
53
61
|
}
|
|
54
62
|
|
|
63
|
+
const plainSite = (entry, text, from) => {
|
|
64
|
+
const hit = text.indexOf(entry.rendered, from)
|
|
65
|
+
return hit === -1 ? null : [hit, hit + entry.rendered.length]
|
|
66
|
+
}
|
|
67
|
+
|
|
55
68
|
/** Strip each fill-map entry at its own fill site: the next untraced occurrence after the previous entry's site. */
|
|
56
|
-
function traceFills(owners, fillMap, fits) {
|
|
69
|
+
function traceFills(owners, fillMap, fits, locate = plainSite) {
|
|
57
70
|
const keys = [...owners.keys()]
|
|
58
71
|
const texts = [...owners.values()]
|
|
59
72
|
let [index, from] = [0, 0]
|
|
@@ -61,12 +74,12 @@ function traceFills(owners, fillMap, fits) {
|
|
|
61
74
|
const { rendered } = entry
|
|
62
75
|
if (!rendered) continue
|
|
63
76
|
for (let i = index, at = from; i < texts.length; i += 1, at = 0) {
|
|
64
|
-
let hit = texts[i]
|
|
65
|
-
const glued = (
|
|
66
|
-
while (hit
|
|
67
|
-
if (hit
|
|
68
|
-
texts[i] = `${texts[i].slice(0, hit)}${TRACED}${texts[i].slice(hit
|
|
69
|
-
;[index, from] = [i, hit + TRACED.length]
|
|
77
|
+
let hit = locate(entry, texts[i], at)
|
|
78
|
+
const glued = ([start, end]) => (/^\d/.test(rendered) && /\d/.test(texts[i][start - 1] ?? "")) || (/\d$/.test(rendered) && /\d/.test(texts[i][end] ?? ""))
|
|
79
|
+
while (hit && (glued(hit) || !fits(entry, texts[i].slice(hit[0])))) hit = locate(entry, texts[i], hit[0] + 1)
|
|
80
|
+
if (!hit) continue
|
|
81
|
+
texts[i] = `${texts[i].slice(0, hit[0])}${TRACED}${texts[i].slice(hit[1])}`
|
|
82
|
+
;[index, from] = [i, hit[0] + TRACED.length]
|
|
70
83
|
break
|
|
71
84
|
}
|
|
72
85
|
}
|
|
@@ -79,7 +92,8 @@ function traceFills(owners, fillMap, fits) {
|
|
|
79
92
|
* @param {{ fact_id: string, rendered: string, start?: number, end?: number }[] | null} fillMap Markdown fill map, or null
|
|
80
93
|
* @param {(code: string, node: object, message: string) => void} flag
|
|
81
94
|
* @param {(entry: object, tail: string) => boolean} fits whether the text from a candidate site on reads as the entry's row
|
|
95
|
+
* @param {(entry: object, text: string, from: number) => [number, number] | null} [locate] the next candidate site of an entry's text
|
|
82
96
|
*/
|
|
83
|
-
export function traceChecks(ctx, charts, fillMap, flag, fits) {
|
|
84
|
-
for (const [owner, text] of traceFills(ownerTexts(ctx, charts), fillMap, fits)) scanOwner(ctx, owner, text, flag)
|
|
97
|
+
export function traceChecks(ctx, charts, fillMap, flag, fits, locate) {
|
|
98
|
+
for (const [owner, text] of traceFills(ownerTexts(ctx, charts), fillMap, fits, locate)) scanOwner(ctx, owner, text, flag)
|
|
85
99
|
}
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
// Value-mode units live outside the fact element. The documented form is the value then its unit,
|
|
2
|
+
// with or without a space, a Korean particle allowed after it ({{F1:span:value}} m, {{F3:rate:value}}%,
|
|
3
|
+
// {{F21:rev:value}}\uC5B5 \uC6D0\uC73C\uB85C); a currency sign right before the value; the :unit element
|
|
4
|
+
// right after it; or the same visual column's table header. A currency the element's own text carries (fill renders a
|
|
5
|
+
// prefix currency value from its raw text, $<data>$12</data>) binds too: every bound sign, outside or inside, must agree. Unknown words are advisory, never guessed units,
|
|
6
|
+
// unless fill recorded a bound unit for the value: then losing it is a unit edit.
|
|
7
|
+
import { cellsOf, rowsOf } from "./fact-locate.mjs"
|
|
8
|
+
import { unitKey } from "./fidelity-binding.mjs"
|
|
9
|
+
import { parseHtml, proseText } from "./html-lite.mjs"
|
|
10
|
+
import { NUMBER_SOURCE } from "./number-grammar.mjs"
|
|
11
|
+
import { QUALIFIERS_AFTER } from "./numerals.mjs"
|
|
12
|
+
import { dimensionOf, normalizeUnit, spacedAliasUnit } from "./units.mjs"
|
|
13
|
+
|
|
14
|
+
// Korean particles a unit may carry (eun/neun, i/ga, eul/reul, ui, e, eseo, ege, euro/ro, wa/gwa, do, man,
|
|
15
|
+
// kkaji, buteo, boda, ssik, in, ida/yeotda/ieotda, imyeo, igo), longest first.
|
|
16
|
+
const PARTICLES = ["\uC774\uC5C8\uB2E4", "\uC600\uB2E4", "\uC774\uB2E4", "\uC774\uBA70", "\uC774\uACE0", "\uC73C\uB85C", "\uC5D0\uC11C", "\uC5D0\uAC8C", "\uAE4C\uC9C0", "\uBD80\uD130", "\uBCF4\uB2E4", "\uC740", "\uB294", "\uC774", "\uAC00", "\uC744", "\uB97C", "\uC758", "\uC5D0", "\uB85C", "\uC640", "\uACFC", "\uB3C4", "\uB9CC", "\uC529", "\uC778"]
|
|
17
|
+
/** The displayed value, read with the one grouped-number grammar; text around it is sign and unit. */
|
|
18
|
+
const NUMBER = new RegExp(NUMBER_SOURCE, "u")
|
|
19
|
+
const SIGNS = ["US$", "$", "\u20AC", "\u20A9", "\u00A3", "\u00A5", "USD", "EUR", "GBP", "JPY", "KRW"]
|
|
20
|
+
|
|
21
|
+
const withoutSuffix = (word, suffixes) => {
|
|
22
|
+
const suffix = suffixes.find((cur) => word.endsWith(cur) && word.length > cur.length)
|
|
23
|
+
return suffix ? word.slice(0, -suffix.length) : word
|
|
24
|
+
}
|
|
25
|
+
const withoutParticle = (word) => withoutSuffix(word, PARTICLES)
|
|
26
|
+
/** Words written after a value that bound or soften it (the numeral qualifiers, jeongdo, garyang, jjeum, gwaban). */
|
|
27
|
+
const QUALIFIER_WORDS = [...QUALIFIERS_AFTER, "\uC815\uB3C4", "\uAC00\uB7C9", "\uCBE4", "\uACFC\uBC18"]
|
|
28
|
+
/** A unit, with a particle and/or a qualifier glued on ("m\uC774\uC0C1\uC758"); a whole qualifier word is never a unit (chogwa is not cho + gwa). */
|
|
29
|
+
const stemsOf = (word) => {
|
|
30
|
+
const bare = withoutParticle(word)
|
|
31
|
+
return QUALIFIER_WORDS.includes(word) ? [] : [word, bare, withoutSuffix(word, QUALIFIER_WORDS), withoutSuffix(bare, QUALIFIER_WORDS)].filter((stem) => !QUALIFIER_WORDS.includes(stem))
|
|
32
|
+
}
|
|
33
|
+
const stemOf = (word) => stemsOf(word).find((stem) => normalizeUnit(stem)) ?? null
|
|
34
|
+
const unitOf = (word) => normalizeUnit(stemOf(word))
|
|
35
|
+
/** Unit aliases that are also ordinary English words ("won awards", "second place"): read only as the row's own unit. */
|
|
36
|
+
const ORDINARY_WORDS = new Set(["won", "second", "pound", "pounds"])
|
|
37
|
+
/** An ordinary word, or an all-caps acronym that only matches a unit case-insensitively ("SEC filings", "WON"). */
|
|
38
|
+
const ordinary = (word) => ORDINARY_WORDS.has(word.toLowerCase()) || (/^[A-Z]{3,}$/.test(word) && normalizeUnit(word) !== word)
|
|
39
|
+
/** Inline elements the text after a value is read through; any other element, or another fact, ends it. */
|
|
40
|
+
const INLINE = new Set(["a", "abbr", "b", "bdi", "cite", "code", "data", "dfn", "em", "i", "ins", "kbd", "mark", "samp", "small", "span", "strong", "sub", "sup", "time", "u", "var"])
|
|
41
|
+
/** "3 bun-ui 1" is the fraction one third, not 3 minutes. */
|
|
42
|
+
const FRACTION = /^\uBD84\uC758\s?\d/u
|
|
43
|
+
const scaleOf = (unit) => /-(e\d+)$/.exec(unit ?? "")?.[1] ?? null
|
|
44
|
+
|
|
45
|
+
/** English words stay apart: letter-only words join only as a spaced alias ("percentage points"), so "m in" is m, then in. */
|
|
46
|
+
const LETTERS = /^[A-Za-z]+$/u
|
|
47
|
+
/** The unit the words spell together: a spaced alias, or (with a symbol or Hangul in them) the words joined ("% p", "\uD37C\uC13C\uD2B8 \uD3EC\uC778\uD2B8"). */
|
|
48
|
+
function unitOfWords(words) {
|
|
49
|
+
if (words.length === 1) return unitOf(words[0])
|
|
50
|
+
const spaced = stemsOf(words.at(-1)).map((last) => spacedAliasUnit([...words.slice(0, -1), last].join(" "))).find(Boolean)
|
|
51
|
+
return spaced ?? (words.every((word) => LETTERS.test(word)) ? null : unitOf(words.join("")))
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** The unit written right after the value: the longest known unit its first words spell ("% \uD3EC\uC778\uD2B8" is %p, not %), else the next word. */
|
|
55
|
+
function unitAfter(text, row) {
|
|
56
|
+
const lead = text.replace(/^[\s\u00A0]+/u, "")
|
|
57
|
+
if (FRACTION.test(lead)) return null
|
|
58
|
+
const bracketed = /^\(([^()]{1,40})\)/u.exec(lead)
|
|
59
|
+
if (bracketed) return unitAfter(bracketed[1], row)
|
|
60
|
+
const clause = lead.split(/[,.;:!?()[\]"]/u)[0]
|
|
61
|
+
const words = clause.split(/[\s\u00A0]+/u).filter(Boolean).slice(0, 3)
|
|
62
|
+
if (words.length === 0) return null
|
|
63
|
+
for (let n = words.length; n > 1; n--) {
|
|
64
|
+
const unit = unitOfWords(words.slice(0, n))
|
|
65
|
+
if (unit) return { token: unit }
|
|
66
|
+
}
|
|
67
|
+
const first = unitOf(words[0])
|
|
68
|
+
if (!first) return null
|
|
69
|
+
// An ordinary word ("pounds", "won awards") counts only as the row's unit or beside a sign or header of its kind (boundUnits).
|
|
70
|
+
if (unitKey(first) !== unitKey(row.unit) && ordinary(stemOf(words[0]))) return { token: first, ordinary: true }
|
|
71
|
+
const currency = words[1] ? unitOf(words[1]) : null
|
|
72
|
+
if (dimensionOf(first) === "scale" && dimensionOf(currency)?.startsWith("currency:")) return { token: `${currency} ${words[0]}`, dimension: dimensionOf(currency), scale: first }
|
|
73
|
+
return { token: first }
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function signBefore(text) {
|
|
77
|
+
const trimmed = text.replace(/[\s\u00A0]+$/u, "")
|
|
78
|
+
// A letter sign is a whole word: "USD 12", never "XUSD 12".
|
|
79
|
+
const sign = SIGNS.find((s) => trimmed.endsWith(s) && !(/^[A-Z]/u.test(s) && /[A-Za-z]$/u.test(trimmed.slice(0, -s.length))))
|
|
80
|
+
return sign ? { token: normalizeUnit(sign), sign: true } : null
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** A currency written after the number inside the element ("12\u20AC", "12\uC6D0"); any other unit there is part of the value. */
|
|
84
|
+
function currencyAfter(text, row) {
|
|
85
|
+
const found = unitAfter(text, row)
|
|
86
|
+
const dimension = found && !found.ordinary ? (found.dimension ?? dimensionOf(found.token)) : null
|
|
87
|
+
return dimension?.startsWith("currency:") ? { token: found.scale ? dimension.slice("currency:".length) : found.token, sign: true } : null
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function agrees(found, row) {
|
|
91
|
+
if (found.scale) return dimensionOf(row.unit) === found.dimension && scaleOf(row.unit) === found.scale
|
|
92
|
+
if (unitKey(found.token) === unitKey(row.unit)) return true
|
|
93
|
+
if (dimensionOf(found.token) === "scale") return scaleOf(row.unit) === found.token
|
|
94
|
+
if (dimensionOf(found.token)?.startsWith("currency:") && found.sign) return dimensionOf(row.unit) === dimensionOf(found.token)
|
|
95
|
+
return unitKey(found.token) === unitKey(row.unit)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const closest = (node, test) => {
|
|
99
|
+
for (let cur = node.parent; cur; cur = cur.parent) if (test(cur)) return cur
|
|
100
|
+
return null
|
|
101
|
+
}
|
|
102
|
+
/** All text under a node, code included: a unit may be set in code ("320 <code>ms</code>"). */
|
|
103
|
+
const plainText = (node) => (node.tag === "#text" ? node.text : node.children.map(plainText).join(""))
|
|
104
|
+
|
|
105
|
+
/** A column-header row: a thead row or a row made only of th cells. A th first in a body row is a row header. */
|
|
106
|
+
const isHeaderRow = (row) => row.parent?.tag === "thead" || (cellsOf(row).length > 0 && cellsOf(row).every((cur) => cur.tag === "th"))
|
|
107
|
+
|
|
108
|
+
/** The header of the cell's visual column; nothing binds in a table with a rowspan or without a header row. */
|
|
109
|
+
function headerUnit(node) {
|
|
110
|
+
const cell = closest(node, (cur) => cur.tag === "td")
|
|
111
|
+
const table = cell && closest(cell, (cur) => cur.tag === "table")
|
|
112
|
+
if (!table || cell.attrs.colspan || cell.attrs.rowspan) return null
|
|
113
|
+
const rows = rowsOf(table)
|
|
114
|
+
if (rows.some((row) => row.children.some((cur) => cur.attrs?.rowspan && cur.attrs.rowspan !== "1"))) return null
|
|
115
|
+
const header = rows.find(isHeaderRow)
|
|
116
|
+
if (!header || header === cell.parent) return null
|
|
117
|
+
const head = cellsOf(header)[cellsOf(cell.parent).indexOf(cell)]
|
|
118
|
+
if (!head) return null
|
|
119
|
+
const text = proseText(head)
|
|
120
|
+
const label = (/\(([^()]+)\)\s*$/u.exec(text)?.[1] ?? text).trim()
|
|
121
|
+
const unit = unitOf(label) ?? unitOf(label.split(/\s+/u).at(-1) ?? "")
|
|
122
|
+
return unit ? { token: unit } : null
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/** The documented split form: the next element is this fact's own :unit element. */
|
|
126
|
+
function unitElementAfter(siblings, at, row) {
|
|
127
|
+
const next = siblings.slice(at + 1).find((cur) => !(cur.tag === "#text" && !cur.text.trim()))
|
|
128
|
+
return next?.attrs?.["data-fact"] === row.fact_id && unitKey(proseText(next)) === unitKey(row.unit)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/** The text after the element within its block, read through inline wrappers and line breaks ("320 <b>m</b>", "<b>320</b> m", "320<br>m"). */
|
|
132
|
+
function textAfter(node) {
|
|
133
|
+
let text = ""
|
|
134
|
+
for (let cur = node; text.length < 80; ) {
|
|
135
|
+
const next = cur.parent?.children[cur.parent.children.indexOf(cur) + 1]
|
|
136
|
+
if (next === undefined) {
|
|
137
|
+
if (!INLINE.has(cur.parent?.tag) || cur.parent.attrs["data-fact"] !== undefined) break
|
|
138
|
+
cur = cur.parent
|
|
139
|
+
} else if (next.tag === "#text" || next.tag === "br" || (INLINE.has(next.tag) && next.attrs["data-fact"] === undefined)) {
|
|
140
|
+
text += next.tag === "br" ? "\n" : plainText(next)
|
|
141
|
+
cur = next
|
|
142
|
+
} else break
|
|
143
|
+
}
|
|
144
|
+
return text
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const INSIDE = "written inside with"
|
|
148
|
+
/**
|
|
149
|
+
* Every unit bound to a value-mode element: a currency sign before it, the unit after it (or its :unit element), its
|
|
150
|
+
* column header, and a currency sign or word inside it (prefix or suffix of the number).
|
|
151
|
+
*/
|
|
152
|
+
function boundUnits(node, row) {
|
|
153
|
+
const siblings = node.parent.children
|
|
154
|
+
const at = siblings.indexOf(node)
|
|
155
|
+
const prev = siblings[at - 1]
|
|
156
|
+
const shown = proseText(node)
|
|
157
|
+
const number = NUMBER.exec(shown)
|
|
158
|
+
const before = signBefore(prev?.tag === "#text" ? prev.text : "")
|
|
159
|
+
// The element's own text is the row's render: a scale word in it ("3,234\uC5B5", "12 million") is part of the value.
|
|
160
|
+
const inside = number ? [signBefore(shown.slice(0, number.index)), currencyAfter(shown.slice(number.index + number[0].length), row)] : []
|
|
161
|
+
const read = unitAfter(textAfter(node), row) ?? (unitElementAfter(siblings, at, row) ? { token: row.unit } : null)
|
|
162
|
+
const header = headerUnit(node)
|
|
163
|
+
const kind = (unit) => dimensionOf(unit.token)?.split(":")[0]
|
|
164
|
+
const after = read?.ordinary && ![before, ...inside, header].some((unit) => unit && kind(unit) === kind(read)) ? null : read
|
|
165
|
+
return [["preceded by", before], ["followed by", after], ["headed with", header], ...inside.map((unit) => [INSIDE, unit])].filter(([, unit]) => unit)
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/** Each data-fact element's own fill-map entry: fill writes one element per entry, so ids pair up in document order. */
|
|
169
|
+
export function fillEntriesByNode(nodes, entries) {
|
|
170
|
+
const queues = new Map()
|
|
171
|
+
for (const entry of entries ?? []) if (entry.replaced === undefined && entry.translates === undefined) queues.set(entry.fact_id, [...(queues.get(entry.fact_id) ?? []), entry])
|
|
172
|
+
return new Map(nodes.filter((node) => node.attrs["data-replaced"] === undefined).flatMap((node) => {
|
|
173
|
+
const entry = queues.get(node.attrs["data-fact"])?.shift()
|
|
174
|
+
return entry ? [[node, entry]] : []
|
|
175
|
+
}))
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/** Fill's record of the unit bound to each value-mode pointer (unit_bound), read off the filled edition (HTML, or the Markdown projection). */
|
|
179
|
+
export function withBoundUnits(html, entries, rows) {
|
|
180
|
+
const all = (node) => [node, ...node.children.flatMap(all)]
|
|
181
|
+
const filled = fillEntriesByNode(all(parseHtml(html)).filter((node) => node.attrs?.["data-fact"] !== undefined), entries)
|
|
182
|
+
const units = new Map([...filled].flatMap(([node, entry]) => {
|
|
183
|
+
const row = rows.get(entry.fact_id)
|
|
184
|
+
// Only a unit outside the element: the element's own text is the row's render, so it can be no record of a unit beside it.
|
|
185
|
+
const unit = entry.mode === "value" && row && unitKey(row.unit) ? boundUnits(node, row).find(([where]) => where !== INSIDE)?.[1].token : null
|
|
186
|
+
return unit ? [[entry, unit]] : []
|
|
187
|
+
}))
|
|
188
|
+
return entries.map((entry) => (units.has(entry) ? { ...entry, unit_bound: units.get(entry) } : entry))
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Value mode comes from fill: the element's data-fact-mode, or its fill-map entry when the edition has one
|
|
193
|
+
* (so deleting the attribute changes nothing). `entry.unit_bound` is the unit fill saw bound to the value.
|
|
194
|
+
*/
|
|
195
|
+
export function valueUnitProblem(node, row, entry = null) {
|
|
196
|
+
if ((entry?.mode !== "value" && node.attrs["data-fact-mode"] !== "value") || !unitKey(row.unit)) return null
|
|
197
|
+
const found = boundUnits(node, row)
|
|
198
|
+
if (entry?.unit_bound && found.every(([where]) => where === INSIDE)) return ["unit_mismatch", `value-mode ${row.fact_id} had the unit "${entry.unit_bound}" beside it when filled, but no recognizable unit is bound beside it now; its row is in "${row.unit}"`]
|
|
199
|
+
if (found.length === 0) return ["unit_unbound", `${row.fact_id} has unit "${row.unit}" but no recognizable unit right after its value, before it as a currency sign, or in its column header`]
|
|
200
|
+
const wrong = found.find(([, unit]) => !agrees(unit, row))
|
|
201
|
+
return wrong ? ["unit_mismatch", `value-mode ${row.fact_id} is ${wrong[0]} "${wrong[1].token}", but its row is in "${row.unit}"`] : null
|
|
202
|
+
}
|