twining-mcp 1.4.2 → 1.5.0

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 (44) hide show
  1. package/README.md +51 -2
  2. package/dist/analytics/analytics-engine.d.ts +18 -0
  3. package/dist/analytics/analytics-engine.d.ts.map +1 -0
  4. package/dist/analytics/analytics-engine.js +114 -0
  5. package/dist/analytics/analytics-engine.js.map +1 -0
  6. package/dist/analytics/instrumented-server.d.ts +12 -0
  7. package/dist/analytics/instrumented-server.d.ts.map +1 -0
  8. package/dist/analytics/instrumented-server.js +76 -0
  9. package/dist/analytics/instrumented-server.js.map +1 -0
  10. package/dist/analytics/metrics-collector.d.ts +15 -0
  11. package/dist/analytics/metrics-collector.d.ts.map +1 -0
  12. package/dist/analytics/metrics-collector.js +36 -0
  13. package/dist/analytics/metrics-collector.js.map +1 -0
  14. package/dist/analytics/metrics-store.d.ts +20 -0
  15. package/dist/analytics/metrics-store.d.ts.map +1 -0
  16. package/dist/analytics/metrics-store.js +109 -0
  17. package/dist/analytics/metrics-store.js.map +1 -0
  18. package/dist/analytics/telemetry-client.d.ts +19 -0
  19. package/dist/analytics/telemetry-client.d.ts.map +1 -0
  20. package/dist/analytics/telemetry-client.js +137 -0
  21. package/dist/analytics/telemetry-client.js.map +1 -0
  22. package/dist/config.d.ts.map +1 -1
  23. package/dist/config.js +10 -0
  24. package/dist/config.js.map +1 -1
  25. package/dist/dashboard/api-routes.d.ts.map +1 -1
  26. package/dist/dashboard/api-routes.js +71 -0
  27. package/dist/dashboard/api-routes.js.map +1 -1
  28. package/dist/dashboard/public/app.js +185 -0
  29. package/dist/dashboard/public/index.html +86 -1
  30. package/dist/dashboard/public/style.css +83 -0
  31. package/dist/index.js +23 -1
  32. package/dist/index.js.map +1 -1
  33. package/dist/server.d.ts +8 -1
  34. package/dist/server.d.ts.map +1 -1
  35. package/dist/server.js +8 -5
  36. package/dist/server.js.map +1 -1
  37. package/dist/storage/init.js +1 -1
  38. package/dist/storage/init.js.map +1 -1
  39. package/dist/utils/types.d.ts +79 -0
  40. package/dist/utils/types.d.ts.map +1 -1
  41. package/package.json +4 -1
  42. package/src/dashboard/public/app.js +185 -0
  43. package/src/dashboard/public/index.html +86 -1
  44. package/src/dashboard/public/style.css +83 -0
@@ -18,6 +18,7 @@ var state = {
18
18
  delegations: { data: [], sortKey: "timestamp", sortDir: "desc", page: 1, pageSize: 25, selectedId: null },
19
19
  handoffs: { data: [], sortKey: "created_at", sortDir: "desc", page: 1, pageSize: 25, selectedId: null },
20
20
  agentsSubView: "agents-list",
21
+ insights: { valueStats: null, toolUsage: [], errors: [] },
21
22
  globalScope: "",
22
23
  status: null,
23
24
  pollTimer: null,
@@ -278,6 +279,44 @@ function fetchHandoffDetail(id) {
278
279
  });
279
280
  }
280
281
 
282
+ /* ========== Insights Fetching ========== */
283
+
284
+ function fetchValueStats() {
285
+ fetch("/api/analytics/value-stats")
286
+ .then(function(res) { return res.json(); })
287
+ .then(function(data) {
288
+ state.insights.valueStats = data;
289
+ renderValueStats();
290
+ })
291
+ .catch(function() {});
292
+ }
293
+
294
+ function fetchToolUsage() {
295
+ fetch("/api/analytics/tool-usage")
296
+ .then(function(res) { return res.json(); })
297
+ .then(function(data) {
298
+ state.insights.toolUsage = data.tools || [];
299
+ renderToolUsage();
300
+ })
301
+ .catch(function() {});
302
+ }
303
+
304
+ function fetchErrorBreakdown() {
305
+ fetch("/api/analytics/errors")
306
+ .then(function(res) { return res.json(); })
307
+ .then(function(data) {
308
+ state.insights.errors = data.errors || [];
309
+ renderErrorBreakdown();
310
+ })
311
+ .catch(function() {});
312
+ }
313
+
314
+ function fetchInsights() {
315
+ fetchValueStats();
316
+ fetchToolUsage();
317
+ fetchErrorBreakdown();
318
+ }
319
+
281
320
  /* ========== Polling Lifecycle ========== */
