kodelyth-ecc 1.5.9 → 1.7.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 (123) hide show
  1. package/.github/workflows/publish.yml +19 -0
  2. package/AGENTS.md +1 -1
  3. package/CHANGELOG.md +455 -0
  4. package/CLAUDE.md +48 -16
  5. package/README.md +265 -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 +286 -4
  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 +154 -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 +5 -5
  84. package/social/github-social-preview.svg +119 -100
  85. package/social/readme-hero.svg +11 -11
  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 +159 -0
  103. package/tests/mcp/catalog.test.js +98 -0
  104. package/tests/mcp/client.test.js +109 -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 -28
  120. package/wiki/Hook-Reference.md +1 -1
  121. package/wiki/Installation-Guide.md +109 -6
  122. package/wiki/Platform-Support.md +239 -25
  123. package/wiki/Skill-Reference.md +101 -6
@@ -28,13 +28,873 @@ const args = process.argv.slice(2).filter((a, i, arr) => {
28
28
  });
29
29
  const isWin = os.platform() === 'win32';
30
30
 
31
+ // ── Subcommand: mcp (start MCP server over stdio) ────────────────────────────
32
+ // Usage: npx kodelyth-ecc mcp
33
+ // Exposes 70 agents, 194 skills, 97 commands, the routing rule, and the local
34
+ // memory store to any MCP-compatible client (Claude Desktop, LangGraph, etc.).
35
+ if (args[0] === 'mcp') {
36
+ // Hand off entirely to the MCP server — it owns stdio from here on.
37
+ require(path.join(ROOT, 'scripts', 'mcp', 'server.js')).main()
38
+ .catch(err => {
39
+ process.stderr.write(`[kodelyth-mcp] fatal: ${err.stack || err.message}\n`);
40
+ process.exit(1);
41
+ });
42
+ return;
43
+ }
44
+
45
+ // ── Subcommand: mcp-* (consume external MCP servers) ──────────────────────────
46
+ // Usage:
47
+ // kodelyth-ecc mcp-add <name> -- <command> [args...]
48
+ // kodelyth-ecc mcp-add github -- npx -y @modelcontextprotocol/server-github
49
+ // kodelyth-ecc mcp-add postgres --env DB_URL=... -- npx -y @modelcontextprotocol/server-postgres
50
+ // kodelyth-ecc mcp-list
51
+ // kodelyth-ecc mcp-remove <name>
52
+ // kodelyth-ecc mcp-tools <name>
53
+ // kodelyth-ecc mcp-call <name> <tool> [--json '{"arg":"value"}']
54
+ if (args[0] && args[0].startsWith('mcp-')) {
55
+ (async () => {
56
+ const sub = args[0].slice(4);
57
+ const client = require(path.join(ROOT, 'scripts', 'mcp', 'client.js'));
58
+ const rest = args.slice(1);
59
+
60
+ function findSeparator(arr) {
61
+ const i = arr.indexOf('--');
62
+ return i;
63
+ }
64
+
65
+ try {
66
+ if (sub === 'list') {
67
+ const servers = client.listServers();
68
+ if (servers.length === 0) {
69
+ console.log('No MCP servers registered. Use `kodelyth-ecc mcp-add <name> -- <command> [args...]` to add one.');
70
+ return;
71
+ }
72
+ for (const s of servers) {
73
+ const argList = s.args && s.args.length ? ' ' + s.args.join(' ') : '';
74
+ const envKeys = Object.keys(s.env || {}).join(', ');
75
+ console.log(`• ${s.name}`);
76
+ console.log(` cmd: ${s.command}${argList}`);
77
+ if (envKeys) console.log(` env: ${envKeys}`);
78
+ if (s.description) console.log(` desc: ${s.description}`);
79
+ console.log(` added: ${s.added_at}`);
80
+ }
81
+ return;
82
+ }
83
+
84
+ if (sub === 'add') {
85
+ // Parse: <name> [--env KEY=VAL]* [--desc "..."] -- <command> [args...]
86
+ const sepIdx = findSeparator(rest);
87
+ if (sepIdx < 0) throw new Error('mcp-add requires `--` before the command. Example: kodelyth-ecc mcp-add github -- npx -y @modelcontextprotocol/server-github');
88
+ const head = rest.slice(0, sepIdx);
89
+ const tail = rest.slice(sepIdx + 1);
90
+ if (head.length === 0) throw new Error('mcp-add: missing <name>');
91
+ if (tail.length === 0) throw new Error('mcp-add: missing command after `--`');
92
+
93
+ const name = head[0];
94
+ const env = {};
95
+ let description = '';
96
+ for (let i = 1; i < head.length; i++) {
97
+ const a = head[i];
98
+ if (a === '--env') { const kv = head[++i] || ''; const eq = kv.indexOf('='); if (eq > 0) env[kv.slice(0, eq)] = kv.slice(eq + 1); continue; }
99
+ if (a === '--desc' || a === '--description') { description = head[++i] || ''; continue; }
100
+ }
101
+ const command = tail[0];
102
+ const argv = tail.slice(1);
103
+
104
+ const spec = client.addServer({ name, command, args: argv, env, description });
105
+ console.log(`✓ registered "${spec.name}" (cmd: ${spec.command} ${spec.args.join(' ')})`);
106
+ console.log(` Try: kodelyth-ecc mcp-tools ${spec.name}`);
107
+ return;
108
+ }
109
+
110
+ if (sub === 'remove' || sub === 'rm') {
111
+ if (rest.length === 0) throw new Error('mcp-remove: missing <name>');
112
+ const ok = client.removeServer(rest[0]);
113
+ console.log(ok ? `✓ removed "${rest[0]}"` : `✗ "${rest[0]}" was not registered`);
114
+ process.exit(ok ? 0 : 1);
115
+ }
116
+
117
+ if (sub === 'tools') {
118
+ if (rest.length === 0) throw new Error('mcp-tools: missing <name>');
119
+ const out = await client.listTools(rest[0]);
120
+ console.log(`tools on "${rest[0]}" (${out.tools.length}):`);
121
+ for (const t of out.tools) {
122
+ console.log(` • ${t.name}${t.description ? ' — ' + String(t.description).split('\n')[0].slice(0, 100) : ''}`);
123
+ }
124
+ return;
125
+ }
126
+
127
+ if (sub === 'resources') {
128
+ if (rest.length === 0) throw new Error('mcp-resources: missing <name>');
129
+ const out = await client.listResources(rest[0]);
130
+ console.log(`resources on "${rest[0]}" (${out.resources.length}):`);
131
+ for (const r of out.resources.slice(0, 30)) {
132
+ console.log(` • ${r.uri}`);
133
+ }
134
+ if (out.resources.length > 30) console.log(` …and ${out.resources.length - 30} more.`);
135
+ return;
136
+ }
137
+
138
+ if (sub === 'prompts') {
139
+ if (rest.length === 0) throw new Error('mcp-prompts: missing <name>');
140
+ const out = await client.listPrompts(rest[0]);
141
+ console.log(`prompts on "${rest[0]}" (${out.prompts.length}):`);
142
+ for (const p of out.prompts) {
143
+ console.log(` • ${p.name}${p.description ? ' — ' + p.description.slice(0, 100) : ''}`);
144
+ }
145
+ return;
146
+ }
147
+
148
+ if (sub === 'call') {
149
+ if (rest.length < 2) throw new Error('mcp-call: usage: mcp-call <name> <tool> [--json \'{"arg":"value"}\']');
150
+ const [name, tool, ...flags] = rest;
151
+ let toolArgs = {};
152
+ for (let i = 0; i < flags.length; i++) {
153
+ if (flags[i] === '--json') { toolArgs = JSON.parse(flags[++i] || '{}'); continue; }
154
+ }
155
+ const out = await client.callTool(name, tool, toolArgs);
156
+ for (const c of out.content || []) {
157
+ if (c.type === 'text') process.stdout.write(c.text + '\n');
158
+ else process.stdout.write(JSON.stringify(c) + '\n');
159
+ }
160
+ if (out.isError) process.exit(2);
161
+ return;
162
+ }
163
+
164
+ console.error(`unknown subcommand: mcp-${sub}`);
165
+ console.error('Available: mcp-add, mcp-list, mcp-remove, mcp-tools, mcp-resources, mcp-prompts, mcp-call');
166
+ process.exit(1);
167
+ } catch (e) {
168
+ process.stderr.write(`[kodelyth-mcp-client] ${e.message}\n`);
169
+ process.exit(1);
170
+ }
171
+ })();
172
+ return;
173
+ }
174
+
175
+ // ── Subcommand: route (cost-aware model tier recommendation) ──────────────────
176
+ // Usage: npx kodelyth-ecc route "<task description>" [--files N] [--agent <name>] [--current <model-id>]
177
+ if (args[0] === 'route') {
178
+ const router = require(path.join(ROOT, 'scripts', 'router', 'classify.js'));
179
+
180
+ // Parse flags out of args[1..].
181
+ const rest = args.slice(1);
182
+ const opts = { project_root: process.cwd() };
183
+ const taskParts = [];
184
+ for (let i = 0; i < rest.length; i++) {
185
+ const a = rest[i];
186
+ if (a === '--files' || a === '--file-count') { opts.file_count = Number(rest[++i]); continue; }
187
+ if (a === '--agent') { opts.active_agent = rest[++i]; continue; }
188
+ if (a === '--current' || a === '--model') { opts.current_model = rest[++i]; continue; }
189
+ if (a === '--budget') { opts.budget_tokens = Number(rest[++i]); continue; }
190
+ if (a === '--used') { opts.session_tokens = Number(rest[++i]); continue; }
191
+ if (a === '--json') { opts.json = true; continue; }
192
+ taskParts.push(a);
193
+ }
194
+ const task = taskParts.join(' ').trim();
195
+ if (!task) {
196
+ console.error('usage: kodelyth-ecc route "<task description>" [--files N] [--agent <name>] [--current <model>] [--json]');
197
+ process.exit(1);
198
+ }
199
+
200
+ const r = router.recommend(task, opts);
201
+ if (opts.json) {
202
+ console.log(JSON.stringify(r, null, 2));
203
+ return;
204
+ }
205
+ if (r.disabled) {
206
+ console.log('[model-router] disabled (KODELYTH_ROUTER=off).');
207
+ return;
208
+ }
209
+ const status = r.mismatched ? `· current=${r.current_model} (mismatched)` : '';
210
+ console.log(`[model-router] task=${r.tier} · suggested=${r.recommended_model} ${status}`.trim());
211
+ console.log(` why: ${r.reasons.slice(0, 3).join(' · ')}`);
212
+ if (r.notes) console.log(` notes: ${r.notes}`);
213
+ console.log(` next: configure your client to use "${r.recommended_model}" for this turn.`);
214
+ return;
215
+ }
216
+
217
+ // ── Subcommand: swarm (parallel agent execution in tmux + git worktrees) ──────
218
+ // Usage:
219
+ // kodelyth-ecc swarm --task "audit oauth flow" --agents 4
220
+ // kodelyth-ecc swarm --task "..." --agents code-reviewer,security-reviewer,architect --execute
221
+ // kodelyth-ecc swarm --plan plan.json --execute
222
+ // kodelyth-ecc swarm --task "..." --agents 6 --harness codex --replace --execute
223
+ if (args[0] === 'swarm') {
224
+ (async () => {
225
+ const builder = require(path.join(ROOT, 'scripts', 'swarm', 'build-plan.js'));
226
+ const orchestrator = require(path.join(ROOT, 'scripts', 'lib', 'tmux-worktree-orchestrator.js'));
227
+ const fs = require('fs');
228
+
229
+ const rest = args.slice(1);
230
+ const opts = { repoRoot: process.cwd(), execute: false, dryRun: false, writeOnly: false };
231
+
232
+ let task = '';
233
+ let agentsSpec = null;
234
+ let count = 4;
235
+
236
+ for (let i = 0; i < rest.length; i++) {
237
+ const a = rest[i];
238
+ if (a === '--task') { task = rest[++i] || ''; continue; }
239
+ if (a === '--agents') { agentsSpec = rest[++i] || ''; continue; }
240
+ if (a === '--count') { count = Number(rest[++i]); continue; }
241
+ if (a === '--harness') { opts.harness = rest[++i]; continue; }
242
+ if (a === '--launcher-cmd') { opts.launcherCmd = rest[++i]; continue; }
243
+ if (a === '--session') { opts.sessionName = rest[++i]; continue; }
244
+ if (a === '--worktree-root') { opts.worktreeRoot = rest[++i]; continue; }
245
+ if (a === '--coordination-root') { opts.coordinationRoot = rest[++i]; continue; }
246
+ if (a === '--base-ref') { opts.baseRef = rest[++i]; continue; }
247
+ if (a === '--seed') { (opts.seedPaths ||= []).push(rest[++i]); continue; }
248
+ if (a === '--replace') { opts.replaceExisting = true; continue; }
249
+ if (a === '--execute') { opts.execute = true; continue; }
250
+ if (a === '--write-only') { opts.writeOnly = true; continue; }
251
+ if (a === '--dry-run') { opts.dryRun = true; continue; }
252
+ if (a === '--plan') { opts.planPath = rest[++i]; continue; }
253
+ if (a === '--json') { opts.json = true; continue; }
254
+ if (a === '--help' || a === '-h') {
255
+ console.log(`
256
+ kodelyth-ecc swarm — parallel agent execution in tmux + git worktrees
257
+
258
+ Auto-build a plan from flags (recommended):
259
+ kodelyth-ecc swarm --task "<task>" [--agents N|name1,name2,...] [--harness claude|codex|opencode|windsurf|echo]
260
+ [--seed path] [--session NAME] [--base-ref REF] [--replace] [--execute|--write-only|--dry-run]
261
+
262
+ Use a hand-written plan.json:
263
+ kodelyth-ecc swarm --plan plan.json [--execute|--write-only]
264
+
265
+ Examples:
266
+ kodelyth-ecc swarm --task "audit oauth flow" --agents 4
267
+ kodelyth-ecc swarm --task "ship v2.0" --agents release-captain,security-reviewer,e2e-runner --execute
268
+ kodelyth-ecc swarm --task "scale API to 100k req/sec" --agents 6 --harness codex --replace --execute
269
+
270
+ Default: prints the plan as JSON without spawning anything (safe to inspect).
271
+ --execute creates worktrees + tmux session and launches agents.
272
+ --write-only just writes coordination files (task.md / handoff.md / status.md).
273
+ `);
274
+ return;
275
+ }
276
+ }
277
+
278
+ let planConfig;
279
+ try {
280
+ if (opts.planPath) {
281
+ // Power-user: load a hand-written plan JSON.
282
+ const raw = fs.readFileSync(path.resolve(opts.planPath), 'utf8');
283
+ planConfig = JSON.parse(raw);
284
+ } else {
285
+ // Auto-build from flags.
286
+ if (!task) {
287
+ console.error('swarm: --task is required (or use --plan plan.json). See --help.');
288
+ process.exit(1);
289
+ }
290
+ let agents = null;
291
+ if (agentsSpec) {
292
+ if (/^\d+$/.test(agentsSpec)) count = Number(agentsSpec);
293
+ else agents = agentsSpec.split(',').map(s => s.trim()).filter(Boolean);
294
+ }
295
+ planConfig = builder.buildSwarmPlan({
296
+ task,
297
+ agents,
298
+ count,
299
+ harness: opts.harness || 'claude',
300
+ launcherCmd: opts.launcherCmd,
301
+ sessionName: opts.sessionName,
302
+ repoRoot: opts.repoRoot,
303
+ worktreeRoot: opts.worktreeRoot,
304
+ coordinationRoot: opts.coordinationRoot,
305
+ baseRef: opts.baseRef,
306
+ seedPaths: opts.seedPaths,
307
+ replaceExisting: !!opts.replaceExisting,
308
+ });
309
+ }
310
+
311
+ const plan = orchestrator.buildOrchestrationPlan(planConfig);
312
+
313
+ if (opts.json) {
314
+ console.log(JSON.stringify(plan, null, 2));
315
+ return;
316
+ }
317
+
318
+ if (opts.writeOnly) {
319
+ orchestrator.materializePlan(plan);
320
+ console.log(`✓ wrote orchestration files to ${plan.coordinationDir}`);
321
+ return;
322
+ }
323
+
324
+ if (!opts.execute) {
325
+ // Dry-run summary.
326
+ console.log(`[swarm] dry-run · session=${plan.sessionName} · workers=${plan.workerPlans.length}`);
327
+ console.log(` repo: ${plan.repoRoot}`);
328
+ console.log(` coord: ${plan.coordinationDir}`);
329
+ console.log(` base: ${plan.baseRef}`);
330
+ for (const w of plan.workerPlans) {
331
+ console.log(` • ${w.workerName.padEnd(24)} → ${w.worktreePath}`);
332
+ console.log(` branch: ${w.branchName}`);
333
+ console.log(` launcher: ${String(w.launchCommand).slice(0, 100)}${w.launchCommand.length > 100 ? '…' : ''}`);
334
+ }
335
+ console.log('\nRe-run with --execute to spawn worktrees + tmux session.');
336
+ console.log('Or --write-only to just create coordination files.');
337
+ return;
338
+ }
339
+
340
+ const result = orchestrator.executePlan(plan);
341
+ console.log(`✓ started tmux session "${result.sessionName}" with ${result.workerCount} worker panes`);
342
+ console.log(` attach: tmux attach -t ${result.sessionName}`);
343
+ console.log(` coord: ${result.coordinationDir}`);
344
+ } catch (e) {
345
+ process.stderr.write(`[swarm] ${e.message}\n`);
346
+ process.exit(1);
347
+ }
348
+ })();
349
+ return;
350
+ }
351
+
352
+ // ── Subcommand: session-export / session-import / replay ─────────────────────
353
+ // Usage:
354
+ // kodelyth-ecc session-export <session> [--out file.json] [--task "..."] [--agents a,b] [--harness h]
355
+ // kodelyth-ecc session-import <bundle.json> [--target dir] [--overwrite]
356
+ // kodelyth-ecc replay <bundle.json|session-name> [--harness h] [--agents a,b] [--base-ref ref]
357
+ // [--session NAME] [--execute|--write-only]
358
+ if (args[0] === 'session-export' || args[0] === 'session-import' || args[0] === 'replay') {
359
+ (async () => {
360
+ const fs = require('fs');
361
+ const sub = args[0];
362
+ const rest = args.slice(1);
363
+ const bundleLib = require(path.join(ROOT, 'scripts', 'replay', 'bundle.js'));
364
+ const replayLib = require(path.join(ROOT, 'scripts', 'replay', 'replay.js'));
365
+ const orchestrator = require(path.join(ROOT, 'scripts', 'lib', 'tmux-worktree-orchestrator.js'));
366
+
367
+ function flag(name) {
368
+ const i = rest.indexOf(name);
369
+ return i >= 0 ? rest[i + 1] : undefined;
370
+ }
371
+ function has(name) { return rest.includes(name); }
372
+
373
+ try {
374
+ // ── session-export ─────────────────────────────────────────────────────
375
+ if (sub === 'session-export') {
376
+ const session = rest.find(a => !a.startsWith('--') && rest.indexOf(a) === 0);
377
+ if (!session) throw new Error('session-export: <session> is required (the directory name under .orchestration/)');
378
+
379
+ const coordRoot = flag('--coord-root') || path.join(process.cwd(), '.orchestration');
380
+ const sessionDir = path.isAbsolute(session) ? session : path.join(coordRoot, session);
381
+ const out = flag('--out') || path.join(coordRoot, `${path.basename(sessionDir)}.bundle.json`);
382
+
383
+ const meta = {};
384
+ const taskFlag = flag('--task'); if (taskFlag) meta.task = taskFlag;
385
+ const agFlag = flag('--agents'); if (agFlag) meta.agents = agFlag.split(',').map(s => s.trim()).filter(Boolean);
386
+ const hFlag = flag('--harness'); if (hFlag) meta.harness = hFlag;
387
+ const refFlag = flag('--base-ref'); if (refFlag) meta.base_ref = refFlag;
388
+
389
+ const bundle = bundleLib.exportBundle({
390
+ sessionDir,
391
+ sessionName: path.basename(sessionDir),
392
+ meta,
393
+ });
394
+ bundleLib.writeBundle(bundle, out);
395
+ console.log(`✓ exported bundle: ${out}`);
396
+ console.log(` session: ${bundle.session}`);
397
+ console.log(` workers: ${bundle.workers.length}`);
398
+ if (Object.keys(bundle.meta).length > 0) console.log(` meta: ${JSON.stringify(bundle.meta)}`);
399
+ return;
400
+ }
401
+
402
+ // ── session-import ─────────────────────────────────────────────────────
403
+ if (sub === 'session-import') {
404
+ const bundlePath = rest.find(a => !a.startsWith('--'));
405
+ if (!bundlePath) throw new Error('session-import: <bundle.json> is required');
406
+
407
+ const bundle = bundleLib.readBundle(bundlePath);
408
+ const coordRoot = flag('--coord-root') || path.join(process.cwd(), '.orchestration');
409
+ const targetDir = flag('--target') || path.join(coordRoot, bundle.session);
410
+ const overwrite = has('--overwrite');
411
+
412
+ const out = bundleLib.importBundle(bundle, { targetDir, overwrite });
413
+ console.log(`✓ imported bundle into: ${out.targetDir}`);
414
+ console.log(` workers: ${out.workers.join(', ')}`);
415
+ console.log(` inspect: cat ${path.join(out.targetDir, out.workers[0], 'handoff.md')}`);
416
+ return;
417
+ }
418
+
419
+ // ── replay ─────────────────────────────────────────────────────────────
420
+ if (sub === 'replay') {
421
+ const target = rest.find(a => !a.startsWith('--'));
422
+ if (!target) throw new Error('replay: <bundle.json|session-name> is required');
423
+
424
+ // Resolve target → bundle object.
425
+ let bundle;
426
+ if (target.endsWith('.json') && fs.existsSync(path.resolve(target))) {
427
+ bundle = bundleLib.readBundle(path.resolve(target));
428
+ } else {
429
+ // Treat as session name under .orchestration/.
430
+ const coordRoot = flag('--coord-root') || path.join(process.cwd(), '.orchestration');
431
+ const sessionDir = path.join(coordRoot, target);
432
+ if (!fs.existsSync(sessionDir)) {
433
+ throw new Error(`replay: not a bundle file or known session: ${target}`);
434
+ }
435
+ bundle = bundleLib.exportBundle({ sessionDir, sessionName: target });
436
+ }
437
+
438
+ // Build the replay plan.
439
+ const opts = {
440
+ repoRoot: process.cwd(),
441
+ baseRef: flag('--base-ref'),
442
+ harness: flag('--harness'),
443
+ sessionName: flag('--session'),
444
+ agents: (flag('--agents') || '').split(',').map(s => s.trim()).filter(Boolean),
445
+ replaceExisting: has('--replace'),
446
+ };
447
+ if (opts.agents.length === 0) opts.agents = null;
448
+
449
+ const planConfig = replayLib.buildReplayPlanConfig(bundle, opts);
450
+ const plan = orchestrator.buildOrchestrationPlan(planConfig);
451
+
452
+ if (has('--json')) {
453
+ console.log(JSON.stringify({ planConfig, plan }, null, 2));
454
+ return;
455
+ }
456
+
457
+ if (has('--write-only')) {
458
+ orchestrator.materializePlan(plan);
459
+ console.log(`✓ replay coordination files written to ${plan.coordinationDir}`);
460
+ console.log(` origin session: ${bundle.session}`);
461
+ console.log(` replay session: ${plan.sessionName}`);
462
+ return;
463
+ }
464
+
465
+ if (!has('--execute')) {
466
+ console.log(`[replay] dry-run · origin=${bundle.session} → replay=${plan.sessionName}`);
467
+ console.log(` workers: ${plan.workerPlans.length}`);
468
+ console.log(` base ref: ${plan.baseRef}`);
469
+ for (const w of plan.workerPlans) {
470
+ console.log(` • ${w.workerName.padEnd(24)} → ${w.worktreePath}`);
471
+ }
472
+ console.log('\nRe-run with --execute to spawn worktrees + tmux session.');
473
+ console.log('Or --write-only to just create coordination files.');
474
+ return;
475
+ }
476
+
477
+ const result = orchestrator.executePlan(plan);
478
+ console.log(`✓ replay started: tmux session "${result.sessionName}"`);
479
+ console.log(` origin: ${bundle.session}`);
480
+ console.log(` attach: tmux attach -t ${result.sessionName}`);
481
+ console.log(` coord: ${result.coordinationDir}`);
482
+ return;
483
+ }
484
+ } catch (e) {
485
+ process.stderr.write(`[${sub}] ${e.message}\n`);
486
+ process.exit(1);
487
+ }
488
+ })();
489
+ return;
490
+ }
491
+
492
+ // ── Subcommand: sbom / manifest / verify ─────────────────────────────────────
493
+ // Usage:
494
+ // kodelyth-ecc sbom [--root DIR] [--out FILE] [--json]
495
+ // kodelyth-ecc manifest [--root DIR] [--out FILE] [--json]
496
+ // kodelyth-ecc verify [--root DIR] [--manifest FILE] [--json]
497
+ if (args[0] === 'sbom' || args[0] === 'manifest' || args[0] === 'verify') {
498
+ (async () => {
499
+ const fs = require('fs');
500
+ const sub = args[0];
501
+ const rest = args.slice(1);
502
+
503
+ function flag(name, dflt) {
504
+ const i = rest.indexOf(name);
505
+ return i >= 0 && rest[i + 1] ? rest[i + 1] : dflt;
506
+ }
507
+ function has(name) { return rest.includes(name); }
508
+
509
+ const rootDir = path.resolve(flag('--root', ROOT));
510
+
511
+ try {
512
+ if (sub === 'sbom') {
513
+ const { generateSBOM } = require(path.join(ROOT, 'scripts', 'supply-chain', 'sbom.js'));
514
+ const bom = generateSBOM({ rootDir });
515
+ const json = JSON.stringify(bom, null, 2);
516
+ const out = flag('--out');
517
+ if (out) {
518
+ fs.writeFileSync(out, json + '\n');
519
+ console.log(`✓ SBOM written: ${out}`);
520
+ console.log(` format: ${bom.bomFormat} ${bom.specVersion}`);
521
+ console.log(` package: ${bom.metadata.component.name}@${bom.metadata.component.version}`);
522
+ console.log(` components: ${bom.components.length}`);
523
+ } else if (has('--json')) {
524
+ process.stdout.write(json + '\n');
525
+ } else {
526
+ console.log(`${bom.bomFormat} ${bom.specVersion}`);
527
+ console.log(`package: ${bom.metadata.component.name}@${bom.metadata.component.version}`);
528
+ console.log(`components: ${bom.components.length}`);
529
+ console.log(`(use --out FILE or --json to emit the full document)`);
530
+ }
531
+ return;
532
+ }
533
+
534
+ if (sub === 'manifest') {
535
+ const { generateManifest } = require(path.join(ROOT, 'scripts', 'supply-chain', 'manifest.js'));
536
+ const m = generateManifest({ rootDir });
537
+ const json = JSON.stringify(m, null, 2);
538
+ const out = flag('--out');
539
+ if (out) {
540
+ fs.writeFileSync(out, json + '\n');
541
+ console.log(`✓ manifest written: ${out}`);
542
+ console.log(` package: ${m.package}@${m.pkg_version}`);
543
+ console.log(` files: ${m.file_count}`);
544
+ console.log(` digest: ${m.digest.slice(0, 16)}…`);
545
+ } else if (has('--json')) {
546
+ process.stdout.write(json + '\n');
547
+ } else {
548
+ console.log(`package: ${m.package}@${m.pkg_version}`);
549
+ console.log(`files: ${m.file_count}`);
550
+ console.log(`digest: ${m.digest}`);
551
+ console.log(`(use --out FILE or --json to emit the full document)`);
552
+ }
553
+ return;
554
+ }
555
+
556
+ if (sub === 'verify') {
557
+ const { verifyAgainstManifest } = require(path.join(ROOT, 'scripts', 'supply-chain', 'verify.js'));
558
+ const manifestPath = flag('--manifest', path.join(rootDir, 'manifest.json'));
559
+ if (!fs.existsSync(manifestPath)) {
560
+ throw new Error(`verify: manifest not found at ${manifestPath} (use --manifest FILE)`);
561
+ }
562
+ const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
563
+ const report = verifyAgainstManifest({ rootDir, manifest });
564
+
565
+ if (has('--json')) {
566
+ process.stdout.write(JSON.stringify(report, null, 2) + '\n');
567
+ process.exit(report.ok ? 0 : 1);
568
+ }
569
+
570
+ const s = report.summary;
571
+ console.log(`Kodelyth ECC supply-chain verify`);
572
+ console.log(` package: ${manifest.package}@${manifest.pkg_version}`);
573
+ console.log(` manifest digest: ${manifest.digest}`);
574
+ console.log(` files in manifest: ${s.total_in_manifest}`);
575
+ console.log(` ✓ ok: ${s.ok}`);
576
+ console.log(` ✗ modified: ${s.modified}`);
577
+ console.log(` ✗ missing: ${s.missing}`);
578
+ console.log(` ⚠ extra: ${s.extra} (advisory)`);
579
+
580
+ if (report.details.modified.length) {
581
+ console.log(`\nModified files:`);
582
+ for (const m of report.details.modified.slice(0, 20)) {
583
+ console.log(` • ${m.path}`);
584
+ }
585
+ if (report.details.modified.length > 20) {
586
+ console.log(` …and ${report.details.modified.length - 20} more (use --json for full list)`);
587
+ }
588
+ }
589
+ if (report.details.missing.length) {
590
+ console.log(`\nMissing files:`);
591
+ for (const m of report.details.missing.slice(0, 20)) {
592
+ console.log(` • ${m.path}`);
593
+ }
594
+ if (report.details.missing.length > 20) {
595
+ console.log(` …and ${report.details.missing.length - 20} more (use --json for full list)`);
596
+ }
597
+ }
598
+ console.log(report.ok ? `\n✓ verify OK` : `\n✗ verify FAILED`);
599
+ process.exit(report.ok ? 0 : 1);
600
+ }
601
+ } catch (e) {
602
+ process.stderr.write(`[${sub}] ${e.message}\n`);
603
+ process.exit(1);
604
+ }
605
+ })();
606
+ return;
607
+ }
608
+
609
+ // ── Subcommand: dashboard (local observability) ───────────────────────────────
610
+ // Usage:
611
+ // kodelyth-ecc dashboard [--port N] [--host 127.0.0.1] [--no-open]
612
+ if (args[0] === 'dashboard') {
613
+ (async () => {
614
+ const rest = args.slice(1);
615
+ function flag(name, dflt) {
616
+ const i = rest.indexOf(name);
617
+ return i >= 0 && rest[i + 1] ? rest[i + 1] : dflt;
618
+ }
619
+ function has(name) { return rest.includes(name); }
620
+ try {
621
+ const { start } = require(path.join(ROOT, 'scripts', 'dashboard', 'server.js'));
622
+ const port = Number(flag('--port', '5747'));
623
+ const host = flag('--host', '127.0.0.1');
624
+ const openBrowser = !has('--no-open');
625
+ await start({ port, host, openBrowser });
626
+ // Keep process alive — http server is already listening.
627
+ } catch (e) {
628
+ process.stderr.write(`[dashboard] ${e.message}\n`);
629
+ process.exit(1);
630
+ }
631
+ })();
632
+ return;
633
+ }
634
+
635
+ // ── Subcommand: evolve (Phase 3.4 — self-evolving memory) ────────────────────
636
+ // Usage:
637
+ // kodelyth-ecc evolve analyze [--json] [--reuse-min N] [--miss-min N]
638
+ // kodelyth-ecc evolve list [--status STATUS] [--json]
639
+ // kodelyth-ecc evolve show <proposalId> [--json]
640
+ // kodelyth-ecc evolve accept <proposalId> [--root DIR] [--overwrite]
641
+ // kodelyth-ecc evolve reject <proposalId> [--note "..."]
642
+ // kodelyth-ecc evolve stats [--json]
643
+ if (args[0] === 'evolve') {
644
+ (async () => {
645
+ const fs = require('fs');
646
+ const sub = args[1] || '';
647
+ const rest = args.slice(2);
648
+
649
+ function flag(name, dflt) {
650
+ const i = rest.indexOf(name);
651
+ return i >= 0 && rest[i + 1] ? rest[i + 1] : dflt;
652
+ }
653
+ function has(name) { return rest.includes(name); }
654
+ function positional() { return rest.find(a => !a.startsWith('--')); }
655
+
656
+ try {
657
+ const statsLib = require(path.join(ROOT, 'scripts', 'evolve', 'stats.js'));
658
+ const analyzeLib = require(path.join(ROOT, 'scripts', 'evolve', 'analyze.js'));
659
+ const proposalsLib = require(path.join(ROOT, 'scripts', 'evolve', 'proposals.js'));
660
+ const memoryStore = require(path.join(ROOT, 'scripts', 'memory', 'store.js'));
661
+
662
+ // ── stats ────────────────────────────────────────────────────────────
663
+ if (sub === 'stats') {
664
+ const reuse = statsLib.getReuseStats();
665
+ const miss = statsLib.getRoutingMissStats();
666
+ if (has('--json')) {
667
+ process.stdout.write(JSON.stringify({ reuse, miss }, null, 2) + '\n');
668
+ return;
669
+ }
670
+ console.log('Kodelyth ECC — self-evolving memory stats');
671
+ console.log(` reuse:`);
672
+ console.log(` memories tracked: ${reuse.total_memories_tracked}`);
673
+ console.log(` total surfaces: ${reuse.total_surfaces}`);
674
+ console.log(` last updated: ${reuse.last_updated || '(none)'}`);
675
+ if (reuse.entries.length) {
676
+ console.log(` top reused:`);
677
+ for (const e of reuse.entries.slice(0, 5)) {
678
+ console.log(` • ${e.id} count=${e.count} sessions=${(e.sessions||[]).length}`);
679
+ }
680
+ }
681
+ console.log(` routing misses:`);
682
+ console.log(` total: ${miss.total_misses}`);
683
+ console.log(` unique prompts: ${miss.unique_prompts}`);
684
+ if (miss.entries.length) {
685
+ console.log(` top clusters:`);
686
+ for (const e of miss.entries.slice(0, 5)) {
687
+ console.log(` • count=${e.count} tokens=[${(e.tokens||[]).slice(0,4).join(', ')}]`);
688
+ }
689
+ }
690
+ return;
691
+ }
692
+
693
+ // ── analyze ──────────────────────────────────────────────────────────
694
+ if (sub === 'analyze') {
695
+ const reuseStats = statsLib.getReuseStats();
696
+ const missStats = statsLib.getRoutingMissStats();
697
+ const memories = memoryStore.listAll();
698
+ const thresholds = {
699
+ reuseMinCount: Number(flag('--reuse-min')) || undefined,
700
+ reuseMinSessions: Number(flag('--reuse-min-sessions')) || undefined,
701
+ missMinCount: Number(flag('--miss-min')) || undefined,
702
+ missMinClusterDistinct: Number(flag('--miss-min-distinct')) || undefined,
703
+ };
704
+ const fresh = analyzeLib.analyzeAll({ reuseStats, missStats, memories, thresholds });
705
+
706
+ let appended = 0;
707
+ for (const p of fresh) {
708
+ const before = proposalsLib.findById(p.id);
709
+ proposalsLib.appendProposal(p);
710
+ if (!before) appended += 1;
711
+ }
712
+
713
+ if (has('--json')) {
714
+ process.stdout.write(JSON.stringify({
715
+ scanned: { reuse_entries: reuseStats.entries.length, miss_entries: missStats.entries.length },
716
+ generated: fresh.length,
717
+ new: appended,
718
+ proposals: fresh,
719
+ }, null, 2) + '\n');
720
+ return;
721
+ }
722
+
723
+ console.log(`✓ analyzed signals`);
724
+ console.log(` reuse entries scanned: ${reuseStats.entries.length}`);
725
+ console.log(` miss entries scanned: ${missStats.entries.length}`);
726
+ console.log(` proposals generated: ${fresh.length}`);
727
+ console.log(` new proposals (added): ${appended}`);
728
+ if (fresh.length === 0) {
729
+ console.log(`\n No proposals yet. Either signals are below thresholds or there's nothing to propose.`);
730
+ console.log(` Defaults: reuse-min=${analyzeLib.DEFAULT_REUSE_MIN_COUNT} reuse-min-sessions=${analyzeLib.DEFAULT_REUSE_MIN_SESSIONS} miss-min=${analyzeLib.DEFAULT_MISS_MIN_COUNT}`);
731
+ } else {
732
+ console.log(`\n Run 'kodelyth-ecc evolve list' to review.`);
733
+ }
734
+ return;
735
+ }
736
+
737
+ // ── list ─────────────────────────────────────────────────────────────
738
+ if (sub === 'list') {
739
+ const wantStatus = flag('--status');
740
+ const proposals = proposalsLib.listByStatus(wantStatus || null);
741
+ if (has('--json')) {
742
+ process.stdout.write(JSON.stringify(proposals, null, 2) + '\n');
743
+ return;
744
+ }
745
+ if (proposals.length === 0) {
746
+ console.log(`No proposals${wantStatus ? ` with status="${wantStatus}"` : ''}. Try 'kodelyth-ecc evolve analyze' first.`);
747
+ return;
748
+ }
749
+ console.log(`Kodelyth ECC — self-evolving memory proposals (${proposals.length})`);
750
+ for (const p of proposals) {
751
+ const tag = p.status === 'pending' ? '⏸' : p.status === 'accepted' ? '✓' : p.status === 'rejected' ? '✗' : p.status === 'applied' ? '★' : '?';
752
+ console.log(` ${tag} [${p.status}] ${p.id} ${p.type}`);
753
+ console.log(` target: ${p.proposal?.target_path || '(none)'}`);
754
+ console.log(` why: ${p.proposal?.rationale || '(none)'}`);
755
+ }
756
+ return;
757
+ }
758
+
759
+ // ── show ─────────────────────────────────────────────────────────────
760
+ if (sub === 'show') {
761
+ const id = positional();
762
+ if (!id) throw new Error('show: <proposalId> is required');
763
+ const p = proposalsLib.findById(id);
764
+ if (!p) throw new Error(`show: proposal "${id}" not found`);
765
+ if (has('--json')) {
766
+ process.stdout.write(JSON.stringify(p, null, 2) + '\n');
767
+ return;
768
+ }
769
+ console.log(`Proposal ${p.id} (${p.type}, status=${p.status})`);
770
+ console.log(` target_path: ${p.proposal?.target_path}`);
771
+ console.log(` rationale: ${p.proposal?.rationale}`);
772
+ console.log(`\n--- proposed file content ---\n`);
773
+ console.log(p.proposal?.diff || '(no diff)');
774
+ console.log(`\n--- evidence ---\n`);
775
+ console.log(JSON.stringify(p.evidence, null, 2));
776
+ return;
777
+ }
778
+
779
+ // ── accept ───────────────────────────────────────────────────────────
780
+ if (sub === 'accept') {
781
+ const id = positional();
782
+ if (!id) throw new Error('accept: <proposalId> is required');
783
+ const p = proposalsLib.findById(id);
784
+ if (!p) throw new Error(`accept: proposal "${id}" not found`);
785
+ if (p.status === 'accepted' || p.status === 'applied') {
786
+ console.log(`Proposal ${id} is already ${p.status}.`);
787
+ if (p.applied_path) console.log(` draft at: ${p.applied_path}`);
788
+ return;
789
+ }
790
+
791
+ const repoRoot = path.resolve(flag('--root', ROOT));
792
+ const overwrite = has('--overwrite');
793
+ const result = proposalsLib.applyProposalToDisk(p, { repoRoot, overwrite });
794
+ proposalsLib.setStatus(id, 'accepted', undefined, { applied_path: result.path });
795
+
796
+ console.log(`✓ accepted ${id}`);
797
+ console.log(` draft written: ${result.path}`);
798
+ console.log(`\n Review the draft. When you're happy with it, commit it.`);
799
+ console.log(` Then mark it applied: kodelyth-ecc evolve list --status applied`);
800
+ console.log(` (Evolve NEVER auto-commits proposals.)`);
801
+ return;
802
+ }
803
+
804
+ // ── reject ───────────────────────────────────────────────────────────
805
+ if (sub === 'reject') {
806
+ const id = positional();
807
+ if (!id) throw new Error('reject: <proposalId> is required');
808
+ const p = proposalsLib.findById(id);
809
+ if (!p) throw new Error(`reject: proposal "${id}" not found`);
810
+ const note = flag('--note');
811
+ proposalsLib.setStatus(id, 'rejected', undefined, note ? { note } : {});
812
+ console.log(`✗ rejected ${id}${note ? ` (note: ${note})` : ''}`);
813
+ return;
814
+ }
815
+
816
+ throw new Error(
817
+ `unknown evolve subcommand "${sub}". ` +
818
+ `Valid: analyze, list, show, accept, reject, stats.`
819
+ );
820
+ } catch (e) {
821
+ process.stderr.write(`[evolve] ${e.message}\n`);
822
+ process.exit(1);
823
+ }
824
+ })();
825
+ return;
826
+ }
827
+
31
828
  // ── Help shortcut ─────────────────────────────────────────────────────────────
