octocode-cli 1.2.6 → 1.2.8
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 +85 -142
- package/out/octocode-cli.js +7063 -6934
- 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,323 @@
|
|
|
1
|
+
import { findImportLine } 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 { DependencyState } from '../types/index.js';
|
|
7
|
+
|
|
8
|
+
export function computeInstability(
|
|
9
|
+
inboundCount: number,
|
|
10
|
+
outboundCount: number
|
|
11
|
+
): number {
|
|
12
|
+
const total = inboundCount + outboundCount;
|
|
13
|
+
if (total === 0) return 0;
|
|
14
|
+
return outboundCount / total;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function detectSdpViolations(
|
|
18
|
+
dependencyState: DependencyState,
|
|
19
|
+
minDelta: number = 0.15,
|
|
20
|
+
maxSourceInstability: number = 0.6
|
|
21
|
+
): FindingDraft[] {
|
|
22
|
+
const findings: FindingDraft[] = [];
|
|
23
|
+
const cache = new Map<string, number>();
|
|
24
|
+
|
|
25
|
+
const getI = (file: string): number => {
|
|
26
|
+
if (cache.has(file)) return cache.get(file)!;
|
|
27
|
+
const ca = (dependencyState.incoming.get(file) || new Set()).size;
|
|
28
|
+
const ce = (dependencyState.outgoing.get(file) || new Set()).size;
|
|
29
|
+
const i = computeInstability(ca, ce);
|
|
30
|
+
cache.set(file, i);
|
|
31
|
+
return i;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
for (const file of dependencyState.files) {
|
|
35
|
+
if (isTestFile(file)) continue;
|
|
36
|
+
const deps = dependencyState.outgoing.get(file) || new Set();
|
|
37
|
+
const iSrc = getI(file);
|
|
38
|
+
|
|
39
|
+
for (const dep of deps) {
|
|
40
|
+
if (!dependencyState.files.has(dep) || isTestFile(dep)) continue;
|
|
41
|
+
const iTgt = getI(dep);
|
|
42
|
+
const delta = iTgt - iSrc;
|
|
43
|
+
|
|
44
|
+
if (delta > minDelta && iSrc < maxSourceInstability) {
|
|
45
|
+
const importRef = findImportLine(dependencyState, file, dep);
|
|
46
|
+
findings.push({
|
|
47
|
+
severity: delta > 0.3 ? 'high' : 'medium',
|
|
48
|
+
category: 'architecture-sdp-violation',
|
|
49
|
+
file,
|
|
50
|
+
lineStart: importRef.lineStart,
|
|
51
|
+
lineEnd: importRef.lineEnd,
|
|
52
|
+
title: `SDP violation: stable module depends on unstable module`,
|
|
53
|
+
reason: `"${file}" (I=${iSrc.toFixed(2)}) depends on "${dep}" (I=${iTgt.toFixed(2)}). Delta=${delta.toFixed(2)}.`,
|
|
54
|
+
files: [file, dep],
|
|
55
|
+
suggestedFix: {
|
|
56
|
+
strategy:
|
|
57
|
+
'Invert dependency via interface/abstraction or move shared code to a stable utility.',
|
|
58
|
+
steps: [
|
|
59
|
+
'Extract a stable interface that the stable module depends on.',
|
|
60
|
+
'Have the unstable module implement that interface.',
|
|
61
|
+
'Consider moving shared logic to a lower-instability utility module.',
|
|
62
|
+
],
|
|
63
|
+
},
|
|
64
|
+
impact:
|
|
65
|
+
'Prevents cascading instability and reduces change propagation risk.',
|
|
66
|
+
tags: ['stability', 'coupling', 'architecture', 'sdp'],
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return findings;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function detectHighCoupling(
|
|
76
|
+
dependencyState: DependencyState,
|
|
77
|
+
threshold: number = 15
|
|
78
|
+
): FindingDraft[] {
|
|
79
|
+
const findings: FindingDraft[] = [];
|
|
80
|
+
|
|
81
|
+
for (const file of dependencyState.files) {
|
|
82
|
+
if (isTestFile(file)) continue;
|
|
83
|
+
const ca = (dependencyState.incoming.get(file) || new Set()).size;
|
|
84
|
+
const ce = (dependencyState.outgoing.get(file) || new Set()).size;
|
|
85
|
+
const total = ca + ce;
|
|
86
|
+
|
|
87
|
+
if (total > threshold) {
|
|
88
|
+
findings.push({
|
|
89
|
+
severity: total > 25 ? 'high' : 'medium',
|
|
90
|
+
category: 'high-coupling',
|
|
91
|
+
file,
|
|
92
|
+
lineStart: 1,
|
|
93
|
+
lineEnd: 1,
|
|
94
|
+
title: `High coupling: ${file}`,
|
|
95
|
+
reason: `Module has ${total} total connections (Ca=${ca}, Ce=${ce}). Threshold: ${threshold}.`,
|
|
96
|
+
files: [file],
|
|
97
|
+
suggestedFix: {
|
|
98
|
+
strategy:
|
|
99
|
+
'Reduce coupling by extracting interfaces or splitting module responsibilities.',
|
|
100
|
+
steps: [
|
|
101
|
+
'Identify groups of related imports/dependents that can be isolated.',
|
|
102
|
+
'Extract focused sub-modules with single responsibilities.',
|
|
103
|
+
'Use dependency inversion to reduce direct coupling.',
|
|
104
|
+
],
|
|
105
|
+
},
|
|
106
|
+
impact:
|
|
107
|
+
'Lower coupling reduces change ripple effects and improves testability.',
|
|
108
|
+
tags: ['coupling', 'change-risk', 'architecture'],
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return findings;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export function detectGodModuleCoupling(
|
|
117
|
+
dependencyState: DependencyState,
|
|
118
|
+
fanInThreshold: number = 20,
|
|
119
|
+
fanOutThreshold: number = 15
|
|
120
|
+
): FindingDraft[] {
|
|
121
|
+
const findings: FindingDraft[] = [];
|
|
122
|
+
|
|
123
|
+
for (const file of dependencyState.files) {
|
|
124
|
+
if (isTestFile(file)) continue;
|
|
125
|
+
const fanIn = (dependencyState.incoming.get(file) || new Set()).size;
|
|
126
|
+
const fanOut = (dependencyState.outgoing.get(file) || new Set()).size;
|
|
127
|
+
|
|
128
|
+
if (fanIn > fanInThreshold) {
|
|
129
|
+
findings.push({
|
|
130
|
+
severity: fanIn > fanInThreshold * 1.5 ? 'high' : 'medium',
|
|
131
|
+
category: 'god-module-coupling',
|
|
132
|
+
file,
|
|
133
|
+
lineStart: 1,
|
|
134
|
+
lineEnd: 1,
|
|
135
|
+
title: `High fan-in bottleneck: ${file}`,
|
|
136
|
+
reason: `Module is depended on by ${fanIn} modules (threshold: ${fanInThreshold}). Changes ripple widely.`,
|
|
137
|
+
files: [file],
|
|
138
|
+
suggestedFix: {
|
|
139
|
+
strategy:
|
|
140
|
+
'Split this module into focused sub-modules to reduce blast radius.',
|
|
141
|
+
steps: [
|
|
142
|
+
'Identify distinct groups of consumers using different parts of this module.',
|
|
143
|
+
'Extract each group into a dedicated module.',
|
|
144
|
+
'Update import paths incrementally.',
|
|
145
|
+
],
|
|
146
|
+
},
|
|
147
|
+
impact:
|
|
148
|
+
'Reduces change blast radius and improves parallel development.',
|
|
149
|
+
tags: ['coupling', 'blast-radius', 'bottleneck'],
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (fanOut > fanOutThreshold) {
|
|
154
|
+
findings.push({
|
|
155
|
+
severity: fanOut > fanOutThreshold * 1.5 ? 'high' : 'medium',
|
|
156
|
+
category: 'god-module-coupling',
|
|
157
|
+
file,
|
|
158
|
+
lineStart: 1,
|
|
159
|
+
lineEnd: 1,
|
|
160
|
+
title: `High fan-out: ${file}`,
|
|
161
|
+
reason: `Module depends on ${fanOut} modules (threshold: ${fanOutThreshold}). It may violate single responsibility.`,
|
|
162
|
+
files: [file],
|
|
163
|
+
suggestedFix: {
|
|
164
|
+
strategy:
|
|
165
|
+
'Reduce dependencies by introducing facade or mediator patterns.',
|
|
166
|
+
steps: [
|
|
167
|
+
'Group related imports behind a single facade module.',
|
|
168
|
+
'Consider splitting this module by responsibility.',
|
|
169
|
+
'Use dependency injection to reduce direct coupling.',
|
|
170
|
+
],
|
|
171
|
+
},
|
|
172
|
+
impact:
|
|
173
|
+
'Cleaner architecture and easier testing through reduced dependencies.',
|
|
174
|
+
tags: ['coupling', 'responsibility', 'sprawl'],
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return findings;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export function detectLayerViolations(
|
|
183
|
+
dependencyState: DependencyState,
|
|
184
|
+
layerOrder: string[]
|
|
185
|
+
): FindingDraft[] {
|
|
186
|
+
if (layerOrder.length < 2) return [];
|
|
187
|
+
|
|
188
|
+
const findings: FindingDraft[] = [];
|
|
189
|
+
|
|
190
|
+
const getLayer = (file: string): number => {
|
|
191
|
+
for (let i = 0; i < layerOrder.length; i++) {
|
|
192
|
+
if (file.includes(layerOrder[i])) return i;
|
|
193
|
+
}
|
|
194
|
+
return -1;
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
for (const file of dependencyState.files) {
|
|
198
|
+
if (isTestFile(file)) continue;
|
|
199
|
+
const srcLayer = getLayer(file);
|
|
200
|
+
if (srcLayer === -1) continue;
|
|
201
|
+
|
|
202
|
+
for (const dep of dependencyState.outgoing.get(file) || new Set()) {
|
|
203
|
+
if (!dependencyState.files.has(dep) || isTestFile(dep)) continue;
|
|
204
|
+
const depLayer = getLayer(dep);
|
|
205
|
+
if (depLayer === -1) continue;
|
|
206
|
+
|
|
207
|
+
if (depLayer < srcLayer) {
|
|
208
|
+
const importRef = findImportLine(dependencyState, file, dep);
|
|
209
|
+
findings.push({
|
|
210
|
+
severity: 'high',
|
|
211
|
+
category: 'layer-violation',
|
|
212
|
+
file,
|
|
213
|
+
lineStart: importRef.lineStart,
|
|
214
|
+
lineEnd: importRef.lineEnd,
|
|
215
|
+
title: `Layer violation: ${layerOrder[srcLayer]} imports from ${layerOrder[depLayer]}`,
|
|
216
|
+
reason: `"${file}" (layer: ${layerOrder[srcLayer]}) imports "${dep}" (layer: ${layerOrder[depLayer]}). Layer order: ${layerOrder.join(' → ')}.`,
|
|
217
|
+
files: [file, dep],
|
|
218
|
+
suggestedFix: {
|
|
219
|
+
strategy:
|
|
220
|
+
'Respect layer boundaries by inverting the dependency or moving shared logic.',
|
|
221
|
+
steps: [
|
|
222
|
+
'Extract shared contracts to a lower layer that both can depend on.',
|
|
223
|
+
'Use dependency inversion: define an interface in the lower layer, implement in higher.',
|
|
224
|
+
'If the dependency is justified, reconsider your layer boundaries.',
|
|
225
|
+
],
|
|
226
|
+
},
|
|
227
|
+
impact:
|
|
228
|
+
'Prevents architectural erosion and keeps dependency flow unidirectional.',
|
|
229
|
+
tags: ['architecture', 'layering', 'coupling'],
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
return findings;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
export function computeAbstractness(
|
|
239
|
+
exports: { name: string; kind: string }[]
|
|
240
|
+
): number {
|
|
241
|
+
if (exports.length === 0) return 0;
|
|
242
|
+
const abstractCount = exports.filter(e => e.kind === 'type').length;
|
|
243
|
+
return abstractCount / exports.length;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
export function detectDistanceFromMainSequence(
|
|
247
|
+
dependencyState: DependencyState,
|
|
248
|
+
distanceThreshold: number = 0.7,
|
|
249
|
+
minCoupling: number = 3
|
|
250
|
+
): FindingDraft[] {
|
|
251
|
+
const findings: FindingDraft[] = [];
|
|
252
|
+
|
|
253
|
+
for (const file of dependencyState.files) {
|
|
254
|
+
if (isTestFile(file)) continue;
|
|
255
|
+
|
|
256
|
+
const exports = dependencyState.declaredExportsByFile.get(file);
|
|
257
|
+
if (!exports || exports.length === 0) continue;
|
|
258
|
+
|
|
259
|
+
const ca = (dependencyState.incoming.get(file) || new Set()).size;
|
|
260
|
+
const ce = (dependencyState.outgoing.get(file) || new Set()).size;
|
|
261
|
+
if (ca + ce < minCoupling) continue;
|
|
262
|
+
|
|
263
|
+
const I = computeInstability(ca, ce);
|
|
264
|
+
const A = computeAbstractness(exports);
|
|
265
|
+
const D = Math.abs(A + I - 1);
|
|
266
|
+
|
|
267
|
+
if (D < distanceThreshold) continue;
|
|
268
|
+
|
|
269
|
+
const isZoneOfPain = A < 0.2 && I < 0.3;
|
|
270
|
+
const isZoneOfUselessness = A > 0.7 && I > 0.7;
|
|
271
|
+
|
|
272
|
+
let zone = '';
|
|
273
|
+
if (isZoneOfPain)
|
|
274
|
+
zone =
|
|
275
|
+
'Zone of Pain (concrete + stable): hard to extend, painful to change.';
|
|
276
|
+
else if (isZoneOfUselessness)
|
|
277
|
+
zone =
|
|
278
|
+
'Zone of Uselessness (abstract + unstable): over-abstracted and unused.';
|
|
279
|
+
else
|
|
280
|
+
zone = `Far from Main Sequence: balance between abstraction and stability is off.`;
|
|
281
|
+
|
|
282
|
+
if (!canAddFinding(findings)) break;
|
|
283
|
+
findings.push({
|
|
284
|
+
severity: D > 0.85 ? 'high' : 'medium',
|
|
285
|
+
category: 'distance-from-main-sequence',
|
|
286
|
+
file,
|
|
287
|
+
lineStart: 1,
|
|
288
|
+
lineEnd: 1,
|
|
289
|
+
title: `Distance from Main Sequence: ${file} (D=${D.toFixed(2)})`,
|
|
290
|
+
reason: `${zone} A=${A.toFixed(2)}, I=${I.toFixed(2)}, D=${D.toFixed(2)} (threshold: ${distanceThreshold}).`,
|
|
291
|
+
files: [file],
|
|
292
|
+
suggestedFix: {
|
|
293
|
+
strategy: isZoneOfPain
|
|
294
|
+
? 'Add abstractions (interfaces/types) or reduce inbound coupling.'
|
|
295
|
+
: isZoneOfUselessness
|
|
296
|
+
? 'Add concrete implementations or remove unused abstractions.'
|
|
297
|
+
: 'Rebalance by adjusting abstraction level or dependency direction.',
|
|
298
|
+
steps: isZoneOfPain
|
|
299
|
+
? [
|
|
300
|
+
'Extract interfaces for key behaviors to increase abstractness.',
|
|
301
|
+
'Consider splitting into abstract contracts + concrete implementations.',
|
|
302
|
+
'Reduce inbound coupling by narrowing the public API surface.',
|
|
303
|
+
]
|
|
304
|
+
: isZoneOfUselessness
|
|
305
|
+
? [
|
|
306
|
+
'Verify abstractions have concrete implementations.',
|
|
307
|
+
'Remove unused interfaces/types that serve no consumer.',
|
|
308
|
+
'Consider consolidating with concrete modules.',
|
|
309
|
+
]
|
|
310
|
+
: [
|
|
311
|
+
'Review the balance between interfaces/types and concrete exports.',
|
|
312
|
+
'Adjust dependency direction to move closer to the Main Sequence.',
|
|
313
|
+
'Consider splitting responsibilities between abstract and concrete modules.',
|
|
314
|
+
],
|
|
315
|
+
},
|
|
316
|
+
impact:
|
|
317
|
+
'Modules on the Main Sequence (D≈0) have optimal balance between stability and extensibility.',
|
|
318
|
+
tags: ['architecture', 'stability', 'abstractness', 'sdp'],
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
return findings;
|
|
323
|
+
}
|
|
@@ -0,0 +1,349 @@
|
|
|
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
|
+
} from '../types/index.js';
|
|
10
|
+
|
|
11
|
+
export function detectTestOnlyModules(
|
|
12
|
+
dependencySummary: DependencySummary
|
|
13
|
+
): FindingDraft[] {
|
|
14
|
+
const findings: FindingDraft[] = [];
|
|
15
|
+
if (dependencySummary.testOnlyModules?.length === 0) return findings;
|
|
16
|
+
for (const file of (dependencySummary.testOnlyModules || []).slice(0, 25)) {
|
|
17
|
+
if (!canAddFinding(findings)) break;
|
|
18
|
+
findings.push({
|
|
19
|
+
severity: 'medium',
|
|
20
|
+
category: 'dependency-test-only',
|
|
21
|
+
file: file.file,
|
|
22
|
+
lineStart: file.lineStart || 1,
|
|
23
|
+
lineEnd: file.lineEnd || 1,
|
|
24
|
+
title: `Module imported only from tests: ${file.file}`,
|
|
25
|
+
reason:
|
|
26
|
+
'No production file imports this module, but tests do. Verify if this module belongs in test fixtures/helpers.',
|
|
27
|
+
files: [file.file],
|
|
28
|
+
suggestedFix: {
|
|
29
|
+
strategy:
|
|
30
|
+
'Move test-only utilities to test scope or make production usage explicit.',
|
|
31
|
+
steps: [
|
|
32
|
+
'Re-run import scanning after moving test-only modules to __tests__ or helper folders.',
|
|
33
|
+
'If this is shared production utility, add a non-test entrypoint/import.',
|
|
34
|
+
'Remove dead or stale production references and delete unused module if confirmed.',
|
|
35
|
+
],
|
|
36
|
+
},
|
|
37
|
+
impact:
|
|
38
|
+
'Reduces shipping of non-production-only modules and clarifies ownership boundaries.',
|
|
39
|
+
tags: ['testing', 'dead-code', 'dependency'],
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
return findings;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function detectDependencyCycles(
|
|
46
|
+
dependencySummary: DependencySummary,
|
|
47
|
+
dependencyState: DependencyState
|
|
48
|
+
): FindingDraft[] {
|
|
49
|
+
const findings: FindingDraft[] = [];
|
|
50
|
+
if (dependencySummary.cycles?.length === 0) return findings;
|
|
51
|
+
for (const cycle of (dependencySummary.cycles || []).slice(0, 15)) {
|
|
52
|
+
const cycleLine = findImportLine(
|
|
53
|
+
dependencyState,
|
|
54
|
+
cycle.path[0],
|
|
55
|
+
cycle.path[1]
|
|
56
|
+
);
|
|
57
|
+
if (!canAddFinding(findings)) break;
|
|
58
|
+
findings.push({
|
|
59
|
+
severity: 'high',
|
|
60
|
+
category: 'dependency-cycle',
|
|
61
|
+
file: cycle.path[0],
|
|
62
|
+
lineStart: cycleLine.lineStart,
|
|
63
|
+
lineEnd: cycleLine.lineEnd,
|
|
64
|
+
title: `Dependency cycle detected (${cycle.nodeCount} node cycle)`,
|
|
65
|
+
reason: `Import cycle exists across: ${cycle.path.join(' -> ')}`,
|
|
66
|
+
files: cycle.path,
|
|
67
|
+
suggestedFix: {
|
|
68
|
+
strategy:
|
|
69
|
+
'Break the cycle with a lower-level abstraction or interface module.',
|
|
70
|
+
steps: [
|
|
71
|
+
'Extract shared contracts/types to a dedicated contract/shared package.',
|
|
72
|
+
'Move implementation in one direction using dependency inversion.',
|
|
73
|
+
'Split stateful modules into protocol and runtime layers.',
|
|
74
|
+
],
|
|
75
|
+
},
|
|
76
|
+
impact:
|
|
77
|
+
'Cycles increase coupling and make incremental loading/debugging and refactors riskier.',
|
|
78
|
+
tags: ['cycle', 'coupling', 'dependency', 'change-risk'],
|
|
79
|
+
lspHints: [
|
|
80
|
+
{
|
|
81
|
+
tool: 'lspGotoDefinition',
|
|
82
|
+
symbolName: cycle.path[1],
|
|
83
|
+
lineHint: cycleLine.lineStart,
|
|
84
|
+
file: cycle.path[0],
|
|
85
|
+
expectedResult: `navigate to the import that creates the cycle edge`,
|
|
86
|
+
},
|
|
87
|
+
],
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
return findings;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function findChainHotspot(
|
|
94
|
+
chainPath: string[],
|
|
95
|
+
dependencyState: DependencyState
|
|
96
|
+
): { module: string; fanOut: number; fanIn: number } {
|
|
97
|
+
let best = { module: chainPath[0], fanOut: 0, fanIn: 0 };
|
|
98
|
+
for (const mod of chainPath) {
|
|
99
|
+
const fanOut = (dependencyState.outgoing.get(mod) || new Set()).size;
|
|
100
|
+
const fanIn = (dependencyState.incoming.get(mod) || new Set()).size;
|
|
101
|
+
if (fanOut > best.fanOut) {
|
|
102
|
+
best = { module: mod, fanOut, fanIn };
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return best;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export function mergeOverlappingChains(
|
|
109
|
+
findings: FindingDraft[],
|
|
110
|
+
overlapThreshold: number = 0.8
|
|
111
|
+
): FindingDraft[] {
|
|
112
|
+
if (findings.length <= 1) return findings;
|
|
113
|
+
|
|
114
|
+
const merged: FindingDraft[] = [];
|
|
115
|
+
const consumed = new Set<number>();
|
|
116
|
+
|
|
117
|
+
for (let i = 0; i < findings.length; i++) {
|
|
118
|
+
if (consumed.has(i)) continue;
|
|
119
|
+
const base = findings[i];
|
|
120
|
+
const baseSet = new Set(base.files);
|
|
121
|
+
const entryPoints = [base.file];
|
|
122
|
+
|
|
123
|
+
for (let j = i + 1; j < findings.length; j++) {
|
|
124
|
+
if (consumed.has(j)) continue;
|
|
125
|
+
const other = findings[j];
|
|
126
|
+
const otherSet = new Set(other.files);
|
|
127
|
+
const intersection = [...baseSet].filter(f => otherSet.has(f)).length;
|
|
128
|
+
const union = new Set([...baseSet, ...otherSet]).size;
|
|
129
|
+
const overlap = union > 0 ? intersection / union : 0;
|
|
130
|
+
|
|
131
|
+
if (overlap >= overlapThreshold) {
|
|
132
|
+
consumed.add(j);
|
|
133
|
+
entryPoints.push(other.file);
|
|
134
|
+
for (const f of other.files) baseSet.add(f);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (entryPoints.length > 1) {
|
|
139
|
+
const allFiles = [...baseSet];
|
|
140
|
+
merged.push({
|
|
141
|
+
...base,
|
|
142
|
+
title: `Critical dependency chain risk: ${allFiles.length} files (${entryPoints.length} entry points)`,
|
|
143
|
+
reason:
|
|
144
|
+
base.reason +
|
|
145
|
+
` Also reached from: ${entryPoints.slice(1).join(', ')}.`,
|
|
146
|
+
files: allFiles,
|
|
147
|
+
});
|
|
148
|
+
} else {
|
|
149
|
+
merged.push(base);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return merged;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export function detectCriticalPaths(
|
|
157
|
+
dependencySummary: DependencySummary,
|
|
158
|
+
dependencyState: DependencyState,
|
|
159
|
+
criticalComplexityThreshold: number
|
|
160
|
+
): FindingDraft[] {
|
|
161
|
+
const rawFindings: FindingDraft[] = [];
|
|
162
|
+
if (dependencySummary.criticalPaths?.length === 0) return rawFindings;
|
|
163
|
+
for (const pathEntry of (dependencySummary.criticalPaths || []).slice(
|
|
164
|
+
0,
|
|
165
|
+
10
|
|
166
|
+
)) {
|
|
167
|
+
if (pathEntry.score < criticalComplexityThreshold * 3) continue;
|
|
168
|
+
const chainLine = findImportLine(
|
|
169
|
+
dependencyState,
|
|
170
|
+
pathEntry.path[0],
|
|
171
|
+
pathEntry.path[1]
|
|
172
|
+
);
|
|
173
|
+
const hotspot = findChainHotspot(pathEntry.path, dependencyState);
|
|
174
|
+
rawFindings.push({
|
|
175
|
+
severity:
|
|
176
|
+
pathEntry.score >= criticalComplexityThreshold * 6
|
|
177
|
+
? 'critical'
|
|
178
|
+
: 'high',
|
|
179
|
+
category: 'dependency-critical-path',
|
|
180
|
+
file: pathEntry.path[0],
|
|
181
|
+
lineStart: chainLine.lineStart,
|
|
182
|
+
lineEnd: chainLine.lineEnd,
|
|
183
|
+
title: `Critical dependency chain risk: ${pathEntry.length} files`,
|
|
184
|
+
reason: `Potentially high-change surface: ${pathEntry.path.join(' -> ')} (${pathEntry.score} weight).`,
|
|
185
|
+
files: pathEntry.path,
|
|
186
|
+
suggestedFix: {
|
|
187
|
+
strategy: `Break chain at \`${hotspot.module}\` (fan-out: ${hotspot.fanOut}, fan-in: ${hotspot.fanIn}).`,
|
|
188
|
+
steps: [
|
|
189
|
+
`Extract interface from \`${hotspot.module}\` — it has ${hotspot.fanOut} outbound dependencies.`,
|
|
190
|
+
'Downstream modules depend on the interface, not the implementation.',
|
|
191
|
+
'This splits the chain into two independent segments.',
|
|
192
|
+
],
|
|
193
|
+
},
|
|
194
|
+
impact:
|
|
195
|
+
'Critical refactor opportunities; shorter chains reduce blast radius of change.',
|
|
196
|
+
tags: ['change-risk', 'dependency', 'blast-radius'],
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
return mergeOverlappingChains(rawFindings);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
export function detectDeadFiles(
|
|
203
|
+
dependencySummary: DependencySummary,
|
|
204
|
+
dependencyState: DependencyState
|
|
205
|
+
): FindingDraft[] {
|
|
206
|
+
const findings: FindingDraft[] = [];
|
|
207
|
+
for (const file of dependencySummary.roots || []) {
|
|
208
|
+
if (isTestFile(file)) continue;
|
|
209
|
+
if (isLikelyEntrypoint(file)) continue;
|
|
210
|
+
const incomingCount = (dependencyState.incoming.get(file) || new Set())
|
|
211
|
+
.size;
|
|
212
|
+
const outgoingCount = (dependencyState.outgoing.get(file) || new Set())
|
|
213
|
+
.size;
|
|
214
|
+
if (incomingCount !== 0) continue;
|
|
215
|
+
if (outgoingCount > 0) continue;
|
|
216
|
+
if (!canAddFinding(findings)) break;
|
|
217
|
+
findings.push({
|
|
218
|
+
severity: 'medium',
|
|
219
|
+
category: 'dead-file',
|
|
220
|
+
file,
|
|
221
|
+
lineStart: 1,
|
|
222
|
+
lineEnd: 1,
|
|
223
|
+
title: `Potential dead file: ${file}`,
|
|
224
|
+
reason:
|
|
225
|
+
'File has no inbound imports and no outbound dependencies. It may be stale or orphaned.',
|
|
226
|
+
files: [file],
|
|
227
|
+
suggestedFix: {
|
|
228
|
+
strategy: 'Validate ownership and remove if truly unused.',
|
|
229
|
+
steps: [
|
|
230
|
+
'Confirm the file is not an explicit runtime entrypoint.',
|
|
231
|
+
'Search runtime config/router/bootstrap references for this file path.',
|
|
232
|
+
'Delete file if confirmed dead and re-run scan.',
|
|
233
|
+
],
|
|
234
|
+
},
|
|
235
|
+
impact: 'Reduces dead surface area and maintenance overhead.',
|
|
236
|
+
tags: ['dead-code', 'cleanup', 'hygiene'],
|
|
237
|
+
lspHints: [
|
|
238
|
+
{
|
|
239
|
+
tool: 'lspFindReferences',
|
|
240
|
+
symbolName: file.split('/').pop() || file,
|
|
241
|
+
lineHint: 1,
|
|
242
|
+
file,
|
|
243
|
+
expectedResult: `confirm zero references exist before deletion`,
|
|
244
|
+
},
|
|
245
|
+
],
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
return findings;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
export function detectOrphanModules(
|
|
252
|
+
dependencyState: DependencyState
|
|
253
|
+
): FindingDraft[] {
|
|
254
|
+
const findings: FindingDraft[] = [];
|
|
255
|
+
|
|
256
|
+
for (const file of dependencyState.files) {
|
|
257
|
+
if (isTestFile(file)) continue;
|
|
258
|
+
if (isLikelyEntrypoint(file)) continue;
|
|
259
|
+
|
|
260
|
+
const ca = (dependencyState.incoming.get(file) || new Set()).size;
|
|
261
|
+
const ce = (dependencyState.outgoing.get(file) || new Set()).size;
|
|
262
|
+
|
|
263
|
+
if (ca === 0 && ce === 0) {
|
|
264
|
+
findings.push({
|
|
265
|
+
severity: 'medium',
|
|
266
|
+
category: 'orphan-module',
|
|
267
|
+
file,
|
|
268
|
+
lineStart: 1,
|
|
269
|
+
lineEnd: 1,
|
|
270
|
+
title: `Orphan module: ${file}`,
|
|
271
|
+
reason:
|
|
272
|
+
'Module has no inbound or outbound dependencies — completely disconnected from the module graph.',
|
|
273
|
+
files: [file],
|
|
274
|
+
suggestedFix: {
|
|
275
|
+
strategy: 'Delete if truly unused, or wire into module graph.',
|
|
276
|
+
steps: [
|
|
277
|
+
'Check if the file is a runtime entrypoint, route, or config.',
|
|
278
|
+
'If truly disconnected, delete and re-run scan.',
|
|
279
|
+
'If needed, add an explicit import from the appropriate parent module.',
|
|
280
|
+
],
|
|
281
|
+
},
|
|
282
|
+
impact: 'Removes dead surface area and clarifies module ownership.',
|
|
283
|
+
tags: ['dead-code', 'dependency', 'isolation'],
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
return findings;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
export function detectUnreachableModules(
|
|
292
|
+
dependencyState: DependencyState
|
|
293
|
+
): FindingDraft[] {
|
|
294
|
+
const findings: FindingDraft[] = [];
|
|
295
|
+
|
|
296
|
+
const entrypoints = new Set<string>();
|
|
297
|
+
for (const file of dependencyState.files) {
|
|
298
|
+
if (isLikelyEntrypoint(file)) entrypoints.add(file);
|
|
299
|
+
}
|
|
300
|
+
if (entrypoints.size === 0) {
|
|
301
|
+
for (const file of dependencyState.files) {
|
|
302
|
+
if ((dependencyState.incoming.get(file) || new Set()).size === 0) {
|
|
303
|
+
entrypoints.add(file);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
const reachable = new Set<string>();
|
|
309
|
+
const queue = [...entrypoints];
|
|
310
|
+
while (queue.length > 0) {
|
|
311
|
+
const current = queue.pop()!;
|
|
312
|
+
if (reachable.has(current)) continue;
|
|
313
|
+
reachable.add(current);
|
|
314
|
+
for (const dep of dependencyState.outgoing.get(current) || new Set()) {
|
|
315
|
+
if (dependencyState.files.has(dep) && !reachable.has(dep))
|
|
316
|
+
queue.push(dep);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
for (const file of dependencyState.files) {
|
|
321
|
+
if (isTestFile(file) || reachable.has(file) || isLikelyEntrypoint(file))
|
|
322
|
+
continue;
|
|
323
|
+
if (!canAddFinding(findings)) break;
|
|
324
|
+
findings.push({
|
|
325
|
+
severity: 'high',
|
|
326
|
+
category: 'unreachable-module',
|
|
327
|
+
file,
|
|
328
|
+
lineStart: 1,
|
|
329
|
+
lineEnd: 1,
|
|
330
|
+
title: `Unreachable module: ${file}`,
|
|
331
|
+
reason:
|
|
332
|
+
'Module is not reachable from any entrypoint via the import graph.',
|
|
333
|
+
files: [file],
|
|
334
|
+
suggestedFix: {
|
|
335
|
+
strategy: 'Verify reachability and remove if truly dead.',
|
|
336
|
+
steps: [
|
|
337
|
+
'Check if this module is loaded dynamically or via framework conventions.',
|
|
338
|
+
'Verify it is not registered as a route, plugin, or middleware.',
|
|
339
|
+
'If confirmed unreachable, delete and re-run scan.',
|
|
340
|
+
],
|
|
341
|
+
},
|
|
342
|
+
impact:
|
|
343
|
+
'Identifies potentially large sections of dead code missed by direct-import checks.',
|
|
344
|
+
tags: ['dead-code', 'dependency', 'reachability'],
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
return findings;
|
|
349
|
+
}
|