@geekbeer/minion 2.70.2 → 3.4.7
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/core/db.js +297 -0
- package/core/lib/thread-watcher.js +3 -6
- package/core/routes/daily-logs.js +17 -11
- package/core/routes/memory.js +16 -10
- package/core/stores/chat-store.js +138 -76
- package/core/stores/daily-log-store.js +84 -105
- package/core/stores/execution-store.js +59 -84
- package/core/stores/memory-store.js +84 -177
- package/core/stores/routine-store.js +55 -72
- package/core/stores/workflow-store.js +52 -69
- package/docs/api-reference.md +6 -2
- package/linux/minion-cli.sh +203 -254
- package/linux/routes/chat.js +54 -7
- package/linux/server.js +3 -1
- package/package.json +3 -2
- package/roles/engineer.md +12 -0
- package/roles/pm.md +16 -0
- package/rules/core.md +66 -0
- package/win/lib/process-manager.js +115 -220
- package/win/minion-cli.ps1 +882 -675
- package/win/routes/chat.js +54 -7
- package/win/server.js +2 -0
- package/win/vendor/README.md +13 -0
- package/win/vendor/nssm.exe +0 -0
|
@@ -1,123 +1,98 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Execution Store
|
|
3
|
-
* Persists skill execution history to local
|
|
2
|
+
* Execution Store (SQLite)
|
|
3
|
+
* Persists skill execution history to local SQLite database.
|
|
4
4
|
* Single source of truth for execution records.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
const
|
|
8
|
-
const path = require('path')
|
|
9
|
-
|
|
10
|
-
const { config } = require('../config')
|
|
11
|
-
const { DATA_DIR } = require('../lib/platform')
|
|
7
|
+
const { getDb } = require('../db')
|
|
12
8
|
|
|
13
9
|
// Max executions to keep (older ones are pruned)
|
|
14
10
|
const MAX_EXECUTIONS = 200
|
|
15
11
|
|
|
16
12
|
/**
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*/
|
|
20
|
-
function getExecutionFilePath() {
|
|
21
|
-
try {
|
|
22
|
-
require('fs').accessSync(DATA_DIR)
|
|
23
|
-
return path.join(DATA_DIR, 'executions.json')
|
|
24
|
-
} catch {
|
|
25
|
-
return path.join(config.HOME_DIR, 'executions.json')
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
const EXECUTION_FILE = getExecutionFilePath()
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Load executions from local file
|
|
33
|
-
* @returns {Promise<Array>} Array of execution objects
|
|
34
|
-
*/
|
|
35
|
-
async function load() {
|
|
36
|
-
try {
|
|
37
|
-
const data = await fs.readFile(EXECUTION_FILE, 'utf-8')
|
|
38
|
-
return JSON.parse(data)
|
|
39
|
-
} catch (err) {
|
|
40
|
-
if (err.code === 'ENOENT') {
|
|
41
|
-
return []
|
|
42
|
-
}
|
|
43
|
-
console.error(`[ExecutionStore] Failed to load executions: ${err.message}`)
|
|
44
|
-
return []
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Save executions to local file
|
|
50
|
-
* @param {Array} executions - Array of execution objects
|
|
51
|
-
*/
|
|
52
|
-
async function saveAll(executions) {
|
|
53
|
-
try {
|
|
54
|
-
await fs.writeFile(EXECUTION_FILE, JSON.stringify(executions, null, 2), 'utf-8')
|
|
55
|
-
} catch (err) {
|
|
56
|
-
console.error(`[ExecutionStore] Failed to save executions: ${err.message}`)
|
|
57
|
-
throw err
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Save a single execution record
|
|
63
|
-
* Upserts by ID, prunes old records if over limit
|
|
13
|
+
* Save a single execution record.
|
|
14
|
+
* Upserts by ID, prunes old records if over limit.
|
|
64
15
|
* @param {object} execution - Execution record
|
|
65
16
|
*/
|
|
66
|
-
|
|
17
|
+
function save(execution) {
|
|
18
|
+
const db = getDb()
|
|
67
19
|
console.log(`[ExecutionStore] save() called: id=${execution.id}, skill=${execution.skill_name}, status=${execution.status}`)
|
|
68
20
|
|
|
69
|
-
const executions =
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
21
|
+
const existing = db.prepare('SELECT data FROM executions WHERE id = ?').get(execution.id)
|
|
22
|
+
|
|
23
|
+
if (existing) {
|
|
24
|
+
const merged = { ...JSON.parse(existing.data), ...execution }
|
|
25
|
+
db.prepare(
|
|
26
|
+
'UPDATE executions SET skill_name = ?, workflow_id = ?, status = ?, data = ? WHERE id = ?'
|
|
27
|
+
).run(
|
|
28
|
+
merged.skill_name || null,
|
|
29
|
+
merged.workflow_id || null,
|
|
30
|
+
merged.status || null,
|
|
31
|
+
JSON.stringify(merged),
|
|
32
|
+
execution.id
|
|
33
|
+
)
|
|
34
|
+
console.log(`[ExecutionStore] Updated existing record: ${execution.status}`)
|
|
78
35
|
} else {
|
|
79
|
-
|
|
80
|
-
|
|
36
|
+
db.prepare(
|
|
37
|
+
'INSERT INTO executions (id, skill_name, workflow_id, status, created_at, data) VALUES (?, ?, ?, ?, ?, ?)'
|
|
38
|
+
).run(
|
|
39
|
+
execution.id,
|
|
40
|
+
execution.skill_name || null,
|
|
41
|
+
execution.workflow_id || null,
|
|
42
|
+
execution.status || null,
|
|
43
|
+
execution.created_at || new Date().toISOString(),
|
|
44
|
+
JSON.stringify(execution)
|
|
45
|
+
)
|
|
46
|
+
console.log(`[ExecutionStore] Added new record`)
|
|
81
47
|
}
|
|
82
48
|
|
|
83
|
-
// Prune old executions
|
|
84
|
-
const
|
|
49
|
+
// Prune old executions beyond MAX_EXECUTIONS
|
|
50
|
+
const count = db.prepare('SELECT COUNT(*) as cnt FROM executions').get().cnt
|
|
51
|
+
if (count > MAX_EXECUTIONS) {
|
|
52
|
+
db.prepare(`
|
|
53
|
+
DELETE FROM executions WHERE id NOT IN (
|
|
54
|
+
SELECT id FROM executions ORDER BY created_at DESC LIMIT ?
|
|
55
|
+
)
|
|
56
|
+
`).run(MAX_EXECUTIONS)
|
|
57
|
+
}
|
|
85
58
|
|
|
86
|
-
|
|
87
|
-
console.log(`[ExecutionStore] ✓ Saved to file: ${execution.id} (${execution.skill_name} → ${execution.status})`)
|
|
59
|
+
console.log(`[ExecutionStore] ✓ Saved: ${execution.id} (${execution.skill_name} → ${execution.status})`)
|
|
88
60
|
}
|
|
89
61
|
|
|
90
62
|
/**
|
|
91
63
|
* List executions with optional limit
|
|
92
64
|
* @param {number} limit - Max number of executions to return
|
|
93
|
-
* @returns {
|
|
65
|
+
* @returns {Array} Array of execution objects (newest first)
|
|
94
66
|
*/
|
|
95
|
-
|
|
96
|
-
const
|
|
97
|
-
|
|
67
|
+
function list(limit = 50) {
|
|
68
|
+
const db = getDb()
|
|
69
|
+
const rows = db.prepare('SELECT data FROM executions ORDER BY created_at DESC LIMIT ?').all(limit)
|
|
70
|
+
return rows.map(r => JSON.parse(r.data))
|
|
98
71
|
}
|
|
99
72
|
|
|
100
73
|
/**
|
|
101
74
|
* Get executions for a specific workflow
|
|
102
75
|
* @param {string} workflowId - Workflow UUID
|
|
103
76
|
* @param {number} limit - Max number to return
|
|
104
|
-
* @returns {
|
|
77
|
+
* @returns {Array} Array of execution objects
|
|
105
78
|
*/
|
|
106
|
-
|
|
107
|
-
const
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
79
|
+
function getByWorkflow(workflowId, limit = 20) {
|
|
80
|
+
const db = getDb()
|
|
81
|
+
const rows = db.prepare(
|
|
82
|
+
'SELECT data FROM executions WHERE workflow_id = ? ORDER BY created_at DESC LIMIT ?'
|
|
83
|
+
).all(workflowId, limit)
|
|
84
|
+
return rows.map(r => JSON.parse(r.data))
|
|
111
85
|
}
|
|
112
86
|
|
|
113
87
|
/**
|
|
114
88
|
* Get a single execution by ID
|
|
115
89
|
* @param {string} id - Execution UUID
|
|
116
|
-
* @returns {
|
|
90
|
+
* @returns {object|null} Execution object or null
|
|
117
91
|
*/
|
|
118
|
-
|
|
119
|
-
const
|
|
120
|
-
|
|
92
|
+
function getById(id) {
|
|
93
|
+
const db = getDb()
|
|
94
|
+
const row = db.prepare('SELECT data FROM executions WHERE id = ?').get(id)
|
|
95
|
+
return row ? JSON.parse(row.data) : null
|
|
121
96
|
}
|
|
122
97
|
|
|
123
98
|
module.exports = {
|
|
@@ -1,160 +1,54 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Memory Store
|
|
3
|
-
* Persistent long-term memory for the minion
|
|
4
|
-
*
|
|
5
|
-
* An index file (MEMORY.md) provides a quick summary of all entries.
|
|
2
|
+
* Memory Store (SQLite)
|
|
3
|
+
* Persistent long-term memory for the minion.
|
|
4
|
+
* Supports full-text search via FTS5.
|
|
6
5
|
*
|
|
7
|
-
*
|
|
8
|
-
* $DATA_DIR/memory/
|
|
9
|
-
* ├── MEMORY.md # Index (auto-generated)
|
|
10
|
-
* └── {id}.md # Individual entries
|
|
6
|
+
* Categories: user, feedback, project, reference
|
|
11
7
|
*/
|
|
12
8
|
|
|
13
|
-
const fs = require('fs').promises
|
|
14
|
-
const path = require('path')
|
|
15
9
|
const crypto = require('crypto')
|
|
16
|
-
|
|
17
|
-
const { config } = require('../config')
|
|
18
|
-
const { DATA_DIR } = require('../lib/platform')
|
|
10
|
+
const { getDb } = require('../db')
|
|
19
11
|
|
|
20
12
|
const VALID_CATEGORIES = ['user', 'feedback', 'project', 'reference']
|
|
21
13
|
|
|
22
14
|
/**
|
|
23
|
-
*
|
|
24
|
-
|
|
25
|
-
function getMemoryDir() {
|
|
26
|
-
try {
|
|
27
|
-
require('fs').accessSync(DATA_DIR)
|
|
28
|
-
return path.join(DATA_DIR, 'memory')
|
|
29
|
-
} catch {
|
|
30
|
-
return path.join(config.HOME_DIR, 'memory')
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
const MEMORY_DIR = getMemoryDir()
|
|
35
|
-
const INDEX_FILE = path.join(MEMORY_DIR, 'MEMORY.md')
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Ensure the memory directory exists
|
|
39
|
-
*/
|
|
40
|
-
async function ensureDir() {
|
|
41
|
-
await fs.mkdir(MEMORY_DIR, { recursive: true })
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Parse YAML-style frontmatter from markdown content.
|
|
46
|
-
* Returns { meta: {}, body: '' }
|
|
47
|
-
*/
|
|
48
|
-
function parseFrontmatter(content) {
|
|
49
|
-
const match = content.match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)$/)
|
|
50
|
-
if (!match) return { meta: {}, body: content }
|
|
51
|
-
|
|
52
|
-
const meta = {}
|
|
53
|
-
for (const line of match[1].split('\n')) {
|
|
54
|
-
const idx = line.indexOf(':')
|
|
55
|
-
if (idx === -1) continue
|
|
56
|
-
const key = line.slice(0, idx).trim()
|
|
57
|
-
const value = line.slice(idx + 1).trim()
|
|
58
|
-
meta[key] = value
|
|
59
|
-
}
|
|
60
|
-
return { meta, body: match[2].trim() }
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* Serialize entry to markdown with YAML frontmatter
|
|
15
|
+
* List all memory entries (metadata + excerpt, no full body).
|
|
16
|
+
* @returns {Array<{ id, title, category, created_at, updated_at, excerpt }>}
|
|
65
17
|
*/
|
|
66
|
-
function
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
'',
|
|
76
|
-
entry.content || '',
|
|
77
|
-
]
|
|
78
|
-
return lines.join('\n')
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* List all memory entries (frontmatter only, no body).
|
|
83
|
-
* @returns {Promise<Array<{ id, title, category, created_at, updated_at }>>}
|
|
84
|
-
*/
|
|
85
|
-
async function listEntries() {
|
|
86
|
-
await ensureDir()
|
|
87
|
-
let files
|
|
88
|
-
try {
|
|
89
|
-
files = await fs.readdir(MEMORY_DIR)
|
|
90
|
-
} catch {
|
|
91
|
-
return []
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
const entries = []
|
|
95
|
-
for (const file of files) {
|
|
96
|
-
if (file === 'MEMORY.md' || !file.endsWith('.md')) continue
|
|
97
|
-
try {
|
|
98
|
-
const raw = await fs.readFile(path.join(MEMORY_DIR, file), 'utf-8')
|
|
99
|
-
const { meta, body } = parseFrontmatter(raw)
|
|
100
|
-
if (meta.id) {
|
|
101
|
-
entries.push({
|
|
102
|
-
id: meta.id,
|
|
103
|
-
title: meta.title || '',
|
|
104
|
-
category: meta.category || 'reference',
|
|
105
|
-
created_at: meta.created_at || '',
|
|
106
|
-
updated_at: meta.updated_at || '',
|
|
107
|
-
excerpt: body.substring(0, 200),
|
|
108
|
-
})
|
|
109
|
-
}
|
|
110
|
-
} catch {
|
|
111
|
-
// Skip unreadable files
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
// Sort by updated_at descending
|
|
116
|
-
entries.sort((a, b) => (b.updated_at || '').localeCompare(a.updated_at || ''))
|
|
117
|
-
return entries
|
|
18
|
+
function listEntries() {
|
|
19
|
+
const db = getDb()
|
|
20
|
+
const rows = db.prepare(`
|
|
21
|
+
SELECT id, title, category, created_at, updated_at,
|
|
22
|
+
substr(content, 1, 200) as excerpt
|
|
23
|
+
FROM memories
|
|
24
|
+
ORDER BY updated_at DESC
|
|
25
|
+
`).all()
|
|
26
|
+
return rows
|
|
118
27
|
}
|
|
119
28
|
|
|
120
29
|
/**
|
|
121
30
|
* Load a single memory entry by ID.
|
|
122
31
|
* @param {string} id
|
|
123
|
-
* @returns {
|
|
32
|
+
* @returns {object|null}
|
|
124
33
|
*/
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
return {
|
|
130
|
-
id: meta.id || id,
|
|
131
|
-
title: meta.title || '',
|
|
132
|
-
category: meta.category || 'reference',
|
|
133
|
-
content: body,
|
|
134
|
-
created_at: meta.created_at || '',
|
|
135
|
-
updated_at: meta.updated_at || '',
|
|
136
|
-
}
|
|
137
|
-
} catch (err) {
|
|
138
|
-
if (err.code === 'ENOENT') return null
|
|
139
|
-
console.error(`[MemoryStore] Failed to load entry ${id}: ${err.message}`)
|
|
140
|
-
return null
|
|
141
|
-
}
|
|
34
|
+
function loadEntry(id) {
|
|
35
|
+
const db = getDb()
|
|
36
|
+
const row = db.prepare('SELECT * FROM memories WHERE id = ?').get(id)
|
|
37
|
+
return row || null
|
|
142
38
|
}
|
|
143
39
|
|
|
144
40
|
/**
|
|
145
41
|
* Save (create or update) a memory entry.
|
|
146
42
|
* @param {{ id?, title, category, content }} entry
|
|
147
|
-
* @returns {
|
|
43
|
+
* @returns {object} The saved entry
|
|
148
44
|
*/
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
45
|
+
function saveEntry(entry) {
|
|
46
|
+
const db = getDb()
|
|
152
47
|
const now = new Date().toISOString()
|
|
153
48
|
const id = entry.id || crypto.randomBytes(6).toString('hex')
|
|
154
49
|
const category = VALID_CATEGORIES.includes(entry.category) ? entry.category : 'reference'
|
|
155
50
|
|
|
156
|
-
|
|
157
|
-
const existing = await loadEntry(id)
|
|
51
|
+
const existing = db.prepare('SELECT created_at FROM memories WHERE id = ?').get(id)
|
|
158
52
|
|
|
159
53
|
const full = {
|
|
160
54
|
id,
|
|
@@ -165,11 +59,17 @@ async function saveEntry(entry) {
|
|
|
165
59
|
updated_at: now,
|
|
166
60
|
}
|
|
167
61
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
62
|
+
if (existing) {
|
|
63
|
+
db.prepare(`
|
|
64
|
+
UPDATE memories SET title = ?, category = ?, content = ?, updated_at = ?
|
|
65
|
+
WHERE id = ?
|
|
66
|
+
`).run(full.title, full.category, full.content, full.updated_at, full.id)
|
|
67
|
+
} else {
|
|
68
|
+
db.prepare(`
|
|
69
|
+
INSERT INTO memories (id, title, category, content, created_at, updated_at)
|
|
70
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
71
|
+
`).run(full.id, full.title, full.category, full.content, full.created_at, full.updated_at)
|
|
72
|
+
}
|
|
173
73
|
|
|
174
74
|
return full
|
|
175
75
|
}
|
|
@@ -177,59 +77,67 @@ async function saveEntry(entry) {
|
|
|
177
77
|
/**
|
|
178
78
|
* Delete a memory entry.
|
|
179
79
|
* @param {string} id
|
|
180
|
-
* @returns {
|
|
80
|
+
* @returns {boolean}
|
|
181
81
|
*/
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
return true
|
|
187
|
-
} catch (err) {
|
|
188
|
-
if (err.code === 'ENOENT') return false
|
|
189
|
-
console.error(`[MemoryStore] Failed to delete entry ${id}: ${err.message}`)
|
|
190
|
-
return false
|
|
191
|
-
}
|
|
82
|
+
function deleteEntry(id) {
|
|
83
|
+
const db = getDb()
|
|
84
|
+
const result = db.prepare('DELETE FROM memories WHERE id = ?').run(id)
|
|
85
|
+
return result.changes > 0
|
|
192
86
|
}
|
|
193
87
|
|
|
194
88
|
/**
|
|
195
|
-
*
|
|
89
|
+
* Search memory entries using FTS5 full-text search (trigram tokenizer).
|
|
90
|
+
* Falls back to LIKE for queries shorter than 3 characters (trigram minimum).
|
|
91
|
+
* @param {string} query - Search query
|
|
92
|
+
* @param {number} limit - Max results
|
|
93
|
+
* @returns {Array<{ id, title, category, content, created_at, updated_at }>}
|
|
196
94
|
*/
|
|
197
|
-
|
|
198
|
-
const
|
|
199
|
-
const
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
|
|
95
|
+
function searchEntries(query, limit = 20) {
|
|
96
|
+
const db = getDb()
|
|
97
|
+
const terms = query.split(/\s+/).filter(Boolean)
|
|
98
|
+
if (terms.length === 0) return []
|
|
99
|
+
|
|
100
|
+
// Trigram tokenizer requires 3+ character terms.
|
|
101
|
+
// If any term is shorter, use LIKE fallback for the entire query.
|
|
102
|
+
const hasShortTerm = terms.some(t => [...t].length < 3)
|
|
103
|
+
|
|
104
|
+
if (hasShortTerm) {
|
|
105
|
+
// LIKE fallback: match all terms with AND
|
|
106
|
+
const conditions = terms.map(() => '(title LIKE ? OR content LIKE ?)').join(' AND ')
|
|
107
|
+
const params = terms.flatMap(t => {
|
|
108
|
+
const like = `%${t}%`
|
|
109
|
+
return [like, like]
|
|
110
|
+
})
|
|
111
|
+
params.push(limit)
|
|
112
|
+
return db.prepare(`
|
|
113
|
+
SELECT id, title, category, content, created_at, updated_at
|
|
114
|
+
FROM memories
|
|
115
|
+
WHERE ${conditions}
|
|
116
|
+
ORDER BY updated_at DESC
|
|
117
|
+
LIMIT ?
|
|
118
|
+
`).all(...params)
|
|
220
119
|
}
|
|
221
120
|
|
|
222
|
-
|
|
121
|
+
// FTS5 trigram search: wrap each term in double quotes for exact substring match
|
|
122
|
+
const sanitized = terms.map(t => `"${t.replace(/"/g, '')}"`).join(' ')
|
|
123
|
+
return db.prepare(`
|
|
124
|
+
SELECT m.id, m.title, m.category, m.content, m.created_at, m.updated_at
|
|
125
|
+
FROM memories m
|
|
126
|
+
JOIN memories_fts f ON m.rowid = f.rowid
|
|
127
|
+
WHERE memories_fts MATCH ?
|
|
128
|
+
ORDER BY rank
|
|
129
|
+
LIMIT ?
|
|
130
|
+
`).all(sanitized, limit)
|
|
223
131
|
}
|
|
224
132
|
|
|
225
133
|
/**
|
|
226
134
|
* Get a context snippet suitable for chat injection.
|
|
227
135
|
* Returns the most important memory entries as a formatted string.
|
|
228
136
|
* @param {number} maxChars - Maximum characters to return
|
|
229
|
-
* @returns {
|
|
137
|
+
* @returns {string}
|
|
230
138
|
*/
|
|
231
|
-
|
|
232
|
-
const entries =
|
|
139
|
+
function getContextSnippet(maxChars = 2000) {
|
|
140
|
+
const entries = listEntries()
|
|
233
141
|
if (entries.length === 0) return ''
|
|
234
142
|
|
|
235
143
|
const parts = []
|
|
@@ -246,11 +154,10 @@ async function getContextSnippet(maxChars = 2000) {
|
|
|
246
154
|
}
|
|
247
155
|
|
|
248
156
|
module.exports = {
|
|
249
|
-
ensureDir,
|
|
250
157
|
listEntries,
|
|
251
158
|
loadEntry,
|
|
252
159
|
saveEntry,
|
|
253
160
|
deleteEntry,
|
|
161
|
+
searchEntries,
|
|
254
162
|
getContextSnippet,
|
|
255
|
-
MEMORY_DIR,
|
|
256
163
|
}
|