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,229 @@
|
|
|
1
|
+
// conversion-probe.mjs - numeric multiset differences after source conversion.
|
|
2
|
+
// Inline markup may split one number; superscripts must not merge into it. Numbers are read with the
|
|
3
|
+
// one grouped-number grammar (number-grammar.mjs). A cell, row, list-item, block or line break is a
|
|
4
|
+
// hard boundary: a space never groups digits across it. A sign counts when no Latin, Greek or
|
|
5
|
+
// Cyrillic letter, digit or underscore touches it (an en dash before a digit is a minus too), and a
|
|
6
|
+
// "-" or U+2212 counts after ONE such letter ("\u0394\u22125") or after an exponent ("1e-5"); a run of
|
|
7
|
+
// two or more ("cite_note-7", "AB-12", "UTF-8", "2019-2023") is an identifier or range and carries no
|
|
8
|
+
// negative. Markdown markup a converter writes beside a sign never decides it: the closer run before it
|
|
9
|
+
// (read with the CommonMark inline grammar in markdown-closers.mjs: emphasis, code, strike, titled and
|
|
10
|
+
// parenthesised link and image targets, reference endings, backslash-escaped closers) is skipped to
|
|
11
|
+
// the first character it closes ("**2019**-2023" and '[1990](/wiki/1990 "1990")-2000' are ranges), and
|
|
12
|
+
// an opener run between the sign and its digits ("-**5**") keeps the sign. A raw-side character
|
|
13
|
+
// reference (`−`, ` `) is decoded on BOTH sides with the one entities.mjs table, so a
|
|
14
|
+
// verbatim copy probes clean. Raw inline HTML a converter leaves in the Markdown (pandoc's gfm writer
|
|
15
|
+
// emits `<sup>`, `<sub>`, `<img ... width=320>`, `<span>`, `<br>`) is read with the html-lite tag
|
|
16
|
+
// reader: sup, sub and break tags are boundaries, as on the raw side, and any other tag or comment is
|
|
17
|
+
// dropped with its attributes, so it is a closer or opener like Markdown markup. Digits a converter
|
|
18
|
+
// writes out from a link target, an image source, alt text, or a title cover a converted number only
|
|
19
|
+
// inside Markdown link or image syntax (`](...)` with any title and balanced parentheses,
|
|
20
|
+
// ``, `<https://...>`, `[ref]: url "title"` with the title on the same or the next line),
|
|
21
|
+
// never in prose; a footnote
|
|
22
|
+
// definition's text ("[^1]: ...") is prose. Converter syntax is not a number: a line-start list marker
|
|
23
|
+
// ("1. ", "2) ", "(3) ", indented or after "> ") is exempt only while the source has an <ol> item with
|
|
24
|
+
// that number and the markers so far do not outnumber the <ol> items (so lazy "1. 1. 1." numbering
|
|
25
|
+
// over three items passes), a numeric reference label ("[x][1]" with its "[1]: url") only while both
|
|
26
|
+
// halves are present, and a footnote definition label ("[^1]:") only while its "[^1]" is used;
|
|
27
|
+
// otherwise each is a candidate. A converter's hard wrap may split a space-grouped number over one line
|
|
28
|
+
// break; the two halves read as one number only when the raw side holds that number and not both
|
|
29
|
+
// halves, so raw "12 345 and 12 and 345" wrapped inside "12 345" reports both halves (it fails safe).
|
|
30
|
+
// The built-in snapshot writes no attribute text, syntax, wraps or tags, and its text is already
|
|
31
|
+
// decoded, so it gets none of these allowances. Constructs outside this grammar are declared out of
|
|
32
|
+
// scope in references/fact-ledger.md: they report as altered (the safe direction).
|
|
33
|
+
|
|
34
|
+
import { existsSync, readFileSync } from "node:fs"
|
|
35
|
+
import { join } from "node:path"
|
|
36
|
+
|
|
37
|
+
import { CliError } from "./cli-support.mjs"
|
|
38
|
+
import { decodeEntities, parseHtml, PROSE_BREAKS, readTag } from "./html-lite.mjs"
|
|
39
|
+
import { escapedAt, inlineLinkSpans, stripClosers } from "./markdown-closers.mjs"
|
|
40
|
+
import { NUMBER_SOURCE, plainDecimal } from "./number-grammar.mjs"
|
|
41
|
+
|
|
42
|
+
const LETTER = "\\p{Script=Latin}\\p{Script=Greek}\\p{Script=Cyrillic}"
|
|
43
|
+
const WORD = new RegExp(`[${LETTER}\\p{N}_]`, "u")
|
|
44
|
+
const ALPHA = new RegExp(`[${LETTER}]`, "u")
|
|
45
|
+
const EXPONENT = new RegExp(`(?<![${LETTER}\\p{N}_.,])\\d+(?:[.,]\\d+)?[eE]$`, "u")
|
|
46
|
+
/** A dash, then any Markdown opener run, then a number; signed() decides whether the dash is a minus. */
|
|
47
|
+
const NUMBERS = new RegExp(`(?:([-\\u2212\\u2013])([*_~\`[]*))?(${NUMBER_SOURCE})`, "gu")
|
|
48
|
+
/** Sup, sub, datetime chunks and every break join no neighbour: neither a digit, a separator, nor whitespace. */
|
|
49
|
+
const BOUNDARY = "\u0001"
|
|
50
|
+
const CARRIED = ["href", "src", "alt", "title"]
|
|
51
|
+
const LIST_MARKER = /(?:^|\u0001)(?: ?>)* ?(?:\d+[.)]|\(\d+\)) /gu
|
|
52
|
+
const REF_USE = /\]\[(\d+)\]/gu
|
|
53
|
+
const REF_DEF = /(?:^|\u0001)(?: ?>)* ?\[(\d+)\]:/gu
|
|
54
|
+
const NOTE_USE = /\[\^(\d+)\](?!:)/gu
|
|
55
|
+
const NOTE_DEF = /(?:^|\u0001)(?: ?>)* ?\[\^(\d+)\]:/gu
|
|
56
|
+
const TITLE = `(?:"[^"\u0001]*"|'[^'\u0001]*'|\\([^)\u0001]*\\))`
|
|
57
|
+
/** A reference definition's title may sit alone on the next line (only whitespace after it). */
|
|
58
|
+
const REF_DEFINITION = `(?:^|\u0001) ?\\[(?!\\^)[^\\]\u0001]+\\]: ?[^\\s\u0001]*(?: ${TITLE}| ?\u0001 ?${TITLE}(?= ?(?:\u0001|$)))?`
|
|
59
|
+
const LINK_SYNTAX = new RegExp(`!\\[[^\\]\u0001]*\\](?:\\([^)\u0001]*\\))?|\\]\\([^)\u0001]*\\)|<https?:\\/\\/[^>\\s\u0001]+>|${REF_DEFINITION}`, "gu")
|
|
60
|
+
const TAG_NAME = /^[a-z][a-z0-9-]*$/u
|
|
61
|
+
/** What a tag leaves in the numeric text on either side: sup, sub and every break join no neighbour. */
|
|
62
|
+
const edgeOf = (tag) => (tag === "sup" || tag === "sub" || PROSE_BREAKS.has(tag) ? BOUNDARY : "")
|
|
63
|
+
|
|
64
|
+
/** Prose text, attribute text and <ol> item numbers of an HTML document; a non-HTML document is prose only. */
|
|
65
|
+
function numericText(source, html) {
|
|
66
|
+
if (!html) return { prose: decodeEntities(source), carried: "", listed: [] }
|
|
67
|
+
const chunks = []
|
|
68
|
+
const carried = []
|
|
69
|
+
const listed = []
|
|
70
|
+
const walk = (node) => {
|
|
71
|
+
if (node.tag === "#text") { chunks.push(node.text.replace(/\s+/gu, " ")); return }
|
|
72
|
+
for (const name of CARRIED) if (node.attrs?.[name] !== undefined) carried.push(node.attrs[name])
|
|
73
|
+
if (node.tag === "ol") {
|
|
74
|
+
const start = /^\s*\d+\s*$/u.test(node.attrs.start ?? "") ? Number(node.attrs.start) : 1
|
|
75
|
+
node.children.filter((child) => child.tag === "li").forEach((_, index) => listed.push(String(start + index)))
|
|
76
|
+
}
|
|
77
|
+
const edge = edgeOf(node.tag)
|
|
78
|
+
chunks.push(edge)
|
|
79
|
+
if (node.tag === "time" && node.attrs.datetime !== undefined) chunks.push(`${BOUNDARY}${node.attrs.datetime}${BOUNDARY}`)
|
|
80
|
+
for (const child of node.children) walk(child)
|
|
81
|
+
chunks.push(edge)
|
|
82
|
+
}
|
|
83
|
+
walk(parseHtml(source))
|
|
84
|
+
return { prose: chunks.join(""), carried: carried.join(BOUNDARY), listed }
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** Raw inline HTML in converted Markdown: sup, sub and break tags become boundaries, other tags and comments go. */
|
|
88
|
+
function inlineHtml(text) {
|
|
89
|
+
let out = ""
|
|
90
|
+
let kept = 0
|
|
91
|
+
for (let lt = text.indexOf("<"); lt !== -1; lt = text.indexOf("<", Math.max(lt + 1, kept))) {
|
|
92
|
+
const comment = text.startsWith("<!--", lt)
|
|
93
|
+
const name = lt + (text[lt + 1] === "/" ? 2 : 1)
|
|
94
|
+
if (escapedAt(text, lt) || (!comment && !/[a-z]/iu.test(text[name] ?? ""))) continue
|
|
95
|
+
const tag = comment ? null : readTag(text, name)
|
|
96
|
+
const end = comment ? text.indexOf("-->", lt + 4) + 3 : tag.end
|
|
97
|
+
if (end <= lt || (tag && !(TAG_NAME.test(tag.name) && text[end - 1] === ">")) || /\n[ \t\r]*\n/u.test(text.slice(lt, end))) continue
|
|
98
|
+
out += text.slice(kept, lt) + (tag ? edgeOf(tag.name) : "")
|
|
99
|
+
kept = end
|
|
100
|
+
}
|
|
101
|
+
return out + text.slice(kept)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Start offsets of numeric reference labels whose use ("][1]") and definition ("[1]:") are both present,
|
|
106
|
+
* and of footnote definition labels ("[^1]:") whose "[^1]" is used (the use itself mirrors the source).
|
|
107
|
+
*/
|
|
108
|
+
function referenceLabels(flat) {
|
|
109
|
+
const uses = [...flat.matchAll(REF_USE)]
|
|
110
|
+
const defs = [...flat.matchAll(REF_DEF)]
|
|
111
|
+
const used = new Set(uses.map((match) => match[1]))
|
|
112
|
+
const defined = new Set(defs.map((match) => match[1]))
|
|
113
|
+
const paired = [...uses.filter((match) => defined.has(match[1])), ...defs.filter((match) => used.has(match[1]))]
|
|
114
|
+
const noted = new Set([...flat.matchAll(NOTE_USE)].map((match) => match[1]))
|
|
115
|
+
const notes = [...flat.matchAll(NOTE_DEF)].filter((match) => noted.has(match[1]))
|
|
116
|
+
return new Set([...paired, ...notes].map((match) => match.index + match[0].search(/\d/u)))
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** Whether the dash at `at` is a minus: Markdown closers (and a backslash escaping the dash) before it are skipped, as is an opener run before a lone letter. */
|
|
120
|
+
function signed(flat, at, dash) {
|
|
121
|
+
const before = stripClosers(flat.slice(Math.max(0, at - 512), escapedAt(flat, at) ? at - 1 : at))
|
|
122
|
+
const last = before.at(-1) ?? ""
|
|
123
|
+
if (!WORD.test(last)) return true
|
|
124
|
+
if (dash === "\u2013") return false
|
|
125
|
+
if (EXPONENT.test(before)) return true
|
|
126
|
+
return ALPHA.test(last) && !WORD.test(before.slice(0, -1).replace(/[*_~`[]+$/u, "").at(-1) ?? "")
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Line breaks become boundaries, then whitespace runs one space; `linked` marks tokens inside link
|
|
131
|
+
* syntax, `marker` a line-start list marker, `label` a paired reference label, `gap` the text since
|
|
132
|
+
* the previous number.
|
|
133
|
+
*/
|
|
134
|
+
function tokensOf(text, { links = false } = {}) {
|
|
135
|
+
const flat = text.replace(/\r\n?|\n|\f/gu, BOUNDARY).replace(/\s+/gu, " ")
|
|
136
|
+
const spans = links ? [...[...flat.matchAll(LINK_SYNTAX)].map((match) => [match.index, match.index + match[0].length]), ...inlineLinkSpans(flat)] : []
|
|
137
|
+
const markers = new Set([...flat.matchAll(LIST_MARKER)].map((match) => match.index + match[0].search(/\d/u)))
|
|
138
|
+
const labels = referenceLabels(flat)
|
|
139
|
+
let previousEnd = 0
|
|
140
|
+
return [...flat.matchAll(NUMBERS)].map((match, n) => {
|
|
141
|
+
const sign = match[1] !== undefined && signed(flat, match.index, match[1])
|
|
142
|
+
const at = sign ? match.index : match.index + match[0].length - match[3].length
|
|
143
|
+
const gap = flat.slice(previousEnd, at)
|
|
144
|
+
previousEnd = match.index + match[0].length
|
|
145
|
+
return {
|
|
146
|
+
n,
|
|
147
|
+
gap,
|
|
148
|
+
text: sign ? match[0] : match[3],
|
|
149
|
+
key: `${sign ? "-" : ""}${plainDecimal(match[3])}`,
|
|
150
|
+
linked: spans.some(([start, end]) => at >= start && at < end),
|
|
151
|
+
marker: markers.has(at),
|
|
152
|
+
label: labels.has(at),
|
|
153
|
+
}
|
|
154
|
+
})
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/** The comparison key of one number token, so other tools can match a converted number against a probe record. */
|
|
158
|
+
export const numberKey = (token) => tokensOf(String(token))[0]?.key ?? null
|
|
159
|
+
|
|
160
|
+
/** The numbers in `texts` that the source's conversion probe recorded as altered (sources/<id>/conversion.json). */
|
|
161
|
+
export function alteredIn(sourceDir, texts) {
|
|
162
|
+
const record = join(sourceDir, "conversion.json")
|
|
163
|
+
let altered = []
|
|
164
|
+
if (existsSync(record)) {
|
|
165
|
+
try {
|
|
166
|
+
altered = JSON.parse(readFileSync(record, "utf8")).conversion_altered_numbers ?? []
|
|
167
|
+
} catch (error) {
|
|
168
|
+
throw new CliError(`${record} is not a readable conversion record (${error.message}); snapshot the source again`)
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
const keys = new Set(altered.map(numberKey))
|
|
172
|
+
return texts.flatMap((text) => tokensOf(String(text ?? ""))).filter((token) => keys.has(token.key)).map((token) => token.text)
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/** Rejoin a grouped number a hard wrap split over one line break, when only the joined reading is in the raw counts. */
|
|
176
|
+
function joinWrapped(tokens, raw) {
|
|
177
|
+
const out = []
|
|
178
|
+
for (const token of tokens) {
|
|
179
|
+
const prev = out.at(-1)
|
|
180
|
+
const wrapped = prev?.n === token.n - 1 && /^ ?\u0001 ?$/u.test(token.gap) && !prev.linked && !token.linked
|
|
181
|
+
const [joined, ...more] = wrapped ? tokensOf(`${prev.text} ${token.text}`) : []
|
|
182
|
+
if (joined && more.length === 0 && raw.has(joined.key) && !(raw.has(prev.key) && raw.has(token.key))) {
|
|
183
|
+
out[out.length - 1] = { ...joined, n: token.n, text: `${prev.text}\n${token.text}` }
|
|
184
|
+
continue
|
|
185
|
+
}
|
|
186
|
+
out.push(token)
|
|
187
|
+
}
|
|
188
|
+
return out
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const countOf = (tokens) => tokens.reduce((counts, token) => counts.set(token.key, (counts.get(token.key) ?? 0) + 1), new Map())
|
|
192
|
+
|
|
193
|
+
/** The tokens `counts` cannot cover; each covered token uses up one count. */
|
|
194
|
+
function uncovered(tokens, counts) {
|
|
195
|
+
return tokens.filter((token) => {
|
|
196
|
+
const remaining = counts.get(token.key) ?? 0
|
|
197
|
+
if (remaining === 0) return true
|
|
198
|
+
counts.set(token.key, remaining - 1)
|
|
199
|
+
return false
|
|
200
|
+
})
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Preserve token spelling in diagnostics; the raw side is read as HTML, the converted side as Markdown
|
|
205
|
+
* with raw inline tags, both decoded with one entity table. `attributes: false`
|
|
206
|
+
* (the built-in snapshot) gives attribute digits no cover, converter syntax no exemption and no wrap rejoin. An exempt
|
|
207
|
+
* marker or label is not a converted number at all: it neither covers a raw number nor counts as altered.
|
|
208
|
+
*/
|
|
209
|
+
export function probeConversion(raw, converted, { attributes = true } = {}) {
|
|
210
|
+
const source = numericText(raw, /<[a-z!/]/i.test(raw))
|
|
211
|
+
const before = tokensOf(source.prose)
|
|
212
|
+
const listed = new Set(source.listed)
|
|
213
|
+
let items = attributes ? source.listed.length : 0
|
|
214
|
+
const syntax = tokensOf(attributes ? decodeEntities(inlineHtml(converted)) : converted, { links: attributes })
|
|
215
|
+
.filter((token) => !(attributes && token.label) && !(token.marker && listed.has(token.key) && items-- > 0))
|
|
216
|
+
const after = attributes ? joinWrapped(syntax, countOf(before)) : syntax
|
|
217
|
+
const counts = countOf(before)
|
|
218
|
+
const extra = new Set(uncovered(after.filter((token) => !token.linked), counts))
|
|
219
|
+
for (const token of tokensOf(source.carried)) counts.set(token.key, (counts.get(token.key) ?? 0) + 1)
|
|
220
|
+
for (const token of uncovered(after.filter((token) => token.linked), counts)) extra.add(token)
|
|
221
|
+
const lost = uncovered(before, countOf(after)).map((token) => token.text)
|
|
222
|
+
return { altered: after.filter((token) => extra.has(token)).map((token) => token.text), lost, raw_count: before.length, converted_count: after.length }
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
export function runConversionProbe(argv) {
|
|
226
|
+
if (argv.length !== 2) throw new CliError("usage: source probe-conversion <raw-file> <converted-file>")
|
|
227
|
+
const result = probeConversion(readFileSync(argv[0], "utf8"), readFileSync(argv[1], "utf8"))
|
|
228
|
+
return { json: result, summary: `source probe-conversion: ${result.altered.length} altered, ${result.lost.length} lost`, exitCode: result.altered.length === 0 ? 0 : 1 }
|
|
229
|
+
}
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import { describe, expect, onTestFinished, test } from "bun:test"
|
|
2
|
+
import { spawnSync } from "node:child_process"
|
|
3
|
+
import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs"
|
|
4
|
+
import { tmpdir } from "node:os"
|
|
5
|
+
import { join } from "node:path"
|
|
6
|
+
|
|
7
|
+
import { numberKey } from "./conversion-probe.mjs"
|
|
8
|
+
import { readFidelityRecord } from "./fidelity-records.mjs"
|
|
9
|
+
import { writeSnapshot } from "./source-snapshot.mjs"
|
|
10
|
+
|
|
11
|
+
const CLI = join(import.meta.dir, "report-tools.mjs")
|
|
12
|
+
const FIXTURE = readFileSync(join(import.meta.dir, "tests/fixtures/conversion-probe.html"), "utf8")
|
|
13
|
+
const FLATTENED = FIXTURE.replace(/<[^>]*>/g, "")
|
|
14
|
+
// Superscript boundaries preserve 4.2 + 2, 27.3 + 1 and 10 + 9 in raw.
|
|
15
|
+
const ALTERED = ["4.22", "27.31", "109"]
|
|
16
|
+
|
|
17
|
+
function sandbox(): string {
|
|
18
|
+
const dir = mkdtempSync(join(tmpdir(), "conversion-probe-"))
|
|
19
|
+
onTestFinished(() => rmSync(dir, { recursive: true, force: true }))
|
|
20
|
+
return dir
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function probe(raw: string, converted: string) {
|
|
24
|
+
const dir = sandbox()
|
|
25
|
+
const rawFile = join(dir, "raw.html")
|
|
26
|
+
const convertedFile = join(dir, "converted.txt")
|
|
27
|
+
writeFileSync(rawFile, raw)
|
|
28
|
+
writeFileSync(convertedFile, converted)
|
|
29
|
+
const proc = spawnSync("node", [CLI, "source", "probe-conversion", rawFile, convertedFile], { encoding: "utf8" })
|
|
30
|
+
expect(proc.error).toBeUndefined()
|
|
31
|
+
expect(proc.stdout, proc.stderr).not.toBe("")
|
|
32
|
+
return { exitCode: proc.status, json: JSON.parse(proc.stdout) }
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
describe("source probe-conversion", () => {
|
|
36
|
+
test("#given the probe page #when naively flattened #then only fabricated numbers are altered", () => {
|
|
37
|
+
const result = probe(FIXTURE, FLATTENED)
|
|
38
|
+
expect(result.exitCode).toBe(1)
|
|
39
|
+
// Stripping the adjacent table tags also joins Total's 2 to the first cell.
|
|
40
|
+
// The flatten glues Total's 2 onto 12,345 and 12,345 onto 15,678: "212,34515,678".
|
|
41
|
+
expect(result.json.altered).toEqual([...ALTERED, "212,34515", "678"])
|
|
42
|
+
expect(result.json.lost).toContain("2026")
|
|
43
|
+
expect(result.json.lost).not.toContain("99.9")
|
|
44
|
+
expect(result.json.altered).not.toContain("99.9")
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
test("#given a plain text #when compared to itself #then identity passes without alterations", () => {
|
|
48
|
+
const text = "USD 4.2 billion; 1,234.5 units; 1 234 567 people; -3.5 and 2019-2023."
|
|
49
|
+
const result = probe(text, text)
|
|
50
|
+
expect(result.exitCode).toBe(0)
|
|
51
|
+
expect(result.json.altered).toEqual([])
|
|
52
|
+
expect(result.json.lost).toEqual([])
|
|
53
|
+
expect(result.json.converted_count).toBe(result.json.raw_count)
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
test("#given a tag-split decimal #when compared with text #then each side has one number", () => {
|
|
57
|
+
const result = probe("<span>1</span><span>,</span><span>234.5</span>", "1,234.5")
|
|
58
|
+
expect(result).toEqual({ exitCode: 0, json: { altered: [], lost: [], raw_count: 1, converted_count: 1 } })
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
test.each(["\u202f", "\u00a0"])("#given %s thousands separators #when converted to spaces #then grouping is equivalent", (separator: string) => {
|
|
62
|
+
const result = probe(`1${separator}234${separator}567`, "1 234 567")
|
|
63
|
+
expect(result).toEqual({ exitCode: 0, json: { altered: [], lost: [], raw_count: 1, converted_count: 1 } })
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
test("#given a machine date #when its attribute is removed #then it is lost, not altered", () => {
|
|
67
|
+
const result = probe('<time datetime="2026-10-01T09:00:00Z">1 Oct</time>', "1 Oct")
|
|
68
|
+
expect(result.exitCode).toBe(0)
|
|
69
|
+
expect(result.json.altered).toEqual([])
|
|
70
|
+
expect(result.json.lost).toEqual(["2026", "10", "01", "09", "00", "00"])
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
test("#given repeated signed decimals #when one count increases #then multiset subtraction preserves the excess", () => {
|
|
74
|
+
const result = probe("-4.2 +7 2", "-4.2 +7 2; 2")
|
|
75
|
+
expect(result.exitCode).toBe(1)
|
|
76
|
+
expect(result.json.altered).toEqual(["2"])
|
|
77
|
+
expect(result.json.lost).toEqual([])
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
test("#given hidden text #when omitted by conversion #then the number is only lost", () => {
|
|
81
|
+
const result = probe('<span style="display:none">99.9</span>', "")
|
|
82
|
+
expect(result.exitCode).toBe(0)
|
|
83
|
+
expect(result.json.altered).toEqual([])
|
|
84
|
+
expect(result.json.lost).toEqual(["99.9"])
|
|
85
|
+
})
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
describe("one grouped-number grammar: a digit glued after the last group is an alteration", () => {
|
|
89
|
+
test("#given a footnote glued onto a grouped number #when probed #then it is altered", () => {
|
|
90
|
+
expect(probe("$1,234<sup>5</sup> million", "$1,2345 million").json.altered).toEqual(["1,2345"])
|
|
91
|
+
expect(probe("<table><tr><td>12,345<sup>1</sup></td></tr></table>", "12,3451").json.altered).toEqual(["12,3451"])
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
test("#given the same cell through the built-in snapshot #when written #then conversion.json lists it", () => {
|
|
95
|
+
const { dir } = writeSnapshot(join(sandbox(), "sources"), "S1", { path: "t.html", html: "<table><tr><td>12,345<sup>1</sup></td></tr></table>", extractor: "model", observed_at: "2026-10-25T00:00:00Z" })
|
|
96
|
+
expect(readFidelityRecord(join(dir, "conversion.json"), "source_conversion").conversion_altered_numbers).toEqual(["12,3451"])
|
|
97
|
+
})
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
describe("Markdown-shaped converter output reports only real artifacts", () => {
|
|
101
|
+
const RAW = [
|
|
102
|
+
"<p>Revenue was 4.2<sup>2</sup> billion.<sup><a href=\"#cite_note-7\">[7]</a></sup></p>",
|
|
103
|
+
"<p><a href=\"https://example.com/2024/03/report-4\">Report</a> covers q4-2023.</p>",
|
|
104
|
+
"<p><img src=\"chart-640x480.png\" alt=\"Chart 2023\" title=\"Fig 4\"></p>",
|
|
105
|
+
"<table><tr><td>12,345<sup>1</sup></td><td>1 234 567</td></tr></table>",
|
|
106
|
+
"<p>From 2019\u20132023 the index fell \u22123.5 points.</p>",
|
|
107
|
+
].join("\n")
|
|
108
|
+
const MARKDOWN = [
|
|
109
|
+
"Revenue was 4.22 billion.[[7]](#cite_note-7)",
|
|
110
|
+
"",
|
|
111
|
+
"[Report](https://example.com/2024/03/report-4) covers q4-2023.",
|
|
112
|
+
"",
|
|
113
|
+
"",
|
|
114
|
+
"",
|
|
115
|
+
"| 12,3451 | 1\u2009234\u2009567 |",
|
|
116
|
+
"",
|
|
117
|
+
"From 2019-2023 the index fell -3.5 points.",
|
|
118
|
+
].join("\n")
|
|
119
|
+
|
|
120
|
+
test("#given markdownify-shaped text #when probed #then altered is exactly 4.22 and 12,3451", () => {
|
|
121
|
+
expect(probe(RAW, MARKDOWN).json.altered).toEqual(["4.22", "12,3451"])
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
test("#given the same page through the built-in snapshot #when written #then only the real artifacts are altered", () => {
|
|
125
|
+
const { dir } = writeSnapshot(join(sandbox(), "sources"), "S1", { path: "m.html", html: RAW, extractor: "model", observed_at: "2026-10-25T00:00:00Z" })
|
|
126
|
+
expect(readFidelityRecord(join(dir, "conversion.json"), "source_conversion").conversion_altered_numbers).toEqual(["4.22", "12,3451"])
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
test("#given a converted text that looks like a tag #when probed #then it is never parsed as HTML", () => {
|
|
130
|
+
expect(probe("<p>if a<b then revenue was 4.2<sup>2</sup> billion</p>", "if a<b then revenue was 4.22 billion").json.altered).toEqual(["4.22"])
|
|
131
|
+
})
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
describe("whitespace and separators match the snapshot side", () => {
|
|
135
|
+
for (const [raw, converted] of [
|
|
136
|
+
["<p>1 234 567</p>", "1 234 567"],
|
|
137
|
+
["<p>1 234</p>", "1 234"],
|
|
138
|
+
["<table><tr><td>2,500<br>300</td></tr></table>", "2,500 300"],
|
|
139
|
+
["<ul><li><p>in 2023</p><p>500 sites</p></li></ul>", "in 2023 500 sites"],
|
|
140
|
+
["<p>1,250\n300</p>", "1,250 300"],
|
|
141
|
+
["<p><span>1</span>\n<span>234</span></p>", "1 234"],
|
|
142
|
+
]) {
|
|
143
|
+
test(`#given ${JSON.stringify(raw)} #when converted to ${JSON.stringify(converted)} #then nothing is altered`, () => {
|
|
144
|
+
const result = probe(raw, converted)
|
|
145
|
+
expect(result.json.altered).toEqual([])
|
|
146
|
+
expect(result.exitCode).toBe(0)
|
|
147
|
+
})
|
|
148
|
+
}
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
test("#given HTML snapshot conversion #when written #then source warnings use the fidelity envelope", () => {
|
|
152
|
+
const { dir } = writeSnapshot(join(sandbox(), "sources"), "S1", {
|
|
153
|
+
path: "fixture.html", html: FIXTURE, extractor: "model", observed_at: "2026-10-25T00:00:00Z",
|
|
154
|
+
})
|
|
155
|
+
const record = readFidelityRecord(join(dir, "conversion.json"), "source_conversion")
|
|
156
|
+
expect(record.conversion_altered_numbers).toEqual(ALTERED)
|
|
157
|
+
expect(record.record_kind).toBe("source_conversion")
|
|
158
|
+
expect(record.artifact_sha256).toMatch(/^[0-9a-f]{64}$/)
|
|
159
|
+
expect(record.report).toBe(join(dir, "source.html"))
|
|
160
|
+
expect(record.written_at).toBe("2026-10-25T00:00:00.000Z")
|
|
161
|
+
expect(record.defects).toEqual([])
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
describe("attribute digits cover only a number inside Markdown link or image syntax", () => {
|
|
165
|
+
const GLUED = "<p>Output reached 10<sup>9</sup> kWh</p>"
|
|
166
|
+
const BOOK = '<p><a href="https://books.example/b?id=x&pg=PA109">book</a></p>'
|
|
167
|
+
|
|
168
|
+
test("#given an href page number 109 #when the prose shows a glued 10<sup>9</sup> #then 109 is altered in flat and Markdown output", () => {
|
|
169
|
+
expect(probe(`${GLUED}${BOOK}`, "Output reached 109 kWh\n\nbook").json.altered).toEqual(["109"])
|
|
170
|
+
expect(probe(`${GLUED}${BOOK}`, "Output reached 109 kWh\n\n[book](https://books.example/b?id=x&pg=PA109)").json.altered).toEqual(["109"])
|
|
171
|
+
})
|
|
172
|
+
|
|
173
|
+
test("#given #ref1..#ref45 links #when 4<sup>2</sup> is glued into 42 #then 42 is altered", () => {
|
|
174
|
+
const links = Array.from({ length: 45 }, (_, k) => `<a href="#ref${k + 1}">note</a>`).join(" ")
|
|
175
|
+
const markdown = Array.from({ length: 45 }, (_, k) => `[note](#ref${k + 1})`).join(" ")
|
|
176
|
+
expect(probe(`<p>Area grew 4<sup>2</sup> times.</p><p>${links}</p>`, `Area grew 42 times.\n\n${markdown}`).json.altered).toEqual(["42"])
|
|
177
|
+
})
|
|
178
|
+
|
|
179
|
+
test("#given an image named chart-109.png #when the built-in snapshot writes the page #then 109 is altered", () => {
|
|
180
|
+
const { dir } = writeSnapshot(join(sandbox(), "sources"), "S1", { path: "g.html", html: `${GLUED}<p><img src="chart-109.png"></p>`, extractor: "model", observed_at: "2026-10-25T00:00:00Z" })
|
|
181
|
+
expect(readFidelityRecord(join(dir, "conversion.json"), "source_conversion").conversion_altered_numbers).toEqual(["109"])
|
|
182
|
+
})
|
|
183
|
+
})
|
|
184
|
+
|
|
185
|
+
describe("cell, row, list-item and line breaks never group digits", () => {
|
|
186
|
+
for (const [raw, converted] of [
|
|
187
|
+
["<table><tr><td>3</td><td>250</td><td>100</td></tr></table>", "| 3 | 250 | 100 |"],
|
|
188
|
+
["<table><tr><td>12</td><td>345</td></tr></table>", "| 12 | 345 |"],
|
|
189
|
+
["<ul><li>3</li><li>250</li></ul>", "- 3\n- 250"],
|
|
190
|
+
["<p>3</p><p>250</p>", "3\n250"],
|
|
191
|
+
]) {
|
|
192
|
+
test(`#given ${JSON.stringify(raw)} #when converted to ${JSON.stringify(converted)} #then nothing is altered or lost`, () => {
|
|
193
|
+
expect(probe(raw, converted)).toMatchObject({ exitCode: 0, json: { altered: [], lost: [] } })
|
|
194
|
+
})
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
test("#given 1 234 567 inside one cell #when converted to a Markdown row #then it stays one grouped number", () => {
|
|
198
|
+
expect(probe("<table><tr><td>1 234 567</td><td>8</td></tr></table>", "| 1 234 567 | 8 |")).toEqual({ exitCode: 0, json: { altered: [], lost: [], raw_count: 2, converted_count: 2 } })
|
|
199
|
+
})
|
|
200
|
+
})
|
|
201
|
+
|
|
202
|
+
describe("a period group needs an unambiguous shape; a dropped minus is altered", () => {
|
|
203
|
+
test("#given 1.234 and 1,234 #when keyed #then they differ, and 1.234.567 still groups", () => {
|
|
204
|
+
expect(numberKey("1.234")).not.toBe(numberKey("1,234"))
|
|
205
|
+
expect(numberKey("1.234.567")).toBe(numberKey("1,234,567"))
|
|
206
|
+
expect(numberKey("1.234,5")).toBe(numberKey("1,234.5"))
|
|
207
|
+
})
|
|
208
|
+
|
|
209
|
+
test("#given a glued 12.34<sup>5</sup> beside a real 12,345 #when flattened #then the glued 12.345 is the altered token", () => {
|
|
210
|
+
expect(probe("<p>12.34<sup>5</sup> and 12,345</p>", "12.345 and 12,345").json.altered).toEqual(["12.345"])
|
|
211
|
+
})
|
|
212
|
+
|
|
213
|
+
test("#given a minus touching Hangul or an en dash minus #when the sign is dropped #then the unsigned number is altered", () => {
|
|
214
|
+
expect(probe("\uAC10\uC18C\uC728-3.5%", "\uAC10\uC18C\uC728 3.5%").json.altered).toEqual(["3.5"])
|
|
215
|
+
expect(probe("fell \u20135%", "fell 5%").json.altered).toEqual(["5"])
|
|
216
|
+
expect(probe("cite_note-7 and 2019\u20132023", "cite_note-7 and 2019-2023").json.altered).toEqual([])
|
|
217
|
+
})
|
|
218
|
+
})
|
package/packages/shared-skills/skills/ulw-research/scripts/copy-returns-fidelity-policy.test.ts
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test"
|
|
2
|
+
import { mkdtempSync, rmSync, writeFileSync } from "node:fs"
|
|
3
|
+
import { tmpdir } from "node:os"
|
|
4
|
+
import { join } from "node:path"
|
|
5
|
+
|
|
6
|
+
import { FILL_INPUT_FORMAT_RULES, FILL_INPUT_FORMATS } from "./fact-commands-fill.mjs"
|
|
7
|
+
import { KIND_RULES } from "./fact-ledger-kinds.mjs"
|
|
8
|
+
import { FILL_FORMATS } from "./fact-fill.mjs"
|
|
9
|
+
import { meaningCheckEnabled as briefMeaningCheckEnabled } from "./fidelity-brief.mjs"
|
|
10
|
+
import { FIDELITY_VALUE_RULES, meaningCheckEnabled, parseSourceFloor, policyErrors, SOURCE_FLOOR_RULES, sourceInsideFloor } from "./fidelity-policy.mjs"
|
|
11
|
+
|
|
12
|
+
type PolicyRow = { fact_id: string; kind: string; status: string; source_id: string | null; operands: string[]; derived_from: string[] }
|
|
13
|
+
const policyRow = (fact_id: string, kind: string, source_id: string | null, operands: string[] = []): PolicyRow => ({ fact_id, kind, status: "active", source_id, operands, derived_from: [] })
|
|
14
|
+
|
|
15
|
+
/** policyErrors over one placeholder per row id under a parsed floor, as code and message pairs. */
|
|
16
|
+
function floorRefusals(rows: PolicyRow[], ids: string[], floor: ReturnType<typeof parseSourceFloor>) {
|
|
17
|
+
const found = ids.map((id, index) => ({ token: `{{${id}}}`, start: index, parsed: { id } }))
|
|
18
|
+
const refused = policyErrors(ids.map((id) => `{{${id}}}`).join(""), found, { rows }, tmpdir(), { derivedNone: false, floor })
|
|
19
|
+
return refused.errors.map((error: { code: string; message: string }) => [error.code, error.message])
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const referenceValues = async (field: string) => {
|
|
23
|
+
const row = (await Bun.file(REFERENCE).text()).split("\n").find((line) => line.startsWith(`| \`${field}\` |`)) ?? ""
|
|
24
|
+
return [...(row.split("|")[2] ?? "").matchAll(/`([^`]+)`/gu)].map((match) => match[1])
|
|
25
|
+
}
|
|
26
|
+
import { pointedFlag } from "./source-commands.mjs"
|
|
27
|
+
|
|
28
|
+
const REFERENCE = join(import.meta.dir, "..", "references", "deliverable-phase.md")
|
|
29
|
+
|
|
30
|
+
function briefWithFloor(floor: string) {
|
|
31
|
+
const dir = mkdtempSync(join(tmpdir(), "policy-registry-"))
|
|
32
|
+
const lines = ["as of: latest at run start, 2026-10-23", "meaning check: on", `source floor: ${floor}`, "derived numbers: allowed", "languages: report en; sources en; quotes original", "identifier classes: none", "fallback: block-quote"]
|
|
33
|
+
writeFileSync(join(dir, "brief.md"), `## Fidelity\n${lines.map((line) => `${line} (answered_by: default)`).join("\n")}\n`)
|
|
34
|
+
return dir
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const floorOf = (text: string) => {
|
|
38
|
+
const parsed = parseSourceFloor(text)
|
|
39
|
+
if (parsed === null) throw new Error(`parseSourceFloor(${text}) read no floor`)
|
|
40
|
+
return parsed
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const derivedRefusal = (kind: string) => {
|
|
44
|
+
const row = { fact_id: "F1", kind, status: "active", source_id: null, operands: [], derived_from: [] }
|
|
45
|
+
const found = [{ token: "{{F1}}", start: 0, parsed: { id: "F1" } }]
|
|
46
|
+
const refused = policyErrors("{{F1}}", found, { rows: [row] }, tmpdir(), { derivedNone: true, floor: null })
|
|
47
|
+
return refused.errors.some((error: { code: string }) => error.code === "derived_not_allowed")
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
describe("fidelity-policy registry: copies must not return", () => {
|
|
51
|
+
test("#given every ledger kind #when derived numbers none refuses #then policyErrors agrees with FIDELITY_VALUE_RULES and assumed is refused", () => {
|
|
52
|
+
const kinds = Object.keys(KIND_RULES)
|
|
53
|
+
for (const kind of kinds) expect({ kind, refused: derivedRefusal(kind) }).toEqual({ kind, refused: FIDELITY_VALUE_RULES.derivedRefused(kind) })
|
|
54
|
+
for (const kind of FIDELITY_VALUE_RULES.derivedKinds) expect(kinds).toContain(kind)
|
|
55
|
+
expect(FIDELITY_VALUE_RULES.derivedRefused("assumed")).toBe(true)
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
test("#given each source floor form #when parsed #then the rule matches the registry and an unknown form is a usage error", () => {
|
|
59
|
+
expect(parseSourceFloor("standard")).toBeNull()
|
|
60
|
+
expect(parseSourceFloor("Primary Only")).toEqual({ kind: "primary" })
|
|
61
|
+
expect(parseSourceFloor("pointed only: S4, https://a.example/x")).toEqual({ kind: "pointed", list: ["S4", "https://a.example/x"] })
|
|
62
|
+
expect(parseSourceFloor("classes: example.com, gov.uk")).toEqual({ kind: "classes", list: ["example.com", "gov.uk"] })
|
|
63
|
+
for (const rule of SOURCE_FLOOR_RULES) expect(Object.isFrozen(rule)).toBe(true)
|
|
64
|
+
expect(() => parseSourceFloor("only nice sources")).toThrow(/source floor/)
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
test("#given pointed and classes floors #when evaluated #then ids, urls, hosts, and suffixes match and notexample.com does not", () => {
|
|
68
|
+
const pointed = floorOf("pointed only: S4, https://a.example/x")
|
|
69
|
+
expect(sourceInsideFloor(pointed, "S4", { url: null })).toBe(true)
|
|
70
|
+
expect(sourceInsideFloor(pointed, "S9", { url: "https://a.example/x" })).toBe(true)
|
|
71
|
+
expect(sourceInsideFloor(pointed, "S9", { url: "https://a.example/y" })).toBe(false)
|
|
72
|
+
const classes = floorOf("classes: example.com")
|
|
73
|
+
for (const [url, inside] of [["https://example.com/a", true], ["https://docs.example.com/a", true], ["https://notexample.com/a", false], [null, false]] as const) {
|
|
74
|
+
expect({ url, inside: sourceInsideFloor(classes, "S1", { url }) }).toEqual({ url, inside })
|
|
75
|
+
}
|
|
76
|
+
expect(sourceInsideFloor({ kind: "primary" }, "S1", { provenance: "primary" })).toBe(true)
|
|
77
|
+
expect(sourceInsideFloor({ kind: "primary" }, "S1", { provenance: "secondary" })).toBe(false)
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
test("#given a brief floor #when pointedFlag reads it #then it agrees with parseSourceFloor for every form", () => {
|
|
81
|
+
const url = "https://a.example/x"
|
|
82
|
+
for (const floor of ["pointed only: S4, https://a.example/x", "pointed only: S4", "standard", "primary only", "classes: a.example"]) {
|
|
83
|
+
const dir = briefWithFloor(floor)
|
|
84
|
+
try {
|
|
85
|
+
const rule = (() => {
|
|
86
|
+
try {
|
|
87
|
+
return parseSourceFloor(floor)
|
|
88
|
+
} catch {
|
|
89
|
+
return null
|
|
90
|
+
}
|
|
91
|
+
})()
|
|
92
|
+
expect({ floor, flag: pointedFlag(dir, url) }).toEqual({ floor, flag: rule?.kind === "pointed" && rule.list.includes(url) })
|
|
93
|
+
} finally {
|
|
94
|
+
rmSync(dir, { recursive: true, force: true })
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
test("#given a malformed or pending floor #when pointedFlag reads it #then malformed is a usage error and pending is not pointed", () => {
|
|
100
|
+
const malformed = briefWithFloor("not a floor")
|
|
101
|
+
const pending = mkdtempSync(join(tmpdir(), "policy-registry-"))
|
|
102
|
+
writeFileSync(join(pending, "brief.md"), "## Fidelity\nsource floor: pending (answered_by: pending)\n")
|
|
103
|
+
try {
|
|
104
|
+
expect(() => pointedFlag(malformed, "https://a.example/x")).toThrow(/source floor "not a floor"/)
|
|
105
|
+
expect(pointedFlag(pending, "https://a.example/x")).toBe(false)
|
|
106
|
+
} finally {
|
|
107
|
+
for (const dir of [malformed, pending]) rmSync(dir, { recursive: true, force: true })
|
|
108
|
+
}
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
test("#given derived numbers and fallback values #when read #then only their table values pass, any case, and anything else names field and value", () => {
|
|
112
|
+
expect(["none", "None", " none "].map((value) => FIDELITY_VALUE_RULES.derivedNone(value))).toEqual([true, true, true])
|
|
113
|
+
expect(["allowed", "Allowed"].map((value) => FIDELITY_VALUE_RULES.derivedNone(value))).toEqual([false, false])
|
|
114
|
+
for (const value of ["nope", "no", "n/a", "none.", "measured only"]) expect(() => FIDELITY_VALUE_RULES.derivedNone(value)).toThrow(`derived numbers "${value}" is not allowed, none`)
|
|
115
|
+
expect(["block-quote", "Omit"].map((value) => FIDELITY_VALUE_RULES.fallback(value))).toEqual(["quote", "omit"])
|
|
116
|
+
for (const value of ["drop", "omit sentence", "quote"]) expect(() => FIDELITY_VALUE_RULES.fallback(value)).toThrow(`fallback "${value}" is not block-quote, omit`)
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
test("#given the deliverable reference #when its derived numbers and fallback rows are read #then they name exactly the registry tables", async () => {
|
|
120
|
+
for (const [field, table] of Object.entries(FIDELITY_VALUE_RULES.tables)) expect({ field, values: await referenceValues(field) }).toEqual({ field, values: Object.keys(table) })
|
|
121
|
+
expect(Object.keys(FIDELITY_VALUE_RULES.tables)).toEqual(["derived numbers", "fallback"])
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
test("#given sourceless rows #when judged under a strict floor and under standard #then strict refuses each, a derived row over one included, and standard refuses none", () => {
|
|
125
|
+
const rows = [policyRow("F1", "measured", "S1"), policyRow("A1", "assumed", null), policyRow("D1", "derived", null, ["F1", "A1"]), policyRow("D2", "derived", null, ["F9"])]
|
|
126
|
+
const ids = ["F1", "A1", "D1", "D2"]
|
|
127
|
+
expect(floorRefusals(rows, ids, floorOf("pointed only: S1"))).toEqual([
|
|
128
|
+
["source_outside_floor", "A1 rests on no source, outside the source floor"],
|
|
129
|
+
["source_outside_floor", "D1 rests on no source, outside the source floor"],
|
|
130
|
+
["source_outside_floor", "D2 rests on no source, outside the source floor"],
|
|
131
|
+
])
|
|
132
|
+
expect(floorRefusals(rows, ids, parseSourceFloor("standard"))).toEqual([])
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
test("#given the meaning check value rule #when read through the policy or the brief #then both are the same function", () => {
|
|
136
|
+
expect(meaningCheckEnabled).toBe(briefMeaningCheckEnabled)
|
|
137
|
+
for (const value of ["on", "off", "Off", "on; force strong: money", "offline"]) {
|
|
138
|
+
expect({ value, on: FIDELITY_VALUE_RULES.meaningCheck(value) }).toEqual({ value, on: meaningCheckEnabled({ fields: { "meaning check": value } }) })
|
|
139
|
+
}
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
test("#given the fill input vocabulary #when derived #then every input maps to a FILL_FORMATS member and every output format is reachable", () => {
|
|
143
|
+
const target = (format: string) => (FILL_INPUT_FORMAT_RULES.aliases as Record<string, string>)[format] ?? format
|
|
144
|
+
for (const format of FILL_INPUT_FORMATS) expect(FILL_FORMATS as readonly string[]).toContain(target(format))
|
|
145
|
+
for (const format of FILL_FORMATS) expect(FILL_INPUT_FORMATS as readonly string[]).toContain(format)
|
|
146
|
+
for (const format of Object.values(FILL_INPUT_FORMAT_RULES.extensions)) expect(FILL_INPUT_FORMATS as readonly string[]).toContain(format)
|
|
147
|
+
expect(FILL_INPUT_FORMAT_RULES.aliases).toEqual({ tex: "md" })
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
test("#given the deliverable reference #when its source floor row is read #then it names exactly the SOURCE_FLOOR_RULES forms", async () => {
|
|
151
|
+
const row = (await Bun.file(REFERENCE).text()).split("\n").find((line) => line.startsWith("| `source floor` |")) ?? ""
|
|
152
|
+
const values = row.split("|")[2] ?? ""
|
|
153
|
+
const forms = [...values.matchAll(/`([^`]+)`/gu)].map((match) => match[1])
|
|
154
|
+
expect(forms).toEqual(SOURCE_FLOOR_RULES.map((rule) => rule.form))
|
|
155
|
+
})
|
|
156
|
+
})
|