@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.
- package/package.json +2 -1
- package/skills/build-fix/SKILL.md +162 -0
- package/skills/code-search/SKILL.md +108 -0
- package/skills/debug/SKILL.md +189 -0
- package/skills/decide/SKILL.md +225 -0
- package/skills/design/SKILL.md +192 -0
- package/skills/docs/SKILL.md +269 -0
- package/skills/git/SKILL.md +181 -0
- package/skills/implement/SKILL.md +240 -0
- package/skills/memorise/SKILL.md +139 -0
- package/skills/perf/SKILL.md +305 -0
- package/skills/plan-swarm/SKILL.md +135 -0
- package/skills/ralph/SKILL.md +483 -0
- package/skills/recall/SKILL.md +90 -0
- package/skills/review/SKILL.md +147 -0
- package/skills/swarm/SKILL.md +501 -0
- package/skills/test/SKILL.md +244 -0
- package/skills/verify/SKILL.md +299 -0
- package/skills/worktree-resolve/SKILL.md +342 -0
- package/src/core/index.ts +1 -0
- package/src/core/partial-memory.ts +354 -0
- package/src/core/session-init.ts +0 -3
- package/src/core/session-summary-logic.ts +2 -6
- package/src/opencode/hooks.ts +237 -25
- package/src/opencode/index.ts +10 -1
- package/src/opencode/types.ts +98 -3
|
@@ -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
|
+
}
|
package/src/core/session-init.ts
CHANGED
|
@@ -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;
|