@jmylchreest/aide-plugin 0.0.24 → 0.0.26

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.
@@ -0,0 +1,342 @@
1
+ ---
2
+ name: worktree-resolve
3
+ description: Resolve and merge git worktrees after swarm completion
4
+ triggers:
5
+ - resolve worktrees
6
+ - merge worktrees
7
+ - cleanup swarm
8
+ - finish swarm
9
+ ---
10
+
11
+ # Worktree Resolution
12
+
13
+ **Recommended model tier:** balanced (sonnet) - this skill performs straightforward operations
14
+
15
+ Merge all swarm worktrees back into the main branch after testing.
16
+
17
+ ## Workflow
18
+
19
+ ### 1. List Active Worktrees
20
+
21
+ ```bash
22
+ git worktree list
23
+ ```
24
+
25
+ Check the AIDE worktree state file for metadata and status:
26
+ ```bash
27
+ cat .aide/state/worktrees.json
28
+ ```
29
+
30
+ **Worktree Status Values:**
31
+ - `active` - Agent is still working on this worktree
32
+ - `agent-complete` - Agent finished, ready for merge review
33
+ - `merged` - Successfully merged to main
34
+
35
+ **Only merge worktrees with status `agent-complete`.**
36
+
37
+ Example state file:
38
+ ```json
39
+ {
40
+ "active": [
41
+ {
42
+ "name": "story-auth",
43
+ "path": ".aide/worktrees/story-auth",
44
+ "branch": "feat/story-auth",
45
+ "taskId": "story-auth",
46
+ "agentId": "agent-auth",
47
+ "status": "agent-complete",
48
+ "createdAt": "2026-02-07T...",
49
+ "completedAt": "2026-02-07T..."
50
+ }
51
+ ],
52
+ "baseBranch": "main"
53
+ }
54
+ ```
55
+
56
+ ### 2. For Each Worktree Branch
57
+
58
+ Run these steps for every `feat/*` branch from swarm:
59
+
60
+ #### a) Test the Branch
61
+
62
+ ```bash
63
+ # Checkout the worktree
64
+ cd .aide/worktrees/<name>
65
+
66
+ # Run tests
67
+ npm test # or appropriate test command
68
+
69
+ # Run linting
70
+ npm run lint # or appropriate lint command
71
+
72
+ # Check build
73
+ npm run build # if applicable
74
+ ```
75
+
76
+ #### b) Review Changes
77
+
78
+ ```bash
79
+ # Back in main repo
80
+ git log main..feat/<name> --oneline
81
+ git diff main...feat/<name> --stat
82
+ ```
83
+
84
+ #### c) Check Merge Compatibility
85
+
86
+ ```bash
87
+ # Dry-run merge check
88
+ git merge-tree $(git merge-base main feat/<name>) main feat/<name>
89
+ ```
90
+
91
+ If conflicts shown, note them for manual resolution.
92
+
93
+ ### 3. Merge Clean Branches
94
+
95
+ For branches that pass tests and have no conflicts:
96
+
97
+ ```bash
98
+ git checkout main
99
+ git merge feat/<branch-name> --no-edit
100
+ ```
101
+
102
+ ### 4. Handle Conflicts (Intelligent Resolution)
103
+
104
+ When merge conflicts occur, **do not use `-X theirs` or `-X ours`** - these blindly discard changes.
105
+
106
+ Instead, resolve conflicts intelligently:
107
+
108
+ #### Step 1: Attempt merge and identify conflicts
109
+ ```bash
110
+ git merge feat/<name> --no-edit
111
+ # If conflicts occur, git will list the conflicted files
112
+ ```
113
+
114
+ #### Step 2: For each conflicted file, read and analyze
115
+ ```bash
116
+ # Read the file with conflict markers
117
+ cat <conflicted-file>
118
+ ```
119
+
120
+ The conflict markers show:
121
+ ```
122
+ <<<<<<< HEAD
123
+ [changes from main branch]
124
+ =======
125
+ [changes from feature branch]
126
+ >>>>>>> feat/<name>
127
+ ```
128
+
129
+ #### Step 3: Resolve as a code review expert
130
+
131
+ Act as an expert code reviewer. For each conflict:
132
+
133
+ 1. **Analyze both code paths** - What was each change trying to achieve?
134
+ 2. **Understand the intent** - Are they complementary, overlapping, or contradictory?
135
+ 3. **Modify the feature branch locally** to incorporate main's changes while preserving the feature's logic
136
+ 4. **Edit the file** to remove conflict markers and combine both sets of functionality correctly
137
+
138
+ The resolution must:
139
+ - Remove all conflict markers (`<<<<<<<`, `=======`, `>>>>>>>`)
140
+ - Preserve the functional intent of BOTH changes
141
+ - Be syntactically and semantically correct
142
+ - Maintain code style consistency
143
+
144
+ #### Step 4: Verify and complete
145
+ ```bash
146
+ # Stage the resolved files
147
+ git add <resolved-file>
148
+
149
+ # Run tests to verify the resolution works
150
+ npm test # or appropriate test command
151
+
152
+ # If tests pass, complete the merge
153
+ git commit --no-edit
154
+ ```
155
+
156
+ #### Step 5: Handle failure
157
+
158
+ If you **cannot resolve** the conflict (logic is contradictory, tests fail after resolution, or changes are too complex):
159
+
160
+ 1. **Abort the merge** to restore clean state:
161
+ ```bash
162
+ git merge --abort
163
+ ```
164
+
165
+ 2. **Record the failure** using aide messaging:
166
+ ```bash
167
+ aide message send --from=resolver --to=orchestrator "CONFLICT: Cannot merge feat/<name> - <brief reason>"
168
+ ```
169
+
170
+ 3. **Skip this branch** and continue with remaining branches
171
+
172
+ 4. **Report at completion** - list unmerged branches in the final summary for manual review
173
+
174
+ **Do NOT:**
175
+ - Force through a broken resolution
176
+ - Use `-X theirs` or `-X ours` to blindly pick one side
177
+ - Get stuck - always abort and report if resolution fails
178
+
179
+ #### Example Resolution
180
+
181
+ **Conflict:**
182
+ ```typescript
183
+ <<<<<<< HEAD
184
+ function getUser(id: string): User {
185
+ return db.users.find(u => u.id === id);
186
+ }
187
+ =======
188
+ function getUser(id: string): User | null {
189
+ const user = db.users.find(u => u.id === id);
190
+ return user ?? null;
191
+ }
192
+ >>>>>>> feat/null-safety
193
+ ```
194
+
195
+ **Analysis:**
196
+ - HEAD: Basic lookup returning User
197
+ - Feature: Added null-safety with explicit null return
198
+
199
+ **Resolution:**
200
+ ```typescript
201
+ function getUser(id: string): User | null {
202
+ const user = db.users.find(u => u.id === id);
203
+ return user ?? null;
204
+ }
205
+ ```
206
+ *Feature branch improved null safety - this is additive, keep it.*
207
+
208
+ ### 5. Cleanup
209
+
210
+ After all branches merged:
211
+
212
+ ```bash
213
+ # Remove each worktree
214
+ git worktree remove .aide/worktrees/<name>
215
+
216
+ # Delete merged branches
217
+ git branch -d feat/<name>
218
+
219
+ # Prune any orphaned worktrees
220
+ git worktree prune
221
+
222
+ # Clear state file
223
+ rm .aide/state/worktrees.json
224
+ ```
225
+
226
+ ### 6. Final Verification
227
+
228
+ ```bash
229
+ # Ensure all tests pass on main
230
+ git checkout main
231
+ npm test
232
+ npm run lint
233
+ npm run build
234
+
235
+ # Check no worktrees remain
236
+ git worktree list # Should only show main
237
+ ```
238
+
239
+ ## Summary Report
240
+
241
+ After resolution, report:
242
+
243
+ ```
244
+ ## Worktree Resolution Complete
245
+
246
+ ### Merged Branches
247
+ - feat/task1-agent1: ✓ (3 files, +150/-20)
248
+ - feat/task2-agent2: ✓ (5 files, +280/-45)
249
+
250
+ ### Skipped (conflicts/failures)
251
+ - feat/task3-agent3: Test failures in auth.test.ts
252
+
253
+ ### Final Status
254
+ - All tests passing: ✓
255
+ - Lint clean: ✓
256
+ - Build successful: ✓
257
+ ```
258
+
259
+ ## Quick Commands
260
+
261
+ ```bash
262
+ # List all feat branches from swarm
263
+ git branch --list 'feat/*'
264
+
265
+ # Merge all clean branches at once (risky - prefer one at a time)
266
+ for branch in $(git branch --list 'feat/*' | tr -d ' '); do
267
+ git merge $branch --no-edit || echo "Conflict in $branch"
268
+ done
269
+
270
+ # Bulk cleanup worktrees
271
+ git worktree list | grep '.aide/worktrees' | awk '{print $1}' | xargs -I {} git worktree remove {}
272
+
273
+ # Bulk delete feat branches (only if merged)
274
+ git branch --list 'feat/*' | xargs git branch -d
275
+ ```
276
+
277
+ ## Failure Handling
278
+
279
+ ### If merge fails:
280
+
281
+ 1. **Abort immediately**: `git merge --abort`
282
+ 2. **Record the failure**:
283
+ ```bash
284
+ aide message send --from=resolver --to=orchestrator "Merge failed: feat/<name> - <reason>"
285
+ ```
286
+ 3. **Continue with remaining branches** - do not block on one failure
287
+ 4. **Include in final report** - list all failed branches with reasons
288
+
289
+ ### If tests fail after merge:
290
+
291
+ 1. **Revert the merge**: `git revert -m 1 HEAD`
292
+ 2. **Record the failure** with aide messaging
293
+ 3. **Continue with other branches**
294
+
295
+ ### If worktree removal fails:
296
+
297
+ ```bash
298
+ # Force remove if necessary
299
+ git worktree remove --force .aide/worktrees/<name>
300
+
301
+ # Prune any orphaned entries
302
+ git worktree prune
303
+ ```
304
+
305
+ ## Verification Criteria
306
+
307
+ ### After each merge:
308
+
309
+ ```bash
310
+ # Verify merge commit exists
311
+ git log -1 --oneline
312
+
313
+ # Verify no uncommitted changes
314
+ git status --porcelain # Should be empty
315
+
316
+ # Verify tests pass
317
+ npm test # or appropriate test command
318
+ ```
319
+
320
+ ### After full resolution:
321
+
322
+ ```bash
323
+ # Verify all worktrees removed
324
+ git worktree list # Should only show main worktree
325
+
326
+ # Verify all feature branches deleted (or list unmerged)
327
+ git branch --list 'feat/*' # Should be empty for merged branches
328
+
329
+ # Verify main is clean
330
+ git status
331
+
332
+ # Verify final tests pass
333
+ npm test && npm run lint && npm run build
334
+ ```
335
+
336
+ ## Safety Notes
337
+
338
+ - **Always test each branch before merging**
339
+ - **Merge one at a time** to isolate issues
340
+ - **Keep backups** - create a tag before bulk operations: `git tag pre-swarm-merge`
341
+ - **Don't force delete** - use `-d` not `-D` to prevent deleting unmerged work
342
+ - **Report all failures** - never silently skip branches
package/src/core/index.ts CHANGED
@@ -13,6 +13,7 @@ export * from "./tool-tracking.js";
13
13
  export * from "./persistence-logic.js";
