@stackmemoryai/stackmemory 0.5.48 → 0.5.51

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/README.md +17 -3
  2. package/dist/cli/claude-sm.js +246 -5
  3. package/dist/cli/claude-sm.js.map +3 -3
  4. package/dist/cli/commands/handoff.js +5 -5
  5. package/dist/cli/commands/handoff.js.map +2 -2
  6. package/dist/cli/commands/sweep.js +190 -421
  7. package/dist/cli/commands/sweep.js.map +3 -3
  8. package/dist/cli/index.js +8 -2
  9. package/dist/cli/index.js.map +2 -2
  10. package/dist/core/config/feature-flags.js +13 -1
  11. package/dist/core/config/feature-flags.js.map +2 -2
  12. package/dist/core/context/enhanced-rehydration.js +355 -9
  13. package/dist/core/context/enhanced-rehydration.js.map +3 -3
  14. package/dist/core/context/shared-context-layer.js +229 -0
  15. package/dist/core/context/shared-context-layer.js.map +2 -2
  16. package/dist/features/sweep/index.js +20 -0
  17. package/dist/features/sweep/index.js.map +7 -0
  18. package/dist/features/sweep/prediction-client.js +155 -0
  19. package/dist/features/sweep/prediction-client.js.map +7 -0
  20. package/dist/features/sweep/prompt-builder.js +85 -0
  21. package/dist/features/sweep/prompt-builder.js.map +7 -0
  22. package/dist/features/sweep/pty-wrapper.js +171 -0
  23. package/dist/features/sweep/pty-wrapper.js.map +7 -0
  24. package/dist/features/sweep/state-watcher.js +87 -0
  25. package/dist/features/sweep/state-watcher.js.map +7 -0
  26. package/dist/features/sweep/status-bar.js +88 -0
  27. package/dist/features/sweep/status-bar.js.map +7 -0
  28. package/dist/features/sweep/sweep-server-manager.js +226 -0
  29. package/dist/features/sweep/sweep-server-manager.js.map +7 -0
  30. package/dist/features/sweep/tab-interceptor.js +38 -0
  31. package/dist/features/sweep/tab-interceptor.js.map +7 -0
  32. package/dist/features/sweep/types.js +18 -0
  33. package/dist/features/sweep/types.js.map +7 -0
  34. package/package.json +1 -1
  35. package/scripts/test-setup-e2e.sh +154 -0
@@ -5,6 +5,327 @@ const __dirname = __pathDirname(__filename);
5
5
  import * as fs from "fs/promises";
6
6
  import * as path from "path";
7
7
  import { logger } from "../monitoring/logger.js";
