@vibecheckai/cli 3.2.6 → 3.3.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/bin/registry.js +192 -5
- package/bin/runners/lib/agent-firewall/change-packet/builder.js +280 -6
- package/bin/runners/lib/agent-firewall/critic/index.js +151 -0
- package/bin/runners/lib/agent-firewall/critic/judge.js +432 -0
- package/bin/runners/lib/agent-firewall/critic/prompts.js +305 -0
- package/bin/runners/lib/agent-firewall/lawbook/distributor.js +465 -0
- package/bin/runners/lib/agent-firewall/lawbook/evaluator.js +604 -0
- package/bin/runners/lib/agent-firewall/lawbook/index.js +304 -0
- package/bin/runners/lib/agent-firewall/lawbook/registry.js +514 -0
- package/bin/runners/lib/agent-firewall/lawbook/schema.js +420 -0
- package/bin/runners/lib/agent-firewall/logger.js +141 -0
- package/bin/runners/lib/agent-firewall/policy/loader.js +312 -4
- package/bin/runners/lib/agent-firewall/policy/rules/ghost-env.js +113 -1
- package/bin/runners/lib/agent-firewall/policy/rules/ghost-route.js +133 -6
- package/bin/runners/lib/agent-firewall/proposal/extractor.js +394 -0
- package/bin/runners/lib/agent-firewall/proposal/index.js +212 -0
- package/bin/runners/lib/agent-firewall/proposal/schema.js +251 -0
- package/bin/runners/lib/agent-firewall/proposal/validator.js +386 -0
- package/bin/runners/lib/agent-firewall/reality/index.js +332 -0
- package/bin/runners/lib/agent-firewall/reality/state.js +625 -0
- package/bin/runners/lib/agent-firewall/reality/watcher.js +322 -0
- package/bin/runners/lib/agent-firewall/risk/index.js +173 -0
- package/bin/runners/lib/agent-firewall/risk/scorer.js +328 -0
- package/bin/runners/lib/agent-firewall/risk/thresholds.js +321 -0
- package/bin/runners/lib/agent-firewall/risk/vectors.js +421 -0
- package/bin/runners/lib/agent-firewall/simulator/diff-simulator.js +472 -0
- package/bin/runners/lib/agent-firewall/simulator/import-resolver.js +346 -0
- package/bin/runners/lib/agent-firewall/simulator/index.js +181 -0
- package/bin/runners/lib/agent-firewall/simulator/route-validator.js +380 -0
- package/bin/runners/lib/agent-firewall/time-machine/incident-correlator.js +661 -0
- package/bin/runners/lib/agent-firewall/time-machine/index.js +267 -0
- package/bin/runners/lib/agent-firewall/time-machine/replay-engine.js +436 -0
- package/bin/runners/lib/agent-firewall/time-machine/state-reconstructor.js +490 -0
- package/bin/runners/lib/agent-firewall/time-machine/timeline-builder.js +530 -0
- package/bin/runners/lib/analyzers.js +81 -18
- package/bin/runners/lib/authority-badge.js +425 -0
- package/bin/runners/lib/cli-output.js +7 -1
- package/bin/runners/lib/error-handler.js +16 -9
- package/bin/runners/lib/exit-codes.js +275 -0
- package/bin/runners/lib/global-flags.js +37 -0
- package/bin/runners/lib/help-formatter.js +413 -0
- package/bin/runners/lib/logger.js +38 -0
- package/bin/runners/lib/unified-cli-output.js +604 -0
- package/bin/runners/lib/upsell.js +148 -0
- package/bin/runners/runApprove.js +1200 -0
- package/bin/runners/runAuth.js +324 -95
- package/bin/runners/runCheckpoint.js +39 -21
- package/bin/runners/runClassify.js +859 -0
- package/bin/runners/runContext.js +136 -24
- package/bin/runners/runDoctor.js +108 -68
- package/bin/runners/runFix.js +6 -5
- package/bin/runners/runGuard.js +212 -118
- package/bin/runners/runInit.js +3 -2
- package/bin/runners/runMcp.js +130 -52
- package/bin/runners/runPolish.js +43 -20
- package/bin/runners/runProve.js +1 -2
- package/bin/runners/runReport.js +3 -2
- package/bin/runners/runScan.js +63 -44
- package/bin/runners/runShip.js +3 -4
- package/bin/runners/runValidate.js +19 -2
- package/bin/runners/runWatch.js +104 -53
- package/bin/vibecheck.js +106 -19
- package/mcp-server/HARDENING_SUMMARY.md +299 -0
- package/mcp-server/agent-firewall-interceptor.js +367 -31
- package/mcp-server/authority-tools.js +569 -0
- package/mcp-server/conductor/conflict-resolver.js +588 -0
- package/mcp-server/conductor/execution-planner.js +544 -0
- package/mcp-server/conductor/index.js +377 -0
- package/mcp-server/conductor/lock-manager.js +615 -0
- package/mcp-server/conductor/request-queue.js +550 -0
- package/mcp-server/conductor/session-manager.js +500 -0
- package/mcp-server/conductor/tools.js +510 -0
- package/mcp-server/index.js +1149 -243
- package/mcp-server/lib/{api-client.js → api-client.cjs} +40 -4
- package/mcp-server/lib/logger.cjs +30 -0
- package/mcp-server/logger.js +173 -0
- package/mcp-server/package.json +2 -2
- package/mcp-server/premium-tools.js +2 -2
- package/mcp-server/tier-auth.js +245 -35
- package/mcp-server/truth-firewall-tools.js +145 -15
- package/mcp-server/vibecheck-tools.js +2 -2
- package/package.json +2 -3
- package/mcp-server/index.old.js +0 -4137
- package/mcp-server/package-lock.json +0 -165
|
@@ -0,0 +1,588 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Conductor Conflict Resolver
|
|
3
|
+
*
|
|
4
|
+
* Builds cross-agent assumption graphs and detects conflicts
|
|
5
|
+
* between multiple AI agents working on the same codebase.
|
|
6
|
+
*
|
|
7
|
+
* Codename: Conductor
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
"use strict";
|
|
11
|
+
|
|
12
|
+
import path from "path";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Conflict types
|
|
16
|
+
*/
|
|
17
|
+
const CONFLICT_TYPES = {
|
|
18
|
+
SAME_FILE: "same_file", // Multiple agents modifying same file
|
|
19
|
+
DEPENDENT_ASSUMPTION: "dependent_assumption", // Agent B depends on what A assumes
|
|
20
|
+
INCOMPATIBLE_OPERATION: "incompatible_operation", // Operations that can't coexist
|
|
21
|
+
RESOURCE_CONTENTION: "resource_contention", // Competing for same resource
|
|
22
|
+
ORDER_DEPENDENCY: "order_dependency", // A must complete before B
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Conflict severity levels
|
|
27
|
+
*/
|
|
28
|
+
const CONFLICT_SEVERITY = {
|
|
29
|
+
CRITICAL: "critical", // Must be resolved before proceeding
|
|
30
|
+
HIGH: "high", // Strongly recommended to resolve
|
|
31
|
+
MEDIUM: "medium", // Can proceed with caution
|
|
32
|
+
LOW: "low", // Advisory only
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Resolution strategies
|
|
37
|
+
*/
|
|
38
|
+
const RESOLUTION_STRATEGIES = {
|
|
39
|
+
QUEUE: "queue", // Queue operations sequentially
|
|
40
|
+
MERGE: "merge", // Attempt to merge operations
|
|
41
|
+
REJECT: "reject", // Reject conflicting operation
|
|
42
|
+
DEFER: "defer", // Defer to higher priority agent
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* @typedef {Object} Proposal
|
|
47
|
+
* @property {string} proposalId - Unique proposal ID
|
|
48
|
+
* @property {string} sessionId - Session that owns this proposal
|
|
49
|
+
* @property {string} agentId - Agent that created this proposal
|
|
50
|
+
* @property {string} intent - What the agent intends to do
|
|
51
|
+
* @property {Object[]} operations - List of operations
|
|
52
|
+
* @property {Object[]} assumptions - Assumptions made by the agent
|
|
53
|
+
* @property {Date} createdAt - When proposal was created
|
|
54
|
+
*/
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* @typedef {Object} Conflict
|
|
58
|
+
* @property {string} type - Conflict type
|
|
59
|
+
* @property {string} severity - Conflict severity
|
|
60
|
+
* @property {string} proposalA - First proposal ID
|
|
61
|
+
* @property {string} proposalB - Second proposal ID
|
|
62
|
+
* @property {string} description - Human-readable description
|
|
63
|
+
* @property {Object} details - Additional details
|
|
64
|
+
* @property {string[]} suggestedResolutions - Possible resolution strategies
|
|
65
|
+
*/
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* @typedef {Object} AssumptionNode
|
|
69
|
+
* @property {string} proposalId - Proposal making this assumption
|
|
70
|
+
* @property {string} type - Assumption type (file_exists, route_exists, etc.)
|
|
71
|
+
* @property {string} target - What the assumption is about
|
|
72
|
+
* @property {*} expectedValue - Expected state
|
|
73
|
+
*/
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Conflict Resolver class
|
|
77
|
+
*/
|
|
78
|
+
class ConflictResolver {
|
|
79
|
+
constructor() {
|
|
80
|
+
this.proposals = new Map(); // proposalId -> Proposal
|
|
81
|
+
this.assumptionGraph = new Map(); // target -> AssumptionNode[]
|
|
82
|
+
this.conflicts = [];
|
|
83
|
+
this.resolutions = new Map(); // conflictId -> Resolution
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Add a proposal for conflict analysis
|
|
88
|
+
* @param {Proposal} proposal - Proposal to add
|
|
89
|
+
*/
|
|
90
|
+
addProposal(proposal) {
|
|
91
|
+
this.proposals.set(proposal.proposalId, {
|
|
92
|
+
...proposal,
|
|
93
|
+
addedAt: new Date(),
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
// Index assumptions
|
|
97
|
+
this.indexAssumptions(proposal);
|
|
98
|
+
|
|
99
|
+
// Detect conflicts with existing proposals
|
|
100
|
+
this.detectConflicts(proposal);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Remove a proposal
|
|
105
|
+
* @param {string} proposalId - Proposal ID to remove
|
|
106
|
+
*/
|
|
107
|
+
removeProposal(proposalId) {
|
|
108
|
+
const proposal = this.proposals.get(proposalId);
|
|
109
|
+
if (!proposal) return;
|
|
110
|
+
|
|
111
|
+
// Remove from assumption graph
|
|
112
|
+
this.removeAssumptions(proposal);
|
|
113
|
+
|
|
114
|
+
// Remove conflicts involving this proposal
|
|
115
|
+
this.conflicts = this.conflicts.filter(
|
|
116
|
+
c => c.proposalA !== proposalId && c.proposalB !== proposalId
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
this.proposals.delete(proposalId);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Index a proposal's assumptions in the graph
|
|
124
|
+
* @param {Proposal} proposal - Proposal to index
|
|
125
|
+
*/
|
|
126
|
+
indexAssumptions(proposal) {
|
|
127
|
+
for (const assumption of proposal.assumptions || []) {
|
|
128
|
+
const target = this.normalizeTarget(assumption.target || assumption.path);
|
|
129
|
+
|
|
130
|
+
if (!this.assumptionGraph.has(target)) {
|
|
131
|
+
this.assumptionGraph.set(target, []);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
this.assumptionGraph.get(target).push({
|
|
135
|
+
proposalId: proposal.proposalId,
|
|
136
|
+
type: assumption.type,
|
|
137
|
+
target,
|
|
138
|
+
expectedValue: assumption.expectedValue || assumption.value,
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Remove a proposal's assumptions from the graph
|
|
145
|
+
* @param {Proposal} proposal - Proposal to remove
|
|
146
|
+
*/
|
|
147
|
+
removeAssumptions(proposal) {
|
|
148
|
+
for (const [target, nodes] of this.assumptionGraph) {
|
|
149
|
+
const filtered = nodes.filter(n => n.proposalId !== proposal.proposalId);
|
|
150
|
+
if (filtered.length === 0) {
|
|
151
|
+
this.assumptionGraph.delete(target);
|
|
152
|
+
} else {
|
|
153
|
+
this.assumptionGraph.set(target, filtered);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Normalize a target path/identifier
|
|
160
|
+
* @param {string} target - Target to normalize
|
|
161
|
+
* @returns {string} Normalized target
|
|
162
|
+
*/
|
|
163
|
+
normalizeTarget(target) {
|
|
164
|
+
if (!target) return "";
|
|
165
|
+
return path.resolve(target).replace(/\\/g, "/").toLowerCase();
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Detect conflicts between a new proposal and existing ones
|
|
170
|
+
* @param {Proposal} newProposal - New proposal to check
|
|
171
|
+
*/
|
|
172
|
+
detectConflicts(newProposal) {
|
|
173
|
+
for (const [proposalId, existingProposal] of this.proposals) {
|
|
174
|
+
if (proposalId === newProposal.proposalId) continue;
|
|
175
|
+
if (existingProposal.sessionId === newProposal.sessionId) continue;
|
|
176
|
+
|
|
177
|
+
// Check for same-file conflicts
|
|
178
|
+
const fileConflicts = this.detectSameFileConflicts(newProposal, existingProposal);
|
|
179
|
+
this.conflicts.push(...fileConflicts);
|
|
180
|
+
|
|
181
|
+
// Check for dependent assumption conflicts
|
|
182
|
+
const assumptionConflicts = this.detectAssumptionConflicts(newProposal, existingProposal);
|
|
183
|
+
this.conflicts.push(...assumptionConflicts);
|
|
184
|
+
|
|
185
|
+
// Check for incompatible operations
|
|
186
|
+
const operationConflicts = this.detectOperationConflicts(newProposal, existingProposal);
|
|
187
|
+
this.conflicts.push(...operationConflicts);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Detect same-file modification conflicts
|
|
193
|
+
* @param {Proposal} proposalA - First proposal
|
|
194
|
+
* @param {Proposal} proposalB - Second proposal
|
|
195
|
+
* @returns {Conflict[]} Detected conflicts
|
|
196
|
+
*/
|
|
197
|
+
detectSameFileConflicts(proposalA, proposalB) {
|
|
198
|
+
const conflicts = [];
|
|
199
|
+
|
|
200
|
+
const filesA = this.extractAffectedFiles(proposalA);
|
|
201
|
+
const filesB = this.extractAffectedFiles(proposalB);
|
|
202
|
+
|
|
203
|
+
// Find overlapping files
|
|
204
|
+
for (const fileA of filesA) {
|
|
205
|
+
const normalizedA = this.normalizeTarget(fileA.path);
|
|
206
|
+
|
|
207
|
+
for (const fileB of filesB) {
|
|
208
|
+
const normalizedB = this.normalizeTarget(fileB.path);
|
|
209
|
+
|
|
210
|
+
if (normalizedA === normalizedB) {
|
|
211
|
+
const conflict = {
|
|
212
|
+
id: `conflict_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
|
|
213
|
+
type: CONFLICT_TYPES.SAME_FILE,
|
|
214
|
+
severity: this.calculateFileSeverity(fileA, fileB),
|
|
215
|
+
proposalA: proposalA.proposalId,
|
|
216
|
+
proposalB: proposalB.proposalId,
|
|
217
|
+
description: `Both proposals modify ${fileA.path}`,
|
|
218
|
+
details: {
|
|
219
|
+
file: fileA.path,
|
|
220
|
+
operationA: fileA.operation,
|
|
221
|
+
operationB: fileB.operation,
|
|
222
|
+
},
|
|
223
|
+
suggestedResolutions: this.suggestResolutions(CONFLICT_TYPES.SAME_FILE, fileA, fileB),
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
conflicts.push(conflict);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
return conflicts;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Detect conflicts from dependent assumptions
|
|
236
|
+
* @param {Proposal} proposalA - First proposal
|
|
237
|
+
* @param {Proposal} proposalB - Second proposal
|
|
238
|
+
* @returns {Conflict[]} Detected conflicts
|
|
239
|
+
*/
|
|
240
|
+
detectAssumptionConflicts(proposalA, proposalB) {
|
|
241
|
+
const conflicts = [];
|
|
242
|
+
|
|
243
|
+
// Check if A's operations would invalidate B's assumptions
|
|
244
|
+
for (const opA of proposalA.operations || []) {
|
|
245
|
+
for (const assumptionB of proposalB.assumptions || []) {
|
|
246
|
+
if (this.operationInvalidatesAssumption(opA, assumptionB)) {
|
|
247
|
+
conflicts.push({
|
|
248
|
+
id: `conflict_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
|
|
249
|
+
type: CONFLICT_TYPES.DEPENDENT_ASSUMPTION,
|
|
250
|
+
severity: CONFLICT_SEVERITY.HIGH,
|
|
251
|
+
proposalA: proposalA.proposalId,
|
|
252
|
+
proposalB: proposalB.proposalId,
|
|
253
|
+
description: `${proposalA.agentId}'s changes would invalidate ${proposalB.agentId}'s assumption`,
|
|
254
|
+
details: {
|
|
255
|
+
operation: opA,
|
|
256
|
+
assumption: assumptionB,
|
|
257
|
+
},
|
|
258
|
+
suggestedResolutions: [RESOLUTION_STRATEGIES.QUEUE, RESOLUTION_STRATEGIES.REJECT],
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Check the reverse
|
|
265
|
+
for (const opB of proposalB.operations || []) {
|
|
266
|
+
for (const assumptionA of proposalA.assumptions || []) {
|
|
267
|
+
if (this.operationInvalidatesAssumption(opB, assumptionA)) {
|
|
268
|
+
conflicts.push({
|
|
269
|
+
id: `conflict_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
|
|
270
|
+
type: CONFLICT_TYPES.DEPENDENT_ASSUMPTION,
|
|
271
|
+
severity: CONFLICT_SEVERITY.HIGH,
|
|
272
|
+
proposalA: proposalB.proposalId,
|
|
273
|
+
proposalB: proposalA.proposalId,
|
|
274
|
+
description: `${proposalB.agentId}'s changes would invalidate ${proposalA.agentId}'s assumption`,
|
|
275
|
+
details: {
|
|
276
|
+
operation: opB,
|
|
277
|
+
assumption: assumptionA,
|
|
278
|
+
},
|
|
279
|
+
suggestedResolutions: [RESOLUTION_STRATEGIES.QUEUE, RESOLUTION_STRATEGIES.REJECT],
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
return conflicts;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Detect incompatible operation conflicts
|
|
290
|
+
* @param {Proposal} proposalA - First proposal
|
|
291
|
+
* @param {Proposal} proposalB - Second proposal
|
|
292
|
+
* @returns {Conflict[]} Detected conflicts
|
|
293
|
+
*/
|
|
294
|
+
detectOperationConflicts(proposalA, proposalB) {
|
|
295
|
+
const conflicts = [];
|
|
296
|
+
|
|
297
|
+
for (const opA of proposalA.operations || []) {
|
|
298
|
+
for (const opB of proposalB.operations || []) {
|
|
299
|
+
if (this.operationsIncompatible(opA, opB)) {
|
|
300
|
+
conflicts.push({
|
|
301
|
+
id: `conflict_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
|
|
302
|
+
type: CONFLICT_TYPES.INCOMPATIBLE_OPERATION,
|
|
303
|
+
severity: CONFLICT_SEVERITY.CRITICAL,
|
|
304
|
+
proposalA: proposalA.proposalId,
|
|
305
|
+
proposalB: proposalB.proposalId,
|
|
306
|
+
description: "Operations cannot coexist",
|
|
307
|
+
details: {
|
|
308
|
+
operationA: opA,
|
|
309
|
+
operationB: opB,
|
|
310
|
+
reason: this.getIncompatibilityReason(opA, opB),
|
|
311
|
+
},
|
|
312
|
+
suggestedResolutions: [RESOLUTION_STRATEGIES.REJECT, RESOLUTION_STRATEGIES.DEFER],
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
return conflicts;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Extract affected files from a proposal
|
|
323
|
+
* @param {Proposal} proposal - Proposal to analyze
|
|
324
|
+
* @returns {Object[]} Affected files with operations
|
|
325
|
+
*/
|
|
326
|
+
extractAffectedFiles(proposal) {
|
|
327
|
+
const files = [];
|
|
328
|
+
|
|
329
|
+
for (const op of proposal.operations || []) {
|
|
330
|
+
if (op.path || op.file || op.filePath) {
|
|
331
|
+
files.push({
|
|
332
|
+
path: op.path || op.file || op.filePath,
|
|
333
|
+
operation: op.type || op.operation || "modify",
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
return files;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* Calculate severity for file conflicts
|
|
343
|
+
* @param {Object} fileA - First file info
|
|
344
|
+
* @param {Object} fileB - Second file info
|
|
345
|
+
* @returns {string} Severity level
|
|
346
|
+
*/
|
|
347
|
+
calculateFileSeverity(fileA, fileB) {
|
|
348
|
+
// Both deleting = critical
|
|
349
|
+
if (fileA.operation === "delete" && fileB.operation === "delete") {
|
|
350
|
+
return CONFLICT_SEVERITY.LOW; // Actually fine, same result
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// One deleting, one modifying = critical
|
|
354
|
+
if (fileA.operation === "delete" || fileB.operation === "delete") {
|
|
355
|
+
return CONFLICT_SEVERITY.CRITICAL;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// Both creating = critical
|
|
359
|
+
if (fileA.operation === "create" && fileB.operation === "create") {
|
|
360
|
+
return CONFLICT_SEVERITY.CRITICAL;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// Both modifying = high
|
|
364
|
+
return CONFLICT_SEVERITY.HIGH;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* Check if an operation invalidates an assumption
|
|
369
|
+
* @param {Object} operation - Operation to check
|
|
370
|
+
* @param {Object} assumption - Assumption to validate against
|
|
371
|
+
* @returns {boolean} Does operation invalidate assumption
|
|
372
|
+
*/
|
|
373
|
+
operationInvalidatesAssumption(operation, assumption) {
|
|
374
|
+
const opPath = this.normalizeTarget(operation.path || operation.file);
|
|
375
|
+
const assumptionTarget = this.normalizeTarget(assumption.target || assumption.path);
|
|
376
|
+
|
|
377
|
+
// File deletion invalidates file_exists assumption
|
|
378
|
+
if (operation.type === "delete" && assumption.type === "file_exists" && opPath === assumptionTarget) {
|
|
379
|
+
return true;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// File modification might invalidate content assumptions
|
|
383
|
+
if (operation.type === "modify" && assumption.type === "file_contains" && opPath === assumptionTarget) {
|
|
384
|
+
return true;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// Route changes
|
|
388
|
+
if (assumption.type === "route_exists" && operation.type === "delete") {
|
|
389
|
+
// Check if deleting a route handler file
|
|
390
|
+
if (opPath.includes("routes") || opPath.includes("api")) {
|
|
391
|
+
return true;
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
return false;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* Check if two operations are incompatible
|
|
400
|
+
* @param {Object} opA - First operation
|
|
401
|
+
* @param {Object} opB - Second operation
|
|
402
|
+
* @returns {boolean} Are operations incompatible
|
|
403
|
+
*/
|
|
404
|
+
operationsIncompatible(opA, opB) {
|
|
405
|
+
const pathA = this.normalizeTarget(opA.path || opA.file);
|
|
406
|
+
const pathB = this.normalizeTarget(opB.path || opB.file);
|
|
407
|
+
|
|
408
|
+
if (pathA !== pathB) return false;
|
|
409
|
+
|
|
410
|
+
// Create + Create = incompatible (which one wins?)
|
|
411
|
+
if (opA.type === "create" && opB.type === "create") {
|
|
412
|
+
return true;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
// Delete + Modify = incompatible
|
|
416
|
+
if ((opA.type === "delete" && opB.type === "modify") ||
|
|
417
|
+
(opA.type === "modify" && opB.type === "delete")) {
|
|
418
|
+
return true;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
// Delete + Create might be rename - allow
|
|
422
|
+
// Modify + Modify might be mergeable - allow (handle at merge time)
|
|
423
|
+
|
|
424
|
+
return false;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
/**
|
|
428
|
+
* Get reason for operation incompatibility
|
|
429
|
+
* @param {Object} opA - First operation
|
|
430
|
+
* @param {Object} opB - Second operation
|
|
431
|
+
* @returns {string} Reason description
|
|
432
|
+
*/
|
|
433
|
+
getIncompatibilityReason(opA, opB) {
|
|
434
|
+
if (opA.type === "create" && opB.type === "create") {
|
|
435
|
+
return "Both agents trying to create the same file";
|
|
436
|
+
}
|
|
437
|
+
if (opA.type === "delete" && opB.type === "modify") {
|
|
438
|
+
return "One agent deleting while another modifies";
|
|
439
|
+
}
|
|
440
|
+
if (opA.type === "modify" && opB.type === "delete") {
|
|
441
|
+
return "One agent modifying while another deletes";
|
|
442
|
+
}
|
|
443
|
+
return "Operations cannot be applied together";
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
/**
|
|
447
|
+
* Suggest resolution strategies for a conflict
|
|
448
|
+
* @param {string} conflictType - Type of conflict
|
|
449
|
+
* @param {Object} contextA - First context
|
|
450
|
+
* @param {Object} contextB - Second context
|
|
451
|
+
* @returns {string[]} Suggested strategies
|
|
452
|
+
*/
|
|
453
|
+
suggestResolutions(conflictType, contextA, contextB) {
|
|
454
|
+
switch (conflictType) {
|
|
455
|
+
case CONFLICT_TYPES.SAME_FILE:
|
|
456
|
+
if (contextA.operation === "modify" && contextB.operation === "modify") {
|
|
457
|
+
return [RESOLUTION_STRATEGIES.MERGE, RESOLUTION_STRATEGIES.QUEUE];
|
|
458
|
+
}
|
|
459
|
+
return [RESOLUTION_STRATEGIES.QUEUE, RESOLUTION_STRATEGIES.REJECT];
|
|
460
|
+
|
|
461
|
+
case CONFLICT_TYPES.DEPENDENT_ASSUMPTION:
|
|
462
|
+
return [RESOLUTION_STRATEGIES.QUEUE, RESOLUTION_STRATEGIES.REJECT];
|
|
463
|
+
|
|
464
|
+
case CONFLICT_TYPES.INCOMPATIBLE_OPERATION:
|
|
465
|
+
return [RESOLUTION_STRATEGIES.REJECT, RESOLUTION_STRATEGIES.DEFER];
|
|
466
|
+
|
|
467
|
+
default:
|
|
468
|
+
return [RESOLUTION_STRATEGIES.QUEUE];
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
/**
|
|
473
|
+
* Get all conflicts for a proposal
|
|
474
|
+
* @param {string} proposalId - Proposal ID
|
|
475
|
+
* @returns {Conflict[]} Conflicts
|
|
476
|
+
*/
|
|
477
|
+
getConflictsForProposal(proposalId) {
|
|
478
|
+
return this.conflicts.filter(
|
|
479
|
+
c => c.proposalA === proposalId || c.proposalB === proposalId
|
|
480
|
+
);
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
/**
|
|
484
|
+
* Get all unresolved conflicts
|
|
485
|
+
* @returns {Conflict[]} Unresolved conflicts
|
|
486
|
+
*/
|
|
487
|
+
getUnresolvedConflicts() {
|
|
488
|
+
return this.conflicts.filter(c => !this.resolutions.has(c.id));
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
/**
|
|
492
|
+
* Resolve a conflict
|
|
493
|
+
* @param {string} conflictId - Conflict ID
|
|
494
|
+
* @param {string} strategy - Resolution strategy
|
|
495
|
+
* @param {Object} details - Resolution details
|
|
496
|
+
*/
|
|
497
|
+
resolveConflict(conflictId, strategy, details = {}) {
|
|
498
|
+
this.resolutions.set(conflictId, {
|
|
499
|
+
strategy,
|
|
500
|
+
details,
|
|
501
|
+
resolvedAt: new Date(),
|
|
502
|
+
});
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
/**
|
|
506
|
+
* Check if a proposal can proceed
|
|
507
|
+
* @param {string} proposalId - Proposal ID
|
|
508
|
+
* @returns {Object} Can proceed result
|
|
509
|
+
*/
|
|
510
|
+
canProceed(proposalId) {
|
|
511
|
+
const conflicts = this.getConflictsForProposal(proposalId);
|
|
512
|
+
|
|
513
|
+
if (conflicts.length === 0) {
|
|
514
|
+
return { canProceed: true, conflicts: [] };
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
const unresolvedCritical = conflicts.filter(
|
|
518
|
+
c => !this.resolutions.has(c.id) && c.severity === CONFLICT_SEVERITY.CRITICAL
|
|
519
|
+
);
|
|
520
|
+
|
|
521
|
+
const unresolvedHigh = conflicts.filter(
|
|
522
|
+
c => !this.resolutions.has(c.id) && c.severity === CONFLICT_SEVERITY.HIGH
|
|
523
|
+
);
|
|
524
|
+
|
|
525
|
+
return {
|
|
526
|
+
canProceed: unresolvedCritical.length === 0,
|
|
527
|
+
mustResolve: unresolvedCritical,
|
|
528
|
+
shouldResolve: unresolvedHigh,
|
|
529
|
+
conflicts,
|
|
530
|
+
};
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
/**
|
|
534
|
+
* Build the full conflict graph
|
|
535
|
+
* @returns {Object} Conflict graph representation
|
|
536
|
+
*/
|
|
537
|
+
buildConflictGraph() {
|
|
538
|
+
const nodes = [];
|
|
539
|
+
const edges = [];
|
|
540
|
+
|
|
541
|
+
for (const [proposalId, proposal] of this.proposals) {
|
|
542
|
+
nodes.push({
|
|
543
|
+
id: proposalId,
|
|
544
|
+
agentId: proposal.agentId,
|
|
545
|
+
sessionId: proposal.sessionId,
|
|
546
|
+
intent: proposal.intent,
|
|
547
|
+
});
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
for (const conflict of this.conflicts) {
|
|
551
|
+
edges.push({
|
|
552
|
+
source: conflict.proposalA,
|
|
553
|
+
target: conflict.proposalB,
|
|
554
|
+
type: conflict.type,
|
|
555
|
+
severity: conflict.severity,
|
|
556
|
+
resolved: this.resolutions.has(conflict.id),
|
|
557
|
+
});
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
return { nodes, edges };
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
/**
|
|
564
|
+
* Clear all proposals and conflicts
|
|
565
|
+
*/
|
|
566
|
+
clear() {
|
|
567
|
+
this.proposals.clear();
|
|
568
|
+
this.assumptionGraph.clear();
|
|
569
|
+
this.conflicts = [];
|
|
570
|
+
this.resolutions.clear();
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
/**
|
|
575
|
+
* Create a new conflict resolver
|
|
576
|
+
* @returns {ConflictResolver} New resolver
|
|
577
|
+
*/
|
|
578
|
+
function createConflictResolver() {
|
|
579
|
+
return new ConflictResolver();
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
export {
|
|
583
|
+
ConflictResolver,
|
|
584
|
+
createConflictResolver,
|
|
585
|
+
CONFLICT_TYPES,
|
|
586
|
+
CONFLICT_SEVERITY,
|
|
587
|
+
RESOLUTION_STRATEGIES,
|
|
588
|
+
};
|