lazycodex-ai 5.1.43 → 5.1.45

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.
Files changed (273) hide show
  1. package/dist/cli/index.js +948 -416
  2. package/dist/cli/service-auth/keystore.d.ts +4 -2
  3. package/dist/cli-node/index.js +948 -416
  4. package/docs/reference/web-terminal-visual-qa.md +8 -0
  5. package/package.json +1 -1
  6. package/packages/lsp-daemon/dist/cli.js +4 -4
  7. package/packages/lsp-daemon/dist/client.js +4 -4
  8. package/packages/lsp-daemon/dist/index.js +4 -4
  9. package/packages/lsp-daemon/package.json +3 -3
  10. package/packages/lsp-tools-mcp/dist/cli.js +4 -4
  11. package/packages/lsp-tools-mcp/dist/mcp.js +4 -4
  12. package/packages/lsp-tools-mcp/dist/tools.js +4 -4
  13. package/packages/lsp-tools-mcp/package.json +3 -3
  14. package/packages/omo-codex/plugin/.codex-plugin/plugin.json +1 -1
  15. package/packages/omo-codex/plugin/components/bootstrap/dist/cli.js +62 -21
  16. package/packages/omo-codex/plugin/components/bootstrap/hooks/hooks.json +1 -1
  17. package/packages/omo-codex/plugin/components/bootstrap/package.json +1 -1
  18. package/packages/omo-codex/plugin/components/bootstrap/src/hook.ts +1 -1
  19. package/packages/omo-codex/plugin/components/comment-checker/biome.json +1 -21
  20. package/packages/omo-codex/plugin/components/comment-checker/hooks/hooks.json +1 -1
  21. package/packages/omo-codex/plugin/components/comment-checker/package.json +4 -4
  22. package/packages/omo-codex/plugin/components/git-bash/hooks/hooks.json +2 -2
  23. package/packages/omo-codex/plugin/components/git-bash/package.json +1 -1
  24. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/biome.json +1 -21
  25. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/hooks/hooks.json +1 -1
  26. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/package.json +4 -4
  27. package/packages/omo-codex/plugin/components/lsp/biome.json +1 -21
  28. package/packages/omo-codex/plugin/components/lsp/dist/.omo-runtime-manifest.json +3 -3
  29. package/packages/omo-codex/plugin/components/lsp/dist/cli.js +4 -4
  30. package/packages/omo-codex/plugin/components/lsp/hooks/hooks.json +2 -2
  31. package/packages/omo-codex/plugin/components/lsp/package.json +4 -4
  32. package/packages/omo-codex/plugin/components/lsp/scripts/build-runtime.mjs +0 -1
  33. package/packages/omo-codex/plugin/components/lsp/test/codex-hook-not-installed.test.ts +1 -1
  34. package/packages/omo-codex/plugin/components/rules/biome.json +1 -21
  35. package/packages/omo-codex/plugin/components/rules/hooks/hooks.json +4 -4
  36. package/packages/omo-codex/plugin/components/rules/package.json +4 -4
  37. package/packages/omo-codex/plugin/components/teammode/hooks/hooks.json +1 -1
  38. package/packages/omo-codex/plugin/components/teammode/package.json +1 -1
  39. package/packages/omo-codex/plugin/components/telemetry/biome.json +1 -21
  40. package/packages/omo-codex/plugin/components/telemetry/dist/cli.js +90 -1
  41. package/packages/omo-codex/plugin/components/telemetry/dist/posthog.js +91 -1
  42. package/packages/omo-codex/plugin/components/telemetry/hooks/hooks.json +1 -1
  43. package/packages/omo-codex/plugin/components/telemetry/package.json +4 -4
  44. package/packages/omo-codex/plugin/components/ultrawork/agents/lazycodex-gate-reviewer.toml +2 -2
  45. package/packages/omo-codex/plugin/components/ultrawork/biome.json +1 -21
  46. package/packages/omo-codex/plugin/components/ultrawork/hooks/hooks.json +1 -1
  47. package/packages/omo-codex/plugin/components/ultrawork/package.json +4 -4
  48. package/packages/omo-codex/plugin/components/ulw-execute-continuation/biome.json +1 -21
  49. package/packages/omo-codex/plugin/components/ulw-execute-continuation/directive.md +1 -1
  50. package/packages/omo-codex/plugin/components/ulw-execute-continuation/hooks/hooks.json +1 -1
  51. package/packages/omo-codex/plugin/components/ulw-execute-continuation/package.json +4 -4
  52. package/packages/omo-codex/plugin/components/ulw-loop/biome.json +1 -21
  53. package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint.js +1 -1
  54. package/packages/omo-codex/plugin/components/ulw-loop/dist/cli.js +7 -7
  55. package/packages/omo-codex/plugin/components/ulw-loop/dist/codex-goal-instruction.js +5 -5
  56. package/packages/omo-codex/plugin/components/ulw-loop/dist/stop-resume-hook.js +1 -1
  57. package/packages/omo-codex/plugin/components/ulw-loop/hooks/hooks.json +5 -5
  58. package/packages/omo-codex/plugin/components/ulw-loop/package.json +4 -4
  59. package/packages/omo-codex/plugin/components/ulw-loop/src/checkpoint.ts +1 -1
  60. package/packages/omo-codex/plugin/components/ulw-loop/src/codex-goal-instruction.ts +5 -5
  61. package/packages/omo-codex/plugin/components/ulw-loop/src/stop-resume-hook.ts +1 -1
  62. package/packages/omo-codex/plugin/components/ulw-loop/test/fixtures/commit-mutations.mjs +2 -0
  63. package/packages/omo-codex/plugin/hooks/post-compact-resetting-git-bash-mcp-reminder.json +1 -1
  64. package/packages/omo-codex/plugin/hooks/post-compact-resetting-lsp-diagnostics-cache.json +1 -1
  65. package/packages/omo-codex/plugin/hooks/post-compact-resetting-project-rule-cache.json +1 -1
  66. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-comments.json +1 -1
  67. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-lsp-diagnostics.json +1 -1
  68. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-thread-title-hygiene.json +1 -1
  69. package/packages/omo-codex/plugin/hooks/post-tool-use-matching-project-rules.json +1 -1
  70. package/packages/omo-codex/plugin/hooks/post-tool-use-recording-spawn-admission.json +1 -1
  71. package/packages/omo-codex/plugin/hooks/pre-tool-use-enforcing-unlimited-goal-budget.json +1 -1
  72. package/packages/omo-codex/plugin/hooks/pre-tool-use-guarding-ulw-loop-spawns.json +1 -1
  73. package/packages/omo-codex/plugin/hooks/pre-tool-use-recommending-git-bash-mcp.json +1 -1
  74. package/packages/omo-codex/plugin/hooks/session-start-checking-auto-update.json +1 -1
  75. package/packages/omo-codex/plugin/hooks/session-start-checking-bootstrap-provisioning.json +1 -1
  76. package/packages/omo-codex/plugin/hooks/session-start-loading-project-rules.json +1 -1
  77. package/packages/omo-codex/plugin/hooks/session-start-recording-session-telemetry.json +1 -1
  78. package/packages/omo-codex/plugin/hooks/stop-checking-ulw-execute-continuation.json +1 -1
  79. package/packages/omo-codex/plugin/hooks/stop-checking-ulw-loop-resume.json +1 -1
  80. package/packages/omo-codex/plugin/hooks/subagent-stop-verifying-lazycodex-executor-evidence.json +1 -1
  81. package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ultrawork-trigger.json +1 -1
  82. package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ulw-loop-steering.json +1 -1
  83. package/packages/omo-codex/plugin/hooks/user-prompt-submit-loading-project-rules.json +1 -1
  84. package/packages/omo-codex/plugin/package-lock.json +12 -12
  85. package/packages/omo-codex/plugin/package.json +1 -1
  86. package/packages/omo-codex/plugin/scripts/sync-skills.mjs +7 -7
  87. package/packages/omo-codex/plugin/shared/test/config-loader.test.ts +2 -0
  88. package/packages/omo-codex/plugin/skills/browser/SKILL.md +21 -22
  89. package/packages/omo-codex/plugin/skills/browser/references/commands.md +2 -9
  90. package/packages/omo-codex/plugin/skills/browser/references/install.md +1 -2
  91. package/packages/omo-codex/plugin/skills/browser/runtime/omowright/manifest.json +1 -1
  92. package/packages/omo-codex/plugin/skills/browser/scripts/browser-doctor.mjs +2 -2
  93. package/packages/omo-codex/plugin/skills/browser/scripts/browser-engine-guard.mjs +63 -487
  94. package/packages/omo-codex/plugin/skills/browser/scripts/browser-install.mjs +1 -1
  95. package/packages/omo-codex/plugin/skills/browser/scripts/omowright.mjs +1 -10
  96. package/packages/omo-codex/plugin/skills/frontend/SKILL.md +79 -118
  97. package/packages/omo-codex/plugin/skills/frontend/references/design/README.md +75 -224
  98. package/packages/omo-codex/plugin/skills/frontend/references/design/stylegallery.md +32 -27
  99. package/packages/omo-codex/plugin/skills/frontend/references/perfection/README.md +2 -2
  100. package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/check-accent-stripes.ts +8 -24
  101. package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/check-draft-guard.ts +40 -0
  102. package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/collect-files.ts +53 -0
  103. package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/draft-guard/findings.ts +16 -0
  104. package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/draft-guard/lint.ts +38 -0
  105. package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/draft-guard/rules.ts +69 -0
  106. package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/draft-guard/text-nodes.ts +121 -0
  107. package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/fixtures/draft-guard/negative/attr-lookalikes.tsx +10 -0
  108. package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/fixtures/draft-guard/negative/bound-values.tsx +12 -0
  109. package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/fixtures/draft-guard/negative/const-generics.tsx +3 -0
  110. package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/fixtures/draft-guard/negative/generics.tsx +7 -0
  111. package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/fixtures/draft-guard/negative/measured-stats.html +6 -0
  112. package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/fixtures/draft-guard/negative/plain-copy.html +9 -0
  113. package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/fixtures/draft-guard/negative/state-copy.html +11 -0
  114. package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/fixtures/draft-guard/positive/attribute-edges.html +7 -0
  115. package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/fixtures/draft-guard/positive/components.tsx +16 -0
  116. package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/fixtures/draft-guard/positive/decorative-badge.vue +7 -0
  117. package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/fixtures/draft-guard/positive/emoji-icons.tsx +9 -0
  118. package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/fixtures/draft-guard/positive/jsx-edges.tsx +1 -0
  119. package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/fixtures/draft-guard/positive/promo-copy.html +6 -0
  120. package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/fixtures/draft-guard/positive/promo-copy.tsx +8 -0
  121. package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/fixtures/draft-guard/positive/vanity-stats.html +5 -0
  122. package/packages/omo-codex/plugin/skills/init-deep/SKILL.md +1 -1
  123. package/packages/omo-codex/plugin/skills/programming/SKILL.md +10 -7
  124. package/packages/omo-codex/plugin/skills/programming/references/typescript/README.md +10 -7
  125. package/packages/omo-codex/plugin/skills/programming/references/typescript/bootstrap.md +9 -23
  126. package/packages/omo-codex/plugin/skills/programming/references/typescript/oxc-lint.md +130 -0
  127. package/packages/omo-codex/plugin/skills/programming/references/typescript/tsconfig-strict.md +6 -62
  128. package/packages/omo-codex/plugin/skills/programming/scripts/typescript/check-no-excuse-rules.ts +12 -12
  129. package/packages/omo-codex/plugin/skills/programming/scripts/typescript/new-project.ts +20 -81
  130. package/packages/omo-codex/plugin/skills/programming/scripts/typescript/oxc/.oxfmtrc.json +9 -0
  131. package/packages/omo-codex/plugin/skills/programming/scripts/typescript/oxc/.oxlintrc.json +46 -0
  132. package/packages/omo-codex/plugin/skills/programming/scripts/typescript/oxc/lint/oxlint-plugin/LICENSE +21 -0
  133. package/packages/omo-codex/plugin/skills/programming/scripts/typescript/oxc/lint/oxlint-plugin/index.js +10 -0
  134. package/packages/omo-codex/plugin/skills/programming/scripts/typescript/oxc/lint/oxlint-plugin/no-em-dash-copy.js +28 -0
  135. package/packages/omo-codex/plugin/skills/programming/scripts/typescript/oxc/lint/oxlint-plugin/require-suppression-reason.js +62 -0
  136. package/packages/omo-codex/plugin/skills/programming/scripts/typescript/oxc/tsconfig.json +29 -0
  137. package/packages/omo-codex/plugin/skills/refactor/SKILL.md +14 -14
  138. package/packages/omo-codex/plugin/skills/remove-ai-slops/SKILL.md +1 -1
  139. package/packages/omo-codex/plugin/skills/review-work/SKILL.md +2 -2
  140. package/packages/omo-codex/plugin/skills/ulw-execute/SKILL.md +2 -2
  141. package/packages/omo-codex/plugin/skills/ulw-loop/SKILL.md +1 -1
  142. package/packages/omo-codex/plugin/skills/ulw-research/AGENTS.md +9 -3
  143. package/packages/omo-codex/plugin/skills/ulw-research/SKILL.md +2 -2
  144. package/packages/omo-codex/plugin/skills/ulw-research/references/deliverable-phase.md +7 -6
  145. package/packages/omo-codex/plugin/skills/ulw-research/references/fact-ledger-grammar.md +116 -0
  146. package/packages/omo-codex/plugin/skills/ulw-research/references/report-gates.md +51 -3
  147. package/packages/omo-codex/plugin/skills/ulw-research/scripts/claims-index.mjs +125 -0
  148. package/packages/omo-codex/plugin/skills/ulw-research/scripts/contracts.mjs +68 -3
  149. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-ledger-kinds.mjs +136 -0
  150. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-ledger-schema.mjs +204 -0
  151. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-ledger.mjs +103 -0
  152. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-brief.mjs +145 -0
  153. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-state.mjs +63 -0
  154. package/packages/omo-codex/plugin/skills/ulw-research/scripts/gates-static.mjs +1 -1
  155. package/packages/omo-codex/plugin/skills/ulw-research/scripts/gates-structure.mjs +1 -1
  156. package/packages/omo-codex/plugin/skills/ulw-research/scripts/gates-text.mjs +1 -27
  157. package/packages/omo-codex/plugin/skills/ulw-research/scripts/lexicon.mjs +47 -0
  158. package/packages/omo-codex/plugin/skills/ulw-research/scripts/numerals.mjs +226 -0
  159. package/packages/omo-codex/plugin/skills/ulw-research/scripts/outcome.mjs +2 -3
  160. package/packages/omo-codex/plugin/skills/ulw-research/scripts/particles-ko.mjs +108 -0
  161. package/packages/omo-codex/plugin/skills/ulw-research/scripts/rational.mjs +139 -0
  162. package/packages/omo-codex/plugin/skills/ulw-research/scripts/report-tools-commands.mjs +2 -2
  163. package/packages/omo-codex/plugin/skills/ulw-research/scripts/sentence-split.mjs +77 -0
  164. package/packages/omo-codex/plugin/skills/ulw-research/scripts/text-normalize.mjs +82 -0
  165. package/packages/omo-codex/plugin/skills/ulw-research/scripts/text-similarity.mjs +28 -0
  166. package/packages/omo-codex/plugin/skills/ulw-research/scripts/units.mjs +105 -0
  167. package/packages/omo-codex/plugin/skills/visual-qa/SKILL.md +71 -64
  168. package/packages/omo-codex/plugin/test/component-bundled-cli.test.mjs +4 -1
  169. package/packages/omo-codex/plugin/test/sync-skills-test-support.mjs +6 -6
  170. package/packages/omo-codex/scripts/install-dist/install-local.mjs +152 -23
  171. package/packages/shared-skills/skills/browser/SKILL.md +21 -22
  172. package/packages/shared-skills/skills/browser/references/commands.md +2 -9
  173. package/packages/shared-skills/skills/browser/references/install.md +1 -2
  174. package/packages/shared-skills/skills/browser/runtime/omowright/manifest.json +1 -1
  175. package/packages/shared-skills/skills/browser/scripts/browser-doctor.mjs +2 -2
  176. package/packages/shared-skills/skills/browser/scripts/browser-engine-guard.mjs +63 -487
  177. package/packages/shared-skills/skills/browser/scripts/browser-install.mjs +1 -1
  178. package/packages/shared-skills/skills/browser/scripts/omowright.mjs +1 -10
  179. package/packages/shared-skills/skills/frontend/SKILL.md +79 -118
  180. package/packages/shared-skills/skills/frontend/references/design/README.md +75 -224
  181. package/packages/shared-skills/skills/frontend/references/design/stylegallery.md +32 -27
  182. package/packages/shared-skills/skills/frontend/references/perfection/README.md +2 -2
  183. package/packages/shared-skills/skills/frontend/scripts/perfection/check-accent-stripes.ts +8 -24
  184. package/packages/shared-skills/skills/frontend/scripts/perfection/check-draft-guard.test.ts +101 -0
  185. package/packages/shared-skills/skills/frontend/scripts/perfection/check-draft-guard.ts +40 -0
  186. package/packages/shared-skills/skills/frontend/scripts/perfection/collect-files.test.ts +50 -0
  187. package/packages/shared-skills/skills/frontend/scripts/perfection/collect-files.ts +53 -0
  188. package/packages/shared-skills/skills/frontend/scripts/perfection/draft-guard/findings.ts +16 -0
  189. package/packages/shared-skills/skills/frontend/scripts/perfection/draft-guard/lint.ts +38 -0
  190. package/packages/shared-skills/skills/frontend/scripts/perfection/draft-guard/rules.ts +69 -0
  191. package/packages/shared-skills/skills/frontend/scripts/perfection/draft-guard/text-nodes.ts +121 -0
  192. package/packages/shared-skills/skills/frontend/scripts/perfection/fixtures/draft-guard/negative/attr-lookalikes.tsx +10 -0
  193. package/packages/shared-skills/skills/frontend/scripts/perfection/fixtures/draft-guard/negative/bound-values.tsx +12 -0
  194. package/packages/shared-skills/skills/frontend/scripts/perfection/fixtures/draft-guard/negative/const-generics.tsx +3 -0
  195. package/packages/shared-skills/skills/frontend/scripts/perfection/fixtures/draft-guard/negative/generics.tsx +7 -0
  196. package/packages/shared-skills/skills/frontend/scripts/perfection/fixtures/draft-guard/negative/measured-stats.html +6 -0
  197. package/packages/shared-skills/skills/frontend/scripts/perfection/fixtures/draft-guard/negative/plain-copy.html +9 -0
  198. package/packages/shared-skills/skills/frontend/scripts/perfection/fixtures/draft-guard/negative/state-copy.html +11 -0
  199. package/packages/shared-skills/skills/frontend/scripts/perfection/fixtures/draft-guard/positive/attribute-edges.html +7 -0
  200. package/packages/shared-skills/skills/frontend/scripts/perfection/fixtures/draft-guard/positive/components.tsx +16 -0
  201. package/packages/shared-skills/skills/frontend/scripts/perfection/fixtures/draft-guard/positive/decorative-badge.vue +7 -0
  202. package/packages/shared-skills/skills/frontend/scripts/perfection/fixtures/draft-guard/positive/emoji-icons.tsx +9 -0
  203. package/packages/shared-skills/skills/frontend/scripts/perfection/fixtures/draft-guard/positive/jsx-edges.tsx +1 -0
  204. package/packages/shared-skills/skills/frontend/scripts/perfection/fixtures/draft-guard/positive/promo-copy.html +6 -0
  205. package/packages/shared-skills/skills/frontend/scripts/perfection/fixtures/draft-guard/positive/promo-copy.tsx +8 -0
  206. package/packages/shared-skills/skills/frontend/scripts/perfection/fixtures/draft-guard/positive/vanity-stats.html +5 -0
  207. package/packages/shared-skills/skills/programming/SKILL.md +10 -7
  208. package/packages/shared-skills/skills/programming/references/typescript/README.md +10 -7
  209. package/packages/shared-skills/skills/programming/references/typescript/bootstrap.md +9 -23
  210. package/packages/shared-skills/skills/programming/references/typescript/oxc-lint.md +130 -0
  211. package/packages/shared-skills/skills/programming/references/typescript/tsconfig-strict.md +6 -62
  212. package/packages/shared-skills/skills/programming/scripts/typescript/check-no-excuse-rules.ts +12 -12
  213. package/packages/shared-skills/skills/programming/scripts/typescript/new-project.test.ts +162 -0
  214. package/packages/shared-skills/skills/programming/scripts/typescript/new-project.ts +20 -81
  215. package/packages/shared-skills/skills/programming/scripts/typescript/oxc/.oxfmtrc.json +9 -0
  216. package/packages/shared-skills/skills/programming/scripts/typescript/oxc/.oxlintrc.json +46 -0
  217. package/packages/shared-skills/skills/programming/scripts/typescript/oxc/lint/oxlint-plugin/LICENSE +21 -0
  218. package/packages/shared-skills/skills/programming/scripts/typescript/oxc/lint/oxlint-plugin/index.js +10 -0
  219. package/packages/shared-skills/skills/programming/scripts/typescript/oxc/lint/oxlint-plugin/no-em-dash-copy.js +28 -0
  220. package/packages/shared-skills/skills/programming/scripts/typescript/oxc/lint/oxlint-plugin/require-suppression-reason.js +62 -0
  221. package/packages/shared-skills/skills/programming/scripts/typescript/oxc/tsconfig.json +29 -0
  222. package/packages/shared-skills/skills/refactor/SKILL.md +13 -13
  223. package/packages/shared-skills/skills/ulw-execute/SKILL.md +1 -1
  224. package/packages/shared-skills/skills/ulw-research/AGENTS.md +9 -3
  225. package/packages/shared-skills/skills/ulw-research/SKILL.md +2 -2
  226. package/packages/shared-skills/skills/ulw-research/references/deliverable-phase.md +7 -6
  227. package/packages/shared-skills/skills/ulw-research/references/fact-ledger-grammar.md +116 -0
  228. package/packages/shared-skills/skills/ulw-research/references/report-gates.md +51 -3
  229. package/packages/shared-skills/skills/ulw-research/scripts/claims-index.mjs +125 -0
  230. package/packages/shared-skills/skills/ulw-research/scripts/claims-index.test.ts +149 -0
  231. package/packages/shared-skills/skills/ulw-research/scripts/contracts.mjs +68 -3
  232. package/packages/shared-skills/skills/ulw-research/scripts/contracts.test.ts +97 -12
  233. package/packages/shared-skills/skills/ulw-research/scripts/fact-ledger-grammar.test.ts +190 -0
  234. package/packages/shared-skills/skills/ulw-research/scripts/fact-ledger-kinds.mjs +136 -0
  235. package/packages/shared-skills/skills/ulw-research/scripts/fact-ledger-kinds.test.ts +141 -0
  236. package/packages/shared-skills/skills/ulw-research/scripts/fact-ledger-schema.mjs +204 -0
  237. package/packages/shared-skills/skills/ulw-research/scripts/fact-ledger-schema.test.ts +43 -0
  238. package/packages/shared-skills/skills/ulw-research/scripts/fact-ledger.mjs +103 -0
  239. package/packages/shared-skills/skills/ulw-research/scripts/fact-ledger.test.ts +252 -0
  240. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-brief.mjs +145 -0
  241. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-brief.test.ts +179 -0
  242. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-state.mjs +63 -0
  243. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-state.test.ts +46 -0
  244. package/packages/shared-skills/skills/ulw-research/scripts/gates-context.test.ts +64 -0
  245. package/packages/shared-skills/skills/ulw-research/scripts/gates-static.mjs +1 -1
  246. package/packages/shared-skills/skills/ulw-research/scripts/gates-structure.mjs +1 -1
  247. package/packages/shared-skills/skills/ulw-research/scripts/gates-text.mjs +1 -27
  248. package/packages/shared-skills/skills/ulw-research/scripts/lexicon.mjs +47 -0
  249. package/packages/shared-skills/skills/ulw-research/scripts/lexicon.test.ts +57 -0
  250. package/packages/shared-skills/skills/ulw-research/scripts/numerals.mjs +226 -0
  251. package/packages/shared-skills/skills/ulw-research/scripts/numerals.test.ts +120 -0
  252. package/packages/shared-skills/skills/ulw-research/scripts/outcome.mjs +2 -3
  253. package/packages/shared-skills/skills/ulw-research/scripts/outcome.test.ts +29 -0
  254. package/packages/shared-skills/skills/ulw-research/scripts/particles-ko.mjs +108 -0
  255. package/packages/shared-skills/skills/ulw-research/scripts/particles-ko.test.ts +86 -0
  256. package/packages/shared-skills/skills/ulw-research/scripts/rational.mjs +139 -0
  257. package/packages/shared-skills/skills/ulw-research/scripts/rational.test.ts +46 -0
  258. package/packages/shared-skills/skills/ulw-research/scripts/report-tools-commands.mjs +2 -2
  259. package/packages/shared-skills/skills/ulw-research/scripts/sentence-split.mjs +77 -0
  260. package/packages/shared-skills/skills/ulw-research/scripts/sentence-split.test.ts +109 -0
  261. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/claim-graph.md +13 -0
  262. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity-brief-locked.md +17 -0
  263. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity-brief-pending.md +17 -0
  264. package/packages/shared-skills/skills/ulw-research/scripts/text-normalize.mjs +82 -0
  265. package/packages/shared-skills/skills/ulw-research/scripts/text-normalize.test.ts +73 -0
  266. package/packages/shared-skills/skills/ulw-research/scripts/text-similarity.mjs +28 -0
  267. package/packages/shared-skills/skills/ulw-research/scripts/text-similarity.test.ts +38 -0
  268. package/packages/shared-skills/skills/ulw-research/scripts/units.mjs +105 -0
  269. package/packages/shared-skills/skills/ulw-research/scripts/units.test.ts +83 -0
  270. package/packages/shared-skills/skills/visual-qa/SKILL.md +70 -63
  271. package/packages/shared-skills/skills/visual-qa/scripts/skill-doc-fields.test.ts +34 -0
  272. package/script/qa/web-terminal-visual-qa.mjs +6 -2
  273. package/script/qa/xterm-live-terminal.mjs +26 -3
