@runweave/server-linux-x64 0.1.7 → 0.1.9

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 (352) hide show
  1. package/manifest.json +1487 -156
  2. package/native.tar.gz +0 -0
  3. package/package.json +1 -1
  4. package/payload/.agents/plugins/marketplace.json +20 -0
  5. package/payload/app-server/build-info.json +3 -3
  6. package/payload/app-server/index.cjs +1 -1
  7. package/payload/backend/build-info.json +4 -4
  8. package/payload/backend/index.cjs +2416 -1684
  9. package/payload/backend/ios-simulators/pool.py +3 -3
  10. package/payload/backend/scheduled-tasks-sqlite-worker.cjs +224 -171
  11. package/payload/frontend/dist/assets/{_baseUniq-i1Ki3twP.js → _baseUniq-qHFga5V8.js} +1 -1
  12. package/payload/frontend/dist/assets/{access-DK9V0f3W.js → access-DgHvmn15.js} +1 -1
  13. package/payload/frontend/dist/assets/{advanced-B13lWv1d.js → advanced-C1Fh14vw.js} +1 -1
  14. package/payload/frontend/dist/assets/{appearance-DumRYmkD.js → appearance-Bs7cSXqO.js} +1 -1
  15. package/payload/frontend/dist/assets/{arc-HKn3_xyV.js → arc-E3iYHQG_.js} +1 -1
  16. package/payload/frontend/dist/assets/{architectureDiagram-Q4EWVU46-C09x7b94.js → architectureDiagram-Q4EWVU46-DgpYpTb9.js} +1 -1
  17. package/payload/frontend/dist/assets/{blockDiagram-DXYQGD6D-CFLFTR6d.js → blockDiagram-DXYQGD6D-CfFOEKZ7.js} +1 -1
  18. package/payload/frontend/dist/assets/{c4Diagram-AHTNJAMY-8P8Q-xqC.js → c4Diagram-AHTNJAMY-BxlHBUj3.js} +1 -1
  19. package/payload/frontend/dist/assets/channel-amxyNKnk.js +1 -0
  20. package/payload/frontend/dist/assets/{chunk-4BX2VUAB-B1KEkTal.js → chunk-4BX2VUAB-xWocgDo8.js} +1 -1
  21. package/payload/frontend/dist/assets/{chunk-4TB4RGXK-Bu_Z47kJ.js → chunk-4TB4RGXK-Bl1cNICf.js} +1 -1
  22. package/payload/frontend/dist/assets/{chunk-55IACEB6-xzHVCHyi.js → chunk-55IACEB6-DVtE836u.js} +1 -1
  23. package/payload/frontend/dist/assets/{chunk-EDXVE4YY-h3HAW06h.js → chunk-EDXVE4YY-Dbd3tDCr.js} +1 -1
  24. package/payload/frontend/dist/assets/{chunk-FMBD7UC4-CQ4faPSh.js → chunk-FMBD7UC4-MPJaTsyN.js} +1 -1
  25. package/payload/frontend/dist/assets/{chunk-OYMX7WX6-CubmLoGT.js → chunk-OYMX7WX6-D3LuudlW.js} +1 -1
  26. package/payload/frontend/dist/assets/{chunk-QZHKN3VN-xk0zKNja.js → chunk-QZHKN3VN-Dn1aF6iQ.js} +1 -1
  27. package/payload/frontend/dist/assets/{chunk-YZCP3GAM-CHpTv8sd.js → chunk-YZCP3GAM-jZYbHnCw.js} +1 -1
  28. package/payload/frontend/dist/assets/classDiagram-6PBFFD2Q-B8cTijjT.js +1 -0
  29. package/payload/frontend/dist/assets/classDiagram-v2-HSJHXN6E-B8cTijjT.js +1 -0
  30. package/payload/frontend/dist/assets/clone-xzI2eGRI.js +1 -0
  31. package/payload/frontend/dist/assets/connection-V2iI-6rM.js +12 -0
  32. package/payload/frontend/dist/assets/{connections-A7InZw2T.js → connections-G_Q7342g.js} +1 -1
  33. package/payload/frontend/dist/assets/{cose-bilkent-S5V4N54A-CQn27vi_.js → cose-bilkent-S5V4N54A-75FLD3RS.js} +1 -1
  34. package/payload/frontend/dist/assets/{dagre-KV5264BT-CClOwB9v.js → dagre-KV5264BT-DEm-DpJL.js} +1 -1
  35. package/payload/frontend/dist/assets/{desktop-BgryfXXM.js → desktop-g5JC-CQd.js} +1 -1
  36. package/payload/frontend/dist/assets/{diagram-5BDNPKRD-DcUJOAmV.js → diagram-5BDNPKRD-BV6P2UTy.js} +1 -1
  37. package/payload/frontend/dist/assets/{diagram-G4DWMVQ6-DdabmTwk.js → diagram-G4DWMVQ6-DuCK-MM2.js} +1 -1
  38. package/payload/frontend/dist/assets/{diagram-MMDJMWI5-DmcxjBIk.js → diagram-MMDJMWI5-D5_GlPgO.js} +1 -1
  39. package/payload/frontend/dist/assets/{diagram-TYMM5635-BUSpXaiN.js → diagram-TYMM5635-QRbOYR1P.js} +1 -1
  40. package/payload/frontend/dist/assets/{erDiagram-SMLLAGMA-vkAXcv5_.js → erDiagram-SMLLAGMA-DefCheAw.js} +1 -1
  41. package/payload/frontend/dist/assets/{flowDiagram-DWJPFMVM-BHDU7BUJ.js → flowDiagram-DWJPFMVM-DVzyWIso.js} +1 -1
  42. package/payload/frontend/dist/assets/{ganttDiagram-T4ZO3ILL-C3lAG7_6.js → ganttDiagram-T4ZO3ILL-BJaClUEF.js} +1 -1
  43. package/payload/frontend/dist/assets/{gitGraphDiagram-UUTBAWPF-DHLyYJf7.js → gitGraphDiagram-UUTBAWPF-BLfbApNz.js} +1 -1
  44. package/payload/frontend/dist/assets/{graph-Bp5bIzXM.js → graph-C-YqFh68.js} +1 -1
  45. package/payload/frontend/dist/assets/{image-DkcKNLTV.js → image-CZo_2Awc.js} +1 -1
  46. package/payload/frontend/dist/assets/{index-BUS7FSlk.js → index-6q2aeqkt.js} +95 -105
  47. package/payload/frontend/dist/assets/{infoDiagram-42DDH7IO-CVp9zvmA.js → infoDiagram-42DDH7IO-BakVOutT.js} +1 -1
  48. package/payload/frontend/dist/assets/{ishikawaDiagram-UXIWVN3A-D07QPX3O.js → ishikawaDiagram-UXIWVN3A-D_1Oa5Dc.js} +1 -1
  49. package/payload/frontend/dist/assets/{journeyDiagram-VCZTEJTY-D-iZPDHM.js → journeyDiagram-VCZTEJTY-Y8TVFv6B.js} +1 -1
  50. package/payload/frontend/dist/assets/{kanban-definition-6JOO6SKY-BR4eWb08.js → kanban-definition-6JOO6SKY-DxeNhTIB.js} +1 -1
  51. package/payload/frontend/dist/assets/{layout-Cyv0DyJm.js → layout-5cMeOTAp.js} +1 -1
  52. package/payload/frontend/dist/assets/{linear-D5M7Pvj1.js → linear-Bnc3ev5g.js} +1 -1
  53. package/payload/frontend/dist/assets/main-3TPtEl66.js +706 -0
  54. package/payload/frontend/dist/assets/main-D4_uoZQa.css +33 -0
  55. package/payload/frontend/dist/assets/{min-BFlloFGg.js → min-BRXKkrS8.js} +1 -1
  56. package/payload/frontend/dist/assets/{mindmap-definition-QFDTVHPH-AKUF178o.js → mindmap-definition-QFDTVHPH-RYZO_BEr.js} +1 -1
  57. package/payload/frontend/dist/assets/{monitoring-B2okceCB.js → monitoring-Fv7kZ4s6.js} +1 -1
  58. package/payload/frontend/dist/assets/{notifications-0lql4_qJ.js → notifications-BzrYqyMH.js} +1 -1
  59. package/payload/frontend/dist/assets/{pieDiagram-DEJITSTG-CRiHmwn7.js → pieDiagram-DEJITSTG-Bzw0dhjz.js} +1 -1
  60. package/payload/frontend/dist/assets/{quadrantDiagram-34T5L4WZ-CzXrq7IJ.js → quadrantDiagram-34T5L4WZ-CQJY_msa.js} +1 -1
  61. package/payload/frontend/dist/assets/{record-content-DTCQKbEj.js → record-content-B7-6olQt.js} +1 -1
  62. package/payload/frontend/dist/assets/{requirementDiagram-MS252O5E-Bbn5ulLV.js → requirementDiagram-MS252O5E-DuP8hAV8.js} +1 -1
  63. package/payload/frontend/dist/assets/{sankeyDiagram-XADWPNL6-D9ZZ46pQ.js → sankeyDiagram-XADWPNL6-D-fQnCZZ.js} +1 -1
  64. package/payload/frontend/dist/assets/{sequenceDiagram-FGHM5R23-DxOP2gDa.js → sequenceDiagram-FGHM5R23-DaZNEccV.js} +1 -1
  65. package/payload/frontend/dist/assets/{stateDiagram-FHFEXIEX-NF_Qq5Cq.js → stateDiagram-FHFEXIEX-CkOLt5R1.js} +1 -1
  66. package/payload/frontend/dist/assets/stateDiagram-v2-QKLJ7IA2-atg1K4nB.js +1 -0
  67. package/payload/frontend/dist/assets/{status-2U71YpVe.js → status-CIIPajZp.js} +1 -1
  68. package/payload/frontend/dist/assets/suiji-C453W-Sg.js +1 -0
  69. package/payload/frontend/dist/assets/{svg-DpI3GSCw.js → svg-BUqAWGtg.js} +1 -1
  70. package/payload/frontend/dist/assets/{tabs-CYKuiiHg.js → tabs-Cwlp1Meu.js} +1 -1
  71. package/payload/frontend/dist/assets/{timeline-definition-GMOUNBTQ-ChEZSNF_.js → timeline-definition-GMOUNBTQ-BOKDMwHS.js} +1 -1
  72. package/payload/frontend/dist/assets/{vennDiagram-DHZGUBPP-Ch26SNbB.js → vennDiagram-DHZGUBPP-fSZjDS41.js} +1 -1
  73. package/payload/frontend/dist/assets/{wardley-RL74JXVD-D0ap5vv0.js → wardley-RL74JXVD-D1KIsvoD.js} +1 -1
  74. package/payload/frontend/dist/assets/{wardleyDiagram-NUSXRM2D-btdjsmHO.js → wardleyDiagram-NUSXRM2D-FMe67dwE.js} +1 -1
  75. package/payload/frontend/dist/assets/{xychartDiagram-5P7HB3ND-CklPtFGT.js → xychartDiagram-5P7HB3ND-B9AM9_9w.js} +1 -1
  76. package/payload/frontend/dist/build-info.json +4 -4
  77. package/payload/frontend/dist/companion.html +1 -1
  78. package/payload/frontend/dist/index.html +3 -3
  79. package/payload/packages/agent-bridge/src/install-pi.mjs +81 -0
  80. package/payload/plugins/pi/dist/SKILL.md +22 -0
  81. package/payload/plugins/pi/dist/bridge/app-server-client.cjs +82 -0
  82. package/payload/plugins/pi/dist/bridge/feishu_stop_notify.sh +169 -0
  83. package/payload/plugins/pi/dist/bridge/runweave-hook-bridge.cjs +583 -0
  84. package/payload/plugins/pi/dist/bridge/runweave-hook-completion.cjs +42 -0
  85. package/payload/plugins/pi/dist/bridge/runweave-hook-payload.cjs +578 -0
  86. package/payload/plugins/pi/dist/bridge/runweave-task-supervision.cjs +167 -0
  87. package/payload/plugins/pi/dist/runweave.js +316 -0
  88. package/payload/plugins/toolkit/.codex-plugin/plugin.json +31 -0
  89. package/payload/plugins/toolkit/README.md +185 -0
  90. package/payload/plugins/toolkit/hooks/AGENTS.md +6 -0
  91. package/payload/plugins/toolkit/hooks/app-server-client.cjs +82 -0
  92. package/payload/plugins/toolkit/hooks/feishu_stop_notify.sh +169 -0
  93. package/payload/plugins/toolkit/hooks/runweave-hook-bridge.cjs +583 -0
  94. package/payload/plugins/toolkit/hooks/runweave-hook-completion.cjs +42 -0
  95. package/payload/plugins/toolkit/hooks/runweave-hook-dispatch.cjs +123 -0
  96. package/payload/plugins/toolkit/hooks/runweave-hook-payload.cjs +578 -0
  97. package/payload/plugins/toolkit/hooks/runweave-task-supervision.cjs +167 -0
  98. package/payload/plugins/toolkit/hooks.json +108 -0
  99. package/payload/plugins/toolkit/skills/acceptance/SKILL.md +70 -0
  100. package/payload/plugins/toolkit/skills/acceptance/agents/openai.yaml +4 -0
  101. package/payload/plugins/toolkit/skills/acceptance/references/capture.md +38 -0
  102. package/payload/plugins/toolkit/skills/acceptance/references/delivery.md +54 -0
  103. package/payload/plugins/toolkit/skills/acceptance/references/gallery-contract.md +84 -0
  104. package/payload/plugins/toolkit/skills/acceptance/references/gallery-copy.md +34 -0
  105. package/payload/plugins/toolkit/skills/acceptance/references/video.md +52 -0
  106. package/payload/plugins/toolkit/skills/acceptance/scripts/capture.py +137 -0
  107. package/payload/plugins/toolkit/skills/acceptance/scripts/common.py +63 -0
  108. package/payload/plugins/toolkit/skills/acceptance/scripts/gallery.py +353 -0
  109. package/payload/plugins/toolkit/skills/acceptance/scripts/preview.py +109 -0
  110. package/payload/plugins/toolkit/skills/acceptance/scripts/video.py +283 -0
  111. package/payload/plugins/toolkit/skills/acceptance/templates/reader.css +576 -0
  112. package/payload/plugins/toolkit/skills/acceptance/templates/reader.html +63 -0
  113. package/payload/plugins/toolkit/skills/acceptance/templates/reader.js +326 -0
  114. package/payload/plugins/toolkit/skills/acceptance/templates/viewer.css +55 -0
  115. package/payload/plugins/toolkit/skills/acceptance-gallery/SKILL.md +8 -0
  116. package/payload/plugins/toolkit/skills/acceptance-gallery/agents/openai.yaml +6 -0
  117. package/payload/plugins/toolkit/skills/acceptance-gallery/references/capture-and-copy.md +3 -0
  118. package/payload/plugins/toolkit/skills/acceptance-gallery/references/content-contract.md +3 -0
  119. package/payload/plugins/toolkit/skills/acceptance-gallery/scripts/gallery.py +9 -0
  120. package/payload/plugins/toolkit/skills/acceptance-video/SKILL.md +8 -0
  121. package/payload/plugins/toolkit/skills/acceptance-video/agents/openai.yaml +6 -0
  122. package/payload/plugins/toolkit/skills/acceptance-video/references/input-and-capture.md +3 -0
  123. package/payload/plugins/toolkit/skills/acceptance-video/references/mobile-review.md +3 -0
  124. package/payload/plugins/toolkit/skills/acceptance-video/scripts/capture.py +9 -0
  125. package/payload/plugins/toolkit/skills/acceptance-video/scripts/video.py +9 -0
  126. package/payload/plugins/toolkit/skills/agent-device/SKILL.md +86 -0
  127. package/payload/plugins/toolkit/skills/agent-device/agents/openai.yaml +4 -0
  128. package/payload/plugins/toolkit/skills/agent-device/references/ios-setup.md +36 -0
  129. package/payload/plugins/toolkit/skills/agent-device/scripts/device.py +398 -0
  130. package/payload/plugins/toolkit/skills/agent-device/scripts/resources.py +248 -0
  131. package/payload/plugins/toolkit/skills/architecture-flow-prototyping/SKILL.md +128 -0
  132. package/payload/plugins/toolkit/skills/architecture-flow-prototyping/agents/openai.yaml +4 -0
  133. package/payload/plugins/toolkit/skills/architecture-flow-prototyping/references/architecture-flow-checklist.md +59 -0
  134. package/payload/plugins/toolkit/skills/architecture-flow-prototyping/references/architecture-flow-guide.md +135 -0
  135. package/payload/plugins/toolkit/skills/architecture-flow-prototyping/references/architecture-flow-handoff-template.md +57 -0
  136. package/payload/plugins/toolkit/skills/ask-first/SKILL.md +81 -0
  137. package/payload/plugins/toolkit/skills/brainstorming/SKILL.md +41 -0
  138. package/payload/plugins/toolkit/skills/browse-record/SKILL.md +35 -0
  139. package/payload/plugins/toolkit/skills/browse-record/references/app-usage.md +19 -0
  140. package/payload/plugins/toolkit/skills/browse-record/references/daily-reporting.md +115 -0
  141. package/payload/plugins/toolkit/skills/browse-record/references/zhihu.md +33 -0
  142. package/payload/plugins/toolkit/skills/browse-record/scripts/app_usage.py +110 -0
  143. package/payload/plugins/toolkit/skills/browse-record/scripts/browse_job.py +385 -0
  144. package/payload/plugins/toolkit/skills/browse-record/scripts/zhihu-read.js +126 -0
  145. package/payload/plugins/toolkit/skills/browse-record/scripts/zhihu_http.py +273 -0
  146. package/payload/plugins/toolkit/skills/clear-technical-explanations/LICENSE +21 -0
  147. package/payload/plugins/toolkit/skills/clear-technical-explanations/SKILL.md +103 -0
  148. package/payload/plugins/toolkit/skills/code-grounded-requirements/SKILL.md +54 -0
  149. package/payload/plugins/toolkit/skills/daily-doc-maintenance/SKILL.md +151 -0
  150. package/payload/plugins/toolkit/skills/daily-record/SKILL.md +63 -0
  151. package/payload/plugins/toolkit/skills/daily-record/agents/openai.yaml +4 -0
  152. package/payload/plugins/toolkit/skills/daily-record/references/browser-history.md +6 -0
  153. package/payload/plugins/toolkit/skills/daily-record/references/feishu.md +90 -0
  154. package/payload/plugins/toolkit/skills/daily-record/references/scheduled-reporting.md +144 -0
  155. package/payload/plugins/toolkit/skills/daily-record/references/scheduled-task.template.json +15 -0
  156. package/payload/plugins/toolkit/skills/daily-record/references/suiji-work-records.md +57 -0
  157. package/payload/plugins/toolkit/skills/daily-record/scripts/feishu.py +280 -0
  158. package/payload/plugins/toolkit/skills/daily-record/scripts/job.py +416 -0
  159. package/payload/plugins/toolkit/skills/daily-record/scripts/job_browser.py +77 -0
  160. package/payload/plugins/toolkit/skills/daily-record/scripts/job_facts.py +132 -0
  161. package/payload/plugins/toolkit/skills/daily-record/scripts/job_isolation.py +69 -0
  162. package/payload/plugins/toolkit/skills/daily-record/scripts/job_materials.py +154 -0
  163. package/payload/plugins/toolkit/skills/daily-record/scripts/job_screen.py +77 -0
  164. package/payload/plugins/toolkit/skills/daily-record/scripts/job_state.py +309 -0
  165. package/payload/plugins/toolkit/skills/daily-refactor/SKILL.md +101 -0
  166. package/payload/plugins/toolkit/skills/daily-refactor/scripts/compare-architecture-reports.mjs +168 -0
  167. package/payload/plugins/toolkit/skills/debugging-strategies/SKILL.md +38 -0
  168. package/payload/plugins/toolkit/skills/debugging-strategies/resources/implementation-playbook.md +520 -0
  169. package/payload/plugins/toolkit/skills/diagnostic-log-debugging/SKILL.md +79 -0
  170. package/payload/plugins/toolkit/skills/doc-coauthoring/SKILL.md +64 -0
  171. package/payload/plugins/toolkit/skills/eli5/LICENSE +21 -0
  172. package/payload/plugins/toolkit/skills/eli5/SKILL.md +18 -0
  173. package/payload/plugins/toolkit/skills/experience/SKILL.md +71 -0
  174. package/payload/plugins/toolkit/skills/experience/package.json +13 -0
  175. package/payload/plugins/toolkit/skills/git-advanced-workflows/SKILL.md +457 -0
  176. package/payload/plugins/toolkit/skills/github-pr/SKILL.md +109 -0
  177. package/payload/plugins/toolkit/skills/github-pr/scripts/github-access.mjs +292 -0
  178. package/payload/plugins/toolkit/skills/github-pr/scripts/preflight.mjs +191 -0
  179. package/payload/plugins/toolkit/skills/github-pr/scripts/source-worktree.mjs +354 -0
  180. package/payload/plugins/toolkit/skills/grill-with-docs/SKILL.md +66 -0
  181. package/payload/plugins/toolkit/skills/grill-with-docs/agents/openai.yaml +6 -0
  182. package/payload/plugins/toolkit/skills/grilling/SKILL.md +14 -0
  183. package/payload/plugins/toolkit/skills/handoff/SKILL.md +98 -0
  184. package/payload/plugins/toolkit/skills/karpathy-guidelines/SKILL.md +73 -0
  185. package/payload/plugins/toolkit/skills/playwright-cli/SKILL.md +562 -0
  186. package/payload/plugins/toolkit/skills/playwright-cli/references/element-attributes.md +23 -0
  187. package/payload/plugins/toolkit/skills/playwright-cli/references/playwright-tests.md +39 -0
  188. package/payload/plugins/toolkit/skills/playwright-cli/references/request-mocking.md +87 -0
  189. package/payload/plugins/toolkit/skills/playwright-cli/references/running-code.md +240 -0
  190. package/payload/plugins/toolkit/skills/playwright-cli/references/session-management.md +226 -0
  191. package/payload/plugins/toolkit/skills/playwright-cli/references/spec-driven-testing.md +312 -0
  192. package/payload/plugins/toolkit/skills/playwright-cli/references/storage-state.md +275 -0
  193. package/payload/plugins/toolkit/skills/playwright-cli/references/test-generation.md +138 -0
  194. package/payload/plugins/toolkit/skills/playwright-cli/references/tracing.md +142 -0
  195. package/payload/plugins/toolkit/skills/playwright-cli/references/video-recording.md +157 -0
  196. package/payload/plugins/toolkit/skills/playwright-cli/templates/standalone.config.json +7 -0
  197. package/payload/plugins/toolkit/skills/product-exploration/SKILL.md +64 -0
  198. package/payload/plugins/toolkit/skills/product-exploration/agents/openai.yaml +5 -0
  199. package/payload/plugins/toolkit/skills/prototype-first-development/SKILL.md +134 -0
  200. package/payload/plugins/toolkit/skills/prototype-first-development/references/html-prototype-template/README.md +88 -0
  201. package/payload/plugins/toolkit/skills/prototype-first-development/references/html-prototype-template/app.js +145 -0
  202. package/payload/plugins/toolkit/skills/prototype-first-development/references/html-prototype-template/index.html +229 -0
  203. package/payload/plugins/toolkit/skills/prototype-first-development/references/html-prototype-template/mock-state.json +60 -0
  204. package/payload/plugins/toolkit/skills/prototype-first-development/references/implementation-plan-handoff-template.md +61 -0
  205. package/payload/plugins/toolkit/skills/prototype-first-development/references/prototype-workflow-checklist.md +64 -0
  206. package/payload/plugins/toolkit/skills/publish-runweave-server/SKILL.md +64 -0
  207. package/payload/plugins/toolkit/skills/publish-runweave-server/agents/openai.yaml +4 -0
  208. package/payload/plugins/toolkit/skills/react-best-practices/AGENTS.md +3227 -0
  209. package/payload/plugins/toolkit/skills/react-best-practices/README.md +127 -0
  210. package/payload/plugins/toolkit/skills/react-best-practices/SKILL.md +143 -0
  211. package/payload/plugins/toolkit/skills/react-best-practices/metadata.json +15 -0
  212. package/payload/plugins/toolkit/skills/react-best-practices/rules/advanced/event-handler-refs.md +55 -0
  213. package/payload/plugins/toolkit/skills/react-best-practices/rules/advanced/init-once.md +42 -0
  214. package/payload/plugins/toolkit/skills/react-best-practices/rules/advanced/use-latest.md +39 -0
  215. package/payload/plugins/toolkit/skills/react-best-practices/rules/async/api-routes.md +38 -0
  216. package/payload/plugins/toolkit/skills/react-best-practices/rules/async/defer-await.md +80 -0
  217. package/payload/plugins/toolkit/skills/react-best-practices/rules/async/dependencies.md +52 -0
  218. package/payload/plugins/toolkit/skills/react-best-practices/rules/async/parallel.md +28 -0
  219. package/payload/plugins/toolkit/skills/react-best-practices/rules/async/suspense-boundaries.md +99 -0
  220. package/payload/plugins/toolkit/skills/react-best-practices/rules/bundle/barrel-imports.md +59 -0
  221. package/payload/plugins/toolkit/skills/react-best-practices/rules/bundle/conditional.md +37 -0
  222. package/payload/plugins/toolkit/skills/react-best-practices/rules/bundle/defer-third-party.md +49 -0
  223. package/payload/plugins/toolkit/skills/react-best-practices/rules/bundle/dynamic-imports.md +35 -0
  224. package/payload/plugins/toolkit/skills/react-best-practices/rules/bundle/preload.md +46 -0
  225. package/payload/plugins/toolkit/skills/react-best-practices/rules/client/event-listeners.md +78 -0
  226. package/payload/plugins/toolkit/skills/react-best-practices/rules/client/localstorage-schema.md +77 -0
  227. package/payload/plugins/toolkit/skills/react-best-practices/rules/client/passive-event-listeners.md +48 -0
  228. package/payload/plugins/toolkit/skills/react-best-practices/rules/client/swr-dedup.md +56 -0
  229. package/payload/plugins/toolkit/skills/react-best-practices/rules/js/batch-dom-css.md +110 -0
  230. package/payload/plugins/toolkit/skills/react-best-practices/rules/js/cache-function-results.md +80 -0
  231. package/payload/plugins/toolkit/skills/react-best-practices/rules/js/cache-property-access.md +28 -0
  232. package/payload/plugins/toolkit/skills/react-best-practices/rules/js/cache-storage.md +70 -0
  233. package/payload/plugins/toolkit/skills/react-best-practices/rules/js/combine-iterations.md +32 -0
  234. package/payload/plugins/toolkit/skills/react-best-practices/rules/js/early-exit.md +50 -0
  235. package/payload/plugins/toolkit/skills/react-best-practices/rules/js/flatmap-filter.md +55 -0
  236. package/payload/plugins/toolkit/skills/react-best-practices/rules/js/hoist-regexp.md +45 -0
  237. package/payload/plugins/toolkit/skills/react-best-practices/rules/js/index-maps.md +37 -0
  238. package/payload/plugins/toolkit/skills/react-best-practices/rules/js/length-check-first.md +50 -0
  239. package/payload/plugins/toolkit/skills/react-best-practices/rules/js/min-max-loop.md +82 -0
  240. package/payload/plugins/toolkit/skills/react-best-practices/rules/js/set-map-lookups.md +24 -0
  241. package/payload/plugins/toolkit/skills/react-best-practices/rules/js/tosorted-immutable.md +57 -0
  242. package/payload/plugins/toolkit/skills/react-best-practices/rules/meta/sections.md +46 -0
  243. package/payload/plugins/toolkit/skills/react-best-practices/rules/meta/template.md +28 -0
  244. package/payload/plugins/toolkit/skills/react-best-practices/rules/rendering/activity.md +26 -0
  245. package/payload/plugins/toolkit/skills/react-best-practices/rules/rendering/animate-svg-wrapper.md +38 -0
  246. package/payload/plugins/toolkit/skills/react-best-practices/rules/rendering/conditional-render.md +32 -0
  247. package/payload/plugins/toolkit/skills/react-best-practices/rules/rendering/content-visibility.md +38 -0
  248. package/payload/plugins/toolkit/skills/react-best-practices/rules/rendering/hoist-jsx.md +36 -0
  249. package/payload/plugins/toolkit/skills/react-best-practices/rules/rendering/hydration-no-flicker.md +72 -0
  250. package/payload/plugins/toolkit/skills/react-best-practices/rules/rendering/hydration-suppress-warning.md +26 -0
  251. package/payload/plugins/toolkit/skills/react-best-practices/rules/rendering/resource-hints.md +89 -0
  252. package/payload/plugins/toolkit/skills/react-best-practices/rules/rendering/script-defer-async.md +71 -0
  253. package/payload/plugins/toolkit/skills/react-best-practices/rules/rendering/svg-precision.md +28 -0
  254. package/payload/plugins/toolkit/skills/react-best-practices/rules/rendering/usetransition-loading.md +75 -0
  255. package/payload/plugins/toolkit/skills/react-best-practices/rules/rerender/defer-reads.md +39 -0
  256. package/payload/plugins/toolkit/skills/react-best-practices/rules/rerender/dependencies.md +45 -0
  257. package/payload/plugins/toolkit/skills/react-best-practices/rules/rerender/derived-state-no-effect.md +40 -0
  258. package/payload/plugins/toolkit/skills/react-best-practices/rules/rerender/derived-state.md +29 -0
  259. package/payload/plugins/toolkit/skills/react-best-practices/rules/rerender/functional-setstate.md +77 -0
  260. package/payload/plugins/toolkit/skills/react-best-practices/rules/rerender/lazy-state-init.md +58 -0
  261. package/payload/plugins/toolkit/skills/react-best-practices/rules/rerender/memo-with-default-value.md +36 -0
  262. package/payload/plugins/toolkit/skills/react-best-practices/rules/rerender/memo.md +44 -0
  263. package/payload/plugins/toolkit/skills/react-best-practices/rules/rerender/move-effect-to-event.md +45 -0
  264. package/payload/plugins/toolkit/skills/react-best-practices/rules/rerender/no-inline-components.md +83 -0
  265. package/payload/plugins/toolkit/skills/react-best-practices/rules/rerender/simple-expression-in-memo.md +35 -0
  266. package/payload/plugins/toolkit/skills/react-best-practices/rules/rerender/transitions.md +40 -0
  267. package/payload/plugins/toolkit/skills/react-best-practices/rules/rerender/use-ref-transient-values.md +73 -0
  268. package/payload/plugins/toolkit/skills/react-best-practices/rules/server/after-nonblocking.md +74 -0
  269. package/payload/plugins/toolkit/skills/react-best-practices/rules/server/auth-actions.md +96 -0
  270. package/payload/plugins/toolkit/skills/react-best-practices/rules/server/cache-lru.md +41 -0
  271. package/payload/plugins/toolkit/skills/react-best-practices/rules/server/cache-react.md +76 -0
  272. package/payload/plugins/toolkit/skills/react-best-practices/rules/server/dedup-props.md +65 -0
  273. package/payload/plugins/toolkit/skills/react-best-practices/rules/server/hoist-static-io.md +139 -0
  274. package/payload/plugins/toolkit/skills/react-best-practices/rules/server/parallel-fetching.md +83 -0
  275. package/payload/plugins/toolkit/skills/react-best-practices/rules/server/serialization.md +38 -0
  276. package/payload/plugins/toolkit/skills/recorded-browser-mcp-verification/SKILL.md +8 -0
  277. package/payload/plugins/toolkit/skills/recorded-browser-mcp-verification/agents/openai.yaml +6 -0
  278. package/payload/plugins/toolkit/skills/reproduce-before-fix/SKILL.md +93 -0
  279. package/payload/plugins/toolkit/skills/reproduce-before-fix/agents/openai.yaml +7 -0
  280. package/payload/plugins/toolkit/skills/review-only/SKILL.md +72 -0
  281. package/payload/plugins/toolkit/skills/review-with-runtime-evidence/SKILL.md +134 -0
  282. package/payload/plugins/toolkit/skills/review-with-runtime-evidence/agents/openai.yaml +7 -0
  283. package/payload/plugins/toolkit/skills/review-with-runtime-evidence/references/contracts.md +114 -0
  284. package/payload/plugins/toolkit/skills/review-with-runtime-evidence/scripts/classify-runtime-verdict.mjs +140 -0
  285. package/payload/plugins/toolkit/skills/review-with-runtime-evidence/scripts/run-static-review.mjs +389 -0
  286. package/payload/plugins/toolkit/skills/review-with-runtime-evidence/scripts/static-review-contract.mjs +243 -0
  287. package/payload/plugins/toolkit/skills/run-test-cases/SKILL.md +63 -0
  288. package/payload/plugins/toolkit/skills/runweave-change-validation/SKILL.md +196 -0
  289. package/payload/plugins/toolkit/skills/runweave-change-validation/agents/openai.yaml +7 -0
  290. package/payload/plugins/toolkit/skills/runweave-change-validation/scripts/assert-dev-session-status.mjs +181 -0
  291. package/payload/plugins/toolkit/skills/runweave-dev-session/SKILL.md +126 -0
  292. package/payload/plugins/toolkit/skills/runweave-dev-session/agents/openai.yaml +7 -0
  293. package/payload/plugins/toolkit/skills/runweave-management/SKILL.md +47 -0
  294. package/payload/plugins/toolkit/skills/scheduled-task/SKILL.md +35 -0
  295. package/payload/plugins/toolkit/skills/shadcn/SKILL.md +245 -0
  296. package/payload/plugins/toolkit/skills/shadcn/agents/openai.yml +5 -0
  297. package/payload/plugins/toolkit/skills/shadcn/assets/shadcn-small.png +0 -0
  298. package/payload/plugins/toolkit/skills/shadcn/assets/shadcn.png +0 -0
  299. package/payload/plugins/toolkit/skills/shadcn/cli.md +276 -0
  300. package/payload/plugins/toolkit/skills/shadcn/customization.md +209 -0
  301. package/payload/plugins/toolkit/skills/shadcn/evals/evals.json +47 -0
  302. package/payload/plugins/toolkit/skills/shadcn/mcp.md +94 -0
  303. package/payload/plugins/toolkit/skills/shadcn/rules/base-vs-radix.md +310 -0
  304. package/payload/plugins/toolkit/skills/shadcn/rules/composition.md +197 -0
  305. package/payload/plugins/toolkit/skills/shadcn/rules/forms.md +198 -0
  306. package/payload/plugins/toolkit/skills/shadcn/rules/icons.md +101 -0
  307. package/payload/plugins/toolkit/skills/shadcn/rules/styling.md +163 -0
  308. package/payload/plugins/toolkit/skills/suiji/SKILL.md +54 -0
  309. package/payload/plugins/toolkit/skills/suiji/references/api.md +72 -0
  310. package/payload/plugins/toolkit/skills/suiji/references/source-adapter.md +40 -0
  311. package/payload/plugins/toolkit/skills/suiji/references/work-records.md +36 -0
  312. package/payload/plugins/toolkit/skills/suiji/scripts/source_adapter.py +46 -0
  313. package/payload/plugins/toolkit/skills/suiji/scripts/suiji.py +358 -0
  314. package/payload/plugins/toolkit/skills/suiji/scripts/work_records.py +328 -0
  315. package/payload/plugins/toolkit/skills/suiji/scripts/work_reports.py +75 -0
  316. package/payload/plugins/toolkit/skills/sync-branch-hard-reset/SKILL.md +59 -0
  317. package/payload/plugins/toolkit/skills/update-runweave-backend/SKILL.md +90 -0
  318. package/payload/plugins/toolkit/skills/update-runweave-backend/agents/openai.yaml +4 -0
  319. package/payload/plugins/toolkit/skills/update-runweave-backend/references/npm-update.md +68 -0
  320. package/payload/plugins/toolkit/skills/update-runweave-backend/references/systemd.md +34 -0
  321. package/payload/plugins/toolkit/skills/update-runweave-desktop/SKILL.md +102 -0
  322. package/payload/plugins/toolkit/skills/update-runweave-ios/SKILL.md +16 -0
  323. package/payload/plugins/toolkit/skills/update-runweave-ios/references/ios-wireless-recovery.md +54 -0
  324. package/payload/plugins/toolkit/skills/using-rw/SKILL.md +265 -0
  325. package/payload/plugins/toolkit/skills/write-plan/SKILL.md +72 -0
  326. package/payload/plugins/toolkit/skills/write-test-cases/SKILL.md +100 -0
  327. package/payload/plugins/toolkit/skills/write-test-cases/assets/test-plan-template.yaml +15 -0
  328. package/payload/plugins/toolkit/skills/write-test-cases/references/coverage-checklist.md +75 -0
  329. package/payload/plugins/toolkit/skills/write-test-cases/references/design-techniques.md +91 -0
  330. package/payload/plugins/toolkit/skills/youtube-reading/SKILL.md +35 -0
  331. package/payload/plugins/toolkit/skills/youtube-reading/agents/openai.yaml +4 -0
  332. package/payload/plugins/toolkit/skills/youtube-reading/assets/runtime-lock.json +92 -0
  333. package/payload/plugins/toolkit/skills/youtube-reading/references/content-contract.md +52 -0
  334. package/payload/plugins/toolkit/skills/youtube-reading/references/setup.md +70 -0
  335. package/payload/plugins/toolkit/skills/youtube-reading/requirements.lock +9 -0
  336. package/payload/plugins/toolkit/skills/youtube-reading/scripts/artifacts.py +206 -0
  337. package/payload/plugins/toolkit/skills/youtube-reading/scripts/runtime.py +424 -0
  338. package/payload/plugins/toolkit/skills/youtube-reading/scripts/youtube_reading.py +314 -0
  339. package/payload/plugins/toolkit-release.json +1 -0
  340. package/payload/scripts/toolkit/codex-state.mjs +75 -0
  341. package/payload/scripts/toolkit/release.mjs +210 -0
  342. package/payload/scripts/toolkit/sync-runtime.mjs +108 -0
  343. package/payload/scripts/toolkit/sync.mjs +585 -0
  344. package/payload/frontend/dist/assets/channel-CuSisha8.js +0 -1
  345. package/payload/frontend/dist/assets/classDiagram-6PBFFD2Q-ClJkT7PY.js +0 -1
  346. package/payload/frontend/dist/assets/classDiagram-v2-HSJHXN6E-ClJkT7PY.js +0 -1
  347. package/payload/frontend/dist/assets/clone-CPcyXi9g.js +0 -1
  348. package/payload/frontend/dist/assets/connection-BhcgnOMb.js +0 -12
  349. package/payload/frontend/dist/assets/main-BDBhLSX2.js +0 -696
  350. package/payload/frontend/dist/assets/main-Cg6Oo1xK.css +0 -33
  351. package/payload/frontend/dist/assets/stateDiagram-v2-QKLJ7IA2-DhdOfEAv.js +0 -1
  352. package/payload/frontend/dist/assets/suiji-DXtfgwxj.js +0 -1
