lazycodex-ai 5.1.45 → 5.1.46
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/README.de.md +0 -5
- package/README.es.md +0 -5
- package/README.fr.md +0 -5
- package/README.it.md +0 -5
- package/README.ja.md +0 -5
- package/README.ko.md +0 -5
- package/README.md +4 -5
- package/README.pt-br.md +0 -5
- package/README.ru.md +0 -6
- package/README.zh-cn.md +0 -5
- package/dist/cli/index.js +76 -156
- package/dist/cli-node/index.js +76 -156
- package/package.json +1 -1
- package/packages/lsp-daemon/dist/cli.js +3 -3
- package/packages/lsp-daemon/dist/client.js +3 -3
- package/packages/lsp-daemon/dist/index.js +3 -3
- package/packages/lsp-tools-mcp/dist/cli.js +3 -3
- package/packages/lsp-tools-mcp/dist/mcp.js +3 -3
- package/packages/omo-codex/plugin/.codex-plugin/plugin.json +1 -1
- package/packages/omo-codex/plugin/components/bootstrap/dist/cli.js +22 -12
- 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/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 +1 -1
- 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/ultrawork/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/ultrawork/package.json +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/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 +12 -12
- package/packages/omo-codex/plugin/package.json +1 -1
- package/packages/omo-codex/plugin/scripts/sync-skills.mjs +8 -3
- package/packages/omo-codex/plugin/shared/test/config-loader.test.ts +0 -2
- package/packages/omo-codex/plugin/skills/ast-grep/AGENTS.md +1 -1
- package/packages/omo-codex/plugin/skills/browser/runtime/omowright/manifest.json +1 -1
- package/packages/omo-codex/plugin/skills/frontend/ATTRIBUTION.md +2 -2
- package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/AGENTS.md +1 -1
- package/packages/omo-codex/plugin/skills/ulw-research/AGENTS.md +14 -2
- package/packages/omo-codex/plugin/skills/ulw-research/SKILL.md +36 -11
- package/packages/omo-codex/plugin/skills/ulw-research/references/deliverable-phase.md +73 -13
- package/packages/omo-codex/plugin/skills/ulw-research/references/fact-ledger-grammar.md +19 -1
- package/packages/omo-codex/plugin/skills/ulw-research/references/fact-ledger.md +95 -0
- package/packages/omo-codex/plugin/skills/ulw-research/references/fidelity-claim-kinds.md +19 -0
- package/packages/omo-codex/plugin/skills/ulw-research/references/report-gates.md +21 -4
- package/packages/omo-codex/plugin/skills/ulw-research/references/report-visualization.md +2 -0
- package/packages/omo-codex/plugin/skills/ulw-research/references/unit-rules.md +157 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/claim-kinds.mjs +116 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/cli-support.mjs +19 -5
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/contracts.mjs +27 -6
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-commands-meaning.mjs +191 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-commands-verify.mjs +164 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-commands.mjs +239 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-derive.mjs +66 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-expr.mjs +108 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-fill.mjs +194 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-ledger-kinds.mjs +127 -13
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-ledger-schema.mjs +33 -16
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-ledger.mjs +29 -9
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-locate.mjs +231 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-pairs.mjs +187 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-recompute.mjs +89 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-binding.mjs +130 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-brief.mjs +5 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-claims.mjs +89 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-lexicon.mjs +145 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-records.mjs +163 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-state.mjs +2 -1
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-trace.mjs +85 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-values.mjs +229 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/gates-fidelity.mjs +113 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/gates-static.mjs +28 -8
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/html-lite.mjs +2 -2
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/html-markup.mjs +182 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/md-lite.mjs +193 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/meaning-check.mjs +196 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/outcome.mjs +6 -5
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/placeholder.mjs +184 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/polish-apply.mjs +115 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/polish-diff.mjs +31 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/polish-project.mjs +125 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/polish-rules.mjs +170 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/report-tools-commands.mjs +19 -8
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/report-tools.mjs +15 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/source-commands.mjs +140 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/source-snapshot.mjs +176 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/unit-algebra.mjs +101 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/unit-errors.mjs +23 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/unit-metadata.mjs +31 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/unit-pairs.mjs +72 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/unit-precision.mjs +44 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/unit-rules-shapes.mjs +64 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/unit-rules.mjs +173 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/units.mjs +4 -4
- package/packages/omo-codex/plugin/test/sync-skills-codex-compatibility.test.mjs +19 -0
- package/packages/omo-codex/plugin/test/sync-skills-test-support.mjs +7 -3
- package/packages/omo-codex/scripts/install-dist/install-local.mjs +26 -16
- package/packages/shared-skills/skills/ast-grep/AGENTS.md +1 -1
- package/packages/shared-skills/skills/browser/runtime/omowright/manifest.json +1 -1
- package/packages/shared-skills/skills/frontend/ATTRIBUTION.md +2 -2
- package/packages/shared-skills/skills/ultimate-browsing/engine/AGENTS.md +1 -1
- package/packages/shared-skills/skills/ulw-research/AGENTS.md +14 -2
- package/packages/shared-skills/skills/ulw-research/SKILL.md +37 -12
- package/packages/shared-skills/skills/ulw-research/references/deliverable-phase.md +73 -13
- package/packages/shared-skills/skills/ulw-research/references/fact-ledger-grammar.md +19 -1
- package/packages/shared-skills/skills/ulw-research/references/fact-ledger.md +95 -0
- package/packages/shared-skills/skills/ulw-research/references/fidelity-claim-kinds.md +19 -0
- package/packages/shared-skills/skills/ulw-research/references/report-gates.md +21 -4
- package/packages/shared-skills/skills/ulw-research/references/report-visualization.md +2 -0
- package/packages/shared-skills/skills/ulw-research/references/unit-rules.md +157 -0
- package/packages/shared-skills/skills/ulw-research/scripts/claim-kinds.mjs +116 -0
- package/packages/shared-skills/skills/ulw-research/scripts/claim-kinds.test.ts +193 -0
- package/packages/shared-skills/skills/ulw-research/scripts/cli-support.mjs +19 -5
- package/packages/shared-skills/skills/ulw-research/scripts/cli-support.test.ts +13 -2
- package/packages/shared-skills/skills/ulw-research/scripts/contracts.mjs +27 -6
- package/packages/shared-skills/skills/ulw-research/scripts/contracts.test.ts +3 -0
- package/packages/shared-skills/skills/ulw-research/scripts/copy-returns.test.ts +183 -0
- package/packages/shared-skills/skills/ulw-research/scripts/delivery-barrier.test.ts +245 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fact-commands-meaning.mjs +191 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fact-commands-verify.mjs +164 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fact-commands.mjs +239 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fact-derive.mjs +66 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fact-derive.test.ts +375 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fact-expr.mjs +108 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fact-fill.mjs +194 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fact-fill.test.ts +221 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fact-ledger-grammar.test.ts +9 -2
- package/packages/shared-skills/skills/ulw-research/scripts/fact-ledger-kinds.mjs +127 -13
- package/packages/shared-skills/skills/ulw-research/scripts/fact-ledger-kinds.test.ts +18 -3
- package/packages/shared-skills/skills/ulw-research/scripts/fact-ledger-legacy.test.ts +160 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fact-ledger-schema.mjs +33 -16
- package/packages/shared-skills/skills/ulw-research/scripts/fact-ledger.mjs +29 -9
- package/packages/shared-skills/skills/ulw-research/scripts/fact-ledger.test.ts +2 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fact-locate.mjs +231 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fact-locate.test.ts +118 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fact-pairs.mjs +187 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fact-pairs.test.ts +167 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fact-recompute.mjs +89 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fact-unit-dimensions.test.ts +409 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fact-unit-rules.test.ts +388 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fidelity-binding.mjs +130 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fidelity-binding.test.ts +81 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fidelity-brief.mjs +5 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fidelity-claims.mjs +89 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fidelity-lexicon.mjs +145 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fidelity-lexicon.test.ts +117 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fidelity-records.mjs +163 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fidelity-state.mjs +2 -1
- package/packages/shared-skills/skills/ulw-research/scripts/fidelity-trace.mjs +85 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fidelity-trace.test.ts +58 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fidelity-values.mjs +229 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fidelity-values.test.ts +270 -0
- package/packages/shared-skills/skills/ulw-research/scripts/gates-fidelity.mjs +113 -0
- package/packages/shared-skills/skills/ulw-research/scripts/gates-fidelity.test.ts +73 -0
- package/packages/shared-skills/skills/ulw-research/scripts/gates-static.mjs +28 -8
- package/packages/shared-skills/skills/ulw-research/scripts/html-lite.mjs +2 -2
- package/packages/shared-skills/skills/ulw-research/scripts/html-markup.mjs +182 -0
- package/packages/shared-skills/skills/ulw-research/scripts/md-lite.mjs +193 -0
- package/packages/shared-skills/skills/ulw-research/scripts/md-lite.test.ts +271 -0
- package/packages/shared-skills/skills/ulw-research/scripts/meaning-check.mjs +196 -0
- package/packages/shared-skills/skills/ulw-research/scripts/meaning-check.test.ts +228 -0
- package/packages/shared-skills/skills/ulw-research/scripts/outcome.mjs +6 -5
- package/packages/shared-skills/skills/ulw-research/scripts/placeholder.mjs +184 -0
- package/packages/shared-skills/skills/ulw-research/scripts/placeholder.test.ts +77 -0
- package/packages/shared-skills/skills/ulw-research/scripts/polish-apply.mjs +115 -0
- package/packages/shared-skills/skills/ulw-research/scripts/polish-apply.test.ts +363 -0
- package/packages/shared-skills/skills/ulw-research/scripts/polish-diff.mjs +31 -0
- package/packages/shared-skills/skills/ulw-research/scripts/polish-diff.test.ts +168 -0
- package/packages/shared-skills/skills/ulw-research/scripts/polish-markup.test.ts +187 -0
- package/packages/shared-skills/skills/ulw-research/scripts/polish-project.mjs +125 -0
- package/packages/shared-skills/skills/ulw-research/scripts/polish-rules.mjs +170 -0
- package/packages/shared-skills/skills/ulw-research/scripts/report-tools-commands.mjs +19 -8
- package/packages/shared-skills/skills/ulw-research/scripts/report-tools.mjs +15 -0
- package/packages/shared-skills/skills/ulw-research/scripts/report-tools.test.ts +458 -2
- package/packages/shared-skills/skills/ulw-research/scripts/source-commands.mjs +140 -0
- package/packages/shared-skills/skills/ulw-research/scripts/source-snapshot.mjs +176 -0
- package/packages/shared-skills/skills/ulw-research/scripts/source-snapshot.test.ts +113 -0
- package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/assets/share.csv +3 -0
- package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/gen-legacy-dev.mjs +89 -0
- package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/ledger-basic.json +223 -0
- package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/ledger-legacy-dev.json +859 -0
- package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/ledger-verify.json +420 -0
- package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/report-bad.html +19 -0
- package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/report-good.html +39 -0
- package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/sources/S4/acronyms.json +3 -0
- package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/sources/S4/meta.json +9 -0
- package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/sources/S4/sentences.jsonl +7 -0
- package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/sources/S4/text.txt +7 -0
- package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/sources/S4.html +19 -0
- package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/sources/S5/acronyms.json +1 -0
- package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/sources/S5/meta.json +9 -0
- package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/sources/S5/sentences.jsonl +1 -0
- package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/sources/S5/text.txt +1 -0
- package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/sources/S9.txt +1 -0
- package/packages/shared-skills/skills/ulw-research/scripts/unit-algebra.mjs +101 -0
- package/packages/shared-skills/skills/ulw-research/scripts/unit-errors.mjs +23 -0
- package/packages/shared-skills/skills/ulw-research/scripts/unit-metadata.mjs +31 -0
- package/packages/shared-skills/skills/ulw-research/scripts/unit-pairs.mjs +72 -0
- package/packages/shared-skills/skills/ulw-research/scripts/unit-precision.mjs +44 -0
- package/packages/shared-skills/skills/ulw-research/scripts/unit-rules-shapes.mjs +64 -0
- package/packages/shared-skills/skills/ulw-research/scripts/unit-rules.mjs +173 -0
- package/packages/shared-skills/skills/ulw-research/scripts/units.mjs +4 -4
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
// md-lite.mjs - a light Markdown block model for md/post deliverables.
|
|
2
|
+
//
|
|
3
|
+
// parseMarkdown(text) returns { blocks } in document order, each block one of heading,
|
|
4
|
+
// paragraph, list_item, table_row, code, blockquote, with start/end offsets into the raw text
|
|
5
|
+
// so text.slice(start, end) === block.text (table cells carry their own offsets). Headings carry
|
|
6
|
+
// level 1-6. Code blocks are fenced (``` or ~~~); an unterminated fence runs to the end of the
|
|
7
|
+
// text and is reported in problems as unterminated_fence, which a caller must refuse rather than
|
|
8
|
+
// skip. Callers skip kind 'code' when they look for numbers. sectionKindsMd(blocks) classifies
|
|
9
|
+
// sections with the gates-static classifier itself (sectionKind, closingTest). It never renders
|
|
10
|
+
// HTML. Plain Node ESM, sibling imports only.
|
|
11
|
+
|
|
12
|
+
import { closingTest, sectionKind } from "./gates-static.mjs"
|
|
13
|
+
|
|
14
|
+
export { closingTest, sectionKind }
|
|
15
|
+
|
|
16
|
+
/** A fence opener: a backtick fence's info string holds no backtick (else it is an inline span). */
|
|
17
|
+
const FENCE = /^ {0,3}(?:(`{3,})[^`]*|(~{3,}).*)$/
|
|
18
|
+
const HEADING = /^ {0,3}(#{1,6})(?=[ \t]|$)/
|
|
19
|
+
const BLOCKQUOTE = /^ {0,3}> ?/
|
|
20
|
+
/** A bullet or an ordered marker (digits, then . or ), then a space); group 1 is the number. */
|
|
21
|
+
const LIST_ITEM = /^[ \t]*(?:[-*+]|(\d{1,9})[.)])[ \t]+/
|
|
22
|
+
const TABLE_SEPARATOR = /^[ \t]*\|?[ \t]*:?-+:?[ \t]*(?:\|[ \t]*:?-+:?[ \t]*)*\|?[ \t]*$/
|
|
23
|
+
const CLOSING_HASHES = /[ \t]+#+[ \t]*$|^#+[ \t]*$/
|
|
24
|
+
const THEMATIC_BREAK = /^ {0,3}([-*_])(?:[ \t]*\1){2,}[ \t]*$/
|
|
25
|
+
|
|
26
|
+
/** The Markdown block grammar, one named pattern per syntax. */
|
|
27
|
+
export const MD_BLOCK_PATTERNS = Object.freeze({ FENCE, HEADING, BLOCKQUOTE, LIST_ITEM, TABLE_SEPARATOR, CLOSING_HASHES, THEMATIC_BREAK })
|
|
28
|
+
|
|
29
|
+
/** Split into lines with offsets; end excludes the newline and a trailing carriage return. */
|
|
30
|
+
function linesOf(text) {
|
|
31
|
+
const lines = []
|
|
32
|
+
let start = 0
|
|
33
|
+
while (start <= text.length) {
|
|
34
|
+
const newline = text.indexOf("\n", start)
|
|
35
|
+
const next = newline === -1 ? text.length + 1 : newline + 1
|
|
36
|
+
let end = newline === -1 ? text.length : newline
|
|
37
|
+
if (end > start && text[end - 1] === "\r") end--
|
|
38
|
+
lines.push({ start, end, next, raw: text.slice(start, end) })
|
|
39
|
+
start = next
|
|
40
|
+
}
|
|
41
|
+
return lines
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Narrow [start, end) to its non-whitespace core. */
|
|
45
|
+
function trimRange(text, start, end) {
|
|
46
|
+
let from = start
|
|
47
|
+
let to = end
|
|
48
|
+
while (from < to && /\s/.test(text[from])) from++
|
|
49
|
+
while (to > from && /\s/.test(text[to - 1])) to--
|
|
50
|
+
return { start: from, end: to }
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function block(text, kind, range, extra = {}) {
|
|
54
|
+
return { kind, ...extra, text: text.slice(range.start, range.end), start: range.start, end: range.end }
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function isSeparator(line) {
|
|
58
|
+
return line !== undefined && line.raw.includes("-") && TABLE_SEPARATOR.test(line.raw)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** Cells split on unescaped pipes, outer pipes dropped, each cell trimmed with offsets. */
|
|
62
|
+
function tableCells(text, range) {
|
|
63
|
+
const bounds = []
|
|
64
|
+
for (let k = range.start; k < range.end; k++) {
|
|
65
|
+
let slashes = 0
|
|
66
|
+
while (k - slashes - 1 >= range.start && text[k - slashes - 1] === "\\") slashes++
|
|
67
|
+
if (text[k] === "|" && slashes % 2 === 0) bounds.push(k)
|
|
68
|
+
}
|
|
69
|
+
const edges = [range.start - 1, ...bounds, range.end]
|
|
70
|
+
if (bounds[0] === range.start) edges.shift()
|
|
71
|
+
if (bounds.at(-1) === range.end - 1 && bounds.length > 0) edges.pop()
|
|
72
|
+
const cells = []
|
|
73
|
+
for (let k = 0; k + 1 < edges.length; k++) {
|
|
74
|
+
const cell = trimRange(text, edges[k] + 1, edges[k + 1])
|
|
75
|
+
cells.push({ text: text.slice(cell.start, cell.end), start: cell.start, end: cell.end })
|
|
76
|
+
}
|
|
77
|
+
return cells
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function headingBlock(text, line, marker) {
|
|
81
|
+
const after = line.start + line.raw.indexOf(marker) + marker.length
|
|
82
|
+
const range = trimRange(text, after, line.end)
|
|
83
|
+
const closing = text.slice(range.start, range.end).match(CLOSING_HASHES)
|
|
84
|
+
if (closing) range.end = trimRange(text, range.start, range.start + (closing.index ?? 0)).end
|
|
85
|
+
return block(text, "heading", range, { level: marker.length })
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Parse Markdown into a flat block list.
|
|
90
|
+
* @param {string} text
|
|
91
|
+
* @returns {{ blocks: { kind: string, level?: number, text: string, start: number, end: number,
|
|
92
|
+
* cells?: { text: string, start: number, end: number }[] }[],
|
|
93
|
+
* problems: { code: "unterminated_fence", line: number, start: number }[] }}
|
|
94
|
+
*/
|
|
95
|
+
export function parseMarkdown(text) {
|
|
96
|
+
const source = String(text ?? "")
|
|
97
|
+
const lines = linesOf(source)
|
|
98
|
+
const blocks = []
|
|
99
|
+
const problems = []
|
|
100
|
+
let paragraph = null
|
|
101
|
+
let inTable = false
|
|
102
|
+
const flush = () => {
|
|
103
|
+
if (paragraph) blocks.push(block(source, "paragraph", paragraph))
|
|
104
|
+
paragraph = null
|
|
105
|
+
}
|
|
106
|
+
for (let i = 0; i < lines.length; i++) {
|
|
107
|
+
const line = lines[i]
|
|
108
|
+
const fence = line.raw.match(FENCE)
|
|
109
|
+
if (fence) {
|
|
110
|
+
flush()
|
|
111
|
+
inTable = false
|
|
112
|
+
const open = fence[1] ?? fence[2]
|
|
113
|
+
const codeStart = Math.min(line.next, source.length)
|
|
114
|
+
let close = lines.length
|
|
115
|
+
for (let k = i + 1; k < lines.length; k++) {
|
|
116
|
+
const candidate = lines[k].raw.trim()
|
|
117
|
+
if (candidate.startsWith(open[0].repeat(open.length)) && /^(`+|~+)$/.test(candidate)) {
|
|
118
|
+
close = k
|
|
119
|
+
break
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
let codeEnd = source.length
|
|
123
|
+
if (close === lines.length) problems.push({ code: "unterminated_fence", line: i + 1, start: line.start })
|
|
124
|
+
else {
|
|
125
|
+
codeEnd = Math.max(codeStart, lines[close].start - 1)
|
|
126
|
+
if (codeEnd > codeStart && source[codeEnd - 1] === "\r") codeEnd--
|
|
127
|
+
}
|
|
128
|
+
blocks.push(block(source, "code", { start: codeStart, end: codeEnd }))
|
|
129
|
+
i = close
|
|
130
|
+
continue
|
|
131
|
+
}
|
|
132
|
+
if (line.raw.trim() === "") {
|
|
133
|
+
flush()
|
|
134
|
+
inTable = false
|
|
135
|
+
continue
|
|
136
|
+
}
|
|
137
|
+
const range = trimRange(source, line.start, line.end)
|
|
138
|
+
if (!inTable && line.raw.includes("|") && isSeparator(lines[i + 1])) inTable = true
|
|
139
|
+
if (inTable && line.raw.includes("|")) {
|
|
140
|
+
flush()
|
|
141
|
+
if (!isSeparator(line)) blocks.push(block(source, "table_row", range, { cells: tableCells(source, range) }))
|
|
142
|
+
continue
|
|
143
|
+
}
|
|
144
|
+
inTable = false
|
|
145
|
+
const heading = line.raw.match(HEADING)
|
|
146
|
+
if (heading) {
|
|
147
|
+
flush()
|
|
148
|
+
blocks.push(headingBlock(source, line, heading[1]))
|
|
149
|
+
continue
|
|
150
|
+
}
|
|
151
|
+
if (THEMATIC_BREAK.test(line.raw)) {
|
|
152
|
+
flush()
|
|
153
|
+
continue
|
|
154
|
+
}
|
|
155
|
+
const marker = line.raw.match(BLOCKQUOTE) ?? line.raw.match(LIST_ITEM)
|
|
156
|
+
// Only an ordered marker numbered 1 may interrupt a paragraph (CommonMark); else it is text.
|
|
157
|
+
const continues = paragraph !== null && marker?.[1] !== undefined && Number(marker[1]) !== 1
|
|
158
|
+
if (marker && !continues) {
|
|
159
|
+
flush()
|
|
160
|
+
const kind = BLOCKQUOTE.test(line.raw) ? "blockquote" : "list_item"
|
|
161
|
+
const content = trimRange(source, line.start + marker[0].length, line.end)
|
|
162
|
+
if (content.end > content.start) blocks.push(block(source, kind, content))
|
|
163
|
+
continue
|
|
164
|
+
}
|
|
165
|
+
if (paragraph) paragraph.end = range.end
|
|
166
|
+
else paragraph = { start: range.start, end: range.end }
|
|
167
|
+
}
|
|
168
|
+
flush()
|
|
169
|
+
return { blocks, problems }
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Section kind of every block, parallel to blocks: an h2 opens a section whose kind comes from
|
|
174
|
+
* its heading text (sources, closing, toc, appendix, glossary, or null for findings); an h3 that
|
|
175
|
+
* reads as a closing heading marks its blocks closing until the next h2 or h3, as in gates-static.
|
|
176
|
+
* @param {{ kind: string, level?: number, text: string }[]} blocks
|
|
177
|
+
* @param {{ closingPattern?: RegExp|string }} [options] extra closing heading pattern (the spec's)
|
|
178
|
+
* @returns {(string|null)[]}
|
|
179
|
+
*/
|
|
180
|
+
export function sectionKindsMd(blocks, options = {}) {
|
|
181
|
+
const isClosing = closingTest(options.closingPattern)
|
|
182
|
+
let section = null
|
|
183
|
+
let closingH3 = false
|
|
184
|
+
return blocks.map((entry) => {
|
|
185
|
+
if (entry.kind === "heading" && entry.level === 2) {
|
|
186
|
+
section = sectionKind(entry.text, isClosing)
|
|
187
|
+
closingH3 = false
|
|
188
|
+
} else if (entry.kind === "heading" && entry.level === 3) {
|
|
189
|
+
closingH3 = isClosing(entry.text)
|
|
190
|
+
}
|
|
191
|
+
return closingH3 ? "closing" : section
|
|
192
|
+
})
|
|
193
|
+
}
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
// meaning-check.mjs - ingest the meaning readers' verdicts over fact-pairs output.
|
|
2
|
+
//
|
|
3
|
+
// ingestVerdicts reads verdict files written by separate reader lanes (cheap and strong tiers),
|
|
4
|
+
// keeps a drift verdict only when its pair exists, its tier and drift_kind are in the closed sets,
|
|
5
|
+
// and both its fragment (from the sentence) and source_fragment (from the quote or statement) are
|
|
6
|
+
// non-empty substrings after whitespace normalization; every other verdict is dropped and logged.
|
|
7
|
+
// Both tiers naming the same drift_kind (not "other") on a pair is meaning_drift_confirmed (major,
|
|
8
|
+
// never integrity); one tier is meaning_drift_warn (minor). Each flagged pair yields one
|
|
9
|
+
// calibration line; a dispute is recorded there and never removes the defect. Coverage is
|
|
10
|
+
// fail-closed: every pair no reader judged, and every force_strong pair no strong reader judged,
|
|
11
|
+
// is listed in `unverified`, so a gate can tell a clean check apart from a pair nobody read.
|
|
12
|
+
// No model, no fetch.
|
|
13
|
+
|
|
14
|
+
import { appendFileSync, readFileSync } from "node:fs"
|
|
15
|
+
|
|
16
|
+
import { DEFECT_CODES } from "./contracts.mjs"
|
|
17
|
+
|
|
18
|
+
export const DRIFT_KINDS = Object.freeze([
|
|
19
|
+
"subject_changed", "scope_widened", "hedge_dropped", "certainty_added", "causality_added",
|
|
20
|
+
"direction_flipped", "population_changed", "evidence_grade_raised", "other",
|
|
21
|
+
])
|
|
22
|
+
export const MODEL_TIERS = Object.freeze(["cheap", "strong"])
|
|
23
|
+
export const VERDICTS = Object.freeze(["drift", "ok"])
|
|
24
|
+
export const isDriftKind = (kind) => DRIFT_KINDS.includes(kind)
|
|
25
|
+
/** The machine-read verdict contract a reader lane receives with pairs.json; built from the registries above. */
|
|
26
|
+
export const VERDICT_SCHEMA = Object.freeze({
|
|
27
|
+
model_tier: MODEL_TIERS,
|
|
28
|
+
verdict: VERDICTS,
|
|
29
|
+
drift_kind: DRIFT_KINDS,
|
|
30
|
+
required: Object.freeze(["pair_id", "verdict"]),
|
|
31
|
+
drift_required: Object.freeze(["drift_kind", "fragment", "source_fragment"]),
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
/** NFC, every whitespace run collapsed to one space, trimmed. */
|
|
35
|
+
const squash = (text) => String(text ?? "").normalize("NFC").replace(/\s+/gu, " ").trim()
|
|
36
|
+
|
|
37
|
+
/** Read a verdict file, mapping an unreadable path or invalid JSON to a short typed error. */
|
|
38
|
+
function parseFile(path) {
|
|
39
|
+
let text
|
|
40
|
+
try {
|
|
41
|
+
text = readFileSync(path, "utf8")
|
|
42
|
+
} catch {
|
|
43
|
+
throw new TypeError(`${path}: cannot read the verdict file`)
|
|
44
|
+
}
|
|
45
|
+
try {
|
|
46
|
+
return JSON.parse(text)
|
|
47
|
+
} catch {
|
|
48
|
+
throw new TypeError(`${path}: the verdict file is not valid JSON`)
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function verdictsOf(input) {
|
|
53
|
+
const label = typeof input === "string" ? input : "<inline>"
|
|
54
|
+
const parsed = typeof input === "string" ? parseFile(input) : input
|
|
55
|
+
const list = Array.isArray(parsed) ? parsed : parsed?.verdicts
|
|
56
|
+
if (!Array.isArray(list)) throw new TypeError(`${label}: a verdict file is an array of verdicts or { verdicts: [...] }`)
|
|
57
|
+
const tier = Array.isArray(parsed) ? undefined : parsed.model_tier
|
|
58
|
+
return list.map((verdict) => ({ verdict: tier === undefined || verdict?.model_tier !== undefined ? verdict : { ...verdict, model_tier: tier }, file: label }))
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** The reason a verdict is dropped, or null when it is accepted. */
|
|
62
|
+
function dropReason(verdict, pair) {
|
|
63
|
+
if (verdict === null || typeof verdict !== "object") return "malformed_verdict"
|
|
64
|
+
if (!pair) return "unknown_pair"
|
|
65
|
+
if (!VERDICTS.includes(verdict.verdict)) return "invalid_verdict"
|
|
66
|
+
if (!MODEL_TIERS.includes(verdict.model_tier)) return "invalid_model_tier"
|
|
67
|
+
if (verdict.verdict === "ok") return null
|
|
68
|
+
if (!isDriftKind(verdict.drift_kind)) return "unknown_drift_kind"
|
|
69
|
+
const [fragment, sourceFragment] = [squash(verdict.fragment), squash(verdict.source_fragment)]
|
|
70
|
+
if (fragment === "" || sourceFragment === "") return "empty_fragment"
|
|
71
|
+
if (!squash(pair.sentence).includes(fragment)) return "fragment_not_found"
|
|
72
|
+
const source = pair.kind === "claim" ? pair.statement : pair.quote
|
|
73
|
+
if (!squash(source).includes(sourceFragment)) return "source_fragment_not_found"
|
|
74
|
+
return null
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function defect(code, pair, driftKind, tiers, verdicts) {
|
|
78
|
+
const entry = DEFECT_CODES[code]
|
|
79
|
+
// The fragment must come from the verdict whose drift_kind is being reported, not from whichever
|
|
80
|
+
// verdict happened to arrive first on the pair.
|
|
81
|
+
const first = verdicts.find((verdict) => verdict.drift_kind === driftKind) ?? verdicts[0]
|
|
82
|
+
return {
|
|
83
|
+
code,
|
|
84
|
+
severity: entry.severity,
|
|
85
|
+
integrity: entry.integrity,
|
|
86
|
+
pair_id: pair.pair_id,
|
|
87
|
+
sentence_key: pair.sentence_key ?? null,
|
|
88
|
+
drift_kind: driftKind,
|
|
89
|
+
tiers,
|
|
90
|
+
fragment: first.fragment,
|
|
91
|
+
source_fragment: first.source_fragment,
|
|
92
|
+
selector: null,
|
|
93
|
+
line: null,
|
|
94
|
+
message: `${pair.pair_id}: ${tiers.join(" and ")} reader${tiers.length > 1 ? "s" : ""} found ${driftKind} at "${squash(first.fragment)}"`,
|
|
95
|
+
hint: entry.hint,
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** One meaning_unverified defect per coverage gap, so blocksDelivery sees an unread pair through the registry. */
|
|
100
|
+
export function unverifiedDefects(unverified) {
|
|
101
|
+
const entry = DEFECT_CODES.meaning_unverified
|
|
102
|
+
return unverified.map((gap) => ({
|
|
103
|
+
code: "meaning_unverified",
|
|
104
|
+
severity: entry.severity,
|
|
105
|
+
integrity: entry.integrity,
|
|
106
|
+
pair_id: gap.pair_id,
|
|
107
|
+
reason: gap.reason,
|
|
108
|
+
selector: null,
|
|
109
|
+
line: null,
|
|
110
|
+
message: `${gap.pair_id}: ${gap.reason === "no_strong_verdict" ? "no strong reader judged this force_strong pair" : "no reader judged this pair"}`,
|
|
111
|
+
hint: entry.hint,
|
|
112
|
+
}))
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/** Pairs with no accepted verdict, and force_strong pairs with no accepted strong verdict, in pair order. */
|
|
116
|
+
function coverageGaps(list, accepted) {
|
|
117
|
+
const tiersByPair = new Map()
|
|
118
|
+
for (const verdict of accepted) {
|
|
119
|
+
tiersByPair.set(verdict.pair_id, (tiersByPair.get(verdict.pair_id) ?? new Set()).add(verdict.model_tier))
|
|
120
|
+
}
|
|
121
|
+
const gaps = []
|
|
122
|
+
for (const pair of list) {
|
|
123
|
+
const tiers = tiersByPair.get(pair.pair_id)
|
|
124
|
+
if (tiers === undefined || tiers.size === 0) gaps.push({ pair_id: pair.pair_id, reason: "no_verdict" })
|
|
125
|
+
else if (pair.force_strong === true && !tiers.has("strong")) gaps.push({ pair_id: pair.pair_id, reason: "no_strong_verdict" })
|
|
126
|
+
}
|
|
127
|
+
return gaps
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** Group accepted drift verdicts per pair, then per tier. */
|
|
131
|
+
function flaggedPairs(accepted) {
|
|
132
|
+
const byPair = new Map()
|
|
133
|
+
for (const verdict of accepted) {
|
|
134
|
+
if (verdict.verdict !== "drift") continue
|
|
135
|
+
const tiers = byPair.get(verdict.pair_id) ?? new Map()
|
|
136
|
+
tiers.set(verdict.model_tier, [...(tiers.get(verdict.model_tier) ?? []), verdict])
|
|
137
|
+
byPair.set(verdict.pair_id, tiers)
|
|
138
|
+
}
|
|
139
|
+
return byPair
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/** The drift_kind both tiers named (never "other"), or null. */
|
|
143
|
+
function agreedKind(tiers) {
|
|
144
|
+
const [cheap, strong] = MODEL_TIERS.map((tier) => tiers.get(tier) ?? [])
|
|
145
|
+
return cheap.map((verdict) => verdict.drift_kind).find((kind) => kind !== "other" && strong.some((verdict) => verdict.drift_kind === kind)) ?? null
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* @param {object[] | { pairs: object[] }} pairs extractPairs(...) output
|
|
150
|
+
* @param {(string | object[] | { verdicts: object[], model_tier?: string })[]} verdictFiles paths, or parsed verdict lists
|
|
151
|
+
* @param {{ dispute?: { pair_id: string, reason: string }, calibrationPath?: string }} [options]
|
|
152
|
+
* calibrationPath: append each calibration line to this JSONL file (fidelity-calibration.jsonl)
|
|
153
|
+
* @returns {{ defects: object[], dropped: object[], unverified: { pair_id: string, reason: string }[], calibrationLines: string[] }}
|
|
154
|
+
* unverified lists, in pair order, every pair no reader judged (`no_verdict`) and every
|
|
155
|
+
* force_strong pair no strong reader judged (`no_strong_verdict`); a verdict that was dropped
|
|
156
|
+
* does not count as read. A consumer MUST treat a non-empty list as blocking: the pairs were
|
|
157
|
+
* never checked, so an empty `defects` list does not mean the report is clean.
|
|
158
|
+
* @throws {TypeError} when pairs is neither an array nor `{ pairs: [...] }` (fail closed: a missing
|
|
159
|
+
* pairs input never reads as a fully verified report)
|
|
160
|
+
*/
|
|
161
|
+
export function ingestVerdicts(pairs, verdictFiles, options = {}) {
|
|
162
|
+
const list = Array.isArray(pairs) ? pairs : pairs?.pairs
|
|
163
|
+
if (!Array.isArray(list)) throw new TypeError("pairs: expected an array of pairs or { pairs: [...] }")
|
|
164
|
+
const byId = new Map(list.map((pair) => [pair.pair_id, pair]))
|
|
165
|
+
const accepted = []
|
|
166
|
+
const dropped = []
|
|
167
|
+
for (const input of verdictFiles) {
|
|
168
|
+
for (const { verdict, file } of verdictsOf(input)) {
|
|
169
|
+
const reason = dropReason(verdict, byId.get(verdict?.pair_id))
|
|
170
|
+
if (reason === null) accepted.push(verdict)
|
|
171
|
+
else dropped.push({ file, pair_id: verdict?.pair_id ?? null, model_tier: verdict?.model_tier ?? null, reason })
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
const dispute = options.dispute ?? null
|
|
175
|
+
const defects = []
|
|
176
|
+
const calibrationLines = []
|
|
177
|
+
const flagged = flaggedPairs(accepted)
|
|
178
|
+
for (const pair of list) {
|
|
179
|
+
const tiers = flagged.get(pair.pair_id)
|
|
180
|
+
if (!tiers) continue
|
|
181
|
+
const tierNames = MODEL_TIERS.filter((tier) => tiers.has(tier))
|
|
182
|
+
const agreed = agreedKind(tiers)
|
|
183
|
+
const verdicts = MODEL_TIERS.flatMap((tier) => tiers.get(tier) ?? [])
|
|
184
|
+
const kind = agreed ?? verdicts[0].drift_kind
|
|
185
|
+
const confirmed = agreed !== null
|
|
186
|
+
defects.push(defect(confirmed ? "meaning_drift_confirmed" : "meaning_drift_warn", pair, kind, tierNames, verdicts))
|
|
187
|
+
const disputed = dispute !== null && dispute.pair_id === pair.pair_id
|
|
188
|
+
calibrationLines.push(JSON.stringify({ kind: pair.kind, pair_id: pair.pair_id, tiers: tierNames, drift_kind: kind, confirmed, disputed, reason: disputed ? String(dispute.reason ?? "") : null }))
|
|
189
|
+
}
|
|
190
|
+
const unverified = coverageGaps(list, accepted)
|
|
191
|
+
if (dispute !== null && !flagged.has(dispute.pair_id)) {
|
|
192
|
+
dropped.push({ file: "<dispute>", pair_id: dispute.pair_id, model_tier: null, reason: "dispute_not_flagged" })
|
|
193
|
+
}
|
|
194
|
+
if (options.calibrationPath && calibrationLines.length > 0) appendFileSync(options.calibrationPath, `${calibrationLines.join("\n")}\n`)
|
|
195
|
+
return { defects, dropped, unverified, calibrationLines }
|
|
196
|
+
}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
// outcome.mjs - the per-run outcome manifest (`outcome.json`) and the closing briefing.
|
|
2
|
-
//
|
|
3
|
-
// manifest, the sources-ledger text, and an injected `now`. node: builtins only.
|
|
1
|
+
// outcome.mjs - the per-run outcome manifest (`outcome.json`) and the closing briefing. Manifest functions are pure (they return a new manifest).
|
|
2
|
+
// The briefing reads only the manifest, the sources-ledger text, and an injected `now`. node: builtins only.
|
|
4
3
|
|
|
5
4
|
import * as nodeFs from "node:fs"
|
|
6
5
|
import { basename, dirname, join } from "node:path"
|
|
@@ -86,7 +85,7 @@ export function setDeliverable(manifest, format, status, detail = {}) {
|
|
|
86
85
|
const next = clone(manifest)
|
|
87
86
|
const index = next.deliverables.findIndex((row) => row.format === format)
|
|
88
87
|
const row = { format, status, promised: index >= 0 ? next.deliverables[index].promised : false, updatedAt: stamp(detail.now) }
|
|
89
|
-
for (const key of ["path", "reason", "pages", "bytes"]) {
|
|
88
|
+
for (const key of ["path", "reason", "pages", "bytes", "verifiedBy", "renderedFrom"]) {
|
|
90
89
|
if (detail[key] !== undefined) row[key] = detail[key]
|
|
91
90
|
}
|
|
92
91
|
if (index >= 0) next.deliverables[index] = row
|
|
@@ -94,11 +93,13 @@ export function setDeliverable(manifest, format, status, detail = {}) {
|
|
|
94
93
|
return next
|
|
95
94
|
}
|
|
96
95
|
|
|
97
|
-
export function setGate(manifest, gate, status) {
|
|
96
|
+
export function setGate(manifest, gate, status, detail = {}) {
|
|
98
97
|
assertOneOf("unknown_gate", "gate", gate, GATE_NAMES)
|
|
99
98
|
assertOneOf("unknown_gate_status", "gate status", status, GATE_STATUSES)
|
|
99
|
+
if (gate === "fidelity" && status === "not_run" && (typeof detail.reason !== "string" || !detail.reason.trim())) throw new OutcomeError("gate_reason_required", "fidelity not_run requires a non-empty --reason")
|
|
100
100
|
const next = clone(manifest)
|
|
101
101
|
next.gates[gate] = status
|
|
102
|
+
next.gateHistory = [...(next.gateHistory ?? []), { gate, status, ...(detail.reason !== undefined ? { reason: detail.reason.trim() } : {}), at: stamp(detail.now) }]
|
|
102
103
|
return next
|
|
103
104
|
}
|
|
104
105
|
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
// placeholder.mjs - the fact pointer grammar of the fill step and its render helpers.
|
|
2
|
+
// Value pointers: {{F12:label}}, {{F12:label:value}}, {{F12:label:unit}}, {{F12:label:q}}.
|
|
3
|
+
// Quote pointers: {{Q7:label}}, {{Q7:label:lead=18}}, {{Q7:label:from="x":to="y"}} (anchors are
|
|
4
|
+
// JSON strings, so they may hold a colon or an escaped quote). Labels are compared exactly by the
|
|
5
|
+
// caller; this module never guesses an id. Pure functions; node: builtins only.
|
|
6
|
+
|
|
7
|
+
import { parseNumeral } from "./numerals.mjs"
|
|
8
|
+
|
|
9
|
+
export const ELLIPSIS = "\u2026"
|
|
10
|
+
|
|
11
|
+
const ID = /^([FQ])\d+$/
|
|
12
|
+
const LABEL = /^[^\s:{}"]+$/
|
|
13
|
+
const VALUE_MODES = new Set(["value", "unit", "q"])
|
|
14
|
+
const LEAD = /^lead=([1-9]\d*)$/
|
|
15
|
+
const LINEAGE = Object.freeze({ measured: "MEASURED", assumed: "ASSUMED", derived: "DERIVED", converted: "DERIVED" })
|
|
16
|
+
|
|
17
|
+
/** End index (exclusive) of a JSON string starting at `at`, or -1 when it is not closed on its line. */
|
|
18
|
+
function jsonStringEnd(text, at) {
|
|
19
|
+
let i = at + 1
|
|
20
|
+
while (i < text.length && text[i] !== '"' && text[i] !== "\n") i += text[i] === "\\" ? 2 : 1
|
|
21
|
+
return text[i] === '"' ? i + 1 : -1
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function readJsonString(text, at) {
|
|
25
|
+
if (text[at] !== '"') return null
|
|
26
|
+
const end = jsonStringEnd(text, at)
|
|
27
|
+
if (end === -1) return null
|
|
28
|
+
try {
|
|
29
|
+
return { value: JSON.parse(text.slice(at, end)), end }
|
|
30
|
+
} catch {
|
|
31
|
+
return null
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function parseAnchors(tail) {
|
|
36
|
+
if (!tail.startsWith("from=")) return null
|
|
37
|
+
const from = readJsonString(tail, 5)
|
|
38
|
+
if (from === null || !tail.startsWith(":to=", from.end)) return null
|
|
39
|
+
const to = readJsonString(tail, from.end + 4)
|
|
40
|
+
if (to === null || to.end !== tail.length || from.value === "" || to.value === "") return null
|
|
41
|
+
return { from: from.value, to: to.value }
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** @returns the parsed pointer, or { error } for a malformed one. */
|
|
45
|
+
export function parsePlaceholder(token) {
|
|
46
|
+
if (!token.startsWith("{{") || !token.endsWith("}}")) return { error: "a placeholder is {{ID:label...}}" }
|
|
47
|
+
const body = token.slice(2, -2)
|
|
48
|
+
const colon = body.indexOf(":")
|
|
49
|
+
const id = colon === -1 ? body : body.slice(0, colon)
|
|
50
|
+
const idMatch = ID.exec(id)
|
|
51
|
+
if (idMatch === null) return { error: `"${id}" is not an F<n> or Q<n> id` }
|
|
52
|
+
if (colon === -1) return { error: `${id} has no label; write {{${id}:label}}` }
|
|
53
|
+
const rest = body.slice(colon + 1)
|
|
54
|
+
const second = rest.indexOf(":")
|
|
55
|
+
const label = second === -1 ? rest : rest.slice(0, second)
|
|
56
|
+
if (!LABEL.test(label)) return { error: `"${label}" is not a label` }
|
|
57
|
+
const tail = second === -1 ? null : rest.slice(second + 1)
|
|
58
|
+
const base = { id, kind: idMatch[1], label }
|
|
59
|
+
if (idMatch[1] === "F") {
|
|
60
|
+
if (tail === null) return { ...base, mode: "full" }
|
|
61
|
+
return VALUE_MODES.has(tail) ? { ...base, mode: tail } : { error: `"${tail}" is not a value mode (value, unit, q)` }
|
|
62
|
+
}
|
|
63
|
+
if (tail === null) return { ...base, mode: "full", selector: "full" }
|
|
64
|
+
const lead = LEAD.exec(tail)
|
|
65
|
+
if (lead !== null) return { ...base, mode: "lead", lead: Number(lead[1]), selector: `lead=${lead[1]}` }
|
|
66
|
+
const anchors = parseAnchors(tail)
|
|
67
|
+
if (anchors === null) return { error: `"${tail}" is not a quote selector (lead=N or from="..":to="..")` }
|
|
68
|
+
return { ...base, mode: "span", ...anchors, selector: `from=${anchors.from};to=${anchors.to}` }
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** End index (exclusive) of the pointer opened at `at`, or -1 when it is not closed on its line. */
|
|
72
|
+
function placeholderEnd(text, at) {
|
|
73
|
+
let i = at + 2
|
|
74
|
+
while (i < text.length && text[i] !== "\n") {
|
|
75
|
+
if (text[i] === '"') {
|
|
76
|
+
const end = jsonStringEnd(text, i)
|
|
77
|
+
if (end === -1) return -1
|
|
78
|
+
i = end
|
|
79
|
+
} else if (text.startsWith("}}", i)) return i + 2
|
|
80
|
+
else i++
|
|
81
|
+
}
|
|
82
|
+
return -1
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** Every `{{...}}` pointer in source order: [{ token, start, end, parsed }]. */
|
|
86
|
+
export function findPlaceholders(text) {
|
|
87
|
+
const found = []
|
|
88
|
+
let at = text.indexOf("{{")
|
|
89
|
+
while (at !== -1) {
|
|
90
|
+
const end = placeholderEnd(text, at)
|
|
91
|
+
if (end === -1) {
|
|
92
|
+
found.push({ token: text.slice(at, text.indexOf("\n", at) === -1 ? text.length : text.indexOf("\n", at)), start: at, end: at + 2, parsed: { error: "the placeholder is not closed on its line" } })
|
|
93
|
+
at = text.indexOf("{{", at + 2)
|
|
94
|
+
continue
|
|
95
|
+
}
|
|
96
|
+
const token = text.slice(at, end)
|
|
97
|
+
found.push({ token, start: at, end, parsed: parsePlaceholder(token) })
|
|
98
|
+
at = text.indexOf("{{", end)
|
|
99
|
+
}
|
|
100
|
+
return found
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function occurrences(text, needle) {
|
|
104
|
+
let count = 0
|
|
105
|
+
for (let at = text.indexOf(needle); at !== -1; at = text.indexOf(needle, at + 1)) count++
|
|
106
|
+
return count
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function cut(quote, start, end) {
|
|
110
|
+
return { text: `${start > 0 ? ELLIPSIS : ""}${quote.slice(start, end)}${end < quote.length ? ELLIPSIS : ""}`, start, end }
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** The contiguous span from `from` through `to`; each anchor must occur exactly once, in order. */
|
|
114
|
+
export function subSpan(quote, from, to) {
|
|
115
|
+
if (occurrences(quote, from) !== 1 || occurrences(quote, to) !== 1) return null
|
|
116
|
+
const start = quote.indexOf(from)
|
|
117
|
+
const toAt = quote.indexOf(to)
|
|
118
|
+
if (toAt < start || toAt + to.length < start + from.length) return null
|
|
119
|
+
return cut(quote, start, toAt + to.length)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** The first `n` whitespace-delimited words, with an ellipsis when words were cut. */
|
|
123
|
+
export function lead(quote, n) {
|
|
124
|
+
const words = [...quote.matchAll(/\S+/g)]
|
|
125
|
+
if (words.length <= n) return { text: quote, start: 0, end: quote.length }
|
|
126
|
+
const last = words[n - 1]
|
|
127
|
+
return cut(quote, 0, last.index + last[0].length)
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** The value and unit parts of a row's raw text ("4.2 eok won" -> value "4.2 eok", unit "won"). */
|
|
131
|
+
export function valueParts(row) {
|
|
132
|
+
const unitText = parseNumeral(row.raw)?.unitRaw ?? row.unit
|
|
133
|
+
const valueText = unitText && row.raw.endsWith(unitText) ? row.raw.slice(0, -unitText.length).trimEnd() : (row.value ?? row.raw)
|
|
134
|
+
return { valueText, unitText: unitText ?? "" }
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/** The quote interval holding the row's qualifier and its raw quantity, or null if absent. */
|
|
138
|
+
export function qualifiedText(row) {
|
|
139
|
+
if (row.qualifier === null) return row.raw
|
|
140
|
+
for (let numeral = parseNumeral(row.quote); numeral !== null; numeral = parseNumeral(row.quote, numeral.span[1])) {
|
|
141
|
+
if (numeral.raw !== row.raw || numeral.qualifierRaw === null) continue
|
|
142
|
+
const [start, end] = numeral.span
|
|
143
|
+
const before = row.quote.lastIndexOf(numeral.qualifierRaw, start)
|
|
144
|
+
const between = before === -1 ? [] : row.quote.slice(before + numeral.qualifierRaw.length, start).split(/\s+/u).filter(Boolean)
|
|
145
|
+
if (before !== -1 && between.length <= 1) return row.quote.slice(before, end)
|
|
146
|
+
const after = row.quote.indexOf(numeral.qualifierRaw, end)
|
|
147
|
+
if (after !== -1) return row.quote.slice(start, after + numeral.qualifierRaw.length)
|
|
148
|
+
}
|
|
149
|
+
return null
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export function escapeHtml(text) {
|
|
153
|
+
return String(text).replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """)
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/** The elements the renderers below emit as fact spans; recognizers build their patterns from this. */
|
|
157
|
+
export const FACT_ELEMENT_TAGS = Object.freeze(["data", "q", "blockquote"])
|
|
158
|
+
|
|
159
|
+
export function renderValueHtml(row, text) {
|
|
160
|
+
const unit = row.unit === null ? "" : ` data-unit="${escapeHtml(row.unit)}"`
|
|
161
|
+
return `<data class="fact" value="${escapeHtml(row.value)}" data-fact="${row.fact_id}"${unit} data-lineage="${LINEAGE[row.kind]}">${escapeHtml(text)}</data>`
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export function renderQuote(row, selector, text, format) {
|
|
165
|
+
const marker = `[${row.source_id}]`
|
|
166
|
+
if (format === "md") return `"${text}" ${marker}`
|
|
167
|
+
return `<q data-fact="${row.fact_id}" data-quote-sel="${escapeHtml(selector)}">${escapeHtml(text)}</q> ${marker}`
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const RENDERED_MD_QUOTE = /^"([\s\S]*)" (\[S\d+\])$/u
|
|
171
|
+
|
|
172
|
+
/** The quote text and source marker of bytes renderQuote wrote for md, or null for any other layout. */
|
|
173
|
+
export function parseRenderedQuote(body) {
|
|
174
|
+
const match = RENDERED_MD_QUOTE.exec(body)
|
|
175
|
+
return match ? { text: match[1], marker: match[2] } : null
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/** The attributed block that replaces a twice-failed sentence; entity and valid_at stay outside the quote. */
|
|
179
|
+
export function renderBlockquote(row, key, format) {
|
|
180
|
+
const attribution = row.entity === null ? "" : `, on ${row.entity}${row.valid_at === null ? "" : ` (${row.valid_at})`}`
|
|
181
|
+
const footer = `[${row.source_id}]${attribution}`
|
|
182
|
+
if (format === "md") return `> "${row.quote}"\n> ${footer}`
|
|
183
|
+
return `<blockquote data-fact="${row.fact_id}" data-replaced="${escapeHtml(key)}">"${escapeHtml(row.quote)}" <footer>${escapeHtml(footer)}</footer></blockquote>`
|
|
184
|
+
}
|