@danielblomma/cortex-mcp 1.7.1 → 2.0.2

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.
Files changed (79) hide show
  1. package/bin/cortex.mjs +679 -32
  2. package/bin/style.mjs +349 -0
  3. package/package.json +4 -3
  4. package/scaffold/mcp/package-lock.json +834 -671
  5. package/scaffold/mcp/package.json +1 -1
  6. package/scaffold/mcp/src/cli/enterprise-setup.ts +124 -0
  7. package/scaffold/mcp/src/cli/govern.ts +987 -0
  8. package/scaffold/mcp/src/cli/run.ts +306 -0
  9. package/scaffold/mcp/src/cli/telemetry-test.ts +158 -0
  10. package/scaffold/mcp/src/cli/ungoverned-detector.ts +168 -0
  11. package/scaffold/mcp/src/core/audit/query.ts +81 -0
  12. package/scaffold/mcp/src/core/audit/writer.ts +68 -0
  13. package/scaffold/mcp/src/core/config.ts +329 -0
  14. package/scaffold/mcp/src/core/index.ts +34 -0
  15. package/scaffold/mcp/src/core/license.ts +202 -0
  16. package/scaffold/mcp/src/core/policy/enforce.ts +98 -0
  17. package/scaffold/mcp/src/core/policy/injection.ts +229 -0
  18. package/scaffold/mcp/src/core/policy/store.ts +197 -0
  19. package/scaffold/mcp/src/core/rbac/check.ts +40 -0
  20. package/scaffold/mcp/src/core/telemetry/collector.ts +234 -0
  21. package/scaffold/mcp/src/core/validators/builtins.ts +711 -0
  22. package/scaffold/mcp/src/core/validators/config.ts +47 -0
  23. package/scaffold/mcp/src/core/validators/engine.ts +199 -0
  24. package/scaffold/mcp/src/core/validators/evaluators/code_comments.ts +294 -0
  25. package/scaffold/mcp/src/core/validators/evaluators/regex.ts +144 -0
  26. package/scaffold/mcp/src/daemon/client.ts +155 -0
  27. package/scaffold/mcp/src/daemon/egress-proxy.ts +331 -0
  28. package/scaffold/mcp/src/daemon/heartbeat-pusher.ts +147 -0
  29. package/scaffold/mcp/src/daemon/heartbeat-tracker.ts +223 -0
  30. package/scaffold/mcp/src/daemon/host-events-pusher.ts +285 -0
  31. package/scaffold/mcp/src/daemon/main.ts +300 -0
  32. package/scaffold/mcp/src/daemon/paths.ts +41 -0
  33. package/scaffold/mcp/src/daemon/protocol.ts +101 -0
  34. package/scaffold/mcp/src/daemon/server.ts +227 -0
  35. package/scaffold/mcp/src/daemon/sync-checker.ts +213 -0
  36. package/scaffold/mcp/src/daemon/ungoverned-scanner.ts +149 -0
  37. package/scaffold/mcp/src/embed.ts +1 -1
  38. package/scaffold/mcp/src/embeddings.ts +1 -1
  39. package/scaffold/mcp/src/enterprise/audit/push.ts +84 -0
  40. package/scaffold/mcp/src/enterprise/index.ts +415 -0
  41. package/scaffold/mcp/src/enterprise/model/deploy.ts +33 -0
  42. package/scaffold/mcp/src/enterprise/policy/sync.ts +146 -0
  43. package/scaffold/mcp/src/enterprise/privacy/boundary.ts +212 -0
  44. package/scaffold/mcp/src/enterprise/reviews/push.ts +79 -0
  45. package/scaffold/mcp/src/enterprise/telemetry/sync.ts +72 -0
  46. package/scaffold/mcp/src/enterprise/tools/enterprise.ts +1031 -0
  47. package/scaffold/mcp/src/enterprise/tools/walk.ts +79 -0
  48. package/scaffold/mcp/src/enterprise/violations/push.ts +102 -0
  49. package/scaffold/mcp/src/enterprise/workflow/push.ts +60 -0
  50. package/scaffold/mcp/src/enterprise/workflow/state.ts +535 -0
  51. package/scaffold/mcp/src/hooks/pre-compact.ts +54 -0
  52. package/scaffold/mcp/src/hooks/pre-tool-use.ts +96 -0
  53. package/scaffold/mcp/src/hooks/session-end.ts +73 -0
  54. package/scaffold/mcp/src/hooks/session-start.ts +78 -0
  55. package/scaffold/mcp/src/hooks/shared.ts +134 -0
  56. package/scaffold/mcp/src/hooks/stop.ts +60 -0
  57. package/scaffold/mcp/src/hooks/user-prompt-submit.ts +64 -0
  58. package/scaffold/mcp/src/plugin.ts +150 -0
  59. package/scaffold/mcp/src/server.ts +218 -7
  60. package/scaffold/mcp/tests/copilot-shim.test.mjs +146 -0
  61. package/scaffold/mcp/tests/daemon-client.test.mjs +32 -0
  62. package/scaffold/mcp/tests/egress-proxy.test.mjs +239 -0
  63. package/scaffold/mcp/tests/enterprise-config.test.mjs +154 -0
  64. package/scaffold/mcp/tests/govern-install.test.mjs +320 -0
  65. package/scaffold/mcp/tests/govern-repair.test.mjs +157 -0
  66. package/scaffold/mcp/tests/govern-status.test.mjs +538 -0
  67. package/scaffold/mcp/tests/govern.test.mjs +74 -0
  68. package/scaffold/mcp/tests/heartbeat-pusher.test.mjs +154 -0
  69. package/scaffold/mcp/tests/heartbeat-tracker.test.mjs +237 -0
  70. package/scaffold/mcp/tests/host-events-pusher.test.mjs +347 -0
  71. package/scaffold/mcp/tests/policy-check.test.mjs +220 -0
  72. package/scaffold/mcp/tests/repo-name.test.mjs +134 -0
  73. package/scaffold/mcp/tests/run.test.mjs +109 -0
  74. package/scaffold/mcp/tests/sync-checker.test.mjs +188 -0
  75. package/scaffold/mcp/tests/ungoverned-detector.test.mjs +191 -0
  76. package/scaffold/mcp/tests/ungoverned-scanner.test.mjs +198 -0
  77. package/scaffold/scripts/bootstrap.sh +0 -11
  78. package/scaffold/scripts/doctor.sh +24 -4
  79. package/types.js +5 -0
