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,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
|
+
}
|
|
@@ -7,12 +7,16 @@
|
|
|
7
7
|
// No model, no network.
|
|
8
8
|
|
|
9
9
|
import { existsSync, readFileSync } from "node:fs"
|
|
10
|
-
import {
|
|
10
|
+
import { resolve, sep } from "node:path"
|
|
11
11
|
|
|
12
|
-
import { sha256Of } from "./cli-support.mjs"
|
|
13
12
|
import { sameValue, unitKey } from "./fidelity-binding.mjs"
|
|
13
|
+
import { readCsv } from "./fidelity-csv.mjs"
|
|
14
14
|
import { claimChecks } from "./fidelity-claims.mjs"
|
|
15
|
+
import { sourceChecks } from "./fidelity-sources.mjs"
|
|
16
|
+
import { convertedHalfFits, followsOriginal, isDerivedRow, originalProblem, renderLocator, staleRender, textAfter } from "./fidelity-originals.mjs"
|
|
17
|
+
import { snapshotCause, unreadableSnapshots } from "./fidelity-snapshot-readability.mjs"
|
|
15
18
|
import { STRUCTURAL, clip, traceChecks } from "./fidelity-trace.mjs"
|
|
19
|
+
import { fillEntriesByNode, valueUnitProblem } from "./fidelity-value-unit.mjs"
|
|
16
20
|
import { parseHtml, proseText } from "./html-lite.mjs"
|
|
17
21
|
import { parseNumeral } from "./numerals.mjs"
|
|
18
22
|
import { div, fromDecimal, sub } from "./rational.mjs"
|
|
@@ -45,11 +49,14 @@ function quoteProblem(row, shown, selector) {
|
|
|
45
49
|
return bare(shown) === bare(want) ? null : ["quote_not_verbatim", `"${clip(shown)}" is not the ${selector} selection of ${row.fact_id}: "${clip(want)}"`]
|
|
46
50
|
}
|
|
47
51
|
|
|
48
|
-
function factProblem(row, shown, attrs = {}) {
|
|
52
|
+
function factProblem(row, shown, attrs = {}, rows = new Map()) {
|
|
53
|
+
const original = originalProblem(row, shown, rows, attrs["data-fact-mode"] === "value")
|
|
54
|
+
if (original) return original
|
|
49
55
|
if (attrs["data-unit"] !== undefined && unitKey(attrs["data-unit"]) !== unitKey(row.unit)) return ["unit_mismatch", `data-unit "${attrs["data-unit"]}" but ${row.fact_id} is in "${row.unit}"`]
|
|
50
56
|
if (attrs.value !== undefined && !sameValue(attrs.value, row.value)) return ["value_mismatch", `value="${attrs.value}" but ${row.fact_id} is ${row.value}`]
|
|
57
|
+
if (convertedHalfFits(row, shown)) return null
|
|
51
58
|
const text = normalizeText(shown)
|
|
52
|
-
const forms = [row.raw, row.render, row.value, row.unit && `${row.value} ${row.unit}`].filter(Boolean).map(normalizeText)
|
|
59
|
+
const forms = [...(["converted", "derived"].includes(row.kind) ? [] : [row.raw, row.render]), row.value, row.unit && `${row.value} ${row.unit}`].filter(Boolean).map(normalizeText)
|
|
53
60
|
if (forms.some((form) => text === form || (text.endsWith(` ${form}`) && !/\d/.test(text.slice(0, -form.length))))) return null
|
|
54
61
|
const parsed = parseNumeral(text)
|
|
55
62
|
if (!parsed) return unitKey(text) === unitKey(row.unit) ? null : ["value_mismatch", `"${clip(shown)}" shows no value of ${row.fact_id} (${row.value})`]
|
|
@@ -73,16 +80,6 @@ export function exportRows(ledger) {
|
|
|
73
80
|
return (ledger?.rows ?? []).filter((row) => row.status === "active" && row.kind !== "quote" && row.value !== null).map((row) => ({ fact_id: row.fact_id, label: row.label, plot_value: row.value, unit: row.unit ?? "" }))
|
|
74
81
|
}
|
|
75
82
|
|
|
76
|
-
function readCsv(path) {
|
|
77
|
-
if (!existsSync(path)) return null
|
|
78
|
-
const lines = readFileSync(path, "utf8").split(/\r?\n/).filter((line) => line.trim() !== "")
|
|
79
|
-
const cells = (line) => line.split(",").map((cell) => cell.trim().replace(/^"(.*)"$/, "$1"))
|
|
80
|
-
const head = cells(lines[0] ?? "")
|
|
81
|
-
const [id, value, unit] = ["fact_id", "plot_value", "unit"].map((name) => head.indexOf(name))
|
|
82
|
-
if (id < 0 || value < 0 || unit < 0 || lines.length < 2) return null
|
|
83
|
-
return lines.slice(1).map((line) => cells(line)).map((c) => ({ fact_id: c[id], plot_value: c[value], unit: c[unit] ?? "" }))
|
|
84
|
-
}
|
|
85
|
-
|
|
86
83
|
const walk = (node) => node.children.filter((child) => child.tag !== "#text").flatMap((child) => [child, ...walk(child)])
|
|
87
84
|
|
|
88
85
|
/** SVG text nodes of a chart: data labels, or ticks with their declared grid. */
|
|
@@ -140,46 +137,57 @@ function chartProblem(ctx, fig, labels, ledger) {
|
|
|
140
137
|
* @param {object} ctx buildContext(...) from gates-static.mjs
|
|
141
138
|
* @param {{ rows: object[] }} ledger
|
|
142
139
|
* @param {{ fact_id: string, rendered: string, selector?: string }[] | null} fillMap Markdown fill map, or null
|
|
143
|
-
* @param {string | null} sourcesDir directory of <S-id>/
|
|
140
|
+
* @param {string | null} sourcesDir directory of <S-id>/ snapshots (source-view.mjs reads them)
|
|
144
141
|
* @param {{ recompute?: (ledger: object, row: object) => { ok: boolean }, asOf?: string | null }} options
|
|
145
142
|
*/
|
|
146
143
|
export function valueChecks(ctx, ledger, fillMap, sourcesDir, options = {}) {
|
|
147
144
|
const out = []
|
|
148
|
-
const flag = (code, node, message) => out.push(ctx.defect(code, node, message))
|
|
145
|
+
const flag = (code, node, message, extra = {}) => out.push({ ...ctx.defect(code, node, message), ...extra })
|
|
149
146
|
const rows = new Map((ledger?.rows ?? []).map((row) => [row.fact_id, row]))
|
|
150
147
|
const cited = new Map()
|
|
148
|
+
const fills = (fillMap ?? []).map((entry) => entry.rendered)
|
|
151
149
|
const check = (id, node, problemOf) => {
|
|
152
150
|
const row = rows.get(id)
|
|
153
151
|
if (!row) return flag("unresolved_placeholder", node, `${id} names no ledger row`)
|
|
154
152
|
if (!cited.has(id)) cited.set(id, node)
|
|
155
153
|
const problem = problemOf(row)
|
|
156
|
-
if (problem)
|
|
154
|
+
if (problem) out.push({ ...ctx.defect(problem[0], node, problem[1]), ...(problem[2] ? { part: problem[2] } : {}) })
|
|
157
155
|
}
|
|
158
156
|
for (const node of ctx.all.filter((cur) => cur.attrs["data-fact"] !== undefined)) {
|
|
159
157
|
const selector = node.attrs["data-quote-sel"] ?? "full"
|
|
160
|
-
|
|
158
|
+
const glued = (row) => isDerivedRow(row) && node.attrs["data-fact-mode"] !== "value" && followsOriginal(row, rows, fills)(textAfter(node)) ? ["value_mismatch", `another original follows ${row.fact_id}: "${clip(textAfter(node))}"`, "original"] : null
|
|
159
|
+
check(node.attrs["data-fact"], node, (row) => (row.kind === "quote" ? quoteProblem(row, withoutFooter(node), selector) : (factProblem(row, proseText(node), node.attrs, rows) ?? glued(row))))
|
|
161
160
|
}
|
|
162
161
|
const docText = bare(proseText(ctx.body))
|
|
163
162
|
for (const entry of fillMap ?? []) {
|
|
164
163
|
check(entry.fact_id, ctx.body, (row) => {
|
|
165
|
-
const problem = row.kind === "quote" ? quoteProblem(row, entry.rendered, entry.selector ?? "full") : factProblem(row, entry.rendered)
|
|
166
|
-
|
|
167
|
-
|
|
164
|
+
const problem = row.kind === "quote" ? quoteProblem(row, entry.rendered, entry.selector ?? "full") : factProblem(row, entry.rendered, { "data-fact-mode": entry.mode }, rows)
|
|
165
|
+
const stale = isDerivedRow(row) ? staleRender(row, entry.rendered, proseText(ctx.body), followsOriginal(row, rows, fills)) : docText.includes(bare(entry.rendered)) ? null : "gone"
|
|
166
|
+
if (problem || stale === null) return problem
|
|
167
|
+
return [row.kind === "quote" ? "quote_not_verbatim" : "value_mismatch", `filled text "${clip(entry.rendered)}" of ${row.fact_id} is no longer in the report${stale === "original" ? " (its original was edited)" : ""}`, ...(stale === "original" ? ["original"] : [])]
|
|
168
168
|
})
|
|
169
169
|
}
|
|
170
170
|
rowChecks(ctx, ledger, rows, cited, options, flag)
|
|
171
|
+
// options.fillEntries: the edition's fill-map entries whose element is in the checked HTML (value mode, fill's unit_bound).
|
|
172
|
+
const facts = ctx.all.filter((cur) => cur.attrs["data-fact"] !== undefined)
|
|
173
|
+
const entries = fillEntriesByNode(facts, options.fillEntries)
|
|
174
|
+
for (const node of facts) {
|
|
175
|
+
const row = rows.get(node.attrs["data-fact"])
|
|
176
|
+
const problem = row && row.kind !== "quote" && !factProblem(row, proseText(node), node.attrs, rows) && valueUnitProblem(node, row, entries.get(node))
|
|
177
|
+
if (problem) flag(problem[0], node, problem[1])
|
|
178
|
+
}
|
|
171
179
|
for (const tr of ctx.all.filter((node) => node.tag === "tr" && node.attrs["data-row-kind"] !== "comparison")) {
|
|
172
180
|
const groups = new Set(ctx.util.descendants(tr).map((node) => rows.get(node.attrs["data-fact"])?.observation_id).filter(Boolean))
|
|
173
181
|
if (groups.size > 1) flag("join_integrity_fail", tr, `one table row joins observations ${[...groups].join(", ")}`)
|
|
174
182
|
}
|
|
175
|
-
const meta = sourceChecks(ctx, rows, cited, sourcesDir, flag)
|
|
183
|
+
const meta = sourceChecks(ctx, rows, cited, sourcesDir, flag, unreadableSnapshots(ctx, rows, cited, sourcesDir, out))
|
|
176
184
|
claimChecks(ctx, rows, (row) => provenance(sourcesDir, meta, row), flag)
|
|
177
185
|
const charts = chartFigures(ctx)
|
|
178
186
|
for (const { node, scope } of ctx.elements) {
|
|
179
187
|
const kind = ctx.kindOf(scope)
|
|
180
188
|
if ((node.tag === "q" || node.tag === "blockquote") && node.attrs["data-fact"] === undefined && kind !== "sources" && kind !== "toc") flag("quote_unbound", node, `<${node.tag}> with no Q row: "${clip(proseText(node))}"`)
|
|
181
189
|
}
|
|
182
|
-
traceChecks(ctx, charts, fillMap, flag, fillSiteFits(rows))
|
|
190
|
+
traceChecks(ctx, charts, fillMap, flag, fillSiteFits(rows), renderLocator(rows, fillMap))
|
|
183
191
|
for (const [fig, labels] of charts) {
|
|
184
192
|
const problem = chartProblem(ctx, fig, labels, ledger)
|
|
185
193
|
if (problem) flag("chart_value_untraceable", fig, problem)
|
|
@@ -190,16 +198,23 @@ export function valueChecks(ctx, ledger, fillMap, sourcesDir, options = {}) {
|
|
|
190
198
|
function rowChecks(ctx, ledger, rows, cited, options, flag) {
|
|
191
199
|
// An as-of of "latest ..." or "... per snapshot" names no fixed revision, so quote_stale_version is skipped.
|
|
192
200
|
const asOf = options.asOf && !/^latest\b|per snapshot/i.test(options.asOf) ? options.asOf : null
|
|
201
|
+
// A sentence-final period ("release v1.2.") is punctuation, not part of the revision token.
|
|
202
|
+
const target = asOf?.replace(/\.(?=\s|$)/gu, "")
|
|
193
203
|
for (const [id, node] of cited) {
|
|
194
204
|
const row = rows.get(id)
|
|
195
205
|
if (row.fuzzy === true) flag("quote_fuzzy_match", node, `${id} was registered from a similarity match on an OCR-noisy snapshot`)
|
|
196
206
|
const revision = typeof row.revision === "string" ? row.revision : (row.revision?.id ?? row.revision?.tag ?? null)
|
|
197
|
-
|
|
207
|
+
const revisionPattern = revision && new RegExp(`(?<![\\w.-])${revision.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(?![\\w.-])`, "u")
|
|
208
|
+
if (asOf && revisionPattern && !revisionPattern.test(target)) flag("quote_stale_version", node, `${id} is from revision ${revision}; the brief targets "${asOf}"`)
|
|
198
209
|
if ((row.kind === "derived" || row.kind === "converted") && options.recompute?.(ledger, row)?.ok === false) flag("value_mismatch", node, `${id} does not recompute from its operands`)
|
|
199
210
|
const window = row.snapshot_window
|
|
200
211
|
if (window && typeof window === "object" && window.start && window.end) {
|
|
201
212
|
const [start, end] = [Date.parse(window.start), Date.parse(window.end)]
|
|
202
|
-
const
|
|
213
|
+
const [absent, present] = [(row.operands ?? []).filter((op) => !rows.has(op)), (row.operands ?? []).filter((op) => rows.has(op))]
|
|
214
|
+
for (const op of absent) flag("operand_missing", node, `operand ${op} of ${id} is not in the fact ledger`)
|
|
215
|
+
const missing = present.filter((op) => !rows.get(op).observed_at)
|
|
216
|
+
for (const op of missing) flag("operand_timestamp_missing", node, `operand ${op} of ${id} has no observed_at timestamp`)
|
|
217
|
+
const outside = present.find((op) => !missing.includes(op) && !(Date.parse(rows.get(op).observed_at) >= start && Date.parse(rows.get(op).observed_at) < end))
|
|
203
218
|
if (outside) flag("snapshot_mismatch", node, `operand ${outside} of ${id} was observed outside ${window.start} to ${window.end}`)
|
|
204
219
|
}
|
|
205
220
|
}
|
|
@@ -213,17 +228,3 @@ function provenance(sourcesDir, meta, row) {
|
|
|
213
228
|
const path = resolve(dir, artifact)
|
|
214
229
|
return path.startsWith(`${dir}${sep}`) && existsSync(path) ? meta.get(row.source_id)?.kind : undefined
|
|
215
230
|
}
|
|
216
|
-
|
|
217
|
-
function sourceChecks(ctx, rows, cited, sourcesDir, flag) {
|
|
218
|
-
const ids = new Set([...cited.keys()].map((id) => rows.get(id)?.source_id).filter(Boolean))
|
|
219
|
-
for (const match of proseText(ctx.body).matchAll(/\[S(\d+)\]/g)) ids.add(`S${match[1]}`)
|
|
220
|
-
const meta = new Map()
|
|
221
|
-
for (const id of sourcesDir ? ids : []) {
|
|
222
|
-
const [metaPath, textPath] = [join(sourcesDir, id, "meta.json"), join(sourcesDir, id, "text.txt")]
|
|
223
|
-
if (!existsSync(metaPath) || !existsSync(textPath)) continue
|
|
224
|
-
meta.set(id, JSON.parse(readFileSync(metaPath, "utf8")))
|
|
225
|
-
const actual = sha256Of(readFileSync(textPath))
|
|
226
|
-
if (meta.get(id).sha256 !== actual) flag("snapshot_hash_mismatch", ctx.body, `${id}/text.txt changed after its snapshot was pinned`)
|
|
227
|
-
}
|
|
228
|
-
return meta
|
|
229
|
-
}
|
|
@@ -1,36 +1,15 @@
|
|
|
1
|
+
/// <reference types="bun-types" />
|
|
1
2
|
import { describe, expect, test } from "bun:test"
|
|
2
|
-
import { cpSync, mkdtempSync,
|
|
3
|
+
import { cpSync, mkdtempSync, rmSync, writeFileSync } from "node:fs"
|
|
3
4
|
import { tmpdir } from "node:os"
|
|
4
5
|
import { join } from "node:path"
|
|
5
6
|
|
|
6
|
-
import { exportRows
|
|
7
|
-
import { buildContext } from "./gates-static.mjs"
|
|
8
|
-
import { parseHtml } from "./html-lite.mjs"
|
|
7
|
+
import { exportRows } from "./fidelity-values.mjs"
|
|
9
8
|
import { similarity } from "./text-similarity.mjs"
|
|
9
|
+
import { LEDGER, SOURCES, ledgerWith, run, type Defect, type Row } from "./tests/fidelity-values-support"
|
|
10
10
|
|
|
11
|
-
type Defect = { code: string; severity: string; selector: string; line: number; message: string; hint: string }
|
|
12
|
-
type Row = Record<string, unknown> & { fact_id: string }
|
|
13
|
-
type Ledger = { schemaVersion: number; rows: Row[]; events: unknown[] }
|
|
14
|
-
type Options = { ledger?: Ledger; fillMap?: unknown[] | null; sourcesDir?: string | null; baseDir?: string; recompute?: (ledger: Ledger, row: Row) => { ok: boolean }; asOf?: string }
|
|
15
|
-
|
|
16
|
-
const FIX = join(import.meta.dir, "tests", "fixtures", "fidelity")
|
|
17
|
-
const SOURCES = join(FIX, "sources")
|
|
18
|
-
const LEDGER: Ledger = JSON.parse(readFileSync(join(FIX, "ledger-verify.json"), "utf8"))
|
|
19
11
|
const FULL = "we estimate completion no earlier than 2027"
|
|
20
12
|
|
|
21
|
-
function ledgerWith(id: string, patch: Record<string, unknown>): Ledger {
|
|
22
|
-
const copy = structuredClone(LEDGER)
|
|
23
|
-
copy.rows = copy.rows.map((row) => (row.fact_id === id ? { ...row, ...patch } : row))
|
|
24
|
-
return copy
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
function run(body: string, options: Options = {}): Defect[] {
|
|
28
|
-
const html = /<h2/.test(body) ? body : `<h2>Findings</h2>\n${body}`
|
|
29
|
-
const ctx = buildContext(parseHtml(html), { baseDir: options.baseDir ?? FIX })
|
|
30
|
-
const sourcesDir = options.sourcesDir === undefined ? SOURCES : options.sourcesDir
|
|
31
|
-
return valueChecks(ctx, options.ledger ?? LEDGER, options.fillMap ?? null, sourcesDir, options) as Defect[]
|
|
32
|
-
}
|
|
33
|
-
|
|
34
13
|
const codes = (defects: Defect[]) => defects.map((defect) => defect.code)
|
|
35
14
|
const fact = (id: string, text: string, unit = "") => `<data class="fact" data-fact="${id}"${unit ? ` data-unit="${unit}"` : ""}>${text}</data>`
|
|
36
15
|
const quote = (sel: string, text: string) => `<p>They wrote <q data-fact="Q1" data-quote-sel="${sel}">${text}</q> [S4].</p>`
|
|
@@ -53,8 +32,8 @@ describe("untraceable numbers", () => {
|
|
|
53
32
|
expect(codes(run("<h2>Appendix</h2><p>Revenue grew 12% [S4].</p>"))).toEqual(["untraceable_number"])
|
|
54
33
|
})
|
|
55
34
|
|
|
56
|
-
test("#given the same sentence under Sources #when checked #then
|
|
57
|
-
expect(run("<h2>Sources</h2><p>Revenue grew 12% [S4].</p>")).toEqual([])
|
|
35
|
+
test("#given the same sentence under Sources #when checked #then prose still needs a ledger row", () => {
|
|
36
|
+
expect(codes(run("<h2>Sources</h2><p>Revenue grew 12% [S4].</p>"))).toEqual(["untraceable_number"])
|
|
58
37
|
})
|
|
59
38
|
|
|
60
39
|
test("#given a date in prose #when checked #then dates are in scope", () => {
|
|
@@ -218,14 +197,14 @@ describe("verification claims are structural (data-claim-kind)", () => {
|
|
|
218
197
|
`<p>The bridge, which ran across the river, spans ${fact("F1", "up to 320 m", "m")} [S4].</p>`,
|
|
219
198
|
`<p>The span is ${fact("F1", "up to 320 m", "m")}, as confirmed with the annual inspection report [S4].</p>`,
|
|
220
199
|
`<p>Clinical tests confirmed the span of ${fact("F1", "up to 320 m", "m")} [S4].</p>`,
|
|
221
|
-
`<p
|
|
200
|
+
`<p>\uD655\uC778\uB41C \uAE38\uC774\uB294 ${fact("F1", "\uCD5C\uB300 320 m", "m")} [S4].</p>`,
|
|
222
201
|
]
|
|
223
202
|
for (const body of quiet) {
|
|
224
203
|
test(`#given findings prose ${JSON.stringify(body.replace(/<[^>]+>/g, ""))} #when checked #then no warning and no major`, () => expect(run(body)).toEqual([]))
|
|
225
204
|
}
|
|
226
205
|
|
|
227
206
|
test("#given an UNMARKED Korean run-and-check sentence #when checked #then verification_claim_unmarked minor only", () => {
|
|
228
|
-
expect(severities(run(`<p
|
|
207
|
+
expect(severities(run(`<p>\uC6B0\uB9AC\uAC00 \uC2E4\uD589\uD574 \uD655\uC778\uD55C \uAE38\uC774\uB294 ${fact("F1", "\uCD5C\uB300 320 m", "m")} [S4].</p>`))).toEqual(["verification_claim_unmarked:minor"])
|
|
229
208
|
})
|
|
230
209
|
|
|
231
210
|
test("#given the same run-and-check sentence MARKED verified on a command row #when checked #then no warning", () => {
|
|
@@ -20,11 +20,13 @@ import { parseHtml } from "./html-lite.mjs"
|
|
|
20
20
|
|
|
21
21
|
/** Every code this gate can raise. */
|
|
22
22
|
export const FIDELITY_GATE_CODES = Object.freeze([
|
|
23
|
+
"unit_unbound",
|
|
23
24
|
"fidelity_interview_incomplete", "unresolved_placeholder", "value_mismatch", "unit_mismatch", "quote_not_verbatim",
|
|
24
25
|
"quote_unbound", "claim_status_invalid", "snapshot_hash_mismatch", "chart_value_untraceable", "untraceable_number",
|
|
25
26
|
"unsupported_verification", "conflicting_readings", "hedge_dropped", "direction_flipped", "causality_added",
|
|
26
|
-
"qualifier_dropped", "unbound_sentence", "snapshot_mismatch", "join_integrity_fail", "quote_stale_version",
|
|
27
|
+
"qualifier_dropped", "unbound_sentence", "snapshot_mismatch", "operand_timestamp_missing", "operand_missing", "join_integrity_fail", "quote_stale_version",
|
|
27
28
|
"quote_fuzzy_match", "name_outside_quote", "scope_widened_warn", "claim_kind_mismatch", "verification_claim_unmarked",
|
|
29
|
+
"snapshot_not_raw", "relocated", "quote_converted_only",
|
|
28
30
|
])
|
|
29
31
|
const RANK = { blocker: 0, major: 1, minor: 2 }
|
|
30
32
|
|
|
@@ -40,8 +42,8 @@ export function deliveryBlockReason(defect) {
|
|
|
40
42
|
}
|
|
41
43
|
|
|
42
44
|
function withIntegrity(defect) {
|
|
43
|
-
const { code, severity, selector, line, message, hint } = defect
|
|
44
|
-
return { code, severity, integrity: DEFECT_CODES[code].integrity, selector, line, message, hint }
|
|
45
|
+
const { code, severity, selector, line, message, hint, part, fact_id } = defect
|
|
46
|
+
return { code, severity, integrity: DEFECT_CODES[code].integrity, selector, line, message, hint, ...(part ? { part } : {}), ...(fact_id ? { fact_id } : {}) }
|
|
45
47
|
}
|
|
46
48
|
|
|
47
49
|
function compareDefects(a, b) {
|
|
@@ -93,17 +95,17 @@ function incomplete(parsed) {
|
|
|
93
95
|
|
|
94
96
|
/**
|
|
95
97
|
* @param {string} html the filled report
|
|
96
|
-
* @param {{ ledger: { rows: object[] }, sourcesDir?: string | null, claims?: object | null, fillMap?: object[] | null,
|
|
98
|
+
* @param {{ ledger: { rows: object[] }, sourcesDir?: string | null, claims?: object | null, fillMap?: object[] | null, fillEntries?: object[] | null,
|
|
97
99
|
* brief: string | object, baseDir?: string, recompute?: (ledger: object, row: object) => { ok: boolean } }} options
|
|
98
100
|
* `brief` is the brief.md text or parseFidelityBlock output; `baseDir` resolves data-chart-source paths.
|
|
99
101
|
* @returns {{ defects: object[], gatesRun: string[] }}
|
|
100
102
|
*/
|
|
101
|
-
export function checkFidelity(html, { ledger, sourcesDir = null, claims = null, fillMap = null, brief, baseDir, recompute } = {}) {
|
|
103
|
+
export function checkFidelity(html, { ledger, sourcesDir = null, claims = null, fillMap = null, fillEntries = null, brief, baseDir, recompute } = {}) {
|
|
102
104
|
const parsed = brief && typeof brief === "object" ? brief : parseFidelityBlock(String(brief ?? ""))
|
|
103
105
|
if (!isLocked(parsed)) return { defects: [incomplete(parsed)], gatesRun: ["fidelity_interview_incomplete"] }
|
|
104
106
|
const ctx = buildContext(parseHtml(html), { baseDir })
|
|
105
107
|
const defects = [
|
|
106
|
-
...valueChecks(ctx, ledger, fillMap, sourcesDir, { recompute, asOf: parsed.fields["as of"] }),
|
|
108
|
+
...valueChecks(ctx, ledger, fillMap, sourcesDir, { recompute, asOf: parsed.fields["as of"], fillEntries }),
|
|
107
109
|
...bindingChecks(ctx, claims, ledger),
|
|
108
110
|
]
|
|
109
111
|
const pairs = fidelityPairs(ctx, ledger, sourcesDir)
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test"
|
|
2
|
+
|
|
3
|
+
import { buildContext, checkStatic } from "./gates-static.mjs"
|
|
4
|
+
import { parseHtml } from "./html-lite.mjs"
|
|
5
|
+
|
|
6
|
+
describe("HTML exact Closing headings", () => {
|
|
7
|
+
test.each([
|
|
8
|
+
["Closing", "closing", false],
|
|
9
|
+
["Closing the gap: GPU prices", null, true],
|
|
10
|
+
] as const)("#given %s as an HTML heading #when checked #then closing scope matches the whole heading", (heading: string, kind: string | null, missing: boolean) => {
|
|
11
|
+
const root = parseHtml(`<h2>${heading}</h2><p>Prices fell 8%.</p>`)
|
|
12
|
+
expect(buildContext(root, {}).sections.map((section: { kind: string | null }) => section.kind)).toEqual([kind])
|
|
13
|
+
const { defects } = checkStatic(root, {})
|
|
14
|
+
expect(defects.some((defect) => "code" in defect && defect.code === "missing_closing_section")).toBe(missing)
|
|
15
|
+
expect(defects.some((defect) => "code" in defect && defect.code === "unsourced_number")).toBe(missing)
|
|
16
|
+
})
|
|
17
|
+
})
|
|
@@ -14,6 +14,7 @@ import { figureGates } from "./gates-figures.mjs"
|
|
|
14
14
|
import { structureGates } from "./gates-structure.mjs"
|
|
15
15
|
import { textGates } from "./gates-text.mjs"
|
|
16
16
|
import { PROSE_TAGS, proseText, selectorPath } from "./html-lite.mjs"
|
|
17
|
+
import { sourcesClaims } from "./sources-citation.mjs"
|
|
17
18
|
|
|
18
19
|
const CITATION_MARKER = /\[S\d+\]|\[Source\s*\d+\]|\[\uCD9C\uCC98[^\]]*\]|\[\^\d+\]/i
|
|
19
20
|
const ANCHOR_HREF = /^#(?:s|src|source|ref|fn)/i
|
|
@@ -65,6 +66,11 @@ function hasCitation(node) {
|
|
|
65
66
|
return CITATION_MARKER.test(proseText(node)) || descendants(node).some(isCitationAnchor)
|
|
66
67
|
}
|
|
67
68
|
|
|
69
|
+
function proseScope(scope, owner) {
|
|
70
|
+
if (scope.section?.kind !== "sources" || !owner || sourcesClaims(owner).length === 0) return scope
|
|
71
|
+
return { ...scope, section: { ...scope.section, kind: null } }
|
|
72
|
+
}
|
|
73
|
+
|
|
68
74
|
function toRegExp(pattern) {
|
|
69
75
|
const re = pattern instanceof RegExp ? pattern : new RegExp(String(pattern), "i")
|
|
70
76
|
return new RegExp(re.source, re.flags.replace(/[gy]/g, ""))
|
|
@@ -181,7 +187,10 @@ export function checkStatic(doc, options = {}) {
|
|
|
181
187
|
continue
|
|
182
188
|
}
|
|
183
189
|
gatesRun.push(...gate.codes)
|
|
184
|
-
|
|
190
|
+
const context = gate.codes.includes("unsourced_number")
|
|
191
|
+
? { ...ctx, elements: ctx.elements.map(({ node, scope }) => ({ node, scope: proseScope(scope, node) })) }
|
|
192
|
+
: ctx
|
|
193
|
+
defects.push(...gate.run(context))
|
|
185
194
|
}
|
|
186
195
|
return { defects, gatesRun, gatesSkipped }
|
|
187
196
|
}
|
|
@@ -23,7 +23,7 @@ const PROSE_SKIP = new Set(["code", "pre", "kbd", "samp", "script", "style", "sv
|
|
|
23
23
|
const SAFE_ID = /^[A-Za-z_][\w-]*$/
|
|
24
24
|
|
|
25
25
|
export const PROSE_TAGS = new Set(["p", "li", ...HEADINGS, "td", "th", "figcaption", "blockquote", "dd", "dt"])
|
|
26
|
-
const PROSE_BREAKS = new Set([...P_CLOSERS, ...PROSE_TAGS, "br", "tr"])
|
|
26
|
+
export const PROSE_BREAKS = new Set([...P_CLOSERS, ...PROSE_TAGS, "br", "tr"])
|
|
27
27
|
|
|
28
28
|
function makeNode(tag, attrs, parent, line) {
|
|
29
29
|
return { tag, attrs, children: [], parent, line, text: "", raw: null }
|
|
@@ -130,6 +130,8 @@ export function parseHtml(source) {
|
|
|
130
130
|
const flushText = (to) => {
|
|
131
131
|
if (to > textStart) {
|
|
132
132
|
const node = makeNode("#text", {}, top(), lineAt(starts, textStart))
|
|
133
|
+
node.source_start = textStart
|
|
134
|
+
node.source_end = to
|
|
133
135
|
node.text = decodeEntities(src.slice(textStart, to))
|
|
134
136
|
top().children.push(node)
|
|
135
137
|
}
|
|
@@ -157,7 +159,7 @@ export function parseHtml(source) {
|
|
|
157
159
|
const tag = readTag(src, lt + 2)
|
|
158
160
|
if (tag.name !== "body" && tag.name !== "html") {
|
|
159
161
|
for (let k = stack.length - 1; k > 0; k--) {
|
|
160
|
-
if (stack[k].tag === tag.name) { stack.length = k; break }
|
|
162
|
+
if (stack[k].tag === tag.name) { stack[k].source_end = lt; stack.length = k; break }
|
|
161
163
|
}
|
|
162
164
|
}
|
|
163
165
|
at = textStart = tag.end
|
|
@@ -169,6 +171,8 @@ export function parseHtml(source) {
|
|
|
169
171
|
if (P_CLOSERS.has(tag.name)) closeWithin("p", P_SCOPE)
|
|
170
172
|
if (tag.name === "li") closeWithin("li", LI_SCOPE)
|
|
171
173
|
const node = makeNode(tag.name, tag.attrs, top(), lineAt(starts, lt))
|
|
174
|
+
node.source_start = tag.end
|
|
175
|
+
node.source_end = src.length
|
|
172
176
|
top().children.push(node)
|
|
173
177
|
if (tag.name === "body") sawBody = true
|
|
174
178
|
if (tag.attrs.id) root.ids.set(tag.attrs.id, (root.ids.get(tag.attrs.id) ?? 0) + 1)
|
|
@@ -61,10 +61,10 @@ function scriptEnd(src, from) {
|
|
|
61
61
|
function closeOf(src, from, name) {
|
|
62
62
|
if (name === "script") return scriptEnd(src, from)
|
|
63
63
|
if (RAW.has(name)) {
|
|
64
|
-
const close = new RegExp(`</${name}(?=[\\s/>])
|
|
64
|
+
const close = new RegExp(`</${name}(?=[\\s/>])`, "gi")
|
|
65
65
|
close.lastIndex = from
|
|
66
66
|
const match = close.exec(src)
|
|
67
|
-
return match ? match.index +
|
|
67
|
+
return match ? readTag(src, match.index + 2).end : src.length
|
|
68
68
|
}
|
|
69
69
|
let depth = 0
|
|
70
70
|
let stop = src.length
|