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.
- package/CHANGELOG.md +171 -0
- package/README.md +39 -280
- package/dist/bin/command-registry.d.ts +25 -0
- package/dist/bin/command-registry.d.ts.map +1 -0
- package/dist/bin/command-registry.js +29 -0
- package/dist/bin/command-registry.js.map +1 -0
- package/dist/bin/spidersan.d.ts +7 -0
- package/dist/bin/spidersan.d.ts.map +1 -1
- package/dist/bin/spidersan.js +38 -57
- package/dist/bin/spidersan.js.map +1 -1
- package/dist/commands/cleanup.d.ts.map +1 -1
- package/dist/commands/cleanup.js +17 -3
- package/dist/commands/cleanup.js.map +1 -1
- package/dist/commands/config.d.ts.map +1 -1
- package/dist/commands/config.js +11 -39
- package/dist/commands/config.js.map +1 -1
- package/dist/commands/conflicts.d.ts +22 -0
- package/dist/commands/conflicts.d.ts.map +1 -1
- package/dist/commands/conflicts.js +459 -256
- package/dist/commands/conflicts.js.map +1 -1
- package/dist/commands/cross-conflicts.d.ts.map +1 -1
- package/dist/commands/cross-conflicts.js +7 -8
- package/dist/commands/cross-conflicts.js.map +1 -1
- package/dist/commands/depends.d.ts +4 -1
- package/dist/commands/depends.d.ts.map +1 -1
- package/dist/commands/depends.js +35 -8
- package/dist/commands/depends.js.map +1 -1
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +25 -7
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/git-watch.d.ts +1 -1
- package/dist/commands/git-watch.js +1 -1
- package/dist/commands/github-sync.d.ts +33 -0
- package/dist/commands/github-sync.d.ts.map +1 -1
- package/dist/commands/github-sync.js +68 -57
- package/dist/commands/github-sync.js.map +1 -1
- package/dist/commands/index.d.ts +2 -13
- package/dist/commands/index.d.ts.map +1 -1
- package/dist/commands/index.js +5 -20
- package/dist/commands/index.js.map +1 -1
- package/dist/commands/merge-order.d.ts.map +1 -1
- package/dist/commands/merge-order.js +8 -4
- package/dist/commands/merge-order.js.map +1 -1
- package/dist/commands/merged.d.ts.map +1 -1
- package/dist/commands/merged.js +5 -1
- package/dist/commands/merged.js.map +1 -1
- package/dist/commands/pr-check.d.ts +24 -0
- package/dist/commands/pr-check.d.ts.map +1 -0
- package/dist/commands/pr-check.js +85 -0
- package/dist/commands/pr-check.js.map +1 -0
- package/dist/commands/ready-check.d.ts.map +1 -1
- package/dist/commands/ready-check.js +6 -3
- package/dist/commands/ready-check.js.map +1 -1
- package/dist/commands/register.d.ts +5 -0
- package/dist/commands/register.d.ts.map +1 -1
- package/dist/commands/register.js +14 -1
- package/dist/commands/register.js.map +1 -1
- package/dist/commands/registry-sync.d.ts.map +1 -1
- package/dist/commands/registry-sync.js +12 -46
- package/dist/commands/registry-sync.js.map +1 -1
- package/dist/commands/stale.d.ts.map +1 -1
- package/dist/commands/stale.js +8 -3
- package/dist/commands/stale.js.map +1 -1
- package/dist/commands/sync.d.ts +14 -1
- package/dist/commands/sync.d.ts.map +1 -1
- package/dist/commands/sync.js +49 -31
- package/dist/commands/sync.js.map +1 -1
- package/dist/commands/verify-trunk.d.ts +18 -0
- package/dist/commands/verify-trunk.d.ts.map +1 -0
- package/dist/commands/verify-trunk.js +77 -0
- package/dist/commands/verify-trunk.js.map +1 -0
- package/dist/commands/watch.d.ts +0 -1
- package/dist/commands/watch.d.ts.map +1 -1
- package/dist/commands/watch.js +50 -77
- package/dist/commands/watch.js.map +1 -1
- package/dist/index.d.ts +0 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +0 -3
- package/dist/index.js.map +1 -1
- package/dist/lib/activity.d.ts.map +1 -1
- package/dist/lib/activity.js +6 -2
- package/dist/lib/activity.js.map +1 -1
- package/dist/lib/ai/context-builder.d.ts.map +1 -1
- package/dist/lib/ai/context-builder.js +19 -6
- package/dist/lib/ai/context-builder.js.map +1 -1
- package/dist/lib/ai/event-handler.d.ts.map +1 -1
- package/dist/lib/ai/event-handler.js +15 -2
- package/dist/lib/ai/event-handler.js.map +1 -1
- package/dist/lib/ai/reasoner.d.ts.map +1 -1
- package/dist/lib/ai/reasoner.js +63 -82
- package/dist/lib/ai/reasoner.js.map +1 -1
- package/dist/lib/ai/setup.d.ts +1 -1
- package/dist/lib/ai/setup.d.ts.map +1 -1
- package/dist/lib/ai/types.d.ts.map +1 -1
- package/dist/lib/ai/types.js +5 -2
- package/dist/lib/ai/types.js.map +1 -1
- package/dist/lib/ast.d.ts +10 -2
- package/dist/lib/ast.d.ts.map +1 -1
- package/dist/lib/ast.js +52 -27
- package/dist/lib/ast.js.map +1 -1
- package/dist/lib/carries.d.ts +22 -0
- package/dist/lib/carries.d.ts.map +1 -0
- package/dist/lib/carries.js +56 -0
- package/dist/lib/carries.js.map +1 -0
- package/dist/lib/colony-subscriber.d.ts +1 -1
- package/dist/lib/colony-subscriber.js +1 -1
- package/dist/lib/config.d.ts +2 -2
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +13 -8
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/conflict-renderer.d.ts.map +1 -1
- package/dist/lib/conflict-renderer.js +17 -34
- package/dist/lib/conflict-renderer.js.map +1 -1
- package/dist/lib/ecosystem-env.d.ts +33 -0
- package/dist/lib/ecosystem-env.d.ts.map +1 -0
- package/dist/lib/ecosystem-env.js +62 -0
- package/dist/lib/ecosystem-env.js.map +1 -0
- package/dist/lib/git-events-subscriber.d.ts +27 -0
- package/dist/lib/git-events-subscriber.d.ts.map +1 -1
- package/dist/lib/git-events-subscriber.js +149 -58
- package/dist/lib/git-events-subscriber.js.map +1 -1
- package/dist/lib/git-merge-analyzer.d.ts +38 -0
- package/dist/lib/git-merge-analyzer.d.ts.map +1 -0
- package/dist/lib/git-merge-analyzer.js +23 -0
- package/dist/lib/git-merge-analyzer.js.map +1 -0
- package/dist/lib/git.d.ts +88 -0
- package/dist/lib/git.d.ts.map +1 -1
- package/dist/lib/git.js +499 -9
- package/dist/lib/git.js.map +1 -1
- package/dist/lib/github.d.ts +69 -1
- package/dist/lib/github.d.ts.map +1 -1
- package/dist/lib/github.js +230 -16
- package/dist/lib/github.js.map +1 -1
- package/dist/lib/graph.d.ts +15 -0
- package/dist/lib/graph.d.ts.map +1 -1
- package/dist/lib/graph.js +31 -0
- package/dist/lib/graph.js.map +1 -1
- package/dist/lib/machine.d.ts +14 -0
- package/dist/lib/machine.d.ts.map +1 -0
- package/dist/lib/machine.js +38 -0
- package/dist/lib/machine.js.map +1 -0
- package/dist/lib/notify.d.ts +68 -0
- package/dist/lib/notify.d.ts.map +1 -0
- package/dist/lib/notify.js +111 -0
- package/dist/lib/notify.js.map +1 -0
- package/dist/lib/outline.d.ts +6 -0
- package/dist/lib/outline.d.ts.map +1 -0
- package/dist/lib/outline.js +60 -0
- package/dist/lib/outline.js.map +1 -0
- package/dist/lib/push-registry.d.ts +22 -0
- package/dist/lib/push-registry.d.ts.map +1 -0
- package/dist/lib/push-registry.js +21 -0
- package/dist/lib/push-registry.js.map +1 -0
- package/dist/lib/reconcile.d.ts +100 -0
- package/dist/lib/reconcile.d.ts.map +1 -0
- package/dist/lib/reconcile.js +121 -0
- package/dist/lib/reconcile.js.map +1 -0
- package/dist/lib/security.d.ts +1 -6
- package/dist/lib/security.d.ts.map +1 -1
- package/dist/lib/security.js +1 -11
- package/dist/lib/security.js.map +1 -1
- package/dist/lib/semantic-analysis.d.ts +31 -0
- package/dist/lib/semantic-analysis.d.ts.map +1 -0
- package/dist/lib/semantic-analysis.js +120 -0
- package/dist/lib/semantic-analysis.js.map +1 -0
- package/dist/lib/supabase-credentials.d.ts +36 -0
- package/dist/lib/supabase-credentials.d.ts.map +1 -0
- package/dist/lib/supabase-credentials.js +43 -0
- package/dist/lib/supabase-credentials.js.map +1 -0
- package/dist/lib/trunk.d.ts +39 -0
- package/dist/lib/trunk.d.ts.map +1 -0
- package/dist/lib/trunk.js +116 -0
- package/dist/lib/trunk.js.map +1 -0
- package/dist/storage/adapter.d.ts +20 -1
- package/dist/storage/adapter.d.ts.map +1 -1
- package/dist/storage/branch-registry-store.d.ts +1 -1
- package/dist/storage/branch-registry-store.d.ts.map +1 -1
- package/dist/storage/factory.d.ts +0 -4
- package/dist/storage/factory.d.ts.map +1 -1
- package/dist/storage/factory.js +2 -26
- package/dist/storage/factory.js.map +1 -1
- package/dist/storage/json-branch-registry-store.d.ts +1 -1
- package/dist/storage/json-branch-registry-store.d.ts.map +1 -1
- package/dist/storage/json-branch-registry-store.js +33 -7
- package/dist/storage/json-branch-registry-store.js.map +1 -1
- package/dist/storage/local.d.ts +1 -1
- package/dist/storage/local.d.ts.map +1 -1
- package/dist/storage/local.js +2 -2
- package/dist/storage/local.js.map +1 -1
- package/dist/storage/memory-branch-registry-store.d.ts +1 -1
- package/dist/storage/memory-branch-registry-store.d.ts.map +1 -1
- package/dist/storage/memory-branch-registry-store.js +4 -1
- package/dist/storage/memory-branch-registry-store.js.map +1 -1
- package/dist/storage/supabase-registry-sync-client-impl.d.ts +1 -7
- package/dist/storage/supabase-registry-sync-client-impl.d.ts.map +1 -1
- package/dist/storage/supabase-registry-sync-client-impl.js +17 -61
- package/dist/storage/supabase-registry-sync-client-impl.js.map +1 -1
- package/dist/storage/supabase-registry-sync-client.d.ts +1 -1
- package/dist/storage/supabase-registry-sync-client.d.ts.map +1 -1
- package/dist/storage/supabase.d.ts +3 -9
- package/dist/storage/supabase.d.ts.map +1 -1
- package/dist/storage/supabase.js +4 -22
- package/dist/storage/supabase.js.map +1 -1
- package/dist/types/cloud.d.ts +1 -0
- package/dist/types/cloud.d.ts.map +1 -1
- package/package.json +88 -90
- package/dist/commands/activate.d.ts +0 -9
- package/dist/commands/activate.d.ts.map +0 -1
- package/dist/commands/activate.js +0 -45
- package/dist/commands/activate.js.map +0 -1
- package/dist/commands/active-windows.d.ts +0 -16
- package/dist/commands/active-windows.d.ts.map +0 -1
- package/dist/commands/active-windows.js +0 -373
- package/dist/commands/active-windows.js.map +0 -1
- package/dist/commands/ai.d.ts +0 -15
- package/dist/commands/ai.d.ts.map +0 -1
- package/dist/commands/ai.js +0 -498
- package/dist/commands/ai.js.map +0 -1
- package/dist/commands/audit-mark.d.ts +0 -12
- package/dist/commands/audit-mark.d.ts.map +0 -1
- package/dist/commands/audit-mark.js +0 -204
- package/dist/commands/audit-mark.js.map +0 -1
- package/dist/commands/auto.d.ts +0 -8
- package/dist/commands/auto.d.ts.map +0 -1
- package/dist/commands/auto.js +0 -207
- package/dist/commands/auto.js.map +0 -1
- package/dist/commands/bot.d.ts +0 -16
- package/dist/commands/bot.d.ts.map +0 -1
- package/dist/commands/bot.js +0 -416
- package/dist/commands/bot.js.map +0 -1
- package/dist/commands/close.d.ts +0 -9
- package/dist/commands/close.d.ts.map +0 -1
- package/dist/commands/close.js +0 -129
- package/dist/commands/close.js.map +0 -1
- package/dist/commands/collab-sync.d.ts +0 -14
- package/dist/commands/collab-sync.d.ts.map +0 -1
- package/dist/commands/collab-sync.js +0 -264
- package/dist/commands/collab-sync.js.map +0 -1
- package/dist/commands/collab.d.ts +0 -6
- package/dist/commands/collab.d.ts.map +0 -1
- package/dist/commands/collab.js +0 -103
- package/dist/commands/collab.js.map +0 -1
- package/dist/commands/completions.d.ts +0 -8
- package/dist/commands/completions.d.ts.map +0 -1
- package/dist/commands/completions.js +0 -83
- package/dist/commands/completions.js.map +0 -1
- package/dist/commands/daily.d.ts +0 -7
- package/dist/commands/daily.d.ts.map +0 -1
- package/dist/commands/daily.js +0 -171
- package/dist/commands/daily.js.map +0 -1
- package/dist/commands/dashboard.d.ts +0 -22
- package/dist/commands/dashboard.d.ts.map +0 -1
- package/dist/commands/dashboard.js +0 -401
- package/dist/commands/dashboard.js.map +0 -1
- package/dist/commands/demo.d.ts +0 -3
- package/dist/commands/demo.d.ts.map +0 -1
- package/dist/commands/demo.js +0 -118
- package/dist/commands/demo.js.map +0 -1
- package/dist/commands/ecosystem-loader.d.ts +0 -14
- package/dist/commands/ecosystem-loader.d.ts.map +0 -1
- package/dist/commands/ecosystem-loader.js +0 -90
- package/dist/commands/ecosystem-loader.js.map +0 -1
- package/dist/commands/fleet-status.d.ts +0 -14
- package/dist/commands/fleet-status.d.ts.map +0 -1
- package/dist/commands/fleet-status.js +0 -127
- package/dist/commands/fleet-status.js.map +0 -1
- package/dist/commands/inbox.d.ts +0 -3
- package/dist/commands/inbox.d.ts.map +0 -1
- package/dist/commands/inbox.js +0 -120
- package/dist/commands/inbox.js.map +0 -1
- package/dist/commands/intent-scan.d.ts +0 -17
- package/dist/commands/intent-scan.d.ts.map +0 -1
- package/dist/commands/intent-scan.js +0 -336
- package/dist/commands/intent-scan.js.map +0 -1
- package/dist/commands/key-import.d.ts +0 -3
- package/dist/commands/key-import.d.ts.map +0 -1
- package/dist/commands/key-import.js +0 -54
- package/dist/commands/key-import.js.map +0 -1
- package/dist/commands/keygen.d.ts +0 -3
- package/dist/commands/keygen.d.ts.map +0 -1
- package/dist/commands/keygen.js +0 -53
- package/dist/commands/keygen.js.map +0 -1
- package/dist/commands/keys.d.ts +0 -3
- package/dist/commands/keys.d.ts.map +0 -1
- package/dist/commands/keys.js +0 -56
- package/dist/commands/keys.js.map +0 -1
- package/dist/commands/lock.d.ts +0 -11
- package/dist/commands/lock.d.ts.map +0 -1
- package/dist/commands/lock.js +0 -257
- package/dist/commands/lock.js.map +0 -1
- package/dist/commands/mcp-health.d.ts +0 -3
- package/dist/commands/mcp-health.d.ts.map +0 -1
- package/dist/commands/mcp-health.js +0 -305
- package/dist/commands/mcp-health.js.map +0 -1
- package/dist/commands/mcp-restart.d.ts +0 -3
- package/dist/commands/mcp-restart.d.ts.map +0 -1
- package/dist/commands/mcp-restart.js +0 -176
- package/dist/commands/mcp-restart.js.map +0 -1
- package/dist/commands/monitor.d.ts +0 -3
- package/dist/commands/monitor.d.ts.map +0 -1
- package/dist/commands/monitor.js +0 -153
- package/dist/commands/monitor.js.map +0 -1
- package/dist/commands/msg-read.d.ts +0 -3
- package/dist/commands/msg-read.d.ts.map +0 -1
- package/dist/commands/msg-read.js +0 -95
- package/dist/commands/msg-read.js.map +0 -1
- package/dist/commands/pulse.d.ts +0 -13
- package/dist/commands/pulse.d.ts.map +0 -1
- package/dist/commands/pulse.js +0 -174
- package/dist/commands/pulse.js.map +0 -1
- package/dist/commands/queen.d.ts +0 -25
- package/dist/commands/queen.d.ts.map +0 -1
- package/dist/commands/queen.js +0 -326
- package/dist/commands/queen.js.map +0 -1
- package/dist/commands/radar.d.ts +0 -3
- package/dist/commands/radar.d.ts.map +0 -1
- package/dist/commands/radar.js +0 -147
- package/dist/commands/radar.js.map +0 -1
- package/dist/commands/rescue.d.ts +0 -11
- package/dist/commands/rescue.d.ts.map +0 -1
- package/dist/commands/rescue.js +0 -147
- package/dist/commands/rescue.js.map +0 -1
- package/dist/commands/semantic.d.ts +0 -3
- package/dist/commands/semantic.d.ts.map +0 -1
- package/dist/commands/semantic.js +0 -62
- package/dist/commands/semantic.js.map +0 -1
- package/dist/commands/send.d.ts +0 -3
- package/dist/commands/send.d.ts.map +0 -1
- package/dist/commands/send.js +0 -121
- package/dist/commands/send.js.map +0 -1
- package/dist/commands/status.d.ts +0 -9
- package/dist/commands/status.d.ts.map +0 -1
- package/dist/commands/status.js +0 -41
- package/dist/commands/status.js.map +0 -1
- package/dist/commands/sync-advisor.d.ts +0 -14
- package/dist/commands/sync-advisor.d.ts.map +0 -1
- package/dist/commands/sync-advisor.js +0 -110
- package/dist/commands/sync-advisor.js.map +0 -1
- package/dist/commands/tension.d.ts +0 -12
- package/dist/commands/tension.d.ts.map +0 -1
- package/dist/commands/tension.js +0 -262
- package/dist/commands/tension.js.map +0 -1
- package/dist/commands/torrent.d.ts +0 -9
- package/dist/commands/torrent.d.ts.map +0 -1
- package/dist/commands/torrent.js +0 -470
- package/dist/commands/torrent.js.map +0 -1
- package/dist/commands/tui.d.ts +0 -8
- package/dist/commands/tui.d.ts.map +0 -1
- package/dist/commands/tui.js +0 -15
- package/dist/commands/tui.js.map +0 -1
- package/dist/commands/wake.d.ts +0 -9
- package/dist/commands/wake.d.ts.map +0 -1
- package/dist/commands/wake.js +0 -188
- package/dist/commands/wake.js.map +0 -1
- package/dist/commands/welcome.d.ts +0 -8
- package/dist/commands/welcome.d.ts.map +0 -1
- package/dist/commands/welcome.js +0 -85
- package/dist/commands/welcome.js.map +0 -1
- package/dist/commands/who-owns.d.ts +0 -3
- package/dist/commands/who-owns.d.ts.map +0 -1
- package/dist/commands/who-owns.js +0 -68
- package/dist/commands/who-owns.js.map +0 -1
- package/dist/commands/who-touched.d.ts +0 -9
- package/dist/commands/who-touched.d.ts.map +0 -1
- package/dist/commands/who-touched.js +0 -198
- package/dist/commands/who-touched.js.map +0 -1
- package/dist/lib/crdt.d.ts +0 -83
- package/dist/lib/crdt.d.ts.map +0 -1
- package/dist/lib/crdt.js +0 -160
- package/dist/lib/crdt.js.map +0 -1
- package/dist/lib/daily-bridge.d.ts +0 -41
- package/dist/lib/daily-bridge.d.ts.map +0 -1
- package/dist/lib/daily-bridge.js +0 -186
- package/dist/lib/daily-bridge.js.map +0 -1
- package/dist/lib/hub.d.ts +0 -31
- package/dist/lib/hub.d.ts.map +0 -1
- package/dist/lib/hub.js +0 -92
- package/dist/lib/hub.js.map +0 -1
- package/dist/lib/license.d.ts +0 -61
- package/dist/lib/license.d.ts.map +0 -1
- package/dist/lib/license.js +0 -171
- package/dist/lib/license.js.map +0 -1
- package/dist/lib/pulse-renderer.d.ts +0 -19
- package/dist/lib/pulse-renderer.d.ts.map +0 -1
- package/dist/lib/pulse-renderer.js +0 -108
- package/dist/lib/pulse-renderer.js.map +0 -1
- package/dist/lib/repo-scanner.d.ts +0 -32
- package/dist/lib/repo-scanner.d.ts.map +0 -1
- package/dist/lib/repo-scanner.js +0 -160
- package/dist/lib/repo-scanner.js.map +0 -1
- package/dist/lib/salvage-analyzer.d.ts +0 -29
- package/dist/lib/salvage-analyzer.d.ts.map +0 -1
- package/dist/lib/salvage-analyzer.js +0 -121
- package/dist/lib/salvage-analyzer.js.map +0 -1
- package/dist/lib/session-logger.d.ts +0 -54
- package/dist/lib/session-logger.d.ts.map +0 -1
- package/dist/lib/session-logger.js +0 -136
- package/dist/lib/session-logger.js.map +0 -1
- package/dist/lib/symbol-extractor.d.ts +0 -41
- package/dist/lib/symbol-extractor.d.ts.map +0 -1
- package/dist/lib/symbol-extractor.js +0 -78
- package/dist/lib/symbol-extractor.js.map +0 -1
- package/dist/storage/git-messages.d.ts +0 -53
- package/dist/storage/git-messages.d.ts.map +0 -1
- package/dist/storage/git-messages.js +0 -376
- package/dist/storage/git-messages.js.map +0 -1
- package/dist/storage/local-messages.d.ts +0 -37
- package/dist/storage/local-messages.d.ts.map +0 -1
- package/dist/storage/local-messages.js +0 -151
- package/dist/storage/local-messages.js.map +0 -1
- package/dist/storage/message-adapter.d.ts +0 -86
- package/dist/storage/message-adapter.d.ts.map +0 -1
- package/dist/storage/message-adapter.js +0 -28
- package/dist/storage/message-adapter.js.map +0 -1
- package/dist/storage/message-factory.d.ts +0 -36
- package/dist/storage/message-factory.d.ts.map +0 -1
- package/dist/storage/message-factory.js +0 -99
- package/dist/storage/message-factory.js.map +0 -1
- package/dist/storage/mycmail-adapter.d.ts +0 -38
- package/dist/storage/mycmail-adapter.d.ts.map +0 -1
- package/dist/storage/mycmail-adapter.js +0 -300
- package/dist/storage/mycmail-adapter.js.map +0 -1
- package/dist/tui/dashboard.d.ts +0 -35
- package/dist/tui/dashboard.d.ts.map +0 -1
- package/dist/tui/dashboard.js +0 -199
- package/dist/tui/dashboard.js.map +0 -1
- package/dist/tui/screen.d.ts +0 -14
- package/dist/tui/screen.d.ts.map +0 -1
- package/dist/tui/screen.js +0 -138
- 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 {
|
|
17
|
-
import {
|
|
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
|
-
|
|
24
|
-
|
|
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
|
|
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
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
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(
|
|
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', '
|
|
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
|
-
|
|
275
|
-
|
|
276
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
//
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
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
|
-
//
|
|
674
|
+
// Deliver the alert if requested
|
|
424
675
|
if (options.notify && (tier3Count > 0 || tier2Count > 0)) {
|
|
425
|
-
await
|
|
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
|
-
|
|
439
|
-
process.exit(1);
|
|
690
|
+
finish(shouldBlock ? 1 : 0);
|
|
440
691
|
return;
|
|
441
692
|
}
|
|
442
693
|
if (conflicts.length === 0) {
|
|
443
|
-
|
|
444
|
-
|
|
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
|