@alaarab/cortex 1.13.2 → 1.13.4

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.
@@ -38,14 +38,24 @@ export function buildHookOutput(selected, usedTokens, intent, gitCtx, detectedPr
38
38
  parts.push("");
39
39
  for (const injected of indexEntries) {
40
40
  recordInjection(cortexPathLocal, injected.key, sessionId);
41
- recordRetrieval(cortexPathLocal, injected.doc.path, injected.doc.type);
41
+ try {
42
+ recordRetrieval(cortexPathLocal, injected.doc.path ?? injected.doc.filename, injected.doc.type);
43
+ }
44
+ catch {
45
+ // best-effort
46
+ }
42
47
  }
43
48
  }
44
49
  else {
45
50
  for (const injected of selected) {
46
51
  const { doc, snippet, key } = injected;
47
52
  recordInjection(cortexPathLocal, key, sessionId);
48
- recordRetrieval(cortexPathLocal, doc.path, doc.type);
53
+ try {
54
+ recordRetrieval(cortexPathLocal, doc.path ?? doc.filename, doc.type);
55
+ }
56
+ catch {
57
+ // best-effort
58
+ }
49
59
  parts.push(`[${getDocSourceKey(doc, cortexPathLocal)}] (${doc.type})`);
50
60
  parts.push(annotateStale(snippet));
51
61
  parts.push("");
@@ -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
- fs.writeFileSync(canonicalPath, `# ${project} Canonical Memories\n\n## Pinned\n\n${bullet} _(pinned ${today})_\n`);
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, updated.endsWith("\n") ? updated : updated + "\n");
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] = {
@@ -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
- fs.writeFileSync(fp, content);
62
+ const tmpFp = fp + ".tmp";
63
+ fs.writeFileSync(tmpFp, content);
64
+ fs.renameSync(tmpFp, fp);
63
65
  }
64
66
  }
65
67
  }
@@ -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 !== authToken) {
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 !== authToken) {
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;
@@ -1,4 +1,5 @@
1
1
  import * as fs from "fs";
2
+ const PROSE_ENTITY_PATTERN = /\b(React|Vue|Angular|Next\.js|Nuxt|Svelte|Express|Fastify|Koa|Hapi|NestJS|Django|Flask|FastAPI|Rails|Spring|Laravel|Redis|Postgres|PostgreSQL|MySQL|MariaDB|SQLite|MongoDB|DynamoDB|Cassandra|Elasticsearch|Docker|Kubernetes|Terraform|Ansible|AWS|GCP|Azure|Vercel|Netlify|Cloudflare|Prisma|TypeORM|Sequelize|Drizzle|Mongoose|Jest|Vitest|Mocha|Cypress|Playwright|Puppeteer|Webpack|Vite|Rollup|esbuild|Turbopack|ESLint|Prettier|Babel|SWC|GraphQL|REST|gRPC|WebSocket|Kafka|RabbitMQ|NATS|Nginx|Caddy|Traefik|Node\.js|Deno|Bun|Python|Rust|Go|Java|Kotlin|Swift|TypeScript|JavaScript)\b/gi;
2
3
  const ENTITY_PATTERNS = [
3
4
  // import/require patterns: import X from 'pkg' or require('pkg')
4
5
  /(?:import\s+.*?\s+from\s+['"])(@?[\w\-/]+)(?:['"])/g,
@@ -6,7 +7,7 @@ const ENTITY_PATTERNS = [
6
7
  // @scope/package patterns in text
7
8
  /@[\w-]+\/[\w-]+/g,
8
9
  // Known library/tool names mentioned in prose (case-insensitive word boundaries)
9
- /\b(React|Vue|Angular|Next\.js|Nuxt|Svelte|Express|Fastify|Django|Flask|Rails|Spring|Redis|Postgres|PostgreSQL|MySQL|MongoDB|SQLite|Docker|Kubernetes|Terraform|AWS|GCP|Azure|Vercel|Netlify|Prisma|TypeORM|Sequelize|Jest|Vitest|Cypress|Playwright|Webpack|Vite|ESLint|Prettier|GraphQL|gRPC|Kafka|RabbitMQ|Elasticsearch|Nginx|Caddy|Node\.js|Deno|Bun|Python|Rust|Go|Java|TypeScript|Zod|Drizzle|tRPC|Tailwind|shadcn)\b/gi,
10
+ PROSE_ENTITY_PATTERN,
10
11
  ];
11
12
  function extractEntityNames(content) {
12
13
  const found = new Set();
@@ -617,12 +617,15 @@ export function getDocSourceKey(doc, cortexPath) {
617
617
  return buildSourceDocKey(doc.project, doc.path, cortexPath, doc.filename);
618
618
  }
619
619
  export function rowToDoc(row) {
620
+ if (!Array.isArray(row) || row.length < 5) {
621
+ throw new Error(`rowToDoc: expected ≥5 columns, got ${Array.isArray(row) ? row.length : typeof row}`);
622
+ }
620
623
  return {
621
- project: row[0],
622
- filename: row[1],
623
- type: row[2],
624
- content: row[3],
625
- path: row[4],
624
+ project: row[0] != null ? String(row[0]) : "",
625
+ filename: row[1] != null ? String(row[1]) : "",
626
+ type: row[2] != null ? String(row[2]) : "",
627
+ content: row[3] != null ? String(row[3]) : "",
628
+ path: row[4] != null ? String(row[4]) : "",
626
629
  };
627
630
  }
628
631
  export function queryDocRows(db, sql, params) {
@@ -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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alaarab/cortex",
3
- "version": "1.13.2",
3
+ "version": "1.13.4",
4
4
  "description": "Long-term memory for AI agents — stored as markdown in a git repo you own.",
5
5
  "type": "module",
6
6
  "bin": {