oh-my-opencode 5.1.47 → 5.1.51

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 (497) hide show
  1. package/dist/agents/refusal-rule.d.ts +7 -0
  2. package/dist/cli/index.js +1194 -752
  3. package/dist/cli/service-auth/accounts-gate.d.ts +11 -0
  4. package/dist/cli/service-auth/commands.d.ts +2 -4
  5. package/dist/cli/service-auth/index.d.ts +1 -1
  6. package/dist/cli/service-auth/keystore.d.ts +26 -2
  7. package/dist/cli/service-auth/protocol.d.ts +7 -1
  8. package/dist/cli/service-auth/session.d.ts +6 -0
  9. package/dist/cli/service-auth/signed-out-marker.d.ts +11 -0
  10. package/dist/cli-node/index.js +1194 -752
  11. package/dist/config/schema/hooks.d.ts +1 -0
  12. package/dist/create-hooks.d.ts +1 -0
  13. package/dist/features/boulder-state/storage.d.ts +1 -1
  14. package/dist/features/boulder-state/types.d.ts +1 -1
  15. package/dist/hooks/atlas/idle-continuation.d.ts +2 -1
  16. package/dist/hooks/atlas/types.d.ts +9 -1
  17. package/dist/hooks/index.d.ts +1 -0
  18. package/dist/hooks/momus-review-persistence/hook.d.ts +19 -0
  19. package/dist/hooks/momus-review-persistence/index.d.ts +1 -0
  20. package/dist/hooks/momus-review-persistence/review-resolution.d.ts +20 -0
  21. package/dist/hooks/prometheus-md-only/constants.d.ts +2 -0
  22. package/dist/hooks/prometheus-md-only/fixtures/denial-log.d.ts +1 -0
  23. package/dist/hooks/prometheus-md-only/fixtures/guarded-hashline.d.ts +1 -0
  24. package/dist/hooks/prometheus-md-only/fixtures/invoke-before.d.ts +7 -0
  25. package/dist/hooks/prometheus-md-only/fixtures/opencode-patch-parser.d.ts +2 -0
  26. package/dist/hooks/prometheus-md-only/hook.d.ts +5 -1
  27. package/dist/hooks/prometheus-md-only/path-policy.d.ts +2 -9
  28. package/dist/hooks/prometheus-md-only/vendor/opencode-patch-parser.d.ts +22 -0
  29. package/dist/index.js +2322 -1045
  30. package/dist/plugin/hooks/create-core-hooks.d.ts +1 -0
  31. package/dist/plugin/hooks/create-tool-guard-hooks.d.ts +2 -1
  32. package/dist/shared/posthog-activity-state.d.ts +1 -0
  33. package/dist/shared/posthog.d.ts +1 -2
  34. package/dist/shared/resolve-file-write-path.d.ts +6 -0
  35. package/dist/skills/ast-grep/README.md +1 -1
  36. package/dist/skills/ast-grep/install.ps1 +5 -3
  37. package/dist/skills/ast-grep/install.sh +4 -5
  38. package/dist/skills/ast-grep/references/install.md +2 -2
  39. package/dist/skills/ast-grep/scripts/ast_grep_helper.py +13 -3
  40. package/dist/skills/browser/runtime/omowright/manifest.json +1 -1
  41. package/dist/skills/coding-agent-sessions/AGENTS.md +5 -4
  42. package/dist/skills/coding-agent-sessions/SKILL.md +11 -11
  43. package/dist/skills/coding-agent-sessions/references/all-platforms.md +2 -2
  44. package/dist/skills/coding-agent-sessions/references/codex.md +4 -4
  45. package/dist/skills/coding-agent-sessions/scripts/agent_sessions/cli.py +1 -1
  46. package/dist/skills/coding-agent-sessions/scripts/find-agent-sessions.py +6 -3
  47. package/dist/skills/frontend/ATTRIBUTION.md +1 -1
  48. package/dist/skills/frontend/SKILL.md +34 -12
  49. package/dist/skills/frontend/references/design/README.md +2 -2
  50. package/dist/skills/frontend/references/design/_INDEX.md +2 -1
  51. package/dist/skills/frontend/references/design/component-catalogs.md +14 -10
  52. package/dist/skills/frontend/references/design/design-system-architecture.md +19 -6
  53. package/dist/skills/frontend/references/design/grounding.md +29 -0
  54. package/dist/skills/frontend/references/ui-ux-db/scripts/design_system.py +2 -0
  55. package/dist/skills/frontend/references/ui-ux-db/scripts/search.py +2 -0
  56. package/dist/skills/ultimate-browsing/SKILL.md +5 -4
  57. package/dist/skills/ultimate-browsing/engine/AGENTS.md +2 -2
  58. package/dist/skills/ultimate-browsing/engine/__main__.py +9 -6
  59. package/dist/skills/ultimate-browsing/engine/summary.py +1 -1
  60. package/dist/skills/ultimate-browsing/references/insane-search/README.md +4 -4
  61. package/dist/skills/ultimate-browsing/scripts/extract_cookies.py +4 -2
  62. package/dist/skills/ulw-research/AGENTS.md +8 -4
  63. package/dist/skills/ulw-research/SKILL.md +7 -259
  64. package/dist/skills/ulw-research/references/deliverable-phase.md +68 -24
  65. package/dist/skills/ulw-research/references/fact-ledger-grammar.md +7 -3
  66. package/dist/skills/ulw-research/references/fact-ledger.md +42 -8
  67. package/dist/skills/ulw-research/references/fidelity-claim-kinds.md +1 -1
  68. package/dist/skills/ulw-research/references/markdown-blocks.md +12 -0
  69. package/dist/skills/ulw-research/references/report-gates.md +14 -6
  70. package/dist/skills/ulw-research/references/research-collection.md +195 -0
  71. package/dist/skills/ulw-research/references/research-delivery.md +123 -0
  72. package/dist/skills/ulw-research/scripts/claim-kinds.mjs +33 -5
  73. package/dist/skills/ulw-research/scripts/claim-kinds.test.ts +37 -2
  74. package/dist/skills/ulw-research/scripts/claim-prefix.test.ts +76 -0
  75. package/dist/skills/ulw-research/scripts/contracts-validation.test.ts +83 -0
  76. package/dist/skills/ulw-research/scripts/contracts.mjs +16 -15
  77. package/dist/skills/ulw-research/scripts/contracts.test.ts +6 -109
  78. package/dist/skills/ulw-research/scripts/conversion-altered.test.ts +154 -0
  79. package/dist/skills/ulw-research/scripts/conversion-originals.test.ts +297 -0
  80. package/dist/skills/ulw-research/scripts/conversion-probe-classes.test.ts +361 -0
  81. package/dist/skills/ulw-research/scripts/conversion-probe.mjs +229 -0
  82. package/dist/skills/ulw-research/scripts/conversion-probe.test.ts +218 -0
  83. package/dist/skills/ulw-research/scripts/copy-returns-fidelity-policy.test.ts +156 -0
  84. package/dist/skills/ulw-research/scripts/copy-returns-polish.test.ts +10 -9
  85. package/dist/skills/ulw-research/scripts/css-hiding.mjs +158 -0
  86. package/dist/skills/ulw-research/scripts/css-lite.mjs +4 -2
  87. package/dist/skills/ulw-research/scripts/delivery-barrier-meaning.test.ts +186 -0
  88. package/dist/skills/ulw-research/scripts/delivery-barrier.test.ts +40 -87
  89. package/dist/skills/ulw-research/scripts/entities.mjs +13 -4
  90. package/dist/skills/ulw-research/scripts/fact-commands-fill.mjs +96 -0
  91. package/dist/skills/ulw-research/scripts/fact-commands-meaning.mjs +26 -30
  92. package/dist/skills/ulw-research/scripts/fact-commands-polish.mjs +46 -19
  93. package/dist/skills/ulw-research/scripts/fact-commands-verify.mjs +37 -6
  94. package/dist/skills/ulw-research/scripts/fact-commands.mjs +40 -62
  95. package/dist/skills/ulw-research/scripts/fact-derive-2.test.ts +149 -0
  96. package/dist/skills/ulw-research/scripts/fact-derive.mjs +7 -2
  97. package/dist/skills/ulw-research/scripts/fact-derive.test.ts +7 -192
  98. package/dist/skills/ulw-research/scripts/fact-fill.mjs +9 -6
  99. package/dist/skills/ulw-research/scripts/fact-ledger-kinds.mjs +6 -7
  100. package/dist/skills/ulw-research/scripts/fact-ledger-kinds.test.ts +2 -1
  101. package/dist/skills/ulw-research/scripts/fact-ledger-legacy.test.ts +2 -1
  102. package/dist/skills/ulw-research/scripts/fact-locate.mjs +15 -9
  103. package/dist/skills/ulw-research/scripts/fact-unit-dimensions-2.test.ts +149 -0
  104. package/dist/skills/ulw-research/scripts/fact-unit-dimensions.test.ts +4 -168
  105. package/dist/skills/ulw-research/scripts/fact-unit-rules.test.ts +3 -229
  106. package/dist/skills/ulw-research/scripts/fidelity-claims.mjs +4 -3
  107. package/dist/skills/ulw-research/scripts/fidelity-csv.mjs +32 -0
  108. package/dist/skills/ulw-research/scripts/fidelity-lexicon.mjs +1 -1
  109. package/dist/skills/ulw-research/scripts/fidelity-originals.mjs +161 -0
  110. package/dist/skills/ulw-research/scripts/fidelity-policy.mjs +183 -0
  111. package/dist/skills/ulw-research/scripts/fidelity-records.mjs +81 -14
  112. package/dist/skills/ulw-research/scripts/fidelity-snapshot-readability.mjs +49 -0
  113. package/dist/skills/ulw-research/scripts/fidelity-sources.mjs +76 -0
  114. package/dist/skills/ulw-research/scripts/fidelity-trace.mjs +25 -11
  115. package/dist/skills/ulw-research/scripts/fidelity-value-unit.mjs +202 -0
  116. package/dist/skills/ulw-research/scripts/fidelity-values.mjs +40 -39
  117. package/dist/skills/ulw-research/scripts/fidelity-values.test.ts +8 -29
  118. package/dist/skills/ulw-research/scripts/gates-fidelity.mjs +8 -6
  119. package/dist/skills/ulw-research/scripts/gates-static-headings.test.ts +17 -0
  120. package/dist/skills/ulw-research/scripts/gates-static.mjs +10 -1
  121. package/dist/skills/ulw-research/scripts/html-lite.mjs +6 -2
  122. package/dist/skills/ulw-research/scripts/html-markup.mjs +2 -2
  123. package/dist/skills/ulw-research/scripts/html-text-map.mjs +140 -0
  124. package/dist/skills/ulw-research/scripts/html-text-map.test.ts +148 -0
  125. package/dist/skills/ulw-research/scripts/markdown-closers.mjs +126 -0
  126. package/dist/skills/ulw-research/scripts/md-lite-boundaries.test.ts +46 -0
  127. package/dist/skills/ulw-research/scripts/md-lite.mjs +1 -1
  128. package/dist/skills/ulw-research/scripts/number-grammar.mjs +33 -0
  129. package/dist/skills/ulw-research/scripts/number-grammar.test.ts +36 -0
  130. package/dist/skills/ulw-research/scripts/numerals.mjs +18 -7
  131. package/dist/skills/ulw-research/scripts/numerals.test.ts +3 -0
  132. package/dist/skills/ulw-research/scripts/outcome-start.mjs +66 -0
  133. package/dist/skills/ulw-research/scripts/outcome-start.test.ts +109 -0
  134. package/dist/skills/ulw-research/scripts/outcome.mjs +11 -22
  135. package/dist/skills/ulw-research/scripts/page-source-locator.test.ts +187 -0
  136. package/dist/skills/ulw-research/scripts/placeholder.mjs +18 -6
  137. package/dist/skills/ulw-research/scripts/polish-apply.mjs +3 -2
  138. package/dist/skills/ulw-research/scripts/polish-markup.test.ts +15 -1
  139. package/dist/skills/ulw-research/scripts/polish-partial.test.ts +42 -0
  140. package/dist/skills/ulw-research/scripts/raw-decode.mjs +110 -0
  141. package/dist/skills/ulw-research/scripts/raw-locator.mjs +47 -0
  142. package/dist/skills/ulw-research/scripts/raw-snapshot.test.ts +139 -0
  143. package/dist/skills/ulw-research/scripts/report-tools-2.test.ts +233 -0
  144. package/dist/skills/ulw-research/scripts/report-tools-3.test.ts +141 -0
  145. package/dist/skills/ulw-research/scripts/report-tools-4.test.ts +135 -0
  146. package/dist/skills/ulw-research/scripts/report-tools-5.test.ts +231 -0
  147. package/dist/skills/ulw-research/scripts/report-tools-commands.mjs +1 -1
  148. package/dist/skills/ulw-research/scripts/report-tools.mjs +7 -1
  149. package/dist/skills/ulw-research/scripts/report-tools.test.ts +6 -560
  150. package/dist/skills/ulw-research/scripts/snapshot-readability.test.ts +116 -0
  151. package/dist/skills/ulw-research/scripts/source-commands.mjs +87 -29
  152. package/dist/skills/ulw-research/scripts/source-integrity.test.ts +243 -0
  153. package/dist/skills/ulw-research/scripts/source-refresh.mjs +82 -0
  154. package/dist/skills/ulw-research/scripts/source-snapshot.mjs +73 -69
  155. package/dist/skills/ulw-research/scripts/source-snapshot.test.ts +7 -4
  156. package/dist/skills/ulw-research/scripts/source-view.mjs +89 -0
  157. package/dist/skills/ulw-research/scripts/sources-citation.mjs +203 -0
  158. package/dist/skills/ulw-research/scripts/sources-citation.test.ts +236 -0
  159. package/dist/skills/ulw-research/scripts/tests/contracts-support.ts +147 -0
  160. package/dist/skills/ulw-research/scripts/tests/delivery-barrier-session.ts +59 -0
  161. package/dist/skills/ulw-research/scripts/tests/derive-clock.ts +4 -0
  162. package/dist/skills/ulw-research/scripts/tests/fact-derive-support.ts +65 -0
  163. package/dist/skills/ulw-research/scripts/tests/fact-unit-dimensions-support.ts +41 -0
  164. package/dist/skills/ulw-research/scripts/tests/fact-unit-rules-support.ts +241 -0
  165. package/dist/skills/ulw-research/scripts/tests/fidelity-values-support.ts +30 -0
  166. package/dist/skills/ulw-research/scripts/tests/fixtures/conversion-probe.html +14 -0
  167. package/dist/skills/ulw-research/scripts/tests/fixtures/sources-shapes.html +20 -0
  168. package/dist/skills/ulw-research/scripts/tests/numeral-classes.ts +196 -0
  169. package/dist/skills/ulw-research/scripts/tests/report-tools-support.ts +182 -0
  170. package/dist/skills/ulw-research/scripts/tests/value-mode-support.ts +48 -0
  171. package/dist/skills/ulw-research/scripts/units.mjs +14 -3
  172. package/dist/skills/ulw-research/scripts/units.test.ts +10 -0
  173. package/dist/skills/ulw-research/scripts/value-mode-unit-classes.test.ts +255 -0
  174. package/dist/skills/ulw-research/scripts/value-mode-unit.test.ts +179 -0
  175. package/dist/skills/ulw-research/scripts/value-rules-cli.test.ts +157 -0
  176. package/dist/skills/ulw-research/scripts/value-rules-followups.test.ts +72 -0
  177. package/dist/skills/ulw-research/scripts/value-rules-priority.test.ts +80 -0
  178. package/dist/tools/delegate-task/executor-types.d.ts +1 -0
  179. package/dist/tools/delegate-task/sync-session-existence.d.ts +4 -0
  180. package/dist/tools/delegate-task/types.d.ts +1 -0
  181. package/dist/tools/skill/description-formatter.d.ts +2 -1
  182. package/dist/tools/skill/types.d.ts +6 -0
  183. package/dist/tui.js +131 -33
  184. package/package.json +18 -15
  185. package/packages/git-bash-mcp/package.json +1 -1
  186. package/packages/omo-codex/plugin/.codex-plugin/plugin.json +1 -1
  187. package/packages/omo-codex/plugin/README.md +1 -1
  188. package/packages/omo-codex/plugin/components/bootstrap/hooks/hooks.json +1 -1
  189. package/packages/omo-codex/plugin/components/bootstrap/package.json +1 -1
  190. package/packages/omo-codex/plugin/components/comment-checker/hooks/hooks.json +1 -1
  191. package/packages/omo-codex/plugin/components/comment-checker/package.json +1 -1
  192. package/packages/omo-codex/plugin/components/git-bash/hooks/hooks.json +2 -2
  193. package/packages/omo-codex/plugin/components/git-bash/package.json +1 -1
  194. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/hooks/hooks.json +1 -1
  195. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/package.json +1 -1
  196. package/packages/omo-codex/plugin/components/lcx/skills/lcx-contribute-bug-fix/SKILL.md +51 -55
  197. package/packages/omo-codex/plugin/components/lcx/skills/lcx-contribute-bug-fix/agents/openai.yaml +1 -1
  198. package/packages/omo-codex/plugin/components/lcx/skills/lcx-report-bug/SKILL.md +7 -7
  199. package/packages/omo-codex/plugin/components/lcx/skills/lcx-report-bug/agents/openai.yaml +1 -1
  200. package/packages/omo-codex/plugin/components/lsp/dist/.omo-runtime-manifest.json +2 -2
  201. package/packages/omo-codex/plugin/components/lsp/hooks/hooks.json +2 -2
  202. package/packages/omo-codex/plugin/components/lsp/package.json +1 -1
  203. package/packages/omo-codex/plugin/components/rules/hooks/hooks.json +4 -4
  204. package/packages/omo-codex/plugin/components/rules/package.json +1 -1
  205. package/packages/omo-codex/plugin/components/teammode/hooks/hooks.json +1 -1
  206. package/packages/omo-codex/plugin/components/teammode/package.json +2 -2
  207. package/packages/omo-codex/plugin/components/telemetry/README.md +2 -2
  208. package/packages/omo-codex/plugin/components/telemetry/dist/cli.js +52 -15
  209. package/packages/omo-codex/plugin/components/telemetry/dist/posthog.js +52 -19
  210. package/packages/omo-codex/plugin/components/telemetry/hooks/hooks.json +1 -1
  211. package/packages/omo-codex/plugin/components/telemetry/package.json +1 -1
  212. package/packages/omo-codex/plugin/components/telemetry/src/codex-hook.ts +2 -9
  213. package/packages/omo-codex/plugin/components/telemetry/src/posthog.ts +4 -13
  214. package/packages/omo-codex/plugin/components/telemetry/src/product-identity.ts +0 -2
  215. package/packages/omo-codex/plugin/components/telemetry/test/codex-hook-diagnostics.test.ts +0 -3
  216. package/packages/omo-codex/plugin/components/telemetry/test/codex-hook.test.ts +3 -8
  217. package/packages/omo-codex/plugin/components/ultrawork/dist/cli.js +3 -0
  218. package/packages/omo-codex/plugin/components/ultrawork/hooks/hooks.json +1 -1
  219. package/packages/omo-codex/plugin/components/ultrawork/package.json +1 -1
  220. package/packages/omo-codex/plugin/components/ultrawork/src/directive-content.ts +1 -1
  221. package/packages/omo-codex/plugin/components/ulw-execute-continuation/hooks/hooks.json +1 -1
  222. package/packages/omo-codex/plugin/components/ulw-execute-continuation/package.json +1 -1
  223. package/packages/omo-codex/plugin/components/ulw-loop/directive.md +3 -0
  224. package/packages/omo-codex/plugin/components/ulw-loop/hooks/hooks.json +5 -5
  225. package/packages/omo-codex/plugin/components/ulw-loop/package.json +1 -1
  226. package/packages/omo-codex/plugin/hooks/post-compact-resetting-git-bash-mcp-reminder.json +1 -1
  227. package/packages/omo-codex/plugin/hooks/post-compact-resetting-lsp-diagnostics-cache.json +1 -1
  228. package/packages/omo-codex/plugin/hooks/post-compact-resetting-project-rule-cache.json +1 -1
  229. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-comments.json +1 -1
  230. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-lsp-diagnostics.json +1 -1
  231. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-thread-title-hygiene.json +1 -1
  232. package/packages/omo-codex/plugin/hooks/post-tool-use-matching-project-rules.json +1 -1
  233. package/packages/omo-codex/plugin/hooks/post-tool-use-recording-spawn-admission.json +1 -1
  234. package/packages/omo-codex/plugin/hooks/pre-tool-use-enforcing-unlimited-goal-budget.json +1 -1
  235. package/packages/omo-codex/plugin/hooks/pre-tool-use-guarding-ulw-loop-spawns.json +1 -1
  236. package/packages/omo-codex/plugin/hooks/pre-tool-use-recommending-git-bash-mcp.json +1 -1
  237. package/packages/omo-codex/plugin/hooks/session-start-checking-auto-update.json +1 -1
  238. package/packages/omo-codex/plugin/hooks/session-start-checking-bootstrap-provisioning.json +1 -1
  239. package/packages/omo-codex/plugin/hooks/session-start-loading-project-rules.json +1 -1
  240. package/packages/omo-codex/plugin/hooks/session-start-recording-session-telemetry.json +1 -1
  241. package/packages/omo-codex/plugin/hooks/stop-checking-ulw-execute-continuation.json +1 -1
  242. package/packages/omo-codex/plugin/hooks/stop-checking-ulw-loop-resume.json +1 -1
  243. package/packages/omo-codex/plugin/hooks/subagent-stop-verifying-lazycodex-executor-evidence.json +1 -1
  244. package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ultrawork-trigger.json +1 -1
  245. package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ulw-loop-steering.json +1 -1
  246. package/packages/omo-codex/plugin/hooks/user-prompt-submit-loading-project-rules.json +1 -1
  247. package/packages/omo-codex/plugin/package-lock.json +16 -16
  248. package/packages/omo-codex/plugin/package.json +1 -1
  249. package/packages/omo-codex/plugin/shared/package.json +1 -1
  250. package/packages/omo-codex/plugin/skills/ast-grep/README.md +1 -1
  251. package/packages/omo-codex/plugin/skills/ast-grep/install.ps1 +5 -3
  252. package/packages/omo-codex/plugin/skills/ast-grep/install.sh +4 -5
  253. package/packages/omo-codex/plugin/skills/ast-grep/references/install.md +2 -2
  254. package/packages/omo-codex/plugin/skills/ast-grep/scripts/ast_grep_helper.py +13 -3
  255. package/packages/omo-codex/plugin/skills/browser/runtime/omowright/manifest.json +1 -1
  256. package/packages/omo-codex/plugin/skills/coding-agent-sessions/AGENTS.md +5 -4
  257. package/packages/omo-codex/plugin/skills/coding-agent-sessions/SKILL.md +11 -11
  258. package/packages/omo-codex/plugin/skills/coding-agent-sessions/references/all-platforms.md +2 -2
  259. package/packages/omo-codex/plugin/skills/coding-agent-sessions/references/codex.md +4 -4
  260. package/packages/omo-codex/plugin/skills/coding-agent-sessions/scripts/agent_sessions/cli.py +1 -1
  261. package/packages/omo-codex/plugin/skills/coding-agent-sessions/scripts/find-agent-sessions.py +6 -3
  262. package/packages/omo-codex/plugin/skills/frontend/ATTRIBUTION.md +1 -1
  263. package/packages/omo-codex/plugin/skills/frontend/SKILL.md +34 -12
  264. package/packages/omo-codex/plugin/skills/frontend/references/design/README.md +2 -2
  265. package/packages/omo-codex/plugin/skills/frontend/references/design/_INDEX.md +2 -1
  266. package/packages/omo-codex/plugin/skills/frontend/references/design/component-catalogs.md +14 -10
  267. package/packages/omo-codex/plugin/skills/frontend/references/design/design-system-architecture.md +19 -6
  268. package/packages/omo-codex/plugin/skills/frontend/references/design/grounding.md +29 -0
  269. package/packages/omo-codex/plugin/skills/frontend/references/ui-ux-db/scripts/design_system.py +2 -0
  270. package/packages/omo-codex/plugin/skills/frontend/references/ui-ux-db/scripts/search.py +2 -0
  271. package/packages/omo-codex/plugin/skills/lcx-contribute-bug-fix/SKILL.md +51 -55
  272. package/packages/omo-codex/plugin/skills/lcx-contribute-bug-fix/agents/openai.yaml +1 -1
  273. package/packages/omo-codex/plugin/skills/lcx-report-bug/SKILL.md +7 -7
  274. package/packages/omo-codex/plugin/skills/lcx-report-bug/agents/openai.yaml +1 -1
  275. package/packages/omo-codex/plugin/skills/ultimate-browsing/SKILL.md +5 -4
  276. package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/AGENTS.md +2 -2
  277. package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/__main__.py +9 -6
  278. package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/summary.py +1 -1
  279. package/packages/omo-codex/plugin/skills/ultimate-browsing/references/insane-search/README.md +4 -4
  280. package/packages/omo-codex/plugin/skills/ultimate-browsing/scripts/extract_cookies.py +4 -2
  281. package/packages/omo-codex/plugin/skills/ultrawork/SKILL.md +3 -0
  282. package/packages/omo-codex/plugin/skills/ulw-research/AGENTS.md +8 -4
  283. package/packages/omo-codex/plugin/skills/ulw-research/SKILL.md +7 -259
  284. package/packages/omo-codex/plugin/skills/ulw-research/references/deliverable-phase.md +68 -24
  285. package/packages/omo-codex/plugin/skills/ulw-research/references/fact-ledger-grammar.md +7 -3
  286. package/packages/omo-codex/plugin/skills/ulw-research/references/fact-ledger.md +42 -8
  287. package/packages/omo-codex/plugin/skills/ulw-research/references/fidelity-claim-kinds.md +1 -1
  288. package/packages/omo-codex/plugin/skills/ulw-research/references/markdown-blocks.md +12 -0
  289. package/packages/omo-codex/plugin/skills/ulw-research/references/report-gates.md +14 -6
  290. package/packages/omo-codex/plugin/skills/ulw-research/references/research-collection.md +195 -0
  291. package/packages/omo-codex/plugin/skills/ulw-research/references/research-delivery.md +123 -0
  292. package/packages/omo-codex/plugin/skills/ulw-research/scripts/claim-kinds.mjs +33 -5
  293. package/packages/omo-codex/plugin/skills/ulw-research/scripts/contracts.mjs +16 -15
  294. package/packages/omo-codex/plugin/skills/ulw-research/scripts/conversion-probe.mjs +229 -0
  295. package/packages/omo-codex/plugin/skills/ulw-research/scripts/css-hiding.mjs +158 -0
  296. package/packages/omo-codex/plugin/skills/ulw-research/scripts/css-lite.mjs +4 -2
  297. package/packages/omo-codex/plugin/skills/ulw-research/scripts/entities.mjs +13 -4
  298. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-commands-fill.mjs +96 -0
  299. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-commands-meaning.mjs +26 -30
  300. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-commands-polish.mjs +46 -19
  301. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-commands-verify.mjs +37 -6
  302. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-commands.mjs +40 -62
  303. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-derive.mjs +7 -2
  304. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-fill.mjs +9 -6
  305. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-ledger-kinds.mjs +6 -7
  306. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-locate.mjs +15 -9
  307. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-claims.mjs +4 -3
  308. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-csv.mjs +32 -0
  309. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-lexicon.mjs +1 -1
  310. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-originals.mjs +161 -0
  311. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-policy.mjs +183 -0
  312. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-records.mjs +81 -14
  313. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-snapshot-readability.mjs +49 -0
  314. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-sources.mjs +76 -0
  315. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-trace.mjs +25 -11
  316. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-value-unit.mjs +202 -0
  317. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-values.mjs +40 -39
  318. package/packages/omo-codex/plugin/skills/ulw-research/scripts/gates-fidelity.mjs +8 -6
  319. package/packages/omo-codex/plugin/skills/ulw-research/scripts/gates-static.mjs +10 -1
  320. package/packages/omo-codex/plugin/skills/ulw-research/scripts/html-lite.mjs +6 -2
  321. package/packages/omo-codex/plugin/skills/ulw-research/scripts/html-markup.mjs +2 -2
  322. package/packages/omo-codex/plugin/skills/ulw-research/scripts/html-text-map.mjs +140 -0
  323. package/packages/omo-codex/plugin/skills/ulw-research/scripts/markdown-closers.mjs +126 -0
  324. package/packages/omo-codex/plugin/skills/ulw-research/scripts/md-lite.mjs +1 -1
  325. package/packages/omo-codex/plugin/skills/ulw-research/scripts/number-grammar.mjs +33 -0
  326. package/packages/omo-codex/plugin/skills/ulw-research/scripts/numerals.mjs +18 -7
  327. package/packages/omo-codex/plugin/skills/ulw-research/scripts/outcome-start.mjs +66 -0
  328. package/packages/omo-codex/plugin/skills/ulw-research/scripts/outcome.mjs +11 -22
  329. package/packages/omo-codex/plugin/skills/ulw-research/scripts/placeholder.mjs +18 -6
  330. package/packages/omo-codex/plugin/skills/ulw-research/scripts/polish-apply.mjs +3 -2
  331. package/packages/omo-codex/plugin/skills/ulw-research/scripts/raw-decode.mjs +110 -0
  332. package/packages/omo-codex/plugin/skills/ulw-research/scripts/raw-locator.mjs +47 -0
  333. package/packages/omo-codex/plugin/skills/ulw-research/scripts/report-tools-commands.mjs +1 -1
  334. package/packages/omo-codex/plugin/skills/ulw-research/scripts/report-tools.mjs +7 -1
  335. package/packages/omo-codex/plugin/skills/ulw-research/scripts/source-commands.mjs +87 -29
  336. package/packages/omo-codex/plugin/skills/ulw-research/scripts/source-refresh.mjs +82 -0
  337. package/packages/omo-codex/plugin/skills/ulw-research/scripts/source-snapshot.mjs +73 -69
  338. package/packages/omo-codex/plugin/skills/ulw-research/scripts/source-view.mjs +89 -0
  339. package/packages/omo-codex/plugin/skills/ulw-research/scripts/sources-citation.mjs +203 -0
  340. package/packages/omo-codex/plugin/skills/ulw-research/scripts/units.mjs +14 -3
  341. package/packages/omo-codex/plugin/test/payload-equivalence.test.mjs +6 -4
  342. package/packages/omo-codex/plugin/test/sync-skills.test.mjs +4 -3
  343. package/packages/omo-codex/scripts/install-dist/install-local.mjs +338 -237
  344. package/packages/prompts-core/prompts/ultrawork/codex.md +3 -0
  345. package/packages/shared-skills/skill-source-filter.mjs +6 -2
  346. package/packages/shared-skills/skills/ast-grep/README.md +1 -1
  347. package/packages/shared-skills/skills/ast-grep/install.ps1 +5 -3
  348. package/packages/shared-skills/skills/ast-grep/install.sh +4 -5
  349. package/packages/shared-skills/skills/ast-grep/references/install.md +2 -2
  350. package/packages/shared-skills/skills/ast-grep/scripts/ast_grep_helper.py +13 -3
  351. package/packages/shared-skills/skills/browser/runtime/omowright/manifest.json +1 -1
  352. package/packages/shared-skills/skills/coding-agent-sessions/AGENTS.md +5 -4
  353. package/packages/shared-skills/skills/coding-agent-sessions/SKILL.md +11 -11
  354. package/packages/shared-skills/skills/coding-agent-sessions/references/all-platforms.md +2 -2
  355. package/packages/shared-skills/skills/coding-agent-sessions/references/codex.md +4 -4
  356. package/packages/shared-skills/skills/coding-agent-sessions/scripts/agent_sessions/cli.py +1 -1
  357. package/packages/shared-skills/skills/coding-agent-sessions/scripts/find-agent-sessions.py +6 -3
  358. package/packages/shared-skills/skills/frontend/ATTRIBUTION.md +1 -1
  359. package/packages/shared-skills/skills/frontend/SKILL.md +34 -12
  360. package/packages/shared-skills/skills/frontend/references/design/README.md +2 -2
  361. package/packages/shared-skills/skills/frontend/references/design/_INDEX.md +2 -1
  362. package/packages/shared-skills/skills/frontend/references/design/component-catalogs.md +14 -10
  363. package/packages/shared-skills/skills/frontend/references/design/design-system-architecture.md +19 -6
  364. package/packages/shared-skills/skills/frontend/references/design/grounding.md +29 -0
  365. package/packages/shared-skills/skills/frontend/references/ui-ux-db/scripts/design_system.py +2 -0
  366. package/packages/shared-skills/skills/frontend/references/ui-ux-db/scripts/search.py +2 -0
  367. package/packages/shared-skills/skills/ultimate-browsing/SKILL.md +5 -4
  368. package/packages/shared-skills/skills/ultimate-browsing/engine/AGENTS.md +2 -2
  369. package/packages/shared-skills/skills/ultimate-browsing/engine/__main__.py +9 -6
  370. package/packages/shared-skills/skills/ultimate-browsing/engine/summary.py +1 -1
  371. package/packages/shared-skills/skills/ultimate-browsing/references/insane-search/README.md +4 -4
  372. package/packages/shared-skills/skills/ultimate-browsing/scripts/extract_cookies.py +4 -2
  373. package/packages/shared-skills/skills/ulw-research/AGENTS.md +8 -4
  374. package/packages/shared-skills/skills/ulw-research/SKILL.md +7 -259
  375. package/packages/shared-skills/skills/ulw-research/references/deliverable-phase.md +68 -24
  376. package/packages/shared-skills/skills/ulw-research/references/fact-ledger-grammar.md +7 -3
  377. package/packages/shared-skills/skills/ulw-research/references/fact-ledger.md +42 -8
  378. package/packages/shared-skills/skills/ulw-research/references/fidelity-claim-kinds.md +1 -1
  379. package/packages/shared-skills/skills/ulw-research/references/markdown-blocks.md +12 -0
  380. package/packages/shared-skills/skills/ulw-research/references/report-gates.md +14 -6
  381. package/packages/shared-skills/skills/ulw-research/references/research-collection.md +195 -0
  382. package/packages/shared-skills/skills/ulw-research/references/research-delivery.md +123 -0
  383. package/packages/shared-skills/skills/ulw-research/scripts/claim-kinds.mjs +33 -5
  384. package/packages/shared-skills/skills/ulw-research/scripts/claim-kinds.test.ts +37 -2
  385. package/packages/shared-skills/skills/ulw-research/scripts/claim-prefix.test.ts +76 -0
  386. package/packages/shared-skills/skills/ulw-research/scripts/contracts-validation.test.ts +83 -0
  387. package/packages/shared-skills/skills/ulw-research/scripts/contracts.mjs +16 -15
  388. package/packages/shared-skills/skills/ulw-research/scripts/contracts.test.ts +6 -109
  389. package/packages/shared-skills/skills/ulw-research/scripts/conversion-altered.test.ts +154 -0
  390. package/packages/shared-skills/skills/ulw-research/scripts/conversion-originals.test.ts +297 -0
  391. package/packages/shared-skills/skills/ulw-research/scripts/conversion-probe-classes.test.ts +361 -0
  392. package/packages/shared-skills/skills/ulw-research/scripts/conversion-probe.mjs +229 -0
  393. package/packages/shared-skills/skills/ulw-research/scripts/conversion-probe.test.ts +218 -0
  394. package/packages/shared-skills/skills/ulw-research/scripts/copy-returns-fidelity-policy.test.ts +156 -0
  395. package/packages/shared-skills/skills/ulw-research/scripts/copy-returns-polish.test.ts +10 -9
  396. package/packages/shared-skills/skills/ulw-research/scripts/css-hiding.mjs +158 -0
  397. package/packages/shared-skills/skills/ulw-research/scripts/css-lite.mjs +4 -2
  398. package/packages/shared-skills/skills/ulw-research/scripts/delivery-barrier-meaning.test.ts +186 -0
  399. package/packages/shared-skills/skills/ulw-research/scripts/delivery-barrier.test.ts +40 -87
  400. package/packages/shared-skills/skills/ulw-research/scripts/entities.mjs +13 -4
  401. package/packages/shared-skills/skills/ulw-research/scripts/fact-commands-fill.mjs +96 -0
  402. package/packages/shared-skills/skills/ulw-research/scripts/fact-commands-meaning.mjs +26 -30
  403. package/packages/shared-skills/skills/ulw-research/scripts/fact-commands-polish.mjs +46 -19
  404. package/packages/shared-skills/skills/ulw-research/scripts/fact-commands-verify.mjs +37 -6
  405. package/packages/shared-skills/skills/ulw-research/scripts/fact-commands.mjs +40 -62
  406. package/packages/shared-skills/skills/ulw-research/scripts/fact-derive-2.test.ts +149 -0
  407. package/packages/shared-skills/skills/ulw-research/scripts/fact-derive.mjs +7 -2
  408. package/packages/shared-skills/skills/ulw-research/scripts/fact-derive.test.ts +7 -192
  409. package/packages/shared-skills/skills/ulw-research/scripts/fact-fill.mjs +9 -6
  410. package/packages/shared-skills/skills/ulw-research/scripts/fact-ledger-kinds.mjs +6 -7
  411. package/packages/shared-skills/skills/ulw-research/scripts/fact-ledger-kinds.test.ts +2 -1
  412. package/packages/shared-skills/skills/ulw-research/scripts/fact-ledger-legacy.test.ts +2 -1
  413. package/packages/shared-skills/skills/ulw-research/scripts/fact-locate.mjs +15 -9
  414. package/packages/shared-skills/skills/ulw-research/scripts/fact-unit-dimensions-2.test.ts +149 -0
  415. package/packages/shared-skills/skills/ulw-research/scripts/fact-unit-dimensions.test.ts +4 -168
  416. package/packages/shared-skills/skills/ulw-research/scripts/fact-unit-rules.test.ts +3 -229
  417. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-claims.mjs +4 -3
  418. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-csv.mjs +32 -0
  419. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-lexicon.mjs +1 -1
  420. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-originals.mjs +161 -0
  421. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-policy.mjs +183 -0
  422. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-records.mjs +81 -14
  423. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-snapshot-readability.mjs +49 -0
  424. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-sources.mjs +76 -0
  425. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-trace.mjs +25 -11
  426. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-value-unit.mjs +202 -0
  427. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-values.mjs +40 -39
  428. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-values.test.ts +8 -29
  429. package/packages/shared-skills/skills/ulw-research/scripts/gates-fidelity.mjs +8 -6
  430. package/packages/shared-skills/skills/ulw-research/scripts/gates-static-headings.test.ts +17 -0
  431. package/packages/shared-skills/skills/ulw-research/scripts/gates-static.mjs +10 -1
  432. package/packages/shared-skills/skills/ulw-research/scripts/html-lite.mjs +6 -2
  433. package/packages/shared-skills/skills/ulw-research/scripts/html-markup.mjs +2 -2
  434. package/packages/shared-skills/skills/ulw-research/scripts/html-text-map.mjs +140 -0
  435. package/packages/shared-skills/skills/ulw-research/scripts/html-text-map.test.ts +148 -0
  436. package/packages/shared-skills/skills/ulw-research/scripts/markdown-closers.mjs +126 -0
  437. package/packages/shared-skills/skills/ulw-research/scripts/md-lite-boundaries.test.ts +46 -0
  438. package/packages/shared-skills/skills/ulw-research/scripts/md-lite.mjs +1 -1
  439. package/packages/shared-skills/skills/ulw-research/scripts/number-grammar.mjs +33 -0
  440. package/packages/shared-skills/skills/ulw-research/scripts/number-grammar.test.ts +36 -0
  441. package/packages/shared-skills/skills/ulw-research/scripts/numerals.mjs +18 -7
  442. package/packages/shared-skills/skills/ulw-research/scripts/numerals.test.ts +3 -0
  443. package/packages/shared-skills/skills/ulw-research/scripts/outcome-start.mjs +66 -0
  444. package/packages/shared-skills/skills/ulw-research/scripts/outcome-start.test.ts +109 -0
  445. package/packages/shared-skills/skills/ulw-research/scripts/outcome.mjs +11 -22
  446. package/packages/shared-skills/skills/ulw-research/scripts/page-source-locator.test.ts +187 -0
  447. package/packages/shared-skills/skills/ulw-research/scripts/placeholder.mjs +18 -6
  448. package/packages/shared-skills/skills/ulw-research/scripts/polish-apply.mjs +3 -2
  449. package/packages/shared-skills/skills/ulw-research/scripts/polish-markup.test.ts +15 -1
  450. package/packages/shared-skills/skills/ulw-research/scripts/polish-partial.test.ts +42 -0
  451. package/packages/shared-skills/skills/ulw-research/scripts/raw-decode.mjs +110 -0
  452. package/packages/shared-skills/skills/ulw-research/scripts/raw-locator.mjs +47 -0
  453. package/packages/shared-skills/skills/ulw-research/scripts/raw-snapshot.test.ts +139 -0
  454. package/packages/shared-skills/skills/ulw-research/scripts/report-tools-2.test.ts +233 -0
  455. package/packages/shared-skills/skills/ulw-research/scripts/report-tools-3.test.ts +141 -0
  456. package/packages/shared-skills/skills/ulw-research/scripts/report-tools-4.test.ts +135 -0
  457. package/packages/shared-skills/skills/ulw-research/scripts/report-tools-5.test.ts +231 -0
  458. package/packages/shared-skills/skills/ulw-research/scripts/report-tools-commands.mjs +1 -1
  459. package/packages/shared-skills/skills/ulw-research/scripts/report-tools.mjs +7 -1
  460. package/packages/shared-skills/skills/ulw-research/scripts/report-tools.test.ts +6 -560
  461. package/packages/shared-skills/skills/ulw-research/scripts/snapshot-readability.test.ts +116 -0
  462. package/packages/shared-skills/skills/ulw-research/scripts/source-commands.mjs +87 -29
  463. package/packages/shared-skills/skills/ulw-research/scripts/source-integrity.test.ts +243 -0
  464. package/packages/shared-skills/skills/ulw-research/scripts/source-refresh.mjs +82 -0
  465. package/packages/shared-skills/skills/ulw-research/scripts/source-snapshot.mjs +73 -69
  466. package/packages/shared-skills/skills/ulw-research/scripts/source-snapshot.test.ts +7 -4
  467. package/packages/shared-skills/skills/ulw-research/scripts/source-view.mjs +89 -0
  468. package/packages/shared-skills/skills/ulw-research/scripts/sources-citation.mjs +203 -0
  469. package/packages/shared-skills/skills/ulw-research/scripts/sources-citation.test.ts +236 -0
  470. package/packages/shared-skills/skills/ulw-research/scripts/tests/contracts-support.ts +147 -0
  471. package/packages/shared-skills/skills/ulw-research/scripts/tests/delivery-barrier-session.ts +59 -0
  472. package/packages/shared-skills/skills/ulw-research/scripts/tests/derive-clock.ts +4 -0
  473. package/packages/shared-skills/skills/ulw-research/scripts/tests/fact-derive-support.ts +65 -0
  474. package/packages/shared-skills/skills/ulw-research/scripts/tests/fact-unit-dimensions-support.ts +41 -0
  475. package/packages/shared-skills/skills/ulw-research/scripts/tests/fact-unit-rules-support.ts +241 -0
  476. package/packages/shared-skills/skills/ulw-research/scripts/tests/fidelity-values-support.ts +30 -0
  477. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/conversion-probe.html +14 -0
  478. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/sources-shapes.html +20 -0
  479. package/packages/shared-skills/skills/ulw-research/scripts/tests/numeral-classes.ts +196 -0
  480. package/packages/shared-skills/skills/ulw-research/scripts/tests/report-tools-support.ts +182 -0
  481. package/packages/shared-skills/skills/ulw-research/scripts/tests/value-mode-support.ts +48 -0
  482. package/packages/shared-skills/skills/ulw-research/scripts/units.mjs +14 -3
  483. package/packages/shared-skills/skills/ulw-research/scripts/units.test.ts +10 -0
  484. package/packages/shared-skills/skills/ulw-research/scripts/value-mode-unit-classes.test.ts +255 -0
  485. package/packages/shared-skills/skills/ulw-research/scripts/value-mode-unit.test.ts +179 -0
  486. package/packages/shared-skills/skills/ulw-research/scripts/value-rules-cli.test.ts +157 -0
  487. package/packages/shared-skills/skills/ulw-research/scripts/value-rules-followups.test.ts +72 -0
  488. package/packages/shared-skills/skills/ulw-research/scripts/value-rules-priority.test.ts +80 -0
  489. package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/tests/fixtures/amp_redirect_stub.html +0 -7
  490. package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/tests/fixtures/search_interstitial.html +0 -19
  491. package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/tests/fixtures/wayback_available.json +0 -1
  492. package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/tests/fixtures/wayback_snapshot.html +0 -1128
  493. package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/tests/test_fetch_chain.py +0 -77
  494. package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/tests/test_playwright_stealth.py +0 -86
  495. package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/tests/test_playwright_templates.py +0 -279
  496. package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/tests/test_surrogate.py +0 -252
  497. package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/tests/test_surrogate_validators.py +0 -78
