@danielblomma/cortex-mcp 2.4.0 → 2.4.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 (214) hide show
  1. package/CHANGELOG.md +226 -0
  2. package/README.md +24 -3
  3. package/bin/cli/arguments.mjs +60 -0
  4. package/bin/cli/context-passthrough.mjs +129 -0
  5. package/bin/cli/daemon.mjs +157 -0
  6. package/bin/cli/enterprise.mjs +516 -0
  7. package/bin/cli/help.mjs +88 -0
  8. package/bin/cli/hooks.mjs +199 -0
  9. package/bin/cli/mcp-command.mjs +87 -0
  10. package/bin/cli/paths.mjs +14 -0
  11. package/bin/cli/process.mjs +49 -0
  12. package/bin/cli/project-commands.mjs +237 -0
  13. package/bin/cli/project-runtime.mjs +34 -0
  14. package/bin/cli/query-command.mjs +18 -0
  15. package/bin/cli/router.mjs +66 -0
  16. package/bin/cli/run-command.mjs +44 -0
  17. package/bin/cli/scaffold-ownership.mjs +936 -0
  18. package/bin/cli/scaffold.mjs +687 -0
  19. package/bin/cli/stage-command.mjs +9 -0
  20. package/bin/cli/telemetry-command.mjs +18 -0
  21. package/bin/cli/trusted-runtime.mjs +18 -0
  22. package/bin/cortex.mjs +12 -1607
  23. package/bin/daemon-control.mjs +162 -0
  24. package/mcp-registry-submission.json +1 -1
  25. package/package.json +8 -3
  26. package/scaffold/mcp/dist/.cortex-build-hash +1 -0
  27. package/scaffold/mcp/dist/cli/enterprise-setup.js +134 -0
  28. package/scaffold/mcp/dist/cli/govern.js +937 -0
  29. package/scaffold/mcp/dist/cli/query.js +409 -0
  30. package/scaffold/mcp/dist/cli/run.js +295 -0
  31. package/scaffold/mcp/dist/cli/stage.js +308 -0
  32. package/scaffold/mcp/dist/cli/telemetry-test.js +141 -0
  33. package/scaffold/mcp/dist/cli/ungoverned-detector.js +133 -0
  34. package/scaffold/mcp/dist/contextEntities.js +282 -0
  35. package/scaffold/mcp/dist/core/audit/query.js +72 -0
  36. package/scaffold/mcp/dist/core/audit/writer.js +28 -0
  37. package/scaffold/mcp/dist/core/config.js +235 -0
  38. package/scaffold/mcp/dist/core/enterprise-host-identity.js +193 -0
  39. package/scaffold/mcp/dist/core/enterprise-identity.js +18 -0
  40. package/scaffold/mcp/dist/core/enterprise-rotation.js +158 -0
  41. package/scaffold/mcp/dist/core/govern-paths.js +21 -0
  42. package/scaffold/mcp/dist/core/index.js +14 -0
  43. package/scaffold/mcp/dist/core/license.js +278 -0
  44. package/scaffold/mcp/dist/core/policy/enforce.js +66 -0
  45. package/scaffold/mcp/dist/core/policy/injection.js +172 -0
  46. package/scaffold/mcp/dist/core/policy/store.js +179 -0
  47. package/scaffold/mcp/dist/core/rbac/check.js +30 -0
  48. package/scaffold/mcp/dist/core/secure-endpoint.js +25 -0
  49. package/scaffold/mcp/dist/core/telemetry/collector.js +285 -0
  50. package/scaffold/mcp/dist/core/telemetry/state-dir.js +31 -0
  51. package/scaffold/mcp/dist/core/validators/builtins.js +632 -0
  52. package/scaffold/mcp/dist/core/validators/config.js +44 -0
  53. package/scaffold/mcp/dist/core/validators/engine.js +120 -0
  54. package/scaffold/mcp/dist/core/validators/evaluators/code_comments.js +236 -0
  55. package/scaffold/mcp/dist/core/validators/evaluators/regex.js +116 -0
  56. package/scaffold/mcp/dist/core/workflow/artifact-io.js +103 -0
  57. package/scaffold/mcp/dist/core/workflow/capabilities.js +88 -0
  58. package/scaffold/mcp/dist/core/workflow/default-workflows.js +102 -0
  59. package/scaffold/mcp/dist/core/workflow/enforcement.js +164 -0
  60. package/scaffold/mcp/dist/core/workflow/envelope.js +132 -0
  61. package/scaffold/mcp/dist/core/workflow/index.js +11 -0
  62. package/scaffold/mcp/dist/core/workflow/mcp-tools.js +175 -0
  63. package/scaffold/mcp/dist/core/workflow/resolution.js +69 -0
  64. package/scaffold/mcp/dist/core/workflow/run-lifecycle.js +123 -0
  65. package/scaffold/mcp/dist/core/workflow/schemas.js +141 -0
  66. package/scaffold/mcp/dist/core/workflow/synced-capability-registry.js +60 -0
  67. package/scaffold/mcp/dist/core/workflow/synced-registry.js +60 -0
  68. package/scaffold/mcp/dist/daemon/capability-sync-checker.js +242 -0
  69. package/scaffold/mcp/dist/daemon/client.js +130 -0
  70. package/scaffold/mcp/dist/daemon/egress-proxy.js +288 -0
  71. package/scaffold/mcp/dist/daemon/global-host-events.js +222 -0
  72. package/scaffold/mcp/dist/daemon/heartbeat-pusher.js +116 -0
  73. package/scaffold/mcp/dist/daemon/heartbeat-tracker.js +165 -0
  74. package/scaffold/mcp/dist/daemon/host-events-pusher.js +249 -0
  75. package/scaffold/mcp/dist/daemon/main.js +449 -0
  76. package/scaffold/mcp/dist/daemon/paths.js +36 -0
  77. package/scaffold/mcp/dist/daemon/project-service-registry.js +78 -0
  78. package/scaffold/mcp/dist/daemon/protocol.js +8 -0
  79. package/scaffold/mcp/dist/daemon/server.js +180 -0
  80. package/scaffold/mcp/dist/daemon/skill-sync-checker.js +557 -0
  81. package/scaffold/mcp/dist/daemon/sync-checker.js +165 -0
  82. package/scaffold/mcp/dist/daemon/ungoverned-scanner.js +136 -0
  83. package/scaffold/mcp/dist/daemon/workflow-sync-checker.js +249 -0
  84. package/scaffold/mcp/dist/defaults.js +6 -0
  85. package/scaffold/mcp/dist/embed.js +627 -0
  86. package/scaffold/mcp/dist/embedScheduler.js +479 -0
  87. package/scaffold/mcp/dist/embeddings.js +167 -0
  88. package/scaffold/mcp/dist/enterprise/audit/push.js +66 -0
  89. package/scaffold/mcp/dist/enterprise/index.js +327 -0
  90. package/scaffold/mcp/dist/enterprise/model/deploy.js +27 -0
  91. package/scaffold/mcp/dist/enterprise/policy/sync.js +129 -0
  92. package/scaffold/mcp/dist/enterprise/privacy/boundary.js +184 -0
  93. package/scaffold/mcp/dist/enterprise/reviews/changed-files.js +33 -0
  94. package/scaffold/mcp/dist/enterprise/reviews/pattern-context.js +202 -0
  95. package/scaffold/mcp/dist/enterprise/reviews/policy-selection.js +46 -0
  96. package/scaffold/mcp/dist/enterprise/reviews/push.js +102 -0
  97. package/scaffold/mcp/dist/enterprise/reviews/trust-state.js +186 -0
  98. package/scaffold/mcp/dist/enterprise/telemetry/sync.js +57 -0
  99. package/scaffold/mcp/dist/enterprise/tools/enterprise.js +826 -0
  100. package/scaffold/mcp/dist/enterprise/tools/harness.js +40 -0
  101. package/scaffold/mcp/dist/enterprise/tools/walk.js +73 -0
  102. package/scaffold/mcp/dist/enterprise/violations/push.js +77 -0
  103. package/scaffold/mcp/dist/enterprise/workflow/push.js +44 -0
  104. package/scaffold/mcp/dist/enterprise/workflow/state.js +329 -0
  105. package/scaffold/mcp/dist/frontmatter.js +33 -0
  106. package/scaffold/mcp/dist/graph.js +769 -0
  107. package/scaffold/mcp/dist/graphCsv.js +55 -0
  108. package/scaffold/mcp/dist/graphMetrics.js +8 -0
  109. package/scaffold/mcp/dist/hooks/permission-request.js +89 -0
  110. package/scaffold/mcp/dist/hooks/post-tool-use.js +105 -0
  111. package/scaffold/mcp/dist/hooks/pre-compact.js +29 -0
  112. package/scaffold/mcp/dist/hooks/pre-tool-use.js +78 -0
  113. package/scaffold/mcp/dist/hooks/session-end.js +43 -0
  114. package/scaffold/mcp/dist/hooks/session-start.js +41 -0
  115. package/scaffold/mcp/dist/hooks/shared.js +194 -0
  116. package/scaffold/mcp/dist/hooks/stop.js +28 -0
  117. package/scaffold/mcp/dist/hooks/user-prompt-submit.js +33 -0
  118. package/scaffold/mcp/dist/impactPresentation.js +137 -0
  119. package/scaffold/mcp/dist/impactRanking.js +191 -0
  120. package/scaffold/mcp/dist/impactResponse.js +30 -0
  121. package/scaffold/mcp/dist/impactResults.js +105 -0
  122. package/scaffold/mcp/dist/impactSeed.js +20 -0
  123. package/scaffold/mcp/dist/impactTraversal.js +64 -0
  124. package/scaffold/mcp/dist/jsonl.js +77 -0
  125. package/scaffold/mcp/dist/loadGraph.js +759 -0
  126. package/scaffold/mcp/dist/lruCache.js +38 -0
  127. package/scaffold/mcp/dist/paths.js +97 -0
  128. package/scaffold/mcp/dist/patternEvidence.js +272 -0
  129. package/scaffold/mcp/dist/plugin.js +81 -0
  130. package/scaffold/mcp/dist/presets.js +78 -0
  131. package/scaffold/mcp/dist/relatedResponse.js +18 -0
  132. package/scaffold/mcp/dist/relatedTraversal.js +78 -0
  133. package/scaffold/mcp/dist/rules.js +23 -0
  134. package/scaffold/mcp/dist/search.js +212 -0
  135. package/scaffold/mcp/dist/searchCore.js +457 -0
  136. package/scaffold/mcp/dist/searchResults.js +230 -0
  137. package/scaffold/mcp/dist/server.js +317 -0
  138. package/scaffold/mcp/dist/types.js +1 -0
  139. package/scaffold/mcp/package-lock.json +321 -200
  140. package/scaffold/mcp/package.json +15 -6
  141. package/scaffold/mcp/src/cli/enterprise-setup.ts +82 -8
  142. package/scaffold/mcp/src/cli/govern.ts +137 -52
  143. package/scaffold/mcp/src/cli/run.ts +53 -19
  144. package/scaffold/mcp/src/cli/stage.ts +8 -3
  145. package/scaffold/mcp/src/core/config.ts +9 -2
  146. package/scaffold/mcp/src/core/enterprise-host-identity.ts +259 -0
  147. package/scaffold/mcp/src/core/enterprise-identity.ts +20 -0
  148. package/scaffold/mcp/src/core/enterprise-rotation.ts +185 -0
  149. package/scaffold/mcp/src/core/govern-paths.ts +30 -0
  150. package/scaffold/mcp/src/core/license.ts +186 -17
  151. package/scaffold/mcp/src/core/secure-endpoint.ts +23 -0
  152. package/scaffold/mcp/src/core/workflow/enforcement.ts +8 -1
  153. package/scaffold/mcp/src/core/workflow/mcp-tools.ts +3 -0
  154. package/scaffold/mcp/src/core/workflow/resolution.ts +9 -1
  155. package/scaffold/mcp/src/core/workflow/synced-capability-registry.ts +15 -0
  156. package/scaffold/mcp/src/core/workflow/synced-registry.ts +15 -0
  157. package/scaffold/mcp/src/daemon/capability-sync-checker.ts +38 -2
  158. package/scaffold/mcp/src/daemon/egress-proxy.ts +14 -8
  159. package/scaffold/mcp/src/daemon/global-host-events.ts +288 -0
  160. package/scaffold/mcp/src/daemon/heartbeat-pusher.ts +4 -0
  161. package/scaffold/mcp/src/daemon/heartbeat-tracker.ts +2 -0
  162. package/scaffold/mcp/src/daemon/host-events-pusher.ts +5 -0
  163. package/scaffold/mcp/src/daemon/main.ts +99 -25
  164. package/scaffold/mcp/src/daemon/project-service-registry.ts +107 -0
  165. package/scaffold/mcp/src/daemon/skill-sync-checker.ts +368 -31
  166. package/scaffold/mcp/src/daemon/sync-checker.ts +4 -0
  167. package/scaffold/mcp/src/daemon/ungoverned-scanner.ts +57 -25
  168. package/scaffold/mcp/src/daemon/workflow-sync-checker.ts +41 -2
  169. package/scaffold/mcp/src/enterprise/audit/push.ts +8 -0
  170. package/scaffold/mcp/src/enterprise/policy/sync.ts +12 -0
  171. package/scaffold/mcp/src/enterprise/reviews/push.ts +9 -0
  172. package/scaffold/mcp/src/enterprise/reviews/trust-state.ts +3 -1
  173. package/scaffold/mcp/src/enterprise/telemetry/sync.ts +9 -0
  174. package/scaffold/mcp/src/enterprise/violations/push.ts +9 -0
  175. package/scaffold/mcp/src/enterprise/workflow/push.ts +7 -0
  176. package/scaffold/mcp/src/plugin.ts +20 -0
  177. package/scaffold/mcp/tests/copilot-shim.test.mjs +19 -1
  178. package/scaffold/mcp/tests/egress-proxy.test.mjs +37 -0
  179. package/scaffold/mcp/tests/enterprise-identity-sync.test.mjs +401 -0
  180. package/scaffold/mcp/tests/enterprise-setup.test.mjs +189 -0
  181. package/scaffold/mcp/tests/global-host-events.test.mjs +81 -0
  182. package/scaffold/mcp/tests/govern-install.test.mjs +95 -2
  183. package/scaffold/mcp/tests/govern-repair.test.mjs +20 -3
  184. package/scaffold/mcp/tests/heartbeat-tracker.test.mjs +26 -0
  185. package/scaffold/mcp/tests/license.test.mjs +367 -0
  186. package/scaffold/mcp/tests/project-service-registry.test.mjs +172 -0
  187. package/scaffold/mcp/tests/review-trust-contract.test.mjs +18 -0
  188. package/scaffold/mcp/tests/secure-enterprise-endpoint.test.mjs +46 -0
  189. package/scaffold/mcp/tests/skill-sync-checker.test.mjs +446 -2
  190. package/scaffold/mcp/tests/ungoverned-scanner.test.mjs +104 -22
  191. package/scaffold/mcp/tests/workflow-cli.test.mjs +14 -1
  192. package/scaffold/mcp/tests/workflow-synced-capabilities.test.mjs +37 -0
  193. package/scaffold/mcp/tests/workflow-synced-registry.test.mjs +40 -1
  194. package/scaffold/ownership/baseline-v2.4.1.json +22 -0
  195. package/scaffold/ownership/current.json +4 -0
  196. package/scaffold/ownership/v1.json +456 -0
  197. package/scaffold/scripts/ingest-parsers.mjs +1 -387
  198. package/scaffold/scripts/ingest-worker.mjs +4 -1
  199. package/scaffold/scripts/ingest.mjs +5 -3899
  200. package/scaffold/scripts/lib/ingest/arguments.mjs +78 -0
  201. package/scaffold/scripts/lib/ingest/chunks.mjs +212 -0
  202. package/scaffold/scripts/lib/ingest/config.mjs +120 -0
  203. package/scaffold/scripts/lib/ingest/constants.mjs +222 -0
  204. package/scaffold/scripts/lib/ingest/files.mjs +387 -0
  205. package/scaffold/scripts/lib/ingest/incremental-state.mjs +131 -0
  206. package/scaffold/scripts/lib/ingest/io.mjs +78 -0
  207. package/scaffold/scripts/lib/ingest/main.mjs +76 -0
  208. package/scaffold/scripts/lib/ingest/parser-composition.mjs +140 -0
  209. package/scaffold/scripts/lib/ingest/parser-registry.mjs +387 -0
  210. package/scaffold/scripts/lib/ingest/pipeline-stages.mjs +1625 -0
  211. package/scaffold/scripts/lib/ingest/projects.mjs +272 -0
  212. package/scaffold/scripts/lib/ingest/relations.mjs +860 -0
  213. package/scaffold/scripts/lib/ingest/runtime-paths.mjs +11 -0
  214. package/scaffold/scripts/lib/ingest/workers.mjs +264 -0