@@ -0,0 +1,103 @@
1
+ // fact-ledger.mjs - the verbatim fact ledger: one row per measured, quoted, derived, assumed,
2
+ // or converted value, with its source locator and exact rational. Rows are never deleted:
3
+ // retract and supersede change status and append an event. Validation and the field vocabulary
4
+ // live in fact-ledger-schema.mjs and are re-exported here. node: builtins only.
5
+
6
+ import { existsSync } from "node:fs"
7
+ import { join } from "node:path"
8
+
9
+ import { CliError, readJsonFile, writeJsonAtomic } from "./cli-support.mjs"
10
+ import { DECIMAL, exactComesFromValue, exactFromDecimal, idPrefix, isString } from "./fact-ledger-kinds.mjs"
11
+ import { LEDGER_SCHEMA_VERSION, ROW_DEFAULTS, ROW_FIELDS, transitionError, validateLedger } from "./fact-ledger-schema.mjs"
12
+
13
+ export { exactFromDecimal } from "./fact-ledger-kinds.mjs"
14
+ export { FACT_KINDS, FACT_STATUSES, LEDGER_SCHEMA_VERSION, OPERATIONS, QUALIFIERS, ROW_FIELDS, TRANSITIONS, transitionError, validateLedger } from "./fact-ledger-schema.mjs"
15
+
16
+ export const LEDGER_FILENAME = "fact-ledger.json"
17
+
18
+ export function emptyLedger() {
19
+ return { schemaVersion: LEDGER_SCHEMA_VERSION, rows: [], events: [] }
20
+ }
21
+
22
+ function assertValid(ledger) {
23
+ const errors = validateLedger(ledger)
24
+ if (errors.length > 0) throw new CliError(`invalid fact ledger: ${errors.join("; ")}`)
25
+ }
26
+
27
+ export function readLedger(dir) {
28
+ const file = join(dir, LEDGER_FILENAME)
29
+ if (!existsSync(file)) return emptyLedger()
30
+ const ledger = readJsonFile(file)
31
+ assertValid(ledger)
32
+ return ledger
33
+ }
34
+
35
+ export function writeLedger(dir, ledger) {
36
+ assertValid(ledger)
37
+ const file = join(dir, LEDGER_FILENAME)
38
+ writeJsonAtomic(file, ledger)
39
+ return file
40
+ }
41
+
42
+ const ID_NUMBER = Object.freeze({ F: /^F(\d+)$/, Q: /^Q(\d+)$/ })
43
+
44
+ /** The next id with the kind's prefix from KIND_RULES (F<n> for value rows, Q<n> for quotes). */
45
+ export function nextId(ledger, kind) {
46
+ const prefix = idPrefix(kind)
47
+ let max = 0
48
+ for (const row of ledger.rows) {
49
+ const match = ID_NUMBER[prefix].exec(row.fact_id ?? "")
50
+ if (match) max = Math.max(max, Number(match[1]))
51
+ }
52
+ return `${prefix}${max + 1}`
53
+ }
54
+
55
+ /**
56
+ * Fill defaults, assign the next id, validate the whole ledger (provenance included), then append.
57
+ * Only kinds whose KIND_RULES exact is from_value (measured) get exact from their decimal:
58
+ * derived and converted rows carry the exact that derive computed from the operands and factor,
59
+ * never one read back from the rounded value.
60
+ */
61
+ export function addRow(ledger, row) {
62
+ const filled = { ...structuredClone(ROW_DEFAULTS), ...row, fact_id: row.fact_id ?? nextId(ledger, row.kind) }
63
+ if (exactComesFromValue(filled.kind) && filled.exact === null && isString(filled.value) && DECIMAL.test(filled.value)) {
64
+ filled.exact = exactFromDecimal(filled.value)
65
+ }
66
+ const ordered = Object.fromEntries(ROW_FIELDS.filter((key) => Object.hasOwn(filled, key)).map((key) => [key, filled[key]]))
67
+ for (const key of Object.keys(filled)) if (!ROW_FIELDS.includes(key)) ordered[key] = filled[key]
68
+ assertValid({ ...ledger, rows: [...ledger.rows, ordered] })
69
+ ledger.rows.push(ordered)
70
+ return ordered
71
+ }
72
+
73
+ /** Build the event from the row's current status and append it only if transitionError allows it. */
74
+ function changeStatus(ledger, id, to, replacement, reason) {
75
+ const row = ledger.rows.find((candidate) => candidate.fact_id === id)
76
+ const event = { fact_id: id, from: row?.status ?? null, to, replacement: replacement ?? null, reason }
77
+ const problem = transitionError(new Map(ledger.rows.map((candidate) => [candidate.fact_id, candidate.status])), event)
78
+ if (problem) throw new CliError(problem)
79
+ ledger.events.push(event)
80
+ row.status = to
81
+ return row
82
+ }
83
+
84
+ export function retractRow(ledger, id, reason) {
85
+ return changeStatus(ledger, id, "retracted", null, reason)
86
+ }
87
+
88
+ export function supersedeRow(ledger, id, replacementId, reason) {
89
+ return changeStatus(ledger, id, "superseded", replacementId, reason)
90
+ }
91
+
92
+ function cell(value) {
93
+ return value === null || value === undefined ? "" : String(value).replace(/\|/g, "\\|").replace(/\n/g, " ")
94
+ }
95
+
96
+ /** The `--view` table: one line per row, retracted and superseded rows included. */
97
+ export function renderLedgerMarkdown(ledger) {
98
+ const lines = ["| fact_id | kind | label | value | unit | source | status | raw |", "|---|---|---|---|---|---|---|---|"]
99
+ for (const row of ledger.rows) {
100
+ lines.push(`| ${[row.fact_id, row.kind, row.label, row.value, row.unit, row.source_id, row.status, row.raw].map(cell).join(" | ")} |`)
101
+ }
102
+ return `${lines.join("\n")}\n`
103
+ }
@@ -0,0 +1,145 @@
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
+ function incompleteDefect(briefPath, parsed) {
113
+ const reasons = []
114
+ if (!parsed.present) reasons.push("no ## Fidelity block")
115
+ else if (parsed.missing.length > 0) reasons.push(`missing: ${parsed.missing.join(", ")}`)
116
+ if (parsed.pending.length > 0) reasons.push(`pending: ${parsed.pending.join(", ")}`)
117
+ return {
118
+ code: FIDELITY_INCOMPLETE,
119
+ severity: "blocker",
120
+ integrity: true,
121
+ selector: null,
122
+ line: null,
123
+ message: `${briefPath}: fidelity interview incomplete (${reasons.join("; ")})`,
124
+ hint: INCOMPLETE_HINT,
125
+ }
126
+ }
127
+
128
+ /**
129
+ * The lock check the reader-facing CLI stages run before writing anything.
130
+ * exitCode 0 = locked; 1 = pending, missing, or no brief (one `fidelity_interview_incomplete`
131
+ * defect); 2 = malformed block (usage error, `errors` name the lines, no defect).
132
+ * @param {string} briefPath
133
+ */
134
+ export function requireLockedBrief(briefPath) {
135
+ let markdown = ""
136
+ try {
137
+ markdown = readFileSync(briefPath, "utf8")
138
+ } catch (error) {
139
+ if (error?.code !== "ENOENT") throw error
140
+ }
141
+ const parsed = parseFidelityBlock(markdown)
142
+ if (parsed.errors.length > 0) return { locked: false, exitCode: 2, defects: [], errors: parsed.errors, parsed }
143
+ if (isLocked(parsed)) return { locked: true, exitCode: 0, defects: [], errors: [], parsed }
144
+ return { locked: false, exitCode: 1, defects: [incompleteDefect(briefPath, parsed)], errors: [], parsed }
145
+ }
@@ -0,0 +1,63 @@
1
+ // fidelity-state.mjs - per-sentence failure counts for the fidelity repair loop. A sentence is
2
+ // keyed by its section slug and the sorted fact ids it cites, so a reworded sentence that
3
+ // cites the same facts keeps its count. node: builtins only.
4
+
5
+ import { existsSync } from "node:fs"
6
+ import { join } from "node:path"
7
+
8
+ import { CliError, readJsonFile, writeJsonAtomic } from "./cli-support.mjs"
9
+
10
+ export const STATE_FILENAME = "fidelity-state.json"
11
+ export const STATE_SCHEMA_VERSION = 1
12
+
13
+ /** slug + '#' + the fact ids sorted and joined by ','. */
14
+ export function sentenceKey(sectionSlug, factIds) {
15
+ return `${sectionSlug}#${[...factIds].sort().join(",")}`
16
+ }
17
+
18
+ function emptyState() {
19
+ return { schemaVersion: STATE_SCHEMA_VERSION, failures: {}, replaced: [] }
20
+ }
21
+
22
+ function isValid(state) {
23
+ return (
24
+ state !== null &&
25
+ typeof state === "object" &&
26
+ state.schemaVersion === STATE_SCHEMA_VERSION &&
27
+ state.failures !== null &&
28
+ typeof state.failures === "object" &&
29
+ !Array.isArray(state.failures) &&
30
+ Object.values(state.failures).every((count) => Number.isInteger(count) && count >= 0) &&
31
+ Array.isArray(state.replaced) &&
32
+ state.replaced.every((key) => typeof key === "string")
33
+ )
34
+ }
35
+
36
+ export function readState(dir) {
37
+ const file = join(dir, STATE_FILENAME)
38
+ if (!existsSync(file)) return emptyState()
39
+ const state = readJsonFile(file)
40
+ if (!isValid(state)) throw new CliError(`${file} is not a valid fidelity state`)
41
+ return state
42
+ }
43
+
44
+ export function recordFailure(state, key) {
45
+ state.failures[key] = (state.failures[key] ?? 0) + 1
46
+ return state.failures[key]
47
+ }
48
+
49
+ export function failureCount(state, key) {
50
+ return state.failures[key] ?? 0
51
+ }
52
+
53
+ export function markReplaced(state, key) {
54
+ if (!state.replaced.includes(key)) state.replaced.push(key)
55
+ return state
56
+ }
57
+
58
+ export function writeState(dir, state) {
59
+ if (!isValid(state)) throw new CliError("refusing to write an invalid fidelity state")
60
+ const file = join(dir, STATE_FILENAME)
61
+ writeJsonAtomic(file, state)
62
+ return file
63
+ }
@@ -108,7 +108,7 @@ function walkBody(ctx, node, state) {
108
108
  }
