@alaarab/cortex 1.13.3 → 1.13.5
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.
|
@@ -192,9 +192,12 @@ export function upsertCanonical(cortexPath, project, memory) {
|
|
|
192
192
|
const canonicalPath = path.join(resolvedDir, "CANONICAL_MEMORIES.md");
|
|
193
193
|
const today = new Date().toISOString().slice(0, 10);
|
|
194
194
|
const bullet = memory.startsWith("- ") ? memory : `- ${memory}`;
|
|
195
|
+
let canonicalContent = "";
|
|
195
196
|
withFileLock(canonicalPath, () => {
|
|
196
197
|
if (!fs.existsSync(canonicalPath)) {
|
|
197
|
-
|
|
198
|
+
const initial = `# ${project} Canonical Memories\n\n## Pinned\n\n${bullet} _(pinned ${today})_\n`;
|
|
199
|
+
fs.writeFileSync(canonicalPath, initial);
|
|
200
|
+
canonicalContent = initial;
|
|
198
201
|
}
|
|
199
202
|
else {
|
|
200
203
|
const existing = fs.readFileSync(canonicalPath, "utf8");
|
|
@@ -203,13 +206,17 @@ export function upsertCanonical(cortexPath, project, memory) {
|
|
|
203
206
|
const updated = existing.includes("## Pinned")
|
|
204
207
|
? existing.replace("## Pinned", `## Pinned\n\n${line}`)
|
|
205
208
|
: `${existing.trimEnd()}\n\n## Pinned\n\n${line}\n`;
|
|
209
|
+
const finalContent = updated.endsWith("\n") ? updated : updated + "\n";
|
|
206
210
|
const tmpPath = canonicalPath + ".tmp";
|
|
207
|
-
fs.writeFileSync(tmpPath,
|
|
211
|
+
fs.writeFileSync(tmpPath, finalContent);
|
|
208
212
|
fs.renameSync(tmpPath, canonicalPath);
|
|
213
|
+
canonicalContent = finalContent;
|
|
214
|
+
}
|
|
215
|
+
else {
|
|
216
|
+
canonicalContent = existing;
|
|
209
217
|
}
|
|
210
218
|
}
|
|
211
219
|
});
|
|
212
|
-
const canonicalContent = fs.readFileSync(canonicalPath, "utf8");
|
|
213
220
|
const locks = loadCanonicalLocks(cortexPath);
|
|
214
221
|
const lockKey = `${project}/CANONICAL_MEMORIES.md`;
|
|
215
222
|
locks[lockKey] = {
|
|
@@ -272,8 +279,11 @@ export function addFindingToFile(cortexPath, project, learning, citationInput, o
|
|
|
272
279
|
}
|
|
273
280
|
const content = fs.readFileSync(learningsPath, "utf8");
|
|
274
281
|
const legacyHistory = opts?.skipLegacyDedup ? "" : readLegacyHistoryContent(resolvedDir);
|
|
275
|
-
// When superseding, strip the old finding from history so dedup doesn't block the intentionally similar replacement
|
|
276
|
-
|
|
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)
|
|
277
287
|
? (legacyHistory ? `${content}\n${legacyHistory}` : content)
|
|
278
288
|
.split("\n")
|
|
279
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
|
@@ -59,7 +59,9 @@ export function register(server, ctx) {
|
|
|
59
59
|
const lineEnd = content.indexOf("\n", idx);
|
|
60
60
|
const insertAt = lineEnd >= 0 ? lineEnd : content.length;
|
|
61
61
|
content = content.slice(0, insertAt) + " " + conflicts.annotations.join(" ") + " <!-- conflicts_checked: true -->" + content.slice(insertAt);
|
|
62
|
-
|
|
62
|
+
const tmpFp = fp + ".tmp";
|
|
63
|
+
fs.writeFileSync(tmpFp, content);
|
|
64
|
+
fs.renameSync(tmpFp, fp);
|
|
63
65
|
}
|
|
64
66
|
}
|
|
65
67
|
}
|
|
@@ -178,7 +180,8 @@ export function register(server, ctx) {
|
|
|
178
180
|
.filter((name) => name && !name.startsWith(".") && name !== "profiles")));
|
|
179
181
|
const commitMsg = message || `cortex: save ${files.length} file(s) across ${projectNames.length} project(s)`;
|
|
180
182
|
runCustomHooks(cortexPath, "pre-save");
|
|
181
|
-
|
|
183
|
+
// Restrict to known cortex file types to avoid staging .env or credential files
|
|
184
|
+
runGit(["add", "--", "*.md", "*.json", "*.yaml", "*.yml", "*.jsonl", "*.txt"]);
|
|
182
185
|
runGit(["commit", "-m", commitMsg]);
|
|
183
186
|
let hasRemote = false;
|
|
184
187
|
try {
|
package/mcp/dist/memory-ui.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as http from "http";
|
|
2
2
|
import * as crypto from "crypto";
|
|
3
|
+
import { timingSafeEqual } from "crypto";
|
|
3
4
|
import * as fs from "fs";
|
|
4
5
|
import * as path from "path";
|
|
5
6
|
import * as querystring from "querystring";
|
|
@@ -35,6 +36,15 @@ function getSubmittedAuthToken(req, url, parsedBody) {
|
|
|
35
36
|
return bodyAuth;
|
|
36
37
|
return "";
|
|
37
38
|
}
|
|
39
|
+
function authTokensMatch(submitted, authToken) {
|
|
40
|
+
if (!authToken || !submitted)
|
|
41
|
+
return false;
|
|
42
|
+
const submittedBuffer = Buffer.from(submitted);
|
|
43
|
+
const authTokenBuffer = Buffer.from(authToken);
|
|
44
|
+
if (submittedBuffer.length !== authTokenBuffer.length)
|
|
45
|
+
return false;
|
|
46
|
+
return timingSafeEqual(submittedBuffer, authTokenBuffer);
|
|
47
|
+
}
|
|
38
48
|
function recentUsage(cortexPath) {
|
|
39
49
|
const usage = path.join(cortexPath, ".governance", "memory-usage.log");
|
|
40
50
|
if (!fs.existsSync(usage))
|
|
@@ -324,7 +334,7 @@ export function createReviewUiServer(cortexPath, opts) {
|
|
|
324
334
|
if (req.method === "GET" && url.startsWith("/api/graph")) {
|
|
325
335
|
if (authToken) {
|
|
326
336
|
const submitted = getSubmittedAuthToken(req, url);
|
|
327
|
-
if (submitted
|
|
337
|
+
if (!authTokensMatch(submitted, authToken)) {
|
|
328
338
|
res.writeHead(401, { "content-type": "text/plain; charset=utf-8" });
|
|
329
339
|
res.end("Unauthorized");
|
|
330
340
|
return;
|
|
@@ -356,7 +366,7 @@ export function createReviewUiServer(cortexPath, opts) {
|
|
|
356
366
|
const parsed = querystring.parse(body);
|
|
357
367
|
if (authToken) {
|
|
358
368
|
const submitted = getSubmittedAuthToken(req, url, parsed);
|
|
359
|
-
if (submitted
|
|
369
|
+
if (!authTokensMatch(submitted, authToken)) {
|
|
360
370
|
res.writeHead(401, { "content-type": "text/plain; charset=utf-8" });
|
|
361
371
|
res.end("Unauthorized");
|
|
362
372
|
return;
|
package/mcp/dist/shared.js
CHANGED
|
@@ -32,11 +32,6 @@ export function forwardErr(result) {
|
|
|
32
32
|
return { ok: false, error: result.error, code: result.code };
|
|
33
33
|
return { ok: false, error: "unexpected forward of ok result" };
|
|
34
34
|
}
|
|
35
|
-
// Type guard: returns true when result is a string error (legacy T | string pattern).
|
|
36
|
-
// Useful during migration from T | string to CortexResult<T>.
|
|
37
|
-
export function isCortexError(result) {
|
|
38
|
-
return typeof result === "string";
|
|
39
|
-
}
|
|
40
35
|
const ERROR_CODES = new Set(Object.values(CortexError));
|
|
41
36
|
// Extract the error code from a legacy error string (e.g. "PROJECT_NOT_FOUND: ...").
|
|
42
37
|
// Returns the code if the string starts with a known CortexError, or undefined.
|