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
@@ -1,9 +1,13 @@
1
- // source-commands.mjs - the `source snapshot` and `claims index` subcommands of report-tools.
2
- // A source dir is immutable: the same id with identical text is a no-op, different text fails
3
- // (exit 1, snapshot_exists), because ledger rows pin the old hash. `--url` fetches with plain
4
- // fetch (30 s timeout, no browser) and records extractor tool:source-snapshot@1; `--html` and
5
- // `--text` import a browser-lane capture and record extractor model (lower trust), or the named
6
- // extractor for `--text --extractor NAME@VERSION`; `--command` runs an argv without a shell and
1
+ // source-commands.mjs - the `source snapshot`, `source refresh` and `claims index` subcommands of
2
+ // report-tools. A source dir is immutable: the same id with identical bytes is a no-op, different
3
+ // bytes fail (exit 1, snapshot_exists), because ledger rows pin the old hash; `source refresh`
4
+ // (source-refresh.mjs) is the one explicit repin. `--replace` swaps in a corrected capture under the
5
+ // same id (the conversion_altered_number recovery) only while no active ledger row cites the id
6
+ // (exit 1, snapshot_pinned, listing them). `--url` fetches with plain fetch (30 s timeout,
7
+ // no browser) and records extractor tool:source-snapshot@1; `--html` imports a browser-lane page
8
+ // capture and `--raw` alone a saved file's bytes (extractor model). `--text` alone registers a converted-only source (no raw bytes:
9
+ // quotes only); `--raw <bytes> --text <extraction> --extractor NAME@VERSION` keeps the original
10
+ // bytes and declares the text their extraction. `--command` runs an argv without a shell and
7
11
  // snapshots its stdout, with the argv, cwd, exit status, and output hashes in command.json.
8
12
  // node: builtins and sibling imports only.
9
13
 
@@ -13,13 +17,18 @@ import { join, resolve } from "node:path"
13
17
 
14
18
  import { indexClaims } from "./claims-index.mjs"
15
19
  import { CliError, parseArgs, sha256Of, writeJsonAtomic } from "./cli-support.mjs"
20
+ import { runConversionProbe } from "./conversion-probe.mjs"
21
+ import { readLedger } from "./fact-ledger.mjs"
16
22
  import { parseFidelityBlock } from "./fidelity-brief.mjs"
23
+ import { parseSourceFloor } from "./fidelity-policy.mjs"
24
+ import { encodingOf, headerCharset } from "./raw-decode.mjs"
17
25
  import { writeSnapshot } from "./source-snapshot.mjs"
26
+ import { readSnapshot } from "./source-view.mjs"
18
27
 
19
28
  const FETCH_TIMEOUT_MS = 30_000
20
29
  const TOOL_EXTRACTOR = "tool:source-snapshot@1"
21
- const SNAPSHOT_USAGE =
22
- "usage: source snapshot --id S<n> (--url <u> | --html <file> | --text <file> [--extractor NAME@VERSION] | --command '<argv json>' --cwd DIR) [--kind page|command|document] [--provenance primary|secondary] [--revision R] --session-dir D"
30
+ export const SNAPSHOT_USAGE =
31
+ "usage: source probe-conversion <raw-file> <converted-file> | source snapshot --id S<n> (--url <u> | --html <file> | --raw <file> [--text <file> --extractor NAME@VERSION] | --text <file> (converted-only) | --command '<argv json>' --cwd DIR) [--charset NAME] [--kind page|command|document|converted-only] [--provenance primary|secondary] [--revision R] [--replace] --session-dir D"
23
32
 
