codesift-mcp 0.3.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +215 -23
- package/dist/cache/hono-cache.d.ts +50 -0
- package/dist/cache/hono-cache.d.ts.map +1 -0
- package/dist/cache/hono-cache.js +132 -0
- package/dist/cache/hono-cache.js.map +1 -0
- package/dist/cli/help.d.ts.map +1 -1
- package/dist/cli/help.js +8 -6
- package/dist/cli/help.js.map +1 -1
- package/dist/cli/platform.d.ts.map +1 -1
- package/dist/cli/platform.js +12 -14
- package/dist/cli/platform.js.map +1 -1
- package/dist/cli/setup.d.ts +1 -1
- package/dist/cli/setup.d.ts.map +1 -1
- package/dist/cli/setup.js +27 -3
- package/dist/cli/setup.js.map +1 -1
- package/dist/formatters-shortening.d.ts +13 -0
- package/dist/formatters-shortening.d.ts.map +1 -1
- package/dist/formatters-shortening.js +131 -0
- package/dist/formatters-shortening.js.map +1 -1
- package/dist/formatters.d.ts +38 -0
- package/dist/formatters.d.ts.map +1 -1
- package/dist/formatters.js +521 -0
- package/dist/formatters.js.map +1 -1
- package/dist/instructions.d.ts +1 -1
- package/dist/instructions.d.ts.map +1 -1
- package/dist/instructions.js +39 -38
- package/dist/instructions.js.map +1 -1
- package/dist/lsp/lsp-servers.d.ts.map +1 -1
- package/dist/lsp/lsp-servers.js +5 -0
- package/dist/lsp/lsp-servers.js.map +1 -1
- package/dist/lsp/lsp-tools.d.ts.map +1 -1
- package/dist/lsp/lsp-tools.js +1 -0
- package/dist/lsp/lsp-tools.js.map +1 -1
- package/dist/parser/astro-template.d.ts +47 -0
- package/dist/parser/astro-template.d.ts.map +1 -0
- package/dist/parser/astro-template.js +171 -0
- package/dist/parser/astro-template.js.map +1 -0
- package/dist/parser/extractors/_shared.d.ts +4 -0
- package/dist/parser/extractors/_shared.d.ts.map +1 -1
- package/dist/parser/extractors/_shared.js +8 -0
- package/dist/parser/extractors/_shared.js.map +1 -1
- package/dist/parser/extractors/astro.d.ts +4 -5
- package/dist/parser/extractors/astro.d.ts.map +1 -1
- package/dist/parser/extractors/astro.js +102 -26
- package/dist/parser/extractors/astro.js.map +1 -1
- package/dist/parser/extractors/gradle-kts.d.ts +4 -0
- package/dist/parser/extractors/gradle-kts.d.ts.map +1 -0
- package/dist/parser/extractors/gradle-kts.js +246 -0
- package/dist/parser/extractors/gradle-kts.js.map +1 -0
- package/dist/parser/extractors/hono-inline-analyzer.d.ts +34 -0
- package/dist/parser/extractors/hono-inline-analyzer.d.ts.map +1 -0
- package/dist/parser/extractors/hono-inline-analyzer.js +465 -0
- package/dist/parser/extractors/hono-inline-analyzer.js.map +1 -0
- package/dist/parser/extractors/hono-model.d.ts +196 -0
- package/dist/parser/extractors/hono-model.d.ts.map +1 -0
- package/dist/parser/extractors/hono-model.js +10 -0
- package/dist/parser/extractors/hono-model.js.map +1 -0
- package/dist/parser/extractors/hono.d.ts +118 -0
- package/dist/parser/extractors/hono.d.ts.map +1 -0
- package/dist/parser/extractors/hono.js +1527 -0
- package/dist/parser/extractors/hono.js.map +1 -0
- package/dist/parser/extractors/kotlin.d.ts +4 -0
- package/dist/parser/extractors/kotlin.d.ts.map +1 -0
- package/dist/parser/extractors/kotlin.js +521 -0
- package/dist/parser/extractors/kotlin.js.map +1 -0
- package/dist/parser/extractors/php.d.ts +22 -0
- package/dist/parser/extractors/php.d.ts.map +1 -0
- package/dist/parser/extractors/php.js +334 -0
- package/dist/parser/extractors/php.js.map +1 -0
- package/dist/parser/extractors/python.d.ts.map +1 -1
- package/dist/parser/extractors/python.js +234 -11
- package/dist/parser/extractors/python.js.map +1 -1
- package/dist/parser/extractors/sql.d.ts +33 -0
- package/dist/parser/extractors/sql.d.ts.map +1 -0
- package/dist/parser/extractors/sql.js +506 -0
- package/dist/parser/extractors/sql.js.map +1 -0
- package/dist/parser/extractors/typescript.d.ts.map +1 -1
- package/dist/parser/extractors/typescript.js +209 -3
- package/dist/parser/extractors/typescript.js.map +1 -1
- package/dist/parser/languages/tree-sitter-javascript.wasm +0 -0
- package/dist/parser/languages/tree-sitter-kotlin.wasm +0 -0
- package/dist/parser/languages/tree-sitter-php.wasm +0 -0
- package/dist/parser/languages/tree-sitter-php_only.wasm +0 -0
- package/dist/parser/languages/tree-sitter-python.wasm +0 -0
- package/dist/parser/parse-cache.d.ts +39 -0
- package/dist/parser/parse-cache.d.ts.map +1 -0
- package/dist/parser/parse-cache.js +87 -0
- package/dist/parser/parse-cache.js.map +1 -0
- package/dist/parser/parser-manager.d.ts +32 -0
- package/dist/parser/parser-manager.d.ts.map +1 -1
- package/dist/parser/parser-manager.js +93 -3
- package/dist/parser/parser-manager.js.map +1 -1
- package/dist/parser/symbol-extractor.d.ts.map +1 -1
- package/dist/parser/symbol-extractor.js +16 -0
- package/dist/parser/symbol-extractor.js.map +1 -1
- package/dist/register-tools.d.ts +38 -2
- package/dist/register-tools.d.ts.map +1 -1
- package/dist/register-tools.js +2444 -195
- package/dist/register-tools.js.map +1 -1
- package/dist/search/reranker.js +1 -1
- package/dist/search/reranker.js.map +1 -1
- package/dist/search/tool-ranker.d.ts +90 -0
- package/dist/search/tool-ranker.d.ts.map +1 -0
- package/dist/search/tool-ranker.js +420 -0
- package/dist/search/tool-ranker.js.map +1 -0
- package/dist/server-helpers.d.ts.map +1 -1
- package/dist/server-helpers.js +11 -0
- package/dist/server-helpers.js.map +1 -1
- package/dist/server.js +47 -14
- package/dist/server.js.map +1 -1
- package/dist/storage/index-store.d.ts +15 -1
- package/dist/storage/index-store.d.ts.map +1 -1
- package/dist/storage/index-store.js +27 -1
- package/dist/storage/index-store.js.map +1 -1
- package/dist/storage/session-state.d.ts +1 -1
- package/dist/storage/session-state.d.ts.map +1 -1
- package/dist/storage/session-state.js +6 -4
- package/dist/storage/session-state.js.map +1 -1
- package/dist/storage/usage-tracker.d.ts.map +1 -1
- package/dist/storage/usage-tracker.js +4 -1
- package/dist/storage/usage-tracker.js.map +1 -1
- package/dist/tools/agent-config-tools.d.ts +24 -0
- package/dist/tools/agent-config-tools.d.ts.map +1 -0
- package/dist/tools/agent-config-tools.js +119 -0
- package/dist/tools/agent-config-tools.js.map +1 -0
- package/dist/tools/architecture-tools.d.ts +23 -0
- package/dist/tools/architecture-tools.d.ts.map +1 -0
- package/dist/tools/architecture-tools.js +140 -0
- package/dist/tools/architecture-tools.js.map +1 -0
- package/dist/tools/astro-actions.d.ts +54 -0
- package/dist/tools/astro-actions.d.ts.map +1 -0
- package/dist/tools/astro-actions.js +561 -0
- package/dist/tools/astro-actions.js.map +1 -0
- package/dist/tools/astro-audit.d.ts +87 -0
- package/dist/tools/astro-audit.d.ts.map +1 -0
- package/dist/tools/astro-audit.js +345 -0
- package/dist/tools/astro-audit.js.map +1 -0
- package/dist/tools/astro-config.d.ts +33 -0
- package/dist/tools/astro-config.d.ts.map +1 -0
- package/dist/tools/astro-config.js +260 -0
- package/dist/tools/astro-config.js.map +1 -0
- package/dist/tools/astro-content-collections.d.ts +44 -0
- package/dist/tools/astro-content-collections.d.ts.map +1 -0
- package/dist/tools/astro-content-collections.js +630 -0
- package/dist/tools/astro-content-collections.js.map +1 -0
- package/dist/tools/astro-islands.d.ts +63 -0
- package/dist/tools/astro-islands.d.ts.map +1 -0
- package/dist/tools/astro-islands.js +255 -0
- package/dist/tools/astro-islands.js.map +1 -0
- package/dist/tools/astro-migration.d.ts +31 -0
- package/dist/tools/astro-migration.d.ts.map +1 -0
- package/dist/tools/astro-migration.js +378 -0
- package/dist/tools/astro-migration.js.map +1 -0
- package/dist/tools/astro-routes.d.ts +49 -0
- package/dist/tools/astro-routes.d.ts.map +1 -0
- package/dist/tools/astro-routes.js +119 -0
- package/dist/tools/astro-routes.js.map +1 -0
- package/dist/tools/async-correctness.d.ts +26 -0
- package/dist/tools/async-correctness.d.ts.map +1 -0
- package/dist/tools/async-correctness.js +166 -0
- package/dist/tools/async-correctness.js.map +1 -0
- package/dist/tools/audit-tools.d.ts +38 -0
- package/dist/tools/audit-tools.d.ts.map +1 -0
- package/dist/tools/audit-tools.js +248 -0
- package/dist/tools/audit-tools.js.map +1 -0
- package/dist/tools/celery-tools.d.ts +38 -0
- package/dist/tools/celery-tools.d.ts.map +1 -0
- package/dist/tools/celery-tools.js +154 -0
- package/dist/tools/celery-tools.js.map +1 -0
- package/dist/tools/clone-tools.js +1 -1
- package/dist/tools/clone-tools.js.map +1 -1
- package/dist/tools/complexity-tools.d.ts +4 -0
- package/dist/tools/complexity-tools.d.ts.map +1 -1
- package/dist/tools/complexity-tools.js +78 -4
- package/dist/tools/complexity-tools.js.map +1 -1
- package/dist/tools/compose-tools.d.ts +60 -0
- package/dist/tools/compose-tools.d.ts.map +1 -0
- package/dist/tools/compose-tools.js +203 -0
- package/dist/tools/compose-tools.js.map +1 -0
- package/dist/tools/coupling-tools.d.ts +50 -0
- package/dist/tools/coupling-tools.d.ts.map +1 -0
- package/dist/tools/coupling-tools.js +262 -0
- package/dist/tools/coupling-tools.js.map +1 -0
- package/dist/tools/dependency-audit-tools.d.ts +65 -0
- package/dist/tools/dependency-audit-tools.d.ts.map +1 -0
- package/dist/tools/dependency-audit-tools.js +553 -0
- package/dist/tools/dependency-audit-tools.js.map +1 -0
- package/dist/tools/django-settings.d.ts +22 -0
- package/dist/tools/django-settings.d.ts.map +1 -0
- package/dist/tools/django-settings.js +301 -0
- package/dist/tools/django-settings.js.map +1 -0
- package/dist/tools/django-view-security-tools.d.ts +32 -0
- package/dist/tools/django-view-security-tools.d.ts.map +1 -0
- package/dist/tools/django-view-security-tools.js +184 -0
- package/dist/tools/django-view-security-tools.js.map +1 -0
- package/dist/tools/fastapi-depends.d.ts +63 -0
- package/dist/tools/fastapi-depends.d.ts.map +1 -0
- package/dist/tools/fastapi-depends.js +191 -0
- package/dist/tools/fastapi-depends.js.map +1 -0
- package/dist/tools/frequency-tools.js +1 -1
- package/dist/tools/frequency-tools.js.map +1 -1
- package/dist/tools/graph-tools.d.ts +8 -2
- package/dist/tools/graph-tools.d.ts.map +1 -1
- package/dist/tools/graph-tools.js +44 -3
- package/dist/tools/graph-tools.js.map +1 -1
- package/dist/tools/hilt-tools.d.ts +55 -0
- package/dist/tools/hilt-tools.d.ts.map +1 -0
- package/dist/tools/hilt-tools.js +258 -0
- package/dist/tools/hilt-tools.js.map +1 -0
- package/dist/tools/hono-analyze-app.d.ts +48 -0
- package/dist/tools/hono-analyze-app.d.ts.map +1 -0
- package/dist/tools/hono-analyze-app.js +94 -0
- package/dist/tools/hono-analyze-app.js.map +1 -0
- package/dist/tools/hono-api-contract.d.ts +22 -0
- package/dist/tools/hono-api-contract.d.ts.map +1 -0
- package/dist/tools/hono-api-contract.js +112 -0
- package/dist/tools/hono-api-contract.js.map +1 -0
- package/dist/tools/hono-conditional-middleware.d.ts +27 -0
- package/dist/tools/hono-conditional-middleware.d.ts.map +1 -0
- package/dist/tools/hono-conditional-middleware.js +62 -0
- package/dist/tools/hono-conditional-middleware.js.map +1 -0
- package/dist/tools/hono-context-flow.d.ts +24 -0
- package/dist/tools/hono-context-flow.d.ts.map +1 -0
- package/dist/tools/hono-context-flow.js +70 -0
- package/dist/tools/hono-context-flow.js.map +1 -0
- package/dist/tools/hono-dead-routes.d.ts +26 -0
- package/dist/tools/hono-dead-routes.d.ts.map +1 -0
- package/dist/tools/hono-dead-routes.js +102 -0
- package/dist/tools/hono-dead-routes.js.map +1 -0
- package/dist/tools/hono-entry-resolver.d.ts +27 -0
- package/dist/tools/hono-entry-resolver.d.ts.map +1 -0
- package/dist/tools/hono-entry-resolver.js +31 -0
- package/dist/tools/hono-entry-resolver.js.map +1 -0
- package/dist/tools/hono-env-regression.d.ts +29 -0
- package/dist/tools/hono-env-regression.d.ts.map +1 -0
- package/dist/tools/hono-env-regression.js +157 -0
- package/dist/tools/hono-env-regression.js.map +1 -0
- package/dist/tools/hono-inline-analyze.d.ts +31 -0
- package/dist/tools/hono-inline-analyze.d.ts.map +1 -0
- package/dist/tools/hono-inline-analyze.js +59 -0
- package/dist/tools/hono-inline-analyze.js.map +1 -0
- package/dist/tools/hono-middleware-chain.d.ts +40 -0
- package/dist/tools/hono-middleware-chain.d.ts.map +1 -0
- package/dist/tools/hono-middleware-chain.js +121 -0
- package/dist/tools/hono-middleware-chain.js.map +1 -0
- package/dist/tools/hono-modules.d.ts +22 -0
- package/dist/tools/hono-modules.d.ts.map +1 -0
- package/dist/tools/hono-modules.js +118 -0
- package/dist/tools/hono-modules.js.map +1 -0
- package/dist/tools/hono-response-types.d.ts +37 -0
- package/dist/tools/hono-response-types.d.ts.map +1 -0
- package/dist/tools/hono-response-types.js +76 -0
- package/dist/tools/hono-response-types.js.map +1 -0
- package/dist/tools/hono-rpc-types.d.ts +21 -0
- package/dist/tools/hono-rpc-types.d.ts.map +1 -0
- package/dist/tools/hono-rpc-types.js +49 -0
- package/dist/tools/hono-rpc-types.js.map +1 -0
- package/dist/tools/hono-security.d.ts +31 -0
- package/dist/tools/hono-security.d.ts.map +1 -0
- package/dist/tools/hono-security.js +269 -0
- package/dist/tools/hono-security.js.map +1 -0
- package/dist/tools/hono-visualize.d.ts +13 -0
- package/dist/tools/hono-visualize.d.ts.map +1 -0
- package/dist/tools/hono-visualize.js +64 -0
- package/dist/tools/hono-visualize.js.map +1 -0
- package/dist/tools/hotspot-tools.d.ts.map +1 -1
- package/dist/tools/hotspot-tools.js +9 -7
- package/dist/tools/hotspot-tools.js.map +1 -1
- package/dist/tools/index-tools.d.ts +17 -0
- package/dist/tools/index-tools.d.ts.map +1 -1
- package/dist/tools/index-tools.js +210 -10
- package/dist/tools/index-tools.js.map +1 -1
- package/dist/tools/kotlin-tools.d.ts +142 -0
- package/dist/tools/kotlin-tools.d.ts.map +1 -0
- package/dist/tools/kotlin-tools.js +572 -0
- package/dist/tools/kotlin-tools.js.map +1 -0
- package/dist/tools/legacy-hono-conventions.d.ts +14 -0
- package/dist/tools/legacy-hono-conventions.d.ts.map +1 -0
- package/dist/tools/legacy-hono-conventions.js +152 -0
- package/dist/tools/legacy-hono-conventions.js.map +1 -0
- package/dist/tools/migration-lint-tools.d.ts +26 -0
- package/dist/tools/migration-lint-tools.d.ts.map +1 -0
- package/dist/tools/migration-lint-tools.js +247 -0
- package/dist/tools/migration-lint-tools.js.map +1 -0
- package/dist/tools/model-tools.d.ts +30 -0
- package/dist/tools/model-tools.d.ts.map +1 -0
- package/dist/tools/model-tools.js +145 -0
- package/dist/tools/model-tools.js.map +1 -0
- package/dist/tools/nest-ext-tools.d.ts +207 -0
- package/dist/tools/nest-ext-tools.d.ts.map +1 -0
- package/dist/tools/nest-ext-tools.js +752 -0
- package/dist/tools/nest-ext-tools.js.map +1 -0
- package/dist/tools/nest-tools.d.ts +198 -0
- package/dist/tools/nest-tools.d.ts.map +1 -0
- package/dist/tools/nest-tools.js +1142 -0
- package/dist/tools/nest-tools.js.map +1 -0
- package/dist/tools/nextjs-api-contract-readers.d.ts +14 -0
- package/dist/tools/nextjs-api-contract-readers.d.ts.map +1 -0
- package/dist/tools/nextjs-api-contract-readers.js +204 -0
- package/dist/tools/nextjs-api-contract-readers.js.map +1 -0
- package/dist/tools/nextjs-api-contract-tools.d.ts +57 -0
- package/dist/tools/nextjs-api-contract-tools.d.ts.map +1 -0
- package/dist/tools/nextjs-api-contract-tools.js +144 -0
- package/dist/tools/nextjs-api-contract-tools.js.map +1 -0
- package/dist/tools/nextjs-boundary-tools.d.ts +39 -0
- package/dist/tools/nextjs-boundary-tools.d.ts.map +1 -0
- package/dist/tools/nextjs-boundary-tools.js +152 -0
- package/dist/tools/nextjs-boundary-tools.js.map +1 -0
- package/dist/tools/nextjs-component-readers.d.ts +101 -0
- package/dist/tools/nextjs-component-readers.d.ts.map +1 -0
- package/dist/tools/nextjs-component-readers.js +287 -0
- package/dist/tools/nextjs-component-readers.js.map +1 -0
- package/dist/tools/nextjs-component-tools.d.ts +51 -0
- package/dist/tools/nextjs-component-tools.d.ts.map +1 -0
- package/dist/tools/nextjs-component-tools.js +212 -0
- package/dist/tools/nextjs-component-tools.js.map +1 -0
- package/dist/tools/nextjs-data-flow-tools.d.ts +42 -0
- package/dist/tools/nextjs-data-flow-tools.d.ts.map +1 -0
- package/dist/tools/nextjs-data-flow-tools.js +158 -0
- package/dist/tools/nextjs-data-flow-tools.js.map +1 -0
- package/dist/tools/nextjs-framework-audit-tools.d.ts +60 -0
- package/dist/tools/nextjs-framework-audit-tools.d.ts.map +1 -0
- package/dist/tools/nextjs-framework-audit-tools.js +394 -0
- package/dist/tools/nextjs-framework-audit-tools.js.map +1 -0
- package/dist/tools/nextjs-link-tools.d.ts +41 -0
- package/dist/tools/nextjs-link-tools.d.ts.map +1 -0
- package/dist/tools/nextjs-link-tools.js +157 -0
- package/dist/tools/nextjs-link-tools.js.map +1 -0
- package/dist/tools/nextjs-metadata-tools.d.ts +74 -0
- package/dist/tools/nextjs-metadata-tools.d.ts.map +1 -0
- package/dist/tools/nextjs-metadata-tools.js +252 -0
- package/dist/tools/nextjs-metadata-tools.js.map +1 -0
- package/dist/tools/nextjs-middleware-coverage-tools.d.ts +41 -0
- package/dist/tools/nextjs-middleware-coverage-tools.d.ts.map +1 -0
- package/dist/tools/nextjs-middleware-coverage-tools.js +88 -0
- package/dist/tools/nextjs-middleware-coverage-tools.js.map +1 -0
- package/dist/tools/nextjs-route-readers.d.ts +81 -0
- package/dist/tools/nextjs-route-readers.d.ts.map +1 -0
- package/dist/tools/nextjs-route-readers.js +340 -0
- package/dist/tools/nextjs-route-readers.js.map +1 -0
- package/dist/tools/nextjs-route-tools.d.ts +36 -0
- package/dist/tools/nextjs-route-tools.d.ts.map +1 -0
- package/dist/tools/nextjs-route-tools.js +175 -0
- package/dist/tools/nextjs-route-tools.js.map +1 -0
- package/dist/tools/nextjs-security-readers.d.ts +22 -0
- package/dist/tools/nextjs-security-readers.d.ts.map +1 -0
- package/dist/tools/nextjs-security-readers.js +318 -0
- package/dist/tools/nextjs-security-readers.js.map +1 -0
- package/dist/tools/nextjs-security-scoring.d.ts +15 -0
- package/dist/tools/nextjs-security-scoring.d.ts.map +1 -0
- package/dist/tools/nextjs-security-scoring.js +65 -0
- package/dist/tools/nextjs-security-scoring.js.map +1 -0
- package/dist/tools/nextjs-security-tools.d.ts +75 -0
- package/dist/tools/nextjs-security-tools.d.ts.map +1 -0
- package/dist/tools/nextjs-security-tools.js +153 -0
- package/dist/tools/nextjs-security-tools.js.map +1 -0
- package/dist/tools/nextjs-tools.d.ts +15 -0
- package/dist/tools/nextjs-tools.d.ts.map +1 -0
- package/dist/tools/nextjs-tools.js +15 -0
- package/dist/tools/nextjs-tools.js.map +1 -0
- package/dist/tools/outline-tools.d.ts.map +1 -1
- package/dist/tools/outline-tools.js +20 -0
- package/dist/tools/outline-tools.js.map +1 -1
- package/dist/tools/pattern-tools.d.ts +8 -0
- package/dist/tools/pattern-tools.d.ts.map +1 -1
- package/dist/tools/pattern-tools.js +651 -3
- package/dist/tools/pattern-tools.js.map +1 -1
- package/dist/tools/perf-tools.d.ts +32 -0
- package/dist/tools/perf-tools.d.ts.map +1 -0
- package/dist/tools/perf-tools.js +227 -0
- package/dist/tools/perf-tools.js.map +1 -0
- package/dist/tools/php-tools.d.ts +185 -0
- package/dist/tools/php-tools.d.ts.map +1 -0
- package/dist/tools/php-tools.js +645 -0
- package/dist/tools/php-tools.js.map +1 -0
- package/dist/tools/plan-turn-tools.d.ts +89 -0
- package/dist/tools/plan-turn-tools.d.ts.map +1 -0
- package/dist/tools/plan-turn-tools.js +508 -0
- package/dist/tools/plan-turn-tools.js.map +1 -0
- package/dist/tools/prisma-schema-tools.d.ts +44 -0
- package/dist/tools/prisma-schema-tools.d.ts.map +1 -0
- package/dist/tools/prisma-schema-tools.js +358 -0
- package/dist/tools/prisma-schema-tools.js.map +1 -0
- package/dist/tools/project-tools.d.ts +116 -7
- package/dist/tools/project-tools.d.ts.map +1 -1
- package/dist/tools/project-tools.js +595 -218
- package/dist/tools/project-tools.js.map +1 -1
- package/dist/tools/pydantic-models.d.ts +46 -0
- package/dist/tools/pydantic-models.d.ts.map +1 -0
- package/dist/tools/pydantic-models.js +249 -0
- package/dist/tools/pydantic-models.js.map +1 -0
- package/dist/tools/pyproject-tools.d.ts +23 -0
- package/dist/tools/pyproject-tools.d.ts.map +1 -0
- package/dist/tools/pyproject-tools.js +133 -0
- package/dist/tools/pyproject-tools.js.map +1 -0
- package/dist/tools/pytest-tools.d.ts +20 -0
- package/dist/tools/pytest-tools.d.ts.map +1 -0
- package/dist/tools/pytest-tools.js +106 -0
- package/dist/tools/pytest-tools.js.map +1 -0
- package/dist/tools/python-audit.d.ts +40 -0
- package/dist/tools/python-audit.d.ts.map +1 -0
- package/dist/tools/python-audit.js +244 -0
- package/dist/tools/python-audit.js.map +1 -0
- package/dist/tools/python-callers.d.ts +28 -0
- package/dist/tools/python-callers.d.ts.map +1 -0
- package/dist/tools/python-callers.js +110 -0
- package/dist/tools/python-callers.js.map +1 -0
- package/dist/tools/python-circular-imports.d.ts +19 -0
- package/dist/tools/python-circular-imports.d.ts.map +1 -0
- package/dist/tools/python-circular-imports.js +126 -0
- package/dist/tools/python-circular-imports.js.map +1 -0
- package/dist/tools/python-constants-tools.d.ts +44 -0
- package/dist/tools/python-constants-tools.d.ts.map +1 -0
- package/dist/tools/python-constants-tools.js +525 -0
- package/dist/tools/python-constants-tools.js.map +1 -0
- package/dist/tools/python-deps-analyzer.d.ts +46 -0
- package/dist/tools/python-deps-analyzer.d.ts.map +1 -0
- package/dist/tools/python-deps-analyzer.js +227 -0
- package/dist/tools/python-deps-analyzer.js.map +1 -0
- package/dist/tools/query-tools.d.ts +23 -0
- package/dist/tools/query-tools.d.ts.map +1 -0
- package/dist/tools/query-tools.js +256 -0
- package/dist/tools/query-tools.js.map +1 -0
- package/dist/tools/react-tools.d.ts +263 -0
- package/dist/tools/react-tools.d.ts.map +1 -0
- package/dist/tools/react-tools.js +839 -0
- package/dist/tools/react-tools.js.map +1 -0
- package/dist/tools/report-tools.js +47 -0
- package/dist/tools/report-tools.js.map +1 -1
- package/dist/tools/review-diff-tools.d.ts +5 -4
- package/dist/tools/review-diff-tools.d.ts.map +1 -1
- package/dist/tools/review-diff-tools.js +157 -66
- package/dist/tools/review-diff-tools.js.map +1 -1
- package/dist/tools/room-tools.d.ts +36 -0
- package/dist/tools/room-tools.d.ts.map +1 -0
- package/dist/tools/room-tools.js +147 -0
- package/dist/tools/room-tools.js.map +1 -0
- package/dist/tools/route-tools.d.ts +27 -1
- package/dist/tools/route-tools.d.ts.map +1 -1
- package/dist/tools/route-tools.js +744 -18
- package/dist/tools/route-tools.js.map +1 -1
- package/dist/tools/ruff-tools.d.ts +32 -0
- package/dist/tools/ruff-tools.d.ts.map +1 -0
- package/dist/tools/ruff-tools.js +114 -0
- package/dist/tools/ruff-tools.js.map +1 -0
- package/dist/tools/search-ranker.d.ts.map +1 -1
- package/dist/tools/search-ranker.js +7 -0
- package/dist/tools/search-ranker.js.map +1 -1
- package/dist/tools/search-tools.d.ts +3 -2
- package/dist/tools/search-tools.d.ts.map +1 -1
- package/dist/tools/search-tools.js +16 -3
- package/dist/tools/search-tools.js.map +1 -1
- package/dist/tools/serialization-tools.d.ts +24 -0
- package/dist/tools/serialization-tools.d.ts.map +1 -0
- package/dist/tools/serialization-tools.js +156 -0
- package/dist/tools/serialization-tools.js.map +1 -0
- package/dist/tools/sql-tools.d.ts +274 -0
- package/dist/tools/sql-tools.d.ts.map +1 -0
- package/dist/tools/sql-tools.js +1160 -0
- package/dist/tools/sql-tools.js.map +1 -0
- package/dist/tools/status-tools.d.ts +10 -0
- package/dist/tools/status-tools.d.ts.map +1 -0
- package/dist/tools/status-tools.js +32 -0
- package/dist/tools/status-tools.js.map +1 -0
- package/dist/tools/symbol-tools.d.ts +19 -0
- package/dist/tools/symbol-tools.d.ts.map +1 -1
- package/dist/tools/symbol-tools.js +75 -4
- package/dist/tools/symbol-tools.js.map +1 -1
- package/dist/tools/taint-tools.d.ts +43 -0
- package/dist/tools/taint-tools.d.ts.map +1 -0
- package/dist/tools/taint-tools.js +922 -0
- package/dist/tools/taint-tools.js.map +1 -0
- package/dist/tools/test-impact-tools.d.ts +29 -0
- package/dist/tools/test-impact-tools.d.ts.map +1 -0
- package/dist/tools/test-impact-tools.js +156 -0
- package/dist/tools/test-impact-tools.js.map +1 -0
- package/dist/tools/typecheck-tools.d.ts +39 -0
- package/dist/tools/typecheck-tools.d.ts.map +1 -0
- package/dist/tools/typecheck-tools.js +191 -0
- package/dist/tools/typecheck-tools.js.map +1 -0
- package/dist/tools/wiring-tools.d.ts +19 -0
- package/dist/tools/wiring-tools.d.ts.map +1 -0
- package/dist/tools/wiring-tools.js +147 -0
- package/dist/tools/wiring-tools.js.map +1 -0
- package/dist/types.d.ts +9 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/framework-detect.d.ts +18 -2
- package/dist/utils/framework-detect.d.ts.map +1 -1
- package/dist/utils/framework-detect.js +150 -3
- package/dist/utils/framework-detect.js.map +1 -1
- package/dist/utils/import-graph.d.ts +42 -0
- package/dist/utils/import-graph.d.ts.map +1 -1
- package/dist/utils/import-graph.js +248 -9
- package/dist/utils/import-graph.js.map +1 -1
- package/dist/utils/language-detect.d.ts +21 -0
- package/dist/utils/language-detect.d.ts.map +1 -0
- package/dist/utils/language-detect.js +183 -0
- package/dist/utils/language-detect.js.map +1 -0
- package/dist/utils/nextjs-ast-readers.d.ts +44 -0
- package/dist/utils/nextjs-ast-readers.d.ts.map +1 -0
- package/dist/utils/nextjs-ast-readers.js +341 -0
- package/dist/utils/nextjs-ast-readers.js.map +1 -0
- package/dist/utils/nextjs-audit-cache.d.ts +51 -0
- package/dist/utils/nextjs-audit-cache.d.ts.map +1 -0
- package/dist/utils/nextjs-audit-cache.js +116 -0
- package/dist/utils/nextjs-audit-cache.js.map +1 -0
- package/dist/utils/nextjs-metadata-readers.d.ts +65 -0
- package/dist/utils/nextjs-metadata-readers.d.ts.map +1 -0
- package/dist/utils/nextjs-metadata-readers.js +447 -0
- package/dist/utils/nextjs-metadata-readers.js.map +1 -0
- package/dist/utils/nextjs.d.ts +42 -0
- package/dist/utils/nextjs.d.ts.map +1 -0
- package/dist/utils/nextjs.js +284 -0
- package/dist/utils/nextjs.js.map +1 -0
- package/dist/utils/python-import-resolver.d.ts +42 -0
- package/dist/utils/python-import-resolver.d.ts.map +1 -0
- package/dist/utils/python-import-resolver.js +101 -0
- package/dist/utils/python-import-resolver.js.map +1 -0
- package/dist/utils/python-imports.d.ts +28 -0
- package/dist/utils/python-imports.d.ts.map +1 -0
- package/dist/utils/python-imports.js +117 -0
- package/dist/utils/python-imports.js.map +1 -0
- package/dist/utils/react-alias.d.ts +15 -0
- package/dist/utils/react-alias.d.ts.map +1 -0
- package/dist/utils/react-alias.js +31 -0
- package/dist/utils/react-alias.js.map +1 -0
- package/dist/utils/test-file.d.ts.map +1 -1
- package/dist/utils/test-file.js +7 -0
- package/dist/utils/test-file.js.map +1 -1
- package/dist/utils/walk.d.ts +22 -0
- package/dist/utils/walk.d.ts.map +1 -1
- package/dist/utils/walk.js +70 -2
- package/dist/utils/walk.js.map +1 -1
- package/package.json +4 -3
- package/rules/codesift.md +71 -5
- package/rules/codesift.mdc +71 -5
- package/rules/codex.md +71 -5
- package/rules/gemini.md +71 -5
- package/src/parser/languages/tree-sitter-javascript.wasm +0 -0
- package/src/parser/languages/tree-sitter-kotlin.wasm +0 -0
- package/src/parser/languages/tree-sitter-php.wasm +0 -0
- package/src/parser/languages/tree-sitter-php_only.wasm +0 -0
- package/src/parser/languages/tree-sitter-python.wasm +0 -0
|
@@ -0,0 +1,645 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PHP/Yii2-specific code intelligence tools.
|
|
3
|
+
*
|
|
4
|
+
* Provides 9 hidden/discoverable tools that augment generic code intelligence
|
|
5
|
+
* with PHP framework awareness: PSR-4 namespace resolution, ActiveRecord schema
|
|
6
|
+
* extraction, event/listener tracing, view mapping, service locator resolution,
|
|
7
|
+
* security scanning, compound project audit (9-gate), N+1 query detection,
|
|
8
|
+
* and god-model detection.
|
|
9
|
+
*/
|
|
10
|
+
import { readFile } from "node:fs/promises";
|
|
11
|
+
import { join } from "node:path";
|
|
12
|
+
import { getCodeIndex } from "./index-tools.js";
|
|
13
|
+
import { searchPatterns } from "./pattern-tools.js";
|
|
14
|
+
export async function resolvePhpNamespace(repo, className) {
|
|
15
|
+
const index = await getCodeIndex(repo);
|
|
16
|
+
if (!index)
|
|
17
|
+
throw new Error(`Repository "${repo}" not found.`);
|
|
18
|
+
const composer = await readJsonSafe(join(index.root, "composer.json"));
|
|
19
|
+
const psr4 = {
|
|
20
|
+
...(composer?.autoload?.["psr-4"] ?? {}),
|
|
21
|
+
...(composer?.["autoload-dev"]?.["psr-4"] ?? {}),
|
|
22
|
+
};
|
|
23
|
+
// Strip leading backslash
|
|
24
|
+
const normalized = className.replace(/^\\/, "");
|
|
25
|
+
const parts = normalized.split("\\");
|
|
26
|
+
const namespaceOnly = parts.slice(0, -1).join("\\");
|
|
27
|
+
const shortName = parts[parts.length - 1];
|
|
28
|
+
// Find matching PSR-4 prefix (longest match wins)
|
|
29
|
+
let bestPrefix = null;
|
|
30
|
+
let bestRoot = null;
|
|
31
|
+
for (const [prefix, roots] of Object.entries(psr4)) {
|
|
32
|
+
const normalizedPrefix = prefix.replace(/\\$/, "");
|
|
33
|
+
if (normalized.startsWith(normalizedPrefix + "\\") || normalized === normalizedPrefix) {
|
|
34
|
+
if (!bestPrefix || normalizedPrefix.length > bestPrefix.length) {
|
|
35
|
+
bestPrefix = normalizedPrefix;
|
|
36
|
+
bestRoot = Array.isArray(roots) ? roots[0] ?? null : roots;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
if (!bestPrefix || !bestRoot) {
|
|
41
|
+
return {
|
|
42
|
+
class_name: shortName,
|
|
43
|
+
namespace: namespaceOnly,
|
|
44
|
+
file_path: null,
|
|
45
|
+
exists: false,
|
|
46
|
+
psr4_root: null,
|
|
47
|
+
psr4_prefix: null,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
// Construct file path: strip prefix, replace \ with /, append .php
|
|
51
|
+
const remainder = normalized.slice(bestPrefix.length).replace(/^\\/, "");
|
|
52
|
+
const relativePath = remainder.replace(/\\/g, "/") + ".php";
|
|
53
|
+
const root = bestRoot.replace(/\/$/, "");
|
|
54
|
+
const filePath = root + "/" + relativePath;
|
|
55
|
+
// Check if file exists in index (strip leading ./ for comparison)
|
|
56
|
+
const normalizedFP = filePath.replace(/^\.\//, "");
|
|
57
|
+
const exists = index.files.some((f) => f.path === normalizedFP || f.path === filePath);
|
|
58
|
+
return {
|
|
59
|
+
class_name: shortName,
|
|
60
|
+
namespace: namespaceOnly,
|
|
61
|
+
file_path: filePath,
|
|
62
|
+
exists,
|
|
63
|
+
psr4_root: bestRoot,
|
|
64
|
+
psr4_prefix: bestPrefix,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
export async function analyzeActiveRecord(repo, options) {
|
|
68
|
+
const index = await getCodeIndex(repo);
|
|
69
|
+
if (!index)
|
|
70
|
+
throw new Error(`Repository "${repo}" not found.`);
|
|
71
|
+
// Find PHP class symbols in model files
|
|
72
|
+
const classSymbols = index.symbols.filter((s) => {
|
|
73
|
+
if (s.kind !== "class")
|
|
74
|
+
return false;
|
|
75
|
+
if (!s.file.endsWith(".php"))
|
|
76
|
+
return false;
|
|
77
|
+
if (options?.model_name && s.name !== options.model_name)
|
|
78
|
+
return false;
|
|
79
|
+
if (options?.file_pattern && !s.file.includes(options.file_pattern))
|
|
80
|
+
return false;
|
|
81
|
+
return true;
|
|
82
|
+
});
|
|
83
|
+
const models = [];
|
|
84
|
+
for (const cls of classSymbols) {
|
|
85
|
+
// Heuristic: only models that have source containing ActiveRecord or extend Model
|
|
86
|
+
if (!cls.source)
|
|
87
|
+
continue;
|
|
88
|
+
const extendsAR = /extends\s+(?:ActiveRecord|Model|\\yii\\db\\ActiveRecord)/.test(cls.source);
|
|
89
|
+
if (!extendsAR)
|
|
90
|
+
continue;
|
|
91
|
+
const model = {
|
|
92
|
+
name: cls.name,
|
|
93
|
+
file: cls.file,
|
|
94
|
+
table_name: null,
|
|
95
|
+
relations: [],
|
|
96
|
+
rules: [],
|
|
97
|
+
behaviors: [],
|
|
98
|
+
methods: [],
|
|
99
|
+
};
|
|
100
|
+
// Extract tableName() return value
|
|
101
|
+
const tableMatch = /function\s+tableName\s*\([^)]*\)[^{]*\{[^}]*return\s+['"]([^'"]+)['"]/s.exec(cls.source);
|
|
102
|
+
if (tableMatch)
|
|
103
|
+
model.table_name = tableMatch[1];
|
|
104
|
+
// Find child method symbols
|
|
105
|
+
const methods = index.symbols.filter((s) => s.parent === cls.id && s.kind === "method");
|
|
106
|
+
model.methods = methods.map((m) => m.name);
|
|
107
|
+
// Extract relations from getX() methods that return hasOne/hasMany.
|
|
108
|
+
// Two-pass detection:
|
|
109
|
+
// Pass 1: find the primary `->hasOne(Target::class, ...)` or
|
|
110
|
+
// `->hasMany(Target::class, ...)` call.
|
|
111
|
+
// Pass 2: scan the rest of the source for modifiers:
|
|
112
|
+
// ->via('relation') (Yii2 2.0.13+ junction table via relation)
|
|
113
|
+
// ->viaTable('tbl', [...]) (direct junction table)
|
|
114
|
+
// ->inverseOf('relation') (bidirectional relation)
|
|
115
|
+
// The presence of `via` or `viaTable` upgrades the relation type
|
|
116
|
+
// to `manyMany`. `inverseOf` is decorative and doesn't change type.
|
|
117
|
+
for (const m of methods) {
|
|
118
|
+
if (!m.name.startsWith("get") || !m.source)
|
|
119
|
+
continue;
|
|
120
|
+
const relName = m.name.slice(3);
|
|
121
|
+
const primaryRe = /->(hasOne|hasMany)\s*\(\s*([\w\\]+)(?:::class)?/;
|
|
122
|
+
const primaryMatch = primaryRe.exec(m.source);
|
|
123
|
+
if (!primaryMatch)
|
|
124
|
+
continue;
|
|
125
|
+
const baseType = primaryMatch[1] === "hasOne" ? "hasOne" : "hasMany";
|
|
126
|
+
const targetClass = primaryMatch[2];
|
|
127
|
+
// Scan the method source for junction-table modifiers on the same chain.
|
|
128
|
+
// If found, the semantic type is manyMany even though the primary call was hasMany.
|
|
129
|
+
const hasJunction = /->(?:via|viaTable)\s*\(/.test(m.source);
|
|
130
|
+
const type = hasJunction ? "manyMany" : baseType;
|
|
131
|
+
model.relations.push({
|
|
132
|
+
name: relName.charAt(0).toLowerCase() + relName.slice(1),
|
|
133
|
+
type,
|
|
134
|
+
target_class: targetClass,
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
// Extract rule validators (loose regex on rules() method source)
|
|
138
|
+
const rulesMethod = methods.find((m) => m.name === "rules");
|
|
139
|
+
if (rulesMethod?.source) {
|
|
140
|
+
const ruleMatches = rulesMethod.source.matchAll(/\[\s*\[?['"]?[\w,\s'"]+['"]?\]?\s*,\s*['"]([\w]+)['"]/g);
|
|
141
|
+
for (const rm of ruleMatches) {
|
|
142
|
+
if (rm[1] && !model.rules.includes(rm[1]))
|
|
143
|
+
model.rules.push(rm[1]);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
// Extract behaviors from behaviors() method
|
|
147
|
+
const behaviorsMethod = methods.find((m) => m.name === "behaviors");
|
|
148
|
+
if (behaviorsMethod?.source) {
|
|
149
|
+
const bMatches = behaviorsMethod.source.matchAll(/([A-Z]\w+Behavior)(?:::class)?/g);
|
|
150
|
+
for (const bm of bMatches) {
|
|
151
|
+
if (bm[1] && !model.behaviors.includes(bm[1]))
|
|
152
|
+
model.behaviors.push(bm[1]);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
models.push(model);
|
|
156
|
+
}
|
|
157
|
+
return { models, total: models.length };
|
|
158
|
+
}
|
|
159
|
+
export async function tracePhpEvent(repo, options) {
|
|
160
|
+
const index = await getCodeIndex(repo);
|
|
161
|
+
if (!index)
|
|
162
|
+
throw new Error(`Repository "${repo}" not found.`);
|
|
163
|
+
const eventMap = new Map();
|
|
164
|
+
const getOrCreate = (name) => {
|
|
165
|
+
let e = eventMap.get(name);
|
|
166
|
+
if (!e) {
|
|
167
|
+
e = { event_name: name, triggers: [], listeners: [] };
|
|
168
|
+
eventMap.set(name, e);
|
|
169
|
+
}
|
|
170
|
+
return e;
|
|
171
|
+
};
|
|
172
|
+
// Scan PHP file symbols for event triggers and listeners
|
|
173
|
+
const phpSymbols = index.symbols.filter((s) => s.file.endsWith(".php") && s.source);
|
|
174
|
+
for (const sym of phpSymbols) {
|
|
175
|
+
const source = sym.source;
|
|
176
|
+
// Triggers: ->trigger('eventName') or Event::trigger(...)
|
|
177
|
+
const triggerRe = /->trigger\s*\(\s*['"]([^'"]+)['"]/g;
|
|
178
|
+
let match;
|
|
179
|
+
while ((match = triggerRe.exec(source)) !== null) {
|
|
180
|
+
const eventName = match[1];
|
|
181
|
+
if (options?.event_name && eventName !== options.event_name)
|
|
182
|
+
continue;
|
|
183
|
+
const line = sym.start_line + (source.slice(0, match.index).match(/\n/g)?.length ?? 0);
|
|
184
|
+
getOrCreate(eventName).triggers.push({
|
|
185
|
+
file: sym.file,
|
|
186
|
+
line,
|
|
187
|
+
context: extractLineContext(source, match.index),
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
// Listeners: ->on('eventName', ...) or Event::on(...)
|
|
191
|
+
const listenerRe = /(?:->|::)on\s*\(\s*['"]([^'"]+)['"]/g;
|
|
192
|
+
while ((match = listenerRe.exec(source)) !== null) {
|
|
193
|
+
const eventName = match[1];
|
|
194
|
+
if (options?.event_name && eventName !== options.event_name)
|
|
195
|
+
continue;
|
|
196
|
+
const line = sym.start_line + (source.slice(0, match.index).match(/\n/g)?.length ?? 0);
|
|
197
|
+
getOrCreate(eventName).listeners.push({
|
|
198
|
+
file: sym.file,
|
|
199
|
+
line,
|
|
200
|
+
context: extractLineContext(source, match.index),
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
const events = [...eventMap.values()];
|
|
205
|
+
return { events, total: events.length };
|
|
206
|
+
}
|
|
207
|
+
export async function findPhpViews(repo, options) {
|
|
208
|
+
const index = await getCodeIndex(repo);
|
|
209
|
+
if (!index)
|
|
210
|
+
throw new Error(`Repository "${repo}" not found.`);
|
|
211
|
+
const mappings = [];
|
|
212
|
+
// Find action methods in controllers
|
|
213
|
+
const controllers = index.symbols.filter((s) => s.kind === "class" && s.name.endsWith("Controller") && s.file.endsWith(".php"));
|
|
214
|
+
for (const ctrl of controllers) {
|
|
215
|
+
if (options?.controller && !ctrl.name.includes(options.controller))
|
|
216
|
+
continue;
|
|
217
|
+
const actions = index.symbols.filter((s) => s.parent === ctrl.id && s.kind === "method" && s.name.startsWith("action"));
|
|
218
|
+
for (const action of actions) {
|
|
219
|
+
if (!action.source)
|
|
220
|
+
continue;
|
|
221
|
+
// Match $this->render('viewName'), renderPartial('...'), renderAjax('...')
|
|
222
|
+
const renderRe = /\$this->render(?:Partial|Ajax|AsJson)?\s*\(\s*['"]([^'"]+)['"]/g;
|
|
223
|
+
let match;
|
|
224
|
+
while ((match = renderRe.exec(action.source)) !== null) {
|
|
225
|
+
const viewName = match[1];
|
|
226
|
+
// Yii2 convention: views/{controller-id}/{view}.php
|
|
227
|
+
const controllerId = pascalToKebab(ctrl.name.replace(/Controller$/, ""));
|
|
228
|
+
const viewFile = `views/${controllerId}/${viewName}.php`;
|
|
229
|
+
const exists = index.files.some((f) => f.path === viewFile || f.path.endsWith("/" + viewFile));
|
|
230
|
+
const line = action.start_line + (action.source.slice(0, match.index).match(/\n/g)?.length ?? 0);
|
|
231
|
+
mappings.push({
|
|
232
|
+
controller: ctrl.name,
|
|
233
|
+
action: action.name,
|
|
234
|
+
view_name: viewName,
|
|
235
|
+
view_file: exists ? viewFile : null,
|
|
236
|
+
render_line: line,
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
return { mappings, total: mappings.length };
|
|
242
|
+
}
|
|
243
|
+
export async function resolvePhpService(repo, options) {
|
|
244
|
+
const index = await getCodeIndex(repo);
|
|
245
|
+
if (!index)
|
|
246
|
+
throw new Error(`Repository "${repo}" not found.`);
|
|
247
|
+
const services = [];
|
|
248
|
+
const configFiles = index.files.filter((f) => /config\/(web|console|main|db)\.php$/.test(f.path));
|
|
249
|
+
for (const cf of configFiles) {
|
|
250
|
+
let source;
|
|
251
|
+
try {
|
|
252
|
+
source = await readFile(join(index.root, cf.path), "utf-8");
|
|
253
|
+
}
|
|
254
|
+
catch {
|
|
255
|
+
continue;
|
|
256
|
+
}
|
|
257
|
+
// Match component definitions: 'componentName' => ['class' => 'FQCN', ...]
|
|
258
|
+
const componentRe = /['"]([\w-]+)['"]\s*=>\s*\[\s*['"]class['"]\s*=>\s*['"]([\w\\]+)['"]/g;
|
|
259
|
+
let match;
|
|
260
|
+
while ((match = componentRe.exec(source)) !== null) {
|
|
261
|
+
const name = match[1];
|
|
262
|
+
const cls = match[2];
|
|
263
|
+
if (options?.service_name && name !== options.service_name)
|
|
264
|
+
continue;
|
|
265
|
+
// Resolve class to file via PSR-4
|
|
266
|
+
let filePath = null;
|
|
267
|
+
try {
|
|
268
|
+
const resolved = await resolvePhpNamespace(repo, cls);
|
|
269
|
+
if (resolved.exists)
|
|
270
|
+
filePath = resolved.file_path;
|
|
271
|
+
}
|
|
272
|
+
catch { /* ignore */ }
|
|
273
|
+
services.push({
|
|
274
|
+
name,
|
|
275
|
+
class: cls,
|
|
276
|
+
file: filePath,
|
|
277
|
+
config_file: cf.path,
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
return { services, total: services.length };
|
|
282
|
+
}
|
|
283
|
+
const PHP_SECURITY_CHECKS = [
|
|
284
|
+
{ pattern: "sql-injection-php", severity: "critical" },
|
|
285
|
+
{ pattern: "xss-php", severity: "critical" },
|
|
286
|
+
{ pattern: "eval-php", severity: "critical" },
|
|
287
|
+
{ pattern: "exec-php", severity: "critical" },
|
|
288
|
+
{ pattern: "unserialize-php", severity: "high" },
|
|
289
|
+
{ pattern: "file-include-var", severity: "high" },
|
|
290
|
+
{ pattern: "unescaped-yii-view", severity: "high" },
|
|
291
|
+
{ pattern: "raw-query-yii", severity: "high" },
|
|
292
|
+
];
|
|
293
|
+
export async function phpSecurityScan(repo, options) {
|
|
294
|
+
const selectedChecks = options?.checks
|
|
295
|
+
? PHP_SECURITY_CHECKS.filter((c) => options.checks.includes(c.pattern))
|
|
296
|
+
: PHP_SECURITY_CHECKS;
|
|
297
|
+
const findings = [];
|
|
298
|
+
const summary = { critical: 0, high: 0, medium: 0, low: 0, total: 0 };
|
|
299
|
+
// Run pattern checks in parallel
|
|
300
|
+
const results = await Promise.all(selectedChecks.map((check) => searchPatterns(repo, check.pattern, {
|
|
301
|
+
file_pattern: options?.file_pattern ?? ".php",
|
|
302
|
+
include_tests: false,
|
|
303
|
+
}).then((r) => ({ check, result: r })).catch(() => null)));
|
|
304
|
+
for (const res of results) {
|
|
305
|
+
if (!res)
|
|
306
|
+
continue;
|
|
307
|
+
for (const m of res.result.matches) {
|
|
308
|
+
findings.push({
|
|
309
|
+
severity: res.check.severity,
|
|
310
|
+
pattern: res.check.pattern,
|
|
311
|
+
file: m.file,
|
|
312
|
+
line: m.start_line,
|
|
313
|
+
context: m.context,
|
|
314
|
+
description: "", // description populated by searchPatterns but not in PatternMatch type
|
|
315
|
+
});
|
|
316
|
+
summary[res.check.severity]++;
|
|
317
|
+
summary.total++;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
return {
|
|
321
|
+
findings,
|
|
322
|
+
summary,
|
|
323
|
+
checks_run: selectedChecks.map((c) => c.pattern),
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
// ---------------------------------------------------------------------------
|
|
327
|
+
// 7h. find_php_n_plus_one — detect foreach + relation access without ->with()
|
|
328
|
+
// ---------------------------------------------------------------------------
|
|
329
|
+
/**
|
|
330
|
+
* Common ActiveRecord scalar field names. Property access like $user->id or
|
|
331
|
+
* $user->created_at inside a foreach is NOT a relation (no N+1 risk), so we
|
|
332
|
+
* allow-list these to cut false positives.
|
|
333
|
+
*/
|
|
334
|
+
const SCALAR_FIELD_NAMES = new Set([
|
|
335
|
+
"id", "name", "title", "created_at", "updated_at", "deleted_at", "status",
|
|
336
|
+
"email", "slug", "code", "type", "value", "label", "description", "enabled",
|
|
337
|
+
"active", "position", "sort", "order", "count", "total", "amount", "price",
|
|
338
|
+
"uuid", "hash", "token", "key", "url", "path", "image", "avatar",
|
|
339
|
+
]);
|
|
340
|
+
/**
|
|
341
|
+
* PHP method names that look like `get*` but are NOT ActiveRecord relation
|
|
342
|
+
* getters. `$item->save()` / `$item->validate()` inside a foreach is fine;
|
|
343
|
+
* flagging them as N+1 would be a false positive. These names are stripped
|
|
344
|
+
* from the `get\w+()` method-call detection before the eager-load check.
|
|
345
|
+
*/
|
|
346
|
+
const METHOD_CALL_BLOCKLIST = new Set([
|
|
347
|
+
"save", "validate", "delete", "refresh", "load", "populate", "toArray",
|
|
348
|
+
"afterSave", "beforeSave", "beforeDelete", "afterDelete",
|
|
349
|
+
"getAttributes", "getAttribute", "getIsNewRecord", "getErrors", "getFirstError",
|
|
350
|
+
"getOldAttributes", "getDirtyAttributes", "getPrimaryKey", "getTableSchema",
|
|
351
|
+
]);
|
|
352
|
+
/**
|
|
353
|
+
* Detect N+1 query patterns in Yii2/Eloquent controllers.
|
|
354
|
+
*
|
|
355
|
+
* Pattern: `foreach ($items as $item) { $item->relation->... }` without a
|
|
356
|
+
* prior `->with('relation')` call in the same method scope. This is the
|
|
357
|
+
* most common N+1 anti-pattern in Yii2 ActiveRecord code.
|
|
358
|
+
*
|
|
359
|
+
* Known limitations (acceptable for a "discovery" tool, not a gate):
|
|
360
|
+
* - Regex-based — can miss multi-line foreach bodies split across nested blocks
|
|
361
|
+
* - Doesn't cross function boundaries — eager loading in caller is invisible
|
|
362
|
+
* - False positives on nested loops if the outer collection is already eager-loaded
|
|
363
|
+
*/
|
|
364
|
+
export async function findPhpNPlusOne(repo, options) {
|
|
365
|
+
const index = await getCodeIndex(repo);
|
|
366
|
+
if (!index)
|
|
367
|
+
throw new Error(`Repository "${repo}" not found.`);
|
|
368
|
+
const findings = [];
|
|
369
|
+
const limit = options?.limit ?? 100;
|
|
370
|
+
const filePattern = options?.file_pattern;
|
|
371
|
+
// Normalize `getProfile` → `profile` so the ->with() check matches whether
|
|
372
|
+
// the relation is accessed as a property or via its auto-generated getter.
|
|
373
|
+
const normalizeGetter = (name) => {
|
|
374
|
+
const bare = name.replace(/^get/, "");
|
|
375
|
+
return bare.length > 0 ? bare.charAt(0).toLowerCase() + bare.slice(1) : "";
|
|
376
|
+
};
|
|
377
|
+
// A finding is emitted exactly once per (foreach × relation-name) tuple so
|
|
378
|
+
// that chained patterns don't double-report the same relation that the
|
|
379
|
+
// property pattern already caught in the same loop body.
|
|
380
|
+
const emitFinding = (sym, foreachIdx, relation, pattern, seen) => {
|
|
381
|
+
if (!relation || seen.has(relation))
|
|
382
|
+
return findings.length >= limit;
|
|
383
|
+
seen.add(relation);
|
|
384
|
+
if (SCALAR_FIELD_NAMES.has(relation.toLowerCase()))
|
|
385
|
+
return findings.length >= limit;
|
|
386
|
+
const beforeForeach = sym.source.slice(0, foreachIdx);
|
|
387
|
+
const withRe = new RegExp(`\\bwith\\s*\\(\\s*['"]${relation}['"]`);
|
|
388
|
+
if (withRe.test(beforeForeach))
|
|
389
|
+
return findings.length >= limit;
|
|
390
|
+
const lineOffset = beforeForeach.split("\n").length - 1;
|
|
391
|
+
findings.push({
|
|
392
|
+
file: sym.file,
|
|
393
|
+
method: sym.name,
|
|
394
|
+
line: sym.start_line + lineOffset,
|
|
395
|
+
relation,
|
|
396
|
+
pattern,
|
|
397
|
+
});
|
|
398
|
+
return findings.length >= limit;
|
|
399
|
+
};
|
|
400
|
+
for (const sym of index.symbols) {
|
|
401
|
+
if (sym.kind !== "method" || !sym.file.endsWith(".php") || !sym.source)
|
|
402
|
+
continue;
|
|
403
|
+
if (filePattern && !sym.file.includes(filePattern))
|
|
404
|
+
continue;
|
|
405
|
+
const src = sym.source;
|
|
406
|
+
const foreachRe = /foreach\s*\(\s*\$(\w+)\s+as\s+(?:\$\w+\s*=>\s*)?\$(\w+)\s*\)/g;
|
|
407
|
+
let fm;
|
|
408
|
+
while ((fm = foreachRe.exec(src)) !== null) {
|
|
409
|
+
const itemVar = fm[2];
|
|
410
|
+
const foreachIdx = fm.index;
|
|
411
|
+
const after = src.slice(foreachIdx);
|
|
412
|
+
// Guard against double-counting: each distinct relation name reported
|
|
413
|
+
// once per foreach, regardless of which pattern matched first.
|
|
414
|
+
const seen = new Set();
|
|
415
|
+
// Pattern 1 — property access: $item->profile
|
|
416
|
+
// The negative lookahead `(?![\w(])` blocks both:
|
|
417
|
+
// 1) following word chars (prevents backtracking to a partial capture
|
|
418
|
+
// like `$item->getProfile()` matching "getProfil" as a property);
|
|
419
|
+
// 2) an opening paren (excludes method calls, handled by pattern 2).
|
|
420
|
+
const propRe = new RegExp(`\\$${itemVar}->(\\w+)(?![\\w(])`, "g");
|
|
421
|
+
let m;
|
|
422
|
+
while ((m = propRe.exec(after)) !== null) {
|
|
423
|
+
if (emitFinding({ file: sym.file, name: sym.name, source: src, start_line: sym.start_line }, foreachIdx, m[1], "foreach-access-without-with", seen)) {
|
|
424
|
+
return { findings, total: findings.length };
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
// Pattern 2 — getter method call: $item->getProfile()
|
|
428
|
+
// Normalize to bare relation name for both the dedup and the ->with() check.
|
|
429
|
+
const getterRe = new RegExp(`\\$${itemVar}->(get\\w+)\\s*\\(\\s*\\)`, "g");
|
|
430
|
+
while ((m = getterRe.exec(after)) !== null) {
|
|
431
|
+
const rawMethod = m[1];
|
|
432
|
+
if (METHOD_CALL_BLOCKLIST.has(rawMethod))
|
|
433
|
+
continue;
|
|
434
|
+
const normalized = normalizeGetter(rawMethod);
|
|
435
|
+
if (!normalized || METHOD_CALL_BLOCKLIST.has(normalized.toLowerCase()))
|
|
436
|
+
continue;
|
|
437
|
+
if (emitFinding({ file: sym.file, name: sym.name, source: src, start_line: sym.start_line }, foreachIdx, normalized, "foreach-getter-without-with", seen)) {
|
|
438
|
+
return { findings, total: findings.length };
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
// Pattern 3 — chained access: $item->rel->sub (the first segment is the trigger)
|
|
442
|
+
const chainRe = new RegExp(`\\$${itemVar}->(\\w+)->\\w`, "g");
|
|
443
|
+
while ((m = chainRe.exec(after)) !== null) {
|
|
444
|
+
if (emitFinding({ file: sym.file, name: sym.name, source: src, start_line: sym.start_line }, foreachIdx, m[1], "foreach-chained-without-with", seen)) {
|
|
445
|
+
return { findings, total: findings.length };
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
return { findings, total: findings.length };
|
|
451
|
+
}
|
|
452
|
+
/**
|
|
453
|
+
* Flag oversized PHP classes. Two scopes:
|
|
454
|
+
*
|
|
455
|
+
* - `scope: "activerecord"` (default) — only models extending ActiveRecord.
|
|
456
|
+
* Uses `analyzeActiveRecord` for model detection and counts relations as a
|
|
457
|
+
* third threshold alongside methods and lines. Classic Yii2 god-model case:
|
|
458
|
+
* Survey.php in Mobi2 with 175 methods, 30 relations, 2291 lines.
|
|
459
|
+
*
|
|
460
|
+
* - `scope: "all"` — every PHP class in the index, regardless of base class.
|
|
461
|
+
* Captures service god-classes (UserService with 80 methods), component
|
|
462
|
+
* aggregates, and any other PHP class that outgrew its responsibility.
|
|
463
|
+
* `relation_count` is 0 for non-AR classes — the `min_relations` check is
|
|
464
|
+
* skipped so a service with 60 methods isn't hidden by a relation threshold.
|
|
465
|
+
*
|
|
466
|
+
* Thresholds default to 50/15/500 but are configurable for both scopes.
|
|
467
|
+
*/
|
|
468
|
+
export async function findPhpGodModel(repo, options) {
|
|
469
|
+
const index = await getCodeIndex(repo);
|
|
470
|
+
if (!index)
|
|
471
|
+
throw new Error(`Repository "${repo}" not found.`);
|
|
472
|
+
const minM = options?.min_methods ?? 50;
|
|
473
|
+
const minR = options?.min_relations ?? 15;
|
|
474
|
+
const minL = options?.min_lines ?? 500;
|
|
475
|
+
const scope = options?.scope ?? "activerecord";
|
|
476
|
+
const models = [];
|
|
477
|
+
if (scope === "activerecord") {
|
|
478
|
+
const ar = await analyzeActiveRecord(repo);
|
|
479
|
+
for (const m of ar.models) {
|
|
480
|
+
// Look up the class symbol by (name, kind, file) — file match keeps
|
|
481
|
+
// duplicate class names in different paths reported independently.
|
|
482
|
+
const classSym = index.symbols.find((s) => s.name === m.name && s.kind === "class" && s.file === m.file);
|
|
483
|
+
const lineCount = classSym ? classSym.end_line - classSym.start_line : 0;
|
|
484
|
+
const reasons = [];
|
|
485
|
+
if (m.methods.length > minM)
|
|
486
|
+
reasons.push(`methods: ${m.methods.length} > ${minM}`);
|
|
487
|
+
if (m.relations.length > minR)
|
|
488
|
+
reasons.push(`relations: ${m.relations.length} > ${minR}`);
|
|
489
|
+
if (lineCount > minL)
|
|
490
|
+
reasons.push(`lines: ${lineCount} > ${minL}`);
|
|
491
|
+
if (reasons.length > 0) {
|
|
492
|
+
models.push({
|
|
493
|
+
name: m.name,
|
|
494
|
+
file: m.file,
|
|
495
|
+
method_count: m.methods.length,
|
|
496
|
+
relation_count: m.relations.length,
|
|
497
|
+
line_count: lineCount,
|
|
498
|
+
reasons,
|
|
499
|
+
});
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
else {
|
|
504
|
+
// scope === "all" — iterate every PHP class symbol directly.
|
|
505
|
+
const classSyms = index.symbols.filter((s) => s.kind === "class" && s.file.endsWith(".php"));
|
|
506
|
+
for (const cls of classSyms) {
|
|
507
|
+
const methodCount = index.symbols.filter((s) => s.parent === cls.id && s.kind === "method").length;
|
|
508
|
+
const lineCount = cls.end_line - cls.start_line;
|
|
509
|
+
const reasons = [];
|
|
510
|
+
if (methodCount > minM)
|
|
511
|
+
reasons.push(`methods: ${methodCount} > ${minM}`);
|
|
512
|
+
if (lineCount > minL)
|
|
513
|
+
reasons.push(`lines: ${lineCount} > ${minL}`);
|
|
514
|
+
// min_relations intentionally skipped in "all" scope — not AR, no relations
|
|
515
|
+
if (reasons.length > 0) {
|
|
516
|
+
models.push({
|
|
517
|
+
name: cls.name,
|
|
518
|
+
file: cls.file,
|
|
519
|
+
method_count: methodCount,
|
|
520
|
+
relation_count: 0,
|
|
521
|
+
line_count: lineCount,
|
|
522
|
+
reasons,
|
|
523
|
+
});
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
// Sort by severity (number of reasons desc, then methods desc)
|
|
528
|
+
models.sort((a, b) => b.reasons.length - a.reasons.length || b.method_count - a.method_count);
|
|
529
|
+
return { models, total: models.length };
|
|
530
|
+
}
|
|
531
|
+
const AUDIT_TIMEOUT = 8000;
|
|
532
|
+
export async function phpProjectAudit(repo, options) {
|
|
533
|
+
const startTime = Date.now();
|
|
534
|
+
const gates = [];
|
|
535
|
+
const allChecks = ["security", "activerecord", "complexity", "dead_code", "patterns", "clones", "hotspots", "n_plus_one", "god_model"];
|
|
536
|
+
const enabled = new Set(options?.checks ?? allChecks);
|
|
537
|
+
const fp = options?.file_pattern ?? ".php";
|
|
538
|
+
const secOpts = {};
|
|
539
|
+
if (options?.file_pattern)
|
|
540
|
+
secOpts.file_pattern = options.file_pattern;
|
|
541
|
+
const tasks = [];
|
|
542
|
+
if (enabled.has("security"))
|
|
543
|
+
tasks.push({ name: "security", run: () => phpSecurityScan(repo, secOpts) });
|
|
544
|
+
if (enabled.has("activerecord"))
|
|
545
|
+
tasks.push({ name: "activerecord", run: () => analyzeActiveRecord(repo, options?.file_pattern ? { file_pattern: options.file_pattern } : undefined) });
|
|
546
|
+
if (enabled.has("complexity"))
|
|
547
|
+
tasks.push({ name: "complexity", run: async () => { const { analyzeComplexity } = await import("./complexity-tools.js"); return analyzeComplexity(repo, { file_pattern: fp, top_n: 10 }); } });
|
|
548
|
+
if (enabled.has("dead_code"))
|
|
549
|
+
tasks.push({ name: "dead_code", run: async () => { const { findDeadCode } = await import("./symbol-tools.js"); return findDeadCode(repo, { file_pattern: fp }); } });
|
|
550
|
+
if (enabled.has("patterns"))
|
|
551
|
+
tasks.push({ name: "patterns", run: () => searchPatterns(repo, "empty-catch", { file_pattern: fp }) });
|
|
552
|
+
if (enabled.has("clones"))
|
|
553
|
+
tasks.push({ name: "clones", run: async () => { const { findClones } = await import("./clone-tools.js"); return findClones(repo, { file_pattern: fp }); } });
|
|
554
|
+
if (enabled.has("hotspots"))
|
|
555
|
+
tasks.push({ name: "hotspots", run: async () => { const { analyzeHotspots } = await import("./hotspot-tools.js"); return analyzeHotspots(repo, {}); } });
|
|
556
|
+
if (enabled.has("n_plus_one"))
|
|
557
|
+
tasks.push({ name: "n_plus_one", run: () => findPhpNPlusOne(repo, options?.file_pattern ? { file_pattern: options.file_pattern } : undefined) });
|
|
558
|
+
if (enabled.has("god_model"))
|
|
559
|
+
tasks.push({ name: "god_model", run: () => findPhpGodModel(repo) });
|
|
560
|
+
const settled = await Promise.allSettled(tasks.map(async (t) => {
|
|
561
|
+
const s = Date.now();
|
|
562
|
+
const r = await Promise.race([t.run(), new Promise((ok) => setTimeout(() => ok("TIMEOUT"), AUDIT_TIMEOUT))]);
|
|
563
|
+
return { name: t.name, result: r, ms: Date.now() - s };
|
|
564
|
+
}));
|
|
565
|
+
let securityResult = { findings: [], summary: { critical: 0, high: 0, medium: 0, low: 0, total: 0 }, checks_run: [] };
|
|
566
|
+
let arResult = { models: [], total: 0 };
|
|
567
|
+
let totalFindings = 0;
|
|
568
|
+
for (const s of settled) {
|
|
569
|
+
if (s.status === "rejected") {
|
|
570
|
+
gates.push({ name: "unknown", status: "error", findings_count: 0, duration_ms: 0, error: String(s.reason) });
|
|
571
|
+
continue;
|
|
572
|
+
}
|
|
573
|
+
const { name, result, ms } = s.value;
|
|
574
|
+
if (result === "TIMEOUT") {
|
|
575
|
+
gates.push({ name, status: "timeout", findings_count: 0, duration_ms: ms });
|
|
576
|
+
continue;
|
|
577
|
+
}
|
|
578
|
+
let count = 0;
|
|
579
|
+
// activerecord is informational (model count), not a problem finding — excluded from totalFindings and health score
|
|
580
|
+
if (name === "security") {
|
|
581
|
+
securityResult = result;
|
|
582
|
+
count = securityResult.summary.total;
|
|
583
|
+
}
|
|
584
|
+
else if (name === "activerecord") {
|
|
585
|
+
arResult = result;
|
|
586
|
+
count = arResult.total;
|
|
587
|
+
}
|
|
588
|
+
else if (name === "complexity")
|
|
589
|
+
count = result?.summary?.above_threshold ?? 0;
|
|
590
|
+
else if (name === "dead_code")
|
|
591
|
+
count = result?.candidates?.length ?? 0;
|
|
592
|
+
else if (name === "patterns")
|
|
593
|
+
count = result?.matches?.length ?? 0;
|
|
594
|
+
else if (name === "clones")
|
|
595
|
+
count = result?.clones?.length ?? 0;
|
|
596
|
+
else if (name === "hotspots")
|
|
597
|
+
count = result?.hotspots?.length ?? 0;
|
|
598
|
+
else if (name === "n_plus_one")
|
|
599
|
+
count = result?.findings?.length ?? 0;
|
|
600
|
+
else if (name === "god_model")
|
|
601
|
+
count = result?.models?.length ?? 0;
|
|
602
|
+
if (name !== "activerecord")
|
|
603
|
+
totalFindings += count;
|
|
604
|
+
gates.push({ name, status: "ok", findings_count: count, duration_ms: ms });
|
|
605
|
+
}
|
|
606
|
+
const sec = securityResult.summary;
|
|
607
|
+
// Logarithmic penalties — a few critical findings are serious, but hundreds of
|
|
608
|
+
// complexity warnings shouldn't tank the score to 0. Each gate uses log2 scaling
|
|
609
|
+
// so 1 finding ≈ 0, 10 ≈ 17, 100 ≈ 33, 1000 ≈ 50 penalty points.
|
|
610
|
+
const secPenalty = sec.total > 0 ? Math.round(Math.log2(sec.total + 1) * (sec.critical > 0 ? 8 : 4)) : 0;
|
|
611
|
+
const qualityFindings = totalFindings - sec.total;
|
|
612
|
+
const qualPenalty = qualityFindings > 0 ? Math.round(Math.log2(qualityFindings + 1) * 4) : 0;
|
|
613
|
+
const healthScore = Math.max(0, Math.min(100, 100 - secPenalty - qualPenalty));
|
|
614
|
+
const topRisks = gates.filter(g => g.findings_count > 0 && g.name !== "activerecord").sort((a, b) => b.findings_count - a.findings_count).slice(0, 3).map(g => `${g.name}: ${g.findings_count} findings`);
|
|
615
|
+
return {
|
|
616
|
+
repo, duration_ms: Date.now() - startTime,
|
|
617
|
+
checks_run: gates.filter(g => g.status === "ok").map(g => g.name),
|
|
618
|
+
gates,
|
|
619
|
+
summary: { total_findings: totalFindings, critical: sec.critical, high: sec.high, medium: sec.medium, low: sec.low, health_score: healthScore, top_risks: topRisks },
|
|
620
|
+
security: securityResult,
|
|
621
|
+
activerecord: arResult,
|
|
622
|
+
};
|
|
623
|
+
}
|
|
624
|
+
// ---------------------------------------------------------------------------
|
|
625
|
+
// Helpers
|
|
626
|
+
// ---------------------------------------------------------------------------
|
|
627
|
+
async function readJsonSafe(path) {
|
|
628
|
+
try {
|
|
629
|
+
const content = await readFile(path, "utf-8");
|
|
630
|
+
return JSON.parse(content);
|
|
631
|
+
}
|
|
632
|
+
catch {
|
|
633
|
+
return null;
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
function extractLineContext(source, index) {
|
|
637
|
+
const lineStart = source.lastIndexOf("\n", index) + 1;
|
|
638
|
+
const lineEnd = source.indexOf("\n", index);
|
|
639
|
+
const end = lineEnd === -1 ? source.length : lineEnd;
|
|
640
|
+
return source.slice(lineStart, end).trim().slice(0, 200);
|
|
641
|
+
}
|
|
642
|
+
function pascalToKebab(s) {
|
|
643
|
+
return s.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase();
|
|
644
|
+
}
|
|
645
|
+
//# sourceMappingURL=php-tools.js.map
|