@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 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
- // Copy CLAUDE.md (skip if already exists to preserve user edits)
480
- const claudeMdSource = path.join(SCAFFOLD_ROOT, "CLAUDE.md");
481
- const claudeMdTarget = path.join(targetDir, "CLAUDE.md");
482
- if (fs.existsSync(claudeMdSource) && !fs.existsSync(claudeMdTarget)) {
483
- fs.copyFileSync(claudeMdSource, claudeMdTarget);
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.14",
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 = join(contextDir, "telemetry", "machine_id");
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 join(contextDir, "telemetry", CACHE_FILE);
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(join(contextDir, "telemetry"), { recursive: true });
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 idPath = join(contextDir, "telemetry", "machine_id");
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(join(contextDir, "telemetry"), { recursive: true });
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
- const telemetryDir = join(contextDir, "telemetry");
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 = join(contextDir, "telemetry", "metrics.json");
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 join(contextDir, "telemetry", "pending-push.json");
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(join(contextDir, "telemetry"), { recursive: true });
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
+ });