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,228 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, test } from "bun:test"
|
|
2
|
+
import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs"
|
|
3
|
+
import { tmpdir } from "node:os"
|
|
4
|
+
import { join } from "node:path"
|
|
5
|
+
|
|
6
|
+
import { DRIFT_KINDS, ingestVerdicts } from "./meaning-check.mjs"
|
|
7
|
+
|
|
8
|
+
type Verdict = Record<string, unknown>
|
|
9
|
+
type Result = {
|
|
10
|
+
defects: { code: string; severity: string; integrity: boolean; pair_id: string; drift_kind: string; fragment: string; source_fragment: string }[]
|
|
11
|
+
dropped: { pair_id: string | null; reason: string; model_tier: string | null; file: string }[]
|
|
12
|
+
unverified: { pair_id: string; reason: string }[]
|
|
13
|
+
calibrationLines: string[]
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const PAIRS = [
|
|
17
|
+
{ pair_id: "P1", kind: "quote", sentence_key: "findings#Q1", fact_ids: ["Q1"], quote: "The pilot covered North America.", sentence: "The rollout was worldwide.", force_strong: true },
|
|
18
|
+
{ pair_id: "P2", kind: "claim", claim_id: "C2", statement: "Revenue may grow in 2026.", sentence: "Revenue will grow in 2026.", force_strong: true },
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
const base = (tier: string, extra: Verdict = {}): Verdict => ({
|
|
22
|
+
pair_id: "P1",
|
|
23
|
+
verdict: "drift",
|
|
24
|
+
drift_kind: "scope_widened",
|
|
25
|
+
fragment: "worldwide",
|
|
26
|
+
source_fragment: "North America",
|
|
27
|
+
explanation: "The assertion broadens the reported region.",
|
|
28
|
+
model_tier: tier,
|
|
29
|
+
...extra,
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
let dir = ""
|
|
33
|
+
beforeEach(() => {
|
|
34
|
+
dir = mkdtempSync(join(tmpdir(), "meaning-check-"))
|
|
35
|
+
})
|
|
36
|
+
afterEach(() => {
|
|
37
|
+
rmSync(dir, { recursive: true, force: true })
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
function file(name: string, verdicts: Verdict[]): string {
|
|
41
|
+
const path = join(dir, name)
|
|
42
|
+
writeFileSync(path, JSON.stringify(verdicts))
|
|
43
|
+
return path
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function run(cheap: Verdict[], strong: Verdict[] = [], options: object = {}): Result {
|
|
47
|
+
return ingestVerdicts(PAIRS, [file("cheap.json", cheap), file("strong.json", strong)], options) as Result
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
describe("ingestVerdicts: tier agreement", () => {
|
|
51
|
+
test("#given cheap and strong agree on scope_widened #when ingested #then meaning_drift_confirmed (major, non-integrity)", () => {
|
|
52
|
+
const result = run([base("cheap")], [base("strong")])
|
|
53
|
+
expect(result.defects).toHaveLength(1)
|
|
54
|
+
expect(result.defects[0]).toMatchObject({ code: "meaning_drift_confirmed", severity: "major", integrity: false, pair_id: "P1", drift_kind: "scope_widened" })
|
|
55
|
+
expect(result.dropped).toEqual([])
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
test("#given only the cheap tier flags #when ingested #then meaning_drift_warn (minor)", () => {
|
|
59
|
+
const result = run([base("cheap")])
|
|
60
|
+
expect(result.defects.map((d) => [d.code, d.severity])).toEqual([["meaning_drift_warn", "minor"]])
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
test("#given both tiers agree on other #when ingested #then warn only, never confirmed", () => {
|
|
64
|
+
const result = run([base("cheap", { drift_kind: "other" })], [base("strong", { drift_kind: "other" })])
|
|
65
|
+
expect(result.defects.map((d) => d.code)).toEqual(["meaning_drift_warn"])
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
test("#given the tiers name different drift kinds #when ingested #then warn only", () => {
|
|
69
|
+
const result = run([base("cheap")], [base("strong", { drift_kind: "subject_changed" })])
|
|
70
|
+
expect(result.defects.map((d) => d.code)).toEqual(["meaning_drift_warn"])
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
test("#given an ok verdict #when ingested #then no defect and no calibration line", () => {
|
|
74
|
+
const result = run([{ pair_id: "P1", verdict: "ok", model_tier: "cheap" }])
|
|
75
|
+
expect(result.defects).toEqual([])
|
|
76
|
+
expect(result.calibrationLines).toEqual([])
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
test("#given a claim pair #when both tiers agree on evidence_grade_raised #then confirmed against the statement", () => {
|
|
80
|
+
const verdict = { pair_id: "P2", verdict: "drift", drift_kind: "evidence_grade_raised", fragment: "will grow", source_fragment: "may grow", explanation: "certainty raised" }
|
|
81
|
+
const result = run([{ ...verdict, model_tier: "cheap" }], [{ ...verdict, model_tier: "strong" }])
|
|
82
|
+
expect(result.defects.map((d) => [d.code, d.pair_id])).toEqual([["meaning_drift_confirmed", "P2"]])
|
|
83
|
+
})
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
describe("ingestVerdicts: grounding drops", () => {
|
|
87
|
+
test("#given a fragment that is not in the sentence #when ingested #then dropped and counted, no defect", () => {
|
|
88
|
+
const result = run([base("cheap", { fragment: "Europe" })])
|
|
89
|
+
expect(result.defects).toEqual([])
|
|
90
|
+
expect(result.dropped).toHaveLength(1)
|
|
91
|
+
expect(result.dropped[0]).toMatchObject({ pair_id: "P1", reason: "fragment_not_found", model_tier: "cheap" })
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
test("#given a source fragment that is not in the quote #when ingested #then dropped", () => {
|
|
95
|
+
const result = run([base("cheap", { source_fragment: "Asia" })])
|
|
96
|
+
expect(result.defects).toEqual([])
|
|
97
|
+
expect(result.dropped.map((d) => d.reason)).toEqual(["source_fragment_not_found"])
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
test("#given empty or blank fragments #when ingested #then dropped as empty", () => {
|
|
101
|
+
const result = run([base("cheap", { fragment: "" })], [base("strong", { source_fragment: " " })])
|
|
102
|
+
expect(result.defects).toEqual([])
|
|
103
|
+
expect(result.dropped.map((d) => d.reason)).toEqual(["empty_fragment", "empty_fragment"])
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
test("#given an unknown drift_kind #when ingested #then dropped", () => {
|
|
107
|
+
const result = run([base("cheap", { drift_kind: "tone_shifted" })])
|
|
108
|
+
expect(result.defects).toEqual([])
|
|
109
|
+
expect(result.dropped.map((d) => d.reason)).toEqual(["unknown_drift_kind"])
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
test("#given an unknown pair id or tier #when ingested #then dropped", () => {
|
|
113
|
+
const result = run([base("cheap", { pair_id: "P99" }), base("medium")])
|
|
114
|
+
expect(result.dropped.map((d) => d.reason)).toEqual(["unknown_pair", "invalid_model_tier"])
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
test("#given extra whitespace in a fragment #when ingested #then it still grounds", () => {
|
|
118
|
+
const result = run([base("cheap", { fragment: "rollout \n was", source_fragment: " North America " })])
|
|
119
|
+
expect(result.dropped).toEqual([])
|
|
120
|
+
expect(result.defects.map((d) => d.code)).toEqual(["meaning_drift_warn"])
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
test("#given the closed drift_kind set #when read #then it holds exactly the nine kinds", () => {
|
|
124
|
+
expect([...DRIFT_KINDS]).toEqual([
|
|
125
|
+
"subject_changed", "scope_widened", "hedge_dropped", "certainty_added", "causality_added",
|
|
126
|
+
"direction_flipped", "population_changed", "evidence_grade_raised", "other",
|
|
127
|
+
])
|
|
128
|
+
})
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
describe("ingestVerdicts: calibration log", () => {
|
|
132
|
+
test("#given a dispute on a confirmed pair #when ingested #then the defect stays and the line records disputed:true", () => {
|
|
133
|
+
const result = run([base("cheap")], [base("strong")], { dispute: { pair_id: "P1", reason: "Scope is justified by a second observation." } })
|
|
134
|
+
expect(result.defects.map((d) => d.code)).toEqual(["meaning_drift_confirmed"])
|
|
135
|
+
expect(result.calibrationLines.map((line) => JSON.parse(line))).toEqual([
|
|
136
|
+
{ kind: "quote", pair_id: "P1", tiers: ["cheap", "strong"], drift_kind: "scope_widened", confirmed: true, disputed: true, reason: "Scope is justified by a second observation." },
|
|
137
|
+
])
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
test("#given two flagged pairs and one ok verdict #when ingested with a calibration path #then exactly one line per flagged pair is appended", () => {
|
|
141
|
+
const calibrationPath = join(dir, "fidelity-calibration.jsonl")
|
|
142
|
+
const claimDrift = { pair_id: "P2", verdict: "drift", drift_kind: "certainty_added", fragment: "will", source_fragment: "may", explanation: "x", model_tier: "cheap" }
|
|
143
|
+
const cheap = [base("cheap"), claimDrift, { pair_id: "P1", verdict: "ok", model_tier: "cheap" }]
|
|
144
|
+
const first = run(cheap, [], { calibrationPath })
|
|
145
|
+
expect(first.calibrationLines).toHaveLength(2)
|
|
146
|
+
const lines = () => readFileSync(calibrationPath, "utf8").split("\n").filter(Boolean)
|
|
147
|
+
expect(lines()).toHaveLength(2)
|
|
148
|
+
expect(JSON.parse(lines()[1])).toEqual({ kind: "claim", pair_id: "P2", tiers: ["cheap"], drift_kind: "certainty_added", confirmed: false, disputed: false, reason: null })
|
|
149
|
+
run(cheap, [], { calibrationPath })
|
|
150
|
+
expect(lines()).toHaveLength(4)
|
|
151
|
+
})
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
describe("ingestVerdicts: coverage", () => {
|
|
155
|
+
test("#given a pair no reader judged #when ingested #then it is reported as unverified, never as clean", () => {
|
|
156
|
+
const result = run([base("cheap")], [base("strong")])
|
|
157
|
+
expect(result.unverified).toEqual([{ pair_id: "P2", reason: "no_verdict" }])
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
test("#given a force_strong pair with only a cheap verdict #when ingested #then unverified says no_strong_verdict", () => {
|
|
161
|
+
const result = run([base("cheap"), { pair_id: "P2", verdict: "ok", model_tier: "cheap" }])
|
|
162
|
+
expect(result.unverified).toEqual([
|
|
163
|
+
{ pair_id: "P1", reason: "no_strong_verdict" },
|
|
164
|
+
{ pair_id: "P2", reason: "no_strong_verdict" },
|
|
165
|
+
])
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
test("#given every pair read by both tiers #when ingested #then unverified is empty", () => {
|
|
169
|
+
const ok = (pair_id: string, tier: string): Verdict => ({ pair_id, verdict: "ok", model_tier: tier })
|
|
170
|
+
const result = run([ok("P1", "cheap"), ok("P2", "cheap")], [ok("P1", "strong"), ok("P2", "strong")])
|
|
171
|
+
expect(result.unverified).toEqual([])
|
|
172
|
+
expect(result.defects).toEqual([])
|
|
173
|
+
})
|
|
174
|
+
|
|
175
|
+
test("#given only dropped verdicts on a pair #when ingested #then the pair counts as unread", () => {
|
|
176
|
+
const result = run([base("cheap", { fragment: "Europe" })], [base("strong", { fragment: "Europe" })])
|
|
177
|
+
expect(result.unverified.map((entry) => entry.pair_id)).toEqual(["P1", "P2"])
|
|
178
|
+
})
|
|
179
|
+
})
|
|
180
|
+
|
|
181
|
+
describe("ingestVerdicts: the reported fragment", () => {
|
|
182
|
+
test("#given an earlier other verdict #when the tiers agree on a later kind #then the fragment comes from the agreed verdict", () => {
|
|
183
|
+
const other = base("cheap", { drift_kind: "other", fragment: "The rollout", source_fragment: "The pilot" })
|
|
184
|
+
const result = run([other, base("cheap")], [base("strong")])
|
|
185
|
+
expect(result.defects).toHaveLength(1)
|
|
186
|
+
expect(result.defects[0]).toMatchObject({ code: "meaning_drift_confirmed", drift_kind: "scope_widened", fragment: "worldwide", source_fragment: "North America" })
|
|
187
|
+
})
|
|
188
|
+
})
|
|
189
|
+
|
|
190
|
+
describe("ingestVerdicts: determinism", () => {
|
|
191
|
+
test("#given the same verdicts twice #when ingested #then both results are byte-for-byte equal", () => {
|
|
192
|
+
const cheap = [base("cheap"), { pair_id: "P2", verdict: "drift", drift_kind: "certainty_added", fragment: "will grow", source_fragment: "may grow", model_tier: "cheap" }, base("medium")]
|
|
193
|
+
const strong = [base("strong")]
|
|
194
|
+
const first = JSON.stringify(run(cheap, strong))
|
|
195
|
+
const second = JSON.stringify(run(cheap, strong))
|
|
196
|
+
expect(second).toBe(first)
|
|
197
|
+
})
|
|
198
|
+
})
|
|
199
|
+
|
|
200
|
+
describe("ingestVerdicts: unreadable verdict files", () => {
|
|
201
|
+
test("#given a missing verdict file #when ingested #then a short typed error names the file", () => {
|
|
202
|
+
const missing = join(dir, "absent.json")
|
|
203
|
+
expect(() => ingestVerdicts(PAIRS, [missing])).toThrow(TypeError)
|
|
204
|
+
expect(() => ingestVerdicts(PAIRS, [missing])).toThrow(/absent\.json: cannot read the verdict file/u)
|
|
205
|
+
})
|
|
206
|
+
|
|
207
|
+
test("#given invalid JSON #when ingested #then a short typed error names the file", () => {
|
|
208
|
+
const path = join(dir, "broken.json")
|
|
209
|
+
writeFileSync(path, "{ not json")
|
|
210
|
+
expect(() => ingestVerdicts(PAIRS, [path])).toThrow(/broken\.json: the verdict file is not valid JSON/u)
|
|
211
|
+
})
|
|
212
|
+
})
|
|
213
|
+
|
|
214
|
+
describe("ingestVerdicts: malformed pairs input", () => {
|
|
215
|
+
test("#given a missing or malformed pairs input #when ingested #then it refuses instead of reporting a clean result", () => {
|
|
216
|
+
for (const bad of [undefined, null, {}, { pairs: "P1" }, "pairs.json", 7]) {
|
|
217
|
+
expect(() => ingestVerdicts(bad as never, []), JSON.stringify(bad)).toThrow(TypeError)
|
|
218
|
+
}
|
|
219
|
+
expect(ingestVerdicts({ pairs: [] }, []).unverified).toEqual([])
|
|
220
|
+
})
|
|
221
|
+
})
|
|
222
|
+
|
|
223
|
+
describe("ingestVerdicts: the no-model contract", () => {
|
|
224
|
+
test("#given the module source #when read #then it never fetches or imports a network or model client", () => {
|
|
225
|
+
const text = readFileSync(join(import.meta.dir, "meaning-check.mjs"), "utf8")
|
|
226
|
+
expect(text).not.toMatch(/\bfetch\s*\(|node:https?|node:net|from\s+"(?!\.\/|node:)/u)
|
|
227
|
+
})
|
|
228
|
+
})
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
// numerals.mjs - shared numeral patterns and the numeral parser.
|
|
2
|
+
//
|
|
3
|
+
// NUM, UNIT, NOT_CLAIMS and CLAIMS are the claim-detection patterns the unsourced-number gate uses
|
|
4
|
+
// (gates-text.mjs imports them from here). parseNumeral reads one numeral into an exact decimal
|
|
5
|
+
// string: thousands separators, Korean and Latin scale words, percent and percentage points, the
|
|
6
|
+
// English number words one to twelve, an adjacent currency symbol or unit, and an adjacent bounding
|
|
7
|
+
// qualifier. No numeral is ever passed through Number(). Plain Node ESM, sibling imports only.
|
|
8
|
+
|
|
9
|
+
import { add, fromDecimal, mul, toDecimal } from "./rational.mjs"
|
|
10
|
+
import { normalizeUnit } from "./units.mjs"
|
|
11
|
+
|
|
12
|
+
const MONTH = "(?:jan|feb|mar|apr|may|jun|jul|aug|sep|sept|oct|nov|dec)[a-z]*\\.?"
|
|
13
|
+
/** Stripped before claim detection: dates, versions, figure refs, times, bare years. */
|
|
14
|
+
export const NOT_CLAIMS = [
|
|
15
|
+
/\b\d{4}[-./]\d{1,2}(?:[-./]\d{1,2})?\b/g,
|
|
16
|
+
/\b\d{2}\.\d{2}\.\d{2}\b/g,
|
|
17
|
+
/\d{2,4}\s*\uB144\s*\d{1,2}\s*\uC6D4(?:\s*\d{1,2}\s*\uC77C)?/g,
|
|
18
|
+
/\d{1,2}\s*\uC6D4(?:\s*\d{1,2}\s*\uC77C)?/g,
|
|
19
|
+
/(?:19|20)\d{2}\s*\uB144/g,
|
|
20
|
+
new RegExp(`\\b${MONTH}\\s+\\d{1,2}(?:st|nd|rd|th)?(?:,?\\s+\\d{4})?`, "gi"),
|
|
21
|
+
new RegExp(`\\b\\d{1,2}(?:st|nd|rd|th)?\\s+${MONTH}(?:\\s+\\d{4})?`, "gi"),
|
|
22
|
+
/\bv\d+(?:\.\d+)*\b|\b\d+(?:\.\d+){2,}\b/gi,
|
|
23
|
+
/\b(?:version|ver\.?|release)\s*\d+(?:\.\d+)*/gi,
|
|
24
|
+
/\b(?:fig(?:ure)?\.?|table|chart|section|step|phase|wave|part)\s*\d+(?:\.\d+)*/gi,
|
|
25
|
+
/(?:\uADF8\uB9BC|\uB3C4\uD45C|\uD45C|\uBC84\uC804)\s*\d+(?:\.\d+)*/g,
|
|
26
|
+
/\b\d{1,2}:\d{2}\b/g,
|
|
27
|
+
/\b(?:19|20)\d{2}s?\b/g,
|
|
28
|
+
]
|
|
29
|
+
export const NUM = "(?<![\\p{L}\\d.,])\\d+(?:[.,]\\d+)*"
|
|
30
|
+
export const UNIT =
|
|
31
|
+
"(?:%|\\u2030|\\u00D7|\\uD37C\\uC13C\\uD2B8|\\uB2EC\\uB7EC|\\uC720\\uB85C|\\uC5D4|\\uC6D0|\\uB9CC|\\uC5B5|\\uC870|\\uCC9C|\\uCD08|\\uBD84|\\uC2DC\\uAC04|\\uC77C|\\uC8FC|\\uAC1C\\uC6D4|\\uB144|\\uBA85|\\uAC74|\\uAC1C|\\uD68C|\\uBC88|\\uBC30" +
|
|
32
|
+
"|(?:percent|pp|bps?|x|k|m|b|bn|million|billion|thousand|trillion|usd|krw|eur|ms|s|secs?|seconds?|mins?|minutes?|h|hrs?|hours?|days?|weeks?|months?|years?|kb|mb|gb|tb|users?|people|employees|engineers|tokens?|requests?|stars?|downloads?|customers?)(?![\\p{L}\\d]))"
|
|
33
|
+
export const CLAIMS = [
|
|
34
|
+
new RegExp(`[$\\u20AC\\u00A3\\u00A5\\u20A9]\\s?${NUM}`, "u"),
|
|
35
|
+
new RegExp(`${NUM}\\s?${UNIT}`, "iu"),
|
|
36
|
+
/(?<![\d.,])\d{1,3}(?:,\d{3})+(?![\d,])/,
|
|
37
|
+
/(?<![\d.])\d+\.\d+(?![\d.])/,
|
|
38
|
+
]
|
|
39
|
+
|
|
40
|
+
const WORDS = ["one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten", "eleven", "twelve"]
|
|
41
|
+
const WORD_RE = new RegExp(`\\b(?:${WORDS.join("|")})\\b`, "gi")
|
|
42
|
+
/** Korean scale words: cheon (1e3) inside a group, then man, eok, jo as group scales. */
|
|
43
|
+
const CHEON = "\uCC9C"
|
|
44
|
+
const BIG = new Map([["\uB9CC", 10000n], ["\uC5B5", 100000000n], ["\uC870", 1000000000000n]])
|
|
45
|
+
/** A scale word that starts a longer word is not a scale: jogak (pieces), mankeum (as much as). */
|
|
46
|
+
const KO_GROUP = /^(\s?)(?:(\uCC9C)?([\uB9CC\uC5B5\uC870])|(\uCC9C))(?![\uAC01\uD07C])/
|
|
47
|
+
const KO_NEXT = new RegExp(`^\\s?(${NUM})(\uCC9C)?([\uB9CC\uC5B5\uC870])?`, "u")
|
|
48
|
+
const LATIN_SCALE = /^(?:(k|K|M|bn|B)|\s(thousand|million|billion|trillion|bn))(?![\p{L}\d])/u
|
|
49
|
+
const LATIN_FACTOR = { k: 1000n, K: 1000n, M: 1000000n, bn: 1000000000n, B: 1000000000n, thousand: 1000n, million: 1000000n, billion: 1000000000n, trillion: 1000000000000n }
|
|
50
|
+
const CURRENCY = "$\u20AC\u00A3\u00A5\u20A9"
|
|
51
|
+
const SYMBOL_UNIT = /^\s?(%p|%|\u2030|\u00D7|\u00B0C|\u00B0F|\u2103|\u2109)/
|
|
52
|
+
const LATIN_UNIT = /^\s?([A-Za-z]+)(?![\p{L}\d])/u
|
|
53
|
+
const HANGUL_UNIT = /^\s?([\uAC00-\uD7A3]+)/
|
|
54
|
+
/** "3 bun-ui 1" is the fraction one third, not 3 minutes. */
|
|
55
|
+
const FRACTION = /^\s?\uBD84\uC758\s?\d/
|
|
56
|
+
const UNIT_AFTER = new RegExp(`^\\s?${UNIT}`, "iu")
|
|
57
|
+
const NUM_AT = new RegExp(`^${NUM}`, "u")
|
|
58
|
+
/** Range separators: hyphen, en dash, tilde, "to". */
|
|
59
|
+
const RANGE_SEP = /^(?:\s?[-\u2013~]\s?|\sto\s)/
|
|
60
|
+
const SIGN = "-\u2212"
|
|
61
|
+
/** Bounding qualifiers: before the value (Korean and English) and right after it (Korean). */
|
|
62
|
+
const QUALIFIERS_BEFORE = ["up to", "at least", "at most", "more than", "less than", "about", "approximately", "nearly", "over", "under", "\uCD5C\uB300", "\uCD5C\uC18C", "\uC57D"]
|
|
63
|
+
const QUALIFIER_AFTER = /^\s?(\uC774\uC0C1|\uC774\uD558|\uBBF8\uB9CC|\uCD08\uACFC|\uB0B4\uC678|\uAE4C\uC9C0)/
|
|
64
|
+
|
|
65
|
+
/** "1,237" -> "1237"; a malformed group ("1,23") keeps only the leading plain number. */
|
|
66
|
+
function cleanDigits(raw) {
|
|
67
|
+
if (/^\d{1,3}(?:,\d{3})+(?:\.\d+)?$/.test(raw)) return { digits: raw.replace(/,/g, ""), length: raw.length }
|
|
68
|
+
const plain = /^\d+(?:\.\d+)?/.exec(raw)[0]
|
|
69
|
+
return { digits: plain, length: plain.length }
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** The bare-year and four-digit-date patterns: a numeral they cover can still be a value. */
|
|
73
|
+
const YEAR_SHAPED = new Set([NOT_CLAIMS[0], NOT_CLAIMS[12]])
|
|
74
|
+
|
|
75
|
+
function excludedRanges(text) {
|
|
76
|
+
const ranges = []
|
|
77
|
+
for (const re of NOT_CLAIMS) {
|
|
78
|
+
for (const match of text.matchAll(re)) ranges.push([match.index, match.index + match[0].length, YEAR_SHAPED.has(re)])
|
|
79
|
+
}
|
|
80
|
+
return ranges
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** A year-shaped numeral is a value when it has a decimal part or a currency, scale word or unit touches it. */
|
|
84
|
+
function anchored(text, at, end, digits) {
|
|
85
|
+
if (digits.includes(".") || (at > 0 && CURRENCY.includes(text[at - 1]))) return true
|
|
86
|
+
return UNIT_AFTER.test(text.slice(end)) || unitAfter(text, end) !== null
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* First digit run or number word at or after `from` that no NOT_CLAIMS pattern covers. A year-shaped
|
|
91
|
+
* match exactly over the numeral (not "2020s", not "2020-03") is lifted when the numeral is anchored.
|
|
92
|
+
*/
|
|
93
|
+
function findStart(text, from, ranges) {
|
|
94
|
+
const covering = (at) => ranges.filter(([s, e]) => at >= s && at < e)
|
|
95
|
+
let digit = null
|
|
96
|
+
for (const match of text.slice(from).matchAll(new RegExp(NUM, "gu"))) {
|
|
97
|
+
const at = from + match.index
|
|
98
|
+
const candidate = { at, ...cleanDigits(match[0]) }
|
|
99
|
+
const end = at + candidate.length
|
|
100
|
+
const cover = covering(at)
|
|
101
|
+
const lifted = cover.every(([s, e, yearShaped]) => yearShaped && s === at && e <= end)
|
|
102
|
+
if (cover.length === 0 || (lifted && anchored(text, at, end, candidate.digits))) {
|
|
103
|
+
digit = candidate
|
|
104
|
+
break
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
const free = (at) => covering(at).length === 0
|
|
108
|
+
for (const match of text.slice(from).matchAll(WORD_RE)) {
|
|
109
|
+
const at = from + match.index
|
|
110
|
+
if (digit && digit.at < at) break
|
|
111
|
+
if (free(at)) return { at, digits: String(WORDS.indexOf(match[0].toLowerCase()) + 1), length: match[0].length }
|
|
112
|
+
}
|
|
113
|
+
return digit
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** Korean groups such as "4jo 2,000eok": each later group needs a smaller scale. */
|
|
117
|
+
function koreanScale(text, at, digits) {
|
|
118
|
+
const first = KO_GROUP.exec(text.slice(at))
|
|
119
|
+
if (!first) return null
|
|
120
|
+
const big = first[3] ? BIG.get(first[3]) : 1n
|
|
121
|
+
const firstFactor = (first[2] || first[4] ? 1000n : 1n) * big
|
|
122
|
+
let value = mul(fromDecimal(digits), firstFactor)
|
|
123
|
+
let end = at + first[0].length
|
|
124
|
+
let last = first[3] ? big : 0n
|
|
125
|
+
let groups = 1
|
|
126
|
+
while (last > 1n) {
|
|
127
|
+
const next = KO_NEXT.exec(text.slice(end))
|
|
128
|
+
if (!next) break
|
|
129
|
+
const factor = (next[2] ? 1000n : 1n) * (next[3] ? BIG.get(next[3]) : 1n)
|
|
130
|
+
if (factor <= 1n || factor >= last) break
|
|
131
|
+
value = add(value, mul(fromDecimal(cleanDigits(next[1]).digits), factor))
|
|
132
|
+
end += next[0].length
|
|
133
|
+
last = next[3] ? BIG.get(next[3]) : 0n
|
|
134
|
+
groups++
|
|
135
|
+
}
|
|
136
|
+
return { value, end, factor: groups === 1 ? firstFactor : null }
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/** Scale words after a numeral: { value (rational) | null, end, factor | null (one plain factor) }. */
|
|
140
|
+
function scaled(text, end, digits) {
|
|
141
|
+
const korean = koreanScale(text, end, digits)
|
|
142
|
+
if (korean) return korean
|
|
143
|
+
const latin = LATIN_SCALE.exec(text.slice(end))
|
|
144
|
+
if (!latin) return { value: null, end, factor: null }
|
|
145
|
+
const factor = LATIN_FACTOR[latin[1] ?? latin[2].toLowerCase()]
|
|
146
|
+
return { value: mul(fromDecimal(digits), factor), end: end + latin[0].length, factor }
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/** The second endpoint of "5-10", "5 to 10", "5~10" or "5\u201310", or null. */
|
|
150
|
+
function rangeEnd(text, at, ranges) {
|
|
151
|
+
const sep = RANGE_SEP.exec(text.slice(at))
|
|
152
|
+
if (!sep) return null
|
|
153
|
+
const pos = at + sep[0].length
|
|
154
|
+
const num = NUM_AT.exec(text.slice(pos))
|
|
155
|
+
if (!num || ranges.some(([s, e]) => pos >= s && pos < e)) return null
|
|
156
|
+
const { digits, length } = cleanDigits(num[0])
|
|
157
|
+
const scale = scaled(text, pos + length, digits)
|
|
158
|
+
return { value: scale.value ? toDecimal(scale.value) : digits, end: scale.end, factor: scale.factor }
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function unitAfter(text, at) {
|
|
162
|
+
const rest = text.slice(at)
|
|
163
|
+
if (FRACTION.test(rest)) return null
|
|
164
|
+
const symbol = SYMBOL_UNIT.exec(rest)
|
|
165
|
+
if (symbol) return { unitRaw: symbol[1], end: at + symbol[0].length }
|
|
166
|
+
const latin = LATIN_UNIT.exec(rest)
|
|
167
|
+
if (latin) return normalizeUnit(latin[1]) ? { unitRaw: latin[1], end: at + latin[0].length } : null
|
|
168
|
+
const hangul = HANGUL_UNIT.exec(rest)
|
|
169
|
+
if (!hangul) return null
|
|
170
|
+
const lead = hangul[0].length - hangul[1].length
|
|
171
|
+
for (let size = hangul[1].length; size > 0; size--) {
|
|
172
|
+
const candidate = hangul[1].slice(0, size)
|
|
173
|
+
if (!BIG.has(candidate) && candidate !== CHEON && normalizeUnit(candidate)) return { unitRaw: candidate, end: at + lead + size }
|
|
174
|
+
}
|
|
175
|
+
return null
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function qualifierBefore(text, at) {
|
|
179
|
+
const tokens = text.slice(0, at).trim().split(/\s+/).slice(-2).map((token) => token.replace(/^[^\p{L}]+/u, ""))
|
|
180
|
+
for (const phrase of QUALIFIERS_BEFORE) {
|
|
181
|
+
const words = phrase.split(" ")
|
|
182
|
+
for (let skip = 0; skip + words.length <= tokens.length; skip++) {
|
|
183
|
+
const window = tokens.slice(tokens.length - skip - words.length, tokens.length - skip)
|
|
184
|
+
if (window.join(" ").toLowerCase() === phrase) return window.join(" ")
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
return null
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* The first numeral at or after `from`: { value, raw, span: [start, end], unitRaw, qualifierRaw },
|
|
192
|
+
* or null. `value` is an exact decimal string with scale words folded in ("3eok won" -> "300000000",
|
|
193
|
+
* unit "won") and a leading minus sign ("-" or U+2212) kept as "-"; `span` covers the sign, an
|
|
194
|
+
* adjacent currency symbol, the numeral, scale words and the unit. A range ("5-10 m", "5 to 10 m",
|
|
195
|
+
* "5~10%") adds `valueEnd` and `range: true`; the unit, and a scale word written only on the second
|
|
196
|
+
* endpoint ("3~5eok"), apply to both endpoints.
|
|
197
|
+
*/
|
|
198
|
+
export function parseNumeral(text, from = 0) {
|
|
199
|
+
const ranges = excludedRanges(text)
|
|
200
|
+
const found = findStart(text, from, ranges)
|
|
201
|
+
if (!found) return null
|
|
202
|
+
let start = found.at
|
|
203
|
+
const first = scaled(text, found.at + found.length, found.digits)
|
|
204
|
+
let value = first.value
|
|
205
|
+
let end = first.end
|
|
206
|
+
const second = rangeEnd(text, end, ranges)
|
|
207
|
+
if (second) {
|
|
208
|
+
end = second.end
|
|
209
|
+
if (!value && second.factor) value = mul(fromDecimal(found.digits), second.factor)
|
|
210
|
+
}
|
|
211
|
+
let unitRaw = null
|
|
212
|
+
if (start > 0 && CURRENCY.includes(text[start - 1])) [unitRaw, start] = [text[start - 1], start - 1]
|
|
213
|
+
const negative = start > 0 && SIGN.includes(text[start - 1]) && !/[\p{L}\p{N}]/u.test(text[start - 2] ?? "")
|
|
214
|
+
if (negative) start--
|
|
215
|
+
const unit = unitRaw ? null : unitAfter(text, end)
|
|
216
|
+
if (unit) [unitRaw, end] = [unit.unitRaw, unit.end]
|
|
217
|
+
const after = QUALIFIER_AFTER.exec(text.slice(end))
|
|
218
|
+
return {
|
|
219
|
+
value: (negative ? "-" : "") + (value ? toDecimal(value) : found.digits),
|
|
220
|
+
...(second ? { valueEnd: second.value, range: true } : {}),
|
|
221
|
+
raw: text.slice(start, end),
|
|
222
|
+
span: [start, end],
|
|
223
|
+
unitRaw,
|
|
224
|
+
qualifierRaw: qualifierBefore(text, start) ?? (after ? after[1] : null),
|
|
225
|
+
}
|
|
226
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test"
|
|
2
|
+
|
|
3
|
+
import { CLAIMS, NOT_CLAIMS, NUM, parseNumeral, UNIT } from "./numerals.mjs"
|
|
4
|
+
import { normalizeUnit } from "./units.mjs"
|
|
5
|
+
|
|
6
|
+
describe("shared claim patterns", () => {
|
|
7
|
+
test("exports the gate patterns", () => {
|
|
8
|
+
expect(typeof NUM).toBe("string")
|
|
9
|
+
expect(typeof UNIT).toBe("string")
|
|
10
|
+
expect(NOT_CLAIMS.length).toBe(13)
|
|
11
|
+
expect(CLAIMS.length).toBe(4)
|
|
12
|
+
})
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
describe("parseNumeral", () => {
|
|
16
|
+
test("a length with its unit", () => {
|
|
17
|
+
expect(parseNumeral("320 m")).toEqual({ value: "320", raw: "320 m", span: [0, 5], unitRaw: "m", qualifierRaw: null })
|
|
18
|
+
expect(parseNumeral("320 mm")?.unitRaw).toBe("mm")
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
test("thousands separators", () => {
|
|
22
|
+
const parsed = parseNumeral("we shipped 1,237 units")
|
|
23
|
+
expect(parsed?.value).toBe("1237")
|
|
24
|
+
expect(parsed?.span).toEqual([11, 16])
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
test("Korean scale words fold into the value and leave the currency unit", () => {
|
|
28
|
+
const parsed = parseNumeral("3억 원")
|
|
29
|
+
expect(parsed?.value).toBe("300000000")
|
|
30
|
+
expect(parsed?.raw).toBe("3억 원")
|
|
31
|
+
expect(normalizeUnit(parsed?.unitRaw as string)).toBe(normalizeUnit("원"))
|
|
32
|
+
expect(parseNumeral("4조 2,000억 원")?.value).toBe("4200000000000")
|
|
33
|
+
expect(parseNumeral("3천만 원")?.value).toBe("30000000")
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
test("Latin suffixes and scale words", () => {
|
|
37
|
+
expect(parseNumeral("1.2k")?.value).toBe("1200")
|
|
38
|
+
expect(parseNumeral("$3M")).toMatchObject({ value: "3000000", raw: "$3M", unitRaw: "$" })
|
|
39
|
+
expect(parseNumeral("2.5bn")?.value).toBe("2500000000")
|
|
40
|
+
expect(parseNumeral("$4B")?.value).toBe("4000000000")
|
|
41
|
+
expect(parseNumeral("3 million users")?.value).toBe("3000000")
|
|
42
|
+
expect(parseNumeral("5 km")).toMatchObject({ value: "5", unitRaw: "km" })
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
test("percent and percentage points stay distinct", () => {
|
|
46
|
+
expect(parseNumeral("15.2%")).toMatchObject({ value: "15.2", unitRaw: "%" })
|
|
47
|
+
expect(parseNumeral("2%p")).toMatchObject({ value: "2", unitRaw: "%p" })
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
test("long decimals keep every digit", () => {
|
|
51
|
+
expect(parseNumeral("10000000000000000.1")?.value).toBe("10000000000000000.1")
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
test("English number words one to twelve only", () => {
|
|
55
|
+
expect(parseNumeral("three engineers")?.value).toBe("3")
|
|
56
|
+
expect(parseNumeral("Twelve")?.value).toBe("12")
|
|
57
|
+
expect(parseNumeral("thirteen")).toBeNull()
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
test("qualifiers before and after the value", () => {
|
|
61
|
+
expect(parseNumeral("최대 4조 2,000억 원")).toMatchObject({ value: "4200000000000", qualifierRaw: "최대", span: [3, 14] })
|
|
62
|
+
expect(parseNumeral("up to 320 m")?.qualifierRaw).toBe("up to")
|
|
63
|
+
expect(parseNumeral("320 m 이상")?.qualifierRaw).toBe("이상")
|
|
64
|
+
expect(parseNumeral("the 320 m mast")?.qualifierRaw).toBeNull()
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
test("dates, versions and figure references are not numerals", () => {
|
|
68
|
+
expect(parseNumeral("v2.3.1")).toBeNull()
|
|
69
|
+
expect(parseNumeral("see figure 3")).toBeNull()
|
|
70
|
+
expect(parseNumeral("2026-10-23")).toBeNull()
|
|
71
|
+
expect(parseNumeral("v2.3.1 shipped 40 fixes")?.value).toBe("40")
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
test("year-shaped values next to a currency, scale word or unit, or with a decimal part, are numerals", () => {
|
|
75
|
+
expect(parseNumeral("2000억 원")).toMatchObject({ value: "200000000000", unitRaw: "원" })
|
|
76
|
+
expect(parseNumeral("1900만 원")).toMatchObject({ value: "19000000", unitRaw: "원" })
|
|
77
|
+
expect(parseNumeral("2050명")).toMatchObject({ value: "2050", unitRaw: "명" })
|
|
78
|
+
expect(parseNumeral("$2000")).toMatchObject({ value: "2000", raw: "$2000", unitRaw: "$" })
|
|
79
|
+
expect(parseNumeral("1999 users")?.value).toBe("1999")
|
|
80
|
+
expect(parseNumeral("1234.56 m")).toMatchObject({ value: "1234.56", unitRaw: "m" })
|
|
81
|
+
expect(parseNumeral("$1500.25")).toMatchObject({ value: "1500.25", unitRaw: "$" })
|
|
82
|
+
expect(parseNumeral("2020.5억")?.value).toBe("202050000000")
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
test("bare years and dates stay excluded", () => {
|
|
86
|
+
expect(parseNumeral("in 2020")).toBeNull()
|
|
87
|
+
expect(parseNumeral("2020-03-01")).toBeNull()
|
|
88
|
+
expect(parseNumeral("the 2020s")).toBeNull()
|
|
89
|
+
expect(parseNumeral("in 2020, 5 users")?.value).toBe("5")
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
test("a leading minus sign is part of the value and the span", () => {
|
|
93
|
+
expect(parseNumeral("-5%")).toMatchObject({ value: "-5", raw: "-5%", span: [0, 3], unitRaw: "%" })
|
|
94
|
+
expect(parseNumeral("fell \u22123.2%")).toMatchObject({ value: "-3.2", span: [5, 10], unitRaw: "%" })
|
|
95
|
+
expect(parseNumeral("COVID-19 cases")?.value).toBe("19")
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
test("ranges carry both endpoints and share the unit", () => {
|
|
99
|
+
expect(parseNumeral("5-10 m")).toMatchObject({ value: "5", valueEnd: "10", range: true, raw: "5-10 m", span: [0, 6], unitRaw: "m" })
|
|
100
|
+
expect(parseNumeral("5 to 10 m")).toMatchObject({ value: "5", valueEnd: "10", range: true, raw: "5 to 10 m", unitRaw: "m" })
|
|
101
|
+
expect(parseNumeral("5~10%")).toMatchObject({ value: "5", valueEnd: "10", range: true, unitRaw: "%" })
|
|
102
|
+
expect(parseNumeral("5\u201310%")).toMatchObject({ value: "5", valueEnd: "10", range: true, unitRaw: "%" })
|
|
103
|
+
expect(parseNumeral("3~5억 원")).toMatchObject({ value: "300000000", valueEnd: "500000000", range: true, unitRaw: "원" })
|
|
104
|
+
expect(parseNumeral("320 m")).not.toHaveProperty("valueEnd")
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
test("Korean scale words and units need a word boundary", () => {
|
|
108
|
+
expect(parseNumeral("5조각")).toMatchObject({ value: "5", raw: "5", unitRaw: null })
|
|
109
|
+
expect(parseNumeral("3분의 1")).toMatchObject({ value: "3", raw: "3", unitRaw: null })
|
|
110
|
+
expect(parseNumeral("3분의 휴식")?.unitRaw).toBe("분")
|
|
111
|
+
expect(parseNumeral("매출 10억은")?.value).toBe("1000000000")
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
test("from skips earlier numerals", () => {
|
|
115
|
+
const text = "320 m and 15%"
|
|
116
|
+
const first = parseNumeral(text)
|
|
117
|
+
expect(parseNumeral(text, first?.span[1])).toMatchObject({ value: "15", unitRaw: "%" })
|
|
118
|
+
expect(parseNumeral("no numbers here")).toBeNull()
|
|
119
|
+
})
|
|
120
|
+
})
|
|
@@ -1,12 +1,10 @@
|
|
|
1
|
-
// outcome.mjs - the per-run outcome manifest (`outcome.json`) and the closing briefing.
|
|
2
|
-
//
|
|
3
|
-
// manifest, the sources-ledger text, and an injected `now`. node: builtins only.
|
|
1
|
+
// outcome.mjs - the per-run outcome manifest (`outcome.json`) and the closing briefing. Manifest functions are pure (they return a new manifest).
|
|
2
|
+
// The briefing reads only the manifest, the sources-ledger text, and an injected `now`. node: builtins only.
|
|
4
3
|
|
|
5
4
|
import * as nodeFs from "node:fs"
|
|
6
5
|
import { basename, dirname, join } from "node:path"
|
|
7
6
|
|
|
8
|
-
import { DELIVERABLE_FORMATS, DELIVERABLE_STATUSES, GATE_NAMES, GATE_STATUSES, LANES } from "./contracts.mjs"
|
|
9
|
-
import { MANIFEST_CODES, SCHEMA_VERSION, validateOutcomeManifest } from "./contracts.mjs"
|
|
7
|
+
import { DELIVERABLE_FORMATS, DELIVERABLE_STATUSES, GATE_NAMES, GATE_STATUSES, LANES, MANIFEST_CODES, normalizeManifest, SCHEMA_VERSION, validateOutcomeManifest } from "./contracts.mjs"
|
|
10
8
|
|
|
11
9
|
export const MANIFEST_FILENAME = "outcome.json"
|
|
12
10
|
const MISSING = MANIFEST_CODES[0]
|
|
@@ -87,7 +85,7 @@ export function setDeliverable(manifest, format, status, detail = {}) {
|
|
|
87
85
|
const next = clone(manifest)
|
|
88
86
|
const index = next.deliverables.findIndex((row) => row.format === format)
|
|
89
87
|
const row = { format, status, promised: index >= 0 ? next.deliverables[index].promised : false, updatedAt: stamp(detail.now) }
|
|
90
|
-
for (const key of ["path", "reason", "pages", "bytes"]) {
|
|
88
|
+
for (const key of ["path", "reason", "pages", "bytes", "verifiedBy", "renderedFrom"]) {
|
|
91
89
|
if (detail[key] !== undefined) row[key] = detail[key]
|
|
92
90
|
}
|
|
93
91
|
if (index >= 0) next.deliverables[index] = row
|
|
@@ -95,11 +93,13 @@ export function setDeliverable(manifest, format, status, detail = {}) {
|
|
|
95
93
|
return next
|
|
96
94
|
}
|
|
97
95
|
|
|
98
|
-
export function setGate(manifest, gate, status) {
|
|
96
|
+
export function setGate(manifest, gate, status, detail = {}) {
|
|
99
97
|
assertOneOf("unknown_gate", "gate", gate, GATE_NAMES)
|
|
100
98
|
assertOneOf("unknown_gate_status", "gate status", status, GATE_STATUSES)
|
|
99
|
+
if (gate === "fidelity" && status === "not_run" && (typeof detail.reason !== "string" || !detail.reason.trim())) throw new OutcomeError("gate_reason_required", "fidelity not_run requires a non-empty --reason")
|
|
101
100
|
const next = clone(manifest)
|
|
102
101
|
next.gates[gate] = status
|
|
102
|
+
next.gateHistory = [...(next.gateHistory ?? []), { gate, status, ...(detail.reason !== undefined ? { reason: detail.reason.trim() } : {}), at: stamp(detail.now) }]
|
|
103
103
|
return next
|
|
104
104
|
}
|
|
105
105
|
|
|
@@ -226,7 +226,7 @@ function assertValid(manifest, where) {
|
|
|
226
226
|
export function readManifest(file) {
|
|
227
227
|
let manifest
|
|
228
228
|
try {
|
|
229
|
-
manifest = JSON.parse(nodeFs.readFileSync(file, "utf8"))
|
|
229
|
+
manifest = normalizeManifest(JSON.parse(nodeFs.readFileSync(file, "utf8")))
|
|
230
230
|
} catch (error) {
|
|
231
231
|
if (error instanceof SyntaxError) throw new OutcomeError("invalid_manifest", `${file}: not valid JSON (${error.message})`)
|
|
232
232
|
throw error
|
|
@@ -258,6 +258,35 @@ describe("readManifest and writeManifest", () => {
|
|
|
258
258
|
writeFileSync(file, "{not json")
|
|
259
259
|
expect(() => readManifest(file)).toThrow(OutcomeError)
|
|
260
260
|
})
|
|
261
|
+
|
|
262
|
+
test("#given a manifest file written before the fidelity gate (four gates) #when read and written back #then it loads and the file gains fidelity not_run", () => {
|
|
263
|
+
// given
|
|
264
|
+
const dir = sessionDir()
|
|
265
|
+
const file = join(dir, MANIFEST_FILENAME)
|
|
266
|
+
const old = {
|
|
267
|
+
schemaVersion: 1,
|
|
268
|
+
sessionDir: dir,
|
|
269
|
+
startedAt: START_LOCAL.toISOString(),
|
|
270
|
+
finishedAt: null,
|
|
271
|
+
elapsedMinutes: null,
|
|
272
|
+
lane: "no-format",
|
|
273
|
+
interview: null,
|
|
274
|
+
deliverables: [{ format: "pdf", status: "pending", promised: true, updatedAt: START_LOCAL.toISOString() }],
|
|
275
|
+
renders: [],
|
|
276
|
+
gates: { static: "pass", layout: "not_run", visual: "fail", proofread: "not_run" },
|
|
277
|
+
residualDefects: [],
|
|
278
|
+
repair: null,
|
|
279
|
+
sources: null,
|
|
280
|
+
}
|
|
281
|
+
writeFileSync(file, JSON.stringify(old))
|
|
282
|
+
// when
|
|
283
|
+
const manifest = readManifest(file)
|
|
284
|
+
writeManifest(file, manifest)
|
|
285
|
+
// then
|
|
286
|
+
const written = JSON.parse(readFileSync(file, "utf8"))
|
|
287
|
+
expect(written.gates).toEqual({ static: "pass", fidelity: "not_run", layout: "not_run", visual: "fail", proofread: "not_run" })
|
|
288
|
+
expect(written.schemaVersion).toBe(1)
|
|
289
|
+
})
|
|
261
290
|
})
|
|
262
291
|
|
|
263
292
|
describe("round trip in a stamped session dir", () => {
|