lazycodex-ai 5.1.44 → 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 +865 -413
- package/dist/cli/service-auth/keystore.d.ts +4 -2
- package/dist/cli-node/index.js +865 -413
- package/docs/reference/web-terminal-visual-qa.md +8 -0
- package/package.json +1 -1
- package/packages/lsp-daemon/dist/cli.js +7 -7
- package/packages/lsp-daemon/dist/client.js +7 -7
- package/packages/lsp-daemon/dist/index.js +7 -7
- package/packages/lsp-daemon/package.json +3 -3
- package/packages/lsp-tools-mcp/dist/cli.js +7 -7
- package/packages/lsp-tools-mcp/dist/mcp.js +7 -7
- package/packages/lsp-tools-mcp/dist/tools.js +4 -4
- package/packages/lsp-tools-mcp/package.json +3 -3
- package/packages/omo-codex/plugin/.codex-plugin/plugin.json +1 -1
- package/packages/omo-codex/plugin/components/bootstrap/dist/cli.js +77 -26
- 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/bootstrap/src/hook.ts +1 -1
- package/packages/omo-codex/plugin/components/comment-checker/biome.json +1 -21
- package/packages/omo-codex/plugin/components/comment-checker/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/comment-checker/package.json +4 -4
- 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/biome.json +1 -21
- 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 +4 -4
- package/packages/omo-codex/plugin/components/lsp/biome.json +1 -21
- package/packages/omo-codex/plugin/components/lsp/dist/.omo-runtime-manifest.json +3 -3
- package/packages/omo-codex/plugin/components/lsp/dist/cli.js +4 -4
- package/packages/omo-codex/plugin/components/lsp/hooks/hooks.json +2 -2
- package/packages/omo-codex/plugin/components/lsp/package.json +4 -4
- package/packages/omo-codex/plugin/components/lsp/scripts/build-runtime.mjs +0 -1
- package/packages/omo-codex/plugin/components/lsp/test/codex-hook-not-installed.test.ts +1 -1
- package/packages/omo-codex/plugin/components/rules/biome.json +1 -21
- package/packages/omo-codex/plugin/components/rules/hooks/hooks.json +4 -4
- package/packages/omo-codex/plugin/components/rules/package.json +4 -4
- 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/biome.json +1 -21
- package/packages/omo-codex/plugin/components/telemetry/dist/cli.js +90 -1
- package/packages/omo-codex/plugin/components/telemetry/dist/posthog.js +91 -1
- package/packages/omo-codex/plugin/components/telemetry/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/telemetry/package.json +4 -4
- package/packages/omo-codex/plugin/components/ultrawork/agents/lazycodex-gate-reviewer.toml +2 -2
- package/packages/omo-codex/plugin/components/ultrawork/biome.json +1 -21
- package/packages/omo-codex/plugin/components/ultrawork/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/ultrawork/package.json +4 -4
- package/packages/omo-codex/plugin/components/ulw-execute-continuation/biome.json +1 -21
- package/packages/omo-codex/plugin/components/ulw-execute-continuation/directive.md +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 +4 -4
- package/packages/omo-codex/plugin/components/ulw-loop/biome.json +1 -21
- package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint.js +1 -1
- package/packages/omo-codex/plugin/components/ulw-loop/dist/cli.js +7 -7
- package/packages/omo-codex/plugin/components/ulw-loop/dist/codex-goal-instruction.js +5 -5
- package/packages/omo-codex/plugin/components/ulw-loop/dist/stop-resume-hook.js +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 +4 -4
- package/packages/omo-codex/plugin/components/ulw-loop/src/checkpoint.ts +1 -1
- package/packages/omo-codex/plugin/components/ulw-loop/src/codex-goal-instruction.ts +5 -5
- package/packages/omo-codex/plugin/components/ulw-loop/src/stop-resume-hook.ts +1 -1
- package/packages/omo-codex/plugin/components/ulw-loop/test/fixtures/commit-mutations.mjs +2 -0
- 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 +14 -9
- package/packages/omo-codex/plugin/skills/ast-grep/AGENTS.md +1 -1
- package/packages/omo-codex/plugin/skills/browser/SKILL.md +21 -22
- package/packages/omo-codex/plugin/skills/browser/references/commands.md +2 -9
- package/packages/omo-codex/plugin/skills/browser/references/install.md +1 -2
- package/packages/omo-codex/plugin/skills/browser/runtime/omowright/manifest.json +1 -1
- package/packages/omo-codex/plugin/skills/browser/scripts/browser-doctor.mjs +2 -2
- package/packages/omo-codex/plugin/skills/browser/scripts/browser-engine-guard.mjs +63 -487
- package/packages/omo-codex/plugin/skills/browser/scripts/browser-install.mjs +1 -1
- package/packages/omo-codex/plugin/skills/browser/scripts/omowright.mjs +1 -10
- package/packages/omo-codex/plugin/skills/frontend/ATTRIBUTION.md +2 -2
- package/packages/omo-codex/plugin/skills/frontend/SKILL.md +79 -118
- package/packages/omo-codex/plugin/skills/frontend/references/design/README.md +75 -224
- package/packages/omo-codex/plugin/skills/frontend/references/design/stylegallery.md +32 -27
- package/packages/omo-codex/plugin/skills/frontend/references/perfection/README.md +2 -2
- package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/check-accent-stripes.ts +8 -24
- package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/check-draft-guard.ts +40 -0
- package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/collect-files.ts +53 -0
- package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/draft-guard/findings.ts +16 -0
- package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/draft-guard/lint.ts +38 -0
- package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/draft-guard/rules.ts +69 -0
- package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/draft-guard/text-nodes.ts +121 -0
- package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/fixtures/draft-guard/negative/attr-lookalikes.tsx +10 -0
- package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/fixtures/draft-guard/negative/bound-values.tsx +12 -0
- package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/fixtures/draft-guard/negative/const-generics.tsx +3 -0
- package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/fixtures/draft-guard/negative/generics.tsx +7 -0
- package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/fixtures/draft-guard/negative/measured-stats.html +6 -0
- package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/fixtures/draft-guard/negative/plain-copy.html +9 -0
- package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/fixtures/draft-guard/negative/state-copy.html +11 -0
- package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/fixtures/draft-guard/positive/attribute-edges.html +7 -0
- package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/fixtures/draft-guard/positive/components.tsx +16 -0
- package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/fixtures/draft-guard/positive/decorative-badge.vue +7 -0
- package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/fixtures/draft-guard/positive/emoji-icons.tsx +9 -0
- package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/fixtures/draft-guard/positive/jsx-edges.tsx +1 -0
- package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/fixtures/draft-guard/positive/promo-copy.html +6 -0
- package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/fixtures/draft-guard/positive/promo-copy.tsx +8 -0
- package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/fixtures/draft-guard/positive/vanity-stats.html +5 -0
- package/packages/omo-codex/plugin/skills/init-deep/SKILL.md +1 -1
- package/packages/omo-codex/plugin/skills/programming/SKILL.md +10 -7
- package/packages/omo-codex/plugin/skills/programming/references/typescript/README.md +10 -7
- package/packages/omo-codex/plugin/skills/programming/references/typescript/bootstrap.md +9 -23
- package/packages/omo-codex/plugin/skills/programming/references/typescript/oxc-lint.md +130 -0
- package/packages/omo-codex/plugin/skills/programming/references/typescript/tsconfig-strict.md +6 -62
- package/packages/omo-codex/plugin/skills/programming/scripts/typescript/check-no-excuse-rules.ts +12 -12
- package/packages/omo-codex/plugin/skills/programming/scripts/typescript/new-project.ts +20 -81
- package/packages/omo-codex/plugin/skills/programming/scripts/typescript/oxc/.oxfmtrc.json +9 -0
- package/packages/omo-codex/plugin/skills/programming/scripts/typescript/oxc/.oxlintrc.json +46 -0
- package/packages/omo-codex/plugin/skills/programming/scripts/typescript/oxc/lint/oxlint-plugin/LICENSE +21 -0
- package/packages/omo-codex/plugin/skills/programming/scripts/typescript/oxc/lint/oxlint-plugin/index.js +10 -0
- package/packages/omo-codex/plugin/skills/programming/scripts/typescript/oxc/lint/oxlint-plugin/no-em-dash-copy.js +28 -0
- package/packages/omo-codex/plugin/skills/programming/scripts/typescript/oxc/lint/oxlint-plugin/require-suppression-reason.js +62 -0
- package/packages/omo-codex/plugin/skills/programming/scripts/typescript/oxc/tsconfig.json +29 -0
- package/packages/omo-codex/plugin/skills/refactor/SKILL.md +14 -14
- package/packages/omo-codex/plugin/skills/remove-ai-slops/SKILL.md +1 -1
- package/packages/omo-codex/plugin/skills/review-work/SKILL.md +2 -2
- package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/AGENTS.md +1 -1
- package/packages/omo-codex/plugin/skills/ulw-execute/SKILL.md +2 -2
- package/packages/omo-codex/plugin/skills/ulw-loop/SKILL.md +1 -1
- package/packages/omo-codex/plugin/skills/ulw-research/AGENTS.md +21 -3
- package/packages/omo-codex/plugin/skills/ulw-research/SKILL.md +37 -12
- package/packages/omo-codex/plugin/skills/ulw-research/references/deliverable-phase.md +77 -16
- package/packages/omo-codex/plugin/skills/ulw-research/references/fact-ledger-grammar.md +134 -0
- 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 +68 -3
- 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/claims-index.mjs +125 -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 +89 -3
- 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 +250 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-ledger-schema.mjs +221 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-ledger.mjs +123 -0
- 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 +150 -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 +64 -0
- 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 +29 -9
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/gates-structure.mjs +1 -1
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/gates-text.mjs +1 -27
- 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/lexicon.mjs +47 -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/numerals.mjs +226 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/outcome.mjs +8 -8
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/particles-ko.mjs +108 -0
- 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/rational.mjs +139 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/report-tools-commands.mjs +21 -10
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/report-tools.mjs +15 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/sentence-split.mjs +77 -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/text-normalize.mjs +82 -0
- package/packages/omo-codex/plugin/skills/ulw-research/scripts/text-similarity.mjs +28 -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 +105 -0
- package/packages/omo-codex/plugin/skills/visual-qa/SKILL.md +71 -64
- package/packages/omo-codex/plugin/test/component-bundled-cli.test.mjs +4 -1
- 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 +12 -8
- package/packages/omo-codex/scripts/install-dist/install-local.mjs +169 -30
- package/packages/shared-skills/skills/ast-grep/AGENTS.md +1 -1
- package/packages/shared-skills/skills/browser/SKILL.md +21 -22
- package/packages/shared-skills/skills/browser/references/commands.md +2 -9
- package/packages/shared-skills/skills/browser/references/install.md +1 -2
- package/packages/shared-skills/skills/browser/runtime/omowright/manifest.json +1 -1
- package/packages/shared-skills/skills/browser/scripts/browser-doctor.mjs +2 -2
- package/packages/shared-skills/skills/browser/scripts/browser-engine-guard.mjs +63 -487
- package/packages/shared-skills/skills/browser/scripts/browser-install.mjs +1 -1
- package/packages/shared-skills/skills/browser/scripts/omowright.mjs +1 -10
- package/packages/shared-skills/skills/frontend/ATTRIBUTION.md +2 -2
- package/packages/shared-skills/skills/frontend/SKILL.md +79 -118
- package/packages/shared-skills/skills/frontend/references/design/README.md +75 -224
- package/packages/shared-skills/skills/frontend/references/design/stylegallery.md +32 -27
- package/packages/shared-skills/skills/frontend/references/perfection/README.md +2 -2
- package/packages/shared-skills/skills/frontend/scripts/perfection/check-accent-stripes.ts +8 -24
- package/packages/shared-skills/skills/frontend/scripts/perfection/check-draft-guard.test.ts +101 -0
- package/packages/shared-skills/skills/frontend/scripts/perfection/check-draft-guard.ts +40 -0
- package/packages/shared-skills/skills/frontend/scripts/perfection/collect-files.test.ts +50 -0
- package/packages/shared-skills/skills/frontend/scripts/perfection/collect-files.ts +53 -0
- package/packages/shared-skills/skills/frontend/scripts/perfection/draft-guard/findings.ts +16 -0
- package/packages/shared-skills/skills/frontend/scripts/perfection/draft-guard/lint.ts +38 -0
- package/packages/shared-skills/skills/frontend/scripts/perfection/draft-guard/rules.ts +69 -0
- package/packages/shared-skills/skills/frontend/scripts/perfection/draft-guard/text-nodes.ts +121 -0
- package/packages/shared-skills/skills/frontend/scripts/perfection/fixtures/draft-guard/negative/attr-lookalikes.tsx +10 -0
- package/packages/shared-skills/skills/frontend/scripts/perfection/fixtures/draft-guard/negative/bound-values.tsx +12 -0
- package/packages/shared-skills/skills/frontend/scripts/perfection/fixtures/draft-guard/negative/const-generics.tsx +3 -0
- package/packages/shared-skills/skills/frontend/scripts/perfection/fixtures/draft-guard/negative/generics.tsx +7 -0
- package/packages/shared-skills/skills/frontend/scripts/perfection/fixtures/draft-guard/negative/measured-stats.html +6 -0
- package/packages/shared-skills/skills/frontend/scripts/perfection/fixtures/draft-guard/negative/plain-copy.html +9 -0
- package/packages/shared-skills/skills/frontend/scripts/perfection/fixtures/draft-guard/negative/state-copy.html +11 -0
- package/packages/shared-skills/skills/frontend/scripts/perfection/fixtures/draft-guard/positive/attribute-edges.html +7 -0
- package/packages/shared-skills/skills/frontend/scripts/perfection/fixtures/draft-guard/positive/components.tsx +16 -0
- package/packages/shared-skills/skills/frontend/scripts/perfection/fixtures/draft-guard/positive/decorative-badge.vue +7 -0
- package/packages/shared-skills/skills/frontend/scripts/perfection/fixtures/draft-guard/positive/emoji-icons.tsx +9 -0
- package/packages/shared-skills/skills/frontend/scripts/perfection/fixtures/draft-guard/positive/jsx-edges.tsx +1 -0
- package/packages/shared-skills/skills/frontend/scripts/perfection/fixtures/draft-guard/positive/promo-copy.html +6 -0
- package/packages/shared-skills/skills/frontend/scripts/perfection/fixtures/draft-guard/positive/promo-copy.tsx +8 -0
- package/packages/shared-skills/skills/frontend/scripts/perfection/fixtures/draft-guard/positive/vanity-stats.html +5 -0
- package/packages/shared-skills/skills/programming/SKILL.md +10 -7
- package/packages/shared-skills/skills/programming/references/typescript/README.md +10 -7
- package/packages/shared-skills/skills/programming/references/typescript/bootstrap.md +9 -23
- package/packages/shared-skills/skills/programming/references/typescript/oxc-lint.md +130 -0
- package/packages/shared-skills/skills/programming/references/typescript/tsconfig-strict.md +6 -62
- package/packages/shared-skills/skills/programming/scripts/typescript/check-no-excuse-rules.ts +12 -12
- package/packages/shared-skills/skills/programming/scripts/typescript/new-project.test.ts +162 -0
- package/packages/shared-skills/skills/programming/scripts/typescript/new-project.ts +20 -81
- package/packages/shared-skills/skills/programming/scripts/typescript/oxc/.oxfmtrc.json +9 -0
- package/packages/shared-skills/skills/programming/scripts/typescript/oxc/.oxlintrc.json +46 -0
- package/packages/shared-skills/skills/programming/scripts/typescript/oxc/lint/oxlint-plugin/LICENSE +21 -0
- package/packages/shared-skills/skills/programming/scripts/typescript/oxc/lint/oxlint-plugin/index.js +10 -0
- package/packages/shared-skills/skills/programming/scripts/typescript/oxc/lint/oxlint-plugin/no-em-dash-copy.js +28 -0
- package/packages/shared-skills/skills/programming/scripts/typescript/oxc/lint/oxlint-plugin/require-suppression-reason.js +62 -0
- package/packages/shared-skills/skills/programming/scripts/typescript/oxc/tsconfig.json +29 -0
- package/packages/shared-skills/skills/refactor/SKILL.md +13 -13
- package/packages/shared-skills/skills/ultimate-browsing/engine/AGENTS.md +1 -1
- package/packages/shared-skills/skills/ulw-execute/SKILL.md +1 -1
- package/packages/shared-skills/skills/ulw-research/AGENTS.md +21 -3
- package/packages/shared-skills/skills/ulw-research/SKILL.md +37 -12
- package/packages/shared-skills/skills/ulw-research/references/deliverable-phase.md +77 -16
- package/packages/shared-skills/skills/ulw-research/references/fact-ledger-grammar.md +134 -0
- 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 +68 -3
- 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/claims-index.mjs +125 -0
- package/packages/shared-skills/skills/ulw-research/scripts/claims-index.test.ts +149 -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 +89 -3
- package/packages/shared-skills/skills/ulw-research/scripts/contracts.test.ts +100 -12
- 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 +197 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fact-ledger-kinds.mjs +250 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fact-ledger-kinds.test.ts +156 -0
- 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 +221 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fact-ledger-schema.test.ts +43 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fact-ledger.mjs +123 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fact-ledger.test.ts +254 -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 +150 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fidelity-brief.test.ts +179 -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 +64 -0
- package/packages/shared-skills/skills/ulw-research/scripts/fidelity-state.test.ts +46 -0
- 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-context.test.ts +64 -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 +29 -9
- package/packages/shared-skills/skills/ulw-research/scripts/gates-structure.mjs +1 -1
- package/packages/shared-skills/skills/ulw-research/scripts/gates-text.mjs +1 -27
- 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/lexicon.mjs +47 -0
- package/packages/shared-skills/skills/ulw-research/scripts/lexicon.test.ts +57 -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/numerals.mjs +226 -0
- package/packages/shared-skills/skills/ulw-research/scripts/numerals.test.ts +120 -0
- package/packages/shared-skills/skills/ulw-research/scripts/outcome.mjs +8 -8
- package/packages/shared-skills/skills/ulw-research/scripts/outcome.test.ts +29 -0
- package/packages/shared-skills/skills/ulw-research/scripts/particles-ko.mjs +108 -0
- package/packages/shared-skills/skills/ulw-research/scripts/particles-ko.test.ts +86 -0
- 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/rational.mjs +139 -0
- package/packages/shared-skills/skills/ulw-research/scripts/rational.test.ts +46 -0
- package/packages/shared-skills/skills/ulw-research/scripts/report-tools-commands.mjs +21 -10
- 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/sentence-split.mjs +77 -0
- package/packages/shared-skills/skills/ulw-research/scripts/sentence-split.test.ts +109 -0
- 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/claim-graph.md +13 -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/tests/fixtures/fidelity-brief-locked.md +17 -0
- package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity-brief-pending.md +17 -0
- package/packages/shared-skills/skills/ulw-research/scripts/text-normalize.mjs +82 -0
- package/packages/shared-skills/skills/ulw-research/scripts/text-normalize.test.ts +73 -0
- package/packages/shared-skills/skills/ulw-research/scripts/text-similarity.mjs +28 -0
- package/packages/shared-skills/skills/ulw-research/scripts/text-similarity.test.ts +38 -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 +105 -0
- package/packages/shared-skills/skills/ulw-research/scripts/units.test.ts +83 -0
- package/packages/shared-skills/skills/visual-qa/SKILL.md +70 -63
- package/packages/shared-skills/skills/visual-qa/scripts/skill-doc-fields.test.ts +34 -0
- package/script/qa/web-terminal-visual-qa.mjs +6 -2
- package/script/qa/xterm-live-terminal.mjs +26 -3
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
// fidelity-brief.mjs - parse the `## Fidelity` block of brief.md and decide whether it is locked.
|
|
2
|
+
//
|
|
3
|
+
// Grammar (fable-interview.md section 3, "Parser contract"): the block starts at a line
|
|
4
|
+
// `## Fidelity` and ends at the next `## ` heading. It holds the seven field lines below, in any
|
|
5
|
+
// order, each `<field>: <value> (answered_by: user|default|request|pending)`, plus an optional
|
|
6
|
+
// `asked: <field>, <field>` history line. Values carry no parentheses, and `answered_by: pending`
|
|
7
|
+
// is legal only with the value `pending`. Trailing whitespace on a line is ignored; a second
|
|
8
|
+
// `## Fidelity` block is a parse error. This module parses; it never interprets a value, which
|
|
9
|
+
// is each consumer's job. Plain Node ESM, node: builtins only.
|
|
10
|
+
|
|
11
|
+
import { readFileSync } from "node:fs"
|
|
12
|
+
|
|
13
|
+
export const FIDELITY_FIELDS = Object.freeze([
|
|
14
|
+
"as of",
|
|
15
|
+
"meaning check",
|
|
16
|
+
"source floor",
|
|
17
|
+
"derived numbers",
|
|
18
|
+
"languages",
|
|
19
|
+
"identifier classes",
|
|
20
|
+
"fallback",
|
|
21
|
+
])
|
|
22
|
+
export const FIDELITY_ANSWERED_BY = Object.freeze(["user", "default", "request", "pending"])
|
|
23
|
+
const LOCKED_ANSWERED_BY = new Set(["user", "default", "request"])
|
|
24
|
+
|
|
25
|
+
export const FIDELITY_INCOMPLETE = "fidelity_interview_incomplete"
|
|
26
|
+
const INCOMPLETE_HINT =
|
|
27
|
+
"A fidelity field is pending or missing in brief.md; answer the open question or record its default, then rerun."
|
|
28
|
+
|
|
29
|
+
const BLOCK_HEADING = /^## Fidelity\s*$/
|
|
30
|
+
const NEXT_HEADING = /^## /
|
|
31
|
+
const FIELD_LINE = /^([a-z][a-z ]*[a-z]): ([^()]+) \(answered_by: (user|default|request|pending)\)$/
|
|
32
|
+
const ASKED_LINE = /^asked:(.*)$/
|
|
33
|
+
|
|
34
|
+
function blockLines(markdown) {
|
|
35
|
+
const lines = String(markdown)
|
|
36
|
+
.split(/\r?\n/)
|
|
37
|
+
.map((line) => line.trimEnd())
|
|
38
|
+
const headings = lines.flatMap((line, index) => (BLOCK_HEADING.test(line) ? [index] : []))
|
|
39
|
+
if (headings.length === 0) return null
|
|
40
|
+
const [start, ...duplicates] = headings
|
|
41
|
+
const body = []
|
|
42
|
+
for (let index = start + 1; index < lines.length; index += 1) {
|
|
43
|
+
if (NEXT_HEADING.test(lines[index])) break
|
|
44
|
+
body.push({ text: lines[index], line: index + 1 })
|
|
45
|
+
}
|
|
46
|
+
return { body, duplicates: duplicates.map((index) => index + 1) }
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function parseAsked(rest, line, text, errors) {
|
|
50
|
+
const names = rest
|
|
51
|
+
.split(",")
|
|
52
|
+
.map((name) => name.trim())
|
|
53
|
+
.filter(Boolean)
|
|
54
|
+
const unknown = names.filter((name) => !FIDELITY_FIELDS.includes(name))
|
|
55
|
+
if (unknown.length > 0) {
|
|
56
|
+
errors.push(`line ${line}: malformed asked line, unknown field ${unknown.join(", ")}: ${text}`)
|
|
57
|
+
return []
|
|
58
|
+
}
|
|
59
|
+
return names
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function parseFieldLine({ text, line }, parsed, errors) {
|
|
63
|
+
const match = FIELD_LINE.exec(text)
|
|
64
|
+
if (!match) return errors.push(`line ${line}: malformed fidelity line: ${text}`)
|
|
65
|
+
const [, field, rawValue, answeredBy] = match
|
|
66
|
+
const value = rawValue.trim()
|
|
67
|
+
if (!FIDELITY_FIELDS.includes(field)) return errors.push(`line ${line}: malformed fidelity line, unknown field "${field}": ${text}`)
|
|
68
|
+
if (Object.hasOwn(parsed.fields, field)) return errors.push(`line ${line}: malformed fidelity line, duplicate field "${field}": ${text}`)
|
|
69
|
+
if ((answeredBy === "pending") !== (value === "pending")) {
|
|
70
|
+
return errors.push(`line ${line}: malformed fidelity line, value pending and answered_by: pending go together: ${text}`)
|
|
71
|
+
}
|
|
72
|
+
parsed.fields[field] = value
|
|
73
|
+
parsed.answered_by[field] = answeredBy
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* @param {string} briefMd the whole brief.md text
|
|
78
|
+
* @returns {{ present: boolean, fields: Record<string, string>, answered_by: Record<string, string>,
|
|
79
|
+
* asked: string[], missing: string[], pending: string[], errors: string[] }}
|
|
80
|
+
*/
|
|
81
|
+
export function parseFidelityBlock(briefMd) {
|
|
82
|
+
const parsed = { present: false, fields: {}, answered_by: {}, asked: [], missing: [], pending: [], errors: [] }
|
|
83
|
+
const block = blockLines(briefMd)
|
|
84
|
+
if (block !== null) {
|
|
85
|
+
const { body, duplicates } = block
|
|
86
|
+
parsed.present = true
|
|
87
|
+
for (const line of duplicates) parsed.errors.push(`line ${line}: duplicate ## Fidelity block, only one is allowed`)
|
|
88
|
+
let sawAsked = false
|
|
89
|
+
for (const entry of body) {
|
|
90
|
+
if (entry.text.trim() === "") continue
|
|
91
|
+
const asked = ASKED_LINE.exec(entry.text)
|
|
92
|
+
if (!asked) {
|
|
93
|
+
parseFieldLine(entry, parsed, parsed.errors)
|
|
94
|
+
continue
|
|
95
|
+
}
|
|
96
|
+
if (sawAsked) parsed.errors.push(`line ${entry.line}: malformed asked line, duplicate: ${entry.text}`)
|
|
97
|
+
else parsed.asked = parseAsked(asked[1], entry.line, entry.text, parsed.errors)
|
|
98
|
+
sawAsked = true
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
parsed.missing = FIDELITY_FIELDS.filter((field) => !Object.hasOwn(parsed.fields, field))
|
|
102
|
+
parsed.pending = FIDELITY_FIELDS.filter((field) => parsed.answered_by[field] === "pending")
|
|
103
|
+
return parsed
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** Locked = no parse error and all seven fields present with answered_by user, default, or request. */
|
|
107
|
+
export function isLocked(parsed) {
|
|
108
|
+
if (!parsed || parsed.errors.length > 0) return false
|
|
109
|
+
return FIDELITY_FIELDS.every((field) => Object.hasOwn(parsed.fields, field) && LOCKED_ANSWERED_BY.has(parsed.answered_by[field]))
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** The brief's `meaning check` field turns the meaning verifier off only when it reads "off". */
|
|
113
|
+
export function meaningCheckEnabled(parsed) {
|
|
114
|
+
return !/^off\b/iu.test(parsed?.fields?.["meaning check"] ?? "")
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function incompleteDefect(briefPath, parsed) {
|
|
118
|
+
const reasons = []
|
|
119
|
+
if (!parsed.present) reasons.push("no ## Fidelity block")
|
|
120
|
+
else if (parsed.missing.length > 0) reasons.push(`missing: ${parsed.missing.join(", ")}`)
|
|
121
|
+
if (parsed.pending.length > 0) reasons.push(`pending: ${parsed.pending.join(", ")}`)
|
|
122
|
+
return {
|
|
123
|
+
code: FIDELITY_INCOMPLETE,
|
|
124
|
+
severity: "blocker",
|
|
125
|
+
integrity: true,
|
|
126
|
+
selector: null,
|
|
127
|
+
line: null,
|
|
128
|
+
message: `${briefPath}: fidelity interview incomplete (${reasons.join("; ")})`,
|
|
129
|
+
hint: INCOMPLETE_HINT,
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* The lock check the reader-facing CLI stages run before writing anything.
|
|
135
|
+
* exitCode 0 = locked; 1 = pending, missing, or no brief (one `fidelity_interview_incomplete`
|
|
136
|
+
* defect); 2 = malformed block (usage error, `errors` name the lines, no defect).
|
|
137
|
+
* @param {string} briefPath
|
|
138
|
+
*/
|
|
139
|
+
export function requireLockedBrief(briefPath) {
|
|
140
|
+
let markdown = ""
|
|
141
|
+
try {
|
|
142
|
+
markdown = readFileSync(briefPath, "utf8")
|
|
143
|
+
} catch (error) {
|
|
144
|
+
if (error?.code !== "ENOENT") throw error
|
|
145
|
+
}
|
|
146
|
+
const parsed = parseFidelityBlock(markdown)
|
|
147
|
+
if (parsed.errors.length > 0) return { locked: false, exitCode: 2, defects: [], errors: parsed.errors, parsed }
|
|
148
|
+
if (isLocked(parsed)) return { locked: true, exitCode: 0, defects: [], errors: [], parsed }
|
|
149
|
+
return { locked: false, exitCode: 1, defects: [incompleteDefect(briefPath, parsed)], errors: [], parsed }
|
|
150
|
+
}
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test"
|
|
2
|
+
import { mkdtempSync, rmSync, writeFileSync } from "node:fs"
|
|
3
|
+
import { tmpdir } from "node:os"
|
|
4
|
+
import { join } from "node:path"
|
|
5
|
+
|
|
6
|
+
import { FIDELITY_FIELDS, isLocked, parseFidelityBlock, requireLockedBrief } from "./fidelity-brief.mjs"
|
|
7
|
+
|
|
8
|
+
const fixture = (name: string) => join(import.meta.dir, "tests", "fixtures", name)
|
|
9
|
+
const lockedMd = await Bun.file(fixture("fidelity-brief-locked.md")).text()
|
|
10
|
+
const pendingMd = await Bun.file(fixture("fidelity-brief-pending.md")).text()
|
|
11
|
+
|
|
12
|
+
function withoutLine(markdown: string, prefix: string): string {
|
|
13
|
+
return markdown
|
|
14
|
+
.split("\n")
|
|
15
|
+
.filter((line) => !line.startsWith(prefix))
|
|
16
|
+
.join("\n")
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
describe("parseFidelityBlock", () => {
|
|
20
|
+
test("#given a complete block #when parsed #then seven fields are read and the brief is locked", () => {
|
|
21
|
+
// when
|
|
22
|
+
const parsed = parseFidelityBlock(lockedMd)
|
|
23
|
+
// then
|
|
24
|
+
expect(parsed.errors).toEqual([])
|
|
25
|
+
expect(Object.keys(parsed.fields).sort()).toEqual([...FIDELITY_FIELDS].sort())
|
|
26
|
+
expect(parsed.fields["as of"]).toBe("latest at run start, 2026-10-23")
|
|
27
|
+
expect(parsed.fields.languages).toBe("report ko; sources ko, en; quotes original with translation beside")
|
|
28
|
+
expect(parsed.answered_by["derived numbers"]).toBe("user")
|
|
29
|
+
expect(parsed.answered_by.fallback).toBe("request")
|
|
30
|
+
expect(parsed.asked).toEqual(["derived numbers"])
|
|
31
|
+
expect(isLocked(parsed)).toBe(true)
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
test("#given a block missing as of #when parsed #then it is not locked and the missing field is named", () => {
|
|
35
|
+
// when
|
|
36
|
+
const parsed = parseFidelityBlock(withoutLine(lockedMd, "as of:"))
|
|
37
|
+
// then
|
|
38
|
+
expect(isLocked(parsed)).toBe(false)
|
|
39
|
+
expect(parsed.missing).toEqual(["as of"])
|
|
40
|
+
expect(parsed.errors).toEqual([])
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
test("#given a field answered_by pending #when parsed #then it is not locked and not malformed", () => {
|
|
44
|
+
// when
|
|
45
|
+
const parsed = parseFidelityBlock(pendingMd)
|
|
46
|
+
// then
|
|
47
|
+
expect(parsed.errors).toEqual([])
|
|
48
|
+
expect(parsed.answered_by["derived numbers"]).toBe("pending")
|
|
49
|
+
expect(parsed.pending).toEqual(["derived numbers"])
|
|
50
|
+
expect(isLocked(parsed)).toBe(false)
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
test("#given a malformed field line #when parsed #then errors name the line as malformed", () => {
|
|
54
|
+
// given
|
|
55
|
+
const markdown = lockedMd.replace("meaning check: on (answered_by: default)", "meaning check: on")
|
|
56
|
+
// when
|
|
57
|
+
const parsed = parseFidelityBlock(markdown)
|
|
58
|
+
// then
|
|
59
|
+
expect(parsed.errors.length).toBe(1)
|
|
60
|
+
expect(parsed.errors[0]).toContain("malformed")
|
|
61
|
+
expect(parsed.errors[0]).toContain("meaning check: on")
|
|
62
|
+
expect(isLocked(parsed)).toBe(false)
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
test("#given grammar violations #when parsed #then each is a malformed error", () => {
|
|
66
|
+
const cases = [
|
|
67
|
+
lockedMd.replace("fallback: block-quote (answered_by: request)", "fallback: block-quote (answered_by: someone)"),
|
|
68
|
+
lockedMd.replace("meaning check: on (answered_by: default)", "meaning check: on (maybe) (answered_by: default)"),
|
|
69
|
+
lockedMd.replace("meaning check: on (answered_by: default)", "meaning check: on (answered_by: pending)"),
|
|
70
|
+
lockedMd.replace("asked: derived numbers", "asked: derived numbers\ntone: formal (answered_by: user)"),
|
|
71
|
+
lockedMd.replace("asked: derived numbers", "asked: derived numbers\nfallback: omit (answered_by: user)"),
|
|
72
|
+
lockedMd.replace("asked: derived numbers", "asked: tone"),
|
|
73
|
+
]
|
|
74
|
+
for (const markdown of cases) expect(parseFidelityBlock(markdown).errors.length).toBe(1)
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
test("#given a brief with no Fidelity block #when parsed #then it is not locked and every field is missing", () => {
|
|
78
|
+
// when
|
|
79
|
+
const parsed = parseFidelityBlock("# Brief\n\n## Deliverable\nformat: pdf (answered_by: request)\n")
|
|
80
|
+
// then
|
|
81
|
+
expect(parsed.present).toBe(false)
|
|
82
|
+
expect(parsed.missing).toEqual([...FIDELITY_FIELDS])
|
|
83
|
+
expect(isLocked(parsed)).toBe(false)
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
test("#given field lines after the next heading #when parsed #then they are outside the block", () => {
|
|
87
|
+
// given
|
|
88
|
+
const markdown = withoutLine(lockedMd, "fallback:") + "\nfallback: omit (answered_by: user)\n"
|
|
89
|
+
// then
|
|
90
|
+
expect(parseFidelityBlock(markdown).missing).toEqual(["fallback"])
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
test("#given a second Fidelity block #when parsed #then the duplicate is an error", () => {
|
|
94
|
+
// given
|
|
95
|
+
const markdown = lockedMd + "\n## Fidelity\nas of: x (answered_by: user)\n"
|
|
96
|
+
// when
|
|
97
|
+
const parsed = parseFidelityBlock(markdown)
|
|
98
|
+
// then
|
|
99
|
+
expect(parsed.errors.length).toBe(1)
|
|
100
|
+
expect(parsed.errors[0]).toContain("duplicate")
|
|
101
|
+
expect(parsed.fields["as of"]).toBe("latest at run start, 2026-10-23")
|
|
102
|
+
expect(isLocked(parsed)).toBe(false)
|
|
103
|
+
})
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
describe("requireLockedBrief", () => {
|
|
107
|
+
function briefFile(contents: string | null): { path: string; cleanup: () => void } {
|
|
108
|
+
const dir = mkdtempSync(join(tmpdir(), "fidelity-brief-"))
|
|
109
|
+
const path = join(dir, "brief.md")
|
|
110
|
+
if (contents !== null) writeFileSync(path, contents)
|
|
111
|
+
return { path, cleanup: () => rmSync(dir, { recursive: true, force: true }) }
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
test("#given a locked brief #when required #then exit 0 and no defect", () => {
|
|
115
|
+
const brief = briefFile(lockedMd)
|
|
116
|
+
try {
|
|
117
|
+
expect(requireLockedBrief(brief.path)).toMatchObject({ locked: true, exitCode: 0, defects: [], errors: [] })
|
|
118
|
+
} finally {
|
|
119
|
+
brief.cleanup()
|
|
120
|
+
}
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
test("#given field lines with trailing spaces, a tab, and CRLF endings #when required #then they parse like trimmed lines", () => {
|
|
124
|
+
// given
|
|
125
|
+
const markdown = lockedMd
|
|
126
|
+
.replace("meaning check: on (answered_by: default)", "meaning check: on (answered_by: default) ")
|
|
127
|
+
.replace("fallback: block-quote (answered_by: request)", "fallback: block-quote (answered_by: request)\t")
|
|
128
|
+
.replace("as of: latest at run start, 2026-10-23 (answered_by: default)\n", "as of: latest at run start, 2026-10-23 (answered_by: default) \r\n")
|
|
129
|
+
const brief = briefFile(markdown)
|
|
130
|
+
try {
|
|
131
|
+
// when
|
|
132
|
+
const result = requireLockedBrief(brief.path)
|
|
133
|
+
// then
|
|
134
|
+
expect(result).toMatchObject({ locked: true, exitCode: 0, defects: [], errors: [] })
|
|
135
|
+
expect(result.parsed.fields.fallback).toBe("block-quote")
|
|
136
|
+
} finally {
|
|
137
|
+
brief.cleanup()
|
|
138
|
+
}
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
test("#given a second Fidelity block #when required #then exit 2 naming the duplicate", () => {
|
|
142
|
+
const brief = briefFile(lockedMd + "\n## Fidelity\nas of: x (answered_by: user)\n")
|
|
143
|
+
try {
|
|
144
|
+
const result = requireLockedBrief(brief.path)
|
|
145
|
+
expect(result).toMatchObject({ locked: false, exitCode: 2, defects: [] })
|
|
146
|
+
expect(result.errors[0]).toContain("duplicate ## Fidelity block")
|
|
147
|
+
} finally {
|
|
148
|
+
brief.cleanup()
|
|
149
|
+
}
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
test("#given a pending, partial, or absent brief #when required #then exit 1 with the single lock defect", () => {
|
|
153
|
+
for (const contents of [pendingMd, withoutLine(lockedMd, "as of:"), "# Brief\n", null]) {
|
|
154
|
+
const brief = briefFile(contents)
|
|
155
|
+
try {
|
|
156
|
+
const result = requireLockedBrief(brief.path)
|
|
157
|
+
expect(result.locked).toBe(false)
|
|
158
|
+
expect(result.exitCode).toBe(1)
|
|
159
|
+
expect(result.defects.length).toBe(1)
|
|
160
|
+
expect(result.defects[0]).toMatchObject({ code: "fidelity_interview_incomplete", severity: "blocker", integrity: true })
|
|
161
|
+
expect(typeof result.defects[0].message).toBe("string")
|
|
162
|
+
expect(typeof result.defects[0].hint).toBe("string")
|
|
163
|
+
} finally {
|
|
164
|
+
brief.cleanup()
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
test("#given a malformed brief #when required #then exit 2 with the parse errors and no defect", () => {
|
|
170
|
+
const brief = briefFile(lockedMd.replace("meaning check: on (answered_by: default)", "meaning check: on"))
|
|
171
|
+
try {
|
|
172
|
+
const result = requireLockedBrief(brief.path)
|
|
173
|
+
expect(result).toMatchObject({ locked: false, exitCode: 2, defects: [] })
|
|
174
|
+
expect(result.errors[0]).toContain("malformed")
|
|
175
|
+
} finally {
|
|
176
|
+
brief.cleanup()
|
|
177
|
+
}
|
|
178
|
+
})
|
|
179
|
+
})
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
// fidelity-claims.mjs - the claim-kind checks of the deterministic fidelity gate.
|
|
2
|
+
//
|
|
3
|
+
// A mark (the data-claim-kind attribute) binds per cited fact: a data-fact element takes the kind
|
|
4
|
+
// of its nearest mark (its own, else the closest marked ancestor), and only that kind is checked
|
|
5
|
+
// against its ledger row (claim-kinds.mjs CLAIM_KINDS); a row that fails is the kind's defect
|
|
6
|
+
// (unsupported_verification for `verified`). A fact under two marks of different kinds is a
|
|
7
|
+
// claim_kind_mismatch, and a mark that owns no fact of its own kind (every fact under it cites
|
|
8
|
+
// nothing or is shadowed by another kind) is its kind's defect: shadowing never satisfies a parent.
|
|
9
|
+
// An unmarked fact citing a row neither fact nor quote accepts (an assumed row) is a
|
|
10
|
+
// claim_kind_mismatch naming the mark it needs. A sentence that matches a kind's unmarked patterns warns (minor, verification_claim_unmarked)
|
|
11
|
+
// unless every fact it cites is marked that kind; a sentence citing no fact warns unless its block
|
|
12
|
+
// is marked. No model, no network.
|
|
13
|
+
|
|
14
|
+
import { CLAIM_ATTRIBUTE, CLAIM_KINDS, claimSatisfied, unmarkedKinds, unmarkedRequires } from "./claim-kinds.mjs"
|
|
15
|
+
import { proseSentences } from "./fidelity-binding.mjs"
|
|
16
|
+
import { clip } from "./fidelity-trace.mjs"
|
|
17
|
+
import { proseText } from "./html-lite.mjs"
|
|
18
|
+
|
|
19
|
+
const marked = (node) => node.attrs?.[CLAIM_ATTRIBUTE] !== undefined
|
|
20
|
+
const kindOf = (node) => node.attrs[CLAIM_ATTRIBUTE].trim()
|
|
21
|
+
const isFact = (node) => node.attrs["data-fact"] !== undefined
|
|
22
|
+
|
|
23
|
+
/** The marks over a node, nearest first: its own mark, then each marked ancestor. */
|
|
24
|
+
function marksOver(node) {
|
|
25
|
+
const marks = []
|
|
26
|
+
for (let cur = node; cur; cur = cur.parent) if (marked(cur)) marks.push(cur)
|
|
27
|
+
return marks
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const nearestKind = (node) => {
|
|
31
|
+
const [mark] = marksOver(node)
|
|
32
|
+
return mark ? kindOf(mark) : null
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Whether `mark` binds `fact`: every mark from the fact up to `mark` names the same kind. */
|
|
36
|
+
function binds(mark, fact) {
|
|
37
|
+
const marks = marksOver(fact)
|
|
38
|
+
const at = marks.indexOf(mark)
|
|
39
|
+
return at >= 0 && marks.slice(0, at + 1).every((cur) => kindOf(cur) === kindOf(mark))
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* @param {object} ctx buildContext(...) from gates-static.mjs
|
|
44
|
+
* @param {Map<string, object>} rows ledger rows by fact_id
|
|
45
|
+
* @param {(row: object) => string | undefined} provenanceOf meta.json kind of the snapshot holding the row's own locator artifact
|
|
46
|
+
* @param {(code: string, node: object, message: string) => void} flag
|
|
47
|
+
*/
|
|
48
|
+
export function claimChecks(ctx, rows, provenanceOf, flag) {
|
|
49
|
+
for (const node of ctx.all.filter(marked)) {
|
|
50
|
+
const name = kindOf(node)
|
|
51
|
+
const entry = CLAIM_KINDS[name]
|
|
52
|
+
if (!entry) {
|
|
53
|
+
flag("claim_kind_mismatch", node, `${CLAIM_ATTRIBUTE}="${name}" is not a claim kind (${Object.keys(CLAIM_KINDS).join(", ")})`)
|
|
54
|
+
continue
|
|
55
|
+
}
|
|
56
|
+
const cited = [node, ...ctx.util.descendants(node)].filter(isFact)
|
|
57
|
+
if (cited.some((fact) => binds(node, fact))) continue
|
|
58
|
+
const why = cited.length === 0 ? "cites no fact" : "cites no fact of its own (a nested mark of another kind never satisfies it)"
|
|
59
|
+
flag(entry.defect, node, `"${clip(proseText(node))}" is marked ${name} but ${why}`)
|
|
60
|
+
}
|
|
61
|
+
for (const fact of ctx.all.filter(isFact)) {
|
|
62
|
+
const [nearest, ...outer] = marksOver(fact)
|
|
63
|
+
const id = fact.attrs["data-fact"]
|
|
64
|
+
if (!nearest) {
|
|
65
|
+
const need = unmarkedRequires(rows.get(id))
|
|
66
|
+
const mark = need && `{{${CLAIM_KINDS[need].prefix}:${id}}} (${CLAIM_ATTRIBUTE}="${need}")`
|
|
67
|
+
if (need) flag("claim_kind_mismatch", fact, `${id} is a ${rows.get(id).kind} row cited with no mark; an unmarked numeral reads as a fact, so mark it ${mark}`)
|
|
68
|
+
continue
|
|
69
|
+
}
|
|
70
|
+
const name = kindOf(nearest)
|
|
71
|
+
const conflict = outer.find((mark) => kindOf(mark) !== name)
|
|
72
|
+
if (conflict) flag("claim_kind_mismatch", fact, `${id} is marked ${name} inside a ${kindOf(conflict)} mark; a fact carries one claim kind`)
|
|
73
|
+
const entry = CLAIM_KINDS[name]
|
|
74
|
+
const row = rows.get(id)
|
|
75
|
+
if (!entry || !row || claimSatisfied(name, row, provenanceOf(row))) continue
|
|
76
|
+
const source = entry.sourceKinds ? ` and not one whose own locator artifact sits in a ${entry.sourceKinds.join(" or ")} snapshot` : ""
|
|
77
|
+
flag(entry.defect, fact, `${row.fact_id} is marked ${name}, but it is a ${row.kind} row${source}`)
|
|
78
|
+
}
|
|
79
|
+
for (const block of proseSentences(ctx)) {
|
|
80
|
+
if (block.kind === "sources" || block.kind === "toc") continue
|
|
81
|
+
for (const sentence of block.sentences) {
|
|
82
|
+
for (const name of unmarkedKinds(sentence.text)) {
|
|
83
|
+
const covered = sentence.facts.length > 0 ? sentence.facts.every((fact) => nearestKind(fact.node) === name) : nearestKind(block.owner) === name
|
|
84
|
+
if (covered) continue
|
|
85
|
+
flag(CLAIM_KINDS[name].unmarked.code, block.owner, `"${clip(sentence.text)}" reads as a ${name} claim but cites a fact with no ${CLAIM_ATTRIBUTE}="${name}" mark`)
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
// fidelity-lexicon.mjs - closed-list lexical checks over (source quote, report sentence) pairs.
|
|
2
|
+
//
|
|
3
|
+
// lexiconChecks compares each pair with the lexicon.mjs lists: hedge_dropped, direction_flipped,
|
|
4
|
+
// causality_added, qualifier_dropped (six-token window around the fact span; a :q rendering puts
|
|
5
|
+
// the qualifier inside the span), scope_widened_warn, and the acronym-aware name_outside_quote.
|
|
6
|
+
// The hedge-position heuristic never yields a code; it only sets force_strong for the meaning
|
|
7
|
+
// verifier, as do name_outside_quote and scope_widened_warn. Latin terms match whole words (terms
|
|
8
|
+
// of four or more letters also match their inflections); Korean terms match as substrings, single
|
|
9
|
+
// syllables as tokens. No model, no IO. Plain Node ESM; Korean strings are \u escapes.
|
|
10
|
+
|
|
11
|
+
import { CAUSAL, CONCESSIVE, DIRECTION, HEDGES, NEGATION, QUALIFIERS, SCOPE_NARROW_TO_BROAD } from "./lexicon.mjs"
|
|
12
|
+
import { normalizeText } from "./text-normalize.mjs"
|
|
13
|
+
import { normalizeUnit } from "./units.mjs"
|
|
14
|
+
|
|
15
|
+
/** Ledger qualifier terms -> their surface forms in either language (closed mapping). */
|
|
16
|
+
export const QUALIFIER_FORMS = Object.freeze({
|
|
17
|
+
upper_inclusive: ["up to", "at most", "\uCD5C\uB300", "\uC774\uD558", "\uAE4C\uC9C0"],
|
|
18
|
+
lower_inclusive: ["at least", "\uCD5C\uC18C", "\uC774\uC0C1"],
|
|
19
|
+
approximate: ["about", "approximately", "\uC57D", "\uB0B4\uC678"],
|
|
20
|
+
near: ["nearly"],
|
|
21
|
+
lower_exclusive: ["over", "more than", "\uCD08\uACFC"],
|
|
22
|
+
upper_exclusive: ["under", "less than", "\uBBF8\uB9CC"],
|
|
23
|
+
})
|
|
24
|
+
/** Irregular and synonym direction forms the stem match cannot reach ("rose", "reduced"). */
|
|
25
|
+
const DIRECTION_FORMS = { up: ["rose", "risen", "rising", "grew", "grown", "gain"], down: ["reduc", "fell", "fallen", "falling", "drop", "lower"] }
|
|
26
|
+
const KOREAN = /[\uAC00-\uD7A3]/
|
|
27
|
+
/** True when the term holds a Hangul syllable (Korean terms match by the Korean rules below). */
|
|
28
|
+
export const isKoreanTerm = (term) => KOREAN.test(term)
|
|
29
|
+
/** The guarded hedge and qualifier vocabulary: the union of HEDGES, QUALIFIERS and every QUALIFIER_FORMS surface form. */
|
|
30
|
+
export const guardedTerms = () => [...new Set([...HEDGES, ...QUALIFIERS, ...Object.values(QUALIFIER_FORMS).flat()])]
|
|
31
|
+
const ACRONYM = /(?<![\p{L}\p{N}])[A-Z]{2,6}(?![\p{L}\p{N}])/gu
|
|
32
|
+
const WINDOW = 6
|
|
33
|
+
const cache = new Map()
|
|
34
|
+
|
|
35
|
+
const escape = (text) => text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* The lexical pattern engine. mode: "stem" (inflections, Korean one-syllable token prefix), "exact",
|
|
39
|
+
* "word", "suffix" (Korean attached to a value), "guard" (single Latin words of four or more letters
|
|
40
|
+
* take inflections, multiword terms never do; Korean terms match as plain substrings).
|
|
41
|
+
*/
|
|
42
|
+
export function lexicalPattern(term, mode) {
|
|
43
|
+
const key = `${mode}\u0001${term}`
|
|
44
|
+
if (cache.has(key)) return cache.get(key)
|
|
45
|
+
let source
|
|
46
|
+
if (!KOREAN.test(term)) {
|
|
47
|
+
const stems = term.length >= 4 && (mode === "stem" || (mode === "guard" && !term.includes(" ")))
|
|
48
|
+
source = `(?<![\\p{L}\\p{N}])${escape(term).replace(/ /g, "\\s+")}${stems ? "\\p{L}*" : ""}(?![\\p{L}\\p{N}])`
|
|
49
|
+
} else if (mode === "guard") source = escape(term)
|
|
50
|
+
else if (mode === "suffix") source = `(?<=^|[\\s\\p{P}\\p{N}A-Za-z])${escape(term)}(?=$|[\\s\\p{P}])`
|
|
51
|
+
else if ([...term].length >= 2 || mode === "word") source = escape(term)
|
|
52
|
+
else source = `(?<=^|[\\s\\p{P}])${escape(term)}${mode === "stem" ? "" : "(?=$|[\\s\\p{P}])"}`
|
|
53
|
+
const re = new RegExp(source, "giu")
|
|
54
|
+
cache.set(key, re)
|
|
55
|
+
return re
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function positions(text, terms, mode) {
|
|
59
|
+
const out = []
|
|
60
|
+
for (const term of terms) for (const match of text.matchAll(lexicalPattern(term, mode))) out.push({ term, at: match.index })
|
|
61
|
+
return out.sort((a, b) => a.at - b.at)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const has = (text, terms, mode) => positions(text, terms, mode).length > 0
|
|
65
|
+
const tokens = (text) => text.split(/\s+/).filter(Boolean)
|
|
66
|
+
const stripQualifiers = (text) => QUALIFIERS.reduce((rest, term) => rest.replace(lexicalPattern(term, "exact"), " "), text)
|
|
67
|
+
|
|
68
|
+
function directions(text) {
|
|
69
|
+
const rest = stripQualifiers(text)
|
|
70
|
+
return {
|
|
71
|
+
up: has(rest, DIRECTION.up, "stem") || has(rest, DIRECTION_FORMS.up, "stem"),
|
|
72
|
+
down: has(rest, DIRECTION.down, "stem") || has(rest, DIRECTION_FORMS.down, "stem"),
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** The fact span plus up to six tokens on each side, never crossing the sentence. */
|
|
77
|
+
function qualifierWindow(sentence, span) {
|
|
78
|
+
const [start, end] = span
|
|
79
|
+
return [...tokens(sentence.slice(0, start)).slice(-WINDOW), sentence.slice(start, end), ...tokens(sentence.slice(end)).slice(0, WINDOW)].join(" ")
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function qualifierDropped(pair) {
|
|
83
|
+
if (!pair.qualifier || !Array.isArray(pair.span)) return false
|
|
84
|
+
const forms = QUALIFIER_FORMS[pair.qualifier] ?? [pair.qualifier]
|
|
85
|
+
const window = qualifierWindow(pair.sentence, pair.span)
|
|
86
|
+
return !forms.some((form) => has(window, [form], KOREAN.test(form) ? "suffix" : "exact"))
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** A hedge before the sentence's last concessive while the quote hedges its own final clause. */
|
|
90
|
+
function hedgeMoved(quote, sentence) {
|
|
91
|
+
const lastConcessive = (text) => positions(text, CONCESSIVE, "word").at(-1)?.at ?? -1
|
|
92
|
+
const sentenceHedge = positions(sentence, HEDGES, "stem")[0]?.at
|
|
93
|
+
const quoteHedge = positions(quote, HEDGES, "stem").at(-1)?.at
|
|
94
|
+
const sentenceConcessive = lastConcessive(sentence)
|
|
95
|
+
return sentenceHedge !== undefined && quoteHedge !== undefined && sentenceHedge < sentenceConcessive && quoteHedge > lastConcessive(quote)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function pairFindings(pair) {
|
|
99
|
+
const quote = normalizeText(pair.quote)
|
|
100
|
+
const sentence = normalizeText(pair.sentence)
|
|
101
|
+
const found = []
|
|
102
|
+
const add = (code, message) => found.push({ pair_id: pair.pair_id, code, message })
|
|
103
|
+
const dropped = qualifierDropped(pair)
|
|
104
|
+
if (dropped) add("qualifier_dropped", `the source bounds the value (${pair.qualifier}) and the sentence drops it`)
|
|
105
|
+
const sourceHedges = positions(quote, HEDGES, "stem")
|
|
106
|
+
const qualifierOnly = sourceHedges.every(({ term }) => QUALIFIERS.includes(term))
|
|
107
|
+
if (sourceHedges.length > 0 && !has(sentence, HEDGES, "stem") && !(dropped && qualifierOnly)) {
|
|
108
|
+
add("hedge_dropped", `the source hedges ("${sourceHedges[0].term}") and the sentence does not`)
|
|
109
|
+
}
|
|
110
|
+
const [from, to] = [directions(quote), directions(sentence)]
|
|
111
|
+
const negated = has(quote, NEGATION, "word") || has(sentence, NEGATION, "word")
|
|
112
|
+
if (!negated && ((from.up && !from.down && to.down && !to.up) || (from.down && !from.up && to.up && !to.down))) {
|
|
113
|
+
add("direction_flipped", `the source says ${from.up ? "up" : "down"} and the sentence says ${to.up ? "up" : "down"}`)
|
|
114
|
+
}
|
|
115
|
+
const causal = positions(sentence, CAUSAL, "word").find(({ term }) => !has(quote, [term], "word"))
|
|
116
|
+
if (causal) add("causality_added", `"${causal.term}" states a cause the source does not`)
|
|
117
|
+
const context = [quote, ...(pair.context ?? []).map(normalizeText)]
|
|
118
|
+
for (const [narrow, broad] of SCOPE_NARROW_TO_BROAD) {
|
|
119
|
+
if (has(quote, [narrow], "word") && has(sentence, [broad], "word") && !context.some((text) => has(text, [broad], "word"))) {
|
|
120
|
+
add("scope_widened_warn", `the source says "${narrow}" and the sentence says "${broad}"`)
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
const acronyms = pair.acronyms ?? {}
|
|
124
|
+
const quoteNames = new Set(quote.match(ACRONYM) ?? [])
|
|
125
|
+
const stray = (sentence.match(ACRONYM) ?? []).find((name) => !quoteNames.has(name) && !Object.hasOwn(acronyms, name) && !normalizeUnit(name))
|
|
126
|
+
if (stray) add("name_outside_quote", `"${stray}" is in neither the quote nor the source's acronym table`)
|
|
127
|
+
return found
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* @param {{ pair_id: string, quote: string, sentence: string, span?: [number, number] | null,
|
|
132
|
+
* qualifier?: string | null, acronyms?: Record<string, string>, context?: string[] }[]} pairs
|
|
133
|
+
* @returns {{ findings: { pair_id: string, code: string, message: string }[], forceStrong: string[] }}
|
|
134
|
+
*/
|
|
135
|
+
export function lexiconChecks(pairs) {
|
|
136
|
+
const findings = []
|
|
137
|
+
const forceStrong = []
|
|
138
|
+
for (const pair of pairs) {
|
|
139
|
+
const found = pairFindings(pair)
|
|
140
|
+
findings.push(...found)
|
|
141
|
+
const routed = found.some(({ code }) => code === "name_outside_quote" || code === "scope_widened_warn")
|
|
142
|
+
if (routed || hedgeMoved(normalizeText(pair.quote), normalizeText(pair.sentence))) forceStrong.push(pair.pair_id)
|
|
143
|
+
}
|
|
144
|
+
return { findings, forceStrong }
|
|
145
|
+
}
|