kiro-memory 1.7.0 → 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 +266 -47
|
@@ -24,19 +24,28 @@ __export(Observations_exports, {
|
|
|
24
24
|
deleteObservation: () => deleteObservation,
|
|
25
25
|
getObservationsByProject: () => getObservationsByProject,
|
|
26
26
|
getObservationsBySession: () => getObservationsBySession,
|
|
27
|
+
isDuplicateObservation: () => isDuplicateObservation,
|
|
27
28
|
searchObservations: () => searchObservations,
|
|
28
29
|
updateLastAccessed: () => updateLastAccessed
|
|
29
30
|
});
|
|
30
31
|
function escapeLikePattern(input) {
|
|
31
32
|
return input.replace(/[%_\\]/g, "\\$&");
|
|
32
33
|
}
|
|
33
|
-
function
|
|
34
|
+
function isDuplicateObservation(db, contentHash, windowMs = 3e4) {
|
|
35
|
+
if (!contentHash) return false;
|
|
36
|
+
const threshold = Date.now() - windowMs;
|
|
37
|
+
const result = db.query(
|
|
38
|
+
"SELECT id FROM observations WHERE content_hash = ? AND created_at_epoch > ? LIMIT 1"
|
|
39
|
+
).get(contentHash, threshold);
|
|
40
|
+
return !!result;
|
|
41
|
+
}
|
|
42
|
+
function createObservation(db, memorySessionId, project, type, title, subtitle, text, narrative, facts, concepts, filesRead, filesModified, promptNumber, contentHash = null, discoveryTokens = 0) {
|
|
34
43
|
const now = /* @__PURE__ */ new Date();
|
|
35
44
|
const result = db.run(
|
|
36
|
-
`INSERT INTO observations
|
|
37
|
-
(memory_session_id, project, type, title, subtitle, text, narrative, facts, concepts, files_read, files_modified, prompt_number, created_at, created_at_epoch)
|
|
38
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
39
|
-
[memorySessionId, project, type, title, subtitle, text, narrative, facts, concepts, filesRead, filesModified, promptNumber, now.toISOString(), now.getTime()]
|
|
45
|
+
`INSERT INTO observations
|
|
46
|
+
(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)
|
|
47
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
48
|
+
[memorySessionId, project, type, title, subtitle, text, narrative, facts, concepts, filesRead, filesModified, promptNumber, now.toISOString(), now.getTime(), contentHash, discoveryTokens]
|
|
40
49
|
);
|
|
41
50
|
return Number(result.lastInsertRowid);
|
|
42
51
|
}
|
|
@@ -92,39 +101,42 @@ function consolidateObservations(db, project, options = {}) {
|
|
|
92
101
|
if (groups.length === 0) return { merged: 0, removed: 0 };
|
|
93
102
|
let totalMerged = 0;
|
|
94
103
|
let totalRemoved = 0;
|
|
95
|
-
|
|
96
|
-
const
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
const keeper = observations[0];
|
|
108
|
-
const others = observations.slice(1);
|
|
109
|
-
const uniqueTexts = /* @__PURE__ */ new Set();
|
|
110
|
-
if (keeper.text) uniqueTexts.add(keeper.text);
|
|
111
|
-
for (const obs of others) {
|
|
112
|
-
if (obs.text && !uniqueTexts.has(obs.text)) {
|
|
113
|
-
uniqueTexts.add(obs.text);
|
|
104
|
+
const runConsolidation = db.transaction(() => {
|
|
105
|
+
for (const group of groups) {
|
|
106
|
+
const obsIds = group.ids.split(",").map(Number);
|
|
107
|
+
const placeholders = obsIds.map(() => "?").join(",");
|
|
108
|
+
const observations = db.query(
|
|
109
|
+
`SELECT * FROM observations WHERE id IN (${placeholders}) ORDER BY created_at_epoch DESC`
|
|
110
|
+
).all(...obsIds);
|
|
111
|
+
if (observations.length < minGroupSize) continue;
|
|
112
|
+
if (options.dryRun) {
|
|
113
|
+
totalMerged += 1;
|
|
114
|
+
totalRemoved += observations.length - 1;
|
|
115
|
+
continue;
|
|
114
116
|
}
|
|
117
|
+
const keeper = observations[0];
|
|
118
|
+
const others = observations.slice(1);
|
|
119
|
+
const uniqueTexts = /* @__PURE__ */ new Set();
|
|
120
|
+
if (keeper.text) uniqueTexts.add(keeper.text);
|
|
121
|
+
for (const obs of others) {
|
|
122
|
+
if (obs.text && !uniqueTexts.has(obs.text)) {
|
|
123
|
+
uniqueTexts.add(obs.text);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
const consolidatedText = Array.from(uniqueTexts).join("\n---\n").substring(0, 1e5);
|
|
127
|
+
db.run(
|
|
128
|
+
"UPDATE observations SET text = ?, title = ? WHERE id = ?",
|
|
129
|
+
[consolidatedText, `[consolidato x${observations.length}] ${keeper.title}`, keeper.id]
|
|
130
|
+
);
|
|
131
|
+
const removeIds = others.map((o) => o.id);
|
|
132
|
+
const removePlaceholders = removeIds.map(() => "?").join(",");
|
|
133
|
+
db.run(`DELETE FROM observations WHERE id IN (${removePlaceholders})`, removeIds);
|
|
134
|
+
db.run(`DELETE FROM observation_embeddings WHERE observation_id IN (${removePlaceholders})`, removeIds);
|
|
135
|
+
totalMerged += 1;
|
|
136
|
+
totalRemoved += removeIds.length;
|
|
115
137
|
}
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
"UPDATE observations SET text = ?, title = ? WHERE id = ?",
|
|
119
|
-
[consolidatedText, `[consolidato x${observations.length}] ${keeper.title}`, keeper.id]
|
|
120
|
-
);
|
|
121
|
-
const removeIds = others.map((o) => o.id);
|
|
122
|
-
const removePlaceholders = removeIds.map(() => "?").join(",");
|
|
123
|
-
db.run(`DELETE FROM observations WHERE id IN (${removePlaceholders})`, removeIds);
|
|
124
|
-
db.run(`DELETE FROM observation_embeddings WHERE observation_id IN (${removePlaceholders})`, removeIds);
|
|
125
|
-
totalMerged += 1;
|
|
126
|
-
totalRemoved += removeIds.length;
|
|
127
|
-
}
|
|
138
|
+
});
|
|
139
|
+
runConsolidation();
|
|
128
140
|
return { merged: totalMerged, removed: totalRemoved };
|
|
129
141
|
}
|
|
130
142
|
var init_Observations = __esm({
|
|
@@ -182,7 +194,7 @@ function searchObservationsFTS(db, query, filters = {}) {
|
|
|
182
194
|
sql += " AND o.created_at_epoch <= ?";
|
|
183
195
|
params.push(filters.dateEnd);
|
|
184
196
|
}
|
|
185
|
-
sql +=
|
|
197
|
+
sql += ` ORDER BY bm25(observations_fts, ${BM25_WEIGHTS}) LIMIT ?`;
|
|
186
198
|
params.push(limit);
|
|
187
199
|
const stmt = db.query(sql);
|
|
188
200
|
return stmt.all(...params);
|
|
@@ -196,7 +208,7 @@ function searchObservationsFTSWithRank(db, query, filters = {}) {
|
|
|
196
208
|
const safeQuery = sanitizeFTS5Query(query);
|
|
197
209
|
if (!safeQuery) return [];
|
|
198
210
|
let sql = `
|
|
199
|
-
SELECT o.*,
|
|
211
|
+
SELECT o.*, bm25(observations_fts, ${BM25_WEIGHTS}) as fts5_rank FROM observations o
|
|
200
212
|
JOIN observations_fts fts ON o.id = fts.rowid
|
|
201
213
|
WHERE observations_fts MATCH ?
|
|
202
214
|
`;
|
|
@@ -217,7 +229,7 @@ function searchObservationsFTSWithRank(db, query, filters = {}) {
|
|
|
217
229
|
sql += " AND o.created_at_epoch <= ?";
|
|
218
230
|
params.push(filters.dateEnd);
|
|
219
231
|
}
|
|
220
|
-
sql +=
|
|
232
|
+
sql += ` ORDER BY bm25(observations_fts, ${BM25_WEIGHTS}) LIMIT ?`;
|
|
221
233
|
params.push(limit);
|
|
222
234
|
const stmt = db.query(sql);
|
|
223
235
|
return stmt.all(...params);
|
|
@@ -321,11 +333,23 @@ function getProjectStats(db, project) {
|
|
|
321
333
|
const sumStmt = db.query("SELECT COUNT(*) as count FROM summaries WHERE project = ?");
|
|
322
334
|
const sesStmt = db.query("SELECT COUNT(*) as count FROM sessions WHERE project = ?");
|
|
323
335
|
const prmStmt = db.query("SELECT COUNT(*) as count FROM prompts WHERE project = ?");
|
|
336
|
+
const discoveryStmt = db.query(
|
|
337
|
+
"SELECT COALESCE(SUM(discovery_tokens), 0) as total FROM observations WHERE project = ?"
|
|
338
|
+
);
|
|
339
|
+
const discoveryTokens = discoveryStmt.get(project)?.total || 0;
|
|
340
|
+
const readStmt = db.query(
|
|
341
|
+
`SELECT COALESCE(SUM(
|
|
342
|
+
CAST((LENGTH(COALESCE(title, '')) + LENGTH(COALESCE(narrative, ''))) / 4 AS INTEGER)
|
|
343
|
+
), 0) as total FROM observations WHERE project = ?`
|
|
344
|
+
);
|
|
345
|
+
const readTokens = readStmt.get(project)?.total || 0;
|
|
346
|
+
const savings = Math.max(0, discoveryTokens - readTokens);
|
|
324
347
|
return {
|
|
325
348
|
observations: obsStmt.get(project)?.count || 0,
|
|
326
349
|
summaries: sumStmt.get(project)?.count || 0,
|
|
327
350
|
sessions: sesStmt.get(project)?.count || 0,
|
|
328
|
-
prompts: prmStmt.get(project)?.count || 0
|
|
351
|
+
prompts: prmStmt.get(project)?.count || 0,
|
|
352
|
+
tokenEconomics: { discoveryTokens, readTokens, savings }
|
|
329
353
|
};
|
|
330
354
|
}
|
|
331
355
|
function getStaleObservations(db, project) {
|
|
@@ -367,9 +391,11 @@ function markObservationsStale(db, ids, stale) {
|
|
|
367
391
|
[stale ? 1 : 0, ...validIds]
|
|
368
392
|
);
|
|
369
393
|
}
|
|
394
|
+
var BM25_WEIGHTS;
|
|
370
395
|
var init_Search = __esm({
|
|
371
396
|
"src/services/sqlite/Search.ts"() {
|
|
372
397
|
"use strict";
|
|
398
|
+
BM25_WEIGHTS = "10.0, 1.0, 5.0, 3.0";
|
|
373
399
|
}
|
|
374
400
|
});
|
|
375
401
|
|
|
@@ -1136,6 +1162,29 @@ var MigrationRunner = class {
|
|
|
1136
1162
|
db.run("CREATE INDEX IF NOT EXISTS idx_checkpoints_project ON checkpoints(project)");
|
|
1137
1163
|
db.run("CREATE INDEX IF NOT EXISTS idx_checkpoints_epoch ON checkpoints(created_at_epoch)");
|
|
1138
1164
|
}
|
|
1165
|
+
},
|
|
1166
|
+
{
|
|
1167
|
+
version: 7,
|
|
1168
|
+
up: (db) => {
|
|
1169
|
+
db.run("ALTER TABLE observations ADD COLUMN content_hash TEXT");
|
|
1170
|
+
db.run("CREATE INDEX IF NOT EXISTS idx_observations_hash ON observations(content_hash)");
|
|
1171
|
+
}
|
|
1172
|
+
},
|
|
1173
|
+
{
|
|
1174
|
+
version: 8,
|
|
1175
|
+
up: (db) => {
|
|
1176
|
+
db.run("ALTER TABLE observations ADD COLUMN discovery_tokens INTEGER DEFAULT 0");
|
|
1177
|
+
db.run("ALTER TABLE summaries ADD COLUMN discovery_tokens INTEGER DEFAULT 0");
|
|
1178
|
+
}
|
|
1179
|
+
},
|
|
1180
|
+
{
|
|
1181
|
+
version: 9,
|
|
1182
|
+
up: (db) => {
|
|
1183
|
+
db.run("CREATE INDEX IF NOT EXISTS idx_observations_project_epoch ON observations(project, created_at_epoch DESC)");
|
|
1184
|
+
db.run("CREATE INDEX IF NOT EXISTS idx_observations_project_type ON observations(project, type)");
|
|
1185
|
+
db.run("CREATE INDEX IF NOT EXISTS idx_summaries_project_epoch ON summaries(project, created_at_epoch DESC)");
|
|
1186
|
+
db.run("CREATE INDEX IF NOT EXISTS idx_prompts_project_epoch ON prompts(project, created_at_epoch DESC)");
|
|
1187
|
+
}
|
|
1139
1188
|
}
|
|
1140
1189
|
];
|
|
1141
1190
|
}
|
|
@@ -1380,6 +1429,7 @@ init_Search();
|
|
|
1380
1429
|
|
|
1381
1430
|
// src/sdk/index.ts
|
|
1382
1431
|
init_Observations();
|
|
1432
|
+
import { createHash } from "crypto";
|
|
1383
1433
|
init_Search();
|
|
1384
1434
|
|
|
1385
1435
|
// src/services/search/EmbeddingService.ts
|
|
@@ -1892,31 +1942,71 @@ var KiroMemorySDK = class {
|
|
|
1892
1942
|
logger.debug("SDK", `Embedding generation fallita per obs ${observationId}: ${error}`);
|
|
1893
1943
|
}
|
|
1894
1944
|
}
|
|
1945
|
+
/**
|
|
1946
|
+
* Genera content hash SHA256 per deduplicazione basata su contenuto.
|
|
1947
|
+
* Usa (project + type + title + narrative) come tupla di identità semantica.
|
|
1948
|
+
* NON include sessionId perché è unico ad ogni invocazione.
|
|
1949
|
+
*/
|
|
1950
|
+
generateContentHash(type, title, narrative) {
|
|
1951
|
+
const payload = `${this.project}|${type}|${title}|${narrative || ""}`;
|
|
1952
|
+
return createHash("sha256").update(payload).digest("hex");
|
|
1953
|
+
}
|
|
1954
|
+
/**
|
|
1955
|
+
* Finestre di deduplicazione per tipo (ms).
|
|
1956
|
+
* Tipi con molte ripetizioni hanno finestre più ampie.
|
|
1957
|
+
*/
|
|
1958
|
+
getDeduplicationWindow(type) {
|
|
1959
|
+
switch (type) {
|
|
1960
|
+
case "file-read":
|
|
1961
|
+
return 6e4;
|
|
1962
|
+
// 60s — letture frequenti sugli stessi file
|
|
1963
|
+
case "file-write":
|
|
1964
|
+
return 1e4;
|
|
1965
|
+
// 10s — scritture rapide consecutive
|
|
1966
|
+
case "command":
|
|
1967
|
+
return 3e4;
|
|
1968
|
+
// 30s — standard
|
|
1969
|
+
case "research":
|
|
1970
|
+
return 12e4;
|
|
1971
|
+
// 120s — web search e fetch ripetuti
|
|
1972
|
+
case "delegation":
|
|
1973
|
+
return 6e4;
|
|
1974
|
+
// 60s — delegazioni rapide
|
|
1975
|
+
default:
|
|
1976
|
+
return 3e4;
|
|
1977
|
+
}
|
|
1978
|
+
}
|
|
1895
1979
|
/**
|
|
1896
1980
|
* Store a new observation
|
|
1897
1981
|
*/
|
|
1898
1982
|
async storeObservation(data) {
|
|
1899
1983
|
this.validateObservationInput(data);
|
|
1984
|
+
const sessionId = "sdk-" + Date.now();
|
|
1985
|
+
const contentHash = this.generateContentHash(data.type, data.title, data.narrative);
|
|
1986
|
+
const dedupWindow = this.getDeduplicationWindow(data.type);
|
|
1987
|
+
if (isDuplicateObservation(this.db.db, contentHash, dedupWindow)) {
|
|
1988
|
+
logger.debug("SDK", `Osservazione duplicata scartata (${data.type}, ${dedupWindow}ms): ${data.title}`);
|
|
1989
|
+
return -1;
|
|
1990
|
+
}
|
|
1991
|
+
const filesRead = data.filesRead || (data.type === "file-read" ? data.files : void 0);
|
|
1992
|
+
const filesModified = data.filesModified || (data.type === "file-write" ? data.files : void 0);
|
|
1993
|
+
const discoveryTokens = Math.ceil(data.content.length / 4);
|
|
1900
1994
|
const observationId = createObservation(
|
|
1901
1995
|
this.db.db,
|
|
1902
|
-
|
|
1996
|
+
sessionId,
|
|
1903
1997
|
this.project,
|
|
1904
1998
|
data.type,
|
|
1905
1999
|
data.title,
|
|
1906
|
-
null,
|
|
1907
|
-
// subtitle
|
|
2000
|
+
data.subtitle || null,
|
|
1908
2001
|
data.content,
|
|
1909
|
-
null,
|
|
1910
|
-
|
|
1911
|
-
null,
|
|
1912
|
-
// facts
|
|
2002
|
+
data.narrative || null,
|
|
2003
|
+
data.facts || null,
|
|
1913
2004
|
data.concepts?.join(", ") || null,
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
// prompt_number
|
|
2005
|
+
filesRead?.join(", ") || null,
|
|
2006
|
+
filesModified?.join(", ") || null,
|
|
2007
|
+
0,
|
|
2008
|
+
contentHash,
|
|
2009
|
+
discoveryTokens
|
|
1920
2010
|
);
|
|
1921
2011
|
this.generateEmbeddingAsync(observationId, data.title, data.content, data.concepts).catch(() => {
|
|
1922
2012
|
});
|
|
@@ -1959,9 +2049,16 @@ var KiroMemorySDK = class {
|
|
|
1959
2049
|
};
|
|
1960
2050
|
}
|
|
1961
2051
|
})();
|
|
2052
|
+
const sessionId = "sdk-" + Date.now();
|
|
2053
|
+
const contentHash = this.generateContentHash(data.type, data.title);
|
|
2054
|
+
if (isDuplicateObservation(this.db.db, contentHash)) {
|
|
2055
|
+
logger.debug("SDK", `Knowledge duplicata scartata: ${data.title}`);
|
|
2056
|
+
return -1;
|
|
2057
|
+
}
|
|
2058
|
+
const discoveryTokens = Math.ceil(data.content.length / 4);
|
|
1962
2059
|
const observationId = createObservation(
|
|
1963
2060
|
this.db.db,
|
|
1964
|
-
|
|
2061
|
+
sessionId,
|
|
1965
2062
|
data.project || this.project,
|
|
1966
2063
|
data.knowledgeType,
|
|
1967
2064
|
// type = knowledgeType
|
|
@@ -1975,9 +2072,12 @@ var KiroMemorySDK = class {
|
|
|
1975
2072
|
// facts = metadati JSON
|
|
1976
2073
|
data.concepts?.join(", ") || null,
|
|
1977
2074
|
data.files?.join(", ") || null,
|
|
1978
|
-
|
|
1979
|
-
|
|
2075
|
+
null,
|
|
2076
|
+
// filesModified: knowledge non modifica file
|
|
2077
|
+
0,
|
|
1980
2078
|
// prompt_number
|
|
2079
|
+
contentHash,
|
|
2080
|
+
discoveryTokens
|
|
1981
2081
|
);
|
|
1982
2082
|
this.generateEmbeddingAsync(observationId, data.title, data.content, data.concepts).catch(() => {
|
|
1983
2083
|
});
|
|
@@ -1993,11 +2093,11 @@ var KiroMemorySDK = class {
|
|
|
1993
2093
|
"sdk-" + Date.now(),
|
|
1994
2094
|
this.project,
|
|
1995
2095
|
data.request || null,
|
|
1996
|
-
null,
|
|
2096
|
+
data.investigated || null,
|
|
1997
2097
|
data.learned || null,
|
|
1998
2098
|
data.completed || null,
|
|
1999
2099
|
data.nextSteps || null,
|
|
2000
|
-
null
|
|
2100
|
+
data.notes || null
|
|
2001
2101
|
);
|
|
2002
2102
|
}
|
|
2003
2103
|
/**
|
|
@@ -2181,7 +2281,14 @@ var KiroMemorySDK = class {
|
|
|
2181
2281
|
}));
|
|
2182
2282
|
} else {
|
|
2183
2283
|
const observations = getObservationsByProject(this.db.db, this.project, 30);
|
|
2184
|
-
|
|
2284
|
+
const knowledgeTypes = new Set(KNOWLEDGE_TYPES);
|
|
2285
|
+
const knowledgeObs = [];
|
|
2286
|
+
const normalObs = [];
|
|
2287
|
+
for (const obs of observations) {
|
|
2288
|
+
if (knowledgeTypes.has(obs.type)) knowledgeObs.push(obs);
|
|
2289
|
+
else normalObs.push(obs);
|
|
2290
|
+
}
|
|
2291
|
+
const scoreObs = (obs) => {
|
|
2185
2292
|
const signals = {
|
|
2186
2293
|
semantic: 0,
|
|
2187
2294
|
fts5: 0,
|
|
@@ -2189,7 +2296,6 @@ var KiroMemorySDK = class {
|
|
|
2189
2296
|
projectMatch: projectMatchScore(obs.project, this.project)
|
|
2190
2297
|
};
|
|
2191
2298
|
const baseScore = computeCompositeScore(signals, CONTEXT_WEIGHTS);
|
|
2192
|
-
const boostedScore = Math.min(1, baseScore * knowledgeTypeBoost(obs.type));
|
|
2193
2299
|
return {
|
|
2194
2300
|
id: obs.id,
|
|
2195
2301
|
title: obs.title,
|
|
@@ -2198,17 +2304,23 @@ var KiroMemorySDK = class {
|
|
|
2198
2304
|
project: obs.project,
|
|
2199
2305
|
created_at: obs.created_at,
|
|
2200
2306
|
created_at_epoch: obs.created_at_epoch,
|
|
2201
|
-
score:
|
|
2307
|
+
score: Math.min(1, baseScore * knowledgeTypeBoost(obs.type)),
|
|
2202
2308
|
signals
|
|
2203
2309
|
};
|
|
2204
|
-
}
|
|
2205
|
-
|
|
2310
|
+
};
|
|
2311
|
+
const scoredKnowledge = knowledgeObs.map(scoreObs).sort((a, b) => b.score - a.score);
|
|
2312
|
+
const scoredNormal = normalObs.map(scoreObs).sort((a, b) => b.score - a.score);
|
|
2313
|
+
items = [...scoredKnowledge, ...scoredNormal];
|
|
2206
2314
|
}
|
|
2207
2315
|
let tokensUsed = 0;
|
|
2316
|
+
const budgetItems = [];
|
|
2208
2317
|
for (const item of items) {
|
|
2209
|
-
|
|
2210
|
-
if (tokensUsed > tokenBudget) break;
|
|
2318
|
+
const itemTokens = Math.ceil((item.title.length + item.content.length) / 4);
|
|
2319
|
+
if (tokensUsed + itemTokens > tokenBudget) break;
|
|
2320
|
+
tokensUsed += itemTokens;
|
|
2321
|
+
budgetItems.push(item);
|
|
2211
2322
|
}
|
|
2323
|
+
items = budgetItems;
|
|
2212
2324
|
return {
|
|
2213
2325
|
project: this.project,
|
|
2214
2326
|
items,
|