109
109
  }
110
110
 
111
- function buildContext(doc, options) {
111
+ export function buildContext(doc, options) {
112
112
  const lineageMode = options.lineageMode ?? "inline"
113
113
  if (!LINEAGE_MODES.includes(lineageMode)) throw new TypeError(`lineageMode must be one of ${LINEAGE_MODES.join(", ")}`)
114
114
  const closingPattern = options.closingPattern ? toRegExp(options.closingPattern) : null
@@ -9,7 +9,7 @@ import { join } from "node:path"
9
9
  import { proseText } from "./html-lite.mjs"
10
10
 
11
11
  const MIN_SECTION_PROSE = 120
12
- const UNCHECKED_KINDS = new Set(["closing", "sources", "toc", "appendix", "glossary"])
12
+ export const UNCHECKED_KINDS = new Set(["closing", "sources", "toc", "appendix", "glossary"])
13
13
  const DEFINITION_TAGS = new Set(["p", "li", "dd", "dt", "td"])
14
14
  const DEFINITION_LINE = /^\[(S|Source\s*)(\d+)\]/i
15
15
  const MIN_DEFINITIONS = 3
@@ -4,6 +4,7 @@
4
4
  // Plain Node ESM, sibling imports only.
5
5
 
6
6
  import { proseText } from "./html-lite.mjs"
7
+ import { CLAIMS, NOT_CLAIMS } from "./numerals.mjs"
7
8
 
8
9
  const HANGUL = /[\uAC00-\uD7A3]/g
9
10
  const KEEP_ALL = /word-break\s*:\s*keep-all/i
@@ -19,33 +20,6 @@ const MAX_HEADING_WORDS = 12
19
20
  const LINEAGE_TOKEN = /\b(?:MEASURED|ASSUMED|DERIVED)\b/
20
21
  const NUMBER_CONTAINERS = new Set(["figure", "table", "nav", "header", "footer"])
21
22
  const MAX_UNSOURCED = 50
22
- const MONTH = "(?:jan|feb|mar|apr|may|jun|jul|aug|sep|sept|oct|nov|dec)[a-z]*\\.?"
23
- /** Stripped before claim detection: dates, versions, figure refs, times, bare years. */
24
- const NOT_CLAIMS = [
25
- /\b\d{4}[-./]\d{1,2}(?:[-./]\d{1,2})?\b/g,
26
- /\b\d{2}\.\d{2}\.\d{2}\b/g,
27
- /\d{2,4}\s*\uB144\s*\d{1,2}\s*\uC6D4(?:\s*\d{1,2}\s*\uC77C)?/g,
28
- /\d{1,2}\s*\uC6D4(?:\s*\d{1,2}\s*\uC77C)?/g,
29
- /(?:19|20)\d{2}\s*\uB144/g,
30
- new RegExp(`\\b${MONTH}\\s+\\d{1,2}(?:st|nd|rd|th)?(?:,?\\s+\\d{4})?`, "gi"),
31
- new RegExp(`\\b\\d{1,2}(?:st|nd|rd|th)?\\s+${MONTH}(?:\\s+\\d{4})?`, "gi"),
32
- /\bv\d+(?:\.\d+)*\b|\b\d+(?:\.\d+){2,}\b/gi,
33
- /\b(?:version|ver\.?|release)\s*\d+(?:\.\d+)*/gi,
34
- /\b(?:fig(?:ure)?\.?|table|chart|section|step|phase|wave|part)\s*\d+(?:\.\d+)*/gi,
35
- /(?:\uADF8\uB9BC|\uB3C4\uD45C|\uD45C|\uBC84\uC804)\s*\d+(?:\.\d+)*/g,
36
- /\b\d{1,2}:\d{2}\b/g,
37
- /\b(?:19|20)\d{2}s?\b/g,
38
- ]
39
- const NUM = "(?<![\\p{L}\\d.,])\\d+(?:[.,]\\d+)*"
40
- const UNIT =
41
- "(?:%|\\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" +
42
- "|(?: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]))"
43
- const CLAIMS = [
44
- new RegExp(`[$\\u20AC\\u00A3\\u00A5\\u20A9]\\s?${NUM}`, "u"),
45
- new RegExp(`${NUM}\\s?${UNIT}`, "iu"),
46
- /(?<![\d.,])\d{1,3}(?:,\d{3})+(?![\d,])/,
47
- /(?<![\d.])\d+\.\d+(?![\d.])/,
48
- ]
49
23
 
50
24
  function snippet(text, index, span = 24) {
51
25
  return text.slice(Math.max(0, index - span), index + span).trim()
@@ -0,0 +1,47 @@
1
+ // Bilingual fidelity lexicon: data only, read by the fidelity checks and the meaning-pair router.
2
+ // Korean entries are \u escapes (this directory ships no Hangul); each list is frozen.
3
+
4
+ // Hedge tokens: a source hedge the report drops is hedge_dropped.
5
+ export const HEDGES = Object.freeze([
6
+ ...["estimated", "about", "approximately", "may", "might", "expected", "forecast", "provisional", "not yet"],
7
+ ...["\uCD94\uC815", "\uC57D", "\uAC00\uB2A5", "\uC804\uB9DD", "\uC608\uC0C1", "\uC7A0\uC815", "\uC544\uC9C1", "\uBBF8\uD655\uC815", "\uAC83\uC73C\uB85C \uBCF4\uC778\uB2E4"],
8
+ ])
9
+
10
+ // Closed qualifier list for the ledger qualifier field (bounding or approximation words).
11
+ export const QUALIFIERS = Object.freeze([
12
+ ...["up to", "at least", "at most", "about", "approximately", "nearly", "over", "under", "more than", "less than"],
13
+ ...["\uCD5C\uB300", "\uCD5C\uC18C", "\uC57D", "\uC774\uC0C1", "\uC774\uD558", "\uBBF8\uB9CC", "\uCD08\uACFC", "\uB0B4\uC678", "\uAE4C\uC9C0"],
14
+ ])
15
+
16
+ // Direction classes: a source in one class and an assertion in the other is direction_flipped.
17
+ export const DIRECTION = Object.freeze({
18
+ up: Object.freeze([...["increase", "rise", "grow", "up"], ...["\uC99D\uAC00", "\uC0C1\uC2B9", "\uB298"]]),
19
+ down: Object.freeze([...["decrease", "fall", "decline", "down"], ...["\uAC10\uC18C", "\uD558\uB77D", "\uC904"]]),
20
+ })
21
+
22
+ // Causal connectives: one in the assertion but in no supporting quote is causality_added.
23
+ export const CAUSAL = Object.freeze([
24
+ ...["because", "due to", "leads to", "results in", "causes", "therefore"],
25
+ ...["\uB54C\uBB38\uC5D0", "\uB85C \uC778\uD574", "\uB530\uB77C\uC11C", "\uADF8\uB798\uC11C", "\uCD08\uB798"],
26
+ ])
27
+
28
+ // Concessive connectives for the hedge-position routing heuristic.
29
+ export const CONCESSIVE = Object.freeze([
30
+ ...["although", "but", "however", "despite", "while"],
31
+ ...["\uC73C\uB098", "\uC9C0\uB9CC", "\uC5D0\uB3C4 \uBD88\uAD6C\uD558\uACE0", "\uBC18\uBA74", "\uC640 \uB2EC\uB9AC"],
32
+ ])
33
+
34
+ // Narrow-to-broad scope pairs [narrow, broad] for scope_widened_warn.
35
+ export const SCOPE_NARROW_TO_BROAD = Object.freeze([
36
+ Object.freeze(["\uBD81\uBBF8", "\uC804 \uC138\uACC4"]),
37
+ Object.freeze(["pilot", "system"]),
38
+ Object.freeze(["one region", "all regions"]),
39
+ Object.freeze(["\uC77C\uBD80", "\uC804\uCCB4"]),
40
+ Object.freeze(["\uD55C \uC9C0\uC5ED", "\uC804\uAD6D"]),
41
+ ])
42
+
43
+ // Negation tokens; negated direction cases route to a reader instead of the lexicon.
44
+ export const NEGATION = Object.freeze([
45
+ ...["not", "no", "never"],
46
+ ...["\uC54A", "\uC5C6", "\uBABB"],
47
+ ])
@@ -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
+ }
@@ -5,8 +5,7 @@
5
5
  import * as nodeFs from "node:fs"
6
6
  import { basename, dirname, join } from "node:path"
7
7
 
8
- import { DELIVERABLE_FORMATS, DELIVERABLE_STATUSES, GATE_NAMES, GATE_STATUSES, LANES } from "./contracts.mjs"
9
- import { MANIFEST_CODES, SCHEMA_VERSION, validateOutcomeManifest } from "./contracts.mjs"
8
+ import { DELIVERABLE_FORMATS, DELIVERABLE_STATUSES, GATE_NAMES, GATE_STATUSES, LANES, MANIFEST_CODES, normalizeManifest, SCHEMA_VERSION, validateOutcomeManifest } from "./contracts.mjs"
10
9
 
11
10
  export const MANIFEST_FILENAME = "outcome.json"
12
11
  const MISSING = MANIFEST_CODES[0]
@@ -226,7 +225,7 @@ function assertValid(manifest, where) {
226
225
  export function readManifest(file) {
227
226
  let manifest
228
227
  try {
229
- manifest = JSON.parse(nodeFs.readFileSync(file, "utf8"))
228
+ manifest = normalizeManifest(JSON.parse(nodeFs.readFileSync(file, "utf8")))
230
229
  } catch (error) {
231
230
  if (error instanceof SyntaxError) throw new OutcomeError("invalid_manifest", `${file}: not valid JSON (${error.message})`)
232
231
  throw error