compact-agent 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (324) hide show
  1. package/README.md +394 -0
  2. package/bin/anycode.js +2 -0
  3. package/bin/crowcoder.js +19 -0
  4. package/bin/ecc-hooks.cjs +138 -0
  5. package/dist/agents.d.ts +17 -0
  6. package/dist/agents.js +1603 -0
  7. package/dist/agents.js.map +1 -0
  8. package/dist/api.d.ts +16 -0
  9. package/dist/api.js +115 -0
  10. package/dist/api.js.map +1 -0
  11. package/dist/autonomous-loops.d.ts +108 -0
  12. package/dist/autonomous-loops.js +526 -0
  13. package/dist/autonomous-loops.js.map +1 -0
  14. package/dist/codemaps.d.ts +53 -0
  15. package/dist/codemaps.js +325 -0
  16. package/dist/codemaps.js.map +1 -0
  17. package/dist/compaction.d.ts +30 -0
  18. package/dist/compaction.js +125 -0
  19. package/dist/compaction.js.map +1 -0
  20. package/dist/config.d.ts +5 -0
  21. package/dist/config.js +79 -0
  22. package/dist/config.js.map +1 -0
  23. package/dist/content-engine.d.ts +97 -0
  24. package/dist/content-engine.js +721 -0
  25. package/dist/content-engine.js.map +1 -0
  26. package/dist/cost-tracker.d.ts +49 -0
  27. package/dist/cost-tracker.js +150 -0
  28. package/dist/cost-tracker.js.map +1 -0
  29. package/dist/counter-button.d.ts +35 -0
  30. package/dist/counter-button.js +48 -0
  31. package/dist/counter-button.js.map +1 -0
  32. package/dist/counter.d.ts +21 -0
  33. package/dist/counter.js +31 -0
  34. package/dist/counter.js.map +1 -0
  35. package/dist/coverage.d.ts +23 -0
  36. package/dist/coverage.js +215 -0
  37. package/dist/coverage.js.map +1 -0
  38. package/dist/docs-sync.d.ts +23 -0
  39. package/dist/docs-sync.js +266 -0
  40. package/dist/docs-sync.js.map +1 -0
  41. package/dist/ecc.d.ts +41 -0
  42. package/dist/ecc.js +644 -0
  43. package/dist/ecc.js.map +1 -0
  44. package/dist/evaluation.d.ts +24 -0
  45. package/dist/evaluation.js +412 -0
  46. package/dist/evaluation.js.map +1 -0
  47. package/dist/export.d.ts +22 -0
  48. package/dist/export.js +109 -0
  49. package/dist/export.js.map +1 -0
  50. package/dist/git-workflow.d.ts +22 -0
  51. package/dist/git-workflow.js +197 -0
  52. package/dist/git-workflow.js.map +1 -0
  53. package/dist/hook-controls.d.ts +34 -0
  54. package/dist/hook-controls.js +90 -0
  55. package/dist/hook-controls.js.map +1 -0
  56. package/dist/hooks.d.ts +30 -0
  57. package/dist/hooks.js +130 -0
  58. package/dist/hooks.js.map +1 -0
  59. package/dist/html-parser.d.ts +18 -0
  60. package/dist/html-parser.js +101 -0
  61. package/dist/html-parser.js.map +1 -0
  62. package/dist/index.d.ts +12 -0
  63. package/dist/index.js +1230 -0
  64. package/dist/index.js.map +1 -0
  65. package/dist/learning.d.ts +35 -0
  66. package/dist/learning.js +238 -0
  67. package/dist/learning.js.map +1 -0
  68. package/dist/login.d.ts +37 -0
  69. package/dist/login.js +191 -0
  70. package/dist/login.js.map +1 -0
  71. package/dist/memory.d.ts +39 -0
  72. package/dist/memory.js +183 -0
  73. package/dist/memory.js.map +1 -0
  74. package/dist/model-router.d.ts +23 -0
  75. package/dist/model-router.js +145 -0
  76. package/dist/model-router.js.map +1 -0
  77. package/dist/modes.d.ts +17 -0
  78. package/dist/modes.js +217 -0
  79. package/dist/modes.js.map +1 -0
  80. package/dist/orchestration.d.ts +37 -0
  81. package/dist/orchestration.js +139 -0
  82. package/dist/orchestration.js.map +1 -0
  83. package/dist/package-detect.d.ts +36 -0
  84. package/dist/package-detect.js +529 -0
  85. package/dist/package-detect.js.map +1 -0
  86. package/dist/permissions.d.ts +25 -0
  87. package/dist/permissions.js +50 -0
  88. package/dist/permissions.js.map +1 -0
  89. package/dist/pm2-manager.d.ts +40 -0
  90. package/dist/pm2-manager.js +127 -0
  91. package/dist/pm2-manager.js.map +1 -0
  92. package/dist/query.d.ts +15 -0
  93. package/dist/query.js +278 -0
  94. package/dist/query.js.map +1 -0
  95. package/dist/refactor.d.ts +22 -0
  96. package/dist/refactor.js +226 -0
  97. package/dist/refactor.js.map +1 -0
  98. package/dist/retry.d.ts +20 -0
  99. package/dist/retry.js +88 -0
  100. package/dist/retry.js.map +1 -0
  101. package/dist/rules.d.ts +34 -0
  102. package/dist/rules.js +942 -0
  103. package/dist/rules.js.map +1 -0
  104. package/dist/schema.d.ts +23 -0
  105. package/dist/schema.js +12 -0
  106. package/dist/schema.js.map +1 -0
  107. package/dist/search-first.d.ts +17 -0
  108. package/dist/search-first.js +301 -0
  109. package/dist/search-first.js.map +1 -0
  110. package/dist/security.d.ts +10 -0
  111. package/dist/security.js +145 -0
  112. package/dist/security.js.map +1 -0
  113. package/dist/sessions.d.ts +21 -0
  114. package/dist/sessions.js +112 -0
  115. package/dist/sessions.js.map +1 -0
  116. package/dist/skill-create.d.ts +38 -0
  117. package/dist/skill-create.js +389 -0
  118. package/dist/skill-create.js.map +1 -0
  119. package/dist/skills.d.ts +34 -0
  120. package/dist/skills.js +161 -0
  121. package/dist/skills.js.map +1 -0
  122. package/dist/strategic-compaction.d.ts +24 -0
  123. package/dist/strategic-compaction.js +144 -0
  124. package/dist/strategic-compaction.js.map +1 -0
  125. package/dist/system-prompt.d.ts +3 -0
  126. package/dist/system-prompt.js +101 -0
  127. package/dist/system-prompt.js.map +1 -0
  128. package/dist/theme.d.ts +60 -0
  129. package/dist/theme.js +220 -0
  130. package/dist/theme.js.map +1 -0
  131. package/dist/tools/bash.d.ts +2 -0
  132. package/dist/tools/bash.js +49 -0
  133. package/dist/tools/bash.js.map +1 -0
  134. package/dist/tools/edit.d.ts +2 -0
  135. package/dist/tools/edit.js +76 -0
  136. package/dist/tools/edit.js.map +1 -0
  137. package/dist/tools/glob.d.ts +2 -0
  138. package/dist/tools/glob.js +54 -0
  139. package/dist/tools/glob.js.map +1 -0
  140. package/dist/tools/grep.d.ts +2 -0
  141. package/dist/tools/grep.js +64 -0
  142. package/dist/tools/grep.js.map +1 -0
  143. package/dist/tools/index.d.ts +5 -0
  144. package/dist/tools/index.js +27 -0
  145. package/dist/tools/index.js.map +1 -0
  146. package/dist/tools/list-dir.d.ts +2 -0
  147. package/dist/tools/list-dir.js +51 -0
  148. package/dist/tools/list-dir.js.map +1 -0
  149. package/dist/tools/read.d.ts +2 -0
  150. package/dist/tools/read.js +56 -0
  151. package/dist/tools/read.js.map +1 -0
  152. package/dist/tools/types.d.ts +45 -0
  153. package/dist/tools/types.js +2 -0
  154. package/dist/tools/types.js.map +1 -0
  155. package/dist/tools/web-fetch.d.ts +2 -0
  156. package/dist/tools/web-fetch.js +41 -0
  157. package/dist/tools/web-fetch.js.map +1 -0
  158. package/dist/tools/web-search.d.ts +27 -0
  159. package/dist/tools/web-search.js +139 -0
  160. package/dist/tools/web-search.js.map +1 -0
  161. package/dist/tools/write.d.ts +2 -0
  162. package/dist/tools/write.js +36 -0
  163. package/dist/tools/write.js.map +1 -0
  164. package/dist/types.d.ts +28 -0
  165. package/dist/types.js +57 -0
  166. package/dist/types.js.map +1 -0
  167. package/dist/users.d.ts +51 -0
  168. package/dist/users.js +193 -0
  169. package/dist/users.js.map +1 -0
  170. package/dist/verification.d.ts +73 -0
  171. package/dist/verification.js +269 -0
  172. package/dist/verification.js.map +1 -0
  173. package/dist/walkthrough.d.ts +10 -0
  174. package/dist/walkthrough.js +121 -0
  175. package/dist/walkthrough.js.map +1 -0
  176. package/package.json +58 -0
  177. package/resources/ecc/agents/architect.json +16 -0
  178. package/resources/ecc/agents/architect.md +212 -0
  179. package/resources/ecc/agents/build-error-resolver.json +17 -0
  180. package/resources/ecc/agents/build-error-resolver.md +116 -0
  181. package/resources/ecc/agents/chief-of-staff.json +17 -0
  182. package/resources/ecc/agents/chief-of-staff.md +153 -0
  183. package/resources/ecc/agents/code-reviewer.json +16 -0
  184. package/resources/ecc/agents/code-reviewer.md +238 -0
  185. package/resources/ecc/agents/database-reviewer.json +16 -0
  186. package/resources/ecc/agents/database-reviewer.md +92 -0
  187. package/resources/ecc/agents/doc-updater.json +16 -0
  188. package/resources/ecc/agents/doc-updater.md +108 -0
  189. package/resources/ecc/agents/e2e-runner.json +17 -0
  190. package/resources/ecc/agents/e2e-runner.md +109 -0
  191. package/resources/ecc/agents/go-build-resolver.json +17 -0
  192. package/resources/ecc/agents/go-build-resolver.md +96 -0
  193. package/resources/ecc/agents/go-reviewer.json +16 -0
  194. package/resources/ecc/agents/go-reviewer.md +77 -0
  195. package/resources/ecc/agents/harness-optimizer.json +15 -0
  196. package/resources/ecc/agents/harness-optimizer.md +34 -0
  197. package/resources/ecc/agents/loop-operator.json +16 -0
  198. package/resources/ecc/agents/loop-operator.md +36 -0
  199. package/resources/ecc/agents/planner.json +15 -0
  200. package/resources/ecc/agents/planner.md +212 -0
  201. package/resources/ecc/agents/python-reviewer.json +16 -0
  202. package/resources/ecc/agents/python-reviewer.md +99 -0
  203. package/resources/ecc/agents/refactor-cleaner.json +17 -0
  204. package/resources/ecc/agents/refactor-cleaner.md +87 -0
  205. package/resources/ecc/agents/security-reviewer.json +16 -0
  206. package/resources/ecc/agents/security-reviewer.md +109 -0
  207. package/resources/ecc/agents/tdd-guide.json +17 -0
  208. package/resources/ecc/agents/tdd-guide.md +93 -0
  209. package/resources/ecc/commands/add-language-rules.md +39 -0
  210. package/resources/ecc/commands/database-migration.md +36 -0
  211. package/resources/ecc/commands/feature-development.md +38 -0
  212. package/resources/ecc/prompts/build-fix.prompt.md +47 -0
  213. package/resources/ecc/prompts/code-review.prompt.md +56 -0
  214. package/resources/ecc/prompts/plan.prompt.md +52 -0
  215. package/resources/ecc/prompts/refactor.prompt.md +50 -0
  216. package/resources/ecc/prompts/security-review.prompt.md +70 -0
  217. package/resources/ecc/prompts/tdd.prompt.md +47 -0
  218. package/resources/ecc/rules/common-agents.md +53 -0
  219. package/resources/ecc/rules/common-coding-style.md +52 -0
  220. package/resources/ecc/rules/common-development-workflow.md +33 -0
  221. package/resources/ecc/rules/common-git-workflow.md +28 -0
  222. package/resources/ecc/rules/common-hooks.md +34 -0
  223. package/resources/ecc/rules/common-patterns.md +35 -0
  224. package/resources/ecc/rules/common-performance.md +59 -0
  225. package/resources/ecc/rules/common-security.md +33 -0
  226. package/resources/ecc/rules/common-testing.md +33 -0
  227. package/resources/ecc/rules/golang-coding-style.md +31 -0
  228. package/resources/ecc/rules/golang-hooks.md +16 -0
  229. package/resources/ecc/rules/golang-patterns.md +44 -0
  230. package/resources/ecc/rules/golang-security.md +33 -0
  231. package/resources/ecc/rules/golang-testing.md +30 -0
  232. package/resources/ecc/rules/kotlin-coding-style.md +39 -0
  233. package/resources/ecc/rules/kotlin-hooks.md +16 -0
  234. package/resources/ecc/rules/kotlin-patterns.md +50 -0
  235. package/resources/ecc/rules/kotlin-security.md +58 -0
  236. package/resources/ecc/rules/kotlin-testing.md +38 -0
  237. package/resources/ecc/rules/php-coding-style.md +25 -0
  238. package/resources/ecc/rules/php-hooks.md +21 -0
  239. package/resources/ecc/rules/php-patterns.md +23 -0
  240. package/resources/ecc/rules/php-security.md +24 -0
  241. package/resources/ecc/rules/php-testing.md +26 -0
  242. package/resources/ecc/rules/python-coding-style.md +42 -0
  243. package/resources/ecc/rules/python-hooks.md +19 -0
  244. package/resources/ecc/rules/python-patterns.md +39 -0
  245. package/resources/ecc/rules/python-security.md +30 -0
  246. package/resources/ecc/rules/python-testing.md +38 -0
  247. package/resources/ecc/rules/swift-coding-style.md +47 -0
  248. package/resources/ecc/rules/swift-hooks.md +20 -0
  249. package/resources/ecc/rules/swift-patterns.md +66 -0
  250. package/resources/ecc/rules/swift-security.md +33 -0
  251. package/resources/ecc/rules/swift-testing.md +45 -0
  252. package/resources/ecc/rules/typescript-coding-style.md +63 -0
  253. package/resources/ecc/rules/typescript-hooks.md +20 -0
  254. package/resources/ecc/rules/typescript-patterns.md +50 -0
  255. package/resources/ecc/rules/typescript-security.md +26 -0
  256. package/resources/ecc/rules/typescript-testing.md +16 -0
  257. package/resources/ecc/skills/agent-introspection-debugging/SKILL.md +152 -0
  258. package/resources/ecc/skills/agent-introspection-debugging/agents/openai.yaml +7 -0
  259. package/resources/ecc/skills/agent-sort/SKILL.md +214 -0
  260. package/resources/ecc/skills/agent-sort/agents/openai.yaml +7 -0
  261. package/resources/ecc/skills/api-design/SKILL.md +522 -0
  262. package/resources/ecc/skills/api-design/agents/openai.yaml +7 -0
  263. package/resources/ecc/skills/article-writing/SKILL.md +78 -0
  264. package/resources/ecc/skills/article-writing/agents/openai.yaml +7 -0
  265. package/resources/ecc/skills/backend-patterns/SKILL.md +597 -0
  266. package/resources/ecc/skills/backend-patterns/agents/openai.yaml +7 -0
  267. package/resources/ecc/skills/brand-voice/SKILL.md +96 -0
  268. package/resources/ecc/skills/brand-voice/agents/openai.yaml +7 -0
  269. package/resources/ecc/skills/brand-voice/references/voice-profile-schema.md +55 -0
  270. package/resources/ecc/skills/bun-runtime/SKILL.md +83 -0
  271. package/resources/ecc/skills/bun-runtime/agents/openai.yaml +7 -0
  272. package/resources/ecc/skills/coding-standards/SKILL.md +548 -0
  273. package/resources/ecc/skills/coding-standards/agents/openai.yaml +7 -0
  274. package/resources/ecc/skills/content-engine/SKILL.md +130 -0
  275. package/resources/ecc/skills/content-engine/agents/openai.yaml +7 -0
  276. package/resources/ecc/skills/crosspost/SKILL.md +110 -0
  277. package/resources/ecc/skills/crosspost/agents/openai.yaml +7 -0
  278. package/resources/ecc/skills/deep-research/SKILL.md +154 -0
  279. package/resources/ecc/skills/deep-research/agents/openai.yaml +7 -0
  280. package/resources/ecc/skills/dmux-workflows/SKILL.md +143 -0
  281. package/resources/ecc/skills/dmux-workflows/agents/openai.yaml +7 -0
  282. package/resources/ecc/skills/documentation-lookup/SKILL.md +89 -0
  283. package/resources/ecc/skills/documentation-lookup/agents/openai.yaml +7 -0
  284. package/resources/ecc/skills/e2e-testing/SKILL.md +325 -0
  285. package/resources/ecc/skills/e2e-testing/agents/openai.yaml +7 -0
  286. package/resources/ecc/skills/eval-harness/SKILL.md +235 -0
  287. package/resources/ecc/skills/eval-harness/agents/openai.yaml +7 -0
  288. package/resources/ecc/skills/everything-claude-code/SKILL.md +442 -0
  289. package/resources/ecc/skills/everything-claude-code/agents/openai.yaml +7 -0
  290. package/resources/ecc/skills/exa-search/SKILL.md +169 -0
  291. package/resources/ecc/skills/exa-search/agents/openai.yaml +7 -0
  292. package/resources/ecc/skills/fal-ai-media/SKILL.md +276 -0
  293. package/resources/ecc/skills/fal-ai-media/agents/openai.yaml +7 -0
  294. package/resources/ecc/skills/frontend-patterns/SKILL.md +647 -0
  295. package/resources/ecc/skills/frontend-patterns/agents/openai.yaml +7 -0
  296. package/resources/ecc/skills/frontend-slides/SKILL.md +183 -0
  297. package/resources/ecc/skills/frontend-slides/STYLE_PRESETS.md +330 -0
  298. package/resources/ecc/skills/frontend-slides/agents/openai.yaml +7 -0
  299. package/resources/ecc/skills/investor-materials/SKILL.md +95 -0
  300. package/resources/ecc/skills/investor-materials/agents/openai.yaml +7 -0
  301. package/resources/ecc/skills/investor-outreach/SKILL.md +90 -0
  302. package/resources/ecc/skills/investor-outreach/agents/openai.yaml +7 -0
  303. package/resources/ecc/skills/market-research/SKILL.md +74 -0
  304. package/resources/ecc/skills/market-research/agents/openai.yaml +7 -0
  305. package/resources/ecc/skills/mcp-server-patterns/SKILL.md +66 -0
  306. package/resources/ecc/skills/mcp-server-patterns/agents/openai.yaml +7 -0
  307. package/resources/ecc/skills/mle-workflow/SKILL.md +346 -0
  308. package/resources/ecc/skills/mle-workflow/agents/openai.yaml +7 -0
  309. package/resources/ecc/skills/nextjs-turbopack/SKILL.md +43 -0
  310. package/resources/ecc/skills/nextjs-turbopack/agents/openai.yaml +7 -0
  311. package/resources/ecc/skills/product-capability/SKILL.md +140 -0
  312. package/resources/ecc/skills/product-capability/agents/openai.yaml +7 -0
  313. package/resources/ecc/skills/security-review/SKILL.md +494 -0
  314. package/resources/ecc/skills/security-review/agents/openai.yaml +7 -0
  315. package/resources/ecc/skills/strategic-compact/SKILL.md +102 -0
  316. package/resources/ecc/skills/strategic-compact/agents/openai.yaml +7 -0
  317. package/resources/ecc/skills/tdd-workflow/SKILL.md +409 -0
  318. package/resources/ecc/skills/tdd-workflow/agents/openai.yaml +7 -0
  319. package/resources/ecc/skills/verification-loop/SKILL.md +125 -0
  320. package/resources/ecc/skills/verification-loop/agents/openai.yaml +7 -0
  321. package/resources/ecc/skills/video-editing/SKILL.md +307 -0
  322. package/resources/ecc/skills/video-editing/agents/openai.yaml +7 -0
  323. package/resources/ecc/skills/x-api/SKILL.md +229 -0
  324. package/resources/ecc/skills/x-api/agents/openai.yaml +7 -0