32
829
  if (args.includes('--help') || args.includes('-h')) {
33
830
  console.log(`
34
831
  Kodelyth ECC — AI Coding Toolkit installer
35
832
 
36
833
  Usage:
37
- npx kodelyth-ecc [--target TARGET] [--profile PROFILE] [languages...]
834
+ npx kodelyth-ecc [--target TARGET] [--bundle BUNDLE] [--profile PROFILE] [languages...]
835
+ npx kodelyth-ecc mcp Start the Kodelyth MCP server over stdio
836
+ npx kodelyth-ecc mcp-add <name> -- <cmd> Register an external MCP server
837
+ npx kodelyth-ecc mcp-list List registered MCP servers
838
+ npx kodelyth-ecc mcp-tools <name> List tools on a registered server
839
+ npx kodelyth-ecc mcp-call <name> <tool> Call a tool on a registered server
840
+ npx kodelyth-ecc route "<task>" Cost-aware model-tier recommendation for a task
841
+ npx kodelyth-ecc swarm --task "<task>" Parallel agent execution in tmux + git worktrees
842
+ npx kodelyth-ecc session-export <session> Export a coordination dir as a portable bundle
843
+ npx kodelyth-ecc replay <bundle|session> Re-run a session with optional variations
844
+ npx kodelyth-ecc sbom [--out file.json] Emit a CycloneDX 1.5 SBOM for the package
845
+ npx kodelyth-ecc manifest [--out file.json] Emit a sha256 content manifest of all assets
846
+ npx kodelyth-ecc verify [--manifest file] Verify installed copy against a content manifest
847
+ npx kodelyth-ecc evolve analyze Generate skill / routing proposals from memory + miss signals
848
+ npx kodelyth-ecc dashboard Boot localhost-only observability dashboard (zero telemetry)
849
+
850
+ Subcommands:
851
+ mcp Start the Kodelyth MCP server over stdio (universal adapter for
852
+ Claude Desktop, LangGraph, AutoGen, CrewAI, OpenAI Agents SDK, etc.).
853
+ See docs/mcp.md for client config snippets.
854
+ mcp-add <name> Register an external MCP server (Stripe, GitHub, Postgres, Redis…).
855
+ Usage: kodelyth-ecc mcp-add github -- npx -y @modelcontextprotocol/server-github
856
+ Or with env vars: kodelyth-ecc mcp-add postgres --env DB_URL=postgres://...
857
+ -- npx -y @modelcontextprotocol/server-postgres
858
+ mcp-list List all registered external MCP servers.
859
+ mcp-remove <name> Unregister an external MCP server.
860
+ mcp-tools <name> List tools exposed by a registered server.
861
+ mcp-resources <name> List resources exposed by a registered server.
862
+ mcp-prompts <name> List prompts exposed by a registered server.
863
+ mcp-call <name> <tool> [--json '{"arg":"value"}']
864
+ Call a tool on a registered server. See docs/mcp-clients.md.
865
+ route Recommend trivial/standard/hard model tier for a task. Reads
866
+ .kodelyth/router.json and KODELYTH_ROUTER_* env vars. Disable with
867
+ KODELYTH_ROUTER=off. Use --json for machine-readable output.
868
+ swarm Run N specialist agents in parallel inside isolated git worktrees +
869
+ a tmux session. Auto-picks agents from --task signals or accepts
870
+ --agents name1,name2,... Smart defaults for 4/6/8-agent rotations.
871
+ Default is dry-run; pass --execute to actually spawn. See docs/swarm.md.
872
+ session-export Export a coordination dir as a portable bundle JSON (task + handoffs
873
+ + status). Optional --task / --agents / --harness / --base-ref to
874
+ enrich the bundle's meta for cleaner replay. See docs/replay.md.
875
+ session-import Restore a bundle into a coordination dir (--target dir, --overwrite).
876
+ replay Re-run a session or bundle. Accepts --harness / --agents / --base-ref
877
+ / --session for A/B variations. Default dry-run; --execute to spawn.
878
+ sbom Generate a CycloneDX 1.5 SBOM from package.json + package-lock.json.
879
+ Use --out FILE to write, --json to stream. See docs/supply-chain.md.
880
+ manifest Generate a sha256 content manifest of every shipped asset (agents,
881
+ skills, commands, rules, hooks, scripts, bin, bundles, root files).
882
+ Output is deterministic and diff-friendly.
883
+ verify Verify an installed copy against a manifest. Reports ok/modified/
884
+ missing/extra. Exits non-zero on any modified or missing file.
885
+ dashboard Local observability dashboard. Boots a localhost-only HTTP
886
+ server (default 127.0.0.1:5747) showing memory, evolve signals,
887
+ catalog, sessions, token-budget snapshot. Auto-opens browser unless
888
+ --no-open. Refuses to bind non-localhost without
889
+ KODELYTH_DASHBOARD_ALLOW_REMOTE=1. Zero telemetry. See docs/dashboard.md.
890
+ evolve Self-evolving memory. Subactions:
891
+ analyze Convert reuse + routing-miss signals into proposals
892
+ list Show pending / accepted / rejected proposals
893
+ show ID Print full proposed file content + evidence
894
+ accept ID Write the draft to disk (NEVER auto-commits)
895
+ reject ID Mark a proposal rejected
896
+ stats Snapshot of recorded signals
897
+ See docs/evolve.md.
38
898
 
39
899
  Targets:
40
900
  claude-home Claude Code — global install (default)
@@ -44,10 +904,24 @@ if (args.includes('--help') || args.includes('-h')) {
44
904
  cursor-project Cursor IDE — project install (.cursor/)
45
905
  codex-home Codex CLI — global install (~/.codex/)
46
906
  opencode OpenCode — project install (.opencode/)
907
+ cline Cline (VS Code) — project install (.clinerules/)
908
+ roocode Roo Code (VS Code) — project install (.roo/)
909
+ aider Aider terminal agent — project install (.aider-ecc/ + CONVENTIONS.md)
910
+ kimi Kimi Code — project install (.kimi/)
911
+ gemini-project Gemini CLI — project install (.gemini/)
912
+ gemini-home Gemini CLI — global install (~/.gemini/)
913
+
914
+ Bundles (audience-tailored — installs everything + curated cheat sheet):
915
+ --bundle indie-hacker Solo founders / SaaS — ship fast, validate, harden
916
+ --bundle red-team Security engineers — devil-mode crew + adversarial workflows
917
+ --bundle enterprise Compliance / audit teams — SBOM, license, supply chain
47
918
 
48
919
  Examples:
49
920
  npx kodelyth-ecc
50
921
  npx kodelyth-ecc --target windsurf-project
922
+ npx kodelyth-ecc --bundle indie-hacker
923
+ npx kodelyth-ecc --bundle red-team --target windsurf-project
924
+ npx kodelyth-ecc --bundle enterprise
51
925
  npx kodelyth-ecc --target antigravity --profile nextjs
52
926
  npx kodelyth-ecc --target claude-home typescript python
53
927