kodelyth-ecc 1.5.10 → 1.7.1

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 (273) hide show
  1. package/.github/workflows/publish.yml +19 -0
  2. package/AGENTS.md +1 -1
  3. package/CHANGELOG.md +475 -0
  4. package/CLAUDE.md +48 -16
  5. package/README.md +285 -81
  6. package/VERSION +1 -1
  7. package/actions/ecc-review/README.md +243 -0
  8. package/actions/ecc-review/action.yml +158 -0
  9. package/actions/ecc-review/post-comment.js +171 -0
  10. package/actions/ecc-review/run-review.js +285 -0
  11. package/agents/backdoor-hunter.md +260 -0
  12. package/agents/chaos-engineer.md +251 -0
  13. package/agents/code-stealer-detector.md +228 -0
  14. package/agents/jailbreak-tester.md +222 -0
  15. package/agents/license-violation-finder.md +212 -0
  16. package/agents/prompt-injection-hunter.md +126 -0
  17. package/agents/secret-hunter.md +209 -0
  18. package/agents/supply-chain-auditor.md +195 -0
  19. package/bin/kodelyth-ecc.js +875 -1
  20. package/bundles/enterprise.md +172 -0
  21. package/bundles/indie-hacker.md +106 -0
  22. package/bundles/red-team.md +138 -0
  23. package/cat +0 -0
  24. package/commands/dashboard.md +67 -0
  25. package/commands/devil-mode.md +121 -0
  26. package/commands/memory-evolve.md +71 -0
  27. package/commands/replay.md +61 -0
  28. package/commands/route-model.md +48 -0
  29. package/commands/swarm.md +68 -0
  30. package/commands/verify-supply-chain.md +59 -0
  31. package/docs/dashboard.md +211 -0
  32. package/docs/evolve.md +303 -0
  33. package/docs/mcp-clients.md +167 -0
  34. package/docs/mcp.md +178 -0
  35. package/docs/replay.md +244 -0
  36. package/docs/supply-chain.md +207 -0
  37. package/docs/swarm.md +243 -0
  38. package/hooks/hooks.json +52 -0
  39. package/hooks/memory/auto-recall.js +29 -1
  40. package/hooks/safety/README.md +124 -0
  41. package/hooks/safety/lib/patterns.js +179 -0
  42. package/hooks/safety/prompt-injection-guard.js +179 -0
  43. package/hooks/safety/token-budget.js +229 -0
  44. package/install.ps1 +199 -2
  45. package/install.sh +285 -3
  46. package/package.json +30 -4
  47. package/rules/common/agent-intent-routing.md +182 -0
  48. package/rules/common/cost-aware-model-routing.md +152 -0
  49. package/scripts/dashboard/data.js +353 -0
  50. package/scripts/dashboard/server.js +324 -0
  51. package/scripts/dashboard/static/index.html +645 -0
  52. package/scripts/evolve/analyze.js +303 -0
  53. package/scripts/evolve/proposals.js +162 -0
  54. package/scripts/evolve/stats.js +219 -0
  55. package/scripts/mcp/catalog.js +244 -0
  56. package/scripts/mcp/client.js +181 -0
  57. package/scripts/mcp/prompts.js +133 -0
  58. package/scripts/mcp/resources.js +94 -0
  59. package/scripts/mcp/server.js +153 -0
  60. package/scripts/mcp/tools.js +465 -0
  61. package/scripts/replay/bundle.js +191 -0
  62. package/scripts/replay/replay.js +107 -0
  63. package/scripts/router/classify.js +232 -0
  64. package/scripts/supply-chain/manifest.js +155 -0
  65. package/scripts/supply-chain/sbom.js +202 -0
  66. package/scripts/supply-chain/verify.js +102 -0
  67. package/scripts/swarm/build-plan.js +193 -0
  68. package/skills/cost-aware-model-routing/SKILL.md +153 -0
  69. package/skills/kodelyth-quickstart/SKILL.md +7 -0
  70. package/skills/observability-dashboard/SKILL.md +119 -0
  71. package/skills/self-evolving-memory/SKILL.md +175 -0
  72. package/skills/session-replay/SKILL.md +199 -0
  73. package/skills/supply-chain-verification/SKILL.md +201 -0
  74. package/skills/swarm-orchestrator/SKILL.md +177 -0
  75. package/social/card-install.svg +1 -1
  76. package/social/facebook-group/POST.md +121 -0
  77. package/social/facebook-group/fb-1-3am-debug.png +0 -0
  78. package/social/facebook-group/fb-1-3am-debug.svg +97 -0
  79. package/social/facebook-group/fb-2-cpu-upgrade.png +0 -0
  80. package/social/facebook-group/fb-2-cpu-upgrade.svg +132 -0
  81. package/social/facebook-group/fb-3-before-after.png +0 -0
  82. package/social/facebook-group/fb-3-before-after.svg +94 -0
  83. package/social/facebook-v150.svg +6 -6
  84. package/social/github-social-preview.svg +119 -100
  85. package/social/readme-hero.svg +12 -12
  86. package/social/section-agents.svg +57 -0
  87. package/social/section-author.svg +54 -0
  88. package/social/section-dashboard.svg +59 -0
  89. package/social/section-devil.svg +54 -0
  90. package/social/section-hooks.svg +51 -0
  91. package/social/section-install.svg +42 -0
  92. package/social/section-learning.svg +52 -0
  93. package/social/section-mcp.svg +46 -0
  94. package/social/section-memory.svg +57 -0
  95. package/social/section-parallel.svg +72 -0
  96. package/social/section-routing.svg +51 -0
  97. package/social/x-card-agents-grid.svg +6 -6
  98. package/tests/dashboard/data.test.js +235 -0
  99. package/tests/dashboard/server.test.js +240 -0
  100. package/tests/evolve/analyze.test.js +169 -0
  101. package/tests/evolve/proposals.test.js +173 -0
  102. package/tests/evolve/stats.test.js +167 -0
  103. package/tests/mcp/catalog.test.js +98 -0
  104. package/tests/mcp/client.test.js +113 -0
  105. package/tests/mcp/resources-prompts.test.js +70 -0
  106. package/tests/mcp/tools.test.js +159 -0
  107. package/tests/replay/bundle.test.js +181 -0
  108. package/tests/replay/replay.test.js +131 -0
  109. package/tests/router/classify.test.js +164 -0
  110. package/tests/safety/patterns.test.js +94 -0
  111. package/tests/safety/prompt-injection-guard.test.js +111 -0
  112. package/tests/safety/token-budget.test.js +119 -0
  113. package/tests/supply-chain/manifest.test.js +147 -0
  114. package/tests/supply-chain/sbom.test.js +170 -0
  115. package/tests/supply-chain/verify.test.js +146 -0
  116. package/tests/swarm/build-plan.test.js +188 -0
  117. package/wiki/Agent-Reference.md +58 -7
  118. package/wiki/FAQ.md +204 -7
  119. package/wiki/Home.md +104 -29
  120. package/wiki/Hook-Reference.md +1 -1
  121. package/wiki/Installation-Guide.md +109 -6
  122. package/wiki/Platform-Support.md +189 -72
  123. package/wiki/Skill-Reference.md +101 -6
  124. package/.agent/rules/README.md +0 -108
  125. package/.agent/rules/agents.md +0 -94
  126. package/.agent/rules/code-review.md +0 -124
  127. package/.agent/rules/coding-style.md +0 -122
  128. package/.agent/rules/design-quality.md +0 -63
  129. package/.agent/rules/development-workflow.md +0 -44
  130. package/.agent/rules/git-mastery.md +0 -366
  131. package/.agent/rules/git-workflow.md +0 -24
  132. package/.agent/rules/hooks.md +0 -25
  133. package/.agent/rules/kodelyth-always-on.md +0 -63
  134. package/.agent/rules/kodelyth-quickstart.md +0 -207
  135. package/.agent/rules/observability.md +0 -390
  136. package/.agent/rules/patterns.md +0 -50
  137. package/.agent/rules/performance.md +0 -178
  138. package/.agent/rules/security.md +0 -58
  139. package/.agent/rules/smart-debug.md +0 -241
  140. package/.agent/rules/soul.md +0 -32
  141. package/.agent/rules/testing.md +0 -46
  142. package/.agent/skills/api-guardian.md +0 -279
  143. package/.agent/skills/architect.md +0 -211
  144. package/.agent/skills/build-error-resolver.md +0 -114
  145. package/.agent/skills/chief-of-staff.md +0 -151
  146. package/.agent/skills/code-architect.md +0 -71
  147. package/.agent/skills/code-explorer.md +0 -69
  148. package/.agent/skills/code-reviewer.md +0 -237
  149. package/.agent/skills/code-simplifier.md +0 -47
  150. package/.agent/skills/comment-analyzer.md +0 -45
  151. package/.agent/skills/conversation-analyzer.md +0 -52
  152. package/.agent/skills/cpp-build-resolver.md +0 -90
  153. package/.agent/skills/cpp-reviewer.md +0 -72
  154. package/.agent/skills/csharp-reviewer.md +0 -101
  155. package/.agent/skills/dart-build-resolver.md +0 -201
  156. package/.agent/skills/database-reviewer.md +0 -91
  157. package/.agent/skills/debug-detective.md +0 -409
  158. package/.agent/skills/doc-updater.md +0 -107
  159. package/.agent/skills/docs-lookup.md +0 -68
  160. package/.agent/skills/e2e-runner.md +0 -107
  161. package/.agent/skills/flutter-reviewer.md +0 -243
  162. package/.agent/skills/gan-evaluator.md +0 -209
  163. package/.agent/skills/gan-generator.md +0 -131
  164. package/.agent/skills/gan-planner.md +0 -99
  165. package/.agent/skills/go-build-resolver.md +0 -94
  166. package/.agent/skills/go-reviewer.md +0 -76
  167. package/.agent/skills/harness-optimizer.md +0 -35
  168. package/.agent/skills/healthcare-reviewer.md +0 -83
  169. package/.agent/skills/java-build-resolver.md +0 -153
  170. package/.agent/skills/java-reviewer.md +0 -92
  171. package/.agent/skills/kodelyth-advisor.md +0 -172
  172. package/.agent/skills/kotlin-build-resolver.md +0 -118
  173. package/.agent/skills/kotlin-reviewer.md +0 -159
  174. package/.agent/skills/loop-operator.md +0 -36
  175. package/.agent/skills/migration-guide.md +0 -368
  176. package/.agent/skills/opensource-forker.md +0 -198
  177. package/.agent/skills/opensource-packager.md +0 -249
  178. package/.agent/skills/opensource-sanitizer.md +0 -188
  179. package/.agent/skills/pair-programmer.md +0 -260
  180. package/.agent/skills/performance-optimizer.md +0 -446
  181. package/.agent/skills/planner.md +0 -212
  182. package/.agent/skills/pr-test-analyzer.md +0 -45
  183. package/.agent/skills/python-reviewer.md +0 -98
  184. package/.agent/skills/pytorch-build-resolver.md +0 -120
  185. package/.agent/skills/refactor-cleaner.md +0 -85
  186. package/.agent/skills/rust-build-resolver.md +0 -148
  187. package/.agent/skills/rust-reviewer.md +0 -94
  188. package/.agent/skills/security-reviewer.md +0 -108
  189. package/.agent/skills/seo-specialist.md +0 -62
  190. package/.agent/skills/silent-failure-hunter.md +0 -50
  191. package/.agent/skills/tdd-guide.md +0 -91
  192. package/.agent/skills/type-design-analyzer.md +0 -41
  193. package/.agent/skills/typescript-reviewer.md +0 -112
  194. package/.agent/skills/ux-reviewer.md +0 -483
  195. package/.agent/workflows/agent-sort.md +0 -23
  196. package/.agent/workflows/aside.md +0 -164
  197. package/.agent/workflows/build-fix.md +0 -62
  198. package/.agent/workflows/checkpoint.md +0 -74
  199. package/.agent/workflows/claw.md +0 -23
  200. package/.agent/workflows/code-review.md +0 -289
  201. package/.agent/workflows/context-budget.md +0 -23
  202. package/.agent/workflows/cpp-build.md +0 -173
  203. package/.agent/workflows/cpp-review.md +0 -132
  204. package/.agent/workflows/cpp-test.md +0 -251
  205. package/.agent/workflows/devfleet.md +0 -23
  206. package/.agent/workflows/docs.md +0 -23
  207. package/.agent/workflows/e2e.md +0 -268
  208. package/.agent/workflows/eval.md +0 -23
  209. package/.agent/workflows/evolve.md +0 -178
  210. package/.agent/workflows/feature-dev.md +0 -49
  211. package/.agent/workflows/flutter-build.md +0 -164
  212. package/.agent/workflows/flutter-review.md +0 -116
  213. package/.agent/workflows/flutter-test.md +0 -144
  214. package/.agent/workflows/gan-build.md +0 -99
  215. package/.agent/workflows/gan-design.md +0 -35
  216. package/.agent/workflows/go-build.md +0 -183
  217. package/.agent/workflows/go-review.md +0 -148
  218. package/.agent/workflows/go-test.md +0 -268
  219. package/.agent/workflows/gradle-build.md +0 -70
  220. package/.agent/workflows/harness-audit.md +0 -73
  221. package/.agent/workflows/hookify-configure.md +0 -14
  222. package/.agent/workflows/hookify-help.md +0 -46
  223. package/.agent/workflows/hookify-list.md +0 -21
  224. package/.agent/workflows/hookify.md +0 -50
  225. package/.agent/workflows/instinct-export.md +0 -66
  226. package/.agent/workflows/instinct-import.md +0 -114
  227. package/.agent/workflows/instinct-status.md +0 -59
  228. package/.agent/workflows/jira.md +0 -106
  229. package/.agent/workflows/kotlin-build.md +0 -174
  230. package/.agent/workflows/kotlin-review.md +0 -140
  231. package/.agent/workflows/kotlin-test.md +0 -312
  232. package/.agent/workflows/learn-eval.md +0 -116
  233. package/.agent/workflows/learn.md +0 -70
  234. package/.agent/workflows/loop-start.md +0 -32
  235. package/.agent/workflows/loop-status.md +0 -24
  236. package/.agent/workflows/model-route.md +0 -26
  237. package/.agent/workflows/multi-backend.md +0 -158
  238. package/.agent/workflows/multi-execute.md +0 -315
  239. package/.agent/workflows/multi-frontend.md +0 -158
  240. package/.agent/workflows/multi-plan.md +0 -268
  241. package/.agent/workflows/multi-workflow.md +0 -191
  242. package/.agent/workflows/orchestrate.md +0 -135
  243. package/.agent/workflows/plan.md +0 -117
  244. package/.agent/workflows/pm2.md +0 -272
  245. package/.agent/workflows/projects.md +0 -39
  246. package/.agent/workflows/promote.md +0 -41
  247. package/.agent/workflows/prompt-optimize.md +0 -23
  248. package/.agent/workflows/prp-commit.md +0 -112
  249. package/.agent/workflows/prp-implement.md +0 -385
  250. package/.agent/workflows/prp-plan.md +0 -502
  251. package/.agent/workflows/prp-pr.md +0 -184
  252. package/.agent/workflows/prp-prd.md +0 -447
  253. package/.agent/workflows/prune.md +0 -31
  254. package/.agent/workflows/python-review.md +0 -297
  255. package/.agent/workflows/quality-gate.md +0 -29
  256. package/.agent/workflows/refactor-clean.md +0 -80
  257. package/.agent/workflows/resume-session.md +0 -156
  258. package/.agent/workflows/review-pr.md +0 -37
  259. package/.agent/workflows/rules-distill.md +0 -20
  260. package/.agent/workflows/rust-build.md +0 -187
  261. package/.agent/workflows/rust-review.md +0 -142
  262. package/.agent/workflows/rust-test.md +0 -308
  263. package/.agent/workflows/santa-loop.md +0 -175
  264. package/.agent/workflows/save-session.md +0 -275
  265. package/.agent/workflows/sessions.md +0 -333
  266. package/.agent/workflows/setup-pm.md +0 -80
  267. package/.agent/workflows/skill-create.md +0 -174
  268. package/.agent/workflows/skill-health.md +0 -54
  269. package/.agent/workflows/tdd.md +0 -231
  270. package/.agent/workflows/test-coverage.md +0 -69
  271. package/.agent/workflows/update-codemaps.md +0 -72
  272. package/.agent/workflows/update-docs.md +0 -84
  273. package/.agent/workflows/verify.md +0 -23
