@sashabogi/foundation 0.1.13 → 2.0.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 +632 -238
- package/dist/cli.d.ts +1 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +159 -1
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +3 -3
- package/dist/index.js.map +1 -1
- package/dist/providers/anthropic.d.ts.map +1 -1
- package/dist/providers/anthropic.js +4 -6
- package/dist/providers/anthropic.js.map +1 -1
- package/dist/providers/fireworks.d.ts +5 -0
- package/dist/providers/fireworks.d.ts.map +1 -1
- package/dist/providers/fireworks.js +18 -0
- package/dist/providers/fireworks.js.map +1 -1
- package/dist/providers/kimi-code.d.ts +5 -0
- package/dist/providers/kimi-code.d.ts.map +1 -1
- package/dist/providers/kimi-code.js +18 -0
- package/dist/providers/kimi-code.js.map +1 -1
- package/dist/providers/zai.d.ts +5 -0
- package/dist/providers/zai.d.ts.map +1 -1
- package/dist/providers/zai.js +18 -0
- package/dist/providers/zai.js.map +1 -1
- package/dist/tools/gaia/index.d.ts +29 -15
- package/dist/tools/gaia/index.d.ts.map +1 -1
- package/dist/tools/gaia/index.js +1206 -274
- package/dist/tools/gaia/index.js.map +1 -1
- package/dist/tools/gaia/storage.d.ts +72 -0
- package/dist/tools/gaia/storage.d.ts.map +1 -0
- package/dist/tools/gaia/storage.js +344 -0
- package/dist/tools/gaia/storage.js.map +1 -0
- package/dist/tools/gaia/test-storage-manual.d.ts +6 -0
- package/dist/tools/gaia/test-storage-manual.d.ts.map +1 -0
- package/dist/tools/gaia/test-storage-manual.js +120 -0
- package/dist/tools/gaia/test-storage-manual.js.map +1 -0
- package/dist/tools/memoria/index.d.ts +25 -0
- package/dist/tools/memoria/index.d.ts.map +1 -0
- package/dist/tools/memoria/index.js +269 -0
- package/dist/tools/memoria/index.js.map +1 -0
- package/dist/tools/memoria/storage.d.ts +72 -0
- package/dist/tools/memoria/storage.d.ts.map +1 -0
- package/dist/tools/memoria/storage.js +344 -0
- package/dist/tools/memoria/storage.js.map +1 -0
- package/dist/tools/memoria/storage.test.d.ts +7 -0
- package/dist/tools/memoria/storage.test.d.ts.map +1 -0
- package/dist/tools/memoria/storage.test.js +350 -0
- package/dist/tools/memoria/storage.test.js.map +1 -0
- package/dist/tools/memoria/test-storage-manual.d.ts +6 -0
- package/dist/tools/memoria/test-storage-manual.d.ts.map +1 -0
- package/dist/tools/memoria/test-storage-manual.js +120 -0
- package/dist/tools/memoria/test-storage-manual.js.map +1 -0
- package/dist/types/index.d.ts +38 -0
- package/dist/types/index.d.ts.map +1 -1
- package/docs/MIGRATION-GUIDE.md +771 -0
- package/package.json +2 -2
- package/docs/REFACTORING-PLAN.md +0 -374
- package/docs/TESTING-WITH-NATIVE-SWARM.md +0 -266
package/dist/tools/gaia/index.js
CHANGED
|
@@ -1,39 +1,59 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Gaia Module Tools
|
|
2
|
+
* Gaia Module Tools v2.0
|
|
3
3
|
*
|
|
4
|
-
* Workflow patterns
|
|
4
|
+
* Workflow patterns + Advanced memory - collective consciousness.
|
|
5
5
|
* Named after Gaia from Foundation - "We are all one, and one is all."
|
|
6
6
|
*
|
|
7
|
-
*
|
|
8
|
-
* seamless handoffs between Claude instances.
|
|
7
|
+
* v2: SQLite + FTS5 memory system with 5-tier hierarchy and BM25 ranking.
|
|
9
8
|
*
|
|
10
|
-
* Tools:
|
|
11
|
-
*
|
|
12
|
-
* -
|
|
13
|
-
* -
|
|
14
|
-
* -
|
|
15
|
-
* -
|
|
16
|
-
* -
|
|
17
|
-
* -
|
|
18
|
-
* -
|
|
9
|
+
* Tools (19 total):
|
|
10
|
+
* Workflow Tools (12):
|
|
11
|
+
* - gaia_checkpoint: Save full structured state to disk
|
|
12
|
+
* - gaia_status: Return lightweight index card (~150 tokens)
|
|
13
|
+
* - gaia_query: Keyword search across checkpoint
|
|
14
|
+
* - gaia_get_decisions: List architectural decisions
|
|
15
|
+
* - gaia_get_progress: Task progress summary
|
|
16
|
+
* - gaia_get_changes: Files changed in session
|
|
17
|
+
* - gaia_handoff: Create session handoff document (NEW v2.0)
|
|
18
|
+
* - gaia_observe: Auto-detect patterns and observations (NEW v2.0)
|
|
19
|
+
* - gaia_migrate: Migrate v1 checkpoint data to v2 (NEW v2.0)
|
|
19
20
|
* - gaia_learn: Record correction for CLAUDE.md
|
|
20
21
|
* - gaia_apply: Write learnings to CLAUDE.md
|
|
21
22
|
* - gaia_review: Review accumulated learnings
|
|
22
|
-
*
|
|
23
|
-
*
|
|
23
|
+
*
|
|
24
|
+
* Memory Tools (5):
|
|
25
|
+
* - gaia_save: Save a new memory (SQLite + FTS5)
|
|
26
|
+
* - gaia_search: Search memories with BM25 + composite scoring
|
|
27
|
+
* - gaia_get: Get memory by ID
|
|
28
|
+
* - gaia_delete: Delete memory + cascade links
|
|
29
|
+
* - gaia_stats: Get memory statistics
|
|
30
|
+
*
|
|
31
|
+
* Linking Tools (2):
|
|
32
|
+
* - gaia_link: Create typed link between memories
|
|
33
|
+
* - gaia_graph: Get link graph for a memory
|
|
24
34
|
*/
|
|
25
35
|
import { z } from 'zod';
|
|
26
36
|
import { nanoid } from 'nanoid';
|
|
37
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
|
|
38
|
+
import { join } from 'path';
|
|
39
|
+
import { homedir } from 'os';
|
|
27
40
|
import { StorageService } from '../../services/storage.service.js';
|
|
28
41
|
import { GitService } from '../../services/git.service.js';
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
42
|
+
import { MemoriaStorage } from './storage.js';
|
|
43
|
+
// =============================================================================
|
|
44
|
+
// Memory Storage (v2.0)
|
|
45
|
+
// =============================================================================
|
|
46
|
+
let memoryStorage = null;
|
|
47
|
+
function getMemoryStorage() {
|
|
48
|
+
if (!memoryStorage) {
|
|
49
|
+
const dbPath = join(homedir(), '.foundation', 'gaia-memory.db');
|
|
50
|
+
memoryStorage = new MemoriaStorage(dbPath);
|
|
51
|
+
}
|
|
52
|
+
return memoryStorage;
|
|
53
|
+
}
|
|
54
|
+
// =============================================================================
|
|
55
|
+
// Zod Schemas
|
|
56
|
+
// =============================================================================
|
|
37
57
|
const LearningCategorySchema = z.enum([
|
|
38
58
|
'code_style',
|
|
39
59
|
'architecture',
|
|
@@ -46,236 +66,863 @@ const LearningCategorySchema = z.enum([
|
|
|
46
66
|
'other',
|
|
47
67
|
]);
|
|
48
68
|
const LearningScopeSchema = z.enum(['global', 'project']);
|
|
69
|
+
const CheckpointDecisionSchema = z.object({
|
|
70
|
+
topic: z.string(),
|
|
71
|
+
decision: z.string(),
|
|
72
|
+
rationale: z.string(),
|
|
73
|
+
timestamp: z.number().optional(),
|
|
74
|
+
});
|
|
75
|
+
const CheckpointProgressSchema = z.object({
|
|
76
|
+
task: z.string(),
|
|
77
|
+
status: z.enum(['pending', 'in_progress', 'completed', 'blocked']),
|
|
78
|
+
details: z.string().optional(),
|
|
79
|
+
});
|
|
80
|
+
const CheckpointChangeSchema = z.object({
|
|
81
|
+
file: z.string(),
|
|
82
|
+
action: z.enum(['created', 'modified', 'deleted']),
|
|
83
|
+
summary: z.string(),
|
|
84
|
+
});
|
|
85
|
+
const CheckpointContextSchema = z.object({
|
|
86
|
+
branch: z.string().optional(),
|
|
87
|
+
openQuestions: z.array(z.string()).optional(),
|
|
88
|
+
relevantFiles: z.array(z.string()).optional(),
|
|
89
|
+
notes: z.string().optional(),
|
|
90
|
+
});
|
|
91
|
+
// =============================================================================
|
|
92
|
+
// Helper Functions
|
|
93
|
+
// =============================================================================
|
|
94
|
+
function getSessionsDir() {
|
|
95
|
+
return join(process.cwd(), '.foundation', 'sessions');
|
|
96
|
+
}
|
|
97
|
+
function ensureDir(dir) {
|
|
98
|
+
if (!existsSync(dir)) {
|
|
99
|
+
mkdirSync(dir, { recursive: true });
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
function generateIndexCard(checkpoint) {
|
|
103
|
+
const lines = [];
|
|
104
|
+
lines.push(`# ${checkpoint.purpose}`);
|
|
105
|
+
lines.push(`Project: ${checkpoint.project || 'unknown'} | Branch: ${checkpoint.sections.context.branch || '?'}`);
|
|
106
|
+
lines.push(`Saved: ${new Date(checkpoint.createdAt).toISOString().slice(0, 16)}`);
|
|
107
|
+
lines.push('');
|
|
108
|
+
lines.push(checkpoint.summary);
|
|
109
|
+
lines.push('');
|
|
110
|
+
const { decisions, progress, changes, context } = checkpoint.sections;
|
|
111
|
+
const completed = progress.filter(p => p.status === 'completed').length;
|
|
112
|
+
const total = progress.length;
|
|
113
|
+
lines.push(`Progress: ${completed}/${total} tasks | ${decisions.length} decisions | ${changes.length} files changed`);
|
|
114
|
+
if (context.openQuestions && context.openQuestions.length > 0) {
|
|
115
|
+
lines.push(`Open questions: ${context.openQuestions.length}`);
|
|
116
|
+
}
|
|
117
|
+
// Hard cap: keep under ~200 tokens (roughly 150 words / 800 chars)
|
|
118
|
+
const result = lines.join('\n');
|
|
119
|
+
if (result.length > 800) {
|
|
120
|
+
return result.slice(0, 797) + '...';
|
|
121
|
+
}
|
|
122
|
+
return result;
|
|
123
|
+
}
|
|
124
|
+
function readJsonFile(filePath) {
|
|
125
|
+
try {
|
|
126
|
+
if (!existsSync(filePath))
|
|
127
|
+
return null;
|
|
128
|
+
const raw = readFileSync(filePath, 'utf-8');
|
|
129
|
+
return JSON.parse(raw);
|
|
130
|
+
}
|
|
131
|
+
catch {
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
// =============================================================================
|
|
136
|
+
// Tool Registration
|
|
137
|
+
// =============================================================================
|
|
49
138
|
export function registerGaiaTools(server) {
|
|
50
139
|
const storage = StorageService.getInstance();
|
|
51
140
|
const git = GitService.getInstance();
|
|
52
141
|
// ===========================================================================
|
|
53
|
-
//
|
|
142
|
+
// Checkpoint Tools (v2)
|
|
54
143
|
// ===========================================================================
|
|
55
|
-
//
|
|
56
|
-
server.tool('
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
144
|
+
// gaia_checkpoint — Save full structured state to disk
|
|
145
|
+
server.tool('gaia_checkpoint', 'Save full session state to disk. The ONLY write operation. Creates checkpoint.json, index.txt, and decisions.json in .foundation/sessions/latest/.', {
|
|
146
|
+
summary: z.string().describe('One-line summary of current state (~20 words)'),
|
|
147
|
+
purpose: z.string().describe('What this session is working on'),
|
|
148
|
+
project: z.string().optional().describe('Project name'),
|
|
149
|
+
decisions: z.array(CheckpointDecisionSchema).optional().describe('Architectural decisions made'),
|
|
150
|
+
progress: z.array(CheckpointProgressSchema).optional().describe('Task progress'),
|
|
151
|
+
changes: z.array(CheckpointChangeSchema).optional().describe('Files changed'),
|
|
152
|
+
context: CheckpointContextSchema.optional().describe('Additional context (branch, questions, files)'),
|
|
153
|
+
}, async ({ summary, purpose, project, decisions = [], progress = [], changes = [], context = {} }) => {
|
|
62
154
|
try {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
155
|
+
// Auto-fill branch from git if not provided
|
|
156
|
+
let branch = context.branch;
|
|
157
|
+
if (!branch) {
|
|
158
|
+
try {
|
|
159
|
+
branch = await git.getCurrentBranch();
|
|
160
|
+
}
|
|
161
|
+
catch {
|
|
162
|
+
branch = undefined;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
const sections = {
|
|
166
|
+
decisions: decisions.map(d => ({
|
|
167
|
+
...d,
|
|
168
|
+
timestamp: d.timestamp || Date.now(),
|
|
169
|
+
})),
|
|
170
|
+
progress,
|
|
171
|
+
changes,
|
|
172
|
+
context: { ...context, branch },
|
|
173
|
+
};
|
|
174
|
+
const checkpoint = {
|
|
175
|
+
id: `ckpt_${nanoid(8)}`,
|
|
176
|
+
version: 1,
|
|
74
177
|
createdAt: Date.now(),
|
|
75
|
-
|
|
178
|
+
project,
|
|
179
|
+
summary,
|
|
180
|
+
purpose,
|
|
181
|
+
sections,
|
|
76
182
|
};
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
183
|
+
// Write to .foundation/sessions/latest/
|
|
184
|
+
const latestDir = join(getSessionsDir(), 'latest');
|
|
185
|
+
ensureDir(latestDir);
|
|
186
|
+
// 1. Full checkpoint
|
|
187
|
+
writeFileSync(join(latestDir, 'checkpoint.json'), JSON.stringify(checkpoint, null, 2), 'utf-8');
|
|
188
|
+
// 2. Index card (~150 tokens)
|
|
189
|
+
const indexCard = generateIndexCard(checkpoint);
|
|
190
|
+
writeFileSync(join(latestDir, 'index.txt'), indexCard, 'utf-8');
|
|
191
|
+
// 3. Decisions extract
|
|
192
|
+
writeFileSync(join(latestDir, 'decisions.json'), JSON.stringify(sections.decisions, null, 2), 'utf-8');
|
|
193
|
+
// Also archive by ID
|
|
194
|
+
const archiveDir = join(getSessionsDir(), checkpoint.id);
|
|
195
|
+
ensureDir(archiveDir);
|
|
196
|
+
writeFileSync(join(archiveDir, 'checkpoint.json'), JSON.stringify(checkpoint, null, 2), 'utf-8');
|
|
197
|
+
writeFileSync(join(archiveDir, 'index.txt'), indexCard, 'utf-8');
|
|
198
|
+
writeFileSync(join(archiveDir, 'decisions.json'), JSON.stringify(sections.decisions, null, 2), 'utf-8');
|
|
83
199
|
return {
|
|
84
|
-
content: [{
|
|
200
|
+
content: [{
|
|
201
|
+
type: 'text',
|
|
202
|
+
text: `✓ Checkpoint saved: ${checkpoint.id}\n\n${indexCard}`,
|
|
203
|
+
}],
|
|
85
204
|
};
|
|
86
205
|
}
|
|
87
206
|
catch (error) {
|
|
88
207
|
return {
|
|
89
|
-
content: [{ type: 'text', text: `Error: ${error.message}` }],
|
|
208
|
+
content: [{ type: 'text', text: `Error saving checkpoint: ${error.message}` }],
|
|
90
209
|
};
|
|
91
210
|
}
|
|
92
211
|
});
|
|
93
|
-
//
|
|
94
|
-
server.tool('
|
|
95
|
-
const
|
|
96
|
-
if (
|
|
212
|
+
// gaia_status — Return lightweight index card only
|
|
213
|
+
server.tool('gaia_status', 'Return the index card for the latest checkpoint (~150 tokens). Use this at session start to understand context without loading full state.', {}, async () => {
|
|
214
|
+
const indexPath = join(getSessionsDir(), 'latest', 'index.txt');
|
|
215
|
+
if (!existsSync(indexPath)) {
|
|
97
216
|
return {
|
|
98
|
-
content: [{ type: 'text', text: 'No
|
|
217
|
+
content: [{ type: 'text', text: 'No checkpoint found. Use gaia_checkpoint to save session state.' }],
|
|
99
218
|
};
|
|
100
219
|
}
|
|
101
|
-
|
|
102
|
-
for (const wt of worktrees) {
|
|
103
|
-
output += `### ${wt.alias ? `[${wt.alias}] ` : ''}${wt.branch}\n`;
|
|
104
|
-
output += `- Path: \`${wt.path}\`\n`;
|
|
105
|
-
output += `- Status: ${wt.status}\n`;
|
|
106
|
-
if (wt.purpose)
|
|
107
|
-
output += `- Purpose: ${wt.purpose}\n`;
|
|
108
|
-
output += '\n';
|
|
109
|
-
}
|
|
220
|
+
const indexCard = readFileSync(indexPath, 'utf-8');
|
|
110
221
|
return {
|
|
111
|
-
content: [{ type: 'text', text:
|
|
222
|
+
content: [{ type: 'text', text: indexCard }],
|
|
112
223
|
};
|
|
113
224
|
});
|
|
114
|
-
//
|
|
115
|
-
server.tool('
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
if (!
|
|
225
|
+
// gaia_query — Keyword search across checkpoint
|
|
226
|
+
server.tool('gaia_query', 'Search the latest checkpoint by keyword. Returns only matching sections to minimize token usage.', {
|
|
227
|
+
keyword: z.string().describe('Keyword or phrase to search for (case-insensitive)'),
|
|
228
|
+
section: z.enum(['decisions', 'progress', 'changes', 'context']).optional().describe('Limit search to a specific section'),
|
|
229
|
+
}, async ({ keyword, section }) => {
|
|
230
|
+
const checkpoint = readJsonFile(join(getSessionsDir(), 'latest', 'checkpoint.json'));
|
|
231
|
+
if (!checkpoint) {
|
|
232
|
+
return {
|
|
233
|
+
content: [{ type: 'text', text: 'No checkpoint found. Use gaia_checkpoint to save session state.' }],
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
const kw = keyword.toLowerCase();
|
|
237
|
+
const results = [];
|
|
238
|
+
const searchIn = section ? [section] : ['decisions', 'progress', 'changes', 'context'];
|
|
239
|
+
for (const s of searchIn) {
|
|
240
|
+
if (s === 'decisions') {
|
|
241
|
+
const matches = checkpoint.sections.decisions.filter(d => d.topic.toLowerCase().includes(kw) ||
|
|
242
|
+
d.decision.toLowerCase().includes(kw) ||
|
|
243
|
+
d.rationale.toLowerCase().includes(kw));
|
|
244
|
+
if (matches.length > 0) {
|
|
245
|
+
results.push(`## Decisions (${matches.length} matches)`);
|
|
246
|
+
for (const m of matches) {
|
|
247
|
+
results.push(`- **${m.topic}**: ${m.decision}`);
|
|
248
|
+
results.push(` _Rationale_: ${m.rationale}`);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
if (s === 'progress') {
|
|
253
|
+
const matches = checkpoint.sections.progress.filter(p => p.task.toLowerCase().includes(kw) ||
|
|
254
|
+
(p.details?.toLowerCase().includes(kw) ?? false));
|
|
255
|
+
if (matches.length > 0) {
|
|
256
|
+
results.push(`## Progress (${matches.length} matches)`);
|
|
257
|
+
for (const m of matches) {
|
|
258
|
+
results.push(`- [${m.status}] ${m.task}${m.details ? `: ${m.details}` : ''}`);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
if (s === 'changes') {
|
|
263
|
+
const matches = checkpoint.sections.changes.filter(c => c.file.toLowerCase().includes(kw) ||
|
|
264
|
+
c.summary.toLowerCase().includes(kw));
|
|
265
|
+
if (matches.length > 0) {
|
|
266
|
+
results.push(`## Changes (${matches.length} matches)`);
|
|
267
|
+
for (const m of matches) {
|
|
268
|
+
results.push(`- ${m.action} \`${m.file}\`: ${m.summary}`);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
if (s === 'context') {
|
|
273
|
+
const ctx = checkpoint.sections.context;
|
|
274
|
+
const ctxMatches = [];
|
|
275
|
+
if (ctx.branch?.toLowerCase().includes(kw))
|
|
276
|
+
ctxMatches.push(`Branch: ${ctx.branch}`);
|
|
277
|
+
if (ctx.notes?.toLowerCase().includes(kw))
|
|
278
|
+
ctxMatches.push(`Notes: ${ctx.notes}`);
|
|
279
|
+
if (ctx.openQuestions) {
|
|
280
|
+
const qMatches = ctx.openQuestions.filter(q => q.toLowerCase().includes(kw));
|
|
281
|
+
if (qMatches.length > 0)
|
|
282
|
+
ctxMatches.push(`Questions: ${qMatches.join('; ')}`);
|
|
283
|
+
}
|
|
284
|
+
if (ctx.relevantFiles) {
|
|
285
|
+
const fMatches = ctx.relevantFiles.filter(f => f.toLowerCase().includes(kw));
|
|
286
|
+
if (fMatches.length > 0)
|
|
287
|
+
ctxMatches.push(`Files: ${fMatches.join(', ')}`);
|
|
288
|
+
}
|
|
289
|
+
if (ctxMatches.length > 0) {
|
|
290
|
+
results.push(`## Context (${ctxMatches.length} matches)`);
|
|
291
|
+
results.push(...ctxMatches.map(m => `- ${m}`));
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
if (results.length === 0) {
|
|
121
296
|
return {
|
|
122
|
-
content: [{ type: 'text', text: `No
|
|
297
|
+
content: [{ type: 'text', text: `No matches for "${keyword}" in checkpoint.` }],
|
|
123
298
|
};
|
|
124
299
|
}
|
|
125
|
-
storage.updateWorktree(worktree.id, { lastAccessedAt: Date.now() });
|
|
126
300
|
return {
|
|
127
|
-
content: [
|
|
128
|
-
{
|
|
129
|
-
type: 'text',
|
|
130
|
-
text: `To switch: \`cd ${worktree.path} && claude\``,
|
|
131
|
-
},
|
|
132
|
-
],
|
|
301
|
+
content: [{ type: 'text', text: results.join('\n') }],
|
|
133
302
|
};
|
|
134
303
|
});
|
|
135
|
-
//
|
|
136
|
-
server.tool('
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
304
|
+
// gaia_get_decisions — List architectural decisions
|
|
305
|
+
server.tool('gaia_get_decisions', 'Get all architectural decisions from the latest checkpoint. Reads decisions.json directly.', {}, async () => {
|
|
306
|
+
const decisions = readJsonFile(join(getSessionsDir(), 'latest', 'decisions.json'));
|
|
307
|
+
if (!decisions || decisions.length === 0) {
|
|
308
|
+
return {
|
|
309
|
+
content: [{ type: 'text', text: 'No decisions recorded. Use gaia_checkpoint to save decisions.' }],
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
let output = `## Decisions (${decisions.length})\n\n`;
|
|
313
|
+
for (const d of decisions) {
|
|
314
|
+
output += `### ${d.topic}\n`;
|
|
315
|
+
output += `**Decision**: ${d.decision}\n`;
|
|
316
|
+
output += `**Rationale**: ${d.rationale}\n`;
|
|
317
|
+
output += `_${new Date(d.timestamp).toISOString().slice(0, 16)}_\n\n`;
|
|
318
|
+
}
|
|
141
319
|
return {
|
|
142
|
-
content: [{ type: 'text', text:
|
|
320
|
+
content: [{ type: 'text', text: output }],
|
|
143
321
|
};
|
|
144
322
|
});
|
|
145
|
-
//
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
323
|
+
// gaia_get_progress — Task progress summary
|
|
324
|
+
server.tool('gaia_get_progress', 'Get task progress from the latest checkpoint.', {}, async () => {
|
|
325
|
+
const checkpoint = readJsonFile(join(getSessionsDir(), 'latest', 'checkpoint.json'));
|
|
326
|
+
if (!checkpoint) {
|
|
327
|
+
return {
|
|
328
|
+
content: [{ type: 'text', text: 'No checkpoint found. Use gaia_checkpoint to save session state.' }],
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
const { progress } = checkpoint.sections;
|
|
332
|
+
if (progress.length === 0) {
|
|
333
|
+
return {
|
|
334
|
+
content: [{ type: 'text', text: 'No tasks tracked. Use gaia_checkpoint with progress data.' }],
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
const completed = progress.filter(p => p.status === 'completed').length;
|
|
338
|
+
const inProgress = progress.filter(p => p.status === 'in_progress').length;
|
|
339
|
+
const blocked = progress.filter(p => p.status === 'blocked').length;
|
|
340
|
+
const pending = progress.filter(p => p.status === 'pending').length;
|
|
341
|
+
let output = `## Progress: ${completed}/${progress.length} complete\n\n`;
|
|
342
|
+
output += `Completed: ${completed} | In progress: ${inProgress} | Blocked: ${blocked} | Pending: ${pending}\n\n`;
|
|
343
|
+
for (const p of progress) {
|
|
344
|
+
const icon = p.status === 'completed' ? '✓' : p.status === 'in_progress' ? '→' : p.status === 'blocked' ? '✗' : '○';
|
|
345
|
+
output += `${icon} [${p.status}] ${p.task}`;
|
|
346
|
+
if (p.details)
|
|
347
|
+
output += ` — ${p.details}`;
|
|
348
|
+
output += '\n';
|
|
349
|
+
}
|
|
167
350
|
return {
|
|
168
|
-
content: [
|
|
169
|
-
{
|
|
170
|
-
type: 'text',
|
|
171
|
-
text: `✓ Session registered: ${session.id}\n\nPurpose: ${purpose}\nType: ${taskType}`,
|
|
172
|
-
},
|
|
173
|
-
],
|
|
351
|
+
content: [{ type: 'text', text: output }],
|
|
174
352
|
};
|
|
175
353
|
});
|
|
176
|
-
//
|
|
177
|
-
server.tool('
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
if (sessionId) {
|
|
181
|
-
const session = storage.getSession(sessionId);
|
|
182
|
-
if (!session) {
|
|
183
|
-
return {
|
|
184
|
-
content: [{ type: 'text', text: `Session not found: ${sessionId}` }],
|
|
185
|
-
};
|
|
186
|
-
}
|
|
354
|
+
// gaia_get_changes — Files changed in session
|
|
355
|
+
server.tool('gaia_get_changes', 'Get files changed from the latest checkpoint.', {}, async () => {
|
|
356
|
+
const checkpoint = readJsonFile(join(getSessionsDir(), 'latest', 'checkpoint.json'));
|
|
357
|
+
if (!checkpoint) {
|
|
187
358
|
return {
|
|
188
|
-
content: [
|
|
189
|
-
{
|
|
190
|
-
type: 'text',
|
|
191
|
-
text: `## Session ${session.id}\n\n- Purpose: ${session.purpose}\n- Status: ${session.status}\n- Type: ${session.taskType}`,
|
|
192
|
-
},
|
|
193
|
-
],
|
|
359
|
+
content: [{ type: 'text', text: 'No checkpoint found. Use gaia_checkpoint to save session state.' }],
|
|
194
360
|
};
|
|
195
361
|
}
|
|
196
|
-
const
|
|
197
|
-
if (
|
|
362
|
+
const { changes } = checkpoint.sections;
|
|
363
|
+
if (changes.length === 0) {
|
|
198
364
|
return {
|
|
199
|
-
content: [{ type: 'text', text: 'No
|
|
365
|
+
content: [{ type: 'text', text: 'No changes tracked. Use gaia_checkpoint with changes data.' }],
|
|
200
366
|
};
|
|
201
367
|
}
|
|
202
|
-
let output =
|
|
203
|
-
for (const
|
|
204
|
-
output += `- **${
|
|
368
|
+
let output = `## Changes (${changes.length} files)\n\n`;
|
|
369
|
+
for (const c of changes) {
|
|
370
|
+
output += `- **${c.action}** \`${c.file}\`: ${c.summary}\n`;
|
|
205
371
|
}
|
|
206
372
|
return {
|
|
207
373
|
content: [{ type: 'text', text: output }],
|
|
208
374
|
};
|
|
209
375
|
});
|
|
210
|
-
//
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
376
|
+
// ===========================================================================
|
|
377
|
+
// Session Handoff Tool (v2)
|
|
378
|
+
// ===========================================================================
|
|
379
|
+
// gaia_handoff — Create session handoff document
|
|
380
|
+
server.tool('gaia_handoff', 'Create a comprehensive handoff document for transferring session context to the next session. Combines checkpoint data with relevant memories.', {
|
|
381
|
+
next_steps: z.array(z.string()).describe('List of next steps for the next session'),
|
|
382
|
+
context_notes: z.string().optional().describe('Additional context notes for the next session'),
|
|
383
|
+
include_memories: z.boolean().optional().describe('Include relevant memories from memory system (default: true)'),
|
|
384
|
+
memory_query: z.string().optional().describe('Query to find relevant memories (default: recent project memories)'),
|
|
385
|
+
}, async ({ next_steps, context_notes, include_memories = true, memory_query }) => {
|
|
386
|
+
try {
|
|
387
|
+
const handoffDir = join(homedir(), '.foundation', 'handoffs');
|
|
388
|
+
ensureDir(handoffDir);
|
|
389
|
+
// Read latest checkpoint if exists
|
|
390
|
+
const latestDir = join(getSessionsDir(), 'latest');
|
|
391
|
+
const checkpoint = readJsonFile(join(latestDir, 'checkpoint.json'));
|
|
392
|
+
// Build handoff document
|
|
393
|
+
const lines = [];
|
|
394
|
+
lines.push('# Session Handoff Document');
|
|
395
|
+
lines.push('');
|
|
396
|
+
lines.push(`Generated: ${new Date().toISOString()}`);
|
|
397
|
+
lines.push('');
|
|
398
|
+
lines.push('---');
|
|
399
|
+
lines.push('');
|
|
400
|
+
// Session state
|
|
401
|
+
if (checkpoint) {
|
|
402
|
+
lines.push('## Current Session State');
|
|
403
|
+
lines.push('');
|
|
404
|
+
lines.push(`**Project**: ${checkpoint.project || 'N/A'}`);
|
|
405
|
+
lines.push(`**Purpose**: ${checkpoint.purpose}`);
|
|
406
|
+
lines.push(`**Branch**: ${checkpoint.sections.context.branch || 'N/A'}`);
|
|
407
|
+
lines.push('');
|
|
408
|
+
lines.push(`**Summary**: ${checkpoint.summary}`);
|
|
409
|
+
lines.push('');
|
|
410
|
+
// Decisions
|
|
411
|
+
if (checkpoint.sections.decisions.length > 0) {
|
|
412
|
+
lines.push('### Recent Decisions');
|
|
413
|
+
lines.push('');
|
|
414
|
+
checkpoint.sections.decisions.forEach(d => {
|
|
415
|
+
lines.push(`**${d.topic}**`);
|
|
416
|
+
lines.push(`- Decision: ${d.decision}`);
|
|
417
|
+
lines.push(`- Rationale: ${d.rationale}`);
|
|
418
|
+
lines.push('');
|
|
419
|
+
});
|
|
420
|
+
}
|
|
421
|
+
// Progress
|
|
422
|
+
if (checkpoint.sections.progress.length > 0) {
|
|
423
|
+
lines.push('### Task Progress');
|
|
424
|
+
lines.push('');
|
|
425
|
+
checkpoint.sections.progress.forEach(p => {
|
|
426
|
+
const emoji = p.status === 'completed' ? '✓' : p.status === 'in_progress' ? '⏳' : p.status === 'blocked' ? '🚫' : '○';
|
|
427
|
+
lines.push(`${emoji} **${p.task}** (${p.status})`);
|
|
428
|
+
if (p.details)
|
|
429
|
+
lines.push(` ${p.details}`);
|
|
430
|
+
lines.push('');
|
|
431
|
+
});
|
|
432
|
+
}
|
|
433
|
+
// Changes
|
|
434
|
+
if (checkpoint.sections.changes.length > 0) {
|
|
435
|
+
lines.push('### Files Changed');
|
|
436
|
+
lines.push('');
|
|
437
|
+
checkpoint.sections.changes.forEach(c => {
|
|
438
|
+
const emoji = c.action === 'created' ? '+' : c.action === 'modified' ? '~' : '-';
|
|
439
|
+
lines.push(`${emoji} \`${c.file}\`: ${c.summary}`);
|
|
440
|
+
});
|
|
441
|
+
lines.push('');
|
|
442
|
+
}
|
|
443
|
+
// Open questions
|
|
444
|
+
if (checkpoint.sections.context.openQuestions && checkpoint.sections.context.openQuestions.length > 0) {
|
|
445
|
+
lines.push('### Open Questions');
|
|
446
|
+
lines.push('');
|
|
447
|
+
checkpoint.sections.context.openQuestions.forEach(q => {
|
|
448
|
+
lines.push(`- ${q}`);
|
|
449
|
+
});
|
|
450
|
+
lines.push('');
|
|
451
|
+
}
|
|
452
|
+
// Relevant files
|
|
453
|
+
if (checkpoint.sections.context.relevantFiles && checkpoint.sections.context.relevantFiles.length > 0) {
|
|
454
|
+
lines.push('### Relevant Files');
|
|
455
|
+
lines.push('');
|
|
456
|
+
checkpoint.sections.context.relevantFiles.forEach(f => {
|
|
457
|
+
lines.push(`- \`${f}\``);
|
|
458
|
+
});
|
|
459
|
+
lines.push('');
|
|
460
|
+
}
|
|
461
|
+
// Context notes from checkpoint
|
|
462
|
+
if (checkpoint.sections.context.notes) {
|
|
463
|
+
lines.push('### Context Notes');
|
|
464
|
+
lines.push('');
|
|
465
|
+
lines.push(checkpoint.sections.context.notes);
|
|
466
|
+
lines.push('');
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
else {
|
|
470
|
+
lines.push('## Current Session State');
|
|
471
|
+
lines.push('');
|
|
472
|
+
lines.push('_No checkpoint found for current session._');
|
|
473
|
+
lines.push('');
|
|
236
474
|
}
|
|
237
|
-
|
|
238
|
-
|
|
475
|
+
// Relevant memories
|
|
476
|
+
if (include_memories) {
|
|
477
|
+
lines.push('---');
|
|
478
|
+
lines.push('');
|
|
479
|
+
lines.push('## Relevant Memories');
|
|
480
|
+
lines.push('');
|
|
481
|
+
try {
|
|
482
|
+
const storage = getMemoryStorage();
|
|
483
|
+
const query = memory_query || (checkpoint?.project || 'project');
|
|
484
|
+
const memories = storage.search({
|
|
485
|
+
query,
|
|
486
|
+
tiers: ['project', 'session'],
|
|
487
|
+
limit: 10,
|
|
488
|
+
});
|
|
489
|
+
if (memories.length > 0) {
|
|
490
|
+
memories.forEach((result, idx) => {
|
|
491
|
+
lines.push(`### ${idx + 1}. ${result.memory.content.substring(0, 100)}...`);
|
|
492
|
+
lines.push('');
|
|
493
|
+
lines.push(`- **Tier**: ${result.memory.tier}`);
|
|
494
|
+
lines.push(`- **Tags**: ${result.memory.tags.join(', ')}`);
|
|
495
|
+
lines.push(`- **Relevance**: ${result.score.toFixed(2)}`);
|
|
496
|
+
if (result.memory.related_files.length > 0) {
|
|
497
|
+
lines.push(`- **Files**: ${result.memory.related_files.join(', ')}`);
|
|
498
|
+
}
|
|
499
|
+
lines.push('');
|
|
500
|
+
lines.push(result.memory.content);
|
|
501
|
+
lines.push('');
|
|
502
|
+
});
|
|
503
|
+
}
|
|
504
|
+
else {
|
|
505
|
+
lines.push('_No relevant memories found._');
|
|
506
|
+
lines.push('');
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
catch (error) {
|
|
510
|
+
lines.push('_Error retrieving memories._');
|
|
511
|
+
lines.push('');
|
|
512
|
+
}
|
|
239
513
|
}
|
|
514
|
+
// Next steps
|
|
515
|
+
lines.push('---');
|
|
516
|
+
lines.push('');
|
|
517
|
+
lines.push('## Next Steps');
|
|
518
|
+
lines.push('');
|
|
519
|
+
next_steps.forEach((step, idx) => {
|
|
520
|
+
lines.push(`${idx + 1}. ${step}`);
|
|
521
|
+
});
|
|
522
|
+
lines.push('');
|
|
523
|
+
// Additional context
|
|
524
|
+
if (context_notes) {
|
|
525
|
+
lines.push('---');
|
|
526
|
+
lines.push('');
|
|
527
|
+
lines.push('## Additional Context');
|
|
528
|
+
lines.push('');
|
|
529
|
+
lines.push(context_notes);
|
|
530
|
+
lines.push('');
|
|
531
|
+
}
|
|
532
|
+
// Footer
|
|
533
|
+
lines.push('---');
|
|
534
|
+
lines.push('');
|
|
535
|
+
lines.push('*Generated by Foundation Gaia v2.0*');
|
|
536
|
+
const handoffContent = lines.join('\n');
|
|
537
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);
|
|
538
|
+
const filename = `handoff-${timestamp}.md`;
|
|
539
|
+
const filePath = join(handoffDir, filename);
|
|
540
|
+
writeFileSync(filePath, handoffContent, 'utf-8');
|
|
541
|
+
return {
|
|
542
|
+
content: [{
|
|
543
|
+
type: 'text',
|
|
544
|
+
text: `✓ Handoff document created: ${filename}\n\nPath: ${filePath}\n\n${handoffContent.substring(0, 500)}...\n\n[Full document saved to file]`,
|
|
545
|
+
}],
|
|
546
|
+
};
|
|
547
|
+
}
|
|
548
|
+
catch (error) {
|
|
549
|
+
return {
|
|
550
|
+
content: [{
|
|
551
|
+
type: 'text',
|
|
552
|
+
text: `Error creating handoff: ${error.message}`,
|
|
553
|
+
}],
|
|
554
|
+
isError: true,
|
|
555
|
+
};
|
|
240
556
|
}
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
557
|
+
});
|
|
558
|
+
// ===========================================================================
|
|
559
|
+
// Autonomous Observation System (v2)
|
|
560
|
+
// ===========================================================================
|
|
561
|
+
/**
|
|
562
|
+
* Analyze session patterns and detect observations
|
|
563
|
+
*/
|
|
564
|
+
function analyzeSessionPatterns(options) {
|
|
565
|
+
const observations = [];
|
|
566
|
+
const { checkpoint, lookback_days = 30 } = options;
|
|
567
|
+
try {
|
|
568
|
+
const memStorage = getMemoryStorage();
|
|
569
|
+
const cutoffTime = Date.now() - (lookback_days * 24 * 60 * 60 * 1000);
|
|
570
|
+
// Pattern 1: Repeated topics in decisions
|
|
571
|
+
if (checkpoint && checkpoint.sections.decisions.length > 0) {
|
|
572
|
+
const topics = checkpoint.sections.decisions.map(d => d.topic);
|
|
573
|
+
const topicCounts = new Map();
|
|
574
|
+
topics.forEach(t => topicCounts.set(t, (topicCounts.get(t) || 0) + 1));
|
|
575
|
+
topicCounts.forEach((count, topic) => {
|
|
576
|
+
if (count >= 2) {
|
|
577
|
+
observations.push({
|
|
578
|
+
pattern: `Repeated decision-making on: ${topic}`,
|
|
579
|
+
evidence: `${count} decisions made about ${topic} in current session`,
|
|
580
|
+
confidence: count >= 3 ? 'high' : 'medium',
|
|
581
|
+
});
|
|
582
|
+
}
|
|
583
|
+
});
|
|
584
|
+
}
|
|
585
|
+
// Pattern 2: Blocked tasks
|
|
586
|
+
if (checkpoint && checkpoint.sections.progress.length > 0) {
|
|
587
|
+
const blockedTasks = checkpoint.sections.progress.filter(p => p.status === 'blocked');
|
|
588
|
+
if (blockedTasks.length > 0) {
|
|
589
|
+
const blockers = blockedTasks.map(t => `"${t.task}"`).join(', ');
|
|
590
|
+
observations.push({
|
|
591
|
+
pattern: 'Task blockers detected',
|
|
592
|
+
evidence: `${blockedTasks.length} blocked task(s): ${blockers}`,
|
|
593
|
+
confidence: 'high',
|
|
594
|
+
});
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
// Pattern 3: Frequent file changes
|
|
598
|
+
if (checkpoint && checkpoint.sections.changes.length > 0) {
|
|
599
|
+
const files = checkpoint.sections.changes.map(c => c.file);
|
|
600
|
+
const fileCounts = new Map();
|
|
601
|
+
files.forEach(f => fileCounts.set(f, (fileCounts.get(f) || 0) + 1));
|
|
602
|
+
fileCounts.forEach((count, file) => {
|
|
603
|
+
if (count >= 3) {
|
|
604
|
+
observations.push({
|
|
605
|
+
pattern: `Frequent modifications to: ${file}`,
|
|
606
|
+
evidence: `${count} changes to ${file} in current session`,
|
|
607
|
+
confidence: 'medium',
|
|
608
|
+
});
|
|
609
|
+
}
|
|
610
|
+
});
|
|
611
|
+
}
|
|
612
|
+
// Pattern 4: Open questions (knowledge gaps)
|
|
613
|
+
if (checkpoint && checkpoint.sections.context.openQuestions && checkpoint.sections.context.openQuestions.length > 0) {
|
|
614
|
+
const questions = checkpoint.sections.context.openQuestions;
|
|
615
|
+
if (questions.length >= 3) {
|
|
616
|
+
observations.push({
|
|
617
|
+
pattern: 'Multiple open questions - potential knowledge gaps',
|
|
618
|
+
evidence: `${questions.length} unresolved questions: ${questions.slice(0, 2).join('; ')}${questions.length > 2 ? '...' : ''}`,
|
|
619
|
+
confidence: 'medium',
|
|
620
|
+
});
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
// Pattern 5: Frequent tags in recent memories
|
|
624
|
+
const allMemories = memStorage.search({
|
|
625
|
+
query: '*',
|
|
626
|
+
tiers: ['project', 'session'],
|
|
627
|
+
limit: 100,
|
|
628
|
+
});
|
|
629
|
+
const recentMemories = allMemories.filter(m => m.memory.created_at >= cutoffTime);
|
|
630
|
+
if (recentMemories.length >= 5) {
|
|
631
|
+
const tagCounts = new Map();
|
|
632
|
+
recentMemories.forEach(m => {
|
|
633
|
+
m.memory.tags.forEach(tag => {
|
|
634
|
+
tagCounts.set(tag, (tagCounts.get(tag) || 0) + 1);
|
|
635
|
+
});
|
|
636
|
+
});
|
|
637
|
+
const sortedTags = Array.from(tagCounts.entries())
|
|
638
|
+
.sort((a, b) => b[1] - a[1])
|
|
639
|
+
.slice(0, 3);
|
|
640
|
+
if (sortedTags.length > 0 && sortedTags[0][1] >= 5) {
|
|
641
|
+
const topTags = sortedTags.map(([tag, count]) => `${tag} (${count}x)`).join(', ');
|
|
642
|
+
observations.push({
|
|
643
|
+
pattern: `High activity in areas: ${sortedTags.map(t => t[0]).join(', ')}`,
|
|
644
|
+
evidence: `Frequent tags in recent memories: ${topTags}`,
|
|
645
|
+
confidence: 'high',
|
|
646
|
+
});
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
// Pattern 6: Reverted changes (created then deleted)
|
|
650
|
+
if (checkpoint && checkpoint.sections.changes.length >= 2) {
|
|
651
|
+
const fileActions = new Map();
|
|
652
|
+
checkpoint.sections.changes.forEach(c => {
|
|
653
|
+
if (!fileActions.has(c.file))
|
|
654
|
+
fileActions.set(c.file, []);
|
|
655
|
+
fileActions.get(c.file).push(c.action);
|
|
656
|
+
});
|
|
657
|
+
fileActions.forEach((actions, file) => {
|
|
658
|
+
if (actions.includes('created') && actions.includes('deleted')) {
|
|
659
|
+
observations.push({
|
|
660
|
+
pattern: `File created and deleted: ${file}`,
|
|
661
|
+
evidence: 'May indicate trial-and-error or false start',
|
|
662
|
+
confidence: 'medium',
|
|
663
|
+
});
|
|
664
|
+
}
|
|
665
|
+
});
|
|
245
666
|
}
|
|
246
667
|
}
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
}
|
|
668
|
+
catch (error) {
|
|
669
|
+
// Silently fail pattern analysis - don't break the tool
|
|
670
|
+
}
|
|
671
|
+
return observations;
|
|
672
|
+
}
|
|
673
|
+
// gaia_observe — Analyze patterns and auto-save observations
|
|
674
|
+
server.tool('gaia_observe', 'Analyze session patterns and automatically capture observations. Detects recurring patterns, blockers, knowledge gaps, and anti-patterns.', {
|
|
675
|
+
lookback_days: z.number().optional().describe('Days of history to analyze (default: 30)'),
|
|
676
|
+
auto_save: z.boolean().optional().describe('Automatically save observations to memory (default: true)'),
|
|
677
|
+
min_confidence: z.enum(['low', 'medium', 'high']).optional().describe('Minimum confidence level to save (default: medium)'),
|
|
678
|
+
}, async ({ lookback_days = 30, auto_save = true, min_confidence = 'medium' }) => {
|
|
679
|
+
try {
|
|
680
|
+
// Read latest checkpoint
|
|
681
|
+
const latestDir = join(getSessionsDir(), 'latest');
|
|
682
|
+
const checkpoint = readJsonFile(join(latestDir, 'checkpoint.json'));
|
|
683
|
+
// Analyze patterns
|
|
684
|
+
const patterns = analyzeSessionPatterns({ checkpoint, lookback_days });
|
|
685
|
+
if (patterns.length === 0) {
|
|
686
|
+
return {
|
|
687
|
+
content: [{
|
|
688
|
+
type: 'text',
|
|
689
|
+
text: '✓ Analysis complete - no significant patterns detected.',
|
|
690
|
+
}],
|
|
691
|
+
};
|
|
692
|
+
}
|
|
693
|
+
// Filter by confidence
|
|
694
|
+
const confidenceLevels = { low: 1, medium: 2, high: 3 };
|
|
695
|
+
const minLevel = confidenceLevels[min_confidence];
|
|
696
|
+
const filteredPatterns = patterns.filter(p => confidenceLevels[p.confidence] >= minLevel);
|
|
697
|
+
if (filteredPatterns.length === 0) {
|
|
698
|
+
return {
|
|
699
|
+
content: [{
|
|
700
|
+
type: 'text',
|
|
701
|
+
text: `✓ Analysis complete - ${patterns.length} pattern(s) found but none meet ${min_confidence} confidence threshold.`,
|
|
702
|
+
}],
|
|
703
|
+
};
|
|
704
|
+
}
|
|
705
|
+
// Build response
|
|
706
|
+
const lines = [];
|
|
707
|
+
lines.push(`## Observations Detected (${filteredPatterns.length})`);
|
|
708
|
+
lines.push('');
|
|
709
|
+
const savedObservations = [];
|
|
710
|
+
// Auto-save observations if enabled
|
|
711
|
+
if (auto_save) {
|
|
712
|
+
const memStorage = getMemoryStorage();
|
|
713
|
+
filteredPatterns.forEach((obs, idx) => {
|
|
714
|
+
const confidenceEmoji = obs.confidence === 'high' ? '🔴' : obs.confidence === 'medium' ? '🟡' : '🟢';
|
|
715
|
+
lines.push(`${idx + 1}. ${confidenceEmoji} **${obs.pattern}**`);
|
|
716
|
+
lines.push(` - Evidence: ${obs.evidence}`);
|
|
717
|
+
lines.push(` - Confidence: ${obs.confidence}`);
|
|
718
|
+
// Save to observation tier
|
|
719
|
+
const memory = memStorage.saveMemory({
|
|
720
|
+
tier: 'observation',
|
|
721
|
+
content: `OBSERVATION: ${obs.pattern}. Evidence: ${obs.evidence}`,
|
|
722
|
+
tags: ['auto-observed', `confidence-${obs.confidence}`, 'pattern-detection'],
|
|
723
|
+
related_files: checkpoint?.sections.context.relevantFiles || [],
|
|
724
|
+
metadata: {
|
|
725
|
+
pattern_type: obs.pattern.split(':')[0].toLowerCase().replace(/\s+/g, '_'),
|
|
726
|
+
confidence: obs.confidence,
|
|
727
|
+
evidence: obs.evidence,
|
|
728
|
+
detected_at: new Date().toISOString(),
|
|
729
|
+
lookback_days,
|
|
730
|
+
},
|
|
731
|
+
});
|
|
732
|
+
savedObservations.push(memory.id);
|
|
733
|
+
lines.push(` - Saved: ${memory.id}`);
|
|
734
|
+
lines.push('');
|
|
735
|
+
});
|
|
736
|
+
lines.push('---');
|
|
737
|
+
lines.push('');
|
|
738
|
+
lines.push(`✓ Saved ${savedObservations.length} observation(s) to memory (tier: observation)`);
|
|
739
|
+
}
|
|
740
|
+
else {
|
|
741
|
+
filteredPatterns.forEach((obs, idx) => {
|
|
742
|
+
const confidenceEmoji = obs.confidence === 'high' ? '🔴' : obs.confidence === 'medium' ? '🟡' : '🟢';
|
|
743
|
+
lines.push(`${idx + 1}. ${confidenceEmoji} **${obs.pattern}**`);
|
|
744
|
+
lines.push(` - Evidence: ${obs.evidence}`);
|
|
745
|
+
lines.push(` - Confidence: ${obs.confidence}`);
|
|
746
|
+
lines.push('');
|
|
747
|
+
});
|
|
748
|
+
lines.push('---');
|
|
749
|
+
lines.push('');
|
|
750
|
+
lines.push('💡 Set `auto_save: true` to save these observations to memory.');
|
|
751
|
+
}
|
|
752
|
+
return {
|
|
753
|
+
content: [{
|
|
754
|
+
type: 'text',
|
|
755
|
+
text: lines.join('\n'),
|
|
756
|
+
}],
|
|
757
|
+
};
|
|
758
|
+
}
|
|
759
|
+
catch (error) {
|
|
760
|
+
return {
|
|
761
|
+
content: [{
|
|
762
|
+
type: 'text',
|
|
763
|
+
text: `Error analyzing patterns: ${error.message}`,
|
|
764
|
+
}],
|
|
765
|
+
isError: true,
|
|
766
|
+
};
|
|
767
|
+
}
|
|
250
768
|
});
|
|
251
|
-
//
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
769
|
+
// ===========================================================================
|
|
770
|
+
// Migration Tool (v1 → v2)
|
|
771
|
+
// ===========================================================================
|
|
772
|
+
// gaia_migrate — Migrate v1 checkpoint data to v2 memory system
|
|
773
|
+
server.tool('gaia_migrate', 'Migrate Gaia v1 checkpoint data to v2 memory system. Converts decisions, progress, and learnings to searchable memories.', {
|
|
774
|
+
dry_run: z.boolean().optional().describe('Preview migration without saving (default: true)'),
|
|
775
|
+
include_checkpoints: z.boolean().optional().describe('Migrate checkpoint decisions (default: true)'),
|
|
776
|
+
sessions_limit: z.number().optional().describe('Limit number of sessions to migrate (default: all)'),
|
|
777
|
+
}, async ({ dry_run = true, include_checkpoints = true, sessions_limit }) => {
|
|
778
|
+
try {
|
|
779
|
+
const lines = [];
|
|
780
|
+
lines.push(`## Gaia v1 → v2 Migration${dry_run ? ' (DRY RUN)' : ''}`);
|
|
781
|
+
lines.push('');
|
|
782
|
+
let totalDecisions = 0;
|
|
783
|
+
let totalProgress = 0;
|
|
784
|
+
let sessionsProcessed = 0;
|
|
785
|
+
const migratedMemories = [];
|
|
786
|
+
if (include_checkpoints) {
|
|
787
|
+
// Scan sessions directory for v1 checkpoints
|
|
788
|
+
const sessionsDir = getSessionsDir();
|
|
789
|
+
if (!existsSync(sessionsDir)) {
|
|
790
|
+
lines.push('⚠️ No v1 sessions directory found');
|
|
791
|
+
lines.push(` Expected: ${sessionsDir}`);
|
|
792
|
+
lines.push('');
|
|
793
|
+
lines.push('Nothing to migrate.');
|
|
794
|
+
return {
|
|
795
|
+
content: [{
|
|
796
|
+
type: 'text',
|
|
797
|
+
text: lines.join('\n'),
|
|
798
|
+
}],
|
|
799
|
+
};
|
|
800
|
+
}
|
|
801
|
+
// Read all session directories
|
|
802
|
+
const { readdirSync, statSync } = await import('fs');
|
|
803
|
+
const sessionDirs = readdirSync(sessionsDir)
|
|
804
|
+
.filter(name => {
|
|
805
|
+
const path = join(sessionsDir, name);
|
|
806
|
+
return statSync(path).isDirectory() && name !== 'latest';
|
|
807
|
+
})
|
|
808
|
+
.sort()
|
|
809
|
+
.reverse(); // Most recent first
|
|
810
|
+
const dirsToProcess = sessions_limit
|
|
811
|
+
? sessionDirs.slice(0, sessions_limit)
|
|
812
|
+
: sessionDirs;
|
|
813
|
+
lines.push(`### Checkpoint Migration`);
|
|
814
|
+
lines.push('');
|
|
815
|
+
lines.push(`Found ${sessionDirs.length} session(s)`);
|
|
816
|
+
lines.push(`Processing ${dirsToProcess.length} session(s)`);
|
|
817
|
+
lines.push('');
|
|
818
|
+
const memStorage = getMemoryStorage();
|
|
819
|
+
for (const sessionDir of dirsToProcess) {
|
|
820
|
+
const checkpointPath = join(sessionsDir, sessionDir, 'checkpoint.json');
|
|
821
|
+
const checkpoint = readJsonFile(checkpointPath);
|
|
822
|
+
if (!checkpoint)
|
|
823
|
+
continue;
|
|
824
|
+
sessionsProcessed++;
|
|
825
|
+
lines.push(`#### Session: ${sessionDir}`);
|
|
826
|
+
lines.push(`Project: ${checkpoint.project || 'N/A'}`);
|
|
827
|
+
lines.push(`Purpose: ${checkpoint.purpose}`);
|
|
828
|
+
lines.push('');
|
|
829
|
+
// Migrate decisions
|
|
830
|
+
if (checkpoint.sections.decisions.length > 0) {
|
|
831
|
+
totalDecisions += checkpoint.sections.decisions.length;
|
|
832
|
+
for (const decision of checkpoint.sections.decisions) {
|
|
833
|
+
const content = `DECISION (migrated from v1): ${decision.topic}. Decision: ${decision.decision}. Rationale: ${decision.rationale}`;
|
|
834
|
+
if (!dry_run) {
|
|
835
|
+
const memory = memStorage.saveMemory({
|
|
836
|
+
tier: 'project',
|
|
837
|
+
content,
|
|
838
|
+
tags: ['migrated-v1', 'decision', decision.topic.toLowerCase().replace(/\s+/g, '-')],
|
|
839
|
+
related_files: checkpoint.sections.context.relevantFiles || [],
|
|
840
|
+
metadata: {
|
|
841
|
+
migrated_from: 'v1',
|
|
842
|
+
original_checkpoint_id: checkpoint.id,
|
|
843
|
+
original_timestamp: decision.timestamp,
|
|
844
|
+
decision_topic: decision.topic,
|
|
845
|
+
},
|
|
846
|
+
});
|
|
847
|
+
migratedMemories.push(memory.id);
|
|
848
|
+
lines.push(` ✓ Decision: ${decision.topic} → ${memory.id}`);
|
|
849
|
+
}
|
|
850
|
+
else {
|
|
851
|
+
lines.push(` • Decision: ${decision.topic}`);
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
// Migrate completed progress items as observations
|
|
856
|
+
const completedTasks = checkpoint.sections.progress.filter(p => p.status === 'completed');
|
|
857
|
+
if (completedTasks.length > 0) {
|
|
858
|
+
totalProgress += completedTasks.length;
|
|
859
|
+
for (const task of completedTasks) {
|
|
860
|
+
const content = `COMPLETED (migrated from v1): ${task.task}${task.details ? `. ${task.details}` : ''}`;
|
|
861
|
+
if (!dry_run) {
|
|
862
|
+
const memory = memStorage.saveMemory({
|
|
863
|
+
tier: 'session',
|
|
864
|
+
content,
|
|
865
|
+
tags: ['migrated-v1', 'completed', 'progress'],
|
|
866
|
+
related_files: [],
|
|
867
|
+
metadata: {
|
|
868
|
+
migrated_from: 'v1',
|
|
869
|
+
original_checkpoint_id: checkpoint.id,
|
|
870
|
+
task_status: task.status,
|
|
871
|
+
},
|
|
872
|
+
});
|
|
873
|
+
migratedMemories.push(memory.id);
|
|
874
|
+
lines.push(` ✓ Progress: ${task.task.substring(0, 50)} → ${memory.id}`);
|
|
875
|
+
}
|
|
876
|
+
else {
|
|
877
|
+
lines.push(` • Progress: ${task.task.substring(0, 50)}`);
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
lines.push('');
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
// Summary
|
|
885
|
+
lines.push('---');
|
|
886
|
+
lines.push('');
|
|
887
|
+
lines.push('### Migration Summary');
|
|
888
|
+
lines.push('');
|
|
889
|
+
lines.push(`Sessions processed: ${sessionsProcessed}`);
|
|
890
|
+
lines.push(`Decisions: ${totalDecisions}`);
|
|
891
|
+
lines.push(`Completed tasks: ${totalProgress}`);
|
|
892
|
+
lines.push(`Total items: ${totalDecisions + totalProgress}`);
|
|
893
|
+
if (dry_run) {
|
|
894
|
+
lines.push('');
|
|
895
|
+
lines.push('⚠️ **DRY RUN MODE** - No data was saved');
|
|
896
|
+
lines.push(' Set `dry_run: false` to perform actual migration');
|
|
897
|
+
}
|
|
898
|
+
else {
|
|
899
|
+
lines.push('');
|
|
900
|
+
lines.push(`✓ Migrated ${migratedMemories.length} memory/memories to v2 system`);
|
|
901
|
+
lines.push('');
|
|
902
|
+
lines.push('**Next steps:**');
|
|
903
|
+
lines.push('1. Search migrated data: `gaia_search({ query: "migrated-v1", tags: ["migrated-v1"] })`');
|
|
904
|
+
lines.push('2. Verify decisions: `gaia_search({ query: "decision", tiers: ["project"] })`');
|
|
905
|
+
lines.push('3. Review in context: Use `current_file` parameter for proximity boosting');
|
|
906
|
+
}
|
|
907
|
+
return {
|
|
908
|
+
content: [{
|
|
909
|
+
type: 'text',
|
|
910
|
+
text: lines.join('\n'),
|
|
911
|
+
}],
|
|
912
|
+
};
|
|
913
|
+
}
|
|
914
|
+
catch (error) {
|
|
915
|
+
return {
|
|
916
|
+
content: [{
|
|
917
|
+
type: 'text',
|
|
918
|
+
text: `Error during migration: ${error.message}`,
|
|
919
|
+
}],
|
|
920
|
+
isError: true,
|
|
921
|
+
};
|
|
922
|
+
}
|
|
276
923
|
});
|
|
277
924
|
// ===========================================================================
|
|
278
|
-
// Learning Tools
|
|
925
|
+
// Learning Tools (unchanged from v1)
|
|
279
926
|
// ===========================================================================
|
|
280
927
|
// gaia_learn
|
|
281
928
|
server.tool('gaia_learn', 'Capture a correction for CLAUDE.md. Gaia learns from mistakes so all future sessions benefit.', {
|
|
@@ -370,97 +1017,382 @@ export function registerGaiaTools(server) {
|
|
|
370
1017
|
};
|
|
371
1018
|
});
|
|
372
1019
|
// ===========================================================================
|
|
373
|
-
//
|
|
1020
|
+
// Memory Tools (v2.0 - SQLite + FTS5)
|
|
374
1021
|
// ===========================================================================
|
|
375
|
-
//
|
|
376
|
-
server.tool('
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
1022
|
+
// gaia_save
|
|
1023
|
+
server.tool('gaia_save', 'Save a new memory with tier, content, tags, and related files. Returns the created memory with ID.', {
|
|
1024
|
+
tier: z.enum(['session', 'project', 'global', 'note', 'observation'])
|
|
1025
|
+
.describe('Memory tier: session (ephemeral), project (cwd), global (user-wide), note (manual), observation (autonomous)'),
|
|
1026
|
+
content: z.string().describe('Memory content (what to remember)'),
|
|
1027
|
+
tags: z.array(z.string()).optional().describe('Tags for categorization (optional)'),
|
|
1028
|
+
related_files: z.array(z.string()).optional().describe('Related file paths (optional)'),
|
|
1029
|
+
session_id: z.string().optional().describe('Session ID for session-tier memories (optional)'),
|
|
1030
|
+
project_path: z.string().optional().describe('Project path for project-tier memories (optional)'),
|
|
1031
|
+
metadata: z.record(z.any()).optional().describe('Additional structured metadata (optional)'),
|
|
1032
|
+
}, async ({ tier, content, tags = [], related_files = [], session_id, project_path, metadata }) => {
|
|
1033
|
+
try {
|
|
1034
|
+
const storage = getMemoryStorage();
|
|
1035
|
+
const memory = storage.saveMemory({
|
|
1036
|
+
tier,
|
|
1037
|
+
content,
|
|
1038
|
+
tags,
|
|
1039
|
+
related_files,
|
|
1040
|
+
session_id,
|
|
1041
|
+
project_path,
|
|
1042
|
+
metadata,
|
|
1043
|
+
});
|
|
1044
|
+
return {
|
|
1045
|
+
content: [{
|
|
1046
|
+
type: 'text',
|
|
1047
|
+
text: JSON.stringify({
|
|
1048
|
+
success: true,
|
|
1049
|
+
memory: {
|
|
1050
|
+
id: memory.id,
|
|
1051
|
+
tier: memory.tier,
|
|
1052
|
+
content: memory.content,
|
|
1053
|
+
tags: memory.tags,
|
|
1054
|
+
related_files: memory.related_files,
|
|
1055
|
+
created_at: memory.created_at,
|
|
1056
|
+
},
|
|
1057
|
+
}, null, 2),
|
|
1058
|
+
}],
|
|
1059
|
+
};
|
|
1060
|
+
}
|
|
1061
|
+
catch (error) {
|
|
1062
|
+
return {
|
|
1063
|
+
content: [{
|
|
1064
|
+
type: 'text',
|
|
1065
|
+
text: `Error saving memory: ${error instanceof Error ? error.message : String(error)}`,
|
|
1066
|
+
}],
|
|
1067
|
+
isError: true,
|
|
1068
|
+
};
|
|
1069
|
+
}
|
|
1070
|
+
});
|
|
1071
|
+
// gaia_search
|
|
1072
|
+
server.tool('gaia_search', 'Search memories using FTS5 full-text search with composite scoring (BM25 + recency + tier + proximity + frequency). Returns ranked results.', {
|
|
1073
|
+
query: z.string().describe('Search query (supports FTS5 syntax: "word1 word2", "phrase", word*)'),
|
|
1074
|
+
tiers: z.array(z.enum(['session', 'project', 'global', 'note', 'observation'])).optional()
|
|
1075
|
+
.describe('Filter by tiers (optional)'),
|
|
1076
|
+
limit: z.number().optional().describe('Maximum results to return (default: 20, max: 100)'),
|
|
1077
|
+
current_file: z.string().optional().describe('Current file path for proximity scoring (optional)'),
|
|
1078
|
+
project_path: z.string().optional().describe('Current project path for context (optional)'),
|
|
1079
|
+
}, async ({ query, tiers, limit = 20, current_file, project_path }) => {
|
|
1080
|
+
try {
|
|
1081
|
+
const storage = getMemoryStorage();
|
|
1082
|
+
const results = storage.search({
|
|
1083
|
+
query,
|
|
1084
|
+
tiers,
|
|
1085
|
+
limit: Math.min(limit, 100),
|
|
1086
|
+
context: current_file || project_path ? { current_file, project_path } : undefined,
|
|
1087
|
+
});
|
|
1088
|
+
return {
|
|
1089
|
+
content: [{
|
|
1090
|
+
type: 'text',
|
|
1091
|
+
text: JSON.stringify({
|
|
1092
|
+
success: true,
|
|
1093
|
+
count: results.length,
|
|
1094
|
+
results: results.map(r => ({
|
|
1095
|
+
id: r.memory.id,
|
|
1096
|
+
tier: r.memory.tier,
|
|
1097
|
+
content: r.memory.content,
|
|
1098
|
+
tags: r.memory.tags,
|
|
1099
|
+
related_files: r.memory.related_files,
|
|
1100
|
+
created_at: r.memory.created_at,
|
|
1101
|
+
score: r.score,
|
|
1102
|
+
score_breakdown: {
|
|
1103
|
+
relevance: r.relevance_score,
|
|
1104
|
+
recency: r.recency_score,
|
|
1105
|
+
tier: r.tier_score,
|
|
1106
|
+
proximity: r.proximity_score,
|
|
1107
|
+
frequency: r.frequency_score,
|
|
1108
|
+
},
|
|
1109
|
+
})),
|
|
1110
|
+
}, null, 2),
|
|
1111
|
+
}],
|
|
1112
|
+
};
|
|
1113
|
+
}
|
|
1114
|
+
catch (error) {
|
|
1115
|
+
return {
|
|
1116
|
+
content: [{
|
|
1117
|
+
type: 'text',
|
|
1118
|
+
text: `Error searching memories: ${error instanceof Error ? error.message : String(error)}`,
|
|
1119
|
+
}],
|
|
1120
|
+
isError: true,
|
|
1121
|
+
};
|
|
395
1122
|
}
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
1123
|
+
});
|
|
1124
|
+
// gaia_get
|
|
1125
|
+
server.tool('gaia_get', 'Get a memory by ID. Increments access count and updates last accessed timestamp.', {
|
|
1126
|
+
id: z.string().describe('Memory ID (starts with "mem_")'),
|
|
1127
|
+
}, async ({ id }) => {
|
|
1128
|
+
try {
|
|
1129
|
+
const storage = getMemoryStorage();
|
|
1130
|
+
const memory = storage.getMemory(id);
|
|
1131
|
+
if (!memory) {
|
|
1132
|
+
return {
|
|
1133
|
+
content: [{
|
|
1134
|
+
type: 'text',
|
|
1135
|
+
text: JSON.stringify({
|
|
1136
|
+
success: false,
|
|
1137
|
+
error: `Memory not found: ${id}`,
|
|
1138
|
+
}, null, 2),
|
|
1139
|
+
}],
|
|
1140
|
+
};
|
|
399
1141
|
}
|
|
400
|
-
|
|
401
|
-
|
|
1142
|
+
return {
|
|
1143
|
+
content: [{
|
|
1144
|
+
type: 'text',
|
|
1145
|
+
text: JSON.stringify({
|
|
1146
|
+
success: true,
|
|
1147
|
+
memory: {
|
|
1148
|
+
id: memory.id,
|
|
1149
|
+
tier: memory.tier,
|
|
1150
|
+
content: memory.content,
|
|
1151
|
+
tags: memory.tags,
|
|
1152
|
+
related_files: memory.related_files,
|
|
1153
|
+
session_id: memory.session_id,
|
|
1154
|
+
project_path: memory.project_path,
|
|
1155
|
+
created_at: memory.created_at,
|
|
1156
|
+
accessed_at: memory.accessed_at,
|
|
1157
|
+
access_count: memory.access_count,
|
|
1158
|
+
metadata: memory.metadata,
|
|
1159
|
+
},
|
|
1160
|
+
}, null, 2),
|
|
1161
|
+
}],
|
|
1162
|
+
};
|
|
1163
|
+
}
|
|
1164
|
+
catch (error) {
|
|
1165
|
+
return {
|
|
1166
|
+
content: [{
|
|
1167
|
+
type: 'text',
|
|
1168
|
+
text: `Error getting memory: ${error instanceof Error ? error.message : String(error)}`,
|
|
1169
|
+
}],
|
|
1170
|
+
isError: true,
|
|
1171
|
+
};
|
|
1172
|
+
}
|
|
1173
|
+
});
|
|
1174
|
+
// gaia_delete
|
|
1175
|
+
server.tool('gaia_delete', 'Delete a memory by ID. Automatically deletes all associated links (CASCADE).', {
|
|
1176
|
+
id: z.string().describe('Memory ID to delete'),
|
|
1177
|
+
}, async ({ id }) => {
|
|
1178
|
+
try {
|
|
1179
|
+
const storage = getMemoryStorage();
|
|
1180
|
+
const result = storage.deleteMemory(id);
|
|
1181
|
+
if (!result.success) {
|
|
1182
|
+
return {
|
|
1183
|
+
content: [{
|
|
1184
|
+
type: 'text',
|
|
1185
|
+
text: JSON.stringify({
|
|
1186
|
+
success: false,
|
|
1187
|
+
error: `Memory not found: ${id}`,
|
|
1188
|
+
}, null, 2),
|
|
1189
|
+
}],
|
|
1190
|
+
};
|
|
402
1191
|
}
|
|
1192
|
+
return {
|
|
1193
|
+
content: [{
|
|
1194
|
+
type: 'text',
|
|
1195
|
+
text: JSON.stringify({
|
|
1196
|
+
success: true,
|
|
1197
|
+
deleted_memory_id: id,
|
|
1198
|
+
deleted_links: result.deleted_links,
|
|
1199
|
+
}, null, 2),
|
|
1200
|
+
}],
|
|
1201
|
+
};
|
|
1202
|
+
}
|
|
1203
|
+
catch (error) {
|
|
1204
|
+
return {
|
|
1205
|
+
content: [{
|
|
1206
|
+
type: 'text',
|
|
1207
|
+
text: `Error deleting memory: ${error instanceof Error ? error.message : String(error)}`,
|
|
1208
|
+
}],
|
|
1209
|
+
isError: true,
|
|
1210
|
+
};
|
|
1211
|
+
}
|
|
1212
|
+
});
|
|
1213
|
+
// gaia_stats
|
|
1214
|
+
server.tool('gaia_stats', 'Get Gaia memory statistics: total memories, breakdown by tier, links, database size, age range.', {}, async () => {
|
|
1215
|
+
try {
|
|
1216
|
+
const storage = getMemoryStorage();
|
|
1217
|
+
const stats = storage.getStats();
|
|
1218
|
+
return {
|
|
1219
|
+
content: [{
|
|
1220
|
+
type: 'text',
|
|
1221
|
+
text: JSON.stringify({
|
|
1222
|
+
success: true,
|
|
1223
|
+
stats: {
|
|
1224
|
+
total_memories: stats.total_memories,
|
|
1225
|
+
by_tier: stats.by_tier,
|
|
1226
|
+
total_links: stats.total_links,
|
|
1227
|
+
database_size_mb: parseFloat(stats.total_size_mb.toFixed(2)),
|
|
1228
|
+
oldest_memory: stats.oldest_memory,
|
|
1229
|
+
newest_memory: stats.newest_memory,
|
|
1230
|
+
age_range_days: stats.oldest_memory > 0
|
|
1231
|
+
? Math.floor((stats.newest_memory - stats.oldest_memory) / (1000 * 60 * 60 * 24))
|
|
1232
|
+
: 0,
|
|
1233
|
+
},
|
|
1234
|
+
}, null, 2),
|
|
1235
|
+
}],
|
|
1236
|
+
};
|
|
1237
|
+
}
|
|
1238
|
+
catch (error) {
|
|
1239
|
+
return {
|
|
1240
|
+
content: [{
|
|
1241
|
+
type: 'text',
|
|
1242
|
+
text: `Error getting stats: ${error instanceof Error ? error.message : String(error)}`,
|
|
1243
|
+
}],
|
|
1244
|
+
isError: true,
|
|
1245
|
+
};
|
|
403
1246
|
}
|
|
404
|
-
const snapshot = {
|
|
405
|
-
id: `mem_${nanoid(8)}`,
|
|
406
|
-
name,
|
|
407
|
-
description,
|
|
408
|
-
data,
|
|
409
|
-
createdAt: Date.now(),
|
|
410
|
-
};
|
|
411
|
-
storage.addSnapshot(snapshot);
|
|
412
|
-
return {
|
|
413
|
-
content: [
|
|
414
|
-
{
|
|
415
|
-
type: 'text',
|
|
416
|
-
text: `✓ Memory saved: ${snapshot.id}\n\nName: ${name}`,
|
|
417
|
-
},
|
|
418
|
-
],
|
|
419
|
-
};
|
|
420
1247
|
});
|
|
421
|
-
//
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
1248
|
+
// ===========================================================================
|
|
1249
|
+
// Cross-Prompt Linking Tools
|
|
1250
|
+
// ===========================================================================
|
|
1251
|
+
// gaia_link
|
|
1252
|
+
server.tool('gaia_link', 'Create a typed link between two memories. Builds cross-prompt dependency graph.', {
|
|
1253
|
+
from_memory_id: z.string().describe('Source memory ID'),
|
|
1254
|
+
to_memory_id: z.string().describe('Target memory ID'),
|
|
1255
|
+
link_type: z.enum(['depends_on', 'extends', 'reverts', 'related', 'contradicts'])
|
|
1256
|
+
.describe('Link type: depends_on (requires), extends (builds on), reverts (undoes), related (associated), contradicts (conflicts)'),
|
|
1257
|
+
}, async ({ from_memory_id, to_memory_id, link_type }) => {
|
|
1258
|
+
try {
|
|
1259
|
+
const storage = getMemoryStorage();
|
|
1260
|
+
// Verify both memories exist
|
|
1261
|
+
const fromMemory = storage.getMemory(from_memory_id);
|
|
1262
|
+
const toMemory = storage.getMemory(to_memory_id);
|
|
1263
|
+
if (!fromMemory) {
|
|
435
1264
|
return {
|
|
436
|
-
content: [{
|
|
1265
|
+
content: [{
|
|
1266
|
+
type: 'text',
|
|
1267
|
+
text: JSON.stringify({
|
|
1268
|
+
success: false,
|
|
1269
|
+
error: `Source memory not found: ${from_memory_id}`,
|
|
1270
|
+
}, null, 2),
|
|
1271
|
+
}],
|
|
437
1272
|
};
|
|
438
1273
|
}
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
1274
|
+
if (!toMemory) {
|
|
1275
|
+
return {
|
|
1276
|
+
content: [{
|
|
1277
|
+
type: 'text',
|
|
1278
|
+
text: JSON.stringify({
|
|
1279
|
+
success: false,
|
|
1280
|
+
error: `Target memory not found: ${to_memory_id}`,
|
|
1281
|
+
}, null, 2),
|
|
1282
|
+
}],
|
|
1283
|
+
};
|
|
442
1284
|
}
|
|
1285
|
+
const link = storage.createLink({
|
|
1286
|
+
from_memory_id,
|
|
1287
|
+
to_memory_id,
|
|
1288
|
+
link_type,
|
|
1289
|
+
});
|
|
443
1290
|
return {
|
|
444
|
-
content: [{
|
|
1291
|
+
content: [{
|
|
1292
|
+
type: 'text',
|
|
1293
|
+
text: JSON.stringify({
|
|
1294
|
+
success: true,
|
|
1295
|
+
link: {
|
|
1296
|
+
from_memory_id: link.from_memory_id,
|
|
1297
|
+
to_memory_id: link.to_memory_id,
|
|
1298
|
+
link_type: link.link_type,
|
|
1299
|
+
created_at: link.created_at,
|
|
1300
|
+
},
|
|
1301
|
+
from_memory_content: fromMemory.content.substring(0, 100),
|
|
1302
|
+
to_memory_content: toMemory.content.substring(0, 100),
|
|
1303
|
+
}, null, 2),
|
|
1304
|
+
}],
|
|
445
1305
|
};
|
|
446
1306
|
}
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
1307
|
+
catch (error) {
|
|
1308
|
+
return {
|
|
1309
|
+
content: [{
|
|
1310
|
+
type: 'text',
|
|
1311
|
+
text: `Error creating link: ${error instanceof Error ? error.message : String(error)}`,
|
|
1312
|
+
}],
|
|
1313
|
+
isError: true,
|
|
1314
|
+
};
|
|
450
1315
|
}
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
1316
|
+
});
|
|
1317
|
+
// gaia_graph
|
|
1318
|
+
server.tool('gaia_graph', 'Get the link graph for a memory. Returns all linked memories grouped by link type.', {
|
|
1319
|
+
memory_id: z.string().describe('Memory ID to get links for'),
|
|
1320
|
+
}, async ({ memory_id }) => {
|
|
1321
|
+
try {
|
|
1322
|
+
const storage = getMemoryStorage();
|
|
1323
|
+
// Verify memory exists
|
|
1324
|
+
const memory = storage.getMemory(memory_id);
|
|
1325
|
+
if (!memory) {
|
|
1326
|
+
return {
|
|
1327
|
+
content: [{
|
|
1328
|
+
type: 'text',
|
|
1329
|
+
text: JSON.stringify({
|
|
1330
|
+
success: false,
|
|
1331
|
+
error: `Memory not found: ${memory_id}`,
|
|
1332
|
+
}, null, 2),
|
|
1333
|
+
}],
|
|
1334
|
+
};
|
|
455
1335
|
}
|
|
456
|
-
|
|
1336
|
+
const links = storage.getLinks(memory_id);
|
|
1337
|
+
// Count total links
|
|
1338
|
+
const totalLinks = Object.values(links).reduce((sum, arr) => sum + arr.length, 0);
|
|
1339
|
+
return {
|
|
1340
|
+
content: [{
|
|
1341
|
+
type: 'text',
|
|
1342
|
+
text: JSON.stringify({
|
|
1343
|
+
success: true,
|
|
1344
|
+
memory_id,
|
|
1345
|
+
memory_content: memory.content.substring(0, 100),
|
|
1346
|
+
total_links: totalLinks,
|
|
1347
|
+
links: {
|
|
1348
|
+
depends_on: links.depends_on.map(m => ({
|
|
1349
|
+
id: m.id,
|
|
1350
|
+
content: m.content.substring(0, 100),
|
|
1351
|
+
tier: m.tier,
|
|
1352
|
+
})),
|
|
1353
|
+
extends: links.extends.map(m => ({
|
|
1354
|
+
id: m.id,
|
|
1355
|
+
content: m.content.substring(0, 100),
|
|
1356
|
+
tier: m.tier,
|
|
1357
|
+
})),
|
|
1358
|
+
reverts: links.reverts.map(m => ({
|
|
1359
|
+
id: m.id,
|
|
1360
|
+
content: m.content.substring(0, 100),
|
|
1361
|
+
tier: m.tier,
|
|
1362
|
+
})),
|
|
1363
|
+
related: links.related.map(m => ({
|
|
1364
|
+
id: m.id,
|
|
1365
|
+
content: m.content.substring(0, 100),
|
|
1366
|
+
tier: m.tier,
|
|
1367
|
+
})),
|
|
1368
|
+
contradicts: links.contradicts.map(m => ({
|
|
1369
|
+
id: m.id,
|
|
1370
|
+
content: m.content.substring(0, 100),
|
|
1371
|
+
tier: m.tier,
|
|
1372
|
+
})),
|
|
1373
|
+
},
|
|
1374
|
+
}, null, 2),
|
|
1375
|
+
}],
|
|
1376
|
+
};
|
|
457
1377
|
}
|
|
458
|
-
|
|
459
|
-
|
|
1378
|
+
catch (error) {
|
|
1379
|
+
return {
|
|
1380
|
+
content: [{
|
|
1381
|
+
type: 'text',
|
|
1382
|
+
text: `Error getting graph: ${error instanceof Error ? error.message : String(error)}`,
|
|
1383
|
+
}],
|
|
1384
|
+
isError: true,
|
|
1385
|
+
};
|
|
460
1386
|
}
|
|
461
|
-
return {
|
|
462
|
-
content: [{ type: 'text', text: output }],
|
|
463
|
-
};
|
|
464
1387
|
});
|
|
465
1388
|
}
|
|
1389
|
+
/**
|
|
1390
|
+
* Cleanup function to close database connection
|
|
1391
|
+
*/
|
|
1392
|
+
export function cleanupGaiaMemory() {
|
|
1393
|
+
if (memoryStorage) {
|
|
1394
|
+
memoryStorage.close();
|
|
1395
|
+
memoryStorage = null;
|
|
1396
|
+
}
|
|
1397
|
+
}
|
|
466
1398
|
//# sourceMappingURL=index.js.map
|