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,271 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test"
|
|
2
|
+
|
|
3
|
+
import * as gatesStatic from "./gates-static.mjs"
|
|
4
|
+
import { parseHtml } from "./html-lite.mjs"
|
|
5
|
+
import * as mdLite from "./md-lite.mjs"
|
|
6
|
+
import { parseMarkdown, sectionKindsMd } from "./md-lite.mjs"
|
|
7
|
+
|
|
8
|
+
type Cell = { text: string; start: number; end: number }
|
|
9
|
+
type Block = { kind: string; level?: number; text: string; start: number; end: number; cells?: Cell[] }
|
|
10
|
+
|
|
11
|
+
const FIXTURE = [
|
|
12
|
+
"# 반도체 시장 보고서",
|
|
13
|
+
"",
|
|
14
|
+
"매출은 {{F1}}로 집계됐다. Revenue grew",
|
|
15
|
+
"across two quarters.",
|
|
16
|
+
"",
|
|
17
|
+
"## Findings",
|
|
18
|
+
"",
|
|
19
|
+
"- 첫 번째 항목 120억 원",
|
|
20
|
+
"* Second item 3.2 m",
|
|
21
|
+
"1. Ordered item 45%",
|
|
22
|
+
"",
|
|
23
|
+
"| 지표 | Value |",
|
|
24
|
+
"| --- | ---: |",
|
|
25
|
+
"| 매출 | 100억 원 |",
|
|
26
|
+
"",
|
|
27
|
+
"> 원문 인용 그대로다.",
|
|
28
|
+
"",
|
|
29
|
+
"```js",
|
|
30
|
+
"const total = 9999",
|
|
31
|
+
"```",
|
|
32
|
+
"",
|
|
33
|
+
"## Sources",
|
|
34
|
+
"",
|
|
35
|
+
"S1 example.org",
|
|
36
|
+
"",
|
|
37
|
+
"## 출처",
|
|
38
|
+
"",
|
|
39
|
+
"## Closing",
|
|
40
|
+
"",
|
|
41
|
+
"### Method",
|
|
42
|
+
"",
|
|
43
|
+
"## 마무리",
|
|
44
|
+
"",
|
|
45
|
+
].join("\n")
|
|
46
|
+
|
|
47
|
+
function blocksOf(text: string): Block[] {
|
|
48
|
+
return (parseMarkdown(text) as { blocks: Block[] }).blocks
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function expectSlices(text: string, blocks: Block[]): void {
|
|
52
|
+
for (const block of blocks) {
|
|
53
|
+
expect(text.slice(block.start, block.end)).toBe(block.text)
|
|
54
|
+
for (const cell of block.cells ?? []) expect(text.slice(cell.start, cell.end)).toBe(cell.text)
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
describe("parseMarkdown", () => {
|
|
59
|
+
test("#given a Korean+English fixture #when parsed #then block kinds come in document order", () => {
|
|
60
|
+
// given
|
|
61
|
+
const text = FIXTURE
|
|
62
|
+
// when
|
|
63
|
+
const blocks = blocksOf(text)
|
|
64
|
+
// then
|
|
65
|
+
expect(blocks.map((b) => b.kind)).toEqual([
|
|
66
|
+
"heading",
|
|
67
|
+
"paragraph",
|
|
68
|
+
"heading",
|
|
69
|
+
"list_item",
|
|
70
|
+
"list_item",
|
|
71
|
+
"list_item",
|
|
72
|
+
"table_row",
|
|
73
|
+
"table_row",
|
|
74
|
+
"blockquote",
|
|
75
|
+
"code",
|
|
76
|
+
"heading",
|
|
77
|
+
"paragraph",
|
|
78
|
+
"heading",
|
|
79
|
+
"heading",
|
|
80
|
+
"heading",
|
|
81
|
+
"heading",
|
|
82
|
+
])
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
test("#given the fixture #when parsed #then every block and cell slices exactly from start to end", () => {
|
|
86
|
+
// given
|
|
87
|
+
const text = FIXTURE
|
|
88
|
+
// when
|
|
89
|
+
const blocks = blocksOf(text)
|
|
90
|
+
// then
|
|
91
|
+
expectSlices(text, blocks)
|
|
92
|
+
expect(blocks[0]).toMatchObject({ kind: "heading", level: 1, text: "반도체 시장 보고서" })
|
|
93
|
+
expect(blocks[1]?.text).toBe("매출은 {{F1}}로 집계됐다. Revenue grew\nacross two quarters.")
|
|
94
|
+
expect(blocks.filter((b) => b.kind === "list_item").map((b) => b.text)).toEqual([
|
|
95
|
+
"첫 번째 항목 120억 원",
|
|
96
|
+
"Second item 3.2 m",
|
|
97
|
+
"Ordered item 45%",
|
|
98
|
+
])
|
|
99
|
+
expect(blocks.find((b) => b.kind === "blockquote")?.text).toBe("원문 인용 그대로다.")
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
test("#given a table with a separator row #when parsed #then rows carry trimmed cells and the separator is skipped", () => {
|
|
103
|
+
// given
|
|
104
|
+
const text = FIXTURE
|
|
105
|
+
// when
|
|
106
|
+
const rows = blocksOf(text).filter((b) => b.kind === "table_row")
|
|
107
|
+
// then
|
|
108
|
+
expect(rows.map((r) => (r.cells ?? []).map((c) => c.text))).toEqual([
|
|
109
|
+
["지표", "Value"],
|
|
110
|
+
["매출", "100억 원"],
|
|
111
|
+
])
|
|
112
|
+
expectSlices(text, rows)
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
test("#given a fenced code block with numerals #when parsed #then the numerals sit only in a block of kind code", () => {
|
|
116
|
+
// given
|
|
117
|
+
const text = FIXTURE
|
|
118
|
+
// when
|
|
119
|
+
const blocks = blocksOf(text)
|
|
120
|
+
// then
|
|
121
|
+
const holders = blocks.filter((b) => b.text.includes("9999"))
|
|
122
|
+
expect(holders.map((b) => b.kind)).toEqual(["code"])
|
|
123
|
+
expect(holders[0]?.text).toBe("const total = 9999")
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
test("#given an unterminated code fence #when parsed #then it is code to EOF and an unterminated_fence problem names its line", () => {
|
|
127
|
+
// given
|
|
128
|
+
const text = "Intro 12.\n\n```\nlet a = 1\n## not a heading\n"
|
|
129
|
+
// when
|
|
130
|
+
const { blocks, problems } = parseMarkdown(text) as { blocks: Block[]; problems: { code: string; line: number }[] }
|
|
131
|
+
// then
|
|
132
|
+
expect(blocks.map((b) => b.kind)).toEqual(["paragraph", "code"])
|
|
133
|
+
expect(blocks[1]?.text).toBe("let a = 1\n## not a heading\n")
|
|
134
|
+
expectSlices(text, blocks)
|
|
135
|
+
expect(problems).toEqual([expect.objectContaining({ code: "unterminated_fence", line: 3 })])
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
test("#given closed fences only #when parsed #then problems is empty", () => {
|
|
139
|
+
// given
|
|
140
|
+
const text = FIXTURE
|
|
141
|
+
// when
|
|
142
|
+
const { problems } = parseMarkdown(text) as { problems: unknown[] }
|
|
143
|
+
// then
|
|
144
|
+
expect(problems).toEqual([])
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
const NUMBER_LINES: [string, string, string[][]][] = [
|
|
148
|
+
["a year-led continuation line", "Revenue rose 12% in\n2024. Costs fell 3%.", [["paragraph", "Revenue rose 12% in\n2024. Costs fell 3%."]]],
|
|
149
|
+
["a bare number at line start", "Sales grew in\n2024 revenue rose 4%.", [["paragraph", "Sales grew in\n2024 revenue rose 4%."]]],
|
|
150
|
+
["a 1. marker after a paragraph", "Intro line\n1. x", [["paragraph", "Intro line"], ["list_item", "x"]]],
|
|
151
|
+
["a 2) marker after a paragraph", "Intro line\n2) y", [["paragraph", "Intro line\n2) y"]]],
|
|
152
|
+
["a 2) marker after a blank line", "Intro line\n\n2) y", [["paragraph", "Intro line"], ["list_item", "y"]]],
|
|
153
|
+
["a later ordered item in a list", "1. x\n2. y", [["list_item", "x"], ["list_item", "y"]]],
|
|
154
|
+
]
|
|
155
|
+
test.each(NUMBER_LINES)("#given %s #when parsed #then no number drops out of the block text", (_label, text, expected) => {
|
|
156
|
+
// given / when
|
|
157
|
+
const blocks = blocksOf(text)
|
|
158
|
+
// then
|
|
159
|
+
expect(blocks.map((b) => [b.kind, b.text])).toEqual(expected)
|
|
160
|
+
expectSlices(text, blocks)
|
|
161
|
+
})
|
|
162
|
+
|
|
163
|
+
test("#given a line that opens with an inline triple-backtick span #when parsed #then it stays a paragraph and later claims are not code", () => {
|
|
164
|
+
// given
|
|
165
|
+
const text = "```inline``` code at start\n\nLater 42% claim."
|
|
166
|
+
// when
|
|
167
|
+
const { blocks, problems } = parseMarkdown(text) as { blocks: Block[]; problems: unknown[] }
|
|
168
|
+
// then
|
|
169
|
+
expect(blocks.map((b) => [b.kind, b.text])).toEqual([
|
|
170
|
+
["paragraph", "```inline``` code at start"],
|
|
171
|
+
["paragraph", "Later 42% claim."],
|
|
172
|
+
])
|
|
173
|
+
expect(problems).toEqual([])
|
|
174
|
+
})
|
|
175
|
+
|
|
176
|
+
test("#given a tilde fence with an info string containing backticks #when parsed #then it is still a fence", () => {
|
|
177
|
+
// given
|
|
178
|
+
const text = "~~~ js `x`\nconst n = 5\n~~~\nAfter 6."
|
|
179
|
+
// when
|
|
180
|
+
const blocks = blocksOf(text)
|
|
181
|
+
// then
|
|
182
|
+
expect(blocks.map((b) => [b.kind, b.text])).toEqual([
|
|
183
|
+
["code", "const n = 5"],
|
|
184
|
+
["paragraph", "After 6."],
|
|
185
|
+
])
|
|
186
|
+
})
|
|
187
|
+
|
|
188
|
+
test("#given a table cell ending in an escaped backslash #when parsed #then the following pipe still splits cells", () => {
|
|
189
|
+
// given
|
|
190
|
+
const text = "| a\\\\| b \\| c |\n| --- | --- |\n| 1 | 2 |"
|
|
191
|
+
// when
|
|
192
|
+
const rows = blocksOf(text).filter((b) => b.kind === "table_row")
|
|
193
|
+
// then
|
|
194
|
+
expect((rows[0]?.cells ?? []).map((c) => c.text)).toEqual(["a\\\\", "b \\| c"])
|
|
195
|
+
expectSlices(text, rows)
|
|
196
|
+
})
|
|
197
|
+
})
|
|
198
|
+
|
|
199
|
+
describe("sectionKindsMd", () => {
|
|
200
|
+
test("#given h2 headings in English and Korean #when classified #then sources and closing kinds match gates-static", () => {
|
|
201
|
+
// given
|
|
202
|
+
const blocks = blocksOf(FIXTURE)
|
|
203
|
+
// when
|
|
204
|
+
const kinds = sectionKindsMd(blocks) as (string | null)[]
|
|
205
|
+
// then
|
|
206
|
+
const byHeading: Record<string, string | null | undefined> = {}
|
|
207
|
+
blocks.forEach((b, i) => {
|
|
208
|
+
if (b.kind === "heading") byHeading[b.text] = kinds[i]
|
|
209
|
+
})
|
|
210
|
+
expect(byHeading.Findings).toBeNull()
|
|
211
|
+
expect(byHeading.Sources).toBe("sources")
|
|
212
|
+
expect(byHeading["출처"]).toBe("sources")
|
|
213
|
+
expect(byHeading.Closing).toBe("closing")
|
|
214
|
+
expect(byHeading["마무리"]).toBe("closing")
|
|
215
|
+
expect(kinds).toHaveLength(blocks.length)
|
|
216
|
+
})
|
|
217
|
+
|
|
218
|
+
test("#given md-lite #when its section classifier is read #then it is the gates-static export itself", () => {
|
|
219
|
+
// given / when / then
|
|
220
|
+
expect(typeof gatesStatic.sectionKind).toBe("function")
|
|
221
|
+
expect(mdLite.sectionKind).toBe(gatesStatic.sectionKind)
|
|
222
|
+
expect(typeof gatesStatic.closingTest).toBe("function")
|
|
223
|
+
expect(mdLite.closingTest).toBe(gatesStatic.closingTest)
|
|
224
|
+
})
|
|
225
|
+
|
|
226
|
+
test("#given one heading set as Markdown h2s and HTML h2s #when classified #then both editions give the same kinds", () => {
|
|
227
|
+
// given
|
|
228
|
+
const headings = ["Findings", "Sources", "\uCD9C\uCC98", "Closing", "\uB9C8\uBB34\uB9AC", "Methodology", "Contents", "Appendix A", "Glossary", "Closing the gap: GPU prices"]
|
|
229
|
+
const md = headings.map((h) => `## ${h}\n\nBody.`).join("\n\n")
|
|
230
|
+
const html = `<html><body>${headings.map((h) => `<h2>${h}</h2><p>Body.</p>`).join("")}</body></html>`
|
|
231
|
+
// when
|
|
232
|
+
const blocks = blocksOf(md)
|
|
233
|
+
const kinds = sectionKindsMd(blocks) as (string | null)[]
|
|
234
|
+
const mdKinds = blocks.flatMap((b, i) => (b.kind === "heading" ? [kinds[i]] : []))
|
|
235
|
+
const htmlKinds = gatesStatic.buildContext(parseHtml(html), {}).sections.map((s: { kind: string | null }) => s.kind)
|
|
236
|
+
// then
|
|
237
|
+
expect(mdKinds).toEqual(htmlKinds)
|
|
238
|
+
expect(mdKinds).toEqual([null, "sources", "sources", "closing", "closing", "closing", "toc", "appendix", "glossary", null])
|
|
239
|
+
})
|
|
240
|
+
|
|
241
|
+
test("#given a findings heading that only starts with Closing #when classified #then it is not a closing section", () => {
|
|
242
|
+
// given
|
|
243
|
+
const blocks = blocksOf("## Closing the gap: GPU prices\n\nPrices fell 8%.\n\n### Closing remarks on supply\n\nSupply rose 2%.")
|
|
244
|
+
// when
|
|
245
|
+
const kinds = sectionKindsMd(blocks) as (string | null)[]
|
|
246
|
+
// then
|
|
247
|
+
expect(kinds).toEqual([null, null, null, null])
|
|
248
|
+
})
|
|
249
|
+
|
|
250
|
+
test("#given a caller closing pattern given as a string #when classified #then it is applied as in gates-static", () => {
|
|
251
|
+
// given
|
|
252
|
+
const blocks = blocksOf("## Wrap-up\n\nDone.")
|
|
253
|
+
// when
|
|
254
|
+
const kinds = sectionKindsMd(blocks, { closingPattern: "wrap-up" }) as (string | null)[]
|
|
255
|
+
// then
|
|
256
|
+
expect(kinds).toEqual(["closing", "closing"])
|
|
257
|
+
})
|
|
258
|
+
|
|
259
|
+
test("#given body blocks under a section #when classified #then they inherit the enclosing h2 kind", () => {
|
|
260
|
+
// given
|
|
261
|
+
const blocks = blocksOf(FIXTURE)
|
|
262
|
+
// when
|
|
263
|
+
const kinds = sectionKindsMd(blocks) as (string | null)[]
|
|
264
|
+
// then
|
|
265
|
+
const codeIndex = blocks.findIndex((b) => b.kind === "code")
|
|
266
|
+
const sourceLine = blocks.findIndex((b) => b.text === "S1 example.org")
|
|
267
|
+
expect(kinds[codeIndex]).toBeNull()
|
|
268
|
+
expect(kinds[sourceLine]).toBe("sources")
|
|
269
|
+
expect(kinds[0]).toBeNull()
|
|
270
|
+
})
|
|
271
|
+
})
|
|
@@ -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
|
+
}
|