@codyswann/lisa 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +867 -0
- package/all/copy-overwrite/.claude/README.md +205 -0
- package/all/copy-overwrite/.claude/agents/agent-architect.md +311 -0
- package/all/copy-overwrite/.claude/agents/codebase-analyzer.md +146 -0
- package/all/copy-overwrite/.claude/agents/codebase-locator.md +125 -0
- package/all/copy-overwrite/.claude/agents/codebase-pattern-finder.md +237 -0
- package/all/copy-overwrite/.claude/agents/git-history-analyzer.md +183 -0
- package/all/copy-overwrite/.claude/agents/hooks-expert.md +74 -0
- package/all/copy-overwrite/.claude/agents/skill-evaluator.md +246 -0
- package/all/copy-overwrite/.claude/agents/slash-command-architect.md +87 -0
- package/all/copy-overwrite/.claude/agents/web-search-researcher.md +112 -0
- package/all/copy-overwrite/.claude/commands/git/commit-and-submit-pr.md +8 -0
- package/all/copy-overwrite/.claude/commands/git/commit.md +44 -0
- package/all/copy-overwrite/.claude/commands/git/prune.md +34 -0
- package/all/copy-overwrite/.claude/commands/git/submit-pr.md +50 -0
- package/all/copy-overwrite/.claude/commands/jira/create.md +50 -0
- package/all/copy-overwrite/.claude/commands/jira/verify.md +34 -0
- package/all/copy-overwrite/.claude/commands/project/archive.md +8 -0
- package/all/copy-overwrite/.claude/commands/project/bootstrap.md +49 -0
- package/all/copy-overwrite/.claude/commands/project/complete-task.md +7 -0
- package/all/copy-overwrite/.claude/commands/project/debrief.md +65 -0
- package/all/copy-overwrite/.claude/commands/project/execute.md +94 -0
- package/all/copy-overwrite/.claude/commands/project/implement.md +42 -0
- package/all/copy-overwrite/.claude/commands/project/local-code-review.md +88 -0
- package/all/copy-overwrite/.claude/commands/project/lower-code-complexity.md +74 -0
- package/all/copy-overwrite/.claude/commands/project/plan.md +314 -0
- package/all/copy-overwrite/.claude/commands/project/research.md +248 -0
- package/all/copy-overwrite/.claude/commands/project/review.md +63 -0
- package/all/copy-overwrite/.claude/commands/project/setup.md +19 -0
- package/all/copy-overwrite/.claude/commands/project/verify.md +38 -0
- package/all/copy-overwrite/.claude/commands/pull-request/review.md +12 -0
- package/all/copy-overwrite/.claude/commands/rules/format-md.md +72 -0
- package/all/copy-overwrite/.claude/commands/sonarqube/check.md +6 -0
- package/all/copy-overwrite/.claude/commands/sonarqube/fix.md +3 -0
- package/all/copy-overwrite/.claude/hooks/README.md +301 -0
- package/all/copy-overwrite/.claude/hooks/notify-ntfy.sh +181 -0
- package/all/copy-overwrite/.claude/settings.json +41 -0
- package/all/copy-overwrite/.claude/settings.local.json.example +14 -0
- package/all/copy-overwrite/.claude/skills/coding-philosophy/SKILL.md +405 -0
- package/all/copy-overwrite/.claude/skills/coding-philosophy/references/function-structure.md +416 -0
- package/all/copy-overwrite/.claude/skills/coding-philosophy/references/immutable-patterns.md +316 -0
- package/all/copy-overwrite/.claude/skills/prompt-complexity-scorer/SKILL.md +118 -0
- package/all/copy-overwrite/.claude/skills/skill-creator/LICENSE.txt +202 -0
- package/all/copy-overwrite/.claude/skills/skill-creator/SKILL.md +210 -0
- package/all/copy-overwrite/.claude/skills/skill-creator/scripts/__pycache__/quick_validate.cpython-312.pyc +0 -0
- package/all/copy-overwrite/.claude/skills/skill-creator/scripts/init_skill.py +303 -0
- package/all/copy-overwrite/.claude/skills/skill-creator/scripts/package_skill.py +110 -0
- package/all/copy-overwrite/.claude/skills/skill-creator/scripts/quick_validate.py +65 -0
- package/all/copy-overwrite/CLAUDE.md +77 -0
- package/all/copy-overwrite/HUMAN.md +17 -0
- package/all/copy-overwrite/specs/.keep +0 -0
- package/all/create-only/PROJECT_RULES.md +0 -0
- package/cdk/merge/package.json +20 -0
- package/dist/cli/index.d.ts +7 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +107 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/prompts.d.ts +45 -0
- package/dist/cli/prompts.d.ts.map +1 -0
- package/dist/cli/prompts.js +58 -0
- package/dist/cli/prompts.js.map +1 -0
- package/dist/core/config.d.ts +73 -0
- package/dist/core/config.d.ts.map +1 -0
- package/dist/core/config.js +36 -0
- package/dist/core/config.js.map +1 -0
- package/dist/core/index.d.ts +4 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +4 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/lisa.d.ts +81 -0
- package/dist/core/lisa.d.ts.map +1 -0
- package/dist/core/lisa.js +459 -0
- package/dist/core/lisa.js.map +1 -0
- package/dist/core/manifest.d.ts +58 -0
- package/dist/core/manifest.d.ts.map +1 -0
- package/dist/core/manifest.js +104 -0
- package/dist/core/manifest.js.map +1 -0
- package/dist/detection/detector.interface.d.ts +15 -0
- package/dist/detection/detector.interface.d.ts.map +1 -0
- package/dist/detection/detector.interface.js +2 -0
- package/dist/detection/detector.interface.js.map +1 -0
- package/dist/detection/detectors/cdk.d.ts +10 -0
- package/dist/detection/detectors/cdk.d.ts.map +1 -0
- package/dist/detection/detectors/cdk.js +34 -0
- package/dist/detection/detectors/cdk.js.map +1 -0
- package/dist/detection/detectors/expo.d.ts +10 -0
- package/dist/detection/detectors/expo.d.ts.map +1 -0
- package/dist/detection/detectors/expo.js +30 -0
- package/dist/detection/detectors/expo.js.map +1 -0
- package/dist/detection/detectors/nestjs.d.ts +10 -0
- package/dist/detection/detectors/nestjs.d.ts.map +1 -0
- package/dist/detection/detectors/nestjs.js +34 -0
- package/dist/detection/detectors/nestjs.js.map +1 -0
- package/dist/detection/detectors/npm-package.d.ts +13 -0
- package/dist/detection/detectors/npm-package.d.ts.map +1 -0
- package/dist/detection/detectors/npm-package.js +30 -0
- package/dist/detection/detectors/npm-package.js.map +1 -0
- package/dist/detection/detectors/typescript.d.ts +10 -0
- package/dist/detection/detectors/typescript.d.ts.map +1 -0
- package/dist/detection/detectors/typescript.js +25 -0
- package/dist/detection/detectors/typescript.js.map +1 -0
- package/dist/detection/index.d.ts +24 -0
- package/dist/detection/index.d.ts.map +1 -0
- package/dist/detection/index.js +57 -0
- package/dist/detection/index.js.map +1 -0
- package/dist/errors/index.d.ts +69 -0
- package/dist/errors/index.d.ts.map +1 -0
- package/dist/errors/index.js +110 -0
- package/dist/errors/index.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/logging/console-logger.d.ts +12 -0
- package/dist/logging/console-logger.d.ts.map +1 -0
- package/dist/logging/console-logger.js +22 -0
- package/dist/logging/console-logger.js.map +1 -0
- package/dist/logging/index.d.ts +4 -0
- package/dist/logging/index.d.ts.map +1 -0
- package/dist/logging/index.js +3 -0
- package/dist/logging/index.js.map +1 -0
- package/dist/logging/logger.interface.d.ts +20 -0
- package/dist/logging/logger.interface.d.ts.map +1 -0
- package/dist/logging/logger.interface.js +2 -0
- package/dist/logging/logger.interface.js.map +1 -0
- package/dist/logging/silent-logger.d.ts +12 -0
- package/dist/logging/silent-logger.d.ts.map +1 -0
- package/dist/logging/silent-logger.js +21 -0
- package/dist/logging/silent-logger.js.map +1 -0
- package/dist/strategies/copy-contents.d.ts +14 -0
- package/dist/strategies/copy-contents.d.ts.map +1 -0
- package/dist/strategies/copy-contents.js +69 -0
- package/dist/strategies/copy-contents.js.map +1 -0
- package/dist/strategies/copy-overwrite.d.ts +14 -0
- package/dist/strategies/copy-overwrite.d.ts.map +1 -0
- package/dist/strategies/copy-overwrite.js +47 -0
- package/dist/strategies/copy-overwrite.js.map +1 -0
- package/dist/strategies/create-only.d.ts +13 -0
- package/dist/strategies/create-only.d.ts.map +1 -0
- package/dist/strategies/create-only.js +30 -0
- package/dist/strategies/create-only.js.map +1 -0
- package/dist/strategies/index.d.ts +31 -0
- package/dist/strategies/index.d.ts.map +1 -0
- package/dist/strategies/index.js +52 -0
- package/dist/strategies/index.js.map +1 -0
- package/dist/strategies/merge.d.ts +13 -0
- package/dist/strategies/merge.d.ts.map +1 -0
- package/dist/strategies/merge.js +60 -0
- package/dist/strategies/merge.js.map +1 -0
- package/dist/strategies/strategy.interface.d.ts +31 -0
- package/dist/strategies/strategy.interface.d.ts.map +1 -0
- package/dist/strategies/strategy.interface.js +2 -0
- package/dist/strategies/strategy.interface.js.map +1 -0
- package/dist/transaction/backup.d.ts +38 -0
- package/dist/transaction/backup.d.ts.map +1 -0
- package/dist/transaction/backup.js +97 -0
- package/dist/transaction/backup.js.map +1 -0
- package/dist/transaction/index.d.ts +4 -0
- package/dist/transaction/index.d.ts.map +1 -0
- package/dist/transaction/index.js +3 -0
- package/dist/transaction/index.js.map +1 -0
- package/dist/transaction/transaction.d.ts +34 -0
- package/dist/transaction/transaction.d.ts.map +1 -0
- package/dist/transaction/transaction.js +68 -0
- package/dist/transaction/transaction.js.map +1 -0
- package/dist/utils/file-operations.d.ts +29 -0
- package/dist/utils/file-operations.d.ts.map +1 -0
- package/dist/utils/file-operations.js +84 -0
- package/dist/utils/file-operations.js.map +1 -0
- package/dist/utils/index.d.ts +4 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +4 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/json-utils.d.ts +22 -0
- package/dist/utils/json-utils.d.ts.map +1 -0
- package/dist/utils/json-utils.js +57 -0
- package/dist/utils/json-utils.js.map +1 -0
- package/dist/utils/path-utils.d.ts +21 -0
- package/dist/utils/path-utils.d.ts.map +1 -0
- package/dist/utils/path-utils.js +35 -0
- package/dist/utils/path-utils.js.map +1 -0
- package/eslint-plugin-code-organization/README.md +149 -0
- package/eslint-plugin-code-organization/__tests__/enforce-statement-order.test.js +468 -0
- package/eslint-plugin-code-organization/index.js +23 -0
- package/eslint-plugin-code-organization/package.json +10 -0
- package/eslint-plugin-code-organization/rules/enforce-statement-order.js +157 -0
- package/expo/copy-overwrite/.claude/skills/apollo-client/SKILL.md +238 -0
- package/expo/copy-overwrite/.claude/skills/apollo-client/references/mutation-patterns.md +360 -0
- package/expo/copy-overwrite/.claude/skills/atomic-design-gluestack/SKILL.md +360 -0
- package/expo/copy-overwrite/.claude/skills/atomic-design-gluestack/references/atomic-levels.md +417 -0
- package/expo/copy-overwrite/.claude/skills/atomic-design-gluestack/references/folder-structure.md +257 -0
- package/expo/copy-overwrite/.claude/skills/atomic-design-gluestack/references/gluestack-mapping.md +233 -0
- package/expo/copy-overwrite/.claude/skills/atomic-design-gluestack/scripts/validate_atomic_structure.py +327 -0
- package/expo/copy-overwrite/.claude/skills/container-view-pattern/SKILL.md +299 -0
- package/expo/copy-overwrite/.claude/skills/container-view-pattern/references/examples.md +749 -0
- package/expo/copy-overwrite/.claude/skills/container-view-pattern/references/patterns.md +318 -0
- package/expo/copy-overwrite/.claude/skills/container-view-pattern/scripts/create_component.py +198 -0
- package/expo/copy-overwrite/.claude/skills/container-view-pattern/scripts/validate_component.py +207 -0
- package/expo/copy-overwrite/.claude/skills/cross-platform-compatibility/SKILL.md +268 -0
- package/expo/copy-overwrite/.claude/skills/cross-platform-compatibility/references/common-issues.md +619 -0
- package/expo/copy-overwrite/.claude/skills/cross-platform-compatibility/references/file-extensions.md +340 -0
- package/expo/copy-overwrite/.claude/skills/cross-platform-compatibility/references/platform-api.md +276 -0
- package/expo/copy-overwrite/.claude/skills/cross-platform-compatibility/scripts/validate_cross_platform.py +414 -0
- package/expo/copy-overwrite/.claude/skills/directory-structure/SKILL.md +202 -0
- package/expo/copy-overwrite/.claude/skills/directory-structure/scripts/validate_structure.py +443 -0
- package/expo/copy-overwrite/.claude/skills/expo-env-config/SKILL.md +309 -0
- package/expo/copy-overwrite/.claude/skills/expo-env-config/references/validation-patterns.md +417 -0
- package/expo/copy-overwrite/.claude/skills/expo-router-best-practices/SKILL.md +431 -0
- package/expo/copy-overwrite/.claude/skills/expo-router-best-practices/references/official-docs.md +290 -0
- package/expo/copy-overwrite/.claude/skills/expo-router-best-practices/scripts/generate-route.py +169 -0
- package/expo/copy-overwrite/.claude/skills/gluestack-nativewind/SKILL.md +411 -0
- package/expo/copy-overwrite/.claude/skills/gluestack-nativewind/references/color-tokens.md +343 -0
- package/expo/copy-overwrite/.claude/skills/gluestack-nativewind/references/component-mapping.md +307 -0
- package/expo/copy-overwrite/.claude/skills/gluestack-nativewind/references/spacing-scale.md +300 -0
- package/expo/copy-overwrite/.claude/skills/gluestack-nativewind/scripts/validate_styling.py +354 -0
- package/expo/copy-overwrite/.claude/skills/local-state/SKILL.md +362 -0
- package/expo/copy-overwrite/.claude/skills/local-state/references/async-storage.md +505 -0
- package/expo/copy-overwrite/.claude/skills/local-state/references/persistence-patterns.md +711 -0
- package/expo/copy-overwrite/.claude/skills/local-state/references/reactive-variables.md +446 -0
- package/expo/copy-overwrite/.claude/skills/playwright-selectors/SKILL.md +223 -0
- package/expo/copy-overwrite/.claude/skills/testing-library/SKILL.md +319 -0
- package/expo/copy-overwrite/.claude/skills/testing-library/references/async-patterns.md +420 -0
- package/expo/copy-overwrite/.claude/skills/testing-library/references/expo-router-testing.md +556 -0
- package/expo/copy-overwrite/.claude/skills/testing-library/references/mocking-patterns.md +590 -0
- package/expo/copy-overwrite/.claude/skills/testing-library/references/query-priority.md +291 -0
- package/expo/copy-overwrite/.easignore.extra +2 -0
- package/expo/copy-overwrite/.mcp.json +33 -0
- package/expo/copy-overwrite/eslint-plugin-component-structure/README.md +234 -0
- package/expo/copy-overwrite/eslint-plugin-component-structure/__tests__/plugin-index.test.js +84 -0
- package/expo/copy-overwrite/eslint-plugin-component-structure/__tests__/require-memo-in-view.test.js +196 -0
- package/expo/copy-overwrite/eslint-plugin-component-structure/__tests__/single-component-per-file.test.js +289 -0
- package/expo/copy-overwrite/eslint-plugin-component-structure/index.js +32 -0
- package/expo/copy-overwrite/eslint-plugin-component-structure/package.json +10 -0
- package/expo/copy-overwrite/eslint-plugin-component-structure/rules/enforce-component-structure.js +230 -0
- package/expo/copy-overwrite/eslint-plugin-component-structure/rules/no-return-in-view.js +91 -0
- package/expo/copy-overwrite/eslint-plugin-component-structure/rules/require-memo-in-view.js +178 -0
- package/expo/copy-overwrite/eslint-plugin-component-structure/rules/single-component-per-file.js +238 -0
- package/expo/copy-overwrite/eslint-plugin-ui-standards/README.md +260 -0
- package/expo/copy-overwrite/eslint-plugin-ui-standards/index.js +29 -0
- package/expo/copy-overwrite/eslint-plugin-ui-standards/package.json +10 -0
- package/expo/copy-overwrite/eslint-plugin-ui-standards/rules/no-classname-outside-ui.js +51 -0
- package/expo/copy-overwrite/eslint-plugin-ui-standards/rules/no-direct-rn-imports.js +55 -0
- package/expo/copy-overwrite/eslint-plugin-ui-standards/rules/no-inline-styles.js +73 -0
- package/expo/copy-overwrite/eslint.config.mjs +560 -0
- package/expo/copy-overwrite/lighthouserc.js +194 -0
- package/expo/create-only/lighthouserc-config.json +28 -0
- package/expo/merge/package.json +132 -0
- package/lisa.sh +35 -0
- package/nestjs/copy-overwrite/.claude/skills/nestjs-graphql/SKILL.md +176 -0
- package/nestjs/copy-overwrite/.claude/skills/nestjs-graphql/references/advanced-features.md +527 -0
- package/nestjs/copy-overwrite/.claude/skills/nestjs-graphql/references/project-patterns.md +483 -0
- package/nestjs/copy-overwrite/.claude/skills/nestjs-graphql/references/quick-start.md +257 -0
- package/nestjs/copy-overwrite/.claude/skills/nestjs-graphql/references/resolvers-mutations.md +413 -0
- package/nestjs/copy-overwrite/.claude/skills/nestjs-graphql/references/types-scalars.md +513 -0
- package/nestjs/copy-overwrite/.claude/skills/nestjs-rules/SKILL.md +536 -0
- package/nestjs/copy-overwrite/.claude/skills/typeorm-patterns/SKILL.md +275 -0
- package/nestjs/copy-overwrite/.claude/skills/typeorm-patterns/references/configuration-patterns.md +487 -0
- package/nestjs/copy-overwrite/.claude/skills/typeorm-patterns/references/entity-patterns.md +450 -0
- package/nestjs/copy-overwrite/.claude/skills/typeorm-patterns/references/observability-patterns.md +536 -0
- package/nestjs/merge/package.json +75 -0
- package/package.json +124 -0
- package/typescript/copy-contents/.husky/commit-msg +91 -0
- package/typescript/copy-contents/.husky/pre-commit +96 -0
- package/typescript/copy-contents/.husky/pre-push +211 -0
- package/typescript/copy-overwrite/.claude/hooks/format-on-edit.sh +74 -0
- package/typescript/copy-overwrite/.claude/hooks/install_pkgs.sh +59 -0
- package/typescript/copy-overwrite/.claude/hooks/lint-on-edit.sh +103 -0
- package/typescript/copy-overwrite/.claude/skills/jsdoc-best-practices/SKILL.md +388 -0
- package/typescript/copy-overwrite/.github/README.md +455 -0
- package/typescript/copy-overwrite/.github/dependabot.yml +40 -0
- package/typescript/copy-overwrite/.github/k6/BROWSER_TESTING_NOTE.md +129 -0
- package/typescript/copy-overwrite/.github/k6/INTEGRATION_GUIDE.md +354 -0
- package/typescript/copy-overwrite/.github/k6/README.md +386 -0
- package/typescript/copy-overwrite/.github/k6/SCENARIO_SELECTION_GUIDE.md +264 -0
- package/typescript/copy-overwrite/.github/k6/examples/customer-deploy-integration.yml +115 -0
- package/typescript/copy-overwrite/.github/k6/examples/data-driven-test.js +268 -0
- package/typescript/copy-overwrite/.github/k6/scenarios/load.js +142 -0
- package/typescript/copy-overwrite/.github/k6/scenarios/load.json +27 -0
- package/typescript/copy-overwrite/.github/k6/scenarios/smoke.js +26 -0
- package/typescript/copy-overwrite/.github/k6/scenarios/smoke.json +20 -0
- package/typescript/copy-overwrite/.github/k6/scenarios/soak.js +244 -0
- package/typescript/copy-overwrite/.github/k6/scenarios/soak.json +29 -0
- package/typescript/copy-overwrite/.github/k6/scenarios/spike.js +180 -0
- package/typescript/copy-overwrite/.github/k6/scenarios/spike.json +32 -0
- package/typescript/copy-overwrite/.github/k6/scenarios/stress.js +206 -0
- package/typescript/copy-overwrite/.github/k6/scenarios/stress.json +38 -0
- package/typescript/copy-overwrite/.github/k6/scripts/api-test.js +452 -0
- package/typescript/copy-overwrite/.github/k6/scripts/default-test.js +185 -0
- package/typescript/copy-overwrite/.github/k6/thresholds/normal.json +30 -0
- package/typescript/copy-overwrite/.github/k6/thresholds/relaxed.json +21 -0
- package/typescript/copy-overwrite/.github/k6/thresholds/strict.json +29 -0
- package/typescript/copy-overwrite/.github/workflows/build.yml +72 -0
- package/typescript/copy-overwrite/.github/workflows/ci.yml +49 -0
- package/typescript/copy-overwrite/.github/workflows/claude.yml +51 -0
- package/typescript/copy-overwrite/.github/workflows/create-github-issue-on-failure.yml +113 -0
- package/typescript/copy-overwrite/.github/workflows/create-jira-issue-on-failure.yml +195 -0
- package/typescript/copy-overwrite/.github/workflows/create-sentry-issue-on-failure.yml +267 -0
- package/typescript/copy-overwrite/.github/workflows/deploy.yml +228 -0
- package/typescript/copy-overwrite/.github/workflows/k6-load-test-README.md +230 -0
- package/typescript/copy-overwrite/.github/workflows/lighthouse.yml +68 -0
- package/typescript/copy-overwrite/.github/workflows/load-test.yml +282 -0
- package/typescript/copy-overwrite/.github/workflows/quality.yml +1737 -0
- package/typescript/copy-overwrite/.github/workflows/release.yml +1599 -0
- package/typescript/copy-overwrite/.gitleaksignore +28 -0
- package/typescript/copy-overwrite/.nvmrc +1 -0
- package/typescript/copy-overwrite/.prettierignore +23 -0
- package/typescript/copy-overwrite/.prettierrc.json +22 -0
- package/typescript/copy-overwrite/.versionrc +42 -0
- package/typescript/copy-overwrite/.yamllint +20 -0
- package/typescript/copy-overwrite/commitlint.config.js +11 -0
- package/typescript/copy-overwrite/eslint-plugin-code-organization/README.md +149 -0
- package/typescript/copy-overwrite/eslint-plugin-code-organization/__tests__/enforce-statement-order.test.js +468 -0
- package/typescript/copy-overwrite/eslint-plugin-code-organization/index.js +23 -0
- package/typescript/copy-overwrite/eslint-plugin-code-organization/package.json +10 -0
- package/typescript/copy-overwrite/eslint-plugin-code-organization/rules/enforce-statement-order.js +157 -0
- package/typescript/copy-overwrite/eslint.config.mjs +390 -0
- package/typescript/copy-overwrite/eslint.ignore.config.json +57 -0
- package/typescript/copy-overwrite/eslint.thresholds.config.json +5 -0
- package/typescript/github-rulesets/base.json +106 -0
- package/typescript/merge/.claude/settings.json +28 -0
- package/typescript/merge/package.json +71 -0
|
@@ -0,0 +1,414 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Cross-Platform Compatibility Validator
|
|
4
|
+
|
|
5
|
+
This script validates Expo/React Native code for cross-platform compatibility issues.
|
|
6
|
+
It checks for:
|
|
7
|
+
1. Platform-specific files in app/ directory without base versions
|
|
8
|
+
2. Platform.OS checks that don't handle all platforms
|
|
9
|
+
3. Web-incompatible API usage without Platform checks
|
|
10
|
+
4. Incomplete Platform.select() usage
|
|
11
|
+
|
|
12
|
+
Usage:
|
|
13
|
+
python3 validate_cross_platform.py [path]
|
|
14
|
+
|
|
15
|
+
path: Optional. Directory or file to validate. Defaults to current directory.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
import os
|
|
19
|
+
import re
|
|
20
|
+
import sys
|
|
21
|
+
from pathlib import Path
|
|
22
|
+
from typing import NamedTuple
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class ValidationIssue(NamedTuple):
|
|
26
|
+
"""Represents a validation issue found in the codebase."""
|
|
27
|
+
|
|
28
|
+
file_path: str
|
|
29
|
+
line_number: int
|
|
30
|
+
issue_type: str
|
|
31
|
+
message: str
|
|
32
|
+
severity: str # "error" or "warning"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
# Platform-specific file extensions
|
|
36
|
+
PLATFORM_EXTENSIONS = [".web", ".native", ".ios", ".android"]
|
|
37
|
+
|
|
38
|
+
# Web-incompatible APIs that require Platform.OS checks
|
|
39
|
+
WEB_INCOMPATIBLE_APIS = [
|
|
40
|
+
"MediaLibrary.saveToLibraryAsync",
|
|
41
|
+
"MediaLibrary.createAssetAsync",
|
|
42
|
+
"MediaLibrary.getAssetsAsync",
|
|
43
|
+
"Haptics.impactAsync",
|
|
44
|
+
"Haptics.notificationAsync",
|
|
45
|
+
"Haptics.selectionAsync",
|
|
46
|
+
"captureRef",
|
|
47
|
+
"SecureStore.getItemAsync",
|
|
48
|
+
"SecureStore.setItemAsync",
|
|
49
|
+
"SecureStore.deleteItemAsync",
|
|
50
|
+
]
|
|
51
|
+
|
|
52
|
+
# Patterns that suggest incomplete platform handling
|
|
53
|
+
INCOMPLETE_PLATFORM_PATTERNS = [
|
|
54
|
+
# Platform.OS === 'web' without else/default
|
|
55
|
+
(
|
|
56
|
+
r"if\s*\(\s*Platform\.OS\s*===?\s*['\"]web['\"]\s*\)\s*\{[^}]+\}(?!\s*else)",
|
|
57
|
+
"Platform.OS check for 'web' without handling other platforms",
|
|
58
|
+
),
|
|
59
|
+
# Platform.OS === 'ios' without android/web handling
|
|
60
|
+
(
|
|
61
|
+
r"if\s*\(\s*Platform\.OS\s*===?\s*['\"]ios['\"]\s*\)\s*\{[^}]+\}(?!\s*else)",
|
|
62
|
+
"Platform.OS check for 'ios' without handling other platforms",
|
|
63
|
+
),
|
|
64
|
+
# Platform.OS === 'android' without ios/web handling
|
|
65
|
+
(
|
|
66
|
+
r"if\s*\(\s*Platform\.OS\s*===?\s*['\"]android['\"]\s*\)\s*\{[^}]+\}(?!\s*else)",
|
|
67
|
+
"Platform.OS check for 'android' without handling other platforms",
|
|
68
|
+
),
|
|
69
|
+
]
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def get_base_filename(filename: str) -> str:
|
|
73
|
+
"""
|
|
74
|
+
Extract the base filename without platform extension.
|
|
75
|
+
|
|
76
|
+
Args:
|
|
77
|
+
filename: The filename to process
|
|
78
|
+
|
|
79
|
+
Returns:
|
|
80
|
+
The base filename without platform extension
|
|
81
|
+
"""
|
|
82
|
+
name = filename
|
|
83
|
+
for ext in PLATFORM_EXTENSIONS:
|
|
84
|
+
if ext in name:
|
|
85
|
+
name = name.replace(ext, "")
|
|
86
|
+
break
|
|
87
|
+
return name
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def has_platform_extension(filename: str) -> bool:
|
|
91
|
+
"""
|
|
92
|
+
Check if a filename has a platform-specific extension.
|
|
93
|
+
|
|
94
|
+
Args:
|
|
95
|
+
filename: The filename to check
|
|
96
|
+
|
|
97
|
+
Returns:
|
|
98
|
+
True if the filename has a platform extension
|
|
99
|
+
"""
|
|
100
|
+
return any(ext in filename for ext in PLATFORM_EXTENSIONS)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def find_orphaned_platform_files(app_dir: Path) -> list[ValidationIssue]:
|
|
104
|
+
"""
|
|
105
|
+
Find platform-specific files in app/ directory without base versions.
|
|
106
|
+
|
|
107
|
+
Args:
|
|
108
|
+
app_dir: Path to the app directory
|
|
109
|
+
|
|
110
|
+
Returns:
|
|
111
|
+
List of validation issues for orphaned files
|
|
112
|
+
"""
|
|
113
|
+
issues = []
|
|
114
|
+
|
|
115
|
+
if not app_dir.exists():
|
|
116
|
+
return issues
|
|
117
|
+
|
|
118
|
+
# Collect all files in app directory
|
|
119
|
+
all_files = set()
|
|
120
|
+
platform_files = []
|
|
121
|
+
|
|
122
|
+
for file_path in app_dir.rglob("*"):
|
|
123
|
+
if file_path.is_file() and file_path.suffix in [".tsx", ".ts", ".jsx", ".js"]:
|
|
124
|
+
relative_path = file_path.relative_to(app_dir)
|
|
125
|
+
all_files.add(str(relative_path))
|
|
126
|
+
|
|
127
|
+
if has_platform_extension(file_path.name):
|
|
128
|
+
platform_files.append(file_path)
|
|
129
|
+
|
|
130
|
+
# Check each platform file for a base version
|
|
131
|
+
for platform_file in platform_files:
|
|
132
|
+
base_name = get_base_filename(platform_file.name)
|
|
133
|
+
relative_dir = platform_file.parent.relative_to(app_dir)
|
|
134
|
+
|
|
135
|
+
# Check if base file exists
|
|
136
|
+
base_path = str(relative_dir / base_name) if str(relative_dir) != "." else base_name
|
|
137
|
+
base_exists = base_path in all_files
|
|
138
|
+
|
|
139
|
+
if not base_exists:
|
|
140
|
+
issues.append(
|
|
141
|
+
ValidationIssue(
|
|
142
|
+
file_path=str(platform_file),
|
|
143
|
+
line_number=0,
|
|
144
|
+
issue_type="orphaned_platform_file",
|
|
145
|
+
message=f"Platform-specific file '{platform_file.name}' in app/ directory "
|
|
146
|
+
f"has no base version '{base_name}'. Routes must be universal for deep linking.",
|
|
147
|
+
severity="error",
|
|
148
|
+
)
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
return issues
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def check_web_incompatible_apis(file_path: Path) -> list[ValidationIssue]:
|
|
155
|
+
"""
|
|
156
|
+
Check for web-incompatible API usage without Platform checks.
|
|
157
|
+
|
|
158
|
+
Args:
|
|
159
|
+
file_path: Path to the file to check
|
|
160
|
+
|
|
161
|
+
Returns:
|
|
162
|
+
List of validation issues for web-incompatible APIs
|
|
163
|
+
"""
|
|
164
|
+
issues = []
|
|
165
|
+
|
|
166
|
+
try:
|
|
167
|
+
content = file_path.read_text(encoding="utf-8")
|
|
168
|
+
except (UnicodeDecodeError, IOError):
|
|
169
|
+
return issues
|
|
170
|
+
|
|
171
|
+
lines = content.split("\n")
|
|
172
|
+
|
|
173
|
+
for api in WEB_INCOMPATIBLE_APIS:
|
|
174
|
+
# Find lines containing the API
|
|
175
|
+
for line_num, line in enumerate(lines, 1):
|
|
176
|
+
if api in line:
|
|
177
|
+
# Check if there's a Platform check nearby (within 10 lines before)
|
|
178
|
+
start_line = max(0, line_num - 10)
|
|
179
|
+
context = "\n".join(lines[start_line : line_num + 1])
|
|
180
|
+
|
|
181
|
+
has_platform_check = (
|
|
182
|
+
"Platform.OS" in context
|
|
183
|
+
or "Platform.select" in context
|
|
184
|
+
or '.native"' in str(file_path)
|
|
185
|
+
or ".native'" in str(file_path)
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
if not has_platform_check:
|
|
189
|
+
issues.append(
|
|
190
|
+
ValidationIssue(
|
|
191
|
+
file_path=str(file_path),
|
|
192
|
+
line_number=line_num,
|
|
193
|
+
issue_type="web_incompatible_api",
|
|
194
|
+
message=f"'{api}' may not work on web. Consider adding a Platform.OS check.",
|
|
195
|
+
severity="warning",
|
|
196
|
+
)
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
return issues
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def check_incomplete_platform_handling(file_path: Path) -> list[ValidationIssue]:
|
|
203
|
+
"""
|
|
204
|
+
Check for incomplete Platform.OS handling patterns.
|
|
205
|
+
|
|
206
|
+
Args:
|
|
207
|
+
file_path: Path to the file to check
|
|
208
|
+
|
|
209
|
+
Returns:
|
|
210
|
+
List of validation issues for incomplete platform handling
|
|
211
|
+
"""
|
|
212
|
+
issues = []
|
|
213
|
+
|
|
214
|
+
try:
|
|
215
|
+
content = file_path.read_text(encoding="utf-8")
|
|
216
|
+
except (UnicodeDecodeError, IOError):
|
|
217
|
+
return issues
|
|
218
|
+
|
|
219
|
+
for pattern, message in INCOMPLETE_PLATFORM_PATTERNS:
|
|
220
|
+
matches = re.finditer(pattern, content, re.MULTILINE | re.DOTALL)
|
|
221
|
+
for match in matches:
|
|
222
|
+
# Calculate line number
|
|
223
|
+
line_num = content[: match.start()].count("\n") + 1
|
|
224
|
+
|
|
225
|
+
issues.append(
|
|
226
|
+
ValidationIssue(
|
|
227
|
+
file_path=str(file_path),
|
|
228
|
+
line_number=line_num,
|
|
229
|
+
issue_type="incomplete_platform_handling",
|
|
230
|
+
message=message,
|
|
231
|
+
severity="warning",
|
|
232
|
+
)
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
return issues
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def check_platform_select_completeness(file_path: Path) -> list[ValidationIssue]:
|
|
239
|
+
"""
|
|
240
|
+
Check if Platform.select() calls handle all platforms or have a default.
|
|
241
|
+
|
|
242
|
+
Args:
|
|
243
|
+
file_path: Path to the file to check
|
|
244
|
+
|
|
245
|
+
Returns:
|
|
246
|
+
List of validation issues for incomplete Platform.select usage
|
|
247
|
+
"""
|
|
248
|
+
issues = []
|
|
249
|
+
|
|
250
|
+
try:
|
|
251
|
+
content = file_path.read_text(encoding="utf-8")
|
|
252
|
+
except (UnicodeDecodeError, IOError):
|
|
253
|
+
return issues
|
|
254
|
+
|
|
255
|
+
# Find Platform.select calls
|
|
256
|
+
pattern = r"Platform\.select\s*\(\s*\{([^}]+)\}\s*\)"
|
|
257
|
+
matches = re.finditer(pattern, content, re.MULTILINE | re.DOTALL)
|
|
258
|
+
|
|
259
|
+
for match in matches:
|
|
260
|
+
select_content = match.group(1)
|
|
261
|
+
line_num = content[: match.start()].count("\n") + 1
|
|
262
|
+
|
|
263
|
+
has_ios = "ios:" in select_content or "'ios'" in select_content or '"ios"' in select_content
|
|
264
|
+
has_android = (
|
|
265
|
+
"android:" in select_content
|
|
266
|
+
or "'android'" in select_content
|
|
267
|
+
or '"android"' in select_content
|
|
268
|
+
)
|
|
269
|
+
has_web = "web:" in select_content or "'web'" in select_content or '"web"' in select_content
|
|
270
|
+
has_native = (
|
|
271
|
+
"native:" in select_content
|
|
272
|
+
or "'native'" in select_content
|
|
273
|
+
or '"native"' in select_content
|
|
274
|
+
)
|
|
275
|
+
has_default = (
|
|
276
|
+
"default:" in select_content
|
|
277
|
+
or "'default'" in select_content
|
|
278
|
+
or '"default"' in select_content
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
# Check completeness
|
|
282
|
+
covers_all = (has_ios and has_android and has_web) or has_default or (has_native and has_web)
|
|
283
|
+
|
|
284
|
+
if not covers_all:
|
|
285
|
+
missing = []
|
|
286
|
+
if not has_ios and not has_native:
|
|
287
|
+
missing.append("ios")
|
|
288
|
+
if not has_android and not has_native:
|
|
289
|
+
missing.append("android")
|
|
290
|
+
if not has_web:
|
|
291
|
+
missing.append("web")
|
|
292
|
+
|
|
293
|
+
if missing and not has_default:
|
|
294
|
+
issues.append(
|
|
295
|
+
ValidationIssue(
|
|
296
|
+
file_path=str(file_path),
|
|
297
|
+
line_number=line_num,
|
|
298
|
+
issue_type="incomplete_platform_select",
|
|
299
|
+
message=f"Platform.select() missing handling for: {', '.join(missing)}. "
|
|
300
|
+
"Consider adding a 'default' key.",
|
|
301
|
+
severity="warning",
|
|
302
|
+
)
|
|
303
|
+
)
|
|
304
|
+
|
|
305
|
+
return issues
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
def validate_directory(root_path: Path) -> list[ValidationIssue]:
|
|
309
|
+
"""
|
|
310
|
+
Validate all files in a directory for cross-platform issues.
|
|
311
|
+
|
|
312
|
+
Args:
|
|
313
|
+
root_path: Path to the directory to validate
|
|
314
|
+
|
|
315
|
+
Returns:
|
|
316
|
+
List of all validation issues found
|
|
317
|
+
"""
|
|
318
|
+
issues = []
|
|
319
|
+
|
|
320
|
+
# Check for orphaned platform files in app/ directory
|
|
321
|
+
app_dir = root_path / "app"
|
|
322
|
+
issues.extend(find_orphaned_platform_files(app_dir))
|
|
323
|
+
|
|
324
|
+
# Check TypeScript/JavaScript files
|
|
325
|
+
extensions = [".tsx", ".ts", ".jsx", ".js"]
|
|
326
|
+
exclude_dirs = ["node_modules", ".git", "dist", "build", ".expo"]
|
|
327
|
+
|
|
328
|
+
for ext in extensions:
|
|
329
|
+
for file_path in root_path.rglob(f"*{ext}"):
|
|
330
|
+
# Skip excluded directories
|
|
331
|
+
if any(excluded in str(file_path) for excluded in exclude_dirs):
|
|
332
|
+
continue
|
|
333
|
+
|
|
334
|
+
# Skip platform-specific files for web incompatibility checks
|
|
335
|
+
# (they're inherently platform-specific)
|
|
336
|
+
if not has_platform_extension(file_path.name):
|
|
337
|
+
issues.extend(check_web_incompatible_apis(file_path))
|
|
338
|
+
|
|
339
|
+
issues.extend(check_incomplete_platform_handling(file_path))
|
|
340
|
+
issues.extend(check_platform_select_completeness(file_path))
|
|
341
|
+
|
|
342
|
+
return issues
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
def print_issues(issues: list[ValidationIssue]) -> None:
|
|
346
|
+
"""
|
|
347
|
+
Print validation issues in a formatted way.
|
|
348
|
+
|
|
349
|
+
Args:
|
|
350
|
+
issues: List of validation issues to print
|
|
351
|
+
"""
|
|
352
|
+
if not issues:
|
|
353
|
+
print("✅ No cross-platform compatibility issues found!")
|
|
354
|
+
return
|
|
355
|
+
|
|
356
|
+
errors = [i for i in issues if i.severity == "error"]
|
|
357
|
+
warnings = [i for i in issues if i.severity == "warning"]
|
|
358
|
+
|
|
359
|
+
print(f"\n🔍 Found {len(issues)} issue(s):\n")
|
|
360
|
+
|
|
361
|
+
if errors:
|
|
362
|
+
print(f"❌ Errors ({len(errors)}):")
|
|
363
|
+
print("-" * 60)
|
|
364
|
+
for issue in errors:
|
|
365
|
+
location = f"{issue.file_path}:{issue.line_number}" if issue.line_number else issue.file_path
|
|
366
|
+
print(f" [{issue.issue_type}] {location}")
|
|
367
|
+
print(f" {issue.message}\n")
|
|
368
|
+
|
|
369
|
+
if warnings:
|
|
370
|
+
print(f"⚠️ Warnings ({len(warnings)}):")
|
|
371
|
+
print("-" * 60)
|
|
372
|
+
for issue in warnings:
|
|
373
|
+
location = f"{issue.file_path}:{issue.line_number}" if issue.line_number else issue.file_path
|
|
374
|
+
print(f" [{issue.issue_type}] {location}")
|
|
375
|
+
print(f" {issue.message}\n")
|
|
376
|
+
|
|
377
|
+
|
|
378
|
+
def main() -> int:
|
|
379
|
+
"""
|
|
380
|
+
Main entry point for the validation script.
|
|
381
|
+
|
|
382
|
+
Returns:
|
|
383
|
+
Exit code (0 for success, 1 for errors found)
|
|
384
|
+
"""
|
|
385
|
+
# Get path from arguments or use current directory
|
|
386
|
+
if len(sys.argv) > 1:
|
|
387
|
+
path = Path(sys.argv[1])
|
|
388
|
+
else:
|
|
389
|
+
path = Path.cwd()
|
|
390
|
+
|
|
391
|
+
if not path.exists():
|
|
392
|
+
print(f"❌ Error: Path '{path}' does not exist")
|
|
393
|
+
return 1
|
|
394
|
+
|
|
395
|
+
print(f"🔍 Validating cross-platform compatibility in: {path}")
|
|
396
|
+
|
|
397
|
+
if path.is_file():
|
|
398
|
+
issues = []
|
|
399
|
+
if not has_platform_extension(path.name):
|
|
400
|
+
issues.extend(check_web_incompatible_apis(path))
|
|
401
|
+
issues.extend(check_incomplete_platform_handling(path))
|
|
402
|
+
issues.extend(check_platform_select_completeness(path))
|
|
403
|
+
else:
|
|
404
|
+
issues = validate_directory(path)
|
|
405
|
+
|
|
406
|
+
print_issues(issues)
|
|
407
|
+
|
|
408
|
+
# Return error code if there are any errors
|
|
409
|
+
has_errors = any(issue.severity == "error" for issue in issues)
|
|
410
|
+
return 1 if has_errors else 0
|
|
411
|
+
|
|
412
|
+
|
|
413
|
+
if __name__ == "__main__":
|
|
414
|
+
sys.exit(main())
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: directory-structure
|
|
3
|
+
description: This skill enforces the project's directory structure standards when creating or moving files. Use this skill when creating new components, screens, features, hooks, utilities, or any other code files to ensure they are placed in the correct location with proper naming conventions. Also use when reviewing file placement or restructuring code.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Directory Structure Enforcement
|
|
7
|
+
|
|
8
|
+
## Overview
|
|
9
|
+
|
|
10
|
+
This skill ensures all new code follows the documented directory structure standards. It validates file placement, enforces the Container/View pattern, and ensures proper organization of features, components, and tests.
|
|
11
|
+
|
|
12
|
+
## When to Use
|
|
13
|
+
|
|
14
|
+
- Creating new components or screens
|
|
15
|
+
- Adding new features
|
|
16
|
+
- Creating hooks, utilities, or stores
|
|
17
|
+
- Adding test files
|
|
18
|
+
- Moving or restructuring existing code
|
|
19
|
+
- Reviewing file placement decisions
|
|
20
|
+
|
|
21
|
+
## Root Directory Structure
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
<project-root>/
|
|
25
|
+
├── app/ # Expo Router - THIN WRAPPERS ONLY
|
|
26
|
+
├── features/ # Feature modules - PRIMARY CODE LOCATION
|
|
27
|
+
├── components/ # Shared components
|
|
28
|
+
│ ├── ui/ # GlueStack UI primitives
|
|
29
|
+
│ ├── icons/ # Icon components
|
|
30
|
+
│ ├── custom/ # Custom UI components
|
|
31
|
+
│ └── [ComponentName]/ # Feature-agnostic components
|
|
32
|
+
├── hooks/ # Global hooks (with __tests__/)
|
|
33
|
+
├── providers/ # Global React context providers (with __tests__/)
|
|
34
|
+
├── stores/ # Global state - Apollo reactive variables (with __tests__/)
|
|
35
|
+
├── utils/ # Utility functions (with __tests__/)
|
|
36
|
+
├── types/ # Global TypeScript types
|
|
37
|
+
├── generated/ # Auto-generated GraphQL types (DO NOT EDIT)
|
|
38
|
+
├── config/ # Configuration files
|
|
39
|
+
├── constants/ # Global constants
|
|
40
|
+
├── assets/ # Static assets (fonts/, icons/, css/)
|
|
41
|
+
├── e2e/ # End-to-end tests (fixtures/, pages/, tests/, utils/)
|
|
42
|
+
├── scripts/ # Build and development scripts
|
|
43
|
+
├── docs/ # Documentation
|
|
44
|
+
└── projects/ # Project-specific files (archive/)
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Feature Module Structure
|
|
48
|
+
|
|
49
|
+
Each feature in `features/` MUST follow this structure:
|
|
50
|
+
|
|
51
|
+
```
|
|
52
|
+
features/[feature-name]/
|
|
53
|
+
├── components/ # Feature-specific components
|
|
54
|
+
│ └── [ComponentName]/ # Each component in its own directory
|
|
55
|
+
│ ├── [ComponentName]Container.tsx
|
|
56
|
+
│ ├── [ComponentName]View.tsx
|
|
57
|
+
│ └── index.tsx
|
|
58
|
+
├── screens/ # Screen components (same pattern as components)
|
|
59
|
+
│ └── [ScreenName]/
|
|
60
|
+
│ ├── [ScreenName]Container.tsx
|
|
61
|
+
│ ├── [ScreenName]View.tsx
|
|
62
|
+
│ └── index.tsx
|
|
63
|
+
├── hooks/ # Feature-specific hooks
|
|
64
|
+
│ └── __tests__/ # Hook tests
|
|
65
|
+
├── stores/ # Feature state (Apollo reactive variables)
|
|
66
|
+
├── utils/ # Feature utilities
|
|
67
|
+
│ └── __tests__/ # Utility tests
|
|
68
|
+
├── types.ts # Feature TypeScript types
|
|
69
|
+
├── constants.ts # Feature constants
|
|
70
|
+
├── config/ # Feature configuration
|
|
71
|
+
└── operations.graphql # GraphQL queries/mutations
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Container/View Pattern Rules
|
|
75
|
+
|
|
76
|
+
Components and screens MUST follow the Container/View pattern:
|
|
77
|
+
|
|
78
|
+
### Container (`[Name]Container.tsx`)
|
|
79
|
+
|
|
80
|
+
- Contains ALL business logic
|
|
81
|
+
- Uses hooks (useState, useEffect, useCallback, useMemo)
|
|
82
|
+
- Manages state and side effects
|
|
83
|
+
- Passes data and handlers as props to View
|
|
84
|
+
- ONLY renders the corresponding View component
|
|
85
|
+
|
|
86
|
+
### View (`[Name]View.tsx`)
|
|
87
|
+
|
|
88
|
+
- Pure presentation component
|
|
89
|
+
- Receives ALL data via props
|
|
90
|
+
- MUST be wrapped in `memo()`
|
|
91
|
+
- NO business logic or hooks (except UI-specific like useRef for scroll)
|
|
92
|
+
- Handles conditional rendering (loading, error, empty, populated states)
|
|
93
|
+
|
|
94
|
+
### Index (`index.tsx`)
|
|
95
|
+
|
|
96
|
+
- Exports the Container as default
|
|
97
|
+
- May export types if needed
|
|
98
|
+
|
|
99
|
+
### Example Structure
|
|
100
|
+
|
|
101
|
+
```typescript
|
|
102
|
+
// PlayerCard/PlayerCardContainer.tsx
|
|
103
|
+
export const PlayerCardContainer = () => {
|
|
104
|
+
const { data, loading } = useQuery(GET_PLAYER);
|
|
105
|
+
const handleClick = useCallback(() => {}, []);
|
|
106
|
+
return <PlayerCardView data={data} loading={loading} onClick={handleClick} />;
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
// PlayerCard/PlayerCardView.tsx
|
|
110
|
+
export const PlayerCardView = memo(({ data, loading, onClick }: Props) => {
|
|
111
|
+
if (loading) return <Skeleton />;
|
|
112
|
+
return <Pressable onPress={onClick}>...</Pressable>;
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
// PlayerCard/index.tsx
|
|
116
|
+
export { PlayerCardContainer as default } from './PlayerCardContainer';
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## App Directory Rules
|
|
120
|
+
|
|
121
|
+
The `app/` directory contains Expo Router file-based routing. Route files MUST be thin wrappers:
|
|
122
|
+
|
|
123
|
+
### Correct Pattern
|
|
124
|
+
|
|
125
|
+
```typescript
|
|
126
|
+
// app/(root)/(tabs)/players/[playerId]/index.tsx
|
|
127
|
+
import { Main } from "@/features/player-detail/screens/Main";
|
|
128
|
+
|
|
129
|
+
export default function PlayerDetailScreen() {
|
|
130
|
+
return <Main />;
|
|
131
|
+
}
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### Violations
|
|
135
|
+
|
|
136
|
+
- Business logic in route files
|
|
137
|
+
- Component definitions in route files
|
|
138
|
+
- Hooks usage beyond route params
|
|
139
|
+
- Direct UI rendering beyond wrapper
|
|
140
|
+
|
|
141
|
+
## Test File Placement
|
|
142
|
+
|
|
143
|
+
Test files MUST be placed in `__tests__/` subdirectories:
|
|
144
|
+
|
|
145
|
+
```
|
|
146
|
+
hooks/
|
|
147
|
+
├── usePlayer.ts
|
|
148
|
+
└── __tests__/
|
|
149
|
+
└── usePlayer.test.ts
|
|
150
|
+
|
|
151
|
+
utils/
|
|
152
|
+
├── formatDate.ts
|
|
153
|
+
└── __tests__/
|
|
154
|
+
└── formatDate.test.ts
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### Test File Naming
|
|
158
|
+
|
|
159
|
+
- Unit tests: `*.test.ts` or `*.test.tsx`
|
|
160
|
+
- Spec tests: `*.spec.ts` or `*.spec.tsx`
|
|
161
|
+
|
|
162
|
+
## Validation Script
|
|
163
|
+
|
|
164
|
+
To validate directory structure, run the validation script:
|
|
165
|
+
|
|
166
|
+
```bash
|
|
167
|
+
python3 .claude/skills/directory-structure/scripts/validate_structure.py [path]
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
The script checks:
|
|
171
|
+
|
|
172
|
+
1. Feature module structure completeness
|
|
173
|
+
2. Container/View pattern compliance
|
|
174
|
+
3. Test file placement in `__tests__/` directories
|
|
175
|
+
4. Route file thin wrapper compliance
|
|
176
|
+
5. Proper naming conventions
|
|
177
|
+
|
|
178
|
+
## Quick Reference
|
|
179
|
+
|
|
180
|
+
| File Type | Correct Location |
|
|
181
|
+
| ------------------- | --------------------------------------- |
|
|
182
|
+
| Feature component | `features/[feature]/components/[Name]/` |
|
|
183
|
+
| Feature screen | `features/[feature]/screens/[Name]/` |
|
|
184
|
+
| Feature hook | `features/[feature]/hooks/` |
|
|
185
|
+
| Feature test | `features/[feature]/hooks/__tests__/` |
|
|
186
|
+
| Global hook | `hooks/` |
|
|
187
|
+
| Global hook test | `hooks/__tests__/` |
|
|
188
|
+
| Shared component | `components/[Name]/` |
|
|
189
|
+
| UI primitive | `components/ui/` |
|
|
190
|
+
| Global utility | `utils/` |
|
|
191
|
+
| Global utility test | `utils/__tests__/` |
|
|
192
|
+
| Route wrapper | `app/(root)/...` |
|
|
193
|
+
| GraphQL types | `generated/` (auto-generated) |
|
|
194
|
+
|
|
195
|
+
## Common Mistakes to Avoid
|
|
196
|
+
|
|
197
|
+
1. **Placing feature code in `app/`** - Route files are wrappers only
|
|
198
|
+
2. **Test files alongside source** - Must be in `__tests__/` subdirectory
|
|
199
|
+
3. **Missing Container/View split** - Every component needs both files
|
|
200
|
+
4. **Business logic in View** - Views are pure presentation only
|
|
201
|
+
5. **Multiple components per file** - One component per file, enforced by ESLint
|
|
202
|
+
6. **Barrel exports** - Never use index.ts to re-export multiple components
|