spidersan 0.10.0 → 0.20.2

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 (430) hide show
  1. package/CHANGELOG.md +171 -0
  2. package/README.md +39 -280
  3. package/dist/bin/command-registry.d.ts +25 -0
  4. package/dist/bin/command-registry.d.ts.map +1 -0
  5. package/dist/bin/command-registry.js +29 -0
  6. package/dist/bin/command-registry.js.map +1 -0
  7. package/dist/bin/spidersan.d.ts +7 -0
  8. package/dist/bin/spidersan.d.ts.map +1 -1
  9. package/dist/bin/spidersan.js +38 -57
  10. package/dist/bin/spidersan.js.map +1 -1
  11. package/dist/commands/cleanup.d.ts.map +1 -1
  12. package/dist/commands/cleanup.js +17 -3
  13. package/dist/commands/cleanup.js.map +1 -1
  14. package/dist/commands/config.d.ts.map +1 -1
  15. package/dist/commands/config.js +11 -39
  16. package/dist/commands/config.js.map +1 -1
  17. package/dist/commands/conflicts.d.ts +22 -0
  18. package/dist/commands/conflicts.d.ts.map +1 -1
  19. package/dist/commands/conflicts.js +459 -256
  20. package/dist/commands/conflicts.js.map +1 -1
  21. package/dist/commands/cross-conflicts.d.ts.map +1 -1
  22. package/dist/commands/cross-conflicts.js +7 -8
  23. package/dist/commands/cross-conflicts.js.map +1 -1
  24. package/dist/commands/depends.d.ts +4 -1
  25. package/dist/commands/depends.d.ts.map +1 -1
  26. package/dist/commands/depends.js +35 -8
  27. package/dist/commands/depends.js.map +1 -1
  28. package/dist/commands/doctor.d.ts.map +1 -1
  29. package/dist/commands/doctor.js +25 -7
  30. package/dist/commands/doctor.js.map +1 -1
  31. package/dist/commands/git-watch.d.ts +1 -1
  32. package/dist/commands/git-watch.js +1 -1
  33. package/dist/commands/github-sync.d.ts +33 -0
  34. package/dist/commands/github-sync.d.ts.map +1 -1
  35. package/dist/commands/github-sync.js +68 -57
  36. package/dist/commands/github-sync.js.map +1 -1
  37. package/dist/commands/index.d.ts +2 -13
  38. package/dist/commands/index.d.ts.map +1 -1
  39. package/dist/commands/index.js +5 -20
  40. package/dist/commands/index.js.map +1 -1
  41. package/dist/commands/merge-order.d.ts.map +1 -1
  42. package/dist/commands/merge-order.js +8 -4
  43. package/dist/commands/merge-order.js.map +1 -1
  44. package/dist/commands/merged.d.ts.map +1 -1
  45. package/dist/commands/merged.js +5 -1
  46. package/dist/commands/merged.js.map +1 -1
  47. package/dist/commands/pr-check.d.ts +24 -0
  48. package/dist/commands/pr-check.d.ts.map +1 -0
  49. package/dist/commands/pr-check.js +85 -0
  50. package/dist/commands/pr-check.js.map +1 -0
  51. package/dist/commands/ready-check.d.ts.map +1 -1
  52. package/dist/commands/ready-check.js +6 -3
  53. package/dist/commands/ready-check.js.map +1 -1
  54. package/dist/commands/register.d.ts +5 -0
  55. package/dist/commands/register.d.ts.map +1 -1
  56. package/dist/commands/register.js +14 -1
  57. package/dist/commands/register.js.map +1 -1
  58. package/dist/commands/registry-sync.d.ts.map +1 -1
  59. package/dist/commands/registry-sync.js +12 -46
  60. package/dist/commands/registry-sync.js.map +1 -1
  61. package/dist/commands/stale.d.ts.map +1 -1
  62. package/dist/commands/stale.js +8 -3
  63. package/dist/commands/stale.js.map +1 -1
  64. package/dist/commands/sync.d.ts +14 -1
  65. package/dist/commands/sync.d.ts.map +1 -1
  66. package/dist/commands/sync.js +49 -31
  67. package/dist/commands/sync.js.map +1 -1
  68. package/dist/commands/verify-trunk.d.ts +18 -0
  69. package/dist/commands/verify-trunk.d.ts.map +1 -0
  70. package/dist/commands/verify-trunk.js +77 -0
  71. package/dist/commands/verify-trunk.js.map +1 -0
  72. package/dist/commands/watch.d.ts +0 -1
  73. package/dist/commands/watch.d.ts.map +1 -1
  74. package/dist/commands/watch.js +50 -77
  75. package/dist/commands/watch.js.map +1 -1
  76. package/dist/index.d.ts +0 -3
  77. package/dist/index.d.ts.map +1 -1
  78. package/dist/index.js +0 -3
  79. package/dist/index.js.map +1 -1
  80. package/dist/lib/activity.d.ts.map +1 -1
  81. package/dist/lib/activity.js +6 -2
  82. package/dist/lib/activity.js.map +1 -1
  83. package/dist/lib/ai/context-builder.d.ts.map +1 -1
  84. package/dist/lib/ai/context-builder.js +19 -6
  85. package/dist/lib/ai/context-builder.js.map +1 -1
  86. package/dist/lib/ai/event-handler.d.ts.map +1 -1
  87. package/dist/lib/ai/event-handler.js +15 -2
  88. package/dist/lib/ai/event-handler.js.map +1 -1
  89. package/dist/lib/ai/reasoner.d.ts.map +1 -1
  90. package/dist/lib/ai/reasoner.js +63 -82
  91. package/dist/lib/ai/reasoner.js.map +1 -1
  92. package/dist/lib/ai/setup.d.ts +1 -1
  93. package/dist/lib/ai/setup.d.ts.map +1 -1
  94. package/dist/lib/ai/types.d.ts.map +1 -1
  95. package/dist/lib/ai/types.js +5 -2
  96. package/dist/lib/ai/types.js.map +1 -1
  97. package/dist/lib/ast.d.ts +10 -2
  98. package/dist/lib/ast.d.ts.map +1 -1
  99. package/dist/lib/ast.js +52 -27
  100. package/dist/lib/ast.js.map +1 -1
  101. package/dist/lib/carries.d.ts +22 -0
  102. package/dist/lib/carries.d.ts.map +1 -0
  103. package/dist/lib/carries.js +56 -0
  104. package/dist/lib/carries.js.map +1 -0
  105. package/dist/lib/colony-subscriber.d.ts +1 -1
  106. package/dist/lib/colony-subscriber.js +1 -1
  107. package/dist/lib/config.d.ts +2 -2
  108. package/dist/lib/config.d.ts.map +1 -1
  109. package/dist/lib/config.js +13 -8
  110. package/dist/lib/config.js.map +1 -1
  111. package/dist/lib/conflict-renderer.d.ts.map +1 -1
  112. package/dist/lib/conflict-renderer.js +17 -34
  113. package/dist/lib/conflict-renderer.js.map +1 -1
  114. package/dist/lib/ecosystem-env.d.ts +33 -0
  115. package/dist/lib/ecosystem-env.d.ts.map +1 -0
  116. package/dist/lib/ecosystem-env.js +62 -0
  117. package/dist/lib/ecosystem-env.js.map +1 -0
  118. package/dist/lib/git-events-subscriber.d.ts +27 -0
  119. package/dist/lib/git-events-subscriber.d.ts.map +1 -1
  120. package/dist/lib/git-events-subscriber.js +149 -58
  121. package/dist/lib/git-events-subscriber.js.map +1 -1
  122. package/dist/lib/git-merge-analyzer.d.ts +38 -0
  123. package/dist/lib/git-merge-analyzer.d.ts.map +1 -0
  124. package/dist/lib/git-merge-analyzer.js +23 -0
  125. package/dist/lib/git-merge-analyzer.js.map +1 -0
  126. package/dist/lib/git.d.ts +88 -0
  127. package/dist/lib/git.d.ts.map +1 -1
  128. package/dist/lib/git.js +499 -9
  129. package/dist/lib/git.js.map +1 -1
  130. package/dist/lib/github.d.ts +69 -1
  131. package/dist/lib/github.d.ts.map +1 -1
  132. package/dist/lib/github.js +230 -16
  133. package/dist/lib/github.js.map +1 -1
  134. package/dist/lib/graph.d.ts +15 -0
  135. package/dist/lib/graph.d.ts.map +1 -1
  136. package/dist/lib/graph.js +31 -0
  137. package/dist/lib/graph.js.map +1 -1
  138. package/dist/lib/machine.d.ts +14 -0
  139. package/dist/lib/machine.d.ts.map +1 -0
  140. package/dist/lib/machine.js +38 -0
  141. package/dist/lib/machine.js.map +1 -0
  142. package/dist/lib/notify.d.ts +68 -0
  143. package/dist/lib/notify.d.ts.map +1 -0
  144. package/dist/lib/notify.js +111 -0
  145. package/dist/lib/notify.js.map +1 -0
  146. package/dist/lib/outline.d.ts +6 -0
  147. package/dist/lib/outline.d.ts.map +1 -0
  148. package/dist/lib/outline.js +60 -0
  149. package/dist/lib/outline.js.map +1 -0
  150. package/dist/lib/push-registry.d.ts +22 -0
  151. package/dist/lib/push-registry.d.ts.map +1 -0
  152. package/dist/lib/push-registry.js +21 -0
  153. package/dist/lib/push-registry.js.map +1 -0
  154. package/dist/lib/reconcile.d.ts +100 -0
  155. package/dist/lib/reconcile.d.ts.map +1 -0
  156. package/dist/lib/reconcile.js +121 -0
  157. package/dist/lib/reconcile.js.map +1 -0
  158. package/dist/lib/security.d.ts +1 -6
  159. package/dist/lib/security.d.ts.map +1 -1
  160. package/dist/lib/security.js +1 -11
  161. package/dist/lib/security.js.map +1 -1
  162. package/dist/lib/semantic-analysis.d.ts +31 -0
  163. package/dist/lib/semantic-analysis.d.ts.map +1 -0
  164. package/dist/lib/semantic-analysis.js +120 -0
  165. package/dist/lib/semantic-analysis.js.map +1 -0
  166. package/dist/lib/supabase-credentials.d.ts +36 -0
  167. package/dist/lib/supabase-credentials.d.ts.map +1 -0
  168. package/dist/lib/supabase-credentials.js +43 -0
  169. package/dist/lib/supabase-credentials.js.map +1 -0
  170. package/dist/lib/trunk.d.ts +39 -0
  171. package/dist/lib/trunk.d.ts.map +1 -0
  172. package/dist/lib/trunk.js +116 -0
  173. package/dist/lib/trunk.js.map +1 -0
  174. package/dist/storage/adapter.d.ts +20 -1
  175. package/dist/storage/adapter.d.ts.map +1 -1
  176. package/dist/storage/branch-registry-store.d.ts +1 -1
  177. package/dist/storage/branch-registry-store.d.ts.map +1 -1
  178. package/dist/storage/factory.d.ts +0 -4
  179. package/dist/storage/factory.d.ts.map +1 -1
  180. package/dist/storage/factory.js +2 -26
  181. package/dist/storage/factory.js.map +1 -1
  182. package/dist/storage/json-branch-registry-store.d.ts +1 -1
  183. package/dist/storage/json-branch-registry-store.d.ts.map +1 -1
  184. package/dist/storage/json-branch-registry-store.js +33 -7
  185. package/dist/storage/json-branch-registry-store.js.map +1 -1
  186. package/dist/storage/local.d.ts +1 -1
  187. package/dist/storage/local.d.ts.map +1 -1
  188. package/dist/storage/local.js +2 -2
  189. package/dist/storage/local.js.map +1 -1
  190. package/dist/storage/memory-branch-registry-store.d.ts +1 -1
  191. package/dist/storage/memory-branch-registry-store.d.ts.map +1 -1
  192. package/dist/storage/memory-branch-registry-store.js +4 -1
  193. package/dist/storage/memory-branch-registry-store.js.map +1 -1
  194. package/dist/storage/supabase-registry-sync-client-impl.d.ts +1 -7
  195. package/dist/storage/supabase-registry-sync-client-impl.d.ts.map +1 -1
  196. package/dist/storage/supabase-registry-sync-client-impl.js +17 -61
  197. package/dist/storage/supabase-registry-sync-client-impl.js.map +1 -1
  198. package/dist/storage/supabase-registry-sync-client.d.ts +1 -1
  199. package/dist/storage/supabase-registry-sync-client.d.ts.map +1 -1
  200. package/dist/storage/supabase.d.ts +3 -9
  201. package/dist/storage/supabase.d.ts.map +1 -1
  202. package/dist/storage/supabase.js +4 -22
  203. package/dist/storage/supabase.js.map +1 -1
  204. package/dist/types/cloud.d.ts +1 -0
  205. package/dist/types/cloud.d.ts.map +1 -1
  206. package/package.json +88 -90
  207. package/dist/commands/activate.d.ts +0 -9
  208. package/dist/commands/activate.d.ts.map +0 -1
  209. package/dist/commands/activate.js +0 -45
  210. package/dist/commands/activate.js.map +0 -1
  211. package/dist/commands/active-windows.d.ts +0 -16
  212. package/dist/commands/active-windows.d.ts.map +0 -1
  213. package/dist/commands/active-windows.js +0 -373
  214. package/dist/commands/active-windows.js.map +0 -1
  215. package/dist/commands/ai.d.ts +0 -15
  216. package/dist/commands/ai.d.ts.map +0 -1
  217. package/dist/commands/ai.js +0 -498
  218. package/dist/commands/ai.js.map +0 -1
  219. package/dist/commands/audit-mark.d.ts +0 -12
  220. package/dist/commands/audit-mark.d.ts.map +0 -1
  221. package/dist/commands/audit-mark.js +0 -204
  222. package/dist/commands/audit-mark.js.map +0 -1
  223. package/dist/commands/auto.d.ts +0 -8
  224. package/dist/commands/auto.d.ts.map +0 -1
  225. package/dist/commands/auto.js +0 -207
  226. package/dist/commands/auto.js.map +0 -1
  227. package/dist/commands/bot.d.ts +0 -16
  228. package/dist/commands/bot.d.ts.map +0 -1
  229. package/dist/commands/bot.js +0 -416
  230. package/dist/commands/bot.js.map +0 -1
  231. package/dist/commands/close.d.ts +0 -9
  232. package/dist/commands/close.d.ts.map +0 -1
  233. package/dist/commands/close.js +0 -129
  234. package/dist/commands/close.js.map +0 -1
  235. package/dist/commands/collab-sync.d.ts +0 -14
  236. package/dist/commands/collab-sync.d.ts.map +0 -1
  237. package/dist/commands/collab-sync.js +0 -264
  238. package/dist/commands/collab-sync.js.map +0 -1
  239. package/dist/commands/collab.d.ts +0 -6
  240. package/dist/commands/collab.d.ts.map +0 -1
  241. package/dist/commands/collab.js +0 -103
  242. package/dist/commands/collab.js.map +0 -1
  243. package/dist/commands/completions.d.ts +0 -8
  244. package/dist/commands/completions.d.ts.map +0 -1
  245. package/dist/commands/completions.js +0 -83
  246. package/dist/commands/completions.js.map +0 -1
  247. package/dist/commands/daily.d.ts +0 -7
  248. package/dist/commands/daily.d.ts.map +0 -1
  249. package/dist/commands/daily.js +0 -171
  250. package/dist/commands/daily.js.map +0 -1
  251. package/dist/commands/dashboard.d.ts +0 -22
  252. package/dist/commands/dashboard.d.ts.map +0 -1
  253. package/dist/commands/dashboard.js +0 -401
  254. package/dist/commands/dashboard.js.map +0 -1
  255. package/dist/commands/demo.d.ts +0 -3
  256. package/dist/commands/demo.d.ts.map +0 -1
  257. package/dist/commands/demo.js +0 -118
  258. package/dist/commands/demo.js.map +0 -1
  259. package/dist/commands/ecosystem-loader.d.ts +0 -14
  260. package/dist/commands/ecosystem-loader.d.ts.map +0 -1
  261. package/dist/commands/ecosystem-loader.js +0 -90
  262. package/dist/commands/ecosystem-loader.js.map +0 -1
  263. package/dist/commands/fleet-status.d.ts +0 -14
  264. package/dist/commands/fleet-status.d.ts.map +0 -1
  265. package/dist/commands/fleet-status.js +0 -127
  266. package/dist/commands/fleet-status.js.map +0 -1
  267. package/dist/commands/inbox.d.ts +0 -3
  268. package/dist/commands/inbox.d.ts.map +0 -1
  269. package/dist/commands/inbox.js +0 -120
  270. package/dist/commands/inbox.js.map +0 -1
  271. package/dist/commands/intent-scan.d.ts +0 -17
  272. package/dist/commands/intent-scan.d.ts.map +0 -1
  273. package/dist/commands/intent-scan.js +0 -336
  274. package/dist/commands/intent-scan.js.map +0 -1
  275. package/dist/commands/key-import.d.ts +0 -3
  276. package/dist/commands/key-import.d.ts.map +0 -1
  277. package/dist/commands/key-import.js +0 -54
  278. package/dist/commands/key-import.js.map +0 -1
  279. package/dist/commands/keygen.d.ts +0 -3
  280. package/dist/commands/keygen.d.ts.map +0 -1
  281. package/dist/commands/keygen.js +0 -53
  282. package/dist/commands/keygen.js.map +0 -1
  283. package/dist/commands/keys.d.ts +0 -3
  284. package/dist/commands/keys.d.ts.map +0 -1
  285. package/dist/commands/keys.js +0 -56
  286. package/dist/commands/keys.js.map +0 -1
  287. package/dist/commands/lock.d.ts +0 -11
  288. package/dist/commands/lock.d.ts.map +0 -1
  289. package/dist/commands/lock.js +0 -257
  290. package/dist/commands/lock.js.map +0 -1
  291. package/dist/commands/mcp-health.d.ts +0 -3
  292. package/dist/commands/mcp-health.d.ts.map +0 -1
  293. package/dist/commands/mcp-health.js +0 -305
  294. package/dist/commands/mcp-health.js.map +0 -1
  295. package/dist/commands/mcp-restart.d.ts +0 -3
  296. package/dist/commands/mcp-restart.d.ts.map +0 -1
  297. package/dist/commands/mcp-restart.js +0 -176
  298. package/dist/commands/mcp-restart.js.map +0 -1
  299. package/dist/commands/monitor.d.ts +0 -3
  300. package/dist/commands/monitor.d.ts.map +0 -1
  301. package/dist/commands/monitor.js +0 -153
  302. package/dist/commands/monitor.js.map +0 -1
  303. package/dist/commands/msg-read.d.ts +0 -3
  304. package/dist/commands/msg-read.d.ts.map +0 -1
  305. package/dist/commands/msg-read.js +0 -95
  306. package/dist/commands/msg-read.js.map +0 -1
  307. package/dist/commands/pulse.d.ts +0 -13
  308. package/dist/commands/pulse.d.ts.map +0 -1
  309. package/dist/commands/pulse.js +0 -174
  310. package/dist/commands/pulse.js.map +0 -1
  311. package/dist/commands/queen.d.ts +0 -25
  312. package/dist/commands/queen.d.ts.map +0 -1
  313. package/dist/commands/queen.js +0 -326
  314. package/dist/commands/queen.js.map +0 -1
  315. package/dist/commands/radar.d.ts +0 -3
  316. package/dist/commands/radar.d.ts.map +0 -1
  317. package/dist/commands/radar.js +0 -147
  318. package/dist/commands/radar.js.map +0 -1
  319. package/dist/commands/rescue.d.ts +0 -11
  320. package/dist/commands/rescue.d.ts.map +0 -1
  321. package/dist/commands/rescue.js +0 -147
  322. package/dist/commands/rescue.js.map +0 -1
  323. package/dist/commands/semantic.d.ts +0 -3
  324. package/dist/commands/semantic.d.ts.map +0 -1
  325. package/dist/commands/semantic.js +0 -62
  326. package/dist/commands/semantic.js.map +0 -1
  327. package/dist/commands/send.d.ts +0 -3
  328. package/dist/commands/send.d.ts.map +0 -1
  329. package/dist/commands/send.js +0 -121
  330. package/dist/commands/send.js.map +0 -1
  331. package/dist/commands/status.d.ts +0 -9
  332. package/dist/commands/status.d.ts.map +0 -1
  333. package/dist/commands/status.js +0 -41
  334. package/dist/commands/status.js.map +0 -1
  335. package/dist/commands/sync-advisor.d.ts +0 -14
  336. package/dist/commands/sync-advisor.d.ts.map +0 -1
  337. package/dist/commands/sync-advisor.js +0 -110
  338. package/dist/commands/sync-advisor.js.map +0 -1
  339. package/dist/commands/tension.d.ts +0 -12
  340. package/dist/commands/tension.d.ts.map +0 -1
  341. package/dist/commands/tension.js +0 -262
  342. package/dist/commands/tension.js.map +0 -1
  343. package/dist/commands/torrent.d.ts +0 -9
  344. package/dist/commands/torrent.d.ts.map +0 -1
  345. package/dist/commands/torrent.js +0 -470
  346. package/dist/commands/torrent.js.map +0 -1
  347. package/dist/commands/tui.d.ts +0 -8
  348. package/dist/commands/tui.d.ts.map +0 -1
  349. package/dist/commands/tui.js +0 -15
  350. package/dist/commands/tui.js.map +0 -1
  351. package/dist/commands/wake.d.ts +0 -9
  352. package/dist/commands/wake.d.ts.map +0 -1
  353. package/dist/commands/wake.js +0 -188
  354. package/dist/commands/wake.js.map +0 -1
  355. package/dist/commands/welcome.d.ts +0 -8
  356. package/dist/commands/welcome.d.ts.map +0 -1
  357. package/dist/commands/welcome.js +0 -85
  358. package/dist/commands/welcome.js.map +0 -1
  359. package/dist/commands/who-owns.d.ts +0 -3
  360. package/dist/commands/who-owns.d.ts.map +0 -1
  361. package/dist/commands/who-owns.js +0 -68
  362. package/dist/commands/who-owns.js.map +0 -1
  363. package/dist/commands/who-touched.d.ts +0 -9
  364. package/dist/commands/who-touched.d.ts.map +0 -1
  365. package/dist/commands/who-touched.js +0 -198
  366. package/dist/commands/who-touched.js.map +0 -1
  367. package/dist/lib/crdt.d.ts +0 -83
  368. package/dist/lib/crdt.d.ts.map +0 -1
  369. package/dist/lib/crdt.js +0 -160
  370. package/dist/lib/crdt.js.map +0 -1
  371. package/dist/lib/daily-bridge.d.ts +0 -41
  372. package/dist/lib/daily-bridge.d.ts.map +0 -1
  373. package/dist/lib/daily-bridge.js +0 -186
  374. package/dist/lib/daily-bridge.js.map +0 -1
  375. package/dist/lib/hub.d.ts +0 -31
  376. package/dist/lib/hub.d.ts.map +0 -1
  377. package/dist/lib/hub.js +0 -92
  378. package/dist/lib/hub.js.map +0 -1
  379. package/dist/lib/license.d.ts +0 -61
  380. package/dist/lib/license.d.ts.map +0 -1
  381. package/dist/lib/license.js +0 -171
  382. package/dist/lib/license.js.map +0 -1
  383. package/dist/lib/pulse-renderer.d.ts +0 -19
  384. package/dist/lib/pulse-renderer.d.ts.map +0 -1
  385. package/dist/lib/pulse-renderer.js +0 -108
  386. package/dist/lib/pulse-renderer.js.map +0 -1
  387. package/dist/lib/repo-scanner.d.ts +0 -32
  388. package/dist/lib/repo-scanner.d.ts.map +0 -1
  389. package/dist/lib/repo-scanner.js +0 -160
  390. package/dist/lib/repo-scanner.js.map +0 -1
  391. package/dist/lib/salvage-analyzer.d.ts +0 -29
  392. package/dist/lib/salvage-analyzer.d.ts.map +0 -1
  393. package/dist/lib/salvage-analyzer.js +0 -121
  394. package/dist/lib/salvage-analyzer.js.map +0 -1
  395. package/dist/lib/session-logger.d.ts +0 -54
  396. package/dist/lib/session-logger.d.ts.map +0 -1
  397. package/dist/lib/session-logger.js +0 -136
  398. package/dist/lib/session-logger.js.map +0 -1
  399. package/dist/lib/symbol-extractor.d.ts +0 -41
  400. package/dist/lib/symbol-extractor.d.ts.map +0 -1
  401. package/dist/lib/symbol-extractor.js +0 -78
  402. package/dist/lib/symbol-extractor.js.map +0 -1
  403. package/dist/storage/git-messages.d.ts +0 -53
  404. package/dist/storage/git-messages.d.ts.map +0 -1
  405. package/dist/storage/git-messages.js +0 -376
  406. package/dist/storage/git-messages.js.map +0 -1
  407. package/dist/storage/local-messages.d.ts +0 -37
  408. package/dist/storage/local-messages.d.ts.map +0 -1
  409. package/dist/storage/local-messages.js +0 -151
  410. package/dist/storage/local-messages.js.map +0 -1
  411. package/dist/storage/message-adapter.d.ts +0 -86
  412. package/dist/storage/message-adapter.d.ts.map +0 -1
  413. package/dist/storage/message-adapter.js +0 -28
  414. package/dist/storage/message-adapter.js.map +0 -1
  415. package/dist/storage/message-factory.d.ts +0 -36
  416. package/dist/storage/message-factory.d.ts.map +0 -1
  417. package/dist/storage/message-factory.js +0 -99
  418. package/dist/storage/message-factory.js.map +0 -1
  419. package/dist/storage/mycmail-adapter.d.ts +0 -38
  420. package/dist/storage/mycmail-adapter.d.ts.map +0 -1
  421. package/dist/storage/mycmail-adapter.js +0 -300
  422. package/dist/storage/mycmail-adapter.js.map +0 -1
  423. package/dist/tui/dashboard.d.ts +0 -35
  424. package/dist/tui/dashboard.d.ts.map +0 -1
  425. package/dist/tui/dashboard.js +0 -199
  426. package/dist/tui/dashboard.js.map +0 -1
  427. package/dist/tui/screen.d.ts +0 -14
  428. package/dist/tui/screen.d.ts.map +0 -1
  429. package/dist/tui/screen.js +0 -138
  430. package/dist/tui/screen.js.map +0 -1
