@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,65 @@
1
+ 'use strict';
2
+
3
+ // Minimal parser for the frontmatter subset Kete Loom allows:
4
+ // key: value (plain or quoted scalar, single line)
5
+ // metadata: (one nested level of key: "string" pairs)
6
+ // Anything else (folded scalars, lists, deeper nesting) is reported as an
7
+ // error, because not every host parses full YAML the same way.
8
+
9
+ // A value that opens with a quote must close with the same one (`"git"\n key: x` is a broken line, not a string).
10
+ const brokenQuote = (raw) => {
11
+ const v = raw.trim();
12
+ if (v.startsWith('"')) return !/^"(?:[^"\\]|\\.)*"\s*(?:#.*)?$/.test(v);
13
+ if (v.startsWith("'")) return !/^'(?:[^']|'')*'\s*(?:#.*)?$/.test(v);
14
+ return false;
15
+ };
16
+
17
+ function unquote(raw) {
18
+ const v = raw.trim();
19
+ if ((v.startsWith('"') && v.endsWith('"')) || (v.startsWith("'") && v.endsWith("'"))) {
20
+ return { value: v.slice(1, -1), quoted: true };
21
+ }
22
+ return { value: v, quoted: false };
23
+ }
24
+
25
+ function parseFrontmatter(text) {
26
+ const errors = [];
27
+ const match = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/.exec(text);
28
+ if (!match) {
29
+ return { data: null, body: text, errors: ['missing frontmatter block (--- ... ---) at top of file'] };
30
+ }
31
+ const data = {};
32
+ let current = null;
33
+ const lines = match[1].split(/\r?\n/);
34
+ lines.forEach((line, i) => {
35
+ if (!line.trim() || line.trim().startsWith('#')) return;
36
+ const nested = /^ {2}([^\s:][^:]*):\s*(.*)$/.exec(line);
37
+ const top = /^([A-Za-z0-9_-]+):\s*(.*)$/.exec(line);
38
+ if (top) {
39
+ const [, key, rest] = top;
40
+ if (rest === '') {
41
+ data[key] = {};
42
+ current = key;
43
+ } else {
44
+ if (/^[>|]/.test(rest.trim())) {
45
+ errors.push(`line ${i + 2}: "${key}" uses a multi-line YAML scalar; keep it on one line`);
46
+ }
47
+ if (rest.trim().startsWith('[') || rest.trim().startsWith('{')) {
48
+ errors.push(`line ${i + 2}: "${key}" uses an inline list/map; only plain strings are portable`);
49
+ }
50
+ if (brokenQuote(rest)) errors.push(`line ${i + 2}: "${key}" has an unclosed or mismatched quote`);
51
+ data[key] = unquote(rest).value;
52
+ current = null;
53
+ }
54
+ } else if (nested && current) {
55
+ const [, key, rest] = nested;
56
+ if (brokenQuote(rest)) errors.push(`line ${i + 2}: "${key.trim()}" has an unclosed or mismatched quote`);
57
+ data[current][key.trim()] = unquote(rest).value;
58
+ } else {
59
+ errors.push(`line ${i + 2}: unsupported frontmatter syntax: ${line.trim()}`);
60
+ }
61
+ });
62
+ return { data, body: text.slice(match[0].length), errors };
63
+ }
64
+
65
+ module.exports = { parseFrontmatter };
@@ -0,0 +1,292 @@
1
+ 'use strict';
2
+
3
+ // Registers kete-loom hooks with each host (verified against host docs and Kete Code's source,
4
+ // Oct 2026). User scope only. Every host calls `"<node>" "~/.kete-loom/hooks/run.js" <host> <hook>
5
+ // <event>`; that command string is how we recognise our own entries, so we never touch anyone else's.
6
+ //
7
+ // Shared settings files (Claude Code, Gemini CLI, Kete Code) are edited in place: parsed as strict
8
+ // JSON, backed up to `<file>.kete-loom-backup`, written atomically, and refused (with a snippet to
9
+ // paste) when they contain comments, since rewriting would drop them.
10
+
11
+ const fs = require('fs');
12
+ const path = require('path');
13
+
14
+ const RUNNER_RE = /\.kete-loom[\\/]hooks[\\/]run\.js"?\s+([a-z]+)\s+([a-z0-9-]+)(?:\s+([a-z-]+))?\s*$/;
15
+ // Seconds. A formatter run after an edit gets longer than a check before one.
16
+ const TIMEOUT_S = { 'pre-shell': 10, 'pre-edit': 10, 'post-edit': 60, 'session-start': 10 };
17
+
18
+ const runtimeDir = (home) => path.join(home, '.kete-loom', 'hooks');
19
+ const command = (home, host, hook, event = 'pre-shell', node = process.execPath) =>
20
+ `"${node}" "${path.join(runtimeDir(home), 'run.js')}" ${host} ${hook} ${event}`;
21
+
22
+ function ours(cmd, hook) {
23
+ const m = typeof cmd === 'string' && RUNNER_RE.exec(cmd);
24
+ return Boolean(m) && (!hook || m[2] === hook);
25
+ }
26
+
27
+ // Host event name and tool matcher for each kete-loom event; a missing entry means the host can't
28
+ // run that event (Cursor has no documented pre-edit hook; OpenCode can't add context at session start).
29
+ const EVENTS = {
30
+ claude: { 'pre-shell': ['PreToolUse', 'Bash'], 'pre-edit': ['PreToolUse', 'Edit|Write|NotebookEdit'], 'post-edit': ['PostToolUse', 'Edit|Write|NotebookEdit'], 'session-start': ['SessionStart'] },
31
+ codex: { 'pre-shell': ['PreToolUse', 'Bash'], 'pre-edit': ['PreToolUse', 'apply_patch'], 'post-edit': ['PostToolUse', 'apply_patch'], 'session-start': ['SessionStart'] },
32
+ gemini: { 'pre-shell': ['BeforeTool', 'run_shell_command'], 'pre-edit': ['BeforeTool', 'write_file|replace'], 'post-edit': ['AfterTool', 'write_file|replace'], 'session-start': ['SessionStart'] },
33
+ kete: { 'pre-shell': ['PreToolUse', 'shell'], 'pre-edit': ['PreToolUse', 'edit|write|patch'], 'post-edit': ['PostToolUse', 'edit|write|patch'], 'session-start': ['SessionStart'] },
34
+ cursor: { 'pre-shell': ['beforeShellExecution'], 'post-edit': ['afterFileEdit'], 'session-start': ['sessionStart'] },
35
+ copilot: { 'pre-shell': ['preToolUse'], 'pre-edit': ['preToolUse'], 'post-edit': ['postToolUse'], 'session-start': ['sessionStart'] },
36
+ opencode: { 'pre-shell': ['tool.execute.before'], 'pre-edit': ['tool.execute.before'], 'post-edit': ['tool.execute.after'] },
37
+ };
38
+
39
+ // Claude-style `{ <event>: [{ matcher, hooks: [entry] }] }` (Claude Code, Codex, Gemini CLI).
40
+ function removeGrouped(map, hook) {
41
+ let removed = 0;
42
+ for (const ev of Object.keys(map || {})) {
43
+ if (!Array.isArray(map[ev])) continue;
44
+ map[ev] = map[ev].filter((group) => {
45
+ if (!group || !Array.isArray(group.hooks)) return true;
46
+ const before = group.hooks.length;
47
+ group.hooks = group.hooks.filter((h) => !ours(h && h.command, hook));
48
+ removed += before - group.hooks.length;
49
+ return group.hooks.length > 0 || before === 0;
50
+ });
51
+ if (!map[ev].length) delete map[ev];
52
+ }
53
+ return removed;
54
+ }
55
+
56
+ // `{ <event>: [entry] }` (Kete Code, Cursor).
57
+ function removeFlat(map, hook) {
58
+ let removed = 0;
59
+ for (const ev of Object.keys(map || {})) {
60
+ if (!Array.isArray(map[ev])) continue;
61
+ const kept = map[ev].filter((h) => !ours(h && h.command, hook));
62
+ removed += map[ev].length - kept.length;
63
+ if (kept.length) map[ev] = kept;
64
+ else delete map[ev];
65
+ }
66
+ return removed;
67
+ }
68
+
69
+ function grouped(host, timeoutUnit = 1) {
70
+ return {
71
+ add: (data, cmd, hook, event) => {
72
+ const [ev, matcher] = EVENTS[host][event];
73
+ const entry = { type: 'command', command: cmd, timeout: TIMEOUT_S[event] * timeoutUnit };
74
+ if (host === 'gemini') entry.name = `kete-loom-${hook}`;
75
+ ((data.hooks ||= {})[ev] ||= []).push({ ...(matcher ? { matcher } : {}), hooks: [entry] });
76
+ },
77
+ remove: (data, hook) => removeGrouped(data.hooks, hook),
78
+ };
79
+ }
80
+
81
+ const pascal = (s) => s.replace(/(^|-)(\w)/g, (_, __, c) => c.toUpperCase());
82
+
83
+ const HOSTS = {
84
+ claude: { file: (home) => path.join(home, '.claude', 'settings.json'), ...grouped('claude') },
85
+ codex: {
86
+ standalone: true,
87
+ file: (home) => path.join(home, '.codex', 'hooks.json'),
88
+ ...grouped('codex'),
89
+ note: 'Codex runs a new or changed hook only after you trust it: open Codex and run /hooks.',
90
+ },
91
+ // Gemini CLI timeouts are milliseconds.
92
+ gemini: { file: (home) => path.join(home, '.gemini', 'settings.json'), ...grouped('gemini', 1000) },
93
+ kete: {
94
+ // Kete Code reads kete.json and kete.jsonc; edit the one that exists, preferring .jsonc.
95
+ file: (home) => {
96
+ const dir = path.join(home, '.config', 'kete');
97
+ const jsonc = path.join(dir, 'kete.jsonc');
98
+ return fs.existsSync(jsonc) ? jsonc : path.join(dir, 'kete.json');
99
+ },
100
+ add: (data, cmd, hook, event) => {
101
+ const [ev, match] = EVENTS.kete[event];
102
+ (((data.kete ||= {}).hooks ||= {})[ev] ||= []).push({ ...(match ? { match } : {}), command: cmd, timeout: TIMEOUT_S[event] });
103
+ },
104
+ remove: (data, hook) => removeFlat(data.kete && data.kete.hooks, hook),
105
+ },
106
+ cursor: {
107
+ standalone: true,
108
+ file: (home) => path.join(home, '.cursor', 'hooks.json'),
109
+ add: (data, cmd, hook, event) => {
110
+ data.version ||= 1;
111
+ ((data.hooks ||= {})[EVENTS.cursor[event][0]] ||= []).push({ command: cmd, timeout: TIMEOUT_S[event] });
112
+ },
113
+ remove: (data, hook) => removeFlat(data.hooks, hook),
114
+ },
115
+ copilot: {
116
+ own: true,
117
+ file: (home, hook) => path.join(process.env.COPILOT_HOME || path.join(home, '.copilot'), 'hooks', `kete-loom-${hook}.json`),
118
+ content: (cmd, hook, event) =>
119
+ JSON.stringify({ version: 1, hooks: { [EVENTS.copilot[event][0]]: [{ type: 'command', bash: cmd, powershell: `& ${cmd}`, timeoutSec: TIMEOUT_S[event] }] } }, null, 2) + '\n',
120
+ isOurs: (text, hook) => {
121
+ try {
122
+ return Object.values(JSON.parse(text).hooks).some((list) => list.some((h) => ours(h.bash, hook)));
123
+ } catch {
124
+ return false;
125
+ }
126
+ },
127
+ },
128
+ opencode: {
129
+ own: true,
130
+ file: (home, hook) => path.join(home, '.config', 'opencode', 'plugins', `kete-loom-${hook}.js`),
131
+ // OpenCode hooks are plugins: hand the raw call to the shared runner, and throw to block.
132
+ content: (cmd, hook, event, home, node = process.execPath) => {
133
+ const key = EVENTS.opencode[event][0];
134
+ const before = key === 'tool.execute.before';
135
+ return `// kete-loom hook ${hook} (${event}): generated by \`npx @ketecode/loom install --hook ${hook}\`; remove with uninstall.
136
+ // runner: ${cmd}
137
+ import { spawnSync } from "node:child_process"
138
+
139
+ export const KeteLoom${pascal(hook)} = async ({ directory }) => ({
140
+ ${JSON.stringify(key)}: async (input, output) => {
141
+ const r = spawnSync(${JSON.stringify(node)}, [${JSON.stringify(path.join(runtimeDir(home), 'run.js'))}, "opencode", ${JSON.stringify(hook)}, ${JSON.stringify(event)}], {
142
+ input: JSON.stringify({ tool: input && input.tool, args: (output && output.args) || (input && input.args), cwd: directory }),
143
+ encoding: "utf8",
144
+ timeout: ${TIMEOUT_S[event] * 1000},
145
+ })${before ? '\n if (r.status === 2) throw new Error((r.stderr || "").trim() || "Blocked by kete-loom")' : ''}
146
+ },
147
+ })
148
+ `;
149
+ },
150
+ isOurs: (text, hook) => ours((/^\/\/ runner: (.*)$/m.exec(text) || [])[1], hook),
151
+ },
152
+ };
153
+
154
+ const HOOK_HOSTS = Object.keys(HOSTS);
155
+ const supports = (host, event) => Boolean(EVENTS[host] && EVENTS[host][event]);
156
+
157
+ class CommentedFileError extends Error {}
158
+
159
+ // After removing our entries, drop the containers they leave empty (`hooks: {}`, `kete: {}`).
160
+ function prune(data) {
161
+ const empty = (v) => v && typeof v === 'object' && !Array.isArray(v) && !Object.keys(v).length;
162
+ if (data.kete && empty(data.kete.hooks)) delete data.kete.hooks;
163
+ if (empty(data.kete)) delete data.kete;
164
+ if (empty(data.hooks)) delete data.hooks;
165
+ return data;
166
+ }
167
+
168
+ function readJson(file) {
169
+ if (!fs.existsSync(file)) return {};
170
+ const text = fs.readFileSync(file, 'utf8');
171
+ if (!text.trim()) return {};
172
+ try {
173
+ const data = JSON.parse(text);
174
+ if (!data || typeof data !== 'object' || Array.isArray(data)) throw new Error('top level is not an object');
175
+ return data;
176
+ } catch (e) {
177
+ // shortcut: comment detection is a strip-and-retry, not a JSONC parser; fine for telling the cases apart.
178
+ const stripped = text.replace(/("(?:[^"\\]|\\.)*")|\/\/[^\n]*|\/\*[\s\S]*?\*\//g, (m, str) => str || '');
179
+ try {
180
+ JSON.parse(stripped.replace(/,(\s*[}\]])/g, '$1'));
181
+ } catch {
182
+ throw new Error(`${file} isn't valid JSON (${e.message}); not touching it`);
183
+ }
184
+ throw new CommentedFileError(`${file} has comments, and rewriting it would drop them`);
185
+ }
186
+ }
187
+
188
+ function writeAtomic(file, text) {
189
+ fs.mkdirSync(path.dirname(file), { recursive: true });
190
+ if (fs.existsSync(file)) fs.copyFileSync(file, `${file}.kete-loom-backup`);
191
+ const tmp = `${file}.kete-loom-tmp`;
192
+ fs.writeFileSync(tmp, text);
193
+ fs.renameSync(tmp, file);
194
+ }
195
+
196
+ function snippet(host, cmd, hook, event) {
197
+ const data = {};
198
+ HOSTS[host].add(data, cmd, hook, event);
199
+ return JSON.stringify(data, null, 2);
200
+ }
201
+
202
+ // Registers one hook with one host. Returns { action, dest, reason?, note? }.
203
+ function register(host, hook, event, { home, dryRun = false, node = process.execPath }) {
204
+ const h = HOSTS[host];
205
+ if (!supports(host, event)) return { action: 'skipped', dest: h.file(home, hook), reason: `${host} has no ${event} hook` };
206
+ const cmd = command(home, host, hook, event, node);
207
+ const file = h.file(home, hook);
208
+ if (h.own) {
209
+ const exists = fs.existsSync(file);
210
+ if (exists && !h.isOurs(fs.readFileSync(file, 'utf8'), hook)) return { action: 'skipped', dest: file, reason: 'a file with this name exists and was not written by kete-loom' };
211
+ if (!dryRun) writeAtomic(file, h.content(cmd, hook, event, home, node));
212
+ return { action: exists ? 'updated' : 'installed', dest: file, note: h.note };
213
+ }
214
+ let data;
215
+ try {
216
+ data = readJson(file);
217
+ } catch (e) {
218
+ const reason = e instanceof CommentedFileError ? `${e.message}. Add this yourself:\n${snippet(host, cmd, hook, event)}` : e.message;
219
+ return { action: 'skipped', dest: file, reason };
220
+ }
221
+ const existed = h.remove(data, hook) > 0;
222
+ h.add(data, cmd, hook, event);
223
+ if (!dryRun) writeAtomic(file, JSON.stringify(data, null, 2) + '\n');
224
+ return { action: existed ? 'updated' : 'installed', dest: file, note: h.note };
225
+ }
226
+
227
+ // Removes one hook (or every kete-loom hook when `hook` is null) from one host.
228
+ function unregister(host, hook, { home, dryRun = false, names = [] }) {
229
+ const h = HOSTS[host];
230
+ if (h.own) {
231
+ const removed = [];
232
+ for (const name of hook ? [hook] : names) {
233
+ const file = h.file(home, name);
234
+ if (fs.existsSync(file) && h.isOurs(fs.readFileSync(file, 'utf8'), name)) {
235
+ if (!dryRun) fs.rmSync(file);
236
+ removed.push({ name, dest: file });
237
+ }
238
+ }
239
+ return removed;
240
+ }
241
+ const file = h.file(home, hook);
242
+ if (!fs.existsSync(file)) return [];
243
+ let data;
244
+ try {
245
+ data = readJson(file);
246
+ } catch {
247
+ return []; // not ours to fix
248
+ }
249
+ const before = registered(host, { home });
250
+ if (!h.remove(data, hook)) return [];
251
+ prune(data);
252
+ if (!dryRun) {
253
+ // A standalone hooks file holding nothing but its version is deleted rather than left behind.
254
+ if (h.standalone && !Object.keys(data).filter((k) => k !== 'version').length) fs.rmSync(file);
255
+ else writeAtomic(file, JSON.stringify(data, null, 2) + '\n');
256
+ }
257
+ return before.filter((name) => !hook || name === hook).map((name) => ({ name, dest: file }));
258
+ }
259
+
260
+ // Names of kete-loom hooks registered with a host.
261
+ function registered(host, { home, names = [] }) {
262
+ const h = HOSTS[host];
263
+ if (h.own) return names.filter((n) => fs.existsSync(h.file(home, n)) && h.isOurs(fs.readFileSync(h.file(home, n), 'utf8'), n));
264
+ let data;
265
+ try {
266
+ data = readJson(h.file(home));
267
+ } catch {
268
+ return [];
269
+ }
270
+ const found = new Set();
271
+ (function walk(v) {
272
+ if (typeof v === 'string') {
273
+ const m = RUNNER_RE.exec(v);
274
+ if (m) found.add(m[2]);
275
+ } else if (v && typeof v === 'object') Object.values(v).forEach(walk);
276
+ })(data);
277
+ return [...found];
278
+ }
279
+
280
+ // Copies run.js and each hook's hook.js into ~/.kete-loom/hooks.
281
+ function installRuntime(hooks, { home, dryRun = false, sourceDir }) {
282
+ if (dryRun) return;
283
+ const dir = runtimeDir(home);
284
+ fs.mkdirSync(dir, { recursive: true });
285
+ fs.copyFileSync(path.join(sourceDir, 'run.js'), path.join(dir, 'run.js'));
286
+ for (const hook of hooks) {
287
+ fs.mkdirSync(path.join(dir, hook.dir), { recursive: true });
288
+ fs.copyFileSync(path.join(hook.path, 'hook.js'), path.join(dir, hook.dir, 'hook.js'));
289
+ }
290
+ }
291
+
292
+ module.exports = { HOSTS, HOOK_HOSTS, EVENTS, supports, register, unregister, registered, installRuntime, runtimeDir, command, ours, readJson, writeAtomic, CommentedFileError };
@@ -0,0 +1,51 @@
1
+ 'use strict';
2
+
3
+ // Translated READMEs (docs/i18n/README.<lang>.md) translate the English README's prose. The generated
4
+ // catalog tables stay English-only, so the hash below ignores them: a translation is only out of date
5
+ // when the prose it was made from changes.
6
+
7
+ const crypto = require('crypto');
8
+ const fs = require('fs');
9
+ const path = require('path');
10
+
11
+ const REPO_ROOT = path.join(__dirname, '..', '..');
12
+ const I18N_DIR = path.join(REPO_ROOT, 'docs', 'i18n');
13
+ const LANGUAGES = {
14
+ ar: 'العربية',
15
+ de: 'Deutsch',
16
+ es: 'Español',
17
+ fr: 'Français',
18
+ ja: '日本語',
19
+ 'pt-BR': 'Português (Brasil)',
20
+ sw: 'Kiswahili',
21
+ 'zh-CN': '简体中文',
22
+ };
23
+ // The searchable catalog site, deployed by .github/workflows/pages.yml.
24
+ const SITE_URL = 'https://kete-org.github.io/kete-loom/';
25
+ const SOURCE_RE = /<!-- translated from README\.md, source ([0-9a-f]{12}) -->/;
26
+ const GENERATED_RE = /(<!-- (\w+):start -->)[\s\S]*?(<!-- \2:end -->)/g;
27
+ const SWITCHER_RE = /<!-- languages:start -->[\s\S]*?<!-- languages:end -->/;
28
+
29
+ // The English prose with generated tables and the language switcher emptied.
30
+ const prose = (text) => text.replace(SWITCHER_RE, '').replace(GENERATED_RE, '$1$3');
31
+ const sourceHash = (text = fs.readFileSync(path.join(REPO_ROOT, 'README.md'), 'utf8')) =>
32
+ crypto.createHash('sha256').update(prose(text)).digest('hex').slice(0, 12);
33
+
34
+ const switcher = (from = '') =>
35
+ `<!-- languages:start -->\n<p align="center">${[['README.md', 'English'], ...Object.entries(LANGUAGES).map(([code, label]) => [`docs/i18n/README.${code}.md`, label])]
36
+ .map(([file, label]) => `<a href="${path.posix.relative(from || '.', file)}">${label}</a>`)
37
+ .join(' · ')} · <a href="${SITE_URL}">Browse the catalog ↗</a></p>\n<!-- languages:end -->`;
38
+
39
+ // Each translation that exists: its language and whether it matches the current English prose.
40
+ function translations() {
41
+ const current = sourceHash();
42
+ return Object.keys(LANGUAGES)
43
+ .map((code) => ({ code, file: path.join(I18N_DIR, `README.${code}.md`) }))
44
+ .filter((t) => fs.existsSync(t.file))
45
+ .map((t) => {
46
+ const m = SOURCE_RE.exec(fs.readFileSync(t.file, 'utf8'));
47
+ return { ...t, source: m && m[1], current: Boolean(m) && m[1] === current };
48
+ });
49
+ }
50
+
51
+ module.exports = { LANGUAGES, I18N_DIR, sourceHash, prose, switcher, translations, SOURCE_RE, SWITCHER_RE };
@@ -0,0 +1,85 @@
1
+ 'use strict';
2
+
3
+ // What kete-loom has installed, where, and at which version, read from the markers it writes:
4
+ // `.kete-loom.json` in skill folders, `kete-loom name@version` comments in agents, commands and rules,
5
+ // and the host configs for hooks, linked rules and MCP servers. `update` and `outdated` build on it.
6
+
7
+ const fs = require('fs');
8
+ const path = require('path');
9
+ const { TARGETS, KINDS, resolveTarget } = require('./targets');
10
+ const Hooks = require('./hooks-install');
11
+ const Rules = require('./rules-install');
12
+ const Mcp = require('./mcp-install');
13
+
14
+ const FILE_MARKER = /^(?:# |<!-- )kete-loom ([a-z0-9-]+)@([^\s>]+)/m;
15
+
16
+ function head(file) {
17
+ try {
18
+ const fd = fs.openSync(file, 'r');
19
+ const buf = Buffer.alloc(512);
20
+ const n = fs.readSync(fd, buf, 0, 512, 0);
21
+ fs.closeSync(fd);
22
+ return buf.toString('utf8', 0, n);
23
+ } catch {
24
+ return '';
25
+ }
26
+ }
27
+
28
+ function skillEntry(dir, entry) {
29
+ const p = path.join(dir, entry);
30
+ let stat;
31
+ try {
32
+ stat = fs.lstatSync(p);
33
+ } catch {
34
+ return null;
35
+ }
36
+ if (stat.isSymbolicLink()) return { name: entry, version: null, linked: true, path: p };
37
+ try {
38
+ const marker = JSON.parse(fs.readFileSync(path.join(p, '.kete-loom.json'), 'utf8'));
39
+ return { name: entry, version: marker.version || null, path: p };
40
+ } catch {
41
+ return null; // not installed by kete-loom
42
+ }
43
+ }
44
+
45
+ // Rows of { target, kind, name, version, path, linked? } for one scope.
46
+ function inventory({ scope = 'user', home, cwd = process.cwd(), mcpAll = Mcp.listMcp(), hookNames = [] }) {
47
+ const rows = [];
48
+ for (const target of Object.keys(TARGETS)) {
49
+ for (const kind of KINDS) {
50
+ const dir = resolveTarget(target, { scope, home, cwd, kind });
51
+ if (!dir || !fs.existsSync(dir)) continue;
52
+ for (const entry of fs.readdirSync(dir)) {
53
+ if (kind === 'skills') {
54
+ const s = skillEntry(dir, entry);
55
+ if (s) rows.push({ target, kind, ...s });
56
+ continue;
57
+ }
58
+ const m = FILE_MARKER.exec(head(path.join(dir, entry)));
59
+ if (m) rows.push({ target, kind, name: m[1], version: m[2], path: path.join(dir, entry) });
60
+ }
61
+ }
62
+ }
63
+ if (scope === 'user') {
64
+ for (const host of Rules.RULE_HOSTS) {
65
+ const dir = Rules.HOSTS[host].dir(home);
66
+ for (const name of Rules.installedNames(dir)) {
67
+ const m = FILE_MARKER.exec(head(path.join(dir, `${name}.md`)));
68
+ rows.push({ target: host, kind: 'rules', name, version: m ? m[2] : null, path: path.join(dir, `${name}.md`) });
69
+ }
70
+ }
71
+ for (const host of Hooks.HOOK_HOSTS) for (const name of Hooks.registered(host, { home, names: hookNames })) rows.push({ target: host, kind: 'hooks', name, version: null });
72
+ }
73
+ for (const host of Mcp.MCP_HOSTS) for (const name of Mcp.registeredMcp(host, mcpAll, { scope, home, cwd })) rows.push({ target: host, kind: 'mcp', name, version: null });
74
+ return rows;
75
+ }
76
+
77
+ // -1, 0 or 1 for two x.y.z versions.
78
+ function compare(a, b) {
79
+ const pa = String(a).split('.').map(Number);
80
+ const pb = String(b).split('.').map(Number);
81
+ for (let i = 0; i < 3; i++) if ((pa[i] || 0) !== (pb[i] || 0)) return (pa[i] || 0) < (pb[i] || 0) ? -1 : 1;
82
+ return 0;
83
+ }
84
+
85
+ module.exports = { inventory, compare };
@@ -0,0 +1,158 @@
1
+ 'use strict';
2
+
3
+ // The spec rules for a single item (docs/SKILL_SPEC.md). Shipped with the CLI so `kete-loom check` can
4
+ // lint skills, agents, commands, rules and hooks you write yourself; scripts/lint-skills.js adds the
5
+ // repository-wide checks on top.
6
+
7
+ const fs = require('fs');
8
+ const path = require('path');
9
+ const { TOOLS } = require('./render');
10
+
11
+ const NAME_RE = /^[a-z0-9]+(-[a-z0-9]+)*$/;
12
+ const SEMVER_RE = /^\d+\.\d+\.\d+$/;
13
+ const TOP_KEYS = new Set(['name', 'description', 'license', 'compatibility', 'metadata']);
14
+ const CATEGORIES = ['coding', 'testing', 'security', 'integrations', 'devops', 'data', 'ai', 'docs', 'business', 'compliance', 'meta'];
15
+ const RISKS = ['read-only', 'writes-files', 'runs-commands', 'network'];
16
+ const MAX_LINES = 300;
17
+ const MIN_EVALS = 3;
18
+ const HOOK_EVENTS = ['pre-shell', 'pre-edit', 'post-edit', 'session-start'];
19
+ const MAX_RULE_LINES = 80;
20
+ // Tool names that only exist in one host. Skills should describe the action instead.
21
+ const HOST_SPECIFIC = /\b(Bash tool|Read tool|Edit tool|Write tool|Grep tool|Glob tool|TodoWrite|apply_patch|str_replace_editor)\b/;
22
+
23
+ function words(s) {
24
+ return new Set(String(s).toLowerCase().match(/[a-z]{4,}/g) || []);
25
+ }
26
+
27
+ function jaccard(a, b) {
28
+ const inter = [...a].filter((x) => b.has(x)).length;
29
+ return inter / (a.size + b.size - inter || 1);
30
+ }
31
+
32
+ function lintItem(item) {
33
+ const errors = [...item.parseErrors];
34
+ const warnings = [];
35
+ const d = item.data;
36
+ const meta = d.metadata && typeof d.metadata === 'object' ? d.metadata : null;
37
+
38
+ for (const key of Object.keys(d)) if (!TOP_KEYS.has(key)) errors.push(`unknown top-level key "${key}" (put host-specific keys under metadata)`);
39
+
40
+ if (!d.name) errors.push('missing "name"');
41
+ else {
42
+ if (!NAME_RE.test(d.name)) errors.push(`name "${d.name}" must match ${NAME_RE}`);
43
+ if (d.name.length > 64) errors.push('name longer than 64 characters');
44
+ if (d.name !== item.dir) errors.push(`name "${d.name}" must equal ${item.kind === 'command' ? 'file' : 'folder'} name "${item.dir}"`);
45
+ }
46
+
47
+ if (!d.description) errors.push('missing "description"');
48
+ else if (item.kind === 'command' || item.kind === 'hook' || item.kind === 'rule') {
49
+ // Commands are picked by name and hooks run on events; the description is one line for menus and the catalog.
50
+ const len = d.description.length;
51
+ if (len < 20 || len > 300) errors.push(`description is ${len} chars; must be 20-300`);
52
+ } else {
53
+ const len = d.description.length;
54
+ if (len < 80 || len > 1024) errors.push(`description is ${len} chars; must be 80-1024`);
55
+ if (!/\buse when\b/i.test(d.description)) errors.push(`description must say when to use the ${item.kind} ("Use when ...")`);
56
+ }
57
+
58
+ if (!d.license) errors.push('missing "license"');
59
+ if (d.compatibility && d.compatibility.length > 500) errors.push('compatibility longer than 500 characters');
60
+
61
+ if (!meta) errors.push('missing "metadata" map');
62
+ else {
63
+ const v = meta['kete-loom/version'];
64
+ if (!v) errors.push('missing metadata.kete-loom/version');
65
+ else if (!SEMVER_RE.test(v)) errors.push(`kete-loom/version "${v}" is not semver`);
66
+ const c = meta['kete-loom/category'];
67
+ if (!CATEGORIES.includes(c)) errors.push(`kete-loom/category must be one of ${CATEGORIES.join(', ')}`);
68
+ const r = meta['kete-loom/risk'];
69
+ if (!RISKS.includes(r)) errors.push(`kete-loom/risk must be one of ${RISKS.join(', ')}`);
70
+ if (item.tools) {
71
+ const bad = item.tools.filter((t) => !TOOLS.includes(t));
72
+ if (bad.length || !item.tools.length) errors.push(`kete-loom/tools must be a comma list of ${TOOLS.join(', ')}`);
73
+ }
74
+ }
75
+
76
+ const lines = item.text.replace(/\r?\n$/, '').split('\n').length;
77
+ if (lines > MAX_LINES) errors.push(`${path.basename(item.file)} is ${lines} lines; limit is ${MAX_LINES}. Move detail into references/.`);
78
+
79
+ // Every relative file the body points at must exist.
80
+ const refs = new Set();
81
+ for (const m of item.body.matchAll(/\]\((?!https?:|#|mailto:)([^)\s]+)\)/g)) refs.add(m[1]);
82
+ for (const m of item.body.matchAll(/`((?:references|scripts|assets|templates)\/[^`\s]+)`/g)) refs.add(m[1]);
83
+ for (const ref of refs) {
84
+ if (!fs.existsSync(path.join(item.kind === 'command' ? path.dirname(item.path) : item.path, ref.split('#')[0]))) errors.push(`referenced file not found: ${ref}`);
85
+ }
86
+
87
+ const scriptsDir = path.join(item.path, 'scripts');
88
+ if (fs.existsSync(scriptsDir)) {
89
+ for (const f of fs.readdirSync(scriptsDir)) {
90
+ const p = path.join(scriptsDir, f);
91
+ const head = fs.readFileSync(p, 'utf8').slice(0, 2);
92
+ if (head === '#!' && !(fs.statSync(p).mode & 0o111) && process.platform !== 'win32') errors.push(`scripts/${f} has a shebang but is not executable (chmod +x)`);
93
+ }
94
+ }
95
+
96
+ if (item.kind === 'hook') errors.push(...lintHook(item));
97
+ // Rules load into every matching session, so they stay short: the always-on cost is paid on every turn.
98
+ if (item.kind === 'rule' && lines > MAX_RULE_LINES) errors.push(`rule is ${lines} lines; rules are loaded into context automatically, keep them under ${MAX_RULE_LINES}`);
99
+ if (item.kind === 'rule' && item.paths.some((p) => /\s/.test(p) || p.startsWith('/'))) errors.push('kete-loom/paths must be relative globs without spaces, comma separated (e.g. "**/*.py, **/*.pyi")');
100
+
101
+ // Hosts route to skills and agents by description, so both need trigger evals. Commands and hooks aren't routed.
102
+ const evalFile = ['command', 'hook', 'rule'].includes(item.kind) ? null : path.join(item.path, 'evals', 'triggers.json');
103
+ if (evalFile && !fs.existsSync(evalFile)) errors.push('missing evals/triggers.json');
104
+ else if (evalFile) {
105
+ try {
106
+ const ev = JSON.parse(fs.readFileSync(evalFile, 'utf8'));
107
+ for (const k of ['should_trigger', 'should_not_trigger']) {
108
+ if (!Array.isArray(ev[k]) || ev[k].length < MIN_EVALS) errors.push(`evals/triggers.json needs at least ${MIN_EVALS} "${k}" prompts`);
109
+ }
110
+ } catch (e) {
111
+ errors.push(`evals/triggers.json is not valid JSON: ${e.message}`);
112
+ }
113
+ }
114
+
115
+ const hostHit = HOST_SPECIFIC.exec(item.body);
116
+ if (hostHit) warnings.push(`mentions host-specific tool "${hostHit[1]}"; describe the action instead so every host can follow it`);
117
+
118
+ return { errors, warnings };
119
+ }
120
+
121
+ // A hook ships hook.js (exports check). Blocking (pre-*) hooks also ship cases.json, and every case must
122
+ // give the expected verdict; a case is a command string or an event object such as { files: [...] }.
123
+ function lintHook(hook) {
124
+ const errors = [];
125
+ if (!HOOK_EVENTS.includes(hook.event)) errors.push(`kete-loom/event must be one of ${HOOK_EVENTS.join(', ')}`);
126
+ let check;
127
+ try {
128
+ ({ check } = require(path.join(hook.path, 'hook.js')));
129
+ } catch (e) {
130
+ return [...errors, `hook.js failed to load: ${e.message}`];
131
+ }
132
+ if (typeof check !== 'function') return [...errors, 'hook.js must export a check(event) function'];
133
+ if (!String(hook.event).startsWith('pre-')) return errors;
134
+ let cases;
135
+ try {
136
+ // Fake secrets are written as {{repeat:c:n}} so the repository never holds key-shaped strings.
137
+ cases = JSON.parse(fs.readFileSync(path.join(hook.path, 'cases.json'), 'utf8').replace(/\{\{repeat:(.):(\d+)\}\}/g, (_, c, n) => c.repeat(Number(n))));
138
+ } catch (e) {
139
+ return [...errors, `cases.json missing or invalid: ${e.message}`];
140
+ }
141
+ for (const [key, want] of [['should_block', true], ['should_allow', false]]) {
142
+ if (!Array.isArray(cases[key]) || cases[key].length < MIN_EVALS) errors.push(`cases.json needs at least ${MIN_EVALS} "${key}" cases`);
143
+ for (const c of cases[key] || []) {
144
+ const event = typeof c === 'string' ? { command: c, cwd: process.cwd() } : { cwd: process.cwd(), ...c };
145
+ let got;
146
+ try {
147
+ got = Boolean(check(event).block);
148
+ } catch (e) {
149
+ errors.push(`check() threw on ${JSON.stringify(c)}: ${e.message}`);
150
+ continue;
151
+ }
152
+ if (got !== want) errors.push(`case ${JSON.stringify(c)} should ${want ? 'block' : 'be allowed'}`);
153
+ }
154
+ }
155
+ return errors;
156
+ }
157
+
158
+ module.exports = { lintItem, lintHook, words, jaccard, NAME_RE, SEMVER_RE, CATEGORIES, RISKS, MAX_LINES, HOOK_EVENTS };