octocode-cli 1.2.5 → 1.2.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -63
- package/README.md +86 -109
- package/out/octocode-cli.js +7027 -7014
- package/package.json +8 -6
- package/skills/README.md +97 -120
- package/skills/octocode-code-engineer/.claude/settings.local.json +18 -0
- package/skills/octocode-code-engineer/.octocode/rfc/RFC-code-engineer-weakness-fixes.md +255 -0
- package/skills/octocode-code-engineer/.plan/VALIDATED_PLAN.md +223 -0
- package/skills/octocode-code-engineer/README.md +178 -0
- package/skills/octocode-code-engineer/SKILL.md +418 -0
- package/skills/octocode-code-engineer/coverage/architecture.ts.html +7828 -0
- package/skills/octocode-code-engineer/coverage/ast-helpers.ts.html +211 -0
- package/skills/octocode-code-engineer/coverage/ast-search.ts.html +1795 -0
- package/skills/octocode-code-engineer/coverage/base.css +224 -0
- package/skills/octocode-code-engineer/coverage/block-navigation.js +87 -0
- package/skills/octocode-code-engineer/coverage/cache.ts.html +376 -0
- package/skills/octocode-code-engineer/coverage/cli.ts.html +982 -0
- package/skills/octocode-code-engineer/coverage/clover.xml +3217 -0
- package/skills/octocode-code-engineer/coverage/collect-effects.ts.html +664 -0
- package/skills/octocode-code-engineer/coverage/collect-input-sources.ts.html +577 -0
- package/skills/octocode-code-engineer/coverage/collect-performance.ts.html +331 -0
- package/skills/octocode-code-engineer/coverage/collect-prototype-pollution.ts.html +421 -0
- package/skills/octocode-code-engineer/coverage/collect-security.ts.html +604 -0
- package/skills/octocode-code-engineer/coverage/collect-test-profile.ts.html +589 -0
- package/skills/octocode-code-engineer/coverage/coverage-final.json +30 -0
- package/skills/octocode-code-engineer/coverage/dependencies.ts.html +997 -0
- package/skills/octocode-code-engineer/coverage/dependency-summary.ts.html +688 -0
- package/skills/octocode-code-engineer/coverage/discovery.ts.html +322 -0
- package/skills/octocode-code-engineer/coverage/favicon.png +0 -0
- package/skills/octocode-code-engineer/coverage/graph-analytics.ts.html +1510 -0
- package/skills/octocode-code-engineer/coverage/index.html +536 -0
- package/skills/octocode-code-engineer/coverage/index.ts.html +826 -0
- package/skills/octocode-code-engineer/coverage/metrics.ts.html +553 -0
- package/skills/octocode-code-engineer/coverage/pipeline.ts.html +2044 -0
- package/skills/octocode-code-engineer/coverage/prettify.css +1 -0
- package/skills/octocode-code-engineer/coverage/prettify.js +2 -0
- package/skills/octocode-code-engineer/coverage/report-analysis.ts.html +1570 -0
- package/skills/octocode-code-engineer/coverage/report-writer.ts.html +1102 -0
- package/skills/octocode-code-engineer/coverage/security-detectors.ts.html +1747 -0
- package/skills/octocode-code-engineer/coverage/semantic-detectors.ts.html +2152 -0
- package/skills/octocode-code-engineer/coverage/semantic.ts.html +1897 -0
- package/skills/octocode-code-engineer/coverage/sort-arrow-sprite.png +0 -0
- package/skills/octocode-code-engineer/coverage/sorter.js +210 -0
- package/skills/octocode-code-engineer/coverage/summary-md.ts.html +1222 -0
- package/skills/octocode-code-engineer/coverage/test-quality-detectors.ts.html +1039 -0
- package/skills/octocode-code-engineer/coverage/tree-sitter-analyzer.ts.html +955 -0
- package/skills/octocode-code-engineer/coverage/ts-analyzer.ts.html +1213 -0
- package/skills/octocode-code-engineer/coverage/types.ts.html +2473 -0
- package/skills/octocode-code-engineer/coverage/utils.ts.html +820 -0
- package/skills/octocode-code-engineer/eslint.config.mjs +54 -0
- package/skills/octocode-code-engineer/minify-scripts.mjs +32 -0
- package/skills/octocode-code-engineer/package.json +54 -0
- package/skills/octocode-code-engineer/references/agent-ast-reading-rfc.md +95 -0
- package/skills/octocode-code-engineer/references/architecture-techniques.md +121 -0
- package/skills/octocode-code-engineer/references/ast-search.md +210 -0
- package/skills/octocode-code-engineer/references/ast-tree-search.md +151 -0
- package/skills/octocode-code-engineer/references/cli-reference.md +167 -0
- package/skills/octocode-code-engineer/references/concepts.md +107 -0
- package/skills/octocode-code-engineer/references/finding-categories.md +128 -0
- package/skills/octocode-code-engineer/references/improvement-roadmap.md +304 -0
- package/skills/octocode-code-engineer/references/output-files.md +144 -0
- package/skills/octocode-code-engineer/references/playbooks.md +204 -0
- package/skills/octocode-code-engineer/references/present-results.md +136 -0
- package/skills/octocode-code-engineer/references/tool-workflows.md +566 -0
- package/skills/octocode-code-engineer/references/validate-investigate.md +225 -0
- package/skills/octocode-code-engineer/scripts/analysis/dependencies.js +1 -0
- package/skills/octocode-code-engineer/scripts/analysis/dependency-summary.js +1 -0
- package/skills/octocode-code-engineer/scripts/analysis/discovery.js +1 -0
- package/skills/octocode-code-engineer/scripts/analysis/graph-analytics.js +1 -0
- package/skills/octocode-code-engineer/scripts/analysis/semantic.js +1 -0
- package/skills/octocode-code-engineer/scripts/ast/helpers.js +1 -0
- package/skills/octocode-code-engineer/scripts/ast/metrics.js +1 -0
- package/skills/octocode-code-engineer/scripts/ast/search.js +2 -0
- package/skills/octocode-code-engineer/scripts/ast/tree-search.js +2 -0
- package/skills/octocode-code-engineer/scripts/ast/tree-sitter.js +1 -0
- package/skills/octocode-code-engineer/scripts/ast/ts-analyzer.js +1 -0
- package/skills/octocode-code-engineer/scripts/collectors/chains.js +1 -0
- package/skills/octocode-code-engineer/scripts/collectors/effects.js +1 -0
- package/skills/octocode-code-engineer/scripts/collectors/input-sources.js +1 -0
- package/skills/octocode-code-engineer/scripts/collectors/performance.js +1 -0
- package/skills/octocode-code-engineer/scripts/collectors/prototype-pollution.js +1 -0
- package/skills/octocode-code-engineer/scripts/collectors/security.js +1 -0
- package/skills/octocode-code-engineer/scripts/collectors/test-profile.js +1 -0
- package/skills/octocode-code-engineer/scripts/common/is-direct-run.js +1 -0
- package/skills/octocode-code-engineer/scripts/common/utils.js +1 -0
- package/skills/octocode-code-engineer/scripts/detectors/code-quality.js +1 -0
- package/skills/octocode-code-engineer/scripts/detectors/cohesion.js +1 -0
- package/skills/octocode-code-engineer/scripts/detectors/coupling.js +1 -0
- package/skills/octocode-code-engineer/scripts/detectors/cycle.js +1 -0
- package/skills/octocode-code-engineer/scripts/detectors/dead-code.js +1 -0
- package/skills/octocode-code-engineer/scripts/detectors/import-style.js +1 -0
- package/skills/octocode-code-engineer/scripts/detectors/index.js +1 -0
- package/skills/octocode-code-engineer/scripts/detectors/security.js +1 -0
- package/skills/octocode-code-engineer/scripts/detectors/semantic.js +1 -0
- package/skills/octocode-code-engineer/scripts/detectors/shared.js +1 -0
- package/skills/octocode-code-engineer/scripts/detectors/test-quality.js +1 -0
- package/skills/octocode-code-engineer/scripts/index.js +1 -0
- package/skills/octocode-code-engineer/scripts/pipeline/cache.js +1 -0
- package/skills/octocode-code-engineer/scripts/pipeline/cli.js +1 -0
- package/skills/octocode-code-engineer/scripts/pipeline/main.js +2 -0
- package/skills/octocode-code-engineer/scripts/reporting/analysis.js +1 -0
- package/skills/octocode-code-engineer/scripts/reporting/summary-md.js +1 -0
- package/skills/octocode-code-engineer/scripts/reporting/writer.js +1 -0
- package/skills/octocode-code-engineer/scripts/types/constants.js +1 -0
- package/skills/octocode-code-engineer/scripts/types/index.js +1 -0
- package/skills/octocode-code-engineer/scripts/types/interfaces.js +1 -0
- package/skills/octocode-code-engineer/src/analysis/dependencies.test.ts +545 -0
- package/skills/octocode-code-engineer/src/analysis/dependencies.ts +406 -0
- package/skills/octocode-code-engineer/src/analysis/dependency-summary.test.ts +566 -0
- package/skills/octocode-code-engineer/src/analysis/dependency-summary.ts +257 -0
- package/skills/octocode-code-engineer/src/analysis/discovery.test.ts +420 -0
- package/skills/octocode-code-engineer/src/analysis/discovery.ts +87 -0
- package/skills/octocode-code-engineer/src/analysis/graph-analytics.test.ts +449 -0
- package/skills/octocode-code-engineer/src/analysis/graph-analytics.ts +534 -0
- package/skills/octocode-code-engineer/src/analysis/semantic.test.ts +1533 -0
- package/skills/octocode-code-engineer/src/analysis/semantic.ts +830 -0
- package/skills/octocode-code-engineer/src/ast/helpers.test.ts +185 -0
- package/skills/octocode-code-engineer/src/ast/helpers.ts +62 -0
- package/skills/octocode-code-engineer/src/ast/metrics.test.ts +304 -0
- package/skills/octocode-code-engineer/src/ast/metrics.ts +204 -0
- package/skills/octocode-code-engineer/src/ast/search.test.ts +647 -0
- package/skills/octocode-code-engineer/src/ast/search.ts +648 -0
- package/skills/octocode-code-engineer/src/ast/tree-search.test.ts +199 -0
- package/skills/octocode-code-engineer/src/ast/tree-search.ts +392 -0
- package/skills/octocode-code-engineer/src/ast/tree-sitter.test.ts +407 -0
- package/skills/octocode-code-engineer/src/ast/tree-sitter.ts +402 -0
- package/skills/octocode-code-engineer/src/ast/ts-analyzer.test.ts +1864 -0
- package/skills/octocode-code-engineer/src/ast/ts-analyzer.ts +509 -0
- package/skills/octocode-code-engineer/src/collectors/chains.ts +74 -0
- package/skills/octocode-code-engineer/src/collectors/effects.test.ts +490 -0
- package/skills/octocode-code-engineer/src/collectors/effects.ts +332 -0
- package/skills/octocode-code-engineer/src/collectors/input-sources.test.ts +144 -0
- package/skills/octocode-code-engineer/src/collectors/input-sources.ts +196 -0
- package/skills/octocode-code-engineer/src/collectors/performance.test.ts +82 -0
- package/skills/octocode-code-engineer/src/collectors/performance.ts +141 -0
- package/skills/octocode-code-engineer/src/collectors/prototype-pollution.test.ts +55 -0
- package/skills/octocode-code-engineer/src/collectors/prototype-pollution.ts +162 -0
- package/skills/octocode-code-engineer/src/collectors/security.test.ts +124 -0
- package/skills/octocode-code-engineer/src/collectors/security.ts +309 -0
- package/skills/octocode-code-engineer/src/collectors/test-profile.test.ts +97 -0
- package/skills/octocode-code-engineer/src/collectors/test-profile.ts +269 -0
- package/skills/octocode-code-engineer/src/common/is-direct-run.test.ts +32 -0
- package/skills/octocode-code-engineer/src/common/is-direct-run.ts +13 -0
- package/skills/octocode-code-engineer/src/common/utils.test.ts +463 -0
- package/skills/octocode-code-engineer/src/common/utils.ts +304 -0
- package/skills/octocode-code-engineer/src/detectors/code-quality.ts +966 -0
- package/skills/octocode-code-engineer/src/detectors/cohesion.ts +539 -0
- package/skills/octocode-code-engineer/src/detectors/coupling.ts +323 -0
- package/skills/octocode-code-engineer/src/detectors/cycle.ts +349 -0
- package/skills/octocode-code-engineer/src/detectors/dead-code.ts +320 -0
- package/skills/octocode-code-engineer/src/detectors/import-style.ts +376 -0
- package/skills/octocode-code-engineer/src/detectors/index.test.ts +3061 -0
- package/skills/octocode-code-engineer/src/detectors/index.ts +88 -0
- package/skills/octocode-code-engineer/src/detectors/security.test.ts +882 -0
- package/skills/octocode-code-engineer/src/detectors/security.ts +821 -0
- package/skills/octocode-code-engineer/src/detectors/semantic.ts +758 -0
- package/skills/octocode-code-engineer/src/detectors/shared.ts +49 -0
- package/skills/octocode-code-engineer/src/detectors/test-quality.test.ts +388 -0
- package/skills/octocode-code-engineer/src/detectors/test-quality.ts +367 -0
- package/skills/octocode-code-engineer/src/index.test.ts +4425 -0
- package/skills/octocode-code-engineer/src/index.ts +403 -0
- package/skills/octocode-code-engineer/src/pipeline/cache.test.ts +199 -0
- package/skills/octocode-code-engineer/src/pipeline/cache.ts +130 -0
- package/skills/octocode-code-engineer/src/pipeline/cli.test.ts +493 -0
- package/skills/octocode-code-engineer/src/pipeline/cli.ts +344 -0
- package/skills/octocode-code-engineer/src/pipeline/main.test.ts +174 -0
- package/skills/octocode-code-engineer/src/pipeline/main.ts +1074 -0
- package/skills/octocode-code-engineer/src/pipeline.test.ts +84 -0
- package/skills/octocode-code-engineer/src/reporting/analysis.test.ts +782 -0
- package/skills/octocode-code-engineer/src/reporting/analysis.ts +688 -0
- package/skills/octocode-code-engineer/src/reporting/output-contract.test.ts +463 -0
- package/skills/octocode-code-engineer/src/reporting/summary-md.test.ts +421 -0
- package/skills/octocode-code-engineer/src/reporting/summary-md.ts +714 -0
- package/skills/octocode-code-engineer/src/reporting/writer.ts +430 -0
- package/skills/octocode-code-engineer/src/sanity.test.ts +47 -0
- package/skills/octocode-code-engineer/src/types/constants.ts +248 -0
- package/skills/octocode-code-engineer/src/types/index.ts +80 -0
- package/skills/octocode-code-engineer/src/types/interfaces.ts +682 -0
- package/skills/octocode-code-engineer/tsconfig.json +17 -0
- package/skills/octocode-code-engineer/vitest.config.ts +8 -0
- package/skills/octocode-documentation-writer/README.md +113 -0
- package/skills/octocode-documentation-writer/SKILL.md +886 -0
- package/skills/octocode-documentation-writer/references/agent-discovery-analysis.md +453 -0
- package/skills/octocode-documentation-writer/references/agent-documentation-writer.md +255 -0
- package/skills/octocode-documentation-writer/references/agent-engineer-questions.md +247 -0
- package/skills/octocode-documentation-writer/references/agent-orchestrator.md +370 -0
- package/skills/octocode-documentation-writer/references/agent-qa-validator.md +227 -0
- package/skills/octocode-documentation-writer/references/agent-researcher.md +250 -0
- package/skills/octocode-documentation-writer/schemas/analysis-schema.json +886 -0
- package/skills/octocode-documentation-writer/schemas/discovery-tasks.json +96 -0
- package/skills/octocode-documentation-writer/schemas/documentation-structure.json +373 -0
- package/skills/octocode-documentation-writer/schemas/partial-discovery-schema.json +102 -0
- package/skills/octocode-documentation-writer/schemas/partial-research-schema.json +98 -0
- package/skills/octocode-documentation-writer/schemas/qa-results-schema.json +113 -0
- package/skills/octocode-documentation-writer/schemas/questions-schema.json +228 -0
- package/skills/octocode-documentation-writer/schemas/research-schema.json +104 -0
- package/skills/octocode-documentation-writer/schemas/state-schema.json +222 -0
- package/skills/octocode-documentation-writer/schemas/work-assignments-schema.json +74 -0
- package/skills/octocode-plan/SKILL.md +122 -116
- package/skills/octocode-prompt-optimizer/SKILL.md +617 -0
- package/skills/octocode-pull-request-reviewer/README.md +249 -0
- package/skills/octocode-pull-request-reviewer/SKILL.md +479 -0
- package/skills/octocode-pull-request-reviewer/references/dependency-check.md +74 -0
- package/skills/octocode-pull-request-reviewer/references/domain-reviewers.md +24 -0
- package/skills/octocode-pull-request-reviewer/references/execution-lifecycle.md +441 -0
- package/skills/octocode-pull-request-reviewer/references/flow-analysis-protocol.md +64 -0
- package/skills/octocode-pull-request-reviewer/references/output-template.md +174 -0
- package/skills/octocode-pull-request-reviewer/references/parallel-agent-protocol.md +182 -0
- package/skills/octocode-pull-request-reviewer/references/review-guidelines.md +26 -0
- package/skills/octocode-pull-request-reviewer/references/verification-checklist.md +40 -0
- package/skills/octocode-research/.claude/settings.local.json +46 -0
- package/skills/octocode-research/.octocode/plan/code-review-fixes/plan.md +312 -0
- package/skills/octocode-research/.octocode/plan/code-review-fixes/research.md +212 -0
- package/skills/octocode-research/.octocode/plans/NODE_SERVER_START_PLAN.md +755 -0
- package/skills/octocode-research/.octocode/research/code-review/research.md +371 -0
- package/skills/octocode-research/.octocode/review/IMPROVEMENTS.md +391 -0
- package/skills/octocode-research/.octocode/review/REVIEW_PLAN.md +289 -0
- package/skills/octocode-research/.octocode/review/REVIEW_REPORT.md +356 -0
- package/skills/octocode-research/AGENTS.md +349 -0
- package/skills/octocode-research/README.md +494 -0
- package/skills/octocode-research/SKILL.md +652 -274
- package/skills/octocode-research/docs/API_REFERENCE.md +562 -0
- package/skills/octocode-research/docs/ARCHITECTURE.md +554 -0
- package/skills/octocode-research/docs/FLOWS.md +577 -0
- package/skills/octocode-research/docs/OVERVIEW.md +564 -0
- package/skills/octocode-research/docs/SERVER_FLOWS.md +631 -0
- package/skills/octocode-research/ecosystem.config.cjs +88 -0
- package/skills/octocode-research/eslint.config.mjs +27 -0
- package/skills/octocode-research/package.json +84 -0
- package/skills/octocode-research/references/GUARDRAILS.md +40 -0
- package/skills/octocode-research/references/PARALLEL_AGENT_PROTOCOL.md +178 -0
- package/skills/octocode-research/references/roast-prompt.md +149 -0
- package/skills/octocode-research/scripts/server-init.d.ts +2 -0
- package/skills/octocode-research/scripts/server-init.js +2 -0
- package/skills/octocode-research/scripts/server.d.ts +8 -0
- package/skills/octocode-research/scripts/server.js +445 -0
- package/skills/octocode-research/src/__tests__/integration/circuitBreaker.test.ts +205 -0
- package/skills/octocode-research/src/__tests__/integration/routes.test.ts +374 -0
- package/skills/octocode-research/src/__tests__/unit/circuitBreaker.test.ts +245 -0
- package/skills/octocode-research/src/__tests__/unit/errorHandler.test.ts +183 -0
- package/skills/octocode-research/src/__tests__/unit/httpPreprocess.test.ts +157 -0
- package/skills/octocode-research/src/__tests__/unit/logger.test.ts +143 -0
- package/skills/octocode-research/src/__tests__/unit/queryParser.test.ts +130 -0
- package/skills/octocode-research/src/__tests__/unit/responseBuilder.test.ts +469 -0
- package/skills/octocode-research/src/__tests__/unit/retry.test.ts +205 -0
- package/skills/octocode-research/src/index.ts +186 -0
- package/skills/octocode-research/src/mcpCache.ts +49 -0
- package/skills/octocode-research/src/middleware/errorHandler.ts +65 -0
- package/skills/octocode-research/src/middleware/logger.ts +61 -0
- package/skills/octocode-research/src/middleware/queryParser.ts +115 -0
- package/skills/octocode-research/src/middleware/readiness.ts +17 -0
- package/skills/octocode-research/src/routes/github.ts +197 -0
- package/skills/octocode-research/src/routes/local.ts +175 -0
- package/skills/octocode-research/src/routes/lsp.ts +177 -0
- package/skills/octocode-research/src/routes/package.ts +127 -0
- package/skills/octocode-research/src/routes/prompts.ts +138 -0
- package/skills/octocode-research/src/routes/tools.ts +677 -0
- package/skills/octocode-research/src/server-init.ts +363 -0
- package/skills/octocode-research/src/server.ts +285 -0
- package/skills/octocode-research/src/types/errorGuards.ts +151 -0
- package/skills/octocode-research/src/types/express.d.ts +76 -0
- package/skills/octocode-research/src/types/guards.ts +98 -0
- package/skills/octocode-research/src/types/mcp.ts +119 -0
- package/skills/octocode-research/src/types/responses.ts +199 -0
- package/skills/octocode-research/src/types/toolTypes.ts +33 -0
- package/skills/octocode-research/src/utils/asyncTimeout.ts +116 -0
- package/skills/octocode-research/src/utils/circuitBreaker.ts +492 -0
- package/skills/octocode-research/src/utils/colors.ts +53 -0
- package/skills/octocode-research/src/utils/errorQueue.ts +71 -0
- package/skills/octocode-research/src/utils/logEmoji.ts +103 -0
- package/skills/octocode-research/src/utils/logger.ts +413 -0
- package/skills/octocode-research/src/utils/resilience.ts +169 -0
- package/skills/octocode-research/src/utils/responseBuilder.ts +495 -0
- package/skills/octocode-research/src/utils/responseFactory.ts +100 -0
- package/skills/octocode-research/src/utils/responseParser.ts +272 -0
- package/skills/octocode-research/src/utils/retry.ts +280 -0
- package/skills/octocode-research/src/utils/routeFactory.ts +117 -0
- package/skills/octocode-research/src/utils/url.ts +20 -0
- package/skills/octocode-research/src/validation/httpPreprocess.ts +155 -0
- package/skills/octocode-research/src/validation/index.ts +2 -0
- package/skills/octocode-research/src/validation/schemas.ts +578 -0
- package/skills/octocode-research/src/validation/toolCallSchema.ts +132 -0
- package/skills/octocode-research/tsconfig.json +21 -0
- package/skills/octocode-research/tsdown.config.ts +42 -0
- package/skills/octocode-research/vitest.config.ts +20 -0
- package/skills/octocode-researcher/SKILL.md +461 -0
- package/skills/octocode-researcher/references/fallbacks.md +120 -0
- package/skills/{octocode-local-search → octocode-researcher}/references/tool-reference.md +132 -49
- package/skills/{octocode-local-search → octocode-researcher}/references/workflow-patterns.md +204 -4
- package/skills/octocode-rfc-generator/SKILL.md +223 -0
- package/skills/octocode-rfc-generator/references/rfc-template.md +193 -0
- package/skills/octocode-roast/SKILL.md +63 -21
- package/skills/octocode-implement/SKILL.md +0 -293
- package/skills/octocode-implement/references/execution-phases.md +0 -317
- package/skills/octocode-implement/references/tool-reference.md +0 -403
- package/skills/octocode-implement/references/workflow-patterns.md +0 -385
- package/skills/octocode-local-search/SKILL.md +0 -449
- package/skills/octocode-pr-review/SKILL.md +0 -391
- package/skills/octocode-pr-review/references/domain-reviewers.md +0 -105
- package/skills/octocode-pr-review/references/execution-lifecycle.md +0 -116
- package/skills/octocode-pr-review/references/research-flows.md +0 -75
- package/skills/octocode-research/references/tool-reference.md +0 -304
- package/skills/octocode-research/references/workflow-patterns.md +0 -325
|
@@ -0,0 +1,539 @@
|
|
|
1
|
+
import { findImportLine, isLikelyEntrypoint } from './shared.js';
|
|
2
|
+
import { canAddFinding } from './shared.js';
|
|
3
|
+
import { isTestFile } from '../common/utils.js';
|
|
4
|
+
|
|
5
|
+
import type { FindingDraft } from './shared.js';
|
|
6
|
+
import type {
|
|
7
|
+
DependencyState,
|
|
8
|
+
DependencySummary,
|
|
9
|
+
FileCriticality,
|
|
10
|
+
FileEntry,
|
|
11
|
+
Finding,
|
|
12
|
+
HotFile,
|
|
13
|
+
} from '../types/index.js';
|
|
14
|
+
|
|
15
|
+
export function detectGodModules(
|
|
16
|
+
fileSummaries: FileEntry[],
|
|
17
|
+
dependencyState: DependencyState,
|
|
18
|
+
stmtThreshold: number = 500,
|
|
19
|
+
exportThreshold: number = 20
|
|
20
|
+
): FindingDraft[] {
|
|
21
|
+
const findings: FindingDraft[] = [];
|
|
22
|
+
|
|
23
|
+
for (const entry of fileSummaries) {
|
|
24
|
+
if (isTestFile(entry.file)) continue;
|
|
25
|
+
const totalStmts = entry.functions.reduce(
|
|
26
|
+
(s, fn) => s + fn.statementCount,
|
|
27
|
+
0
|
|
28
|
+
);
|
|
29
|
+
const exportCount = (
|
|
30
|
+
dependencyState.declaredExportsByFile.get(entry.file) || []
|
|
31
|
+
).length;
|
|
32
|
+
const reasons: string[] = [];
|
|
33
|
+
if (totalStmts > stmtThreshold)
|
|
34
|
+
reasons.push(`${totalStmts} statements (threshold: ${stmtThreshold})`);
|
|
35
|
+
if (exportCount > exportThreshold)
|
|
36
|
+
reasons.push(`${exportCount} exports (threshold: ${exportThreshold})`);
|
|
37
|
+
if (reasons.length === 0) continue;
|
|
38
|
+
|
|
39
|
+
if (!canAddFinding(findings)) break;
|
|
40
|
+
findings.push({
|
|
41
|
+
severity: 'high',
|
|
42
|
+
category: 'god-module',
|
|
43
|
+
file: entry.file,
|
|
44
|
+
lineStart: 1,
|
|
45
|
+
lineEnd: 1,
|
|
46
|
+
title: `God module: ${entry.file}`,
|
|
47
|
+
reason: `Module is excessively large: ${reasons.join('; ')}.`,
|
|
48
|
+
files: [entry.file],
|
|
49
|
+
suggestedFix: {
|
|
50
|
+
strategy:
|
|
51
|
+
'Split module into focused sub-modules with single responsibilities.',
|
|
52
|
+
steps: [
|
|
53
|
+
'Identify distinct functional groups within the module.',
|
|
54
|
+
'Extract each group into a dedicated module.',
|
|
55
|
+
'Create a barrel if backward compatibility is needed.',
|
|
56
|
+
'Update imports incrementally.',
|
|
57
|
+
],
|
|
58
|
+
},
|
|
59
|
+
impact: 'Smaller modules are easier to understand, test, and maintain.',
|
|
60
|
+
tags: ['complexity', 'responsibility', 'size'],
|
|
61
|
+
lspHints: [
|
|
62
|
+
{
|
|
63
|
+
tool: 'lspFindReferences',
|
|
64
|
+
symbolName: entry.file.split('/').pop() || entry.file,
|
|
65
|
+
lineHint: 1,
|
|
66
|
+
file: entry.file,
|
|
67
|
+
expectedResult: `identify consumer clusters to guide module splitting strategy`,
|
|
68
|
+
},
|
|
69
|
+
],
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return findings;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function folderOf(filePath: string): string {
|
|
77
|
+
const normalized = filePath.replace(/\\/g, '/');
|
|
78
|
+
const idx = normalized.lastIndexOf('/');
|
|
79
|
+
return idx === -1 ? '.' : normalized.slice(0, idx);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function detectMegaFolders(
|
|
83
|
+
fileSummaries: FileEntry[],
|
|
84
|
+
minFiles: number = 25,
|
|
85
|
+
concentrationThreshold: number = 0.25
|
|
86
|
+
): FindingDraft[] {
|
|
87
|
+
const findings: FindingDraft[] = [];
|
|
88
|
+
const productionFiles = fileSummaries.filter(
|
|
89
|
+
entry => !isTestFile(entry.file)
|
|
90
|
+
);
|
|
91
|
+
if (productionFiles.length === 0) return findings;
|
|
92
|
+
|
|
93
|
+
const byFolder = new Map<string, FileEntry[]>();
|
|
94
|
+
for (const entry of productionFiles) {
|
|
95
|
+
const folder = folderOf(entry.file);
|
|
96
|
+
if (!byFolder.has(folder)) byFolder.set(folder, []);
|
|
97
|
+
byFolder.get(folder)!.push(entry);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const sortedFolders = [...byFolder.entries()]
|
|
101
|
+
.map(([folder, entries]) => ({ folder, entries, count: entries.length }))
|
|
102
|
+
.filter(
|
|
103
|
+
({ count }) =>
|
|
104
|
+
count >= minFiles &&
|
|
105
|
+
count / productionFiles.length >= concentrationThreshold
|
|
106
|
+
)
|
|
107
|
+
.sort((a, b) => b.count - a.count);
|
|
108
|
+
|
|
109
|
+
for (const candidate of sortedFolders) {
|
|
110
|
+
const concentration = candidate.count / productionFiles.length;
|
|
111
|
+
const severity: Finding['severity'] =
|
|
112
|
+
concentration >= 0.5 || candidate.count >= 50 ? 'high' : 'medium';
|
|
113
|
+
const topFiles = candidate.entries
|
|
114
|
+
.map(entry => entry.file)
|
|
115
|
+
.sort()
|
|
116
|
+
.slice(0, 8);
|
|
117
|
+
const representativeFile = candidate.entries[0]?.file ?? candidate.folder;
|
|
118
|
+
|
|
119
|
+
if (!canAddFinding(findings)) break;
|
|
120
|
+
findings.push({
|
|
121
|
+
severity,
|
|
122
|
+
category: 'mega-folder',
|
|
123
|
+
file: representativeFile,
|
|
124
|
+
lineStart: 1,
|
|
125
|
+
lineEnd: 1,
|
|
126
|
+
title: `Mega folder: ${candidate.folder} (${candidate.count} files)`,
|
|
127
|
+
reason: `${candidate.folder} contains ${candidate.count} production files (${(concentration * 100).toFixed(1)}% of the codebase), which usually indicates mixed responsibilities and weak module boundaries.`,
|
|
128
|
+
files: topFiles,
|
|
129
|
+
suggestedFix: {
|
|
130
|
+
strategy:
|
|
131
|
+
'Map the import graph, identify domain clusters, then restructure with an automated migration script.',
|
|
132
|
+
steps: [
|
|
133
|
+
'Extract the local import graph (rg/localSearchCode) and group files into clusters by what imports what.',
|
|
134
|
+
'Design target directories that follow the data flow (e.g., types → parsing → analysis → detection → reporting → orchestration).',
|
|
135
|
+
'Write a disposable migration script that maps old basenames to { dir, name } targets, moves files, and rewrites all relative import paths atomically.',
|
|
136
|
+
'Validate after each phase: tsc --noEmit, eslint --fix, test suite.',
|
|
137
|
+
'Move shared primitives into a dedicated common/ folder to avoid cross-domain coupling.',
|
|
138
|
+
],
|
|
139
|
+
},
|
|
140
|
+
impact:
|
|
141
|
+
'Improves navigability, ownership boundaries, and change isolation.',
|
|
142
|
+
tags: [
|
|
143
|
+
'architecture',
|
|
144
|
+
'modularity',
|
|
145
|
+
'folder-structure',
|
|
146
|
+
'maintainability',
|
|
147
|
+
],
|
|
148
|
+
evidence: {
|
|
149
|
+
folderPath: candidate.folder,
|
|
150
|
+
fileCount: candidate.count,
|
|
151
|
+
totalProductionFiles: productionFiles.length,
|
|
152
|
+
concentration,
|
|
153
|
+
},
|
|
154
|
+
lspHints: [
|
|
155
|
+
{
|
|
156
|
+
tool: 'lspGotoDefinition',
|
|
157
|
+
symbolName: candidate.folder,
|
|
158
|
+
lineHint: 1,
|
|
159
|
+
file: representativeFile,
|
|
160
|
+
expectedResult:
|
|
161
|
+
'inventory representative modules in this folder before planning decomposition',
|
|
162
|
+
},
|
|
163
|
+
],
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return findings;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export function detectGodFunctions(
|
|
171
|
+
fileSummaries: FileEntry[],
|
|
172
|
+
stmtThreshold: number = 100,
|
|
173
|
+
miThreshold: number = 10
|
|
174
|
+
): FindingDraft[] {
|
|
175
|
+
const findings: FindingDraft[] = [];
|
|
176
|
+
const MIN_LOC_FOR_MI = 30;
|
|
177
|
+
|
|
178
|
+
for (const entry of fileSummaries) {
|
|
179
|
+
if (isTestFile(entry.file)) continue;
|
|
180
|
+
for (const fn of entry.functions) {
|
|
181
|
+
const byStatements = fn.statementCount > stmtThreshold;
|
|
182
|
+
const byMI =
|
|
183
|
+
fn.maintainabilityIndex !== undefined &&
|
|
184
|
+
fn.maintainabilityIndex < miThreshold &&
|
|
185
|
+
fn.lengthLines > MIN_LOC_FOR_MI;
|
|
186
|
+
|
|
187
|
+
if (byStatements || byMI) {
|
|
188
|
+
const miNote =
|
|
189
|
+
byMI && fn.maintainabilityIndex !== undefined
|
|
190
|
+
? ` MI=${fn.maintainabilityIndex.toFixed(1)} (threshold: ${miThreshold}).`
|
|
191
|
+
: '';
|
|
192
|
+
const stmtNote = byStatements
|
|
193
|
+
? `${fn.statementCount} statements (threshold: ${stmtThreshold}).`
|
|
194
|
+
: '';
|
|
195
|
+
findings.push({
|
|
196
|
+
severity: 'high',
|
|
197
|
+
category: 'god-function',
|
|
198
|
+
file: entry.file,
|
|
199
|
+
lineStart: fn.lineStart,
|
|
200
|
+
lineEnd: fn.lineEnd,
|
|
201
|
+
title: `God function: ${fn.name}`,
|
|
202
|
+
reason: `Function "${fn.name}" triggers god-function detection. ${stmtNote}${miNote}`.trim(),
|
|
203
|
+
files: [`${entry.file}:${fn.lineStart}-${fn.lineEnd}`],
|
|
204
|
+
suggestedFix: {
|
|
205
|
+
strategy: 'Break down into smaller, focused functions.',
|
|
206
|
+
steps: [
|
|
207
|
+
'Identify logical steps within the function.',
|
|
208
|
+
'Extract each step into a named helper.',
|
|
209
|
+
'Keep the original as a high-level orchestrator.',
|
|
210
|
+
'Test each extracted function independently.',
|
|
211
|
+
],
|
|
212
|
+
},
|
|
213
|
+
impact: 'Improves readability, testability, and maintenance.',
|
|
214
|
+
tags: ['complexity', 'responsibility', 'size'],
|
|
215
|
+
lspHints: [
|
|
216
|
+
{
|
|
217
|
+
tool: 'lspCallHierarchy',
|
|
218
|
+
symbolName: fn.name,
|
|
219
|
+
lineHint: fn.lineStart,
|
|
220
|
+
file: entry.file,
|
|
221
|
+
expectedResult: `map callers and callees to identify safe extraction boundaries for ${fn.name}`,
|
|
222
|
+
},
|
|
223
|
+
],
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
return findings;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
export function detectLowCohesion(
|
|
233
|
+
dependencyState: DependencyState,
|
|
234
|
+
minExports: number = 3
|
|
235
|
+
): FindingDraft[] {
|
|
236
|
+
const findings: FindingDraft[] = [];
|
|
237
|
+
|
|
238
|
+
for (const file of dependencyState.files) {
|
|
239
|
+
if (isTestFile(file) || isLikelyEntrypoint(file)) continue;
|
|
240
|
+
|
|
241
|
+
const exports = dependencyState.declaredExportsByFile.get(file);
|
|
242
|
+
if (!exports || exports.length < minExports) continue;
|
|
243
|
+
|
|
244
|
+
const exportNames = new Set(exports.map(e => e.name));
|
|
245
|
+
|
|
246
|
+
const symbolConsumers = new Map<string, Set<string>>();
|
|
247
|
+
for (const [
|
|
248
|
+
consumer,
|
|
249
|
+
imports,
|
|
250
|
+
] of dependencyState.importedSymbolsByFile.entries()) {
|
|
251
|
+
for (const imp of imports) {
|
|
252
|
+
if (imp.resolvedModule !== file) continue;
|
|
253
|
+
if (!exportNames.has(imp.importedName)) continue;
|
|
254
|
+
if (!symbolConsumers.has(imp.importedName))
|
|
255
|
+
symbolConsumers.set(imp.importedName, new Set());
|
|
256
|
+
symbolConsumers.get(imp.importedName)!.add(consumer);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
const consumedSymbols = [...symbolConsumers.keys()];
|
|
261
|
+
if (consumedSymbols.length < 2) continue;
|
|
262
|
+
|
|
263
|
+
const adj = new Map<string, Set<string>>();
|
|
264
|
+
for (const sym of consumedSymbols) adj.set(sym, new Set());
|
|
265
|
+
|
|
266
|
+
for (const imports of dependencyState.importedSymbolsByFile.values()) {
|
|
267
|
+
const fromThisFile = imports
|
|
268
|
+
.filter(
|
|
269
|
+
i => i.resolvedModule === file && exportNames.has(i.importedName)
|
|
270
|
+
)
|
|
271
|
+
.map(i => i.importedName);
|
|
272
|
+
for (let i = 0; i < fromThisFile.length; i++) {
|
|
273
|
+
for (let j = i + 1; j < fromThisFile.length; j++) {
|
|
274
|
+
adj.get(fromThisFile[i])?.add(fromThisFile[j]);
|
|
275
|
+
adj.get(fromThisFile[j])?.add(fromThisFile[i]);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
const visited = new Set<string>();
|
|
281
|
+
let components = 0;
|
|
282
|
+
for (const sym of consumedSymbols) {
|
|
283
|
+
if (visited.has(sym)) continue;
|
|
284
|
+
components++;
|
|
285
|
+
const queue = [sym];
|
|
286
|
+
while (queue.length > 0) {
|
|
287
|
+
const curr = queue.pop()!;
|
|
288
|
+
if (visited.has(curr)) continue;
|
|
289
|
+
visited.add(curr);
|
|
290
|
+
for (const neighbor of adj.get(curr) || []) {
|
|
291
|
+
if (!visited.has(neighbor)) queue.push(neighbor);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
if (components > 1) {
|
|
297
|
+
findings.push({
|
|
298
|
+
severity: components >= 4 ? 'high' : 'medium',
|
|
299
|
+
category: 'low-cohesion',
|
|
300
|
+
file,
|
|
301
|
+
lineStart: 1,
|
|
302
|
+
lineEnd: 1,
|
|
303
|
+
title: `Low cohesion: ${file} (LCOM=${components})`,
|
|
304
|
+
reason: `Module exports ${consumedSymbols.length} consumed symbols that form ${components} independent groups. Consumers never import symbols across groups — the module serves unrelated purposes.`,
|
|
305
|
+
files: [file],
|
|
306
|
+
suggestedFix: {
|
|
307
|
+
strategy: `Split into ${components} focused modules, one per cohesion group.`,
|
|
308
|
+
steps: [
|
|
309
|
+
'Identify which exports belong to each independent group.',
|
|
310
|
+
'Create a new module for each group with a descriptive name.',
|
|
311
|
+
'Move exports and their dependencies to the appropriate module.',
|
|
312
|
+
'Update consumer imports to point to the new modules.',
|
|
313
|
+
],
|
|
314
|
+
},
|
|
315
|
+
impact:
|
|
316
|
+
'Higher cohesion = easier navigation, focused testing, and smaller change blast radius.',
|
|
317
|
+
tags: ['cohesion', 'responsibility', 'architecture'],
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
return findings;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
export function computeHotFiles(
|
|
326
|
+
dependencyState: DependencyState,
|
|
327
|
+
dependencySummary: DependencySummary,
|
|
328
|
+
fileCriticalityByPath: Map<string, FileCriticality>,
|
|
329
|
+
maxResults: number = 20
|
|
330
|
+
): HotFile[] {
|
|
331
|
+
const cycleFiles = new Set<string>();
|
|
332
|
+
for (const cycle of dependencySummary.cycles) {
|
|
333
|
+
for (const node of cycle.path) cycleFiles.add(node);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
const criticalPathFiles = new Set<string>();
|
|
337
|
+
for (const cp of dependencySummary.criticalPaths) {
|
|
338
|
+
for (const node of cp.path) criticalPathFiles.add(node);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
const results: HotFile[] = [];
|
|
342
|
+
for (const file of dependencyState.files) {
|
|
343
|
+
if (isTestFile(file)) continue;
|
|
344
|
+
|
|
345
|
+
const fanIn = (dependencyState.incoming.get(file) || new Set()).size;
|
|
346
|
+
const fanOut = (dependencyState.outgoing.get(file) || new Set()).size;
|
|
347
|
+
const crit = fileCriticalityByPath.get(file);
|
|
348
|
+
const complexityScore = crit?.score ?? 0;
|
|
349
|
+
const exportCount = (dependencyState.declaredExportsByFile.get(file) || [])
|
|
350
|
+
.length;
|
|
351
|
+
const inCycle = cycleFiles.has(file);
|
|
352
|
+
const onCriticalPath = criticalPathFiles.has(file);
|
|
353
|
+
|
|
354
|
+
const riskScore = Math.round(
|
|
355
|
+
fanIn * 3 +
|
|
356
|
+
complexityScore * 0.5 +
|
|
357
|
+
exportCount * 1.5 +
|
|
358
|
+
fanOut * 0.5 +
|
|
359
|
+
(inCycle ? 20 : 0) +
|
|
360
|
+
(onCriticalPath ? 10 : 0)
|
|
361
|
+
);
|
|
362
|
+
|
|
363
|
+
if (riskScore > 0) {
|
|
364
|
+
results.push({
|
|
365
|
+
file,
|
|
366
|
+
riskScore,
|
|
367
|
+
fanIn,
|
|
368
|
+
fanOut,
|
|
369
|
+
complexityScore,
|
|
370
|
+
exportCount,
|
|
371
|
+
inCycle,
|
|
372
|
+
onCriticalPath,
|
|
373
|
+
});
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
results.sort((a, b) => b.riskScore - a.riskScore);
|
|
378
|
+
return results.slice(0, maxResults);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
export function detectUntestedCriticalCode(
|
|
382
|
+
dependencyState: DependencyState,
|
|
383
|
+
hotFiles: HotFile[],
|
|
384
|
+
fileCriticalityByPath: Map<string, FileCriticality>,
|
|
385
|
+
criticalityScoreThreshold: number = 40
|
|
386
|
+
): FindingDraft[] {
|
|
387
|
+
const findings: FindingDraft[] = [];
|
|
388
|
+
const seen = new Set<string>();
|
|
389
|
+
|
|
390
|
+
const hasTestCoverage = (file: string): boolean => {
|
|
391
|
+
const testImporters = dependencyState.incomingFromTests.get(file);
|
|
392
|
+
return !!testImporters && testImporters.size > 0;
|
|
393
|
+
};
|
|
394
|
+
|
|
395
|
+
const addFinding = (
|
|
396
|
+
file: string,
|
|
397
|
+
riskScore: number,
|
|
398
|
+
reasons: string[]
|
|
399
|
+
): void => {
|
|
400
|
+
if (seen.has(file)) return;
|
|
401
|
+
seen.add(file);
|
|
402
|
+
if (isTestFile(file)) return;
|
|
403
|
+
if (hasTestCoverage(file)) return;
|
|
404
|
+
|
|
405
|
+
const isCritical = riskScore >= 60;
|
|
406
|
+
if (!canAddFinding(findings)) return;
|
|
407
|
+
findings.push({
|
|
408
|
+
severity: isCritical ? 'critical' : 'high',
|
|
409
|
+
category: 'untested-critical-code',
|
|
410
|
+
file,
|
|
411
|
+
lineStart: 1,
|
|
412
|
+
lineEnd: 1,
|
|
413
|
+
title: `Untested critical code: ${file}`,
|
|
414
|
+
reason: `High-risk file has no test imports. ${reasons.join('; ')} (risk score: ${riskScore}).`,
|
|
415
|
+
files: [file],
|
|
416
|
+
suggestedFix: {
|
|
417
|
+
strategy: 'Add test coverage for this critical module.',
|
|
418
|
+
steps: [
|
|
419
|
+
'Create a test file that imports and exercises the public API of this module.',
|
|
420
|
+
'Focus on the highest-complexity functions and exported behaviors first.',
|
|
421
|
+
'Add integration tests if this module sits on a critical dependency path.',
|
|
422
|
+
'Consider property-based tests for complex data transformations.',
|
|
423
|
+
],
|
|
424
|
+
},
|
|
425
|
+
impact:
|
|
426
|
+
'Untested critical code is the highest-risk area for regressions and undetected bugs.',
|
|
427
|
+
tags: ['testing', 'coverage', 'change-risk', 'critical'],
|
|
428
|
+
});
|
|
429
|
+
};
|
|
430
|
+
|
|
431
|
+
for (const hf of hotFiles) {
|
|
432
|
+
const reasons: string[] = [];
|
|
433
|
+
reasons.push(
|
|
434
|
+
`fan-in=${hf.fanIn}, fan-out=${hf.fanOut}, complexity=${hf.complexityScore}`
|
|
435
|
+
);
|
|
436
|
+
if (hf.inCycle) reasons.push('in dependency cycle');
|
|
437
|
+
if (hf.onCriticalPath) reasons.push('on critical dependency path');
|
|
438
|
+
addFinding(hf.file, hf.riskScore, reasons);
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
for (const [file, crit] of fileCriticalityByPath) {
|
|
442
|
+
if (crit.score < criticalityScoreThreshold) continue;
|
|
443
|
+
const reasons = [
|
|
444
|
+
`high complexity score (${crit.score}), ${crit.highComplexityFunctions} high-complexity functions`,
|
|
445
|
+
];
|
|
446
|
+
addFinding(file, crit.score, reasons);
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
findings.sort((a, b) => {
|
|
450
|
+
const sevOrder: Record<string, number> = {
|
|
451
|
+
critical: 4,
|
|
452
|
+
high: 3,
|
|
453
|
+
medium: 2,
|
|
454
|
+
low: 1,
|
|
455
|
+
info: 0,
|
|
456
|
+
};
|
|
457
|
+
return (sevOrder[b.severity] || 0) - (sevOrder[a.severity] || 0);
|
|
458
|
+
});
|
|
459
|
+
|
|
460
|
+
return findings.slice(0, 25);
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
export function detectFeatureEnvy(
|
|
464
|
+
dependencyState: DependencyState,
|
|
465
|
+
envyRatio: number = 0.6,
|
|
466
|
+
minSymbols: number = 5
|
|
467
|
+
): FindingDraft[] {
|
|
468
|
+
const findings: FindingDraft[] = [];
|
|
469
|
+
|
|
470
|
+
for (const [
|
|
471
|
+
file,
|
|
472
|
+
imports,
|
|
473
|
+
] of dependencyState.importedSymbolsByFile.entries()) {
|
|
474
|
+
if (isTestFile(file)) continue;
|
|
475
|
+
if (!dependencyState.files.has(file)) continue;
|
|
476
|
+
|
|
477
|
+
const internalImports = imports.filter(
|
|
478
|
+
i => i.resolvedModule && !i.isTypeOnly
|
|
479
|
+
);
|
|
480
|
+
if (internalImports.length < minSymbols) continue;
|
|
481
|
+
|
|
482
|
+
const countByTarget = new Map<string, number>();
|
|
483
|
+
for (const imp of internalImports) {
|
|
484
|
+
if (!imp.resolvedModule) continue;
|
|
485
|
+
countByTarget.set(
|
|
486
|
+
imp.resolvedModule,
|
|
487
|
+
(countByTarget.get(imp.resolvedModule) || 0) + 1
|
|
488
|
+
);
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
for (const [target, count] of countByTarget) {
|
|
492
|
+
const ratio = count / internalImports.length;
|
|
493
|
+
if (ratio >= envyRatio && count >= minSymbols) {
|
|
494
|
+
const importRef = findImportLine(dependencyState, file, target);
|
|
495
|
+
findings.push({
|
|
496
|
+
severity: ratio > 0.8 ? 'high' : 'medium',
|
|
497
|
+
category: 'feature-envy',
|
|
498
|
+
file,
|
|
499
|
+
lineStart: importRef.lineStart,
|
|
500
|
+
lineEnd: importRef.lineEnd,
|
|
501
|
+
title: `Feature envy: ${file} → ${target}`,
|
|
502
|
+
reason: `Module imports ${count}/${internalImports.length} symbols (${(ratio * 100).toFixed(0)}%) from "${target}". This suggests the logic may belong in or closer to the target module.`,
|
|
503
|
+
files: [file, target],
|
|
504
|
+
suggestedFix: {
|
|
505
|
+
strategy:
|
|
506
|
+
'Move dependent logic to the target module or extract a shared module.',
|
|
507
|
+
steps: [
|
|
508
|
+
'Identify which functions/logic in this file use the imported symbols.',
|
|
509
|
+
'Move that logic to the target module if it belongs there.',
|
|
510
|
+
'If shared, extract a dedicated module that both can import from.',
|
|
511
|
+
'Reduce the import surface by passing data instead of importing behaviors.',
|
|
512
|
+
],
|
|
513
|
+
},
|
|
514
|
+
impact:
|
|
515
|
+
'Misplaced logic increases coupling and makes changes ripple across module boundaries.',
|
|
516
|
+
tags: ['coupling', 'responsibility', 'misplaced-logic'],
|
|
517
|
+
lspHints: [
|
|
518
|
+
{
|
|
519
|
+
tool: 'lspCallHierarchy',
|
|
520
|
+
symbolName: file.split('/').pop() || file,
|
|
521
|
+
lineHint: importRef.lineStart,
|
|
522
|
+
file,
|
|
523
|
+
expectedResult: `trace which functions use imports from ${target} to decide what to move`,
|
|
524
|
+
},
|
|
525
|
+
{
|
|
526
|
+
tool: 'lspGotoDefinition',
|
|
527
|
+
symbolName: target.split('/').pop() || target,
|
|
528
|
+
lineHint: importRef.lineStart,
|
|
529
|
+
file,
|
|
530
|
+
expectedResult: `inspect target module to evaluate if logic belongs there`,
|
|
531
|
+
},
|
|
532
|
+
],
|
|
533
|
+
});
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
return findings;
|
|
539
|
+
}
|