claude-code-starter 0.2.0 → 0.4.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 (3) hide show
  1. package/README.md +24 -10
  2. package/dist/cli.js +1968 -206
  3. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -163,6 +163,62 @@ function detectFrameworks(packageJson, files, rootDir) {
163
163
  if (cargo.includes("axum")) add("axum");
164
164
  if (cargo.includes("rocket")) add("rocket");
165
165
  }
166
+ const packageSwiftPath = path.join(rootDir, "Package.swift");
167
+ const xcodeprojExists = files.some((f) => f.endsWith(".xcodeproj")) || files.some((f) => f.endsWith(".xcworkspace"));
168
+ if (fs.existsSync(packageSwiftPath)) {
169
+ const packageSwift = fs.readFileSync(packageSwiftPath, "utf-8").toLowerCase();
170
+ if (packageSwift.includes("vapor")) add("vapor");
171
+ }
172
+ if (xcodeprojExists || files.some((f) => f.endsWith(".swift"))) {
173
+ const srcFiles = listSourceFilesShallow(rootDir, [".swift"]);
174
+ const hasSwiftUI = srcFiles.some(
175
+ (f) => f.includes("ContentView") || f.includes("App.swift") || f.includes("@main struct")
176
+ );
177
+ const hasUIKit = srcFiles.some(
178
+ (f) => f.includes("ViewController") || f.includes("AppDelegate") || f.includes("SceneDelegate")
179
+ );
180
+ if (hasSwiftUI) add("swiftui");
181
+ if (hasUIKit) add("uikit");
182
+ for (const file of srcFiles.slice(0, 10)) {
183
+ try {
184
+ const content = fs.readFileSync(file, "utf-8");
185
+ if (content.includes("import SwiftData") || content.includes("@Model")) add("swiftdata");
186
+ if (content.includes("import Combine") || content.includes("PassthroughSubject"))
187
+ add("combine");
188
+ } catch {
189
+ }
190
+ }
191
+ }
192
+ const gradlePath = path.join(rootDir, "build.gradle");
193
+ const gradleKtsPath = path.join(rootDir, "build.gradle.kts");
194
+ const appGradlePath = path.join(rootDir, "app", "build.gradle");
195
+ const appGradleKtsPath = path.join(rootDir, "app", "build.gradle.kts");
196
+ let gradleContent = "";
197
+ for (const gPath of [gradlePath, gradleKtsPath, appGradlePath, appGradleKtsPath]) {
198
+ if (fs.existsSync(gPath)) {
199
+ gradleContent += fs.readFileSync(gPath, "utf-8").toLowerCase();
200
+ }
201
+ }
202
+ if (gradleContent) {
203
+ if (gradleContent.includes("com.android") || gradleContent.includes("android {")) {
204
+ if (gradleContent.includes("compose") || gradleContent.includes("androidx.compose") || gradleContent.includes("composeoptions")) {
205
+ add("jetpack-compose");
206
+ } else {
207
+ add("android-views");
208
+ }
209
+ if (gradleContent.includes("room") || gradleContent.includes("androidx.room")) {
210
+ add("room");
211
+ }
212
+ if (gradleContent.includes("hilt") || gradleContent.includes("dagger.hilt")) {
213
+ add("hilt");
214
+ }
215
+ if (gradleContent.includes("ktor")) {
216
+ add("ktor-android");
217
+ }
218
+ }
219
+ if (gradleContent.includes("spring")) add("spring");
220
+ if (gradleContent.includes("quarkus")) add("quarkus");
221
+ }
166
222
  return frameworks;
167
223
  }
