kiro-memory 1.9.0 → 3.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 +5 -1
- package/package.json +5 -5
- package/plugin/dist/cli/contextkit.js +2611 -345
- package/plugin/dist/hooks/agentSpawn.js +853 -223
- package/plugin/dist/hooks/kiro-hooks.js +841 -211
- package/plugin/dist/hooks/postToolUse.js +853 -222
- package/plugin/dist/hooks/stop.js +850 -220
- package/plugin/dist/hooks/userPromptSubmit.js +848 -216
- package/plugin/dist/index.js +843 -340
- package/plugin/dist/plugins/github/github-client.js +152 -0
- package/plugin/dist/plugins/github/index.js +412 -0
- package/plugin/dist/plugins/github/issue-parser.js +54 -0
- package/plugin/dist/plugins/slack/formatter.js +90 -0
- package/plugin/dist/plugins/slack/index.js +215 -0
- package/plugin/dist/sdk/index.js +841 -215
- package/plugin/dist/servers/mcp-server.js +4461 -397
- package/plugin/dist/services/search/EmbeddingService.js +146 -37
- package/plugin/dist/services/search/HybridSearch.js +564 -116
- package/plugin/dist/services/search/VectorSearch.js +187 -60
- package/plugin/dist/services/search/index.js +565 -254
- package/plugin/dist/services/sqlite/Backup.js +416 -0
- package/plugin/dist/services/sqlite/Database.js +126 -153
- package/plugin/dist/services/sqlite/ImportExport.js +452 -0
- package/plugin/dist/services/sqlite/Observations.js +314 -19
- package/plugin/dist/services/sqlite/Prompts.js +1 -1
- package/plugin/dist/services/sqlite/Search.js +41 -29
- package/plugin/dist/services/sqlite/Summaries.js +4 -4
- package/plugin/dist/services/sqlite/index.js +1428 -208
- package/plugin/dist/viewer.css +1 -0
- package/plugin/dist/viewer.html +2 -179
- package/plugin/dist/viewer.js +23 -24942
- package/plugin/dist/viewer.js.map +7 -0
- package/plugin/dist/worker-service.js +427 -5569
- package/plugin/dist/worker-service.js.map +7 -0
|
@@ -1,5 +1,277 @@
|
|
|
1
1
|
import { createRequire } from 'module';const require = createRequire(import.meta.url);
|
|
2
2
|
|
|
3
|
+
// src/utils/secrets.ts
|
|
4
|
+
var SECRET_PATTERNS = [
|
|
5
|
+
// AWS Access Keys (AKIA, ABIA, ACCA, ASIA prefixes + 16 alphanumeric chars)
|
|
6
|
+
{ name: "aws-key", pattern: /(?:AKIA|ABIA|ACCA|ASIA)[A-Z0-9]{16}/g },
|
|
7
|
+
// JWT tokens (three base64url segments separated by dots)
|
|
8
|
+
{ name: "jwt", pattern: /eyJ[a-zA-Z0-9_-]{10,}\.eyJ[a-zA-Z0-9_-]{10,}\.[a-zA-Z0-9_-]{10,}/g },
|
|
9
|
+
// Generic API keys in key=value or key: value assignments
|
|
10
|
+
{ name: "api-key", pattern: /(?:api[_-]?key|apikey|api[_-]?secret)\s*[:=]\s*['"]?([a-zA-Z0-9_\-]{20,})['"]?/gi },
|
|
11
|
+
// Password/secret/token in variable assignments
|
|
12
|
+
{ name: "credential", pattern: /(?:password|passwd|pwd|secret|token|auth[_-]?token|access[_-]?token|bearer)\s*[:=]\s*['"]?([^\s'"]{8,})['"]?/gi },
|
|
13
|
+
// Credentials embedded in URLs (user:pass@host)
|
|
14
|
+
{ name: "url-credential", pattern: /(?:https?:\/\/)([^:]+):([^@]+)@/g },
|
|
15
|
+
// PEM-encoded private keys (RSA, EC, DSA, OpenSSH)
|
|
16
|
+
{ name: "private-key", pattern: /-----BEGIN (?:RSA |EC |DSA |OPENSSH )?PRIVATE KEY-----/g },
|
|
17
|
+
// GitHub personal access tokens (ghp_, gho_, ghu_, ghs_, ghr_ prefixes)
|
|
18
|
+
{ name: "github-token", pattern: /gh[pousr]_[a-zA-Z0-9]{36,}/g },
|
|
19
|
+
// Slack bot/user/app tokens
|
|
20
|
+
{ name: "slack-token", pattern: /xox[bpoas]-[a-zA-Z0-9-]{10,}/g },
|
|
21
|
+
// HTTP Authorization Bearer header values
|
|
22
|
+
{ name: "bearer-header", pattern: /\bBearer\s+([a-zA-Z0-9_\-\.]{20,})/g },
|
|
23
|
+
// Generic hex secrets (32+ hex chars after a key/secret/token/password label)
|
|
24
|
+
{ name: "hex-secret", pattern: /(?:key|secret|token|password)\s*[:=]\s*['"]?([0-9a-f]{32,})['"]?/gi }
|
|
25
|
+
];
|
|
26
|
+
function redactSecrets(text) {
|
|
27
|
+
if (!text) return text;
|
|
28
|
+
let redacted = text;
|
|
29
|
+
for (const { pattern } of SECRET_PATTERNS) {
|
|
30
|
+
pattern.lastIndex = 0;
|
|
31
|
+
redacted = redacted.replace(pattern, (match) => {
|
|
32
|
+
const prefix = match.substring(0, Math.min(4, match.length));
|
|
33
|
+
return `${prefix}***REDACTED***`;
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
return redacted;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// src/utils/categorizer.ts
|
|
40
|
+
var CATEGORY_RULES = [
|
|
41
|
+
{
|
|
42
|
+
category: "security",
|
|
43
|
+
keywords: [
|
|
44
|
+
"security",
|
|
45
|
+
"vulnerability",
|
|
46
|
+
"cve",
|
|
47
|
+
"xss",
|
|
48
|
+
"csrf",
|
|
49
|
+
"injection",
|
|
50
|
+
"sanitize",
|
|
51
|
+
"escape",
|
|
52
|
+
"auth",
|
|
53
|
+
"authentication",
|
|
54
|
+
"authorization",
|
|
55
|
+
"permission",
|
|
56
|
+
"helmet",
|
|
57
|
+
"cors",
|
|
58
|
+
"rate-limit",
|
|
59
|
+
"token",
|
|
60
|
+
"encrypt",
|
|
61
|
+
"decrypt",
|
|
62
|
+
"secret",
|
|
63
|
+
"redact",
|
|
64
|
+
"owasp"
|
|
65
|
+
],
|
|
66
|
+
filePatterns: [/security/i, /auth/i, /secrets?\.ts/i],
|
|
67
|
+
weight: 10
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
category: "testing",
|
|
71
|
+
keywords: [
|
|
72
|
+
"test",
|
|
73
|
+
"spec",
|
|
74
|
+
"expect",
|
|
75
|
+
"assert",
|
|
76
|
+
"mock",
|
|
77
|
+
"stub",
|
|
78
|
+
"fixture",
|
|
79
|
+
"coverage",
|
|
80
|
+
"jest",
|
|
81
|
+
"vitest",
|
|
82
|
+
"bun test",
|
|
83
|
+
"unit test",
|
|
84
|
+
"integration test",
|
|
85
|
+
"e2e"
|
|
86
|
+
],
|
|
87
|
+
types: ["test"],
|
|
88
|
+
filePatterns: [/\.test\./i, /\.spec\./i, /tests?\//i, /__tests__/i],
|
|
89
|
+
weight: 8
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
category: "debugging",
|
|
93
|
+
keywords: [
|
|
94
|
+
"debug",
|
|
95
|
+
"fix",
|
|
96
|
+
"bug",
|
|
97
|
+
"error",
|
|
98
|
+
"crash",
|
|
99
|
+
"stacktrace",
|
|
100
|
+
"stack trace",
|
|
101
|
+
"exception",
|
|
102
|
+
"breakpoint",
|
|
103
|
+
"investigate",
|
|
104
|
+
"root cause",
|
|
105
|
+
"troubleshoot",
|
|
106
|
+
"diagnose",
|
|
107
|
+
"bisect",
|
|
108
|
+
"regression"
|
|
109
|
+
],
|
|
110
|
+
types: ["bugfix"],
|
|
111
|
+
weight: 8
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
category: "architecture",
|
|
115
|
+
keywords: [
|
|
116
|
+
"architect",
|
|
117
|
+
"design",
|
|
118
|
+
"pattern",
|
|
119
|
+
"modular",
|
|
120
|
+
"migration",
|
|
121
|
+
"schema",
|
|
122
|
+
"database",
|
|
123
|
+
"api design",
|
|
124
|
+
"abstract",
|
|
125
|
+
"dependency injection",
|
|
126
|
+
"singleton",
|
|
127
|
+
"factory",
|
|
128
|
+
"observer",
|
|
129
|
+
"middleware",
|
|
130
|
+
"pipeline",
|
|
131
|
+
"microservice",
|
|
132
|
+
"monolith"
|
|
133
|
+
],
|
|
134
|
+
types: ["decision", "constraint"],
|
|
135
|
+
weight: 7
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
category: "refactoring",
|
|
139
|
+
keywords: [
|
|
140
|
+
"refactor",
|
|
141
|
+
"rename",
|
|
142
|
+
"extract",
|
|
143
|
+
"inline",
|
|
144
|
+
"move",
|
|
145
|
+
"split",
|
|
146
|
+
"merge",
|
|
147
|
+
"simplify",
|
|
148
|
+
"cleanup",
|
|
149
|
+
"clean up",
|
|
150
|
+
"dead code",
|
|
151
|
+
"consolidate",
|
|
152
|
+
"reorganize",
|
|
153
|
+
"restructure",
|
|
154
|
+
"decouple"
|
|
155
|
+
],
|
|
156
|
+
weight: 6
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
category: "config",
|
|
160
|
+
keywords: [
|
|
161
|
+
"config",
|
|
162
|
+
"configuration",
|
|
163
|
+
"env",
|
|
164
|
+
"environment",
|
|
165
|
+
"dotenv",
|
|
166
|
+
".env",
|
|
167
|
+
"settings",
|
|
168
|
+
"tsconfig",
|
|
169
|
+
"eslint",
|
|
170
|
+
"prettier",
|
|
171
|
+
"webpack",
|
|
172
|
+
"vite",
|
|
173
|
+
"esbuild",
|
|
174
|
+
"docker",
|
|
175
|
+
"ci/cd",
|
|
176
|
+
"github actions",
|
|
177
|
+
"deploy",
|
|
178
|
+
"build",
|
|
179
|
+
"bundle",
|
|
180
|
+
"package.json"
|
|
181
|
+
],
|
|
182
|
+
filePatterns: [
|
|
183
|
+
/\.config\./i,
|
|
184
|
+
/\.env/i,
|
|
185
|
+
/tsconfig/i,
|
|
186
|
+
/\.ya?ml/i,
|
|
187
|
+
/Dockerfile/i,
|
|
188
|
+
/docker-compose/i
|
|
189
|
+
],
|
|
190
|
+
weight: 5
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
category: "docs",
|
|
194
|
+
keywords: [
|
|
195
|
+
"document",
|
|
196
|
+
"readme",
|
|
197
|
+
"changelog",
|
|
198
|
+
"jsdoc",
|
|
199
|
+
"comment",
|
|
200
|
+
"explain",
|
|
201
|
+
"guide",
|
|
202
|
+
"tutorial",
|
|
203
|
+
"api doc",
|
|
204
|
+
"openapi",
|
|
205
|
+
"swagger"
|
|
206
|
+
],
|
|
207
|
+
types: ["docs"],
|
|
208
|
+
filePatterns: [/\.md$/i, /docs?\//i, /readme/i, /changelog/i],
|
|
209
|
+
weight: 5
|
|
210
|
+
},
|
|
211
|
+
{
|
|
212
|
+
category: "feature-dev",
|
|
213
|
+
keywords: [
|
|
214
|
+
"feature",
|
|
215
|
+
"implement",
|
|
216
|
+
"add",
|
|
217
|
+
"create",
|
|
218
|
+
"new",
|
|
219
|
+
"endpoint",
|
|
220
|
+
"component",
|
|
221
|
+
"module",
|
|
222
|
+
"service",
|
|
223
|
+
"handler",
|
|
224
|
+
"route",
|
|
225
|
+
"hook",
|
|
226
|
+
"plugin",
|
|
227
|
+
"integration"
|
|
228
|
+
],
|
|
229
|
+
types: ["feature", "file-write"],
|
|
230
|
+
weight: 3
|
|
231
|
+
// lowest — generic catch-all for development
|
|
232
|
+
}
|
|
233
|
+
];
|
|
234
|
+
function categorize(input) {
|
|
235
|
+
const scores = /* @__PURE__ */ new Map();
|
|
236
|
+
const searchText = [
|
|
237
|
+
input.title,
|
|
238
|
+
input.text || "",
|
|
239
|
+
input.narrative || "",
|
|
240
|
+
input.concepts || ""
|
|
241
|
+
].join(" ").toLowerCase();
|
|
242
|
+
const allFiles = [input.filesModified || "", input.filesRead || ""].join(",");
|
|
243
|
+
for (const rule of CATEGORY_RULES) {
|
|
244
|
+
let score = 0;
|
|
245
|
+
for (const kw of rule.keywords) {
|
|
246
|
+
if (searchText.includes(kw.toLowerCase())) {
|
|
247
|
+
score += rule.weight;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
if (rule.types && rule.types.includes(input.type)) {
|
|
251
|
+
score += rule.weight * 2;
|
|
252
|
+
}
|
|
253
|
+
if (rule.filePatterns && allFiles) {
|
|
254
|
+
for (const pattern of rule.filePatterns) {
|
|
255
|
+
if (pattern.test(allFiles)) {
|
|
256
|
+
score += rule.weight;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
if (score > 0) {
|
|
261
|
+
scores.set(rule.category, (scores.get(rule.category) || 0) + score);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
let bestCategory = "general";
|
|
265
|
+
let bestScore = 0;
|
|
266
|
+
for (const [category, score] of scores) {
|
|
267
|
+
if (score > bestScore) {
|
|
268
|
+
bestScore = score;
|
|
269
|
+
bestCategory = category;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
return bestCategory;
|
|
273
|
+
}
|
|
274
|
+
|
|
3
275
|
// src/services/sqlite/Observations.ts
|
|
4
276
|
function escapeLikePattern(input) {
|
|
5
277
|
return input.replace(/[%_\\]/g, "\\$&");
|
|
@@ -14,11 +286,23 @@ function isDuplicateObservation(db, contentHash, windowMs = 3e4) {
|
|
|
14
286
|
}
|
|
15
287
|
function createObservation(db, memorySessionId, project, type, title, subtitle, text, narrative, facts, concepts, filesRead, filesModified, promptNumber, contentHash = null, discoveryTokens = 0) {
|
|
16
288
|
const now = /* @__PURE__ */ new Date();
|
|
289
|
+
const safeTitle = redactSecrets(title);
|
|
290
|
+
const safeText = text ? redactSecrets(text) : text;
|
|
291
|
+
const safeNarrative = narrative ? redactSecrets(narrative) : narrative;
|
|
292
|
+
const autoCategory = categorize({
|
|
293
|
+
type,
|
|
294
|
+
title: safeTitle,
|
|
295
|
+
text: safeText,
|
|
296
|
+
narrative: safeNarrative,
|
|
297
|
+
concepts,
|
|
298
|
+
filesModified,
|
|
299
|
+
filesRead
|
|
300
|
+
});
|
|
17
301
|
const result = db.run(
|
|
18
302
|
`INSERT INTO observations
|
|
19
|
-
(memory_session_id, project, type, title, subtitle, text, narrative, facts, concepts, files_read, files_modified, prompt_number, created_at, created_at_epoch, content_hash, discovery_tokens)
|
|
20
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
21
|
-
[memorySessionId, project, type,
|
|
303
|
+
(memory_session_id, project, type, title, subtitle, text, narrative, facts, concepts, files_read, files_modified, prompt_number, created_at, created_at_epoch, content_hash, discovery_tokens, auto_category)
|
|
304
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
305
|
+
[memorySessionId, project, type, safeTitle, subtitle, safeText, safeNarrative, facts, concepts, filesRead, filesModified, promptNumber, now.toISOString(), now.getTime(), contentHash, discoveryTokens, autoCategory]
|
|
22
306
|
);
|
|
23
307
|
return Number(result.lastInsertRowid);
|
|
24
308
|
}
|
|
@@ -30,16 +314,16 @@ function getObservationsBySession(db, memorySessionId) {
|
|
|
30
314
|
}
|
|
31
315
|
function getObservationsByProject(db, project, limit = 100) {
|
|
32
316
|
const query = db.query(
|
|
33
|
-
"SELECT * FROM observations WHERE project = ? ORDER BY created_at_epoch DESC LIMIT ?"
|
|
317
|
+
"SELECT * FROM observations WHERE project = ? ORDER BY created_at_epoch DESC, id DESC LIMIT ?"
|
|
34
318
|
);
|
|
35
319
|
return query.all(project, limit);
|
|
36
320
|
}
|
|
37
321
|
function searchObservations(db, searchTerm, project) {
|
|
38
322
|
const sql = project ? `SELECT * FROM observations
|
|
39
323
|
WHERE project = ? AND (title LIKE ? ESCAPE '\\' OR text LIKE ? ESCAPE '\\' OR narrative LIKE ? ESCAPE '\\')
|
|
40
|
-
ORDER BY created_at_epoch DESC` : `SELECT * FROM observations
|
|
324
|
+
ORDER BY created_at_epoch DESC, id DESC` : `SELECT * FROM observations
|
|
41
325
|
WHERE title LIKE ? ESCAPE '\\' OR text LIKE ? ESCAPE '\\' OR narrative LIKE ? ESCAPE '\\'
|
|
42
|
-
ORDER BY created_at_epoch DESC`;
|
|
326
|
+
ORDER BY created_at_epoch DESC, id DESC`;
|
|
43
327
|
const pattern = `%${escapeLikePattern(searchTerm)}%`;
|
|
44
328
|
const query = db.query(sql);
|
|
45
329
|
if (project) {
|
|
@@ -72,21 +356,32 @@ function consolidateObservations(db, project, options = {}) {
|
|
|
72
356
|
ORDER BY cnt DESC
|
|
73
357
|
`).all(project, minGroupSize);
|
|
74
358
|
if (groups.length === 0) return { merged: 0, removed: 0 };
|
|
75
|
-
|
|
76
|
-
|
|
359
|
+
if (options.dryRun) {
|
|
360
|
+
let totalMerged = 0;
|
|
361
|
+
let totalRemoved = 0;
|
|
362
|
+
for (const group of groups) {
|
|
363
|
+
const obsIds = group.ids.split(",").map(Number);
|
|
364
|
+
const placeholders = obsIds.map(() => "?").join(",");
|
|
365
|
+
const count = db.query(
|
|
366
|
+
`SELECT COUNT(*) as cnt FROM observations WHERE id IN (${placeholders})`
|
|
367
|
+
).get(...obsIds)?.cnt || 0;
|
|
368
|
+
if (count >= minGroupSize) {
|
|
369
|
+
totalMerged += 1;
|
|
370
|
+
totalRemoved += count - 1;
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
return { merged: totalMerged, removed: totalRemoved };
|
|
374
|
+
}
|
|
77
375
|
const runConsolidation = db.transaction(() => {
|
|
376
|
+
let merged = 0;
|
|
377
|
+
let removed = 0;
|
|
78
378
|
for (const group of groups) {
|
|
79
379
|
const obsIds = group.ids.split(",").map(Number);
|
|
80
380
|
const placeholders = obsIds.map(() => "?").join(",");
|
|
81
381
|
const observations = db.query(
|
|
82
|
-
`SELECT * FROM observations WHERE id IN (${placeholders}) ORDER BY created_at_epoch DESC`
|
|
382
|
+
`SELECT * FROM observations WHERE id IN (${placeholders}) ORDER BY created_at_epoch DESC, id DESC`
|
|
83
383
|
).all(...obsIds);
|
|
84
384
|
if (observations.length < minGroupSize) continue;
|
|
85
|
-
if (options.dryRun) {
|
|
86
|
-
totalMerged += 1;
|
|
87
|
-
totalRemoved += observations.length - 1;
|
|
88
|
-
continue;
|
|
89
|
-
}
|
|
90
385
|
const keeper = observations[0];
|
|
91
386
|
const others = observations.slice(1);
|
|
92
387
|
const uniqueTexts = /* @__PURE__ */ new Set();
|
|
@@ -99,18 +394,18 @@ function consolidateObservations(db, project, options = {}) {
|
|
|
99
394
|
const consolidatedText = Array.from(uniqueTexts).join("\n---\n").substring(0, 1e5);
|
|
100
395
|
db.run(
|
|
101
396
|
"UPDATE observations SET text = ?, title = ? WHERE id = ?",
|
|
102
|
-
[consolidatedText, `[
|
|
397
|
+
[consolidatedText, `[consolidated x${observations.length}] ${keeper.title}`, keeper.id]
|
|
103
398
|
);
|
|
104
399
|
const removeIds = others.map((o) => o.id);
|
|
105
400
|
const removePlaceholders = removeIds.map(() => "?").join(",");
|
|
106
401
|
db.run(`DELETE FROM observations WHERE id IN (${removePlaceholders})`, removeIds);
|
|
107
402
|
db.run(`DELETE FROM observation_embeddings WHERE observation_id IN (${removePlaceholders})`, removeIds);
|
|
108
|
-
|
|
109
|
-
|
|
403
|
+
merged += 1;
|
|
404
|
+
removed += removeIds.length;
|
|
110
405
|
}
|
|
406
|
+
return { merged, removed };
|
|
111
407
|
});
|
|
112
|
-
runConsolidation();
|
|
113
|
-
return { merged: totalMerged, removed: totalRemoved };
|
|
408
|
+
return runConsolidation();
|
|
114
409
|
}
|
|
115
410
|
export {
|
|
116
411
|
consolidateObservations,
|
|
@@ -19,7 +19,7 @@ function getPromptsBySession(db, contentSessionId) {
|
|
|
19
19
|
}
|
|
20
20
|
function getPromptsByProject(db, project, limit = 100) {
|
|
21
21
|
const query = db.query(
|
|
22
|
-
"SELECT * FROM prompts WHERE project = ? ORDER BY created_at_epoch DESC LIMIT ?"
|
|
22
|
+
"SELECT * FROM prompts WHERE project = ? ORDER BY created_at_epoch DESC, id DESC LIMIT ?"
|
|
23
23
|
);
|
|
24
24
|
return query.all(project, limit);
|
|
25
25
|
}
|
|
@@ -8,7 +8,7 @@ function escapeLikePattern(input) {
|
|
|
8
8
|
}
|
|
9
9
|
function sanitizeFTS5Query(query) {
|
|
10
10
|
const trimmed = query.length > 1e4 ? query.substring(0, 1e4) : query;
|
|
11
|
-
const terms = trimmed.replace(/[""]/g, "").split(/\s+/).filter((t) => t.length > 0).slice(0, 100).map((t) => `"${t}"`);
|
|
11
|
+
const terms = trimmed.replace(/[""\u0022]/g, "").split(/\s+/).filter((t) => t.length > 0).slice(0, 100).map((t) => `"${t}"`);
|
|
12
12
|
return terms.join(" ");
|
|
13
13
|
}
|
|
14
14
|
function searchObservationsFTS(db, query, filters = {}) {
|
|
@@ -105,7 +105,7 @@ function searchObservationsLIKE(db, query, filters = {}) {
|
|
|
105
105
|
sql += " AND created_at_epoch <= ?";
|
|
106
106
|
params.push(filters.dateEnd);
|
|
107
107
|
}
|
|
108
|
-
sql += " ORDER BY created_at_epoch DESC LIMIT ?";
|
|
108
|
+
sql += " ORDER BY created_at_epoch DESC, id DESC LIMIT ?";
|
|
109
109
|
params.push(limit);
|
|
110
110
|
const stmt = db.query(sql);
|
|
111
111
|
return stmt.all(...params);
|
|
@@ -130,7 +130,7 @@ function searchSummariesFiltered(db, query, filters = {}) {
|
|
|
130
130
|
sql += " AND created_at_epoch <= ?";
|
|
131
131
|
params.push(filters.dateEnd);
|
|
132
132
|
}
|
|
133
|
-
sql += " ORDER BY created_at_epoch DESC LIMIT ?";
|
|
133
|
+
sql += " ORDER BY created_at_epoch DESC, id DESC LIMIT ?";
|
|
134
134
|
params.push(limit);
|
|
135
135
|
const stmt = db.query(sql);
|
|
136
136
|
return stmt.all(...params);
|
|
@@ -140,7 +140,7 @@ function getObservationsByIds(db, ids) {
|
|
|
140
140
|
const validIds = ids.filter((id) => typeof id === "number" && Number.isInteger(id) && id > 0).slice(0, 500);
|
|
141
141
|
if (validIds.length === 0) return [];
|
|
142
142
|
const placeholders = validIds.map(() => "?").join(",");
|
|
143
|
-
const sql = `SELECT * FROM observations WHERE id IN (${placeholders}) ORDER BY created_at_epoch DESC`;
|
|
143
|
+
const sql = `SELECT * FROM observations WHERE id IN (${placeholders}) ORDER BY created_at_epoch DESC, id DESC`;
|
|
144
144
|
const stmt = db.query(sql);
|
|
145
145
|
return stmt.all(...validIds);
|
|
146
146
|
}
|
|
@@ -152,11 +152,11 @@ function getTimeline(db, anchorId, depthBefore = 5, depthAfter = 5) {
|
|
|
152
152
|
const beforeStmt = db.query(`
|
|
153
153
|
SELECT id, 'observation' as type, title, text as content, project, created_at, created_at_epoch
|
|
154
154
|
FROM observations
|
|
155
|
-
WHERE created_at_epoch < ?
|
|
156
|
-
ORDER BY created_at_epoch DESC
|
|
155
|
+
WHERE (created_at_epoch < ? OR (created_at_epoch = ? AND id < ?))
|
|
156
|
+
ORDER BY created_at_epoch DESC, id DESC
|
|
157
157
|
LIMIT ?
|
|
158
158
|
`);
|
|
159
|
-
const before = beforeStmt.all(anchorEpoch, depthBefore).reverse();
|
|
159
|
+
const before = beforeStmt.all(anchorEpoch, anchorEpoch, anchorId, depthBefore).reverse();
|
|
160
160
|
const selfStmt = db.query(`
|
|
161
161
|
SELECT id, 'observation' as type, title, text as content, project, created_at, created_at_epoch
|
|
162
162
|
FROM observations WHERE id = ?
|
|
@@ -165,34 +165,46 @@ function getTimeline(db, anchorId, depthBefore = 5, depthAfter = 5) {
|
|
|
165
165
|
const afterStmt = db.query(`
|
|
166
166
|
SELECT id, 'observation' as type, title, text as content, project, created_at, created_at_epoch
|
|
167
167
|
FROM observations
|
|
168
|
-
WHERE created_at_epoch > ?
|
|
169
|
-
ORDER BY created_at_epoch ASC
|
|
168
|
+
WHERE (created_at_epoch > ? OR (created_at_epoch = ? AND id > ?))
|
|
169
|
+
ORDER BY created_at_epoch ASC, id ASC
|
|
170
170
|
LIMIT ?
|
|
171
171
|
`);
|
|
172
|
-
const after = afterStmt.all(anchorEpoch, depthAfter);
|
|
172
|
+
const after = afterStmt.all(anchorEpoch, anchorEpoch, anchorId, depthAfter);
|
|
173
173
|
return [...before, ...self, ...after];
|
|
174
174
|
}
|
|
175
175
|
function getProjectStats(db, project) {
|
|
176
|
-
const
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
176
|
+
const sql = `
|
|
177
|
+
WITH
|
|
178
|
+
obs_stats AS (
|
|
179
|
+
SELECT
|
|
180
|
+
COUNT(*) as count,
|
|
181
|
+
COALESCE(SUM(discovery_tokens), 0) as discovery_tokens,
|
|
182
|
+
COALESCE(SUM(
|
|
183
|
+
CAST((LENGTH(COALESCE(title, '')) + LENGTH(COALESCE(narrative, ''))) / 4 AS INTEGER)
|
|
184
|
+
), 0) as read_tokens
|
|
185
|
+
FROM observations WHERE project = ?
|
|
186
|
+
),
|
|
187
|
+
sum_count AS (SELECT COUNT(*) as count FROM summaries WHERE project = ?),
|
|
188
|
+
ses_count AS (SELECT COUNT(*) as count FROM sessions WHERE project = ?),
|
|
189
|
+
prm_count AS (SELECT COUNT(*) as count FROM prompts WHERE project = ?)
|
|
190
|
+
SELECT
|
|
191
|
+
obs_stats.count as observations,
|
|
192
|
+
obs_stats.discovery_tokens,
|
|
193
|
+
obs_stats.read_tokens,
|
|
194
|
+
sum_count.count as summaries,
|
|
195
|
+
ses_count.count as sessions,
|
|
196
|
+
prm_count.count as prompts
|
|
197
|
+
FROM obs_stats, sum_count, ses_count, prm_count
|
|
198
|
+
`;
|
|
199
|
+
const row = db.query(sql).get(project, project, project, project);
|
|
200
|
+
const discoveryTokens = row?.discovery_tokens || 0;
|
|
201
|
+
const readTokens = row?.read_tokens || 0;
|
|
190
202
|
const savings = Math.max(0, discoveryTokens - readTokens);
|
|
191
203
|
return {
|
|
192
|
-
observations:
|
|
193
|
-
summaries:
|
|
194
|
-
sessions:
|
|
195
|
-
prompts:
|
|
204
|
+
observations: row?.observations || 0,
|
|
205
|
+
summaries: row?.summaries || 0,
|
|
206
|
+
sessions: row?.sessions || 0,
|
|
207
|
+
prompts: row?.prompts || 0,
|
|
196
208
|
tokenEconomics: { discoveryTokens, readTokens, savings }
|
|
197
209
|
};
|
|
198
210
|
}
|
|
@@ -200,7 +212,7 @@ function getStaleObservations(db, project) {
|
|
|
200
212
|
const rows = db.query(`
|
|
201
213
|
SELECT * FROM observations
|
|
202
214
|
WHERE project = ? AND files_modified IS NOT NULL AND files_modified != ''
|
|
203
|
-
ORDER BY created_at_epoch DESC
|
|
215
|
+
ORDER BY created_at_epoch DESC, id DESC
|
|
204
216
|
LIMIT 500
|
|
205
217
|
`).all(project);
|
|
206
218
|
const staleObs = [];
|
|
@@ -15,21 +15,21 @@ function createSummary(db, sessionId, project, request, investigated, learned, c
|
|
|
15
15
|
return Number(result.lastInsertRowid);
|
|
16
16
|
}
|
|
17
17
|
function getSummaryBySession(db, sessionId) {
|
|
18
|
-
const query = db.query("SELECT * FROM summaries WHERE session_id = ? ORDER BY created_at_epoch DESC LIMIT 1");
|
|
18
|
+
const query = db.query("SELECT * FROM summaries WHERE session_id = ? ORDER BY created_at_epoch DESC, id DESC LIMIT 1");
|
|
19
19
|
return query.get(sessionId);
|
|
20
20
|
}
|
|
21
21
|
function getSummariesByProject(db, project, limit = 50) {
|
|
22
22
|
const query = db.query(
|
|
23
|
-
"SELECT * FROM summaries WHERE project = ? ORDER BY created_at_epoch DESC LIMIT ?"
|
|
23
|
+
"SELECT * FROM summaries WHERE project = ? ORDER BY created_at_epoch DESC, id DESC LIMIT ?"
|
|
24
24
|
);
|
|
25
25
|
return query.all(project, limit);
|
|
26
26
|
}
|
|
27
27
|
function searchSummaries(db, searchTerm, project) {
|
|
28
28
|
const sql = project ? `SELECT * FROM summaries
|
|
29
29
|
WHERE project = ? AND (request LIKE ? ESCAPE '\\' OR learned LIKE ? ESCAPE '\\' OR completed LIKE ? ESCAPE '\\' OR notes LIKE ? ESCAPE '\\')
|
|
30
|
-
ORDER BY created_at_epoch DESC` : `SELECT * FROM summaries
|
|
30
|
+
ORDER BY created_at_epoch DESC, id DESC` : `SELECT * FROM summaries
|
|
31
31
|
WHERE request LIKE ? ESCAPE '\\' OR learned LIKE ? ESCAPE '\\' OR completed LIKE ? ESCAPE '\\' OR notes LIKE ? ESCAPE '\\'
|
|
32
|
-
ORDER BY created_at_epoch DESC`;
|
|
32
|
+
ORDER BY created_at_epoch DESC, id DESC`;
|
|
33
33
|
const pattern = `%${escapeLikePattern(searchTerm)}%`;
|
|
34
34
|
const query = db.query(sql);
|
|
35
35
|
if (project) {
|