opencode-skills-collection 4.0.0 → 4.0.1

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 (241) hide show
  1. package/bundled-skills/.antigravity-install-manifest.json +24 -1
  2. package/bundled-skills/007/SKILL.md +2 -2
  3. package/bundled-skills/10-andruia-skill-smith/SKILL.md +1 -1
  4. package/bundled-skills/agent-squad/SKILL.md +5 -0
  5. package/bundled-skills/agent-squad/alex/SKILL.md +3 -0
  6. package/bundled-skills/agent-squad/aria/SKILL.md +3 -0
  7. package/bundled-skills/agent-squad/dep/SKILL.md +3 -0
  8. package/bundled-skills/agent-squad/luna/SKILL.md +4 -1
  9. package/bundled-skills/agent-squad/mason/SKILL.md +3 -0
  10. package/bundled-skills/agent-squad/max/SKILL.md +3 -0
  11. package/bundled-skills/agent-squad/quinn/SKILL.md +3 -0
  12. package/bundled-skills/agent-squad/rex/SKILL.md +3 -0
  13. package/bundled-skills/angular/metadata.json +1 -1
  14. package/bundled-skills/angular-best-practices/metadata.json +1 -1
  15. package/bundled-skills/angular-state-management/metadata.json +1 -1
  16. package/bundled-skills/angular-ui-patterns/metadata.json +1 -1
  17. package/bundled-skills/antigravity-skill-orchestrator/SKILL.md +2 -2
  18. package/bundled-skills/apify-actor-development/SKILL.md +2 -2
  19. package/bundled-skills/apple-container/SKILL.md +165 -0
  20. package/bundled-skills/apple-container/references/commands.md +1593 -0
  21. package/bundled-skills/apple-container/references/concepts.md +294 -0
  22. package/bundled-skills/apple-container/references/configuration.md +499 -0
  23. package/bundled-skills/apple-container/references/workflows.md +498 -0
  24. package/bundled-skills/ask-copilot/SKILL.md +112 -0
  25. package/bundled-skills/auto-research/SKILL.md +126 -0
  26. package/bundled-skills/brooks-lint/SKILL.md +1 -1
  27. package/bundled-skills/browser-harness/references/install.md +3 -1
  28. package/bundled-skills/chrome-extension-developer/SKILL.md +1 -1
  29. package/bundled-skills/clean-code-guard/SKILL.md +184 -0
  30. package/bundled-skills/clean-code-guard/references/ai-failure-modes.md +345 -0
  31. package/bundled-skills/clean-code-guard/references/comments-and-formatting.md +115 -0
  32. package/bundled-skills/clean-code-guard/references/dry-kiss-yagni.md +133 -0
  33. package/bundled-skills/clean-code-guard/references/naming-and-functions.md +187 -0
  34. package/bundled-skills/clean-code-guard/references/review-checklist.md +150 -0
  35. package/bundled-skills/clean-code-guard/references/solid.md +280 -0
  36. package/bundled-skills/clean-code-guard/references/sources.md +47 -0
  37. package/bundled-skills/cloudflare-security-audit/SKILL.md +137 -0
  38. package/bundled-skills/cloudflare-security-audit/references/AI-AND-LLM.md +67 -0
  39. package/bundled-skills/cloudflare-security-audit/references/ATTACK-CLASSES.md +116 -0
  40. package/bundled-skills/cloudflare-security-audit/references/CLIENT-SIDE.md +67 -0
  41. package/bundled-skills/cloudflare-security-audit/references/HUNTING.md +110 -0
  42. package/bundled-skills/cloudflare-security-audit/references/MEMORY-SAFETY-AND-BINARY.md +58 -0
  43. package/bundled-skills/cloudflare-security-audit/references/RECONNAISSANCE.md +46 -0
  44. package/bundled-skills/cloudflare-security-audit/references/VALIDATION-AND-REPORTING.md +109 -0
  45. package/bundled-skills/cloudflare-security-audit/references/WEB-PROTOCOL-AND-AUTH.md +85 -0
  46. package/bundled-skills/cloudflare-security-audit/resources/report-schema.json +210 -0
  47. package/bundled-skills/comfyui-gateway/references/troubleshooting.md +1 -1
  48. package/bundled-skills/commit/SKILL.md +2 -1
  49. package/bundled-skills/deepapi/SKILL.md +6 -6
  50. package/bundled-skills/docs/README.md +1 -1
  51. package/bundled-skills/docs/contributors/quality-bar.md +1 -1
  52. package/bundled-skills/docs/contributors/security-guardrails.md +1 -1
  53. package/bundled-skills/docs/integrations/jetski-cortex.md +5 -5
  54. package/bundled-skills/docs/integrations/jetski-gemini-loader/README.md +3 -3
  55. package/bundled-skills/docs/maintainers/ci-drift-fix.md +7 -5
  56. package/bundled-skills/docs/maintainers/merge-batch.md +24 -7
  57. package/bundled-skills/docs/maintainers/pr-autonomy.md +89 -0
  58. package/bundled-skills/docs/maintainers/release-notes-7.2.0.md +1 -1
  59. package/bundled-skills/docs/maintainers/release-process.md +9 -9
  60. package/bundled-skills/docs/maintainers/repo-growth-seo.md +5 -5
  61. package/bundled-skills/docs/maintainers/skills-date-tracking.md +1 -1
  62. package/bundled-skills/docs/maintainers/skills-update-guide.md +2 -2
  63. package/bundled-skills/docs/users/{antigravity-awesome-skills-vs-awesome-claude-skills.md → agentic-awesome-skills-vs-awesome-claude-skills.md} +5 -5
  64. package/bundled-skills/docs/users/ai-agent-skills.md +4 -4
  65. package/bundled-skills/docs/users/best-claude-code-skills-github.md +3 -3
  66. package/bundled-skills/docs/users/best-cursor-skills-github.md +3 -3
  67. package/bundled-skills/docs/users/bundles.md +56 -57
  68. package/bundled-skills/docs/users/claude-code-skills.md +6 -6
  69. package/bundled-skills/docs/users/codex-cli-skills.md +5 -5
  70. package/bundled-skills/docs/users/cursor-skills.md +3 -3
  71. package/bundled-skills/docs/users/faq.md +27 -15
  72. package/bundled-skills/docs/users/gemini-cli-skills.md +4 -4
  73. package/bundled-skills/docs/users/getting-started.md +22 -12
  74. package/bundled-skills/docs/users/kiro-integration.md +7 -7
  75. package/bundled-skills/docs/users/plugins.md +8 -9
  76. package/bundled-skills/docs/users/security-skills.md +1 -1
  77. package/bundled-skills/docs/users/skills-vs-mcp-tools.md +1 -1
  78. package/bundled-skills/docs/users/specialized-plugin-roadmap.md +3 -4
  79. package/bundled-skills/docs/users/usage.md +9 -9
  80. package/bundled-skills/docs/users/visual-guide.md +7 -7
  81. package/bundled-skills/docs/users/windows-truncation-recovery.md +1 -1
  82. package/bundled-skills/docs/users/workflows.md +1 -1
  83. package/bundled-skills/docs/vietnamese/BUNDLES.vi.md +1 -1
  84. package/bundled-skills/docs/vietnamese/FAQ.vi.md +15 -8
  85. package/bundled-skills/docs/vietnamese/GETTING_STARTED.vi.md +12 -5
  86. package/bundled-skills/docs/vietnamese/QUALITY_BAR.vi.md +1 -1
  87. package/bundled-skills/docs/vietnamese/README.vi.md +21 -17
  88. package/bundled-skills/docs/vietnamese/SECURITY_GUARDRAILS.vi.md +1 -1
  89. package/bundled-skills/docs/vietnamese/SKILLS_README.vi.md +2 -2
  90. package/bundled-skills/docs/vietnamese/TRANSLATION_PLAN.vi.md +1 -1
  91. package/bundled-skills/docs/vietnamese/VISUAL_GUIDE.vi.md +8 -8
  92. package/bundled-skills/docs-guard/SKILL.md +109 -0
  93. package/bundled-skills/docs-guard/references/code-samples.md +46 -0
  94. package/bundled-skills/docs-guard/references/docstrings.md +60 -0
  95. package/bundled-skills/docs-guard/references/review-checklist.md +45 -0
  96. package/bundled-skills/docs-guard/references/sources.md +22 -0
  97. package/bundled-skills/docs-guard/references/verification.md +51 -0
  98. package/bundled-skills/efficient-web-research/SKILL.md +4 -0
  99. package/bundled-skills/electron-development/SKILL.md +1 -1
  100. package/bundled-skills/event-staffing-compliance/SKILL.md +3 -0
  101. package/bundled-skills/event-staffing-ordering/SKILL.md +3 -0
  102. package/bundled-skills/feature-tracking/SKILL.md +222 -0
  103. package/bundled-skills/flutter-expert/SKILL.md +6 -1
  104. package/bundled-skills/flutter-expert/resources/implementation-playbook.md +214 -0
  105. package/bundled-skills/gemini-deep-research/README.md +246 -0
  106. package/bundled-skills/gemini-deep-research/SKILL.md +136 -0
  107. package/bundled-skills/gemini-deep-research/requirements.txt +2 -0
  108. package/bundled-skills/gemini-deep-research/scripts/research.py +692 -0
  109. package/bundled-skills/git-pushing/SKILL.md +21 -4
  110. package/bundled-skills/git-pushing/scripts/smart_commit.sh +206 -11
  111. package/bundled-skills/go-in-depth/scripts/workflow-script.js +4 -1
  112. package/bundled-skills/grok-build/SKILL.md +167 -0
  113. package/bundled-skills/grok-build/references/cli.md +99 -0
  114. package/bundled-skills/huggingface-tool-builder/references/find_models_by_paper.sh +1 -1
  115. package/bundled-skills/idea-autopsy/SKILL.md +147 -0
  116. package/bundled-skills/it-manager-hospital/README.md +1 -1
  117. package/bundled-skills/it-manager-pro/README.md +1 -1
  118. package/bundled-skills/itil-expert/README.md +1 -1
  119. package/bundled-skills/langgraph/SKILL.md +1 -1
  120. package/bundled-skills/linkedin-content-generator/SKILL.md +1 -1
  121. package/bundled-skills/lint-and-validate/scripts/lint_runner.py +23 -23
  122. package/bundled-skills/lint-and-validate/scripts/type_coverage.py +30 -30
  123. package/bundled-skills/logic-lens/SKILL.md +1 -1
  124. package/bundled-skills/lore/LICENSE +21 -0
  125. package/bundled-skills/lore/README.md +386 -0
  126. package/bundled-skills/lore/README.zh-CN.md +386 -0
  127. package/bundled-skills/lore/SKILL.md +453 -0
  128. package/bundled-skills/lore/WORKFLOWS.md +216 -0
  129. package/bundled-skills/lore/WORKFLOWS.zh-CN.md +216 -0
  130. package/bundled-skills/lore/references/audit-template.md +60 -0
  131. package/bundled-skills/lore/references/compatibility.md +228 -0
  132. package/bundled-skills/lore/references/config.md +125 -0
  133. package/bundled-skills/lore/references/entry-format.md +70 -0
  134. package/bundled-skills/lore/references/history-command.md +106 -0
  135. package/bundled-skills/lore/references/monorepo-detection.md +78 -0
  136. package/bundled-skills/lore/references/platform-mirrors.md +377 -0
  137. package/bundled-skills/lore/references/stale-new-markers.md +62 -0
  138. package/bundled-skills/lore/references/summary-template.md +98 -0
  139. package/bundled-skills/lore/scripts/README.md +54 -0
  140. package/bundled-skills/lore/scripts/README.zh-CN.md +54 -0
  141. package/bundled-skills/lore/scripts/find_duplicates.py +211 -0
  142. package/bundled-skills/lore/scripts/find_stale.py +116 -0
  143. package/bundled-skills/lore/scripts/history.py +527 -0
  144. package/bundled-skills/lore/scripts/id_hash.py +32 -0
  145. package/bundled-skills/lore/scripts/list_entries.py +183 -0
  146. package/bundled-skills/lovable-cleanup/SKILL.md +3 -3
  147. package/bundled-skills/makepad-splash/SKILL.md +1 -1
  148. package/bundled-skills/mmx-cli/SKILL.md +14 -1
  149. package/bundled-skills/monopoly/SKILL.md +5 -0
  150. package/bundled-skills/monopoly/patterns/SKILL.md +4 -0
  151. package/bundled-skills/monopoly/scale-benchmarks/SKILL.md +4 -0
  152. package/bundled-skills/monopoly/security-checklist/SKILL.md +4 -0
  153. package/bundled-skills/monopoly/tech-matrix/SKILL.md +4 -0
  154. package/bundled-skills/mtls-configuration/SKILL.md +1 -1
  155. package/bundled-skills/network-101/SKILL.md +4 -5
  156. package/bundled-skills/nika/SKILL.md +270 -0
  157. package/bundled-skills/not-a-vibe-coder/SKILL.md +4 -0
  158. package/bundled-skills/ontoly-software-graph/SKILL.md +160 -0
  159. package/bundled-skills/openclaw-github-repo-commander/SKILL.md +1 -1
  160. package/bundled-skills/pilot-protocol/SKILL.md +6 -3
  161. package/bundled-skills/postgres-readonly-queries/README.md +77 -0
  162. package/bundled-skills/postgres-readonly-queries/SKILL.md +152 -0
  163. package/bundled-skills/postgres-readonly-queries/connections.example.json +34 -0
  164. package/bundled-skills/postgres-readonly-queries/requirements.txt +1 -0
  165. package/bundled-skills/postgres-readonly-queries/scripts/query.py +262 -0
  166. package/bundled-skills/pptx-deck-creation/SKILL.md +275 -0
  167. package/bundled-skills/pptx-deck-creation/references/audit-checklist.md +156 -0
  168. package/bundled-skills/pptx-deck-creation/references/design-profiles.md +96 -0
  169. package/bundled-skills/pptx-deck-creation/references/python-snippets.md +150 -0
  170. package/bundled-skills/pptx-deck-creation/references/reference-deck-analysis.md +91 -0
  171. package/bundled-skills/pptx-deck-creation/references/visual-asset-adapters.md +158 -0
  172. package/bundled-skills/privilege-escalation-methods/SKILL.md +1 -1
  173. package/bundled-skills/product-decision-agent/SKILL.md +94 -0
  174. package/bundled-skills/product-decision-agent/agents/openai.yaml +7 -0
  175. package/bundled-skills/product-decision-agent/references/methodology-basis.md +232 -0
  176. package/bundled-skills/product-decision-agent/references/product-playbooks.md +813 -0
  177. package/bundled-skills/product-decision-agent/references/reasoning-engine.md +289 -0
  178. package/bundled-skills/product-decision-agent/references/response-examples.md +279 -0
  179. package/bundled-skills/product-decision-agent/scripts/quality_gate.py +221 -0
  180. package/bundled-skills/product-decision-agent/scripts/test_quality_gate.py +94 -0
  181. package/bundled-skills/pytest-skill/reference/playbook.md +358 -0
  182. package/bundled-skills/quit-sponsor/SKILL.md +96 -0
  183. package/bundled-skills/rclone-cli/references/providers/s3.md +1 -1
  184. package/bundled-skills/security-and-hardening/SKILL.md +1 -1
  185. package/bundled-skills/semgrep-rule-creator/SKILL.md +6 -6
  186. package/bundled-skills/skill-creator/README.md +3 -3
  187. package/bundled-skills/skill-creator/SKILL.md +8 -4
  188. package/bundled-skills/skill-creator/scripts/quick_validate.py +1 -1
  189. package/bundled-skills/sql-injection-testing/SKILL.md +4 -4
  190. package/bundled-skills/subagent-orchestrator/README.md +1 -1
  191. package/bundled-skills/super-code/bash/SKILL.md +3 -0
  192. package/bundled-skills/super-code/c/SKILL.md +3 -0
  193. package/bundled-skills/super-code/cpp/SKILL.md +3 -0
  194. package/bundled-skills/super-code/csharp/SKILL.md +3 -0
  195. package/bundled-skills/super-code/dart/SKILL.md +3 -0
  196. package/bundled-skills/super-code/elixir/SKILL.md +3 -0
  197. package/bundled-skills/super-code/go/SKILL.md +3 -0
  198. package/bundled-skills/super-code/java/SKILL.md +3 -0
  199. package/bundled-skills/super-code/kotlin/SKILL.md +3 -0
  200. package/bundled-skills/super-code/php/SKILL.md +3 -0
  201. package/bundled-skills/super-code/python/SKILL.md +3 -0
  202. package/bundled-skills/super-code/ruby/SKILL.md +3 -0
  203. package/bundled-skills/super-code/rust/SKILL.md +3 -0
  204. package/bundled-skills/super-code/scala/SKILL.md +3 -0
  205. package/bundled-skills/super-code/swift/SKILL.md +3 -0
  206. package/bundled-skills/super-code/typescript/SKILL.md +3 -0
  207. package/bundled-skills/telegram-bot-messaging/README.md +185 -0
  208. package/bundled-skills/telegram-bot-messaging/SKILL.md +101 -0
  209. package/bundled-skills/telegram-bot-messaging/scripts/telegram.sh +420 -0
  210. package/bundled-skills/test-guard/SKILL.md +132 -0
  211. package/bundled-skills/test-guard/references/jest.md +50 -0
  212. package/bundled-skills/test-guard/references/llm-app-testing.md +40 -0
  213. package/bundled-skills/test-guard/references/phpunit.md +56 -0
  214. package/bundled-skills/test-guard/references/pytest.md +65 -0
  215. package/bundled-skills/trading-ledger/SKILL.md +11 -3
  216. package/bundled-skills/uizze-ui-research/SKILL.md +93 -0
  217. package/bundled-skills/vibe-code-auditor/SKILL.md +1 -1
  218. package/bundled-skills/vibecode-production-qa-validator/SKILL.md +1 -1
  219. package/bundled-skills/videodb/scripts/ws_listener.py +29 -15
  220. package/bundled-skills/voice-ai-engine-development/README.md +1 -1
  221. package/bundled-skills/vulnerability-scanner/SKILL.md +1 -1
  222. package/bundled-skills/warehouse/SKILL.md +154 -0
  223. package/bundled-skills/woo-guard/SKILL.md +113 -0
  224. package/bundled-skills/woo-guard/references/checkout-and-money.md +64 -0
  225. package/bundled-skills/woo-guard/references/hpos-and-crud.md +87 -0
  226. package/bundled-skills/woo-guard/references/review-checklist.md +44 -0
  227. package/bundled-skills/woo-guard/references/sources.md +17 -0
  228. package/bundled-skills/wp-guard/SKILL.md +119 -0
  229. package/bundled-skills/wp-guard/references/i18n.md +88 -0
  230. package/bundled-skills/wp-guard/references/performance.md +70 -0
  231. package/bundled-skills/wp-guard/references/review-checklist.md +57 -0
  232. package/bundled-skills/wp-guard/references/security.md +100 -0
  233. package/bundled-skills/wp-guard/references/sources.md +27 -0
  234. package/bundled-skills/writing-skills/gotchas.md +1 -1
  235. package/bundled-skills/writing-skills/references/standards/metadata-standard.md +1 -1
  236. package/bundled-skills/x-twitter-scraper/SKILL.md +14 -32
  237. package/bundled-skills/xss-html-injection/SKILL.md +3 -3
  238. package/bundled-skills/youtube-seo-optimizer/SKILL.md +1 -1
  239. package/bundled-skills/youtube-summarizer/CHANGELOG.md +2 -2
  240. package/package.json +1 -1
  241. package/skills_index.json +273 -0