168
224
  const allDeps = {
@@ -356,6 +412,27 @@ function hasDevDep(packageJson, dep) {
356
412
  const deps = packageJson.dependencies || {};
357
413
  return dep in devDeps || dep in deps;
358
414
  }
415
+ function listSourceFilesShallow(rootDir, extensions) {
416
+ const files = [];
417
+ const ignoreDirs = ["node_modules", ".git", "build", "dist", "Pods", ".build", "DerivedData"];
418
+ function scan(dir, depth) {
419
+ if (depth > 2) return;
420
+ try {
421
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
422
+ if (ignoreDirs.includes(entry.name)) continue;
423
+ const fullPath = path.join(dir, entry.name);
424
+ if (entry.isDirectory()) {
425
+ scan(fullPath, depth + 1);
426
+ } else if (extensions.some((ext) => entry.name.endsWith(ext))) {
427
+ files.push(fullPath);
428
+ }
429
+ }
430
+ } catch {
431
+ }
432
+ }
433
+ scan(rootDir, 0);
434
+ return files;
435
+ }
359
436
  function countSourceFiles(rootDir, _languages) {
360
437
  const extensions = [
361
438
  // JavaScript/TypeScript
@@ -539,6 +616,7 @@ function generateClaudeMd(projectInfo) {
539
616
  sections.push("| `/status` | Show current task state |");
540
617
  sections.push("| `/done` | Mark current task complete |");
541
618
  sections.push("| `/analyze <area>` | Deep-dive into specific code |");
619
+ sections.push("| `/code-review` | Review changes for quality and security |");
542
620
  sections.push("");
543
621
  sections.push("## Common Operations");
544
622
  sections.push("");
@@ -553,6 +631,18 @@ function generateClaudeMd(projectInfo) {
553
631
  sections.push("3. **Test Before Done** - Run tests before marking complete");
554
632
  sections.push("4. **Update State** - Keep task.md current as you work");
555
633
  sections.push("5. **Match Patterns** - Follow existing code conventions");
634
+ sections.push("6. **TDD Workflow** - Write failing tests first, then implement");
635
+ sections.push("");
636
+ sections.push("## Quality Gates");
637
+ sections.push("");
638
+ sections.push("Enforced constraints for code quality:");
639
+ sections.push("");
640
+ sections.push("| Constraint | Limit | Action |");
641
+ sections.push("|------------|-------|--------|");
642
+ sections.push("| Lines per function | 20 max | Decompose immediately |");
643
+ sections.push("| Parameters per function | 3 max | Use options object |");
644
+ sections.push("| Lines per file | 200 max | Split by responsibility |");
645
+ sections.push("| Test coverage | 80% min | Add tests before merge |");
556
646
  sections.push("");
557
647
  sections.push("## Skills");
558
648
  sections.push("");
@@ -757,9 +847,16 @@ function generateSettings(stack) {
757
847
  }
758
848
  function getSkillsForStack(stack) {
759
849
  const skills = [
850
+ // Core methodology skills
760
851
  { name: "pattern-discovery", description: "Finding codebase patterns" },
761
852
  { name: "systematic-debugging", description: "Debugging approach" },
762
- { name: "testing-methodology", description: "Testing strategy" }
853
+ { name: "testing-methodology", description: "Testing strategy" },
854
+ // Process discipline skills
855
+ { name: "iterative-development", description: "TDD loops until tests pass" },
856
+ { name: "commit-hygiene", description: "Atomic commits and PR size limits" },
857
+ { name: "code-deduplication", description: "Prevent semantic code duplication" },
858
+ { name: "simplicity-rules", description: "Code complexity constraints" },
859
+ { name: "security", description: "Security patterns and secrets management" }
763
860
  ];
764
861
  if (stack.frameworks.includes("nextjs")) {
765
862
  skills.push({ name: "nextjs-patterns", description: "Next.js App Router patterns" });
@@ -776,6 +873,21 @@ function getSkillsForStack(stack) {
776
873
  if (stack.frameworks.includes("prisma") || stack.frameworks.includes("drizzle")) {
777
874
  skills.push({ name: "database-patterns", description: "Database and ORM patterns" });
778
875
  }
876
+ if (stack.frameworks.includes("swiftui")) {
877
+ skills.push({ name: "swiftui-patterns", description: "SwiftUI declarative UI patterns" });
878
+ }
879
+ if (stack.frameworks.includes("uikit")) {
880
+ skills.push({ name: "uikit-patterns", description: "UIKit view controller patterns" });
881
+ }
882
+ if (stack.frameworks.includes("vapor")) {
883
+ skills.push({ name: "vapor-patterns", description: "Vapor server-side Swift patterns" });
884
+ }
885
+ if (stack.frameworks.includes("jetpack-compose")) {
886
+ skills.push({ name: "compose-patterns", description: "Jetpack Compose UI patterns" });
887
+ }
888
+ if (stack.frameworks.includes("android-views")) {
889
+ skills.push({ name: "android-views-patterns", description: "Android XML views patterns" });
890
+ }
779
891
  return skills;
780
892
  }
781
893
  function generateSkills(stack) {
@@ -783,6 +895,11 @@ function generateSkills(stack) {
783
895
  artifacts.push(generatePatternDiscoverySkill());
784
896
  artifacts.push(generateSystematicDebuggingSkill());
785
897
  artifacts.push(generateTestingMethodologySkill(stack));
898
+ artifacts.push(generateIterativeDevelopmentSkill(stack));
899
+ artifacts.push(generateCommitHygieneSkill());
900
+ artifacts.push(generateCodeDeduplicationSkill());
901
+ artifacts.push(generateSimplicityRulesSkill());
902
+ artifacts.push(generateSecuritySkill(stack));
786
903
  if (stack.frameworks.includes("nextjs")) {
787
904
  artifacts.push(generateNextJsSkill());
788
905
  }
@@ -795,6 +912,21 @@ function generateSkills(stack) {
795
912
  if (stack.frameworks.includes("nestjs")) {
796
913
  artifacts.push(generateNestJSSkill());
797
914
  }
915
+ if (stack.frameworks.includes("swiftui")) {
916
+ artifacts.push(generateSwiftUISkill());
917
+ }
918
+ if (stack.frameworks.includes("uikit")) {
919
+ artifacts.push(generateUIKitSkill());
920
+ }
921
+ if (stack.frameworks.includes("vapor")) {
922
+ artifacts.push(generateVaporSkill());
923
+ }
924
+ if (stack.frameworks.includes("jetpack-compose")) {
925
+ artifacts.push(generateJetpackComposeSkill());
926
+ }
927
+ if (stack.frameworks.includes("android-views")) {
928
+ artifacts.push(generateAndroidViewsSkill());
929
+ }
798
930
  return artifacts;
799
931
  }
800
932
  function generatePatternDiscoverySkill() {
@@ -1634,236 +1766,1773 @@ describe('UsersService', () => {
1634
1766
  isNew: true
1635
1767
  };
1636
1768
  }
1637
- function getAgentsForStack(stack) {
1638
- const agents = [
1639
- { name: "code-reviewer", description: "Reviews code for quality and security" },
1640
- { name: "test-writer", description: "Generates tests for code" }
1641
- ];
1642
- if (stack.hasDocker) {
1643
- agents.push({ name: "docker-helper", description: "Helps with Docker and containerization" });
1644
- }
1645
- return agents;
1646
- }
1647
- function generateAgents(stack) {
1648
- const artifacts = [];
1649
- artifacts.push(generateCodeReviewerAgent(stack));
1650
- artifacts.push(generateTestWriterAgent(stack));
1651
- return artifacts;
1652
- }
1653
- function generateCodeReviewerAgent(stack) {
1654
- const lintCommand = getLintCommand(stack);
1769
+ function generateSwiftUISkill() {
1655
1770
  return {
1656
- type: "agent",
1657
- path: ".claude/agents/code-reviewer.md",
1771
+ type: "skill",
1772
+ path: ".claude/skills/swiftui-patterns.md",
1658
1773
  content: `---
1659
- name: code-reviewer
1660
- description: Reviews code for quality, security issues, and best practices
1661
- tools: Read, Grep, Glob${lintCommand ? `, Bash(${lintCommand})` : ""}
1662
- disallowedTools: Write, Edit
1663
- model: sonnet
1774
+ name: swiftui-patterns
1775
+ description: SwiftUI declarative UI patterns and best practices
1776
+ globs:
1777
+ - "**/*.swift"
1664
1778
  ---
1665
1779
 
1666
- You are a senior code reviewer with expertise in security and performance.
1780
+ # SwiftUI Patterns
1667
1781
 
1668
- ## Code Style Reference
1782
+ ## View Structure
1669
1783
 
1670
- Read these files to understand project conventions:
1671
- ${stack.linter === "eslint" ? "- `eslint.config.js` or `.eslintrc.*`" : ""}
1672
- ${stack.formatter === "prettier" ? "- `.prettierrc`" : ""}
1673
- ${stack.languages.includes("typescript") ? "- `tsconfig.json`" : ""}
1674
- ${stack.languages.includes("python") ? "- `pyproject.toml` or `setup.cfg`" : ""}
1784
+ \`\`\`swift
1785
+ import SwiftUI
1675
1786
 
1676
- ${lintCommand ? `Run \`${lintCommand}\` to check violations programmatically.` : ""}
1787
+ struct ContentView: View {
1788
+ @State private var count = 0
1789
+ @StateObject private var viewModel = ContentViewModel()
1677
1790
 
1678
- ## Review Process
1791
+ var body: some View {
1792
+ VStack(spacing: 16) {
1793
+ Text("Count: \\(count)")
1794
+ .font(.title)
1679
1795
 
1680
- 1. Run \`git diff\` to identify changed files
1681
- 2. Analyze each change for:
1682
- - Security vulnerabilities (OWASP Top 10)
1683
- - Performance issues
1684
- - Code style violations
1685
- - Missing error handling
1686
- - Test coverage gaps
1796
+ Button("Increment") {
1797
+ count += 1
1798
+ }
1799
+ .buttonStyle(.borderedProminent)
1800
+ }
1801
+ .padding()
1802
+ }
1803
+ }
1687
1804
 
1688
- ## Output Format
1805
+ #Preview {
1806
+ ContentView()
1807
+ }
1808
+ \`\`\`
1689
1809
 
1690
- For each finding:
1810
+ ## Property Wrappers
1691
1811
 
1692
- - **Critical**: Must fix before merge
1693
- - **Warning**: Should address
1694
- - **Suggestion**: Consider improving
1812
+ | Wrapper | Use Case |
1813
+ |---------|----------|
1814
+ | \`@State\` | Simple value types owned by view |
1815
+ | \`@Binding\` | Two-way connection to parent's state |
1816
+ | \`@StateObject\` | Reference type owned by view (create once) |
1817
+ | \`@ObservedObject\` | Reference type passed from parent |
1818
+ | \`@EnvironmentObject\` | Shared data through view hierarchy |
1819
+ | \`@Environment\` | System environment values |
1695
1820
 
1696
- Include file:line references for each issue.
1697
- `,
1698
- isNew: true
1699
- };
1700
- }
1701
- function generateTestWriterAgent(stack) {
1702
- const testCommand = getTestCommand(stack);
1703
- return {
1704
- type: "agent",
1705
- path: ".claude/agents/test-writer.md",
1706
- content: `---
1707
- name: test-writer
1708
- description: Generates comprehensive tests for code
1709
- tools: Read, Grep, Glob, Write, Edit, Bash(${testCommand})
1710
- model: sonnet
1711
- ---
1821
+ ## MVVM Pattern
1712
1822
 
1713
- You are a testing expert who writes thorough, maintainable tests.
1823
+ \`\`\`swift
1824
+ // ViewModel
1825
+ @MainActor
1826
+ class UserViewModel: ObservableObject {
1827
+ @Published var users: [User] = []
1828
+ @Published var isLoading = false
1829
+ @Published var error: Error?
1714
1830
 
1715
- ## Testing Framework
1831
+ private let service: UserService
1716
1832
 
1717
- This project uses: **${stack.testingFramework || "unknown"}**
1833
+ init(service: UserService = .shared) {
1834
+ self.service = service
1835
+ }
1718
1836
 
1719
- ## Your Process
1837
+ func fetchUsers() async {
1838
+ isLoading = true
1839
+ defer { isLoading = false }
1720
1840
 
1721
- 1. Read the code to be tested
1722
- 2. Identify test cases:
1723
- - Happy path scenarios
1724
- - Edge cases
1725
- - Error conditions
1726
- - Boundary values
1727
- 3. Write tests following project patterns
1728
- 4. Run tests to verify they pass
1841
+ do {
1842
+ users = try await service.getUsers()
1843
+ } catch {
1844
+ self.error = error
1845
+ }
1846
+ }
1847
+ }
1729
1848
 
1730
- ## Test Structure
1849
+ // View
1850
+ struct UsersView: View {
1851
+ @StateObject private var viewModel = UserViewModel()
1731
1852
 
1732
- Follow the AAA pattern:
1733
- - **Arrange**: Set up test data
1734
- - **Act**: Execute the code
1735
- - **Assert**: Verify results
1853
+ var body: some View {
1854
+ List(viewModel.users) { user in
1855
+ UserRow(user: user)
1856
+ }
1857
+ .overlay {
1858
+ if viewModel.isLoading {
1859
+ ProgressView()
1860
+ }
1861
+ }
1862
+ .task {
1863
+ await viewModel.fetchUsers()
1864
+ }
1865
+ }
1866
+ }
1867
+ \`\`\`
1736
1868
 
1737
- ## Guidelines
1869
+ ## Navigation (iOS 16+)
1870
+
1871
+ \`\`\`swift
1872
+ struct AppNavigation: View {
1873
+ @State private var path = NavigationPath()
1874
+
1875
+ var body: some View {
1876
+ NavigationStack(path: $path) {
1877
+ HomeView()
1878
+ .navigationDestination(for: User.self) { user in
1879
+ UserDetailView(user: user)
1880
+ }
1881
+ .navigationDestination(for: Settings.self) { settings in
1882
+ SettingsView(settings: settings)
1883
+ }
1884
+ }
1885
+ }
1886
+ }
1887
+ \`\`\`
1738
1888
 
1739
- - One assertion focus per test
1740
- - Descriptive test names
1741
- - Mock external dependencies
1742
- - Don't test implementation details
1743
- - Aim for behavior coverage
1889
+ ## Async/Await Patterns
1744
1890
 
1745
- ## Run Tests
1891
+ \`\`\`swift
1892
+ // Task modifier for view lifecycle
1893
+ .task {
1894
+ await loadData()
1895
+ }
1746
1896
 
1747
- \`\`\`bash
1748
- ${testCommand}
1897
+ // Task with cancellation
1898
+ .task(id: searchText) {
1899
+ try? await Task.sleep(for: .milliseconds(300))
1900
+ await search(searchText)
1901
+ }
1902
+
1903
+ // Refreshable
1904
+ .refreshable {
1905
+ await viewModel.refresh()
1906
+ }
1749
1907
  \`\`\`
1908
+
1909
+ ## Best Practices
1910
+
1911
+ 1. **Keep views small** - Extract subviews for reusability
1912
+ 2. **Use \`@MainActor\`** - For ViewModels updating UI
1913
+ 3. **Prefer value types** - Structs over classes when possible
1914
+ 4. **Use \`.task\`** - Instead of \`.onAppear\` for async work
1915
+ 5. **Preview extensively** - Use #Preview for rapid iteration
1750
1916
  `,
1751
1917
  isNew: true
1752
1918
  };
1753
1919
  }
1754
- function getLintCommand(stack) {
1755
- switch (stack.linter) {
1756
- case "eslint":
1757
- return `${stack.packageManager === "bun" ? "bun" : "npx"} eslint .`;
1758
- case "biome":
1759
- return `${stack.packageManager === "bun" ? "bun" : "npx"} biome check .`;
1760
- case "ruff":
1761
- return "ruff check .";
1762
- default:
1763
- return "";
1764
- }
1765
- }
1766
- function getTestCommand(stack) {
1767
- switch (stack.testingFramework) {
1768
- case "vitest":
1769
- return `${stack.packageManager || "npm"} ${stack.packageManager === "npm" ? "run " : ""}test`;
1770
- case "jest":
1771
- return `${stack.packageManager || "npm"} ${stack.packageManager === "npm" ? "run " : ""}test`;
1772
- case "bun-test":
1773
- return "bun test";
1774
- case "pytest":
1775
- return "pytest";
1776
- case "go-test":
1777
- return "go test ./...";
1778
- case "rust-test":
1779
- return "cargo test";
1780
- default:
1781
- return `${stack.packageManager || "npm"} test`;
1782
- }
1783
- }
1784
- function generateRules(stack) {
1785
- const artifacts = [];
1786
- if (stack.languages.includes("typescript")) {
1787
- artifacts.push(generateTypeScriptRules());
1788
- }
1789
- if (stack.languages.includes("python")) {
1790
- artifacts.push(generatePythonRules());
1791
- }
1792
- artifacts.push(generateCodeStyleRule(stack));
1793
- return artifacts;
1794
- }
1795
- function generateTypeScriptRules() {
1920
+ function generateUIKitSkill() {
1796
1921
  return {
1797
- type: "rule",
1798
- path: ".claude/rules/typescript.md",
1922
+ type: "skill",
1923
+ path: ".claude/skills/uikit-patterns.md",
1799
1924
  content: `---
1800
- paths:
1801
- - "**/*.ts"
1802
- - "**/*.tsx"
1925
+ name: uikit-patterns
1926
+ description: UIKit view controller patterns and best practices
1927
+ globs:
1928
+ - "**/*.swift"
1803
1929
  ---
1804
1930
 
1805
- # TypeScript Rules
1931
+ # UIKit Patterns
1806
1932
 
1807
- ## Type Safety
1933
+ ## View Controller Structure
1808
1934
 
1809
- - Avoid \`any\` - use \`unknown\` and narrow types
1810
- - Prefer interfaces for objects, types for unions/intersections
1811
- - Use strict mode (\`strict: true\` in tsconfig)
1812
- - Enable \`noUncheckedIndexedAccess\` for safer array access
1935
+ \`\`\`swift
1936
+ import UIKit
1813
1937
 
1814
- ## Patterns
1938
+ class UserViewController: UIViewController {
1939
+ // MARK: - Properties
1940
+ private let viewModel: UserViewModel
1941
+ private var cancellables = Set<AnyCancellable>()
1815
1942
 
1816
- \`\`\`typescript
1817
- // Prefer
1818
- const user: User | undefined = users.find(u => u.id === id);
1819
- if (user) { /* use user */ }
1943
+ // MARK: - UI Components
1944
+ private lazy var tableView: UITableView = {
1945
+ let table = UITableView()
1946
+ table.translatesAutoresizingMaskIntoConstraints = false
1947
+ table.delegate = self
1948
+ table.dataSource = self
1949
+ table.register(UserCell.self, forCellReuseIdentifier: UserCell.identifier)
1950
+ return table
1951
+ }()
1820
1952
 
1821
- // Avoid
1822
- const user = users.find(u => u.id === id) as User;
1823
- \`\`\`
1953
+ // MARK: - Lifecycle
1954
+ init(viewModel: UserViewModel) {
1955
+ self.viewModel = viewModel
1956
+ super.init(nibName: nil, bundle: nil)
1957
+ }
1824
1958
 
1825
- ## Naming
1959
+ required init?(coder: NSCoder) {
1960
+ fatalError("init(coder:) has not been implemented")
1961
+ }
1826
1962
 
1827
- - Interfaces: PascalCase (e.g., \`UserProfile\`)
1828
- - Types: PascalCase (e.g., \`ApiResponse\`)
1829
- - Functions: camelCase (e.g., \`getUserById\`)
1830
- - Constants: SCREAMING_SNAKE_CASE for true constants
1963
+ override func viewDidLoad() {
1964
+ super.viewDidLoad()
1965
+ setupUI()
1966
+ setupBindings()
1967
+ viewModel.fetchUsers()
1968
+ }
1831
1969
 
1832
- ## Imports
1970
+ // MARK: - Setup
1971
+ private func setupUI() {
1972
+ view.backgroundColor = .systemBackground
1973
+ view.addSubview(tableView)
1974
+
1975
+ NSLayoutConstraint.activate([
1976
+ tableView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
1977
+ tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
1978
+ tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
1979
+ tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
1980
+ ])
1981
+ }
1833
1982
 
1834
- - Group imports: external, internal, relative
1835
- - Use path aliases when configured
1836
- - Prefer named exports over default exports
1837
- `,
1838
- isNew: true
1839
- };
1983
+ private func setupBindings() {
1984
+ viewModel.$users
1985
+ .receive(on: DispatchQueue.main)
1986
+ .sink { [weak self] _ in
1987
+ self?.tableView.reloadData()
1988
+ }
1989
+ .store(in: &cancellables)
1990
+ }
1840
1991
  }
1841
- function generatePythonRules() {
1842
- return {
1843
- type: "rule",
1844
- path: ".claude/rules/python.md",
1845
- content: `---
1846
- paths:
1847
- - "**/*.py"
1848
- ---
1992
+ \`\`\`
1849
1993
 
1850
- # Python Rules
1994
+ ## Coordinator Pattern
1851
1995
 
1852
- ## Style
1996
+ \`\`\`swift
1997
+ protocol Coordinator: AnyObject {
1998
+ var childCoordinators: [Coordinator] { get set }
1999
+ var navigationController: UINavigationController { get set }
2000
+ func start()
2001
+ }
1853
2002
 
1854
- - Follow PEP 8
1855
- - Use type hints for function signatures
1856
- - Docstrings for public functions (Google style)
1857
- - Max line length: 88 (Black default)
2003
+ class AppCoordinator: Coordinator {
2004
+ var childCoordinators: [Coordinator] = []
2005
+ var navigationController: UINavigationController
1858
2006
 
1859
- ## Patterns
2007
+ init(navigationController: UINavigationController) {
2008
+ self.navigationController = navigationController
2009
+ }
1860
2010
 
1861
- \`\`\`python
1862
- # Prefer
1863
- def get_user(user_id: int) -> User | None:
1864
- """Fetch user by ID.
2011
+ func start() {
2012
+ let vc = HomeViewController()
2013
+ vc.coordinator = self
2014
+ navigationController.pushViewController(vc, animated: false)
2015
+ }
1865
2016
 
1866
- Args:
2017
+ func showUserDetail(_ user: User) {
2018
+ let vc = UserDetailViewController(user: user)
2019
+ navigationController.pushViewController(vc, animated: true)
2020
+ }
2021
+ }
2022
+ \`\`\`
2023
+
2024
+ ## Table View Cell
2025
+
2026
+ \`\`\`swift
2027
+ class UserCell: UITableViewCell {
2028
+ static let identifier = "UserCell"
2029
+
2030
+ private let nameLabel: UILabel = {
2031
+ let label = UILabel()
2032
+ label.font = .preferredFont(forTextStyle: .headline)
2033
+ label.translatesAutoresizingMaskIntoConstraints = false
2034
+ return label
2035
+ }()
2036
+
2037
+ override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
2038
+ super.init(style: style, reuseIdentifier: reuseIdentifier)
2039
+ setupUI()
2040
+ }
2041
+
2042
+ required init?(coder: NSCoder) {
2043
+ fatalError("init(coder:) has not been implemented")
2044
+ }
2045
+
2046
+ private func setupUI() {
2047
+ contentView.addSubview(nameLabel)
2048
+ NSLayoutConstraint.activate([
2049
+ nameLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 16),
2050
+ nameLabel.centerYAnchor.constraint(equalTo: contentView.centerYAnchor)
2051
+ ])
2052
+ }
2053
+
2054
+ func configure(with user: User) {
2055
+ nameLabel.text = user.name
2056
+ }
2057
+ }
2058
+ \`\`\`
2059
+
2060
+ ## Best Practices
2061
+
2062
+ 1. **Use Auto Layout** - Programmatic constraints over Storyboards
2063
+ 2. **MARK comments** - Organize code sections
2064
+ 3. **Coordinator pattern** - For navigation logic
2065
+ 4. **Dependency injection** - Pass dependencies via init
2066
+ 5. **Combine for bindings** - Reactive updates from ViewModel
2067
+ `,
2068
+ isNew: true
2069
+ };
2070
+ }
2071
+ function generateVaporSkill() {
2072
+ return {
2073
+ type: "skill",
2074
+ path: ".claude/skills/vapor-patterns.md",
2075
+ content: `---
2076
+ name: vapor-patterns
2077
+ description: Vapor server-side Swift patterns
2078
+ globs:
2079
+ - "**/*.swift"
2080
+ - "Package.swift"
2081
+ ---
2082
+
2083
+ # Vapor Patterns
2084
+
2085
+ ## Route Structure
2086
+
2087
+ \`\`\`swift
2088
+ import Vapor
2089
+
2090
+ func routes(_ app: Application) throws {
2091
+ // Basic routes
2092
+ app.get { req in
2093
+ "Hello, world!"
2094
+ }
2095
+
2096
+ // Route groups
2097
+ let api = app.grouped("api", "v1")
2098
+
2099
+ // Controller registration
2100
+ try api.register(collection: UserController())
2101
+ }
2102
+ \`\`\`
2103
+
2104
+ ## Controller Pattern
2105
+
2106
+ \`\`\`swift
2107
+ import Vapor
2108
+
2109
+ struct UserController: RouteCollection {
2110
+ func boot(routes: RoutesBuilder) throws {
2111
+ let users = routes.grouped("users")
2112
+
2113
+ users.get(use: index)
2114
+ users.post(use: create)
2115
+ users.group(":userID") { user in
2116
+ user.get(use: show)
2117
+ user.put(use: update)
2118
+ user.delete(use: delete)
2119
+ }
2120
+ }
2121
+
2122
+ // GET /users
2123
+ func index(req: Request) async throws -> [UserDTO] {
2124
+ try await User.query(on: req.db).all().map { $0.toDTO() }
2125
+ }
2126
+
2127
+ // POST /users
2128
+ func create(req: Request) async throws -> UserDTO {
2129
+ let input = try req.content.decode(CreateUserInput.self)
2130
+ let user = User(name: input.name, email: input.email)
2131
+ try await user.save(on: req.db)
2132
+ return user.toDTO()
2133
+ }
2134
+
2135
+ // GET /users/:userID
2136
+ func show(req: Request) async throws -> UserDTO {
2137
+ guard let user = try await User.find(req.parameters.get("userID"), on: req.db) else {
2138
+ throw Abort(.notFound)
2139
+ }
2140
+ return user.toDTO()
2141
+ }
2142
+ }
2143
+ \`\`\`
2144
+
2145
+ ## Fluent Models
2146
+
2147
+ \`\`\`swift
2148
+ import Fluent
2149
+ import Vapor
2150
+
2151
+ final class User: Model, Content {
2152
+ static let schema = "users"
2153
+
2154
+ @ID(key: .id)
2155
+ var id: UUID?
2156
+
2157
+ @Field(key: "name")
2158
+ var name: String
2159
+
2160
+ @Field(key: "email")
2161
+ var email: String
2162
+
2163
+ @Timestamp(key: "created_at", on: .create)
2164
+ var createdAt: Date?
2165
+
2166
+ @Children(for: \\.$user)
2167
+ var posts: [Post]
2168
+
2169
+ init() {}
2170
+
2171
+ init(id: UUID? = nil, name: String, email: String) {
2172
+ self.id = id
2173
+ self.name = name
2174
+ self.email = email
2175
+ }
2176
+ }
2177
+
2178
+ // Migration
2179
+ struct CreateUser: AsyncMigration {
2180
+ func prepare(on database: Database) async throws {
2181
+ try await database.schema("users")
2182
+ .id()
2183
+ .field("name", .string, .required)
2184
+ .field("email", .string, .required)
2185
+ .field("created_at", .datetime)
2186
+ .unique(on: "email")
2187
+ .create()
2188
+ }
2189
+
2190
+ func revert(on database: Database) async throws {
2191
+ try await database.schema("users").delete()
2192
+ }
2193
+ }
2194
+ \`\`\`
2195
+
2196
+ ## DTOs and Validation
2197
+
2198
+ \`\`\`swift
2199
+ struct CreateUserInput: Content, Validatable {
2200
+ var name: String
2201
+ var email: String
2202
+
2203
+ static func validations(_ validations: inout Validations) {
2204
+ validations.add("name", as: String.self, is: !.empty)
2205
+ validations.add("email", as: String.self, is: .email)
2206
+ }
2207
+ }
2208
+
2209
+ struct UserDTO: Content {
2210
+ var id: UUID?
2211
+ var name: String
2212
+ var email: String
2213
+ }
2214
+
2215
+ extension User {
2216
+ func toDTO() -> UserDTO {
2217
+ UserDTO(id: id, name: name, email: email)
2218
+ }
2219
+ }
2220
+ \`\`\`
2221
+
2222
+ ## Middleware
2223
+
2224
+ \`\`\`swift
2225
+ struct AuthMiddleware: AsyncMiddleware {
2226
+ func respond(to request: Request, chainingTo next: AsyncResponder) async throws -> Response {
2227
+ guard let token = request.headers.bearerAuthorization?.token else {
2228
+ throw Abort(.unauthorized)
2229
+ }
2230
+
2231
+ // Validate token
2232
+ let user = try await validateToken(token, on: request)
2233
+ request.auth.login(user)
2234
+
2235
+ return try await next.respond(to: request)
2236
+ }
2237
+ }
2238
+ \`\`\`
2239
+
2240
+ ## Testing
2241
+
2242
+ \`\`\`swift
2243
+ @testable import App
2244
+ import XCTVapor
2245
+
2246
+ final class UserTests: XCTestCase {
2247
+ var app: Application!
2248
+
2249
+ override func setUp() async throws {
2250
+ app = Application(.testing)
2251
+ try configure(app)
2252
+ try await app.autoMigrate()
2253
+ }
2254
+
2255
+ override func tearDown() async throws {
2256
+ try await app.autoRevert()
2257
+ app.shutdown()
2258
+ }
2259
+
2260
+ func testCreateUser() async throws {
2261
+ try app.test(.POST, "api/v1/users", beforeRequest: { req in
2262
+ try req.content.encode(CreateUserInput(name: "Test", email: "test@example.com"))
2263
+ }, afterResponse: { res in
2264
+ XCTAssertEqual(res.status, .ok)
2265
+ let user = try res.content.decode(UserDTO.self)
2266
+ XCTAssertEqual(user.name, "Test")
2267
+ })
2268
+ }
2269
+ }
2270
+ \`\`\`
2271
+ `,
2272
+ isNew: true
2273
+ };
2274
+ }
2275
+ function generateJetpackComposeSkill() {
2276
+ return {
2277
+ type: "skill",
2278
+ path: ".claude/skills/compose-patterns.md",
2279
+ content: `---
2280
+ name: compose-patterns
2281
+ description: Jetpack Compose UI patterns and best practices
2282
+ globs:
2283
+ - "**/*.kt"
2284
+ ---
2285
+
2286
+ # Jetpack Compose Patterns
2287
+
2288
+ ## Composable Structure
2289
+
2290
+ \`\`\`kotlin
2291
+ @Composable
2292
+ fun UserScreen(
2293
+ viewModel: UserViewModel = hiltViewModel()
2294
+ ) {
2295
+ val uiState by viewModel.uiState.collectAsStateWithLifecycle()
2296
+
2297
+ UserScreenContent(
2298
+ uiState = uiState,
2299
+ onRefresh = viewModel::refresh,
2300
+ onUserClick = viewModel::selectUser
2301
+ )
2302
+ }
2303
+
2304
+ @Composable
2305
+ private fun UserScreenContent(
2306
+ uiState: UserUiState,
2307
+ onRefresh: () -> Unit,
2308
+ onUserClick: (User) -> Unit
2309
+ ) {
2310
+ Scaffold(
2311
+ topBar = {
2312
+ TopAppBar(title = { Text("Users") })
2313
+ }
2314
+ ) { padding ->
2315
+ when (uiState) {
2316
+ is UserUiState.Loading -> LoadingIndicator()
2317
+ is UserUiState.Success -> UserList(
2318
+ users = uiState.users,
2319
+ onUserClick = onUserClick,
2320
+ modifier = Modifier.padding(padding)
2321
+ )
2322
+ is UserUiState.Error -> ErrorMessage(uiState.message)
2323
+ }
2324
+ }
2325
+ }
2326
+ \`\`\`
2327
+
2328
+ ## State Management
2329
+
2330
+ \`\`\`kotlin
2331
+ // UI State
2332
+ sealed interface UserUiState {
2333
+ object Loading : UserUiState
2334
+ data class Success(val users: List<User>) : UserUiState
2335
+ data class Error(val message: String) : UserUiState
2336
+ }
2337
+
2338
+ // ViewModel
2339
+ @HiltViewModel
2340
+ class UserViewModel @Inject constructor(
2341
+ private val repository: UserRepository
2342
+ ) : ViewModel() {
2343
+
2344
+ private val _uiState = MutableStateFlow<UserUiState>(UserUiState.Loading)
2345
+ val uiState: StateFlow<UserUiState> = _uiState.asStateFlow()
2346
+
2347
+ init {
2348
+ loadUsers()
2349
+ }
2350
+
2351
+ fun refresh() {
2352
+ loadUsers()
2353
+ }
2354
+
2355
+ private fun loadUsers() {
2356
+ viewModelScope.launch {
2357
+ _uiState.value = UserUiState.Loading
2358
+ repository.getUsers()
2359
+ .onSuccess { users ->
2360
+ _uiState.value = UserUiState.Success(users)
2361
+ }
2362
+ .onFailure { error ->
2363
+ _uiState.value = UserUiState.Error(error.message ?: "Unknown error")
2364
+ }
2365
+ }
2366
+ }
2367
+ }
2368
+ \`\`\`
2369
+
2370
+ ## Reusable Components
2371
+
2372
+ \`\`\`kotlin
2373
+ @Composable
2374
+ fun UserCard(
2375
+ user: User,
2376
+ onClick: () -> Unit,
2377
+ modifier: Modifier = Modifier
2378
+ ) {
2379
+ Card(
2380
+ onClick = onClick,
2381
+ modifier = modifier.fillMaxWidth()
2382
+ ) {
2383
+ Row(
2384
+ modifier = Modifier.padding(16.dp),
2385
+ verticalAlignment = Alignment.CenterVertically
2386
+ ) {
2387
+ AsyncImage(
2388
+ model = user.avatarUrl,
2389
+ contentDescription = null,
2390
+ modifier = Modifier
2391
+ .size(48.dp)
2392
+ .clip(CircleShape)
2393
+ )
2394
+ Spacer(modifier = Modifier.width(16.dp))
2395
+ Column {
2396
+ Text(
2397
+ text = user.name,
2398
+ style = MaterialTheme.typography.titleMedium
2399
+ )
2400
+ Text(
2401
+ text = user.email,
2402
+ style = MaterialTheme.typography.bodySmall
2403
+ )
2404
+ }
2405
+ }
2406
+ }
2407
+ }
2408
+ \`\`\`
2409
+
2410
+ ## Navigation
2411
+
2412
+ \`\`\`kotlin
2413
+ @Composable
2414
+ fun AppNavigation() {
2415
+ val navController = rememberNavController()
2416
+
2417
+ NavHost(
2418
+ navController = navController,
2419
+ startDestination = "home"
2420
+ ) {
2421
+ composable("home") {
2422
+ HomeScreen(
2423
+ onUserClick = { userId ->
2424
+ navController.navigate("user/$userId")
2425
+ }
2426
+ )
2427
+ }
2428
+ composable(
2429
+ route = "user/{userId}",
2430
+ arguments = listOf(navArgument("userId") { type = NavType.StringType })
2431
+ ) { backStackEntry ->
2432
+ val userId = backStackEntry.arguments?.getString("userId")
2433
+ UserDetailScreen(userId = userId)
2434
+ }
2435
+ }
2436
+ }
2437
+ \`\`\`
2438
+
2439
+ ## Theming
2440
+
2441
+ \`\`\`kotlin
2442
+ @Composable
2443
+ fun AppTheme(
2444
+ darkTheme: Boolean = isSystemInDarkTheme(),
2445
+ content: @Composable () -> Unit
2446
+ ) {
2447
+ val colorScheme = if (darkTheme) {
2448
+ darkColorScheme(
2449
+ primary = Purple80,
2450
+ secondary = PurpleGrey80
2451
+ )
2452
+ } else {
2453
+ lightColorScheme(
2454
+ primary = Purple40,
2455
+ secondary = PurpleGrey40
2456
+ )
2457
+ }
2458
+
2459
+ MaterialTheme(
2460
+ colorScheme = colorScheme,
2461
+ typography = Typography,
2462
+ content = content
2463
+ )
2464
+ }
2465
+ \`\`\`
2466
+
2467
+ ## Best Practices
2468
+
2469
+ 1. **Stateless composables** - Pass state down, events up
2470
+ 2. **Remember wisely** - Use \`remember\` for expensive calculations
2471
+ 3. **Lifecycle-aware collection** - Use \`collectAsStateWithLifecycle()\`
2472
+ 4. **Modifier parameter** - Always accept Modifier as last parameter
2473
+ 5. **Preview annotations** - Add @Preview for rapid iteration
2474
+ `,
2475
+ isNew: true
2476
+ };
2477
+ }
2478
+ function generateAndroidViewsSkill() {
2479
+ return {
2480
+ type: "skill",
2481
+ path: ".claude/skills/android-views-patterns.md",
2482
+ content: `---
2483
+ name: android-views-patterns
2484
+ description: Android XML views and traditional patterns
2485
+ globs:
2486
+ - "**/*.kt"
2487
+ - "**/*.xml"
2488
+ ---
2489
+
2490
+ # Android Views Patterns
2491
+
2492
+ ## Activity Structure
2493
+
2494
+ \`\`\`kotlin
2495
+ class MainActivity : AppCompatActivity() {
2496
+
2497
+ private lateinit var binding: ActivityMainBinding
2498
+ private val viewModel: MainViewModel by viewModels()
2499
+
2500
+ override fun onCreate(savedInstanceState: Bundle?) {
2501
+ super.onCreate(savedInstanceState)
2502
+ binding = ActivityMainBinding.inflate(layoutInflater)
2503
+ setContentView(binding.root)
2504
+
2505
+ setupUI()
2506
+ observeViewModel()
2507
+ }
2508
+
2509
+ private fun setupUI() {
2510
+ binding.recyclerView.apply {
2511
+ layoutManager = LinearLayoutManager(this@MainActivity)
2512
+ adapter = userAdapter
2513
+ }
2514
+
2515
+ binding.swipeRefresh.setOnRefreshListener {
2516
+ viewModel.refresh()
2517
+ }
2518
+ }
2519
+
2520
+ private fun observeViewModel() {
2521
+ lifecycleScope.launch {
2522
+ repeatOnLifecycle(Lifecycle.State.STARTED) {
2523
+ viewModel.uiState.collect { state ->
2524
+ updateUI(state)
2525
+ }
2526
+ }
2527
+ }
2528
+ }
2529
+
2530
+ private fun updateUI(state: MainUiState) {
2531
+ binding.swipeRefresh.isRefreshing = state.isLoading
2532
+ userAdapter.submitList(state.users)
2533
+ binding.errorText.isVisible = state.error != null
2534
+ binding.errorText.text = state.error
2535
+ }
2536
+ }
2537
+ \`\`\`
2538
+
2539
+ ## Fragment Pattern
2540
+
2541
+ \`\`\`kotlin
2542
+ class UserFragment : Fragment(R.layout.fragment_user) {
2543
+
2544
+ private var _binding: FragmentUserBinding? = null
2545
+ private val binding get() = _binding!!
2546
+
2547
+ private val viewModel: UserViewModel by viewModels()
2548
+ private val args: UserFragmentArgs by navArgs()
2549
+
2550
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
2551
+ super.onViewCreated(view, savedInstanceState)
2552
+ _binding = FragmentUserBinding.bind(view)
2553
+
2554
+ setupUI()
2555
+ observeViewModel()
2556
+ viewModel.loadUser(args.userId)
2557
+ }
2558
+
2559
+ override fun onDestroyView() {
2560
+ super.onDestroyView()
2561
+ _binding = null
2562
+ }
2563
+ }
2564
+ \`\`\`
2565
+
2566
+ ## RecyclerView Adapter
2567
+
2568
+ \`\`\`kotlin
2569
+ class UserAdapter(
2570
+ private val onItemClick: (User) -> Unit
2571
+ ) : ListAdapter<User, UserAdapter.ViewHolder>(UserDiffCallback()) {
2572
+
2573
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
2574
+ val binding = ItemUserBinding.inflate(
2575
+ LayoutInflater.from(parent.context),
2576
+ parent,
2577
+ false
2578
+ )
2579
+ return ViewHolder(binding)
2580
+ }
2581
+
2582
+ override fun onBindViewHolder(holder: ViewHolder, position: Int) {
2583
+ holder.bind(getItem(position))
2584
+ }
2585
+
2586
+ inner class ViewHolder(
2587
+ private val binding: ItemUserBinding
2588
+ ) : RecyclerView.ViewHolder(binding.root) {
2589
+
2590
+ init {
2591
+ binding.root.setOnClickListener {
2592
+ onItemClick(getItem(adapterPosition))
2593
+ }
2594
+ }
2595
+
2596
+ fun bind(user: User) {
2597
+ binding.nameText.text = user.name
2598
+ binding.emailText.text = user.email
2599
+ Glide.with(binding.avatar)
2600
+ .load(user.avatarUrl)
2601
+ .circleCrop()
2602
+ .into(binding.avatar)
2603
+ }
2604
+ }
2605
+
2606
+ class UserDiffCallback : DiffUtil.ItemCallback<User>() {
2607
+ override fun areItemsTheSame(oldItem: User, newItem: User) =
2608
+ oldItem.id == newItem.id
2609
+
2610
+ override fun areContentsTheSame(oldItem: User, newItem: User) =
2611
+ oldItem == newItem
2612
+ }
2613
+ }
2614
+ \`\`\`
2615
+
2616
+ ## XML Layout
2617
+
2618
+ \`\`\`xml
2619
+ <?xml version="1.0" encoding="utf-8"?>
2620
+ <androidx.constraintlayout.widget.ConstraintLayout
2621
+ xmlns:android="http://schemas.android.com/apk/res/android"
2622
+ xmlns:app="http://schemas.android.com/apk/res-auto"
2623
+ android:layout_width="match_parent"
2624
+ android:layout_height="match_parent">
2625
+
2626
+ <com.google.android.material.appbar.MaterialToolbar
2627
+ android:id="@+id/toolbar"
2628
+ android:layout_width="0dp"
2629
+ android:layout_height="?attr/actionBarSize"
2630
+ app:title="Users"
2631
+ app:layout_constraintTop_toTopOf="parent"
2632
+ app:layout_constraintStart_toStartOf="parent"
2633
+ app:layout_constraintEnd_toEndOf="parent" />
2634
+
2635
+ <androidx.swiperefreshlayout.widget.SwipeRefreshLayout
2636
+ android:id="@+id/swipeRefresh"
2637
+ android:layout_width="0dp"
2638
+ android:layout_height="0dp"
2639
+ app:layout_constraintTop_toBottomOf="@id/toolbar"
2640
+ app:layout_constraintBottom_toBottomOf="parent"
2641
+ app:layout_constraintStart_toStartOf="parent"
2642
+ app:layout_constraintEnd_toEndOf="parent">
2643
+
2644
+ <androidx.recyclerview.widget.RecyclerView
2645
+ android:id="@+id/recyclerView"
2646
+ android:layout_width="match_parent"
2647
+ android:layout_height="match_parent" />
2648
+
2649
+ </androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
2650
+
2651
+ </androidx.constraintlayout.widget.ConstraintLayout>
2652
+ \`\`\`
2653
+
2654
+ ## ViewModel with Repository
2655
+
2656
+ \`\`\`kotlin
2657
+ @HiltViewModel
2658
+ class UserViewModel @Inject constructor(
2659
+ private val repository: UserRepository,
2660
+ private val savedStateHandle: SavedStateHandle
2661
+ ) : ViewModel() {
2662
+
2663
+ private val _uiState = MutableStateFlow(UserUiState())
2664
+ val uiState: StateFlow<UserUiState> = _uiState.asStateFlow()
2665
+
2666
+ fun loadUsers() {
2667
+ viewModelScope.launch {
2668
+ _uiState.update { it.copy(isLoading = true) }
2669
+ try {
2670
+ val users = repository.getUsers()
2671
+ _uiState.update { it.copy(users = users, isLoading = false) }
2672
+ } catch (e: Exception) {
2673
+ _uiState.update { it.copy(error = e.message, isLoading = false) }
2674
+ }
2675
+ }
2676
+ }
2677
+ }
2678
+
2679
+ data class UserUiState(
2680
+ val users: List<User> = emptyList(),
2681
+ val isLoading: Boolean = false,
2682
+ val error: String? = null
2683
+ )
2684
+ \`\`\`
2685
+
2686
+ ## Best Practices
2687
+
2688
+ 1. **View Binding** - Use over findViewById or synthetic imports
2689
+ 2. **Lifecycle awareness** - Collect flows in repeatOnLifecycle
2690
+ 3. **ListAdapter** - For efficient RecyclerView updates
2691
+ 4. **Navigation Component** - For fragment navigation
2692
+ 5. **Clean up bindings** - Set to null in onDestroyView
2693
+ `,
2694
+ isNew: true
2695
+ };
2696
+ }
2697
+ function generateIterativeDevelopmentSkill(stack) {
2698
+ const testCmd = getTestCommand(stack);
2699
+ const lintCmd = getLintCommand(stack);
2700
+ return {
2701
+ type: "skill",
2702
+ path: ".claude/skills/iterative-development.md",
2703
+ content: `---
2704
+ name: iterative-development
2705
+ description: TDD-driven iterative loops until tests pass
2706
+ globs:
2707
+ - "**/*.ts"
2708
+ - "**/*.tsx"
2709
+ - "**/*.js"
2710
+ - "**/*.py"
2711
+ - "**/*.go"
2712
+ ---
2713
+
2714
+ # Iterative Development (TDD Loops)
2715
+
2716
+ Self-referential development loops where you iterate until completion criteria are met.
2717
+
2718
+ ## Core Philosophy
2719
+
2720
+ \`\`\`
2721
+ \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510
2722
+ \u2502 ITERATION > PERFECTION \u2502
2723
+ \u2502 Don't aim for perfect on first try. \u2502
2724
+ \u2502 Let the loop refine the work. \u2502
2725
+ \u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524
2726
+ \u2502 FAILURES ARE DATA \u2502
2727
+ \u2502 Failed tests, lint errors, type mismatches are signals. \u2502
2728
+ \u2502 Use them to guide the next iteration. \u2502
2729
+ \u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524
2730
+ \u2502 CLEAR COMPLETION CRITERIA \u2502
2731
+ \u2502 Define exactly what "done" looks like. \u2502
2732
+ \u2502 Tests passing. Coverage met. Lint clean. \u2502
2733
+ \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518
2734
+ \`\`\`
2735
+
2736
+ ## TDD Workflow (Mandatory)
2737
+
2738
+ Every implementation task MUST follow this workflow:
2739
+
2740
+ ### 1. RED: Write Tests First
2741
+ \`\`\`bash
2742
+ # Write tests based on requirements
2743
+ # Run tests - they MUST FAIL
2744
+ ${testCmd}
2745
+ \`\`\`
2746
+
2747
+ ### 2. GREEN: Implement Feature
2748
+ \`\`\`bash
2749
+ # Write minimum code to pass tests
2750
+ # Run tests - they MUST PASS
2751
+ ${testCmd}
2752
+ \`\`\`
2753
+
2754
+ ### 3. VALIDATE: Quality Gates
2755
+ \`\`\`bash
2756
+ # Full quality check
2757
+ ${lintCmd ? `${lintCmd} && ` : ""}${testCmd}
2758
+ \`\`\`
2759
+
2760
+ ## Completion Criteria Template
2761
+
2762
+ For any implementation task, define:
2763
+
2764
+ \`\`\`markdown
2765
+ ### Completion Criteria
2766
+ - [ ] All tests passing
2767
+ - [ ] Coverage >= 80% (on new code)
2768
+ - [ ] Lint clean (no errors)
2769
+ - [ ] Type check passing
2770
+ \`\`\`
2771
+
2772
+ ## When to Use This Workflow
2773
+
2774
+ | Task Type | Use TDD Loop? |
2775
+ |-----------|---------------|
2776
+ | New feature | \u2705 Always |
2777
+ | Bug fix | \u2705 Always (write test that reproduces bug first) |
2778
+ | Refactoring | \u2705 Always (existing tests must stay green) |
2779
+ | Spike/exploration | \u274C Skip (but document findings) |
2780
+ | Documentation | \u274C Skip |
2781
+
2782
+ ## Anti-Patterns
2783
+
2784
+ - \u274C Writing code before tests
2785
+ - \u274C Skipping the RED phase (tests that never fail are useless)
2786
+ - \u274C Moving on when tests fail
2787
+ - \u274C Large batches (prefer small, focused iterations)
2788
+ `,
2789
+ isNew: true
2790
+ };
2791
+ }
2792
+ function generateCommitHygieneSkill() {
2793
+ return {
2794
+ type: "skill",
2795
+ path: ".claude/skills/commit-hygiene.md",
2796
+ content: `---
2797
+ name: commit-hygiene
2798
+ description: Atomic commits, PR size limits, commit thresholds
2799
+ globs:
2800
+ - "**/*"
2801
+ ---
2802
+
2803
+ # Commit Hygiene
2804
+
2805
+ Keep commits atomic, PRs reviewable, and git history clean.
2806
+
2807
+ ## Size Thresholds
2808
+
2809
+ | Metric | \u{1F7E2} Good | \u{1F7E1} Warning | \u{1F534} Commit Now |
2810
+ |--------|---------|------------|---------------|
2811
+ | Files changed | 1-5 | 6-10 | > 10 |
2812
+ | Lines added | < 150 | 150-300 | > 300 |
2813
+ | Total changes | < 250 | 250-400 | > 400 |
2814
+
2815
+ **Research shows:** PRs > 400 lines have 40%+ defect rates vs 15% for smaller changes.
2816
+
2817
+ ## When to Commit
2818
+
2819
+ ### Commit Triggers (Any = Commit)
2820
+
2821
+ | Trigger | Action |
2822
+ |---------|--------|
2823
+ | Test passes | Just got a test green \u2192 commit |
2824
+ | Feature complete | Finished a function \u2192 commit |
2825
+ | Refactor done | Renamed across files \u2192 commit |
2826
+ | Bug fixed | Fixed the issue \u2192 commit |
2827
+ | Threshold hit | > 5 files or > 200 lines \u2192 commit |
2828
+
2829
+ ### Commit Immediately If
2830
+
2831
+ - \u2705 Tests are passing after being red
2832
+ - \u2705 You're about to make a "big change"
2833
+ - \u2705 You've been coding for 30+ minutes
2834
+ - \u2705 You're about to try something risky
2835
+ - \u2705 The current state is "working"
2836
+
2837
+ ## Atomic Commit Patterns
2838
+
2839
+ ### Good Commits \u2705
2840
+
2841
+ \`\`\`
2842
+ "Add email validation to signup form"
2843
+ - 3 files: validator.ts, signup.tsx, signup.test.ts
2844
+ - 120 lines changed
2845
+ - Single purpose: email validation
2846
+
2847
+ "Fix null pointer in user lookup"
2848
+ - 2 files: userService.ts, userService.test.ts
2849
+ - 25 lines changed
2850
+ - Single purpose: fix one bug
2851
+ \`\`\`
2852
+
2853
+ ### Bad Commits \u274C
2854
+
2855
+ \`\`\`
2856
+ "Add authentication, fix bugs, update styles"
2857
+ - 25 files changed, 800 lines
2858
+ - Multiple unrelated purposes
2859
+
2860
+ "WIP" / "Updates" / "Fix stuff"
2861
+ - Unknown scope, no clear purpose
2862
+ \`\`\`
2863
+
2864
+ ## Quick Status Check
2865
+
2866
+ Run frequently to check current state:
2867
+
2868
+ \`\`\`bash
2869
+ # See what's changed
2870
+ git status --short
2871
+
2872
+ # Count changes
2873
+ git diff --shortstat
2874
+
2875
+ # Full summary
2876
+ git diff --stat HEAD
2877
+ \`\`\`
2878
+
2879
+ ## PR Size Rules
2880
+
2881
+ | PR Size | Review Time | Quality |
2882
+ |---------|-------------|---------|
2883
+ | < 200 lines | < 30 min | High confidence |
2884
+ | 200-400 lines | 30-60 min | Good confidence |
2885
+ | 400-1000 lines | 1-2 hours | Declining quality |
2886
+ | > 1000 lines | Often skipped | Rubber-stamped |
2887
+
2888
+ **Best practice:** If a PR will be > 400 lines, split into stacked PRs.
2889
+ `,
2890
+ isNew: true
2891
+ };
2892
+ }
2893
+ function generateCodeDeduplicationSkill() {
2894
+ return {
2895
+ type: "skill",
2896
+ path: ".claude/skills/code-deduplication.md",
2897
+ content: `---
2898
+ name: code-deduplication
2899
+ description: Prevent semantic code duplication with capability index
2900
+ globs:
2901
+ - "**/*.ts"
2902
+ - "**/*.tsx"
2903
+ - "**/*.js"
2904
+ - "**/*.py"
2905
+ ---
2906
+
2907
+ # Code Deduplication
2908
+
2909
+ Prevent semantic duplication by maintaining awareness of existing capabilities.
2910
+
2911
+ ## Core Principle
2912
+
2913
+ \`\`\`
2914
+ \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510
2915
+ \u2502 CHECK BEFORE YOU WRITE \u2502
2916
+ \u2502 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 \u2502
2917
+ \u2502 AI doesn't copy/paste - it reimplements. \u2502
2918
+ \u2502 The problem isn't duplicate code, it's duplicate PURPOSE. \u2502
2919
+ \u2502 \u2502
2920
+ \u2502 Before writing ANY new function: \u2502
2921
+ \u2502 1. Search codebase for similar functionality \u2502
2922
+ \u2502 2. Check utils/, helpers/, lib/ for existing implementations \u2502
2923
+ \u2502 3. Extend existing code if possible \u2502
2924
+ \u2502 4. Only create new if nothing suitable exists \u2502
2925
+ \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518
2926
+ \`\`\`
2927
+
2928
+ ## Before Writing New Code
2929
+
2930
+ ### Search Checklist
2931
+
2932
+ 1. **Search by purpose**: "format date", "validate email", "fetch user"
2933
+ 2. **Search common locations**:
2934
+ - \`src/utils/\` or \`lib/\`
2935
+ - \`src/helpers/\`
2936
+ - \`src/common/\`
2937
+ - \`src/shared/\`
2938
+ 3. **Search by function signature**: Similar inputs/outputs
2939
+
2940
+ ### Common Duplicate Candidates
2941
+
2942
+ | Category | Look For |
2943
+ |----------|----------|
2944
+ | Date/Time | formatDate, parseDate, isExpired, addDays |
2945
+ | Validation | isEmail, isPhone, isURL, isUUID |
2946
+ | Strings | slugify, truncate, capitalize, pluralize |
2947
+ | API | fetchUser, createItem, handleError |
2948
+ | Auth | validateToken, requireAuth, getCurrentUser |
2949
+
2950
+ ## If Similar Code Exists
2951
+
2952
+ ### Option 1: Reuse directly
2953
+ \`\`\`typescript
2954
+ // Import and use existing function
2955
+ import { formatDate } from '@/utils/dates';
2956
+ \`\`\`
2957
+
2958
+ ### Option 2: Extend with options
2959
+ \`\`\`typescript
2960
+ // Add optional parameter to existing function
2961
+ export function formatDate(
2962
+ date: Date,
2963
+ format: string = 'short',
2964
+ locale?: string // NEW: added locale support
2965
+ ): string { ... }
2966
+ \`\`\`
2967
+
2968
+ ### Option 3: Compose from existing
2969
+ \`\`\`typescript
2970
+ // Build on existing utilities
2971
+ export function formatDateRange(start: Date, end: Date) {
2972
+ return \`\${formatDate(start)} - \${formatDate(end)}\`;
2973
+ }
2974
+ \`\`\`
2975
+
2976
+ ## File Header Pattern
2977
+
2978
+ Document what each file provides:
2979
+
2980
+ \`\`\`typescript
2981
+ /**
2982
+ * @file User validation utilities
2983
+ * @description Email, phone, and identity validation functions.
2984
+ *
2985
+ * Key exports:
2986
+ * - isEmail(email) - Validates email format
2987
+ * - isPhone(phone, country?) - Validates phone with country
2988
+ * - isValidUsername(username) - Checks username rules
2989
+ */
2990
+ \`\`\`
2991
+
2992
+ ## Anti-Patterns
2993
+
2994
+ - \u274C Writing date formatter without checking utils/
2995
+ - \u274C Creating new API client when one exists
2996
+ - \u274C Duplicating validation logic across files
2997
+ - \u274C Copy-pasting functions between files
2998
+ - \u274C "I'll refactor later" (you won't)
2999
+ `,
3000
+ isNew: true
3001
+ };
3002
+ }
3003
+ function generateSimplicityRulesSkill() {
3004
+ return {
3005
+ type: "skill",
3006
+ path: ".claude/skills/simplicity-rules.md",
3007
+ content: `---
3008
+ name: simplicity-rules
3009
+ description: Enforced code complexity constraints
3010
+ globs:
3011
+ - "**/*.ts"
3012
+ - "**/*.tsx"
3013
+ - "**/*.js"
3014
+ - "**/*.py"
3015
+ - "**/*.go"
3016
+ ---
3017
+
3018
+ # Simplicity Rules
3019
+
3020
+ Complexity is the enemy. Every line of code is a liability.
3021
+
3022
+ ## Enforced Limits
3023
+
3024
+ **CRITICAL: These limits are non-negotiable. Check and enforce for EVERY file.**
3025
+
3026
+ ### Function Level
3027
+
3028
+ | Constraint | Limit | Action if Exceeded |
3029
+ |------------|-------|-------------------|
3030
+ | Lines per function | 20 max | Decompose immediately |
3031
+ | Parameters | 3 max | Use options object |
3032
+ | Nesting levels | 2 max | Flatten with early returns |
3033
+
3034
+ ### File Level
3035
+
3036
+ | Constraint | Limit | Action if Exceeded |
3037
+ |------------|-------|-------------------|
3038
+ | Lines per file | 200 max | Split by responsibility |
3039
+ | Functions per file | 10 max | Split into modules |
3040
+
3041
+ ### Module Level
3042
+
3043
+ | Constraint | Limit | Reason |
3044
+ |------------|-------|--------|
3045
+ | Directory nesting | 3 levels max | Flat is better |
3046
+ | Circular deps | 0 | Never acceptable |
3047
+
3048
+ ## Enforcement Protocol
3049
+
3050
+ **Before completing ANY file:**
3051
+
3052
+ \`\`\`
3053
+ 1. Count total lines \u2192 if > 200, STOP and split
3054
+ 2. Count functions \u2192 if > 10, STOP and split
3055
+ 3. Check function length \u2192 if any > 20 lines, decompose
3056
+ 4. Check parameters \u2192 if any > 3, refactor to options object
3057
+ \`\`\`
3058
+
3059
+ ## Violation Response
3060
+
3061
+ When limits are exceeded:
3062
+
3063
+ \`\`\`
3064
+ \u26A0\uFE0F FILE SIZE VIOLATION DETECTED
3065
+
3066
+ [filename] has [X] lines (limit: 200)
3067
+
3068
+ Splitting into:
3069
+ - [filename-a].ts - [responsibility A]
3070
+ - [filename-b].ts - [responsibility B]
3071
+ \`\`\`
3072
+
3073
+ **Never defer refactoring.** Fix violations immediately.
3074
+
3075
+ ## Decomposition Patterns
3076
+
3077
+ ### Long Function \u2192 Multiple Functions
3078
+
3079
+ \`\`\`typescript
3080
+ // BEFORE: 40 lines
3081
+ function processOrder(order) {
3082
+ // validate... 10 lines
3083
+ // calculate totals... 15 lines
3084
+ // apply discounts... 10 lines
3085
+ // save... 5 lines
3086
+ }
3087
+
3088
+ // AFTER: 4 functions, each < 15 lines
3089
+ function processOrder(order) {
3090
+ validateOrder(order);
3091
+ const totals = calculateTotals(order);
3092
+ const finalPrice = applyDiscounts(totals, order.coupons);
3093
+ return saveOrder({ ...order, finalPrice });
3094
+ }
3095
+ \`\`\`
3096
+
3097
+ ### Many Parameters \u2192 Options Object
3098
+
3099
+ \`\`\`typescript
3100
+ // BEFORE: 6 parameters
3101
+ function createUser(name, email, password, role, team, settings) { }
3102
+
3103
+ // AFTER: 1 options object
3104
+ interface CreateUserOptions {
3105
+ name: string;
3106
+ email: string;
3107
+ password: string;
3108
+ role?: string;
3109
+ team?: string;
3110
+ settings?: UserSettings;
3111
+ }
3112
+ function createUser(options: CreateUserOptions) { }
3113
+ \`\`\`
3114
+
3115
+ ### Deep Nesting \u2192 Early Returns
3116
+
3117
+ \`\`\`typescript
3118
+ // BEFORE: 4 levels deep
3119
+ function process(data) {
3120
+ if (data) {
3121
+ if (data.valid) {
3122
+ if (data.items) {
3123
+ for (const item of data.items) {
3124
+ // actual logic here
3125
+ }
3126
+ }
3127
+ }
3128
+ }
3129
+ }
3130
+
3131
+ // AFTER: 1 level deep
3132
+ function process(data) {
3133
+ if (!data?.valid || !data.items) return;
3134
+
3135
+ for (const item of data.items) {
3136
+ // actual logic here
3137
+ }
3138
+ }
3139
+ \`\`\`
3140
+
3141
+ ## Anti-Patterns
3142
+
3143
+ - \u274C God objects/files (do everything)
3144
+ - \u274C "Just one more line" (compound violations)
3145
+ - \u274C "I'll split it later" (you won't)
3146
+ - \u274C Deep inheritance hierarchies
3147
+ - \u274C Complex conditionals without extraction
3148
+ `,
3149
+ isNew: true
3150
+ };
3151
+ }
3152
+ function generateSecuritySkill(stack) {
3153
+ const isJS = stack.languages.includes("typescript") || stack.languages.includes("javascript");
3154
+ const isPython = stack.languages.includes("python");
3155
+ return {
3156
+ type: "skill",
3157
+ path: ".claude/skills/security.md",
3158
+ content: `---
3159
+ name: security
3160
+ description: Security best practices, secrets management, OWASP patterns
3161
+ globs:
3162
+ - "**/*"
3163
+ ---
3164
+
3165
+ # Security Best Practices
3166
+
3167
+ Security is not optional. Every project must pass security checks.
3168
+
3169
+ ## Required .gitignore Entries
3170
+
3171
+ **NEVER commit these:**
3172
+
3173
+ \`\`\`gitignore
3174
+ # Environment files
3175
+ .env
3176
+ .env.*
3177
+ !.env.example
3178
+
3179
+ # Secrets and credentials
3180
+ *.pem
3181
+ *.key
3182
+ *.p12
3183
+ credentials.json
3184
+ secrets.json
3185
+ *-credentials.json
3186
+ service-account*.json
3187
+
3188
+ # IDE secrets
3189
+ .idea/
3190
+ .vscode/settings.json
3191
+ \`\`\`
3192
+
3193
+ ## Environment Variables
3194
+
3195
+ ### Create .env.example
3196
+
3197
+ Document required vars without values:
3198
+
3199
+ \`\`\`bash
3200
+ # Server-side only (never expose to client)
3201
+ DATABASE_URL=
3202
+ API_SECRET_KEY=
3203
+ ANTHROPIC_API_KEY=
3204
+
3205
+ # Client-side safe (public, non-sensitive)
3206
+ ${isJS ? "VITE_API_URL=\nNEXT_PUBLIC_SITE_URL=" : "API_BASE_URL="}
3207
+ \`\`\`
3208
+
3209
+ ${isJS ? `### Frontend Exposure Rules
3210
+
3211
+ | Framework | Client-Exposed Prefix | Server-Only |
3212
+ |-----------|----------------------|-------------|
3213
+ | Vite | \`VITE_*\` | No prefix |
3214
+ | Next.js | \`NEXT_PUBLIC_*\` | No prefix |
3215
+ | CRA | \`REACT_APP_*\` | N/A |
3216
+
3217
+ **CRITICAL:** Never put secrets in client-exposed env vars!
3218
+
3219
+ \`\`\`typescript
3220
+ // \u274C WRONG - Secret exposed to browser
3221
+ const key = import.meta.env.VITE_API_SECRET;
3222
+
3223
+ // \u2705 CORRECT - Secret stays server-side
3224
+ const key = process.env.API_SECRET; // in API route only
3225
+ \`\`\`
3226
+ ` : ""}
3227
+
3228
+ ### Validate at Startup
3229
+
3230
+ ${isJS ? `\`\`\`typescript
3231
+ import { z } from 'zod';
3232
+
3233
+ const envSchema = z.object({
3234
+ DATABASE_URL: z.string().url(),
3235
+ API_SECRET_KEY: z.string().min(32),
3236
+ NODE_ENV: z.enum(['development', 'production', 'test']),
3237
+ });
3238
+
3239
+ export const env = envSchema.parse(process.env);
3240
+ \`\`\`` : ""}
3241
+ ${isPython ? `\`\`\`python
3242
+ from pydantic_settings import BaseSettings
3243
+
3244
+ class Settings(BaseSettings):
3245
+ database_url: str
3246
+ api_secret_key: str
3247
+ environment: str = "development"
3248
+
3249
+ class Config:
3250
+ env_file = ".env"
3251
+
3252
+ settings = Settings()
3253
+ \`\`\`` : ""}
3254
+
3255
+ ## OWASP Top 10 Checklist
3256
+
3257
+ | Vulnerability | Prevention |
3258
+ |---------------|------------|
3259
+ | Injection (SQL, NoSQL, Command) | Parameterized queries, input validation |
3260
+ | Broken Auth | Secure session management, MFA |
3261
+ | Sensitive Data Exposure | Encryption at rest and in transit |
3262
+ | XXE | Disable external entity processing |
3263
+ | Broken Access Control | Verify permissions on every request |
3264
+ | Security Misconfiguration | Secure defaults, minimal permissions |
3265
+ | XSS | Output encoding, CSP headers |
3266
+ | Insecure Deserialization | Validate all serialized data |
3267
+ | Using Vulnerable Components | Keep dependencies updated |
3268
+ | Insufficient Logging | Log security events, monitor |
3269
+
3270
+ ## Input Validation
3271
+
3272
+ \`\`\`
3273
+ RULE: Never trust user input. Validate everything.
3274
+
3275
+ - Validate type, length, format, range
3276
+ - Sanitize before storage
3277
+ - Encode before output
3278
+ - Use allowlists over denylists
3279
+ \`\`\`
3280
+
3281
+ ## Secrets Detection
3282
+
3283
+ Before committing, check for:
3284
+
3285
+ - API keys (usually 32+ chars, specific patterns)
3286
+ - Passwords in code
3287
+ - Connection strings with credentials
3288
+ - Private keys (BEGIN RSA/EC/PRIVATE KEY)
3289
+ - Tokens (jwt, bearer, oauth)
3290
+
3291
+ ## Security Review Checklist
3292
+
3293
+ Before PR merge:
3294
+
3295
+ - [ ] No secrets in code or config
3296
+ - [ ] Input validation on all user data
3297
+ - [ ] Output encoding where displayed
3298
+ - [ ] Authentication checked on protected routes
3299
+ - [ ] Authorization verified for resources
3300
+ - [ ] Dependencies scanned for vulnerabilities
3301
+ - [ ] Error messages don't leak internals
3302
+ `,
3303
+ isNew: true
3304
+ };
3305
+ }
3306
+ function getAgentsForStack(stack) {
3307
+ const agents = [
3308
+ { name: "code-reviewer", description: "Reviews code for quality and security" },
3309
+ { name: "test-writer", description: "Generates tests for code" }
3310
+ ];
3311
+ if (stack.hasDocker) {
3312
+ agents.push({ name: "docker-helper", description: "Helps with Docker and containerization" });
3313
+ }
3314
+ return agents;
3315
+ }
3316
+ function generateAgents(stack) {
3317
+ const artifacts = [];
3318
+ artifacts.push(generateCodeReviewerAgent(stack));
3319
+ artifacts.push(generateTestWriterAgent(stack));
3320
+ return artifacts;
3321
+ }
3322
+ function generateCodeReviewerAgent(stack) {
3323
+ const lintCommand = getLintCommand(stack);
3324
+ return {
3325
+ type: "agent",
3326
+ path: ".claude/agents/code-reviewer.md",
3327
+ content: `---
3328
+ name: code-reviewer
3329
+ description: Reviews code for quality, security issues, and best practices
3330
+ tools: Read, Grep, Glob${lintCommand ? `, Bash(${lintCommand})` : ""}
3331
+ disallowedTools: Write, Edit
3332
+ model: sonnet
3333
+ ---
3334
+
3335
+ You are a senior code reviewer with expertise in security and performance.
3336
+
3337
+ ## Code Style Reference
3338
+
3339
+ Read these files to understand project conventions:
3340
+ ${stack.linter === "eslint" ? "- `eslint.config.js` or `.eslintrc.*`" : ""}
3341
+ ${stack.formatter === "prettier" ? "- `.prettierrc`" : ""}
3342
+ ${stack.languages.includes("typescript") ? "- `tsconfig.json`" : ""}
3343
+ ${stack.languages.includes("python") ? "- `pyproject.toml` or `setup.cfg`" : ""}
3344
+
3345
+ ${lintCommand ? `Run \`${lintCommand}\` to check violations programmatically.` : ""}
3346
+
3347
+ ## Review Process
3348
+
3349
+ 1. Run \`git diff\` to identify changed files
3350
+ 2. Analyze each change for:
3351
+ - Security vulnerabilities (OWASP Top 10)
3352
+ - Performance issues
3353
+ - Code style violations
3354
+ - Missing error handling
3355
+ - Test coverage gaps
3356
+
3357
+ ## Output Format
3358
+
3359
+ For each finding:
3360
+
3361
+ - **Critical**: Must fix before merge
3362
+ - **Warning**: Should address
3363
+ - **Suggestion**: Consider improving
3364
+
3365
+ Include file:line references for each issue.
3366
+ `,
3367
+ isNew: true
3368
+ };
3369
+ }
3370
+ function generateTestWriterAgent(stack) {
3371
+ const testCommand = getTestCommand(stack);
3372
+ return {
3373
+ type: "agent",
3374
+ path: ".claude/agents/test-writer.md",
3375
+ content: `---
3376
+ name: test-writer
3377
+ description: Generates comprehensive tests for code
3378
+ tools: Read, Grep, Glob, Write, Edit, Bash(${testCommand})
3379
+ model: sonnet
3380
+ ---
3381
+
3382
+ You are a testing expert who writes thorough, maintainable tests.
3383
+
3384
+ ## Testing Framework
3385
+
3386
+ This project uses: **${stack.testingFramework || "unknown"}**
3387
+
3388
+ ## Your Process
3389
+
3390
+ 1. Read the code to be tested
3391
+ 2. Identify test cases:
3392
+ - Happy path scenarios
3393
+ - Edge cases
3394
+ - Error conditions
3395
+ - Boundary values
3396
+ 3. Write tests following project patterns
3397
+ 4. Run tests to verify they pass
3398
+
3399
+ ## Test Structure
3400
+
3401
+ Follow the AAA pattern:
3402
+ - **Arrange**: Set up test data
3403
+ - **Act**: Execute the code
3404
+ - **Assert**: Verify results
3405
+
3406
+ ## Guidelines
3407
+
3408
+ - One assertion focus per test
3409
+ - Descriptive test names
3410
+ - Mock external dependencies
3411
+ - Don't test implementation details
3412
+ - Aim for behavior coverage
3413
+
3414
+ ## Run Tests
3415
+
3416
+ \`\`\`bash
3417
+ ${testCommand}
3418
+ \`\`\`
3419
+ `,
3420
+ isNew: true
3421
+ };
3422
+ }
3423
+ function getLintCommand(stack) {
3424
+ switch (stack.linter) {
3425
+ case "eslint":
3426
+ return `${stack.packageManager === "bun" ? "bun" : "npx"} eslint .`;
3427
+ case "biome":
3428
+ return `${stack.packageManager === "bun" ? "bun" : "npx"} biome check .`;
3429
+ case "ruff":
3430
+ return "ruff check .";
3431
+ default:
3432
+ return "";
3433
+ }
3434
+ }
3435
+ function getTestCommand(stack) {
3436
+ switch (stack.testingFramework) {
3437
+ case "vitest":
3438
+ return `${stack.packageManager || "npm"} ${stack.packageManager === "npm" ? "run " : ""}test`;
3439
+ case "jest":
3440
+ return `${stack.packageManager || "npm"} ${stack.packageManager === "npm" ? "run " : ""}test`;
3441
+ case "bun-test":
3442
+ return "bun test";
3443
+ case "pytest":
3444
+ return "pytest";
3445
+ case "go-test":
3446
+ return "go test ./...";
3447
+ case "rust-test":
3448
+ return "cargo test";
3449
+ default:
3450
+ return `${stack.packageManager || "npm"} test`;
3451
+ }
3452
+ }
3453
+ function generateRules(stack) {
3454
+ const artifacts = [];
3455
+ if (stack.languages.includes("typescript")) {
3456
+ artifacts.push(generateTypeScriptRules());
3457
+ }
3458
+ if (stack.languages.includes("python")) {
3459
+ artifacts.push(generatePythonRules());
3460
+ }
3461
+ artifacts.push(generateCodeStyleRule(stack));
3462
+ return artifacts;
3463
+ }
3464
+ function generateTypeScriptRules() {
3465
+ return {
3466
+ type: "rule",
3467
+ path: ".claude/rules/typescript.md",
3468
+ content: `---
3469
+ paths:
3470
+ - "**/*.ts"
3471
+ - "**/*.tsx"
3472
+ ---
3473
+
3474
+ # TypeScript Rules
3475
+
3476
+ ## Type Safety
3477
+
3478
+ - Avoid \`any\` - use \`unknown\` and narrow types
3479
+ - Prefer interfaces for objects, types for unions/intersections
3480
+ - Use strict mode (\`strict: true\` in tsconfig)
3481
+ - Enable \`noUncheckedIndexedAccess\` for safer array access
3482
+
3483
+ ## Patterns
3484
+
3485
+ \`\`\`typescript
3486
+ // Prefer
3487
+ const user: User | undefined = users.find(u => u.id === id);
3488
+ if (user) { /* use user */ }
3489
+
3490
+ // Avoid
3491
+ const user = users.find(u => u.id === id) as User;
3492
+ \`\`\`
3493
+
3494
+ ## Naming
3495
+
3496
+ - Interfaces: PascalCase (e.g., \`UserProfile\`)
3497
+ - Types: PascalCase (e.g., \`ApiResponse\`)
3498
+ - Functions: camelCase (e.g., \`getUserById\`)
3499
+ - Constants: SCREAMING_SNAKE_CASE for true constants
3500
+
3501
+ ## Imports
3502
+
3503
+ - Group imports: external, internal, relative
3504
+ - Use path aliases when configured
3505
+ - Prefer named exports over default exports
3506
+ `,
3507
+ isNew: true
3508
+ };
3509
+ }
3510
+ function generatePythonRules() {
3511
+ return {
3512
+ type: "rule",
3513
+ path: ".claude/rules/python.md",
3514
+ content: `---
3515
+ paths:
3516
+ - "**/*.py"
3517
+ ---
3518
+
3519
+ # Python Rules
3520
+
3521
+ ## Style
3522
+
3523
+ - Follow PEP 8
3524
+ - Use type hints for function signatures
3525
+ - Docstrings for public functions (Google style)
3526
+ - Max line length: 88 (Black default)
3527
+
3528
+ ## Patterns
3529
+
3530
+ \`\`\`python
3531
+ # Prefer
3532
+ def get_user(user_id: int) -> User | None:
3533
+ """Fetch user by ID.
3534
+
3535
+ Args:
1867
3536
  user_id: The user's unique identifier.
1868
3537
 
1869
3538
  Returns:
@@ -1948,7 +3617,8 @@ function generateCommands() {
1948
3617
  generateTaskCommand(),
1949
3618
  generateStatusCommand(),
1950
3619
  generateDoneCommand(),
1951
- generateAnalyzeCommand()
3620
+ generateAnalyzeCommand(),
3621
+ generateCodeReviewCommand()
1952
3622
  ];
1953
3623
  }
1954
3624
  function generateTaskCommand() {
@@ -2092,6 +3762,71 @@ Any improvements or concerns noted.
2092
3762
  isNew: true
2093
3763
  };
2094
3764
  }
3765
+ function generateCodeReviewCommand() {
3766
+ return {
3767
+ type: "command",
3768
+ path: ".claude/commands/code-review.md",
3769
+ content: `---
3770
+ allowed-tools: Read, Glob, Grep, Bash(git diff), Bash(git status), Bash(git log)
3771
+ description: Review code changes for quality, security, and best practices
3772
+ ---
3773
+
3774
+ # Code Review
3775
+
3776
+ ## Changes to Review
3777
+
3778
+ !git diff --stat HEAD~1 2>/dev/null || git diff --stat
3779
+
3780
+ ## Review Process
3781
+
3782
+ Analyze all changes for:
3783
+
3784
+ ### 1. Security (Critical)
3785
+ - [ ] No secrets/credentials in code
3786
+ - [ ] Input validation present
3787
+ - [ ] Output encoding where needed
3788
+ - [ ] Auth/authz checks on protected routes
3789
+
3790
+ ### 2. Quality
3791
+ - [ ] Functions \u2264 20 lines
3792
+ - [ ] Files \u2264 200 lines
3793
+ - [ ] No code duplication
3794
+ - [ ] Clear naming
3795
+ - [ ] Proper error handling
3796
+
3797
+ ### 3. Testing
3798
+ - [ ] Tests exist for new code
3799
+ - [ ] Edge cases covered
3800
+ - [ ] Tests are meaningful (not just for coverage)
3801
+
3802
+ ### 4. Style
3803
+ - [ ] Matches existing patterns
3804
+ - [ ] Consistent formatting
3805
+ - [ ] No commented-out code
3806
+
3807
+ ## Output Format
3808
+
3809
+ For each finding, include file:line reference:
3810
+
3811
+ ### Critical (Must Fix)
3812
+ Issues that block merge
3813
+
3814
+ ### Warning (Should Fix)
3815
+ Issues that should be addressed
3816
+
3817
+ ### Suggestion (Consider)
3818
+ Optional improvements
3819
+
3820
+ ## Summary
3821
+
3822
+ Provide:
3823
+ 1. Overall assessment (Ready / Changes Needed / Not Ready)
3824
+ 2. Count of findings by severity
3825
+ 3. Top priorities to address
3826
+ `,
3827
+ isNew: true
3828
+ };
3829
+ }
2095
3830
  function formatLanguage(lang) {
2096
3831
  const names = {
2097
3832
  typescript: "TypeScript",
@@ -2111,6 +3846,7 @@ function formatLanguage(lang) {
2111
3846
  }
2112
3847
  function formatFramework(fw) {
2113
3848
  const names = {
3849
+ // JavaScript/TypeScript Frontend
2114
3850
  nextjs: "Next.js",
2115
3851
  react: "React",
2116
3852
  vue: "Vue.js",
@@ -2122,30 +3858,50 @@ function formatFramework(fw) {
2122
3858
  remix: "Remix",
2123
3859
  gatsby: "Gatsby",
2124
3860
  solid: "Solid.js",
3861
+ // JavaScript/TypeScript Backend
2125
3862
  express: "Express",
2126
3863
  nestjs: "NestJS",
2127
3864
  fastify: "Fastify",
2128
3865
  hono: "Hono",
2129
3866
  elysia: "Elysia",
2130
3867
  koa: "Koa",
3868
+ // Python
2131
3869
  fastapi: "FastAPI",
2132
3870
  django: "Django",
2133
3871
  flask: "Flask",
2134
3872
  starlette: "Starlette",
3873
+ // Go
2135
3874
  gin: "Gin",
2136
3875
  echo: "Echo",
2137
3876
  fiber: "Fiber",
3877
+ // Rust
2138
3878
  actix: "Actix",
2139
3879
  axum: "Axum",
2140
3880
  rocket: "Rocket",
3881
+ // Ruby
2141
3882
  rails: "Rails",
2142
3883
  sinatra: "Sinatra",
3884
+ // Java/Kotlin
2143
3885
  spring: "Spring",
2144
3886
  quarkus: "Quarkus",
3887
+ // Android
3888
+ "jetpack-compose": "Jetpack Compose",
3889
+ "android-views": "Android Views",
3890
+ room: "Room",
3891
+ hilt: "Hilt",
3892
+ "ktor-android": "Ktor",
3893
+ // Swift/iOS
3894
+ swiftui: "SwiftUI",
3895
+ uikit: "UIKit",
3896
+ vapor: "Vapor",
3897
+ swiftdata: "SwiftData",
3898
+ combine: "Combine",
3899
+ // CSS/UI
2145
3900
  tailwind: "Tailwind CSS",
2146
3901
  shadcn: "shadcn/ui",
2147
3902
  chakra: "Chakra UI",
2148
3903
  mui: "Material UI",
3904
+ // Database/ORM
2149
3905
  prisma: "Prisma",
2150
3906
  drizzle: "Drizzle",
2151
3907
  typeorm: "TypeORM",
@@ -2203,15 +3959,14 @@ ${pc.bold("MORE INFO")}
2203
3959
  `);
2204
3960
  }
2205
3961
  function showBanner() {
2206
- console.log(pc.cyan("\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557"));
2207
- console.log(pc.cyan(`\u2551 Claude Code Starter v${VERSION.padEnd(24)}\u2551`));
2208
- console.log(pc.cyan("\u2551 Intelligent AI-Assisted Development Setup \u2551"));
2209
- console.log(pc.cyan("\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D"));
3962
+ console.log();
3963
+ console.log(pc.bold("Claude Code Starter") + pc.gray(` v${VERSION}`));
3964
+ console.log(pc.gray("Intelligent AI-Assisted Development Setup"));
2210
3965
  console.log();
2211
3966
  }
2212
3967
  function showTechStack(projectInfo, verbose) {
2213
3968
  const { techStack } = projectInfo;
2214
- console.log(pc.blue("\u{1F4CA} Tech Stack Analysis"));
3969
+ console.log(pc.bold("Tech Stack"));
2215
3970
  console.log();
2216
3971
  if (techStack.primaryLanguage) {
2217
3972
  console.log(` ${pc.bold("Language:")} ${formatLanguage2(techStack.primaryLanguage)}`);
@@ -2251,7 +4006,7 @@ async function promptNewProject(args) {
2251
4006
  if (!args.interactive) {
2252
4007
  return null;
2253
4008
  }
2254
- console.log(pc.yellow("\u{1F195} New project detected - let's set it up!"));
4009
+ console.log(pc.yellow("New project detected - let's set it up!"));
2255
4010
  console.log();
2256
4011
  const response = await prompts([
2257
4012
  {
@@ -2441,7 +4196,7 @@ async function main() {
2441
4196
  }
2442
4197
  showBanner();
2443
4198
  const projectDir = process.cwd();
2444
- console.log(pc.blue("\u{1F50D} Analyzing repository..."));
4199
+ console.log(pc.gray("Analyzing repository..."));
2445
4200
  console.log();
2446
4201
  const projectInfo = analyzeRepository(projectDir);
2447
4202
  showTechStack(projectInfo, args.verbose);
@@ -2457,11 +4212,11 @@ async function main() {
2457
4212
  projectInfo.description = preferences.description;
2458
4213
  }
2459
4214
  } else {
2460
- console.log(pc.green(`\u{1F4C1} Existing project \xB7 ${projectInfo.fileCount} source files`));
4215
+ console.log(pc.gray(`Existing project with ${projectInfo.fileCount} source files`));
2461
4216
  console.log();
2462
4217
  }
2463
4218
  if (projectInfo.techStack.hasClaudeConfig && !args.force) {
2464
- console.log(pc.yellow("\u26A0\uFE0F Existing .claude/ configuration detected"));
4219
+ console.log(pc.yellow("Existing .claude/ configuration detected"));
2465
4220
  console.log();
2466
4221
  if (args.interactive) {
2467
4222
  const { proceed } = await prompts({
@@ -2477,32 +4232,32 @@ async function main() {
2477
4232
  }
2478
4233
  console.log();
2479
4234
  }
2480
- console.log(pc.blue("\u2699\uFE0F Generating Claude Code configuration..."));
4235
+ console.log(pc.gray("Generating configuration..."));
2481
4236
  console.log();
2482
4237
  const result = generateArtifacts(projectInfo);
2483
4238
  const { created, updated, skipped } = writeArtifacts(result.artifacts, projectDir, args.force);
2484
4239
  if (created.length > 0) {
2485
- console.log(pc.green(" Created:"));
4240
+ console.log(pc.green("Created:"));
2486
4241
  for (const file of created) {
2487
- console.log(pc.green(` \u2713 ${file}`));
4242
+ console.log(pc.green(` + ${file}`));
2488
4243
  }
2489
4244
  }
2490
4245
  if (updated.length > 0) {
2491
- console.log(pc.blue(" Updated:"));
4246
+ console.log(pc.blue("Updated:"));
2492
4247
  for (const file of updated) {
2493
- console.log(pc.blue(` \u21BB ${file}`));
4248
+ console.log(pc.blue(` ~ ${file}`));
2494
4249
  }
2495
4250
  }
2496
4251
  if (skipped.length > 0 && args.verbose) {
2497
- console.log(pc.gray(" Preserved:"));
4252
+ console.log(pc.gray("Preserved:"));
2498
4253
  for (const file of skipped) {
2499
- console.log(pc.gray(` - ${file}`));
4254
+ console.log(pc.gray(` - ${file}`));
2500
4255
  }
2501
4256
  }
2502
4257
  console.log();
2503
4258
  createTaskFile(projectInfo, preferences);
2504
4259
  const totalFiles = created.length + updated.length;
2505
- console.log(pc.green(`\u2705 Configuration complete! (${totalFiles} files)`));
4260
+ console.log(pc.green(`Done! (${totalFiles} files)`));
2506
4261
  console.log();
2507
4262
  console.log(pc.bold("Generated for your stack:"));
2508
4263
  const skills = result.artifacts.filter((a) => a.type === "skill");
@@ -2510,24 +4265,24 @@ async function main() {
2510
4265
  const rules = result.artifacts.filter((a) => a.type === "rule");
2511
4266
  if (skills.length > 0) {
2512
4267
  console.log(
2513
- ` \u{1F4DA} ${skills.length} skills (${skills.map((s) => path3.basename(s.path, ".md")).join(", ")})`
4268
+ ` ${skills.length} skills (${skills.map((s) => path3.basename(s.path, ".md")).join(", ")})`
2514
4269
  );
2515
4270
  }
2516
4271
  if (agents.length > 0) {
2517
4272
  console.log(
2518
- ` \u{1F916} ${agents.length} agents (${agents.map((a) => path3.basename(a.path, ".md")).join(", ")})`
4273
+ ` ${agents.length} agents (${agents.map((a) => path3.basename(a.path, ".md")).join(", ")})`
2519
4274
  );
2520
4275
  }
2521
4276
  if (rules.length > 0) {
2522
- console.log(` \u{1F4CF} ${rules.length} rules`);
4277
+ console.log(` ${rules.length} rules`);
2523
4278
  }
2524
4279
  console.log();
2525
4280
  console.log(`${pc.cyan("Next step:")} Run ${pc.bold("claude")} to start working!`);
2526
4281
  console.log();
2527
4282
  if (!projectInfo.isExisting) {
2528
- console.log(pc.gray("\u{1F4A1} Tip: Use /task to define your first task"));
4283
+ console.log(pc.gray("Tip: Use /task to define your first task"));
2529
4284
  } else {
2530
- console.log(pc.gray("\u{1F4A1} Tip: Use /analyze to explore specific areas of your codebase"));
4285
+ console.log(pc.gray("Tip: Use /analyze to explore specific areas of your codebase"));
2531
4286
  }
2532
4287
  }
2533
4288
  main().catch((err) => {
@@ -2538,6 +4293,13 @@ main().catch((err) => {
2538
4293
  process.exit(1);
2539
4294
  });
2540
4295
  export {
4296
+ createTaskFile,
4297
+ formatFramework2 as formatFramework,
4298
+ formatLanguage2 as formatLanguage,
2541
4299
  getVersion,
2542
- parseArgs
4300
+ parseArgs,
4301
+ promptNewProject,
4302
+ showBanner,
4303
+ showHelp,
4304
+ showTechStack
2543
4305
  };