@softspark/ai-toolkit 1.0.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 (327) hide show
  1. package/AGENTS.md +412 -0
  2. package/CHANGELOG.md +68 -0
  3. package/LICENSE +21 -0
  4. package/README.md +632 -0
  5. package/action.yml +53 -0
  6. package/app/.claude-plugin/plugin.json +44 -0
  7. package/app/ARCHITECTURE.md +306 -0
  8. package/app/CLAUDE.md.template +23 -0
  9. package/app/agents/ai-engineer.md +128 -0
  10. package/app/agents/backend-specialist.md +193 -0
  11. package/app/agents/business-intelligence.md +54 -0
  12. package/app/agents/chaos-monkey.md +67 -0
  13. package/app/agents/chief-of-staff.md +51 -0
  14. package/app/agents/code-archaeologist.md +127 -0
  15. package/app/agents/code-reviewer.md +184 -0
  16. package/app/agents/command-expert.md +131 -0
  17. package/app/agents/data-analyst.md +205 -0
  18. package/app/agents/data-scientist.md +151 -0
  19. package/app/agents/database-architect.md +317 -0
  20. package/app/agents/debugger.md +238 -0
  21. package/app/agents/devops-implementer.md +194 -0
  22. package/app/agents/documenter.md +364 -0
  23. package/app/agents/explorer-agent.md +145 -0
  24. package/app/agents/fact-checker.md +172 -0
  25. package/app/agents/frontend-specialist.md +209 -0
  26. package/app/agents/game-developer.md +216 -0
  27. package/app/agents/incident-responder.md +226 -0
  28. package/app/agents/infrastructure-architect.md +127 -0
  29. package/app/agents/infrastructure-validator.md +247 -0
  30. package/app/agents/llm-ops-engineer.md +237 -0
  31. package/app/agents/mcp-expert.md +228 -0
  32. package/app/agents/mcp-server-architect.md +195 -0
  33. package/app/agents/mcp-testing-engineer.md +292 -0
  34. package/app/agents/meta-architect.md +58 -0
  35. package/app/agents/ml-engineer.md +136 -0
  36. package/app/agents/mobile-developer.md +190 -0
  37. package/app/agents/night-watchman.md +55 -0
  38. package/app/agents/nlp-engineer.md +154 -0
  39. package/app/agents/orchestrator.md +437 -0
  40. package/app/agents/performance-optimizer.md +254 -0
  41. package/app/agents/predictive-analyst.md +57 -0
  42. package/app/agents/product-manager.md +194 -0
  43. package/app/agents/project-planner.md +287 -0
  44. package/app/agents/prompt-engineer.md +103 -0
  45. package/app/agents/qa-automation-engineer.md +182 -0
  46. package/app/agents/rag-engineer.md +201 -0
  47. package/app/agents/research-synthesizer.md +138 -0
  48. package/app/agents/search-specialist.md +101 -0
  49. package/app/agents/security-architect.md +62 -0
  50. package/app/agents/security-auditor.md +293 -0
  51. package/app/agents/seo-specialist.md +111 -0
  52. package/app/agents/system-governor.md +57 -0
  53. package/app/agents/tech-lead.md +62 -0
  54. package/app/agents/technical-researcher.md +103 -0
  55. package/app/agents/test-engineer.md +264 -0
  56. package/app/constitution.md +38 -0
  57. package/app/hooks/_profile-check.sh +11 -0
  58. package/app/hooks/guard-destructive.sh +74 -0
  59. package/app/hooks/guard-path.sh +73 -0
  60. package/app/hooks/post-tool-use.sh +35 -0
  61. package/app/hooks/pre-compact.sh +31 -0
  62. package/app/hooks/quality-check.sh +22 -0
  63. package/app/hooks/quality-gate.sh +49 -0
  64. package/app/hooks/save-session.sh +24 -0
  65. package/app/hooks/session-end.sh +37 -0
  66. package/app/hooks/session-start.sh +29 -0
  67. package/app/hooks/subagent-start.sh +16 -0
  68. package/app/hooks/subagent-stop.sh +16 -0
  69. package/app/hooks/track-usage.sh +50 -0
  70. package/app/hooks/user-prompt-submit.sh +25 -0
  71. package/app/hooks.json +178 -0
  72. package/app/mcp-defaults.json +23 -0
  73. package/app/output-styles/golden-rules.md +43 -0
  74. package/app/plugins/README.md +19 -0
  75. package/app/plugins/csharp-pack/README.md +11 -0
  76. package/app/plugins/csharp-pack/plugin.json +18 -0
  77. package/app/plugins/enterprise-pack/README.md +16 -0
  78. package/app/plugins/enterprise-pack/hooks/output-style.sh +6 -0
  79. package/app/plugins/enterprise-pack/hooks/status-line.sh +8 -0
  80. package/app/plugins/enterprise-pack/plugin.json +24 -0
  81. package/app/plugins/frontend-pack/README.md +14 -0
  82. package/app/plugins/frontend-pack/plugin.json +22 -0
  83. package/app/plugins/java-pack/README.md +11 -0
  84. package/app/plugins/java-pack/plugin.json +18 -0
  85. package/app/plugins/kotlin-pack/README.md +11 -0
  86. package/app/plugins/kotlin-pack/plugin.json +18 -0
  87. package/app/plugins/memory-pack/README.md +24 -0
  88. package/app/plugins/memory-pack/hooks/observation-capture.sh +67 -0
  89. package/app/plugins/memory-pack/hooks/session-summary.sh +71 -0
  90. package/app/plugins/memory-pack/plugin.json +22 -0
  91. package/app/plugins/memory-pack/scripts/init_db.py +81 -0
  92. package/app/plugins/memory-pack/scripts/strip_private.py +22 -0
  93. package/app/plugins/memory-pack/skills/mem-search/SKILL.md +70 -0
  94. package/app/plugins/research-pack/README.md +14 -0
  95. package/app/plugins/research-pack/plugin.json +22 -0
  96. package/app/plugins/ruby-pack/README.md +11 -0
  97. package/app/plugins/ruby-pack/plugin.json +18 -0
  98. package/app/plugins/rust-pack/README.md +11 -0
  99. package/app/plugins/rust-pack/plugin.json +18 -0
  100. package/app/plugins/security-pack/README.md +15 -0
  101. package/app/plugins/security-pack/plugin.json +23 -0
  102. package/app/plugins/swift-pack/README.md +11 -0
  103. package/app/plugins/swift-pack/plugin.json +18 -0
  104. package/app/rules/claude-toolkit-rules.md +21 -0
  105. package/app/rules/git-conventions.md +5 -0
  106. package/app/rules/quality-gates.md +10 -0
  107. package/app/skills/_lib/__init__.py +1 -0
  108. package/app/skills/_lib/detect_utils.py +150 -0
  109. package/app/skills/agent-creator/SKILL.md +82 -0
  110. package/app/skills/analyze/SKILL.md +92 -0
  111. package/app/skills/analyze/scripts/complexity.py +165 -0
  112. package/app/skills/api-patterns/SKILL.md +305 -0
  113. package/app/skills/app-builder/SKILL.md +187 -0
  114. package/app/skills/architecture-audit/SKILL.md +141 -0
  115. package/app/skills/architecture-decision/SKILL.md +55 -0
  116. package/app/skills/architecture-decision/templates/adr-template.md +36 -0
  117. package/app/skills/biz-scan/SKILL.md +30 -0
  118. package/app/skills/briefing/SKILL.md +27 -0
  119. package/app/skills/build/SKILL.md +97 -0
  120. package/app/skills/build/scripts/detect-build.py +151 -0
  121. package/app/skills/chaos/SKILL.md +32 -0
  122. package/app/skills/ci/SKILL.md +77 -0
  123. package/app/skills/ci/scripts/ci-detect.py +135 -0
  124. package/app/skills/ci/templates/github-actions-node.yml +38 -0
  125. package/app/skills/ci/templates/github-actions-python.yml +42 -0
  126. package/app/skills/ci-cd-patterns/SKILL.md +299 -0
  127. package/app/skills/clean-code/SKILL.md +110 -0
  128. package/app/skills/clean-code/reference/dart.md +18 -0
  129. package/app/skills/clean-code/reference/go.md +23 -0
  130. package/app/skills/clean-code/reference/php.md +32 -0
  131. package/app/skills/clean-code/reference/python.md +180 -0
  132. package/app/skills/clean-code/reference/typescript.md +26 -0
  133. package/app/skills/command-creator/SKILL.md +83 -0
  134. package/app/skills/commit/SKILL.md +98 -0
  135. package/app/skills/commit/scripts/pre-commit-check.py +87 -0
  136. package/app/skills/commit/templates/conventional-commit.md +52 -0
  137. package/app/skills/csharp-patterns/SKILL.md +450 -0
  138. package/app/skills/database-patterns/SKILL.md +297 -0
  139. package/app/skills/debug/SKILL.md +154 -0
  140. package/app/skills/debug/scripts/error-parser.py +187 -0
  141. package/app/skills/debugging-tactics/SKILL.md +136 -0
  142. package/app/skills/deploy/SKILL.md +130 -0
  143. package/app/skills/deploy/scripts/pre_deploy_check.py +171 -0
  144. package/app/skills/deploy/templates/deployment-checklist.md +31 -0
  145. package/app/skills/design-an-interface/SKILL.md +105 -0
  146. package/app/skills/design-engineering/SKILL.md +260 -0
  147. package/app/skills/docker-devops/SKILL.md +303 -0
  148. package/app/skills/docs/SKILL.md +145 -0
  149. package/app/skills/docs/scripts/doc-inventory.py +176 -0
  150. package/app/skills/docs/templates/adr-template.md +36 -0
  151. package/app/skills/docs/templates/readme-template.md +67 -0
  152. package/app/skills/documentation-standards/SKILL.md +191 -0
  153. package/app/skills/ecommerce-patterns/SKILL.md +209 -0
  154. package/app/skills/evaluate/SKILL.md +132 -0
  155. package/app/skills/evolve/SKILL.md +27 -0
  156. package/app/skills/explain/SKILL.md +54 -0
  157. package/app/skills/explain/scripts/dependency-graph.py +215 -0
  158. package/app/skills/explore/SKILL.md +112 -0
  159. package/app/skills/explore/scripts/visualize.py +117 -0
  160. package/app/skills/fix/SKILL.md +78 -0
  161. package/app/skills/fix/scripts/error-classifier.py +191 -0
  162. package/app/skills/flutter-patterns/SKILL.md +254 -0
  163. package/app/skills/git-mastery/SKILL.md +70 -0
  164. package/app/skills/grill-me/SKILL.md +38 -0
  165. package/app/skills/health/SKILL.md +91 -0
  166. package/app/skills/health/scripts/health_check.py +162 -0
  167. package/app/skills/hive-mind/SKILL.md +56 -0
  168. package/app/skills/hook-creator/SKILL.md +107 -0
  169. package/app/skills/index/SKILL.md +74 -0
  170. package/app/skills/instinct-review/SKILL.md +77 -0
  171. package/app/skills/java-patterns/SKILL.md +442 -0
  172. package/app/skills/kotlin-patterns/SKILL.md +446 -0
  173. package/app/skills/lint/SKILL.md +103 -0
  174. package/app/skills/lint/scripts/detect-linters.py +112 -0
  175. package/app/skills/mcp-patterns/SKILL.md +270 -0
  176. package/app/skills/mem-search/SKILL.md +70 -0
  177. package/app/skills/migrate/SKILL.md +90 -0
  178. package/app/skills/migrate/scripts/migration-status.py +195 -0
  179. package/app/skills/migration-patterns/SKILL.md +260 -0
  180. package/app/skills/night-watch/SKILL.md +28 -0
  181. package/app/skills/observability-patterns/SKILL.md +203 -0
  182. package/app/skills/onboard/SKILL.md +76 -0
  183. package/app/skills/orchestrate/SKILL.md +86 -0
  184. package/app/skills/panic/SKILL.md +30 -0
  185. package/app/skills/performance-profiling/SKILL.md +59 -0
  186. package/app/skills/plan/SKILL.md +110 -0
  187. package/app/skills/plan/templates/plan-template.md +40 -0
  188. package/app/skills/plan-writing/SKILL.md +201 -0
  189. package/app/skills/plugin-creator/SKILL.md +78 -0
  190. package/app/skills/pr/SKILL.md +129 -0
  191. package/app/skills/pr/scripts/pr-summary.py +175 -0
  192. package/app/skills/prd-to-issues/SKILL.md +108 -0
  193. package/app/skills/prd-to-plan/SKILL.md +120 -0
  194. package/app/skills/predict/SKILL.md +30 -0
  195. package/app/skills/qa-session/SKILL.md +110 -0
  196. package/app/skills/rag-patterns/SKILL.md +203 -0
  197. package/app/skills/refactor/SKILL.md +124 -0
  198. package/app/skills/refactor/scripts/refactor-scan.py +210 -0
  199. package/app/skills/refactor-plan/SKILL.md +112 -0
  200. package/app/skills/repeat/SKILL.md +149 -0
  201. package/app/skills/research-mastery/SKILL.md +56 -0
  202. package/app/skills/review/SKILL.md +141 -0
  203. package/app/skills/review/scripts/diff-analyzer.py +170 -0
  204. package/app/skills/rollback/SKILL.md +87 -0
  205. package/app/skills/rollback/scripts/rollback_info.py +149 -0
  206. package/app/skills/ruby-patterns/SKILL.md +454 -0
  207. package/app/skills/rust-patterns/SKILL.md +446 -0
  208. package/app/skills/search/SKILL.md +64 -0
  209. package/app/skills/security-patterns/SKILL.md +91 -0
  210. package/app/skills/security-patterns/reference/authentication.md +37 -0
  211. package/app/skills/security-patterns/reference/authorization.md +22 -0
  212. package/app/skills/security-patterns/reference/input-validation.md +30 -0
  213. package/app/skills/security-patterns/reference/oauth-csrf-audit.md +131 -0
  214. package/app/skills/skill-creator/SKILL.md +154 -0
  215. package/app/skills/skill-creator/templates/dashboard/index.html +130 -0
  216. package/app/skills/skill-creator/templates/reasoning-engine/assets/example.json +12 -0
  217. package/app/skills/skill-creator/templates/reasoning-engine/search.py +110 -0
  218. package/app/skills/subagent-development/SKILL.md +225 -0
  219. package/app/skills/subagent-development/reference/code-quality-reviewer-prompt.md +145 -0
  220. package/app/skills/subagent-development/reference/implementer-prompt.md +118 -0
  221. package/app/skills/subagent-development/reference/spec-reviewer-prompt.md +100 -0
  222. package/app/skills/swarm/SKILL.md +81 -0
  223. package/app/skills/swift-patterns/SKILL.md +500 -0
  224. package/app/skills/tdd/SKILL.md +174 -0
  225. package/app/skills/tdd/reference/deep-modules.md +32 -0
  226. package/app/skills/tdd/reference/interface-design.md +32 -0
  227. package/app/skills/tdd/reference/mocking.md +52 -0
  228. package/app/skills/tdd/reference/refactoring.md +10 -0
  229. package/app/skills/tdd/reference/tests.md +59 -0
  230. package/app/skills/teams/SKILL.md +101 -0
  231. package/app/skills/test/SKILL.md +107 -0
  232. package/app/skills/test/scripts/detect-runner.py +113 -0
  233. package/app/skills/testing-patterns/SKILL.md +73 -0
  234. package/app/skills/testing-patterns/reference/flutter-testing.md +33 -0
  235. package/app/skills/testing-patterns/reference/go-testing.md +52 -0
  236. package/app/skills/testing-patterns/reference/php-phpunit.md +39 -0
  237. package/app/skills/testing-patterns/reference/python-pytest.md +228 -0
  238. package/app/skills/testing-patterns/reference/typescript-vitest.md +50 -0
  239. package/app/skills/triage-issue/SKILL.md +120 -0
  240. package/app/skills/typescript-patterns/SKILL.md +256 -0
  241. package/app/skills/ubiquitous-language/SKILL.md +74 -0
  242. package/app/skills/verification-before-completion/SKILL.md +108 -0
  243. package/app/skills/workflow/SKILL.md +250 -0
  244. package/app/skills/write-a-prd/SKILL.md +129 -0
  245. package/app/skills/write-a-prd/reference/visual-companion.md +78 -0
  246. package/app/skills/write-a-prd/scripts/frame-template.html +111 -0
  247. package/app/skills/write-a-prd/scripts/visual-server.cjs +79 -0
  248. package/app/templates/skill/generator/SKILL.md.template +40 -0
  249. package/app/templates/skill/knowledge/SKILL.md.template +52 -0
  250. package/app/templates/skill/linter/SKILL.md.template +34 -0
  251. package/app/templates/skill/reviewer/SKILL.md.template +51 -0
  252. package/app/templates/skill/workflow/SKILL.md.template +49 -0
  253. package/benchmarks/README.md +111 -0
  254. package/benchmarks/ecosystem-dashboard.json +148 -0
  255. package/benchmarks/ecosystem-harvest.json +148 -0
  256. package/benchmarks/results.json +38 -0
  257. package/benchmarks/run.py +351 -0
  258. package/bin/ai-toolkit.js +345 -0
  259. package/kb/best-practices/README.md +11 -0
  260. package/kb/howto/README.md +11 -0
  261. package/kb/procedures/maintenance-sop.md +306 -0
  262. package/kb/reference/agents-catalog.md +124 -0
  263. package/kb/reference/anti-pattern-registry-format.md +221 -0
  264. package/kb/reference/architecture-overview.md +232 -0
  265. package/kb/reference/benchmark-config.md +62 -0
  266. package/kb/reference/ci-integration.md +66 -0
  267. package/kb/reference/claude-ecosystem-benchmark-snapshot.md +80 -0
  268. package/kb/reference/claude-ecosystem-expansion-foundations.md +102 -0
  269. package/kb/reference/commands-catalog.md +21 -0
  270. package/kb/reference/distribution-model.md +63 -0
  271. package/kb/reference/global-install-model.md +56 -0
  272. package/kb/reference/hierarchical-override-pattern.md +200 -0
  273. package/kb/reference/hooks-catalog.md +306 -0
  274. package/kb/reference/integrations.md +88 -0
  275. package/kb/reference/language-packs.md +52 -0
  276. package/kb/reference/merge-friendly-install-model.md +58 -0
  277. package/kb/reference/plugin-pack-conventions.md +151 -0
  278. package/kb/reference/quick-wins-implementation-summary.md +70 -0
  279. package/kb/reference/skill-templates.md +50 -0
  280. package/kb/reference/skills-catalog.md +215 -0
  281. package/kb/reference/skills-unification.md +57 -0
  282. package/kb/reference/stats.md +69 -0
  283. package/kb/reference/sync.md +76 -0
  284. package/kb/troubleshooting/README.md +11 -0
  285. package/llms-full.txt +3068 -0
  286. package/llms.txt +39 -0
  287. package/package.json +75 -0
  288. package/scripts/_common.py +160 -0
  289. package/scripts/add_rule.py +50 -0
  290. package/scripts/benchmark_config.py +127 -0
  291. package/scripts/benchmark_ecosystem.py +288 -0
  292. package/scripts/check_deps.py +260 -0
  293. package/scripts/create_skill.py +118 -0
  294. package/scripts/doctor.py +504 -0
  295. package/scripts/eject.py +113 -0
  296. package/scripts/emission.py +256 -0
  297. package/scripts/evaluate_skills.py +260 -0
  298. package/scripts/frontmatter.py +58 -0
  299. package/scripts/generate_agents_md.py +91 -0
  300. package/scripts/generate_aider_conf.py +51 -0
  301. package/scripts/generate_cline.py +35 -0
  302. package/scripts/generate_copilot.py +30 -0
  303. package/scripts/generate_cursor_rules.py +35 -0
  304. package/scripts/generate_gemini.py +28 -0
  305. package/scripts/generate_llms_txt.py +164 -0
  306. package/scripts/generate_roo_modes.py +80 -0
  307. package/scripts/generate_windsurf.py +35 -0
  308. package/scripts/generator_base.py +140 -0
  309. package/scripts/harvest_ecosystem.py +50 -0
  310. package/scripts/inject_rule_cli.py +101 -0
  311. package/scripts/inject_section_cli.py +47 -0
  312. package/scripts/injection.py +180 -0
  313. package/scripts/install.py +236 -0
  314. package/scripts/install_git_hooks.py +71 -0
  315. package/scripts/install_steps/__init__.py +5 -0
  316. package/scripts/install_steps/ai_tools.py +261 -0
  317. package/scripts/install_steps/hooks.py +90 -0
  318. package/scripts/install_steps/markers.py +79 -0
  319. package/scripts/install_steps/symlinks.py +87 -0
  320. package/scripts/merge-hooks.py +192 -0
  321. package/scripts/plugin.py +642 -0
  322. package/scripts/plugin_schema.py +138 -0
  323. package/scripts/remove_rule.py +58 -0
  324. package/scripts/stats.py +81 -0
  325. package/scripts/sync.py +215 -0
  326. package/scripts/uninstall.py +292 -0
  327. package/scripts/validate.py +700 -0