@@ -0,0 +1,152 @@
1
+ # Cost-Aware Model Routing — Auto-Pick the Right Model Tier per Task
2
+
3
+ This rule teaches the AI to **classify the current task and recommend the right model tier** instead of defaulting to one expensive model for everything. Pairs with the `agent-intent-routing` rule (agent selection) and the `token-budget` safety hook (spend control) to keep ECC sessions both effective AND affordable.
4
+
5
+ > **The 80/20 of cost in agent workflows:** 80% of turns are trivial (rename, format, doc, summary, list-files) and burn frontier-model tokens for no benefit. Routing them to a fast/cheap tier saves 60–80% of monthly spend with no measurable quality loss.
6
+
7
+ ---
8
+
9
+ ## The Rule
10
+
11
+ Before composing your response, classify the task by **scope, ambiguity, and risk**, then either:
12
+
13
+ 1. **Route silently** — the user already picked a model that matches the tier (do nothing).
14
+ 2. **Suggest a tier change** — emit a one-line recommendation when the active model is wildly mismatched (frontier model on a doc-typo fix, or a tiny model on a security audit).
15
+
16
+ Always be transparent. Never silently switch models. Always teach the user how to make the decision next time.
17
+
18
+ ---
19
+
20
+ ## Tier definitions
21
+
22
+ | Tier | Examples | Default models (provider-agnostic) |
23
+ |---|---|---|
24
+ | **trivial** | Single-line rename, format-only, fix typo, add JSDoc, generate plain summary, list files, regex tweak, version bump, capitalisation. Output usually < 50 lines. | Claude Haiku · Gemini Flash · GPT-4.1-mini · Qwen 2.5 Coder 7B (local) · Ollama (Llama 3.1 8B). |
25
+ | **standard** | Code review of a single PR, write a small unit test, refactor one file, fix a non-critical bug, write a focused doc section, design a small data model. | Claude Sonnet · GPT-4.1 · Gemini Pro · Qwen 2.5 Coder 32B. |
26
+ | **hard** | Architecture decision, debug-blitz of intermittent prod bug, security audit, multi-file refactor (3+ files), incident postmortem, design review, devil-mode adversarial sweep, agent harness changes, anything `code-architect`/`incident-commander`/`debug-detective` would own. | Claude Opus · GPT-5 · Gemini Ultra · best-available frontier model. |
27
+
28
+ **The names are reference points** — pick the equivalent tier in whatever provider stack the user has configured. The classification is what matters, not the brand.
29
+
30
+ ---
31
+
32
+ ## Classification heuristics (signals to weigh)
33
+
34
+ Use these signals together — single signals are noisy, three or four together are reliable:
35
+
36
+ ### Trivial signals
37
+ - One file, < 50 lines of context
38
+ - Pure formatting / renaming / documentation work
39
+ - No tests need updating
40
+ - The user's question fits in a single sentence
41
+ - No ambiguity ("rename `getUserName` to `getUserDisplayName`")
42
+ - Verbs: format, rename, capitalize, fix typo, add comment, list, count, summarize
43
+
44
+ ### Standard signals
45
+ - 1–3 files in scope
46
+ - Behavior change + tests required
47
+ - One subsystem
48
+ - Verbs: refactor, review, fix bug, write test, optimize, document, migrate (small)
49
+ - The user pasted a stack trace from one place
50
+
51
+ ### Hard signals
52
+ - Cross-cutting concern (auth, payments, telemetry, retries, concurrency)
53
+ - Multiple subsystems / 4+ files
54
+ - Security implications (PII, secrets, authz, supply chain)
55
+ - Production / incident framing ("urgent", "down", "leaking", "live")
56
+ - Architectural framing ("decide", "design", "should we", "trade-off")
57
+ - The user invoked `code-architect`, `incident-commander`, `debug-detective`, `security-reviewer`, `chaos-engineer`, `backdoor-hunter`, `prompt-injection-hunter`
58
+ - Devil-mode parallel commands
59
+ - The session has already burned > 30k tokens (compounding context)
60
+ - Agent handoff chains involving 3+ specialists
61
+
62
+ If signals are **mixed or contradictory**, default to **standard** and surface the ambiguity to the user.
63
+
64
+ ---
65
+
66
+ ## Output format when suggesting a tier change
67
+
68
+ Emit exactly one block, then proceed normally:
69
+
70
+ ```
71
+ [model-router] task=<trivial|standard|hard> · suggested=<tier> · current=<active model or "unknown">
72
+ why: <one short sentence on the dominant signals>
73
+ next: <how to switch — provider-specific, e.g. "use --model claude-haiku" or "switch to /model haiku">
74
+ ```
75
+
76
+ **Only emit this block when the active model is mismatched.** If the user is already on the right tier, route silently.
77
+
78
+ ---
79
+
80
+ ## Per-team configurability
81
+
82
+ Teams override the defaults via env vars or `.kodelyth/router.json`:
83
+
84
+ ### Env vars
85
+
86
+ | Variable | Effect |
87
+ |---|---|
88
+ | `KODELYTH_ROUTER=off` | Disable the rule entirely. The AI never suggests model changes. |
89
+ | `KODELYTH_ROUTER_TRIVIAL=<model-id>` | Override trivial tier. |
90
+ | `KODELYTH_ROUTER_STANDARD=<model-id>` | Override standard tier. |
91
+ | `KODELYTH_ROUTER_HARD=<model-id>` | Override hard tier. |
92
+ | `KODELYTH_ROUTER_DEFAULT=<trivial\|standard\|hard>` | Default tier for ambiguous tasks (default `standard`). |
93
+
94
+ ### Project file: `.kodelyth/router.json`
95
+
96
+ ```json
97
+ {
98
+ "trivial": "claude-haiku-4-5-20251001",
99
+ "standard": "claude-sonnet-4-6",
100
+ "hard": "claude-opus-4-1",
101
+ "default": "standard",
102
+ "notes": "Set by infra team. Bump trivial→sonnet for any task that touches /payments."
103
+ }
104
+ ```
105
+
106
+ A `notes` field is purely human — the AI surfaces it when emitting a recommendation so the team's reasoning rides along.
107
+
108
+ ---
109
+
110
+ ## Hard rules (never break these)
111
+
112
+ 1. **Security audits, incident triage, and devil-mode never downgrade.** If the active task involves `security-reviewer`, `incident-commander`, `debug-detective`, `prompt-injection-hunter`, `supply-chain-auditor`, `secret-hunter`, `backdoor-hunter`, `chaos-engineer`, `jailbreak-tester`, `code-stealer-detector`, or `license-violation-finder` — recommend **hard** tier even if other signals look trivial.
113
+ 2. **Production framing wins.** "Down", "leaking", "broken in prod", "incident", "outage" → hard.
114
+ 3. **No silent demotion of multi-file refactors** — if 3+ files are in scope, never recommend trivial.
115
+ 4. **Respect explicit user choice.** If the user said "use haiku" or "stay on sonnet", do not override. Surface only if a new turn introduces signals that conflict.
116
+ 5. **Budget-aware:** if the `token-budget` safety hook reports usage > 70% of budget, bias one tier downward when ambiguous to extend the session.
117
+
118
+ ---
119
+
120
+ ## Examples
121
+
122
+ ```
123
+ User: "rename getUserName to getUserDisplayName everywhere"
124
+ [model-router] task=trivial · suggested=trivial · current=opus
125
+ why: single rename, mechanical, no behavior change.
126
+ next: switch to /model claude-haiku to save ~80% on this turn.
127
+ ```
128
+
129
+ ```
130
+ User: "production is down and the auth service is throwing 502s, help"
131
+ [model-router] task=hard · suggested=hard · current=haiku
132
+ why: production incident with security-relevant subsystem.
133
+ next: switch to /model claude-opus before continuing — this is incident-commander territory.
134
+ ```
135
+
136
+ ```
137
+ User: "review this 40-line PR"
138
+ (active model: sonnet — silent route, no block emitted)
139
+ ```
140
+
141
+ ---
142
+
143
+ ## Why this rule exists
144
+
145
+ LLM cost is the #1 reason teams stop using agent toolkits. Two complementary primitives:
146
+
147
+ - The **token-budget safety hook** prevents catastrophic blow-ups (hard cap per session).
148
+ - This **routing rule** prevents the slow leak (frontier-model overuse on trivial work).
149
+
150
+ Together they typically cut LLM bills by 50–70% with no measurable quality drop on standard work.
151
+
152
+ For implementation patterns inside user code (your own pipelines that call LLMs), see the [`cost-aware-llm-pipeline`](../../skills/cost-aware-llm-pipeline/SKILL.md) skill.
@@ -0,0 +1,353 @@
1
+ // scripts/dashboard/data.js
2
+ //
3
+ // Local observability dashboard: pure aggregators (v1.7.0).
4
+ //
5
+ // All data the dashboard renders comes from:
6
+ // - The MCP catalog (filesystem reads of agents/, skills/, commands/, rules/, bundles/)
7
+ // - The BM25 memory store (~/.kodelyth/memory/)
8
+ // - The evolve signal streams (~/.kodelyth/evolve/)
9
+ // - The token-budget hook state (${KODELYTH_TOKEN_BUDGET_DIR})
10
+ // - The orchestration session dirs (.orchestration/<session>/)
11
+ //
12
+ // Every aggregator is pure: takes its inputs explicitly OR uses well-defined
13
+ // env-overridable defaults. No global mutable state. Errors are swallowed
14
+ // and replaced with sane empty defaults — the dashboard must never crash
15
+ // because one data source is unavailable.
16
+ 'use strict';
17
+
18
+ const fs = require('fs');
19
+ const os = require('os');
20
+ const path = require('path');
21
+
22
+ const ROOT = path.resolve(__dirname, '..', '..');
23
+
24
+ // Lazy + defensive requires — the dashboard renders even if one module is broken.
25
+ function safeRequire(rel) {
26
+ try { return require(path.join(ROOT, rel)); } catch { return null; }
27
+ }
28
+
29
+ const memoryStore = safeRequire('scripts/memory/store.js');
30
+ const catalog = safeRequire('scripts/mcp/catalog.js');
31
+ const evolveStats = safeRequire('scripts/evolve/stats.js');
32
+ const evolveProposals = safeRequire('scripts/evolve/proposals.js');
33
+
34
+ // ── helpers ──────────────────────────────────────────────────────────────────
35
+
36
+ function defaultMemoryDir() {
37
+ return process.env.KODELYTH_MEMORY_DIR || path.join(os.homedir(), '.kodelyth', 'memory');
38
+ }
39
+ function defaultEvolveDir() {
40
+ return process.env.KODELYTH_EVOLVE_DIR || path.join(os.homedir(), '.kodelyth', 'evolve');
41
+ }
42
+ function defaultBudgetDir() {
43
+ return process.env.KODELYTH_TOKEN_BUDGET_DIR || path.join(os.homedir(), '.kodelyth', 'token-budget');
44
+ }
45
+ function defaultCoordRoot() {
46
+ return process.env.KODELYTH_COORDINATION_ROOT || path.join(process.cwd(), '.orchestration');
47
+ }
48
+
49
+ function safeReadDir(p) {
50
+ try { return fs.readdirSync(p, { withFileTypes: true }); } catch { return []; }
51
+ }
52
+ function safeStat(p) {
53
+ try { return fs.statSync(p); } catch { return null; }
54
+ }
55
+ function safeReadJson(p, fallback) {
56
+ try {
57
+ return JSON.parse(fs.readFileSync(p, 'utf8'));
58
+ } catch { return fallback; }
59
+ }
60
+
61
+ // ── overview ─────────────────────────────────────────────────────────────────
62
+
63
+ function overview({
64
+ repoRoot = ROOT,
65
+ memoryDir = defaultMemoryDir(),
66
+ evolveDir = defaultEvolveDir(),
67
+ coordRoot = defaultCoordRoot(),
68
+ } = {}) {
69
+ let catalogStats = { agents: 0, skills: 0, commands: 0, rules: 0, bundles: 0 };
70
+ if (catalog?.stats) {
71
+ try { catalogStats = catalog.stats(); } catch { /* swallow */ }
72
+ }
73
+
74
+ let memoryTotal = 0;
75
+ let memoryProjects = 0;
76
+ if (memoryStore?.stats) {
77
+ try {
78
+ const ms = memoryStore.stats();
79
+ memoryTotal = ms.total || 0;
80
+ memoryProjects = ms.projects || 0;
81
+ } catch { /* swallow */ }
82
+ }
83
+
84
+ let proposalsCounts = { pending: 0, accepted: 0, rejected: 0, applied: 0, total: 0 };
85
+ if (evolveProposals?.readLatest) {
86
+ try {
87
+ const all = evolveProposals.readLatest(evolveDir);
88
+ proposalsCounts.total = all.length;
89
+ for (const p of all) {
90
+ if (p.status && proposalsCounts[p.status] !== undefined) {
91
+ proposalsCounts[p.status] += 1;
92
+ }
93
+ }
94
+ } catch { /* swallow */ }
95
+ }
96
+
97
+ let routingMisses = 0;
98
+ if (evolveStats?.getRoutingMissStats) {
99
+ try { routingMisses = evolveStats.getRoutingMissStats(evolveDir).total_misses || 0; } catch { /* */ }
100
+ }
101
+
102
+ let surfaceTotal = 0;
103
+ if (evolveStats?.getReuseStats) {
104
+ try { surfaceTotal = evolveStats.getReuseStats(evolveDir).total_surfaces || 0; } catch { /* */ }
105
+ }
106
+
107
+ let sessionsCount = 0;
108
+ try {
109
+ if (fs.existsSync(coordRoot)) {
110
+ sessionsCount = safeReadDir(coordRoot).filter(e => e.isDirectory()).length;
111
+ }
112
+ } catch { /* */ }
113
+
114
+ return {
115
+ package_root: repoRoot,
116
+ storage: {
117
+ memory_dir: memoryDir,
118
+ evolve_dir: evolveDir,
119
+ coord_root: coordRoot,
120
+ home: os.homedir(),
121
+ },
122
+ catalog: catalogStats,
123
+ memory: { total: memoryTotal, projects: memoryProjects },
124
+ evolve: {
125
+ proposals: proposalsCounts,
126
+ routing_misses: routingMisses,
127
+ surfaces_total: surfaceTotal,
128
+ },
129
+ swarm: {
130
+ sessions: sessionsCount,
131
+ },
132
+ generated_at: new Date().toISOString(),
133
+ };
134
+ }
135
+
136
+ // ── memory ───────────────────────────────────────────────────────────────────
137
+
138
+ // NOTE: the memory store reads `KODELYTH_MEMORY_DIR` once at require time
139
+ // and freezes its path. The dashboard therefore reads from whatever store
140
+ // path was configured at boot — there's no way to redirect mid-process.
141
+ // `memoryDir` here is reported back via /api/overview but cannot be
142
+ // overridden per-call.
143
+ function memoryStats() {
144
+ if (!memoryStore?.stats) return { total: 0, projects: 0, byLanguage: {}, topTags: [], storageDir: defaultMemoryDir() };
145
+ try { return memoryStore.stats(); }
146
+ catch { return { total: 0, projects: 0, byLanguage: {}, topTags: [], storageDir: defaultMemoryDir() }; }
147
+ }
148
+
149
+ function recentMemories({ limit = 20 } = {}) {
150
+ if (!memoryStore?.listAll) return [];
151
+ try {
152
+ const all = memoryStore.listAll();
153
+ all.sort((a, b) => String(b.captured_at || '').localeCompare(String(a.captured_at || '')));
154
+ return all.slice(0, Math.max(1, Math.min(200, limit))).map(m => ({
155
+ id: m.id,
156
+ captured_at: m.captured_at,
157
+ problem: m.problem,
158
+ approach: (m.approach || '').slice(0, 240),
159
+ tags: m.tags || [],
160
+ language: m.language || null,
161
+ project_path: m.project_path || null,
162
+ source: m.source || null,
163
+ }));
164
+ } catch { return []; }
165
+ }
166
+
167
+ function memorySearch({ query, limit = 10 } = {}) {
168
+ if (!memoryStore?.recall || !query) return [];
169
+ try {
170
+ const results = memoryStore.recall(query, { limit: Math.max(1, Math.min(50, limit)) });
171
+ return results.map(r => ({
172
+ id: r.id,
173
+ score: Number((r.score || 0).toFixed(3)),
174
+ problem: r.problem,
175
+ approach: (r.approach || '').slice(0, 240),
176
+ tags: r.tags || [],
177
+ language: r.language || null,
178
+ }));
179
+ } catch { return []; }
180
+ }
181
+
182
+ // ── evolve ───────────────────────────────────────────────────────────────────
183
+
184
+ function evolveSnapshot({ evolveDir = defaultEvolveDir(), proposalLimit = 50 } = {}) {
185
+ proposalLimit = Math.max(1, Math.min(200, proposalLimit));
186
+ let reuse = { total_memories_tracked: 0, total_surfaces: 0, last_updated: null, entries: [] };
187
+ let miss = { total_misses: 0, unique_prompts: 0, entries: [] };
188
+ if (evolveStats?.getReuseStats) {
189
+ try { reuse = evolveStats.getReuseStats(evolveDir); } catch { /* */ }
190
+ }
191
+ if (evolveStats?.getRoutingMissStats) {
192
+ try { miss = evolveStats.getRoutingMissStats(evolveDir); } catch { /* */ }
193
+ }
194
+
195
+ let proposals = [];
196
+ if (evolveProposals?.readLatest) {
197
+ try {
198
+ proposals = evolveProposals.readLatest(evolveDir).slice(0, proposalLimit).map(p => ({
199
+ id: p.id,
200
+ type: p.type,
201
+ status: p.status,
202
+ created_at: p.created_at,
203
+ last_updated: p.last_updated || p.timestamp,
204
+ target_path: p.proposal?.target_path || null,
205
+ rationale: p.proposal?.rationale || null,
206
+ applied_path: p.applied_path || null,
207
+ note: p.note || null,
208
+ }));
209
+ } catch { /* */ }
210
+ }
211
+
212
+ return { reuse, miss, proposals };
213
+ }
214
+
215
+ // ── catalog ──────────────────────────────────────────────────────────────────
216
+
217
+ function catalogList({ kind, query = '', limit = 50 } = {}) {
218
+ if (!catalog) return [];
219
+ const fnByKind = {
220
+ agents: catalog.loadAgents,
221
+ skills: catalog.loadSkills,
222
+ commands: catalog.loadCommands,
223
+ rules: catalog.loadRules,
224
+ bundles: catalog.loadBundles,
225
+ };
226
+ const fn = fnByKind[kind];
227
+ if (!fn) return [];
228
+
229
+ let items;
230
+ try { items = fn() || []; } catch { return []; }
231
+
232
+ const q = String(query || '').toLowerCase().trim();
233
+ if (q) {
234
+ items = items.filter(it => {
235
+ const hay = `${it.name || ''} ${it.description || ''} ${(it.tags || []).join(' ')}`.toLowerCase();
236
+ return hay.includes(q);
237
+ });
238
+ }
239
+ return items.slice(0, Math.max(1, Math.min(500, limit))).map(it => ({
240
+ name: it.name,
241
+ description: it.description || '',
242
+ tags: it.tags || [],
243
+ path: it.path || null,
244
+ origin: it.origin || null,
245
+ }));
246
+ }
247
+
248
+ function catalogCounts() {
249
+ if (!catalog?.stats) return { agents: 0, skills: 0, commands: 0, rules: 0, bundles: 0 };
250
+ try { return catalog.stats(); } catch { return { agents: 0, skills: 0, commands: 0, rules: 0, bundles: 0 }; }
251
+ }
252
+
253
+ // ── sessions (orchestration / swarm) ─────────────────────────────────────────
254
+
255
+ function sessionsList({ coordRoot = defaultCoordRoot(), limit = 30 } = {}) {
256
+ if (!fs.existsSync(coordRoot)) return [];
257
+ const dirs = safeReadDir(coordRoot).filter(e => e.isDirectory());
258
+ const out = [];
259
+ for (const d of dirs) {
260
+ const abs = path.join(coordRoot, d.name);
261
+ const st = safeStat(abs);
262
+ if (!st) continue;
263
+ const workers = safeReadDir(abs).filter(e => e.isDirectory()).map(e => e.name);
264
+ out.push({
265
+ session: d.name,
266
+ path: abs,
267
+ modified: st.mtime.toISOString(),
268
+ workers,
269
+ worker_count: workers.length,
270
+ });
271
+ }
272
+ out.sort((a, b) => b.modified.localeCompare(a.modified));
273
+ return out.slice(0, Math.max(1, Math.min(200, limit)));
274
+ }
275
+
276
+ function sessionDetail({ session, coordRoot = defaultCoordRoot() } = {}) {
277
+ if (!session || session === '..' || session === '.') return null;
278
+ const dir = path.join(coordRoot, session);
279
+ // Containment check — ensure the resolved path stays within coordRoot.
280
+ if (!dir.startsWith(coordRoot + path.sep) && dir !== coordRoot) return null;
281
+ if (!fs.existsSync(dir)) return null;
282
+ const workers = safeReadDir(dir).filter(e => e.isDirectory());
283
+ return {
284
+ session,
285
+ path: dir,
286
+ workers: workers.map(w => {
287
+ const wdir = path.join(dir, w.name);
288
+ const taskPath = path.join(wdir, 'task.md');
289
+ const handoffPath = path.join(wdir, 'handoff.md');
290
+ const statusPath = path.join(wdir, 'status.md');
291
+ return {
292
+ slug: w.name,
293
+ has_task: fs.existsSync(taskPath),
294
+ has_handoff: fs.existsSync(handoffPath),
295
+ has_status: fs.existsSync(statusPath),
296
+ task_excerpt: safeReadExcerpt(taskPath, 800),
297
+ handoff_excerpt: safeReadExcerpt(handoffPath, 800),
298
+ status_excerpt: safeReadExcerpt(statusPath, 800),
299
+ };
300
+ }),
301
+ };
302
+ }
303
+
304
+ function safeReadExcerpt(p, max = 800) {
305
+ try {
306
+ const content = fs.readFileSync(p, 'utf8');
307
+ return content.length > max ? content.slice(0, max) + '\n…(truncated)' : content;
308
+ } catch { return null; }
309
+ }
310
+
311
+ // ── token budget snapshot ────────────────────────────────────────────────────
312
+
313
+ function tokenBudgetSnapshot({ budgetDir = defaultBudgetDir() } = {}) {
314
+ if (!fs.existsSync(budgetDir)) return { sessions: [], total_tokens: 0 };
315
+ const files = safeReadDir(budgetDir).filter(e => e.isFile() && e.name.startsWith('budget-') && e.name.endsWith('.json'));
316
+ const sessions = [];
317
+ let total = 0;
318
+ for (const f of files) {
319
+ const data = safeReadJson(path.join(budgetDir, f.name), null);
320
+ if (!data) continue;
321
+ sessions.push({
322
+ session_id: data.session_id || f.name.replace(/^budget-|\.json$/g, ''),
323
+ tokens: Number(data.tokens) || 0,
324
+ budget: data.budget || null,
325
+ mode: data.mode || null,
326
+ last_event: data.last_event || data.updated_at || null,
327
+ });
328
+ total += Number(data.tokens) || 0;
329
+ }
330
+ sessions.sort((a, b) => (b.last_event || '').localeCompare(a.last_event || ''));
331
+ return { sessions: sessions.slice(0, 50), total_tokens: total };
332
+ }
333
+
334
+ module.exports = {
335
+ // overview
336
+ overview,
337
+ // memory
338
+ memoryStats,
339
+ recentMemories,
340
+ memorySearch,
341
+ // evolve
342
+ evolveSnapshot,
343
+ // catalog
344
+ catalogList,
345
+ catalogCounts,
346
+ // sessions
347
+ sessionsList,
348
+ sessionDetail,
349
+ // token budget
350
+ tokenBudgetSnapshot,
351
+ // exposed for tests
352
+ _internals: { defaultMemoryDir, defaultEvolveDir, defaultBudgetDir, defaultCoordRoot, safeReadDir, safeReadJson, safeReadExcerpt },
353
+ };