@softspark/ai-toolkit 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/AGENTS.md +412 -0
- package/CHANGELOG.md +68 -0
- package/LICENSE +21 -0
- package/README.md +632 -0
- package/action.yml +53 -0
- package/app/.claude-plugin/plugin.json +44 -0
- package/app/ARCHITECTURE.md +306 -0
- package/app/CLAUDE.md.template +23 -0
- package/app/agents/ai-engineer.md +128 -0
- package/app/agents/backend-specialist.md +193 -0
- package/app/agents/business-intelligence.md +54 -0
- package/app/agents/chaos-monkey.md +67 -0
- package/app/agents/chief-of-staff.md +51 -0
- package/app/agents/code-archaeologist.md +127 -0
- package/app/agents/code-reviewer.md +184 -0
- package/app/agents/command-expert.md +131 -0
- package/app/agents/data-analyst.md +205 -0
- package/app/agents/data-scientist.md +151 -0
- package/app/agents/database-architect.md +317 -0
- package/app/agents/debugger.md +238 -0
- package/app/agents/devops-implementer.md +194 -0
- package/app/agents/documenter.md +364 -0
- package/app/agents/explorer-agent.md +145 -0
- package/app/agents/fact-checker.md +172 -0
- package/app/agents/frontend-specialist.md +209 -0
- package/app/agents/game-developer.md +216 -0
- package/app/agents/incident-responder.md +226 -0
- package/app/agents/infrastructure-architect.md +127 -0
- package/app/agents/infrastructure-validator.md +247 -0
- package/app/agents/llm-ops-engineer.md +237 -0
- package/app/agents/mcp-expert.md +228 -0
- package/app/agents/mcp-server-architect.md +195 -0
- package/app/agents/mcp-testing-engineer.md +292 -0
- package/app/agents/meta-architect.md +58 -0
- package/app/agents/ml-engineer.md +136 -0
- package/app/agents/mobile-developer.md +190 -0
- package/app/agents/night-watchman.md +55 -0
- package/app/agents/nlp-engineer.md +154 -0
- package/app/agents/orchestrator.md +437 -0
- package/app/agents/performance-optimizer.md +254 -0
- package/app/agents/predictive-analyst.md +57 -0
- package/app/agents/product-manager.md +194 -0
- package/app/agents/project-planner.md +287 -0
- package/app/agents/prompt-engineer.md +103 -0
- package/app/agents/qa-automation-engineer.md +182 -0
- package/app/agents/rag-engineer.md +201 -0
- package/app/agents/research-synthesizer.md +138 -0
- package/app/agents/search-specialist.md +101 -0
- package/app/agents/security-architect.md +62 -0
- package/app/agents/security-auditor.md +293 -0
- package/app/agents/seo-specialist.md +111 -0
- package/app/agents/system-governor.md +57 -0
- package/app/agents/tech-lead.md +62 -0
- package/app/agents/technical-researcher.md +103 -0
- package/app/agents/test-engineer.md +264 -0
- package/app/constitution.md +38 -0
- package/app/hooks/_profile-check.sh +11 -0
- package/app/hooks/guard-destructive.sh +74 -0
- package/app/hooks/guard-path.sh +73 -0
- package/app/hooks/post-tool-use.sh +35 -0
- package/app/hooks/pre-compact.sh +31 -0
- package/app/hooks/quality-check.sh +22 -0
- package/app/hooks/quality-gate.sh +49 -0
- package/app/hooks/save-session.sh +24 -0
- package/app/hooks/session-end.sh +37 -0
- package/app/hooks/session-start.sh +29 -0
- package/app/hooks/subagent-start.sh +16 -0
- package/app/hooks/subagent-stop.sh +16 -0
- package/app/hooks/track-usage.sh +50 -0
- package/app/hooks/user-prompt-submit.sh +25 -0
- package/app/hooks.json +178 -0
- package/app/mcp-defaults.json +23 -0
- package/app/output-styles/golden-rules.md +43 -0
- package/app/plugins/README.md +19 -0
- package/app/plugins/csharp-pack/README.md +11 -0
- package/app/plugins/csharp-pack/plugin.json +18 -0
- package/app/plugins/enterprise-pack/README.md +16 -0
- package/app/plugins/enterprise-pack/hooks/output-style.sh +6 -0
- package/app/plugins/enterprise-pack/hooks/status-line.sh +8 -0
- package/app/plugins/enterprise-pack/plugin.json +24 -0
- package/app/plugins/frontend-pack/README.md +14 -0
- package/app/plugins/frontend-pack/plugin.json +22 -0
- package/app/plugins/java-pack/README.md +11 -0
- package/app/plugins/java-pack/plugin.json +18 -0
- package/app/plugins/kotlin-pack/README.md +11 -0
- package/app/plugins/kotlin-pack/plugin.json +18 -0
- package/app/plugins/memory-pack/README.md +24 -0
- package/app/plugins/memory-pack/hooks/observation-capture.sh +67 -0
- package/app/plugins/memory-pack/hooks/session-summary.sh +71 -0
- package/app/plugins/memory-pack/plugin.json +22 -0
- package/app/plugins/memory-pack/scripts/init_db.py +81 -0
- package/app/plugins/memory-pack/scripts/strip_private.py +22 -0
- package/app/plugins/memory-pack/skills/mem-search/SKILL.md +70 -0
- package/app/plugins/research-pack/README.md +14 -0
- package/app/plugins/research-pack/plugin.json +22 -0
- package/app/plugins/ruby-pack/README.md +11 -0
- package/app/plugins/ruby-pack/plugin.json +18 -0
- package/app/plugins/rust-pack/README.md +11 -0
- package/app/plugins/rust-pack/plugin.json +18 -0
- package/app/plugins/security-pack/README.md +15 -0
- package/app/plugins/security-pack/plugin.json +23 -0
- package/app/plugins/swift-pack/README.md +11 -0
- package/app/plugins/swift-pack/plugin.json +18 -0
- package/app/rules/claude-toolkit-rules.md +21 -0
- package/app/rules/git-conventions.md +5 -0
- package/app/rules/quality-gates.md +10 -0
- package/app/skills/_lib/__init__.py +1 -0
- package/app/skills/_lib/detect_utils.py +150 -0
- package/app/skills/agent-creator/SKILL.md +82 -0
- package/app/skills/analyze/SKILL.md +92 -0
- package/app/skills/analyze/scripts/complexity.py +165 -0
- package/app/skills/api-patterns/SKILL.md +305 -0
- package/app/skills/app-builder/SKILL.md +187 -0
- package/app/skills/architecture-audit/SKILL.md +141 -0
- package/app/skills/architecture-decision/SKILL.md +55 -0
- package/app/skills/architecture-decision/templates/adr-template.md +36 -0
- package/app/skills/biz-scan/SKILL.md +30 -0
- package/app/skills/briefing/SKILL.md +27 -0
- package/app/skills/build/SKILL.md +97 -0
- package/app/skills/build/scripts/detect-build.py +151 -0
- package/app/skills/chaos/SKILL.md +32 -0
- package/app/skills/ci/SKILL.md +77 -0
- package/app/skills/ci/scripts/ci-detect.py +135 -0
- package/app/skills/ci/templates/github-actions-node.yml +38 -0
- package/app/skills/ci/templates/github-actions-python.yml +42 -0
- package/app/skills/ci-cd-patterns/SKILL.md +299 -0
- package/app/skills/clean-code/SKILL.md +110 -0
- package/app/skills/clean-code/reference/dart.md +18 -0
- package/app/skills/clean-code/reference/go.md +23 -0
- package/app/skills/clean-code/reference/php.md +32 -0
- package/app/skills/clean-code/reference/python.md +180 -0
- package/app/skills/clean-code/reference/typescript.md +26 -0
- package/app/skills/command-creator/SKILL.md +83 -0
- package/app/skills/commit/SKILL.md +98 -0
- package/app/skills/commit/scripts/pre-commit-check.py +87 -0
- package/app/skills/commit/templates/conventional-commit.md +52 -0
- package/app/skills/csharp-patterns/SKILL.md +450 -0
- package/app/skills/database-patterns/SKILL.md +297 -0
- package/app/skills/debug/SKILL.md +154 -0
- package/app/skills/debug/scripts/error-parser.py +187 -0
- package/app/skills/debugging-tactics/SKILL.md +136 -0
- package/app/skills/deploy/SKILL.md +130 -0
- package/app/skills/deploy/scripts/pre_deploy_check.py +171 -0
- package/app/skills/deploy/templates/deployment-checklist.md +31 -0
- package/app/skills/design-an-interface/SKILL.md +105 -0
- package/app/skills/design-engineering/SKILL.md +260 -0
- package/app/skills/docker-devops/SKILL.md +303 -0
- package/app/skills/docs/SKILL.md +145 -0
- package/app/skills/docs/scripts/doc-inventory.py +176 -0
- package/app/skills/docs/templates/adr-template.md +36 -0
- package/app/skills/docs/templates/readme-template.md +67 -0
- package/app/skills/documentation-standards/SKILL.md +191 -0
- package/app/skills/ecommerce-patterns/SKILL.md +209 -0
- package/app/skills/evaluate/SKILL.md +132 -0
- package/app/skills/evolve/SKILL.md +27 -0
- package/app/skills/explain/SKILL.md +54 -0
- package/app/skills/explain/scripts/dependency-graph.py +215 -0
- package/app/skills/explore/SKILL.md +112 -0
- package/app/skills/explore/scripts/visualize.py +117 -0
- package/app/skills/fix/SKILL.md +78 -0
- package/app/skills/fix/scripts/error-classifier.py +191 -0
- package/app/skills/flutter-patterns/SKILL.md +254 -0
- package/app/skills/git-mastery/SKILL.md +70 -0
- package/app/skills/grill-me/SKILL.md +38 -0
- package/app/skills/health/SKILL.md +91 -0
- package/app/skills/health/scripts/health_check.py +162 -0
- package/app/skills/hive-mind/SKILL.md +56 -0
- package/app/skills/hook-creator/SKILL.md +107 -0
- package/app/skills/index/SKILL.md +74 -0
- package/app/skills/instinct-review/SKILL.md +77 -0
- package/app/skills/java-patterns/SKILL.md +442 -0
- package/app/skills/kotlin-patterns/SKILL.md +446 -0
- package/app/skills/lint/SKILL.md +103 -0
- package/app/skills/lint/scripts/detect-linters.py +112 -0
- package/app/skills/mcp-patterns/SKILL.md +270 -0
- package/app/skills/mem-search/SKILL.md +70 -0
- package/app/skills/migrate/SKILL.md +90 -0
- package/app/skills/migrate/scripts/migration-status.py +195 -0
- package/app/skills/migration-patterns/SKILL.md +260 -0
- package/app/skills/night-watch/SKILL.md +28 -0
- package/app/skills/observability-patterns/SKILL.md +203 -0
- package/app/skills/onboard/SKILL.md +76 -0
- package/app/skills/orchestrate/SKILL.md +86 -0
- package/app/skills/panic/SKILL.md +30 -0
- package/app/skills/performance-profiling/SKILL.md +59 -0
- package/app/skills/plan/SKILL.md +110 -0
- package/app/skills/plan/templates/plan-template.md +40 -0
- package/app/skills/plan-writing/SKILL.md +201 -0
- package/app/skills/plugin-creator/SKILL.md +78 -0
- package/app/skills/pr/SKILL.md +129 -0
- package/app/skills/pr/scripts/pr-summary.py +175 -0
- package/app/skills/prd-to-issues/SKILL.md +108 -0
- package/app/skills/prd-to-plan/SKILL.md +120 -0
- package/app/skills/predict/SKILL.md +30 -0
- package/app/skills/qa-session/SKILL.md +110 -0
- package/app/skills/rag-patterns/SKILL.md +203 -0
- package/app/skills/refactor/SKILL.md +124 -0
- package/app/skills/refactor/scripts/refactor-scan.py +210 -0
- package/app/skills/refactor-plan/SKILL.md +112 -0
- package/app/skills/repeat/SKILL.md +149 -0
- package/app/skills/research-mastery/SKILL.md +56 -0
- package/app/skills/review/SKILL.md +141 -0
- package/app/skills/review/scripts/diff-analyzer.py +170 -0
- package/app/skills/rollback/SKILL.md +87 -0
- package/app/skills/rollback/scripts/rollback_info.py +149 -0
- package/app/skills/ruby-patterns/SKILL.md +454 -0
- package/app/skills/rust-patterns/SKILL.md +446 -0
- package/app/skills/search/SKILL.md +64 -0
- package/app/skills/security-patterns/SKILL.md +91 -0
- package/app/skills/security-patterns/reference/authentication.md +37 -0
- package/app/skills/security-patterns/reference/authorization.md +22 -0
- package/app/skills/security-patterns/reference/input-validation.md +30 -0
- package/app/skills/security-patterns/reference/oauth-csrf-audit.md +131 -0
- package/app/skills/skill-creator/SKILL.md +154 -0
- package/app/skills/skill-creator/templates/dashboard/index.html +130 -0
- package/app/skills/skill-creator/templates/reasoning-engine/assets/example.json +12 -0
- package/app/skills/skill-creator/templates/reasoning-engine/search.py +110 -0
- package/app/skills/subagent-development/SKILL.md +225 -0
- package/app/skills/subagent-development/reference/code-quality-reviewer-prompt.md +145 -0
- package/app/skills/subagent-development/reference/implementer-prompt.md +118 -0
- package/app/skills/subagent-development/reference/spec-reviewer-prompt.md +100 -0
- package/app/skills/swarm/SKILL.md +81 -0
- package/app/skills/swift-patterns/SKILL.md +500 -0
- package/app/skills/tdd/SKILL.md +174 -0
- package/app/skills/tdd/reference/deep-modules.md +32 -0
- package/app/skills/tdd/reference/interface-design.md +32 -0
- package/app/skills/tdd/reference/mocking.md +52 -0
- package/app/skills/tdd/reference/refactoring.md +10 -0
- package/app/skills/tdd/reference/tests.md +59 -0
- package/app/skills/teams/SKILL.md +101 -0
- package/app/skills/test/SKILL.md +107 -0
- package/app/skills/test/scripts/detect-runner.py +113 -0
- package/app/skills/testing-patterns/SKILL.md +73 -0
- package/app/skills/testing-patterns/reference/flutter-testing.md +33 -0
- package/app/skills/testing-patterns/reference/go-testing.md +52 -0
- package/app/skills/testing-patterns/reference/php-phpunit.md +39 -0
- package/app/skills/testing-patterns/reference/python-pytest.md +228 -0
- package/app/skills/testing-patterns/reference/typescript-vitest.md +50 -0
- package/app/skills/triage-issue/SKILL.md +120 -0
- package/app/skills/typescript-patterns/SKILL.md +256 -0
- package/app/skills/ubiquitous-language/SKILL.md +74 -0
- package/app/skills/verification-before-completion/SKILL.md +108 -0
- package/app/skills/workflow/SKILL.md +250 -0
- package/app/skills/write-a-prd/SKILL.md +129 -0
- package/app/skills/write-a-prd/reference/visual-companion.md +78 -0
- package/app/skills/write-a-prd/scripts/frame-template.html +111 -0
- package/app/skills/write-a-prd/scripts/visual-server.cjs +79 -0
- package/app/templates/skill/generator/SKILL.md.template +40 -0
- package/app/templates/skill/knowledge/SKILL.md.template +52 -0
- package/app/templates/skill/linter/SKILL.md.template +34 -0
- package/app/templates/skill/reviewer/SKILL.md.template +51 -0
- package/app/templates/skill/workflow/SKILL.md.template +49 -0
- package/benchmarks/README.md +111 -0
- package/benchmarks/ecosystem-dashboard.json +148 -0
- package/benchmarks/ecosystem-harvest.json +148 -0
- package/benchmarks/results.json +38 -0
- package/benchmarks/run.py +351 -0
- package/bin/ai-toolkit.js +345 -0
- package/kb/best-practices/README.md +11 -0
- package/kb/howto/README.md +11 -0
- package/kb/procedures/maintenance-sop.md +306 -0
- package/kb/reference/agents-catalog.md +124 -0
- package/kb/reference/anti-pattern-registry-format.md +221 -0
- package/kb/reference/architecture-overview.md +232 -0
- package/kb/reference/benchmark-config.md +62 -0
- package/kb/reference/ci-integration.md +66 -0
- package/kb/reference/claude-ecosystem-benchmark-snapshot.md +80 -0
- package/kb/reference/claude-ecosystem-expansion-foundations.md +102 -0
- package/kb/reference/commands-catalog.md +21 -0
- package/kb/reference/distribution-model.md +63 -0
- package/kb/reference/global-install-model.md +56 -0
- package/kb/reference/hierarchical-override-pattern.md +200 -0
- package/kb/reference/hooks-catalog.md +306 -0
- package/kb/reference/integrations.md +88 -0
- package/kb/reference/language-packs.md +52 -0
- package/kb/reference/merge-friendly-install-model.md +58 -0
- package/kb/reference/plugin-pack-conventions.md +151 -0
- package/kb/reference/quick-wins-implementation-summary.md +70 -0
- package/kb/reference/skill-templates.md +50 -0
- package/kb/reference/skills-catalog.md +215 -0
- package/kb/reference/skills-unification.md +57 -0
- package/kb/reference/stats.md +69 -0
- package/kb/reference/sync.md +76 -0
- package/kb/troubleshooting/README.md +11 -0
- package/llms-full.txt +3068 -0
- package/llms.txt +39 -0
- package/package.json +75 -0
- package/scripts/_common.py +160 -0
- package/scripts/add_rule.py +50 -0
- package/scripts/benchmark_config.py +127 -0
- package/scripts/benchmark_ecosystem.py +288 -0
- package/scripts/check_deps.py +260 -0
- package/scripts/create_skill.py +118 -0
- package/scripts/doctor.py +504 -0
- package/scripts/eject.py +113 -0
- package/scripts/emission.py +256 -0
- package/scripts/evaluate_skills.py +260 -0
- package/scripts/frontmatter.py +58 -0
- package/scripts/generate_agents_md.py +91 -0
- package/scripts/generate_aider_conf.py +51 -0
- package/scripts/generate_cline.py +35 -0
- package/scripts/generate_copilot.py +30 -0
- package/scripts/generate_cursor_rules.py +35 -0
- package/scripts/generate_gemini.py +28 -0
- package/scripts/generate_llms_txt.py +164 -0
- package/scripts/generate_roo_modes.py +80 -0
- package/scripts/generate_windsurf.py +35 -0
- package/scripts/generator_base.py +140 -0
- package/scripts/harvest_ecosystem.py +50 -0
- package/scripts/inject_rule_cli.py +101 -0
- package/scripts/inject_section_cli.py +47 -0
- package/scripts/injection.py +180 -0
- package/scripts/install.py +236 -0
- package/scripts/install_git_hooks.py +71 -0
- package/scripts/install_steps/__init__.py +5 -0
- package/scripts/install_steps/ai_tools.py +261 -0
- package/scripts/install_steps/hooks.py +90 -0
- package/scripts/install_steps/markers.py +79 -0
- package/scripts/install_steps/symlinks.py +87 -0
- package/scripts/merge-hooks.py +192 -0
- package/scripts/plugin.py +642 -0
- package/scripts/plugin_schema.py +138 -0
- package/scripts/remove_rule.py +58 -0
- package/scripts/stats.py +81 -0
- package/scripts/sync.py +215 -0
- package/scripts/uninstall.py +292 -0
- package/scripts/validate.py +700 -0
|
@@ -0,0 +1,500 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: swift-patterns
|
|
3
|
+
description: "Loaded when user asks about Swift or iOS development patterns"
|
|
4
|
+
effort: medium
|
|
5
|
+
user-invocable: false
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Swift / iOS Patterns
|
|
9
|
+
|
|
10
|
+
## Project Structure
|
|
11
|
+
|
|
12
|
+
### Swift Package (SPM)
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
MyPackage/
|
|
16
|
+
├── Package.swift
|
|
17
|
+
├── Sources/
|
|
18
|
+
│ ├── MyLibrary/
|
|
19
|
+
│ └── MyExecutable/
|
|
20
|
+
├── Tests/
|
|
21
|
+
│ └── MyLibraryTests/
|
|
22
|
+
└── Plugins/
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
```swift
|
|
26
|
+
// swift-tools-version: 5.10
|
|
27
|
+
import PackageDescription
|
|
28
|
+
|
|
29
|
+
let package = Package(
|
|
30
|
+
name: "MyPackage",
|
|
31
|
+
platforms: [.iOS(.v17), .macOS(.v14)],
|
|
32
|
+
products: [
|
|
33
|
+
.library(name: "MyLibrary", targets: ["MyLibrary"]),
|
|
34
|
+
],
|
|
35
|
+
dependencies: [
|
|
36
|
+
.package(url: "https://github.com/apple/swift-algorithms", from: "1.2.0"),
|
|
37
|
+
],
|
|
38
|
+
targets: [
|
|
39
|
+
.target(name: "MyLibrary",
|
|
40
|
+
dependencies: [.product(name: "Algorithms", package: "swift-algorithms")]),
|
|
41
|
+
.testTarget(name: "MyLibraryTests", dependencies: ["MyLibrary"]),
|
|
42
|
+
]
|
|
43
|
+
)
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### Xcode Project Layout
|
|
47
|
+
|
|
48
|
+
```
|
|
49
|
+
MyApp/
|
|
50
|
+
├── MyApp/
|
|
51
|
+
│ ├── App/ # Entry point, ContentView
|
|
52
|
+
│ ├── Features/ # Feature modules (Views, ViewModels, Models)
|
|
53
|
+
│ ├── Core/ # Networking, Storage, Extensions
|
|
54
|
+
│ └── Resources/ # Assets.xcassets, Info.plist
|
|
55
|
+
├── MyAppTests/
|
|
56
|
+
└── MyAppUITests/
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## Idioms / Code Style
|
|
62
|
+
|
|
63
|
+
### Optionals
|
|
64
|
+
|
|
65
|
+
```swift
|
|
66
|
+
// guard-let for early exit
|
|
67
|
+
func process(user: User?) {
|
|
68
|
+
guard let user else { return }
|
|
69
|
+
print(user.name)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Optional chaining + nil coalescing
|
|
73
|
+
let name = user?.profile?.displayName ?? "Anonymous"
|
|
74
|
+
|
|
75
|
+
// map/flatMap on optionals
|
|
76
|
+
let length: Int? = optionalString.map { $0.count }
|
|
77
|
+
// Never force-unwrap in production: user!.name
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Protocol-Oriented Programming
|
|
81
|
+
|
|
82
|
+
```swift
|
|
83
|
+
protocol Cacheable: Identifiable where ID: Hashable {
|
|
84
|
+
var cacheKey: String { get }
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
extension Cacheable where ID == String {
|
|
88
|
+
var cacheKey: String { id }
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### Value Types vs Reference Types
|
|
93
|
+
|
|
94
|
+
Use structs by default (value semantics, thread-safe). Use classes when identity matters, shared mutable state is intentional, inheritance is needed, or ObjC interop is required.
|
|
95
|
+
|
|
96
|
+
### Property Wrappers
|
|
97
|
+
|
|
98
|
+
```swift
|
|
99
|
+
@propertyWrapper
|
|
100
|
+
struct Clamped<Value: Comparable> {
|
|
101
|
+
var wrappedValue: Value {
|
|
102
|
+
didSet { wrappedValue = min(max(wrappedValue, range.lowerBound), range.upperBound) }
|
|
103
|
+
}
|
|
104
|
+
let range: ClosedRange<Value>
|
|
105
|
+
|
|
106
|
+
init(wrappedValue: Value, _ range: ClosedRange<Value>) {
|
|
107
|
+
self.range = range
|
|
108
|
+
self.wrappedValue = min(max(wrappedValue, range.lowerBound), range.upperBound)
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
struct Volume {
|
|
113
|
+
@Clamped(0...100) var level: Int = 50
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### Result Builders
|
|
118
|
+
|
|
119
|
+
```swift
|
|
120
|
+
@resultBuilder
|
|
121
|
+
struct ArrayBuilder<Element> {
|
|
122
|
+
static func buildBlock(_ components: [Element]...) -> [Element] { components.flatMap { $0 } }
|
|
123
|
+
static func buildExpression(_ expression: Element) -> [Element] { [expression] }
|
|
124
|
+
static func buildOptional(_ component: [Element]?) -> [Element] { component ?? [] }
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
## Error Handling
|
|
131
|
+
|
|
132
|
+
### throws / try / catch
|
|
133
|
+
|
|
134
|
+
```swift
|
|
135
|
+
enum NetworkError: Error, LocalizedError {
|
|
136
|
+
case invalidURL
|
|
137
|
+
case timeout(seconds: Int)
|
|
138
|
+
case serverError(statusCode: Int)
|
|
139
|
+
|
|
140
|
+
var errorDescription: String? {
|
|
141
|
+
switch self {
|
|
142
|
+
case .invalidURL: "Invalid URL."
|
|
143
|
+
case .timeout(let s): "Timed out after \(s)s."
|
|
144
|
+
case .serverError(let code): "Server returned \(code)."
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
do {
|
|
150
|
+
let user = try fetchUser(id: "123")
|
|
151
|
+
} catch let error as NetworkError {
|
|
152
|
+
handleNetworkError(error)
|
|
153
|
+
} catch {
|
|
154
|
+
handleUnexpected(error)
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
let user = try? fetchUser(id: "123") // nil on error
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### Result Type
|
|
161
|
+
|
|
162
|
+
```swift
|
|
163
|
+
switch fetchData(from: url) {
|
|
164
|
+
case .success(let data): process(data)
|
|
165
|
+
case .failure(let error): showError(error)
|
|
166
|
+
}
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### Typed Throws (Swift 6) and Async Throws
|
|
170
|
+
|
|
171
|
+
```swift
|
|
172
|
+
func load() throws(DatabaseError) -> [Item] { /* compiler-enforced error type */ }
|
|
173
|
+
|
|
174
|
+
func fetchUser(id: String) async throws -> User {
|
|
175
|
+
let (data, response) = try await URLSession.shared.data(from: url)
|
|
176
|
+
guard let http = response as? HTTPURLResponse, http.statusCode == 200 else {
|
|
177
|
+
throw NetworkError.serverError(statusCode: 0)
|
|
178
|
+
}
|
|
179
|
+
return try JSONDecoder().decode(User.self, from: data)
|
|
180
|
+
}
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
---
|
|
184
|
+
|
|
185
|
+
## Testing
|
|
186
|
+
|
|
187
|
+
### XCTest
|
|
188
|
+
|
|
189
|
+
```swift
|
|
190
|
+
final class UserServiceTests: XCTestCase {
|
|
191
|
+
var sut: UserService!
|
|
192
|
+
var mockRepo: MockUserRepository!
|
|
193
|
+
|
|
194
|
+
override func setUp() {
|
|
195
|
+
mockRepo = MockUserRepository()
|
|
196
|
+
sut = UserService(repository: mockRepo)
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
func testFetchUser_success() async throws {
|
|
200
|
+
mockRepo.stubbedUser = User(id: "1", name: "Alice")
|
|
201
|
+
let user = try await sut.fetchUser(id: "1")
|
|
202
|
+
XCTAssertEqual(user.name, "Alice")
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
func testFetchUser_notFound_throws() async {
|
|
206
|
+
mockRepo.shouldFail = true
|
|
207
|
+
do {
|
|
208
|
+
_ = try await sut.fetchUser(id: "999")
|
|
209
|
+
XCTFail("Expected error")
|
|
210
|
+
} catch {
|
|
211
|
+
XCTAssertTrue(error is UserService.Error)
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
### Swift Testing Framework (Swift 6+)
|
|
218
|
+
|
|
219
|
+
```swift
|
|
220
|
+
import Testing
|
|
221
|
+
|
|
222
|
+
@Suite("UserService")
|
|
223
|
+
struct UserServiceTests {
|
|
224
|
+
@Test("fetches user by ID")
|
|
225
|
+
func fetchUser() async throws {
|
|
226
|
+
let mockRepo = MockUserRepository()
|
|
227
|
+
mockRepo.stubbedUser = User(id: "1", name: "Alice")
|
|
228
|
+
let sut = UserService(repository: mockRepo)
|
|
229
|
+
let user = try await sut.fetchUser(id: "1")
|
|
230
|
+
#expect(user.name == "Alice")
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
@Test("throws on missing user", arguments: ["999", ""])
|
|
234
|
+
func fetchMissingUser(id: String) async {
|
|
235
|
+
let sut = UserService(repository: MockUserRepository())
|
|
236
|
+
await #expect(throws: UserService.Error.self) {
|
|
237
|
+
try await sut.fetchUser(id: id)
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
### Protocol-Based Mocking
|
|
244
|
+
|
|
245
|
+
```swift
|
|
246
|
+
protocol UserRepository {
|
|
247
|
+
func fetch(id: String) async throws -> User
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
final class MockUserRepository: UserRepository {
|
|
251
|
+
var stubbedUser: User?
|
|
252
|
+
var shouldFail = false
|
|
253
|
+
private(set) var fetchCallCount = 0
|
|
254
|
+
|
|
255
|
+
func fetch(id: String) async throws -> User {
|
|
256
|
+
fetchCallCount += 1
|
|
257
|
+
if shouldFail { throw NSError(domain: "", code: 0) }
|
|
258
|
+
return stubbedUser ?? User(id: id, name: "Default")
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
### UI Testing
|
|
264
|
+
|
|
265
|
+
```swift
|
|
266
|
+
func testLoginFlow() {
|
|
267
|
+
let app = XCUIApplication()
|
|
268
|
+
app.launchArguments = ["--uitesting"]
|
|
269
|
+
app.launch()
|
|
270
|
+
app.textFields["email"].tap()
|
|
271
|
+
app.textFields["email"].typeText("user@example.com")
|
|
272
|
+
app.secureTextFields["password"].typeText("pass")
|
|
273
|
+
app.buttons["Sign In"].tap()
|
|
274
|
+
XCTAssertTrue(app.staticTexts["Welcome"].waitForExistence(timeout: 5))
|
|
275
|
+
}
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
---
|
|
279
|
+
|
|
280
|
+
## Common Frameworks
|
|
281
|
+
|
|
282
|
+
### SwiftUI + @Observable (iOS 17+)
|
|
283
|
+
|
|
284
|
+
```swift
|
|
285
|
+
@Observable
|
|
286
|
+
final class UserViewModel {
|
|
287
|
+
var users: [User] = []
|
|
288
|
+
var isLoading = false
|
|
289
|
+
private let service: UserService
|
|
290
|
+
|
|
291
|
+
init(service: UserService) { self.service = service }
|
|
292
|
+
|
|
293
|
+
func load() async {
|
|
294
|
+
isLoading = true
|
|
295
|
+
defer { isLoading = false }
|
|
296
|
+
users = (try? await service.fetchAll()) ?? []
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
struct UserListView: View {
|
|
301
|
+
@State private var vm: UserViewModel
|
|
302
|
+
|
|
303
|
+
init(service: UserService) {
|
|
304
|
+
_vm = State(initialValue: UserViewModel(service: service))
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
var body: some View {
|
|
308
|
+
NavigationStack {
|
|
309
|
+
List(vm.users) { user in
|
|
310
|
+
NavigationLink(value: user) { Text(user.name) }
|
|
311
|
+
}
|
|
312
|
+
.navigationTitle("Users")
|
|
313
|
+
.navigationDestination(for: User.self) { UserDetailView(user: $0) }
|
|
314
|
+
.task { await vm.load() }
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
### Combine
|
|
321
|
+
|
|
322
|
+
```swift
|
|
323
|
+
class SearchVM: ObservableObject {
|
|
324
|
+
@Published var query = ""
|
|
325
|
+
@Published var results: [Item] = []
|
|
326
|
+
private var cancellables = Set<AnyCancellable>()
|
|
327
|
+
|
|
328
|
+
init(service: SearchService) {
|
|
329
|
+
$query
|
|
330
|
+
.debounce(for: .milliseconds(300), scheduler: DispatchQueue.main)
|
|
331
|
+
.removeDuplicates()
|
|
332
|
+
.filter { !$0.isEmpty }
|
|
333
|
+
.flatMap { service.search(query: $0) }
|
|
334
|
+
.receive(on: DispatchQueue.main)
|
|
335
|
+
.sink(receiveCompletion: { _ in },
|
|
336
|
+
receiveValue: { [weak self] in self?.results = $0 })
|
|
337
|
+
.store(in: &cancellables)
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
### Structured Concurrency
|
|
343
|
+
|
|
344
|
+
```swift
|
|
345
|
+
func fetchAllProfiles(ids: [String]) async throws -> [Profile] {
|
|
346
|
+
try await withThrowingTaskGroup(of: Profile.self) { group in
|
|
347
|
+
for id in ids { group.addTask { try await fetchProfile(id: id) } }
|
|
348
|
+
return try await group.reduce(into: []) { $0.append($1) }
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// AsyncStream for bridging callbacks
|
|
353
|
+
let locations = AsyncStream<Location> { continuation in
|
|
354
|
+
manager.onUpdate = { continuation.yield($0) }
|
|
355
|
+
continuation.onTermination = { _ in manager.stop() }
|
|
356
|
+
}
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
### SwiftData
|
|
360
|
+
|
|
361
|
+
```swift
|
|
362
|
+
@Model
|
|
363
|
+
final class Item {
|
|
364
|
+
var title: String
|
|
365
|
+
var timestamp: Date
|
|
366
|
+
@Relationship(deleteRule: .cascade) var tags: [Tag]
|
|
367
|
+
init(title: String) { self.title = title; self.timestamp = .now; self.tags = [] }
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
struct ItemListView: View {
|
|
371
|
+
@Query(sort: \Item.timestamp, order: .reverse) private var items: [Item]
|
|
372
|
+
@Environment(\.modelContext) private var context
|
|
373
|
+
|
|
374
|
+
var body: some View {
|
|
375
|
+
List(items) { Text($0.title) }
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
### Vapor (Server-Side)
|
|
381
|
+
|
|
382
|
+
```swift
|
|
383
|
+
app.get("users", ":id") { req async throws -> User in
|
|
384
|
+
guard let id = req.parameters.get("id", as: UUID.self) else { throw Abort(.badRequest) }
|
|
385
|
+
guard let user = try await User.find(id, on: req.db) else { throw Abort(.notFound) }
|
|
386
|
+
return user
|
|
387
|
+
}
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
---
|
|
391
|
+
|
|
392
|
+
## Performance
|
|
393
|
+
|
|
394
|
+
### Copy-on-Write
|
|
395
|
+
|
|
396
|
+
Array, String, Dictionary use COW automatically. For custom value types:
|
|
397
|
+
|
|
398
|
+
```swift
|
|
399
|
+
struct LargeData {
|
|
400
|
+
private final class Storage { var buffer: [UInt8]; init(_ b: [UInt8]) { buffer = b } }
|
|
401
|
+
private var storage: Storage
|
|
402
|
+
|
|
403
|
+
var buffer: [UInt8] {
|
|
404
|
+
get { storage.buffer }
|
|
405
|
+
set {
|
|
406
|
+
if !isKnownUniquelyReferenced(&storage) { storage = Storage(newValue) }
|
|
407
|
+
else { storage.buffer = newValue }
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
### ARC Retain Cycles
|
|
414
|
+
|
|
415
|
+
```swift
|
|
416
|
+
// weak — closure may outlive self
|
|
417
|
+
service.fetch { [weak self] result in
|
|
418
|
+
guard let self else { return }
|
|
419
|
+
self.update(with: result)
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
// unowned — self guaranteed to outlive closure
|
|
423
|
+
lazy var tick: () -> Void = { [unowned self] in self.count += 1 }
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
### Sendable and Actors
|
|
427
|
+
|
|
428
|
+
```swift
|
|
429
|
+
struct Config: Sendable { let apiURL: URL; let timeout: TimeInterval }
|
|
430
|
+
|
|
431
|
+
actor ImageCache {
|
|
432
|
+
private var cache: [URL: Data] = [:]
|
|
433
|
+
func image(for url: URL) -> Data? { cache[url] }
|
|
434
|
+
func store(_ data: Data, for url: URL) { cache[url] = data }
|
|
435
|
+
}
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
### Instruments
|
|
439
|
+
|
|
440
|
+
| Instrument | Use For |
|
|
441
|
+
|---|---|
|
|
442
|
+
| Time Profiler | CPU bottlenecks |
|
|
443
|
+
| Allocations | Memory growth |
|
|
444
|
+
| Leaks | Retain cycles |
|
|
445
|
+
| SwiftUI | View body re-evaluations |
|
|
446
|
+
| Core Animation | FPS, offscreen rendering |
|
|
447
|
+
|
|
448
|
+
Profile on device (not simulator). Use `os_signpost` for custom spans.
|
|
449
|
+
|
|
450
|
+
---
|
|
451
|
+
|
|
452
|
+
## Build / Package Management
|
|
453
|
+
|
|
454
|
+
### SPM Commands
|
|
455
|
+
|
|
456
|
+
```bash
|
|
457
|
+
swift package resolve # Resolve deps
|
|
458
|
+
swift build -c release # Release build
|
|
459
|
+
swift test --filter MyTests # Filtered test run
|
|
460
|
+
```
|
|
461
|
+
|
|
462
|
+
### xcconfig
|
|
463
|
+
|
|
464
|
+
```
|
|
465
|
+
// Shared.xcconfig
|
|
466
|
+
SWIFT_VERSION = 5.10
|
|
467
|
+
IPHONEOS_DEPLOYMENT_TARGET = 17.0
|
|
468
|
+
SWIFT_STRICT_CONCURRENCY = complete
|
|
469
|
+
|
|
470
|
+
// Debug.xcconfig
|
|
471
|
+
#include "Shared.xcconfig"
|
|
472
|
+
SWIFT_OPTIMIZATION_LEVEL = -Onone
|
|
473
|
+
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG
|
|
474
|
+
|
|
475
|
+
// Release.xcconfig
|
|
476
|
+
#include "Shared.xcconfig"
|
|
477
|
+
SWIFT_OPTIMIZATION_LEVEL = -O
|
|
478
|
+
SWIFT_COMPILATION_MODE = wholemodule
|
|
479
|
+
```
|
|
480
|
+
|
|
481
|
+
### Tuist
|
|
482
|
+
|
|
483
|
+
Define targets in `Project.swift` using `ProjectDescription`. Map each app, framework, and test target with `bundleId`, `sources`, and `dependencies`. Use `.external(name:)` for SPM deps and `.target(name:)` for internal.
|
|
484
|
+
|
|
485
|
+
Schemes: separate Debug/Release/Testing. Enable ASan + TSan in test schemes.
|
|
486
|
+
|
|
487
|
+
---
|
|
488
|
+
|
|
489
|
+
## Anti-Patterns
|
|
490
|
+
|
|
491
|
+
| Anti-Pattern | Problem | Fix |
|
|
492
|
+
|---|---|---|
|
|
493
|
+
| Force unwrap `!` | Runtime crash | `guard let`, `if let`, `??` |
|
|
494
|
+
| Massive view controller | Untestable | MVVM, composable views |
|
|
495
|
+
| Stringly-typed APIs | No compiler checks | Enums, phantom types |
|
|
496
|
+
| Ignoring `@MainActor` | Off-main-thread UI | Annotate view models |
|
|
497
|
+
| Retain cycles | Memory leaks | `[weak self]` / `[unowned self]` |
|
|
498
|
+
| Blocking main thread | UI freezes | `async/await`, `Task { }` |
|
|
499
|
+
| `UserDefaults` for secrets | Insecure | Keychain (`SecItemAdd`) |
|
|
500
|
+
| `@ObservedObject` for owned state | Object recreated | `@StateObject` or `@State` + `@Observable` |
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: tdd
|
|
3
|
+
description: "Test-driven development with red-green-refactor loop and vertical slices. Use when user wants TDD, test-first development, red-green-refactor, or building features with tests driving the implementation."
|
|
4
|
+
user-invocable: true
|
|
5
|
+
effort: high
|
|
6
|
+
argument-hint: "[feature or behavior to implement]"
|
|
7
|
+
allowed-tools: Read, Write, Edit, Grep, Glob, Bash, Agent
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Test-Driven Development
|
|
11
|
+
|
|
12
|
+
$ARGUMENTS
|
|
13
|
+
|
|
14
|
+
Build features using strict RED → GREEN → REFACTOR cycles with vertical slices.
|
|
15
|
+
|
|
16
|
+
## Usage
|
|
17
|
+
|
|
18
|
+
```
|
|
19
|
+
/tdd [feature or behavior to implement]
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## The Iron Law
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
NO PRODUCTION CODE WITHOUT A FAILING TEST FIRST
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Write code before the test? **Delete it. Start over.**
|
|
29
|
+
|
|
30
|
+
**No exceptions:**
|
|
31
|
+
- Don't keep it as "reference"
|
|
32
|
+
- Don't "adapt" it while writing tests
|
|
33
|
+
- Don't look at it
|
|
34
|
+
- Delete means delete
|
|
35
|
+
|
|
36
|
+
Implement fresh from tests. Period.
|
|
37
|
+
|
|
38
|
+
**Violating the letter of this rule is violating the spirit of this rule.**
|
|
39
|
+
|
|
40
|
+
## Philosophy
|
|
41
|
+
|
|
42
|
+
**Core principle**: Tests verify behavior through public interfaces, not implementation details. Code can change entirely; tests shouldn't.
|
|
43
|
+
|
|
44
|
+
**Good tests**: Integration-style, exercise real code paths through public APIs. Describe _what_ the system does, not _how_. Read like specifications. Survive refactors.
|
|
45
|
+
|
|
46
|
+
**Bad tests**: Coupled to implementation. Mock internal collaborators, test private methods, verify through external means. Warning sign: test breaks on refactor but behavior is unchanged.
|
|
47
|
+
|
|
48
|
+
See [reference/tests.md](reference/tests.md) for examples, [reference/mocking.md](reference/mocking.md) for mocking guidelines.
|
|
49
|
+
|
|
50
|
+
## Anti-Pattern: Horizontal Slices
|
|
51
|
+
|
|
52
|
+
**DO NOT write all tests first, then all implementation.**
|
|
53
|
+
|
|
54
|
+
```
|
|
55
|
+
WRONG (horizontal):
|
|
56
|
+
RED: test1, test2, test3, test4, test5
|
|
57
|
+
GREEN: impl1, impl2, impl3, impl4, impl5
|
|
58
|
+
|
|
59
|
+
RIGHT (vertical):
|
|
60
|
+
RED→GREEN: test1→impl1
|
|
61
|
+
RED→GREEN: test2→impl2
|
|
62
|
+
RED→GREEN: test3→impl3
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Tests written in bulk test _imagined_ behavior. Vertical slices let each test respond to what you learned from the previous cycle.
|
|
66
|
+
|
|
67
|
+
## Workflow
|
|
68
|
+
|
|
69
|
+
### 1. Planning
|
|
70
|
+
|
|
71
|
+
Before writing any code:
|
|
72
|
+
|
|
73
|
+
- [ ] Confirm with user what interface changes are needed
|
|
74
|
+
- [ ] Confirm which behaviors to test (prioritize — you can't test everything)
|
|
75
|
+
- [ ] Identify opportunities for [deep modules](reference/deep-modules.md)
|
|
76
|
+
- [ ] Design interfaces for [testability](reference/interface-design.md)
|
|
77
|
+
- [ ] List behaviors to test (not implementation steps)
|
|
78
|
+
- [ ] Get user approval
|
|
79
|
+
|
|
80
|
+
### 2. Tracer Bullet
|
|
81
|
+
|
|
82
|
+
Write ONE test that confirms ONE thing:
|
|
83
|
+
|
|
84
|
+
```
|
|
85
|
+
RED: Write test for first behavior → test fails
|
|
86
|
+
GREEN: Write minimal code to pass → test passes
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
This proves the path works end-to-end.
|
|
90
|
+
|
|
91
|
+
### 3. Incremental Loop
|
|
92
|
+
|
|
93
|
+
For each remaining behavior:
|
|
94
|
+
|
|
95
|
+
```
|
|
96
|
+
RED: Write next test → fails
|
|
97
|
+
GREEN: Minimal code to pass → passes
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
| Rule | Description |
|
|
101
|
+
|------|-------------|
|
|
102
|
+
| One at a time | One test per cycle |
|
|
103
|
+
| Minimal | Only enough code to pass current test |
|
|
104
|
+
| No anticipation | Don't code for future tests |
|
|
105
|
+
| Behavioral | Tests focus on observable behavior |
|
|
106
|
+
|
|
107
|
+
### 4. Refactor
|
|
108
|
+
|
|
109
|
+
After all tests pass, look for [refactor candidates](reference/refactoring.md):
|
|
110
|
+
|
|
111
|
+
- [ ] Extract duplication
|
|
112
|
+
- [ ] Deepen modules (complexity behind simple interfaces)
|
|
113
|
+
- [ ] Apply SOLID where natural
|
|
114
|
+
- [ ] Run tests after each refactor step
|
|
115
|
+
|
|
116
|
+
**Never refactor while RED.** Get to GREEN first.
|
|
117
|
+
|
|
118
|
+
## Checklist Per Cycle
|
|
119
|
+
|
|
120
|
+
```
|
|
121
|
+
[ ] Test describes behavior, not implementation
|
|
122
|
+
[ ] Test uses public interface only
|
|
123
|
+
[ ] Test would survive internal refactor
|
|
124
|
+
[ ] Code is minimal for this test
|
|
125
|
+
[ ] No speculative features added
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
## Rules
|
|
129
|
+
|
|
130
|
+
- One RED→GREEN cycle at a time — never batch
|
|
131
|
+
- Tests assert on observable outcomes, not internal state
|
|
132
|
+
- Mock only at system boundaries (see [reference/mocking.md](reference/mocking.md))
|
|
133
|
+
- Each cycle leaves the codebase in a working state
|
|
134
|
+
|
|
135
|
+
## Red Flags — STOP and Start Over
|
|
136
|
+
|
|
137
|
+
If you catch yourself doing ANY of these, **delete the code and restart with TDD**:
|
|
138
|
+
|
|
139
|
+
- Writing production code before a failing test
|
|
140
|
+
- Writing tests after implementation
|
|
141
|
+
- Test passes immediately (you're testing existing behavior — fix the test)
|
|
142
|
+
- Can't explain why the test failed
|
|
143
|
+
- Tests added "later"
|
|
144
|
+
- Rationalizing "just this once"
|
|
145
|
+
- "I already manually tested it"
|
|
146
|
+
- "Keep as reference" or "adapt existing code"
|
|
147
|
+
|
|
148
|
+
## Common Rationalizations
|
|
149
|
+
|
|
150
|
+
| Excuse | Reality |
|
|
151
|
+
|--------|---------|
|
|
152
|
+
| "Too simple to test" | Simple code breaks. Test takes 30 seconds. |
|
|
153
|
+
| "I'll test after" | Tests passing immediately prove nothing. |
|
|
154
|
+
| "Tests after achieve same goals" | Tests-after = "what does this do?" Tests-first = "what should this do?" |
|
|
155
|
+
| "Already manually tested" | Ad-hoc ≠ systematic. No record, can't re-run. |
|
|
156
|
+
| "Deleting X hours is wasteful" | Sunk cost fallacy. Keeping unverified code is technical debt. |
|
|
157
|
+
| "Need to explore first" | Fine. Throw away exploration, start with TDD. |
|
|
158
|
+
| "TDD will slow me down" | TDD faster than debugging. Pragmatic = test-first. |
|
|
159
|
+
| "This is different because..." | No. Apply the Iron Law. |
|
|
160
|
+
|
|
161
|
+
## Verification Checklist
|
|
162
|
+
|
|
163
|
+
Before marking work complete:
|
|
164
|
+
|
|
165
|
+
- [ ] Every new function/method has a test
|
|
166
|
+
- [ ] Watched each test fail before implementing
|
|
167
|
+
- [ ] Each test failed for expected reason (feature missing, not typo)
|
|
168
|
+
- [ ] Wrote minimal code to pass each test
|
|
169
|
+
- [ ] All tests pass
|
|
170
|
+
- [ ] Output pristine (no errors, warnings)
|
|
171
|
+
- [ ] Tests use real code (mocks only at system boundaries)
|
|
172
|
+
- [ ] Edge cases and errors covered
|
|
173
|
+
|
|
174
|
+
Can't check all boxes? You skipped TDD. Start over.
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Deep Modules
|
|
2
|
+
|
|
3
|
+
From "A Philosophy of Software Design" (John Ousterhout):
|
|
4
|
+
|
|
5
|
+
**Deep module** = small interface + lots of implementation
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
┌─────────────────────┐
|
|
9
|
+
│ Small Interface │ ← Few methods, simple params
|
|
10
|
+
├─────────────────────┤
|
|
11
|
+
│ │
|
|
12
|
+
│ │
|
|
13
|
+
│ Deep Implementation│ ← Complex logic hidden
|
|
14
|
+
│ │
|
|
15
|
+
│ │
|
|
16
|
+
└─────────────────────┘
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
**Shallow module** = large interface + little implementation (avoid)
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
┌─────────────────────────────────┐
|
|
23
|
+
│ Large Interface │ ← Many methods, complex params
|
|
24
|
+
├─────────────────────────────────┤
|
|
25
|
+
│ Thin Implementation │ ← Just passes through
|
|
26
|
+
└─────────────────────────────────┘
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
When designing interfaces, ask:
|
|
30
|
+
- Can I reduce the number of methods?
|
|
31
|
+
- Can I simplify the parameters?
|
|
32
|
+
- Can I hide more complexity inside?
|