@takuma-hirai/hirai-method 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 (822) hide show
  1. package/.claude/.stale-harness-state/last-check +0 -0
  2. package/.claude/CommonRules.md +121 -0
  3. package/.claude/agents/01-core-development/api-designer.md +237 -0
  4. package/.claude/agents/01-core-development/backend-developer.md +222 -0
  5. package/.claude/agents/01-core-development/design-bridge.md +127 -0
  6. package/.claude/agents/01-core-development/electron-pro.md +240 -0
  7. package/.claude/agents/01-core-development/frontend-developer.md +133 -0
  8. package/.claude/agents/01-core-development/fullstack-developer.md +235 -0
  9. package/.claude/agents/01-core-development/graphql-architect.md +238 -0
  10. package/.claude/agents/01-core-development/microservices-architect.md +239 -0
  11. package/.claude/agents/01-core-development/mobile-developer.md +283 -0
  12. package/.claude/agents/01-core-development/ui-designer.md +174 -0
  13. package/.claude/agents/01-core-development/websocket-engineer.md +150 -0
  14. package/.claude/agents/03-infrastructure/azure-infra-engineer.md +53 -0
  15. package/.claude/agents/03-infrastructure/cloud-architect.md +277 -0
  16. package/.claude/agents/03-infrastructure/database-administrator.md +287 -0
  17. package/.claude/agents/03-infrastructure/deployment-engineer.md +287 -0
  18. package/.claude/agents/03-infrastructure/devops-engineer.md +287 -0
  19. package/.claude/agents/03-infrastructure/devops-incident-responder.md +287 -0
  20. package/.claude/agents/03-infrastructure/docker-expert.md +278 -0
  21. package/.claude/agents/03-infrastructure/incident-responder.md +287 -0
  22. package/.claude/agents/03-infrastructure/kubernetes-specialist.md +287 -0
  23. package/.claude/agents/03-infrastructure/network-engineer.md +287 -0
  24. package/.claude/agents/03-infrastructure/platform-engineer.md +287 -0
  25. package/.claude/agents/03-infrastructure/security-engineer.md +277 -0
  26. package/.claude/agents/03-infrastructure/sre-engineer.md +287 -0
  27. package/.claude/agents/03-infrastructure/terraform-engineer.md +287 -0
  28. package/.claude/agents/03-infrastructure/terragrunt-expert.md +307 -0
  29. package/.claude/agents/03-infrastructure/windows-infra-admin.md +52 -0
  30. package/.claude/agents/04-quality-security/accessibility-tester.md +277 -0
  31. package/.claude/agents/04-quality-security/ad-security-reviewer.md +56 -0
  32. package/.claude/agents/04-quality-security/ai-writing-auditor.md +77 -0
  33. package/.claude/agents/04-quality-security/architect-reviewer.md +287 -0
  34. package/.claude/agents/04-quality-security/chaos-engineer.md +277 -0
  35. package/.claude/agents/04-quality-security/code-reviewer.md +287 -0
  36. package/.claude/agents/04-quality-security/compliance-auditor.md +277 -0
  37. package/.claude/agents/04-quality-security/debugger.md +287 -0
  38. package/.claude/agents/04-quality-security/error-detective.md +287 -0
  39. package/.claude/agents/04-quality-security/penetration-tester.md +287 -0
  40. package/.claude/agents/04-quality-security/performance-engineer.md +287 -0
  41. package/.claude/agents/04-quality-security/powershell-security-hardening.md +54 -0
  42. package/.claude/agents/04-quality-security/qa-expert.md +287 -0
  43. package/.claude/agents/04-quality-security/security-auditor.md +287 -0
  44. package/.claude/agents/04-quality-security/test-automator.md +287 -0
  45. package/.claude/agents/04-quality-security/ui-ux-tester.md +234 -0
  46. package/.claude/agents/06-developer-experience/build-engineer.md +286 -0
  47. package/.claude/agents/06-developer-experience/cli-developer.md +286 -0
  48. package/.claude/agents/06-developer-experience/dependency-manager.md +286 -0
  49. package/.claude/agents/06-developer-experience/documentation-engineer.md +276 -0
  50. package/.claude/agents/06-developer-experience/dx-optimizer.md +286 -0
  51. package/.claude/agents/06-developer-experience/git-workflow-manager.md +286 -0
  52. package/.claude/agents/06-developer-experience/legacy-modernizer.md +286 -0
  53. package/.claude/agents/06-developer-experience/mcp-developer.md +275 -0
  54. package/.claude/agents/06-developer-experience/powershell-module-architect.md +58 -0
  55. package/.claude/agents/06-developer-experience/powershell-ui-architect.md +135 -0
  56. package/.claude/agents/06-developer-experience/readme-generator.md +238 -0
  57. package/.claude/agents/06-developer-experience/refactoring-specialist.md +286 -0
  58. package/.claude/agents/06-developer-experience/slack-expert.md +232 -0
  59. package/.claude/agents/06-developer-experience/tooling-engineer.md +286 -0
  60. package/.claude/agents/09-meta-orchestration/agent-installer.md +97 -0
  61. package/.claude/agents/09-meta-orchestration/agent-organizer.md +287 -0
  62. package/.claude/agents/09-meta-orchestration/codebase-orchestrator.md +249 -0
  63. package/.claude/agents/09-meta-orchestration/context-manager.md +287 -0
  64. package/.claude/agents/09-meta-orchestration/error-coordinator.md +287 -0
  65. package/.claude/agents/09-meta-orchestration/it-ops-orchestrator.md +60 -0
  66. package/.claude/agents/09-meta-orchestration/knowledge-synthesizer.md +287 -0
  67. package/.claude/agents/09-meta-orchestration/multi-agent-coordinator.md +287 -0
  68. package/.claude/agents/09-meta-orchestration/performance-monitor.md +287 -0
  69. package/.claude/agents/09-meta-orchestration/task-distributor.md +287 -0
  70. package/.claude/agents/09-meta-orchestration/workflow-orchestrator.md +287 -0
  71. package/.claude/agents/10-research-analysis/competitive-analyst.md +287 -0
  72. package/.claude/agents/10-research-analysis/data-researcher.md +287 -0
  73. package/.claude/agents/10-research-analysis/market-researcher.md +287 -0
  74. package/.claude/agents/10-research-analysis/project-idea-validator.md +269 -0
  75. package/.claude/agents/10-research-analysis/research-analyst.md +287 -0
  76. package/.claude/agents/10-research-analysis/scientific-literature-researcher.md +151 -0
  77. package/.claude/agents/10-research-analysis/search-specialist.md +287 -0
  78. package/.claude/agents/10-research-analysis/trend-analyst.md +287 -0
  79. package/.claude/archive/README.md +47 -0
  80. package/.claude/archive/agents/02-language-specialists/angular-architect.md +287 -0
  81. package/.claude/archive/agents/02-language-specialists/cpp-pro.md +277 -0
  82. package/.claude/archive/agents/02-language-specialists/csharp-developer.md +287 -0
  83. package/.claude/archive/agents/02-language-specialists/django-developer.md +287 -0
  84. package/.claude/archive/agents/02-language-specialists/dotnet-core-expert.md +287 -0
  85. package/.claude/archive/agents/02-language-specialists/dotnet-framework-4.8-expert.md +306 -0
  86. package/.claude/archive/agents/02-language-specialists/elixir-expert.md +311 -0
  87. package/.claude/archive/agents/02-language-specialists/expo-react-native-expert.md +268 -0
  88. package/.claude/archive/agents/02-language-specialists/fastapi-developer.md +287 -0
  89. package/.claude/archive/agents/02-language-specialists/flutter-expert.md +287 -0
  90. package/.claude/archive/agents/02-language-specialists/golang-pro.md +277 -0
  91. package/.claude/archive/agents/02-language-specialists/java-architect.md +287 -0
  92. package/.claude/archive/agents/02-language-specialists/javascript-pro.md +277 -0
  93. package/.claude/archive/agents/02-language-specialists/kotlin-specialist.md +287 -0
  94. package/.claude/archive/agents/02-language-specialists/laravel-specialist.md +287 -0
  95. package/.claude/archive/agents/02-language-specialists/nextjs-developer.md +287 -0
  96. package/.claude/archive/agents/02-language-specialists/node-specialist.md +124 -0
  97. package/.claude/archive/agents/02-language-specialists/php-pro.md +287 -0
  98. package/.claude/archive/agents/02-language-specialists/powershell-5.1-expert.md +59 -0
  99. package/.claude/archive/agents/02-language-specialists/powershell-7-expert.md +57 -0
  100. package/.claude/archive/agents/02-language-specialists/python-pro.md +277 -0
  101. package/.claude/archive/agents/02-language-specialists/rails-expert.md +358 -0
  102. package/.claude/archive/agents/02-language-specialists/react-specialist.md +287 -0
  103. package/.claude/archive/agents/02-language-specialists/rust-engineer.md +287 -0
  104. package/.claude/archive/agents/02-language-specialists/spring-boot-engineer.md +287 -0
  105. package/.claude/archive/agents/02-language-specialists/sql-pro.md +287 -0
  106. package/.claude/archive/agents/02-language-specialists/swift-expert.md +287 -0
  107. package/.claude/archive/agents/02-language-specialists/symfony-specialist.md +354 -0
  108. package/.claude/archive/agents/02-language-specialists/typescript-pro.md +277 -0
  109. package/.claude/archive/agents/02-language-specialists/vue-expert.md +287 -0
  110. package/.claude/archive/agents/05-data-ai/ai-engineer.md +287 -0
  111. package/.claude/archive/agents/05-data-ai/data-analyst.md +277 -0
  112. package/.claude/archive/agents/05-data-ai/data-engineer.md +287 -0
  113. package/.claude/archive/agents/05-data-ai/data-scientist.md +287 -0
  114. package/.claude/archive/agents/05-data-ai/database-optimizer.md +287 -0
  115. package/.claude/archive/agents/05-data-ai/llm-architect.md +287 -0
  116. package/.claude/archive/agents/05-data-ai/machine-learning-engineer.md +277 -0
  117. package/.claude/archive/agents/05-data-ai/ml-engineer.md +287 -0
  118. package/.claude/archive/agents/05-data-ai/mlops-engineer.md +287 -0
  119. package/.claude/archive/agents/05-data-ai/nlp-engineer.md +287 -0
  120. package/.claude/archive/agents/05-data-ai/postgres-pro.md +287 -0
  121. package/.claude/archive/agents/05-data-ai/prompt-engineer.md +287 -0
  122. package/.claude/archive/agents/05-data-ai/reinforcement-learning-engineer.md +277 -0
  123. package/.claude/archive/agents/07-specialized-domains/api-documenter.md +277 -0
  124. package/.claude/archive/agents/07-specialized-domains/blockchain-developer.md +287 -0
  125. package/.claude/archive/agents/07-specialized-domains/embedded-systems.md +287 -0
  126. package/.claude/archive/agents/07-specialized-domains/fintech-engineer.md +287 -0
  127. package/.claude/archive/agents/07-specialized-domains/game-developer.md +287 -0
  128. package/.claude/archive/agents/07-specialized-domains/healthcare-admin.md +199 -0
  129. package/.claude/archive/agents/07-specialized-domains/iot-engineer.md +287 -0
  130. package/.claude/archive/agents/07-specialized-domains/m365-admin.md +48 -0
  131. package/.claude/archive/agents/07-specialized-domains/mobile-app-developer.md +287 -0
  132. package/.claude/archive/agents/07-specialized-domains/payment-integration.md +287 -0
  133. package/.claude/archive/agents/07-specialized-domains/quant-analyst.md +287 -0
  134. package/.claude/archive/agents/07-specialized-domains/risk-manager.md +287 -0
  135. package/.claude/archive/agents/07-specialized-domains/seo-specialist.md +184 -0
  136. package/.claude/archive/agents/08-business-product/business-analyst.md +287 -0
  137. package/.claude/archive/agents/08-business-product/content-marketer.md +287 -0
  138. package/.claude/archive/agents/08-business-product/customer-success-manager.md +287 -0
  139. package/.claude/archive/agents/08-business-product/legal-advisor.md +287 -0
  140. package/.claude/archive/agents/08-business-product/license-engineer.md +295 -0
  141. package/.claude/archive/agents/08-business-product/product-manager.md +287 -0
  142. package/.claude/archive/agents/08-business-product/project-manager.md +287 -0
  143. package/.claude/archive/agents/08-business-product/sales-engineer.md +287 -0
  144. package/.claude/archive/agents/08-business-product/scrum-master.md +287 -0
  145. package/.claude/archive/agents/08-business-product/technical-writer.md +287 -0
  146. package/.claude/archive/agents/08-business-product/ux-researcher.md +287 -0
  147. package/.claude/archive/agents/08-business-product/wordpress-master.md +316 -0
  148. package/.claude/archive/skills/competitive-ads-extractor/SKILL.md +293 -0
  149. package/.claude/archive/skills/developer-growth-analysis/SKILL.md +322 -0
  150. package/.claude/archive/skills/document-docx/LICENSE.txt +30 -0
  151. package/.claude/archive/skills/document-docx/SKILL.md +197 -0
  152. package/.claude/archive/skills/document-docx/docx-js.md +350 -0
  153. package/.claude/archive/skills/document-docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-chart.xsd +1499 -0
  154. package/.claude/archive/skills/document-docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd +146 -0
  155. package/.claude/archive/skills/document-docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd +1085 -0
  156. package/.claude/archive/skills/document-docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd +11 -0
  157. package/.claude/archive/skills/document-docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-main.xsd +3081 -0
  158. package/.claude/archive/skills/document-docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-picture.xsd +23 -0
  159. package/.claude/archive/skills/document-docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd +185 -0
  160. package/.claude/archive/skills/document-docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd +287 -0
  161. package/.claude/archive/skills/document-docx/ooxml/schemas/ISO-IEC29500-4_2016/pml.xsd +1676 -0
  162. package/.claude/archive/skills/document-docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd +28 -0
  163. package/.claude/archive/skills/document-docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd +144 -0
  164. package/.claude/archive/skills/document-docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd +174 -0
  165. package/.claude/archive/skills/document-docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd +25 -0
  166. package/.claude/archive/skills/document-docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd +18 -0
  167. package/.claude/archive/skills/document-docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd +59 -0
  168. package/.claude/archive/skills/document-docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd +56 -0
  169. package/.claude/archive/skills/document-docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd +195 -0
  170. package/.claude/archive/skills/document-docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-math.xsd +582 -0
  171. package/.claude/archive/skills/document-docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd +25 -0
  172. package/.claude/archive/skills/document-docx/ooxml/schemas/ISO-IEC29500-4_2016/sml.xsd +4439 -0
  173. package/.claude/archive/skills/document-docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-main.xsd +570 -0
  174. package/.claude/archive/skills/document-docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd +509 -0
  175. package/.claude/archive/skills/document-docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd +12 -0
  176. package/.claude/archive/skills/document-docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd +108 -0
  177. package/.claude/archive/skills/document-docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd +96 -0
  178. package/.claude/archive/skills/document-docx/ooxml/schemas/ISO-IEC29500-4_2016/wml.xsd +3646 -0
  179. package/.claude/archive/skills/document-docx/ooxml/schemas/ISO-IEC29500-4_2016/xml.xsd +116 -0
  180. package/.claude/archive/skills/document-docx/ooxml/schemas/ecma/fouth-edition/opc-contentTypes.xsd +42 -0
  181. package/.claude/archive/skills/document-docx/ooxml/schemas/ecma/fouth-edition/opc-coreProperties.xsd +50 -0
  182. package/.claude/archive/skills/document-docx/ooxml/schemas/ecma/fouth-edition/opc-digSig.xsd +49 -0
  183. package/.claude/archive/skills/document-docx/ooxml/schemas/ecma/fouth-edition/opc-relationships.xsd +33 -0
  184. package/.claude/archive/skills/document-docx/ooxml/schemas/mce/mc.xsd +75 -0
  185. package/.claude/archive/skills/document-docx/ooxml/schemas/microsoft/wml-2010.xsd +560 -0
  186. package/.claude/archive/skills/document-docx/ooxml/schemas/microsoft/wml-2012.xsd +67 -0
  187. package/.claude/archive/skills/document-docx/ooxml/schemas/microsoft/wml-2018.xsd +14 -0
  188. package/.claude/archive/skills/document-docx/ooxml/schemas/microsoft/wml-cex-2018.xsd +20 -0
  189. package/.claude/archive/skills/document-docx/ooxml/schemas/microsoft/wml-cid-2016.xsd +13 -0
  190. package/.claude/archive/skills/document-docx/ooxml/schemas/microsoft/wml-sdtdatahash-2020.xsd +4 -0
  191. package/.claude/archive/skills/document-docx/ooxml/schemas/microsoft/wml-symex-2015.xsd +8 -0
  192. package/.claude/archive/skills/document-docx/ooxml/scripts/pack.py +159 -0
  193. package/.claude/archive/skills/document-docx/ooxml/scripts/unpack.py +29 -0
  194. package/.claude/archive/skills/document-docx/ooxml/scripts/validate.py +69 -0
  195. package/.claude/archive/skills/document-docx/ooxml/scripts/validation/__init__.py +15 -0
  196. package/.claude/archive/skills/document-docx/ooxml/scripts/validation/base.py +951 -0
  197. package/.claude/archive/skills/document-docx/ooxml/scripts/validation/docx.py +274 -0
  198. package/.claude/archive/skills/document-docx/ooxml/scripts/validation/pptx.py +315 -0
  199. package/.claude/archive/skills/document-docx/ooxml/scripts/validation/redlining.py +279 -0
  200. package/.claude/archive/skills/document-docx/ooxml.md +610 -0
  201. package/.claude/archive/skills/document-docx/scripts/__init__.py +1 -0
  202. package/.claude/archive/skills/document-docx/scripts/document.py +1276 -0
  203. package/.claude/archive/skills/document-docx/scripts/templates/comments.xml +3 -0
  204. package/.claude/archive/skills/document-docx/scripts/templates/commentsExtended.xml +3 -0
  205. package/.claude/archive/skills/document-docx/scripts/templates/commentsExtensible.xml +3 -0
  206. package/.claude/archive/skills/document-docx/scripts/templates/commentsIds.xml +3 -0
  207. package/.claude/archive/skills/document-docx/scripts/templates/people.xml +3 -0
  208. package/.claude/archive/skills/document-docx/scripts/utilities.py +374 -0
  209. package/.claude/archive/skills/document-pdf/LICENSE.txt +30 -0
  210. package/.claude/archive/skills/document-pdf/SKILL.md +294 -0
  211. package/.claude/archive/skills/document-pdf/forms.md +205 -0
  212. package/.claude/archive/skills/document-pdf/reference.md +612 -0
  213. package/.claude/archive/skills/document-pdf/scripts/check_bounding_boxes.py +70 -0
  214. package/.claude/archive/skills/document-pdf/scripts/check_bounding_boxes_test.py +226 -0
  215. package/.claude/archive/skills/document-pdf/scripts/check_fillable_fields.py +12 -0
  216. package/.claude/archive/skills/document-pdf/scripts/convert_pdf_to_images.py +35 -0
  217. package/.claude/archive/skills/document-pdf/scripts/create_validation_image.py +41 -0
  218. package/.claude/archive/skills/document-pdf/scripts/extract_form_field_info.py +152 -0
  219. package/.claude/archive/skills/document-pdf/scripts/fill_fillable_fields.py +114 -0
  220. package/.claude/archive/skills/document-pdf/scripts/fill_pdf_form_with_annotations.py +108 -0
  221. package/.claude/archive/skills/document-pptx/LICENSE.txt +30 -0
  222. package/.claude/archive/skills/document-pptx/SKILL.md +484 -0
  223. package/.claude/archive/skills/document-pptx/html2pptx.md +625 -0
  224. package/.claude/archive/skills/document-pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-chart.xsd +1499 -0
  225. package/.claude/archive/skills/document-pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd +146 -0
  226. package/.claude/archive/skills/document-pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd +1085 -0
  227. package/.claude/archive/skills/document-pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd +11 -0
  228. package/.claude/archive/skills/document-pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-main.xsd +3081 -0
  229. package/.claude/archive/skills/document-pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-picture.xsd +23 -0
  230. package/.claude/archive/skills/document-pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd +185 -0
  231. package/.claude/archive/skills/document-pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd +287 -0
  232. package/.claude/archive/skills/document-pptx/ooxml/schemas/ISO-IEC29500-4_2016/pml.xsd +1676 -0
  233. package/.claude/archive/skills/document-pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd +28 -0
  234. package/.claude/archive/skills/document-pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd +144 -0
  235. package/.claude/archive/skills/document-pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd +174 -0
  236. package/.claude/archive/skills/document-pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd +25 -0
  237. package/.claude/archive/skills/document-pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd +18 -0
  238. package/.claude/archive/skills/document-pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd +59 -0
  239. package/.claude/archive/skills/document-pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd +56 -0
  240. package/.claude/archive/skills/document-pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd +195 -0
  241. package/.claude/archive/skills/document-pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-math.xsd +582 -0
  242. package/.claude/archive/skills/document-pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd +25 -0
  243. package/.claude/archive/skills/document-pptx/ooxml/schemas/ISO-IEC29500-4_2016/sml.xsd +4439 -0
  244. package/.claude/archive/skills/document-pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-main.xsd +570 -0
  245. package/.claude/archive/skills/document-pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd +509 -0
  246. package/.claude/archive/skills/document-pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd +12 -0
  247. package/.claude/archive/skills/document-pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd +108 -0
  248. package/.claude/archive/skills/document-pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd +96 -0
  249. package/.claude/archive/skills/document-pptx/ooxml/schemas/ISO-IEC29500-4_2016/wml.xsd +3646 -0
  250. package/.claude/archive/skills/document-pptx/ooxml/schemas/ISO-IEC29500-4_2016/xml.xsd +116 -0
  251. package/.claude/archive/skills/document-pptx/ooxml/schemas/ecma/fouth-edition/opc-contentTypes.xsd +42 -0
  252. package/.claude/archive/skills/document-pptx/ooxml/schemas/ecma/fouth-edition/opc-coreProperties.xsd +50 -0
  253. package/.claude/archive/skills/document-pptx/ooxml/schemas/ecma/fouth-edition/opc-digSig.xsd +49 -0
  254. package/.claude/archive/skills/document-pptx/ooxml/schemas/ecma/fouth-edition/opc-relationships.xsd +33 -0
  255. package/.claude/archive/skills/document-pptx/ooxml/schemas/mce/mc.xsd +75 -0
  256. package/.claude/archive/skills/document-pptx/ooxml/schemas/microsoft/wml-2010.xsd +560 -0
  257. package/.claude/archive/skills/document-pptx/ooxml/schemas/microsoft/wml-2012.xsd +67 -0
  258. package/.claude/archive/skills/document-pptx/ooxml/schemas/microsoft/wml-2018.xsd +14 -0
  259. package/.claude/archive/skills/document-pptx/ooxml/schemas/microsoft/wml-cex-2018.xsd +20 -0
  260. package/.claude/archive/skills/document-pptx/ooxml/schemas/microsoft/wml-cid-2016.xsd +13 -0
  261. package/.claude/archive/skills/document-pptx/ooxml/schemas/microsoft/wml-sdtdatahash-2020.xsd +4 -0
  262. package/.claude/archive/skills/document-pptx/ooxml/schemas/microsoft/wml-symex-2015.xsd +8 -0
  263. package/.claude/archive/skills/document-pptx/ooxml/scripts/pack.py +159 -0
  264. package/.claude/archive/skills/document-pptx/ooxml/scripts/unpack.py +29 -0
  265. package/.claude/archive/skills/document-pptx/ooxml/scripts/validate.py +69 -0
  266. package/.claude/archive/skills/document-pptx/ooxml/scripts/validation/__init__.py +15 -0
  267. package/.claude/archive/skills/document-pptx/ooxml/scripts/validation/base.py +951 -0
  268. package/.claude/archive/skills/document-pptx/ooxml/scripts/validation/docx.py +274 -0
  269. package/.claude/archive/skills/document-pptx/ooxml/scripts/validation/pptx.py +315 -0
  270. package/.claude/archive/skills/document-pptx/ooxml/scripts/validation/redlining.py +279 -0
  271. package/.claude/archive/skills/document-pptx/ooxml.md +427 -0
  272. package/.claude/archive/skills/document-pptx/scripts/html2pptx.js +979 -0
  273. package/.claude/archive/skills/document-pptx/scripts/inventory.py +1020 -0
  274. package/.claude/archive/skills/document-pptx/scripts/rearrange.py +231 -0
  275. package/.claude/archive/skills/document-pptx/scripts/replace.py +385 -0
  276. package/.claude/archive/skills/document-pptx/scripts/thumbnail.py +450 -0
  277. package/.claude/archive/skills/document-xlsx/LICENSE.txt +30 -0
  278. package/.claude/archive/skills/document-xlsx/SKILL.md +289 -0
  279. package/.claude/archive/skills/document-xlsx/recalc.py +178 -0
  280. package/.claude/archive/skills/image-enhancer/SKILL.md +99 -0
  281. package/.claude/archive/skills/meeting-insights-analyzer/SKILL.md +327 -0
  282. package/.claude/archive/skills/slack-gif-creator/LICENSE.txt +202 -0
  283. package/.claude/archive/skills/slack-gif-creator/SKILL.md +646 -0
  284. package/.claude/archive/skills/slack-gif-creator/core/color_palettes.py +302 -0
  285. package/.claude/archive/skills/slack-gif-creator/core/easing.py +230 -0
  286. package/.claude/archive/skills/slack-gif-creator/core/frame_composer.py +469 -0
  287. package/.claude/archive/skills/slack-gif-creator/core/gif_builder.py +246 -0
  288. package/.claude/archive/skills/slack-gif-creator/core/typography.py +357 -0
  289. package/.claude/archive/skills/slack-gif-creator/core/validators.py +264 -0
  290. package/.claude/archive/skills/slack-gif-creator/core/visual_effects.py +494 -0
  291. package/.claude/archive/skills/slack-gif-creator/requirements.txt +4 -0
  292. package/.claude/archive/skills/slack-gif-creator/templates/bounce.py +106 -0
  293. package/.claude/archive/skills/slack-gif-creator/templates/explode.py +331 -0
  294. package/.claude/archive/skills/slack-gif-creator/templates/fade.py +329 -0
  295. package/.claude/archive/skills/slack-gif-creator/templates/flip.py +291 -0
  296. package/.claude/archive/skills/slack-gif-creator/templates/kaleidoscope.py +211 -0
  297. package/.claude/archive/skills/slack-gif-creator/templates/morph.py +329 -0
  298. package/.claude/archive/skills/slack-gif-creator/templates/move.py +293 -0
  299. package/.claude/archive/skills/slack-gif-creator/templates/pulse.py +268 -0
  300. package/.claude/archive/skills/slack-gif-creator/templates/shake.py +127 -0
  301. package/.claude/archive/skills/slack-gif-creator/templates/slide.py +291 -0
  302. package/.claude/archive/skills/slack-gif-creator/templates/spin.py +269 -0
  303. package/.claude/archive/skills/slack-gif-creator/templates/wiggle.py +300 -0
  304. package/.claude/archive/skills/slack-gif-creator/templates/zoom.py +312 -0
  305. package/.claude/archive/skills/twitter-algorithm-optimizer/SKILL.md +327 -0
  306. package/.claude/archive/skills/video-downloader/SKILL.md +99 -0
  307. package/.claude/archive/skills/video-downloader/scripts/download_video.py +145 -0
  308. package/.claude/bash-whitelist-requests/2026-05-28-grep-find-rg.md +68 -0
  309. package/.claude/bash-whitelist-requests/2026-06-01-readonly-filters.md +76 -0
  310. package/.claude/bash-whitelist.txt +124 -0
  311. package/.claude/commands/agent-introspect.md +89 -0
  312. package/.claude/commands/apply-rules.md +363 -0
  313. package/.claude/commands/approve-design.md +219 -0
  314. package/.claude/commands/approve-org-money.md +267 -0
  315. package/.claude/commands/build.md +234 -0
  316. package/.claude/commands/commit.md +97 -0
  317. package/.claude/commands/context-fetch.md +113 -0
  318. package/.claude/commands/create-tool.md +496 -0
  319. package/.claude/commands/design-review.md +138 -0
  320. package/.claude/commands/design.md +807 -0
  321. package/.claude/commands/discharge-byproduct.md +208 -0
  322. package/.claude/commands/doc-review.md +165 -0
  323. package/.claude/commands/document-pair.md +76 -0
  324. package/.claude/commands/error-triage.md +435 -0
  325. package/.claude/commands/eval.md +70 -0
  326. package/.claude/commands/evolve.md +49 -0
  327. package/.claude/commands/finish-task.md +105 -0
  328. package/.claude/commands/gan-build.md +91 -0
  329. package/.claude/commands/gan-design.md +82 -0
  330. package/.claude/commands/gate-bypass.md +77 -0
  331. package/.claude/commands/gate-clear.md +45 -0
  332. package/.claude/commands/gate-status.md +46 -0
  333. package/.claude/commands/harness-audit.md +151 -0
  334. package/.claude/commands/hearing.md +138 -0
  335. package/.claude/commands/impact-check.md +486 -0
  336. package/.claude/commands/init-tasks.md +49 -0
  337. package/.claude/commands/instinct-export.md +47 -0
  338. package/.claude/commands/instinct-import.md +41 -0
  339. package/.claude/commands/instinct-status.md +43 -0
  340. package/.claude/commands/investigate.md +547 -0
  341. package/.claude/commands/learn.md +55 -0
  342. package/.claude/commands/lint-rules.md +400 -0
  343. package/.claude/commands/mode.md +58 -0
  344. package/.claude/commands/modify-feature.md +209 -0
  345. package/.claude/commands/module-review.md +149 -0
  346. package/.claude/commands/move-section.md +67 -0
  347. package/.claude/commands/new-draft.md +67 -0
  348. package/.claude/commands/new-feature.md +286 -0
  349. package/.claude/commands/new-task.md +156 -0
  350. package/.claude/commands/notification.md +107 -0
  351. package/.claude/commands/pm-start.md +119 -0
  352. package/.claude/commands/projects.md +32 -0
  353. package/.claude/commands/promote.md +43 -0
  354. package/.claude/commands/rasis-report.md +1323 -0
  355. package/.claude/commands/release-note.md +130 -0
  356. package/.claude/commands/reply-watch.md +149 -0
  357. package/.claude/commands/requirement.md +352 -0
  358. package/.claude/commands/resume-state.md +187 -0
  359. package/.claude/commands/reviewpr.md +118 -0
  360. package/.claude/commands/save-state.md +100 -0
  361. package/.claude/commands/sentry-pr.md +157 -0
  362. package/.claude/commands/start-task.md +87 -0
  363. package/.claude/commands/system-review.md +147 -0
  364. package/.claude/commands/task-bypass.md +70 -0
  365. package/.claude/commands/task-estimate.md +100 -0
  366. package/.claude/commands/template-apply.md +89 -0
  367. package/.claude/commands/test-design.md +116 -0
  368. package/.claude/commands/transfer-mismatch.md +317 -0
  369. package/.claude/commands/verify.md +51 -0
  370. package/.claude/evals/grader-loop-mode-autonomy.sh +165 -0
  371. package/.claude/evals/grader-system-reminder-attention.sh +99 -0
  372. package/.claude/evals/loop-mode-autonomy.md +121 -0
  373. package/.claude/evals/loop-mode-autonomy.results.template.md +133 -0
  374. package/.claude/evals/system-reminder-attention.md +123 -0
  375. package/.claude/evals/system-reminder-attention.results.template.md +93 -0
  376. package/.claude/evals/system-reminder-attention.runner.md +353 -0
  377. package/.claude/harness-config.local.yml +48 -0
  378. package/.claude/harness-config.yml +534 -0
  379. package/.claude/hooks/agent-marker-clear.sh +43 -0
  380. package/.claude/hooks/agent-marker-set.sh +40 -0
  381. package/.claude/hooks/agent-router-suggest.sh +123 -0
  382. package/.claude/hooks/autonomous-action-guard.sh +242 -0
  383. package/.claude/hooks/byproduct-discharge-guard.sh +128 -0
  384. package/.claude/hooks/check-md-mermaid.sh +144 -0
  385. package/.claude/hooks/check-required-env.sh +95 -0
  386. package/.claude/hooks/check-serena-mcp.sh +123 -0
  387. package/.claude/hooks/confidence-gate.sh +139 -0
  388. package/.claude/hooks/context-budget.sh +233 -0
  389. package/.claude/hooks/delegation-guard.sh +99 -0
  390. package/.claude/hooks/dispatcher-manifest.tsv +38 -0
  391. package/.claude/hooks/draft-flow-guard.sh +304 -0
  392. package/.claude/hooks/failure-loop-detect.sh +139 -0
  393. package/.claude/hooks/gateguard.sh +209 -0
  394. package/.claude/hooks/improvement-proposal.sh +112 -0
  395. package/.claude/hooks/init-tasks-on-start.sh +34 -0
  396. package/.claude/hooks/lib/bypass-logger.sh +82 -0
  397. package/.claude/hooks/lib/confidence-gate/bypass.sh +48 -0
  398. package/.claude/hooks/lib/confidence-gate/extract.sh +99 -0
  399. package/.claude/hooks/lib/confidence-gate/major-agent-filter.sh +59 -0
  400. package/.claude/hooks/lib/confidence-gate/messages.sh +53 -0
  401. package/.claude/hooks/lib/config-loader.sh +784 -0
  402. package/.claude/hooks/lib/delegation-guard/bash-whitelist.sh +323 -0
  403. package/.claude/hooks/lib/delegation-guard/git-deny.sh +188 -0
  404. package/.claude/hooks/lib/delegation-guard/protected-paths.sh +105 -0
  405. package/.claude/hooks/lib/delegation-guard/subagent-detect.sh +40 -0
  406. package/.claude/hooks/lib/dispatcher-core.sh +454 -0
  407. package/.claude/hooks/lib/improvement-proposal/aggregate.py +466 -0
  408. package/.claude/hooks/lib/improvement-proposal/cache.sh +78 -0
  409. package/.claude/hooks/lib/mode-loader.sh +80 -0
  410. package/.claude/hooks/lib/next-actions-parser.sh +153 -0
  411. package/.claude/hooks/lib/project-root.sh +60 -0
  412. package/.claude/hooks/list-md-plan-first-reminder.sh +143 -0
  413. package/.claude/hooks/loop-auto-progress-reminder.sh +108 -0
  414. package/.claude/hooks/loop-confirmation-detector.sh +241 -0
  415. package/.claude/hooks/mode-asana-prompt.sh +61 -0
  416. package/.claude/hooks/mode-enforce.sh +57 -0
  417. package/.claude/hooks/mode-session-start.sh +93 -0
  418. package/.claude/hooks/next-actions-surface.sh +136 -0
  419. package/.claude/hooks/notification-dispatcher.sh +9 -0
  420. package/.claude/hooks/notify.sh +27 -0
  421. package/.claude/hooks/parallel-subagent-reminder.sh +469 -0
  422. package/.claude/hooks/post-tool-use-dispatcher.sh +9 -0
  423. package/.claude/hooks/pre-tool-use-dispatcher.sh +9 -0
  424. package/.claude/hooks/reviewer-count-guard.sh +313 -0
  425. package/.claude/hooks/session-help-surface.sh +192 -0
  426. package/.claude/hooks/session-start-dispatcher.sh +9 -0
  427. package/.claude/hooks/session-start-wrapper.sh +156 -0
  428. package/.claude/hooks/stale-harness-detect.sh +422 -0
  429. package/.claude/hooks/stop-dispatcher.sh +9 -0
  430. package/.claude/hooks/stop.sh +25 -0
  431. package/.claude/hooks/subagent-stop-dispatcher.sh +9 -0
  432. package/.claude/hooks/task-rule-guard.sh +317 -0
  433. package/.claude/hooks/tests/run-tests.sh +23 -0
  434. package/.claude/hooks/tests/test-agent-marker-warn.sh +86 -0
  435. package/.claude/hooks/tests/test-check-required-env.sh +138 -0
  436. package/.claude/hooks/tests/test-confidence-gate.sh +170 -0
  437. package/.claude/hooks/tests/test-config-env-override.sh +220 -0
  438. package/.claude/hooks/tests/test-gate-disable.sh +118 -0
  439. package/.claude/hooks/tests/test-improvement-proposal.sh +284 -0
  440. package/.claude/hooks/tool-call-slip-detector.sh +188 -0
  441. package/.claude/hooks/user-prompt-submit-dispatcher.sh +9 -0
  442. package/.claude/hooks/why-x5-reminder.sh +45 -0
  443. package/.claude/hooks/why-x5-violation-detect.sh +152 -0
  444. package/.claude/hooks/workflow-guard.sh +263 -0
  445. package/.claude/mode.yml +28 -0
  446. package/.claude/project-rules/development-process.md +8 -0
  447. package/.claude/project-rules/git-workflow.md +8 -0
  448. package/.claude/project-rules/modes.md +8 -0
  449. package/.claude/project-rules/self-improvement.md +8 -0
  450. package/.claude/project-rules/task-management.md +8 -0
  451. package/.claude/project-rules/why-x5-output.md +8 -0
  452. package/.claude/project-rules/workflow.md +8 -0
  453. package/.claude/rules/development-process.md +293 -0
  454. package/.claude/rules/git-workflow.md +71 -0
  455. package/.claude/rules/modes.md +189 -0
  456. package/.claude/rules/self-improvement.md +76 -0
  457. package/.claude/rules/task-management.md +261 -0
  458. package/.claude/rules/why-x5-output.md +97 -0
  459. package/.claude/rules/workflow.md +157 -0
  460. package/.claude/rules-details/README.md +67 -0
  461. package/.claude/rules-details/development-process/confidence-gate.md +22 -0
  462. package/.claude/rules-details/development-process/cross-repo-write.md +35 -0
  463. package/.claude/rules-details/development-process/delegation-requirements.md +158 -0
  464. package/.claude/rules-details/development-process/harness-sync.md +21 -0
  465. package/.claude/rules-details/development-process/origin.md +13 -0
  466. package/.claude/rules-details/development-process/parallelization-origin.md +22 -0
  467. package/.claude/rules-details/development-process/research-reuse.md +22 -0
  468. package/.claude/rules-details/development-process/staging-strategy.md +47 -0
  469. package/.claude/rules-details/modes/artifacts.md +34 -0
  470. package/.claude/rules-details/modes/compliance-items.md +120 -0
  471. package/.claude/rules-details/modes/five-layer-enforcement.md +46 -0
  472. package/.claude/rules-details/modes/mode-hooks.md +51 -0
  473. package/.claude/rules-details/modes/origin.md +17 -0
  474. package/.claude/rules-details/self-improvement/l4-mechanics.md +36 -0
  475. package/.claude/rules-details/self-improvement/origin.md +8 -0
  476. package/.claude/rules-details/self-improvement/related-skills.md +35 -0
  477. package/.claude/rules-details/self-improvement/when-to-use-layers.md +39 -0
  478. package/.claude/rules-details/task-management/hook-enforcement.md +25 -0
  479. package/.claude/rules-details/task-management/mandatory-reading.md +20 -0
  480. package/.claude/rules-details/task-management/origin.md +12 -0
  481. package/.claude/rules-details/task-management/parking-lot.md +26 -0
  482. package/.claude/rules-details/task-management/plan-first.md +44 -0
  483. package/.claude/rules-details/task-management/six-articles.md +68 -0
  484. package/.claude/rules-details/task-management/task-migration.md +16 -0
  485. package/.claude/rules-details/task-management/ui-detection.md +11 -0
  486. package/.claude/rules-details/why-x5-output/examples.md +41 -0
  487. package/.claude/rules-details/why-x5-output/feedback-memory.md +14 -0
  488. package/.claude/rules-details/why-x5-output/origin.md +10 -0
  489. package/.claude/rules-details/why-x5-output/v1-v10-history.md +19 -0
  490. package/.claude/rules-details/workflow/10-stage.md +43 -0
  491. package/.claude/rules-details/workflow/14-stage.md +52 -0
  492. package/.claude/rules-details/workflow/byproduct-discharge.md +39 -0
  493. package/.claude/rules-details/workflow/draft-flow-guard.md +31 -0
  494. package/.claude/rules-details/workflow/fan-out.md +70 -0
  495. package/.claude/rules-details/workflow/mece-20.md +36 -0
  496. package/.claude/rules-details/workflow/origin.md +14 -0
  497. package/.claude/rules-details/workflow/refactoring.md +48 -0
  498. package/.claude/rules-details/workflow/related-skills.md +22 -0
  499. package/.claude/rules-details/workflow/reviewer-prompt.md +100 -0
  500. package/.claude/rules-details/workflow/session-persistence.md +46 -0
  501. package/.claude/rules-details/workflow/workflow-guard.md +36 -0
  502. package/.claude/scripts/__pycache__/harness-audit.cpython-313.pyc +0 -0
  503. package/.claude/scripts/agent-stocktake.py +421 -0
  504. package/.claude/scripts/check-md-mermaid.mjs +138 -0
  505. package/.claude/scripts/generate-settings.sh +0 -0
  506. package/.claude/scripts/harness-audit.py +1547 -0
  507. package/.claude/scripts/hc-config.sh +2265 -0
  508. package/.claude/scripts/init-tasks.sh +117 -0
  509. package/.claude/scripts/lib/enforcement-matrix-parse.sh +81 -0
  510. package/.claude/scripts/lib/hc-config-metadata.sh +190 -0
  511. package/.claude/scripts/lib/hc-config-web-server.js +1528 -0
  512. package/.claude/scripts/lib/hc-config-web-ui/app.js +1054 -0
  513. package/.claude/scripts/lib/hc-config-web-ui/index.html +130 -0
  514. package/.claude/scripts/lib/hc-config-web-ui/style.css +522 -0
  515. package/.claude/scripts/new-task-helper.sh +432 -0
  516. package/.claude/scripts/observe-repair.sh +437 -0
  517. package/.claude/scripts/observe-rotate.sh +311 -0
  518. package/.claude/scripts/statusline.sh +239 -0
  519. package/.claude/settings.generated.preview.json +211 -0
  520. package/.claude/settings.json +215 -0
  521. package/.claude/settings.local.example.json +20 -0
  522. package/.claude/settings.local.json +36 -0
  523. package/.claude/skills/agent-introspection-debugging/SKILL.md +123 -0
  524. package/.claude/skills/agent-router/README.md +137 -0
  525. package/.claude/skills/agent-router/SKILL.md +74 -0
  526. package/.claude/skills/agent-router/dispatch-table.yml +352 -0
  527. package/.claude/skills/agent-router/router.py +1086 -0
  528. package/.claude/skills/agent-router/samples/representative_prompts.txt +24 -0
  529. package/.claude/skills/agent-router/tests/__init__.py +0 -0
  530. package/.claude/skills/agent-router/tests/test_router.py +762 -0
  531. package/.claude/skills/artifacts-builder/LICENSE.txt +202 -0
  532. package/.claude/skills/artifacts-builder/SKILL.md +74 -0
  533. package/.claude/skills/artifacts-builder/scripts/bundle-artifact.sh +54 -0
  534. package/.claude/skills/artifacts-builder/scripts/init-artifact.sh +322 -0
  535. package/.claude/skills/artifacts-builder/scripts/shadcn-components.tar.gz +0 -0
  536. package/.claude/skills/brand-guidelines/LICENSE.txt +202 -0
  537. package/.claude/skills/brand-guidelines/SKILL.md +73 -0
  538. package/.claude/skills/canvas-design/LICENSE.txt +202 -0
  539. package/.claude/skills/canvas-design/SKILL.md +130 -0
  540. package/.claude/skills/canvas-design/canvas-fonts/ArsenalSC-OFL.txt +93 -0
  541. package/.claude/skills/canvas-design/canvas-fonts/ArsenalSC-Regular.ttf +0 -0
  542. package/.claude/skills/canvas-design/canvas-fonts/BigShoulders-Bold.ttf +0 -0
  543. package/.claude/skills/canvas-design/canvas-fonts/BigShoulders-OFL.txt +93 -0
  544. package/.claude/skills/canvas-design/canvas-fonts/BigShoulders-Regular.ttf +0 -0
  545. package/.claude/skills/canvas-design/canvas-fonts/Boldonse-OFL.txt +93 -0
  546. package/.claude/skills/canvas-design/canvas-fonts/Boldonse-Regular.ttf +0 -0
  547. package/.claude/skills/canvas-design/canvas-fonts/BricolageGrotesque-Bold.ttf +0 -0
  548. package/.claude/skills/canvas-design/canvas-fonts/BricolageGrotesque-OFL.txt +93 -0
  549. package/.claude/skills/canvas-design/canvas-fonts/BricolageGrotesque-Regular.ttf +0 -0
  550. package/.claude/skills/canvas-design/canvas-fonts/CrimsonPro-Bold.ttf +0 -0
  551. package/.claude/skills/canvas-design/canvas-fonts/CrimsonPro-Italic.ttf +0 -0
  552. package/.claude/skills/canvas-design/canvas-fonts/CrimsonPro-OFL.txt +93 -0
  553. package/.claude/skills/canvas-design/canvas-fonts/CrimsonPro-Regular.ttf +0 -0
  554. package/.claude/skills/canvas-design/canvas-fonts/DMMono-OFL.txt +93 -0
  555. package/.claude/skills/canvas-design/canvas-fonts/DMMono-Regular.ttf +0 -0
  556. package/.claude/skills/canvas-design/canvas-fonts/EricaOne-OFL.txt +94 -0
  557. package/.claude/skills/canvas-design/canvas-fonts/EricaOne-Regular.ttf +0 -0
  558. package/.claude/skills/canvas-design/canvas-fonts/GeistMono-Bold.ttf +0 -0
  559. package/.claude/skills/canvas-design/canvas-fonts/GeistMono-OFL.txt +93 -0
  560. package/.claude/skills/canvas-design/canvas-fonts/GeistMono-Regular.ttf +0 -0
  561. package/.claude/skills/canvas-design/canvas-fonts/Gloock-OFL.txt +93 -0
  562. package/.claude/skills/canvas-design/canvas-fonts/Gloock-Regular.ttf +0 -0
  563. package/.claude/skills/canvas-design/canvas-fonts/IBMPlexMono-Bold.ttf +0 -0
  564. package/.claude/skills/canvas-design/canvas-fonts/IBMPlexMono-OFL.txt +93 -0
  565. package/.claude/skills/canvas-design/canvas-fonts/IBMPlexMono-Regular.ttf +0 -0
  566. package/.claude/skills/canvas-design/canvas-fonts/IBMPlexSerif-Bold.ttf +0 -0
  567. package/.claude/skills/canvas-design/canvas-fonts/IBMPlexSerif-BoldItalic.ttf +0 -0
  568. package/.claude/skills/canvas-design/canvas-fonts/IBMPlexSerif-Italic.ttf +0 -0
  569. package/.claude/skills/canvas-design/canvas-fonts/IBMPlexSerif-Regular.ttf +0 -0
  570. package/.claude/skills/canvas-design/canvas-fonts/InstrumentSans-Bold.ttf +0 -0
  571. package/.claude/skills/canvas-design/canvas-fonts/InstrumentSans-BoldItalic.ttf +0 -0
  572. package/.claude/skills/canvas-design/canvas-fonts/InstrumentSans-Italic.ttf +0 -0
  573. package/.claude/skills/canvas-design/canvas-fonts/InstrumentSans-OFL.txt +93 -0
  574. package/.claude/skills/canvas-design/canvas-fonts/InstrumentSans-Regular.ttf +0 -0
  575. package/.claude/skills/canvas-design/canvas-fonts/InstrumentSerif-Italic.ttf +0 -0
  576. package/.claude/skills/canvas-design/canvas-fonts/InstrumentSerif-Regular.ttf +0 -0
  577. package/.claude/skills/canvas-design/canvas-fonts/Italiana-OFL.txt +93 -0
  578. package/.claude/skills/canvas-design/canvas-fonts/Italiana-Regular.ttf +0 -0
  579. package/.claude/skills/canvas-design/canvas-fonts/JetBrainsMono-Bold.ttf +0 -0
  580. package/.claude/skills/canvas-design/canvas-fonts/JetBrainsMono-OFL.txt +93 -0
  581. package/.claude/skills/canvas-design/canvas-fonts/JetBrainsMono-Regular.ttf +0 -0
  582. package/.claude/skills/canvas-design/canvas-fonts/Jura-Light.ttf +0 -0
  583. package/.claude/skills/canvas-design/canvas-fonts/Jura-Medium.ttf +0 -0
  584. package/.claude/skills/canvas-design/canvas-fonts/Jura-OFL.txt +93 -0
  585. package/.claude/skills/canvas-design/canvas-fonts/LibreBaskerville-OFL.txt +93 -0
  586. package/.claude/skills/canvas-design/canvas-fonts/LibreBaskerville-Regular.ttf +0 -0
  587. package/.claude/skills/canvas-design/canvas-fonts/Lora-Bold.ttf +0 -0
  588. package/.claude/skills/canvas-design/canvas-fonts/Lora-BoldItalic.ttf +0 -0
  589. package/.claude/skills/canvas-design/canvas-fonts/Lora-Italic.ttf +0 -0
  590. package/.claude/skills/canvas-design/canvas-fonts/Lora-OFL.txt +93 -0
  591. package/.claude/skills/canvas-design/canvas-fonts/Lora-Regular.ttf +0 -0
  592. package/.claude/skills/canvas-design/canvas-fonts/NationalPark-Bold.ttf +0 -0
  593. package/.claude/skills/canvas-design/canvas-fonts/NationalPark-OFL.txt +93 -0
  594. package/.claude/skills/canvas-design/canvas-fonts/NationalPark-Regular.ttf +0 -0
  595. package/.claude/skills/canvas-design/canvas-fonts/NothingYouCouldDo-OFL.txt +93 -0
  596. package/.claude/skills/canvas-design/canvas-fonts/NothingYouCouldDo-Regular.ttf +0 -0
  597. package/.claude/skills/canvas-design/canvas-fonts/Outfit-Bold.ttf +0 -0
  598. package/.claude/skills/canvas-design/canvas-fonts/Outfit-OFL.txt +93 -0
  599. package/.claude/skills/canvas-design/canvas-fonts/Outfit-Regular.ttf +0 -0
  600. package/.claude/skills/canvas-design/canvas-fonts/PixelifySans-Medium.ttf +0 -0
  601. package/.claude/skills/canvas-design/canvas-fonts/PixelifySans-OFL.txt +93 -0
  602. package/.claude/skills/canvas-design/canvas-fonts/PoiretOne-OFL.txt +93 -0
  603. package/.claude/skills/canvas-design/canvas-fonts/PoiretOne-Regular.ttf +0 -0
  604. package/.claude/skills/canvas-design/canvas-fonts/RedHatMono-Bold.ttf +0 -0
  605. package/.claude/skills/canvas-design/canvas-fonts/RedHatMono-OFL.txt +93 -0
  606. package/.claude/skills/canvas-design/canvas-fonts/RedHatMono-Regular.ttf +0 -0
  607. package/.claude/skills/canvas-design/canvas-fonts/Silkscreen-OFL.txt +93 -0
  608. package/.claude/skills/canvas-design/canvas-fonts/Silkscreen-Regular.ttf +0 -0
  609. package/.claude/skills/canvas-design/canvas-fonts/SmoochSans-Medium.ttf +0 -0
  610. package/.claude/skills/canvas-design/canvas-fonts/SmoochSans-OFL.txt +93 -0
  611. package/.claude/skills/canvas-design/canvas-fonts/Tektur-Medium.ttf +0 -0
  612. package/.claude/skills/canvas-design/canvas-fonts/Tektur-OFL.txt +93 -0
  613. package/.claude/skills/canvas-design/canvas-fonts/Tektur-Regular.ttf +0 -0
  614. package/.claude/skills/canvas-design/canvas-fonts/WorkSans-Bold.ttf +0 -0
  615. package/.claude/skills/canvas-design/canvas-fonts/WorkSans-BoldItalic.ttf +0 -0
  616. package/.claude/skills/canvas-design/canvas-fonts/WorkSans-Italic.ttf +0 -0
  617. package/.claude/skills/canvas-design/canvas-fonts/WorkSans-OFL.txt +93 -0
  618. package/.claude/skills/canvas-design/canvas-fonts/WorkSans-Regular.ttf +0 -0
  619. package/.claude/skills/canvas-design/canvas-fonts/YoungSerif-OFL.txt +93 -0
  620. package/.claude/skills/canvas-design/canvas-fonts/YoungSerif-Regular.ttf +0 -0
  621. package/.claude/skills/changelog-generator/SKILL.md +104 -0
  622. package/.claude/skills/check-md-mermaid/SKILL.md +62 -0
  623. package/.claude/skills/connect/SKILL.md +156 -0
  624. package/.claude/skills/connect-apps/SKILL.md +80 -0
  625. package/.claude/skills/content-research-writer/SKILL.md +538 -0
  626. package/.claude/skills/continuous-agent-loop/SKILL.md +187 -0
  627. package/.claude/skills/continuous-learning-v2/SKILL.md +238 -0
  628. package/.claude/skills/continuous-learning-v2/config.json +35 -0
  629. package/.claude/skills/continuous-learning-v2/hooks/observe.sh +333 -0
  630. package/.claude/skills/continuous-learning-v2/instinct-cli.py +406 -0
  631. package/.claude/skills/domain-name-brainstormer/SKILL.md +212 -0
  632. package/.claude/skills/eval-harness/SKILL.md +100 -0
  633. package/.claude/skills/eval-harness/swe-bench/README.md +80 -0
  634. package/.claude/skills/eval-harness/swe-bench/config.yml +29 -0
  635. package/.claude/skills/eval-harness/swe-bench/docker/Dockerfile +25 -0
  636. package/.claude/skills/eval-harness/swe-bench/docker/docker-compose.yml +18 -0
  637. package/.claude/skills/eval-harness/swe-bench/results/dry-run-2026-05-04.json +137 -0
  638. package/.claude/skills/eval-harness/swe-bench/results/dry-run-comparison-2026-05-04.md +112 -0
  639. package/.claude/skills/eval-harness/swe-bench/results/dry-run-improved-2026-05-04.json +165 -0
  640. package/.claude/skills/eval-harness/swe-bench/results/raw/astropy__astropy-12907.patch +12 -0
  641. package/.claude/skills/eval-harness/swe-bench/results/raw/astropy__astropy-12907.txt +322 -0
  642. package/.claude/skills/eval-harness/swe-bench/results/raw/astropy__astropy-12907.whole-file.txt +322 -0
  643. package/.claude/skills/eval-harness/swe-bench/runner.py +845 -0
  644. package/.claude/skills/eval-harness/swe-bench/scoring.py +298 -0
  645. package/.claude/skills/eval-harness/swe-bench/tasks/fetch_tasks.py +81 -0
  646. package/.claude/skills/eval-harness/swe-bench/tasks/lite-50.json +702 -0
  647. package/.claude/skills/file-organizer/SKILL.md +433 -0
  648. package/.claude/skills/gan-style-harness/SKILL.md +111 -0
  649. package/.claude/skills/gateguard/.gateguard.yml +47 -0
  650. package/.claude/skills/gateguard/SKILL.md +99 -0
  651. package/.claude/skills/internal-comms/LICENSE.txt +202 -0
  652. package/.claude/skills/internal-comms/SKILL.md +32 -0
  653. package/.claude/skills/internal-comms/examples/3p-updates.md +47 -0
  654. package/.claude/skills/internal-comms/examples/company-newsletter.md +65 -0
  655. package/.claude/skills/internal-comms/examples/faq-answers.md +30 -0
  656. package/.claude/skills/internal-comms/examples/general-comms.md +16 -0
  657. package/.claude/skills/invoice-organizer/SKILL.md +446 -0
  658. package/.claude/skills/karpathy-guidelines/SKILL.md +67 -0
  659. package/.claude/skills/langsmith-fetch/SKILL.md +485 -0
  660. package/.claude/skills/lead-research-assistant/SKILL.md +199 -0
  661. package/.claude/skills/mcp-builder/LICENSE.txt +202 -0
  662. package/.claude/skills/mcp-builder/SKILL.md +328 -0
  663. package/.claude/skills/mcp-builder/reference/evaluation.md +602 -0
  664. package/.claude/skills/mcp-builder/reference/mcp_best_practices.md +915 -0
  665. package/.claude/skills/mcp-builder/reference/node_mcp_server.md +916 -0
  666. package/.claude/skills/mcp-builder/reference/python_mcp_server.md +752 -0
  667. package/.claude/skills/mcp-builder/scripts/connections.py +151 -0
  668. package/.claude/skills/mcp-builder/scripts/evaluation.py +373 -0
  669. package/.claude/skills/mcp-builder/scripts/example_evaluation.xml +22 -0
  670. package/.claude/skills/mcp-builder/scripts/requirements.txt +2 -0
  671. package/.claude/skills/raffle-winner-picker/SKILL.md +159 -0
  672. package/.claude/skills/repo-map/README.md +125 -0
  673. package/.claude/skills/repo-map/SKILL.md +128 -0
  674. package/.claude/skills/repo-map/examples/sample-output.md +1194 -0
  675. package/.claude/skills/repo-map/repo-map.py +715 -0
  676. package/.claude/skills/salesforce-e2e-testing/SKILL.md +116 -0
  677. package/.claude/skills/salesforce-e2e-testing/catalog-template.md +161 -0
  678. package/.claude/skills/salesforce-e2e-testing/methodology.md +179 -0
  679. package/.claude/skills/salesforce-e2e-testing/observation-rules.md +280 -0
  680. package/.claude/skills/salesforce-e2e-testing/pattern-taxonomy.md +392 -0
  681. package/.claude/skills/salesforce-e2e-testing/procedure-template.md +376 -0
  682. package/.claude/skills/skill-creator/LICENSE.txt +202 -0
  683. package/.claude/skills/skill-creator/SKILL.md +209 -0
  684. package/.claude/skills/skill-creator/scripts/init_skill.py +303 -0
  685. package/.claude/skills/skill-creator/scripts/package_skill.py +110 -0
  686. package/.claude/skills/skill-creator/scripts/quick_validate.py +65 -0
  687. package/.claude/skills/skill-share/SKILL.md +80 -0
  688. package/.claude/skills/tailored-resume-generator/SKILL.md +345 -0
  689. package/.claude/skills/template-skill/SKILL.md +6 -0
  690. package/.claude/skills/theme-factory/LICENSE.txt +202 -0
  691. package/.claude/skills/theme-factory/SKILL.md +59 -0
  692. package/.claude/skills/theme-factory/theme-showcase.pdf +0 -0
  693. package/.claude/skills/theme-factory/themes/arctic-frost.md +19 -0
  694. package/.claude/skills/theme-factory/themes/botanical-garden.md +19 -0
  695. package/.claude/skills/theme-factory/themes/desert-rose.md +19 -0
  696. package/.claude/skills/theme-factory/themes/forest-canopy.md +19 -0
  697. package/.claude/skills/theme-factory/themes/golden-hour.md +19 -0
  698. package/.claude/skills/theme-factory/themes/midnight-galaxy.md +19 -0
  699. package/.claude/skills/theme-factory/themes/modern-minimalist.md +19 -0
  700. package/.claude/skills/theme-factory/themes/ocean-depths.md +19 -0
  701. package/.claude/skills/theme-factory/themes/sunset-boulevard.md +19 -0
  702. package/.claude/skills/theme-factory/themes/tech-innovation.md +19 -0
  703. package/.claude/skills/verification-loop/SKILL.md +129 -0
  704. package/.claude/skills/webapp-testing/LICENSE.txt +202 -0
  705. package/.claude/skills/webapp-testing/SKILL.md +96 -0
  706. package/.claude/skills/webapp-testing/examples/console_logging.py +35 -0
  707. package/.claude/skills/webapp-testing/examples/element_discovery.py +40 -0
  708. package/.claude/skills/webapp-testing/examples/static_html_automation.py +33 -0
  709. package/.claude/skills/webapp-testing/scripts/with_server.py +106 -0
  710. package/.claude/templates/docs/draft/_DRAFT_TEMPLATE.md +162 -0
  711. package/.claude/templates/docs/draft/_TEST_DESIGN_TEMPLATE.md +76 -0
  712. package/.claude/templates/docs/tasks/_TASK_TEMPLATE.md +276 -0
  713. package/.claude/templates/docs/tasks/list.md +80 -0
  714. package/.claude/templates/docs/tasks/parking-lot.md +82 -0
  715. package/.claude/templates/settings.user-level.json.template +306 -0
  716. package/.claude/tests/SMOKE-CLASSIFICATION.md +199 -0
  717. package/.claude/tests/action-space-count-smoke.sh +130 -0
  718. package/.claude/tests/agent-router-suggest-wiring-smoke.sh +188 -0
  719. package/.claude/tests/audit-followups-smoke.sh +158 -0
  720. package/.claude/tests/autonomous-action-guard-relaxation-smoke.sh +479 -0
  721. package/.claude/tests/autonomous-action-guard-smoke.sh +187 -0
  722. package/.claude/tests/check-serena-mcp-smoke.sh +156 -0
  723. package/.claude/tests/common-rules-import-smoke.sh +209 -0
  724. package/.claude/tests/confidence-gate-smoke.sh +220 -0
  725. package/.claude/tests/config-feature-toggles-smoke.sh +389 -0
  726. package/.claude/tests/context-budget-smoke.sh +222 -0
  727. package/.claude/tests/custom-pm-commands-smoke.sh +93 -0
  728. package/.claude/tests/delegation-guard-code-smoke.sh +244 -0
  729. package/.claude/tests/delegation-guard-deny-layers-smoke.sh +356 -0
  730. package/.claude/tests/delegation-guard-readonly-filter-smoke.sh +205 -0
  731. package/.claude/tests/delegation-guard-search-whitelist-smoke.sh +152 -0
  732. package/.claude/tests/delegation-guard-segment-smoke.sh +109 -0
  733. package/.claude/tests/dispatcher-blocker-invariance-smoke.sh +700 -0
  734. package/.claude/tests/dispatcher-core-smoke.sh +452 -0
  735. package/.claude/tests/dispatcher-merge-matrix-smoke.sh +825 -0
  736. package/.claude/tests/dispatcher-success-stdout-smoke.sh +290 -0
  737. package/.claude/tests/draft-flow-guard-approved-dir-smoke.sh +234 -0
  738. package/.claude/tests/draft-flow-guard-smoke.sh +194 -0
  739. package/.claude/tests/dual-mode-portability-smoke.sh +131 -0
  740. package/.claude/tests/effective-hook-matrix-smoke.sh +261 -0
  741. package/.claude/tests/enforcement-mismatch-smoke.sh +263 -0
  742. package/.claude/tests/fixtures/cascade-sample.jsonl +9 -0
  743. package/.claude/tests/fixtures/next-actions/case-clean.md +14 -0
  744. package/.claude/tests/fixtures/next-actions/case-with-red.md +16 -0
  745. package/.claude/tests/fixtures/next-actions/case-with-yellow-only.md +14 -0
  746. package/.claude/tests/fixtures/normal-broken-scatter.jsonl +5 -0
  747. package/.claude/tests/fixtures/task-71/blocker-baseline.tsv +24 -0
  748. package/.claude/tests/fixtures/task-71/settings-inventory.tsv +37 -0
  749. package/.claude/tests/fixtures/transcript-50pct.jsonl +2 -0
  750. package/.claude/tests/fixtures/transcript-60pct.jsonl +2 -0
  751. package/.claude/tests/fixtures/transcript-80pct.jsonl +2 -0
  752. package/.claude/tests/fixtures/transcript-95pct.jsonl +2 -0
  753. package/.claude/tests/fixtures/workflow-guard/case-2-mid.json +21 -0
  754. package/.claude/tests/fixtures/workflow-guard/case-3-blocked.json +33 -0
  755. package/.claude/tests/fixtures/workflow-guard/case-4-clean.json +27 -0
  756. package/.claude/tests/fixtures/workflow-guard/case-8-modify.json +23 -0
  757. package/.claude/tests/fixtures/workflow-guard/inputs/case-1.json +1 -0
  758. package/.claude/tests/fixtures/workflow-guard/inputs/case-2.json +1 -0
  759. package/.claude/tests/fixtures/workflow-guard/inputs/case-3.json +1 -0
  760. package/.claude/tests/fixtures/workflow-guard/inputs/case-4.json +1 -0
  761. package/.claude/tests/fixtures/workflow-guard/inputs/case-5.json +1 -0
  762. package/.claude/tests/fixtures/workflow-guard/inputs/case-6.json +1 -0
  763. package/.claude/tests/fixtures/workflow-guard/inputs/case-7.json +1 -0
  764. package/.claude/tests/fixtures/workflow-guard/inputs/case-8.json +1 -0
  765. package/.claude/tests/gateguard-smoke.sh +213 -0
  766. package/.claude/tests/git-deny-mainline-policy-smoke.sh +222 -0
  767. package/.claude/tests/harness-audit-c-batch-smoke.sh +270 -0
  768. package/.claude/tests/harness-audit-compare-smoke.sh +186 -0
  769. package/.claude/tests/harness-audit-pipeline-health-smoke.sh +326 -0
  770. package/.claude/tests/harness-config-local-smoke.sh +232 -0
  771. package/.claude/tests/hc-config-git-policy-smoke.sh +241 -0
  772. package/.claude/tests/hc-config-key-parity-smoke.sh +149 -0
  773. package/.claude/tests/hc-config-migration-smoke.sh +251 -0
  774. package/.claude/tests/hc-config-script-smoke.sh +1106 -0
  775. package/.claude/tests/hc-config-tui-smoke.sh +801 -0
  776. package/.claude/tests/hc-config-web-ui-smoke.sh +3224 -0
  777. package/.claude/tests/hook-cwd-robustness-smoke.sh +206 -0
  778. package/.claude/tests/hook-frequency-tweaks-smoke.sh +312 -0
  779. package/.claude/tests/improvement-proposal-cache-smoke.sh +238 -0
  780. package/.claude/tests/install-sh-overwrite-all-smoke.sh +274 -0
  781. package/.claude/tests/install-sh-regen-settings-smoke.sh +301 -0
  782. package/.claude/tests/install-sh-sync-drift-smoke.sh +285 -0
  783. package/.claude/tests/layer-b-context-isolation-smoke.sh +392 -0
  784. package/.claude/tests/list-md-plan-first-reminder-smoke.sh +313 -0
  785. package/.claude/tests/loop-auto-progress-smoke.sh +372 -0
  786. package/.claude/tests/loop-confirmation-detector-smoke.sh +674 -0
  787. package/.claude/tests/new-task-batch-update-smoke.sh +664 -0
  788. package/.claude/tests/next-actions-hooks-smoke.sh +283 -0
  789. package/.claude/tests/npx-cli-smoke.sh +696 -0
  790. package/.claude/tests/observe-flock-smoke.sh +223 -0
  791. package/.claude/tests/observe-jq-parse-smoke.sh +250 -0
  792. package/.claude/tests/observe-repair-smoke.sh +475 -0
  793. package/.claude/tests/observe-rotate-smoke.sh +428 -0
  794. package/.claude/tests/observe-subagent-stop-smoke.sh +476 -0
  795. package/.claude/tests/parallel-subagent-reminder-smoke.sh +918 -0
  796. package/.claude/tests/project-root-smoke.sh +140 -0
  797. package/.claude/tests/project-rules-protection-smoke.sh +199 -0
  798. package/.claude/tests/review-required-min-count-smoke.sh +286 -0
  799. package/.claude/tests/reviewer-count-guard-smoke.sh +490 -0
  800. package/.claude/tests/rule-architecture-smoke.sh +418 -0
  801. package/.claude/tests/rule-change-draft-flow-guard-smoke.sh +343 -0
  802. package/.claude/tests/run-all-smokes.sh +340 -0
  803. package/.claude/tests/session-help-surface-smoke.sh +224 -0
  804. package/.claude/tests/session-start-parallel-smoke.sh +165 -0
  805. package/.claude/tests/sessionstart-budget-smoke.sh +185 -0
  806. package/.claude/tests/sessionstart-footprint-smoke.sh +258 -0
  807. package/.claude/tests/settings-dispatcher-baseline-smoke.sh +709 -0
  808. package/.claude/tests/settings-generation-feature-pruning-smoke.sh +196 -0
  809. package/.claude/tests/stale-harness-detect-smoke.sh +974 -0
  810. package/.claude/tests/statusline-smoke.sh +180 -0
  811. package/.claude/tests/task-rule-guard-smoke.sh +656 -0
  812. package/.claude/tests/tool-call-slip-detector-smoke.sh +101 -0
  813. package/.claude/tests/wave-precheck-template-smoke.sh +159 -0
  814. package/.claude/tests/why-x5-violation-detect-smoke.sh +157 -0
  815. package/.claude/tests/workflow-guard-smoke.sh +266 -0
  816. package/CLAUDE.md +75 -0
  817. package/LICENSE +21 -0
  818. package/README.md +790 -0
  819. package/bin/cli.js +395 -0
  820. package/docs/INVENTORY.md +163 -0
  821. package/install.sh +769 -0
  822. package/package.json +25 -0
