mcp-efficiency-engine 0.1.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 (360) hide show
  1. package/.github/CODEOWNERS +40 -0
  2. package/.github/GITHUB_MCP_INTEGRATION.md +366 -0
  3. package/.github/ISSUE_TEMPLATE/bug.md +51 -0
  4. package/.github/ISSUE_TEMPLATE/feature_request.md +45 -0
  5. package/.github/ISSUE_TEMPLATE/question.md +18 -0
  6. package/.github/agents/backend.agent.md +18 -0
  7. package/.github/agents/community-manager.agent.md +18 -0
  8. package/.github/agents/dba.agent.md +18 -0
  9. package/.github/agents/frontend-agent.agent.md +18 -0
  10. package/.github/agents/github-repository-manager.agent.md +338 -0
  11. package/.github/agents/iot.agent.md +20 -0
  12. package/.github/agents/legacy.agent.md +18 -0
  13. package/.github/agents/rag-azure.agent.md +18 -0
  14. package/.github/agents/rag-local.agent.md +18 -0
  15. package/.github/agents/snapshot.agent.md +18 -0
  16. package/.github/agents/ux-ui.agent.md +18 -0
  17. package/.github/agents/wiki-agent.agent.md +23 -0
  18. package/.github/copilot-instructions.md +44 -0
  19. package/.github/instructions/always-on-optimization.instructions.md +54 -0
  20. package/.github/prompts/auto-route.prompt.md +33 -0
  21. package/.github/prompts/azure-rag.query.prompt.md +19 -0
  22. package/.github/prompts/backend.fix-bug.prompt.md +13 -0
  23. package/.github/prompts/cavecrew.prompt.md +16 -0
  24. package/.github/prompts/caveman-commit.prompt.md +14 -0
  25. package/.github/prompts/caveman-compress.prompt.md +15 -0
  26. package/.github/prompts/caveman-debug.prompt.md +13 -0
  27. package/.github/prompts/caveman-help.prompt.md +11 -0
  28. package/.github/prompts/caveman-review.prompt.md +15 -0
  29. package/.github/prompts/caveman-stats.prompt.md +11 -0
  30. package/.github/prompts/caveman.prompt.md +11 -0
  31. package/.github/prompts/community.post.prompt.md +14 -0
  32. package/.github/prompts/dba.query-review.prompt.md +15 -0
  33. package/.github/prompts/dev.fix-bug.prompt.md +13 -0
  34. package/.github/prompts/frontend.code.prompt.md +13 -0
  35. package/.github/prompts/legacy.impact-analysis.prompt.md +14 -0
  36. package/.github/prompts/project.kickoff-analysis.prompt.md +51 -0
  37. package/.github/prompts/rag.knowledge-answer.prompt.md +13 -0
  38. package/.github/prompts/token-saver-review.prompt.md +14 -0
  39. package/.github/prompts/ux-ui.review.prompt.md +14 -0
  40. package/.github/pull_request_template.md +71 -0
  41. package/.github/skills/ahorro-tokens/SKILL.md +36 -0
  42. package/.github/skills/azure-rag-enterprise/SKILL.md +24 -0
  43. package/.github/skills/backend-coding/SKILL.md +24 -0
  44. package/.github/skills/cavecrew/SKILL.md +17 -0
  45. package/.github/skills/caveman/SKILL.md +25 -0
  46. package/.github/skills/caveman-commit/SKILL.md +15 -0
  47. package/.github/skills/caveman-compress/SKILL.md +14 -0
  48. package/.github/skills/caveman-help/SKILL.md +19 -0
  49. package/.github/skills/caveman-mode/SKILL.md +43 -0
  50. package/.github/skills/caveman-review/SKILL.md +16 -0
  51. package/.github/skills/caveman-stats/SKILL.md +13 -0
  52. package/.github/skills/community-content/SKILL.md +22 -0
  53. package/.github/skills/database-analysis/SKILL.md +23 -0
  54. package/.github/skills/dev-coding/SKILL.md +24 -0
  55. package/.github/skills/frontend-coding/SKILL.md +24 -0
  56. package/.github/skills/human-in-the-loop/SKILL.md +47 -0
  57. package/.github/skills/iot-architecture/SKILL.md +22 -0
  58. package/.github/skills/legacy-migration/SKILL.md +22 -0
  59. package/.github/skills/rag-knowledge/SKILL.md +21 -0
  60. package/.github/skills/token-saver/SKILL.md +36 -0
  61. package/.github/skills/ux-ui-governance/SKILL.md +24 -0
  62. package/.github/skills/wiki-tools.json +24 -0
  63. package/.github/workflows/auto-devlog.yml +56 -0
  64. package/.github/workflows/auto-manage-issues.yml +64 -0
  65. package/.github/workflows/auto-sync.yml +54 -0
  66. package/.github/workflows/autodocs-sync.yml +114 -0
  67. package/.github/workflows/ci.yml +68 -0
  68. package/.github/workflows/pages.yml +59 -0
  69. package/.github/workflows/routing-evals.yml +59 -0
  70. package/.github/workflows/techriders-coverage-effective.yml +91 -0
  71. package/.vscode/mcp.json +44 -0
  72. package/AGENTS.md +160 -0
  73. package/ARCHITECTURE.md +57 -0
  74. package/FINAL_USAGE_GUIDE.md +106 -0
  75. package/LICENSE +21 -0
  76. package/README.md +385 -0
  77. package/README_WIKI.md +130 -0
  78. package/autodocs/README.md +22 -0
  79. package/autodocs/schema/wiki-node.schema.json +56 -0
  80. package/autolearning/feedback-loop.md +90 -0
  81. package/bin/install-host.js +326 -0
  82. package/bin/mcpee.js +116 -0
  83. package/context/repomix/repomix.config.json +20 -0
  84. package/memory/cross-memory-reasoning.md +17 -0
  85. package/memory/mempry-registry.yml +16 -0
  86. package/observability/evals/chat-token-usage-report.json +64 -0
  87. package/observability/evals/chat-token-usage-report.md +33 -0
  88. package/observability/evals/iteration-value-report.json +76 -0
  89. package/observability/evals/iteration-value-report.md +39 -0
  90. package/observability/evals/learning-loop-report.json +268 -0
  91. package/observability/evals/learning-loop-report.md +37 -0
  92. package/observability/evals/repo-intake-eval-cases.md +23 -0
  93. package/observability/evals/routing-eval-cases.json +68 -0
  94. package/observability/evals/routing-eval-cases.md +25 -0
  95. package/observability/evals/routing-eval-report.json +72 -0
  96. package/observability/evaluation.md +34 -0
  97. package/observability/logs/copilot-usage-ingest-report.json +13 -0
  98. package/observability/logs/iteration-metrics.jsonl +75 -0
  99. package/observability/logs/learning-feedback.jsonl +152 -0
  100. package/observability/logs/routing-decisions.jsonl +333 -0
  101. package/observability/logs/session/agent-pipeline-preflight-20260701-004146.log +8 -0
  102. package/observability/logs/session/agent-pipeline-preflight-20260701-005554.log +8 -0
  103. package/observability/logs/session/agent-pipeline-preflight-20260701-010351.log +8 -0
  104. package/observability/logs/session/agent-pipeline-preflight-20260704-145829.log +8 -0
  105. package/observability/logs/session/agent-pipeline-preflight-20260704-150021.log +8 -0
  106. package/observability/logs/session/agent-pipeline-preflight-20260704-154328.log +8 -0
  107. package/observability/logs/session/bye-20260629-221513.json +39 -0
  108. package/observability/logs/session/bye-20260629-221545.json +59 -0
  109. package/observability/logs/session/bye-20260629-222007.json +52 -0
  110. package/observability/logs/session/bye-20260629-222133.json +59 -0
  111. package/observability/logs/session/bye-20260629-222412.json +45 -0
  112. package/observability/logs/session/bye-20260629-223224.json +52 -0
  113. package/observability/logs/session/bye-20260629-224050.json +59 -0
  114. package/observability/logs/session/bye-20260630-105732.json +24 -0
  115. package/observability/logs/session/bye-20260630-211536.json +87 -0
  116. package/observability/logs/session/bye-20260630-223423.json +87 -0
  117. package/observability/logs/session/bye-20260630-232534.json +87 -0
  118. package/observability/logs/session/bye-20260701-003638.json +126 -0
  119. package/observability/logs/session/bye-20260701-003849.json +126 -0
  120. package/observability/logs/session/bye-20260701-004315.json +138 -0
  121. package/observability/logs/session/bye-20260701-004559.json +76 -0
  122. package/observability/logs/session/bye-20260701-005913.json +138 -0
  123. package/observability/logs/session/bye-20260701-010753.json +138 -0
  124. package/observability/logs/session/bye-20260703-194106.json +24 -0
  125. package/observability/logs/session/bye-20260703-233443.json +76 -0
  126. package/observability/logs/session/bye-20260703-233653.json +76 -0
  127. package/observability/logs/session/bye-20260704-150317.json +152 -0
  128. package/observability/logs/session/bye-20260704-150929.json +75 -0
  129. package/observability/logs/session/bye-20260704-151050.json +75 -0
  130. package/observability/logs/session/bye-20260705-115458.json +165 -0
  131. package/observability/logs/session/codegraph-status-20260701-004228.log +39 -0
  132. package/observability/logs/session/codegraph-status-20260701-004310.log +39 -0
  133. package/observability/logs/session/codegraph-status-20260701-005637.log +39 -0
  134. package/observability/logs/session/codegraph-status-20260701-005908.log +39 -0
  135. package/observability/logs/session/codegraph-status-20260701-010444.log +39 -0
  136. package/observability/logs/session/codegraph-status-20260701-010747.log +39 -0
  137. package/observability/logs/session/codegraph-status-20260704-150137.log +26 -0
  138. package/observability/logs/session/codegraph-status-20260704-150313.log +28 -0
  139. package/observability/logs/session/codegraph-status-20260704-154445.log +26 -0
  140. package/observability/logs/session/codegraph-status-20260705-115454.log +27 -0
  141. package/observability/logs/session/codegraph-sync-20260701-004211.log +88 -0
  142. package/observability/logs/session/codegraph-sync-20260701-004241.log +82 -0
  143. package/observability/logs/session/codegraph-sync-20260701-005620.log +89 -0
  144. package/observability/logs/session/codegraph-sync-20260701-005840.log +88 -0
  145. package/observability/logs/session/codegraph-sync-20260701-010419.log +93 -0
  146. package/observability/logs/session/codegraph-sync-20260701-010609.log +90 -0
  147. package/observability/logs/session/codegraph-sync-20260704-145856.log +70 -0
  148. package/observability/logs/session/codegraph-sync-20260704-150047.log +53 -0
  149. package/observability/logs/session/codegraph-sync-20260704-150204.log +44 -0
  150. package/observability/logs/session/codegraph-sync-20260704-154353.log +26 -0
  151. package/observability/logs/session/codegraph-sync-20260705-115337.log +32 -0
  152. package/observability/logs/session/hi-20260629-221459.json +38 -0
  153. package/observability/logs/session/hi-20260629-221945.json +59 -0
  154. package/observability/logs/session/hi-20260629-222108.json +66 -0
  155. package/observability/logs/session/hi-20260629-222352.json +45 -0
  156. package/observability/logs/session/hi-20260629-225719.json +59 -0
  157. package/observability/logs/session/hi-20260629-233722.json +52 -0
  158. package/observability/logs/session/hi-20260629-233913.json +198 -0
  159. package/observability/logs/session/hi-20260630-110156.json +177 -0
  160. package/observability/logs/session/hi-20260630-191243.json +184 -0
  161. package/observability/logs/session/hi-20260630-230050.json +234 -0
  162. package/observability/logs/session/hi-20260630-230523.json +227 -0
  163. package/observability/logs/session/hi-20260630-230855.json +227 -0
  164. package/observability/logs/session/hi-20260630-231638.json +227 -0
  165. package/observability/logs/session/hi-20260630-231756.json +219 -0
  166. package/observability/logs/session/hi-20260701-002842.json +234 -0
  167. package/observability/logs/session/hi-20260701-003458.json +282 -0
  168. package/observability/logs/session/hi-20260701-004233.json +290 -0
  169. package/observability/logs/session/hi-20260701-004554.json +236 -0
  170. package/observability/logs/session/hi-20260701-005641.json +290 -0
  171. package/observability/logs/session/hi-20260701-010450.json +290 -0
  172. package/observability/logs/session/hi-20260703-233854.json +312 -0
  173. package/observability/logs/session/hi-20260703-234448.json +318 -0
  174. package/observability/logs/session/hi-20260704-145907.json +353 -0
  175. package/observability/logs/session/hi-20260704-150141.json +392 -0
  176. package/observability/logs/session/hi-20260704-154448.json +392 -0
  177. package/observability/logs/session/iteration-value-refresh-20260701-004309.log +3 -0
  178. package/observability/logs/session/iteration-value-refresh-20260701-005907.log +3 -0
  179. package/observability/logs/session/iteration-value-refresh-20260701-010747.log +3 -0
  180. package/observability/logs/session/iteration-value-refresh-20260704-150312.log +3 -0
  181. package/observability/logs/session/iteration-value-refresh-20260705-115453.log +3 -0
  182. package/observability/logs/session/learning-loop-refresh-20260701-004309.log +3 -0
  183. package/observability/logs/session/learning-loop-refresh-20260701-005906.log +3 -0
  184. package/observability/logs/session/learning-loop-refresh-20260701-010745.log +3 -0
  185. package/observability/logs/session/learning-loop-refresh-20260704-150309.log +3 -0
  186. package/observability/logs/session/learning-loop-refresh-20260705-115450.log +3 -0
  187. package/observability/logs/session/project-notes-refresh-20260701-004227.log +1 -0
  188. package/observability/logs/session/project-notes-refresh-20260701-004308.log +1 -0
  189. package/observability/logs/session/project-notes-refresh-20260701-005636.log +1 -0
  190. package/observability/logs/session/project-notes-refresh-20260701-005906.log +1 -0
  191. package/observability/logs/session/project-notes-refresh-20260701-010443.log +1 -0
  192. package/observability/logs/session/project-notes-refresh-20260701-010744.log +1 -0
  193. package/observability/logs/session/project-notes-refresh-20260704-150133.log +1 -0
  194. package/observability/logs/session/project-notes-refresh-20260704-150309.log +1 -0
  195. package/observability/logs/session/project-notes-refresh-20260704-154442.log +1 -0
  196. package/observability/logs/session/project-notes-refresh-20260705-115449.log +1 -0
  197. package/observability/logs/session/repo-intake-20260701-004218.log +6 -0
  198. package/observability/logs/session/repo-intake-20260701-005626.log +6 -0
  199. package/observability/logs/session/repo-intake-20260701-010433.log +6 -0
  200. package/observability/logs/session/repo-intake-20260704-150053.log +6 -0
  201. package/observability/logs/session/repo-intake-20260704-154404.log +6 -0
  202. package/observability/logs/session/repo-intake-refresh-20260701-004247.log +6 -0
  203. package/observability/logs/session/repo-intake-refresh-20260701-005847.log +6 -0
  204. package/observability/logs/session/repo-intake-refresh-20260701-010615.log +6 -0
  205. package/observability/logs/session/repo-intake-refresh-20260704-150209.log +6 -0
  206. package/observability/logs/session/repo-intake-refresh-20260705-115342.log +6 -0
  207. package/observability/logs/session/repo-registry-validation-20260701-004234.log +2 -0
  208. package/observability/logs/session/repo-registry-validation-20260701-004556.log +2 -0
  209. package/observability/logs/session/repo-registry-validation-20260701-005833.log +2 -0
  210. package/observability/logs/session/repo-registry-validation-20260701-010601.log +2 -0
  211. package/observability/logs/session/repo-registry-validation-20260704-150155.log +2 -0
  212. package/observability/logs/session/repo-registry-validation-20260705-115323.log +2 -0
  213. package/observability/logs/session/repomix-refresh-20260701-003823.log +4236 -0
  214. package/observability/logs/session/repomix-refresh-20260701-004251.log +4275 -0
  215. package/observability/logs/session/repomix-refresh-20260701-005850.log +4248 -0
  216. package/observability/logs/session/repomix-refresh-20260701-010619.log +6531 -0
  217. package/observability/logs/session/repomix-refresh-20260704-150239.log +652 -0
  218. package/observability/logs/session/repomix-refresh-20260705-115419.log +178 -0
  219. package/observability/logs/session/routing-evals-20260701-004221.log +2 -0
  220. package/observability/logs/session/routing-evals-20260701-004235.log +2 -0
  221. package/observability/logs/session/routing-evals-20260701-005629.log +2 -0
  222. package/observability/logs/session/routing-evals-20260701-005834.log +2 -0
  223. package/observability/logs/session/routing-evals-20260701-010437.log +2 -0
  224. package/observability/logs/session/routing-evals-20260701-010603.log +2 -0
  225. package/observability/logs/session/routing-evals-20260704-150125.log +2 -0
  226. package/observability/logs/session/routing-evals-20260704-150157.log +2 -0
  227. package/observability/logs/session/routing-evals-20260704-154434.log +2 -0
  228. package/observability/logs/session/routing-evals-20260705-115327.log +2 -0
  229. package/observability/logs/session/sibling-discovery-refresh-20260701-004250.log +3 -0
  230. package/observability/logs/session/sibling-discovery-refresh-20260701-005850.log +3 -0
  231. package/observability/logs/session/sibling-discovery-refresh-20260701-010618.log +3 -0
  232. package/observability/logs/session/sibling-discovery-refresh-20260704-150238.log +3 -0
  233. package/observability/logs/session/sibling-discovery-refresh-20260705-115418.log +3 -0
  234. package/observability/logs.schema.json +95 -0
  235. package/observability/logs.schema.md +26 -0
  236. package/observability/metrics.md +35 -0
  237. package/observability/routing-evaluator.md +37 -0
  238. package/observability/scoring-rubric.md +38 -0
  239. package/optimization/ALWAYS_ON_OPTIMIZATION.md +98 -0
  240. package/optimization/caveman-mode.md +62 -0
  241. package/optimization/examples/login-bug.md +25 -0
  242. package/optimization/examples/sla-query.md +25 -0
  243. package/optimization/optimization-routing.md +30 -0
  244. package/optimization/policies/response-style.policy.md +45 -0
  245. package/optimization/policies/token-budget.policy.md +28 -0
  246. package/optimization/profiles/always-on.profile.md +19 -0
  247. package/optimization/profiles/architecture.profile.md +20 -0
  248. package/optimization/profiles/debug.profile.md +20 -0
  249. package/optimization/profiles/enterprise-rag.profile.md +20 -0
  250. package/optimization/profiles/training.profile.md +20 -0
  251. package/optimization/token-saver.md +58 -0
  252. package/orchestrator/corporate-routing.md +51 -0
  253. package/orchestrator/decision-matrix.md +33 -0
  254. package/orchestrator/fallback.md +29 -0
  255. package/orchestrator/repo-routing.md +58 -0
  256. package/orchestrator/router.md +75 -0
  257. package/orchestrator/wiki/__init__.py +5 -0
  258. package/orchestrator/wiki/graph_consolidator.py +506 -0
  259. package/orchestrator/wiki/incremental_engine.py +57 -0
  260. package/orchestrator/wiki/plugin_manager.py +40 -0
  261. package/package.json +70 -0
  262. package/policies/context-policy.md +25 -0
  263. package/policies/cost-policy.md +25 -0
  264. package/policies/repo-intake-policy.md +36 -0
  265. package/policies/security-policy.md +25 -0
  266. package/repo-intake/README.md +81 -0
  267. package/repo-intake/templates/agent.template.md +4 -0
  268. package/repo-intake/templates/context-manifest.template.md +4 -0
  269. package/repo-intake/templates/skill.template.md +3 -0
  270. package/repo-registry/repos.schema.json +102 -0
  271. package/repo-registry/repos.template.json +26 -0
  272. package/requirements.txt +1 -0
  273. package/scripts/README.md +109 -0
  274. package/scripts/__init__.py +0 -0
  275. package/scripts/bootstrap-portable.cmd +4 -0
  276. package/scripts/bootstrap-portable.ps1 +90 -0
  277. package/scripts/context/azure-rag-mcp-adapter.py +1 -0
  278. package/scripts/context/build-graphify.ps1 +1 -0
  279. package/scripts/context/build-repomix.ps1 +1 -0
  280. package/scripts/discovery/discover-boost-repos.cmd +4 -0
  281. package/scripts/discovery/discover-boost-repos.py +186 -0
  282. package/scripts/discovery/refresh-project-notes.py +179 -0
  283. package/scripts/github/INSTALL.md +295 -0
  284. package/scripts/github/README.md +455 -0
  285. package/scripts/github/apply-boost-templates.ps1 +163 -0
  286. package/scripts/github/apply-single-repo.ps1 +85 -0
  287. package/scripts/github/create-devlog.ps1 +380 -0
  288. package/scripts/github/manage-issues.ps1 +332 -0
  289. package/scripts/github/sync-repo.ps1 +267 -0
  290. package/scripts/intake/agent-pipeline-preflight.py +151 -0
  291. package/scripts/intake/init-template-registry.cmd +4 -0
  292. package/scripts/intake/init-template-registry.ps1 +258 -0
  293. package/scripts/intake/repo-intake.py +354 -0
  294. package/scripts/intake/resolve-routing.py +620 -0
  295. package/scripts/intake/run-repo-intake.cmd +4 -0
  296. package/scripts/intake/run-repo-intake.ps1 +46 -0
  297. package/scripts/intake/run-routing-evals.py +147 -0
  298. package/scripts/intake/validate-database-routing.py +91 -0
  299. package/scripts/intake/validate-rag-routing.py +85 -0
  300. package/scripts/intake/validate-repo-registry.ps1 +20 -0
  301. package/scripts/intake/validate-repo-registry.py +286 -0
  302. package/scripts/intake/validate-security.py +170 -0
  303. package/scripts/learning/autolearning-gate.py +71 -0
  304. package/scripts/learning/chat-token-usage-report.py +326 -0
  305. package/scripts/learning/ingest-copilot-session-usage.py +421 -0
  306. package/scripts/learning/iteration-value-report.py +275 -0
  307. package/scripts/learning/learning-loop-report.py +272 -0
  308. package/scripts/learning/record-iteration-metrics.py +116 -0
  309. package/scripts/learning/record-learning-feedback.py +91 -0
  310. package/scripts/ops/bye.ps1 +620 -0
  311. package/scripts/ops/compare-demo-results.ps1 +826 -0
  312. package/scripts/ops/demo1.ps1 +11 -0
  313. package/scripts/ops/demo2.ps1 +11 -0
  314. package/scripts/ops/demo3.ps1 +11 -0
  315. package/scripts/ops/demo4.ps1 +11 -0
  316. package/scripts/ops/hi.ps1 +1045 -0
  317. package/scripts/ops/llm-token-probe.py +164 -0
  318. package/scripts/ops/run-all-demos.ps1 +39 -0
  319. package/scripts/ops/run-demo-block.ps1 +85 -0
  320. package/scripts/ops/run-demo-session.ps1 +532 -0
  321. package/scripts/setup/optimize-engines.ps1 +183 -0
  322. package/scripts/setup/setup-codegraph.ps1 +3 -0
  323. package/scripts/setup/setup-gitnexus.ps1 +3 -0
  324. package/scripts/setup/setup-graphify.ps1 +2 -0
  325. package/scripts/setup/setup-prerequisites.ps1 +205 -0
  326. package/scripts/setup/start-gitnexus.ps1 +71 -0
  327. package/scripts/setup/validate-context.ps1 +361 -0
  328. package/scripts/wiki/__init__.py +0 -0
  329. package/scripts/wiki/compiler_main.py +189 -0
  330. package/scripts/wiki/normalizer.py +206 -0
  331. package/scripts/wiki/providers/base_provider.py +25 -0
  332. package/scripts/wiki/providers/codegraph_provider.py +49 -0
  333. package/scripts/wiki/providers/graphify_provider.py +49 -0
  334. package/scripts/wiki/providers/repo_content_provider.py +408 -0
  335. package/scripts/wiki/query_autodocs.py +60 -0
  336. package/scripts/wiki/validator.py +183 -0
  337. package/scripts/wiki/wiki_compiler.py +5 -0
  338. package/specs/architecture.spec.md +32 -0
  339. package/specs/azure-rag.spec.md +28 -0
  340. package/specs/coding-standards.spec.md +24 -0
  341. package/specs/database.spec.md +29 -0
  342. package/specs/migration.spec.md +26 -0
  343. package/specs/observability.spec.md +24 -0
  344. package/specs/optimization.spec.md +31 -0
  345. package/specs/rag.spec.md +30 -0
  346. package/specs/repo-intake.spec.md +25 -0
  347. package/specs/routing.spec.md +24 -0
  348. package/specs/security.spec.md +30 -0
  349. package/specs/wiki/provider-contract.json +33 -0
  350. package/specs/wiki/unified-knowledge-graph.json +25 -0
  351. package/templates/BOOST_README_TEMPLATE.md +96 -0
  352. package/templates/CODEOWNERS_TEMPLATE.txt +29 -0
  353. package/templates/LICENSE_MIT_TEMPLATE.txt +21 -0
  354. package/templates/PERSONAL_README_TEMPLATE.md +90 -0
  355. package/templates/README_boost_RAG_Azure.md +46 -0
  356. package/templates/README_boost_azure_iot_edge.md +48 -0
  357. package/templates/README_boost_backend.md +47 -0
  358. package/templates/README_boost_dba.md +57 -0
  359. package/templates/ci.yml.template +73 -0
  360. package/tooling/tooling.manifest.json +72 -0
