@codyswann/lisa 1.47.1 → 1.48.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (210) hide show
  1. package/all/copy-overwrite/.claude/rules/lisa.md +23 -10
  2. package/all/copy-overwrite/.claude/settings.json +10 -252
  3. package/all/deletions.json +67 -1
  4. package/cdk/copy-overwrite/.claude/settings.json +80 -0
  5. package/cdk/create-only/.github/workflows/ci.yml +1 -1
  6. package/cdk/create-only/.github/workflows/deploy.yml +1 -1
  7. package/dist/core/lisa.d.ts +14 -0
  8. package/dist/core/lisa.d.ts.map +1 -1
  9. package/dist/core/lisa.js +47 -0
  10. package/dist/core/lisa.js.map +1 -1
  11. package/expo/copy-overwrite/.claude/settings.json +80 -0
  12. package/expo/copy-overwrite/eslint.expo.ts +2 -2
  13. package/expo/create-only/.github/workflows/ci.yml +1 -1
  14. package/expo/create-only/.github/workflows/deploy.yml +1 -1
  15. package/expo/deletions.json +33 -0
  16. package/expo/package-lisa/package.lisa.json +2 -2
  17. package/nestjs/copy-overwrite/.claude/settings.json +80 -0
  18. package/nestjs/create-only/.github/workflows/ci.yml +1 -1
  19. package/nestjs/create-only/.github/workflows/deploy.yml +1 -1
  20. package/nestjs/deletions.json +8 -0
  21. package/package.json +8 -4
  22. package/rails/copy-overwrite/.claude/settings.json +80 -0
  23. package/rails/create-only/.github/workflows/ci.yml +1 -1
  24. package/rails/deletions.json +11 -1
  25. package/typescript/copy-overwrite/.claude/settings.json +13 -253
  26. package/typescript/copy-overwrite/eslint.typescript.ts +1 -1
  27. package/typescript/create-only/.github/workflows/ci.yml +1 -1
  28. package/typescript/deletions.json +12 -1
  29. package/typescript/package-lisa/package.lisa.json +1 -1
  30. package/all/copy-overwrite/.claude/agents/agent-architect.md +0 -310
  31. package/all/copy-overwrite/.claude/agents/architecture-specialist.md +0 -53
  32. package/all/copy-overwrite/.claude/agents/debug-specialist.md +0 -204
  33. package/all/copy-overwrite/.claude/agents/git-history-analyzer.md +0 -183
  34. package/all/copy-overwrite/.claude/agents/hooks-expert.md +0 -74
  35. package/all/copy-overwrite/.claude/agents/implementer.md +0 -54
  36. package/all/copy-overwrite/.claude/agents/learner.md +0 -44
  37. package/all/copy-overwrite/.claude/agents/performance-specialist.md +0 -95
  38. package/all/copy-overwrite/.claude/agents/product-specialist.md +0 -72
  39. package/all/copy-overwrite/.claude/agents/quality-specialist.md +0 -55
  40. package/all/copy-overwrite/.claude/agents/security-specialist.md +0 -58
  41. package/all/copy-overwrite/.claude/agents/skill-evaluator.md +0 -246
  42. package/all/copy-overwrite/.claude/agents/slash-command-architect.md +0 -87
  43. package/all/copy-overwrite/.claude/agents/test-specialist.md +0 -64
  44. package/all/copy-overwrite/.claude/agents/verification-specialist.md +0 -189
  45. package/all/copy-overwrite/.claude/agents/web-search-researcher.md +0 -112
  46. package/all/copy-overwrite/.claude/commands/git/commit-and-submit-pr.md +0 -7
  47. package/all/copy-overwrite/.claude/commands/git/commit-submit-pr-and-verify.md +0 -7
  48. package/all/copy-overwrite/.claude/commands/git/commit-submit-pr-deploy-and-verify.md +0 -7
  49. package/all/copy-overwrite/.claude/commands/git/commit.md +0 -7
  50. package/all/copy-overwrite/.claude/commands/git/prune.md +0 -6
  51. package/all/copy-overwrite/.claude/commands/git/submit-pr.md +0 -7
  52. package/all/copy-overwrite/.claude/commands/jira/create.md +0 -7
  53. package/all/copy-overwrite/.claude/commands/jira/sync.md +0 -7
  54. package/all/copy-overwrite/.claude/commands/jira/verify.md +0 -7
  55. package/all/copy-overwrite/.claude/commands/lisa/review-implementation.md +0 -7
  56. package/all/copy-overwrite/.claude/commands/plan/add-test-coverage.md +0 -7
  57. package/all/copy-overwrite/.claude/commands/plan/create.md +0 -6
  58. package/all/copy-overwrite/.claude/commands/plan/execute.md +0 -7
  59. package/all/copy-overwrite/.claude/commands/plan/fix-linter-error.md +0 -7
  60. package/all/copy-overwrite/.claude/commands/plan/local-code-review.md +0 -6
  61. package/all/copy-overwrite/.claude/commands/plan/lower-code-complexity.md +0 -6
  62. package/all/copy-overwrite/.claude/commands/plan/reduce-max-lines-per-function.md +0 -7
  63. package/all/copy-overwrite/.claude/commands/plan/reduce-max-lines.md +0 -7
  64. package/all/copy-overwrite/.claude/commands/pull-request/review.md +0 -7
  65. package/all/copy-overwrite/.claude/commands/security/zap-scan.md +0 -6
  66. package/all/copy-overwrite/.claude/commands/sonarqube/check.md +0 -6
  67. package/all/copy-overwrite/.claude/commands/sonarqube/fix.md +0 -6
  68. package/all/copy-overwrite/.claude/commands/tasks/load.md +0 -7
  69. package/all/copy-overwrite/.claude/commands/tasks/sync.md +0 -7
  70. package/all/copy-overwrite/.claude/hooks/check-tired-boss.sh +0 -61
  71. package/all/copy-overwrite/.claude/hooks/debug-hook.sh +0 -47
  72. package/all/copy-overwrite/.claude/hooks/enforce-plan-rules.sh +0 -15
  73. package/all/copy-overwrite/.claude/hooks/notify-ntfy.sh +0 -183
  74. package/all/copy-overwrite/.claude/hooks/setup-jira-cli.sh +0 -52
  75. package/all/copy-overwrite/.claude/hooks/sync-tasks.sh +0 -107
  76. package/all/copy-overwrite/.claude/hooks/ticket-sync-reminder.sh +0 -23
  77. package/all/copy-overwrite/.claude/hooks/track-plan-sessions.sh +0 -164
  78. package/all/copy-overwrite/.claude/hooks/verify-completion.sh +0 -77
  79. package/all/copy-overwrite/.claude/rules/coding-philosophy.md +0 -428
  80. package/all/copy-overwrite/.claude/rules/verfication.md +0 -596
  81. package/all/copy-overwrite/.claude/skills/agent-design-best-practices/SKILL.md +0 -219
  82. package/all/copy-overwrite/.claude/skills/git-commit/SKILL.md +0 -48
  83. package/all/copy-overwrite/.claude/skills/git-commit-and-submit-pr/SKILL.md +0 -8
  84. package/all/copy-overwrite/.claude/skills/git-commit-submit-pr-and-verify/SKILL.md +0 -7
  85. package/all/copy-overwrite/.claude/skills/git-commit-submit-pr-deploy-and-verify/SKILL.md +0 -7
  86. package/all/copy-overwrite/.claude/skills/git-prune/SKILL.md +0 -35
  87. package/all/copy-overwrite/.claude/skills/git-submit-pr/SKILL.md +0 -44
  88. package/all/copy-overwrite/.claude/skills/jira-create/SKILL.md +0 -41
  89. package/all/copy-overwrite/.claude/skills/jira-sync/SKILL.md +0 -63
  90. package/all/copy-overwrite/.claude/skills/jira-verify/SKILL.md +0 -29
  91. package/all/copy-overwrite/.claude/skills/lisa-review-implementation/SKILL.md +0 -209
  92. package/all/copy-overwrite/.claude/skills/plan-add-test-coverage/SKILL.md +0 -44
  93. package/all/copy-overwrite/.claude/skills/plan-execute/SKILL.md +0 -89
  94. package/all/copy-overwrite/.claude/skills/plan-fix-linter-error/SKILL.md +0 -45
  95. package/all/copy-overwrite/.claude/skills/plan-local-code-review/SKILL.md +0 -88
  96. package/all/copy-overwrite/.claude/skills/plan-lower-code-complexity/SKILL.md +0 -44
  97. package/all/copy-overwrite/.claude/skills/plan-reduce-max-lines/SKILL.md +0 -45
  98. package/all/copy-overwrite/.claude/skills/plan-reduce-max-lines-per-function/SKILL.md +0 -46
  99. package/all/copy-overwrite/.claude/skills/pull-request-review/SKILL.md +0 -68
  100. package/all/copy-overwrite/.claude/skills/security-zap-scan/SKILL.md +0 -33
  101. package/all/copy-overwrite/.claude/skills/skill-creator/LICENSE.txt +0 -202
  102. package/all/copy-overwrite/.claude/skills/skill-creator/SKILL.md +0 -210
  103. package/all/copy-overwrite/.claude/skills/skill-creator/scripts/__pycache__/quick_validate.cpython-312.pyc +0 -0
  104. package/all/copy-overwrite/.claude/skills/skill-creator/scripts/init_skill.py +0 -305
  105. package/all/copy-overwrite/.claude/skills/skill-creator/scripts/package_skill.py +0 -112
  106. package/all/copy-overwrite/.claude/skills/skill-creator/scripts/quick_validate.py +0 -67
  107. package/all/copy-overwrite/.claude/skills/sonarqube-check/SKILL.md +0 -11
  108. package/all/copy-overwrite/.claude/skills/sonarqube-fix/SKILL.md +0 -8
  109. package/all/copy-overwrite/.claude/skills/tasks-load/SKILL.md +0 -88
  110. package/all/copy-overwrite/.claude/skills/tasks-sync/SKILL.md +0 -108
  111. package/eslint-plugin-code-organization/README.md +0 -149
  112. package/eslint-plugin-code-organization/__tests__/enforce-statement-order.test.js +0 -473
  113. package/eslint-plugin-code-organization/index.js +0 -28
  114. package/eslint-plugin-code-organization/package.json +0 -10
  115. package/eslint-plugin-code-organization/rules/enforce-statement-order.js +0 -162
  116. package/expo/copy-overwrite/.claude/agents/ops-specialist.md +0 -124
  117. package/expo/copy-overwrite/.claude/rules/expo-verification.md +0 -261
  118. package/expo/copy-overwrite/.claude/skills/apollo-client/SKILL.md +0 -238
  119. package/expo/copy-overwrite/.claude/skills/apollo-client/references/mutation-patterns.md +0 -360
  120. package/expo/copy-overwrite/.claude/skills/atomic-design-gluestack/SKILL.md +0 -360
  121. package/expo/copy-overwrite/.claude/skills/atomic-design-gluestack/references/atomic-levels.md +0 -417
  122. package/expo/copy-overwrite/.claude/skills/atomic-design-gluestack/references/folder-structure.md +0 -257
  123. package/expo/copy-overwrite/.claude/skills/atomic-design-gluestack/references/gluestack-mapping.md +0 -233
  124. package/expo/copy-overwrite/.claude/skills/atomic-design-gluestack/scripts/validate_atomic_structure.py +0 -329
  125. package/expo/copy-overwrite/.claude/skills/container-view-pattern/SKILL.md +0 -299
  126. package/expo/copy-overwrite/.claude/skills/container-view-pattern/references/examples.md +0 -749
  127. package/expo/copy-overwrite/.claude/skills/container-view-pattern/references/patterns.md +0 -318
  128. package/expo/copy-overwrite/.claude/skills/container-view-pattern/scripts/create_component.py +0 -200
  129. package/expo/copy-overwrite/.claude/skills/container-view-pattern/scripts/validate_component.py +0 -209
  130. package/expo/copy-overwrite/.claude/skills/cross-platform-compatibility/SKILL.md +0 -268
  131. package/expo/copy-overwrite/.claude/skills/cross-platform-compatibility/references/common-issues.md +0 -619
  132. package/expo/copy-overwrite/.claude/skills/cross-platform-compatibility/references/file-extensions.md +0 -340
  133. package/expo/copy-overwrite/.claude/skills/cross-platform-compatibility/references/platform-api.md +0 -276
  134. package/expo/copy-overwrite/.claude/skills/cross-platform-compatibility/scripts/validate_cross_platform.py +0 -416
  135. package/expo/copy-overwrite/.claude/skills/directory-structure/SKILL.md +0 -202
  136. package/expo/copy-overwrite/.claude/skills/directory-structure/scripts/validate_structure.py +0 -445
  137. package/expo/copy-overwrite/.claude/skills/expo-env-config/SKILL.md +0 -309
  138. package/expo/copy-overwrite/.claude/skills/expo-env-config/references/validation-patterns.md +0 -417
  139. package/expo/copy-overwrite/.claude/skills/expo-router-best-practices/SKILL.md +0 -431
  140. package/expo/copy-overwrite/.claude/skills/expo-router-best-practices/references/official-docs.md +0 -290
  141. package/expo/copy-overwrite/.claude/skills/expo-router-best-practices/scripts/generate-route.py +0 -171
  142. package/expo/copy-overwrite/.claude/skills/gluestack-nativewind/SKILL.md +0 -411
  143. package/expo/copy-overwrite/.claude/skills/gluestack-nativewind/references/color-tokens.md +0 -343
  144. package/expo/copy-overwrite/.claude/skills/gluestack-nativewind/references/component-mapping.md +0 -307
  145. package/expo/copy-overwrite/.claude/skills/gluestack-nativewind/references/spacing-scale.md +0 -300
  146. package/expo/copy-overwrite/.claude/skills/gluestack-nativewind/scripts/validate_styling.py +0 -315
  147. package/expo/copy-overwrite/.claude/skills/local-state/SKILL.md +0 -362
  148. package/expo/copy-overwrite/.claude/skills/local-state/references/async-storage.md +0 -505
  149. package/expo/copy-overwrite/.claude/skills/local-state/references/persistence-patterns.md +0 -711
  150. package/expo/copy-overwrite/.claude/skills/local-state/references/reactive-variables.md +0 -446
  151. package/expo/copy-overwrite/.claude/skills/ops-browser-uat/SKILL.md +0 -124
  152. package/expo/copy-overwrite/.claude/skills/ops-check-logs/SKILL.md +0 -211
  153. package/expo/copy-overwrite/.claude/skills/ops-db-ops/SKILL.md +0 -119
  154. package/expo/copy-overwrite/.claude/skills/ops-deploy/SKILL.md +0 -119
  155. package/expo/copy-overwrite/.claude/skills/ops-monitor-errors/SKILL.md +0 -99
  156. package/expo/copy-overwrite/.claude/skills/ops-performance/SKILL.md +0 -165
  157. package/expo/copy-overwrite/.claude/skills/ops-run-local/SKILL.md +0 -166
  158. package/expo/copy-overwrite/.claude/skills/ops-verify-health/SKILL.md +0 -101
  159. package/expo/copy-overwrite/.claude/skills/owasp-zap/SKILL.md +0 -56
  160. package/expo/copy-overwrite/.claude/skills/playwright-selectors/SKILL.md +0 -223
  161. package/expo/copy-overwrite/.claude/skills/testing-library/SKILL.md +0 -314
  162. package/expo/copy-overwrite/.claude/skills/testing-library/references/async-patterns.md +0 -420
  163. package/expo/copy-overwrite/.claude/skills/testing-library/references/expo-router-testing.md +0 -556
  164. package/expo/copy-overwrite/.claude/skills/testing-library/references/mocking-patterns.md +0 -590
  165. package/expo/copy-overwrite/.claude/skills/testing-library/references/query-priority.md +0 -291
  166. package/expo/copy-overwrite/eslint-plugin-component-structure/README.md +0 -234
  167. package/expo/copy-overwrite/eslint-plugin-component-structure/__tests__/plugin-index.test.js +0 -89
  168. package/expo/copy-overwrite/eslint-plugin-component-structure/__tests__/require-memo-in-view.test.js +0 -201
  169. package/expo/copy-overwrite/eslint-plugin-component-structure/__tests__/single-component-per-file.test.js +0 -294
  170. package/expo/copy-overwrite/eslint-plugin-component-structure/index.js +0 -37
  171. package/expo/copy-overwrite/eslint-plugin-component-structure/package.json +0 -10
  172. package/expo/copy-overwrite/eslint-plugin-component-structure/rules/enforce-component-structure.js +0 -235
  173. package/expo/copy-overwrite/eslint-plugin-component-structure/rules/no-return-in-view.js +0 -96
  174. package/expo/copy-overwrite/eslint-plugin-component-structure/rules/require-memo-in-view.js +0 -183
  175. package/expo/copy-overwrite/eslint-plugin-component-structure/rules/single-component-per-file.js +0 -243
  176. package/expo/copy-overwrite/eslint-plugin-ui-standards/README.md +0 -192
  177. package/expo/copy-overwrite/eslint-plugin-ui-standards/index.js +0 -31
  178. package/expo/copy-overwrite/eslint-plugin-ui-standards/package.json +0 -10
  179. package/expo/copy-overwrite/eslint-plugin-ui-standards/rules/no-classname-outside-ui.js +0 -56
  180. package/expo/copy-overwrite/eslint-plugin-ui-standards/rules/no-direct-rn-imports.js +0 -60
  181. package/nestjs/copy-overwrite/.claude/skills/nestjs-graphql/SKILL.md +0 -176
  182. package/nestjs/copy-overwrite/.claude/skills/nestjs-graphql/references/advanced-features.md +0 -527
  183. package/nestjs/copy-overwrite/.claude/skills/nestjs-graphql/references/project-patterns.md +0 -483
  184. package/nestjs/copy-overwrite/.claude/skills/nestjs-graphql/references/quick-start.md +0 -257
  185. package/nestjs/copy-overwrite/.claude/skills/nestjs-graphql/references/resolvers-mutations.md +0 -413
  186. package/nestjs/copy-overwrite/.claude/skills/nestjs-graphql/references/types-scalars.md +0 -513
  187. package/nestjs/copy-overwrite/.claude/skills/nestjs-rules/SKILL.md +0 -536
  188. package/nestjs/copy-overwrite/.claude/skills/security-zap-scan/SKILL.md +0 -33
  189. package/nestjs/copy-overwrite/.claude/skills/typeorm-patterns/SKILL.md +0 -275
  190. package/nestjs/copy-overwrite/.claude/skills/typeorm-patterns/references/configuration-patterns.md +0 -487
  191. package/nestjs/copy-overwrite/.claude/skills/typeorm-patterns/references/entity-patterns.md +0 -450
  192. package/nestjs/copy-overwrite/.claude/skills/typeorm-patterns/references/observability-patterns.md +0 -536
  193. package/rails/copy-overwrite/.claude/skills/action-controller-best-practices/SKILL.md +0 -374
  194. package/rails/copy-overwrite/.claude/skills/action-view-best-practices/SKILL.md +0 -335
  195. package/rails/copy-overwrite/.claude/skills/active-record-model-best-practices/SKILL.md +0 -166
  196. package/rails/copy-overwrite/.claude/skills/plan-add-test-coverage/SKILL.md +0 -45
  197. package/rails/copy-overwrite/.claude/skills/plan-fix-linter-error/SKILL.md +0 -45
  198. package/rails/copy-overwrite/.claude/skills/plan-lower-code-complexity/SKILL.md +0 -48
  199. package/rails/copy-overwrite/.claude/skills/plan-reduce-max-lines/SKILL.md +0 -46
  200. package/rails/copy-overwrite/.claude/skills/plan-reduce-max-lines-per-function/SKILL.md +0 -46
  201. package/typescript/copy-overwrite/.claude/hooks/format-on-edit.sh +0 -76
  202. package/typescript/copy-overwrite/.claude/hooks/install-pkgs.sh +0 -64
  203. package/typescript/copy-overwrite/.claude/hooks/lint-on-edit.sh +0 -81
  204. package/typescript/copy-overwrite/.claude/hooks/sg-scan-on-edit.sh +0 -68
  205. package/typescript/copy-overwrite/.claude/skills/jsdoc-best-practices/SKILL.md +0 -432
  206. package/typescript/copy-overwrite/eslint-plugin-code-organization/README.md +0 -149
  207. package/typescript/copy-overwrite/eslint-plugin-code-organization/__tests__/enforce-statement-order.test.js +0 -473
  208. package/typescript/copy-overwrite/eslint-plugin-code-organization/index.js +0 -28
  209. package/typescript/copy-overwrite/eslint-plugin-code-organization/package.json +0 -10
  210. package/typescript/copy-overwrite/eslint-plugin-code-organization/rules/enforce-statement-order.js +0 -162