@@ -0,0 +1,261 @@
1
+ """Install AI tool configs (Cursor, Windsurf, Gemini) and local project setup."""
2
+ from __future__ import annotations
3
+
4
+ import shutil
5
+ import subprocess
6
+ from pathlib import Path
7
+
8
+ from _common import app_dir, inject_section, should_install, toolkit_dir
9
+ from injection import (
10
+ collapse_blank_runs as _collapse_blank_runs,
11
+ strip_section as _strip_section,
12
+ trim_trailing_blanks as _trim_trailing_blanks,
13
+ )
14
+
15
+
16
+ def install_ai_tools(target_dir: Path, rules_dir: Path,
17
+ only: str, skip: str, dry_run: bool) -> None:
18
+ """Install Cursor, Windsurf, Gemini global configs."""
19
+ print()
20
+ print("## Other AI Tools (global)")
21
+ print()
22
+
23
+ if should_install("cursor", only, skip):
24
+ cursor_file = target_dir / ".cursor" / "rules"
25
+ if dry_run:
26
+ print(" Would inject: ~/.cursor/rules")
27
+ else:
28
+ inject_with_rules("generate-cursor-rules.sh", cursor_file, rules_dir)
29
+ else:
30
+ print(" Skipped: cursor")
31
+
32
+ if should_install("windsurf", only, skip):
33
+ windsurf_file = target_dir / ".codeium" / "windsurf" / "memories" / "global_rules.md"
34
+ if dry_run:
35
+ print(" Would inject: ~/.codeium/windsurf/memories/global_rules.md")
36
+ else:
37
+ inject_with_rules("generate-windsurf.sh", windsurf_file, rules_dir)
38
+ else:
39
+ print(" Skipped: windsurf")
40
+
41
+ if should_install("gemini", only, skip):
42
+ gemini_file = target_dir / ".gemini" / "GEMINI.md"
43
+ if dry_run:
44
+ print(" Would inject: ~/.gemini/GEMINI.md")
45
+ else:
46
+ inject_with_rules("generate-gemini.sh", gemini_file, rules_dir)
47
+ else:
48
+ print(" Skipped: gemini")
49
+
50
+ print()
51
+ print(" Note: Copilot, Cline, Roo Code, and Aider have no global config -- use 'ai-toolkit install --local' per project")
52
+
53
+
54
+ def inject_with_rules(
55
+ generator_script: str,
56
+ target_file: Path,
57
+ rules_dir: Path,
58
+ ) -> None:
59
+ """Run a generator script, write output, then inject registered rules."""
60
+ scripts_dir = toolkit_dir / "scripts"
61
+ py_name = generator_script.replace(".sh", ".py").replace("-", "_")
62
+ if (scripts_dir / py_name).is_file():
63
+ cmd = ["python3", str(scripts_dir / py_name)]
64
+ else:
65
+ cmd = ["bash", str(scripts_dir / generator_script)]
66
+
67
+ result = subprocess.run(cmd, capture_output=True, text=True)
68
+ if result.returncode != 0:
69
+ print(f" ERROR: {generator_script} failed: {result.stderr.strip()}")
70
+ return
71
+
72
+ generated = result.stdout
73
+ start_marker = "<!-- TOOLKIT:ai-toolkit START -->"
74
+
75
+ target_file = Path(target_file)
76
+ target_file.parent.mkdir(parents=True, exist_ok=True)
77
+ if not target_file.exists():
78
+ target_file.touch()
79
+
80
+ existing = target_file.read_text(encoding="utf-8")
81
+ if start_marker in existing:
82
+ existing = _strip_section(existing, "ai-toolkit")
83
+ existing = _trim_trailing_blanks(existing)
84
+
85
+ parts: list[str] = []
86
+ if existing.strip():
87
+ parts.append(existing)
88
+ parts.append("")
89
+ parts.append(generated.rstrip("\n"))
90
+
91
+ output = "\n".join(parts) + "\n"
92
+ output = _collapse_blank_runs(output)
93
+ target_file.write_text(output, encoding="utf-8")
94
+
95
+ if rules_dir.is_dir():
96
+ for rule_file in sorted(rules_dir.glob("*.md")):
97
+ inject_section(rule_file, target_file, rule_file.stem)
98
+
99
+ print(f" Updated: {target_file}")
100
+
101
+
102
+ def run_script(script_name: str, *args: str, capture: bool = False) -> str:
103
+ """Run a script from the scripts/ directory (prefers .py over .sh)."""
104
+ scripts_dir = toolkit_dir / "scripts"
105
+ py_name = script_name.replace(".sh", ".py").replace("-", "_")
106
+ if (scripts_dir / py_name).is_file():
107
+ cmd = ["python3", str(scripts_dir / py_name), *args]
108
+ else:
109
+ cmd = ["bash", str(scripts_dir / script_name), *args]
110
+ result = subprocess.run(cmd, capture_output=capture, text=True)
111
+ return result.stdout if capture else ""
112
+
113
+
114
+ def install_local_project(rules_dir: Path, dry_run: bool, reset: bool) -> None:
115
+ """Install project-local configs."""
116
+ cwd = Path.cwd()
117
+ print()
118
+ print(f"## Project-local ({cwd})")
119
+ if reset:
120
+ print(" Mode: RESET (all local configs will be wiped and recreated)")
121
+ print()
122
+
123
+ if dry_run:
124
+ _install_local_dry_run(reset)
125
+ return
126
+
127
+ (cwd / ".claude").mkdir(parents=True, exist_ok=True)
128
+
129
+ if reset:
130
+ _reset_local_configs(cwd)
131
+
132
+ _create_local_claude_md(cwd, reset)
133
+ _create_local_settings(cwd, reset)
134
+
135
+ legacy_local_hooks = cwd / ".claude" / "hooks.json"
136
+ if legacy_local_hooks.is_file():
137
+ legacy_local_hooks.unlink()
138
+ print(" Removed: .claude/hooks.json (legacy)")
139
+ print(" Note: hooks are merged into global ~/.claude/settings.json only (not project-local)")
140
+
141
+ constitution_src = app_dir / "constitution.md"
142
+ if constitution_src.is_file():
143
+ inject_section(
144
+ constitution_src,
145
+ cwd / ".claude" / "constitution.md",
146
+ "constitution",
147
+ )
148
+ print(" Injected: .claude/constitution.md")
149
+
150
+ _create_local_ai_tool_configs(cwd, rules_dir)
151
+
152
+
153
+ def _install_local_dry_run(reset: bool) -> None:
154
+ if reset:
155
+ print(" Would remove: CLAUDE.md, .claude/settings.local.json")
156
+ print(" Would remove: .claude/constitution.md, .github/copilot-instructions.md, .clinerules, .roomodes, .aider.conf.yml")
157
+ print(" Would recreate all from templates (clean slate)")
158
+ print(" Would install git hooks (if .git/hooks exists)")
159
+ else:
160
+ print(" Would create: CLAUDE.md (if missing)")
161
+ print(" Would create: .claude/settings.local.json (if missing)")
162
+ print(" Would inject: .claude/constitution.md")
163
+ print(" Would inject: .github/copilot-instructions.md")
164
+ print(" Would inject: .clinerules")
165
+ print(" Would inject: .roomodes")
166
+ print(" Would inject: .aider.conf.yml")
167
+ print(" Would install: .git/hooks/pre-commit")
168
+
169
+
170
+ def _reset_local_configs(cwd: Path) -> None:
171
+ for rel in (
172
+ "CLAUDE.md",
173
+ ".claude/settings.local.json",
174
+ ".claude/constitution.md",
175
+ ".github/copilot-instructions.md",
176
+ ".clinerules",
177
+ ".roomodes",
178
+ ".aider.conf.yml",
179
+ ):
180
+ p = cwd / rel
181
+ if p.is_file():
182
+ p.unlink()
183
+ print(f" Removed: {rel}")
184
+
185
+
186
+ def _create_local_claude_md(cwd: Path, reset: bool) -> None:
187
+ claude_local = cwd / "CLAUDE.md"
188
+ if reset or not claude_local.is_file():
189
+ template = app_dir / "CLAUDE.md.template"
190
+ if template.is_file():
191
+ shutil.copy2(template, claude_local)
192
+ print(" Created: CLAUDE.md (from template)")
193
+ else:
194
+ claude_local.write_text(
195
+ """\
196
+ # [Project Name]
197
+
198
+ ## Overview
199
+
200
+ ## Tech Stack
201
+ - **Language**:
202
+ - **Framework**:
203
+ - **Database**:
204
+
205
+ ## Commands
206
+ ```bash
207
+ # Dev:
208
+ # Test:
209
+ # Lint:
210
+ # Build:
211
+ ```
212
+
213
+ ## Key Conventions
214
+
215
+ ## MCP Servers
216
+ """,
217
+ encoding="utf-8",
218
+ )
219
+ print(" Created: CLAUDE.md (default template)")
220
+ else:
221
+ print(" Kept: CLAUDE.md (already exists)")
222
+
223
+
224
+ def _create_local_settings(cwd: Path, reset: bool) -> None:
225
+ settings_local = cwd / ".claude" / "settings.local.json"
226
+ if reset or not settings_local.is_file():
227
+ mcp_defaults = app_dir / "mcp-defaults.json"
228
+ if mcp_defaults.is_file():
229
+ shutil.copy2(mcp_defaults, settings_local)
230
+ print(" Created: .claude/settings.local.json (from mcp-defaults)")
231
+ else:
232
+ settings_local.write_text(
233
+ '{\n "mcpServers": {},\n "env": {}\n}\n',
234
+ encoding="utf-8",
235
+ )
236
+ print(" Created: .claude/settings.local.json")
237
+ else:
238
+ print(" Kept: .claude/settings.local.json (already exists)")
239
+
240
+
241
+ def _create_local_ai_tool_configs(cwd: Path, rules_dir: Path) -> None:
242
+ inject_with_rules(
243
+ "generate-copilot.sh",
244
+ cwd / ".github" / "copilot-instructions.md",
245
+ rules_dir,
246
+ )
247
+ inject_with_rules(
248
+ "generate-cline.sh",
249
+ cwd / ".clinerules",
250
+ rules_dir,
251
+ )
252
+
253
+ roo_output = run_script("generate-roo-modes.sh", capture=True)
254
+ (cwd / ".roomodes").write_text(roo_output, encoding="utf-8")
255
+ print(" Created: .roomodes")
256
+
257
+ aider_output = run_script("generate-aider-conf.sh", capture=True)
258
+ (cwd / ".aider.conf.yml").write_text(aider_output, encoding="utf-8")
259
+ print(" Created: .aider.conf.yml")
260
+
261
+ run_script("install-git-hooks.sh", str(cwd))
@@ -0,0 +1,90 @@
1
+ """Hook installation: copy scripts, merge JSON, output styles."""
2
+ from __future__ import annotations
3
+
4
+ import json
5
+ import shutil
6
+ import subprocess
7
+ from pathlib import Path
8
+
9
+ from _common import app_dir, should_install, toolkit_dir
10
+
11
+
12
+ def install_hooks(claude_dir: Path, hooks_scripts_dir: Path,
13
+ only: str, skip: str, dry_run: bool) -> None:
14
+ """Install hooks: copy scripts, merge JSON, install output styles."""
15
+ if not should_install("hooks", only, skip):
16
+ print(" Skipped: hooks")
17
+ return
18
+
19
+ hooks_json = app_dir / "hooks.json"
20
+ if not hooks_json.is_file():
21
+ return
22
+
23
+ if dry_run:
24
+ print(" Would merge: .claude/settings.json hooks (toolkit entries into user settings)")
25
+ output_styles_dir = app_dir / "output-styles"
26
+ if output_styles_dir.is_dir():
27
+ print(" Would install: output styles to ~/.claude/output-styles/ + set outputStyle in settings.json")
28
+ return
29
+
30
+ _copy_hook_scripts(claude_dir, hooks_scripts_dir)
31
+ _run_merge_hooks(
32
+ "inject",
33
+ str(hooks_json),
34
+ str(claude_dir / "settings.json"),
35
+ )
36
+ print(" Merged: .claude/settings.json (hooks)")
37
+
38
+ legacy_hooks_json = claude_dir / "hooks.json"
39
+ if legacy_hooks_json.is_file():
40
+ legacy_hooks_json.unlink()
41
+ print(" Removed: .claude/hooks.json (legacy)")
42
+
43
+ _install_output_styles(claude_dir)
44
+
45
+
46
+ def _copy_hook_scripts(claude_dir: Path, hooks_scripts_dir: Path) -> None:
47
+ hooks_src = app_dir / "hooks"
48
+ if not hooks_src.is_dir():
49
+ return
50
+ copied = 0
51
+ for hook_file in sorted(hooks_src.glob("*.sh")):
52
+ dst = hooks_scripts_dir / hook_file.name
53
+ shutil.copy2(hook_file, dst)
54
+ dst.chmod(dst.stat().st_mode | 0o111)
55
+ copied += 1
56
+ print(f" Copied: {copied} hook scripts to ~/.ai-toolkit/hooks/")
57
+ legacy_hooks = claude_dir / "hooks"
58
+ if legacy_hooks.is_symlink():
59
+ legacy_hooks.unlink()
60
+ print(" Removed: .claude/hooks (legacy symlink)")
61
+
62
+
63
+ def _run_merge_hooks(action: str, *args: str) -> None:
64
+ cmd = ["python3", str(toolkit_dir / "scripts" / "merge-hooks.py"), action, *args]
65
+ subprocess.run(cmd, check=True)
66
+
67
+
68
+ def _install_output_styles(claude_dir: Path) -> None:
69
+ output_styles_src = app_dir / "output-styles"
70
+ if not output_styles_src.is_dir():
71
+ return
72
+ output_styles_dst = claude_dir / "output-styles"
73
+ output_styles_dst.mkdir(parents=True, exist_ok=True)
74
+ styles_copied = 0
75
+ for style_file in sorted(output_styles_src.glob("*.md")):
76
+ shutil.copy2(style_file, output_styles_dst / style_file.name)
77
+ styles_copied += 1
78
+ if styles_copied > 0:
79
+ print(f" Copied: {styles_copied} output style(s) to ~/.claude/output-styles/")
80
+ settings_path = claude_dir / "settings.json"
81
+ if settings_path.is_file():
82
+ with open(settings_path, encoding="utf-8") as f:
83
+ settings_data = json.load(f)
84
+ else:
85
+ settings_data = {}
86
+ if "outputStyle" not in settings_data:
87
+ settings_data["outputStyle"] = "Golden Rules"
88
+ with open(settings_path, "w", encoding="utf-8") as f:
89
+ json.dump(settings_data, f, indent=4)
90
+ print(" Set: outputStyle = 'Golden Rules' in settings.json")
@@ -0,0 +1,79 @@
1
+ """Marker file injection and rule injection."""
2
+ from __future__ import annotations
3
+
4
+ from pathlib import Path
5
+
6
+ from _common import app_dir, inject_rule, inject_section, should_install
7
+
8
+
9
+ def install_marker_files(claude_dir: Path, only: str, skip: str,
10
+ dry_run: bool) -> None:
11
+ """Inject constitution.md, ARCHITECTURE.md via markers."""
12
+ marker_files = [
13
+ ("constitution.md", "constitution", "constitution"),
14
+ ("ARCHITECTURE.md", "architecture", "architecture"),
15
+ ]
16
+ for filename, component, section in marker_files:
17
+ if not should_install(component, only, skip):
18
+ print(f" Skipped: .claude/{filename}")
19
+ continue
20
+ src = app_dir / filename
21
+ if not src.is_file():
22
+ continue
23
+ if dry_run:
24
+ print(f" Would inject: .claude/{filename} (marker-based, preserves user content)")
25
+ continue
26
+ dst = claude_dir / filename
27
+ if dst.is_symlink():
28
+ dst.unlink()
29
+ print(f" Upgraded: .claude/{filename} (symlink -> marker injection)")
30
+ inject_section(src, dst, section)
31
+ print(f" Injected: .claude/{filename}")
32
+
33
+
34
+ def inject_rules(claude_dir: Path, target_dir: Path, rules_dir: Path,
35
+ only: str, skip: str, dry_run: bool) -> None:
36
+ """Inject rules into CLAUDE.md."""
37
+ claude_md = claude_dir / "CLAUDE.md"
38
+
39
+ if dry_run:
40
+ _inject_rules_dry_run(rules_dir)
41
+ return
42
+
43
+ if not claude_md.is_file():
44
+ claude_md.touch()
45
+ print(" Created: ~/.claude/CLAUDE.md")
46
+
47
+ if not should_install("rules", only, skip):
48
+ print(" Skipped: rules injection")
49
+
50
+ rules_injected: list[str] = []
51
+
52
+ if rules_dir.is_dir():
53
+ for rule_file in sorted(rules_dir.glob("*.md")):
54
+ rule_name = rule_file.stem
55
+ inject_rule(rule_file, target_dir)
56
+ rules_injected.append(rule_name)
57
+
58
+ if should_install("rules", only, skip):
59
+ rules_src = app_dir / "rules"
60
+ if rules_src.is_dir():
61
+ for source_file in sorted(rules_src.glob("*.md")):
62
+ rule_name = source_file.stem
63
+ inject_rule(source_file, target_dir)
64
+ rules_injected.append(rule_name)
65
+
66
+ print(f" Rules injected: {' '.join(rules_injected)}")
67
+
68
+
69
+ def _inject_rules_dry_run(rules_dir: Path) -> None:
70
+ rules_src = app_dir / "rules"
71
+ rule_names = " ".join(
72
+ f.stem for f in sorted(rules_src.glob("*.md"))
73
+ ) if rules_src.is_dir() else ""
74
+ print(f" Would inject rules: {rule_names}")
75
+ if rules_dir.is_dir():
76
+ registered = list(rules_dir.glob("*.md"))
77
+ if registered:
78
+ reg_names = " ".join(f.stem for f in sorted(registered))
79
+ print(f" Would inject registered rules: {reg_names}")
@@ -0,0 +1,87 @@
1
+ """Agent and skill symlink management."""
2
+ from __future__ import annotations
3
+
4
+ from pathlib import Path
5
+
6
+ from _common import app_dir, should_install
7
+
8
+
9
+ def install_agents(claude_dir: Path, only: str, skip: str, dry_run: bool) -> None:
10
+ """Create per-file symlinks for agents."""
11
+ if not should_install("agents", only, skip):
12
+ print(" Skipped: .claude/agents")
13
+ return
14
+
15
+ agents_src = app_dir / "agents"
16
+ if not agents_src.is_dir():
17
+ return
18
+
19
+ if dry_run:
20
+ print(" Would link: .claude/agents/*.md (per-file merge)")
21
+ return
22
+
23
+ agents_dst = claude_dir / "agents"
24
+ if agents_dst.is_symlink():
25
+ agents_dst.unlink()
26
+ print(" Upgraded: .claude/agents (directory symlink -> per-file)")
27
+ agents_dst.mkdir(parents=True, exist_ok=True)
28
+ linked = 0
29
+ skipped = 0
30
+ for agent_file in sorted(agents_src.glob("*.md")):
31
+ target = agents_dst / agent_file.name
32
+ if target.is_symlink():
33
+ target.unlink()
34
+ if target.is_file():
35
+ skipped += 1
36
+ continue
37
+ target.symlink_to(agent_file)
38
+ linked += 1
39
+ print(f" Linked: .claude/agents/ ({linked} files)")
40
+ if skipped > 0:
41
+ print(f" Kept: {skipped} user agent(s) (name conflict)")
42
+
43
+
44
+ def install_skills(claude_dir: Path, only: str, skip: str, dry_run: bool) -> None:
45
+ """Create per-directory symlinks for skills."""
46
+ if not should_install("skills", only, skip):
47
+ print(" Skipped: .claude/skills")
48
+ return
49
+
50
+ skills_src = app_dir / "skills"
51
+ if not skills_src.is_dir():
52
+ return
53
+
54
+ if dry_run:
55
+ print(" Would link: .claude/skills/*/ (per-directory merge)")
56
+ return
57
+
58
+ skills_dst = claude_dir / "skills"
59
+ if skills_dst.is_symlink():
60
+ skills_dst.unlink()
61
+ print(" Upgraded: .claude/skills (directory symlink -> per-directory)")
62
+ skills_dst.mkdir(parents=True, exist_ok=True)
63
+ linked = 0
64
+ skipped = 0
65
+ for skill_dir in sorted(skills_src.iterdir()):
66
+ if not skill_dir.is_dir() or skill_dir.name.startswith("_"):
67
+ continue
68
+ target = skills_dst / skill_dir.name
69
+ if target.is_symlink():
70
+ target.unlink()
71
+ if target.is_dir():
72
+ skipped += 1
73
+ continue
74
+ target.symlink_to(skill_dir)
75
+ linked += 1
76
+ print(f" Linked: .claude/skills/ ({linked} directories)")
77
+ if skipped > 0:
78
+ print(f" Kept: {skipped} user skill(s) (name conflict)")
79
+
80
+
81
+ def clean_legacy_commands(claude_dir: Path, dry_run: bool) -> None:
82
+ """Remove legacy commands symlink."""
83
+ legacy_commands = claude_dir / "commands"
84
+ if legacy_commands.is_symlink():
85
+ if not dry_run:
86
+ legacy_commands.unlink()
87
+ print(" Removed: .claude/commands (legacy)")