24
33
  function readInput(path) {
25
34
  try {
@@ -36,20 +45,27 @@ export function sessionDirOf(flags) {
36
45
  return resolve(dir)
37
46
  }
38
47
 
39
- /** The snapshot of one source: meta.json, text.txt, and source.html when the source was a page. */
48
+ /** The snapshot as source-view.mjs reads it ({ meta, raw, sha256, drift, view }); unreadable is a usage error. */
40
49
  export function snapshotOf(sessionDir, id) {
41
50
  const dir = join(sessionDir, "sources", String(id))
42
51
  if (!existsSync(join(dir, "meta.json"))) throw new CliError(`source ${id} has no snapshot; run source snapshot --id ${id} first`)
43
- const html = join(dir, "source.html")
44
- return { meta: JSON.parse(readInput(join(dir, "meta.json"))), text: readInput(join(dir, "text.txt")), html: existsSync(html) ? readInput(html) : null }
52
+ try {
53
+ return readSnapshot(dir)
54
+ } catch (error) {
55
+ throw new CliError(`source ${id}: ${error.message}`)
56
+ }
45
57
  }
46
58
 
47
- /** Whether the brief's source floor lists this source as pointed at (`pointed only: a, b`). */
59
+ /**
60
+ * Whether the brief's source floor lists this source as pointed at (`pointed only: a, b`). An absent
61
+ * or pending floor points at nothing; a malformed floor is a usage error (parseSourceFloor's CliError).
62
+ */
48
63
  export function pointedFlag(sessionDir, url) {
49
64
  const brief = join(sessionDir, "brief.md")
50
65
  const floor = existsSync(brief) ? (parseFidelityBlock(readInput(brief)).fields?.["source floor"] ?? "") : ""
51
- const match = /^pointed only:\s*(.+)$/i.exec(floor.trim())
52
- return match ? match[1].split(",").map((entry) => entry.trim()).includes(url) : false
66
+ if (floor === "" || floor === "pending") return false
67
+ const rule = parseSourceFloor(floor)
68
+ return rule?.kind === "pointed" && rule.list.includes(url)
53
69
  }
54
70
 
55
71
  async function fetchSource(url) {
@@ -67,10 +83,10 @@ async function fetchSource(url) {
67
83
  const type = response.headers.get("content-type") ?? ""
68
84
  if (/pdf/i.test(type)) {
69
85
  await response.body?.cancel()
70
- throw new CliError(`pdf response: ${url}; extract its text and use --text <file> --extractor NAME@VERSION`)
86
+ throw new CliError(`pdf response: ${url}; save the bytes and use --raw <file.pdf> --text <extracted.txt> --extractor NAME@VERSION`)
71
87
  }
72
- const body = await response.text()
73
- return /html|xml/i.test(type) || /^\s*</.test(body) ? { url, html: body } : { url, text: body }
88
+ const raw = Buffer.from(await response.arrayBuffer())
89
+ return { url, raw, charset: headerCharset(type), markup: /html|xml/i.test(type) || /^\s*</.test(raw.subarray(0, 1024).toString("latin1")) }
74
90
  }
75
91
 
76
92
  function runCommand(argvJson, cwd) {
@@ -84,35 +100,76 @@ function runCommand(argvJson, cwd) {
84
100
  throw new CliError("--command must be a non-empty JSON array of non-empty strings")
85
101
  }
86
102
  if (!cwd || cwd === true) throw new CliError("--command needs --cwd DIR")
87
- const proc = spawnSync(argv[0], argv.slice(1), { cwd: resolve(cwd), encoding: "utf8", shell: false, maxBuffer: 64 * 1024 * 1024 })
103
+ const proc = spawnSync(argv[0], argv.slice(1), { cwd: resolve(cwd), shell: false, maxBuffer: 64 * 1024 * 1024 })
88
104
  if (proc.error) throw new CliError(`--command could not start: ${proc.error.message}`)
89
105
  const receipt = { argv, cwd: resolve(cwd), exit_status: proc.status, stdout_sha256: sha256Of(proc.stdout), stderr_sha256: sha256Of(proc.stderr) }
90
- return { path: `command:${argv.join(" ")}`, text: proc.stdout, kind: "command", receipt }
106
+ return { path: `command:${argv.join(" ")}`, raw: proc.stdout, kind: "command", receipt }
107
+ }
108
+
109
+ function readBytes(path) {
110
+ try {
111
+ return readFileSync(path)
112
+ } catch (error) {
113
+ throw new CliError(`cannot read ${path}: ${error.code ?? error.message}`)
114
+ }
91
115
  }
92
116
 
93
- async function acquire(flags) {
117
+ /** The snapshot input the flags describe; shared by `source snapshot` and `source refresh`. */
118
+ export async function acquire(flags) {
94
119
  const given = ["url", "html", "text", "command"].filter((name) => flags[name] !== undefined)
95
- if (given.length !== 1) throw new CliError(SNAPSHOT_USAGE)
120
+ if (flags.raw !== undefined && given.some((name) => name !== "text")) throw new CliError("--raw goes alone or with --text (--url, --html and --command already keep the raw bytes)")
121
+ if (given.length + (flags.raw !== undefined && flags.text === undefined ? 1 : 0) !== 1) throw new CliError(SNAPSHOT_USAGE)
96
122
  if (flags.extractor !== undefined && flags.text === undefined) throw new CliError("--extractor goes with --text only")
97
- if (flags.url !== undefined) return { ...(await fetchSource(flags.url)), extractor: TOOL_EXTRACTOR }
98
- if (flags.html !== undefined) return { path: resolve(flags.html), html: readInput(flags.html), extractor: "model" }
99
- if (flags.text !== undefined) return { path: resolve(flags.text), text: readInput(flags.text), extractor: flags.extractor === undefined ? "model" : `extractor:${flags.extractor}` }
123
+ if (flags.charset !== undefined && encodingOf(flags.charset) === null) throw new CliError(`--charset ${flags.charset} is not a known encoding`)
124
+ const charset = flags.charset === undefined ? {} : { charset: encodingOf(flags.charset) }
125
+ if (flags.url !== undefined) return { ...(await fetchSource(flags.url)), ...charset, extractor: TOOL_EXTRACTOR }
126
+ if (flags.html !== undefined) return { path: resolve(flags.html), raw: readBytes(flags.html), markup: true, ...charset, extractor: "model" }
127
+ if (flags.raw !== undefined && flags.text === undefined) return { path: resolve(flags.raw), raw: readBytes(flags.raw), ...charset, extractor: "model" }
128
+ if (flags.text !== undefined) {
129
+ const extractor = flags.extractor === undefined ? "model" : `extractor:${flags.extractor}`
130
+ if (flags.raw !== undefined) return { path: resolve(flags.raw), raw: readBytes(flags.raw), text: readInput(flags.text), ...charset, extractor }
131
+ if (flags.kind !== undefined && flags.kind !== "converted-only") throw new CliError("--text without --raw registers a converted-only source; pass --raw <original bytes> to keep a measurable snapshot")
132
+ return { path: resolve(flags.text), text: readInput(flags.text), kind: "converted-only", extractor }
133
+ }
100
134
  return { ...runCommand(flags.command, flags.cwd), extractor: TOOL_EXTRACTOR }
101
135
  }
102
136
 
137
+ /**
138
+ * `--replace` with `--text` alone (the conversion_altered_number recovery) keeps the source's raw.bin and
139
+ * declares the verified text its extraction, so value rows locate in text.txt while the original bytes stay
140
+ * pinned; plain `--text` without `--replace`, or over an id with no raw.bin, stays converted-only.
141
+ */
142
+ function declareOverKeptRaw(sourcesDir, id, input) {
143
+ const dir = join(sourcesDir, String(id))
144
+ if (input.kind !== "converted-only" || !existsSync(join(dir, "raw.bin")) || !existsSync(join(dir, "meta.json"))) return
145
+ const before = JSON.parse(readFileSync(join(dir, "meta.json"), "utf8"))
146
+ Object.assign(input, { url: before.url, raw: readFileSync(join(dir, "raw.bin")), kind: before.kind, declared: true })
147
+ delete input.path
148
+ }
149
+
103
150
  export async function runSource(argv) {
104
151
  const [sub, ...rest] = argv
152
+ if (sub === "refresh") return (await import("./source-refresh.mjs")).runRefresh(rest)
153
+ if (sub === "probe-conversion") return runConversionProbe(rest)
105
154
  if (sub !== "snapshot") throw new CliError(SNAPSHOT_USAGE)
106
- const args = parseArgs(rest)
155
+ const args = parseArgs(rest, { booleans: ["replace"] })
107
156
  const id = args.required("id")
108
- const sourcesDir = join(sessionDirOf(args.flags), "sources")
157
+ const sessionDir = sessionDirOf(args.flags)
158
+ const sourcesDir = join(sessionDir, "sources")
159
+ const replace = args.flags.replace === true
160
+ const pinned = replace ? readLedger(sessionDir).rows.filter((row) => row.source_id === id && (row.status ?? "active") === "active").map((row) => row.fact_id) : []
161
+ if (pinned.length > 0) {
162
+ const message = `${id} is cited by active row(s) ${pinned.join(", ")}; retract them first, or snapshot the corrected text under a new id`
163
+ return { json: { command: "source snapshot", status: "fail", exitCode: 1, error: "snapshot_pinned", message, rows: pinned }, summary: `source snapshot: ${message}`, exitCode: 1 }
164
+ }
109
165
  const { receipt, ...input } = await acquire(args.flags)
166
+ if (replace) declareOverKeptRaw(sourcesDir, id, input)
110
167
  if (args.flags.kind !== undefined) input.kind = args.flags.kind
111
168
  if (args.flags.provenance !== undefined) input.provenance = args.flags.provenance
112
169
  if (args.flags.revision !== undefined) input.revision = args.flags.revision
113
170
  let written
114
171
  try {
115
- written = writeSnapshot(sourcesDir, id, input)
172
+ written = writeSnapshot(sourcesDir, id, input, { replace })
116
173
  } catch (error) {
117
174
  if (String(error.message).startsWith("snapshot_exists")) {
118
175
  return { json: { command: "source snapshot", status: "fail", exitCode: 1, error: "snapshot_exists", message: error.message }, summary: `source snapshot: ${error.message}`, exitCode: 1 }
@@ -122,8 +179,9 @@ export async function runSource(argv) {
122
179
  }
123
180
  if (receipt) writeFileSync(join(written.dir, "command.json"), `${JSON.stringify(receipt, null, 2)}\n`)
124
181
  const meta = JSON.parse(readFileSync(join(written.dir, "meta.json"), "utf8"))
125
- const data = { source_id: id, directory: written.dir, snapshot_sha256: written.sha256, extractor: meta.extractor, kind: meta.kind, provenance: meta.provenance }
126
- return { json: { command: "source snapshot", status: "pass", exitCode: 0, data }, summary: `source snapshot: ${id} ${written.sha256.slice(0, 12)} (${meta.extractor})`, exitCode: 0 }
182
+ const data = { source_id: id, directory: written.dir, snapshot_sha256: written.sha256, extractor: meta.extractor, kind: meta.kind, provenance: meta.provenance, text_source: meta.text_source, ...(written.altered ? { conversion_altered_numbers: written.altered } : {}) }
183
+ const probe = written.altered ? `; conversion ${written.altered.length} altered${written.altered.length ? ` (${written.altered.join(", ")}): fact add refuses those numbers` : ""}` : ""
184
+ return { json: { command: "source snapshot", status: "pass", exitCode: 0, data }, summary: `source snapshot: ${id} ${written.sha256.slice(0, 12)} (${meta.extractor}, ${meta.text_source})${probe}`, exitCode: 0 }
127
185
  }
128
186
 
129
187
  export function runClaims(argv) {
@@ -0,0 +1,82 @@
1
+ // source-refresh.mjs - `source refresh <S<n>>`: the one explicit repin of a source snapshot.
2
+ //
3
+ // Re-fetches meta.url (or reads the new capture given with the source snapshot input flags),
4
+ // replaces the snapshot, and re-anchors every active byte-located ledger row on that source by its
5
+ // exact quote plus its stored context (raw-locator.mjs reanchor): a row found once gets the new
6
+ // snapshot_sha256 and keeps its old locator, so `fact verify` then reports minor `relocated` with
7
+ // the old and new byte offsets; a row whose quote is gone keeps its old pin, is reported as
8
+ // quote_not_verbatim here, and stays a snapshot_hash_mismatch blocker until it is retracted or
9
+ // re-added. A legacy text.txt row on the source (no byte locator) cannot be re-anchored: it is
10
+ // listed as legacy_not_reanchored and stays a blocker. Exit 0 when every row re-anchored, 1 otherwise. node: builtins and sibling imports only.
11
+
12
+ import { writeFileSync } from "node:fs"
13
+ import { join } from "node:path"
14
+
15
+ import { CliError, parseArgs } from "./cli-support.mjs"
16
+ import { DEFECT_CODES } from "./contracts.mjs"
17
+ import { readLedger, writeLedger } from "./fact-ledger.mjs"
18
+ import { reanchor } from "./raw-locator.mjs"
19
+ import { acquire, sessionDirOf, snapshotOf } from "./source-commands.mjs"
20
+ import { writeSnapshot } from "./source-snapshot.mjs"
21
+
22
+ export const REFRESH_USAGE =
23
+ "usage: source refresh S<n> [--url <u> | --html <file> | --raw <file> [--text <file> --extractor NAME@VERSION] | --text <file> | --command '<argv json>' --cwd DIR] [--charset NAME] --session-dir D (no input flag re-fetches the source's http(s) url)"
24
+
25
+ const INPUTS = ["url", "html", "raw", "text", "command"]
26
+
27
+ const defect = (code, message) => ({ code, severity: DEFECT_CODES[code].severity, integrity: DEFECT_CODES[code].integrity, message })
28
+
29
+ export async function runRefresh(argv) {
30
+ const args = parseArgs(argv)
31
+ const id = args.positionals[0] ?? args.flags.id
32
+ if (!id || id === true) throw new CliError(REFRESH_USAGE)
33
+ const sessionDir = sessionDirOf(args.flags)
34
+ const before = snapshotOf(sessionDir, id)
35
+ const flags = { ...args.flags }
36
+ if (!INPUTS.some((name) => flags[name] !== undefined)) {
37
+ if (!/^https?:\/\//i.test(String(before.meta.url))) throw new CliError(`${id} was not fetched from an http(s) url; pass its new capture. ${REFRESH_USAGE}`)
38
+ flags.url = before.meta.url
39
+ }
40
+ const { receipt, ...input } = await acquire(flags)
41
+ input.kind ??= flags.kind ?? before.meta.kind
42
+ input.provenance = flags.provenance ?? before.meta.provenance
43
+ input.revision = flags.revision ?? before.meta.revision ?? undefined
44
+ let written
45
+ try {
46
+ written = writeSnapshot(join(sessionDir, "sources"), id, input, { replace: true })
47
+ } catch (error) {
48
+ if (error instanceof TypeError) throw new CliError(error.message)
49
+ throw error
50
+ }
51
+ if (receipt) writeFileSync(join(written.dir, "command.json"), `${JSON.stringify(receipt, null, 2)}\n`)
52
+ const after = snapshotOf(sessionDir, id)
53
+ const ledger = readLedger(sessionDir)
54
+ const rows = []
55
+ const defects = []
56
+ for (const row of ledger.rows) {
57
+ if (row.source_id !== id || row.status !== "active") continue
58
+ if (row.locator?.unit === undefined) {
59
+ rows.push({ fact_id: row.fact_id, status: "legacy_not_reanchored" })
60
+ defects.push(defect("snapshot_hash_mismatch", `${row.fact_id}: a legacy text.txt row has no byte locator to re-anchor on refreshed ${id}; add it again from the new snapshot or retract it`))
61
+ continue
62
+ }
63
+ if (row.locator.unit !== "bytes") continue
64
+ const anchor = row.locator.artifact === after.view.artifact ? reanchor(after.view, row) : null
65
+ const span = `${row.locator.char_start}:${row.locator.char_end}`
66
+ if (!anchor) {
67
+ rows.push({ fact_id: row.fact_id, status: "not_found", from: span })
68
+ defects.push(defect("quote_not_verbatim", `${row.fact_id}: its quote and context are not found once in refreshed ${id}; retract it or add it again from the new snapshot`))
69
+ continue
70
+ }
71
+ row.snapshot_sha256 = after.sha256
72
+ rows.push({ fact_id: row.fact_id, status: anchor.relocated ? "relocated" : "unchanged", from: span, to: `${anchor.start}:${anchor.end}` })
73
+ if (anchor.relocated) defects.push(defect("relocated", `${row.fact_id}: ${span} -> ${anchor.start}:${anchor.end} (bytes of ${id}/${after.view.artifact})`))
74
+ }
75
+ writeLedger(sessionDir, ledger)
76
+ const failed = rows.filter((row) => row.status === "not_found" || row.status === "legacy_not_reanchored").length
77
+ const exitCode = failed > 0 ? 1 : 0
78
+ const data = { source_id: id, previous_sha256: before.sha256, snapshot_sha256: after.sha256, text_source: after.view.mode, rows }
79
+ const json = { command: "source refresh", status: exitCode === 0 ? "pass" : "fail", exitCode, defects, data }
80
+ const moved = rows.filter((row) => row.status === "relocated").length
81
+ return { json, summary: `source refresh: ${id} ${before.sha256.slice(0, 12)} -> ${after.sha256.slice(0, 12)}; ${rows.length - failed} row(s) re-anchored (${moved} relocated), ${failed} not re-anchored`, exitCode }
82
+ }
@@ -1,77 +1,44 @@
1
1
  // source-snapshot.mjs - the per-source snapshot that fact locators read.
2
2
  //
3
- // snapshotFromHtml turns a page into text.txt form: one line per block (prose blocks via
4
- // proseText, one table cell per line; head, scripts, styles and code are dropped). writeSnapshot
5
- // writes <sourcesDir>/<S-id>/ with text.txt (pages joined by a form feed), source.html when html
6
- // was given, meta.json, sentences.jsonl (sid S<n>:s<k>) and acronyms.json. The sha256 covers
7
- // text.txt only. Nothing here touches the network: the caller supplies the html or text.
3
+ // writeSnapshot writes <sourcesDir>/<S-id>/ with raw.bin (the fetched or captured bytes, absent
4
+ // only for converted-only sources), meta.json (sha256 of the pinned body, text_source, charset),
5
+ // text.txt (the locator text of source-view.mjs: the visible text nodes of a page, the decoded
6
+ // bytes of a document, or a declared/converted extraction), source.html for pages (decoded, a
7
+ // reading aid), sentences.jsonl (sid S<n>:s<k>, indexed over text.txt) and acronyms.json; a page
8
+ // also gets conversion.json, the conversion probe's record of the numbers its text.txt altered.
8
9
  // Plain Node ESM, node: builtins and sibling imports only.
9
10
 
10
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs"
11
+ import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs"
11
12
  import { join } from "node:path"
12
13
 
13
14
  import { sha256Of } from "./cli-support.mjs"
14
- import { parseHtml, PROSE_TAGS, proseText } from "./html-lite.mjs"
15
+ import { probeConversion } from "./conversion-probe.mjs"
16
+ import { writeFidelityRecord } from "./fidelity-records.mjs"
17
+ import { extractHtml } from "./html-text-map.mjs"
15
18
  import { splitSentences } from "./sentence-split.mjs"
19
+ import { buildView } from "./source-view.mjs"
16
20
 
17
- const SKIP = new Set(["head", "script", "style", "svg", "code", "pre", "kbd", "samp", "template", "noscript"])
18
- const INLINE = new Set([
19
- "a", "abbr", "b", "bdi", "bdo", "cite", "data", "del", "dfn", "em", "font", "i", "ins", "label", "mark", "q", "s",
20
- "small", "span", "strong", "sub", "sup", "time", "u", "var",
21
- ])
22
21
  const SOURCE_ID = /^S[1-9]\d*$/
23
22
  const EXTRACTOR = /^(?:tool:source-snapshot@1|model|extractor:[\w.@+-]+)$/
24
- const KINDS = new Set(["page", "command", "document"])
23
+ const KINDS = new Set(["page", "command", "document", "converted-only"])
25
24
  const PROVENANCE = new Set(["primary", "secondary", "unknown"])
26
25
  const ACRONYM_AFTER = /\(([A-Z]{2,6})\)/g
27
26
  const ACRONYM_BEFORE = /(?<![\p{L}\p{N}])([A-Z]{2,6})\s?\(([A-Z][^()\n]{2,79})\)/gu
28
27
  const NAME_WORD = /^[A-Z][\p{L}\p{N}-]*$/u
29
28
  const CONNECTOR = new Set(["of", "and", "for", "the", "in", "on", "to", "&"])
29
+ const MARKUP = /^\s*</
30
30
 
31
31
  /**
32
- * Block lines of a page: { root, text, blocks: [{ node, start, end }] } where text joins the block
33
- * texts with "\n" and text.slice(start, end) is one block. fact-locate reads cells through `node`.
32
+ * Block lines of a page: { root, text, blocks: [{ node, start, end }], starts, ends } where text
33
+ * joins the visible block texts with "\n" (html-text-map.mjs). fact-locate reads cells through `node`.
34
34
  */
35
35
  export function snapshotBlocks(html) {
36
- const root = parseHtml(html)
37
- const lines = []
38
- let buffer = ""
39
- const flush = (node) => {
40
- const line = buffer.replace(/\s+/g, " ").trim()
41
- buffer = ""
42
- if (line) lines.push({ node, text: line })
43
- }
44
- const walk = (node) => {
45
- for (const child of node.children) {
46
- if (child.tag === "#text") buffer += child.text
47
- else if (SKIP.has(child.tag)) continue
48
- else if (child.tag === "br") flush(node)
49
- else if (INLINE.has(child.tag)) walk(child)
50
- else if (PROSE_TAGS.has(child.tag)) {
51
- flush(node)
52
- const line = proseText(child)
53
- if (line) lines.push({ node: child, text: line })
54
- } else {
55
- flush(node)
56
- walk(child)
57
- flush(child)
58
- }
59
- }
60
- }
61
- walk(root)
62
- flush(root)
63
- const blocks = []
64
- let at = 0
65
- for (const line of lines) {
66
- blocks.push({ node: line.node, start: at, end: at + line.text.length })
67
- at += line.text.length + 1
68
- }
69
- return { root, text: lines.map((line) => line.text).join("\n"), blocks }
36
+ return extractHtml(html)
70
37
  }
71
38
 
72
39
  /** text.txt form of an html page. */
73
40
  export function snapshotFromHtml(html) {
74
- return snapshotBlocks(html).text
41
+ return extractHtml(html).text
75
42
  }
76
43
 
77
44
  /** Sentence index rows { sid: "S<n>:s<k>", start, end, text }; a form feed (page break) always ends a sentence. */
@@ -127,36 +94,61 @@ export function findAcronyms(text, sentences) {
127
94
  function snapshotText(input) {
128
95
  if (Array.isArray(input.pages)) return input.pages.join("\f")
129
96
  if (typeof input.text === "string") return input.text
130
- if (typeof input.html === "string") return snapshotFromHtml(input.html)
131
- throw new TypeError("writeSnapshot needs text, pages, or html")
97
+ throw new TypeError("writeSnapshot needs raw, html, text, or pages")
98
+ }
99
+
100
+ /** Which source-view text source the input makes, with the bytes it pins. */
101
+ function bodiesOf(input) {
102
+ if (input.kind === "converted-only") return { mode: "converted", raw: null, textBody: Buffer.from(snapshotText(input)) }
103
+ if (input.declared === true) return { mode: "declared", raw: Buffer.from(input.raw), textBody: Buffer.from(snapshotText(input)) }
104
+ if (input.raw === undefined && typeof input.html === "string") return { mode: "html", raw: Buffer.from(input.html), textBody: null }
105
+ if (input.raw === undefined) return { mode: "decoded", raw: Buffer.from(snapshotText(input)), textBody: null }
106
+ const raw = Buffer.from(input.raw)
107
+ if (input.markup === true || typeof input.html === "string" || input.kind === "page" || MARKUP.test(raw.subarray(0, 1024).toString("latin1").replace(/^\u00ef\u00bb\u00bf/, ""))) return { mode: "html", raw, textBody: null }
108
+ if (input.text !== undefined || input.pages !== undefined) return { mode: "declared", raw, textBody: Buffer.from(snapshotText(input)) }
109
+ return { mode: "decoded", raw, textBody: null }
132
110
  }
133
111
 
134
112
  function checkInput(sourceId, input) {
135
113
  if (!SOURCE_ID.test(String(sourceId))) throw new TypeError(`source id must be S<n>, got ${sourceId}`)
136
114
  if (!EXTRACTOR.test(String(input.extractor))) throw new TypeError(`extractor must be tool:source-snapshot@1, model, or extractor:<name>, got ${input.extractor}`)
137
115
  if (typeof (input.url ?? input.path) !== "string") throw new TypeError("writeSnapshot needs a url or a path")
138
- if (input.kind !== undefined && !KINDS.has(input.kind)) throw new TypeError(`kind must be page, command, or document, got ${input.kind}`)
116
+ if (input.kind !== undefined && !KINDS.has(input.kind)) throw new TypeError(`kind must be page, command, document, or converted-only, got ${input.kind}`)
139
117
  if (input.provenance !== undefined && !PROVENANCE.has(input.provenance)) throw new TypeError(`provenance must be primary, secondary, or unknown, got ${input.provenance}`)
118
+ if (input.observed_at !== undefined && (typeof input.observed_at !== "string" || Number.isNaN(new Date(input.observed_at).getTime()))) throw new TypeError(`observed_at must be an ISO timestamp, got ${input.observed_at}`)
119
+ }
120
+
121
+ function pinnedHash(dir) {
122
+ const metaPath = join(dir, "meta.json")
123
+ return existsSync(metaPath) ? JSON.parse(readFileSync(metaPath, "utf8")).sha256 : null
140
124
  }
141
125
 
142
126
  /**
143
- * Write <sourcesDir>/<sourceId>/ for one source; returns { dir, sha256 } with sha256 over text.txt.
144
- * input: { url | path, text | pages | html, extractor, observed_at?, revision?, kind?, provenance? }.
145
- * Re-writing the same text is a no-op change; a different text over an existing snapshot throws
146
- * snapshot_exists, because ledger rows pin the old hash.
127
+ * Write <sourcesDir>/<sourceId>/ for one source; returns { dir, sha256, altered } over the pinned body,
128
+ * where altered lists the numbers the page conversion altered (null when no page conversion ran).
129
+ * input: { url | path, raw?, markup?, html?, text? | pages?, charset?, extractor, observed_at?, revision?,
130
+ * kind?, provenance? } (see bodiesOf). Re-writing the same bytes is a no-op; different bytes over
131
+ * an existing snapshot throw snapshot_exists, because ledger rows pin the old hash, unless
132
+ * options.replace (`source refresh`, which re-anchors those rows, or `source snapshot --replace`, which
133
+ * has checked that no active row pins it) is set; a replacement drops the old capture's command.json.
147
134
  */
148
- export function writeSnapshot(sourcesDir, sourceId, input) {
135
+ export function writeSnapshot(sourcesDir, sourceId, input, options = {}) {
149
136
  checkInput(sourceId, input)
150
- const text = snapshotText(input)
151
- const sha256 = sha256Of(Buffer.from(text, "utf8"))
137
+ const { mode, raw, textBody } = bodiesOf(input)
138
+ const view = buildView(mode, { raw, textBody, charset: input.charset ?? null })
139
+ const sha256 = sha256Of(raw ?? textBody)
152
140
  const dir = join(sourcesDir, sourceId)
153
- const textPath = join(dir, "text.txt")
154
- if (existsSync(textPath) && sha256Of(readFileSync(textPath)) !== sha256) {
155
- throw new Error(`snapshot_exists: ${sourceId} already holds a different text.txt`)
141
+ const pinned = pinnedHash(dir)
142
+ if (pinned !== null && pinned !== sha256) {
143
+ if (options.replace !== true) throw new Error(`snapshot_exists: ${sourceId} already holds different snapshot bytes`)
144
+ rmSync(join(dir, "command.json"), { force: true })
156
145
  }
157
146
  mkdirSync(dir, { recursive: true })
158
- writeFileSync(textPath, text)
159
- if (typeof input.html === "string") writeFileSync(join(dir, "source.html"), input.html)
147
+ if (raw) writeFileSync(join(dir, "raw.bin"), raw)
148
+ else rmSync(join(dir, "raw.bin"), { force: true })
149
+ writeFileSync(join(dir, "text.txt"), textBody ?? view.text)
150
+ if (view.html !== null) writeFileSync(join(dir, "source.html"), view.html)
151
+ else rmSync(join(dir, "source.html"), { force: true })
160
152
  const meta = {
161
153
  source_id: sourceId,
162
154
  url: input.url ?? input.path,
@@ -164,13 +156,25 @@ export function writeSnapshot(sourcesDir, sourceId, input) {
164
156
  sha256,
165
157
  extractor: input.extractor,
166
158
  revision: input.revision ?? null,
167
- kind: input.kind ?? (input.url !== undefined || typeof input.html === "string" ? "page" : "document"),
159
+ kind: input.kind ?? (input.url !== undefined || mode === "html" ? "page" : "document"),
168
160
  provenance: input.provenance ?? "unknown",
161
+ text_source: mode,
162
+ ...(raw ? { charset: view.encoding } : {}),
163
+ ...(mode === "declared" ? { text_sha256: sha256Of(textBody) } : {}),
169
164
  }
170
165
  writeFileSync(join(dir, "meta.json"), `${JSON.stringify(meta, null, 2)}\n`)
171
- const sentences = indexSentences(text, sourceId)
166
+ let altered = null
167
+ if (mode === "html") {
168
+ const { altered: changed, ...counts } = probeConversion(view.html, view.text, { attributes: false })
169
+ altered = changed
170
+ writeFidelityRecord(join(dir, "conversion.json"), "source_conversion", {
171
+ artifact_sha256: sha256Of(view.html), report: join(dir, "source.html"), defects: [],
172
+ conversion_altered_numbers: changed, ...counts,
173
+ }, new Date(meta.observed_at))
174
+ } else rmSync(join(dir, "conversion.json"), { force: true })
175
+ const sentences = indexSentences(view.text, sourceId)
172
176
  writeFileSync(join(dir, "sentences.jsonl"), sentences.map((row) => `${JSON.stringify(row)}\n`).join(""))
173
- const acronyms = { schemaVersion: 1, source_id: sourceId, snapshot_sha256: sha256, entries: findAcronyms(text, sentences) }
177
+ const acronyms = { schemaVersion: 1, source_id: sourceId, snapshot_sha256: sha256, entries: findAcronyms(view.text, sentences) }
174
178
  writeFileSync(join(dir, "acronyms.json"), `${JSON.stringify(acronyms, null, 2)}\n`)
175
- return { dir, sha256 }
179
+ return { dir, sha256, altered }
176
180
  }
@@ -0,0 +1,89 @@
1
+ // source-view.mjs - the one reading of a source snapshot that locators, verify, and refresh share.
2
+ //
3
+ // A snapshot's meta.json `text_source` says where its locator text comes from:
4
+ // html - text nodes of raw.bin (html-text-map.mjs), offsets are raw.bin bytes
5
+ // decoded - raw.bin decoded in its charset, offsets are raw.bin bytes
6
+ // declared - text.txt is a declared extraction (--raw + --text) of non-HTML raw.bin; offsets
7
+ // are text.txt bytes, and raw.bin and text.txt are both pinned
8
+ // converted - converted-only: no raw.bin, text.txt is all there is (quotes only)
9
+ // A snapshot without text_source and without raw.bin is a legacy text.txt snapshot. byteSpan maps
10
+ // a [start, end) range of `text` to [startByte, endByte) in `artifact`, never through a re-encode.
11
+ // node: builtins and sibling imports only.
12
+
13
+ import { readFileSync } from "node:fs"
14
+ import { join } from "node:path"
15
+
16
+ import { sha256Of } from "./cli-support.mjs"
17
+ import { extractHtml } from "./html-text-map.mjs"
18
+ import { decodeRaw } from "./raw-decode.mjs"
19
+
20
+ export const TEXT_SOURCES = Object.freeze(["html", "decoded", "declared", "converted"])
21
+
22
+ /** Read one snapshot file; the error names the file and the cause (missing, EACCES, ...). */
23
+ export function readPart(dir, name, encoding) {
24
+ try {
25
+ return readFileSync(join(dir, name), encoding)
26
+ } catch (error) {
27
+ const cause = error.code === "ENOENT" ? "is missing" : error.code === "EISDIR" ? "is not a readable file" : `is unreadable (${error.code ?? error.message})`
28
+ throw Object.assign(new Error(`${name} ${cause}`), { code: error.code, part: name })
29
+ }
30
+ }
31
+
32
+ /** The text source a meta.json declares, inferring it for snapshots written before the field. */
33
+ export function textSourceOf(meta, hasRaw) {
34
+ if (TEXT_SOURCES.includes(meta.text_source)) return meta.text_source
35
+ if (!hasRaw) return meta.kind === "converted-only" ? "converted" : "legacy"
36
+ return meta.kind === "page" ? "html" : "decoded"
37
+ }
38
+
39
+ /**
40
+ * Locator text and byte map for one snapshot body. raw is raw.bin (null for converted/legacy),
41
+ * textBody is text.txt bytes (needed for declared, converted, legacy).
42
+ */
43
+ export function buildView(mode, { raw = null, textBody = null, charset = null }) {
44
+ if (mode === "html") {
45
+ const decoded = decodeRaw(raw, charset, true)
46
+ const map = extractHtml(decoded.text)
47
+ const byteSpan = (start, end) => {
48
+ if (end <= start) return [decoded.bytes[map.ends[start - 1] ?? 0], decoded.bytes[map.ends[start - 1] ?? 0]]
49
+ return [decoded.bytes[map.starts[start]], decoded.bytes[map.ends[end - 1]]]
50
+ }
51
+ return { mode, artifact: "raw.bin", encoding: decoded.encoding, text: map.text, html: decoded.text, root: map.root, blocks: map.blocks, byteSpan }
52
+ }
53
+ if (mode === "legacy") return { mode, artifact: "text.txt", encoding: "utf-8", text: Buffer.from(textBody).toString("utf8"), html: null, root: null, blocks: null, byteSpan: null }
54
+ const body = mode === "decoded" ? raw : textBody
55
+ const decoded = decodeRaw(body, mode === "decoded" ? charset : "utf-8")
56
+ const byteSpan = (start, end) => [decoded.bytes[start], decoded.bytes[Math.max(start, end)]]
57
+ return { mode, artifact: mode === "decoded" ? "raw.bin" : "text.txt", encoding: decoded.encoding, text: decoded.text, html: null, root: null, blocks: null, byteSpan }
58
+ }
59
+
60
+ /**
61
+ * Read and hash a snapshot directory. Throws (with the file and cause) when meta.json, raw.bin, or
62
+ * text.txt cannot be read; hash drift is returned, not thrown: { meta, view, raw, sha256, drift }.
63
+ * sha256 is the hash of the pinned body (raw.bin, or text.txt for converted and legacy snapshots).
64
+ */
65
+ export function readSnapshot(dir) {
66
+ let meta
67
+ try {
68
+ meta = JSON.parse(readPart(dir, "meta.json", "utf8"))
69
+ } catch (error) {
70
+ throw error.part ? error : new Error("meta.json is not valid JSON")
71
+ }
72
+ if (meta === null || typeof meta !== "object" || typeof meta.sha256 !== "string") throw new Error("has invalid snapshot metadata (meta.json has no sha256)")
73
+ let raw = null
74
+ if (meta.text_source === undefined) {
75
+ try {
76
+ raw = readPart(dir, "raw.bin")
77
+ } catch (error) {
78
+ if (error.code !== "ENOENT") throw error
79
+ }
80
+ }
81
+ const mode = textSourceOf(meta, raw !== null)
82
+ if (raw === null && ["html", "decoded", "declared"].includes(mode)) raw = readPart(dir, "raw.bin")
83
+ const textBody = ["declared", "converted", "legacy"].includes(mode) ? readPart(dir, "text.txt") : null
84
+ const sha256 = sha256Of(raw ?? textBody)
85
+ const drift = []
86
+ if (sha256 !== meta.sha256) drift.push(`hash mismatch: ${raw ? "raw.bin" : "text.txt"} changed after its snapshot was pinned`)
87
+ if (mode === "declared" && sha256Of(textBody) !== meta.text_sha256) drift.push("hash mismatch: text.txt (the declared extraction) changed after it was pinned")
88
+ return { meta, raw, sha256, drift, view: buildView(mode, { raw, textBody, charset: meta.charset ?? null }) }
89
+ }