@mclawnet/swarm 0.1.13 → 0.1.14

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 (320) hide show
  1. package/dist/__tests__/always-on-activity-reader.test.d.ts +2 -0
  2. package/dist/__tests__/always-on-activity-reader.test.d.ts.map +1 -0
  3. package/dist/__tests__/always-on-activity-reader.test.js +193 -0
  4. package/dist/__tests__/always-on-activity-reader.test.js.map +1 -0
  5. package/dist/__tests__/always-on-config.test.d.ts +2 -0
  6. package/dist/__tests__/always-on-config.test.d.ts.map +1 -0
  7. package/dist/__tests__/always-on-config.test.js +285 -0
  8. package/dist/__tests__/always-on-config.test.js.map +1 -0
  9. package/dist/__tests__/always-on-manager.test.d.ts +2 -0
  10. package/dist/__tests__/always-on-manager.test.d.ts.map +1 -0
  11. package/dist/__tests__/always-on-manager.test.js +797 -0
  12. package/dist/__tests__/always-on-manager.test.js.map +1 -0
  13. package/dist/__tests__/always-on-parity.test.d.ts +2 -0
  14. package/dist/__tests__/always-on-parity.test.d.ts.map +1 -0
  15. package/dist/__tests__/always-on-parity.test.js +20 -0
  16. package/dist/__tests__/always-on-parity.test.js.map +1 -0
  17. package/dist/__tests__/cascade-picker.test.d.ts +2 -0
  18. package/dist/__tests__/cascade-picker.test.d.ts.map +1 -0
  19. package/dist/__tests__/cascade-picker.test.js +122 -0
  20. package/dist/__tests__/cascade-picker.test.js.map +1 -0
  21. package/dist/__tests__/coordinator-shipment.test.d.ts +2 -0
  22. package/dist/__tests__/coordinator-shipment.test.d.ts.map +1 -0
  23. package/dist/__tests__/coordinator-shipment.test.js +280 -0
  24. package/dist/__tests__/coordinator-shipment.test.js.map +1 -0
  25. package/dist/__tests__/coordinator-workspace-recover.test.d.ts +2 -0
  26. package/dist/__tests__/coordinator-workspace-recover.test.d.ts.map +1 -0
  27. package/dist/__tests__/coordinator-workspace-recover.test.js +140 -0
  28. package/dist/__tests__/coordinator-workspace-recover.test.js.map +1 -0
  29. package/dist/__tests__/coordinator-workspace.test.d.ts +2 -0
  30. package/dist/__tests__/coordinator-workspace.test.d.ts.map +1 -0
  31. package/dist/__tests__/coordinator-workspace.test.js +135 -0
  32. package/dist/__tests__/coordinator-workspace.test.js.map +1 -0
  33. package/dist/__tests__/default-runner-epipe.test.d.ts +2 -0
  34. package/dist/__tests__/default-runner-epipe.test.d.ts.map +1 -0
  35. package/dist/__tests__/default-runner-epipe.test.js +43 -0
  36. package/dist/__tests__/default-runner-epipe.test.js.map +1 -0
  37. package/dist/__tests__/discovery-scheduler.test.d.ts +2 -0
  38. package/dist/__tests__/discovery-scheduler.test.d.ts.map +1 -0
  39. package/dist/__tests__/discovery-scheduler.test.js +367 -0
  40. package/dist/__tests__/discovery-scheduler.test.js.map +1 -0
  41. package/dist/__tests__/env-forward-e2e.test.d.ts +2 -0
  42. package/dist/__tests__/env-forward-e2e.test.d.ts.map +1 -0
  43. package/dist/__tests__/env-forward-e2e.test.js +57 -0
  44. package/dist/__tests__/env-forward-e2e.test.js.map +1 -0
  45. package/dist/__tests__/gh-pr-creator.test.d.ts +2 -0
  46. package/dist/__tests__/gh-pr-creator.test.d.ts.map +1 -0
  47. package/dist/__tests__/gh-pr-creator.test.js +107 -0
  48. package/dist/__tests__/gh-pr-creator.test.js.map +1 -0
  49. package/dist/__tests__/git-worktree-provider.test.d.ts +2 -0
  50. package/dist/__tests__/git-worktree-provider.test.d.ts.map +1 -0
  51. package/dist/__tests__/git-worktree-provider.test.js +98 -0
  52. package/dist/__tests__/git-worktree-provider.test.js.map +1 -0
  53. package/dist/__tests__/gitignore-check.test.d.ts +2 -0
  54. package/dist/__tests__/gitignore-check.test.d.ts.map +1 -0
  55. package/dist/__tests__/gitignore-check.test.js +39 -0
  56. package/dist/__tests__/gitignore-check.test.js.map +1 -0
  57. package/dist/__tests__/idea-research-source.test.d.ts +2 -0
  58. package/dist/__tests__/idea-research-source.test.d.ts.map +1 -0
  59. package/dist/__tests__/idea-research-source.test.js +425 -0
  60. package/dist/__tests__/idea-research-source.test.js.map +1 -0
  61. package/dist/__tests__/idea-todo-source.test.d.ts +2 -0
  62. package/dist/__tests__/idea-todo-source.test.d.ts.map +1 -0
  63. package/dist/__tests__/idea-todo-source.test.js +258 -0
  64. package/dist/__tests__/idea-todo-source.test.js.map +1 -0
  65. package/dist/__tests__/introspection-dedupe.test.d.ts +2 -0
  66. package/dist/__tests__/introspection-dedupe.test.d.ts.map +1 -0
  67. package/dist/__tests__/introspection-dedupe.test.js +484 -0
  68. package/dist/__tests__/introspection-dedupe.test.js.map +1 -0
  69. package/dist/__tests__/introspection-source.test.d.ts +2 -0
  70. package/dist/__tests__/introspection-source.test.d.ts.map +1 -0
  71. package/dist/__tests__/introspection-source.test.js +1051 -0
  72. package/dist/__tests__/introspection-source.test.js.map +1 -0
  73. package/dist/__tests__/migration-roles.test.js +1 -22
  74. package/dist/__tests__/migration-roles.test.js.map +1 -1
  75. package/dist/__tests__/reconcile-researching.test.d.ts +2 -0
  76. package/dist/__tests__/reconcile-researching.test.d.ts.map +1 -0
  77. package/dist/__tests__/reconcile-researching.test.js +224 -0
  78. package/dist/__tests__/reconcile-researching.test.js.map +1 -0
  79. package/dist/__tests__/role-loader-preamble-all.test.js +3 -1
  80. package/dist/__tests__/role-loader-preamble-all.test.js.map +1 -1
  81. package/dist/__tests__/role-loader.test.js +95 -0
  82. package/dist/__tests__/role-loader.test.js.map +1 -1
  83. package/dist/__tests__/role-prompt-no-legacy-protocol.test.js +3 -1
  84. package/dist/__tests__/role-prompt-no-legacy-protocol.test.js.map +1 -1
  85. package/dist/__tests__/secret-scrub.test.d.ts +2 -0
  86. package/dist/__tests__/secret-scrub.test.d.ts.map +1 -0
  87. package/dist/__tests__/secret-scrub.test.js +55 -0
  88. package/dist/__tests__/secret-scrub.test.js.map +1 -0
  89. package/dist/__tests__/shipment-actions.test.d.ts +2 -0
  90. package/dist/__tests__/shipment-actions.test.d.ts.map +1 -0
  91. package/dist/__tests__/shipment-actions.test.js +378 -0
  92. package/dist/__tests__/shipment-actions.test.js.map +1 -0
  93. package/dist/__tests__/shipment-persistence.test.d.ts +2 -0
  94. package/dist/__tests__/shipment-persistence.test.d.ts.map +1 -0
  95. package/dist/__tests__/shipment-persistence.test.js +120 -0
  96. package/dist/__tests__/shipment-persistence.test.js.map +1 -0
  97. package/dist/__tests__/shipment-pipeline.test.d.ts +2 -0
  98. package/dist/__tests__/shipment-pipeline.test.d.ts.map +1 -0
  99. package/dist/__tests__/shipment-pipeline.test.js +392 -0
  100. package/dist/__tests__/shipment-pipeline.test.js.map +1 -0
  101. package/dist/__tests__/shipment-report.test.d.ts +2 -0
  102. package/dist/__tests__/shipment-report.test.d.ts.map +1 -0
  103. package/dist/__tests__/shipment-report.test.js +78 -0
  104. package/dist/__tests__/shipment-report.test.js.map +1 -0
  105. package/dist/__tests__/shipment-stdin-integration.test.d.ts +2 -0
  106. package/dist/__tests__/shipment-stdin-integration.test.d.ts.map +1 -0
  107. package/dist/__tests__/shipment-stdin-integration.test.js +49 -0
  108. package/dist/__tests__/shipment-stdin-integration.test.js.map +1 -0
  109. package/dist/__tests__/shipment-type-parity.test.d.ts +2 -0
  110. package/dist/__tests__/shipment-type-parity.test.d.ts.map +1 -0
  111. package/dist/__tests__/shipment-type-parity.test.js +10 -0
  112. package/dist/__tests__/shipment-type-parity.test.js.map +1 -0
  113. package/dist/__tests__/snapshot-copy-provider.test.d.ts +2 -0
  114. package/dist/__tests__/snapshot-copy-provider.test.d.ts.map +1 -0
  115. package/dist/__tests__/snapshot-copy-provider.test.js +88 -0
  116. package/dist/__tests__/snapshot-copy-provider.test.js.map +1 -0
  117. package/dist/__tests__/swarm-coordinator-backend.test.js +153 -0
  118. package/dist/__tests__/swarm-coordinator-backend.test.js.map +1 -1
  119. package/dist/__tests__/swarm-coordinator-complete-intercept.test.d.ts +2 -0
  120. package/dist/__tests__/swarm-coordinator-complete-intercept.test.d.ts.map +1 -0
  121. package/dist/__tests__/swarm-coordinator-complete-intercept.test.js +111 -0
  122. package/dist/__tests__/swarm-coordinator-complete-intercept.test.js.map +1 -0
  123. package/dist/__tests__/task-store-source.test.d.ts +2 -0
  124. package/dist/__tests__/task-store-source.test.d.ts.map +1 -0
  125. package/dist/__tests__/task-store-source.test.js +56 -0
  126. package/dist/__tests__/task-store-source.test.js.map +1 -0
  127. package/dist/__tests__/transport-detect.test.d.ts +2 -0
  128. package/dist/__tests__/transport-detect.test.d.ts.map +1 -0
  129. package/dist/__tests__/transport-detect.test.js +92 -0
  130. package/dist/__tests__/transport-detect.test.js.map +1 -0
  131. package/dist/__tests__/workcycle-runner-cascade.test.d.ts +2 -0
  132. package/dist/__tests__/workcycle-runner-cascade.test.d.ts.map +1 -0
  133. package/dist/__tests__/workcycle-runner-cascade.test.js +203 -0
  134. package/dist/__tests__/workcycle-runner-cascade.test.js.map +1 -0
  135. package/dist/__tests__/workcycle-runner.test.d.ts +2 -0
  136. package/dist/__tests__/workcycle-runner.test.d.ts.map +1 -0
  137. package/dist/__tests__/workcycle-runner.test.js +369 -0
  138. package/dist/__tests__/workcycle-runner.test.js.map +1 -0
  139. package/dist/__tests__/workspace-diff.test.d.ts +2 -0
  140. package/dist/__tests__/workspace-diff.test.d.ts.map +1 -0
  141. package/dist/__tests__/workspace-diff.test.js +62 -0
  142. package/dist/__tests__/workspace-diff.test.js.map +1 -0
  143. package/dist/__tests__/workspace-manager.test.d.ts +2 -0
  144. package/dist/__tests__/workspace-manager.test.d.ts.map +1 -0
  145. package/dist/__tests__/workspace-manager.test.js +120 -0
  146. package/dist/__tests__/workspace-manager.test.js.map +1 -0
  147. package/dist/__tests__/workspace-types.test.d.ts +2 -0
  148. package/dist/__tests__/workspace-types.test.d.ts.map +1 -0
  149. package/dist/__tests__/workspace-types.test.js +37 -0
  150. package/dist/__tests__/workspace-types.test.js.map +1 -0
  151. package/dist/__tests__/worktree-gc.test.d.ts +2 -0
  152. package/dist/__tests__/worktree-gc.test.d.ts.map +1 -0
  153. package/dist/__tests__/worktree-gc.test.js +183 -0
  154. package/dist/__tests__/worktree-gc.test.js.map +1 -0
  155. package/dist/always-on/activity-reader.d.ts +27 -0
  156. package/dist/always-on/activity-reader.d.ts.map +1 -0
  157. package/dist/always-on/activity-reader.js +95 -0
  158. package/dist/always-on/activity-reader.js.map +1 -0
  159. package/dist/always-on/always-on-manager.d.ts +170 -0
  160. package/dist/always-on/always-on-manager.d.ts.map +1 -0
  161. package/dist/always-on/always-on-manager.js +538 -0
  162. package/dist/always-on/always-on-manager.js.map +1 -0
  163. package/dist/always-on/config.d.ts +141 -0
  164. package/dist/always-on/config.d.ts.map +1 -0
  165. package/dist/always-on/config.js +324 -0
  166. package/dist/always-on/config.js.map +1 -0
  167. package/dist/always-on/discovery-scheduler.d.ts +60 -0
  168. package/dist/always-on/discovery-scheduler.d.ts.map +1 -0
  169. package/dist/always-on/discovery-scheduler.js +287 -0
  170. package/dist/always-on/discovery-scheduler.js.map +1 -0
  171. package/dist/always-on/ideas-client.d.ts +23 -0
  172. package/dist/always-on/ideas-client.d.ts.map +1 -0
  173. package/dist/always-on/ideas-client.js +13 -0
  174. package/dist/always-on/ideas-client.js.map +1 -0
  175. package/dist/always-on/reconcile-researching.d.ts +42 -0
  176. package/dist/always-on/reconcile-researching.d.ts.map +1 -0
  177. package/dist/always-on/reconcile-researching.js +133 -0
  178. package/dist/always-on/reconcile-researching.js.map +1 -0
  179. package/dist/always-on/task-sources/cascade-picker.d.ts +42 -0
  180. package/dist/always-on/task-sources/cascade-picker.d.ts.map +1 -0
  181. package/dist/always-on/task-sources/cascade-picker.js +65 -0
  182. package/dist/always-on/task-sources/cascade-picker.js.map +1 -0
  183. package/dist/always-on/task-sources/idea-dedupe.d.ts +62 -0
  184. package/dist/always-on/task-sources/idea-dedupe.d.ts.map +1 -0
  185. package/dist/always-on/task-sources/idea-dedupe.js +130 -0
  186. package/dist/always-on/task-sources/idea-dedupe.js.map +1 -0
  187. package/dist/always-on/task-sources/idea-research-source.d.ts +46 -0
  188. package/dist/always-on/task-sources/idea-research-source.d.ts.map +1 -0
  189. package/dist/always-on/task-sources/idea-research-source.js +308 -0
  190. package/dist/always-on/task-sources/idea-research-source.js.map +1 -0
  191. package/dist/always-on/task-sources/idea-sort.d.ts +3 -0
  192. package/dist/always-on/task-sources/idea-sort.d.ts.map +1 -0
  193. package/dist/always-on/task-sources/idea-sort.js +25 -0
  194. package/dist/always-on/task-sources/idea-sort.js.map +1 -0
  195. package/dist/always-on/task-sources/idea-todo-source.d.ts +48 -0
  196. package/dist/always-on/task-sources/idea-todo-source.d.ts.map +1 -0
  197. package/dist/always-on/task-sources/idea-todo-source.js +226 -0
  198. package/dist/always-on/task-sources/idea-todo-source.js.map +1 -0
  199. package/dist/always-on/task-sources/introspection-source.d.ts +101 -0
  200. package/dist/always-on/task-sources/introspection-source.d.ts.map +1 -0
  201. package/dist/always-on/task-sources/introspection-source.js +695 -0
  202. package/dist/always-on/task-sources/introspection-source.js.map +1 -0
  203. package/dist/always-on/task-sources/task-store-source.d.ts +15 -0
  204. package/dist/always-on/task-sources/task-store-source.d.ts.map +1 -0
  205. package/dist/always-on/task-sources/task-store-source.js +59 -0
  206. package/dist/always-on/task-sources/task-store-source.js.map +1 -0
  207. package/dist/always-on/task-sources/types.d.ts +108 -0
  208. package/dist/always-on/task-sources/types.d.ts.map +1 -0
  209. package/dist/always-on/task-sources/types.js +13 -0
  210. package/dist/always-on/task-sources/types.js.map +1 -0
  211. package/dist/always-on/types.d.ts +76 -0
  212. package/dist/always-on/types.d.ts.map +1 -0
  213. package/dist/always-on/types.js +17 -0
  214. package/dist/always-on/types.js.map +1 -0
  215. package/dist/always-on/workcycle-runner.d.ts +115 -0
  216. package/dist/always-on/workcycle-runner.d.ts.map +1 -0
  217. package/dist/always-on/workcycle-runner.js +285 -0
  218. package/dist/always-on/workcycle-runner.js.map +1 -0
  219. package/dist/always-on/worktree-gc.d.ts +41 -0
  220. package/dist/always-on/worktree-gc.d.ts.map +1 -0
  221. package/dist/always-on/worktree-gc.js +167 -0
  222. package/dist/always-on/worktree-gc.js.map +1 -0
  223. package/dist/index.d.ts +26 -2
  224. package/dist/index.d.ts.map +1 -1
  225. package/dist/index.js +24 -1
  226. package/dist/index.js.map +1 -1
  227. package/dist/persistence.d.ts +37 -1
  228. package/dist/persistence.d.ts.map +1 -1
  229. package/dist/persistence.js +48 -0
  230. package/dist/persistence.js.map +1 -1
  231. package/dist/retrospective.d.ts.map +1 -1
  232. package/dist/retrospective.js +6 -0
  233. package/dist/retrospective.js.map +1 -1
  234. package/dist/roles/role-loader.d.ts +1 -1
  235. package/dist/roles/role-loader.d.ts.map +1 -1
  236. package/dist/roles/role-loader.js +18 -0
  237. package/dist/roles/role-loader.js.map +1 -1
  238. package/dist/roles/types.d.ts +12 -0
  239. package/dist/roles/types.d.ts.map +1 -1
  240. package/dist/shipment/gh-pr-creator.d.ts +28 -0
  241. package/dist/shipment/gh-pr-creator.d.ts.map +1 -0
  242. package/dist/shipment/gh-pr-creator.js +80 -0
  243. package/dist/shipment/gh-pr-creator.js.map +1 -0
  244. package/dist/shipment/report.d.ts +27 -0
  245. package/dist/shipment/report.d.ts.map +1 -0
  246. package/dist/shipment/report.js +41 -0
  247. package/dist/shipment/report.js.map +1 -0
  248. package/dist/shipment/secret-scrub.d.ts +12 -0
  249. package/dist/shipment/secret-scrub.d.ts.map +1 -0
  250. package/dist/shipment/secret-scrub.js +30 -0
  251. package/dist/shipment/secret-scrub.js.map +1 -0
  252. package/dist/shipment/shipment-actions.d.ts +85 -0
  253. package/dist/shipment/shipment-actions.d.ts.map +1 -0
  254. package/dist/shipment/shipment-actions.js +190 -0
  255. package/dist/shipment/shipment-actions.js.map +1 -0
  256. package/dist/shipment/shipment-pipeline.d.ts +48 -0
  257. package/dist/shipment/shipment-pipeline.d.ts.map +1 -0
  258. package/dist/shipment/shipment-pipeline.js +256 -0
  259. package/dist/shipment/shipment-pipeline.js.map +1 -0
  260. package/dist/shipment/transport-detect.d.ts +16 -0
  261. package/dist/shipment/transport-detect.d.ts.map +1 -0
  262. package/dist/shipment/transport-detect.js +54 -0
  263. package/dist/shipment/transport-detect.js.map +1 -0
  264. package/dist/shipment/workspace-diff.d.ts +39 -0
  265. package/dist/shipment/workspace-diff.d.ts.map +1 -0
  266. package/dist/shipment/workspace-diff.js +64 -0
  267. package/dist/shipment/workspace-diff.js.map +1 -0
  268. package/dist/swarm-coordinator.d.ts +20 -1
  269. package/dist/swarm-coordinator.d.ts.map +1 -1
  270. package/dist/swarm-coordinator.js +193 -10
  271. package/dist/swarm-coordinator.js.map +1 -1
  272. package/dist/types.d.ts +62 -0
  273. package/dist/types.d.ts.map +1 -1
  274. package/dist/workspace/git-worktree-provider.d.ts +11 -0
  275. package/dist/workspace/git-worktree-provider.d.ts.map +1 -0
  276. package/dist/workspace/git-worktree-provider.js +123 -0
  277. package/dist/workspace/git-worktree-provider.js.map +1 -0
  278. package/dist/workspace/gitignore-check.d.ts +10 -0
  279. package/dist/workspace/gitignore-check.d.ts.map +1 -0
  280. package/dist/workspace/gitignore-check.js +25 -0
  281. package/dist/workspace/gitignore-check.js.map +1 -0
  282. package/dist/workspace/index.d.ts +5 -0
  283. package/dist/workspace/index.d.ts.map +1 -0
  284. package/dist/workspace/index.js +5 -0
  285. package/dist/workspace/index.js.map +1 -0
  286. package/dist/workspace/snapshot-copy-provider.d.ts +11 -0
  287. package/dist/workspace/snapshot-copy-provider.d.ts.map +1 -0
  288. package/dist/workspace/snapshot-copy-provider.js +66 -0
  289. package/dist/workspace/snapshot-copy-provider.js.map +1 -0
  290. package/dist/workspace/types.d.ts +36 -0
  291. package/dist/workspace/types.d.ts.map +1 -0
  292. package/dist/workspace/types.js +2 -0
  293. package/dist/workspace/types.js.map +1 -0
  294. package/dist/workspace/workspace-manager.d.ts +30 -0
  295. package/dist/workspace/workspace-manager.d.ts.map +1 -0
  296. package/dist/workspace/workspace-manager.js +104 -0
  297. package/dist/workspace/workspace-manager.js.map +1 -0
  298. package/package.json +4 -4
  299. package/roles/queen.md +1 -0
  300. package/templates/introspection.md +64 -0
  301. package/templates/research-only.md +58 -0
  302. package/roles/preset-analyst-simons.md +0 -39
  303. package/roles/preset-architect-knuth.md +0 -39
  304. package/roles/preset-designer-norman.md +0 -39
  305. package/roles/preset-designer.md +0 -39
  306. package/roles/preset-dev-carmack.md +0 -39
  307. package/roles/preset-dev-gosling.md +0 -39
  308. package/roles/preset-developer.md +0 -52
  309. package/roles/preset-manager-grove.md +0 -39
  310. package/roles/preset-manager-musk.md +0 -39
  311. package/roles/preset-pm.md +0 -78
  312. package/roles/preset-researcher-feynman.md +0 -39
  313. package/roles/preset-reviewer.md +0 -46
  314. package/roles/preset-strategist-buffett.md +0 -39
  315. package/roles/preset-strategist-munger.md +0 -39
  316. package/roles/preset-strategist-sunzi.md +0 -39
  317. package/roles/preset-tester-beck.md +0 -40
  318. package/roles/preset-tester.md +0 -47
  319. package/roles/preset-writer-orwell.md +0 -39
  320. package/roles/preset-writer.md +0 -39