@@ -0,0 +1,234 @@
1
+ import { readFileSync, writeFileSync, mkdirSync, existsSync } from "node:fs";
2
+ import { createHash } from "node:crypto";
3
+ import { hostname, platform, arch } from "node:os";
4
+ import { join } from "node:path";
5
+
6
+ export type TelemetryMetrics = {
7
+ period_start: string;
8
+ period_end: string;
9
+ total_tool_calls: number;
10
+ successful_tool_calls: number;
11
+ failed_tool_calls: number;
12
+ total_duration_ms: number;
13
+ session_starts: number;
14
+ session_ends: number;
15
+ session_duration_ms_total: number;
16
+ searches: number;
17
+ related_lookups: number;
18
+ caller_lookups: number;
19
+ trace_lookups: number;
20
+ impact_analyses: number;
21
+ rule_lookups: number;
22
+ reloads: number;
23
+ total_results_returned: number;
24
+ estimated_tokens_saved: number;
25
+ estimated_tokens_total: number;
26
+ client_version: string;
27
+ instance_id: string;
28
+ tool_metrics: Record<string, {
29
+ calls: number;
30
+ failures: number;
31
+ total_duration_ms: number;
32
+ total_results_returned: number;
33
+ estimated_tokens_saved: number;
34
+ }>;
35
+ };
36
+
37
+ const AVG_TOKENS_PER_RESULT = 400;
38
+
39
+ function generateInstanceId(contextDir: string): string {
40
+ const idPath = join(contextDir, "telemetry", "machine_id");
41
+ if (existsSync(idPath)) {
42
+ try {
43
+ const existing = readFileSync(idPath, "utf8").trim();
44
+ if (existing.length > 0) return existing;
45
+ } catch (err) {
46
+ process.stderr.write(`[cortex-enterprise] machine_id exists but is unreadable: ${err instanceof Error ? err.message : String(err)}\n`);
47
+ }
48
+ }
49
+ // Note: hostname|platform|arch may collide on machines with identical defaults.
50
+ // Consider adding a random salt if fleet-wide uniqueness is critical.
51
+ const fingerprint = `${hostname()}|${platform()}|${arch()}`;
52
+ const id = createHash("sha256").update(fingerprint).digest("hex").slice(0, 16);
53
+ try {
54
+ mkdirSync(join(contextDir, "telemetry"), { recursive: true });
55
+ writeFileSync(idPath, id, "utf8");
56
+ } catch (err) {
57
+ process.stderr.write(`[cortex-enterprise] Could not persist instance id: ${err instanceof Error ? err.message : String(err)}\n`);
58
+ }
59
+ return id;
60
+ }
61
+
62
+ function emptyMetrics(clientVersion: string, instanceId: string): TelemetryMetrics {
63
+ const now = new Date().toISOString();
64
+ return {
65
+ period_start: now,
66
+ period_end: now,
67
+ total_tool_calls: 0,
68
+ successful_tool_calls: 0,
69
+ failed_tool_calls: 0,
70
+ total_duration_ms: 0,
71
+ session_starts: 0,
72
+ session_ends: 0,
73
+ session_duration_ms_total: 0,
74
+ searches: 0,
75
+ related_lookups: 0,
76
+ caller_lookups: 0,
77
+ trace_lookups: 0,
78
+ impact_analyses: 0,
79
+ rule_lookups: 0,
80
+ reloads: 0,
81
+ total_results_returned: 0,
82
+ estimated_tokens_saved: 0,
83
+ estimated_tokens_total: 0,
84
+ client_version: clientVersion,
85
+ instance_id: instanceId,
86
+ tool_metrics: {},
87
+ };
88
+ }
89
+
90
+ export type TelemetryEvent = {
91
+ tool: string;
92
+ phase: "success" | "error";
93
+ result_count?: number;
94
+ estimated_tokens_saved?: number;
95
+ duration_ms?: number;
96
+ };
97
+
98
+ export class TelemetryCollector {
99
+ private metrics: TelemetryMetrics;
100
+ private readonly metricsPath: string;
101
+ private readonly clientVersion: string;
102
+ private readonly instanceId: string;
103
+ private dirty = false;
104
+
105
+ constructor(contextDir: string, clientVersion = "unknown") {
106
+ this.clientVersion = clientVersion;
107
+ this.instanceId = generateInstanceId(contextDir);
108
+ const telemetryDir = join(contextDir, "telemetry");
109
+ this.metricsPath = join(telemetryDir, "metrics.json");
110
+
111
+ // Load existing metrics or start fresh
112
+ try {
113
+ const raw = readFileSync(this.metricsPath, "utf8");
114
+ this.metrics = JSON.parse(raw);
115
+ this.metrics.client_version = clientVersion;
116
+ this.metrics.instance_id = this.instanceId;
117
+ } catch {
118
+ this.metrics = emptyMetrics(clientVersion, this.instanceId);
119
+ }
120
+ }
121
+
122
+ private bucket(toolName: string) {
123
+ if (!this.metrics.tool_metrics[toolName]) {
124
+ this.metrics.tool_metrics[toolName] = {
125
+ calls: 0,
126
+ failures: 0,
127
+ total_duration_ms: 0,
128
+ total_results_returned: 0,
129
+ estimated_tokens_saved: 0,
130
+ };
131
+ }
132
+ return this.metrics.tool_metrics[toolName];
133
+ }
134
+
135
+ recordEvent(event: TelemetryEvent): void {
136
+ const resultCount = event.result_count ?? 0;
137
+ const tokensSaved = event.estimated_tokens_saved ?? 0;
138
+ const durationMs = event.duration_ms ?? 0;
139
+ const toolBucket = this.bucket(event.tool);
140
+
141
+ this.metrics.total_tool_calls++;
142
+ this.metrics.total_duration_ms += durationMs;
143
+ this.metrics.period_end = new Date().toISOString();
144
+
145
+ toolBucket.calls++;
146
+ toolBucket.total_duration_ms += durationMs;
147
+
148
+ if (event.phase === "error") {
149
+ this.metrics.failed_tool_calls++;
150
+ toolBucket.failures++;
151
+ this.dirty = true;
152
+ return;
153
+ }
154
+
155
+ this.metrics.successful_tool_calls++;
156
+
157
+ switch (event.tool) {
158
+ case "context.search":
159
+ this.metrics.searches++;
160
+ break;
161
+ case "context.get_related":
162
+ this.metrics.related_lookups++;
163
+ break;
164
+ case "context.find_callers":
165
+ this.metrics.caller_lookups++;
166
+ break;
167
+ case "context.trace_calls":
168
+ this.metrics.trace_lookups++;
169
+ break;
170
+ case "context.impact_analysis":
171
+ this.metrics.impact_analyses++;
172
+ break;
173
+ case "context.get_rules":
174
+ this.metrics.rule_lookups++;
175
+ break;
176
+ case "context.reload":
177
+ this.metrics.reloads++;
178
+ break;
179
+ }
180
+
181
+ this.metrics.total_results_returned += resultCount;
182
+ this.metrics.estimated_tokens_saved += tokensSaved;
183
+ this.metrics.estimated_tokens_total += tokensSaved + resultCount * AVG_TOKENS_PER_RESULT;
184
+
185
+ toolBucket.total_results_returned += resultCount;
186
+ toolBucket.estimated_tokens_saved += tokensSaved;
187
+ this.dirty = true;
188
+ }
189
+
190
+ record(toolName: string, resultCount: number, tokensSaved: number): void {
191
+ this.recordEvent({
192
+ tool: toolName,
193
+ phase: "success",
194
+ result_count: resultCount,
195
+ estimated_tokens_saved: tokensSaved,
196
+ duration_ms: 0,
197
+ });
198
+ }
199
+
200
+ recordSessionStart(): void {
201
+ this.metrics.session_starts++;
202
+ this.metrics.period_end = new Date().toISOString();
203
+ this.dirty = true;
204
+ }
205
+
206
+ recordSessionEnd(durationMs: number): void {
207
+ this.metrics.session_ends++;
208
+ this.metrics.session_duration_ms_total += Math.max(0, durationMs);
209
+ this.metrics.period_end = new Date().toISOString();
210
+ this.dirty = true;
211
+ }
212
+
213
+ getMetrics(): TelemetryMetrics {
214
+ return { ...this.metrics };
215
+ }
216
+
217
+ flush(): void {
218
+ if (!this.dirty) return;
219
+
220
+ try {
221
+ const dir = join(this.metricsPath, "..");
222
+ mkdirSync(dir, { recursive: true });
223
+ writeFileSync(this.metricsPath, JSON.stringify(this.metrics, null, 2));
224
+ this.dirty = false;
225
+ } catch {
226
+ process.stderr.write("[cortex-enterprise] Failed to flush telemetry metrics\n");
227
+ }
228
+ }
229
+
230
+ reset(): void {
231
+ this.metrics = emptyMetrics(this.clientVersion, this.instanceId);
232
+ this.dirty = true;
233
+ }
234
+ }