mcp-efficiency-engine 0.1.0 → 0.1.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 (191) hide show
  1. package/.githooks/post-commit +20 -0
  2. package/.github/workflows/autodocs-sync.yml +2 -15
  3. package/.github/workflows/release-npm.yml +111 -0
  4. package/FINAL_USAGE_GUIDE.md +78 -0
  5. package/README.md +231 -0
  6. package/autodocs/README.md +6 -0
  7. package/bin/install-host.js +26 -1
  8. package/observability/metrics.md +33 -0
  9. package/observability/scoring-rubric.md +20 -0
  10. package/orchestrator/wiki/graph_consolidator.py +60 -1
  11. package/orchestrator/wiki/incremental_engine.py +23 -1
  12. package/orchestrator/wiki/plugin_manager.py +87 -22
  13. package/package.json +10 -2
  14. package/requirements.txt +2 -0
  15. package/scripts/intake/resolve-routing.py +124 -87
  16. package/scripts/intake/validate-database-routing.py +13 -4
  17. package/scripts/intake/validate-rag-routing.py +4 -3
  18. package/scripts/learning/learning-loop-report.py +28 -10
  19. package/scripts/ops/bye.ps1 +30 -0
  20. package/scripts/ops/hi.ps1 +13 -0
  21. package/scripts/ops/llm-token-probe.py +103 -39
  22. package/scripts/ops/post-commit-refresh.ps1 +110 -0
  23. package/scripts/ops/publish-langsmith-kpis.py +302 -0
  24. package/scripts/ops/telemetry-benchmark.py +184 -0
  25. package/scripts/setup/install-project-hooks.ps1 +33 -0
  26. package/scripts/setup/setup-prerequisites.ps1 +42 -15
  27. package/scripts/wiki/compiler_main.py +108 -76
  28. package/scripts/wiki/providers/repo_content_provider.py +51 -0
  29. package/scripts/wiki/validator.py +40 -15
  30. package/telemetry/__init__.py +4 -0
  31. package/telemetry/bootstrap.py +47 -0
  32. package/telemetry/collector/engine.py +261 -0
  33. package/telemetry/config.json +20 -0
  34. package/telemetry/config.py +131 -0
  35. package/telemetry/context/propagation.py +85 -0
  36. package/telemetry/events/catalog.py +24 -0
  37. package/telemetry/events/factory.py +42 -0
  38. package/telemetry/exporters/base.py +16 -0
  39. package/telemetry/exporters/console/exporter.py +30 -0
  40. package/telemetry/exporters/json/exporter.py +34 -0
  41. package/telemetry/exporters/langsmith/exporter.py +415 -0
  42. package/telemetry/metrics/aggregator.py +60 -0
  43. package/telemetry/scoring/efficiency.py +68 -0
  44. package/telemetry/tracing/span.py +43 -0
  45. package/observability/evals/chat-token-usage-report.json +0 -64
  46. package/observability/evals/chat-token-usage-report.md +0 -33
  47. package/observability/evals/iteration-value-report.json +0 -76
  48. package/observability/evals/iteration-value-report.md +0 -39
  49. package/observability/evals/learning-loop-report.json +0 -268
  50. package/observability/evals/learning-loop-report.md +0 -37
  51. package/observability/evals/repo-intake-eval-cases.md +0 -23
  52. package/observability/evals/routing-eval-cases.json +0 -68
  53. package/observability/evals/routing-eval-cases.md +0 -25
  54. package/observability/evals/routing-eval-report.json +0 -72
  55. package/observability/logs/copilot-usage-ingest-report.json +0 -13
  56. package/observability/logs/iteration-metrics.jsonl +0 -75
  57. package/observability/logs/learning-feedback.jsonl +0 -152
  58. package/observability/logs/routing-decisions.jsonl +0 -333
  59. package/observability/logs/session/agent-pipeline-preflight-20260701-004146.log +0 -8
  60. package/observability/logs/session/agent-pipeline-preflight-20260701-005554.log +0 -8
  61. package/observability/logs/session/agent-pipeline-preflight-20260701-010351.log +0 -8
  62. package/observability/logs/session/agent-pipeline-preflight-20260704-145829.log +0 -8
  63. package/observability/logs/session/agent-pipeline-preflight-20260704-150021.log +0 -8
  64. package/observability/logs/session/agent-pipeline-preflight-20260704-154328.log +0 -8
  65. package/observability/logs/session/bye-20260629-221513.json +0 -39
  66. package/observability/logs/session/bye-20260629-221545.json +0 -59
  67. package/observability/logs/session/bye-20260629-222007.json +0 -52
  68. package/observability/logs/session/bye-20260629-222133.json +0 -59
  69. package/observability/logs/session/bye-20260629-222412.json +0 -45
  70. package/observability/logs/session/bye-20260629-223224.json +0 -52
  71. package/observability/logs/session/bye-20260629-224050.json +0 -59
  72. package/observability/logs/session/bye-20260630-105732.json +0 -24
  73. package/observability/logs/session/bye-20260630-211536.json +0 -87
  74. package/observability/logs/session/bye-20260630-223423.json +0 -87
  75. package/observability/logs/session/bye-20260630-232534.json +0 -87
  76. package/observability/logs/session/bye-20260701-003638.json +0 -126
  77. package/observability/logs/session/bye-20260701-003849.json +0 -126
  78. package/observability/logs/session/bye-20260701-004315.json +0 -138
  79. package/observability/logs/session/bye-20260701-004559.json +0 -76
  80. package/observability/logs/session/bye-20260701-005913.json +0 -138
  81. package/observability/logs/session/bye-20260701-010753.json +0 -138
  82. package/observability/logs/session/bye-20260703-194106.json +0 -24
  83. package/observability/logs/session/bye-20260703-233443.json +0 -76
  84. package/observability/logs/session/bye-20260703-233653.json +0 -76
  85. package/observability/logs/session/bye-20260704-150317.json +0 -152
  86. package/observability/logs/session/bye-20260704-150929.json +0 -75
  87. package/observability/logs/session/bye-20260704-151050.json +0 -75
  88. package/observability/logs/session/bye-20260705-115458.json +0 -165
  89. package/observability/logs/session/codegraph-status-20260701-004228.log +0 -39
  90. package/observability/logs/session/codegraph-status-20260701-004310.log +0 -39
  91. package/observability/logs/session/codegraph-status-20260701-005637.log +0 -39
  92. package/observability/logs/session/codegraph-status-20260701-005908.log +0 -39
  93. package/observability/logs/session/codegraph-status-20260701-010444.log +0 -39
  94. package/observability/logs/session/codegraph-status-20260701-010747.log +0 -39
  95. package/observability/logs/session/codegraph-status-20260704-150137.log +0 -26
  96. package/observability/logs/session/codegraph-status-20260704-150313.log +0 -28
  97. package/observability/logs/session/codegraph-status-20260704-154445.log +0 -26
  98. package/observability/logs/session/codegraph-status-20260705-115454.log +0 -27
  99. package/observability/logs/session/codegraph-sync-20260701-004211.log +0 -88
  100. package/observability/logs/session/codegraph-sync-20260701-004241.log +0 -82
  101. package/observability/logs/session/codegraph-sync-20260701-005620.log +0 -89
  102. package/observability/logs/session/codegraph-sync-20260701-005840.log +0 -88
  103. package/observability/logs/session/codegraph-sync-20260701-010419.log +0 -93
  104. package/observability/logs/session/codegraph-sync-20260701-010609.log +0 -90
  105. package/observability/logs/session/codegraph-sync-20260704-145856.log +0 -70
  106. package/observability/logs/session/codegraph-sync-20260704-150047.log +0 -53
  107. package/observability/logs/session/codegraph-sync-20260704-150204.log +0 -44
  108. package/observability/logs/session/codegraph-sync-20260704-154353.log +0 -26
  109. package/observability/logs/session/codegraph-sync-20260705-115337.log +0 -32
  110. package/observability/logs/session/hi-20260629-221459.json +0 -38
  111. package/observability/logs/session/hi-20260629-221945.json +0 -59
  112. package/observability/logs/session/hi-20260629-222108.json +0 -66
  113. package/observability/logs/session/hi-20260629-222352.json +0 -45
  114. package/observability/logs/session/hi-20260629-225719.json +0 -59
  115. package/observability/logs/session/hi-20260629-233722.json +0 -52
  116. package/observability/logs/session/hi-20260629-233913.json +0 -198
  117. package/observability/logs/session/hi-20260630-110156.json +0 -177
  118. package/observability/logs/session/hi-20260630-191243.json +0 -184
  119. package/observability/logs/session/hi-20260630-230050.json +0 -234
  120. package/observability/logs/session/hi-20260630-230523.json +0 -227
  121. package/observability/logs/session/hi-20260630-230855.json +0 -227
  122. package/observability/logs/session/hi-20260630-231638.json +0 -227
  123. package/observability/logs/session/hi-20260630-231756.json +0 -219
  124. package/observability/logs/session/hi-20260701-002842.json +0 -234
  125. package/observability/logs/session/hi-20260701-003458.json +0 -282
  126. package/observability/logs/session/hi-20260701-004233.json +0 -290
  127. package/observability/logs/session/hi-20260701-004554.json +0 -236
  128. package/observability/logs/session/hi-20260701-005641.json +0 -290
  129. package/observability/logs/session/hi-20260701-010450.json +0 -290
  130. package/observability/logs/session/hi-20260703-233854.json +0 -312
  131. package/observability/logs/session/hi-20260703-234448.json +0 -318
  132. package/observability/logs/session/hi-20260704-145907.json +0 -353
  133. package/observability/logs/session/hi-20260704-150141.json +0 -392
  134. package/observability/logs/session/hi-20260704-154448.json +0 -392
  135. package/observability/logs/session/iteration-value-refresh-20260701-004309.log +0 -3
  136. package/observability/logs/session/iteration-value-refresh-20260701-005907.log +0 -3
  137. package/observability/logs/session/iteration-value-refresh-20260701-010747.log +0 -3
  138. package/observability/logs/session/iteration-value-refresh-20260704-150312.log +0 -3
  139. package/observability/logs/session/iteration-value-refresh-20260705-115453.log +0 -3
  140. package/observability/logs/session/learning-loop-refresh-20260701-004309.log +0 -3
  141. package/observability/logs/session/learning-loop-refresh-20260701-005906.log +0 -3
  142. package/observability/logs/session/learning-loop-refresh-20260701-010745.log +0 -3
  143. package/observability/logs/session/learning-loop-refresh-20260704-150309.log +0 -3
  144. package/observability/logs/session/learning-loop-refresh-20260705-115450.log +0 -3
  145. package/observability/logs/session/project-notes-refresh-20260701-004227.log +0 -1
  146. package/observability/logs/session/project-notes-refresh-20260701-004308.log +0 -1
  147. package/observability/logs/session/project-notes-refresh-20260701-005636.log +0 -1
  148. package/observability/logs/session/project-notes-refresh-20260701-005906.log +0 -1
  149. package/observability/logs/session/project-notes-refresh-20260701-010443.log +0 -1
  150. package/observability/logs/session/project-notes-refresh-20260701-010744.log +0 -1
  151. package/observability/logs/session/project-notes-refresh-20260704-150133.log +0 -1
  152. package/observability/logs/session/project-notes-refresh-20260704-150309.log +0 -1
  153. package/observability/logs/session/project-notes-refresh-20260704-154442.log +0 -1
  154. package/observability/logs/session/project-notes-refresh-20260705-115449.log +0 -1
  155. package/observability/logs/session/repo-intake-20260701-004218.log +0 -6
  156. package/observability/logs/session/repo-intake-20260701-005626.log +0 -6
  157. package/observability/logs/session/repo-intake-20260701-010433.log +0 -6
  158. package/observability/logs/session/repo-intake-20260704-150053.log +0 -6
  159. package/observability/logs/session/repo-intake-20260704-154404.log +0 -6
  160. package/observability/logs/session/repo-intake-refresh-20260701-004247.log +0 -6
  161. package/observability/logs/session/repo-intake-refresh-20260701-005847.log +0 -6
  162. package/observability/logs/session/repo-intake-refresh-20260701-010615.log +0 -6
  163. package/observability/logs/session/repo-intake-refresh-20260704-150209.log +0 -6
  164. package/observability/logs/session/repo-intake-refresh-20260705-115342.log +0 -6
  165. package/observability/logs/session/repo-registry-validation-20260701-004234.log +0 -2
  166. package/observability/logs/session/repo-registry-validation-20260701-004556.log +0 -2
  167. package/observability/logs/session/repo-registry-validation-20260701-005833.log +0 -2
  168. package/observability/logs/session/repo-registry-validation-20260701-010601.log +0 -2
  169. package/observability/logs/session/repo-registry-validation-20260704-150155.log +0 -2
  170. package/observability/logs/session/repo-registry-validation-20260705-115323.log +0 -2
  171. package/observability/logs/session/repomix-refresh-20260701-003823.log +0 -4236
  172. package/observability/logs/session/repomix-refresh-20260701-004251.log +0 -4275
  173. package/observability/logs/session/repomix-refresh-20260701-005850.log +0 -4248
  174. package/observability/logs/session/repomix-refresh-20260701-010619.log +0 -6531
  175. package/observability/logs/session/repomix-refresh-20260704-150239.log +0 -652
  176. package/observability/logs/session/repomix-refresh-20260705-115419.log +0 -178
  177. package/observability/logs/session/routing-evals-20260701-004221.log +0 -2
  178. package/observability/logs/session/routing-evals-20260701-004235.log +0 -2
  179. package/observability/logs/session/routing-evals-20260701-005629.log +0 -2
  180. package/observability/logs/session/routing-evals-20260701-005834.log +0 -2
  181. package/observability/logs/session/routing-evals-20260701-010437.log +0 -2
  182. package/observability/logs/session/routing-evals-20260701-010603.log +0 -2
  183. package/observability/logs/session/routing-evals-20260704-150125.log +0 -2
  184. package/observability/logs/session/routing-evals-20260704-150157.log +0 -2
  185. package/observability/logs/session/routing-evals-20260704-154434.log +0 -2
  186. package/observability/logs/session/routing-evals-20260705-115327.log +0 -2
  187. package/observability/logs/session/sibling-discovery-refresh-20260701-004250.log +0 -3
  188. package/observability/logs/session/sibling-discovery-refresh-20260701-005850.log +0 -3
  189. package/observability/logs/session/sibling-discovery-refresh-20260701-010618.log +0 -3
  190. package/observability/logs/session/sibling-discovery-refresh-20260704-150238.log +0 -3
  191. package/observability/logs/session/sibling-discovery-refresh-20260705-115418.log +0 -3
