@toolbaux/guardian 0.1.9 → 0.1.11

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.
@@ -17,6 +17,65 @@
17
17
  import fs from "node:fs/promises";
18
18
  import path from "node:path";
19
19
  import readline from "node:readline";
20
+ const metrics = {
21
+ session_start: Date.now(),
22
+ calls: [],
23
+ intel_reloads: 0,
24
+ cache_hits: 0,
25
+ record(tool, args, responseText, cacheHit) {
26
+ const chars = responseText.length;
27
+ const estimatedTokens = Math.ceil(chars / 3.5); // rough token estimate
28
+ this.calls.push({
29
+ tool,
30
+ args,
31
+ timestamp: Date.now(),
32
+ response_chars: chars,
33
+ estimated_tokens: estimatedTokens,
34
+ cache_hit: cacheHit,
35
+ });
36
+ if (cacheHit)
37
+ this.cache_hits++;
38
+ },
39
+ summary() {
40
+ const duration = Math.round((Date.now() - this.session_start) / 1000);
41
+ const totalCalls = this.calls.length;
42
+ const totalTokensSpent = this.calls.reduce((s, c) => s + c.estimated_tokens, 0);
43
+ // Estimate tokens saved: each guardian call replaces ~3 Read/Grep calls (~400 tokens each)
44
+ const estimatedTokensSaved = totalCalls * 400 - totalTokensSpent;
45
+ const toolBreakdown = {};
46
+ for (const c of this.calls) {
47
+ if (!toolBreakdown[c.tool])
48
+ toolBreakdown[c.tool] = { calls: 0, tokens: 0 };
49
+ toolBreakdown[c.tool].calls++;
50
+ toolBreakdown[c.tool].tokens += c.estimated_tokens;
51
+ }
52
+ return {
53
+ session_duration_seconds: duration,
54
+ total_mcp_calls: totalCalls,
55
+ total_tokens_spent: totalTokensSpent,
56
+ estimated_tokens_saved: Math.max(0, estimatedTokensSaved),
57
+ savings_ratio: totalCalls > 0
58
+ ? `${Math.round((estimatedTokensSaved / (totalCalls * 400)) * 100)}%`
59
+ : "n/a",
60
+ cache_hits: this.cache_hits,
61
+ intel_reloads: this.intel_reloads,
62
+ tool_breakdown: toolBreakdown,
63
+ avg_tokens_per_call: totalCalls > 0 ? Math.round(totalTokensSpent / totalCalls) : 0,
64
+ };
65
+ },
66
+ };
67
+ // ── Response cache (dedup repeated queries) ──
68
+ const responseCache = new Map();
69
+ const CACHE_TTL = 30_000; // 30s cache
70
+ function getCached(key) {
71
+ const entry = responseCache.get(key);
72
+ if (entry && Date.now() - entry.time < CACHE_TTL)
73
+ return entry.text;
74
+ return null;
75
+ }
76
+ function setCache(key, text) {
77
+ responseCache.set(key, { text, time: Date.now() });
78
+ }
20
79
  // ── Intelligence loader ──
21
80
  let intel = null;
22
81
  let intelPath = "";
@@ -225,6 +284,14 @@ const TOOLS = [
225
284
  properties: {},
226
285
  },
227
286
  },
287
+ {
288
+ name: "guardian_metrics",
289
+ description: "Get MCP usage metrics for this session: calls made, tokens spent, tokens saved, cache hits. Call at end of session to evaluate guardian's usefulness.",
290
+ inputSchema: {
291
+ type: "object",
292
+ properties: {},
293
+ },
294
+ },
228
295
  ];
229
296
  const TOOL_HANDLERS = {
230
297
  guardian_file_context: fileContext,
@@ -232,6 +299,7 @@ const TOOL_HANDLERS = {
232
299
  guardian_endpoint_trace: endpointTrace,
233
300
  guardian_impact_check: impactCheck,
234
301
  guardian_overview: overview,
302
+ guardian_metrics: async () => JSON.stringify(metrics.summary(), null, 2),
235
303
  };
236
304
  function respond(id, result) {
237
305
  const msg = JSON.stringify({ jsonrpc: "2.0", id, result });
@@ -268,7 +336,17 @@ async function handleRequest(req) {
268
336
  break;
269
337
  }
270
338
  try {
339
+ // Check cache first
340
+ const cacheKey = `${toolName}:${JSON.stringify(toolArgs)}`;
341
+ const cached = getCached(cacheKey);
342
+ if (cached) {
343
+ metrics.record(toolName, toolArgs, cached, true);
344
+ respond(req.id, { content: [{ type: "text", text: cached }] });
345
+ break;
346
+ }
271
347
  const result = await handler(toolArgs);
348
+ setCache(cacheKey, result);
349
+ metrics.record(toolName, toolArgs, result, false);
272
350
  respond(req.id, {
273
351
  content: [{ type: "text", text: result }],
274
352
  });
@@ -307,7 +385,18 @@ export async function runMcpServe(options) {
307
385
  respondError(null, -32700, `Parse error: ${err.message}`);
308
386
  }
309
387
  });
310
- rl.on("close", () => {
388
+ rl.on("close", async () => {
389
+ // Persist session metrics to .specs/machine/mcp-metrics.jsonl
390
+ const metricsPath = path.join(specsDir, "machine", "mcp-metrics.jsonl");
391
+ try {
392
+ const entry = JSON.stringify({
393
+ ...metrics.summary(),
394
+ session_end: new Date().toISOString(),
395
+ });
396
+ await fs.appendFile(metricsPath, entry + "\n", "utf8");
397
+ process.stderr.write(`Guardian metrics saved to ${metricsPath}\n`);
398
+ }
399
+ catch { }
311
400
  process.stderr.write("Guardian MCP server stopped.\n");
312
401
  process.exit(0);
313
402
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@toolbaux/guardian",
3
- "version": "0.1.9",
3
+ "version": "0.1.11",
4
4
  "type": "module",
5
5
  "description": "Architectural intelligence for codebases. Verify that AI-generated code matches your architectural intent.",
6
6
  "keywords": [