@alaarab/cortex 1.13.4 → 1.13.6
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.
|
@@ -279,8 +279,11 @@ export function addFindingToFile(cortexPath, project, learning, citationInput, o
|
|
|
279
279
|
}
|
|
280
280
|
const content = fs.readFileSync(learningsPath, "utf8");
|
|
281
281
|
const legacyHistory = opts?.skipLegacyDedup ? "" : readLegacyHistoryContent(resolvedDir);
|
|
282
|
-
// When superseding, strip the old finding from history so dedup doesn't block the intentionally similar replacement
|
|
283
|
-
|
|
282
|
+
// When superseding, strip the old finding from history so dedup doesn't block the intentionally similar replacement.
|
|
283
|
+
// Skip the strip if new finding is identical to the superseded one (self-supersession should still be blocked by dedup).
|
|
284
|
+
const isSelfSupersession = supersedesText &&
|
|
285
|
+
learning.trim().toLowerCase().slice(0, 60) === supersedesText.trim().toLowerCase().slice(0, 60);
|
|
286
|
+
const historyForDedup = (supersedesText && !isSelfSupersession)
|
|
284
287
|
? (legacyHistory ? `${content}\n${legacyHistory}` : content)
|
|
285
288
|
.split("\n")
|
|
286
289
|
.filter(line => !line.startsWith("- ") || !line.toLowerCase().includes(supersedesText.slice(0, 40).toLowerCase()))
|
package/mcp/dist/data-access.js
CHANGED
|
@@ -338,7 +338,7 @@ export function completeBacklogItem(cortexPath, project, match) {
|
|
|
338
338
|
return forwardErr(parsed);
|
|
339
339
|
const found = findItemByMatch(parsed.data, match);
|
|
340
340
|
if (found.error)
|
|
341
|
-
return cortexErr(found.error);
|
|
341
|
+
return cortexErr(found.error, CortexError.AMBIGUOUS_MATCH);
|
|
342
342
|
if (!found.match)
|
|
343
343
|
return cortexErr(`No backlog item matching "${match}" in project "${project}". Check the item text or use its ID (shown in the backlog view).`, CortexError.NOT_FOUND);
|
|
344
344
|
const [item] = parsed.data.items[found.match.section].splice(found.match.index, 1);
|
|
@@ -359,7 +359,7 @@ export function updateBacklogItem(cortexPath, project, match, updates) {
|
|
|
359
359
|
return forwardErr(parsed);
|
|
360
360
|
const found = findItemByMatch(parsed.data, match);
|
|
361
361
|
if (found.error)
|
|
362
|
-
return cortexErr(found.error);
|
|
362
|
+
return cortexErr(found.error, CortexError.AMBIGUOUS_MATCH);
|
|
363
363
|
if (!found.match)
|
|
364
364
|
return cortexErr(`No backlog item matching "${match}" in project "${project}". Check the item text or use its ID (shown in the backlog view).`, CortexError.NOT_FOUND);
|
|
365
365
|
const item = parsed.data.items[found.match.section][found.match.index];
|
|
@@ -405,7 +405,7 @@ export function pinBacklogItem(cortexPath, project, match) {
|
|
|
405
405
|
return forwardErr(parsed);
|
|
406
406
|
const found = findItemByMatch(parsed.data, match);
|
|
407
407
|
if (found.error)
|
|
408
|
-
return cortexErr(found.error);
|
|
408
|
+
return cortexErr(found.error, CortexError.AMBIGUOUS_MATCH);
|
|
409
409
|
if (!found.match)
|
|
410
410
|
return cortexErr(`No backlog item matching "${match}" in project "${project}". Check the item text or use its ID (shown in the backlog view).`, CortexError.NOT_FOUND);
|
|
411
411
|
const item = parsed.data.items[found.match.section][found.match.index];
|
|
@@ -427,7 +427,7 @@ export function unpinBacklogItem(cortexPath, project, match) {
|
|
|
427
427
|
return forwardErr(parsed);
|
|
428
428
|
const found = findItemByMatch(parsed.data, match);
|
|
429
429
|
if (found.error)
|
|
430
|
-
return cortexErr(found.error);
|
|
430
|
+
return cortexErr(found.error, CortexError.AMBIGUOUS_MATCH);
|
|
431
431
|
if (!found.match)
|
|
432
432
|
return cortexErr(`No backlog item matching "${match}" in project "${project}". Check the item text or use its ID (shown in the backlog view).`, CortexError.NOT_FOUND);
|
|
433
433
|
const item = parsed.data.items[found.match.section][found.match.index];
|
package/mcp/dist/mcp-finding.js
CHANGED
|
@@ -180,7 +180,8 @@ export function register(server, ctx) {
|
|
|
180
180
|
.filter((name) => name && !name.startsWith(".") && name !== "profiles")));
|
|
181
181
|
const commitMsg = message || `cortex: save ${files.length} file(s) across ${projectNames.length} project(s)`;
|
|
182
182
|
runCustomHooks(cortexPath, "pre-save");
|
|
183
|
-
|
|
183
|
+
// Restrict to known cortex file types to avoid staging .env or credential files
|
|
184
|
+
runGit(["add", "--", "*.md", "*.json", "*.yaml", "*.yml", "*.jsonl", "*.txt"]);
|
|
184
185
|
runGit(["commit", "-m", commitMsg]);
|
|
185
186
|
let hasRemote = false;
|
|
186
187
|
try {
|
|
@@ -14,6 +14,26 @@ let dfCacheGeneration = 0;
|
|
|
14
14
|
export function invalidateDfCache() {
|
|
15
15
|
dfCacheGeneration++;
|
|
16
16
|
dfCache.clear();
|
|
17
|
+
tokenCache.clear();
|
|
18
|
+
}
|
|
19
|
+
// Module-level cache for tokenized document content.
|
|
20
|
+
// Keyed by a short content hash so the same document content is only tokenized once per server lifetime.
|
|
21
|
+
// Cleared on full rebuild (same lifecycle as dfCache). Max 2000 entries to bound memory.
|
|
22
|
+
const MAX_TOKEN_CACHE = 2000;
|
|
23
|
+
const tokenCache = new Map();
|
|
24
|
+
function cachedTokenize(text) {
|
|
25
|
+
// Use first 16 chars + length as a cheap key (avoids hashing cost for most cases)
|
|
26
|
+
const key = `${text.length}:${text.slice(0, 32)}`;
|
|
27
|
+
const hit = tokenCache.get(key);
|
|
28
|
+
if (hit)
|
|
29
|
+
return hit;
|
|
30
|
+
const tokens = tokenize(text);
|
|
31
|
+
if (tokenCache.size >= MAX_TOKEN_CACHE) {
|
|
32
|
+
// Evict oldest entry
|
|
33
|
+
tokenCache.delete(tokenCache.keys().next().value ?? "");
|
|
34
|
+
}
|
|
35
|
+
tokenCache.set(key, tokens);
|
|
36
|
+
return tokens;
|
|
17
37
|
}
|
|
18
38
|
/**
|
|
19
39
|
* Tokenize text into non-stop-word tokens for TF-IDF computation, with stemming.
|
|
@@ -34,10 +54,10 @@ function tfidfCosine(docs, query) {
|
|
|
34
54
|
const queryTokens = tokenize(query);
|
|
35
55
|
if (queryTokens.length === 0)
|
|
36
56
|
return docs.map(() => 0);
|
|
37
|
-
// Collect all unique terms from query + all docs
|
|
57
|
+
// Collect all unique terms from query + all docs (use cached tokenization for repeated content)
|
|
38
58
|
const allTokens = new Set(queryTokens);
|
|
39
59
|
const docTokenLists = docs.map(d => {
|
|
40
|
-
const tokens =
|
|
60
|
+
const tokens = cachedTokenize(d);
|
|
41
61
|
for (const t of tokens)
|
|
42
62
|
allTokens.add(t);
|
|
43
63
|
return tokens;
|