@ketecode/loom 0.0.0-stage → 0.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 (656) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +516 -2
  3. package/agents/accessibility-reviewer/AGENT.md +70 -0
  4. package/agents/accessibility-reviewer/evals/triggers.json +15 -0
  5. package/agents/agent-evaluator/AGENT.md +74 -0
  6. package/agents/agent-evaluator/evals/triggers.json +15 -0
  7. package/agents/architect/AGENT.md +82 -0
  8. package/agents/architect/evals/triggers.json +17 -0
  9. package/agents/billing-reviewer/AGENT.md +68 -0
  10. package/agents/billing-reviewer/evals/triggers.json +16 -0
  11. package/agents/code-reviewer/AGENT.md +51 -0
  12. package/agents/code-reviewer/evals/triggers.json +14 -0
  13. package/agents/code-simplifier/AGENT.md +74 -0
  14. package/agents/code-simplifier/evals/triggers.json +15 -0
  15. package/agents/cpp-build-resolver/AGENT.md +73 -0
  16. package/agents/cpp-build-resolver/evals/triggers.json +16 -0
  17. package/agents/cpp-reviewer/AGENT.md +59 -0
  18. package/agents/cpp-reviewer/evals/triggers.json +16 -0
  19. package/agents/csharp-build-resolver/AGENT.md +84 -0
  20. package/agents/csharp-build-resolver/evals/triggers.json +17 -0
  21. package/agents/csharp-reviewer/AGENT.md +88 -0
  22. package/agents/csharp-reviewer/evals/triggers.json +17 -0
  23. package/agents/dart-build-resolver/AGENT.md +80 -0
  24. package/agents/dart-build-resolver/evals/triggers.json +17 -0
  25. package/agents/dart-reviewer/AGENT.md +66 -0
  26. package/agents/dart-reviewer/evals/triggers.json +17 -0
  27. package/agents/database-reviewer/AGENT.md +72 -0
  28. package/agents/database-reviewer/evals/triggers.json +16 -0
  29. package/agents/django-reviewer/AGENT.md +61 -0
  30. package/agents/django-reviewer/evals/triggers.json +15 -0
  31. package/agents/doc-updater/AGENT.md +92 -0
  32. package/agents/doc-updater/evals/triggers.json +14 -0
  33. package/agents/e2e-runner/AGENT.md +88 -0
  34. package/agents/e2e-runner/evals/triggers.json +16 -0
  35. package/agents/fastapi-reviewer/AGENT.md +63 -0
  36. package/agents/fastapi-reviewer/evals/triggers.json +15 -0
  37. package/agents/flutter-reviewer/AGENT.md +70 -0
  38. package/agents/flutter-reviewer/evals/triggers.json +15 -0
  39. package/agents/fsharp-build-resolver/AGENT.md +81 -0
  40. package/agents/fsharp-build-resolver/evals/triggers.json +15 -0
  41. package/agents/fsharp-reviewer/AGENT.md +77 -0
  42. package/agents/fsharp-reviewer/evals/triggers.json +15 -0
  43. package/agents/go-build-resolver/AGENT.md +85 -0
  44. package/agents/go-build-resolver/evals/triggers.json +16 -0
  45. package/agents/go-reviewer/AGENT.md +66 -0
  46. package/agents/go-reviewer/evals/triggers.json +16 -0
  47. package/agents/healthcare-reviewer/AGENT.md +65 -0
  48. package/agents/healthcare-reviewer/evals/triggers.json +16 -0
  49. package/agents/i18n-reviewer/AGENT.md +64 -0
  50. package/agents/i18n-reviewer/evals/triggers.json +16 -0
  51. package/agents/infra-reviewer/AGENT.md +93 -0
  52. package/agents/infra-reviewer/evals/triggers.json +15 -0
  53. package/agents/java-build-resolver/AGENT.md +82 -0
  54. package/agents/java-build-resolver/evals/triggers.json +16 -0
  55. package/agents/java-reviewer/AGENT.md +64 -0
  56. package/agents/java-reviewer/evals/triggers.json +16 -0
  57. package/agents/kotlin-build-resolver/AGENT.md +89 -0
  58. package/agents/kotlin-build-resolver/evals/triggers.json +16 -0
  59. package/agents/kotlin-reviewer/AGENT.md +75 -0
  60. package/agents/kotlin-reviewer/evals/triggers.json +16 -0
  61. package/agents/mle-reviewer/AGENT.md +68 -0
  62. package/agents/mle-reviewer/evals/triggers.json +16 -0
  63. package/agents/network-config-reviewer/AGENT.md +68 -0
  64. package/agents/network-config-reviewer/evals/triggers.json +17 -0
  65. package/agents/payments-reviewer/AGENT.md +64 -0
  66. package/agents/payments-reviewer/evals/triggers.json +16 -0
  67. package/agents/performance-optimizer/AGENT.md +86 -0
  68. package/agents/performance-optimizer/evals/triggers.json +16 -0
  69. package/agents/perl-build-resolver/AGENT.md +84 -0
  70. package/agents/perl-build-resolver/evals/triggers.json +15 -0
  71. package/agents/perl-reviewer/AGENT.md +77 -0
  72. package/agents/perl-reviewer/evals/triggers.json +15 -0
  73. package/agents/php-build-resolver/AGENT.md +88 -0
  74. package/agents/php-build-resolver/evals/triggers.json +16 -0
  75. package/agents/php-reviewer/AGENT.md +61 -0
  76. package/agents/php-reviewer/evals/triggers.json +15 -0
  77. package/agents/planner/AGENT.md +75 -0
  78. package/agents/planner/evals/triggers.json +17 -0
  79. package/agents/pr-test-analyzer/AGENT.md +82 -0
  80. package/agents/pr-test-analyzer/evals/triggers.json +16 -0
  81. package/agents/python-build-resolver/AGENT.md +87 -0
  82. package/agents/python-build-resolver/evals/triggers.json +16 -0
  83. package/agents/python-reviewer/AGENT.md +63 -0
  84. package/agents/python-reviewer/evals/triggers.json +15 -0
  85. package/agents/rag-reviewer/AGENT.md +71 -0
  86. package/agents/rag-reviewer/evals/triggers.json +16 -0
  87. package/agents/react-reviewer/AGENT.md +70 -0
  88. package/agents/react-reviewer/evals/triggers.json +16 -0
  89. package/agents/refactor-cleaner/AGENT.md +82 -0
  90. package/agents/refactor-cleaner/evals/triggers.json +16 -0
  91. package/agents/ruby-build-resolver/AGENT.md +93 -0
  92. package/agents/ruby-build-resolver/evals/triggers.json +16 -0
  93. package/agents/ruby-reviewer/AGENT.md +62 -0
  94. package/agents/ruby-reviewer/evals/triggers.json +16 -0
  95. package/agents/rust-build-resolver/AGENT.md +98 -0
  96. package/agents/rust-build-resolver/evals/triggers.json +16 -0
  97. package/agents/rust-reviewer/AGENT.md +78 -0
  98. package/agents/rust-reviewer/evals/triggers.json +15 -0
  99. package/agents/security-reviewer/AGENT.md +84 -0
  100. package/agents/security-reviewer/evals/triggers.json +16 -0
  101. package/agents/silent-failure-hunter/AGENT.md +66 -0
  102. package/agents/silent-failure-hunter/evals/triggers.json +16 -0
  103. package/agents/solidity-reviewer/AGENT.md +70 -0
  104. package/agents/solidity-reviewer/evals/triggers.json +16 -0
  105. package/agents/swift-build-resolver/AGENT.md +85 -0
  106. package/agents/swift-build-resolver/evals/triggers.json +15 -0
  107. package/agents/swift-reviewer/AGENT.md +75 -0
  108. package/agents/swift-reviewer/evals/triggers.json +15 -0
  109. package/agents/typescript-build-resolver/AGENT.md +76 -0
  110. package/agents/typescript-build-resolver/evals/triggers.json +15 -0
  111. package/agents/typescript-reviewer/AGENT.md +76 -0
  112. package/agents/typescript-reviewer/evals/triggers.json +15 -0
  113. package/agents/vue-reviewer/AGENT.md +75 -0
  114. package/agents/vue-reviewer/evals/triggers.json +15 -0
  115. package/bin/kete-loom.js +683 -0
  116. package/commands/aside.md +19 -0
  117. package/commands/build-fix.md +26 -0
  118. package/commands/campaign.md +19 -0
  119. package/commands/changelog.md +36 -0
  120. package/commands/checkpoint.md +26 -0
  121. package/commands/codemap.md +25 -0
  122. package/commands/commit.md +26 -0
  123. package/commands/deps-audit.md +32 -0
  124. package/commands/epic-decompose.md +21 -0
  125. package/commands/explain.md +37 -0
  126. package/commands/feature-dev.md +22 -0
  127. package/commands/hookify.md +23 -0
  128. package/commands/i18n-check.md +39 -0
  129. package/commands/issue-sync.md +22 -0
  130. package/commands/jira.md +19 -0
  131. package/commands/learn.md +23 -0
  132. package/commands/loom.md +21 -0
  133. package/commands/loop.md +20 -0
  134. package/commands/model-route.md +27 -0
  135. package/commands/onboard.md +35 -0
  136. package/commands/orchestrate.md +21 -0
  137. package/commands/plan-feature.md +36 -0
  138. package/commands/postmortem.md +42 -0
  139. package/commands/pr-describe.md +39 -0
  140. package/commands/pr.md +22 -0
  141. package/commands/prd.md +41 -0
  142. package/commands/project-init.md +21 -0
  143. package/commands/refactor-clean.md +24 -0
  144. package/commands/resume-session.md +22 -0
  145. package/commands/review.md +35 -0
  146. package/commands/save-session.md +37 -0
  147. package/commands/security-scan.md +32 -0
  148. package/commands/sessions.md +21 -0
  149. package/commands/skill-create.md +21 -0
  150. package/commands/tdd.md +36 -0
  151. package/commands/test-coverage.md +24 -0
  152. package/commands/threat-model.md +36 -0
  153. package/commands/update-docs.md +25 -0
  154. package/commands/verify.md +21 -0
  155. package/hooks/block-dangerous-commands/HOOK.md +30 -0
  156. package/hooks/block-dangerous-commands/cases.json +50 -0
  157. package/hooks/block-dangerous-commands/hook.js +75 -0
  158. package/hooks/block-no-verify/HOOK.md +25 -0
  159. package/hooks/block-no-verify/cases.json +30 -0
  160. package/hooks/block-no-verify/hook.js +43 -0
  161. package/hooks/format-on-edit/HOOK.md +24 -0
  162. package/hooks/format-on-edit/hook.js +89 -0
  163. package/hooks/guard-secrets/HOOK.md +19 -0
  164. package/hooks/guard-secrets/cases.json +110 -0
  165. package/hooks/guard-secrets/hook.js +44 -0
  166. package/hooks/load-session-context/HOOK.md +20 -0
  167. package/hooks/load-session-context/hook.js +56 -0
  168. package/hooks/protect-files/HOOK.md +24 -0
  169. package/hooks/protect-files/cases.json +27 -0
  170. package/hooks/protect-files/hook.js +39 -0
  171. package/hooks/run.js +132 -0
  172. package/mcp/atlassian.json +11 -0
  173. package/mcp/chrome-devtools.json +18 -0
  174. package/mcp/cloudflare.json +11 -0
  175. package/mcp/context7.json +11 -0
  176. package/mcp/fetch.json +17 -0
  177. package/mcp/figma.json +11 -0
  178. package/mcp/git.json +17 -0
  179. package/mcp/github.json +11 -0
  180. package/mcp/linear.json +11 -0
  181. package/mcp/memory.json +18 -0
  182. package/mcp/notion.json +11 -0
  183. package/mcp/playwright.json +18 -0
  184. package/mcp/sentry.json +11 -0
  185. package/mcp/stripe.json +11 -0
  186. package/mcp/supabase.json +11 -0
  187. package/mcp/time.json +17 -0
  188. package/package.json +59 -4
  189. package/profiles.json +165 -0
  190. package/rules/common.md +21 -0
  191. package/rules/cpp.md +29 -0
  192. package/rules/csharp.md +25 -0
  193. package/rules/dart.md +29 -0
  194. package/rules/fsharp.md +28 -0
  195. package/rules/git.md +26 -0
  196. package/rules/go.md +29 -0
  197. package/rules/java.md +29 -0
  198. package/rules/kotlin.md +27 -0
  199. package/rules/perl.md +26 -0
  200. package/rules/php.md +28 -0
  201. package/rules/python.md +30 -0
  202. package/rules/ruby.md +27 -0
  203. package/rules/rust.md +26 -0
  204. package/rules/security.md +29 -0
  205. package/rules/swift.md +28 -0
  206. package/rules/testing.md +25 -0
  207. package/rules/typescript.md +29 -0
  208. package/schemas/skill-frontmatter.schema.json +52 -0
  209. package/scripts/lib/audit.js +96 -0
  210. package/scripts/lib/frontmatter.js +65 -0
  211. package/scripts/lib/hooks-install.js +292 -0
  212. package/scripts/lib/i18n.js +51 -0
  213. package/scripts/lib/inventory.js +85 -0
  214. package/scripts/lib/lint.js +158 -0
  215. package/scripts/lib/mcp-install.js +284 -0
  216. package/scripts/lib/profiles.js +128 -0
  217. package/scripts/lib/render.js +86 -0
  218. package/scripts/lib/rules-install.js +118 -0
  219. package/scripts/lib/skills.js +86 -0
  220. package/scripts/lib/targets.js +113 -0
  221. package/skills/accounting-api-integration/SKILL.md +81 -0
  222. package/skills/accounting-api-integration/evals/triggers.json +17 -0
  223. package/skills/accounting-api-integration/references/quickbooks.md +48 -0
  224. package/skills/accounting-api-integration/references/xero.md +45 -0
  225. package/skills/agent-harness-design/SKILL.md +125 -0
  226. package/skills/agent-harness-design/evals/triggers.json +21 -0
  227. package/skills/ai-media-generation/SKILL.md +89 -0
  228. package/skills/ai-media-generation/evals/triggers.json +16 -0
  229. package/skills/ai-media-generation/references/job-pipeline.md +76 -0
  230. package/skills/angular-patterns/SKILL.md +122 -0
  231. package/skills/angular-patterns/evals/triggers.json +16 -0
  232. package/skills/api-design/SKILL.md +96 -0
  233. package/skills/api-design/evals/triggers.json +18 -0
  234. package/skills/api-design/references/conventions.md +133 -0
  235. package/skills/architecture-decision-records/SKILL.md +98 -0
  236. package/skills/architecture-decision-records/evals/triggers.json +17 -0
  237. package/skills/article-writing/SKILL.md +108 -0
  238. package/skills/article-writing/evals/triggers.json +16 -0
  239. package/skills/aspnet-core-patterns/SKILL.md +149 -0
  240. package/skills/aspnet-core-patterns/evals/triggers.json +16 -0
  241. package/skills/auth-patterns/SKILL.md +104 -0
  242. package/skills/auth-patterns/evals/triggers.json +17 -0
  243. package/skills/auth-patterns/references/oauth-oidc.md +64 -0
  244. package/skills/autonomous-loops/SKILL.md +99 -0
  245. package/skills/autonomous-loops/evals/triggers.json +14 -0
  246. package/skills/aws-patterns/SKILL.md +91 -0
  247. package/skills/aws-patterns/evals/triggers.json +16 -0
  248. package/skills/azure-patterns/SKILL.md +86 -0
  249. package/skills/azure-patterns/evals/triggers.json +16 -0
  250. package/skills/benchmark-methodology/SKILL.md +88 -0
  251. package/skills/benchmark-methodology/evals/triggers.json +16 -0
  252. package/skills/benchmark-methodology/references/language-harnesses.md +130 -0
  253. package/skills/bgp-diagnostics/SKILL.md +84 -0
  254. package/skills/bgp-diagnostics/evals/triggers.json +18 -0
  255. package/skills/bgp-diagnostics/references/bgp-reference.md +81 -0
  256. package/skills/bioinformatics-workflows/SKILL.md +108 -0
  257. package/skills/bioinformatics-workflows/evals/triggers.json +16 -0
  258. package/skills/bioinformatics-workflows/references/commands.md +71 -0
  259. package/skills/bioinformatics-workflows/references/formats.md +24 -0
  260. package/skills/bioinformatics-workflows/references/workflow-managers.md +107 -0
  261. package/skills/brand-voice/SKILL.md +112 -0
  262. package/skills/brand-voice/evals/triggers.json +16 -0
  263. package/skills/browser-qa/SKILL.md +99 -0
  264. package/skills/browser-qa/evals/triggers.json +16 -0
  265. package/skills/bun-runtime/SKILL.md +93 -0
  266. package/skills/bun-runtime/evals/triggers.json +16 -0
  267. package/skills/cisco-ios-config/SKILL.md +101 -0
  268. package/skills/cisco-ios-config/evals/triggers.json +18 -0
  269. package/skills/cisco-ios-config/references/verification.md +63 -0
  270. package/skills/clickhouse-patterns/SKILL.md +101 -0
  271. package/skills/clickhouse-patterns/evals/triggers.json +18 -0
  272. package/skills/clickhouse-patterns/references/engines-and-views.md +138 -0
  273. package/skills/clinical-decision-support/SKILL.md +90 -0
  274. package/skills/clinical-decision-support/evals/triggers.json +16 -0
  275. package/skills/clinical-decision-support/references/cds-hooks.md +76 -0
  276. package/skills/clinical-decision-support/references/regulatory-boundaries.md +37 -0
  277. package/skills/code-tour/SKILL.md +97 -0
  278. package/skills/code-tour/evals/triggers.json +14 -0
  279. package/skills/codebase-onboarding/SKILL.md +112 -0
  280. package/skills/codebase-onboarding/evals/triggers.json +14 -0
  281. package/skills/cold-outreach-sequences/SKILL.md +90 -0
  282. package/skills/cold-outreach-sequences/evals/triggers.json +16 -0
  283. package/skills/content-repurposing/SKILL.md +82 -0
  284. package/skills/content-repurposing/evals/triggers.json +16 -0
  285. package/skills/context-budget/SKILL.md +84 -0
  286. package/skills/context-budget/evals/triggers.json +14 -0
  287. package/skills/conventional-commits/SKILL.md +144 -0
  288. package/skills/conventional-commits/evals/triggers.json +16 -0
  289. package/skills/cpp-patterns/SKILL.md +129 -0
  290. package/skills/cpp-patterns/evals/triggers.json +16 -0
  291. package/skills/cpp-patterns/references/cmake-and-tooling.md +88 -0
  292. package/skills/cpp-patterns/references/undefined-behaviour.md +75 -0
  293. package/skills/cpp-testing/SKILL.md +131 -0
  294. package/skills/cpp-testing/evals/triggers.json +16 -0
  295. package/skills/csharp-patterns/SKILL.md +110 -0
  296. package/skills/csharp-patterns/evals/triggers.json +17 -0
  297. package/skills/csharp-patterns/references/analyzers.md +59 -0
  298. package/skills/csharp-testing/SKILL.md +85 -0
  299. package/skills/csharp-testing/evals/triggers.json +17 -0
  300. package/skills/csharp-testing/references/integration-tests.md +77 -0
  301. package/skills/css-architecture/SKILL.md +143 -0
  302. package/skills/css-architecture/evals/triggers.json +16 -0
  303. package/skills/customs-trade-compliance/SKILL.md +95 -0
  304. package/skills/customs-trade-compliance/evals/triggers.json +16 -0
  305. package/skills/customs-trade-compliance/references/incoterms-2020.md +42 -0
  306. package/skills/dart-patterns/SKILL.md +157 -0
  307. package/skills/dart-patterns/evals/triggers.json +18 -0
  308. package/skills/dart-testing/SKILL.md +164 -0
  309. package/skills/dart-testing/evals/triggers.json +18 -0
  310. package/skills/data-analysis-notebooks/SKILL.md +113 -0
  311. package/skills/data-analysis-notebooks/evals/triggers.json +16 -0
  312. package/skills/data-analysis-notebooks/references/dataframe-pitfalls.md +49 -0
  313. package/skills/data-analysis-notebooks/references/statistics-pitfalls.md +24 -0
  314. package/skills/data-pipelines/SKILL.md +90 -0
  315. package/skills/data-pipelines/evals/triggers.json +16 -0
  316. package/skills/data-pipelines/references/orchestrators.md +64 -0
  317. package/skills/database-migrations/SKILL.md +102 -0
  318. package/skills/database-migrations/evals/triggers.json +16 -0
  319. package/skills/database-migrations/references/recipes.md +98 -0
  320. package/skills/database-migrations/references/tools.md +21 -0
  321. package/skills/dbt-patterns/SKILL.md +96 -0
  322. package/skills/dbt-patterns/evals/triggers.json +16 -0
  323. package/skills/dbt-patterns/references/yaml-examples.md +114 -0
  324. package/skills/deep-research/SKILL.md +107 -0
  325. package/skills/deep-research/evals/triggers.json +14 -0
  326. package/skills/dependency-audit/SKILL.md +103 -0
  327. package/skills/dependency-audit/evals/triggers.json +16 -0
  328. package/skills/dependency-audit/references/tools.md +124 -0
  329. package/skills/deployment-strategies/SKILL.md +108 -0
  330. package/skills/deployment-strategies/evals/triggers.json +16 -0
  331. package/skills/django-patterns/SKILL.md +123 -0
  332. package/skills/django-patterns/evals/triggers.json +16 -0
  333. package/skills/docker-patterns/SKILL.md +132 -0
  334. package/skills/docker-patterns/evals/triggers.json +16 -0
  335. package/skills/docker-patterns/references/dockerfiles.md +105 -0
  336. package/skills/e2e-testing-playwright/SKILL.md +94 -0
  337. package/skills/e2e-testing-playwright/evals/triggers.json +16 -0
  338. package/skills/e2e-testing-playwright/references/config-and-ci.md +129 -0
  339. package/skills/ef-core-patterns/SKILL.md +117 -0
  340. package/skills/ef-core-patterns/evals/triggers.json +16 -0
  341. package/skills/email-deliverability/SKILL.md +99 -0
  342. package/skills/email-deliverability/evals/triggers.json +17 -0
  343. package/skills/email-deliverability/references/dns-records.md +85 -0
  344. package/skills/error-handling-patterns/SKILL.md +112 -0
  345. package/skills/error-handling-patterns/evals/triggers.json +18 -0
  346. package/skills/error-handling-patterns/references/language-idioms.md +49 -0
  347. package/skills/estimation-and-scoping/SKILL.md +97 -0
  348. package/skills/estimation-and-scoping/evals/triggers.json +14 -0
  349. package/skills/evm-dapp-integration/SKILL.md +73 -0
  350. package/skills/evm-dapp-integration/evals/triggers.json +16 -0
  351. package/skills/fastapi-patterns/SKILL.md +127 -0
  352. package/skills/fastapi-patterns/evals/triggers.json +16 -0
  353. package/skills/ffmpeg-media-processing/SKILL.md +94 -0
  354. package/skills/ffmpeg-media-processing/evals/triggers.json +16 -0
  355. package/skills/ffmpeg-media-processing/references/recipes.md +112 -0
  356. package/skills/fhir-integration/SKILL.md +81 -0
  357. package/skills/fhir-integration/evals/triggers.json +16 -0
  358. package/skills/fhir-integration/references/search-and-bundles.md +73 -0
  359. package/skills/fhir-integration/references/smart-on-fhir.md +50 -0
  360. package/skills/flask-patterns/SKILL.md +132 -0
  361. package/skills/flask-patterns/evals/triggers.json +16 -0
  362. package/skills/flutter-patterns/SKILL.md +99 -0
  363. package/skills/flutter-patterns/evals/triggers.json +16 -0
  364. package/skills/flutterwave-integration/SKILL.md +75 -0
  365. package/skills/flutterwave-integration/evals/triggers.json +16 -0
  366. package/skills/flutterwave-integration/references/api.md +73 -0
  367. package/skills/flutterwave-integration/references/webhooks.md +49 -0
  368. package/skills/fsharp-patterns/SKILL.md +133 -0
  369. package/skills/fsharp-patterns/evals/triggers.json +16 -0
  370. package/skills/fsharp-testing/SKILL.md +136 -0
  371. package/skills/fsharp-testing/evals/triggers.json +16 -0
  372. package/skills/gcp-patterns/SKILL.md +86 -0
  373. package/skills/gcp-patterns/evals/triggers.json +16 -0
  374. package/skills/git-workflow/SKILL.md +114 -0
  375. package/skills/git-workflow/evals/triggers.json +16 -0
  376. package/skills/github-actions-ci/SKILL.md +117 -0
  377. package/skills/github-actions-ci/evals/triggers.json +16 -0
  378. package/skills/github-api-automation/SKILL.md +101 -0
  379. package/skills/github-api-automation/evals/triggers.json +17 -0
  380. package/skills/go-patterns/SKILL.md +118 -0
  381. package/skills/go-patterns/evals/triggers.json +17 -0
  382. package/skills/go-patterns/references/concurrency.md +162 -0
  383. package/skills/go-testing/SKILL.md +154 -0
  384. package/skills/go-testing/evals/triggers.json +17 -0
  385. package/skills/hexagonal-architecture/SKILL.md +89 -0
  386. package/skills/hexagonal-architecture/evals/triggers.json +17 -0
  387. package/skills/hipaa-compliance/SKILL.md +91 -0
  388. package/skills/hipaa-compliance/evals/triggers.json +18 -0
  389. package/skills/hipaa-compliance/references/safeguards.md +68 -0
  390. package/skills/hl7v2-integration/SKILL.md +69 -0
  391. package/skills/hl7v2-integration/evals/triggers.json +16 -0
  392. package/skills/hl7v2-integration/references/messages-and-ack.md +78 -0
  393. package/skills/homelab-network-setup/SKILL.md +125 -0
  394. package/skills/homelab-network-setup/evals/triggers.json +18 -0
  395. package/skills/homelab-network-setup/references/firewall-and-dns.md +45 -0
  396. package/skills/homelab-network-setup/references/wireguard.md +72 -0
  397. package/skills/hubtel-integration/SKILL.md +77 -0
  398. package/skills/hubtel-integration/evals/triggers.json +16 -0
  399. package/skills/hubtel-integration/references/api.md +92 -0
  400. package/skills/i18n-localization/SKILL.md +114 -0
  401. package/skills/i18n-localization/evals/triggers.json +16 -0
  402. package/skills/i18n-localization/references/libraries.md +52 -0
  403. package/skills/incident-postmortem/SKILL.md +118 -0
  404. package/skills/incident-postmortem/evals/triggers.json +14 -0
  405. package/skills/inventory-demand-planning/SKILL.md +89 -0
  406. package/skills/inventory-demand-planning/evals/triggers.json +16 -0
  407. package/skills/inventory-demand-planning/references/formulas.md +62 -0
  408. package/skills/investor-materials/SKILL.md +127 -0
  409. package/skills/investor-materials/evals/triggers.json +16 -0
  410. package/skills/investor-materials/references/templates.md +74 -0
  411. package/skills/invoicing-and-tax/SKILL.md +84 -0
  412. package/skills/invoicing-and-tax/evals/triggers.json +17 -0
  413. package/skills/invoicing-and-tax/references/e-invoicing.md +24 -0
  414. package/skills/invoicing-and-tax/references/invoice-fields.md +38 -0
  415. package/skills/invoicing-and-tax/references/rounding.md +26 -0
  416. package/skills/java-patterns/SKILL.md +141 -0
  417. package/skills/java-patterns/evals/triggers.json +17 -0
  418. package/skills/java-testing/SKILL.md +142 -0
  419. package/skills/java-testing/evals/triggers.json +16 -0
  420. package/skills/jetpack-compose-patterns/SKILL.md +88 -0
  421. package/skills/jetpack-compose-patterns/evals/triggers.json +16 -0
  422. package/skills/jira-integration/SKILL.md +92 -0
  423. package/skills/jira-integration/evals/triggers.json +17 -0
  424. package/skills/jpa-hibernate/SKILL.md +92 -0
  425. package/skills/jpa-hibernate/evals/triggers.json +18 -0
  426. package/skills/jpa-hibernate/references/mapping.md +99 -0
  427. package/skills/kotlin-multiplatform/SKILL.md +97 -0
  428. package/skills/kotlin-multiplatform/evals/triggers.json +16 -0
  429. package/skills/kotlin-patterns/SKILL.md +128 -0
  430. package/skills/kotlin-patterns/evals/triggers.json +16 -0
  431. package/skills/kotlin-testing/SKILL.md +117 -0
  432. package/skills/kotlin-testing/evals/triggers.json +16 -0
  433. package/skills/ktor-patterns/SKILL.md +135 -0
  434. package/skills/ktor-patterns/evals/triggers.json +18 -0
  435. package/skills/kubernetes-patterns/SKILL.md +106 -0
  436. package/skills/kubernetes-patterns/evals/triggers.json +16 -0
  437. package/skills/kubernetes-patterns/references/manifests.md +115 -0
  438. package/skills/laravel-patterns/SKILL.md +136 -0
  439. package/skills/laravel-patterns/evals/triggers.json +16 -0
  440. package/skills/linear-integration/SKILL.md +98 -0
  441. package/skills/linear-integration/evals/triggers.json +17 -0
  442. package/skills/literature-review/SKILL.md +109 -0
  443. package/skills/literature-review/evals/triggers.json +16 -0
  444. package/skills/literature-review/references/appraisal.md +41 -0
  445. package/skills/literature-review/references/search-sources.md +39 -0
  446. package/skills/llm-app-patterns/SKILL.md +97 -0
  447. package/skills/llm-app-patterns/evals/triggers.json +17 -0
  448. package/skills/llm-evals/SKILL.md +98 -0
  449. package/skills/llm-evals/evals/triggers.json +16 -0
  450. package/skills/llm-evals/references/graders.md +66 -0
  451. package/skills/logistics-exception-management/SKILL.md +90 -0
  452. package/skills/logistics-exception-management/evals/triggers.json +16 -0
  453. package/skills/manim-animations/SKILL.md +106 -0
  454. package/skills/manim-animations/evals/triggers.json +16 -0
  455. package/skills/market-research/SKILL.md +117 -0
  456. package/skills/market-research/evals/triggers.json +16 -0
  457. package/skills/marketing-campaign/SKILL.md +111 -0
  458. package/skills/marketing-campaign/evals/triggers.json +16 -0
  459. package/skills/mcp-server-patterns/SKILL.md +127 -0
  460. package/skills/mcp-server-patterns/evals/triggers.json +18 -0
  461. package/skills/mcp-server-patterns/references/protocol-notes.md +68 -0
  462. package/skills/metrics-dashboards/SKILL.md +113 -0
  463. package/skills/metrics-dashboards/evals/triggers.json +16 -0
  464. package/skills/ml-experiment-workflow/SKILL.md +125 -0
  465. package/skills/ml-experiment-workflow/evals/triggers.json +16 -0
  466. package/skills/mongodb-patterns/SKILL.md +115 -0
  467. package/skills/mongodb-patterns/evals/triggers.json +18 -0
  468. package/skills/mongodb-patterns/references/indexes-and-aggregation.md +94 -0
  469. package/skills/mpesa-daraja-integration/SKILL.md +70 -0
  470. package/skills/mpesa-daraja-integration/evals/triggers.json +16 -0
  471. package/skills/mpesa-daraja-integration/references/api.md +103 -0
  472. package/skills/mpesa-daraja-integration/references/callbacks.md +74 -0
  473. package/skills/mtn-momo-integration/SKILL.md +66 -0
  474. package/skills/mtn-momo-integration/evals/triggers.json +16 -0
  475. package/skills/mtn-momo-integration/references/api.md +153 -0
  476. package/skills/mtn-momo-integration/references/callbacks.md +92 -0
  477. package/skills/mtn-momo-integration/references/sandbox-and-go-live.md +51 -0
  478. package/skills/mtn-momo-integration/scripts/sandbox-setup.js +79 -0
  479. package/skills/mysql-patterns/SKILL.md +96 -0
  480. package/skills/mysql-patterns/evals/triggers.json +16 -0
  481. package/skills/mysql-patterns/references/explain.md +34 -0
  482. package/skills/mysql-patterns/references/online-ddl.md +41 -0
  483. package/skills/nestjs-patterns/SKILL.md +114 -0
  484. package/skills/nestjs-patterns/evals/triggers.json +16 -0
  485. package/skills/nestjs-patterns/references/testing.md +68 -0
  486. package/skills/network-automation/SKILL.md +107 -0
  487. package/skills/network-automation/evals/triggers.json +18 -0
  488. package/skills/network-automation/references/tool-snippets.md +153 -0
  489. package/skills/network-troubleshooting/SKILL.md +152 -0
  490. package/skills/network-troubleshooting/evals/triggers.json +18 -0
  491. package/skills/network-troubleshooting/references/commands.md +33 -0
  492. package/skills/nextjs-patterns/SKILL.md +150 -0
  493. package/skills/nextjs-patterns/evals/triggers.json +18 -0
  494. package/skills/nextjs-patterns/references/caching.md +87 -0
  495. package/skills/node-backend-patterns/SKILL.md +103 -0
  496. package/skills/node-backend-patterns/evals/triggers.json +16 -0
  497. package/skills/node-backend-patterns/references/templates.md +122 -0
  498. package/skills/nuxt-patterns/SKILL.md +103 -0
  499. package/skills/nuxt-patterns/evals/triggers.json +16 -0
  500. package/skills/parallel-agent-orchestration/SKILL.md +106 -0
  501. package/skills/parallel-agent-orchestration/evals/triggers.json +14 -0
  502. package/skills/paystack-integration/SKILL.md +59 -0
  503. package/skills/paystack-integration/evals/triggers.json +14 -0
  504. package/skills/paystack-integration/references/api.md +73 -0
  505. package/skills/paystack-integration/references/mobile-money.md +50 -0
  506. package/skills/paystack-integration/references/webhooks.md +80 -0
  507. package/skills/pci-dss-scope/SKILL.md +103 -0
  508. package/skills/pci-dss-scope/evals/triggers.json +18 -0
  509. package/skills/pci-dss-scope/references/saq-and-data.md +36 -0
  510. package/skills/perl-patterns/SKILL.md +135 -0
  511. package/skills/perl-patterns/evals/triggers.json +18 -0
  512. package/skills/perl-testing/SKILL.md +126 -0
  513. package/skills/perl-testing/evals/triggers.json +17 -0
  514. package/skills/php-patterns/SKILL.md +116 -0
  515. package/skills/php-patterns/evals/triggers.json +16 -0
  516. package/skills/php-testing/SKILL.md +123 -0
  517. package/skills/php-testing/evals/triggers.json +16 -0
  518. package/skills/postgres-patterns/SKILL.md +103 -0
  519. package/skills/postgres-patterns/evals/triggers.json +17 -0
  520. package/skills/postgres-patterns/references/explain.md +59 -0
  521. package/skills/postgres-patterns/references/locks.md +53 -0
  522. package/skills/prd-writing/SKILL.md +124 -0
  523. package/skills/prd-writing/evals/triggers.json +15 -0
  524. package/skills/prisma-patterns/SKILL.md +83 -0
  525. package/skills/prisma-patterns/evals/triggers.json +18 -0
  526. package/skills/prisma-patterns/references/prisma-8.md +46 -0
  527. package/skills/prisma-patterns/references/workflow.md +50 -0
  528. package/skills/privacy-compliance/SKILL.md +121 -0
  529. package/skills/privacy-compliance/evals/triggers.json +18 -0
  530. package/skills/privacy-compliance/references/jurisdictions.md +51 -0
  531. package/skills/production-scheduling/SKILL.md +98 -0
  532. package/skills/production-scheduling/evals/triggers.json +16 -0
  533. package/skills/prompt-engineering/SKILL.md +92 -0
  534. package/skills/prompt-engineering/evals/triggers.json +16 -0
  535. package/skills/python-patterns/SKILL.md +107 -0
  536. package/skills/python-patterns/evals/triggers.json +16 -0
  537. package/skills/python-patterns/references/tooling.md +101 -0
  538. package/skills/python-testing/SKILL.md +126 -0
  539. package/skills/python-testing/evals/triggers.json +16 -0
  540. package/skills/python-testing/references/mocking.md +71 -0
  541. package/skills/pytorch-patterns/SKILL.md +139 -0
  542. package/skills/pytorch-patterns/evals/triggers.json +16 -0
  543. package/skills/pytorch-patterns/references/debugging.md +39 -0
  544. package/skills/quarkus-patterns/SKILL.md +93 -0
  545. package/skills/quarkus-patterns/evals/triggers.json +18 -0
  546. package/skills/rag-pipeline-patterns/SKILL.md +117 -0
  547. package/skills/rag-pipeline-patterns/evals/triggers.json +18 -0
  548. package/skills/rag-pipeline-patterns/references/evaluation.md +37 -0
  549. package/skills/rag-pipeline-patterns/references/retrieval-recipes.md +78 -0
  550. package/skills/rails-patterns/SKILL.md +132 -0
  551. package/skills/rails-patterns/evals/triggers.json +16 -0
  552. package/skills/react-native-patterns/SKILL.md +101 -0
  553. package/skills/react-native-patterns/evals/triggers.json +16 -0
  554. package/skills/react-patterns/SKILL.md +140 -0
  555. package/skills/react-patterns/evals/triggers.json +18 -0
  556. package/skills/react-performance/SKILL.md +107 -0
  557. package/skills/react-performance/evals/triggers.json +18 -0
  558. package/skills/redis-patterns/SKILL.md +109 -0
  559. package/skills/redis-patterns/evals/triggers.json +18 -0
  560. package/skills/redis-patterns/references/operations.md +61 -0
  561. package/skills/refactoring-safely/SKILL.md +107 -0
  562. package/skills/refactoring-safely/evals/triggers.json +17 -0
  563. package/skills/release-management/SKILL.md +141 -0
  564. package/skills/release-management/evals/triggers.json +16 -0
  565. package/skills/remotion-video/SKILL.md +91 -0
  566. package/skills/remotion-video/evals/triggers.json +16 -0
  567. package/skills/remotion-video/references/lambda.md +66 -0
  568. package/skills/returns-reverse-logistics/SKILL.md +97 -0
  569. package/skills/returns-reverse-logistics/evals/triggers.json +16 -0
  570. package/skills/ruby-patterns/SKILL.md +127 -0
  571. package/skills/ruby-patterns/evals/triggers.json +18 -0
  572. package/skills/ruby-testing/SKILL.md +113 -0
  573. package/skills/ruby-testing/evals/triggers.json +18 -0
  574. package/skills/runbook-writing/SKILL.md +101 -0
  575. package/skills/runbook-writing/evals/triggers.json +14 -0
  576. package/skills/rust-patterns/SKILL.md +118 -0
  577. package/skills/rust-patterns/evals/triggers.json +16 -0
  578. package/skills/rust-patterns/references/cargo-and-clippy.md +105 -0
  579. package/skills/rust-patterns/references/errors-and-traits.md +97 -0
  580. package/skills/rust-testing/SKILL.md +148 -0
  581. package/skills/rust-testing/evals/triggers.json +16 -0
  582. package/skills/rust-testing/references/fakes-proptest-async.md +113 -0
  583. package/skills/search-first/SKILL.md +73 -0
  584. package/skills/search-first/evals/triggers.json +14 -0
  585. package/skills/security-review/SKILL.md +58 -0
  586. package/skills/security-review/evals/triggers.json +14 -0
  587. package/skills/security-review/references/checklist.md +54 -0
  588. package/skills/security-review/scripts/scan-secrets.js +79 -0
  589. package/skills/seo-audit/SKILL.md +111 -0
  590. package/skills/seo-audit/evals/triggers.json +16 -0
  591. package/skills/seo-audit/references/checks.md +65 -0
  592. package/skills/session-handoff/SKILL.md +101 -0
  593. package/skills/session-handoff/evals/triggers.json +14 -0
  594. package/skills/skill-authoring/SKILL.md +87 -0
  595. package/skills/skill-authoring/evals/triggers.json +14 -0
  596. package/skills/skill-authoring/references/frontmatter.md +40 -0
  597. package/skills/slack-app-integration/SKILL.md +108 -0
  598. package/skills/slack-app-integration/evals/triggers.json +17 -0
  599. package/skills/smart-contract-security/SKILL.md +110 -0
  600. package/skills/smart-contract-security/evals/triggers.json +16 -0
  601. package/skills/smart-contract-security/references/vulnerability-checklist.md +79 -0
  602. package/skills/social-media-publishing/SKILL.md +84 -0
  603. package/skills/social-media-publishing/evals/triggers.json +17 -0
  604. package/skills/social-media-publishing/references/platform-apis.md +116 -0
  605. package/skills/solidity-patterns/SKILL.md +95 -0
  606. package/skills/solidity-patterns/evals/triggers.json +16 -0
  607. package/skills/spring-boot-patterns/SKILL.md +128 -0
  608. package/skills/spring-boot-patterns/evals/triggers.json +18 -0
  609. package/skills/spring-boot-patterns/references/testing.md +72 -0
  610. package/skills/stripe-integration/SKILL.md +69 -0
  611. package/skills/stripe-integration/evals/triggers.json +16 -0
  612. package/skills/stripe-integration/references/api.md +71 -0
  613. package/skills/stripe-integration/references/billing.md +58 -0
  614. package/skills/stripe-integration/references/webhooks.md +76 -0
  615. package/skills/subscription-billing-ops/SKILL.md +118 -0
  616. package/skills/subscription-billing-ops/evals/triggers.json +17 -0
  617. package/skills/subscription-billing-ops/references/proration.md +47 -0
  618. package/skills/subscription-billing-ops/references/revenue-metrics.md +38 -0
  619. package/skills/svelte-patterns/SKILL.md +122 -0
  620. package/skills/svelte-patterns/evals/triggers.json +16 -0
  621. package/skills/swift-patterns/SKILL.md +113 -0
  622. package/skills/swift-patterns/evals/triggers.json +16 -0
  623. package/skills/swift-patterns/references/concurrency-migration.md +40 -0
  624. package/skills/swift-testing/SKILL.md +121 -0
  625. package/skills/swift-testing/evals/triggers.json +16 -0
  626. package/skills/swift-testing/references/swift-testing-cheatsheet.md +81 -0
  627. package/skills/swiftui-patterns/SKILL.md +102 -0
  628. package/skills/swiftui-patterns/evals/triggers.json +16 -0
  629. package/skills/tdd-workflow/SKILL.md +67 -0
  630. package/skills/tdd-workflow/evals/triggers.json +14 -0
  631. package/skills/technical-writing/SKILL.md +95 -0
  632. package/skills/technical-writing/evals/triggers.json +14 -0
  633. package/skills/terraform-patterns/SKILL.md +122 -0
  634. package/skills/terraform-patterns/evals/triggers.json +16 -0
  635. package/skills/terraform-patterns/references/backends.md +67 -0
  636. package/skills/test-coverage-improvement/SKILL.md +107 -0
  637. package/skills/test-coverage-improvement/evals/triggers.json +16 -0
  638. package/skills/threat-modeling/SKILL.md +113 -0
  639. package/skills/threat-modeling/evals/triggers.json +16 -0
  640. package/skills/typescript-patterns/SKILL.md +122 -0
  641. package/skills/typescript-patterns/evals/triggers.json +16 -0
  642. package/skills/typescript-testing/SKILL.md +101 -0
  643. package/skills/typescript-testing/evals/triggers.json +16 -0
  644. package/skills/verification-loop/SKILL.md +70 -0
  645. package/skills/verification-loop/evals/triggers.json +14 -0
  646. package/skills/verification-loop/scripts/detect-checks.js +102 -0
  647. package/skills/vite-patterns/SKILL.md +117 -0
  648. package/skills/vite-patterns/evals/triggers.json +16 -0
  649. package/skills/vue-patterns/SKILL.md +104 -0
  650. package/skills/vue-patterns/evals/triggers.json +16 -0
  651. package/skills/web-accessibility/SKILL.md +119 -0
  652. package/skills/web-accessibility/evals/triggers.json +16 -0
  653. package/skills/web-performance/SKILL.md +107 -0
  654. package/skills/web-performance/evals/triggers.json +16 -0
  655. package/skills/web-scraping/SKILL.md +80 -0
  656. package/skills/web-scraping/evals/triggers.json +16 -0