282
321
 
283
322
  function refreshData() {
@@ -293,6 +332,7 @@ function refreshData() {
293
332
  else if (state.agentsSubView === "delegations") fetchDelegations();
294
333
  else if (state.agentsSubView === "handoffs") fetchHandoffs();
295
334
  }
335
+ else if (tab === "insights") fetchInsights();
296
336
  }
297
337
 
298
338
  function startPolling() {
@@ -456,6 +496,13 @@ function renderStatus() {
456
496
  setVal("stat-pending-delegations", s.pending_delegations);
457
497
  setVal("stat-total-handoffs", s.total_handoffs);
458
498
 
499
+ // Update project name in header and page title
500
+ if (s.project_name) {
501
+ var titleEl = document.getElementById("dashboard-title");
502
+ if (titleEl) titleEl.textContent = s.project_name + " — Twining";
503
+ document.title = s.project_name + " — Twining Dashboard";
504
+ }
505
+
459
506
  var msgEl = document.getElementById("uninitialized-msg");
460
507
  if (msgEl) {
461
508
  msgEl.style.display = (s.initialized === false) ? "block" : "none";
@@ -2391,6 +2438,144 @@ function updateGraphData() {
2391
2438
  }
2392
2439
  }
2393
2440
 
2441
+ /* ========== Insights Rendering ========== */
2442
+
2443
+ function renderValueStats() {
2444
+ var vs = state.insights.valueStats;
2445
+ if (!vs) return;
2446
+
2447
+ // Blind decisions prevented
2448
+ var prevRate = document.getElementById("insight-prevention-rate");
2449
+ var prevDetail = document.getElementById("insight-prevention-detail");
2450
+ if (prevRate) prevRate.textContent = Math.round(vs.blind_decisions_prevented.prevention_rate * 100) + "%";
2451
+ if (prevDetail) prevDetail.textContent = vs.blind_decisions_prevented.assembled_before + " of " + vs.blind_decisions_prevented.total_decisions + " decisions had context assembled first";
2452
+
2453
+ // Warnings surfaced
2454
+ var warnTotal = document.getElementById("insight-warnings-total");
2455
+ var warnDetail = document.getElementById("insight-warnings-detail");
2456
+ if (warnTotal) warnTotal.textContent = vs.warnings_surfaced.total;
2457
+ if (warnDetail) warnDetail.textContent = vs.warnings_surfaced.acknowledged + " acknowledged, " + vs.warnings_surfaced.resolved + " resolved, " + vs.warnings_surfaced.ignored + " unaddressed";
2458
+
2459
+ // Test coverage
2460
+ var testCov = document.getElementById("insight-test-coverage");
2461
+ var testDetail = document.getElementById("insight-test-detail");
2462
+ if (testCov) testCov.textContent = Math.round(vs.test_coverage.coverage_rate * 100) + "%";
2463
+ if (testDetail) testDetail.textContent = vs.test_coverage.with_tested_by + " of " + vs.test_coverage.total_decisions + " decisions have tested_by relations";
2464
+
2465
+ // Commit traceability
2466
+ var traceVal = document.getElementById("insight-traceability");
2467
+ var traceDetail = document.getElementById("insight-traceability-detail");
2468
+ if (traceVal) traceVal.textContent = Math.round(vs.commit_traceability.traceability_rate * 100) + "%";
2469
+ if (traceDetail) traceDetail.textContent = vs.commit_traceability.with_commits + " of " + vs.commit_traceability.total_decisions + " decisions linked to commits";
2470
+
2471
+ // Decision lifecycle
2472
+ var lcVal = document.getElementById("insight-lifecycle");
2473
+ var lcDetail = document.getElementById("insight-lifecycle-detail");
2474
+ var lc = vs.decision_lifecycle;
2475
+ if (lcVal) lcVal.textContent = (lc.active + lc.provisional + lc.superseded + lc.overridden);
2476
+ if (lcDetail) lcDetail.textContent = lc.active + " active, " + lc.provisional + " provisional, " + lc.superseded + " superseded, " + lc.overridden + " overridden";
2477
+
2478
+ // Knowledge graph
2479
+ var graphVal = document.getElementById("insight-graph");
2480
+ var graphDetail = document.getElementById("insight-graph-detail");
2481
+ if (graphVal) graphVal.textContent = vs.knowledge_graph.entities + " / " + vs.knowledge_graph.relations;
2482
+ if (graphDetail) {
2483
+ var typeStrs = [];
2484
+ var ebt = vs.knowledge_graph.entities_by_type;
2485
+ for (var ek in ebt) { if (ebt.hasOwnProperty(ek)) typeStrs.push(ek + ": " + ebt[ek]); }
2486
+ graphDetail.textContent = typeStrs.length > 0 ? "Entities: " + typeStrs.join(", ") : "No entities";
2487
+ }
2488
+
2489
+ // Agent coordination
2490
+ var coordVal = document.getElementById("insight-coordination");
2491
+ var coordDetail = document.getElementById("insight-coordination-detail");
2492
+ if (coordVal) coordVal.textContent = vs.agent_coordination.total_handoffs;
2493
+ if (coordDetail) coordDetail.textContent = "Acknowledgment rate: " + Math.round(vs.agent_coordination.acknowledgment_rate * 100) + "%";
2494
+ }
2495
+
2496
+ function renderToolUsage() {
2497
+ var tbody = document.querySelector("#tool-usage-table tbody");
2498
+ if (!tbody) return;
2499
+ clearElement(tbody);
2500
+
2501
+ var tools = state.insights.toolUsage;
2502
+ if (tools.length === 0) {
2503
+ var row = document.createElement("tr");
2504
+ var cell = document.createElement("td");
2505
+ cell.colSpan = 6;
2506
+ cell.textContent = "No tool calls recorded";
2507
+ cell.className = "placeholder";
2508
+ row.appendChild(cell);
2509
+ tbody.appendChild(row);
2510
+ return;
2511
+ }
2512
+
2513
+ for (var i = 0; i < tools.length; i++) {
2514
+ var t = tools[i];
2515
+ var tr = document.createElement("tr");
2516
+
2517
+ var tdName = document.createElement("td");
2518
+ tdName.textContent = t.tool_name;
2519
+ tr.appendChild(tdName);
2520
+
2521
+ var tdCalls = document.createElement("td");
2522
+ tdCalls.textContent = t.call_count;
2523
+ tr.appendChild(tdCalls);
2524
+
2525
+ var tdErrors = document.createElement("td");
2526
+ tdErrors.textContent = t.error_count;
2527
+ if (t.error_count > 0) tdErrors.className = "error-count";
2528
+ tr.appendChild(tdErrors);
2529
+
2530
+ var tdAvg = document.createElement("td");
2531
+ tdAvg.textContent = t.avg_duration_ms;
2532
+ tr.appendChild(tdAvg);
2533
+
2534
+ var tdP95 = document.createElement("td");
2535
+ tdP95.textContent = t.p95_duration_ms;
2536
+ tr.appendChild(tdP95);
2537
+
2538
+ var tdLast = document.createElement("td");
2539
+ tdLast.textContent = formatTime(t.last_called);
2540
+ tr.appendChild(tdLast);
2541
+
2542
+ tbody.appendChild(tr);
2543
+ }
2544
+ }
2545
+
2546
+ function renderErrorBreakdown() {
2547
+ var tbody = document.querySelector("#error-breakdown-table tbody");
2548
+ var noMsg = document.getElementById("no-errors-msg");
2549
+ if (!tbody) return;
2550
+ clearElement(tbody);
2551
+
2552
+ var errors = state.insights.errors;
2553
+ if (errors.length === 0) {
2554
+ if (noMsg) noMsg.style.display = "block";
2555
+ return;
2556
+ }
2557
+ if (noMsg) noMsg.style.display = "none";
2558
+
2559
+ for (var i = 0; i < errors.length; i++) {
2560
+ var e = errors[i];
2561
+ var tr = document.createElement("tr");
2562
+
2563
+ var tdTool = document.createElement("td");
2564
+ tdTool.textContent = e.tool_name;
2565
+ tr.appendChild(tdTool);
2566
+
2567
+ var tdCode = document.createElement("td");
2568
+ tdCode.textContent = e.error_code;
2569
+ tr.appendChild(tdCode);
2570
+
2571
+ var tdCount = document.createElement("td");
2572
+ tdCount.textContent = e.count;
2573
+ tr.appendChild(tdCount);
2574
+
2575
+ tbody.appendChild(tr);
2576
+ }
2577
+ }
2578
+
2394
2579
  /* ========== Initialization ========== */
2395
2580
 
2396
2581
  document.addEventListener("DOMContentLoaded", function() {
@@ -17,7 +17,10 @@
17
17
  <header>
18
18
  <div class="header-brand">
19
19
  <img src="assets/favicon.svg" alt="Twining" width="28" height="28" class="header-logo">
20
- <h1>Twining Dashboard</h1>
20
+ <h1 id="dashboard-title">Twining Dashboard</h1>
21
+ <a href="https://github.com/twining-mcp/twining-mcp" target="_blank" rel="noopener noreferrer" class="github-link" title="View on GitHub">
22
+ <svg width="20" height="20" viewBox="0 0 16 16" fill="currentColor"><path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z"/></svg>
23
+ </a>
21
24
  </div>
22
25
  <div class="global-scope-filter">
23
26
  <label for="global-scope">Scope:</label>
@@ -46,6 +49,7 @@
46
49
  <button class="tab-btn" data-tab="graph">Graph</button>
47
50
  <button class="tab-btn" data-tab="search">Search</button>
48
51
  <button class="tab-btn" data-tab="agents">Agents</button>
52
+ <button class="tab-btn" data-tab="insights">Insights</button>
49
53
  </nav>
50
54
 
51
55
  <div class="search-bar">
@@ -347,6 +351,87 @@
347
351
  </div>
348
352
  </div>
349
353
  </div>
354
+
355
+ <!-- Insights Tab -->
356
+ <div class="tab-content" id="tab-insights" style="display:none">
357
+ <h3 class="insights-heading">Value Metrics</h3>
358
+ <div class="insights-grid">
359
+ <div class="insight-card">
360
+ <div class="insight-icon">&#x1F6E1;</div>
361
+ <div class="insight-label">Blind Decisions Prevented</div>
362
+ <div class="insight-value" id="insight-prevention-rate">--</div>
363
+ <div class="insight-detail" id="insight-prevention-detail"></div>
364
+ </div>
365
+ <div class="insight-card">
366
+ <div class="insight-icon">&#x26A0;</div>
367
+ <div class="insight-label">Warnings Surfaced</div>
368
+ <div class="insight-value" id="insight-warnings-total">--</div>
369
+ <div class="insight-detail" id="insight-warnings-detail"></div>
370
+ </div>
371
+ <div class="insight-card">
372
+ <div class="insight-icon">&#x2705;</div>
373
+ <div class="insight-label">Test Coverage</div>
374
+ <div class="insight-value" id="insight-test-coverage">--</div>
375
+ <div class="insight-detail" id="insight-test-detail"></div>
376
+ </div>
377
+ <div class="insight-card">
378
+ <div class="insight-icon">&#x1F517;</div>
379
+ <div class="insight-label">Commit Traceability</div>
380
+ <div class="insight-value" id="insight-traceability">--</div>
381
+ <div class="insight-detail" id="insight-traceability-detail"></div>
382
+ </div>
383
+ <div class="insight-card">
384
+ <div class="insight-icon">&#x1F4CA;</div>
385
+ <div class="insight-label">Decision Lifecycle</div>
386
+ <div class="insight-value" id="insight-lifecycle">--</div>
387
+ <div class="insight-detail" id="insight-lifecycle-detail"></div>
388
+ </div>
389
+ <div class="insight-card">
390
+ <div class="insight-icon">&#x1F578;</div>
391
+ <div class="insight-label">Knowledge Graph</div>
392
+ <div class="insight-value" id="insight-graph">--</div>
393
+ <div class="insight-detail" id="insight-graph-detail"></div>
394
+ </div>
395
+ <div class="insight-card">
396
+ <div class="insight-icon">&#x1F91D;</div>
397
+ <div class="insight-label">Agent Coordination</div>
398
+ <div class="insight-value" id="insight-coordination">--</div>
399
+ <div class="insight-detail" id="insight-coordination-detail"></div>
400
+ </div>
401
+ </div>
402
+
403
+ <h3 class="insights-heading">Tool Usage</h3>
404
+ <div id="insights-tool-usage">
405
+ <table class="data-table" id="tool-usage-table">
406
+ <thead>
407
+ <tr>
408
+ <th>Tool</th>
409
+ <th>Calls</th>
410
+ <th>Errors</th>
411
+ <th>Avg (ms)</th>
412
+ <th>P95 (ms)</th>
413
+ <th>Last Called</th>
414
+ </tr>
415
+ </thead>
416
+ <tbody></tbody>
417
+ </table>
418
+ </div>
419
+
420
+ <h3 class="insights-heading">Error Breakdown</h3>
421
+ <div id="insights-errors">
422
+ <table class="data-table" id="error-breakdown-table">
423
+ <thead>
424
+ <tr>
425
+ <th>Tool</th>
426
+ <th>Error Code</th>
427
+ <th>Count</th>
428
+ </tr>
429
+ </thead>
430
+ <tbody></tbody>
431
+ </table>
432
+ <p class="placeholder" id="no-errors-msg" style="display:none">No errors recorded</p>
433
+ </div>
434
+ </div>
350
435
  </main>
351
436
 
352
437
  <footer>
@@ -57,6 +57,16 @@ header h1 {
57
57
  font-weight: 600;
58
58
  }
59
59
 
60
+ .github-link {
61
+ color: var(--text-secondary, #6b7280);
62
+ display: flex;
63
+ align-items: center;
64
+ transition: color 0.15s;
65
+ }
66
+ .github-link:hover {
67
+ color: var(--text);
68
+ }
69
+
60
70
  .header-status {
61
71
  display: flex;
62
72
  gap: 1.5rem;
@@ -976,3 +986,76 @@ tr.delegation-expired td { text-decoration: line-through; }
976
986
  font-size: 0.75rem;
977
987
  color: var(--muted);
978
988
  }
989
+
990
+ /* ========== Insights Tab ========== */
991
+
992
+ .insights-heading {
993
+ margin: 1.5rem 0 0.75rem 0;
994
+ font-size: 1rem;
995
+ font-weight: 600;
996
+ color: var(--text);
997
+ }
998
+
999
+ .insights-heading:first-child {
1000
+ margin-top: 0;
1001
+ }
1002
+
1003
+ .insights-grid {
1004
+ display: grid;
1005
+ grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
1006
+ gap: 1rem;
1007
+ }
1008
+
1009
+ .insight-card {
1010
+ background: var(--card-bg);
1011
+ border: 1px solid var(--card-border);
1012
+ border-radius: 8px;
1013
+ padding: 1rem;
1014
+ text-align: center;
1015
+ transition: border-color 0.2s;
1016
+ }
1017
+
1018
+ .insight-card:hover {
1019
+ border-color: var(--accent);
1020
+ }
1021
+
1022
+ .insight-icon {
1023
+ font-size: 1.5rem;
1024
+ margin-bottom: 0.25rem;
1025
+ }
1026
+
1027
+ .insight-label {
1028
+ font-size: 0.75rem;
1029
+ color: var(--muted);
1030
+ text-transform: uppercase;
1031
+ letter-spacing: 0.05em;
1032
+ margin-bottom: 0.25rem;
1033
+ }
1034
+
1035
+ .insight-value {
1036
+ font-size: 1.75rem;
1037
+ font-weight: 700;
1038
+ color: var(--accent);
1039
+ margin-bottom: 0.25rem;
1040
+ }
1041
+
1042
+ .insight-detail {
1043
+ font-size: 0.75rem;
1044
+ color: var(--muted);
1045
+ line-height: 1.4;
1046
+ }
1047
+
1048
+ #insights-tool-usage,
1049
+ #insights-errors {
1050
+ margin-bottom: 1.5rem;
1051
+ }
1052
+
1053
+ #tool-usage-table td,
1054
+ #error-breakdown-table td {
1055
+ padding: 0.5rem 0.75rem;
1056
+ }
1057
+
1058
+ .error-count {
1059
+ color: var(--error);
1060
+ font-weight: 600;
1061
+ }