@@ -0,0 +1,1086 @@
1
+ #!/usr/bin/env python3
2
+ """agent-router/router.py — Route a prompt to the most relevant named subagent.
3
+
4
+ Reads dispatch-table.yml (single source of truth, alongside this script) and
5
+ returns a JSON blob describing the recommended agent, its confidence score,
6
+ matched keywords, and whether the recommendation is a fallback.
7
+
8
+ Standard library only. No external YAML parser dependency — uses a small
9
+ purpose-built parser for the constrained subset of YAML the table uses.
10
+
11
+ Phase 2 (Hybrid mode):
12
+ When --use-llm-fallback is on AND keyword confidence < --llm-threshold,
13
+ the router invokes a Claude CLI selector to score candidate agents in
14
+ natural language. Failure modes (timeout / parse / unknown agent) fall
15
+ back to the keyword result. Cycle detection prevents A→A→A loops.
16
+
17
+ Usage:
18
+ python3 router.py "review the auth module for SQL injection"
19
+ python3 router.py --explain "<prompt>" # include per-agent breakdown
20
+ python3 router.py --use-llm-fallback "<prompt>" # Hybrid mode
21
+ python3 router.py --use-llm-fallback --llm-threshold 0.6 "<prompt>"
22
+
23
+ Exit codes:
24
+ 0 — JSON result on stdout (always emitted)
25
+ 2 — internal error (table missing / unparseable). JSON err on stdout
26
+ and human message on stderr. Caller should fall back to
27
+ general-purpose.
28
+ """
29
+
30
+ from __future__ import annotations
31
+
32
+ import argparse
33
+ import hashlib
34
+ import json
35
+ import os
36
+ import re
37
+ import subprocess
38
+ import sys
39
+ from collections import defaultdict
40
+ from dataclasses import dataclass, field, asdict
41
+ from datetime import datetime, timezone
42
+ from pathlib import Path
43
+ from typing import Iterable
44
+
45
+ DEFAULT_TABLE = Path(__file__).resolve().parent / "dispatch-table.yml"
46
+
47
+ # Lightweight stop-word list — keep small to avoid filtering domain terms.
48
+ STOP_WORDS = {
49
+ "the", "a", "an", "and", "or", "but", "if", "then", "for", "to", "of",
50
+ "in", "on", "with", "by", "at", "from", "as", "is", "are", "was", "were",
51
+ "be", "been", "being", "have", "has", "had", "do", "does", "did",
52
+ "will", "would", "shall", "should", "can", "could", "may", "might",
53
+ "must", "this", "that", "these", "those", "it", "its", "i", "you",
54
+ "we", "they", "me", "my", "our", "your", "their", "what", "which",
55
+ "who", "when", "where", "why", "how", "please", "pls", "thanks",
56
+ "thank", "ok", "now", "also",
57
+ }
58
+
59
+ # Token regex: lowercase alnum + - + _ + .
60
+ TOKEN_RE = re.compile(r"[a-z0-9][a-z0-9._\-]*")
61
+
62
+ # Phase 2 constants
63
+ DEFAULT_LLM_THRESHOLD = 0.5
64
+ DEFAULT_LLM_MODEL = "claude-haiku-4-5"
65
+ DEFAULT_LLM_TIMEOUT = 30 # seconds
66
+ DEFAULT_LLM_MAX_ATTEMPTS = 3
67
+ DEFAULT_LLM_BUDGET_USD = 0.05
68
+ DEFAULT_HISTORY_PATH = Path.home() / ".claude" / "agent-router-history.json"
69
+ DEFAULT_HISTORY_LOOKBACK = 3
70
+
71
+ # Phase 6: long-term dispatch telemetry (separate from cycle-detection history).
72
+ DEFAULT_HOMUNCULUS_ROOT = Path.home() / ".claude" / "homunculus"
73
+ DISPATCH_LOG_NAME = "dispatch.jsonl"
74
+
75
+ # Phase 3: ordered fallback ladder. fallback_chain on RoutingResult is the
76
+ # prefix of layers the router actually traversed to reach the final agent.
77
+ FALLBACK_LAYERS = ("keyword", "llm", "previous", "general-purpose")
78
+
79
+
80
+ @dataclass
81
+ class RoutingResult:
82
+ agent: str
83
+ confidence: float
84
+ reason: str
85
+ fallback: bool
86
+ matched_keywords: list[str] = field(default_factory=list)
87
+ candidates: list[dict] = field(default_factory=list)
88
+ # Phase 2 fields — present only when LLM selector is used.
89
+ llm_used: bool = False
90
+ llm_attempts: int = 0
91
+ llm_cost_usd: float = 0.0
92
+ cycle_broken: bool = False
93
+ # Phase 3: ordered list of fallback layers traversed to pick `agent`.
94
+ # Always at least one entry; the *last* element is the layer that
95
+ # produced the final agent.
96
+ fallback_chain: list[str] = field(default_factory=list)
97
+
98
+ def to_json(self) -> str:
99
+ d = asdict(self)
100
+ # Always include core fields; strip empty / default optional fields.
101
+ if not d["candidates"]:
102
+ d.pop("candidates", None)
103
+ if not d["llm_used"]:
104
+ d.pop("llm_used", None)
105
+ d.pop("llm_attempts", None)
106
+ d.pop("llm_cost_usd", None)
107
+ if not d["cycle_broken"]:
108
+ d.pop("cycle_broken", None)
109
+ if not d.get("fallback_chain"):
110
+ d.pop("fallback_chain", None)
111
+ return json.dumps(d, ensure_ascii=False)
112
+
113
+
114
+ @dataclass
115
+ class DispatchTable:
116
+ threshold: float
117
+ saturation: float
118
+ specialty_boost: float
119
+ fallback: str
120
+ # keyword -> (agent_name, weight). Multi-word phrases also live here.
121
+ keywords: dict[str, tuple[str, float]]
122
+
123
+ @property
124
+ def single_word_keywords(self) -> dict[str, tuple[str, float]]:
125
+ return {k: v for k, v in self.keywords.items() if " " not in k}
126
+
127
+ @property
128
+ def phrase_keywords(self) -> dict[str, tuple[str, float]]:
129
+ return {k: v for k, v in self.keywords.items() if " " in k}
130
+
131
+ @property
132
+ def known_agents(self) -> set[str]:
133
+ return {agent for agent, _ in self.keywords.values()} | {self.fallback}
134
+
135
+
136
+ def _strip_inline_comment(value: str) -> str:
137
+ """Strip ' # ...' inline comments while respecting quoted strings."""
138
+ in_str = False
139
+ quote = ""
140
+ out_chars = []
141
+ i = 0
142
+ while i < len(value):
143
+ ch = value[i]
144
+ if in_str:
145
+ if ch == quote:
146
+ in_str = False
147
+ out_chars.append(ch)
148
+ else:
149
+ if ch in ("\"", "\'"):
150
+ in_str = True
151
+ quote = ch
152
+ out_chars.append(ch)
153
+ elif ch == "#" and (i == 0 or value[i - 1].isspace()):
154
+ break
155
+ else:
156
+ out_chars.append(ch)
157
+ i += 1
158
+ return "".join(out_chars).rstrip()
159
+
160
+
161
+ def _unquote(value: str) -> str:
162
+ value = value.strip()
163
+ if len(value) >= 2 and value[0] == value[-1] and value[0] in ("\"", "\'"):
164
+ return value[1:-1]
165
+ return value
166
+
167
+
168
+ def parse_dispatch_table(text: str) -> DispatchTable:
169
+ """Parse the constrained YAML subset used by dispatch-table.yml.
170
+
171
+ Supports:
172
+ - top-level scalar fields: threshold, saturation, specialty_boost, fallback
173
+ - top-level "keywords:" mapping with two value forms:
174
+ <keyword>: <agent_name>
175
+ <keyword>: { agent: <name>, weight: <float> }
176
+ - quoted keys ("multi word"), single or double
177
+ - "# ..." comments (full line or trailing)
178
+ """
179
+ threshold = 0.30
180
+ saturation = 6.0
181
+ specialty_boost = 0.5
182
+ fallback = "general-purpose"
183
+ keywords: dict[str, tuple[str, float]] = {}
184
+
185
+ in_keywords = False
186
+ for raw_line in text.splitlines():
187
+ line = _strip_inline_comment(raw_line)
188
+ stripped = line.strip()
189
+ if not stripped:
190
+ continue
191
+ if not raw_line.startswith((" ", "\t")):
192
+ # top-level
193
+ in_keywords = False
194
+ if ":" not in stripped:
195
+ continue
196
+ key, _, value = stripped.partition(":")
197
+ key = key.strip()
198
+ value = value.strip()
199
+ if key == "keywords":
200
+ in_keywords = True
201
+ continue
202
+ if key == "threshold":
203
+ threshold = float(value)
204
+ elif key == "saturation":
205
+ saturation = float(value)
206
+ elif key == "specialty_boost":
207
+ specialty_boost = float(value)
208
+ elif key == "fallback":
209
+ fallback = _unquote(value)
210
+ elif in_keywords:
211
+ if ":" not in stripped:
212
+ continue
213
+ key_raw, _, value = stripped.partition(":")
214
+ key = _unquote(key_raw.strip()).lower()
215
+ value = value.strip()
216
+ if value.startswith("{") and value.endswith("}"):
217
+ inner = value[1:-1]
218
+ agent_name = ""
219
+ weight = 1.0
220
+ for part in inner.split(","):
221
+ if ":" not in part:
222
+ continue
223
+ k2, _, v2 = part.partition(":")
224
+ k2 = k2.strip()
225
+ v2 = _unquote(v2.strip())
226
+ if k2 == "agent":
227
+ agent_name = v2
228
+ elif k2 == "weight":
229
+ try:
230
+ weight = float(v2)
231
+ except ValueError:
232
+ weight = 1.0
233
+ if agent_name:
234
+ keywords[key] = (agent_name, weight)
235
+ else:
236
+ agent_name = _unquote(value)
237
+ if agent_name:
238
+ keywords[key] = (agent_name, 1.0)
239
+
240
+ return DispatchTable(
241
+ threshold=threshold,
242
+ saturation=saturation,
243
+ specialty_boost=specialty_boost,
244
+ fallback=fallback,
245
+ keywords=keywords,
246
+ )
247
+
248
+
249
+ def load_table(path: Path | None = None) -> DispatchTable:
250
+ p = Path(path) if path else DEFAULT_TABLE
251
+ if not p.exists():
252
+ raise FileNotFoundError(f"dispatch table not found: {p}")
253
+ return parse_dispatch_table(p.read_text(encoding="utf-8"))
254
+
255
+
256
+ def tokenize(prompt: str) -> list[str]:
257
+ lowered = prompt.lower()
258
+ return [t for t in TOKEN_RE.findall(lowered) if t not in STOP_WORDS and len(t) >= 2]
259
+
260
+
261
+ def _word_boundary_phrase_search(prompt_lower: str, phrase: str) -> bool:
262
+ """Match a multi-word phrase on word boundaries (case-insensitive)."""
263
+ pattern = r"\b" + re.escape(phrase) + r"\b"
264
+ return re.search(pattern, prompt_lower) is not None
265
+
266
+
267
+ def score_prompt(prompt: str, table: DispatchTable) -> tuple[dict[str, float], dict[str, list[str]]]:
268
+ """Return (agent -> raw score, agent -> matched keywords)."""
269
+ tokens = set(tokenize(prompt))
270
+ prompt_lower = prompt.lower()
271
+
272
+ scores: dict[str, float] = defaultdict(float)
273
+ matches: dict[str, list[str]] = defaultdict(list)
274
+ hit_counts: dict[str, int] = defaultdict(int)
275
+
276
+ # Single-word keywords: token-set lookup.
277
+ for kw, (agent, weight) in table.single_word_keywords.items():
278
+ if kw in tokens:
279
+ scores[agent] += weight
280
+ matches[agent].append(kw)
281
+ hit_counts[agent] += 1
282
+
283
+ # Multi-word phrases: regex word-boundary search.
284
+ for kw, (agent, weight) in table.phrase_keywords.items():
285
+ if _word_boundary_phrase_search(prompt_lower, kw):
286
+ scores[agent] += weight
287
+ matches[agent].append(kw)
288
+ hit_counts[agent] += 1
289
+
290
+ # Specialty boost — adds (hits-1) * specialty_boost to encourage agents
291
+ # that match multiple distinct keywords (signal > noise).
292
+ for agent, hits in hit_counts.items():
293
+ if hits > 1:
294
+ scores[agent] += (hits - 1) * table.specialty_boost
295
+
296
+ return dict(scores), {a: sorted(set(m)) for a, m in matches.items()}
297
+
298
+
299
+ # ---------------------------------------------------------------------------
300
+ # Phase 2: Name normalization (CrewAI #1823 mitigation)
301
+ # ---------------------------------------------------------------------------
302
+
303
+ def normalize_agent_name(raw: str, known_agents: set[str]) -> str | None:
304
+ """Map a free-form agent name returned by the LLM to a known agent.
305
+
306
+ Tries, in order:
307
+ 1. Exact match (case-insensitive).
308
+ 2. Normalized form: lowercase, hyphens / spaces / underscores collapsed
309
+ to a single hyphen.
310
+ 3. Substring match against known agents (longest first).
311
+
312
+ Returns None when no candidate matches.
313
+ """
314
+ if not raw:
315
+ return None
316
+ raw_clean = raw.strip().strip("\"\'")
317
+ if not raw_clean:
318
+ return None
319
+
320
+ def _norm(s: str) -> str:
321
+ s = s.strip().lower()
322
+ s = re.sub(r"[\s_]+", "-", s)
323
+ s = re.sub(r"-+", "-", s)
324
+ return s.strip("-")
325
+
326
+ target = _norm(raw_clean)
327
+
328
+ # 1. Build normalized → canonical map for known agents.
329
+ canon_map: dict[str, str] = {}
330
+ for agent in known_agents:
331
+ canon_map[_norm(agent)] = agent
332
+
333
+ if target in canon_map:
334
+ return canon_map[target]
335
+
336
+ # 2. Substring containment (prefer longest known agent name first).
337
+ sorted_known = sorted(known_agents, key=lambda a: -len(a))
338
+ for agent in sorted_known:
339
+ agent_norm = _norm(agent)
340
+ if not agent_norm:
341
+ continue
342
+ if agent_norm in target or target in agent_norm:
343
+ # Avoid unintentionally matching extremely short normalized names
344
+ # (e.g. "qa" inside "qa-expert"); require ≥4 chars on the matched
345
+ # side.
346
+ shorter = min(len(agent_norm), len(target))
347
+ if shorter >= 4:
348
+ return agent
349
+
350
+ return None
351
+
352
+
353
+ # ---------------------------------------------------------------------------
354
+ # Phase 2: LLM selector
355
+ # ---------------------------------------------------------------------------
356
+
357
+ SELECTOR_PROMPT_TEMPLATE = """\
358
+ あなたは agent dispatcher です。以下の user prompt に対し、最適な named agent を 1 つ選択してください。
359
+
360
+ # user prompt
361
+ {prompt}
362
+
363
+ # 候補 agents(最大 30 件)
364
+ {candidate_list}
365
+
366
+ # 出力フォーマット(JSON のみ、他テキスト禁止)
367
+ {{"agent": "<agent_name>", "confidence": 0.0-1.0, "reason": "<200 字以内の理由>"}}
368
+
369
+ 候補に該当なしなら: {{"agent": "general-purpose", "confidence": 0.3, "reason": "no specialty match"}}
370
+ """
371
+
372
+
373
+ def build_candidate_list(
374
+ table: DispatchTable,
375
+ keyword_scores: dict[str, float],
376
+ max_candidates: int = 30,
377
+ ) -> list[str]:
378
+ """Return up to max_candidates agent names to surface to the LLM selector.
379
+
380
+ Strategy: top-N keyword candidates first, then a sample of other known
381
+ agents to give the selector breadth without blowing token budget.
382
+ """
383
+ seen: list[str] = []
384
+ seen_set: set[str] = set()
385
+
386
+ # Keyword-ranked candidates first.
387
+ ranked = sorted(keyword_scores.items(), key=lambda kv: kv[1], reverse=True)
388
+ for agent, _score in ranked:
389
+ if agent not in seen_set:
390
+ seen.append(agent)
391
+ seen_set.add(agent)
392
+ if len(seen) >= max_candidates:
393
+ return seen
394
+
395
+ # Fill with other known agents (sorted alphabetically for determinism).
396
+ for agent in sorted(table.known_agents):
397
+ if agent not in seen_set:
398
+ seen.append(agent)
399
+ seen_set.add(agent)
400
+ if len(seen) >= max_candidates:
401
+ break
402
+
403
+ return seen
404
+
405
+
406
+ def _invoke_claude_selector(
407
+ prompt: str,
408
+ candidate_list: list[str],
409
+ *,
410
+ model: str,
411
+ timeout: int,
412
+ max_budget_usd: float,
413
+ runner=None,
414
+ ) -> tuple[str | None, dict]:
415
+ """Run the Claude CLI selector once. Returns (raw_response_text, meta).
416
+
417
+ `runner` is an optional injection point for unit tests. When supplied it
418
+ is called as runner(candidate_list, selector_prompt, timeout) and must
419
+ return (text, meta).
420
+ """
421
+ selector_prompt = SELECTOR_PROMPT_TEMPLATE.format(
422
+ prompt=prompt.strip(),
423
+ candidate_list="\n".join(f"- {name}" for name in candidate_list),
424
+ )
425
+
426
+ if runner is not None:
427
+ return runner(candidate_list, selector_prompt, timeout)
428
+
429
+ cmd = [
430
+ "claude", "-p",
431
+ "--model", model,
432
+ "--output-format", "json",
433
+ "--input-format", "text",
434
+ "--max-budget-usd", str(max_budget_usd),
435
+ "--no-session-persistence",
436
+ "--permission-mode", "bypassPermissions",
437
+ "--disallowedTools", "Bash,Edit,Write,Read,Grep,Glob,WebSearch,WebFetch,Task",
438
+ ]
439
+
440
+ try:
441
+ proc = subprocess.run(
442
+ cmd,
443
+ input=selector_prompt,
444
+ capture_output=True,
445
+ text=True,
446
+ timeout=timeout,
447
+ env=os.environ.copy(),
448
+ )
449
+ except subprocess.TimeoutExpired:
450
+ return None, {"error": "timeout", "cost_usd": 0.0}
451
+ except FileNotFoundError:
452
+ return None, {"error": "claude CLI not found", "cost_usd": 0.0}
453
+
454
+ raw = proc.stdout or ""
455
+ meta: dict = {"cost_usd": 0.0, "duration_ms": 0, "rc": proc.returncode}
456
+ text_response: str | None = raw
457
+
458
+ try:
459
+ parsed = json.loads(raw)
460
+ if isinstance(parsed, dict):
461
+ text_response = parsed.get("result") or parsed.get("text") or raw
462
+ usage = parsed.get("usage") or {}
463
+ meta["cost_usd"] = float(parsed.get("total_cost_usd") or usage.get("total_cost_usd") or 0.0)
464
+ meta["duration_ms"] = int(parsed.get("duration_ms") or 0)
465
+ except json.JSONDecodeError:
466
+ pass
467
+
468
+ if proc.returncode != 0:
469
+ meta["error"] = (proc.stderr or "")[:500]
470
+ return None, meta
471
+ return text_response, meta
472
+
473
+
474
+ def _parse_selector_response(text: str | None) -> dict | None:
475
+ """Parse the selector's JSON output. Returns None on any failure."""
476
+ if not text:
477
+ return None
478
+ s = text.strip()
479
+ # The model may wrap in ```json fences; strip them.
480
+ if s.startswith("```"):
481
+ s = re.sub(r"^```(?:json)?\s*", "", s)
482
+ s = re.sub(r"\s*```\s*$", "", s)
483
+ # Take the first {...} JSON object encountered.
484
+ match = re.search(r"\{.*?\}", s, re.DOTALL)
485
+ if not match:
486
+ return None
487
+ try:
488
+ obj = json.loads(match.group(0))
489
+ except json.JSONDecodeError:
490
+ return None
491
+ if not isinstance(obj, dict):
492
+ return None
493
+ return obj
494
+
495
+
496
+ def llm_select(
497
+ prompt: str,
498
+ table: DispatchTable,
499
+ keyword_scores: dict[str, float],
500
+ *,
501
+ model: str = DEFAULT_LLM_MODEL,
502
+ timeout: int = DEFAULT_LLM_TIMEOUT,
503
+ max_attempts: int = DEFAULT_LLM_MAX_ATTEMPTS,
504
+ max_budget_usd: float = DEFAULT_LLM_BUDGET_USD,
505
+ runner=None,
506
+ ) -> tuple[dict | None, dict]:
507
+ """Invoke the selector with up to max_attempts retries.
508
+
509
+ Returns (parsed_dict_or_None, meta) where meta carries:
510
+ - attempts: int
511
+ - cost_usd: float (cumulative)
512
+ - error: str | None (last error)
513
+ """
514
+ candidates = build_candidate_list(table, keyword_scores)
515
+ cumulative_cost = 0.0
516
+ last_error: str | None = None
517
+ attempts = 0
518
+
519
+ for attempt in range(1, max_attempts + 1):
520
+ attempts = attempt
521
+ text, meta = _invoke_claude_selector(
522
+ prompt,
523
+ candidates,
524
+ model=model,
525
+ timeout=timeout,
526
+ max_budget_usd=max_budget_usd,
527
+ runner=runner,
528
+ )
529
+ cumulative_cost += float(meta.get("cost_usd", 0.0) or 0.0)
530
+ if "error" in meta and meta["error"]:
531
+ last_error = str(meta["error"])
532
+ parsed = _parse_selector_response(text)
533
+ if parsed is None:
534
+ continue
535
+
536
+ raw_agent = parsed.get("agent", "")
537
+ normalized = normalize_agent_name(raw_agent, table.known_agents)
538
+ if not normalized:
539
+ last_error = f"unknown agent in selector output: {raw_agent!r}"
540
+ continue
541
+
542
+ try:
543
+ confidence = float(parsed.get("confidence", 0.0))
544
+ except (TypeError, ValueError):
545
+ confidence = 0.0
546
+ confidence = max(0.0, min(1.0, confidence))
547
+
548
+ reason = str(parsed.get("reason") or "selector recommendation")[:200]
549
+
550
+ return (
551
+ {
552
+ "agent": normalized,
553
+ "confidence": confidence,
554
+ "reason": reason,
555
+ "raw_agent": raw_agent,
556
+ },
557
+ {"attempts": attempts, "cost_usd": cumulative_cost, "error": None},
558
+ )
559
+
560
+ return None, {"attempts": attempts, "cost_usd": cumulative_cost, "error": last_error}
561
+
562
+
563
+ # ---------------------------------------------------------------------------
564
+ # Phase 2: Cycle detection
565
+ # ---------------------------------------------------------------------------
566
+
567
+ def _hash_prompt(prompt: str) -> str:
568
+ return hashlib.sha256(prompt.strip().encode("utf-8")).hexdigest()[:16]
569
+
570
+
571
+ def _load_history(path: Path) -> list[dict]:
572
+ if not path.exists():
573
+ return []
574
+ try:
575
+ data = json.loads(path.read_text(encoding="utf-8"))
576
+ if isinstance(data, list):
577
+ return data
578
+ except (json.JSONDecodeError, OSError):
579
+ pass
580
+ return []
581
+
582
+
583
+ def _save_history(path: Path, entries: list[dict]) -> None:
584
+ try:
585
+ path.parent.mkdir(parents=True, exist_ok=True)
586
+ # Trim to a reasonable size (last 100 entries).
587
+ path.write_text(json.dumps(entries[-100:], ensure_ascii=False), encoding="utf-8")
588
+ except OSError:
589
+ # History persistence is best-effort; never fail routing because of it.
590
+ pass
591
+
592
+
593
+ def detect_cycle(
594
+ prompt: str,
595
+ candidate_agent: str,
596
+ history_path: Path,
597
+ lookback: int = DEFAULT_HISTORY_LOOKBACK,
598
+ ) -> bool:
599
+ """Return True when dispatching candidate_agent for this prompt would
600
+ repeat the most recent dispatch on the same prompt hash.
601
+ """
602
+ history = _load_history(history_path)
603
+ if not history:
604
+ return False
605
+ prompt_hash = _hash_prompt(prompt)
606
+ recent = [e for e in history[-lookback:] if e.get("prompt_hash") == prompt_hash]
607
+ if not recent:
608
+ return False
609
+ last_same_prompt = recent[-1]
610
+ return last_same_prompt.get("dispatched") == candidate_agent
611
+
612
+
613
+ def record_dispatch(
614
+ prompt: str,
615
+ dispatched_agent: str,
616
+ history_path: Path,
617
+ ) -> None:
618
+ history = _load_history(history_path)
619
+ history.append({
620
+ "ts": datetime.now(timezone.utc).isoformat(timespec="seconds").replace("+00:00", "Z"),
621
+ "prompt_hash": _hash_prompt(prompt),
622
+ "dispatched": dispatched_agent,
623
+ })
624
+ _save_history(history_path, history)
625
+
626
+
627
+ def pick_alternative_agent(
628
+ blocked_agent: str,
629
+ keyword_scores: dict[str, float],
630
+ fallback: str,
631
+ ) -> str:
632
+ """Return the next-best agent that is not blocked_agent."""
633
+ ranked = sorted(keyword_scores.items(), key=lambda kv: kv[1], reverse=True)
634
+ for agent, _score in ranked:
635
+ if agent != blocked_agent:
636
+ return agent
637
+ return fallback
638
+
639
+
640
+ # ---------------------------------------------------------------------------
641
+ # Phase 3: previous-agent fallback (third stage of the ladder)
642
+ # ---------------------------------------------------------------------------
643
+
644
+ def get_previous_dispatch(
645
+ prompt: str,
646
+ history_path: Path,
647
+ fallback: str,
648
+ *,
649
+ lookback: int = 10,
650
+ ) -> str | None:
651
+ """Return the most recent dispatched named agent for the *same prompt hash*,
652
+ or None when no prior dispatch exists or the only matches were the fallback
653
+ itself.
654
+
655
+ The "previous" layer is intentionally prompt-keyed (not session-keyed) so
656
+ re-issuing the same vague prompt resolves to the same specialist as last
657
+ time, providing context continuity without leaking across topics.
658
+ """
659
+ history = _load_history(history_path)
660
+ if not history:
661
+ return None
662
+ prompt_hash = _hash_prompt(prompt)
663
+ # Walk newest-first within the lookback window.
664
+ candidates = [e for e in history[-lookback:] if e.get("prompt_hash") == prompt_hash]
665
+ for entry in reversed(candidates):
666
+ agent = entry.get("dispatched")
667
+ if agent and agent != fallback:
668
+ return agent
669
+ return None
670
+
671
+
672
+ # ---------------------------------------------------------------------------
673
+ # Phase 6: dispatch.jsonl telemetry (long-term, project-scoped)
674
+ # ---------------------------------------------------------------------------
675
+
676
+ def _project_hash_for_telemetry(cwd: Path | None = None) -> str:
677
+ """Derive a stable per-project hash. Mirrors the convention used by
678
+ harness-audit.py / observe.sh: SHA-256 prefix of the git remote URL when
679
+ available, falling back to the absolute CWD path for non-git directories.
680
+ """
681
+ import hashlib
682
+ import subprocess
683
+
684
+ try:
685
+ out = subprocess.check_output(
686
+ ["git", "remote", "get-url", "origin"],
687
+ stderr=subprocess.DEVNULL,
688
+ text=True,
689
+ cwd=str(cwd) if cwd else None,
690
+ ).strip()
691
+ if out:
692
+ return hashlib.sha256(out.encode()).hexdigest()[:12]
693
+ except Exception:
694
+ pass
695
+ base = str((cwd or Path.cwd()).resolve())
696
+ return hashlib.sha256(base.encode()).hexdigest()[:12]
697
+
698
+
699
+ def dispatch_log_path(
700
+ homunculus_root: Path | None = None,
701
+ cwd: Path | None = None,
702
+ ) -> Path:
703
+ """Return the project-scoped dispatch.jsonl path."""
704
+ root = Path(homunculus_root) if homunculus_root else DEFAULT_HOMUNCULUS_ROOT
705
+ return root / "projects" / _project_hash_for_telemetry(cwd) / DISPATCH_LOG_NAME
706
+
707
+
708
+ def append_dispatch_log(
709
+ log_path: Path,
710
+ *,
711
+ prompt: str,
712
+ dispatched_agent: str,
713
+ fallback_layer: str,
714
+ confidence: float,
715
+ cost_usd: float = 0.0,
716
+ cycle_broken: bool = False,
717
+ ) -> None:
718
+ """Atomically append a dispatch event to dispatch.jsonl.
719
+
720
+ Atomicity strategy: write the new line to a per-process temp file, then
721
+ rename-append by reading current contents + the new line into a temp file
722
+ and `os.replace`-ing it onto the target. This avoids partial-line writes
723
+ even if the process is killed mid-write.
724
+ """
725
+ record = {
726
+ "ts": datetime.now(timezone.utc).isoformat(timespec="seconds").replace("+00:00", "Z"),
727
+ "prompt_hash": _hash_prompt(prompt),
728
+ "dispatched_agent": dispatched_agent,
729
+ "fallback_layer": fallback_layer,
730
+ "confidence": round(float(confidence), 3),
731
+ "cost_usd": round(float(cost_usd), 6),
732
+ "cycle_broken": bool(cycle_broken),
733
+ }
734
+
735
+ try:
736
+ log_path.parent.mkdir(parents=True, exist_ok=True)
737
+ existing = ""
738
+ if log_path.exists():
739
+ try:
740
+ existing = log_path.read_text(encoding="utf-8")
741
+ except OSError:
742
+ existing = ""
743
+ if existing and not existing.endswith("\n"):
744
+ existing += "\n"
745
+ new_blob = existing + json.dumps(record, ensure_ascii=False) + "\n"
746
+ tmp = log_path.with_suffix(log_path.suffix + f".tmp.{os.getpid()}")
747
+ tmp.write_text(new_blob, encoding="utf-8")
748
+ os.replace(tmp, log_path)
749
+ except OSError:
750
+ # Telemetry is best-effort; never break routing because of it.
751
+ pass
752
+
753
+
754
+ # ---------------------------------------------------------------------------
755
+ # Routing entry point
756
+ # ---------------------------------------------------------------------------
757
+
758
+ def route(
759
+ prompt: str,
760
+ table: DispatchTable | None = None,
761
+ table_path: Path | None = None,
762
+ explain: bool = False,
763
+ *,
764
+ use_llm_fallback: bool = False,
765
+ llm_threshold: float = DEFAULT_LLM_THRESHOLD,
766
+ llm_model: str = DEFAULT_LLM_MODEL,
767
+ llm_timeout: int = DEFAULT_LLM_TIMEOUT,
768
+ llm_max_attempts: int = DEFAULT_LLM_MAX_ATTEMPTS,
769
+ llm_budget_usd: float = DEFAULT_LLM_BUDGET_USD,
770
+ llm_runner=None,
771
+ enable_cycle_detection: bool = True,
772
+ history_path: Path | None = None,
773
+ record_history: bool = True,
774
+ enable_previous_fallback: bool = True,
775
+ record_dispatch_log: bool = True,
776
+ homunculus_root: Path | None = None,
777
+ cwd_for_telemetry: Path | None = None,
778
+ ) -> RoutingResult:
779
+ if table is None:
780
+ table = load_table(table_path)
781
+
782
+ if not prompt or not prompt.strip():
783
+ return RoutingResult(
784
+ agent=table.fallback,
785
+ confidence=0.0,
786
+ reason="empty prompt",
787
+ fallback=True,
788
+ fallback_chain=["general-purpose"],
789
+ )
790
+
791
+ history_path = history_path or DEFAULT_HISTORY_PATH
792
+ chain: list[str] = ["keyword"]
793
+
794
+ scores, matches = score_prompt(prompt, table)
795
+
796
+ if not scores:
797
+ # No keyword hit at all. LLM fallback (if on) is our only chance.
798
+ keyword_result = RoutingResult(
799
+ agent=table.fallback,
800
+ confidence=0.0,
801
+ reason="no keyword matched",
802
+ fallback=True,
803
+ )
804
+ else:
805
+ ranked = sorted(
806
+ scores.items(),
807
+ key=lambda kv: (kv[1], len(matches.get(kv[0], []))),
808
+ reverse=True,
809
+ )
810
+ top_agent, top_score = ranked[0]
811
+ confidence = min(top_score / table.saturation, 1.0)
812
+ if confidence < table.threshold:
813
+ keyword_result = RoutingResult(
814
+ agent=table.fallback,
815
+ confidence=round(confidence, 3),
816
+ reason=(
817
+ f"top candidate {top_agent} (score {round(top_score, 2)}) "
818
+ f"under threshold {table.threshold}"
819
+ ),
820
+ fallback=True,
821
+ matched_keywords=matches.get(top_agent, []),
822
+ candidates=_explain_candidates(ranked, matches, table) if explain else [],
823
+ )
824
+ else:
825
+ keyword_result = RoutingResult(
826
+ agent=top_agent,
827
+ confidence=round(confidence, 3),
828
+ reason="matched: " + ", ".join(matches.get(top_agent, [])),
829
+ fallback=False,
830
+ matched_keywords=matches.get(top_agent, []),
831
+ candidates=_explain_candidates(ranked, matches, table) if explain else [],
832
+ )
833
+
834
+ # ---- Decide whether to invoke LLM selector ----------------------------
835
+ final = keyword_result
836
+ llm_failed = False
837
+
838
+ if use_llm_fallback and keyword_result.confidence < llm_threshold:
839
+ chain.append("llm")
840
+ parsed, meta = llm_select(
841
+ prompt,
842
+ table,
843
+ scores,
844
+ model=llm_model,
845
+ timeout=llm_timeout,
846
+ max_attempts=llm_max_attempts,
847
+ max_budget_usd=llm_budget_usd,
848
+ runner=llm_runner,
849
+ )
850
+ attempts = int(meta.get("attempts", 0))
851
+ cost_usd = float(meta.get("cost_usd", 0.0))
852
+ if parsed is not None:
853
+ final = RoutingResult(
854
+ agent=parsed["agent"],
855
+ confidence=round(parsed["confidence"], 3),
856
+ reason=f"llm-selector: {parsed['reason']}",
857
+ fallback=parsed["agent"] == table.fallback,
858
+ matched_keywords=keyword_result.matched_keywords,
859
+ candidates=keyword_result.candidates,
860
+ llm_used=True,
861
+ llm_attempts=attempts,
862
+ llm_cost_usd=round(cost_usd, 6),
863
+ )
864
+ else:
865
+ llm_failed = True
866
+ # All LLM attempts failed; keep keyword result but record the
867
+ # attempt count + cost so callers can audit.
868
+ final = RoutingResult(
869
+ agent=keyword_result.agent,
870
+ confidence=keyword_result.confidence,
871
+ reason=(
872
+ keyword_result.reason
873
+ + f" | llm-selector failed after {attempts} attempts: "
874
+ + str(meta.get("error") or "unknown error")
875
+ ),
876
+ fallback=keyword_result.fallback,
877
+ matched_keywords=keyword_result.matched_keywords,
878
+ candidates=keyword_result.candidates,
879
+ llm_used=True,
880
+ llm_attempts=attempts,
881
+ llm_cost_usd=round(cost_usd, 6),
882
+ )
883
+
884
+ # ---- Phase 3: previous-agent layer ------------------------------------
885
+ # Only triggered when LLM was attempted and failed AND we still don't
886
+ # have a named agent. Skip when the previous dispatch was the fallback
887
+ # itself (that gives no signal).
888
+ if enable_previous_fallback and llm_failed and final.fallback:
889
+ prev = get_previous_dispatch(prompt, history_path, table.fallback)
890
+ if prev and prev != final.agent:
891
+ chain.append("previous")
892
+ final = RoutingResult(
893
+ agent=prev,
894
+ confidence=max(final.confidence, 0.4),
895
+ reason=(
896
+ f"previous-agent fallback: inheriting {prev} from prior "
897
+ f"dispatch on same prompt. {final.reason}"
898
+ ),
899
+ fallback=False,
900
+ matched_keywords=final.matched_keywords,
901
+ candidates=final.candidates,
902
+ llm_used=final.llm_used,
903
+ llm_attempts=final.llm_attempts,
904
+ llm_cost_usd=final.llm_cost_usd,
905
+ )
906
+
907
+ # ---- Final layer: general-purpose fallback ---------------------------
908
+ if final.fallback or final.agent == table.fallback:
909
+ chain.append("general-purpose")
910
+
911
+ # ---- Cycle detection --------------------------------------------------
912
+ # Skip when we deliberately inherited the previous agent — repeating it is
913
+ # the whole point of the previous-fallback layer.
914
+ came_from_previous = chain and chain[-1] == "previous"
915
+ if enable_cycle_detection and not final.fallback and not came_from_previous:
916
+ if detect_cycle(prompt, final.agent, history_path):
917
+ alt = pick_alternative_agent(final.agent, scores, table.fallback)
918
+ if alt != final.agent:
919
+ final = RoutingResult(
920
+ agent=alt,
921
+ confidence=final.confidence,
922
+ reason=f"cycle-broken: {final.agent} repeated; routed to {alt}. {final.reason}",
923
+ fallback=alt == table.fallback,
924
+ matched_keywords=final.matched_keywords,
925
+ candidates=final.candidates,
926
+ llm_used=final.llm_used,
927
+ llm_attempts=final.llm_attempts,
928
+ llm_cost_usd=final.llm_cost_usd,
929
+ cycle_broken=True,
930
+ )
931
+ if alt == table.fallback and "general-purpose" not in chain:
932
+ chain.append("general-purpose")
933
+
934
+ # Stamp the chain on the result.
935
+ final.fallback_chain = chain
936
+
937
+ if record_history:
938
+ record_dispatch(prompt, final.agent, history_path)
939
+
940
+ # Phase 6: append project-scoped dispatch telemetry.
941
+ if record_dispatch_log:
942
+ try:
943
+ log_path = dispatch_log_path(homunculus_root, cwd_for_telemetry)
944
+ append_dispatch_log(
945
+ log_path,
946
+ prompt=prompt,
947
+ dispatched_agent=final.agent,
948
+ fallback_layer=chain[-1] if chain else "keyword",
949
+ confidence=final.confidence,
950
+ cost_usd=final.llm_cost_usd,
951
+ cycle_broken=final.cycle_broken,
952
+ )
953
+ except Exception:
954
+ # Never let telemetry break routing.
955
+ pass
956
+
957
+ return final
958
+
959
+
960
+ def _explain_candidates(
961
+ ranked: Iterable[tuple[str, float]],
962
+ matches: dict[str, list[str]],
963
+ table: DispatchTable,
964
+ ) -> list[dict]:
965
+ out = []
966
+ for agent, score in list(ranked)[:5]:
967
+ out.append({
968
+ "agent": agent,
969
+ "score": round(score, 3),
970
+ "confidence": round(min(score / table.saturation, 1.0), 3),
971
+ "matched": matches.get(agent, []),
972
+ })
973
+ return out
974
+
975
+
976
+ # ---------------------------------------------------------------------------
977
+ # CLI
978
+ # ---------------------------------------------------------------------------
979
+
980
+ def _env_truthy(name: str) -> bool:
981
+ val = os.environ.get(name, "").strip().lower()
982
+ return val in {"1", "true", "yes", "on"}
983
+
984
+
985
+ def main(argv: list[str] | None = None) -> int:
986
+ parser = argparse.ArgumentParser(description="Route a prompt to a named agent")
987
+ parser.add_argument("prompt", nargs="*", help="Prompt text (joined with spaces)")
988
+ parser.add_argument("--table", default=None, help="Override dispatch table path")
989
+ parser.add_argument("--explain", action="store_true", help="Include candidate breakdown")
990
+ parser.add_argument("--stdin", action="store_true", help="Read prompt from stdin")
991
+ parser.add_argument(
992
+ "--use-llm-fallback",
993
+ action="store_true",
994
+ default=_env_truthy("AGENT_ROUTER_LLM_FALLBACK"),
995
+ help=(
996
+ "Invoke a Claude CLI selector when keyword confidence < --llm-threshold. "
997
+ "Default: off (env AGENT_ROUTER_LLM_FALLBACK=on enables)."
998
+ ),
999
+ )
1000
+ parser.add_argument(
1001
+ "--llm-threshold",
1002
+ type=float,
1003
+ default=float(os.environ.get("AGENT_ROUTER_LLM_THRESHOLD", DEFAULT_LLM_THRESHOLD)),
1004
+ help="Confidence below which the LLM selector kicks in (default: 0.5)",
1005
+ )
1006
+ parser.add_argument(
1007
+ "--llm-model",
1008
+ default=os.environ.get("AGENT_ROUTER_LLM_MODEL", DEFAULT_LLM_MODEL),
1009
+ help=f"Claude model used by the selector (default: {DEFAULT_LLM_MODEL})",
1010
+ )
1011
+ parser.add_argument(
1012
+ "--llm-timeout",
1013
+ type=int,
1014
+ default=int(os.environ.get("AGENT_ROUTER_LLM_TIMEOUT", DEFAULT_LLM_TIMEOUT)),
1015
+ help="Per-attempt timeout in seconds (default: 30)",
1016
+ )
1017
+ parser.add_argument(
1018
+ "--llm-max-attempts",
1019
+ type=int,
1020
+ default=int(os.environ.get("AGENT_ROUTER_LLM_MAX_ATTEMPTS", DEFAULT_LLM_MAX_ATTEMPTS)),
1021
+ help="Max selector retries on parse / timeout / unknown agent (default: 3)",
1022
+ )
1023
+ parser.add_argument(
1024
+ "--llm-budget-usd",
1025
+ type=float,
1026
+ default=float(os.environ.get("AGENT_ROUTER_LLM_BUDGET_USD", DEFAULT_LLM_BUDGET_USD)),
1027
+ help="Per-call cost cap for the selector (default: 0.05 USD)",
1028
+ )
1029
+ parser.add_argument(
1030
+ "--no-cycle-detection",
1031
+ action="store_true",
1032
+ help="Disable cycle detection / history recording",
1033
+ )
1034
+ parser.add_argument(
1035
+ "--no-record",
1036
+ action="store_true",
1037
+ help="Skip writing the dispatch result to history (cycle detection still reads)",
1038
+ )
1039
+ parser.add_argument(
1040
+ "--no-dispatch-log",
1041
+ action="store_true",
1042
+ help="Skip appending to ~/.claude/homunculus/projects/<hash>/dispatch.jsonl",
1043
+ )
1044
+ parser.add_argument(
1045
+ "--no-previous-fallback",
1046
+ action="store_true",
1047
+ help="Disable Phase 3 'previous-agent' fallback layer",
1048
+ )
1049
+ args = parser.parse_args(argv)
1050
+
1051
+ if args.stdin:
1052
+ prompt = sys.stdin.read()
1053
+ else:
1054
+ prompt = " ".join(args.prompt)
1055
+
1056
+ try:
1057
+ result = route(
1058
+ prompt,
1059
+ table_path=Path(args.table) if args.table else None,
1060
+ explain=args.explain,
1061
+ use_llm_fallback=args.use_llm_fallback,
1062
+ llm_threshold=args.llm_threshold,
1063
+ llm_model=args.llm_model,
1064
+ llm_timeout=args.llm_timeout,
1065
+ llm_max_attempts=args.llm_max_attempts,
1066
+ llm_budget_usd=args.llm_budget_usd,
1067
+ enable_cycle_detection=not args.no_cycle_detection,
1068
+ record_history=not (args.no_record or args.no_cycle_detection),
1069
+ enable_previous_fallback=not args.no_previous_fallback,
1070
+ record_dispatch_log=not (args.no_dispatch_log or args.no_record),
1071
+ )
1072
+ except FileNotFoundError as exc:
1073
+ print(json.dumps({"error": str(exc), "agent": "general-purpose", "fallback": True}), flush=True)
1074
+ print(f"agent-router: {exc}", file=sys.stderr)
1075
+ return 2
1076
+ except Exception as exc: # pragma: no cover - defensive
1077
+ print(json.dumps({"error": repr(exc), "agent": "general-purpose", "fallback": True}), flush=True)
1078
+ print(f"agent-router internal error: {exc}", file=sys.stderr)
1079
+ return 2
1080
+
1081
+ print(result.to_json(), flush=True)
1082
+ return 0
1083
+
1084
+
1085
+ if __name__ == "__main__":
1086
+ raise SystemExit(main())