@@ -0,0 +1,66 @@
1
+ // outcome-start.mjs - the run clock (outcome.json startedAt). `outcome init` is its one writer and
2
+ // records it once: --started-at from the orchestrator, else the startedAt of a valid manifest an
3
+ // earlier init wrote (a re-init keeps the clock), else terminal-state.json.started_at when a
4
+ // supervisor wrote one, else the YYYYMMDD-HHMMSS session dir name. A recorded value must be a real
5
+ // ISO time with Z or an offset, not after `now`, and within a day of a stamped dir name; otherwise
6
+ // init refuses instead of falling back. Never the interview's as-of date. No clock reads.
7
+ import { readFileSync } from "node:fs"
8
+ import { basename, join, resolve } from "node:path"
9
+
10
+ const ISO = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.\d+)?(?:Z|[+-](\d{2}):(\d{2}))$/
11
+ const SESSION_STAMP = /^(\d{4})(\d{2})(\d{2})-(\d{2})(\d{2})(\d{2})$/
12
+ const DAY_MS = 86_400_000
13
+
14
+ /** A real calendar time (no 2026-02-30, no T24:00), or null. */
15
+ function strictIso(value) {
16
+ const match = typeof value === "string" ? ISO.exec(value) : null
17
+ if (!match) return null
18
+ const [y, mo, d, h, mi, s, oh = 0, om = 0] = match.slice(1).map((part) => Number(part ?? 0))
19
+ const day = new Date(Date.UTC(y, mo - 1, d))
20
+ if (day.getUTCMonth() !== mo - 1 || day.getUTCDate() !== d || h > 23 || mi > 59 || s > 59 || oh > 23 || om > 59) return null
21
+ const date = new Date(value)
22
+ return Number.isNaN(date.getTime()) ? null : date
23
+ }
24
+
25
+ /** Local start time from a YYYYMMDD-HHMMSS dir name, null when the name is not a stamp. */
26
+ function dirStamp(sessionDir, fail) {
27
+ const match = SESSION_STAMP.exec(basename(sessionDir))
28
+ if (!match) return null
29
+ const [y, mo, d, h, mi, s] = match.slice(1).map(Number)
30
+ const date = new Date(y, mo - 1, d, h, mi, s)
31
+ if (date.getMonth() !== mo - 1 || date.getDate() !== d || date.getHours() !== h) fail(`session dir basename is not a real local time: ${sessionDir}`)
32
+ return date
33
+ }
34
+
35
+ function readJson(sessionDir, file) {
36
+ try {
37
+ return { data: JSON.parse(readFileSync(join(sessionDir, file), "utf8")) }
38
+ } catch (error) {
39
+ if (error.code === "ENOENT" || error.code === "ENOTDIR") return null
40
+ if (error instanceof SyntaxError) return { corrupt: `${join(sessionDir, file)} is not valid JSON (${error.message})` }
41
+ throw error
42
+ }
43
+ }
44
+
45
+ /**
46
+ * @param {{ sessionDir: string, startedAt?: string, now?: Date, validManifest: (data: unknown) => boolean, fail: (message: string) => never }} input
47
+ * @returns {Date}
48
+ */
49
+ export function runStart({ sessionDir, startedAt, now, validManifest, fail }) {
50
+ const dir = dirStamp(sessionDir, fail)
51
+ const check = (value, location) => {
52
+ const date = strictIso(value)
53
+ if (!date) fail(`invalid run timestamp in ${location}: ${JSON.stringify(value)} is not a real ISO time with Z or an offset`)
54
+ if (now && date > now) fail(`run timestamp in ${location} (${value}) is after now (${now.toISOString()})`)
55
+ if (dir && Math.abs(date - dir) > DAY_MS) fail(`run timestamp in ${location} (${value}) is not within a day of the session dir name ${basename(sessionDir)}: a copied or stale value`)
56
+ return date
57
+ }
58
+ if (startedAt !== undefined) return check(startedAt, "--started-at")
59
+ const manifest = readJson(sessionDir, "outcome.json")
60
+ if (manifest?.data && validManifest(manifest.data) && resolve(String(manifest.data.sessionDir)) === resolve(sessionDir)) return check(manifest.data.startedAt, "outcome.json.startedAt")
61
+ const terminal = readJson(sessionDir, "terminal-state.json")
62
+ if (terminal?.corrupt) fail(terminal.corrupt)
63
+ if (terminal?.data?.started_at !== undefined) return check(terminal.data.started_at, "terminal-state.json.started_at")
64
+ if (dir) return dir
65
+ return fail(`missing run timestamp: pass --started-at <ISO>, record terminal-state.json.started_at, or name the session dir YYYYMMDD-HHMMSS (${sessionDir})`)
66
+ }
@@ -0,0 +1,109 @@
1
+ import { describe, expect, onTestFinished, test } from "bun:test"
2
+ import { spawnSync } from "node:child_process"
3
+ import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs"
4
+ import { tmpdir } from "node:os"
5
+ import { join } from "node:path"
6
+
7
+ import { validateOutcomeManifest } from "./contracts.mjs"
8
+ import { initManifest } from "./outcome.mjs"
9
+ import { sampleManifest } from "./tests/contracts-support"
10
+
11
+ const START = "2026-10-25T12:52:01.832Z"
12
+ const cli = join(import.meta.dir, "report-tools.mjs")
13
+
14
+ function directory(name = "E") {
15
+ const root = mkdtempSync(join(tmpdir(), "outcome-start-"))
16
+ onTestFinished(() => rmSync(root, { recursive: true, force: true }))
17
+ const dir = join(root, name)
18
+ mkdirSync(dir)
19
+ return dir
20
+ }
21
+
22
+ function init(dir: string, ...extra: string[]) {
23
+ return spawnSync("node", [cli, "outcome", "init", "--session-dir", dir, "--promised", "html", "--now", START, ...extra], { encoding: "utf8" })
24
+ }
25
+ const startedAt = (dir: string) => JSON.parse(readFileSync(join(dir, "outcome.json"), "utf8")).startedAt
26
+ const manifestAt = (dir: string, value: string) => writeFileSync(join(dir, "outcome.json"), JSON.stringify({ ...sampleManifest(), sessionDir: dir, startedAt: value }))
27
+
28
+ describe("outcome init is the one writer of the run clock", () => {
29
+ test("#given --started-at from the orchestrator #when init runs in an unstamped dir #then that value is recorded", () => {
30
+ const dir = directory()
31
+ expect(init(dir, "--started-at", START).status).toBe(0)
32
+ expect(startedAt(dir)).toBe(START)
33
+ })
34
+
35
+ test("#given a supervisor's terminal-state.json #when init runs #then started_at is recorded", () => {
36
+ const dir = directory()
37
+ writeFileSync(join(dir, "terminal-state.json"), JSON.stringify({ started_at: START }))
38
+ expect(init(dir).status).toBe(0)
39
+ expect(startedAt(dir)).toBe(START)
40
+ })
41
+
42
+ test("#given no flag, no metadata, and no stamped dir name #when init runs #then it refuses and names every source", () => {
43
+ const result = init(directory())
44
+ expect(result.status).toBe(1)
45
+ expect(result.stderr).toMatch(/--started-at/)
46
+ })
47
+
48
+ test("#given only a stamped dir name #when init runs #then the dir name is the fallback clock", () => {
49
+ const dir = directory("20260101-120000")
50
+ expect(init(dir).status).toBe(0)
51
+ expect(startedAt(dir)).toMatch(/^2026-01-01T/)
52
+ })
53
+
54
+ test("#given a valid manifest from an earlier init #when init runs again #then the recorded clock survives", () => {
55
+ const dir = directory("20261025-120000")
56
+ const first = "2026-10-25T03:00:00.000Z"
57
+ manifestAt(dir, first)
58
+ expect(init(dir).status).toBe(0)
59
+ expect(startedAt(dir)).toBe(first)
60
+ })
61
+ })
62
+
63
+ describe("a recorded run clock is validated, never trusted raw", () => {
64
+ test("#given a manifest copied with a 2020 startedAt into a 20260104-120000 dir #when init runs #then it refuses", () => {
65
+ const dir = directory("20260104-120000")
66
+ manifestAt(dir, "2020-01-01T00:00:00.000Z")
67
+ const result = init(dir)
68
+ expect(result.status).toBe(1)
69
+ expect(result.stderr).toMatch(/outcome\.json\.startedAt/)
70
+ })
71
+
72
+ for (const value of ["not-a-time", "2099-01-01T00:00:00Z", "2026-02-30T12:00:00Z", "2026-10-25T24:00:00Z", "2026-10-25 12:00:00"]) {
73
+ test(`#given started_at ${value} with --now ${START} #when init runs #then it refuses without falling back`, () => {
74
+ const dir = directory("20261025-120000")
75
+ writeFileSync(join(dir, "terminal-state.json"), JSON.stringify({ started_at: value }))
76
+ const result = init(dir)
77
+ expect(result.status).toBe(1)
78
+ expect(result.stderr).toMatch(/terminal-state\.json\.started_at/)
79
+ })
80
+ }
81
+
82
+ test("#given a valid manifest copied from another session into an unstamped dir #when init runs #then its clock is not reused", () => {
83
+ const dir = directory()
84
+ writeFileSync(join(dir, "outcome.json"), JSON.stringify({ ...sampleManifest(), sessionDir: "/elsewhere/E", startedAt: "2020-01-01T00:00:00.000Z" }))
85
+ const result = init(dir)
86
+ expect(result.status).toBe(1)
87
+ expect(result.stderr).toMatch(/--started-at/)
88
+ })
89
+
90
+ test("#given a corrupt terminal-state.json #when init runs #then the error names the file", () => {
91
+ const dir = directory("20261025-120000")
92
+ writeFileSync(join(dir, "terminal-state.json"), "{")
93
+ const result = init(dir)
94
+ expect(result.status).toBe(1)
95
+ expect(result.stderr).toContain("terminal-state.json")
96
+ })
97
+
98
+ test("#given a corrupt outcome.json #when init runs #then init overwrites it, as before", () => {
99
+ const dir = directory("20261025-120000")
100
+ writeFileSync(join(dir, "outcome.json"), "{")
101
+ expect(init(dir).status).toBe(0)
102
+ expect(validateOutcomeManifest(JSON.parse(readFileSync(join(dir, "outcome.json"), "utf8"))).ok).toBe(true)
103
+ })
104
+
105
+ test("#given the legacy library call with a stamped dir #when initialized #then the manifest validates", () => {
106
+ const manifest = initManifest({ sessionDir: "/nonexistent/20260101-120000", promised: [], lane: null })
107
+ expect(validateOutcomeManifest(manifest)).toEqual({ ok: true, errors: [] })
108
+ })
109
+ })
@@ -1,14 +1,13 @@
1
1
  // outcome.mjs - the per-run outcome manifest (`outcome.json`) and the closing briefing. Manifest functions are pure (they return a new manifest).