@@ -0,0 +1,101 @@
1
+ ---
2
+ name: telegram-bot-messaging
3
+ description: "Send Telegram messages, files, and alerts via bot API; ask questions with inline buttons and wait for the answer. Supports multiple bots, named chat targets, and CI/cron/hook notifications."
4
+ category: productivity
5
+ risk: critical
6
+ source: https://github.com/sanjay3290/ai-skills/tree/main/skills/telegram
7
+ source_repo: sanjay3290/ai-skills
8
+ source_type: community
9
+ date_added: "2026-07-09"
10
+ author: sanjay3290
11
+ tags: [telegram, notifications, bots, approvals]
12
+ tools: [claude, cursor, gemini]
13
+ license: "Apache-2.0"
14
+ license_source: "https://github.com/sanjay3290/ai-skills/blob/main/LICENSE"
15
+ ---
16
+
17
+ # Telegram
18
+
19
+ ## When to Use
20
+
21
+ - Use when you need to send a Telegram message, file, or alert from a workflow, hook, cron job, or CI pipeline
22
+ - Use when a long-running task should notify you or ask for approval on your phone (inline-button questions that wait for the answer)
23
+ - Use when wiring "notify me when done" or "ask me before proceeding" behavior into automated sessions
24
+
25
+ Send updates, alerts, and files to Telegram; read replies; run ask-and-wait
26
+ approval flows. Pure bash + curl + jq — no install beyond a bot token.
27
+
28
+ First run: `bash scripts/telegram.sh setup` (guided BotFather walkthrough).
29
+
30
+ ## Safety Gate
31
+
32
+ Before setup, sending a message or file, reading replies, or enabling a hook, obtain the
33
+ user's explicit approval for the target chat, bot account, and exact content or file. Never
34
+ send workspace, customer, credential, or secret data automatically. Treat a token as a secret:
35
+ do not echo it, commit it, or place it in shell history.
36
+
37
+ ## Commands
38
+
39
+ ```bash
40
+ bash scripts/telegram.sh send "Deploy finished ✅" # basic alert
41
+ bash scripts/telegram.sh send "low priority" --silent # no notification sound
42
+ bash scripts/telegram.sh send "*bold* alert" --format md # MarkdownV2 (falls back to plain)
43
+ bash scripts/telegram.sh send "hi" --to alerts --bot work # named target + named bot
44
+ bash scripts/telegram.sh file report.pdf "Q3 report" # document (photos auto-detected)
45
+ bash scripts/telegram.sh read # new incoming messages since last read
46
+ ANSWER=$(bash scripts/telegram.sh ask "Deploy to prod?" --options "Yes,No" --timeout 300)
47
+ # exit 0 = answered (stdout = answer), 2 = timeout
48
+ ```
49
+
50
+ ## Config
51
+
52
+ Env vars win, then `~/.config/telegram/config` (mode 600):
53
+
54
+ ```
55
+ TELEGRAM_BOT_TOKEN=123:ABC... # default bot
56
+ TELEGRAM_CHAT_ID=987654321 # default target
57
+ BOT_ALERTS_TOKEN=456:DEF... # --bot alerts (add via: setup --bot alerts)
58
+ TARGET_FAMILY=-100987... # --to family (any chat/group/channel id)
59
+ TELEGRAM_APPROVER_IDS=123456789 # default group approver user IDs (comma-separated)
60
+ APPROVERS_FAMILY=123456789,987654321 # approvers for --to family (overrides default)
61
+ ```
62
+
63
+ Replies and answers are only accepted from configured chat IDs. Private chats preserve the
64
+ direct-chat behavior (the sender user ID must equal the chat ID). Because a group chat ID is
65
+ shared by every member, `ask` fails closed for groups unless `TELEGRAM_APPROVER_IDS` or the
66
+ target-specific `APPROVERS_<NAME>` explicitly lists the Telegram user IDs allowed to answer.
67
+
68
+ ## Claude Code hooks (settings.json)
69
+
70
+ Ping your phone when Claude needs input, and when it finishes:
71
+
72
+ ```json
73
+ {
74
+ "hooks": {
75
+ "Notification": [{"hooks": [{"type": "command",
76
+ "command": "bash ~/.claude/skills/telegram/scripts/telegram.sh send \"🔔 Claude needs input in $(basename \\\"$PWD\\\")\""}]}],
77
+ "Stop": [{"hooks": [{"type": "command",
78
+ "command": "bash ~/.claude/skills/telegram/scripts/telegram.sh send \"✅ Claude finished in $(basename \\\"$PWD\\\")\" --silent"}]}]
79
+ }
80
+ }
81
+ ```
82
+
83
+ Approval gate in any script/automation:
84
+
85
+ ```bash
86
+ if [ "$(bash scripts/telegram.sh ask 'Deploy to prod?' --options 'Yes,No')" = "Yes" ]; then
87
+ ./deploy.sh
88
+ fi
89
+ ```
90
+
91
+ ## Limitations
92
+
93
+ - Telegram is a third-party service: message and file contents leave the local machine and may
94
+ be retained under Telegram's policies.
95
+ - This skill cannot verify that a chat ID belongs to the intended recipient; confirm the target
96
+ before every new destination or automation.
97
+ - Bot tokens grant control of the bot. Store them only in a protected local secret store or
98
+ mode-600 configuration file. The script supplies token-bearing API URLs to curl through
99
+ stdin rather than process arguments; rotate a token if exposure is suspected.
100
+ - Do not use the examples to create unattended notifications or approval flows without the
101
+ user's explicit, current authorization.
@@ -0,0 +1,420 @@
1
+ #!/usr/bin/env bash
2
+ # telegram.sh — send/receive Telegram messages via the Bot API.
3
+ # Deps: bash, curl, jq. Config: env vars or ~/.config/telegram/config.
4
+ set -euo pipefail
5
+
6
+ API_BASE="https://api.telegram.org"
7
+ CONFIG_DIR="${TELEGRAM_CONFIG_DIR:-$HOME/.config/telegram}"
8
+ CONFIG_FILE="$CONFIG_DIR/config"
9
+
10
+ usage() {
11
+ cat >&2 <<'EOF'
12
+ Usage: telegram.sh <command> [args]
13
+
14
+ Commands:
15
+ setup [--bot NAME]
16
+ Guided bot registration (BotFather walkthrough + chat-ID discovery)
17
+ send MESSAGE [--to TARGET] [--bot NAME] [--silent] [--format md|html]
18
+ Send a text message (auto-splits over 4096 chars)
19
+ file PATH [CAPTION] [--to TARGET] [--bot NAME] [--silent]
20
+ Send a document (photos for png/jpg/jpeg/gif/webp)
21
+ ask QUESTION [--options "Yes,No"] [--timeout SECS] [--to TARGET] [--bot NAME]
22
+ Ask with inline buttons, wait for tap or text reply; prints answer.
23
+ Exit 0 = answered, 2 = timeout
24
+ read [--limit N] [--bot NAME] [--all]
25
+ Print new incoming messages since last read
26
+
27
+ Config: TELEGRAM_BOT_TOKEN / TELEGRAM_CHAT_ID env vars, or ~/.config/telegram/config
28
+ Named bots: BOT_<NAME>_TOKEN Named targets: TARGET_<NAME>=<chat_id>
29
+ Group approvals: TELEGRAM_APPROVER_IDS or APPROVERS_<TARGET>=<user_id,...>
30
+ EOF
31
+ exit 1
32
+ }
33
+
34
+ die() { printf 'telegram.sh: %s\n' "$*" >&2; exit 1; }
35
+
36
+ check_deps() {
37
+ command -v curl >/dev/null 2>&1 || die "curl is required but not found"
38
+ command -v jq >/dev/null 2>&1 || die "jq is required but not found (brew install jq / apt install jq)"
39
+ }
40
+
41
+ # Env vars win; config file fills in whatever the environment didn't set.
42
+ load_config() {
43
+ [ -f "$CONFIG_FILE" ] || return 0
44
+ local key val
45
+ while IFS='=' read -r key val; do
46
+ case "$key" in ''|\#*) continue ;; esac
47
+ [[ "$key" =~ ^[A-Za-z_][A-Za-z0-9_]*$ ]] || continue
48
+ if [ -z "${!key:-}" ]; then export "$key=$val"; fi
49
+ done < "$CONFIG_FILE"
50
+ }
51
+
52
+ upper_key() { printf '%s' "$1" | tr '[:lower:]-' '[:upper:]_'; }
53
+
54
+ resolve_bot() {
55
+ local name="${1:-}"
56
+ if [ -z "$name" ]; then
57
+ BOT_TOKEN="${TELEGRAM_BOT_TOKEN:-}"
58
+ BOT_KEY="default"
59
+ [ -n "$BOT_TOKEN" ] || die "no bot token — run 'telegram.sh setup' or set TELEGRAM_BOT_TOKEN"
60
+ else
61
+ local var
62
+ var="BOT_$(upper_key "$name")_TOKEN"
63
+ BOT_TOKEN="${!var:-}"
64
+ BOT_KEY="$name"
65
+ [ -n "$BOT_TOKEN" ] || die "no token for bot '$name' — run 'telegram.sh setup --bot $name' or set $var"
66
+ fi
67
+ [[ "$BOT_TOKEN" =~ ^[0-9]+:[A-Za-z0-9_-]+$ ]] \
68
+ || die "invalid bot token format"
69
+ }
70
+
71
+ resolve_target() {
72
+ local t="${1:-}"
73
+ APPROVER_IDS_RAW="${TELEGRAM_APPROVER_IDS:-}"
74
+ if [ -z "$t" ]; then
75
+ CHAT_ID="${TELEGRAM_CHAT_ID:-}"
76
+ [ -n "$CHAT_ID" ] || die "no default chat — run 'telegram.sh setup' or set TELEGRAM_CHAT_ID"
77
+ elif [[ "$t" =~ ^-?[0-9]+$ ]]; then
78
+ CHAT_ID="$t"
79
+ else
80
+ local var approver_var
81
+ var="TARGET_$(upper_key "$t")"
82
+ CHAT_ID="${!var:-}"
83
+ [ -n "$CHAT_ID" ] || die "unknown target '$t' — add $var=<chat_id> to $CONFIG_FILE"
84
+ approver_var="APPROVERS_$(upper_key "$t")"
85
+ APPROVER_IDS_RAW="${!approver_var:-${TELEGRAM_APPROVER_IDS:-}}"
86
+ fi
87
+ }
88
+
89
+ # Private chat IDs identify the sender, so existing private-chat approval flows
90
+ # need no extra configuration. Group chat IDs are shared: fail closed unless an
91
+ # explicit sender-user allowlist is configured for that target (or globally).
92
+ resolve_approvers() {
93
+ if [[ "$CHAT_ID" != -* ]]; then
94
+ APPROVER_IDS="$CHAT_ID"
95
+ return 0
96
+ fi
97
+
98
+ [ -n "$APPROVER_IDS_RAW" ] \
99
+ || die "group ask requires TELEGRAM_APPROVER_IDS or APPROVERS_<TARGET>"
100
+
101
+ local raw id normalized=""
102
+ raw="${APPROVER_IDS_RAW//,/ }"
103
+ for id in $raw; do
104
+ [[ "$id" =~ ^[0-9]+$ ]] \
105
+ || die "approver IDs must be comma-separated Telegram user IDs"
106
+ normalized="${normalized}${normalized:+$'\n'}${id}"
107
+ done
108
+ [ -n "$normalized" ] || die "group ask requires at least one approver user ID"
109
+ APPROVER_IDS="$normalized"
110
+ }
111
+
112
+ # Keep the token-bearing URL out of curl's argv. The URL is supplied through a
113
+ # one-shot config on stdin; all other request arguments remain ordinary argv.
114
+ curl_telegram() {
115
+ local method="$1"
116
+ shift
117
+ printf 'url = "%s/bot%s/%s"\n' "$API_BASE" "$BOT_TOKEN" "$method" \
118
+ | curl -sS --max-time "${TELEGRAM_CURL_TIMEOUT:-35}" --config - "$@"
119
+ }
120
+
121
+ # api METHOD [curl args...] — prints the JSON response, dies if .ok != true
122
+ api() {
123
+ local method="$1" resp
124
+ shift
125
+ resp=$(curl_telegram "$method" "$@") || die "network error calling $method"
126
+ [ "$(jq -r '.ok' <<<"$resp")" = "true" ] \
127
+ || die "$method failed: $(jq -r '.description // "unknown error"' <<<"$resp")"
128
+ printf '%s' "$resp"
129
+ }
130
+
131
+ cmd_send() {
132
+ local msg="" to="" bot="" silent="false" format=""
133
+ while [ $# -gt 0 ]; do
134
+ case "$1" in
135
+ --to) to="$2"; shift 2 ;;
136
+ --bot) bot="$2"; shift 2 ;;
137
+ --silent) silent="true"; shift ;;
138
+ --format) format="$2"; shift 2 ;;
139
+ -*) die "unknown flag for send: $1" ;;
140
+ *) if [ -z "$msg" ]; then msg="$1"; else die "unexpected argument: $1"; fi; shift ;;
141
+ esac
142
+ done
143
+ [ -n "$msg" ] || die "send needs a message (telegram.sh help)"
144
+ resolve_bot "$bot"
145
+ resolve_target "$to"
146
+
147
+ local parse_mode=""
148
+ case "$format" in
149
+ '') ;;
150
+ md) parse_mode="MarkdownV2" ;;
151
+ html) parse_mode="HTML" ;;
152
+ *) die "--format must be md or html" ;;
153
+ esac
154
+
155
+ local chunk
156
+ while [ -n "$msg" ]; do
157
+ chunk="${msg:0:4096}"
158
+ msg="${msg:4096}"
159
+ send_chunk "$chunk" "$parse_mode" "$silent"
160
+ done
161
+ }
162
+
163
+ # send_chunk TEXT PARSE_MODE SILENT — one sendMessage; formatted sends fall
164
+ # back to plain text if Telegram rejects the markup, so alerts never get lost.
165
+ send_chunk() {
166
+ local text="$1" parse_mode="$2" silent="$3" resp
167
+ if [ -n "$parse_mode" ]; then
168
+ resp=$(curl_telegram sendMessage \
169
+ -d "chat_id=$CHAT_ID" --data-urlencode "text=$text" \
170
+ -d "disable_notification=$silent" -d "parse_mode=$parse_mode") \
171
+ || die "network error calling sendMessage"
172
+ if [ "$(jq -r '.ok' <<<"$resp")" = "true" ]; then return 0; fi
173
+ fi
174
+ api sendMessage -d "chat_id=$CHAT_ID" --data-urlencode "text=$text" \
175
+ -d "disable_notification=$silent" >/dev/null
176
+ }
177
+
178
+ cmd_file() {
179
+ local path="" caption="" to="" bot="" silent="false"
180
+ while [ $# -gt 0 ]; do
181
+ case "$1" in
182
+ --to) to="$2"; shift 2 ;;
183
+ --bot) bot="$2"; shift 2 ;;
184
+ --silent) silent="true"; shift ;;
185
+ -*) die "unknown flag for file: $1" ;;
186
+ *)
187
+ if [ -z "$path" ]; then path="$1"
188
+ elif [ -z "$caption" ]; then caption="$1"
189
+ else die "unexpected argument: $1"; fi
190
+ shift ;;
191
+ esac
192
+ done
193
+ [ -n "$path" ] || die "file needs a path (telegram.sh help)"
194
+ [ -f "$path" ] || die "file not found: $path"
195
+ resolve_bot "$bot"
196
+ resolve_target "$to"
197
+
198
+ local ext method="sendDocument" field="document"
199
+ ext=$(printf '%s' "${path##*.}" | tr '[:upper:]' '[:lower:]')
200
+ case "$ext" in
201
+ png|jpg|jpeg|gif|webp) method="sendPhoto" field="photo" ;;
202
+ esac
203
+ api "$method" -F "chat_id=$CHAT_ID" -F "$field=@$path" \
204
+ -F "caption=$caption" -F "disable_notification=$silent" >/dev/null
205
+ }
206
+
207
+ offset_file() { printf '%s/offset.%s' "$CONFIG_DIR" "$BOT_KEY"; }
208
+
209
+ get_offset() { cat "$(offset_file)" 2>/dev/null || printf '0'; }
210
+
211
+ save_offset() {
212
+ ( umask 077; mkdir -p "$CONFIG_DIR"; printf '%s' "$1" > "$(offset_file)" )
213
+ }
214
+
215
+ # Chat IDs we accept incoming messages from: the default chat plus every
216
+ # TARGET_* value in the config file and the environment. Strangers who
217
+ # message a public bot never match.
218
+ allowed_chats() {
219
+ {
220
+ printf '%s\n' "${TELEGRAM_CHAT_ID:-}"
221
+ if [ -f "$CONFIG_FILE" ]; then
222
+ grep -E '^TARGET_[A-Za-z0-9_]+=' "$CONFIG_FILE" | cut -d= -f2 || true
223
+ fi
224
+ env | grep -E '^TARGET_[A-Za-z0-9_]+=' | cut -d= -f2 || true
225
+ } | grep -E '^-?[0-9]+$' | sort -u || true
226
+ }
227
+
228
+ cmd_read() {
229
+ local limit=20 bot="" all="false"
230
+ while [ $# -gt 0 ]; do
231
+ case "$1" in
232
+ --limit) limit="$2"; shift 2 ;;
233
+ --bot) bot="$2"; shift 2 ;;
234
+ --all) all="true"; shift ;;
235
+ *) die "unknown flag for read: $1" ;;
236
+ esac
237
+ done
238
+ [[ "$limit" =~ ^[0-9]+$ ]] || die "--limit must be a whole number"
239
+ resolve_bot "$bot"
240
+
241
+ local offset=0 resp
242
+ if [ "$all" != "true" ]; then offset=$(get_offset); fi
243
+ resp=$(api getUpdates -d "offset=$offset" -d "limit=$limit")
244
+
245
+ jq -r --arg allowed "$(allowed_chats)" '
246
+ ($allowed | split("\n")) as $ok
247
+ | .result[]
248
+ | select(.message.text != null)
249
+ | select((.message.chat.id | tostring) as $c | $ok | index($c) != null)
250
+ | "[\(.message.chat.id)] \(.message.from.first_name // "?"): \(.message.text)"
251
+ ' <<<"$resp"
252
+
253
+ local last
254
+ last=$(jq -r '.result | if length > 0 then (.[-1].update_id + 1 | tostring) else "" end' <<<"$resp")
255
+ if [ -n "$last" ] && [ "$all" != "true" ]; then
256
+ save_offset "$last"
257
+ fi
258
+ }
259
+
260
+ cmd_ask() {
261
+ local question="" options="Yes,No" timeout=300 to="" bot=""
262
+ while [ $# -gt 0 ]; do
263
+ case "$1" in
264
+ --options) options="$2"; shift 2 ;;
265
+ --timeout) timeout="$2"; shift 2 ;;
266
+ --to) to="$2"; shift 2 ;;
267
+ --bot) bot="$2"; shift 2 ;;
268
+ -*) die "unknown flag for ask: $1" ;;
269
+ *) if [ -z "$question" ]; then question="$1"; else die "unexpected argument: $1"; fi; shift ;;
270
+ esac
271
+ done
272
+ [ -n "$question" ] || die "ask needs a question (telegram.sh help)"
273
+ [[ "$timeout" =~ ^[0-9]+$ ]] || die "--timeout must be a whole number of seconds"
274
+ resolve_bot "$bot"
275
+ resolve_target "$to"
276
+ resolve_approvers
277
+
278
+ # Flush pending updates so stale messages can't answer the question.
279
+ local offset resp last
280
+ offset=$(get_offset)
281
+ resp=$(api getUpdates -d "offset=$offset" -d "timeout=0" -d "limit=100")
282
+ last=$(jq -r '.result | if length > 0 then (.[-1].update_id + 1 | tostring) else "" end' <<<"$resp")
283
+ if [ -n "$last" ]; then offset="$last"; save_offset "$offset"; fi
284
+
285
+ local keyboard msg_id
286
+ keyboard=$(jq -cn --arg opts "$options" \
287
+ '{inline_keyboard: [[$opts | split(",")[] | {text: ., callback_data: .}]]}')
288
+ resp=$(api sendMessage -d "chat_id=$CHAT_ID" \
289
+ --data-urlencode "text=$question" --data-urlencode "reply_markup=$keyboard")
290
+ msg_id=$(jq -r '.result.message_id' <<<"$resp")
291
+
292
+ local deadline now remain answer cb
293
+ deadline=$(( $(date +%s) + timeout ))
294
+ while :; do
295
+ now=$(date +%s)
296
+ remain=$(( deadline - now ))
297
+ if [ "$remain" -le 0 ]; then break; fi
298
+ if [ "$remain" -gt 25 ]; then remain=25; fi
299
+
300
+ resp=$(api getUpdates -d "offset=$offset" -d "timeout=$remain")
301
+ last=$(jq -r '.result | if length > 0 then (.[-1].update_id + 1 | tostring) else "" end' <<<"$resp")
302
+ if [ -n "$last" ]; then offset="$last"; save_offset "$offset"; fi
303
+
304
+ # Button tap on our question message?
305
+ cb=$(jq -r --argjson mid "$msg_id" --argjson chat "$CHAT_ID" --arg allowed "$APPROVER_IDS" '
306
+ ($allowed | split("\n")) as $ok
307
+ | [.result[]
308
+ | select(.callback_query.message.message_id == $mid)
309
+ | select(.callback_query.message.chat.id == $chat)
310
+ | select((.callback_query.from.id | tostring) as $u | $ok | index($u) != null)
311
+ | .callback_query]
312
+ | if length > 0 then "\(.[0].data)\t\(.[0].id)" else "" end' <<<"$resp")
313
+ if [ -n "$cb" ]; then
314
+ answer="${cb%%$'\t'*}"
315
+ api answerCallbackQuery -d "callback_query_id=${cb##*$'\t'}" >/dev/null
316
+ api editMessageText -d "chat_id=$CHAT_ID" -d "message_id=$msg_id" \
317
+ --data-urlencode "text=$question
318
+ ✅ $answer" >/dev/null
319
+ printf '%s\n' "$answer"
320
+ return 0
321
+ fi
322
+
323
+ # Free-text reply from the asked chat?
324
+ answer=$(jq -r --argjson chat "$CHAT_ID" --arg allowed "$APPROVER_IDS" '
325
+ ($allowed | split("\n")) as $ok
326
+ | [.result[]
327
+ | select(.message.chat.id == $chat)
328
+ | select((.message.from.id | tostring) as $u | $ok | index($u) != null)
329
+ | select(.message.text != null)
330
+ | .message.text]
331
+ | if length > 0 then .[0] else "" end' <<<"$resp")
332
+ if [ -n "$answer" ]; then
333
+ api editMessageText -d "chat_id=$CHAT_ID" -d "message_id=$msg_id" \
334
+ --data-urlencode "text=$question
335
+ 💬 $answer" >/dev/null
336
+ printf '%s\n' "$answer"
337
+ return 0
338
+ fi
339
+ done
340
+
341
+ api editMessageText -d "chat_id=$CHAT_ID" -d "message_id=$msg_id" \
342
+ --data-urlencode "text=$question
343
+ ⏰ timed out" >/dev/null || true
344
+ return 2
345
+ }
346
+
347
+ set_config_key() {
348
+ local key="$1" val="$2" tmp
349
+ ( umask 077; mkdir -p "$CONFIG_DIR"; touch "$CONFIG_FILE" )
350
+ tmp=$(mktemp "$CONFIG_DIR/.config.XXXXXX")
351
+ grep -v "^$key=" "$CONFIG_FILE" > "$tmp" || true
352
+ printf '%s=%s\n' "$key" "$val" >> "$tmp"
353
+ mv "$tmp" "$CONFIG_FILE"
354
+ chmod 600 "$CONFIG_FILE"
355
+ }
356
+
357
+ cmd_setup() {
358
+ local bot=""
359
+ while [ $# -gt 0 ]; do
360
+ case "$1" in
361
+ --bot) bot="$2"; shift 2 ;;
362
+ *) die "unknown flag for setup: $1" ;;
363
+ esac
364
+ done
365
+
366
+ printf 'Telegram bot setup%s\n\n' "${bot:+ (named bot: $bot)}" >&2
367
+ printf ' 1. Open https://t.me/BotFather in Telegram\n' >&2
368
+ printf ' 2. Send /newbot and follow the prompts (display name, then a username ending in "bot")\n' >&2
369
+ printf ' 3. BotFather replies with an HTTP API token\n\n' >&2
370
+ printf 'Paste the token here: ' >&2
371
+ local token
372
+ read -r token
373
+ [ -n "$token" ] || die "no token entered"
374
+
375
+ BOT_TOKEN="$token"
376
+ local username
377
+ username=$(api getMe | jq -r '.result.username')
378
+ printf 'Token valid — bot is @%s\n\n' "$username" >&2
379
+
380
+ printf 'Now send any message to https://t.me/%s then press Enter... ' "$username" >&2
381
+ read -r _ || true
382
+ local chat_id
383
+ chat_id=$(api getUpdates | jq -r \
384
+ '[.result[] | .message.chat.id | select(. != null)] | if length > 0 then (.[-1] | tostring) else "" end')
385
+ [ -n "$chat_id" ] || die "no message found — send a message to @$username and rerun setup"
386
+
387
+ if [ -z "$bot" ]; then
388
+ set_config_key TELEGRAM_BOT_TOKEN "$token"
389
+ set_config_key TELEGRAM_CHAT_ID "$chat_id"
390
+ else
391
+ set_config_key "BOT_$(upper_key "$bot")_TOKEN" "$token"
392
+ if ! grep -q '^TELEGRAM_CHAT_ID=' "$CONFIG_FILE" 2>/dev/null; then
393
+ set_config_key TELEGRAM_CHAT_ID "$chat_id"
394
+ fi
395
+ fi
396
+
397
+ CHAT_ID="$chat_id"
398
+ api sendMessage -d "chat_id=$chat_id" \
399
+ --data-urlencode "text=✅ telegram.sh setup complete for @$username" >/dev/null
400
+ printf 'Config written to %s (chat %s) — confirmation sent.\n' "$CONFIG_FILE" "$chat_id" >&2
401
+ }
402
+
403
+ main() {
404
+ check_deps
405
+ load_config
406
+ [ $# -ge 1 ] || usage
407
+ local cmd="$1"
408
+ shift
409
+ case "$cmd" in
410
+ setup) cmd_setup "$@" ;;
411
+ send) cmd_send "$@" ;;
412
+ file) cmd_file "$@" ;;
413
+ ask) cmd_ask "$@" ;;
414
+ read) cmd_read "$@" ;;
415
+ -h|--help|help) usage ;;
416
+ *) die "unknown command: $cmd (telegram.sh help)" ;;
417
+ esac
418
+ }
419
+
420
+ main "$@"
@@ -0,0 +1,132 @@
1
+ ---
2
+ name: "test-guard"
3
+ description: "Review generated or changed test code against universal testing rules before it ships or is presented for approval."
4
+ risk: "critical"
5
+ source: "community"
6
+ source_repo: "amElnagdy/guard-skills"
7
+ source_type: "community"
8
+ date_added: 2026-07-13
9
+ author: "community"
10
+ tags: []
11
+ tools: []
12
+ ---
13
+
14
+
15
+ # Test Guard
16
+
17
+ You are reviewing generated or changed test code before it ships. Enforce the rules below after the first test-writing pass and before the tests are presented, committed, or merged. Be a sharp reviewer, not a pedantic one: flag what wastes maintenance effort or hides real bugs, ignore cosmetic preferences.
18
+
19
+ These rules exist because coding agents over-generate tests. The common failure modes: mock-heavy unit tests that assert implementation details, near-duplicate test bodies that differ by one value, and tests that re-verify the framework instead of the project's logic. Each looks productive in a diff and costs maintenance forever.
20
+
21
+ ## When to Use
22
+
23
+ Use this skill when reviewing generated or changed test code before it ships. Activate it reactively after an agent writes, edits, generates, or refactors tests — unit tests, integration tests, e2e tests, or snapshot tests in any framework.
24
+
25
+ ## When this skill activates
26
+
27
+ - A coding agent has just written new test functions or test files, in any language
28
+ - You are editing existing tests
29
+ - You are reviewing a diff that contains test changes
30
+ - The user asks you to write, add, or review tests
31
+
32
+ ## Adapt to the project first
33
+
34
+ These rules are universal, but their application is not. Before reviewing:
35
+
36
+ 1. Check the project's own agent instructions (CLAUDE.md, AGENTS.md) and testing docs. Project-specific testing rules win over this skill when they conflict.
37
+ 2. Identify the test stack, then read the matching reference for concrete patterns:
38
+ - Python / pytest → [references/pytest.md](references/pytest.md)
39
+ - PHP / PHPUnit / Pest / WordPress → [references/phpunit.md](references/phpunit.md)
40
+ - JavaScript / TypeScript / Jest / Vitest → [references/jest.md](references/jest.md)
41
+ 3. If the project calls LLM APIs, uses agent frameworks, or wires up observability/telemetry, also read [references/llm-app-testing.md](references/llm-app-testing.md) — it adds three rules specific to LLM applications.
42
+ 4. Map the project's system boundaries: network calls, databases, filesystem, clock and randomness, third-party SDKs, LLM APIs. Existing fixtures and test helpers usually reveal where the project already draws these lines.
43
+
44
+ ## What to do
45
+
46
+ 1. Read the test code: the diff, the new file, or the section being modified.
47
+ 2. Check each test against the rules below.
48
+ 3. Report violations concisely: rule number, location, why it violates, suggested fix.
49
+ 4. If the user explicitly invokes this skill before test writing, apply the rules as you write — don't write violations and then flag them.
50
+
51
+ When writing new tests, ask for each test: "What specific bug does this catch that no other test in this suite catches?" If you can't answer clearly, don't write it.
52
+
53
+ ## The Nine Rules
54
+
55
+ ### Rule 1: Test behavior, not implementation
56
+ Test what code does from the caller's perspective. Assert return values and observable side effects. Never assert that an internal helper was called with specific arguments — that test breaks on every refactor while catching nothing.
57
+
58
+ **Violation pattern:** asserting a mock of an internal function was called, where that function is not a system boundary.
59
+ **Fix:** assert the return value or the state change the caller observes.
60
+
61
+ ### Rule 2: Every mock must be justified
62
+ Mock only at system boundaries: network and HTTP calls, LLM APIs, databases, filesystem I/O on external files, clock and randomness, third-party SDKs. Never mock internal classes or helper functions to isolate a "unit" — the seams you create hide the integration bugs worth catching.
63
+
64
+ When you mock a boundary, assert what the caller *does with the response*, not that the mock received specific arguments.
65
+
66
+ ### Rule 3: One scenario per test, data-driven for variants
67
+ If two or more tests share identical setup and differ only in input/output values, merge them into one data-driven test (`@pytest.mark.parametrize`, PHPUnit `#[DataProvider]`, Jest `test.each`).
68
+
69
+ **When separate tests ARE correct:** different setup, different assertions, different mock configurations, or genuinely different scenarios that happen to exercise the same function.
70
+
71
+ ### Rule 4: Every test must justify its existence
72
+ Ask: "What bug does this catch that no other test catches?" Delete tests that only catch typos, verify default values of data classes, or test trivial pass-through logic.
73
+
74
+ **Common unjustified tests:** constructors setting attributes, a function rejecting input the type system already forbids, string formatting of log messages, a constant equaling its literal value.
75
+
76
+ ### Rule 5: Name tests for the scenario
77
+ Pattern: `test_<scenario>_<expected_outcome>`. The name should read like a requirement, not echo the function signature.
78
+
79
+ | Bad | Good |
80
+ |-----|------|
81
+ | `test_parse_response_missing_field` | `test_malformed_response_falls_back_to_default` |
82
+ | `test_get_language_no_class` | `test_element_without_class_returns_empty_language` |
83
+ | `test_add_tags_single_string` | `test_single_tag_normalizes_to_list` |
84
+
85
+ ### Rule 6: Production regression tests are sacred
86
+ Tests that reproduce a real production bug are always justified. Reference the incident (date, issue ID, or short description) in the name or a comment, and never delete them. They are exempt from Rule 4 — their justification is the incident.
87
+
88
+ ### Rule 7: No tests for framework guarantees
89
+ Don't test that the validation library validates, the ORM commits, the router returns 404, or the test framework's fixtures work. Test *your* logic that sits on top of the framework.
90
+
91
+ **Violation pattern:** a test that would still pass if you deleted all the project's custom code and kept only framework defaults.
92
+
93
+ ### Rule 8: State and value objects are real, never mocked
94
+ Never mock a data model, DTO, entity, or state object. Construct a real instance. Mocking state hides field-name typos and validation errors — exactly the bugs worth catching. If constructing the real object is painful, that is design feedback, not a reason to mock; add a small builder or factory helper.
95
+
96
+ ### Rule 9: Infrastructure under test gets real infrastructure
97
+ When database queries, schema behavior, or persistence logic *is the subject* of the test, run against a real test database with real migrations applied via fixtures. Mocking the session there tests nothing. Mocking the database is fine when persistence is only a side effect of the behavior under test.
98
+
99
+ ## Reporting format
100
+
101
+ When flagging violations, use this format:
102
+
103
+ ```
104
+ **Rule N violation** in `tests/path/file.ext::<test_name>`
105
+ - What: <one sentence describing the violation>
106
+ - Fix: <one sentence describing what to do instead>
107
+ ```
108
+
109
+ Group violations by file. If a file has no violations, don't mention it.
110
+
111
+ ## Severity guide
112
+
113
+ Not all violations are equal. Use judgment:
114
+
115
+ - **Must fix:** Rules 1, 2, 8 — these hide real bugs or make tests brittle
116
+ - **Should fix:** Rules 3, 4, 5, 7 — these cause bloat and maintenance drag
117
+ - **Sacred:** Rule 6 — never delete, always allow
118
+ - **Worth noting:** Rule 9 — test architecture; flag it, but don't block small changes on it
119
+
120
+ ## References
121
+
122
+ - [references/pytest.md](references/pytest.md) — Python/pytest patterns: parametrize, fixtures, mock boundaries, real Pydantic instances
123
+ - [references/phpunit.md](references/phpunit.md) — PHP/PHPUnit/Pest patterns, including WordPress and WooCommerce test boundaries
124
+ - [references/jest.md](references/jest.md) — Jest/Vitest patterns: test.each, module mocks, msw, snapshot discipline
125
+ - [references/llm-app-testing.md](references/llm-app-testing.md) — three extra rules for LLM applications: prompt contracts, observability wiring, agent-flow transitions
126
+
127
+ ## What this skill does NOT do
128
+
129
+ - It does not run tests. Use the project's test runner for that.
130
+ - It does not enforce code style — that's the linter's job.
131
+ - It does not decide *what* to test — only *how* to test it.
132
+ - It does not flag pre-existing violations in files you're not touching, unless asked to audit.