@@ -0,0 +1,62 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { mkdtempSync, writeFileSync, rmSync } from "node:fs";
3
+ import { execSync } from "node:child_process";
4
+ import { tmpdir } from "node:os";
5
+ import { join } from "node:path";
6
+ import { computeWorkspaceDiff } from "../shipment/workspace-diff.js";
7
+ function setupRepoWithBase() {
8
+ const dir = mkdtempSync(join(tmpdir(), "wd-"));
9
+ execSync("git init -q && git config user.email t@t && git config user.name t", { cwd: dir, shell: "/bin/sh" });
10
+ writeFileSync(join(dir, "README.md"), "# original\n");
11
+ execSync("git add -A && git commit -q -m base", { cwd: dir, shell: "/bin/sh" });
12
+ const baseCommit = execSync("git rev-parse HEAD", { cwd: dir, shell: "/bin/sh" }).toString().trim();
13
+ return { dir, baseCommit };
14
+ }
15
+ describe("computeWorkspaceDiff", () => {
16
+ it("returns empty diff when nothing changed", async () => {
17
+ const { dir, baseCommit } = setupRepoWithBase();
18
+ const d = await computeWorkspaceDiff({ worktreeCwd: dir, baseCommit });
19
+ expect(d.files).toEqual([]);
20
+ expect(d.patch).toBe("");
21
+ expect(d.stat).toBe("");
22
+ rmSync(dir, { recursive: true, force: true });
23
+ });
24
+ it("captures stat / patch / files for committed changes", async () => {
25
+ const { dir, baseCommit } = setupRepoWithBase();
26
+ writeFileSync(join(dir, "README.md"), "# original\n# added\n");
27
+ writeFileSync(join(dir, "newfile.ts"), "export const x = 1;\n");
28
+ execSync("git add -A && git commit -q -m work", { cwd: dir, shell: "/bin/sh" });
29
+ const d = await computeWorkspaceDiff({ worktreeCwd: dir, baseCommit });
30
+ expect(d.stat).toContain("README.md");
31
+ expect(d.stat).toContain("newfile.ts");
32
+ expect(d.patch).toContain("+# added");
33
+ expect(d.patch).toContain("+export const x = 1;");
34
+ expect(d.files.length).toBe(2);
35
+ const f = d.files.find(f => f.path === "newfile.ts");
36
+ expect(f.added).toBe(1);
37
+ expect(f.removed).toBe(0);
38
+ rmSync(dir, { recursive: true, force: true });
39
+ });
40
+ it("returns uncommitted changes (workdir + index, not just HEAD)", async () => {
41
+ const { dir, baseCommit } = setupRepoWithBase();
42
+ // change only in working tree, never committed
43
+ writeFileSync(join(dir, "README.md"), "# original\n# uncommitted\n");
44
+ const d = await computeWorkspaceDiff({ worktreeCwd: dir, baseCommit });
45
+ expect(d.patch).toContain("+# uncommitted");
46
+ rmSync(dir, { recursive: true, force: true });
47
+ });
48
+ it("returns error result (not throws) on non-git dir", async () => {
49
+ const d = await computeWorkspaceDiff({ worktreeCwd: "/no/such/dir/xyz", baseCommit: "abc" });
50
+ expect(d.files).toEqual([]);
51
+ expect(d.patch).toBe("");
52
+ expect(d.error).toBeDefined();
53
+ });
54
+ it("captures removed lines correctly", async () => {
55
+ const { dir, baseCommit } = setupRepoWithBase();
56
+ writeFileSync(join(dir, "README.md"), ""); // remove the line
57
+ const d = await computeWorkspaceDiff({ worktreeCwd: dir, baseCommit });
58
+ const f = d.files.find(f => f.path === "README.md");
59
+ expect(f.removed).toBeGreaterThan(0);
60
+ });
61
+ });
62
+ //# sourceMappingURL=workspace-diff.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workspace-diff.test.js","sourceRoot":"","sources":["../../src/__tests__/workspace-diff.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=workspace-manager.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workspace-manager.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/workspace-manager.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,120 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { mkdtempSync, rmSync, writeFileSync, existsSync } from "node:fs";
3
+ import { execSync } from "node:child_process";
4
+ import { tmpdir } from "node:os";
5
+ import { join } from "node:path";
6
+ import { WorkspaceManager } from "../workspace/workspace-manager.js";
7
+ function setupCleanRepo() {
8
+ const dir = mkdtempSync(join(tmpdir(), "wm-"));
9
+ execSync("git init -q && git config user.email t@t && git config user.name t", { cwd: dir, shell: "/bin/sh" });
10
+ writeFileSync(join(dir, "f"), "x");
11
+ execSync("git add -A && git commit -q -m init", { cwd: dir, shell: "/bin/sh" });
12
+ return dir;
13
+ }
14
+ describe("WorkspaceManager", () => {
15
+ it("picks git-worktree for clean git repo", async () => {
16
+ const dir = setupCleanRepo();
17
+ const mgr = new WorkspaceManager();
18
+ const h = await mgr.prepare({ swarmId: "s1", projectRoot: dir });
19
+ expect(h.strategy).toBe("git-worktree");
20
+ await mgr.dispose("s1", { keep: false });
21
+ rmSync(dir, { recursive: true, force: true });
22
+ });
23
+ it("picks snapshot-copy for non-git dir", async () => {
24
+ const dir = mkdtempSync(join(tmpdir(), "wm-"));
25
+ writeFileSync(join(dir, "f"), "x");
26
+ const mgr = new WorkspaceManager();
27
+ const h = await mgr.prepare({ swarmId: "s1", projectRoot: dir });
28
+ expect(h.strategy).toBe("snapshot-copy");
29
+ await mgr.dispose("s1", { keep: false });
30
+ rmSync(dir, { recursive: true, force: true });
31
+ });
32
+ it("picks snapshot-copy for dirty git repo", async () => {
33
+ const dir = setupCleanRepo();
34
+ writeFileSync(join(dir, "dirty"), "x");
35
+ const mgr = new WorkspaceManager();
36
+ const h = await mgr.prepare({ swarmId: "s1", projectRoot: dir });
37
+ expect(h.strategy).toBe("snapshot-copy");
38
+ await mgr.dispose("s1", { keep: false });
39
+ rmSync(dir, { recursive: true, force: true });
40
+ });
41
+ it("dispose: unknown swarmId is a no-op", async () => {
42
+ const mgr = new WorkspaceManager();
43
+ await expect(mgr.dispose("nope", { keep: false })).resolves.not.toThrow();
44
+ });
45
+ it("get(swarmId) returns handle until disposed", async () => {
46
+ const dir = setupCleanRepo();
47
+ const mgr = new WorkspaceManager();
48
+ await mgr.prepare({ swarmId: "x", projectRoot: dir });
49
+ expect(mgr.get("x")?.strategy).toBe("git-worktree");
50
+ await mgr.dispose("x", { keep: false });
51
+ expect(mgr.get("x")).toBeUndefined();
52
+ rmSync(dir, { recursive: true, force: true });
53
+ });
54
+ it("adopt: register an existing handle so dispose can find it later", async () => {
55
+ const dir = setupCleanRepo();
56
+ const mgr1 = new WorkspaceManager();
57
+ const h = await mgr1.prepare({ swarmId: "x", projectRoot: dir });
58
+ // Simulate fresh process: new WorkspaceManager instance, no in-memory state
59
+ const mgr2 = new WorkspaceManager();
60
+ expect(mgr2.get("x")).toBeUndefined();
61
+ mgr2.adopt(h);
62
+ expect(mgr2.get("x")).toBe(h);
63
+ await mgr2.dispose("x", { keep: false });
64
+ expect(mgr2.get("x")).toBeUndefined();
65
+ rmSync(dir, { recursive: true, force: true });
66
+ });
67
+ it("prepare: rejects swarmId with path-traversal or special chars", async () => {
68
+ const dir = setupCleanRepo();
69
+ const mgr = new WorkspaceManager();
70
+ for (const bad of ["", "../escape", "foo/bar", "with space", "../../etc", "a".repeat(65), "with\0null"]) {
71
+ await expect(mgr.prepare({ swarmId: bad, projectRoot: dir })).rejects.toThrow(/[Ii]nvalid swarmId/);
72
+ }
73
+ rmSync(dir, { recursive: true, force: true });
74
+ });
75
+ it("prepare: concurrent calls with same swarmId share the in-flight promise", async () => {
76
+ const dir = setupCleanRepo();
77
+ const mgr = new WorkspaceManager();
78
+ const [a, b] = await Promise.all([
79
+ mgr.prepare({ swarmId: "concurrent", projectRoot: dir }),
80
+ mgr.prepare({ swarmId: "concurrent", projectRoot: dir }),
81
+ ]);
82
+ expect(a).toBe(b); // same handle, single underlying prepare
83
+ await mgr.dispose("concurrent", { keep: false });
84
+ rmSync(dir, { recursive: true, force: true });
85
+ });
86
+ it("gitignore advisory: writes sentinel so it doesn't re-warn next session", async () => {
87
+ // setupCleanRepo() has no .gitignore -> would warn
88
+ const dir = setupCleanRepo();
89
+ const sentinel = join(dir, ".clawnet", ".gitignore-warned");
90
+ expect(existsSync(sentinel)).toBe(false);
91
+ const mgr1 = new WorkspaceManager();
92
+ await mgr1.prepare({ swarmId: "x", projectRoot: dir });
93
+ await mgr1.dispose("x", { keep: false });
94
+ expect(existsSync(sentinel)).toBe(true);
95
+ // fresh manager (simulates process restart) — sentinel suppresses re-warn.
96
+ // We only assert the file is still there (no easy log spy across loggers).
97
+ const mgr2 = new WorkspaceManager();
98
+ await mgr2.prepare({ swarmId: "y", projectRoot: dir });
99
+ await mgr2.dispose("y", { keep: false });
100
+ expect(existsSync(sentinel)).toBe(true);
101
+ rmSync(dir, { recursive: true, force: true });
102
+ });
103
+ it("custom providers respected (e.g. inject a stub for testing)", async () => {
104
+ const dir = mkdtempSync(join(tmpdir(), "wm-"));
105
+ const stubHandle = { swarmId: "x", projectRoot: dir, strategy: "git-worktree", cwd: dir + "/x", metadata: {} };
106
+ const stub = {
107
+ id: "git-worktree",
108
+ priority: 1,
109
+ isApplicable: async () => true,
110
+ prepare: async () => stubHandle,
111
+ dispose: async () => { },
112
+ };
113
+ const mgr = new WorkspaceManager([stub]);
114
+ const h = await mgr.prepare({ swarmId: "x", projectRoot: dir });
115
+ expect(h).toBe(stubHandle);
116
+ await mgr.dispose("x", { keep: false });
117
+ rmSync(dir, { recursive: true, force: true });
118
+ });
119
+ });
120
+ //# sourceMappingURL=workspace-manager.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workspace-manager.test.js","sourceRoot":"","sources":["../../src/__tests__/workspace-manager.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=workspace-types.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workspace-types.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/workspace-types.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,37 @@
1
+ import { describe, it, expect } from "vitest";
2
+ describe("WorkspaceProvider types", () => {
3
+ it("WorkspaceHandle has the expected shape (compile-time + runtime sanity)", () => {
4
+ const h = {
5
+ swarmId: "s1",
6
+ projectRoot: "/proj",
7
+ strategy: "git-worktree",
8
+ cwd: "/proj/.worktrees/s1",
9
+ metadata: { repoRoot: "/proj", baseCommit: "abc123", branchName: "clawnet/swarm-s1" },
10
+ };
11
+ expect(h.strategy).toBe("git-worktree");
12
+ expect(h.metadata.branchName).toBe("clawnet/swarm-s1");
13
+ });
14
+ it("WorkspaceStrategy union admits both variants", () => {
15
+ const a = "git-worktree";
16
+ const b = "snapshot-copy";
17
+ expect([a, b]).toHaveLength(2);
18
+ });
19
+ it("WorkspaceProvider interface enforces the four members", () => {
20
+ // Compile-time only: this fake implementation must satisfy the interface.
21
+ const fake = {
22
+ id: "git-worktree",
23
+ priority: 1,
24
+ isApplicable: async () => true,
25
+ prepare: async (input) => ({
26
+ swarmId: input.swarmId,
27
+ projectRoot: input.projectRoot,
28
+ strategy: "git-worktree",
29
+ cwd: "/x",
30
+ metadata: {},
31
+ }),
32
+ dispose: async () => { },
33
+ };
34
+ expect(fake.id).toBe("git-worktree");
35
+ });
36
+ });
37
+ //# sourceMappingURL=workspace-types.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workspace-types.test.js","sourceRoot":"","sources":["../../src/__tests__/workspace-types.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAG9C,QAAQ,CAAC,yBAAyB,EAAE,GAAG,EAAE;IACvC,EAAE,CAAC,wEAAwE,EAAE,GAAG,EAAE;QAChF,MAAM,CAAC,GAAoB;YACzB,OAAO,EAAE,IAAI;YACb,WAAW,EAAE,OAAO;YACpB,QAAQ,EAAE,cAAc;YACxB,GAAG,EAAE,qBAAqB;YAC1B,QAAQ,EAAE,EAAE,QAAQ,EAAE,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,UAAU,EAAE,kBAAkB,EAAE;SACtF,CAAC;QACF,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;QACxC,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAC;IACzD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,8CAA8C,EAAE,GAAG,EAAE;QACtD,MAAM,CAAC,GAAsB,cAAc,CAAC;QAC5C,MAAM,CAAC,GAAsB,eAAe,CAAC;QAC7C,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;IACjC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,uDAAuD,EAAE,GAAG,EAAE;QAC/D,0EAA0E;QAC1E,MAAM,IAAI,GAAsB;YAC9B,EAAE,EAAE,cAAc;YAClB,QAAQ,EAAE,CAAC;YACX,YAAY,EAAE,KAAK,IAAI,EAAE,CAAC,IAAI;YAC9B,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC;gBACzB,OAAO,EAAE,KAAK,CAAC,OAAO;gBACtB,WAAW,EAAE,KAAK,CAAC,WAAW;gBAC9B,QAAQ,EAAE,cAAc;gBACxB,GAAG,EAAE,IAAI;gBACT,QAAQ,EAAE,EAAE;aACb,CAAC;YACF,OAAO,EAAE,KAAK,IAAI,EAAE,GAAE,CAAC;SACxB,CAAC;QACF,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;IACvC,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=worktree-gc.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"worktree-gc.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/worktree-gc.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,183 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from "vitest";
2
+ import { mkdirSync, mkdtempSync, rmSync, writeFileSync, existsSync, utimesSync, } from "node:fs";
3
+ import { tmpdir } from "node:os";
4
+ import { join } from "node:path";
5
+ import { projectRoot } from "@mclawnet/task";
6
+ import { gcWorktrees, DEFAULT_WORKTREE_GC_AGE_MS } from "../always-on/worktree-gc.js";
7
+ let WORK;
8
+ let HOME;
9
+ beforeEach(() => {
10
+ WORK = mkdtempSync(join(tmpdir(), "wgc-work-"));
11
+ HOME = mkdtempSync(join(tmpdir(), "wgc-home-"));
12
+ mkdirSync(join(WORK, ".worktrees"), { recursive: true });
13
+ });
14
+ afterEach(() => {
15
+ try {
16
+ rmSync(WORK, { recursive: true, force: true });
17
+ }
18
+ catch { /* ignore */ }
19
+ try {
20
+ rmSync(HOME, { recursive: true, force: true });
21
+ }
22
+ catch { /* ignore */ }
23
+ });
24
+ /** Create a fake worktree dir with one file; back-date its mtime by `ageMs`. */
25
+ function seedWorktree(name, ageMs, fileBytes = 100) {
26
+ const dir = join(WORK, ".worktrees", name);
27
+ mkdirSync(dir, { recursive: true });
28
+ const fp = join(dir, "fake-file.txt");
29
+ writeFileSync(fp, "x".repeat(fileBytes));
30
+ const past = new Date(Date.now() - ageMs);
31
+ utimesSync(dir, past, past);
32
+ utimesSync(fp, past, past);
33
+ return dir;
34
+ }
35
+ /** Create a swarm snapshot at the conventional path with `status`. */
36
+ function seedSnapshot(swarmId, status) {
37
+ const dir = join(projectRoot(WORK, HOME), "swarms", swarmId);
38
+ mkdirSync(dir, { recursive: true });
39
+ writeFileSync(join(dir, "recovery.json"), JSON.stringify({ id: swarmId, workDir: WORK, status }));
40
+ }
41
+ describe("gcWorktrees (PR-B Sub-PR-B4)", () => {
42
+ it("retains active swarms regardless of age", async () => {
43
+ // An old dir but its swarm is still running → must NOT delete
44
+ seedWorktree("active-old", 48 * 3600 * 1000); // 48h ago
45
+ seedSnapshot("active-old", "running");
46
+ const r = gcWorktrees({ workDir: WORK, home: HOME });
47
+ expect(r.scanned).toBe(1);
48
+ expect(r.removed).toBe(0);
49
+ expect(r.retained).toBe(1);
50
+ expect(r.decisions[0]).toMatchObject({ name: "active-old", action: "retained-active" });
51
+ expect(existsSync(join(WORK, ".worktrees", "active-old"))).toBe(true);
52
+ });
53
+ it("retains young dirs (within 24h debug window) even when swarm is terminal", async () => {
54
+ seedWorktree("young-failed", 1 * 3600 * 1000); // 1h ago
55
+ seedSnapshot("young-failed", "failed");
56
+ const r = gcWorktrees({ workDir: WORK, home: HOME });
57
+ expect(r.removed).toBe(0);
58
+ expect(r.retained).toBe(1);
59
+ expect(r.decisions[0].action).toBe("retained-young");
60
+ expect(existsSync(join(WORK, ".worktrees", "young-failed"))).toBe(true);
61
+ });
62
+ it("removes old dirs whose swarm is terminal (completed/failed/cancelled)", async () => {
63
+ seedWorktree("old-completed", 48 * 3600 * 1000);
64
+ seedSnapshot("old-completed", "completed");
65
+ const r = gcWorktrees({ workDir: WORK, home: HOME });
66
+ expect(r.removed).toBe(1);
67
+ expect(r.retained).toBe(0);
68
+ expect(r.decisions[0].action).toBe("removed");
69
+ expect(existsSync(join(WORK, ".worktrees", "old-completed"))).toBe(false);
70
+ });
71
+ it("removes old dirs with NO matching snapshot at all (snapshot already cleaned up)", async () => {
72
+ seedWorktree("orphan-old", 48 * 3600 * 1000);
73
+ // No snapshot seeded — coordinator already evicted it
74
+ const r = gcWorktrees({ workDir: WORK, home: HOME });
75
+ expect(r.removed).toBe(1);
76
+ expect(existsSync(join(WORK, ".worktrees", "orphan-old"))).toBe(false);
77
+ });
78
+ it("treats paused as ACTIVE (D14 runner will promote it next tick)", async () => {
79
+ seedWorktree("paused-old", 48 * 3600 * 1000);
80
+ seedSnapshot("paused-old", "paused");
81
+ const r = gcWorktrees({ workDir: WORK, home: HOME });
82
+ expect(r.removed).toBe(0);
83
+ expect(r.retained).toBe(1);
84
+ expect(r.decisions[0].action).toBe("retained-active");
85
+ });
86
+ it("scans + reaps multiple entries without touching worktree contents", async () => {
87
+ // PR-B follow-up — replaces the original "reports total bytes" case.
88
+ // Size telemetry was deleted because the recursive readdirSync/statSync
89
+ // walk it required pegged the agent at 99% CPU for ~15min per tick
90
+ // when worktrees contained node_modules (~70k files each). The reap
91
+ // decision only needs scanned/removed/retained — never the byte total.
92
+ seedWorktree("a-active", 1000, 200);
93
+ seedSnapshot("a-active", "running");
94
+ seedWorktree("b-removed", 48 * 3600 * 1000, 500);
95
+ seedSnapshot("b-removed", "completed");
96
+ seedWorktree("c-young", 1000, 300);
97
+ const r = gcWorktrees({ workDir: WORK, home: HOME });
98
+ expect(r.scanned).toBe(3);
99
+ expect(r.removed).toBe(1);
100
+ expect(r.retained).toBe(2);
101
+ // Size telemetry intentionally absent — re-introducing it MUST NOT
102
+ // walk worktree contents. See the doc comment at the top of
103
+ // worktree-gc.ts. Type-level + runtime double guard: ts-expect-error
104
+ // catches the regression at `tsc` time (no need to run vitest),
105
+ // toHaveProperty catches it at runtime in case someone adds the
106
+ // field via index signature / Record cast.
107
+ // @ts-expect-error — totalBytesBefore intentionally removed; see worktree-gc.ts header
108
+ void r.totalBytesBefore;
109
+ // @ts-expect-error — totalBytesAfter intentionally removed; see worktree-gc.ts header
110
+ void r.totalBytesAfter;
111
+ // @ts-expect-error — decisions[i].bytes intentionally removed; see worktree-gc.ts header
112
+ void r.decisions[0].bytes;
113
+ expect(r).not.toHaveProperty("totalBytesBefore");
114
+ expect(r).not.toHaveProperty("totalBytesAfter");
115
+ expect(r.decisions[0]).not.toHaveProperty("bytes");
116
+ });
117
+ it("dryRun=true counts but doesn't actually delete", async () => {
118
+ seedWorktree("dry-old", 48 * 3600 * 1000);
119
+ seedSnapshot("dry-old", "completed");
120
+ const r = gcWorktrees({ workDir: WORK, home: HOME, dryRun: true });
121
+ expect(r.removed).toBe(1);
122
+ expect(r.decisions[0].action).toBe("removed");
123
+ // BUT the dir still exists
124
+ expect(existsSync(join(WORK, ".worktrees", "dry-old"))).toBe(true);
125
+ });
126
+ it("no .worktrees/ dir → empty result, no error", () => {
127
+ const fresh = mkdtempSync(join(tmpdir(), "wgc-empty-"));
128
+ try {
129
+ const r = gcWorktrees({ workDir: fresh, home: HOME });
130
+ expect(r.scanned).toBe(0);
131
+ expect(r.removed).toBe(0);
132
+ expect(r.retained).toBe(0);
133
+ expect(r.decisions).toEqual([]);
134
+ }
135
+ finally {
136
+ rmSync(fresh, { recursive: true, force: true });
137
+ }
138
+ });
139
+ it("default maxAgeMs is 24h", () => {
140
+ expect(DEFAULT_WORKTREE_GC_AGE_MS).toBe(24 * 60 * 60 * 1000);
141
+ });
142
+ // PR-B follow-up regression — the original safeDirBytes walked every
143
+ // file under every worktree synchronously, including node_modules. In
144
+ // production this issued ~1M sync stat syscalls per tick, pegged the
145
+ // agent at 99% CPU for ~15min, and froze the Node event loop (silent
146
+ // log, agent_timeout from hub, InboxWatcher fs.watch never fires,
147
+ // swarm_complete_request stays delivered=false). The fix deleted the
148
+ // recursive walker entirely. This test pins that door shut: even when
149
+ // a worktree contains a fake node_modules tree, the result shape MUST
150
+ // NOT carry per-entry byte counts. Anyone re-introducing the walker
151
+ // would have to re-add the fields too, which fails the structural
152
+ // assertions below.
153
+ it("retains heavy worktree without exposing per-entry byte fields", () => {
154
+ const wtName = "fake-heavy";
155
+ const wtDir = join(WORK, ".worktrees", wtName);
156
+ mkdirSync(wtDir, { recursive: true });
157
+ seedSnapshot(wtName, "running"); // mark active → retained-active branch
158
+ // Realistic-shape fake node_modules under the worktree: 200 files
159
+ // across 20 sub-dirs. Detail doesn't matter to the assertion — it's
160
+ // here purely to fail-fast if any code re-adds a recursive walker
161
+ // that would inflate per-tick wall clock.
162
+ const nm = join(wtDir, "node_modules");
163
+ for (let i = 0; i < 20; i++) {
164
+ const pkg = join(nm, `pkg-${i}`);
165
+ mkdirSync(pkg, { recursive: true });
166
+ for (let j = 0; j < 10; j++) {
167
+ writeFileSync(join(pkg, `f-${j}.js`), "x".repeat(50));
168
+ }
169
+ }
170
+ const r = gcWorktrees({ workDir: WORK, home: HOME });
171
+ expect(r.scanned).toBe(1);
172
+ expect(r.retained).toBe(1);
173
+ expect(r.decisions[0].action).toBe("retained-active");
174
+ // The structural guard: byte counts are gone. If a future change
175
+ // re-introduces safeDirBytes (or any sync recursive walk over
176
+ // worktree contents), it'll need to add these fields back AND
177
+ // update consumers — these assertions force the conversation.
178
+ expect(r).not.toHaveProperty("totalBytesBefore");
179
+ expect(r).not.toHaveProperty("totalBytesAfter");
180
+ expect(r.decisions[0]).not.toHaveProperty("bytes");
181
+ });
182
+ });
183
+ //# sourceMappingURL=worktree-gc.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"worktree-gc.test.js","sourceRoot":"","sources":["../../src/__tests__/worktree-gc.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,27 @@
1
+ import type { AlwaysOnActivityRecord } from "@mclawnet/shared";
2
+ export interface ReadActivityOptions {
3
+ /** Clamped to [1, 500] by readAlwaysOnActivity. Default 100. */
4
+ limit?: number;
5
+ /** Default 0. */
6
+ skip?: number;
7
+ /** Optional filter on tick decision. */
8
+ decision?: "fired" | "skipped" | "halted";
9
+ }
10
+ export interface ReadActivityResult {
11
+ ticks: AlwaysOnActivityRecord[];
12
+ hasMore: boolean;
13
+ /** Total count AFTER decision filter, BEFORE pagination. */
14
+ total: number;
15
+ }
16
+ /**
17
+ * Parse the always-on.log file under <projectRoot> and return a paginated
18
+ * newest-first slice.
19
+ *
20
+ * Tolerant by design:
21
+ * - Missing file → empty result (NOT an error).
22
+ * - Blank lines → skipped.
23
+ * - JSON.parse errors → that line is skipped (NOT thrown), so a single
24
+ * corrupt line never 500s the endpoint.
25
+ */
26
+ export declare function readAlwaysOnActivity(workDir: string, home: string, opts?: ReadActivityOptions): ReadActivityResult;
27
+ //# sourceMappingURL=activity-reader.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"activity-reader.d.ts","sourceRoot":"","sources":["../../src/always-on/activity-reader.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,kBAAkB,CAAC;AAI/D,MAAM,WAAW,mBAAmB;IAClC,gEAAgE;IAChE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,iBAAiB;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,wCAAwC;IACxC,QAAQ,CAAC,EAAE,OAAO,GAAG,SAAS,GAAG,QAAQ,CAAC;CAC3C;AAED,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,sBAAsB,EAAE,CAAC;IAChC,OAAO,EAAE,OAAO,CAAC;IACjB,4DAA4D;IAC5D,KAAK,EAAE,MAAM,CAAC;CACf;AAKD;;;;;;;;;GASG;AACH,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EACZ,IAAI,GAAE,mBAAwB,GAC7B,kBAAkB,CAuCpB"}
@@ -0,0 +1,95 @@
1
+ // Always-on activity log reader (M9 t1).
2
+ //
3
+ // Pure read-path over `<projectRoot>/always-on.log` (JSONL, one tick per
4
+ // line). Written by AlwaysOnManager.appendLog; this module never touches
5
+ // the writer. Returns wire-shape records (AlwaysOnActivityRecord) so the
6
+ // Hub REST layer can pass them through verbatim.
7
+ //
8
+ // Known limitations (MVP):
9
+ // - Reads only `always-on.log`; the rotated `.log.1` backup is ignored.
10
+ // - Whole-file read + reverse: fine at ≤10MB cap; revisit if cap grows.
11
+ import { readFileSync } from "node:fs";
12
+ import { join } from "node:path";
13
+ import { projectRoot } from "@mclawnet/task";
14
+ const LOG_FILENAME = "always-on.log";
15
+ const DEFAULT_LIMIT = 100;
16
+ const MAX_LIMIT = 500;
17
+ /**
18
+ * Parse the always-on.log file under <projectRoot> and return a paginated
19
+ * newest-first slice.
20
+ *
21
+ * Tolerant by design:
22
+ * - Missing file → empty result (NOT an error).
23
+ * - Blank lines → skipped.
24
+ * - JSON.parse errors → that line is skipped (NOT thrown), so a single
25
+ * corrupt line never 500s the endpoint.
26
+ */
27
+ export function readAlwaysOnActivity(workDir, home, opts = {}) {
28
+ const limit = clampLimit(opts.limit);
29
+ const skip = Math.max(0, Math.floor(opts.skip ?? 0));
30
+ const decision = opts.decision;
31
+ const path = join(projectRoot(workDir, home), LOG_FILENAME);
32
+ let raw;
33
+ try {
34
+ raw = readFileSync(path, "utf-8");
35
+ }
36
+ catch (err) {
37
+ if (err?.code === "ENOENT") {
38
+ return { ticks: [], hasMore: false, total: 0 };
39
+ }
40
+ throw err;
41
+ }
42
+ const records = [];
43
+ for (const line of raw.split("\n")) {
44
+ const trimmed = line.trim();
45
+ if (!trimmed)
46
+ continue;
47
+ let parsed;
48
+ try {
49
+ parsed = JSON.parse(trimmed);
50
+ }
51
+ catch {
52
+ // Corrupt line — skip silently. Don't fail the whole read.
53
+ continue;
54
+ }
55
+ if (!isActivityRecord(parsed))
56
+ continue;
57
+ if (decision && parsed.decision !== decision)
58
+ continue;
59
+ records.push(parsed);
60
+ }
61
+ // Newest-first. JSONL is append-only chronological, so reversing is enough.
62
+ records.reverse();
63
+ const total = records.length;
64
+ const slice = records.slice(skip, skip + limit);
65
+ const hasMore = skip + slice.length < total;
66
+ return { ticks: slice, hasMore, total };
67
+ }
68
+ function clampLimit(input) {
69
+ if (input == null || !Number.isFinite(input))
70
+ return DEFAULT_LIMIT;
71
+ const n = Math.floor(input);
72
+ if (n < 1)
73
+ return 1;
74
+ if (n > MAX_LIMIT)
75
+ return MAX_LIMIT;
76
+ return n;
77
+ }
78
+ function isActivityRecord(value) {
79
+ if (!value || typeof value !== "object")
80
+ return false;
81
+ const obj = value;
82
+ if (typeof obj.at !== "string")
83
+ return false;
84
+ if (obj.decision !== "fired" &&
85
+ obj.decision !== "skipped" &&
86
+ obj.decision !== "halted") {
87
+ return false;
88
+ }
89
+ if (!Array.isArray(obj.gateBreakdown))
90
+ return false;
91
+ if (typeof obj.durationMs !== "number")
92
+ return false;
93
+ return true;
94
+ }
95
+ //# sourceMappingURL=activity-reader.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"activity-reader.js","sourceRoot":"","sources":["../../src/always-on/activity-reader.ts"],"names":[],"mappings":"AAAA,yCAAyC;AACzC,EAAE;AACF,yEAAyE;AACzE,yEAAyE;AACzE,yEAAyE;AACzE,iDAAiD;AACjD,EAAE;AACF,2BAA2B;AAC3B,0EAA0E;AAC1E,0EAA0E;AAE1E,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAG7C,MAAM,YAAY,GAAG,eAAe,CAAC;AAkBrC,MAAM,aAAa,GAAG,GAAG,CAAC;AAC1B,MAAM,SAAS,GAAG,GAAG,CAAC;AAEtB;;;;;;;;;GASG;AACH,MAAM,UAAU,oBAAoB,CAClC,OAAe,EACf,IAAY,EACZ,OAA4B,EAAE;IAE9B,MAAM,KAAK,GAAG,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACrC,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC;IACrD,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC;IAE/B,MAAM,IAAI,GAAG,IAAI,CAAC,WAAW,CAAC,OAAO,EAAE,IAAI,CAAC,EAAE,YAAY,CAAC,CAAC;IAC5D,IAAI,GAAW,CAAC;IAChB,IAAI,CAAC;QACH,GAAG,GAAG,YAAY,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IACpC,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAK,GAAyC,EAAE,IAAI,KAAK,QAAQ,EAAE,CAAC;YAClE,OAAO,EAAE,KAAK,EAAE,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC;QACjD,CAAC;QACD,MAAM,GAAG,CAAC;IACZ,CAAC;IAED,MAAM,OAAO,GAA6B,EAAE,CAAC;IAC7C,KAAK,MAAM,IAAI,IAAI,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;QACnC,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;QAC5B,IAAI,CAAC,OAAO;YAAE,SAAS;QACvB,IAAI,MAAe,CAAC;QACpB,IAAI,CAAC;YACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QAC/B,CAAC;QAAC,MAAM,CAAC;YACP,2DAA2D;YAC3D,SAAS;QACX,CAAC;QACD,IAAI,CAAC,gBAAgB,CAAC,MAAM,CAAC;YAAE,SAAS;QACxC,IAAI,QAAQ,IAAI,MAAM,CAAC,QAAQ,KAAK,QAAQ;YAAE,SAAS;QACvD,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACvB,CAAC;IAED,4EAA4E;IAC5E,OAAO,CAAC,OAAO,EAAE,CAAC;IAElB,MAAM,KAAK,GAAG,OAAO,CAAC,MAAM,CAAC;IAC7B,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,IAAI,EAAE,IAAI,GAAG,KAAK,CAAC,CAAC;IAChD,MAAM,OAAO,GAAG,IAAI,GAAG,KAAK,CAAC,MAAM,GAAG,KAAK,CAAC;IAC5C,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;AAC1C,CAAC;AAED,SAAS,UAAU,CAAC,KAAyB;IAC3C,IAAI,KAAK,IAAI,IAAI,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC;QAAE,OAAO,aAAa,CAAC;IACnE,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IAC5B,IAAI,CAAC,GAAG,CAAC;QAAE,OAAO,CAAC,CAAC;IACpB,IAAI,CAAC,GAAG,SAAS;QAAE,OAAO,SAAS,CAAC;IACpC,OAAO,CAAC,CAAC;AACX,CAAC;AAED,SAAS,gBAAgB,CAAC,KAAc;IACtC,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC;IACtD,MAAM,GAAG,GAAG,KAAgC,CAAC;IAC7C,IAAI,OAAO,GAAG,CAAC,EAAE,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC;IAC7C,IACE,GAAG,CAAC,QAAQ,KAAK,OAAO;QACxB,GAAG,CAAC,QAAQ,KAAK,SAAS;QAC1B,GAAG,CAAC,QAAQ,KAAK,QAAQ,EACzB,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC;IACD,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC;QAAE,OAAO,KAAK,CAAC;IACpD,IAAI,OAAO,GAAG,CAAC,UAAU,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC;IACrD,OAAO,IAAI,CAAC;AACd,CAAC"}