@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,101 @@
1
+ #!/usr/bin/env python3
2
+ """CLI wrapper around inject_rule() and remove_rule_section() from _common.
3
+
4
+ Injects a rule file into CLAUDE.md using idempotent marker-based injection.
5
+ Re-running updates only the marked section. Content outside markers is
6
+ never touched.
7
+
8
+ Usage:
9
+ inject_rule_cli.py <rule-file> [target-dir]
10
+ inject_rule_cli.py --remove <rule-name> [target-dir]
11
+
12
+ Arguments:
13
+ rule-file Path to a .md file with the rule content
14
+ target-dir Directory containing .claude/CLAUDE.md (default: $HOME)
15
+
16
+ Flags:
17
+ --remove Remove a rule section instead of injecting
18
+ """
19
+ from __future__ import annotations
20
+
21
+ import sys
22
+ from pathlib import Path
23
+
24
+ sys.path.insert(0, str(Path(__file__).resolve().parent))
25
+ from _common import inject_rule, remove_rule_section
26
+
27
+
28
+ def _parse_args(argv: list[str]) -> dict:
29
+ """Parse CLI arguments."""
30
+ result: dict = {
31
+ "remove_mode": False,
32
+ "remove_name": "",
33
+ "source_file": "",
34
+ "target_dir": str(Path.home()),
35
+ }
36
+
37
+ i = 0
38
+ while i < len(argv):
39
+ arg = argv[i]
40
+ if arg == "--remove":
41
+ result["remove_mode"] = True
42
+ i += 1
43
+ if i >= len(argv):
44
+ print("--remove requires a rule name", file=sys.stderr)
45
+ sys.exit(1)
46
+ result["remove_name"] = argv[i]
47
+ elif arg.startswith("-"):
48
+ print(f"Unknown option: {arg}", file=sys.stderr)
49
+ sys.exit(1)
50
+ elif not result["source_file"] and not result["remove_mode"]:
51
+ result["source_file"] = arg
52
+ else:
53
+ result["target_dir"] = arg
54
+ i += 1
55
+
56
+ return result
57
+
58
+
59
+ def main() -> None:
60
+ """Inject or remove a rule in CLAUDE.md."""
61
+ args = _parse_args(sys.argv[1:])
62
+ target_dir = Path(args["target_dir"])
63
+ claude_md = target_dir / ".claude" / "CLAUDE.md"
64
+
65
+ # -- remove mode ---------------------------------------------------------
66
+ if args["remove_mode"]:
67
+ rule_name = args["remove_name"]
68
+ if not claude_md.is_file():
69
+ print(f"No CLAUDE.md found at {target_dir}", file=sys.stderr)
70
+ sys.exit(1)
71
+
72
+ found = remove_rule_section(rule_name, target_dir)
73
+ if found:
74
+ print(f"Removed rule '{rule_name}' from {claude_md}")
75
+ else:
76
+ print(f"Rule '{rule_name}' not found in {claude_md}")
77
+ return
78
+
79
+ # -- inject mode ---------------------------------------------------------
80
+ source_file = args["source_file"]
81
+ if not source_file:
82
+ print("Usage: inject_rule_cli.py <rule-file> [target-dir]", file=sys.stderr)
83
+ print(" inject_rule_cli.py --remove <rule-name> [target-dir]", file=sys.stderr)
84
+ sys.exit(1)
85
+
86
+ source_path = Path(source_file)
87
+ if not source_path.is_file():
88
+ print(f"Rule file not found: {source_path}", file=sys.stderr)
89
+ sys.exit(1)
90
+
91
+ # Ensure .claude/ directory and CLAUDE.md exist
92
+ claude_md.parent.mkdir(parents=True, exist_ok=True)
93
+ if not claude_md.is_file():
94
+ print(f"Created: {claude_md}")
95
+
96
+ action = inject_rule(source_path, target_dir)
97
+ print(f" {action}: {claude_md}")
98
+
99
+
100
+ if __name__ == "__main__":
101
+ main()
@@ -0,0 +1,47 @@
1
+ #!/usr/bin/env python3
2
+ """CLI wrapper around inject_section() from _common.
3
+
4
+ Injects content between TOOLKIT markers into any file.
5
+ Existing content outside the markers is preserved -- never overwritten.
6
+ Re-running updates only the marked section (idempotent).
7
+
8
+ Usage:
9
+ inject_section_cli.py <content-file> <target-file> [section-name]
10
+
11
+ Arguments:
12
+ content-file Path to file with content to inject
13
+ target-file Path to target file (created if missing)
14
+ section-name Marker label (default: ai-toolkit)
15
+ """
16
+ from __future__ import annotations
17
+
18
+ import sys
19
+ from pathlib import Path
20
+
21
+ sys.path.insert(0, str(Path(__file__).resolve().parent))
22
+ from _common import inject_section
23
+
24
+
25
+ def main() -> None:
26
+ """Inject a content file into a target file between TOOLKIT markers."""
27
+ if len(sys.argv) < 3:
28
+ print(
29
+ "Usage: inject_section_cli.py <content-file> <target-file> [section-name]",
30
+ file=sys.stderr,
31
+ )
32
+ sys.exit(1)
33
+
34
+ content_file = Path(sys.argv[1])
35
+ target_file = Path(sys.argv[2])
36
+ section = sys.argv[3] if len(sys.argv) > 3 else "ai-toolkit"
37
+
38
+ if not content_file.is_file():
39
+ print(f"Content file not found: {content_file}", file=sys.stderr)
40
+ sys.exit(1)
41
+
42
+ action = inject_section(content_file, target_file, section)
43
+ print(f" {action}: {target_file}")
44
+
45
+
46
+ if __name__ == "__main__":
47
+ main()
@@ -0,0 +1,180 @@
1
+ """Marker-based section injection and rule management.
2
+
3
+ Handles idempotent injection of content between TOOLKIT markers
4
+ into target files. Also manages rule injection into CLAUDE.md.
5
+
6
+ Stdlib-only.
7
+
8
+ Usage::
9
+
10
+ from injection import inject_section, inject_rule, remove_rule_section
11
+ """
12
+ from __future__ import annotations
13
+
14
+ import re
15
+ from pathlib import Path
16
+
17
+
18
+ # ---------------------------------------------------------------------------
19
+ # Markers
20
+ # ---------------------------------------------------------------------------
21
+
22
+ def markers_start(section: str = "ai-toolkit") -> str:
23
+ """Return the TOOLKIT start marker block."""
24
+ return (
25
+ f"<!-- TOOLKIT:{section} START -->\n"
26
+ f"<!-- Auto-injected by ai-toolkit. Re-run to update. -->\n"
27
+ )
28
+
29
+
30
+ def markers_end(section: str = "ai-toolkit") -> str:
31
+ """Return the TOOLKIT end marker."""
32
+ return f"\n<!-- TOOLKIT:{section} END -->"
33
+
34
+
35
+ # ---------------------------------------------------------------------------
36
+ # Internal helpers
37
+ # ---------------------------------------------------------------------------
38
+
39
+ def strip_section(content: str, section: str) -> str:
40
+ """Remove a TOOLKIT marker section from content."""
41
+ start = f"<!-- TOOLKIT:{section} START -->"
42
+ end = f"<!-- TOOLKIT:{section} END -->"
43
+ lines: list[str] = []
44
+ skip = False
45
+ for line in content.splitlines(keepends=True):
46
+ stripped = line.rstrip("\n")
47
+ if stripped == start:
48
+ skip = True
49
+ continue
50
+ if stripped == end:
51
+ skip = False
52
+ continue
53
+ if not skip:
54
+ lines.append(line)
55
+ return "".join(lines)
56
+
57
+
58
+ def trim_trailing_blanks(text: str) -> str:
59
+ """Remove trailing blank lines from text."""
60
+ lines = text.splitlines()
61
+ while lines and not lines[-1].strip():
62
+ lines.pop()
63
+ return "\n".join(lines)
64
+
65
+
66
+ def collapse_blank_runs(text: str, max_blanks: int = 2) -> str:
67
+ """Collapse runs of more than max_blanks consecutive blank lines."""
68
+ lines = text.splitlines(keepends=True)
69
+ result: list[str] = []
70
+ blanks = 0
71
+ for line in lines:
72
+ if not line.strip():
73
+ blanks += 1
74
+ if blanks <= max_blanks:
75
+ result.append(line)
76
+ else:
77
+ blanks = 0
78
+ result.append(line)
79
+ return "".join(result)
80
+
81
+
82
+ # ---------------------------------------------------------------------------
83
+ # Public API
84
+ # ---------------------------------------------------------------------------
85
+
86
+ def inject_section(
87
+ content_file: str | Path,
88
+ target_file: str | Path,
89
+ section: str = "ai-toolkit",
90
+ ) -> str:
91
+ """Inject content between TOOLKIT markers into target file.
92
+
93
+ Existing content outside markers is preserved. Re-running updates
94
+ only the marked section (idempotent).
95
+
96
+ Returns action taken: "Created" or "Updated".
97
+ """
98
+ content_file = Path(content_file)
99
+ target_file = Path(target_file)
100
+
101
+ # Sanitize section name
102
+ section = re.sub(r"[^a-zA-Z0-9_-]", "", section)
103
+
104
+ # Create parent dir and target if missing
105
+ target_file.parent.mkdir(parents=True, exist_ok=True)
106
+ if not target_file.exists():
107
+ target_file.touch()
108
+ action = "Created"
109
+ else:
110
+ action = "Updated"
111
+
112
+ # Read existing content
113
+ existing = target_file.read_text(encoding="utf-8")
114
+
115
+ # Strip existing section
116
+ existing = strip_section(existing, section)
117
+ existing = trim_trailing_blanks(existing)
118
+
119
+ # Read content to inject
120
+ new_content = content_file.read_text(encoding="utf-8")
121
+
122
+ # Build output
123
+ parts: list[str] = []
124
+ if existing.strip():
125
+ parts.append(existing)
126
+ parts.append("")
127
+
128
+ parts.append(f"<!-- TOOLKIT:{section} START -->")
129
+ parts.append("<!-- Auto-injected by ai-toolkit. Re-run to update. -->")
130
+ parts.append("")
131
+ parts.append(new_content.rstrip("\n"))
132
+ parts.append("")
133
+ parts.append(f"<!-- TOOLKIT:{section} END -->")
134
+
135
+ output = "\n".join(parts) + "\n"
136
+ output = collapse_blank_runs(output)
137
+
138
+ target_file.write_text(output, encoding="utf-8")
139
+ return action
140
+
141
+
142
+ def inject_rule(rule_file: str | Path, target_dir: str | Path) -> str:
143
+ """Inject a rule file into target_dir/.claude/CLAUDE.md.
144
+
145
+ Returns action taken.
146
+ """
147
+ rule_file = Path(rule_file)
148
+ target_dir = Path(target_dir)
149
+
150
+ if not rule_file.is_file():
151
+ raise FileNotFoundError(f"Rule file not found: {rule_file}")
152
+
153
+ rule_name = re.sub(r"[^a-zA-Z0-9_-]", "", rule_file.stem)
154
+ claude_md = target_dir / ".claude" / "CLAUDE.md"
155
+ claude_md.parent.mkdir(parents=True, exist_ok=True)
156
+
157
+ return inject_section(rule_file, claude_md, rule_name)
158
+
159
+
160
+ def remove_rule_section(rule_name: str, target_dir: str | Path) -> bool:
161
+ """Remove a rule section from target_dir/.claude/CLAUDE.md.
162
+
163
+ Returns True if section was found and removed.
164
+ """
165
+ target_dir = Path(target_dir)
166
+ claude_md = target_dir / ".claude" / "CLAUDE.md"
167
+
168
+ if not claude_md.is_file():
169
+ return False
170
+
171
+ content = claude_md.read_text(encoding="utf-8")
172
+ start_marker = f"<!-- TOOLKIT:{rule_name} START -->"
173
+
174
+ if start_marker not in content:
175
+ return False
176
+
177
+ content = strip_section(content, rule_name)
178
+ content = trim_trailing_blanks(content) + "\n"
179
+ claude_md.write_text(content, encoding="utf-8")
180
+ return True
@@ -0,0 +1,236 @@
1
+ #!/usr/bin/env python3
2
+ """AI Toolkit Installer.
3
+
4
+ Installs toolkit GLOBALLY -- Claude Code + all supported AI tools.
5
+ Re-running is idempotent: updates only marker-delimited sections,
6
+ never touching user content outside the markers.
7
+
8
+ Claude Code (~/.claude/):
9
+ - Per-file symlinks: agents/*.md, skills/*/ (merges with user files)
10
+ - Merged JSON: hooks.json (toolkit entries tagged with _source)
11
+ - Marker injection: constitution.md, ARCHITECTURE.md (preserves user content)
12
+ - Rules injected into ~/.claude/CLAUDE.md
13
+
14
+ Other tools (global config locations):
15
+ - Cursor: ~/.cursor/rules
16
+ - Windsurf: ~/.codeium/windsurf/memories/global_rules.md
17
+ - Gemini: ~/.gemini/GEMINI.md
18
+
19
+ Registered rules (~/.ai-toolkit/rules/*.md) are also injected into
20
+ all of the above. Add rules with: ai-toolkit add-rule <rule.md>
21
+
22
+ Usage:
23
+ python3 scripts/install.py [target-dir] [options]
24
+
25
+ Options:
26
+ --only agents,hooks Install only listed components
27
+ --skip skills Skip listed components
28
+ --local Also inject into project-local configs
29
+ --list, --dry-run Dry-run: show what would be installed
30
+ --reset Wipe and recreate local configs
31
+ --profile <p> minimal|standard|strict
32
+
33
+ Components: agents, skills, hooks, constitution, architecture, rules,
34
+ cursor, windsurf, gemini
35
+ """
36
+ from __future__ import annotations
37
+
38
+ import sys
39
+ from pathlib import Path
40
+
41
+ sys.path.insert(0, str(Path(__file__).resolve().parent))
42
+ from _common import toolkit_dir
43
+ from emission import agent_count as count_agents, skill_count as count_skills
44
+
45
+ # Step modules
46
+ from install_steps.symlinks import install_agents, install_skills, clean_legacy_commands
47
+ from install_steps.hooks import install_hooks
48
+ from install_steps.markers import install_marker_files, inject_rules
49
+ from install_steps.ai_tools import install_ai_tools, install_local_project, run_script
50
+
51
+
52
+ # ---------------------------------------------------------------------------
53
+ # Argument parsing
54
+ # ---------------------------------------------------------------------------
55
+
56
+ def parse_args(argv: list[str]) -> dict:
57
+ """Parse CLI arguments into a config dict."""
58
+ cfg: dict = {
59
+ "target_dir": Path.home(),
60
+ "only": "",
61
+ "skip": "",
62
+ "dry_run": False,
63
+ "local": False,
64
+ "reset": False,
65
+ "profile": "",
66
+ }
67
+ i = 0
68
+ while i < len(argv):
69
+ arg = argv[i]
70
+ if arg in ("--list", "--dry-run"):
71
+ cfg["dry_run"] = True
72
+ elif arg == "--local":
73
+ cfg["local"] = True
74
+ elif arg == "--reset":
75
+ cfg["reset"] = True
76
+ elif arg.startswith("--only="):
77
+ cfg["only"] = arg.split("=", 1)[1]
78
+ elif arg == "--only":
79
+ i += 1
80
+ cfg["only"] = argv[i] if i < len(argv) else ""
81
+ elif arg.startswith("--skip="):
82
+ cfg["skip"] = arg.split("=", 1)[1]
83
+ elif arg == "--skip":
84
+ i += 1
85
+ cfg["skip"] = argv[i] if i < len(argv) else ""
86
+ elif arg.startswith("--profile="):
87
+ cfg["profile"] = arg.split("=", 1)[1]
88
+ elif arg == "--profile":
89
+ i += 1
90
+ cfg["profile"] = argv[i] if i < len(argv) else ""
91
+ elif arg.startswith("-"):
92
+ print(f"Unknown option: {arg}")
93
+ sys.exit(1)
94
+ else:
95
+ cfg["target_dir"] = Path(arg)
96
+ i += 1
97
+ return cfg
98
+
99
+
100
+ # ---------------------------------------------------------------------------
101
+ # Dependency check
102
+ # ---------------------------------------------------------------------------
103
+
104
+ def check_dependencies() -> None:
105
+ """Run check_deps and abort if any required dep is missing."""
106
+ from check_deps import check_deps
107
+
108
+ results = check_deps(verbose=False)
109
+ if not results["all_ok"]:
110
+ from check_deps import print_report
111
+ print_report(results)
112
+ print("Aborting install: missing required dependencies.")
113
+ sys.exit(1)
114
+
115
+
116
+ # ---------------------------------------------------------------------------
117
+ # Profile / Banner / Summary
118
+ # ---------------------------------------------------------------------------
119
+
120
+ def resolve_profile(profile: str, only: str) -> str:
121
+ if profile == "minimal":
122
+ if not only:
123
+ return "agents,skills"
124
+ elif profile in ("standard", ""):
125
+ pass
126
+ elif profile == "strict":
127
+ pass
128
+ else:
129
+ print(f"Unknown profile: {profile} (valid: minimal, standard, strict)")
130
+ sys.exit(1)
131
+ return only
132
+
133
+
134
+ def print_banner(target_dir: Path, rules_dir: Path, profile: str,
135
+ only: str, skip: str, dry_run: bool) -> None:
136
+ print("AI Toolkit Installer")
137
+ print("========================")
138
+ print(f"Toolkit: {toolkit_dir}")
139
+ print(f"Target: {target_dir.resolve()}")
140
+ print(f"Rules: {rules_dir}")
141
+ if profile:
142
+ print(f"Profile: {profile}")
143
+ if only:
144
+ print(f"Only: {only}")
145
+ if skip:
146
+ print(f"Skip: {skip}")
147
+ if dry_run:
148
+ print("Mode: DRY-RUN (no changes)")
149
+ print()
150
+
151
+
152
+ def print_summary() -> None:
153
+ print()
154
+ print("Done.")
155
+ print()
156
+ print("Next steps:")
157
+ print(" 1. Edit ~/.claude/CLAUDE.md -- add your global rules above the toolkit sections")
158
+ print(" 2. Per project: ai-toolkit install --local (or: ai-toolkit update --local)")
159
+ print(" 3. To update: npm install -g @softspark/ai-toolkit@latest && ai-toolkit update")
160
+ print(" 4. To register rules from other tools: ai-toolkit add-rule <rule.md>")
161
+
162
+
163
+ # ---------------------------------------------------------------------------
164
+ # Install Claude Code (orchestrator)
165
+ # ---------------------------------------------------------------------------
166
+
167
+ def install_claude_code(target_dir: Path, hooks_scripts_dir: Path,
168
+ rules_dir: Path, only: str, skip: str,
169
+ dry_run: bool) -> None:
170
+ print("## Claude Code (~/.claude/)")
171
+ print()
172
+
173
+ claude_dir = target_dir / ".claude"
174
+ claude_dir.mkdir(parents=True, exist_ok=True)
175
+
176
+ install_agents(claude_dir, only, skip, dry_run)
177
+ install_skills(claude_dir, only, skip, dry_run)
178
+ clean_legacy_commands(claude_dir, dry_run)
179
+ install_hooks(claude_dir, hooks_scripts_dir, only, skip, dry_run)
180
+ install_marker_files(claude_dir, only, skip, dry_run)
181
+
182
+ if not dry_run:
183
+ print(" Note: settings.local.json is project-specific -- use 'ai-toolkit install --local' per project")
184
+
185
+ print()
186
+ print(f" Available: {count_agents()} agents, {count_skills()} skills")
187
+
188
+ inject_rules(claude_dir, target_dir, rules_dir, only, skip, dry_run)
189
+
190
+
191
+ def install_strict_git_hooks(profile: str, local: bool, dry_run: bool) -> None:
192
+ if profile == "strict" and not local and not dry_run:
193
+ cwd = Path.cwd()
194
+ if (cwd / ".git").is_dir():
195
+ run_script("install-git-hooks.sh", str(cwd))
196
+ print(f" Strict profile: git hooks installed in {cwd}")
197
+
198
+
199
+ # ---------------------------------------------------------------------------
200
+ # Main
201
+ # ---------------------------------------------------------------------------
202
+
203
+ def main() -> None:
204
+ cfg = parse_args(sys.argv[1:])
205
+
206
+ target_dir: Path = cfg["target_dir"]
207
+ only: str = cfg["only"]
208
+ skip: str = cfg["skip"]
209
+ dry_run: bool = cfg["dry_run"]
210
+ local: bool = cfg["local"]
211
+ reset: bool = cfg["reset"]
212
+ profile: str = cfg["profile"]
213
+
214
+ rules_dir = Path.home() / ".ai-toolkit" / "rules"
215
+ hooks_scripts_dir = Path.home() / ".ai-toolkit" / "hooks"
216
+
217
+ only = resolve_profile(profile, only)
218
+ check_dependencies()
219
+ print_banner(target_dir, rules_dir, profile, only, skip, dry_run)
220
+
221
+ if not dry_run:
222
+ rules_dir.mkdir(parents=True, exist_ok=True)
223
+ hooks_scripts_dir.mkdir(parents=True, exist_ok=True)
224
+
225
+ install_claude_code(target_dir, hooks_scripts_dir, rules_dir, only, skip, dry_run)
226
+ install_ai_tools(target_dir, rules_dir, only, skip, dry_run)
227
+
228
+ if local:
229
+ install_local_project(rules_dir, dry_run, reset)
230
+
231
+ install_strict_git_hooks(profile, local, dry_run)
232
+ print_summary()
233
+
234
+
235
+ if __name__ == "__main__":
236
+ main()
@@ -0,0 +1,71 @@
1
+ #!/usr/bin/env python3
2
+ """Install a fallback pre-commit hook for the ai-toolkit.
3
+
4
+ Used for non-Claude editors to enforce the constitution and quality gates.
5
+
6
+ Usage: python3 install_git_hooks.py [target-dir]
7
+ """
8
+ from __future__ import annotations
9
+
10
+ import os
11
+ import sys
12
+ from pathlib import Path
13
+
14
+ PRE_COMMIT_CONTENT = """\
15
+ #!/usr/bin/env bash
16
+ # ai-toolkit fallback pre-commit hook
17
+ # Auto-generated by ai-toolkit.
18
+
19
+ # This hook acts as a safety fallback to ensure AI-generated commits
20
+ # aren't bypassing standard quality checks (like unresolved conflicts or missing tests)
21
+ # when using editors without native bash process hooks (like Cursor or Windsurf).
22
+
23
+ echo "[ai-toolkit] Running pre-commit quality gate..."
24
+
25
+ # 1. Check for unresolved merge conflicts
26
+ if git diff --cached -S'<<<<<<<' --name-only | grep -q '.*'; then
27
+ echo "ERROR: Unresolved merge conflicts found in staged files."
28
+ echo " Please resolve conflicts and remove '<<<<<<<' markers before committing."
29
+ exit 1
30
+ fi
31
+
32
+ # 2. Call the global quality-check if it exists
33
+ QUALITY_CHECK="$HOME/.ai-toolkit/hooks/quality-check.sh"
34
+ if [ -x "$QUALITY_CHECK" ]; then
35
+ if ! "$QUALITY_CHECK"; then
36
+ echo "ERROR: Linter or type checks failed."
37
+ echo " Use 'git commit --no-verify' if you absolutely must bypass this."
38
+ exit 1
39
+ fi
40
+ fi
41
+
42
+ echo "[ai-toolkit] Pre-commit checks passed."
43
+ exit 0
44
+ """
45
+
46
+
47
+ def main() -> None:
48
+ target_dir = Path(sys.argv[1]) if len(sys.argv) > 1 else Path.cwd()
49
+ git_hooks_dir = target_dir / ".git" / "hooks"
50
+
51
+ if not git_hooks_dir.is_dir():
52
+ print(" Skipped: git hooks (not a git repository)")
53
+ return
54
+
55
+ pre_commit = git_hooks_dir / "pre-commit"
56
+
57
+ # Back up existing pre-commit hook if not ours
58
+ if pre_commit.is_file():
59
+ content = pre_commit.read_text(encoding="utf-8", errors="replace")
60
+ if "ai-toolkit fallback pre-commit hook" not in content:
61
+ backup = git_hooks_dir / "pre-commit.backup"
62
+ pre_commit.rename(backup)
63
+ print(" Backed up existing pre-commit hook to pre-commit.backup")
64
+
65
+ pre_commit.write_text(PRE_COMMIT_CONTENT, encoding="utf-8")
66
+ pre_commit.chmod(pre_commit.stat().st_mode | 0o111)
67
+ print(" Installed: .git/hooks/pre-commit (ai-toolkit safety fallback)")
68
+
69
+
70
+ if __name__ == "__main__":
71
+ main()
@@ -0,0 +1,5 @@
1
+ """Install step modules.
2
+
3
+ Each module handles one installation concern.
4
+ Imported by install.py as the orchestrator.
5
+ """