opencode-skills-collection 4.0.0 → 4.0.2
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.
- package/bundled-skills/.antigravity-install-manifest.json +28 -1
- package/bundled-skills/007/SKILL.md +2 -2
- package/bundled-skills/10-andruia-skill-smith/SKILL.md +1 -1
- package/bundled-skills/agent-squad/SKILL.md +5 -0
- package/bundled-skills/agent-squad/alex/SKILL.md +3 -0
- package/bundled-skills/agent-squad/aria/SKILL.md +3 -0
- package/bundled-skills/agent-squad/dep/SKILL.md +3 -0
- package/bundled-skills/agent-squad/luna/SKILL.md +4 -1
- package/bundled-skills/agent-squad/mason/SKILL.md +3 -0
- package/bundled-skills/agent-squad/max/SKILL.md +3 -0
- package/bundled-skills/agent-squad/quinn/SKILL.md +3 -0
- package/bundled-skills/agent-squad/rex/SKILL.md +3 -0
- package/bundled-skills/angular/metadata.json +1 -1
- package/bundled-skills/angular-best-practices/metadata.json +1 -1
- package/bundled-skills/angular-state-management/metadata.json +1 -1
- package/bundled-skills/angular-ui-patterns/metadata.json +1 -1
- package/bundled-skills/antigravity-skill-orchestrator/SKILL.md +2 -2
- package/bundled-skills/anywrite/SKILL.md +106 -0
- package/bundled-skills/apify-actor-development/SKILL.md +2 -2
- package/bundled-skills/apple-container/SKILL.md +165 -0
- package/bundled-skills/apple-container/references/commands.md +1593 -0
- package/bundled-skills/apple-container/references/concepts.md +294 -0
- package/bundled-skills/apple-container/references/configuration.md +499 -0
- package/bundled-skills/apple-container/references/workflows.md +498 -0
- package/bundled-skills/ask-copilot/SKILL.md +112 -0
- package/bundled-skills/auto-research/SKILL.md +126 -0
- package/bundled-skills/brooks-lint/SKILL.md +1 -1
- package/bundled-skills/browser-harness/references/install.md +3 -1
- package/bundled-skills/chrome-extension-developer/SKILL.md +1 -1
- package/bundled-skills/clean-code-guard/SKILL.md +184 -0
- package/bundled-skills/clean-code-guard/references/ai-failure-modes.md +345 -0
- package/bundled-skills/clean-code-guard/references/comments-and-formatting.md +115 -0
- package/bundled-skills/clean-code-guard/references/dry-kiss-yagni.md +133 -0
- package/bundled-skills/clean-code-guard/references/naming-and-functions.md +187 -0
- package/bundled-skills/clean-code-guard/references/review-checklist.md +150 -0
- package/bundled-skills/clean-code-guard/references/solid.md +280 -0
- package/bundled-skills/clean-code-guard/references/sources.md +47 -0
- package/bundled-skills/cloudflare-security-audit/SKILL.md +137 -0
- package/bundled-skills/cloudflare-security-audit/references/AI-AND-LLM.md +67 -0
- package/bundled-skills/cloudflare-security-audit/references/ATTACK-CLASSES.md +116 -0
- package/bundled-skills/cloudflare-security-audit/references/CLIENT-SIDE.md +67 -0
- package/bundled-skills/cloudflare-security-audit/references/HUNTING.md +110 -0
- package/bundled-skills/cloudflare-security-audit/references/MEMORY-SAFETY-AND-BINARY.md +58 -0
- package/bundled-skills/cloudflare-security-audit/references/RECONNAISSANCE.md +46 -0
- package/bundled-skills/cloudflare-security-audit/references/VALIDATION-AND-REPORTING.md +109 -0
- package/bundled-skills/cloudflare-security-audit/references/WEB-PROTOCOL-AND-AUTH.md +85 -0
- package/bundled-skills/cloudflare-security-audit/resources/report-schema.json +210 -0
- package/bundled-skills/comfyui-gateway/references/troubleshooting.md +1 -1
- package/bundled-skills/commit/SKILL.md +2 -1
- package/bundled-skills/deepapi/SKILL.md +6 -6
- package/bundled-skills/ditto/SKILL.md +173 -0
- package/bundled-skills/docs/README.md +1 -1
- package/bundled-skills/docs/contributors/quality-bar.md +1 -1
- package/bundled-skills/docs/contributors/security-guardrails.md +1 -1
- package/bundled-skills/docs/integrations/jetski-cortex.md +5 -5
- package/bundled-skills/docs/integrations/jetski-gemini-loader/README.md +3 -3
- package/bundled-skills/docs/maintainers/ci-drift-fix.md +7 -5
- package/bundled-skills/docs/maintainers/merge-batch.md +24 -7
- package/bundled-skills/docs/maintainers/pr-autonomy.md +89 -0
- package/bundled-skills/docs/maintainers/release-notes-7.2.0.md +1 -1
- package/bundled-skills/docs/maintainers/release-process.md +9 -9
- package/bundled-skills/docs/maintainers/repo-growth-seo.md +5 -5
- package/bundled-skills/docs/maintainers/skills-date-tracking.md +1 -1
- package/bundled-skills/docs/maintainers/skills-update-guide.md +2 -2
- package/bundled-skills/docs/users/{antigravity-awesome-skills-vs-awesome-claude-skills.md → agentic-awesome-skills-vs-awesome-claude-skills.md} +5 -5
- package/bundled-skills/docs/users/ai-agent-skills.md +4 -4
- package/bundled-skills/docs/users/best-claude-code-skills-github.md +3 -3
- package/bundled-skills/docs/users/best-cursor-skills-github.md +3 -3
- package/bundled-skills/docs/users/bundles.md +56 -57
- package/bundled-skills/docs/users/claude-code-skills.md +6 -6
- package/bundled-skills/docs/users/codex-cli-skills.md +5 -5
- package/bundled-skills/docs/users/cursor-skills.md +3 -3
- package/bundled-skills/docs/users/faq.md +27 -15
- package/bundled-skills/docs/users/gemini-cli-skills.md +4 -4
- package/bundled-skills/docs/users/getting-started.md +22 -12
- package/bundled-skills/docs/users/kiro-integration.md +7 -7
- package/bundled-skills/docs/users/plugins.md +8 -9
- package/bundled-skills/docs/users/security-skills.md +1 -1
- package/bundled-skills/docs/users/skills-vs-mcp-tools.md +1 -1
- package/bundled-skills/docs/users/specialized-plugin-roadmap.md +3 -4
- package/bundled-skills/docs/users/usage.md +9 -9
- package/bundled-skills/docs/users/visual-guide.md +7 -7
- package/bundled-skills/docs/users/windows-truncation-recovery.md +1 -1
- package/bundled-skills/docs/users/workflows.md +1 -1
- package/bundled-skills/docs/vietnamese/BUNDLES.vi.md +1 -1
- package/bundled-skills/docs/vietnamese/FAQ.vi.md +15 -8
- package/bundled-skills/docs/vietnamese/GETTING_STARTED.vi.md +12 -5
- package/bundled-skills/docs/vietnamese/QUALITY_BAR.vi.md +1 -1
- package/bundled-skills/docs/vietnamese/README.vi.md +21 -17
- package/bundled-skills/docs/vietnamese/SECURITY_GUARDRAILS.vi.md +1 -1
- package/bundled-skills/docs/vietnamese/SKILLS_README.vi.md +2 -2
- package/bundled-skills/docs/vietnamese/TRANSLATION_PLAN.vi.md +1 -1
- package/bundled-skills/docs/vietnamese/VISUAL_GUIDE.vi.md +8 -8
- package/bundled-skills/docs-guard/SKILL.md +109 -0
- package/bundled-skills/docs-guard/references/code-samples.md +46 -0
- package/bundled-skills/docs-guard/references/docstrings.md +60 -0
- package/bundled-skills/docs-guard/references/review-checklist.md +45 -0
- package/bundled-skills/docs-guard/references/sources.md +22 -0
- package/bundled-skills/docs-guard/references/verification.md +51 -0
- package/bundled-skills/efficient-web-research/SKILL.md +4 -0
- package/bundled-skills/electron-development/SKILL.md +1 -1
- package/bundled-skills/event-staffing-compliance/SKILL.md +3 -0
- package/bundled-skills/event-staffing-ordering/SKILL.md +3 -0
- package/bundled-skills/feature-tracking/SKILL.md +222 -0
- package/bundled-skills/flutter-expert/SKILL.md +6 -1
- package/bundled-skills/flutter-expert/resources/implementation-playbook.md +214 -0
- package/bundled-skills/gemini-deep-research/README.md +246 -0
- package/bundled-skills/gemini-deep-research/SKILL.md +136 -0
- package/bundled-skills/gemini-deep-research/requirements.txt +2 -0
- package/bundled-skills/gemini-deep-research/scripts/research.py +692 -0
- package/bundled-skills/git-pushing/SKILL.md +21 -4
- package/bundled-skills/git-pushing/scripts/smart_commit.sh +206 -11
- package/bundled-skills/go-in-depth/scripts/workflow-script.js +4 -1
- package/bundled-skills/grok-build/SKILL.md +167 -0
- package/bundled-skills/grok-build/references/cli.md +99 -0
- package/bundled-skills/huggingface-tool-builder/references/find_models_by_paper.sh +1 -1
- package/bundled-skills/idea-autopsy/SKILL.md +147 -0
- package/bundled-skills/it-manager-hospital/README.md +1 -1
- package/bundled-skills/it-manager-pro/README.md +1 -1
- package/bundled-skills/itil-expert/README.md +1 -1
- package/bundled-skills/langgraph/SKILL.md +1 -1
- package/bundled-skills/linkedin-content-generator/SKILL.md +1 -1
- package/bundled-skills/lint-and-validate/scripts/lint_runner.py +23 -23
- package/bundled-skills/lint-and-validate/scripts/type_coverage.py +30 -30
- package/bundled-skills/logic-lens/SKILL.md +1 -1
- package/bundled-skills/lore/LICENSE +21 -0
- package/bundled-skills/lore/README.md +386 -0
- package/bundled-skills/lore/README.zh-CN.md +386 -0
- package/bundled-skills/lore/SKILL.md +453 -0
- package/bundled-skills/lore/WORKFLOWS.md +216 -0
- package/bundled-skills/lore/WORKFLOWS.zh-CN.md +216 -0
- package/bundled-skills/lore/references/audit-template.md +60 -0
- package/bundled-skills/lore/references/compatibility.md +228 -0
- package/bundled-skills/lore/references/config.md +125 -0
- package/bundled-skills/lore/references/entry-format.md +70 -0
- package/bundled-skills/lore/references/history-command.md +106 -0
- package/bundled-skills/lore/references/monorepo-detection.md +78 -0
- package/bundled-skills/lore/references/platform-mirrors.md +377 -0
- package/bundled-skills/lore/references/stale-new-markers.md +62 -0
- package/bundled-skills/lore/references/summary-template.md +98 -0
- package/bundled-skills/lore/scripts/README.md +54 -0
- package/bundled-skills/lore/scripts/README.zh-CN.md +54 -0
- package/bundled-skills/lore/scripts/find_duplicates.py +211 -0
- package/bundled-skills/lore/scripts/find_stale.py +116 -0
- package/bundled-skills/lore/scripts/history.py +527 -0
- package/bundled-skills/lore/scripts/id_hash.py +32 -0
- package/bundled-skills/lore/scripts/list_entries.py +183 -0
- package/bundled-skills/lovable-cleanup/SKILL.md +3 -3
- package/bundled-skills/makepad-splash/SKILL.md +1 -1
- package/bundled-skills/mmx-cli/SKILL.md +14 -1
- package/bundled-skills/monopoly/SKILL.md +5 -0
- package/bundled-skills/monopoly/patterns/SKILL.md +4 -0
- package/bundled-skills/monopoly/scale-benchmarks/SKILL.md +4 -0
- package/bundled-skills/monopoly/security-checklist/SKILL.md +4 -0
- package/bundled-skills/monopoly/tech-matrix/SKILL.md +4 -0
- package/bundled-skills/mtls-configuration/SKILL.md +1 -1
- package/bundled-skills/network-101/SKILL.md +4 -5
- package/bundled-skills/nika/SKILL.md +270 -0
- package/bundled-skills/not-a-vibe-coder/SKILL.md +4 -0
- package/bundled-skills/ontoly-software-graph/SKILL.md +160 -0
- package/bundled-skills/openclaw-github-repo-commander/SKILL.md +1 -1
- package/bundled-skills/optim-agent/SKILL.md +78 -0
- package/bundled-skills/pilot-protocol/SKILL.md +6 -3
- package/bundled-skills/postgres-readonly-queries/README.md +77 -0
- package/bundled-skills/postgres-readonly-queries/SKILL.md +152 -0
- package/bundled-skills/postgres-readonly-queries/connections.example.json +34 -0
- package/bundled-skills/postgres-readonly-queries/requirements.txt +1 -0
- package/bundled-skills/postgres-readonly-queries/scripts/query.py +262 -0
- package/bundled-skills/pptx-deck-creation/SKILL.md +275 -0
- package/bundled-skills/pptx-deck-creation/references/audit-checklist.md +156 -0
- package/bundled-skills/pptx-deck-creation/references/design-profiles.md +96 -0
- package/bundled-skills/pptx-deck-creation/references/python-snippets.md +150 -0
- package/bundled-skills/pptx-deck-creation/references/reference-deck-analysis.md +91 -0
- package/bundled-skills/pptx-deck-creation/references/visual-asset-adapters.md +158 -0
- package/bundled-skills/privilege-escalation-methods/SKILL.md +1 -1
- package/bundled-skills/product-decision-agent/SKILL.md +94 -0
- package/bundled-skills/product-decision-agent/agents/openai.yaml +7 -0
- package/bundled-skills/product-decision-agent/references/methodology-basis.md +232 -0
- package/bundled-skills/product-decision-agent/references/product-playbooks.md +813 -0
- package/bundled-skills/product-decision-agent/references/reasoning-engine.md +289 -0
- package/bundled-skills/product-decision-agent/references/response-examples.md +279 -0
- package/bundled-skills/product-decision-agent/scripts/quality_gate.py +221 -0
- package/bundled-skills/product-decision-agent/scripts/test_quality_gate.py +94 -0
- package/bundled-skills/pytest-skill/reference/playbook.md +358 -0
- package/bundled-skills/quit-sponsor/SKILL.md +96 -0
- package/bundled-skills/rclone-cli/references/providers/s3.md +1 -1
- package/bundled-skills/security-and-hardening/SKILL.md +1 -1
- package/bundled-skills/semgrep-rule-creator/SKILL.md +6 -6
- package/bundled-skills/skill-creator/README.md +3 -3
- package/bundled-skills/skill-creator/SKILL.md +8 -4
- package/bundled-skills/skill-creator/scripts/quick_validate.py +1 -1
- package/bundled-skills/sql-injection-testing/SKILL.md +4 -4
- package/bundled-skills/sshepherd/SKILL.md +107 -0
- package/bundled-skills/subagent-orchestrator/README.md +1 -1
- package/bundled-skills/super-code/bash/SKILL.md +3 -0
- package/bundled-skills/super-code/c/SKILL.md +3 -0
- package/bundled-skills/super-code/cpp/SKILL.md +3 -0
- package/bundled-skills/super-code/csharp/SKILL.md +3 -0
- package/bundled-skills/super-code/dart/SKILL.md +3 -0
- package/bundled-skills/super-code/elixir/SKILL.md +3 -0
- package/bundled-skills/super-code/go/SKILL.md +3 -0
- package/bundled-skills/super-code/java/SKILL.md +3 -0
- package/bundled-skills/super-code/kotlin/SKILL.md +3 -0
- package/bundled-skills/super-code/php/SKILL.md +3 -0
- package/bundled-skills/super-code/python/SKILL.md +3 -0
- package/bundled-skills/super-code/ruby/SKILL.md +3 -0
- package/bundled-skills/super-code/rust/SKILL.md +3 -0
- package/bundled-skills/super-code/scala/SKILL.md +3 -0
- package/bundled-skills/super-code/swift/SKILL.md +3 -0
- package/bundled-skills/super-code/typescript/SKILL.md +3 -0
- package/bundled-skills/telegram-bot-messaging/README.md +185 -0
- package/bundled-skills/telegram-bot-messaging/SKILL.md +101 -0
- package/bundled-skills/telegram-bot-messaging/scripts/telegram.sh +420 -0
- package/bundled-skills/test-guard/SKILL.md +132 -0
- package/bundled-skills/test-guard/references/jest.md +50 -0
- package/bundled-skills/test-guard/references/llm-app-testing.md +40 -0
- package/bundled-skills/test-guard/references/phpunit.md +56 -0
- package/bundled-skills/test-guard/references/pytest.md +65 -0
- package/bundled-skills/trading-ledger/SKILL.md +11 -3
- package/bundled-skills/uizze-ui-research/SKILL.md +93 -0
- package/bundled-skills/vibe-code-auditor/SKILL.md +1 -1
- package/bundled-skills/vibecode-production-qa-validator/SKILL.md +1 -1
- package/bundled-skills/videodb/scripts/ws_listener.py +29 -15
- package/bundled-skills/voice-ai-engine-development/README.md +1 -1
- package/bundled-skills/vulnerability-scanner/SKILL.md +1 -1
- package/bundled-skills/warehouse/SKILL.md +154 -0
- package/bundled-skills/woo-guard/SKILL.md +113 -0
- package/bundled-skills/woo-guard/references/checkout-and-money.md +64 -0
- package/bundled-skills/woo-guard/references/hpos-and-crud.md +87 -0
- package/bundled-skills/woo-guard/references/review-checklist.md +44 -0
- package/bundled-skills/woo-guard/references/sources.md +17 -0
- package/bundled-skills/wp-guard/SKILL.md +119 -0
- package/bundled-skills/wp-guard/references/i18n.md +88 -0
- package/bundled-skills/wp-guard/references/performance.md +70 -0
- package/bundled-skills/wp-guard/references/review-checklist.md +57 -0
- package/bundled-skills/wp-guard/references/security.md +100 -0
- package/bundled-skills/wp-guard/references/sources.md +27 -0
- package/bundled-skills/writing-skills/gotchas.md +1 -1
- package/bundled-skills/writing-skills/references/standards/metadata-standard.md +1 -1
- package/bundled-skills/x-twitter-scraper/SKILL.md +14 -32
- package/bundled-skills/xss-html-injection/SKILL.md +3 -3
- package/bundled-skills/youtube-seo-optimizer/SKILL.md +1 -1
- package/bundled-skills/youtube-summarizer/CHANGELOG.md +2 -2
- package/package.json +2 -2
- package/skills_index.json +407 -0
|
@@ -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.
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# Test Guard — JavaScript / TypeScript / Jest / Vitest Patterns
|
|
2
|
+
|
|
3
|
+
Concrete applications of the nine rules for JS/TS projects. Read this when reviewing or writing Jest or Vitest tests.
|
|
4
|
+
|
|
5
|
+
## Rule 2: Mock boundaries in JS/TS
|
|
6
|
+
|
|
7
|
+
Justified mock targets:
|
|
8
|
+
|
|
9
|
+
- Network: prefer `msw` (Mock Service Worker) over `jest.mock`-ing your own fetch wrapper — it mocks at the true boundary
|
|
10
|
+
- LLM / third-party SDK clients (`openai`, `@anthropic-ai/sdk`, Stripe, etc.)
|
|
11
|
+
- Timers and randomness: `vi.useFakeTimers()` / `jest.useFakeTimers()`, seeded RNG
|
|
12
|
+
- Filesystem and process env in Node code
|
|
13
|
+
|
|
14
|
+
Unjustified mocks:
|
|
15
|
+
|
|
16
|
+
- `jest.mock('../utils/helpers')` — mocking your own internal module to isolate a "unit" (Rule 2)
|
|
17
|
+
- Mocking a class's private method via prototype patching (Rule 1)
|
|
18
|
+
- Hand-built object literals pretending to be domain entities when a real constructor or factory exists (Rule 8)
|
|
19
|
+
|
|
20
|
+
## Rule 3: test.each
|
|
21
|
+
|
|
22
|
+
```ts
|
|
23
|
+
// Violation: three near-identical it() blocks
|
|
24
|
+
// Fix:
|
|
25
|
+
test.each([
|
|
26
|
+
['Hello World', 'hello-world'],
|
|
27
|
+
[' padded ', 'padded'],
|
|
28
|
+
['Café Menu', 'cafe-menu'],
|
|
29
|
+
])('slugify(%s) → %s', (raw, expected) => {
|
|
30
|
+
expect(slugify(raw)).toBe(expected);
|
|
31
|
+
});
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Snapshot discipline
|
|
35
|
+
|
|
36
|
+
Snapshot tests are implementation tests in disguise unless the snapshot *is* the contract (e.g., a public JSON output, a CLI's help text). Avoid snapshots of:
|
|
37
|
+
|
|
38
|
+
- Full component trees that change on every styling tweak (Rule 1 — brittle, asserts implementation)
|
|
39
|
+
- Large objects nobody reviews — an unread snapshot approves itself (Rule 4)
|
|
40
|
+
|
|
41
|
+
Prefer targeted assertions: `expect(screen.getByRole('button')).toHaveTextContent('Save')`.
|
|
42
|
+
|
|
43
|
+
## UI component tests
|
|
44
|
+
|
|
45
|
+
- Test what the user sees and does (Testing Library queries by role/label), not component internals or state hooks (Rule 1).
|
|
46
|
+
- Don't test that React renders, routes resolve, or props propagate — framework guarantees (Rule 7).
|
|
47
|
+
|
|
48
|
+
## Rule 9: Real persistence
|
|
49
|
+
|
|
50
|
+
For data-layer logic (Prisma/Drizzle/Knex queries), run against a real test database — `testcontainers`, a Dockerized Postgres, or SQLite where compatible. Mocking the query builder to test the query builder tests nothing.
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# Test Guard — LLM Application Rules
|
|
2
|
+
|
|
3
|
+
Three additional rules for projects that call LLM APIs, use agent/workflow frameworks (LangGraph, CrewAI, custom state machines), or wire up observability/telemetry (Langfuse, LangSmith, OpenTelemetry). Apply these on top of the nine core rules.
|
|
4
|
+
|
|
5
|
+
## Rule 10: Prompt tests — test the contract, not the content
|
|
6
|
+
|
|
7
|
+
Prompt text changes constantly; tests pinned to wording rot within a week. Don't assert specific phrasing.
|
|
8
|
+
|
|
9
|
+
Do test:
|
|
10
|
+
|
|
11
|
+
- The prompt template exists and loads without error (smoke test)
|
|
12
|
+
- Template variables are substituted correctly — no leftover `{placeholder}` markers
|
|
13
|
+
- The prompt contains required structural markers *if the caller parses them* (e.g., a JSON schema block, a delimiter the parser splits on)
|
|
14
|
+
|
|
15
|
+
## Rule 11: Observability is infrastructure
|
|
16
|
+
|
|
17
|
+
Don't unit-test telemetry wiring. The violation pattern is asserting a tracing/analytics mock's call arguments:
|
|
18
|
+
|
|
19
|
+
```python
|
|
20
|
+
# Violation — tests wiring, not behavior
|
|
21
|
+
mock_tracer.assert_called_once_with(session_id=..., tags=[...])
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Mocking observability calls to *prevent side effects* during tests is fine and often necessary. Just don't assert on the mock's call args. If telemetry breaks, dashboards show it; a unit test asserting wiring only breaks refactors.
|
|
25
|
+
|
|
26
|
+
## Rule 12: Agent and flow tests test transitions
|
|
27
|
+
|
|
28
|
+
For agent frameworks and state machines: test that given a state plus an event, the flow reaches the correct next state with the correct fields set. Mock the LLM calls to return controlled responses.
|
|
29
|
+
|
|
30
|
+
Test: state in → state out.
|
|
31
|
+
|
|
32
|
+
Don't test: the exact prompt string passed to the LLM, the number of LLM calls made, or internal retry logic — those are implementation details (Rule 1) that change with every model upgrade.
|
|
33
|
+
|
|
34
|
+
A useful pattern is a table of transition cases (data-driven, Rule 3): starting state, mocked LLM response, expected resulting state.
|
|
35
|
+
|
|
36
|
+
## Severity
|
|
37
|
+
|
|
38
|
+
- **Must fix:** Rule 12 violations that assert prompt strings or call counts — they break on every model/prompt change
|
|
39
|
+
- **Should fix:** Rule 10 wording assertions
|
|
40
|
+
- **Worth noting:** Rule 11 — flag it, but don't block a small change on it
|