14
14
  export * from "./session-summary-logic.js";
15
15
  export * from "./pre-compact-logic.js";
16
+ export * from "./partial-memory.js";
16
17
  export * from "./cleanup.js";
17
18
  export * from "./mcp-sync.js";
18
19
  export * from "./tool-enforcement.js";
@@ -0,0 +1,354 @@
1
+ /**
2
+ * Partial memory logic — platform-agnostic.
3
+ *
4
+ * Writes granular "partial" memories on significant tool events.
5
+ * These are tagged with "partial" so they:
6
+ * - Are excluded from normal recall (via ExcludeTags)
7
+ * - Can be gathered and rolled up into a final session summary
8
+ * - Get cleaned up (tagged "forget") after the final summary is written
9
+ *
10
+ * What counts as "significant":
11
+ * - Write/Edit/MultiEdit to a file (code change)
12
+ * - Bash commands that succeed (shell operations)
13
+ * - Task tool completions (subagent work)
14
+ * - NOT: Read, Grep, Glob (pure reads — no side effects)
15
+ */
16
+
17
+ import { execFileSync } from "child_process";
18
+ import { debug } from "../lib/logger.js";
19
+
20
+ const SOURCE = "partial-memory";
21
+
22
+ /** Tools that represent significant (state-changing) actions */
23
+ const SIGNIFICANT_TOOLS = new Set([
24
+ "Write",
25
+ "Edit",
26
+ "MultiEdit",
27
+ "Bash",
28
+ "Task",
29
+ ]);
30
+
31
+ /** Information about a completed tool use */
32
+ export interface ToolCompletionInfo {
33
+ toolName: string;
34
+ sessionId: string;
35
+ /** File path affected (Write/Edit) */
36
+ filePath?: string;
37
+ /** Bash command executed */
38
+ command?: string;
39
+ /** Task description */
40
+ description?: string;
41
+ /** Whether the tool succeeded */
42
+ success?: boolean;
43
+ }
44
+
45
+ /**
46
+ * Check whether a tool completion is "significant" enough to write a partial.
47
+ */
48
+ export function isSignificantToolUse(info: ToolCompletionInfo): boolean {
49
+ if (!SIGNIFICANT_TOOLS.has(info.toolName)) return false;
50
+
51
+ // For Bash, only track if we have a command (skip empty/failed)
52
+ if (info.toolName === "Bash") {
53
+ if (!info.command) return false;
54
+ // Skip trivial read-only commands
55
+ const cmd = info.command.trim().toLowerCase();
56
+ if (
57
+ cmd.startsWith("cat ") ||
58
+ cmd.startsWith("ls ") ||
59
+ cmd.startsWith("echo ") ||
60
+ cmd.startsWith("pwd") ||
61
+ cmd.startsWith("which ") ||
62
+ cmd.startsWith("type ")
63
+ ) {
64
+ return false;
65
+ }
66
+ }
67
+
68
+ return true;
69
+ }
70
+
71
+ /**
72
+ * Build a concise partial memory content string from tool completion info.
73
+ */
74
+ export function buildPartialContent(info: ToolCompletionInfo): string {
75
+ switch (info.toolName) {
76
+ case "Write":
77
+ return info.filePath
78
+ ? `Created file: ${info.filePath}`
79
+ : "Created a file";
80
+ case "Edit":
81
+ case "MultiEdit":
82
+ return info.filePath ? `Edited file: ${info.filePath}` : "Edited a file";
83
+ case "Bash": {
84
+ const cmd =
85
+ info.command && info.command.length > 100
86
+ ? info.command.slice(0, 97) + "..."
87
+ : info.command;
88
+ return `Ran command: ${cmd}`;
89
+ }
90
+ case "Task":
91
+ return info.description
92
+ ? `Completed task: ${info.description}`
93
+ : "Completed a subtask";
94
+ default:
95
+ return `Used tool: ${info.toolName}`;
96
+ }
97
+ }
98
+
99
+ /**
100
+ * Build the tags for a partial memory.
101
+ */
102
+ export function buildPartialTags(
103
+ sessionId: string,
104
+ info: ToolCompletionInfo,
105
+ ): string[] {
106
+ const tags = [
107
+ "partial",
108
+ `session:${sessionId.slice(0, 8)}`,
109
+ `tool:${info.toolName.toLowerCase()}`,
110
+ ];
111
+ if (info.filePath) {
112
+ // Add a tag for the file extension to allow grouping
113
+ const ext = info.filePath.split(".").pop();
114
+ if (ext) tags.push(`ext:${ext}`);
115
+ }
116
+ return tags;
117
+ }
118
+
119
+ /**
120
+ * Store a partial memory for a significant tool event.
121
+ *
122
+ * Returns true if stored successfully.
123
+ */
124
+ export function storePartialMemory(
125
+ binary: string,
126
+ cwd: string,
127
+ info: ToolCompletionInfo,
128
+ ): boolean {
129
+ if (!isSignificantToolUse(info)) return false;
130
+
131
+ try {
132
+ const content = buildPartialContent(info);
133
+ const tags = buildPartialTags(info.sessionId, info);
134
+
135
+ execFileSync(
136
+ binary,
137
+ [
138
+ "memory",
139
+ "add",
140
+ "--category=session",
141
+ `--tags=${tags.join(",")}`,
142
+ content,
143
+ ],
144
+ { cwd, stdio: "pipe", timeout: 3000 },
145
+ );
146
+
147
+ debug(SOURCE, `Stored partial: ${content} [${tags.join(", ")}]`);
148
+ return true;
149
+ } catch (err) {
150
+ debug(SOURCE, `Failed to store partial (non-fatal): ${err}`);
151
+ return false;
152
+ }
153
+ }
154
+
155
+ /**
156
+ * Gather all partial memories for a session.
157
+ *
158
+ * Uses `aide memory list` with tag filtering to find all partials.
159
+ * Returns the raw output or null if none found.
160
+ */
161
+ export function gatherPartials(
162
+ binary: string,
163
+ cwd: string,
164
+ sessionId: string,
165
+ ): string[] {
166
+ try {
167
+ const sessionTag = `session:${sessionId.slice(0, 8)}`;
168
+
169
+ const output = execFileSync(
170
+ binary,
171
+ [
172
+ "memory",
173
+ "list",
174
+ "--tags=partial",
175
+ "--all", // Include even if tagged forget (shouldn't be, but defensive)
176
+ "--format=json",
177
+ "--limit=500",
178
+ ],
179
+ {
180
+ cwd,
181
+ encoding: "utf-8",
182
+ stdio: ["pipe", "pipe", "pipe"],
183
+ timeout: 5000,
184
+ },
185
+ ).trim();
186
+
187
+ if (!output || output === "[]") return [];
188
+
189
+ interface PartialMemory {
190
+ id: string;
191
+ tags: string[];
192
+ content: string;
193
+ }
194
+
195
+ const memories: PartialMemory[] = JSON.parse(output);
196
+ // Filter to this session's partials
197
+ return memories
198
+ .filter((m) => m.tags?.includes(sessionTag))
199
+ .map((m) => m.content);
200
+ } catch (err) {
201
+ debug(SOURCE, `Failed to gather partials: ${err}`);
202
+ return [];
203
+ }
204
+ }
205
+
206
+ /**
207
+ * Gather partial memory IDs for a session (for cleanup).
208
+ */
209
+ export function gatherPartialIds(
210
+ binary: string,
211
+ cwd: string,
212
+ sessionId: string,
213
+ ): string[] {
214
+ try {
215
+ const sessionTag = `session:${sessionId.slice(0, 8)}`;
216
+
217
+ const output = execFileSync(
218
+ binary,
219
+ [
220
+ "memory",
221
+ "list",
222
+ "--tags=partial",
223
+ "--all",
224
+ "--format=json",
225
+ "--limit=500",
226
+ ],
227
+ {
228
+ cwd,
229
+ encoding: "utf-8",
230
+ stdio: ["pipe", "pipe", "pipe"],
231
+ timeout: 5000,
232
+ },
233
+ ).trim();
234
+
235
+ if (!output || output === "[]") return [];
236
+
237
+ interface PartialMemory {
238
+ id: string;
239
+ tags: string[];
240
+ }
241
+
242
+ const memories: PartialMemory[] = JSON.parse(output);
243
+ return memories
244
+ .filter((m) => m.tags?.includes(sessionTag))
245
+ .map((m) => m.id);
246
+ } catch (err) {
247
+ debug(SOURCE, `Failed to gather partial IDs: ${err}`);
248
+ return [];
249
+ }
250
+ }
251
+
252
+ /**
253
+ * Clean up partials for a session by tagging them as "forget".
254
+ *
255
+ * This soft-deletes them so they won't appear in future queries
256
+ * but remain recoverable if needed.
257
+ */
258
+ export function cleanupPartials(
259
+ binary: string,
260
+ cwd: string,
261
+ sessionId: string,
262
+ ): number {
263
+ const ids = gatherPartialIds(binary, cwd, sessionId);
264
+ let cleaned = 0;
265
+
266
+ for (const id of ids) {
267
+ try {
268
+ execFileSync(binary, ["memory", "tag", id, "--add=forget"], {
269
+ cwd,
270
+ stdio: "pipe",
271
+ timeout: 3000,
272
+ });
273
+ cleaned++;
274
+ } catch (err) {
275
+ debug(SOURCE, `Failed to cleanup partial ${id}: ${err}`);
276
+ }
277
+ }
278
+
279
+ if (cleaned > 0) {
280
+ debug(
281
+ SOURCE,
282
+ `Cleaned up ${cleaned} partials for session ${sessionId.slice(0, 8)}`,
283
+ );
284
+ }
285
+ return cleaned;
286
+ }
287
+
288
+ /**
289
+ * Build a final session summary that incorporates partials.
290
+ *
291
+ * If partials are available, they're included alongside git data
292
+ * to produce a richer summary than either source alone.
293
+ */
294
+ export function buildSummaryFromPartials(
295
+ partials: string[],
296
+ gitCommits: string[],
297
+ gitFiles: string[],
298
+ ): string | null {
299
+ const summaryParts: string[] = [];
300
+
301
+ // Deduplicate and categorise partials
302
+ const fileChanges = new Set<string>();
303
+ const commands: string[] = [];
304
+ const tasks: string[] = [];
305
+ const other: string[] = [];
306
+
307
+ for (const p of partials) {
308
+ if (p.startsWith("Created file: ") || p.startsWith("Edited file: ")) {
309
+ fileChanges.add(p.replace(/^(Created|Edited) file: /, ""));
310
+ } else if (p.startsWith("Ran command: ")) {
311
+ commands.push(p.replace("Ran command: ", ""));
312
+ } else if (p.startsWith("Completed task: ")) {
313
+ tasks.push(p.replace("Completed task: ", ""));
314
+ } else {
315
+ other.push(p);
316
+ }
317
+ }
318
+
319
+ if (tasks.length > 0) {
320
+ summaryParts.push(
321
+ `## Tasks\n${tasks
322
+ .slice(0, 5)
323
+ .map((t) => `- ${t}`)
324
+ .join("\n")}`,
325
+ );
326
+ }
327
+
328
+ if (gitCommits.length > 0) {
329
+ summaryParts.push(
330
+ `## Commits\n${gitCommits.map((c) => `- ${c}`).join("\n")}`,
331
+ );
332
+ }
333
+
334
+ // Merge file changes from partials and git
335
+ const allFiles = new Set([...fileChanges, ...gitFiles]);
336
+ if (allFiles.size > 0) {
337
+ const files = Array.from(allFiles).slice(0, 15);
338
+ summaryParts.push(
339
+ `## Files Modified\n${files.map((f) => `- ${f}`).join("\n")}`,
340
+ );
341
+ }
342
+
343
+ if (commands.length > 0) {
344
+ summaryParts.push(
345
+ `## Commands\n${commands
346
+ .slice(0, 10)
347
+ .map((c) => `- ${c}`)
348
+ .join("\n")}`,
349
+ );
350
+ }
351
+
352
+ const summary = summaryParts.join("\n\n");
353
+ return summary.length >= 50 ? summary : null;
354
+ }
@@ -241,8 +241,6 @@ export function runSessionInit(
241
241
  return result;
242
242
  }
243
243
 
244
- const dbPath = join(cwd, ".aide", "memory", "store.db");
245
-
246
244
  try {
247
245
  const args = [
248
246
  "session",
@@ -255,7 +253,6 @@ export function runSessionInit(
255
253
  cwd,
256
254
  encoding: "utf-8",
257
255
  timeout: 15000,
258
- env: { ...process.env, AIDE_MEMORY_DB: dbPath },
259
256
  }).trim();
260
257
 
261
258
  if (!output) return result;