@danielblomma/cortex-mcp 2.1.0 → 2.1.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.
- package/README.md +19 -0
- package/bin/cortex.mjs +2 -0
- package/package.json +3 -2
- package/scaffold/mcp/build.mjs +92 -0
- package/scaffold/mcp/package-lock.json +3 -3
- package/scaffold/mcp/package.json +5 -5
- package/scaffold/mcp/src/embed.ts +182 -66
- package/scaffold/mcp/src/embedScheduler.ts +509 -0
- package/scaffold/mcp/src/embeddings.ts +31 -30
- package/scaffold/mcp/src/graphCsv.ts +65 -0
- package/scaffold/mcp/src/jsonl.ts +69 -14
- package/scaffold/mcp/src/loadGraph.ts +276 -40
- package/scaffold/mcp/src/lruCache.ts +41 -0
- package/scaffold/mcp/src/searchCore.ts +1 -1
- package/scaffold/mcp/src/searchResults.ts +3 -3
- package/scaffold/mcp/src/types.ts +6 -1
- package/scaffold/mcp/tests/embed-scheduler.test.mjs +474 -0
- package/scaffold/mcp/tests/embedding-jsonl.test.mjs +39 -0
- package/scaffold/mcp/tests/graph-bulk-load.test.mjs +258 -0
- package/scaffold/mcp/tests/graph-csv.test.mjs +118 -0
- package/scaffold/mcp/tests/lru-cache.test.mjs +37 -0
- package/scaffold/mcp/tests/vector-index.test.mjs +109 -0
- package/scaffold/mcp/tsconfig.json +3 -1
- package/scaffold/scripts/bootstrap.sh +34 -2
- package/scaffold/scripts/dashboard.mjs +1 -1
- package/scaffold/scripts/ingest-parsers.mjs +387 -0
- package/scaffold/scripts/ingest-worker.mjs +52 -0
- package/scaffold/scripts/ingest.mjs +550 -378
- package/scaffold/scripts/parsers/csharp.mjs +2 -1
- package/scaffold/scripts/parsers/javascript/ast.mjs +160 -8
- package/scaffold/scripts/parsers/javascript/chunks.mjs +129 -22
- package/scaffold/scripts/parsers/javascript/imports.mjs +38 -4
- package/scaffold/scripts/parsers/vbnet.mjs +2 -1
- package/scaffold/scripts/status.sh +50 -14
|
@@ -35,6 +35,8 @@ base.Class = (node, st, visit) => {
|
|
|
35
35
|
|
|
36
36
|
const tsNodeHandlers = {
|
|
37
37
|
TSAsExpression(node, st, visit) { visit(node.expression, st); },
|
|
38
|
+
TSSatisfiesExpression(node, st, visit) { visit(node.expression, st); },
|
|
39
|
+
TSTypeAssertion(node, st, visit) { visit(node.expression, st); },
|
|
38
40
|
TSTypeAnnotation(node, st, visit) {
|
|
39
41
|
if (node.typeAnnotation) {
|
|
40
42
|
visit(node.typeAnnotation, st);
|
|
@@ -63,15 +65,80 @@ const tsNodeHandlers = {
|
|
|
63
65
|
visit(node.typeParameters, st);
|
|
64
66
|
}
|
|
65
67
|
},
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
68
|
+
TSQualifiedName(node, st, visit) {
|
|
69
|
+
if (node.left) visit(node.left, st);
|
|
70
|
+
if (node.right) visit(node.right, st);
|
|
71
|
+
},
|
|
72
|
+
TSInterfaceDeclaration(node, st, visit) {
|
|
73
|
+
if (node.extends) {
|
|
74
|
+
for (const extendedType of node.extends) {
|
|
75
|
+
visit(extendedType, st);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
if (node.body) {
|
|
79
|
+
visit(node.body, st);
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
TSInterfaceBody(node, st, visit) {
|
|
83
|
+
for (const member of node.body || []) {
|
|
84
|
+
visit(member, st);
|
|
85
|
+
}
|
|
86
|
+
},
|
|
87
|
+
TSTypeAliasDeclaration(node, st, visit) {
|
|
88
|
+
if (node.typeParameters) {
|
|
89
|
+
visit(node.typeParameters, st);
|
|
90
|
+
}
|
|
91
|
+
if (node.typeAnnotation) {
|
|
92
|
+
visit(node.typeAnnotation, st);
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
TSEnumDeclaration(node, st, visit) {
|
|
96
|
+
for (const member of node.members || []) {
|
|
97
|
+
visit(member, st);
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
TSEnumMember(node, st, visit) {
|
|
101
|
+
if (node.initializer) {
|
|
102
|
+
visit(node.initializer, st);
|
|
103
|
+
}
|
|
104
|
+
},
|
|
69
105
|
TSModuleDeclaration() {},
|
|
70
106
|
TSDeclareFunction() {},
|
|
71
|
-
TSPropertySignature() {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
107
|
+
TSPropertySignature(node, st, visit) {
|
|
108
|
+
if (node.computed && node.key) {
|
|
109
|
+
visit(node.key, st);
|
|
110
|
+
}
|
|
111
|
+
if (node.typeAnnotation) {
|
|
112
|
+
visit(node.typeAnnotation, st);
|
|
113
|
+
}
|
|
114
|
+
},
|
|
115
|
+
TSMethodSignature(node, st, visit) {
|
|
116
|
+
if (node.computed && node.key) {
|
|
117
|
+
visit(node.key, st);
|
|
118
|
+
}
|
|
119
|
+
if (node.typeParameters) {
|
|
120
|
+
visit(node.typeParameters, st);
|
|
121
|
+
}
|
|
122
|
+
for (const param of node.params || []) {
|
|
123
|
+
visit(param, st);
|
|
124
|
+
}
|
|
125
|
+
if (node.returnType) {
|
|
126
|
+
visit(node.returnType, st);
|
|
127
|
+
}
|
|
128
|
+
},
|
|
129
|
+
TSIndexSignature(node, st, visit) {
|
|
130
|
+
for (const param of node.params || []) {
|
|
131
|
+
visit(param, st);
|
|
132
|
+
}
|
|
133
|
+
if (node.typeAnnotation) {
|
|
134
|
+
visit(node.typeAnnotation, st);
|
|
135
|
+
}
|
|
136
|
+
},
|
|
137
|
+
TSTypeLiteral(node, st, visit) {
|
|
138
|
+
for (const member of node.members || node.body || []) {
|
|
139
|
+
visit(member, st);
|
|
140
|
+
}
|
|
141
|
+
},
|
|
75
142
|
TSUnionType(node, st, visit) {
|
|
76
143
|
for (const typeNode of node.types || []) {
|
|
77
144
|
visit(typeNode, st);
|
|
@@ -132,9 +199,94 @@ const tsNodeHandlers = {
|
|
|
132
199
|
}
|
|
133
200
|
},
|
|
134
201
|
TSNonNullExpression(node, st, visit) { visit(node.expression, st); },
|
|
135
|
-
TSInstantiationExpression(node, st, visit) { visit(node.expression, st); }
|
|
202
|
+
TSInstantiationExpression(node, st, visit) { visit(node.expression, st); },
|
|
203
|
+
JSXElement(node, st, visit) {
|
|
204
|
+
if (node.openingElement) {
|
|
205
|
+
visit(node.openingElement, st);
|
|
206
|
+
}
|
|
207
|
+
for (const child of node.children || []) {
|
|
208
|
+
visit(child, st);
|
|
209
|
+
}
|
|
210
|
+
if (node.closingElement) {
|
|
211
|
+
visit(node.closingElement, st);
|
|
212
|
+
}
|
|
213
|
+
},
|
|
214
|
+
JSXFragment(node, st, visit) {
|
|
215
|
+
if (node.openingFragment) {
|
|
216
|
+
visit(node.openingFragment, st);
|
|
217
|
+
}
|
|
218
|
+
for (const child of node.children || []) {
|
|
219
|
+
visit(child, st);
|
|
220
|
+
}
|
|
221
|
+
if (node.closingFragment) {
|
|
222
|
+
visit(node.closingFragment, st);
|
|
223
|
+
}
|
|
224
|
+
},
|
|
225
|
+
JSXOpeningElement(node, st, visit) {
|
|
226
|
+
if (node.name) {
|
|
227
|
+
visit(node.name, st);
|
|
228
|
+
}
|
|
229
|
+
for (const attribute of node.attributes || []) {
|
|
230
|
+
visit(attribute, st);
|
|
231
|
+
}
|
|
232
|
+
},
|
|
233
|
+
JSXClosingElement(node, st, visit) {
|
|
234
|
+
if (node.name) {
|
|
235
|
+
visit(node.name, st);
|
|
236
|
+
}
|
|
237
|
+
},
|
|
238
|
+
JSXAttribute(node, st, visit) {
|
|
239
|
+
if (node.value) {
|
|
240
|
+
visit(node.value, st);
|
|
241
|
+
}
|
|
242
|
+
},
|
|
243
|
+
JSXExpressionContainer(node, st, visit) {
|
|
244
|
+
if (node.expression && node.expression.type !== "JSXEmptyExpression") {
|
|
245
|
+
visit(node.expression, st);
|
|
246
|
+
}
|
|
247
|
+
},
|
|
248
|
+
JSXSpreadAttribute(node, st, visit) {
|
|
249
|
+
if (node.argument) {
|
|
250
|
+
visit(node.argument, st);
|
|
251
|
+
}
|
|
252
|
+
},
|
|
253
|
+
JSXMemberExpression(node, st, visit) {
|
|
254
|
+
if (node.object) {
|
|
255
|
+
visit(node.object, st);
|
|
256
|
+
}
|
|
257
|
+
},
|
|
258
|
+
JSXNamespacedName(node, st, visit) {
|
|
259
|
+
if (node.namespace) {
|
|
260
|
+
visit(node.namespace, st);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
136
263
|
};
|
|
137
264
|
|
|
265
|
+
for (const nodeType of [
|
|
266
|
+
"JSXClosingFragment",
|
|
267
|
+
"JSXEmptyExpression",
|
|
268
|
+
"JSXIdentifier",
|
|
269
|
+
"JSXOpeningFragment",
|
|
270
|
+
"JSXText",
|
|
271
|
+
"TSAnyKeyword",
|
|
272
|
+
"TSBigIntKeyword",
|
|
273
|
+
"TSBooleanKeyword",
|
|
274
|
+
"TSIntrinsicKeyword",
|
|
275
|
+
"TSNeverKeyword",
|
|
276
|
+
"TSNullKeyword",
|
|
277
|
+
"TSNumberKeyword",
|
|
278
|
+
"TSObjectKeyword",
|
|
279
|
+
"TSStringKeyword",
|
|
280
|
+
"TSSymbolKeyword",
|
|
281
|
+
"TSUndefinedKeyword",
|
|
282
|
+
"TSUnknownKeyword",
|
|
283
|
+
"TSVoidKeyword",
|
|
284
|
+
"TSThisType",
|
|
285
|
+
"TSLiteralType"
|
|
286
|
+
]) {
|
|
287
|
+
tsNodeHandlers[nodeType] = () => {};
|
|
288
|
+
}
|
|
289
|
+
|
|
138
290
|
Object.assign(base, tsNodeHandlers);
|
|
139
291
|
|
|
140
292
|
export const WALK_BASE = base;
|
|
@@ -45,6 +45,18 @@ export function discoverChunks(ast, code, language = "javascript") {
|
|
|
45
45
|
}
|
|
46
46
|
},
|
|
47
47
|
|
|
48
|
+
TSInterfaceDeclaration(node) {
|
|
49
|
+
pushChunk(extractTypeDeclarationChunk(node, "interface", code));
|
|
50
|
+
},
|
|
51
|
+
|
|
52
|
+
TSTypeAliasDeclaration(node) {
|
|
53
|
+
pushChunk(extractTypeDeclarationChunk(node, "type", code));
|
|
54
|
+
},
|
|
55
|
+
|
|
56
|
+
TSEnumDeclaration(node) {
|
|
57
|
+
pushChunk(extractTypeDeclarationChunk(node, "enum", code));
|
|
58
|
+
},
|
|
59
|
+
|
|
48
60
|
VariableDeclaration(node) {
|
|
49
61
|
for (const declarator of node.declarations || []) {
|
|
50
62
|
if (!declarator.id || declarator.id.type !== "Identifier" || !declarator.init) {
|
|
@@ -100,6 +112,15 @@ function collectExportedNames(ast) {
|
|
|
100
112
|
exportedNames.add(node.declaration.id.name);
|
|
101
113
|
}
|
|
102
114
|
|
|
115
|
+
if (
|
|
116
|
+
(node.declaration.type === "TSInterfaceDeclaration" ||
|
|
117
|
+
node.declaration.type === "TSTypeAliasDeclaration" ||
|
|
118
|
+
node.declaration.type === "TSEnumDeclaration") &&
|
|
119
|
+
node.declaration.id?.name
|
|
120
|
+
) {
|
|
121
|
+
exportedNames.add(node.declaration.id.name);
|
|
122
|
+
}
|
|
123
|
+
|
|
103
124
|
if (node.declaration.type === "VariableDeclaration") {
|
|
104
125
|
for (const declarator of node.declaration.declarations || []) {
|
|
105
126
|
if (declarator.id?.type === "Identifier") {
|
|
@@ -292,34 +313,120 @@ function extractClassMethods(classNode, code, language) {
|
|
|
292
313
|
const className = classNode.id?.name || "UnknownClass";
|
|
293
314
|
|
|
294
315
|
for (const member of classNode.body.body || []) {
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
316
|
+
const methodChunk =
|
|
317
|
+
member.type === "MethodDefinition"
|
|
318
|
+
? extractClassMethodChunk(member, className, code, language)
|
|
319
|
+
: extractClassFieldFunctionChunk(member, className, code, language);
|
|
298
320
|
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
const bodyStart = findLeadingCommentStart(code, member);
|
|
303
|
-
|
|
304
|
-
methods.push({
|
|
305
|
-
name: `${className}.${member.key.name}`,
|
|
306
|
-
kind: "method",
|
|
307
|
-
signature: `${prefix}${member.key.name}(${params.join(", ")})`,
|
|
308
|
-
body: code.slice(bodyStart, member.end),
|
|
309
|
-
startLine: member.loc.start.line,
|
|
310
|
-
endLine: member.loc.end.line,
|
|
311
|
-
callNode: member.value.body,
|
|
312
|
-
importNode: member,
|
|
313
|
-
static: isStatic,
|
|
314
|
-
async: member.value.async === true,
|
|
315
|
-
generator: member.value.generator === true,
|
|
316
|
-
language
|
|
317
|
-
});
|
|
321
|
+
if (methodChunk) {
|
|
322
|
+
methods.push(methodChunk);
|
|
323
|
+
}
|
|
318
324
|
}
|
|
319
325
|
|
|
320
326
|
return methods;
|
|
321
327
|
}
|
|
322
328
|
|
|
329
|
+
function extractClassMethodChunk(member, className, code, language) {
|
|
330
|
+
const methodName = getPropertyName(member.key, member.computed);
|
|
331
|
+
if (!methodName) {
|
|
332
|
+
return null;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
const params = (member.value.params || []).map(formatParameterName);
|
|
336
|
+
const isStatic = member.static === true;
|
|
337
|
+
const prefix = isStatic ? "static " : "";
|
|
338
|
+
const bodyStart = findLeadingCommentStart(code, member);
|
|
339
|
+
|
|
340
|
+
return {
|
|
341
|
+
name: `${className}.${methodName}`,
|
|
342
|
+
kind: "method",
|
|
343
|
+
signature: `${prefix}${methodName}(${params.join(", ")})`,
|
|
344
|
+
body: code.slice(bodyStart, member.end),
|
|
345
|
+
startLine: member.loc.start.line,
|
|
346
|
+
endLine: member.loc.end.line,
|
|
347
|
+
callNode: member.value.body,
|
|
348
|
+
importNode: member,
|
|
349
|
+
static: isStatic,
|
|
350
|
+
async: member.value.async === true,
|
|
351
|
+
generator: member.value.generator === true,
|
|
352
|
+
language
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
function extractClassFieldFunctionChunk(member, className, code, language) {
|
|
357
|
+
if (member.type !== "PropertyDefinition" && member.type !== "FieldDefinition") {
|
|
358
|
+
return null;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
const value = member.value;
|
|
362
|
+
const isFunctionField =
|
|
363
|
+
value?.type === "FunctionExpression" || value?.type === "ArrowFunctionExpression";
|
|
364
|
+
if (!isFunctionField) {
|
|
365
|
+
return null;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
const fieldName = getPropertyName(member.key, member.computed);
|
|
369
|
+
if (!fieldName) {
|
|
370
|
+
return null;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
const params = (value.params || []).map(formatParameterName);
|
|
374
|
+
const isStatic = member.static === true;
|
|
375
|
+
const prefix = isStatic ? "static " : "";
|
|
376
|
+
const bodyStart = findLeadingCommentStart(code, member);
|
|
377
|
+
|
|
378
|
+
return {
|
|
379
|
+
name: `${className}.${fieldName}`,
|
|
380
|
+
kind: "method",
|
|
381
|
+
signature: `${prefix}${fieldName}(${params.join(", ")})`,
|
|
382
|
+
body: code.slice(bodyStart, member.end),
|
|
383
|
+
startLine: member.loc.start.line,
|
|
384
|
+
endLine: member.loc.end.line,
|
|
385
|
+
callNode: value.body || value,
|
|
386
|
+
importNode: member,
|
|
387
|
+
static: isStatic,
|
|
388
|
+
async: value.async === true,
|
|
389
|
+
generator: value.generator === true,
|
|
390
|
+
field: true,
|
|
391
|
+
language
|
|
392
|
+
};
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
function extractTypeDeclarationChunk(node, kind, code) {
|
|
396
|
+
const name = node.id?.name;
|
|
397
|
+
if (!name) {
|
|
398
|
+
return null;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
const bodyStart = findLeadingCommentStart(code, node);
|
|
402
|
+
return {
|
|
403
|
+
name,
|
|
404
|
+
kind,
|
|
405
|
+
signature: `${kind} ${name}`,
|
|
406
|
+
body: code.slice(bodyStart, node.end),
|
|
407
|
+
startLine: node.loc.start.line,
|
|
408
|
+
endLine: node.loc.end.line,
|
|
409
|
+
callNode: node,
|
|
410
|
+
importNode: node
|
|
411
|
+
};
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
function getPropertyName(key, computed = false) {
|
|
415
|
+
if (!key) {
|
|
416
|
+
return null;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
if (!computed && key.type === "Identifier") {
|
|
420
|
+
return key.name;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
if (key.type === "Literal" && typeof key.value === "string") {
|
|
424
|
+
return key.value;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
return null;
|
|
428
|
+
}
|
|
429
|
+
|
|
323
430
|
function findLeadingCommentStart(code, node) {
|
|
324
431
|
let bodyStart = node.start;
|
|
325
432
|
let lineStart = code.lastIndexOf("\n", node.start - 1) + 1;
|
|
@@ -103,16 +103,17 @@ function extractReferencedStaticImportSources(bodyNode, bindings) {
|
|
|
103
103
|
fullAncestor(
|
|
104
104
|
bodyNode,
|
|
105
105
|
(node, state, ancestors) => {
|
|
106
|
-
|
|
106
|
+
const localName = getReferencedImportName(node, ancestors);
|
|
107
|
+
if (!localName) {
|
|
107
108
|
return;
|
|
108
109
|
}
|
|
109
110
|
|
|
110
|
-
const source = importsByLocalName.get(
|
|
111
|
-
if (!source
|
|
111
|
+
const source = importsByLocalName.get(localName);
|
|
112
|
+
if (!source) {
|
|
112
113
|
return;
|
|
113
114
|
}
|
|
114
115
|
|
|
115
|
-
if (resolveIdentifier(
|
|
116
|
+
if (resolveIdentifier(localName, ancestors, scopeGraph)) {
|
|
116
117
|
return;
|
|
117
118
|
}
|
|
118
119
|
|
|
@@ -127,6 +128,39 @@ function extractReferencedStaticImportSources(bodyNode, bindings) {
|
|
|
127
128
|
return [...sources].sort();
|
|
128
129
|
}
|
|
129
130
|
|
|
131
|
+
function getReferencedImportName(node, ancestors) {
|
|
132
|
+
if (node.type === "Identifier") {
|
|
133
|
+
return isReferenceIdentifier(node, ancestors) ? node.name : null;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (node.type !== "JSXIdentifier" || isIntrinsicJsxName(node.name)) {
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const parent = ancestors[ancestors.length - 2] ?? null;
|
|
141
|
+
if (!parent) {
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (parent.type === "JSXOpeningElement" && parent.name === node) {
|
|
146
|
+
return node.name;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (parent.type === "JSXMemberExpression" && parent.object === node) {
|
|
150
|
+
return node.name;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (parent.type === "JSXNamespacedName" && parent.namespace === node) {
|
|
154
|
+
return node.name;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function isIntrinsicJsxName(name) {
|
|
161
|
+
return /^[a-z]/.test(name);
|
|
162
|
+
}
|
|
163
|
+
|
|
130
164
|
function extractDynamicImports(bodyNode) {
|
|
131
165
|
if (!bodyNode) {
|
|
132
166
|
return [];
|
|
@@ -255,23 +255,59 @@ try {
|
|
|
255
255
|
return raw.split(/\r?\n/)[0].trim();
|
|
256
256
|
};
|
|
257
257
|
|
|
258
|
-
|
|
259
|
-
|
|
258
|
+
// The latest-version lookup is a network call; cache it for a day so
|
|
259
|
+
// repeated bootstraps/status checks stay fast and offline-friendly.
|
|
260
|
+
const fs = require("node:fs");
|
|
261
|
+
const versionCheckCache = path.join(cacheDir, "version-check.json");
|
|
262
|
+
const VERSION_CHECK_TTL_MS = 24 * 60 * 60 * 1000;
|
|
263
|
+
let latestVersion = null;
|
|
260
264
|
try {
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
265
|
+
const cached = JSON.parse(fs.readFileSync(versionCheckCache, "utf8"));
|
|
266
|
+
const age = cached ? Date.now() - cached.checked_at : NaN;
|
|
267
|
+
if (
|
|
268
|
+
cached &&
|
|
269
|
+
typeof cached.latest === "string" &&
|
|
270
|
+
Number.isFinite(cached.checked_at) &&
|
|
271
|
+
age >= 0 &&
|
|
272
|
+
age < VERSION_CHECK_TTL_MS &&
|
|
273
|
+
parseVersion(cached.latest)
|
|
274
|
+
) {
|
|
275
|
+
latestVersion = cached.latest;
|
|
276
|
+
}
|
|
277
|
+
} catch {
|
|
278
|
+
// No usable cache; fall through to the live lookup.
|
|
271
279
|
}
|
|
272
280
|
|
|
273
|
-
|
|
274
|
-
|
|
281
|
+
if (!latestVersion) {
|
|
282
|
+
const npmCache = path.join(cacheDir, "npm-cache");
|
|
283
|
+
let latestRaw = "";
|
|
284
|
+
try {
|
|
285
|
+
latestRaw = execSync("npm view github:DanielBlomma/cortex version --json", {
|
|
286
|
+
cwd: repoRoot,
|
|
287
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
288
|
+
encoding: "utf8",
|
|
289
|
+
timeout: 2500,
|
|
290
|
+
env: { ...process.env, NPM_CONFIG_CACHE: npmCache }
|
|
291
|
+
}).trim();
|
|
292
|
+
} catch (error) {
|
|
293
|
+
console.log(`[status] cortex_update_check=unavailable (${summarizeError(error)})`);
|
|
294
|
+
process.exit(0);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
const parsedLatest = JSON.parse(latestRaw);
|
|
298
|
+
latestVersion = Array.isArray(parsedLatest) ? parsedLatest[parsedLatest.length - 1] : parsedLatest;
|
|
299
|
+
if (parseVersion(latestVersion)) {
|
|
300
|
+
try {
|
|
301
|
+
fs.mkdirSync(cacheDir, { recursive: true });
|
|
302
|
+
fs.writeFileSync(
|
|
303
|
+
versionCheckCache,
|
|
304
|
+
JSON.stringify({ checked_at: Date.now(), latest: latestVersion })
|
|
305
|
+
);
|
|
306
|
+
} catch {
|
|
307
|
+
// Cache write failures must never break status reporting.
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
}
|
|
275
311
|
|
|
276
312
|
const localParsed = parseVersion(localVersion);
|
|
277
313
|
const latestParsed = parseVersion(latestVersion);
|