@kennethsolomon/shipkit 3.7.0 → 3.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (27) hide show
  1. package/README.md +5 -0
  2. package/package.json +1 -1
  3. package/skills/sk:fast-track/SKILL.md +80 -0
  4. package/skills/sk:gates/SKILL.md +97 -0
  5. package/skills/sk:retro/SKILL.md +124 -0
  6. package/skills/sk:reverse-doc/SKILL.md +116 -0
  7. package/skills/sk:scope-check/SKILL.md +93 -0
  8. package/skills/sk:setup-claude/SKILL.md +53 -0
  9. package/skills/sk:setup-claude/scripts/apply_setup_claude.py +206 -6
  10. package/skills/sk:setup-claude/templates/.claude/agents/e2e-tester.md +46 -0
  11. package/skills/sk:setup-claude/templates/.claude/agents/linter.md +53 -0
  12. package/skills/sk:setup-claude/templates/.claude/agents/perf-auditor.md +43 -0
  13. package/skills/sk:setup-claude/templates/.claude/agents/security-auditor.md +47 -0
  14. package/skills/sk:setup-claude/templates/.claude/agents/test-runner.md +42 -0
  15. package/skills/sk:setup-claude/templates/.claude/rules/api.md.template +14 -0
  16. package/skills/sk:setup-claude/templates/.claude/rules/frontend.md.template +15 -0
  17. package/skills/sk:setup-claude/templates/.claude/rules/laravel.md.template +15 -0
  18. package/skills/sk:setup-claude/templates/.claude/rules/react.md.template +14 -0
  19. package/skills/sk:setup-claude/templates/.claude/rules/tests.md.template +16 -0
  20. package/skills/sk:setup-claude/templates/.claude/settings.json.template +76 -0
  21. package/skills/sk:setup-claude/templates/.claude/statusline.sh +50 -0
  22. package/skills/sk:setup-claude/templates/hooks/log-agent.sh +24 -0
  23. package/skills/sk:setup-claude/templates/hooks/pre-compact.sh +44 -0
  24. package/skills/sk:setup-claude/templates/hooks/session-start.sh +53 -0
  25. package/skills/sk:setup-claude/templates/hooks/session-stop.sh +33 -0
  26. package/skills/sk:setup-claude/templates/hooks/validate-commit.sh +81 -0
  27. package/skills/sk:setup-claude/templates/hooks/validate-push.sh +43 -0
@@ -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,
@@ -370,12 +513,54 @@ def apply(
370
513
  else:
371
514
  action, p = ("skipped", dest_path)
372
515
 
373
- if action == "created":
374
- created.append(str(p.relative_to(repo_root)))
375
- elif action == "updated":
376
- updated.append(str(p.relative_to(repo_root)))
516
+ _collect_results([(action, p)], repo_root, created, updated, skipped)
517
+
518
+ # --- Deploy hooks ---
519
+ hooks_src = skill_root / "templates" / "hooks"
520
+ hooks_dest = repo_root / ".claude" / "hooks"
521
+ _collect_results(
522
+ _deploy_directory(hooks_src, hooks_dest, dry_run=dry_run, executable=True),
523
+ repo_root, created, updated, skipped,
524
+ )
525
+
526
+ # --- Deploy agents ---
527
+ agents_src = skill_root / "templates" / ".claude" / "agents"
528
+ agents_dest = repo_root / ".claude" / "agents"
529
+ _collect_results(
530
+ _deploy_directory(agents_src, agents_dest, dry_run=dry_run),
531
+ repo_root, created, updated, skipped,
532
+ )
533
+
534
+ # --- Deploy rules (stack-filtered) ---
535
+ rules_src = skill_root / "templates" / ".claude" / "rules"
536
+ rules_dest = repo_root / ".claude" / "rules"
537
+ _collect_results(
538
+ _deploy_directory(rules_src, rules_dest, dry_run=dry_run, filter_fn=_rules_filter(detection)),
539
+ repo_root, created, updated, skipped,
540
+ )
541
+
542
+ # --- Deploy settings.json ---
543
+ settings_template = skill_root / "templates" / ".claude" / "settings.json.template"
544
+ settings_dest = repo_root / ".claude" / "settings.json"
545
+ _collect_results(
546
+ [_deploy_rendered_file(settings_template, settings_dest, detection, dry_run=dry_run)],
547
+ repo_root, created, updated, skipped,
548
+ )
549
+
550
+ # --- Deploy statusline.sh ---
551
+ statusline_src = skill_root / "templates" / ".claude" / "statusline.sh"
552
+ statusline_dest = repo_root / ".claude" / "statusline.sh"
553
+ if statusline_src.exists():
554
+ if statusline_dest.exists():
555
+ action_sl = "skipped"
556
+ elif dry_run:
557
+ action_sl = "created"
377
558
  else:
378
- skipped.append(str(p.relative_to(repo_root)))
559
+ statusline_dest.parent.mkdir(parents=True, exist_ok=True)
560
+ shutil.copy2(statusline_src, statusline_dest)
561
+ _make_executable(statusline_dest)
562
+ action_sl = "created"
563
+ _collect_results([(action_sl, statusline_dest)], repo_root, created, updated, skipped)
379
564
 
380
565
  if dry_run:
381
566
  print("setup-claude dry-run complete (no files written)")
@@ -415,6 +600,11 @@ def main(argv: List[str]) -> int:
415
600
  action="store_true",
416
601
  help="Print detected values as JSON and exit unless combined with --dry-run.",
417
602
  )
603
+ parser.add_argument(
604
+ "--force-detect",
605
+ action="store_true",
606
+ help="Bypass cached detection and re-run stack detection from scratch.",
607
+ )
418
608
  args = parser.parse_args(argv[1:])
419
609
 
420
610
  repo_root = Path(args.repo_root).resolve()
@@ -424,7 +614,17 @@ def main(argv: List[str]) -> int:
424
614
  print(f"Repo root not found: {repo_root}", file=sys.stderr)
425
615
  return 2
426
616
 
427
- detection = detect(repo_root)
617
+ # Use cached detection unless --force-detect is set
618
+ detection = None
619
+ if not args.force_detect:
620
+ detection = _read_cached_detection(repo_root)
621
+ if detection:
622
+ print("Using cached detection (< 7 days old). Pass --force-detect to re-run.")
623
+ if detection is None:
624
+ detection = detect(repo_root)
625
+ if not args.dry_run:
626
+ _write_cached_detection(repo_root, detection)
627
+
428
628
  if args.print_detection:
429
629
  print(json.dumps(asdict(detection), indent=2, sort_keys=True))
430
630
  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
+ }