@@ -0,0 +1,284 @@
1
+ 'use strict';
2
+
3
+ // Registers curated MCP servers (mcp/<name>.json) with each host (verified against host docs and Kete
4
+ // Code's source, Oct 2026). Servers carry no secrets: remote ones sign in with the host's own OAuth
5
+ // flow, local ones need no tokens. Each host gets the server in its own shape:
6
+ // Claude Code user: `claude mcp add-json --scope user` (never edits ~/.claude.json); project: .mcp.json
7
+ // Codex [mcp_servers.<name>] tables in a marked block of config.toml
8
+ // Gemini CLI settings.json `mcpServers` (httpUrl = streamable HTTP)
9
+ // Cursor mcp.json `mcpServers`
10
+ // Copilot CLI ~/.copilot/mcp-config.json `mcpServers`; local servers only (its OAuth isn't documented)
11
+ // OpenCode opencode.json `mcp` (command is an array)
12
+ // Kete Code kete.json `mcp.servers`
13
+ // An entry is ours when it equals what we would write, so a server you configured yourself under the
14
+ // same name is never touched. Claude Code's CLI-managed servers are tracked in ~/.kete-loom/mcp.json.
15
+
16
+ const fs = require('fs');
17
+ const path = require('path');
18
+ const { execFileSync } = require('child_process');
19
+ const { readJson, writeAtomic, CommentedFileError } = require('./hooks-install');
20
+
21
+ const MCP_DIR = path.join(__dirname, '..', '..', 'mcp');
22
+ const listMcp = (dir = MCP_DIR) =>
23
+ fs.existsSync(dir)
24
+ ? fs.readdirSync(dir).filter((f) => f.endsWith('.json')).sort().map((f) => ({ kind: 'mcp', dir: f.slice(0, -5), file: path.join(dir, f), ...JSON.parse(fs.readFileSync(path.join(dir, f), 'utf8')) }))
25
+ : [];
26
+
27
+ const http = (s) => s.transport === 'http';
28
+ // Each host's JSON for one server, or null when that host can't run it.
29
+ const RENDER = {
30
+ claude: (s) => (http(s) ? { type: 'http', url: s.url } : { type: 'stdio', command: s.command, args: s.args }),
31
+ cursor: (s) => (http(s) ? { url: s.url } : { type: 'stdio', command: s.command, args: s.args }),
32
+ gemini: (s) => (http(s) ? { httpUrl: s.url } : { command: s.command, args: s.args }),
33
+ copilot: (s) => (http(s) ? null : { type: 'local', command: s.command, args: s.args, tools: ['*'] }),
34
+ opencode: (s) => (http(s) ? { type: 'remote', url: s.url, enabled: true } : { type: 'local', command: [s.command, ...s.args], enabled: true }),
35
+ kete: (s) => (http(s) ? { type: 'remote', url: s.url } : { type: 'local', command: [s.command, ...s.args] }),
36
+ };
37
+
38
+ const SIGN_IN = {
39
+ claude: (n) => `run /mcp in Claude Code to sign in to ${n}`,
40
+ codex: (n) => `codex mcp login ${n}`,
41
+ gemini: (n) => `run /mcp auth ${n} in Gemini CLI`,
42
+ cursor: (n) => `Cursor asks you to sign in to ${n} the first time it connects`,
43
+ opencode: (n) => `opencode mcp auth ${n}`,
44
+ kete: (n) => `kete mcp auth ${n}`,
45
+ };
46
+
47
+ const keteFile = (dir) => (fs.existsSync(path.join(dir, 'kete.jsonc')) ? path.join(dir, 'kete.jsonc') : path.join(dir, 'kete.json'));
48
+
49
+ // JSON hosts: file per scope, and the object that holds the servers.
50
+ const JSON_HOSTS = {
51
+ claude: { file: { project: (cwd) => path.join(cwd, '.mcp.json') }, holder: (d) => (d.mcpServers ||= {}), standalone: true },
52
+ cursor: { file: { user: (h) => path.join(h, '.cursor', 'mcp.json'), project: (cwd) => path.join(cwd, '.cursor', 'mcp.json') }, holder: (d) => (d.mcpServers ||= {}), standalone: true },
53
+ gemini: { file: { user: (h) => path.join(h, '.gemini', 'settings.json'), project: (cwd) => path.join(cwd, '.gemini', 'settings.json') }, holder: (d) => (d.mcpServers ||= {}) },
54
+ copilot: { file: { user: (h) => path.join(process.env.COPILOT_HOME || path.join(h, '.copilot'), 'mcp-config.json') }, holder: (d) => (d.mcpServers ||= {}), standalone: true },
55
+ opencode: { file: { user: (h) => path.join(h, '.config', 'opencode', 'opencode.json'), project: (cwd) => path.join(cwd, 'opencode.json') }, holder: (d) => (d.mcp ||= {}) },
56
+ kete: { file: { user: (h) => keteFile(path.join(h, '.config', 'kete')), project: (cwd) => keteFile(path.join(cwd, '.kete')) }, holder: (d) => ((d.mcp ||= {}).servers ||= {}) },
57
+ };
58
+ const MCP_HOSTS = ['claude', 'codex', 'gemini', 'cursor', 'copilot', 'opencode', 'kete'];
59
+
60
+ const same = (a, b) => JSON.stringify(a) === JSON.stringify(b);
61
+
62
+ function pruneJson(data) {
63
+ const empty = (v) => v && typeof v === 'object' && !Array.isArray(v) && !Object.keys(v).length;
64
+ if (data.mcp && empty(data.mcp.servers)) delete data.mcp.servers;
65
+ for (const k of ['mcpServers', 'mcp']) if (empty(data[k])) delete data[k];
66
+ return data;
67
+ }
68
+
69
+ function jsonRegister(host, servers, { file, dryRun }) {
70
+ let data;
71
+ try {
72
+ data = readJson(file);
73
+ } catch (e) {
74
+ const reason = e instanceof CommentedFileError ? `${e.message}; add the servers yourself` : e.message;
75
+ return servers.map((s) => ({ name: s.dir, action: 'skipped', reason, dest: file }));
76
+ }
77
+ const holder = JSON_HOSTS[host].holder(data);
78
+ const rows = servers.map((s) => {
79
+ const want = RENDER[host](s);
80
+ if (!want) return { name: s.dir, action: 'skipped', reason: `${host} can't sign in to remote MCP servers yet; add ${s.dir} there yourself`, dest: file };
81
+ const have = holder[s.dir];
82
+ if (have && !same(have, want)) return { name: s.dir, action: 'skipped', reason: `a server named ${s.dir} already exists there and wasn't installed by kete-loom`, dest: file };
83
+ holder[s.dir] = want;
84
+ return { name: s.dir, action: have ? 'updated' : 'installed', dest: file };
85
+ });
86
+ pruneJson(data);
87
+ if (!dryRun && rows.some((r) => r.action !== 'skipped')) writeAtomic(file, JSON.stringify(data, null, 2) + '\n');
88
+ return rows;
89
+ }
90
+
91
+ function jsonUnregister(host, servers, { file, dryRun }) {
92
+ if (!fs.existsSync(file)) return [];
93
+ let data;
94
+ try {
95
+ data = readJson(file);
96
+ } catch {
97
+ return [];
98
+ }
99
+ const holder = JSON_HOSTS[host].holder(data);
100
+ const gone = servers.filter((s) => holder[s.dir] && RENDER[host](s) && same(holder[s.dir], RENDER[host](s)));
101
+ if (!gone.length) return [];
102
+ for (const s of gone) delete holder[s.dir];
103
+ pruneJson(data);
104
+ if (!dryRun) {
105
+ if (JSON_HOSTS[host].standalone && !Object.keys(data).length) fs.rmSync(file);
106
+ else writeAtomic(file, JSON.stringify(data, null, 2) + '\n');
107
+ }
108
+ return gone.map((s) => ({ name: s.dir, action: 'removed', dest: file }));
109
+ }
110
+
111
+ // Codex: TOML tables in a block we own. Strings and arrays are written as JSON, which is valid TOML.
112
+ const TOML_START = '# kete-loom:mcp:start (managed by kete-loom: edit outside this block)';
113
+ const TOML_END = '# kete-loom:mcp:end';
114
+ const TOML_BLOCK_RE = /\n*# kete-loom:mcp:start[^\n]*\n[\s\S]*?# kete-loom:mcp:end\n?/;
115
+ const codexFile = (scope, home, cwd) => (scope === 'project' ? path.join(cwd, '.codex', 'config.toml') : path.join(home, '.codex', 'config.toml'));
116
+ const tomlTable = (s) =>
117
+ [`[mcp_servers.${s.dir}]`, ...(http(s) ? [`url = ${JSON.stringify(s.url)}`] : [`command = ${JSON.stringify(s.command)}`, `args = ${JSON.stringify(s.args)}`])].join('\n');
118
+ const blockNames = (text) => [...((TOML_BLOCK_RE.exec(text) || [''])[0].matchAll(/^\[mcp_servers\.([a-z0-9-]+)\]$/gm))].map((m) => m[1]);
119
+
120
+ function codexWrite(file, text, names, all, dryRun) {
121
+ const rest = text.replace(TOML_BLOCK_RE, '\n').trimEnd();
122
+ const tables = all.filter((s) => names.includes(s.dir)).map(tomlTable);
123
+ const next = tables.length ? `${rest ? `${rest}\n\n` : ''}${TOML_START}\n${tables.join('\n\n')}\n${TOML_END}\n` : rest ? `${rest}\n` : '';
124
+ if (dryRun || next === text) return;
125
+ if (next) writeAtomic(file, next);
126
+ else fs.rmSync(file);
127
+ }
128
+
129
+ function codexRegister(servers, all, { file, dryRun }) {
130
+ const text = fs.existsSync(file) ? fs.readFileSync(file, 'utf8') : '';
131
+ const outside = text.replace(TOML_BLOCK_RE, '\n');
132
+ const ours = blockNames(text);
133
+ const rows = servers.map((s) => {
134
+ // A table or dotted key for this name outside our block would make config.toml invalid TOML.
135
+ if (new RegExp(`(^|\\n)\\s*\\[mcp_servers\\.(?:"${s.dir}"|${s.dir})\\]|mcp_servers\\.(?:"${s.dir}"|${s.dir})\\.`).test(outside)) {
136
+ return { name: s.dir, action: 'skipped', reason: `config.toml already defines mcp_servers.${s.dir}`, dest: file };
137
+ }
138
+ return { name: s.dir, action: ours.includes(s.dir) ? 'updated' : 'installed', dest: file };
139
+ });
140
+ const names = [...new Set([...ours, ...rows.filter((r) => r.action !== 'skipped').map((r) => r.name)])];
141
+ codexWrite(file, text, names, all, dryRun);
142
+ return rows;
143
+ }
144
+
145
+ function codexUnregister(servers, all, { file, dryRun }) {
146
+ if (!fs.existsSync(file)) return [];
147
+ const text = fs.readFileSync(file, 'utf8');
148
+ const ours = blockNames(text);
149
+ const gone = ours.filter((n) => servers.some((s) => s.dir === n));
150
+ if (!gone.length) return [];
151
+ codexWrite(file, text, ours.filter((n) => !gone.includes(n)), all, dryRun);
152
+ return gone.map((name) => ({ name, action: 'removed', dest: file }));
153
+ }
154
+
155
+ // Claude Code user scope goes through its CLI; ~/.kete-loom/mcp.json records which servers we added.
156
+ const stateFile = (home) => path.join(home, '.kete-loom', 'mcp.json');
157
+ const readState = (home) => {
158
+ try {
159
+ return JSON.parse(fs.readFileSync(stateFile(home), 'utf8'));
160
+ } catch {
161
+ return {};
162
+ }
163
+ };
164
+ function writeState(home, state) {
165
+ const file = stateFile(home);
166
+ if (Object.values(state).some((v) => v.length)) {
167
+ // Our own state file: atomic, but no backup (backups are for files that belong to you).
168
+ fs.mkdirSync(path.dirname(file), { recursive: true });
169
+ fs.writeFileSync(`${file}.tmp`, JSON.stringify(state, null, 2) + '\n');
170
+ fs.renameSync(`${file}.tmp`, file);
171
+ }
172
+ else if (fs.existsSync(file)) {
173
+ fs.rmSync(file);
174
+ if (!fs.readdirSync(path.dirname(file)).length) fs.rmdirSync(path.dirname(file));
175
+ }
176
+ }
177
+ const claudeBin = () => process.env.KETE_LOOM_CLAUDE_BIN || 'claude';
178
+ const claude = (args) => execFileSync(claudeBin(), args, { stdio: ['ignore', 'pipe', 'pipe'], encoding: 'utf8', timeout: 30000 });
179
+
180
+ function claudeUserRegister(servers, { home, dryRun }) {
181
+ const state = readState(home);
182
+ const mine = new Set(state['claude-user'] || []);
183
+ const rows = servers.map((s) => {
184
+ const json = JSON.stringify(RENDER.claude(s));
185
+ const dest = `claude mcp (user scope)`;
186
+ try {
187
+ if (mine.has(s.dir)) {
188
+ if (!dryRun) {
189
+ claude(['mcp', 'remove', s.dir, '--scope', 'user']);
190
+ claude(['mcp', 'add-json', s.dir, json, '--scope', 'user']);
191
+ }
192
+ return { name: s.dir, action: 'updated', dest };
193
+ }
194
+ let exists = true;
195
+ try {
196
+ claude(['mcp', 'get', s.dir]);
197
+ } catch (e) {
198
+ if (e.code === 'ENOENT') throw e;
199
+ exists = false;
200
+ }
201
+ if (exists) return { name: s.dir, action: 'skipped', reason: `Claude Code already has a server named ${s.dir}, not installed by kete-loom`, dest };
202
+ if (!dryRun) claude(['mcp', 'add-json', s.dir, json, '--scope', 'user']);
203
+ mine.add(s.dir);
204
+ return { name: s.dir, action: 'installed', dest };
205
+ } catch (e) {
206
+ const reason = e.code === 'ENOENT' ? `the claude CLI isn't on your PATH; run: claude mcp add-json ${s.dir} '${json}' --scope user` : `claude mcp failed: ${(e.stderr || e.message).trim().slice(0, 200)}`;
207
+ return { name: s.dir, action: 'skipped', reason, dest };
208
+ }
209
+ });
210
+ if (!dryRun) writeState(home, { ...state, 'claude-user': [...mine].sort() });
211
+ return rows;
212
+ }
213
+
214
+ function claudeUserUnregister(servers, { home, dryRun }) {
215
+ const state = readState(home);
216
+ const mine = state['claude-user'] || [];
217
+ const gone = mine.filter((n) => servers.some((s) => s.dir === n));
218
+ const removed = [];
219
+ for (const name of gone) {
220
+ try {
221
+ if (!dryRun) claude(['mcp', 'remove', name, '--scope', 'user']);
222
+ removed.push(name);
223
+ } catch (e) {
224
+ // Already removed by hand: forget it either way.
225
+ if (e.code !== 'ENOENT') removed.push(name);
226
+ }
227
+ }
228
+ if (!dryRun) writeState(home, { ...state, 'claude-user': mine.filter((n) => !removed.includes(n)) });
229
+ return removed.map((name) => ({ name, action: 'removed', dest: 'claude mcp (user scope)' }));
230
+ }
231
+
232
+ function fileFor(host, scope, home, cwd) {
233
+ if (host === 'codex') return codexFile(scope, home, cwd);
234
+ const f = JSON_HOSTS[host].file[scope];
235
+ return f ? f(scope === 'project' ? cwd : home) : null;
236
+ }
237
+
238
+ // Registers servers with one host. Returns rows { target, kind, name, action, dest, reason?, note? }.
239
+ function installMcp(host, servers, all, { scope = 'user', home, cwd = process.cwd(), dryRun = false }) {
240
+ let rows;
241
+ if (host === 'claude' && scope === 'user') rows = claudeUserRegister(servers, { home, dryRun });
242
+ else {
243
+ const file = fileFor(host, scope, home, cwd);
244
+ if (!file) {
245
+ const reason = host === 'copilot' ? "Copilot CLI reads the project's .mcp.json, which kete-loom writes for Claude Code" : `${host} has no ${scope} MCP config`;
246
+ return servers.map((s) => ({ target: host, kind: 'mcp', name: s.dir, action: 'skipped', reason, dest: '' }));
247
+ }
248
+ rows = host === 'codex' ? codexRegister(servers, all, { file, dryRun }) : jsonRegister(host, servers, { file, dryRun });
249
+ }
250
+ return rows.map((r) => {
251
+ const s = servers.find((x) => x.dir === r.name);
252
+ const note = r.action !== 'skipped' && s && http(s) && SIGN_IN[host] ? `Remote MCP servers sign in with OAuth. ${host}: ${SIGN_IN[host]('<server>')}.` : undefined;
253
+ return { target: host, kind: 'mcp', ...r, ...(note ? { note } : {}) };
254
+ });
255
+ }
256
+
257
+ function uninstallMcp(host, servers, all, { scope = 'user', home, cwd = process.cwd(), dryRun = false }) {
258
+ let rows;
259
+ if (host === 'claude' && scope === 'user') rows = claudeUserUnregister(servers, { home, dryRun });
260
+ else {
261
+ const file = fileFor(host, scope, home, cwd);
262
+ if (!file) return [];
263
+ rows = host === 'codex' ? codexUnregister(servers, all, { file, dryRun }) : jsonUnregister(host, servers, { file, dryRun });
264
+ }
265
+ return rows.map((r) => ({ target: host, kind: 'mcp', ...r }));
266
+ }
267
+
268
+ // Names of kete-loom MCP servers registered with a host (exact matches only).
269
+ function registeredMcp(host, all, { scope = 'user', home, cwd = process.cwd() }) {
270
+ if (host === 'claude' && scope === 'user') return readState(home)['claude-user'] || [];
271
+ const file = fileFor(host, scope, home, cwd);
272
+ if (!file || !fs.existsSync(file)) return [];
273
+ if (host === 'codex') return blockNames(fs.readFileSync(file, 'utf8'));
274
+ let data;
275
+ try {
276
+ data = readJson(file);
277
+ } catch {
278
+ return [];
279
+ }
280
+ const holder = JSON_HOSTS[host].holder(data);
281
+ return all.filter((s) => holder[s.dir] && RENDER[host](s) && same(holder[s.dir], RENDER[host](s))).map((s) => s.dir);
282
+ }
283
+
284
+ module.exports = { MCP_HOSTS, MCP_DIR, listMcp, RENDER, installMcp, uninstallMcp, registeredMcp, tomlTable };
@@ -0,0 +1,128 @@
1
+ 'use strict';
2
+
3
+ // Install profiles (profiles.json): named selections of skills, agents, commands and hooks.
4
+ // A profile can extend others, add whole categories, and `detect` the project's languages,
5
+ // frameworks and tools so only the packs it uses are installed.
6
+
7
+ const fs = require('fs');
8
+ const path = require('path');
9
+
10
+ const PROFILES_FILE = path.join(__dirname, '..', '..', 'profiles.json');
11
+ const KINDS = ['skills', 'agents', 'commands', 'rules', 'hooks', 'mcp'];
12
+
13
+ const loadProfiles = (file = PROFILES_FILE) => JSON.parse(fs.readFileSync(file, 'utf8'));
14
+
15
+ // What a project uses -> the items that help with it. `when` gets helpers over the project folder.
16
+ const lang = (name) => ({ skills: [`${name}-patterns`, `${name}-testing`], agents: [`${name}-reviewer`, `${name}-build-resolver`], rules: [name] });
17
+ const DETECT = [
18
+ { what: 'TypeScript/JavaScript', when: (p) => p.has('package.json') || p.has('tsconfig.json'), ...lang('typescript') },
19
+ { what: 'Python', when: (p) => p.has('pyproject.toml') || p.has('requirements.txt') || p.has('setup.py') || p.has('Pipfile'), ...lang('python') },
20
+ { what: 'Go', when: (p) => p.has('go.mod'), ...lang('go') },
21
+ { what: 'Rust', when: (p) => p.has('Cargo.toml'), ...lang('rust') },
22
+ { what: 'Java', when: (p) => p.has('pom.xml') || p.has('build.gradle') || p.ext('.java'), ...lang('java') },
23
+ { what: 'Kotlin', when: (p) => p.has('build.gradle.kts') || p.ext('.kt'), ...lang('kotlin') },
24
+ { what: 'C#', when: (p) => p.ext('.csproj') || p.ext('.sln'), ...lang('csharp') },
25
+ { what: 'F#', when: (p) => p.ext('.fsproj'), ...lang('fsharp') },
26
+ { what: 'C++', when: (p) => p.has('CMakeLists.txt') || p.ext('.cpp') || p.ext('.hpp'), ...lang('cpp') },
27
+ { what: 'Swift', when: (p) => p.has('Package.swift') || p.ext('.xcodeproj'), ...lang('swift') },
28
+ { what: 'Dart', when: (p) => p.has('pubspec.yaml'), ...lang('dart') },
29
+ { what: 'PHP', when: (p) => p.has('composer.json'), ...lang('php') },
30
+ { what: 'Ruby', when: (p) => p.has('Gemfile'), ...lang('ruby') },
31
+ { what: 'Perl', when: (p) => p.has('cpanfile') || p.has('Makefile.PL'), ...lang('perl') },
32
+ { what: 'React', when: (p) => p.dep('react'), skills: ['react-patterns', 'react-performance'], agents: ['react-reviewer'] },
33
+ { what: 'Next.js', when: (p) => p.dep('next'), skills: ['nextjs-patterns'] },
34
+ { what: 'Vue', when: (p) => p.dep('vue'), skills: ['vue-patterns'], agents: ['vue-reviewer'] },
35
+ { what: 'Nuxt', when: (p) => p.dep('nuxt'), skills: ['nuxt-patterns'] },
36
+ { what: 'Angular', when: (p) => p.dep('@angular/core'), skills: ['angular-patterns'] },
37
+ { what: 'Svelte', when: (p) => p.dep('svelte'), skills: ['svelte-patterns'] },
38
+ { what: 'React Native', when: (p) => p.dep('react-native'), skills: ['react-native-patterns'] },
39
+ { what: 'NestJS', when: (p) => p.dep('@nestjs/core'), skills: ['nestjs-patterns'] },
40
+ { what: 'Node HTTP server', when: (p) => p.dep('express') || p.dep('fastify'), skills: ['node-backend-patterns'] },
41
+ { what: 'Prisma', when: (p) => p.dep('prisma') || p.dep('@prisma/client'), skills: ['prisma-patterns'] },
42
+ { what: 'Vite', when: (p) => p.dep('vite'), skills: ['vite-patterns'] },
43
+ { what: 'Playwright', when: (p) => p.dep('@playwright/test'), skills: ['e2e-testing-playwright'], agents: ['e2e-runner'] },
44
+ { what: 'Django', when: (p) => p.py('django'), skills: ['django-patterns'], agents: ['django-reviewer'] },
45
+ { what: 'FastAPI', when: (p) => p.py('fastapi'), skills: ['fastapi-patterns'], agents: ['fastapi-reviewer'] },
46
+ { what: 'Flask', when: (p) => p.py('flask'), skills: ['flask-patterns'] },
47
+ { what: 'PyTorch', when: (p) => p.py('torch'), skills: ['pytorch-patterns'], agents: ['mle-reviewer'] },
48
+ { what: 'Rails', when: (p) => /\brails\b/.test(p.read('Gemfile')), skills: ['rails-patterns'] },
49
+ { what: 'Laravel', when: (p) => /laravel\/framework/.test(p.read('composer.json')), skills: ['laravel-patterns'] },
50
+ { what: 'Spring Boot', when: (p) => /spring-boot/.test(p.read('pom.xml') + p.read('build.gradle') + p.read('build.gradle.kts')), skills: ['spring-boot-patterns', 'jpa-hibernate'] },
51
+ { what: 'Quarkus', when: (p) => /io\.quarkus/.test(p.read('pom.xml') + p.read('build.gradle') + p.read('build.gradle.kts')), skills: ['quarkus-patterns'] },
52
+ { what: 'Ktor', when: (p) => /io\.ktor/.test(p.read('build.gradle.kts') + p.read('build.gradle')), skills: ['ktor-patterns'] },
53
+ { what: 'ASP.NET Core', when: (p) => p.ext('.csproj') && /Microsoft\.NET\.Sdk\.Web|AspNetCore/.test(p.readExt('.csproj')), skills: ['aspnet-core-patterns'] },
54
+ { what: 'EF Core', when: (p) => /EntityFrameworkCore/.test(p.readExt('.csproj')), skills: ['ef-core-patterns'] },
55
+ { what: 'Flutter', when: (p) => /\bflutter:/.test(p.read('pubspec.yaml')), skills: ['flutter-patterns'], agents: ['flutter-reviewer'] },
56
+ { what: 'Docker', when: (p) => p.has('Dockerfile') || p.has('compose.yaml') || p.has('docker-compose.yml'), skills: ['docker-patterns'] },
57
+ { what: 'Terraform', when: (p) => p.ext('.tf'), skills: ['terraform-patterns'], agents: ['infra-reviewer'] },
58
+ { what: 'GitHub Actions', when: (p) => p.has('.github/workflows'), skills: ['github-actions-ci'] },
59
+ { what: 'dbt', when: (p) => p.has('dbt_project.yml'), skills: ['dbt-patterns'] },
60
+ { what: 'Solidity', when: (p) => p.has('foundry.toml') || p.has('hardhat.config.ts') || p.has('hardhat.config.js'), skills: ['solidity-patterns', 'smart-contract-security'], agents: ['solidity-reviewer'] },
61
+ ];
62
+
63
+ // Read-only view of a project folder (top level only, plus package.json/pyproject dependencies).
64
+ function inspect(dir) {
65
+ let entries = [];
66
+ try {
67
+ entries = fs.readdirSync(dir);
68
+ } catch {
69
+ // unreadable or missing folder: nothing detected
70
+ }
71
+ const read = (f) => {
72
+ try {
73
+ return fs.readFileSync(path.join(dir, f), 'utf8');
74
+ } catch {
75
+ return '';
76
+ }
77
+ };
78
+ let pkg = {};
79
+ try {
80
+ pkg = JSON.parse(read('package.json') || '{}');
81
+ } catch {
82
+ // invalid package.json: no JS dependencies detected
83
+ }
84
+ const deps = { ...pkg.dependencies, ...pkg.devDependencies, ...pkg.peerDependencies };
85
+ const python = (read('pyproject.toml') + read('requirements.txt') + read('setup.py') + read('Pipfile')).toLowerCase();
86
+ return {
87
+ has: (f) => fs.existsSync(path.join(dir, f)),
88
+ ext: (e) => entries.some((f) => f.endsWith(e)),
89
+ read,
90
+ readExt: (e) => entries.filter((f) => f.endsWith(e)).map(read).join('\n'),
91
+ dep: (name) => Object.prototype.hasOwnProperty.call(deps, name),
92
+ py: (name) => new RegExp(`(^|[\\s"'\\[,])${name}([\\s"'<>=~!\\],;]|$)`, 'm').test(python),
93
+ };
94
+ }
95
+
96
+ function detect(dir) {
97
+ const p = inspect(dir);
98
+ return DETECT.filter((d) => d.when(p));
99
+ }
100
+
101
+ // Resolves profile names to { all, skills, agents, commands, hooks: Set, detected: [labels] }.
102
+ function resolveProfiles(names, { catalog, cwd = process.cwd(), profiles = loadProfiles() }) {
103
+ const out = { all: false, detected: [], ...Object.fromEntries(KINDS.map((k) => [k, new Set()])) };
104
+ const seen = new Set();
105
+ const visit = (name, chain = []) => {
106
+ const p = profiles[name];
107
+ if (!p) throw new Error(`unknown profile "${name}". Known: ${Object.keys(profiles).join(', ')}`);
108
+ if (chain.includes(name)) throw new Error(`profile "${name}" extends itself: ${[...chain, name].join(' -> ')}`);
109
+ if (seen.has(name)) return;
110
+ seen.add(name);
111
+ for (const parent of p.extends || []) visit(parent, [...chain, name]);
112
+ if (p.all) out.all = true;
113
+ for (const k of KINDS) for (const n of p[k] || []) out[k].add(n);
114
+ // MCP servers reach the network and run code, so they come only from a profile's explicit `mcp` list.
115
+ for (const cat of p.categories || []) for (const k of KINDS.filter((x) => x !== 'mcp')) for (const item of catalog[k] || []) if (item.category === cat) out[k].add(item.dir);
116
+ if (p.detect) {
117
+ for (const d of detect(cwd)) {
118
+ out.detected.push(d.what);
119
+ for (const k of ['skills', 'agents', 'rules']) for (const n of d[k] || []) out[k].add(n);
120
+ }
121
+ }
122
+ };
123
+ for (const n of names) visit(n);
124
+ out.detected = [...new Set(out.detected)];
125
+ return out;
126
+ }
127
+
128
+ module.exports = { loadProfiles, resolveProfiles, detect, DETECT, PROFILES_FILE };
@@ -0,0 +1,86 @@
1
+ 'use strict';
2
+
3
+ // Turns one portable agent or command into the file each host expects.
4
+ // Every rendered file carries a `# kete-loom <name>@<version>` comment (YAML or TOML),
5
+ // which is how install and uninstall recognise files kete-loom wrote.
6
+
7
+ const TOOLS = ['read', 'search', 'shell', 'edit', 'web'];
8
+ // `# kete-loom name@version` inside YAML/TOML, or `<!-- kete-loom name@version -->` in plain Markdown.
9
+ const MARKER_RE = /^(?:# |<!-- )kete-loom [a-z0-9-]+@/m;
10
+
11
+ const CLAUDE_TOOLS = { read: ['Read'], search: ['Grep', 'Glob'], shell: ['Bash'], edit: ['Edit', 'Write'], web: ['WebFetch', 'WebSearch'] };
12
+ const COPILOT_TOOLS = { read: 'read', search: 'search', shell: 'execute', edit: 'edit', web: 'web' };
13
+
14
+ const marker = (item) => `# kete-loom ${item.dir}@${item.version}`;
15
+ // JSON strings are valid YAML double-quoted scalars and TOML basic strings (TOML also bans raw DEL).
16
+ const str = (s) => JSON.stringify(String(s)).replace(/\x7f/g, '\\u007f');
17
+ const body = (item) => item.body.replace(/^\s*\n/, '');
18
+
19
+ function markdown(item, fields) {
20
+ const lines = [marker(item)];
21
+ for (const [k, v] of Object.entries(fields)) {
22
+ if (v === undefined || v === null) continue;
23
+ if (Array.isArray(v)) lines.push(`${k}: [${v.map(str).join(', ')}]`);
24
+ else if (typeof v === 'object') {
25
+ lines.push(`${k}:`);
26
+ for (const [k2, v2] of Object.entries(v)) lines.push(` ${k2}: ${v2}`);
27
+ } else lines.push(`${k}: ${typeof v === 'string' ? str(v) : v}`);
28
+ }
29
+ return `---\n${lines.join('\n')}\n---\n\n${body(item)}`;
30
+ }
31
+
32
+ // Multi-line literal ('''...''') keeps prompts readable; it can't hold ''' or control characters other than tab/newline.
33
+ const tomlValue = (v) => (/\n/.test(v) && !v.includes("'''") && !/[\x00-\x08\x0b-\x1f\x7f]/.test(v) ? `'''\n${v}'''` : str(v));
34
+
35
+ function toml(item, fields) {
36
+ return [marker(item), ...Object.entries(fields).filter(([, v]) => v !== undefined).map(([k, v]) => `${k} = ${tomlValue(v)}`)].join('\n') + '\n';
37
+ }
38
+
39
+ const has = (item, tool) => !item.tools || item.tools.includes(tool);
40
+
41
+ // OpenCode restricts with permissions rather than a tool list.
42
+ function opencodePermission(item) {
43
+ if (!item.tools) return undefined;
44
+ const p = {};
45
+ if (!has(item, 'edit')) p.edit = 'deny';
46
+ if (!has(item, 'shell')) p.bash = 'deny';
47
+ if (!has(item, 'web')) p.webfetch = 'deny';
48
+ return Object.keys(p).length ? p : undefined;
49
+ }
50
+
51
+ // shortcut: Codex and Gemini agents inherit every tool (Codex can't set a sandbox per agent; Gemini's
52
+ // tool names aren't mapped yet), so `kete-loom/tools` is advisory there. Map them when hosts allow it.
53
+ const AGENT = {
54
+ claude: (a) => ({ file: `${a.dir}.md`, content: markdown(a, { name: a.dir, description: a.description, tools: a.tools && a.tools.flatMap((t) => CLAUDE_TOOLS[t]).join(', ') }) }),
55
+ codex: (a) => ({ file: `${a.dir}.toml`, content: toml(a, { name: a.dir, description: a.description, developer_instructions: body(a) }) }),
56
+ opencode: (a) => ({ file: `${a.dir}.md`, content: markdown(a, { description: a.description, mode: 'subagent', permission: opencodePermission(a) }) }),
57
+ cursor: (a) => ({ file: `${a.dir}.md`, content: markdown(a, { name: a.dir, description: a.description, readonly: a.tools && !has(a, 'edit') && !has(a, 'shell') ? true : undefined }) }),
58
+ gemini: (a) => ({ file: `${a.dir}.md`, content: markdown(a, { name: a.dir, description: a.description }) }),
59
+ kete: (a) => AGENT.opencode(a), // Kete Code reads the OpenCode agent format
60
+ copilot: (a) => ({ file: `${a.dir}.agent.md`, content: markdown(a, { name: a.dir, description: a.description, tools: a.tools && a.tools.map((t) => COPILOT_TOOLS[t]) }) }),
61
+ };
62
+
63
+ const COMMAND = {
64
+ claude: (c) => ({ file: `${c.dir}.md`, content: markdown(c, { description: c.description, 'argument-hint': c.argumentHint }) }),
65
+ opencode: (c) => ({ file: `${c.dir}.md`, content: markdown(c, { description: c.description }) }),
66
+ kete: (c) => COMMAND.opencode(c),
67
+ gemini: (c) => ({ file: `${c.dir}.toml`, content: toml(c, { description: c.description, prompt: body(c).replace(/\$ARGUMENTS/g, '{{args}}') }) }),
68
+ };
69
+
70
+ // Rules: path-scoped where the host supports it (Claude Code, Copilot, Cursor). Hosts that can't scope
71
+ // get plain Markdown that says which files it is about (scripts/lib/rules-install.js).
72
+ const appliesTo = (r) => (r.paths.length ? `Applies when working on files matching: ${r.paths.join(', ')}.\n\n` : '');
73
+ const RULE = {
74
+ claude: (r) => ({ file: `${r.dir}.md`, content: markdown(r, { paths: r.paths.length ? r.paths : undefined }) }),
75
+ copilot: (r) => ({ file: `${r.dir}.instructions.md`, content: markdown(r, { applyTo: r.paths.length ? r.paths.join(',') : '**' }) }),
76
+ cursor: (r) => ({ file: `${r.dir}.mdc`, content: markdown(r, { description: r.description, globs: r.paths.length ? r.paths.join(', ') : undefined, alwaysApply: !r.paths.length }) }),
77
+ plain: (r) => ({ file: `${r.dir}.md`, content: `<!-- ${marker(r).slice(2)} -->\n# ${r.dir} rules\n\n${appliesTo(r)}${body(r)}` }),
78
+ };
79
+
80
+ function render(item, target) {
81
+ if (item.kind === 'rule') return RULE[target] ? RULE[target](item) : null;
82
+ const table = item.kind === 'agent' ? AGENT : COMMAND;
83
+ return table[target] ? table[target](item) : null;
84
+ }
85
+
86
+ module.exports = { TOOLS, MARKER_RE, render };
@@ -0,0 +1,118 @@
1
+ 'use strict';
2
+
3
+ // Rules for hosts that can't scope instructions to file paths (verified against host docs and Kete
4
+ // Code's source, Oct 2026). User scope only. Each rule is written to a kete-loom folder that is the
5
+ // source of truth, and the host's instructions are pointed at that folder:
6
+ // Kete Code `@kete-loom/rules/<name>.md` imports in ~/.config/kete/AGENTS.md (relative to that file)
7
+ // Gemini CLI `@./kete-loom/rules/<name>.md` imports in ~/.gemini/GEMINI.md
8
+ // OpenCode one `<folder>/*.md` entry in the `instructions` array of ~/.config/opencode/opencode.json
9
+ // Codex no imports: the rule text inline in ~/.codex/AGENTS.md, kept under its 32 KiB limit
10
+ // Shared files are only changed between our markers, backed up and written atomically.
11
+
12
+ const fs = require('fs');
13
+ const path = require('path');
14
+ const { render } = require('./render');
15
+ const { readJson, writeAtomic, CommentedFileError } = require('./hooks-install');
16
+
17
+ const START = '<!-- kete-loom:rules:start (managed by kete-loom: edit outside this block) -->';
18
+ const END = '<!-- kete-loom:rules:end -->';
19
+ const BLOCK_RE = /\n*<!-- kete-loom:rules:start[^\n]*-->[\s\S]*?<!-- kete-loom:rules:end -->\n?/;
20
+ const CODEX_MAX_BYTES = 32768;
21
+
22
+ const HOSTS = {
23
+ kete: { dir: (h) => path.join(h, '.config', 'kete', 'kete-loom', 'rules'), file: (h) => path.join(h, '.config', 'kete', 'AGENTS.md'), mode: 'import', ref: (n) => `@kete-loom/rules/${n}.md` },
24
+ gemini: { dir: (h) => path.join(h, '.gemini', 'kete-loom', 'rules'), file: (h) => path.join(h, '.gemini', 'GEMINI.md'), mode: 'import', ref: (n) => `@./kete-loom/rules/${n}.md` },
25
+ opencode: { dir: (h) => path.join(h, '.config', 'opencode', 'kete-loom', 'rules'), file: (h) => path.join(h, '.config', 'opencode', 'opencode.json'), mode: 'instructions' },
26
+ codex: { dir: (h) => path.join(h, '.codex', 'kete-loom', 'rules'), file: (h) => path.join(h, '.codex', 'AGENTS.md'), mode: 'inline' },
27
+ };
28
+ const RULE_HOSTS = Object.keys(HOSTS);
29
+
30
+ const installedNames = (dir) => (fs.existsSync(dir) ? fs.readdirSync(dir).filter((f) => f.endsWith('.md')).map((f) => f.slice(0, -3)).sort() : []);
31
+
32
+ // Replaces our block in a Markdown file (or appends one); an empty block is removed.
33
+ function withBlock(text, lines) {
34
+ const rest = text.replace(BLOCK_RE, '\n').replace(/\n{3,}$/, '\n\n');
35
+ if (!lines.length) return rest.trim() ? `${rest.trimEnd()}\n` : '';
36
+ return `${rest.trimEnd() ? `${rest.trimEnd()}\n\n` : ''}${START}\n${lines.join('\n')}\n${END}\n`;
37
+ }
38
+
39
+ // Points the host at whatever is in its kete-loom rules folder now. Returns a skip reason or null.
40
+ function sync(host, home, dryRun) {
41
+ const h = HOSTS[host];
42
+ const dir = h.dir(home);
43
+ const names = installedNames(dir);
44
+ const file = h.file(home);
45
+ if (h.mode === 'instructions') {
46
+ let data;
47
+ try {
48
+ data = readJson(file);
49
+ } catch (e) {
50
+ return e instanceof CommentedFileError ? `${e.message}. Add "${path.join(dir, '*.md')}" to its "instructions" array yourself.` : e.message;
51
+ }
52
+ const entry = path.join(dir, '*.md');
53
+ const list = (Array.isArray(data.instructions) ? data.instructions : []).filter((i) => i !== entry);
54
+ if (names.length) list.push(entry);
55
+ if (list.length) data.instructions = list;
56
+ else delete data.instructions;
57
+ if (!dryRun) {
58
+ if (Object.keys(data).length) writeAtomic(file, JSON.stringify(data, null, 2) + '\n');
59
+ else if (fs.existsSync(file)) fs.rmSync(file);
60
+ }
61
+ return null;
62
+ }
63
+ const lines = h.mode === 'import'
64
+ ? names.map(h.ref)
65
+ : names.map((n) => fs.readFileSync(path.join(dir, `${n}.md`), 'utf8').replace(/^<!--[^\n]*-->\n/, '').trimEnd());
66
+ const old = fs.existsSync(file) ? fs.readFileSync(file, 'utf8') : '';
67
+ const next = withBlock(old, h.mode === 'inline' ? [lines.join('\n\n')].filter(Boolean) : lines);
68
+ if (h.mode === 'inline' && Buffer.byteLength(next) > CODEX_MAX_BYTES) {
69
+ return `~/.codex/AGENTS.md would be ${Buffer.byteLength(next)} bytes, over Codex's ${CODEX_MAX_BYTES}-byte limit; install fewer rules for Codex`;
70
+ }
71
+ if (!dryRun && next !== old) {
72
+ if (next) writeAtomic(file, next);
73
+ else fs.rmSync(file);
74
+ }
75
+ return null;
76
+ }
77
+
78
+ // Installs rules for one host. Returns result rows like the main installer's.
79
+ function installRules(host, rules, { home, dryRun = false }) {
80
+ const h = HOSTS[host];
81
+ const dir = h.dir(home);
82
+ const before = installedNames(dir);
83
+ const rows = rules.map((r) => ({ target: host, kind: 'rule', name: r.dir, action: before.includes(r.dir) ? 'updated' : 'installed', dest: h.file(home) }));
84
+ if (dryRun) return rows;
85
+ fs.mkdirSync(dir, { recursive: true });
86
+ const previous = new Map(before.map((n) => [n, fs.readFileSync(path.join(dir, `${n}.md`), 'utf8')]));
87
+ for (const r of rules) fs.writeFileSync(path.join(dir, `${r.dir}.md`), render(r, 'plain').content);
88
+ const reason = sync(host, home, false);
89
+ if (reason) {
90
+ // Leave the folder as it was so it keeps matching what the host loads.
91
+ for (const r of rules) {
92
+ if (previous.has(r.dir)) fs.writeFileSync(path.join(dir, `${r.dir}.md`), previous.get(r.dir));
93
+ else fs.rmSync(path.join(dir, `${r.dir}.md`), { force: true });
94
+ }
95
+ return rows.map((row) => ({ ...row, action: 'skipped', reason }));
96
+ }
97
+ return rows;
98
+ }
99
+
100
+ // Removes the named rules (or all kete-loom rules) for one host.
101
+ function uninstallRules(host, names, { home, dryRun = false }) {
102
+ const h = HOSTS[host];
103
+ const dir = h.dir(home);
104
+ const gone = installedNames(dir).filter((n) => !names || names.includes(n));
105
+ if (!gone.length) return [];
106
+ if (!dryRun) {
107
+ for (const n of gone) fs.rmSync(path.join(dir, `${n}.md`));
108
+ sync(host, home, false);
109
+ if (!installedNames(dir).length) {
110
+ fs.rmSync(dir, { recursive: true, force: true });
111
+ const parent = path.dirname(dir);
112
+ if (fs.existsSync(parent) && !fs.readdirSync(parent).length) fs.rmdirSync(parent);
113
+ }
114
+ }
115
+ return gone.map((n) => ({ target: host, kind: 'rule', name: n, action: 'removed', dest: h.file(home) }));
116
+ }
117
+
118
+ module.exports = { RULE_HOSTS, HOSTS, installRules, uninstallRules, installedNames, withBlock };