octocode-cli 1.2.8 → 1.2.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +42 -35
- package/out/octocode-cli.js +36 -11767
- package/package.json +36 -36
- package/skills/README.md +42 -114
- package/skills/{octocode-code-engineer → octocode-engineer}/.claude/settings.local.json +2 -1
- package/skills/octocode-engineer/README.md +99 -0
- package/skills/octocode-engineer/SKILL.md +499 -0
- package/skills/octocode-engineer/build.mjs +29 -0
- package/skills/{octocode-code-engineer → octocode-engineer}/eslint.config.mjs +3 -13
- package/skills/{octocode-code-engineer → octocode-engineer}/package.json +28 -27
- package/skills/octocode-engineer/references/ast-reference.md +166 -0
- package/skills/{octocode-code-engineer → octocode-engineer}/references/cli-reference.md +80 -6
- package/skills/octocode-engineer/references/externals.md +86 -0
- package/skills/{octocode-code-engineer → octocode-engineer}/references/output-files.md +46 -6
- package/skills/octocode-engineer/references/quality-indicators.md +202 -0
- package/skills/octocode-engineer/references/tool-workflows.md +298 -0
- package/skills/octocode-engineer/references/validation-playbooks.md +99 -0
- package/skills/octocode-engineer/scripts/ast/search.js +45 -0
- package/skills/octocode-engineer/scripts/ast/tree-search.js +27 -0
- package/skills/octocode-engineer/scripts/index.js +173 -0
- package/skills/octocode-engineer/scripts/run.js +179 -0
- package/skills/octocode-engineer/src/analysis/dependencies.ts +378 -0
- package/skills/{octocode-code-engineer → octocode-engineer}/src/analysis/discovery.test.ts +57 -0
- package/skills/{octocode-code-engineer → octocode-engineer}/src/analysis/discovery.ts +43 -0
- package/skills/{octocode-code-engineer → octocode-engineer}/src/ast/search.test.ts +113 -0
- package/skills/{octocode-code-engineer → octocode-engineer}/src/ast/search.ts +64 -1
- package/skills/{octocode-code-engineer → octocode-engineer}/src/ast/tree-sitter.test.ts +118 -2
- package/skills/{octocode-code-engineer → octocode-engineer}/src/ast/tree-sitter.ts +65 -3
- package/skills/{octocode-code-engineer → octocode-engineer}/src/ast/ts-analyzer.test.ts +281 -1
- package/skills/{octocode-code-engineer → octocode-engineer}/src/ast/ts-analyzer.ts +173 -3
- package/skills/{octocode-code-engineer → octocode-engineer}/src/collectors/security.test.ts +73 -0
- package/skills/{octocode-code-engineer → octocode-engineer}/src/collectors/security.ts +62 -4
- package/skills/octocode-engineer/src/detector-gating.test.ts +59 -0
- package/skills/{octocode-code-engineer → octocode-engineer}/src/detectors/code-quality.ts +342 -0
- package/skills/{octocode-code-engineer → octocode-engineer}/src/detectors/index.ts +8 -0
- package/skills/{octocode-code-engineer → octocode-engineer}/src/index.test.ts +565 -11
- package/skills/octocode-engineer/src/index.ts +468 -0
- package/skills/octocode-engineer/src/pipeline/affected.test.ts +147 -0
- package/skills/octocode-engineer/src/pipeline/affected.ts +68 -0
- package/skills/octocode-engineer/src/pipeline/baseline.test.ts +276 -0
- package/skills/octocode-engineer/src/pipeline/baseline.ts +76 -0
- package/skills/{octocode-code-engineer → octocode-engineer}/src/pipeline/cli.test.ts +300 -53
- package/skills/{octocode-code-engineer → octocode-engineer}/src/pipeline/cli.ts +180 -36
- package/skills/octocode-engineer/src/pipeline/config-loader.test.ts +264 -0
- package/skills/octocode-engineer/src/pipeline/config-loader.ts +109 -0
- package/skills/octocode-engineer/src/pipeline/create-options.ts +55 -0
- package/skills/octocode-engineer/src/pipeline/health-score.test.ts +65 -0
- package/skills/{octocode-code-engineer → octocode-engineer}/src/pipeline/main.ts +130 -17
- package/skills/octocode-engineer/src/pipeline/progress.ts +51 -0
- package/skills/octocode-engineer/src/pipeline/reporters.test.ts +155 -0
- package/skills/octocode-engineer/src/pipeline/reporters.ts +64 -0
- package/skills/octocode-engineer/src/reporting/graph-features.test.ts +279 -0
- package/skills/{octocode-code-engineer → octocode-engineer}/src/reporting/output-contract.test.ts +6 -0
- package/skills/octocode-engineer/src/reporting/summary-md.test.ts +1066 -0
- package/skills/octocode-engineer/src/reporting/summary-md.ts +1604 -0
- package/skills/{octocode-code-engineer → octocode-engineer}/src/reporting/writer.ts +136 -13
- package/skills/octocode-engineer/src/run.ts +78 -0
- package/skills/{octocode-code-engineer → octocode-engineer}/src/sanity.test.ts +1 -1
- package/skills/octocode-engineer/src/types/analysis.ts +25 -0
- package/skills/octocode-engineer/src/types/collectors.ts +134 -0
- package/skills/{octocode-code-engineer → octocode-engineer}/src/types/constants.ts +75 -41
- package/skills/octocode-engineer/src/types/core.ts +203 -0
- package/skills/octocode-engineer/src/types/dependency.ts +215 -0
- package/skills/octocode-engineer/src/types/file-entry.ts +108 -0
- package/skills/octocode-engineer/src/types/findings.ts +105 -0
- package/skills/{octocode-code-engineer → octocode-engineer}/src/types/index.ts +60 -30
- package/skills/octocode-engineer/src/types/tree-sitter.ts +38 -0
- package/skills/{octocode-code-engineer → octocode-engineer}/tsconfig.json +1 -0
- package/skills/octocode-research/.octocode/scan/.cache/analysis-cache.json +1 -0
- package/skills/octocode-research/.octocode/scan/2026-03-22T10-32-27-073Z/architecture.json +1 -0
- package/skills/octocode-research/.octocode/scan/2026-03-22T10-32-27-073Z/ast-trees.txt +5566 -0
- package/skills/octocode-research/.octocode/scan/2026-03-22T10-32-27-073Z/code-quality.json +1 -0
- package/skills/octocode-research/.octocode/scan/2026-03-22T10-32-27-073Z/dead-code.json +1 -0
- package/skills/octocode-research/.octocode/scan/2026-03-22T10-32-27-073Z/file-inventory.json +1 -0
- package/skills/octocode-research/.octocode/scan/2026-03-22T10-32-27-073Z/findings.json +1 -0
- package/skills/octocode-research/.octocode/scan/2026-03-22T10-32-27-073Z/graph.md +189 -0
- package/skills/octocode-research/.octocode/scan/2026-03-22T10-32-27-073Z/security.json +1 -0
- package/skills/octocode-research/.octocode/scan/2026-03-22T10-32-27-073Z/summary.json +1 -0
- package/skills/octocode-research/.octocode/scan/2026-03-22T10-32-27-073Z/summary.md +265 -0
- package/skills/octocode-research/.octocode/scan/2026-03-22T10-40-10-469Z/architecture.json +1 -0
- package/skills/octocode-research/.octocode/scan/2026-03-22T10-40-10-469Z/ast-trees.txt +5555 -0
- package/skills/octocode-research/.octocode/scan/2026-03-22T10-40-10-469Z/code-quality.json +1 -0
- package/skills/octocode-research/.octocode/scan/2026-03-22T10-40-10-469Z/dead-code.json +1 -0
- package/skills/octocode-research/.octocode/scan/2026-03-22T10-40-10-469Z/file-inventory.json +1 -0
- package/skills/octocode-research/.octocode/scan/2026-03-22T10-40-10-469Z/findings.json +1 -0
- package/skills/octocode-research/.octocode/scan/2026-03-22T10-40-10-469Z/graph.md +190 -0
- package/skills/octocode-research/.octocode/scan/2026-03-22T10-40-10-469Z/security.json +1 -0
- package/skills/octocode-research/.octocode/scan/2026-03-22T10-40-10-469Z/summary.json +1 -0
- package/skills/octocode-research/.octocode/scan/2026-03-22T10-40-10-469Z/summary.md +265 -0
- package/skills/octocode-research/CHANGELOG.md +60 -0
- package/skills/octocode-research/README.md +102 -388
- package/skills/octocode-research/SKILL.md +169 -498
- package/skills/octocode-research/package.json +19 -31
- package/skills/octocode-research/references/PARALLEL_AGENT_PROTOCOL.md +19 -0
- package/skills/octocode-research/references/SESSION_MANAGEMENT.md +38 -0
- package/skills/octocode-research/scripts/server-init.js +1 -1
- package/skills/octocode-research/scripts/server.d.ts +2 -1
- package/skills/octocode-research/scripts/server.js +329 -233
- package/skills/octocode-research/src/__tests__/integration/promptsRoutes.test.ts +180 -0
- package/skills/octocode-research/src/__tests__/integration/serverHttp.test.ts +221 -0
- package/skills/octocode-research/src/__tests__/integration/serverLifecycle.test.ts +194 -0
- package/skills/octocode-research/src/__tests__/integration/toolsRoutes.test.ts +501 -0
- package/skills/octocode-research/src/__tests__/unit/readiness.test.ts +61 -0
- package/skills/octocode-research/src/__tests__/unit/resilience.test.ts +192 -0
- package/skills/octocode-research/src/__tests__/unit/responseFactory.test.ts +172 -0
- package/skills/octocode-research/src/__tests__/unit/responseParser.test.ts +288 -0
- package/skills/octocode-research/src/__tests__/unit/schemas.test.ts +509 -0
- package/skills/octocode-research/src/index.ts +4 -124
- package/skills/octocode-research/src/middleware/queryParser.ts +0 -26
- package/skills/octocode-research/src/routes/lsp.ts +58 -59
- package/skills/octocode-research/src/routes/package.ts +35 -65
- package/skills/octocode-research/src/routes/prompts.ts +3 -3
- package/skills/octocode-research/src/routes/tools.ts +8 -20
- package/skills/octocode-research/src/server-init.ts +30 -237
- package/skills/octocode-research/src/server.ts +50 -23
- package/skills/octocode-research/src/types/errorGuards.ts +9 -80
- package/skills/octocode-research/src/types/guards.ts +0 -28
- package/skills/octocode-research/src/types/mcp.ts +11 -66
- package/skills/octocode-research/src/types/responses.ts +11 -129
- package/skills/octocode-research/src/utils/circuitBreaker.ts +0 -21
- package/skills/octocode-research/src/utils/logger.ts +1 -97
- package/skills/octocode-research/src/utils/resilience.ts +2 -12
- package/skills/octocode-research/src/utils/responseFactory.ts +0 -42
- package/skills/octocode-research/src/utils/responseParser.ts +3 -25
- package/skills/octocode-research/src/utils/retry.ts +0 -63
- package/skills/octocode-research/src/utils/routeFactory.ts +1 -1
- package/skills/octocode-research/src/validation/httpPreprocess.ts +0 -3
- package/skills/octocode-research/src/validation/index.ts +0 -1
- package/skills/octocode-research/src/validation/schemas.ts +0 -63
- package/skills/octocode-research/src/validation/toolCallSchema.ts +3 -3
- package/skills/octocode-research/tsdown.config.ts +4 -0
- package/skills/octocode-research/vitest.config.ts +3 -0
- package/skills/octocode-code-engineer/.plan/VALIDATED_PLAN.md +0 -223
- package/skills/octocode-code-engineer/README.md +0 -178
- package/skills/octocode-code-engineer/SKILL.md +0 -418
- package/skills/octocode-code-engineer/minify-scripts.mjs +0 -32
- package/skills/octocode-code-engineer/references/agent-ast-reading-rfc.md +0 -95
- package/skills/octocode-code-engineer/references/architecture-techniques.md +0 -121
- package/skills/octocode-code-engineer/references/ast-search.md +0 -210
- package/skills/octocode-code-engineer/references/ast-tree-search.md +0 -151
- package/skills/octocode-code-engineer/references/concepts.md +0 -107
- package/skills/octocode-code-engineer/references/finding-categories.md +0 -128
- package/skills/octocode-code-engineer/references/improvement-roadmap.md +0 -304
- package/skills/octocode-code-engineer/references/playbooks.md +0 -204
- package/skills/octocode-code-engineer/references/present-results.md +0 -136
- package/skills/octocode-code-engineer/references/tool-workflows.md +0 -566
- package/skills/octocode-code-engineer/references/validate-investigate.md +0 -225
- package/skills/octocode-code-engineer/scripts/analysis/dependencies.js +0 -1
- package/skills/octocode-code-engineer/scripts/analysis/dependency-summary.js +0 -1
- package/skills/octocode-code-engineer/scripts/analysis/discovery.js +0 -1
- package/skills/octocode-code-engineer/scripts/analysis/graph-analytics.js +0 -1
- package/skills/octocode-code-engineer/scripts/analysis/semantic.js +0 -1
- package/skills/octocode-code-engineer/scripts/ast/helpers.js +0 -1
- package/skills/octocode-code-engineer/scripts/ast/metrics.js +0 -1
- package/skills/octocode-code-engineer/scripts/ast/search.js +0 -2
- package/skills/octocode-code-engineer/scripts/ast/tree-search.js +0 -2
- package/skills/octocode-code-engineer/scripts/ast/tree-sitter.js +0 -1
- package/skills/octocode-code-engineer/scripts/ast/ts-analyzer.js +0 -1
- package/skills/octocode-code-engineer/scripts/collectors/chains.js +0 -1
- package/skills/octocode-code-engineer/scripts/collectors/effects.js +0 -1
- package/skills/octocode-code-engineer/scripts/collectors/input-sources.js +0 -1
- package/skills/octocode-code-engineer/scripts/collectors/performance.js +0 -1
- package/skills/octocode-code-engineer/scripts/collectors/prototype-pollution.js +0 -1
- package/skills/octocode-code-engineer/scripts/collectors/security.js +0 -1
- package/skills/octocode-code-engineer/scripts/collectors/test-profile.js +0 -1
- package/skills/octocode-code-engineer/scripts/common/is-direct-run.js +0 -1
- package/skills/octocode-code-engineer/scripts/common/utils.js +0 -1
- package/skills/octocode-code-engineer/scripts/detectors/code-quality.js +0 -1
- package/skills/octocode-code-engineer/scripts/detectors/cohesion.js +0 -1
- package/skills/octocode-code-engineer/scripts/detectors/coupling.js +0 -1
- package/skills/octocode-code-engineer/scripts/detectors/cycle.js +0 -1
- package/skills/octocode-code-engineer/scripts/detectors/dead-code.js +0 -1
- package/skills/octocode-code-engineer/scripts/detectors/import-style.js +0 -1
- package/skills/octocode-code-engineer/scripts/detectors/index.js +0 -1
- package/skills/octocode-code-engineer/scripts/detectors/security.js +0 -1
- package/skills/octocode-code-engineer/scripts/detectors/semantic.js +0 -1
- package/skills/octocode-code-engineer/scripts/detectors/shared.js +0 -1
- package/skills/octocode-code-engineer/scripts/detectors/test-quality.js +0 -1
- package/skills/octocode-code-engineer/scripts/index.js +0 -1
- package/skills/octocode-code-engineer/scripts/pipeline/cache.js +0 -1
- package/skills/octocode-code-engineer/scripts/pipeline/cli.js +0 -1
- package/skills/octocode-code-engineer/scripts/pipeline/main.js +0 -2
- package/skills/octocode-code-engineer/scripts/reporting/analysis.js +0 -1
- package/skills/octocode-code-engineer/scripts/reporting/summary-md.js +0 -1
- package/skills/octocode-code-engineer/scripts/reporting/writer.js +0 -1
- package/skills/octocode-code-engineer/scripts/types/constants.js +0 -1
- package/skills/octocode-code-engineer/scripts/types/index.js +0 -1
- package/skills/octocode-code-engineer/scripts/types/interfaces.js +0 -1
- package/skills/octocode-code-engineer/src/analysis/dependencies.ts +0 -406
- package/skills/octocode-code-engineer/src/index.ts +0 -403
- package/skills/octocode-code-engineer/src/reporting/summary-md.test.ts +0 -421
- package/skills/octocode-code-engineer/src/reporting/summary-md.ts +0 -714
- package/skills/octocode-code-engineer/src/types/interfaces.ts +0 -682
- package/skills/octocode-research/src/types/toolTypes.ts +0 -33
- package/skills/octocode-research/src/utils/logEmoji.ts +0 -103
- /package/skills/{octocode-code-engineer → octocode-engineer}/.octocode/rfc/RFC-code-engineer-weakness-fixes.md +0 -0
- /package/skills/{octocode-code-engineer → octocode-engineer}/coverage/architecture.ts.html +0 -0
- /package/skills/{octocode-code-engineer → octocode-engineer}/coverage/ast-helpers.ts.html +0 -0
- /package/skills/{octocode-code-engineer → octocode-engineer}/coverage/ast-search.ts.html +0 -0
- /package/skills/{octocode-code-engineer → octocode-engineer}/coverage/base.css +0 -0
- /package/skills/{octocode-code-engineer → octocode-engineer}/coverage/block-navigation.js +0 -0
- /package/skills/{octocode-code-engineer → octocode-engineer}/coverage/cache.ts.html +0 -0
- /package/skills/{octocode-code-engineer → octocode-engineer}/coverage/cli.ts.html +0 -0
- /package/skills/{octocode-code-engineer → octocode-engineer}/coverage/clover.xml +0 -0
- /package/skills/{octocode-code-engineer → octocode-engineer}/coverage/collect-effects.ts.html +0 -0
- /package/skills/{octocode-code-engineer → octocode-engineer}/coverage/collect-input-sources.ts.html +0 -0
- /package/skills/{octocode-code-engineer → octocode-engineer}/coverage/collect-performance.ts.html +0 -0
- /package/skills/{octocode-code-engineer → octocode-engineer}/coverage/collect-prototype-pollution.ts.html +0 -0
- /package/skills/{octocode-code-engineer → octocode-engineer}/coverage/collect-security.ts.html +0 -0
- /package/skills/{octocode-code-engineer → octocode-engineer}/coverage/collect-test-profile.ts.html +0 -0
- /package/skills/{octocode-code-engineer → octocode-engineer}/coverage/coverage-final.json +0 -0
- /package/skills/{octocode-code-engineer → octocode-engineer}/coverage/dependencies.ts.html +0 -0
- /package/skills/{octocode-code-engineer → octocode-engineer}/coverage/dependency-summary.ts.html +0 -0
- /package/skills/{octocode-code-engineer → octocode-engineer}/coverage/discovery.ts.html +0 -0
- /package/skills/{octocode-code-engineer → octocode-engineer}/coverage/favicon.png +0 -0
- /package/skills/{octocode-code-engineer → octocode-engineer}/coverage/graph-analytics.ts.html +0 -0
- /package/skills/{octocode-code-engineer → octocode-engineer}/coverage/index.html +0 -0
- /package/skills/{octocode-code-engineer → octocode-engineer}/coverage/index.ts.html +0 -0
- /package/skills/{octocode-code-engineer → octocode-engineer}/coverage/metrics.ts.html +0 -0
- /package/skills/{octocode-code-engineer → octocode-engineer}/coverage/pipeline.ts.html +0 -0
- /package/skills/{octocode-code-engineer → octocode-engineer}/coverage/prettify.css +0 -0
- /package/skills/{octocode-code-engineer → octocode-engineer}/coverage/prettify.js +0 -0
- /package/skills/{octocode-code-engineer → octocode-engineer}/coverage/report-analysis.ts.html +0 -0
- /package/skills/{octocode-code-engineer → octocode-engineer}/coverage/report-writer.ts.html +0 -0
- /package/skills/{octocode-code-engineer → octocode-engineer}/coverage/security-detectors.ts.html +0 -0
- /package/skills/{octocode-code-engineer → octocode-engineer}/coverage/semantic-detectors.ts.html +0 -0
- /package/skills/{octocode-code-engineer → octocode-engineer}/coverage/semantic.ts.html +0 -0
- /package/skills/{octocode-code-engineer → octocode-engineer}/coverage/sort-arrow-sprite.png +0 -0
- /package/skills/{octocode-code-engineer → octocode-engineer}/coverage/sorter.js +0 -0
- /package/skills/{octocode-code-engineer → octocode-engineer}/coverage/summary-md.ts.html +0 -0
- /package/skills/{octocode-code-engineer → octocode-engineer}/coverage/test-quality-detectors.ts.html +0 -0
- /package/skills/{octocode-code-engineer → octocode-engineer}/coverage/tree-sitter-analyzer.ts.html +0 -0
- /package/skills/{octocode-code-engineer → octocode-engineer}/coverage/ts-analyzer.ts.html +0 -0
- /package/skills/{octocode-code-engineer → octocode-engineer}/coverage/types.ts.html +0 -0
- /package/skills/{octocode-code-engineer → octocode-engineer}/coverage/utils.ts.html +0 -0
- /package/skills/{octocode-code-engineer → octocode-engineer}/src/analysis/dependencies.test.ts +0 -0
- /package/skills/{octocode-code-engineer → octocode-engineer}/src/analysis/dependency-summary.test.ts +0 -0
- /package/skills/{octocode-code-engineer → octocode-engineer}/src/analysis/dependency-summary.ts +0 -0
- /package/skills/{octocode-code-engineer → octocode-engineer}/src/analysis/graph-analytics.test.ts +0 -0
- /package/skills/{octocode-code-engineer → octocode-engineer}/src/analysis/graph-analytics.ts +0 -0
- /package/skills/{octocode-code-engineer → octocode-engineer}/src/analysis/semantic.test.ts +0 -0
- /package/skills/{octocode-code-engineer → octocode-engineer}/src/analysis/semantic.ts +0 -0
- /package/skills/{octocode-code-engineer → octocode-engineer}/src/ast/helpers.test.ts +0 -0
- /package/skills/{octocode-code-engineer → octocode-engineer}/src/ast/helpers.ts +0 -0
- /package/skills/{octocode-code-engineer → octocode-engineer}/src/ast/metrics.test.ts +0 -0
- /package/skills/{octocode-code-engineer → octocode-engineer}/src/ast/metrics.ts +0 -0
- /package/skills/{octocode-code-engineer → octocode-engineer}/src/ast/tree-search.test.ts +0 -0
- /package/skills/{octocode-code-engineer → octocode-engineer}/src/ast/tree-search.ts +0 -0
- /package/skills/{octocode-code-engineer → octocode-engineer}/src/collectors/chains.ts +0 -0
- /package/skills/{octocode-code-engineer → octocode-engineer}/src/collectors/effects.test.ts +0 -0
- /package/skills/{octocode-code-engineer → octocode-engineer}/src/collectors/effects.ts +0 -0
- /package/skills/{octocode-code-engineer → octocode-engineer}/src/collectors/input-sources.test.ts +0 -0
- /package/skills/{octocode-code-engineer → octocode-engineer}/src/collectors/input-sources.ts +0 -0
- /package/skills/{octocode-code-engineer → octocode-engineer}/src/collectors/performance.test.ts +0 -0
- /package/skills/{octocode-code-engineer → octocode-engineer}/src/collectors/performance.ts +0 -0
- /package/skills/{octocode-code-engineer → octocode-engineer}/src/collectors/prototype-pollution.test.ts +0 -0
- /package/skills/{octocode-code-engineer → octocode-engineer}/src/collectors/prototype-pollution.ts +0 -0
- /package/skills/{octocode-code-engineer → octocode-engineer}/src/collectors/test-profile.test.ts +0 -0
- /package/skills/{octocode-code-engineer → octocode-engineer}/src/collectors/test-profile.ts +0 -0
- /package/skills/{octocode-code-engineer → octocode-engineer}/src/common/is-direct-run.test.ts +0 -0
- /package/skills/{octocode-code-engineer → octocode-engineer}/src/common/is-direct-run.ts +0 -0
- /package/skills/{octocode-code-engineer → octocode-engineer}/src/common/utils.test.ts +0 -0
- /package/skills/{octocode-code-engineer → octocode-engineer}/src/common/utils.ts +0 -0
- /package/skills/{octocode-code-engineer → octocode-engineer}/src/detectors/cohesion.ts +0 -0
- /package/skills/{octocode-code-engineer → octocode-engineer}/src/detectors/coupling.ts +0 -0
- /package/skills/{octocode-code-engineer → octocode-engineer}/src/detectors/cycle.ts +0 -0
- /package/skills/{octocode-code-engineer → octocode-engineer}/src/detectors/dead-code.ts +0 -0
- /package/skills/{octocode-code-engineer → octocode-engineer}/src/detectors/import-style.ts +0 -0
- /package/skills/{octocode-code-engineer → octocode-engineer}/src/detectors/index.test.ts +0 -0
- /package/skills/{octocode-code-engineer → octocode-engineer}/src/detectors/security.test.ts +0 -0
- /package/skills/{octocode-code-engineer → octocode-engineer}/src/detectors/security.ts +0 -0
- /package/skills/{octocode-code-engineer → octocode-engineer}/src/detectors/semantic.ts +0 -0
- /package/skills/{octocode-code-engineer → octocode-engineer}/src/detectors/shared.ts +0 -0
- /package/skills/{octocode-code-engineer → octocode-engineer}/src/detectors/test-quality.test.ts +0 -0
- /package/skills/{octocode-code-engineer → octocode-engineer}/src/detectors/test-quality.ts +0 -0
- /package/skills/{octocode-code-engineer → octocode-engineer}/src/pipeline/cache.test.ts +0 -0
- /package/skills/{octocode-code-engineer → octocode-engineer}/src/pipeline/cache.ts +0 -0
- /package/skills/{octocode-code-engineer → octocode-engineer}/src/pipeline/main.test.ts +0 -0
- /package/skills/{octocode-code-engineer → octocode-engineer}/src/pipeline.test.ts +0 -0
- /package/skills/{octocode-code-engineer → octocode-engineer}/src/reporting/analysis.test.ts +0 -0
- /package/skills/{octocode-code-engineer → octocode-engineer}/src/reporting/analysis.ts +0 -0
- /package/skills/{octocode-code-engineer → octocode-engineer}/vitest.config.ts +0 -0
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{findImportLine}from"./shared.js";import{canAddFinding}from"./shared.js";import{isTestFile}from"../common/utils.js";export function computeInstability(e,t){const i=e+t;return 0===i?0:t/i}export function detectSdpViolations(e,t=.15,i=.6){const n=[],s=new Map,o=t=>{if(s.has(t))return s.get(t);const i=computeInstability((e.incoming.get(t)||new Set).size,(e.outgoing.get(t)||new Set).size);return s.set(t,i),i};for(const s of e.files){if(isTestFile(s))continue;const r=e.outgoing.get(s)||new Set,a=o(s);for(const c of r){if(!e.files.has(c)||isTestFile(c))continue;const r=o(c),l=r-a;if(l>t&&a<i){const t=findImportLine(e,s,c);n.push({severity:l>.3?"high":"medium",category:"architecture-sdp-violation",file:s,lineStart:t.lineStart,lineEnd:t.lineEnd,title:"SDP violation: stable module depends on unstable module",reason:`"${s}" (I=${a.toFixed(2)}) depends on "${c}" (I=${r.toFixed(2)}). Delta=${l.toFixed(2)}.`,files:[s,c],suggestedFix:{strategy:"Invert dependency via interface/abstraction or move shared code to a stable utility.",steps:["Extract a stable interface that the stable module depends on.","Have the unstable module implement that interface.","Consider moving shared logic to a lower-instability utility module."]},impact:"Prevents cascading instability and reduces change propagation risk.",tags:["stability","coupling","architecture","sdp"]})}}}return n}export function detectHighCoupling(e,t=15){const i=[];for(const n of e.files){if(isTestFile(n))continue;const s=(e.incoming.get(n)||new Set).size,o=(e.outgoing.get(n)||new Set).size,r=s+o;r>t&&i.push({severity:r>25?"high":"medium",category:"high-coupling",file:n,lineStart:1,lineEnd:1,title:`High coupling: ${n}`,reason:`Module has ${r} total connections (Ca=${s}, Ce=${o}). Threshold: ${t}.`,files:[n],suggestedFix:{strategy:"Reduce coupling by extracting interfaces or splitting module responsibilities.",steps:["Identify groups of related imports/dependents that can be isolated.","Extract focused sub-modules with single responsibilities.","Use dependency inversion to reduce direct coupling."]},impact:"Lower coupling reduces change ripple effects and improves testability.",tags:["coupling","change-risk","architecture"]})}return i}export function detectGodModuleCoupling(e,t=20,i=15){const n=[];for(const s of e.files){if(isTestFile(s))continue;const o=(e.incoming.get(s)||new Set).size,r=(e.outgoing.get(s)||new Set).size;o>t&&n.push({severity:o>1.5*t?"high":"medium",category:"god-module-coupling",file:s,lineStart:1,lineEnd:1,title:`High fan-in bottleneck: ${s}`,reason:`Module is depended on by ${o} modules (threshold: ${t}). Changes ripple widely.`,files:[s],suggestedFix:{strategy:"Split this module into focused sub-modules to reduce blast radius.",steps:["Identify distinct groups of consumers using different parts of this module.","Extract each group into a dedicated module.","Update import paths incrementally."]},impact:"Reduces change blast radius and improves parallel development.",tags:["coupling","blast-radius","bottleneck"]}),r>i&&n.push({severity:r>1.5*i?"high":"medium",category:"god-module-coupling",file:s,lineStart:1,lineEnd:1,title:`High fan-out: ${s}`,reason:`Module depends on ${r} modules (threshold: ${i}). It may violate single responsibility.`,files:[s],suggestedFix:{strategy:"Reduce dependencies by introducing facade or mediator patterns.",steps:["Group related imports behind a single facade module.","Consider splitting this module by responsibility.","Use dependency injection to reduce direct coupling."]},impact:"Cleaner architecture and easier testing through reduced dependencies.",tags:["coupling","responsibility","sprawl"]})}return n}export function detectLayerViolations(e,t){if(t.length<2)return[];const i=[],n=e=>{for(let i=0;i<t.length;i++)if(e.includes(t[i]))return i;return-1};for(const s of e.files){if(isTestFile(s))continue;const o=n(s);if(-1!==o)for(const r of e.outgoing.get(s)||new Set){if(!e.files.has(r)||isTestFile(r))continue;const a=n(r);if(-1!==a&&a<o){const n=findImportLine(e,s,r);i.push({severity:"high",category:"layer-violation",file:s,lineStart:n.lineStart,lineEnd:n.lineEnd,title:`Layer violation: ${t[o]} imports from ${t[a]}`,reason:`"${s}" (layer: ${t[o]}) imports "${r}" (layer: ${t[a]}). Layer order: ${t.join(" → ")}.`,files:[s,r],suggestedFix:{strategy:"Respect layer boundaries by inverting the dependency or moving shared logic.",steps:["Extract shared contracts to a lower layer that both can depend on.","Use dependency inversion: define an interface in the lower layer, implement in higher.","If the dependency is justified, reconsider your layer boundaries."]},impact:"Prevents architectural erosion and keeps dependency flow unidirectional.",tags:["architecture","layering","coupling"]})}}}return i}export function computeAbstractness(e){if(0===e.length)return 0;return e.filter(e=>"type"===e.kind).length/e.length}export function detectDistanceFromMainSequence(e,t=.7,i=3){const n=[];for(const s of e.files){if(isTestFile(s))continue;const o=e.declaredExportsByFile.get(s);if(!o||0===o.length)continue;const r=(e.incoming.get(s)||new Set).size,a=(e.outgoing.get(s)||new Set).size;if(r+a<i)continue;const c=computeInstability(r,a),l=computeAbstractness(o),d=Math.abs(l+c-1);if(d<t)continue;const u=l<.2&&c<.3,g=l>.7&&c>.7;let p="";if(p=u?"Zone of Pain (concrete + stable): hard to extend, painful to change.":g?"Zone of Uselessness (abstract + unstable): over-abstracted and unused.":"Far from Main Sequence: balance between abstraction and stability is off.",!canAddFinding(n))break;n.push({severity:d>.85?"high":"medium",category:"distance-from-main-sequence",file:s,lineStart:1,lineEnd:1,title:`Distance from Main Sequence: ${s} (D=${d.toFixed(2)})`,reason:`${p} A=${l.toFixed(2)}, I=${c.toFixed(2)}, D=${d.toFixed(2)} (threshold: ${t}).`,files:[s],suggestedFix:{strategy:u?"Add abstractions (interfaces/types) or reduce inbound coupling.":g?"Add concrete implementations or remove unused abstractions.":"Rebalance by adjusting abstraction level or dependency direction.",steps:u?["Extract interfaces for key behaviors to increase abstractness.","Consider splitting into abstract contracts + concrete implementations.","Reduce inbound coupling by narrowing the public API surface."]:g?["Verify abstractions have concrete implementations.","Remove unused interfaces/types that serve no consumer.","Consider consolidating with concrete modules."]:["Review the balance between interfaces/types and concrete exports.","Adjust dependency direction to move closer to the Main Sequence.","Consider splitting responsibilities between abstract and concrete modules."]},impact:"Modules on the Main Sequence (D≈0) have optimal balance between stability and extensibility.",tags:["architecture","stability","abstractness","sdp"]})}return n}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{findImportLine,isLikelyEntrypoint}from"./shared.js";import{canAddFinding}from"./shared.js";import{isTestFile}from"../common/utils.js";export function detectTestOnlyModules(e){const t=[];if(0===e.testOnlyModules?.length)return t;for(const n of(e.testOnlyModules||[]).slice(0,25)){if(!canAddFinding(t))break;t.push({severity:"medium",category:"dependency-test-only",file:n.file,lineStart:n.lineStart||1,lineEnd:n.lineEnd||1,title:`Module imported only from tests: ${n.file}`,reason:"No production file imports this module, but tests do. Verify if this module belongs in test fixtures/helpers.",files:[n.file],suggestedFix:{strategy:"Move test-only utilities to test scope or make production usage explicit.",steps:["Re-run import scanning after moving test-only modules to __tests__ or helper folders.","If this is shared production utility, add a non-test entrypoint/import.","Remove dead or stale production references and delete unused module if confirmed."]},impact:"Reduces shipping of non-production-only modules and clarifies ownership boundaries.",tags:["testing","dead-code","dependency"]})}return t}export function detectDependencyCycles(e,t){const n=[];if(0===e.cycles?.length)return n;for(const i of(e.cycles||[]).slice(0,15)){const e=findImportLine(t,i.path[0],i.path[1]);if(!canAddFinding(n))break;n.push({severity:"high",category:"dependency-cycle",file:i.path[0],lineStart:e.lineStart,lineEnd:e.lineEnd,title:`Dependency cycle detected (${i.nodeCount} node cycle)`,reason:`Import cycle exists across: ${i.path.join(" -> ")}`,files:i.path,suggestedFix:{strategy:"Break the cycle with a lower-level abstraction or interface module.",steps:["Extract shared contracts/types to a dedicated contract/shared package.","Move implementation in one direction using dependency inversion.","Split stateful modules into protocol and runtime layers."]},impact:"Cycles increase coupling and make incremental loading/debugging and refactors riskier.",tags:["cycle","coupling","dependency","change-risk"],lspHints:[{tool:"lspGotoDefinition",symbolName:i.path[1],lineHint:e.lineStart,file:i.path[0],expectedResult:"navigate to the import that creates the cycle edge"}]})}return n}function findChainHotspot(e,t){let n={module:e[0],fanOut:0,fanIn:0};for(const i of e){const e=(t.outgoing.get(i)||new Set).size,o=(t.incoming.get(i)||new Set).size;e>n.fanOut&&(n={module:i,fanOut:e,fanIn:o})}return n}export function mergeOverlappingChains(e,t=.8){if(e.length<=1)return e;const n=[],i=new Set;for(let o=0;o<e.length;o++){if(i.has(o))continue;const s=e[o],r=new Set(s.files),a=[s.file];for(let n=o+1;n<e.length;n++){if(i.has(n))continue;const o=e[n],s=new Set(o.files),d=[...r].filter(e=>s.has(e)).length,l=new Set([...r,...s]).size;if((l>0?d/l:0)>=t){i.add(n),a.push(o.file);for(const e of o.files)r.add(e)}}if(a.length>1){const e=[...r];n.push({...s,title:`Critical dependency chain risk: ${e.length} files (${a.length} entry points)`,reason:s.reason+` Also reached from: ${a.slice(1).join(", ")}.`,files:e})}else n.push(s)}return n}export function detectCriticalPaths(e,t,n){const i=[];if(0===e.criticalPaths?.length)return i;for(const o of(e.criticalPaths||[]).slice(0,10)){if(o.score<3*n)continue;const e=findImportLine(t,o.path[0],o.path[1]),s=findChainHotspot(o.path,t);i.push({severity:o.score>=6*n?"critical":"high",category:"dependency-critical-path",file:o.path[0],lineStart:e.lineStart,lineEnd:e.lineEnd,title:`Critical dependency chain risk: ${o.length} files`,reason:`Potentially high-change surface: ${o.path.join(" -> ")} (${o.score} weight).`,files:o.path,suggestedFix:{strategy:`Break chain at \`${s.module}\` (fan-out: ${s.fanOut}, fan-in: ${s.fanIn}).`,steps:[`Extract interface from \`${s.module}\` — it has ${s.fanOut} outbound dependencies.`,"Downstream modules depend on the interface, not the implementation.","This splits the chain into two independent segments."]},impact:"Critical refactor opportunities; shorter chains reduce blast radius of change.",tags:["change-risk","dependency","blast-radius"]})}return mergeOverlappingChains(i)}export function detectDeadFiles(e,t){const n=[];for(const i of e.roots||[]){if(isTestFile(i))continue;if(isLikelyEntrypoint(i))continue;const e=(t.incoming.get(i)||new Set).size,o=(t.outgoing.get(i)||new Set).size;if(0===e&&!(o>0)){if(!canAddFinding(n))break;n.push({severity:"medium",category:"dead-file",file:i,lineStart:1,lineEnd:1,title:`Potential dead file: ${i}`,reason:"File has no inbound imports and no outbound dependencies. It may be stale or orphaned.",files:[i],suggestedFix:{strategy:"Validate ownership and remove if truly unused.",steps:["Confirm the file is not an explicit runtime entrypoint.","Search runtime config/router/bootstrap references for this file path.","Delete file if confirmed dead and re-run scan."]},impact:"Reduces dead surface area and maintenance overhead.",tags:["dead-code","cleanup","hygiene"],lspHints:[{tool:"lspFindReferences",symbolName:i.split("/").pop()||i,lineHint:1,file:i,expectedResult:"confirm zero references exist before deletion"}]})}}return n}export function detectOrphanModules(e){const t=[];for(const n of e.files){if(isTestFile(n))continue;if(isLikelyEntrypoint(n))continue;const i=(e.incoming.get(n)||new Set).size,o=(e.outgoing.get(n)||new Set).size;0===i&&0===o&&t.push({severity:"medium",category:"orphan-module",file:n,lineStart:1,lineEnd:1,title:`Orphan module: ${n}`,reason:"Module has no inbound or outbound dependencies — completely disconnected from the module graph.",files:[n],suggestedFix:{strategy:"Delete if truly unused, or wire into module graph.",steps:["Check if the file is a runtime entrypoint, route, or config.","If truly disconnected, delete and re-run scan.","If needed, add an explicit import from the appropriate parent module."]},impact:"Removes dead surface area and clarifies module ownership.",tags:["dead-code","dependency","isolation"]})}return t}export function detectUnreachableModules(e){const t=[],n=new Set;for(const t of e.files)isLikelyEntrypoint(t)&&n.add(t);if(0===n.size)for(const t of e.files)0===(e.incoming.get(t)||new Set).size&&n.add(t);const i=new Set,o=[...n];for(;o.length>0;){const t=o.pop();if(!i.has(t)){i.add(t);for(const n of e.outgoing.get(t)||new Set)e.files.has(n)&&!i.has(n)&&o.push(n)}}for(const n of e.files)if(!(isTestFile(n)||i.has(n)||isLikelyEntrypoint(n))){if(!canAddFinding(t))break;t.push({severity:"high",category:"unreachable-module",file:n,lineStart:1,lineEnd:1,title:`Unreachable module: ${n}`,reason:"Module is not reachable from any entrypoint via the import graph.",files:[n],suggestedFix:{strategy:"Verify reachability and remove if truly dead.",steps:["Check if this module is loaded dynamically or via framework conventions.","Verify it is not registered as a route, plugin, or middleware.","If confirmed unreachable, delete and re-run scan."]},impact:"Identifies potentially large sections of dead code missed by direct-import checks.",tags:["dead-code","dependency","reachability"]})}return t}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{findImportLine,isLikelyEntrypoint}from"./shared.js";import{isTestFile}from"../common/utils.js";export function buildConsumedFromModule(e){const t=new Map,n=new Map;for(const[s,o]of e.importedSymbolsByFile.entries()){const e=isTestFile(s)?n:t;for(const t of o){const n=t.resolvedModule;n&&(e.has(n)||e.set(n,new Set),e.get(n).add(t.importedName))}}for(const[s,o]of e.reExportsByFile.entries()){const e=isTestFile(s)?n:t;for(const t of o){const n=t.resolvedModule;n&&(e.has(n)||e.set(n,new Set),e.get(n).add(t.importedName))}}return{production:t,test:n}}export function detectDeadExports(e,t,n){const s=[];for(const[o,i]of e.declaredExportsByFile.entries()){if(isTestFile(o))continue;if(isLikelyEntrypoint(o))continue;const e=t.get(o)||new Set,r=n?.get(o)||new Set,a=e.has("*"),c=r.has("*");for(const t of i)"default"===t.name&&isLikelyEntrypoint(o)||a||e.has(t.name)||c||r.has(t.name)||s.push({severity:"type"===t.kind?"medium":"high",category:"dead-export",file:o,lineStart:t.lineStart||1,lineEnd:t.lineEnd||t.lineStart||1,title:`Unused export: ${t.name}`,reason:`Exported symbol "${t.name}" has no observed import or re-export usage in production or test files.`,files:[`${o}:${t.lineStart||1}-${t.lineEnd||t.lineStart||1}`],suggestedFix:{strategy:"Remove or internalize unused exports.",steps:["Confirm symbol is not part of intentional public API surface.","Remove export modifier or delete symbol if truly unused.","Re-run scan and tests to ensure no hidden runtime usage."]},impact:"Shrinks public API surface and reduces accidental coupling.",tags:["dead-code","api-surface","cleanup"],lspHints:[{tool:"lspFindReferences",symbolName:t.name,lineHint:t.lineStart||1,file:o,expectedResult:`confirm "${t.name}" has no import references before removing`}]})}return s}export function detectDeadReExports(e,t){const n=[];for(const[s,o]of e.reExportsByFile.entries()){if(isTestFile(s))continue;const i=t.get(s)||new Set,r=i.has("*"),a=new Map,c=new Set((e.declaredExportsByFile.get(s)||[]).map(e=>e.name));for(const e of o){const t=e.exportedAs;a.has(t)||a.set(t,new Set),a.get(t).add(e.resolvedModule||e.sourceModule);r||i.has(t)||e.isStar&&i.size>0||n.push({severity:"medium",category:"dead-re-export",file:s,lineStart:e.lineStart||1,lineEnd:e.lineEnd||e.lineStart||1,title:`Unused re-export: ${t}`,reason:`Re-exported symbol "${t}" from ${e.sourceModule} has no observed downstream imports from this module.`,files:[`${s}:${e.lineStart||1}-${e.lineEnd||e.lineStart||1}`],suggestedFix:{strategy:"Remove stale barrel re-exports.",steps:["Verify no dynamic import/runtime reflection depends on this export.","Remove the re-export clause.","Re-run scan to confirm barrel surface is still complete."]},impact:"Keeps barrel modules focused and easier to reason about.",tags:["dead-code","barrel","cleanup"]})}for(const[e,t]of a.entries())t.size>1&&n.push({severity:"medium",category:"re-export-duplication",file:s,lineStart:1,lineEnd:1,title:`Duplicate re-export paths: ${e}`,reason:`Symbol "${e}" is re-exported from multiple sources in the same barrel.`,files:[s],suggestedFix:{strategy:"Keep one canonical re-export source per symbol.",steps:["Select a canonical module for the symbol.","Remove duplicate re-export paths.","Document intended public export map for the barrel."]},impact:"Reduces API ambiguity and import inconsistency.",tags:["duplication","barrel","api-surface"]}),"*"!==e&&c.has(e)&&n.push({severity:"high",category:"re-export-shadowed",file:s,lineStart:1,lineEnd:1,title:`Shadowed export in barrel: ${e}`,reason:`Barrel exports "${e}" both locally and through re-export, which can hide origin and create ambiguity.`,files:[s],suggestedFix:{strategy:"Disambiguate local vs re-exported symbol ownership.",steps:["Pick a single source of truth for the symbol in this barrel.","Rename or remove the conflicting export path.","Update import call-sites to use the canonical export."]},impact:"Prevents subtle API conflicts and shadowing confusion.",tags:["barrel","api-surface","ambiguity"]})}return n}export function detectUnusedNpmDeps(e,t,n={}){const s=[],o=new Set;for(const t of e.values())for(const e of t){const t=e.split("/");o.add(e.startsWith("@")&&t.length>=2?`${t[0]}/${t[1]}`:t[0])}for(const e of Object.keys(t))o.has(e)||s.push({severity:"medium",category:"unused-npm-dependency",file:"package.json",lineStart:1,lineEnd:1,title:`Unused dependency: ${e}`,reason:`Package "${e}" is in dependencies but no import was found.`,files:["package.json"],suggestedFix:{strategy:"Remove unused dependency from package.json.",steps:["Verify the package is not loaded dynamically or via CLI scripts.","Check if it is a peer dependency required at runtime.","Run `npm uninstall` or remove from package.json."]},impact:"Reduces install size and attack surface.",tags:["dependency","hygiene","bundle-size"]});for(const e of Object.keys(n))o.has(e)||s.push({severity:"low",category:"unused-npm-dependency",file:"package.json",lineStart:1,lineEnd:1,title:`Unused devDependency: ${e}`,reason:`Package "${e}" is in devDependencies but no import was found.`,files:["package.json"],suggestedFix:{strategy:"Remove unused devDependency from package.json.",steps:["Verify the package is not used by build scripts, config files, or CLI tools.","Run `npm uninstall` or remove from package.json."]},impact:"Reduces install size and dependency maintenance burden.",tags:["dependency","hygiene","dev-tooling"]});return s}export function detectBoundaryViolations(e){const t=[];for(const n of e.files){if(isTestFile(n))continue;const s=n.match(/^packages\/([^/]+)\//);if(!s)continue;const o=s[1];for(const s of e.outgoing.get(n)||new Set){const i=s.match(/^packages\/([^/]+)\//);if(!i)continue;if(i[1]===o)continue;if(!/^packages\/[^/]+\/(src\/)?index\.[mc]?[jt]sx?$/.test(s)){const o=s.includes("/internal/")||s.includes("/private/"),i=findImportLine(e,n,s);t.push({severity:o?"high":"medium",category:"package-boundary-violation",file:n,lineStart:i.lineStart,lineEnd:i.lineEnd,title:"Cross-package import bypasses public API",reason:`"${n}" imports "${s}" directly instead of through the package public entry.`,files:[n,s],suggestedFix:{strategy:"Import through the package public API (index file).",steps:["Re-export the needed symbol from the target package index.","Update the import to use the package name or index path.","If the symbol is internal, reconsider the dependency."]},impact:"Enforces clean package boundaries and prevents coupling to internals.",tags:["boundary","coupling","encapsulation"]})}}}return t}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{isLikelyEntrypoint}from"./shared.js";import{canAddFinding}from"./shared.js";import{isTestFile}from"../common/utils.js";export function computeBarrelDepth(e,t,i=new Set){if(i.has(e))return 0;i.add(e);const r=t.reExportsByFile.get(e);if(!r||0===r.length)return 0;let o=0;for(const e of r){const r=e.resolvedModule;if(!r)continue;const s=t.reExportsByFile.get(r);s&&s.length>0&&(o=Math.max(o,computeBarrelDepth(r,t,i)))}return 1+o}export function detectBarrelExplosion(e,t=30){const i=[];for(const[r,o]of e.reExportsByFile.entries()){if(isTestFile(r))continue;if(0===o.length)continue;o.length>t&&i.push({severity:o.length>2*t?"high":"medium",category:"barrel-explosion",file:r,lineStart:1,lineEnd:1,title:`Barrel explosion: ${r}`,reason:`Barrel re-exports ${o.length} symbols (threshold: ${t}). Large barrels hurt bundling.`,files:[r],suggestedFix:{strategy:"Split barrel or use direct imports to reduce bundler cost.",steps:["Group re-exports by domain into sub-barrels.","Let consumers import directly from source modules.","Remove unused re-exports (check dead-re-export findings)."]},impact:"Reduces bundle size and speeds up IDE/tooling.",tags:["barrel","bundle-size","tree-shaking"]});const s=computeBarrelDepth(r,e);s>2&&i.push({severity:"high",category:"barrel-explosion",file:r,lineStart:1,lineEnd:1,title:`Deep barrel chain: ${r} (depth ${s})`,reason:`Barrel chain is ${s} levels deep. Deep chains defeat tree-shaking.`,files:[r],suggestedFix:{strategy:"Flatten barrel chain to at most 2 levels.",steps:["Re-export directly from source modules instead of intermediate barrels.","Remove intermediate barrel layers that add no value."]},impact:"Improves tree-shaking efficiency and import resolution speed.",tags:["barrel","bundle-size","tree-shaking"]})}return i}export function detectImportSideEffectRisk(e,t,i,r){const o=[],s=new Set;for(const e of i.cycles)for(const t of e.path)s.add(t);const n=new Set;for(const e of i.criticalPaths)for(const t of e.path)n.add(t);const l=new Map;for(const e of r)l.set(e.file,e);for(const i of e){if(isTestFile(i.file))continue;const e=i.topLevelEffects;if(!e||0===e.length)continue;let r=0;for(const t of e)r+=t.weight;const l=(t.incoming.get(i.file)||new Set).size;let a=0;l>=20?a+=8:l>=8&&(a+=4),n.has(i.file)&&(a+=6),s.has(i.file)&&(a+=3);let c=0;isLikelyEntrypoint(i.file)&&(c+=4);const d=r+a-c;if(d<4)continue;const u=d>=18?"critical":d>=12?"high":d>=7?"medium":"low",p=e.filter(e=>"high"===e.confidence).length>0?"high":e.some(e=>"medium"===e.confidence)?"medium":"low",m=e.map(e=>`${e.detail} (line ${e.lineStart})`).join("; "),f=[];l>=8&&f.push(`fan-in=${l}`),n.has(i.file)&&f.push("on critical path"),s.has(i.file)&&f.push("in dependency cycle"),isLikelyEntrypoint(i.file)&&f.push("entrypoint (discounted)");const h=f.length>0?` Architecture context: ${f.join(", ")}.`:"",g=e[0];if(!canAddFinding(o))break;o.push({severity:u,category:"import-side-effect-risk",file:i.file,lineStart:g.lineStart,lineEnd:g.lineEnd,title:`Import-time side effect${e.length>1?`s (${e.length})`:""}: ${i.file}`,reason:`Module executes work at import time: ${m}. Risk score: ${d} (ast=${r}, impact=+${a}, role=-${c}). Confidence: ${p}.${h}`,files:[i.file],suggestedFix:{strategy:"Move import-time side effects behind explicit initialization or lazy loading.",steps:["Wrap startup logic in an exported init() function instead of running at module scope.","Replace synchronous I/O with async alternatives called at runtime.","Guard side-effect imports with dynamic import() behind feature checks.","If this is an intentional entrypoint, consider adding a suppression comment."]},impact:`Importing this module triggers ${e.length} side effect(s). With fan-in=${l}, unintended imports can degrade startup latency and cause surprising runtime behavior.`,tags:["import-side-effect","startup","architecture","performance"],lspHints:[{tool:"lspFindReferences",symbolName:i.file.split("/").pop()?.replace(/\.[^.]+$/,"")||i.file,lineHint:1,file:i.file,expectedResult:"find all modules that import this file and may trigger side effects"}]})}return o}export function detectNamespaceImport(e){const t=[];for(const[i,r]of e.importedSymbolsByFile.entries())if(!isTestFile(i))for(const o of r){if("*"!==o.importedName)continue;if(o.isTypeOnly)continue;if("require"===o.localName)continue;const r=null!=o.resolvedModule,s=r?(e.incoming.get(o.resolvedModule)||new Set).size:0;t.push({severity:r&&s>5?"high":"medium",category:"namespace-import",file:i,lineStart:o.lineStart||1,lineEnd:o.lineEnd||o.lineStart||1,title:`Namespace import blocks tree-shaking: import * as ${o.localName}`,reason:`\`import * as ${o.localName} from '${o.sourceModule}'\` forces bundlers to include the entire module. Named imports allow dead-code elimination of unused exports.${r?` Target module has fan-in=${s}.`:""}`,files:[`${i}:${o.lineStart||1}-${o.lineEnd||o.lineStart||1}`],suggestedFix:{strategy:"Replace namespace import with named imports for used symbols.",steps:[`Find which properties of \`${o.localName}\` are actually accessed in this file.`,`Replace \`import * as ${o.localName}\` with \`import { usedA, usedB } from '${o.sourceModule}'\`.`,"If many properties are used, consider splitting the source module into smaller modules."]},impact:"Enables bundlers to tree-shake unused exports, reducing bundle size.",tags:["tree-shaking","bundle-size","namespace-import"]})}return t}export function detectCommonJsInEsm(e){const t=[];for(const[i,r]of e.importedSymbolsByFile.entries()){if(isTestFile(i))continue;const e=r.filter(e=>"require"===e.localName&&!e.isTypeOnly);if(0===e.length)continue;const o=r.some(e=>"require"!==e.localName),s=o?"high":"medium";for(const r of e)t.push({severity:s,category:o?"mixed-module-format":"commonjs-in-esm",file:i,lineStart:r.lineStart||1,lineEnd:r.lineEnd||r.lineStart||1,title:o?`Mixed ESM/CJS: require('${r.sourceModule}') in ESM file`:`CommonJS require blocks tree-shaking: require('${r.sourceModule}')`,reason:o?`File uses both ESM \`import\` and CJS \`require()\`. Mixed formats force bundlers to treat the module as CJS, disabling tree-shaking entirely. Found ${e.length} require() call(s).`:`\`require('${r.sourceModule}')\` is a CommonJS pattern that bundlers cannot statically analyze. ESM \`import\` enables tree-shaking.`,files:[`${i}:${r.lineStart||1}-${r.lineEnd||r.lineStart||1}`],suggestedFix:{strategy:"Convert require() to ESM import.",steps:[`Replace \`const mod = require('${r.sourceModule}')\` with \`import mod from '${r.sourceModule}'\` or named imports.`,"If the require is conditional, use dynamic `import()` instead.",'Ensure the target module supports ESM (check package.json "type" or "module" field).']},impact:"ESM imports enable tree-shaking; CJS requires pull the entire module.",tags:["tree-shaking","bundle-size","commonjs","module-format"]})}return t}export function detectExportStarLeak(e){const t=[];for(const[i,r]of e.reExportsByFile.entries()){if(isTestFile(i))continue;const o=r.filter(e=>e.isStar&&!e.isTypeOnly);if(0!==o.length)for(const r of o){const o=r.resolvedModule?(e.declaredExportsByFile.get(r.resolvedModule)||[]).length:0,s=!!r.resolvedModule&&(e.reExportsByFile.get(r.resolvedModule)||[]).some(e=>e.isStar),n=s||o>20?"high":"medium";t.push({severity:n,category:"export-star-leak",file:i,lineStart:r.lineStart||1,lineEnd:r.lineEnd||r.lineStart||1,title:`export * leaks entire module surface: ${r.sourceModule}`,reason:`\`export * from '${r.sourceModule}'\` re-exports every symbol from the source, defeating granular tree-shaking.${o>0?` Target exports ${o} symbols.`:""}${s?" Target itself contains export-star chains, amplifying the leak.":""}`,files:[`${i}:${r.lineStart||1}-${r.lineEnd||r.lineStart||1}`],suggestedFix:{strategy:"Replace export * with explicit named re-exports.",steps:[`List the symbols actually consumed from \`${r.sourceModule}\` by downstream modules.`,`Replace \`export * from '${r.sourceModule}'\` with \`export { A, B, C } from '${r.sourceModule}'\`.`,"This lets bundlers eliminate unused re-exports during tree-shaking."]},impact:"Explicit re-exports enable precise tree-shaking and make the public API surface visible.",tags:["tree-shaking","bundle-size","export-star","api-surface"]})}}return t}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export{isLikelyEntrypoint}from"./shared.js";export{detectTestOnlyModules,detectDependencyCycles,mergeOverlappingChains,detectCriticalPaths,detectDeadFiles,detectOrphanModules,detectUnreachableModules}from"./cycle.js";export{computeInstability,detectSdpViolations,detectHighCoupling,detectGodModuleCoupling,detectLayerViolations,computeAbstractness,detectDistanceFromMainSequence}from"./coupling.js";export{detectGodModules,detectMegaFolders,detectGodFunctions,detectLowCohesion,computeHotFiles,detectUntestedCriticalCode,detectFeatureEnvy}from"./cohesion.js";export{computeBarrelDepth,detectBarrelExplosion,detectImportSideEffectRisk,detectNamespaceImport,detectCommonJsInEsm,detectExportStarLeak}from"./import-style.js";export{buildConsumedFromModule,detectDeadExports,detectDeadReExports,detectUnusedNpmDeps,detectBoundaryViolations}from"./dead-code.js";export{detectDuplicateFunctionBodies,detectDuplicateFlowStructures,detectFunctionOptimization,computeCognitiveComplexity,detectCognitiveComplexity,detectExcessiveParameters,detectEmptyCatchBlocks,detectSwitchNoDefault,detectUnsafeAny,detectHighHalsteadEffort,detectLowMaintainability,detectTypeAssertionEscape,detectMessageChains,detectMissingErrorBoundary,detectPromiseMisuse,detectAwaitInLoop,detectSyncIo,detectUnclearedTimers,detectListenerLeakRisk,detectUnboundedCollection,detectSimilarFunctionBodies}from"./code-quality.js";export{detectCommandInjectionRisk,detectDebugLogLeakage,detectEvalUsage,detectHardcodedSecrets,detectInputPassthroughRisk,detectPathTraversalRisk,detectPrototypePollutionRisk,detectSensitiveDataLogging,detectSqlInjectionRisk,detectUnsafeHtml,detectUnsafeRegex,detectUnvalidatedInputSink}from"./security.js";
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{isTestFile}from"../common/utils.js";const NESTED_QUANTIFIER_RE=/(\(.+[+*]\))[+*]|(\(.+\?\))\{/,toSecurityFinding=(e,t,i,s)=>({...e,ruleId:t,confidence:i,evidence:s});export function detectHardcodedSecrets(e){const t=[];for(const i of e){if(isTestFile(i.file))continue;const e=(i.suspiciousStrings||[]).filter(e=>"hardcoded-secret"===e.kind&&"regex-definition"!==e.context&&"error-message"!==e.context);if(0!==e.length)for(const s of e)t.push(toSecurityFinding({severity:"high",category:"hardcoded-secret",file:i.file,lineStart:s.lineStart,lineEnd:s.lineEnd,title:"Potential hardcoded secret"+(s.snippet?`: ${s.snippet.slice(0,20)}…`:""),reason:"String literal matches a secret pattern (password, API key, token, high-entropy string). Secrets in source code risk credential leaks. Validate: use localSearchCode to find the variable, then lspFindReferences to check if it is used in auth or network calls.",files:[i.file],suggestedFix:{strategy:"Move secret to environment variable or secrets manager.",steps:["Replace the hardcoded value with process.env.YOUR_SECRET.","Add the variable to your .env file (excluded from git).","Verify the secret is not committed in git history."]},impact:"Credential leak in source code exposes API access, database credentials, or authentication tokens to anyone with repo access.",tags:["security","secrets"],lspHints:[{tool:"lspFindReferences",symbolName:s.snippet?.split(/[=:]/)[0]?.trim()||"secret",lineHint:s.lineStart,file:i.file,expectedResult:"find all usages of this secret value — if used only in tests or as a regex pattern, it is a false positive"}]},"security.hardcoded-secret","high",{source:s.snippet||"",sink:"runtime usage",context:s.context||"literal",sanitizerStatus:"missing",propagationSteps:[`${i.file}:${s.lineStart}`]}))}return t}export function detectEvalUsage(e){const t=[];for(const i of e)if(!isTestFile(i.file))for(const e of i.evalUsages||[])t.push(toSecurityFinding({severity:"critical",category:"eval-usage",file:i.file,lineStart:e.lineStart,lineEnd:e.lineEnd,title:"Dynamic code execution (eval/Function)",reason:"eval(), new Function(), or string-based setTimeout/setInterval allows arbitrary code execution. This is a code injection vector.",files:[i.file],suggestedFix:{strategy:"Replace dynamic code execution with safe alternatives.",steps:["For JSON parsing: use JSON.parse() instead of eval().","For dynamic dispatch: use a lookup table or switch statement.","For setTimeout: pass a function reference, not a string."]},impact:"Arbitrary code execution enables full application takeover — the most severe class of injection vulnerability.",tags:["security","injection","critical"],lspHints:[{tool:"lspCallHierarchy",symbolName:"eval",lineHint:e.lineStart,file:i.file,expectedResult:"trace callers to find how user input reaches the eval site"}]},"security.eval-usage","high",{sink:`eval at ${i.file}:${e.lineStart}-${e.lineEnd}`,sanitizerStatus:"missing"}));return t}export function detectUnsafeHtml(e){const t=[];for(const i of e)if(!isTestFile(i.file))for(const e of i.unsafeHtmlAssignments||[])t.push(toSecurityFinding({severity:"high",category:"unsafe-html",file:i.file,lineStart:e.lineStart,lineEnd:e.lineEnd,title:"Unsafe HTML manipulation",reason:"innerHTML, outerHTML, dangerouslySetInnerHTML, or document.write can execute unsanitized user input as HTML/script. XSS vector.",files:[i.file],suggestedFix:{strategy:"Use safe DOM APIs or sanitize input before insertion.",steps:["Replace innerHTML with textContent for plain text.","Use a sanitizer library (e.g. DOMPurify) if HTML is required.","In React, avoid dangerouslySetInnerHTML — use JSX instead."]},impact:"Unsanitized HTML insertion enables cross-site scripting (XSS) — attackers can steal sessions, credentials, or execute actions as the victim.",tags:["security","xss"]},"security.unsafe-html","high",{sink:"DOM assignment",sanitizerStatus:"missing",propagationSteps:["html assignment"]}));return t}export function detectSqlInjectionRisk(e){const t=[];for(const i of e){if(isTestFile(i.file))continue;const e=(i.suspiciousStrings||[]).filter(e=>"sql-injection"===e.kind);for(const s of e)t.push(toSecurityFinding({severity:"high",category:"sql-injection-risk",file:i.file,lineStart:s.lineStart,lineEnd:s.lineEnd,title:"SQL query built with template literal interpolation",reason:"Template literals with SQL keywords and interpolated expressions risk SQL injection if user input flows into the query.",files:[i.file],suggestedFix:{strategy:"Use parameterized queries or a query builder.",steps:["Replace template literal with parameterized query (e.g. db.query(sql, [param])).","Use an ORM or query builder that handles escaping.","If raw SQL is necessary, validate and sanitize all interpolated values."]},impact:"SQL injection can expose, modify, or destroy database contents and potentially escalate to full server compromise.",tags:["security","injection","sql"]},"security.sql-injection-risk","high",{sink:"sql template literal",sanitizerStatus:"missing",propagationSteps:[`${i.file}:${s.lineStart}-${s.lineEnd}`]}))}return t}export function detectUnsafeRegex(e){const t=[];for(const i of e)if(!isTestFile(i.file))for(const e of i.regexLiterals||[])NESTED_QUANTIFIER_RE.test(e.pattern)&&t.push(toSecurityFinding({severity:"medium",category:"unsafe-regex",file:i.file,lineStart:e.lineStart,lineEnd:e.lineEnd,title:"Regex with catastrophic backtracking risk",reason:`Pattern "${e.pattern.slice(0,40)}" has nested quantifiers that can cause exponential backtracking (ReDoS).`,files:[i.file],suggestedFix:{strategy:"Simplify the regex or use atomic groups / possessive quantifiers.",steps:["Remove nested quantifiers — e.g. change (a+)+ to a+.","Use a regex linter (e.g. safe-regex) to validate patterns.","Consider using string methods instead of complex regexes."]},impact:"Catastrophic backtracking causes CPU exhaustion — a single crafted input string can hang the event loop (ReDoS).",tags:["security","regex","performance"],lspHints:[{tool:"lspFindReferences",symbolName:e.pattern.slice(0,20),lineHint:e.lineStart,file:i.file,expectedResult:"find where this regex is used to assess if user input reaches it"}]},"security.unsafe-regex","medium",{source:e.pattern,sink:"Regex execution",sanitizerStatus:"not-applicable",propagationSteps:[`${i.file}:${e.lineStart}-${e.lineEnd}`]}));return t}export function detectPrototypePollutionRisk(e){const t=[];for(const i of e)if(!isTestFile(i.file)&&i.prototypePollutionSites&&0!==i.prototypePollutionSites.length)for(const e of i.prototypePollutionSites){let s,n;"computed-property-write"===e.kind?e.guarded?(s="low",n="low"):(s="high",n="medium"):(s="medium",n="medium"),t.push(toSecurityFinding({severity:s,category:"prototype-pollution-risk",file:i.file,lineStart:e.lineStart,lineEnd:e.lineEnd,title:`Prototype pollution risk: ${e.kind}${e.guarded?" (guarded)":""}`,reason:`${e.detail}${e.guarded?" — guards detected (internal iteration or key check), likely false positive. Verify the key variable does not trace to external input.":""}`,files:[i.file],suggestedFix:{strategy:"Guard against __proto__, constructor, and prototype keys before merging.",steps:['Validate keys: reject "__proto__", "constructor", "prototype" before assignment.',"Use Object.create(null) as the target for merges when possible.","Replace custom deep-merge with a hardened library (e.g. lodash.merge with prototype guard).","For Object.assign, ensure the source is sanitized or use structuredClone()."]},impact:"Prototype pollution can override built-in methods, bypass security checks, or achieve remote code execution.",tags:["security","prototype-pollution","injection"],lspHints:[{tool:"lspCallHierarchy",symbolName:"computed-property-write"===e.kind?"bracket-assignment":e.detail.split("(")[0],lineHint:e.lineStart,file:i.file,expectedResult:"trace callers to determine if user-controlled data reaches this site — if key comes from Object.keys() on internal object, dismiss as false positive"}]},"security.prototype-pollution-risk",n,{source:e.kind,sink:e.detail,guarded:e.guarded,sanitizerStatus:e.guarded?"present":"missing",propagationSteps:[`${i.file}:${e.lineStart}`]}))}return t}export function detectUnvalidatedInputSink(e){const t=[];for(const i of e)if(!isTestFile(i.file))for(const e of i.inputSources||[]){if(!e.hasSinkInBody||e.hasValidation)continue;const s=e.sinkKinds.join(", "),n="low"===e.paramConfidence?"medium":"high";t.push(toSecurityFinding({severity:n,category:"unvalidated-input-sink",file:i.file,lineStart:e.lineStart,lineEnd:e.lineEnd,title:`Unvalidated input reaches ${s} sink in ${e.functionName}(${e.sourceParams.join(", ")})`,reason:`Parameter${e.sourceParams.length>1?"s":""} '${e.sourceParams.join("', '")}' (external input) flow${1===e.sourceParams.length?"s":""} into ${s} without validation (no type guard, schema call, or conditional check).`,files:[i.file],suggestedFix:{strategy:"Add input validation before the sink operation.",steps:["Add schema validation (e.g. zod, joi) for input parameters.","Use parameterized APIs instead of template interpolation for SQL/exec.",`Trace data flow: lspCallHierarchy(outgoing) on ${e.functionName}.`]},impact:"Unvalidated external input reaching a dangerous sink (eval, SQL, exec, innerHTML, file write) enables injection attacks.",tags:["security","input-validation","injection"],lspHints:[{tool:"lspCallHierarchy",symbolName:e.functionName,lineHint:e.lineStart,file:i.file,expectedResult:`trace outgoing calls to see where ${e.sourceParams.join(", ")} data flows`},{tool:"lspFindReferences",symbolName:e.sourceParams[0],lineHint:e.lineStart,file:i.file,expectedResult:`check all usages of ${e.sourceParams[0]} parameter within function`}]},"security.unvalidated-input-sink","high"===n?"high":"medium",{sourceParameters:e.sourceParams,sink:s,sanitizerStatus:e.hasValidation?"present":"missing",propagationSteps:e.callsWithInputArgs.map(e=>`${e.callee}:${e.lineStart}`)}))}return t}export function detectInputPassthroughRisk(e){const t=[];for(const i of e)if(!isTestFile(i.file))for(const e of i.inputSources||[]){if(0===e.callsWithInputArgs.length||e.hasValidation)continue;if(e.hasSinkInBody)continue;if("low"===e.paramConfidence)continue;const s=e.callsWithInputArgs.map(e=>e.callee),n=[...new Set(s)],a="high"===e.paramConfidence?"medium":"low";t.push(toSecurityFinding({severity:a,category:"input-passthrough-risk",file:i.file,lineStart:e.lineStart,lineEnd:e.lineEnd,title:`Input passthrough without validation in ${e.functionName}(${e.sourceParams.join(", ")})`,reason:`Parameter${e.sourceParams.length>1?"s":""} '${e.sourceParams.join("', '")}' (external input) ${1===e.sourceParams.length?"is":"are"} passed to ${n.join(", ")} without validation. Downstream callees may not validate either.`,files:[i.file],suggestedFix:{strategy:"Validate input before passing to downstream functions.",steps:["Add schema validation (e.g. zod, joi) at the entry point.",`Trace downstream: lspCallHierarchy(outgoing) on ${e.functionName} to verify callees validate.`,"Search for validation middleware: localSearchCode for guard/validate/sanitize patterns."]},impact:"Unchecked input passed downstream can reach sinks in callees — validation gaps compound across the call chain.",tags:["security","input-validation","passthrough"],lspHints:[{tool:"lspCallHierarchy",symbolName:e.functionName,lineHint:e.lineStart,file:i.file,expectedResult:`trace outgoing calls to verify downstream validation of ${e.sourceParams.join(", ")}`},{tool:"lspFindReferences",symbolName:e.sourceParams[0],lineHint:e.lineStart,file:i.file,expectedResult:`find all usages of ${e.sourceParams[0]} to check if validation occurs upstream`}]},"security.input-passthrough-risk",a,{sourceParameters:e.sourceParams,sink:n.join(", "),sanitizerStatus:e.hasValidation?"present":"missing",propagationSteps:e.callsWithInputArgs.map(e=>`${e.callee}:${e.lineStart}`)}))}return t}export function detectPathTraversalRisk(e){const t=[];for(const i of e)if(!isTestFile(i.file))for(const e of i.inputSources||[]){const s=e.sinkKinds.filter(e=>"fs-read"===e||"path-resolve"===e);if(0===s.length)continue;if("low"===e.paramConfidence)continue;const n=e.hasValidation,a=n?"medium":"high",r=s.join(", ");t.push(toSecurityFinding({severity:a,category:"path-traversal-risk",file:i.file,lineStart:e.lineStart,lineEnd:e.lineEnd,title:`Path traversal risk: ${e.functionName}(${e.sourceParams.join(", ")}) → ${r}`,reason:`Parameter${e.sourceParams.length>1?"s":""} '${e.sourceParams.join("', '")}' (external input) flow${1===e.sourceParams.length?"s":""} into ${r} ${n?"with partial validation — verify path normalization + prefix check + realpath resolution":"without validation. Path traversal (e.g. ../../etc/passwd) can read or write arbitrary files"}.`,files:[i.file],suggestedFix:{strategy:"Add multi-layer path validation before file system operations.",steps:["Normalize the path: path.resolve(basePath, userInput).","Prefix check: resolvedPath.startsWith(basePath + path.sep).","Resolve symlinks: fs.realpathSync() to prevent symlink escape.","Re-validate after symlink resolution."]},impact:"Path traversal enables reading sensitive files (credentials, configs, source code) or writing to arbitrary locations (code injection via file overwrite).",tags:["security","path-traversal","agentic"],lspHints:[{tool:"lspCallHierarchy",symbolName:e.functionName,lineHint:e.lineStart,file:i.file,expectedResult:"trace incoming callers to determine if path parameter comes from user input — then trace outgoing to the fs/path call"}]},"security.path-traversal-risk","high"===e.paramConfidence?"high":"medium",{sourceParameters:e.sourceParams,sink:r,sanitizerStatus:n?"partial":"missing",propagationSteps:e.callsWithInputArgs.map(e=>`${e.callee}:${e.lineStart}`)}))}return t}export function detectCommandInjectionRisk(e){const t=[];for(const i of e)if(!isTestFile(i.file))for(const e of i.inputSources||[]){if(0===e.sinkKinds.filter(e=>"exec"===e).length)continue;if("low"===e.paramConfidence)continue;const s=e.callsWithInputArgs.filter(e=>/\.exec\b|^exec$|^execSync$|child_process\.exec/.test(e.callee)),n=e.callsWithInputArgs.filter(e=>/\.spawn\b|^spawn$|^spawnSync$|child_process\.spawn/.test(e.callee));if(s.length>0){const n="high"===e.paramConfidence?"critical":"high";t.push(toSecurityFinding({severity:n,category:"command-injection-risk",file:i.file,lineStart:e.lineStart,lineEnd:e.lineEnd,title:`Command injection risk: ${e.functionName}(${e.sourceParams.join(", ")}) → exec`,reason:`Parameter${e.sourceParams.length>1?"s":""} '${e.sourceParams.join("', '")}' (external input) flow${1===e.sourceParams.length?"s":""} into exec/execSync. exec() runs commands through a shell — string interpolation enables command injection.`,files:[i.file],suggestedFix:{strategy:"Replace exec with spawn using array arguments (no shell interpretation).",steps:["Replace child_process.exec(cmd) with child_process.spawn(binary, [args]).","Never interpolate user input into command strings.","Use an allowlist for permitted commands if dynamic dispatch is needed.","If shell features are required, validate input against a strict allowlist."]},impact:"Command injection enables arbitrary OS command execution — full server compromise, data exfiltration, or lateral movement.",tags:["security","command-injection","critical","agentic"],lspHints:[{tool:"lspCallHierarchy",symbolName:e.functionName,lineHint:e.lineStart,file:i.file,expectedResult:"trace incoming callers to verify if user input reaches the exec call — check for allowlist or sanitization"}]},"security.command-injection-risk","high"===e.paramConfidence?"high":"medium",{sourceParameters:e.sourceParams,sink:"exec",sanitizerStatus:e.hasValidation?"partial":"missing",propagationSteps:s.map(e=>`${e.callee}:${e.lineStart}`)}))}n.length>0&&0===s.length&&t.push(toSecurityFinding({severity:"high",category:"command-injection-risk",file:i.file,lineStart:e.lineStart,lineEnd:e.lineEnd,title:`Potential command injection: ${e.functionName}(${e.sourceParams.join(", ")}) → spawn`,reason:`Parameter${e.sourceParams.length>1?"s":""} '${e.sourceParams.join("', '")}' (external input) flow${1===e.sourceParams.length?"s":""} into spawn. If shell:true is set, this is equivalent to exec. Verify spawn uses array args without shell option.`,files:[i.file],suggestedFix:{strategy:"Ensure spawn uses array arguments without shell: true.",steps:["Verify spawn is called as spawn(binary, [arg1, arg2]) — NOT spawn(cmd, { shell: true }).","Remove shell: true if present.","Validate command arguments against an allowlist."]},impact:"spawn with shell:true enables the same command injection as exec. Without shell:true, spawn with array args is safe from injection.",tags:["security","command-injection","agentic"],lspHints:[{tool:"lspCallHierarchy",symbolName:e.functionName,lineHint:e.lineStart,file:i.file,expectedResult:"trace incoming callers — check if spawn uses shell:true option"}]},"security.command-injection-risk","medium",{sourceParameters:e.sourceParams,sink:"spawn",sanitizerStatus:e.hasValidation?"partial":"missing",propagationSteps:n.map(e=>`${e.callee}:${e.lineStart}`)}))}return t}export function detectDebugLogLeakage(e){const t=[];for(const i of e)if(!isTestFile(i.file))for(const e of i.consoleLogs||[])"debugger"===e.method?t.push(toSecurityFinding({severity:"high",category:"debug-log-leakage",file:i.file,lineStart:e.lineStart,lineEnd:e.lineEnd,title:"Debugger statement in production code",reason:"A `debugger` statement pauses execution when DevTools are open. In production it can expose internal state and halt the application.",files:[i.file],suggestedFix:{strategy:"Remove the debugger statement before shipping.",steps:["Delete the `debugger;` line.","Use structured logging (pino, winston) or feature-flagged debug helpers instead."]},impact:"Debugger statements in production can halt request processing and expose internal runtime state to anyone with browser DevTools open.",tags:["security","debug","production-safety"]},"security.debug-log-leakage","high",{method:"debugger",line:e.lineStart})):"debug"!==e.method&&"trace"!==e.method||t.push(toSecurityFinding({severity:"medium",category:"debug-log-leakage",file:i.file,lineStart:e.lineStart,lineEnd:e.lineEnd,title:`console.${e.method}() in production code`,reason:`console.${e.method}() is a development-only call. Left in production it leaks internal state, variable values, and execution paths — all useful to attackers.`,files:[i.file],suggestedFix:{strategy:"Replace with a structured logger that respects log-level configuration.",steps:[`Remove or gate the console.${e.method}() call behind a LOG_LEVEL check.`,"Use a structured logger (pino, winston) with level filtering instead.","Ensure debug/trace levels are disabled in production config."]},impact:"Debug/trace logs expose internal object state and execution flow, making reconnaissance easier for attackers and violating minimal disclosure.",tags:["security","debug","information-disclosure"],lspHints:[{tool:"lspFindReferences",symbolName:`console.${e.method}`,lineHint:e.lineStart,file:i.file,expectedResult:"find all debug/trace log calls in this file to assess total leakage surface"}]},"security.debug-log-leakage","medium",{method:e.method,snippet:e.argSnippet,line:e.lineStart}));return t}export function detectSensitiveDataLogging(e){const t=[];for(const i of e)if(!isTestFile(i.file))for(const e of i.consoleLogs||[])"debugger"!==e.method&&e.hasSensitiveArg&&t.push(toSecurityFinding({severity:"high",category:"sensitive-data-logging",file:i.file,lineStart:e.lineStart,lineEnd:e.lineEnd,title:`Sensitive data logged via console.${e.method}()${e.argSnippet?`: ${e.argSnippet.slice(0,40)}`:""}`,reason:`console.${e.method}() argument matches a sensitive-data pattern (password, token, secret, credential, API key, session, SSN). Logging secrets writes them to stdout/stderr, log aggregators, error monitoring services, and persistent log files.`,files:[i.file],suggestedFix:{strategy:"Remove or redact sensitive values before logging.",steps:["Never log raw passwords, tokens, API keys, or session identifiers.",'If logging for debugging, redact: log({ ...user, password: "[REDACTED]" }).',"Use a structured logger with field-level redaction hooks (e.g. pino redact option).","Audit all log aggregation pipelines (Datadog, Splunk, CloudWatch) for secret exposure."]},impact:"Sensitive data in logs is written to stdout/stderr, forwarded to log aggregators (Splunk, Datadog, CloudWatch), and often stored long-term — creating a persistent credential leak accessible to anyone with log access.",tags:["security","sensitive-data","credential-leak","compliance"],lspHints:[{tool:"lspCallHierarchy",symbolName:e.method,lineHint:e.lineStart,file:i.file,expectedResult:`trace incoming callers to understand where sensitive data originates before reaching console.${e.method}`}]},"security.sensitive-data-logging","high",{method:e.method,snippet:e.argSnippet,line:e.lineStart,sanitizerStatus:"missing"}));return t}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import path from"node:path";import*as ts from"typescript";export function detectSemanticDeadExports(e){const t=[];for(const n of e)for(const[e,i]of n.referenceCountByExport)0===i.count&&t.push({severity:"high",category:"semantic-dead-export",file:n.file,lineStart:i.lineStart,lineEnd:i.lineEnd,title:`Semantically dead export: ${e}`,reason:`Exported symbol "${e}" has zero semantic references across the entire program (confirmed via TypeChecker, not just import matching).`,files:[n.file],suggestedFix:{strategy:"Remove the export or delete the symbol if unused internally.",steps:["Verify the symbol is not used via dynamic imports or runtime reflection.","Remove the export keyword, or delete the symbol entirely if also unused locally.","Re-run scan to confirm finding is resolved."]},impact:"Dead exports bloat the public API surface and confuse contributors.",tags:["architecture","dead-code","semantic"],lspHints:[{tool:"lspFindReferences",symbolName:e,lineHint:i.lineStart,file:n.file,expectedResult:"zero references confirms dead export"}]});return t}export function detectOverAbstraction(e,t){const n=[],i=new Map;for(const n of t){const t=e.program.getSourceFile(path.resolve(e.root,n.file));if(!t)continue;const s=r=>{if(ts.isInterfaceDeclaration(r)&&r.name){const s=r.name.text,o=t.getLineAndCharacterOfPosition(r.getStart(t)).line+1,a=e.service.getImplementationAtPosition(path.resolve(e.root,n.file),r.name.getStart(t)),c=new Set;if(a)for(const i of a){const s=i.fileName;s===path.resolve(e.root,n.file)&&i.textSpan.start===r.getStart(t)||c.add(s)}i.has(s)||i.set(s,{files:new Set,line:o,file:n.file});const l=i.get(s);for(const e of c)l.files.add(e)}ts.forEachChild(r,s)};ts.forEachChild(t,s)}for(const[t,s]of i)if(1===s.files.size){const i=[...s.files][0],r=path.relative(e.root,i);n.push({severity:"medium",category:"over-abstraction",file:s.file,lineStart:s.line,lineEnd:s.line,title:`Over-abstraction: interface ${t} has exactly 1 implementor`,reason:`Interface "${t}" is implemented only by one class in "${r}". The abstraction layer adds complexity without enabling polymorphism.`,files:[s.file,r],suggestedFix:{strategy:"Inline the interface into the concrete class or keep it only if future implementors are planned.",steps:["Evaluate whether the interface is needed for testing (mocking) or future extensibility.","If not, merge the interface declaration into the concrete class.","Update consumers to depend on the concrete class directly."]},impact:"Over-abstraction adds indirection without polymorphic benefit, increasing cognitive load.",tags:["architecture","abstraction","semantic"],lspHints:[{tool:"lspFindReferences",symbolName:t,lineHint:s.line,file:s.file,expectedResult:"exactly 1 implementation confirms over-abstraction"}]})}return n}export function detectConcreteDependency(e){const t=[];for(const n of e)for(const e of n.concreteImports)t.push({severity:"medium",category:"concrete-dependency",file:n.file,lineStart:e.lineStart,lineEnd:e.lineStart,title:`Concrete dependency: ${n.file} imports class ${e.name}`,reason:`Module imports concrete class "${e.name}" from "${e.targetFile}" instead of an interface or abstract class. This violates the Dependency Inversion Principle (DIP).`,files:[n.file,e.targetFile],suggestedFix:{strategy:"Depend on an interface or abstract class instead of the concrete implementation.",steps:["Extract an interface from the concrete class covering the methods used by this module.","Update imports to reference the interface instead of the concrete class.","Use dependency injection to provide the concrete implementation at runtime."]},impact:"Concrete dependencies make modules harder to test and tightly coupled to implementation details.",tags:["architecture","dip","coupling","semantic"],lspHints:[{tool:"lspGotoDefinition",symbolName:e.name,lineHint:e.lineStart,file:n.file,expectedResult:"resolves to concrete class (not interface/abstract)"}]});return t}export function detectCircularTypeDependency(e,t){const n=[],i=new Map;for(const n of t){const t=e.program.getSourceFile(path.resolve(e.root,n.file));if(!t)continue;const s=new Set,r=new Map,o=t=>{if((ts.isInterfaceDeclaration(t)||ts.isTypeAliasDeclaration(t))&&t.name){const i=`${n.file}::${t.name.text}`;s.add(i);const o=new Set,a=t=>{if(ts.isTypeReferenceNode(t)&&ts.isIdentifier(t.typeName)){const n=t.typeName.text,i=e.checker.getSymbolAtLocation(t.typeName);if(i){const t=i.getDeclarations?.()?.[0];if(t){const i=t.getSourceFile().fileName,s=path.relative(e.root,i);o.add(`${s}::${n}`)}}}ts.forEachChild(t,a)};ts.forEachChild(t,a),r.set(i,o)}ts.forEachChild(t,o)};ts.forEachChild(t,o);for(const[e,t]of r){i.has(e)||i.set(e,new Set);for(const n of t)i.get(e).add(n)}}const s=new Set,r=new Set,o=new Set,a=(e,t)=>{if(r.has(e)){const i=t.indexOf(e);if(i>=0){const e=t.slice(i),s=[...e].sort().join("→");if(!o.has(s)&&e.length>=2){o.add(s);const t=e[0],[i]=t.split("::");n.push({severity:"high",category:"circular-type-dependency",file:i,lineStart:1,lineEnd:1,title:`Circular type dependency: ${e.map(e=>e.split("::")[1]).join(" → ")}`,reason:`Type-level circular dependency detected: ${e.map(e=>e.split("::")[1]).join(" → ")} → ${e[0].split("::")[1]}. Types reference each other creating a cycle.`,files:[...new Set(e.map(e=>e.split("::")[0]))],suggestedFix:{strategy:"Break the type cycle by extracting shared type definitions.",steps:["Identify the minimal set of type properties causing the cycle.","Extract shared types to a dedicated types file that both sides can import.","Replace direct type references with the shared type."]},impact:"Circular type dependencies make types harder to understand, refactor, and can cause issues with type inference.",tags:["architecture","types","cycle","semantic"]})}}return}if(!s.has(e)){r.add(e),t.push(e);for(const n of i.get(e)??[])a(n,t);t.pop(),r.delete(e),s.add(e)}};for(const e of i.keys())a(e,[]);return n}export function detectUnusedParameters(e){const t=[];for(const n of e)for(const e of n.unusedParams)t.push({severity:"medium",category:"unused-parameter",file:n.file,lineStart:e.lineStart,lineEnd:e.lineEnd,title:`Unused parameter: ${e.paramName} in ${e.functionName}`,reason:`Parameter "${e.paramName}" in function "${e.functionName}" is never referenced in the function body (confirmed via semantic analysis).`,files:[n.file],suggestedFix:{strategy:"Remove the parameter or prefix with underscore to indicate intentional non-use.",steps:["Check if the parameter is required by an interface or callback signature.","If not required, remove it and update all call sites.","If required by contract, prefix with _ (e.g. _unused) to signal intent."]},impact:"Unused parameters add noise to function signatures and confuse callers about what the function actually needs.",tags:["code-quality","parameters","semantic"],lspHints:[{tool:"lspFindReferences",symbolName:e.paramName,lineHint:e.lineStart,file:n.file,expectedResult:"zero non-declaration references confirms unused"}]});return t}export function detectDeepOverrideChain(e,t=3){const n=[];for(const i of e)for(const e of i.overrideChains)e.depth>t&&n.push({severity:e.depth>4?"high":"medium",category:"deep-override-chain",file:i.file,lineStart:e.lineStart,lineEnd:e.lineStart,title:`Deep override chain: ${e.className}.${e.methodName} (depth ${e.depth})`,reason:`Method "${e.methodName}" in class "${e.className}" overrides a method ${e.depth} levels up in the inheritance chain (threshold: ${t}).`,files:[i.file],suggestedFix:{strategy:"Reduce override depth by flattening the class hierarchy or using the template method pattern.",steps:["Identify if intermediate overrides are necessary or if they just pass through.","Consider extracting the behavior into a strategy or template method.","Flatten unnecessary intermediate classes."]},impact:"Deep override chains make method behavior unpredictable — understanding what runs requires tracing through many classes.",tags:["code-quality","inheritance","override","semantic"]});return n}export function detectInterfaceCompliance(e){const t=[];for(const n of e)for(const e of n.interfaceImpls){const n=[];e.missingMembers.length>0&&n.push(`missing members: ${e.missingMembers.join(", ")}`),e.anycastMembers.length>0&&n.push(`any-cast members: ${e.anycastMembers.join(", ")}`),n.length>0&&t.push({severity:e.missingMembers.length>0?"high":"medium",category:"interface-compliance",file:e.classFile,lineStart:e.classLine,lineEnd:e.classLine,title:`Fragile interface compliance: ${e.className} implements ${e.interfaceName}`,reason:`Class "${e.className}" implements "${e.interfaceName}" with issues: ${n.join("; ")}.`,files:[e.classFile],suggestedFix:{strategy:"Fix the implementation to fully satisfy the interface contract.",steps:[...e.missingMembers.length>0?[`Implement missing members: ${e.missingMembers.join(", ")}.`]:[],...e.anycastMembers.length>0?[`Replace \`any\` types with proper types for: ${e.anycastMembers.join(", ")}.`]:[],"Enable strict type checking to catch these at compile time."]},impact:"Incomplete interface implementations create runtime surprises and defeat the purpose of type contracts.",tags:["code-quality","types","interface","semantic"],lspHints:[{tool:"lspGotoDefinition",symbolName:e.interfaceName,lineHint:e.classLine,file:e.classFile,expectedResult:"interface definition showing expected contract"}]})}return t}export function detectUnusedImports(e){const t=[];for(const n of e)for(const e of n.unusedImports)t.push({severity:"low",category:"unused-import",file:n.file,lineStart:e.lineStart,lineEnd:e.lineStart,title:`Unused import: ${e.name}`,reason:`Imported symbol "${e.name}" is never referenced in this file (confirmed via semantic analysis, not just text matching).`,files:[n.file],suggestedFix:{strategy:"Remove the unused import statement.",steps:["Verify the import is not used for side effects (e.g. polyfills, CSS).","Remove the import statement.","If part of a multi-import, remove only the unused symbol."]},impact:"Unused imports slow down IDE performance, increase bundle size (if not tree-shaken), and add noise.",tags:["dead-code","imports","semantic"],lspHints:[{tool:"lspFindReferences",symbolName:e.name,lineHint:e.lineStart,file:n.file,expectedResult:"zero usage references confirms unused import"}]});return t}export function detectOrphanImplementation(e,t){const n=[];for(const i of t){const t=e.program.getSourceFile(path.resolve(e.root,i.file));if(!t)continue;const s=r=>{if(ts.isClassDeclaration(r)&&r.name){if(r.heritageClauses&&r.heritageClauses.length>0)return void ts.forEachChild(r,s);const o=r.modifiers?.some(e=>e.kind===ts.SyntaxKind.ExportKeyword);if(!o)return void ts.forEachChild(r,s);const a=e.service.findReferences(path.resolve(e.root,i.file),r.name.getStart(t));let c=0;if(a)for(const t of a)for(const n of t.references)if(!n.isDefinition){n.fileName!==path.resolve(e.root,i.file)&&c++}if(0===c){const e=t.getLineAndCharacterOfPosition(r.getStart(t)).line+1;n.push({severity:"medium",category:"orphan-implementation",file:i.file,lineStart:e,lineEnd:e,title:`Orphan implementation: class ${r.name.text}`,reason:`Exported class "${r.name.text}" has no external references and does not implement any interface or extend any base class. It may be unreachable dead code.`,files:[i.file],suggestedFix:{strategy:"Verify the class is needed and wire it in, or remove it.",steps:["Check if the class is used via dynamic imports, reflection, or DI containers.","If unused, remove the class and its export.","If needed, wire it into the dependency graph via an interface or direct import."]},impact:"Orphan implementations waste maintenance effort and bloat the codebase.",tags:["dead-code","class","orphan","semantic"],lspHints:[{tool:"lspFindReferences",symbolName:r.name.text,lineHint:e,file:i.file,expectedResult:"zero external references confirms orphan"}]})}}ts.forEachChild(r,s)};ts.forEachChild(t,s)}return n}export function detectShotgunSurgery(e,t=8){const n=[];for(const i of e)for(const[e,s]of i.referenceCountByExport)s.uniqueFiles>=t&&n.push({severity:s.uniqueFiles>12?"high":"medium",category:"shotgun-surgery",file:i.file,lineStart:s.lineStart,lineEnd:s.lineEnd,title:`Shotgun surgery risk: ${e} used in ${s.uniqueFiles} files`,reason:`Exported symbol "${e}" is referenced from ${s.uniqueFiles} unique files (threshold: ${t}). Any change to this symbol forces coordinated edits across all consumers.`,files:[i.file],suggestedFix:{strategy:"Reduce coupling by introducing a facade, adapter, or event-based decoupling.",steps:["Identify the consumers and group them by usage pattern.","Extract a stable interface that consumers depend on instead of the implementation.","Consider the Mediator or Facade pattern to reduce direct dependencies.","If the symbol is a utility, ensure it has a single, well-defined responsibility."]},impact:"High fan-out symbols are the #1 source of cascading changes during refactoring.",tags:["architecture","coupling","change-risk","semantic"],lspHints:[{tool:"lspFindReferences",symbolName:e,lineHint:s.lineStart,file:i.file,expectedResult:`${s.uniqueFiles}+ unique referencing files confirms shotgun surgery risk`}]});return n}export function detectMoveToCaller(e){const t=[];for(const n of e)for(const[e,i]of n.referenceCountByExport)1===i.uniqueFiles&&i.count>0&&t.push({severity:"low",category:"move-to-caller",file:n.file,lineStart:i.lineStart,lineEnd:i.lineEnd,title:`Single-consumer export: ${e} (used by 1 file)`,reason:`Exported symbol "${e}" is consumed by exactly 1 file. Consider moving it to the consumer or inlining it to reduce module surface.`,files:[n.file],suggestedFix:{strategy:"Move the symbol to its only consumer or inline it.",steps:["Verify no dynamic or reflection-based usage exists.","Move the function/class/constant to the consumer file.","Remove the export and the import from the consumer.","If the symbol is large, keep it but remove the export keyword."]},impact:"Single-consumer exports add unnecessary module surface and indirection.",tags:["dead-code","module-surface","refactoring","semantic"],lspHints:[{tool:"lspFindReferences",symbolName:e,lineHint:i.lineStart,file:n.file,expectedResult:"exactly 1 referencing file confirms single-consumer"}]});return t}export function detectNarrowableType(e){const t=[];for(const n of e)for(const e of n.narrowableParams)t.push({severity:"low",category:"narrowable-type",file:n.file,lineStart:e.lineStart,lineEnd:e.lineEnd,title:`Narrowable param: ${e.functionName}(${e.paramName}: ${e.declaredType}) → ${e.narrowedType}`,reason:`Parameter "${e.paramName}" in "${e.functionName}" is declared as \`${e.declaredType}\` but all call sites pass \`${e.narrowedType}\`. The type can be safely narrowed.`,files:[n.file],suggestedFix:{strategy:"Narrow the parameter type to match actual usage.",steps:[`Change the parameter type from \`${e.declaredType}\` to \`${e.narrowedType}\`.`,"Verify no future callers need the broader type.","If the function is part of a public API, consider keeping the broad type with a narrower overload."]},impact:"Overly broad parameter types weaken type checking — narrowing catches bugs at compile time.",tags:["code-quality","types","refactoring","semantic"],lspHints:[{tool:"lspCallHierarchy",symbolName:e.functionName,lineHint:e.lineStart,file:n.file,expectedResult:`all incoming calls pass ${e.narrowedType}`}]});return t}export function runSemanticDetectors(e,t,n={}){const i=[];return i.push(...detectOverAbstraction(e,t)),i.push(...detectConcreteDependency(t)),i.push(...detectCircularTypeDependency(e,t)),i.push(...detectUnusedParameters(t)),i.push(...detectDeepOverrideChain(t,n.overrideChainThreshold??3)),i.push(...detectInterfaceCompliance(t)),i.push(...detectUnusedImports(t)),i.push(...detectOrphanImplementation(e,t)),i.push(...detectShotgunSurgery(t,n.shotgunThreshold??8)),i.push(...detectMoveToCaller(t)),i.push(...detectNarrowableType(t)),i.push(...detectSemanticDeadExports(t)),i}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
const MAX_FINDINGS_PER_DETECTOR=200;export function canAddFinding(t){return t.length<200}export function findImportLine(t,n,e){const i=t.importedSymbolsByFile.get(n);if(i)for(const t of i)if(t.resolvedModule===e&&t.lineStart)return{lineStart:t.lineStart,lineEnd:t.lineEnd??t.lineStart};const r=t.reExportsByFile.get(n);if(r)for(const t of r)if(t.resolvedModule===e&&t.lineStart)return{lineStart:t.lineStart,lineEnd:t.lineEnd??t.lineStart};return{lineStart:1,lineEnd:1}}export function isLikelyEntrypoint(t){const n=t.toLowerCase();return!!/(^|\/)(index|main|app|server|cli|public)\.[mc]?[jt]sx?$/.test(n)||!!/\.(config)\.[mc]?[jt]sx?$/.test(n)}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{isTestFile}from"../common/utils.js";export function detectLowAssertionDensity(e){const t=[];for(const s of e){if(!isTestFile(s.file)||!s.testProfile)continue;const{testBlocks:e}=s.testProfile;if(0===e.length)continue;const i=e.reduce((e,t)=>e+t.assertionCount,0),o=i/e.length;o<1&&t.push({severity:"medium",category:"low-assertion-density",file:s.file,lineStart:e[0].lineStart,lineEnd:e[e.length-1].lineEnd,title:`Low assertion density: ${i} assertions across ${e.length} tests`,reason:`Average ${o.toFixed(1)} assertions per test. Tests with few assertions may pass without verifying actual behavior.`,files:[s.file],suggestedFix:{strategy:"Add meaningful assertions to each test case.",steps:["Review each test block and add expect() calls that verify outcomes.","Test both success and failure paths.","Assert return values, state changes, and side effects."]},impact:"Low assertion density means tests pass without verifying behavior — bugs slip through with false confidence.",tags:["test-quality","assertions"]})}return t}export function detectTestNoAssertion(e){const t=[];for(const s of e)if(isTestFile(s.file)&&s.testProfile)for(const e of s.testProfile.testBlocks)0===e.assertionCount&&t.push({severity:"high",category:"test-no-assertion",file:s.file,lineStart:e.lineStart,lineEnd:e.lineEnd,title:`Test "${e.name}" has no assertions`,reason:"A test without assertions always passes. It provides no verification of behavior.",files:[s.file],suggestedFix:{strategy:"Add at least one expect() or assert() call.",steps:["Identify what behavior this test should verify.","Add expect(result).toBe(expected) or similar assertion.","If the test only checks that code does not throw, use expect(() => fn()).not.toThrow()."]},impact:"Zero-assertion tests always pass — they provide no safety net and create a false sense of coverage.",tags:["test-quality","assertions","false-pass"]});return t}export function detectExcessiveMocking(e,t=10){const s=[];for(const i of e){if(!isTestFile(i.file)||!i.testProfile)continue;const{mockCalls:e}=i.testProfile;e.length>t&&s.push({severity:"medium",category:"excessive-mocking",file:i.file,lineStart:e[0].lineStart,lineEnd:e[e.length-1].lineEnd,title:`${e.length} mock/spy calls in test file (threshold: ${t})`,reason:"Excessive mocking couples tests to implementation details, making them brittle and hard to maintain.",files:[i.file],suggestedFix:{strategy:"Reduce mocks by testing through public interfaces.",steps:["Identify mocks that can be replaced with real implementations.","Use dependency injection to simplify test setup.","Consider integration tests for complex interaction chains."]},impact:"Heavy mocking couples tests to implementation details — any refactor breaks them even if behavior is unchanged.",tags:["test-quality","mocking","brittleness"]})}return s}export function detectSharedMutableState(e){const t=[];for(const s of e)if(isTestFile(s.file)&&s.testProfile)for(const e of s.testProfile.mutableStateDecls)t.push({severity:"medium",category:"shared-mutable-state",file:s.file,lineStart:e.lineStart,lineEnd:e.lineEnd,title:"Mutable variable at describe scope",reason:"let/var at describe scope creates shared mutable state between tests. Tests may pass or fail depending on execution order.",files:[s.file],suggestedFix:{strategy:"Move variable declaration inside each test or use beforeEach.",steps:["Move the variable into each it()/test() block that uses it.","Or initialize it in beforeEach() so each test gets a fresh copy.","Use const where possible."]},impact:"Shared mutable state causes order-dependent test results — tests pass in isolation but fail or flake in suite runs.",tags:["test-quality","isolation","flaky"]});return t}export function detectMissingTestCleanup(e){const t=[];for(const s of e){if(!isTestFile(s.file)||!s.testProfile)continue;const{setupCalls:e}=s.testProfile,i=e.some(e=>"beforeAll"===e.kind),o=e.some(e=>"afterAll"===e.kind),n=e.some(e=>"beforeEach"===e.kind),a=e.some(e=>"afterEach"===e.kind);if(i&&!o){const i=e.find(e=>"beforeAll"===e.kind);t.push({severity:"medium",category:"missing-test-cleanup",file:s.file,lineStart:i.lineStart,lineEnd:i.lineStart,title:"beforeAll without afterAll",reason:"Setup in beforeAll without teardown in afterAll can leak state (open connections, modified globals, temp files) across test suites.",files:[s.file],suggestedFix:{strategy:"Add afterAll() to clean up resources allocated in beforeAll().",steps:["Identify what beforeAll() sets up (connections, mocks, temp state).","Add afterAll() to tear it down."]},impact:"Missing teardown leaks resources (connections, file handles, globals) that poison subsequent test suites.",tags:["test-quality","cleanup","leak"]})}if(n&&!a){const i=e.find(e=>"beforeEach"===e.kind);t.push({severity:"medium",category:"missing-test-cleanup",file:s.file,lineStart:i.lineStart,lineEnd:i.lineStart,title:"beforeEach without afterEach",reason:"Setup in beforeEach without teardown in afterEach can accumulate side effects across tests.",files:[s.file],suggestedFix:{strategy:"Add afterEach() to clean up resources allocated in beforeEach().",steps:["Identify what beforeEach() sets up.","Add afterEach() to tear it down or restore state."]},impact:"Per-test setup without teardown accumulates side effects, causing cascading failures in later tests.",tags:["test-quality","cleanup"]})}}return t}export function detectFocusedTests(e){const t=[];for(const s of e)if(isTestFile(s.file)&&s.testProfile)for(const e of s.testProfile.focusedCalls)t.push({severity:"medium",category:"focused-test",file:s.file,lineStart:e.lineStart,lineEnd:e.lineEnd,title:`Focused test marker: ${e.kind}`,reason:`${e.kind} limits scan or production of focused tests; it can hide unrelated failures and reduce suite coverage when committed.`,files:[s.file],suggestedFix:{strategy:"Avoid focused/skip patterns in committed tests.",steps:["Remove `.only`/`.skip`/`.todo` markers before merging.","Use local test filtering only for interactive local debugging.","If temporarily needed, add a TODO and a tracked follow-up task."]},impact:"Focused tests can create a false green signal by skipping broader test coverage.",tags:["test-quality","selection","flaky","coverage"],ruleId:"test-quality.focused-test",confidence:"high",evidence:{marker:e.kind,lineStart:e.lineStart,lineEnd:e.lineEnd,category:"focused-test"}});return t}export function detectFakeTimersWithoutRestore(e){const t=[];for(const s of e){if(!isTestFile(s.file)||!s.testProfile)continue;const e=s.testProfile.timerControls.filter(e=>"jest.useFakeTimers"===e.kind||"vi.useFakeTimers"===e.kind);if(0===e.length)continue;if(s.testProfile.timerControls.some(e=>"jest.useRealTimers"===e.kind||"vi.useRealTimers"===e.kind))continue;const i=e[0];t.push({severity:"medium",category:"fake-timer-no-restore",file:s.file,lineStart:i.lineStart,lineEnd:i.lineEnd,title:"Fake timers activated without restore",reason:"Tests that switch to fake timers without restoring real timers can leak timing behavior into subsequent tests.",files:[s.file],suggestedFix:{strategy:"Pair fake timer activation with a restore in the same test scope.",steps:["Call `jest.useRealTimers()` or `vi.useRealTimers()` in afterEach() or afterAll().","Prefer per-test setup/teardown with explicit timer cleanup."]},impact:"Leaked fake-timer configuration can cause subtle, order-dependent failures across unrelated suites.",tags:["test-quality","timers","isolation"],ruleId:"test-quality.fake-timer-no-restore",confidence:"medium",evidence:{fakeTimerActivationLines:e.map(e=>`${e.kind}:${e.lineStart}`)}})}return t}export function detectMissingMockRestoration(e){const t=[];for(const s of e){if(!isTestFile(s.file)||!s.testProfile)continue;if(0===s.testProfile.spyOrStubCalls.length)continue;if(s.testProfile.mockRestores.some(e=>"restoreAll"===e.kind))continue;const e=new Set(s.testProfile.mockRestores.filter(e=>"restore"===e.kind&&!!e.target).map(e=>e.target)),i=s.testProfile.spyOrStubCalls.find(t=>!t.target||!e.has(t.target));if(!i)continue;const o=i;t.push({severity:"medium",category:"missing-mock-restoration",file:s.file,lineStart:o.lineStart,lineEnd:o.lineEnd,title:"Spy/stub not restored",reason:"Spies/stubs modify implementation behavior and must be restored to avoid cross-test leakage.",files:[s.file],suggestedFix:{strategy:"Restore every spy/stub after each test or in a file-level teardown.",steps:["Call `mockRestore()` on each spy/stub returned handle.","Or use `jest.restoreAllMocks()`/`vi.restoreAllMocks()` in afterEach/afterAll."]},impact:"Unrestored spies/stubs make tests order-dependent and can mask regressions.",tags:["test-quality","cleanup","mocks","isolation"],ruleId:"test-quality.missing-mock-restoration",confidence:"high",evidence:{spyOrStubCalls:s.testProfile.spyOrStubCalls.map(e=>`${e.lineStart}`)}})}return t}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{isDirectRun}from"./common/is-direct-run.js";import{buildConsumedFromModule,computeHotFiles,detectAwaitInLoop,detectBarrelExplosion,detectBoundaryViolations,detectCognitiveComplexity,detectCommonJsInEsm,detectCriticalPaths,detectDeadExports,detectDeadFiles,detectDeadReExports,detectDependencyCycles,detectDistanceFromMainSequence,detectDuplicateFlowStructures,detectDuplicateFunctionBodies,detectEmptyCatchBlocks,detectExcessiveParameters,detectExportStarLeak,detectFeatureEnvy,detectFunctionOptimization,detectGodFunctions,detectGodModuleCoupling,detectGodModules,detectHighCoupling,detectHighHalsteadEffort,detectImportSideEffectRisk,detectLayerViolations,detectListenerLeakRisk,detectLowCohesion,detectLowMaintainability,detectMegaFolders,detectMessageChains,detectMissingErrorBoundary,detectNamespaceImport,detectOrphanModules,detectPromiseMisuse,detectSdpViolations,detectSimilarFunctionBodies,detectSwitchNoDefault,detectSyncIo,detectTestOnlyModules,detectTypeAssertionEscape,detectUnboundedCollection,detectUnclearedTimers,detectUnreachableModules,detectUnsafeAny,detectUntestedCriticalCode,detectUnusedNpmDeps}from"./detectors/index.js";import{detectCommandInjectionRisk,detectDebugLogLeakage,detectEvalUsage,detectHardcodedSecrets,detectInputPassthroughRisk,detectPathTraversalRisk,detectPrototypePollutionRisk,detectSensitiveDataLogging,detectSqlInjectionRisk,detectUnsafeHtml,detectUnsafeRegex,detectUnvalidatedInputSink}from"./detectors/security.js";import{detectExcessiveMocking,detectFakeTimersWithoutRestore,detectFocusedTests,detectLowAssertionDensity,detectMissingMockRestoration,detectMissingTestCleanup,detectSharedMutableState,detectTestNoAssertion}from"./detectors/test-quality.js";import{diversifyFindings}from"./reporting/summary-md.js";import{SEVERITY_ORDER}from"./types/index.js";export{buildDependencySummary,computeDependencyCycles,computeDependencyCriticalPaths}from"./analysis/dependency-summary.js";export{REPORT_SCHEMA_VERSION,ARCHITECTURE_CATEGORIES,CODE_QUALITY_CATEGORIES,DEAD_CODE_CATEGORIES,SECURITY_CATEGORIES,TEST_QUALITY_CATEGORIES,writeMultiFileReport,generateMermaidGraph}from"./reporting/writer.js";export{severityBreakdown,categoryBreakdown,computeHealthScore,collectTagCloud,formatFileSize,diversifyFindings,diverseTopRecommendations,generateSummaryMd}from"./reporting/summary-md.js";export function buildIssueCatalog(e,t,o,s,c,n,i={},r={},d=new Map,a=[],f=new Map,l=[]){const u=[],p=e=>{n.features&&!n.features.has(e.category)||u.push(e)},{production:m,test:g}=buildConsumedFromModule(c);for(const t of detectDuplicateFunctionBodies(e))p(t);for(const e of detectDuplicateFlowStructures(t,n.flowDupThreshold))p(e);for(const e of detectFunctionOptimization(o,n.criticalComplexityThreshold))p(e);for(const e of detectTestOnlyModules(s))p(e);for(const e of detectDependencyCycles(s,c))p(e);for(const e of detectCriticalPaths(s,c,n.criticalComplexityThreshold))p(e);for(const e of detectDeadFiles(s,c))p(e);for(const e of detectDeadExports(c,m,g))p(e);for(const e of detectDeadReExports(c,m))p(e);for(const e of detectSdpViolations(c,n.sdpMinDelta,n.sdpMaxSourceInstability))p(e);for(const e of detectHighCoupling(c,n.couplingThreshold))p(e);for(const e of detectGodModuleCoupling(c,n.fanInThreshold,n.fanOutThreshold))p(e);for(const e of detectOrphanModules(c))p(e);for(const e of detectUnreachableModules(c))p(e);for(const e of detectUnusedNpmDeps(c.externalCounts,i,r))p(e);for(const e of detectBoundaryViolations(c))p(e);for(const e of detectBarrelExplosion(c,n.barrelSymbolThreshold))p(e);for(const e of detectGodModules(o,c,n.godModuleStatements,n.godModuleExports))p(e);for(const e of detectMegaFolders(o))p(e);for(const e of detectGodFunctions(o,n.godFunctionStatements,n.godFunctionMiThreshold))p(e);for(const e of detectCognitiveComplexity(o,n.cognitiveComplexityThreshold))p(e);if(n.layerOrder.length>=2)for(const e of detectLayerViolations(c,n.layerOrder))p(e);for(const e of detectLowCohesion(c))p(e);for(const e of detectDistanceFromMainSequence(c))p(e);for(const e of detectFeatureEnvy(c))p(e);const y=computeHotFiles(c,s,d);for(const e of detectUntestedCriticalCode(c,y,d))p(e);for(const e of detectImportSideEffectRisk(o,c,s,y))p(e);for(const e of detectNamespaceImport(c))p(e);for(const e of detectCommonJsInEsm(c))p(e);for(const e of detectExportStarLeak(c))p(e);for(const e of detectExcessiveParameters(o,n.parameterThreshold))p(e);for(const e of detectEmptyCatchBlocks(o))p(e);for(const e of detectSwitchNoDefault(o))p(e);for(const e of detectUnsafeAny(o,n.anyThreshold))p(e);for(const e of detectHighHalsteadEffort(o,n.halsteadEffortThreshold))p(e);for(const e of detectLowMaintainability(o,n.maintainabilityIndexThreshold))p(e);for(const e of detectTypeAssertionEscape(o))p(e);for(const e of detectMissingErrorBoundary(o))p(e);for(const e of detectPromiseMisuse(o))p(e);for(const e of detectAwaitInLoop(o))p(e);for(const e of detectSyncIo(o))p(e);for(const e of detectUnclearedTimers(o))p(e);for(const e of detectListenerLeakRisk(o))p(e);for(const e of detectUnboundedCollection(o))p(e);for(const e of detectMessageChains(o))p(e);for(const e of detectSimilarFunctionBodies(f,n.similarityThreshold))p(e);for(const e of detectHardcodedSecrets(o))p(e);for(const e of detectEvalUsage(o))p(e);for(const e of detectUnsafeHtml(o))p(e);for(const e of detectSqlInjectionRisk(o))p(e);for(const e of detectUnsafeRegex(o))p(e);for(const e of detectUnvalidatedInputSink(o))p(e);for(const e of detectInputPassthroughRisk(o))p(e);for(const e of detectPrototypePollutionRisk(o))p(e);for(const e of detectPathTraversalRisk(o))p(e);for(const e of detectCommandInjectionRisk(o))p(e);for(const e of detectDebugLogLeakage(o))p(e);for(const e of detectSensitiveDataLogging(o))p(e);for(const e of detectLowAssertionDensity(o))p(e);for(const e of detectTestNoAssertion(o))p(e);for(const e of detectExcessiveMocking(o,n.mockThreshold))p(e);for(const e of detectSharedMutableState(o))p(e);for(const e of detectMissingTestCleanup(o))p(e);for(const e of detectFocusedTests(o))p(e);for(const e of detectFakeTimersWithoutRestore(o))p(e);for(const e of detectMissingMockRestoration(o))p(e);for(const e of a)p(e);for(const e of l)p(e);const h=u.sort((e,t)=>{const o=SEVERITY_ORDER[t.severity]-SEVERITY_ORDER[e.severity];return 0!==o?o:e.category<t.category?-1:e.category>t.category?1:0}),{findings:E,totalBeforeTruncation:S,droppedCategories:C}=applyFindingsLimit(h,n),{findings:T,byFile:M}=assignFindingIds(E);return{allFindings:h,findings:T,byFile:M,totalBeforeTruncation:S,droppedCategories:C}}export function applyFindingsLimit(e,t){const o=e.length,s=new Set(e.map(e=>e.category)),c=t.findingsLimit,n=Number.isFinite(c)&&null!=c?t.noDiversify?e.slice(0,c):diversifyFindings(e,c):e,i=new Set(n.map(e=>e.category));return{findings:n,totalBeforeTruncation:o,droppedCategories:[...s].filter(e=>!i.has(e))}}export function assignFindingIds(e){const t=[],o=new Map;for(const[s,c]of e.entries()){const e=`AST-ISSUE-${String(s+1).padStart(4,"0")}`,n={id:e,...c};t.push(n),n.file&&(o.has(n.file)||o.set(n.file,[]),o.get(n.file).push(e))}return{findings:t,byFile:o}}isDirectRun(import.meta.url)&&import("./pipeline/main.js").then(e=>e.main()).catch(e=>{console.error(e),process.exit(1)});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import fs from"node:fs";import path from"node:path";export const ANALYSIS_SCHEMA_VERSION="1.1.0";const CACHE_VERSION=1,DEFAULT_MAX_AGE_MS=6048e5;export function loadCache(e){const t=path.join(e,".octocode","scan",".cache","analysis-cache.json");try{const n=JSON.parse(fs.readFileSync(t,"utf8"));return 1!==n.version||n.root!==e||"1.1.0"!==n.schemaVersion?null:n}catch{return null}}export function saveCache(e,t){const n=path.join(e,".octocode","scan",".cache");fs.mkdirSync(n,{recursive:!0}),fs.writeFileSync(path.join(n,"analysis-cache.json"),JSON.stringify(t),"utf8")}export function clearCache(e){const t=path.join(e,".octocode","scan",".cache","analysis-cache.json");try{fs.unlinkSync(t)}catch{}}export function isCacheHit(e,t,n){if(!e)return!1;const s=e.entries[t];return!!s&&(s.mtimeMs===n.mtimeMs&&s.sizeBytes===n.size)}export function getCachedResult(e,t){const n=e.entries[t];return n&&(n.lastAccessMs=Date.now()),n?.result}export function setCacheEntry(e,t,n,s){e.entries[t]={mtimeMs:n.mtimeMs,sizeBytes:n.size,result:s,lastAccessMs:Date.now()}}export function createEmptyCache(e){return{version:1,schemaVersion:"1.1.0",root:e,entries:{}}}export function garbageCollect(e,t=6048e5){const n=Date.now(),s=[];for(const[c,o]of Object.entries(e.entries))n-o.lastAccessMs>t&&s.push(c);for(const t of s)delete e.entries[t];return s.length}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import path from"node:path";import{ALL_CATEGORIES,DEFAULT_OPTS,PILLAR_CATEGORIES}from"../types/index.js";function parseNumeric(e,t){const n=parseInt(e??"",10);return Number.isNaN(n)?t:n}function parseDecimal(e,t){const n=parseFloat(e??"");return Number.isNaN(n)?t:n}function resolveCategories(e,t){const n=e.split(",").map(e=>e.trim()).filter(Boolean),o=new Set;for(const e of n)if(PILLAR_CATEGORIES[e])for(const t of PILLAR_CATEGORIES[e])o.add(t);else ALL_CATEGORIES.has(e)?o.add(e):(console.error(`Unknown ${t}: "${e}". Use pillar names (${Object.keys(PILLAR_CATEGORIES).join(", ")}) or category names.`),process.exit(1));return o}function parseScope(e,t){const n=[],o=new Map;for(const s of e.split(",").map(e=>e.trim()).filter(Boolean)){const e=s.lastIndexOf(":");if(e>0&&!s.substring(0,e).includes(":")){const r=s.substring(0,e),i=s.substring(e+1),a=path.resolve(t,r);n.push(a),o.has(a)||o.set(a,[]),o.get(a).push(i)}else n.push(path.resolve(t,s))}return{paths:n,symbols:o}}const BOOL_FLAGS={"--json":e=>{e.json=!0},"--include-tests":e=>{e.includeTests=!0},"--emit-tree":e=>{e.emitTree=!0},"--no-tree":e=>{e.emitTree=!1},"--graph":e=>{e.graph=!0},"--semantic":e=>{e.semantic=!0},"--no-diversify":e=>{e.noDiversify=!0},"--no-cache":e=>{e.noCache=!0},"--clear-cache":e=>{e.clearCache=!0},"--graph-advanced":e=>{e.graphAdvanced=!0},"--flow":e=>{e.flow=!0},"--all":e=>{e.includeTests=!0,e.semantic=!0}},INT_FLAGS={"--findings-limit":"findingsLimit","--min-function-statements":"minFunctionStatements","--min-flow-statements":"minFlowStatements","--critical-complexity-threshold":"criticalComplexityThreshold","--deep-link-topn":"deepLinkTopN","--tree-depth":"treeDepth","--coupling-threshold":"couplingThreshold","--fan-in-threshold":"fanInThreshold","--fan-out-threshold":"fanOutThreshold","--god-module-statements":"godModuleStatements","--god-module-exports":"godModuleExports","--god-function-statements":"godFunctionStatements","--god-function-mi-threshold":"godFunctionMiThreshold","--cognitive-complexity-threshold":"cognitiveComplexityThreshold","--barrel-symbol-threshold":"barrelSymbolThreshold","--parameter-threshold":"parameterThreshold","--halstead-effort-threshold":"halsteadEffortThreshold","--maintainability-index-threshold":"maintainabilityIndexThreshold","--any-threshold":"anyThreshold","--flow-dup-threshold":"flowDupThreshold","--max-recs-per-category":"maxRecsPerCategory","--override-chain-threshold":"overrideChainThreshold","--shotgun-threshold":"shotgunThreshold","--secret-min-length":"secretMinLength","--mock-threshold":"mockThreshold"},FLOAT_FLAGS={"--secret-entropy-threshold":"secretEntropyThreshold","--similarity-threshold":"similarityThreshold","--sdp-min-delta":"sdpMinDelta","--sdp-max-source-instability":"sdpMaxSourceInstability"},SPECIAL_FLAGS={"--parser":(e,t,n)=>{const o=t[n+1];return["auto","typescript","tree-sitter"].includes(o)||(console.error(`Unsupported parser: ${o}. Use auto|typescript|tree-sitter`),process.exit(1)),e.parser=o,n+1},"--root":(e,t,n)=>(e.root=path.resolve(t[n+1]),n+1),"--out":(e,t,n)=>(e.out=t[n+1],n+1),"--layer-order":(e,t,n)=>(e.layerOrder=t[n+1].split(",").map(e=>e.trim()),n+1),"--help":()=>(printHelp(),process.exit(0)),"-h":()=>(printHelp(),process.exit(0))};export function parseArgs(e){const t={...DEFAULT_OPTS};let n=null;for(let o=0;o<e.length;o++){const s=e[o];if(BOOL_FLAGS[s])BOOL_FLAGS[s](t,!0);else{if(INT_FLAGS[s]){const n=INT_FLAGS[s];t[n]=parseNumeric(e[++o],DEFAULT_OPTS[n]);continue}if(FLOAT_FLAGS[s]){const n=FLOAT_FLAGS[s];t[n]=parseDecimal(e[++o],DEFAULT_OPTS[n]);continue}if(SPECIAL_FLAGS[s])o=SPECIAL_FLAGS[s](t,e,o);else if(s.startsWith("--out="))t.out=s.slice(6);else{if("--scope"===s||s.startsWith("--scope=")){const n=s.includes("=")?s.split("=")[1]:e[++o],{paths:r,symbols:i}=parseScope(n,t.root);t.scope=r,i.size>0&&(t.scopeSymbols=i);continue}if("--features"===s||s.startsWith("--features=")){const n=s.startsWith("--features=")?s.slice(11):e[++o];t.features=resolveCategories(n,"feature");continue}if("--exclude"===s||s.startsWith("--exclude=")){const t=s.startsWith("--exclude=")?s.slice(10):e[++o];n=resolveCategories(t,"exclude");continue}}}}if(t.packageRoot=path.join(t.root,"packages"),null!==t.features&&null!==n&&(console.error("--features and --exclude are mutually exclusive. Use one or the other."),process.exit(1)),null!==n&&(t.features=new Set([...ALL_CATEGORIES].filter(e=>!n.has(e)))),null!==t.features){const e=new Set(PILLAR_CATEGORIES["test-quality"]);[...t.features].some(t=>e.has(t))&&(t.includeTests=!0)}return t}export function printHelp(){console.log("\nUsage:\n node scripts/index.js [options]\n\nOptions:\n --root <path> Analyze a different repo root (default: cwd)\n --out <path> Output directory for split report files (timestamped dir by default).\n If path ends with .json, writes single monolithic file (legacy mode).\n --json Print report JSON to stdout\n --include-tests Include *.test* and *.spec* files\n --parser <auto|typescript|tree-sitter>\n Parser engine for extra AST metadata (default: auto)\n --no-tree Do not write AST trees to report\n --emit-tree Force include tree blocks\n --graph Emit Mermaid dependency graph to .md file alongside JSON\n --graph-advanced Enable advanced graph overlays and additional architecture findings\n --flow Enable lightweight flow enrichment for evidence traces and cfgFlags\n --min-function-statements N Minimum function body statement count for duplicate matching (default 6)\n --min-flow-statements N Minimum control-flow statement count for duplicate matching (default 6)\n --critical-complexity-threshold N\n Complexity threshold for HIGH complexity findings and critical path weighting.\n --findings-limit N Cap findings in the report (default: no limit)\n --deep-link-topn N Max number of critical dependency paths to report (default 12)\n --tree-depth N AST tree depth when tree snapshots are emitted (default 4)\n --coupling-threshold N Ca+Ce threshold for high-coupling findings (default 15)\n --fan-in-threshold N Fan-in threshold for god-module-coupling (default 20)\n --fan-out-threshold N Fan-out threshold for god-module-coupling (default 15)\n --god-module-statements N Statement threshold for god-module findings (default 500)\n --god-module-exports N Export threshold for god-module findings (default 20)\n --god-function-statements N Statement threshold for god-function findings (default 100)\n --god-function-mi-threshold N MI threshold for god-function findings (default 10, fires when MI < N and LOC > 30)\n --cognitive-complexity-threshold N\n Cognitive complexity threshold for findings (default 15)\n --barrel-symbol-threshold N Re-export count threshold for barrel-explosion (default 30)\n --layer-order <layers> Comma-separated layer names for violation detection (e.g. ui,service,repository)\n --parameter-threshold N Max function parameters before flagging (default 5)\n --halstead-effort-threshold N Halstead effort threshold for findings (default 500000)\n --maintainability-index-threshold N\n MI below this triggers a finding (default 20, scale 0-100)\n --any-threshold N Max `any` type usages per file before flagging (default 5)\n --flow-dup-threshold N Min occurrences for a repeated flow to become a finding (default 3)\n --max-recs-per-category N Max findings per category in top recommendations (default 2)\n --scope=X,Y,Z Limit scan to specific paths, files, or functions. Comma-separated.\n Supports file:functionName to drill into a specific function.\n Examples: --scope=packages/octocode-mcp\n --scope=packages/octocode-mcp/src/tools\n --scope=packages/octocode-mcp/src/session.ts\n --scope=packages/octocode-mcp/src/session.ts:initSession\n --scope=packages/foo,packages/bar\n --features=X,Y,Z Run only selected features. Accepts pillar names (architecture,\n code-quality, dead-code, security, test-quality) or individual\n category names. Comma-separated.\n Examples: --features=architecture\n --features=dead-code,cognitive-complexity\n --features=dependency-cycle,dead-export\n --exclude=X,Y,Z Run everything EXCEPT the given pillars or categories. Mutually\n exclusive with --features. Same pillar/category names as --features.\n Examples: --exclude=architecture\n --exclude=dead-export,unsafe-any\n --semantic Enable semantic analysis phase (TypeChecker + LanguageService).\n Adds 14 categories: over-abstraction, concrete-dependency,\n circular-type-dependency, unused-parameter,\n deep-override-chain, interface-compliance, unused-import,\n orphan-implementation, shotgun-surgery, move-to-caller,\n narrowable-type, semantic-dead-export.\n --override-chain-threshold N Max method override depth before flagging (default 3, requires --semantic)\n --shotgun-threshold N Unique-file threshold for shotgun-surgery (default 8, requires --semantic)\n --sdp-min-delta N Min instability delta for SDP violations (default 0.15)\n --sdp-max-source-instability N Max source instability to report SDP (default 0.6)\n --secret-entropy-threshold N Shannon entropy threshold for secret detection (default 4.5)\n --secret-min-length N Min string length for entropy-based secret detection (default 20)\n --similarity-threshold N Jaccard similarity threshold for near-clone detection (default 0.85)\n --mock-threshold N Max mock/spy calls per test file (default 10)\n --no-diversify Disable category-aware diversification when truncating findings.\n By default, --findings-limit interleaves categories so the\n truncated list is diverse. Use this to get pure severity ordering.\n --no-cache Disable incremental cache; re-parse all files\n --clear-cache Delete the analysis cache and exit (no scan)\n --all Enable all features: --include-tests --semantic\n --help Show this message\n")}
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import fs from"node:fs";import path from"node:path";import*as ts from"typescript";import{clearCache,createEmptyCache,garbageCollect,getCachedResult,isCacheHit,loadCache,saveCache,setCacheEntry}from"./cache.js";import{parseArgs}from"./cli.js";import{collectDependencyProfile}from"../analysis/dependencies.js";import{buildDependencySummary}from"../analysis/dependency-summary.js";import{collectFiles,fileSummaryWithFindings,listWorkspacePackages,safeRead}from"../analysis/discovery.js";import{buildAdvancedGraphFindings,computeGraphAnalytics}from"../analysis/graph-analytics.js";import{analyzeSemanticProfile,collectAllAbsoluteFiles,createSemanticContext}from"../analysis/semantic.js";import{analyzeTreeSitterFile,resolveTreeSitter}from"../ast/tree-sitter.js";import{analyzeSourceFile,buildDependencyCriticality}from"../ast/ts-analyzer.js";import{isDirectRun}from"../common/is-direct-run.js";import{canonicalScriptKind,increment}from"../common/utils.js";import{computeHotFiles}from"../detectors/index.js";import{runSemanticDetectors}from"../detectors/semantic.js";import{applyFindingsLimit,assignFindingIds,buildIssueCatalog}from"../index.js";import{computeReportAnalysisSummary,enrichFileInventoryEntries,enrichFindings}from"../reporting/analysis.js";import{diverseTopRecommendations}from"../reporting/summary-md.js";import{generateMermaidGraph,writeMultiFileReport}from"../reporting/writer.js";import{PILLAR_CATEGORIES,SEMANTIC_CATEGORIES}from"../types/index.js";function discoverPackages(e,t){let n=listWorkspacePackages(e,t);if(!n.length){const o=path.join(e,"package.json");if(fs.existsSync(o))try{const t=JSON.parse(fs.readFileSync(o,"utf8"));n=[{name:"string"==typeof t.name?t.name:path.basename(e),dir:e,folder:path.basename(e)}]}catch{console.error(`No packages found in ${t} and root package.json is unreadable`),process.exit(1)}else console.error(`No packages found in ${t} and no package.json in root`),process.exit(1)}return n}function groupDuplicates(e,t,n){const o=[...e.entries()].map(([e,t])=>{if(t.length<2)return null;const[n]=t,[o]=e.split("|"),s=n.name||n.kind||"<flow>",i=[...new Set(t.map(e=>e.file))];return{hash:o,signature:s,kind:n.kind,occurrences:t.length,filesCount:i.length,locations:t.slice(0,20)}}).filter(e=>null!==e).sort((e,t)=>t.occurrences-e.occurrences),s=[...t.entries()].map(([e,t])=>{if(t.length<=1)return null;const[,n]=e.split("|"),o=[...new Set(t.map(e=>e.file))];return{kind:n,occurrences:t.length,filesCount:o.length,locations:t.slice(0,20)}}).filter(e=>null!==e).sort((e,t)=>t.occurrences-e.occurrences),i=[];for(const e of o.slice(0,200))e.occurrences>=2&&i.push({type:"duplicate-function-body",message:`Identical function body for ${e.signature}`,file:e.locations[0]?.file,lineStart:e.locations[0]?.lineStart,lineEnd:e.locations[0]?.lineEnd,details:`Occurs ${e.occurrences} times in ${e.filesCount} files.`});for(const[e,t]of s.slice(0,100).entries())if(t.occurrences>=n&&i.push({type:"repeated-flow",message:`Repeated ${t.kind} control structure`,file:t.locations[0]?.file,lineStart:t.locations[0]?.lineStart,lineEnd:t.locations[0]?.lineEnd,details:`Structure appears ${t.occurrences} times across ${t.filesCount} file(s).`}),e>100)break;return{duplicateFunctions:o,redundantFlows:s,duplicateFlowHints:i}}function runSemanticPhase(e,t,n,o){if(!n.semantic)return[];if(!(!n.features||[...SEMANTIC_CATEGORIES].some(e=>n.features.has(e))))return[];try{const o=collectAllAbsoluteFiles(e,t,n.root),s=createSemanticContext(o,n.root),i=[];for(const t of e){const e=path.resolve(n.root,t.file);try{i.push(analyzeSemanticProfile(s,e,t,n.includeTests))}catch{}}return runSemanticDetectors(s,i,{overrideChainThreshold:n.overrideChainThreshold,shotgunThreshold:n.shotgunThreshold})}catch(e){return o.push({file:"<semantic>",message:`Semantic analysis failed: ${String(e?.message||e)}`}),[]}}function applyScopeFilter(e,t,n){if(!t.scope)return e;const o=e=>{const n=path.resolve(t.root,e);return t.scope.some(e=>{const t=path.normalize(e),o=path.normalize(n);return o===t||o.startsWith(t+path.sep)})};let s=e.filter(e=>o(e.file)||(e.files?.some(o)??!1));if(t.scopeSymbols&&t.scopeSymbols.size>0){const e=[],o=[];for(const[s,i]of t.scopeSymbols){const r=path.relative(t.root,s),l=n.find(e=>e.file===r);if(l)for(const t of i){const n=l.functions.find(e=>e.name===t);if(n){e.push({file:r,lineStart:n.lineStart,lineEnd:n.lineEnd,name:t});continue}const s=l.dependencyProfile?.declaredExports?.find(e=>e.name===t&&null!=e.lineStart&&null!=e.lineEnd);s?e.push({file:r,lineStart:s.lineStart,lineEnd:s.lineEnd,name:t}):o.push(`${r}:${t}`)}else for(const e of i)o.push(`${r}:${e}`)}if(o.length>0&&console.warn(`Warning: symbol scope could not resolve: ${o.join(", ")}. Falling back to file-level scope for those entries.`),e.length>0){const t=(e,t,n,o)=>e<=o&&t>=n;s=s.filter(n=>e.some(e=>n.file===e.file&&t(n.lineStart,n.lineEnd,e.lineStart,e.lineEnd)))}}return s}function printConsoleResults(e,t,n,o,s,i,r,l){console.log(`AST analysis complete: ${e.totalFiles} files, ${e.totalFunctions} functions, ${e.totalFlows} flow nodes`),e.totalDependencyFiles!==e.totalFiles&&console.log(`Dependency scan analyzed ${e.totalDependencyFiles} files (including tests where present).`),console.log(`Duplicate function bodies: ${t.length}`);for(const e of t.slice(0,20))console.log(`- ${e.kind} "${e.signature}" occurs ${e.occurrences}x in ${e.filesCount} file(s)`);console.log(`\nRepeated control-flow structures: ${n.length}`);for(const e of n.slice(0,20))console.log(`- ${e.kind} appears ${e.occurrences}x across ${e.filesCount} file(s)`);console.log(`\nDependency graph: ${o.totalModules} modules, ${o.totalEdges} import edges`),o.totalModules>0&&(console.log(`- Critical chains: ${o.criticalPaths.length} (showing top ${Math.min(r.deepLinkTopN,o.criticalPaths.length)})`),console.log(`- Root modules: ${o.rootsCount}, Leaf modules: ${o.leavesCount}`),console.log(`- Test-only modules: ${o.testOnlyModules.length}`),console.log(`- Cycles: ${o.cycles.length}`)),console.log(`\nAgent Findings: ${s.length}`);for(const e of s.slice(0,20))console.log(`- [${e.severity.toUpperCase()}] ${e.title}`),console.log(` - ${e.reason}`),console.log(` - fix: ${e.suggestedFix.strategy}`);i.length>0&&(console.log(`\nParse errors: ${i.length}`),i.slice(0,10).forEach(e=>{console.log(`- ${e.file}: ${e.message}`)})),console.log(`\nParser engine used: ${l}`)}async function initScanState(){const e=parseArgs(process.argv.slice(2));if(e.clearCache)return clearCache(e.root),console.error("Cache cleared."),null;const t=(new Date).toISOString().replace(/[:.]/g,"-"),n=e.out?.endsWith(".json")??!1,o=n?null:e.out||path.join(e.root,".octocode","scan",t),s=n?e.out??null:null,i=discoverPackages(e.root,e.packageRoot);let r=e.parser;const l="tree-sitter"===e.parser||"auto"===e.parser?await resolveTreeSitter():{available:!1,error:null,parserTs:null,parserTsx:null},a=("tree-sitter"===e.parser||"auto"===e.parser)&&Boolean(l?.available);"tree-sitter"!==e.parser||l?.available||(console.warn(`Tree-sitter requested but unavailable: ${l?.error||"missing parser modules"}`),console.warn("Falling back to TypeScript parser for duplicate detection."),r="typescript"),"tree-sitter"===e.parser&&l?.available&&(r="tree-sitter (primary) + typescript (dependencies)"),"auto"===e.parser&&l?.available&&(r="typescript (primary) + tree-sitter (node count)");const c={},p={};for(const e of i)try{const t=JSON.parse(fs.readFileSync(path.join(e.dir,"package.json"),"utf8"));Object.assign(c,t.dependencies||{}),Object.assign(p,t.devDependencies||{})}catch{}return{options:e,packages:i,effectiveParser:r,useTreeSitter:a,summary:{totalPackages:i.length,totalFiles:0,totalNodes:0,totalFunctions:0,totalFlows:0,totalDependencyFiles:0,byPackage:{}},flowMap:new Map,controlMap:new Map,trees:[],fileSummaries:[],parseErrors:[],dependencyState:{files:new Set,outgoing:new Map,incoming:new Map,incomingFromTests:new Map,incomingFromProduction:new Map,externalCounts:new Map,unresolvedCounts:new Map,declaredExportsByFile:new Map,importedSymbolsByFile:new Map,reExportsByFile:new Map},packageFileStats:Object.fromEntries(i.map(e=>[e.name,{fileCount:0,nodeCount:0,functionCount:0,flowCount:0,kindCounts:{},functions:[],flows:[]}])),allPkgJsonDeps:c,allPkgJsonDevDeps:p,cacheHits:0,isLegacyMode:n,outputDir:o,outputPath:s,treeSitterAvailable:a,treeSitterError:l?.available?null:l?.error||null}}function collectFileData(e){const{options:t,packages:n,useTreeSitter:o,summary:s,flowMap:i,controlMap:r,trees:l,fileSummaries:a,parseErrors:c,dependencyState:p,packageFileStats:u}=e,f=t.noCache?null:loadCache(t.root),d=createEmptyCache(t.root);for(const m of n){let n=u[m.name];n||(n={fileCount:0,nodeCount:0,functionCount:0,flowCount:0,kindCounts:{},functions:[],flows:[]},u[m.name]=n);const g=collectFiles(m.dir,t),h=collectFiles(m.dir,{...t,includeTests:!0}),y=e=>null!=t.scope&&t.scope.some(t=>{const n=path.normalize(t),o=path.normalize(e);return o===n||o.startsWith(n+path.sep)}),S=t.scope?g.filter(e=>y(e)):g,C=new Set(S);for(const u of h){const g=safeRead(u);if(null===g){c.push({file:path.relative(t.root,u),message:"Failed to read file"});continue}const h=path.extname(u),y=ts.createSourceFile(u,g,ts.ScriptTarget.ESNext,!0,canonicalScriptKind(h));try{const c=collectDependencyProfile(y,u,m.name,t,p);if(!C.has(u))continue;const h=path.relative(t.root,u),S=fs.statSync(u),F={mtimeMs:S.mtimeMs,size:S.size};if(f&&isCacheHit(f,h,F)){const t=getCachedResult(f,h);if(t?.fileEntry){for(const[e,n]of t.flowMapEntries??[])for(const t of n)increment(i,e,t);for(const[e,n]of t.controlMapEntries??[])for(const t of n)increment(r,e,t);const o={...t.fileEntry,dependencyProfile:c};n.fileCount+=1,n.nodeCount+=o.nodeCount,n.functionCount+=o.functions.length,n.flowCount+=o.flows.length;for(const[e,t]of Object.entries(o.kindCounts))n.kindCounts[e]=(n.kindCounts[e]||0)+t;for(const e of o.functions)n.functions.push(e);t.treeEntry&&l.push(t.treeEntry),s.totalFiles+=1,s.totalNodes+=o.nodeCount,s.totalFunctions+=o.functions.length,s.totalFlows+=o.flows.length,a.push(o),setCacheEntry(d,h,F,t),e.cacheHits++;continue}}const w=new Map,v=new Map;let E;if(o&&"tree-sitter"===t.parser){const e=analyzeTreeSitterFile(u,g,t,m.name,{flowMap:w,controlMap:v});if(e){const o=path.relative(t.root,u);E={package:m.name,file:o,parseEngine:"tree-sitter",nodeCount:e.nodeCount,kindCounts:{},functions:e.functions,flows:e.flows,dependencyProfile:c},e.tree&&t.emitTree&&l.push({package:m.name,file:o,tree:e.tree}),n.fileCount+=1,n.nodeCount+=e.nodeCount,n.functionCount+=e.functions.length,n.flowCount+=e.flows.length;for(const t of e.functions)n.functions.push(t)}else{const e=analyzeSourceFile(y,m.name,n,t,{flowMap:w,controlMap:v},l,c);e.parserFallback="typescript (tree-sitter failed)",E=e}}else if(E=analyzeSourceFile(y,m.name,n,t,{flowMap:w,controlMap:v},l,c),o)try{const e=analyzeTreeSitterFile(u,g,t,m.name,null);e&&(E.treeSitterNodeCount=e.nodeCount)}catch(e){E.treeSitterError=String(e?.message||e)}for(const[e,t]of w)for(const n of t)increment(i,e,n);for(const[e,t]of v)for(const n of t)increment(r,e,n);const b=t.emitTree?l.find(e=>e.file===h):void 0,$={fileEntry:E,flowMapEntries:[...w.entries()],controlMapEntries:[...v.entries()],...b&&{treeEntry:b}};setCacheEntry(d,h,F,$),s.totalFiles+=1,s.totalNodes+=E.nodeCount,s.totalFunctions+=E.functions.length,s.totalFlows+=E.flows.length,a.push(E)}catch(e){c.push({file:path.relative(t.root,u),message:String(e?.message||e)})}}s.byPackage[m.name]={files:n.fileCount,nodes:n.nodeCount,functions:n.functionCount,flows:n.flowCount,topKinds:Object.entries(n.kindCounts).sort((e,t)=>t[1]-e[1]).slice(0,8),rootPath:m.folder}}t.noCache||(garbageCollect(d),saveCache(t.root,d)),e.cacheHits>0&&!t.json&&console.error(`Cache: ${e.cacheHits} hits, ${a.length-e.cacheHits} misses`),s.totalDependencyFiles=p.files.size}function analyzeAndReport(e){const{options:t,effectiveParser:n,summary:o,flowMap:s,controlMap:i,trees:r,fileSummaries:l,parseErrors:a,dependencyState:c,allPkgJsonDeps:p,allPkgJsonDevDeps:u,isLegacyMode:f,outputDir:d,outputPath:m,treeSitterAvailable:g,treeSitterError:h}=e,{duplicateFunctions:y,redundantFlows:S,duplicateFlowHints:C}=groupDuplicates(s,i,t.flowDupThreshold),F=new Map(l.map(e=>[e.file,buildDependencyCriticality(e,t)])),w=buildDependencySummary(c,F,t),v=computeGraphAnalytics(c,w,F),E=t.graphAdvanced?buildAdvancedGraphFindings(v,c,l):[],b=runSemanticPhase(l,c,t,a);let $=buildIssueCatalog(y,S,l,w,c,t,p,u,F,b,s,E).allFindings;$=applyScopeFilter($,t,l);const{findings:k,totalBeforeTruncation:M,droppedCategories:j}=applyFindingsLimit($,t),T=assignFindingIds(k);let D=T.findings;const P=T.byFile,A=buildFindingStats($),R=enrichFileInventoryEntries(l,{flowEnabled:!!t.flow}),x=computeHotFiles(c,w,F);D=enrichFindings(D,R,x,v,{flowEnabled:!!t.flow});const z=computeReportAnalysisSummary(D,R,x,v),O=fileSummaryWithFindings(R,P),I={generatedAt:(new Date).toISOString(),repoRoot:t.root,options:{...t,ignoreDirs:[...t.ignoreDirs]},parser:{requested:t.parser,effective:n,treeSitterAvailable:g,treeSitterError:h},summary:o,fileInventory:O,duplicateFlows:{duplicatedFunctions:y.slice(0,200),duplicatedControlFlow:S.slice(0,200),totalFunctionGroups:y.length,totalFlowGroups:S.length},dependencyGraph:w,dependencyFindings:D.filter(e=>e.category?.startsWith("dependency")),agentOutput:{totalFindings:D.length,totalBeforeTruncation:M,droppedCategories:j,findingStats:A,analysisSummary:{strongestGraphSignal:z.strongestGraphSignal,strongestAstSignal:z.strongestAstSignal,combinedSignals:z.combinedSignals,recommendedValidation:z.recommendedValidation},highPriority:D.filter(e=>"high"===e.severity||"critical"===e.severity).length,mediumPriority:D.filter(e=>"medium"===e.severity).length,lowPriority:D.filter(e=>"low"===e.severity||"info"===e.severity).length,topRecommendations:diverseTopRecommendations(D,20,t.maxRecsPerCategory).map(e=>({id:e.id,file:e.file,severity:e.severity,category:e.category,title:e.title,reason:e.reason,suggestedFix:e.suggestedFix})),filesWithIssues:[...P.entries()].map(([e,t])=>({file:e,issueCount:t.length,issueIds:t}))},optimizationOpportunities:C,optimizationFindings:D,parseErrors:a,astTrees:void 0,graphAnalytics:v,reportAnalysis:z};if(t.emitTree&&(I.astTrees=r),t.json?console.log(JSON.stringify(I)):printConsoleResults(o,y,S,w,D,a,t,I.parser.effective),f&&m){if(fs.mkdirSync(path.dirname(m),{recursive:!0}),fs.writeFileSync(m,JSON.stringify(I),"utf8"),t.json||console.log(`\nFull report written to ${path.relative(t.root,m)}`),t.graph){const e=generateMermaidGraph(c,w,F),n=m.replace(/\.json$/,"-graph.md");fs.writeFileSync(n,e,"utf8"),t.json||console.log(`Dependency graph written to ${path.relative(t.root,n)}`)}}else if(d){const e=writeMultiFileReport(d,I,t,c,w,F);if(!t.json){const n=path.relative(t.root,d);console.log(`\nReport written to ${n}/`);for(const[t,n]of Object.entries(e))console.log(` ${t}: ${n}`)}}}async function main(){const e=await initScanState();e&&(collectFileData(e),analyzeAndReport(e))}export{main};function buildFindingStats(e){const t=e=>{const t={critical:0,high:0,medium:0,low:0,info:0};for(const n of e)t[n.severity]=(t[n.severity]||0)+1;return t};return{overall:{totalFindings:e.length,severityBreakdown:t(e)},pillars:Object.fromEntries(Object.entries(PILLAR_CATEGORIES).map(([n,o])=>{const s=new Set(o),i=e.filter(e=>s.has(e.category));return[n,{totalFindings:i.length,severityBreakdown:t(i)}]}))}}isDirectRun(import.meta.url)&&main().catch(e=>{console.error(e),process.exit(1)});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
function buildEffectProfile(e){const t=e.topLevelEffects||[];if(0===t.length)return;const n={};let i=0,o=null,s=-1;for(const e of t)n[e.kind]=(n[e.kind]||0)+1,i+=e.weight,e.weight>s&&(s=e.weight,o=e.kind);return{totalEffects:t.length,totalWeight:i,byKind:n,highestRisk:o}}function buildSymbolUsageSummary(e){const t=e.dependencyProfile.importedSymbols.filter(e=>!!e.resolvedModule),n=0===t.length?null:[...t.reduce((e,t)=>{const n=t.resolvedModule||t.sourceModule;return e.set(n,(e.get(n)||0)+1),e},new Map).entries()].sort((e,t)=>t[1]-e[1])[0]?.[0]||null;return{declaredExportCount:e.dependencyProfile.declaredExports.length,importedSymbolCount:e.dependencyProfile.importedSymbols.length,internalImportCount:t.length,externalImportCount:e.dependencyProfile.externalDependencies.length,reExportCount:e.dependencyProfile.reExports.length,dominantInternalDependency:n}}function buildBoundaryRoleHints(e){const t=e.file.replace(/\\/g,"/").toLowerCase(),n=[],i=(e,t,i)=>{n.push({role:e,confidence:t,reasons:i})};if(/(^|\/)(index|main|app|server|cli)\.[mc]?[jt]sx?$/.test(t)&&i("entrypoint","high",["path matches a conventional entrypoint filename"]),/(^|\/)(components|ui|pages|screens)\//.test(t)&&i("ui","high",["path indicates UI-facing code"]),/(^|\/)(routes|controllers|handlers|http|api)\//.test(t)&&i("transport","medium",["path indicates controller, handler, or API transport code"]),/(^|\/)(service|services|use-cases|usecases)\//.test(t)&&i("service","medium",["path indicates orchestration or service logic"]),/(^|\/)(repo|repos|repository|repositories|db|persistence)\//.test(t)&&i("persistence","high",["path indicates persistence or repository code"]),(e.topLevelEffects?.length||0)>0&&i("runtime-bootstrap","medium",["module has import-time side effects"]),e.dependencyProfile.declaredExports.length>=8&&e.dependencyProfile.importedSymbols.length<=2&&i("shared-utility","medium",["exports many symbols and imports little internal behavior"]),0!==n.length)return n.slice(0,3)}function buildCfgFlags(e){return{hasValidationChecks:(e.inputSources||[]).some(e=>e.hasValidation),hasCleanupHooks:!!e.testProfile?.setupCalls.some(e=>"afterAll"===e.kind||"afterEach"===e.kind),exitPointCount:e.functions.reduce((e,t)=>e+t.returns,0),asyncBoundaryCount:e.functions.reduce((e,t)=>e+t.awaits,0),hasTopLevelEffects:(e.topLevelEffects?.length||0)>0}}export function enrichFileInventoryEntries(e,t={}){const{flowEnabled:n=!1}=t;return e.map(e=>({...e,effectProfile:e.effectProfile||buildEffectProfile(e),symbolUsageSummary:e.symbolUsageSummary||buildSymbolUsageSummary(e),boundaryRoleHints:e.boundaryRoleHints||buildBoundaryRoleHints(e)||[],cfgFlags:n?e.cfgFlags||buildCfgFlags(e):void 0}))}function inferAnalysisLens(e){return["dependency-cycle","dependency-critical-path","architecture-sdp-violation","high-coupling","god-module-coupling","orphan-module","unreachable-module","cycle-cluster","broker-module","bridge-module","mega-folder"].includes(e)?"graph":["layer-violation","low-cohesion","feature-envy","import-side-effect-risk","package-boundary-chatter","startup-risk-hub","unvalidated-input-sink","input-passthrough-risk","missing-test-cleanup","fake-timer-no-restore","missing-mock-restoration"].includes(e)?"hybrid":e.startsWith("dependency-")?"graph":e.startsWith("test-")||"focused-test"===e||["hardcoded-secret","eval-usage","unsafe-html","sql-injection-risk","unsafe-regex","prototype-pollution-risk","path-traversal-risk","command-injection-risk","debug-log-leakage","sensitive-data-logging"].includes(e)||["over-abstraction","concrete-dependency","circular-type-dependency","unused-parameter","deep-override-chain","interface-compliance","unused-import","orphan-implementation","shotgun-surgery","move-to-caller","narrowable-type","semantic-dead-export"].includes(e)?"hybrid":"ast"}function defaultConfidence(e){return e.confidence?e.confidence:"critical"===e.severity?"high":"graph"===e.analysisLens||"hybrid"===e.analysisLens?"medium":"low"}function parseFlowTraceSteps(e,t){if(!t)return;if(e.flowTrace&&e.flowTrace.length>0)return e.flowTrace;const n=e.evidence?.propagationSteps;if(!Array.isArray(n))return;const i=n.map(e=>{if("string"!=typeof e)return null;const t=e.match(/^(.*?):(\d+)(?:-(\d+))?$/);return t?{file:t[1],lineStart:Number(t[2]),lineEnd:Number(t[3]||t[2]),label:"propagation step"}:null}).filter(e=>null!==e);return i.length>0?i:void 0}function buildRecommendedValidation(e){const t=e.lspHints?.[0];return t?{summary:t.expectedResult,tools:["localSearchCode",t.tool]}:"graph"===e.analysisLens?{summary:"Confirm the dependency edge or hub behavior with localSearchCode and an LSP navigation step.",tools:["localSearchCode","lspGotoDefinition"]}:"hybrid"===e.analysisLens?{summary:"Validate both the structural location and the behavioral path before presenting the claim as fact.",tools:["localSearchCode","lspCallHierarchy"]}:{summary:"Confirm the code location and inspect the matched structure before proposing a refactor.",tools:["localSearchCode"]}}export function enrichFindings(e,t,n,i,o={}){const{flowEnabled:s=!1}=o,a=new Map(t.map(e=>[e.file,e])),r=new Set(n.map(e=>e.file)),l=new Set,c=new Set;if(i){for(const e of i.sccClusters)for(const t of e.files)l.add(t);for(const e of i.chokepoints)e.onCriticalPath&&c.add(e.file)}const d=new Map;for(const t of e)d.has(t.file)||d.set(t.file,new Set),d.get(t.file).add(t.category);return e.map(e=>{const t=e.analysisLens||inferAnalysisLens(e.category),n=a.get(e.file),i=new Set(e.correlatedSignals||[]);r.has(e.file)&&i.add("hot-file"),l.has(e.file)&&i.add("cycle-context"),c.has(e.file)&&i.add("critical-path-context"),n?.effectProfile?.totalEffects&&i.add("top-level-effects");for(const t of d.get(e.file)||new Set)t!==e.category&&i.add(`paired:${t}`);const o={category:e.category,location:`${e.file}:${e.lineStart}-${e.lineEnd}`,...e.evidence||{}};return{...e,ruleId:e.ruleId||`${t}.${e.category}`,analysisLens:t,confidence:defaultConfidence({...e,analysisLens:t}),evidence:o,correlatedSignals:[...i].slice(0,8),recommendedValidation:e.recommendedValidation||buildRecommendedValidation({...e,analysisLens:t}),flowTrace:parseFlowTraceSteps({...e,evidence:o},s)}})}function makeSignal(e,t,n,i,o,s,a,r,l){return{kind:e,lens:t,title:n,summary:i,confidence:o,score:s,files:a,categories:r,evidence:l}}export function computeReportAnalysisSummary(e,t,n,i){const o=new Map;for(const t of e)o.has(t.file)||o.set(t.file,new Set),o.get(t.file).add(t.category);const s=[],a=[];if(i?.chokepoints.length){const e=i.chokepoints[0];s.push(makeSignal("structural-chokepoint","graph","Structural chokepoint",`${e.file} concentrates dependency pressure (${e.reasons.join(", ")}).`,e.articulation?"high":"medium",e.score,[e.file],["broker-module","bridge-module"],{score:e.score,reasons:e.reasons}))}if(i?.sccClusters.length){const e=i.sccClusters[0];s.push(makeSignal("cycle-cluster","graph","Cycle cluster",`${e.id} links ${e.nodeCount} files into a single strongly connected group.`,e.nodeCount>=5?"high":"medium",6*e.nodeCount+e.edgeCount,e.files,["dependency-cycle","cycle-cluster"],{clusterId:e.id,nodeCount:e.nodeCount,hubFiles:e.hubFiles}))}if(i?.packageGraphSummary.hotspots.length){const e=i.packageGraphSummary.hotspots[0];s.push(makeSignal("package-chatter","graph","Package boundary chatter",`${e.from} and ${e.to} exchange ${e.edges} cross-package dependency edge(s).`,e.edges>=8?"high":"medium",4*e.edges,[e.from,e.to],["package-boundary-chatter"],e))}const r=e.filter(e=>"mega-folder"===e.category).sort((e,t)=>{const n=Number(e.evidence?.fileCount||0);return Number(t.evidence?.fileCount||0)-n});if(r.length>0){const e=r[0],t=e.evidence||{},n="string"==typeof t.folderPath?t.folderPath:folderOf(e.file),i=Number(t.fileCount||e.files.length||0),o=Number(t.concentration||0);s.push(makeSignal("mega-folder-cluster","graph","Mega folder concentration",`${n} concentrates ${i} files (${(100*o).toFixed(1)}% of analyzed production files), which is a structural decomposition risk.`,o>=.5||i>=50?"high":"medium",Math.round(3*i+100*o),e.files.length>0?e.files:[e.file],["mega-folder"],{folderPath:n,fileCount:i,concentration:o}))}for(const e of t){const t=o.get(e.file)||new Set;t.has("low-cohesion")&&t.has("feature-envy")&&a.push(makeSignal("boundary-leak-shape","ast","Boundary leak shape",`${e.file} shows both low cohesion and feature envy, suggesting the module boundary is doing multiple jobs.`,"high",90,[e.file],["low-cohesion","feature-envy"],{file:e.file})),(e.effectProfile?.totalEffects||0)>0&&t.has("import-side-effect-risk")&&a.push(makeSignal("hidden-initialization","ast","Hidden initialization logic",`${e.file} performs import-time work that matches the reported side-effect risk.`,"medium",75,[e.file],["import-side-effect-risk"],{totalEffects:e.effectProfile?.totalEffects,highestRisk:e.effectProfile?.highestRisk})),t.has("duplicate-flow-structure")&&t.has("function-optimization")&&a.push(makeSignal("orchestration-duplication","ast","Repeated orchestration shape",`${e.file} combines repeated control-flow shape with complex functions, which usually means orchestration duplication.`,"medium",70,[e.file],["duplicate-flow-structure","function-optimization"],{file:e.file}))}const l=s.sort((e,t)=>t.score-e.score)[0]||null,c=a.sort((e,t)=>t.score-e.score)[0]||null;let d=null;if(l&&c){const e=l.files.find(e=>c.files.includes(e)),t=e?"high":"medium";d=makeSignal("combined-interpretation","hybrid","Combined interpretation",e?`${e} is both a structural hotspot and a suspicious code-shape hotspot, so it should be investigated first.`:`${l.title} and ${c.title} both appear in this scan, so use a hybrid investigation instead of a single-lens conclusion.`,t,Math.round((l.score+c.score)/2),e?[e]:[...new Set([...l.files,...c.files])].slice(0,4),[...new Set([...l.categories,...c.categories])],{graphKind:l.kind,astKind:c.kind,sharedFile:e||null})}else if(l||c){const e=l||c;d=makeSignal("combined-interpretation",e.lens,"Combined interpretation",e.summary,e.confidence,e.score,e.files,e.categories,e.evidence)}const u=new Set(d?.files||[]),f=new Set(d?.categories||[]),h=e.find(e=>u.has(e.file)||f.has(e.category))||e[0],p=h?.recommendedValidation||null,g=new Set;l&&g.add(`Inspect ${l.files[0]} first and validate the graph claim with localSearchCode plus LSP navigation.`),c&&g.add(`Use file-inventory.json for ${c.files[0]} to explain why the code shape matches the finding.`),"high"===d?.confidence?g.add("Treat the aligned graph and AST signal as an architecture priority, not just a local cleanup task."):d&&g.add("Use a hybrid investigation before proposing a refactor because the signals do not fully align yet."),n.length>0&&g.add(`Cross-check the top hotspot ${n[0].file} with the strongest architecture finding before editing code.`);const m=s.find(e=>"mega-folder-cluster"===e.kind);return m&&g.add(`Map the import graph of ${m.evidence.folderPath}, identify domain clusters, and restructure with an automated migration script that moves files and rewrites import paths atomically. Validate with tsc + lint + tests after each phase.`),{graphSignals:s.sort((e,t)=>t.score-e.score),astSignals:a.sort((e,t)=>t.score-e.score),combinedSignals:d?[d]:[],strongestGraphSignal:l,strongestAstSignal:c,combinedInterpretation:d,recommendedValidation:p,investigationPrompts:[...g]}}function folderOf(e){const t=e.replace(/\\/g,"/"),n=t.lastIndexOf("/");return-1===n?".":t.slice(0,n)}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import fs from"node:fs";import path from"node:path";import{PILLAR_CATEGORIES,SEVERITY_ORDER}from"../types/index.js";export function severityBreakdown(e){const t={critical:0,high:0,medium:0,low:0,info:0};for(const s of e)t[s.severity]=(t[s.severity]||0)+1;return t}export function categoryBreakdown(e){const t={};for(const s of e)t[s.category]=(t[s.category]||0)+1;return t}export function computeHealthScore(e,t){return computeHealthScoreFromSeverityBreakdown(severityBreakdown(e),t)}function computeHealthScoreFromSeverityBreakdown(e,t){if(0===t)return 100;const s={critical:25,high:10,medium:3,low:1,info:0};let n=0;for(const[t,i]of Object.entries(e))n+=(s[t]||0)*i;const i=n/t;return Math.max(0,Math.min(100,Math.round(100/(1+i/10))))}export function collectTagCloud(e){const t=new Map;for(const s of e)if(s.tags)for(const e of s.tags)t.set(e,(t.get(e)||0)+1);return[...t.entries()].map(([e,t])=>({tag:e,count:t})).sort((e,t)=>t.count-e.count)}export function formatFileSize(e){return e<1024?`${e} B`:e<1048576?`${(e/1024).toFixed(1)} KB`:`${(e/1048576).toFixed(1)} MB`}function summarizeActiveFeatures(e){const t=new Set(e),s=[];for(const[e,n]of Object.entries(PILLAR_CATEGORIES))if(n.length>0&&n.every(e=>t.has(e))){s.push(e);for(const e of n)t.delete(e)}return[...s,...[...t].sort()]}function isPillarActive(e,t){if(!t)return!0;return(PILLAR_CATEGORIES[e]||[]).some(e=>t.has(e))}export function diversifyFindings(e,t){if(!Number.isFinite(t)||t>=e.length)return e;const s=new Map;for(const t of e){const e=t.category;s.has(e)||s.set(e,[]),s.get(e).push(t)}const n=[...s.entries()].sort((e,t)=>{const s=SEVERITY_ORDER[e[1][0].severity]??0;return(SEVERITY_ORDER[t[1][0].severity]??0)-s}),i=[],o=new Map;for(const[e]of n)o.set(e,0);for(;i.length<t;){let e=!1;for(const[s,r]of n){if(i.length>=t)break;const n=o.get(s);n<r.length&&(i.push(r[n]),o.set(s,n+1),e=!0)}if(!e)break}return i}export function diverseTopRecommendations(e,t=20,s=2){const n=[],i=new Map;for(const o of e){const e=i.get(o.category)||0;if(!(e>=s)&&(n.push(o),i.set(o.category,e+1),n.length>=t))break}return n}function formatCliPath(e){return JSON.stringify(e.replace(/\\/g,"/"))}export function generateSummaryMd(e){const{dir:t,report:s,outputFiles:n,architectureFindings:i,codeQualityFindings:o,deadCodeFindings:r,hotFiles:a=[],activeFeatures:c=null,scope:l=null,root:u=process.cwd(),scopeSymbols:d=null,semanticEnabled:p=!1,securityFindings:h=[],testQualityFindings:g=[],reportAnalysis:f=null}=e,m=s.optimizationFindings||[],y=s.summary,$=s.agentOutput,S=$?.findingStats??null,F=s.dependencyGraph,v=path.relative(u,t)||".",w=((l?.[0]??"src/index").split(":")[0]||"src/index").replace(/\\/g,"/"),k=S?.overall??{totalFindings:m.length,severityBreakdown:severityBreakdown(m)},C=[];C.push("# Code Quality Scan Report\n"),C.push(`**Generated**: ${s.generatedAt} `),C.push(`**Root**: \`${s.repoRoot}\`\n`),C.push("## Scan Scope\n"),C.push("| Metric | Count |"),C.push("|--------|-------|"),C.push(`| Files analyzed | ${y.totalFiles??"—"} |`),C.push(`| Functions | ${y.totalFunctions??"—"} |`),C.push(`| Flow nodes | ${y.totalFlows??"—"} |`),C.push(`| Dependency files | ${y.totalDependencyFiles??"—"} |`),C.push(`| Packages | ${y.totalPackages??"—"} |`),C.push(""),C.push("## Findings Overview\n"),C.push("| Severity | Count |"),C.push("|----------|-------|"),C.push(`| Critical | ${k.severityBreakdown.critical??0} |`),C.push(`| High | ${k.severityBreakdown.high??0} |`),C.push(`| Medium | ${k.severityBreakdown.medium??0} |`),C.push(`| Low | ${k.severityBreakdown.low??0} |`),C.push(`| **Total** | **${k.totalFindings}** |`),C.push("");const B=k.totalFindings||$?.totalBeforeTruncation,T=$?.droppedCategories;if(B&&B>m.length&&(C.push(`> **Truncated**: Showing ${m.length} of ${B} findings (\`--findings-limit ${m.length}\`).`),T&&T.length>0&&C.push(`> Dropped categories: ${T.map(e=>`\`${e}\``).join(", ")}`),C.push("")),c){const e=summarizeActiveFeatures(c);C.push(`> **Features filter**: \`--features=${e.join(",")}\``),C.push("")}if(l&&l.length>0){const e=l.map(e=>path.relative(u,e)).filter(Boolean);if(e.length>0){let t=e.map(e=>`\`${e}\``).join(", ");if(d&&d.size>0){const e=[];for(const[t,s]of d){const n=path.relative(u,t);e.push(...s.map(e=>`\`${n}:${e}\``))}t=e.join(", ")}C.push(`> **Scoped scan**: Only showing findings for: ${t}`),C.push("")}}p&&(C.push("> **Semantic analysis**: TypeChecker + LanguageService enabled (14 additional categories)"),C.push(""));const R=(e,t)=>{const s=categoryBreakdown(t),n=PILLAR_CATEGORIES[e]||[],i=null!==c;for(const e of n){const t=s[e]||0;i&&!c.has(e)?C.push(`- \`${e}\`: — *(skipped)*`):C.push(`- \`${e}\`: ${t}`)}C.push("")},A=y.totalFiles||1,x=S?.pillars?.architecture,H=S?.pillars?.["code-quality"],P=S?.pillars?.["dead-code"],E=S?.pillars?.security,b=S?.pillars?.["test-quality"],I=computeHealthScoreFromSeverityBreakdown(k.severityBreakdown,A),j=computeHealthScoreFromSeverityBreakdown(x?.severityBreakdown??severityBreakdown(i),A),O=computeHealthScoreFromSeverityBreakdown(H?.severityBreakdown??severityBreakdown(o),A),M=computeHealthScoreFromSeverityBreakdown(P?.severityBreakdown??severityBreakdown(r),A),q=computeHealthScoreFromSeverityBreakdown(E?.severityBreakdown??severityBreakdown(h),A),D=computeHealthScoreFromSeverityBreakdown(b?.severityBreakdown??severityBreakdown(g),A);C.push("## Health Scores\n"),C.push("| Pillar | Score | Grade |"),C.push("|--------|-------|-------|");const Q=e=>e>=80?"A":e>=60?"B":e>=40?"C":e>=20?"D":"F",L=(e,t,s)=>{isPillarActive(t,c)?C.push(`| ${e} | ${s}/100 | ${Q(s)} |`):C.push(`| ${e} | — | skipped |`)},z=(e,t,s,i,o)=>{isPillarActive(e,c)?i&&n[i]?C.push(`> ${t} findings (score: ${s}/100) — see [\`${o}\`](./${n[i]})\n`):o?C.push(`> ${t} findings (score: ${s}/100) — no \`${o}\` written for this scan\n`):C.push(`> ${t} findings (score: ${s}/100)\n`):C.push("> skipped by feature filter\n")};C.push(`| **Overall** | **${I}/100** | **${Q(I)}** |`),L("Architecture","architecture",j),L("Code Quality","code-quality",O),L("Dead Code & Hygiene","dead-code",M),L("Security","security",q),L("Test Quality","test-quality",D),C.push("");const G=collectTagCloud(m);if(G.length>0){C.push("## Top Concern Tags\n"),C.push("Searchable tags across all findings — use to filter `findings.json` with `jq`.\n");for(const{tag:e,count:t}of G.slice(0,12))C.push(`- \`${e}\`: ${t} findings`);C.push("")}if(f){C.push("## Analysis Signals\n"),C.push(`- **Graph Signal**: ${f.strongestGraphSignal?.summary||"No dominant graph signal in this scan."}`),C.push(`- **AST Signal**: ${f.strongestAstSignal?.summary||"No dominant AST signal in this scan."}`),C.push(`- **Combined Interpretation**: ${f.combinedInterpretation?.summary||"No combined interpretation available yet."}`),C.push(`- **Confidence**: ${f.combinedInterpretation?.confidence||f.strongestGraphSignal?.confidence||f.strongestAstSignal?.confidence||"low"}`);const e=f.recommendedValidation?`${f.recommendedValidation.summary} (tools: ${f.recommendedValidation.tools.join(" -> ")})`:"Use Octocode local tools to confirm the strongest signal before presenting it as fact.";C.push(`- **Recommended Validation**: ${e}`);const t=f.graphSignals.find(e=>"mega-folder-cluster"===e.kind);if(t&&C.push(`- **Structural Layout Alert**: ${t.summary}`),f.investigationPrompts.length>0){C.push(""),C.push("**Investigation Prompts**");for(const e of f.investigationPrompts.slice(0,4))C.push(`- ${e}`)}C.push("")}if(C.push("## Architecture Health\n"),z("architecture",x?.totalFindings??i.length,j,"architecture","architecture.json"),F&&(C.push("| Metric | Value |"),C.push("|--------|-------|"),C.push(`| Modules | ${F.totalModules} |`),C.push(`| Import edges | ${F.totalEdges} |`),C.push(`| Cycles | ${F.cycles?.length??0} |`),C.push(`| Critical paths | ${F.criticalPaths?.length??0} |`),C.push(`| Root modules | ${F.rootsCount} |`),C.push(`| Leaf modules | ${F.leavesCount} |`),C.push(`| Test-only modules | ${F.testOnlyModules?.length??0} |`),C.push(`| Unresolved imports | ${F.unresolvedEdgeCount} |`),C.push("")),R("architecture",i),renderHotspots(C,a),renderPillarSections(C,{architectureFindings:i,codeQualityFindings:o,deadCodeFindings:r,securityFindings:h,testQualityFindings:g,archStats:x,qualStats:H,deadStats:P,secStats:E,testStats:b,archHealth:j,qualHealth:O,deadHealth:M,secHealth:q,testHealth:D,activeFeatures:c,outputFiles:n,renderPillarCategories:R,pushPillarSummary:z}),renderRecommendations(C,$),n.astTrees&&renderAstTreesSection(C,t,n,u,v,w),renderOutputFilesTable(C,t,n),s.parseErrors?.length>0){C.push("## Parse Errors\n"),C.push(`${s.parseErrors.length} file(s) failed to parse:\n`);for(const e of s.parseErrors.slice(0,10))C.push(`- \`${e.file}\`: ${e.message}`);C.push("")}return C.join("\n")}function renderHotspots(e,t){if(t&&0!==t.length){e.push("## Change Risk Hotspots\n"),e.push("Files most dangerous to change — high fan-in, complexity, or cycle membership.\n"),e.push("| File | Risk | Fan-In | Fan-Out | Complexity | Exports | Cycle | Critical Path |"),e.push("|------|------|--------|---------|------------|---------|-------|---------------|");for(const s of t.slice(0,15))e.push(`| \`${s.file}\` | ${s.riskScore} | ${s.fanIn} | ${s.fanOut} | ${s.complexityScore} | ${s.exportCount} | ${s.inCycle?"Y":"-"} | ${s.onCriticalPath?"Y":"-"} |`);e.push("")}}function renderPillarSections(e,t){const{architectureFindings:s,codeQualityFindings:n,deadCodeFindings:i,securityFindings:o,testQualityFindings:r}=t,{qualStats:a,deadStats:c,secStats:l,testStats:u}=t,{qualHealth:d,deadHealth:p,secHealth:h,testHealth:g}=t,{renderPillarCategories:f,pushPillarSummary:m}=t;e.push("## Code Quality\n"),m("code-quality",a?.totalFindings??n.length,d,"codeQuality","code-quality.json"),f("code-quality",n),e.push("## Dead Code & Hygiene\n"),m("dead-code",c?.totalFindings??i.length,p,"deadCode","dead-code.json"),f("dead-code",i),e.push("## Security\n"),m("security",l?.totalFindings??o.length,h,"security","security.json"),f("security",o),e.push("## Test Quality\n"),m("test-quality",u?.totalFindings??r.length,g,"testQuality","test-quality.json"),f("test-quality",r);const y=s.filter(e=>"untested-critical-code"===e.category).length;y>0&&0===(u?.totalFindings??r.length)&&e.push(`> **Note**: Test Quality reflects analyzed test files only. ${y} modules flagged as \`untested-critical-code\` (architecture pillar) have no test coverage — use \`--include-tests\` for test-quality analysis.\n`)}function renderRecommendations(e,t){const s=t?.topRecommendations??[];if(s.length>0){e.push("## Top Recommendations\n");for(const t of s.slice(0,10))e.push(`- **[${t.severity.toUpperCase()}]** \`${t.file}\` — ${t.title} *(${t.category})* `);e.push("")}}function renderAstTreesSection(e,t,s,n,i,o){const r=formatCliPath(path.resolve(t,s.astTrees));e.push("## AST Trees (`ast-trees.txt`)\n"),e.push("Compact indented text format — each node is `Kind[startLine:endLine]`, nesting = indentation.\n"),e.push(`Run these commands from the skill directory. Current scan: \`${i}\`.\n`),e.push("```"),e.push("SourceFile[1:152]"),e.push(" ImportDeclaration[1]"),e.push(" FunctionDeclaration[3:20]"),e.push(" Block[4:19]"),e.push(" IfStatement[5:12] ..."),e.push("```\n"),e.push("**Smart navigation:**\n"),e.push(`- Find functions: \`node scripts/ast/tree-search.js -i ${r} -k function_declaration --limit 25\``),e.push(`- Find classes: \`node scripts/ast/tree-search.js -i ${r} -k class_declaration --limit 25\``),e.push(`- Find control flow: \`node scripts/ast/tree-search.js -i ${r} -p 'IfStatement|SwitchStatement|ForStatement|WhileStatement' --limit 25\``),e.push(`- Narrow to one file: \`node scripts/ast/tree-search.js -i ${r} --file "${o}" -k function_declaration --limit 10\``),e.push(`- Raw text fallback: \`rg 'FunctionDeclaration|IfStatement' ${r}\``),e.push("")}function renderOutputFilesTable(e,t,s){e.push("## Output Files\n"),e.push("| File | Size | Description |"),e.push("|------|------|-------------|");const n={summary:"Scan metadata, agent output, parse errors",architecture:"Dependency graph, cycles, critical paths, architecture findings",codeQuality:"Duplicate detection, complexity, god modules/functions",deadCode:"Dead files/exports/re-exports, unused deps, boundary violations",fileInventory:"Per-file function/flow/dependency details",findings:"All findings across all categories (master list)",graph:"Mermaid dependency graph",astTrees:"AST tree snapshots (compact indented text — grep/regex friendly)",summaryMd:"This file — human-readable overview"};for(const[i,o]of Object.entries(s)){let s="—";try{s=formatFileSize(fs.statSync(path.join(t,o)).size)}catch{s="—"}e.push(`| [\`${o}\`](./${o}) | ${s} | ${n[i]||i} |`)}e.push("")}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import fs from"node:fs";import path from"node:path";import{computeReportAnalysisSummary,enrichFileInventoryEntries,enrichFindings}from"./analysis.js";import{categoryBreakdown,generateSummaryMd,severityBreakdown}from"./summary-md.js";import{computeGraphAnalytics}from"../analysis/graph-analytics.js";import{renderTreesText}from"../common/utils.js";import{computeHotFiles}from"../detectors/index.js";import{PILLAR_CATEGORIES}from"../types/index.js";export const REPORT_SCHEMA_VERSION="1.1.0";export const ARCHITECTURE_CATEGORIES=new Set(PILLAR_CATEGORIES.architecture);export const CODE_QUALITY_CATEGORIES=new Set(PILLAR_CATEGORIES["code-quality"]);export const DEAD_CODE_CATEGORIES=new Set(PILLAR_CATEGORIES["dead-code"]);export const SECURITY_CATEGORIES=new Set(PILLAR_CATEGORIES.security);export const TEST_QUALITY_CATEGORIES=new Set(PILLAR_CATEGORIES["test-quality"]);export function writeMultiFileReport(e,t,n,s,o,r){fs.mkdirSync(e,{recursive:!0});const a=(t,n)=>{fs.writeFileSync(path.join(e,t),JSON.stringify(n),"utf8")},i={summary:"summary.json",architecture:"architecture.json",codeQuality:"code-quality.json",deadCode:"dead-code.json",fileInventory:"file-inventory.json",findings:"findings.json"},c=computeHotFiles(s,o,r),d=t.graphAnalytics??computeGraphAnalytics(s,o,r),l=enrichFileInventoryEntries(t.fileInventory||[],{flowEnabled:!!n.flow}),p=enrichFindings(t.optimizationFindings||[],l,c,d,{flowEnabled:!!n.flow}),u=p.filter(e=>ARCHITECTURE_CATEGORIES.has(e.category)),h=p.filter(e=>CODE_QUALITY_CATEGORIES.has(e.category)),g=p.filter(e=>DEAD_CODE_CATEGORIES.has(e.category)),m=p.filter(e=>SECURITY_CATEGORIES.has(e.category)),y=p.filter(e=>TEST_QUALITY_CATEGORIES.has(e.category)),f=t.reportAnalysis??computeReportAnalysisSummary(p,l,c,d);if(a("architecture.json",{schemaVersion:"1.1.0",generatedAt:t.generatedAt,dependencyGraph:t.dependencyGraph,dependencyFindings:t.dependencyFindings,findings:u,findingsCount:u.length,severityBreakdown:severityBreakdown(u),categoryBreakdown:categoryBreakdown(u),hotFiles:c,graphSignals:f.graphSignals,chokepoints:d.chokepoints,criticalHubCandidates:d.chokepoints.slice(0,10),sccClusters:n.graphAdvanced?d.sccClusters:[],packageGraphSummary:n.graphAdvanced?d.packageGraphSummary:null,packageHotspots:n.graphAdvanced?d.packageGraphSummary.hotspots:[]}),a("code-quality.json",{schemaVersion:"1.1.0",generatedAt:t.generatedAt,duplicateFlows:t.duplicateFlows,optimizationOpportunities:t.optimizationOpportunities,findings:h,findingsCount:h.length,severityBreakdown:severityBreakdown(h),categoryBreakdown:categoryBreakdown(h)}),a("dead-code.json",{schemaVersion:"1.1.0",generatedAt:t.generatedAt,findings:g,findingsCount:g.length,severityBreakdown:severityBreakdown(g),categoryBreakdown:categoryBreakdown(g)}),m.length>0&&(a("security.json",{schemaVersion:"1.1.0",generatedAt:t.generatedAt,findings:m,findingsCount:m.length,severityBreakdown:severityBreakdown(m),categoryBreakdown:categoryBreakdown(m)}),i.security="security.json"),y.length>0&&(a("test-quality.json",{schemaVersion:"1.1.0",generatedAt:t.generatedAt,findings:y,findingsCount:y.length,severityBreakdown:severityBreakdown(y),categoryBreakdown:categoryBreakdown(y)}),i.testQuality="test-quality.json"),a("file-inventory.json",{schemaVersion:"1.1.0",generatedAt:t.generatedAt,fileInventory:l,fileCount:l.length}),a("findings.json",{schemaVersion:"1.1.0",generatedAt:t.generatedAt,optimizationFindings:p,totalFindings:p.length}),n.graph){const t=generateMermaidGraph(s,o,r);fs.writeFileSync(path.join(e,"graph.md"),t,"utf8"),i.graph="graph.md"}t.astTrees&&(fs.writeFileSync(path.join(e,"ast-trees.txt"),renderTreesText(t.astTrees,t.generatedAt),"utf8"),i.astTrees="ast-trees.txt");const S={schemaVersion:"1.1.0",generatedAt:t.generatedAt,repoRoot:t.repoRoot,options:t.options,parser:t.parser,summary:t.summary,agentOutput:t.agentOutput,analysisSummary:{graphSignals:f.graphSignals,astSignals:f.astSignals,strongestGraphSignal:f.strongestGraphSignal,strongestAstSignal:f.strongestAstSignal,combinedSignals:f.combinedSignals,recommendedValidation:f.recommendedValidation},strongestGraphSignal:f.strongestGraphSignal,strongestAstSignal:f.strongestAstSignal,combinedSignals:f.combinedSignals,recommendedValidation:f.recommendedValidation,investigationPrompts:f.investigationPrompts,parseErrors:t.parseErrors,outputFiles:i};a("summary.json",S);const A=generateSummaryMd({dir:e,report:t,outputFiles:i,architectureFindings:u,codeQualityFindings:h,deadCodeFindings:g,hotFiles:c,activeFeatures:n.features,scope:n.scope,root:n.root,scopeSymbols:n.scopeSymbols,semanticEnabled:n.semantic,securityFindings:m,testQualityFindings:y,reportAnalysis:f});return fs.writeFileSync(path.join(e,"summary.md"),A,"utf8"),i.summaryMd="summary.md",a("summary.json",{...S,outputFiles:i}),i}export function generateMermaidGraph(e,t,n){const s=[];s.push("# Dependency Graph\n"),s.push("## Module Dependency Map\n"),s.push("```mermaid"),s.push("graph LR");const o=new Set((t.criticalModules||[]).map(e=>e.file)),r=new Set;for(const e of t.cycles||[])for(const t of e.path)r.add(t);const a=e=>{const t=e.split("/");return t.length<=2?t.join("/"):`${t[0]}/…/${t[t.length-1]}`},i=e=>e.replace(/[^a-zA-Z0-9]/g,"_"),c=new Set,d=new Set,l=[...(t.outgoingTop||[]).slice(0,15),...(t.inboundTop||[]).slice(0,15),...(t.criticalModules||[]).slice(0,10)],p=new Set(l.map(e=>e.file));for(const e of(t.cycles||[]).slice(0,5))for(const t of e.path)p.add(t);for(const e of p){const t=i(e);if(c.has(t))continue;c.add(t);const n=a(e);r.has(e)?s.push(` ${t}["🔴 ${n}"]`):o.has(e)?s.push(` ${t}["⚠️ ${n}"]`):s.push(` ${t}["${n}"]`)}for(const t of p){const n=e.outgoing.get(t)||new Set;for(const e of n){if(!p.has(e))continue;const n=`${i(t)}--\x3e${i(e)}`;d.has(n)||(d.add(n),r.has(t)&&r.has(e)?s.push(` ${i(t)} -. cycle .-> ${i(e)}`):s.push(` ${i(t)} --\x3e ${i(e)}`))}}if(s.push("```\n"),t.cycles?.length>0){s.push("## Dependency Cycles\n"),s.push("```mermaid"),s.push("graph LR");for(const[e,n]of t.cycles.slice(0,10).entries())for(let t=0;t<n.path.length-1;t++){const o=i(n.path[t]),r=i(n.path[t+1]);s.push(` ${o}["${a(n.path[t])}"] -. "cycle ${e+1}" .-> ${r}["${a(n.path[t+1])}"]`)}s.push("```\n")}if(t.criticalPaths?.length>0){s.push("## Critical Dependency Chains\n"),s.push("```mermaid"),s.push("graph LR");for(const e of t.criticalPaths.slice(0,8))for(let t=0;t<e.path.length-1;t++){const n=i(e.path[t]),o=i(e.path[t+1]);s.push(` ${n}["${a(e.path[t])}"] ==> ${o}["${a(e.path[t+1])}"]`)}s.push("```\n")}if(s.push("## Summary\n"),s.push("| Metric | Value |"),s.push("|--------|-------|"),s.push(`| Total modules | ${t.totalModules} |`),s.push(`| Total edges | ${t.totalEdges} |`),s.push(`| Root modules | ${t.rootsCount} |`),s.push(`| Leaf modules | ${t.leavesCount} |`),s.push(`| Cycles | ${t.cycles?.length||0} |`),s.push(`| Critical paths | ${t.criticalPaths?.length||0} |`),s.push(`| Test-only modules | ${t.testOnlyModules?.length||0} |`),s.push(`| Unresolved imports | ${t.unresolvedEdgeCount||0} |`),s.push(""),t.criticalModules?.length>0){s.push("## Critical Modules (Hub Nodes)\n"),s.push("| Module | Score | Risk | Inbound | Outbound |"),s.push("|--------|-------|------|---------|----------|");for(const e of t.criticalModules.slice(0,20))s.push(`| \`${e.file}\` | ${e.score} | ${e.riskBand||"-"} | ${e.inboundCount} | ${e.outboundCount} |`);s.push("")}if(t.testOnlyModules?.length>0){s.push("## Test-Only Modules\n");for(const e of t.testOnlyModules.slice(0,20))s.push(`- \`${e.file}\``);s.push("")}return s.join("\n")}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import path from"node:path";import*as ts from"typescript";export const DEFAULT_OPTS={minFunctionStatements:6,minFlowStatements:6,root:process.cwd(),includeTests:!1,emitTree:!0,json:!1,graph:!1,out:null,treeDepth:4,findingsLimit:1/0,parser:"auto",criticalComplexityThreshold:30,deepLinkTopN:12,packageRoot:path.join(process.cwd(),"packages"),ignoreDirs:new Set([".git",".next",".yarn",".cache",".octocode","node_modules","dist","coverage","out"]),couplingThreshold:15,fanInThreshold:20,fanOutThreshold:15,godModuleStatements:500,godModuleExports:20,godFunctionStatements:100,godFunctionMiThreshold:10,cognitiveComplexityThreshold:15,barrelSymbolThreshold:30,layerOrder:[],parameterThreshold:5,halsteadEffortThreshold:5e5,maintainabilityIndexThreshold:20,anyThreshold:5,flowDupThreshold:3,maxRecsPerCategory:2,features:null,scope:null,scopeSymbols:null,noCache:!1,clearCache:!1,semantic:!1,overrideChainThreshold:3,shotgunThreshold:8,sdpMinDelta:.15,sdpMaxSourceInstability:.6,secretEntropyThreshold:4.5,secretMinLength:20,similarityThreshold:.85,mockThreshold:10,noDiversify:!1,graphAdvanced:!1,flow:!1};export const PILLAR_CATEGORIES={architecture:["dependency-cycle","dependency-critical-path","dependency-test-only","architecture-sdp-violation","high-coupling","god-module-coupling","orphan-module","unreachable-module","layer-violation","low-cohesion","mega-folder","distance-from-main-sequence","feature-envy","untested-critical-code","over-abstraction","concrete-dependency","circular-type-dependency","shotgun-surgery","import-side-effect-risk","cycle-cluster","broker-module","bridge-module","package-boundary-chatter","startup-risk-hub","namespace-import","commonjs-in-esm","export-star-leak","mixed-module-format"],"code-quality":["duplicate-function-body","duplicate-flow-structure","function-optimization","cognitive-complexity","god-module","god-function","halstead-effort","low-maintainability","excessive-parameters","unsafe-any","empty-catch","switch-no-default","unused-parameter","deep-override-chain","interface-compliance","type-assertion-escape","promise-misuse","narrowable-type","missing-error-boundary","await-in-loop","sync-io","uncleared-timer","listener-leak-risk","unbounded-collection","similar-function-body","message-chain"],"dead-code":["dead-export","dead-re-export","re-export-duplication","re-export-shadowed","unused-npm-dependency","package-boundary-violation","barrel-explosion","unused-import","orphan-implementation","move-to-caller","semantic-dead-export","dead-file"],security:["hardcoded-secret","eval-usage","unsafe-html","sql-injection-risk","unsafe-regex","prototype-pollution-risk","unvalidated-input-sink","input-passthrough-risk","path-traversal-risk","command-injection-risk","debug-log-leakage","sensitive-data-logging"],"test-quality":["low-assertion-density","test-no-assertion","excessive-mocking","shared-mutable-state","missing-test-cleanup","focused-test","fake-timer-no-restore","missing-mock-restoration"]};export const ALL_CATEGORIES=new Set(Object.values(PILLAR_CATEGORIES).flat());export const SEMANTIC_CATEGORIES=new Set(["over-abstraction","concrete-dependency","circular-type-dependency","unused-parameter","deep-override-chain","interface-compliance","unused-import","orphan-implementation","shotgun-surgery","move-to-caller","narrowable-type","semantic-dead-export"]);export const ALLOWED_EXTS=new Set([".ts",".tsx",".js",".jsx",".mjs",".cjs"]);export const IMPORT_RESOLVE_EXTS=[".ts",".tsx",".js",".jsx",".mjs",".cjs",".d.ts"];export const TS_CONTROL_KINDS=new Set([ts.SyntaxKind.IfStatement,ts.SyntaxKind.SwitchStatement,ts.SyntaxKind.TryStatement,ts.SyntaxKind.ForStatement,ts.SyntaxKind.WhileStatement,ts.SyntaxKind.DoStatement,ts.SyntaxKind.ForOfStatement,ts.SyntaxKind.ForInStatement,ts.SyntaxKind.ConditionalExpression]);export const TS_TREE_SITTER_CONTROL_TYPES=new Set(["if_statement","switch_statement","try_statement","for_statement","while_statement","do_statement","for_in_statement","for_of_statement","for_await_statement","conditional_expression","conditional_expression?","catch_clause"]);export const TS_TREE_SITTER_FUNCTION_TYPES=new Set(["function_declaration","function","generator_function","generator_function_declaration","method_definition","arrow_function","function_expression"]);export const SEVERITY_ORDER={critical:4,high:3,medium:2,low:1,info:0};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export{ALL_CATEGORIES,ALLOWED_EXTS,DEFAULT_OPTS,IMPORT_RESOLVE_EXTS,PILLAR_CATEGORIES,SEMANTIC_CATEGORIES,SEVERITY_ORDER,TS_CONTROL_KINDS,TS_TREE_SITTER_CONTROL_TYPES,TS_TREE_SITTER_FUNCTION_TYPES}from"./constants.js";
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export{};
|