@@ -11,17 +11,83 @@ import { Command } from 'commander';
11
11
  import { compilePatterns } from '../lib/regex-utils.js';
12
12
  import { execFileSync, spawnSync } from 'child_process';
13
13
  import { basename } from 'path';
14
- import { homedir } from 'os';
15
14
  import { getStorage } from '../storage/index.js';
16
- import { ASTParser } from '../lib/ast.js';
17
- import { validateBranchName } from '../lib/security.js';
15
+ import { SupabaseStorage } from '../storage/supabase.js';
16
+ import { resolveSupabaseCredentials } from '../lib/supabase-credentials.js';
17
+ import { loadMachineIdentity } from '../lib/machine.js';
18
+ import { getRepoName, registryRepoNames, resolveBranchRef } from '../lib/git.js';
19
+ import { analyzeSemantic, resolveBranchTip, resolveCrossMachineRef } from '../lib/semantic-analysis.js';
20
+ import { getCLIPath } from '../lib/security.js';
18
21
  import { isExcludedPath } from './register.js';
19
22
  import { loadConfig } from '../lib/config.js';
23
+ import { ECOSYSTEM_UNCONFIGURED_HINT, resolveEcosystemRepos } from '../lib/ecosystem-env.js';
20
24
  import { logActivity } from '../lib/activity.js';
