codesift-mcp 0.3.0 → 0.5.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/README.md +215 -23
- package/dist/cache/hono-cache.d.ts +50 -0
- package/dist/cache/hono-cache.d.ts.map +1 -0
- package/dist/cache/hono-cache.js +132 -0
- package/dist/cache/hono-cache.js.map +1 -0
- package/dist/cli/help.d.ts.map +1 -1
- package/dist/cli/help.js +8 -6
- package/dist/cli/help.js.map +1 -1
- package/dist/cli/platform.d.ts.map +1 -1
- package/dist/cli/platform.js +12 -14
- package/dist/cli/platform.js.map +1 -1
- package/dist/cli/setup.d.ts +1 -1
- package/dist/cli/setup.d.ts.map +1 -1
- package/dist/cli/setup.js +27 -3
- package/dist/cli/setup.js.map +1 -1
- package/dist/formatters-shortening.d.ts +13 -0
- package/dist/formatters-shortening.d.ts.map +1 -1
- package/dist/formatters-shortening.js +131 -0
- package/dist/formatters-shortening.js.map +1 -1
- package/dist/formatters.d.ts +38 -0
- package/dist/formatters.d.ts.map +1 -1
- package/dist/formatters.js +521 -0
- package/dist/formatters.js.map +1 -1
- package/dist/instructions.d.ts +1 -1
- package/dist/instructions.d.ts.map +1 -1
- package/dist/instructions.js +39 -38
- package/dist/instructions.js.map +1 -1
- package/dist/lsp/lsp-servers.d.ts.map +1 -1
- package/dist/lsp/lsp-servers.js +5 -0
- package/dist/lsp/lsp-servers.js.map +1 -1
- package/dist/lsp/lsp-tools.d.ts.map +1 -1
- package/dist/lsp/lsp-tools.js +1 -0
- package/dist/lsp/lsp-tools.js.map +1 -1
- package/dist/parser/astro-template.d.ts +47 -0
- package/dist/parser/astro-template.d.ts.map +1 -0
- package/dist/parser/astro-template.js +171 -0
- package/dist/parser/astro-template.js.map +1 -0
- package/dist/parser/extractors/_shared.d.ts +4 -0
- package/dist/parser/extractors/_shared.d.ts.map +1 -1
- package/dist/parser/extractors/_shared.js +8 -0
- package/dist/parser/extractors/_shared.js.map +1 -1
- package/dist/parser/extractors/astro.d.ts +4 -5
- package/dist/parser/extractors/astro.d.ts.map +1 -1
- package/dist/parser/extractors/astro.js +102 -26
- package/dist/parser/extractors/astro.js.map +1 -1
- package/dist/parser/extractors/gradle-kts.d.ts +4 -0
- package/dist/parser/extractors/gradle-kts.d.ts.map +1 -0
- package/dist/parser/extractors/gradle-kts.js +246 -0
- package/dist/parser/extractors/gradle-kts.js.map +1 -0
- package/dist/parser/extractors/hono-inline-analyzer.d.ts +34 -0
- package/dist/parser/extractors/hono-inline-analyzer.d.ts.map +1 -0
- package/dist/parser/extractors/hono-inline-analyzer.js +465 -0
- package/dist/parser/extractors/hono-inline-analyzer.js.map +1 -0
- package/dist/parser/extractors/hono-model.d.ts +196 -0
- package/dist/parser/extractors/hono-model.d.ts.map +1 -0
- package/dist/parser/extractors/hono-model.js +10 -0
- package/dist/parser/extractors/hono-model.js.map +1 -0
- package/dist/parser/extractors/hono.d.ts +118 -0
- package/dist/parser/extractors/hono.d.ts.map +1 -0
- package/dist/parser/extractors/hono.js +1527 -0
- package/dist/parser/extractors/hono.js.map +1 -0
- package/dist/parser/extractors/kotlin.d.ts +4 -0
- package/dist/parser/extractors/kotlin.d.ts.map +1 -0
- package/dist/parser/extractors/kotlin.js +521 -0
- package/dist/parser/extractors/kotlin.js.map +1 -0
- package/dist/parser/extractors/php.d.ts +22 -0
- package/dist/parser/extractors/php.d.ts.map +1 -0
- package/dist/parser/extractors/php.js +334 -0
- package/dist/parser/extractors/php.js.map +1 -0
- package/dist/parser/extractors/python.d.ts.map +1 -1
- package/dist/parser/extractors/python.js +234 -11
- package/dist/parser/extractors/python.js.map +1 -1
- package/dist/parser/extractors/sql.d.ts +33 -0
- package/dist/parser/extractors/sql.d.ts.map +1 -0
- package/dist/parser/extractors/sql.js +506 -0
- package/dist/parser/extractors/sql.js.map +1 -0
- package/dist/parser/extractors/typescript.d.ts.map +1 -1
- package/dist/parser/extractors/typescript.js +209 -3
- package/dist/parser/extractors/typescript.js.map +1 -1
- package/dist/parser/languages/tree-sitter-javascript.wasm +0 -0
- package/dist/parser/languages/tree-sitter-kotlin.wasm +0 -0
- package/dist/parser/languages/tree-sitter-php.wasm +0 -0
- package/dist/parser/languages/tree-sitter-php_only.wasm +0 -0
- package/dist/parser/languages/tree-sitter-python.wasm +0 -0
- package/dist/parser/parse-cache.d.ts +39 -0
- package/dist/parser/parse-cache.d.ts.map +1 -0
- package/dist/parser/parse-cache.js +87 -0
- package/dist/parser/parse-cache.js.map +1 -0
- package/dist/parser/parser-manager.d.ts +32 -0
- package/dist/parser/parser-manager.d.ts.map +1 -1
- package/dist/parser/parser-manager.js +93 -3
- package/dist/parser/parser-manager.js.map +1 -1
- package/dist/parser/symbol-extractor.d.ts.map +1 -1
- package/dist/parser/symbol-extractor.js +16 -0
- package/dist/parser/symbol-extractor.js.map +1 -1
- package/dist/register-tools.d.ts +38 -2
- package/dist/register-tools.d.ts.map +1 -1
- package/dist/register-tools.js +2444 -195
- package/dist/register-tools.js.map +1 -1
- package/dist/search/reranker.js +1 -1
- package/dist/search/reranker.js.map +1 -1
- package/dist/search/tool-ranker.d.ts +90 -0
- package/dist/search/tool-ranker.d.ts.map +1 -0
- package/dist/search/tool-ranker.js +420 -0
- package/dist/search/tool-ranker.js.map +1 -0
- package/dist/server-helpers.d.ts.map +1 -1
- package/dist/server-helpers.js +11 -0
- package/dist/server-helpers.js.map +1 -1
- package/dist/server.js +47 -14
- package/dist/server.js.map +1 -1
- package/dist/storage/index-store.d.ts +15 -1
- package/dist/storage/index-store.d.ts.map +1 -1
- package/dist/storage/index-store.js +27 -1
- package/dist/storage/index-store.js.map +1 -1
- package/dist/storage/session-state.d.ts +1 -1
- package/dist/storage/session-state.d.ts.map +1 -1
- package/dist/storage/session-state.js +6 -4
- package/dist/storage/session-state.js.map +1 -1
- package/dist/storage/usage-tracker.d.ts.map +1 -1
- package/dist/storage/usage-tracker.js +4 -1
- package/dist/storage/usage-tracker.js.map +1 -1
- package/dist/tools/agent-config-tools.d.ts +24 -0
- package/dist/tools/agent-config-tools.d.ts.map +1 -0
- package/dist/tools/agent-config-tools.js +119 -0
- package/dist/tools/agent-config-tools.js.map +1 -0
- package/dist/tools/architecture-tools.d.ts +23 -0
- package/dist/tools/architecture-tools.d.ts.map +1 -0
- package/dist/tools/architecture-tools.js +140 -0
- package/dist/tools/architecture-tools.js.map +1 -0
- package/dist/tools/astro-actions.d.ts +54 -0
- package/dist/tools/astro-actions.d.ts.map +1 -0
- package/dist/tools/astro-actions.js +561 -0
- package/dist/tools/astro-actions.js.map +1 -0
- package/dist/tools/astro-audit.d.ts +87 -0
- package/dist/tools/astro-audit.d.ts.map +1 -0
- package/dist/tools/astro-audit.js +345 -0
- package/dist/tools/astro-audit.js.map +1 -0
- package/dist/tools/astro-config.d.ts +33 -0
- package/dist/tools/astro-config.d.ts.map +1 -0
- package/dist/tools/astro-config.js +260 -0
- package/dist/tools/astro-config.js.map +1 -0
- package/dist/tools/astro-content-collections.d.ts +44 -0
- package/dist/tools/astro-content-collections.d.ts.map +1 -0
- package/dist/tools/astro-content-collections.js +630 -0
- package/dist/tools/astro-content-collections.js.map +1 -0
- package/dist/tools/astro-islands.d.ts +63 -0
- package/dist/tools/astro-islands.d.ts.map +1 -0
- package/dist/tools/astro-islands.js +255 -0
- package/dist/tools/astro-islands.js.map +1 -0
- package/dist/tools/astro-migration.d.ts +31 -0
- package/dist/tools/astro-migration.d.ts.map +1 -0
- package/dist/tools/astro-migration.js +378 -0
- package/dist/tools/astro-migration.js.map +1 -0
- package/dist/tools/astro-routes.d.ts +49 -0
- package/dist/tools/astro-routes.d.ts.map +1 -0
- package/dist/tools/astro-routes.js +119 -0
- package/dist/tools/astro-routes.js.map +1 -0
- package/dist/tools/async-correctness.d.ts +26 -0
- package/dist/tools/async-correctness.d.ts.map +1 -0
- package/dist/tools/async-correctness.js +166 -0
- package/dist/tools/async-correctness.js.map +1 -0
- package/dist/tools/audit-tools.d.ts +38 -0
- package/dist/tools/audit-tools.d.ts.map +1 -0
- package/dist/tools/audit-tools.js +248 -0
- package/dist/tools/audit-tools.js.map +1 -0
- package/dist/tools/celery-tools.d.ts +38 -0
- package/dist/tools/celery-tools.d.ts.map +1 -0
- package/dist/tools/celery-tools.js +154 -0
- package/dist/tools/celery-tools.js.map +1 -0
- package/dist/tools/clone-tools.js +1 -1
- package/dist/tools/clone-tools.js.map +1 -1
- package/dist/tools/complexity-tools.d.ts +4 -0
- package/dist/tools/complexity-tools.d.ts.map +1 -1
- package/dist/tools/complexity-tools.js +78 -4
- package/dist/tools/complexity-tools.js.map +1 -1
- package/dist/tools/compose-tools.d.ts +60 -0
- package/dist/tools/compose-tools.d.ts.map +1 -0
- package/dist/tools/compose-tools.js +203 -0
- package/dist/tools/compose-tools.js.map +1 -0
- package/dist/tools/coupling-tools.d.ts +50 -0
- package/dist/tools/coupling-tools.d.ts.map +1 -0
- package/dist/tools/coupling-tools.js +262 -0
- package/dist/tools/coupling-tools.js.map +1 -0
- package/dist/tools/dependency-audit-tools.d.ts +65 -0
- package/dist/tools/dependency-audit-tools.d.ts.map +1 -0
- package/dist/tools/dependency-audit-tools.js +553 -0
- package/dist/tools/dependency-audit-tools.js.map +1 -0
- package/dist/tools/django-settings.d.ts +22 -0
- package/dist/tools/django-settings.d.ts.map +1 -0
- package/dist/tools/django-settings.js +301 -0
- package/dist/tools/django-settings.js.map +1 -0
- package/dist/tools/django-view-security-tools.d.ts +32 -0
- package/dist/tools/django-view-security-tools.d.ts.map +1 -0
- package/dist/tools/django-view-security-tools.js +184 -0
- package/dist/tools/django-view-security-tools.js.map +1 -0
- package/dist/tools/fastapi-depends.d.ts +63 -0
- package/dist/tools/fastapi-depends.d.ts.map +1 -0
- package/dist/tools/fastapi-depends.js +191 -0
- package/dist/tools/fastapi-depends.js.map +1 -0
- package/dist/tools/frequency-tools.js +1 -1
- package/dist/tools/frequency-tools.js.map +1 -1
- package/dist/tools/graph-tools.d.ts +8 -2
- package/dist/tools/graph-tools.d.ts.map +1 -1
- package/dist/tools/graph-tools.js +44 -3
- package/dist/tools/graph-tools.js.map +1 -1
- package/dist/tools/hilt-tools.d.ts +55 -0
- package/dist/tools/hilt-tools.d.ts.map +1 -0
- package/dist/tools/hilt-tools.js +258 -0
- package/dist/tools/hilt-tools.js.map +1 -0
- package/dist/tools/hono-analyze-app.d.ts +48 -0
- package/dist/tools/hono-analyze-app.d.ts.map +1 -0
- package/dist/tools/hono-analyze-app.js +94 -0
- package/dist/tools/hono-analyze-app.js.map +1 -0
- package/dist/tools/hono-api-contract.d.ts +22 -0
- package/dist/tools/hono-api-contract.d.ts.map +1 -0
- package/dist/tools/hono-api-contract.js +112 -0
- package/dist/tools/hono-api-contract.js.map +1 -0
- package/dist/tools/hono-conditional-middleware.d.ts +27 -0
- package/dist/tools/hono-conditional-middleware.d.ts.map +1 -0
- package/dist/tools/hono-conditional-middleware.js +62 -0
- package/dist/tools/hono-conditional-middleware.js.map +1 -0
- package/dist/tools/hono-context-flow.d.ts +24 -0
- package/dist/tools/hono-context-flow.d.ts.map +1 -0
- package/dist/tools/hono-context-flow.js +70 -0
- package/dist/tools/hono-context-flow.js.map +1 -0
- package/dist/tools/hono-dead-routes.d.ts +26 -0
- package/dist/tools/hono-dead-routes.d.ts.map +1 -0
- package/dist/tools/hono-dead-routes.js +102 -0
- package/dist/tools/hono-dead-routes.js.map +1 -0
- package/dist/tools/hono-entry-resolver.d.ts +27 -0
- package/dist/tools/hono-entry-resolver.d.ts.map +1 -0
- package/dist/tools/hono-entry-resolver.js +31 -0
- package/dist/tools/hono-entry-resolver.js.map +1 -0
- package/dist/tools/hono-env-regression.d.ts +29 -0
- package/dist/tools/hono-env-regression.d.ts.map +1 -0
- package/dist/tools/hono-env-regression.js +157 -0
- package/dist/tools/hono-env-regression.js.map +1 -0
- package/dist/tools/hono-inline-analyze.d.ts +31 -0
- package/dist/tools/hono-inline-analyze.d.ts.map +1 -0
- package/dist/tools/hono-inline-analyze.js +59 -0
- package/dist/tools/hono-inline-analyze.js.map +1 -0
- package/dist/tools/hono-middleware-chain.d.ts +40 -0
- package/dist/tools/hono-middleware-chain.d.ts.map +1 -0
- package/dist/tools/hono-middleware-chain.js +121 -0
- package/dist/tools/hono-middleware-chain.js.map +1 -0
- package/dist/tools/hono-modules.d.ts +22 -0
- package/dist/tools/hono-modules.d.ts.map +1 -0
- package/dist/tools/hono-modules.js +118 -0
- package/dist/tools/hono-modules.js.map +1 -0
- package/dist/tools/hono-response-types.d.ts +37 -0
- package/dist/tools/hono-response-types.d.ts.map +1 -0
- package/dist/tools/hono-response-types.js +76 -0
- package/dist/tools/hono-response-types.js.map +1 -0
- package/dist/tools/hono-rpc-types.d.ts +21 -0
- package/dist/tools/hono-rpc-types.d.ts.map +1 -0
- package/dist/tools/hono-rpc-types.js +49 -0
- package/dist/tools/hono-rpc-types.js.map +1 -0
- package/dist/tools/hono-security.d.ts +31 -0
- package/dist/tools/hono-security.d.ts.map +1 -0
- package/dist/tools/hono-security.js +269 -0
- package/dist/tools/hono-security.js.map +1 -0
- package/dist/tools/hono-visualize.d.ts +13 -0
- package/dist/tools/hono-visualize.d.ts.map +1 -0
- package/dist/tools/hono-visualize.js +64 -0
- package/dist/tools/hono-visualize.js.map +1 -0
- package/dist/tools/hotspot-tools.d.ts.map +1 -1
- package/dist/tools/hotspot-tools.js +9 -7
- package/dist/tools/hotspot-tools.js.map +1 -1
- package/dist/tools/index-tools.d.ts +17 -0
- package/dist/tools/index-tools.d.ts.map +1 -1
- package/dist/tools/index-tools.js +210 -10
- package/dist/tools/index-tools.js.map +1 -1
- package/dist/tools/kotlin-tools.d.ts +142 -0
- package/dist/tools/kotlin-tools.d.ts.map +1 -0
- package/dist/tools/kotlin-tools.js +572 -0
- package/dist/tools/kotlin-tools.js.map +1 -0
- package/dist/tools/legacy-hono-conventions.d.ts +14 -0
- package/dist/tools/legacy-hono-conventions.d.ts.map +1 -0
- package/dist/tools/legacy-hono-conventions.js +152 -0
- package/dist/tools/legacy-hono-conventions.js.map +1 -0
- package/dist/tools/migration-lint-tools.d.ts +26 -0
- package/dist/tools/migration-lint-tools.d.ts.map +1 -0
- package/dist/tools/migration-lint-tools.js +247 -0
- package/dist/tools/migration-lint-tools.js.map +1 -0
- package/dist/tools/model-tools.d.ts +30 -0
- package/dist/tools/model-tools.d.ts.map +1 -0
- package/dist/tools/model-tools.js +145 -0
- package/dist/tools/model-tools.js.map +1 -0
- package/dist/tools/nest-ext-tools.d.ts +207 -0
- package/dist/tools/nest-ext-tools.d.ts.map +1 -0
- package/dist/tools/nest-ext-tools.js +752 -0
- package/dist/tools/nest-ext-tools.js.map +1 -0
- package/dist/tools/nest-tools.d.ts +198 -0
- package/dist/tools/nest-tools.d.ts.map +1 -0
- package/dist/tools/nest-tools.js +1142 -0
- package/dist/tools/nest-tools.js.map +1 -0
- package/dist/tools/nextjs-api-contract-readers.d.ts +14 -0
- package/dist/tools/nextjs-api-contract-readers.d.ts.map +1 -0
- package/dist/tools/nextjs-api-contract-readers.js +204 -0
- package/dist/tools/nextjs-api-contract-readers.js.map +1 -0
- package/dist/tools/nextjs-api-contract-tools.d.ts +57 -0
- package/dist/tools/nextjs-api-contract-tools.d.ts.map +1 -0
- package/dist/tools/nextjs-api-contract-tools.js +144 -0
- package/dist/tools/nextjs-api-contract-tools.js.map +1 -0
- package/dist/tools/nextjs-boundary-tools.d.ts +39 -0
- package/dist/tools/nextjs-boundary-tools.d.ts.map +1 -0
- package/dist/tools/nextjs-boundary-tools.js +152 -0
- package/dist/tools/nextjs-boundary-tools.js.map +1 -0
- package/dist/tools/nextjs-component-readers.d.ts +101 -0
- package/dist/tools/nextjs-component-readers.d.ts.map +1 -0
- package/dist/tools/nextjs-component-readers.js +287 -0
- package/dist/tools/nextjs-component-readers.js.map +1 -0
- package/dist/tools/nextjs-component-tools.d.ts +51 -0
- package/dist/tools/nextjs-component-tools.d.ts.map +1 -0
- package/dist/tools/nextjs-component-tools.js +212 -0
- package/dist/tools/nextjs-component-tools.js.map +1 -0
- package/dist/tools/nextjs-data-flow-tools.d.ts +42 -0
- package/dist/tools/nextjs-data-flow-tools.d.ts.map +1 -0
- package/dist/tools/nextjs-data-flow-tools.js +158 -0
- package/dist/tools/nextjs-data-flow-tools.js.map +1 -0
- package/dist/tools/nextjs-framework-audit-tools.d.ts +60 -0
- package/dist/tools/nextjs-framework-audit-tools.d.ts.map +1 -0
- package/dist/tools/nextjs-framework-audit-tools.js +394 -0
- package/dist/tools/nextjs-framework-audit-tools.js.map +1 -0
- package/dist/tools/nextjs-link-tools.d.ts +41 -0
- package/dist/tools/nextjs-link-tools.d.ts.map +1 -0
- package/dist/tools/nextjs-link-tools.js +157 -0
- package/dist/tools/nextjs-link-tools.js.map +1 -0
- package/dist/tools/nextjs-metadata-tools.d.ts +74 -0
- package/dist/tools/nextjs-metadata-tools.d.ts.map +1 -0
- package/dist/tools/nextjs-metadata-tools.js +252 -0
- package/dist/tools/nextjs-metadata-tools.js.map +1 -0
- package/dist/tools/nextjs-middleware-coverage-tools.d.ts +41 -0
- package/dist/tools/nextjs-middleware-coverage-tools.d.ts.map +1 -0
- package/dist/tools/nextjs-middleware-coverage-tools.js +88 -0
- package/dist/tools/nextjs-middleware-coverage-tools.js.map +1 -0
- package/dist/tools/nextjs-route-readers.d.ts +81 -0
- package/dist/tools/nextjs-route-readers.d.ts.map +1 -0
- package/dist/tools/nextjs-route-readers.js +340 -0
- package/dist/tools/nextjs-route-readers.js.map +1 -0
- package/dist/tools/nextjs-route-tools.d.ts +36 -0
- package/dist/tools/nextjs-route-tools.d.ts.map +1 -0
- package/dist/tools/nextjs-route-tools.js +175 -0
- package/dist/tools/nextjs-route-tools.js.map +1 -0
- package/dist/tools/nextjs-security-readers.d.ts +22 -0
- package/dist/tools/nextjs-security-readers.d.ts.map +1 -0
- package/dist/tools/nextjs-security-readers.js +318 -0
- package/dist/tools/nextjs-security-readers.js.map +1 -0
- package/dist/tools/nextjs-security-scoring.d.ts +15 -0
- package/dist/tools/nextjs-security-scoring.d.ts.map +1 -0
- package/dist/tools/nextjs-security-scoring.js +65 -0
- package/dist/tools/nextjs-security-scoring.js.map +1 -0
- package/dist/tools/nextjs-security-tools.d.ts +75 -0
- package/dist/tools/nextjs-security-tools.d.ts.map +1 -0
- package/dist/tools/nextjs-security-tools.js +153 -0
- package/dist/tools/nextjs-security-tools.js.map +1 -0
- package/dist/tools/nextjs-tools.d.ts +15 -0
- package/dist/tools/nextjs-tools.d.ts.map +1 -0
- package/dist/tools/nextjs-tools.js +15 -0
- package/dist/tools/nextjs-tools.js.map +1 -0
- package/dist/tools/outline-tools.d.ts.map +1 -1
- package/dist/tools/outline-tools.js +20 -0
- package/dist/tools/outline-tools.js.map +1 -1
- package/dist/tools/pattern-tools.d.ts +8 -0
- package/dist/tools/pattern-tools.d.ts.map +1 -1
- package/dist/tools/pattern-tools.js +651 -3
- package/dist/tools/pattern-tools.js.map +1 -1
- package/dist/tools/perf-tools.d.ts +32 -0
- package/dist/tools/perf-tools.d.ts.map +1 -0
- package/dist/tools/perf-tools.js +227 -0
- package/dist/tools/perf-tools.js.map +1 -0
- package/dist/tools/php-tools.d.ts +185 -0
- package/dist/tools/php-tools.d.ts.map +1 -0
- package/dist/tools/php-tools.js +645 -0
- package/dist/tools/php-tools.js.map +1 -0
- package/dist/tools/plan-turn-tools.d.ts +89 -0
- package/dist/tools/plan-turn-tools.d.ts.map +1 -0
- package/dist/tools/plan-turn-tools.js +508 -0
- package/dist/tools/plan-turn-tools.js.map +1 -0
- package/dist/tools/prisma-schema-tools.d.ts +44 -0
- package/dist/tools/prisma-schema-tools.d.ts.map +1 -0
- package/dist/tools/prisma-schema-tools.js +358 -0
- package/dist/tools/prisma-schema-tools.js.map +1 -0
- package/dist/tools/project-tools.d.ts +116 -7
- package/dist/tools/project-tools.d.ts.map +1 -1
- package/dist/tools/project-tools.js +595 -218
- package/dist/tools/project-tools.js.map +1 -1
- package/dist/tools/pydantic-models.d.ts +46 -0
- package/dist/tools/pydantic-models.d.ts.map +1 -0
- package/dist/tools/pydantic-models.js +249 -0
- package/dist/tools/pydantic-models.js.map +1 -0
- package/dist/tools/pyproject-tools.d.ts +23 -0
- package/dist/tools/pyproject-tools.d.ts.map +1 -0
- package/dist/tools/pyproject-tools.js +133 -0
- package/dist/tools/pyproject-tools.js.map +1 -0
- package/dist/tools/pytest-tools.d.ts +20 -0
- package/dist/tools/pytest-tools.d.ts.map +1 -0
- package/dist/tools/pytest-tools.js +106 -0
- package/dist/tools/pytest-tools.js.map +1 -0
- package/dist/tools/python-audit.d.ts +40 -0
- package/dist/tools/python-audit.d.ts.map +1 -0
- package/dist/tools/python-audit.js +244 -0
- package/dist/tools/python-audit.js.map +1 -0
- package/dist/tools/python-callers.d.ts +28 -0
- package/dist/tools/python-callers.d.ts.map +1 -0
- package/dist/tools/python-callers.js +110 -0
- package/dist/tools/python-callers.js.map +1 -0
- package/dist/tools/python-circular-imports.d.ts +19 -0
- package/dist/tools/python-circular-imports.d.ts.map +1 -0
- package/dist/tools/python-circular-imports.js +126 -0
- package/dist/tools/python-circular-imports.js.map +1 -0
- package/dist/tools/python-constants-tools.d.ts +44 -0
- package/dist/tools/python-constants-tools.d.ts.map +1 -0
- package/dist/tools/python-constants-tools.js +525 -0
- package/dist/tools/python-constants-tools.js.map +1 -0
- package/dist/tools/python-deps-analyzer.d.ts +46 -0
- package/dist/tools/python-deps-analyzer.d.ts.map +1 -0
- package/dist/tools/python-deps-analyzer.js +227 -0
- package/dist/tools/python-deps-analyzer.js.map +1 -0
- package/dist/tools/query-tools.d.ts +23 -0
- package/dist/tools/query-tools.d.ts.map +1 -0
- package/dist/tools/query-tools.js +256 -0
- package/dist/tools/query-tools.js.map +1 -0
- package/dist/tools/react-tools.d.ts +263 -0
- package/dist/tools/react-tools.d.ts.map +1 -0
- package/dist/tools/react-tools.js +839 -0
- package/dist/tools/react-tools.js.map +1 -0
- package/dist/tools/report-tools.js +47 -0
- package/dist/tools/report-tools.js.map +1 -1
- package/dist/tools/review-diff-tools.d.ts +5 -4
- package/dist/tools/review-diff-tools.d.ts.map +1 -1
- package/dist/tools/review-diff-tools.js +157 -66
- package/dist/tools/review-diff-tools.js.map +1 -1
- package/dist/tools/room-tools.d.ts +36 -0
- package/dist/tools/room-tools.d.ts.map +1 -0
- package/dist/tools/room-tools.js +147 -0
- package/dist/tools/room-tools.js.map +1 -0
- package/dist/tools/route-tools.d.ts +27 -1
- package/dist/tools/route-tools.d.ts.map +1 -1
- package/dist/tools/route-tools.js +744 -18
- package/dist/tools/route-tools.js.map +1 -1
- package/dist/tools/ruff-tools.d.ts +32 -0
- package/dist/tools/ruff-tools.d.ts.map +1 -0
- package/dist/tools/ruff-tools.js +114 -0
- package/dist/tools/ruff-tools.js.map +1 -0
- package/dist/tools/search-ranker.d.ts.map +1 -1
- package/dist/tools/search-ranker.js +7 -0
- package/dist/tools/search-ranker.js.map +1 -1
- package/dist/tools/search-tools.d.ts +3 -2
- package/dist/tools/search-tools.d.ts.map +1 -1
- package/dist/tools/search-tools.js +16 -3
- package/dist/tools/search-tools.js.map +1 -1
- package/dist/tools/serialization-tools.d.ts +24 -0
- package/dist/tools/serialization-tools.d.ts.map +1 -0
- package/dist/tools/serialization-tools.js +156 -0
- package/dist/tools/serialization-tools.js.map +1 -0
- package/dist/tools/sql-tools.d.ts +274 -0
- package/dist/tools/sql-tools.d.ts.map +1 -0
- package/dist/tools/sql-tools.js +1160 -0
- package/dist/tools/sql-tools.js.map +1 -0
- package/dist/tools/status-tools.d.ts +10 -0
- package/dist/tools/status-tools.d.ts.map +1 -0
- package/dist/tools/status-tools.js +32 -0
- package/dist/tools/status-tools.js.map +1 -0
- package/dist/tools/symbol-tools.d.ts +19 -0
- package/dist/tools/symbol-tools.d.ts.map +1 -1
- package/dist/tools/symbol-tools.js +75 -4
- package/dist/tools/symbol-tools.js.map +1 -1
- package/dist/tools/taint-tools.d.ts +43 -0
- package/dist/tools/taint-tools.d.ts.map +1 -0
- package/dist/tools/taint-tools.js +922 -0
- package/dist/tools/taint-tools.js.map +1 -0
- package/dist/tools/test-impact-tools.d.ts +29 -0
- package/dist/tools/test-impact-tools.d.ts.map +1 -0
- package/dist/tools/test-impact-tools.js +156 -0
- package/dist/tools/test-impact-tools.js.map +1 -0
- package/dist/tools/typecheck-tools.d.ts +39 -0
- package/dist/tools/typecheck-tools.d.ts.map +1 -0
- package/dist/tools/typecheck-tools.js +191 -0
- package/dist/tools/typecheck-tools.js.map +1 -0
- package/dist/tools/wiring-tools.d.ts +19 -0
- package/dist/tools/wiring-tools.d.ts.map +1 -0
- package/dist/tools/wiring-tools.js +147 -0
- package/dist/tools/wiring-tools.js.map +1 -0
- package/dist/types.d.ts +9 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/framework-detect.d.ts +18 -2
- package/dist/utils/framework-detect.d.ts.map +1 -1
- package/dist/utils/framework-detect.js +150 -3
- package/dist/utils/framework-detect.js.map +1 -1
- package/dist/utils/import-graph.d.ts +42 -0
- package/dist/utils/import-graph.d.ts.map +1 -1
- package/dist/utils/import-graph.js +248 -9
- package/dist/utils/import-graph.js.map +1 -1
- package/dist/utils/language-detect.d.ts +21 -0
- package/dist/utils/language-detect.d.ts.map +1 -0
- package/dist/utils/language-detect.js +183 -0
- package/dist/utils/language-detect.js.map +1 -0
- package/dist/utils/nextjs-ast-readers.d.ts +44 -0
- package/dist/utils/nextjs-ast-readers.d.ts.map +1 -0
- package/dist/utils/nextjs-ast-readers.js +341 -0
- package/dist/utils/nextjs-ast-readers.js.map +1 -0
- package/dist/utils/nextjs-audit-cache.d.ts +51 -0
- package/dist/utils/nextjs-audit-cache.d.ts.map +1 -0
- package/dist/utils/nextjs-audit-cache.js +116 -0
- package/dist/utils/nextjs-audit-cache.js.map +1 -0
- package/dist/utils/nextjs-metadata-readers.d.ts +65 -0
- package/dist/utils/nextjs-metadata-readers.d.ts.map +1 -0
- package/dist/utils/nextjs-metadata-readers.js +447 -0
- package/dist/utils/nextjs-metadata-readers.js.map +1 -0
- package/dist/utils/nextjs.d.ts +42 -0
- package/dist/utils/nextjs.d.ts.map +1 -0
- package/dist/utils/nextjs.js +284 -0
- package/dist/utils/nextjs.js.map +1 -0
- package/dist/utils/python-import-resolver.d.ts +42 -0
- package/dist/utils/python-import-resolver.d.ts.map +1 -0
- package/dist/utils/python-import-resolver.js +101 -0
- package/dist/utils/python-import-resolver.js.map +1 -0
- package/dist/utils/python-imports.d.ts +28 -0
- package/dist/utils/python-imports.d.ts.map +1 -0
- package/dist/utils/python-imports.js +117 -0
- package/dist/utils/python-imports.js.map +1 -0
- package/dist/utils/react-alias.d.ts +15 -0
- package/dist/utils/react-alias.d.ts.map +1 -0
- package/dist/utils/react-alias.js +31 -0
- package/dist/utils/react-alias.js.map +1 -0
- package/dist/utils/test-file.d.ts.map +1 -1
- package/dist/utils/test-file.js +7 -0
- package/dist/utils/test-file.js.map +1 -1
- package/dist/utils/walk.d.ts +22 -0
- package/dist/utils/walk.d.ts.map +1 -1
- package/dist/utils/walk.js +70 -2
- package/dist/utils/walk.js.map +1 -1
- package/package.json +4 -3
- package/rules/codesift.md +71 -5
- package/rules/codesift.mdc +71 -5
- package/rules/codex.md +71 -5
- package/rules/gemini.md +71 -5
- package/src/parser/languages/tree-sitter-javascript.wasm +0 -0
- package/src/parser/languages/tree-sitter-kotlin.wasm +0 -0
- package/src/parser/languages/tree-sitter-php.wasm +0 -0
- package/src/parser/languages/tree-sitter-php_only.wasm +0 -0
- package/src/parser/languages/tree-sitter-python.wasm +0 -0
|
@@ -0,0 +1,839 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* React-specific code intelligence tools.
|
|
3
|
+
*
|
|
4
|
+
* - traceComponentTree: BFS component composition tree (which components
|
|
5
|
+
* render which, via JSX usage)
|
|
6
|
+
* - analyzeHooks: hook inventory + Rule of Hooks violation detection
|
|
7
|
+
* - analyzeRenders: static re-render risk analysis (inline props, missing memo)
|
|
8
|
+
*/
|
|
9
|
+
import { getCodeIndex } from "./index-tools.js";
|
|
10
|
+
import { isTestFileStrict as isTestFile } from "../utils/test-file.js";
|
|
11
|
+
import { resolveAlias } from "../utils/react-alias.js";
|
|
12
|
+
export { buildJsxAdjacency, buildComponentTree, extractJsxComponents, extractHookCalls, extractHookNames, findRuleOfHooksViolations, findRenderRisks };
|
|
13
|
+
// formatRendersMarkdown and buildContextGraph are exported inline at definition site below
|
|
14
|
+
// ── Limits (mirror graph-tools.ts) ──────────────────────────
|
|
15
|
+
const MAX_TREE_NODES = 500;
|
|
16
|
+
const MAX_CHILDREN_PER_NODE = 20;
|
|
17
|
+
const DEFAULT_DEPTH = 3;
|
|
18
|
+
// ── React stdlib hooks (used as denylist in tracing and inventory) ──
|
|
19
|
+
export const REACT_STDLIB_HOOKS = new Set([
|
|
20
|
+
"useState", "useEffect", "useCallback", "useMemo", "useRef",
|
|
21
|
+
"useContext", "useReducer", "useLayoutEffect", "useImperativeHandle",
|
|
22
|
+
"useDebugValue", "useDeferredValue", "useTransition", "useId",
|
|
23
|
+
"useSyncExternalStore", "useInsertionEffect", "useOptimistic",
|
|
24
|
+
"useFormState", "useFormStatus", "use",
|
|
25
|
+
]);
|
|
26
|
+
/**
|
|
27
|
+
* Extract PascalCase JSX element names from source code.
|
|
28
|
+
* Returns the set of component names used via `<ComponentName ...>` or `<ComponentName/>`.
|
|
29
|
+
* HTML elements (lowercase) are skipped.
|
|
30
|
+
*/
|
|
31
|
+
function extractJsxComponents(source) {
|
|
32
|
+
const names = new Set();
|
|
33
|
+
const pattern = /<([A-Z][a-zA-Z0-9_$]*)\b/g;
|
|
34
|
+
let m;
|
|
35
|
+
while ((m = pattern.exec(source)) !== null) {
|
|
36
|
+
names.add(m[1]);
|
|
37
|
+
}
|
|
38
|
+
return names;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Build component → rendered-components adjacency from the index.
|
|
42
|
+
* Only considers symbols with kind "component" (requires Wave 1 extractor).
|
|
43
|
+
*/
|
|
44
|
+
function buildJsxAdjacency(symbols) {
|
|
45
|
+
const children = new Map();
|
|
46
|
+
// name → component symbols lookup (may have multiple definitions with same name)
|
|
47
|
+
const nameToComponents = new Map();
|
|
48
|
+
// file → component symbols lookup (used by alias-resolved imports)
|
|
49
|
+
const fileToComponents = new Map();
|
|
50
|
+
for (const sym of symbols) {
|
|
51
|
+
if (sym.kind !== "component")
|
|
52
|
+
continue;
|
|
53
|
+
const existing = nameToComponents.get(sym.name);
|
|
54
|
+
if (existing)
|
|
55
|
+
existing.push(sym);
|
|
56
|
+
else
|
|
57
|
+
nameToComponents.set(sym.name, [sym]);
|
|
58
|
+
const fileExisting = fileToComponents.get(sym.file);
|
|
59
|
+
if (fileExisting)
|
|
60
|
+
fileExisting.push(sym);
|
|
61
|
+
else
|
|
62
|
+
fileToComponents.set(sym.file, [sym]);
|
|
63
|
+
}
|
|
64
|
+
// Build a synthetic files list for resolveAlias() — needs only `path` field.
|
|
65
|
+
const filesList = [...fileToComponents.keys()].map((path) => ({ path }));
|
|
66
|
+
for (const sym of symbols) {
|
|
67
|
+
if (sym.kind !== "component" || !sym.source)
|
|
68
|
+
continue;
|
|
69
|
+
const rendered = extractJsxComponents(sym.source);
|
|
70
|
+
const childList = [];
|
|
71
|
+
// Parse alias imports in this component's source for fallback resolution
|
|
72
|
+
// (Tier 4 — Item 8 wired into trace_component_tree).
|
|
73
|
+
// Pattern: import { X, Y as Z } from "@/components/Button"
|
|
74
|
+
// Pattern: import Default from "@/components/Foo"
|
|
75
|
+
const aliasImports = new Map(); // localName → resolved file
|
|
76
|
+
const importRe = /import\s+(?:(\w+)|(?:\{([^}]+)\}))(?:\s*,\s*(?:(\w+)|(?:\{([^}]+)\})))?\s+from\s+["'](@\/[^"']+)["']/g;
|
|
77
|
+
let im;
|
|
78
|
+
while ((im = importRe.exec(sym.source)) !== null) {
|
|
79
|
+
const aliasPath = im[5];
|
|
80
|
+
const resolved = resolveAlias(aliasPath, filesList);
|
|
81
|
+
if (!resolved)
|
|
82
|
+
continue;
|
|
83
|
+
// default import
|
|
84
|
+
if (im[1])
|
|
85
|
+
aliasImports.set(im[1], resolved);
|
|
86
|
+
if (im[3])
|
|
87
|
+
aliasImports.set(im[3], resolved);
|
|
88
|
+
// named imports {A, B as C}
|
|
89
|
+
const namedGroup = im[2] ?? im[4];
|
|
90
|
+
if (namedGroup) {
|
|
91
|
+
for (const part of namedGroup.split(",")) {
|
|
92
|
+
const trimmed = part.trim();
|
|
93
|
+
// Handle "X as Y" → local name is Y
|
|
94
|
+
const asMatch = trimmed.match(/^(\w+)\s+as\s+(\w+)$/);
|
|
95
|
+
const localName = asMatch ? asMatch[2] : trimmed.replace(/\s.*$/, "");
|
|
96
|
+
if (localName)
|
|
97
|
+
aliasImports.set(localName, resolved);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
for (const name of rendered) {
|
|
102
|
+
if (name === sym.name)
|
|
103
|
+
continue; // skip self-reference
|
|
104
|
+
const targets = nameToComponents.get(name);
|
|
105
|
+
if (targets) {
|
|
106
|
+
for (const target of targets) {
|
|
107
|
+
if (target.id !== sym.id)
|
|
108
|
+
childList.push(target);
|
|
109
|
+
}
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
// Fallback: alias-resolved import lookup
|
|
113
|
+
const aliasTargetFile = aliasImports.get(name);
|
|
114
|
+
if (aliasTargetFile) {
|
|
115
|
+
const fileTargets = fileToComponents.get(aliasTargetFile) ?? [];
|
|
116
|
+
for (const target of fileTargets) {
|
|
117
|
+
if (target.id !== sym.id)
|
|
118
|
+
childList.push(target);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
if (childList.length > 0)
|
|
123
|
+
children.set(sym.id, childList);
|
|
124
|
+
}
|
|
125
|
+
return { children };
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* BFS the component composition tree from a root component.
|
|
129
|
+
* Returns a CallNode tree of rendered components up to maxDepth.
|
|
130
|
+
*/
|
|
131
|
+
function buildComponentTree(root, adjacency, maxDepth) {
|
|
132
|
+
const visited = new Set([root.id]);
|
|
133
|
+
let totalNodes = 1;
|
|
134
|
+
function expand(symbol, depth) {
|
|
135
|
+
if (depth >= maxDepth || totalNodes >= MAX_TREE_NODES) {
|
|
136
|
+
return { symbol, children: [] };
|
|
137
|
+
}
|
|
138
|
+
const kids = adjacency.children.get(symbol.id) ?? [];
|
|
139
|
+
const out = [];
|
|
140
|
+
for (const kid of kids) {
|
|
141
|
+
if (totalNodes >= MAX_TREE_NODES)
|
|
142
|
+
break;
|
|
143
|
+
if (out.length >= MAX_CHILDREN_PER_NODE)
|
|
144
|
+
break;
|
|
145
|
+
if (visited.has(kid.id))
|
|
146
|
+
continue;
|
|
147
|
+
visited.add(kid.id);
|
|
148
|
+
totalNodes++;
|
|
149
|
+
out.push(expand(kid, depth + 1));
|
|
150
|
+
}
|
|
151
|
+
return { symbol, children: out };
|
|
152
|
+
}
|
|
153
|
+
return expand(root, 0);
|
|
154
|
+
}
|
|
155
|
+
function stripSource(sym) {
|
|
156
|
+
const { source: _, repo: _r, tokens: _t, start_col: _sc, end_col: _ec, id, ...rest } = sym;
|
|
157
|
+
const shortId = id.includes(":") ? id.slice(id.indexOf(":") + 1) : id;
|
|
158
|
+
return { ...rest, id: shortId };
|
|
159
|
+
}
|
|
160
|
+
function stripTreeSource(node) {
|
|
161
|
+
return {
|
|
162
|
+
symbol: stripSource(node.symbol),
|
|
163
|
+
children: node.children.map(stripTreeSource),
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
function treeToMermaid(tree, rootName) {
|
|
167
|
+
const lines = ["graph TD"];
|
|
168
|
+
const visited = new Set();
|
|
169
|
+
function nodeId(sym) {
|
|
170
|
+
return sym.id.replace(/[^a-zA-Z0-9_]/g, "_");
|
|
171
|
+
}
|
|
172
|
+
function nodeLabel(sym) {
|
|
173
|
+
const shortFile = sym.file.split("/").pop() ?? sym.file;
|
|
174
|
+
return `${sym.name}<br/><small>${shortFile}:${sym.start_line}</small>`;
|
|
175
|
+
}
|
|
176
|
+
function walk(node, parentId) {
|
|
177
|
+
const id = nodeId(node.symbol);
|
|
178
|
+
if (!visited.has(id)) {
|
|
179
|
+
visited.add(id);
|
|
180
|
+
lines.push(` ${id}["${nodeLabel(node.symbol)}"]`);
|
|
181
|
+
}
|
|
182
|
+
if (parentId) {
|
|
183
|
+
lines.push(` ${parentId} --> ${id}`);
|
|
184
|
+
}
|
|
185
|
+
for (const child of node.children) {
|
|
186
|
+
walk(child, id);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
walk(tree);
|
|
190
|
+
lines.push(` %% root: ${rootName}`);
|
|
191
|
+
return lines.join("\n");
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Trace the React component composition tree from a root component.
|
|
195
|
+
* Returns a tree of JSX children (which components the root renders,
|
|
196
|
+
* and recursively their children).
|
|
197
|
+
*/
|
|
198
|
+
export async function traceComponentTree(repo, rootComponent, options) {
|
|
199
|
+
const index = await getCodeIndex(repo);
|
|
200
|
+
if (!index) {
|
|
201
|
+
throw new Error(`Repository not found: ${repo}`);
|
|
202
|
+
}
|
|
203
|
+
const maxDepth = options?.depth ?? DEFAULT_DEPTH;
|
|
204
|
+
const outputFormat = options?.output_format ?? "json";
|
|
205
|
+
const includeSource = options?.include_source ?? false;
|
|
206
|
+
const includeTests = options?.include_tests ?? false;
|
|
207
|
+
const symbols = includeTests
|
|
208
|
+
? index.symbols
|
|
209
|
+
: index.symbols.filter((s) => !isTestFile(s.file));
|
|
210
|
+
// Find root component
|
|
211
|
+
const candidates = symbols.filter((s) => s.name === rootComponent && s.kind === "component");
|
|
212
|
+
const target = candidates[0];
|
|
213
|
+
if (!target) {
|
|
214
|
+
throw new Error(`Component "${rootComponent}" not found in repository "${repo}". ` +
|
|
215
|
+
`Make sure it has kind: "component" — index may need refresh.`);
|
|
216
|
+
}
|
|
217
|
+
const adjacency = buildJsxAdjacency(symbols);
|
|
218
|
+
const tree = buildComponentTree(target, adjacency, maxDepth);
|
|
219
|
+
if (outputFormat === "mermaid") {
|
|
220
|
+
return { mermaid: treeToMermaid(tree, rootComponent), root: rootComponent, depth: maxDepth };
|
|
221
|
+
}
|
|
222
|
+
return includeSource ? tree : stripTreeSource(tree);
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Scan a component/hook source for Rule of Hooks violations.
|
|
226
|
+
* Detects: hook inside if/for/while/switch, hook after early return.
|
|
227
|
+
* Returns a list of human-readable violation descriptions.
|
|
228
|
+
*/
|
|
229
|
+
function findRuleOfHooksViolations(source) {
|
|
230
|
+
const violations = [];
|
|
231
|
+
// Heuristic: hook call inside if/for/while/switch block
|
|
232
|
+
const conditionalHook = /\b(if|for|while|switch)\s*\([^)]*\)\s*\{[^}]*\b(use[A-Z]\w*)\s*\(/;
|
|
233
|
+
const condMatch = conditionalHook.exec(source);
|
|
234
|
+
if (condMatch) {
|
|
235
|
+
violations.push(`Hook "${condMatch[2]}" called inside ${condMatch[1]} block — violates Rule of Hooks`);
|
|
236
|
+
}
|
|
237
|
+
// Heuristic: hook after early return
|
|
238
|
+
const earlyReturnHook = /\breturn\s+[^;{]*;\s*\n[\s\S]*?\b(use[A-Z]\w*)\s*\(/;
|
|
239
|
+
const earlyMatch = earlyReturnHook.exec(source);
|
|
240
|
+
if (earlyMatch) {
|
|
241
|
+
violations.push(`Hook "${earlyMatch[1]}" called after early return — violates Rule of Hooks`);
|
|
242
|
+
}
|
|
243
|
+
return violations;
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Extract unique hook names from source (no line numbers, no cap).
|
|
247
|
+
* Use when you only need the set of hooks — e.g. React context bundle.
|
|
248
|
+
*/
|
|
249
|
+
function extractHookNames(source) {
|
|
250
|
+
const names = new Set();
|
|
251
|
+
const pattern = /\b(use[A-Z]\w*)\s*\(/g;
|
|
252
|
+
let m;
|
|
253
|
+
while ((m = pattern.exec(source)) !== null) {
|
|
254
|
+
names.add(m[1]);
|
|
255
|
+
}
|
|
256
|
+
return names;
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* Extract hook calls from source with their relative line number.
|
|
260
|
+
*/
|
|
261
|
+
function extractHookCalls(source) {
|
|
262
|
+
const calls = [];
|
|
263
|
+
const lines = source.split("\n");
|
|
264
|
+
const pattern = /\b(use[A-Z]\w*)\s*\(/;
|
|
265
|
+
for (let i = 0; i < lines.length && calls.length < 20; i++) {
|
|
266
|
+
const line = lines[i];
|
|
267
|
+
const m = pattern.exec(line);
|
|
268
|
+
if (m) {
|
|
269
|
+
calls.push({
|
|
270
|
+
name: m[1],
|
|
271
|
+
line: i + 1,
|
|
272
|
+
is_stdlib: REACT_STDLIB_HOOKS.has(m[1]),
|
|
273
|
+
context: line.trim().slice(0, 160),
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
return calls;
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* Analyze hook usage across components and custom hooks in a repo.
|
|
281
|
+
*
|
|
282
|
+
* Returns:
|
|
283
|
+
* - per-symbol inventory (hooks called, violations)
|
|
284
|
+
* - codebase-wide hook usage summary
|
|
285
|
+
* - Rule of Hooks violation count
|
|
286
|
+
*/
|
|
287
|
+
export async function analyzeHooks(repo, options) {
|
|
288
|
+
const index = await getCodeIndex(repo);
|
|
289
|
+
if (!index) {
|
|
290
|
+
throw new Error(`Repository not found: ${repo}`);
|
|
291
|
+
}
|
|
292
|
+
const componentName = options?.component_name;
|
|
293
|
+
const filePattern = options?.file_pattern;
|
|
294
|
+
const includeTests = options?.include_tests ?? false;
|
|
295
|
+
const maxEntries = options?.max_entries ?? 100;
|
|
296
|
+
// Filter symbols to components and hooks
|
|
297
|
+
let symbols = index.symbols.filter((s) => s.kind === "component" || s.kind === "hook");
|
|
298
|
+
if (!includeTests)
|
|
299
|
+
symbols = symbols.filter((s) => !isTestFile(s.file));
|
|
300
|
+
if (componentName)
|
|
301
|
+
symbols = symbols.filter((s) => s.name === componentName);
|
|
302
|
+
if (filePattern)
|
|
303
|
+
symbols = symbols.filter((s) => s.file.includes(filePattern));
|
|
304
|
+
const entries = [];
|
|
305
|
+
const globalHookCount = new Map();
|
|
306
|
+
let totalComponents = 0;
|
|
307
|
+
let totalHooks = 0;
|
|
308
|
+
let violationsCount = 0;
|
|
309
|
+
for (const sym of symbols) {
|
|
310
|
+
if (!sym.source)
|
|
311
|
+
continue;
|
|
312
|
+
const hookCalls = extractHookCalls(sym.source);
|
|
313
|
+
const violations = findRuleOfHooksViolations(sym.source);
|
|
314
|
+
if (sym.kind === "component")
|
|
315
|
+
totalComponents++;
|
|
316
|
+
else if (sym.kind === "hook")
|
|
317
|
+
totalHooks++;
|
|
318
|
+
// Skip empty entries (no hooks, no violations)
|
|
319
|
+
if (hookCalls.length === 0 && violations.length === 0)
|
|
320
|
+
continue;
|
|
321
|
+
violationsCount += violations.length;
|
|
322
|
+
for (const call of hookCalls) {
|
|
323
|
+
globalHookCount.set(call.name, (globalHookCount.get(call.name) ?? 0) + 1);
|
|
324
|
+
}
|
|
325
|
+
entries.push({
|
|
326
|
+
name: sym.name,
|
|
327
|
+
kind: sym.kind,
|
|
328
|
+
file: sym.file,
|
|
329
|
+
start_line: sym.start_line,
|
|
330
|
+
hook_count: hookCalls.length,
|
|
331
|
+
hooks: hookCalls,
|
|
332
|
+
violations,
|
|
333
|
+
});
|
|
334
|
+
if (entries.length >= maxEntries)
|
|
335
|
+
break;
|
|
336
|
+
}
|
|
337
|
+
// Sort entries: violations first, then by hook_count descending
|
|
338
|
+
entries.sort((a, b) => {
|
|
339
|
+
const vdiff = b.violations.length - a.violations.length;
|
|
340
|
+
if (vdiff !== 0)
|
|
341
|
+
return vdiff;
|
|
342
|
+
return b.hook_count - a.hook_count;
|
|
343
|
+
});
|
|
344
|
+
const hook_usage = [...globalHookCount.entries()]
|
|
345
|
+
.sort((a, b) => b[1] - a[1])
|
|
346
|
+
.slice(0, 20)
|
|
347
|
+
.map(([name, count]) => ({
|
|
348
|
+
name,
|
|
349
|
+
count,
|
|
350
|
+
is_stdlib: REACT_STDLIB_HOOKS.has(name),
|
|
351
|
+
}));
|
|
352
|
+
return {
|
|
353
|
+
entries,
|
|
354
|
+
total_components: totalComponents,
|
|
355
|
+
total_custom_hooks: totalHooks,
|
|
356
|
+
hook_usage,
|
|
357
|
+
violations_count: violationsCount,
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
/** Patterns for inline prop creation in JSX — each creates a new reference every render */
|
|
361
|
+
const INLINE_OBJECT_RE = /\b\w+\s*=\s*\{\s*\{/g; // prop={{ ... }}
|
|
362
|
+
const INLINE_ARRAY_RE = /\b\w+\s*=\s*\{\s*\[/g; // prop={[ ... ]}
|
|
363
|
+
const INLINE_FN_RE = /\bon[A-Z]\w*\s*=\s*\{\s*(?:\([^)]*\)|[a-z_$][\w$]*)\s*=>/g; // onX={() => ...}
|
|
364
|
+
/** Default prop values that create new references: = [] or = {} in params */
|
|
365
|
+
const UNSTABLE_DEFAULT_RE = /(?:[:,]\s*)(\w+)\s*=\s*(\[\s*\]|\{\s*\})/g;
|
|
366
|
+
/**
|
|
367
|
+
* Analyze a single component source for re-render risks.
|
|
368
|
+
*/
|
|
369
|
+
function findRenderRisks(source) {
|
|
370
|
+
const risks = [];
|
|
371
|
+
const lines = source.split("\n");
|
|
372
|
+
for (let i = 0; i < lines.length && risks.length < 20; i++) {
|
|
373
|
+
const line = lines[i];
|
|
374
|
+
const lineNum = i + 1;
|
|
375
|
+
const trimmed = line.trim();
|
|
376
|
+
// Skip comments
|
|
377
|
+
if (trimmed.startsWith("//") || trimmed.startsWith("*"))
|
|
378
|
+
continue;
|
|
379
|
+
// Inline object prop: prop={{ key: val }}
|
|
380
|
+
if (INLINE_OBJECT_RE.test(line)) {
|
|
381
|
+
INLINE_OBJECT_RE.lastIndex = 0;
|
|
382
|
+
risks.push({
|
|
383
|
+
type: "inline-object",
|
|
384
|
+
line: lineNum,
|
|
385
|
+
context: trimmed.slice(0, 120),
|
|
386
|
+
suggestion: "Extract to a const or useMemo — object literal creates new reference every render",
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
// Inline array prop: prop={[1, 2, 3]}
|
|
390
|
+
if (INLINE_ARRAY_RE.test(line)) {
|
|
391
|
+
INLINE_ARRAY_RE.lastIndex = 0;
|
|
392
|
+
risks.push({
|
|
393
|
+
type: "inline-array",
|
|
394
|
+
line: lineNum,
|
|
395
|
+
context: trimmed.slice(0, 120),
|
|
396
|
+
suggestion: "Extract to a const or useMemo — array literal creates new reference every render",
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
// Inline function in event handler: onClick={() => ...}
|
|
400
|
+
if (INLINE_FN_RE.test(line)) {
|
|
401
|
+
INLINE_FN_RE.lastIndex = 0;
|
|
402
|
+
risks.push({
|
|
403
|
+
type: "inline-function",
|
|
404
|
+
line: lineNum,
|
|
405
|
+
context: trimmed.slice(0, 120),
|
|
406
|
+
suggestion: "Extract to useCallback or a named handler — arrow function creates new reference every render",
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
// Unstable default value: { items = [], config = {} }
|
|
410
|
+
UNSTABLE_DEFAULT_RE.lastIndex = 0;
|
|
411
|
+
let dm;
|
|
412
|
+
while ((dm = UNSTABLE_DEFAULT_RE.exec(line)) !== null && risks.length < 20) {
|
|
413
|
+
risks.push({
|
|
414
|
+
type: "unstable-default",
|
|
415
|
+
line: lineNum,
|
|
416
|
+
context: trimmed.slice(0, 120),
|
|
417
|
+
suggestion: `Default value \`${dm[1]} = ${dm[2]}\` creates new reference every render — hoist to module const`,
|
|
418
|
+
});
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
return risks;
|
|
422
|
+
}
|
|
423
|
+
/**
|
|
424
|
+
* Analyze re-render risk across React components in a repo.
|
|
425
|
+
*
|
|
426
|
+
* Detects:
|
|
427
|
+
* - Inline object/array/function props in JSX (new reference every render)
|
|
428
|
+
* - Unstable default parameter values ([] or {} in component params)
|
|
429
|
+
* - Components not wrapped in React.memo that render children (missing-memo)
|
|
430
|
+
*
|
|
431
|
+
* Returns per-component risk assessment with actionable suggestions.
|
|
432
|
+
*/
|
|
433
|
+
/**
|
|
434
|
+
* Format an AnalyzeRendersResult as human-readable Markdown.
|
|
435
|
+
* Used by analyzeRenders when format="markdown" is requested.
|
|
436
|
+
*/
|
|
437
|
+
export function formatRendersMarkdown(result) {
|
|
438
|
+
const lines = [];
|
|
439
|
+
lines.push("# Render Analysis");
|
|
440
|
+
lines.push("");
|
|
441
|
+
lines.push(`Total components: ${result.total_components} | High risk: ${result.high_risk_count}`);
|
|
442
|
+
lines.push("");
|
|
443
|
+
lines.push("## Summary");
|
|
444
|
+
lines.push(`- inline_objects: ${result.summary.inline_objects}`);
|
|
445
|
+
lines.push(`- inline_arrays: ${result.summary.inline_arrays}`);
|
|
446
|
+
lines.push(`- inline_functions: ${result.summary.inline_functions}`);
|
|
447
|
+
lines.push(`- unstable_defaults: ${result.summary.unstable_defaults}`);
|
|
448
|
+
lines.push(`- missing_memo: ${result.summary.missing_memo}`);
|
|
449
|
+
lines.push("");
|
|
450
|
+
lines.push("## Entries");
|
|
451
|
+
lines.push("");
|
|
452
|
+
lines.push("| Component | File | Risk | Issues | Children | Memo |");
|
|
453
|
+
lines.push("|-----------|------|------|--------|----------|------|");
|
|
454
|
+
for (const e of result.entries) {
|
|
455
|
+
const file = e.file.length > 40 ? "…" + e.file.slice(-39) : e.file;
|
|
456
|
+
lines.push(`| ${e.name} | ${file}:${e.start_line} | ${e.risk_level} | ${e.risk_count} | ${e.children_count} | ${e.is_memoized ? "✓" : "✗"} |`);
|
|
457
|
+
}
|
|
458
|
+
return lines.join("\n");
|
|
459
|
+
}
|
|
460
|
+
export async function analyzeRenders(repo, options) {
|
|
461
|
+
const index = await getCodeIndex(repo);
|
|
462
|
+
if (!index) {
|
|
463
|
+
throw new Error(`Repository not found: ${repo}`);
|
|
464
|
+
}
|
|
465
|
+
const componentName = options?.component_name;
|
|
466
|
+
const filePattern = options?.file_pattern;
|
|
467
|
+
const includeTests = options?.include_tests ?? false;
|
|
468
|
+
const maxEntries = options?.max_entries ?? 100;
|
|
469
|
+
let components = index.symbols.filter((s) => s.kind === "component");
|
|
470
|
+
if (!includeTests)
|
|
471
|
+
components = components.filter((s) => !isTestFile(s.file));
|
|
472
|
+
if (componentName)
|
|
473
|
+
components = components.filter((s) => s.name === componentName);
|
|
474
|
+
if (filePattern)
|
|
475
|
+
components = components.filter((s) => s.file.includes(filePattern));
|
|
476
|
+
const entries = [];
|
|
477
|
+
const summary = { inline_objects: 0, inline_arrays: 0, inline_functions: 0, unstable_defaults: 0, missing_memo: 0 };
|
|
478
|
+
let highRiskCount = 0;
|
|
479
|
+
for (const sym of components) {
|
|
480
|
+
if (entries.length >= maxEntries)
|
|
481
|
+
break;
|
|
482
|
+
if (!sym.source)
|
|
483
|
+
continue;
|
|
484
|
+
const isMemoized = /\b(?:React\.)?memo\s*\(/.test(sym.source);
|
|
485
|
+
const risks = findRenderRisks(sym.source);
|
|
486
|
+
// Count children rendered (PascalCase JSX elements)
|
|
487
|
+
const childrenSet = new Set();
|
|
488
|
+
const jsxPattern = /<([A-Z][a-zA-Z0-9_$]*)\b/g;
|
|
489
|
+
let jm;
|
|
490
|
+
while ((jm = jsxPattern.exec(sym.source)) !== null) {
|
|
491
|
+
if (jm[1] !== sym.name)
|
|
492
|
+
childrenSet.add(jm[1]);
|
|
493
|
+
}
|
|
494
|
+
// Check missing-memo: component renders children and is not memoized
|
|
495
|
+
if (!isMemoized && childrenSet.size > 0 && risks.length > 0) {
|
|
496
|
+
risks.push({
|
|
497
|
+
type: "missing-memo",
|
|
498
|
+
line: 1,
|
|
499
|
+
context: `${sym.name} renders ${childrenSet.size} child component(s) and has ${risks.length} inline props`,
|
|
500
|
+
suggestion: "Wrap in React.memo() — parent re-renders will propagate to children via new prop references",
|
|
501
|
+
});
|
|
502
|
+
summary.missing_memo++;
|
|
503
|
+
}
|
|
504
|
+
// Aggregate summary counts
|
|
505
|
+
for (const r of risks) {
|
|
506
|
+
if (r.type === "inline-object")
|
|
507
|
+
summary.inline_objects++;
|
|
508
|
+
else if (r.type === "inline-array")
|
|
509
|
+
summary.inline_arrays++;
|
|
510
|
+
else if (r.type === "inline-function")
|
|
511
|
+
summary.inline_functions++;
|
|
512
|
+
else if (r.type === "unstable-default")
|
|
513
|
+
summary.unstable_defaults++;
|
|
514
|
+
}
|
|
515
|
+
// Risk level classification — factors children_count to amplify components
|
|
516
|
+
// that propagate prop instability to many children (Bug #4 fix).
|
|
517
|
+
const riskLevel = ((risks.length >= 3 && childrenSet.size >= 3) || risks.length >= 5) ? "high" :
|
|
518
|
+
(risks.length >= 2 || (risks.length >= 1 && childrenSet.size >= 1)) ? "medium" : "low";
|
|
519
|
+
if (riskLevel === "high")
|
|
520
|
+
highRiskCount++;
|
|
521
|
+
// Only include components with risks or if specifically queried
|
|
522
|
+
if (risks.length > 0 || componentName) {
|
|
523
|
+
entries.push({
|
|
524
|
+
name: sym.name,
|
|
525
|
+
file: sym.file,
|
|
526
|
+
start_line: sym.start_line,
|
|
527
|
+
is_memoized: isMemoized,
|
|
528
|
+
risk_count: risks.length,
|
|
529
|
+
risk_level: riskLevel,
|
|
530
|
+
risks,
|
|
531
|
+
children_count: childrenSet.size,
|
|
532
|
+
});
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
// Sort: high risk first, then by risk_count descending
|
|
536
|
+
entries.sort((a, b) => {
|
|
537
|
+
const levelOrder = { high: 3, medium: 2, low: 1 };
|
|
538
|
+
const ld = levelOrder[b.risk_level] - levelOrder[a.risk_level];
|
|
539
|
+
if (ld !== 0)
|
|
540
|
+
return ld;
|
|
541
|
+
return b.risk_count - a.risk_count;
|
|
542
|
+
});
|
|
543
|
+
const jsonResult = {
|
|
544
|
+
entries,
|
|
545
|
+
total_components: components.length,
|
|
546
|
+
high_risk_count: highRiskCount,
|
|
547
|
+
summary,
|
|
548
|
+
};
|
|
549
|
+
if (options?.format === "markdown") {
|
|
550
|
+
return formatRendersMarkdown(jsonResult);
|
|
551
|
+
}
|
|
552
|
+
return jsonResult;
|
|
553
|
+
}
|
|
554
|
+
const MAX_CONTEXT_SYMBOLS = 500;
|
|
555
|
+
/**
|
|
556
|
+
* Build a graph of React Context flows: createContext → Provider → useContext consumers.
|
|
557
|
+
*
|
|
558
|
+
* Single-pass scan over all symbols (capped at 500). No cycle detection — relies
|
|
559
|
+
* on visited set keyed by context name to prevent re-processing.
|
|
560
|
+
*
|
|
561
|
+
* Detection patterns:
|
|
562
|
+
* - createContext call: `const X = createContext(...)` or `const X = React.createContext(...)`
|
|
563
|
+
* - Provider usage: `<X.Provider value={...}>` (anywhere in any source)
|
|
564
|
+
* - Consumer usage: `useContext(X)` (anywhere in any source)
|
|
565
|
+
*
|
|
566
|
+
* Phase 2 features (not implemented here):
|
|
567
|
+
* - Cycle detection in provider chains
|
|
568
|
+
* - Re-render impact analysis ("which consumers re-render when X changes")
|
|
569
|
+
* - Context value type tracking
|
|
570
|
+
*/
|
|
571
|
+
export function buildContextGraph(symbols) {
|
|
572
|
+
const contexts = new Map();
|
|
573
|
+
const createPattern = /\bconst\s+(\w+)\s*(?::[^=]+)?\s*=\s*(?:React\.)?createContext\b/g;
|
|
574
|
+
// Pass 1: Find context definitions
|
|
575
|
+
let scanned = 0;
|
|
576
|
+
for (const sym of symbols) {
|
|
577
|
+
if (scanned >= MAX_CONTEXT_SYMBOLS)
|
|
578
|
+
break;
|
|
579
|
+
if (!sym.source)
|
|
580
|
+
continue;
|
|
581
|
+
scanned++;
|
|
582
|
+
let m;
|
|
583
|
+
createPattern.lastIndex = 0;
|
|
584
|
+
while ((m = createPattern.exec(sym.source)) !== null) {
|
|
585
|
+
const name = m[1];
|
|
586
|
+
if (contexts.has(name))
|
|
587
|
+
continue; // visited — skip duplicate definition
|
|
588
|
+
// Compute line offset within symbol source
|
|
589
|
+
const linesBefore = sym.source.slice(0, m.index).split("\n").length;
|
|
590
|
+
contexts.set(name, {
|
|
591
|
+
name,
|
|
592
|
+
created_in: { file: sym.file, line: sym.start_line + linesBefore - 1 },
|
|
593
|
+
providers: [],
|
|
594
|
+
consumers: [],
|
|
595
|
+
});
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
if (contexts.size === 0)
|
|
599
|
+
return { contexts: [] };
|
|
600
|
+
// Pass 2: Find providers and consumers
|
|
601
|
+
scanned = 0;
|
|
602
|
+
for (const sym of symbols) {
|
|
603
|
+
if (scanned >= MAX_CONTEXT_SYMBOLS)
|
|
604
|
+
break;
|
|
605
|
+
if (!sym.source)
|
|
606
|
+
continue;
|
|
607
|
+
if (sym.kind !== "component" && sym.kind !== "hook")
|
|
608
|
+
continue;
|
|
609
|
+
scanned++;
|
|
610
|
+
for (const [ctxName, info] of contexts) {
|
|
611
|
+
// Provider: <X.Provider
|
|
612
|
+
const providerRe = new RegExp(`<${ctxName}\\.Provider\\b`);
|
|
613
|
+
if (providerRe.test(sym.source)) {
|
|
614
|
+
info.providers.push({ file: sym.file, line: sym.start_line });
|
|
615
|
+
}
|
|
616
|
+
// Consumer: useContext(X)
|
|
617
|
+
const consumerRe = new RegExp(`useContext\\s*\\(\\s*${ctxName}\\b`);
|
|
618
|
+
if (consumerRe.test(sym.source)) {
|
|
619
|
+
info.consumers.push({
|
|
620
|
+
file: sym.file,
|
|
621
|
+
component: sym.name,
|
|
622
|
+
line: sym.start_line,
|
|
623
|
+
});
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
return { contexts: [...contexts.values()] };
|
|
628
|
+
}
|
|
629
|
+
// ─────────────────────────────────────────────────────────────
|
|
630
|
+
// audit_compiler_readiness — React Compiler adoption readiness
|
|
631
|
+
// ─────────────────────────────────────────────────────────────
|
|
632
|
+
const COMPILER_PATTERNS = [
|
|
633
|
+
"compiler-side-effect-in-render",
|
|
634
|
+
"compiler-ref-read-in-render",
|
|
635
|
+
"compiler-prop-mutation",
|
|
636
|
+
"compiler-state-mutation",
|
|
637
|
+
"compiler-try-catch-bailout",
|
|
638
|
+
"compiler-redundant-memo",
|
|
639
|
+
"compiler-redundant-usecallback",
|
|
640
|
+
];
|
|
641
|
+
/**
|
|
642
|
+
* Audit a React codebase for React Compiler (v1.0) adoption readiness.
|
|
643
|
+
*
|
|
644
|
+
* Scans all components for patterns that cause the compiler to silently
|
|
645
|
+
* bail out of auto-memoization. Returns a readiness score (0-100) with
|
|
646
|
+
* prioritized fix list.
|
|
647
|
+
*
|
|
648
|
+
* No competitor offers codebase-wide compiler readiness analysis.
|
|
649
|
+
*/
|
|
650
|
+
export async function auditCompilerReadiness(repo, options) {
|
|
651
|
+
const { searchPatterns } = await import("./pattern-tools.js");
|
|
652
|
+
const index = await getCodeIndex(repo);
|
|
653
|
+
if (!index)
|
|
654
|
+
throw new Error(`Repository not found: ${repo}`);
|
|
655
|
+
const includeTests = options?.include_tests ?? false;
|
|
656
|
+
const filePattern = options?.file_pattern;
|
|
657
|
+
// Count total components
|
|
658
|
+
let components = index.symbols.filter((s) => s.kind === "component");
|
|
659
|
+
if (!includeTests)
|
|
660
|
+
components = components.filter((s) => !isTestFile(s.file));
|
|
661
|
+
if (filePattern)
|
|
662
|
+
components = components.filter((s) => s.file.includes(filePattern));
|
|
663
|
+
const totalComponents = components.length;
|
|
664
|
+
// Run all compiler patterns in parallel
|
|
665
|
+
const patternResults = await Promise.all(COMPILER_PATTERNS.map(async (pattern) => {
|
|
666
|
+
try {
|
|
667
|
+
const result = await searchPatterns(repo, pattern, {
|
|
668
|
+
file_pattern: filePattern,
|
|
669
|
+
include_tests: includeTests,
|
|
670
|
+
max_results: 200,
|
|
671
|
+
});
|
|
672
|
+
return { pattern, matches: result.matches, description: result.pattern };
|
|
673
|
+
}
|
|
674
|
+
catch {
|
|
675
|
+
return { pattern, matches: [], description: pattern };
|
|
676
|
+
}
|
|
677
|
+
}));
|
|
678
|
+
// Aggregate: which components have bailout issues
|
|
679
|
+
const componentIssues = new Map(); // "name@file" → issue count
|
|
680
|
+
let redundantMemoization = 0;
|
|
681
|
+
const issues = [];
|
|
682
|
+
for (const { pattern, matches, description } of patternResults) {
|
|
683
|
+
if (matches.length === 0)
|
|
684
|
+
continue;
|
|
685
|
+
const isRedundant = pattern === "compiler-redundant-memo" || pattern === "compiler-redundant-usecallback";
|
|
686
|
+
if (isRedundant) {
|
|
687
|
+
redundantMemoization += matches.length;
|
|
688
|
+
}
|
|
689
|
+
issues.push({
|
|
690
|
+
pattern,
|
|
691
|
+
count: matches.length,
|
|
692
|
+
description: description.split(": ").slice(1).join(": ") || description,
|
|
693
|
+
});
|
|
694
|
+
for (const m of matches) {
|
|
695
|
+
if (!isRedundant) {
|
|
696
|
+
const key = `${m.name}@${m.file}`;
|
|
697
|
+
componentIssues.set(key, (componentIssues.get(key) ?? 0) + 1);
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
// Sort issues by count descending
|
|
702
|
+
issues.sort((a, b) => b.count - a.count);
|
|
703
|
+
// Top bailout components
|
|
704
|
+
const top_bailout_components = [...componentIssues.entries()]
|
|
705
|
+
.sort((a, b) => b[1] - a[1])
|
|
706
|
+
.slice(0, 10)
|
|
707
|
+
.map(([key, count]) => {
|
|
708
|
+
const [name, file] = key.split("@");
|
|
709
|
+
return { name: name, file: file, issues: count };
|
|
710
|
+
});
|
|
711
|
+
const bailoutCount = componentIssues.size;
|
|
712
|
+
const compatibleCount = Math.max(0, totalComponents - bailoutCount);
|
|
713
|
+
// Readiness score: percentage of components without bailout issues
|
|
714
|
+
const readiness_score = totalComponents > 0
|
|
715
|
+
? Math.round((compatibleCount / totalComponents) * 100)
|
|
716
|
+
: 100; // empty repo = ready
|
|
717
|
+
return {
|
|
718
|
+
readiness_score,
|
|
719
|
+
total_components: totalComponents,
|
|
720
|
+
compatible_components: compatibleCount,
|
|
721
|
+
bailout_components: bailoutCount,
|
|
722
|
+
redundant_memoization: redundantMemoization,
|
|
723
|
+
issues,
|
|
724
|
+
top_bailout_components,
|
|
725
|
+
};
|
|
726
|
+
}
|
|
727
|
+
/**
|
|
728
|
+
* Day-1 onboarding composite for React projects. Single call that runs:
|
|
729
|
+
* - Component/hook inventory
|
|
730
|
+
* - Stack detection (state mgmt, routing, UI lib, form lib, build tool)
|
|
731
|
+
* - Critical pattern scan (XSS, Rule of Hooks, memory leaks)
|
|
732
|
+
* - Top hook usage summary
|
|
733
|
+
* - Suggested follow-up queries
|
|
734
|
+
*
|
|
735
|
+
* Meant to be the first tool a React developer runs on an unfamiliar codebase.
|
|
736
|
+
* Replaces 5-6 manual tool calls with one structured report.
|
|
737
|
+
*/
|
|
738
|
+
export async function reactQuickstart(repo) {
|
|
739
|
+
const { searchPatterns } = await import("./pattern-tools.js");
|
|
740
|
+
const { analyzeProject } = await import("./project-tools.js");
|
|
741
|
+
const index = await getCodeIndex(repo);
|
|
742
|
+
if (!index)
|
|
743
|
+
throw new Error(`Repository not found: ${repo}`);
|
|
744
|
+
// Inventory
|
|
745
|
+
const components = index.symbols.filter((s) => s.kind === "component" && !isTestFile(s.file));
|
|
746
|
+
const hooks = index.symbols.filter((s) => s.kind === "hook" && !isTestFile(s.file));
|
|
747
|
+
// Find likely root component: prefer App > Root > Main > Layout > Page
|
|
748
|
+
const rootNames = ["App", "Root", "Main", "Layout", "Page"];
|
|
749
|
+
const likelyRoot = components.find((c) => rootNames.includes(c.name))?.name
|
|
750
|
+
?? components[0]?.name
|
|
751
|
+
?? null;
|
|
752
|
+
// Stack detection via analyze_project
|
|
753
|
+
let stack = {
|
|
754
|
+
state_management: null,
|
|
755
|
+
routing: null,
|
|
756
|
+
ui_library: null,
|
|
757
|
+
form_library: null,
|
|
758
|
+
build_tool: null,
|
|
759
|
+
};
|
|
760
|
+
try {
|
|
761
|
+
const proj = await analyzeProject(repo, { force: false });
|
|
762
|
+
const rc = proj?.conventions?.react_conventions;
|
|
763
|
+
const si = proj?.stack;
|
|
764
|
+
if (rc) {
|
|
765
|
+
stack = {
|
|
766
|
+
state_management: rc.state_management ?? null,
|
|
767
|
+
routing: rc.routing ?? null,
|
|
768
|
+
ui_library: rc.ui_library ?? null,
|
|
769
|
+
form_library: rc.form_library ?? null,
|
|
770
|
+
build_tool: si?.build_tool ?? null,
|
|
771
|
+
};
|
|
772
|
+
}
|
|
773
|
+
else if (si) {
|
|
774
|
+
stack.build_tool = si.build_tool ?? null;
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
catch {
|
|
778
|
+
// analyze_project may fail on non-React repos — fall through
|
|
779
|
+
}
|
|
780
|
+
// Critical pattern scans — run in parallel
|
|
781
|
+
const criticalPatterns = [
|
|
782
|
+
{ name: "dangerously-set-html", severity: "critical" },
|
|
783
|
+
{ name: "hook-in-condition", severity: "critical" },
|
|
784
|
+
{ name: "conditional-render-hook", severity: "critical" },
|
|
785
|
+
{ name: "useEffect-missing-cleanup", severity: "warning" },
|
|
786
|
+
{ name: "useEffect-setstate-loop", severity: "critical" },
|
|
787
|
+
{ name: "rsc-non-serializable-prop", severity: "warning" },
|
|
788
|
+
];
|
|
789
|
+
const scanResults = await Promise.all(criticalPatterns.map(async ({ name, severity }) => {
|
|
790
|
+
try {
|
|
791
|
+
const result = await searchPatterns(repo, name, { max_results: 20 });
|
|
792
|
+
return { pattern: name, count: result.matches.length, severity };
|
|
793
|
+
}
|
|
794
|
+
catch {
|
|
795
|
+
return { pattern: name, count: 0, severity };
|
|
796
|
+
}
|
|
797
|
+
}));
|
|
798
|
+
const critical_issues = scanResults.filter((r) => r.count > 0);
|
|
799
|
+
// Top hooks used across components
|
|
800
|
+
const hookCounts = new Map();
|
|
801
|
+
for (const c of components) {
|
|
802
|
+
if (!c.source)
|
|
803
|
+
continue;
|
|
804
|
+
const matches = c.source.matchAll(/\b(use[A-Z]\w*)\s*\(/g);
|
|
805
|
+
for (const m of matches) {
|
|
806
|
+
const name = m[1];
|
|
807
|
+
hookCounts.set(name, (hookCounts.get(name) ?? 0) + 1);
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
const top_hooks = [...hookCounts.entries()]
|
|
811
|
+
.sort((a, b) => b[1] - a[1])
|
|
812
|
+
.slice(0, 5)
|
|
813
|
+
.map(([name, count]) => ({ name, count }));
|
|
814
|
+
// Suggested next queries
|
|
815
|
+
const suggested_queries = [];
|
|
816
|
+
if (likelyRoot) {
|
|
817
|
+
suggested_queries.push(`trace_component_tree("${likelyRoot}") // explore render hierarchy`);
|
|
818
|
+
}
|
|
819
|
+
suggested_queries.push(`analyze_renders() // find re-render risks`);
|
|
820
|
+
suggested_queries.push(`analyze_hooks() // Rule of Hooks + hook inventory`);
|
|
821
|
+
if (components.length >= 10) {
|
|
822
|
+
suggested_queries.push(`audit_compiler_readiness() // React Compiler adoption check`);
|
|
823
|
+
}
|
|
824
|
+
if (critical_issues.some((i) => i.severity === "critical")) {
|
|
825
|
+
suggested_queries.push(`search_patterns("dangerously-set-html") // investigate XSS risks`);
|
|
826
|
+
}
|
|
827
|
+
return {
|
|
828
|
+
overview: {
|
|
829
|
+
total_components: components.length,
|
|
830
|
+
total_custom_hooks: hooks.length,
|
|
831
|
+
likely_root_component: likelyRoot,
|
|
832
|
+
stack,
|
|
833
|
+
},
|
|
834
|
+
critical_issues,
|
|
835
|
+
top_hooks,
|
|
836
|
+
suggested_queries,
|
|
837
|
+
};
|
|
838
|
+
}
|
|
839
|
+
//# sourceMappingURL=react-tools.js.map
|