2
2
  // The briefing reads only the manifest, the sources-ledger text, and an injected `now`. node: builtins only.
3
-
4
3
  import * as nodeFs from "node:fs"
5
4
  import { basename, dirname, join } from "node:path"
6
5
 
7
6
  import { DELIVERABLE_FORMATS, DELIVERABLE_STATUSES, GATE_NAMES, GATE_STATUSES, LANES, MANIFEST_CODES, normalizeManifest, SCHEMA_VERSION, validateOutcomeManifest } from "./contracts.mjs"
7
+ import { runStart } from "./outcome-start.mjs"
8
8
 
9
9
  export const MANIFEST_FILENAME = "outcome.json"
10
10
  const MISSING = MANIFEST_CODES[0]
11
- const SESSION_STAMP = /^(\d{4})(\d{2})(\d{2})-(\d{2})(\d{2})(\d{2})$/
12
11
  const SOURCE_LINE = /^\[(S|Source )\d+\]/
13
12
  const URL_PATTERN = /https?:\/\/[^\s)<>\]"'`]+/g
14
13
  const DRAFT_MARKER = "STATUS: draft"
@@ -33,26 +32,20 @@ function assertOneOf(code, label, value, allowed) {
33
32
 
34
33
  const clone = (manifest) => structuredClone(manifest)
35
34
  const isMissing = (error) => error?.code === "ENOENT" || error?.code === "ENOTDIR"
36
-
37
35
  function stamp(now) {
38
36
  return (now ?? new Date()).toISOString()
39
37
  }
40
-
41
- /** Local wall-clock start time from a `YYYYMMDD-HHMMSS` session dir basename. */
42
- export function parseSessionStart(sessionDir) {
43
- const match = SESSION_STAMP.exec(basename(sessionDir))
44
- if (!match) throw new OutcomeError("invalid_session_dir", `session dir basename must be YYYYMMDD-HHMMSS: ${sessionDir}`)
45
- const [y, mo, d, h, mi, s] = match.slice(1).map(Number)
46
- const date = new Date(y, mo - 1, d, h, mi, s)
47
- if (date.getMonth() !== mo - 1 || date.getDate() !== d || date.getHours() !== h) {
48
- throw new OutcomeError("invalid_session_dir", `session dir basename is not a real local time: ${sessionDir}`)
38
+ /** The run clock outcome init records (outcome-start.mjs owns the contract). */
39
+ export function parseSessionStart(sessionDir, { startedAt, now } = {}) {
40
+ const fail = (message) => {
41
+ throw new OutcomeError("invalid_session_dir", message)
49
42
  }
50
- return date
43
+ return runStart({ sessionDir, startedAt, now, validManifest: (data) => validateOutcomeManifest(normalizeManifest(data)).ok, fail })
51
44
  }
52
45
 
53
- /** @param {{ sessionDir: string, promised: string[], lane: string|null, interview?: object|null, now?: Date }} input */
54
- export function initManifest({ sessionDir, promised, lane, interview = null, now }) {
55
- const startedAt = parseSessionStart(sessionDir).toISOString()
46
+ /** @param {{ sessionDir: string, promised: string[], lane: string|null, interview?: object|null, now?: Date, startedAt?: string }} input */
47
+ export function initManifest({ sessionDir, promised, lane, interview = null, now, startedAt: recorded }) {
48
+ const startedAt = parseSessionStart(sessionDir, { startedAt: recorded, now }).toISOString()
56
49
  if (lane !== null && lane !== undefined) assertOneOf("unknown_lane", "lane", lane, LANES)
57
50
  const formats = [...new Set(promised)]
58
51
  for (const format of formats) assertOneOf("unknown_format", "deliverable format", format, DELIVERABLE_FORMATS)
@@ -74,9 +67,7 @@ export function initManifest({ sessionDir, promised, lane, interview = null, now
74
67
  }
75
68
  }
76
69
 
77
- /**
78
- * Set one format's row; every other row is kept as is. An unpromised format gets a new
79
- * row with promised=false. The row's detail fields are replaced by the ones given.
70
+ /** Set one format's row, keeping other rows. New formats have promised=false.
80
71
  * @param {{ path?: string, reason?: string, pages?: number, bytes?: number, now?: Date }} [detail]
81
72
  */
82
73
  export function setDeliverable(manifest, format, status, detail = {}) {
@@ -143,9 +134,7 @@ export function verifyManifest(manifest, fs = nodeFs) {
143
134
  return { ok: problems.length === 0, problems }
144
135
  }
145
136
 
146
- /**
147
- * none = missing or empty file; partial = `STATUS: draft` in the first 5 lines, or the
148
- * manifest (given, else a sibling outcome.json) still has a pending row; else complete.
137
+ /** Missing/empty = none; draft marker or pending manifest = partial; else complete.
149
138
  * @returns {"none"|"partial"|"complete"}
150
139
  */
151
140
  export function deliverableState(deliverablePath, manifestOrNull) {
@@ -0,0 +1,187 @@
1
+ import { describe, expect, onTestFinished, 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 { readLedger } from "./fact-ledger.mjs"
7
+ import { run } from "./report-tools.mjs"
8
+
9
+ const BRIEF = readFileSync(join(import.meta.dir, "tests/fixtures/fidelity-brief-locked.md"), "utf8")
10
+
11
+ type Row = { fact_id: string; value: string; unit: string; quote: string; raw: string; sentence_id: string; locator: { artifact: string; unit: string; char_start: number; char_end: number } }
12
+ type Defect = { code: string; message: string }
13
+
14
+ /** {{F1}} -> {{F1:<its ledger label>}}: fill requires the label. */
15
+ const labeled = (dir: string) => {
16
+ const labels = new Map(readLedger(dir).rows.map((row: { fact_id: string; label: string }) => [row.fact_id, row.label]))
17
+ return (placeholder: string) => placeholder.replace(/\{\{(\w+)\}\}/, (_, id) => `{{${id}:${labels.get(id)}}}`)
18
+ }
19
+
20
+ function session() {
21
+ const dir = mkdtempSync(join(tmpdir(), "page-source-"))
22
+ onTestFinished(() => rmSync(dir, { recursive: true, force: true }))
23
+ writeFileSync(join(dir, "brief.md"), BRIEF)
24
+ return dir
25
+ }
26
+
27
+ async function snapshotPage(dir: string, bytes: Buffer | string, id = "S1") {
28
+ const file = join(dir, `${id}.capture.html`)
29
+ writeFileSync(file, bytes)
30
+ const result = await run(["source", "snapshot", "--id", id, "--html", file, "--session-dir", dir])
31
+ expect(result.exitCode).toBe(0)
32
+ return Buffer.from(bytes)
33
+ }
34
+
35
+ const find = (dir: string, anchor: string, id = "S1") => run(["fact", "add", "--session-dir", dir, "--source", id, "--find", anchor, "--json"])
36
+ const quote = (dir: string, match: string) => run(["fact", "quote", "--session-dir", dir, "--source", "S1", "--match", match, "--json"])
37
+
38
+ /** Fill a report citing every given placeholder, then verify it; returns the verify result and the filled html. */
39
+ async function fillAndVerify(dir: string, placeholders: string[]) {
40
+ const body = [
41
+ '<!doctype html><html lang="en"><head><meta charset="utf-8"><title>Bridge</title></head><body><h1>Bridge</h1>',
42
+ "<h2>Findings</h2>",
43
+ ...placeholders.map(labeled(dir)).map((placeholder) => `<p>The source states ${placeholder} [S1].</p>`),
44
+ "<h2>How this report was made</h2><p>Collected from one cited public page [S1].</p>",
45
+ '<h2>Sources</h2><ol><li id="s1">[S1] Bridge page, retrieved 2026-10-01.</li></ol></body></html>',
46
+ ].join("\n")
47
+ writeFileSync(join(dir, "report.src.html"), body)
48
+ const filled = await run(["fact", "fill", join(dir, "report.src.html"), "--out", join(dir, "report.html"), "--session-dir", dir])
49
+ expect(filled.exitCode).toBe(0)
50
+ const verified = await run(["fact", "verify", join(dir, "report.html"), "--session-dir", dir])
51
+ return { verified, html: readFileSync(join(dir, "report.html"), "utf8") }
52
+ }
53
+
54
+ const codes = (json: { defects: Defect[] }) => json.defects.map((defect) => defect.code)
55
+
56
+ const PAGE = [
57
+ "<!doctype html><html><head><title>Deck 9 m</title><style>.deck{font-size:14px}</style><script>var span = 9;</script></head><body>",
58
+ '<p>The main deck spans <span title="9 m" data-v="320">320</span> m in total.</p>',
59
+ '<p>The upper deck spans <span style="font-size:14px">330</span> m.</p>',
60
+ "</body></html>",
61
+ ].join("")
62
+
63
+ describe("page sources locate on visible text nodes only", () => {
64
+ test("#given numbers in title, data and style attributes #when fact add --find runs #then the text-node value is recorded and verifies", async () => {
65
+ const dir = session()
66
+ const raw = await snapshotPage(dir, PAGE)
67
+ const main = await find(dir, "main deck spans")
68
+ const upper = await find(dir, "upper deck spans")
69
+ expect(main.exitCode).toBe(0)
70
+ const row: Row = main.json.data.row
71
+ expect([row.value, row.unit, row.quote]).toEqual(["320", "m", "The main deck spans 320 m in total."])
72
+ expect(row.locator).toMatchObject({ artifact: "raw.bin", unit: "bytes" })
73
+ const span = raw.subarray(row.locator.char_start, row.locator.char_end).toString("utf8")
74
+ expect(span).toBe('The main deck spans <span title="9 m" data-v="320">320</span> m in total.')
75
+ expect([upper.json.data.row.value, upper.json.data.row.unit]).toEqual(["330", "m"])
76
+ const { verified, html } = await fillAndVerify(dir, ["{{F1}}", "{{F2}}"])
77
+ expect(verified.exitCode).toBe(0)
78
+ expect(html).toContain("320 m")
79
+ expect(html).not.toContain(">9 m<")
80
+ })
81
+
82
+ test("#given the same anchor in a hidden element and a visible paragraph #when --find runs #then only the visible number is located", async () => {
83
+ const dir = session()
84
+ const hidden = [
85
+ '<div style="display:none"><p>Visible length is 999 m.</p></div>',
86
+ '<div style="color:red; visibility: hidden"><p>Shadow length is 998 m.</p></div>',
87
+ '<p aria-hidden="true">Ghost length is 997 m.</p>',
88
+ "<p hidden>Secret length is 996 m.</p>",
89
+ "<noscript>Script length is 995 m.</noscript>",
90
+ "<template><p>Template length is 994 m.</p></template>",
91
+ ].join("")
92
+ await snapshotPage(dir, `<html><head><script>Inline length is 993 m.</script><style>.x{}</style></head><body>${hidden}<p>Visible length is 410 m.</p></body></html>`)
93
+ const visible = await find(dir, "Visible length is")
94
+ expect(visible.exitCode).toBe(0)
95
+ expect(visible.json.data.row.value).toBe("410")
96
+ for (const anchor of ["Shadow length is", "Ghost length is", "Secret length is", "Script length is", "Template length is", "Inline length is"]) {
97
+ const result = await find(dir, anchor)
98
+ expect([anchor, result.json.error]).toEqual([anchor, "fact_not_located"])
99
+ }
100
+ expect(readFileSync(join(dir, "sources/S1/text.txt"), "utf8")).toBe("Visible length is 410 m.")
101
+ })
102
+
103
+ test("#given elements hidden by <style> rules on a class, an id, or a tag #when --find runs #then they are not located", async () => {
104
+ const dir = session()
105
+ const style = "<style>.d-none, .sr-only { display: none } #gone{visibility:hidden} footer{color:red;display:none} div p.keep{display:none}</style>"
106
+ const body = '<p class="lead d-none">Class length is 999 m.</p><p id="gone">Id length is 998 m.</p><footer>Tag length is 997 m.</footer><p class="keep">Kept length is 410 m.</p>'
107
+ await snapshotPage(dir, `<html><head>${style}</head><body>${body}</body></html>`)
108
+ for (const anchor of ["Class length is", "Id length is", "Tag length is"]) expect([anchor, (await find(dir, anchor)).json.error]).toEqual([anchor, "fact_not_located"])
109
+ expect((await find(dir, "Kept length is")).json.data.row.value).toBe("410")
110
+ })
111
+
112
+ test("#given rules inside @media (a responsive or a print copy) #when --find runs #then the text is located, and an unconditional rule still hides", async () => {
113
+ const dir = session()
114
+ const style = "<style>@media (max-width:600px){.desk{display:none}} @media print{.screen{display:none}} .d-none{display:none}</style>"
115
+ const body = '<p class="desk">Desktop length is 410 m.</p><p class="screen">Screen length is 411 m.</p><p class="d-none">Mobile length is 412 m.</p>'
116
+ await snapshotPage(dir, `<html><head>${style}</head><body>${body}</body></html>`)
117
+ const desktop = await find(dir, "Desktop length is")
118
+ expect(desktop.exitCode).toBe(0)
119
+ expect(desktop.json.data.row.value).toBe("410")
120
+ expect((await find(dir, "Screen length is")).json.data.row.value).toBe("411")
121
+ expect((await find(dir, "Mobile length is")).json.error).toBe("fact_not_located")
122
+ })
123
+
124
+ test("#given a quote across inline markup #when fact quote --match runs #then the quotation is that sentence, indexed like sentences.jsonl", async () => {
125
+ const dir = session()
126
+ await snapshotPage(dir, "<!doctype html><html><head><script>var x = 1;</script></head><body><p>Construction opens <em>no earlier</em> than 2027. Funding is pending.</p></body></html>")
127
+ const result = await quote(dir, "opens no earlier than 2027")
128
+ expect(result.exitCode).toBe(0)
129
+ const row: Row = result.json.data.row
130
+ expect(row.quote).toBe("Construction opens no earlier than 2027.")
131
+ const sentences = readFileSync(join(dir, "sources/S1/sentences.jsonl"), "utf8").trim().split("\n").map((line) => JSON.parse(line))
132
+ expect(sentences.find((entry: { sid: string }) => entry.sid === row.sentence_id)?.text).toBe(row.quote)
133
+ const { verified, html } = await fillAndVerify(dir, ["{{Q1}}"])
134
+ expect(verified.exitCode).toBe(0)
135
+ expect(html).toContain("Construction opens no earlier than 2027.")
136
+ expect(html).not.toContain("&lt;")
137
+ })
138
+
139
+ test("#given a table cell without </td> #when --table runs #then the quote is the cell text, not the rest of the document", async () => {
140
+ const dir = session()
141
+ const raw = await snapshotPage(dir, '<table id="sizes"><tr><th>Name</th><th>Length</th></tr><tr><td>Deck</td><td><b title="9">320</b> m</tr></table><p>Appendix 9 holds notes.</p>')
142
+ const result = await run(["fact", "add", "--session-dir", dir, "--source", "S1", "--table", "sizes", "--row", "Deck", "--col", "Length", "--json"])
143
+ expect(result.exitCode).toBe(0)
144
+ const row: Row = result.json.data.row
145
+ expect([row.value, row.unit, row.quote]).toEqual(["320", "m", "320 m"])
146
+ expect(raw.subarray(row.locator.char_start, row.locator.char_end).toString()).toBe('320</b> m')
147
+ expect((await fillAndVerify(dir, ["{{F1}}"])).verified.exitCode).toBe(0)
148
+ })
149
+ })
150
+
151
+ describe("byte offsets count the source's own encoding", () => {
152
+ const latin1 = (text: string) => Buffer.from(text, "latin1")
153
+
154
+ test("#given a declared ISO-8859-1 page with Caf\\xE9 before the value #when added and verified unchanged #then offsets hit the real bytes and verify is clean", async () => {
155
+ const dir = session()
156
+ const raw = await snapshotPage(dir, latin1('<html><head><meta charset="iso-8859-1"></head><body><p>Caf\u00e9. The span is 320 m.</p></body></html>'))
157
+ const result = await find(dir, "The span is")
158
+ const row: Row = result.json.data.row
159
+ expect(raw.subarray(row.locator.char_start, row.locator.char_end).toString("latin1")).toBe("The span is 320 m.")
160
+ const { verified } = await fillAndVerify(dir, ["{{F1}}"])
161
+ expect(verified.exitCode).toBe(0)
162
+ expect(codes(verified.json)).not.toContain("relocated")
163
+ })
164
+
165
+ test("#given an undeclared Latin-1 byte with more than 32 characters before the value #when verified unchanged #then no false relocation", async () => {
166
+ const dir = session()
167
+ await snapshotPage(dir, latin1("<p>Caf\u00e9 au lait is served here all day long. The span is 320 m.</p>"))
168
+ await find(dir, "The span is")
169
+ const { verified } = await fillAndVerify(dir, ["{{F1}}"])
170
+ expect(verified.exitCode).toBe(0)
171
+ expect(codes(verified.json)).toEqual([])
172
+ })
173
+
174
+ test("#given an EUC-KR page #when added and verified unchanged #then the locator spans the EUC-KR bytes and verify is clean", async () => {
175
+ const dir = session()
176
+ const bytes = Buffer.from("3c68746d6c3e3c686561643e3c6d65746120636861727365743d226575632d6b72223e3c2f686561643e3c626f64793e3c703ec1d620b1b3b7aec0c720b1e6c0ccb4c220333230206d20c0ccb4d92e3c2f703e3c2f626f64793e3c2f68746d6c3e", "hex")
177
+ const raw = await snapshotPage(dir, bytes)
178
+ const result = await find(dir, "\uae38\uc774\ub294")
179
+ expect(result.exitCode).toBe(0)
180
+ const row: Row = result.json.data.row
181
+ expect(row.value).toBe("320")
182
+ expect(raw.subarray(row.locator.char_start, row.locator.char_end).toString("hex")).toBe("c1d620b1b3b7aec0c720b1e6c0ccb4c220333230206d20c0ccb4d92e")
183
+ const { verified } = await fillAndVerify(dir, ["{{F1}}"])
184
+ expect(verified.exitCode).toBe(0)
185
+ expect(codes(verified.json)).toEqual([])
186
+ })
187
+ })
@@ -1,14 +1,16 @@
1
1
  // placeholder.mjs - the fact pointer grammar of the fill step and its render helpers.
2
2
  // Value pointers: {{F12:label}}, {{F12:label:value}}, {{F12:label:unit}}, {{F12:label:q}}.
3
+ // Claim prefixes from CLAIM_KINDS (V, D, A) precede any one pointer: {{D:F12:label:value}}, {{V:Q7:label:lead=18}}.
3
4
  // Quote pointers: {{Q7:label}}, {{Q7:label:lead=18}}, {{Q7:label:from="x":to="y"}} (anchors are
4
5
  // JSON strings, so they may hold a colon or an escaped quote). Labels are compared exactly by the
5
6
  // caller; this module never guesses an id. Pure functions; node: builtins only.
6
7
 
7
8
  import { parseNumeral } from "./numerals.mjs"
9
+ import { CLAIM_KINDS } from "./claim-kinds.mjs"
8
10
 
9
11
  export const ELLIPSIS = "\u2026"
10
12
 
11
- const ID = /^([FQ])\d+$/
13
+ const ID = /^([FQ])[1-9]\d*$/
12
14
  const LABEL = /^[^\s:{}"]+$/
13
15
  const VALUE_MODES = new Set(["value", "unit", "q"])
14
16
  const LEAD = /^lead=([1-9]\d*)$/
@@ -45,6 +47,12 @@ function parseAnchors(tail) {
45
47
  export function parsePlaceholder(token) {
46
48
  if (!token.startsWith("{{") || !token.endsWith("}}")) return { error: "a placeholder is {{ID:label...}}" }
47
49
  const body = token.slice(2, -2)
50
+ const marked = Object.entries(CLAIM_KINDS).find(([, entry]) => entry.prefix && body.startsWith(`${entry.prefix}:`))
51
+ if (marked) {
52
+ const [claimKind, entry] = marked
53
+ const parsed = parsePlaceholder(`{{${body.slice(entry.prefix.length + 1)}}}`)
54
+ return parsed.error || parsed.claimKind ? { error: "a claim prefix must precede one fact or quote pointer" } : { ...parsed, claimKind }
55
+ }
48
56
  const colon = body.indexOf(":")
49
57
  const id = colon === -1 ? body : body.slice(0, colon)
50
58
  const idMatch = ID.exec(id)
@@ -130,7 +138,7 @@ export function lead(quote, n) {
130
138
  /** The value and unit parts of a row's raw text ("4.2 eok won" -> value "4.2 eok", unit "won"). */
131
139
  export function valueParts(row) {
132
140
  const unitText = parseNumeral(row.raw)?.unitRaw ?? row.unit
133
- const valueText = unitText && row.raw.endsWith(unitText) ? row.raw.slice(0, -unitText.length).trimEnd() : (row.value ?? row.raw)
141
+ const valueText = unitText && row.raw.endsWith(unitText) ? row.raw.slice(0, -unitText.length).trimEnd() : row.raw
134
142
  return { valueText, unitText: unitText ?? "" }
135
143
  }
136
144
 
@@ -156,15 +164,19 @@ export function escapeHtml(text) {
156
164
  /** The elements the renderers below emit as fact spans; recognizers build their patterns from this. */
157
165
  export const FACT_ELEMENT_TAGS = Object.freeze(["data", "q", "blockquote"])
158
166
 
159
- export function renderValueHtml(row, text) {
167
+ /** `mode` "value" marks a bare value whose unit verify checks beside it; `claimKind` is the claim mark. */
168
+ export function renderValueHtml(row, text, { mode = "full", claimKind = null } = {}) {
160
169
  const unit = row.unit === null ? "" : ` data-unit="${escapeHtml(row.unit)}"`
161
- return `<data class="fact" value="${escapeHtml(row.value)}" data-fact="${row.fact_id}"${unit} data-lineage="${LINEAGE[row.kind]}">${escapeHtml(text)}</data>`
170
+ const marker = mode === "value" ? ' data-fact-mode="value"' : ""
171
+ const mark = claimKind === null ? "" : ` data-claim-kind="${escapeHtml(claimKind)}"`
172
+ return `<data class="fact" value="${escapeHtml(row.value)}" data-fact="${row.fact_id}"${unit}${marker}${mark} data-lineage="${LINEAGE[row.kind]}">${escapeHtml(text)}</data>`
162
173
  }
163
174
 
164
- export function renderQuote(row, selector, text, format) {
175
+ export function renderQuote(row, selector, text, format, claimKind = null) {
165
176
  const marker = `[${row.source_id}]`
166
177
  if (format === "md") return `"${text}" ${marker}`
167
- return `<q data-fact="${row.fact_id}" data-quote-sel="${escapeHtml(selector)}">${escapeHtml(text)}</q> ${marker}`
178
+ const mark = claimKind === null ? "" : ` data-claim-kind="${escapeHtml(claimKind)}"`
179
+ return `<q data-fact="${row.fact_id}"${mark} data-quote-sel="${escapeHtml(selector)}">${escapeHtml(text)}</q> ${marker}`
168
180
  }
169
181
 
170
182
  const RENDERED_MD_QUOTE = /^"([\s\S]*)" (\[S\d+\])$/u
@@ -54,9 +54,10 @@ export const POLISH_REFERENCE_REASON = "edit touches a character reference: rout
54
54
  /** True when [start, end) overlaps a character reference of html or starts inside one. */
55
55
  export function touchesReference(html, start, end) {
56
56
  for (const match of String(html).matchAll(CHARACTER_REFERENCE)) {
57
- if (match.index < end && match.index + match[0].length > start) return true
57
+ const stop = match.index + match[0].length
58
+ if (match.index < end && (stop > start || (stop === start && !match[0].endsWith(";")))) return true
58
59
  }
59
- return /&[#\w]*$/.test(html.slice(Math.max(0, start - 16), start))
60
+ return false
60
61
  }
61
62
 
62
63
  const escape = (text) => text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")
@@ -1,7 +1,7 @@
1
1
  import { describe, expect, test } from "bun:test"
2
2
 
3
3
  import { markupParts, skeletonChange } from "./html-markup.mjs"
4
- import { applyEdits, POLISH_MARKUP_REASON, textOf } from "./polish-apply.mjs"
4
+ import { applyEdits, POLISH_MARKUP_REASON, POLISH_REFERENCE_REASON, textOf } from "./polish-apply.mjs"
5
5
  import { diffPolish } from "./polish-diff.mjs"
6
6
 
7
7
  const LEDGER = { rows: [{ fact_id: "F1", quote: "Revenue may reach 320 m." }] }
@@ -147,6 +147,20 @@ describe("polish edits never carry markup characters", () => {
147
147
  const AMPD = "<p>We fund R&amp;D labs here.</p>"
148
148
  const TOM = "<p>Tom & Jerry ran.</p>"
149
149
  describe("polish keeps character references intact", () => {
150
+ test.each(["style", "p"])("#given a quoted greater-than in a %s end tag #when its quote changes #then diff blocks", (tag: string) => {
151
+ const before = `<${tag}>p{}</${tag} a='>'> trailing prose`
152
+ const after = before.replace(">'", ">x")
153
+ expect(diffPolish(before, after, LEDGER).map((item: { code: string }) => item.code)).toContain("polish_diff_fail")
154
+ })
155
+
156
+ test.each(["&amp.", `&${"a".repeat(24)}.`])("#given %s immediately before an edit #when extended #then the reference reason is actionable", (reference: string) => {
157
+ const html = `<p>${reference} next</p>`
158
+ const result = applyEdits(html, [{ before: " next", after: "later" }], LEDGER)
159
+ expect(result.rejected[0]?.reason).toContain(POLISH_REFERENCE_REASON)
160
+ expect(result.out).toBe(html)
161
+ expect(diffPolish(html, html.replace(" next", "later"), LEDGER).map((item: { code: string }) => item.code)).toContain("polish_diff_fail")
162
+ })
163
+
150
164
  test.each([
151
165
  ["an edit starting inside an entity", AMPD, "amp;D labs", "lt;D labs"],
152
166
  ["an edit starting right after a bare ampersand", TOM, " Jerry ran", "lt; ran"],
@@ -0,0 +1,42 @@
1
+ import { 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
+ import { spawnSync } from "node:child_process"
6
+
7
+ describe("polish partial-apply CLI contract", () => {
8
+ test.each([
9
+ ["boundary rejection", [{ before: "Fine <em>words", after: "Better words" }], false, 0, 1, 0],
10
+ ["accepted wording", [{ before: "Fine", after: "Good" }], true, 1, 0, 0],
11
+ ["mixed edits", [{ before: "Fine", after: "Good" }, { before: "<em>words", after: "words" }], false, 1, 1, 0],
12
+ ["unlocated wording", [{ before: "Missing", after: "Better" }], false, 0, 0, 1],
13
+ ] as const)("#given %s #when partially applied #then JSON distinguishes full application from output success", (_label, editsInput, applied, count, rejected, unlocated) => {
14
+ const dir = mkdtempSync(join(tmpdir(), "polish-boundary-"))
15
+ try {
16
+ const report = join(dir, "report.html")
17
+ const edits = join(dir, "edits.json")
18
+ const ledger = join(dir, "ledger.json")
19
+ const out = join(dir, "out.html")
20
+ const html = "<p>Fine <em>words</em> here.</p>"
21
+ writeFileSync(report, html)
22
+ writeFileSync(edits, JSON.stringify(editsInput))
23
+ writeFileSync(ledger, JSON.stringify({ schemaVersion: 1, rows: [], events: [] }))
24
+ const result = spawnSync("node", [
25
+ join(import.meta.dir, "report-tools.mjs"), "fact", "polish-apply", report,
26
+ "--edits", edits, "--ledger", ledger, "--out", out,
27
+ "--brief", join(import.meta.dir, "tests/fixtures/fidelity-brief-locked.md"),
28
+ ], { encoding: "utf8" })
29
+ expect(result.error).toBeUndefined()
30
+ const json = JSON.parse(result.stdout)
31
+ expect(json.data).toMatchObject({ applied: count, rejected, unlocated })
32
+ expect(json.defects.map((defect: { code: string }) => defect.code)).toEqual(
33
+ rejected > 0 ? ["polish_edit_rejected"] : unlocated > 0 ? ["polish_edit_unlocated"] : [],
34
+ )
35
+ expect(readFileSync(out, "utf8")).toBe(count > 0 ? html.replace("Fine", "Good") : html)
36
+ expect(result.status).toBe(0)
37
+ expect(json.applied).toBe(applied)
38
+ } finally {
39
+ rmSync(dir, { recursive: true, force: true })
40
+ }
41
+ })
42
+ })