spidersan 0.6.0 ā 0.9.0
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 +42 -52
- package/README.md +238 -3
- package/dist/bin/spidersan.d.ts.map +1 -1
- package/dist/bin/spidersan.js +13 -1
- package/dist/bin/spidersan.js.map +1 -1
- package/dist/commands/abandon.d.ts.map +1 -1
- package/dist/commands/abandon.js +1 -9
- package/dist/commands/abandon.js.map +1 -1
- package/dist/commands/ai.d.ts +15 -0
- package/dist/commands/ai.d.ts.map +1 -0
- package/dist/commands/ai.js +498 -0
- package/dist/commands/ai.js.map +1 -0
- package/dist/commands/auto.d.ts.map +1 -1
- package/dist/commands/auto.js +2 -1
- package/dist/commands/auto.js.map +1 -1
- package/dist/commands/bot.d.ts +16 -0
- package/dist/commands/bot.d.ts.map +1 -0
- package/dist/commands/bot.js +398 -0
- package/dist/commands/bot.js.map +1 -0
- package/dist/commands/conflicts.d.ts.map +1 -1
- package/dist/commands/conflicts.js +260 -277
- package/dist/commands/conflicts.js.map +1 -1
- package/dist/commands/context.d.ts +8 -0
- package/dist/commands/context.d.ts.map +1 -0
- package/dist/commands/context.js +104 -0
- package/dist/commands/context.js.map +1 -0
- package/dist/commands/cross-conflicts.d.ts.map +1 -1
- package/dist/commands/cross-conflicts.js +1 -41
- package/dist/commands/cross-conflicts.js.map +1 -1
- package/dist/commands/depends.d.ts.map +1 -1
- package/dist/commands/depends.js +1 -9
- package/dist/commands/depends.js.map +1 -1
- package/dist/commands/fleet-status.d.ts +14 -0
- package/dist/commands/fleet-status.d.ts.map +1 -0
- package/dist/commands/fleet-status.js +127 -0
- package/dist/commands/fleet-status.js.map +1 -0
- package/dist/commands/git-watch.d.ts +24 -0
- package/dist/commands/git-watch.d.ts.map +1 -0
- package/dist/commands/git-watch.js +84 -0
- package/dist/commands/git-watch.js.map +1 -0
- package/dist/commands/index.d.ts +5 -0
- package/dist/commands/index.d.ts.map +1 -1
- package/dist/commands/index.js +7 -0
- package/dist/commands/index.js.map +1 -1
- package/dist/commands/merge-order.d.ts.map +1 -1
- package/dist/commands/merge-order.js +18 -67
- package/dist/commands/merge-order.js.map +1 -1
- package/dist/commands/merged.d.ts.map +1 -1
- package/dist/commands/merged.js +1 -9
- package/dist/commands/merged.js.map +1 -1
- package/dist/commands/pulse.d.ts.map +1 -1
- package/dist/commands/pulse.js +134 -63
- package/dist/commands/pulse.js.map +1 -1
- package/dist/commands/queen.d.ts.map +1 -1
- package/dist/commands/queen.js +11 -7
- package/dist/commands/queen.js.map +1 -1
- package/dist/commands/ready-check.d.ts +2 -1
- package/dist/commands/ready-check.d.ts.map +1 -1
- package/dist/commands/ready-check.js +6 -30
- package/dist/commands/ready-check.js.map +1 -1
- package/dist/commands/register.d.ts.map +1 -1
- package/dist/commands/register.js +7 -29
- package/dist/commands/register.js.map +1 -1
- package/dist/commands/torrent.d.ts.map +1 -1
- package/dist/commands/torrent.js +29 -18
- package/dist/commands/torrent.js.map +1 -1
- package/dist/commands/watch.d.ts.map +1 -1
- package/dist/commands/watch.js +13 -34
- package/dist/commands/watch.js.map +1 -1
- package/dist/lib/ai/context-builder.d.ts +16 -0
- package/dist/lib/ai/context-builder.d.ts.map +1 -0
- package/dist/lib/ai/context-builder.js +216 -0
- package/dist/lib/ai/context-builder.js.map +1 -0
- package/dist/lib/ai/event-handler.d.ts +21 -0
- package/dist/lib/ai/event-handler.d.ts.map +1 -0
- package/dist/lib/ai/event-handler.js +98 -0
- package/dist/lib/ai/event-handler.js.map +1 -0
- package/dist/lib/ai/index.d.ts +13 -0
- package/dist/lib/ai/index.d.ts.map +1 -0
- package/dist/lib/ai/index.js +11 -0
- package/dist/lib/ai/index.js.map +1 -0
- package/dist/lib/ai/llm-client.d.ts +37 -0
- package/dist/lib/ai/llm-client.d.ts.map +1 -0
- package/dist/lib/ai/llm-client.js +225 -0
- package/dist/lib/ai/llm-client.js.map +1 -0
- package/dist/lib/ai/reasoner.d.ts +11 -0
- package/dist/lib/ai/reasoner.d.ts.map +1 -0
- package/dist/lib/ai/reasoner.js +246 -0
- package/dist/lib/ai/reasoner.js.map +1 -0
- package/dist/lib/ai/setup.d.ts +40 -0
- package/dist/lib/ai/setup.d.ts.map +1 -0
- package/dist/lib/ai/setup.js +154 -0
- package/dist/lib/ai/setup.js.map +1 -0
- package/dist/lib/ai/types.d.ts +135 -0
- package/dist/lib/ai/types.d.ts.map +1 -0
- package/dist/lib/ai/types.js +39 -0
- package/dist/lib/ai/types.js.map +1 -0
- package/dist/lib/colony-subscriber.d.ts +15 -12
- package/dist/lib/colony-subscriber.d.ts.map +1 -1
- package/dist/lib/colony-subscriber.js +146 -65
- package/dist/lib/colony-subscriber.js.map +1 -1
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +18 -4
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/conflict-analyzer.d.ts +33 -0
- package/dist/lib/conflict-analyzer.d.ts.map +1 -0
- package/dist/lib/conflict-analyzer.js +114 -0
- package/dist/lib/conflict-analyzer.js.map +1 -0
- package/dist/lib/conflict-renderer.d.ts +7 -0
- package/dist/lib/conflict-renderer.d.ts.map +1 -0
- package/dist/lib/conflict-renderer.js +162 -0
- package/dist/lib/conflict-renderer.js.map +1 -0
- package/dist/lib/conflict-tier.d.ts +20 -0
- package/dist/lib/conflict-tier.d.ts.map +1 -0
- package/dist/lib/conflict-tier.js +49 -0
- package/dist/lib/conflict-tier.js.map +1 -0
- package/dist/lib/crypto.js +1 -1
- package/dist/lib/crypto.js.map +1 -1
- package/dist/lib/git-events-subscriber.d.ts +59 -0
- package/dist/lib/git-events-subscriber.d.ts.map +1 -0
- package/dist/lib/git-events-subscriber.js +779 -0
- package/dist/lib/git-events-subscriber.js.map +1 -0
- package/dist/lib/git.d.ts +15 -0
- package/dist/lib/git.d.ts.map +1 -0
- package/dist/lib/git.js +180 -0
- package/dist/lib/git.js.map +1 -0
- package/dist/lib/github.d.ts.map +1 -1
- package/dist/lib/github.js +14 -9
- package/dist/lib/github.js.map +1 -1
- package/dist/lib/graph.d.ts +23 -0
- package/dist/lib/graph.d.ts.map +1 -0
- package/dist/lib/graph.js +134 -0
- package/dist/lib/graph.js.map +1 -0
- package/dist/lib/hub.d.ts +31 -0
- package/dist/lib/hub.d.ts.map +1 -0
- package/dist/lib/hub.js +92 -0
- package/dist/lib/hub.js.map +1 -0
- package/dist/lib/remote-drift.d.ts +60 -0
- package/dist/lib/remote-drift.d.ts.map +1 -0
- package/dist/lib/remote-drift.js +225 -0
- package/dist/lib/remote-drift.js.map +1 -0
- package/dist/lib/salvage-analyzer.d.ts.map +1 -1
- package/dist/lib/salvage-analyzer.js +2 -3
- package/dist/lib/salvage-analyzer.js.map +1 -1
- package/dist/lib/security.d.ts +11 -0
- package/dist/lib/security.d.ts.map +1 -1
- package/dist/lib/security.js +24 -1
- package/dist/lib/security.js.map +1 -1
- package/dist/lib/session-logger.d.ts +54 -0
- package/dist/lib/session-logger.d.ts.map +1 -0
- package/dist/lib/session-logger.js +136 -0
- package/dist/lib/session-logger.js.map +1 -0
- package/dist/storage/adapter.d.ts +4 -0
- package/dist/storage/adapter.d.ts.map +1 -1
- package/dist/storage/branch-registry-store.d.ts +13 -0
- package/dist/storage/branch-registry-store.d.ts.map +1 -0
- package/dist/storage/branch-registry-store.js +2 -0
- package/dist/storage/branch-registry-store.js.map +1 -0
- package/dist/storage/factory.d.ts +4 -0
- package/dist/storage/factory.d.ts.map +1 -1
- package/dist/storage/factory.js +25 -9
- package/dist/storage/factory.js.map +1 -1
- package/dist/storage/git-messages.d.ts +53 -0
- package/dist/storage/git-messages.d.ts.map +1 -0
- package/dist/storage/git-messages.js +376 -0
- package/dist/storage/git-messages.js.map +1 -0
- package/dist/storage/index.d.ts +5 -0
- package/dist/storage/index.d.ts.map +1 -1
- package/dist/storage/index.js +5 -0
- package/dist/storage/index.js.map +1 -1
- package/dist/storage/json-branch-registry-store.d.ts +19 -0
- package/dist/storage/json-branch-registry-store.d.ts.map +1 -0
- package/dist/storage/json-branch-registry-store.js +112 -0
- package/dist/storage/json-branch-registry-store.js.map +1 -0
- package/dist/storage/local-messages.d.ts +37 -0
- package/dist/storage/local-messages.d.ts.map +1 -0
- package/dist/storage/local-messages.js +151 -0
- package/dist/storage/local-messages.js.map +1 -0
- package/dist/storage/local.d.ts +2 -6
- package/dist/storage/local.d.ts.map +1 -1
- package/dist/storage/local.js +13 -76
- package/dist/storage/local.js.map +1 -1
- package/dist/storage/memory-branch-registry-store.d.ts +16 -0
- package/dist/storage/memory-branch-registry-store.d.ts.map +1 -0
- package/dist/storage/memory-branch-registry-store.js +65 -0
- package/dist/storage/memory-branch-registry-store.js.map +1 -0
- package/dist/storage/message-adapter.d.ts +86 -0
- package/dist/storage/message-adapter.d.ts.map +1 -0
- package/dist/storage/message-adapter.js +28 -0
- package/dist/storage/message-adapter.js.map +1 -0
- package/dist/storage/message-factory.d.ts +36 -0
- package/dist/storage/message-factory.d.ts.map +1 -0
- package/dist/storage/message-factory.js +99 -0
- package/dist/storage/message-factory.js.map +1 -0
- package/dist/storage/mycmail-adapter.d.ts +38 -0
- package/dist/storage/mycmail-adapter.d.ts.map +1 -0
- package/dist/storage/mycmail-adapter.js +300 -0
- package/dist/storage/mycmail-adapter.js.map +1 -0
- package/dist/storage/supabase-registry-sync-client-impl.d.ts +46 -0
- package/dist/storage/supabase-registry-sync-client-impl.d.ts.map +1 -0
- package/dist/storage/supabase-registry-sync-client-impl.js +322 -0
- package/dist/storage/supabase-registry-sync-client-impl.js.map +1 -0
- package/dist/storage/supabase-registry-sync-client.d.ts +9 -0
- package/dist/storage/supabase-registry-sync-client.d.ts.map +1 -0
- package/dist/storage/supabase-registry-sync-client.js +2 -0
- package/dist/storage/supabase-registry-sync-client.js.map +1 -0
- package/dist/storage/supabase.d.ts +8 -46
- package/dist/storage/supabase.d.ts.map +1 -1
- package/dist/storage/supabase.js +30 -342
- package/dist/storage/supabase.js.map +1 -1
- package/dist/tui/screen.d.ts.map +1 -1
- package/dist/tui/screen.js +5 -3
- package/dist/tui/screen.js.map +1 -1
- package/package.json +92 -90
|
@@ -9,168 +9,38 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import { Command } from 'commander';
|
|
11
11
|
import { compilePatterns } from '../lib/regex-utils.js';
|
|
12
|
-
import {
|
|
12
|
+
import { spawnSync } from 'child_process';
|
|
13
13
|
import { basename } from 'path';
|
|
14
14
|
import { homedir } from 'os';
|
|
15
15
|
import { getStorage } from '../storage/index.js';
|
|
16
16
|
import { ASTParser } from '../lib/ast.js';
|
|
17
|
+
import { analyzeConflicts } from '../lib/conflict-analyzer.js';
|
|
18
|
+
import { renderConflictReport } from '../lib/conflict-renderer.js';
|
|
19
|
+
import { getCurrentBranch, getFileAtRef } from '../lib/git.js';
|
|
17
20
|
import { validateBranchName } from '../lib/security.js';
|
|
18
21
|
import { isExcludedPath } from './register.js';
|
|
19
22
|
import { loadConfig } from '../lib/config.js';
|
|
20
23
|
import { logActivity } from '../lib/activity.js';
|
|
21
24
|
import { isGhAvailable, getPRDetails } from '../lib/github.js';
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
const TIER_3_PATTERNS = [
|
|
26
|
-
/\.env$/,
|
|
27
|
-
/secrets?\./i,
|
|
28
|
-
/credentials/i,
|
|
29
|
-
/password/i,
|
|
30
|
-
/api[_-]?key/i,
|
|
31
|
-
/private[_-]?key/i,
|
|
32
|
-
/\.pem$/,
|
|
33
|
-
/auth\.(ts|js)$/,
|
|
34
|
-
/security\.(ts|js)$/,
|
|
35
|
-
];
|
|
36
|
-
// Important files that trigger TIER 2 pause
|
|
37
|
-
const TIER_2_PATTERNS = [
|
|
38
|
-
/package\.json$/,
|
|
39
|
-
/package-lock\.json$/,
|
|
40
|
-
/tsconfig\.json$/,
|
|
41
|
-
/CLAUDE\.md$/,
|
|
42
|
-
/\.gitignore$/,
|
|
43
|
-
/server\.(ts|js)$/,
|
|
44
|
-
/index\.(ts|js)$/,
|
|
45
|
-
/config\.(ts|js)$/,
|
|
46
|
-
];
|
|
47
|
-
// Optimization: Pre-compile static patterns
|
|
48
|
-
const COMPILED_TIER_3 = compilePatterns(TIER_3_PATTERNS);
|
|
49
|
-
const COMPILED_TIER_2 = compilePatterns(TIER_2_PATTERNS);
|
|
50
|
-
function getConflictTier(file, compiledHigh = [], compiledMedium = []) {
|
|
51
|
-
// Check TIER 3 first (most critical)
|
|
52
|
-
const tier3 = [...COMPILED_TIER_3, ...compiledHigh];
|
|
53
|
-
for (const pattern of tier3) {
|
|
54
|
-
if (pattern.test(file)) {
|
|
55
|
-
return {
|
|
56
|
-
tier: 3,
|
|
57
|
-
label: 'BLOCK',
|
|
58
|
-
icon: 'š“',
|
|
59
|
-
action: 'Merge blocked. Resolve conflict first.'
|
|
60
|
-
};
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
// Check TIER 2
|
|
64
|
-
const tier2 = [...COMPILED_TIER_2, ...compiledMedium];
|
|
65
|
-
for (const pattern of tier2) {
|
|
66
|
-
if (pattern.test(file)) {
|
|
67
|
-
return {
|
|
68
|
-
tier: 2,
|
|
69
|
-
label: 'PAUSE',
|
|
70
|
-
icon: 'š ',
|
|
71
|
-
action: 'Coordinate with other agent before proceeding.'
|
|
72
|
-
};
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
// Default: TIER 1
|
|
76
|
-
return {
|
|
77
|
-
tier: 1,
|
|
78
|
-
label: 'WARN',
|
|
79
|
-
icon: 'š”',
|
|
80
|
-
action: 'Consider coordinating, but safe to proceed.'
|
|
81
|
-
};
|
|
82
|
-
}
|
|
83
|
-
function getCurrentBranch() {
|
|
84
|
-
try {
|
|
85
|
-
return execFileSync('git', ['rev-parse', '--abbrev-ref', 'HEAD'], { encoding: 'utf-8' }).trim();
|
|
86
|
-
}
|
|
87
|
-
catch {
|
|
88
|
-
throw new Error('Not in a git repository');
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
async function notifyHub(branch, conflicts) {
|
|
92
|
-
const tier3 = conflicts.filter(c => c.tier === 3);
|
|
93
|
-
const tier2 = conflicts.filter(c => c.tier === 2);
|
|
94
|
-
if (tier3.length === 0 && tier2.length === 0)
|
|
95
|
-
return;
|
|
96
|
-
const severity = tier3.length > 0 ? 'š“ TIER 3 BLOCK' : 'š TIER 2 PAUSE';
|
|
97
|
-
const message = `š·ļøā ļø **Conflict Alert** on \`${branch}\`\n\n${severity}\n\nConflicting files require coordination.`;
|
|
98
|
-
try {
|
|
99
|
-
await fetch(`${HUB_URL}/api/chat`, {
|
|
100
|
-
method: 'POST',
|
|
101
|
-
headers: { 'Content-Type': 'application/json' },
|
|
102
|
-
body: JSON.stringify({
|
|
103
|
-
agent: 'spidersan',
|
|
104
|
-
name: 'Spidersan',
|
|
105
|
-
message,
|
|
106
|
-
glyph: 'š·ļø'
|
|
107
|
-
})
|
|
108
|
-
});
|
|
109
|
-
}
|
|
110
|
-
catch {
|
|
111
|
-
// Hub offline - silent fail
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
/**
|
|
115
|
-
* Wake a conflicting agent and send them a message about what to fix
|
|
116
|
-
*/
|
|
117
|
-
async function wakeConflictingAgent(agentId, myBranch, theirBranch, conflictingFiles) {
|
|
118
|
-
const fileList = conflictingFiles.slice(0, 5).join(', ');
|
|
119
|
-
const more = conflictingFiles.length > 5 ? ` (+${conflictingFiles.length - 5} more)` : '';
|
|
120
|
-
// 1. Wake the agent via Hub
|
|
121
|
-
try {
|
|
122
|
-
const wakeResponse = await fetch(`${HUB_URL}/api/wake/${agentId}`, {
|
|
123
|
-
method: 'POST',
|
|
124
|
-
headers: { 'Content-Type': 'application/json' },
|
|
125
|
-
body: JSON.stringify({
|
|
126
|
-
sender: 'spidersan',
|
|
127
|
-
reason: `Conflict on ${theirBranch} - need resolution`
|
|
128
|
-
})
|
|
129
|
-
});
|
|
130
|
-
if (wakeResponse.ok) {
|
|
131
|
-
console.log(` š Wake signal sent to ${agentId}`);
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
catch {
|
|
135
|
-
console.log(` ā ļø Could not wake ${agentId} via Hub`);
|
|
136
|
-
}
|
|
137
|
-
return true;
|
|
138
|
-
}
|
|
25
|
+
import { TIER_LABELS } from '../lib/conflict-tier.js';
|
|
26
|
+
import { createHubClient } from '../lib/hub.js';
|
|
27
|
+
const hub = createHubClient();
|
|
139
28
|
/**
|
|
140
29
|
* Wait for a specified time
|
|
141
30
|
*/
|
|
142
31
|
function sleep(ms) {
|
|
143
32
|
return new Promise(resolve => setTimeout(resolve, ms));
|
|
144
33
|
}
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
if (/\.md$/.test(f) || /docs\//.test(f)) {
|
|
156
|
-
addAddFiles.add(f);
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
if (addAddFiles.size === 0)
|
|
161
|
-
return;
|
|
162
|
-
console.log('\nš” Add/Add Conflict Helper');
|
|
163
|
-
console.log('āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā');
|
|
164
|
-
console.log('Detected files added in both branches. Recommended actions:');
|
|
165
|
-
console.log(' 1. Preserve incoming variant for auditability:');
|
|
166
|
-
console.log(' git checkout --theirs <file> && mv <file> <file>.<their-branch>.incoming && git add <file>.<their-branch>.incoming');
|
|
167
|
-
console.log(' 2. Keep canonical version in main branch and include note linking the incoming variant.');
|
|
168
|
-
console.log(' 3. Commit and continue rebase: git add -A && git rebase --continue');
|
|
169
|
-
console.log('\nExamples:');
|
|
170
|
-
for (const f of addAddFiles) {
|
|
171
|
-
console.log(` ⢠${f} -> preserve incoming as ${f}.incoming`);
|
|
172
|
-
}
|
|
173
|
-
console.log('āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā\n');
|
|
34
|
+
function writeStdout(message = '') {
|
|
35
|
+
process.stdout.write(`${message}\n`);
|
|
36
|
+
}
|
|
37
|
+
function writeStderr(message) {
|
|
38
|
+
process.stderr.write(`${message}\n`);
|
|
39
|
+
}
|
|
40
|
+
function fail(messages) {
|
|
41
|
+
const output = Array.isArray(messages) ? messages.join('\n') : messages;
|
|
42
|
+
writeStderr(output);
|
|
43
|
+
process.exit(1);
|
|
174
44
|
}
|
|
175
45
|
/**
|
|
176
46
|
* Prompt user for Y/N confirmation (prevents Ralph Wiggum loops)
|
|
@@ -179,7 +49,7 @@ function suggestAddAddResolution(conflicts) {
|
|
|
179
49
|
*/
|
|
180
50
|
async function confirmAction(prompt, autoConfirm = false) {
|
|
181
51
|
if (autoConfirm) {
|
|
182
|
-
|
|
52
|
+
writeStdout(`${prompt} [Y/n]: Y (auto)`);
|
|
183
53
|
return true;
|
|
184
54
|
}
|
|
185
55
|
const readline = await import('readline');
|
|
@@ -195,6 +65,141 @@ async function confirmAction(prompt, autoConfirm = false) {
|
|
|
195
65
|
});
|
|
196
66
|
});
|
|
197
67
|
}
|
|
68
|
+
function filterConflictReport(report, minTier) {
|
|
69
|
+
const conflicts = report.conflicts.filter((conflict) => conflict.tier >= minTier);
|
|
70
|
+
const byTier = {
|
|
71
|
+
1: conflicts.filter((conflict) => conflict.tier === 1),
|
|
72
|
+
2: conflicts.filter((conflict) => conflict.tier === 2),
|
|
73
|
+
3: conflicts.filter((conflict) => conflict.tier === 3),
|
|
74
|
+
};
|
|
75
|
+
const highest = conflicts.length > 0
|
|
76
|
+
? conflicts.reduce((max, conflict) => (conflict.tier > max ? conflict.tier : max), 1)
|
|
77
|
+
: 1;
|
|
78
|
+
return {
|
|
79
|
+
conflicts,
|
|
80
|
+
byTier,
|
|
81
|
+
highest,
|
|
82
|
+
shouldBlock: highest === 3,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
function buildBranchConflicts(report) {
|
|
86
|
+
const branchOrder = inferBranchOrder(report);
|
|
87
|
+
const branchOrderIndex = new Map(branchOrder.map((branch, index) => [branch, index]));
|
|
88
|
+
const grouped = new Map();
|
|
89
|
+
for (const conflict of report.conflicts) {
|
|
90
|
+
for (const branch of conflict.branches) {
|
|
91
|
+
let entry = grouped.get(branch.name);
|
|
92
|
+
if (!entry) {
|
|
93
|
+
entry = {
|
|
94
|
+
branch: branch.name,
|
|
95
|
+
files: [],
|
|
96
|
+
tier: 1,
|
|
97
|
+
tierInfo: TIER_LABELS[1],
|
|
98
|
+
};
|
|
99
|
+
grouped.set(branch.name, entry);
|
|
100
|
+
}
|
|
101
|
+
if (!entry.files.includes(conflict.file)) {
|
|
102
|
+
entry.files.push(conflict.file);
|
|
103
|
+
}
|
|
104
|
+
if (conflict.tier > entry.tier) {
|
|
105
|
+
entry.tier = conflict.tier;
|
|
106
|
+
entry.tierInfo = TIER_LABELS[conflict.tier];
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return [...grouped.values()].sort((left, right) => {
|
|
111
|
+
if (right.tier !== left.tier) {
|
|
112
|
+
return right.tier - left.tier;
|
|
113
|
+
}
|
|
114
|
+
return (branchOrderIndex.get(left.branch) ?? Number.MAX_SAFE_INTEGER)
|
|
115
|
+
- (branchOrderIndex.get(right.branch) ?? Number.MAX_SAFE_INTEGER);
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
function inferBranchOrder(report) {
|
|
119
|
+
const adjacency = new Map();
|
|
120
|
+
const indegree = new Map();
|
|
121
|
+
const firstSeen = new Map();
|
|
122
|
+
let seenIndex = 0;
|
|
123
|
+
const ensureBranch = (branch) => {
|
|
124
|
+
if (!adjacency.has(branch)) {
|
|
125
|
+
adjacency.set(branch, new Set());
|
|
126
|
+
}
|
|
127
|
+
if (!indegree.has(branch)) {
|
|
128
|
+
indegree.set(branch, 0);
|
|
129
|
+
}
|
|
130
|
+
if (!firstSeen.has(branch)) {
|
|
131
|
+
firstSeen.set(branch, seenIndex++);
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
for (const conflict of report.conflicts) {
|
|
135
|
+
const branchNames = conflict.branches.map((branch) => branch.name);
|
|
136
|
+
for (const branch of branchNames) {
|
|
137
|
+
ensureBranch(branch);
|
|
138
|
+
}
|
|
139
|
+
for (let index = 0; index < branchNames.length; index++) {
|
|
140
|
+
for (let nextIndex = index + 1; nextIndex < branchNames.length; nextIndex++) {
|
|
141
|
+
const from = branchNames[index];
|
|
142
|
+
const to = branchNames[nextIndex];
|
|
143
|
+
const edges = adjacency.get(from);
|
|
144
|
+
if (!edges || edges.has(to)) {
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
edges.add(to);
|
|
148
|
+
indegree.set(to, (indegree.get(to) ?? 0) + 1);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
const queue = [...indegree.entries()]
|
|
153
|
+
.filter(([, degree]) => degree === 0)
|
|
154
|
+
.map(([branch]) => branch)
|
|
155
|
+
.sort((left, right) => (firstSeen.get(left) ?? 0) - (firstSeen.get(right) ?? 0));
|
|
156
|
+
const ordered = [];
|
|
157
|
+
while (queue.length > 0) {
|
|
158
|
+
const branch = queue.shift();
|
|
159
|
+
if (!branch) {
|
|
160
|
+
break;
|
|
161
|
+
}
|
|
162
|
+
ordered.push(branch);
|
|
163
|
+
for (const next of adjacency.get(branch) ?? []) {
|
|
164
|
+
const nextDegree = (indegree.get(next) ?? 0) - 1;
|
|
165
|
+
indegree.set(next, nextDegree);
|
|
166
|
+
if (nextDegree === 0) {
|
|
167
|
+
queue.push(next);
|
|
168
|
+
queue.sort((left, right) => (firstSeen.get(left) ?? 0) - (firstSeen.get(right) ?? 0));
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
if (ordered.length === indegree.size) {
|
|
173
|
+
return ordered;
|
|
174
|
+
}
|
|
175
|
+
return [...firstSeen.entries()]
|
|
176
|
+
.sort((left, right) => left[1] - right[1])
|
|
177
|
+
.map(([branch]) => branch);
|
|
178
|
+
}
|
|
179
|
+
function countByTier(report) {
|
|
180
|
+
const conflicts = buildBranchConflicts(report);
|
|
181
|
+
return {
|
|
182
|
+
tier1: conflicts.filter((conflict) => conflict.tier === 1).length,
|
|
183
|
+
tier2: conflicts.filter((conflict) => conflict.tier === 2).length,
|
|
184
|
+
tier3: conflicts.filter((conflict) => conflict.tier === 3).length,
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
function createJsonReport(report, branch, blocked) {
|
|
188
|
+
const summary = countByTier(report);
|
|
189
|
+
return {
|
|
190
|
+
...report,
|
|
191
|
+
branch,
|
|
192
|
+
conflicts: report.conflicts.map((conflict) => ({
|
|
193
|
+
...conflict,
|
|
194
|
+
files: [conflict.file],
|
|
195
|
+
branch: conflict.branches[0]?.name,
|
|
196
|
+
})),
|
|
197
|
+
summary: {
|
|
198
|
+
...summary,
|
|
199
|
+
blocked,
|
|
200
|
+
},
|
|
201
|
+
};
|
|
202
|
+
}
|
|
198
203
|
// āā Ecosystem scan āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
199
204
|
/**
|
|
200
205
|
* Default ecosystem repos. Configurable via SPIDERSAN_ECOSYSTEM env var.
|
|
@@ -275,41 +280,42 @@ function runEcosystemScan(repos, asJson) {
|
|
|
275
280
|
const totals = results.reduce((acc, r) => ({ tier3: acc.tier3 + r.tier3, tier2: acc.tier2 + r.tier2, tier1: acc.tier1 + r.tier1 }), { tier3: 0, tier2: 0, tier1: 0 });
|
|
276
281
|
const reposWithConflicts = results.filter(r => r.tier3 + r.tier2 + r.tier1 > 0).length;
|
|
277
282
|
if (asJson) {
|
|
278
|
-
|
|
283
|
+
writeStdout(JSON.stringify({
|
|
279
284
|
ecosystem: true,
|
|
280
285
|
repos: results,
|
|
281
286
|
summary: { ...totals, repos_scanned: results.length, repos_with_conflicts: reposWithConflicts },
|
|
282
287
|
}, null, 2));
|
|
283
288
|
return;
|
|
284
289
|
}
|
|
285
|
-
|
|
290
|
+
writeStdout(`
|
|
286
291
|
š·ļø ECOSYSTEM CONFLICT SCAN
|
|
287
292
|
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
288
293
|
`);
|
|
289
294
|
for (const r of results) {
|
|
290
295
|
if (r.skipped) {
|
|
291
|
-
|
|
296
|
+
writeStdout(` ⬠${r.name.padEnd(22)} (not initialized)`);
|
|
292
297
|
continue;
|
|
293
298
|
}
|
|
294
299
|
if (r.error) {
|
|
295
|
-
|
|
300
|
+
writeStdout(` ā ${r.name.padEnd(22)} (${r.error})`);
|
|
296
301
|
continue;
|
|
297
302
|
}
|
|
298
303
|
const icon = r.tier3 > 0 ? 'š“' : r.tier2 > 0 ? 'š ' : r.tier1 > 0 ? 'š”' : 'ā
';
|
|
299
304
|
const counts = r.tier3 + r.tier2 + r.tier1 > 0
|
|
300
305
|
? ` T3:${r.tier3} T2:${r.tier2} T1:${r.tier1}`
|
|
301
306
|
: ' clean';
|
|
302
|
-
|
|
307
|
+
writeStdout(` ${icon} ${r.name.padEnd(22)}${counts}`);
|
|
303
308
|
}
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
309
|
+
writeStdout('\nāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā');
|
|
310
|
+
writeStdout(`š Total: š“ ${totals.tier3} BLOCK | š ${totals.tier2} PAUSE | š” ${totals.tier1} WARN`);
|
|
311
|
+
writeStdout(` Repos scanned: ${results.length} | With conflicts: ${reposWithConflicts}`);
|
|
307
312
|
}
|
|
308
313
|
export const conflictsCommand = new Command('conflicts')
|
|
309
314
|
.description('Detect file conflicts between branches (with tiered blocking)')
|
|
310
315
|
.option('--branch <name>', 'Check conflicts for specific branch')
|
|
311
316
|
.option('--pr <number>', 'Check conflicts for a GitHub pull request by number')
|
|
312
317
|
.option('--json', 'Output as JSON')
|
|
318
|
+
.option('--verbose', 'Show detailed symbol overlap information in human output')
|
|
313
319
|
.option('--tier <level>', 'Filter by minimum tier (1, 2, or 3)', '1')
|
|
314
320
|
.option('--strict', 'Strict mode: exit with error if TIER 2+ conflicts found')
|
|
315
321
|
.option('--notify', 'Notify Hub of TIER 2+ conflicts')
|
|
@@ -332,8 +338,7 @@ export const conflictsCommand = new Command('conflicts')
|
|
|
332
338
|
const storage = await getStorage();
|
|
333
339
|
const config = await loadConfig();
|
|
334
340
|
if (!await storage.isInitialized()) {
|
|
335
|
-
|
|
336
|
-
process.exit(1);
|
|
341
|
+
fail('ā Spidersan not initialized. Run: spidersan init');
|
|
337
342
|
}
|
|
338
343
|
const minTier = parseInt(options.tier, 10);
|
|
339
344
|
// Load custom patterns from config
|
|
@@ -347,13 +352,13 @@ export const conflictsCommand = new Command('conflicts')
|
|
|
347
352
|
// --pr mode: fetch PR head branch + changed files from GitHub
|
|
348
353
|
const prNumber = parseInt(options.pr, 10);
|
|
349
354
|
if (isNaN(prNumber) || prNumber <= 0) {
|
|
350
|
-
|
|
351
|
-
process.exit(1);
|
|
355
|
+
fail('ā Invalid PR number. Provide a positive integer, e.g. --pr 42');
|
|
352
356
|
}
|
|
353
357
|
if (!isGhAvailable()) {
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
358
|
+
fail([
|
|
359
|
+
'ā GitHub CLI (gh) is not available or not authenticated.',
|
|
360
|
+
' Install: https://cli.github.com ā gh auth login',
|
|
361
|
+
]);
|
|
357
362
|
}
|
|
358
363
|
let prDetails;
|
|
359
364
|
try {
|
|
@@ -361,58 +366,47 @@ export const conflictsCommand = new Command('conflicts')
|
|
|
361
366
|
}
|
|
362
367
|
catch (err) {
|
|
363
368
|
const msg = err instanceof Error ? err.message : String(err);
|
|
364
|
-
|
|
365
|
-
process.exit(1);
|
|
369
|
+
fail(`ā Failed to fetch PR #${prNumber}: ${msg}`);
|
|
366
370
|
}
|
|
367
371
|
targetBranch = prDetails.headBranch;
|
|
368
372
|
targetFiles = prDetails.files;
|
|
369
|
-
|
|
370
|
-
|
|
373
|
+
writeStdout(`š·ļø Checking conflicts for PR #${prNumber}: "${prDetails.title}"`);
|
|
374
|
+
writeStdout(` Branch: ${targetBranch} (${targetFiles.length} file(s) changed)`);
|
|
371
375
|
}
|
|
372
376
|
else {
|
|
373
377
|
// Normal --branch or current branch mode
|
|
374
378
|
targetBranch = options.branch || getCurrentBranch();
|
|
375
379
|
const target = await storage.get(targetBranch);
|
|
376
380
|
if (!target) {
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
381
|
+
fail([
|
|
382
|
+
`ā Branch "${targetBranch}" is not registered.`,
|
|
383
|
+
' Run: spidersan register --files "..."',
|
|
384
|
+
]);
|
|
380
385
|
}
|
|
381
386
|
targetFiles = target.files;
|
|
382
387
|
}
|
|
383
388
|
const allBranches = await storage.list();
|
|
384
|
-
const
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
}
|
|
402
|
-
if (maxTier.tier >= minTier) {
|
|
403
|
-
conflicts.push({
|
|
404
|
-
branch: branch.name,
|
|
405
|
-
files: overlappingFiles,
|
|
406
|
-
tier: maxTier.tier,
|
|
407
|
-
tierInfo: maxTier
|
|
408
|
-
});
|
|
409
|
-
}
|
|
410
|
-
}
|
|
411
|
-
}
|
|
389
|
+
const analysisBranches = allBranches
|
|
390
|
+
.filter(branch => branch.name !== targetBranch && branch.status === 'active')
|
|
391
|
+
.map(branch => ({
|
|
392
|
+
...branch,
|
|
393
|
+
files: branch.files.filter(file => !isExcludedPath(file)),
|
|
394
|
+
}));
|
|
395
|
+
const rawReport = analyzeConflicts({
|
|
396
|
+
branches: analysisBranches,
|
|
397
|
+
targetFiles: targetFiles.filter(file => !isExcludedPath(file)),
|
|
398
|
+
options: {
|
|
399
|
+
extraTier3: compiledHigh,
|
|
400
|
+
extraTier2: compiledMedium,
|
|
401
|
+
useSymbolAware: false,
|
|
402
|
+
},
|
|
403
|
+
});
|
|
404
|
+
const report = filterConflictReport(rawReport, minTier);
|
|
405
|
+
const conflicts = buildBranchConflicts(report);
|
|
412
406
|
// SEMANTIC ANALYSIS with AST parser
|
|
413
407
|
const semanticConflicts = [];
|
|
414
408
|
if (options.semantic && conflicts.length > 0) {
|
|
415
|
-
|
|
409
|
+
writeStdout('\nš¬ Running semantic (AST) analysis...');
|
|
416
410
|
const astParser = new ASTParser();
|
|
417
411
|
for (const conflict of conflicts) {
|
|
418
412
|
for (const file of conflict.files) {
|
|
@@ -420,10 +414,11 @@ export const conflictsCommand = new Command('conflicts')
|
|
|
420
414
|
if (!/\.(ts|js|tsx|jsx)$/.test(file))
|
|
421
415
|
continue;
|
|
422
416
|
try {
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
417
|
+
const currentContent = getFileAtRef('HEAD', file);
|
|
418
|
+
const otherContent = getFileAtRef(conflict.branch, file);
|
|
419
|
+
if (currentContent === null || otherContent === null) {
|
|
420
|
+
continue;
|
|
421
|
+
}
|
|
427
422
|
const symbolConflicts = astParser.findSymbolConflicts(currentContent, `${targetBranch}:${file}`, otherContent, `${conflict.branch}:${file}`);
|
|
428
423
|
semanticConflicts.push(...symbolConflicts);
|
|
429
424
|
}
|
|
@@ -433,92 +428,73 @@ export const conflictsCommand = new Command('conflicts')
|
|
|
433
428
|
}
|
|
434
429
|
}
|
|
435
430
|
}
|
|
436
|
-
// Sort by tier (highest first)
|
|
437
|
-
conflicts.sort((a, b) => b.tier - a.tier);
|
|
438
431
|
// Log conflict detection to activity log
|
|
439
432
|
if (conflicts.length > 0) {
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
433
|
+
try {
|
|
434
|
+
const summary = countByTier(report);
|
|
435
|
+
logActivity({
|
|
436
|
+
event: 'conflict_detected',
|
|
437
|
+
branch: targetBranch,
|
|
438
|
+
details: {
|
|
439
|
+
tier3: summary.tier3,
|
|
440
|
+
tier2: summary.tier2,
|
|
441
|
+
tier1: summary.tier1,
|
|
442
|
+
conflicting_branches: conflicts.map(c => c.branch),
|
|
443
|
+
}
|
|
444
|
+
});
|
|
445
|
+
}
|
|
446
|
+
catch {
|
|
447
|
+
// Telemetry should never block conflict detection.
|
|
448
|
+
}
|
|
450
449
|
}
|
|
451
450
|
// Check for blocking conditions
|
|
452
|
-
const
|
|
453
|
-
const tier2Count = conflicts.filter(c => c.tier === 2).length;
|
|
451
|
+
const { tier2: tier2Count, tier3: tier3Count } = countByTier(report);
|
|
454
452
|
const shouldBlock = options.strict && (tier3Count > 0 || tier2Count > 0);
|
|
455
453
|
// Notify Hub if requested
|
|
456
454
|
if (options.notify && (tier3Count > 0 || tier2Count > 0)) {
|
|
457
|
-
await
|
|
455
|
+
await hub.notifyConflict(targetBranch, conflicts);
|
|
458
456
|
}
|
|
459
457
|
if (options.json) {
|
|
460
|
-
console.log(JSON.stringify(
|
|
461
|
-
branch: targetBranch,
|
|
462
|
-
conflicts,
|
|
463
|
-
summary: {
|
|
464
|
-
tier3: tier3Count,
|
|
465
|
-
tier2: tier2Count,
|
|
466
|
-
tier1: conflicts.filter(c => c.tier === 1).length,
|
|
467
|
-
blocked: shouldBlock
|
|
468
|
-
}
|
|
469
|
-
}, null, 2));
|
|
458
|
+
console.log(JSON.stringify(createJsonReport(report, targetBranch, shouldBlock), null, 2));
|
|
470
459
|
if (shouldBlock)
|
|
471
460
|
process.exit(1);
|
|
472
461
|
return;
|
|
473
462
|
}
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
}
|
|
479
|
-
console.log(`
|
|
463
|
+
const renderedReport = renderConflictReport(report, { verbose: !!options.verbose });
|
|
464
|
+
const humanOutput = conflicts.length === 0
|
|
465
|
+
? renderedReport.replace('š·ļø No conflicts detected', `š·ļø No conflicts detected for "${targetBranch}"`)
|
|
466
|
+
: `
|
|
480
467
|
š·ļø CONFLICT ANALYSIS: "${targetBranch}"
|
|
481
468
|
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
for (const file of conflict.files) {
|
|
486
|
-
const fileTier = getConflictTier(file, compiledHigh, compiledMedium);
|
|
487
|
-
console.log(` ${fileTier.icon} ${file}`);
|
|
488
|
-
}
|
|
489
|
-
console.log(` ā ${conflict.tierInfo.action}`);
|
|
490
|
-
console.log('');
|
|
491
|
-
}
|
|
492
|
-
console.log('āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā');
|
|
493
|
-
console.log(`š Summary: š“ ${tier3Count} BLOCK | š ${tier2Count} PAUSE | š” ${conflicts.filter(c => c.tier === 1).length} WARN`);
|
|
494
|
-
// Offer add/add resolution suggestions for likely add/add files
|
|
495
|
-
suggestAddAddResolution(conflicts);
|
|
469
|
+
|
|
470
|
+
${renderedReport}`;
|
|
471
|
+
console.log(humanOutput);
|
|
496
472
|
// Show semantic analysis results
|
|
497
473
|
if (options.semantic && semanticConflicts.length > 0) {
|
|
498
|
-
|
|
499
|
-
|
|
474
|
+
writeStdout(`\nš¬ SEMANTIC CONFLICTS DETECTED (${semanticConflicts.length} symbols):`);
|
|
475
|
+
writeStdout('āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā');
|
|
500
476
|
for (const sc of semanticConflicts) {
|
|
501
|
-
|
|
502
|
-
|
|
477
|
+
writeStdout(` ā” ${sc.symbolType} '${sc.symbolName}'`);
|
|
478
|
+
writeStdout(' Modified in BOTH branches (different content)');
|
|
503
479
|
}
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
480
|
+
writeStdout('āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā');
|
|
481
|
+
writeStdout('\nš” TIP: Coordinate on these specific functions/classes,');
|
|
482
|
+
writeStdout(' not just the files. One of you should rebase.');
|
|
507
483
|
}
|
|
508
484
|
else if (options.semantic && semanticConflicts.length === 0) {
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
485
|
+
writeStdout('\nš¬ SEMANTIC ANALYSIS: No symbol-level conflicts!');
|
|
486
|
+
writeStdout(' Files overlap, but different functions were modified.');
|
|
487
|
+
writeStdout(' ā
Likely safe to merge (git will auto-merge).');
|
|
512
488
|
}
|
|
513
489
|
if (tier3Count > 0) {
|
|
514
|
-
|
|
490
|
+
writeStdout(`
|
|
515
491
|
š“ TIER 3 CONFLICTS FOUND
|
|
516
492
|
These files are security-critical and MUST be resolved.
|
|
517
493
|
Merge is BLOCKED until conflicts are cleared.
|
|
518
494
|
`);
|
|
519
495
|
}
|
|
520
496
|
else if (tier2Count > 0) {
|
|
521
|
-
|
|
497
|
+
writeStdout(`
|
|
522
498
|
š TIER 2 CONFLICTS FOUND
|
|
523
499
|
Coordinate with the other agent before proceeding.
|
|
524
500
|
`);
|
|
@@ -538,60 +514,67 @@ export const conflictsCommand = new Command('conflicts')
|
|
|
538
514
|
}
|
|
539
515
|
}
|
|
540
516
|
if (agentsToWake.size === 0) {
|
|
541
|
-
|
|
517
|
+
writeStdout('\nā ļø No agents registered on conflicting branches.');
|
|
542
518
|
}
|
|
543
519
|
else {
|
|
544
520
|
// Show what will happen and ask for confirmation
|
|
545
|
-
|
|
546
|
-
|
|
521
|
+
writeStdout('\nš WAKE AGENTS?');
|
|
522
|
+
writeStdout('āāāāāāāāāāāāāāāāāāāāāāāāāāāāāā');
|
|
547
523
|
for (const [agent, info] of agentsToWake) {
|
|
548
|
-
|
|
524
|
+
writeStdout(` ⢠${agent} (${info.branch})`);
|
|
549
525
|
}
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
526
|
+
writeStdout('āāāāāāāāāāāāāāāāāāāāāāāāāāāāāā');
|
|
527
|
+
writeStdout('This will:');
|
|
528
|
+
writeStdout(' 1. Send wake signal via Hub\n');
|
|
553
529
|
const confirmed = await confirmAction('Wake these agents?', options.auto);
|
|
554
530
|
if (confirmed) {
|
|
555
|
-
|
|
531
|
+
writeStdout('\nš WAKING AGENTS...\n');
|
|
556
532
|
for (const [agentId, info] of agentsToWake) {
|
|
557
|
-
await
|
|
533
|
+
const woke = await hub.wakeAgent(agentId, `Conflict on ${info.branch} - need resolution`);
|
|
534
|
+
if (woke) {
|
|
535
|
+
writeStdout(` š Wake signal sent to ${agentId}`);
|
|
536
|
+
}
|
|
537
|
+
else {
|
|
538
|
+
writeStdout(` ā ļø Could not wake ${agentId} via Hub`);
|
|
539
|
+
}
|
|
558
540
|
}
|
|
559
|
-
|
|
541
|
+
writeStdout(`\nā
Woke ${agentsToWake.size} agent(s)`);
|
|
560
542
|
// If --retry is set, ask before waiting
|
|
561
543
|
if (options.retry) {
|
|
562
544
|
const waitSeconds = parseInt(options.retry, 10);
|
|
563
545
|
const retryConfirmed = await confirmAction(`\nWait ${waitSeconds}s and re-check conflicts?`, options.auto);
|
|
564
546
|
if (retryConfirmed) {
|
|
565
|
-
|
|
547
|
+
writeStdout(`\nā³ Waiting ${waitSeconds}s for agents to resolve conflicts...`);
|
|
566
548
|
await sleep(waitSeconds * 1000);
|
|
567
|
-
|
|
549
|
+
writeStdout('\nš RE-CHECKING CONFLICTS...\n');
|
|
568
550
|
const { execFileSync } = await import('child_process');
|
|
569
551
|
try {
|
|
552
|
+
const { getCLIPath } = await import('../lib/security.js');
|
|
570
553
|
// Security: Use execFileSync with argument array
|
|
571
554
|
const safeBranch = validateBranchName(targetBranch);
|
|
572
555
|
const tierArg = String(parseInt(options.tier, 10) || 1);
|
|
573
|
-
execFileSync(
|
|
556
|
+
execFileSync(process.execPath, [getCLIPath(), 'conflicts', '--branch', safeBranch, '--tier', tierArg], {
|
|
574
557
|
encoding: 'utf-8',
|
|
575
558
|
stdio: 'inherit'
|
|
576
559
|
});
|
|
577
560
|
return;
|
|
578
561
|
}
|
|
579
562
|
catch {
|
|
580
|
-
|
|
563
|
+
writeStdout('ā Conflicts still exist after retry.');
|
|
581
564
|
}
|
|
582
565
|
}
|
|
583
566
|
else {
|
|
584
|
-
|
|
567
|
+
writeStdout('āļø Skipping retry.');
|
|
585
568
|
}
|
|
586
569
|
}
|
|
587
570
|
}
|
|
588
571
|
else {
|
|
589
|
-
|
|
572
|
+
writeStdout('āļø Skipping wake.');
|
|
590
573
|
}
|
|
591
574
|
}
|
|
592
575
|
}
|
|
593
576
|
if (shouldBlock) {
|
|
594
|
-
|
|
577
|
+
writeStdout('ā Strict mode: Exiting with error due to TIER 2+ conflicts.');
|
|
595
578
|
process.exit(1);
|
|
596
579
|
}
|
|
597
580
|
});
|