8
+ class CompactionHandler {
9
+ frameManager;
10
+ metrics;
11
+ tokenAccumulator = 0;
12
+ preservedAnchors = /* @__PURE__ */ new Map();
13
+ constructor(frameManager) {
14
+ this.frameManager = frameManager;
15
+ this.metrics = {
16
+ estimatedTokens: 0,
17
+ warningThreshold: 15e4,
18
+ // 150K tokens
19
+ criticalThreshold: 17e4,
20
+ // 170K tokens
21
+ anchorsPreserved: 0
22
+ };
23
+ }
24
+ /**
25
+ * Track token usage from a message
26
+ */
27
+ trackTokens(content) {
28
+ const estimatedTokens = Math.ceil(content.length / 4);
29
+ this.tokenAccumulator += estimatedTokens;
30
+ this.metrics.estimatedTokens += estimatedTokens;
31
+ if (this.isApproachingCompaction()) {
32
+ this.preserveCriticalContext();
33
+ }
34
+ }
35
+ /**
36
+ * Check if approaching compaction threshold
37
+ */
38
+ isApproachingCompaction() {
39
+ return this.metrics.estimatedTokens >= this.metrics.warningThreshold;
40
+ }
41
+ /**
42
+ * Check if past critical threshold
43
+ */
44
+ isPastCriticalThreshold() {
45
+ return this.metrics.estimatedTokens >= this.metrics.criticalThreshold;
46
+ }
47
+ /**
48
+ * Detect if compaction likely occurred
49
+ */
50
+ detectCompactionEvent(content) {
51
+ const compactionIndicators = [
52
+ "earlier in this conversation",
53
+ "previously discussed",
54
+ "as mentioned before",
55
+ "summarized for brevity",
56
+ "[conversation compressed]",
57
+ "[context truncated]"
58
+ ];
59
+ const lowerContent = content.toLowerCase();
60
+ return compactionIndicators.some(
61
+ (indicator) => lowerContent.includes(indicator)
62
+ );
63
+ }
64
+ /**
65
+ * Preserve critical context before compaction
66
+ */
67
+ async preserveCriticalContext() {
68
+ try {
69
+ const currentFrameId = this.frameManager.getCurrentFrameId();
70
+ if (!currentFrameId) {
71
+ logger.warn("No active frame to preserve context from");
72
+ return;
73
+ }
74
+ const events = this.frameManager.getFrameEvents(currentFrameId);
75
+ const toolCalls = this.extractToolCalls(events);
76
+ const fileOps = this.extractFileOperations(events);
77
+ const decisions = this.extractDecisions(events);
78
+ const errorPatterns = this.extractErrorPatterns(events);
79
+ const anchor = {
80
+ anchor_id: `compact_${Date.now()}`,
81
+ type: "COMPACTION_PRESERVE",
82
+ priority: 10,
83
+ content: {
84
+ tool_calls: toolCalls,
85
+ file_operations: fileOps,
86
+ decisions,
87
+ error_resolutions: errorPatterns
88
+ },
89
+ created_at: Date.now(),
90
+ token_estimate: this.metrics.estimatedTokens
91
+ };
92
+ this.frameManager.addAnchor(
93
+ "CONSTRAINT",
94
+ // Using CONSTRAINT type for now
95
+ JSON.stringify(anchor),
96
+ 10,
97
+ {
98
+ compaction_preserve: true,
99
+ token_count: this.metrics.estimatedTokens
100
+ },
101
+ currentFrameId
102
+ );
103
+ this.preservedAnchors.set(anchor.anchor_id, anchor);
104
+ this.metrics.anchorsPreserved++;
105
+ logger.info(
106
+ `Preserved critical context at ${this.metrics.estimatedTokens} tokens`
107
+ );
108
+ } catch (error) {
109
+ logger.error(
110
+ "Failed to preserve critical context:",
111
+ error instanceof Error ? error : void 0
112
+ );
113
+ }
114
+ }
115
+ /**
116
+ * Extract tool calls from events
117
+ */
118
+ extractToolCalls(events) {
119
+ const toolCalls = [];
120
+ const toolEvents = events.filter((e) => e.event_type === "tool_call");
121
+ for (const event of toolEvents) {
122
+ const resultEvent = events.find(
123
+ (e) => e.event_type === "tool_result" && e.seq > event.seq && e.payload.tool_name === event.payload.tool_name
124
+ );
125
+ toolCalls.push({
126
+ tool: event.payload.tool_name || "unknown",
127
+ timestamp: event.ts,
128
+ key_inputs: this.extractKeyInputs(event.payload),
129
+ key_outputs: resultEvent ? this.extractKeyOutputs(resultEvent.payload) : {},
130
+ files_affected: this.extractAffectedFiles(
131
+ event.payload,
132
+ resultEvent?.payload
133
+ ),
134
+ success: resultEvent ? !resultEvent.payload.error : false,
135
+ error: resultEvent?.payload.error
136
+ });
137
+ }
138
+ return toolCalls;
139
+ }
140
+ /**
141
+ * Extract key inputs from tool call
142
+ */
143
+ extractKeyInputs(payload) {
144
+ const keys = [
145
+ "file_path",
146
+ "command",
147
+ "query",
148
+ "path",
149
+ "pattern",
150
+ "content"
151
+ ];
152
+ const result = {};
153
+ for (const key of keys) {
154
+ if (payload.arguments?.[key]) {
155
+ result[key] = payload.arguments[key];
156
+ }
157
+ }
158
+ return result;
159
+ }
160
+ /**
161
+ * Extract key outputs from tool result
162
+ */
163
+ extractKeyOutputs(payload) {
164
+ return {
165
+ success: !payload.error,
166
+ error: payload.error,
167
+ result_type: payload.result_type,
168
+ files_created: payload.files_created,
169
+ files_modified: payload.files_modified
170
+ };
171
+ }
172
+ /**
173
+ * Extract affected files from tool events
174
+ */
175
+ extractAffectedFiles(callPayload, resultPayload) {
176
+ const files = /* @__PURE__ */ new Set();
177
+ if (callPayload?.arguments?.file_path) {
178
+ files.add(callPayload.arguments.file_path);
179
+ }
180
+ if (callPayload?.arguments?.path) {
181
+ files.add(callPayload.arguments.path);
182
+ }
183
+ if (resultPayload?.files_created) {
184
+ resultPayload.files_created.forEach((f) => files.add(f));
185
+ }
186
+ if (resultPayload?.files_modified) {
187
+ resultPayload.files_modified.forEach((f) => files.add(f));
188
+ }
189
+ return Array.from(files);
190
+ }
191
+ /**
192
+ * Extract file operations from events
193
+ */
194
+ extractFileOperations(events) {
195
+ const fileOps = [];
196
+ const fileTools = ["Read", "Write", "Edit", "MultiEdit", "Delete"];
197
+ const toolEvents = events.filter(
198
+ (e) => e.event_type === "tool_call" && fileTools.includes(e.payload.tool_name)
199
+ );
200
+ for (const event of toolEvents) {
201
+ const operation = this.mapToolToOperation(event.payload.tool_name);
202
+ const path2 = event.payload.arguments?.file_path || event.payload.arguments?.path || "unknown";
203
+ fileOps.push({
204
+ type: operation,
205
+ path: path2,
206
+ timestamp: event.ts,
207
+ success: true,
208
+ // Will be updated from result
209
+ error: void 0
210
+ });
211
+ }
212
+ return fileOps;
213
+ }
214
+ /**
215
+ * Map tool name to file operation type
216
+ */
217
+ mapToolToOperation(toolName) {
218
+ const mapping = {
219
+ Read: "read",
220
+ Write: "write",
221
+ Edit: "edit",
222
+ MultiEdit: "edit",
223
+ Delete: "delete"
224
+ };
225
+ return mapping[toolName] || "read";
226
+ }
227
+ /**
228
+ * Extract decisions from events
229
+ */
230
+ extractDecisions(events) {
231
+ const decisions = [];
232
+ const decisionEvents = events.filter((e) => e.event_type === "decision");
233
+ for (const event of decisionEvents) {
234
+ if (event.payload.text) {
235
+ decisions.push(event.payload.text);
236
+ }
237
+ }
238
+ return decisions;
239
+ }
240
+ /**
241
+ * Extract error patterns and resolutions
242
+ */
243
+ extractErrorPatterns(events) {
244
+ const patterns = [];
245
+ const errorEvents = events.filter(
246
+ (e) => e.event_type === "tool_result" && e.payload.error
247
+ );
248
+ for (const errorEvent of errorEvents) {
249
+ const subsequentTools = events.filter((e) => e.event_type === "tool_call" && e.seq > errorEvent.seq).slice(0, 3);
250
+ if (subsequentTools.length > 0) {
251
+ patterns.push({
252
+ error: errorEvent.payload.error,
253
+ resolution: `Attempted resolution with ${subsequentTools.map((t) => t.payload.tool_name).join(", ")}`,
254
+ tool_sequence: subsequentTools.map((t) => t.payload.tool_name),
255
+ timestamp: errorEvent.ts
256
+ });
257
+ }
258
+ }
259
+ return patterns;
260
+ }
261
+ /**
262
+ * Restore context after compaction detected
263
+ */
264
+ async restoreContext() {
265
+ if (this.preservedAnchors.size === 0) {
266
+ logger.warn("No preserved anchors to restore from");
267
+ return;
268
+ }
269
+ const anchors = Array.from(this.preservedAnchors.values());
270
+ anchors.sort((a, b) => b.created_at - a.created_at);
271
+ const latestAnchor = anchors[0];
272
+ const restorationFrame = this.frameManager.createFrame({
273
+ type: "review",
274
+ name: "Context Restoration After Compaction",
275
+ inputs: { reason: "autocompaction_detected" }
276
+ });
277
+ this.frameManager.addAnchor(
278
+ "FACT",
279
+ `Context restored from token position ${latestAnchor.token_estimate}`,
280
+ 10,
281
+ { restoration: true },
282
+ restorationFrame
283
+ );
284
+ const toolSequence = latestAnchor.content.tool_calls.map((t) => t.tool).join(" \u2192 ");
285
+ this.frameManager.addAnchor(
286
+ "FACT",
287
+ `Tool sequence: ${toolSequence}`,
288
+ 9,
289
+ {},
290
+ restorationFrame
291
+ );
292
+ const files = /* @__PURE__ */ new Set();
293
+ latestAnchor.content.file_operations.forEach((op) => files.add(op.path));
294
+ if (files.size > 0) {
295
+ this.frameManager.addAnchor(
296
+ "FACT",
297
+ `Files touched: ${Array.from(files).join(", ")}`,
298
+ 8,
299
+ {},
300
+ restorationFrame
301
+ );
302
+ }
303
+ for (const decision of latestAnchor.content.decisions) {
304
+ this.frameManager.addAnchor(
305
+ "DECISION",
306
+ decision,
307
+ 7,
308
+ {},
309
+ restorationFrame
310
+ );
311
+ }
312
+ logger.info("Context restored after compaction detection");
313
+ }
314
+ /**
315
+ * Get current metrics
316
+ */
317
+ getMetrics() {
318
+ return { ...this.metrics };
319
+ }
320
+ /**
321
+ * Reset token counter (e.g., at session start)
322
+ */
323
+ resetTokenCount() {
324
+ this.metrics.estimatedTokens = 0;
325
+ this.tokenAccumulator = 0;
326
+ this.metrics.lastCompactionAt = void 0;
327
+ }
328
+ }
8
329
  class EnhancedRehydrationManager {
9
330
  frameManager;
10
331
  compactionHandler;
@@ -303,7 +624,9 @@ class EnhancedRehydrationManager {
303
624
  }
304
625
  const dirs = files.map((f) => path.dirname(f)).filter((v, i, a) => a.indexOf(v) === i);
305
626
  mapping.key_directories = dirs.filter(
306
- (dir) => ["src", "lib", "components", "pages", "api", "utils", "types"].some((key) => dir.includes(key))
627
+ (dir) => ["src", "lib", "components", "pages", "api", "utils", "types"].some(
628
+ (key) => dir.includes(key)
629
+ )
307
630
  );
308
631
  } catch (error) {
309
632
  logger.warn("Failed to analyze project mapping:", error);
@@ -321,7 +644,10 @@ class EnhancedRehydrationManager {
321
644
  const fileSnapshots = [];
322
645
  const recentFiles = await this.getRecentlyModifiedFiles(workingDir);
323
646
  for (const file of recentFiles.slice(0, 20)) {
324
- const snapshot = await this.captureFileSnapshot(file, this.inferContextTags(file));
647
+ const snapshot = await this.captureFileSnapshot(
648
+ file,
649
+ this.inferContextTags(file)
650
+ );
325
651
  if (snapshot) {
326
652
  fileSnapshots.push(snapshot);
327
653
  }
@@ -336,13 +662,21 @@ class EnhancedRehydrationManager {
336
662
  conversation_context: conversationContext,
337
663
  project_mapping: projectMapping,
338
664
  active_workflows: this.detectActiveWorkflows(fileSnapshots),
339
- current_focus: this.inferCurrentFocus(fileSnapshots, conversationContext)
665
+ current_focus: this.inferCurrentFocus(
666
+ fileSnapshots,
667
+ conversationContext
668
+ )
340
669
  },
341
- recovery_anchors: this.createRecoveryAnchors(fileSnapshots, conversationContext)
670
+ recovery_anchors: this.createRecoveryAnchors(
671
+ fileSnapshots,
672
+ conversationContext
673
+ )
342
674
  };
343
675
  this.rehydrationStorage.set(checkpointId, rehydrationContext);
344
676
  await this.persistRehydrationContext(checkpointId, rehydrationContext);
345
- logger.info(`Created rehydration checkpoint ${checkpointId} with ${fileSnapshots.length} file snapshots`);
677
+ logger.info(
678
+ `Created rehydration checkpoint ${checkpointId} with ${fileSnapshots.length} file snapshots`
679
+ );
346
680
  return checkpointId;
347
681
  } catch (error) {
348
682
  logger.error("Failed to create rehydration checkpoint:", error);
@@ -383,7 +717,10 @@ class EnhancedRehydrationManager {
383
717
  logger.warn("No active frame for context injection");
384
718
  return;
385
719
  }
386
- for (const snapshot of context.pre_compact_state.file_snapshots.slice(0, 5)) {
720
+ for (const snapshot of context.pre_compact_state.file_snapshots.slice(
721
+ 0,
722
+ 5
723
+ )) {
387
724
  this.frameManager.addAnchor(
388
725
  "FACT",
389
726
  `File: ${snapshot.path} (${snapshot.contextTags.join(", ")})
@@ -479,7 +816,8 @@ Stack preview: ${trace.stack_frames.slice(0, 3).join("\n")}`,
479
816
  inferContextTags(filePath) {
480
817
  const tags = [];
481
818
  const content = filePath.toLowerCase();
482
- if (content.includes("pipeline") || content.includes("migrate")) tags.push("migration");
819
+ if (content.includes("pipeline") || content.includes("migrate"))
820
+ tags.push("migration");
483
821
  if (content.includes("hubspot")) tags.push("hubspot");
484
822
  if (content.includes("pipedream")) tags.push("pipedream");
485
823
  if (content.includes("test")) tags.push("test");
@@ -600,7 +938,9 @@ Stack preview: ${trace.stack_frames.slice(0, 3).join("\n")}`,
600
938
  createRecoveryAnchors(snapshots, context) {
601
939
  const anchors = [];
602
940
  for (const snapshot of snapshots.slice(0, 3)) {
603
- anchors.push(`File context: ${snapshot.path} with ${snapshot.contextTags.join(", ")}`);
941
+ anchors.push(
942
+ `File context: ${snapshot.path} with ${snapshot.contextTags.join(", ")}`
943
+ );
604
944
  }
605
945
  return anchors;
606
946
  }
@@ -614,7 +954,12 @@ Stack preview: ${trace.stack_frames.slice(0, 3).join("\n")}`,
614
954
  }
615
955
  async loadPersistedContext(id) {
616
956
  try {
617
- const contextPath = path.join(process.cwd(), ".stackmemory", "rehydration", `${id}.json`);
957
+ const contextPath = path.join(
958
+ process.cwd(),
959
+ ".stackmemory",
960
+ "rehydration",
961
+ `${id}.json`
962
+ );
618
963
  const content = await fs.readFile(contextPath, "utf8");
619
964
  return JSON.parse(content);
620
965
  } catch {
@@ -643,6 +988,7 @@ Stack preview: ${trace.stack_frames.slice(0, 3).join("\n")}`,
643
988
  }
644
989
  }
645
990
  export {
991
+ CompactionHandler,
646
992
  EnhancedRehydrationManager
647
993
  };
648
994
  //# sourceMappingURL=enhanced-rehydration.js.map