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,66 @@
|
|
|
1
|
+
// outcome-start.mjs - the run clock (outcome.json startedAt). `outcome init` is its one writer and
|
|
2
|
+
// records it once: --started-at from the orchestrator, else the startedAt of a valid manifest an
|
|
3
|
+
// earlier init wrote (a re-init keeps the clock), else terminal-state.json.started_at when a
|
|
4
|
+
// supervisor wrote one, else the YYYYMMDD-HHMMSS session dir name. A recorded value must be a real
|
|
5
|
+
// ISO time with Z or an offset, not after `now`, and within a day of a stamped dir name; otherwise
|
|
6
|
+
// init refuses instead of falling back. Never the interview's as-of date. No clock reads.
|
|
7
|
+
import { readFileSync } from "node:fs"
|
|
8
|
+
import { basename, join, resolve } from "node:path"
|
|
9
|
+
|
|
10
|
+
const ISO = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.\d+)?(?:Z|[+-](\d{2}):(\d{2}))$/
|
|
11
|
+
const SESSION_STAMP = /^(\d{4})(\d{2})(\d{2})-(\d{2})(\d{2})(\d{2})$/
|
|
12
|
+
const DAY_MS = 86_400_000
|
|
13
|
+
|
|
14
|
+
/** A real calendar time (no 2026-02-30, no T24:00), or null. */
|
|
15
|
+
function strictIso(value) {
|
|
16
|
+
const match = typeof value === "string" ? ISO.exec(value) : null
|
|
17
|
+
if (!match) return null
|
|
18
|
+
const [y, mo, d, h, mi, s, oh = 0, om = 0] = match.slice(1).map((part) => Number(part ?? 0))
|
|
19
|
+
const day = new Date(Date.UTC(y, mo - 1, d))
|
|
20
|
+
if (day.getUTCMonth() !== mo - 1 || day.getUTCDate() !== d || h > 23 || mi > 59 || s > 59 || oh > 23 || om > 59) return null
|
|
21
|
+
const date = new Date(value)
|
|
22
|
+
return Number.isNaN(date.getTime()) ? null : date
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Local start time from a YYYYMMDD-HHMMSS dir name, null when the name is not a stamp. */
|
|
26
|
+
function dirStamp(sessionDir, fail) {
|
|
27
|
+
const match = SESSION_STAMP.exec(basename(sessionDir))
|
|
28
|
+
if (!match) return null
|
|
29
|
+
const [y, mo, d, h, mi, s] = match.slice(1).map(Number)
|
|
30
|
+
const date = new Date(y, mo - 1, d, h, mi, s)
|
|
31
|
+
if (date.getMonth() !== mo - 1 || date.getDate() !== d || date.getHours() !== h) fail(`session dir basename is not a real local time: ${sessionDir}`)
|
|
32
|
+
return date
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function readJson(sessionDir, file) {
|
|
36
|
+
try {
|
|
37
|
+
return { data: JSON.parse(readFileSync(join(sessionDir, file), "utf8")) }
|
|
38
|
+
} catch (error) {
|
|
39
|
+
if (error.code === "ENOENT" || error.code === "ENOTDIR") return null
|
|
40
|
+
if (error instanceof SyntaxError) return { corrupt: `${join(sessionDir, file)} is not valid JSON (${error.message})` }
|
|
41
|
+
throw error
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* @param {{ sessionDir: string, startedAt?: string, now?: Date, validManifest: (data: unknown) => boolean, fail: (message: string) => never }} input
|
|
47
|
+
* @returns {Date}
|
|
48
|
+
*/
|
|
49
|
+
export function runStart({ sessionDir, startedAt, now, validManifest, fail }) {
|
|
50
|
+
const dir = dirStamp(sessionDir, fail)
|
|
51
|
+
const check = (value, location) => {
|
|
52
|
+
const date = strictIso(value)
|
|
53
|
+
if (!date) fail(`invalid run timestamp in ${location}: ${JSON.stringify(value)} is not a real ISO time with Z or an offset`)
|
|
54
|
+
if (now && date > now) fail(`run timestamp in ${location} (${value}) is after now (${now.toISOString()})`)
|
|
55
|
+
if (dir && Math.abs(date - dir) > DAY_MS) fail(`run timestamp in ${location} (${value}) is not within a day of the session dir name ${basename(sessionDir)}: a copied or stale value`)
|
|
56
|
+
return date
|
|
57
|
+
}
|
|
58
|
+
if (startedAt !== undefined) return check(startedAt, "--started-at")
|
|
59
|
+
const manifest = readJson(sessionDir, "outcome.json")
|
|
60
|
+
if (manifest?.data && validManifest(manifest.data) && resolve(String(manifest.data.sessionDir)) === resolve(sessionDir)) return check(manifest.data.startedAt, "outcome.json.startedAt")
|
|
61
|
+
const terminal = readJson(sessionDir, "terminal-state.json")
|
|
62
|
+
if (terminal?.corrupt) fail(terminal.corrupt)
|
|
63
|
+
if (terminal?.data?.started_at !== undefined) return check(terminal.data.started_at, "terminal-state.json.started_at")
|
|
64
|
+
if (dir) return dir
|
|
65
|
+
return fail(`missing run timestamp: pass --started-at <ISO>, record terminal-state.json.started_at, or name the session dir YYYYMMDD-HHMMSS (${sessionDir})`)
|
|
66
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { describe, expect, onTestFinished, test } from "bun:test"
|
|
2
|
+
import { spawnSync } from "node:child_process"
|
|
3
|
+
import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs"
|
|
4
|
+
import { tmpdir } from "node:os"
|
|
5
|
+
import { join } from "node:path"
|
|
6
|
+
|
|
7
|
+
import { validateOutcomeManifest } from "./contracts.mjs"
|
|
8
|
+
import { initManifest } from "./outcome.mjs"
|
|
9
|
+
import { sampleManifest } from "./tests/contracts-support"
|
|
10
|
+
|
|
11
|
+
const START = "2026-10-25T12:52:01.832Z"
|
|
12
|
+
const cli = join(import.meta.dir, "report-tools.mjs")
|
|
13
|
+
|
|
14
|
+
function directory(name = "E") {
|
|
15
|
+
const root = mkdtempSync(join(tmpdir(), "outcome-start-"))
|
|
16
|
+
onTestFinished(() => rmSync(root, { recursive: true, force: true }))
|
|
17
|
+
const dir = join(root, name)
|
|
18
|
+
mkdirSync(dir)
|
|
19
|
+
return dir
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function init(dir: string, ...extra: string[]) {
|
|
23
|
+
return spawnSync("node", [cli, "outcome", "init", "--session-dir", dir, "--promised", "html", "--now", START, ...extra], { encoding: "utf8" })
|
|
24
|
+
}
|
|
25
|
+
const startedAt = (dir: string) => JSON.parse(readFileSync(join(dir, "outcome.json"), "utf8")).startedAt
|
|
26
|
+
const manifestAt = (dir: string, value: string) => writeFileSync(join(dir, "outcome.json"), JSON.stringify({ ...sampleManifest(), sessionDir: dir, startedAt: value }))
|
|
27
|
+
|
|
28
|
+
describe("outcome init is the one writer of the run clock", () => {
|
|
29
|
+
test("#given --started-at from the orchestrator #when init runs in an unstamped dir #then that value is recorded", () => {
|
|
30
|
+
const dir = directory()
|
|
31
|
+
expect(init(dir, "--started-at", START).status).toBe(0)
|
|
32
|
+
expect(startedAt(dir)).toBe(START)
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
test("#given a supervisor's terminal-state.json #when init runs #then started_at is recorded", () => {
|
|
36
|
+
const dir = directory()
|
|
37
|
+
writeFileSync(join(dir, "terminal-state.json"), JSON.stringify({ started_at: START }))
|
|
38
|
+
expect(init(dir).status).toBe(0)
|
|
39
|
+
expect(startedAt(dir)).toBe(START)
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
test("#given no flag, no metadata, and no stamped dir name #when init runs #then it refuses and names every source", () => {
|
|
43
|
+
const result = init(directory())
|
|
44
|
+
expect(result.status).toBe(1)
|
|
45
|
+
expect(result.stderr).toMatch(/--started-at/)
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
test("#given only a stamped dir name #when init runs #then the dir name is the fallback clock", () => {
|
|
49
|
+
const dir = directory("20260101-120000")
|
|
50
|
+
expect(init(dir).status).toBe(0)
|
|
51
|
+
expect(startedAt(dir)).toMatch(/^2026-01-01T/)
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
test("#given a valid manifest from an earlier init #when init runs again #then the recorded clock survives", () => {
|
|
55
|
+
const dir = directory("20261025-120000")
|
|
56
|
+
const first = "2026-10-25T03:00:00.000Z"
|
|
57
|
+
manifestAt(dir, first)
|
|
58
|
+
expect(init(dir).status).toBe(0)
|
|
59
|
+
expect(startedAt(dir)).toBe(first)
|
|
60
|
+
})
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
describe("a recorded run clock is validated, never trusted raw", () => {
|
|
64
|
+
test("#given a manifest copied with a 2020 startedAt into a 20260104-120000 dir #when init runs #then it refuses", () => {
|
|
65
|
+
const dir = directory("20260104-120000")
|
|
66
|
+
manifestAt(dir, "2020-01-01T00:00:00.000Z")
|
|
67
|
+
const result = init(dir)
|
|
68
|
+
expect(result.status).toBe(1)
|
|
69
|
+
expect(result.stderr).toMatch(/outcome\.json\.startedAt/)
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
for (const value of ["not-a-time", "2099-01-01T00:00:00Z", "2026-02-30T12:00:00Z", "2026-10-25T24:00:00Z", "2026-10-25 12:00:00"]) {
|
|
73
|
+
test(`#given started_at ${value} with --now ${START} #when init runs #then it refuses without falling back`, () => {
|
|
74
|
+
const dir = directory("20261025-120000")
|
|
75
|
+
writeFileSync(join(dir, "terminal-state.json"), JSON.stringify({ started_at: value }))
|
|
76
|
+
const result = init(dir)
|
|
77
|
+
expect(result.status).toBe(1)
|
|
78
|
+
expect(result.stderr).toMatch(/terminal-state\.json\.started_at/)
|
|
79
|
+
})
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
test("#given a valid manifest copied from another session into an unstamped dir #when init runs #then its clock is not reused", () => {
|
|
83
|
+
const dir = directory()
|
|
84
|
+
writeFileSync(join(dir, "outcome.json"), JSON.stringify({ ...sampleManifest(), sessionDir: "/elsewhere/E", startedAt: "2020-01-01T00:00:00.000Z" }))
|
|
85
|
+
const result = init(dir)
|
|
86
|
+
expect(result.status).toBe(1)
|
|
87
|
+
expect(result.stderr).toMatch(/--started-at/)
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
test("#given a corrupt terminal-state.json #when init runs #then the error names the file", () => {
|
|
91
|
+
const dir = directory("20261025-120000")
|
|
92
|
+
writeFileSync(join(dir, "terminal-state.json"), "{")
|
|
93
|
+
const result = init(dir)
|
|
94
|
+
expect(result.status).toBe(1)
|
|
95
|
+
expect(result.stderr).toContain("terminal-state.json")
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
test("#given a corrupt outcome.json #when init runs #then init overwrites it, as before", () => {
|
|
99
|
+
const dir = directory("20261025-120000")
|
|
100
|
+
writeFileSync(join(dir, "outcome.json"), "{")
|
|
101
|
+
expect(init(dir).status).toBe(0)
|
|
102
|
+
expect(validateOutcomeManifest(JSON.parse(readFileSync(join(dir, "outcome.json"), "utf8"))).ok).toBe(true)
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
test("#given the legacy library call with a stamped dir #when initialized #then the manifest validates", () => {
|
|
106
|
+
const manifest = initManifest({ sessionDir: "/nonexistent/20260101-120000", promised: [], lane: null })
|
|
107
|
+
expect(validateOutcomeManifest(manifest)).toEqual({ ok: true, errors: [] })
|
|
108
|
+
})
|
|
109
|
+
})
|
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
// outcome.mjs - the per-run outcome manifest (`outcome.json`) and the closing briefing. Manifest functions are pure (they return a new manifest).
|
|
2
2
|
// The briefing reads only the manifest, the sources-ledger text, and an injected `now`. node: builtins only.
|
|
3
|
-
|
|
4
3
|
import * as nodeFs from "node:fs"
|
|
5
4
|
import { basename, dirname, join } from "node:path"
|
|
6
5
|
|
|
7
6
|
import { DELIVERABLE_FORMATS, DELIVERABLE_STATUSES, GATE_NAMES, GATE_STATUSES, LANES, MANIFEST_CODES, normalizeManifest, SCHEMA_VERSION, validateOutcomeManifest } from "./contracts.mjs"
|
|
7
|
+
import { runStart } from "./outcome-start.mjs"
|
|
8
8
|
|
|
9
9
|
export const MANIFEST_FILENAME = "outcome.json"
|
|
10
10
|
const MISSING = MANIFEST_CODES[0]
|
|
11
|
-
const SESSION_STAMP = /^(\d{4})(\d{2})(\d{2})-(\d{2})(\d{2})(\d{2})$/
|
|
12
11
|
const SOURCE_LINE = /^\[(S|Source )\d+\]/
|
|
13
12
|
const URL_PATTERN = /https?:\/\/[^\s)<>\]"'`]+/g
|
|
14
13
|
const DRAFT_MARKER = "STATUS: draft"
|
|
@@ -33,26 +32,20 @@ function assertOneOf(code, label, value, allowed) {
|
|
|
33
32
|
|
|
34
33
|
const clone = (manifest) => structuredClone(manifest)
|
|
35
34
|
const isMissing = (error) => error?.code === "ENOENT" || error?.code === "ENOTDIR"
|
|
36
|
-
|
|
37
35
|
function stamp(now) {
|
|
38
36
|
return (now ?? new Date()).toISOString()
|
|
39
37
|
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
if (!match) throw new OutcomeError("invalid_session_dir", `session dir basename must be YYYYMMDD-HHMMSS: ${sessionDir}`)
|
|
45
|
-
const [y, mo, d, h, mi, s] = match.slice(1).map(Number)
|
|
46
|
-
const date = new Date(y, mo - 1, d, h, mi, s)
|
|
47
|
-
if (date.getMonth() !== mo - 1 || date.getDate() !== d || date.getHours() !== h) {
|
|
48
|
-
throw new OutcomeError("invalid_session_dir", `session dir basename is not a real local time: ${sessionDir}`)
|
|
38
|
+
/** The run clock outcome init records (outcome-start.mjs owns the contract). */
|
|
39
|
+
export function parseSessionStart(sessionDir, { startedAt, now } = {}) {
|
|
40
|
+
const fail = (message) => {
|
|
41
|
+
throw new OutcomeError("invalid_session_dir", message)
|
|
49
42
|
}
|
|
50
|
-
return
|
|
43
|
+
return runStart({ sessionDir, startedAt, now, validManifest: (data) => validateOutcomeManifest(normalizeManifest(data)).ok, fail })
|
|
51
44
|
}
|
|
52
45
|
|
|
53
|
-
/** @param {{ sessionDir: string, promised: string[], lane: string|null, interview?: object|null, now?: Date }} input */
|
|
54
|
-
export function initManifest({ sessionDir, promised, lane, interview = null, now }) {
|
|
55
|
-
const startedAt = parseSessionStart(sessionDir).toISOString()
|
|
46
|
+
/** @param {{ sessionDir: string, promised: string[], lane: string|null, interview?: object|null, now?: Date, startedAt?: string }} input */
|
|
47
|
+
export function initManifest({ sessionDir, promised, lane, interview = null, now, startedAt: recorded }) {
|
|
48
|
+
const startedAt = parseSessionStart(sessionDir, { startedAt: recorded, now }).toISOString()
|
|
56
49
|
if (lane !== null && lane !== undefined) assertOneOf("unknown_lane", "lane", lane, LANES)
|
|
57
50
|
const formats = [...new Set(promised)]
|
|
58
51
|
for (const format of formats) assertOneOf("unknown_format", "deliverable format", format, DELIVERABLE_FORMATS)
|
|
@@ -74,9 +67,7 @@ export function initManifest({ sessionDir, promised, lane, interview = null, now
|
|
|
74
67
|
}
|
|
75
68
|
}
|
|
76
69
|
|
|
77
|
-
/**
|
|
78
|
-
* Set one format's row; every other row is kept as is. An unpromised format gets a new
|
|
79
|
-
* row with promised=false. The row's detail fields are replaced by the ones given.
|
|
70
|
+
/** Set one format's row, keeping other rows. New formats have promised=false.
|
|
80
71
|
* @param {{ path?: string, reason?: string, pages?: number, bytes?: number, now?: Date }} [detail]
|
|
81
72
|
*/
|
|
82
73
|
export function setDeliverable(manifest, format, status, detail = {}) {
|
|
@@ -143,9 +134,7 @@ export function verifyManifest(manifest, fs = nodeFs) {
|
|
|
143
134
|
return { ok: problems.length === 0, problems }
|
|
144
135
|
}
|
|
145
136
|
|
|
146
|
-
/**
|
|
147
|
-
* none = missing or empty file; partial = `STATUS: draft` in the first 5 lines, or the
|
|
148
|
-
* manifest (given, else a sibling outcome.json) still has a pending row; else complete.
|
|
137
|
+
/** Missing/empty = none; draft marker or pending manifest = partial; else complete.
|
|
149
138
|
* @returns {"none"|"partial"|"complete"}
|
|
150
139
|
*/
|
|
151
140
|
export function deliverableState(deliverablePath, manifestOrNull) {
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import { describe, expect, onTestFinished, test } from "bun:test"
|
|
2
|
+
import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs"
|
|
3
|
+
import { tmpdir } from "node:os"
|
|
4
|
+
import { join } from "node:path"
|
|
5
|
+
|
|
6
|
+
import { readLedger } from "./fact-ledger.mjs"
|
|
7
|
+
import { run } from "./report-tools.mjs"
|
|
8
|
+
|
|
9
|
+
const BRIEF = readFileSync(join(import.meta.dir, "tests/fixtures/fidelity-brief-locked.md"), "utf8")
|
|
10
|
+
|
|
11
|
+
type Row = { fact_id: string; value: string; unit: string; quote: string; raw: string; sentence_id: string; locator: { artifact: string; unit: string; char_start: number; char_end: number } }
|
|
12
|
+
type Defect = { code: string; message: string }
|
|
13
|
+
|
|
14
|
+
/** {{F1}} -> {{F1:<its ledger label>}}: fill requires the label. */
|
|
15
|
+
const labeled = (dir: string) => {
|
|
16
|
+
const labels = new Map(readLedger(dir).rows.map((row: { fact_id: string; label: string }) => [row.fact_id, row.label]))
|
|
17
|
+
return (placeholder: string) => placeholder.replace(/\{\{(\w+)\}\}/, (_, id) => `{{${id}:${labels.get(id)}}}`)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function session() {
|
|
21
|
+
const dir = mkdtempSync(join(tmpdir(), "page-source-"))
|
|
22
|
+
onTestFinished(() => rmSync(dir, { recursive: true, force: true }))
|
|
23
|
+
writeFileSync(join(dir, "brief.md"), BRIEF)
|
|
24
|
+
return dir
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async function snapshotPage(dir: string, bytes: Buffer | string, id = "S1") {
|
|
28
|
+
const file = join(dir, `${id}.capture.html`)
|
|
29
|
+
writeFileSync(file, bytes)
|
|
30
|
+
const result = await run(["source", "snapshot", "--id", id, "--html", file, "--session-dir", dir])
|
|
31
|
+
expect(result.exitCode).toBe(0)
|
|
32
|
+
return Buffer.from(bytes)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const find = (dir: string, anchor: string, id = "S1") => run(["fact", "add", "--session-dir", dir, "--source", id, "--find", anchor, "--json"])
|
|
36
|
+
const quote = (dir: string, match: string) => run(["fact", "quote", "--session-dir", dir, "--source", "S1", "--match", match, "--json"])
|
|
37
|
+
|
|
38
|
+
/** Fill a report citing every given placeholder, then verify it; returns the verify result and the filled html. */
|
|
39
|
+
async function fillAndVerify(dir: string, placeholders: string[]) {
|
|
40
|
+
const body = [
|
|
41
|
+
'<!doctype html><html lang="en"><head><meta charset="utf-8"><title>Bridge</title></head><body><h1>Bridge</h1>',
|
|
42
|
+
"<h2>Findings</h2>",
|
|
43
|
+
...placeholders.map(labeled(dir)).map((placeholder) => `<p>The source states ${placeholder} [S1].</p>`),
|
|
44
|
+
"<h2>How this report was made</h2><p>Collected from one cited public page [S1].</p>",
|
|
45
|
+
'<h2>Sources</h2><ol><li id="s1">[S1] Bridge page, retrieved 2026-10-01.</li></ol></body></html>',
|
|
46
|
+
].join("\n")
|
|
47
|
+
writeFileSync(join(dir, "report.src.html"), body)
|
|
48
|
+
const filled = await run(["fact", "fill", join(dir, "report.src.html"), "--out", join(dir, "report.html"), "--session-dir", dir])
|
|
49
|
+
expect(filled.exitCode).toBe(0)
|
|
50
|
+
const verified = await run(["fact", "verify", join(dir, "report.html"), "--session-dir", dir])
|
|
51
|
+
return { verified, html: readFileSync(join(dir, "report.html"), "utf8") }
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const codes = (json: { defects: Defect[] }) => json.defects.map((defect) => defect.code)
|
|
55
|
+
|
|
56
|
+
const PAGE = [
|
|
57
|
+
"<!doctype html><html><head><title>Deck 9 m</title><style>.deck{font-size:14px}</style><script>var span = 9;</script></head><body>",
|
|
58
|
+
'<p>The main deck spans <span title="9 m" data-v="320">320</span> m in total.</p>',
|
|
59
|
+
'<p>The upper deck spans <span style="font-size:14px">330</span> m.</p>',
|
|
60
|
+
"</body></html>",
|
|
61
|
+
].join("")
|
|
62
|
+
|
|
63
|
+
describe("page sources locate on visible text nodes only", () => {
|
|
64
|
+
test("#given numbers in title, data and style attributes #when fact add --find runs #then the text-node value is recorded and verifies", async () => {
|
|
65
|
+
const dir = session()
|
|
66
|
+
const raw = await snapshotPage(dir, PAGE)
|
|
67
|
+
const main = await find(dir, "main deck spans")
|
|
68
|
+
const upper = await find(dir, "upper deck spans")
|
|
69
|
+
expect(main.exitCode).toBe(0)
|
|
70
|
+
const row: Row = main.json.data.row
|
|
71
|
+
expect([row.value, row.unit, row.quote]).toEqual(["320", "m", "The main deck spans 320 m in total."])
|
|
72
|
+
expect(row.locator).toMatchObject({ artifact: "raw.bin", unit: "bytes" })
|
|
73
|
+
const span = raw.subarray(row.locator.char_start, row.locator.char_end).toString("utf8")
|
|
74
|
+
expect(span).toBe('The main deck spans <span title="9 m" data-v="320">320</span> m in total.')
|
|
75
|
+
expect([upper.json.data.row.value, upper.json.data.row.unit]).toEqual(["330", "m"])
|
|
76
|
+
const { verified, html } = await fillAndVerify(dir, ["{{F1}}", "{{F2}}"])
|
|
77
|
+
expect(verified.exitCode).toBe(0)
|
|
78
|
+
expect(html).toContain("320 m")
|
|
79
|
+
expect(html).not.toContain(">9 m<")
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
test("#given the same anchor in a hidden element and a visible paragraph #when --find runs #then only the visible number is located", async () => {
|
|
83
|
+
const dir = session()
|
|
84
|
+
const hidden = [
|
|
85
|
+
'<div style="display:none"><p>Visible length is 999 m.</p></div>',
|
|
86
|
+
'<div style="color:red; visibility: hidden"><p>Shadow length is 998 m.</p></div>',
|
|
87
|
+
'<p aria-hidden="true">Ghost length is 997 m.</p>',
|
|
88
|
+
"<p hidden>Secret length is 996 m.</p>",
|
|
89
|
+
"<noscript>Script length is 995 m.</noscript>",
|
|
90
|
+
"<template><p>Template length is 994 m.</p></template>",
|
|
91
|
+
].join("")
|
|
92
|
+
await snapshotPage(dir, `<html><head><script>Inline length is 993 m.</script><style>.x{}</style></head><body>${hidden}<p>Visible length is 410 m.</p></body></html>`)
|
|
93
|
+
const visible = await find(dir, "Visible length is")
|
|
94
|
+
expect(visible.exitCode).toBe(0)
|
|
95
|
+
expect(visible.json.data.row.value).toBe("410")
|
|
96
|
+
for (const anchor of ["Shadow length is", "Ghost length is", "Secret length is", "Script length is", "Template length is", "Inline length is"]) {
|
|
97
|
+
const result = await find(dir, anchor)
|
|
98
|
+
expect([anchor, result.json.error]).toEqual([anchor, "fact_not_located"])
|
|
99
|
+
}
|
|
100
|
+
expect(readFileSync(join(dir, "sources/S1/text.txt"), "utf8")).toBe("Visible length is 410 m.")
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
test("#given elements hidden by <style> rules on a class, an id, or a tag #when --find runs #then they are not located", async () => {
|
|
104
|
+
const dir = session()
|
|
105
|
+
const style = "<style>.d-none, .sr-only { display: none } #gone{visibility:hidden} footer{color:red;display:none} div p.keep{display:none}</style>"
|
|
106
|
+
const body = '<p class="lead d-none">Class length is 999 m.</p><p id="gone">Id length is 998 m.</p><footer>Tag length is 997 m.</footer><p class="keep">Kept length is 410 m.</p>'
|
|
107
|
+
await snapshotPage(dir, `<html><head>${style}</head><body>${body}</body></html>`)
|
|
108
|
+
for (const anchor of ["Class length is", "Id length is", "Tag length is"]) expect([anchor, (await find(dir, anchor)).json.error]).toEqual([anchor, "fact_not_located"])
|
|
109
|
+
expect((await find(dir, "Kept length is")).json.data.row.value).toBe("410")
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
test("#given rules inside @media (a responsive or a print copy) #when --find runs #then the text is located, and an unconditional rule still hides", async () => {
|
|
113
|
+
const dir = session()
|
|
114
|
+
const style = "<style>@media (max-width:600px){.desk{display:none}} @media print{.screen{display:none}} .d-none{display:none}</style>"
|
|
115
|
+
const body = '<p class="desk">Desktop length is 410 m.</p><p class="screen">Screen length is 411 m.</p><p class="d-none">Mobile length is 412 m.</p>'
|
|
116
|
+
await snapshotPage(dir, `<html><head>${style}</head><body>${body}</body></html>`)
|
|
117
|
+
const desktop = await find(dir, "Desktop length is")
|
|
118
|
+
expect(desktop.exitCode).toBe(0)
|
|
119
|
+
expect(desktop.json.data.row.value).toBe("410")
|
|
120
|
+
expect((await find(dir, "Screen length is")).json.data.row.value).toBe("411")
|
|
121
|
+
expect((await find(dir, "Mobile length is")).json.error).toBe("fact_not_located")
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
test("#given a quote across inline markup #when fact quote --match runs #then the quotation is that sentence, indexed like sentences.jsonl", async () => {
|
|
125
|
+
const dir = session()
|
|
126
|
+
await snapshotPage(dir, "<!doctype html><html><head><script>var x = 1;</script></head><body><p>Construction opens <em>no earlier</em> than 2027. Funding is pending.</p></body></html>")
|
|
127
|
+
const result = await quote(dir, "opens no earlier than 2027")
|
|
128
|
+
expect(result.exitCode).toBe(0)
|
|
129
|
+
const row: Row = result.json.data.row
|
|
130
|
+
expect(row.quote).toBe("Construction opens no earlier than 2027.")
|
|
131
|
+
const sentences = readFileSync(join(dir, "sources/S1/sentences.jsonl"), "utf8").trim().split("\n").map((line) => JSON.parse(line))
|
|
132
|
+
expect(sentences.find((entry: { sid: string }) => entry.sid === row.sentence_id)?.text).toBe(row.quote)
|
|
133
|
+
const { verified, html } = await fillAndVerify(dir, ["{{Q1}}"])
|
|
134
|
+
expect(verified.exitCode).toBe(0)
|
|
135
|
+
expect(html).toContain("Construction opens no earlier than 2027.")
|
|
136
|
+
expect(html).not.toContain("<")
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
test("#given a table cell without </td> #when --table runs #then the quote is the cell text, not the rest of the document", async () => {
|
|
140
|
+
const dir = session()
|
|
141
|
+
const raw = await snapshotPage(dir, '<table id="sizes"><tr><th>Name</th><th>Length</th></tr><tr><td>Deck</td><td><b title="9">320</b> m</tr></table><p>Appendix 9 holds notes.</p>')
|
|
142
|
+
const result = await run(["fact", "add", "--session-dir", dir, "--source", "S1", "--table", "sizes", "--row", "Deck", "--col", "Length", "--json"])
|
|
143
|
+
expect(result.exitCode).toBe(0)
|
|
144
|
+
const row: Row = result.json.data.row
|
|
145
|
+
expect([row.value, row.unit, row.quote]).toEqual(["320", "m", "320 m"])
|
|
146
|
+
expect(raw.subarray(row.locator.char_start, row.locator.char_end).toString()).toBe('320</b> m')
|
|
147
|
+
expect((await fillAndVerify(dir, ["{{F1}}"])).verified.exitCode).toBe(0)
|
|
148
|
+
})
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
describe("byte offsets count the source's own encoding", () => {
|
|
152
|
+
const latin1 = (text: string) => Buffer.from(text, "latin1")
|
|
153
|
+
|
|
154
|
+
test("#given a declared ISO-8859-1 page with Caf\\xE9 before the value #when added and verified unchanged #then offsets hit the real bytes and verify is clean", async () => {
|
|
155
|
+
const dir = session()
|
|
156
|
+
const raw = await snapshotPage(dir, latin1('<html><head><meta charset="iso-8859-1"></head><body><p>Caf\u00e9. The span is 320 m.</p></body></html>'))
|
|
157
|
+
const result = await find(dir, "The span is")
|
|
158
|
+
const row: Row = result.json.data.row
|
|
159
|
+
expect(raw.subarray(row.locator.char_start, row.locator.char_end).toString("latin1")).toBe("The span is 320 m.")
|
|
160
|
+
const { verified } = await fillAndVerify(dir, ["{{F1}}"])
|
|
161
|
+
expect(verified.exitCode).toBe(0)
|
|
162
|
+
expect(codes(verified.json)).not.toContain("relocated")
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
test("#given an undeclared Latin-1 byte with more than 32 characters before the value #when verified unchanged #then no false relocation", async () => {
|
|
166
|
+
const dir = session()
|
|
167
|
+
await snapshotPage(dir, latin1("<p>Caf\u00e9 au lait is served here all day long. The span is 320 m.</p>"))
|
|
168
|
+
await find(dir, "The span is")
|
|
169
|
+
const { verified } = await fillAndVerify(dir, ["{{F1}}"])
|
|
170
|
+
expect(verified.exitCode).toBe(0)
|
|
171
|
+
expect(codes(verified.json)).toEqual([])
|
|
172
|
+
})
|
|
173
|
+
|
|
174
|
+
test("#given an EUC-KR page #when added and verified unchanged #then the locator spans the EUC-KR bytes and verify is clean", async () => {
|
|
175
|
+
const dir = session()
|
|
176
|
+
const bytes = Buffer.from("3c68746d6c3e3c686561643e3c6d65746120636861727365743d226575632d6b72223e3c2f686561643e3c626f64793e3c703ec1d620b1b3b7aec0c720b1e6c0ccb4c220333230206d20c0ccb4d92e3c2f703e3c2f626f64793e3c2f68746d6c3e", "hex")
|
|
177
|
+
const raw = await snapshotPage(dir, bytes)
|
|
178
|
+
const result = await find(dir, "\uae38\uc774\ub294")
|
|
179
|
+
expect(result.exitCode).toBe(0)
|
|
180
|
+
const row: Row = result.json.data.row
|
|
181
|
+
expect(row.value).toBe("320")
|
|
182
|
+
expect(raw.subarray(row.locator.char_start, row.locator.char_end).toString("hex")).toBe("c1d620b1b3b7aec0c720b1e6c0ccb4c220333230206d20c0ccb4d92e")
|
|
183
|
+
const { verified } = await fillAndVerify(dir, ["{{F1}}"])
|
|
184
|
+
expect(verified.exitCode).toBe(0)
|
|
185
|
+
expect(codes(verified.json)).toEqual([])
|
|
186
|
+
})
|
|
187
|
+
})
|
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
// placeholder.mjs - the fact pointer grammar of the fill step and its render helpers.
|
|
2
2
|
// Value pointers: {{F12:label}}, {{F12:label:value}}, {{F12:label:unit}}, {{F12:label:q}}.
|
|
3
|
+
// Claim prefixes from CLAIM_KINDS (V, D, A) precede any one pointer: {{D:F12:label:value}}, {{V:Q7:label:lead=18}}.
|
|
3
4
|
// Quote pointers: {{Q7:label}}, {{Q7:label:lead=18}}, {{Q7:label:from="x":to="y"}} (anchors are
|
|
4
5
|
// JSON strings, so they may hold a colon or an escaped quote). Labels are compared exactly by the
|
|
5
6
|
// caller; this module never guesses an id. Pure functions; node: builtins only.
|
|
6
7
|
|
|
7
8
|
import { parseNumeral } from "./numerals.mjs"
|
|
9
|
+
import { CLAIM_KINDS } from "./claim-kinds.mjs"
|
|
8
10
|
|
|
9
11
|
export const ELLIPSIS = "\u2026"
|
|
10
12
|
|
|
11
|
-
const ID = /^([FQ])\d
|
|
13
|
+
const ID = /^([FQ])[1-9]\d*$/
|
|
12
14
|
const LABEL = /^[^\s:{}"]+$/
|
|
13
15
|
const VALUE_MODES = new Set(["value", "unit", "q"])
|
|
14
16
|
const LEAD = /^lead=([1-9]\d*)$/
|
|
@@ -45,6 +47,12 @@ function parseAnchors(tail) {
|
|
|
45
47
|
export function parsePlaceholder(token) {
|
|
46
48
|
if (!token.startsWith("{{") || !token.endsWith("}}")) return { error: "a placeholder is {{ID:label...}}" }
|
|
47
49
|
const body = token.slice(2, -2)
|
|
50
|
+
const marked = Object.entries(CLAIM_KINDS).find(([, entry]) => entry.prefix && body.startsWith(`${entry.prefix}:`))
|
|
51
|
+
if (marked) {
|
|
52
|
+
const [claimKind, entry] = marked
|
|
53
|
+
const parsed = parsePlaceholder(`{{${body.slice(entry.prefix.length + 1)}}}`)
|
|
54
|
+
return parsed.error || parsed.claimKind ? { error: "a claim prefix must precede one fact or quote pointer" } : { ...parsed, claimKind }
|
|
55
|
+
}
|
|
48
56
|
const colon = body.indexOf(":")
|
|
49
57
|
const id = colon === -1 ? body : body.slice(0, colon)
|
|
50
58
|
const idMatch = ID.exec(id)
|
|
@@ -130,7 +138,7 @@ export function lead(quote, n) {
|
|
|
130
138
|
/** The value and unit parts of a row's raw text ("4.2 eok won" -> value "4.2 eok", unit "won"). */
|
|
131
139
|
export function valueParts(row) {
|
|
132
140
|
const unitText = parseNumeral(row.raw)?.unitRaw ?? row.unit
|
|
133
|
-
const valueText = unitText && row.raw.endsWith(unitText) ? row.raw.slice(0, -unitText.length).trimEnd() :
|
|
141
|
+
const valueText = unitText && row.raw.endsWith(unitText) ? row.raw.slice(0, -unitText.length).trimEnd() : row.raw
|
|
134
142
|
return { valueText, unitText: unitText ?? "" }
|
|
135
143
|
}
|
|
136
144
|
|
|
@@ -156,15 +164,19 @@ export function escapeHtml(text) {
|
|
|
156
164
|
/** The elements the renderers below emit as fact spans; recognizers build their patterns from this. */
|
|
157
165
|
export const FACT_ELEMENT_TAGS = Object.freeze(["data", "q", "blockquote"])
|
|
158
166
|
|
|
159
|
-
|
|
167
|
+
/** `mode` "value" marks a bare value whose unit verify checks beside it; `claimKind` is the claim mark. */
|
|
168
|
+
export function renderValueHtml(row, text, { mode = "full", claimKind = null } = {}) {
|
|
160
169
|
const unit = row.unit === null ? "" : ` data-unit="${escapeHtml(row.unit)}"`
|
|
161
|
-
|
|
170
|
+
const marker = mode === "value" ? ' data-fact-mode="value"' : ""
|
|
171
|
+
const mark = claimKind === null ? "" : ` data-claim-kind="${escapeHtml(claimKind)}"`
|
|
172
|
+
return `<data class="fact" value="${escapeHtml(row.value)}" data-fact="${row.fact_id}"${unit}${marker}${mark} data-lineage="${LINEAGE[row.kind]}">${escapeHtml(text)}</data>`
|
|
162
173
|
}
|
|
163
174
|
|
|
164
|
-
export function renderQuote(row, selector, text, format) {
|
|
175
|
+
export function renderQuote(row, selector, text, format, claimKind = null) {
|
|
165
176
|
const marker = `[${row.source_id}]`
|
|
166
177
|
if (format === "md") return `"${text}" ${marker}`
|
|
167
|
-
|
|
178
|
+
const mark = claimKind === null ? "" : ` data-claim-kind="${escapeHtml(claimKind)}"`
|
|
179
|
+
return `<q data-fact="${row.fact_id}"${mark} data-quote-sel="${escapeHtml(selector)}">${escapeHtml(text)}</q> ${marker}`
|
|
168
180
|
}
|
|
169
181
|
|
|
170
182
|
const RENDERED_MD_QUOTE = /^"([\s\S]*)" (\[S\d+\])$/u
|
|
@@ -54,9 +54,10 @@ export const POLISH_REFERENCE_REASON = "edit touches a character reference: rout
|
|
|
54
54
|
/** True when [start, end) overlaps a character reference of html or starts inside one. */
|
|
55
55
|
export function touchesReference(html, start, end) {
|
|
56
56
|
for (const match of String(html).matchAll(CHARACTER_REFERENCE)) {
|
|
57
|
-
|
|
57
|
+
const stop = match.index + match[0].length
|
|
58
|
+
if (match.index < end && (stop > start || (stop === start && !match[0].endsWith(";")))) return true
|
|
58
59
|
}
|
|
59
|
-
return
|
|
60
|
+
return false
|
|
60
61
|
}
|
|
61
62
|
|
|
62
63
|
const escape = (text) => text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { describe, expect, test } from "bun:test"
|
|
2
2
|
|
|
3
3
|
import { markupParts, skeletonChange } from "./html-markup.mjs"
|
|
4
|
-
import { applyEdits, POLISH_MARKUP_REASON, textOf } from "./polish-apply.mjs"
|
|
4
|
+
import { applyEdits, POLISH_MARKUP_REASON, POLISH_REFERENCE_REASON, textOf } from "./polish-apply.mjs"
|
|
5
5
|
import { diffPolish } from "./polish-diff.mjs"
|
|
6
6
|
|
|
7
7
|
const LEDGER = { rows: [{ fact_id: "F1", quote: "Revenue may reach 320 m." }] }
|
|
@@ -147,6 +147,20 @@ describe("polish edits never carry markup characters", () => {
|
|
|
147
147
|
const AMPD = "<p>We fund R&D labs here.</p>"
|
|
148
148
|
const TOM = "<p>Tom & Jerry ran.</p>"
|
|
149
149
|
describe("polish keeps character references intact", () => {
|
|
150
|
+
test.each(["style", "p"])("#given a quoted greater-than in a %s end tag #when its quote changes #then diff blocks", (tag: string) => {
|
|
151
|
+
const before = `<${tag}>p{}</${tag} a='>'> trailing prose`
|
|
152
|
+
const after = before.replace(">'", ">x")
|
|
153
|
+
expect(diffPolish(before, after, LEDGER).map((item: { code: string }) => item.code)).toContain("polish_diff_fail")
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
test.each(["&.", `&${"a".repeat(24)}.`])("#given %s immediately before an edit #when extended #then the reference reason is actionable", (reference: string) => {
|
|
157
|
+
const html = `<p>${reference} next</p>`
|
|
158
|
+
const result = applyEdits(html, [{ before: " next", after: "later" }], LEDGER)
|
|
159
|
+
expect(result.rejected[0]?.reason).toContain(POLISH_REFERENCE_REASON)
|
|
160
|
+
expect(result.out).toBe(html)
|
|
161
|
+
expect(diffPolish(html, html.replace(" next", "later"), LEDGER).map((item: { code: string }) => item.code)).toContain("polish_diff_fail")
|
|
162
|
+
})
|
|
163
|
+
|
|
150
164
|
test.each([
|
|
151
165
|
["an edit starting inside an entity", AMPD, "amp;D labs", "lt;D labs"],
|
|
152
166
|
["an edit starting right after a bare ampersand", TOM, " Jerry ran", "lt; ran"],
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test"
|
|
2
|
+
import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs"
|
|
3
|
+
import { tmpdir } from "node:os"
|
|
4
|
+
import { join } from "node:path"
|
|
5
|
+
import { spawnSync } from "node:child_process"
|
|
6
|
+
|
|
7
|
+
describe("polish partial-apply CLI contract", () => {
|
|
8
|
+
test.each([
|
|
9
|
+
["boundary rejection", [{ before: "Fine <em>words", after: "Better words" }], false, 0, 1, 0],
|
|
10
|
+
["accepted wording", [{ before: "Fine", after: "Good" }], true, 1, 0, 0],
|
|
11
|
+
["mixed edits", [{ before: "Fine", after: "Good" }, { before: "<em>words", after: "words" }], false, 1, 1, 0],
|
|
12
|
+
["unlocated wording", [{ before: "Missing", after: "Better" }], false, 0, 0, 1],
|
|
13
|
+
] as const)("#given %s #when partially applied #then JSON distinguishes full application from output success", (_label, editsInput, applied, count, rejected, unlocated) => {
|
|
14
|
+
const dir = mkdtempSync(join(tmpdir(), "polish-boundary-"))
|
|
15
|
+
try {
|
|
16
|
+
const report = join(dir, "report.html")
|
|
17
|
+
const edits = join(dir, "edits.json")
|
|
18
|
+
const ledger = join(dir, "ledger.json")
|
|
19
|
+
const out = join(dir, "out.html")
|
|
20
|
+
const html = "<p>Fine <em>words</em> here.</p>"
|
|
21
|
+
writeFileSync(report, html)
|
|
22
|
+
writeFileSync(edits, JSON.stringify(editsInput))
|
|
23
|
+
writeFileSync(ledger, JSON.stringify({ schemaVersion: 1, rows: [], events: [] }))
|
|
24
|
+
const result = spawnSync("node", [
|
|
25
|
+
join(import.meta.dir, "report-tools.mjs"), "fact", "polish-apply", report,
|
|
26
|
+
"--edits", edits, "--ledger", ledger, "--out", out,
|
|
27
|
+
"--brief", join(import.meta.dir, "tests/fixtures/fidelity-brief-locked.md"),
|
|
28
|
+
], { encoding: "utf8" })
|
|
29
|
+
expect(result.error).toBeUndefined()
|
|
30
|
+
const json = JSON.parse(result.stdout)
|
|
31
|
+
expect(json.data).toMatchObject({ applied: count, rejected, unlocated })
|
|
32
|
+
expect(json.defects.map((defect: { code: string }) => defect.code)).toEqual(
|
|
33
|
+
rejected > 0 ? ["polish_edit_rejected"] : unlocated > 0 ? ["polish_edit_unlocated"] : [],
|
|
34
|
+
)
|
|
35
|
+
expect(readFileSync(out, "utf8")).toBe(count > 0 ? html.replace("Fine", "Good") : html)
|
|
36
|
+
expect(result.status).toBe(0)
|
|
37
|
+
expect(json.applied).toBe(applied)
|
|
38
|
+
} finally {
|
|
39
|
+
rmSync(dir, { recursive: true, force: true })
|
|
40
|
+
}
|
|
41
|
+
})
|
|
42
|
+
})
|