@kennethsolomon/shipkit 3.7.0 → 3.9.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/README.md +181 -257
- package/package.json +1 -1
- package/skills/sk:fast-track/SKILL.md +80 -0
- package/skills/sk:gates/SKILL.md +97 -0
- package/skills/sk:retro/SKILL.md +124 -0
- package/skills/sk:reverse-doc/SKILL.md +116 -0
- package/skills/sk:scope-check/SKILL.md +93 -0
- package/skills/sk:setup-claude/SKILL.md +55 -0
- package/skills/sk:setup-claude/scripts/apply_setup_claude.py +207 -6
- package/skills/sk:setup-claude/templates/.claude/agents/e2e-tester.md +46 -0
- package/skills/sk:setup-claude/templates/.claude/agents/linter.md +53 -0
- package/skills/sk:setup-claude/templates/.claude/agents/perf-auditor.md +43 -0
- package/skills/sk:setup-claude/templates/.claude/agents/security-auditor.md +47 -0
- package/skills/sk:setup-claude/templates/.claude/agents/test-runner.md +42 -0
- package/skills/sk:setup-claude/templates/.claude/rules/api.md.template +14 -0
- package/skills/sk:setup-claude/templates/.claude/rules/frontend.md.template +15 -0
- package/skills/sk:setup-claude/templates/.claude/rules/laravel.md.template +15 -0
- package/skills/sk:setup-claude/templates/.claude/rules/react.md.template +14 -0
- package/skills/sk:setup-claude/templates/.claude/rules/tests.md.template +16 -0
- package/skills/sk:setup-claude/templates/.claude/settings.json.template +76 -0
- package/skills/sk:setup-claude/templates/.claude/statusline.sh +50 -0
- package/skills/sk:setup-claude/templates/CLAUDE.md.template +18 -1
- package/skills/sk:setup-claude/templates/hooks/log-agent.sh +24 -0
- package/skills/sk:setup-claude/templates/hooks/pre-compact.sh +44 -0
- package/skills/sk:setup-claude/templates/hooks/session-start.sh +53 -0
- package/skills/sk:setup-claude/templates/hooks/session-stop.sh +33 -0
- package/skills/sk:setup-claude/templates/hooks/validate-commit.sh +81 -0
- package/skills/sk:setup-claude/templates/hooks/validate-push.sh +43 -0
- package/skills/sk:setup-claude/templates/tasks/cross-platform.md.template +31 -0
- package/skills/sk:setup-optimizer/SKILL.md +2 -1
|
@@ -7,12 +7,18 @@ import hashlib
|
|
|
7
7
|
import json
|
|
8
8
|
import os
|
|
9
9
|
import re
|
|
10
|
+
import shutil
|
|
11
|
+
import stat
|
|
10
12
|
import sys
|
|
11
13
|
from dataclasses import asdict, dataclass
|
|
14
|
+
from datetime import datetime, timezone
|
|
12
15
|
from pathlib import Path
|
|
13
16
|
from typing import Dict, Iterable, List, Optional, Tuple
|
|
14
17
|
|
|
15
18
|
|
|
19
|
+
CACHE_MAX_AGE_DAYS = 7
|
|
20
|
+
|
|
21
|
+
|
|
16
22
|
GENERATED_MARKER = "<!-- Generated by /setup-claude -->"
|
|
17
23
|
TEMPLATE_HASH_MARKER = "<!-- Template Hash: "
|
|
18
24
|
TEMPLATE_HASH_END = " -->"
|
|
@@ -59,6 +65,46 @@ def _any_dep_prefix(pkg: dict, prefix: str) -> bool:
|
|
|
59
65
|
return any(k.startswith(prefix) for k in deps.keys())
|
|
60
66
|
|
|
61
67
|
|
|
68
|
+
def _cache_path(repo_root: Path) -> Path:
|
|
69
|
+
return repo_root / ".shipkit" / "config.json"
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _read_cached_detection(repo_root: Path) -> Optional[Detection]:
|
|
73
|
+
"""Return cached Detection if cache exists and is less than CACHE_MAX_AGE_DAYS old."""
|
|
74
|
+
cache = _cache_path(repo_root)
|
|
75
|
+
data = _read_json(cache)
|
|
76
|
+
if data is None:
|
|
77
|
+
return None
|
|
78
|
+
detected_at = data.get("detected_at")
|
|
79
|
+
if not detected_at:
|
|
80
|
+
return None
|
|
81
|
+
try:
|
|
82
|
+
ts = datetime.fromisoformat(detected_at)
|
|
83
|
+
age = datetime.now(timezone.utc) - ts
|
|
84
|
+
if age.days >= CACHE_MAX_AGE_DAYS:
|
|
85
|
+
return None
|
|
86
|
+
except (ValueError, TypeError):
|
|
87
|
+
return None
|
|
88
|
+
det = data.get("detection")
|
|
89
|
+
if not det:
|
|
90
|
+
return None
|
|
91
|
+
try:
|
|
92
|
+
return Detection(**det)
|
|
93
|
+
except (TypeError, KeyError):
|
|
94
|
+
return None
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def _write_cached_detection(repo_root: Path, detection: Detection) -> None:
|
|
98
|
+
"""Persist detection results to .shipkit/config.json with a detected_at timestamp."""
|
|
99
|
+
cache = _cache_path(repo_root)
|
|
100
|
+
cache.parent.mkdir(parents=True, exist_ok=True)
|
|
101
|
+
payload = {
|
|
102
|
+
"detected_at": datetime.now(timezone.utc).isoformat(),
|
|
103
|
+
"detection": asdict(detection),
|
|
104
|
+
}
|
|
105
|
+
cache.write_text(json.dumps(payload, indent=2, sort_keys=True), encoding="utf-8")
|
|
106
|
+
|
|
107
|
+
|
|
62
108
|
def detect(repo_root: Path) -> Detection:
|
|
63
109
|
package_json = _read_json(repo_root / "package.json") or {}
|
|
64
110
|
scripts = (package_json.get("scripts") or {}) if isinstance(package_json, dict) else {}
|
|
@@ -272,6 +318,103 @@ def _plan_file_if_generated(dest: Path, content: str) -> str:
|
|
|
272
318
|
return "skipped" if existing == content else "updated"
|
|
273
319
|
|
|
274
320
|
|
|
321
|
+
def _collect_results(
|
|
322
|
+
results,
|
|
323
|
+
repo_root: Path,
|
|
324
|
+
created: List[str],
|
|
325
|
+
updated: List[str],
|
|
326
|
+
skipped: List[str],
|
|
327
|
+
) -> None:
|
|
328
|
+
"""Categorize deployment results into created/updated/skipped lists."""
|
|
329
|
+
for action, p in results:
|
|
330
|
+
rel = str(p.relative_to(repo_root))
|
|
331
|
+
if action == "created":
|
|
332
|
+
created.append(rel)
|
|
333
|
+
elif action == "updated":
|
|
334
|
+
updated.append(rel)
|
|
335
|
+
else:
|
|
336
|
+
skipped.append(rel)
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
def _make_executable(path: Path) -> None:
|
|
340
|
+
"""Add owner-execute permission to a file."""
|
|
341
|
+
st = path.stat()
|
|
342
|
+
path.chmod(st.st_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
def _deploy_directory(
|
|
346
|
+
src_dir: Path,
|
|
347
|
+
dest_dir: Path,
|
|
348
|
+
*,
|
|
349
|
+
dry_run: bool,
|
|
350
|
+
executable: bool = False,
|
|
351
|
+
filter_fn=None,
|
|
352
|
+
) -> List[tuple]:
|
|
353
|
+
"""Copy files from src_dir to dest_dir. Returns list of (action, relative_path)."""
|
|
354
|
+
results: List[tuple] = []
|
|
355
|
+
if not src_dir.exists():
|
|
356
|
+
return results
|
|
357
|
+
for src_file in sorted(src_dir.iterdir()):
|
|
358
|
+
if not src_file.is_file():
|
|
359
|
+
continue
|
|
360
|
+
if filter_fn and not filter_fn(src_file.name):
|
|
361
|
+
continue
|
|
362
|
+
dest_file = dest_dir / src_file.name
|
|
363
|
+
if dest_file.exists():
|
|
364
|
+
results.append(("skipped", dest_file))
|
|
365
|
+
elif dry_run:
|
|
366
|
+
results.append(("created", dest_file))
|
|
367
|
+
else:
|
|
368
|
+
dest_dir.mkdir(parents=True, exist_ok=True)
|
|
369
|
+
shutil.copy2(src_file, dest_file)
|
|
370
|
+
if executable:
|
|
371
|
+
_make_executable(dest_file)
|
|
372
|
+
results.append(("created", dest_file))
|
|
373
|
+
return results
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
def _deploy_rendered_file(
|
|
377
|
+
template_path: Path,
|
|
378
|
+
dest: Path,
|
|
379
|
+
detection: Detection,
|
|
380
|
+
*,
|
|
381
|
+
dry_run: bool,
|
|
382
|
+
executable: bool = False,
|
|
383
|
+
) -> tuple:
|
|
384
|
+
"""Render a template and write to dest. Returns (action, path)."""
|
|
385
|
+
if not template_path.exists():
|
|
386
|
+
return ("skipped", dest)
|
|
387
|
+
template_text = template_path.read_text(encoding="utf-8")
|
|
388
|
+
rendered = render_template(template_text, detection)
|
|
389
|
+
if dest.exists():
|
|
390
|
+
return ("skipped", dest)
|
|
391
|
+
if dry_run:
|
|
392
|
+
return ("created", dest)
|
|
393
|
+
dest.parent.mkdir(parents=True, exist_ok=True)
|
|
394
|
+
dest.write_text(rendered, encoding="utf-8")
|
|
395
|
+
if executable:
|
|
396
|
+
_make_executable(dest)
|
|
397
|
+
return ("created", dest)
|
|
398
|
+
|
|
399
|
+
|
|
400
|
+
def _rules_filter(detection: Detection):
|
|
401
|
+
"""Return a filter function that selects rules relevant to the detected stack."""
|
|
402
|
+
always = {"tests.md.template", "frontend.md.template", "api.md.template"}
|
|
403
|
+
|
|
404
|
+
def _filter(filename: str) -> bool:
|
|
405
|
+
if filename in always:
|
|
406
|
+
return True
|
|
407
|
+
if filename == "laravel.md.template" and "Laravel" in detection.framework:
|
|
408
|
+
return True
|
|
409
|
+
if filename == "react.md.template" and (
|
|
410
|
+
"React" in detection.framework or detection.framework == "Next.js (App Router)"
|
|
411
|
+
):
|
|
412
|
+
return True
|
|
413
|
+
return False
|
|
414
|
+
|
|
415
|
+
return _filter
|
|
416
|
+
|
|
417
|
+
|
|
275
418
|
def apply(
|
|
276
419
|
repo_root: Path,
|
|
277
420
|
skill_root: Path,
|
|
@@ -300,6 +443,7 @@ def apply(
|
|
|
300
443
|
add("templates/tasks/lessons.md.template", "tasks/lessons.md", "missing")
|
|
301
444
|
add("templates/tasks/security-findings.md.template", "tasks/security-findings.md", "missing")
|
|
302
445
|
add("templates/tasks/workflow-status.md.template", "tasks/workflow-status.md", "missing")
|
|
446
|
+
add("templates/tasks/cross-platform.md.template", "tasks/cross-platform.md", "missing")
|
|
303
447
|
|
|
304
448
|
# commands (update if generated)
|
|
305
449
|
add("templates/commands/brainstorm.md.template", ".claude/commands/brainstorm.md", "generated")
|
|
@@ -370,12 +514,54 @@ def apply(
|
|
|
370
514
|
else:
|
|
371
515
|
action, p = ("skipped", dest_path)
|
|
372
516
|
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
517
|
+
_collect_results([(action, p)], repo_root, created, updated, skipped)
|
|
518
|
+
|
|
519
|
+
# --- Deploy hooks ---
|
|
520
|
+
hooks_src = skill_root / "templates" / "hooks"
|
|
521
|
+
hooks_dest = repo_root / ".claude" / "hooks"
|
|
522
|
+
_collect_results(
|
|
523
|
+
_deploy_directory(hooks_src, hooks_dest, dry_run=dry_run, executable=True),
|
|
524
|
+
repo_root, created, updated, skipped,
|
|
525
|
+
)
|
|
526
|
+
|
|
527
|
+
# --- Deploy agents ---
|
|
528
|
+
agents_src = skill_root / "templates" / ".claude" / "agents"
|
|
529
|
+
agents_dest = repo_root / ".claude" / "agents"
|
|
530
|
+
_collect_results(
|
|
531
|
+
_deploy_directory(agents_src, agents_dest, dry_run=dry_run),
|
|
532
|
+
repo_root, created, updated, skipped,
|
|
533
|
+
)
|
|
534
|
+
|
|
535
|
+
# --- Deploy rules (stack-filtered) ---
|
|
536
|
+
rules_src = skill_root / "templates" / ".claude" / "rules"
|
|
537
|
+
rules_dest = repo_root / ".claude" / "rules"
|
|
538
|
+
_collect_results(
|
|
539
|
+
_deploy_directory(rules_src, rules_dest, dry_run=dry_run, filter_fn=_rules_filter(detection)),
|
|
540
|
+
repo_root, created, updated, skipped,
|
|
541
|
+
)
|
|
542
|
+
|
|
543
|
+
# --- Deploy settings.json ---
|
|
544
|
+
settings_template = skill_root / "templates" / ".claude" / "settings.json.template"
|
|
545
|
+
settings_dest = repo_root / ".claude" / "settings.json"
|
|
546
|
+
_collect_results(
|
|
547
|
+
[_deploy_rendered_file(settings_template, settings_dest, detection, dry_run=dry_run)],
|
|
548
|
+
repo_root, created, updated, skipped,
|
|
549
|
+
)
|
|
550
|
+
|
|
551
|
+
# --- Deploy statusline.sh ---
|
|
552
|
+
statusline_src = skill_root / "templates" / ".claude" / "statusline.sh"
|
|
553
|
+
statusline_dest = repo_root / ".claude" / "statusline.sh"
|
|
554
|
+
if statusline_src.exists():
|
|
555
|
+
if statusline_dest.exists():
|
|
556
|
+
action_sl = "skipped"
|
|
557
|
+
elif dry_run:
|
|
558
|
+
action_sl = "created"
|
|
377
559
|
else:
|
|
378
|
-
|
|
560
|
+
statusline_dest.parent.mkdir(parents=True, exist_ok=True)
|
|
561
|
+
shutil.copy2(statusline_src, statusline_dest)
|
|
562
|
+
_make_executable(statusline_dest)
|
|
563
|
+
action_sl = "created"
|
|
564
|
+
_collect_results([(action_sl, statusline_dest)], repo_root, created, updated, skipped)
|
|
379
565
|
|
|
380
566
|
if dry_run:
|
|
381
567
|
print("setup-claude dry-run complete (no files written)")
|
|
@@ -415,6 +601,11 @@ def main(argv: List[str]) -> int:
|
|
|
415
601
|
action="store_true",
|
|
416
602
|
help="Print detected values as JSON and exit unless combined with --dry-run.",
|
|
417
603
|
)
|
|
604
|
+
parser.add_argument(
|
|
605
|
+
"--force-detect",
|
|
606
|
+
action="store_true",
|
|
607
|
+
help="Bypass cached detection and re-run stack detection from scratch.",
|
|
608
|
+
)
|
|
418
609
|
args = parser.parse_args(argv[1:])
|
|
419
610
|
|
|
420
611
|
repo_root = Path(args.repo_root).resolve()
|
|
@@ -424,7 +615,17 @@ def main(argv: List[str]) -> int:
|
|
|
424
615
|
print(f"Repo root not found: {repo_root}", file=sys.stderr)
|
|
425
616
|
return 2
|
|
426
617
|
|
|
427
|
-
detection
|
|
618
|
+
# Use cached detection unless --force-detect is set
|
|
619
|
+
detection = None
|
|
620
|
+
if not args.force_detect:
|
|
621
|
+
detection = _read_cached_detection(repo_root)
|
|
622
|
+
if detection:
|
|
623
|
+
print("Using cached detection (< 7 days old). Pass --force-detect to re-run.")
|
|
624
|
+
if detection is None:
|
|
625
|
+
detection = detect(repo_root)
|
|
626
|
+
if not args.dry_run:
|
|
627
|
+
_write_cached_detection(repo_root, detection)
|
|
628
|
+
|
|
428
629
|
if args.print_detection:
|
|
429
630
|
print(json.dumps(asdict(detection), indent=2, sort_keys=True))
|
|
430
631
|
if not args.dry_run:
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: e2e-tester
|
|
3
|
+
model: sonnet
|
|
4
|
+
description: Run E2E behavioral verification using Playwright CLI or agent-browser. Fix failures and auto-commit.
|
|
5
|
+
allowed_tools: Bash, Read, Edit, Write, Glob, Grep
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# E2E Tester Agent
|
|
9
|
+
|
|
10
|
+
You are a specialized E2E testing agent. Your job is to verify the complete implementation works end-to-end from a user's perspective.
|
|
11
|
+
|
|
12
|
+
## Behavior
|
|
13
|
+
|
|
14
|
+
1. **Detect E2E framework**:
|
|
15
|
+
- If `playwright.config.ts` exists -> use Playwright CLI
|
|
16
|
+
- If `cypress.config.ts` exists -> use Cypress
|
|
17
|
+
- If `tests/verify-workflow.sh` exists -> use bash test suite
|
|
18
|
+
- Otherwise -> report no E2E framework detected
|
|
19
|
+
|
|
20
|
+
2. **Run E2E tests**:
|
|
21
|
+
- Playwright: `npx playwright test --reporter=list`
|
|
22
|
+
- Cypress: `npx cypress run`
|
|
23
|
+
- Bash: `bash tests/verify-workflow.sh`
|
|
24
|
+
|
|
25
|
+
3. **If tests fail**:
|
|
26
|
+
- Analyze failure output and screenshots (if Playwright)
|
|
27
|
+
- Determine if failure is in test or implementation
|
|
28
|
+
- Fix the root cause
|
|
29
|
+
- Stage: `git add <files>`
|
|
30
|
+
- auto-commit: `fix(e2e): resolve failing E2E scenarios`
|
|
31
|
+
- Re-run from scratch
|
|
32
|
+
- Loop until all pass
|
|
33
|
+
|
|
34
|
+
4. **Pre-existing failures** (tests that were already failing before this branch):
|
|
35
|
+
- Log to `tasks/tech-debt.md`:
|
|
36
|
+
```
|
|
37
|
+
### [YYYY-MM-DD] Found during: sk:e2e
|
|
38
|
+
File: path/to/test.ext
|
|
39
|
+
Issue: Pre-existing E2E failure — [description]
|
|
40
|
+
Severity: medium
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
5. **Report** when passing:
|
|
44
|
+
```
|
|
45
|
+
E2E: [N] scenarios passed, 0 failed (attempt [M])
|
|
46
|
+
```
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: linter
|
|
3
|
+
model: haiku
|
|
4
|
+
description: Run all project linters and dependency audits. Auto-fix issues, auto-commit fixes, and re-run until clean.
|
|
5
|
+
allowed_tools: Bash, Read, Edit, Write, Glob, Grep
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Linter Agent
|
|
9
|
+
|
|
10
|
+
You are a specialized linting agent. Your job is to run all detected linters and dependency audits, fix any issues found, and loop until everything passes clean.
|
|
11
|
+
|
|
12
|
+
## Behavior
|
|
13
|
+
|
|
14
|
+
1. **Detect linters**: Check for project linting tools:
|
|
15
|
+
- PHP: `vendor/bin/pint`, `vendor/bin/phpstan`, `vendor/bin/rector`
|
|
16
|
+
- JS/TS: `npx eslint`, `npx prettier`, eslint in package.json scripts
|
|
17
|
+
- Python: `ruff`, `black`, `flake8`, `mypy`
|
|
18
|
+
- Go: `gofmt`, `golangci-lint`
|
|
19
|
+
- Rust: `cargo fmt`, `cargo clippy`
|
|
20
|
+
- General: `npm run lint`, `composer lint` from package.json/composer.json scripts
|
|
21
|
+
|
|
22
|
+
2. **Detect dependency audits**: `npm audit`, `composer audit`, `pip-audit`, `cargo audit`
|
|
23
|
+
|
|
24
|
+
3. **Run formatters first** (sequential — order matters):
|
|
25
|
+
- Prettier/Pint/Black/gofmt/cargo fmt
|
|
26
|
+
|
|
27
|
+
4. **Run analyzers** (parallel where possible):
|
|
28
|
+
- ESLint/PHPStan/Rector/Ruff/Clippy
|
|
29
|
+
|
|
30
|
+
5. **Run dependency audits**
|
|
31
|
+
|
|
32
|
+
6. **Fix loop**: For each issue found:
|
|
33
|
+
- Fix the issue
|
|
34
|
+
- Stage the fix: `git add <files>`
|
|
35
|
+
- auto-commit with message: `fix(lint): resolve lint and dep audit issues`
|
|
36
|
+
- Re-run ALL linters from scratch
|
|
37
|
+
- Loop until clean — do not stop after one pass
|
|
38
|
+
|
|
39
|
+
7. **Pre-existing issues**: If an issue exists in a file NOT in `git diff main..HEAD --name-only`:
|
|
40
|
+
- Log to `tasks/tech-debt.md` using format:
|
|
41
|
+
```
|
|
42
|
+
### [YYYY-MM-DD] Found during: sk:lint
|
|
43
|
+
File: path/to/file.ext:line
|
|
44
|
+
Issue: description
|
|
45
|
+
Severity: low
|
|
46
|
+
```
|
|
47
|
+
- Do NOT fix it — it's out of scope
|
|
48
|
+
|
|
49
|
+
8. **Report** when clean:
|
|
50
|
+
```
|
|
51
|
+
Lint: clean (attempt N)
|
|
52
|
+
Dep audit: 0 vulnerabilities
|
|
53
|
+
```
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: perf-auditor
|
|
3
|
+
model: sonnet
|
|
4
|
+
description: Audit changed code for performance issues including bundle size, N+1 queries, Core Web Vitals, and memory leaks.
|
|
5
|
+
allowed_tools: Bash, Read, Edit, Write, Glob, Grep
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Performance Auditor Agent
|
|
9
|
+
|
|
10
|
+
You are a specialized performance audit agent. Your job is to review changed code for performance issues and fix critical/high findings.
|
|
11
|
+
|
|
12
|
+
## Behavior
|
|
13
|
+
|
|
14
|
+
1. **Identify changed files**: `git diff main..HEAD --name-only`
|
|
15
|
+
|
|
16
|
+
2. **Audit categories** (check what's applicable based on file types):
|
|
17
|
+
- **N+1 queries**: Eloquent/ORM queries inside loops, missing eager loading
|
|
18
|
+
- **Bundle size**: Importing entire libraries when only a function is needed
|
|
19
|
+
- **Memory**: Unbounded arrays, missing cleanup in effects/listeners, leaked subscriptions
|
|
20
|
+
- **Core Web Vitals**: Layout shifts (missing width/height on images), blocking scripts, large DOM
|
|
21
|
+
- **Database**: Missing indexes on filtered/sorted columns, SELECT * instead of specific columns
|
|
22
|
+
- **Caching**: Repeated expensive computations that could be memoized or cached
|
|
23
|
+
- **Rendering**: Unnecessary re-renders, missing React.memo/useMemo where profiling shows need
|
|
24
|
+
|
|
25
|
+
3. **Classify findings**: critical, high, medium, low
|
|
26
|
+
|
|
27
|
+
4. **Fix critical/high** in-scope findings:
|
|
28
|
+
- Fix the issue
|
|
29
|
+
- Stage: `git add <files>`
|
|
30
|
+
- auto-commit: `fix(perf): resolve [severity] performance issue`
|
|
31
|
+
- Re-run audit
|
|
32
|
+
|
|
33
|
+
5. **Medium/low** findings: Log only, do not fix
|
|
34
|
+
|
|
35
|
+
6. **Pre-existing issues**: Log to `tasks/tech-debt.md`
|
|
36
|
+
|
|
37
|
+
7. **Generate report**: Write findings to `tasks/perf-findings.md`
|
|
38
|
+
|
|
39
|
+
8. **Report** when clean:
|
|
40
|
+
```
|
|
41
|
+
Performance: 0 critical/high findings (attempt [N])
|
|
42
|
+
Audited: [M] files
|
|
43
|
+
```
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: security-auditor
|
|
3
|
+
model: sonnet
|
|
4
|
+
description: Audit changed code for OWASP Top 10 and security best practices. Fix findings and auto-commit.
|
|
5
|
+
allowed_tools: Bash, Read, Edit, Write, Glob, Grep
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Security Auditor Agent
|
|
9
|
+
|
|
10
|
+
You are a specialized security audit agent. Your job is to review all changed code for security vulnerabilities following OWASP Top 10 and industry best practices.
|
|
11
|
+
|
|
12
|
+
## Behavior
|
|
13
|
+
|
|
14
|
+
1. **Identify changed files**: `git diff main..HEAD --name-only`
|
|
15
|
+
|
|
16
|
+
2. **Read each changed file** and audit for:
|
|
17
|
+
- **Injection** (SQL, command, XSS, template): User input used in queries/commands without sanitization
|
|
18
|
+
- **Broken auth**: Hardcoded credentials, missing auth checks, weak token generation
|
|
19
|
+
- **Sensitive data exposure**: Secrets in code, missing encryption, verbose error messages
|
|
20
|
+
- **Broken access control**: Missing authorization checks, IDOR vulnerabilities
|
|
21
|
+
- **Security misconfiguration**: Debug mode in production, permissive CORS, missing security headers
|
|
22
|
+
- **Vulnerable dependencies**: Known CVEs in dependencies (check with `npm audit`, `composer audit`, etc.)
|
|
23
|
+
- **Input validation**: Missing or insufficient validation at system boundaries
|
|
24
|
+
|
|
25
|
+
3. **For each finding**:
|
|
26
|
+
- Classify severity: critical, high, medium, low
|
|
27
|
+
- If in scope (file in branch diff): Fix immediately
|
|
28
|
+
- Stage fix: `git add <files>`
|
|
29
|
+
- auto-commit: `fix(security): resolve [severity] [type] finding`
|
|
30
|
+
- Re-run audit on fixed files
|
|
31
|
+
|
|
32
|
+
4. **Pre-existing issues** (file NOT in branch diff):
|
|
33
|
+
- Log to `tasks/tech-debt.md`:
|
|
34
|
+
```
|
|
35
|
+
### [YYYY-MM-DD] Found during: sk:security-check
|
|
36
|
+
File: path/to/file.ext:line
|
|
37
|
+
Issue: [OWASP category] — description
|
|
38
|
+
Severity: [critical|high|medium|low]
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
5. **Generate report**: Append findings to `tasks/security-findings.md`
|
|
42
|
+
|
|
43
|
+
6. **Report** when clean:
|
|
44
|
+
```
|
|
45
|
+
Security: 0 findings (attempt [N])
|
|
46
|
+
Audited: [M] files
|
|
47
|
+
```
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: test-runner
|
|
3
|
+
model: sonnet
|
|
4
|
+
description: Run all project test suites, fix failures, ensure 100% coverage on new code.
|
|
5
|
+
allowed_tools: Bash, Read, Edit, Write, Glob, Grep
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Test Runner Agent
|
|
9
|
+
|
|
10
|
+
You are a specialized testing agent. Your job is to run all detected test suites, fix failing tests, and ensure 100% coverage on new code.
|
|
11
|
+
|
|
12
|
+
## Behavior
|
|
13
|
+
|
|
14
|
+
1. **Detect test frameworks**:
|
|
15
|
+
- PHP: `vendor/bin/pest`, `vendor/bin/phpunit`
|
|
16
|
+
- JS/TS: `npx vitest`, `npx jest`, `npm test`
|
|
17
|
+
- Python: `pytest`, `python -m unittest`
|
|
18
|
+
- Go: `go test ./...`
|
|
19
|
+
- Rust: `cargo test`
|
|
20
|
+
- Bash: `bash tests/verify-workflow.sh`
|
|
21
|
+
|
|
22
|
+
2. **Run all detected suites**
|
|
23
|
+
|
|
24
|
+
3. **If tests fail**:
|
|
25
|
+
- Analyze the failure output
|
|
26
|
+
- Fix the root cause (not just the test — fix the implementation if it's wrong)
|
|
27
|
+
- Stage fixes: `git add <files>`
|
|
28
|
+
- auto-commit: `fix(test): resolve failing tests`
|
|
29
|
+
- Re-run the failing suite
|
|
30
|
+
- Loop until all pass
|
|
31
|
+
|
|
32
|
+
4. **Coverage check**: If the test framework supports coverage:
|
|
33
|
+
- Run with coverage enabled
|
|
34
|
+
- Check that new code (files in `git diff main..HEAD --name-only`) has 100% coverage
|
|
35
|
+
- If coverage gaps exist, write additional tests
|
|
36
|
+
- auto-commit: `fix(test): add missing test coverage`
|
|
37
|
+
|
|
38
|
+
5. **Report** when passing:
|
|
39
|
+
```
|
|
40
|
+
Tests: [N] passed, 0 failed (attempt [M])
|
|
41
|
+
Coverage: 100% on new code
|
|
42
|
+
```
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
<!-- Generated by /setup-claude -->
|
|
2
|
+
# API Standards
|
|
3
|
+
|
|
4
|
+
Applies to: `routes/api/`, `app/Http/Controllers/Api/`, `src/api/`, `src/routes/`
|
|
5
|
+
|
|
6
|
+
## Conventions
|
|
7
|
+
|
|
8
|
+
- **Validation**: Validate all input at the boundary. Use form requests, schemas, or middleware — never trust raw input.
|
|
9
|
+
- **Error responses**: Return structured JSON errors with appropriate HTTP status codes. Include enough context to debug.
|
|
10
|
+
- **Authentication**: Every endpoint must explicitly declare its auth requirement (public, authenticated, admin).
|
|
11
|
+
- **Rate limiting**: Apply rate limits to public and authentication endpoints.
|
|
12
|
+
- **Versioning**: Use URL or header versioning for breaking changes.
|
|
13
|
+
- **Response shape**: Consistent response envelope — `{ data, meta, errors }` or framework convention.
|
|
14
|
+
- **Idempotency**: POST/PUT/PATCH operations should be idempotent where possible.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
<!-- Generated by /setup-claude -->
|
|
2
|
+
# Frontend Standards
|
|
3
|
+
|
|
4
|
+
Applies to: `resources/`, `src/components/`, `app/components/`, `src/pages/`, `src/views/`
|
|
5
|
+
|
|
6
|
+
## Conventions
|
|
7
|
+
|
|
8
|
+
- **Component structure**: One component per file. Name matches filename.
|
|
9
|
+
- **Props**: Type all props explicitly. No `any` types.
|
|
10
|
+
- **State**: Keep state as close to where it's used as possible. Lift only when necessary.
|
|
11
|
+
- **Side effects**: Isolate side effects in hooks/composables. Keep render functions pure.
|
|
12
|
+
- **Accessibility**: All interactive elements must be keyboard accessible. Use semantic HTML. Include ARIA labels where needed.
|
|
13
|
+
- **Loading states**: Handle loading, error, and empty states for every data-dependent component.
|
|
14
|
+
- **Event handlers**: Name handlers descriptively (`handleSubmitForm`, not `onClick`).
|
|
15
|
+
- **CSS**: Use utility classes or scoped styles. No global style modifications from components.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
<!-- Generated by /setup-claude -->
|
|
2
|
+
# Laravel Standards
|
|
3
|
+
|
|
4
|
+
Applies to: `app/`, `routes/`, `database/`, `config/`
|
|
5
|
+
|
|
6
|
+
## Conventions
|
|
7
|
+
|
|
8
|
+
- **Eloquent**: Use query scopes for reusable queries. Avoid raw SQL unless necessary for performance.
|
|
9
|
+
- **N+1**: Always eager-load relationships. Use `->with()` or `->load()`.
|
|
10
|
+
- **Form Requests**: Validate in Form Request classes, not in controllers.
|
|
11
|
+
- **Service Layer**: Business logic belongs in services, not controllers or models.
|
|
12
|
+
- **Resources**: Use API Resources for response transformation.
|
|
13
|
+
- **Migrations**: One logical change per migration. Never modify a published migration.
|
|
14
|
+
- **Config**: Access config via `config()` helper, never `env()` outside config files.
|
|
15
|
+
- **Strict mode**: Models use strict mode (prevent lazy loading, silently discarding attributes, accessing missing attributes).
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
<!-- Generated by /setup-claude -->
|
|
2
|
+
# React Standards
|
|
3
|
+
|
|
4
|
+
Applies to: `src/components/`, `src/hooks/`, `src/pages/`, `app/components/`
|
|
5
|
+
|
|
6
|
+
## Conventions
|
|
7
|
+
|
|
8
|
+
- **Hooks**: Follow Rules of Hooks. Custom hooks start with `use`. Extract complex logic into custom hooks.
|
|
9
|
+
- **Components**: Prefer function components. Use `React.memo()` only when profiling shows a need.
|
|
10
|
+
- **State**: Use `useState` for local state, context for shared state, external stores (Zustand/Redux) for complex state.
|
|
11
|
+
- **Effects**: Minimize `useEffect`. Prefer derived state and event handlers. Always specify dependency arrays.
|
|
12
|
+
- **Keys**: Use stable, unique keys for lists. Never use array index as key for dynamic lists.
|
|
13
|
+
- **Error boundaries**: Wrap route-level components in error boundaries.
|
|
14
|
+
- **TypeScript**: Type props interfaces, not inline. Export prop types for reusable components.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<!-- Generated by /setup-claude -->
|
|
2
|
+
# Testing Standards
|
|
3
|
+
|
|
4
|
+
Applies to: `tests/`, `test/`, `__tests__/`, `spec/`
|
|
5
|
+
|
|
6
|
+
## Conventions
|
|
7
|
+
|
|
8
|
+
- **Naming**: `test_[system]_[scenario]_[expected_result]` or `describe > it` blocks with descriptive names
|
|
9
|
+
- **Structure**: Arrange / Act / Assert — every test must clearly separate setup, execution, and verification
|
|
10
|
+
- **Independence**: Unit tests must not depend on external state (filesystem, network, database)
|
|
11
|
+
- **Cleanup**: Integration tests must clean up artifacts after execution
|
|
12
|
+
- **Coverage**: All new code requires test coverage. Target 100% coverage on new code paths.
|
|
13
|
+
- **Regression**: Every bug fix requires a regression test that would have caught the original defect
|
|
14
|
+
- **Fixtures**: Test data belongs in the test itself or dedicated fixtures — never shared mutable state
|
|
15
|
+
- **Mocking**: Mock external dependencies, not the code under test. Test behavior, not implementation.
|
|
16
|
+
- **Performance**: Tests should run fast. Mock slow dependencies (network, disk, database) in unit tests.
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json.schemastore.org/claude-code-settings.json",
|
|
3
|
+
"statusline": {
|
|
4
|
+
"command": "bash .claude/statusline.sh"
|
|
5
|
+
},
|
|
6
|
+
"permissions": {
|
|
7
|
+
"allow": [
|
|
8
|
+
"Bash(git status*)",
|
|
9
|
+
"Bash(git diff*)",
|
|
10
|
+
"Bash(git log*)",
|
|
11
|
+
"Bash(git branch*)",
|
|
12
|
+
"Bash(git rev-parse*)",
|
|
13
|
+
"Bash(ls*)",
|
|
14
|
+
"Bash(cat package.json)",
|
|
15
|
+
"Bash(cat composer.json)"
|
|
16
|
+
],
|
|
17
|
+
"deny": [
|
|
18
|
+
"Bash(rm -rf*)",
|
|
19
|
+
"Bash(git push --force*)",
|
|
20
|
+
"Bash(git reset --hard*)",
|
|
21
|
+
"Bash(sudo *)",
|
|
22
|
+
"Bash(chmod -R 777*)",
|
|
23
|
+
"Bash(cat .env*)"
|
|
24
|
+
]
|
|
25
|
+
},
|
|
26
|
+
"hooks": {
|
|
27
|
+
"SessionStart": [
|
|
28
|
+
{
|
|
29
|
+
"type": "command",
|
|
30
|
+
"command": "bash .claude/hooks/session-start.sh",
|
|
31
|
+
"timeout": 10000
|
|
32
|
+
}
|
|
33
|
+
],
|
|
34
|
+
"PreCompact": [
|
|
35
|
+
{
|
|
36
|
+
"type": "command",
|
|
37
|
+
"command": "bash .claude/hooks/pre-compact.sh",
|
|
38
|
+
"timeout": 10000
|
|
39
|
+
}
|
|
40
|
+
],
|
|
41
|
+
"PreToolUse": [
|
|
42
|
+
{
|
|
43
|
+
"type": "command",
|
|
44
|
+
"command": "bash .claude/hooks/validate-commit.sh",
|
|
45
|
+
"timeout": 10000,
|
|
46
|
+
"matcher": {
|
|
47
|
+
"tool_name": "Bash",
|
|
48
|
+
"command_pattern": "git commit*"
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
"type": "command",
|
|
53
|
+
"command": "bash .claude/hooks/validate-push.sh",
|
|
54
|
+
"timeout": 5000,
|
|
55
|
+
"matcher": {
|
|
56
|
+
"tool_name": "Bash",
|
|
57
|
+
"command_pattern": "git push*"
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
],
|
|
61
|
+
"SubagentStart": [
|
|
62
|
+
{
|
|
63
|
+
"type": "command",
|
|
64
|
+
"command": "bash .claude/hooks/log-agent.sh",
|
|
65
|
+
"timeout": 5000
|
|
66
|
+
}
|
|
67
|
+
],
|
|
68
|
+
"Stop": [
|
|
69
|
+
{
|
|
70
|
+
"type": "command",
|
|
71
|
+
"command": "bash .claude/hooks/session-stop.sh",
|
|
72
|
+
"timeout": 10000
|
|
73
|
+
}
|
|
74
|
+
]
|
|
75
|
+
}
|
|
76
|
+
}
|