@@ -0,0 +1,285 @@
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
+ import { resolveTelemetryStateDir, telemetryStatePath } from "./state-dir.js";
6
+ const AVG_TOKENS_PER_RESULT = 400;
7
+ function generateInstanceId(contextDir) {
8
+ const telemetryDir = resolveTelemetryStateDir(contextDir);
9
+ const idPath = join(telemetryDir, "machine_id");
10
+ if (existsSync(idPath)) {
11
+ try {
12
+ const existing = readFileSync(idPath, "utf8").trim();
13
+ if (existing.length > 0)
14
+ return existing;
15
+ }
16
+ catch (err) {
17
+ process.stderr.write(`[cortex-enterprise] machine_id exists but is unreadable: ${err instanceof Error ? err.message : String(err)}\n`);
18
+ }
19
+ }
20
+ // Note: hostname|platform|arch may collide on machines with identical defaults.
21
+ // Consider adding a random salt if fleet-wide uniqueness is critical.
22
+ const fingerprint = `${hostname()}|${platform()}|${arch()}`;
23
+ const id = createHash("sha256").update(fingerprint).digest("hex").slice(0, 16);
24
+ try {
25
+ mkdirSync(telemetryDir, { recursive: true });
26
+ writeFileSync(idPath, id, "utf8");
27
+ }
28
+ catch (err) {
29
+ process.stderr.write(`[cortex-enterprise] Could not persist instance id: ${err instanceof Error ? err.message : String(err)}\n`);
30
+ }
31
+ return id;
32
+ }
33
+ function emptyMetrics(clientVersion, instanceId) {
34
+ const now = new Date().toISOString();
35
+ return {
36
+ period_start: now,
37
+ period_end: now,
38
+ total_tool_calls: 0,
39
+ successful_tool_calls: 0,
40
+ failed_tool_calls: 0,
41
+ total_duration_ms: 0,
42
+ session_starts: 0,
43
+ session_ends: 0,
44
+ session_duration_ms_total: 0,
45
+ searches: 0,
46
+ related_lookups: 0,
47
+ caller_lookups: 0,
48
+ trace_lookups: 0,
49
+ impact_analyses: 0,
50
+ rule_lookups: 0,
51
+ reloads: 0,
52
+ total_results_returned: 0,
53
+ estimated_tokens_saved: 0,
54
+ estimated_tokens_total: 0,
55
+ client_version: clientVersion,
56
+ instance_id: instanceId,
57
+ tool_metrics: {},
58
+ };
59
+ }
60
+ function subtractCounter(current, pushed) {
61
+ return Math.max(0, current - pushed);
62
+ }
63
+ function hasUsage(metrics) {
64
+ if (metrics.total_tool_calls > 0 ||
65
+ metrics.successful_tool_calls > 0 ||
66
+ metrics.failed_tool_calls > 0 ||
67
+ metrics.total_duration_ms > 0 ||
68
+ metrics.session_starts > 0 ||
69
+ metrics.session_ends > 0 ||
70
+ metrics.session_duration_ms_total > 0 ||
71
+ metrics.searches > 0 ||
72
+ metrics.related_lookups > 0 ||
73
+ metrics.caller_lookups > 0 ||
74
+ metrics.trace_lookups > 0 ||
75
+ metrics.impact_analyses > 0 ||
76
+ metrics.rule_lookups > 0 ||
77
+ metrics.reloads > 0 ||
78
+ metrics.total_results_returned > 0 ||
79
+ metrics.estimated_tokens_saved > 0 ||
80
+ metrics.estimated_tokens_total > 0) {
81
+ return true;
82
+ }
83
+ return Object.values(metrics.tool_metrics).some((bucket) => bucket.calls > 0 ||
84
+ bucket.failures > 0 ||
85
+ bucket.total_duration_ms > 0 ||
86
+ bucket.total_results_returned > 0 ||
87
+ bucket.estimated_tokens_saved > 0);
88
+ }
89
+ export class TelemetryCollector {
90
+ metrics;
91
+ metricsPath;
92
+ clientVersion;
93
+ instanceId;
94
+ dirty = false;
95
+ constructor(contextDir, clientVersion = "unknown") {
96
+ this.clientVersion = clientVersion;
97
+ this.instanceId = generateInstanceId(contextDir);
98
+ this.metricsPath = telemetryStatePath(contextDir, "metrics.json");
99
+ // Load existing metrics or start fresh
100
+ try {
101
+ const raw = readFileSync(this.metricsPath, "utf8");
102
+ this.metrics = JSON.parse(raw);
103
+ this.metrics.client_version = clientVersion;
104
+ this.metrics.instance_id = this.instanceId;
105
+ }
106
+ catch {
107
+ this.metrics = emptyMetrics(clientVersion, this.instanceId);
108
+ }
109
+ }
110
+ bucket(toolName) {
111
+ if (!this.metrics.tool_metrics[toolName]) {
112
+ this.metrics.tool_metrics[toolName] = {
113
+ calls: 0,
114
+ failures: 0,
115
+ total_duration_ms: 0,
116
+ total_results_returned: 0,
117
+ estimated_tokens_saved: 0,
118
+ };
119
+ }
120
+ return this.metrics.tool_metrics[toolName];
121
+ }
122
+ recordEvent(event) {
123
+ const resultCount = event.result_count ?? 0;
124
+ const tokensSaved = event.estimated_tokens_saved ?? 0;
125
+ const durationMs = event.duration_ms ?? 0;
126
+ const toolBucket = this.bucket(event.tool);
127
+ this.metrics.total_tool_calls++;
128
+ this.metrics.total_duration_ms += durationMs;
129
+ this.metrics.period_end = new Date().toISOString();
130
+ toolBucket.calls++;
131
+ toolBucket.total_duration_ms += durationMs;
132
+ if (event.phase === "error") {
133
+ this.metrics.failed_tool_calls++;
134
+ toolBucket.failures++;
135
+ this.dirty = true;
136
+ return;
137
+ }
138
+ this.metrics.successful_tool_calls++;
139
+ switch (event.tool) {
140
+ case "context.search":
141
+ this.metrics.searches++;
142
+ break;
143
+ case "context.get_related":
144
+ this.metrics.related_lookups++;
145
+ break;
146
+ case "context.find_callers":
147
+ this.metrics.caller_lookups++;
148
+ break;
149
+ case "context.trace_calls":
150
+ this.metrics.trace_lookups++;
151
+ break;
152
+ case "context.impact":
153
+ case "context.impact_analysis":
154
+ this.metrics.impact_analyses++;
155
+ break;
156
+ case "context.get_rules":
157
+ this.metrics.rule_lookups++;
158
+ break;
159
+ case "context.reload":
160
+ this.metrics.reloads++;
161
+ break;
162
+ }
163
+ this.metrics.total_results_returned += resultCount;
164
+ this.metrics.estimated_tokens_saved += tokensSaved;
165
+ this.metrics.estimated_tokens_total += tokensSaved + resultCount * AVG_TOKENS_PER_RESULT;
166
+ toolBucket.total_results_returned += resultCount;
167
+ toolBucket.estimated_tokens_saved += tokensSaved;
168
+ this.dirty = true;
169
+ }
170
+ record(toolName, resultCount, tokensSaved) {
171
+ this.recordEvent({
172
+ tool: toolName,
173
+ phase: "success",
174
+ result_count: resultCount,
175
+ estimated_tokens_saved: tokensSaved,
176
+ duration_ms: 0,
177
+ });
178
+ }
179
+ recordSessionStart() {
180
+ this.metrics.session_starts++;
181
+ this.metrics.period_end = new Date().toISOString();
182
+ this.dirty = true;
183
+ }
184
+ recordSessionEnd(durationMs) {
185
+ this.metrics.session_ends++;
186
+ this.metrics.session_duration_ms_total += Math.max(0, durationMs);
187
+ this.metrics.period_end = new Date().toISOString();
188
+ this.dirty = true;
189
+ }
190
+ getMetrics() {
191
+ return {
192
+ ...this.metrics,
193
+ tool_metrics: Object.fromEntries(Object.entries(this.metrics.tool_metrics).map(([toolName, bucket]) => [
194
+ toolName,
195
+ { ...bucket },
196
+ ])),
197
+ };
198
+ }
199
+ acknowledgePush(pushed) {
200
+ const nextToolMetrics = {};
201
+ const toolNames = new Set([
202
+ ...Object.keys(this.metrics.tool_metrics),
203
+ ...Object.keys(pushed.tool_metrics ?? {}),
204
+ ]);
205
+ for (const toolName of toolNames) {
206
+ const currentBucket = this.metrics.tool_metrics[toolName] ?? {
207
+ calls: 0,
208
+ failures: 0,
209
+ total_duration_ms: 0,
210
+ total_results_returned: 0,
211
+ estimated_tokens_saved: 0,
212
+ };
213
+ const pushedBucket = pushed.tool_metrics?.[toolName] ?? {
214
+ calls: 0,
215
+ failures: 0,
216
+ total_duration_ms: 0,
217
+ total_results_returned: 0,
218
+ estimated_tokens_saved: 0,
219
+ };
220
+ const nextBucket = {
221
+ calls: subtractCounter(currentBucket.calls, pushedBucket.calls),
222
+ failures: subtractCounter(currentBucket.failures, pushedBucket.failures),
223
+ total_duration_ms: subtractCounter(currentBucket.total_duration_ms, pushedBucket.total_duration_ms),
224
+ total_results_returned: subtractCounter(currentBucket.total_results_returned, pushedBucket.total_results_returned),
225
+ estimated_tokens_saved: subtractCounter(currentBucket.estimated_tokens_saved, pushedBucket.estimated_tokens_saved),
226
+ };
227
+ if (nextBucket.calls > 0 ||
228
+ nextBucket.failures > 0 ||
229
+ nextBucket.total_duration_ms > 0 ||
230
+ nextBucket.total_results_returned > 0 ||
231
+ nextBucket.estimated_tokens_saved > 0) {
232
+ nextToolMetrics[toolName] = nextBucket;
233
+ }
234
+ }
235
+ const nextMetrics = {
236
+ ...this.metrics,
237
+ period_start: pushed.period_end,
238
+ total_tool_calls: subtractCounter(this.metrics.total_tool_calls, pushed.total_tool_calls),
239
+ successful_tool_calls: subtractCounter(this.metrics.successful_tool_calls, pushed.successful_tool_calls),
240
+ failed_tool_calls: subtractCounter(this.metrics.failed_tool_calls, pushed.failed_tool_calls),
241
+ total_duration_ms: subtractCounter(this.metrics.total_duration_ms, pushed.total_duration_ms),
242
+ session_starts: subtractCounter(this.metrics.session_starts, pushed.session_starts),
243
+ session_ends: subtractCounter(this.metrics.session_ends, pushed.session_ends),
244
+ session_duration_ms_total: subtractCounter(this.metrics.session_duration_ms_total, pushed.session_duration_ms_total),
245
+ searches: subtractCounter(this.metrics.searches, pushed.searches),
246
+ related_lookups: subtractCounter(this.metrics.related_lookups, pushed.related_lookups),
247
+ caller_lookups: subtractCounter(this.metrics.caller_lookups, pushed.caller_lookups),
248
+ trace_lookups: subtractCounter(this.metrics.trace_lookups, pushed.trace_lookups),
249
+ impact_analyses: subtractCounter(this.metrics.impact_analyses, pushed.impact_analyses),
250
+ rule_lookups: subtractCounter(this.metrics.rule_lookups, pushed.rule_lookups),
251
+ reloads: subtractCounter(this.metrics.reloads, pushed.reloads),
252
+ total_results_returned: subtractCounter(this.metrics.total_results_returned, pushed.total_results_returned),
253
+ estimated_tokens_saved: subtractCounter(this.metrics.estimated_tokens_saved, pushed.estimated_tokens_saved),
254
+ estimated_tokens_total: subtractCounter(this.metrics.estimated_tokens_total, pushed.estimated_tokens_total),
255
+ client_version: this.clientVersion,
256
+ instance_id: this.instanceId,
257
+ tool_metrics: nextToolMetrics,
258
+ };
259
+ if (hasUsage(nextMetrics)) {
260
+ this.metrics = nextMetrics;
261
+ this.metrics.period_end = new Date().toISOString();
262
+ }
263
+ else {
264
+ this.metrics = emptyMetrics(this.clientVersion, this.instanceId);
265
+ }
266
+ this.dirty = true;
267
+ }
268
+ flush() {
269
+ if (!this.dirty)
270
+ return;
271
+ try {
272
+ const dir = join(this.metricsPath, "..");
273
+ mkdirSync(dir, { recursive: true });
274
+ writeFileSync(this.metricsPath, JSON.stringify(this.metrics, null, 2));
275
+ this.dirty = false;
276
+ }
277
+ catch {
278
+ process.stderr.write("[cortex-enterprise] Failed to flush telemetry metrics\n");
279
+ }
280
+ }
281
+ reset() {
282
+ this.metrics = emptyMetrics(this.clientVersion, this.instanceId);
283
+ this.dirty = true;
284
+ }
285
+ }
@@ -0,0 +1,31 @@
1
+ import { accessSync, constants, mkdirSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ const warnedFallbacks = new Set();
4
+ function canUseDirectory(dir) {
5
+ try {
6
+ mkdirSync(dir, { recursive: true });
7
+ accessSync(dir, constants.R_OK | constants.W_OK | constants.X_OK);
8
+ return true;
9
+ }
10
+ catch {
11
+ return false;
12
+ }
13
+ }
14
+ export function resolveTelemetryStateDir(contextDir) {
15
+ const primary = join(contextDir, "telemetry");
16
+ if (canUseDirectory(primary))
17
+ return primary;
18
+ const fallback = join(contextDir, "cache", "telemetry");
19
+ if (canUseDirectory(fallback)) {
20
+ const warningKey = `${primary}->${fallback}`;
21
+ if (!warnedFallbacks.has(warningKey)) {
22
+ warnedFallbacks.add(warningKey);
23
+ process.stderr.write(`[cortex-enterprise] telemetry dir not writable at ${primary}; using ${fallback}\n`);
24
+ }
25
+ return fallback;
26
+ }
27
+ return primary;
28
+ }
29
+ export function telemetryStatePath(contextDir, ...parts) {
30
+ return join(resolveTelemetryStateDir(contextDir), ...parts);
31
+ }