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,72 @@
1
+ <svg viewBox="0 0 900 185" xmlns="http://www.w3.org/2000/svg" font-family="'Segoe UI', system-ui, -apple-system, sans-serif">
2
+ <defs>
3
+ <linearGradient id="bg" x1="0%" y1="0%" x2="100%" y2="100%">
4
+ <stop offset="0%" style="stop-color:#0d1117"/>
5
+ <stop offset="100%" style="stop-color:#161b22"/>
6
+ </linearGradient>
7
+ <marker id="arr" markerWidth="7" markerHeight="7" refX="5" refY="3" orient="auto">
8
+ <path d="M0,0 L5,3 L0,6 Z" fill="#a78bfa" opacity="0.8"/>
9
+ </marker>
10
+ <marker id="arrg" markerWidth="7" markerHeight="7" refX="5" refY="3" orient="auto">
11
+ <path d="M0,0 L5,3 L0,6 Z" fill="#34d399" opacity="0.8"/>
12
+ </marker>
13
+ </defs>
14
+ <rect width="900" height="185" fill="url(#bg)" rx="12"/>
15
+ <rect width="900" height="185" fill="none" stroke="#21262d" stroke-width="1" rx="12"/>
16
+
17
+ <!-- Label — widened to fit text -->
18
+ <rect x="32" y="20" width="248" height="22" rx="11" fill="#f59e0b" fill-opacity="0.12" stroke="#f59e0b" stroke-opacity="0.4" stroke-width="1"/>
19
+ <text x="156" y="35" font-size="10" fill="#fbbf24" text-anchor="middle" font-weight="700" letter-spacing="1">PARALLEL MULTI-AGENT COMMANDS</text>
20
+
21
+ <text x="32" y="72" font-size="21" font-weight="800" fill="#ffffff">8 specialists fire simultaneously — not one by one</text>
22
+ <text x="32" y="94" font-size="12" fill="#6e7681">60 min sequential review → 15 min parallel · all results aggregated into one structured report</text>
23
+
24
+ <!-- Command trigger box -->
25
+ <rect x="32" y="112" width="120" height="56" rx="7" fill="#161b22" stroke="#a78bfa" stroke-opacity="0.5" stroke-width="1"/>
26
+ <text x="92" y="132" font-size="10" fill="#a78bfa" text-anchor="middle" font-weight="700">/team-review</text>
27
+ <text x="92" y="148" font-size="8" fill="#6e7681" text-anchor="middle">one command</text>
28
+ <text x="92" y="160" font-size="8" fill="#6e7681" text-anchor="middle">fires 4 agents</text>
29
+
30
+ <!-- Fan-out lines to 4 agents -->
31
+ <line x1="155" y1="122" x2="196" y2="115" stroke="#a78bfa" stroke-width="1" marker-end="url(#arr)" opacity="0.6"/>
32
+ <line x1="155" y1="131" x2="196" y2="131" stroke="#a78bfa" stroke-width="1" marker-end="url(#arr)" opacity="0.6"/>
33
+ <line x1="155" y1="140" x2="196" y2="147" stroke="#a78bfa" stroke-width="1" marker-end="url(#arr)" opacity="0.6"/>
34
+ <line x1="155" y1="149" x2="196" y2="163" stroke="#a78bfa" stroke-width="1" marker-end="url(#arr)" opacity="0.6"/>
35
+
36
+ <!-- 4 agent pills — 130px wide each, text fits at font-size 9 -->
37
+ <rect x="199" y="106" width="130" height="20" rx="5" fill="#a78bfa" fill-opacity="0.12" stroke="#a78bfa" stroke-opacity="0.4" stroke-width="1"/>
38
+ <text x="264" y="119" font-size="9" fill="#a78bfa" text-anchor="middle" font-weight="600">code-reviewer</text>
39
+
40
+ <rect x="199" y="122" width="130" height="20" rx="5" fill="#ef4444" fill-opacity="0.1" stroke="#ef4444" stroke-opacity="0.35" stroke-width="1"/>
41
+ <text x="264" y="135" font-size="9" fill="#f87171" text-anchor="middle" font-weight="600">security-reviewer</text>
42
+
43
+ <rect x="199" y="138" width="130" height="20" rx="5" fill="#f59e0b" fill-opacity="0.1" stroke="#f59e0b" stroke-opacity="0.35" stroke-width="1"/>
44
+ <text x="264" y="151" font-size="9" fill="#fbbf24" text-anchor="middle" font-weight="600">performance-optimizer</text>
45
+
46
+ <rect x="199" y="154" width="130" height="20" rx="5" fill="#2563eb" fill-opacity="0.1" stroke="#2563eb" stroke-opacity="0.35" stroke-width="1"/>
47
+ <text x="264" y="167" font-size="9" fill="#60a5fa" text-anchor="middle" font-weight="600">api-guardian</text>
48
+
49
+ <!-- Fan-in lines to result -->
50
+ <line x1="332" y1="115" x2="370" y2="132" stroke="#34d399" stroke-width="1" marker-end="url(#arrg)" opacity="0.6"/>
51
+ <line x1="332" y1="131" x2="370" y2="136" stroke="#34d399" stroke-width="1" marker-end="url(#arrg)" opacity="0.6"/>
52
+ <line x1="332" y1="147" x2="370" y2="140" stroke="#34d399" stroke-width="1" marker-end="url(#arrg)" opacity="0.6"/>
53
+ <line x1="332" y1="163" x2="370" y2="144" stroke="#34d399" stroke-width="1" marker-end="url(#arrg)" opacity="0.6"/>
54
+
55
+ <!-- Result box -->
56
+ <rect x="373" y="112" width="148" height="56" rx="7" fill="#059669" fill-opacity="0.1" stroke="#059669" stroke-opacity="0.4" stroke-width="1"/>
57
+ <text x="447" y="131" font-size="10" fill="#34d399" text-anchor="middle" font-weight="700">Team Review Report</text>
58
+ <text x="447" y="147" font-size="8" fill="#34d399" text-anchor="middle" opacity="0.8">CRITICAL · HIGH · MEDIUM</text>
59
+ <text x="447" y="160" font-size="8" fill="#34d399" text-anchor="middle" opacity="0.75">1 critical found → fix first</text>
60
+
61
+ <!-- All 8 parallel commands — two columns -->
62
+ <text x="546" y="112" font-size="9" fill="#6e7681" font-weight="600">All 8 parallel commands:</text>
63
+ <text x="546" y="127" font-size="9" fill="#a78bfa">/team-review</text>
64
+ <text x="546" y="141" font-size="9" fill="#a78bfa">/project-launch</text>
65
+ <text x="546" y="155" font-size="9" fill="#ef4444">/devil-mode</text>
66
+ <text x="546" y="169" font-size="9" fill="#f59e0b">/security-audit</text>
67
+
68
+ <text x="700" y="127" font-size="9" fill="#38bdf8">/debug-blitz</text>
69
+ <text x="700" y="141" font-size="9" fill="#38bdf8">/refactor-sprint</text>
70
+ <text x="700" y="155" font-size="9" fill="#34d399">/pre-release</text>
71
+ <text x="700" y="169" font-size="9" fill="#34d399">/onboard</text>
72
+ </svg>
@@ -0,0 +1,51 @@
1
+ <svg viewBox="0 0 900 170" xmlns="http://www.w3.org/2000/svg" font-family="'Segoe UI', system-ui, -apple-system, sans-serif">
2
+ <defs>
3
+ <linearGradient id="bg" x1="0%" y1="0%" x2="100%" y2="100%">
4
+ <stop offset="0%" style="stop-color:#0d1117"/>
5
+ <stop offset="100%" style="stop-color:#161b22"/>
6
+ </linearGradient>
7
+ <linearGradient id="acc" x1="0%" y1="0%" x2="100%" y2="0%">
8
+ <stop offset="0%" style="stop-color:#a78bfa"/>
9
+ <stop offset="100%" style="stop-color:#38bdf8"/>
10
+ </linearGradient>
11
+ <marker id="arr" markerWidth="8" markerHeight="8" refX="6" refY="3" orient="auto">
12
+ <path d="M0,0 L6,3 L0,6 Z" fill="#a78bfa" opacity="0.7"/>
13
+ </marker>
14
+ </defs>
15
+ <rect width="900" height="170" fill="url(#bg)" rx="12"/>
16
+ <rect width="900" height="170" fill="none" stroke="#21262d" stroke-width="1" rx="12"/>
17
+
18
+ <!-- Section label -->
19
+ <rect x="32" y="20" width="180" height="22" rx="11" fill="#a78bfa" fill-opacity="0.12" stroke="#a78bfa" stroke-opacity="0.35" stroke-width="1"/>
20
+ <text x="122" y="35" font-size="10" fill="#a78bfa" text-anchor="middle" font-weight="700" letter-spacing="1">INTENT ROUTING SYSTEM</text>
21
+
22
+ <!-- Title -->
23
+ <text x="32" y="72" font-size="22" font-weight="800" fill="#ffffff">Plain language → right specialist — automatically</text>
24
+ <text x="32" y="96" font-size="12" fill="#6e7681">The 10-tier routing rule fires on every session. No agent names needed. The AI announces who's taking over.</text>
25
+
26
+ <!-- Flow diagram -->
27
+ <rect x="32" y="118" width="120" height="40" rx="6" fill="#21262d" stroke="#30363d" stroke-width="1"/>
28
+ <text x="92" y="138" font-size="10" fill="#c9d1d9" text-anchor="middle">"I've been stuck for</text>
29
+ <text x="92" y="152" font-size="10" fill="#c9d1d9" text-anchor="middle">hours on this bug"</text>
30
+
31
+ <line x1="155" y1="138" x2="195" y2="138" stroke="#a78bfa" stroke-width="1.5" marker-end="url(#arr)" opacity="0.7"/>
32
+
33
+ <rect x="198" y="118" width="108" height="40" rx="6" fill="#a78bfa" fill-opacity="0.15" stroke="#a78bfa" stroke-opacity="0.5" stroke-width="1"/>
34
+ <text x="252" y="136" font-size="10" fill="#a78bfa" text-anchor="middle" font-weight="700">10-tier router</text>
35
+ <text x="252" y="150" font-size="9" fill="#a78bfa" text-anchor="middle" opacity="0.7">frustration + error signal</text>
36
+
37
+ <line x1="309" y1="138" x2="349" y2="138" stroke="#a78bfa" stroke-width="1.5" marker-end="url(#arr)" opacity="0.7"/>
38
+
39
+ <rect x="352" y="118" width="130" height="40" rx="6" fill="#38bdf8" fill-opacity="0.12" stroke="#38bdf8" stroke-opacity="0.4" stroke-width="1"/>
40
+ <text x="417" y="136" font-size="10" fill="#38bdf8" text-anchor="middle" font-weight="700">→ debug-detective</text>
41
+ <text x="417" y="150" font-size="9" fill="#38bdf8" text-anchor="middle" opacity="0.7">root-cause analyst</text>
42
+
43
+ <line x1="485" y1="138" x2="525" y2="138" stroke="#6e7681" stroke-width="1" stroke-dasharray="4,3" opacity="0.5"/>
44
+
45
+ <rect x="528" y="118" width="155" height="40" rx="6" fill="#059669" fill-opacity="0.1" stroke="#059669" stroke-opacity="0.4" stroke-width="1"/>
46
+ <text x="605" y="135" font-size="9.5" fill="#34d399" text-anchor="middle">"Tip: next time type</text>
47
+ <text x="605" y="149" font-size="9.5" fill="#34d399" text-anchor="middle">'use debug-detective'"</text>
48
+
49
+ <!-- Right label -->
50
+ <text x="868" y="96" font-size="10" fill="#6e7681" text-anchor="end">70 agents · 10 tiers · 50+ patterns</text>
51
+ </svg>
@@ -19,7 +19,7 @@
19
19
  <circle cx="100" cy="100" r="200" fill="#a78bfa" opacity="0.03"/>