@@ -1,445 +0,0 @@
1
- #!/usr/bin/env python3
2
- # This file is managed by Lisa.
3
- # Do not edit directly — changes will be overwritten on the next `lisa` run.
4
- """
5
- Directory Structure Validation Script
6
-
7
- Validates that files and directories follow the project's documented structure:
8
- - Feature module structure (components/, screens/, hooks/, etc.)
9
- - Container/View pattern compliance
10
- - Test file placement in __tests__/ directories
11
- - Route file thin wrapper pattern
12
- - Proper naming conventions
13
-
14
- Usage:
15
- python3 validate_structure.py [path]
16
-
17
- If no path provided, validates from current directory.
18
- """
19
-
20
- import os
21
- import re
22
- import sys
23
- from pathlib import Path
24
- from typing import List, Tuple, NamedTuple
25
- from dataclasses import dataclass, field
26
-
27
-
28
- @dataclass
29
- class ValidationResult:
30
- """Result of a validation check."""
31
- passed: bool
32
- message: str
33
- file_path: str = ""
34
- suggestion: str = ""
35
-
36
-
37
- @dataclass
38
- class ValidationReport:
39
- """Complete validation report."""
40
- errors: List[ValidationResult] = field(default_factory=list)
41
- warnings: List[ValidationResult] = field(default_factory=list)
42
- passed: List[ValidationResult] = field(default_factory=list)
43
-
44
- @property
45
- def has_errors(self) -> bool:
46
- return len(self.errors) > 0
47
-
48
- @property
49
- def has_warnings(self) -> bool:
50
- return len(self.warnings) > 0
51
-
52
-
53
- # Directories that should contain __tests__/ subdirectories for test files
54
- TESTABLE_DIRS = {'hooks', 'utils', 'stores', 'providers'}
55
-
56
- # Valid feature subdirectories
57
- FEATURE_SUBDIRS = {'components', 'screens', 'hooks', 'stores', 'utils', 'types', 'constants', 'config'}
58
-
59
- # Files that can exist at feature root level
60
- FEATURE_ROOT_FILES = {'types.ts', 'constants.ts', 'operations.graphql', 'index.ts', 'index.tsx'}
61
-
62
- # Pattern for Container/View files
63
- CONTAINER_PATTERN = re.compile(r'^([A-Z][a-zA-Z0-9]*)Container\.tsx$')
64
- VIEW_PATTERN = re.compile(r'^([A-Z][a-zA-Z0-9]*)View\.tsx$')
65
-
66
- # Directories to skip during validation
67
- SKIP_DIRS = {
68
- 'node_modules', '.git', 'dist', 'build', '.expo',
69
- '.next', 'coverage', '__pycache__', '.claude'
70
- }
71
-
72
-
73
- def find_project_root(start_path: Path) -> Path:
74
- """Find the project root by looking for package.json."""
75
- current = start_path.resolve()
76
- while current != current.parent:
77
- if (current / 'package.json').exists():
78
- return current
79
- current = current.parent
80
- return start_path.resolve()
81
-
82
-
83
- def validate_test_file_placement(root: Path) -> List[ValidationResult]:
84
- """Check that test files are in __tests__/ subdirectories."""
85
- results = []
86
- test_pattern = re.compile(r'\.(test|spec)\.(ts|tsx|js|jsx)$')
87
-
88
- for dirpath, dirnames, filenames in os.walk(root):
89
- # Skip excluded directories
90
- dirnames[:] = [d for d in dirnames if d not in SKIP_DIRS]
91
-
92
- path = Path(dirpath)
93
-
94
- # Skip if we're inside __tests__ directory
95
- if '__tests__' in path.parts:
96
- continue
97
-
98
- # Skip e2e directory (has different structure)
99
- if 'e2e' in path.parts:
100
- continue
101
-
102
- for filename in filenames:
103
- if test_pattern.search(filename):
104
- file_path = path / filename
105
- rel_path = file_path.relative_to(root)
106
-
107
- # Check if parent directory should have __tests__
108
- parent_name = path.name
109
-
110
- results.append(ValidationResult(
111
- passed=False,
112
- message=f"Test file not in __tests__/ directory",
113
- file_path=str(rel_path),
114
- suggestion=f"Move to {path}/__tests__/{filename}"
115
- ))
116
-
117
- return results
118
-
119
-
120
- def validate_component_structure(component_dir: Path, root: Path) -> List[ValidationResult]:
121
- """Validate Container/View pattern in a component directory."""
122
- results = []
123
- rel_path = component_dir.relative_to(root)
124
-
125
- if not component_dir.is_dir():
126
- return results
127
-
128
- files = list(component_dir.iterdir())
129
- file_names = [f.name for f in files if f.is_file()]
130
-
131
- component_name = component_dir.name
132
-
133
- expected_container = f"{component_name}Container.tsx"
134
- expected_view = f"{component_name}View.tsx"
135
- expected_index = "index.tsx"
136
-
137
- has_container = expected_container in file_names
138
- has_view = expected_view in file_names
139
- has_index = expected_index in file_names or "index.ts" in file_names
140
-
141
- # Check for Container
142
- if not has_container:
143
- # Check for any Container file
144
- containers = [f for f in file_names if CONTAINER_PATTERN.match(f)]
145
- if containers:
146
- results.append(ValidationResult(
147
- passed=False,
148
- message=f"Container file name mismatch",
149
- file_path=str(rel_path),
150
- suggestion=f"Rename {containers[0]} to {expected_container}"
151
- ))
152
- else:
153
- results.append(ValidationResult(
154
- passed=False,
155
- message=f"Missing Container file",
156
- file_path=str(rel_path),
157
- suggestion=f"Create {expected_container}"
158
- ))
159
-
160
- # Check for View
161
- if not has_view:
162
- views = [f for f in file_names if VIEW_PATTERN.match(f)]
163
- if views:
164
- results.append(ValidationResult(
165
- passed=False,
166
- message=f"View file name mismatch",
167
- file_path=str(rel_path),
168
- suggestion=f"Rename {views[0]} to {expected_view}"
169
- ))
170
- else:
171
- results.append(ValidationResult(
172
- passed=False,
173
- message=f"Missing View file",
174
- file_path=str(rel_path),
175
- suggestion=f"Create {expected_view}"
176
- ))
177
-
178
- # Check for index
179
- if not has_index:
180
- results.append(ValidationResult(
181
- passed=False,
182
- message=f"Missing index.tsx",
183
- file_path=str(rel_path),
184
- suggestion=f"Create index.tsx that exports Container"
185
- ))
186
-
187
- return results
188
-
189
-
190
- def validate_feature_structure(feature_dir: Path, root: Path) -> List[ValidationResult]:
191
- """Validate feature module structure."""
192
- results = []
193
- rel_path = feature_dir.relative_to(root)
194
-
195
- if not feature_dir.is_dir():
196
- return results
197
-
198
- # Check components directory
199
- components_dir = feature_dir / 'components'
200
- if components_dir.exists():
201
- for item in components_dir.iterdir():
202
- if item.is_dir() and not item.name.startswith('_'):
203
- results.extend(validate_component_structure(item, root))
204
-
205
- # Check screens directory
206
- screens_dir = feature_dir / 'screens'
207
- if screens_dir.exists():
208
- for item in screens_dir.iterdir():
209
- if item.is_dir() and not item.name.startswith('_'):
210
- results.extend(validate_component_structure(item, root))
211
-
212
- # Check hooks have __tests__ if there are test files
213
- hooks_dir = feature_dir / 'hooks'
214
- if hooks_dir.exists():
215
- tests_dir = hooks_dir / '__tests__'
216
- hook_files = [f for f in hooks_dir.iterdir() if f.is_file() and f.suffix in {'.ts', '.tsx'}]
217
- if hook_files and not tests_dir.exists():
218
- results.append(ValidationResult(
219
- passed=False,
220
- message=f"Missing __tests__/ directory for hooks",
221
- file_path=str(rel_path / 'hooks'),
222
- suggestion=f"Create {rel_path}/hooks/__tests__/ for test files"
223
- ))
224
-
225
- # Check utils have __tests__ if there are test files
226
- utils_dir = feature_dir / 'utils'
227
- if utils_dir.exists():
228
- tests_dir = utils_dir / '__tests__'
229
- util_files = [f for f in utils_dir.iterdir() if f.is_file() and f.suffix in {'.ts', '.tsx'}]
230
- if util_files and not tests_dir.exists():
231
- results.append(ValidationResult(
232
- passed=False,
233
- message=f"Missing __tests__/ directory for utils",
234
- file_path=str(rel_path / 'utils'),
235
- suggestion=f"Create {rel_path}/utils/__tests__/ for test files"
236
- ))
237
-
238
- return results
239
-
240
-
241
- def validate_app_directory(app_dir: Path, root: Path) -> List[ValidationResult]:
242
- """Validate that app/ directory only contains thin route wrappers."""
243
- results = []
244
-
245
- if not app_dir.exists():
246
- return results
247
-
248
- # Patterns that indicate non-wrapper code
249
- business_logic_patterns = [
250
- (re.compile(r'useState\s*\('), "useState hook usage"),
251
- (re.compile(r'useEffect\s*\('), "useEffect hook usage"),
252
- (re.compile(r'useCallback\s*\('), "useCallback hook usage"),
253
- (re.compile(r'useMemo\s*\('), "useMemo hook usage"),
254
- (re.compile(r'useQuery\s*\('), "useQuery hook usage"),
255
- (re.compile(r'useMutation\s*\('), "useMutation hook usage"),
256
- ]
257
-
258
- # Files that are allowed (layout files, etc.)
259
- allowed_patterns = {'_layout.tsx', '_layout.ts', '+not-found.tsx', '+html.tsx'}
260
-
261
- for dirpath, dirnames, filenames in os.walk(app_dir):
262
- dirnames[:] = [d for d in dirnames if d not in SKIP_DIRS]
263
- path = Path(dirpath)
264
-
265
- for filename in filenames:
266
- if not filename.endswith(('.tsx', '.ts')):
267
- continue
268
-
269
- if filename in allowed_patterns:
270
- continue
271
-
272
- if filename.startswith('_') or filename.startswith('+'):
273
- continue
274
-
275
- file_path = path / filename
276
- rel_path = file_path.relative_to(root)
277
-
278
- try:
279
- content = file_path.read_text()
280
-
281
- for pattern, description in business_logic_patterns:
282
- if pattern.search(content):
283
- results.append(ValidationResult(
284
- passed=False,
285
- message=f"Route file contains business logic: {description}",
286
- file_path=str(rel_path),
287
- suggestion="Move business logic to features/ directory"
288
- ))
289
- break # Only report first violation per file
290
-
291
- except Exception as e:
292
- pass # Skip files we can't read
293
-
294
- return results
295
-
296
-
297
- def validate_naming_conventions(root: Path) -> List[ValidationResult]:
298
- """Validate file and directory naming conventions."""
299
- results = []
300
-
301
- # Feature directory names should be kebab-case
302
- features_dir = root / 'features'
303
- if features_dir.exists():
304
- kebab_pattern = re.compile(r'^[a-z][a-z0-9]*(-[a-z0-9]+)*$')
305
- for item in features_dir.iterdir():
306
- if item.is_dir() and not item.name.startswith('.'):
307
- if not kebab_pattern.match(item.name):
308
- results.append(ValidationResult(
309
- passed=False,
310
- message=f"Feature directory not in kebab-case",
311
- file_path=f"features/{item.name}",
312
- suggestion=f"Rename to kebab-case (e.g., 'my-feature')"
313
- ))
314
-
315
- # Component directories should be PascalCase
316
- pascal_pattern = re.compile(r'^[A-Z][a-zA-Z0-9]*$')
317
-
318
- def check_component_dirs(base_dir: Path, dir_type: str):
319
- if not base_dir.exists():
320
- return
321
- for item in base_dir.iterdir():
322
- if item.is_dir() and not item.name.startswith('_'):
323
- if not pascal_pattern.match(item.name):
324
- rel_path = item.relative_to(root)
325
- results.append(ValidationResult(
326
- passed=False,
327
- message=f"{dir_type} directory not in PascalCase",
328
- file_path=str(rel_path),
329
- suggestion=f"Rename to PascalCase (e.g., 'MyComponent')"
330
- ))
331
-
332
- # Check components directory (skip special subdirectories)
333
- components_dir = root / 'components'
334
- if components_dir.exists():
335
- for item in components_dir.iterdir():
336
- # Skip special directories that are allowed to be lowercase
337
- if item.name in {'ui', 'icons', 'custom', 'shared'}:
338
- continue
339
- if item.is_dir() and not item.name.startswith('_'):
340
- if not pascal_pattern.match(item.name):
341
- rel_path = item.relative_to(root)
342
- results.append(ValidationResult(
343
- passed=False,
344
- message=f"Component directory not in PascalCase",
345
- file_path=str(rel_path),
346
- suggestion=f"Rename to PascalCase (e.g., 'MyComponent')"
347
- ))
348
-
349
- # Check feature components and screens
350
- if features_dir.exists():
351
- for feature in features_dir.iterdir():
352
- if feature.is_dir():
353
- check_component_dirs(feature / 'components', 'Component')
354
- check_component_dirs(feature / 'screens', 'Screen')
355
-
356
- return results
357
-
358
-
359
- def run_validation(path: Path) -> ValidationReport:
360
- """Run all validations and return a report."""
361
- report = ValidationReport()
362
- root = find_project_root(path)
363
-
364
- print(f"Validating directory structure from: {root}\n")
365
-
366
- # Run all validations
367
- report.errors.extend(validate_test_file_placement(root))
368
- report.errors.extend(validate_naming_conventions(root))
369
- report.errors.extend(validate_app_directory(root / 'app', root))
370
-
371
- # Validate features
372
- features_dir = root / 'features'
373
- if features_dir.exists():
374
- for feature in features_dir.iterdir():
375
- if feature.is_dir() and not feature.name.startswith('.'):
376
- report.errors.extend(validate_feature_structure(feature, root))
377
-
378
- # Validate global components
379
- components_dir = root / 'components'
380
- if components_dir.exists():
381
- for item in components_dir.iterdir():
382
- # Skip ui/, icons/, custom/ as they have different structure
383
- if item.name in {'ui', 'icons', 'custom', 'shared'}:
384
- continue
385
- if item.is_dir() and not item.name.startswith('_'):
386
- report.errors.extend(validate_component_structure(item, root))
387
-
388
- return report
389
-
390
-
391
- def print_report(report: ValidationReport) -> None:
392
- """Print the validation report."""
393
- if report.errors:
394
- print("=" * 60)
395
- print(f"ERRORS ({len(report.errors)})")
396
- print("=" * 60)
397
- for result in report.errors:
398
- print(f"\n❌ {result.message}")
399
- if result.file_path:
400
- print(f" File: {result.file_path}")
401
- if result.suggestion:
402
- print(f" Fix: {result.suggestion}")
403
-
404
- if report.warnings:
405
- print("\n" + "=" * 60)
406
- print(f"WARNINGS ({len(report.warnings)})")
407
- print("=" * 60)
408
- for result in report.warnings:
409
- print(f"\n⚠️ {result.message}")
410
- if result.file_path:
411
- print(f" File: {result.file_path}")
412
- if result.suggestion:
413
- print(f" Fix: {result.suggestion}")
414
-
415
- print("\n" + "=" * 60)
416
- print("SUMMARY")
417
- print("=" * 60)
418
- print(f"Errors: {len(report.errors)}")
419
- print(f"Warnings: {len(report.warnings)}")
420
-
421
- if not report.has_errors and not report.has_warnings:
422
- print("\n✅ All directory structure validations passed!")
423
- elif report.has_errors:
424
- print("\n❌ Validation failed with errors")
425
- else:
426
- print("\n⚠️ Validation passed with warnings")
427
-
428
-
429
- def main():
430
- """Main entry point."""
431
- path = Path(sys.argv[1]) if len(sys.argv) > 1 else Path.cwd()
432
-
433
- if not path.exists():
434
- print(f"Error: Path does not exist: {path}")
435
- sys.exit(1)
436
-
437
- report = run_validation(path)
438
- print_report(report)
439
-
440
- # Exit with error code if there are errors
441
- sys.exit(1 if report.has_errors else 0)
442
-
443
-
444
- if __name__ == '__main__':
445
- main()