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,191 @@
1
+ // =============================================================================
2
+ // Kodelyth ECC — Session Bundle (Phase 2.8)
3
+ //
4
+ // Pure read/write of portable session bundles. A bundle is a single JSON file
5
+ // that captures everything in a coordination directory:
6
+ //
7
+ // .orchestration/<session>/ ← input
8
+ // <worker-slug>/
9
+ // task.md
10
+ // handoff.md
11
+ // status.md
12
+ //
13
+ // Bundle format (v1):
14
+ // {
15
+ // "schema": "kodelyth.session-bundle/v1",
16
+ // "session": "swarm-2026-05-10-4a",
17
+ // "exported_at": "2026-05-10T17:30:00Z",
18
+ // "exported_by": "kodelyth-ecc@1.7.0",
19
+ // "meta": { ← optional, for replay variations
20
+ // "task": "audit oauth flow",
21
+ // "agents": ["security-reviewer", "code-reviewer", ...],
22
+ // "harness": "claude",
23
+ // "base_ref": "HEAD",
24
+ // "repo_root": "/path/to/repo"
25
+ // },
26
+ // "workers": [
27
+ // { "slug": "security-reviewer", "task": "...", "handoff": "...", "status": "..." }
28
+ // ]
29
+ // }
30
+ //
31
+ // Pure functions, zero deps. Tested without spawning anything.
32
+ // =============================================================================
33
+
34
+ 'use strict';
35
+
36
+ const fs = require('fs');
37
+ const path = require('path');
38
+
39
+ const BUNDLE_SCHEMA = 'kodelyth.session-bundle/v1';
40
+ const BUNDLE_VERSION = '1.7.0';
41
+
42
+ // ── Read a coordination dir into a bundle ────────────────────────────────────
43
+ function exportBundle({ sessionDir, sessionName, meta = {} }) {
44
+ if (!sessionDir || typeof sessionDir !== 'string') {
45
+ throw new Error('exportBundle: sessionDir is required');
46
+ }
47
+ if (!fs.existsSync(sessionDir)) {
48
+ throw new Error(`exportBundle: session directory not found: ${sessionDir}`);
49
+ }
50
+ const session = sessionName || path.basename(sessionDir);
51
+
52
+ const entries = fs.readdirSync(sessionDir, { withFileTypes: true });
53
+ const workers = [];
54
+ for (const entry of entries) {
55
+ if (!entry.isDirectory()) continue;
56
+ const slug = entry.name;
57
+ const dir = path.join(sessionDir, slug);
58
+ workers.push({
59
+ slug,
60
+ task: safeRead(path.join(dir, 'task.md')),
61
+ handoff: safeRead(path.join(dir, 'handoff.md')),
62
+ status: safeRead(path.join(dir, 'status.md')),
63
+ });
64
+ }
65
+
66
+ workers.sort((a, b) => a.slug.localeCompare(b.slug));
67
+
68
+ return {
69
+ schema: BUNDLE_SCHEMA,
70
+ session,
71
+ exported_at: new Date().toISOString(),
72
+ exported_by: `kodelyth-ecc@${BUNDLE_VERSION}`,
73
+ meta: { ...meta },
74
+ workers,
75
+ };
76
+ }
77
+
78
+ // ── Write a bundle JSON to disk ──────────────────────────────────────────────
79
+ function writeBundle(bundle, outPath) {
80
+ if (!bundle || bundle.schema !== BUNDLE_SCHEMA) {
81
+ throw new Error(`writeBundle: invalid schema (${bundle && bundle.schema})`);
82
+ }
83
+ fs.mkdirSync(path.dirname(path.resolve(outPath)), { recursive: true });
84
+ fs.writeFileSync(outPath, JSON.stringify(bundle, null, 2) + '\n', 'utf8');
85
+ return outPath;
86
+ }
87
+
88
+ // ── Read + validate a bundle file ────────────────────────────────────────────
89
+ function readBundle(bundlePath) {
90
+ if (!fs.existsSync(bundlePath)) {
91
+ throw new Error(`readBundle: file not found: ${bundlePath}`);
92
+ }
93
+ const raw = JSON.parse(fs.readFileSync(bundlePath, 'utf8'));
94
+ return validateBundle(raw);
95
+ }
96
+
97
+ function validateBundle(raw) {
98
+ if (!raw || typeof raw !== 'object') throw new Error('bundle: not an object');
99
+ if (raw.schema !== BUNDLE_SCHEMA) {
100
+ throw new Error(`bundle: unsupported schema "${raw.schema}" (expected "${BUNDLE_SCHEMA}")`);
101
+ }
102
+ if (typeof raw.session !== 'string' || !raw.session) {
103
+ throw new Error('bundle: missing "session"');
104
+ }
105
+ if (!Array.isArray(raw.workers) || raw.workers.length === 0) {
106
+ throw new Error('bundle: "workers" must be a non-empty array');
107
+ }
108
+ for (const w of raw.workers) {
109
+ if (!w || typeof w.slug !== 'string') throw new Error('bundle: each worker needs a string "slug"');
110
+ }
111
+ return raw;
112
+ }
113
+
114
+ // ── Restore a bundle into a coordination dir ─────────────────────────────────
115
+ function importBundle(bundle, { targetDir, overwrite = false } = {}) {
116
+ validateBundle(bundle);
117
+ if (!targetDir) throw new Error('importBundle: targetDir is required');
118
+ const dir = path.resolve(targetDir);
119
+ if (fs.existsSync(dir)) {
120
+ if (!overwrite) throw new Error(`importBundle: targetDir already exists: ${dir} (pass overwrite=true to replace)`);
121
+ fs.rmSync(dir, { recursive: true, force: true });
122
+ }
123
+ fs.mkdirSync(dir, { recursive: true });
124
+ for (const w of bundle.workers) {
125
+ const wdir = path.join(dir, w.slug);
126
+ fs.mkdirSync(wdir, { recursive: true });
127
+ if (typeof w.task === 'string') fs.writeFileSync(path.join(wdir, 'task.md'), w.task, 'utf8');
128
+ if (typeof w.handoff === 'string') fs.writeFileSync(path.join(wdir, 'handoff.md'), w.handoff, 'utf8');
129
+ if (typeof w.status === 'string') fs.writeFileSync(path.join(wdir, 'status.md'), w.status, 'utf8');
130
+ }
131
+ return { targetDir: dir, workers: bundle.workers.map(w => w.slug) };
132
+ }
133
+
134
+ // ── Diff: compare two bundles' handoffs (for A/B replay analysis) ────────────
135
+ function diffBundles(a, b) {
136
+ validateBundle(a);
137
+ validateBundle(b);
138
+
139
+ const aBySlug = new Map(a.workers.map(w => [w.slug, w]));
140
+ const bBySlug = new Map(b.workers.map(w => [w.slug, w]));
141
+ const allSlugs = new Set([...aBySlug.keys(), ...bBySlug.keys()]);
142
+
143
+ const out = [];
144
+ for (const slug of [...allSlugs].sort()) {
145
+ const A = aBySlug.get(slug);
146
+ const B = bBySlug.get(slug);
147
+ out.push({
148
+ slug,
149
+ in_a: !!A,
150
+ in_b: !!B,
151
+ task_changed: !!A && !!B && (A.task || '') !== (B.task || ''),
152
+ handoff_changed: !!A && !!B && (A.handoff || '') !== (B.handoff || ''),
153
+ status_changed: !!A && !!B && (A.status || '') !== (B.status || ''),
154
+ a_handoff_len: A ? (A.handoff || '').length : 0,
155
+ b_handoff_len: B ? (B.handoff || '').length : 0,
156
+ });
157
+ }
158
+ return out;
159
+ }
160
+
161
+ // ── Helper ───────────────────────────────────────────────────────────────────
162
+ function safeRead(p) {
163
+ try { return fs.readFileSync(p, 'utf8'); }
164
+ catch { return ''; }
165
+ }
166
+
167
+ // ── Compute the next replay session name ─────────────────────────────────────
168
+ function nextReplayName(originalSession, takenSet = new Set()) {
169
+ if (typeof originalSession !== 'string' || !originalSession) {
170
+ throw new Error('nextReplayName: originalSession is required');
171
+ }
172
+ // Strip any existing -replay-N suffix to get the canonical base.
173
+ const base = originalSession.replace(/-replay-\d+$/, '');
174
+ for (let n = 1; n < 1000; n++) {
175
+ const candidate = `${base}-replay-${n}`;
176
+ if (!takenSet.has(candidate)) return candidate;
177
+ }
178
+ throw new Error('nextReplayName: exhausted replay numbers');
179
+ }
180
+
181
+ module.exports = {
182
+ BUNDLE_SCHEMA,
183
+ BUNDLE_VERSION,
184
+ exportBundle,
185
+ writeBundle,
186
+ readBundle,
187
+ validateBundle,
188
+ importBundle,
189
+ diffBundles,
190
+ nextReplayName,
191
+ };
@@ -0,0 +1,107 @@
1
+ // =============================================================================
2
+ // Kodelyth ECC — Session Replay Engine (Phase 2.8)
3
+ //
4
+ // Takes a bundle (or a live coordination dir) and reconstructs a swarm plan-
5
+ // config that re-runs the same task. Supports replay variations:
6
+ //
7
+ // - Different harness (--harness codex) — model A/B test
8
+ // - Different agents (--agents new1,new2) — agent A/B test
9
+ // - Different baseRef (--base-ref main) — re-test against new code
10
+ // - Different model via cost router env vars — model A/B test
11
+ //
12
+ // Pure planning. The actual execution is delegated to the existing
13
+ // orchestrator (scripts/lib/tmux-worktree-orchestrator.js).
14
+ // =============================================================================
15
+
16
+ 'use strict';
17
+
18
+ const path = require('path');
19
+
20
+ const { buildSwarmPlan } = require('../swarm/build-plan.js');
21
+ const { nextReplayName } = require('./bundle.js');
22
+
23
+ // ── Extract a replay-able task from a bundle ─────────────────────────────────
24
+ function extractTaskFromBundle(bundle) {
25
+ // Prefer explicit meta.task (set when exporting from a swarm).
26
+ if (bundle.meta && typeof bundle.meta.task === 'string' && bundle.meta.task.trim()) {
27
+ return bundle.meta.task.trim();
28
+ }
29
+ // Fallback: read the FIRST worker's task.md and strip the agent-shaped header
30
+ // we added in scripts/swarm/build-plan.js (shapeTaskForAgent).
31
+ const first = bundle.workers && bundle.workers[0];
32
+ if (first && typeof first.task === 'string') {
33
+ return extractSharedTaskFromShapedPrompt(first.task);
34
+ }
35
+ throw new Error('replay: cannot extract task — bundle has no meta.task and no worker tasks');
36
+ }
37
+
38
+ function extractSharedTaskFromShapedPrompt(prompt) {
39
+ // The shaped format is:
40
+ // # <agent> — swarm task
41
+ // ...
42
+ // ## Shared Task
43
+ //
44
+ // <THE TASK>
45
+ //
46
+ // ## Required handoff sections
47
+ // ...
48
+ const m = prompt.match(/##\s*Shared Task\s*\n([\s\S]*?)\n##\s*Required handoff sections/);
49
+ if (m) return m[1].trim();
50
+ // Tmux orchestrator's own task.md has "## Objective\n<task>\n## Completion".
51
+ const m2 = prompt.match(/##\s*Objective\s*\n([\s\S]*?)\n##\s*Completion/);
52
+ if (m2) return m2[1].trim();
53
+ // Last-resort: return the whole prompt.
54
+ return prompt.trim();
55
+ }
56
+
57
+ // ── Build a replay plan-config ───────────────────────────────────────────────
58
+ function buildReplayPlanConfig(bundle, opts = {}) {
59
+ const {
60
+ repoRoot = process.cwd(),
61
+ coordinationRoot,
62
+ worktreeRoot,
63
+ baseRef,
64
+ harness,
65
+ agents, // array | null — overrides bundle's agent list
66
+ sessionName, // explicit override; otherwise auto next-replay-N
67
+ takenSessions = new Set(),
68
+ replaceExisting = false,
69
+ seedPaths = [],
70
+ launcherCmd,
71
+ } = opts;
72
+
73
+ const task = extractTaskFromBundle(bundle);
74
+
75
+ // Resolve which agents to run.
76
+ let agentList;
77
+ if (Array.isArray(agents) && agents.length > 0) {
78
+ agentList = agents.slice();
79
+ } else if (bundle.meta && Array.isArray(bundle.meta.agents) && bundle.meta.agents.length > 0) {
80
+ agentList = bundle.meta.agents.slice();
81
+ } else {
82
+ agentList = bundle.workers.map(w => w.slug);
83
+ }
84
+
85
+ // Resolve session name.
86
+ const session = sessionName || nextReplayName(bundle.session, takenSessions);
87
+
88
+ return buildSwarmPlan({
89
+ task,
90
+ agents: agentList,
91
+ harness: harness || (bundle.meta && bundle.meta.harness) || 'claude',
92
+ launcherCmd,
93
+ sessionName: session,
94
+ repoRoot,
95
+ worktreeRoot,
96
+ coordinationRoot,
97
+ baseRef: baseRef || (bundle.meta && bundle.meta.base_ref) || 'HEAD',
98
+ seedPaths,
99
+ replaceExisting,
100
+ });
101
+ }
102
+
103
+ module.exports = {
104
+ extractTaskFromBundle,
105
+ extractSharedTaskFromShapedPrompt,
106
+ buildReplayPlanConfig,
107
+ };
@@ -0,0 +1,232 @@
1
+ // =============================================================================
2
+ // Kodelyth ECC — Cost-Aware Model Router (Classifier + Config Loader)
3
+ //
4
+ // Pure-function task classifier. Given a task description and optional
5
+ // signals (file count, agent in scope, security flags, etc.), returns a
6
+ // recommended tier ("trivial" | "standard" | "hard") plus the top reasons.
7
+ //
8
+ // Used by:
9
+ // - npx kodelyth-ecc route <task...> ad-hoc CLI
10
+ // - skills/cost-aware-model-routing explicit invocation
11
+ // - any future hook that wants a hint
12
+ //
13
+ // Pure JS, zero deps, deterministic.
14
+ // =============================================================================
15
+
16
+ 'use strict';
17
+
18
+ const fs = require('fs');
19
+ const os = require('os');
20
+ const path = require('path');
21
+
22
+ // ── Defaults (provider-agnostic) ─────────────────────────────────────────────
23
+ const DEFAULT_MODELS = {
24
+ trivial: 'claude-haiku-4-5-20251001',
25
+ standard: 'claude-sonnet-4-6',
26
+ hard: 'claude-opus-4-1',
27
+ default: 'standard',
28
+ };
29
+
30
+ // Agents that should NEVER downgrade — security / incident / adversarial work.
31
+ const HARD_TIER_AGENTS = new Set([
32
+ 'security-reviewer', 'incident-commander', 'debug-detective',
33
+ 'prompt-injection-hunter', 'supply-chain-auditor', 'secret-hunter',
34
+ 'backdoor-hunter', 'chaos-engineer', 'jailbreak-tester',
35
+ 'code-stealer-detector', 'license-violation-finder',
36
+ 'code-architect', 'architect',
37
+ ]);
38
+
39
+ // ── Signal catalogues ────────────────────────────────────────────────────────
40
+ const TRIVIAL_SIGNALS = [
41
+ { id: 'rename', regex: /\b(rename|renaming)\b/i },
42
+ { id: 'format', regex: /\b(format|formatting|prettier|biome|reformat)\b/i },
43
+ { id: 'typo', regex: /\b(typo|misspell|spelling)\b/i },
44
+ { id: 'doc-comment', regex: /\b(jsdoc|tsdoc|docstring|add comments?|add docs?)\b/i },
45
+ { id: 'capitalize', regex: /\b(capitali[sz]e|lowercase|uppercase|kebab-case|snake_case|camelCase)\b/i },
46
+ { id: 'list-files', regex: /\b(list|count|enumerate)\s+(files?|imports?|exports?|functions?)\b/i },
47
+ { id: 'summarize', regex: /\b(summari[sz]e|tl;?dr|short summary)\b/i },
48
+ { id: 'version-bump',regex: /\b(version\s+bump|bump\s+(version|deps?))\b/i },
49
+ { id: 'simple-regex',regex: /\b(simple\s+regex|one[\s-]liner|small\s+regex)\b/i },
50
+ ];
51
+
52
+ const HARD_SIGNALS = [
53
+ { id: 'production', regex: /\b(production|prod|live|outage|incident|down|leaking|on[\s-]?call|p[01]|sev[12])\b/i, weight: 3 },
54
+ { id: 'security', regex: /\b(security|vuln(?:erability)?|cve|exploit|injection|auth(?:n|z)?\s+(?:bypass|flaw)|secrets?\s+(?:leak|exposure))\b/i, weight: 3 },
55
+ { id: 'architecture',regex: /\b(architect(?:ure|ural)?\s+(?:decision|design|review|trade[\s-]?off)|design\s+review|adr|RFC)\b/i, weight: 2 },
56
+ { id: 'multi-file', regex: /\b(across\s+(?:the\s+)?(?:codebase|repo|project)|every(?:where)?|all\s+(?:files|modules|services))\b/i, weight: 2 },
57
+ { id: 'concurrency', regex: /\b(race\s+condition|deadlock|concurrency|thread[\s-]safe|atomic|mutex|lock)\b/i, weight: 2 },
58
+ { id: 'urgent', regex: /\b(urgent|asap|right\s+now|emergency|critical(?:al)?ly|fire|burning)\b/i, weight: 2 },
59
+ { id: 'devil-mode', regex: /\b(\/devil-mode|devil[\s-]?mode|red[\s-]?team|adversarial)\b/i, weight: 3 },
60
+ { id: 'incident', regex: /\bincident[\s-]?(commander|response|triage|postmortem|post[\s-]?mortem)\b/i, weight: 3 },
61
+ ];
62
+
63
+ const STANDARD_SIGNALS = [
64
+ { id: 'review-pr', regex: /\b(review\s+(?:this\s+)?pr|code\s+review|pr\s+review)\b/i },
65
+ { id: 'write-test', regex: /\b(write\s+(?:a\s+)?(?:unit\s+)?tests?|add\s+tests?)\b/i },
66
+ { id: 'fix-bug', regex: /\b(fix\s+(?:this\s+)?bug|debug\s+this|why\s+is\s+this\s+failing)\b/i },
67
+ { id: 'refactor', regex: /\brefactor(?:ing)?\b/i },
68
+ { id: 'optimize', regex: /\b(optimi[sz]e|speed\s+up|improve\s+performance)\b/i },
69
+ { id: 'doc-section', regex: /\b(write\s+docs?|document(?:ation)?\s+for)\b/i },
70
+ ];
71
+
72
+ // ── Token utilities ──────────────────────────────────────────────────────────
73
+ function chars(s) { return typeof s === 'string' ? s.length : 0; }
74
+
75
+ // ── Main classifier ──────────────────────────────────────────────────────────
76
+ function classify(task, opts = {}) {
77
+ if (!task || typeof task !== 'string') {
78
+ return { tier: 'standard', reasons: ['no task text — defaulting to standard'], score: { trivial: 0, standard: 0, hard: 0 } };
79
+ }
80
+ const t = task.trim();
81
+ const len = chars(t);
82
+
83
+ const fileCount = Number(opts.file_count) || 0;
84
+ const activeAgent = String(opts.active_agent || '').trim();
85
+ const sessionTokens = Number(opts.session_tokens) || 0;
86
+ const budgetTokens = Number(opts.budget_tokens) || 0;
87
+ const explicitTier = opts.explicit_tier; // if user set --model X, we respect it.
88
+
89
+ const score = { trivial: 0, standard: 0, hard: 0 };
90
+ const reasons = [];
91
+
92
+ // ── HARD: agent in scope ────────────────────────────────────────────────────
93
+ if (activeAgent && HARD_TIER_AGENTS.has(activeAgent)) {
94
+ score.hard += 4;
95
+ reasons.push(`active agent "${activeAgent}" never downgrades`);
96
+ }
97
+
98
+ // ── HARD signals ────────────────────────────────────────────────────────────
99
+ for (const s of HARD_SIGNALS) {
100
+ if (s.regex.test(t)) {
101
+ score.hard += s.weight;
102
+ reasons.push(`hard:${s.id}`);
103
+ }
104
+ }
105
+
106
+ // ── STANDARD signals ────────────────────────────────────────────────────────
107
+ for (const s of STANDARD_SIGNALS) {
108
+ if (s.regex.test(t)) {
109
+ score.standard += 1;
110
+ reasons.push(`standard:${s.id}`);
111
+ }
112
+ }
113
+
114
+ // ── TRIVIAL signals ─────────────────────────────────────────────────────────
115
+ for (const s of TRIVIAL_SIGNALS) {
116
+ if (s.regex.test(t)) {
117
+ score.trivial += 1;
118
+ reasons.push(`trivial:${s.id}`);
119
+ }
120
+ }
121
+
122
+ // ── File-count signals ──────────────────────────────────────────────────────
123
+ if (fileCount >= 4) {
124
+ score.hard += 2;
125
+ reasons.push(`file_count=${fileCount} ≥ 4 (multi-file refactor)`);
126
+ } else if (fileCount === 0 && len < 80 && score.hard === 0) {
127
+ score.trivial += 1;
128
+ reasons.push(`short task (${len} chars), no files in scope`);
129
+ } else if (fileCount >= 1 && fileCount <= 3) {
130
+ score.standard += 1;
131
+ reasons.push(`file_count=${fileCount} (focused scope)`);
132
+ }
133
+
134
+ // ── Length signals ──────────────────────────────────────────────────────────
135
+ if (len > 800 && score.trivial === 0) {
136
+ score.standard += 1;
137
+ reasons.push(`long task description (${len} chars)`);
138
+ }
139
+
140
+ // ── Budget pressure: bias one tier downward when ambiguous ──────────────────
141
+ if (budgetTokens > 0 && sessionTokens / budgetTokens > 0.7 && score.hard === 0) {
142
+ if (score.standard > 0) {
143
+ score.trivial += 1;
144
+ reasons.push('budget > 70% — biasing downward when standard/trivial tied');
145
+ }
146
+ }
147
+
148
+ // ── Tier resolution ─────────────────────────────────────────────────────────
149
+ // Hard rules first.
150
+ if (score.hard >= 2) {
151
+ return { tier: 'hard', reasons, score, explicit_tier: explicitTier || null };
152
+ }
153
+
154
+ // Trivial wins ONLY if it dominates and no hard signal at all.
155
+ if (score.hard === 0 && score.trivial > 0 && score.trivial >= score.standard) {
156
+ return { tier: 'trivial', reasons, score, explicit_tier: explicitTier || null };
157
+ }
158
+
159
+ // Anything with a hard signal but not enough weight → standard (defensive).
160
+ if (score.hard > 0) {
161
+ return { tier: 'standard', reasons: [...reasons, 'hard signal present but weight < 2 → defensive standard'], score, explicit_tier: explicitTier || null };
162
+ }
163
+
164
+ return { tier: 'standard', reasons, score, explicit_tier: explicitTier || null };
165
+ }
166
+
167
+ // ── Config loader (env vars + .kodelyth/router.json) ─────────────────────────
168
+ function loadConfig({ projectRoot = process.cwd() } = {}) {
169
+ const cfg = { ...DEFAULT_MODELS, notes: '' };
170
+
171
+ // 1. Project-level override.
172
+ try {
173
+ const file = path.join(projectRoot, '.kodelyth', 'router.json');
174
+ if (fs.existsSync(file)) {
175
+ const raw = JSON.parse(fs.readFileSync(file, 'utf8'));
176
+ for (const k of ['trivial', 'standard', 'hard', 'default', 'notes']) {
177
+ if (typeof raw[k] === 'string') cfg[k] = raw[k];
178
+ }
179
+ }
180
+ } catch { /* ignore — fall back to defaults */ }
181
+
182
+ // 2. Env-var override (highest precedence).
183
+ if (process.env.KODELYTH_ROUTER_TRIVIAL) cfg.trivial = process.env.KODELYTH_ROUTER_TRIVIAL;
184
+ if (process.env.KODELYTH_ROUTER_STANDARD) cfg.standard = process.env.KODELYTH_ROUTER_STANDARD;
185
+ if (process.env.KODELYTH_ROUTER_HARD) cfg.hard = process.env.KODELYTH_ROUTER_HARD;
186
+ if (process.env.KODELYTH_ROUTER_DEFAULT) cfg.default = process.env.KODELYTH_ROUTER_DEFAULT;
187
+
188
+ cfg.disabled = String(process.env.KODELYTH_ROUTER || '').toLowerCase() === 'off';
189
+ return cfg;
190
+ }
191
+
192
+ // ── Recommend (combines classify + config) ───────────────────────────────────
193
+ function recommend(task, opts = {}) {
194
+ const cfg = loadConfig({ projectRoot: opts.project_root });
195
+ if (cfg.disabled) {
196
+ return {
197
+ disabled: true,
198
+ tier: 'unknown',
199
+ model: opts.current_model || null,
200
+ reasons: ['KODELYTH_ROUTER=off — router disabled'],
201
+ config: cfg,
202
+ };
203
+ }
204
+ const cls = classify(task, opts);
205
+ const tier = cls.tier;
206
+ const recommendedModel = cfg[tier] || cfg.standard;
207
+ const currentModel = opts.current_model || null;
208
+ const mismatched = !!currentModel && currentModel !== recommendedModel;
209
+
210
+ return {
211
+ disabled: false,
212
+ tier,
213
+ recommended_model: recommendedModel,
214
+ current_model: currentModel,
215
+ mismatched,
216
+ reasons: cls.reasons,
217
+ score: cls.score,
218
+ notes: cfg.notes || '',
219
+ config: cfg,
220
+ };
221
+ }
222
+
223
+ module.exports = {
224
+ DEFAULT_MODELS,
225
+ HARD_TIER_AGENTS,
226
+ TRIVIAL_SIGNALS,
227
+ STANDARD_SIGNALS,
228
+ HARD_SIGNALS,
229
+ classify,
230
+ loadConfig,
231
+ recommend,
232
+ };
@@ -0,0 +1,155 @@
1
+ // scripts/supply-chain/manifest.js
2
+ //
3
+ // Content manifest generator for kodelyth-ecc.
4
+ //
5
+ // Walks every shipped asset directory, computes sha256 of each file, and
6
+ // produces a stable, deterministic JSON manifest.
7
+ //
8
+ // The manifest is the "ground truth" for `kodelyth-ecc verify`: an installed
9
+ // copy is compared file-by-file against this manifest.
10
+ //
11
+ // Pure functions — no network, no external deps.
12
+ //
13
+ // Exports:
14
+ // - MANIFEST_SCHEMA
15
+ // - SHIPPED_DIRS (default list of asset directories)
16
+ // - generateManifest({ rootDir, dirs?, timestamp? })
17
+ // - hashFile(absPath)
18
+ // - walkFiles(rootDir, dirs) (iterator-friendly array, sorted)
19
+ //
20
+ 'use strict';
21
+
22
+ const fs = require('fs');
23
+ const path = require('path');
24
+ const crypto = require('crypto');
25
+
26
+ const MANIFEST_SCHEMA = 'kodelyth.content-manifest/v1';
27
+ const MANIFEST_VERSION = 1;
28
+
29
+ // Directories whose contents are considered part of the shipped artifact.
30
+ // Order doesn't matter — output is sorted by relative path.
31
+ const SHIPPED_DIRS = [
32
+ 'agents',
33
+ 'skills',
34
+ 'commands',
35
+ 'rules',
36
+ 'hooks',
37
+ 'scripts',
38
+ 'bin',
39
+ 'parallel-commands',
40
+ 'bundles',
41
+ ];
42
+
43
+ // Files that should be included individually from the repo root.
44
+ const SHIPPED_ROOT_FILES = [
45
+ 'package.json',
46
+ 'README.md',
47
+ 'CHANGELOG.md',
48
+ 'VERSION',
49
+ 'install.sh',
50
+ 'install.ps1',
51
+ ];
52
+
53
+ // Patterns to skip even within shipped dirs.
54
+ // Use [/\\] so patterns match on both POSIX (/) and Windows (\) paths.
55
+ const SKIP_PATTERNS = [
56
+ /[/\\]\.DS_Store$/,
57
+ /[/\\]node_modules[/\\]/,
58
+ /[/\\]\.git[/\\]/,
59
+ /[/\\]__pycache__[/\\]/,
60
+ /\.pyc$/,
61
+ ];
62
+
63
+ function isSkipped(absPath) {
64
+ return SKIP_PATTERNS.some(p => p.test(absPath));
65
+ }
66
+
67
+ function hashFile(absPath) {
68
+ const buf = fs.readFileSync(absPath);
69
+ return crypto.createHash('sha256').update(buf).digest('hex');
70
+ }
71
+
72
+ function* walkRecursive(absDir) {
73
+ let entries;
74
+ try { entries = fs.readdirSync(absDir, { withFileTypes: true }); }
75
+ catch { return; }
76
+ // Sort for stability.
77
+ entries.sort((a, b) => a.name.localeCompare(b.name));
78
+ for (const e of entries) {
79
+ const abs = path.join(absDir, e.name);
80
+ if (isSkipped(abs)) continue;
81
+ if (e.isDirectory()) yield* walkRecursive(abs);
82
+ else if (e.isFile()) yield abs;
83
+ }
84
+ }
85
+
86
+ function walkFiles(rootDir, dirs = SHIPPED_DIRS, rootFiles = SHIPPED_ROOT_FILES) {
87
+ if (!rootDir) throw new Error('walkFiles: rootDir is required');
88
+ const out = [];
89
+
90
+ for (const file of rootFiles) {
91
+ const abs = path.join(rootDir, file);
92
+ if (fs.existsSync(abs) && fs.statSync(abs).isFile() && !isSkipped(abs)) {
93
+ out.push(abs);
94
+ }
95
+ }
96
+
97
+ for (const dir of dirs) {
98
+ const abs = path.join(rootDir, dir);
99
+ if (!fs.existsSync(abs) || !fs.statSync(abs).isDirectory()) continue;
100
+ for (const f of walkRecursive(abs)) out.push(f);
101
+ }
102
+
103
+ // Final sort by relative path so manifest output is deterministic.
104
+ return out.sort((a, b) => a.localeCompare(b));
105
+ }
106
+
107
+ function generateManifest({ rootDir, dirs, rootFiles, timestamp } = {}) {
108
+ if (!rootDir) throw new Error('generateManifest: rootDir is required');
109
+ const files = walkFiles(rootDir, dirs, rootFiles);
110
+
111
+ const pkgPath = path.join(rootDir, 'package.json');
112
+ let pkgName = 'kodelyth-ecc';
113
+ let pkgVersion = '0.0.0';
114
+ try {
115
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
116
+ pkgName = pkg.name || pkgName;
117
+ pkgVersion = pkg.version || pkgVersion;
118
+ } catch { /* leave defaults */ }
119
+
120
+ const entries = files.map(abs => {
121
+ const rel = path.relative(rootDir, abs).split(path.sep).join('/');
122
+ const stat = fs.statSync(abs);
123
+ return {
124
+ path: rel,
125
+ size: stat.size,
126
+ sha256: hashFile(abs),
127
+ };
128
+ });
129
+
130
+ // Compute a manifest-level digest = sha256 over the deterministic JSON of entries.
131
+ const entriesJson = JSON.stringify(entries);
132
+ const manifestDigest = crypto.createHash('sha256').update(entriesJson).digest('hex');
133
+
134
+ return {
135
+ schema: MANIFEST_SCHEMA,
136
+ version: MANIFEST_VERSION,
137
+ package: pkgName,
138
+ pkg_version: pkgVersion,
139
+ generated_at: timestamp || new Date().toISOString(),
140
+ file_count: entries.length,
141
+ digest: manifestDigest,
142
+ files: entries,
143
+ };
144
+ }
145
+
146
+ module.exports = {
147
+ MANIFEST_SCHEMA,
148
+ MANIFEST_VERSION,
149
+ SHIPPED_DIRS,
150
+ SHIPPED_ROOT_FILES,
151
+ SKIP_PATTERNS,
152
+ generateManifest,
153
+ hashFile,
154
+ walkFiles,
155
+ };