@iamnishankhan/opencode-kit-grok 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/cli.js +46 -0
- package/kit/.opencode/AGENTS.md +392 -0
- package/kit/.opencode/README.md +71 -0
- package/kit/.opencode/USAGE.md +405 -0
- package/kit/.opencode/agents/backend-specialist.md +278 -0
- package/kit/.opencode/agents/code-archaeologist.md +114 -0
- package/kit/.opencode/agents/database-architect.md +233 -0
- package/kit/.opencode/agents/debugger.md +234 -0
- package/kit/.opencode/agents/devops-engineer.md +252 -0
- package/kit/.opencode/agents/documentation-writer.md +111 -0
- package/kit/.opencode/agents/explorer-agent.md +83 -0
- package/kit/.opencode/agents/frontend-specialist.md +606 -0
- package/kit/.opencode/agents/game-developer.md +169 -0
- package/kit/.opencode/agents/mobile-developer.md +385 -0
- package/kit/.opencode/agents/orchestrator.md +213 -0
- package/kit/.opencode/agents/penetration-tester.md +197 -0
- package/kit/.opencode/agents/performance-optimizer.md +194 -0
- package/kit/.opencode/agents/product-manager.md +120 -0
- package/kit/.opencode/agents/product-owner.md +103 -0
- package/kit/.opencode/agents/project-planner.md +418 -0
- package/kit/.opencode/agents/qa-automation-engineer.md +113 -0
- package/kit/.opencode/agents/security-auditor.md +179 -0
- package/kit/.opencode/agents/seo-specialist.md +119 -0
- package/kit/.opencode/agents/test-engineer.md +169 -0
- package/kit/.opencode/commands/brainstorm.md +113 -0
- package/kit/.opencode/commands/coordinate.md +72 -0
- package/kit/.opencode/commands/create.md +63 -0
- package/kit/.opencode/commands/debug.md +102 -0
- package/kit/.opencode/commands/deploy.md +175 -0
- package/kit/.opencode/commands/enhance.md +62 -0
- package/kit/.opencode/commands/orchestrate.md +241 -0
- package/kit/.opencode/commands/plan.md +89 -0
- package/kit/.opencode/commands/preview.md +80 -0
- package/kit/.opencode/commands/remember.md +67 -0
- package/kit/.opencode/commands/status.md +86 -0
- package/kit/.opencode/commands/test.md +143 -0
- package/kit/.opencode/commands/verify.md +71 -0
- package/kit/.opencode/memory/MEMORY.md +6 -0
- package/kit/.opencode/memory/feedback-history.md +9 -0
- package/kit/.opencode/memory/project-conventions.md +16 -0
- package/kit/.opencode/memory/tech-decisions.md +10 -0
- package/kit/.opencode/memory/user-preferences.md +9 -0
- package/kit/.opencode/opencode.json +15 -0
- package/kit/.opencode/scripts/README.md +95 -0
- package/kit/.opencode/scripts/auto_preview.py +149 -0
- package/kit/.opencode/scripts/checklist.py +75 -0
- package/kit/.opencode/scripts/component_registry.py +238 -0
- package/kit/.opencode/scripts/dependency_graph.py +73 -0
- package/kit/.opencode/scripts/generate_manifest.py +44 -0
- package/kit/.opencode/scripts/session_manager.py +120 -0
- package/kit/.opencode/scripts/tests/test_toolkit.py +238 -0
- package/kit/.opencode/scripts/validate_kit.py +397 -0
- package/kit/.opencode/scripts/validation_runner.py +216 -0
- package/kit/.opencode/scripts/verify_all.py +85 -0
- package/kit/.opencode/skills/api-patterns/SKILL.md +83 -0
- package/kit/.opencode/skills/api-patterns/api-style.md +42 -0
- package/kit/.opencode/skills/api-patterns/auth.md +24 -0
- package/kit/.opencode/skills/api-patterns/documentation.md +26 -0
- package/kit/.opencode/skills/api-patterns/graphql.md +41 -0
- package/kit/.opencode/skills/api-patterns/rate-limiting.md +31 -0
- package/kit/.opencode/skills/api-patterns/response.md +37 -0
- package/kit/.opencode/skills/api-patterns/rest.md +40 -0
- package/kit/.opencode/skills/api-patterns/scripts/api_validator.py +211 -0
- package/kit/.opencode/skills/api-patterns/security-testing.md +122 -0
- package/kit/.opencode/skills/api-patterns/trpc.md +41 -0
- package/kit/.opencode/skills/api-patterns/versioning.md +22 -0
- package/kit/.opencode/skills/app-builder/SKILL.md +78 -0
- package/kit/.opencode/skills/app-builder/agent-coordination.md +71 -0
- package/kit/.opencode/skills/app-builder/feature-building.md +53 -0
- package/kit/.opencode/skills/app-builder/project-detection.md +45 -0
- package/kit/.opencode/skills/app-builder/scaffolding.md +110 -0
- package/kit/.opencode/skills/app-builder/tech-stack.md +41 -0
- package/kit/.opencode/skills/app-builder/templates/SKILL.md +39 -0
- package/kit/.opencode/skills/app-builder/templates/astro-static/TEMPLATE.md +78 -0
- package/kit/.opencode/skills/app-builder/templates/chrome-extension/TEMPLATE.md +96 -0
- package/kit/.opencode/skills/app-builder/templates/cli-tool/TEMPLATE.md +88 -0
- package/kit/.opencode/skills/app-builder/templates/electron-desktop/TEMPLATE.md +97 -0
- package/kit/.opencode/skills/app-builder/templates/express-api/TEMPLATE.md +89 -0
- package/kit/.opencode/skills/app-builder/templates/flutter-app/TEMPLATE.md +93 -0
- package/kit/.opencode/skills/app-builder/templates/monorepo-turborepo/TEMPLATE.md +97 -0
- package/kit/.opencode/skills/app-builder/templates/nextjs-fullstack/TEMPLATE.md +126 -0
- package/kit/.opencode/skills/app-builder/templates/nextjs-saas/TEMPLATE.md +125 -0
- package/kit/.opencode/skills/app-builder/templates/nextjs-static/TEMPLATE.md +174 -0
- package/kit/.opencode/skills/app-builder/templates/nuxt-app/TEMPLATE.md +127 -0
- package/kit/.opencode/skills/app-builder/templates/python-fastapi/TEMPLATE.md +94 -0
- package/kit/.opencode/skills/app-builder/templates/react-native-app/TEMPLATE.md +121 -0
- package/kit/.opencode/skills/architecture/SKILL.md +57 -0
- package/kit/.opencode/skills/architecture/context-discovery.md +43 -0
- package/kit/.opencode/skills/architecture/examples.md +94 -0
- package/kit/.opencode/skills/architecture/pattern-selection.md +68 -0
- package/kit/.opencode/skills/architecture/patterns-reference.md +50 -0
- package/kit/.opencode/skills/architecture/trade-off-analysis.md +77 -0
- package/kit/.opencode/skills/bash-linux/SKILL.md +201 -0
- package/kit/.opencode/skills/batch-operations/SKILL.md +105 -0
- package/kit/.opencode/skills/behavioral-modes/SKILL.md +253 -0
- package/kit/.opencode/skills/brainstorming/SKILL.md +178 -0
- package/kit/.opencode/skills/brainstorming/dynamic-questioning.md +350 -0
- package/kit/.opencode/skills/clean-code/SKILL.md +201 -0
- package/kit/.opencode/skills/code-review-checklist/SKILL.md +111 -0
- package/kit/.opencode/skills/code-review-graph/SKILL.md +305 -0
- package/kit/.opencode/skills/context-compression/SKILL.md +145 -0
- package/kit/.opencode/skills/coordinator-mode/SKILL.md +208 -0
- package/kit/.opencode/skills/database-design/SKILL.md +54 -0
- package/kit/.opencode/skills/database-design/database-selection.md +43 -0
- package/kit/.opencode/skills/database-design/indexing.md +39 -0
- package/kit/.opencode/skills/database-design/migrations.md +48 -0
- package/kit/.opencode/skills/database-design/optimization.md +36 -0
- package/kit/.opencode/skills/database-design/orm-selection.md +30 -0
- package/kit/.opencode/skills/database-design/schema-design.md +56 -0
- package/kit/.opencode/skills/database-design/scripts/schema_validator.py +172 -0
- package/kit/.opencode/skills/deployment-procedures/SKILL.md +243 -0
- package/kit/.opencode/skills/design-spec/SKILL.md +185 -0
- package/kit/.opencode/skills/design-spec/collection.md +106 -0
- package/kit/.opencode/skills/documentation-templates/SKILL.md +196 -0
- package/kit/.opencode/skills/frontend-architecture/SKILL.md +245 -0
- package/kit/.opencode/skills/frontend-design/SKILL.md +1222 -0
- package/kit/.opencode/skills/frontend-design/redesign.md +178 -0
- package/kit/.opencode/skills/frontend-design/scripts/accessibility_checker.py +145 -0
- package/kit/.opencode/skills/frontend-design/scripts/ux_audit.py +722 -0
- package/kit/.opencode/skills/frontend-design/style-brutalist.md +92 -0
- package/kit/.opencode/skills/frontend-design/style-minimalist.md +85 -0
- package/kit/.opencode/skills/game-development/2d-games/SKILL.md +118 -0
- package/kit/.opencode/skills/game-development/3d-games/SKILL.md +134 -0
- package/kit/.opencode/skills/game-development/SKILL.md +169 -0
- package/kit/.opencode/skills/game-development/game-art/SKILL.md +184 -0
- package/kit/.opencode/skills/game-development/game-audio/SKILL.md +189 -0
- package/kit/.opencode/skills/game-development/game-design/SKILL.md +128 -0
- package/kit/.opencode/skills/game-development/mobile-games/SKILL.md +107 -0
- package/kit/.opencode/skills/game-development/multiplayer/SKILL.md +131 -0
- package/kit/.opencode/skills/game-development/pc-games/SKILL.md +143 -0
- package/kit/.opencode/skills/game-development/vr-ar/SKILL.md +122 -0
- package/kit/.opencode/skills/game-development/web-games/SKILL.md +149 -0
- package/kit/.opencode/skills/geo-fundamentals/SKILL.md +158 -0
- package/kit/.opencode/skills/geo-fundamentals/scripts/geo_checker.py +273 -0
- package/kit/.opencode/skills/i18n-localization/SKILL.md +156 -0
- package/kit/.opencode/skills/i18n-localization/scripts/i18n_checker.py +223 -0
- package/kit/.opencode/skills/intelligent-routing/SKILL.md +337 -0
- package/kit/.opencode/skills/lint-and-validate/SKILL.md +47 -0
- package/kit/.opencode/skills/lint-and-validate/scripts/lint_runner.py +184 -0
- package/kit/.opencode/skills/lint-and-validate/scripts/type_coverage.py +245 -0
- package/kit/.opencode/skills/mcp-builder/SKILL.md +205 -0
- package/kit/.opencode/skills/memory-system/SKILL.md +181 -0
- package/kit/.opencode/skills/mobile-design/SKILL.md +396 -0
- package/kit/.opencode/skills/mobile-design/decision-trees.md +516 -0
- package/kit/.opencode/skills/mobile-design/mobile-backend.md +491 -0
- package/kit/.opencode/skills/mobile-design/mobile-color-system.md +420 -0
- package/kit/.opencode/skills/mobile-design/mobile-debugging.md +122 -0
- package/kit/.opencode/skills/mobile-design/mobile-design-thinking.md +357 -0
- package/kit/.opencode/skills/mobile-design/mobile-navigation.md +458 -0
- package/kit/.opencode/skills/mobile-design/mobile-performance.md +767 -0
- package/kit/.opencode/skills/mobile-design/mobile-testing.md +356 -0
- package/kit/.opencode/skills/mobile-design/mobile-typography.md +433 -0
- package/kit/.opencode/skills/mobile-design/platform-android.md +666 -0
- package/kit/.opencode/skills/mobile-design/platform-ios.md +561 -0
- package/kit/.opencode/skills/mobile-design/scripts/mobile_audit.py +670 -0
- package/kit/.opencode/skills/mobile-design/touch-psychology.md +537 -0
- package/kit/.opencode/skills/nextjs-react-expert/1-async-eliminating-waterfalls.md +351 -0
- package/kit/.opencode/skills/nextjs-react-expert/2-bundle-bundle-size-optimization.md +240 -0
- package/kit/.opencode/skills/nextjs-react-expert/3-server-server-side-performance.md +490 -0
- package/kit/.opencode/skills/nextjs-react-expert/4-client-client-side-data-fetching.md +264 -0
- package/kit/.opencode/skills/nextjs-react-expert/5-rerender-re-render-optimization.md +581 -0
- package/kit/.opencode/skills/nextjs-react-expert/6-rendering-rendering-performance.md +432 -0
- package/kit/.opencode/skills/nextjs-react-expert/7-js-javascript-performance.md +684 -0
- package/kit/.opencode/skills/nextjs-react-expert/8-advanced-advanced-patterns.md +150 -0
- package/kit/.opencode/skills/nextjs-react-expert/9-cache-components.md +103 -0
- package/kit/.opencode/skills/nextjs-react-expert/SKILL.md +293 -0
- package/kit/.opencode/skills/nextjs-react-expert/scripts/convert_rules.py +222 -0
- package/kit/.opencode/skills/nextjs-react-expert/scripts/react_performance_checker.py +281 -0
- package/kit/.opencode/skills/nodejs-best-practices/SKILL.md +336 -0
- package/kit/.opencode/skills/parallel-agents/SKILL.md +192 -0
- package/kit/.opencode/skills/performance-profiling/SKILL.md +145 -0
- package/kit/.opencode/skills/performance-profiling/scripts/bundle_analyzer.py +120 -0
- package/kit/.opencode/skills/performance-profiling/scripts/lighthouse_audit.py +108 -0
- package/kit/.opencode/skills/plan-writing/SKILL.md +153 -0
- package/kit/.opencode/skills/powershell-windows/SKILL.md +169 -0
- package/kit/.opencode/skills/python-patterns/SKILL.md +443 -0
- package/kit/.opencode/skills/red-team-tactics/SKILL.md +201 -0
- package/kit/.opencode/skills/rust-pro/SKILL.md +177 -0
- package/kit/.opencode/skills/seo-fundamentals/SKILL.md +131 -0
- package/kit/.opencode/skills/seo-fundamentals/scripts/seo_checker.py +219 -0
- package/kit/.opencode/skills/server-management/SKILL.md +163 -0
- package/kit/.opencode/skills/simplify-code/SKILL.md +131 -0
- package/kit/.opencode/skills/skillify/SKILL.md +115 -0
- package/kit/.opencode/skills/systematic-debugging/SKILL.md +111 -0
- package/kit/.opencode/skills/tailwind-patterns/SKILL.md +271 -0
- package/kit/.opencode/skills/tdd-workflow/SKILL.md +151 -0
- package/kit/.opencode/skills/testing-patterns/SKILL.md +180 -0
- package/kit/.opencode/skills/testing-patterns/scripts/test_runner.py +219 -0
- package/kit/.opencode/skills/verify-changes/SKILL.md +127 -0
- package/kit/.opencode/skills/vulnerability-scanner/SKILL.md +278 -0
- package/kit/.opencode/skills/vulnerability-scanner/checklists.md +124 -0
- package/kit/.opencode/skills/vulnerability-scanner/scripts/dependency_analyzer.py +144 -0
- package/kit/.opencode/skills/vulnerability-scanner/scripts/security_scan.py +410 -0
- package/kit/.opencode/skills/web-design-guidelines/SKILL.md +56 -0
- package/kit/.opencode/skills/webapp-testing/SKILL.md +189 -0
- package/kit/.opencode/skills/webapp-testing/scripts/playwright_runner.py +123 -0
- package/lib/init.js +61 -0
- package/package.json +21 -0
|
@@ -0,0 +1,410 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""AG Kit security scanner.
|
|
3
|
+
|
|
4
|
+
Performs lightweight, local checks for dependency hygiene, hard-coded secrets,
|
|
5
|
+
dangerous code patterns, and common security misconfiguration.
|
|
6
|
+
|
|
7
|
+
The scanner is intentionally dependency-free. It is a guardrail, not a
|
|
8
|
+
replacement for a full SAST/SCA product.
|
|
9
|
+
"""
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import argparse
|
|
13
|
+
import io
|
|
14
|
+
import json
|
|
15
|
+
import os
|
|
16
|
+
import re
|
|
17
|
+
import subprocess
|
|
18
|
+
import sys
|
|
19
|
+
import tokenize
|
|
20
|
+
from datetime import datetime, timezone
|
|
21
|
+
from pathlib import Path
|
|
22
|
+
from typing import Any, Callable
|
|
23
|
+
|
|
24
|
+
try:
|
|
25
|
+
sys.stdout.reconfigure(encoding="utf-8", errors="replace")
|
|
26
|
+
sys.stderr.reconfigure(encoding="utf-8", errors="replace")
|
|
27
|
+
except AttributeError:
|
|
28
|
+
pass
|
|
29
|
+
|
|
30
|
+
SECRET_PATTERNS: tuple[tuple[str, str, str], ...] = (
|
|
31
|
+
(r"api[_-]?key\s*[=:]\s*[\"'][^\"']{10,}[\"']", "API Key", "high"),
|
|
32
|
+
(r"token\s*[=:]\s*[\"'][^\"']{10,}[\"']", "Token", "high"),
|
|
33
|
+
(r"bearer\s+[a-zA-Z0-9\-_.]{16,}", "Bearer Token", "critical"),
|
|
34
|
+
(r"AKIA[0-9A-Z]{16}", "AWS Access Key", "critical"),
|
|
35
|
+
(r"aws[_-]?secret[_-]?access[_-]?key\s*[=:]\s*[\"'][^\"']+[\"']", "AWS Secret", "critical"),
|
|
36
|
+
(r"AZURE[_-]?[A-Z_]+\s*[=:]\s*[\"'][^\"']+[\"']", "Azure Credential", "critical"),
|
|
37
|
+
(r"GOOGLE[_-]?[A-Z_]+\s*[=:]\s*[\"'][^\"']+[\"']", "GCP Credential", "critical"),
|
|
38
|
+
(r"password\s*[=:]\s*[\"'][^\"']{6,}[\"']", "Password", "high"),
|
|
39
|
+
(r"(?:mongodb|postgres(?:ql)?|mysql|redis):\/\/[^\s\"']+", "Database Connection String", "critical"),
|
|
40
|
+
(r"-----BEGIN\s+(?:RSA|PRIVATE|EC|OPENSSH)\s+KEY-----", "Private Key", "critical"),
|
|
41
|
+
(r"ssh-rsa\s+[A-Za-z0-9+/]{80,}", "SSH Key", "critical"),
|
|
42
|
+
(r"eyJ[A-Za-z0-9_-]+\.eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+", "JWT Token", "high"),
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
DANGEROUS_PATTERNS: tuple[tuple[str, str, str, str], ...] = (
|
|
46
|
+
(r"\beval\s*\(", "eval() usage", "critical", "Code injection risk"),
|
|
47
|
+
(r"\bexec\s*\(", "exec() usage", "critical", "Code injection risk"),
|
|
48
|
+
(r"new\s+Function\s*\(", "Function constructor", "high", "Code injection risk"),
|
|
49
|
+
(r"child_process\.exec\s*\(", "child_process.exec", "high", "Command injection risk"),
|
|
50
|
+
(r"subprocess\.(?:call|run|Popen)\s*\([^\n]*shell\s*=\s*True", "subprocess shell=True", "high", "Command injection risk"),
|
|
51
|
+
(r"dangerouslySetInnerHTML", "dangerouslySetInnerHTML", "high", "XSS risk"),
|
|
52
|
+
(r"\.innerHTML\s*=", "innerHTML assignment", "medium", "XSS risk"),
|
|
53
|
+
(r"document\.write\s*\(", "document.write", "medium", "XSS risk"),
|
|
54
|
+
(r"[\"'][^\"']*\+\s*[a-zA-Z_]\w*\s*\+\s*[\"'][^\n]*(?:SELECT|INSERT|UPDATE|DELETE)", "SQL string concatenation", "critical", "SQL injection risk"),
|
|
55
|
+
(r"f[\"'][^\"']*(?:SELECT|INSERT|UPDATE|DELETE)[^\"']*\{", "SQL f-string", "critical", "SQL injection risk"),
|
|
56
|
+
(r"verify\s*=\s*False", "SSL verification disabled", "high", "MITM risk"),
|
|
57
|
+
(r"--insecure\b", "Insecure flag", "medium", "Security disabled"),
|
|
58
|
+
(r"disable[_-]?ssl", "SSL disabled", "high", "MITM risk"),
|
|
59
|
+
(r"pickle\.loads?\s*\(", "pickle usage", "high", "Unsafe deserialization risk"),
|
|
60
|
+
(r"yaml\.load\s*\(", "yaml.load usage", "high", "Unsafe deserialization risk"),
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
SEVERITY_RANK = {"none": 99, "low": 1, "medium": 2, "high": 3, "critical": 4}
|
|
64
|
+
SKIP_DIRS = {
|
|
65
|
+
"node_modules", ".git", "dist", "build", "__pycache__", ".venv", "venv",
|
|
66
|
+
".next", ".agents", ".agent", ".tox", ".mypy_cache", ".pytest_cache",
|
|
67
|
+
"coverage", ".coverage", "vendor",
|
|
68
|
+
}
|
|
69
|
+
CODE_EXTENSIONS = {".js", ".mjs", ".cjs", ".ts", ".tsx", ".jsx", ".py", ".go", ".java", ".rb", ".php"}
|
|
70
|
+
CONFIG_EXTENSIONS = {".json", ".yaml", ".yml", ".toml", ".env", ".ini", ".conf"}
|
|
71
|
+
PLACEHOLDER_MARKERS = (
|
|
72
|
+
"YOUR_", "EXAMPLE", "CHANGEME", "CHANGE_ME", "REPLACE_ME", "PLACEHOLDER",
|
|
73
|
+
"DUMMY", "SAMPLE", "FAKE_", "TEST_TOKEN", "TEST_KEY",
|
|
74
|
+
)
|
|
75
|
+
SELF_PATH = Path(__file__).resolve()
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def _new_result(tool: str, status: str) -> dict[str, Any]:
|
|
79
|
+
return {"tool": tool, "findings": [], "status": status, "scanned_files": 0}
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def _iter_files(project: Path, extensions: set[str]):
|
|
83
|
+
for root, dirs, files in os.walk(project):
|
|
84
|
+
dirs[:] = [d for d in dirs if d not in SKIP_DIRS]
|
|
85
|
+
for filename in files:
|
|
86
|
+
path = Path(root) / filename
|
|
87
|
+
if path.resolve() == SELF_PATH:
|
|
88
|
+
continue
|
|
89
|
+
if path.suffix.lower() in extensions or filename.startswith(".env"):
|
|
90
|
+
yield path
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def _relative(path: Path, project: Path) -> str:
|
|
94
|
+
try:
|
|
95
|
+
return path.relative_to(project).as_posix()
|
|
96
|
+
except ValueError:
|
|
97
|
+
return path.as_posix()
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def _is_placeholder(value: str) -> bool:
|
|
101
|
+
upper = value.upper()
|
|
102
|
+
return any(marker in upper for marker in PLACEHOLDER_MARKERS)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def _redact(value: str) -> str:
|
|
106
|
+
value = value.strip().replace("\n", " ")
|
|
107
|
+
if len(value) <= 12:
|
|
108
|
+
return "<redacted>"
|
|
109
|
+
return f"{value[:4]}…{value[-4:]}"
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def _python_code_without_strings(text: str) -> list[str]:
|
|
113
|
+
lines = [list(line) for line in text.splitlines(keepends=True)]
|
|
114
|
+
try:
|
|
115
|
+
tokens = tokenize.generate_tokens(io.StringIO(text).readline)
|
|
116
|
+
for tok in tokens:
|
|
117
|
+
if tok.type not in {tokenize.STRING, tokenize.COMMENT}:
|
|
118
|
+
continue
|
|
119
|
+
(srow, scol), (erow, ecol) = tok.start, tok.end
|
|
120
|
+
for row in range(srow, erow + 1):
|
|
121
|
+
if not (1 <= row <= len(lines)):
|
|
122
|
+
continue
|
|
123
|
+
start = scol if row == srow else 0
|
|
124
|
+
end = ecol if row == erow else len(lines[row - 1])
|
|
125
|
+
for col in range(start, min(end, len(lines[row - 1]))):
|
|
126
|
+
if lines[row - 1][col] not in "\r\n":
|
|
127
|
+
lines[row - 1][col] = " "
|
|
128
|
+
except (tokenize.TokenError, IndentationError):
|
|
129
|
+
pass
|
|
130
|
+
return ["".join(line) for line in lines]
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def _generic_code_without_strings(text: str) -> list[str]:
|
|
134
|
+
cleaned = re.sub(r"/\*.*?\*/", lambda m: "\n" * m.group(0).count("\n"), text, flags=re.DOTALL)
|
|
135
|
+
output: list[str] = []
|
|
136
|
+
string_re = re.compile(r"(?:\"(?:\\.|[^\"\\])*\"|'(?:\\.|[^'\\])*'|`(?:\\.|[^`\\])*`)")
|
|
137
|
+
for line in cleaned.splitlines(keepends=True):
|
|
138
|
+
no_comment = re.sub(r"//.*$", "", line)
|
|
139
|
+
output.append(string_re.sub("", no_comment))
|
|
140
|
+
return output
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def scan_dependencies(project_path: str) -> dict[str, Any]:
|
|
144
|
+
project = Path(project_path)
|
|
145
|
+
result = _new_result("dependency_scanner", "[OK] Supply chain checks passed")
|
|
146
|
+
|
|
147
|
+
ecosystems = {
|
|
148
|
+
"node": (["package.json"], ["package-lock.json", "npm-shrinkwrap.json", "yarn.lock", "pnpm-lock.yaml", "bun.lock", "bun.lockb"]),
|
|
149
|
+
"python": (["pyproject.toml", "setup.py", "setup.cfg", "requirements.in"], ["poetry.lock", "Pipfile.lock", "uv.lock", "requirements.txt"]),
|
|
150
|
+
"ruby": (["Gemfile"], ["Gemfile.lock"]),
|
|
151
|
+
"rust": (["Cargo.toml"], ["Cargo.lock"]),
|
|
152
|
+
"go": (["go.mod"], ["go.sum"]),
|
|
153
|
+
}
|
|
154
|
+
for name, (manifests, locks) in ecosystems.items():
|
|
155
|
+
if any((project / f).exists() for f in manifests) and not any((project / f).exists() for f in locks):
|
|
156
|
+
result["findings"].append({
|
|
157
|
+
"type": "Missing lock file",
|
|
158
|
+
"severity": "high",
|
|
159
|
+
"ecosystem": name,
|
|
160
|
+
"message": f"{name}: manifest found but no supported lock file is present.",
|
|
161
|
+
})
|
|
162
|
+
|
|
163
|
+
if (project / "package.json").exists():
|
|
164
|
+
try:
|
|
165
|
+
proc = subprocess.run(
|
|
166
|
+
["npm", "audit", "--json"], cwd=project, capture_output=True,
|
|
167
|
+
text=True, timeout=90, check=False,
|
|
168
|
+
)
|
|
169
|
+
raw = proc.stdout.strip() or proc.stderr.strip()
|
|
170
|
+
if raw:
|
|
171
|
+
data = json.loads(raw)
|
|
172
|
+
metadata = data.get("metadata", {}).get("vulnerabilities", {})
|
|
173
|
+
counts = {key: int(metadata.get(key, 0) or 0) for key in ("critical", "high", "moderate", "low")}
|
|
174
|
+
result["npm_audit"] = counts
|
|
175
|
+
for severity in ("critical", "high"):
|
|
176
|
+
if counts[severity]:
|
|
177
|
+
result["findings"].append({
|
|
178
|
+
"type": "npm audit", "severity": severity,
|
|
179
|
+
"message": f"{counts[severity]} {severity} dependency vulnerabilities.",
|
|
180
|
+
})
|
|
181
|
+
except FileNotFoundError:
|
|
182
|
+
result["notes"] = ["npm is unavailable; npm audit was skipped."]
|
|
183
|
+
except subprocess.TimeoutExpired:
|
|
184
|
+
result["notes"] = ["npm audit timed out."]
|
|
185
|
+
except (json.JSONDecodeError, OSError) as exc:
|
|
186
|
+
result["notes"] = [f"npm audit output could not be parsed: {exc}"]
|
|
187
|
+
|
|
188
|
+
if result["findings"]:
|
|
189
|
+
result["status"] = "[!] Dependency risks found"
|
|
190
|
+
return result
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def scan_secrets(project_path: str) -> dict[str, Any]:
|
|
194
|
+
project = Path(project_path).resolve()
|
|
195
|
+
result = _new_result("secret_scanner", "[OK] No secrets detected")
|
|
196
|
+
counts = {"critical": 0, "high": 0, "medium": 0, "low": 0}
|
|
197
|
+
|
|
198
|
+
for path in _iter_files(project, CODE_EXTENSIONS | CONFIG_EXTENSIONS):
|
|
199
|
+
result["scanned_files"] += 1
|
|
200
|
+
try:
|
|
201
|
+
content = path.read_text("utf-8", errors="ignore")
|
|
202
|
+
except OSError:
|
|
203
|
+
continue
|
|
204
|
+
for pattern, secret_type, severity in SECRET_PATTERNS:
|
|
205
|
+
for match in re.finditer(pattern, content, re.IGNORECASE):
|
|
206
|
+
matched = match.group(0)
|
|
207
|
+
line = content.count("\n", 0, match.start()) + 1
|
|
208
|
+
source_line = content.splitlines()[line - 1] if content.splitlines() else ""
|
|
209
|
+
if _is_placeholder(matched) or "agkit: allow-secret" in source_line.lower() or "# nosec" in source_line.lower():
|
|
210
|
+
continue
|
|
211
|
+
result["findings"].append({
|
|
212
|
+
"file": _relative(path, project), "line": line, "type": secret_type,
|
|
213
|
+
"severity": severity, "preview": _redact(matched),
|
|
214
|
+
})
|
|
215
|
+
counts[severity] += 1
|
|
216
|
+
|
|
217
|
+
result["by_severity"] = counts
|
|
218
|
+
if counts["critical"]:
|
|
219
|
+
result["status"] = "[!!] CRITICAL: Potential secrets exposed"
|
|
220
|
+
elif counts["high"]:
|
|
221
|
+
result["status"] = "[!] HIGH: Potential secrets found"
|
|
222
|
+
elif sum(counts.values()):
|
|
223
|
+
result["status"] = "[?] Potential secrets need review"
|
|
224
|
+
return result
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
def scan_code_patterns(project_path: str) -> dict[str, Any]:
|
|
228
|
+
project = Path(project_path).resolve()
|
|
229
|
+
result = _new_result("pattern_scanner", "[OK] No dangerous patterns")
|
|
230
|
+
by_category: dict[str, int] = {}
|
|
231
|
+
|
|
232
|
+
for path in _iter_files(project, CODE_EXTENSIONS):
|
|
233
|
+
result["scanned_files"] += 1
|
|
234
|
+
try:
|
|
235
|
+
text = path.read_text("utf-8", errors="ignore")
|
|
236
|
+
except OSError:
|
|
237
|
+
continue
|
|
238
|
+
lines = _python_code_without_strings(text) if path.suffix.lower() == ".py" else _generic_code_without_strings(text)
|
|
239
|
+
original = text.splitlines()
|
|
240
|
+
for line_number, code_line in enumerate(lines, 1):
|
|
241
|
+
for pattern, name, severity, category in DANGEROUS_PATTERNS:
|
|
242
|
+
if re.search(pattern, code_line, re.IGNORECASE):
|
|
243
|
+
snippet = original[line_number - 1].strip()[:120] if line_number <= len(original) else ""
|
|
244
|
+
result["findings"].append({
|
|
245
|
+
"file": _relative(path, project), "line": line_number,
|
|
246
|
+
"pattern": name, "severity": severity, "category": category,
|
|
247
|
+
"snippet": snippet,
|
|
248
|
+
})
|
|
249
|
+
by_category[category] = by_category.get(category, 0) + 1
|
|
250
|
+
result["by_category"] = by_category
|
|
251
|
+
critical = sum(f["severity"] == "critical" for f in result["findings"])
|
|
252
|
+
high = sum(f["severity"] == "high" for f in result["findings"])
|
|
253
|
+
if critical:
|
|
254
|
+
result["status"] = f"[!!] CRITICAL: {critical} dangerous pattern(s)"
|
|
255
|
+
elif high:
|
|
256
|
+
result["status"] = f"[!] HIGH: {high} risky pattern(s)"
|
|
257
|
+
elif result["findings"]:
|
|
258
|
+
result["status"] = "[?] Patterns need review"
|
|
259
|
+
return result
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
def _looks_like_web_project(project: Path) -> bool:
|
|
263
|
+
indicators = (
|
|
264
|
+
"package.json", "next.config.js", "next.config.mjs", "vite.config.ts",
|
|
265
|
+
"vite.config.js", "nuxt.config.ts", "angular.json", "nginx.conf",
|
|
266
|
+
)
|
|
267
|
+
return any((project / name).exists() for name in indicators)
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
def scan_configuration(project_path: str) -> dict[str, Any]:
|
|
271
|
+
project = Path(project_path).resolve()
|
|
272
|
+
result = _new_result("config_scanner", "[OK] Configuration checks passed")
|
|
273
|
+
checks: dict[str, Any] = {}
|
|
274
|
+
patterns = (
|
|
275
|
+
(r'"DEBUG"\s*:\s*true', "Debug mode enabled", "high"),
|
|
276
|
+
(r"\bdebug\s*=\s*True", "Debug mode enabled", "high"),
|
|
277
|
+
(r'"CORS_ALLOW_ALL"\s*:\s*true', "CORS allows every origin", "high"),
|
|
278
|
+
(r'Access-Control-Allow-Origin[^\n]*[\"\']\*[\"\']', "CORS wildcard", "high"),
|
|
279
|
+
(r"allowCredentials[^\n]*true[^\n]*origin[^\n]*\*", "Credentials with wildcard CORS", "critical"),
|
|
280
|
+
)
|
|
281
|
+
|
|
282
|
+
extra_names = {"next.config.js", "next.config.mjs", "next.config.ts", "webpack.config.js", ".eslintrc.js", "nginx.conf"}
|
|
283
|
+
header_files = {"next.config.js", "next.config.mjs", "next.config.ts", "middleware.ts", "middleware.js", "nginx.conf", "vercel.json"}
|
|
284
|
+
header_pattern = re.compile(
|
|
285
|
+
r"Content-Security-Policy|Strict-Transport-Security|X-Frame-Options|frame-ancestors|Referrer-Policy",
|
|
286
|
+
re.IGNORECASE,
|
|
287
|
+
)
|
|
288
|
+
has_security_headers = False
|
|
289
|
+
for root, dirs, files in os.walk(project):
|
|
290
|
+
dirs[:] = [d for d in dirs if d not in SKIP_DIRS]
|
|
291
|
+
for filename in files:
|
|
292
|
+
path = Path(root) / filename
|
|
293
|
+
if path.resolve() == SELF_PATH:
|
|
294
|
+
continue
|
|
295
|
+
if path.suffix.lower() not in CONFIG_EXTENSIONS and filename not in extra_names and not filename.startswith(".env"):
|
|
296
|
+
continue
|
|
297
|
+
result["scanned_files"] += 1
|
|
298
|
+
try:
|
|
299
|
+
content = path.read_text("utf-8", errors="ignore")
|
|
300
|
+
except OSError:
|
|
301
|
+
continue
|
|
302
|
+
if filename in header_files and header_pattern.search(content):
|
|
303
|
+
has_security_headers = True
|
|
304
|
+
for pattern, issue, severity in patterns:
|
|
305
|
+
if re.search(pattern, content, re.IGNORECASE):
|
|
306
|
+
result["findings"].append({"file": _relative(path, project), "issue": issue, "severity": severity})
|
|
307
|
+
|
|
308
|
+
if _looks_like_web_project(project):
|
|
309
|
+
checks["security_headers_config"] = has_security_headers
|
|
310
|
+
if not checks["security_headers_config"]:
|
|
311
|
+
result["findings"].append({
|
|
312
|
+
"issue": "No obvious security-header configuration found",
|
|
313
|
+
"severity": "medium",
|
|
314
|
+
"recommendation": "Configure CSP, HSTS, frame-ancestors/X-Frame-Options, and Referrer-Policy as appropriate.",
|
|
315
|
+
})
|
|
316
|
+
else:
|
|
317
|
+
checks["security_headers_config"] = "not_applicable"
|
|
318
|
+
result["checks"] = checks
|
|
319
|
+
|
|
320
|
+
if any(f["severity"] == "critical" for f in result["findings"]):
|
|
321
|
+
result["status"] = "[!!] CRITICAL: Configuration issues"
|
|
322
|
+
elif any(f["severity"] == "high" for f in result["findings"]):
|
|
323
|
+
result["status"] = "[!] HIGH: Configuration review needed"
|
|
324
|
+
elif result["findings"]:
|
|
325
|
+
result["status"] = "[?] Configuration review recommended"
|
|
326
|
+
return result
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
def run_full_scan(project_path: str, scan_type: str = "all") -> dict[str, Any]:
|
|
330
|
+
project = str(Path(project_path).resolve())
|
|
331
|
+
report: dict[str, Any] = {
|
|
332
|
+
"project": project,
|
|
333
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
|
334
|
+
"scan_type": scan_type,
|
|
335
|
+
"scans": {},
|
|
336
|
+
"summary": {"total_findings": 0, "critical": 0, "high": 0, "medium": 0, "low": 0, "overall_status": "[OK] SECURE"},
|
|
337
|
+
}
|
|
338
|
+
scanners: dict[str, tuple[str, Callable[[str], dict[str, Any]]]] = {
|
|
339
|
+
"deps": ("dependencies", scan_dependencies),
|
|
340
|
+
"secrets": ("secrets", scan_secrets),
|
|
341
|
+
"patterns": ("code_patterns", scan_code_patterns),
|
|
342
|
+
"config": ("configuration", scan_configuration),
|
|
343
|
+
}
|
|
344
|
+
for key, (name, scanner) in scanners.items():
|
|
345
|
+
if scan_type not in {"all", key}:
|
|
346
|
+
continue
|
|
347
|
+
scan_result = scanner(project)
|
|
348
|
+
report["scans"][name] = scan_result
|
|
349
|
+
findings = scan_result.get("findings", [])
|
|
350
|
+
report["summary"]["total_findings"] += len(findings)
|
|
351
|
+
for finding in findings:
|
|
352
|
+
severity = finding.get("severity", "low")
|
|
353
|
+
if severity in {"critical", "high", "medium", "low"}:
|
|
354
|
+
report["summary"][severity] += 1
|
|
355
|
+
|
|
356
|
+
summary = report["summary"]
|
|
357
|
+
if summary["critical"]:
|
|
358
|
+
summary["overall_status"] = "[!!] CRITICAL ISSUES FOUND"
|
|
359
|
+
elif summary["high"]:
|
|
360
|
+
summary["overall_status"] = "[!] HIGH RISK ISSUES"
|
|
361
|
+
elif summary["total_findings"]:
|
|
362
|
+
summary["overall_status"] = "[?] REVIEW RECOMMENDED"
|
|
363
|
+
return report
|
|
364
|
+
|
|
365
|
+
|
|
366
|
+
def _should_fail(report: dict[str, Any], threshold: str) -> bool:
|
|
367
|
+
if threshold == "none":
|
|
368
|
+
return False
|
|
369
|
+
required_rank = SEVERITY_RANK[threshold]
|
|
370
|
+
return any(
|
|
371
|
+
report["summary"].get(severity, 0) > 0 and rank >= required_rank
|
|
372
|
+
for severity, rank in SEVERITY_RANK.items()
|
|
373
|
+
if severity != "none"
|
|
374
|
+
)
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
def main() -> int:
|
|
378
|
+
parser = argparse.ArgumentParser(description="Run lightweight AG Kit security checks")
|
|
379
|
+
parser.add_argument("project_path", nargs="?", default=".", help="Project directory to scan")
|
|
380
|
+
parser.add_argument("--scan-type", choices=["all", "deps", "secrets", "patterns", "config"], default="all")
|
|
381
|
+
parser.add_argument("--output", choices=["json", "summary"], default="json")
|
|
382
|
+
parser.add_argument(
|
|
383
|
+
"--fail-on", choices=["none", "low", "medium", "high", "critical"], default="high",
|
|
384
|
+
help="Return exit code 1 when a finding at or above this severity exists (default: high)",
|
|
385
|
+
)
|
|
386
|
+
args = parser.parse_args()
|
|
387
|
+
project = Path(args.project_path).resolve()
|
|
388
|
+
if not project.is_dir():
|
|
389
|
+
print(json.dumps({"error": f"Directory not found: {project}"}))
|
|
390
|
+
return 2
|
|
391
|
+
|
|
392
|
+
report = run_full_scan(str(project), args.scan_type)
|
|
393
|
+
if args.output == "summary":
|
|
394
|
+
summary = report["summary"]
|
|
395
|
+
print(f"\n{'=' * 60}\nSecurity Scan: {report['project']}\n{'=' * 60}")
|
|
396
|
+
print(f"Status: {summary['overall_status']}")
|
|
397
|
+
print(f"Total Findings: {summary['total_findings']}")
|
|
398
|
+
print(f" Critical: {summary['critical']}\n High: {summary['high']}\n Medium: {summary['medium']}\n Low: {summary['low']}")
|
|
399
|
+
print("=" * 60)
|
|
400
|
+
for name, scan in report["scans"].items():
|
|
401
|
+
print(f"\n{name.upper()}: {scan['status']}")
|
|
402
|
+
for finding in scan.get("findings", [])[:10]:
|
|
403
|
+
print(f" - {finding}")
|
|
404
|
+
else:
|
|
405
|
+
print(json.dumps(report, indent=2, ensure_ascii=False))
|
|
406
|
+
return 1 if _should_fail(report, args.fail_on) else 0
|
|
407
|
+
|
|
408
|
+
|
|
409
|
+
if __name__ == "__main__":
|
|
410
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: web-design-guidelines
|
|
3
|
+
description: Review UI code for Web Interface Guidelines compliance. Use when asked to "review my UI", "check accessibility", "audit design", "review UX", or "check my site against best practices".
|
|
4
|
+
compatibility: opencode
|
|
5
|
+
metadata:
|
|
6
|
+
when_to_use: "\"When auditing web UI for best practices, checking accessibility, or reviewing design against Web Interface Guidelines.\""
|
|
7
|
+
version: "1.0.0"
|
|
8
|
+
---
|
|
9
|
+
# Web Interface Guidelines
|
|
10
|
+
|
|
11
|
+
Review files for compliance with Web Interface Guidelines.
|
|
12
|
+
|
|
13
|
+
## How It Works
|
|
14
|
+
|
|
15
|
+
1. Fetch the latest guidelines from the source URL below
|
|
16
|
+
2. Read the specified files (or prompt user for files/pattern)
|
|
17
|
+
3. Check against all rules in the fetched guidelines
|
|
18
|
+
4. Output findings in the terse `file:line` format
|
|
19
|
+
|
|
20
|
+
## Guidelines Source
|
|
21
|
+
|
|
22
|
+
Fetch fresh guidelines before each review:
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
https://raw.githubusercontent.com/vercel-labs/web-interface-guidelines/main/command.md
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Use WebFetch to retrieve the latest rules. The fetched content contains all the rules and output format instructions.
|
|
29
|
+
|
|
30
|
+
## Usage
|
|
31
|
+
|
|
32
|
+
When a user provides a file or pattern argument:
|
|
33
|
+
1. Fetch guidelines from the source URL above
|
|
34
|
+
2. Read the specified files
|
|
35
|
+
3. Apply all rules from the fetched guidelines
|
|
36
|
+
4. Output findings using the format specified in the guidelines
|
|
37
|
+
|
|
38
|
+
If no files specified, ask the user which files to review.
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## Related Skills
|
|
43
|
+
|
|
44
|
+
| Skill | When to Use |
|
|
45
|
+
|-------|-------------|
|
|
46
|
+
| **[frontend-design](../frontend-design/SKILL.md)** | Before coding - Learn design principles (color, typography, UX psychology) |
|
|
47
|
+
| **web-design-guidelines** (this) | After coding - Audit for accessibility, performance, and best practices |
|
|
48
|
+
|
|
49
|
+
## Design Workflow
|
|
50
|
+
|
|
51
|
+
```
|
|
52
|
+
1. DESIGN → Read frontend-design principles
|
|
53
|
+
2. CODE → Implement the design
|
|
54
|
+
3. AUDIT → Run web-design-guidelines review ← YOU ARE HERE
|
|
55
|
+
4. FIX → Address findings from audit
|
|
56
|
+
```
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: webapp-testing
|
|
3
|
+
description: Web application testing principles. E2E, Playwright, deep audit strategies.
|
|
4
|
+
compatibility: opencode
|
|
5
|
+
metadata:
|
|
6
|
+
when_to_use: "\"When writing E2E tests with Playwright, performing deep web app audits, or testing user flows. Use with /test workflow.\""
|
|
7
|
+
version: "1.0.0"
|
|
8
|
+
---
|
|
9
|
+
# Web App Testing
|
|
10
|
+
|
|
11
|
+
> Discover and test everything. Leave no route untested.
|
|
12
|
+
|
|
13
|
+
## 🔧 Runtime Scripts
|
|
14
|
+
|
|
15
|
+
**Execute these for automated browser testing:**
|
|
16
|
+
|
|
17
|
+
| Script | Purpose | Usage |
|
|
18
|
+
|--------|---------|-------|
|
|
19
|
+
| `.opencode/scripts/playwright_runner.py` | Basic browser test | `python .opencode/scripts/playwright_runner.py https://example.com` |
|
|
20
|
+
| | With screenshot | `python .opencode/scripts/playwright_runner.py <url> --screenshot` |
|
|
21
|
+
| | Accessibility check | `python .opencode/scripts/playwright_runner.py <url> --a11y` |
|
|
22
|
+
|
|
23
|
+
**Requires:** `pip install playwright && playwright install chromium`
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## 1. Deep Audit Approach
|
|
28
|
+
|
|
29
|
+
### Discovery First
|
|
30
|
+
|
|
31
|
+
| Target | How to Find |
|
|
32
|
+
|--------|-------------|
|
|
33
|
+
| Routes | Scan app/, pages/, router files |
|
|
34
|
+
| API endpoints | Grep for HTTP methods |
|
|
35
|
+
| Components | Find component directories |
|
|
36
|
+
| Features | Read documentation |
|
|
37
|
+
|
|
38
|
+
### Systematic Testing
|
|
39
|
+
|
|
40
|
+
1. **Map** - List all routes/APIs
|
|
41
|
+
2. **Scan** - Verify they respond
|
|
42
|
+
3. **Test** - Cover critical paths
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## 2. Testing Pyramid for Web
|
|
47
|
+
|
|
48
|
+
```
|
|
49
|
+
/\ E2E (Few)
|
|
50
|
+
/ \ Critical user flows
|
|
51
|
+
/----\
|
|
52
|
+
/ \ Integration (Some)
|
|
53
|
+
/--------\ API, data flow
|
|
54
|
+
/ \
|
|
55
|
+
/------------\ Component (Many)
|
|
56
|
+
Individual UI pieces
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## 3. E2E Test Principles
|
|
62
|
+
|
|
63
|
+
### What to Test
|
|
64
|
+
|
|
65
|
+
| Priority | Tests |
|
|
66
|
+
|----------|-------|
|
|
67
|
+
| 1 | Happy path user flows |
|
|
68
|
+
| 2 | Authentication flows |
|
|
69
|
+
| 3 | Critical business actions |
|
|
70
|
+
| 4 | Error handling |
|
|
71
|
+
|
|
72
|
+
### E2E Best Practices
|
|
73
|
+
|
|
74
|
+
| Practice | Why |
|
|
75
|
+
|----------|-----|
|
|
76
|
+
| Use data-testid | Stable selectors |
|
|
77
|
+
| Wait for elements | Avoid flaky tests |
|
|
78
|
+
| Clean state | Independent tests |
|
|
79
|
+
| Avoid implementation details | Test user behavior |
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
## 4. Playwright Principles
|
|
84
|
+
|
|
85
|
+
### Core Concepts
|
|
86
|
+
|
|
87
|
+
| Concept | Use |
|
|
88
|
+
|---------|-----|
|
|
89
|
+
| Page Object Model | Encapsulate page logic |
|
|
90
|
+
| Fixtures | Reusable test setup |
|
|
91
|
+
| Assertions | Built-in auto-wait |
|
|
92
|
+
| Trace Viewer | Debug failures |
|
|
93
|
+
|
|
94
|
+
### Configuration
|
|
95
|
+
|
|
96
|
+
| Setting | Recommendation |
|
|
97
|
+
|---------|----------------|
|
|
98
|
+
| Retries | 2 on CI |
|
|
99
|
+
| Trace | on-first-retry |
|
|
100
|
+
| Screenshots | on-failure |
|
|
101
|
+
| Video | retain-on-failure |
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
## 5. Visual Testing
|
|
106
|
+
|
|
107
|
+
### When to Use
|
|
108
|
+
|
|
109
|
+
| Scenario | Value |
|
|
110
|
+
|----------|-------|
|
|
111
|
+
| Design system | High |
|
|
112
|
+
| Marketing pages | High |
|
|
113
|
+
| Component library | Medium |
|
|
114
|
+
| Dynamic content | Lower |
|
|
115
|
+
|
|
116
|
+
### Strategy
|
|
117
|
+
|
|
118
|
+
- Baseline screenshots
|
|
119
|
+
- Compare on changes
|
|
120
|
+
- Review visual diffs
|
|
121
|
+
- Update intentional changes
|
|
122
|
+
|
|
123
|
+
---
|
|
124
|
+
|
|
125
|
+
## 6. API Testing Principles
|
|
126
|
+
|
|
127
|
+
### Coverage Areas
|
|
128
|
+
|
|
129
|
+
| Area | Tests |
|
|
130
|
+
|------|-------|
|
|
131
|
+
| Status codes | 200, 400, 404, 500 |
|
|
132
|
+
| Response shape | Matches schema |
|
|
133
|
+
| Error messages | User-friendly |
|
|
134
|
+
| Edge cases | Empty, large, special chars |
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
## 7. Test Organization
|
|
139
|
+
|
|
140
|
+
### File Structure
|
|
141
|
+
|
|
142
|
+
```
|
|
143
|
+
tests/
|
|
144
|
+
├── e2e/ # Full user flows
|
|
145
|
+
├── integration/ # API, data
|
|
146
|
+
├── component/ # UI units
|
|
147
|
+
└── fixtures/ # Shared data
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### Naming Convention
|
|
151
|
+
|
|
152
|
+
| Pattern | Example |
|
|
153
|
+
|---------|---------|
|
|
154
|
+
| Feature-based | `login.spec.ts` |
|
|
155
|
+
| Descriptive | `user-can-checkout.spec.ts` |
|
|
156
|
+
|
|
157
|
+
---
|
|
158
|
+
|
|
159
|
+
## 8. CI Integration
|
|
160
|
+
|
|
161
|
+
### Pipeline Steps
|
|
162
|
+
|
|
163
|
+
1. Install dependencies
|
|
164
|
+
2. Install browsers
|
|
165
|
+
3. Run tests
|
|
166
|
+
4. Upload artifacts (traces, screenshots)
|
|
167
|
+
|
|
168
|
+
### Parallelization
|
|
169
|
+
|
|
170
|
+
| Strategy | Use |
|
|
171
|
+
|----------|-----|
|
|
172
|
+
| Per file | Playwright default |
|
|
173
|
+
| Sharding | Large suites |
|
|
174
|
+
| Workers | Multiple browsers |
|
|
175
|
+
|
|
176
|
+
---
|
|
177
|
+
|
|
178
|
+
## 9. Anti-Patterns
|
|
179
|
+
|
|
180
|
+
| ❌ Don't | ✅ Do |
|
|
181
|
+
|----------|-------|
|
|
182
|
+
| Test implementation | Test behavior |
|
|
183
|
+
| Hardcode waits | Use auto-wait |
|
|
184
|
+
| Skip cleanup | Isolate tests |
|
|
185
|
+
| Ignore flaky tests | Fix root cause |
|
|
186
|
+
|
|
187
|
+
---
|
|
188
|
+
|
|
189
|
+
> **Remember:** E2E tests are expensive. Use them for critical paths only.
|