claude-code-starter 0.9.0 → 0.11.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/dist/cli.js +779 -3138
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -533,3055 +533,111 @@ function summarizeTechStack(stack) {
|
|
|
533
533
|
// src/generator.ts
|
|
534
534
|
import fs2 from "fs";
|
|
535
535
|
import path2 from "path";
|
|
536
|
-
function
|
|
537
|
-
const
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
}
|
|
548
|
-
const summary = {
|
|
549
|
-
created: artifacts.filter((a) => a.isNew).length,
|
|
550
|
-
updated: artifacts.filter((a) => !a.isNew).length,
|
|
551
|
-
skipped: 0
|
|
552
|
-
};
|
|
553
|
-
return { artifacts, summary };
|
|
554
|
-
}
|
|
555
|
-
function writeArtifacts(artifacts, rootDir, force) {
|
|
556
|
-
const created = [];
|
|
557
|
-
const updated = [];
|
|
558
|
-
const skipped = [];
|
|
559
|
-
for (const artifact of artifacts) {
|
|
560
|
-
const fullPath = path2.join(rootDir, artifact.path);
|
|
561
|
-
const dir = path2.dirname(fullPath);
|
|
562
|
-
fs2.mkdirSync(dir, { recursive: true });
|
|
563
|
-
const exists = fs2.existsSync(fullPath);
|
|
564
|
-
if (exists && !force) {
|
|
565
|
-
const shouldPreserve = artifact.path.includes("state/task.md");
|
|
566
|
-
if (shouldPreserve) {
|
|
567
|
-
skipped.push(artifact.path);
|
|
568
|
-
continue;
|
|
569
|
-
}
|
|
570
|
-
}
|
|
571
|
-
fs2.writeFileSync(fullPath, artifact.content);
|
|
572
|
-
if (exists) {
|
|
573
|
-
updated.push(artifact.path);
|
|
574
|
-
} else {
|
|
575
|
-
created.push(artifact.path);
|
|
576
|
-
}
|
|
536
|
+
function ensureDirectories(rootDir) {
|
|
537
|
+
const dirs = [
|
|
538
|
+
".claude",
|
|
539
|
+
".claude/skills",
|
|
540
|
+
".claude/agents",
|
|
541
|
+
".claude/rules",
|
|
542
|
+
".claude/commands",
|
|
543
|
+
".claude/state"
|
|
544
|
+
];
|
|
545
|
+
for (const dir of dirs) {
|
|
546
|
+
fs2.mkdirSync(path2.join(rootDir, dir), { recursive: true });
|
|
577
547
|
}
|
|
578
|
-
return { created, updated, skipped };
|
|
579
548
|
}
|
|
580
549
|
function generateSettings(stack) {
|
|
581
|
-
const permissions = ["Read(**)", "Edit(**)", "Write(.claude/**)", "Bash(git:*)"];
|
|
582
|
-
const pkgManagers = ["npm", "yarn", "pnpm", "bun", "npx"];
|
|
583
|
-
for (const pm of pkgManagers) {
|
|
584
|
-
permissions.push(`Bash(${pm}:*)`);
|
|
585
|
-
}
|
|
586
|
-
if (stack.languages.includes("typescript") || stack.languages.includes("javascript")) {
|
|
587
|
-
permissions.push("Bash(node:*)", "Bash(tsc:*)");
|
|
588
|
-
}
|
|
589
|
-
if (stack.languages.includes("python")) {
|
|
590
|
-
permissions.push(
|
|
591
|
-
"Bash(python:*)",
|
|
592
|
-
"Bash(pip:*)",
|
|
593
|
-
"Bash(poetry:*)",
|
|
594
|
-
"Bash(pytest:*)",
|
|
595
|
-
"Bash(uvicorn:*)"
|
|
596
|
-
);
|
|
597
|
-
}
|
|
598
|
-
if (stack.languages.includes("go")) {
|
|
599
|
-
permissions.push("Bash(go:*)");
|
|
600
|
-
}
|
|
601
|
-
if (stack.languages.includes("rust")) {
|
|
602
|
-
permissions.push("Bash(cargo:*)", "Bash(rustc:*)");
|
|
603
|
-
}
|
|
604
|
-
if (stack.languages.includes("ruby")) {
|
|
605
|
-
permissions.push("Bash(ruby:*)", "Bash(bundle:*)", "Bash(rails:*)", "Bash(rake:*)");
|
|
606
|
-
}
|
|
607
|
-
if (stack.testingFramework) {
|
|
608
|
-
const testCommands = {
|
|
609
|
-
jest: ["jest:*"],
|
|
610
|
-
vitest: ["vitest:*"],
|
|
611
|
-
playwright: ["playwright:*"],
|
|
612
|
-
cypress: ["cypress:*"],
|
|
613
|
-
pytest: ["pytest:*"],
|
|
614
|
-
rspec: ["rspec:*"]
|
|
615
|
-
};
|
|
616
|
-
const cmds = testCommands[stack.testingFramework];
|
|
617
|
-
if (cmds) {
|
|
618
|
-
permissions.push(...cmds.map((c) => `Bash(${c})`));
|
|
619
|
-
}
|
|
620
|
-
}
|
|
621
|
-
if (stack.linter) {
|
|
622
|
-
permissions.push(`Bash(${stack.linter}:*)`);
|
|
623
|
-
}
|
|
624
|
-
if (stack.formatter) {
|
|
625
|
-
permissions.push(`Bash(${stack.formatter}:*)`);
|
|
626
|
-
}
|
|
627
|
-
permissions.push(
|
|
628
|
-
"Bash(ls:*)",
|
|
629
|
-
"Bash(mkdir:*)",
|
|
630
|
-
"Bash(cat:*)",
|
|
631
|
-
"Bash(echo:*)",
|
|
632
|
-
"Bash(grep:*)",
|
|
633
|
-
"Bash(find:*)"
|
|
634
|
-
);
|
|
635
|
-
if (stack.hasDocker) {
|
|
636
|
-
permissions.push("Bash(docker:*)", "Bash(docker-compose:*)");
|
|
637
|
-
}
|
|
638
|
-
const settings = {
|
|
639
|
-
$schema: "https://json.schemastore.org/claude-code-settings.json",
|
|
640
|
-
permissions: {
|
|
641
|
-
allow: [...new Set(permissions)]
|
|
642
|
-
// Deduplicate
|
|
643
|
-
}
|
|
644
|
-
};
|
|
645
|
-
return {
|
|
646
|
-
type: "settings",
|
|
647
|
-
path: ".claude/settings.json",
|
|
648
|
-
content: JSON.stringify(settings, null, 2),
|
|
649
|
-
isNew: true
|
|
650
|
-
};
|
|
651
|
-
}
|
|
652
|
-
function generateSkills(stack) {
|
|
653
|
-
const artifacts = [];
|
|
654
|
-
artifacts.push(generatePatternDiscoverySkill());
|
|
655
|
-
artifacts.push(generateSystematicDebuggingSkill());
|
|
656
|
-
artifacts.push(generateTestingMethodologySkill(stack));
|
|
657
|
-
artifacts.push(generateIterativeDevelopmentSkill(stack));
|
|
658
|
-
artifacts.push(generateCommitHygieneSkill());
|
|
659
|
-
artifacts.push(generateCodeDeduplicationSkill());
|
|
660
|
-
artifacts.push(generateSimplicityRulesSkill());
|
|
661
|
-
artifacts.push(generateSecuritySkill(stack));
|
|
662
|
-
if (stack.frameworks.includes("nextjs")) {
|
|
663
|
-
artifacts.push(generateNextJsSkill());
|
|
664
|
-
}
|
|
665
|
-
if (stack.frameworks.includes("react") && !stack.frameworks.includes("nextjs")) {
|
|
666
|
-
artifacts.push(generateReactSkill());
|
|
667
|
-
}
|
|
668
|
-
if (stack.frameworks.includes("fastapi")) {
|
|
669
|
-
artifacts.push(generateFastAPISkill());
|
|
670
|
-
}
|
|
671
|
-
if (stack.frameworks.includes("nestjs")) {
|
|
672
|
-
artifacts.push(generateNestJSSkill());
|
|
673
|
-
}
|
|
674
|
-
if (stack.frameworks.includes("swiftui")) {
|
|
675
|
-
artifacts.push(generateSwiftUISkill());
|
|
676
|
-
}
|
|
677
|
-
if (stack.frameworks.includes("uikit")) {
|
|
678
|
-
artifacts.push(generateUIKitSkill());
|
|
679
|
-
}
|
|
680
|
-
if (stack.frameworks.includes("vapor")) {
|
|
681
|
-
artifacts.push(generateVaporSkill());
|
|
682
|
-
}
|
|
683
|
-
if (stack.frameworks.includes("jetpack-compose")) {
|
|
684
|
-
artifacts.push(generateJetpackComposeSkill());
|
|
685
|
-
}
|
|
686
|
-
if (stack.frameworks.includes("android-views")) {
|
|
687
|
-
artifacts.push(generateAndroidViewsSkill());
|
|
688
|
-
}
|
|
689
|
-
return artifacts;
|
|
690
|
-
}
|
|
691
|
-
function generatePatternDiscoverySkill() {
|
|
692
|
-
return {
|
|
693
|
-
type: "skill",
|
|
694
|
-
path: ".claude/skills/pattern-discovery.md",
|
|
695
|
-
content: `---
|
|
696
|
-
name: pattern-discovery
|
|
697
|
-
description: Analyze existing codebase to discover and document patterns
|
|
698
|
-
globs:
|
|
699
|
-
- "src/**/*"
|
|
700
|
-
- "lib/**/*"
|
|
701
|
-
- "app/**/*"
|
|
702
|
-
- "components/**/*"
|
|
703
|
-
- "pages/**/*"
|
|
704
|
-
- "api/**/*"
|
|
705
|
-
- "services/**/*"
|
|
706
|
-
---
|
|
707
|
-
|
|
708
|
-
# Pattern Discovery
|
|
709
|
-
|
|
710
|
-
When starting work on a project, analyze the existing code to understand its patterns.
|
|
711
|
-
|
|
712
|
-
## Discovery Process
|
|
713
|
-
|
|
714
|
-
### 1. Check for Existing Documentation
|
|
715
|
-
|
|
716
|
-
\`\`\`
|
|
717
|
-
Look for:
|
|
718
|
-
- README.md, CONTRIBUTING.md
|
|
719
|
-
- docs/ folder
|
|
720
|
-
- Code comments and JSDoc/TSDoc
|
|
721
|
-
- .editorconfig, .prettierrc, eslint config
|
|
722
|
-
\`\`\`
|
|
723
|
-
|
|
724
|
-
### 2. Analyze Project Structure
|
|
725
|
-
|
|
726
|
-
\`\`\`
|
|
727
|
-
Questions to answer:
|
|
728
|
-
- How are files organized? (by feature, by type, flat?)
|
|
729
|
-
- Where does business logic live?
|
|
730
|
-
- Where are tests located?
|
|
731
|
-
- How are configs managed?
|
|
732
|
-
\`\`\`
|
|
733
|
-
|
|
734
|
-
### 3. Detect Code Patterns
|
|
735
|
-
|
|
736
|
-
\`\`\`
|
|
737
|
-
Look at 3-5 similar files to find:
|
|
738
|
-
- Naming conventions (camelCase, snake_case, PascalCase)
|
|
739
|
-
- Import organization (grouped? sorted? relative vs absolute?)
|
|
740
|
-
- Export style (named, default, barrel files?)
|
|
741
|
-
- Error handling approach
|
|
742
|
-
- Logging patterns
|
|
743
|
-
\`\`\`
|
|
744
|
-
|
|
745
|
-
### 4. Identify Architecture
|
|
746
|
-
|
|
747
|
-
\`\`\`
|
|
748
|
-
Common patterns to detect:
|
|
749
|
-
- MVC / MVVM / Clean Architecture
|
|
750
|
-
- Repository pattern
|
|
751
|
-
- Service layer
|
|
752
|
-
- Dependency injection
|
|
753
|
-
- Event-driven
|
|
754
|
-
- Functional vs OOP
|
|
755
|
-
\`\`\`
|
|
756
|
-
|
|
757
|
-
## When No Code Exists
|
|
758
|
-
|
|
759
|
-
If starting a new project:
|
|
760
|
-
|
|
761
|
-
1. Ask about preferred patterns
|
|
762
|
-
2. Check package.json/config files for framework hints
|
|
763
|
-
3. Use sensible defaults for detected stack
|
|
764
|
-
4. Document decisions in \`.claude/state/task.md\`
|
|
765
|
-
|
|
766
|
-
## Important
|
|
767
|
-
|
|
768
|
-
- **Match existing patterns** - don't impose new ones
|
|
769
|
-
- **When in doubt, check similar files** in the codebase
|
|
770
|
-
- **Document as you discover** - note patterns in task state
|
|
771
|
-
- **Ask if unclear** - better to ask than assume
|
|
772
|
-
`,
|
|
773
|
-
isNew: true
|
|
774
|
-
};
|
|
775
|
-
}
|
|
776
|
-
function generateSystematicDebuggingSkill() {
|
|
777
|
-
return {
|
|
778
|
-
type: "skill",
|
|
779
|
-
path: ".claude/skills/systematic-debugging.md",
|
|
780
|
-
content: `---
|
|
781
|
-
name: systematic-debugging
|
|
782
|
-
description: Methodical approach to finding and fixing bugs
|
|
783
|
-
globs:
|
|
784
|
-
- "**/*.ts"
|
|
785
|
-
- "**/*.tsx"
|
|
786
|
-
- "**/*.js"
|
|
787
|
-
- "**/*.jsx"
|
|
788
|
-
- "**/*.py"
|
|
789
|
-
- "**/*.go"
|
|
790
|
-
- "**/*.rs"
|
|
791
|
-
---
|
|
792
|
-
|
|
793
|
-
# Systematic Debugging
|
|
794
|
-
|
|
795
|
-
A 4-phase methodology for finding and fixing bugs efficiently.
|
|
796
|
-
|
|
797
|
-
## Phase 1: Reproduce
|
|
798
|
-
|
|
799
|
-
Before fixing, confirm you can reproduce the bug.
|
|
800
|
-
|
|
801
|
-
\`\`\`
|
|
802
|
-
1. Get exact steps to reproduce
|
|
803
|
-
2. Identify expected vs actual behavior
|
|
804
|
-
3. Note any error messages verbatim
|
|
805
|
-
4. Check if it's consistent or intermittent
|
|
806
|
-
\`\`\`
|
|
807
|
-
|
|
808
|
-
## Phase 2: Locate
|
|
809
|
-
|
|
810
|
-
Narrow down where the bug occurs.
|
|
811
|
-
|
|
812
|
-
\`\`\`
|
|
813
|
-
Techniques:
|
|
814
|
-
- Binary search through code flow
|
|
815
|
-
- Add logging at key points
|
|
816
|
-
- Check recent changes (git log, git diff)
|
|
817
|
-
- Review stack traces carefully
|
|
818
|
-
- Use debugger breakpoints
|
|
819
|
-
\`\`\`
|
|
820
|
-
|
|
821
|
-
## Phase 3: Diagnose
|
|
822
|
-
|
|
823
|
-
Understand WHY the bug happens.
|
|
824
|
-
|
|
825
|
-
\`\`\`
|
|
826
|
-
Questions:
|
|
827
|
-
- What assumptions are being violated?
|
|
828
|
-
- What state is unexpected?
|
|
829
|
-
- Is this a logic error, data error, or timing issue?
|
|
830
|
-
- Are there edge cases not handled?
|
|
831
|
-
\`\`\`
|
|
832
|
-
|
|
833
|
-
## Phase 4: Fix
|
|
834
|
-
|
|
835
|
-
Apply the minimal correct fix.
|
|
836
|
-
|
|
837
|
-
\`\`\`
|
|
838
|
-
Guidelines:
|
|
839
|
-
- Fix the root cause, not symptoms
|
|
840
|
-
- Make the smallest change that fixes the issue
|
|
841
|
-
- Add a test that would have caught this bug
|
|
842
|
-
- Check for similar bugs elsewhere
|
|
843
|
-
- Update documentation if needed
|
|
844
|
-
\`\`\`
|
|
845
|
-
|
|
846
|
-
## Quick Reference
|
|
847
|
-
|
|
848
|
-
| Symptom | Check First |
|
|
849
|
-
|---------|-------------|
|
|
850
|
-
| TypeError | Null/undefined values, type mismatches |
|
|
851
|
-
| Off-by-one | Loop bounds, array indices |
|
|
852
|
-
| Race condition | Async operations, shared state |
|
|
853
|
-
| Memory leak | Event listeners, subscriptions, closures |
|
|
854
|
-
| Infinite loop | Exit conditions, recursive calls |
|
|
855
|
-
`,
|
|
856
|
-
isNew: true
|
|
857
|
-
};
|
|
858
|
-
}
|
|
859
|
-
function generateTestingMethodologySkill(stack) {
|
|
860
|
-
const testingFramework = stack.testingFramework || "generic";
|
|
861
|
-
const examples = getTestingExamples(stack);
|
|
862
|
-
return {
|
|
863
|
-
type: "skill",
|
|
864
|
-
path: ".claude/skills/testing-methodology.md",
|
|
865
|
-
content: `---
|
|
866
|
-
name: testing-methodology
|
|
867
|
-
description: Testing patterns and best practices for this project
|
|
868
|
-
globs:
|
|
869
|
-
- "**/*.test.*"
|
|
870
|
-
- "**/*.spec.*"
|
|
871
|
-
- "**/test/**"
|
|
872
|
-
- "**/tests/**"
|
|
873
|
-
- "**/__tests__/**"
|
|
874
|
-
---
|
|
875
|
-
|
|
876
|
-
# Testing Methodology
|
|
877
|
-
|
|
878
|
-
## Testing Framework
|
|
879
|
-
|
|
880
|
-
This project uses: **${testingFramework}**
|
|
881
|
-
|
|
882
|
-
## The AAA Pattern
|
|
883
|
-
|
|
884
|
-
Structure every test with:
|
|
885
|
-
|
|
886
|
-
\`\`\`
|
|
887
|
-
Arrange - Set up test data and conditions
|
|
888
|
-
Act - Execute the code being tested
|
|
889
|
-
Assert - Verify the expected outcome
|
|
890
|
-
\`\`\`
|
|
891
|
-
|
|
892
|
-
## What to Test
|
|
893
|
-
|
|
894
|
-
### Must Test
|
|
895
|
-
- Core business logic
|
|
896
|
-
- Edge cases and boundaries
|
|
897
|
-
- Error handling paths
|
|
898
|
-
- Public API contracts
|
|
899
|
-
|
|
900
|
-
### Consider Testing
|
|
901
|
-
- Integration points
|
|
902
|
-
- Complex conditional logic
|
|
903
|
-
- State transitions
|
|
904
|
-
|
|
905
|
-
### Skip Testing
|
|
906
|
-
- Framework internals
|
|
907
|
-
- Simple getters/setters
|
|
908
|
-
- Configuration constants
|
|
909
|
-
|
|
910
|
-
## Example Patterns
|
|
911
|
-
|
|
912
|
-
${examples}
|
|
913
|
-
|
|
914
|
-
## Test Naming
|
|
915
|
-
|
|
916
|
-
\`\`\`
|
|
917
|
-
Format: [unit]_[scenario]_[expected result]
|
|
918
|
-
|
|
919
|
-
Examples:
|
|
920
|
-
- calculateTotal_withEmptyCart_returnsZero
|
|
921
|
-
- userService_createUser_savesToDatabase
|
|
922
|
-
- parseDate_invalidFormat_throwsError
|
|
923
|
-
\`\`\`
|
|
924
|
-
|
|
925
|
-
## Mocking Guidelines
|
|
926
|
-
|
|
927
|
-
1. **Mock external dependencies** - APIs, databases, file system
|
|
928
|
-
2. **Don't mock what you own** - Prefer real implementations for your code
|
|
929
|
-
3. **Keep mocks simple** - Complex mocks often indicate design issues
|
|
930
|
-
4. **Reset mocks between tests** - Avoid state leakage
|
|
931
|
-
|
|
932
|
-
## Coverage Philosophy
|
|
933
|
-
|
|
934
|
-
- Aim for **80%+ coverage** on critical paths
|
|
935
|
-
- Don't chase 100% - it often leads to brittle tests
|
|
936
|
-
- Focus on **behavior coverage**, not line coverage
|
|
937
|
-
`,
|
|
938
|
-
isNew: true
|
|
939
|
-
};
|
|
940
|
-
}
|
|
941
|
-
function getTestingExamples(stack) {
|
|
942
|
-
if (stack.testingFramework === "vitest" || stack.testingFramework === "jest") {
|
|
943
|
-
return `
|
|
944
|
-
\`\`\`typescript
|
|
945
|
-
import { describe, it, expect } from '${stack.testingFramework}';
|
|
946
|
-
|
|
947
|
-
describe('UserService', () => {
|
|
948
|
-
it('should create user with valid data', async () => {
|
|
949
|
-
// Arrange
|
|
950
|
-
const userData = { name: 'Test', email: 'test@example.com' };
|
|
951
|
-
|
|
952
|
-
// Act
|
|
953
|
-
const user = await userService.create(userData);
|
|
954
|
-
|
|
955
|
-
// Assert
|
|
956
|
-
expect(user.id).toBeDefined();
|
|
957
|
-
expect(user.name).toBe('Test');
|
|
958
|
-
});
|
|
959
|
-
|
|
960
|
-
it('should throw on invalid email', async () => {
|
|
961
|
-
// Arrange
|
|
962
|
-
const userData = { name: 'Test', email: 'invalid' };
|
|
963
|
-
|
|
964
|
-
// Act & Assert
|
|
965
|
-
await expect(userService.create(userData)).rejects.toThrow('Invalid email');
|
|
966
|
-
});
|
|
967
|
-
});
|
|
968
|
-
\`\`\``;
|
|
969
|
-
}
|
|
970
|
-
if (stack.testingFramework === "pytest") {
|
|
971
|
-
return `
|
|
972
|
-
\`\`\`python
|
|
973
|
-
import pytest
|
|
974
|
-
from myapp.services import UserService
|
|
975
|
-
|
|
976
|
-
class TestUserService:
|
|
977
|
-
def test_create_user_with_valid_data(self, db_session):
|
|
978
|
-
# Arrange
|
|
979
|
-
user_data = {"name": "Test", "email": "test@example.com"}
|
|
980
|
-
service = UserService(db_session)
|
|
981
|
-
|
|
982
|
-
# Act
|
|
983
|
-
user = service.create(user_data)
|
|
984
|
-
|
|
985
|
-
# Assert
|
|
986
|
-
assert user.id is not None
|
|
987
|
-
assert user.name == "Test"
|
|
988
|
-
|
|
989
|
-
def test_create_user_invalid_email_raises(self, db_session):
|
|
990
|
-
# Arrange
|
|
991
|
-
user_data = {"name": "Test", "email": "invalid"}
|
|
992
|
-
service = UserService(db_session)
|
|
993
|
-
|
|
994
|
-
# Act & Assert
|
|
995
|
-
with pytest.raises(ValueError, match="Invalid email"):
|
|
996
|
-
service.create(user_data)
|
|
997
|
-
\`\`\``;
|
|
998
|
-
}
|
|
999
|
-
if (stack.testingFramework === "go-test") {
|
|
1000
|
-
return `
|
|
1001
|
-
\`\`\`go
|
|
1002
|
-
func TestUserService_Create(t *testing.T) {
|
|
1003
|
-
t.Run("creates user with valid data", func(t *testing.T) {
|
|
1004
|
-
// Arrange
|
|
1005
|
-
svc := NewUserService(mockDB)
|
|
1006
|
-
userData := UserInput{Name: "Test", Email: "test@example.com"}
|
|
1007
|
-
|
|
1008
|
-
// Act
|
|
1009
|
-
user, err := svc.Create(userData)
|
|
1010
|
-
|
|
1011
|
-
// Assert
|
|
1012
|
-
assert.NoError(t, err)
|
|
1013
|
-
assert.NotEmpty(t, user.ID)
|
|
1014
|
-
assert.Equal(t, "Test", user.Name)
|
|
1015
|
-
})
|
|
1016
|
-
|
|
1017
|
-
t.Run("returns error on invalid email", func(t *testing.T) {
|
|
1018
|
-
// Arrange
|
|
1019
|
-
svc := NewUserService(mockDB)
|
|
1020
|
-
userData := UserInput{Name: "Test", Email: "invalid"}
|
|
1021
|
-
|
|
1022
|
-
// Act
|
|
1023
|
-
_, err := svc.Create(userData)
|
|
1024
|
-
|
|
1025
|
-
// Assert
|
|
1026
|
-
assert.ErrorContains(t, err, "invalid email")
|
|
1027
|
-
})
|
|
1028
|
-
}
|
|
1029
|
-
\`\`\``;
|
|
1030
|
-
}
|
|
1031
|
-
return `
|
|
1032
|
-
\`\`\`
|
|
1033
|
-
// Add examples for your testing framework here
|
|
1034
|
-
describe('Component', () => {
|
|
1035
|
-
it('should behave correctly', () => {
|
|
1036
|
-
// Arrange - set up test conditions
|
|
1037
|
-
// Act - execute the code
|
|
1038
|
-
// Assert - verify results
|
|
1039
|
-
});
|
|
1040
|
-
});
|
|
1041
|
-
\`\`\``;
|
|
1042
|
-
}
|
|
1043
|
-
function generateNextJsSkill() {
|
|
1044
|
-
return {
|
|
1045
|
-
type: "skill",
|
|
1046
|
-
path: ".claude/skills/nextjs-patterns.md",
|
|
1047
|
-
content: `---
|
|
1048
|
-
name: nextjs-patterns
|
|
1049
|
-
description: Next.js App Router patterns and best practices
|
|
1050
|
-
globs:
|
|
1051
|
-
- "app/**/*"
|
|
1052
|
-
- "src/app/**/*"
|
|
1053
|
-
- "components/**/*"
|
|
1054
|
-
---
|
|
1055
|
-
|
|
1056
|
-
# Next.js Patterns (App Router)
|
|
1057
|
-
|
|
1058
|
-
## File Conventions
|
|
1059
|
-
|
|
1060
|
-
| File | Purpose |
|
|
1061
|
-
|------|---------|
|
|
1062
|
-
| \`page.tsx\` | Route UI |
|
|
1063
|
-
| \`layout.tsx\` | Shared layout wrapper |
|
|
1064
|
-
| \`loading.tsx\` | Loading UI (Suspense) |
|
|
1065
|
-
| \`error.tsx\` | Error boundary |
|
|
1066
|
-
| \`not-found.tsx\` | 404 page |
|
|
1067
|
-
| \`route.ts\` | API endpoint |
|
|
1068
|
-
|
|
1069
|
-
## Server vs Client Components
|
|
1070
|
-
|
|
1071
|
-
\`\`\`tsx
|
|
1072
|
-
// Server Component (default) - runs on server only
|
|
1073
|
-
export default function ServerComponent() {
|
|
1074
|
-
// Can use: async/await, direct DB access, server-only code
|
|
1075
|
-
// Cannot use: useState, useEffect, browser APIs
|
|
1076
|
-
return <div>Server rendered</div>;
|
|
1077
|
-
}
|
|
1078
|
-
|
|
1079
|
-
// Client Component - runs on client
|
|
1080
|
-
'use client';
|
|
1081
|
-
export default function ClientComponent() {
|
|
1082
|
-
// Can use: hooks, event handlers, browser APIs
|
|
1083
|
-
const [state, setState] = useState();
|
|
1084
|
-
return <button onClick={() => setState(...)}>Click</button>;
|
|
1085
|
-
}
|
|
1086
|
-
\`\`\`
|
|
1087
|
-
|
|
1088
|
-
## Data Fetching
|
|
1089
|
-
|
|
1090
|
-
\`\`\`tsx
|
|
1091
|
-
// Server Component - fetch directly
|
|
1092
|
-
async function ProductPage({ params }: { params: { id: string } }) {
|
|
1093
|
-
const product = await db.product.findUnique({ where: { id: params.id } });
|
|
1094
|
-
return <ProductDetails product={product} />;
|
|
1095
|
-
}
|
|
1096
|
-
|
|
1097
|
-
// With caching
|
|
1098
|
-
const getData = cache(async (id: string) => {
|
|
1099
|
-
return await db.find(id);
|
|
1100
|
-
});
|
|
1101
|
-
\`\`\`
|
|
1102
|
-
|
|
1103
|
-
## Server Actions
|
|
1104
|
-
|
|
1105
|
-
\`\`\`tsx
|
|
1106
|
-
// actions.ts
|
|
1107
|
-
'use server';
|
|
1108
|
-
|
|
1109
|
-
export async function createPost(formData: FormData) {
|
|
1110
|
-
const title = formData.get('title');
|
|
1111
|
-
await db.post.create({ data: { title } });
|
|
1112
|
-
revalidatePath('/posts');
|
|
1113
|
-
}
|
|
1114
|
-
|
|
1115
|
-
// In component
|
|
1116
|
-
<form action={createPost}>
|
|
1117
|
-
<input name="title" />
|
|
1118
|
-
<button type="submit">Create</button>
|
|
1119
|
-
</form>
|
|
1120
|
-
\`\`\`
|
|
1121
|
-
|
|
1122
|
-
## Route Handlers
|
|
1123
|
-
|
|
1124
|
-
\`\`\`tsx
|
|
1125
|
-
// app/api/users/route.ts
|
|
1126
|
-
import { NextResponse } from 'next/server';
|
|
1127
|
-
|
|
1128
|
-
export async function GET() {
|
|
1129
|
-
const users = await db.user.findMany();
|
|
1130
|
-
return NextResponse.json(users);
|
|
1131
|
-
}
|
|
1132
|
-
|
|
1133
|
-
export async function POST(request: Request) {
|
|
1134
|
-
const body = await request.json();
|
|
1135
|
-
const user = await db.user.create({ data: body });
|
|
1136
|
-
return NextResponse.json(user, { status: 201 });
|
|
1137
|
-
}
|
|
1138
|
-
\`\`\`
|
|
1139
|
-
|
|
1140
|
-
## Patterns to Follow
|
|
1141
|
-
|
|
1142
|
-
1. **Default to Server Components** - Only use 'use client' when needed
|
|
1143
|
-
2. **Colocate related files** - Keep components near their routes
|
|
1144
|
-
3. **Use route groups** - \`(auth)/login\` for organization without URL impact
|
|
1145
|
-
4. **Parallel routes** - \`@modal/\` for simultaneous rendering
|
|
1146
|
-
5. **Intercepting routes** - \`(.)/photo\` for modal patterns
|
|
1147
|
-
`,
|
|
1148
|
-
isNew: true
|
|
1149
|
-
};
|
|
1150
|
-
}
|
|
1151
|
-
function generateReactSkill() {
|
|
1152
|
-
return {
|
|
1153
|
-
type: "skill",
|
|
1154
|
-
path: ".claude/skills/react-components.md",
|
|
1155
|
-
content: `---
|
|
1156
|
-
name: react-components
|
|
1157
|
-
description: React component patterns and best practices
|
|
1158
|
-
globs:
|
|
1159
|
-
- "src/components/**/*"
|
|
1160
|
-
- "components/**/*"
|
|
1161
|
-
- "**/*.tsx"
|
|
1162
|
-
- "**/*.jsx"
|
|
1163
|
-
---
|
|
1164
|
-
|
|
1165
|
-
# React Component Patterns
|
|
1166
|
-
|
|
1167
|
-
## Component Structure
|
|
1168
|
-
|
|
1169
|
-
\`\`\`tsx
|
|
1170
|
-
// Standard component structure
|
|
1171
|
-
import { useState, useCallback } from 'react';
|
|
1172
|
-
import type { ComponentProps } from './types';
|
|
1173
|
-
|
|
1174
|
-
interface Props {
|
|
1175
|
-
title: string;
|
|
1176
|
-
onAction?: () => void;
|
|
1177
|
-
children?: React.ReactNode;
|
|
1178
|
-
}
|
|
1179
|
-
|
|
1180
|
-
export function MyComponent({ title, onAction, children }: Props) {
|
|
1181
|
-
const [state, setState] = useState(false);
|
|
1182
|
-
|
|
1183
|
-
const handleClick = useCallback(() => {
|
|
1184
|
-
setState(true);
|
|
1185
|
-
onAction?.();
|
|
1186
|
-
}, [onAction]);
|
|
1187
|
-
|
|
1188
|
-
return (
|
|
1189
|
-
<div>
|
|
1190
|
-
<h1>{title}</h1>
|
|
1191
|
-
<button onClick={handleClick}>Action</button>
|
|
1192
|
-
{children}
|
|
1193
|
-
</div>
|
|
1194
|
-
);
|
|
1195
|
-
}
|
|
1196
|
-
\`\`\`
|
|
1197
|
-
|
|
1198
|
-
## Hooks Patterns
|
|
1199
|
-
|
|
1200
|
-
\`\`\`tsx
|
|
1201
|
-
// Custom hook for data fetching
|
|
1202
|
-
function useUser(id: string) {
|
|
1203
|
-
const [user, setUser] = useState<User | null>(null);
|
|
1204
|
-
const [loading, setLoading] = useState(true);
|
|
1205
|
-
const [error, setError] = useState<Error | null>(null);
|
|
1206
|
-
|
|
1207
|
-
useEffect(() => {
|
|
1208
|
-
fetchUser(id)
|
|
1209
|
-
.then(setUser)
|
|
1210
|
-
.catch(setError)
|
|
1211
|
-
.finally(() => setLoading(false));
|
|
1212
|
-
}, [id]);
|
|
1213
|
-
|
|
1214
|
-
return { user, loading, error };
|
|
1215
|
-
}
|
|
1216
|
-
\`\`\`
|
|
1217
|
-
|
|
1218
|
-
## State Management
|
|
1219
|
-
|
|
1220
|
-
\`\`\`tsx
|
|
1221
|
-
// Use reducer for complex state
|
|
1222
|
-
const [state, dispatch] = useReducer(reducer, initialState);
|
|
1223
|
-
|
|
1224
|
-
// Use context for shared state
|
|
1225
|
-
const ThemeContext = createContext<Theme>('light');
|
|
1226
|
-
export const useTheme = () => useContext(ThemeContext);
|
|
1227
|
-
\`\`\`
|
|
1228
|
-
|
|
1229
|
-
## Performance
|
|
1230
|
-
|
|
1231
|
-
1. **Memoize expensive calculations**: \`useMemo\`
|
|
1232
|
-
2. **Memoize callbacks**: \`useCallback\`
|
|
1233
|
-
3. **Memoize components**: \`React.memo\`
|
|
1234
|
-
4. **Avoid inline objects/arrays in props**
|
|
1235
|
-
|
|
1236
|
-
## Testing
|
|
1237
|
-
|
|
1238
|
-
\`\`\`tsx
|
|
1239
|
-
import { render, screen, fireEvent } from '@testing-library/react';
|
|
1240
|
-
|
|
1241
|
-
test('button triggers action', () => {
|
|
1242
|
-
const onAction = vi.fn();
|
|
1243
|
-
render(<MyComponent title="Test" onAction={onAction} />);
|
|
1244
|
-
|
|
1245
|
-
fireEvent.click(screen.getByRole('button'));
|
|
1246
|
-
|
|
1247
|
-
expect(onAction).toHaveBeenCalled();
|
|
1248
|
-
});
|
|
1249
|
-
\`\`\`
|
|
1250
|
-
`,
|
|
1251
|
-
isNew: true
|
|
1252
|
-
};
|
|
1253
|
-
}
|
|
1254
|
-
function generateFastAPISkill() {
|
|
1255
|
-
return {
|
|
1256
|
-
type: "skill",
|
|
1257
|
-
path: ".claude/skills/fastapi-patterns.md",
|
|
1258
|
-
content: `---
|
|
1259
|
-
name: fastapi-patterns
|
|
1260
|
-
description: FastAPI endpoint patterns and best practices
|
|
1261
|
-
globs:
|
|
1262
|
-
- "app/**/*.py"
|
|
1263
|
-
- "src/**/*.py"
|
|
1264
|
-
- "api/**/*.py"
|
|
1265
|
-
- "routers/**/*.py"
|
|
1266
|
-
---
|
|
1267
|
-
|
|
1268
|
-
# FastAPI Patterns
|
|
1269
|
-
|
|
1270
|
-
## Router Structure
|
|
1271
|
-
|
|
1272
|
-
\`\`\`python
|
|
1273
|
-
# routers/users.py
|
|
1274
|
-
from fastapi import APIRouter, Depends, HTTPException
|
|
1275
|
-
from sqlalchemy.orm import Session
|
|
1276
|
-
|
|
1277
|
-
from app.database import get_db
|
|
1278
|
-
from app.schemas import UserCreate, UserResponse
|
|
1279
|
-
from app.services import UserService
|
|
1280
|
-
|
|
1281
|
-
router = APIRouter(prefix="/users", tags=["users"])
|
|
1282
|
-
|
|
1283
|
-
@router.get("/", response_model=list[UserResponse])
|
|
1284
|
-
async def list_users(
|
|
1285
|
-
skip: int = 0,
|
|
1286
|
-
limit: int = 100,
|
|
1287
|
-
db: Session = Depends(get_db)
|
|
1288
|
-
):
|
|
1289
|
-
service = UserService(db)
|
|
1290
|
-
return service.get_all(skip=skip, limit=limit)
|
|
1291
|
-
|
|
1292
|
-
@router.post("/", response_model=UserResponse, status_code=201)
|
|
1293
|
-
async def create_user(
|
|
1294
|
-
user: UserCreate,
|
|
1295
|
-
db: Session = Depends(get_db)
|
|
1296
|
-
):
|
|
1297
|
-
service = UserService(db)
|
|
1298
|
-
return service.create(user)
|
|
1299
|
-
\`\`\`
|
|
1300
|
-
|
|
1301
|
-
## Dependency Injection
|
|
1302
|
-
|
|
1303
|
-
\`\`\`python
|
|
1304
|
-
# Dependencies
|
|
1305
|
-
def get_current_user(token: str = Depends(oauth2_scheme)) -> User:
|
|
1306
|
-
user = decode_token(token)
|
|
1307
|
-
if not user:
|
|
1308
|
-
raise HTTPException(status_code=401, detail="Invalid token")
|
|
1309
|
-
return user
|
|
1310
|
-
|
|
1311
|
-
def require_admin(user: User = Depends(get_current_user)) -> User:
|
|
1312
|
-
if not user.is_admin:
|
|
1313
|
-
raise HTTPException(status_code=403, detail="Admin required")
|
|
1314
|
-
return user
|
|
1315
|
-
|
|
1316
|
-
# Usage
|
|
1317
|
-
@router.delete("/{id}")
|
|
1318
|
-
async def delete_user(id: int, admin: User = Depends(require_admin)):
|
|
1319
|
-
...
|
|
1320
|
-
\`\`\`
|
|
1321
|
-
|
|
1322
|
-
## Pydantic Schemas
|
|
1323
|
-
|
|
1324
|
-
\`\`\`python
|
|
1325
|
-
from pydantic import BaseModel, EmailStr, Field
|
|
1326
|
-
|
|
1327
|
-
class UserBase(BaseModel):
|
|
1328
|
-
email: EmailStr
|
|
1329
|
-
name: str = Field(..., min_length=1, max_length=100)
|
|
1330
|
-
|
|
1331
|
-
class UserCreate(UserBase):
|
|
1332
|
-
password: str = Field(..., min_length=8)
|
|
1333
|
-
|
|
1334
|
-
class UserResponse(UserBase):
|
|
1335
|
-
id: int
|
|
1336
|
-
created_at: datetime
|
|
1337
|
-
|
|
1338
|
-
class Config:
|
|
1339
|
-
from_attributes = True # For ORM mode
|
|
1340
|
-
\`\`\`
|
|
1341
|
-
|
|
1342
|
-
## Error Handling
|
|
1343
|
-
|
|
1344
|
-
\`\`\`python
|
|
1345
|
-
from fastapi import HTTPException
|
|
1346
|
-
from fastapi.responses import JSONResponse
|
|
1347
|
-
|
|
1348
|
-
# Custom exception
|
|
1349
|
-
class NotFoundError(Exception):
|
|
1350
|
-
def __init__(self, resource: str, id: int):
|
|
1351
|
-
self.resource = resource
|
|
1352
|
-
self.id = id
|
|
1353
|
-
|
|
1354
|
-
# Exception handler
|
|
1355
|
-
@app.exception_handler(NotFoundError)
|
|
1356
|
-
async def not_found_handler(request, exc: NotFoundError):
|
|
1357
|
-
return JSONResponse(
|
|
1358
|
-
status_code=404,
|
|
1359
|
-
content={"error": f"{exc.resource} {exc.id} not found"}
|
|
1360
|
-
)
|
|
1361
|
-
\`\`\`
|
|
1362
|
-
|
|
1363
|
-
## Testing
|
|
1364
|
-
|
|
1365
|
-
\`\`\`python
|
|
1366
|
-
from fastapi.testclient import TestClient
|
|
1367
|
-
|
|
1368
|
-
def test_create_user(client: TestClient):
|
|
1369
|
-
response = client.post("/users/", json={
|
|
1370
|
-
"email": "test@example.com",
|
|
1371
|
-
"name": "Test",
|
|
1372
|
-
"password": "password123"
|
|
1373
|
-
})
|
|
1374
|
-
assert response.status_code == 201
|
|
1375
|
-
assert response.json()["email"] == "test@example.com"
|
|
1376
|
-
\`\`\`
|
|
1377
|
-
`,
|
|
1378
|
-
isNew: true
|
|
1379
|
-
};
|
|
1380
|
-
}
|
|
1381
|
-
function generateNestJSSkill() {
|
|
1382
|
-
return {
|
|
1383
|
-
type: "skill",
|
|
1384
|
-
path: ".claude/skills/nestjs-patterns.md",
|
|
1385
|
-
content: `---
|
|
1386
|
-
name: nestjs-patterns
|
|
1387
|
-
description: NestJS module patterns and best practices
|
|
1388
|
-
globs:
|
|
1389
|
-
- "src/**/*.ts"
|
|
1390
|
-
- "**/*.module.ts"
|
|
1391
|
-
- "**/*.controller.ts"
|
|
1392
|
-
- "**/*.service.ts"
|
|
1393
|
-
---
|
|
1394
|
-
|
|
1395
|
-
# NestJS Patterns
|
|
1396
|
-
|
|
1397
|
-
## Module Structure
|
|
1398
|
-
|
|
1399
|
-
\`\`\`typescript
|
|
1400
|
-
// users/users.module.ts
|
|
1401
|
-
import { Module } from '@nestjs/common';
|
|
1402
|
-
import { TypeOrmModule } from '@nestjs/typeorm';
|
|
1403
|
-
import { UsersController } from './users.controller';
|
|
1404
|
-
import { UsersService } from './users.service';
|
|
1405
|
-
import { User } from './entities/user.entity';
|
|
1406
|
-
|
|
1407
|
-
@Module({
|
|
1408
|
-
imports: [TypeOrmModule.forFeature([User])],
|
|
1409
|
-
controllers: [UsersController],
|
|
1410
|
-
providers: [UsersService],
|
|
1411
|
-
exports: [UsersService],
|
|
1412
|
-
})
|
|
1413
|
-
export class UsersModule {}
|
|
1414
|
-
\`\`\`
|
|
1415
|
-
|
|
1416
|
-
## Controller Pattern
|
|
1417
|
-
|
|
1418
|
-
\`\`\`typescript
|
|
1419
|
-
// users/users.controller.ts
|
|
1420
|
-
import { Controller, Get, Post, Body, Param, UseGuards } from '@nestjs/common';
|
|
1421
|
-
import { UsersService } from './users.service';
|
|
1422
|
-
import { CreateUserDto } from './dto/create-user.dto';
|
|
1423
|
-
import { JwtAuthGuard } from '../auth/jwt-auth.guard';
|
|
1424
|
-
|
|
1425
|
-
@Controller('users')
|
|
1426
|
-
export class UsersController {
|
|
1427
|
-
constructor(private readonly usersService: UsersService) {}
|
|
1428
|
-
|
|
1429
|
-
@Get()
|
|
1430
|
-
findAll() {
|
|
1431
|
-
return this.usersService.findAll();
|
|
1432
|
-
}
|
|
1433
|
-
|
|
1434
|
-
@Get(':id')
|
|
1435
|
-
findOne(@Param('id') id: string) {
|
|
1436
|
-
return this.usersService.findOne(+id);
|
|
1437
|
-
}
|
|
1438
|
-
|
|
1439
|
-
@Post()
|
|
1440
|
-
@UseGuards(JwtAuthGuard)
|
|
1441
|
-
create(@Body() createUserDto: CreateUserDto) {
|
|
1442
|
-
return this.usersService.create(createUserDto);
|
|
1443
|
-
}
|
|
1444
|
-
}
|
|
1445
|
-
\`\`\`
|
|
1446
|
-
|
|
1447
|
-
## Service Pattern
|
|
1448
|
-
|
|
1449
|
-
\`\`\`typescript
|
|
1450
|
-
// users/users.service.ts
|
|
1451
|
-
import { Injectable, NotFoundException } from '@nestjs/common';
|
|
1452
|
-
import { InjectRepository } from '@nestjs/typeorm';
|
|
1453
|
-
import { Repository } from 'typeorm';
|
|
1454
|
-
import { User } from './entities/user.entity';
|
|
1455
|
-
|
|
1456
|
-
@Injectable()
|
|
1457
|
-
export class UsersService {
|
|
1458
|
-
constructor(
|
|
1459
|
-
@InjectRepository(User)
|
|
1460
|
-
private usersRepository: Repository<User>,
|
|
1461
|
-
) {}
|
|
1462
|
-
|
|
1463
|
-
findAll(): Promise<User[]> {
|
|
1464
|
-
return this.usersRepository.find();
|
|
1465
|
-
}
|
|
1466
|
-
|
|
1467
|
-
async findOne(id: number): Promise<User> {
|
|
1468
|
-
const user = await this.usersRepository.findOne({ where: { id } });
|
|
1469
|
-
if (!user) {
|
|
1470
|
-
throw new NotFoundException(\`User #\${id} not found\`);
|
|
1471
|
-
}
|
|
1472
|
-
return user;
|
|
1473
|
-
}
|
|
1474
|
-
}
|
|
1475
|
-
\`\`\`
|
|
1476
|
-
|
|
1477
|
-
## DTO Validation
|
|
1478
|
-
|
|
1479
|
-
\`\`\`typescript
|
|
1480
|
-
// dto/create-user.dto.ts
|
|
1481
|
-
import { IsEmail, IsString, MinLength } from 'class-validator';
|
|
1482
|
-
|
|
1483
|
-
export class CreateUserDto {
|
|
1484
|
-
@IsEmail()
|
|
1485
|
-
email: string;
|
|
1486
|
-
|
|
1487
|
-
@IsString()
|
|
1488
|
-
@MinLength(1)
|
|
1489
|
-
name: string;
|
|
1490
|
-
|
|
1491
|
-
@IsString()
|
|
1492
|
-
@MinLength(8)
|
|
1493
|
-
password: string;
|
|
1494
|
-
}
|
|
1495
|
-
\`\`\`
|
|
1496
|
-
|
|
1497
|
-
## Testing
|
|
1498
|
-
|
|
1499
|
-
\`\`\`typescript
|
|
1500
|
-
describe('UsersService', () => {
|
|
1501
|
-
let service: UsersService;
|
|
1502
|
-
let repository: MockType<Repository<User>>;
|
|
1503
|
-
|
|
1504
|
-
beforeEach(async () => {
|
|
1505
|
-
const module = await Test.createTestingModule({
|
|
1506
|
-
providers: [
|
|
1507
|
-
UsersService,
|
|
1508
|
-
{ provide: getRepositoryToken(User), useFactory: repositoryMockFactory },
|
|
1509
|
-
],
|
|
1510
|
-
}).compile();
|
|
1511
|
-
|
|
1512
|
-
service = module.get(UsersService);
|
|
1513
|
-
repository = module.get(getRepositoryToken(User));
|
|
1514
|
-
});
|
|
1515
|
-
|
|
1516
|
-
it('should find all users', async () => {
|
|
1517
|
-
const users = [{ id: 1, name: 'Test' }];
|
|
1518
|
-
repository.find.mockReturnValue(users);
|
|
1519
|
-
|
|
1520
|
-
expect(await service.findAll()).toEqual(users);
|
|
1521
|
-
});
|
|
1522
|
-
});
|
|
1523
|
-
\`\`\`
|
|
1524
|
-
`,
|
|
1525
|
-
isNew: true
|
|
1526
|
-
};
|
|
1527
|
-
}
|
|
1528
|
-
function generateSwiftUISkill() {
|
|
1529
|
-
return {
|
|
1530
|
-
type: "skill",
|
|
1531
|
-
path: ".claude/skills/swiftui-patterns.md",
|
|
1532
|
-
content: `---
|
|
1533
|
-
name: swiftui-patterns
|
|
1534
|
-
description: SwiftUI declarative UI patterns and best practices
|
|
1535
|
-
globs:
|
|
1536
|
-
- "**/*.swift"
|
|
1537
|
-
---
|
|
1538
|
-
|
|
1539
|
-
# SwiftUI Patterns
|
|
1540
|
-
|
|
1541
|
-
## View Structure
|
|
1542
|
-
|
|
1543
|
-
\`\`\`swift
|
|
1544
|
-
import SwiftUI
|
|
1545
|
-
|
|
1546
|
-
struct ContentView: View {
|
|
1547
|
-
@State private var count = 0
|
|
1548
|
-
@StateObject private var viewModel = ContentViewModel()
|
|
1549
|
-
|
|
1550
|
-
var body: some View {
|
|
1551
|
-
VStack(spacing: 16) {
|
|
1552
|
-
Text("Count: \\(count)")
|
|
1553
|
-
.font(.title)
|
|
1554
|
-
|
|
1555
|
-
Button("Increment") {
|
|
1556
|
-
count += 1
|
|
1557
|
-
}
|
|
1558
|
-
.buttonStyle(.borderedProminent)
|
|
1559
|
-
}
|
|
1560
|
-
.padding()
|
|
1561
|
-
}
|
|
1562
|
-
}
|
|
1563
|
-
|
|
1564
|
-
#Preview {
|
|
1565
|
-
ContentView()
|
|
1566
|
-
}
|
|
1567
|
-
\`\`\`
|
|
1568
|
-
|
|
1569
|
-
## Property Wrappers
|
|
1570
|
-
|
|
1571
|
-
| Wrapper | Use Case |
|
|
1572
|
-
|---------|----------|
|
|
1573
|
-
| \`@State\` | Simple value types owned by view |
|
|
1574
|
-
| \`@Binding\` | Two-way connection to parent's state |
|
|
1575
|
-
| \`@StateObject\` | Reference type owned by view (create once) |
|
|
1576
|
-
| \`@ObservedObject\` | Reference type passed from parent |
|
|
1577
|
-
| \`@EnvironmentObject\` | Shared data through view hierarchy |
|
|
1578
|
-
| \`@Environment\` | System environment values |
|
|
1579
|
-
|
|
1580
|
-
## MVVM Pattern
|
|
1581
|
-
|
|
1582
|
-
\`\`\`swift
|
|
1583
|
-
// ViewModel
|
|
1584
|
-
@MainActor
|
|
1585
|
-
class UserViewModel: ObservableObject {
|
|
1586
|
-
@Published var users: [User] = []
|
|
1587
|
-
@Published var isLoading = false
|
|
1588
|
-
@Published var error: Error?
|
|
1589
|
-
|
|
1590
|
-
private let service: UserService
|
|
1591
|
-
|
|
1592
|
-
init(service: UserService = .shared) {
|
|
1593
|
-
self.service = service
|
|
1594
|
-
}
|
|
1595
|
-
|
|
1596
|
-
func fetchUsers() async {
|
|
1597
|
-
isLoading = true
|
|
1598
|
-
defer { isLoading = false }
|
|
1599
|
-
|
|
1600
|
-
do {
|
|
1601
|
-
users = try await service.getUsers()
|
|
1602
|
-
} catch {
|
|
1603
|
-
self.error = error
|
|
1604
|
-
}
|
|
1605
|
-
}
|
|
1606
|
-
}
|
|
1607
|
-
|
|
1608
|
-
// View
|
|
1609
|
-
struct UsersView: View {
|
|
1610
|
-
@StateObject private var viewModel = UserViewModel()
|
|
1611
|
-
|
|
1612
|
-
var body: some View {
|
|
1613
|
-
List(viewModel.users) { user in
|
|
1614
|
-
UserRow(user: user)
|
|
1615
|
-
}
|
|
1616
|
-
.overlay {
|
|
1617
|
-
if viewModel.isLoading {
|
|
1618
|
-
ProgressView()
|
|
1619
|
-
}
|
|
1620
|
-
}
|
|
1621
|
-
.task {
|
|
1622
|
-
await viewModel.fetchUsers()
|
|
1623
|
-
}
|
|
1624
|
-
}
|
|
1625
|
-
}
|
|
1626
|
-
\`\`\`
|
|
1627
|
-
|
|
1628
|
-
## Navigation (iOS 16+)
|
|
1629
|
-
|
|
1630
|
-
\`\`\`swift
|
|
1631
|
-
struct AppNavigation: View {
|
|
1632
|
-
@State private var path = NavigationPath()
|
|
1633
|
-
|
|
1634
|
-
var body: some View {
|
|
1635
|
-
NavigationStack(path: $path) {
|
|
1636
|
-
HomeView()
|
|
1637
|
-
.navigationDestination(for: User.self) { user in
|
|
1638
|
-
UserDetailView(user: user)
|
|
1639
|
-
}
|
|
1640
|
-
.navigationDestination(for: Settings.self) { settings in
|
|
1641
|
-
SettingsView(settings: settings)
|
|
1642
|
-
}
|
|
1643
|
-
}
|
|
1644
|
-
}
|
|
1645
|
-
}
|
|
1646
|
-
\`\`\`
|
|
1647
|
-
|
|
1648
|
-
## Async/Await Patterns
|
|
1649
|
-
|
|
1650
|
-
\`\`\`swift
|
|
1651
|
-
// Task modifier for view lifecycle
|
|
1652
|
-
.task {
|
|
1653
|
-
await loadData()
|
|
1654
|
-
}
|
|
1655
|
-
|
|
1656
|
-
// Task with cancellation
|
|
1657
|
-
.task(id: searchText) {
|
|
1658
|
-
try? await Task.sleep(for: .milliseconds(300))
|
|
1659
|
-
await search(searchText)
|
|
1660
|
-
}
|
|
1661
|
-
|
|
1662
|
-
// Refreshable
|
|
1663
|
-
.refreshable {
|
|
1664
|
-
await viewModel.refresh()
|
|
1665
|
-
}
|
|
1666
|
-
\`\`\`
|
|
1667
|
-
|
|
1668
|
-
## Best Practices
|
|
1669
|
-
|
|
1670
|
-
1. **Keep views small** - Extract subviews for reusability
|
|
1671
|
-
2. **Use \`@MainActor\`** - For ViewModels updating UI
|
|
1672
|
-
3. **Prefer value types** - Structs over classes when possible
|
|
1673
|
-
4. **Use \`.task\`** - Instead of \`.onAppear\` for async work
|
|
1674
|
-
5. **Preview extensively** - Use #Preview for rapid iteration
|
|
1675
|
-
`,
|
|
1676
|
-
isNew: true
|
|
1677
|
-
};
|
|
1678
|
-
}
|
|
1679
|
-
function generateUIKitSkill() {
|
|
1680
|
-
return {
|
|
1681
|
-
type: "skill",
|
|
1682
|
-
path: ".claude/skills/uikit-patterns.md",
|
|
1683
|
-
content: `---
|
|
1684
|
-
name: uikit-patterns
|
|
1685
|
-
description: UIKit view controller patterns and best practices
|
|
1686
|
-
globs:
|
|
1687
|
-
- "**/*.swift"
|
|
1688
|
-
---
|
|
1689
|
-
|
|
1690
|
-
# UIKit Patterns
|
|
1691
|
-
|
|
1692
|
-
## View Controller Structure
|
|
1693
|
-
|
|
1694
|
-
\`\`\`swift
|
|
1695
|
-
import UIKit
|
|
1696
|
-
|
|
1697
|
-
class UserViewController: UIViewController {
|
|
1698
|
-
// MARK: - Properties
|
|
1699
|
-
private let viewModel: UserViewModel
|
|
1700
|
-
private var cancellables = Set<AnyCancellable>()
|
|
1701
|
-
|
|
1702
|
-
// MARK: - UI Components
|
|
1703
|
-
private lazy var tableView: UITableView = {
|
|
1704
|
-
let table = UITableView()
|
|
1705
|
-
table.translatesAutoresizingMaskIntoConstraints = false
|
|
1706
|
-
table.delegate = self
|
|
1707
|
-
table.dataSource = self
|
|
1708
|
-
table.register(UserCell.self, forCellReuseIdentifier: UserCell.identifier)
|
|
1709
|
-
return table
|
|
1710
|
-
}()
|
|
1711
|
-
|
|
1712
|
-
// MARK: - Lifecycle
|
|
1713
|
-
init(viewModel: UserViewModel) {
|
|
1714
|
-
self.viewModel = viewModel
|
|
1715
|
-
super.init(nibName: nil, bundle: nil)
|
|
1716
|
-
}
|
|
1717
|
-
|
|
1718
|
-
required init?(coder: NSCoder) {
|
|
1719
|
-
fatalError("init(coder:) has not been implemented")
|
|
1720
|
-
}
|
|
1721
|
-
|
|
1722
|
-
override func viewDidLoad() {
|
|
1723
|
-
super.viewDidLoad()
|
|
1724
|
-
setupUI()
|
|
1725
|
-
setupBindings()
|
|
1726
|
-
viewModel.fetchUsers()
|
|
1727
|
-
}
|
|
1728
|
-
|
|
1729
|
-
// MARK: - Setup
|
|
1730
|
-
private func setupUI() {
|
|
1731
|
-
view.backgroundColor = .systemBackground
|
|
1732
|
-
view.addSubview(tableView)
|
|
1733
|
-
|
|
1734
|
-
NSLayoutConstraint.activate([
|
|
1735
|
-
tableView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
|
|
1736
|
-
tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
|
|
1737
|
-
tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
|
|
1738
|
-
tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
|
|
1739
|
-
])
|
|
1740
|
-
}
|
|
1741
|
-
|
|
1742
|
-
private func setupBindings() {
|
|
1743
|
-
viewModel.$users
|
|
1744
|
-
.receive(on: DispatchQueue.main)
|
|
1745
|
-
.sink { [weak self] _ in
|
|
1746
|
-
self?.tableView.reloadData()
|
|
1747
|
-
}
|
|
1748
|
-
.store(in: &cancellables)
|
|
1749
|
-
}
|
|
1750
|
-
}
|
|
1751
|
-
\`\`\`
|
|
1752
|
-
|
|
1753
|
-
## Coordinator Pattern
|
|
1754
|
-
|
|
1755
|
-
\`\`\`swift
|
|
1756
|
-
protocol Coordinator: AnyObject {
|
|
1757
|
-
var childCoordinators: [Coordinator] { get set }
|
|
1758
|
-
var navigationController: UINavigationController { get set }
|
|
1759
|
-
func start()
|
|
1760
|
-
}
|
|
1761
|
-
|
|
1762
|
-
class AppCoordinator: Coordinator {
|
|
1763
|
-
var childCoordinators: [Coordinator] = []
|
|
1764
|
-
var navigationController: UINavigationController
|
|
1765
|
-
|
|
1766
|
-
init(navigationController: UINavigationController) {
|
|
1767
|
-
self.navigationController = navigationController
|
|
1768
|
-
}
|
|
1769
|
-
|
|
1770
|
-
func start() {
|
|
1771
|
-
let vc = HomeViewController()
|
|
1772
|
-
vc.coordinator = self
|
|
1773
|
-
navigationController.pushViewController(vc, animated: false)
|
|
1774
|
-
}
|
|
1775
|
-
|
|
1776
|
-
func showUserDetail(_ user: User) {
|
|
1777
|
-
let vc = UserDetailViewController(user: user)
|
|
1778
|
-
navigationController.pushViewController(vc, animated: true)
|
|
1779
|
-
}
|
|
1780
|
-
}
|
|
1781
|
-
\`\`\`
|
|
1782
|
-
|
|
1783
|
-
## Table View Cell
|
|
1784
|
-
|
|
1785
|
-
\`\`\`swift
|
|
1786
|
-
class UserCell: UITableViewCell {
|
|
1787
|
-
static let identifier = "UserCell"
|
|
1788
|
-
|
|
1789
|
-
private let nameLabel: UILabel = {
|
|
1790
|
-
let label = UILabel()
|
|
1791
|
-
label.font = .preferredFont(forTextStyle: .headline)
|
|
1792
|
-
label.translatesAutoresizingMaskIntoConstraints = false
|
|
1793
|
-
return label
|
|
1794
|
-
}()
|
|
1795
|
-
|
|
1796
|
-
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
|
|
1797
|
-
super.init(style: style, reuseIdentifier: reuseIdentifier)
|
|
1798
|
-
setupUI()
|
|
1799
|
-
}
|
|
1800
|
-
|
|
1801
|
-
required init?(coder: NSCoder) {
|
|
1802
|
-
fatalError("init(coder:) has not been implemented")
|
|
1803
|
-
}
|
|
1804
|
-
|
|
1805
|
-
private func setupUI() {
|
|
1806
|
-
contentView.addSubview(nameLabel)
|
|
1807
|
-
NSLayoutConstraint.activate([
|
|
1808
|
-
nameLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 16),
|
|
1809
|
-
nameLabel.centerYAnchor.constraint(equalTo: contentView.centerYAnchor)
|
|
1810
|
-
])
|
|
1811
|
-
}
|
|
1812
|
-
|
|
1813
|
-
func configure(with user: User) {
|
|
1814
|
-
nameLabel.text = user.name
|
|
1815
|
-
}
|
|
1816
|
-
}
|
|
1817
|
-
\`\`\`
|
|
1818
|
-
|
|
1819
|
-
## Best Practices
|
|
1820
|
-
|
|
1821
|
-
1. **Use Auto Layout** - Programmatic constraints over Storyboards
|
|
1822
|
-
2. **MARK comments** - Organize code sections
|
|
1823
|
-
3. **Coordinator pattern** - For navigation logic
|
|
1824
|
-
4. **Dependency injection** - Pass dependencies via init
|
|
1825
|
-
5. **Combine for bindings** - Reactive updates from ViewModel
|
|
1826
|
-
`,
|
|
1827
|
-
isNew: true
|
|
1828
|
-
};
|
|
1829
|
-
}
|
|
1830
|
-
function generateVaporSkill() {
|
|
1831
|
-
return {
|
|
1832
|
-
type: "skill",
|
|
1833
|
-
path: ".claude/skills/vapor-patterns.md",
|
|
1834
|
-
content: `---
|
|
1835
|
-
name: vapor-patterns
|
|
1836
|
-
description: Vapor server-side Swift patterns
|
|
1837
|
-
globs:
|
|
1838
|
-
- "**/*.swift"
|
|
1839
|
-
- "Package.swift"
|
|
1840
|
-
---
|
|
1841
|
-
|
|
1842
|
-
# Vapor Patterns
|
|
1843
|
-
|
|
1844
|
-
## Route Structure
|
|
1845
|
-
|
|
1846
|
-
\`\`\`swift
|
|
1847
|
-
import Vapor
|
|
1848
|
-
|
|
1849
|
-
func routes(_ app: Application) throws {
|
|
1850
|
-
// Basic routes
|
|
1851
|
-
app.get { req in
|
|
1852
|
-
"Hello, world!"
|
|
1853
|
-
}
|
|
1854
|
-
|
|
1855
|
-
// Route groups
|
|
1856
|
-
let api = app.grouped("api", "v1")
|
|
1857
|
-
|
|
1858
|
-
// Controller registration
|
|
1859
|
-
try api.register(collection: UserController())
|
|
1860
|
-
}
|
|
1861
|
-
\`\`\`
|
|
1862
|
-
|
|
1863
|
-
## Controller Pattern
|
|
1864
|
-
|
|
1865
|
-
\`\`\`swift
|
|
1866
|
-
import Vapor
|
|
1867
|
-
|
|
1868
|
-
struct UserController: RouteCollection {
|
|
1869
|
-
func boot(routes: RoutesBuilder) throws {
|
|
1870
|
-
let users = routes.grouped("users")
|
|
1871
|
-
|
|
1872
|
-
users.get(use: index)
|
|
1873
|
-
users.post(use: create)
|
|
1874
|
-
users.group(":userID") { user in
|
|
1875
|
-
user.get(use: show)
|
|
1876
|
-
user.put(use: update)
|
|
1877
|
-
user.delete(use: delete)
|
|
1878
|
-
}
|
|
1879
|
-
}
|
|
1880
|
-
|
|
1881
|
-
// GET /users
|
|
1882
|
-
func index(req: Request) async throws -> [UserDTO] {
|
|
1883
|
-
try await User.query(on: req.db).all().map { $0.toDTO() }
|
|
1884
|
-
}
|
|
1885
|
-
|
|
1886
|
-
// POST /users
|
|
1887
|
-
func create(req: Request) async throws -> UserDTO {
|
|
1888
|
-
let input = try req.content.decode(CreateUserInput.self)
|
|
1889
|
-
let user = User(name: input.name, email: input.email)
|
|
1890
|
-
try await user.save(on: req.db)
|
|
1891
|
-
return user.toDTO()
|
|
1892
|
-
}
|
|
1893
|
-
|
|
1894
|
-
// GET /users/:userID
|
|
1895
|
-
func show(req: Request) async throws -> UserDTO {
|
|
1896
|
-
guard let user = try await User.find(req.parameters.get("userID"), on: req.db) else {
|
|
1897
|
-
throw Abort(.notFound)
|
|
1898
|
-
}
|
|
1899
|
-
return user.toDTO()
|
|
1900
|
-
}
|
|
1901
|
-
}
|
|
1902
|
-
\`\`\`
|
|
1903
|
-
|
|
1904
|
-
## Fluent Models
|
|
1905
|
-
|
|
1906
|
-
\`\`\`swift
|
|
1907
|
-
import Fluent
|
|
1908
|
-
import Vapor
|
|
1909
|
-
|
|
1910
|
-
final class User: Model, Content {
|
|
1911
|
-
static let schema = "users"
|
|
1912
|
-
|
|
1913
|
-
@ID(key: .id)
|
|
1914
|
-
var id: UUID?
|
|
1915
|
-
|
|
1916
|
-
@Field(key: "name")
|
|
1917
|
-
var name: String
|
|
1918
|
-
|
|
1919
|
-
@Field(key: "email")
|
|
1920
|
-
var email: String
|
|
1921
|
-
|
|
1922
|
-
@Timestamp(key: "created_at", on: .create)
|
|
1923
|
-
var createdAt: Date?
|
|
1924
|
-
|
|
1925
|
-
@Children(for: \\.$user)
|
|
1926
|
-
var posts: [Post]
|
|
1927
|
-
|
|
1928
|
-
init() {}
|
|
1929
|
-
|
|
1930
|
-
init(id: UUID? = nil, name: String, email: String) {
|
|
1931
|
-
self.id = id
|
|
1932
|
-
self.name = name
|
|
1933
|
-
self.email = email
|
|
1934
|
-
}
|
|
1935
|
-
}
|
|
1936
|
-
|
|
1937
|
-
// Migration
|
|
1938
|
-
struct CreateUser: AsyncMigration {
|
|
1939
|
-
func prepare(on database: Database) async throws {
|
|
1940
|
-
try await database.schema("users")
|
|
1941
|
-
.id()
|
|
1942
|
-
.field("name", .string, .required)
|
|
1943
|
-
.field("email", .string, .required)
|
|
1944
|
-
.field("created_at", .datetime)
|
|
1945
|
-
.unique(on: "email")
|
|
1946
|
-
.create()
|
|
1947
|
-
}
|
|
1948
|
-
|
|
1949
|
-
func revert(on database: Database) async throws {
|
|
1950
|
-
try await database.schema("users").delete()
|
|
1951
|
-
}
|
|
1952
|
-
}
|
|
1953
|
-
\`\`\`
|
|
1954
|
-
|
|
1955
|
-
## DTOs and Validation
|
|
1956
|
-
|
|
1957
|
-
\`\`\`swift
|
|
1958
|
-
struct CreateUserInput: Content, Validatable {
|
|
1959
|
-
var name: String
|
|
1960
|
-
var email: String
|
|
1961
|
-
|
|
1962
|
-
static func validations(_ validations: inout Validations) {
|
|
1963
|
-
validations.add("name", as: String.self, is: !.empty)
|
|
1964
|
-
validations.add("email", as: String.self, is: .email)
|
|
1965
|
-
}
|
|
1966
|
-
}
|
|
1967
|
-
|
|
1968
|
-
struct UserDTO: Content {
|
|
1969
|
-
var id: UUID?
|
|
1970
|
-
var name: String
|
|
1971
|
-
var email: String
|
|
1972
|
-
}
|
|
1973
|
-
|
|
1974
|
-
extension User {
|
|
1975
|
-
func toDTO() -> UserDTO {
|
|
1976
|
-
UserDTO(id: id, name: name, email: email)
|
|
1977
|
-
}
|
|
1978
|
-
}
|
|
1979
|
-
\`\`\`
|
|
1980
|
-
|
|
1981
|
-
## Middleware
|
|
1982
|
-
|
|
1983
|
-
\`\`\`swift
|
|
1984
|
-
struct AuthMiddleware: AsyncMiddleware {
|
|
1985
|
-
func respond(to request: Request, chainingTo next: AsyncResponder) async throws -> Response {
|
|
1986
|
-
guard let token = request.headers.bearerAuthorization?.token else {
|
|
1987
|
-
throw Abort(.unauthorized)
|
|
1988
|
-
}
|
|
1989
|
-
|
|
1990
|
-
// Validate token
|
|
1991
|
-
let user = try await validateToken(token, on: request)
|
|
1992
|
-
request.auth.login(user)
|
|
1993
|
-
|
|
1994
|
-
return try await next.respond(to: request)
|
|
1995
|
-
}
|
|
1996
|
-
}
|
|
1997
|
-
\`\`\`
|
|
1998
|
-
|
|
1999
|
-
## Testing
|
|
2000
|
-
|
|
2001
|
-
\`\`\`swift
|
|
2002
|
-
@testable import App
|
|
2003
|
-
import XCTVapor
|
|
2004
|
-
|
|
2005
|
-
final class UserTests: XCTestCase {
|
|
2006
|
-
var app: Application!
|
|
2007
|
-
|
|
2008
|
-
override func setUp() async throws {
|
|
2009
|
-
app = Application(.testing)
|
|
2010
|
-
try configure(app)
|
|
2011
|
-
try await app.autoMigrate()
|
|
2012
|
-
}
|
|
2013
|
-
|
|
2014
|
-
override func tearDown() async throws {
|
|
2015
|
-
try await app.autoRevert()
|
|
2016
|
-
app.shutdown()
|
|
2017
|
-
}
|
|
2018
|
-
|
|
2019
|
-
func testCreateUser() async throws {
|
|
2020
|
-
try app.test(.POST, "api/v1/users", beforeRequest: { req in
|
|
2021
|
-
try req.content.encode(CreateUserInput(name: "Test", email: "test@example.com"))
|
|
2022
|
-
}, afterResponse: { res in
|
|
2023
|
-
XCTAssertEqual(res.status, .ok)
|
|
2024
|
-
let user = try res.content.decode(UserDTO.self)
|
|
2025
|
-
XCTAssertEqual(user.name, "Test")
|
|
2026
|
-
})
|
|
2027
|
-
}
|
|
2028
|
-
}
|
|
2029
|
-
\`\`\`
|
|
2030
|
-
`,
|
|
2031
|
-
isNew: true
|
|
2032
|
-
};
|
|
2033
|
-
}
|
|
2034
|
-
function generateJetpackComposeSkill() {
|
|
2035
|
-
return {
|
|
2036
|
-
type: "skill",
|
|
2037
|
-
path: ".claude/skills/compose-patterns.md",
|
|
2038
|
-
content: `---
|
|
2039
|
-
name: compose-patterns
|
|
2040
|
-
description: Jetpack Compose UI patterns and best practices
|
|
2041
|
-
globs:
|
|
2042
|
-
- "**/*.kt"
|
|
2043
|
-
---
|
|
2044
|
-
|
|
2045
|
-
# Jetpack Compose Patterns
|
|
2046
|
-
|
|
2047
|
-
## Composable Structure
|
|
2048
|
-
|
|
2049
|
-
\`\`\`kotlin
|
|
2050
|
-
@Composable
|
|
2051
|
-
fun UserScreen(
|
|
2052
|
-
viewModel: UserViewModel = hiltViewModel()
|
|
2053
|
-
) {
|
|
2054
|
-
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
|
|
2055
|
-
|
|
2056
|
-
UserScreenContent(
|
|
2057
|
-
uiState = uiState,
|
|
2058
|
-
onRefresh = viewModel::refresh,
|
|
2059
|
-
onUserClick = viewModel::selectUser
|
|
2060
|
-
)
|
|
2061
|
-
}
|
|
2062
|
-
|
|
2063
|
-
@Composable
|
|
2064
|
-
private fun UserScreenContent(
|
|
2065
|
-
uiState: UserUiState,
|
|
2066
|
-
onRefresh: () -> Unit,
|
|
2067
|
-
onUserClick: (User) -> Unit
|
|
2068
|
-
) {
|
|
2069
|
-
Scaffold(
|
|
2070
|
-
topBar = {
|
|
2071
|
-
TopAppBar(title = { Text("Users") })
|
|
2072
|
-
}
|
|
2073
|
-
) { padding ->
|
|
2074
|
-
when (uiState) {
|
|
2075
|
-
is UserUiState.Loading -> LoadingIndicator()
|
|
2076
|
-
is UserUiState.Success -> UserList(
|
|
2077
|
-
users = uiState.users,
|
|
2078
|
-
onUserClick = onUserClick,
|
|
2079
|
-
modifier = Modifier.padding(padding)
|
|
2080
|
-
)
|
|
2081
|
-
is UserUiState.Error -> ErrorMessage(uiState.message)
|
|
2082
|
-
}
|
|
2083
|
-
}
|
|
2084
|
-
}
|
|
2085
|
-
\`\`\`
|
|
2086
|
-
|
|
2087
|
-
## State Management
|
|
2088
|
-
|
|
2089
|
-
\`\`\`kotlin
|
|
2090
|
-
// UI State
|
|
2091
|
-
sealed interface UserUiState {
|
|
2092
|
-
object Loading : UserUiState
|
|
2093
|
-
data class Success(val users: List<User>) : UserUiState
|
|
2094
|
-
data class Error(val message: String) : UserUiState
|
|
2095
|
-
}
|
|
2096
|
-
|
|
2097
|
-
// ViewModel
|
|
2098
|
-
@HiltViewModel
|
|
2099
|
-
class UserViewModel @Inject constructor(
|
|
2100
|
-
private val repository: UserRepository
|
|
2101
|
-
) : ViewModel() {
|
|
2102
|
-
|
|
2103
|
-
private val _uiState = MutableStateFlow<UserUiState>(UserUiState.Loading)
|
|
2104
|
-
val uiState: StateFlow<UserUiState> = _uiState.asStateFlow()
|
|
2105
|
-
|
|
2106
|
-
init {
|
|
2107
|
-
loadUsers()
|
|
2108
|
-
}
|
|
2109
|
-
|
|
2110
|
-
fun refresh() {
|
|
2111
|
-
loadUsers()
|
|
2112
|
-
}
|
|
2113
|
-
|
|
2114
|
-
private fun loadUsers() {
|
|
2115
|
-
viewModelScope.launch {
|
|
2116
|
-
_uiState.value = UserUiState.Loading
|
|
2117
|
-
repository.getUsers()
|
|
2118
|
-
.onSuccess { users ->
|
|
2119
|
-
_uiState.value = UserUiState.Success(users)
|
|
2120
|
-
}
|
|
2121
|
-
.onFailure { error ->
|
|
2122
|
-
_uiState.value = UserUiState.Error(error.message ?: "Unknown error")
|
|
2123
|
-
}
|
|
2124
|
-
}
|
|
2125
|
-
}
|
|
2126
|
-
}
|
|
2127
|
-
\`\`\`
|
|
2128
|
-
|
|
2129
|
-
## Reusable Components
|
|
2130
|
-
|
|
2131
|
-
\`\`\`kotlin
|
|
2132
|
-
@Composable
|
|
2133
|
-
fun UserCard(
|
|
2134
|
-
user: User,
|
|
2135
|
-
onClick: () -> Unit,
|
|
2136
|
-
modifier: Modifier = Modifier
|
|
2137
|
-
) {
|
|
2138
|
-
Card(
|
|
2139
|
-
onClick = onClick,
|
|
2140
|
-
modifier = modifier.fillMaxWidth()
|
|
2141
|
-
) {
|
|
2142
|
-
Row(
|
|
2143
|
-
modifier = Modifier.padding(16.dp),
|
|
2144
|
-
verticalAlignment = Alignment.CenterVertically
|
|
2145
|
-
) {
|
|
2146
|
-
AsyncImage(
|
|
2147
|
-
model = user.avatarUrl,
|
|
2148
|
-
contentDescription = null,
|
|
2149
|
-
modifier = Modifier
|
|
2150
|
-
.size(48.dp)
|
|
2151
|
-
.clip(CircleShape)
|
|
2152
|
-
)
|
|
2153
|
-
Spacer(modifier = Modifier.width(16.dp))
|
|
2154
|
-
Column {
|
|
2155
|
-
Text(
|
|
2156
|
-
text = user.name,
|
|
2157
|
-
style = MaterialTheme.typography.titleMedium
|
|
2158
|
-
)
|
|
2159
|
-
Text(
|
|
2160
|
-
text = user.email,
|
|
2161
|
-
style = MaterialTheme.typography.bodySmall
|
|
2162
|
-
)
|
|
2163
|
-
}
|
|
2164
|
-
}
|
|
2165
|
-
}
|
|
2166
|
-
}
|
|
2167
|
-
\`\`\`
|
|
2168
|
-
|
|
2169
|
-
## Navigation
|
|
2170
|
-
|
|
2171
|
-
\`\`\`kotlin
|
|
2172
|
-
@Composable
|
|
2173
|
-
fun AppNavigation() {
|
|
2174
|
-
val navController = rememberNavController()
|
|
2175
|
-
|
|
2176
|
-
NavHost(
|
|
2177
|
-
navController = navController,
|
|
2178
|
-
startDestination = "home"
|
|
2179
|
-
) {
|
|
2180
|
-
composable("home") {
|
|
2181
|
-
HomeScreen(
|
|
2182
|
-
onUserClick = { userId ->
|
|
2183
|
-
navController.navigate("user/$userId")
|
|
2184
|
-
}
|
|
2185
|
-
)
|
|
2186
|
-
}
|
|
2187
|
-
composable(
|
|
2188
|
-
route = "user/{userId}",
|
|
2189
|
-
arguments = listOf(navArgument("userId") { type = NavType.StringType })
|
|
2190
|
-
) { backStackEntry ->
|
|
2191
|
-
val userId = backStackEntry.arguments?.getString("userId")
|
|
2192
|
-
UserDetailScreen(userId = userId)
|
|
2193
|
-
}
|
|
2194
|
-
}
|
|
2195
|
-
}
|
|
2196
|
-
\`\`\`
|
|
2197
|
-
|
|
2198
|
-
## Theming
|
|
2199
|
-
|
|
2200
|
-
\`\`\`kotlin
|
|
2201
|
-
@Composable
|
|
2202
|
-
fun AppTheme(
|
|
2203
|
-
darkTheme: Boolean = isSystemInDarkTheme(),
|
|
2204
|
-
content: @Composable () -> Unit
|
|
2205
|
-
) {
|
|
2206
|
-
val colorScheme = if (darkTheme) {
|
|
2207
|
-
darkColorScheme(
|
|
2208
|
-
primary = Purple80,
|
|
2209
|
-
secondary = PurpleGrey80
|
|
2210
|
-
)
|
|
2211
|
-
} else {
|
|
2212
|
-
lightColorScheme(
|
|
2213
|
-
primary = Purple40,
|
|
2214
|
-
secondary = PurpleGrey40
|
|
2215
|
-
)
|
|
2216
|
-
}
|
|
2217
|
-
|
|
2218
|
-
MaterialTheme(
|
|
2219
|
-
colorScheme = colorScheme,
|
|
2220
|
-
typography = Typography,
|
|
2221
|
-
content = content
|
|
2222
|
-
)
|
|
2223
|
-
}
|
|
2224
|
-
\`\`\`
|
|
2225
|
-
|
|
2226
|
-
## Best Practices
|
|
2227
|
-
|
|
2228
|
-
1. **Stateless composables** - Pass state down, events up
|
|
2229
|
-
2. **Remember wisely** - Use \`remember\` for expensive calculations
|
|
2230
|
-
3. **Lifecycle-aware collection** - Use \`collectAsStateWithLifecycle()\`
|
|
2231
|
-
4. **Modifier parameter** - Always accept Modifier as last parameter
|
|
2232
|
-
5. **Preview annotations** - Add @Preview for rapid iteration
|
|
2233
|
-
`,
|
|
2234
|
-
isNew: true
|
|
2235
|
-
};
|
|
2236
|
-
}
|
|
2237
|
-
function generateAndroidViewsSkill() {
|
|
2238
|
-
return {
|
|
2239
|
-
type: "skill",
|
|
2240
|
-
path: ".claude/skills/android-views-patterns.md",
|
|
2241
|
-
content: `---
|
|
2242
|
-
name: android-views-patterns
|
|
2243
|
-
description: Android XML views and traditional patterns
|
|
2244
|
-
globs:
|
|
2245
|
-
- "**/*.kt"
|
|
2246
|
-
- "**/*.xml"
|
|
2247
|
-
---
|
|
2248
|
-
|
|
2249
|
-
# Android Views Patterns
|
|
2250
|
-
|
|
2251
|
-
## Activity Structure
|
|
2252
|
-
|
|
2253
|
-
\`\`\`kotlin
|
|
2254
|
-
class MainActivity : AppCompatActivity() {
|
|
2255
|
-
|
|
2256
|
-
private lateinit var binding: ActivityMainBinding
|
|
2257
|
-
private val viewModel: MainViewModel by viewModels()
|
|
2258
|
-
|
|
2259
|
-
override fun onCreate(savedInstanceState: Bundle?) {
|
|
2260
|
-
super.onCreate(savedInstanceState)
|
|
2261
|
-
binding = ActivityMainBinding.inflate(layoutInflater)
|
|
2262
|
-
setContentView(binding.root)
|
|
2263
|
-
|
|
2264
|
-
setupUI()
|
|
2265
|
-
observeViewModel()
|
|
2266
|
-
}
|
|
2267
|
-
|
|
2268
|
-
private fun setupUI() {
|
|
2269
|
-
binding.recyclerView.apply {
|
|
2270
|
-
layoutManager = LinearLayoutManager(this@MainActivity)
|
|
2271
|
-
adapter = userAdapter
|
|
2272
|
-
}
|
|
2273
|
-
|
|
2274
|
-
binding.swipeRefresh.setOnRefreshListener {
|
|
2275
|
-
viewModel.refresh()
|
|
2276
|
-
}
|
|
2277
|
-
}
|
|
2278
|
-
|
|
2279
|
-
private fun observeViewModel() {
|
|
2280
|
-
lifecycleScope.launch {
|
|
2281
|
-
repeatOnLifecycle(Lifecycle.State.STARTED) {
|
|
2282
|
-
viewModel.uiState.collect { state ->
|
|
2283
|
-
updateUI(state)
|
|
2284
|
-
}
|
|
2285
|
-
}
|
|
2286
|
-
}
|
|
2287
|
-
}
|
|
2288
|
-
|
|
2289
|
-
private fun updateUI(state: MainUiState) {
|
|
2290
|
-
binding.swipeRefresh.isRefreshing = state.isLoading
|
|
2291
|
-
userAdapter.submitList(state.users)
|
|
2292
|
-
binding.errorText.isVisible = state.error != null
|
|
2293
|
-
binding.errorText.text = state.error
|
|
2294
|
-
}
|
|
2295
|
-
}
|
|
2296
|
-
\`\`\`
|
|
2297
|
-
|
|
2298
|
-
## Fragment Pattern
|
|
2299
|
-
|
|
2300
|
-
\`\`\`kotlin
|
|
2301
|
-
class UserFragment : Fragment(R.layout.fragment_user) {
|
|
2302
|
-
|
|
2303
|
-
private var _binding: FragmentUserBinding? = null
|
|
2304
|
-
private val binding get() = _binding!!
|
|
2305
|
-
|
|
2306
|
-
private val viewModel: UserViewModel by viewModels()
|
|
2307
|
-
private val args: UserFragmentArgs by navArgs()
|
|
2308
|
-
|
|
2309
|
-
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
|
2310
|
-
super.onViewCreated(view, savedInstanceState)
|
|
2311
|
-
_binding = FragmentUserBinding.bind(view)
|
|
2312
|
-
|
|
2313
|
-
setupUI()
|
|
2314
|
-
observeViewModel()
|
|
2315
|
-
viewModel.loadUser(args.userId)
|
|
2316
|
-
}
|
|
2317
|
-
|
|
2318
|
-
override fun onDestroyView() {
|
|
2319
|
-
super.onDestroyView()
|
|
2320
|
-
_binding = null
|
|
2321
|
-
}
|
|
2322
|
-
}
|
|
2323
|
-
\`\`\`
|
|
2324
|
-
|
|
2325
|
-
## RecyclerView Adapter
|
|
2326
|
-
|
|
2327
|
-
\`\`\`kotlin
|
|
2328
|
-
class UserAdapter(
|
|
2329
|
-
private val onItemClick: (User) -> Unit
|
|
2330
|
-
) : ListAdapter<User, UserAdapter.ViewHolder>(UserDiffCallback()) {
|
|
2331
|
-
|
|
2332
|
-
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
|
2333
|
-
val binding = ItemUserBinding.inflate(
|
|
2334
|
-
LayoutInflater.from(parent.context),
|
|
2335
|
-
parent,
|
|
2336
|
-
false
|
|
2337
|
-
)
|
|
2338
|
-
return ViewHolder(binding)
|
|
2339
|
-
}
|
|
2340
|
-
|
|
2341
|
-
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
|
2342
|
-
holder.bind(getItem(position))
|
|
2343
|
-
}
|
|
2344
|
-
|
|
2345
|
-
inner class ViewHolder(
|
|
2346
|
-
private val binding: ItemUserBinding
|
|
2347
|
-
) : RecyclerView.ViewHolder(binding.root) {
|
|
2348
|
-
|
|
2349
|
-
init {
|
|
2350
|
-
binding.root.setOnClickListener {
|
|
2351
|
-
onItemClick(getItem(adapterPosition))
|
|
2352
|
-
}
|
|
2353
|
-
}
|
|
2354
|
-
|
|
2355
|
-
fun bind(user: User) {
|
|
2356
|
-
binding.nameText.text = user.name
|
|
2357
|
-
binding.emailText.text = user.email
|
|
2358
|
-
Glide.with(binding.avatar)
|
|
2359
|
-
.load(user.avatarUrl)
|
|
2360
|
-
.circleCrop()
|
|
2361
|
-
.into(binding.avatar)
|
|
2362
|
-
}
|
|
2363
|
-
}
|
|
2364
|
-
|
|
2365
|
-
class UserDiffCallback : DiffUtil.ItemCallback<User>() {
|
|
2366
|
-
override fun areItemsTheSame(oldItem: User, newItem: User) =
|
|
2367
|
-
oldItem.id == newItem.id
|
|
2368
|
-
|
|
2369
|
-
override fun areContentsTheSame(oldItem: User, newItem: User) =
|
|
2370
|
-
oldItem == newItem
|
|
2371
|
-
}
|
|
2372
|
-
}
|
|
2373
|
-
\`\`\`
|
|
2374
|
-
|
|
2375
|
-
## XML Layout
|
|
2376
|
-
|
|
2377
|
-
\`\`\`xml
|
|
2378
|
-
<?xml version="1.0" encoding="utf-8"?>
|
|
2379
|
-
<androidx.constraintlayout.widget.ConstraintLayout
|
|
2380
|
-
xmlns:android="http://schemas.android.com/apk/res/android"
|
|
2381
|
-
xmlns:app="http://schemas.android.com/apk/res-auto"
|
|
2382
|
-
android:layout_width="match_parent"
|
|
2383
|
-
android:layout_height="match_parent">
|
|
2384
|
-
|
|
2385
|
-
<com.google.android.material.appbar.MaterialToolbar
|
|
2386
|
-
android:id="@+id/toolbar"
|
|
2387
|
-
android:layout_width="0dp"
|
|
2388
|
-
android:layout_height="?attr/actionBarSize"
|
|
2389
|
-
app:title="Users"
|
|
2390
|
-
app:layout_constraintTop_toTopOf="parent"
|
|
2391
|
-
app:layout_constraintStart_toStartOf="parent"
|
|
2392
|
-
app:layout_constraintEnd_toEndOf="parent" />
|
|
2393
|
-
|
|
2394
|
-
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
|
2395
|
-
android:id="@+id/swipeRefresh"
|
|
2396
|
-
android:layout_width="0dp"
|
|
2397
|
-
android:layout_height="0dp"
|
|
2398
|
-
app:layout_constraintTop_toBottomOf="@id/toolbar"
|
|
2399
|
-
app:layout_constraintBottom_toBottomOf="parent"
|
|
2400
|
-
app:layout_constraintStart_toStartOf="parent"
|
|
2401
|
-
app:layout_constraintEnd_toEndOf="parent">
|
|
2402
|
-
|
|
2403
|
-
<androidx.recyclerview.widget.RecyclerView
|
|
2404
|
-
android:id="@+id/recyclerView"
|
|
2405
|
-
android:layout_width="match_parent"
|
|
2406
|
-
android:layout_height="match_parent" />
|
|
2407
|
-
|
|
2408
|
-
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
|
|
2409
|
-
|
|
2410
|
-
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
2411
|
-
\`\`\`
|
|
2412
|
-
|
|
2413
|
-
## ViewModel with Repository
|
|
2414
|
-
|
|
2415
|
-
\`\`\`kotlin
|
|
2416
|
-
@HiltViewModel
|
|
2417
|
-
class UserViewModel @Inject constructor(
|
|
2418
|
-
private val repository: UserRepository,
|
|
2419
|
-
private val savedStateHandle: SavedStateHandle
|
|
2420
|
-
) : ViewModel() {
|
|
2421
|
-
|
|
2422
|
-
private val _uiState = MutableStateFlow(UserUiState())
|
|
2423
|
-
val uiState: StateFlow<UserUiState> = _uiState.asStateFlow()
|
|
2424
|
-
|
|
2425
|
-
fun loadUsers() {
|
|
2426
|
-
viewModelScope.launch {
|
|
2427
|
-
_uiState.update { it.copy(isLoading = true) }
|
|
2428
|
-
try {
|
|
2429
|
-
val users = repository.getUsers()
|
|
2430
|
-
_uiState.update { it.copy(users = users, isLoading = false) }
|
|
2431
|
-
} catch (e: Exception) {
|
|
2432
|
-
_uiState.update { it.copy(error = e.message, isLoading = false) }
|
|
2433
|
-
}
|
|
2434
|
-
}
|
|
2435
|
-
}
|
|
2436
|
-
}
|
|
2437
|
-
|
|
2438
|
-
data class UserUiState(
|
|
2439
|
-
val users: List<User> = emptyList(),
|
|
2440
|
-
val isLoading: Boolean = false,
|
|
2441
|
-
val error: String? = null
|
|
2442
|
-
)
|
|
2443
|
-
\`\`\`
|
|
2444
|
-
|
|
2445
|
-
## Best Practices
|
|
2446
|
-
|
|
2447
|
-
1. **View Binding** - Use over findViewById or synthetic imports
|
|
2448
|
-
2. **Lifecycle awareness** - Collect flows in repeatOnLifecycle
|
|
2449
|
-
3. **ListAdapter** - For efficient RecyclerView updates
|
|
2450
|
-
4. **Navigation Component** - For fragment navigation
|
|
2451
|
-
5. **Clean up bindings** - Set to null in onDestroyView
|
|
2452
|
-
`,
|
|
2453
|
-
isNew: true
|
|
2454
|
-
};
|
|
2455
|
-
}
|
|
2456
|
-
function generateIterativeDevelopmentSkill(stack) {
|
|
2457
|
-
const testCmd = getTestCommand(stack);
|
|
2458
|
-
const lintCmd = getLintCommand(stack);
|
|
2459
|
-
return {
|
|
2460
|
-
type: "skill",
|
|
2461
|
-
path: ".claude/skills/iterative-development.md",
|
|
2462
|
-
content: `---
|
|
2463
|
-
name: iterative-development
|
|
2464
|
-
description: TDD-driven iterative loops until tests pass
|
|
2465
|
-
globs:
|
|
2466
|
-
- "**/*.ts"
|
|
2467
|
-
- "**/*.tsx"
|
|
2468
|
-
- "**/*.js"
|
|
2469
|
-
- "**/*.py"
|
|
2470
|
-
- "**/*.go"
|
|
2471
|
-
---
|
|
2472
|
-
|
|
2473
|
-
# Iterative Development (TDD Loops)
|
|
2474
|
-
|
|
2475
|
-
Self-referential development loops where you iterate until completion criteria are met.
|
|
2476
|
-
|
|
2477
|
-
## Core Philosophy
|
|
2478
|
-
|
|
2479
|
-
\`\`\`
|
|
2480
|
-
\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
|
|
2481
|
-
\u2502 ITERATION > PERFECTION \u2502
|
|
2482
|
-
\u2502 Don't aim for perfect on first try. \u2502
|
|
2483
|
-
\u2502 Let the loop refine the work. \u2502
|
|
2484
|
-
\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
|
|
2485
|
-
\u2502 FAILURES ARE DATA \u2502
|
|
2486
|
-
\u2502 Failed tests, lint errors, type mismatches are signals. \u2502
|
|
2487
|
-
\u2502 Use them to guide the next iteration. \u2502
|
|
2488
|
-
\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
|
|
2489
|
-
\u2502 CLEAR COMPLETION CRITERIA \u2502
|
|
2490
|
-
\u2502 Define exactly what "done" looks like. \u2502
|
|
2491
|
-
\u2502 Tests passing. Coverage met. Lint clean. \u2502
|
|
2492
|
-
\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
|
|
2493
|
-
\`\`\`
|
|
2494
|
-
|
|
2495
|
-
## TDD Workflow (Mandatory)
|
|
2496
|
-
|
|
2497
|
-
Every implementation task MUST follow this workflow:
|
|
2498
|
-
|
|
2499
|
-
### 1. RED: Write Tests First
|
|
2500
|
-
\`\`\`bash
|
|
2501
|
-
# Write tests based on requirements
|
|
2502
|
-
# Run tests - they MUST FAIL
|
|
2503
|
-
${testCmd}
|
|
2504
|
-
\`\`\`
|
|
2505
|
-
|
|
2506
|
-
### 2. GREEN: Implement Feature
|
|
2507
|
-
\`\`\`bash
|
|
2508
|
-
# Write minimum code to pass tests
|
|
2509
|
-
# Run tests - they MUST PASS
|
|
2510
|
-
${testCmd}
|
|
2511
|
-
\`\`\`
|
|
2512
|
-
|
|
2513
|
-
### 3. VALIDATE: Quality Gates
|
|
2514
|
-
\`\`\`bash
|
|
2515
|
-
# Full quality check
|
|
2516
|
-
${lintCmd ? `${lintCmd} && ` : ""}${testCmd}
|
|
2517
|
-
\`\`\`
|
|
2518
|
-
|
|
2519
|
-
## Completion Criteria Template
|
|
2520
|
-
|
|
2521
|
-
For any implementation task, define:
|
|
2522
|
-
|
|
2523
|
-
\`\`\`markdown
|
|
2524
|
-
### Completion Criteria
|
|
2525
|
-
- [ ] All tests passing
|
|
2526
|
-
- [ ] Coverage >= 80% (on new code)
|
|
2527
|
-
- [ ] Lint clean (no errors)
|
|
2528
|
-
- [ ] Type check passing
|
|
2529
|
-
\`\`\`
|
|
2530
|
-
|
|
2531
|
-
## When to Use This Workflow
|
|
2532
|
-
|
|
2533
|
-
| Task Type | Use TDD Loop? |
|
|
2534
|
-
|-----------|---------------|
|
|
2535
|
-
| New feature | \u2705 Always |
|
|
2536
|
-
| Bug fix | \u2705 Always (write test that reproduces bug first) |
|
|
2537
|
-
| Refactoring | \u2705 Always (existing tests must stay green) |
|
|
2538
|
-
| Spike/exploration | \u274C Skip (but document findings) |
|
|
2539
|
-
| Documentation | \u274C Skip |
|
|
2540
|
-
|
|
2541
|
-
## Anti-Patterns
|
|
2542
|
-
|
|
2543
|
-
- \u274C Writing code before tests
|
|
2544
|
-
- \u274C Skipping the RED phase (tests that never fail are useless)
|
|
2545
|
-
- \u274C Moving on when tests fail
|
|
2546
|
-
- \u274C Large batches (prefer small, focused iterations)
|
|
2547
|
-
`,
|
|
2548
|
-
isNew: true
|
|
2549
|
-
};
|
|
2550
|
-
}
|
|
2551
|
-
function generateCommitHygieneSkill() {
|
|
2552
|
-
return {
|
|
2553
|
-
type: "skill",
|
|
2554
|
-
path: ".claude/skills/commit-hygiene.md",
|
|
2555
|
-
content: `---
|
|
2556
|
-
name: commit-hygiene
|
|
2557
|
-
description: Atomic commits, PR size limits, commit thresholds
|
|
2558
|
-
globs:
|
|
2559
|
-
- "**/*"
|
|
2560
|
-
---
|
|
2561
|
-
|
|
2562
|
-
# Commit Hygiene
|
|
2563
|
-
|
|
2564
|
-
Keep commits atomic, PRs reviewable, and git history clean.
|
|
2565
|
-
|
|
2566
|
-
## Size Thresholds
|
|
2567
|
-
|
|
2568
|
-
| Metric | \u{1F7E2} Good | \u{1F7E1} Warning | \u{1F534} Commit Now |
|
|
2569
|
-
|--------|---------|------------|---------------|
|
|
2570
|
-
| Files changed | 1-5 | 6-10 | > 10 |
|
|
2571
|
-
| Lines added | < 150 | 150-300 | > 300 |
|
|
2572
|
-
| Total changes | < 250 | 250-400 | > 400 |
|
|
2573
|
-
|
|
2574
|
-
**Research shows:** PRs > 400 lines have 40%+ defect rates vs 15% for smaller changes.
|
|
2575
|
-
|
|
2576
|
-
## When to Commit
|
|
2577
|
-
|
|
2578
|
-
### Commit Triggers (Any = Commit)
|
|
2579
|
-
|
|
2580
|
-
| Trigger | Action |
|
|
2581
|
-
|---------|--------|
|
|
2582
|
-
| Test passes | Just got a test green \u2192 commit |
|
|
2583
|
-
| Feature complete | Finished a function \u2192 commit |
|
|
2584
|
-
| Refactor done | Renamed across files \u2192 commit |
|
|
2585
|
-
| Bug fixed | Fixed the issue \u2192 commit |
|
|
2586
|
-
| Threshold hit | > 5 files or > 200 lines \u2192 commit |
|
|
2587
|
-
|
|
2588
|
-
### Commit Immediately If
|
|
2589
|
-
|
|
2590
|
-
- \u2705 Tests are passing after being red
|
|
2591
|
-
- \u2705 You're about to make a "big change"
|
|
2592
|
-
- \u2705 You've been coding for 30+ minutes
|
|
2593
|
-
- \u2705 You're about to try something risky
|
|
2594
|
-
- \u2705 The current state is "working"
|
|
2595
|
-
|
|
2596
|
-
## Atomic Commit Patterns
|
|
2597
|
-
|
|
2598
|
-
### Good Commits \u2705
|
|
2599
|
-
|
|
2600
|
-
\`\`\`
|
|
2601
|
-
"Add email validation to signup form"
|
|
2602
|
-
- 3 files: validator.ts, signup.tsx, signup.test.ts
|
|
2603
|
-
- 120 lines changed
|
|
2604
|
-
- Single purpose: email validation
|
|
2605
|
-
|
|
2606
|
-
"Fix null pointer in user lookup"
|
|
2607
|
-
- 2 files: userService.ts, userService.test.ts
|
|
2608
|
-
- 25 lines changed
|
|
2609
|
-
- Single purpose: fix one bug
|
|
2610
|
-
\`\`\`
|
|
2611
|
-
|
|
2612
|
-
### Bad Commits \u274C
|
|
2613
|
-
|
|
2614
|
-
\`\`\`
|
|
2615
|
-
"Add authentication, fix bugs, update styles"
|
|
2616
|
-
- 25 files changed, 800 lines
|
|
2617
|
-
- Multiple unrelated purposes
|
|
2618
|
-
|
|
2619
|
-
"WIP" / "Updates" / "Fix stuff"
|
|
2620
|
-
- Unknown scope, no clear purpose
|
|
2621
|
-
\`\`\`
|
|
2622
|
-
|
|
2623
|
-
## Quick Status Check
|
|
2624
|
-
|
|
2625
|
-
Run frequently to check current state:
|
|
2626
|
-
|
|
2627
|
-
\`\`\`bash
|
|
2628
|
-
# See what's changed
|
|
2629
|
-
git status --short
|
|
2630
|
-
|
|
2631
|
-
# Count changes
|
|
2632
|
-
git diff --shortstat
|
|
2633
|
-
|
|
2634
|
-
# Full summary
|
|
2635
|
-
git diff --stat HEAD
|
|
2636
|
-
\`\`\`
|
|
2637
|
-
|
|
2638
|
-
## PR Size Rules
|
|
2639
|
-
|
|
2640
|
-
| PR Size | Review Time | Quality |
|
|
2641
|
-
|---------|-------------|---------|
|
|
2642
|
-
| < 200 lines | < 30 min | High confidence |
|
|
2643
|
-
| 200-400 lines | 30-60 min | Good confidence |
|
|
2644
|
-
| 400-1000 lines | 1-2 hours | Declining quality |
|
|
2645
|
-
| > 1000 lines | Often skipped | Rubber-stamped |
|
|
2646
|
-
|
|
2647
|
-
**Best practice:** If a PR will be > 400 lines, split into stacked PRs.
|
|
2648
|
-
`,
|
|
2649
|
-
isNew: true
|
|
2650
|
-
};
|
|
2651
|
-
}
|
|
2652
|
-
function generateCodeDeduplicationSkill() {
|
|
2653
|
-
return {
|
|
2654
|
-
type: "skill",
|
|
2655
|
-
path: ".claude/skills/code-deduplication.md",
|
|
2656
|
-
content: `---
|
|
2657
|
-
name: code-deduplication
|
|
2658
|
-
description: Prevent semantic code duplication with capability index
|
|
2659
|
-
globs:
|
|
2660
|
-
- "**/*.ts"
|
|
2661
|
-
- "**/*.tsx"
|
|
2662
|
-
- "**/*.js"
|
|
2663
|
-
- "**/*.py"
|
|
2664
|
-
---
|
|
2665
|
-
|
|
2666
|
-
# Code Deduplication
|
|
2667
|
-
|
|
2668
|
-
Prevent semantic duplication by maintaining awareness of existing capabilities.
|
|
2669
|
-
|
|
2670
|
-
## Core Principle
|
|
2671
|
-
|
|
2672
|
-
\`\`\`
|
|
2673
|
-
\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
|
|
2674
|
-
\u2502 CHECK BEFORE YOU WRITE \u2502
|
|
2675
|
-
\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
|
|
2676
|
-
\u2502 AI doesn't copy/paste - it reimplements. \u2502
|
|
2677
|
-
\u2502 The problem isn't duplicate code, it's duplicate PURPOSE. \u2502
|
|
2678
|
-
\u2502 \u2502
|
|
2679
|
-
\u2502 Before writing ANY new function: \u2502
|
|
2680
|
-
\u2502 1. Search codebase for similar functionality \u2502
|
|
2681
|
-
\u2502 2. Check utils/, helpers/, lib/ for existing implementations \u2502
|
|
2682
|
-
\u2502 3. Extend existing code if possible \u2502
|
|
2683
|
-
\u2502 4. Only create new if nothing suitable exists \u2502
|
|
2684
|
-
\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
|
|
2685
|
-
\`\`\`
|
|
2686
|
-
|
|
2687
|
-
## Before Writing New Code
|
|
2688
|
-
|
|
2689
|
-
### Search Checklist
|
|
2690
|
-
|
|
2691
|
-
1. **Search by purpose**: "format date", "validate email", "fetch user"
|
|
2692
|
-
2. **Search common locations**:
|
|
2693
|
-
- \`src/utils/\` or \`lib/\`
|
|
2694
|
-
- \`src/helpers/\`
|
|
2695
|
-
- \`src/common/\`
|
|
2696
|
-
- \`src/shared/\`
|
|
2697
|
-
3. **Search by function signature**: Similar inputs/outputs
|
|
2698
|
-
|
|
2699
|
-
### Common Duplicate Candidates
|
|
2700
|
-
|
|
2701
|
-
| Category | Look For |
|
|
2702
|
-
|----------|----------|
|
|
2703
|
-
| Date/Time | formatDate, parseDate, isExpired, addDays |
|
|
2704
|
-
| Validation | isEmail, isPhone, isURL, isUUID |
|
|
2705
|
-
| Strings | slugify, truncate, capitalize, pluralize |
|
|
2706
|
-
| API | fetchUser, createItem, handleError |
|
|
2707
|
-
| Auth | validateToken, requireAuth, getCurrentUser |
|
|
2708
|
-
|
|
2709
|
-
## If Similar Code Exists
|
|
2710
|
-
|
|
2711
|
-
### Option 1: Reuse directly
|
|
2712
|
-
\`\`\`typescript
|
|
2713
|
-
// Import and use existing function
|
|
2714
|
-
import { formatDate } from '@/utils/dates';
|
|
2715
|
-
\`\`\`
|
|
2716
|
-
|
|
2717
|
-
### Option 2: Extend with options
|
|
2718
|
-
\`\`\`typescript
|
|
2719
|
-
// Add optional parameter to existing function
|
|
2720
|
-
export function formatDate(
|
|
2721
|
-
date: Date,
|
|
2722
|
-
format: string = 'short',
|
|
2723
|
-
locale?: string // NEW: added locale support
|
|
2724
|
-
): string { ... }
|
|
2725
|
-
\`\`\`
|
|
2726
|
-
|
|
2727
|
-
### Option 3: Compose from existing
|
|
2728
|
-
\`\`\`typescript
|
|
2729
|
-
// Build on existing utilities
|
|
2730
|
-
export function formatDateRange(start: Date, end: Date) {
|
|
2731
|
-
return \`\${formatDate(start)} - \${formatDate(end)}\`;
|
|
2732
|
-
}
|
|
2733
|
-
\`\`\`
|
|
2734
|
-
|
|
2735
|
-
## File Header Pattern
|
|
2736
|
-
|
|
2737
|
-
Document what each file provides:
|
|
2738
|
-
|
|
2739
|
-
\`\`\`typescript
|
|
2740
|
-
/**
|
|
2741
|
-
* @file User validation utilities
|
|
2742
|
-
* @description Email, phone, and identity validation functions.
|
|
2743
|
-
*
|
|
2744
|
-
* Key exports:
|
|
2745
|
-
* - isEmail(email) - Validates email format
|
|
2746
|
-
* - isPhone(phone, country?) - Validates phone with country
|
|
2747
|
-
* - isValidUsername(username) - Checks username rules
|
|
2748
|
-
*/
|
|
2749
|
-
\`\`\`
|
|
2750
|
-
|
|
2751
|
-
## Anti-Patterns
|
|
2752
|
-
|
|
2753
|
-
- \u274C Writing date formatter without checking utils/
|
|
2754
|
-
- \u274C Creating new API client when one exists
|
|
2755
|
-
- \u274C Duplicating validation logic across files
|
|
2756
|
-
- \u274C Copy-pasting functions between files
|
|
2757
|
-
- \u274C "I'll refactor later" (you won't)
|
|
2758
|
-
`,
|
|
2759
|
-
isNew: true
|
|
2760
|
-
};
|
|
2761
|
-
}
|
|
2762
|
-
function generateSimplicityRulesSkill() {
|
|
2763
|
-
return {
|
|
2764
|
-
type: "skill",
|
|
2765
|
-
path: ".claude/skills/simplicity-rules.md",
|
|
2766
|
-
content: `---
|
|
2767
|
-
name: simplicity-rules
|
|
2768
|
-
description: Enforced code complexity constraints
|
|
2769
|
-
globs:
|
|
2770
|
-
- "**/*.ts"
|
|
2771
|
-
- "**/*.tsx"
|
|
2772
|
-
- "**/*.js"
|
|
2773
|
-
- "**/*.py"
|
|
2774
|
-
- "**/*.go"
|
|
2775
|
-
---
|
|
2776
|
-
|
|
2777
|
-
# Simplicity Rules
|
|
2778
|
-
|
|
2779
|
-
Complexity is the enemy. Every line of code is a liability.
|
|
2780
|
-
|
|
2781
|
-
## Enforced Limits
|
|
2782
|
-
|
|
2783
|
-
**CRITICAL: These limits are non-negotiable. Check and enforce for EVERY file.**
|
|
2784
|
-
|
|
2785
|
-
### Function Level
|
|
2786
|
-
|
|
2787
|
-
| Constraint | Limit | Action if Exceeded |
|
|
2788
|
-
|------------|-------|-------------------|
|
|
2789
|
-
| Lines per function | 20 max | Decompose immediately |
|
|
2790
|
-
| Parameters | 3 max | Use options object |
|
|
2791
|
-
| Nesting levels | 2 max | Flatten with early returns |
|
|
2792
|
-
|
|
2793
|
-
### File Level
|
|
2794
|
-
|
|
2795
|
-
| Constraint | Limit | Action if Exceeded |
|
|
2796
|
-
|------------|-------|-------------------|
|
|
2797
|
-
| Lines per file | 200 max | Split by responsibility |
|
|
2798
|
-
| Functions per file | 10 max | Split into modules |
|
|
2799
|
-
|
|
2800
|
-
### Module Level
|
|
2801
|
-
|
|
2802
|
-
| Constraint | Limit | Reason |
|
|
2803
|
-
|------------|-------|--------|
|
|
2804
|
-
| Directory nesting | 3 levels max | Flat is better |
|
|
2805
|
-
| Circular deps | 0 | Never acceptable |
|
|
2806
|
-
|
|
2807
|
-
## Enforcement Protocol
|
|
2808
|
-
|
|
2809
|
-
**Before completing ANY file:**
|
|
2810
|
-
|
|
2811
|
-
\`\`\`
|
|
2812
|
-
1. Count total lines \u2192 if > 200, STOP and split
|
|
2813
|
-
2. Count functions \u2192 if > 10, STOP and split
|
|
2814
|
-
3. Check function length \u2192 if any > 20 lines, decompose
|
|
2815
|
-
4. Check parameters \u2192 if any > 3, refactor to options object
|
|
2816
|
-
\`\`\`
|
|
2817
|
-
|
|
2818
|
-
## Violation Response
|
|
2819
|
-
|
|
2820
|
-
When limits are exceeded:
|
|
2821
|
-
|
|
2822
|
-
\`\`\`
|
|
2823
|
-
\u26A0\uFE0F FILE SIZE VIOLATION DETECTED
|
|
2824
|
-
|
|
2825
|
-
[filename] has [X] lines (limit: 200)
|
|
2826
|
-
|
|
2827
|
-
Splitting into:
|
|
2828
|
-
- [filename-a].ts - [responsibility A]
|
|
2829
|
-
- [filename-b].ts - [responsibility B]
|
|
2830
|
-
\`\`\`
|
|
2831
|
-
|
|
2832
|
-
**Never defer refactoring.** Fix violations immediately.
|
|
2833
|
-
|
|
2834
|
-
## Decomposition Patterns
|
|
2835
|
-
|
|
2836
|
-
### Long Function \u2192 Multiple Functions
|
|
2837
|
-
|
|
2838
|
-
\`\`\`typescript
|
|
2839
|
-
// BEFORE: 40 lines
|
|
2840
|
-
function processOrder(order) {
|
|
2841
|
-
// validate... 10 lines
|
|
2842
|
-
// calculate totals... 15 lines
|
|
2843
|
-
// apply discounts... 10 lines
|
|
2844
|
-
// save... 5 lines
|
|
2845
|
-
}
|
|
2846
|
-
|
|
2847
|
-
// AFTER: 4 functions, each < 15 lines
|
|
2848
|
-
function processOrder(order) {
|
|
2849
|
-
validateOrder(order);
|
|
2850
|
-
const totals = calculateTotals(order);
|
|
2851
|
-
const finalPrice = applyDiscounts(totals, order.coupons);
|
|
2852
|
-
return saveOrder({ ...order, finalPrice });
|
|
2853
|
-
}
|
|
2854
|
-
\`\`\`
|
|
2855
|
-
|
|
2856
|
-
### Many Parameters \u2192 Options Object
|
|
2857
|
-
|
|
2858
|
-
\`\`\`typescript
|
|
2859
|
-
// BEFORE: 6 parameters
|
|
2860
|
-
function createUser(name, email, password, role, team, settings) { }
|
|
2861
|
-
|
|
2862
|
-
// AFTER: 1 options object
|
|
2863
|
-
interface CreateUserOptions {
|
|
2864
|
-
name: string;
|
|
2865
|
-
email: string;
|
|
2866
|
-
password: string;
|
|
2867
|
-
role?: string;
|
|
2868
|
-
team?: string;
|
|
2869
|
-
settings?: UserSettings;
|
|
2870
|
-
}
|
|
2871
|
-
function createUser(options: CreateUserOptions) { }
|
|
2872
|
-
\`\`\`
|
|
2873
|
-
|
|
2874
|
-
### Deep Nesting \u2192 Early Returns
|
|
2875
|
-
|
|
2876
|
-
\`\`\`typescript
|
|
2877
|
-
// BEFORE: 4 levels deep
|
|
2878
|
-
function process(data) {
|
|
2879
|
-
if (data) {
|
|
2880
|
-
if (data.valid) {
|
|
2881
|
-
if (data.items) {
|
|
2882
|
-
for (const item of data.items) {
|
|
2883
|
-
// actual logic here
|
|
2884
|
-
}
|
|
2885
|
-
}
|
|
2886
|
-
}
|
|
2887
|
-
}
|
|
2888
|
-
}
|
|
2889
|
-
|
|
2890
|
-
// AFTER: 1 level deep
|
|
2891
|
-
function process(data) {
|
|
2892
|
-
if (!data?.valid || !data.items) return;
|
|
2893
|
-
|
|
2894
|
-
for (const item of data.items) {
|
|
2895
|
-
// actual logic here
|
|
2896
|
-
}
|
|
2897
|
-
}
|
|
2898
|
-
\`\`\`
|
|
2899
|
-
|
|
2900
|
-
## Anti-Patterns
|
|
2901
|
-
|
|
2902
|
-
- \u274C God objects/files (do everything)
|
|
2903
|
-
- \u274C "Just one more line" (compound violations)
|
|
2904
|
-
- \u274C "I'll split it later" (you won't)
|
|
2905
|
-
- \u274C Deep inheritance hierarchies
|
|
2906
|
-
- \u274C Complex conditionals without extraction
|
|
2907
|
-
`,
|
|
2908
|
-
isNew: true
|
|
2909
|
-
};
|
|
2910
|
-
}
|
|
2911
|
-
function generateSecuritySkill(stack) {
|
|
2912
|
-
const isJS = stack.languages.includes("typescript") || stack.languages.includes("javascript");
|
|
2913
|
-
const isPython = stack.languages.includes("python");
|
|
2914
|
-
return {
|
|
2915
|
-
type: "skill",
|
|
2916
|
-
path: ".claude/skills/security.md",
|
|
2917
|
-
content: `---
|
|
2918
|
-
name: security
|
|
2919
|
-
description: Security best practices, secrets management, OWASP patterns
|
|
2920
|
-
globs:
|
|
2921
|
-
- "**/*"
|
|
2922
|
-
---
|
|
2923
|
-
|
|
2924
|
-
# Security Best Practices
|
|
2925
|
-
|
|
2926
|
-
Security is not optional. Every project must pass security checks.
|
|
2927
|
-
|
|
2928
|
-
## Required .gitignore Entries
|
|
2929
|
-
|
|
2930
|
-
**NEVER commit these:**
|
|
2931
|
-
|
|
2932
|
-
\`\`\`gitignore
|
|
2933
|
-
# Environment files
|
|
2934
|
-
.env
|
|
2935
|
-
.env.*
|
|
2936
|
-
!.env.example
|
|
2937
|
-
|
|
2938
|
-
# Secrets and credentials
|
|
2939
|
-
*.pem
|
|
2940
|
-
*.key
|
|
2941
|
-
*.p12
|
|
2942
|
-
credentials.json
|
|
2943
|
-
secrets.json
|
|
2944
|
-
*-credentials.json
|
|
2945
|
-
service-account*.json
|
|
2946
|
-
|
|
2947
|
-
# IDE secrets
|
|
2948
|
-
.idea/
|
|
2949
|
-
.vscode/settings.json
|
|
2950
|
-
\`\`\`
|
|
2951
|
-
|
|
2952
|
-
## Environment Variables
|
|
2953
|
-
|
|
2954
|
-
### Create .env.example
|
|
2955
|
-
|
|
2956
|
-
Document required vars without values:
|
|
2957
|
-
|
|
2958
|
-
\`\`\`bash
|
|
2959
|
-
# Server-side only (never expose to client)
|
|
2960
|
-
DATABASE_URL=
|
|
2961
|
-
API_SECRET_KEY=
|
|
2962
|
-
ANTHROPIC_API_KEY=
|
|
2963
|
-
|
|
2964
|
-
# Client-side safe (public, non-sensitive)
|
|
2965
|
-
${isJS ? "VITE_API_URL=\nNEXT_PUBLIC_SITE_URL=" : "API_BASE_URL="}
|
|
2966
|
-
\`\`\`
|
|
2967
|
-
|
|
2968
|
-
${isJS ? `### Frontend Exposure Rules
|
|
2969
|
-
|
|
2970
|
-
| Framework | Client-Exposed Prefix | Server-Only |
|
|
2971
|
-
|-----------|----------------------|-------------|
|
|
2972
|
-
| Vite | \`VITE_*\` | No prefix |
|
|
2973
|
-
| Next.js | \`NEXT_PUBLIC_*\` | No prefix |
|
|
2974
|
-
| CRA | \`REACT_APP_*\` | N/A |
|
|
2975
|
-
|
|
2976
|
-
**CRITICAL:** Never put secrets in client-exposed env vars!
|
|
2977
|
-
|
|
2978
|
-
\`\`\`typescript
|
|
2979
|
-
// \u274C WRONG - Secret exposed to browser
|
|
2980
|
-
const key = import.meta.env.VITE_API_SECRET;
|
|
2981
|
-
|
|
2982
|
-
// \u2705 CORRECT - Secret stays server-side
|
|
2983
|
-
const key = process.env.API_SECRET; // in API route only
|
|
2984
|
-
\`\`\`
|
|
2985
|
-
` : ""}
|
|
2986
|
-
|
|
2987
|
-
### Validate at Startup
|
|
2988
|
-
|
|
2989
|
-
${isJS ? `\`\`\`typescript
|
|
2990
|
-
import { z } from 'zod';
|
|
2991
|
-
|
|
2992
|
-
const envSchema = z.object({
|
|
2993
|
-
DATABASE_URL: z.string().url(),
|
|
2994
|
-
API_SECRET_KEY: z.string().min(32),
|
|
2995
|
-
NODE_ENV: z.enum(['development', 'production', 'test']),
|
|
2996
|
-
});
|
|
2997
|
-
|
|
2998
|
-
export const env = envSchema.parse(process.env);
|
|
2999
|
-
\`\`\`` : ""}
|
|
3000
|
-
${isPython ? `\`\`\`python
|
|
3001
|
-
from pydantic_settings import BaseSettings
|
|
3002
|
-
|
|
3003
|
-
class Settings(BaseSettings):
|
|
3004
|
-
database_url: str
|
|
3005
|
-
api_secret_key: str
|
|
3006
|
-
environment: str = "development"
|
|
3007
|
-
|
|
3008
|
-
class Config:
|
|
3009
|
-
env_file = ".env"
|
|
3010
|
-
|
|
3011
|
-
settings = Settings()
|
|
3012
|
-
\`\`\`` : ""}
|
|
3013
|
-
|
|
3014
|
-
## OWASP Top 10 Checklist
|
|
3015
|
-
|
|
3016
|
-
| Vulnerability | Prevention |
|
|
3017
|
-
|---------------|------------|
|
|
3018
|
-
| Injection (SQL, NoSQL, Command) | Parameterized queries, input validation |
|
|
3019
|
-
| Broken Auth | Secure session management, MFA |
|
|
3020
|
-
| Sensitive Data Exposure | Encryption at rest and in transit |
|
|
3021
|
-
| XXE | Disable external entity processing |
|
|
3022
|
-
| Broken Access Control | Verify permissions on every request |
|
|
3023
|
-
| Security Misconfiguration | Secure defaults, minimal permissions |
|
|
3024
|
-
| XSS | Output encoding, CSP headers |
|
|
3025
|
-
| Insecure Deserialization | Validate all serialized data |
|
|
3026
|
-
| Using Vulnerable Components | Keep dependencies updated |
|
|
3027
|
-
| Insufficient Logging | Log security events, monitor |
|
|
3028
|
-
|
|
3029
|
-
## Input Validation
|
|
3030
|
-
|
|
3031
|
-
\`\`\`
|
|
3032
|
-
RULE: Never trust user input. Validate everything.
|
|
3033
|
-
|
|
3034
|
-
- Validate type, length, format, range
|
|
3035
|
-
- Sanitize before storage
|
|
3036
|
-
- Encode before output
|
|
3037
|
-
- Use allowlists over denylists
|
|
3038
|
-
\`\`\`
|
|
3039
|
-
|
|
3040
|
-
## Secrets Detection
|
|
3041
|
-
|
|
3042
|
-
Before committing, check for:
|
|
3043
|
-
|
|
3044
|
-
- API keys (usually 32+ chars, specific patterns)
|
|
3045
|
-
- Passwords in code
|
|
3046
|
-
- Connection strings with credentials
|
|
3047
|
-
- Private keys (BEGIN RSA/EC/PRIVATE KEY)
|
|
3048
|
-
- Tokens (jwt, bearer, oauth)
|
|
3049
|
-
|
|
3050
|
-
## Security Review Checklist
|
|
3051
|
-
|
|
3052
|
-
Before PR merge:
|
|
3053
|
-
|
|
3054
|
-
- [ ] No secrets in code or config
|
|
3055
|
-
- [ ] Input validation on all user data
|
|
3056
|
-
- [ ] Output encoding where displayed
|
|
3057
|
-
- [ ] Authentication checked on protected routes
|
|
3058
|
-
- [ ] Authorization verified for resources
|
|
3059
|
-
- [ ] Dependencies scanned for vulnerabilities
|
|
3060
|
-
- [ ] Error messages don't leak internals
|
|
3061
|
-
`,
|
|
3062
|
-
isNew: true
|
|
3063
|
-
};
|
|
3064
|
-
}
|
|
3065
|
-
function generateAgents(stack) {
|
|
3066
|
-
const artifacts = [];
|
|
3067
|
-
artifacts.push(generateCodeReviewerAgent(stack));
|
|
3068
|
-
artifacts.push(generateTestWriterAgent(stack));
|
|
3069
|
-
return artifacts;
|
|
3070
|
-
}
|
|
3071
|
-
function generateCodeReviewerAgent(stack) {
|
|
3072
|
-
const lintCommand = getLintCommand(stack);
|
|
3073
|
-
return {
|
|
3074
|
-
type: "agent",
|
|
3075
|
-
path: ".claude/agents/code-reviewer.md",
|
|
3076
|
-
content: `---
|
|
3077
|
-
name: code-reviewer
|
|
3078
|
-
description: Reviews code for quality, security issues, and best practices
|
|
3079
|
-
tools: Read, Grep, Glob${lintCommand ? `, Bash(${lintCommand})` : ""}
|
|
3080
|
-
disallowedTools: Write, Edit
|
|
3081
|
-
model: sonnet
|
|
3082
|
-
---
|
|
3083
|
-
|
|
3084
|
-
You are a senior code reviewer with expertise in security and performance.
|
|
3085
|
-
|
|
3086
|
-
## Code Style Reference
|
|
3087
|
-
|
|
3088
|
-
Read these files to understand project conventions:
|
|
3089
|
-
${stack.linter === "eslint" ? "- `eslint.config.js` or `.eslintrc.*`" : ""}
|
|
3090
|
-
${stack.formatter === "prettier" ? "- `.prettierrc`" : ""}
|
|
3091
|
-
${stack.languages.includes("typescript") ? "- `tsconfig.json`" : ""}
|
|
3092
|
-
${stack.languages.includes("python") ? "- `pyproject.toml` or `setup.cfg`" : ""}
|
|
3093
|
-
|
|
3094
|
-
${lintCommand ? `Run \`${lintCommand}\` to check violations programmatically.` : ""}
|
|
3095
|
-
|
|
3096
|
-
## Review Process
|
|
3097
|
-
|
|
3098
|
-
1. Run \`git diff\` to identify changed files
|
|
3099
|
-
2. Analyze each change for:
|
|
3100
|
-
- Security vulnerabilities (OWASP Top 10)
|
|
3101
|
-
- Performance issues
|
|
3102
|
-
- Code style violations
|
|
3103
|
-
- Missing error handling
|
|
3104
|
-
- Test coverage gaps
|
|
3105
|
-
|
|
3106
|
-
## Output Format
|
|
3107
|
-
|
|
3108
|
-
For each finding:
|
|
3109
|
-
|
|
3110
|
-
- **Critical**: Must fix before merge
|
|
3111
|
-
- **Warning**: Should address
|
|
3112
|
-
- **Suggestion**: Consider improving
|
|
3113
|
-
|
|
3114
|
-
Include file:line references for each issue.
|
|
3115
|
-
`,
|
|
3116
|
-
isNew: true
|
|
3117
|
-
};
|
|
3118
|
-
}
|
|
3119
|
-
function generateTestWriterAgent(stack) {
|
|
3120
|
-
const testCommand = getTestCommand(stack);
|
|
3121
|
-
return {
|
|
3122
|
-
type: "agent",
|
|
3123
|
-
path: ".claude/agents/test-writer.md",
|
|
3124
|
-
content: `---
|
|
3125
|
-
name: test-writer
|
|
3126
|
-
description: Generates comprehensive tests for code
|
|
3127
|
-
tools: Read, Grep, Glob, Write, Edit, Bash(${testCommand})
|
|
3128
|
-
model: sonnet
|
|
3129
|
-
---
|
|
3130
|
-
|
|
3131
|
-
You are a testing expert who writes thorough, maintainable tests.
|
|
3132
|
-
|
|
3133
|
-
## Testing Framework
|
|
3134
|
-
|
|
3135
|
-
This project uses: **${stack.testingFramework || "unknown"}**
|
|
3136
|
-
|
|
3137
|
-
## Your Process
|
|
3138
|
-
|
|
3139
|
-
1. Read the code to be tested
|
|
3140
|
-
2. Identify test cases:
|
|
3141
|
-
- Happy path scenarios
|
|
3142
|
-
- Edge cases
|
|
3143
|
-
- Error conditions
|
|
3144
|
-
- Boundary values
|
|
3145
|
-
3. Write tests following project patterns
|
|
3146
|
-
4. Run tests to verify they pass
|
|
3147
|
-
|
|
3148
|
-
## Test Structure
|
|
3149
|
-
|
|
3150
|
-
Follow the AAA pattern:
|
|
3151
|
-
- **Arrange**: Set up test data
|
|
3152
|
-
- **Act**: Execute the code
|
|
3153
|
-
- **Assert**: Verify results
|
|
3154
|
-
|
|
3155
|
-
## Guidelines
|
|
3156
|
-
|
|
3157
|
-
- One assertion focus per test
|
|
3158
|
-
- Descriptive test names
|
|
3159
|
-
- Mock external dependencies
|
|
3160
|
-
- Don't test implementation details
|
|
3161
|
-
- Aim for behavior coverage
|
|
3162
|
-
|
|
3163
|
-
## Run Tests
|
|
3164
|
-
|
|
3165
|
-
\`\`\`bash
|
|
3166
|
-
${testCommand}
|
|
3167
|
-
\`\`\`
|
|
3168
|
-
`,
|
|
3169
|
-
isNew: true
|
|
3170
|
-
};
|
|
3171
|
-
}
|
|
3172
|
-
function getLintCommand(stack) {
|
|
3173
|
-
switch (stack.linter) {
|
|
3174
|
-
case "eslint":
|
|
3175
|
-
return `${stack.packageManager === "bun" ? "bun" : "npx"} eslint .`;
|
|
3176
|
-
case "biome":
|
|
3177
|
-
return `${stack.packageManager === "bun" ? "bun" : "npx"} biome check .`;
|
|
3178
|
-
case "ruff":
|
|
3179
|
-
return "ruff check .";
|
|
3180
|
-
default:
|
|
3181
|
-
return "";
|
|
3182
|
-
}
|
|
3183
|
-
}
|
|
3184
|
-
function getTestCommand(stack) {
|
|
3185
|
-
switch (stack.testingFramework) {
|
|
3186
|
-
case "vitest":
|
|
3187
|
-
return `${stack.packageManager || "npm"} ${stack.packageManager === "npm" ? "run " : ""}test`;
|
|
3188
|
-
case "jest":
|
|
3189
|
-
return `${stack.packageManager || "npm"} ${stack.packageManager === "npm" ? "run " : ""}test`;
|
|
3190
|
-
case "bun-test":
|
|
3191
|
-
return "bun test";
|
|
3192
|
-
case "pytest":
|
|
3193
|
-
return "pytest";
|
|
3194
|
-
case "go-test":
|
|
3195
|
-
return "go test ./...";
|
|
3196
|
-
case "rust-test":
|
|
3197
|
-
return "cargo test";
|
|
3198
|
-
default:
|
|
3199
|
-
return `${stack.packageManager || "npm"} test`;
|
|
3200
|
-
}
|
|
3201
|
-
}
|
|
3202
|
-
function generateRules(stack) {
|
|
3203
|
-
const artifacts = [];
|
|
3204
|
-
if (stack.languages.includes("typescript")) {
|
|
3205
|
-
artifacts.push(generateTypeScriptRules());
|
|
3206
|
-
}
|
|
3207
|
-
if (stack.languages.includes("python")) {
|
|
3208
|
-
artifacts.push(generatePythonRules());
|
|
3209
|
-
}
|
|
3210
|
-
artifacts.push(generateCodeStyleRule(stack));
|
|
3211
|
-
return artifacts;
|
|
3212
|
-
}
|
|
3213
|
-
function generateTypeScriptRules() {
|
|
3214
|
-
return {
|
|
3215
|
-
type: "rule",
|
|
3216
|
-
path: ".claude/rules/typescript.md",
|
|
3217
|
-
content: `---
|
|
3218
|
-
paths:
|
|
3219
|
-
- "**/*.ts"
|
|
3220
|
-
- "**/*.tsx"
|
|
3221
|
-
---
|
|
3222
|
-
|
|
3223
|
-
# TypeScript Rules
|
|
3224
|
-
|
|
3225
|
-
## Type Safety
|
|
3226
|
-
|
|
3227
|
-
- Avoid \`any\` - use \`unknown\` and narrow types
|
|
3228
|
-
- Prefer interfaces for objects, types for unions/intersections
|
|
3229
|
-
- Use strict mode (\`strict: true\` in tsconfig)
|
|
3230
|
-
- Enable \`noUncheckedIndexedAccess\` for safer array access
|
|
3231
|
-
|
|
3232
|
-
## Patterns
|
|
3233
|
-
|
|
3234
|
-
\`\`\`typescript
|
|
3235
|
-
// Prefer
|
|
3236
|
-
const user: User | undefined = users.find(u => u.id === id);
|
|
3237
|
-
if (user) { /* use user */ }
|
|
3238
|
-
|
|
3239
|
-
// Avoid
|
|
3240
|
-
const user = users.find(u => u.id === id) as User;
|
|
3241
|
-
\`\`\`
|
|
3242
|
-
|
|
3243
|
-
## Naming
|
|
3244
|
-
|
|
3245
|
-
- Interfaces: PascalCase (e.g., \`UserProfile\`)
|
|
3246
|
-
- Types: PascalCase (e.g., \`ApiResponse\`)
|
|
3247
|
-
- Functions: camelCase (e.g., \`getUserById\`)
|
|
3248
|
-
- Constants: SCREAMING_SNAKE_CASE for true constants
|
|
3249
|
-
|
|
3250
|
-
## Imports
|
|
3251
|
-
|
|
3252
|
-
- Group imports: external, internal, relative
|
|
3253
|
-
- Use path aliases when configured
|
|
3254
|
-
- Prefer named exports over default exports
|
|
3255
|
-
`,
|
|
3256
|
-
isNew: true
|
|
3257
|
-
};
|
|
3258
|
-
}
|
|
3259
|
-
function generatePythonRules() {
|
|
3260
|
-
return {
|
|
3261
|
-
type: "rule",
|
|
3262
|
-
path: ".claude/rules/python.md",
|
|
3263
|
-
content: `---
|
|
3264
|
-
paths:
|
|
3265
|
-
- "**/*.py"
|
|
3266
|
-
---
|
|
3267
|
-
|
|
3268
|
-
# Python Rules
|
|
3269
|
-
|
|
3270
|
-
## Style
|
|
3271
|
-
|
|
3272
|
-
- Follow PEP 8
|
|
3273
|
-
- Use type hints for function signatures
|
|
3274
|
-
- Docstrings for public functions (Google style)
|
|
3275
|
-
- Max line length: 88 (Black default)
|
|
3276
|
-
|
|
3277
|
-
## Patterns
|
|
3278
|
-
|
|
3279
|
-
\`\`\`python
|
|
3280
|
-
# Prefer
|
|
3281
|
-
def get_user(user_id: int) -> User | None:
|
|
3282
|
-
"""Fetch user by ID.
|
|
3283
|
-
|
|
3284
|
-
Args:
|
|
3285
|
-
user_id: The user's unique identifier.
|
|
3286
|
-
|
|
3287
|
-
Returns:
|
|
3288
|
-
User object if found, None otherwise.
|
|
3289
|
-
"""
|
|
3290
|
-
return db.query(User).filter(User.id == user_id).first()
|
|
3291
|
-
|
|
3292
|
-
# Avoid
|
|
3293
|
-
def get_user(id):
|
|
3294
|
-
return db.query(User).filter(User.id == id).first()
|
|
3295
|
-
\`\`\`
|
|
3296
|
-
|
|
3297
|
-
## Naming
|
|
3298
|
-
|
|
3299
|
-
- Functions/variables: snake_case
|
|
3300
|
-
- Classes: PascalCase
|
|
3301
|
-
- Constants: SCREAMING_SNAKE_CASE
|
|
3302
|
-
- Private: _leading_underscore
|
|
3303
|
-
|
|
3304
|
-
## Imports
|
|
3305
|
-
|
|
3306
|
-
\`\`\`python
|
|
3307
|
-
# Standard library
|
|
3308
|
-
import os
|
|
3309
|
-
from pathlib import Path
|
|
3310
|
-
|
|
3311
|
-
# Third-party
|
|
3312
|
-
from fastapi import FastAPI
|
|
3313
|
-
from pydantic import BaseModel
|
|
3314
|
-
|
|
3315
|
-
# Local
|
|
3316
|
-
from app.models import User
|
|
3317
|
-
from app.services import UserService
|
|
3318
|
-
\`\`\`
|
|
3319
|
-
`,
|
|
3320
|
-
isNew: true
|
|
3321
|
-
};
|
|
3322
|
-
}
|
|
3323
|
-
function generateCodeStyleRule(stack) {
|
|
3324
|
-
return {
|
|
3325
|
-
type: "rule",
|
|
3326
|
-
path: ".claude/rules/code-style.md",
|
|
3327
|
-
content: `# Code Style
|
|
3328
|
-
|
|
3329
|
-
## General Principles
|
|
3330
|
-
|
|
3331
|
-
1. **Clarity over cleverness** - Code is read more than written
|
|
3332
|
-
2. **Consistency** - Match existing patterns in the codebase
|
|
3333
|
-
3. **Simplicity** - Prefer simple solutions over complex ones
|
|
3334
|
-
|
|
3335
|
-
## Formatting
|
|
3336
|
-
|
|
3337
|
-
${stack.formatter ? `This project uses **${stack.formatter}** for formatting. Run it before committing.` : "Format code consistently with the existing codebase."}
|
|
3338
|
-
|
|
3339
|
-
${stack.linter ? `This project uses **${stack.linter}** for linting. Fix all warnings.` : ""}
|
|
3340
|
-
|
|
3341
|
-
## Comments
|
|
3342
|
-
|
|
3343
|
-
- Write self-documenting code first
|
|
3344
|
-
- Comment the "why", not the "what"
|
|
3345
|
-
- Keep comments up to date with code changes
|
|
3346
|
-
- Use TODO/FIXME with context
|
|
3347
|
-
|
|
3348
|
-
## Error Handling
|
|
3349
|
-
|
|
3350
|
-
- Handle errors at appropriate boundaries
|
|
3351
|
-
- Provide meaningful error messages
|
|
3352
|
-
- Log errors with context
|
|
3353
|
-
- Don't swallow errors silently
|
|
3354
|
-
|
|
3355
|
-
## Git Commits
|
|
3356
|
-
|
|
3357
|
-
- Write clear, concise commit messages
|
|
3358
|
-
- Use conventional commits format when applicable
|
|
3359
|
-
- Keep commits focused and atomic
|
|
3360
|
-
`,
|
|
3361
|
-
isNew: true
|
|
3362
|
-
};
|
|
3363
|
-
}
|
|
3364
|
-
function generateCommands() {
|
|
3365
|
-
return [
|
|
3366
|
-
generateTaskCommand(),
|
|
3367
|
-
generateStatusCommand(),
|
|
3368
|
-
generateDoneCommand(),
|
|
3369
|
-
generateAnalyzeCommand(),
|
|
3370
|
-
generateCodeReviewCommand()
|
|
3371
|
-
];
|
|
3372
|
-
}
|
|
3373
|
-
function generateTaskCommand() {
|
|
3374
|
-
return {
|
|
3375
|
-
type: "command",
|
|
3376
|
-
path: ".claude/commands/task.md",
|
|
3377
|
-
content: `---
|
|
3378
|
-
allowed-tools: Read, Write, Edit, Glob, Grep
|
|
3379
|
-
argument-hint: [task description]
|
|
3380
|
-
description: Start or switch to a new task
|
|
3381
|
-
---
|
|
3382
|
-
|
|
3383
|
-
# Start Task
|
|
3384
|
-
|
|
3385
|
-
## Current State
|
|
3386
|
-
!cat .claude/state/task.md 2>/dev/null || echo "No existing task"
|
|
3387
|
-
|
|
3388
|
-
## Your Task
|
|
3389
|
-
|
|
3390
|
-
Start or switch to the task: **$ARGUMENTS**
|
|
3391
|
-
|
|
3392
|
-
1. Read current state from \`.claude/state/task.md\`
|
|
3393
|
-
2. If switching tasks, summarize previous progress
|
|
3394
|
-
3. Update \`.claude/state/task.md\` with:
|
|
3395
|
-
- Status: In Progress
|
|
3396
|
-
- Task description
|
|
3397
|
-
- Initial context/understanding
|
|
3398
|
-
- Planned next steps
|
|
3399
|
-
|
|
3400
|
-
4. Begin working on the task
|
|
3401
|
-
`,
|
|
3402
|
-
isNew: true
|
|
3403
|
-
};
|
|
3404
|
-
}
|
|
3405
|
-
function generateStatusCommand() {
|
|
3406
|
-
return {
|
|
3407
|
-
type: "command",
|
|
3408
|
-
path: ".claude/commands/status.md",
|
|
3409
|
-
content: `---
|
|
3410
|
-
allowed-tools: Read, Glob
|
|
3411
|
-
description: Show current task and session state
|
|
3412
|
-
---
|
|
3413
|
-
|
|
3414
|
-
# Status Check
|
|
3415
|
-
|
|
3416
|
-
## Current Task State
|
|
3417
|
-
!cat .claude/state/task.md 2>/dev/null || echo "No task in progress"
|
|
3418
|
-
|
|
3419
|
-
## Your Response
|
|
3420
|
-
|
|
3421
|
-
Provide a concise status update:
|
|
3422
|
-
|
|
3423
|
-
1. **Current Task**: What are you working on?
|
|
3424
|
-
2. **Progress**: What's been completed?
|
|
3425
|
-
3. **Blockers**: Any issues or questions?
|
|
3426
|
-
4. **Next Steps**: What's coming up?
|
|
3427
|
-
|
|
3428
|
-
Keep it brief - this is a quick check-in.
|
|
3429
|
-
`,
|
|
3430
|
-
isNew: true
|
|
3431
|
-
};
|
|
3432
|
-
}
|
|
3433
|
-
function generateDoneCommand() {
|
|
3434
|
-
return {
|
|
3435
|
-
type: "command",
|
|
3436
|
-
path: ".claude/commands/done.md",
|
|
3437
|
-
content: `---
|
|
3438
|
-
allowed-tools: Read, Write, Edit, Glob, Bash(git diff), Bash(git status)
|
|
3439
|
-
description: Mark current task complete
|
|
3440
|
-
---
|
|
3441
|
-
|
|
3442
|
-
# Complete Task
|
|
3443
|
-
|
|
3444
|
-
## Current State
|
|
3445
|
-
!cat .claude/state/task.md
|
|
3446
|
-
|
|
3447
|
-
## Completion Checklist
|
|
3448
|
-
|
|
3449
|
-
Before marking complete, verify:
|
|
3450
|
-
|
|
3451
|
-
1. [ ] All requirements met
|
|
3452
|
-
2. [ ] Tests pass (if applicable)
|
|
3453
|
-
3. [ ] No linting errors
|
|
3454
|
-
4. [ ] Code reviewed for quality
|
|
3455
|
-
|
|
3456
|
-
## Your Task
|
|
3457
|
-
|
|
3458
|
-
1. Run final checks (tests, lint)
|
|
3459
|
-
2. Update \`.claude/state/task.md\`:
|
|
3460
|
-
- Status: **Completed**
|
|
3461
|
-
- Summary of what was done
|
|
3462
|
-
- Files changed
|
|
3463
|
-
- Any follow-up items
|
|
3464
|
-
|
|
3465
|
-
3. Show git status/diff for review
|
|
3466
|
-
`,
|
|
3467
|
-
isNew: true
|
|
550
|
+
const permissions = ["Read(**)", "Edit(**)", "Write(.claude/**)", "Bash(git:*)"];
|
|
551
|
+
const pkgManagers = ["npm", "yarn", "pnpm", "bun", "npx"];
|
|
552
|
+
for (const pm of pkgManagers) {
|
|
553
|
+
permissions.push(`Bash(${pm}:*)`);
|
|
554
|
+
}
|
|
555
|
+
if (stack.languages.includes("typescript") || stack.languages.includes("javascript")) {
|
|
556
|
+
permissions.push("Bash(node:*)", "Bash(tsc:*)");
|
|
557
|
+
}
|
|
558
|
+
if (stack.languages.includes("python")) {
|
|
559
|
+
permissions.push(
|
|
560
|
+
"Bash(python:*)",
|
|
561
|
+
"Bash(pip:*)",
|
|
562
|
+
"Bash(poetry:*)",
|
|
563
|
+
"Bash(pytest:*)",
|
|
564
|
+
"Bash(uvicorn:*)"
|
|
565
|
+
);
|
|
566
|
+
}
|
|
567
|
+
if (stack.languages.includes("go")) {
|
|
568
|
+
permissions.push("Bash(go:*)");
|
|
569
|
+
}
|
|
570
|
+
if (stack.languages.includes("rust")) {
|
|
571
|
+
permissions.push("Bash(cargo:*)", "Bash(rustc:*)");
|
|
572
|
+
}
|
|
573
|
+
if (stack.languages.includes("ruby")) {
|
|
574
|
+
permissions.push("Bash(ruby:*)", "Bash(bundle:*)", "Bash(rails:*)", "Bash(rake:*)");
|
|
575
|
+
}
|
|
576
|
+
if (stack.testingFramework) {
|
|
577
|
+
const testCommands = {
|
|
578
|
+
jest: ["jest:*"],
|
|
579
|
+
vitest: ["vitest:*"],
|
|
580
|
+
playwright: ["playwright:*"],
|
|
581
|
+
cypress: ["cypress:*"],
|
|
582
|
+
pytest: ["pytest:*"],
|
|
583
|
+
rspec: ["rspec:*"]
|
|
584
|
+
};
|
|
585
|
+
const cmds = testCommands[stack.testingFramework];
|
|
586
|
+
if (cmds) {
|
|
587
|
+
permissions.push(...cmds.map((c) => `Bash(${c})`));
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
if (stack.linter) {
|
|
591
|
+
permissions.push(`Bash(${stack.linter}:*)`);
|
|
592
|
+
}
|
|
593
|
+
if (stack.formatter) {
|
|
594
|
+
permissions.push(`Bash(${stack.formatter}:*)`);
|
|
595
|
+
}
|
|
596
|
+
permissions.push(
|
|
597
|
+
"Bash(ls:*)",
|
|
598
|
+
"Bash(mkdir:*)",
|
|
599
|
+
"Bash(cat:*)",
|
|
600
|
+
"Bash(echo:*)",
|
|
601
|
+
"Bash(grep:*)",
|
|
602
|
+
"Bash(find:*)"
|
|
603
|
+
);
|
|
604
|
+
if (stack.hasDocker) {
|
|
605
|
+
permissions.push("Bash(docker:*)", "Bash(docker-compose:*)");
|
|
606
|
+
}
|
|
607
|
+
const settings = {
|
|
608
|
+
$schema: "https://json.schemastore.org/claude-code-settings.json",
|
|
609
|
+
permissions: {
|
|
610
|
+
allow: [...new Set(permissions)]
|
|
611
|
+
// Deduplicate
|
|
612
|
+
}
|
|
3468
613
|
};
|
|
3469
|
-
}
|
|
3470
|
-
function generateAnalyzeCommand() {
|
|
3471
614
|
return {
|
|
3472
|
-
|
|
3473
|
-
|
|
3474
|
-
content: `---
|
|
3475
|
-
allowed-tools: Read, Glob, Grep
|
|
3476
|
-
argument-hint: [area to analyze]
|
|
3477
|
-
description: Deep analysis of a specific area
|
|
3478
|
-
---
|
|
3479
|
-
|
|
3480
|
-
# Analyze: $ARGUMENTS
|
|
3481
|
-
|
|
3482
|
-
## Analysis Scope
|
|
3483
|
-
|
|
3484
|
-
Perform deep analysis of: **$ARGUMENTS**
|
|
3485
|
-
|
|
3486
|
-
## Process
|
|
3487
|
-
|
|
3488
|
-
1. **Locate relevant files** using Glob and Grep
|
|
3489
|
-
2. **Read and understand** the code structure
|
|
3490
|
-
3. **Identify patterns** and conventions
|
|
3491
|
-
4. **Document findings** with file:line references
|
|
3492
|
-
|
|
3493
|
-
## Output Format
|
|
3494
|
-
|
|
3495
|
-
### Overview
|
|
3496
|
-
Brief description of what this area does.
|
|
3497
|
-
|
|
3498
|
-
### Key Files
|
|
3499
|
-
- \`path/to/file.ts:10\` - Purpose
|
|
3500
|
-
|
|
3501
|
-
### Patterns Found
|
|
3502
|
-
- Pattern 1: Description
|
|
3503
|
-
- Pattern 2: Description
|
|
3504
|
-
|
|
3505
|
-
### Dependencies
|
|
3506
|
-
What this area depends on and what depends on it.
|
|
3507
|
-
|
|
3508
|
-
### Recommendations
|
|
3509
|
-
Any improvements or concerns noted.
|
|
3510
|
-
`,
|
|
3511
|
-
isNew: true
|
|
615
|
+
path: ".claude/settings.json",
|
|
616
|
+
content: JSON.stringify(settings, null, 2)
|
|
3512
617
|
};
|
|
3513
618
|
}
|
|
3514
|
-
function
|
|
3515
|
-
|
|
3516
|
-
|
|
3517
|
-
|
|
3518
|
-
|
|
3519
|
-
|
|
3520
|
-
description: Review code changes for quality, security, and best practices
|
|
3521
|
-
---
|
|
3522
|
-
|
|
3523
|
-
# Code Review
|
|
3524
|
-
|
|
3525
|
-
## Changes to Review
|
|
3526
|
-
|
|
3527
|
-
!git diff --stat HEAD~1 2>/dev/null || git diff --stat
|
|
3528
|
-
|
|
3529
|
-
## Review Process
|
|
3530
|
-
|
|
3531
|
-
Analyze all changes for:
|
|
3532
|
-
|
|
3533
|
-
### 1. Security (Critical)
|
|
3534
|
-
- [ ] No secrets/credentials in code
|
|
3535
|
-
- [ ] Input validation present
|
|
3536
|
-
- [ ] Output encoding where needed
|
|
3537
|
-
- [ ] Auth/authz checks on protected routes
|
|
3538
|
-
|
|
3539
|
-
### 2. Quality
|
|
3540
|
-
- [ ] Functions \u2264 20 lines
|
|
3541
|
-
- [ ] Files \u2264 200 lines
|
|
3542
|
-
- [ ] No code duplication
|
|
3543
|
-
- [ ] Clear naming
|
|
3544
|
-
- [ ] Proper error handling
|
|
3545
|
-
|
|
3546
|
-
### 3. Testing
|
|
3547
|
-
- [ ] Tests exist for new code
|
|
3548
|
-
- [ ] Edge cases covered
|
|
3549
|
-
- [ ] Tests are meaningful (not just for coverage)
|
|
3550
|
-
|
|
3551
|
-
### 4. Style
|
|
3552
|
-
- [ ] Matches existing patterns
|
|
3553
|
-
- [ ] Consistent formatting
|
|
3554
|
-
- [ ] No commented-out code
|
|
3555
|
-
|
|
3556
|
-
## Output Format
|
|
3557
|
-
|
|
3558
|
-
For each finding, include file:line reference:
|
|
3559
|
-
|
|
3560
|
-
### Critical (Must Fix)
|
|
3561
|
-
Issues that block merge
|
|
3562
|
-
|
|
3563
|
-
### Warning (Should Fix)
|
|
3564
|
-
Issues that should be addressed
|
|
3565
|
-
|
|
3566
|
-
### Suggestion (Consider)
|
|
3567
|
-
Optional improvements
|
|
3568
|
-
|
|
3569
|
-
## Summary
|
|
3570
|
-
|
|
3571
|
-
Provide:
|
|
3572
|
-
1. Overall assessment (Ready / Changes Needed / Not Ready)
|
|
3573
|
-
2. Count of findings by severity
|
|
3574
|
-
3. Top priorities to address
|
|
3575
|
-
`,
|
|
3576
|
-
isNew: true
|
|
3577
|
-
};
|
|
619
|
+
function writeSettings(rootDir, stack) {
|
|
620
|
+
const { path: settingsPath, content } = generateSettings(stack);
|
|
621
|
+
const fullPath = path2.join(rootDir, settingsPath);
|
|
622
|
+
const dir = path2.dirname(fullPath);
|
|
623
|
+
fs2.mkdirSync(dir, { recursive: true });
|
|
624
|
+
fs2.writeFileSync(fullPath, content);
|
|
3578
625
|
}
|
|
3579
626
|
|
|
3580
627
|
// src/prompt.ts
|
|
3581
628
|
function getAnalysisPrompt(projectInfo) {
|
|
3582
629
|
const context = buildContextSection(projectInfo);
|
|
630
|
+
const templateVars = buildTemplateVariables(projectInfo);
|
|
3583
631
|
return `${ANALYSIS_PROMPT}
|
|
3584
632
|
|
|
633
|
+
${SKILLS_PROMPT}
|
|
634
|
+
|
|
635
|
+
${AGENTS_PROMPT}
|
|
636
|
+
|
|
637
|
+
${RULES_PROMPT}
|
|
638
|
+
|
|
639
|
+
${COMMANDS_PROMPT}
|
|
640
|
+
|
|
3585
641
|
---
|
|
3586
642
|
|
|
3587
643
|
## Pre-detected Context
|
|
@@ -3591,6 +647,10 @@ Use this as a starting point - verify and expand on it during your analysis.
|
|
|
3591
647
|
|
|
3592
648
|
${context}
|
|
3593
649
|
|
|
650
|
+
### Template Variables (use these in generated files)
|
|
651
|
+
|
|
652
|
+
${templateVars}
|
|
653
|
+
|
|
3594
654
|
---
|
|
3595
655
|
|
|
3596
656
|
## Execute Now
|
|
@@ -3600,9 +660,14 @@ ${context}
|
|
|
3600
660
|
3. Execute Phase 2 - generate the CLAUDE.md using only discovered information
|
|
3601
661
|
4. Execute Phase 3 - verify quality before writing
|
|
3602
662
|
5. Use the Write tool to create \`.claude/CLAUDE.md\` with the final content
|
|
3603
|
-
6.
|
|
663
|
+
6. Execute Phase 4 - generate ALL skill files
|
|
664
|
+
7. Execute Phase 5 - generate agent files
|
|
665
|
+
8. Execute Phase 6 - generate rule files
|
|
666
|
+
9. Execute Phase 7 - generate command files
|
|
667
|
+
10. Output a brief summary of what was generated and any gaps found
|
|
3604
668
|
|
|
3605
|
-
Do NOT output
|
|
669
|
+
Do NOT output file contents to stdout. Write all files to disk using the Write tool.
|
|
670
|
+
Generate ALL files in a single pass \u2014 do not stop after CLAUDE.md.`;
|
|
3606
671
|
}
|
|
3607
672
|
function buildContextSection(projectInfo) {
|
|
3608
673
|
const { name, description, techStack, fileCount } = projectInfo;
|
|
@@ -3654,11 +719,88 @@ function buildContextSection(projectInfo) {
|
|
|
3654
719
|
}
|
|
3655
720
|
return lines.join("\n");
|
|
3656
721
|
}
|
|
722
|
+
function buildTemplateVariables(projectInfo) {
|
|
723
|
+
const { techStack } = projectInfo;
|
|
724
|
+
const vars = [];
|
|
725
|
+
vars.push(`- **detected_languages**: ${techStack.languages.join(", ") || "none detected"}`);
|
|
726
|
+
vars.push(`- **detected_frameworks**: ${techStack.frameworks.join(", ") || "none detected"}`);
|
|
727
|
+
vars.push(`- **detected_testing_framework**: ${techStack.testingFramework || "none detected"}`);
|
|
728
|
+
vars.push(`- **test_command**: ${getTestCommand(techStack)}`);
|
|
729
|
+
vars.push(`- **lint_command**: ${getLintCommand(techStack)}`);
|
|
730
|
+
vars.push(`- **detected_linter**: ${techStack.linter || "none detected"}`);
|
|
731
|
+
vars.push(`- **detected_formatter**: ${techStack.formatter || "none detected"}`);
|
|
732
|
+
vars.push(`- **source_glob_patterns**: ${getSourceGlobs(techStack)}`);
|
|
733
|
+
return vars.join("\n");
|
|
734
|
+
}
|
|
735
|
+
function getTestCommand(stack) {
|
|
736
|
+
if (stack.testingFramework) {
|
|
737
|
+
const commands = {
|
|
738
|
+
jest: "npx jest",
|
|
739
|
+
vitest: "npx vitest",
|
|
740
|
+
"bun-test": "bun test",
|
|
741
|
+
pytest: "pytest",
|
|
742
|
+
"go-test": "go test ./...",
|
|
743
|
+
"rust-test": "cargo test",
|
|
744
|
+
rspec: "bundle exec rspec",
|
|
745
|
+
junit: "mvn test",
|
|
746
|
+
mocha: "npx mocha",
|
|
747
|
+
playwright: "npx playwright test",
|
|
748
|
+
cypress: "npx cypress run",
|
|
749
|
+
unittest: "python -m unittest discover"
|
|
750
|
+
};
|
|
751
|
+
return commands[stack.testingFramework] || stack.testingFramework;
|
|
752
|
+
}
|
|
753
|
+
if (stack.packageManager === "bun") return "bun test";
|
|
754
|
+
if (stack.packageManager === "cargo") return "cargo test";
|
|
755
|
+
if (stack.packageManager === "go") return "go test ./...";
|
|
756
|
+
if (stack.packageManager) return `${stack.packageManager} test`;
|
|
757
|
+
return "npm test";
|
|
758
|
+
}
|
|
759
|
+
function getLintCommand(stack) {
|
|
760
|
+
if (stack.linter) {
|
|
761
|
+
const commands = {
|
|
762
|
+
eslint: "npx eslint .",
|
|
763
|
+
biome: "npx biome check .",
|
|
764
|
+
pylint: "pylint",
|
|
765
|
+
flake8: "flake8",
|
|
766
|
+
ruff: "ruff check .",
|
|
767
|
+
"golangci-lint": "golangci-lint run",
|
|
768
|
+
clippy: "cargo clippy",
|
|
769
|
+
rubocop: "bundle exec rubocop"
|
|
770
|
+
};
|
|
771
|
+
return commands[stack.linter] || stack.linter;
|
|
772
|
+
}
|
|
773
|
+
return "no linter detected";
|
|
774
|
+
}
|
|
775
|
+
function getSourceGlobs(stack) {
|
|
776
|
+
const globs = [];
|
|
777
|
+
for (const lang of stack.languages) {
|
|
778
|
+
const langGlobs = {
|
|
779
|
+
typescript: ["**/*.ts", "**/*.tsx"],
|
|
780
|
+
javascript: ["**/*.js", "**/*.jsx"],
|
|
781
|
+
python: ["**/*.py"],
|
|
782
|
+
go: ["**/*.go"],
|
|
783
|
+
rust: ["**/*.rs"],
|
|
784
|
+
java: ["**/*.java"],
|
|
785
|
+
ruby: ["**/*.rb"],
|
|
786
|
+
csharp: ["**/*.cs"],
|
|
787
|
+
swift: ["**/*.swift"],
|
|
788
|
+
kotlin: ["**/*.kt", "**/*.kts"],
|
|
789
|
+
php: ["**/*.php"],
|
|
790
|
+
cpp: ["**/*.cpp", "**/*.hpp", "**/*.h"]
|
|
791
|
+
};
|
|
792
|
+
const patterns = langGlobs[lang];
|
|
793
|
+
if (patterns) {
|
|
794
|
+
globs.push(...patterns);
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
return globs.length > 0 ? globs.join(", ") : "**/*";
|
|
798
|
+
}
|
|
3657
799
|
var ANALYSIS_PROMPT = `You are a senior software architect performing a comprehensive codebase analysis.
|
|
3658
|
-
Your goal is to generate
|
|
800
|
+
Your goal is to generate ALL \`.claude/\` configuration files that give Claude
|
|
3659
801
|
complete context to work effectively in this project.
|
|
3660
802
|
|
|
3661
|
-
**This is NOT a generic template.** Every
|
|
803
|
+
**This is NOT a generic template.** Every file must contain information specific to THIS
|
|
3662
804
|
project, discovered through actual file reading and analysis. If you cannot determine
|
|
3663
805
|
something, omit that section entirely - do not fill in generic boilerplate.
|
|
3664
806
|
|
|
@@ -3667,7 +809,7 @@ something, omit that section entirely - do not fill in generic boilerplate.
|
|
|
3667
809
|
## Phase 1: Discovery (Read Before You Write)
|
|
3668
810
|
|
|
3669
811
|
Perform these analysis steps IN ORDER. Do not skip any step. Do not start writing
|
|
3670
|
-
|
|
812
|
+
any files until all discovery is complete.
|
|
3671
813
|
|
|
3672
814
|
### 1.1 Project Identity
|
|
3673
815
|
|
|
@@ -3901,6 +1043,264 @@ Before writing the CLAUDE.md, verify:
|
|
|
3901
1043
|
5. **Keep it maintainable.** Don't include metrics that go stale quickly.
|
|
3902
1044
|
|
|
3903
1045
|
6. **Respect existing CLAUDE.md.** If one exists, read it first and preserve manually-added sections.`;
|
|
1046
|
+
var SKILLS_PROMPT = `---
|
|
1047
|
+
|
|
1048
|
+
## Phase 4: Generate Skills
|
|
1049
|
+
|
|
1050
|
+
Write each skill file to \`.claude/skills/\` using the Write tool. Every skill must have
|
|
1051
|
+
YAML frontmatter with \`name\`, \`description\`, and optionally \`globs\` for file matching.
|
|
1052
|
+
|
|
1053
|
+
**Tailor ALL skills to this specific project** \u2014 use the actual test command, lint command,
|
|
1054
|
+
file patterns, and conventions discovered during Phase 1.
|
|
1055
|
+
|
|
1056
|
+
### 4.1 Core Skills (ALWAYS generate all 8)
|
|
1057
|
+
|
|
1058
|
+
**\`.claude/skills/pattern-discovery.md\`**
|
|
1059
|
+
- Name: pattern-discovery
|
|
1060
|
+
- Description: Analyze codebase to discover and document patterns
|
|
1061
|
+
- Content: How to search for patterns in THIS project's structure. Include the actual source directories, key file patterns, and import conventions found.
|
|
1062
|
+
|
|
1063
|
+
**\`.claude/skills/systematic-debugging.md\`**
|
|
1064
|
+
- Name: systematic-debugging
|
|
1065
|
+
- Description: 4-phase debugging methodology \u2014 Reproduce, Locate, Diagnose, Fix
|
|
1066
|
+
- Content: Tailor reproduction steps to the project's actual test runner and dev server commands. Include how to use the project's logging/debugging setup.
|
|
1067
|
+
|
|
1068
|
+
**\`.claude/skills/testing-methodology.md\`**
|
|
1069
|
+
- Name: testing-methodology
|
|
1070
|
+
- Description: AAA testing pattern with project-specific framework syntax
|
|
1071
|
+
- Content: Use the project's actual testing framework syntax. Include real examples of test patterns found in the codebase (describe/it blocks, pytest fixtures, etc.). Reference the actual test command. Include mocking/stubbing patterns specific to the stack.
|
|
1072
|
+
|
|
1073
|
+
**\`.claude/skills/iterative-development.md\`**
|
|
1074
|
+
- Name: iterative-development
|
|
1075
|
+
- Description: TDD workflow with project-specific test and lint commands
|
|
1076
|
+
- Content: The TDD loop using the actual test command and lint command. Include the project's verification steps (typecheck, build, etc.).
|
|
1077
|
+
|
|
1078
|
+
**\`.claude/skills/commit-hygiene.md\`**
|
|
1079
|
+
- Name: commit-hygiene
|
|
1080
|
+
- Description: Atomic commits, conventional format, size thresholds
|
|
1081
|
+
- Content: Size thresholds (\xB1300 lines per commit), when-to-commit triggers, conventional commit format. If the project uses commitlint or similar, reference its config.
|
|
1082
|
+
|
|
1083
|
+
**\`.claude/skills/code-deduplication.md\`**
|
|
1084
|
+
- Name: code-deduplication
|
|
1085
|
+
- Description: Check-before-write principle and search checklist
|
|
1086
|
+
- Content: Search existing code before writing new code. Include project-specific glob patterns for source files. Reference the actual directory structure for where to look.
|
|
1087
|
+
|
|
1088
|
+
**\`.claude/skills/simplicity-rules.md\`**
|
|
1089
|
+
- Name: simplicity-rules
|
|
1090
|
+
- Description: Function and file size limits, decomposition patterns
|
|
1091
|
+
- Content: Function length limits (40 lines), file limits (300 lines), cyclomatic complexity. Decomposition patterns appropriate for the project's architecture style.
|
|
1092
|
+
|
|
1093
|
+
**\`.claude/skills/security.md\`**
|
|
1094
|
+
- Name: security
|
|
1095
|
+
- Description: Security patterns and secrets management for this stack
|
|
1096
|
+
- Content: .gitignore entries appropriate for the detected stack. Environment variable handling patterns. OWASP checklist items relevant to the detected framework. Include actual secrets patterns to watch for (API keys, database URLs, etc.).
|
|
1097
|
+
|
|
1098
|
+
### 4.2 Framework-Specific Skills (ONLY if detected)
|
|
1099
|
+
|
|
1100
|
+
Generate the matching skill ONLY if the framework was detected in the tech stack:
|
|
1101
|
+
|
|
1102
|
+
- **Next.js detected** \u2192 Write \`.claude/skills/nextjs-patterns.md\` \u2014 App Router patterns, Server/Client Components, data fetching (fetch, server actions), middleware, image optimization, caching strategies. Use patterns from the actual codebase.
|
|
1103
|
+
|
|
1104
|
+
- **React (without Next.js) detected** \u2192 Write \`.claude/skills/react-components.md\` \u2014 Hooks patterns, component composition, state management (whatever is used), performance (memo, useMemo, useCallback), error boundaries.
|
|
1105
|
+
|
|
1106
|
+
- **FastAPI detected** \u2192 Write \`.claude/skills/fastapi-patterns.md\` \u2014 Router organization, dependency injection, Pydantic models, async/await patterns, middleware, exception handlers.
|
|
1107
|
+
|
|
1108
|
+
- **NestJS detected** \u2192 Write \`.claude/skills/nestjs-patterns.md\` \u2014 Module structure, controllers, services, decorators, pipes, guards, interceptors, custom providers.
|
|
1109
|
+
|
|
1110
|
+
- **SwiftUI detected** \u2192 Write \`.claude/skills/swiftui-patterns.md\` \u2014 Property wrappers (@State, @Binding, @StateObject, @EnvironmentObject), MVVM, navigation (NavigationStack/NavigationSplitView), previews, accessibility.
|
|
1111
|
+
|
|
1112
|
+
- **UIKit detected** \u2192 Write \`.claude/skills/uikit-patterns.md\` \u2014 View controller lifecycle, Auto Layout (programmatic and storyboard), delegates/datasources, MVC, coordinator pattern.
|
|
1113
|
+
|
|
1114
|
+
- **Vapor detected** \u2192 Write \`.claude/skills/vapor-patterns.md\` \u2014 Routes, middleware, Fluent ORM, async controllers, content negotiation, validation.
|
|
1115
|
+
|
|
1116
|
+
- **Jetpack Compose detected** \u2192 Write \`.claude/skills/compose-patterns.md\` \u2014 @Composable functions, remember/rememberSaveable, ViewModel integration, navigation, side effects (LaunchedEffect, DisposableEffect), theming.
|
|
1117
|
+
|
|
1118
|
+
- **Android Views detected** \u2192 Write \`.claude/skills/android-views-patterns.md\` \u2014 Activities, Fragments, XML layouts, ViewBinding, RecyclerView, lifecycle awareness.
|
|
1119
|
+
|
|
1120
|
+
- **Vue/Nuxt detected** \u2192 Write \`.claude/skills/vue-patterns.md\` \u2014 Composition API (ref, reactive, computed), composables, Pinia stores, routing, auto-imports.
|
|
1121
|
+
|
|
1122
|
+
- **Django detected** \u2192 Write \`.claude/skills/django-patterns.md\` \u2014 Models, views (class-based and function-based), serializers, middleware, admin customization, signals.
|
|
1123
|
+
|
|
1124
|
+
- **Rails detected** \u2192 Write \`.claude/skills/rails-patterns.md\` \u2014 MVC, ActiveRecord, concerns, service objects, jobs, mailers, strong parameters.
|
|
1125
|
+
|
|
1126
|
+
- **Spring detected** \u2192 Write \`.claude/skills/spring-patterns.md\` \u2014 Beans, controllers, services, repositories, AOP, dependency injection, configuration properties.`;
|
|
1127
|
+
var AGENTS_PROMPT = `---
|
|
1128
|
+
|
|
1129
|
+
## Phase 5: Generate Agents
|
|
1130
|
+
|
|
1131
|
+
Write 2 agent files to \`.claude/agents/\`.
|
|
1132
|
+
|
|
1133
|
+
### \`.claude/agents/code-reviewer.md\`
|
|
1134
|
+
|
|
1135
|
+
YAML frontmatter:
|
|
1136
|
+
\`\`\`yaml
|
|
1137
|
+
---
|
|
1138
|
+
name: code-reviewer
|
|
1139
|
+
description: Reviews code for quality, security issues, and best practices
|
|
1140
|
+
tools:
|
|
1141
|
+
- Read
|
|
1142
|
+
- Grep
|
|
1143
|
+
- Glob
|
|
1144
|
+
- "Bash({lint_command})"
|
|
1145
|
+
disallowed_tools:
|
|
1146
|
+
- Write
|
|
1147
|
+
- Edit
|
|
1148
|
+
model: sonnet
|
|
1149
|
+
---
|
|
1150
|
+
\`\`\`
|
|
1151
|
+
|
|
1152
|
+
Body content \u2014 instructions for the code reviewer agent:
|
|
1153
|
+
- Check naming conventions match project patterns
|
|
1154
|
+
- Verify error handling follows project style
|
|
1155
|
+
- Look for security issues (injection, XSS, auth bypass, secrets exposure)
|
|
1156
|
+
- Verify test coverage for changed code
|
|
1157
|
+
- Check import organization
|
|
1158
|
+
- Flag code duplication
|
|
1159
|
+
- Use the project's actual linter for automated checks
|
|
1160
|
+
|
|
1161
|
+
### \`.claude/agents/test-writer.md\`
|
|
1162
|
+
|
|
1163
|
+
YAML frontmatter:
|
|
1164
|
+
\`\`\`yaml
|
|
1165
|
+
---
|
|
1166
|
+
name: test-writer
|
|
1167
|
+
description: Generates comprehensive tests for code
|
|
1168
|
+
tools:
|
|
1169
|
+
- Read
|
|
1170
|
+
- Grep
|
|
1171
|
+
- Glob
|
|
1172
|
+
- Write
|
|
1173
|
+
- Edit
|
|
1174
|
+
- "Bash({test_command})"
|
|
1175
|
+
model: sonnet
|
|
1176
|
+
---
|
|
1177
|
+
\`\`\`
|
|
1178
|
+
|
|
1179
|
+
Body content \u2014 instructions for the test writer agent:
|
|
1180
|
+
- Follow the AAA pattern (Arrange, Act, Assert)
|
|
1181
|
+
- Use the project's actual testing framework and syntax
|
|
1182
|
+
- Follow existing test file naming conventions
|
|
1183
|
+
- Include edge cases: empty inputs, nulls, errors, boundaries
|
|
1184
|
+
- Mock external dependencies following project patterns
|
|
1185
|
+
- Run tests after writing to verify they pass`;
|
|
1186
|
+
var RULES_PROMPT = `---
|
|
1187
|
+
|
|
1188
|
+
## Phase 6: Generate Rules
|
|
1189
|
+
|
|
1190
|
+
Write rule files to \`.claude/rules/\`. Each rule file needs YAML frontmatter.
|
|
1191
|
+
|
|
1192
|
+
### Always Generate:
|
|
1193
|
+
|
|
1194
|
+
**\`.claude/rules/code-style.md\`** (no \`paths\` \u2014 applies to all files)
|
|
1195
|
+
|
|
1196
|
+
Content based on what was discovered in Phase 1:
|
|
1197
|
+
- Which formatter/linter to use and how (include actual commands)
|
|
1198
|
+
- Comment style: "why" not "what", keep comments current
|
|
1199
|
+
- Error handling patterns specific to this project
|
|
1200
|
+
- Git commit message conventions (conventional commits if commitlint is configured)
|
|
1201
|
+
- Import ordering conventions found in the codebase
|
|
1202
|
+
|
|
1203
|
+
### Conditional Rules (generate ONLY if the language was detected):
|
|
1204
|
+
|
|
1205
|
+
**TypeScript detected** \u2192 Write \`.claude/rules/typescript.md\`
|
|
1206
|
+
\`\`\`yaml
|
|
1207
|
+
---
|
|
1208
|
+
paths: ["**/*.ts", "**/*.tsx"]
|
|
1209
|
+
---
|
|
1210
|
+
\`\`\`
|
|
1211
|
+
Content: strict mode settings, type annotation preferences (interface vs type), import style (type imports), null handling, generic patterns found in the codebase.
|
|
1212
|
+
|
|
1213
|
+
**Python detected** \u2192 Write \`.claude/rules/python.md\`
|
|
1214
|
+
\`\`\`yaml
|
|
1215
|
+
---
|
|
1216
|
+
paths: ["**/*.py"]
|
|
1217
|
+
---
|
|
1218
|
+
\`\`\`
|
|
1219
|
+
Content: type hint style, docstring format (Google/NumPy/Sphinx), import ordering (isort), virtual environment conventions, Python version requirements.
|
|
1220
|
+
|
|
1221
|
+
**Swift detected** \u2192 Write \`.claude/rules/swift.md\`
|
|
1222
|
+
\`\`\`yaml
|
|
1223
|
+
---
|
|
1224
|
+
paths: ["**/*.swift"]
|
|
1225
|
+
---
|
|
1226
|
+
\`\`\`
|
|
1227
|
+
Content: access control patterns, optional handling, protocol-oriented patterns, SwiftLint rules if configured.
|
|
1228
|
+
|
|
1229
|
+
**Go detected** \u2192 Write \`.claude/rules/go.md\`
|
|
1230
|
+
\`\`\`yaml
|
|
1231
|
+
---
|
|
1232
|
+
paths: ["**/*.go"]
|
|
1233
|
+
---
|
|
1234
|
+
\`\`\`
|
|
1235
|
+
Content: error handling patterns (wrap errors), interface placement, package naming, go fmt/vet/lint conventions.
|
|
1236
|
+
|
|
1237
|
+
**Rust detected** \u2192 Write \`.claude/rules/rust.md\`
|
|
1238
|
+
\`\`\`yaml
|
|
1239
|
+
---
|
|
1240
|
+
paths: ["**/*.rs"]
|
|
1241
|
+
---
|
|
1242
|
+
\`\`\`
|
|
1243
|
+
Content: ownership/borrowing patterns, error handling (Result/Option, thiserror/anyhow), trait patterns, clippy lints.`;
|
|
1244
|
+
var COMMANDS_PROMPT = `---
|
|
1245
|
+
|
|
1246
|
+
## Phase 7: Generate Commands
|
|
1247
|
+
|
|
1248
|
+
Write 5 command files to \`.claude/commands/\`. Each needs YAML frontmatter with
|
|
1249
|
+
\`allowed-tools\`, \`description\`, and optionally \`argument-hint\`.
|
|
1250
|
+
|
|
1251
|
+
### \`.claude/commands/task.md\`
|
|
1252
|
+
\`\`\`yaml
|
|
1253
|
+
---
|
|
1254
|
+
allowed-tools: ["Read", "Write", "Edit", "Glob"]
|
|
1255
|
+
description: "Start or switch to a new task"
|
|
1256
|
+
argument-hint: "<task description>"
|
|
1257
|
+
---
|
|
1258
|
+
\`\`\`
|
|
1259
|
+
Body: Instructions to read current \`.claude/state/task.md\`, update status to "In Progress",
|
|
1260
|
+
record the task description and timestamp. If starting a new task, archive the previous one.
|
|
1261
|
+
|
|
1262
|
+
### \`.claude/commands/status.md\`
|
|
1263
|
+
\`\`\`yaml
|
|
1264
|
+
---
|
|
1265
|
+
allowed-tools: ["Read", "Glob", "Grep", "Bash(git status)", "Bash(git diff --stat)"]
|
|
1266
|
+
description: "Show current task and session state"
|
|
1267
|
+
---
|
|
1268
|
+
\`\`\`
|
|
1269
|
+
Body: Read \`.claude/state/task.md\`, show git status, list recently modified files,
|
|
1270
|
+
summarize current state in a concise format.
|
|
1271
|
+
|
|
1272
|
+
### \`.claude/commands/done.md\`
|
|
1273
|
+
\`\`\`yaml
|
|
1274
|
+
---
|
|
1275
|
+
allowed-tools: ["Read", "Write", "Edit", "Glob", "Grep", "Bash(git:*)", "Bash({test_command})", "Bash({lint_command})"]
|
|
1276
|
+
description: "Mark current task complete"
|
|
1277
|
+
---
|
|
1278
|
+
\`\`\`
|
|
1279
|
+
Body: Run tests and lint checks. If they pass, update \`.claude/state/task.md\`
|
|
1280
|
+
status to "Done". Show a summary of what was accomplished. Suggest next steps.
|
|
1281
|
+
|
|
1282
|
+
### \`.claude/commands/analyze.md\`
|
|
1283
|
+
\`\`\`yaml
|
|
1284
|
+
---
|
|
1285
|
+
allowed-tools: ["Read", "Glob", "Grep"]
|
|
1286
|
+
description: "Deep analysis of a specific area"
|
|
1287
|
+
argument-hint: "<area or file path>"
|
|
1288
|
+
---
|
|
1289
|
+
\`\`\`
|
|
1290
|
+
Body: Perform thorough analysis of the specified area. Read relevant files,
|
|
1291
|
+
trace data flow, identify patterns, document findings. Output a structured report.
|
|
1292
|
+
|
|
1293
|
+
### \`.claude/commands/code-review.md\`
|
|
1294
|
+
\`\`\`yaml
|
|
1295
|
+
---
|
|
1296
|
+
allowed-tools: ["Read", "Glob", "Grep", "Bash(git diff)", "Bash(git diff --cached)", "Bash({lint_command})"]
|
|
1297
|
+
description: "Review code changes for quality and security"
|
|
1298
|
+
---
|
|
1299
|
+
\`\`\`
|
|
1300
|
+
Body: Review staged and unstaged changes. Check for: naming consistency,
|
|
1301
|
+
error handling, security issues, test coverage, import organization,
|
|
1302
|
+
code duplication. Run the project's linter. Provide a summary with
|
|
1303
|
+
severity levels (critical, warning, suggestion).`;
|
|
3904
1304
|
|
|
3905
1305
|
// src/cli.ts
|
|
3906
1306
|
var __dirname2 = path3.dirname(fileURLToPath(import.meta.url));
|
|
@@ -3938,11 +1338,12 @@ ${pc.bold("OPTIONS")}
|
|
|
3938
1338
|
${pc.bold("WHAT IT DOES")}
|
|
3939
1339
|
1. Analyzes your repository's tech stack
|
|
3940
1340
|
2. Launches Claude CLI to deeply analyze your codebase
|
|
3941
|
-
3. Generates
|
|
1341
|
+
3. Generates all .claude/ configuration files:
|
|
3942
1342
|
- CLAUDE.md with project-specific instructions (via Claude analysis)
|
|
3943
|
-
- Skills for your frameworks
|
|
1343
|
+
- Skills for your frameworks and workflows
|
|
3944
1344
|
- Agents for code review and testing
|
|
3945
1345
|
- Rules matching your code style
|
|
1346
|
+
- Commands for task management
|
|
3946
1347
|
|
|
3947
1348
|
${pc.bold("REQUIREMENTS")}
|
|
3948
1349
|
Claude CLI must be installed: https://claude.ai/download
|
|
@@ -3995,70 +1396,277 @@ function showTechStack(projectInfo, verbose) {
|
|
|
3995
1396
|
}
|
|
3996
1397
|
console.log();
|
|
3997
1398
|
}
|
|
1399
|
+
var frameworkChoices = {
|
|
1400
|
+
typescript: [
|
|
1401
|
+
{ title: "Next.js", value: "nextjs" },
|
|
1402
|
+
{ title: "React", value: "react" },
|
|
1403
|
+
{ title: "Vue", value: "vue" },
|
|
1404
|
+
{ title: "Svelte", value: "svelte" },
|
|
1405
|
+
{ title: "Express", value: "express" },
|
|
1406
|
+
{ title: "NestJS", value: "nestjs" },
|
|
1407
|
+
{ title: "Fastify", value: "fastify" },
|
|
1408
|
+
{ title: "Hono", value: "hono" },
|
|
1409
|
+
{ title: "Astro", value: "astro" },
|
|
1410
|
+
{ title: "None / Other", value: null }
|
|
1411
|
+
],
|
|
1412
|
+
javascript: [
|
|
1413
|
+
{ title: "Next.js", value: "nextjs" },
|
|
1414
|
+
{ title: "React", value: "react" },
|
|
1415
|
+
{ title: "Vue", value: "vue" },
|
|
1416
|
+
{ title: "Svelte", value: "svelte" },
|
|
1417
|
+
{ title: "Express", value: "express" },
|
|
1418
|
+
{ title: "NestJS", value: "nestjs" },
|
|
1419
|
+
{ title: "Fastify", value: "fastify" },
|
|
1420
|
+
{ title: "Hono", value: "hono" },
|
|
1421
|
+
{ title: "Astro", value: "astro" },
|
|
1422
|
+
{ title: "None / Other", value: null }
|
|
1423
|
+
],
|
|
1424
|
+
python: [
|
|
1425
|
+
{ title: "FastAPI", value: "fastapi" },
|
|
1426
|
+
{ title: "Django", value: "django" },
|
|
1427
|
+
{ title: "Flask", value: "flask" },
|
|
1428
|
+
{ title: "None / Other", value: null }
|
|
1429
|
+
],
|
|
1430
|
+
go: [
|
|
1431
|
+
{ title: "Gin", value: "gin" },
|
|
1432
|
+
{ title: "Echo", value: "echo" },
|
|
1433
|
+
{ title: "Fiber", value: "fiber" },
|
|
1434
|
+
{ title: "None / Other", value: null }
|
|
1435
|
+
],
|
|
1436
|
+
swift: [
|
|
1437
|
+
{ title: "SwiftUI", value: "swiftui" },
|
|
1438
|
+
{ title: "UIKit", value: "uikit" },
|
|
1439
|
+
{ title: "Vapor", value: "vapor" },
|
|
1440
|
+
{ title: "None / Other", value: null }
|
|
1441
|
+
],
|
|
1442
|
+
kotlin: [
|
|
1443
|
+
{ title: "Jetpack Compose", value: "jetpack-compose" },
|
|
1444
|
+
{ title: "Android Views", value: "android-views" },
|
|
1445
|
+
{ title: "Spring", value: "spring" },
|
|
1446
|
+
{ title: "None / Other", value: null }
|
|
1447
|
+
],
|
|
1448
|
+
java: [
|
|
1449
|
+
{ title: "Spring", value: "spring" },
|
|
1450
|
+
{ title: "Quarkus", value: "quarkus" },
|
|
1451
|
+
{ title: "None / Other", value: null }
|
|
1452
|
+
],
|
|
1453
|
+
ruby: [
|
|
1454
|
+
{ title: "Rails", value: "rails" },
|
|
1455
|
+
{ title: "Sinatra", value: "sinatra" },
|
|
1456
|
+
{ title: "None / Other", value: null }
|
|
1457
|
+
],
|
|
1458
|
+
rust: [
|
|
1459
|
+
{ title: "Actix", value: "actix" },
|
|
1460
|
+
{ title: "Axum", value: "axum" },
|
|
1461
|
+
{ title: "Rocket", value: "rocket" },
|
|
1462
|
+
{ title: "None / Other", value: null }
|
|
1463
|
+
]
|
|
1464
|
+
};
|
|
1465
|
+
var defaultFrameworkChoices = [{ title: "None / Other", value: null }];
|
|
3998
1466
|
async function promptNewProject(args) {
|
|
3999
1467
|
if (!args.interactive) {
|
|
4000
1468
|
return null;
|
|
4001
1469
|
}
|
|
4002
1470
|
console.log(pc.yellow("New project detected - let's set it up!"));
|
|
4003
1471
|
console.log();
|
|
4004
|
-
const
|
|
4005
|
-
|
|
4006
|
-
|
|
4007
|
-
|
|
4008
|
-
|
|
4009
|
-
|
|
4010
|
-
|
|
4011
|
-
{
|
|
4012
|
-
type: "select",
|
|
4013
|
-
name: "primaryLanguage",
|
|
4014
|
-
message: "Primary language?",
|
|
4015
|
-
choices: [
|
|
4016
|
-
{ title: "TypeScript", value: "typescript" },
|
|
4017
|
-
{ title: "JavaScript", value: "javascript" },
|
|
4018
|
-
{ title: "Python", value: "python" },
|
|
4019
|
-
{ title: "Go", value: "go" },
|
|
4020
|
-
{ title: "Rust", value: "rust" },
|
|
4021
|
-
{ title: "Other", value: null }
|
|
4022
|
-
]
|
|
4023
|
-
},
|
|
4024
|
-
{
|
|
4025
|
-
type: (prev) => prev === "typescript" || prev === "javascript" ? "select" : null,
|
|
4026
|
-
name: "framework",
|
|
4027
|
-
message: "Framework?",
|
|
4028
|
-
choices: [
|
|
4029
|
-
{ title: "Next.js", value: "nextjs" },
|
|
4030
|
-
{ title: "React", value: "react" },
|
|
4031
|
-
{ title: "Vue", value: "vue" },
|
|
4032
|
-
{ title: "Svelte", value: "svelte" },
|
|
4033
|
-
{ title: "Express", value: "express" },
|
|
4034
|
-
{ title: "NestJS", value: "nestjs" },
|
|
4035
|
-
{ title: "Hono", value: "hono" },
|
|
4036
|
-
{ title: "None / Other", value: null }
|
|
4037
|
-
]
|
|
4038
|
-
},
|
|
4039
|
-
{
|
|
4040
|
-
type: (_, values) => values.primaryLanguage === "python" ? "select" : null,
|
|
4041
|
-
name: "framework",
|
|
4042
|
-
message: "Framework?",
|
|
4043
|
-
choices: [
|
|
4044
|
-
{ title: "FastAPI", value: "fastapi" },
|
|
4045
|
-
{ title: "Django", value: "django" },
|
|
4046
|
-
{ title: "Flask", value: "flask" },
|
|
4047
|
-
{ title: "None / Other", value: null }
|
|
4048
|
-
]
|
|
4049
|
-
}
|
|
4050
|
-
]);
|
|
4051
|
-
if (!response.description) {
|
|
1472
|
+
const descResponse = await prompts({
|
|
1473
|
+
type: "text",
|
|
1474
|
+
name: "description",
|
|
1475
|
+
message: "What are you building?",
|
|
1476
|
+
initial: "A new project"
|
|
1477
|
+
});
|
|
1478
|
+
if (!descResponse.description) {
|
|
4052
1479
|
return null;
|
|
4053
1480
|
}
|
|
1481
|
+
const langResponse = await prompts({
|
|
1482
|
+
type: "select",
|
|
1483
|
+
name: "primaryLanguage",
|
|
1484
|
+
message: "Primary language?",
|
|
1485
|
+
choices: [
|
|
1486
|
+
{ title: "TypeScript", value: "typescript" },
|
|
1487
|
+
{ title: "JavaScript", value: "javascript" },
|
|
1488
|
+
{ title: "Python", value: "python" },
|
|
1489
|
+
{ title: "Go", value: "go" },
|
|
1490
|
+
{ title: "Rust", value: "rust" },
|
|
1491
|
+
{ title: "Swift", value: "swift" },
|
|
1492
|
+
{ title: "Kotlin", value: "kotlin" },
|
|
1493
|
+
{ title: "Java", value: "java" },
|
|
1494
|
+
{ title: "Ruby", value: "ruby" },
|
|
1495
|
+
{ title: "C#", value: "csharp" },
|
|
1496
|
+
{ title: "PHP", value: "php" },
|
|
1497
|
+
{ title: "C++", value: "cpp" }
|
|
1498
|
+
]
|
|
1499
|
+
});
|
|
1500
|
+
const lang = langResponse.primaryLanguage || "typescript";
|
|
1501
|
+
const fwChoices = frameworkChoices[lang] || defaultFrameworkChoices;
|
|
1502
|
+
const fwResponse = await prompts({
|
|
1503
|
+
type: "select",
|
|
1504
|
+
name: "framework",
|
|
1505
|
+
message: "Framework?",
|
|
1506
|
+
choices: fwChoices
|
|
1507
|
+
});
|
|
1508
|
+
const pmChoices = getPackageManagerChoices(lang);
|
|
1509
|
+
const pmResponse = await prompts({
|
|
1510
|
+
type: "select",
|
|
1511
|
+
name: "packageManager",
|
|
1512
|
+
message: "Package manager?",
|
|
1513
|
+
choices: pmChoices
|
|
1514
|
+
});
|
|
1515
|
+
const testChoices = getTestingFrameworkChoices(lang);
|
|
1516
|
+
const testResponse = await prompts({
|
|
1517
|
+
type: "select",
|
|
1518
|
+
name: "testingFramework",
|
|
1519
|
+
message: "Testing framework?",
|
|
1520
|
+
choices: testChoices
|
|
1521
|
+
});
|
|
1522
|
+
const lintChoices = getLinterFormatterChoices(lang);
|
|
1523
|
+
const lintResponse = await prompts({
|
|
1524
|
+
type: "select",
|
|
1525
|
+
name: "linter",
|
|
1526
|
+
message: "Linter/Formatter?",
|
|
1527
|
+
choices: lintChoices
|
|
1528
|
+
});
|
|
1529
|
+
const typeResponse = await prompts({
|
|
1530
|
+
type: "select",
|
|
1531
|
+
name: "projectType",
|
|
1532
|
+
message: "Project type?",
|
|
1533
|
+
choices: [
|
|
1534
|
+
{ title: "Web App", value: "Web App" },
|
|
1535
|
+
{ title: "API / Backend", value: "API/Backend" },
|
|
1536
|
+
{ title: "CLI Tool", value: "CLI Tool" },
|
|
1537
|
+
{ title: "Library / Package", value: "Library/Package" },
|
|
1538
|
+
{ title: "Mobile App", value: "Mobile App" },
|
|
1539
|
+
{ title: "Desktop App", value: "Desktop App" },
|
|
1540
|
+
{ title: "Monorepo", value: "Monorepo" },
|
|
1541
|
+
{ title: "Other", value: "Other" }
|
|
1542
|
+
]
|
|
1543
|
+
});
|
|
4054
1544
|
return {
|
|
4055
|
-
description:
|
|
4056
|
-
primaryLanguage:
|
|
4057
|
-
framework:
|
|
1545
|
+
description: descResponse.description,
|
|
1546
|
+
primaryLanguage: langResponse.primaryLanguage || "typescript",
|
|
1547
|
+
framework: fwResponse.framework || null,
|
|
4058
1548
|
includeTests: true,
|
|
4059
|
-
includeLinting: true
|
|
1549
|
+
includeLinting: true,
|
|
1550
|
+
packageManager: pmResponse.packageManager || null,
|
|
1551
|
+
testingFramework: testResponse.testingFramework || null,
|
|
1552
|
+
linter: lintResponse.linter || null,
|
|
1553
|
+
formatter: lintResponse.linter || null,
|
|
1554
|
+
// Use same as linter for simplicity
|
|
1555
|
+
projectType: typeResponse.projectType || "Other"
|
|
4060
1556
|
};
|
|
4061
1557
|
}
|
|
1558
|
+
function getPackageManagerChoices(lang) {
|
|
1559
|
+
if (lang === "typescript" || lang === "javascript") {
|
|
1560
|
+
return [
|
|
1561
|
+
{ title: "npm", value: "npm" },
|
|
1562
|
+
{ title: "yarn", value: "yarn" },
|
|
1563
|
+
{ title: "pnpm", value: "pnpm" },
|
|
1564
|
+
{ title: "bun", value: "bun" }
|
|
1565
|
+
];
|
|
1566
|
+
}
|
|
1567
|
+
if (lang === "python") {
|
|
1568
|
+
return [
|
|
1569
|
+
{ title: "pip", value: "pip" },
|
|
1570
|
+
{ title: "poetry", value: "poetry" }
|
|
1571
|
+
];
|
|
1572
|
+
}
|
|
1573
|
+
if (lang === "rust") {
|
|
1574
|
+
return [{ title: "cargo", value: "cargo" }];
|
|
1575
|
+
}
|
|
1576
|
+
if (lang === "go") {
|
|
1577
|
+
return [{ title: "go modules", value: "go" }];
|
|
1578
|
+
}
|
|
1579
|
+
if (lang === "ruby") {
|
|
1580
|
+
return [{ title: "bundler", value: "bundler" }];
|
|
1581
|
+
}
|
|
1582
|
+
if (lang === "java" || lang === "kotlin") {
|
|
1583
|
+
return [
|
|
1584
|
+
{ title: "Maven", value: "maven" },
|
|
1585
|
+
{ title: "Gradle", value: "gradle" }
|
|
1586
|
+
];
|
|
1587
|
+
}
|
|
1588
|
+
return [{ title: "None / Default", value: null }];
|
|
1589
|
+
}
|
|
1590
|
+
function getTestingFrameworkChoices(lang) {
|
|
1591
|
+
if (lang === "typescript" || lang === "javascript") {
|
|
1592
|
+
return [
|
|
1593
|
+
{ title: "Vitest", value: "vitest" },
|
|
1594
|
+
{ title: "Jest", value: "jest" },
|
|
1595
|
+
{ title: "Bun Test", value: "bun-test" },
|
|
1596
|
+
{ title: "Playwright", value: "playwright" },
|
|
1597
|
+
{ title: "None / I'll set it up later", value: null }
|
|
1598
|
+
];
|
|
1599
|
+
}
|
|
1600
|
+
if (lang === "python") {
|
|
1601
|
+
return [
|
|
1602
|
+
{ title: "pytest", value: "pytest" },
|
|
1603
|
+
{ title: "unittest", value: "unittest" },
|
|
1604
|
+
{ title: "None / I'll set it up later", value: null }
|
|
1605
|
+
];
|
|
1606
|
+
}
|
|
1607
|
+
if (lang === "go") {
|
|
1608
|
+
return [
|
|
1609
|
+
{ title: "go test", value: "go-test" },
|
|
1610
|
+
{ title: "None / I'll set it up later", value: null }
|
|
1611
|
+
];
|
|
1612
|
+
}
|
|
1613
|
+
if (lang === "rust") {
|
|
1614
|
+
return [
|
|
1615
|
+
{ title: "cargo test", value: "rust-test" },
|
|
1616
|
+
{ title: "None / I'll set it up later", value: null }
|
|
1617
|
+
];
|
|
1618
|
+
}
|
|
1619
|
+
if (lang === "ruby") {
|
|
1620
|
+
return [
|
|
1621
|
+
{ title: "RSpec", value: "rspec" },
|
|
1622
|
+
{ title: "None / I'll set it up later", value: null }
|
|
1623
|
+
];
|
|
1624
|
+
}
|
|
1625
|
+
if (lang === "java" || lang === "kotlin") {
|
|
1626
|
+
return [
|
|
1627
|
+
{ title: "JUnit", value: "junit" },
|
|
1628
|
+
{ title: "None / I'll set it up later", value: null }
|
|
1629
|
+
];
|
|
1630
|
+
}
|
|
1631
|
+
return [{ title: "None / I'll set it up later", value: null }];
|
|
1632
|
+
}
|
|
1633
|
+
function getLinterFormatterChoices(lang) {
|
|
1634
|
+
if (lang === "typescript" || lang === "javascript") {
|
|
1635
|
+
return [
|
|
1636
|
+
{ title: "Biome", value: "biome" },
|
|
1637
|
+
{ title: "ESLint + Prettier", value: "eslint" },
|
|
1638
|
+
{ title: "ESLint", value: "eslint" },
|
|
1639
|
+
{ title: "None", value: null }
|
|
1640
|
+
];
|
|
1641
|
+
}
|
|
1642
|
+
if (lang === "python") {
|
|
1643
|
+
return [
|
|
1644
|
+
{ title: "Ruff", value: "ruff" },
|
|
1645
|
+
{ title: "Flake8 + Black", value: "flake8" },
|
|
1646
|
+
{ title: "Pylint", value: "pylint" },
|
|
1647
|
+
{ title: "None", value: null }
|
|
1648
|
+
];
|
|
1649
|
+
}
|
|
1650
|
+
if (lang === "go") {
|
|
1651
|
+
return [
|
|
1652
|
+
{ title: "golangci-lint", value: "golangci-lint" },
|
|
1653
|
+
{ title: "None", value: null }
|
|
1654
|
+
];
|
|
1655
|
+
}
|
|
1656
|
+
if (lang === "rust") {
|
|
1657
|
+
return [
|
|
1658
|
+
{ title: "Clippy", value: "clippy" },
|
|
1659
|
+
{ title: "None", value: null }
|
|
1660
|
+
];
|
|
1661
|
+
}
|
|
1662
|
+
if (lang === "ruby") {
|
|
1663
|
+
return [
|
|
1664
|
+
{ title: "RuboCop", value: "rubocop" },
|
|
1665
|
+
{ title: "None", value: null }
|
|
1666
|
+
];
|
|
1667
|
+
}
|
|
1668
|
+
return [{ title: "None", value: null }];
|
|
1669
|
+
}
|
|
4062
1670
|
function createTaskFile(projectInfo, preferences) {
|
|
4063
1671
|
const taskPath = path3.join(projectInfo.rootDir, ".claude", "state", "task.md");
|
|
4064
1672
|
fs3.mkdirSync(path3.dirname(taskPath), { recursive: true });
|
|
@@ -4189,25 +1797,33 @@ function runClaudeAnalysis(projectDir, projectInfo) {
|
|
|
4189
1797
|
return new Promise((resolve) => {
|
|
4190
1798
|
const prompt = getAnalysisPrompt(projectInfo);
|
|
4191
1799
|
console.log(pc.cyan("Launching Claude for deep project analysis..."));
|
|
4192
|
-
console.log(
|
|
1800
|
+
console.log(
|
|
1801
|
+
pc.gray("Claude will read your codebase and generate all .claude/ configuration files")
|
|
1802
|
+
);
|
|
4193
1803
|
console.log();
|
|
4194
1804
|
const child = spawn(
|
|
4195
1805
|
"claude",
|
|
4196
1806
|
[
|
|
4197
1807
|
"-p",
|
|
4198
|
-
|
|
1808
|
+
"--dangerously-skip-permissions",
|
|
4199
1809
|
"--allowedTools",
|
|
4200
1810
|
"Read",
|
|
1811
|
+
"--allowedTools",
|
|
4201
1812
|
"Glob",
|
|
1813
|
+
"--allowedTools",
|
|
4202
1814
|
"Grep",
|
|
4203
|
-
|
|
4204
|
-
|
|
1815
|
+
"--allowedTools",
|
|
1816
|
+
"Write",
|
|
1817
|
+
"--allowedTools",
|
|
1818
|
+
"Edit"
|
|
4205
1819
|
],
|
|
4206
1820
|
{
|
|
4207
1821
|
cwd: projectDir,
|
|
4208
|
-
stdio: ["
|
|
1822
|
+
stdio: ["pipe", "inherit", "inherit"]
|
|
4209
1823
|
}
|
|
4210
1824
|
);
|
|
1825
|
+
child.stdin.write(prompt);
|
|
1826
|
+
child.stdin.end();
|
|
4211
1827
|
child.on("error", (err) => {
|
|
4212
1828
|
console.error(pc.red(`Failed to launch Claude CLI: ${err.message}`));
|
|
4213
1829
|
resolve(false);
|
|
@@ -4224,6 +1840,24 @@ function runClaudeAnalysis(projectDir, projectInfo) {
|
|
|
4224
1840
|
});
|
|
4225
1841
|
});
|
|
4226
1842
|
}
|
|
1843
|
+
function getGeneratedFiles(projectDir) {
|
|
1844
|
+
const claudeDir = path3.join(projectDir, ".claude");
|
|
1845
|
+
const files = [];
|
|
1846
|
+
function walk(dir) {
|
|
1847
|
+
if (!fs3.existsSync(dir)) return;
|
|
1848
|
+
const entries = fs3.readdirSync(dir, { withFileTypes: true });
|
|
1849
|
+
for (const entry of entries) {
|
|
1850
|
+
const fullPath = path3.join(dir, entry.name);
|
|
1851
|
+
if (entry.isDirectory()) {
|
|
1852
|
+
walk(fullPath);
|
|
1853
|
+
} else {
|
|
1854
|
+
files.push(path3.relative(projectDir, fullPath));
|
|
1855
|
+
}
|
|
1856
|
+
}
|
|
1857
|
+
}
|
|
1858
|
+
walk(claudeDir);
|
|
1859
|
+
return files;
|
|
1860
|
+
}
|
|
4227
1861
|
async function main() {
|
|
4228
1862
|
const args = parseArgs(process.argv.slice(2));
|
|
4229
1863
|
if (args.help) {
|
|
@@ -4249,6 +1883,18 @@ async function main() {
|
|
|
4249
1883
|
projectInfo.techStack.primaryFramework = preferences.framework;
|
|
4250
1884
|
projectInfo.techStack.frameworks = [preferences.framework];
|
|
4251
1885
|
}
|
|
1886
|
+
if (preferences.packageManager) {
|
|
1887
|
+
projectInfo.techStack.packageManager = preferences.packageManager;
|
|
1888
|
+
}
|
|
1889
|
+
if (preferences.testingFramework) {
|
|
1890
|
+
projectInfo.techStack.testingFramework = preferences.testingFramework;
|
|
1891
|
+
}
|
|
1892
|
+
if (preferences.linter) {
|
|
1893
|
+
projectInfo.techStack.linter = preferences.linter;
|
|
1894
|
+
}
|
|
1895
|
+
if (preferences.formatter) {
|
|
1896
|
+
projectInfo.techStack.formatter = preferences.formatter;
|
|
1897
|
+
}
|
|
4252
1898
|
projectInfo.description = preferences.description;
|
|
4253
1899
|
}
|
|
4254
1900
|
} else {
|
|
@@ -4277,28 +1923,12 @@ async function main() {
|
|
|
4277
1923
|
console.error(pc.gray("Install it from: https://claude.ai/download"));
|
|
4278
1924
|
process.exit(1);
|
|
4279
1925
|
}
|
|
4280
|
-
console.log(pc.gray("
|
|
1926
|
+
console.log(pc.gray("Setting up .claude/ directory structure..."));
|
|
4281
1927
|
console.log();
|
|
4282
|
-
|
|
4283
|
-
|
|
4284
|
-
|
|
4285
|
-
|
|
4286
|
-
for (const file of created) {
|
|
4287
|
-
console.log(pc.green(` + ${file}`));
|
|
4288
|
-
}
|
|
4289
|
-
}
|
|
4290
|
-
if (updated.length > 0) {
|
|
4291
|
-
console.log(pc.blue("Updated:"));
|
|
4292
|
-
for (const file of updated) {
|
|
4293
|
-
console.log(pc.blue(` ~ ${file}`));
|
|
4294
|
-
}
|
|
4295
|
-
}
|
|
4296
|
-
if (skipped.length > 0 && args.verbose) {
|
|
4297
|
-
console.log(pc.gray("Preserved:"));
|
|
4298
|
-
for (const file of skipped) {
|
|
4299
|
-
console.log(pc.gray(` - ${file}`));
|
|
4300
|
-
}
|
|
4301
|
-
}
|
|
1928
|
+
writeSettings(projectDir, projectInfo.techStack);
|
|
1929
|
+
ensureDirectories(projectDir);
|
|
1930
|
+
console.log(pc.green("Created:"));
|
|
1931
|
+
console.log(pc.green(" + .claude/settings.json"));
|
|
4302
1932
|
console.log();
|
|
4303
1933
|
createTaskFile(projectInfo, preferences);
|
|
4304
1934
|
const success = await runClaudeAnalysis(projectDir, projectInfo);
|
|
@@ -4306,44 +1936,55 @@ async function main() {
|
|
|
4306
1936
|
console.error(pc.red("Claude analysis failed. Please try again."));
|
|
4307
1937
|
process.exit(1);
|
|
4308
1938
|
}
|
|
4309
|
-
const
|
|
1939
|
+
const generatedFiles = getGeneratedFiles(projectDir);
|
|
4310
1940
|
console.log();
|
|
4311
|
-
console.log(pc.green(`Done! (${
|
|
1941
|
+
console.log(pc.green(`Done! (${generatedFiles.length} files)`));
|
|
4312
1942
|
console.log();
|
|
4313
1943
|
console.log(pc.bold("Generated for your stack:"));
|
|
4314
|
-
|
|
4315
|
-
const
|
|
4316
|
-
const
|
|
4317
|
-
const
|
|
1944
|
+
const skills = generatedFiles.filter((f) => f.includes("/skills/"));
|
|
1945
|
+
const agents = generatedFiles.filter((f) => f.includes("/agents/"));
|
|
1946
|
+
const rules = generatedFiles.filter((f) => f.includes("/rules/"));
|
|
1947
|
+
const commands = generatedFiles.filter((f) => f.includes("/commands/"));
|
|
1948
|
+
if (generatedFiles.some((f) => f.endsWith("CLAUDE.md"))) {
|
|
1949
|
+
console.log(pc.cyan(" CLAUDE.md (deep analysis by Claude)"));
|
|
1950
|
+
}
|
|
4318
1951
|
if (skills.length > 0) {
|
|
4319
1952
|
console.log(
|
|
4320
|
-
` ${skills.length} skills (${skills.map((s) => path3.basename(s
|
|
1953
|
+
` ${skills.length} skills (${skills.map((s) => path3.basename(s, ".md")).join(", ")})`
|
|
4321
1954
|
);
|
|
4322
1955
|
}
|
|
4323
1956
|
if (agents.length > 0) {
|
|
4324
1957
|
console.log(
|
|
4325
|
-
` ${agents.length} agents (${agents.map((a) => path3.basename(a
|
|
1958
|
+
` ${agents.length} agents (${agents.map((a) => path3.basename(a, ".md")).join(", ")})`
|
|
4326
1959
|
);
|
|
4327
1960
|
}
|
|
4328
1961
|
if (rules.length > 0) {
|
|
4329
1962
|
console.log(` ${rules.length} rules`);
|
|
4330
1963
|
}
|
|
1964
|
+
if (commands.length > 0) {
|
|
1965
|
+
console.log(` ${commands.length} commands`);
|
|
1966
|
+
}
|
|
4331
1967
|
console.log();
|
|
4332
1968
|
console.log(`${pc.cyan("Next step:")} Run ${pc.bold("claude")} to start working!`);
|
|
4333
1969
|
console.log();
|
|
4334
1970
|
console.log(
|
|
4335
|
-
pc.gray(
|
|
1971
|
+
pc.gray(
|
|
1972
|
+
"Your .claude/ files were generated by deep analysis - review them with: ls -la .claude/"
|
|
1973
|
+
)
|
|
4336
1974
|
);
|
|
4337
1975
|
}
|
|
4338
|
-
|
|
4339
|
-
|
|
4340
|
-
|
|
4341
|
-
|
|
4342
|
-
|
|
4343
|
-
|
|
4344
|
-
|
|
4345
|
-
|
|
4346
|
-
|
|
1976
|
+
try {
|
|
1977
|
+
const isMain = process.argv[1] && fs3.realpathSync(process.argv[1]) === fileURLToPath(import.meta.url);
|
|
1978
|
+
if (isMain) {
|
|
1979
|
+
main().catch((err) => {
|
|
1980
|
+
console.error(pc.red("Error:"), err.message);
|
|
1981
|
+
if (process.env.DEBUG) {
|
|
1982
|
+
console.error(err.stack);
|
|
1983
|
+
}
|
|
1984
|
+
process.exit(1);
|
|
1985
|
+
});
|
|
1986
|
+
}
|
|
1987
|
+
} catch {
|
|
4347
1988
|
}
|
|
4348
1989
|
export {
|
|
4349
1990
|
checkClaudeCli,
|