@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,149 @@
1
+ #!/usr/bin/env python3
2
+ """Gather rollback context: git state, migrations, docker services.
3
+
4
+ Detects the current git state, identifies the migration tool in use,
5
+ and lists running Docker Compose services. Returns a JSON object with
6
+ ``git``, ``migrations``, and ``docker`` sections.
7
+
8
+ Usage::
9
+
10
+ python3 rollback_info.py
11
+ """
12
+ from __future__ import annotations
13
+
14
+ import json
15
+ import shutil
16
+ import subprocess
17
+ import sys
18
+ from datetime import datetime, timezone
19
+ from pathlib import Path
20
+ from typing import Any
21
+
22
+
23
+ def _run(cmd: list[str]) -> subprocess.CompletedProcess[str]:
24
+ """Run a command, capturing output as text. Never raises."""
25
+ return subprocess.run(
26
+ cmd,
27
+ capture_output=True,
28
+ text=True,
29
+ )
30
+
31
+
32
+ def _git(args: list[str], default: str = "unknown") -> str:
33
+ """Run a git sub-command and return stripped stdout, or *default*."""
34
+ result = _run(["git"] + args)
35
+ return result.stdout.strip() if result.returncode == 0 and result.stdout.strip() else default
36
+
37
+
38
+ def _git_info() -> dict[str, Any]:
39
+ """Collect git state information."""
40
+ current_commit = _git(["rev-parse", "HEAD"])
41
+ previous_commit = _git(["rev-parse", "HEAD~1"])
42
+ last_msg = _git(["log", "-1", "--pretty=%s"])
43
+ branch = _git(["branch", "--show-current"])
44
+
45
+ porcelain = _run(["git", "status", "--porcelain"])
46
+ uncommitted = bool(porcelain.stdout.strip()) if porcelain.returncode == 0 else False
47
+
48
+ # Determine main branch name
49
+ main_branch = "main"
50
+ verify = _run(["git", "rev-parse", "--verify", "origin/main"])
51
+ if verify.returncode != 0:
52
+ main_branch = "master"
53
+
54
+ ahead_result = _run(["git", "rev-list", "--count", f"origin/{main_branch}..HEAD"])
55
+ try:
56
+ ahead = int(ahead_result.stdout.strip())
57
+ except (ValueError, TypeError):
58
+ ahead = 0
59
+
60
+ return {
61
+ "branch": branch,
62
+ "current_commit": current_commit,
63
+ "previous_commit": previous_commit,
64
+ "last_commit_message": last_msg,
65
+ f"commits_ahead_of_{main_branch}": ahead,
66
+ "uncommitted_changes": uncommitted,
67
+ }
68
+
69
+
70
+ def _migration_info() -> dict[str, Any]:
71
+ """Detect migration tool and build rollback command."""
72
+ tool = "none"
73
+ pending: str = "unknown"
74
+ rollback_cmd: str | None = None
75
+
76
+ if Path("alembic.ini").exists():
77
+ tool = "alembic"
78
+ rollback_cmd = "alembic downgrade -1"
79
+ if shutil.which("alembic") is not None:
80
+ result = _run(["alembic", "heads"])
81
+ if result.returncode == 0:
82
+ head_count = len(
83
+ [line for line in result.stdout.strip().splitlines() if line.strip()]
84
+ )
85
+ pending = f"{head_count} pending head(s)"
86
+ elif Path("prisma/schema.prisma").exists():
87
+ tool = "prisma"
88
+ rollback_cmd = "npx prisma migrate resolve --rolled-back <name>"
89
+ elif Path("database/migrations").is_dir() and Path("artisan").exists():
90
+ tool = "laravel"
91
+ rollback_cmd = "php artisan migrate:rollback --step=1"
92
+ elif Path("manage.py").exists():
93
+ tool = "django"
94
+ rollback_cmd = "python manage.py migrate <app> <previous_migration>"
95
+ elif Path("drizzle.config.ts").exists() or Path("drizzle.config.js").exists():
96
+ tool = "drizzle"
97
+ rollback_cmd = "manual rollback required"
98
+
99
+ return {
100
+ "tool": tool,
101
+ "pending": pending,
102
+ "rollback_command": rollback_cmd,
103
+ }
104
+
105
+
106
+ def _docker_info() -> dict[str, Any]:
107
+ """Detect Docker Compose services."""
108
+ services: list[dict[str, str]] = []
109
+ running = False
110
+
111
+ has_compose = (
112
+ Path("docker-compose.yml").exists()
113
+ or Path("compose.yml").exists()
114
+ )
115
+ if not (shutil.which("docker") and has_compose):
116
+ return {"running": running, "services": services}
117
+
118
+ result = _run([
119
+ "docker", "compose", "ps",
120
+ "--format", "{{.Service}}:{{.Image}}:{{.Status}}",
121
+ ])
122
+ if result.returncode == 0 and result.stdout.strip():
123
+ running = True
124
+ for line in result.stdout.strip().splitlines():
125
+ parts = line.split(":", 2)
126
+ if len(parts) >= 2:
127
+ services.append({
128
+ "service": parts[0],
129
+ "image": parts[1],
130
+ })
131
+
132
+ return {"running": running, "services": services}
133
+
134
+
135
+ def main() -> None:
136
+ """Entry point: gather rollback context and print JSON to stdout."""
137
+ now = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
138
+
139
+ output: dict[str, Any] = {
140
+ "timestamp": now,
141
+ "git": _git_info(),
142
+ "migrations": _migration_info(),
143
+ "docker": _docker_info(),
144
+ }
145
+ print(json.dumps(output, indent=2))
146
+
147
+
148
+ if __name__ == "__main__":
149
+ main()
@@ -0,0 +1,454 @@
1
+ ---
2
+ name: ruby-patterns
3
+ description: "Loaded when user asks about Ruby development patterns"
4
+ effort: medium
5
+ user-invocable: false
6
+ ---
7
+
8
+ # Ruby Patterns
9
+
10
+ ## Project Structure
11
+
12
+ ### Gem Layout
13
+ ```
14
+ my_gem/
15
+ ├── lib/
16
+ │ ├── my_gem.rb # Entry point, require sub-files
17
+ │ └── my_gem/
18
+ │ ├── version.rb
19
+ │ ├── configuration.rb
20
+ │ ├── client.rb
21
+ │ └── errors.rb
22
+ ├── spec/
23
+ │ ├── spec_helper.rb
24
+ │ ├── my_gem/
25
+ │ │ └── client_spec.rb
26
+ │ └── fixtures/
27
+ ├── bin/
28
+ │ └── console # IRB with gem loaded
29
+ ├── sig/ # RBS type signatures
30
+ ├── Gemfile
31
+ ├── Rakefile
32
+ ├── my_gem.gemspec
33
+ ├── .rubocop.yml
34
+ └── .ruby-version
35
+ ```
36
+
37
+ ### Rails Standard Structure
38
+ ```
39
+ app/
40
+ ├── controllers/
41
+ │ ├── application_controller.rb
42
+ │ └── api/v1/
43
+ │ └── users_controller.rb
44
+ ├── models/
45
+ │ ├── application_record.rb
46
+ │ ├── user.rb
47
+ │ └── concerns/
48
+ │ └── searchable.rb
49
+ ├── services/
50
+ │ └── users/
51
+ │ ├── create_service.rb
52
+ │ └── import_service.rb
53
+ ├── jobs/
54
+ │ └── user_sync_job.rb
55
+ ├── mailers/
56
+ ├── serializers/
57
+ │ └── user_serializer.rb
58
+ └── views/
59
+ config/
60
+ ├── routes.rb
61
+ ├── database.yml
62
+ ├── initializers/
63
+ │ ├── sidekiq.rb
64
+ │ └── cors.rb
65
+ └── environments/
66
+ db/
67
+ ├── migrate/
68
+ ├── schema.rb
69
+ └── seeds.rb
70
+ spec/
71
+ ├── rails_helper.rb
72
+ ├── spec_helper.rb
73
+ ├── models/
74
+ ├── requests/
75
+ ├── services/
76
+ ├── factories/
77
+ │ └── users.rb
78
+ └── support/
79
+ └── shared_examples/
80
+ ```
81
+
82
+ ### Gemfile Best Practices
83
+ ```ruby
84
+ source "https://rubygems.org"
85
+
86
+ ruby "~> 3.3"
87
+
88
+ gem "rails", "~> 7.2"
89
+ gem "pg"
90
+ gem "puma", ">= 6.0"
91
+ gem "sidekiq", "~> 7.0"
92
+ gem "redis", ">= 5.0"
93
+
94
+ group :development, :test do
95
+ gem "rspec-rails"
96
+ gem "factory_bot_rails"
97
+ gem "faker"
98
+ gem "debug"
99
+ gem "rubocop-rails", require: false
100
+ gem "rubocop-rspec", require: false
101
+ end
102
+
103
+ group :test do
104
+ gem "shoulda-matchers"
105
+ gem "webmock"
106
+ gem "vcr"
107
+ gem "simplecov", require: false
108
+ end
109
+ ```
110
+
111
+ ---
112
+
113
+ ## Idioms / Code Style
114
+
115
+ ### Blocks, Procs, and Lambdas
116
+ ```ruby
117
+ # Block -- yielded to, not stored
118
+ def with_retry(attempts: 3)
119
+ attempts.times do |i|
120
+ return yield
121
+ rescue StandardError => e
122
+ raise if i == attempts - 1
123
+ sleep(2**i)
124
+ end
125
+ end
126
+
127
+ with_retry { http_client.get("/data") }
128
+
129
+ # Proc -- flexible arity, returns from enclosing method
130
+ validator = Proc.new { |val| val.to_s.strip.length > 0 }
131
+
132
+ # Lambda -- strict arity, returns from itself
133
+ transform = ->(x) { x.to_s.downcase.strip }
134
+ words = ["Hello ", " WORLD"].map(&transform)
135
+
136
+ # Method reference
137
+ names = users.map(&:name)
138
+ valid = values.select(&method(:valid?))
139
+ ```
140
+
141
+ ### Modules and Mixins
142
+ ```ruby
143
+ # Concern pattern (Rails)
144
+ module Searchable
145
+ extend ActiveSupport::Concern
146
+
147
+ included do
148
+ scope :search, ->(query) {
149
+ where("name ILIKE ?", "%#{sanitize_sql_like(query)}%")
150
+ }
151
+ end
152
+
153
+ class_methods do
154
+ def searchable_columns
155
+ %i[name email]
156
+ end
157
+ end
158
+ end
159
+
160
+ # Pure Ruby mixin
161
+ module Loggable
162
+ def logger
163
+ @logger ||= Logger.new($stdout, progname: self.class.name)
164
+ end
165
+
166
+ def log_info(msg) = logger.info(msg)
167
+ def log_error(msg) = logger.error(msg)
168
+ end
169
+ ```
170
+
171
+ ### method_missing with respond_to_missing?
172
+ ```ruby
173
+ class Config
174
+ def initialize(data = {})
175
+ @data = data
176
+ end
177
+
178
+ def method_missing(name, *args)
179
+ key = name.to_s.chomp("=").to_sym
180
+ if name.to_s.end_with?("=")
181
+ @data[key] = args.first
182
+ elsif @data.key?(key)
183
+ @data[key]
184
+ else
185
+ super
186
+ end
187
+ end
188
+
189
+ def respond_to_missing?(name, include_private = false)
190
+ @data.key?(name.to_s.chomp("=").to_sym) || super
191
+ end
192
+ end
193
+ ```
194
+
195
+ ### Frozen String Literals
196
+ ```ruby
197
+ # frozen_string_literal: true
198
+
199
+ # Add to every file. Prevents accidental mutation, improves memory.
200
+ # Enforce via RuboCop: Style/FrozenStringLiteralComment
201
+ ```
202
+
203
+ ### Pattern Matching (Ruby 3+)
204
+ ```ruby
205
+ case response
206
+ in { status: 200, body: { data: Array => items } }
207
+ process_items(items)
208
+ in { status: 200, body: { data: Hash => item } }
209
+ process_item(item)
210
+ in { status: 404 }
211
+ raise NotFoundError
212
+ in { status: (500..) }
213
+ raise ServerError, response[:body]
214
+ end
215
+
216
+ # Find pattern
217
+ case users
218
+ in [*, { role: "admin", name: String => admin_name }, *]
219
+ puts "Found admin: #{admin_name}"
220
+ end
221
+
222
+ # Pin operator
223
+ expected_status = 200
224
+ case response
225
+ in { status: ^expected_status }
226
+ handle_success(response)
227
+ end
228
+ ```
229
+
230
+ ### Enumerable Idioms
231
+ ```ruby
232
+ # Chaining
233
+ active_emails = users
234
+ .select(&:active?)
235
+ .reject { |u| u.email.nil? }
236
+ .map(&:email)
237
+ .uniq
238
+ .sort
239
+
240
+ # Grouping and tallying
241
+ users.group_by(&:role) # => { "admin" => [...], "user" => [...] }
242
+ users.tally_by(&:role) # => { "admin" => 3, "user" => 15 }
243
+ orders.sum(&:total)
244
+ scores.filter_map { |s| s.value if s.valid? }
245
+
246
+ # each_with_object over inject for building hashes
247
+ users.each_with_object({}) do |user, memo|
248
+ memo[user.id] = user.name
249
+ end
250
+ ```
251
+
252
+ ---
253
+
254
+ ## Error Handling
255
+
256
+ ### begin/rescue/ensure
257
+ ```ruby
258
+ def fetch_user(id)
259
+ user = api_client.get("/users/#{id}")
260
+ User.new(user)
261
+ rescue Faraday::TimeoutError => e
262
+ logger.warn("Timeout fetching user #{id}: #{e.message}")
263
+ nil
264
+ rescue Faraday::ClientError => e
265
+ raise if e.response_status != 404
266
+ nil
267
+ rescue StandardError => e
268
+ logger.error("Unexpected error: #{e.class} - #{e.message}")
269
+ raise
270
+ ensure
271
+ api_client.close if api_client
272
+ end
273
+ ```
274
+
275
+ ### Custom Exceptions
276
+ ```ruby
277
+ module MyApp
278
+ class Error < StandardError; end
279
+
280
+ class NotFoundError < Error
281
+ attr_reader :resource, :id
282
+
283
+ def initialize(resource:, id:)
284
+ @resource = resource
285
+ @id = id
286
+ super("#{resource} not found: #{id}")
287
+ end
288
+ end
289
+
290
+ class ValidationError < Error
291
+ attr_reader :errors
292
+
293
+ def initialize(errors)
294
+ @errors = errors
295
+ super(errors.join(", "))
296
+ end
297
+ end
298
+
299
+ class RateLimitError < Error
300
+ attr_reader :retry_after
301
+
302
+ def initialize(retry_after:)
303
+ @retry_after = retry_after
304
+ super("Rate limited. Retry after #{retry_after}s")
305
+ end
306
+ end
307
+ end
308
+ ```
309
+
310
+ ### Retry with Backoff
311
+ ```ruby
312
+ def with_retries(max: 3, base_delay: 0.5, errors: [StandardError])
313
+ attempts = 0
314
+ begin
315
+ attempts += 1
316
+ yield
317
+ rescue *errors => e
318
+ raise if attempts >= max
319
+ delay = base_delay * (2**(attempts - 1)) + rand(0.0..0.5)
320
+ sleep(delay)
321
+ retry
322
+ end
323
+ end
324
+
325
+ with_retries(max: 5, errors: [Net::OpenTimeout, Faraday::TimeoutError]) do
326
+ api_client.post("/webhook", payload)
327
+ end
328
+ ```
329
+
330
+ ### Dry::Monads (Railway-oriented)
331
+ ```ruby
332
+ require "dry/monads"
333
+
334
+ class CreateUser
335
+ include Dry::Monads[:result, :do]
336
+
337
+ def call(params)
338
+ values = yield validate(params)
339
+ user = yield persist(values)
340
+ yield send_welcome_email(user)
341
+ Success(user)
342
+ end
343
+
344
+ private
345
+
346
+ def validate(params)
347
+ result = UserContract.new.call(params)
348
+ result.success? ? Success(result.to_h) : Failure(result.errors.to_h)
349
+ end
350
+
351
+ def persist(values)
352
+ user = User.create(values)
353
+ user.persisted? ? Success(user) : Failure(user.errors.full_messages)
354
+ end
355
+
356
+ def send_welcome_email(user)
357
+ UserMailer.welcome(user).deliver_later
358
+ Success(user)
359
+ rescue StandardError => e
360
+ # Non-critical -- log and continue
361
+ Rails.logger.error("Welcome email failed: #{e.message}")
362
+ Success(user)
363
+ end
364
+ end
365
+ ```
366
+
367
+ ---
368
+
369
+ ## Testing Patterns
370
+
371
+ ### RSpec Structure
372
+ ```ruby
373
+ RSpec.describe UserService, "#create" do
374
+ subject(:result) { described_class.new(repo: repo).create(params) }
375
+
376
+ let(:repo) { instance_double(UserRepository) }
377
+ let(:params) { { name: "Ada", email: "ada@example.com" } }
378
+
379
+ context "when params are valid" do
380
+ before do
381
+ allow(repo).to receive(:save).and_return(build(:user, **params))
382
+ end
383
+
384
+ it "returns the created user" do
385
+ expect(result).to be_a(User)
386
+ expect(result.name).to eq("Ada")
387
+ end
388
+
389
+ it "persists via repository" do
390
+ result
391
+ expect(repo).to have_received(:save).with(hash_including(name: "Ada"))
392
+ end
393
+ end
394
+
395
+ context "when email is taken" do
396
+ before do
397
+ allow(repo).to receive(:save).and_raise(ActiveRecord::RecordNotUnique)
398
+ end
399
+
400
+ it "raises a duplicate error" do
401
+ expect { result }.to raise_error(UserService::DuplicateEmail)
402
+ end
403
+ end
404
+ end
405
+ ```
406
+
407
+ ### FactoryBot
408
+ ```ruby
409
+ FactoryBot.define do
410
+ factory :user do
411
+ name { Faker::Name.name }
412
+ email { Faker::Internet.unique.email }
413
+ role { :user }
414
+
415
+ trait :admin do
416
+ role { :admin }
417
+ end
418
+
419
+ trait :with_orders do
420
+ transient do
421
+ order_count { 3 }
422
+ end
423
+
424
+ after(:create) do |user, ctx|
425
+ create_list(:order, ctx.order_count, user: user)
426
+ end
427
+ end
428
+ end
429
+ end
430
+
431
+ # Usage
432
+ create(:user, :admin)
433
+ create(:user, :with_orders, order_count: 5)
434
+ build_stubbed(:user) # No DB hit
435
+ ```
436
+
437
+ ### VCR / WebMock
438
+ ```ruby
439
+ # spec/support/vcr.rb
440
+ VCR.configure do |c|
441
+ c.cassette_library_dir = "spec/fixtures/cassettes"
442
+ c.hook_into :webmock
443
+ c.filter_sensitive_data("<API_KEY>") { ENV.fetch("API_KEY") }
444
+ c.default_cassette_options = { record: :once, decode_compressed_response: true }
445
+ end
446
+
447
+ RSpec.describe GitHubClient do
448
+ it "fetches repositories", vcr: { cassette_name: "github/repos" } do
449
+ repos = described_class.new.repos("rails")
450
+ expect(repos).not_to be_empty
451
+ expect(repos.first).to respond_to(:name)
452
+ end
453
+ end
454
+ ```