21
- import { isGhAvailable, getPRDetails } from '../lib/github.js';
25
+ import { isGhAvailable, getPRDetails, getPRLabels, listOpenPRs, getPRChangedFiles } from '../lib/github.js';
26
+ import { analyzeCarries } from '../lib/carries.js';
22
27
  import { classifyWithLabel } from '../lib/conflict-tier.js';
23
- // Config
24
- const HUB_URL = process.env.HUB_URL || 'https://hub.treebird.uk';
28
+ import { createNotifier, deliver } from '../lib/notify.js';
29
+ import { analyzeRealConflicts } from '../lib/git-merge-analyzer.js';
30
+ import { getTrunkBranch } from '../lib/trunk.js';
31
+ import { activeBranches } from '../lib/reconcile.js';
32
+ // Lazy so the transport is resolved at first use, not at import.
33
+ let _notifier = null;
34
+ function notifier() {
35
+ return (_notifier ??= createNotifier());
36
+ }
37
+ /**
38
+ * Pull other machines' registered branches as synthetic conflict targets.
39
+ *
40
+ * Why this exists: the registry is per-machine local state, and the pre-push /
41
+ * pre-merge hook calls `conflicts`. Without this, a teammate on another machine
42
+ * editing the same file was NEVER flagged — the hook advisory silently only ever
43
+ * saw local branches, giving false "you're good to merge" confidence. The data
44
+ * was already in Supabase (`registry-sync --push` writes it, `cross-conflicts`
45
+ * reads it); `conflicts` just never asked (tb-ly0b).
46
+ *
47
+ * Fail-open by design: unconfigured / offline / auth failure returns [] so the
48
+ * check degrades to local-only rather than blocking a push. Callers that need to
49
+ * distinguish "no conflicts" from "couldn't check" should use `cross-conflicts`.
50
+ *
51
+ * ponytail: one Supabase round-trip, only on the branch-overlap path (--real and
52
+ * --ecosystem return before this). Sub-second in practice and the hook is fail-open.
53
+ */
54
+ export function crossMachineBranches(views) {
55
+ const branches = [];
56
+ for (const view of views) {
57
+ for (const b of view.branches) {
58
+ // Non-active entries are another machine's finished/abandoned work,
59
+ // and a file-less entry can never overlap — both would be phantom
60
+ // conflicts, which this codebase treats as worse than a miss.
61
+ if (b.status !== 'active' || !b.files?.length)
62
+ continue;
63
+ branches.push({
64
+ ...b,
65
+ // Machine-qualified so the report distinguishes a remote branch
66
+ // from a local one of the same name (e.g. two mains).
67
+ name: `${view.machine_name}/${b.name}`,
68
+ });
69
+ }
70
+ }
71
+ return branches;
72
+ }
73
+ async function fetchCrossMachineBranches(ownBranches = []) {
74
+ try {
75
+ const creds = await resolveSupabaseCredentials();
76
+ if (!creds)
77
+ return { branches: [], degraded: false, status: 'no-credentials' };
78
+ const machine = await loadMachineIdentity();
79
+ const supabase = new SupabaseStorage(creds);
80
+ // Excluding our own machine_id matters: without it this machine's own
81
+ // pushed rows come back and conflict against themselves.
82
+ // ownBranches: a branch pushed from here can reach the registry as a pushed-branch
83
+ // row before this machine's own registry-sync lands; it must not conflict with itself.
84
+ const views = await supabase.pullRegistries(registryRepoNames(), machine.id, ownBranches);
85
+ return { branches: crossMachineBranches(views), degraded: false, status: 'checked' };
86
+ }
87
+ catch {
88
+ return { branches: [], degraded: true, status: 'degraded' };
89
+ }
90
+ }
25
91
  function getCurrentBranch() {
26
92
  try {
27
93
  return execFileSync('git', ['rev-parse', '--abbrev-ref', 'HEAD'], { encoding: 'utf-8' }).trim();
@@ -30,7 +96,7 @@ function getCurrentBranch() {
30
96
  throw new Error('Not in a git repository');
31
97
  }
32
98
  }
33
- async function notifyHub(branch, conflicts) {
99
+ async function notifyConflicts(branch, conflicts) {
34
100
  let hasTier3 = false;
35
101
  let hasTier2 = false;
36
102
  for (const c of conflicts) {
@@ -45,154 +111,26 @@ async function notifyHub(branch, conflicts) {
45
111
  return;
46
112
  const severity = hasTier3 ? 'šŸ”“ TIER 3 BLOCK' : '🟠 TIER 2 PAUSE';
47
113
  const message = `šŸ•·ļøāš ļø **Conflict Alert** on \`${branch}\`\n\n${severity}\n\nConflicting files require coordination.`;
48
- try {
49
- await fetch(`${HUB_URL}/api/chat`, {
50
- method: 'POST',
51
- headers: { 'Content-Type': 'application/json' },
52
- body: JSON.stringify({
53
- agent: 'spidersan',
54
- name: 'Spidersan',
55
- message,
56
- glyph: 'šŸ•·ļø'
57
- })
58
- });
59
- }
60
- catch {
61
- // Hub offline - silent fail
62
- }
114
+ const contested = [...new Set(conflicts.flatMap((c) => c.files))].sort();
115
+ // `deliver` inspects the result and logs the real outcome. The previous
116
+ // implementation POSTed to a decommissioned host inside a bare `catch {}`,
117
+ // so `--notify` reported nothing either way. (sp-hnjf)
118
+ await deliver(notifier(), {
119
+ repo: getRepoName(),
120
+ branch,
121
+ tier: hasTier3 ? 3 : 2,
122
+ message,
123
+ dedupe: `conflicts:${branch}:${contested.join(',')}`,
124
+ files: contested,
125
+ // stderr, not stdout: this runs before the --json blob is printed, and a
126
+ // delivery diagnostic must never end up inside machine-readable output.
127
+ }, (m) => console.error(m));
63
128
  }
64
- /**
65
- * Wake a conflicting agent and send them a message about what to fix
66
- */
67
- async function wakeConflictingAgent(agentId, myBranch, theirBranch, _conflictingFiles) {
68
- // 1. Wake the agent via Hub
69
- try {
70
- const wakeResponse = await fetch(`${HUB_URL}/api/wake/${agentId}`, {
71
- method: 'POST',
72
- headers: { 'Content-Type': 'application/json' },
73
- body: JSON.stringify({
74
- sender: 'spidersan',
75
- reason: `Conflict on ${theirBranch} - need resolution`
76
- })
77
- });
78
- if (wakeResponse.ok) {
79
- console.log(` šŸ”” Wake signal sent to ${agentId}`);
80
- }
81
- }
82
- catch {
83
- console.log(` āš ļø Could not wake ${agentId} via Hub`);
84
- }
85
- return true;
86
- }
87
- /**
88
- * Wait for a specified time
89
- */
90
- function sleep(ms) {
91
- return new Promise(resolve => setTimeout(resolve, ms));
92
- }
93
- /**
94
- * Suggest simple resolutions for add/add style conflicts where both branches added the same file
95
- * Typically occurs with docs or generated files. This prints guidance and a recommended sequence
96
- * to preserve both variants for auditability (e.g., create a .incoming copy).
97
- */
98
- function suggestAddAddResolution(conflicts) {
99
- const addAddFiles = new Set();
100
- for (const conflict of conflicts) {
101
- for (const f of conflict.files) {
102
- // Simple heuristic: docs and markdown files are common add/add candidates
103
- if (/\.md$/.test(f) || /docs\//.test(f)) {
104
- addAddFiles.add(f);
105
- }
106
- }
107
- }
108
- if (addAddFiles.size === 0)
109
- return;
110
- console.log('\nšŸ’” Add/Add Conflict Helper');
111
- console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
112
- console.log('Detected files added in both branches. Recommended actions:');
113
- console.log(' 1. Preserve incoming variant for auditability:');
114
- console.log(' git checkout --theirs <file> && mv <file> <file>.<their-branch>.incoming && git add <file>.<their-branch>.incoming');
115
- console.log(' 2. Keep canonical version in main branch and include note linking the incoming variant.');
116
- console.log(' 3. Commit and continue rebase: git add -A && git rebase --continue');
117
- console.log('\nExamples:');
118
- for (const f of addAddFiles) {
119
- console.log(` • ${f} -> preserve incoming as ${f}.incoming`);
120
- }
121
- console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n');
122
- }
123
- /**
124
- * Prompt user for Y/N confirmation (prevents Ralph Wiggum loops)
125
- * @param prompt - The question to ask
126
- * @param autoConfirm - If true, skip prompt and return true (for --auto mode)
127
- */
128
- async function confirmAction(prompt, autoConfirm = false) {
129
- if (autoConfirm) {
130
- console.log(`${prompt} [Y/n]: Y (auto)`);
131
- return true;
132
- }
133
- const readline = await import('readline');
134
- const rl = readline.createInterface({
135
- input: process.stdin,
136
- output: process.stdout
137
- });
138
- return new Promise((resolve) => {
139
- rl.question(`${prompt} [Y/n]: `, (answer) => {
140
- rl.close();
141
- const normalized = answer.toLowerCase().trim();
142
- resolve(normalized === '' || normalized === 'y' || normalized === 'yes');
143
- });
144
- });
145
- }
146
- // ── Ecosystem scan ────────────────────────────────────────────────────────────
147
- /**
148
- * Default ecosystem repos. Configurable via SPIDERSAN_ECOSYSTEM env var.
149
- * Falls back to ~/Dev/ structure (old) or ~/Dev/projects/ structure (new).
150
- */
151
- function getDefaultEcosystemRepos() {
152
- if (process.env.SPIDERSAN_ECOSYSTEM) {
153
- return process.env.SPIDERSAN_ECOSYSTEM.split(':').map((r) => r.trim()).filter(Boolean);
154
- }
155
- const homeDir = process.env.HOME || homedir();
156
- return [
157
- `${homeDir}/Dev/Envoak`,
158
- `${homeDir}/Dev/treebird-internal`,
159
- `${homeDir}/Dev/spidersan`,
160
- `${homeDir}/Dev/Toak`,
161
- `${homeDir}/Dev/flockview`,
162
- `${homeDir}/Dev/mappersan`,
163
- `${homeDir}/Dev/treebird`,
164
- `${homeDir}/Dev/treementor`,
165
- // Agent repos
166
- `${homeDir}/Dev/Artisan`,
167
- `${homeDir}/Dev/Birdsan`,
168
- `${homeDir}/Dev/Cosan`,
169
- `${homeDir}/Dev/Marksan`,
170
- `${homeDir}/Dev/Sherlocksan`,
171
- `${homeDir}/Dev/Teachersan`,
172
- `${homeDir}/Dev/Watsan`,
173
- `${homeDir}/Dev/Yosef`,
174
- // Tools & libs
175
- `${homeDir}/Dev/boidz`,
176
- `${homeDir}/Dev/beads`,
177
- `${homeDir}/Dev/birdseye`,
178
- `${homeDir}/Dev/invoak`,
179
- `${homeDir}/Dev/myceliumail`,
180
- `${homeDir}/Dev/nanoclaw`,
181
- `${homeDir}/Dev/nanoclaw-private`,
182
- `${homeDir}/Dev/proaksy`,
183
- `${homeDir}/Dev/sasu`,
184
- `${homeDir}/Dev/skills`,
185
- `${homeDir}/Dev/spidersan`,
186
- `${homeDir}/Dev/treebird-heavy`,
187
- `${homeDir}/Dev/Recovery-Tree-New`,
188
- ];
189
- }
190
- const DEFAULT_ECOSYSTEM_REPOS = getDefaultEcosystemRepos();
191
129
  function runEcosystemScan(repos, asJson) {
192
130
  const results = [];
193
131
  for (const repo of repos) {
194
132
  const name = basename(repo);
195
- const result = spawnSync('spidersan', ['conflicts', '--json'], { cwd: repo, encoding: 'utf-8', timeout: 15000 });
133
+ const result = spawnSync(process.execPath, [getCLIPath(), 'conflicts', '--json'], { cwd: repo, encoding: 'utf-8', timeout: 15000 });
196
134
  if (result.status === null || result.error) {
197
135
  results.push({ repo, name, tier3: 0, tier2: 0, tier1: 0, error: 'spawn failed' });
198
136
  continue;
@@ -253,33 +191,280 @@ function runEcosystemScan(repos, asJson) {
253
191
  console.log(`šŸ“Š Total: šŸ”“ ${totals.tier3} BLOCK | 🟠 ${totals.tier2} PAUSE | 🟔 ${totals.tier1} WARN`);
254
192
  console.log(` Repos scanned: ${results.length} | With conflicts: ${reposWithConflicts}`);
255
193
  }
194
+ // ── Real (git merge-tree) conflict scan ─────────────────────────────────────────
195
+ /**
196
+ * Render a single real-conflict report (git merge-tree backed) to the console.
197
+ * `āœ… <branch>: clean vs <base>` or `āš ļø <branch>: N real conflict(s) vs <base>`
198
+ * with the conflicting file list + kind, plus a dim `method` line. On an
199
+ * environmental `error`, prints it and returns (caller continues to the next target).
200
+ */
201
+ function renderRealReport(report) {
202
+ if (report.error) {
203
+ console.log(`āŒ ${report.branch}: ${report.error}`);
204
+ return;
205
+ }
206
+ if (report.clean) {
207
+ console.log(`āœ… ${report.branch}: clean vs ${report.base}`);
208
+ }
209
+ else {
210
+ const n = report.conflicts.length;
211
+ console.log(`āš ļø ${report.branch}: ${n} real conflict${n === 1 ? '' : 's'} vs ${report.base}`);
212
+ for (const c of report.conflicts) {
213
+ console.log(` • ${c.file} (${c.kind})`);
214
+ }
215
+ }
216
+ // Dim trailing line showing which git path produced the result.
217
+ console.log(`\x1b[2m method: ${report.method}\x1b[0m`);
218
+ }
219
+ /**
220
+ * `conflicts --real`: compute TRUE merge conflicts via git merge-tree against the
221
+ * trunk (or `--base`), for the current branch / `--branch` / (with `--all`) every
222
+ * active registered branch.
223
+ *
224
+ * Pure git for the single-target path — works with NO `.spidersan/registry.json`.
225
+ * Only `--all` (which enumerates registered branches) requires an initialized
226
+ * registry, and that path is gated here.
227
+ *
228
+ * @returns the process exit code (0 always, unless `--exit-code` + conflicts → 1).
229
+ */
230
+ /**
231
+ * The ref `--real` merges INTO: the remote-tracking trunk when it exists,
232
+ * otherwise the local branch name.
233
+ *
234
+ * A local trunk in a long-lived checkout can lag origin badly — a leftover
235
+ * worktree pinning the branch ref held one 78 commits back — and merge-tree
236
+ * against a stale base resolves cleanly where the real merge does not. That is
237
+ * a green earned by comparing against the wrong thing (sp-vifo). The `--pr` arm
238
+ * already reasoned this way (tb-57fr); this generalises it to `--branch`,
239
+ * `--all` and the current branch.
240
+ *
241
+ * Does NOT fetch: it reads refs already on disk, because this runs from a
242
+ * pre-merge hook and a check that hits the network every invocation is a
243
+ * different problem. `origin/<trunk>` can therefore be stale too — the report
244
+ * always names the base it used, so the reader can see what was compared.
245
+ */
246
+ function resolveRealBase(trunk) {
247
+ const ref = resolveBranchRef(trunk);
248
+ return ref?.startsWith('refs/remotes/') ? `origin/${trunk}` : trunk;
249
+ }
250
+ /** precondition(pr): `needs-<kind>:<resource>` labels gate a PR until they're cleared. */
251
+ const NEEDS_LABEL = /^needs-[a-z0-9]+:/i;
252
+ function preconditionLabels(labels) {
253
+ return labels.filter((l) => NEEDS_LABEL.test(l));
254
+ }
255
+ function renderGate(gate) {
256
+ if (gate.unknown) {
257
+ console.log('\nšŸ›‘ precondition UNKNOWN: could not read PR labels (gh missing/unauthenticated) — failing closed.');
258
+ }
259
+ else if (gate.unmet) {
260
+ console.log(`\nšŸ›‘ precondition UNMET: ${gate.labels.join(', ')} — resource not satisfied; clear the label to merge.`);
261
+ }
262
+ else {
263
+ console.log('\nāœ… precondition: no unmet needs-* gate.');
264
+ }
265
+ }
266
+ function renderCarries(report) {
267
+ const total = report.own.length + report.inherited.length;
268
+ console.log(`\n🧬 carries: ${total} commit(s) — ${report.own.length} own, ${report.inherited.length} inherited`);
269
+ for (const c of report.own)
270
+ console.log(` • ${c.sha.slice(0, 7)} ${c.subject} (own)`);
271
+ for (const c of report.inherited) {
272
+ console.log(` 🚩 ${c.sha.slice(0, 7)} ${c.subject} — also on ${c.branches.join(', ')}`);
273
+ }
274
+ if (report.flagged) {
275
+ console.log(' āš ļø provenance: this PR carries commits from another open branch — merging lands them too.');
276
+ }
277
+ }
278
+ async function runRealConflicts(options) {
279
+ // `|| resolveRealBase(...)` short-circuits: an explicit --base skips trunk
280
+ // detection entirely, as it always has.
281
+ let base = options.base || resolveRealBase(getTrunkBranch());
282
+ // Determine target branches.
283
+ let targets;
284
+ let prNumber;
285
+ if (options.pr) {
286
+ // --pr in real mode: the PR head isn't a local ref, so fetch it. Without
287
+ // this, --real fell through to getCurrentBranch() and silently checked
288
+ // trunk-vs-trunk → a false "clean" (tb-57fr).
289
+ prNumber = parseInt(options.pr, 10);
290
+ if (isNaN(prNumber) || prNumber <= 0) {
291
+ console.error('āŒ Invalid PR number. Provide a positive integer, e.g. --pr 42');
292
+ return 1;
293
+ }
294
+ const ref = `refs/spidersan/pr-${prNumber}`;
295
+ try {
296
+ // pull/<n>/head exists on the base repo even for fork PRs.
297
+ // Leading '+' forces the update — after a PR force-push the stashed
298
+ // ref is non-fast-forward and a plain refspec would fail forever.
299
+ execFileSync('git', ['fetch', '--quiet', 'origin', `+pull/${prNumber}/head:${ref}`], { stdio: 'pipe' });
300
+ // Check against the REMOTE trunk, not a possibly-stale local one — a PR
301
+ // merges into origin's HEAD. Falling back to local `main` (which can lag)
302
+ // is the same false-confidence class as tb-57fr.
303
+ if (!options.base) {
304
+ const trunk = getTrunkBranch();
305
+ // Unlike resolveRealBase(), this arm DOES fetch — it is already
306
+ // fetching the PR head, and a PR merges into origin's tip.
307
+ execFileSync('git', ['fetch', '--quiet', 'origin', trunk], { stdio: 'pipe' });
308
+ base = `origin/${trunk}`;
309
+ }
310
+ }
311
+ catch (err) {
312
+ const msg = err instanceof Error ? err.message : String(err);
313
+ console.error(`āŒ Could not fetch PR #${prNumber} head: ${msg}`);
314
+ return 1;
315
+ }
316
+ targets = [ref];
317
+ }
318
+ else if (options.all) {
319
+ // --all enumerates registered branches → requires an initialized registry.
320
+ const storage = await getStorage();
321
+ if (!(await storage.isInitialized())) {
322
+ console.error('āŒ Spidersan not initialized. Run: spidersan init');
323
+ console.error(' (--real --all needs the registry; --real alone does not)');
324
+ return 1;
325
+ }
326
+ // Reconcile-on-read: fold git truth over stale `active` status so a
327
+ // branch already merged into trunk isn't checked for conflicts (tb-8sa.1).
328
+ targets = activeBranches(await storage.list()).map((b) => b.name);
329
+ }
330
+ else {
331
+ targets = [options.branch || getCurrentBranch()];
332
+ }
333
+ const results = [];
334
+ for (const branch of targets) {
335
+ results.push(await analyzeRealConflicts(base, branch));
336
+ }
337
+ const anyConflicts = results.some((r) => !r.error && !r.clean);
338
+ // --pr extras: precondition() gate + carries() provenance (opt-in, compose).
339
+ let gate;
340
+ let carries;
341
+ if (prNumber !== undefined) {
342
+ const ref = `refs/spidersan/pr-${prNumber}`;
343
+ if (options.gate) {
344
+ // Fail closed: if gh is missing or the label fetch errors we cannot
345
+ // prove the gate is clear, so it stays unmet.
346
+ const labels = isGhAvailable() ? await getPRLabels(prNumber) : null;
347
+ if (labels === null) {
348
+ gate = { labels: [], unmet: true, unknown: true };
349
+ }
350
+ else {
351
+ const needs = preconditionLabels(labels);
352
+ gate = { labels: needs, unmet: needs.length > 0 };
353
+ }
354
+ }
355
+ if (options.carries) {
356
+ const self = [base]; // exclude base + the PR's own remote head from "other branch"
357
+ try {
358
+ self.push(`origin/${(await getPRDetails(prNumber)).headBranch}`);
359
+ }
360
+ catch { /* best-effort self-exclusion */ }
361
+ carries = analyzeCarries(base, ref, self);
362
+ }
363
+ }
364
+ if (options.json) {
365
+ console.log(JSON.stringify({ base, results, carries, gate }, null, 2));
366
+ }
367
+ else {
368
+ if (results.length === 0) {
369
+ console.log(`šŸ•·ļø No active branches to check vs ${base}.`);
370
+ }
371
+ for (const report of results) {
372
+ renderRealReport(report);
373
+ }
374
+ if (gate)
375
+ renderGate(gate);
376
+ if (carries)
377
+ renderCarries(carries);
378
+ }
379
+ // --strict is the advertised CI flag on the legacy path; honour it here too
380
+ // instead of silently ignoring it (real conflicts are always tier-blocking).
381
+ return (options.exitCode || options.strict) && (anyConflicts || gate?.unmet === true) ? 1 : 0;
382
+ }
256
383
  export const conflictsCommand = new Command('conflicts')
257
384
  .description('Detect file conflicts between branches (with tiered blocking)')
258
385
  .option('--branch <name>', 'Check conflicts for specific branch')
259
386
  .option('--pr <number>', 'Check conflicts for a GitHub pull request by number')
387
+ .option('--vs-prs', 'Also detect conflicts against other open PRs (not just registered branches); composes with --real')
260
388
  .option('--json', 'Output as JSON')
261
389
  .option('--tier <level>', 'Filter by minimum tier (1, 2, or 3)', '1')
262
390
  .option('--strict', 'Strict mode: exit with error if TIER 2+ conflicts found')
263
- .option('--notify', 'Notify Hub of TIER 2+ conflicts')
264
- .option('--wake', 'Wake conflicting agents and send them fix instructions')
265
- .option('--retry <seconds>', 'After waking, wait N seconds and re-check conflicts')
266
- .option('--auto', 'Auto mode: skip confirmations (enables Ralph Wiggum loop)')
267
- .option('--max-retries <count>', 'Maximum retry attempts in auto mode (default: 5)', '5')
391
+ .option('--notify', 'Deliver TIER 2+ conflicts to the configured alert room (SPIDERSAN_ROOM_TOKEN)')
268
392
  .option('--semantic', 'Use semantic (AST) analysis for symbol-level conflict detection')
269
393
  .option('--ecosystem', 'Scan all ecosystem repos and aggregate conflict tiers')
270
394
  .option('--repos <paths>', 'Comma-separated repo paths for --ecosystem scan')
395
+ .option('--real', 'Compute TRUE merge conflicts via git merge-tree (vs registry overlap)')
396
+ .option('--base <ref>', 'Base/trunk ref to merge into for --real (default: detected trunk)')
397
+ .option('--all', 'With --real: check every active registered branch')
398
+ .option('--exit-code', 'With --real: exit 1 if any real conflicts found (like git diff)')
399
+ .option('--carries', 'With --pr: show carries() provenance (own vs inherited commits)')
400
+ .option('--gate', 'With --pr: check precondition() needs-* labels; --exit-code blocks if unmet')
271
401
  .action(async (options) => {
402
+ // ── Real (git merge-tree) conflicts ──────────────────────────────────
403
+ // Runs BEFORE the storage-init guard: a single-target real check is pure
404
+ // git and must work with no .spidersan/registry.json. Only --real --all
405
+ // touches the registry, and runRealConflicts gates that path itself.
406
+ //
407
+ // --pr defaults to the real merge-tree path (tb-8hgg): "will this PR merge
408
+ // into trunk" is the common question and file-overlap false-positives it
409
+ // (see #80/identity.ts). Opt into the legacy file-overlap check with --vs-prs.
410
+ //
411
+ // --real --vs-prs COMPOSES: it runs the merge-tree check vs trunk AND then
412
+ // falls through to the cross-PR/overlap pass, because the two answer
413
+ // different questions ("does this merge?" vs "is anyone else touching these
414
+ // files?") and /spidersan documents `conflicts --pr <N> --real --vs-prs` as
415
+ // THE authoritative pre-merge check. Before sp-a1l3 the `options.real` arm
416
+ // returned here, so that documented command silently dropped the cross-PR
417
+ // half and exited 0 looking healthy — a check reporting success while doing
418
+ // nothing. Exit codes are composed at the end via finish(): real conflicts
419
+ // are tier-blocking, overlap is advisory (only --strict makes it block), so
420
+ // the process code is max(real, overlap).
421
+ const composeRealAndOverlap = !!(options.real && options.vsPrs);
422
+ let realExitCode = 0;
423
+ if (options.real || (options.pr && !options.vsPrs)) {
424
+ realExitCode = await runRealConflicts(options);
425
+ if (!composeRealAndOverlap) {
426
+ if (realExitCode !== 0)
427
+ process.exit(realExitCode);
428
+ return;
429
+ }
430
+ console.log(''); // separate the two reports
431
+ }
432
+ /**
433
+ * Terminate the overlap pass, folding in the real-conflict verdict.
434
+ * On the composed path a non-zero code from EITHER half must survive;
435
+ * on the plain overlap path realExitCode is 0, so behaviour is unchanged.
436
+ */
437
+ const finish = (overlapCode) => {
438
+ const code = Math.max(realExitCode, overlapCode);
439
+ if (code !== 0)
440
+ process.exit(code);
441
+ };
272
442
  // ── Ecosystem shortcut ───────────────────────────────────────────────
273
443
  if (options.ecosystem) {
274
- const repos = options.repos
275
- ? options.repos.split(',').map((r) => r.trim()).filter(Boolean)
276
- : DEFAULT_ECOSYSTEM_REPOS;
444
+ // No built-in repo list ships with the package: the list comes from
445
+ // --repos, SPIDERSAN_ECOSYSTEM (path list) or conflicts.ecosystemRepos.
446
+ const { repos } = resolveEcosystemRepos(await loadConfig(), {
447
+ reposFlag: options.repos,
448
+ });
449
+ if (repos.length === 0) {
450
+ console.error(`ā„¹ļø ${ECOSYSTEM_UNCONFIGURED_HINT}`);
451
+ }
277
452
  runEcosystemScan(repos, !!options.json);
453
+ finish(0);
278
454
  return;
279
455
  }
280
456
  const storage = await getStorage();
281
457
  const config = await loadConfig();
282
458
  if (!await storage.isInitialized()) {
459
+ // --real is deliberately registry-independent; adding --vs-prs must not
460
+ // turn a working real check into a hard failure. Degrade loudly instead
461
+ // of dying — but never silently (sp-a1l3).
462
+ if (composeRealAndOverlap) {
463
+ console.error('āš ļø Overlap/cross-PR check skipped: spidersan not initialized here (run: spidersan init).');
464
+ console.error(' The --real merge-tree result above stands on its own; the --vs-prs half did NOT run.');
465
+ finish(0);
466
+ return;
467
+ }
283
468
  console.error('āŒ Spidersan not initialized. Run: spidersan init');
284
469
  process.exit(1);
285
470
  }
@@ -291,6 +476,8 @@ export const conflictsCommand = new Command('conflicts')
291
476
  const compiledMedium = compilePatterns(mediumSeverity);
292
477
  let targetBranch;
293
478
  let targetFiles;
479
+ let targetPrNumber;
480
+ const prNumberByLabel = new Map(); // display label -> PR number (labels are NOT git refs)
294
481
  if (options.pr) {
295
482
  // --pr mode: fetch PR head branch + changed files from GitHub
296
483
  const prNumber = parseInt(options.pr, 10);
@@ -314,6 +501,7 @@ export const conflictsCommand = new Command('conflicts')
314
501
  }
315
502
  targetBranch = prDetails.headBranch;
316
503
  targetFiles = prDetails.files;
504
+ targetPrNumber = prNumber;
317
505
  console.log(`šŸ•·ļø Checking conflicts for PR #${prNumber}: "${prDetails.title}"`);
318
506
  console.log(` Branch: ${targetBranch} (${targetFiles.length} file(s) changed)`);
319
507
  }
@@ -322,13 +510,57 @@ export const conflictsCommand = new Command('conflicts')
322
510
  targetBranch = options.branch || getCurrentBranch();
323
511
  const target = await storage.get(targetBranch);
324
512
  if (!target) {
513
+ // Same reasoning as the isInitialized() guard above: on the composed
514
+ // --real --vs-prs path the real check already produced a valid answer,
515
+ // so an unregistered branch degrades the overlap half loudly rather
516
+ // than failing the whole command (sp-a1l3).
517
+ if (composeRealAndOverlap) {
518
+ console.error(`āš ļø Overlap/cross-PR check skipped: branch "${targetBranch}" is not registered (run: spidersan register --files "...").`);
519
+ console.error(' The --real merge-tree result above stands on its own; the --vs-prs half did NOT run.');
520
+ finish(0);
521
+ return;
522
+ }
325
523
  console.error(`āŒ Branch "${targetBranch}" is not registered.`);
326
524
  console.error(' Run: spidersan register --files "..."');
327
525
  process.exit(1);
328
526
  }
329
527
  targetFiles = target.files;
330
528
  }
331
- const allBranches = await storage.list();
529
+ // Reconcile-on-read: branches already merged into trunk drop out before
530
+ // overlap detection, so a stale `active` entry can't phantom-conflict (tb-8sa.1).
531
+ const allBranches = activeBranches(await storage.list());
532
+ // --vs-prs: pull other open PRs as conflict targets too, so two simultaneous
533
+ // PRs touching the same files flag each other even when neither branch is in
534
+ // the local registry. PRs become synthetic active "branches" (no agent → wake
535
+ // skips them). Self (same headBranch) is excluded.
536
+ // ponytail: 1 + N gh calls (list + per-PR diff); fine for typical open-PR counts,
537
+ // parallelise if a repo ever carries hundreds of open PRs.
538
+ const prHeadBranches = [];
539
+ if (options.vsPrs) {
540
+ if (!isGhAvailable()) {
541
+ console.error('āš ļø --vs-prs needs gh (unavailable/unauthenticated); checking registered branches only.');
542
+ }
543
+ else {
544
+ const openPRs = (await listOpenPRs()).filter(pr => pr.headBranch !== targetBranch);
545
+ prHeadBranches.push(...openPRs.map(pr => pr.headBranch));
546
+ openPRs.forEach(pr => prNumberByLabel.set(`PR #${pr.number} (${pr.headBranch})`, pr.number));
547
+ const prBranches = await Promise.all(openPRs.map(async (pr) => ({
548
+ name: `PR #${pr.number} (${pr.headBranch})`,
549
+ files: await getPRChangedFiles(pr.number),
550
+ registeredAt: new Date(),
551
+ status: 'active',
552
+ description: pr.title,
553
+ })));
554
+ allBranches.push(...prBranches.filter(b => b.files.length > 0));
555
+ }
556
+ }
557
+ // Cross-machine: other machines' registered branches are conflict targets
558
+ // too. Automatic (not opt-in) so the pre-push/pre-merge hook — which calls
559
+ // plain `conflicts` — actually catches a teammate on another machine
560
+ // touching the same file. Fail-open: degraded => local-only (tb-ly0b).
561
+ const cross = await fetchCrossMachineBranches([targetBranch, ...allBranches.map(b => b.name), ...prHeadBranches]);
562
+ allBranches.push(...cross.branches);
563
+ const crossMachineLabels = new Set(cross.branches.map(b => b.name)); // origin-only refs for these
332
564
  const conflicts = [];
333
565
  // Performance Optimization: Convert target files to Set for O(1) lookup
334
566
  // Reduces complexity from O(N*M*K) to O(N*M) where N=branches, M=files/branch, K=target_files
@@ -363,33 +595,52 @@ export const conflictsCommand = new Command('conflicts')
363
595
  branch: branch.name,
364
596
  files: overlappingFiles,
365
597
  tier: maxTier.tier,
366
- tierInfo: maxTier
598
+ tierInfo: maxTier,
599
+ ...(branch.sessionId ? { sessionId: branch.sessionId } : {}),
367
600
  });
368
601
  }
369
602
  }
370
603
  }
371
604
  // SEMANTIC ANALYSIS with AST parser
372
- const semanticConflicts = [];
605
+ let semanticConflicts = [];
606
+ let semanticSkipped = []; // files we could not analyze — "no conflicts" is unproven if non-empty
607
+ let semanticUnsupported = []; // overlapping files of a type --semantic doesn't cover
373
608
  if (options.semantic && conflicts.length > 0) {
374
609
  console.log('\nšŸ”¬ Running semantic (AST) analysis...');
375
- const astParser = new ASTParser();
376
- for (const conflict of conflicts) {
377
- for (const file of conflict.files) {
378
- // Only analyze TypeScript/JavaScript files
379
- if (!/\.(ts|js|tsx|jsx)$/.test(file))
380
- continue;
381
- try {
382
- // Get file content from both branches
383
- // Security: Use execFileSync to prevent command injection via file names
384
- const currentContent = execFileSync('git', ['show', `HEAD:${file}`], { encoding: 'utf-8' });
385
- const otherContent = execFileSync('git', ['show', `${conflict.branch}:${file}`], { encoding: 'utf-8' });
386
- const symbolConflicts = astParser.findSymbolConflicts(currentContent, `${targetBranch}:${file}`, otherContent, `${conflict.branch}:${file}`);
387
- semanticConflicts.push(...symbolConflicts);
388
- }
389
- catch {
390
- // File might not exist in one branch, skip
391
- }
610
+ const fetchPrRef = (n) => {
611
+ const ref = `refs/spidersan/pr-${n}`;
612
+ try {
613
+ // '+' forces the update after a PR force-push (same as the --real arm)
614
+ execFileSync('git', ['fetch', '--quiet', 'origin', `+pull/${n}/head:${ref}`], { stdio: 'pipe', timeout: 30_000 }); // bounded: a stalled origin must not hang the command
615
+ return ref;
616
+ }
617
+ catch {
618
+ return null;
392
619
  }
620
+ };
621
+ // Labels ("PR #N (branch)", "machine/branch") are display text, not refs:
622
+ // resolve each to a real ref, or null (→ reported as skipped, never swallowed).
623
+ const refForLabel = (label) => {
624
+ const n = prNumberByLabel.get(label);
625
+ if (n !== undefined)
626
+ return fetchPrRef(n);
627
+ // another machine's branch resolves only against origin; a registered local branch is local-first
628
+ return crossMachineLabels.has(label) ? resolveCrossMachineRef(label) : resolveBranchTip(label);
629
+ };
630
+ // Current side: the PR's own head under --pr (HEAD is just whatever is checked out),
631
+ // else the target branch (HEAD when it is the checked-out one).
632
+ const currentRef = targetPrNumber !== undefined
633
+ ? fetchPrRef(targetPrNumber)
634
+ : targetBranch === getCurrentBranch() ? 'HEAD' : resolveBranchTip(targetBranch);
635
+ const res = analyzeSemantic(targetBranch, currentRef, conflicts.map(c => ({ label: c.branch, files: c.files, ref: refForLabel(c.branch) })));
636
+ semanticConflicts = res.conflicts;
637
+ semanticSkipped = res.skipped;
638
+ semanticUnsupported = res.unsupported;
639
+ for (const line of semanticSkipped)
640
+ console.log(` āš ļø ${line}`);
641
+ if (semanticUnsupported.length > 0) {
642
+ const shown = semanticUnsupported.slice(0, 5).join(', ') + (semanticUnsupported.length > 5 ? `, +${semanticUnsupported.length - 5} more` : '');
643
+ console.log(` ā„¹ļø not analyzable by --semantic (unsupported file type): ${shown}`);
393
644
  }
394
645
  }
395
646
  // Sort by tier (highest first)
@@ -420,14 +671,15 @@ export const conflictsCommand = new Command('conflicts')
420
671
  }
421
672
  // Check for blocking conditions
422
673
  const shouldBlock = options.strict && (tier3Count > 0 || tier2Count > 0);
423
- // Notify Hub if requested
674
+ // Deliver the alert if requested
424
675
  if (options.notify && (tier3Count > 0 || tier2Count > 0)) {
425
- await notifyHub(targetBranch, conflicts);
676
+ await notifyConflicts(targetBranch, conflicts);
426
677
  }
427
678
  if (options.json) {
428
679
  console.log(JSON.stringify({
429
680
  branch: targetBranch,
430
681
  conflicts,
682
+ crossMachine: cross.status,
431
683
  summary: {
432
684
  tier3: tier3Count,
433
685
  tier2: tier2Count,
@@ -435,13 +687,28 @@ export const conflictsCommand = new Command('conflicts')
435
687
  blocked: shouldBlock
436
688
  }
437
689
  }, null, 2));
438
- if (shouldBlock)
439
- process.exit(1);
690
+ finish(shouldBlock ? 1 : 0);
440
691
  return;
441
692
  }
442
693
  if (conflicts.length === 0) {
443
- console.log(`šŸ•·ļø No conflicts detected for "${targetBranch}"`);
444
- console.log(' āœ… You\'re good to merge!');
694
+ // Say what was CHECKED, not what we wish it proved. This path compares
695
+ // registered file lists; it never runs a merge. It is structurally blind
696
+ // to add/add and content conflicts against the base — so "no overlap"
697
+ // is not "merges cleanly", and claiming the latter is the lie this
698
+ // wording removes (PATTERN_silent_success, instance #5).
699
+ const compared = allBranches.filter((b) => b.name !== targetBranch && b.status === 'active').length;
700
+ console.log(`šŸ•·ļø No registry overlap for "${targetBranch}" (vs ${compared} registered branch${compared === 1 ? '' : 'es'})`);
701
+ console.log(' ā„¹ļø Registry-only — NOT a merge simulation. For merge readiness: spidersan conflicts --real');
702
+ // Never let a failed cross-machine check read as an all-clear — that
703
+ // silent false confidence is the whole bug this path fixes (tb-ly0b).
704
+ if (cross.degraded) {
705
+ console.error(' āš ļø Cross-machine check unavailable — LOCAL branches only.');
706
+ }
707
+ else if (cross.status === 'no-credentials') {
708
+ // Same lie, quieter: no creds in this shell reads exactly like a clean cloud check.
709
+ console.log(' ā„¹ļø Cloud registry not checked (no Supabase credentials) — LOCAL branches only.');
710
+ }
711
+ finish(0);
445
712
  return;
446
713
  }
447
714
  console.log(`
@@ -449,7 +716,7 @@ export const conflictsCommand = new Command('conflicts')
449
716
  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
450
717
  `);
451
718
  for (const conflict of conflicts) {
452
- console.log(`${conflict.tierInfo.icon} TIER ${conflict.tier} (${conflict.tierInfo.label}): ${conflict.branch}`);
719
+ console.log(`${conflict.tierInfo.icon} TIER ${conflict.tier} (${conflict.tierInfo.label}): ${conflict.branch}${conflict.sessionId ? ` (session ${conflict.sessionId})` : ''}`);
453
720
  for (const file of conflict.files) {
454
721
  const fileTier = classifyWithLabel(file, {
455
722
  extraTier3: compiledHigh,
@@ -462,8 +729,6 @@ export const conflictsCommand = new Command('conflicts')
462
729
  }
463
730
  console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
464
731
  console.log(`šŸ“Š Summary: šŸ”“ ${tier3Count} BLOCK | 🟠 ${tier2Count} PAUSE | 🟔 ${tier1Count} WARN`);
465
- // Offer add/add resolution suggestions for likely add/add files
466
- suggestAddAddResolution(conflicts);
467
732
  // Show semantic analysis results
468
733
  if (options.semantic && semanticConflicts.length > 0) {
469
734
  console.log(`\nšŸ”¬ SEMANTIC CONFLICTS DETECTED (${semanticConflicts.length} symbols):`);
@@ -475,6 +740,12 @@ export const conflictsCommand = new Command('conflicts')
475
740
  console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
476
741
  console.log('\nšŸ’” TIP: Coordinate on these specific functions/classes,');
477
742
  console.log(' not just the files. One of you should rebase.');
743
+ if (semanticSkipped.length + semanticUnsupported.length > 0) {
744
+ console.log(` āš ļø incomplete: ${semanticSkipped.length + semanticUnsupported.length} file(s) could not be analyzed, so more may exist.`);
745
+ }
746
+ }
747
+ else if (options.semantic && semanticSkipped.length + semanticUnsupported.length > 0) {
748
+ console.log(`\nšŸ”¬ SEMANTIC ANALYSIS: incomplete — ${semanticSkipped.length + semanticUnsupported.length} file(s) could not be analyzed; no verdict.`);
478
749
  }
479
750
  else if (options.semantic && semanticConflicts.length === 0) {
480
751
  console.log('\nšŸ”¬ SEMANTIC ANALYSIS: No symbol-level conflicts!');
@@ -494,77 +765,9 @@ export const conflictsCommand = new Command('conflicts')
494
765
  Coordinate with the other agent before proceeding.
495
766
  `);
496
767
  }
497
- // Wake conflicting agents if --wake flag is set
498
- if (options.wake && conflicts.length > 0) {
499
- // Collect agents to wake
500
- const agentsToWake = new Map();
501
- // Performance Optimization: Use the already fetched allBranches
502
- // to avoid N+1 slow network requests to storage.get()
503
- const branchMap = new Map(allBranches.map(b => [b.name, b]));
504
- for (const conflict of conflicts) {
505
- const conflictBranch = branchMap.get(conflict.branch);
506
- const agentId = conflictBranch?.agent;
507
- if (agentId && !agentsToWake.has(agentId)) {
508
- agentsToWake.set(agentId, { branch: conflict.branch, files: conflict.files });
509
- }
510
- }
511
- if (agentsToWake.size === 0) {
512
- console.log('\nāš ļø No agents registered on conflicting branches.');
513
- }
514
- else {
515
- // Show what will happen and ask for confirmation
516
- console.log('\nšŸ”” WAKE AGENTS?');
517
- console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
518
- for (const [agent, info] of agentsToWake) {
519
- console.log(` • ${agent} (${info.branch})`);
520
- }
521
- console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
522
- console.log('This will:');
523
- console.log(' 1. Send wake signal via Hub\n');
524
- const confirmed = await confirmAction('Wake these agents?', options.auto);
525
- if (confirmed) {
526
- console.log('\nšŸ”” WAKING AGENTS...\n');
527
- for (const [agentId, info] of agentsToWake) {
528
- await wakeConflictingAgent(agentId, targetBranch, info.branch, info.files);
529
- }
530
- console.log(`\nāœ… Woke ${agentsToWake.size} agent(s)`);
531
- // If --retry is set, ask before waiting
532
- if (options.retry) {
533
- const waitSeconds = parseInt(options.retry, 10);
534
- const retryConfirmed = await confirmAction(`\nWait ${waitSeconds}s and re-check conflicts?`, options.auto);
535
- if (retryConfirmed) {
536
- console.log(`\nā³ Waiting ${waitSeconds}s for agents to resolve conflicts...`);
537
- await sleep(waitSeconds * 1000);
538
- console.log('\nšŸ”„ RE-CHECKING CONFLICTS...\n');
539
- const { execFileSync } = await import('child_process');
540
- try {
541
- const { getCLIPath } = await import('../lib/security.js');
542
- // Security: Use execFileSync with argument array
543
- const safeBranch = validateBranchName(targetBranch);
544
- const tierArg = String(parseInt(options.tier, 10) || 1);
545
- execFileSync(process.execPath, [getCLIPath(), 'conflicts', '--branch', safeBranch, '--tier', tierArg], {
546
- encoding: 'utf-8',
547
- stdio: 'inherit'
548
- });
549
- return;
550
- }
551
- catch {
552
- console.log('āŒ Conflicts still exist after retry.');
553
- }
554
- }
555
- else {
556
- console.log('ā­ļø Skipping retry.');
557
- }
558
- }
559
- }
560
- else {
561
- console.log('ā­ļø Skipping wake.');
562
- }
563
- }
564
- }
565
768
  if (shouldBlock) {
566
769
  console.log('āŒ Strict mode: Exiting with error due to TIER 2+ conflicts.');
567
- process.exit(1);
568
770
  }
771
+ finish(shouldBlock ? 1 : 0);
569
772
  });
570
773
  //# sourceMappingURL=conflicts.js.map