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,534 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
Chokepoint,
|
|
3
|
+
DependencyState,
|
|
4
|
+
DependencySummary,
|
|
5
|
+
FileCriticality,
|
|
6
|
+
FileEntry,
|
|
7
|
+
Finding,
|
|
8
|
+
PackageGraphNode,
|
|
9
|
+
PackageGraphSummary,
|
|
10
|
+
PackageHotspot,
|
|
11
|
+
SccCluster,
|
|
12
|
+
} from '../types/index.js';
|
|
13
|
+
|
|
14
|
+
type FindingDraft = Omit<Finding, 'id'>;
|
|
15
|
+
|
|
16
|
+
export interface GraphAnalyticsSummary {
|
|
17
|
+
sccClusters: SccCluster[];
|
|
18
|
+
chokepoints: Chokepoint[];
|
|
19
|
+
packageGraphSummary: PackageGraphSummary;
|
|
20
|
+
articulationPoints: string[];
|
|
21
|
+
bridgeEdges: Array<{ from: string; to: string }>;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function packageKeyForFile(file: string): string {
|
|
25
|
+
const normalized = file.replace(/\\/g, '/').replace(/^\.?\//, '');
|
|
26
|
+
const pkgMatch = normalized.match(/^packages\/([^/]+)/);
|
|
27
|
+
if (pkgMatch) return `packages/${pkgMatch[1]}`;
|
|
28
|
+
const [top] = normalized.split('/');
|
|
29
|
+
return top || '<root>';
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function computeSccClusters(
|
|
33
|
+
dependencyState: DependencyState
|
|
34
|
+
): SccCluster[] {
|
|
35
|
+
const nodes = [...dependencyState.files].sort();
|
|
36
|
+
const indexMap = new Map<string, number>();
|
|
37
|
+
const lowLink = new Map<string, number>();
|
|
38
|
+
const stack: string[] = [];
|
|
39
|
+
const inStack = new Set<string>();
|
|
40
|
+
let index = 0;
|
|
41
|
+
const clusters: SccCluster[] = [];
|
|
42
|
+
|
|
43
|
+
const strongConnect = (node: string): void => {
|
|
44
|
+
indexMap.set(node, index);
|
|
45
|
+
lowLink.set(node, index);
|
|
46
|
+
index += 1;
|
|
47
|
+
stack.push(node);
|
|
48
|
+
inStack.add(node);
|
|
49
|
+
|
|
50
|
+
const outgoing = dependencyState.outgoing.get(node) || new Set<string>();
|
|
51
|
+
for (const next of outgoing) {
|
|
52
|
+
if (!dependencyState.files.has(next)) continue;
|
|
53
|
+
if (!indexMap.has(next)) {
|
|
54
|
+
strongConnect(next);
|
|
55
|
+
lowLink.set(node, Math.min(lowLink.get(node)!, lowLink.get(next)!));
|
|
56
|
+
} else if (inStack.has(next)) {
|
|
57
|
+
lowLink.set(node, Math.min(lowLink.get(node)!, indexMap.get(next)!));
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (lowLink.get(node) !== indexMap.get(node)) return;
|
|
62
|
+
|
|
63
|
+
const component: string[] = [];
|
|
64
|
+
while (stack.length > 0) {
|
|
65
|
+
const current = stack.pop()!;
|
|
66
|
+
inStack.delete(current);
|
|
67
|
+
component.push(current);
|
|
68
|
+
if (current === node) break;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const hasSelfLoop = (dependencyState.outgoing.get(node) || new Set()).has(
|
|
72
|
+
node
|
|
73
|
+
);
|
|
74
|
+
if (component.length <= 1 && !hasSelfLoop) return;
|
|
75
|
+
|
|
76
|
+
const componentSet = new Set(component);
|
|
77
|
+
let edgeCount = 0;
|
|
78
|
+
let entryEdges = 0;
|
|
79
|
+
let exitEdges = 0;
|
|
80
|
+
for (const file of component) {
|
|
81
|
+
for (const dep of dependencyState.outgoing.get(file) ||
|
|
82
|
+
new Set<string>()) {
|
|
83
|
+
if (!dependencyState.files.has(dep)) continue;
|
|
84
|
+
if (componentSet.has(dep)) edgeCount += 1;
|
|
85
|
+
else exitEdges += 1;
|
|
86
|
+
}
|
|
87
|
+
for (const incoming of dependencyState.incoming.get(file) ||
|
|
88
|
+
new Set<string>()) {
|
|
89
|
+
if (!componentSet.has(incoming)) entryEdges += 1;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const hubFiles = [...component]
|
|
94
|
+
.sort((a, b) => {
|
|
95
|
+
const aScore =
|
|
96
|
+
(dependencyState.incoming.get(a) || new Set()).size * 2 +
|
|
97
|
+
(dependencyState.outgoing.get(a) || new Set()).size;
|
|
98
|
+
const bScore =
|
|
99
|
+
(dependencyState.incoming.get(b) || new Set()).size * 2 +
|
|
100
|
+
(dependencyState.outgoing.get(b) || new Set()).size;
|
|
101
|
+
return bScore - aScore;
|
|
102
|
+
})
|
|
103
|
+
.slice(0, 3);
|
|
104
|
+
|
|
105
|
+
clusters.push({
|
|
106
|
+
id: `scc-${clusters.length + 1}`,
|
|
107
|
+
files: component.sort(),
|
|
108
|
+
nodeCount: component.length,
|
|
109
|
+
edgeCount,
|
|
110
|
+
entryEdges,
|
|
111
|
+
exitEdges,
|
|
112
|
+
hubFiles,
|
|
113
|
+
});
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
for (const node of nodes) {
|
|
117
|
+
if (!indexMap.has(node)) strongConnect(node);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return clusters.sort(
|
|
121
|
+
(a, b) => b.nodeCount - a.nodeCount || b.edgeCount - a.edgeCount
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export function computePackageGraphSummary(
|
|
126
|
+
dependencyState: DependencyState
|
|
127
|
+
): PackageGraphSummary {
|
|
128
|
+
const nodeMap = new Map<string, PackageGraphNode>();
|
|
129
|
+
const edgeMap = new Map<string, number>();
|
|
130
|
+
|
|
131
|
+
for (const file of dependencyState.files) {
|
|
132
|
+
const pkg = packageKeyForFile(file);
|
|
133
|
+
if (!nodeMap.has(pkg))
|
|
134
|
+
nodeMap.set(pkg, {
|
|
135
|
+
package: pkg,
|
|
136
|
+
inbound: 0,
|
|
137
|
+
outbound: 0,
|
|
138
|
+
internalFiles: 0,
|
|
139
|
+
});
|
|
140
|
+
nodeMap.get(pkg)!.internalFiles += 1;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
for (const [from, outgoing] of dependencyState.outgoing.entries()) {
|
|
144
|
+
const fromPkg = packageKeyForFile(from);
|
|
145
|
+
for (const to of outgoing) {
|
|
146
|
+
if (!dependencyState.files.has(to)) continue;
|
|
147
|
+
const toPkg = packageKeyForFile(to);
|
|
148
|
+
if (fromPkg === toPkg) continue;
|
|
149
|
+
const key = `${fromPkg}=>${toPkg}`;
|
|
150
|
+
edgeMap.set(key, (edgeMap.get(key) || 0) + 1);
|
|
151
|
+
nodeMap.get(fromPkg)!.outbound += 1;
|
|
152
|
+
nodeMap.get(toPkg)!.inbound += 1;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const hotspots: PackageHotspot[] = [...edgeMap.entries()]
|
|
157
|
+
.map(([key, edges]) => {
|
|
158
|
+
const [from, to] = key.split('=>');
|
|
159
|
+
return { from, to, edges };
|
|
160
|
+
})
|
|
161
|
+
.sort((a, b) => b.edges - a.edges)
|
|
162
|
+
.slice(0, 20);
|
|
163
|
+
|
|
164
|
+
return {
|
|
165
|
+
packageCount: nodeMap.size,
|
|
166
|
+
edgeCount: [...edgeMap.values()].reduce((sum, count) => sum + count, 0),
|
|
167
|
+
packages: [...nodeMap.values()].sort(
|
|
168
|
+
(a, b) => b.inbound + b.outbound - (a.inbound + a.outbound)
|
|
169
|
+
),
|
|
170
|
+
hotspots,
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function computeArticulationAndBridges(dependencyState: DependencyState): {
|
|
175
|
+
articulationPoints: Set<string>;
|
|
176
|
+
bridgeEdges: Array<{ from: string; to: string }>;
|
|
177
|
+
} {
|
|
178
|
+
const nodes = [...dependencyState.files].sort();
|
|
179
|
+
const adjacency = new Map<string, Set<string>>();
|
|
180
|
+
for (const node of nodes) adjacency.set(node, new Set());
|
|
181
|
+
for (const [from, outgoing] of dependencyState.outgoing.entries()) {
|
|
182
|
+
for (const to of outgoing) {
|
|
183
|
+
if (!dependencyState.files.has(to)) continue;
|
|
184
|
+
adjacency.get(from)!.add(to);
|
|
185
|
+
adjacency.get(to)!.add(from);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const visited = new Set<string>();
|
|
190
|
+
const disc = new Map<string, number>();
|
|
191
|
+
const low = new Map<string, number>();
|
|
192
|
+
const parent = new Map<string, string | null>();
|
|
193
|
+
const articulationPoints = new Set<string>();
|
|
194
|
+
const bridgeEdges: Array<{ from: string; to: string }> = [];
|
|
195
|
+
let time = 0;
|
|
196
|
+
|
|
197
|
+
const dfs = (node: string): void => {
|
|
198
|
+
visited.add(node);
|
|
199
|
+
disc.set(node, time);
|
|
200
|
+
low.set(node, time);
|
|
201
|
+
time += 1;
|
|
202
|
+
let children = 0;
|
|
203
|
+
|
|
204
|
+
for (const next of adjacency.get(node) || new Set<string>()) {
|
|
205
|
+
if (!visited.has(next)) {
|
|
206
|
+
children += 1;
|
|
207
|
+
parent.set(next, node);
|
|
208
|
+
dfs(next);
|
|
209
|
+
low.set(node, Math.min(low.get(node)!, low.get(next)!));
|
|
210
|
+
|
|
211
|
+
if (parent.get(node) == null && children > 1)
|
|
212
|
+
articulationPoints.add(node);
|
|
213
|
+
if (parent.get(node) != null && low.get(next)! >= disc.get(node)!)
|
|
214
|
+
articulationPoints.add(node);
|
|
215
|
+
if (low.get(next)! > disc.get(node)!) {
|
|
216
|
+
bridgeEdges.push(
|
|
217
|
+
node < next ? { from: node, to: next } : { from: next, to: node }
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
} else if (next !== parent.get(node)) {
|
|
221
|
+
low.set(node, Math.min(low.get(node)!, disc.get(next)!));
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
for (const node of nodes) {
|
|
227
|
+
if (!visited.has(node)) {
|
|
228
|
+
parent.set(node, null);
|
|
229
|
+
dfs(node);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
return { articulationPoints, bridgeEdges };
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
export function computeChokepoints(
|
|
237
|
+
dependencyState: DependencyState,
|
|
238
|
+
dependencySummary: DependencySummary,
|
|
239
|
+
fileCriticalityByPath: Map<string, FileCriticality>,
|
|
240
|
+
sccClusters: SccCluster[]
|
|
241
|
+
): Chokepoint[] {
|
|
242
|
+
const { articulationPoints, bridgeEdges } =
|
|
243
|
+
computeArticulationAndBridges(dependencyState);
|
|
244
|
+
const criticalPathFiles = new Set<string>();
|
|
245
|
+
for (const chain of dependencySummary.criticalPaths || []) {
|
|
246
|
+
for (const file of chain.path) criticalPathFiles.add(file);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const cycleMembership = new Map<string, number>();
|
|
250
|
+
for (const cluster of sccClusters) {
|
|
251
|
+
for (const file of cluster.files) {
|
|
252
|
+
cycleMembership.set(file, (cycleMembership.get(file) || 0) + 1);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const bridgeCounts = new Map<string, number>();
|
|
257
|
+
for (const bridge of bridgeEdges) {
|
|
258
|
+
bridgeCounts.set(bridge.from, (bridgeCounts.get(bridge.from) || 0) + 1);
|
|
259
|
+
bridgeCounts.set(bridge.to, (bridgeCounts.get(bridge.to) || 0) + 1);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
return [...dependencyState.files]
|
|
263
|
+
.map(file => {
|
|
264
|
+
const fanIn = (dependencyState.incoming.get(file) || new Set()).size;
|
|
265
|
+
const fanOut = (dependencyState.outgoing.get(file) || new Set()).size;
|
|
266
|
+
const articulation = articulationPoints.has(file);
|
|
267
|
+
const bridgeCount = bridgeCounts.get(file) || 0;
|
|
268
|
+
const cycleClusterCount = cycleMembership.get(file) || 0;
|
|
269
|
+
const onCriticalPath = criticalPathFiles.has(file);
|
|
270
|
+
const criticality = fileCriticalityByPath.get(file)?.score || 0;
|
|
271
|
+
const score = Math.round(
|
|
272
|
+
fanIn * 3 +
|
|
273
|
+
fanOut * 1.2 +
|
|
274
|
+
criticality / 10 +
|
|
275
|
+
(articulation ? 12 : 0) +
|
|
276
|
+
bridgeCount * 4 +
|
|
277
|
+
cycleClusterCount * 6 +
|
|
278
|
+
(onCriticalPath ? 8 : 0)
|
|
279
|
+
);
|
|
280
|
+
const reasons: string[] = [];
|
|
281
|
+
if (fanIn >= 8) reasons.push(`high fan-in (${fanIn})`);
|
|
282
|
+
if (fanOut >= 6) reasons.push(`high fan-out (${fanOut})`);
|
|
283
|
+
if (articulation) reasons.push('articulation point');
|
|
284
|
+
if (bridgeCount > 0) reasons.push(`${bridgeCount} bridge edge(s)`);
|
|
285
|
+
if (cycleClusterCount > 0)
|
|
286
|
+
reasons.push(`in ${cycleClusterCount} cycle cluster(s)`);
|
|
287
|
+
if (onCriticalPath) reasons.push('on critical path');
|
|
288
|
+
if (criticality >= 20)
|
|
289
|
+
reasons.push(`high complexity risk (${criticality})`);
|
|
290
|
+
return {
|
|
291
|
+
file,
|
|
292
|
+
score,
|
|
293
|
+
reasons,
|
|
294
|
+
fanIn,
|
|
295
|
+
fanOut,
|
|
296
|
+
articulation,
|
|
297
|
+
bridgeCount,
|
|
298
|
+
cycleClusterCount,
|
|
299
|
+
onCriticalPath,
|
|
300
|
+
};
|
|
301
|
+
})
|
|
302
|
+
.filter(entry => entry.score > 0 && entry.reasons.length > 0)
|
|
303
|
+
.sort((a, b) => b.score - a.score)
|
|
304
|
+
.slice(0, 40);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
export function computeGraphAnalytics(
|
|
308
|
+
dependencyState: DependencyState,
|
|
309
|
+
dependencySummary: DependencySummary,
|
|
310
|
+
fileCriticalityByPath: Map<string, FileCriticality>
|
|
311
|
+
): GraphAnalyticsSummary {
|
|
312
|
+
const sccClusters = computeSccClusters(dependencyState);
|
|
313
|
+
const { articulationPoints, bridgeEdges } =
|
|
314
|
+
computeArticulationAndBridges(dependencyState);
|
|
315
|
+
const chokepoints = computeChokepoints(
|
|
316
|
+
dependencyState,
|
|
317
|
+
dependencySummary,
|
|
318
|
+
fileCriticalityByPath,
|
|
319
|
+
sccClusters
|
|
320
|
+
);
|
|
321
|
+
return {
|
|
322
|
+
sccClusters,
|
|
323
|
+
chokepoints,
|
|
324
|
+
packageGraphSummary: computePackageGraphSummary(dependencyState),
|
|
325
|
+
articulationPoints: [...articulationPoints].sort(),
|
|
326
|
+
bridgeEdges,
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
function findImportLine(
|
|
331
|
+
state: DependencyState,
|
|
332
|
+
fromFile: string,
|
|
333
|
+
toFile?: string
|
|
334
|
+
): { lineStart: number; lineEnd: number } {
|
|
335
|
+
const imports = state.importedSymbolsByFile.get(fromFile) || [];
|
|
336
|
+
for (const ref of imports) {
|
|
337
|
+
if (!toFile || ref.resolvedModule === toFile) {
|
|
338
|
+
return {
|
|
339
|
+
lineStart: ref.lineStart || 1,
|
|
340
|
+
lineEnd: ref.lineEnd || ref.lineStart || 1,
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
return { lineStart: 1, lineEnd: 1 };
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
export function buildAdvancedGraphFindings(
|
|
348
|
+
graphAnalytics: GraphAnalyticsSummary,
|
|
349
|
+
dependencyState: DependencyState,
|
|
350
|
+
fileSummaries: FileEntry[]
|
|
351
|
+
): FindingDraft[] {
|
|
352
|
+
const findings: FindingDraft[] = [];
|
|
353
|
+
|
|
354
|
+
for (const cluster of graphAnalytics.sccClusters.slice(0, 6)) {
|
|
355
|
+
if (cluster.nodeCount < 3) continue;
|
|
356
|
+
const anchor = cluster.hubFiles[0] || cluster.files[0];
|
|
357
|
+
const loc = findImportLine(dependencyState, anchor);
|
|
358
|
+
findings.push({
|
|
359
|
+
severity: cluster.nodeCount >= 5 ? 'high' : 'medium',
|
|
360
|
+
category: 'cycle-cluster',
|
|
361
|
+
file: anchor,
|
|
362
|
+
lineStart: loc.lineStart,
|
|
363
|
+
lineEnd: loc.lineEnd,
|
|
364
|
+
title: `Cycle cluster detected (${cluster.nodeCount} files)`,
|
|
365
|
+
reason: `Strongly connected cluster ${cluster.id} has ${cluster.nodeCount} files, ${cluster.entryEdges} entry edge(s), and ${cluster.exitEdges} exit edge(s).`,
|
|
366
|
+
files: cluster.files,
|
|
367
|
+
suggestedFix: {
|
|
368
|
+
strategy:
|
|
369
|
+
'Break the cluster at one of the hub files and move shared contracts lower.',
|
|
370
|
+
steps: [
|
|
371
|
+
'Inspect the hub files first.',
|
|
372
|
+
'Extract shared interfaces or types from the cluster.',
|
|
373
|
+
'Remove at least one high-traffic edge to split the SCC.',
|
|
374
|
+
],
|
|
375
|
+
},
|
|
376
|
+
impact:
|
|
377
|
+
'Large cycle clusters spread change risk across multiple files and hide the true architectural boundary.',
|
|
378
|
+
tags: ['architecture', 'cycle', 'graph', 'change-risk'],
|
|
379
|
+
ruleId: 'architecture.cycle-cluster',
|
|
380
|
+
confidence: 'high',
|
|
381
|
+
evidence: {
|
|
382
|
+
clusterId: cluster.id,
|
|
383
|
+
nodeCount: cluster.nodeCount,
|
|
384
|
+
entryEdges: cluster.entryEdges,
|
|
385
|
+
exitEdges: cluster.exitEdges,
|
|
386
|
+
hubFiles: cluster.hubFiles,
|
|
387
|
+
},
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
for (const point of graphAnalytics.chokepoints.slice(0, 8)) {
|
|
392
|
+
if (point.fanIn < 3 || point.fanOut < 3 || point.score < 18) continue;
|
|
393
|
+
const loc = findImportLine(dependencyState, point.file);
|
|
394
|
+
findings.push({
|
|
395
|
+
severity: point.articulation || point.score >= 30 ? 'high' : 'medium',
|
|
396
|
+
category: 'broker-module',
|
|
397
|
+
file: point.file,
|
|
398
|
+
lineStart: loc.lineStart,
|
|
399
|
+
lineEnd: loc.lineEnd,
|
|
400
|
+
title: `Broker module chokepoint: ${point.file}`,
|
|
401
|
+
reason: `Module concentrates dependency traffic (${point.reasons.join(', ')}).`,
|
|
402
|
+
files: [point.file],
|
|
403
|
+
suggestedFix: {
|
|
404
|
+
strategy:
|
|
405
|
+
'Split orchestration responsibilities and reduce fan-in/fan-out through narrower seams.',
|
|
406
|
+
steps: [
|
|
407
|
+
'Identify which consumers rely on this file for unrelated concerns.',
|
|
408
|
+
'Extract narrower APIs or introduce an internal facade.',
|
|
409
|
+
'Move side-effectful or persistence-specific logic into dedicated modules.',
|
|
410
|
+
],
|
|
411
|
+
},
|
|
412
|
+
impact:
|
|
413
|
+
'Broker modules silently become architecture bottlenecks — a small change cascades broadly.',
|
|
414
|
+
tags: ['architecture', 'graph', 'chokepoint', 'coupling'],
|
|
415
|
+
ruleId: 'architecture.broker-module',
|
|
416
|
+
confidence: point.articulation ? 'high' : 'medium',
|
|
417
|
+
evidence: {
|
|
418
|
+
score: point.score,
|
|
419
|
+
reasons: point.reasons,
|
|
420
|
+
fanIn: point.fanIn,
|
|
421
|
+
fanOut: point.fanOut,
|
|
422
|
+
},
|
|
423
|
+
});
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
for (const point of graphAnalytics.chokepoints
|
|
427
|
+
.filter(entry => entry.articulation)
|
|
428
|
+
.slice(0, 8)) {
|
|
429
|
+
const loc = findImportLine(dependencyState, point.file);
|
|
430
|
+
findings.push({
|
|
431
|
+
severity: point.bridgeCount >= 2 ? 'high' : 'medium',
|
|
432
|
+
category: 'bridge-module',
|
|
433
|
+
file: point.file,
|
|
434
|
+
lineStart: loc.lineStart,
|
|
435
|
+
lineEnd: loc.lineEnd,
|
|
436
|
+
title: `Bridge module detected: ${point.file}`,
|
|
437
|
+
reason: `Module acts as a graph articulation point with ${point.bridgeCount} bridge edge(s).`,
|
|
438
|
+
files: [point.file],
|
|
439
|
+
suggestedFix: {
|
|
440
|
+
strategy:
|
|
441
|
+
'Reduce the amount of architecture that depends on this single bridge module.',
|
|
442
|
+
steps: [
|
|
443
|
+
'Split unrelated responsibilities out of the bridge module.',
|
|
444
|
+
'Add lower-level contracts so adjacent subsystems do not all route through one file.',
|
|
445
|
+
'Prefer explicit package boundaries over central catch-all utilities.',
|
|
446
|
+
],
|
|
447
|
+
},
|
|
448
|
+
impact:
|
|
449
|
+
'Bridge modules are structurally brittle — they become the single point where subsystem changes collide.',
|
|
450
|
+
tags: ['architecture', 'graph', 'bridge', 'fragility'],
|
|
451
|
+
ruleId: 'architecture.bridge-module',
|
|
452
|
+
confidence: 'high',
|
|
453
|
+
evidence: {
|
|
454
|
+
score: point.score,
|
|
455
|
+
bridgeCount: point.bridgeCount,
|
|
456
|
+
reasons: point.reasons,
|
|
457
|
+
},
|
|
458
|
+
});
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
for (const hotspot of graphAnalytics.packageGraphSummary.hotspots.slice(
|
|
462
|
+
0,
|
|
463
|
+
5
|
|
464
|
+
)) {
|
|
465
|
+
if (hotspot.edges < 4) continue;
|
|
466
|
+
findings.push({
|
|
467
|
+
severity: hotspot.edges >= 8 ? 'high' : 'medium',
|
|
468
|
+
category: 'package-boundary-chatter',
|
|
469
|
+
file: hotspot.from,
|
|
470
|
+
lineStart: 1,
|
|
471
|
+
lineEnd: 1,
|
|
472
|
+
title: `Heavy package chatter: ${hotspot.from} -> ${hotspot.to}`,
|
|
473
|
+
reason: `Detected ${hotspot.edges} cross-package dependency edge(s) between these package groups.`,
|
|
474
|
+
files: [hotspot.from, hotspot.to],
|
|
475
|
+
suggestedFix: {
|
|
476
|
+
strategy:
|
|
477
|
+
'Reduce cross-package chatter by consolidating APIs or introducing a narrower shared contract.',
|
|
478
|
+
steps: [
|
|
479
|
+
'Map the symbols crossing this boundary most often.',
|
|
480
|
+
'Promote a smaller public API surface between the packages.',
|
|
481
|
+
'Move implementation detail imports behind a dedicated package boundary.',
|
|
482
|
+
],
|
|
483
|
+
},
|
|
484
|
+
impact:
|
|
485
|
+
'High package chatter is a sign of architectural erosion — packages stop behaving like isolated subsystems.',
|
|
486
|
+
tags: ['architecture', 'packages', 'boundary', 'graph'],
|
|
487
|
+
ruleId: 'architecture.package-boundary-chatter',
|
|
488
|
+
confidence: 'medium',
|
|
489
|
+
evidence: hotspot,
|
|
490
|
+
});
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
const chokepointMap = new Map(
|
|
494
|
+
graphAnalytics.chokepoints.map(entry => [entry.file, entry])
|
|
495
|
+
);
|
|
496
|
+
for (const entry of fileSummaries) {
|
|
497
|
+
const effects = entry.topLevelEffects || [];
|
|
498
|
+
if (effects.length === 0) continue;
|
|
499
|
+
const point = chokepointMap.get(entry.file);
|
|
500
|
+
if (!point || point.fanIn < 8 || point.score < 18) continue;
|
|
501
|
+
const first = effects[0];
|
|
502
|
+
findings.push({
|
|
503
|
+
severity: point.fanIn >= 20 ? 'high' : 'medium',
|
|
504
|
+
category: 'startup-risk-hub',
|
|
505
|
+
file: entry.file,
|
|
506
|
+
lineStart: first.lineStart,
|
|
507
|
+
lineEnd: first.lineEnd,
|
|
508
|
+
title: `Startup risk hub: ${entry.file}`,
|
|
509
|
+
reason: `Module performs ${effects.length} import-time effect(s) and also behaves as a chokepoint (${point.reasons.join(', ')}).`,
|
|
510
|
+
files: [entry.file],
|
|
511
|
+
suggestedFix: {
|
|
512
|
+
strategy:
|
|
513
|
+
'Move import-time work behind explicit initialization and reduce inbound dependency pressure.',
|
|
514
|
+
steps: [
|
|
515
|
+
'Extract side effects into init() or lazy code paths.',
|
|
516
|
+
'Avoid importing this module from broad utility or entrypoint chains.',
|
|
517
|
+
'Keep only declarations and light configuration at module scope.',
|
|
518
|
+
],
|
|
519
|
+
},
|
|
520
|
+
impact:
|
|
521
|
+
'Import-time side effects in a high fan-in hub create startup latency, hidden ordering bugs, and broad runtime blast radius.',
|
|
522
|
+
tags: ['architecture', 'startup', 'side-effects', 'graph'],
|
|
523
|
+
ruleId: 'architecture.startup-risk-hub',
|
|
524
|
+
confidence: 'high',
|
|
525
|
+
evidence: {
|
|
526
|
+
fanIn: point.fanIn,
|
|
527
|
+
chokepointScore: point.score,
|
|
528
|
+
topLevelEffects: effects.map(effect => effect.kind),
|
|
529
|
+
},
|
|
530
|
+
});
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
return findings;
|
|
534
|
+
}
|