codesift-mcp 0.2.18 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +143 -20
- 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/setup.d.ts.map +1 -1
- package/dist/cli/setup.js +17 -2
- 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 +498 -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 +27 -26
- 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 +326 -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 +166 -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/parser-manager.d.ts +32 -0
- package/dist/parser/parser-manager.d.ts.map +1 -1
- package/dist/parser/parser-manager.js +82 -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 +37 -1
- package/dist/register-tools.d.ts.map +1 -1
- package/dist/register-tools.js +2657 -191
- 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/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 +28 -1
- 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/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-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-islands.d.ts +61 -0
- package/dist/tools/astro-islands.d.ts.map +1 -0
- package/dist/tools/astro-islands.js +240 -0
- package/dist/tools/astro-islands.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/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/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 +102 -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 +80 -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 +78 -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 +109 -0
- package/dist/tools/hono-dead-routes.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 +67 -0
- package/dist/tools/hono-inline-analyze.js.map +1 -0
- package/dist/tools/hono-middleware-chain.d.ts +22 -0
- package/dist/tools/hono-middleware-chain.d.ts.map +1 -0
- package/dist/tools/hono-middleware-chain.js +84 -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 +126 -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 +84 -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 +57 -0
- package/dist/tools/hono-rpc-types.js.map +1 -0
- package/dist/tools/hono-security.d.ts +21 -0
- package/dist/tools/hono-security.d.ts.map +1 -0
- package/dist/tools/hono-security.js +98 -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 +72 -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 +92 -0
- package/dist/tools/nest-ext-tools.d.ts.map +1 -0
- package/dist/tools/nest-ext-tools.js +359 -0
- package/dist/tools/nest-ext-tools.js.map +1 -0
- package/dist/tools/nest-tools.d.ts +171 -0
- package/dist/tools/nest-tools.d.ts.map +1 -0
- package/dist/tools/nest-tools.js +1042 -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-tools.d.ts +121 -0
- package/dist/tools/nextjs-component-tools.d.ts.map +1 -0
- package/dist/tools/nextjs-component-tools.js +460 -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 +37 -0
- package/dist/tools/nextjs-framework-audit-tools.d.ts.map +1 -0
- package/dist/tools/nextjs-framework-audit-tools.js +211 -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-tools.d.ts +100 -0
- package/dist/tools/nextjs-route-tools.d.ts.map +1 -0
- package/dist/tools/nextjs-route-tools.js +493 -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 +561 -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 +176 -0
- package/dist/tools/php-tools.d.ts.map +1 -0
- package/dist/tools/php-tools.js +543 -0
- package/dist/tools/php-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 +115 -6
- package/dist/tools/project-tools.d.ts.map +1 -1
- package/dist/tools/project-tools.js +594 -217
- package/dist/tools/project-tools.js.map +1 -1
- 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-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-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 +218 -0
- package/dist/tools/react-tools.d.ts.map +1 -0
- package/dist/tools/react-tools.js +714 -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 +2 -6
- package/dist/tools/review-diff-tools.d.ts.map +1 -1
- package/dist/tools/review-diff-tools.js +51 -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/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 +234 -0
- package/dist/tools/sql-tools.d.ts.map +1 -0
- package/dist/tools/sql-tools.js +1037 -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 +78 -4
- package/dist/tools/symbol-tools.js.map +1 -1
- 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 +36 -0
- package/dist/utils/import-graph.d.ts.map +1 -1
- package/dist/utils/import-graph.js +212 -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 +3 -2
- package/rules/codesift.md +34 -5
- package/rules/codesift.mdc +34 -5
- package/rules/codex.md +34 -5
- package/rules/gemini.md +34 -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
package/dist/register-tools.js
CHANGED
|
@@ -2,11 +2,14 @@ import { z } from "zod";
|
|
|
2
2
|
/** Boolean that also accepts "true"/"false" strings (LLMs often send strings instead of booleans) */
|
|
3
3
|
const zBool = () => z.union([z.boolean(), z.string().transform((s) => s === "true")]).optional();
|
|
4
4
|
import { wrapTool, registerShortener } from "./server-helpers.js";
|
|
5
|
-
import {
|
|
5
|
+
import { detectProjectLanguagesSync } from "./utils/language-detect.js";
|
|
6
|
+
import { indexFolder, indexFile, indexRepo, listAllRepos, invalidateCache, getCodeIndex } from "./tools/index-tools.js";
|
|
7
|
+
import { STUB_LANGUAGES } from "./parser/parser-manager.js";
|
|
6
8
|
import { searchSymbols, searchText, semanticSearch } from "./tools/search-tools.js";
|
|
7
9
|
import { getFileTree, getFileOutline, getRepoOutline, suggestQueries } from "./tools/outline-tools.js";
|
|
8
10
|
import { getSymbol, getSymbols, findAndShow, findReferences, findReferencesBatch, findDeadCode, getContextBundle, formatRefsCompact, formatSymbolCompact, formatSymbolsCompact, formatBundleCompact } from "./tools/symbol-tools.js";
|
|
9
11
|
import { traceCallChain } from "./tools/graph-tools.js";
|
|
12
|
+
import { traceComponentTree, analyzeHooks, analyzeRenders, buildContextGraph, auditCompilerReadiness } from "./tools/react-tools.js";
|
|
10
13
|
import { impactAnalysis } from "./tools/impact-tools.js";
|
|
11
14
|
import { traceRoute } from "./tools/route-tools.js";
|
|
12
15
|
import { detectCommunities } from "./tools/community-tools.js";
|
|
@@ -24,14 +27,58 @@ import { getUsageStats, formatUsageReport } from "./storage/usage-stats.js";
|
|
|
24
27
|
import { goToDefinition, getTypeInfo, renameSymbol, getCallHierarchy } from "./lsp/lsp-tools.js";
|
|
25
28
|
import { indexConversations, searchConversations, searchAllConversations, findConversationsForSymbol } from "./tools/conversation-tools.js";
|
|
26
29
|
import { scanSecrets } from "./tools/secret-tools.js";
|
|
30
|
+
import { resolvePhpNamespace, analyzeActiveRecord, tracePhpEvent, findPhpViews, resolvePhpService, phpSecurityScan, phpProjectAudit, findPhpNPlusOne, findPhpGodModel, } from "./tools/php-tools.js";
|
|
27
31
|
import { consolidateMemories, readMemory } from "./tools/memory-tools.js";
|
|
28
32
|
import { createAnalysisPlan, writeScratchpad, readScratchpad, listScratchpad, updateStepStatus, getPlan, listPlans } from "./tools/coordinator-tools.js";
|
|
29
33
|
import { frequencyAnalysis } from "./tools/frequency-tools.js";
|
|
34
|
+
import { findExtensionFunctions, analyzeSealedHierarchy, traceSuspendChain, analyzeKmpDeclarations, traceFlowChain } from "./tools/kotlin-tools.js";
|
|
35
|
+
import { traceHiltGraph } from "./tools/hilt-tools.js";
|
|
36
|
+
import { traceComposeTree, analyzeComposeRecomposition } from "./tools/compose-tools.js";
|
|
37
|
+
import { traceRoomSchema } from "./tools/room-tools.js";
|
|
38
|
+
import { extractKotlinSerializationContract } from "./tools/serialization-tools.js";
|
|
39
|
+
import { astroAnalyzeIslands, astroHydrationAudit } from "./tools/astro-islands.js";
|
|
40
|
+
import { astroRouteMap } from "./tools/astro-routes.js";
|
|
41
|
+
import { analyzeNextjsComponents } from "./tools/nextjs-component-tools.js";
|
|
42
|
+
import { nextjsRouteMap } from "./tools/nextjs-route-tools.js";
|
|
43
|
+
import { nextjsMetadataAudit } from "./tools/nextjs-metadata-tools.js";
|
|
44
|
+
import { nextjsAuditServerActions } from "./tools/nextjs-security-tools.js";
|
|
45
|
+
import { nextjsApiContract } from "./tools/nextjs-api-contract-tools.js";
|
|
46
|
+
import { nextjsBoundaryAnalyzer } from "./tools/nextjs-boundary-tools.js";
|
|
47
|
+
import { nextjsLinkIntegrity } from "./tools/nextjs-link-tools.js";
|
|
48
|
+
import { nextjsDataFlow } from "./tools/nextjs-data-flow-tools.js";
|
|
49
|
+
import { nextjsMiddlewareCoverage } from "./tools/nextjs-middleware-coverage-tools.js";
|
|
50
|
+
import { frameworkAudit } from "./tools/nextjs-framework-audit-tools.js";
|
|
51
|
+
import { astroConfigAnalyze } from "./tools/astro-config.js";
|
|
30
52
|
import { analyzeProject, getExtractorVersions } from "./tools/project-tools.js";
|
|
53
|
+
import { getModelGraph } from "./tools/model-tools.js";
|
|
54
|
+
import { getTestFixtures } from "./tools/pytest-tools.js";
|
|
55
|
+
import { findFrameworkWiring } from "./tools/wiring-tools.js";
|
|
56
|
+
import { runRuff } from "./tools/ruff-tools.js";
|
|
57
|
+
import { parsePyproject } from "./tools/pyproject-tools.js";
|
|
58
|
+
import { findPythonCallers } from "./tools/python-callers.js";
|
|
59
|
+
import { analyzeDjangoSettings } from "./tools/django-settings.js";
|
|
60
|
+
import { traceCeleryChain } from "./tools/celery-tools.js";
|
|
61
|
+
import { runMypy, runPyright } from "./tools/typecheck-tools.js";
|
|
62
|
+
import { analyzePythonDeps } from "./tools/python-deps-analyzer.js";
|
|
63
|
+
import { findPythonCircularImports } from "./tools/python-circular-imports.js";
|
|
31
64
|
import { reviewDiff } from "./tools/review-diff-tools.js";
|
|
65
|
+
import { auditScan } from "./tools/audit-tools.js";
|
|
66
|
+
import { indexStatus } from "./tools/status-tools.js";
|
|
67
|
+
import { auditAgentConfig } from "./tools/agent-config-tools.js";
|
|
68
|
+
import { testImpactAnalysis } from "./tools/test-impact-tools.js";
|
|
69
|
+
import { dependencyAudit } from "./tools/dependency-audit-tools.js";
|
|
70
|
+
import { migrationLint } from "./tools/migration-lint-tools.js";
|
|
71
|
+
import { analyzePrismaSchema } from "./tools/prisma-schema-tools.js";
|
|
72
|
+
import { findPerfHotspots } from "./tools/perf-tools.js";
|
|
73
|
+
import { fanInFanOut, coChangeAnalysis } from "./tools/coupling-tools.js";
|
|
74
|
+
import { architectureSummary } from "./tools/architecture-tools.js";
|
|
75
|
+
import { nestLifecycleMap, nestModuleGraph, nestDIGraph, nestGuardChain, nestRouteInventory, nestAudit } from "./tools/nest-tools.js";
|
|
76
|
+
import { nestGraphQLMap, nestWebSocketMap, nestScheduleMap, nestTypeOrmMap, nestMicroserviceMap } from "./tools/nest-ext-tools.js";
|
|
77
|
+
import { explainQuery } from "./tools/query-tools.js";
|
|
32
78
|
import { formatSnapshot, getContext, getSessionState } from "./storage/session-state.js";
|
|
33
79
|
import { formatComplexityCompact, formatComplexityCounts, formatClonesCompact, formatClonesCounts, formatHotspotsCompact, formatHotspotsCounts, formatTraceRouteCompact, formatTraceRouteCounts } from "./formatters-shortening.js";
|
|
34
|
-
import { formatSearchSymbols, formatFileTree, formatFileOutline, formatSearchPatterns, formatDeadCode, formatComplexity, formatClones, formatHotspots, formatRepoOutline, formatSuggestQueries, formatSecrets, formatConversations, formatRoles, formatAssembleContext, formatCommunities, formatCallTree, formatTraceRoute, formatKnowledgeMap, formatImpactAnalysis, formatDiffOutline, formatChangedSymbols, formatReviewDiff } from "./formatters.js";
|
|
80
|
+
import { formatSearchSymbols, formatFileTree, formatFileOutline, formatSearchPatterns, formatDeadCode, formatComplexity, formatClones, formatHotspots, formatRepoOutline, formatSuggestQueries, formatSecrets, formatConversations, formatRoles, formatAssembleContext, formatCommunities, formatCallTree, formatTraceRoute, formatKnowledgeMap, formatImpactAnalysis, formatDiffOutline, formatChangedSymbols, formatReviewDiff, formatPerfHotspots, formatFanInFanOut, formatCoChange, formatArchitectureSummary, formatNextjsComponents, formatNextjsRouteMap, formatNextjsMetadataAudit, formatNextjsAuditServerActions, formatNextjsApiContract, formatNextjsBoundaryAnalyzer, formatNextjsLinkIntegrity, formatNextjsDataFlow, formatNextjsMiddlewareCoverage, formatFrameworkAudit } from "./formatters.js";
|
|
81
|
+
import { formatNextjsRouteMapCompact, formatNextjsRouteMapCounts, formatNextjsMetadataAuditCompact, formatNextjsMetadataAuditCounts, formatFrameworkAuditCompact, formatFrameworkAuditCounts, formatServerActionsAuditCompact, formatServerActionsAuditCounts, formatApiContractCompact, formatApiContractCounts, formatBoundaryAnalyzerCompact, formatBoundaryAnalyzerCounts } from "./formatters-shortening.js";
|
|
35
82
|
const zFiniteNumber = z.number().finite();
|
|
36
83
|
/** Coerce string→number for numeric params while rejecting NaN/empty strings. */
|
|
37
84
|
export const zNum = () => z.union([
|
|
@@ -43,6 +90,73 @@ export const zNum = () => z.union([
|
|
|
43
90
|
.pipe(zFiniteNumber),
|
|
44
91
|
]).optional();
|
|
45
92
|
// ---------------------------------------------------------------------------
|
|
93
|
+
// H11 — warn when symbol tools return empty for repos with text_stub languages
|
|
94
|
+
// ---------------------------------------------------------------------------
|
|
95
|
+
export const SYMBOL_TOOLS = new Set([
|
|
96
|
+
"search_symbols", "get_file_outline", "get_symbol", "get_symbols",
|
|
97
|
+
"find_references", "trace_call_chain", "find_dead_code", "analyze_complexity",
|
|
98
|
+
]);
|
|
99
|
+
/**
|
|
100
|
+
* Build an H11 hint string from a list of FileEntry-like records. Returns
|
|
101
|
+
* null when no hint is needed. Separated from `checkTextStubHint` so the
|
|
102
|
+
* purely-deterministic core can be unit-tested without spinning up a real
|
|
103
|
+
* index.
|
|
104
|
+
*
|
|
105
|
+
* A file is counted as a "stub" when its language appears in STUB_LANGUAGES
|
|
106
|
+
* (queried dynamically). Languages like `kotlin` that have a real extractor
|
|
107
|
+
* are automatically excluded because they live outside STUB_LANGUAGES, so
|
|
108
|
+
* H11 no longer fires for Kotlin-heavy repos.
|
|
109
|
+
*/
|
|
110
|
+
export function buildH11Hint(files) {
|
|
111
|
+
if (files.length === 0)
|
|
112
|
+
return null;
|
|
113
|
+
const stubFiles = files.filter((f) => STUB_LANGUAGES.has(f.language));
|
|
114
|
+
if (stubFiles.length === 0)
|
|
115
|
+
return null;
|
|
116
|
+
const stubPct = Math.round((stubFiles.length / files.length) * 100);
|
|
117
|
+
if (stubPct < 30)
|
|
118
|
+
return null;
|
|
119
|
+
const stubExts = [...new Set(stubFiles.map((f) => "." + f.path.split(".").pop()))].slice(0, 3).join(", ");
|
|
120
|
+
return `⚡H11 No parser for ${stubExts} files (${stubPct}% of repo). Symbol tools return empty.\n` +
|
|
121
|
+
` → search_text(query) works on ALL files (uses ripgrep, not parser)\n` +
|
|
122
|
+
` → get_file_tree shows file listing\n` +
|
|
123
|
+
` → Only symbol-based tools (this one) need a parser to return results.\n`;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Check if a repo has stub-language files as a dominant portion. Returns a
|
|
127
|
+
* hint string to prepend to empty results, or null if no hint needed.
|
|
128
|
+
*/
|
|
129
|
+
async function checkTextStubHint(repo, toolName, resultEmpty) {
|
|
130
|
+
if (!resultEmpty || !repo || !SYMBOL_TOOLS.has(toolName))
|
|
131
|
+
return null;
|
|
132
|
+
const index = await getCodeIndex(repo);
|
|
133
|
+
if (!index)
|
|
134
|
+
return null;
|
|
135
|
+
return buildH11Hint(index.files);
|
|
136
|
+
}
|
|
137
|
+
function formatAuditScan(result) {
|
|
138
|
+
const lines = [];
|
|
139
|
+
lines.push(`AUDIT SCAN: ${result.repo}`);
|
|
140
|
+
lines.push(`Gates checked: ${result.summary.gates_checked} | Findings: ${result.summary.total_findings} (${result.summary.critical} critical, ${result.summary.warning} warning)`);
|
|
141
|
+
lines.push("");
|
|
142
|
+
for (const gate of result.gates) {
|
|
143
|
+
const count = gate.findings.length;
|
|
144
|
+
const status = count === 0 ? "✓ PASS" : `✗ ${count} finding${count > 1 ? "s" : ""}`;
|
|
145
|
+
lines.push(`${gate.gate} ${status} — ${gate.description}`);
|
|
146
|
+
lines.push(` tool: ${gate.tool_used}`);
|
|
147
|
+
for (const f of gate.findings.slice(0, 10)) {
|
|
148
|
+
const loc = f.line ? `:${f.line}` : "";
|
|
149
|
+
const sev = f.severity === "critical" ? "🔴" : "🟡";
|
|
150
|
+
lines.push(` ${sev} ${f.file}${loc} — ${f.detail}`);
|
|
151
|
+
}
|
|
152
|
+
if (gate.findings.length > 10) {
|
|
153
|
+
lines.push(` ... +${gate.findings.length - 10} more`);
|
|
154
|
+
}
|
|
155
|
+
lines.push("");
|
|
156
|
+
}
|
|
157
|
+
return lines.join("\n");
|
|
158
|
+
}
|
|
159
|
+
// ---------------------------------------------------------------------------
|
|
46
160
|
// Registered tool handles — populated by registerTools(), used by describe_tools reveal
|
|
47
161
|
// ---------------------------------------------------------------------------
|
|
48
162
|
const toolHandles = new Map();
|
|
@@ -50,6 +164,247 @@ const toolHandles = new Map();
|
|
|
50
164
|
export function getToolHandle(name) {
|
|
51
165
|
return toolHandles.get(name);
|
|
52
166
|
}
|
|
167
|
+
/** Framework-specific tool bundles — auto-enabled when the framework is detected in an indexed repo */
|
|
168
|
+
const FRAMEWORK_TOOL_BUNDLES = {
|
|
169
|
+
nestjs: [
|
|
170
|
+
// Wave 1
|
|
171
|
+
"nest_lifecycle_map",
|
|
172
|
+
"nest_module_graph",
|
|
173
|
+
"nest_di_graph",
|
|
174
|
+
"nest_guard_chain",
|
|
175
|
+
"nest_route_inventory",
|
|
176
|
+
// Wave 2
|
|
177
|
+
"nest_graphql_map",
|
|
178
|
+
"nest_websocket_map",
|
|
179
|
+
"nest_schedule_map",
|
|
180
|
+
"nest_typeorm_map",
|
|
181
|
+
"nest_microservice_map",
|
|
182
|
+
// nest_audit is already core — always visible
|
|
183
|
+
],
|
|
184
|
+
};
|
|
185
|
+
/** Track which framework bundles have been auto-enabled this session (avoid repeat work) */
|
|
186
|
+
const enabledFrameworkBundles = new Set();
|
|
187
|
+
/**
|
|
188
|
+
* Enable framework-specific tool bundle — called after indexing when framework is detected.
|
|
189
|
+
* Idempotent: safe to call multiple times. Only enables tools that exist and are currently disabled.
|
|
190
|
+
*/
|
|
191
|
+
export function enableFrameworkToolBundle(framework) {
|
|
192
|
+
if (enabledFrameworkBundles.has(framework))
|
|
193
|
+
return [];
|
|
194
|
+
const bundle = FRAMEWORK_TOOL_BUNDLES[framework];
|
|
195
|
+
if (!bundle)
|
|
196
|
+
return [];
|
|
197
|
+
const enabled = [];
|
|
198
|
+
for (const name of bundle) {
|
|
199
|
+
const handle = toolHandles.get(name);
|
|
200
|
+
if (handle && typeof handle.enable === "function") {
|
|
201
|
+
handle.enable();
|
|
202
|
+
enabled.push(name);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
if (enabled.length > 0)
|
|
206
|
+
enabledFrameworkBundles.add(framework);
|
|
207
|
+
return enabled;
|
|
208
|
+
}
|
|
209
|
+
// ---------------------------------------------------------------------------
|
|
210
|
+
// Framework-specific tool auto-loading
|
|
211
|
+
// ---------------------------------------------------------------------------
|
|
212
|
+
/**
|
|
213
|
+
* Tool groups that should be auto-enabled when a matching project type is detected at CWD.
|
|
214
|
+
* Keys are detection signals (files at CWD root), values are tool names to enable.
|
|
215
|
+
*/
|
|
216
|
+
const FRAMEWORK_TOOL_GROUPS = {
|
|
217
|
+
// PHP / Yii2 / Laravel — detected by composer.json
|
|
218
|
+
"composer.json": [
|
|
219
|
+
"resolve_php_namespace",
|
|
220
|
+
"analyze_activerecord",
|
|
221
|
+
"trace_php_event",
|
|
222
|
+
"find_php_views",
|
|
223
|
+
"resolve_php_service",
|
|
224
|
+
"php_security_scan",
|
|
225
|
+
"php_project_audit",
|
|
226
|
+
"find_php_n_plus_one",
|
|
227
|
+
"find_php_god_model",
|
|
228
|
+
],
|
|
229
|
+
// Kotlin / Android / Gradle — detected by build.gradle.kts or settings.gradle.kts
|
|
230
|
+
"build.gradle.kts": [
|
|
231
|
+
"find_extension_functions",
|
|
232
|
+
"analyze_sealed_hierarchy",
|
|
233
|
+
"trace_hilt_graph",
|
|
234
|
+
"trace_suspend_chain",
|
|
235
|
+
"analyze_kmp_declarations",
|
|
236
|
+
"trace_compose_tree",
|
|
237
|
+
"analyze_compose_recomposition",
|
|
238
|
+
"trace_room_schema",
|
|
239
|
+
"extract_kotlin_serialization_contract",
|
|
240
|
+
"trace_flow_chain",
|
|
241
|
+
],
|
|
242
|
+
"settings.gradle.kts": [
|
|
243
|
+
"find_extension_functions",
|
|
244
|
+
"analyze_sealed_hierarchy",
|
|
245
|
+
"trace_hilt_graph",
|
|
246
|
+
"trace_suspend_chain",
|
|
247
|
+
"analyze_kmp_declarations",
|
|
248
|
+
"trace_compose_tree",
|
|
249
|
+
"analyze_compose_recomposition",
|
|
250
|
+
"trace_room_schema",
|
|
251
|
+
"extract_kotlin_serialization_contract",
|
|
252
|
+
"trace_flow_chain",
|
|
253
|
+
],
|
|
254
|
+
// Fallback — Android projects with Groovy gradle but Kotlin source
|
|
255
|
+
"build.gradle": [
|
|
256
|
+
"find_extension_functions",
|
|
257
|
+
"analyze_sealed_hierarchy",
|
|
258
|
+
"trace_hilt_graph",
|
|
259
|
+
"trace_suspend_chain",
|
|
260
|
+
"analyze_kmp_declarations",
|
|
261
|
+
"trace_compose_tree",
|
|
262
|
+
"analyze_compose_recomposition",
|
|
263
|
+
"trace_room_schema",
|
|
264
|
+
"extract_kotlin_serialization_contract",
|
|
265
|
+
"trace_flow_chain",
|
|
266
|
+
],
|
|
267
|
+
};
|
|
268
|
+
/**
|
|
269
|
+
* React-specific tools — auto-enabled when a React project is detected.
|
|
270
|
+
* Detection requires BOTH a package.json with react dependency AND presence
|
|
271
|
+
* of .tsx/.jsx files (prevents false positives on non-UI projects that happen
|
|
272
|
+
* to have react as a transitive dep).
|
|
273
|
+
*/
|
|
274
|
+
const REACT_TOOLS = [
|
|
275
|
+
"trace_component_tree",
|
|
276
|
+
"analyze_hooks",
|
|
277
|
+
"analyze_renders",
|
|
278
|
+
"analyze_context_graph",
|
|
279
|
+
"audit_compiler_readiness",
|
|
280
|
+
];
|
|
281
|
+
/**
|
|
282
|
+
* Hono-specific tools — auto-enabled when a Hono project is detected.
|
|
283
|
+
* Core tools (trace_middleware_chain, analyze_hono_app) are already in
|
|
284
|
+
* CORE_TOOL_NAMES. This list covers the 5 hidden tools that agents need
|
|
285
|
+
* to discover via describe_tools/discover_tools otherwise.
|
|
286
|
+
*
|
|
287
|
+
* Detection: package.json with "hono" OR "@hono/zod-openapi" dep.
|
|
288
|
+
* Content-based (not filename), so lives outside FRAMEWORK_TOOL_GROUPS.
|
|
289
|
+
*/
|
|
290
|
+
/**
|
|
291
|
+
* Next.js Tier-1 tools — auto-enabled when 'next' is in package.json deps.
|
|
292
|
+
* These are the 7 hidden tools; the 3 core tools (nextjs_route_map,
|
|
293
|
+
* nextjs_metadata_audit, framework_audit) are always visible.
|
|
294
|
+
*/
|
|
295
|
+
const NEXTJS_TOOLS = [
|
|
296
|
+
"analyze_nextjs_components",
|
|
297
|
+
"nextjs_audit_server_actions",
|
|
298
|
+
"nextjs_api_contract",
|
|
299
|
+
"nextjs_boundary_analyzer",
|
|
300
|
+
"nextjs_link_integrity",
|
|
301
|
+
"nextjs_data_flow",
|
|
302
|
+
"nextjs_middleware_coverage",
|
|
303
|
+
];
|
|
304
|
+
const HONO_TOOLS = [
|
|
305
|
+
"trace_context_flow",
|
|
306
|
+
"extract_api_contract",
|
|
307
|
+
"trace_rpc_types",
|
|
308
|
+
"audit_hono_security",
|
|
309
|
+
"visualize_hono_routes",
|
|
310
|
+
// Phase 2 additions — closes blog-API demo gaps + GitHub issues #3587/#4121/#4270
|
|
311
|
+
"trace_conditional_middleware",
|
|
312
|
+
"analyze_inline_handler",
|
|
313
|
+
"extract_response_types",
|
|
314
|
+
"detect_middleware_env_regression",
|
|
315
|
+
"detect_hono_modules",
|
|
316
|
+
"find_dead_hono_routes",
|
|
317
|
+
];
|
|
318
|
+
/**
|
|
319
|
+
* Detect project type at CWD and return list of tools that should be auto-enabled.
|
|
320
|
+
* Returns empty array if no framework-specific tools apply.
|
|
321
|
+
* Exported for unit testing.
|
|
322
|
+
*/
|
|
323
|
+
export async function detectAutoLoadTools(cwd) {
|
|
324
|
+
const { existsSync, readFileSync, readdirSync } = await import("node:fs");
|
|
325
|
+
const { join } = await import("node:path");
|
|
326
|
+
const toEnable = [];
|
|
327
|
+
for (const [signalFile, tools] of Object.entries(FRAMEWORK_TOOL_GROUPS)) {
|
|
328
|
+
if (existsSync(join(cwd, signalFile))) {
|
|
329
|
+
toEnable.push(...tools);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
// React + Hono detection: both need to read package.json for dep signals.
|
|
333
|
+
const pkgPath = join(cwd, "package.json");
|
|
334
|
+
if (existsSync(pkgPath)) {
|
|
335
|
+
try {
|
|
336
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
337
|
+
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
338
|
+
// React: dep + .tsx/.jsx files
|
|
339
|
+
const hasReact = !!(allDeps["react"] || allDeps["next"] || allDeps["@remix-run/react"]);
|
|
340
|
+
if (hasReact && hasJsxFilesShallow(cwd, readdirSync)) {
|
|
341
|
+
toEnable.push(...REACT_TOOLS);
|
|
342
|
+
}
|
|
343
|
+
// Hono: any hono package is enough — the framework is only pulled in
|
|
344
|
+
// when used, and all 5 hidden tools degrade gracefully on non-Hono repos
|
|
345
|
+
// so false positives are harmless (return "no Hono detected" errors).
|
|
346
|
+
const hasHono = !!(allDeps["hono"] ||
|
|
347
|
+
allDeps["@hono/zod-openapi"] ||
|
|
348
|
+
allDeps["@hono/node-server"] ||
|
|
349
|
+
allDeps["hono-openapi"] ||
|
|
350
|
+
allDeps["chanfana"]);
|
|
351
|
+
if (hasHono) {
|
|
352
|
+
toEnable.push(...HONO_TOOLS);
|
|
353
|
+
}
|
|
354
|
+
// Next.js: auto-enable hidden tools when next dep is present
|
|
355
|
+
const hasNext = !!allDeps["next"];
|
|
356
|
+
if (hasNext) {
|
|
357
|
+
toEnable.push(...NEXTJS_TOOLS);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
catch { /* malformed package.json */ }
|
|
361
|
+
}
|
|
362
|
+
return toEnable;
|
|
363
|
+
}
|
|
364
|
+
/**
|
|
365
|
+
* Quick recursive scan for .tsx/.jsx files in common source dirs.
|
|
366
|
+
* Limits depth to 3 and stops on first match to stay fast (<10ms on typical repos).
|
|
367
|
+
* Skips node_modules, dist, build, .next, .astro, .git.
|
|
368
|
+
*/
|
|
369
|
+
function hasJsxFilesShallow(cwd, readdirSyncFn) {
|
|
370
|
+
const { join } = require("node:path");
|
|
371
|
+
const IGNORE = new Set([
|
|
372
|
+
"node_modules", "dist", "build", ".next", ".astro", ".git",
|
|
373
|
+
"out", "coverage", ".turbo", ".vercel", ".cache",
|
|
374
|
+
]);
|
|
375
|
+
const ROOTS = ["src", "app", "pages", "components", "."];
|
|
376
|
+
function scan(dir, depth) {
|
|
377
|
+
if (depth > 3)
|
|
378
|
+
return false;
|
|
379
|
+
let entries;
|
|
380
|
+
try {
|
|
381
|
+
entries = readdirSyncFn(dir, { withFileTypes: true });
|
|
382
|
+
}
|
|
383
|
+
catch {
|
|
384
|
+
return false;
|
|
385
|
+
}
|
|
386
|
+
for (const e of entries) {
|
|
387
|
+
if (e.isFile() && /\.(tsx|jsx)$/.test(e.name))
|
|
388
|
+
return true;
|
|
389
|
+
}
|
|
390
|
+
for (const e of entries) {
|
|
391
|
+
if (e.isDirectory() && !IGNORE.has(e.name) && !e.name.startsWith(".")) {
|
|
392
|
+
if (scan(join(dir, e.name), depth + 1))
|
|
393
|
+
return true;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
return false;
|
|
397
|
+
}
|
|
398
|
+
for (const root of ROOTS) {
|
|
399
|
+
const dir = root === "." ? cwd : join(cwd, root);
|
|
400
|
+
try {
|
|
401
|
+
if (scan(dir, 0))
|
|
402
|
+
return true;
|
|
403
|
+
}
|
|
404
|
+
catch { /* skip */ }
|
|
405
|
+
}
|
|
406
|
+
return false;
|
|
407
|
+
}
|
|
53
408
|
// ---------------------------------------------------------------------------
|
|
54
409
|
// Output schemas — typed results for structured validation & documentation
|
|
55
410
|
// ---------------------------------------------------------------------------
|
|
@@ -109,23 +464,62 @@ export const OutputSchemas = {
|
|
|
109
464
|
/** list_repos */
|
|
110
465
|
repoList: z.union([z.array(z.string()), z.array(z.object({ name: z.string() }).passthrough())]),
|
|
111
466
|
};
|
|
112
|
-
/** Tools
|
|
113
|
-
const CORE_TOOL_NAMES = new Set([
|
|
114
|
-
|
|
115
|
-
"
|
|
116
|
-
"
|
|
117
|
-
"
|
|
118
|
-
"
|
|
119
|
-
"
|
|
120
|
-
"
|
|
121
|
-
"
|
|
122
|
-
"
|
|
123
|
-
"search_patterns", // #
|
|
124
|
-
"
|
|
125
|
-
|
|
126
|
-
"
|
|
127
|
-
"
|
|
128
|
-
"
|
|
467
|
+
/** Tools visible in ListTools — core (high usage) + direct-use (agents call without discovery) */
|
|
468
|
+
export const CORE_TOOL_NAMES = new Set([
|
|
469
|
+
// --- Top 10 by usage (91% of calls) ---
|
|
470
|
+
"search_text", // #1: 1841 calls
|
|
471
|
+
"codebase_retrieval", // #2: 574 calls
|
|
472
|
+
"get_file_outline", // #3: 351 calls
|
|
473
|
+
"search_symbols", // #4: 332 calls
|
|
474
|
+
"list_repos", // #5: 292 calls
|
|
475
|
+
"get_file_tree", // #6: 268 calls
|
|
476
|
+
"index_file", // #7: 209 calls
|
|
477
|
+
"get_symbol", // #8: 138 calls
|
|
478
|
+
"search_patterns", // #9: 135 calls
|
|
479
|
+
"index_conversations", // #10: 127 calls
|
|
480
|
+
// --- Direct-use: agents call these without discovery ---
|
|
481
|
+
"assemble_context", // 64 calls, 21 sessions, 100% direct
|
|
482
|
+
"get_symbols", // 69 calls — batch symbol reads
|
|
483
|
+
"find_references", // 39 calls — symbol usage
|
|
484
|
+
"find_and_show", // 55 calls — symbol + refs
|
|
485
|
+
"search_conversations", // 37 calls, 100% direct
|
|
486
|
+
"get_context_bundle", // 36 calls, 19 sessions, 100% direct
|
|
487
|
+
"analyze_complexity", // 33 calls, 28 sessions
|
|
488
|
+
"detect_communities", // 32 calls, 24 sessions
|
|
489
|
+
"search_all_conversations", // 27 calls, 100% direct
|
|
490
|
+
"analyze_hotspots", // 22 calls, 18 sessions
|
|
491
|
+
"trace_call_chain", // 15 calls, 100% direct
|
|
492
|
+
"suggest_queries", // 13 calls, 13 sessions
|
|
493
|
+
"usage_stats", // 11 calls, 100% direct
|
|
494
|
+
"get_knowledge_map", // 10 calls, 100% direct
|
|
495
|
+
"get_repo_outline", // 9 calls, 100% direct
|
|
496
|
+
"trace_route", // 9 calls, 100% direct
|
|
497
|
+
"get_type_info", // 8 calls, 100% direct
|
|
498
|
+
"impact_analysis", // 4 calls, 100% direct
|
|
499
|
+
"go_to_definition", // 4 calls, 100% direct
|
|
500
|
+
// --- Composite tools ---
|
|
501
|
+
"audit_scan", // one-call audit: CQ8+CQ11+CQ13+CQ14+CQ17
|
|
502
|
+
"nest_audit", // one-call NestJS analysis: modules+DI+guards+routes+lifecycle
|
|
503
|
+
// --- Essential infrastructure ---
|
|
504
|
+
"index_folder", // repo onboarding
|
|
505
|
+
"discover_tools", // meta: discovers remaining hidden tools
|
|
506
|
+
"describe_tools", // meta: full schema for hidden tools
|
|
507
|
+
"get_session_snapshot", // session: compaction survival
|
|
508
|
+
"analyze_project", // project profile
|
|
509
|
+
"get_extractor_versions", // cache invalidation
|
|
510
|
+
"index_status", // meta: check if repo is indexed
|
|
511
|
+
// --- Astro tools ---
|
|
512
|
+
"astro_analyze_islands",
|
|
513
|
+
"astro_hydration_audit",
|
|
514
|
+
"astro_route_map",
|
|
515
|
+
"astro_config_analyze",
|
|
516
|
+
// --- Hono tools (Task 23) ---
|
|
517
|
+
"trace_middleware_chain", // core: top Hono pain point (Discussion #4255)
|
|
518
|
+
"analyze_hono_app", // core: meta-tool, first call for any Hono project
|
|
519
|
+
// --- Next.js tools ---
|
|
520
|
+
"nextjs_route_map",
|
|
521
|
+
"nextjs_metadata_audit",
|
|
522
|
+
"framework_audit",
|
|
129
523
|
]);
|
|
130
524
|
/** Get all tool definitions (exported for testing) */
|
|
131
525
|
export function getToolDefinitions() {
|
|
@@ -174,8 +568,16 @@ const TOOL_DEFINITIONS = [
|
|
|
174
568
|
description: "List indexed repos. Only needed for multi-repo discovery — single-repo tools auto-resolve from CWD. Set compact=false for full metadata.",
|
|
175
569
|
schema: {
|
|
176
570
|
compact: zBool().describe("true=names only (default), false=full metadata"),
|
|
571
|
+
name_contains: z.string().optional().describe("Filter repos by name substring (case-insensitive). E.g. 'tgm' matches 'local/tgm-panel'"),
|
|
572
|
+
},
|
|
573
|
+
handler: (args) => {
|
|
574
|
+
const opts = {
|
|
575
|
+
compact: args.compact ?? true,
|
|
576
|
+
};
|
|
577
|
+
if (args.name_contains)
|
|
578
|
+
opts.name_contains = args.name_contains;
|
|
579
|
+
return listAllRepos(opts);
|
|
177
580
|
},
|
|
178
|
-
handler: (args) => listAllRepos({ compact: args.compact ?? true }),
|
|
179
581
|
},
|
|
180
582
|
{
|
|
181
583
|
name: "invalidate_cache",
|
|
@@ -227,18 +629,20 @@ const TOOL_DEFINITIONS = [
|
|
|
227
629
|
token_budget: args.token_budget,
|
|
228
630
|
rerank: args.rerank,
|
|
229
631
|
});
|
|
230
|
-
|
|
632
|
+
const output = formatSearchSymbols(results);
|
|
633
|
+
const hint = await checkTextStubHint(args.repo, "search_symbols", results.length === 0);
|
|
634
|
+
return hint ? hint + output : output;
|
|
231
635
|
},
|
|
232
636
|
},
|
|
233
637
|
{
|
|
234
638
|
name: "ast_query",
|
|
235
639
|
category: "search",
|
|
236
|
-
searchHint: "AST tree-sitter query structural pattern matching code shape",
|
|
237
|
-
description: "Search AST patterns via tree-sitter S-expressions. Finds code by structural shape.",
|
|
640
|
+
searchHint: "AST tree-sitter query structural pattern matching code shape jsx react",
|
|
641
|
+
description: "Search AST patterns via tree-sitter S-expressions. Finds code by structural shape. React examples (language='tsx'): `(jsx_element open_tag: (jsx_opening_element name: (identifier) @tag))` finds all JSX component usage; `(call_expression function: (identifier) @fn (#match? @fn \"^use[A-Z]\"))` finds all hook calls.",
|
|
238
642
|
schema: {
|
|
239
643
|
repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
|
|
240
|
-
query: z.string().describe("Tree-sitter query in S-expression syntax"),
|
|
241
|
-
language: z.string().describe("Tree-sitter grammar: typescript, javascript, python, go, rust, java, ruby, php"),
|
|
644
|
+
query: z.string().describe("Tree-sitter query in S-expression syntax. For JSX/React use language='tsx'."),
|
|
645
|
+
language: z.string().describe("Tree-sitter grammar: typescript, tsx, javascript, python, go, rust, java, ruby, php"),
|
|
242
646
|
file_pattern: z.string().optional().describe("Filter to files matching this path substring"),
|
|
243
647
|
max_matches: zNum().describe("Maximum matches to return (default: 50)"),
|
|
244
648
|
},
|
|
@@ -341,7 +745,10 @@ const TOOL_DEFINITIONS = [
|
|
|
341
745
|
},
|
|
342
746
|
handler: async (args) => {
|
|
343
747
|
const result = await getFileOutline(args.repo, args.file_path);
|
|
344
|
-
|
|
748
|
+
const output = formatFileOutline(result);
|
|
749
|
+
const isEmpty = !result || (Array.isArray(result) && result.length === 0);
|
|
750
|
+
const hint = await checkTextStubHint(args.repo, "get_file_outline", isEmpty);
|
|
751
|
+
return hint ? hint + output : output;
|
|
345
752
|
},
|
|
346
753
|
},
|
|
347
754
|
{
|
|
@@ -387,8 +794,10 @@ const TOOL_DEFINITIONS = [
|
|
|
387
794
|
if (args.include_related != null)
|
|
388
795
|
opts.include_related = args.include_related;
|
|
389
796
|
const result = await getSymbol(args.repo, args.symbol_id, opts);
|
|
390
|
-
if (!result)
|
|
391
|
-
|
|
797
|
+
if (!result) {
|
|
798
|
+
const hint = await checkTextStubHint(args.repo, "get_symbol", true);
|
|
799
|
+
return hint ?? null;
|
|
800
|
+
}
|
|
392
801
|
let text = formatSymbolCompact(result.symbol);
|
|
393
802
|
if (result.related && result.related.length > 0) {
|
|
394
803
|
text += "\n\n--- children ---\n" + result.related.map((s) => `${s.kind} ${s.name}${s.signature ? s.signature : ""} [${s.file}:${s.start_line}]`).join("\n");
|
|
@@ -410,7 +819,9 @@ const TOOL_DEFINITIONS = [
|
|
|
410
819
|
},
|
|
411
820
|
handler: async (args) => {
|
|
412
821
|
const syms = await getSymbols(args.repo, args.symbol_ids);
|
|
413
|
-
|
|
822
|
+
const output = formatSymbolsCompact(syms);
|
|
823
|
+
const hint = await checkTextStubHint(args.repo, "get_symbols", syms.length === 0);
|
|
824
|
+
return hint ? hint + output : output;
|
|
414
825
|
},
|
|
415
826
|
},
|
|
416
827
|
{
|
|
@@ -470,16 +881,17 @@ const TOOL_DEFINITIONS = [
|
|
|
470
881
|
return findReferencesBatch(args.repo, names, args.file_pattern);
|
|
471
882
|
}
|
|
472
883
|
const refs = await findReferences(args.repo, args.symbol_name, args.file_pattern);
|
|
473
|
-
|
|
474
|
-
|
|
884
|
+
const output = formatRefsCompact(refs);
|
|
885
|
+
const hint = await checkTextStubHint(args.repo, "find_references", refs.length === 0);
|
|
886
|
+
return hint ? hint + output : output;
|
|
475
887
|
},
|
|
476
888
|
},
|
|
477
889
|
{
|
|
478
890
|
name: "trace_call_chain",
|
|
479
891
|
category: "graph",
|
|
480
|
-
searchHint: "trace call chain callers callees dependency graph mermaid",
|
|
892
|
+
searchHint: "trace call chain callers callees dependency graph mermaid react hooks",
|
|
481
893
|
outputSchema: OutputSchemas.callTree,
|
|
482
|
-
description: "Trace call chain: callers or callees. output_format='mermaid' for diagram.",
|
|
894
|
+
description: "Trace call chain: callers or callees. output_format='mermaid' for diagram. filter_react_hooks=true skips useState/useEffect etc. for cleaner React graphs.",
|
|
483
895
|
schema: {
|
|
484
896
|
repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
|
|
485
897
|
symbol_name: z.string().describe("Name of the symbol to trace"),
|
|
@@ -488,6 +900,7 @@ const TOOL_DEFINITIONS = [
|
|
|
488
900
|
include_source: zBool().describe("Include full source code of each symbol (default: false)"),
|
|
489
901
|
include_tests: zBool().describe("Include test files in trace results (default: false)"),
|
|
490
902
|
output_format: z.enum(["json", "mermaid"]).optional().describe("Output format: 'json' (default) or 'mermaid' (flowchart diagram)"),
|
|
903
|
+
filter_react_hooks: zBool().describe("Skip edges to React stdlib hooks (useState, useEffect, etc.) to reduce call graph noise in React codebases (default: false)"),
|
|
491
904
|
},
|
|
492
905
|
handler: async (args) => {
|
|
493
906
|
const result = await traceCallChain(args.repo, args.symbol_name, args.direction, {
|
|
@@ -495,8 +908,12 @@ const TOOL_DEFINITIONS = [
|
|
|
495
908
|
include_source: args.include_source,
|
|
496
909
|
include_tests: args.include_tests,
|
|
497
910
|
output_format: args.output_format,
|
|
911
|
+
filter_react_hooks: args.filter_react_hooks,
|
|
498
912
|
});
|
|
499
|
-
|
|
913
|
+
const output = formatCallTree(result);
|
|
914
|
+
const isEmpty = typeof result === "object" && result != null && "children" in result && Array.isArray(result.children) && result.children.length === 0;
|
|
915
|
+
const hint = await checkTextStubHint(args.repo, "trace_call_chain", isEmpty);
|
|
916
|
+
return hint ? hint + output : output;
|
|
500
917
|
},
|
|
501
918
|
},
|
|
502
919
|
{
|
|
@@ -521,6 +938,107 @@ const TOOL_DEFINITIONS = [
|
|
|
521
938
|
return formatImpactAnalysis(result);
|
|
522
939
|
},
|
|
523
940
|
},
|
|
941
|
+
{
|
|
942
|
+
name: "trace_component_tree",
|
|
943
|
+
category: "graph",
|
|
944
|
+
searchHint: "react component tree composition render jsx parent child hierarchy",
|
|
945
|
+
description: "Trace React component composition tree from a root component. Shows which components render which via JSX. React equivalent of trace_call_chain. output_format='mermaid' for diagram.",
|
|
946
|
+
schema: {
|
|
947
|
+
repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
|
|
948
|
+
component_name: z.string().describe("Root component name (must have kind 'component' in index)"),
|
|
949
|
+
depth: zNum().describe("Maximum depth of composition tree (default: 3)"),
|
|
950
|
+
include_source: zBool().describe("Include full source of each component (default: false)"),
|
|
951
|
+
include_tests: zBool().describe("Include test files (default: false)"),
|
|
952
|
+
output_format: z.enum(["json", "mermaid"]).optional().describe("Output format: 'json' (default) or 'mermaid'"),
|
|
953
|
+
},
|
|
954
|
+
handler: async (args) => {
|
|
955
|
+
const result = await traceComponentTree(args.repo, args.component_name, {
|
|
956
|
+
depth: args.depth,
|
|
957
|
+
include_source: args.include_source,
|
|
958
|
+
include_tests: args.include_tests,
|
|
959
|
+
output_format: args.output_format,
|
|
960
|
+
});
|
|
961
|
+
return JSON.stringify(result, null, 2);
|
|
962
|
+
},
|
|
963
|
+
},
|
|
964
|
+
{
|
|
965
|
+
name: "analyze_hooks",
|
|
966
|
+
category: "analysis",
|
|
967
|
+
searchHint: "react hooks analyze inventory rule of hooks violations usestate useeffect custom",
|
|
968
|
+
description: "Analyze React hooks: inventory per component, Rule of Hooks violations (hook inside if/loop, hook after early return), custom hook composition, codebase-wide hook usage summary.",
|
|
969
|
+
schema: {
|
|
970
|
+
repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
|
|
971
|
+
component_name: z.string().optional().describe("Filter to single component/hook (default: all)"),
|
|
972
|
+
file_pattern: z.string().optional().describe("Filter by file path substring"),
|
|
973
|
+
include_tests: zBool().describe("Include test files (default: false)"),
|
|
974
|
+
max_entries: zNum().describe("Max entries to return (default: 100)"),
|
|
975
|
+
},
|
|
976
|
+
handler: async (args) => {
|
|
977
|
+
const result = await analyzeHooks(args.repo, {
|
|
978
|
+
component_name: args.component_name,
|
|
979
|
+
file_pattern: args.file_pattern,
|
|
980
|
+
include_tests: args.include_tests,
|
|
981
|
+
max_entries: args.max_entries,
|
|
982
|
+
});
|
|
983
|
+
return JSON.stringify(result, null, 2);
|
|
984
|
+
},
|
|
985
|
+
},
|
|
986
|
+
{
|
|
987
|
+
name: "analyze_renders",
|
|
988
|
+
category: "analysis",
|
|
989
|
+
searchHint: "react render performance inline props memo useCallback useMemo re-render risk optimization",
|
|
990
|
+
description: "Static re-render risk analysis for React components. Detects inline object/array/function props in JSX (new reference every render), unstable default values (= [] or = {}), and components missing React.memo that render children. Returns per-component risk level (low/medium/high) with actionable suggestions.",
|
|
991
|
+
schema: {
|
|
992
|
+
repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
|
|
993
|
+
component_name: z.string().optional().describe("Filter to single component (default: all)"),
|
|
994
|
+
file_pattern: z.string().optional().describe("Filter by file path substring"),
|
|
995
|
+
include_tests: zBool().describe("Include test files (default: false)"),
|
|
996
|
+
max_entries: zNum().describe("Max entries to return (default: 100)"),
|
|
997
|
+
},
|
|
998
|
+
handler: async (args) => {
|
|
999
|
+
const result = await analyzeRenders(args.repo, {
|
|
1000
|
+
component_name: args.component_name,
|
|
1001
|
+
file_pattern: args.file_pattern,
|
|
1002
|
+
include_tests: args.include_tests,
|
|
1003
|
+
max_entries: args.max_entries,
|
|
1004
|
+
});
|
|
1005
|
+
return JSON.stringify(result, null, 2);
|
|
1006
|
+
},
|
|
1007
|
+
},
|
|
1008
|
+
{
|
|
1009
|
+
name: "analyze_context_graph",
|
|
1010
|
+
category: "analysis",
|
|
1011
|
+
searchHint: "react context createContext provider useContext consumer re-render propagation",
|
|
1012
|
+
description: "Map React context flows: createContext → Provider → useContext consumers. Shows which components consume each context and which provide values. Helps identify unnecessary re-renders from context value changes.",
|
|
1013
|
+
schema: {
|
|
1014
|
+
repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
|
|
1015
|
+
},
|
|
1016
|
+
handler: async (args) => {
|
|
1017
|
+
const index = await getCodeIndex(args.repo);
|
|
1018
|
+
if (!index)
|
|
1019
|
+
throw new Error(`Repository not found: ${args.repo}`);
|
|
1020
|
+
const result = buildContextGraph(index.symbols);
|
|
1021
|
+
return JSON.stringify(result, null, 2);
|
|
1022
|
+
},
|
|
1023
|
+
},
|
|
1024
|
+
{
|
|
1025
|
+
name: "audit_compiler_readiness",
|
|
1026
|
+
category: "analysis",
|
|
1027
|
+
searchHint: "react compiler forget memoization bailout readiness migration adoption auto-memo",
|
|
1028
|
+
description: "Audit React Compiler (v1.0) adoption readiness. Scans all components for patterns that cause silent bailout (side effects in render, ref reads, prop/state mutation, try/catch). Returns readiness score (0-100), prioritized fix list, and count of redundant manual memoization safe to remove post-adoption. No competitor offers codebase-wide compiler readiness analysis.",
|
|
1029
|
+
schema: {
|
|
1030
|
+
repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
|
|
1031
|
+
file_pattern: z.string().optional().describe("Filter by file path substring"),
|
|
1032
|
+
include_tests: zBool().describe("Include test files (default: false)"),
|
|
1033
|
+
},
|
|
1034
|
+
handler: async (args) => {
|
|
1035
|
+
const result = await auditCompilerReadiness(args.repo, {
|
|
1036
|
+
file_pattern: args.file_pattern,
|
|
1037
|
+
include_tests: args.include_tests,
|
|
1038
|
+
});
|
|
1039
|
+
return JSON.stringify(result, null, 2);
|
|
1040
|
+
},
|
|
1041
|
+
},
|
|
524
1042
|
{
|
|
525
1043
|
name: "trace_route",
|
|
526
1044
|
category: "graph",
|
|
@@ -842,7 +1360,10 @@ const TOOL_DEFINITIONS = [
|
|
|
842
1360
|
file_pattern: args.file_pattern,
|
|
843
1361
|
include_tests: args.include_tests,
|
|
844
1362
|
});
|
|
845
|
-
|
|
1363
|
+
const output = formatDeadCode(result);
|
|
1364
|
+
const isEmpty = !result || (result.candidates?.length ?? 0) === 0;
|
|
1365
|
+
const hint = await checkTextStubHint(args.repo, "find_dead_code", isEmpty);
|
|
1366
|
+
return hint ? hint + output : output;
|
|
846
1367
|
},
|
|
847
1368
|
},
|
|
848
1369
|
{
|
|
@@ -893,7 +1414,10 @@ const TOOL_DEFINITIONS = [
|
|
|
893
1414
|
min_complexity: args.min_complexity,
|
|
894
1415
|
include_tests: args.include_tests,
|
|
895
1416
|
});
|
|
896
|
-
|
|
1417
|
+
const output = formatComplexity(result);
|
|
1418
|
+
const isEmpty = !result || (result.functions?.length ?? 0) === 0;
|
|
1419
|
+
const hint = await checkTextStubHint(args.repo, "analyze_complexity", isEmpty);
|
|
1420
|
+
return hint ? hint + output : output;
|
|
897
1421
|
},
|
|
898
1422
|
},
|
|
899
1423
|
{
|
|
@@ -1118,161 +1642,718 @@ const TOOL_DEFINITIONS = [
|
|
|
1118
1642
|
return formatSecrets(result);
|
|
1119
1643
|
},
|
|
1120
1644
|
},
|
|
1121
|
-
// ---
|
|
1645
|
+
// --- Kotlin tools (discoverable via discover_tools(query="kotlin")) ---
|
|
1122
1646
|
{
|
|
1123
|
-
name: "
|
|
1124
|
-
category: "
|
|
1125
|
-
|
|
1126
|
-
|
|
1647
|
+
name: "find_extension_functions",
|
|
1648
|
+
category: "analysis",
|
|
1649
|
+
requiresLanguage: "kotlin",
|
|
1650
|
+
searchHint: "kotlin extension function receiver type method discovery",
|
|
1651
|
+
description: "Find all Kotlin extension functions for a given receiver type. Scans indexed symbols for signatures matching 'ReceiverType.' prefix.",
|
|
1127
1652
|
schema: {
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1653
|
+
repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
|
|
1654
|
+
receiver_type: z.string().describe("Receiver type name, e.g. 'String', 'List', 'User'"),
|
|
1655
|
+
file_pattern: z.string().optional().describe("Filter by file path substring"),
|
|
1131
1656
|
},
|
|
1132
1657
|
handler: async (args) => {
|
|
1133
1658
|
const opts = {};
|
|
1134
|
-
if (typeof args.
|
|
1135
|
-
opts.
|
|
1136
|
-
|
|
1137
|
-
opts.min_confidence = args.min_confidence;
|
|
1138
|
-
const result = await consolidateMemories(args.project_path, opts);
|
|
1139
|
-
return result;
|
|
1659
|
+
if (typeof args.file_pattern === "string")
|
|
1660
|
+
opts.file_pattern = args.file_pattern;
|
|
1661
|
+
return await findExtensionFunctions(args.repo, args.receiver_type, opts);
|
|
1140
1662
|
},
|
|
1141
1663
|
},
|
|
1142
1664
|
{
|
|
1143
|
-
name: "
|
|
1144
|
-
category: "
|
|
1145
|
-
|
|
1146
|
-
|
|
1665
|
+
name: "analyze_sealed_hierarchy",
|
|
1666
|
+
category: "analysis",
|
|
1667
|
+
requiresLanguage: "kotlin",
|
|
1668
|
+
searchHint: "kotlin sealed class interface subtype when exhaustive branch missing hierarchy",
|
|
1669
|
+
description: "Analyze a Kotlin sealed class/interface: find all subtypes and check when() blocks for exhaustiveness (missing branches).",
|
|
1147
1670
|
schema: {
|
|
1148
|
-
|
|
1671
|
+
repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
|
|
1672
|
+
sealed_class: z.string().describe("Name of the sealed class or interface to analyze"),
|
|
1149
1673
|
},
|
|
1150
1674
|
handler: async (args) => {
|
|
1151
|
-
|
|
1152
|
-
if (!result)
|
|
1153
|
-
return { error: "No MEMORY.md found. Run consolidate_memories first." };
|
|
1154
|
-
return result.content;
|
|
1675
|
+
return await analyzeSealedHierarchy(args.repo, args.sealed_class);
|
|
1155
1676
|
},
|
|
1156
1677
|
},
|
|
1157
|
-
// --- Coordinator ---
|
|
1158
1678
|
{
|
|
1159
|
-
name: "
|
|
1160
|
-
category: "
|
|
1161
|
-
searchHint: "
|
|
1162
|
-
description: "
|
|
1679
|
+
name: "trace_hilt_graph",
|
|
1680
|
+
category: "analysis",
|
|
1681
|
+
searchHint: "hilt dagger DI dependency injection viewmodel inject module provides binds android kotlin graph",
|
|
1682
|
+
description: "Trace a Hilt DI dependency tree rooted at a class annotated with @HiltViewModel / @AndroidEntryPoint / @HiltAndroidApp. Returns constructor dependencies with matching @Provides/@Binds providers and their module. Unresolved deps are flagged.",
|
|
1163
1683
|
schema: {
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
description: z.string(),
|
|
1168
|
-
tool: z.string(),
|
|
1169
|
-
args: z.record(z.string(), z.unknown()),
|
|
1170
|
-
result_key: z.string().optional(),
|
|
1171
|
-
depends_on: z.array(z.string()).optional(),
|
|
1172
|
-
})),
|
|
1173
|
-
z.string().transform((s) => JSON.parse(s)),
|
|
1174
|
-
]).describe("Steps array: {description, tool, args, result_key?, depends_on?}. JSON string OK."),
|
|
1684
|
+
repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
|
|
1685
|
+
class_name: z.string().describe("Name of the Hilt-annotated class (e.g. 'UserViewModel')"),
|
|
1686
|
+
depth: z.number().optional().describe("Max traversal depth (default: 1)"),
|
|
1175
1687
|
},
|
|
1176
1688
|
handler: async (args) => {
|
|
1177
|
-
const
|
|
1178
|
-
|
|
1689
|
+
const opts = {};
|
|
1690
|
+
if (typeof args.depth === "number")
|
|
1691
|
+
opts.depth = args.depth;
|
|
1692
|
+
return await traceHiltGraph(args.repo, args.class_name, opts);
|
|
1179
1693
|
},
|
|
1180
1694
|
},
|
|
1181
1695
|
{
|
|
1182
|
-
name: "
|
|
1183
|
-
category: "
|
|
1184
|
-
searchHint: "
|
|
1185
|
-
description: "
|
|
1696
|
+
name: "trace_suspend_chain",
|
|
1697
|
+
category: "analysis",
|
|
1698
|
+
searchHint: "kotlin coroutine suspend dispatcher withContext runBlocking Thread.sleep blocking chain trace anti-pattern",
|
|
1699
|
+
description: "Trace the call chain of a Kotlin suspend function, emitting dispatcher transitions (withContext(Dispatchers.X)) and warnings for coroutine anti-patterns: runBlocking inside suspend, Thread.sleep, non-cancellable while(true) loops. Lexical walk — follows callee names found in the source, filtered to suspend-only functions.",
|
|
1186
1700
|
schema: {
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1701
|
+
repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
|
|
1702
|
+
function_name: z.string().describe("Name of the suspend function to trace"),
|
|
1703
|
+
depth: z.number().optional().describe("Max chain depth (default: 3)"),
|
|
1704
|
+
},
|
|
1705
|
+
handler: async (args) => {
|
|
1706
|
+
const opts = {};
|
|
1707
|
+
if (typeof args.depth === "number")
|
|
1708
|
+
opts.depth = args.depth;
|
|
1709
|
+
return await traceSuspendChain(args.repo, args.function_name, opts);
|
|
1190
1710
|
},
|
|
1191
|
-
handler: async (args) => writeScratchpad(args.plan_id, args.key, args.value),
|
|
1192
1711
|
},
|
|
1193
1712
|
{
|
|
1194
|
-
name: "
|
|
1195
|
-
category: "
|
|
1196
|
-
searchHint: "
|
|
1197
|
-
description: "
|
|
1713
|
+
name: "analyze_kmp_declarations",
|
|
1714
|
+
category: "analysis",
|
|
1715
|
+
searchHint: "kotlin multiplatform kmp expect actual source set common main android ios jvm js missing orphan",
|
|
1716
|
+
description: "Validate Kotlin Multiplatform expect/actual declarations across source sets. For each `expect` in commonMain, check every platform source set (androidMain/iosMain/jvmMain/jsMain/etc. discovered from the repo layout) for a matching `actual`. Reports fully matched pairs, expects missing on a platform, and orphan actuals with no corresponding expect.",
|
|
1198
1717
|
schema: {
|
|
1199
|
-
|
|
1200
|
-
key: z.string().describe("Key name to read"),
|
|
1718
|
+
repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
|
|
1201
1719
|
},
|
|
1202
1720
|
handler: async (args) => {
|
|
1203
|
-
|
|
1204
|
-
return result ?? { error: "Key not found in scratchpad" };
|
|
1721
|
+
return await analyzeKmpDeclarations(args.repo);
|
|
1205
1722
|
},
|
|
1206
1723
|
},
|
|
1724
|
+
// --- Kotlin Wave 3 tools ---
|
|
1207
1725
|
{
|
|
1208
|
-
name: "
|
|
1209
|
-
category: "
|
|
1210
|
-
searchHint: "
|
|
1211
|
-
description: "
|
|
1726
|
+
name: "trace_compose_tree",
|
|
1727
|
+
category: "analysis",
|
|
1728
|
+
searchHint: "kotlin compose composable component tree hierarchy ui call graph jetpack preview",
|
|
1729
|
+
description: "Build a Jetpack Compose component hierarchy rooted at a @Composable function. Traces PascalCase calls matching indexed composables, excludes @Preview. Reports tree depth, leaf components, and total component count.",
|
|
1212
1730
|
schema: {
|
|
1213
|
-
|
|
1731
|
+
repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
|
|
1732
|
+
root_name: z.string().describe("Name of the root @Composable function (e.g. 'HomeScreen')"),
|
|
1733
|
+
depth: z.number().optional().describe("Max tree depth (default: 10)"),
|
|
1734
|
+
},
|
|
1735
|
+
handler: async (args) => {
|
|
1736
|
+
const opts = {};
|
|
1737
|
+
if (typeof args.depth === "number")
|
|
1738
|
+
opts.depth = args.depth;
|
|
1739
|
+
return await traceComposeTree(args.repo, args.root_name, opts);
|
|
1214
1740
|
},
|
|
1215
|
-
handler: (args) => listScratchpad(args.plan_id),
|
|
1216
1741
|
},
|
|
1217
1742
|
{
|
|
1218
|
-
name: "
|
|
1219
|
-
category: "
|
|
1220
|
-
searchHint: "
|
|
1221
|
-
description: "
|
|
1743
|
+
name: "analyze_compose_recomposition",
|
|
1744
|
+
category: "analysis",
|
|
1745
|
+
searchHint: "kotlin compose recomposition unstable remember mutableStateOf performance skip lambda collection",
|
|
1746
|
+
description: "Detect recomposition hazards in @Composable functions: mutableStateOf without remember (critical), unstable collection parameters (List/Map/Set), excessive function-type params. Scans all indexed composables, skipping @Preview.",
|
|
1222
1747
|
schema: {
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
status: z.enum(["pending", "in_progress", "completed", "failed", "skipped"]).describe("New status for the step"),
|
|
1226
|
-
error: z.string().optional().describe("Error message if status is 'failed'"),
|
|
1748
|
+
repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
|
|
1749
|
+
file_pattern: z.string().optional().describe("Filter by file path substring"),
|
|
1227
1750
|
},
|
|
1228
1751
|
handler: async (args) => {
|
|
1229
|
-
const
|
|
1230
|
-
|
|
1752
|
+
const opts = {};
|
|
1753
|
+
if (typeof args.file_pattern === "string")
|
|
1754
|
+
opts.file_pattern = args.file_pattern;
|
|
1755
|
+
return await analyzeComposeRecomposition(args.repo, opts);
|
|
1231
1756
|
},
|
|
1232
1757
|
},
|
|
1233
1758
|
{
|
|
1234
|
-
name: "
|
|
1235
|
-
category: "
|
|
1236
|
-
searchHint: "
|
|
1237
|
-
description: "
|
|
1759
|
+
name: "trace_room_schema",
|
|
1760
|
+
category: "analysis",
|
|
1761
|
+
searchHint: "kotlin room database entity dao query insert update delete schema sqlite persistence android",
|
|
1762
|
+
description: "Build a Room persistence schema graph: @Entity classes (with table names, primary keys), @Dao interfaces (with @Query SQL extraction), @Database declarations (with entity refs and version). Index-only.",
|
|
1238
1763
|
schema: {
|
|
1239
|
-
|
|
1764
|
+
repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
|
|
1240
1765
|
},
|
|
1241
1766
|
handler: async (args) => {
|
|
1242
|
-
|
|
1243
|
-
return plan ?? { error: "Plan not found" };
|
|
1767
|
+
return await traceRoomSchema(args.repo);
|
|
1244
1768
|
},
|
|
1245
1769
|
},
|
|
1246
1770
|
{
|
|
1247
|
-
name: "
|
|
1248
|
-
category: "
|
|
1249
|
-
searchHint: "
|
|
1250
|
-
description: "
|
|
1251
|
-
schema: {
|
|
1252
|
-
|
|
1771
|
+
name: "extract_kotlin_serialization_contract",
|
|
1772
|
+
category: "analysis",
|
|
1773
|
+
searchHint: "kotlin serialization serializable json schema serialname field type api contract data class",
|
|
1774
|
+
description: "Derive JSON field schema from @Serializable data classes. Extracts field names, types, @SerialName remapping, nullable flags, and defaults.",
|
|
1775
|
+
schema: {
|
|
1776
|
+
repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
|
|
1777
|
+
file_pattern: z.string().optional().describe("Filter by file path substring"),
|
|
1778
|
+
class_name: z.string().optional().describe("Filter to a single class by name"),
|
|
1779
|
+
},
|
|
1780
|
+
handler: async (args) => {
|
|
1781
|
+
const opts = {};
|
|
1782
|
+
if (typeof args.file_pattern === "string")
|
|
1783
|
+
opts.file_pattern = args.file_pattern;
|
|
1784
|
+
if (typeof args.class_name === "string")
|
|
1785
|
+
opts.class_name = args.class_name;
|
|
1786
|
+
return await extractKotlinSerializationContract(args.repo, opts);
|
|
1787
|
+
},
|
|
1253
1788
|
},
|
|
1254
|
-
// --- Review diff ---
|
|
1255
1789
|
{
|
|
1256
|
-
name: "
|
|
1257
|
-
category: "
|
|
1258
|
-
searchHint: "
|
|
1259
|
-
description: "
|
|
1790
|
+
name: "trace_flow_chain",
|
|
1791
|
+
category: "analysis",
|
|
1792
|
+
searchHint: "kotlin flow coroutine operator map filter collect stateIn shareIn catch chain pipeline reactive",
|
|
1793
|
+
description: "Analyze a Kotlin Flow<T> operator chain: detects 50+ operators, reports ordered list, warns about .collect without .catch and .stateIn without lifecycle scope.",
|
|
1260
1794
|
schema: {
|
|
1261
1795
|
repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
|
|
1262
|
-
|
|
1263
|
-
until: z.string().optional().describe("Target ref. Default: HEAD. Special: WORKING, STAGED"),
|
|
1264
|
-
checks: z.string().optional().describe("Comma-separated check names (default: all)"),
|
|
1265
|
-
exclude_patterns: z.string().optional().describe("Comma-separated globs to exclude"),
|
|
1266
|
-
token_budget: zNum().describe("Max tokens (default: 15000)"),
|
|
1267
|
-
max_files: zNum().describe("Warn above N files (default: 50)"),
|
|
1268
|
-
check_timeout_ms: zNum().describe("Per-check timeout ms (default: 8000)"),
|
|
1796
|
+
symbol_name: z.string().describe("Name of the function or property containing the Flow chain"),
|
|
1269
1797
|
},
|
|
1270
1798
|
handler: async (args) => {
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1799
|
+
return await traceFlowChain(args.repo, args.symbol_name);
|
|
1800
|
+
},
|
|
1801
|
+
},
|
|
1802
|
+
// --- Python tools (all discoverable via discover_tools(query="python")) ---
|
|
1803
|
+
{
|
|
1804
|
+
name: "get_model_graph",
|
|
1805
|
+
category: "analysis",
|
|
1806
|
+
requiresLanguage: "python",
|
|
1807
|
+
searchHint: "python django sqlalchemy orm model relationship foreignkey manytomany entity graph mermaid",
|
|
1808
|
+
description: "Extract ORM model relationships (Django ForeignKey/M2M/O2O, SQLAlchemy relationship). JSON or mermaid erDiagram.",
|
|
1809
|
+
schema: {
|
|
1810
|
+
repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
|
|
1811
|
+
file_pattern: z.string().optional().describe("Filter by file path substring"),
|
|
1812
|
+
output_format: z.enum(["json", "mermaid"]).optional().describe("Output as structured JSON or mermaid erDiagram"),
|
|
1813
|
+
},
|
|
1814
|
+
handler: async (args) => {
|
|
1815
|
+
const opts = {};
|
|
1816
|
+
if (args.file_pattern != null)
|
|
1817
|
+
opts.file_pattern = args.file_pattern;
|
|
1818
|
+
if (args.output_format != null)
|
|
1819
|
+
opts.output_format = args.output_format;
|
|
1820
|
+
return await getModelGraph(args.repo, opts);
|
|
1821
|
+
},
|
|
1822
|
+
},
|
|
1823
|
+
{
|
|
1824
|
+
name: "get_test_fixtures",
|
|
1825
|
+
category: "analysis",
|
|
1826
|
+
requiresLanguage: "python",
|
|
1827
|
+
searchHint: "python pytest fixture conftest scope autouse dependency graph session function",
|
|
1828
|
+
description: "Extract pytest fixture dependency graph: conftest hierarchy, scope, autouse, fixture-to-fixture deps.",
|
|
1829
|
+
schema: {
|
|
1830
|
+
repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
|
|
1831
|
+
file_pattern: z.string().optional().describe("Filter by file path substring"),
|
|
1832
|
+
},
|
|
1833
|
+
handler: async (args) => {
|
|
1834
|
+
const opts = {};
|
|
1835
|
+
if (args.file_pattern != null)
|
|
1836
|
+
opts.file_pattern = args.file_pattern;
|
|
1837
|
+
return await getTestFixtures(args.repo, opts);
|
|
1838
|
+
},
|
|
1839
|
+
},
|
|
1840
|
+
{
|
|
1841
|
+
name: "find_framework_wiring",
|
|
1842
|
+
category: "analysis",
|
|
1843
|
+
requiresLanguage: "python",
|
|
1844
|
+
searchHint: "python django signal receiver celery task middleware management command flask fastapi event wiring",
|
|
1845
|
+
description: "Discover implicit control flow: Django signals, Celery tasks/.delay() calls, middleware, management commands, Flask init_app, FastAPI events.",
|
|
1846
|
+
schema: {
|
|
1847
|
+
repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
|
|
1848
|
+
file_pattern: z.string().optional().describe("Filter by file path substring"),
|
|
1849
|
+
},
|
|
1850
|
+
handler: async (args) => {
|
|
1851
|
+
const opts = {};
|
|
1852
|
+
if (args.file_pattern != null)
|
|
1853
|
+
opts.file_pattern = args.file_pattern;
|
|
1854
|
+
return await findFrameworkWiring(args.repo, opts);
|
|
1855
|
+
},
|
|
1856
|
+
},
|
|
1857
|
+
{
|
|
1858
|
+
name: "run_ruff",
|
|
1859
|
+
category: "analysis",
|
|
1860
|
+
requiresLanguage: "python",
|
|
1861
|
+
searchHint: "python ruff lint check bugbear performance simplify security async unused argument",
|
|
1862
|
+
description: "Run ruff linter with symbol graph correlation. Configurable rule categories (B, PERF, SIM, UP, S, ASYNC, RET, ARG).",
|
|
1863
|
+
schema: {
|
|
1864
|
+
repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
|
|
1865
|
+
categories: z.array(z.string()).optional().describe("Rule categories to enable (default: B,PERF,SIM,UP,S,ASYNC,RET,ARG)"),
|
|
1866
|
+
file_pattern: z.string().optional().describe("Filter by file path substring"),
|
|
1867
|
+
max_results: zFiniteNumber.optional().describe("Max findings to return (default: 100)"),
|
|
1868
|
+
},
|
|
1869
|
+
handler: async (args) => {
|
|
1870
|
+
const opts = {};
|
|
1871
|
+
if (args.categories != null)
|
|
1872
|
+
opts.categories = args.categories;
|
|
1873
|
+
if (args.file_pattern != null)
|
|
1874
|
+
opts.file_pattern = args.file_pattern;
|
|
1875
|
+
if (args.max_results != null)
|
|
1876
|
+
opts.max_results = args.max_results;
|
|
1877
|
+
return await runRuff(args.repo, opts);
|
|
1878
|
+
},
|
|
1879
|
+
},
|
|
1880
|
+
{
|
|
1881
|
+
name: "parse_pyproject",
|
|
1882
|
+
category: "analysis",
|
|
1883
|
+
requiresLanguage: "python",
|
|
1884
|
+
searchHint: "python pyproject toml dependencies version build system entry points scripts tools ruff pytest mypy",
|
|
1885
|
+
description: "Parse pyproject.toml: name, version, Python version, build system, dependencies, optional groups, entry points, configured tools.",
|
|
1886
|
+
schema: { repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)") },
|
|
1887
|
+
handler: async (args) => { return await parsePyproject(args.repo); },
|
|
1888
|
+
},
|
|
1889
|
+
{
|
|
1890
|
+
name: "find_python_callers",
|
|
1891
|
+
category: "analysis",
|
|
1892
|
+
requiresLanguage: "python",
|
|
1893
|
+
searchHint: "python callers call site usage trace cross module import delay apply_async constructor",
|
|
1894
|
+
description: "Find all call sites of a Python symbol: direct calls, method calls, Celery .delay()/.apply_async(), constructor, references.",
|
|
1895
|
+
schema: {
|
|
1896
|
+
repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
|
|
1897
|
+
target_name: z.string().describe("Name of the target function/class/method"),
|
|
1898
|
+
target_file: z.string().optional().describe("Disambiguate target by file path substring"),
|
|
1899
|
+
file_pattern: z.string().optional().describe("Restrict caller search scope"),
|
|
1900
|
+
max_results: zFiniteNumber.optional().describe("Max callers to return (default: 100)"),
|
|
1901
|
+
},
|
|
1902
|
+
handler: async (args) => {
|
|
1903
|
+
const opts = {};
|
|
1904
|
+
if (args.target_file != null)
|
|
1905
|
+
opts.target_file = args.target_file;
|
|
1906
|
+
if (args.file_pattern != null)
|
|
1907
|
+
opts.file_pattern = args.file_pattern;
|
|
1908
|
+
if (args.max_results != null)
|
|
1909
|
+
opts.max_results = args.max_results;
|
|
1910
|
+
return await findPythonCallers(args.repo, args.target_name, opts);
|
|
1911
|
+
},
|
|
1912
|
+
},
|
|
1913
|
+
{
|
|
1914
|
+
name: "analyze_django_settings",
|
|
1915
|
+
category: "security",
|
|
1916
|
+
requiresLanguage: "python",
|
|
1917
|
+
searchHint: "python django settings security debug secret key allowed hosts csrf middleware cookie hsts cors",
|
|
1918
|
+
description: "Audit Django settings.py: 15 security/config checks (DEBUG, SECRET_KEY, CSRF, CORS, HSTS, cookies, sqlite, middleware).",
|
|
1919
|
+
schema: {
|
|
1920
|
+
repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
|
|
1921
|
+
settings_file: z.string().optional().describe("Explicit settings file path (auto-detects if omitted)"),
|
|
1922
|
+
},
|
|
1923
|
+
handler: async (args) => {
|
|
1924
|
+
const opts = {};
|
|
1925
|
+
if (args.settings_file != null)
|
|
1926
|
+
opts.settings_file = args.settings_file;
|
|
1927
|
+
return await analyzeDjangoSettings(args.repo, opts);
|
|
1928
|
+
},
|
|
1929
|
+
},
|
|
1930
|
+
{
|
|
1931
|
+
name: "trace_celery_chain",
|
|
1932
|
+
category: "analysis",
|
|
1933
|
+
requiresLanguage: "python",
|
|
1934
|
+
searchHint: "python celery task shared_task delay apply_async chain group chord canvas retry orphan queue",
|
|
1935
|
+
description: "Celery task tracing: tasks with policies (bind, retries, queue), .delay() call sites, canvas operators (chain/group/chord), orphan tasks.",
|
|
1936
|
+
schema: {
|
|
1937
|
+
repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
|
|
1938
|
+
file_pattern: z.string().optional().describe("Filter by file path substring"),
|
|
1939
|
+
task_name: z.string().optional().describe("Focus on a specific task by name"),
|
|
1940
|
+
},
|
|
1941
|
+
handler: async (args) => {
|
|
1942
|
+
const opts = {};
|
|
1943
|
+
if (args.file_pattern != null)
|
|
1944
|
+
opts.file_pattern = args.file_pattern;
|
|
1945
|
+
if (args.task_name != null)
|
|
1946
|
+
opts.task_name = args.task_name;
|
|
1947
|
+
return await traceCeleryChain(args.repo, opts);
|
|
1948
|
+
},
|
|
1949
|
+
},
|
|
1950
|
+
{
|
|
1951
|
+
name: "run_mypy",
|
|
1952
|
+
category: "analysis",
|
|
1953
|
+
requiresLanguage: "python",
|
|
1954
|
+
searchHint: "python mypy type check error strict return incompatible argument missing",
|
|
1955
|
+
description: "Run mypy type checker with symbol correlation. Parses error codes, maps to containing symbols.",
|
|
1956
|
+
schema: {
|
|
1957
|
+
repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
|
|
1958
|
+
file_pattern: z.string().optional().describe("Filter by file path substring"),
|
|
1959
|
+
strict: zBool().describe("Enable mypy --strict mode"),
|
|
1960
|
+
max_results: zFiniteNumber.optional().describe("Max findings (default: 100)"),
|
|
1961
|
+
},
|
|
1962
|
+
handler: async (args) => {
|
|
1963
|
+
const opts = {};
|
|
1964
|
+
if (args.file_pattern != null)
|
|
1965
|
+
opts.file_pattern = args.file_pattern;
|
|
1966
|
+
if (args.strict != null)
|
|
1967
|
+
opts.strict = args.strict;
|
|
1968
|
+
if (args.max_results != null)
|
|
1969
|
+
opts.max_results = args.max_results;
|
|
1970
|
+
return await runMypy(args.repo, opts);
|
|
1971
|
+
},
|
|
1972
|
+
},
|
|
1973
|
+
{
|
|
1974
|
+
name: "run_pyright",
|
|
1975
|
+
category: "analysis",
|
|
1976
|
+
requiresLanguage: "python",
|
|
1977
|
+
searchHint: "python pyright type check reportMissingImports reportGeneralTypeIssues",
|
|
1978
|
+
description: "Run pyright type checker with symbol correlation. Parses JSON diagnostics, maps to containing symbols.",
|
|
1979
|
+
schema: {
|
|
1980
|
+
repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
|
|
1981
|
+
file_pattern: z.string().optional().describe("Filter by file path substring"),
|
|
1982
|
+
strict: zBool().describe("Enable strict level"),
|
|
1983
|
+
max_results: zFiniteNumber.optional().describe("Max findings (default: 100)"),
|
|
1984
|
+
},
|
|
1985
|
+
handler: async (args) => {
|
|
1986
|
+
const opts = {};
|
|
1987
|
+
if (args.file_pattern != null)
|
|
1988
|
+
opts.file_pattern = args.file_pattern;
|
|
1989
|
+
if (args.strict != null)
|
|
1990
|
+
opts.strict = args.strict;
|
|
1991
|
+
if (args.max_results != null)
|
|
1992
|
+
opts.max_results = args.max_results;
|
|
1993
|
+
return await runPyright(args.repo, opts);
|
|
1994
|
+
},
|
|
1995
|
+
},
|
|
1996
|
+
{
|
|
1997
|
+
name: "analyze_python_deps",
|
|
1998
|
+
category: "analysis",
|
|
1999
|
+
requiresLanguage: "python",
|
|
2000
|
+
searchHint: "python dependency version outdated vulnerable CVE pypi osv requirements pyproject",
|
|
2001
|
+
description: "Python dependency analysis: parse pyproject.toml/requirements.txt, detect unpinned deps, optional PyPI freshness, optional OSV.dev CVE scan.",
|
|
2002
|
+
schema: {
|
|
2003
|
+
repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
|
|
2004
|
+
check_pypi: zBool().describe("Check PyPI for latest versions (network, opt-in)"),
|
|
2005
|
+
check_vulns: zBool().describe("Check OSV.dev for CVEs (network, opt-in)"),
|
|
2006
|
+
},
|
|
2007
|
+
handler: async (args) => {
|
|
2008
|
+
const opts = {};
|
|
2009
|
+
if (args.check_pypi != null)
|
|
2010
|
+
opts.check_pypi = args.check_pypi;
|
|
2011
|
+
if (args.check_vulns != null)
|
|
2012
|
+
opts.check_vulns = args.check_vulns;
|
|
2013
|
+
return await analyzePythonDeps(args.repo, opts);
|
|
2014
|
+
},
|
|
2015
|
+
},
|
|
2016
|
+
{
|
|
2017
|
+
name: "find_python_circular_imports",
|
|
2018
|
+
category: "analysis",
|
|
2019
|
+
requiresLanguage: "python",
|
|
2020
|
+
searchHint: "python circular import cycle ImportError TYPE_CHECKING DFS dependency",
|
|
2021
|
+
description: "Detect Python circular imports via DFS on the import graph. Skips TYPE_CHECKING-only imports. Reports cycle paths with severity.",
|
|
2022
|
+
schema: {
|
|
2023
|
+
repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
|
|
2024
|
+
file_pattern: z.string().optional().describe("Filter by file path substring"),
|
|
2025
|
+
max_cycles: zFiniteNumber.optional().describe("Max cycles to report (default: 50)"),
|
|
2026
|
+
},
|
|
2027
|
+
handler: async (args) => {
|
|
2028
|
+
const opts = {};
|
|
2029
|
+
if (args.file_pattern != null)
|
|
2030
|
+
opts.file_pattern = args.file_pattern;
|
|
2031
|
+
if (args.max_cycles != null)
|
|
2032
|
+
opts.max_cycles = args.max_cycles;
|
|
2033
|
+
return await findPythonCircularImports(args.repo, opts);
|
|
2034
|
+
},
|
|
2035
|
+
},
|
|
2036
|
+
// --- PHP / Yii2 tools (all discoverable via discover_tools(query="php")) ---
|
|
2037
|
+
{
|
|
2038
|
+
name: "resolve_php_namespace",
|
|
2039
|
+
category: "analysis",
|
|
2040
|
+
requiresLanguage: "php",
|
|
2041
|
+
searchHint: "php namespace resolve PSR-4 autoload composer class file path yii2 laravel symfony",
|
|
2042
|
+
description: "Resolve a PHP FQCN to file path via composer.json PSR-4 autoload mapping.",
|
|
2043
|
+
schema: {
|
|
2044
|
+
repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
|
|
2045
|
+
class_name: z.string().describe("Fully-qualified class name, e.g. 'App\\\\Models\\\\User'"),
|
|
2046
|
+
},
|
|
2047
|
+
handler: async (args) => {
|
|
2048
|
+
return await resolvePhpNamespace(args.repo, args.class_name);
|
|
2049
|
+
},
|
|
2050
|
+
},
|
|
2051
|
+
{
|
|
2052
|
+
name: "analyze_activerecord",
|
|
2053
|
+
category: "analysis",
|
|
2054
|
+
requiresLanguage: "php",
|
|
2055
|
+
searchHint: "php activerecord eloquent model schema relations rules behaviors table yii2 laravel orm",
|
|
2056
|
+
description: "Extract PHP ActiveRecord/Eloquent model schema: table name, relations, validation rules, behaviors.",
|
|
2057
|
+
schema: {
|
|
2058
|
+
repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
|
|
2059
|
+
model_name: z.string().optional().describe("Filter by specific model class name"),
|
|
2060
|
+
file_pattern: z.string().optional().describe("Filter by file path substring"),
|
|
2061
|
+
},
|
|
2062
|
+
handler: async (args) => {
|
|
2063
|
+
const opts = {};
|
|
2064
|
+
if (typeof args.model_name === "string")
|
|
2065
|
+
opts.model_name = args.model_name;
|
|
2066
|
+
if (typeof args.file_pattern === "string")
|
|
2067
|
+
opts.file_pattern = args.file_pattern;
|
|
2068
|
+
return await analyzeActiveRecord(args.repo, opts);
|
|
2069
|
+
},
|
|
2070
|
+
},
|
|
2071
|
+
{
|
|
2072
|
+
name: "trace_php_event",
|
|
2073
|
+
category: "analysis",
|
|
2074
|
+
requiresLanguage: "php",
|
|
2075
|
+
searchHint: "php event listener trigger handler chain yii2 laravel observer dispatch",
|
|
2076
|
+
description: "Trace PHP event → listener chains: find trigger() calls and matching on() handlers.",
|
|
2077
|
+
schema: {
|
|
2078
|
+
repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
|
|
2079
|
+
event_name: z.string().optional().describe("Filter by specific event name"),
|
|
2080
|
+
},
|
|
2081
|
+
handler: async (args) => {
|
|
2082
|
+
const opts = {};
|
|
2083
|
+
if (typeof args.event_name === "string")
|
|
2084
|
+
opts.event_name = args.event_name;
|
|
2085
|
+
return await tracePhpEvent(args.repo, opts);
|
|
2086
|
+
},
|
|
2087
|
+
},
|
|
2088
|
+
{
|
|
2089
|
+
name: "find_php_views",
|
|
2090
|
+
category: "analysis",
|
|
2091
|
+
requiresLanguage: "php",
|
|
2092
|
+
searchHint: "php view render template controller widget yii2 laravel blade",
|
|
2093
|
+
description: "Map PHP controller render() calls to view files. Yii2/Laravel convention-aware.",
|
|
2094
|
+
schema: {
|
|
2095
|
+
repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
|
|
2096
|
+
controller: z.string().optional().describe("Filter by controller class name"),
|
|
2097
|
+
},
|
|
2098
|
+
handler: async (args) => {
|
|
2099
|
+
const opts = {};
|
|
2100
|
+
if (typeof args.controller === "string")
|
|
2101
|
+
opts.controller = args.controller;
|
|
2102
|
+
return await findPhpViews(args.repo, opts);
|
|
2103
|
+
},
|
|
2104
|
+
},
|
|
2105
|
+
{
|
|
2106
|
+
name: "resolve_php_service",
|
|
2107
|
+
category: "analysis",
|
|
2108
|
+
requiresLanguage: "php",
|
|
2109
|
+
searchHint: "php service locator DI container component resolve yii2 laravel facade provider",
|
|
2110
|
+
description: "Resolve PHP service locator references (Yii::$app->X, Laravel facades) to concrete classes via config parsing.",
|
|
2111
|
+
schema: {
|
|
2112
|
+
repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
|
|
2113
|
+
service_name: z.string().optional().describe("Filter by specific service name (e.g. 'db', 'user', 'cache')"),
|
|
2114
|
+
},
|
|
2115
|
+
handler: async (args) => {
|
|
2116
|
+
const opts = {};
|
|
2117
|
+
if (typeof args.service_name === "string")
|
|
2118
|
+
opts.service_name = args.service_name;
|
|
2119
|
+
return await resolvePhpService(args.repo, opts);
|
|
2120
|
+
},
|
|
2121
|
+
},
|
|
2122
|
+
{
|
|
2123
|
+
name: "php_security_scan",
|
|
2124
|
+
category: "security",
|
|
2125
|
+
requiresLanguage: "php",
|
|
2126
|
+
searchHint: "php security scan audit vulnerability injection XSS CSRF SQL eval exec unserialize",
|
|
2127
|
+
description: "Scan PHP code for security vulnerabilities: SQL injection, XSS, eval, exec, unserialize, file inclusion. Parallel pattern checks.",
|
|
2128
|
+
schema: {
|
|
2129
|
+
repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
|
|
2130
|
+
file_pattern: z.string().optional().describe("Glob pattern to filter scanned files (default: '*.php')"),
|
|
2131
|
+
checks: z.array(z.string()).optional().describe("Subset of checks to run: sql-injection-php, xss-php, eval-php, exec-php, unserialize-php, file-include-var, unescaped-yii-view, raw-query-yii"),
|
|
2132
|
+
},
|
|
2133
|
+
handler: async (args) => {
|
|
2134
|
+
const opts = {};
|
|
2135
|
+
if (typeof args.file_pattern === "string")
|
|
2136
|
+
opts.file_pattern = args.file_pattern;
|
|
2137
|
+
if (Array.isArray(args.checks))
|
|
2138
|
+
opts.checks = args.checks;
|
|
2139
|
+
return await phpSecurityScan(args.repo, opts);
|
|
2140
|
+
},
|
|
2141
|
+
},
|
|
2142
|
+
{
|
|
2143
|
+
name: "php_project_audit",
|
|
2144
|
+
category: "analysis",
|
|
2145
|
+
requiresLanguage: "php",
|
|
2146
|
+
searchHint: "php project audit health quality technical debt code review comprehensive yii2 laravel",
|
|
2147
|
+
description: "Compound PHP project audit: security scan + ActiveRecord analysis + health score. Runs checks in parallel.",
|
|
2148
|
+
schema: {
|
|
2149
|
+
repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
|
|
2150
|
+
file_pattern: z.string().optional().describe("Glob pattern to filter analyzed files"),
|
|
2151
|
+
},
|
|
2152
|
+
handler: async (args) => {
|
|
2153
|
+
const opts = {};
|
|
2154
|
+
if (typeof args.file_pattern === "string")
|
|
2155
|
+
opts.file_pattern = args.file_pattern;
|
|
2156
|
+
return await phpProjectAudit(args.repo, opts);
|
|
2157
|
+
},
|
|
2158
|
+
},
|
|
2159
|
+
{
|
|
2160
|
+
name: "find_php_n_plus_one",
|
|
2161
|
+
category: "analysis",
|
|
2162
|
+
requiresLanguage: "php",
|
|
2163
|
+
searchHint: "php n+1 query foreach activerecord with eager loading yii2 eloquent relation",
|
|
2164
|
+
description: "Detect N+1 query patterns in PHP: foreach loops accessing ActiveRecord relations without eager loading via ->with(). Yii2/Laravel aware.",
|
|
2165
|
+
schema: {
|
|
2166
|
+
repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
|
|
2167
|
+
limit: z.number().optional().describe("Max findings to return (default: 100)"),
|
|
2168
|
+
file_pattern: z.string().optional().describe("Substring filter on file paths (e.g. 'controllers/')"),
|
|
2169
|
+
},
|
|
2170
|
+
handler: async (args) => {
|
|
2171
|
+
const opts = {};
|
|
2172
|
+
if (typeof args.limit === "number")
|
|
2173
|
+
opts.limit = args.limit;
|
|
2174
|
+
if (typeof args.file_pattern === "string")
|
|
2175
|
+
opts.file_pattern = args.file_pattern;
|
|
2176
|
+
return await findPhpNPlusOne(args.repo, opts);
|
|
2177
|
+
},
|
|
2178
|
+
},
|
|
2179
|
+
{
|
|
2180
|
+
name: "find_php_god_model",
|
|
2181
|
+
category: "analysis",
|
|
2182
|
+
requiresLanguage: "php",
|
|
2183
|
+
searchHint: "php god model god class anti-pattern too many methods relations oversized yii2 activerecord",
|
|
2184
|
+
description: "Find oversized ActiveRecord models (god classes) with configurable thresholds for method, relation, and line counts. Reports reasons per model.",
|
|
2185
|
+
schema: {
|
|
2186
|
+
repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
|
|
2187
|
+
min_methods: z.number().optional().describe("Method count threshold (default: 50)"),
|
|
2188
|
+
min_relations: z.number().optional().describe("Relation count threshold (default: 15)"),
|
|
2189
|
+
min_lines: z.number().optional().describe("Line count threshold (default: 500)"),
|
|
2190
|
+
},
|
|
2191
|
+
handler: async (args) => {
|
|
2192
|
+
const opts = {};
|
|
2193
|
+
if (typeof args.min_methods === "number")
|
|
2194
|
+
opts.min_methods = args.min_methods;
|
|
2195
|
+
if (typeof args.min_relations === "number")
|
|
2196
|
+
opts.min_relations = args.min_relations;
|
|
2197
|
+
if (typeof args.min_lines === "number")
|
|
2198
|
+
opts.min_lines = args.min_lines;
|
|
2199
|
+
return await findPhpGodModel(args.repo, opts);
|
|
2200
|
+
},
|
|
2201
|
+
},
|
|
2202
|
+
// --- Memory consolidation ---
|
|
2203
|
+
{
|
|
2204
|
+
name: "consolidate_memories",
|
|
2205
|
+
category: "conversations",
|
|
2206
|
+
searchHint: "consolidate memories dream knowledge MEMORY.md decisions solutions patterns",
|
|
2207
|
+
description: "Consolidate conversations into MEMORY.md — decisions, solutions, patterns.",
|
|
2208
|
+
schema: {
|
|
2209
|
+
project_path: z.string().optional().describe("Project path (auto-detects from cwd if omitted)"),
|
|
2210
|
+
output_path: z.string().optional().describe("Custom output file path (default: MEMORY.md in project root)"),
|
|
2211
|
+
min_confidence: z.enum(["high", "medium", "low"]).optional().describe("Minimum confidence level for extracted memories (default: low)"),
|
|
2212
|
+
},
|
|
2213
|
+
handler: async (args) => {
|
|
2214
|
+
const opts = {};
|
|
2215
|
+
if (typeof args.output_path === "string")
|
|
2216
|
+
opts.output_path = args.output_path;
|
|
2217
|
+
if (typeof args.min_confidence === "string")
|
|
2218
|
+
opts.min_confidence = args.min_confidence;
|
|
2219
|
+
const result = await consolidateMemories(args.project_path, opts);
|
|
2220
|
+
return result;
|
|
2221
|
+
},
|
|
2222
|
+
},
|
|
2223
|
+
{
|
|
2224
|
+
name: "read_memory",
|
|
2225
|
+
category: "conversations",
|
|
2226
|
+
searchHint: "read memory MEMORY.md institutional knowledge past decisions",
|
|
2227
|
+
description: "Read MEMORY.md knowledge file with past decisions and patterns.",
|
|
2228
|
+
schema: {
|
|
2229
|
+
project_path: z.string().optional().describe("Project path (default: current directory)"),
|
|
2230
|
+
},
|
|
2231
|
+
handler: async (args) => {
|
|
2232
|
+
const result = await readMemory(args.project_path);
|
|
2233
|
+
if (!result)
|
|
2234
|
+
return { error: "No MEMORY.md found. Run consolidate_memories first." };
|
|
2235
|
+
return result.content;
|
|
2236
|
+
},
|
|
2237
|
+
},
|
|
2238
|
+
// --- Coordinator ---
|
|
2239
|
+
{
|
|
2240
|
+
name: "create_analysis_plan",
|
|
2241
|
+
category: "meta",
|
|
2242
|
+
searchHint: "create plan multi-step analysis workflow coordinator scratchpad",
|
|
2243
|
+
description: "Create multi-step analysis plan with shared scratchpad and dependencies.",
|
|
2244
|
+
schema: {
|
|
2245
|
+
title: z.string().describe("Plan title describing the analysis goal"),
|
|
2246
|
+
steps: z.union([
|
|
2247
|
+
z.array(z.object({
|
|
2248
|
+
description: z.string(),
|
|
2249
|
+
tool: z.string(),
|
|
2250
|
+
args: z.record(z.string(), z.unknown()),
|
|
2251
|
+
result_key: z.string().optional(),
|
|
2252
|
+
depends_on: z.array(z.string()).optional(),
|
|
2253
|
+
})),
|
|
2254
|
+
z.string().transform((s) => JSON.parse(s)),
|
|
2255
|
+
]).describe("Steps array: {description, tool, args, result_key?, depends_on?}. JSON string OK."),
|
|
2256
|
+
},
|
|
2257
|
+
handler: async (args) => {
|
|
2258
|
+
const result = await createAnalysisPlan(args.title, args.steps);
|
|
2259
|
+
return result;
|
|
2260
|
+
},
|
|
2261
|
+
},
|
|
2262
|
+
{
|
|
2263
|
+
name: "scratchpad_write",
|
|
2264
|
+
category: "meta",
|
|
2265
|
+
searchHint: "scratchpad write store knowledge cross-step data persist",
|
|
2266
|
+
description: "Write key-value to plan scratchpad for cross-step knowledge sharing.",
|
|
2267
|
+
schema: {
|
|
2268
|
+
plan_id: z.string().describe("Analysis plan identifier"),
|
|
2269
|
+
key: z.string().describe("Key name for the entry"),
|
|
2270
|
+
value: z.string().describe("Value to store"),
|
|
2271
|
+
},
|
|
2272
|
+
handler: async (args) => writeScratchpad(args.plan_id, args.key, args.value),
|
|
2273
|
+
},
|
|
2274
|
+
{
|
|
2275
|
+
name: "scratchpad_read",
|
|
2276
|
+
category: "meta",
|
|
2277
|
+
searchHint: "scratchpad read retrieve knowledge entry",
|
|
2278
|
+
description: "Read a key from a plan's scratchpad. Returns the stored value or null if not found.",
|
|
2279
|
+
schema: {
|
|
2280
|
+
plan_id: z.string().describe("Analysis plan identifier"),
|
|
2281
|
+
key: z.string().describe("Key name to read"),
|
|
2282
|
+
},
|
|
2283
|
+
handler: async (args) => {
|
|
2284
|
+
const result = await readScratchpad(args.plan_id, args.key);
|
|
2285
|
+
return result ?? { error: "Key not found in scratchpad" };
|
|
2286
|
+
},
|
|
2287
|
+
},
|
|
2288
|
+
{
|
|
2289
|
+
name: "scratchpad_list",
|
|
2290
|
+
category: "meta",
|
|
2291
|
+
searchHint: "scratchpad list entries keys",
|
|
2292
|
+
description: "List all entries in a plan's scratchpad with their sizes.",
|
|
2293
|
+
schema: {
|
|
2294
|
+
plan_id: z.string().describe("Analysis plan identifier"),
|
|
2295
|
+
},
|
|
2296
|
+
handler: (args) => listScratchpad(args.plan_id),
|
|
2297
|
+
},
|
|
2298
|
+
{
|
|
2299
|
+
name: "update_step_status",
|
|
2300
|
+
category: "meta",
|
|
2301
|
+
searchHint: "update step status plan progress completed failed",
|
|
2302
|
+
description: "Update step status in plan. Auto-updates plan status on completion.",
|
|
2303
|
+
schema: {
|
|
2304
|
+
plan_id: z.string().describe("Analysis plan identifier"),
|
|
2305
|
+
step_id: z.string().describe("Step identifier (e.g. step_1)"),
|
|
2306
|
+
status: z.enum(["pending", "in_progress", "completed", "failed", "skipped"]).describe("New status for the step"),
|
|
2307
|
+
error: z.string().optional().describe("Error message if status is 'failed'"),
|
|
2308
|
+
},
|
|
2309
|
+
handler: async (args) => {
|
|
2310
|
+
const result = await updateStepStatus(args.plan_id, args.step_id, args.status, args.error);
|
|
2311
|
+
return result;
|
|
2312
|
+
},
|
|
2313
|
+
},
|
|
2314
|
+
{
|
|
2315
|
+
name: "get_analysis_plan",
|
|
2316
|
+
category: "meta",
|
|
2317
|
+
searchHint: "get plan status steps progress",
|
|
2318
|
+
description: "Get the current state of an analysis plan including all step statuses.",
|
|
2319
|
+
schema: {
|
|
2320
|
+
plan_id: z.string().describe("Analysis plan identifier"),
|
|
2321
|
+
},
|
|
2322
|
+
handler: async (args) => {
|
|
2323
|
+
const plan = getPlan(args.plan_id);
|
|
2324
|
+
return plan ?? { error: "Plan not found" };
|
|
2325
|
+
},
|
|
2326
|
+
},
|
|
2327
|
+
{
|
|
2328
|
+
name: "list_analysis_plans",
|
|
2329
|
+
category: "meta",
|
|
2330
|
+
searchHint: "list plans active analysis workflows",
|
|
2331
|
+
description: "List all active analysis plans with their completion status.",
|
|
2332
|
+
schema: {},
|
|
2333
|
+
handler: async () => listPlans(),
|
|
2334
|
+
},
|
|
2335
|
+
// --- Review diff ---
|
|
2336
|
+
{
|
|
2337
|
+
name: "review_diff",
|
|
2338
|
+
category: "diff",
|
|
2339
|
+
searchHint: "review diff static analysis git changes secrets breaking-changes complexity dead-code blast-radius",
|
|
2340
|
+
description: "Run 9 parallel static analysis checks on a git diff: secrets, breaking changes, coupling gaps, complexity, dead-code, blast-radius, bug-patterns, test-gaps, hotspots. Returns a scored verdict (pass/warn/fail) with tiered findings.",
|
|
2341
|
+
schema: {
|
|
2342
|
+
repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
|
|
2343
|
+
since: z.string().optional().describe("Base git ref (default: HEAD~1)"),
|
|
2344
|
+
until: z.string().optional().describe("Target ref. Default: HEAD. Special: WORKING, STAGED"),
|
|
2345
|
+
checks: z.string().optional().describe("Comma-separated check names (default: all)"),
|
|
2346
|
+
exclude_patterns: z.string().optional().describe("Comma-separated globs to exclude"),
|
|
2347
|
+
token_budget: zNum().describe("Max tokens (default: 15000)"),
|
|
2348
|
+
max_files: zNum().describe("Warn above N files (default: 50)"),
|
|
2349
|
+
check_timeout_ms: zNum().describe("Per-check timeout ms (default: 8000)"),
|
|
2350
|
+
},
|
|
2351
|
+
handler: async (args) => {
|
|
2352
|
+
const checksArr = args.checks
|
|
2353
|
+
? args.checks.split(",").map((c) => c.trim()).filter(Boolean)
|
|
2354
|
+
: undefined;
|
|
2355
|
+
const excludeArr = args.exclude_patterns
|
|
2356
|
+
? args.exclude_patterns.split(",").map((p) => p.trim()).filter(Boolean)
|
|
1276
2357
|
: undefined;
|
|
1277
2358
|
const opts = {
|
|
1278
2359
|
repo: args.repo,
|
|
@@ -1281,84 +2362,1418 @@ const TOOL_DEFINITIONS = [
|
|
|
1281
2362
|
opts.since = args.since;
|
|
1282
2363
|
if (args.until != null)
|
|
1283
2364
|
opts.until = args.until;
|
|
1284
|
-
if (checksArr != null)
|
|
1285
|
-
opts.checks = checksArr.join(",");
|
|
1286
|
-
if (excludeArr != null)
|
|
1287
|
-
opts.exclude_patterns = excludeArr;
|
|
1288
|
-
if (args.token_budget != null)
|
|
1289
|
-
opts.token_budget = args.token_budget;
|
|
2365
|
+
if (checksArr != null)
|
|
2366
|
+
opts.checks = checksArr.join(",");
|
|
2367
|
+
if (excludeArr != null)
|
|
2368
|
+
opts.exclude_patterns = excludeArr;
|
|
2369
|
+
if (args.token_budget != null)
|
|
2370
|
+
opts.token_budget = args.token_budget;
|
|
2371
|
+
if (args.max_files != null)
|
|
2372
|
+
opts.max_files = args.max_files;
|
|
2373
|
+
if (args.check_timeout_ms != null)
|
|
2374
|
+
opts.check_timeout_ms = args.check_timeout_ms;
|
|
2375
|
+
const result = await reviewDiff(args.repo, opts);
|
|
2376
|
+
return formatReviewDiff(result);
|
|
2377
|
+
},
|
|
2378
|
+
},
|
|
2379
|
+
// --- Stats ---
|
|
2380
|
+
{
|
|
2381
|
+
name: "usage_stats",
|
|
2382
|
+
category: "meta",
|
|
2383
|
+
searchHint: "usage statistics tool calls tokens timing metrics",
|
|
2384
|
+
outputSchema: OutputSchemas.usageStats,
|
|
2385
|
+
description: "Show usage statistics for all CodeSift tool calls (call counts, tokens, timing, repos)",
|
|
2386
|
+
schema: {},
|
|
2387
|
+
handler: async () => {
|
|
2388
|
+
const stats = await getUsageStats();
|
|
2389
|
+
return { report: formatUsageReport(stats) };
|
|
2390
|
+
},
|
|
2391
|
+
},
|
|
2392
|
+
// ── Session context tools ───────────────────────────────────────────────
|
|
2393
|
+
{
|
|
2394
|
+
name: "get_session_snapshot",
|
|
2395
|
+
category: "session",
|
|
2396
|
+
searchHint: "session context snapshot compaction summary explored symbols files queries",
|
|
2397
|
+
description: "Get a compact ~200 token snapshot of what was explored in this session. Designed to survive context compaction. Call proactively before long tasks.",
|
|
2398
|
+
schema: {
|
|
2399
|
+
repo: z.string().optional().describe("Filter to specific repo. Default: most recent repo."),
|
|
2400
|
+
},
|
|
2401
|
+
handler: async (args) => {
|
|
2402
|
+
return formatSnapshot(getSessionState(), args.repo);
|
|
2403
|
+
},
|
|
2404
|
+
},
|
|
2405
|
+
{
|
|
2406
|
+
name: "get_session_context",
|
|
2407
|
+
category: "session",
|
|
2408
|
+
searchHint: "session context full explored symbols files queries negative evidence",
|
|
2409
|
+
description: "Get full session context: explored symbols, files, queries, and negative evidence (searched but not found). Use get_session_snapshot for a compact version.",
|
|
2410
|
+
schema: {
|
|
2411
|
+
repo: z.string().optional().describe("Filter to specific repo"),
|
|
2412
|
+
include_stale: zBool().describe("Include stale negative evidence entries (default: false)"),
|
|
2413
|
+
},
|
|
2414
|
+
handler: async (args) => {
|
|
2415
|
+
const includeStale = args.include_stale === true || args.include_stale === "true";
|
|
2416
|
+
return getContext(args.repo, includeStale);
|
|
2417
|
+
},
|
|
2418
|
+
},
|
|
2419
|
+
// --- Project Analysis ---
|
|
2420
|
+
{
|
|
2421
|
+
name: "analyze_project",
|
|
2422
|
+
category: "analysis",
|
|
2423
|
+
searchHint: "project profile stack conventions middleware routes rate-limits auth detection",
|
|
2424
|
+
description: "Analyze a repository to extract stack, file classifications, and framework-specific conventions. Returns a structured project profile (schema v1.0) with file:line evidence for convention-level facts.",
|
|
2425
|
+
schema: {
|
|
2426
|
+
repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
|
|
2427
|
+
force: zBool().describe("Ignore cached results and re-analyze"),
|
|
2428
|
+
},
|
|
2429
|
+
handler: async (args) => {
|
|
2430
|
+
const result = await analyzeProject(args.repo, {
|
|
2431
|
+
force: args.force,
|
|
2432
|
+
});
|
|
2433
|
+
return result;
|
|
2434
|
+
},
|
|
2435
|
+
},
|
|
2436
|
+
{
|
|
2437
|
+
name: "get_extractor_versions",
|
|
2438
|
+
category: "meta",
|
|
2439
|
+
searchHint: "extractor version cache invalidation profile parser languages",
|
|
2440
|
+
description: "Return parser_languages (tree-sitter symbol extractors) and profile_frameworks (analyze_project detectors). Text tools (search_text, get_file_tree) work on ALL files regardless — use this only for cache invalidation or to check symbol support for a specific language.",
|
|
2441
|
+
schema: {},
|
|
2442
|
+
handler: async () => getExtractorVersions(),
|
|
2443
|
+
},
|
|
2444
|
+
// --- Composite tools ---
|
|
2445
|
+
{
|
|
2446
|
+
name: "audit_scan",
|
|
2447
|
+
category: "analysis",
|
|
2448
|
+
searchHint: "audit scan code quality CQ gates dead code clones complexity patterns",
|
|
2449
|
+
description: "Run 5 analysis tools in parallel, return findings keyed by CQ gate. One call replaces sequential find_dead_code + search_patterns + find_clones + analyze_complexity + analyze_hotspots. Returns: CQ8 (empty catch), CQ11 (complexity), CQ13 (dead code), CQ14 (clones), CQ17 (perf anti-patterns).",
|
|
2450
|
+
schema: {
|
|
2451
|
+
repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
|
|
2452
|
+
file_pattern: z.string().optional().describe("Filter to files matching this path substring"),
|
|
2453
|
+
include_tests: zBool().describe("Include test files (default: false)"),
|
|
2454
|
+
checks: z.string().optional().describe("Comma-separated CQ gates to check (default: all). E.g. 'CQ8,CQ11,CQ14'"),
|
|
2455
|
+
},
|
|
2456
|
+
handler: async (args) => {
|
|
2457
|
+
const checks = args.checks ? args.checks.split(",").map(s => s.trim()) : undefined;
|
|
2458
|
+
const opts = {};
|
|
2459
|
+
if (args.file_pattern)
|
|
2460
|
+
opts.file_pattern = args.file_pattern;
|
|
2461
|
+
if (args.include_tests)
|
|
2462
|
+
opts.include_tests = args.include_tests;
|
|
2463
|
+
if (checks)
|
|
2464
|
+
opts.checks = checks;
|
|
2465
|
+
const result = await auditScan(args.repo, opts);
|
|
2466
|
+
return formatAuditScan(result);
|
|
2467
|
+
},
|
|
2468
|
+
},
|
|
2469
|
+
// --- New tools (agent-requested) ---
|
|
2470
|
+
{
|
|
2471
|
+
name: "index_status",
|
|
2472
|
+
category: "meta",
|
|
2473
|
+
searchHint: "index status indexed repo check files symbols languages",
|
|
2474
|
+
description: "Check whether a repository is indexed and return index metadata: file count, symbol count, language breakdown, text_stub languages (no parser). Use this before calling symbol-based tools on unfamiliar repos.",
|
|
2475
|
+
schema: {
|
|
2476
|
+
repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
|
|
2477
|
+
},
|
|
2478
|
+
handler: async (args) => {
|
|
2479
|
+
const result = await indexStatus(args.repo);
|
|
2480
|
+
if (!result.indexed)
|
|
2481
|
+
return "index_status: NOT INDEXED — run index_folder first";
|
|
2482
|
+
const langs = Object.entries(result.language_breakdown ?? {})
|
|
2483
|
+
.sort(([, a], [, b]) => b - a)
|
|
2484
|
+
.map(([lang, count]) => `${lang}(${count})`)
|
|
2485
|
+
.join(", ");
|
|
2486
|
+
const parts = [
|
|
2487
|
+
`index_status: indexed=true`,
|
|
2488
|
+
`files: ${result.file_count} | symbols: ${result.symbol_count} | last_indexed: ${result.last_indexed}`,
|
|
2489
|
+
`languages: ${langs}`,
|
|
2490
|
+
];
|
|
2491
|
+
if (result.text_stub_languages) {
|
|
2492
|
+
parts.push(`text_stub (no parser): ${result.text_stub_languages.join(", ")}`);
|
|
2493
|
+
}
|
|
2494
|
+
return parts.join("\n");
|
|
2495
|
+
},
|
|
2496
|
+
},
|
|
2497
|
+
{
|
|
2498
|
+
name: "find_perf_hotspots",
|
|
2499
|
+
category: "analysis",
|
|
2500
|
+
searchHint: "performance perf hotspot N+1 unbounded query sync handler pagination findMany pLimit",
|
|
2501
|
+
description: "Scan for 6 performance anti-patterns: unbounded DB queries, sync I/O in handlers, N+1 loops, unbounded Promise.all, missing pagination, expensive recompute. Returns findings grouped by severity (high/medium/low) with fix hints.",
|
|
2502
|
+
schema: {
|
|
2503
|
+
repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
|
|
2504
|
+
patterns: z.string().optional().describe("Comma-separated pattern names to check (default: all). Options: unbounded-query, sync-in-handler, n-plus-one, unbounded-parallel, missing-pagination, expensive-recompute"),
|
|
2505
|
+
file_pattern: z.string().optional().describe("Filter to files matching this path substring"),
|
|
2506
|
+
include_tests: zBool().describe("Include test files (default: false)"),
|
|
2507
|
+
max_results: zNum().describe("Max findings to return (default: 50)"),
|
|
2508
|
+
},
|
|
2509
|
+
handler: async (args) => {
|
|
2510
|
+
const patterns = args.patterns
|
|
2511
|
+
? args.patterns.split(",").map((s) => s.trim()).filter(Boolean)
|
|
2512
|
+
: undefined;
|
|
2513
|
+
const opts = {};
|
|
2514
|
+
if (patterns)
|
|
2515
|
+
opts.patterns = patterns;
|
|
2516
|
+
if (args.file_pattern != null)
|
|
2517
|
+
opts.file_pattern = args.file_pattern;
|
|
2518
|
+
if (args.include_tests != null)
|
|
2519
|
+
opts.include_tests = args.include_tests;
|
|
2520
|
+
if (args.max_results != null)
|
|
2521
|
+
opts.max_results = args.max_results;
|
|
2522
|
+
const result = await findPerfHotspots(args.repo, opts);
|
|
2523
|
+
return formatPerfHotspots(result);
|
|
2524
|
+
},
|
|
2525
|
+
},
|
|
2526
|
+
{
|
|
2527
|
+
name: "fan_in_fan_out",
|
|
2528
|
+
category: "architecture",
|
|
2529
|
+
searchHint: "fan-in fan-out coupling dependencies imports hub afferent efferent instability threshold",
|
|
2530
|
+
description: "Analyze import graph to find most-imported files (fan-in), most-dependent files (fan-out), and hub files (high both — instability risk). Returns coupling score 0-100. Use min_fan_in/min_fan_out for threshold-based audits ('all files with fan_in > 50') instead of top_n cap.",
|
|
2531
|
+
schema: {
|
|
2532
|
+
repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
|
|
2533
|
+
path: z.string().optional().describe("Focus on files in this directory"),
|
|
2534
|
+
top_n: zNum().describe("How many entries per list (default: 20)"),
|
|
2535
|
+
min_fan_in: zNum().describe("Only return files with fan_in >= this value (default: 0). Use for audits."),
|
|
2536
|
+
min_fan_out: zNum().describe("Only return files with fan_out >= this value (default: 0). Use for audits."),
|
|
2537
|
+
},
|
|
2538
|
+
handler: async (args) => {
|
|
2539
|
+
const opts = {};
|
|
2540
|
+
if (args.path != null)
|
|
2541
|
+
opts.path = args.path;
|
|
2542
|
+
if (args.top_n != null)
|
|
2543
|
+
opts.top_n = args.top_n;
|
|
2544
|
+
if (args.min_fan_in != null)
|
|
2545
|
+
opts.min_fan_in = args.min_fan_in;
|
|
2546
|
+
if (args.min_fan_out != null)
|
|
2547
|
+
opts.min_fan_out = args.min_fan_out;
|
|
2548
|
+
const result = await fanInFanOut(args.repo, opts);
|
|
2549
|
+
return formatFanInFanOut(result);
|
|
2550
|
+
},
|
|
2551
|
+
},
|
|
2552
|
+
{
|
|
2553
|
+
name: "co_change_analysis",
|
|
2554
|
+
category: "architecture",
|
|
2555
|
+
searchHint: "co-change temporal coupling git history Jaccard co-commit correlation cluster",
|
|
2556
|
+
description: "Analyze git history to find files that frequently change together (temporal coupling). Returns file pairs ranked by Jaccard similarity, plus clusters of always-co-changed files. Useful for detecting hidden dependencies.",
|
|
2557
|
+
schema: {
|
|
2558
|
+
repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
|
|
2559
|
+
since_days: zNum().describe("Analyze last N days of history (default: 180)"),
|
|
2560
|
+
min_support: zNum().describe("Minimum co-commits to include a pair (default: 3)"),
|
|
2561
|
+
min_jaccard: zNum().describe("Minimum Jaccard similarity threshold (default: 0.3)"),
|
|
2562
|
+
path: z.string().optional().describe("Focus on files in this directory"),
|
|
2563
|
+
top_n: zNum().describe("Max pairs to return (default: 30)"),
|
|
2564
|
+
},
|
|
2565
|
+
handler: async (args) => {
|
|
2566
|
+
const opts = {};
|
|
2567
|
+
if (args.since_days != null)
|
|
2568
|
+
opts.since_days = args.since_days;
|
|
2569
|
+
if (args.min_support != null)
|
|
2570
|
+
opts.min_support = args.min_support;
|
|
2571
|
+
if (args.min_jaccard != null)
|
|
2572
|
+
opts.min_jaccard = args.min_jaccard;
|
|
2573
|
+
if (args.path != null)
|
|
2574
|
+
opts.path = args.path;
|
|
2575
|
+
if (args.top_n != null)
|
|
2576
|
+
opts.top_n = args.top_n;
|
|
2577
|
+
const result = await coChangeAnalysis(args.repo, opts);
|
|
2578
|
+
return formatCoChange(result);
|
|
2579
|
+
},
|
|
2580
|
+
},
|
|
2581
|
+
{
|
|
2582
|
+
name: "architecture_summary",
|
|
2583
|
+
category: "architecture",
|
|
2584
|
+
searchHint: "architecture summary overview structure stack framework communities coupling circular dependencies entry points",
|
|
2585
|
+
description: "One-call architecture profile: stack detection, module communities, coupling hotspots, circular dependencies, LOC distribution, and entry points. Runs 5 analyses in parallel. Supports Mermaid diagram output.",
|
|
2586
|
+
schema: {
|
|
2587
|
+
repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
|
|
2588
|
+
focus: z.string().optional().describe("Focus on this directory path"),
|
|
2589
|
+
output_format: z.enum(["text", "mermaid"]).optional().describe("Output format (default: text)"),
|
|
2590
|
+
token_budget: zNum().describe("Max tokens for output"),
|
|
2591
|
+
},
|
|
2592
|
+
handler: async (args) => {
|
|
2593
|
+
const opts = {};
|
|
2594
|
+
if (args.focus != null)
|
|
2595
|
+
opts.focus = args.focus;
|
|
2596
|
+
if (args.output_format != null)
|
|
2597
|
+
opts.output_format = args.output_format;
|
|
2598
|
+
if (args.token_budget != null)
|
|
2599
|
+
opts.token_budget = args.token_budget;
|
|
2600
|
+
const result = await architectureSummary(args.repo, opts);
|
|
2601
|
+
return formatArchitectureSummary(result);
|
|
2602
|
+
},
|
|
2603
|
+
},
|
|
2604
|
+
{
|
|
2605
|
+
name: "explain_query",
|
|
2606
|
+
category: "analysis",
|
|
2607
|
+
searchHint: "explain query SQL Prisma ORM database performance EXPLAIN ANALYZE findMany pagination index",
|
|
2608
|
+
description: "Parse a Prisma call and generate approximate SQL with EXPLAIN ANALYZE. Detects: unbounded queries, N+1 risks from includes, missing indexes. MVP: Prisma only. Supports postgresql/mysql/sqlite dialects.",
|
|
2609
|
+
schema: {
|
|
2610
|
+
code: z.string().describe("Prisma code snippet (e.g. prisma.user.findMany({...}))"),
|
|
2611
|
+
dialect: z.enum(["postgresql", "mysql", "sqlite"]).optional().describe("SQL dialect (default: postgresql)"),
|
|
2612
|
+
},
|
|
2613
|
+
handler: async (args) => {
|
|
2614
|
+
const eqOpts = {};
|
|
2615
|
+
if (args.dialect != null)
|
|
2616
|
+
eqOpts.dialect = args.dialect;
|
|
2617
|
+
const result = explainQuery(args.code, eqOpts);
|
|
2618
|
+
const parts = [
|
|
2619
|
+
`explain_query: prisma.${result.parsed.model}.${result.parsed.method}`,
|
|
2620
|
+
`─── Generated SQL (${args.dialect ?? "postgresql"}) ───`,
|
|
2621
|
+
` ${result.sql}`,
|
|
2622
|
+
`─── EXPLAIN command ───`,
|
|
2623
|
+
` ${result.explain_command}`,
|
|
2624
|
+
];
|
|
2625
|
+
if (result.warnings.length > 0) {
|
|
2626
|
+
parts.push("─── Warnings ───");
|
|
2627
|
+
for (const w of result.warnings)
|
|
2628
|
+
parts.push(` ⚠ ${w}`);
|
|
2629
|
+
}
|
|
2630
|
+
if (result.optimization_hints.length > 0) {
|
|
2631
|
+
parts.push("─── Optimization hints ───");
|
|
2632
|
+
for (const h of result.optimization_hints)
|
|
2633
|
+
parts.push(` → ${h}`);
|
|
2634
|
+
}
|
|
2635
|
+
return parts.join("\n");
|
|
2636
|
+
},
|
|
2637
|
+
},
|
|
2638
|
+
// --- NestJS analysis tools ---
|
|
2639
|
+
{
|
|
2640
|
+
name: "nest_lifecycle_map",
|
|
2641
|
+
category: "nestjs",
|
|
2642
|
+
searchHint: "nestjs lifecycle hook onModuleInit onApplicationBootstrap shutdown",
|
|
2643
|
+
description: "Map NestJS lifecycle hooks across the codebase — onModuleInit, onModuleDestroy, etc.",
|
|
2644
|
+
schema: { repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)") },
|
|
2645
|
+
handler: async (args) => nestLifecycleMap(args.repo ?? ""),
|
|
2646
|
+
},
|
|
2647
|
+
{
|
|
2648
|
+
name: "nest_module_graph",
|
|
2649
|
+
category: "nestjs",
|
|
2650
|
+
searchHint: "nestjs module dependency graph circular import boundary",
|
|
2651
|
+
description: "Build NestJS module dependency graph with circular dependency detection and boundary analysis.",
|
|
2652
|
+
schema: {
|
|
2653
|
+
repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
|
|
2654
|
+
max_modules: z.number().optional().describe("Max modules to process (default: 200)"),
|
|
2655
|
+
output_format: z.enum(["json", "mermaid"]).optional().describe("Output format: json (default) or mermaid"),
|
|
2656
|
+
},
|
|
2657
|
+
handler: async (args) => nestModuleGraph(args.repo ?? "", args),
|
|
2658
|
+
},
|
|
2659
|
+
{
|
|
2660
|
+
name: "nest_di_graph",
|
|
2661
|
+
category: "nestjs",
|
|
2662
|
+
searchHint: "nestjs dependency injection provider constructor inject graph cycle",
|
|
2663
|
+
description: "Build NestJS provider DI graph with constructor injection tracking and cycle detection.",
|
|
2664
|
+
schema: {
|
|
2665
|
+
repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
|
|
2666
|
+
max_nodes: z.number().optional().describe("Max provider nodes (default: 200)"),
|
|
2667
|
+
focus: z.string().optional().describe("Path substring to filter files"),
|
|
2668
|
+
},
|
|
2669
|
+
handler: async (args) => nestDIGraph(args.repo ?? "", args),
|
|
2670
|
+
},
|
|
2671
|
+
{
|
|
2672
|
+
name: "nest_guard_chain",
|
|
2673
|
+
category: "nestjs",
|
|
2674
|
+
searchHint: "nestjs guard interceptor pipe filter middleware chain route security",
|
|
2675
|
+
description: "Show guard/interceptor/pipe/filter execution chain per NestJS route (global → controller → method).",
|
|
2676
|
+
schema: {
|
|
2677
|
+
repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
|
|
2678
|
+
path: z.string().optional().describe("Filter to specific route path"),
|
|
2679
|
+
max_routes: z.number().optional().describe("Max routes (default: 300)"),
|
|
2680
|
+
},
|
|
2681
|
+
handler: async (args) => nestGuardChain(args.repo ?? "", args),
|
|
2682
|
+
},
|
|
2683
|
+
{
|
|
2684
|
+
name: "nest_route_inventory",
|
|
2685
|
+
category: "nestjs",
|
|
2686
|
+
searchHint: "nestjs route endpoint api map inventory list all guards params",
|
|
2687
|
+
description: "Full NestJS route map with guards, params, and protected/unprotected stats.",
|
|
2688
|
+
schema: {
|
|
2689
|
+
repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
|
|
2690
|
+
max_routes: z.number().optional().describe("Max routes (default: 500)"),
|
|
2691
|
+
},
|
|
2692
|
+
handler: async (args) => nestRouteInventory(args.repo ?? "", args),
|
|
2693
|
+
},
|
|
2694
|
+
{
|
|
2695
|
+
name: "nest_audit",
|
|
2696
|
+
category: "nestjs",
|
|
2697
|
+
searchHint: "nestjs audit analysis comprehensive module di guard route lifecycle pattern graphql websocket schedule typeorm microservice",
|
|
2698
|
+
description: "One-call NestJS architecture audit: modules, DI, guards, routes, lifecycle, patterns, GraphQL, WebSocket, schedule, TypeORM, microservices.",
|
|
2699
|
+
schema: {
|
|
2700
|
+
repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
|
|
2701
|
+
checks: z.string().optional().describe("Comma-separated checks (default: all). Options: modules,routes,di,guards,lifecycle,patterns,graphql,websocket,schedule,typeorm,microservice"),
|
|
2702
|
+
},
|
|
2703
|
+
handler: async (args) => {
|
|
2704
|
+
const checks = args.checks?.split(",").map((s) => s.trim()).filter(Boolean);
|
|
2705
|
+
return nestAudit(args.repo ?? "", checks ? { checks } : undefined);
|
|
2706
|
+
},
|
|
2707
|
+
},
|
|
2708
|
+
// --- Wave 2 NestJS tools (nest-ext-tools.ts) ---
|
|
2709
|
+
{
|
|
2710
|
+
name: "nest_graphql_map",
|
|
2711
|
+
category: "nestjs",
|
|
2712
|
+
searchHint: "nestjs graphql resolver query mutation subscription apollo",
|
|
2713
|
+
description: "Map NestJS GraphQL resolvers — Query, Mutation, Subscription handlers with return types.",
|
|
2714
|
+
schema: {
|
|
2715
|
+
repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
|
|
2716
|
+
max_entries: z.number().optional().describe("Max resolver entries (default: 300)"),
|
|
2717
|
+
},
|
|
2718
|
+
handler: async (args) => nestGraphQLMap(args.repo ?? "", args),
|
|
2719
|
+
},
|
|
2720
|
+
{
|
|
2721
|
+
name: "nest_websocket_map",
|
|
2722
|
+
category: "nestjs",
|
|
2723
|
+
searchHint: "nestjs websocket gateway subscribemessage socketio realtime event",
|
|
2724
|
+
description: "Map NestJS WebSocket gateways with port, namespace, and subscribed events.",
|
|
2725
|
+
schema: {
|
|
2726
|
+
repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
|
|
2727
|
+
max_gateways: z.number().optional().describe("Max gateways (default: 100)"),
|
|
2728
|
+
},
|
|
2729
|
+
handler: async (args) => nestWebSocketMap(args.repo ?? "", args),
|
|
2730
|
+
},
|
|
2731
|
+
{
|
|
2732
|
+
name: "nest_schedule_map",
|
|
2733
|
+
category: "nestjs",
|
|
2734
|
+
searchHint: "nestjs cron interval timeout scheduled job task onevent event listener",
|
|
2735
|
+
description: "Map NestJS scheduled tasks (@Cron/@Interval/@Timeout) and event listeners (@OnEvent).",
|
|
2736
|
+
schema: {
|
|
2737
|
+
repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
|
|
2738
|
+
max_schedules: z.number().optional().describe("Max schedule entries (default: 300)"),
|
|
2739
|
+
max_files_scanned: z.number().optional().describe("Max files to scan (default: 2000)"),
|
|
2740
|
+
},
|
|
2741
|
+
handler: async (args) => nestScheduleMap(args.repo ?? "", args),
|
|
2742
|
+
},
|
|
2743
|
+
{
|
|
2744
|
+
name: "nest_typeorm_map",
|
|
2745
|
+
category: "nestjs",
|
|
2746
|
+
searchHint: "nestjs typeorm entity relation onetomany manytoone database schema",
|
|
2747
|
+
description: "Build TypeORM entity relation graph with OneToMany/ManyToOne edges and cycle detection.",
|
|
2748
|
+
schema: {
|
|
2749
|
+
repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
|
|
2750
|
+
max_entities: z.number().optional().describe("Max entities (default: 200)"),
|
|
2751
|
+
},
|
|
2752
|
+
handler: async (args) => nestTypeOrmMap(args.repo ?? "", args),
|
|
2753
|
+
},
|
|
2754
|
+
{
|
|
2755
|
+
name: "nest_microservice_map",
|
|
2756
|
+
category: "nestjs",
|
|
2757
|
+
searchHint: "nestjs microservice messagepattern eventpattern kafka rabbitmq nats transport",
|
|
2758
|
+
description: "Map NestJS microservice @MessagePattern and @EventPattern handlers.",
|
|
2759
|
+
schema: {
|
|
2760
|
+
repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
|
|
2761
|
+
max_patterns: z.number().optional().describe("Max patterns (default: 300)"),
|
|
2762
|
+
},
|
|
2763
|
+
handler: async (args) => nestMicroserviceMap(args.repo ?? "", args),
|
|
2764
|
+
},
|
|
2765
|
+
// --- Agent config audit ---
|
|
2766
|
+
{
|
|
2767
|
+
name: "audit_agent_config",
|
|
2768
|
+
category: "meta",
|
|
2769
|
+
searchHint: "audit agent config CLAUDE.md cursorrules stale symbols dead paths token waste redundancy",
|
|
2770
|
+
description: "Scan a config file (CLAUDE.md, .cursorrules) for stale symbol references, dead file paths, token cost, and redundancy. Cross-references against the CodeSift index. Optionally compares two config files for redundant content blocks.",
|
|
2771
|
+
schema: {
|
|
2772
|
+
repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
|
|
2773
|
+
config_path: z.string().optional().describe("Path to config file (default: CLAUDE.md in repo root)"),
|
|
2774
|
+
compare_with: z.string().optional().describe("Path to second config file for redundancy detection"),
|
|
2775
|
+
},
|
|
2776
|
+
handler: async (args) => {
|
|
2777
|
+
const opts = {};
|
|
2778
|
+
if (args.config_path != null)
|
|
2779
|
+
opts.config_path = args.config_path;
|
|
2780
|
+
if (args.compare_with != null)
|
|
2781
|
+
opts.compare_with = args.compare_with;
|
|
2782
|
+
const result = await auditAgentConfig(args.repo, opts);
|
|
2783
|
+
const parts = [`audit_agent_config: ${result.config_path}`, `token_cost: ~${result.token_cost} tokens`];
|
|
2784
|
+
if (result.stale_symbols.length > 0) {
|
|
2785
|
+
parts.push(`\n─── Stale Symbols (${result.stale_symbols.length}) ───`);
|
|
2786
|
+
for (const s of result.stale_symbols)
|
|
2787
|
+
parts.push(` line ${s.line}: \`${s.symbol}\` — not found in index`);
|
|
2788
|
+
}
|
|
2789
|
+
if (result.dead_paths.length > 0) {
|
|
2790
|
+
parts.push(`\n─── Dead Paths (${result.dead_paths.length}) ───`);
|
|
2791
|
+
for (const p of result.dead_paths)
|
|
2792
|
+
parts.push(` line ${p.line}: ${p.path} — file not in index`);
|
|
2793
|
+
}
|
|
2794
|
+
if (result.redundant_blocks.length > 0) {
|
|
2795
|
+
parts.push(`\n─── Redundant Blocks (${result.redundant_blocks.length}) ───`);
|
|
2796
|
+
for (const b of result.redundant_blocks)
|
|
2797
|
+
parts.push(` "${b.text.slice(0, 60)}..." found in: ${b.found_in.join(", ")}`);
|
|
2798
|
+
}
|
|
2799
|
+
if (result.findings.length > 0) {
|
|
2800
|
+
parts.push(`\n─── Findings ───`);
|
|
2801
|
+
for (const f of result.findings)
|
|
2802
|
+
parts.push(` ${f}`);
|
|
2803
|
+
}
|
|
2804
|
+
if (result.stale_symbols.length === 0 && result.dead_paths.length === 0 && result.redundant_blocks.length === 0) {
|
|
2805
|
+
parts.push("\nAll references valid. No issues found.");
|
|
2806
|
+
}
|
|
2807
|
+
return parts.join("\n");
|
|
2808
|
+
},
|
|
2809
|
+
},
|
|
2810
|
+
// --- Test impact analysis ---
|
|
2811
|
+
{
|
|
2812
|
+
name: "test_impact_analysis",
|
|
2813
|
+
category: "analysis",
|
|
2814
|
+
searchHint: "test impact analysis affected tests changed files CI confidence which tests to run",
|
|
2815
|
+
description: "Determine which tests to run based on changed files. Uses impact analysis, co-change correlation, and naming convention matching. Returns prioritized test list with confidence scores and a suggested test command.",
|
|
2816
|
+
schema: {
|
|
2817
|
+
repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
|
|
2818
|
+
since: z.string().optional().describe("Git ref to compare from (default: HEAD~1)"),
|
|
2819
|
+
until: z.string().optional().describe("Git ref to compare to (default: HEAD)"),
|
|
2820
|
+
},
|
|
2821
|
+
handler: async (args) => {
|
|
2822
|
+
const opts = {};
|
|
2823
|
+
if (args.since != null)
|
|
2824
|
+
opts.since = args.since;
|
|
2825
|
+
if (args.until != null)
|
|
2826
|
+
opts.until = args.until;
|
|
2827
|
+
const result = await testImpactAnalysis(args.repo, opts);
|
|
2828
|
+
const parts = [`test_impact: ${result.affected_tests.length} tests affected | ${result.changed_files.length} files changed`];
|
|
2829
|
+
if (result.suggested_command)
|
|
2830
|
+
parts.push(`\nRun: ${result.suggested_command}`);
|
|
2831
|
+
if (result.affected_tests.length > 0) {
|
|
2832
|
+
parts.push("\n─── Affected Tests ───");
|
|
2833
|
+
for (const t of result.affected_tests) {
|
|
2834
|
+
parts.push(` ${t.test_file} (confidence: ${t.confidence.toFixed(2)}) — ${t.reasons.join(", ")}`);
|
|
2835
|
+
}
|
|
2836
|
+
}
|
|
2837
|
+
else {
|
|
2838
|
+
parts.push("\nNo affected tests found.");
|
|
2839
|
+
}
|
|
2840
|
+
return parts.join("\n");
|
|
2841
|
+
},
|
|
2842
|
+
},
|
|
2843
|
+
// --- Dependency audit (composite) ---
|
|
2844
|
+
{
|
|
2845
|
+
name: "dependency_audit",
|
|
2846
|
+
category: "analysis",
|
|
2847
|
+
searchHint: "dependency audit npm vulnerabilities CVE licenses outdated freshness lockfile drift supply chain",
|
|
2848
|
+
description: "Composite dependency health check: vulnerabilities (npm/pnpm/yarn audit), licenses (problematic copyleft detection), freshness (outdated count + major gaps), lockfile integrity (drift, duplicates). Runs 4 sub-checks in parallel. Replaces ~40 manual bash calls for D1-D5 audit dimensions.",
|
|
2849
|
+
schema: {
|
|
2850
|
+
repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
|
|
2851
|
+
workspace_path: z.string().optional().describe("Workspace path (default: index root)"),
|
|
2852
|
+
skip_licenses: zBool().describe("Skip license check (faster, default: false)"),
|
|
2853
|
+
min_severity: z.enum(["low", "moderate", "high", "critical"]).optional().describe("Filter vulnerabilities by minimum severity"),
|
|
2854
|
+
},
|
|
2855
|
+
handler: async (args) => {
|
|
2856
|
+
const opts = {};
|
|
2857
|
+
if (args.workspace_path != null)
|
|
2858
|
+
opts.workspace_path = args.workspace_path;
|
|
2859
|
+
if (args.skip_licenses != null)
|
|
2860
|
+
opts.skip_licenses = args.skip_licenses;
|
|
2861
|
+
if (args.min_severity != null)
|
|
2862
|
+
opts.min_severity = args.min_severity;
|
|
2863
|
+
const result = await dependencyAudit(args.repo, opts);
|
|
2864
|
+
const parts = [
|
|
2865
|
+
`dependency_audit: ${result.workspace} (${result.package_manager}) — ${result.duration_ms}ms`,
|
|
2866
|
+
`\n─── Vulnerabilities (${result.vulnerabilities.total}) ───`,
|
|
2867
|
+
` critical: ${result.vulnerabilities.by_severity.critical} | high: ${result.vulnerabilities.by_severity.high} | moderate: ${result.vulnerabilities.by_severity.moderate} | low: ${result.vulnerabilities.by_severity.low}`,
|
|
2868
|
+
];
|
|
2869
|
+
for (const v of result.vulnerabilities.findings.slice(0, 10)) {
|
|
2870
|
+
parts.push(` [${v.severity}] ${v.package}${v.fix_available ? " (fix available)" : ""}`);
|
|
2871
|
+
}
|
|
2872
|
+
parts.push(`\n─── Licenses (${result.licenses.total}) ───`);
|
|
2873
|
+
if (result.licenses.problematic.length > 0) {
|
|
2874
|
+
parts.push(` ⚠ Problematic: ${result.licenses.problematic.length}`);
|
|
2875
|
+
for (const l of result.licenses.problematic.slice(0, 10))
|
|
2876
|
+
parts.push(` ${l.package}: ${l.license}`);
|
|
2877
|
+
}
|
|
2878
|
+
parts.push(`\n─── Freshness (${result.freshness.outdated_count} outdated) ───`);
|
|
2879
|
+
for (const o of result.freshness.major_gaps.slice(0, 10)) {
|
|
2880
|
+
parts.push(` ${o.package}: ${o.current} → ${o.latest} (${o.major_gap} major)`);
|
|
2881
|
+
}
|
|
2882
|
+
parts.push(`\n─── Lockfile ───`);
|
|
2883
|
+
parts.push(` present: ${result.lockfile.present} | issues: ${result.lockfile.issues.length}`);
|
|
2884
|
+
for (const i of result.lockfile.issues.slice(0, 5))
|
|
2885
|
+
parts.push(` ${i.type}: ${i.message}`);
|
|
2886
|
+
if (result.errors.length > 0) {
|
|
2887
|
+
parts.push(`\n─── Sub-check errors (${result.errors.length}) ───`);
|
|
2888
|
+
for (const e of result.errors)
|
|
2889
|
+
parts.push(` ${e}`);
|
|
2890
|
+
}
|
|
2891
|
+
return parts.join("\n");
|
|
2892
|
+
},
|
|
2893
|
+
},
|
|
2894
|
+
// --- Migration safety linter (squawk wrapper) ---
|
|
2895
|
+
{
|
|
2896
|
+
name: "migration_lint",
|
|
2897
|
+
category: "analysis",
|
|
2898
|
+
searchHint: "migration lint squawk SQL postgresql safety linter unsafe-migration not-null drop-column alter-column-type concurrently",
|
|
2899
|
+
description: "PostgreSQL migration safety linter via squawk wrapper. Detects 30+ anti-patterns: NOT NULL without default, DROP COLUMN, ALTER COLUMN TYPE, CREATE INDEX without CONCURRENTLY, etc. Requires squawk CLI installed (brew install squawk OR cargo install squawk-cli). Auto-discovers prisma/migrations, migrations/, db/migrate, drizzle/.",
|
|
2900
|
+
schema: {
|
|
2901
|
+
repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
|
|
2902
|
+
migration_glob: z.string().optional().describe("Custom migration file glob pattern"),
|
|
2903
|
+
excluded_rules: z.union([z.array(z.string()), z.string().transform((s) => s.split(",").map((x) => x.trim()))]).optional().describe("Squawk rules to exclude (comma-sep or array)"),
|
|
2904
|
+
pg_version: z.string().optional().describe("PostgreSQL version for version-aware rules (e.g. '13')"),
|
|
2905
|
+
},
|
|
2906
|
+
handler: async (args) => {
|
|
2907
|
+
const opts = {};
|
|
2908
|
+
if (args.migration_glob != null)
|
|
2909
|
+
opts.migration_glob = args.migration_glob;
|
|
2910
|
+
if (args.excluded_rules != null)
|
|
2911
|
+
opts.excluded_rules = args.excluded_rules;
|
|
2912
|
+
if (args.pg_version != null)
|
|
2913
|
+
opts.pg_version = args.pg_version;
|
|
2914
|
+
const result = await migrationLint(args.repo, opts);
|
|
2915
|
+
if (!result.squawk_installed) {
|
|
2916
|
+
return `migration_lint: squawk not installed.\n${result.install_hint}\n${result.files_checked} migration files would be checked.`;
|
|
2917
|
+
}
|
|
2918
|
+
const parts = [
|
|
2919
|
+
`migration_lint: squawk ${result.squawk_version ?? "unknown"} — ${result.files_checked} files checked`,
|
|
2920
|
+
`errors: ${result.by_severity.error} | warnings: ${result.by_severity.warning}`,
|
|
2921
|
+
];
|
|
2922
|
+
if (result.findings.length > 0) {
|
|
2923
|
+
parts.push("\n─── Findings ───");
|
|
2924
|
+
for (const f of result.findings.slice(0, 30)) {
|
|
2925
|
+
parts.push(` [${f.level}] ${f.file}:${f.line} ${f.rule} — ${f.message}`);
|
|
2926
|
+
}
|
|
2927
|
+
}
|
|
2928
|
+
else {
|
|
2929
|
+
parts.push("\nNo issues found.");
|
|
2930
|
+
}
|
|
2931
|
+
return parts.join("\n");
|
|
2932
|
+
},
|
|
2933
|
+
},
|
|
2934
|
+
// --- Prisma schema analyzer ---
|
|
2935
|
+
{
|
|
2936
|
+
name: "analyze_prisma_schema",
|
|
2937
|
+
category: "analysis",
|
|
2938
|
+
searchHint: "prisma schema analyze ast model field index foreign-key relation soft-delete enum coverage",
|
|
2939
|
+
description: "Parse schema.prisma into structured AST. Returns model coverage: fields, indexes, FKs, relations, soft-delete detection, FK index coverage %, unindexed FKs (audit warning), status-as-String suggestions. Uses @mrleebo/prisma-ast for proper AST parsing (vs regex-only extractor).",
|
|
2940
|
+
schema: {
|
|
2941
|
+
repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
|
|
2942
|
+
schema_path: z.string().optional().describe("Path to schema.prisma (default: auto-detected)"),
|
|
2943
|
+
},
|
|
2944
|
+
handler: async (args) => {
|
|
2945
|
+
const opts = {};
|
|
2946
|
+
if (args.schema_path != null)
|
|
2947
|
+
opts.schema_path = args.schema_path;
|
|
2948
|
+
const result = await analyzePrismaSchema(args.repo, opts);
|
|
2949
|
+
const parts = [
|
|
2950
|
+
`analyze_prisma_schema: ${result.schema_path}`,
|
|
2951
|
+
`models: ${result.model_count} | enums: ${result.enum_count}`,
|
|
2952
|
+
`\n─── FK Index Coverage ───`,
|
|
2953
|
+
` ${result.totals.fk_with_index}/${result.totals.fk_columns} FKs indexed (${result.totals.fk_index_coverage_pct.toFixed(1)}%)`,
|
|
2954
|
+
` unindexed FKs: ${result.totals.fk_without_index}`,
|
|
2955
|
+
` soft-delete models: ${result.totals.soft_delete_models}`,
|
|
2956
|
+
` composite indexes: ${result.totals.composite_indexes} | single indexes: ${result.totals.single_indexes}`,
|
|
2957
|
+
];
|
|
2958
|
+
if (result.warnings.length > 0) {
|
|
2959
|
+
parts.push(`\n─── Warnings (${result.warnings.length}) ───`);
|
|
2960
|
+
for (const w of result.warnings.slice(0, 20))
|
|
2961
|
+
parts.push(` ⚠ ${w}`);
|
|
2962
|
+
}
|
|
2963
|
+
// List models with audit issues
|
|
2964
|
+
const auditModels = result.models.filter((m) => m.fk_columns_without_index.length > 0 || m.status_like_string_fields.length > 0);
|
|
2965
|
+
if (auditModels.length > 0) {
|
|
2966
|
+
parts.push(`\n─── Models with issues (${auditModels.length}) ───`);
|
|
2967
|
+
for (const m of auditModels.slice(0, 15)) {
|
|
2968
|
+
const issues = [];
|
|
2969
|
+
if (m.fk_columns_without_index.length > 0)
|
|
2970
|
+
issues.push(`unindexed FKs: ${m.fk_columns_without_index.join(",")}`);
|
|
2971
|
+
if (m.status_like_string_fields.length > 0)
|
|
2972
|
+
issues.push(`status-as-String: ${m.status_like_string_fields.join(",")}`);
|
|
2973
|
+
parts.push(` ${m.name} — ${issues.join(" | ")}`);
|
|
2974
|
+
}
|
|
2975
|
+
}
|
|
2976
|
+
return parts.join("\n");
|
|
2977
|
+
},
|
|
2978
|
+
},
|
|
2979
|
+
// --- Astro tools ---
|
|
2980
|
+
{
|
|
2981
|
+
name: "astro_analyze_islands",
|
|
2982
|
+
category: "analysis",
|
|
2983
|
+
searchHint: "astro islands client hydration directives framework",
|
|
2984
|
+
description: "Analyze Astro islands (client:* directives) in a repo. Finds all interactive components with hydration directives, lists server islands with fallback status, and optionally generates optimization recommendations.",
|
|
2985
|
+
schema: {
|
|
2986
|
+
repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
|
|
2987
|
+
path_prefix: z.string().optional().describe("Only scan files under this path prefix"),
|
|
2988
|
+
include_recommendations: z.boolean().default(true).describe("Include optimization recommendations (default: true)"),
|
|
2989
|
+
},
|
|
2990
|
+
handler: async (args) => {
|
|
2991
|
+
const opts = {};
|
|
2992
|
+
if (args.repo != null)
|
|
2993
|
+
opts.repo = args.repo;
|
|
2994
|
+
if (args.path_prefix != null)
|
|
2995
|
+
opts.path_prefix = args.path_prefix;
|
|
2996
|
+
if (args.include_recommendations != null)
|
|
2997
|
+
opts.include_recommendations = args.include_recommendations;
|
|
2998
|
+
return await astroAnalyzeIslands(opts);
|
|
2999
|
+
},
|
|
3000
|
+
},
|
|
3001
|
+
{
|
|
3002
|
+
name: "astro_hydration_audit",
|
|
3003
|
+
category: "analysis",
|
|
3004
|
+
searchHint: "astro hydration audit anti-patterns client load",
|
|
3005
|
+
description: "Audit Astro hydration usage for anti-patterns such as client:load on heavy components, missing client directives, or suboptimal hydration strategies. Returns issues grouped by severity with a letter grade.",
|
|
3006
|
+
schema: {
|
|
3007
|
+
repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
|
|
3008
|
+
severity: z.enum(["all", "warnings", "errors"]).default("all").describe("Filter issues by severity (default: all)"),
|
|
3009
|
+
path_prefix: z.string().optional().describe("Only scan files under this path prefix"),
|
|
3010
|
+
},
|
|
3011
|
+
handler: async (args) => {
|
|
3012
|
+
const opts = {};
|
|
3013
|
+
if (args.repo != null)
|
|
3014
|
+
opts.repo = args.repo;
|
|
3015
|
+
if (args.severity != null)
|
|
3016
|
+
opts.severity = args.severity;
|
|
3017
|
+
if (args.path_prefix != null)
|
|
3018
|
+
opts.path_prefix = args.path_prefix;
|
|
3019
|
+
return await astroHydrationAudit(opts);
|
|
3020
|
+
},
|
|
3021
|
+
},
|
|
3022
|
+
{
|
|
3023
|
+
name: "astro_route_map",
|
|
3024
|
+
category: "navigation",
|
|
3025
|
+
searchHint: "astro routes pages endpoints file-based routing",
|
|
3026
|
+
description: "Map all Astro routes (pages + API endpoints) discovered from the file-based routing structure. Returns routes with type, dynamic params, and handler symbols. Supports json/tree/table output formats.",
|
|
3027
|
+
schema: {
|
|
3028
|
+
repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
|
|
3029
|
+
include_endpoints: z.boolean().default(true).describe("Include API endpoint routes (default: true)"),
|
|
3030
|
+
output_format: z.enum(["json", "tree", "table"]).default("json").describe("Output format: json | tree | table (default: json)"),
|
|
3031
|
+
},
|
|
3032
|
+
handler: async (args) => {
|
|
3033
|
+
const opts = {};
|
|
3034
|
+
if (args.repo != null)
|
|
3035
|
+
opts.repo = args.repo;
|
|
3036
|
+
if (args.include_endpoints != null)
|
|
3037
|
+
opts.include_endpoints = args.include_endpoints;
|
|
3038
|
+
if (args.output_format != null)
|
|
3039
|
+
opts.output_format = args.output_format;
|
|
3040
|
+
return await astroRouteMap(opts);
|
|
3041
|
+
},
|
|
3042
|
+
},
|
|
3043
|
+
{
|
|
3044
|
+
name: "astro_config_analyze",
|
|
3045
|
+
category: "analysis",
|
|
3046
|
+
searchHint: "astro config integrations adapter output mode",
|
|
3047
|
+
description: "Analyze an Astro project's configuration file (astro.config.mjs/ts/js). Extracts output mode (static/server/hybrid), adapter, integrations, site URL, and base path. Identifies dynamic/unresolved config.",
|
|
3048
|
+
schema: {
|
|
3049
|
+
repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
|
|
3050
|
+
},
|
|
3051
|
+
handler: async (args) => {
|
|
3052
|
+
const index = await getCodeIndex(args.repo ?? "");
|
|
3053
|
+
if (!index)
|
|
3054
|
+
throw new Error("Repository not found — run index_folder first");
|
|
3055
|
+
return await astroConfigAnalyze({ project_root: index.root });
|
|
3056
|
+
},
|
|
3057
|
+
},
|
|
3058
|
+
// --- Hono framework tools (Task 23) ---
|
|
3059
|
+
{
|
|
3060
|
+
name: "trace_middleware_chain",
|
|
3061
|
+
category: "graph",
|
|
3062
|
+
searchHint: "hono middleware chain trace order scope auth use",
|
|
3063
|
+
description: "Trace the ordered middleware chain for a Hono route. Returns full middleware stack in registration order for a given path+method.",
|
|
3064
|
+
schema: {
|
|
3065
|
+
repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
|
|
3066
|
+
path: z.string().describe("URL path to trace (e.g. '/api/users/:id')"),
|
|
3067
|
+
method: z.string().optional().describe("HTTP method filter (GET, POST, etc.)"),
|
|
3068
|
+
},
|
|
3069
|
+
handler: async (args) => {
|
|
3070
|
+
const { traceMiddlewareChain } = await import("./tools/hono-middleware-chain.js");
|
|
3071
|
+
return await traceMiddlewareChain(args.repo, args.path, args.method);
|
|
3072
|
+
},
|
|
3073
|
+
},
|
|
3074
|
+
{
|
|
3075
|
+
name: "analyze_hono_app",
|
|
3076
|
+
category: "analysis",
|
|
3077
|
+
searchHint: "hono overview analyze app routes middleware runtime env bindings rpc",
|
|
3078
|
+
description: "Complete Hono application overview: routes grouped by method/scope, middleware map, context vars, OpenAPI status, RPC exports (flags Issue #3869 slow pattern), runtime, env bindings. One call for full project analysis.",
|
|
3079
|
+
schema: {
|
|
3080
|
+
repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
|
|
3081
|
+
entry_file: z.string().optional().describe("Hono entry file (auto-detected if omitted)"),
|
|
3082
|
+
force_refresh: z.boolean().optional().describe("Clear cache and rebuild"),
|
|
3083
|
+
},
|
|
3084
|
+
handler: async (args) => {
|
|
3085
|
+
const { analyzeHonoApp } = await import("./tools/hono-analyze-app.js");
|
|
3086
|
+
return await analyzeHonoApp(args.repo, args.entry_file, args.force_refresh);
|
|
3087
|
+
},
|
|
3088
|
+
},
|
|
3089
|
+
{
|
|
3090
|
+
name: "trace_context_flow",
|
|
3091
|
+
category: "analysis",
|
|
3092
|
+
searchHint: "hono context flow c.set c.get c.var c.env middleware variable unguarded",
|
|
3093
|
+
description: "Trace Hono context variable flow (c.set/c.get/c.var/c.env). Detects MISSING_CONTEXT_VARIABLE findings where routes access variables that no middleware in their scope sets.",
|
|
3094
|
+
schema: {
|
|
3095
|
+
repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
|
|
3096
|
+
variable: z.string().optional().describe("Specific variable name to trace (default: all)"),
|
|
3097
|
+
},
|
|
3098
|
+
handler: async (args) => {
|
|
3099
|
+
const { traceContextFlow } = await import("./tools/hono-context-flow.js");
|
|
3100
|
+
return await traceContextFlow(args.repo, args.variable);
|
|
3101
|
+
},
|
|
3102
|
+
},
|
|
3103
|
+
{
|
|
3104
|
+
name: "extract_api_contract",
|
|
3105
|
+
category: "analysis",
|
|
3106
|
+
searchHint: "hono openapi contract api schema createRoute zValidator",
|
|
3107
|
+
description: "Extract OpenAPI-style API contract from a Hono app. Uses explicit createRoute() definitions when available, infers from regular routes otherwise. Format: 'openapi' (paths object) or 'summary' (table).",
|
|
3108
|
+
schema: {
|
|
3109
|
+
repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
|
|
3110
|
+
entry_file: z.string().optional().describe("Hono entry file (auto-detected if omitted)"),
|
|
3111
|
+
format: z.enum(["openapi", "summary"]).optional().describe("Output format (default: openapi)"),
|
|
3112
|
+
},
|
|
3113
|
+
handler: async (args) => {
|
|
3114
|
+
const { extractApiContract } = await import("./tools/hono-api-contract.js");
|
|
3115
|
+
return await extractApiContract(args.repo, args.entry_file, args.format);
|
|
3116
|
+
},
|
|
3117
|
+
},
|
|
3118
|
+
{
|
|
3119
|
+
name: "trace_rpc_types",
|
|
3120
|
+
category: "analysis",
|
|
3121
|
+
searchHint: "hono rpc client type export typeof slow pattern Issue 3869 compile time",
|
|
3122
|
+
description: "Analyze Hono RPC type exports. Detects the slow `export type X = typeof app` pattern from Issue #3869 (8-min CI compile time) and recommends splitting into per-route-group types.",
|
|
3123
|
+
schema: {
|
|
3124
|
+
repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
|
|
3125
|
+
},
|
|
3126
|
+
handler: async (args) => {
|
|
3127
|
+
const { traceRpcTypes } = await import("./tools/hono-rpc-types.js");
|
|
3128
|
+
return await traceRpcTypes(args.repo);
|
|
3129
|
+
},
|
|
3130
|
+
},
|
|
3131
|
+
{
|
|
3132
|
+
name: "audit_hono_security",
|
|
3133
|
+
category: "security",
|
|
3134
|
+
searchHint: "hono security audit rate limit secure headers auth order csrf",
|
|
3135
|
+
description: "Security audit of a Hono app: missing rate limiting on mutation routes, missing secure-headers middleware globally, auth middleware ordering violations. Returns prioritized findings.",
|
|
3136
|
+
schema: {
|
|
3137
|
+
repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
|
|
3138
|
+
},
|
|
3139
|
+
handler: async (args) => {
|
|
3140
|
+
const { auditHonoSecurity } = await import("./tools/hono-security.js");
|
|
3141
|
+
return await auditHonoSecurity(args.repo);
|
|
3142
|
+
},
|
|
3143
|
+
},
|
|
3144
|
+
{
|
|
3145
|
+
name: "visualize_hono_routes",
|
|
3146
|
+
category: "reporting",
|
|
3147
|
+
searchHint: "hono routes visualize mermaid tree diagram documentation",
|
|
3148
|
+
description: "Produce a visualization of Hono routing topology. Supports 'mermaid' (diagram) and 'tree' (ASCII) formats.",
|
|
3149
|
+
schema: {
|
|
3150
|
+
repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
|
|
3151
|
+
format: z.enum(["mermaid", "tree"]).optional().describe("Output format (default: tree)"),
|
|
3152
|
+
},
|
|
3153
|
+
handler: async (args) => {
|
|
3154
|
+
const { visualizeHonoRoutes } = await import("./tools/hono-visualize.js");
|
|
3155
|
+
return await visualizeHonoRoutes(args.repo, args.format);
|
|
3156
|
+
},
|
|
3157
|
+
},
|
|
3158
|
+
// --- Hono Phase 2 tools (T13) ---
|
|
3159
|
+
{
|
|
3160
|
+
name: "trace_conditional_middleware",
|
|
3161
|
+
category: "analysis",
|
|
3162
|
+
searchHint: "hono conditional middleware applied_when if method header path basicAuth auth gated",
|
|
3163
|
+
description: "List Hono middleware entries that are applied under a runtime condition (e.g., basicAuth only for non-GET methods). Each entry carries condition_type (method|header|path|custom) + condition_text. Closes blog-API false positive where audit_hono_security flagged inline-arrow conditional auth as missing.",
|
|
3164
|
+
schema: {
|
|
3165
|
+
repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
|
|
3166
|
+
scope: z.string().optional().describe("Filter to a specific middleware scope (e.g. '/posts/*')"),
|
|
3167
|
+
},
|
|
3168
|
+
handler: async (args) => {
|
|
3169
|
+
const { traceConditionalMiddleware } = await import("./tools/hono-conditional-middleware.js");
|
|
3170
|
+
return await traceConditionalMiddleware(args.repo, args.scope);
|
|
3171
|
+
},
|
|
3172
|
+
},
|
|
3173
|
+
{
|
|
3174
|
+
name: "analyze_inline_handler",
|
|
3175
|
+
category: "analysis",
|
|
3176
|
+
searchHint: "hono inline handler analyze c.json c.text status response error db fetch context",
|
|
3177
|
+
description: "Structured body analysis for each Hono inline handler: responses (c.json/text/html/redirect/newResponse with status + shape_hint), errors (throw new HTTPException/Error), db calls (prisma/db/knex/drizzle/mongoose/supabase), fetch calls, c.set/get/var/env access, inline validators, has_try_catch. Optional method + path filter. Named-handler routes return empty.",
|
|
3178
|
+
schema: {
|
|
3179
|
+
repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
|
|
3180
|
+
method: z.string().optional().describe("HTTP method filter (case-insensitive)"),
|
|
3181
|
+
path: z.string().optional().describe("Route path filter (exact match, e.g. '/users/:id')"),
|
|
3182
|
+
},
|
|
3183
|
+
handler: async (args) => {
|
|
3184
|
+
const { analyzeInlineHandler } = await import("./tools/hono-inline-analyze.js");
|
|
3185
|
+
return await analyzeInlineHandler(args.repo, args.method, args.path);
|
|
3186
|
+
},
|
|
3187
|
+
},
|
|
3188
|
+
{
|
|
3189
|
+
name: "extract_response_types",
|
|
3190
|
+
category: "analysis",
|
|
3191
|
+
searchHint: "hono response types status codes error paths RPC client InferResponseType Issue 4270",
|
|
3192
|
+
description: "Aggregate statically-knowable response types per route: c.json/text/html/body/redirect/newResponse emissions + throw new HTTPException/Error entries with status codes. Closes Hono Issue #4270 — RPC clients can generate types that include error paths. Returns routes[] plus total_statuses across the app.",
|
|
3193
|
+
schema: {
|
|
3194
|
+
repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
|
|
3195
|
+
},
|
|
3196
|
+
handler: async (args) => {
|
|
3197
|
+
const { extractResponseTypes } = await import("./tools/hono-response-types.js");
|
|
3198
|
+
return await extractResponseTypes(args.repo);
|
|
3199
|
+
},
|
|
3200
|
+
},
|
|
3201
|
+
{
|
|
3202
|
+
name: "detect_middleware_env_regression",
|
|
3203
|
+
category: "analysis",
|
|
3204
|
+
searchHint: "hono middleware env regression createMiddleware generic BlankEnv type Issue 3587",
|
|
3205
|
+
description: "Heuristic static check for Hono Issue #3587: flags middleware chains of 3+ entries where an intermediate member is declared with plain createMiddleware(...) (no Env generic), which resets the accumulated Env type to BlankEnv for downstream middleware. Reports chain_scope + chain_length + middleware_name + definition file:line. Includes a heuristic disclaimer in the result note.",
|
|
3206
|
+
schema: {
|
|
3207
|
+
repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
|
|
3208
|
+
},
|
|
3209
|
+
handler: async (args) => {
|
|
3210
|
+
const { detectMiddlewareEnvRegression } = await import("./tools/hono-env-regression.js");
|
|
3211
|
+
return await detectMiddlewareEnvRegression(args.repo);
|
|
3212
|
+
},
|
|
3213
|
+
},
|
|
3214
|
+
{
|
|
3215
|
+
name: "detect_hono_modules",
|
|
3216
|
+
category: "analysis",
|
|
3217
|
+
searchHint: "hono modules architecture cluster path prefix middleware bindings enterprise Issue 4121",
|
|
3218
|
+
description: "Cluster Hono routes into logical modules by 2-segment path prefix, rolling up middleware chains, env bindings (from inline_analysis context_access), and source files per module. Closes Hono Issue #4121 — surfaces the implicit module structure for architecture review of enterprise apps. No new AST walking; post-processes the existing HonoAppModel.",
|
|
3219
|
+
schema: {
|
|
3220
|
+
repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
|
|
3221
|
+
},
|
|
3222
|
+
handler: async (args) => {
|
|
3223
|
+
const { detectHonoModules } = await import("./tools/hono-modules.js");
|
|
3224
|
+
return await detectHonoModules(args.repo);
|
|
3225
|
+
},
|
|
3226
|
+
},
|
|
3227
|
+
{
|
|
3228
|
+
name: "find_dead_hono_routes",
|
|
3229
|
+
category: "analysis",
|
|
3230
|
+
searchHint: "hono dead routes unused RPC client caller refactor monorepo cleanup",
|
|
3231
|
+
description: "Heuristically flag Hono server routes whose path segments do not appear in any non-server .ts/.tsx/.js/.jsx source file in the repo. Useful in monorepos to identify server endpoints that no Hono RPC client calls after refactors. Fully-dynamic routes (`/:id` only) are skipped. Documented as best-effort via the result note field.",
|
|
3232
|
+
schema: {
|
|
3233
|
+
repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
|
|
3234
|
+
},
|
|
3235
|
+
handler: async (args) => {
|
|
3236
|
+
const { findDeadHonoRoutes } = await import("./tools/hono-dead-routes.js");
|
|
3237
|
+
return await findDeadHonoRoutes(args.repo);
|
|
3238
|
+
},
|
|
3239
|
+
},
|
|
3240
|
+
// --- Next.js framework tools ---
|
|
3241
|
+
{
|
|
3242
|
+
name: "analyze_nextjs_components",
|
|
3243
|
+
category: "analysis",
|
|
3244
|
+
searchHint: "nextjs next.js component server client classifier use client use server hooks",
|
|
3245
|
+
description: "Classify Next.js files as Server or Client components via AST analysis. Detects 'use client'/'use server' directives (with 512-byte window + comment stripping), hooks, JSX event handlers, browser globals, and next/dynamic({ ssr:false }). Flags unnecessary 'use client' and async client components. Supports monorepo workspace auto-detection.",
|
|
3246
|
+
schema: {
|
|
3247
|
+
repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
|
|
3248
|
+
workspace: z.string().optional().describe("Monorepo workspace path, e.g. 'apps/web'"),
|
|
3249
|
+
file_pattern: z.string().optional().describe("Glob to scope the scan, e.g. 'app/products/**'"),
|
|
3250
|
+
max_files: z.number().int().positive().optional().describe("Max files to scan (default 2000)"),
|
|
3251
|
+
},
|
|
3252
|
+
handler: async (args) => {
|
|
3253
|
+
const opts = {};
|
|
3254
|
+
if (args.workspace != null)
|
|
3255
|
+
opts.workspace = args.workspace;
|
|
3256
|
+
if (args.file_pattern != null)
|
|
3257
|
+
opts.file_pattern = args.file_pattern;
|
|
1290
3258
|
if (args.max_files != null)
|
|
1291
3259
|
opts.max_files = args.max_files;
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
const result = await reviewDiff(args.repo, opts);
|
|
1295
|
-
return formatReviewDiff(result);
|
|
3260
|
+
const result = await analyzeNextjsComponents(args.repo ?? "", opts);
|
|
3261
|
+
return formatNextjsComponents(result);
|
|
1296
3262
|
},
|
|
1297
3263
|
},
|
|
1298
|
-
// --- Stats ---
|
|
1299
3264
|
{
|
|
1300
|
-
name: "
|
|
1301
|
-
category: "
|
|
1302
|
-
searchHint: "
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
3265
|
+
name: "nextjs_route_map",
|
|
3266
|
+
category: "analysis",
|
|
3267
|
+
searchHint: "nextjs next.js route map app router pages router rendering strategy SSG SSR ISR edge middleware",
|
|
3268
|
+
description: "Complete Next.js route map with rendering strategy per route. Enumerates App Router and Pages Router conventions, reads route segment config exports (dynamic/revalidate/runtime), classifies each route as static/ssr/isr/edge/client, detects metadata exports, computes layout chain, and flags hybrid conflicts where the same URL is served by both routers.",
|
|
3269
|
+
schema: {
|
|
3270
|
+
repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
|
|
3271
|
+
workspace: z.string().optional().describe("Monorepo workspace path, e.g. 'apps/web'"),
|
|
3272
|
+
router: z.enum(["app", "pages", "both"]).optional().describe("Which routers to scan (default 'both')"),
|
|
3273
|
+
include_metadata: z.boolean().optional().describe("Include metadata export detection (default true)"),
|
|
3274
|
+
max_routes: z.number().int().positive().optional().describe("Max routes to process (default 1000)"),
|
|
3275
|
+
},
|
|
3276
|
+
handler: async (args) => {
|
|
3277
|
+
const opts = {};
|
|
3278
|
+
if (args.workspace != null)
|
|
3279
|
+
opts.workspace = args.workspace;
|
|
3280
|
+
if (args.router != null)
|
|
3281
|
+
opts.router = args.router;
|
|
3282
|
+
if (args.include_metadata != null)
|
|
3283
|
+
opts.include_metadata = args.include_metadata;
|
|
3284
|
+
if (args.max_routes != null)
|
|
3285
|
+
opts.max_routes = args.max_routes;
|
|
3286
|
+
const result = await nextjsRouteMap(args.repo ?? "", opts);
|
|
3287
|
+
return formatNextjsRouteMap(result);
|
|
1309
3288
|
},
|
|
1310
3289
|
},
|
|
1311
|
-
// ── Session context tools ───────────────────────────────────────────────
|
|
1312
3290
|
{
|
|
1313
|
-
name: "
|
|
1314
|
-
category: "
|
|
1315
|
-
searchHint: "
|
|
1316
|
-
description: "
|
|
3291
|
+
name: "nextjs_metadata_audit",
|
|
3292
|
+
category: "analysis",
|
|
3293
|
+
searchHint: "nextjs seo metadata title description og image audit canonical twitter json-ld",
|
|
3294
|
+
description: "Audit Next.js page metadata for SEO completeness with per-route scoring. Walks app/page.tsx files, extracts title/description/openGraph/canonical/twitter/JSON-LD via tree-sitter, scores each route 0-100 with a weighted formula, and aggregates a per-grade distribution + top issue list.",
|
|
1317
3295
|
schema: {
|
|
1318
|
-
repo: z.string().optional().describe("
|
|
3296
|
+
repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
|
|
3297
|
+
workspace: z.string().optional().describe("Monorepo workspace path, e.g. 'apps/web'"),
|
|
3298
|
+
max_routes: z.number().int().positive().optional().describe("Max routes to process (default 1000)"),
|
|
1319
3299
|
},
|
|
1320
3300
|
handler: async (args) => {
|
|
1321
|
-
|
|
3301
|
+
const opts = {};
|
|
3302
|
+
if (args.workspace != null)
|
|
3303
|
+
opts.workspace = args.workspace;
|
|
3304
|
+
if (args.max_routes != null)
|
|
3305
|
+
opts.max_routes = args.max_routes;
|
|
3306
|
+
const result = await nextjsMetadataAudit(args.repo ?? "", opts);
|
|
3307
|
+
return formatNextjsMetadataAudit(result);
|
|
1322
3308
|
},
|
|
1323
3309
|
},
|
|
1324
3310
|
{
|
|
1325
|
-
name: "
|
|
1326
|
-
category: "
|
|
1327
|
-
searchHint: "
|
|
1328
|
-
description: "
|
|
3311
|
+
name: "nextjs_audit_server_actions",
|
|
3312
|
+
category: "security",
|
|
3313
|
+
searchHint: "nextjs server actions security audit auth validation rate limit zod use server",
|
|
3314
|
+
description: "Audit Next.js Server Actions for security weaknesses across four checks: authorization guards, input validation (Zod-aware), rate limiting, and structured error handling. Per-action weighted scoring (auth 40, validation 30, rate 20, errors 10) with grade buckets.",
|
|
1329
3315
|
schema: {
|
|
1330
|
-
repo: z.string().optional().describe("
|
|
1331
|
-
|
|
3316
|
+
repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
|
|
3317
|
+
workspace: z.string().optional().describe("Monorepo workspace path, e.g. 'apps/web'"),
|
|
3318
|
+
max_files: z.number().int().positive().optional().describe("Max files to scan (default 2000)"),
|
|
1332
3319
|
},
|
|
1333
3320
|
handler: async (args) => {
|
|
1334
|
-
const
|
|
1335
|
-
|
|
3321
|
+
const opts = {};
|
|
3322
|
+
if (args.workspace != null)
|
|
3323
|
+
opts.workspace = args.workspace;
|
|
3324
|
+
if (args.max_files != null)
|
|
3325
|
+
opts.max_files = args.max_files;
|
|
3326
|
+
const result = await nextjsAuditServerActions(args.repo ?? "", opts);
|
|
3327
|
+
return formatNextjsAuditServerActions(result);
|
|
1336
3328
|
},
|
|
1337
3329
|
},
|
|
1338
|
-
// --- Project Analysis ---
|
|
1339
3330
|
{
|
|
1340
|
-
name: "
|
|
3331
|
+
name: "nextjs_api_contract",
|
|
1341
3332
|
category: "analysis",
|
|
1342
|
-
searchHint: "
|
|
1343
|
-
description: "
|
|
3333
|
+
searchHint: "nextjs api contract route handler openapi method body schema response zod",
|
|
3334
|
+
description: "Extract API handler contracts from Next.js route handlers (App Router app/api/**/route.ts and Pages Router pages/api/**/*.ts). Captures HTTP methods, query params, request body schemas (Zod-aware), response shapes, and inferred status codes per handler.",
|
|
1344
3335
|
schema: {
|
|
1345
3336
|
repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
|
|
1346
|
-
|
|
3337
|
+
workspace: z.string().optional().describe("Monorepo workspace path, e.g. 'apps/web'"),
|
|
3338
|
+
max_files: z.number().int().positive().optional().describe("Max files to scan (default 1000)"),
|
|
1347
3339
|
},
|
|
1348
3340
|
handler: async (args) => {
|
|
1349
|
-
const
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
3341
|
+
const opts = {};
|
|
3342
|
+
if (args.workspace != null)
|
|
3343
|
+
opts.workspace = args.workspace;
|
|
3344
|
+
if (args.max_files != null)
|
|
3345
|
+
opts.max_files = args.max_files;
|
|
3346
|
+
const result = await nextjsApiContract(args.repo ?? "", opts);
|
|
3347
|
+
return formatNextjsApiContract(result);
|
|
1353
3348
|
},
|
|
1354
3349
|
},
|
|
1355
3350
|
{
|
|
1356
|
-
name: "
|
|
1357
|
-
category: "
|
|
1358
|
-
searchHint: "
|
|
1359
|
-
description: "
|
|
1360
|
-
schema: {
|
|
1361
|
-
|
|
3351
|
+
name: "nextjs_boundary_analyzer",
|
|
3352
|
+
category: "analysis",
|
|
3353
|
+
searchHint: "nextjs client boundary use client component bundle imports loc score",
|
|
3354
|
+
description: "Analyze Next.js client component boundaries — walks `app/**/*.{tsx,jsx}` files marked `\"use client\"`, computes a deterministic ranking score from cheap signals (LOC, import counts, dynamic imports, third-party imports), and returns a top-N list of largest offenders. Score is signal-based, not actual bundle bytes.",
|
|
3355
|
+
schema: {
|
|
3356
|
+
repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
|
|
3357
|
+
workspace: z.string().optional().describe("Monorepo workspace path, e.g. 'apps/web'"),
|
|
3358
|
+
top_n: z.number().int().positive().optional().describe("Number of top entries to return (default 20)"),
|
|
3359
|
+
},
|
|
3360
|
+
handler: async (args) => {
|
|
3361
|
+
const opts = {};
|
|
3362
|
+
if (args.workspace != null)
|
|
3363
|
+
opts.workspace = args.workspace;
|
|
3364
|
+
if (args.top_n != null)
|
|
3365
|
+
opts.top_n = args.top_n;
|
|
3366
|
+
const result = await nextjsBoundaryAnalyzer(args.repo ?? "", opts);
|
|
3367
|
+
return formatNextjsBoundaryAnalyzer(result);
|
|
3368
|
+
},
|
|
3369
|
+
},
|
|
3370
|
+
{
|
|
3371
|
+
name: "nextjs_link_integrity",
|
|
3372
|
+
category: "analysis",
|
|
3373
|
+
searchHint: "nextjs link integrity broken navigation Link href router push 404",
|
|
3374
|
+
description: "Cross-reference Next.js navigation refs (<Link href>, router.push/.replace) against the route map to flag broken links. Template-literal hrefs are bucketed as 'unresolved' rather than guessed.",
|
|
3375
|
+
schema: {
|
|
3376
|
+
repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
|
|
3377
|
+
workspace: z.string().optional().describe("Monorepo workspace path, e.g. 'apps/web'"),
|
|
3378
|
+
max_files: z.number().int().positive().optional().describe("Max files to scan (default 2000)"),
|
|
3379
|
+
},
|
|
3380
|
+
handler: async (args) => {
|
|
3381
|
+
const opts = {};
|
|
3382
|
+
if (args.workspace != null)
|
|
3383
|
+
opts.workspace = args.workspace;
|
|
3384
|
+
if (args.max_files != null)
|
|
3385
|
+
opts.max_files = args.max_files;
|
|
3386
|
+
const result = await nextjsLinkIntegrity(args.repo ?? "", opts);
|
|
3387
|
+
return formatNextjsLinkIntegrity(result);
|
|
3388
|
+
},
|
|
3389
|
+
},
|
|
3390
|
+
{
|
|
3391
|
+
name: "nextjs_data_flow",
|
|
3392
|
+
category: "analysis",
|
|
3393
|
+
searchHint: "nextjs data flow fetch waterfall cache cookies headers ssr revalidate",
|
|
3394
|
+
description: "Analyze data fetching patterns in Next.js pages: detect fetch waterfalls (sequential awaits in same scope), classify cache strategies (no-cache, cached, ISR), and aggregate per-page data flow with totals.",
|
|
3395
|
+
schema: {
|
|
3396
|
+
repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
|
|
3397
|
+
workspace: z.string().optional().describe("Monorepo workspace path, e.g. 'apps/web'"),
|
|
3398
|
+
url_path: z.string().optional().describe("Filter to a single URL path"),
|
|
3399
|
+
},
|
|
3400
|
+
handler: async (args) => {
|
|
3401
|
+
const opts = {};
|
|
3402
|
+
if (args.workspace != null)
|
|
3403
|
+
opts.workspace = args.workspace;
|
|
3404
|
+
if (args.url_path != null)
|
|
3405
|
+
opts.url_path = args.url_path;
|
|
3406
|
+
const result = await nextjsDataFlow(args.repo ?? "", opts);
|
|
3407
|
+
return formatNextjsDataFlow(result);
|
|
3408
|
+
},
|
|
3409
|
+
},
|
|
3410
|
+
{
|
|
3411
|
+
name: "nextjs_middleware_coverage",
|
|
3412
|
+
category: "security",
|
|
3413
|
+
searchHint: "nextjs middleware coverage protected admin auth route matcher security",
|
|
3414
|
+
description: "Cross-reference Next.js routes with middleware matcher config to compute coverage. Flags admin/dashboard routes without middleware protection as high-severity warnings.",
|
|
3415
|
+
schema: {
|
|
3416
|
+
repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
|
|
3417
|
+
workspace: z.string().optional().describe("Monorepo workspace path, e.g. 'apps/web'"),
|
|
3418
|
+
flag_admin_prefix: z.union([z.string(), z.array(z.string())]).optional().describe("Admin path prefix(es) to flag (default: ['/admin', '/dashboard'])"),
|
|
3419
|
+
},
|
|
3420
|
+
handler: async (args) => {
|
|
3421
|
+
const opts = {};
|
|
3422
|
+
if (args.workspace != null)
|
|
3423
|
+
opts.workspace = args.workspace;
|
|
3424
|
+
if (args.flag_admin_prefix != null)
|
|
3425
|
+
opts.flag_admin_prefix = args.flag_admin_prefix;
|
|
3426
|
+
const result = await nextjsMiddlewareCoverage(args.repo ?? "", opts);
|
|
3427
|
+
return formatNextjsMiddlewareCoverage(result);
|
|
3428
|
+
},
|
|
3429
|
+
},
|
|
3430
|
+
{
|
|
3431
|
+
name: "framework_audit",
|
|
3432
|
+
category: "analysis",
|
|
3433
|
+
searchHint: "nextjs framework audit meta-tool overall score security metadata routes components",
|
|
3434
|
+
description: "Run all Next.js sub-audits (components, routes, metadata, security, api_contract, boundary, links, data_flow, middleware_coverage) and aggregate into a unified weighted overall score with grade. Use as a single first-call for any Next.js project.",
|
|
3435
|
+
schema: {
|
|
3436
|
+
repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
|
|
3437
|
+
workspace: z.string().optional().describe("Monorepo workspace path, e.g. 'apps/web'"),
|
|
3438
|
+
tools: z.array(z.string()).optional().describe("Subset of tools to run (default: all 9). Names: components, routes, metadata, security, api_contract, boundary, links, data_flow, middleware_coverage"),
|
|
3439
|
+
},
|
|
3440
|
+
handler: async (args) => {
|
|
3441
|
+
const opts = {};
|
|
3442
|
+
if (args.workspace != null)
|
|
3443
|
+
opts.workspace = args.workspace;
|
|
3444
|
+
if (args.tools != null)
|
|
3445
|
+
opts.tools = args.tools;
|
|
3446
|
+
const result = await frameworkAudit(args.repo ?? "", opts);
|
|
3447
|
+
return formatFrameworkAudit(result);
|
|
3448
|
+
},
|
|
3449
|
+
},
|
|
3450
|
+
// ── SQL analysis tools (hidden/discoverable) ─────────────
|
|
3451
|
+
{
|
|
3452
|
+
name: "analyze_schema",
|
|
3453
|
+
category: "analysis",
|
|
3454
|
+
searchHint: "SQL schema ERD entity relationship tables views columns foreign key database migration",
|
|
3455
|
+
description: "Analyze SQL schema: tables, views, columns, foreign keys, relationships. Output as JSON or Mermaid ERD.",
|
|
3456
|
+
schema: {
|
|
3457
|
+
repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
|
|
3458
|
+
file_pattern: z.string().optional().describe("Filter SQL files by pattern (e.g. 'migrations/')"),
|
|
3459
|
+
output_format: z.enum(["json", "mermaid"]).optional().describe("Output format (default: json)"),
|
|
3460
|
+
include_columns: zBool().describe("Include column details in output (default: true)"),
|
|
3461
|
+
},
|
|
3462
|
+
handler: async (args) => {
|
|
3463
|
+
const { analyzeSchema } = await import("./tools/sql-tools.js");
|
|
3464
|
+
const opts = {};
|
|
3465
|
+
if (args.file_pattern != null)
|
|
3466
|
+
opts.file_pattern = args.file_pattern;
|
|
3467
|
+
if (args.output_format != null)
|
|
3468
|
+
opts.output_format = args.output_format;
|
|
3469
|
+
if (args.include_columns != null)
|
|
3470
|
+
opts.include_columns = args.include_columns;
|
|
3471
|
+
const result = await analyzeSchema(args.repo, opts);
|
|
3472
|
+
const parts = [];
|
|
3473
|
+
parts.push(`Tables: ${result.tables.length} | Views: ${result.views.length} | Relationships: ${result.relationships.length}`);
|
|
3474
|
+
if (result.warnings.length > 0)
|
|
3475
|
+
parts.push(`Warnings: ${result.warnings.join("; ")}`);
|
|
3476
|
+
if (result.mermaid) {
|
|
3477
|
+
parts.push("");
|
|
3478
|
+
parts.push(result.mermaid);
|
|
3479
|
+
}
|
|
3480
|
+
else {
|
|
3481
|
+
for (const t of result.tables) {
|
|
3482
|
+
const cols = t.columns.map((c) => `${c.name} ${c.type}`).join(", ");
|
|
3483
|
+
parts.push(` ${t.name} (${t.file}:${t.line}) — ${cols || "(no columns)"}`);
|
|
3484
|
+
}
|
|
3485
|
+
for (const v of result.views) {
|
|
3486
|
+
parts.push(` VIEW ${v.name} (${v.file}:${v.line})`);
|
|
3487
|
+
}
|
|
3488
|
+
if (result.relationships.length > 0) {
|
|
3489
|
+
parts.push("Relationships:");
|
|
3490
|
+
for (const r of result.relationships) {
|
|
3491
|
+
parts.push(` ${r.from_table}.${r.from_column} → ${r.to_table}.${r.to_column} [${r.type}]`);
|
|
3492
|
+
}
|
|
3493
|
+
}
|
|
3494
|
+
}
|
|
3495
|
+
return parts.join("\n");
|
|
3496
|
+
},
|
|
3497
|
+
},
|
|
3498
|
+
{
|
|
3499
|
+
name: "trace_query",
|
|
3500
|
+
category: "analysis",
|
|
3501
|
+
searchHint: "SQL table query trace references cross-language ORM Prisma Drizzle migration",
|
|
3502
|
+
description: "Trace SQL table references across the codebase: DDL, DML, FK, and ORM models (Prisma, Drizzle).",
|
|
3503
|
+
schema: {
|
|
3504
|
+
repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
|
|
3505
|
+
table: z.string().describe("Table name to trace (required)"),
|
|
3506
|
+
include_orm: zBool().describe("Check Prisma/Drizzle ORM models (default: true)"),
|
|
3507
|
+
file_pattern: z.string().optional().describe("Scope search to files matching pattern"),
|
|
3508
|
+
max_references: zNum().describe("Maximum references to return (default: 500)"),
|
|
3509
|
+
},
|
|
3510
|
+
handler: async (args) => {
|
|
3511
|
+
const { traceQuery } = await import("./tools/sql-tools.js");
|
|
3512
|
+
const opts = {
|
|
3513
|
+
table: args.table,
|
|
3514
|
+
};
|
|
3515
|
+
if (args.include_orm != null)
|
|
3516
|
+
opts.include_orm = args.include_orm;
|
|
3517
|
+
if (args.file_pattern != null)
|
|
3518
|
+
opts.file_pattern = args.file_pattern;
|
|
3519
|
+
if (args.max_references != null)
|
|
3520
|
+
opts.max_references = args.max_references;
|
|
3521
|
+
const result = await traceQuery(args.repo, opts);
|
|
3522
|
+
const parts = [];
|
|
3523
|
+
if (result.table_definition) {
|
|
3524
|
+
parts.push(`Definition: ${result.table_definition.file}:${result.table_definition.line} [${result.table_definition.kind}]`);
|
|
3525
|
+
}
|
|
3526
|
+
else {
|
|
3527
|
+
parts.push(`Definition: not found in index`);
|
|
3528
|
+
}
|
|
3529
|
+
parts.push(`SQL references: ${result.sql_references.length}${result.truncated ? " (truncated)" : ""}`);
|
|
3530
|
+
for (const ref of result.sql_references.slice(0, 50)) {
|
|
3531
|
+
parts.push(` ${ref.file}:${ref.line} [${ref.type}] ${ref.context}`);
|
|
3532
|
+
}
|
|
3533
|
+
if (result.orm_references.length > 0) {
|
|
3534
|
+
parts.push(`ORM references: ${result.orm_references.length}`);
|
|
3535
|
+
for (const ref of result.orm_references) {
|
|
3536
|
+
parts.push(` ${ref.file}:${ref.line} [${ref.orm}] model ${ref.model_name}`);
|
|
3537
|
+
}
|
|
3538
|
+
}
|
|
3539
|
+
if (result.warnings.length > 0) {
|
|
3540
|
+
parts.push(`Warnings: ${result.warnings.join("; ")}`);
|
|
3541
|
+
}
|
|
3542
|
+
return parts.join("\n");
|
|
3543
|
+
},
|
|
3544
|
+
},
|
|
3545
|
+
{
|
|
3546
|
+
name: "analyze_schema_complexity",
|
|
3547
|
+
category: "analysis",
|
|
3548
|
+
searchHint: "schema complexity god table column count FK index score refactor",
|
|
3549
|
+
description: "Per-table complexity score based on column count, FK relationships, and indexes. Identifies god tables needing refactoring. Sorted by score descending.",
|
|
3550
|
+
schema: {
|
|
3551
|
+
repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
|
|
3552
|
+
file_pattern: z.string().optional().describe("Scope to files matching pattern"),
|
|
3553
|
+
top_n: zNum().describe("Return top N most complex tables (default: 50)"),
|
|
3554
|
+
},
|
|
3555
|
+
handler: async (args) => {
|
|
3556
|
+
const { analyzeSchemaComplexity } = await import("./tools/sql-tools.js");
|
|
3557
|
+
const opts = {};
|
|
3558
|
+
if (args.file_pattern != null)
|
|
3559
|
+
opts.file_pattern = args.file_pattern;
|
|
3560
|
+
if (args.top_n != null)
|
|
3561
|
+
opts.top_n = args.top_n;
|
|
3562
|
+
const result = await analyzeSchemaComplexity(args.repo, opts);
|
|
3563
|
+
const parts = [];
|
|
3564
|
+
parts.push(`Schema complexity: ${result.tables.length} tables`);
|
|
3565
|
+
parts.push(`${"Table".padEnd(30)} ${"Cols".padStart(5)} ${"FKs".padStart(4)} ${"Idx".padStart(4)} ${"Score".padStart(7)}`);
|
|
3566
|
+
for (const t of result.tables) {
|
|
3567
|
+
parts.push(` ${t.name.padEnd(28)} ${String(t.column_count).padStart(5)} ${String(t.fk_count).padStart(4)} ${String(t.index_count).padStart(4)} ${t.score.toFixed(1).padStart(7)}`);
|
|
3568
|
+
}
|
|
3569
|
+
return parts.join("\n");
|
|
3570
|
+
},
|
|
3571
|
+
},
|
|
3572
|
+
{
|
|
3573
|
+
name: "scan_dml_safety",
|
|
3574
|
+
category: "analysis",
|
|
3575
|
+
searchHint: "DML safety SQL DELETE UPDATE SELECT star WHERE clause unbounded dangerous query",
|
|
3576
|
+
description: "Scan codebase for unsafe SQL DML patterns: DELETE/UPDATE without WHERE (data loss risk), SELECT * (unbounded reads). Cross-language — finds SQL in .ts, .py, .go, .php files.",
|
|
3577
|
+
schema: {
|
|
3578
|
+
repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
|
|
3579
|
+
file_pattern: z.string().optional().describe("Scope to files matching pattern"),
|
|
3580
|
+
max_results: zNum().describe("Max findings per pattern (default: 200)"),
|
|
3581
|
+
},
|
|
3582
|
+
handler: async (args) => {
|
|
3583
|
+
const { scanDmlSafety } = await import("./tools/sql-tools.js");
|
|
3584
|
+
const opts = {};
|
|
3585
|
+
if (args.file_pattern != null)
|
|
3586
|
+
opts.file_pattern = args.file_pattern;
|
|
3587
|
+
if (args.max_results != null)
|
|
3588
|
+
opts.max_results = args.max_results;
|
|
3589
|
+
const result = await scanDmlSafety(args.repo, opts);
|
|
3590
|
+
const parts = [];
|
|
3591
|
+
parts.push(`DML safety: ${result.summary.total} findings across ${result.summary.files_scanned} files`);
|
|
3592
|
+
for (const [rule, count] of Object.entries(result.summary.by_rule)) {
|
|
3593
|
+
parts.push(` ${rule}: ${count}`);
|
|
3594
|
+
}
|
|
3595
|
+
const high = result.findings.filter((f) => f.severity === "high");
|
|
3596
|
+
if (high.length > 0) {
|
|
3597
|
+
parts.push("\n⚠ HIGH RISK:");
|
|
3598
|
+
for (const f of high.slice(0, 20)) {
|
|
3599
|
+
parts.push(` [${f.rule}] ${f.file}:${f.line} ${f.context ?? ""}`);
|
|
3600
|
+
}
|
|
3601
|
+
}
|
|
3602
|
+
return parts.join("\n");
|
|
3603
|
+
},
|
|
3604
|
+
},
|
|
3605
|
+
{
|
|
3606
|
+
name: "lint_schema",
|
|
3607
|
+
category: "analysis",
|
|
3608
|
+
searchHint: "lint SQL schema anti-pattern primary key wide table duplicate index design",
|
|
3609
|
+
description: "Lint SQL schema for anti-patterns: missing primary key, wide tables (>20 cols), duplicate index names. Conservative ruleset with near-zero false positives.",
|
|
3610
|
+
schema: {
|
|
3611
|
+
repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
|
|
3612
|
+
file_pattern: z.string().optional().describe("Scope to files matching pattern"),
|
|
3613
|
+
},
|
|
3614
|
+
handler: async (args) => {
|
|
3615
|
+
const { lintSchema } = await import("./tools/sql-tools.js");
|
|
3616
|
+
const opts = {};
|
|
3617
|
+
if (args.file_pattern != null)
|
|
3618
|
+
opts.file_pattern = args.file_pattern;
|
|
3619
|
+
const result = await lintSchema(args.repo, opts);
|
|
3620
|
+
const parts = [];
|
|
3621
|
+
parts.push(`Schema lint: ${result.summary.total} finding${result.summary.total === 1 ? "" : "s"}`);
|
|
3622
|
+
for (const [rule, count] of Object.entries(result.summary.by_rule)) {
|
|
3623
|
+
parts.push(` ${rule}: ${count}`);
|
|
3624
|
+
}
|
|
3625
|
+
if (result.warnings.length > 0) {
|
|
3626
|
+
for (const w of result.warnings)
|
|
3627
|
+
parts.push(`⚠ ${w}`);
|
|
3628
|
+
}
|
|
3629
|
+
if (result.findings.length > 0) {
|
|
3630
|
+
parts.push("");
|
|
3631
|
+
for (const f of result.findings.slice(0, 30)) {
|
|
3632
|
+
parts.push(` [${f.severity.toUpperCase()}] ${f.rule}: ${f.detail} (${f.file}:${f.line})`);
|
|
3633
|
+
}
|
|
3634
|
+
}
|
|
3635
|
+
return parts.join("\n");
|
|
3636
|
+
},
|
|
3637
|
+
},
|
|
3638
|
+
{
|
|
3639
|
+
name: "diff_migrations",
|
|
3640
|
+
category: "analysis",
|
|
3641
|
+
searchHint: "migration diff SQL destructive DROP ALTER ADD schema change deploy risk",
|
|
3642
|
+
description: "Scan SQL migration files and classify operations as additive (CREATE TABLE), modifying (ALTER ADD), or destructive (DROP TABLE, DROP COLUMN, TRUNCATE). Flags deploy risks.",
|
|
3643
|
+
schema: {
|
|
3644
|
+
repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
|
|
3645
|
+
file_pattern: z.string().optional().describe("Scope to migration files matching pattern"),
|
|
3646
|
+
},
|
|
3647
|
+
handler: async (args) => {
|
|
3648
|
+
const { diffMigrations } = await import("./tools/sql-tools.js");
|
|
3649
|
+
const opts = {};
|
|
3650
|
+
if (args.file_pattern != null)
|
|
3651
|
+
opts.file_pattern = args.file_pattern;
|
|
3652
|
+
const result = await diffMigrations(args.repo, opts);
|
|
3653
|
+
const parts = [];
|
|
3654
|
+
parts.push(`Migration ops: ${result.summary.additive + result.summary.modifying + result.summary.destructive} across ${result.summary.total_files} files`);
|
|
3655
|
+
parts.push(` additive: ${result.summary.additive}`);
|
|
3656
|
+
parts.push(` modifying: ${result.summary.modifying}`);
|
|
3657
|
+
parts.push(` destructive: ${result.summary.destructive}`);
|
|
3658
|
+
if (result.destructive.length > 0) {
|
|
3659
|
+
parts.push("\n⚠ DESTRUCTIVE:");
|
|
3660
|
+
for (const d of result.destructive) {
|
|
3661
|
+
parts.push(` [${d.severity.toUpperCase()}] ${d.operation} ${d.target} (${d.file}:${d.line})`);
|
|
3662
|
+
}
|
|
3663
|
+
}
|
|
3664
|
+
if (result.modifying.length > 0) {
|
|
3665
|
+
parts.push("\nModifying:");
|
|
3666
|
+
for (const m of result.modifying.slice(0, 20)) {
|
|
3667
|
+
parts.push(` ${m.operation} ${m.target} (${m.file}:${m.line})`);
|
|
3668
|
+
}
|
|
3669
|
+
}
|
|
3670
|
+
return parts.join("\n");
|
|
3671
|
+
},
|
|
3672
|
+
},
|
|
3673
|
+
{
|
|
3674
|
+
name: "find_orphan_tables",
|
|
3675
|
+
category: "analysis",
|
|
3676
|
+
searchHint: "orphan table SQL unused dead unreferenced no query no model drop candidate",
|
|
3677
|
+
description: "Find SQL tables with zero references in the codebase — no DML queries, no ORM models, no FK references. Candidates for DROP TABLE.",
|
|
3678
|
+
schema: {
|
|
3679
|
+
repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
|
|
3680
|
+
file_pattern: z.string().optional().describe("Scope to SQL files matching pattern"),
|
|
3681
|
+
},
|
|
3682
|
+
handler: async (args) => {
|
|
3683
|
+
const { findOrphanTables } = await import("./tools/sql-tools.js");
|
|
3684
|
+
const opts = {};
|
|
3685
|
+
if (args.file_pattern != null)
|
|
3686
|
+
opts.file_pattern = args.file_pattern;
|
|
3687
|
+
const result = await findOrphanTables(args.repo, opts);
|
|
3688
|
+
const parts = [];
|
|
3689
|
+
parts.push(`Tables: ${result.total_tables} | Orphans: ${result.orphan_count}`);
|
|
3690
|
+
if (result.orphans.length > 0) {
|
|
3691
|
+
parts.push("");
|
|
3692
|
+
for (const o of result.orphans) {
|
|
3693
|
+
parts.push(` ${o.name.padEnd(30)} ${o.column_count} cols ${o.file}:${o.line}`);
|
|
3694
|
+
}
|
|
3695
|
+
}
|
|
3696
|
+
else {
|
|
3697
|
+
parts.push("No orphan tables found — all tables have at least one reference.");
|
|
3698
|
+
}
|
|
3699
|
+
return parts.join("\n");
|
|
3700
|
+
},
|
|
3701
|
+
},
|
|
3702
|
+
{
|
|
3703
|
+
name: "search_columns",
|
|
3704
|
+
category: "search",
|
|
3705
|
+
searchHint: "search column SQL table field name type database schema find",
|
|
3706
|
+
description: "Search SQL columns across all tables by name (substring), type (int/string/float/...), or parent table. Returns column name, type, table, file, and line. Like search_symbols but scoped to SQL fields.",
|
|
3707
|
+
schema: {
|
|
3708
|
+
repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
|
|
3709
|
+
query: z.string().describe("Column name substring to match (case-insensitive). Empty = no name filter."),
|
|
3710
|
+
type: z.string().optional().describe("Filter by normalized type: int, string, float, bool, datetime, json, uuid, bytes"),
|
|
3711
|
+
table: z.string().optional().describe("Filter by table name substring"),
|
|
3712
|
+
file_pattern: z.string().optional().describe("Scope to files matching pattern"),
|
|
3713
|
+
max_results: zNum().describe("Max columns to return (default: 100)"),
|
|
3714
|
+
},
|
|
3715
|
+
handler: async (args) => {
|
|
3716
|
+
const { searchColumns } = await import("./tools/sql-tools.js");
|
|
3717
|
+
const opts = {
|
|
3718
|
+
query: args.query ?? "",
|
|
3719
|
+
};
|
|
3720
|
+
if (args.type != null)
|
|
3721
|
+
opts.type = args.type;
|
|
3722
|
+
if (args.table != null)
|
|
3723
|
+
opts.table = args.table;
|
|
3724
|
+
if (args.file_pattern != null)
|
|
3725
|
+
opts.file_pattern = args.file_pattern;
|
|
3726
|
+
if (args.max_results != null)
|
|
3727
|
+
opts.max_results = args.max_results;
|
|
3728
|
+
const result = await searchColumns(args.repo, opts);
|
|
3729
|
+
const parts = [];
|
|
3730
|
+
parts.push(`Columns: ${result.columns.length}${result.truncated ? `/${result.total} (truncated)` : ""}`);
|
|
3731
|
+
for (const c of result.columns) {
|
|
3732
|
+
parts.push(` ${c.table}.${c.name.padEnd(24)} ${c.normalized_type.padEnd(10)} ${c.file}:${c.line}`);
|
|
3733
|
+
}
|
|
3734
|
+
return parts.join("\n");
|
|
3735
|
+
},
|
|
3736
|
+
},
|
|
3737
|
+
{
|
|
3738
|
+
name: "analyze_schema_drift",
|
|
3739
|
+
category: "analysis",
|
|
3740
|
+
searchHint: "schema drift ORM Prisma Drizzle SQL mismatch migration type field comparison database",
|
|
3741
|
+
description: "Detect schema drift between ORM models (Prisma) and SQL schema. Flags fields in ORM not in SQL, SQL columns not in ORM, and type mismatches. Catches 'forgot to run migration' bugs before production.",
|
|
3742
|
+
schema: {
|
|
3743
|
+
repo: z.string().optional().describe("Repository identifier (default: auto-detected from CWD)"),
|
|
3744
|
+
file_pattern: z.string().optional().describe("Scope analysis to files matching pattern"),
|
|
3745
|
+
},
|
|
3746
|
+
handler: async (args) => {
|
|
3747
|
+
const { analyzeSchemaDrift } = await import("./tools/sql-tools.js");
|
|
3748
|
+
const opts = {};
|
|
3749
|
+
if (args.file_pattern != null)
|
|
3750
|
+
opts.file_pattern = args.file_pattern;
|
|
3751
|
+
const result = await analyzeSchemaDrift(args.repo, opts);
|
|
3752
|
+
const parts = [];
|
|
3753
|
+
parts.push(`Schema drift: ${result.summary.total} issue${result.summary.total === 1 ? "" : "s"}`);
|
|
3754
|
+
parts.push(` extra in ORM: ${result.summary.extra_in_orm}`);
|
|
3755
|
+
parts.push(` extra in SQL: ${result.summary.extra_in_sql}`);
|
|
3756
|
+
parts.push(` type mismatches: ${result.summary.type_mismatches}`);
|
|
3757
|
+
parts.push(` ORMs detected: ${result.orms_detected.join(", ") || "(none)"}`);
|
|
3758
|
+
if (result.warnings.length > 0) {
|
|
3759
|
+
parts.push("");
|
|
3760
|
+
for (const w of result.warnings)
|
|
3761
|
+
parts.push(`⚠ ${w}`);
|
|
3762
|
+
}
|
|
3763
|
+
if (result.drifts.length > 0) {
|
|
3764
|
+
parts.push("");
|
|
3765
|
+
parts.push("─── Drifts ───");
|
|
3766
|
+
for (const d of result.drifts.slice(0, 50)) {
|
|
3767
|
+
const loc = d.orm_file ? `${d.orm_file}:${d.orm_line}` : (d.sql_file ? `${d.sql_file}:${d.sql_line}` : "");
|
|
3768
|
+
parts.push(` [${d.kind}] ${loc}`);
|
|
3769
|
+
parts.push(` ${d.detail}`);
|
|
3770
|
+
}
|
|
3771
|
+
if (result.drifts.length > 50) {
|
|
3772
|
+
parts.push(` ... and ${result.drifts.length - 50} more`);
|
|
3773
|
+
}
|
|
3774
|
+
}
|
|
3775
|
+
return parts.join("\n");
|
|
3776
|
+
},
|
|
1362
3777
|
},
|
|
1363
3778
|
];
|
|
1364
3779
|
function buildToolSummaries() {
|
|
@@ -1465,6 +3880,21 @@ export function discoverTools(query, category) {
|
|
|
1465
3880
|
// ---------------------------------------------------------------------------
|
|
1466
3881
|
export function registerTools(server, options) {
|
|
1467
3882
|
const deferNonCore = options?.deferNonCore ?? false;
|
|
3883
|
+
const projectRoot = options?.projectRoot ?? process.cwd();
|
|
3884
|
+
// Detect which languages the project actually uses — drives language-gated
|
|
3885
|
+
// tool registration. Tools with requiresLanguage="python" are only surfaced
|
|
3886
|
+
// when .py files exist, same for PHP and Kotlin.
|
|
3887
|
+
let languages;
|
|
3888
|
+
try {
|
|
3889
|
+
languages = detectProjectLanguagesSync(projectRoot);
|
|
3890
|
+
}
|
|
3891
|
+
catch {
|
|
3892
|
+
// On failure, enable everything — conservative fallback
|
|
3893
|
+
languages = {
|
|
3894
|
+
python: true, php: true, typescript: true, javascript: true,
|
|
3895
|
+
kotlin: true, go: true, rust: true, ruby: true,
|
|
3896
|
+
};
|
|
3897
|
+
}
|
|
1468
3898
|
// Clear handles from any previous registration (e.g. tests calling registerTools multiple times)
|
|
1469
3899
|
toolHandles.clear();
|
|
1470
3900
|
// Register ALL tools with full schema; store returned handles
|
|
@@ -1472,6 +3902,18 @@ export function registerTools(server, options) {
|
|
|
1472
3902
|
const handle = server.tool(tool.name, tool.description, tool.schema, async (args) => wrapTool(tool.name, args, () => tool.handler(args))());
|
|
1473
3903
|
toolHandles.set(tool.name, handle);
|
|
1474
3904
|
}
|
|
3905
|
+
// Language-gated disabling — tools requiring a language absent from the
|
|
3906
|
+
// project are disabled (still registered but hidden from ListTools).
|
|
3907
|
+
// Users can re-enable via describe_tools(reveal=true) if needed.
|
|
3908
|
+
for (const tool of TOOL_DEFINITIONS) {
|
|
3909
|
+
if (!tool.requiresLanguage)
|
|
3910
|
+
continue;
|
|
3911
|
+
if (languages[tool.requiresLanguage])
|
|
3912
|
+
continue;
|
|
3913
|
+
const handle = toolHandles.get(tool.name);
|
|
3914
|
+
if (handle)
|
|
3915
|
+
handle.disable();
|
|
3916
|
+
}
|
|
1475
3917
|
// Always register discover_tools meta-tool
|
|
1476
3918
|
const discoverHandle = server.tool("discover_tools", "Search tool catalog by keyword or category. Returns matching tools with descriptions.", {
|
|
1477
3919
|
query: z.string().describe("Keywords to search for (e.g. 'dead code', 'complexity', 'rename', 'secrets')"),
|
|
@@ -1504,14 +3946,37 @@ export function registerTools(server, options) {
|
|
|
1504
3946
|
handle.disable();
|
|
1505
3947
|
}
|
|
1506
3948
|
}
|
|
3949
|
+
// Auto-enable framework-specific tools when project type is detected at CWD.
|
|
3950
|
+
// E.g. composer.json → enable PHP/Yii2 tools automatically.
|
|
3951
|
+
detectAutoLoadTools(process.cwd())
|
|
3952
|
+
.then((toEnable) => {
|
|
3953
|
+
for (const name of toEnable) {
|
|
3954
|
+
const h = toolHandles.get(name);
|
|
3955
|
+
if (h)
|
|
3956
|
+
h.enable();
|
|
3957
|
+
}
|
|
3958
|
+
if (toEnable.length > 0) {
|
|
3959
|
+
console.error(`[codesift] Auto-loaded ${toEnable.length} framework tools for detected project type: ${toEnable.join(", ")}`);
|
|
3960
|
+
}
|
|
3961
|
+
})
|
|
3962
|
+
.catch(() => {
|
|
3963
|
+
// Silently ignore — auto-detection is best-effort
|
|
3964
|
+
});
|
|
1507
3965
|
}
|
|
1508
3966
|
// Register progressive shorteners for analysis tools with large outputs
|
|
1509
3967
|
registerShortener("analyze_complexity", { compact: formatComplexityCompact, counts: formatComplexityCounts });
|
|
1510
3968
|
registerShortener("find_clones", { compact: formatClonesCompact, counts: formatClonesCounts });
|
|
1511
3969
|
registerShortener("analyze_hotspots", { compact: formatHotspotsCompact, counts: formatHotspotsCounts });
|
|
1512
3970
|
registerShortener("trace_route", { compact: formatTraceRouteCompact, counts: formatTraceRouteCounts });
|
|
3971
|
+
registerShortener("nextjs_route_map", { compact: formatNextjsRouteMapCompact, counts: formatNextjsRouteMapCounts });
|
|
3972
|
+
registerShortener("nextjs_metadata_audit", { compact: formatNextjsMetadataAuditCompact, counts: formatNextjsMetadataAuditCounts });
|
|
3973
|
+
registerShortener("framework_audit", { compact: formatFrameworkAuditCompact, counts: formatFrameworkAuditCounts });
|
|
3974
|
+
registerShortener("nextjs_audit_server_actions", { compact: formatServerActionsAuditCompact, counts: formatServerActionsAuditCounts });
|
|
3975
|
+
registerShortener("nextjs_api_contract", { compact: formatApiContractCompact, counts: formatApiContractCounts });
|
|
3976
|
+
registerShortener("nextjs_boundary_analyzer", { compact: formatBoundaryAnalyzerCompact, counts: formatBoundaryAnalyzerCounts });
|
|
1513
3977
|
registerShortener("get_session_context", {
|
|
1514
|
-
compact: (
|
|
3978
|
+
compact: (raw) => {
|
|
3979
|
+
const text = typeof raw === "string" ? raw : JSON.stringify(raw);
|
|
1515
3980
|
try {
|
|
1516
3981
|
const data = JSON.parse(text);
|
|
1517
3982
|
return `session:${data.session_id?.slice(0, 8)} calls:${data.call_count} files:${data.explored_files?.count} symbols:${data.explored_symbols?.count} queries:${data.queries?.count} neg:${data.negative_evidence?.count}`;
|
|
@@ -1520,7 +3985,8 @@ export function registerTools(server, options) {
|
|
|
1520
3985
|
return text.slice(0, 500);
|
|
1521
3986
|
}
|
|
1522
3987
|
},
|
|
1523
|
-
counts: (
|
|
3988
|
+
counts: (raw) => {
|
|
3989
|
+
const text = typeof raw === "string" ? raw : JSON.stringify(raw);
|
|
1524
3990
|
try {
|
|
1525
3991
|
const data = JSON.parse(text);
|
|
1526
3992
|
return `files:${data.explored_files?.count} symbols:${data.explored_symbols?.count} queries:${data.queries?.count} neg:${data.negative_evidence?.count}`;
|