kiro-memory 1.7.1 → 1.8.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 +26 -7
- package/package.json +3 -5
- package/plugin/dist/cli/contextkit.js +177 -65
- package/plugin/dist/hooks/agentSpawn.js +177 -65
- package/plugin/dist/hooks/kiro-hooks.js +177 -65
- package/plugin/dist/hooks/postToolUse.js +415 -106
- package/plugin/dist/hooks/stop.js +213 -74
- package/plugin/dist/hooks/userPromptSubmit.js +177 -65
- package/plugin/dist/index.js +177 -65
- package/plugin/dist/sdk/index.js +177 -65
- package/plugin/dist/servers/mcp-server.js +28 -0
- package/plugin/dist/services/search/HybridSearch.js +66 -40
- package/plugin/dist/services/search/index.js +66 -40
- package/plugin/dist/services/sqlite/Database.js +23 -0
- package/plugin/dist/services/sqlite/Observations.js +48 -36
- package/plugin/dist/services/sqlite/Search.js +17 -4
- package/plugin/dist/services/sqlite/index.js +88 -40
- package/plugin/dist/viewer.html +5 -0
- package/plugin/dist/viewer.js +529 -250
- package/plugin/dist/worker-service.js +263 -44
|
@@ -1570,19 +1570,28 @@ __export(Observations_exports, {
|
|
|
1570
1570
|
deleteObservation: () => deleteObservation,
|
|
1571
1571
|
getObservationsByProject: () => getObservationsByProject,
|
|
1572
1572
|
getObservationsBySession: () => getObservationsBySession,
|
|
1573
|
+
isDuplicateObservation: () => isDuplicateObservation,
|
|
1573
1574
|
searchObservations: () => searchObservations,
|
|
1574
1575
|
updateLastAccessed: () => updateLastAccessed
|
|
1575
1576
|
});
|
|
1576
1577
|
function escapeLikePattern(input) {
|
|
1577
1578
|
return input.replace(/[%_\\]/g, "\\$&");
|
|
1578
1579
|
}
|
|
1579
|
-
function
|
|
1580
|
+
function isDuplicateObservation(db2, contentHash, windowMs = 3e4) {
|
|
1581
|
+
if (!contentHash) return false;
|
|
1582
|
+
const threshold = Date.now() - windowMs;
|
|
1583
|
+
const result = db2.query(
|
|
1584
|
+
"SELECT id FROM observations WHERE content_hash = ? AND created_at_epoch > ? LIMIT 1"
|
|
1585
|
+
).get(contentHash, threshold);
|
|
1586
|
+
return !!result;
|
|
1587
|
+
}
|
|
1588
|
+
function createObservation(db2, memorySessionId, project, type, title, subtitle, text, narrative, facts, concepts, filesRead, filesModified, promptNumber, contentHash = null, discoveryTokens = 0) {
|
|
1580
1589
|
const now = /* @__PURE__ */ new Date();
|
|
1581
1590
|
const result = db2.run(
|
|
1582
|
-
`INSERT INTO observations
|
|
1583
|
-
(memory_session_id, project, type, title, subtitle, text, narrative, facts, concepts, files_read, files_modified, prompt_number, created_at, created_at_epoch)
|
|
1584
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
1585
|
-
[memorySessionId, project, type, title, subtitle, text, narrative, facts, concepts, filesRead, filesModified, promptNumber, now.toISOString(), now.getTime()]
|
|
1591
|
+
`INSERT INTO observations
|
|
1592
|
+
(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)
|
|
1593
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
1594
|
+
[memorySessionId, project, type, title, subtitle, text, narrative, facts, concepts, filesRead, filesModified, promptNumber, now.toISOString(), now.getTime(), contentHash, discoveryTokens]
|
|
1586
1595
|
);
|
|
1587
1596
|
return Number(result.lastInsertRowid);
|
|
1588
1597
|
}
|
|
@@ -1638,39 +1647,42 @@ function consolidateObservations(db2, project, options = {}) {
|
|
|
1638
1647
|
if (groups.length === 0) return { merged: 0, removed: 0 };
|
|
1639
1648
|
let totalMerged = 0;
|
|
1640
1649
|
let totalRemoved = 0;
|
|
1641
|
-
|
|
1642
|
-
const
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
const keeper = observations[0];
|
|
1654
|
-
const others = observations.slice(1);
|
|
1655
|
-
const uniqueTexts = /* @__PURE__ */ new Set();
|
|
1656
|
-
if (keeper.text) uniqueTexts.add(keeper.text);
|
|
1657
|
-
for (const obs of others) {
|
|
1658
|
-
if (obs.text && !uniqueTexts.has(obs.text)) {
|
|
1659
|
-
uniqueTexts.add(obs.text);
|
|
1650
|
+
const runConsolidation = db2.transaction(() => {
|
|
1651
|
+
for (const group of groups) {
|
|
1652
|
+
const obsIds = group.ids.split(",").map(Number);
|
|
1653
|
+
const placeholders = obsIds.map(() => "?").join(",");
|
|
1654
|
+
const observations = db2.query(
|
|
1655
|
+
`SELECT * FROM observations WHERE id IN (${placeholders}) ORDER BY created_at_epoch DESC`
|
|
1656
|
+
).all(...obsIds);
|
|
1657
|
+
if (observations.length < minGroupSize) continue;
|
|
1658
|
+
if (options.dryRun) {
|
|
1659
|
+
totalMerged += 1;
|
|
1660
|
+
totalRemoved += observations.length - 1;
|
|
1661
|
+
continue;
|
|
1660
1662
|
}
|
|
1663
|
+
const keeper = observations[0];
|
|
1664
|
+
const others = observations.slice(1);
|
|
1665
|
+
const uniqueTexts = /* @__PURE__ */ new Set();
|
|
1666
|
+
if (keeper.text) uniqueTexts.add(keeper.text);
|
|
1667
|
+
for (const obs of others) {
|
|
1668
|
+
if (obs.text && !uniqueTexts.has(obs.text)) {
|
|
1669
|
+
uniqueTexts.add(obs.text);
|
|
1670
|
+
}
|
|
1671
|
+
}
|
|
1672
|
+
const consolidatedText = Array.from(uniqueTexts).join("\n---\n").substring(0, 1e5);
|
|
1673
|
+
db2.run(
|
|
1674
|
+
"UPDATE observations SET text = ?, title = ? WHERE id = ?",
|
|
1675
|
+
[consolidatedText, `[consolidato x${observations.length}] ${keeper.title}`, keeper.id]
|
|
1676
|
+
);
|
|
1677
|
+
const removeIds = others.map((o) => o.id);
|
|
1678
|
+
const removePlaceholders = removeIds.map(() => "?").join(",");
|
|
1679
|
+
db2.run(`DELETE FROM observations WHERE id IN (${removePlaceholders})`, removeIds);
|
|
1680
|
+
db2.run(`DELETE FROM observation_embeddings WHERE observation_id IN (${removePlaceholders})`, removeIds);
|
|
1681
|
+
totalMerged += 1;
|
|
1682
|
+
totalRemoved += removeIds.length;
|
|
1661
1683
|
}
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
"UPDATE observations SET text = ?, title = ? WHERE id = ?",
|
|
1665
|
-
[consolidatedText, `[consolidato x${observations.length}] ${keeper.title}`, keeper.id]
|
|
1666
|
-
);
|
|
1667
|
-
const removeIds = others.map((o) => o.id);
|
|
1668
|
-
const removePlaceholders = removeIds.map(() => "?").join(",");
|
|
1669
|
-
db2.run(`DELETE FROM observations WHERE id IN (${removePlaceholders})`, removeIds);
|
|
1670
|
-
db2.run(`DELETE FROM observation_embeddings WHERE observation_id IN (${removePlaceholders})`, removeIds);
|
|
1671
|
-
totalMerged += 1;
|
|
1672
|
-
totalRemoved += removeIds.length;
|
|
1673
|
-
}
|
|
1684
|
+
});
|
|
1685
|
+
runConsolidation();
|
|
1674
1686
|
return { merged: totalMerged, removed: totalRemoved };
|
|
1675
1687
|
}
|
|
1676
1688
|
var init_Observations = __esm({
|
|
@@ -1728,7 +1740,7 @@ function searchObservationsFTS(db2, query, filters = {}) {
|
|
|
1728
1740
|
sql += " AND o.created_at_epoch <= ?";
|
|
1729
1741
|
params.push(filters.dateEnd);
|
|
1730
1742
|
}
|
|
1731
|
-
sql +=
|
|
1743
|
+
sql += ` ORDER BY bm25(observations_fts, ${BM25_WEIGHTS}) LIMIT ?`;
|
|
1732
1744
|
params.push(limit);
|
|
1733
1745
|
const stmt = db2.query(sql);
|
|
1734
1746
|
return stmt.all(...params);
|
|
@@ -1742,7 +1754,7 @@ function searchObservationsFTSWithRank(db2, query, filters = {}) {
|
|
|
1742
1754
|
const safeQuery = sanitizeFTS5Query(query);
|
|
1743
1755
|
if (!safeQuery) return [];
|
|
1744
1756
|
let sql = `
|
|
1745
|
-
SELECT o.*,
|
|
1757
|
+
SELECT o.*, bm25(observations_fts, ${BM25_WEIGHTS}) as fts5_rank FROM observations o
|
|
1746
1758
|
JOIN observations_fts fts ON o.id = fts.rowid
|
|
1747
1759
|
WHERE observations_fts MATCH ?
|
|
1748
1760
|
`;
|
|
@@ -1763,7 +1775,7 @@ function searchObservationsFTSWithRank(db2, query, filters = {}) {
|
|
|
1763
1775
|
sql += " AND o.created_at_epoch <= ?";
|
|
1764
1776
|
params.push(filters.dateEnd);
|
|
1765
1777
|
}
|
|
1766
|
-
sql +=
|
|
1778
|
+
sql += ` ORDER BY bm25(observations_fts, ${BM25_WEIGHTS}) LIMIT ?`;
|
|
1767
1779
|
params.push(limit);
|
|
1768
1780
|
const stmt = db2.query(sql);
|
|
1769
1781
|
return stmt.all(...params);
|
|
@@ -1867,11 +1879,23 @@ function getProjectStats(db2, project) {
|
|
|
1867
1879
|
const sumStmt = db2.query("SELECT COUNT(*) as count FROM summaries WHERE project = ?");
|
|
1868
1880
|
const sesStmt = db2.query("SELECT COUNT(*) as count FROM sessions WHERE project = ?");
|
|
1869
1881
|
const prmStmt = db2.query("SELECT COUNT(*) as count FROM prompts WHERE project = ?");
|
|
1882
|
+
const discoveryStmt = db2.query(
|
|
1883
|
+
"SELECT COALESCE(SUM(discovery_tokens), 0) as total FROM observations WHERE project = ?"
|
|
1884
|
+
);
|
|
1885
|
+
const discoveryTokens = discoveryStmt.get(project)?.total || 0;
|
|
1886
|
+
const readStmt = db2.query(
|
|
1887
|
+
`SELECT COALESCE(SUM(
|
|
1888
|
+
CAST((LENGTH(COALESCE(title, '')) + LENGTH(COALESCE(narrative, ''))) / 4 AS INTEGER)
|
|
1889
|
+
), 0) as total FROM observations WHERE project = ?`
|
|
1890
|
+
);
|
|
1891
|
+
const readTokens = readStmt.get(project)?.total || 0;
|
|
1892
|
+
const savings = Math.max(0, discoveryTokens - readTokens);
|
|
1870
1893
|
return {
|
|
1871
1894
|
observations: obsStmt.get(project)?.count || 0,
|
|
1872
1895
|
summaries: sumStmt.get(project)?.count || 0,
|
|
1873
1896
|
sessions: sesStmt.get(project)?.count || 0,
|
|
1874
|
-
prompts: prmStmt.get(project)?.count || 0
|
|
1897
|
+
prompts: prmStmt.get(project)?.count || 0,
|
|
1898
|
+
tokenEconomics: { discoveryTokens, readTokens, savings }
|
|
1875
1899
|
};
|
|
1876
1900
|
}
|
|
1877
1901
|
function getStaleObservations(db2, project) {
|
|
@@ -1913,9 +1937,11 @@ function markObservationsStale(db2, ids, stale) {
|
|
|
1913
1937
|
[stale ? 1 : 0, ...validIds]
|
|
1914
1938
|
);
|
|
1915
1939
|
}
|
|
1940
|
+
var BM25_WEIGHTS;
|
|
1916
1941
|
var init_Search = __esm({
|
|
1917
1942
|
"src/services/sqlite/Search.ts"() {
|
|
1918
1943
|
"use strict";
|
|
1944
|
+
BM25_WEIGHTS = "10.0, 1.0, 5.0, 3.0";
|
|
1919
1945
|
}
|
|
1920
1946
|
});
|
|
1921
1947
|
|
|
@@ -3945,6 +3971,29 @@ var MigrationRunner = class {
|
|
|
3945
3971
|
db2.run("CREATE INDEX IF NOT EXISTS idx_checkpoints_project ON checkpoints(project)");
|
|
3946
3972
|
db2.run("CREATE INDEX IF NOT EXISTS idx_checkpoints_epoch ON checkpoints(created_at_epoch)");
|
|
3947
3973
|
}
|
|
3974
|
+
},
|
|
3975
|
+
{
|
|
3976
|
+
version: 7,
|
|
3977
|
+
up: (db2) => {
|
|
3978
|
+
db2.run("ALTER TABLE observations ADD COLUMN content_hash TEXT");
|
|
3979
|
+
db2.run("CREATE INDEX IF NOT EXISTS idx_observations_hash ON observations(content_hash)");
|
|
3980
|
+
}
|
|
3981
|
+
},
|
|
3982
|
+
{
|
|
3983
|
+
version: 8,
|
|
3984
|
+
up: (db2) => {
|
|
3985
|
+
db2.run("ALTER TABLE observations ADD COLUMN discovery_tokens INTEGER DEFAULT 0");
|
|
3986
|
+
db2.run("ALTER TABLE summaries ADD COLUMN discovery_tokens INTEGER DEFAULT 0");
|
|
3987
|
+
}
|
|
3988
|
+
},
|
|
3989
|
+
{
|
|
3990
|
+
version: 9,
|
|
3991
|
+
up: (db2) => {
|
|
3992
|
+
db2.run("CREATE INDEX IF NOT EXISTS idx_observations_project_epoch ON observations(project, created_at_epoch DESC)");
|
|
3993
|
+
db2.run("CREATE INDEX IF NOT EXISTS idx_observations_project_type ON observations(project, type)");
|
|
3994
|
+
db2.run("CREATE INDEX IF NOT EXISTS idx_summaries_project_epoch ON summaries(project, created_at_epoch DESC)");
|
|
3995
|
+
db2.run("CREATE INDEX IF NOT EXISTS idx_prompts_project_epoch ON prompts(project, created_at_epoch DESC)");
|
|
3996
|
+
}
|
|
3948
3997
|
}
|
|
3949
3998
|
];
|
|
3950
3999
|
}
|
|
@@ -4510,6 +4559,14 @@ function getAnalyticsOverview(db2, project) {
|
|
|
4510
4559
|
const knowledgeSql = project ? `SELECT COUNT(*) as count FROM observations WHERE project = ? AND type IN ('constraint', 'decision', 'heuristic', 'rejected')` : `SELECT COUNT(*) as count FROM observations WHERE type IN ('constraint', 'decision', 'heuristic', 'rejected')`;
|
|
4511
4560
|
const knowledgeStmt = db2.query(knowledgeSql);
|
|
4512
4561
|
const knowledgeCount = project ? knowledgeStmt.get(project)?.count || 0 : knowledgeStmt.get()?.count || 0;
|
|
4562
|
+
const discoverySql = project ? "SELECT COALESCE(SUM(discovery_tokens), 0) as total FROM observations WHERE project = ?" : "SELECT COALESCE(SUM(discovery_tokens), 0) as total FROM observations";
|
|
4563
|
+
const discoveryStmt = db2.query(discoverySql);
|
|
4564
|
+
const discoveryTokens = project ? discoveryStmt.get(project)?.total || 0 : discoveryStmt.get()?.total || 0;
|
|
4565
|
+
const readSql = project ? `SELECT COALESCE(SUM(CAST((LENGTH(COALESCE(title, '')) + LENGTH(COALESCE(narrative, ''))) / 4 AS INTEGER)), 0) as total FROM observations WHERE project = ?` : `SELECT COALESCE(SUM(CAST((LENGTH(COALESCE(title, '')) + LENGTH(COALESCE(narrative, ''))) / 4 AS INTEGER)), 0) as total FROM observations`;
|
|
4566
|
+
const readStmt = db2.query(readSql);
|
|
4567
|
+
const readTokens = project ? readStmt.get(project)?.total || 0 : readStmt.get()?.total || 0;
|
|
4568
|
+
const savings = Math.max(0, discoveryTokens - readTokens);
|
|
4569
|
+
const reductionPct = discoveryTokens > 0 ? Math.round((1 - readTokens / discoveryTokens) * 100) : 0;
|
|
4513
4570
|
return {
|
|
4514
4571
|
observations,
|
|
4515
4572
|
summaries,
|
|
@@ -4518,7 +4575,8 @@ function getAnalyticsOverview(db2, project) {
|
|
|
4518
4575
|
observationsToday,
|
|
4519
4576
|
observationsThisWeek,
|
|
4520
4577
|
staleCount,
|
|
4521
|
-
knowledgeCount
|
|
4578
|
+
knowledgeCount,
|
|
4579
|
+
tokenEconomics: { discoveryTokens, readTokens, savings, reductionPct }
|
|
4522
4580
|
};
|
|
4523
4581
|
}
|
|
4524
4582
|
|
|
@@ -4951,6 +5009,7 @@ app.post("/api/observations", (req, res) => {
|
|
|
4951
5009
|
0
|
|
4952
5010
|
);
|
|
4953
5011
|
broadcast("observation-created", { id, project, title });
|
|
5012
|
+
projectsCache.ts = 0;
|
|
4954
5013
|
generateEmbeddingForObservation(id, title, content, concepts).catch(() => {
|
|
4955
5014
|
});
|
|
4956
5015
|
res.json({ id, success: true });
|
|
@@ -5301,8 +5360,12 @@ app.get("/api/project-aliases", (_req, res) => {
|
|
|
5301
5360
|
app.put("/api/project-aliases/:project", (req, res) => {
|
|
5302
5361
|
const { project } = req.params;
|
|
5303
5362
|
const { displayName } = req.body;
|
|
5304
|
-
if (!
|
|
5305
|
-
res.status(400).json({ error:
|
|
5363
|
+
if (!isValidProject(project)) {
|
|
5364
|
+
res.status(400).json({ error: "Invalid project name" });
|
|
5365
|
+
return;
|
|
5366
|
+
}
|
|
5367
|
+
if (!displayName || typeof displayName !== "string" || displayName.trim().length === 0 || displayName.length > 100) {
|
|
5368
|
+
res.status(400).json({ error: 'Field "displayName" is required (string, max 100 chars)' });
|
|
5306
5369
|
return;
|
|
5307
5370
|
}
|
|
5308
5371
|
try {
|
|
@@ -5319,8 +5382,15 @@ app.put("/api/project-aliases/:project", (req, res) => {
|
|
|
5319
5382
|
res.status(500).json({ error: "Failed to update project alias" });
|
|
5320
5383
|
}
|
|
5321
5384
|
});
|
|
5385
|
+
var projectsCache = { data: [], ts: 0 };
|
|
5386
|
+
var PROJECTS_CACHE_TTL = 6e4;
|
|
5322
5387
|
app.get("/api/projects", (_req, res) => {
|
|
5323
5388
|
try {
|
|
5389
|
+
const now = Date.now();
|
|
5390
|
+
if (now - projectsCache.ts < PROJECTS_CACHE_TTL && projectsCache.data.length > 0) {
|
|
5391
|
+
res.json(projectsCache.data);
|
|
5392
|
+
return;
|
|
5393
|
+
}
|
|
5324
5394
|
const stmt = db.db.query(
|
|
5325
5395
|
`SELECT DISTINCT project FROM (
|
|
5326
5396
|
SELECT project FROM observations
|
|
@@ -5331,7 +5401,8 @@ app.get("/api/projects", (_req, res) => {
|
|
|
5331
5401
|
) ORDER BY project ASC`
|
|
5332
5402
|
);
|
|
5333
5403
|
const rows = stmt.all();
|
|
5334
|
-
|
|
5404
|
+
projectsCache = { data: rows.map((r) => r.project), ts: now };
|
|
5405
|
+
res.json(projectsCache.data);
|
|
5335
5406
|
} catch (error) {
|
|
5336
5407
|
logger.error("WORKER", "Lista progetti fallita", {}, error);
|
|
5337
5408
|
res.status(500).json({ error: "Failed to list projects" });
|
|
@@ -5477,6 +5548,154 @@ app.get("/api/report", (req, res) => {
|
|
|
5477
5548
|
res.status(500).json({ error: "Report generation failed" });
|
|
5478
5549
|
}
|
|
5479
5550
|
});
|
|
5551
|
+
app.post("/api/memory/save", (req, res) => {
|
|
5552
|
+
const { project, title, content, type, concepts } = req.body;
|
|
5553
|
+
if (!isValidProject(project)) {
|
|
5554
|
+
res.status(400).json({ error: 'Invalid or missing "project"' });
|
|
5555
|
+
return;
|
|
5556
|
+
}
|
|
5557
|
+
if (!isValidString(title, 500)) {
|
|
5558
|
+
res.status(400).json({ error: 'Invalid or missing "title" (max 500 chars)' });
|
|
5559
|
+
return;
|
|
5560
|
+
}
|
|
5561
|
+
if (!isValidString(content, 1e5)) {
|
|
5562
|
+
res.status(400).json({ error: 'Invalid or missing "content" (max 100KB)' });
|
|
5563
|
+
return;
|
|
5564
|
+
}
|
|
5565
|
+
const obsType = type || "research";
|
|
5566
|
+
const conceptStr = Array.isArray(concepts) ? concepts.join(", ") : concepts || null;
|
|
5567
|
+
try {
|
|
5568
|
+
const id = createObservation(
|
|
5569
|
+
db.db,
|
|
5570
|
+
"memory-save-" + Date.now(),
|
|
5571
|
+
project,
|
|
5572
|
+
obsType,
|
|
5573
|
+
title,
|
|
5574
|
+
null,
|
|
5575
|
+
// subtitle
|
|
5576
|
+
content,
|
|
5577
|
+
// text
|
|
5578
|
+
content,
|
|
5579
|
+
// narrative
|
|
5580
|
+
null,
|
|
5581
|
+
// facts
|
|
5582
|
+
conceptStr,
|
|
5583
|
+
null,
|
|
5584
|
+
// filesRead
|
|
5585
|
+
null,
|
|
5586
|
+
// filesModified
|
|
5587
|
+
0
|
|
5588
|
+
// promptNumber
|
|
5589
|
+
);
|
|
5590
|
+
broadcast("observation-created", { id, project, title });
|
|
5591
|
+
projectsCache.ts = 0;
|
|
5592
|
+
generateEmbeddingForObservation(id, title, content, Array.isArray(concepts) ? concepts : void 0).catch(() => {
|
|
5593
|
+
});
|
|
5594
|
+
res.json({ id, success: true });
|
|
5595
|
+
} catch (error) {
|
|
5596
|
+
logger.error("WORKER", "Memory save fallito", {}, error);
|
|
5597
|
+
res.status(500).json({ error: "Failed to save memory" });
|
|
5598
|
+
}
|
|
5599
|
+
});
|
|
5600
|
+
app.post("/api/retention/cleanup", (req, res) => {
|
|
5601
|
+
const { maxAgeDays, dryRun } = req.body || {};
|
|
5602
|
+
const days = parseIntSafe(String(maxAgeDays), 90, 7, 730);
|
|
5603
|
+
const threshold = Date.now() - days * 864e5;
|
|
5604
|
+
try {
|
|
5605
|
+
if (dryRun) {
|
|
5606
|
+
const obsCount = db.db.query("SELECT COUNT(*) as c FROM observations WHERE created_at_epoch < ?").get(threshold).c;
|
|
5607
|
+
const sumCount = db.db.query("SELECT COUNT(*) as c FROM summaries WHERE created_at_epoch < ?").get(threshold).c;
|
|
5608
|
+
const promptCount = db.db.query("SELECT COUNT(*) as c FROM prompts WHERE created_at_epoch < ?").get(threshold).c;
|
|
5609
|
+
res.json({ dryRun: true, maxAgeDays: days, wouldDelete: { observations: obsCount, summaries: sumCount, prompts: promptCount } });
|
|
5610
|
+
return;
|
|
5611
|
+
}
|
|
5612
|
+
const cleanup = db.db.transaction(() => {
|
|
5613
|
+
db.db.run("DELETE FROM observation_embeddings WHERE observation_id IN (SELECT id FROM observations WHERE created_at_epoch < ?)", [threshold]);
|
|
5614
|
+
const obsResult = db.db.run("DELETE FROM observations WHERE created_at_epoch < ?", [threshold]);
|
|
5615
|
+
const sumResult = db.db.run("DELETE FROM summaries WHERE created_at_epoch < ?", [threshold]);
|
|
5616
|
+
const promptResult = db.db.run("DELETE FROM prompts WHERE created_at_epoch < ?", [threshold]);
|
|
5617
|
+
return {
|
|
5618
|
+
observations: obsResult.changes,
|
|
5619
|
+
summaries: sumResult.changes,
|
|
5620
|
+
prompts: promptResult.changes
|
|
5621
|
+
};
|
|
5622
|
+
});
|
|
5623
|
+
const deleted = cleanup();
|
|
5624
|
+
projectsCache.ts = 0;
|
|
5625
|
+
logger.info("WORKER", `Retention cleanup: eliminati ${deleted.observations} obs, ${deleted.summaries} sum, ${deleted.prompts} prompts (> ${days}gg)`);
|
|
5626
|
+
res.json({ success: true, maxAgeDays: days, deleted });
|
|
5627
|
+
} catch (error) {
|
|
5628
|
+
logger.error("WORKER", "Retention cleanup fallito", { maxAgeDays: days }, error);
|
|
5629
|
+
res.status(500).json({ error: "Retention cleanup failed" });
|
|
5630
|
+
}
|
|
5631
|
+
});
|
|
5632
|
+
app.get("/api/export", (req, res) => {
|
|
5633
|
+
const { project, format: fmt, type, days } = req.query;
|
|
5634
|
+
if (project && !isValidProject(project)) {
|
|
5635
|
+
res.status(400).json({ error: "Invalid project name" });
|
|
5636
|
+
return;
|
|
5637
|
+
}
|
|
5638
|
+
const daysBack = parseIntSafe(days, 30, 1, 365);
|
|
5639
|
+
const threshold = Date.now() - daysBack * 864e5;
|
|
5640
|
+
try {
|
|
5641
|
+
let sql = "SELECT * FROM observations WHERE created_at_epoch > ?";
|
|
5642
|
+
const params = [threshold];
|
|
5643
|
+
if (project) {
|
|
5644
|
+
sql += " AND project = ?";
|
|
5645
|
+
params.push(project);
|
|
5646
|
+
}
|
|
5647
|
+
if (type) {
|
|
5648
|
+
sql += " AND type = ?";
|
|
5649
|
+
params.push(type);
|
|
5650
|
+
}
|
|
5651
|
+
sql += " ORDER BY created_at_epoch DESC LIMIT 1000";
|
|
5652
|
+
const observations = db.db.query(sql).all(...params);
|
|
5653
|
+
let sumSql = "SELECT * FROM summaries WHERE created_at_epoch > ?";
|
|
5654
|
+
const sumParams = [threshold];
|
|
5655
|
+
if (project) {
|
|
5656
|
+
sumSql += " AND project = ?";
|
|
5657
|
+
sumParams.push(project);
|
|
5658
|
+
}
|
|
5659
|
+
sumSql += " ORDER BY created_at_epoch DESC LIMIT 100";
|
|
5660
|
+
const summaries = db.db.query(sumSql).all(...sumParams);
|
|
5661
|
+
if (fmt === "markdown" || fmt === "md") {
|
|
5662
|
+
const lines = [
|
|
5663
|
+
`# Kiro Memory Export`,
|
|
5664
|
+
`> Project: ${project || "All"} | Period: ${daysBack} days | Generated: ${(/* @__PURE__ */ new Date()).toISOString()}`,
|
|
5665
|
+
"",
|
|
5666
|
+
`## Observations (${observations.length})`,
|
|
5667
|
+
""
|
|
5668
|
+
];
|
|
5669
|
+
for (const obs of observations) {
|
|
5670
|
+
const date = new Date(obs.created_at_epoch).toISOString().split("T")[0];
|
|
5671
|
+
lines.push(`### [${obs.type}] ${obs.title}`);
|
|
5672
|
+
lines.push(`- **Date**: ${date} | **Project**: ${obs.project} | **ID**: #${obs.id}`);
|
|
5673
|
+
if (obs.narrative) lines.push(`- ${obs.narrative}`);
|
|
5674
|
+
if (obs.concepts) lines.push(`- **Concepts**: ${obs.concepts}`);
|
|
5675
|
+
lines.push("");
|
|
5676
|
+
}
|
|
5677
|
+
lines.push(`## Summaries (${summaries.length})`, "");
|
|
5678
|
+
for (const sum of summaries) {
|
|
5679
|
+
const date = new Date(sum.created_at_epoch).toISOString().split("T")[0];
|
|
5680
|
+
lines.push(`### Session ${sum.session_id} (${date})`);
|
|
5681
|
+
if (sum.request) lines.push(`- **Request**: ${sum.request}`);
|
|
5682
|
+
if (sum.completed) lines.push(`- **Completed**: ${sum.completed}`);
|
|
5683
|
+
if (sum.next_steps) lines.push(`- **Next steps**: ${sum.next_steps}`);
|
|
5684
|
+
lines.push("");
|
|
5685
|
+
}
|
|
5686
|
+
res.type("text/markdown").send(lines.join("\n"));
|
|
5687
|
+
} else {
|
|
5688
|
+
res.json({
|
|
5689
|
+
meta: { project: project || "all", daysBack, exportedAt: (/* @__PURE__ */ new Date()).toISOString() },
|
|
5690
|
+
observations,
|
|
5691
|
+
summaries
|
|
5692
|
+
});
|
|
5693
|
+
}
|
|
5694
|
+
} catch (error) {
|
|
5695
|
+
logger.error("WORKER", "Export fallito", { project, fmt }, error);
|
|
5696
|
+
res.status(500).json({ error: "Export failed" });
|
|
5697
|
+
}
|
|
5698
|
+
});
|
|
5480
5699
|
app.use(express.static(__worker_dirname, {
|
|
5481
5700
|
index: false,
|
|
5482
5701
|
maxAge: "1h"
|