20
20
 
21
21
  <!-- Header -->
22
- <text x="80" y="72" font-size="48" font-weight="900" letter-spacing="-2" fill="#ffffff">62 agents. One install.</text>
22
+ <text x="80" y="72" font-size="48" font-weight="900" letter-spacing="-2" fill="#ffffff">70 agents. One install.</text>
23
23
  <text x="80" y="110" font-size="18" fill="#6e7681">Semantic routing picks the right one — you never need to know their names.</text>
24
24
 
25
25
  <!-- Left column — agent cards -->
@@ -91,18 +91,18 @@
91
91
  <text x="460" y="608" font-size="12" fill="#484f58" text-anchor="middle">Works with Claude Code · Windsurf · Cursor · Codex · Antigravity · OpenCode</text>
92
92
 
93
93
  <!-- Stats -->
94
- <text x="920" y="200" font-size="72" font-weight="900" fill="#ffffff" text-anchor="middle">62</text>
94
+ <text x="920" y="200" font-size="72" font-weight="900" fill="#ffffff" text-anchor="middle">70</text>
95
95
  <text x="920" y="228" font-size="10" fill="#484f58" text-anchor="middle" letter-spacing="3">AGENTS</text>
96
96
  <line x1="860" y1="248" x2="980" y2="248" stroke="#21262d" stroke-width="1"/>
