@stackmemoryai/stackmemory 1.10.4 → 1.12.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/README.md +104 -23
- package/dist/src/cli/claude-sm.js +266 -84
- package/dist/src/cli/codex-sm.js +185 -33
- package/dist/src/cli/commands/bench.js +209 -2
- package/dist/src/cli/commands/cache.js +126 -0
- package/dist/src/cli/commands/daemon.js +41 -0
- package/dist/src/cli/commands/handoff.js +40 -9
- package/dist/src/cli/commands/onboard.js +70 -3
- package/dist/src/cli/commands/optimize.js +117 -0
- package/dist/src/cli/commands/orchestrate.js +230 -5
- package/dist/src/cli/commands/orchestrator.js +312 -24
- package/dist/src/cli/commands/pack.js +322 -0
- package/dist/src/cli/commands/search.js +40 -1
- package/dist/src/cli/commands/setup.js +177 -7
- package/dist/src/cli/commands/skills.js +10 -1
- package/dist/src/cli/commands/state.js +265 -0
- package/dist/src/cli/commands/wiki.js +33 -0
- package/dist/src/cli/gemini-sm.js +19 -29
- package/dist/src/cli/index.js +90 -29
- package/dist/src/cli/opencode-sm.js +38 -21
- package/dist/src/cli/utils/determinism-watcher.js +66 -0
- package/dist/src/cli/utils/real-cli-bin.js +44 -0
- package/dist/src/core/cache/content-cache.js +238 -0
- package/dist/src/core/cache/index.js +11 -0
- package/dist/src/core/cache/token-estimator.js +16 -0
- package/dist/src/core/context/frame-database.js +38 -30
- package/dist/src/core/cross-search/cross-project-search.js +269 -0
- package/dist/src/core/{merge → cross-search}/index.js +6 -4
- package/dist/src/core/database/sqlite-adapter.js +0 -83
- package/dist/src/core/extensions/provider-adapter.js +5 -0
- package/dist/src/core/models/model-router.js +22 -2
- package/dist/src/core/monitoring/logger.js +2 -1
- package/dist/src/core/optimization/trace-optimizer.js +413 -0
- package/dist/src/core/provenance/confidence-scorer.js +128 -0
- package/dist/src/core/provenance/index.js +40 -0
- package/dist/src/core/provenance/provenance-store.js +194 -0
- package/dist/src/core/provenance/types.js +82 -0
- package/dist/src/core/session/project-handoff.js +64 -0
- package/dist/src/core/session/session-manager.js +28 -0
- package/dist/src/core/shared-state/canonical-store.js +564 -0
- package/dist/src/core/skill-packs/index.js +18 -0
- package/dist/src/core/skill-packs/parser.js +42 -0
- package/dist/src/core/skill-packs/registry.js +224 -0
- package/dist/src/core/skill-packs/types.js +66 -0
- package/dist/src/core/trace/trace-event-store.js +282 -0
- package/dist/src/core/trace/trace-event.js +4 -0
- package/dist/src/core/wiki/wiki-compiler.js +219 -0
- package/dist/src/daemon/daemon-config.js +7 -0
- package/dist/src/daemon/services/github-service.js +126 -0
- package/dist/src/daemon/unified-daemon.js +30 -0
- package/dist/src/features/sweep/pty-wrapper.js +13 -5
- package/dist/src/hooks/schemas.js +2 -0
- package/dist/src/integrations/claude-code/subagent-client.js +89 -0
- package/dist/src/integrations/github/pr-state.js +158 -0
- package/dist/src/integrations/linear/client.js +4 -1
- package/dist/src/integrations/mcp/handlers/cross-search-handlers.js +188 -0
- package/dist/src/integrations/mcp/handlers/index.js +40 -59
- package/dist/src/integrations/mcp/server.js +425 -311
- package/dist/src/integrations/mcp/tool-alias-registry.js +370 -0
- package/dist/src/integrations/mcp/tool-definitions.js +98 -229
- package/dist/src/integrations/ralph/context/stackmemory-context-loader.js +3 -40
- package/dist/src/integrations/ralph/learning/pattern-learner.js +1 -20
- package/dist/src/integrations/ralph/swarm/swarm-coordinator.js +0 -2
- package/dist/src/mcp/stackmemory-mcp-server.js +315 -0
- package/dist/src/orchestrators/multimodal/determinism.js +243 -0
- package/dist/src/orchestrators/multimodal/harness.js +147 -77
- package/dist/src/orchestrators/multimodal/providers.js +44 -3
- package/dist/src/utils/hook-installer.js +8 -8
- package/package.json +10 -1
- package/packs/coding/python-fastapi/instructions.md +60 -0
- package/packs/coding/python-fastapi/pack.yaml +28 -0
- package/packs/coding/typescript-react/instructions.md +47 -0
- package/packs/coding/typescript-react/pack.yaml +28 -0
- package/packs/core/commands/capture.md +32 -0
- package/packs/core/commands/learn.md +73 -0
- package/packs/core/commands/next.md +36 -0
- package/packs/core/commands/restart.md +58 -0
- package/packs/core/commands/restore.md +29 -0
- package/packs/core/commands/start.md +57 -0
- package/packs/core/commands/stop.md +65 -0
- package/packs/core/commands/summary.md +40 -0
- package/packs/core/manifest.json +24 -0
- package/packs/ops/decision-recovery/instructions.md +65 -0
- package/packs/ops/decision-recovery/pack.yaml +89 -0
- package/templates/claude-hooks/doc-ingest.js +76 -0
- package/dist/src/cli/commands/team.js +0 -168
- package/dist/src/core/context/shared-context-layer.js +0 -620
- package/dist/src/core/context/stack-merge-resolver.js +0 -748
- package/dist/src/core/merge/conflict-detector.js +0 -430
- package/dist/src/core/merge/resolution-engine.js +0 -557
- package/dist/src/core/merge/stack-diff.js +0 -531
- package/dist/src/core/merge/unified-merge-resolver.js +0 -302
- package/dist/src/integrations/mcp/handlers/cord-handlers.js +0 -397
- package/dist/src/integrations/mcp/handlers/team-handlers.js +0 -211
- /package/dist/src/core/{merge → cache}/types.js +0 -0
|
@@ -1,748 +0,0 @@
|
|
|
1
|
-
import { fileURLToPath as __fileURLToPath } from 'url';
|
|
2
|
-
import { dirname as __pathDirname } from 'path';
|
|
3
|
-
const __filename = __fileURLToPath(import.meta.url);
|
|
4
|
-
const __dirname = __pathDirname(__filename);
|
|
5
|
-
import { logger } from "../monitoring/logger.js";
|
|
6
|
-
import { ValidationError, DatabaseError, ErrorCode } from "../errors/index.js";
|
|
7
|
-
import {
|
|
8
|
-
validateInput,
|
|
9
|
-
StartMergeSessionSchema,
|
|
10
|
-
CreateMergePolicySchema,
|
|
11
|
-
ConflictResolutionSchema
|
|
12
|
-
} from "./validation.js";
|
|
13
|
-
class StackMergeResolver {
|
|
14
|
-
dualStackManager;
|
|
15
|
-
activeSessions = /* @__PURE__ */ new Map();
|
|
16
|
-
mergePolicies = /* @__PURE__ */ new Map();
|
|
17
|
-
constructor(dualStackManager) {
|
|
18
|
-
this.dualStackManager = dualStackManager;
|
|
19
|
-
this.initializeDefaultPolicies();
|
|
20
|
-
logger.debug("StackMergeResolver initialized", {
|
|
21
|
-
policies: Array.from(this.mergePolicies.keys())
|
|
22
|
-
});
|
|
23
|
-
}
|
|
24
|
-
/**
|
|
25
|
-
* Start a merge session with conflict analysis
|
|
26
|
-
*/
|
|
27
|
-
async startMergeSession(sourceStackId, targetStackId, frameIds, policyName = "default") {
|
|
28
|
-
const input = validateInput(StartMergeSessionSchema, {
|
|
29
|
-
sourceStackId,
|
|
30
|
-
targetStackId,
|
|
31
|
-
frameIds,
|
|
32
|
-
policyName
|
|
33
|
-
});
|
|
34
|
-
const sessionId = `merge-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
35
|
-
logger.debug("Looking for merge policy", {
|
|
36
|
-
policyName: input.policyName,
|
|
37
|
-
availablePolicies: Array.from(this.mergePolicies.keys())
|
|
38
|
-
});
|
|
39
|
-
const policy = this.mergePolicies.get(input.policyName);
|
|
40
|
-
if (!policy) {
|
|
41
|
-
logger.error("Merge policy not found", {
|
|
42
|
-
requested: input.policyName,
|
|
43
|
-
available: Array.from(this.mergePolicies.keys())
|
|
44
|
-
});
|
|
45
|
-
throw new ValidationError(
|
|
46
|
-
`Merge policy not found: ${input.policyName}`,
|
|
47
|
-
ErrorCode.RESOURCE_NOT_FOUND
|
|
48
|
-
);
|
|
49
|
-
}
|
|
50
|
-
try {
|
|
51
|
-
const currentUserId = this.dualStackManager.getCurrentContext().ownerId || "unknown";
|
|
52
|
-
await this.dualStackManager.getPermissionManager().enforcePermission(
|
|
53
|
-
this.dualStackManager.getPermissionManager().createContext(currentUserId, "merge", "stack", input.sourceStackId)
|
|
54
|
-
);
|
|
55
|
-
await this.dualStackManager.getPermissionManager().enforcePermission(
|
|
56
|
-
this.dualStackManager.getPermissionManager().createContext(currentUserId, "merge", "stack", input.targetStackId)
|
|
57
|
-
);
|
|
58
|
-
const session = {
|
|
59
|
-
sessionId,
|
|
60
|
-
sourceStackId: input.sourceStackId,
|
|
61
|
-
targetStackId: input.targetStackId,
|
|
62
|
-
conflicts: [],
|
|
63
|
-
resolutions: [],
|
|
64
|
-
policy,
|
|
65
|
-
status: "analyzing",
|
|
66
|
-
startedAt: /* @__PURE__ */ new Date(),
|
|
67
|
-
metadata: {
|
|
68
|
-
totalFrames: 0,
|
|
69
|
-
conflictFrames: 0,
|
|
70
|
-
autoResolvedConflicts: 0,
|
|
71
|
-
manualResolvedConflicts: 0
|
|
72
|
-
}
|
|
73
|
-
};
|
|
74
|
-
this.activeSessions.set(sessionId, session);
|
|
75
|
-
await this.analyzeConflicts(sessionId, frameIds);
|
|
76
|
-
await this.autoResolveConflicts(sessionId);
|
|
77
|
-
logger.info(`Merge session started: ${sessionId}`, {
|
|
78
|
-
sourceStack: sourceStackId,
|
|
79
|
-
targetStack: targetStackId,
|
|
80
|
-
conflicts: session.conflicts.length,
|
|
81
|
-
policy: policyName
|
|
82
|
-
});
|
|
83
|
-
return sessionId;
|
|
84
|
-
} catch (error) {
|
|
85
|
-
logger.error("Failed to start merge session", {
|
|
86
|
-
error: error instanceof Error ? error.message : error,
|
|
87
|
-
sourceStackId: input.sourceStackId,
|
|
88
|
-
targetStackId: input.targetStackId,
|
|
89
|
-
policyName: input.policyName
|
|
90
|
-
});
|
|
91
|
-
throw new DatabaseError(
|
|
92
|
-
"Failed to start merge session",
|
|
93
|
-
ErrorCode.OPERATION_FAILED,
|
|
94
|
-
{ sourceStackId, targetStackId },
|
|
95
|
-
error instanceof Error ? error : void 0
|
|
96
|
-
);
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
/**
|
|
100
|
-
* Analyze conflicts between source and target stacks
|
|
101
|
-
*/
|
|
102
|
-
async analyzeConflicts(sessionId, frameIds) {
|
|
103
|
-
const session = this.activeSessions.get(sessionId);
|
|
104
|
-
if (!session) {
|
|
105
|
-
throw new DatabaseError(
|
|
106
|
-
`Merge session not found: ${sessionId}`,
|
|
107
|
-
ErrorCode.RESOURCE_NOT_FOUND
|
|
108
|
-
);
|
|
109
|
-
}
|
|
110
|
-
try {
|
|
111
|
-
const sourceStack = this.getStackManager(session.sourceStackId);
|
|
112
|
-
const targetStack = this.getStackManager(session.targetStackId);
|
|
113
|
-
const framesToAnalyze = frameIds || (await sourceStack.getActiveFrames()).map((f) => f.frame_id);
|
|
114
|
-
session.metadata.totalFrames = framesToAnalyze.length;
|
|
115
|
-
for (const frameId of framesToAnalyze) {
|
|
116
|
-
const sourceFrame = await sourceStack.getFrame(frameId);
|
|
117
|
-
if (!sourceFrame) continue;
|
|
118
|
-
const targetFrame = await targetStack.getFrame(frameId);
|
|
119
|
-
if (!targetFrame) continue;
|
|
120
|
-
const conflicts = await this.analyzeFrameConflicts(
|
|
121
|
-
sourceFrame,
|
|
122
|
-
targetFrame
|
|
123
|
-
);
|
|
124
|
-
session.conflicts.push(...conflicts);
|
|
125
|
-
}
|
|
126
|
-
session.metadata.conflictFrames = new Set(
|
|
127
|
-
session.conflicts.map((c) => c.frameId)
|
|
128
|
-
).size;
|
|
129
|
-
session.status = "resolving";
|
|
130
|
-
this.activeSessions.set(sessionId, session);
|
|
131
|
-
logger.info(`Conflict analysis completed: ${sessionId}`, {
|
|
132
|
-
totalConflicts: session.conflicts.length,
|
|
133
|
-
conflictFrames: session.metadata.conflictFrames
|
|
134
|
-
});
|
|
135
|
-
} catch (error) {
|
|
136
|
-
session.status = "failed";
|
|
137
|
-
this.activeSessions.set(sessionId, session);
|
|
138
|
-
throw error;
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
/**
|
|
142
|
-
* Analyze conflicts within a single frame
|
|
143
|
-
*/
|
|
144
|
-
async analyzeFrameConflicts(sourceFrame, targetFrame) {
|
|
145
|
-
const conflicts = [];
|
|
146
|
-
if (sourceFrame.name !== targetFrame.name) {
|
|
147
|
-
conflicts.push({
|
|
148
|
-
frameId: sourceFrame.frame_id,
|
|
149
|
-
conflictType: "content",
|
|
150
|
-
sourceFrame,
|
|
151
|
-
targetFrame,
|
|
152
|
-
conflictDetails: [
|
|
153
|
-
{
|
|
154
|
-
field: "name",
|
|
155
|
-
sourceValue: sourceFrame.name,
|
|
156
|
-
targetValue: targetFrame.name,
|
|
157
|
-
lastModified: {
|
|
158
|
-
source: new Date(sourceFrame.created_at * 1e3),
|
|
159
|
-
target: new Date(targetFrame.created_at * 1e3)
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
],
|
|
163
|
-
severity: "medium",
|
|
164
|
-
autoResolvable: false
|
|
165
|
-
});
|
|
166
|
-
}
|
|
167
|
-
if (sourceFrame.state !== targetFrame.state) {
|
|
168
|
-
conflicts.push({
|
|
169
|
-
frameId: sourceFrame.frame_id,
|
|
170
|
-
conflictType: "metadata",
|
|
171
|
-
sourceFrame,
|
|
172
|
-
targetFrame,
|
|
173
|
-
conflictDetails: [
|
|
174
|
-
{
|
|
175
|
-
field: "state",
|
|
176
|
-
sourceValue: sourceFrame.state,
|
|
177
|
-
targetValue: targetFrame.state,
|
|
178
|
-
lastModified: {
|
|
179
|
-
source: new Date(sourceFrame.created_at * 1e3),
|
|
180
|
-
target: new Date(targetFrame.created_at * 1e3)
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
],
|
|
184
|
-
severity: "high",
|
|
185
|
-
autoResolvable: true
|
|
186
|
-
// Can auto-resolve based on timestamps
|
|
187
|
-
});
|
|
188
|
-
}
|
|
189
|
-
if (JSON.stringify(sourceFrame.inputs) !== JSON.stringify(targetFrame.inputs)) {
|
|
190
|
-
conflicts.push({
|
|
191
|
-
frameId: sourceFrame.frame_id,
|
|
192
|
-
conflictType: "content",
|
|
193
|
-
sourceFrame,
|
|
194
|
-
targetFrame,
|
|
195
|
-
conflictDetails: [
|
|
196
|
-
{
|
|
197
|
-
field: "inputs",
|
|
198
|
-
sourceValue: sourceFrame.inputs,
|
|
199
|
-
targetValue: targetFrame.inputs,
|
|
200
|
-
lastModified: {
|
|
201
|
-
source: new Date(sourceFrame.created_at * 1e3),
|
|
202
|
-
target: new Date(targetFrame.created_at * 1e3)
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
],
|
|
206
|
-
severity: "medium",
|
|
207
|
-
autoResolvable: false
|
|
208
|
-
});
|
|
209
|
-
}
|
|
210
|
-
const eventConflicts = await this.analyzeEventConflicts(
|
|
211
|
-
sourceFrame,
|
|
212
|
-
targetFrame
|
|
213
|
-
);
|
|
214
|
-
conflicts.push(...eventConflicts);
|
|
215
|
-
const anchorConflicts = await this.analyzeAnchorConflicts(
|
|
216
|
-
sourceFrame,
|
|
217
|
-
targetFrame
|
|
218
|
-
);
|
|
219
|
-
conflicts.push(...anchorConflicts);
|
|
220
|
-
return conflicts;
|
|
221
|
-
}
|
|
222
|
-
/**
|
|
223
|
-
* Analyze conflicts in frame events
|
|
224
|
-
*/
|
|
225
|
-
async analyzeEventConflicts(sourceFrame, targetFrame) {
|
|
226
|
-
const conflicts = [];
|
|
227
|
-
try {
|
|
228
|
-
const sourceStack = this.getStackManager(sourceFrame.project_id);
|
|
229
|
-
const targetStack = this.getStackManager(targetFrame.project_id);
|
|
230
|
-
const sourceEvents = await sourceStack.getFrameEvents(
|
|
231
|
-
sourceFrame.frame_id
|
|
232
|
-
);
|
|
233
|
-
const targetEvents = await targetStack.getFrameEvents(
|
|
234
|
-
targetFrame.frame_id
|
|
235
|
-
);
|
|
236
|
-
if (sourceEvents.length !== targetEvents.length) {
|
|
237
|
-
conflicts.push({
|
|
238
|
-
frameId: sourceFrame.frame_id,
|
|
239
|
-
conflictType: "sequence",
|
|
240
|
-
sourceFrame,
|
|
241
|
-
targetFrame,
|
|
242
|
-
conflictDetails: [
|
|
243
|
-
{
|
|
244
|
-
field: "event_count",
|
|
245
|
-
sourceValue: sourceEvents.length,
|
|
246
|
-
targetValue: targetEvents.length,
|
|
247
|
-
lastModified: {
|
|
248
|
-
source: /* @__PURE__ */ new Date(),
|
|
249
|
-
target: /* @__PURE__ */ new Date()
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
],
|
|
253
|
-
severity: "high",
|
|
254
|
-
autoResolvable: true
|
|
255
|
-
// Can merge events
|
|
256
|
-
});
|
|
257
|
-
}
|
|
258
|
-
const minLength = Math.min(sourceEvents.length, targetEvents.length);
|
|
259
|
-
for (let i = 0; i < minLength; i++) {
|
|
260
|
-
const sourceEvent = sourceEvents[i];
|
|
261
|
-
const targetEvent = targetEvents[i];
|
|
262
|
-
if (sourceEvent.text !== targetEvent.text || JSON.stringify(sourceEvent.metadata) !== JSON.stringify(targetEvent.metadata)) {
|
|
263
|
-
conflicts.push({
|
|
264
|
-
frameId: sourceFrame.frame_id,
|
|
265
|
-
conflictType: "content",
|
|
266
|
-
sourceFrame,
|
|
267
|
-
targetFrame,
|
|
268
|
-
conflictDetails: [
|
|
269
|
-
{
|
|
270
|
-
field: `event_${i}`,
|
|
271
|
-
sourceValue: {
|
|
272
|
-
text: sourceEvent.text,
|
|
273
|
-
metadata: sourceEvent.metadata
|
|
274
|
-
},
|
|
275
|
-
targetValue: {
|
|
276
|
-
text: targetEvent.text,
|
|
277
|
-
metadata: targetEvent.metadata
|
|
278
|
-
},
|
|
279
|
-
lastModified: {
|
|
280
|
-
source: /* @__PURE__ */ new Date(),
|
|
281
|
-
target: /* @__PURE__ */ new Date()
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
],
|
|
285
|
-
severity: "medium",
|
|
286
|
-
autoResolvable: false
|
|
287
|
-
});
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
} catch (error) {
|
|
291
|
-
logger.warn(
|
|
292
|
-
`Failed to analyze event conflicts for frame: ${sourceFrame.frame_id}`,
|
|
293
|
-
error
|
|
294
|
-
);
|
|
295
|
-
}
|
|
296
|
-
return conflicts;
|
|
297
|
-
}
|
|
298
|
-
/**
|
|
299
|
-
* Analyze conflicts in frame anchors
|
|
300
|
-
*/
|
|
301
|
-
async analyzeAnchorConflicts(sourceFrame, targetFrame) {
|
|
302
|
-
const conflicts = [];
|
|
303
|
-
try {
|
|
304
|
-
const sourceStack = this.getStackManager(sourceFrame.project_id);
|
|
305
|
-
const targetStack = this.getStackManager(targetFrame.project_id);
|
|
306
|
-
const sourceAnchors = await sourceStack.getFrameAnchors(
|
|
307
|
-
sourceFrame.frame_id
|
|
308
|
-
);
|
|
309
|
-
const targetAnchors = await targetStack.getFrameAnchors(
|
|
310
|
-
targetFrame.frame_id
|
|
311
|
-
);
|
|
312
|
-
const sourceAnchorsByType = this.groupAnchorsByType(sourceAnchors);
|
|
313
|
-
const targetAnchorsByType = this.groupAnchorsByType(targetAnchors);
|
|
314
|
-
const allTypes = /* @__PURE__ */ new Set([
|
|
315
|
-
...Object.keys(sourceAnchorsByType),
|
|
316
|
-
...Object.keys(targetAnchorsByType)
|
|
317
|
-
]);
|
|
318
|
-
for (const type of allTypes) {
|
|
319
|
-
const sourceTypeAnchors = sourceAnchorsByType[type] || [];
|
|
320
|
-
const targetTypeAnchors = targetAnchorsByType[type] || [];
|
|
321
|
-
if (sourceTypeAnchors.length !== targetTypeAnchors.length || !this.anchorsEqual(sourceTypeAnchors, targetTypeAnchors)) {
|
|
322
|
-
conflicts.push({
|
|
323
|
-
frameId: sourceFrame.frame_id,
|
|
324
|
-
conflictType: "content",
|
|
325
|
-
sourceFrame,
|
|
326
|
-
targetFrame,
|
|
327
|
-
conflictDetails: [
|
|
328
|
-
{
|
|
329
|
-
field: `anchors_${type}`,
|
|
330
|
-
sourceValue: sourceTypeAnchors,
|
|
331
|
-
targetValue: targetTypeAnchors,
|
|
332
|
-
lastModified: {
|
|
333
|
-
source: /* @__PURE__ */ new Date(),
|
|
334
|
-
target: /* @__PURE__ */ new Date()
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
],
|
|
338
|
-
severity: "low",
|
|
339
|
-
autoResolvable: true
|
|
340
|
-
// Can merge anchors
|
|
341
|
-
});
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
} catch (error) {
|
|
345
|
-
logger.warn(
|
|
346
|
-
`Failed to analyze anchor conflicts for frame: ${sourceFrame.frame_id}`,
|
|
347
|
-
error
|
|
348
|
-
);
|
|
349
|
-
}
|
|
350
|
-
return conflicts;
|
|
351
|
-
}
|
|
352
|
-
/**
|
|
353
|
-
* Auto-resolve conflicts based on merge policy
|
|
354
|
-
*/
|
|
355
|
-
async autoResolveConflicts(sessionId) {
|
|
356
|
-
const session = this.activeSessions.get(sessionId);
|
|
357
|
-
if (!session) return;
|
|
358
|
-
const autoResolvableConflicts = session.conflicts.filter(
|
|
359
|
-
(c) => c.autoResolvable
|
|
360
|
-
);
|
|
361
|
-
for (const conflict of autoResolvableConflicts) {
|
|
362
|
-
const resolution = await this.applyMergePolicy(conflict, session.policy);
|
|
363
|
-
if (resolution) {
|
|
364
|
-
session.resolutions.push(resolution);
|
|
365
|
-
session.metadata.autoResolvedConflicts++;
|
|
366
|
-
logger.debug(`Auto-resolved conflict: ${conflict.frameId}`, {
|
|
367
|
-
type: conflict.conflictType,
|
|
368
|
-
strategy: resolution.strategy
|
|
369
|
-
});
|
|
370
|
-
}
|
|
371
|
-
}
|
|
372
|
-
const remainingConflicts = session.conflicts.filter(
|
|
373
|
-
(c) => !session.resolutions.find((r) => r.conflictId === c.frameId)
|
|
374
|
-
);
|
|
375
|
-
if (remainingConflicts.length === 0) {
|
|
376
|
-
session.status = "completed";
|
|
377
|
-
session.completedAt = /* @__PURE__ */ new Date();
|
|
378
|
-
} else if (remainingConflicts.every((c) => !c.autoResolvable)) {
|
|
379
|
-
session.status = "manual_review";
|
|
380
|
-
}
|
|
381
|
-
this.activeSessions.set(sessionId, session);
|
|
382
|
-
}
|
|
383
|
-
/**
|
|
384
|
-
* Apply merge policy to resolve conflicts automatically
|
|
385
|
-
*/
|
|
386
|
-
async applyMergePolicy(conflict, policy) {
|
|
387
|
-
const sortedRules = policy.rules.sort((a, b) => b.priority - a.priority);
|
|
388
|
-
for (const rule of sortedRules) {
|
|
389
|
-
if (this.evaluateRuleCondition(conflict, rule.condition)) {
|
|
390
|
-
return {
|
|
391
|
-
conflictId: conflict.frameId,
|
|
392
|
-
strategy: rule.action === "require_manual" ? "manual" : rule.action,
|
|
393
|
-
resolvedBy: "system",
|
|
394
|
-
resolvedAt: /* @__PURE__ */ new Date(),
|
|
395
|
-
notes: `Auto-resolved by policy: ${policy.name}`
|
|
396
|
-
};
|
|
397
|
-
}
|
|
398
|
-
}
|
|
399
|
-
return null;
|
|
400
|
-
}
|
|
401
|
-
/**
|
|
402
|
-
* Manually resolve a specific conflict
|
|
403
|
-
*/
|
|
404
|
-
async resolveConflict(sessionId, conflictId, resolution) {
|
|
405
|
-
const input = validateInput(ConflictResolutionSchema, {
|
|
406
|
-
strategy: resolution.strategy,
|
|
407
|
-
resolvedBy: resolution.resolvedBy,
|
|
408
|
-
notes: resolution.notes
|
|
409
|
-
});
|
|
410
|
-
const session = this.activeSessions.get(sessionId);
|
|
411
|
-
if (!session) {
|
|
412
|
-
throw new ValidationError(
|
|
413
|
-
`Merge session not found: ${sessionId}`,
|
|
414
|
-
ErrorCode.MERGE_SESSION_INVALID
|
|
415
|
-
);
|
|
416
|
-
}
|
|
417
|
-
const conflict = session.conflicts.find((c) => c.frameId === conflictId);
|
|
418
|
-
if (!conflict) {
|
|
419
|
-
throw new ValidationError(
|
|
420
|
-
`Conflict not found: ${conflictId}`,
|
|
421
|
-
ErrorCode.MERGE_CONFLICT_UNRESOLVABLE
|
|
422
|
-
);
|
|
423
|
-
}
|
|
424
|
-
const fullResolution = {
|
|
425
|
-
...input,
|
|
426
|
-
conflictId,
|
|
427
|
-
resolvedAt: /* @__PURE__ */ new Date()
|
|
428
|
-
};
|
|
429
|
-
session.resolutions.push(fullResolution);
|
|
430
|
-
session.metadata.manualResolvedConflicts++;
|
|
431
|
-
const resolvedConflictIds = new Set(
|
|
432
|
-
session.resolutions.map((r) => r.conflictId)
|
|
433
|
-
);
|
|
434
|
-
const allResolved = session.conflicts.every(
|
|
435
|
-
(c) => resolvedConflictIds.has(c.frameId)
|
|
436
|
-
);
|
|
437
|
-
if (allResolved) {
|
|
438
|
-
session.status = "completed";
|
|
439
|
-
session.completedAt = /* @__PURE__ */ new Date();
|
|
440
|
-
}
|
|
441
|
-
this.activeSessions.set(sessionId, session);
|
|
442
|
-
logger.info(`Conflict manually resolved: ${conflictId}`, {
|
|
443
|
-
strategy: resolution.strategy,
|
|
444
|
-
resolvedBy: resolution.resolvedBy
|
|
445
|
-
});
|
|
446
|
-
}
|
|
447
|
-
/**
|
|
448
|
-
* Execute merge with resolved conflicts
|
|
449
|
-
*/
|
|
450
|
-
async executeMerge(sessionId) {
|
|
451
|
-
const session = this.activeSessions.get(sessionId);
|
|
452
|
-
if (!session) {
|
|
453
|
-
throw new DatabaseError(
|
|
454
|
-
`Merge session not found: ${sessionId}`,
|
|
455
|
-
ErrorCode.RESOURCE_NOT_FOUND
|
|
456
|
-
);
|
|
457
|
-
}
|
|
458
|
-
if (session.status !== "completed") {
|
|
459
|
-
throw new DatabaseError(
|
|
460
|
-
`Merge session not ready for execution: ${session.status}`,
|
|
461
|
-
ErrorCode.INVALID_STATE
|
|
462
|
-
);
|
|
463
|
-
}
|
|
464
|
-
try {
|
|
465
|
-
const resolutionMap = new Map(
|
|
466
|
-
session.resolutions.map((r) => [r.conflictId, r])
|
|
467
|
-
);
|
|
468
|
-
const result = {
|
|
469
|
-
success: true,
|
|
470
|
-
conflictFrames: [],
|
|
471
|
-
mergedFrames: [],
|
|
472
|
-
errors: []
|
|
473
|
-
};
|
|
474
|
-
const sourceStack = this.getStackManager(session.sourceStackId);
|
|
475
|
-
const targetStack = this.getStackManager(session.targetStackId);
|
|
476
|
-
const conflictsByFrame = /* @__PURE__ */ new Map();
|
|
477
|
-
for (const conflict of session.conflicts) {
|
|
478
|
-
const existing = conflictsByFrame.get(conflict.frameId) || [];
|
|
479
|
-
existing.push(conflict);
|
|
480
|
-
conflictsByFrame.set(conflict.frameId, existing);
|
|
481
|
-
}
|
|
482
|
-
for (const [frameId] of conflictsByFrame) {
|
|
483
|
-
try {
|
|
484
|
-
const resolution = resolutionMap.get(frameId);
|
|
485
|
-
if (!resolution) {
|
|
486
|
-
result.errors.push({
|
|
487
|
-
frameId,
|
|
488
|
-
error: "No resolution found",
|
|
489
|
-
resolution: "skipped"
|
|
490
|
-
});
|
|
491
|
-
result.conflictFrames.push(frameId);
|
|
492
|
-
continue;
|
|
493
|
-
}
|
|
494
|
-
const sourceFrame = await sourceStack.getFrame(frameId);
|
|
495
|
-
const targetFrame = await targetStack.getFrame(frameId);
|
|
496
|
-
if (!sourceFrame) {
|
|
497
|
-
result.errors.push({
|
|
498
|
-
frameId,
|
|
499
|
-
error: "Source frame not found",
|
|
500
|
-
resolution: "skipped"
|
|
501
|
-
});
|
|
502
|
-
continue;
|
|
503
|
-
}
|
|
504
|
-
switch (resolution.strategy) {
|
|
505
|
-
case "source_wins":
|
|
506
|
-
if (targetFrame) await targetStack.deleteFrame(frameId);
|
|
507
|
-
await this.copyFrameToStack(
|
|
508
|
-
sourceFrame,
|
|
509
|
-
sourceStack,
|
|
510
|
-
targetStack
|
|
511
|
-
);
|
|
512
|
-
result.mergedFrames.push(frameId);
|
|
513
|
-
break;
|
|
514
|
-
case "target_wins":
|
|
515
|
-
result.mergedFrames.push(frameId);
|
|
516
|
-
break;
|
|
517
|
-
case "merge_both":
|
|
518
|
-
if (targetFrame) {
|
|
519
|
-
await this.mergeFrameContents(
|
|
520
|
-
sourceFrame,
|
|
521
|
-
targetFrame,
|
|
522
|
-
sourceStack,
|
|
523
|
-
targetStack
|
|
524
|
-
);
|
|
525
|
-
} else {
|
|
526
|
-
await this.copyFrameToStack(
|
|
527
|
-
sourceFrame,
|
|
528
|
-
sourceStack,
|
|
529
|
-
targetStack
|
|
530
|
-
);
|
|
531
|
-
}
|
|
532
|
-
result.mergedFrames.push(frameId);
|
|
533
|
-
break;
|
|
534
|
-
case "skip":
|
|
535
|
-
result.conflictFrames.push(frameId);
|
|
536
|
-
break;
|
|
537
|
-
case "manual":
|
|
538
|
-
result.errors.push({
|
|
539
|
-
frameId,
|
|
540
|
-
error: "Manual resolution not applied",
|
|
541
|
-
resolution: "skipped"
|
|
542
|
-
});
|
|
543
|
-
result.conflictFrames.push(frameId);
|
|
544
|
-
break;
|
|
545
|
-
}
|
|
546
|
-
} catch (error) {
|
|
547
|
-
result.errors.push({
|
|
548
|
-
frameId,
|
|
549
|
-
error: error instanceof Error ? error.message : String(error),
|
|
550
|
-
resolution: "skipped"
|
|
551
|
-
});
|
|
552
|
-
result.success = false;
|
|
553
|
-
}
|
|
554
|
-
}
|
|
555
|
-
logger.info(`Merge executed: ${sessionId}`, {
|
|
556
|
-
mergedFrames: result.mergedFrames.length,
|
|
557
|
-
conflicts: result.conflictFrames.length,
|
|
558
|
-
errors: result.errors.length
|
|
559
|
-
});
|
|
560
|
-
return result;
|
|
561
|
-
} catch (error) {
|
|
562
|
-
throw new DatabaseError(
|
|
563
|
-
"Failed to execute merge",
|
|
564
|
-
ErrorCode.OPERATION_FAILED,
|
|
565
|
-
{ sessionId },
|
|
566
|
-
error instanceof Error ? error : void 0
|
|
567
|
-
);
|
|
568
|
-
}
|
|
569
|
-
}
|
|
570
|
-
async copyFrameToStack(frame, sourceStack, targetStack) {
|
|
571
|
-
await targetStack.createFrame({
|
|
572
|
-
frame_id: frame.frame_id,
|
|
573
|
-
name: frame.name,
|
|
574
|
-
type: frame.type,
|
|
575
|
-
parent_frame_id: frame.parent_frame_id,
|
|
576
|
-
inputs: frame.inputs,
|
|
577
|
-
outputs: frame.outputs
|
|
578
|
-
});
|
|
579
|
-
const events = await sourceStack.getFrameEvents(frame.frame_id);
|
|
580
|
-
for (const event of events) {
|
|
581
|
-
await targetStack.addEvent(frame.frame_id, {
|
|
582
|
-
type: event.type,
|
|
583
|
-
text: event.text,
|
|
584
|
-
metadata: event.metadata
|
|
585
|
-
});
|
|
586
|
-
}
|
|
587
|
-
const anchors = await sourceStack.getFrameAnchors(frame.frame_id);
|
|
588
|
-
for (const anchor of anchors) {
|
|
589
|
-
await targetStack.addAnchor(frame.frame_id, {
|
|
590
|
-
type: anchor.type,
|
|
591
|
-
text: anchor.text,
|
|
592
|
-
priority: anchor.priority,
|
|
593
|
-
metadata: anchor.metadata
|
|
594
|
-
});
|
|
595
|
-
}
|
|
596
|
-
}
|
|
597
|
-
async mergeFrameContents(sourceFrame, targetFrame, sourceStack, targetStack) {
|
|
598
|
-
const sourceEvents = await sourceStack.getFrameEvents(sourceFrame.frame_id);
|
|
599
|
-
const targetEvents = await targetStack.getFrameEvents(targetFrame.frame_id);
|
|
600
|
-
const existingSignatures = new Set(
|
|
601
|
-
targetEvents.map((e) => `${e.type}:${e.text}`)
|
|
602
|
-
);
|
|
603
|
-
for (const event of sourceEvents) {
|
|
604
|
-
const sig = `${event.type}:${event.text}`;
|
|
605
|
-
if (!existingSignatures.has(sig)) {
|
|
606
|
-
await targetStack.addEvent(targetFrame.frame_id, {
|
|
607
|
-
type: event.type,
|
|
608
|
-
text: event.text,
|
|
609
|
-
metadata: { ...event.metadata, merged: true }
|
|
610
|
-
});
|
|
611
|
-
existingSignatures.add(sig);
|
|
612
|
-
}
|
|
613
|
-
}
|
|
614
|
-
const sourceAnchors = await sourceStack.getFrameAnchors(
|
|
615
|
-
sourceFrame.frame_id
|
|
616
|
-
);
|
|
617
|
-
const targetAnchors = await targetStack.getFrameAnchors(
|
|
618
|
-
targetFrame.frame_id
|
|
619
|
-
);
|
|
620
|
-
const existingAnchorSigs = new Set(
|
|
621
|
-
targetAnchors.map((a) => `${a.type}:${a.text}:${a.priority}`)
|
|
622
|
-
);
|
|
623
|
-
for (const anchor of sourceAnchors) {
|
|
624
|
-
const sig = `${anchor.type}:${anchor.text}:${anchor.priority}`;
|
|
625
|
-
if (!existingAnchorSigs.has(sig)) {
|
|
626
|
-
await targetStack.addAnchor(targetFrame.frame_id, {
|
|
627
|
-
type: anchor.type,
|
|
628
|
-
text: anchor.text,
|
|
629
|
-
priority: anchor.priority,
|
|
630
|
-
metadata: { ...anchor.metadata, merged: true }
|
|
631
|
-
});
|
|
632
|
-
existingAnchorSigs.add(sig);
|
|
633
|
-
}
|
|
634
|
-
}
|
|
635
|
-
logger.debug(`Merged frame contents: ${sourceFrame.frame_id}`);
|
|
636
|
-
}
|
|
637
|
-
/**
|
|
638
|
-
* Get merge session details
|
|
639
|
-
*/
|
|
640
|
-
async getMergeSession(sessionId) {
|
|
641
|
-
return this.activeSessions.get(sessionId) || null;
|
|
642
|
-
}
|
|
643
|
-
/**
|
|
644
|
-
* Create custom merge policy
|
|
645
|
-
*/
|
|
646
|
-
async createMergePolicy(policy) {
|
|
647
|
-
const input = validateInput(CreateMergePolicySchema, policy);
|
|
648
|
-
this.mergePolicies.set(input.name, input);
|
|
649
|
-
logger.info(`Created merge policy: ${input.name}`, {
|
|
650
|
-
rules: input.rules.length,
|
|
651
|
-
autoApplyThreshold: input.autoApplyThreshold
|
|
652
|
-
});
|
|
653
|
-
}
|
|
654
|
-
/**
|
|
655
|
-
* Initialize default merge policies
|
|
656
|
-
*/
|
|
657
|
-
initializeDefaultPolicies() {
|
|
658
|
-
this.mergePolicies.set("conservative", {
|
|
659
|
-
name: "conservative",
|
|
660
|
-
description: "Prefer manual resolution for most conflicts",
|
|
661
|
-
rules: [
|
|
662
|
-
{
|
|
663
|
-
condition: '$.conflictType == "metadata" && $.severity == "low"',
|
|
664
|
-
action: "target_wins",
|
|
665
|
-
priority: 1
|
|
666
|
-
},
|
|
667
|
-
{
|
|
668
|
-
condition: '$.severity == "critical"',
|
|
669
|
-
action: "require_manual",
|
|
670
|
-
priority: 10
|
|
671
|
-
}
|
|
672
|
-
],
|
|
673
|
-
autoApplyThreshold: "never"
|
|
674
|
-
});
|
|
675
|
-
this.mergePolicies.set("aggressive", {
|
|
676
|
-
name: "aggressive",
|
|
677
|
-
description: "Auto-resolve conflicts when safe",
|
|
678
|
-
rules: [
|
|
679
|
-
{
|
|
680
|
-
condition: '$.conflictType == "sequence"',
|
|
681
|
-
action: "merge_both",
|
|
682
|
-
priority: 5
|
|
683
|
-
},
|
|
684
|
-
{
|
|
685
|
-
condition: '$.severity == "low"',
|
|
686
|
-
action: "source_wins",
|
|
687
|
-
priority: 2
|
|
688
|
-
},
|
|
689
|
-
{
|
|
690
|
-
condition: '$.severity == "medium" && $.autoResolvable',
|
|
691
|
-
action: "merge_both",
|
|
692
|
-
priority: 4
|
|
693
|
-
}
|
|
694
|
-
],
|
|
695
|
-
autoApplyThreshold: "medium"
|
|
696
|
-
});
|
|
697
|
-
this.mergePolicies.set("default", {
|
|
698
|
-
name: "default",
|
|
699
|
-
description: "Balanced conflict resolution",
|
|
700
|
-
rules: [
|
|
701
|
-
{
|
|
702
|
-
condition: '$.conflictType == "sequence" && $.severity == "low"',
|
|
703
|
-
action: "merge_both",
|
|
704
|
-
priority: 3
|
|
705
|
-
},
|
|
706
|
-
{
|
|
707
|
-
condition: '$.conflictType == "metadata" && $.autoResolvable',
|
|
708
|
-
action: "target_wins",
|
|
709
|
-
priority: 2
|
|
710
|
-
},
|
|
711
|
-
{
|
|
712
|
-
condition: '$.severity == "critical"',
|
|
713
|
-
action: "require_manual",
|
|
714
|
-
priority: 10
|
|
715
|
-
}
|
|
716
|
-
],
|
|
717
|
-
autoApplyThreshold: "low"
|
|
718
|
-
});
|
|
719
|
-
}
|
|
720
|
-
// Helper methods
|
|
721
|
-
getStackManager(stackId) {
|
|
722
|
-
return this.dualStackManager.getStackManager(stackId);
|
|
723
|
-
}
|
|
724
|
-
groupAnchorsByType(anchors) {
|
|
725
|
-
return anchors.reduce(
|
|
726
|
-
(groups, anchor) => {
|
|
727
|
-
if (!groups[anchor.type]) groups[anchor.type] = [];
|
|
728
|
-
groups[anchor.type].push(anchor);
|
|
729
|
-
return groups;
|
|
730
|
-
},
|
|
731
|
-
{}
|
|
732
|
-
);
|
|
733
|
-
}
|
|
734
|
-
anchorsEqual(anchors1, anchors2) {
|
|
735
|
-
if (anchors1.length !== anchors2.length) return false;
|
|
736
|
-
const sorted1 = [...anchors1].sort((a, b) => a.text.localeCompare(b.text));
|
|
737
|
-
const sorted2 = [...anchors2].sort((a, b) => a.text.localeCompare(b.text));
|
|
738
|
-
return sorted1.every(
|
|
739
|
-
(anchor, i) => anchor.text === sorted2[i].text && anchor.priority === sorted2[i].priority
|
|
740
|
-
);
|
|
741
|
-
}
|
|
742
|
-
evaluateRuleCondition(conflict, condition) {
|
|
743
|
-
return condition.includes(conflict.conflictType) || condition.includes(conflict.severity);
|
|
744
|
-
}
|
|
745
|
-
}
|
|
746
|
-
export {
|
|
747
|
-
StackMergeResolver
|
|
748
|
-
};
|