@@ -0,0 +1,302 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ import os
5
+ import sys
6
+ import uuid
7
+ from datetime import datetime, timezone
8
+ from pathlib import Path
9
+ from typing import Any
10
+
11
+ REPO_ROOT = Path(__file__).resolve().parents[2]
12
+ if str(REPO_ROOT) not in sys.path:
13
+ sys.path.insert(0, str(REPO_ROOT))
14
+
15
+ from telemetry.config import load_config
16
+
17
+
18
+ def _read_json(path: Path) -> dict[str, Any]:
19
+ if not path.exists():
20
+ return {}
21
+ try:
22
+ payload = json.loads(path.read_text(encoding="utf-8"))
23
+ except Exception:
24
+ return {}
25
+ return payload if isinstance(payload, dict) else {}
26
+
27
+
28
+ def _iso_now() -> datetime:
29
+ return datetime.now(timezone.utc)
30
+
31
+
32
+ def _slug(value: str) -> str:
33
+ return "".join(ch.lower() if ch.isalnum() else "-" for ch in value).strip("-")
34
+
35
+
36
+ def _resolve_host_scope(repo_root: Path) -> tuple[str, str]:
37
+ host_project = (os.getenv("MCPEE_HOST_PROJECT") or repo_root.name or "unknown-project").strip()
38
+ host_slug = _slug(host_project) or "unknown-project"
39
+ platform_slugs = {"mcp-efficiency-engine", "efficiency", "mcpee"}
40
+ scope = "platform" if host_slug in platform_slugs else "consumer"
41
+ return host_project, scope
42
+
43
+
44
+ def _flatten_kpis(payload: dict[str, Any]) -> dict[str, Any]:
45
+ flat: dict[str, Any] = {}
46
+ for key, value in payload.items():
47
+ flat[f"kpi_{key}"] = value
48
+ return flat
49
+
50
+
51
+ def _publish_run(
52
+ client: Any,
53
+ *,
54
+ project: str,
55
+ name: str,
56
+ inputs: dict[str, Any],
57
+ outputs: dict[str, Any],
58
+ tags: list[str],
59
+ metadata: dict[str, Any] | None = None,
60
+ total_cost: float | None = None,
61
+ total_tokens: int | None = None,
62
+ run_type: str = "chain",
63
+ ) -> str:
64
+ now = _iso_now()
65
+ run_id = str(uuid.uuid4())
66
+ payload: dict[str, Any] = {
67
+ "id": run_id,
68
+ "project_name": project,
69
+ "name": name,
70
+ "run_type": run_type,
71
+ "inputs": inputs,
72
+ "outputs": outputs,
73
+ "start_time": now,
74
+ "end_time": now,
75
+ "tags": tags,
76
+ "extra": {"metadata": metadata or {}},
77
+ }
78
+
79
+ if total_cost is not None:
80
+ payload["total_cost"] = float(total_cost)
81
+ payload["prompt_cost"] = 0.0
82
+ payload["completion_cost"] = float(total_cost)
83
+ if total_tokens is not None:
84
+ payload["total_tokens"] = int(total_tokens)
85
+ # For LLM-compatible dashboard metrics, expose token fields explicitly.
86
+ payload["prompt_tokens"] = 0
87
+ payload["completion_tokens"] = int(total_tokens)
88
+
89
+ usage_metadata: dict[str, Any] = {
90
+ "input_tokens": int(payload.get("prompt_tokens", 0) or 0),
91
+ "output_tokens": int(payload.get("completion_tokens", 0) or 0),
92
+ "total_tokens": int(payload.get("total_tokens", 0) or 0),
93
+ "total_cost": float(payload.get("total_cost", 0.0) or 0.0),
94
+ }
95
+
96
+ outputs_kpi = payload.setdefault("outputs", {})
97
+ outputs_kpi["usage_metadata"] = usage_metadata
98
+
99
+ meta = payload.setdefault("extra", {}).setdefault("metadata", {})
100
+ meta["usage_metadata"] = usage_metadata
101
+
102
+ client.create_run(**payload)
103
+ return run_id
104
+
105
+
106
+ def _publish_feedback_metrics(client: Any, *, run_id: str, metrics: dict[str, Any], source: str) -> None:
107
+ for key, raw_value in metrics.items():
108
+ try:
109
+ value = float(raw_value)
110
+ except Exception:
111
+ continue
112
+
113
+ try:
114
+ client.create_feedback(
115
+ run_id=run_id,
116
+ key=str(key),
117
+ score=value,
118
+ comment=f"mcpee kpi:{source}",
119
+ )
120
+ except Exception:
121
+ # Feedback is optional. Keep publishing resilient.
122
+ continue
123
+
124
+
125
+ def main() -> int:
126
+ repo_root = REPO_ROOT
127
+ cfg = load_config(repo_root)
128
+ host_project, telemetry_scope = _resolve_host_scope(repo_root)
129
+ host_slug = _slug(host_project)
130
+
131
+ if not cfg.langsmith.enabled:
132
+ print("LangSmith disabled in effective config. Nothing to publish.")
133
+ return 0
134
+
135
+ if not cfg.langsmith.api_key.strip() or not cfg.langsmith.project.strip():
136
+ print("LangSmith config missing api_key or project. Nothing to publish.")
137
+ return 0
138
+
139
+ try:
140
+ from langsmith import Client # type: ignore
141
+ except Exception as exc:
142
+ print(f"langsmith SDK is not available: {exc}")
143
+ return 1
144
+
145
+ kwargs: dict[str, Any] = {"api_key": cfg.langsmith.api_key}
146
+ if cfg.langsmith.endpoint:
147
+ kwargs["api_url"] = cfg.langsmith.endpoint
148
+ if cfg.langsmith.workspace_id:
149
+ kwargs["workspace_id"] = cfg.langsmith.workspace_id
150
+
151
+ client = Client(**kwargs)
152
+
153
+ learning_path = repo_root / "observability" / "evals" / "learning-loop-report.json"
154
+ value_path = repo_root / "observability" / "evals" / "iteration-value-report.json"
155
+
156
+ learning = _read_json(learning_path)
157
+ value = _read_json(value_path)
158
+
159
+ published = 0
160
+
161
+ if learning:
162
+ learning_kpis = learning.get("kpis", {}) if isinstance(learning.get("kpis", {}), dict) else {}
163
+ learning_flat_kpis = _flatten_kpis(learning_kpis)
164
+ run_id = _publish_run(
165
+ client,
166
+ project=cfg.langsmith.project,
167
+ name="KPI::LearningLoop",
168
+ inputs={
169
+ "source_file": str(learning_path),
170
+ "timestamp": learning.get("timestamp"),
171
+ },
172
+ outputs={
173
+ "kpis": learning_kpis,
174
+ **learning_flat_kpis,
175
+ "health": learning.get("health", {}),
176
+ "totals": {
177
+ "total_events": learning.get("total_events"),
178
+ "confirmed_events": learning.get("confirmed_events"),
179
+ "pending_events": learning.get("pending_events"),
180
+ },
181
+ },
182
+ tags=[
183
+ "mcpee",
184
+ "kpi",
185
+ "learning-loop",
186
+ "dashboard",
187
+ f"scope:{telemetry_scope}",
188
+ f"host:{host_slug}",
189
+ ],
190
+ metadata={
191
+ "kpi_source": "learning-loop",
192
+ "kpi_kind": "snapshot",
193
+ "host_project": host_project,
194
+ "host_project_slug": host_slug,
195
+ "telemetry_scope": telemetry_scope,
196
+ **learning_flat_kpis,
197
+ },
198
+ )
199
+ _publish_feedback_metrics(client, run_id=run_id, metrics=learning_flat_kpis, source="learning-loop")
200
+ published += 1
201
+
202
+ if value:
203
+ value_kpis = value.get("kpis", {}) if isinstance(value.get("kpis", {}), dict) else {}
204
+ value_flat_kpis = _flatten_kpis(value_kpis)
205
+ run_id = _publish_run(
206
+ client,
207
+ project=cfg.langsmith.project,
208
+ name="KPI::IterationValue",
209
+ inputs={
210
+ "source_file": str(value_path),
211
+ "timestamp": value.get("timestamp"),
212
+ },
213
+ outputs={
214
+ "kpis": value_kpis,
215
+ **value_flat_kpis,
216
+ "totals": value.get("totals", {}),
217
+ "assessment": value.get("assessment", {}),
218
+ },
219
+ tags=[
220
+ "mcpee",
221
+ "kpi",
222
+ "iteration-value",
223
+ "dashboard",
224
+ f"scope:{telemetry_scope}",
225
+ f"host:{host_slug}",
226
+ ],
227
+ metadata={
228
+ "kpi_source": "iteration-value",
229
+ "kpi_kind": "snapshot",
230
+ "host_project": host_project,
231
+ "host_project_slug": host_slug,
232
+ "telemetry_scope": telemetry_scope,
233
+ **value_flat_kpis,
234
+ },
235
+ total_cost=float(value_kpis.get("total_cost_usd", 0.0) or 0.0),
236
+ total_tokens=int(value_kpis.get("total_tokens", 0) or 0),
237
+ run_type="llm",
238
+ )
239
+ _publish_feedback_metrics(client, run_id=run_id, metrics=value_flat_kpis, source="iteration-value")
240
+ published += 1
241
+
242
+ if learning or value:
243
+ learning_kpis = learning.get("kpis", {}) if isinstance(learning.get("kpis", {}), dict) else {}
244
+ value_kpis = value.get("kpis", {}) if isinstance(value.get("kpis", {}), dict) else {}
245
+ learning_flat_kpis = _flatten_kpis(learning_kpis)
246
+ value_flat_kpis = _flatten_kpis(value_kpis)
247
+ run_id = _publish_run(
248
+ client,
249
+ project=cfg.langsmith.project,
250
+ name="KPI::AlignmentSnapshot",
251
+ inputs={
252
+ "source": "local-observability-evals",
253
+ "learning_present": bool(learning),
254
+ "value_present": bool(value),
255
+ },
256
+ outputs={
257
+ "learning_kpis": learning_kpis,
258
+ "value_kpis": value_kpis,
259
+ **{f"learning_{k}": v for k, v in learning_flat_kpis.items()},
260
+ **{f"value_{k}": v for k, v in value_flat_kpis.items()},
261
+ "published_at": _iso_now().isoformat(),
262
+ },
263
+ tags=[
264
+ "mcpee",
265
+ "kpi",
266
+ "alignment",
267
+ "dashboard",
268
+ f"scope:{telemetry_scope}",
269
+ f"host:{host_slug}",
270
+ ],
271
+ metadata={
272
+ "kpi_source": "alignment",
273
+ "kpi_kind": "snapshot",
274
+ "host_project": host_project,
275
+ "host_project_slug": host_slug,
276
+ "telemetry_scope": telemetry_scope,
277
+ **{f"learning_{k}": v for k, v in learning_flat_kpis.items()},
278
+ **{f"value_{k}": v for k, v in value_flat_kpis.items()},
279
+ },
280
+ total_cost=float(value_kpis.get("total_cost_usd", 0.0) or 0.0),
281
+ total_tokens=int(value_kpis.get("total_tokens", 0) or 0),
282
+ run_type="llm",
283
+ )
284
+ alignment_feedback = {
285
+ **{f"learning_{k}": v for k, v in learning_flat_kpis.items()},
286
+ **{f"value_{k}": v for k, v in value_flat_kpis.items()},
287
+ }
288
+ _publish_feedback_metrics(client, run_id=run_id, metrics=alignment_feedback, source="alignment")
289
+ published += 1
290
+
291
+ print(
292
+ f"Published KPI runs: {published} to project '{cfg.langsmith.project}' "
293
+ f"(host_project='{host_project}', scope='{telemetry_scope}')."
294
+ )
295
+ if published == 0:
296
+ print("No local KPI report JSON files were found in observability/evals.")
297
+
298
+ return 0
299
+
300
+
301
+ if __name__ == "__main__":
302
+ raise SystemExit(main())
@@ -0,0 +1,184 @@
1
+ from __future__ import annotations
2
+
3
+ import argparse
4
+ import json
5
+ import os
6
+ import statistics
7
+ import subprocess
8
+ import time
9
+ from datetime import datetime, timezone
10
+ from pathlib import Path
11
+ from typing import Any
12
+
13
+
14
+ def utc_now() -> str:
15
+ return datetime.now(timezone.utc).isoformat()
16
+
17
+
18
+ def build_command(python_exe: str) -> list[str]:
19
+ return [
20
+ python_exe,
21
+ "scripts/intake/resolve-routing.py",
22
+ "--input",
23
+ "telemetry benchmark",
24
+ "--intent",
25
+ "feature",
26
+ "--domain",
27
+ "backend",
28
+ "--source-type",
29
+ "code",
30
+ "--input-tokens",
31
+ "120",
32
+ "--output-tokens",
33
+ "45",
34
+ "--estimated-cost-usd",
35
+ "0.0032",
36
+ "--model",
37
+ "GPT-5.3-Codex",
38
+ "--local-tools",
39
+ "2",
40
+ "--remote-tools",
41
+ "1",
42
+ ]
43
+
44
+
45
+ def run_variant(*, repo_root: Path, iterations: int, telemetry_enabled: bool, python_exe: str) -> dict[str, Any]:
46
+ durations_ms: list[float] = []
47
+ failures = 0
48
+ for _ in range(iterations):
49
+ env = os.environ.copy()
50
+ env["TELEMETRY_ENABLED"] = "true" if telemetry_enabled else "false"
51
+ env["TELEMETRY_EXPORTERS"] = "console,json"
52
+
53
+ start = time.perf_counter()
54
+ proc = subprocess.run(
55
+ build_command(python_exe),
56
+ cwd=str(repo_root),
57
+ env=env,
58
+ stdout=subprocess.DEVNULL,
59
+ stderr=subprocess.DEVNULL,
60
+ check=False,
61
+ )
62
+ elapsed_ms = (time.perf_counter() - start) * 1000.0
63
+ durations_ms.append(elapsed_ms)
64
+ if proc.returncode != 0:
65
+ failures += 1
66
+
67
+ avg_ms = statistics.mean(durations_ms) if durations_ms else 0.0
68
+ p95_ms = statistics.quantiles(durations_ms, n=20)[18] if len(durations_ms) >= 20 else max(durations_ms, default=0.0)
69
+
70
+ return {
71
+ "telemetry_enabled": telemetry_enabled,
72
+ "iterations": iterations,
73
+ "failures": failures,
74
+ "avg_ms": round(avg_ms, 3),
75
+ "min_ms": round(min(durations_ms) if durations_ms else 0.0, 3),
76
+ "max_ms": round(max(durations_ms) if durations_ms else 0.0, 3),
77
+ "p95_ms": round(p95_ms, 3),
78
+ }
79
+
80
+
81
+ def main() -> int:
82
+ parser = argparse.ArgumentParser(description="Benchmark telemetry overhead on/off using resolve-routing as workload.")
83
+ parser.add_argument("--iterations", type=int, default=10)
84
+ parser.add_argument("--python", default="py")
85
+ parser.add_argument("--python-arg", default="-3")
86
+ parser.add_argument("--out", default="observability/evals/telemetry-overhead-benchmark.json")
87
+ args = parser.parse_args()
88
+
89
+ repo_root = Path(__file__).resolve().parents[2]
90
+ iterations = max(2, int(args.iterations))
91
+ python_exe = args.python
92
+
93
+ # Windows default launcher pattern: py -3
94
+ if args.python == "py" and args.python_arg:
95
+ python_exe = f"{args.python} {args.python_arg}" # handled below
96
+
97
+ if " " in python_exe:
98
+ tokens = python_exe.split()
99
+ base = tokens[0]
100
+ extra = tokens[1:]
101
+ else:
102
+ base = python_exe
103
+ extra = []
104
+
105
+ def build_python() -> str:
106
+ if not extra:
107
+ return base
108
+ return base
109
+
110
+ # Probe python command in current environment.
111
+ probe = subprocess.run([base, *extra, "--version"], cwd=str(repo_root), stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=False)
112
+ if probe.returncode != 0:
113
+ print("Unable to resolve python interpreter for benchmark")
114
+ return 1
115
+
116
+ def command_python() -> str:
117
+ return base
118
+
119
+ # Wrap variant runner to include optional extra interpreter args.
120
+ def run_with_variant(telemetry_enabled: bool) -> dict[str, Any]:
121
+ durations_ms: list[float] = []
122
+ failures = 0
123
+ for _ in range(iterations):
124
+ env = os.environ.copy()
125
+ env["TELEMETRY_ENABLED"] = "true" if telemetry_enabled else "false"
126
+ env["TELEMETRY_EXPORTERS"] = "console,json"
127
+ cmd = [base, *extra, *build_command(command_python())[1:]]
128
+
129
+ start = time.perf_counter()
130
+ proc = subprocess.run(
131
+ cmd,
132
+ cwd=str(repo_root),
133
+ env=env,
134
+ stdout=subprocess.DEVNULL,
135
+ stderr=subprocess.DEVNULL,
136
+ check=False,
137
+ )
138
+ elapsed_ms = (time.perf_counter() - start) * 1000.0
139
+ durations_ms.append(elapsed_ms)
140
+ if proc.returncode != 0:
141
+ failures += 1
142
+
143
+ avg_ms = statistics.mean(durations_ms) if durations_ms else 0.0
144
+ p95_ms = statistics.quantiles(durations_ms, n=20)[18] if len(durations_ms) >= 20 else max(durations_ms, default=0.0)
145
+
146
+ return {
147
+ "telemetry_enabled": telemetry_enabled,
148
+ "iterations": iterations,
149
+ "failures": failures,
150
+ "avg_ms": round(avg_ms, 3),
151
+ "min_ms": round(min(durations_ms) if durations_ms else 0.0, 3),
152
+ "max_ms": round(max(durations_ms) if durations_ms else 0.0, 3),
153
+ "p95_ms": round(p95_ms, 3),
154
+ }
155
+
156
+ enabled = run_with_variant(True)
157
+ disabled = run_with_variant(False)
158
+
159
+ overhead_ms = enabled["avg_ms"] - disabled["avg_ms"]
160
+ overhead_pct = ((overhead_ms / disabled["avg_ms"]) * 100.0) if disabled["avg_ms"] > 0 else 0.0
161
+
162
+ report = {
163
+ "timestamp": utc_now(),
164
+ "workload": "scripts/intake/resolve-routing.py",
165
+ "iterations": iterations,
166
+ "with_telemetry": enabled,
167
+ "without_telemetry": disabled,
168
+ "overhead": {
169
+ "avg_ms": round(overhead_ms, 3),
170
+ "avg_pct": round(overhead_pct, 3),
171
+ },
172
+ }
173
+
174
+ out_path = (repo_root / args.out).resolve()
175
+ out_path.parent.mkdir(parents=True, exist_ok=True)
176
+ out_path.write_text(json.dumps(report, indent=2, ensure_ascii=False) + "\n", encoding="utf-8")
177
+
178
+ print(json.dumps(report, indent=2, ensure_ascii=False))
179
+ print(f"Benchmark report: {out_path}")
180
+ return 0
181
+
182
+
183
+ if __name__ == "__main__":
184
+ raise SystemExit(main())
@@ -0,0 +1,33 @@
1
+ param(
2
+ [switch]$Force
3
+ )
4
+
5
+ Set-StrictMode -Version Latest
6
+ $ErrorActionPreference = 'Stop'
7
+
8
+ $repoRoot = (& git rev-parse --show-toplevel 2>$null)
9
+ if (-not $repoRoot) {
10
+ throw 'Not inside a git repository.'
11
+ }
12
+
13
+ Set-Location $repoRoot
14
+ $hooksDir = Join-Path $repoRoot '.githooks'
15
+ $postCommitHook = Join-Path $hooksDir 'post-commit'
16
+
17
+ if (-not (Test-Path $postCommitHook)) {
18
+ throw "Missing hook template: $postCommitHook"
19
+ }
20
+
21
+ $currentHooksPath = (& git config --local --get core.hooksPath 2>$null)
22
+ if (-not $Force -and $currentHooksPath -and $currentHooksPath -ne '.githooks') {
23
+ Write-Warning "core.hooksPath is already set to '$currentHooksPath'. Skipping update. Use -Force to override."
24
+ exit 0
25
+ }
26
+
27
+ & git config --local core.hooksPath .githooks
28
+ if ($LASTEXITCODE -ne 0) {
29
+ throw 'Failed to configure core.hooksPath.'
30
+ }
31
+
32
+ Write-Host '[mcpee] Git hooks configured: core.hooksPath=.githooks'
33
+ Write-Host '[mcpee] Hook active: post-commit -> scripts/ops/post-commit-refresh.ps1'
@@ -111,12 +111,36 @@ function Install-PythonRequirements {
111
111
 
112
112
  function Resolve-PythonCommand {
113
113
  if (Test-Command 'py') {
114
- return @('py', '-3.14')
114
+ return @('py', '-3')
115
115
  }
116
116
  if (Test-Command 'python') {
117
117
  return @('python')
118
118
  }
119
- throw 'Python 3.14 is required for graphify MCP. Install Python first: https://www.python.org/downloads/'
119
+ throw 'Python 3 is required for MCP scripts. Install Python first: https://www.python.org/downloads/'
120
+ }
121
+
122
+ function Ensure-PythonModuleInstalled {
123
+ param(
124
+ [Parameter(Mandatory = $true)][string]$PythonCommand,
125
+ [string[]]$PythonArgs = @(),
126
+ [Parameter(Mandatory = $true)][string]$Module,
127
+ [Parameter(Mandatory = $true)][string]$Package
128
+ )
129
+
130
+ if (Test-PythonImport -PythonCommand $PythonCommand -PythonArgs $PythonArgs -Module $Module) {
131
+ Write-Host "[ok] Python module '$Module' already available"
132
+ return
133
+ }
134
+
135
+ Write-Host "[install] $PythonCommand $($PythonArgs -join ' ') -m pip install $Package"
136
+ & $PythonCommand @PythonArgs -m pip install $Package
137
+ if ($LASTEXITCODE -ne 0) {
138
+ throw "Failed to install Python package '$Package'"
139
+ }
140
+
141
+ if (-not (Test-PythonImport -PythonCommand $PythonCommand -PythonArgs $PythonArgs -Module $Module)) {
142
+ throw "Python module '$Module' is still unavailable after installing '$Package'"
143
+ }
120
144
  }
121
145
 
122
146
  Write-Host '== MCP platform prerequisites setup =='
@@ -159,21 +183,24 @@ if (-not $SkipRepomix) {
159
183
  Install-ToolFromManifest -Tool $toolByCommand['repomix']
160
184
  }
161
185
 
162
- if (-not $SkipGraphify) {
163
- $py = Resolve-PythonCommand
164
- $pyCmd = $py[0]
165
- $pyArgs = @()
166
- if ($py.Length -gt 1) {
167
- $pyArgs = $py[1..($py.Length - 1)]
168
- }
186
+ $py = Resolve-PythonCommand
187
+ $pyCmd = $py[0]
188
+ $pyArgs = @()
189
+ if ($py.Length -gt 1) {
190
+ $pyArgs = $py[1..($py.Length - 1)]
191
+ }
169
192
 
170
- if ($py.Length -gt 1) {
171
- Write-Host "[setup] Using Python launcher: $($py -join ' ')"
172
- }
173
- else {
174
- Write-Host "[setup] Using Python launcher: $pyCmd"
175
- }
193
+ if ($py.Length -gt 1) {
194
+ Write-Host "[setup] Using Python launcher: $($py -join ' ')"
195
+ }
196
+ else {
197
+ Write-Host "[setup] Using Python launcher: $pyCmd"
198
+ }
199
+
200
+ # Core observability dependency used by KPI publishing and telemetry export.
201
+ Ensure-PythonModuleInstalled -PythonCommand $pyCmd -PythonArgs $pyArgs -Module 'langsmith' -Package 'langsmith'
176
202
 
203
+ if (-not $SkipGraphify) {
177
204
  Install-PythonRequirements -PythonCommand $pyCmd -PythonArgs $pyArgs -RequirementsPath 'requirements.txt'
178
205
 
179
206
  if (-not (Test-PythonImport -PythonCommand $pyCmd -PythonArgs $pyArgs -Module 'graphify.serve')) {