97
- <text x="920" y="280" font-size="32" font-weight="800" fill="#ffffff" text-anchor="middle">188</text>
97
+ <text x="920" y="280" font-size="32" font-weight="800" fill="#ffffff" text-anchor="middle">194</text>
98
98
  <text x="920" y="302" font-size="10" fill="#484f58" text-anchor="middle" letter-spacing="2">SKILLS</text>
99
99
  <line x1="860" y1="320" x2="980" y2="320" stroke="#21262d" stroke-width="1"/>
100
- <text x="920" y="352" font-size="32" font-weight="800" fill="#ffffff" text-anchor="middle">20+</text>
100
+ <text x="920" y="352" font-size="32" font-weight="800" fill="#ffffff" text-anchor="middle">22+</text>
101
101
  <text x="920" y="374" font-size="10" fill="#484f58" text-anchor="middle" letter-spacing="2">HOOKS</text>
102
102
  <line x1="860" y1="392" x2="980" y2="392" stroke="#21262d" stroke-width="1"/>
103
- <text x="920" y="424" font-size="32" font-weight="800" fill="#ffffff" text-anchor="middle">6</text>
103
+ <text x="920" y="424" font-size="32" font-weight="800" fill="#ffffff" text-anchor="middle">12</text>
104
104
  <text x="920" y="446" font-size="10" fill="#484f58" text-anchor="middle" letter-spacing="2">PLATFORMS</text>
