@danielblomma/cortex-mcp 2.2.4 → 2.4.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 +62 -7
- package/bin/cortex.mjs +31 -167
- package/package.json +2 -2
- package/scaffold/mcp/package-lock.json +24 -21
- package/scaffold/mcp/src/cli/query.ts +36 -2
- package/scaffold/mcp/src/embed.ts +234 -21
- package/scaffold/mcp/src/embedScheduler.ts +69 -1
- package/scaffold/mcp/src/enterprise/index.ts +1 -1
- package/scaffold/mcp/src/enterprise/reviews/changed-files.ts +34 -0
- package/scaffold/mcp/src/enterprise/reviews/pattern-context.ts +231 -0
- package/scaffold/mcp/src/enterprise/tools/enterprise.ts +49 -36
- package/scaffold/mcp/src/paths.ts +3 -5
- package/scaffold/mcp/src/patternEvidence.ts +347 -0
- package/scaffold/mcp/src/search.ts +35 -9
- package/scaffold/mcp/src/searchCore.ts +254 -12
- package/scaffold/mcp/src/searchResults.ts +96 -9
- package/scaffold/mcp/src/types.ts +7 -0
- package/scaffold/mcp/tests/changed-files.test.mjs +41 -0
- package/scaffold/mcp/tests/embed-entities.test.mjs +109 -1
- package/scaffold/mcp/tests/embed-scheduler.test.mjs +52 -0
- package/scaffold/mcp/tests/enterprise-pattern-context.test.mjs +322 -0
- package/scaffold/mcp/tests/paths.test.mjs +11 -3
- package/scaffold/mcp/tests/pattern-evidence.test.mjs +321 -0
- package/scaffold/mcp/tests/query-cli.test.mjs +73 -1
- package/scaffold/mcp/tests/search-graph-score.test.mjs +167 -0
- package/scaffold/scripts/dashboard.mjs +29 -18
- package/scaffold/scripts/doctor.sh +26 -8
- package/scaffold/scripts/ingest.mjs +37 -4
- package/scaffold/scripts/status.sh +13 -6
package/README.md
CHANGED
|
@@ -143,6 +143,26 @@ Check context status:
|
|
|
143
143
|
cortex status
|
|
144
144
|
```
|
|
145
145
|
|
|
146
|
+
## Agent plugin (Claude Code + Codex)
|
|
147
|
+
|
|
148
|
+
The `plugins/cortex` directory is a dual-manifest agent plugin: five behavior
|
|
149
|
+
skills (`using-cortex`, `repo-research`, `change-impact`, `pattern-review`,
|
|
150
|
+
`context-review`), a SessionStart bootstrap that re-injects Cortex
|
|
151
|
+
instructions after new sessions, `/clear`, and compaction (Claude Code), and
|
|
152
|
+
an MCP config that follows the active workspace.
|
|
153
|
+
|
|
154
|
+
Claude Code:
|
|
155
|
+
|
|
156
|
+
```bash
|
|
157
|
+
/plugin marketplace add DanielBlomma/cortex
|
|
158
|
+
/plugin install cortex@cortex
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
Codex discovers the same skills through `.codex-plugin/plugin.json`; repos
|
|
162
|
+
initialized with `cortex init` also get an AGENTS.md bootstrap section as a
|
|
163
|
+
fallback when the plugin is not installed. The CLI remains the engine — the
|
|
164
|
+
plugin only adds the behavior layer, and `cortex connect` stays opt-in.
|
|
165
|
+
|
|
146
166
|
## Query From The CLI
|
|
147
167
|
|
|
148
168
|
Use the CLI as the default local agent interface:
|
|
@@ -153,6 +173,7 @@ cortex related file:src/auth.ts --json
|
|
|
153
173
|
cortex impact "payment service" --json
|
|
154
174
|
cortex rules --json
|
|
155
175
|
cortex explain "where retries are configured" --json
|
|
176
|
+
cortex pattern-evidence src/auth.ts --query "error handling" --json
|
|
156
177
|
```
|
|
157
178
|
|
|
158
179
|
These commands read the same local graph, embeddings, and rules used by the MCP server, but they do not require an MCP client registration.
|
|
@@ -381,12 +402,39 @@ cortex related <entity-id> [--json]
|
|
|
381
402
|
cortex impact <query|entity-id> [--json]
|
|
382
403
|
cortex rules [--json]
|
|
383
404
|
cortex explain <query|entity-id> [--json]
|
|
405
|
+
cortex pattern-evidence <file-path|entity-id> [--query <text>] [--top-k <n>] [--json]
|
|
384
406
|
cortex status
|
|
385
407
|
cortex dashboard [--interval <sec>]
|
|
386
408
|
cortex watch [start|stop|status|run|once] [--interval <sec>] [--debounce <sec>] [--mode <auto|event|poll>]
|
|
387
409
|
cortex help
|
|
388
410
|
```
|
|
389
411
|
|
|
412
|
+
## Enterprise Pattern Review
|
|
413
|
+
|
|
414
|
+
Enterprise `context.review` includes bounded repo-local pattern context by
|
|
415
|
+
default. This evidence is advisory and does not change policy validator totals,
|
|
416
|
+
workflow approval, or review trust.
|
|
417
|
+
|
|
418
|
+
Optional MCP inputs:
|
|
419
|
+
|
|
420
|
+
- `include_pattern_evidence` — enable or disable pattern context (default `true`).
|
|
421
|
+
- `pattern_query` — shared pattern query; otherwise Cortex derives one per file.
|
|
422
|
+
- `pattern_top_k` — evidence items per locality tier, from 1 to 5 (default `2`).
|
|
423
|
+
- `pattern_limit` — analyzed review targets, from 1 to 25 (default `10`).
|
|
424
|
+
|
|
425
|
+
The response adds `pattern_review` with deterministic targets, the canonical
|
|
426
|
+
review question, cited evidence tiers, explicit repository fallback, unindexed
|
|
427
|
+
status, and omitted-file counts. Pattern evidence never constitutes automatic
|
|
428
|
+
code approval. Enterprise pattern review is lexical-only and never downloads an
|
|
429
|
+
embedding model or calls an external service.
|
|
430
|
+
|
|
431
|
+
Every target uses the same response fields. `status` is one of
|
|
432
|
+
`local_evidence`, `repo_fallback`, `no_evidence`, `not_indexed`, or `error`;
|
|
433
|
+
`local_pattern_found` and `fallback_used` are always explicit booleans, and
|
|
434
|
+
`evidence_order` plus all four `tiers` are always present. Unavailable evidence
|
|
435
|
+
uses empty tiers and sanitized messages rather than local paths or runtime
|
|
436
|
+
errors.
|
|
437
|
+
|
|
390
438
|
## Automated Release
|
|
391
439
|
|
|
392
440
|
This repository includes two GitHub Actions workflows:
|
|
@@ -418,14 +466,21 @@ all derived from the available CPU cores, RAM, and repository size at run time
|
|
|
418
466
|
(container memory limits included). No configuration is needed — on a laptop or
|
|
419
467
|
a CI runner, cortex picks safe settings by itself.
|
|
420
468
|
|
|
421
|
-
The embedding token budget defaults to `auto`, which
|
|
422
|
-
model's own maximum context
|
|
423
|
-
|
|
424
|
-
|
|
469
|
+
The embedding token budget defaults to `auto`, which is quality-first but
|
|
470
|
+
memory-aware. Cortex starts from the embedding model's own maximum context and
|
|
471
|
+
only lowers the cap when the local memory headroom is unlikely to fit that
|
|
472
|
+
model/context combination. Degraded auto runs are explicit in logs, for example
|
|
473
|
+
`token_budget=auto_degraded reason=memory_headroom cap=2048`. To force a
|
|
425
474
|
specific capped run, set `CORTEX_EMBED_MAX_TOKENS` to a number such as `2048`;
|
|
426
|
-
set it to `model` or `full` to
|
|
427
|
-
several cortex instances share one machine, set
|
|
428
|
-
each its fair share of cores.
|
|
475
|
+
set it to `model` or `full` to force the full-model baseline even on tight
|
|
476
|
+
machines. When several cortex instances share one machine, set
|
|
477
|
+
`CORTEX_EMBED_THREADS` to give each its fair share of cores.
|
|
478
|
+
|
|
479
|
+
For experiments on very large repositories, `CORTEX_EMBED_TEXT_PROFILE=compact-files`
|
|
480
|
+
compacts only large file-level embedding records while keeping chunk-level
|
|
481
|
+
embedding text full. The default remains `full`; use the compact profile only
|
|
482
|
+
with a before/after semantic quality check because file-level compaction can
|
|
483
|
+
change retrieval behavior.
|
|
429
484
|
|
|
430
485
|
## Limitations
|
|
431
486
|
|
package/bin/cortex.mjs
CHANGED
|
@@ -81,6 +81,7 @@ function printHelp() {
|
|
|
81
81
|
console.log(helpRow("impact <query|entity-id> [--json]", "Trace likely impact paths"));
|
|
82
82
|
console.log(helpRow("rules [--json]", "List active context rules"));
|
|
83
83
|
console.log(helpRow("explain <query|entity-id> [--json]", "Show search score evidence"));
|
|
84
|
+
console.log(helpRow("pattern-evidence <file|entity-id> [--query <text>] [--top-k <n>] [--json]", "Collect cited repo-local pattern evidence"));
|
|
84
85
|
console.log(helpRow("dashboard [--interval <sec>]", "Live local dashboard"));
|
|
85
86
|
console.log(helpRow("memory-compile [--dry-run] [--verbose]", "Compile memory artifacts"));
|
|
86
87
|
console.log(helpRow("memory-lint [--verbose] [--json]", "Lint compiled memory"));
|
|
@@ -198,81 +199,7 @@ function ensureScaffoldExists() {
|
|
|
198
199
|
// Files that should never be overwritten if they already exist in the target.
|
|
199
200
|
// These contain user-specific configuration that would be lost on re-init.
|
|
200
201
|
const PRESERVE_FILES = new Set(["config.yaml", "rules.yaml", "enterprise.yml", "enterprise.yaml", "CLAUDE.md", "AGENTS.md"]);
|
|
201
|
-
const DEFAULT_SOURCE_PATHS = [
|
|
202
|
-
"src",
|
|
203
|
-
"docs",
|
|
204
|
-
"design",
|
|
205
|
-
".context/notes",
|
|
206
|
-
".context/decisions",
|
|
207
|
-
"README.md"
|
|
208
|
-
];
|
|
209
|
-
const INIT_SKIP_DIRECTORIES = new Set([
|
|
210
|
-
".git",
|
|
211
|
-
".idea",
|
|
212
|
-
".vscode",
|
|
213
|
-
"node_modules",
|
|
214
|
-
"dist",
|
|
215
|
-
"build",
|
|
216
|
-
"coverage",
|
|
217
|
-
".next",
|
|
218
|
-
".cache",
|
|
219
|
-
".context",
|
|
220
|
-
"scripts",
|
|
221
|
-
".githooks",
|
|
222
|
-
"bin",
|
|
223
|
-
"obj"
|
|
224
|
-
]);
|
|
225
|
-
const INIT_SOURCE_EXTENSIONS = new Set([
|
|
226
|
-
".md",
|
|
227
|
-
".mdx",
|
|
228
|
-
".txt",
|
|
229
|
-
".adoc",
|
|
230
|
-
".rst",
|
|
231
|
-
".yaml",
|
|
232
|
-
".yml",
|
|
233
|
-
".json",
|
|
234
|
-
".toml",
|
|
235
|
-
".csv",
|
|
236
|
-
".ts",
|
|
237
|
-
".tsx",
|
|
238
|
-
".mts",
|
|
239
|
-
".cts",
|
|
240
|
-
".js",
|
|
241
|
-
".jsx",
|
|
242
|
-
".mjs",
|
|
243
|
-
".cjs",
|
|
244
|
-
".py",
|
|
245
|
-
".go",
|
|
246
|
-
".java",
|
|
247
|
-
".cs",
|
|
248
|
-
".vb",
|
|
249
|
-
".sln",
|
|
250
|
-
".vbproj",
|
|
251
|
-
".csproj",
|
|
252
|
-
".fsproj",
|
|
253
|
-
".props",
|
|
254
|
-
".targets",
|
|
255
|
-
".config",
|
|
256
|
-
".resx",
|
|
257
|
-
".settings",
|
|
258
|
-
".rb",
|
|
259
|
-
".rs",
|
|
260
|
-
".php",
|
|
261
|
-
".swift",
|
|
262
|
-
".kt",
|
|
263
|
-
".sql",
|
|
264
|
-
".sh",
|
|
265
|
-
".bash",
|
|
266
|
-
".zsh",
|
|
267
|
-
".ps1",
|
|
268
|
-
".c",
|
|
269
|
-
".h",
|
|
270
|
-
".cpp",
|
|
271
|
-
".hpp",
|
|
272
|
-
".cc",
|
|
273
|
-
".hh"
|
|
274
|
-
]);
|
|
275
|
-
const ROOT_DOC_PATHS = new Set(["docs", "design"]);
|
|
202
|
+
const DEFAULT_SOURCE_PATHS = ["."];
|
|
276
203
|
|
|
277
204
|
function copyDirectory(sourceDir, targetDir) {
|
|
278
205
|
fs.mkdirSync(targetDir, { recursive: true });
|
|
@@ -318,92 +245,8 @@ function slugifyRepoId(value) {
|
|
|
318
245
|
return dashed || "cortex";
|
|
319
246
|
}
|
|
320
247
|
|
|
321
|
-
function isInterestingSourceFile(fileName) {
|
|
322
|
-
const base = fileName.toLowerCase();
|
|
323
|
-
const ext = path.extname(fileName).toLowerCase();
|
|
324
|
-
return INIT_SOURCE_EXTENSIONS.has(ext) || base === "readme" || base.startsWith("readme.");
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
function directoryContainsInterestingFiles(directoryPath) {
|
|
328
|
-
const stack = [directoryPath];
|
|
329
|
-
while (stack.length > 0) {
|
|
330
|
-
const current = stack.pop();
|
|
331
|
-
let entries = [];
|
|
332
|
-
try {
|
|
333
|
-
entries = fs.readdirSync(current, { withFileTypes: true });
|
|
334
|
-
} catch {
|
|
335
|
-
continue;
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
for (const entry of entries) {
|
|
339
|
-
const absolutePath = path.join(current, entry.name);
|
|
340
|
-
if (entry.isDirectory()) {
|
|
341
|
-
if (INIT_SKIP_DIRECTORIES.has(entry.name)) {
|
|
342
|
-
continue;
|
|
343
|
-
}
|
|
344
|
-
stack.push(absolutePath);
|
|
345
|
-
continue;
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
if (entry.isFile() && isInterestingSourceFile(entry.name)) {
|
|
349
|
-
return true;
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
return false;
|
|
355
|
-
}
|
|
356
|
-
|
|
357
248
|
function detectInitialSourcePaths(targetDir) {
|
|
358
|
-
|
|
359
|
-
return [...DEFAULT_SOURCE_PATHS];
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
let entries = [];
|
|
363
|
-
try {
|
|
364
|
-
entries = fs.readdirSync(targetDir, { withFileTypes: true });
|
|
365
|
-
} catch {
|
|
366
|
-
return [...DEFAULT_SOURCE_PATHS];
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
const codeDirs = [];
|
|
370
|
-
const docDirs = [];
|
|
371
|
-
const rootFiles = [];
|
|
372
|
-
|
|
373
|
-
for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
|
|
374
|
-
const absolutePath = path.join(targetDir, entry.name);
|
|
375
|
-
|
|
376
|
-
if (entry.isDirectory()) {
|
|
377
|
-
if (INIT_SKIP_DIRECTORIES.has(entry.name)) {
|
|
378
|
-
continue;
|
|
379
|
-
}
|
|
380
|
-
if (!directoryContainsInterestingFiles(absolutePath)) {
|
|
381
|
-
continue;
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
const bucket = ROOT_DOC_PATHS.has(entry.name) ? docDirs : codeDirs;
|
|
385
|
-
bucket.push(toPosixPath(entry.name));
|
|
386
|
-
continue;
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
if (entry.isFile() && isInterestingSourceFile(entry.name)) {
|
|
390
|
-
rootFiles.push(toPosixPath(entry.name));
|
|
391
|
-
}
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
const readmeFiles = rootFiles.filter((filePath) => /^readme(\.|$)/i.test(path.basename(filePath)));
|
|
395
|
-
const nonReadmeRootFiles = rootFiles.filter((filePath) => !readmeFiles.includes(filePath));
|
|
396
|
-
const detected = [
|
|
397
|
-
...codeDirs,
|
|
398
|
-
...nonReadmeRootFiles,
|
|
399
|
-
...docDirs,
|
|
400
|
-
".context/notes",
|
|
401
|
-
".context/decisions",
|
|
402
|
-
...readmeFiles
|
|
403
|
-
];
|
|
404
|
-
const uniqueDetected = [...new Set(detected)];
|
|
405
|
-
const hasConcreteRepoContent = uniqueDetected.some((value) => !value.startsWith(".context/"));
|
|
406
|
-
return hasConcreteRepoContent ? uniqueDetected : [...DEFAULT_SOURCE_PATHS];
|
|
249
|
+
return [...DEFAULT_SOURCE_PATHS];
|
|
407
250
|
}
|
|
408
251
|
|
|
409
252
|
function buildInitialConfig(targetDir) {
|
|
@@ -418,12 +261,10 @@ function buildInitialConfig(targetDir) {
|
|
|
418
261
|
" - RULE",
|
|
419
262
|
" - CODE",
|
|
420
263
|
" - WIKI",
|
|
421
|
-
"#
|
|
422
|
-
"# constrain an entity (docs are hubs, leaf code is not). Tuned as a pair with",
|
|
423
|
-
"# the midrank-percentile graph_score.",
|
|
264
|
+
"# Tuned together with the midrank-percentile graph_score in searchResults.ts.",
|
|
424
265
|
"ranking:",
|
|
425
|
-
" semantic: 0.
|
|
426
|
-
" graph: 0.
|
|
266
|
+
" semantic: 0.40",
|
|
267
|
+
" graph: 0.25",
|
|
427
268
|
" trust: 0.20",
|
|
428
269
|
" recency: 0.15",
|
|
429
270
|
"runtime:",
|
|
@@ -645,6 +486,10 @@ function installCodexAgentsSection(targetDir) {
|
|
|
645
486
|
const startMarker = "<!-- cortex:auto:start -->";
|
|
646
487
|
const endMarker = "<!-- cortex:auto:end -->";
|
|
647
488
|
const section = `## Cortex Auto Workflow
|
|
489
|
+
- Use the \`using-cortex\` skill if available; otherwise follow the commands below.
|
|
490
|
+
- Search before answering code questions: \`cortex search "<query>" --json\`; never answer from memory.
|
|
491
|
+
- Check \`cortex rules --json\` before suggesting changes and \`cortex impact "<query>" --json\` before refactors.
|
|
492
|
+
- Review changed files with \`cortex pattern-evidence <file> --json\` before finalizing.
|
|
648
493
|
- Run \`cortex update\` before completing substantial code changes.
|
|
649
494
|
- If background sync is enabled, check with \`cortex watch status\`.`;
|
|
650
495
|
const changed = upsertSectionByMarkers(agentsPath, startMarker, endMarker, section);
|
|
@@ -956,12 +801,31 @@ async function ensureProjectInitializedForMcp(targetDir) {
|
|
|
956
801
|
}
|
|
957
802
|
}
|
|
958
803
|
|
|
804
|
+
// The session bootstrap hook caches index status for 10 minutes; drop the
|
|
805
|
+
// cache whenever a command may have changed the index so new sessions never
|
|
806
|
+
// see a stale "no index"/"index is old" verdict.
|
|
807
|
+
const INDEX_MUTATING_COMMANDS = new Set(["bootstrap", "update", "refresh", "ingest", "embed", "graph-load"]);
|
|
808
|
+
|
|
809
|
+
function invalidateSessionStatusCache(cwd) {
|
|
810
|
+
try {
|
|
811
|
+
fs.rmSync(path.join(cwd, ".context", "cache", "session-status.json"), { force: true });
|
|
812
|
+
} catch {
|
|
813
|
+
// best effort: a stale cache only delays the status refresh
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
|
|
959
817
|
async function runContextCommand(cwd, contextArgs) {
|
|
960
818
|
const contextScript = path.join(cwd, CONTEXT_SCRIPTS_REL, "context.sh");
|
|
961
819
|
if (!fs.existsSync(contextScript)) {
|
|
962
820
|
throw new Error(`Missing ${contextScript}. Run 'cortex init' first.`);
|
|
963
821
|
}
|
|
964
|
-
|
|
822
|
+
try {
|
|
823
|
+
await runCommand("bash", [contextScript, ...contextArgs], cwd);
|
|
824
|
+
} finally {
|
|
825
|
+
if (INDEX_MUTATING_COMMANDS.has(contextArgs[0])) {
|
|
826
|
+
invalidateSessionStatusCache(cwd);
|
|
827
|
+
}
|
|
828
|
+
}
|
|
965
829
|
}
|
|
966
830
|
|
|
967
831
|
async function run() {
|
|
@@ -1588,7 +1452,7 @@ async function runEnterpriseInstall(args) {
|
|
|
1588
1452
|
}
|
|
1589
1453
|
|
|
1590
1454
|
const RUN_CLIS = new Set(["claude", "codex", "copilot"]);
|
|
1591
|
-
const QUERY_COMMANDS = new Set(["search", "related", "impact", "rules", "explain"]);
|
|
1455
|
+
const QUERY_COMMANDS = new Set(["search", "related", "impact", "rules", "explain", "pattern-evidence"]);
|
|
1592
1456
|
|
|
1593
1457
|
async function runRunCommand(args) {
|
|
1594
1458
|
const sub = args[0];
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@danielblomma/cortex-mcp",
|
|
3
3
|
"mcpName": "io.github.DanielBlomma/cortex",
|
|
4
|
-
"version": "2.
|
|
4
|
+
"version": "2.4.0",
|
|
5
5
|
"description": "Local, repo-scoped context platform for coding assistants. Semantic search, graph relationships, and architectural rule context.",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"author": "Daniel Blomma",
|
|
@@ -64,7 +64,7 @@
|
|
|
64
64
|
],
|
|
65
65
|
"scripts": {
|
|
66
66
|
"pretest": "test -d scaffold/scripts/parsers/node_modules || npm --prefix scaffold/scripts/parsers install --no-fund --no-update-notifier --silent",
|
|
67
|
-
"test": "node tests/context-regressions.test.mjs && node --test tests/ingest-units.test.mjs tests/ingest-parallel.test.mjs tests/ingest-worker-crash.test.mjs tests/javascript-parser.test.mjs tests/markdown-parser.test.mjs tests/sql-parser.test.mjs tests/config-parser.test.mjs tests/resources-parser.test.mjs tests/vbnet-parser.test.mjs tests/cpp-parser.test.mjs tests/dashboard.test.mjs tests/init-config.test.mjs tests/init-agents.test.mjs tests/multi-level.test.mjs tests/no-legacy-paths.test.mjs tests/tree-sitter-error-reporting.test.mjs tests/tree-sitter-body-cap.test.mjs tests/tree-sitter-exported.test.mjs tests/tree-sitter-robustness.test.mjs tests/bootstrapbench-run.test.mjs tests/bootstrapbench-stats.test.mjs tests/bootstrapbench-aggregate.test.mjs tests/bootstrapbench-cleanup.test.mjs tests/query-cli-shim.test.mjs",
|
|
67
|
+
"test": "node tests/context-regressions.test.mjs && node --test tests/ingest-units.test.mjs tests/ingest-parallel.test.mjs tests/ingest-worker-crash.test.mjs tests/javascript-parser.test.mjs tests/markdown-parser.test.mjs tests/sql-parser.test.mjs tests/config-parser.test.mjs tests/resources-parser.test.mjs tests/vbnet-parser.test.mjs tests/cpp-parser.test.mjs tests/dashboard.test.mjs tests/init-config.test.mjs tests/init-agents.test.mjs tests/multi-level.test.mjs tests/no-legacy-paths.test.mjs tests/tree-sitter-error-reporting.test.mjs tests/tree-sitter-body-cap.test.mjs tests/tree-sitter-exported.test.mjs tests/tree-sitter-robustness.test.mjs tests/bootstrapbench-run.test.mjs tests/bootstrapbench-stats.test.mjs tests/bootstrapbench-aggregate.test.mjs tests/bootstrapbench-cleanup.test.mjs tests/bootstrapbench-query-packs.test.mjs tests/query-cli-shim.test.mjs tests/session-bootstrap.test.mjs tests/plugin-skills.test.mjs tests/plugin-manifests.test.mjs",
|
|
68
68
|
"release:sync-version": "node scripts/sync-release-version.mjs",
|
|
69
69
|
"release:check-version-sync": "node scripts/sync-release-version.mjs --check",
|
|
70
70
|
"prepublishOnly": "echo 'Ready to publish to npm'"
|
|
@@ -742,9 +742,9 @@
|
|
|
742
742
|
"license": "BSD-3-Clause"
|
|
743
743
|
},
|
|
744
744
|
"node_modules/@protobufjs/eventemitter": {
|
|
745
|
-
"version": "1.1.
|
|
746
|
-
"resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.
|
|
747
|
-
"integrity": "sha512-
|
|
745
|
+
"version": "1.1.1",
|
|
746
|
+
"resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.1.tgz",
|
|
747
|
+
"integrity": "sha512-vW1GmwMZNnL+gMRaovlh9yZX74kc+TTU3FObkkurpMaRtBfLP3ldjS9KQWlwZgraRE0+dheEEoAxdzcJQ8eXZg==",
|
|
748
748
|
"license": "BSD-3-Clause"
|
|
749
749
|
},
|
|
750
750
|
"node_modules/@protobufjs/fetch": {
|
|
@@ -762,12 +762,6 @@
|
|
|
762
762
|
"integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==",
|
|
763
763
|
"license": "BSD-3-Clause"
|
|
764
764
|
},
|
|
765
|
-
"node_modules/@protobufjs/inquire": {
|
|
766
|
-
"version": "1.1.2",
|
|
767
|
-
"resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.2.tgz",
|
|
768
|
-
"integrity": "sha512-pa0vFRuws4wkvaXKK1uXZMAwAX4/t8ANaJo45iw/oQHNQ9q5xUzwgFmVJGXiga2BeN+zpX7Vf9vmsiIa2J+MUw==",
|
|
769
|
-
"license": "BSD-3-Clause"
|
|
770
|
-
},
|
|
771
765
|
"node_modules/@protobufjs/path": {
|
|
772
766
|
"version": "1.1.2",
|
|
773
767
|
"resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz",
|
|
@@ -1865,9 +1859,19 @@
|
|
|
1865
1859
|
}
|
|
1866
1860
|
},
|
|
1867
1861
|
"node_modules/js-yaml": {
|
|
1868
|
-
"version": "4.
|
|
1869
|
-
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.
|
|
1870
|
-
"integrity": "sha512-
|
|
1862
|
+
"version": "4.2.0",
|
|
1863
|
+
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.2.0.tgz",
|
|
1864
|
+
"integrity": "sha512-ePWsvanv0DWuDRsW8dnt+R4jQ31SCRCQ7hhNcPXZPsoBZiemuZNYGf7adZdqX2D86j6rvKp3RpCxVTSb8WQlOw==",
|
|
1865
|
+
"funding": [
|
|
1866
|
+
{
|
|
1867
|
+
"type": "github",
|
|
1868
|
+
"url": "https://github.com/sponsors/puzrin"
|
|
1869
|
+
},
|
|
1870
|
+
{
|
|
1871
|
+
"type": "github",
|
|
1872
|
+
"url": "https://github.com/sponsors/nodeca"
|
|
1873
|
+
}
|
|
1874
|
+
],
|
|
1871
1875
|
"license": "MIT",
|
|
1872
1876
|
"dependencies": {
|
|
1873
1877
|
"argparse": "^2.0.1"
|
|
@@ -2128,24 +2132,23 @@
|
|
|
2128
2132
|
"license": "MIT"
|
|
2129
2133
|
},
|
|
2130
2134
|
"node_modules/protobufjs": {
|
|
2131
|
-
"version": "7.
|
|
2132
|
-
"resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.
|
|
2133
|
-
"integrity": "sha512-
|
|
2135
|
+
"version": "7.6.4",
|
|
2136
|
+
"resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.6.4.tgz",
|
|
2137
|
+
"integrity": "sha512-RJJPTTpvFfHcWLkIa2JFWK4XvtSzS0yEWDmunqHXli1h3JlkbcQZXDZdcWxv+JK3Xsl5/UFDPZ0iGm7DAengYw==",
|
|
2134
2138
|
"hasInstallScript": true,
|
|
2135
2139
|
"license": "BSD-3-Clause",
|
|
2136
2140
|
"dependencies": {
|
|
2137
2141
|
"@protobufjs/aspromise": "^1.1.2",
|
|
2138
2142
|
"@protobufjs/base64": "^1.1.2",
|
|
2139
2143
|
"@protobufjs/codegen": "^2.0.5",
|
|
2140
|
-
"@protobufjs/eventemitter": "^1.1.
|
|
2144
|
+
"@protobufjs/eventemitter": "^1.1.1",
|
|
2141
2145
|
"@protobufjs/fetch": "^1.1.1",
|
|
2142
2146
|
"@protobufjs/float": "^1.0.2",
|
|
2143
|
-
"@protobufjs/inquire": "^1.1.2",
|
|
2144
2147
|
"@protobufjs/path": "^1.1.2",
|
|
2145
2148
|
"@protobufjs/pool": "^1.1.0",
|
|
2146
2149
|
"@protobufjs/utf8": "^1.1.1",
|
|
2147
2150
|
"@types/node": ">=13.7.0",
|
|
2148
|
-
"long": "^5.
|
|
2151
|
+
"long": "^5.3.2"
|
|
2149
2152
|
},
|
|
2150
2153
|
"engines": {
|
|
2151
2154
|
"node": ">=12.0.0"
|
|
@@ -2539,9 +2542,9 @@
|
|
|
2539
2542
|
}
|
|
2540
2543
|
},
|
|
2541
2544
|
"node_modules/tar": {
|
|
2542
|
-
"version": "7.5.
|
|
2543
|
-
"resolved": "https://registry.npmjs.org/tar/-/tar-7.5.
|
|
2544
|
-
"integrity": "sha512-
|
|
2545
|
+
"version": "7.5.16",
|
|
2546
|
+
"resolved": "https://registry.npmjs.org/tar/-/tar-7.5.16.tgz",
|
|
2547
|
+
"integrity": "sha512-56adEpPMouktRlBLXiaYFFzZ/3+JXa8P9n7WbR+ibIjtviN55mEaOkiysCnPnWm+7kkui1Dn8J9l+g6zV8731w==",
|
|
2545
2548
|
"license": "BlueOak-1.0.0",
|
|
2546
2549
|
"dependencies": {
|
|
2547
2550
|
"@isaacs/fs-minipass": "^4.0.0",
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { runContextRules } from "../rules.js";
|
|
2
|
+
import { runPatternEvidence } from "../patternEvidence.js";
|
|
2
3
|
import { runContextImpact, runContextRelated, runContextSearch } from "../search.js";
|
|
3
4
|
import type {
|
|
4
5
|
ImpactParams,
|
|
6
|
+
PatternEvidenceParams,
|
|
5
7
|
RelatedParams,
|
|
6
8
|
RelationType,
|
|
7
9
|
RulesParams,
|
|
@@ -29,7 +31,7 @@ type JsonEnvelope = {
|
|
|
29
31
|
};
|
|
30
32
|
};
|
|
31
33
|
|
|
32
|
-
const QUERY_COMMANDS = new Set(["search", "related", "impact", "rules", "explain"]);
|
|
34
|
+
const QUERY_COMMANDS = new Set(["search", "related", "impact", "rules", "explain", "pattern-evidence"]);
|
|
33
35
|
|
|
34
36
|
const ENTITY_ID_PREFIXES = [
|
|
35
37
|
"file:",
|
|
@@ -66,6 +68,8 @@ export async function runQueryCommand(args: string[]): Promise<void> {
|
|
|
66
68
|
return await runRules(rest);
|
|
67
69
|
case "explain":
|
|
68
70
|
return await runExplain(rest);
|
|
71
|
+
case "pattern-evidence":
|
|
72
|
+
return await runPatternEvidenceCommand(rest);
|
|
69
73
|
default:
|
|
70
74
|
throw new Error(`Unknown query command: ${command}`);
|
|
71
75
|
}
|
|
@@ -93,6 +97,7 @@ function printHelp(): void {
|
|
|
93
97
|
" cortex impact <query-or-entity-id> [--entity-id <id>] [--query <q>] [--depth <n>] [--top-k <n>] [--json]",
|
|
94
98
|
" cortex rules [--scope <scope>] [--include-inactive] [--json]",
|
|
95
99
|
" cortex explain <query-or-entity-id> [--top-k <n>] [--json]",
|
|
100
|
+
" cortex pattern-evidence <file-path|entity-id> [--query <text>] [--top-k <n>] [--json]",
|
|
96
101
|
"",
|
|
97
102
|
"These commands read the local Cortex graph and emit MCP-equivalent data with --json.",
|
|
98
103
|
];
|
|
@@ -157,7 +162,11 @@ function parsePositiveIntFlag(
|
|
|
157
162
|
if (typeof raw !== "string" || raw.trim().length === 0) {
|
|
158
163
|
throw new Error(`--${name} requires a numeric value`);
|
|
159
164
|
}
|
|
160
|
-
const
|
|
165
|
+
const normalized = raw.trim();
|
|
166
|
+
if (!/^[1-9]\d*$/.test(normalized)) {
|
|
167
|
+
throw new Error(`--${name} must be an integer from 1 to ${max}`);
|
|
168
|
+
}
|
|
169
|
+
const parsed = Number(normalized);
|
|
161
170
|
if (!Number.isInteger(parsed) || parsed <= 0 || parsed > max) {
|
|
162
171
|
throw new Error(`--${name} must be an integer from 1 to ${max}`);
|
|
163
172
|
}
|
|
@@ -453,3 +462,28 @@ async function runExplain(args: string[]): Promise<void> {
|
|
|
453
462
|
}
|
|
454
463
|
printSummary("explain", data);
|
|
455
464
|
}
|
|
465
|
+
|
|
466
|
+
async function runPatternEvidenceCommand(args: string[]): Promise<void> {
|
|
467
|
+
const { flags, rest } = parseArgs(args);
|
|
468
|
+
const target = optionalString(flags, "target") ?? positionalText(rest, "file-path-or-entity-id");
|
|
469
|
+
const input: PatternEvidenceParams = {
|
|
470
|
+
target,
|
|
471
|
+
query: optionalString(flags, "query"),
|
|
472
|
+
top_k: parsePositiveIntFlag(flags, "top-k", 3, 10),
|
|
473
|
+
include_deprecated: isFlagEnabled(flags, "include-deprecated"),
|
|
474
|
+
};
|
|
475
|
+
const data = await runPatternEvidence(input);
|
|
476
|
+
if (isFlagEnabled(flags, "json")) {
|
|
477
|
+
emitEnvelope("pattern-evidence", input, data);
|
|
478
|
+
return;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
const tiers = Array.isArray(data.tiers) ? data.tiers : [];
|
|
482
|
+
process.stdout.write(`pattern-evidence: local_pattern_found=${String(data.local_pattern_found)}\n`);
|
|
483
|
+
for (const value of tiers) {
|
|
484
|
+
if (!value || typeof value !== "object") continue;
|
|
485
|
+
const tier = value as Record<string, unknown>;
|
|
486
|
+
const evidence = Array.isArray(tier.evidence) ? tier.evidence : [];
|
|
487
|
+
process.stdout.write(`- ${String(tier.name ?? "")}: ${evidence.length}\n`);
|
|
488
|
+
}
|
|
489
|
+
}
|