package/dist/ecc.js ADDED
@@ -0,0 +1,644 @@
1
+ /**
2
+ * ECC (everything-claude-code) integration.
3
+ *
4
+ * Imports skills, agents, slash commands, rules, and hook behaviors from the
5
+ * bundled `resources/ecc/` directory into Crowcoder's runtime stores:
6
+ * ~/.crowcoder/skills/ — JSON skills generated from SKILL.md
7
+ * ~/.crowcoder/rules/ — language rule files
8
+ * ~/.crowcoder/ecc-commands/ — markdown prompt templates for /ecc-<cmd>
9
+ * ~/.crowcoder/ecc-agents/ — agent prompt templates
10
+ * ~/.crowcoder/hooks.json — augmented with ECC hook entries
11
+ *
12
+ * Each ECC skill becomes a Crowcoder Skill (skills.ts schema) with id
13
+ * `ecc-<slug>`, triggers derived from name + description keywords, and the
14
+ * SKILL.md body as the prompt template.
15
+ *
16
+ * The import is idempotent — re-running overwrites prior ECC entries but
17
+ * leaves user-created skills/rules/hooks alone (ECC entries are scoped by
18
+ * id prefix `ecc-` or `ecc:` category).
19
+ */
20
+ import { readFileSync, writeFileSync, readdirSync, mkdirSync, existsSync, statSync, copyFileSync, unlinkSync, } from 'node:fs';
21
+ import { join, dirname, basename, resolve } from 'node:path';
22
+ import { fileURLToPath } from 'node:url';
23
+ import chalk from 'chalk';
24
+ import { getConfigDir } from './config.js';
25
+ import { saveSkill, listSkills, deleteSkill } from './skills.js';
26
+ import { listHooks, saveHooksConfig } from './hooks.js';
27
+ // ── Resource resolution ─────────────────────────────────
28
+ // resources/ live one level above the compiled dist/ (and one above src/).
29
+ const __filename = fileURLToPath(import.meta.url);
30
+ const __dirname = dirname(__filename);
31
+ const RESOURCES_ROOT = resolve(__dirname, '..', 'resources', 'ecc');
32
+ const ECC_SKILLS_SRC = join(RESOURCES_ROOT, 'skills');
33
+ const ECC_AGENTS_SRC = join(RESOURCES_ROOT, 'agents');
34
+ const ECC_COMMANDS_SRC = join(RESOURCES_ROOT, 'commands');
35
+ const ECC_PROMPTS_SRC = join(RESOURCES_ROOT, 'prompts');
36
+ const ECC_RULES_SRC = join(RESOURCES_ROOT, 'rules');
37
+ const RULES_DIR = join(getConfigDir(), 'rules');
38
+ const ECC_COMMANDS_DST = join(getConfigDir(), 'ecc-commands');
39
+ const ECC_AGENTS_DST = join(getConfigDir(), 'ecc-agents');
40
+ const ECC_STATE_FILE = join(getConfigDir(), 'ecc-state.json');
41
+ const ECC_SKILL_ID_PREFIX = 'ecc-';
42
+ const ECC_HOOK_TAG = '__ecc__';
43
+ export function loadEccState() {
44
+ if (!existsSync(ECC_STATE_FILE))
45
+ return null;
46
+ try {
47
+ return JSON.parse(readFileSync(ECC_STATE_FILE, 'utf-8'));
48
+ }
49
+ catch {
50
+ return null;
51
+ }
52
+ }
53
+ function saveEccState(s) {
54
+ mkdirSync(getConfigDir(), { recursive: true });
55
+ writeFileSync(ECC_STATE_FILE, JSON.stringify(s, null, 2), 'utf-8');
56
+ }
57
+ export function eccResourcesAvailable() {
58
+ return existsSync(RESOURCES_ROOT) && existsSync(ECC_SKILLS_SRC);
59
+ }
60
+ /**
61
+ * Minimal YAML frontmatter parser — handles the subset used by ECC:
62
+ * key: value
63
+ * key: "value with: colons"
64
+ * key:
65
+ * - item1
66
+ * - item2
67
+ * key: ["a", "b"]
68
+ * Anything more exotic is preserved as raw string.
69
+ */
70
+ function parseFrontmatter(raw) {
71
+ if (!raw.startsWith('---'))
72
+ return { frontmatter: {}, body: raw };
73
+ const end = raw.indexOf('\n---', 3);
74
+ if (end < 0)
75
+ return { frontmatter: {}, body: raw };
76
+ const yamlBlock = raw.slice(3, end).replace(/^\r?\n/, '');
77
+ const body = raw.slice(end + 4).replace(/^\r?\n/, '');
78
+ const fm = {};
79
+ const lines = yamlBlock.split(/\r?\n/);
80
+ let currentKey = null;
81
+ let listAcc = null;
82
+ const stripQuotes = (s) => {
83
+ const t = s.trim();
84
+ if ((t.startsWith('"') && t.endsWith('"')) || (t.startsWith("'") && t.endsWith("'"))) {
85
+ return t.slice(1, -1);
86
+ }
87
+ return t;
88
+ };
89
+ for (const rawLine of lines) {
90
+ const line = rawLine.replace(/\s+$/, '');
91
+ if (!line.trim())
92
+ continue;
93
+ const listMatch = line.match(/^\s+-\s+(.*)$/);
94
+ if (listMatch && currentKey && listAcc) {
95
+ listAcc.push(stripQuotes(listMatch[1]));
96
+ continue;
97
+ }
98
+ // commit previous list
99
+ if (currentKey && listAcc) {
100
+ fm[currentKey] = listAcc;
101
+ listAcc = null;
102
+ currentKey = null;
103
+ }
104
+ const kv = line.match(/^([A-Za-z_][\w-]*)\s*:\s*(.*)$/);
105
+ if (!kv)
106
+ continue;
107
+ const key = kv[1];
108
+ const valRaw = kv[2];
109
+ if (valRaw === '' || valRaw === undefined) {
110
+ // start of multi-line value (list)
111
+ currentKey = key;
112
+ listAcc = [];
113
+ continue;
114
+ }
115
+ // inline array
116
+ if (valRaw.startsWith('[') && valRaw.endsWith(']')) {
117
+ const inner = valRaw.slice(1, -1).trim();
118
+ const items = inner.length
119
+ ? inner.split(',').map(s => stripQuotes(s))
120
+ : [];
121
+ fm[key] = items;
122
+ continue;
123
+ }
124
+ fm[key] = stripQuotes(valRaw);
125
+ }
126
+ // flush trailing list
127
+ if (currentKey && listAcc)
128
+ fm[currentKey] = listAcc;
129
+ return { frontmatter: fm, body };
130
+ }
131
+ // ── Trigger derivation ──────────────────────────────────
132
+ const STOPWORDS = new Set([
133
+ 'the', 'and', 'for', 'with', 'this', 'that', 'when', 'use', 'used',
134
+ 'using', 'from', 'into', 'onto', 'a', 'an', 'of', 'to', 'in', 'on',
135
+ 'as', 'by', 'is', 'are', 'be', 'been', 'or', 'but', 'not', 'all',
136
+ 'any', 'some', 'must', 'should', 'must', 'will', 'can', 'skill',
137
+ 'workflow', 'pattern', 'patterns', 'rules', 'rule', 'using',
138
+ ]);
139
+ function deriveTriggers(name, description, extras = []) {
140
+ const triggers = new Set();
141
+ // slug parts of name
142
+ for (const part of name.toLowerCase().split(/[-_\s]+/)) {
143
+ if (part.length >= 3)
144
+ triggers.add(part);
145
+ }
146
+ triggers.add(name.toLowerCase());
147
+ // first 60 chars of description, keywords
148
+ const descSample = description.toLowerCase().slice(0, 200);
149
+ for (const word of descSample.split(/[^a-z0-9]+/)) {
150
+ if (word.length >= 5 && !STOPWORDS.has(word))
151
+ triggers.add(word);
152
+ }
153
+ for (const e of extras) {
154
+ const norm = e.toLowerCase().trim();
155
+ if (norm.length >= 3)
156
+ triggers.add(norm);
157
+ }
158
+ return Array.from(triggers).slice(0, 12);
159
+ }
160
+ // ── Skill import ────────────────────────────────────────
161
+ function listSkillDirs() {
162
+ if (!existsSync(ECC_SKILLS_SRC))
163
+ return [];
164
+ return readdirSync(ECC_SKILLS_SRC)
165
+ .map(n => join(ECC_SKILLS_SRC, n))
166
+ .filter(p => {
167
+ try {
168
+ return statSync(p).isDirectory();
169
+ }
170
+ catch {
171
+ return false;
172
+ }
173
+ });
174
+ }
175
+ function readSkillMd(dir) {
176
+ const skillPath = join(dir, 'SKILL.md');
177
+ if (!existsSync(skillPath))
178
+ return null;
179
+ const raw = readFileSync(skillPath, 'utf-8');
180
+ // Pull in sibling reference docs if present (e.g. STYLE_PRESETS.md)
181
+ const references = [];
182
+ for (const f of readdirSync(dir)) {
183
+ if (f === 'SKILL.md')
184
+ continue;
185
+ if (!f.endsWith('.md'))
186
+ continue;
187
+ references.push(readFileSync(join(dir, f), 'utf-8'));
188
+ }
189
+ // Also pull in references/ subdir
190
+ const refDir = join(dir, 'references');
191
+ if (existsSync(refDir)) {
192
+ try {
193
+ for (const f of readdirSync(refDir)) {
194
+ if (f.endsWith('.md')) {
195
+ references.push(readFileSync(join(refDir, f), 'utf-8'));
196
+ }
197
+ }
198
+ }
199
+ catch { /* ignore */ }
200
+ }
201
+ return { raw, references };
202
+ }
203
+ function eccSkillId(slug) {
204
+ return `${ECC_SKILL_ID_PREFIX}${slug}`;
205
+ }
206
+ function importSkills() {
207
+ const errors = [];
208
+ // Clean prior ECC skills first so renames don't leave stragglers
209
+ for (const s of listSkills()) {
210
+ if (s.id.startsWith(ECC_SKILL_ID_PREFIX)) {
211
+ deleteSkill(s.id);
212
+ }
213
+ }
214
+ let count = 0;
215
+ for (const dir of listSkillDirs()) {
216
+ const slug = basename(dir);
217
+ try {
218
+ const doc = readSkillMd(dir);
219
+ if (!doc)
220
+ continue;
221
+ const { frontmatter, body } = parseFrontmatter(doc.raw);
222
+ const name = String(frontmatter.name || slug);
223
+ const description = String(frontmatter.description || `ECC skill: ${slug}`);
224
+ const triggers = deriveTriggers(name, description, [slug]);
225
+ const fullBody = doc.references.length
226
+ ? body + '\n\n---\n\n' + doc.references.join('\n\n---\n\n')
227
+ : body;
228
+ const skill = {
229
+ id: eccSkillId(slug),
230
+ name,
231
+ description,
232
+ prompt: fullBody.trim(),
233
+ triggers,
234
+ category: 'ecc',
235
+ createdAt: new Date().toISOString(),
236
+ useCount: 0,
237
+ };
238
+ saveSkill(skill);
239
+ count++;
240
+ }
241
+ catch (err) {
242
+ errors.push(`skill ${slug}: ${err instanceof Error ? err.message : String(err)}`);
243
+ }
244
+ }
245
+ return { count, errors };
246
+ }
247
+ // ── Agent import ────────────────────────────────────────
248
+ /**
249
+ * Kiro agents are JSON + MD pairs:
250
+ * <name>.json — { name, description, prompt, allowedTools, ... }
251
+ * <name>.md — frontmatter (name, description, allowedTools) + body prompt
252
+ *
253
+ * We materialize each as a markdown file in ~/.crowcoder/ecc-agents/<name>.md
254
+ * (canonical prompt for /ecc-agent <name>) and ALSO register a Crowcoder Skill
255
+ * with id `ecc-agent-<name>` so it surfaces in /skills and trigger search.
256
+ */
257
+ function importAgents() {
258
+ const errors = [];
259
+ if (!existsSync(ECC_AGENTS_SRC))
260
+ return { count: 0, errors };
261
+ mkdirSync(ECC_AGENTS_DST, { recursive: true });
262
+ // Clean prior agent skills
263
+ for (const s of listSkills()) {
264
+ if (s.id.startsWith(`${ECC_SKILL_ID_PREFIX}agent-`)) {
265
+ deleteSkill(s.id);
266
+ }
267
+ }
268
+ let count = 0;
269
+ const files = readdirSync(ECC_AGENTS_SRC).filter(f => f.endsWith('.json'));
270
+ for (const file of files) {
271
+ const slug = file.replace(/\.json$/, '');
272
+ try {
273
+ const jsonPath = join(ECC_AGENTS_SRC, file);
274
+ const mdPath = join(ECC_AGENTS_SRC, `${slug}.md`);
275
+ const jsonRaw = readFileSync(jsonPath, 'utf-8');
276
+ const json = JSON.parse(jsonRaw);
277
+ let prompt = String(json.prompt || '');
278
+ let description = String(json.description || `ECC agent: ${slug}`);
279
+ const allowed = json.allowedTools || [];
280
+ if (existsSync(mdPath)) {
281
+ const md = readFileSync(mdPath, 'utf-8');
282
+ const { frontmatter, body } = parseFrontmatter(md);
283
+ if (body.trim().length > prompt.length)
284
+ prompt = body.trim();
285
+ if (frontmatter.description)
286
+ description = String(frontmatter.description);
287
+ }
288
+ // Write canonical agent prompt to ecc-agents dir
289
+ const agentDoc = [
290
+ `# ${slug}`,
291
+ '',
292
+ `> ${description}`,
293
+ '',
294
+ `**Allowed tools**: ${allowed.length ? allowed.join(', ') : '(any)'}`,
295
+ '',
296
+ prompt,
297
+ ].join('\n');
298
+ writeFileSync(join(ECC_AGENTS_DST, `${slug}.md`), agentDoc, 'utf-8');
299
+ // Register a skill so it surfaces in /skills + trigger search
300
+ const triggers = deriveTriggers(slug, description, [slug, 'agent']);
301
+ saveSkill({
302
+ id: `${ECC_SKILL_ID_PREFIX}agent-${slug}`,
303
+ name: `agent: ${slug}`,
304
+ description,
305
+ prompt,
306
+ triggers,
307
+ category: 'ecc-agent',
308
+ createdAt: new Date().toISOString(),
309
+ useCount: 0,
310
+ });
311
+ count++;
312
+ }
313
+ catch (err) {
314
+ errors.push(`agent ${slug}: ${err instanceof Error ? err.message : String(err)}`);
315
+ }
316
+ }
317
+ return { count, errors };
318
+ }
319
+ // ── Command + prompt import ─────────────────────────────
320
+ /**
321
+ * .claude/commands/<name>.md and .github/prompts/<name>.prompt.md are both
322
+ * frontmatter+body prompt templates. We copy them verbatim into
323
+ * ~/.crowcoder/ecc-commands/ so /ecc-<name> can read them at runtime.
324
+ */
325
+ function importCommandsAndPrompts() {
326
+ const errors = [];
327
+ let commands = 0;
328
+ let prompts = 0;
329
+ mkdirSync(ECC_COMMANDS_DST, { recursive: true });
330
+ // Wipe prior ECC commands
331
+ for (const f of readdirSync(ECC_COMMANDS_DST)) {
332
+ try {
333
+ unlinkSync(join(ECC_COMMANDS_DST, f));
334
+ }
335
+ catch { /* ignore */ }
336
+ }
337
+ const copyMd = (srcDir, suffix = '') => {
338
+ if (!existsSync(srcDir))
339
+ return 0;
340
+ let n = 0;
341
+ for (const f of readdirSync(srcDir)) {
342
+ if (!f.endsWith('.md'))
343
+ continue;
344
+ const src = join(srcDir, f);
345
+ try {
346
+ const stat = statSync(src);
347
+ if (!stat.isFile())
348
+ continue;
349
+ }
350
+ catch {
351
+ continue;
352
+ }
353
+ const baseName = f.replace(/\.prompt\.md$/, '').replace(/\.md$/, '');
354
+ const dst = join(ECC_COMMANDS_DST, `${baseName}${suffix}.md`);
355
+ copyFileSync(src, dst);
356
+ n++;
357
+ }
358
+ return n;
359
+ };
360
+ try {
361
+ commands = copyMd(ECC_COMMANDS_SRC);
362
+ }
363
+ catch (err) {
364
+ errors.push(`commands: ${err instanceof Error ? err.message : err}`);
365
+ }
366
+ try {
367
+ prompts = copyMd(ECC_PROMPTS_SRC);
368
+ }
369
+ catch (err) {
370
+ errors.push(`prompts: ${err instanceof Error ? err.message : err}`);
371
+ }
372
+ return { commands, prompts, errors };
373
+ }
374
+ export function listEccCommands() {
375
+ if (!existsSync(ECC_COMMANDS_DST))
376
+ return [];
377
+ return readdirSync(ECC_COMMANDS_DST)
378
+ .filter(f => f.endsWith('.md'))
379
+ .map(f => f.replace(/\.md$/, ''))
380
+ .sort();
381
+ }
382
+ /**
383
+ * Resolve a `/ecc-<name>` command to its prompt body. Frontmatter is stripped;
384
+ * if `allowed_tools` is set, it's preserved as a note at the top.
385
+ */
386
+ export function getEccCommandPrompt(name) {
387
+ const path = join(ECC_COMMANDS_DST, `${name}.md`);
388
+ if (!existsSync(path))
389
+ return null;
390
+ const raw = readFileSync(path, 'utf-8');
391
+ const { frontmatter, body } = parseFrontmatter(raw);
392
+ const header = [];
393
+ if (frontmatter.description)
394
+ header.push(`> ${frontmatter.description}`);
395
+ if (frontmatter.allowed_tools) {
396
+ const tools = Array.isArray(frontmatter.allowed_tools)
397
+ ? frontmatter.allowed_tools.join(', ')
398
+ : String(frontmatter.allowed_tools);
399
+ header.push(`> Allowed tools: ${tools}`);
400
+ }
401
+ return [...header, '', body].join('\n').trim();
402
+ }
403
+ // ── Rules import ────────────────────────────────────────
404
+ /**
405
+ * ECC ships per-language rules split into 5 files
406
+ * (coding-style, security, patterns, testing, hooks). Crowcoder reads
407
+ * `~/.crowcoder/rules/<language>.md` as a single bundle per language, so we
408
+ * concatenate the 5 files per language. Existing user content in those files
409
+ * is preserved by appending under a clearly-marked ECC section.
410
+ */
411
+ function importRules() {
412
+ const errors = [];
413
+ if (!existsSync(ECC_RULES_SRC))
414
+ return { count: 0, errors };
415
+ mkdirSync(RULES_DIR, { recursive: true });
416
+ const byLang = new Map();
417
+ for (const f of readdirSync(ECC_RULES_SRC)) {
418
+ if (!f.endsWith('.md'))
419
+ continue;
420
+ const m = f.match(/^([a-z]+)-([a-z-]+)\.md$/);
421
+ if (!m)
422
+ continue;
423
+ const lang = m[1];
424
+ const section = m[2];
425
+ const content = readFileSync(join(ECC_RULES_SRC, f), 'utf-8');
426
+ if (!byLang.has(lang))
427
+ byLang.set(lang, []);
428
+ byLang.get(lang).push(`## ${section}\n\n${content.trim()}`);
429
+ }
430
+ let count = 0;
431
+ for (const [lang, sections] of byLang.entries()) {
432
+ try {
433
+ const dst = join(RULES_DIR, `${lang}.md`);
434
+ const ECC_BEGIN = '<!-- ECC-RULES:BEGIN -->';
435
+ const ECC_END = '<!-- ECC-RULES:END -->';
436
+ const eccBlock = [
437
+ ECC_BEGIN,
438
+ `# ${lang} — everything-claude-code rules`,
439
+ '',
440
+ sections.join('\n\n---\n\n'),
441
+ ECC_END,
442
+ ].join('\n');
443
+ let final = eccBlock;
444
+ if (existsSync(dst)) {
445
+ const existing = readFileSync(dst, 'utf-8');
446
+ // Strip prior ECC block before appending
447
+ const stripped = existing.replace(new RegExp(`${ECC_BEGIN}[\\s\\S]*?${ECC_END}`, 'g'), '').trim();
448
+ final = stripped ? `${stripped}\n\n${eccBlock}` : eccBlock;
449
+ }
450
+ writeFileSync(dst, final, 'utf-8');
451
+ count++;
452
+ }
453
+ catch (err) {
454
+ errors.push(`rules ${lang}: ${err instanceof Error ? err.message : String(err)}`);
455
+ }
456
+ }
457
+ return { count, errors };
458
+ }
459
+ // ── Hook seeding ────────────────────────────────────────
460
+ /**
461
+ * Seeds Crowcoder's hooks.json with ECC-compatible default hooks.
462
+ *
463
+ * The cursor hook scripts have a dense in-repo dependency tree (scripts/lib/,
464
+ * scripts/hooks/) and require their original directory layout — we don't try
465
+ * to run them from Crowcoder. Instead we install native equivalents for the
466
+ * highest-value ECC hook behaviors: block-no-verify, secret-in-prompt detection,
467
+ * console.log warnings post-edit, dev-server tmux reminder.
468
+ *
469
+ * Each hook entry is tagged with __ecc__ so /ecc-install can refresh them
470
+ * without touching user-defined hooks.
471
+ */
472
+ function seedHooks() {
473
+ const installDir = resolve(__dirname, '..');
474
+ const hookScript = join(installDir, 'bin', 'ecc-hooks.cjs');
475
+ if (!existsSync(hookScript))
476
+ return 0;
477
+ const nodeBin = process.platform === 'win32' ? 'node' : 'node';
478
+ // Strip prior ECC-tagged hooks
479
+ const existing = listHooks().filter(h => !h.command.includes(ECC_HOOK_TAG));
480
+ const eccHooks = [
481
+ {
482
+ event: 'PreToolUse',
483
+ match: 'bash',
484
+ command: `${nodeBin} "${hookScript}" block-no-verify ${ECC_HOOK_TAG}`,
485
+ blocking: true,
486
+ enabled: true,
487
+ timeout: 5000,
488
+ },
489
+ {
490
+ event: 'PreToolUse',
491
+ match: 'bash',
492
+ command: `${nodeBin} "${hookScript}" dev-server-tmux ${ECC_HOOK_TAG}`,
493
+ blocking: false,
494
+ enabled: true,
495
+ timeout: 5000,
496
+ },
497
+ {
498
+ event: 'PreToolUse',
499
+ match: 'read_file',
500
+ command: `${nodeBin} "${hookScript}" sensitive-file ${ECC_HOOK_TAG}`,
501
+ blocking: false,
502
+ enabled: true,
503
+ timeout: 5000,
504
+ },
505
+ {
506
+ event: 'PostToolUse',
507
+ match: 'edit_file',
508
+ command: `${nodeBin} "${hookScript}" console-log-warn ${ECC_HOOK_TAG}`,
509
+ blocking: false,
510
+ enabled: true,
511
+ timeout: 5000,
512
+ },
513
+ {
514
+ event: 'PostToolUse',
515
+ match: 'write_file',
516
+ command: `${nodeBin} "${hookScript}" console-log-warn ${ECC_HOOK_TAG}`,
517
+ blocking: false,
518
+ enabled: true,
519
+ timeout: 5000,
520
+ },
521
+ ];
522
+ saveHooksConfig({ hooks: [...existing, ...eccHooks] });
523
+ return eccHooks.length;
524
+ }
525
+ // ── Top-level install ───────────────────────────────────
526
+ export function installEcc(opts = {}) {
527
+ if (!eccResourcesAvailable()) {
528
+ return {
529
+ skills: 0, agents: 0, commands: 0, rules: 0, prompts: 0,
530
+ errors: [`ECC resources not found at ${RESOURCES_ROOT}`],
531
+ };
532
+ }
533
+ mkdirSync(getConfigDir(), { recursive: true });
534
+ const s = importSkills();
535
+ const a = importAgents();
536
+ const cp = importCommandsAndPrompts();
537
+ const r = importRules();
538
+ const hookCount = seedHooks();
539
+ const report = {
540
+ skills: s.count,
541
+ agents: a.count,
542
+ commands: cp.commands,
543
+ rules: r.count,
544
+ prompts: cp.prompts,
545
+ errors: [...s.errors, ...a.errors, ...cp.errors, ...r.errors],
546
+ };
547
+ saveEccState({
548
+ installedAt: new Date().toISOString(),
549
+ version: '1.0.0',
550
+ counts: {
551
+ skills: report.skills,
552
+ agents: report.agents,
553
+ commands: report.commands,
554
+ rules: report.rules,
555
+ prompts: report.prompts,
556
+ },
557
+ });
558
+ if (opts.verbose) {
559
+ console.log(chalk.cyan(`\n ECC install complete:`));
560
+ console.log(chalk.dim(` skills: ${report.skills}`));
561
+ console.log(chalk.dim(` agents: ${report.agents}`));
562
+ console.log(chalk.dim(` commands: ${report.commands}`));
563
+ console.log(chalk.dim(` prompts: ${report.prompts}`));
564
+ console.log(chalk.dim(` rules: ${report.rules} languages`));
565
+ console.log(chalk.dim(` hooks: ${hookCount} native hooks seeded`));
566
+ if (report.errors.length) {
567
+ console.log(chalk.yellow(`\n ${report.errors.length} errors:`));
568
+ for (const e of report.errors.slice(0, 5)) {
569
+ console.log(chalk.dim(` ${e}`));
570
+ }
571
+ }
572
+ }
573
+ return report;
574
+ }
575
+ // ── Status output ───────────────────────────────────────
576
+ export function printEccStatus() {
577
+ const state = loadEccState();
578
+ if (!state) {
579
+ console.log(chalk.yellow('\n ECC not yet installed.'));
580
+ console.log(chalk.dim(' Run /ecc-install to import skills, agents, commands, rules, and hooks.\n'));
581
+ return;
582
+ }
583
+ console.log(chalk.cyan('\n everything-claude-code integration'));
584
+ console.log(chalk.dim(` installed: ${state.installedAt}`));
585
+ console.log(chalk.dim(` version: ${state.version}`));
586
+ console.log(chalk.dim(` skills: ${state.counts.skills}`));
587
+ console.log(chalk.dim(` agents: ${state.counts.agents}`));
588
+ console.log(chalk.dim(` commands: ${state.counts.commands}`));
589
+ console.log(chalk.dim(` prompts: ${state.counts.prompts}`));
590
+ console.log(chalk.dim(` rules: ${state.counts.rules} languages`));
591
+ console.log(chalk.dim(`\n Commands available: /ecc, /ecc-install, /ecc-skills, /ecc-agents,`));
592
+ console.log(chalk.dim(` /ecc-commands, /ecc-<command-name>`));
593
+ console.log();
594
+ }
595
+ export function printEccSkills() {
596
+ const skills = listSkills().filter(s => s.id.startsWith(ECC_SKILL_ID_PREFIX) && !s.id.startsWith(`${ECC_SKILL_ID_PREFIX}agent-`));
597
+ console.log(chalk.cyan(`\n ECC skills: ${skills.length}`));
598
+ for (const s of skills.sort((a, b) => a.name.localeCompare(b.name))) {
599
+ const desc = s.description.length > 70 ? s.description.slice(0, 67) + '...' : s.description;
600
+ console.log(chalk.dim(` ${s.name.padEnd(28)} ${desc}`));
601
+ }
602
+ console.log();
603
+ }
604
+ export function printEccAgents() {
605
+ const agents = listSkills().filter(s => s.id.startsWith(`${ECC_SKILL_ID_PREFIX}agent-`));
606
+ console.log(chalk.cyan(`\n ECC agents: ${agents.length}`));
607
+ for (const a of agents.sort((x, y) => x.name.localeCompare(y.name))) {
608
+ const desc = a.description.length > 70 ? a.description.slice(0, 67) + '...' : a.description;
609
+ console.log(chalk.dim(` ${a.name.padEnd(28)} ${desc}`));
610
+ }
611
+ console.log();
612
+ }
613
+ export function printEccCommandList() {
614
+ const cmds = listEccCommands();
615
+ console.log(chalk.cyan(`\n ECC commands: ${cmds.length}`));
616
+ for (const c of cmds) {
617
+ console.log(chalk.dim(` /ecc-${c}`));
618
+ }
619
+ console.log();
620
+ }
621
+ /**
622
+ * Match the user's free-form text against ECC skill triggers and return the
623
+ * top-matching skill prompt body, or null if nothing fires. Used to auto-inject
624
+ * skill content into the system prompt — bumps relevance for that turn.
625
+ */
626
+ export function findEccSkillForQuery(query) {
627
+ if (!query)
628
+ return null;
629
+ const q = query.toLowerCase();
630
+ const candidates = listSkills().filter(s => s.id.startsWith(ECC_SKILL_ID_PREFIX));
631
+ let best = null;
632
+ for (const s of candidates) {
633
+ let score = 0;
634
+ for (const t of s.triggers) {
635
+ if (q.includes(t.toLowerCase()))
636
+ score += t.length;
637
+ }
638
+ if (score > 0 && (!best || score > best.score)) {
639
+ best = { skill: s, score };
640
+ }
641
+ }
642
+ return best?.skill ?? null;
643
+ }
644
+ //# sourceMappingURL=ecc.js.map