@@ -0,0 +1,170 @@
1
+ from __future__ import annotations
2
+
3
+ """Validate security posture against security-policy.md.
4
+
5
+ Checks:
6
+ 1. security-policy.md exists and contains required rule keywords.
7
+ 2. No secret-like patterns in observable artefacts (logs, snapshots, repomix output).
8
+ 3. Routing events with hitl_required=true have a valid HITL structure.
9
+
10
+ Exit 0 = all checks pass. Exit 1 = one or more failures.
11
+ """
12
+
13
+ import json
14
+ import re
15
+ import sys
16
+ from pathlib import Path
17
+
18
+ # ---------------------------------------------------------------------------
19
+ # Config
20
+ # ---------------------------------------------------------------------------
21
+
22
+ REPO_ROOT = Path(__file__).resolve().parents[2]
23
+
24
+ SECURITY_POLICY_PATH = REPO_ROOT / "policies" / "security-policy.md"
25
+
26
+ REQUIRED_POLICY_KEYWORDS = [
27
+ "secretos",
28
+ "tokens",
29
+ "credenciales",
30
+ "confirmacion",
31
+ "sensible",
32
+ ]
33
+
34
+ SECRET_PATTERN = re.compile(
35
+ r"""(?xi)
36
+ (?:
37
+ (?:api[_\-]?key|api[_\-]?secret|access[_\-]?token|auth[_\-]?token|
38
+ client[_\-]?secret|private[_\-]?key|password|passwd|
39
+ bearer\s+[a-z0-9\-._~+/]+=*)
40
+ \s*[=:]\s*['"]?[a-z0-9\-._~+/]{16,}['"]?
41
+ )
42
+ |
43
+ -----BEGIN\s+(?:RSA\s+)?PRIVATE\s+KEY-----
44
+ """,
45
+ re.IGNORECASE,
46
+ )
47
+
48
+ SCAN_PATHS = [
49
+ REPO_ROOT / "observability" / "logs",
50
+ REPO_ROOT / "context" / "repomix",
51
+ ]
52
+
53
+ ROUTING_LOG = REPO_ROOT / "observability" / "logs" / "routing-decisions.jsonl"
54
+
55
+
56
+ # ---------------------------------------------------------------------------
57
+ # Checks
58
+ # ---------------------------------------------------------------------------
59
+
60
+
61
+ def check_policy_exists() -> list[str]:
62
+ errors: list[str] = []
63
+ if not SECURITY_POLICY_PATH.exists():
64
+ errors.append(f"MISSING: {SECURITY_POLICY_PATH.relative_to(REPO_ROOT)}")
65
+ return errors
66
+ content = SECURITY_POLICY_PATH.read_text(encoding="utf-8").lower()
67
+ for kw in REQUIRED_POLICY_KEYWORDS:
68
+ if kw not in content:
69
+ errors.append(f"security-policy.md missing required keyword: '{kw}'")
70
+ return errors
71
+
72
+
73
+ def check_no_secrets_in_artefacts() -> list[str]:
74
+ errors: list[str] = []
75
+ for base in SCAN_PATHS:
76
+ if not base.exists():
77
+ continue
78
+ for path in base.rglob("*"):
79
+ if not path.is_file():
80
+ continue
81
+ # Skip binary files larger than 1 MB
82
+ if path.stat().st_size > 1_048_576:
83
+ continue
84
+ try:
85
+ text = path.read_text(encoding="utf-8", errors="ignore")
86
+ except Exception:
87
+ continue
88
+ for m in SECRET_PATTERN.finditer(text):
89
+ line_no = text[: m.start()].count("\n") + 1
90
+ rel = path.relative_to(REPO_ROOT)
91
+ errors.append(f"POTENTIAL SECRET at {rel}:{line_no} → {m.group()[:60]!r}")
92
+ return errors
93
+
94
+
95
+ def check_hitl_events() -> list[str]:
96
+ errors: list[str] = []
97
+ if not ROUTING_LOG.exists():
98
+ return errors
99
+ for raw in ROUTING_LOG.read_text(encoding="utf-8").splitlines():
100
+ raw = raw.strip()
101
+ if not raw:
102
+ continue
103
+ try:
104
+ event = json.loads(raw)
105
+ except Exception:
106
+ continue
107
+ if not isinstance(event, dict):
108
+ continue
109
+ hitl = event.get("hitl")
110
+ if not isinstance(hitl, dict):
111
+ continue
112
+ required = hitl.get("required")
113
+ if required is not True:
114
+ continue
115
+ # High-risk events must declare an action (not "none")
116
+ action = str(hitl.get("action", "none")).lower()
117
+ if action == "none":
118
+ eid = event.get("event_id", "unknown")
119
+ errors.append(f"Event {eid}: hitl.required=true but action='none' (HITL not routed)")
120
+ return errors
121
+
122
+
123
+ # ---------------------------------------------------------------------------
124
+ # Runner
125
+ # ---------------------------------------------------------------------------
126
+
127
+
128
+ def main() -> int:
129
+ all_errors: list[str] = []
130
+
131
+ print("=== validate-security: checking security posture ===")
132
+
133
+ policy_errors = check_policy_exists()
134
+ if policy_errors:
135
+ print("[FAIL] Policy check:")
136
+ for e in policy_errors:
137
+ print(f" - {e}")
138
+ else:
139
+ print("[OK] security-policy.md present and contains required keywords")
140
+ all_errors.extend(policy_errors)
141
+
142
+ secret_errors = check_no_secrets_in_artefacts()
143
+ if secret_errors:
144
+ print("[FAIL] Secret scan:")
145
+ for e in secret_errors:
146
+ print(f" - {e}")
147
+ else:
148
+ print("[OK] No secret-like patterns found in observable artefacts")
149
+ all_errors.extend(secret_errors)
150
+
151
+ hitl_errors = check_hitl_events()
152
+ if hitl_errors:
153
+ print("[FAIL] HITL routing check:")
154
+ for e in hitl_errors:
155
+ print(f" - {e}")
156
+ else:
157
+ print("[OK] All high-risk routing events have valid HITL routing")
158
+ all_errors.extend(hitl_errors)
159
+
160
+ print()
161
+ if all_errors:
162
+ print(f"validate-security FAILED: {len(all_errors)} issue(s) found")
163
+ return 1
164
+
165
+ print("validate-security PASSED: all security checks OK")
166
+ return 0
167
+
168
+
169
+ if __name__ == "__main__":
170
+ raise SystemExit(main())
@@ -0,0 +1,71 @@
1
+ from __future__ import annotations
2
+
3
+ import argparse
4
+ import json
5
+ from pathlib import Path
6
+
7
+
8
+ def main() -> int:
9
+ parser = argparse.ArgumentParser(description="Enforce AutoLearning value gate from iteration-value report.")
10
+ parser.add_argument("--report", default="observability/evals/iteration-value-report.json")
11
+ parser.add_argument("--min-events-with-id", type=int, default=5)
12
+ parser.add_argument("--min-confirmed", type=int, default=3)
13
+ parser.add_argument("--min-metrics-coverage", type=float, default=0.6)
14
+ parser.add_argument("--min-feedback-coverage", type=float, default=0.6)
15
+ parser.add_argument("--min-success-rate", type=float, default=0.7)
16
+ parser.add_argument("--max-cost-per-success", type=float, default=0.05)
17
+ args = parser.parse_args()
18
+
19
+ report_path = Path(args.report)
20
+ if not report_path.exists():
21
+ raise SystemExit(f"Missing report: {report_path}")
22
+
23
+ data = json.loads(report_path.read_text(encoding="utf-8"))
24
+ totals = data.get("totals", {}) if isinstance(data.get("totals", {}), dict) else {}
25
+ kpis = data.get("kpis", {}) if isinstance(data.get("kpis", {}), dict) else {}
26
+ assessment = data.get("assessment", {}) if isinstance(data.get("assessment", {}), dict) else {}
27
+
28
+ events_with_id = int(totals.get("events_with_event_id", 0) or 0)
29
+ confirmed = int(totals.get("feedback_confirmed", 0) or 0)
30
+
31
+ metrics_cov = float(kpis.get("metrics_coverage_rate", 0.0) or 0.0)
32
+ feedback_cov = float(kpis.get("confirmed_feedback_rate", 0.0) or 0.0)
33
+ success_rate = float(kpis.get("confirmed_success_rate", 0.0) or 0.0)
34
+ cps = float(kpis.get("cost_per_confirmed_success_usd", 0.0) or 0.0)
35
+ has_value_signal = bool(assessment.get("has_value_signal", False))
36
+
37
+ failures: list[str] = []
38
+ if events_with_id < args.min_events_with_id:
39
+ failures.append(f"events_with_event_id {events_with_id} < {args.min_events_with_id}")
40
+ if confirmed < args.min_confirmed:
41
+ failures.append(f"feedback_confirmed {confirmed} < {args.min_confirmed}")
42
+ if metrics_cov < args.min_metrics_coverage:
43
+ failures.append(f"metrics_coverage_rate {metrics_cov:.4f} < {args.min_metrics_coverage:.4f}")
44
+ if feedback_cov < args.min_feedback_coverage:
45
+ failures.append(f"confirmed_feedback_rate {feedback_cov:.4f} < {args.min_feedback_coverage:.4f}")
46
+ if success_rate < args.min_success_rate:
47
+ failures.append(f"confirmed_success_rate {success_rate:.4f} < {args.min_success_rate:.4f}")
48
+ if has_value_signal and cps > args.max_cost_per_success:
49
+ failures.append(f"cost_per_confirmed_success_usd {cps:.6f} > {args.max_cost_per_success:.6f}")
50
+
51
+ print("AutoLearning Gate Summary")
52
+ print(f"- events_with_event_id: {events_with_id}")
53
+ print(f"- feedback_confirmed: {confirmed}")
54
+ print(f"- metrics_coverage_rate: {metrics_cov:.4f}")
55
+ print(f"- confirmed_feedback_rate: {feedback_cov:.4f}")
56
+ print(f"- confirmed_success_rate: {success_rate:.4f}")
57
+ print(f"- cost_per_confirmed_success_usd: {cps:.6f}")
58
+ print(f"- has_value_signal: {has_value_signal}")
59
+
60
+ if failures:
61
+ print("AutoLearning Gate FAILED:")
62
+ for f in failures:
63
+ print(f" - {f}")
64
+ raise SystemExit(1)
65
+
66
+ print("AutoLearning Gate PASSED")
67
+ return 0
68
+
69
+
70
+ if __name__ == "__main__":
71
+ raise SystemExit(main())
@@ -0,0 +1,326 @@
1
+ from __future__ import annotations
2
+
3
+ import argparse
4
+ import json
5
+ from collections import defaultdict
6
+ from datetime import datetime, timezone
7
+ from pathlib import Path
8
+ from typing import Any
9
+
10
+
11
+ PLAN_INCLUDED_CREDITS_PER_USER = {
12
+ "free": 0.0,
13
+ "pro": 15.0,
14
+ "pro+": 70.0,
15
+ "max": 200.0,
16
+ "business": 19.0,
17
+ "enterprise": 39.0,
18
+ }
19
+
20
+ OFFICIAL_COPILOT_PLANS_URL = (
21
+ "https://github.com/features/copilot/plans?ref_cta=View+pricing+and+plans"
22
+ "&ref_loc=hero&ref_page=%2Ffeatures_copilot_copilot_business&plans=business"
23
+ )
24
+
25
+
26
+ def utc_now() -> str:
27
+ return datetime.now(timezone.utc).isoformat()
28
+
29
+
30
+ def parse_jsonl(path: Path) -> list[dict[str, Any]]:
31
+ if not path.exists():
32
+ return []
33
+ rows: list[dict[str, Any]] = []
34
+ for line in path.read_text(encoding="utf-8").splitlines():
35
+ line = line.strip()
36
+ if not line:
37
+ continue
38
+ try:
39
+ obj = json.loads(line)
40
+ except Exception:
41
+ continue
42
+ if isinstance(obj, dict):
43
+ rows.append(obj)
44
+ return rows
45
+
46
+
47
+ def parse_iso8601(value: str) -> datetime | None:
48
+ text = value.strip()
49
+ if not text:
50
+ return None
51
+ if text.isdigit():
52
+ try:
53
+ raw = float(text)
54
+ # Copilot session logs use epoch milliseconds.
55
+ if raw > 10_000_000_000:
56
+ raw = raw / 1000.0
57
+ return datetime.fromtimestamp(raw, tz=timezone.utc)
58
+ except Exception:
59
+ return None
60
+ if text.endswith("Z"):
61
+ text = text[:-1] + "+00:00"
62
+ try:
63
+ dt = datetime.fromisoformat(text)
64
+ except Exception:
65
+ return None
66
+ if dt.tzinfo is None:
67
+ return dt.replace(tzinfo=timezone.utc)
68
+ return dt.astimezone(timezone.utc)
69
+
70
+
71
+ def parse_date_arg(value: str, label: str) -> datetime:
72
+ dt = parse_iso8601(value)
73
+ if dt is None:
74
+ raise ValueError(f"{label} must be ISO-8601, for example 2026-07-03T00:00:00Z")
75
+ return dt
76
+
77
+
78
+ def is_copilot_session_row(row: dict[str, Any]) -> bool:
79
+ usage = row.get("usage", {}) if isinstance(row.get("usage", {}), dict) else {}
80
+ source = str(usage.get("source", "")).strip()
81
+ if source == "copilot-session":
82
+ return True
83
+ event_id = str(row.get("event_id", "")).strip()
84
+ return event_id.startswith("copilot-session::")
85
+
86
+
87
+ def row_timestamp(row: dict[str, Any]) -> datetime | None:
88
+ raw = str(row.get("timestamp", "")).strip()
89
+ if not raw:
90
+ return None
91
+ return parse_iso8601(raw)
92
+
93
+
94
+ def build_report(
95
+ rows: list[dict[str, Any]],
96
+ source_label: str,
97
+ dt_from: datetime | None,
98
+ dt_to: datetime | None,
99
+ plan: str,
100
+ seats: int,
101
+ ) -> dict[str, Any]:
102
+ filtered: list[dict[str, Any]] = []
103
+
104
+ for row in rows:
105
+ if not is_copilot_session_row(row):
106
+ continue
107
+
108
+ ts = row_timestamp(row)
109
+ if dt_from is not None and (ts is None or ts < dt_from):
110
+ continue
111
+ if dt_to is not None and (ts is None or ts > dt_to):
112
+ continue
113
+ filtered.append(row)
114
+
115
+ total_input_tokens = 0
116
+ total_output_tokens = 0
117
+ total_tokens = 0
118
+ total_copilot_credits = 0.0
119
+
120
+ by_model = defaultdict(lambda: {"count": 0, "input_tokens": 0, "output_tokens": 0, "total_tokens": 0, "copilot_credits": 0.0})
121
+ by_day = defaultdict(lambda: {"count": 0, "input_tokens": 0, "output_tokens": 0, "total_tokens": 0, "copilot_credits": 0.0})
122
+
123
+ for row in filtered:
124
+ cost = row.get("cost", {}) if isinstance(row.get("cost", {}), dict) else {}
125
+ model = str(row.get("model", "unknown"))
126
+ ts = row_timestamp(row)
127
+ day = ts.date().isoformat() if ts is not None else "unknown"
128
+
129
+ input_tokens = int(cost.get("input_tokens", 0) or 0)
130
+ output_tokens = int(cost.get("output_tokens", 0) or 0)
131
+ row_total = int(cost.get("total_tokens", input_tokens + output_tokens) or 0)
132
+ row_credits = float(cost.get("copilot_credits", 0.0) or 0.0)
133
+
134
+ total_input_tokens += input_tokens
135
+ total_output_tokens += output_tokens
136
+ total_tokens += row_total
137
+ total_copilot_credits += row_credits
138
+
139
+ by_model[model]["count"] += 1
140
+ by_model[model]["input_tokens"] += input_tokens
141
+ by_model[model]["output_tokens"] += output_tokens
142
+ by_model[model]["total_tokens"] += row_total
143
+ by_model[model]["copilot_credits"] += row_credits
144
+
145
+ by_day[day]["count"] += 1
146
+ by_day[day]["input_tokens"] += input_tokens
147
+ by_day[day]["output_tokens"] += output_tokens
148
+ by_day[day]["total_tokens"] += row_total
149
+ by_day[day]["copilot_credits"] += row_credits
150
+
151
+ report = {
152
+ "timestamp": utc_now(),
153
+ "source": source_label,
154
+ "filters": {
155
+ "source": "copilot-session",
156
+ "from": dt_from.isoformat() if dt_from is not None else "",
157
+ "to": dt_to.isoformat() if dt_to is not None else "",
158
+ },
159
+ "totals": {
160
+ "events": len(filtered),
161
+ "input_tokens": total_input_tokens,
162
+ "output_tokens": total_output_tokens,
163
+ "total_tokens": total_tokens,
164
+ "copilot_credits": round(total_copilot_credits, 6),
165
+ },
166
+ "breakdown": {
167
+ "model": dict(sorted(by_model.items())),
168
+ "day": dict(sorted(by_day.items())),
169
+ },
170
+ }
171
+
172
+ included_per_user = float(PLAN_INCLUDED_CREDITS_PER_USER.get(plan, 0.0))
173
+ included_total = included_per_user * float(seats)
174
+ used_credits = float(report["totals"].get("copilot_credits", 0.0) or 0.0)
175
+ overage_credits = max(0.0, used_credits - included_total)
176
+ utilization = 0.0
177
+ equivalent_seats_used = 0.0
178
+ percent_of_one_seat = 0.0
179
+ if included_total > 0:
180
+ utilization = used_credits / included_total
181
+ if included_per_user > 0:
182
+ equivalent_seats_used = used_credits / included_per_user
183
+ percent_of_one_seat = equivalent_seats_used * 100.0
184
+
185
+ report["license"] = {
186
+ "plan": plan,
187
+ "seats": seats,
188
+ "included_credits_per_user_month": included_per_user,
189
+ "included_credits_total_month": round(included_total, 6),
190
+ "official_plans_url": OFFICIAL_COPILOT_PLANS_URL,
191
+ }
192
+ report["budget"] = {
193
+ "used_copilot_credits": round(used_credits, 6),
194
+ "budget_utilization_rate": round(utilization, 6),
195
+ "overage_credits": round(overage_credits, 6),
196
+ "equivalent_seats_used": round(equivalent_seats_used, 6),
197
+ "percent_of_one_seat": round(percent_of_one_seat, 2),
198
+ }
199
+
200
+ return report
201
+
202
+
203
+ def to_markdown(report: dict[str, Any]) -> str:
204
+ totals = report.get("totals", {}) if isinstance(report.get("totals", {}), dict) else {}
205
+ breakdown = report.get("breakdown", {}) if isinstance(report.get("breakdown", {}), dict) else {}
206
+ filters = report.get("filters", {}) if isinstance(report.get("filters", {}), dict) else {}
207
+ license_info = report.get("license", {}) if isinstance(report.get("license", {}), dict) else {}
208
+ budget = report.get("budget", {}) if isinstance(report.get("budget", {}), dict) else {}
209
+
210
+ lines = [
211
+ "# Chat Token Usage Report",
212
+ "",
213
+ f"- timestamp: {report.get('timestamp', '')}",
214
+ f"- source: {report.get('source', '')}",
215
+ f"- filter.source: {filters.get('source', '')}",
216
+ f"- filter.from: {filters.get('from', '')}",
217
+ f"- filter.to: {filters.get('to', '')}",
218
+ f"- plan: {license_info.get('plan', '')}",
219
+ f"- seats: {license_info.get('seats', 0)}",
220
+ f"- official_plans_url: {license_info.get('official_plans_url', '')}",
221
+ "",
222
+ "## Totals",
223
+ "",
224
+ f"- events: {totals.get('events', 0)}",
225
+ f"- input_tokens: {totals.get('input_tokens', 0)}",
226
+ f"- output_tokens: {totals.get('output_tokens', 0)}",
227
+ f"- total_tokens: {totals.get('total_tokens', 0)}",
228
+ f"- copilot_credits: {totals.get('copilot_credits', 0.0)}",
229
+ f"- included_credits_total_month: {license_info.get('included_credits_total_month', 0.0)}",
230
+ f"- budget_utilization_rate: {budget.get('budget_utilization_rate', 0.0)}",
231
+ f"- overage_credits: {budget.get('overage_credits', 0.0)}",
232
+ f"- equivalent_seats_used: {budget.get('equivalent_seats_used', 0.0)}",
233
+ f"- percent_of_one_seat: {budget.get('percent_of_one_seat', 0.0)}",
234
+ "",
235
+ "## Breakdown by Model",
236
+ "",
237
+ ]
238
+
239
+ by_model = breakdown.get("model", {}) if isinstance(breakdown.get("model", {}), dict) else {}
240
+ if not by_model:
241
+ lines.append("- none")
242
+ else:
243
+ for model, data in by_model.items():
244
+ if not isinstance(data, dict):
245
+ continue
246
+ lines.append(
247
+ f"- {model}: count={data.get('count', 0)}, input={data.get('input_tokens', 0)}, output={data.get('output_tokens', 0)}, total={data.get('total_tokens', 0)}, credits={round(float(data.get('copilot_credits', 0.0)), 6)}"
248
+ )
249
+
250
+ lines.extend(["", "## Breakdown by Day", ""])
251
+ by_day = breakdown.get("day", {}) if isinstance(breakdown.get("day", {}), dict) else {}
252
+ if not by_day:
253
+ lines.append("- none")
254
+ else:
255
+ for day, data in by_day.items():
256
+ if not isinstance(data, dict):
257
+ continue
258
+ lines.append(
259
+ f"- {day}: count={data.get('count', 0)}, input={data.get('input_tokens', 0)}, output={data.get('output_tokens', 0)}, total={data.get('total_tokens', 0)}, credits={round(float(data.get('copilot_credits', 0.0)), 6)}"
260
+ )
261
+
262
+ lines.append("")
263
+ return "\n".join(lines)
264
+
265
+
266
+ def main() -> int:
267
+ parser = argparse.ArgumentParser(description="Build chat-only token usage report from iteration-metrics.")
268
+ parser.add_argument("--metrics-log", default="observability/logs/iteration-metrics.jsonl")
269
+ parser.add_argument("--from", dest="dt_from", default="", help="ISO-8601 lower bound, inclusive")
270
+ parser.add_argument("--to", dest="dt_to", default="", help="ISO-8601 upper bound, inclusive")
271
+ parser.add_argument(
272
+ "--plan",
273
+ default="business",
274
+ choices=["free", "pro", "pro+", "max", "business", "enterprise"],
275
+ help="Copilot plan for budget context. Verify current pricing/allowances at the official Copilot plans URL.",
276
+ )
277
+ parser.add_argument("--seats", type=int, default=1, help="Number of licensed users for pooled monthly credits")
278
+ parser.add_argument("--out-json", default="observability/evals/chat-token-usage-report.json")
279
+ parser.add_argument("--out-md", default="observability/evals/chat-token-usage-report.md")
280
+ args = parser.parse_args()
281
+
282
+ if args.seats <= 0:
283
+ print("--seats must be >= 1")
284
+ return 1
285
+
286
+ dt_from = None
287
+ dt_to = None
288
+ if args.dt_from.strip():
289
+ try:
290
+ dt_from = parse_date_arg(args.dt_from, "--from")
291
+ except ValueError as ex:
292
+ print(str(ex))
293
+ return 1
294
+ if args.dt_to.strip():
295
+ try:
296
+ dt_to = parse_date_arg(args.dt_to, "--to")
297
+ except ValueError as ex:
298
+ print(str(ex))
299
+ return 1
300
+
301
+ if dt_from is not None and dt_to is not None and dt_from > dt_to:
302
+ print("--from must be <= --to")
303
+ return 1
304
+
305
+ repo_root = Path(__file__).resolve().parents[2]
306
+ metrics_path = (repo_root / args.metrics_log).resolve()
307
+ out_json = (repo_root / args.out_json).resolve()
308
+ out_md = (repo_root / args.out_md).resolve()
309
+
310
+ rows = parse_jsonl(metrics_path)
311
+ report = build_report(rows, args.metrics_log.replace("\\", "/"), dt_from, dt_to, args.plan, args.seats)
312
+
313
+ out_json.parent.mkdir(parents=True, exist_ok=True)
314
+ out_json.write_text(json.dumps(report, indent=2, ensure_ascii=False) + "\n", encoding="utf-8")
315
+ out_md.write_text(to_markdown(report), encoding="utf-8")
316
+
317
+ totals = report.get("totals", {}) if isinstance(report.get("totals", {}), dict) else {}
318
+ print("Chat token usage report generated")
319
+ print(f"events={totals.get('events', 0)} total_tokens={totals.get('total_tokens', 0)}")
320
+ print(f"JSON: {out_json}")
321
+ print(f"MD: {out_md}")
322
+ return 0
323
+
324
+
325
+ if __name__ == "__main__":
326
+ raise SystemExit(main())