105
105
  <line x1="860" y1="464" x2="980" y2="464" stroke="#21262d" stroke-width="1"/>
106
- <text x="920" y="496" font-size="20" font-weight="800" fill="#ffffff" text-anchor="middle">v1.5.8</text>
106
+ <text x="920" y="496" font-size="20" font-weight="800" fill="#ffffff" text-anchor="middle">v1.7.0</text>
107
107
  <text x="920" y="516" font-size="10" fill="#484f58" text-anchor="middle" letter-spacing="2">LATEST</text>
108
108
  </svg>
@@ -0,0 +1,235 @@
1
+ // Tests for scripts/dashboard/data.js
2
+ 'use strict';
3
+
4
+ const test = require('node:test');
5
+ const assert = require('node:assert/strict');
6
+ const fs = require('node:fs');
7
+ const os = require('node:os');
8
+ const path = require('node:path');
9
+
10
+ // IMPORTANT: the memory store freezes KODELYTH_MEMORY_DIR at require time.
11
+ // Configure a TMP dir before requiring anything that loads it transitively.
12
+ const TMP_MEM = fs.mkdtempSync(path.join(os.tmpdir(), 'kodelyth-dash-mem-suite-'));
13
+ process.env.KODELYTH_MEMORY_DIR = TMP_MEM;
14
+
15
+ const data = require('../../scripts/dashboard/data.js');
16
+ const memoryStore = require('../../scripts/memory/store.js');
17
+
18
+ function tmpDir(prefix) { return fs.mkdtempSync(path.join(os.tmpdir(), prefix)); }
19
+ function cleanup(dir) { try { fs.rmSync(dir, { recursive: true, force: true }); } catch { /* */ } }
20
+
21
+ // ── overview ────────────────────────────────────────────────────────────────
22
+
23
+ test('overview: returns shape with counts + storage paths even on cold dirs', () => {
24
+ const memDir = tmpDir('kodelyth-dash-mem-');
25
+ const evolveDir = tmpDir('kodelyth-dash-evol-');
26
+ const coordDir = tmpDir('kodelyth-dash-coord-');
27
+ try {
28
+ const o = data.overview({ memoryDir: memDir, evolveDir, coordRoot: coordDir });
29
+ assert.equal(o.storage.memory_dir, memDir);
30
+ assert.equal(o.storage.evolve_dir, evolveDir);
31
+ assert.equal(o.storage.coord_root, coordDir);
32
+ assert.ok(typeof o.catalog.agents === 'number');
33
+ assert.ok(typeof o.catalog.skills === 'number');
34
+ assert.equal(o.evolve.proposals.pending, 0);
35
+ assert.equal(o.evolve.proposals.total, 0);
36
+ assert.equal(o.swarm.sessions, 0);
37
+ assert.match(o.generated_at, /^\d{4}-\d{2}-\d{2}T/);
38
+ } finally { cleanup(memDir); cleanup(evolveDir); cleanup(coordDir); }
39
+ });
40
+
41
+ test('overview: catalog counts are non-zero for the real package root', () => {
42
+ const o = data.overview();
43
+ assert.ok(o.catalog.skills > 0, 'skills count > 0 for real install');
44
+ assert.ok(o.catalog.commands > 0);
45
+ });
46
+
47
+ // ── memory ──────────────────────────────────────────────────────────────────
48
+ // All memory tests share TMP_MEM (the store freezes its dir at require time).
49
+ // Run order matters: empty assertion → seed → readback.
50
+
51
+ test('memory: empty store yields empty recent list', () => {
52
+ // Runs first, before any captures.
53
+ assert.deepEqual(data.recentMemories({ limit: 5 }), []);
54
+ assert.equal(data.memoryStats().total, 0);
55
+ });
56
+
57
+ test('memory: empty query returns []', () => {
58
+ assert.deepEqual(data.memorySearch({ query: '' }), []);
59
+ assert.deepEqual(data.memorySearch({}), []);
60
+ });
61
+
62
+ test('memory: recent + search after seeding two captures', async () => {
63
+ const m1 = memoryStore.capture({ problem: 'unique-token-tailwind-v4-arbitrary one', approach: 'A1', tags: ['css'], language: 'js' });
64
+ await new Promise(r => setTimeout(r, 15));
65
+ const m2 = memoryStore.capture({ problem: 'second-distinct-problem-here', approach: 'A2', tags: ['ts'], language: 'ts' });
66
+
67
+ const recent = data.recentMemories({ limit: 5 });
68
+ assert.ok(recent.length >= 2, 'has at least the two captures');
69
+ // Newest first.
70
+ const newestId = recent[0].id;
71
+ assert.equal(newestId, m2.id);
72
+ assert.ok(recent.some(r => r.id === m1.id));
73
+ assert.match(recent[0].approach, /^A2/);
74
+
75
+ const stats = data.memoryStats();
76
+ assert.ok(stats.total >= 2);
77
+ assert.ok(typeof stats.byLanguage === 'object');
78
+
79
+ const hits = data.memorySearch({ query: 'unique-token-tailwind-v4-arbitrary', limit: 5 });
80
+ assert.ok(hits.length >= 1, 'BM25 finds seeded memory');
81
+ assert.match(hits[0].problem, /unique-token-tailwind-v4-arbitrary/);
82
+ assert.ok(typeof hits[0].score === 'number');
83
+ });
84
+
85
+ // ── evolve ──────────────────────────────────────────────────────────────────
86
+
87
+ test('evolveSnapshot: empty dir → zero counts + empty arrays', () => {
88
+ const evolveDir = tmpDir('kodelyth-dash-evol-');
89
+ try {
90
+ const e = data.evolveSnapshot({ evolveDir });
91
+ assert.equal(e.reuse.total_memories_tracked, 0);
92
+ assert.equal(e.reuse.total_surfaces, 0);
93
+ assert.equal(e.miss.total_misses, 0);
94
+ assert.deepEqual(e.proposals, []);
95
+ } finally { cleanup(evolveDir); }
96
+ });
97
+
98
+ test('evolveSnapshot: surfaces a recorded reuse + proposal', () => {
99
+ const evolveDir = tmpDir('kodelyth-dash-evol-');
100
+ try {
101
+ const stats = require('../../scripts/evolve/stats.js');
102
+ const proposals = require('../../scripts/evolve/proposals.js');
103
+ stats.recordSurface({ memoryId: 'mem-x', sessionId: 's1' }, evolveDir);
104
+ proposals.appendProposal({
105
+ id: 'skill-test',
106
+ type: 'skill-upgrade',
107
+ proposal: { kind: 'create-skill', target_path: 'skills/x/SKILL.md', diff: '# x', rationale: 'why' },
108
+ }, evolveDir);
109
+
110
+ const e = data.evolveSnapshot({ evolveDir });
111
+ assert.equal(e.reuse.total_memories_tracked, 1);
112
+ assert.equal(e.reuse.total_surfaces, 1);
113
+ assert.equal(e.proposals.length, 1);
114
+ assert.equal(e.proposals[0].id, 'skill-test');
115
+ assert.equal(e.proposals[0].status, 'pending');
116
+ assert.equal(e.proposals[0].target_path, 'skills/x/SKILL.md');
117
+ } finally { cleanup(evolveDir); }
118
+ });
119
+
120
+ // ── catalog ─────────────────────────────────────────────────────────────────
121
+
122
+ test('catalogList: rejects unknown kind by returning []', () => {
123
+ assert.deepEqual(data.catalogList({ kind: 'junk' }), []);
124
+ });
125
+
126
+ test('catalogList: returns skills for kind=skills', () => {
127
+ const items = data.catalogList({ kind: 'skills', limit: 5 });
128
+ assert.ok(items.length >= 1);
129
+ assert.ok(typeof items[0].name === 'string');
130
+ });
131
+
132
+ test('catalogList: query filters by substring', () => {
133
+ const items = data.catalogList({ kind: 'skills', query: 'self-evolving', limit: 50 });
134
+ // Phase 3.4 just shipped this skill — must be findable.
135
+ assert.ok(items.some(it => it.name.includes('self-evolving')), 'finds self-evolving-memory skill');
136
+ });
137
+
138
+ test('catalogList: limit caps results', () => {
139
+ const items = data.catalogList({ kind: 'skills', limit: 3 });
140
+ assert.ok(items.length <= 3);
141
+ });
142
+
143
+ test('catalogCounts: matches stats() shape', () => {
144
+ const c = data.catalogCounts();
145
+ for (const k of ['agents', 'skills', 'commands', 'rules', 'bundles']) {
146
+ assert.ok(typeof c[k] === 'number');
147
+ }
148
+ });
149
+
150
+ // ── sessions ────────────────────────────────────────────────────────────────
151
+
152
+ test('sessionsList: missing coord dir returns []', () => {
153
+ assert.deepEqual(data.sessionsList({ coordRoot: '/tmp/__never_exists_xyz_kodelyth' }), []);
154
+ });
155
+
156
+ test('sessionsList: returns sessions with worker counts', () => {
157
+ const root = tmpDir('kodelyth-dash-coord-');
158
+ try {
159
+ fs.mkdirSync(path.join(root, 'sess-A', 'worker-1'), { recursive: true });
160
+ fs.mkdirSync(path.join(root, 'sess-A', 'worker-2'), { recursive: true });
161
+ fs.mkdirSync(path.join(root, 'sess-B', 'worker-1'), { recursive: true });
162
+ const list = data.sessionsList({ coordRoot: root });
163
+ assert.equal(list.length, 2);
164
+ const a = list.find(s => s.session === 'sess-A');
165
+ assert.equal(a.worker_count, 2);
166
+ assert.deepEqual(a.workers.sort(), ['worker-1', 'worker-2']);
167
+ } finally { cleanup(root); }
168
+ });
169
+
170
+ test('sessionDetail: returns null for missing session', () => {
171
+ assert.equal(data.sessionDetail({ session: 'nope', coordRoot: '/tmp/__never_exists_xyz_kodelyth' }), null);
172
+ });
173
+
174
+ test('sessionDetail: reads task/handoff/status excerpts', () => {
175
+ const root = tmpDir('kodelyth-dash-coord-');
176
+ try {
177
+ const wdir = path.join(root, 'sess-X', 'worker-1');
178
+ fs.mkdirSync(wdir, { recursive: true });
179
+ fs.writeFileSync(path.join(wdir, 'task.md'), '# task\nbody\n');
180
+ fs.writeFileSync(path.join(wdir, 'handoff.md'), '# handoff\nhello\n');
181
+ const d = data.sessionDetail({ session: 'sess-X', coordRoot: root });
182
+ assert.ok(d);
183
+ assert.equal(d.workers.length, 1);
184
+ const w = d.workers[0];
185
+ assert.equal(w.has_task, true);
186
+ assert.equal(w.has_handoff, true);
187
+ assert.equal(w.has_status, false);
188
+ assert.match(w.task_excerpt, /^# task/);
189
+ assert.match(w.handoff_excerpt, /hello/);
190
+ } finally { cleanup(root); }
191
+ });
192
+
193
+ test('safeReadExcerpt: truncates large files', () => {
194
+ const tmp = tmpDir('kodelyth-dash-trunc-');
195
+ const f = path.join(tmp, 'big.md');
196
+ try {
197
+ fs.writeFileSync(f, 'x'.repeat(5000));
198
+ const ex = data._internals.safeReadExcerpt(f, 100);
199
+ assert.ok(ex.endsWith('…(truncated)'));
200
+ assert.ok(ex.length < 5000);
201
+ } finally { cleanup(tmp); }
202
+ });
203
+
204
+ // ── token budget ────────────────────────────────────────────────────────────
205
+
206
+ test('tokenBudgetSnapshot: missing dir → empty result', () => {
207
+ const r = data.tokenBudgetSnapshot({ budgetDir: '/tmp/__never_exists_kodelyth_budget' });
208
+ assert.deepEqual(r, { sessions: [], total_tokens: 0 });
209
+ });
210
+
211
+ test('tokenBudgetSnapshot: aggregates JSON budget files', () => {
212
+ const dir = tmpDir('kodelyth-dash-budget-');
213
+ try {
214
+ fs.writeFileSync(path.join(dir, 'budget-s1.json'),
215
+ JSON.stringify({ session_id: 's1', tokens: 1234, budget: 10000, mode: 'warn', last_event: '2026-05-10T10:00:00Z' }));
216
+ fs.writeFileSync(path.join(dir, 'budget-s2.json'),
217
+ JSON.stringify({ session_id: 's2', tokens: 5678, budget: 10000, mode: 'warn', last_event: '2026-05-10T11:00:00Z' }));
218
+ const r = data.tokenBudgetSnapshot({ budgetDir: dir });
219
+ assert.equal(r.sessions.length, 2);
220
+ assert.equal(r.total_tokens, 1234 + 5678);
221
+ assert.equal(r.sessions[0].session_id, 's2', 'sorted by last_event desc');
222
+ } finally { cleanup(dir); }
223
+ });
224
+
225
+ test('tokenBudgetSnapshot: silently skips malformed budget files', () => {
226
+ const dir = tmpDir('kodelyth-dash-budget-');
227
+ try {
228
+ fs.writeFileSync(path.join(dir, 'budget-broken.json'), 'not-json{{{');
229
+ fs.writeFileSync(path.join(dir, 'budget-good.json'),
230
+ JSON.stringify({ session_id: 'good', tokens: 100 }));
231
+ const r = data.tokenBudgetSnapshot({ budgetDir: dir });
232
+ assert.equal(r.sessions.length, 1, 'only good file counted');
233
+ assert.equal(r.total_tokens, 100);
234
+ } finally { cleanup(dir); }
235
+ });
@@ -0,0 +1,240 @@
1
+ // Smoke tests for scripts/dashboard/server.js
2
+ //
3
+ // We boot the real HTTP server on an ephemeral localhost port, exercise
4
+ // every route via the built-in `http` module (no external HTTP client),
5
+ // and assert response shapes + security posture.
6
+ 'use strict';
7
+
8
+ const test = require('node:test');
9
+ const assert = require('node:assert/strict');
10
+ const http = require('node:http');
11
+
12
+ const { createServer, _internals } = require('../../scripts/dashboard/server.js');
13
+ const dashboard = require('../../scripts/dashboard/server.js');
14
+
15
+ // ── helpers ──────────────────────────────────────────────────────────────────
16
+
17
+ function listenEphemeral(server) {
18
+ return new Promise((resolve, reject) => {
19
+ server.once('error', reject);
20
+ server.listen(0, '127.0.0.1', () => resolve(server.address().port));
21
+ });
22
+ }
23
+
24
+ function get(port, path, { method = 'GET', headers = {} } = {}) {
25
+ return new Promise((resolve, reject) => {
26
+ const req = http.request({ host: '127.0.0.1', port, path, method, headers }, res => {
27
+ const chunks = [];
28
+ res.on('data', c => chunks.push(c));
29
+ res.on('end', () => resolve({
30
+ status: res.statusCode,
31
+ headers: res.headers,
32
+ body: Buffer.concat(chunks).toString('utf8'),
33
+ }));
34
+ });
35
+ req.on('error', reject);
36
+ req.end();
37
+ });
38
+ }
39
+
40
+ function shutdown(server) {
41
+ return new Promise(resolve => server.close(resolve));
42
+ }
43
+
44
+ // ── tests ───────────────────────────────────────────────────────────────────
45
+
46
+ test('GET /api/health returns ok+time', async () => {
47
+ const server = createServer();
48
+ const port = await listenEphemeral(server);
49
+ try {
50
+ const r = await get(port, '/api/health');
51
+ assert.equal(r.status, 200);
52
+ const body = JSON.parse(r.body);
53
+ assert.equal(body.ok, true);
54
+ assert.match(body.time, /^\d{4}-\d{2}-\d{2}T/);
55
+ } finally { await shutdown(server); }
56
+ });
57
+
58
+ test('GET /api/overview returns shape', async () => {
59
+ const server = createServer();
60
+ const port = await listenEphemeral(server);
61
+ try {
62
+ const r = await get(port, '/api/overview');
63
+ assert.equal(r.status, 200);
64
+ const body = JSON.parse(r.body);
65
+ assert.ok(body.catalog);
66
+ assert.ok(body.memory);
67
+ assert.ok(body.evolve);
68
+ assert.ok(body.swarm);
69
+ assert.ok(body.storage);
70
+ } finally { await shutdown(server); }
71
+ });
72
+
73
+ test('GET /api/memory returns stats + recent', async () => {
74
+ const server = createServer();
75
+ const port = await listenEphemeral(server);
76
+ try {
77
+ const r = await get(port, '/api/memory?limit=5');
78
+ assert.equal(r.status, 200);
79
+ const body = JSON.parse(r.body);
80
+ assert.ok(body.stats);
81
+ assert.ok(Array.isArray(body.recent));
82
+ } finally { await shutdown(server); }
83
+ });
84
+
85
+ test('GET /api/memory/search rejects missing q', async () => {
86
+ const server = createServer();
87
+ const port = await listenEphemeral(server);
88
+ try {
89
+ const r = await get(port, '/api/memory/search');
90
+ assert.equal(r.status, 400);
91
+ const body = JSON.parse(r.body);
92
+ assert.match(body.error, /q/);
93
+ } finally { await shutdown(server); }
94
+ });
95
+
96
+ test('GET /api/evolve returns reuse + miss + proposals', async () => {
97
+ const server = createServer();
98
+ const port = await listenEphemeral(server);
99
+ try {
100
+ const r = await get(port, '/api/evolve');
101
+ assert.equal(r.status, 200);
102
+ const body = JSON.parse(r.body);
103
+ assert.ok(body.reuse);
104
+ assert.ok(body.miss);
105
+ assert.ok(Array.isArray(body.proposals));
106
+ } finally { await shutdown(server); }
107
+ });
108
+
109
+ test('GET /api/catalog?kind=skills returns items', async () => {
110
+ const server = createServer();
111
+ const port = await listenEphemeral(server);
112
+ try {
113
+ const r = await get(port, '/api/catalog?kind=skills&limit=5');
114
+ assert.equal(r.status, 200);
115
+ const body = JSON.parse(r.body);
116
+ assert.equal(body.kind, 'skills');
117
+ assert.ok(body.counts);
118
+ assert.ok(Array.isArray(body.items));
119
+ } finally { await shutdown(server); }
120
+ });
121
+
122
+ test('GET /api/catalog rejects bad kind with 400', async () => {
123
+ const server = createServer();
124
+ const port = await listenEphemeral(server);
125
+ try {
126
+ const r = await get(port, '/api/catalog?kind=junk');
127
+ assert.equal(r.status, 400);
128
+ const body = JSON.parse(r.body);
129
+ assert.match(body.error, /kind/);
130
+ } finally { await shutdown(server); }
131
+ });
132
+
133
+ test('GET /api/sessions returns array', async () => {
134
+ const server = createServer();
135
+ const port = await listenEphemeral(server);
136
+ try {
137
+ const r = await get(port, '/api/sessions');
138
+ assert.equal(r.status, 200);
139
+ const body = JSON.parse(r.body);
140
+ assert.ok(Array.isArray(body.sessions));
141
+ } finally { await shutdown(server); }
142
+ });
143
+
144
+ test('GET /api/sessions/:nonexistent returns 404', async () => {
145
+ const server = createServer();
146
+ const port = await listenEphemeral(server);
147
+ try {
148
+ const r = await get(port, '/api/sessions/this-session-never-exists-xyz');
149
+ assert.equal(r.status, 404);
150
+ } finally { await shutdown(server); }
151
+ });
152
+
153
+ test('GET /api/token-budget returns aggregate shape', async () => {
154
+ const server = createServer();
155
+ const port = await listenEphemeral(server);
156
+ try {
157
+ const r = await get(port, '/api/token-budget');
158
+ assert.equal(r.status, 200);
159
+ const body = JSON.parse(r.body);
160
+ assert.ok(Array.isArray(body.sessions));
161
+ assert.ok(typeof body.total_tokens === 'number');
162
+ } finally { await shutdown(server); }
163
+ });
164
+
165
+ test('GET /api/missing-route returns 404', async () => {
166
+ const server = createServer();
167
+ const port = await listenEphemeral(server);
168
+ try {
169
+ const r = await get(port, '/api/totally-made-up');
170
+ assert.equal(r.status, 404);
171
+ } finally { await shutdown(server); }
172
+ });
173
+
174
+ test('POST is rejected with 405', async () => {
175
+ const server = createServer();
176
+ const port = await listenEphemeral(server);
177
+ try {
178
+ const r = await get(port, '/api/health', { method: 'POST' });
179
+ assert.equal(r.status, 405);
180
+ assert.equal(r.headers.allow, 'GET');
181
+ } finally { await shutdown(server); }
182
+ });
183
+
184
+ test('static root serves index.html', async () => {
185
+ const server = createServer();
186
+ const port = await listenEphemeral(server);
187
+ try {
188
+ const r = await get(port, '/');
189
+ assert.equal(r.status, 200);
190
+ assert.match(r.headers['content-type'], /^text\/html/);
191
+ assert.match(r.body, /Kodelyth ECC/);
192
+ } finally { await shutdown(server); }
193
+ });
194
+
195
+ test('hardened headers on every response', async () => {
196
+ const server = createServer();
197
+ const port = await listenEphemeral(server);
198
+ try {
199
+ const r = await get(port, '/api/health');
200
+ assert.equal(r.headers['x-frame-options'], 'DENY');
201
+ assert.equal(r.headers['x-content-type-options'], 'nosniff');
202
+ assert.equal(r.headers['referrer-policy'], 'no-referrer');
203
+ assert.equal(r.headers['cache-control'], 'no-store');
204
+ } finally { await shutdown(server); }
205
+ });
206
+
207
+ test('start() refuses non-localhost without override', async () => {
208
+ await assert.rejects(
209
+ () => dashboard.start({ port: 0, host: '0.0.0.0', openBrowser: false, log: () => {} }),
210
+ /refusing to bind/,
211
+ );
212
+ });
213
+
214
+ test('start() allows non-localhost when override env var is set', async () => {
215
+ const oldEnv = process.env.KODELYTH_DASHBOARD_ALLOW_REMOTE;
216
+ process.env.KODELYTH_DASHBOARD_ALLOW_REMOTE = '1';
217
+ try {
218
+ const { server } = await dashboard.start({ port: 0, host: '127.0.0.1', openBrowser: false, log: () => {} });
219
+ await shutdown(server);
220
+ } finally {
221
+ if (oldEnv === undefined) delete process.env.KODELYTH_DASHBOARD_ALLOW_REMOTE;
222
+ else process.env.KODELYTH_DASHBOARD_ALLOW_REMOTE = oldEnv;
223
+ }
224
+ });
225
+
226
+ test('resolveStatic rejects path traversal', () => {
227
+ assert.equal(_internals.resolveStatic('/../etc/passwd'), null);
228
+ assert.equal(_internals.resolveStatic('/..%2F..%2Fpackage.json'), null);
229
+ });
230
+
231
+ test('resolveStatic resolves root to index.html', () => {
232
+ const p = _internals.resolveStatic('/');
233
+ assert.match(p || '', /index\.html$/);
234
+ });
235
+
236
+ test('chooseOpenCommand returns an OS-appropriate string', () => {
237
+ const cmd = _internals.chooseOpenCommand();
238
+ assert.ok(typeof cmd === 'string');
239
+ assert.ok(cmd.length > 0);
240
+ });