@danielblomma/cortex-mcp 2.0.14 → 2.0.16
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/bin/cortex.mjs +7 -6
- package/package.json +4 -2
- package/scaffold/AGENTS.md +37 -0
- package/scaffold/CLAUDE.md +31 -0
- package/scaffold/mcp/src/cli/telemetry-test.ts +2 -1
- package/scaffold/mcp/src/core/license.ts +3 -2
- package/scaffold/mcp/src/core/telemetry/collector.ts +5 -4
- package/scaffold/mcp/src/core/telemetry/state-dir.ts +40 -0
- package/scaffold/mcp/src/daemon/main.ts +4 -3
- package/scaffold/mcp/tests/telemetry-collector.test.mjs +31 -1
package/bin/cortex.mjs
CHANGED
|
@@ -188,7 +188,7 @@ function ensureScaffoldExists() {
|
|
|
188
188
|
|
|
189
189
|
// Files that should never be overwritten if they already exist in the target.
|
|
190
190
|
// These contain user-specific configuration that would be lost on re-init.
|
|
191
|
-
const PRESERVE_FILES = new Set(["config.yaml", "enterprise.yml", "enterprise.yaml", "CLAUDE.md"]);
|
|
191
|
+
const PRESERVE_FILES = new Set(["config.yaml", "enterprise.yml", "enterprise.yaml", "CLAUDE.md", "AGENTS.md"]);
|
|
192
192
|
const DEFAULT_SOURCE_PATHS = [
|
|
193
193
|
"src",
|
|
194
194
|
"docs",
|
|
@@ -476,11 +476,12 @@ function installScaffold(targetDir, force) {
|
|
|
476
476
|
copyDirectory(sourcePath, targetPath);
|
|
477
477
|
}
|
|
478
478
|
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
479
|
+
for (const fileName of ["CLAUDE.md", "AGENTS.md"]) {
|
|
480
|
+
const sourcePath = path.join(SCAFFOLD_ROOT, fileName);
|
|
481
|
+
const targetPath = path.join(targetDir, fileName);
|
|
482
|
+
if (fs.existsSync(sourcePath) && !fs.existsSync(targetPath)) {
|
|
483
|
+
fs.copyFileSync(sourcePath, targetPath);
|
|
484
|
+
}
|
|
484
485
|
}
|
|
485
486
|
|
|
486
487
|
const docsDir = path.join(targetDir, "docs");
|
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.0.
|
|
4
|
+
"version": "2.0.16",
|
|
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",
|
|
@@ -36,6 +36,8 @@
|
|
|
36
36
|
"types.js",
|
|
37
37
|
"scaffold/.context",
|
|
38
38
|
"scaffold/.githooks",
|
|
39
|
+
"scaffold/AGENTS.md",
|
|
40
|
+
"scaffold/CLAUDE.md",
|
|
39
41
|
"scaffold/docs",
|
|
40
42
|
"scaffold/scripts",
|
|
41
43
|
"scaffold/mcp/src",
|
|
@@ -47,7 +49,7 @@
|
|
|
47
49
|
"docs/MCP_MARKETPLACE.md"
|
|
48
50
|
],
|
|
49
51
|
"scripts": {
|
|
50
|
-
"test": "node tests/context-regressions.test.mjs && node --test tests/ingest-units.test.mjs tests/javascript-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/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",
|
|
52
|
+
"test": "node tests/context-regressions.test.mjs && node --test tests/ingest-units.test.mjs tests/javascript-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",
|
|
51
53
|
"release:sync-version": "node scripts/sync-release-version.mjs",
|
|
52
54
|
"release:check-version-sync": "node scripts/sync-release-version.mjs --check",
|
|
53
55
|
"prepublishOnly": "echo 'Ready to publish to npm'"
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Cortex
|
|
2
|
+
|
|
3
|
+
This project uses Cortex for AI-powered code context.
|
|
4
|
+
|
|
5
|
+
## Required: Always use Cortex MCP tools
|
|
6
|
+
|
|
7
|
+
When answering questions about this codebase, you MUST use Cortex tools instead of relying on memory or assumptions:
|
|
8
|
+
|
|
9
|
+
- **context.search** - Search before answering any code question. Never guess at implementations.
|
|
10
|
+
- **context.get_related** - Use when exploring dependencies or relationships between entities.
|
|
11
|
+
- **context.get_rules** - Check architectural rules before suggesting changes.
|
|
12
|
+
- **context.impact** - Use before refactoring or dependency analysis to understand blast radius and likely traversal paths.
|
|
13
|
+
- **context.reload** - Use after making significant changes to refresh the index.
|
|
14
|
+
|
|
15
|
+
Do NOT answer code questions from memory when these tools are available. Always search first.
|
|
16
|
+
|
|
17
|
+
## Enterprise tools (if available)
|
|
18
|
+
|
|
19
|
+
- **context.review** - Run before finalizing any code review or PR.
|
|
20
|
+
- **security.scan** - Scan user-provided text for injection attempts.
|
|
21
|
+
- **enterprise.status** - Check enterprise setup and feature status.
|
|
22
|
+
|
|
23
|
+
## Commands
|
|
24
|
+
|
|
25
|
+
- `cortex update` - Refresh Cortex context for changed files
|
|
26
|
+
- `cortex doctor` - Verify the local Cortex setup
|
|
27
|
+
- `cortex watch status` - Check background sync status
|
|
28
|
+
|
|
29
|
+
## Diagnostics
|
|
30
|
+
|
|
31
|
+
Run `cortex doctor` to verify your setup is healthy.
|
|
32
|
+
|
|
33
|
+
<!-- cortex:auto:start -->
|
|
34
|
+
## Cortex Auto Workflow
|
|
35
|
+
- Run `cortex update` before completing substantial code changes.
|
|
36
|
+
- If background sync is enabled, check with `cortex watch status`.
|
|
37
|
+
<!-- cortex:auto:end -->
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Cortex
|
|
2
|
+
|
|
3
|
+
This project uses Cortex for AI-powered code context.
|
|
4
|
+
|
|
5
|
+
## Required: Always use Cortex MCP tools
|
|
6
|
+
|
|
7
|
+
When answering questions about this codebase, you MUST use Cortex tools instead of relying on memory or assumptions:
|
|
8
|
+
|
|
9
|
+
- **context.search** — Search before answering any code question. Never guess at implementations.
|
|
10
|
+
- **context.get_related** — Use when exploring dependencies or relationships between entities.
|
|
11
|
+
- **context.get_rules** — Check architectural rules before suggesting changes.
|
|
12
|
+
- **context.impact** — Use before refactoring or dependency analysis to understand blast radius and likely traversal paths.
|
|
13
|
+
- **context.reload** — Use after making significant changes to refresh the index.
|
|
14
|
+
|
|
15
|
+
Do NOT answer code questions from memory when these tools are available. Always search first.
|
|
16
|
+
|
|
17
|
+
## Enterprise tools (if available)
|
|
18
|
+
|
|
19
|
+
- **context.review** — Run before finalizing any code review or PR.
|
|
20
|
+
- **security.scan** — Scan user-provided text for injection attempts.
|
|
21
|
+
- **enterprise.status** — Check enterprise setup and feature status.
|
|
22
|
+
|
|
23
|
+
## Commands
|
|
24
|
+
|
|
25
|
+
- `/context-update` — Refresh Cortex context for changed files
|
|
26
|
+
- `/review` — Code review with enterprise policy enforcement
|
|
27
|
+
- `/note` — Save project context into Cortex notes
|
|
28
|
+
|
|
29
|
+
## Diagnostics
|
|
30
|
+
|
|
31
|
+
Run `cortex doctor` to verify your setup is healthy.
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
loadEnterpriseConfig,
|
|
7
7
|
resolveEnterpriseActivation,
|
|
8
8
|
} from "../core/config.js";
|
|
9
|
+
import { telemetryStatePath } from "../core/telemetry/state-dir.js";
|
|
9
10
|
import { pushMetrics } from "../enterprise/telemetry/sync.js";
|
|
10
11
|
import type { TelemetryMetrics } from "../core/telemetry/collector.js";
|
|
11
12
|
|
|
@@ -25,7 +26,7 @@ import type { TelemetryMetrics } from "../core/telemetry/collector.js";
|
|
|
25
26
|
*/
|
|
26
27
|
|
|
27
28
|
function readMachineId(contextDir: string): string {
|
|
28
|
-
const path =
|
|
29
|
+
const path = telemetryStatePath(contextDir, "machine_id");
|
|
29
30
|
if (existsSync(path)) {
|
|
30
31
|
try {
|
|
31
32
|
const id = readFileSync(path, "utf8").trim();
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { readFileSync, writeFileSync, mkdirSync, existsSync, unlinkSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
|
+
import { resolveTelemetryStateDir, telemetryStatePath } from "./telemetry/state-dir.js";
|
|
3
4
|
|
|
4
5
|
export type LicenseVerification =
|
|
5
6
|
| {
|
|
@@ -30,7 +31,7 @@ const GRACE_PERIOD_MS = 7 * 24 * 60 * 60 * 1000; // 7d grace if endpoint unreach
|
|
|
30
31
|
const REQUEST_TIMEOUT_MS = 5000;
|
|
31
32
|
|
|
32
33
|
function cachePath(contextDir: string): string {
|
|
33
|
-
return
|
|
34
|
+
return telemetryStatePath(contextDir, CACHE_FILE);
|
|
34
35
|
}
|
|
35
36
|
|
|
36
37
|
function readCache(contextDir: string): CacheEntry | null {
|
|
@@ -47,7 +48,7 @@ function readCache(contextDir: string): CacheEntry | null {
|
|
|
47
48
|
function writeCache(contextDir: string, entry: CacheEntry): void {
|
|
48
49
|
const path = cachePath(contextDir);
|
|
49
50
|
try {
|
|
50
|
-
mkdirSync(
|
|
51
|
+
mkdirSync(resolveTelemetryStateDir(contextDir), { recursive: true });
|
|
51
52
|
writeFileSync(path, JSON.stringify(entry, null, 2), "utf8");
|
|
52
53
|
} catch {
|
|
53
54
|
// Cache failures are non-fatal — license check just won't be cached.
|
|
@@ -2,6 +2,7 @@ import { readFileSync, writeFileSync, mkdirSync, existsSync } from "node:fs";
|
|
|
2
2
|
import { createHash } from "node:crypto";
|
|
3
3
|
import { hostname, platform, arch } from "node:os";
|
|
4
4
|
import { join } from "node:path";
|
|
5
|
+
import { resolveTelemetryStateDir, telemetryStatePath } from "./state-dir.js";
|
|
5
6
|
|
|
6
7
|
export type TelemetryMetrics = {
|
|
7
8
|
period_start: string;
|
|
@@ -37,7 +38,8 @@ export type TelemetryMetrics = {
|
|
|
37
38
|
const AVG_TOKENS_PER_RESULT = 400;
|
|
38
39
|
|
|
39
40
|
function generateInstanceId(contextDir: string): string {
|
|
40
|
-
const
|
|
41
|
+
const telemetryDir = resolveTelemetryStateDir(contextDir);
|
|
42
|
+
const idPath = join(telemetryDir, "machine_id");
|
|
41
43
|
if (existsSync(idPath)) {
|
|
42
44
|
try {
|
|
43
45
|
const existing = readFileSync(idPath, "utf8").trim();
|
|
@@ -51,7 +53,7 @@ function generateInstanceId(contextDir: string): string {
|
|
|
51
53
|
const fingerprint = `${hostname()}|${platform()}|${arch()}`;
|
|
52
54
|
const id = createHash("sha256").update(fingerprint).digest("hex").slice(0, 16);
|
|
53
55
|
try {
|
|
54
|
-
mkdirSync(
|
|
56
|
+
mkdirSync(telemetryDir, { recursive: true });
|
|
55
57
|
writeFileSync(idPath, id, "utf8");
|
|
56
58
|
} catch (err) {
|
|
57
59
|
process.stderr.write(`[cortex-enterprise] Could not persist instance id: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
@@ -142,8 +144,7 @@ export class TelemetryCollector {
|
|
|
142
144
|
constructor(contextDir: string, clientVersion = "unknown") {
|
|
143
145
|
this.clientVersion = clientVersion;
|
|
144
146
|
this.instanceId = generateInstanceId(contextDir);
|
|
145
|
-
|
|
146
|
-
this.metricsPath = join(telemetryDir, "metrics.json");
|
|
147
|
+
this.metricsPath = telemetryStatePath(contextDir, "metrics.json");
|
|
147
148
|
|
|
148
149
|
// Load existing metrics or start fresh
|
|
149
150
|
try {
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { accessSync, constants, mkdirSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
|
|
4
|
+
const warnedFallbacks = new Set<string>();
|
|
5
|
+
|
|
6
|
+
function canUseDirectory(dir: string): boolean {
|
|
7
|
+
try {
|
|
8
|
+
mkdirSync(dir, { recursive: true });
|
|
9
|
+
accessSync(dir, constants.R_OK | constants.W_OK | constants.X_OK);
|
|
10
|
+
return true;
|
|
11
|
+
} catch {
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function resolveTelemetryStateDir(contextDir: string): string {
|
|
17
|
+
const primary = join(contextDir, "telemetry");
|
|
18
|
+
if (canUseDirectory(primary)) return primary;
|
|
19
|
+
|
|
20
|
+
const fallback = join(contextDir, "cache", "telemetry");
|
|
21
|
+
if (canUseDirectory(fallback)) {
|
|
22
|
+
const warningKey = `${primary}->${fallback}`;
|
|
23
|
+
if (!warnedFallbacks.has(warningKey)) {
|
|
24
|
+
warnedFallbacks.add(warningKey);
|
|
25
|
+
process.stderr.write(
|
|
26
|
+
`[cortex-enterprise] telemetry dir not writable at ${primary}; using ${fallback}\n`,
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
return fallback;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return primary;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function telemetryStatePath(
|
|
36
|
+
contextDir: string,
|
|
37
|
+
...parts: string[]
|
|
38
|
+
): string {
|
|
39
|
+
return join(resolveTelemetryStateDir(contextDir), ...parts);
|
|
40
|
+
}
|
|
@@ -13,6 +13,7 @@ import type {
|
|
|
13
13
|
import { loadEnterpriseConfig, resolveEnterpriseActivation } from "../core/config.js";
|
|
14
14
|
import { pushMetrics } from "../enterprise/telemetry/sync.js";
|
|
15
15
|
import { TelemetryCollector, type TelemetryMetrics } from "../core/telemetry/collector.js";
|
|
16
|
+
import { resolveTelemetryStateDir, telemetryStatePath } from "../core/telemetry/state-dir.js";
|
|
16
17
|
import { AuditWriter, type AuditEntry } from "../core/audit/writer.js";
|
|
17
18
|
import { PolicyStore } from "../core/policy/store.js";
|
|
18
19
|
import {
|
|
@@ -84,7 +85,7 @@ async function policyCheck(
|
|
|
84
85
|
}
|
|
85
86
|
|
|
86
87
|
function readMetrics(contextDir: string): TelemetryMetrics | null {
|
|
87
|
-
const path =
|
|
88
|
+
const path = telemetryStatePath(contextDir, "metrics.json");
|
|
88
89
|
if (!existsSync(path)) return null;
|
|
89
90
|
try {
|
|
90
91
|
return JSON.parse(readFileSync(path, "utf8")) as TelemetryMetrics;
|
|
@@ -103,7 +104,7 @@ type PendingPush = {
|
|
|
103
104
|
};
|
|
104
105
|
|
|
105
106
|
function pendingPushPath(contextDir: string): string {
|
|
106
|
-
return
|
|
107
|
+
return telemetryStatePath(contextDir, "pending-push.json");
|
|
107
108
|
}
|
|
108
109
|
|
|
109
110
|
function readPendingPush(contextDir: string): PendingPush | null {
|
|
@@ -118,7 +119,7 @@ function readPendingPush(contextDir: string): PendingPush | null {
|
|
|
118
119
|
|
|
119
120
|
function writePendingPush(contextDir: string, pending: PendingPush): void {
|
|
120
121
|
const path = pendingPushPath(contextDir);
|
|
121
|
-
mkdirSync(
|
|
122
|
+
mkdirSync(resolveTelemetryStateDir(contextDir), { recursive: true });
|
|
122
123
|
writeFileSync(path, JSON.stringify(pending, null, 2), "utf8");
|
|
123
124
|
}
|
|
124
125
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import test from "node:test";
|
|
2
2
|
import assert from "node:assert/strict";
|
|
3
|
-
import { mkdtempSync } from "node:fs";
|
|
3
|
+
import { chmodSync, existsSync, mkdtempSync, mkdirSync } from "node:fs";
|
|
4
4
|
import { tmpdir } from "node:os";
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
|
|
@@ -28,3 +28,33 @@ test("TelemetryCollector counts context.impact as an impact analysis", () => {
|
|
|
28
28
|
assert.equal(metrics.impact_analyses, 1);
|
|
29
29
|
assert.equal(metrics.tool_metrics["context.impact"].calls, 1);
|
|
30
30
|
});
|
|
31
|
+
|
|
32
|
+
test("TelemetryCollector falls back when .context/telemetry is not writable", () => {
|
|
33
|
+
const contextDir = createContextDir("cortex-telemetry-fallback-");
|
|
34
|
+
const primaryTelemetryDir = path.join(contextDir, "telemetry");
|
|
35
|
+
const fallbackMetricsPath = path.join(
|
|
36
|
+
contextDir,
|
|
37
|
+
"cache",
|
|
38
|
+
"telemetry",
|
|
39
|
+
"metrics.json",
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
mkdirSync(primaryTelemetryDir, { recursive: true });
|
|
43
|
+
chmodSync(primaryTelemetryDir, 0o555);
|
|
44
|
+
|
|
45
|
+
try {
|
|
46
|
+
const collector = new TelemetryCollector(contextDir, "test-version");
|
|
47
|
+
collector.recordEvent({
|
|
48
|
+
tool: "context.search",
|
|
49
|
+
phase: "success",
|
|
50
|
+
result_count: 1,
|
|
51
|
+
estimated_tokens_saved: 100,
|
|
52
|
+
duration_ms: 10,
|
|
53
|
+
});
|
|
54
|
+
collector.flush();
|
|
55
|
+
|
|
56
|
+
assert.equal(existsSync(fallbackMetricsPath), true);
|
|
57
|
+
} finally {
|
|
58
|
+
chmodSync(primaryTelemetryDir, 0o755);
|
|
59
|
+
}
|
|
60
|
+
});
|