@@ -0,0 +1,69 @@
1
+ """Retire legacy browsing inputs without replaying unknown work writes."""
2
+ import copy
3
+ from pathlib import Path
4
+ from job_state import require, read, write, binding, process_identity, digest
5
+
6
+
7
+ def blocked_scope(value):
8
+ return any(value.startswith(p) for p in ("browser:", "materials:", "browse:"))
9
+
10
+
11
+ def assert_payload(payload):
12
+ require(not payload.get("materialResults") and not any(
13
+ any(blocked_scope(s) for s in day.get("requestedScopes", [])) or
14
+ any(ref.get("channel") in ("browser-visit", "suiji-material") or blocked_scope(ref.get("sourceScope", ""))
15
+ for fact in day.get("facts", []) for ref in fact.get("sourceRefs", []))
16
+ for day in payload.get("days", [])), "BROWSING_IS_NOT_WORK: retire the old batch with isolate-work")
17
+
18
+
19
+ def migrate(state, config_path, dry_run, suiji):
20
+ require(not (state.root / "source-migration.json").exists(), "FINISH_SOURCE_MIGRATION_FIRST")
21
+ journal = state.root / "work-isolation.json"
22
+ if journal.exists():
23
+ saved = read(journal)
24
+ require(saved["configPath"] == str(Path(config_path).resolve()), "ISOLATION_TARGET_CHANGED")
25
+ original_state = read(state.root / "before-work-isolation-state.json")
26
+ original_config = read(state.root / "before-work-isolation-config.json")
27
+ require(state.data.get("binding") in (saved["state"]["binding"], original_state["binding"]) and state.c in (saved["config"], original_config), "ISOLATION_IDENTITY_CHANGED")
28
+ if not dry_run:
29
+ write(state.file, saved["state"])
30
+ write(config_path, saved["config"])
31
+ state.data = saved["state"]
32
+ return {"dryRun": dry_run, "isolated": not dry_run, "newContextRequired": True}
33
+ updated = copy.deepcopy(state.data)
34
+ config = copy.deepcopy(state.c)
35
+ for key in ("browser", "materials"):
36
+ config["sources"].pop(key, None)
37
+ active = updated.get("active")
38
+ receipt = None
39
+ if active:
40
+ path = Path(updated["batches"][active]["path"])
41
+ meta = read(path / "batch.json")
42
+ require(process_identity(meta["ownerPid"]) != meta["ownerProcess"], "BUSY: original Agent is still active")
43
+ pending = meta.get("pending")
44
+ if pending:
45
+ original = read(pending["request"])
46
+ require(digest(original) == pending["hash"], "FROZEN_REQUEST_CHANGED")
47
+ connection = read(config["suiji"]["configPath"])
48
+ client, _ = suiji.connect(connection, config["suiji"]["tokenFile"])
49
+ # This endpoint compares the original immutable operation hash and returns no fact bodies.
50
+ receipt = client.call("get_work_import_receipt", {"idempotencyKey": original["arguments"]["idempotencyKey"], "input": original["arguments"]["input"]})
51
+ updated["batches"][active] = {**updated["batches"][active], "phase": "retired-browsing", "receipt": receipt}
52
+ updated["active"] = None
53
+ updated["identities"] = {key: value for key, value in updated["identities"].items()
54
+ if not any(blocked_scope(a) for a in value.get("anchors", []))}
55
+ updated["forbiddenWorkThreads"] = updated.get("excludedThreads", [])
56
+ updated["workIsolationVersion"] = 1
57
+ updated["binding"] = binding(config)
58
+ for day in updated["days"].values():
59
+ day["receipts"] = [r for r in day.get("receipts", []) if not blocked_scope(r["sourceScope"])]
60
+ day["complete"] = bool(day["receipts"]) and all(r["status"] == "complete" for r in day["receipts"])
61
+ result = {"dryRun": dry_run, "retiredBatch": active, "retainedIdentities": len(updated["identities"]), "receipt": receipt, "newContextRequired": True}
62
+ if not dry_run:
63
+ write(state.root / "before-work-isolation-state.json", state.data)
64
+ write(state.root / "before-work-isolation-config.json", state.c)
65
+ write(journal, {"configPath": str(Path(config_path).resolve()), "state": updated, "config": config})
66
+ write(state.file, updated)
67
+ write(config_path, config)
68
+ state.data = updated
69
+ return result
@@ -0,0 +1,154 @@
1
+ """Opt-in user supplied materials, with separate scan and consumption checkpoints."""
2
+ import base64
3
+ import copy
4
+ import hashlib
5
+ from pathlib import Path
6
+ from job_state import directory, read, require, stamp, window, write, materials_enabled
7
+
8
+
9
+ def client_for(c, suiji):
10
+ connection = read(c["suiji"]["configPath"])
11
+ client, info = suiji.connect(connection, c["suiji"]["tokenFile"])
12
+ require(info.get("features", {}).get("materials") and info.get("features", {}).get("workMaterials"), "MATERIALS_UNAVAILABLE")
13
+ require(c["sources"]["materials"]["accountScope"] == info["serverId"] + ":" + info["ownerId"], "MATERIAL_ACCOUNT_MISMATCH")
14
+ return client
15
+
16
+
17
+ def scope(c):
18
+ return "materials:" + c["sources"]["materials"]["accountScope"]
19
+
20
+
21
+ def reconcile(state, suiji):
22
+ if not materials_enabled(state.c):
23
+ return
24
+ client = client_for(state.c, suiji)
25
+ snapshot = copy.deepcopy(state.data.get("materials", {"cursor": None, "records": {}}))
26
+ cursor, changed = snapshot["cursor"], set()
27
+ while True:
28
+ page = client.call("list_changes", {"limit": 50, **({"cursor": cursor} if cursor else {})})
29
+ for item in page["items"]:
30
+ if item["kind"] == "followup_added":
31
+ continue
32
+ changed.add(item["recordId"])
33
+ require(page["nextCursor"] != cursor or not page["hasMore"], "MATERIAL_CHANGE_CURSOR_STUCK")
34
+ cursor = page["nextCursor"]
35
+ if not page["hasMore"]:
36
+ break
37
+ affected = set()
38
+ for record_id in sorted(changed):
39
+ old = snapshot["records"].get(record_id)
40
+ if old:
41
+ affected.add(old["material"]["materialDate"])
42
+ try:
43
+ record = client.call("get_record", {"recordId": record_id})["record"]
44
+ except suiji.Failure as error:
45
+ if str(error) != "NOT_FOUND":
46
+ raise
47
+ record = None
48
+ if record and record.get("material") and not record.get("deletedAt"):
49
+ snapshot["records"][record_id] = record
50
+ affected.add(record["material"]["materialDate"])
51
+ else:
52
+ snapshot["records"].pop(record_id, None)
53
+ snapshot["cursor"] = cursor
54
+ state.data["materials"] = snapshot
55
+ for day in affected:
56
+ if materials_enabled(state.c, day):
57
+ state.data["days"].setdefault(day, {})["complete"] = False
58
+ # Index and cursor commit atomically. Consumption is only confirmed after publish readback.
59
+ write(state.file, state.data)
60
+
61
+
62
+ def rows(state, day, bundle, suiji):
63
+ client = client_for(state.c, suiji)
64
+ directory(bundle)
65
+ result, complete = [], True
66
+ for expected in state.data.get("materials", {}).get("records", {}).values():
67
+ if expected["material"]["materialDate"] != day:
68
+ continue
69
+ record = client.call("get_record", {"recordId": expected["id"]})["record"]
70
+ material = record.get("material")
71
+ require(material and not record.get("deletedAt") and material["sourceHash"] == expected["material"]["sourceHash"], "MATERIAL_VERSION_CHANGED: recollect")
72
+ images, image_error = [], False
73
+ for attachment in record["attachments"]:
74
+ if attachment["kind"] != "image":
75
+ # First release consumes image/text/link only. Do not claim an unread attachment.
76
+ image_error = True
77
+ continue
78
+ try:
79
+ value = client.call("read_attachment", {"attachmentId": attachment["id"]})
80
+ blocks = [b for b in value.get("content", []) if b.get("type") == "image"]
81
+ metadata = value.get("structuredContent", {})
82
+ require(len(blocks) == 1 and metadata.get("attachmentId") == attachment["id"], "MATERIAL_IMAGE_IDENTITY_MISMATCH")
83
+ data = base64.b64decode(blocks[0]["data"], validate=True)
84
+ sha = hashlib.sha256(data).hexdigest()
85
+ require(len(data) == attachment["byteSize"] and blocks[0]["mimeType"] == attachment["mimeType"] and metadata.get("sha256") == sha, "MATERIAL_IMAGE_CONTENT_MISMATCH")
86
+ path = bundle / (attachment["id"] + (".png" if attachment["mimeType"] == "image/png" else ".jpg"))
87
+ with path.open("wb") as stream:
88
+ stream.write(data)
89
+ path.chmod(0o600)
90
+ images.append({"attachmentId": attachment["id"], "path": str(path), "sha256": sha})
91
+ except Exception:
92
+ image_error = True
93
+ current = client.call("get_record", {"recordId": record["id"]})["record"]
94
+ require(current.get("material", {}).get("sourceHash") == material["sourceHash"], "MATERIAL_VERSION_CHANGED: recollect")
95
+ complete = complete and not image_error
96
+ result.append({"id": "material:" + record["id"], "day": day, "role": "user", "text": record["body"],
97
+ "anchor": scope(state.c) + ":" + record["id"], "project": None, "images": images, "imageError": image_error,
98
+ "material": material, "ref": {"channel": "suiji-material", "sourceScope": scope(state.c),
99
+ "nativeId": record["id"], "revision": str(record["version"]), "hash": material["sourceHash"]}})
100
+ return result, {"sourceScope": scope(state.c), "status": "complete" if complete else "partial", "collectedAt": stamp(), **window(day)}
101
+
102
+
103
+ def acknowledge_image(path, meta, material, receipt_path):
104
+ """Executor calls only AFTER passing the actual image to its vision-capable model."""
105
+ require(material["ref"]["channel"] == "suiji-material" and material["images"], "IMAGE_MATERIAL_REQUIRED")
106
+ receipt = read(receipt_path)
107
+ require(set(receipt) == {"materialId", "sourceHash", "reader", "toolCallId", "images", "observation"}, "INVALID_IMAGE_READ_RECEIPT")
108
+ require(receipt["materialId"] == material["id"] and receipt["sourceHash"] == material["ref"]["hash"], "IMAGE_READ_VERSION_MISMATCH")
109
+ require(all(isinstance(receipt[k], str) and receipt[k].strip() for k in ("reader", "toolCallId", "observation")), "ACTUAL_VISION_READ_REQUIRED")
110
+ expected = [{"attachmentId": i["attachmentId"], "sha256": i["sha256"]} for i in material["images"]]
111
+ require(receipt["images"] == expected, "IMAGE_READ_IDENTITY_MISMATCH")
112
+ for item in material["images"]:
113
+ require(hashlib.sha256(Path(item["path"]).read_bytes()).hexdigest() == item["sha256"], "IMAGE_FILE_CHANGED")
114
+ meta.setdefault("imageReads", {})[material["id"]] = receipt
115
+ write(path / (material["ref"]["nativeId"] + "-image-read.json"), receipt)
116
+ return {"acknowledged": material["id"], "sourceHash": material["ref"]["hash"]}
117
+
118
+
119
+ def results(materials, candidates, meta, exported):
120
+ source = {m["id"]: m for m in materials if m["ref"]["channel"] == "suiji-material"}
121
+ reviews = candidates.get("materialReviews", [])
122
+ require(isinstance(reviews, list) and len(reviews) == len(source) and {r.get("id") for r in reviews} == set(source), "REVIEW_EVERY_MATERIAL: include deferred when vision is unavailable")
123
+ output = []
124
+ for review in reviews:
125
+ require(set(review) == {"id", "outcome", "reason"} and review["outcome"] in ("included", "merged", "no-new-fact", "deferred") and isinstance(review["reason"], str) and review["reason"].strip(), "INVALID_MATERIAL_REVIEW")
126
+ m = source[review["id"]]
127
+ facts = [f for day in exported for f in day["facts"] if any(r["channel"] == "suiji-material" and r["nativeId"] == m["ref"]["nativeId"] for r in f["sourceRefs"])]
128
+ if review["outcome"] == "deferred":
129
+ require(not facts, "DEFERRED_MATERIAL_CANNOT_PROVE_FACT")
130
+ for day in exported:
131
+ if day["date"] == m["day"]:
132
+ for r in day["receipts"]:
133
+ if r["sourceScope"] == m["ref"]["sourceScope"]:
134
+ r["status"] = "partial"
135
+ continue
136
+ require(not m["imageError"], "MATERIAL_ATTACHMENT_UNREAD")
137
+ if m["images"]:
138
+ receipt = meta.get("imageReads", {}).get(m["id"], {})
139
+ require(receipt.get("sourceHash") == m["ref"]["hash"], "ACTUAL_VISION_READ_REQUIRED: text read does not read images")
140
+ for image in m["images"]:
141
+ require(hashlib.sha256(Path(image["path"]).read_bytes()).hexdigest() == image["sha256"], "IMAGE_FILE_CHANGED")
142
+ require(bool(facts) == (review["outcome"] != "no-new-fact"), "MATERIAL_OUTCOME_FACT_MISMATCH")
143
+ output.append({"recordId": m["ref"]["nativeId"], "recordVersion": int(m["ref"]["revision"]), "sourceHash": m["ref"]["hash"],
144
+ "outcome": review["outcome"], "eventKeys": sorted({f["eventKey"] for f in facts})})
145
+ return output
146
+
147
+
148
+ def verify_versions(c, payload, suiji):
149
+ if not payload.get("materialResults"):
150
+ return
151
+ client = client_for(c, suiji)
152
+ for item in payload["materialResults"]:
153
+ record = client.call("get_record", {"recordId": item["recordId"]})["record"]
154
+ require(record.get("material", {}).get("sourceHash") == item["sourceHash"] and record["version"] == item["recordVersion"], "MATERIAL_VERSION_CHANGED: recollect before publishing")
@@ -0,0 +1,77 @@
1
+ """Read a frozen, authenticated ScreenJournal export; never open the media store."""
2
+ import datetime as dt
3
+ import hashlib
4
+ import json
5
+ import subprocess
6
+ from zoneinfo import ZoneInfo
7
+ from job_state import Failure, digest, read, require, stamp, window
8
+
9
+
10
+ def source_scope(c):
11
+ return "screen:" + c["producerId"] + ":" + c["sources"]["screen"]["expectedDeviceID"] + ":observed"
12
+
13
+
14
+ def rows(bundle, day, c):
15
+ manifest = read(bundle / "manifest.json")
16
+ raw = (bundle / "export.json").read_bytes()
17
+ require(len(raw) <= 8 * 1024 * 1024 and hashlib.sha256(raw).hexdigest() == manifest["sha256"], "SCREEN_EXPORT_CHANGED")
18
+ value = json.loads(raw)
19
+ require(value["schemaVersion"] == 1 and value["deviceID"] == c["sources"]["screen"]["expectedDeviceID"] and value["day"] == day and value["timeZone"] == "Asia/Shanghai", "SCREEN_IDENTITY_MISMATCH")
20
+ require(value["revision"] == manifest["revision"] and value["snapshotID"] == manifest["snapshotID"] and not value.get("nextCursor"), "SCREEN_SNAPSHOT_INCOMPLETE")
21
+ canonical = dict(value, contentHash="")
22
+ expected_hash = hashlib.sha256(json.dumps(canonical, ensure_ascii=False, sort_keys=True, separators=(",", ":")).encode()).hexdigest()
23
+ require(value.get("contentHash") == expected_hash, "SCREEN_CONTENT_HASH_MISMATCH")
24
+ require(value["coverage"] in ("complete", "partial", "unavailable"), "SCREEN_COVERAGE_INVALID")
25
+ result, seen = [], set()
26
+ for card in value["cards"]:
27
+ require(card["cardID"] not in seen and isinstance(card["revision"], int) and card["revision"] > 0, "SCREEN_CARD_IDENTITY_INVALID")
28
+ seen.add(card["cardID"])
29
+ require(card["evidenceIDs"] and card["spans"], "SCREEN_EVIDENCE_REQUIRED")
30
+ for span in card["spans"]:
31
+ for ms in (span["startMs"], span["endMs"]):
32
+ require(dt.datetime.fromtimestamp(ms / 1000, ZoneInfo("Asia/Shanghai")).date().isoformat() == day, "SCREEN_DAY_MISMATCH")
33
+ # Only facts and stable IDs cross the publish boundary. No JPEG, path,
34
+ # transient view ID or raw thread transcript is a source reference.
35
+ ref = {"channel": "screen-timeline", "sourceScope": source_scope(c), "nativeId": card["cardID"], "revision": str(card["revision"]), "hash": digest(card)}
36
+ result.append({"id": "screen:" + value["deviceID"] + ":" + card["cardID"], "day": day, "role": "observation", "text": json.dumps(card, ensure_ascii=False),
37
+ "occurredAt": dt.datetime.fromtimestamp(min(span["startMs"] for span in card["spans"]) / 1000, dt.timezone.utc).isoformat(),
38
+ "sourceVersion": str(card["revision"]) + ":" + ref["hash"],
39
+ "anchor": source_scope(c) + ":" + card["matterID"], "project": None, "ref": ref})
40
+ receipt = {"sourceScope": source_scope(c), "status": value["coverage"], "collectedAt": stamp(), **window(day)}
41
+ return result, receipt, value["revision"]
42
+
43
+
44
+ def unavailable(day, c):
45
+ return {"sourceScope": source_scope(c), "status": "unavailable", "collectedAt": stamp(), **window(day)}
46
+
47
+
48
+ def export(c, day, bundle=None):
49
+ argv = c["sources"]["screen"]["exporterArgv"] + ["--day", day, "--json"]
50
+ if bundle is not None:
51
+ argv += ["--out", str(bundle)]
52
+ try:
53
+ result = subprocess.run(argv, capture_output=True, text=True, timeout=120 if bundle is not None else 10, check=False)
54
+ except (OSError, subprocess.TimeoutExpired):
55
+ require(False, "SCREEN_EXPORT_UNAVAILABLE")
56
+ require(result.returncode == 0 and len(result.stdout.encode()) <= 8 * 1024 * 1024, "SCREEN_EXPORT_UNAVAILABLE")
57
+ return json.loads(result.stdout)
58
+
59
+
60
+ def reconcile_revisions(state):
61
+ # Called only after the original active/pending package is resolved.
62
+ today = dt.datetime.now(ZoneInfo("Asia/Shanghai")).date()
63
+ screen = state.c["sources"].get("screen", {})
64
+ if not screen.get("enabled"):
65
+ return
66
+ for offset in range(1, 8):
67
+ day = (today - dt.timedelta(days=offset)).isoformat()
68
+ old = state.data["days"].get(day)
69
+ if old is None or day < screen["effectiveFrom"]:
70
+ continue
71
+ try:
72
+ value = export(state.c, day)
73
+ require(value["deviceID"] == screen["expectedDeviceID"] and value["day"] == day and value["schemaVersion"] == 1, "SCREEN_IDENTITY_MISMATCH")
74
+ if old.get("screenRevision") != value["revision"]:
75
+ old["complete"] = False
76
+ except (OSError, ValueError, KeyError, subprocess.SubprocessError, Failure):
77
+ old["complete"] = False
@@ -0,0 +1,309 @@
1
+ """Private local state for the shared scheduled Daily Record workflow."""
2
+ import datetime as dt
3
+ import fcntl
4
+ import hashlib
5
+ import json
6
+ import os
7
+ from pathlib import Path
8
+ import re
9
+ import subprocess
10
+ import tempfile
11
+ import sys
12
+ import copy
13
+ import uuid
14
+ from zoneinfo import ZoneInfo
15
+
16
+
17
+ class Failure(Exception):
18
+ pass
19
+
20
+
21
+ def require(ok, message):
22
+ if not ok:
23
+ raise Failure(message)
24
+
25
+
26
+ def digest(value):
27
+ return hashlib.sha256(json.dumps(value, ensure_ascii=False, sort_keys=True, separators=(",", ":")).encode()).hexdigest()
28
+
29
+
30
+ def read(path):
31
+ path = Path(path)
32
+ require(not path.is_symlink() and path.is_file() and not path.stat().st_mode & 0o077, "PRIVATE_FILE_REQUIRED")
33
+ return json.loads(path.read_bytes())
34
+
35
+
36
+ def write(path, value):
37
+ path = Path(path)
38
+ fd, name = tempfile.mkstemp(prefix=".daily-", dir=path.parent)
39
+ try:
40
+ with os.fdopen(fd, "wb") as stream:
41
+ stream.write((json.dumps(value, ensure_ascii=False, indent=2) + "\n").encode())
42
+ stream.flush()
43
+ os.fsync(stream.fileno())
44
+ os.replace(name, path)
45
+ descriptor = os.open(path.parent, os.O_RDONLY)
46
+ try:
47
+ os.fsync(descriptor)
48
+ finally:
49
+ os.close(descriptor)
50
+ finally:
51
+ if os.path.exists(name):
52
+ os.unlink(name)
53
+
54
+
55
+ def directory(path):
56
+ path = Path(path)
57
+ require(not path.is_symlink(), "DIRECTORY_SYMLINK_REJECTED")
58
+ path.mkdir(mode=0o700, parents=True, exist_ok=True)
59
+ require(path.is_dir() and not path.stat().st_mode & 0o077, "PRIVATE_DIRECTORY_REQUIRED")
60
+ return path
61
+
62
+
63
+ def date(value):
64
+ parsed = dt.date.fromisoformat(value)
65
+ require(parsed.isoformat() == value, "INVALID_DATE")
66
+ return parsed
67
+
68
+
69
+ def today():
70
+ return dt.datetime.now(ZoneInfo("Asia/Shanghai")).date()
71
+
72
+
73
+ def stamp():
74
+ return dt.datetime.now(dt.timezone.utc).isoformat()
75
+
76
+
77
+ def window(day):
78
+ start = dt.datetime.combine(date(day), dt.time(), ZoneInfo("Asia/Shanghai"))
79
+ return {"from": start.isoformat(), "to": (start + dt.timedelta(days=1)).isoformat()}
80
+
81
+
82
+ def exact(value, required, optional=()):
83
+ require(isinstance(value, dict) and set(required) <= set(value) and not set(value) - set(required) - set(optional), "INVALID_CONFIG_FIELDS")
84
+
85
+
86
+ def scope(value):
87
+ require(isinstance(value, str) and re.fullmatch(r"[A-Za-z0-9_.:-]{1,120}", value), "INVALID_SCOPE")
88
+
89
+
90
+ def config(path):
91
+ c = read(path)
92
+ exact(c, {"schemaVersion", "producerId", "sourceNamespace", "startDate", "timeZone", "stateDir", "collectorArgv", "sources", "projectAliases", "suiji"}, {"maxDaysPerRun"})
93
+ require(c["schemaVersion"] == 1 and c["timeZone"] == "Asia/Shanghai", "INVALID_JOB_VERSION_OR_TIMEZONE")
94
+ scope(c["producerId"])
95
+ scope(c["sourceNamespace"])
96
+ date(c["startDate"])
97
+ exact(c["sources"], {"localAgent", "feishu"}, {"browser", "screen", "materials"})
98
+ local, feishu = c["sources"]["localAgent"], c["sources"]["feishu"]
99
+ exact(local, {"providers"}, {"home"})
100
+ require(isinstance(local["providers"], list) and all(p in ("codex", "pi", "claude", "trae") for p in local["providers"]) and len(set(local["providers"])) == len(local["providers"]), "INVALID_PROVIDERS")
101
+ exact(feishu, {"enabled", "channels"}, {"accountScope"})
102
+ require(type(feishu["enabled"]) is bool and feishu["channels"] == "messages", "MESSAGES_ONLY")
103
+ if feishu["enabled"]:
104
+ scope(feishu.get("accountScope"))
105
+ browser = c["sources"].get("browser", {"enabled": False})
106
+ require(type(browser.get("enabled")) is bool, "INVALID_BROWSER_ENABLED")
107
+ if browser["enabled"]:
108
+ exact(browser, {"enabled", "effectiveFrom", "root", "collectorArgv", "expectedSourceIds"})
109
+ require(sys.platform == "darwin", "BROWSER_REQUIRES_MACOS")
110
+ date(browser["effectiveFrom"])
111
+ require(browser["effectiveFrom"] >= c["startDate"], "BROWSER_BEFORE_JOB_START")
112
+ require(isinstance(browser["root"], str) and Path(browser["root"]).is_absolute(), "ABSOLUTE_PATH_REQUIRED")
113
+ require(isinstance(browser["collectorArgv"], list) and browser["collectorArgv"] and all(isinstance(v, str) and v and "\0" not in v for v in browser["collectorArgv"]), "INVALID_BROWSER_ARGV")
114
+ ids = browser["expectedSourceIds"]
115
+ require(isinstance(ids, list) and ids and len(ids) == len(set(ids)) and all(isinstance(v, str) and str(uuid.UUID(v)) == v for v in ids), "INVALID_BROWSER_SOURCE_IDS")
116
+ else:
117
+ exact(browser, {"enabled"})
118
+ # Keep the original binding byte-for-byte for old/disabled configurations.
119
+ c["sources"].pop("browser", None)
120
+ screen = c["sources"].get("screen", {"enabled": False})
121
+ require(type(screen.get("enabled")) is bool, "INVALID_SCREEN_ENABLED")
122
+ if screen["enabled"]:
123
+ exact(screen, {"enabled", "effectiveFrom", "expectedDeviceID", "exporterArgv"})
124
+ require(sys.platform == "darwin", "SCREEN_REQUIRES_MACOS")
125
+ date(screen["effectiveFrom"])
126
+ require(screen["effectiveFrom"] >= c["startDate"], "SCREEN_BEFORE_JOB_START")
127
+ require(isinstance(screen["expectedDeviceID"], str) and str(uuid.UUID(screen["expectedDeviceID"])).lower() == screen["expectedDeviceID"].lower(), "INVALID_SCREEN_DEVICE")
128
+ argv = screen["exporterArgv"]
129
+ require(isinstance(argv, list) and argv and all(isinstance(v, str) and v and "\0" not in v for v in argv), "INVALID_SCREEN_ARGV")
130
+ require(argv[1:] == ["screen-recording", "analysis", "export", "--instance", "stable"], "SCREEN_EXPORTER_MUST_USE_STABLE")
131
+ require(Path(argv[0]).is_absolute(), "ABSOLUTE_PATH_REQUIRED")
132
+ else:
133
+ exact(screen, {"enabled"})
134
+ c["sources"].pop("screen", None)
135
+ materials = c["sources"].get("materials", {"enabled": False})
136
+ require(type(materials.get("enabled")) is bool, "INVALID_MATERIALS_ENABLED")
137
+ if materials["enabled"]:
138
+ exact(materials, {"enabled", "effectiveFrom", "accountScope"})
139
+ date(materials["effectiveFrom"])
140
+ require(materials["effectiveFrom"] >= c["startDate"], "MATERIALS_BEFORE_JOB_START")
141
+ connection = read(c["suiji"]["configPath"])
142
+ require(materials["accountScope"] == connection["serverId"] + ":" + connection["ownerId"], "MATERIAL_ACCOUNT_MISMATCH")
143
+ else:
144
+ exact(materials, {"enabled"})
145
+ c["sources"].pop("materials", None)
146
+ require(local["providers"] or feishu["enabled"] or (browser["enabled"] and browser["effectiveFrom"] == c["startDate"]) or (materials["enabled"] and materials["effectiveFrom"] == c["startDate"]), "NO_ENABLED_SOURCE_FOR_START_DATE")
147
+ require(isinstance(c["collectorArgv"], list) and c["collectorArgv"] and all(isinstance(v, str) and v and "\0" not in v for v in c["collectorArgv"]), "INVALID_COLLECTOR_ARGV")
148
+ exact(c["suiji"], {"configPath", "tokenFile", "autoPublish"})
149
+ require(type(c["suiji"]["autoPublish"]) is bool, "INVALID_AUTO_PUBLISH")
150
+ for value in [c["stateDir"], c["suiji"]["configPath"], c["suiji"]["tokenFile"]] + ([local["home"]] if "home" in local else []):
151
+ require(isinstance(value, str) and Path(value).is_absolute(), "ABSOLUTE_PATH_REQUIRED")
152
+ require(isinstance(c["projectAliases"], dict) and all(isinstance(k, str) and isinstance(v, str) and 0 < len(v.strip()) <= 80 and not any(ch in v for ch in "\r\n\t") for k, v in c["projectAliases"].items()), "INVALID_PROJECT_ALIASES")
153
+ c.setdefault("maxDaysPerRun", 7)
154
+ require(type(c["maxDaysPerRun"]) is int and 1 <= c["maxDaysPerRun"] <= 366, "INVALID_DAY_BUDGET")
155
+ scopes(c)
156
+ return c
157
+
158
+
159
+ def browser_enabled(c, day=None):
160
+ browser = c["sources"].get("browser", {})
161
+ return bool(browser.get("enabled") and (day is None or day >= browser["effectiveFrom"]))
162
+
163
+
164
+ def screen_enabled(c, day=None):
165
+ screen = c["sources"].get("screen", {})
166
+ return bool(screen.get("enabled") and (day is None or day >= screen["effectiveFrom"]))
167
+
168
+
169
+ def materials_enabled(c, day=None):
170
+ value = c["sources"].get("materials", {})
171
+ return bool(value.get("enabled") and (day is None or day >= value["effectiveFrom"]))
172
+
173
+
174
+ def binding(c):
175
+ connection = read(c["suiji"]["configPath"])
176
+ value = {k: c[k] for k in ("producerId", "sourceNamespace", "startDate", "sources")}
177
+ value["target"] = {k: connection[k] for k in ("endpoint", "serverId", "ownerId")}
178
+ return digest(value)
179
+
180
+
181
+ def scopes(c, day=None):
182
+ values = ["agent:" + c["producerId"] + ":" + p for p in c["sources"]["localAgent"]["providers"]]
183
+ if c["sources"]["feishu"]["enabled"]:
184
+ values.append("feishu:" + c["sources"]["feishu"]["accountScope"] + ":messages")
185
+ if browser_enabled(c, day):
186
+ values.append("browser:" + c["producerId"] + ":observed")
187
+ if screen_enabled(c, day):
188
+ values.append("screen:" + c["producerId"] + ":" + c["sources"]["screen"]["expectedDeviceID"] + ":observed")
189
+ if materials_enabled(c, day):
190
+ values.append("materials:" + c["sources"]["materials"]["accountScope"])
191
+ for value in values:
192
+ scope(value)
193
+ return values
194
+
195
+
196
+ def process_identity(pid):
197
+ try:
198
+ result = subprocess.run(["ps", "-p", str(pid), "-o", "lstart="], capture_output=True, text=True, timeout=5, check=False)
199
+ return result.stdout.strip() if result.returncode == 0 else None
200
+ except (OSError, subprocess.TimeoutExpired):
201
+ raise Failure("OWNER_CHECK_UNAVAILABLE") from None
202
+
203
+
204
+ class State:
205
+ def __init__(self, c, migrating=False):
206
+ self.c = c
207
+ self.migrating = migrating
208
+ self.root = directory(Path(c["stateDir"]).resolve())
209
+ self.file = self.root / "state.json"
210
+ self.lock = None
211
+
212
+ def __enter__(self):
213
+ lock_path = self.root / "lock"
214
+ require(not lock_path.is_symlink(), "LOCK_SYMLINK_REJECTED")
215
+ self.lock = os.fdopen(os.open(lock_path, os.O_CREAT | os.O_RDWR, 0o600), "r+")
216
+ try:
217
+ fcntl.flock(self.lock, fcntl.LOCK_EX | fcntl.LOCK_NB)
218
+ except BlockingIOError:
219
+ self.lock.close()
220
+ raise Failure("BUSY") from None
221
+ try:
222
+ expected = binding(self.c)
223
+ journal = self.root / "source-migration.json"
224
+ require(self.migrating or not journal.exists(), "SOURCE_MIGRATION_IN_PROGRESS: resume migrate-sources")
225
+ self.data = read(self.file) if self.file.exists() else {"schemaVersion": 1, "binding": expected, "days": {}, "identities": {}, "batches": {}, "excludedThreads": []}
226
+ self.original_digest = digest(self.data)
227
+ if not (self.migrating and (journal.exists() or (self.root / "work-isolation.json").exists())):
228
+ require(self.data.get("binding") == expected, "JOB_IDENTITY_OR_SOURCES_CHANGED")
229
+ except Exception:
230
+ self.lock.close()
231
+ raise
232
+ return self
233
+
234
+ def __exit__(self, kind, error, traceback):
235
+ try:
236
+ if hasattr(self, "data") and digest(self.data) != self.original_digest:
237
+ write(self.file, self.data)
238
+ finally:
239
+ self.lock.close()
240
+
241
+ def batch(self, args):
242
+ path = Path(args.batch).absolute()
243
+ require(not path.is_symlink() and path.resolve().parent == (self.root / "batches").resolve(), "INVALID_BATCH_PATH")
244
+ meta = read(path / "batch.json")
245
+ require(meta["id"] in self.data["batches"] and self.data["active"] == meta["id"], "BATCH_NOT_ACTIVE")
246
+ require(meta["runId"] == args.run_id and meta["generation"] == args.generation, "BATCH_OWNER_CHANGED")
247
+ if meta.get("identitySnapshot") is not None:
248
+ self.data["identities"] = meta["identitySnapshot"]
249
+ self.data["days"].update(meta.get("confirmations", {}))
250
+ return path, meta
251
+
252
+ def save_batch(self, path, meta):
253
+ write(path / "batch.json", meta)
254
+ self.data["batches"][meta["id"]] = {"phase": meta["phase"], "path": str(path), "days": meta["days"]}
255
+
256
+ def missing(self):
257
+ result = []
258
+ day = date(self.c["startDate"])
259
+ while day < today():
260
+ if not self.data["days"].get(day.isoformat(), {}).get("complete", False):
261
+ result.append(day.isoformat())
262
+ day += dt.timedelta(days=1)
263
+ return result
264
+
265
+
266
+ def migrate_sources(state, current_path, next_path, dry_run):
267
+ """Journal one additive browser or screen source under the existing lock."""
268
+ target = str(Path(current_path).resolve())
269
+ next_c = config(next_path)
270
+ journal_path = state.root / "source-migration.json"
271
+ completed_path = state.root / "source-migration-completed.json"
272
+ if not journal_path.exists() and state.c == next_c and completed_path.exists():
273
+ completed = read(completed_path)
274
+ require(completed["configPath"] == target and completed["newConfig"] == next_c and state.data["binding"] == completed["newState"]["binding"], "MIGRATION_TARGET_CHANGED")
275
+ return {"dryRun": dry_run, "migrated": True, "alreadyApplied": True, "affectedDays": completed["affectedDays"]}
276
+
277
+ if journal_path.exists():
278
+ journal = read(journal_path)
279
+ require(journal["configPath"] == target and journal["newConfig"] == next_c, "MIGRATION_TARGET_CHANGED")
280
+ require(state.data["binding"] in (journal["oldBinding"], journal["newState"]["binding"]) and state.c in (journal["oldConfig"], journal["newConfig"]), "MIGRATION_IDENTITY_CHANGED")
281
+ else:
282
+ added = [name for name, enabled in (("browser", browser_enabled), ("screen", screen_enabled), ("materials", materials_enabled)) if enabled(next_c) and not enabled(state.c)]
283
+ require(len(added) == 1, "ADDITIVE_SOURCE_MIGRATION_ONLY")
284
+ source = added[0]
285
+ old_base, new_base = copy.deepcopy(state.c), copy.deepcopy(next_c)
286
+ new_base["sources"].pop(source)
287
+ require(old_base == new_base, "MIGRATION_MAY_ONLY_ADD_ONE_SOURCE")
288
+ require(not state.data.get("active") and all(b["phase"] == "confirmed" for b in state.data["batches"].values()), "ACTIVE_OR_PENDING_BATCH: recover it before migration")
289
+ updated = copy.deepcopy(state.data)
290
+ updated["binding"] = binding(next_c)
291
+ effective = next_c["sources"][source]["effectiveFrom"]
292
+ affected = [d for d in updated["days"] if d >= effective]
293
+ for day in affected:
294
+ updated["days"][day]["complete"] = False
295
+ journal = {"source": source, "configPath": target, "oldConfig": state.c, "newConfig": next_c, "oldBinding": state.data["binding"], "newState": updated, "affectedDays": affected}
296
+ source = journal.get("source", "browser")
297
+ result = {"dryRun": dry_run, "effectiveFrom": next_c["sources"][source]["effectiveFrom"], "affectedDays": journal["affectedDays"], "oldBinding": journal["oldBinding"], "newBinding": journal["newState"]["binding"]}
298
+ if dry_run:
299
+ return result
300
+ if not journal_path.exists():
301
+ write(state.root / ("before-" + source + "-config.json"), journal["oldConfig"])
302
+ write(state.root / ("before-" + source + "-state.json"), state.data)
303
+ write(journal_path, journal)
304
+ write(state.file, journal["newState"])
305
+ write(target, next_c)
306
+ state.data = journal["newState"]
307
+ state.original_digest = digest(state.data)
308
+ journal_path.rename(state.root / "source-migration-completed.json")
309
+ return {**result, "migrated": True}