octocode-cli 1.2.6 → 1.2.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (303) hide show
  1. package/LICENSE +21 -63
  2. package/README.md +85 -142
  3. package/out/octocode-cli.js +7026 -6945
  4. package/package.json +8 -6
  5. package/skills/README.md +97 -120
  6. package/skills/octocode-code-engineer/.claude/settings.local.json +18 -0
  7. package/skills/octocode-code-engineer/.octocode/rfc/RFC-code-engineer-weakness-fixes.md +255 -0
  8. package/skills/octocode-code-engineer/.plan/VALIDATED_PLAN.md +223 -0
  9. package/skills/octocode-code-engineer/README.md +178 -0
  10. package/skills/octocode-code-engineer/SKILL.md +418 -0
  11. package/skills/octocode-code-engineer/coverage/architecture.ts.html +7828 -0
  12. package/skills/octocode-code-engineer/coverage/ast-helpers.ts.html +211 -0
  13. package/skills/octocode-code-engineer/coverage/ast-search.ts.html +1795 -0
  14. package/skills/octocode-code-engineer/coverage/base.css +224 -0
  15. package/skills/octocode-code-engineer/coverage/block-navigation.js +87 -0
  16. package/skills/octocode-code-engineer/coverage/cache.ts.html +376 -0
  17. package/skills/octocode-code-engineer/coverage/cli.ts.html +982 -0
  18. package/skills/octocode-code-engineer/coverage/clover.xml +3217 -0
  19. package/skills/octocode-code-engineer/coverage/collect-effects.ts.html +664 -0
  20. package/skills/octocode-code-engineer/coverage/collect-input-sources.ts.html +577 -0
  21. package/skills/octocode-code-engineer/coverage/collect-performance.ts.html +331 -0
  22. package/skills/octocode-code-engineer/coverage/collect-prototype-pollution.ts.html +421 -0
  23. package/skills/octocode-code-engineer/coverage/collect-security.ts.html +604 -0
  24. package/skills/octocode-code-engineer/coverage/collect-test-profile.ts.html +589 -0
  25. package/skills/octocode-code-engineer/coverage/coverage-final.json +30 -0
  26. package/skills/octocode-code-engineer/coverage/dependencies.ts.html +997 -0
  27. package/skills/octocode-code-engineer/coverage/dependency-summary.ts.html +688 -0
  28. package/skills/octocode-code-engineer/coverage/discovery.ts.html +322 -0
  29. package/skills/octocode-code-engineer/coverage/favicon.png +0 -0
  30. package/skills/octocode-code-engineer/coverage/graph-analytics.ts.html +1510 -0
  31. package/skills/octocode-code-engineer/coverage/index.html +536 -0
  32. package/skills/octocode-code-engineer/coverage/index.ts.html +826 -0
  33. package/skills/octocode-code-engineer/coverage/metrics.ts.html +553 -0
  34. package/skills/octocode-code-engineer/coverage/pipeline.ts.html +2044 -0
  35. package/skills/octocode-code-engineer/coverage/prettify.css +1 -0
  36. package/skills/octocode-code-engineer/coverage/prettify.js +2 -0
  37. package/skills/octocode-code-engineer/coverage/report-analysis.ts.html +1570 -0
  38. package/skills/octocode-code-engineer/coverage/report-writer.ts.html +1102 -0
  39. package/skills/octocode-code-engineer/coverage/security-detectors.ts.html +1747 -0
  40. package/skills/octocode-code-engineer/coverage/semantic-detectors.ts.html +2152 -0
  41. package/skills/octocode-code-engineer/coverage/semantic.ts.html +1897 -0
  42. package/skills/octocode-code-engineer/coverage/sort-arrow-sprite.png +0 -0
  43. package/skills/octocode-code-engineer/coverage/sorter.js +210 -0
  44. package/skills/octocode-code-engineer/coverage/summary-md.ts.html +1222 -0
  45. package/skills/octocode-code-engineer/coverage/test-quality-detectors.ts.html +1039 -0
  46. package/skills/octocode-code-engineer/coverage/tree-sitter-analyzer.ts.html +955 -0
  47. package/skills/octocode-code-engineer/coverage/ts-analyzer.ts.html +1213 -0
  48. package/skills/octocode-code-engineer/coverage/types.ts.html +2473 -0
  49. package/skills/octocode-code-engineer/coverage/utils.ts.html +820 -0
  50. package/skills/octocode-code-engineer/eslint.config.mjs +54 -0
  51. package/skills/octocode-code-engineer/minify-scripts.mjs +32 -0
  52. package/skills/octocode-code-engineer/package.json +54 -0
  53. package/skills/octocode-code-engineer/references/agent-ast-reading-rfc.md +95 -0
  54. package/skills/octocode-code-engineer/references/architecture-techniques.md +121 -0
  55. package/skills/octocode-code-engineer/references/ast-search.md +210 -0
  56. package/skills/octocode-code-engineer/references/ast-tree-search.md +151 -0
  57. package/skills/octocode-code-engineer/references/cli-reference.md +167 -0
  58. package/skills/octocode-code-engineer/references/concepts.md +107 -0
  59. package/skills/octocode-code-engineer/references/finding-categories.md +128 -0
  60. package/skills/octocode-code-engineer/references/improvement-roadmap.md +304 -0
  61. package/skills/octocode-code-engineer/references/output-files.md +144 -0
  62. package/skills/octocode-code-engineer/references/playbooks.md +204 -0
  63. package/skills/octocode-code-engineer/references/present-results.md +136 -0
  64. package/skills/octocode-code-engineer/references/tool-workflows.md +566 -0
  65. package/skills/octocode-code-engineer/references/validate-investigate.md +225 -0
  66. package/skills/octocode-code-engineer/scripts/analysis/dependencies.js +1 -0
  67. package/skills/octocode-code-engineer/scripts/analysis/dependency-summary.js +1 -0
  68. package/skills/octocode-code-engineer/scripts/analysis/discovery.js +1 -0
  69. package/skills/octocode-code-engineer/scripts/analysis/graph-analytics.js +1 -0
  70. package/skills/octocode-code-engineer/scripts/analysis/semantic.js +1 -0
  71. package/skills/octocode-code-engineer/scripts/ast/helpers.js +1 -0
  72. package/skills/octocode-code-engineer/scripts/ast/metrics.js +1 -0
  73. package/skills/octocode-code-engineer/scripts/ast/search.js +2 -0
  74. package/skills/octocode-code-engineer/scripts/ast/tree-search.js +2 -0
  75. package/skills/octocode-code-engineer/scripts/ast/tree-sitter.js +1 -0
  76. package/skills/octocode-code-engineer/scripts/ast/ts-analyzer.js +1 -0
  77. package/skills/octocode-code-engineer/scripts/collectors/chains.js +1 -0
  78. package/skills/octocode-code-engineer/scripts/collectors/effects.js +1 -0
  79. package/skills/octocode-code-engineer/scripts/collectors/input-sources.js +1 -0
  80. package/skills/octocode-code-engineer/scripts/collectors/performance.js +1 -0
  81. package/skills/octocode-code-engineer/scripts/collectors/prototype-pollution.js +1 -0
  82. package/skills/octocode-code-engineer/scripts/collectors/security.js +1 -0
  83. package/skills/octocode-code-engineer/scripts/collectors/test-profile.js +1 -0
  84. package/skills/octocode-code-engineer/scripts/common/is-direct-run.js +1 -0
  85. package/skills/octocode-code-engineer/scripts/common/utils.js +1 -0
  86. package/skills/octocode-code-engineer/scripts/detectors/code-quality.js +1 -0
  87. package/skills/octocode-code-engineer/scripts/detectors/cohesion.js +1 -0
  88. package/skills/octocode-code-engineer/scripts/detectors/coupling.js +1 -0
  89. package/skills/octocode-code-engineer/scripts/detectors/cycle.js +1 -0
  90. package/skills/octocode-code-engineer/scripts/detectors/dead-code.js +1 -0
  91. package/skills/octocode-code-engineer/scripts/detectors/import-style.js +1 -0
  92. package/skills/octocode-code-engineer/scripts/detectors/index.js +1 -0
  93. package/skills/octocode-code-engineer/scripts/detectors/security.js +1 -0
  94. package/skills/octocode-code-engineer/scripts/detectors/semantic.js +1 -0
  95. package/skills/octocode-code-engineer/scripts/detectors/shared.js +1 -0
  96. package/skills/octocode-code-engineer/scripts/detectors/test-quality.js +1 -0
  97. package/skills/octocode-code-engineer/scripts/index.js +1 -0
  98. package/skills/octocode-code-engineer/scripts/pipeline/cache.js +1 -0
  99. package/skills/octocode-code-engineer/scripts/pipeline/cli.js +1 -0
  100. package/skills/octocode-code-engineer/scripts/pipeline/main.js +2 -0
  101. package/skills/octocode-code-engineer/scripts/reporting/analysis.js +1 -0
  102. package/skills/octocode-code-engineer/scripts/reporting/summary-md.js +1 -0
  103. package/skills/octocode-code-engineer/scripts/reporting/writer.js +1 -0
  104. package/skills/octocode-code-engineer/scripts/types/constants.js +1 -0
  105. package/skills/octocode-code-engineer/scripts/types/index.js +1 -0
  106. package/skills/octocode-code-engineer/scripts/types/interfaces.js +1 -0
  107. package/skills/octocode-code-engineer/src/analysis/dependencies.test.ts +545 -0
  108. package/skills/octocode-code-engineer/src/analysis/dependencies.ts +406 -0
  109. package/skills/octocode-code-engineer/src/analysis/dependency-summary.test.ts +566 -0
  110. package/skills/octocode-code-engineer/src/analysis/dependency-summary.ts +257 -0
  111. package/skills/octocode-code-engineer/src/analysis/discovery.test.ts +420 -0
  112. package/skills/octocode-code-engineer/src/analysis/discovery.ts +87 -0
  113. package/skills/octocode-code-engineer/src/analysis/graph-analytics.test.ts +449 -0
  114. package/skills/octocode-code-engineer/src/analysis/graph-analytics.ts +534 -0
  115. package/skills/octocode-code-engineer/src/analysis/semantic.test.ts +1533 -0
  116. package/skills/octocode-code-engineer/src/analysis/semantic.ts +830 -0
  117. package/skills/octocode-code-engineer/src/ast/helpers.test.ts +185 -0
  118. package/skills/octocode-code-engineer/src/ast/helpers.ts +62 -0
  119. package/skills/octocode-code-engineer/src/ast/metrics.test.ts +304 -0
  120. package/skills/octocode-code-engineer/src/ast/metrics.ts +204 -0
  121. package/skills/octocode-code-engineer/src/ast/search.test.ts +647 -0
  122. package/skills/octocode-code-engineer/src/ast/search.ts +648 -0
  123. package/skills/octocode-code-engineer/src/ast/tree-search.test.ts +199 -0
  124. package/skills/octocode-code-engineer/src/ast/tree-search.ts +392 -0
  125. package/skills/octocode-code-engineer/src/ast/tree-sitter.test.ts +407 -0
  126. package/skills/octocode-code-engineer/src/ast/tree-sitter.ts +402 -0
  127. package/skills/octocode-code-engineer/src/ast/ts-analyzer.test.ts +1864 -0
  128. package/skills/octocode-code-engineer/src/ast/ts-analyzer.ts +509 -0
  129. package/skills/octocode-code-engineer/src/collectors/chains.ts +74 -0
  130. package/skills/octocode-code-engineer/src/collectors/effects.test.ts +490 -0
  131. package/skills/octocode-code-engineer/src/collectors/effects.ts +332 -0
  132. package/skills/octocode-code-engineer/src/collectors/input-sources.test.ts +144 -0
  133. package/skills/octocode-code-engineer/src/collectors/input-sources.ts +196 -0
  134. package/skills/octocode-code-engineer/src/collectors/performance.test.ts +82 -0
  135. package/skills/octocode-code-engineer/src/collectors/performance.ts +141 -0
  136. package/skills/octocode-code-engineer/src/collectors/prototype-pollution.test.ts +55 -0
  137. package/skills/octocode-code-engineer/src/collectors/prototype-pollution.ts +162 -0
  138. package/skills/octocode-code-engineer/src/collectors/security.test.ts +124 -0
  139. package/skills/octocode-code-engineer/src/collectors/security.ts +309 -0
  140. package/skills/octocode-code-engineer/src/collectors/test-profile.test.ts +97 -0
  141. package/skills/octocode-code-engineer/src/collectors/test-profile.ts +269 -0
  142. package/skills/octocode-code-engineer/src/common/is-direct-run.test.ts +32 -0
  143. package/skills/octocode-code-engineer/src/common/is-direct-run.ts +13 -0
  144. package/skills/octocode-code-engineer/src/common/utils.test.ts +463 -0
  145. package/skills/octocode-code-engineer/src/common/utils.ts +304 -0
  146. package/skills/octocode-code-engineer/src/detectors/code-quality.ts +966 -0
  147. package/skills/octocode-code-engineer/src/detectors/cohesion.ts +539 -0
  148. package/skills/octocode-code-engineer/src/detectors/coupling.ts +323 -0
  149. package/skills/octocode-code-engineer/src/detectors/cycle.ts +349 -0
  150. package/skills/octocode-code-engineer/src/detectors/dead-code.ts +320 -0
  151. package/skills/octocode-code-engineer/src/detectors/import-style.ts +376 -0
  152. package/skills/octocode-code-engineer/src/detectors/index.test.ts +3061 -0
  153. package/skills/octocode-code-engineer/src/detectors/index.ts +88 -0
  154. package/skills/octocode-code-engineer/src/detectors/security.test.ts +882 -0
  155. package/skills/octocode-code-engineer/src/detectors/security.ts +821 -0
  156. package/skills/octocode-code-engineer/src/detectors/semantic.ts +758 -0
  157. package/skills/octocode-code-engineer/src/detectors/shared.ts +49 -0
  158. package/skills/octocode-code-engineer/src/detectors/test-quality.test.ts +388 -0
  159. package/skills/octocode-code-engineer/src/detectors/test-quality.ts +367 -0
  160. package/skills/octocode-code-engineer/src/index.test.ts +4425 -0
  161. package/skills/octocode-code-engineer/src/index.ts +403 -0
  162. package/skills/octocode-code-engineer/src/pipeline/cache.test.ts +199 -0
  163. package/skills/octocode-code-engineer/src/pipeline/cache.ts +130 -0
  164. package/skills/octocode-code-engineer/src/pipeline/cli.test.ts +493 -0
  165. package/skills/octocode-code-engineer/src/pipeline/cli.ts +344 -0
  166. package/skills/octocode-code-engineer/src/pipeline/main.test.ts +174 -0
  167. package/skills/octocode-code-engineer/src/pipeline/main.ts +1074 -0
  168. package/skills/octocode-code-engineer/src/pipeline.test.ts +84 -0
  169. package/skills/octocode-code-engineer/src/reporting/analysis.test.ts +782 -0
  170. package/skills/octocode-code-engineer/src/reporting/analysis.ts +688 -0
  171. package/skills/octocode-code-engineer/src/reporting/output-contract.test.ts +463 -0
  172. package/skills/octocode-code-engineer/src/reporting/summary-md.test.ts +421 -0
  173. package/skills/octocode-code-engineer/src/reporting/summary-md.ts +714 -0
  174. package/skills/octocode-code-engineer/src/reporting/writer.ts +430 -0
  175. package/skills/octocode-code-engineer/src/sanity.test.ts +47 -0
  176. package/skills/octocode-code-engineer/src/types/constants.ts +248 -0
  177. package/skills/octocode-code-engineer/src/types/index.ts +80 -0
  178. package/skills/octocode-code-engineer/src/types/interfaces.ts +682 -0
  179. package/skills/octocode-code-engineer/tsconfig.json +17 -0
  180. package/skills/octocode-code-engineer/vitest.config.ts +8 -0
  181. package/skills/octocode-documentation-writer/README.md +113 -0
  182. package/skills/octocode-documentation-writer/SKILL.md +886 -0
  183. package/skills/octocode-documentation-writer/references/agent-discovery-analysis.md +453 -0
  184. package/skills/octocode-documentation-writer/references/agent-documentation-writer.md +255 -0
  185. package/skills/octocode-documentation-writer/references/agent-engineer-questions.md +247 -0
  186. package/skills/octocode-documentation-writer/references/agent-orchestrator.md +370 -0
  187. package/skills/octocode-documentation-writer/references/agent-qa-validator.md +227 -0
  188. package/skills/octocode-documentation-writer/references/agent-researcher.md +250 -0
  189. package/skills/octocode-documentation-writer/schemas/analysis-schema.json +886 -0
  190. package/skills/octocode-documentation-writer/schemas/discovery-tasks.json +96 -0
  191. package/skills/octocode-documentation-writer/schemas/documentation-structure.json +373 -0
  192. package/skills/octocode-documentation-writer/schemas/partial-discovery-schema.json +102 -0
  193. package/skills/octocode-documentation-writer/schemas/partial-research-schema.json +98 -0
  194. package/skills/octocode-documentation-writer/schemas/qa-results-schema.json +113 -0
  195. package/skills/octocode-documentation-writer/schemas/questions-schema.json +228 -0
  196. package/skills/octocode-documentation-writer/schemas/research-schema.json +104 -0
  197. package/skills/octocode-documentation-writer/schemas/state-schema.json +222 -0
  198. package/skills/octocode-documentation-writer/schemas/work-assignments-schema.json +74 -0
  199. package/skills/octocode-plan/SKILL.md +122 -116
  200. package/skills/octocode-prompt-optimizer/SKILL.md +617 -0
  201. package/skills/octocode-pull-request-reviewer/README.md +249 -0
  202. package/skills/octocode-pull-request-reviewer/SKILL.md +479 -0
  203. package/skills/octocode-pull-request-reviewer/references/dependency-check.md +74 -0
  204. package/skills/octocode-pull-request-reviewer/references/domain-reviewers.md +24 -0
  205. package/skills/octocode-pull-request-reviewer/references/execution-lifecycle.md +441 -0
  206. package/skills/octocode-pull-request-reviewer/references/flow-analysis-protocol.md +64 -0
  207. package/skills/octocode-pull-request-reviewer/references/output-template.md +174 -0
  208. package/skills/octocode-pull-request-reviewer/references/parallel-agent-protocol.md +182 -0
  209. package/skills/octocode-pull-request-reviewer/references/review-guidelines.md +26 -0
  210. package/skills/octocode-pull-request-reviewer/references/verification-checklist.md +40 -0
  211. package/skills/octocode-research/.claude/settings.local.json +46 -0
  212. package/skills/octocode-research/.octocode/plan/code-review-fixes/plan.md +312 -0
  213. package/skills/octocode-research/.octocode/plan/code-review-fixes/research.md +212 -0
  214. package/skills/octocode-research/.octocode/plans/NODE_SERVER_START_PLAN.md +755 -0
  215. package/skills/octocode-research/.octocode/research/code-review/research.md +371 -0
  216. package/skills/octocode-research/.octocode/review/IMPROVEMENTS.md +391 -0
  217. package/skills/octocode-research/.octocode/review/REVIEW_PLAN.md +289 -0
  218. package/skills/octocode-research/.octocode/review/REVIEW_REPORT.md +356 -0
  219. package/skills/octocode-research/AGENTS.md +349 -0
  220. package/skills/octocode-research/README.md +494 -0
  221. package/skills/octocode-research/SKILL.md +652 -274
  222. package/skills/octocode-research/docs/API_REFERENCE.md +562 -0
  223. package/skills/octocode-research/docs/ARCHITECTURE.md +554 -0
  224. package/skills/octocode-research/docs/FLOWS.md +577 -0
  225. package/skills/octocode-research/docs/OVERVIEW.md +564 -0
  226. package/skills/octocode-research/docs/SERVER_FLOWS.md +631 -0
  227. package/skills/octocode-research/ecosystem.config.cjs +88 -0
  228. package/skills/octocode-research/eslint.config.mjs +27 -0
  229. package/skills/octocode-research/package.json +84 -0
  230. package/skills/octocode-research/references/GUARDRAILS.md +40 -0
  231. package/skills/octocode-research/references/PARALLEL_AGENT_PROTOCOL.md +178 -0
  232. package/skills/octocode-research/references/roast-prompt.md +149 -0
  233. package/skills/octocode-research/scripts/server-init.d.ts +2 -0
  234. package/skills/octocode-research/scripts/server-init.js +2 -0
  235. package/skills/octocode-research/scripts/server.d.ts +8 -0
  236. package/skills/octocode-research/scripts/server.js +445 -0
  237. package/skills/octocode-research/src/__tests__/integration/circuitBreaker.test.ts +205 -0
  238. package/skills/octocode-research/src/__tests__/integration/routes.test.ts +374 -0
  239. package/skills/octocode-research/src/__tests__/unit/circuitBreaker.test.ts +245 -0
  240. package/skills/octocode-research/src/__tests__/unit/errorHandler.test.ts +183 -0
  241. package/skills/octocode-research/src/__tests__/unit/httpPreprocess.test.ts +157 -0
  242. package/skills/octocode-research/src/__tests__/unit/logger.test.ts +143 -0
  243. package/skills/octocode-research/src/__tests__/unit/queryParser.test.ts +130 -0
  244. package/skills/octocode-research/src/__tests__/unit/responseBuilder.test.ts +469 -0
  245. package/skills/octocode-research/src/__tests__/unit/retry.test.ts +205 -0
  246. package/skills/octocode-research/src/index.ts +186 -0
  247. package/skills/octocode-research/src/mcpCache.ts +49 -0
  248. package/skills/octocode-research/src/middleware/errorHandler.ts +65 -0
  249. package/skills/octocode-research/src/middleware/logger.ts +61 -0
  250. package/skills/octocode-research/src/middleware/queryParser.ts +115 -0
  251. package/skills/octocode-research/src/middleware/readiness.ts +17 -0
  252. package/skills/octocode-research/src/routes/github.ts +197 -0
  253. package/skills/octocode-research/src/routes/local.ts +175 -0
  254. package/skills/octocode-research/src/routes/lsp.ts +177 -0
  255. package/skills/octocode-research/src/routes/package.ts +127 -0
  256. package/skills/octocode-research/src/routes/prompts.ts +138 -0
  257. package/skills/octocode-research/src/routes/tools.ts +677 -0
  258. package/skills/octocode-research/src/server-init.ts +363 -0
  259. package/skills/octocode-research/src/server.ts +285 -0
  260. package/skills/octocode-research/src/types/errorGuards.ts +151 -0
  261. package/skills/octocode-research/src/types/express.d.ts +76 -0
  262. package/skills/octocode-research/src/types/guards.ts +98 -0
  263. package/skills/octocode-research/src/types/mcp.ts +119 -0
  264. package/skills/octocode-research/src/types/responses.ts +199 -0
  265. package/skills/octocode-research/src/types/toolTypes.ts +33 -0
  266. package/skills/octocode-research/src/utils/asyncTimeout.ts +116 -0
  267. package/skills/octocode-research/src/utils/circuitBreaker.ts +492 -0
  268. package/skills/octocode-research/src/utils/colors.ts +53 -0
  269. package/skills/octocode-research/src/utils/errorQueue.ts +71 -0
  270. package/skills/octocode-research/src/utils/logEmoji.ts +103 -0
  271. package/skills/octocode-research/src/utils/logger.ts +413 -0
  272. package/skills/octocode-research/src/utils/resilience.ts +169 -0
  273. package/skills/octocode-research/src/utils/responseBuilder.ts +495 -0
  274. package/skills/octocode-research/src/utils/responseFactory.ts +100 -0
  275. package/skills/octocode-research/src/utils/responseParser.ts +272 -0
  276. package/skills/octocode-research/src/utils/retry.ts +280 -0
  277. package/skills/octocode-research/src/utils/routeFactory.ts +117 -0
  278. package/skills/octocode-research/src/utils/url.ts +20 -0
  279. package/skills/octocode-research/src/validation/httpPreprocess.ts +155 -0
  280. package/skills/octocode-research/src/validation/index.ts +2 -0
  281. package/skills/octocode-research/src/validation/schemas.ts +578 -0
  282. package/skills/octocode-research/src/validation/toolCallSchema.ts +132 -0
  283. package/skills/octocode-research/tsconfig.json +21 -0
  284. package/skills/octocode-research/tsdown.config.ts +42 -0
  285. package/skills/octocode-research/vitest.config.ts +20 -0
  286. package/skills/octocode-researcher/SKILL.md +461 -0
  287. package/skills/octocode-researcher/references/fallbacks.md +120 -0
  288. package/skills/{octocode-local-search → octocode-researcher}/references/tool-reference.md +132 -49
  289. package/skills/{octocode-local-search → octocode-researcher}/references/workflow-patterns.md +204 -4
  290. package/skills/octocode-rfc-generator/SKILL.md +223 -0
  291. package/skills/octocode-rfc-generator/references/rfc-template.md +193 -0
  292. package/skills/octocode-roast/SKILL.md +63 -21
  293. package/skills/octocode-implement/SKILL.md +0 -293
  294. package/skills/octocode-implement/references/execution-phases.md +0 -317
  295. package/skills/octocode-implement/references/tool-reference.md +0 -403
  296. package/skills/octocode-implement/references/workflow-patterns.md +0 -385
  297. package/skills/octocode-local-search/SKILL.md +0 -449
  298. package/skills/octocode-pr-review/SKILL.md +0 -391
  299. package/skills/octocode-pr-review/references/domain-reviewers.md +0 -105
  300. package/skills/octocode-pr-review/references/execution-lifecycle.md +0 -116
  301. package/skills/octocode-pr-review/references/research-flows.md +0 -75
  302. package/skills/octocode-research/references/tool-reference.md +0 -304
  303. package/skills/octocode-research/references/workflow-patterns.md +0 -325
@@ -0,0 +1,1533 @@
1
+ import fs from 'node:fs';
2
+ import os from 'node:os';
3
+ import path from 'node:path';
4
+
5
+ import { describe, expect, it } from 'vitest';
6
+
7
+ import {
8
+ analyzeSemanticProfile,
9
+ collectAllAbsoluteFiles,
10
+ createSemanticContext,
11
+ } from './semantic.js';
12
+ import {
13
+ detectCircularTypeDependency,
14
+ detectConcreteDependency,
15
+ detectDeepOverrideChain,
16
+ detectInterfaceCompliance,
17
+ detectMoveToCaller,
18
+ detectNarrowableType,
19
+ detectOrphanImplementation,
20
+ detectOverAbstraction,
21
+ detectSemanticDeadExports,
22
+ detectShotgunSurgery,
23
+ detectUnusedImports,
24
+ detectUnusedParameters,
25
+ runSemanticDetectors,
26
+ } from '../detectors/semantic.js';
27
+
28
+ import type { SemanticProfile } from './semantic.js';
29
+ import type { DependencyState, FileEntry } from '../types/index.js';
30
+
31
+ function makeTempDir(): string {
32
+ return fs.mkdtempSync(path.join(os.tmpdir(), 'sem-test-'));
33
+ }
34
+
35
+ function writeFiles(dir: string, files: Record<string, string>): void {
36
+ for (const [name, content] of Object.entries(files)) {
37
+ const filePath = path.join(dir, name);
38
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
39
+ fs.writeFileSync(filePath, content, 'utf8');
40
+ }
41
+ }
42
+
43
+ function makeFileEntry(
44
+ file: string,
45
+ overrides: Partial<FileEntry> = {}
46
+ ): FileEntry {
47
+ return {
48
+ package: 'test',
49
+ file,
50
+ parseEngine: 'typescript',
51
+ nodeCount: 0,
52
+ kindCounts: {},
53
+ functions: [],
54
+ flows: [],
55
+ dependencyProfile: {
56
+ internalDependencies: [],
57
+ externalDependencies: [],
58
+ unresolvedDependencies: [],
59
+ declaredExports: [],
60
+ importedSymbols: [],
61
+ reExports: [],
62
+ },
63
+ ...overrides,
64
+ };
65
+ }
66
+
67
+ function makeProfile(
68
+ file: string,
69
+ overrides: Partial<SemanticProfile> = {}
70
+ ): SemanticProfile {
71
+ return {
72
+ file,
73
+ referenceCountByExport: new Map(),
74
+ unusedParams: [],
75
+ interfaceImpls: [],
76
+ typeHierarchyDepth: 0,
77
+ typeHierarchies: [],
78
+ overrideChains: [],
79
+ abstractnessRatio: 0,
80
+ unusedImports: [],
81
+ concreteImports: [],
82
+ leakyReturns: [],
83
+ narrowableParams: [],
84
+ ...overrides,
85
+ };
86
+ }
87
+
88
+ describe('createSemanticContext', () => {
89
+ it('creates context from valid TypeScript files', () => {
90
+ const dir = makeTempDir();
91
+ writeFiles(dir, {
92
+ 'a.ts': 'export const x = 1;\n',
93
+ 'tsconfig.json': JSON.stringify({
94
+ compilerOptions: {
95
+ target: 'ES2022',
96
+ module: 'ESNext',
97
+ moduleResolution: 'node',
98
+ strict: true,
99
+ },
100
+ }),
101
+ });
102
+ const ctx = createSemanticContext([path.join(dir, 'a.ts')], dir);
103
+ expect(ctx.service).toBeDefined();
104
+ expect(ctx.checker).toBeDefined();
105
+ expect(ctx.program).toBeDefined();
106
+ fs.rmSync(dir, { recursive: true });
107
+ });
108
+
109
+ it('works without tsconfig.json (uses defaults)', () => {
110
+ const dir = makeTempDir();
111
+ writeFiles(dir, { 'b.ts': 'export function foo() { return 42; }\n' });
112
+ const ctx = createSemanticContext([path.join(dir, 'b.ts')], dir);
113
+ expect(ctx.program.getSourceFile(path.join(dir, 'b.ts'))).toBeDefined();
114
+ fs.rmSync(dir, { recursive: true });
115
+ });
116
+ });
117
+
118
+ describe('detectSemanticDeadExports', () => {
119
+ it('flags exports with zero references', () => {
120
+ const profile = makeProfile('test.ts', {
121
+ referenceCountByExport: new Map([
122
+ ['usedFn', { count: 5, uniqueFiles: 3, lineStart: 1, lineEnd: 3 }],
123
+ ['deadFn', { count: 0, uniqueFiles: 0, lineStart: 10, lineEnd: 15 }],
124
+ [
125
+ 'anotherDead',
126
+ { count: 0, uniqueFiles: 0, lineStart: 20, lineEnd: 25 },
127
+ ],
128
+ ]),
129
+ });
130
+ const findings = detectSemanticDeadExports([profile]);
131
+ expect(findings.length).toBe(2);
132
+ expect(findings.every(f => f.category === 'semantic-dead-export')).toBe(
133
+ true
134
+ );
135
+ expect(findings.every(f => f.severity === 'high')).toBe(true);
136
+ expect(findings.every(f => f.lspHints && f.lspHints.length > 0)).toBe(true);
137
+ expect(findings[0].lineStart).toBe(10);
138
+ expect(findings[1].lineStart).toBe(20);
139
+ });
140
+
141
+ it('skips exports with references', () => {
142
+ const profile = makeProfile('test.ts', {
143
+ referenceCountByExport: new Map([
144
+ ['usedFn', { count: 3, uniqueFiles: 2, lineStart: 1, lineEnd: 5 }],
145
+ ]),
146
+ });
147
+ expect(detectSemanticDeadExports([profile])).toHaveLength(0);
148
+ });
149
+ });
150
+
151
+ describe('detectConcreteDependency', () => {
152
+ it('flags concrete class imports', () => {
153
+ const profile = makeProfile('consumer.ts', {
154
+ concreteImports: [
155
+ { name: 'MyService', targetFile: 'service.ts', lineStart: 5 },
156
+ ],
157
+ });
158
+ const findings = detectConcreteDependency([profile]);
159
+ expect(findings.length).toBe(1);
160
+ expect(findings[0].category).toBe('concrete-dependency');
161
+ expect(findings[0].title).toContain('MyService');
162
+ expect(findings[0].lspHints?.[0]?.tool).toBe('lspGotoDefinition');
163
+ });
164
+
165
+ it('produces no findings when no concrete imports', () => {
166
+ const profile = makeProfile('consumer.ts', { concreteImports: [] });
167
+ expect(detectConcreteDependency([profile])).toHaveLength(0);
168
+ });
169
+ });
170
+
171
+ describe('detectUnusedParameters', () => {
172
+ it('flags unused parameters', () => {
173
+ const profile = makeProfile('handler.ts', {
174
+ unusedParams: [
175
+ {
176
+ functionName: 'handleRequest',
177
+ paramName: 'ctx',
178
+ lineStart: 10,
179
+ lineEnd: 25,
180
+ },
181
+ ],
182
+ });
183
+ const findings = detectUnusedParameters([profile]);
184
+ expect(findings.length).toBe(1);
185
+ expect(findings[0].category).toBe('unused-parameter');
186
+ expect(findings[0].title).toContain('ctx');
187
+ expect(findings[0].title).toContain('handleRequest');
188
+ expect(findings[0].lspHints?.[0]?.tool).toBe('lspFindReferences');
189
+ });
190
+
191
+ it('produces no findings when all params used', () => {
192
+ const profile = makeProfile('handler.ts', { unusedParams: [] });
193
+ expect(detectUnusedParameters([profile])).toHaveLength(0);
194
+ });
195
+ });
196
+
197
+ describe('detectDeepOverrideChain', () => {
198
+ it('flags deep method override chains', () => {
199
+ const profile = makeProfile('overrides.ts', {
200
+ overrideChains: [
201
+ {
202
+ methodName: 'render',
203
+ className: 'SuperWidget',
204
+ depth: 4,
205
+ chain: [],
206
+ lineStart: 30,
207
+ },
208
+ ],
209
+ });
210
+ const findings = detectDeepOverrideChain([profile], 3);
211
+ expect(findings.length).toBe(1);
212
+ expect(findings[0].category).toBe('deep-override-chain');
213
+ expect(findings[0].title).toContain('render');
214
+ expect(findings[0].title).toContain('SuperWidget');
215
+ });
216
+
217
+ it('does not flag shallow overrides', () => {
218
+ const profile = makeProfile('overrides.ts', {
219
+ overrideChains: [
220
+ {
221
+ methodName: 'render',
222
+ className: 'Widget',
223
+ depth: 1,
224
+ chain: [],
225
+ lineStart: 10,
226
+ },
227
+ ],
228
+ });
229
+ expect(detectDeepOverrideChain([profile], 3)).toHaveLength(0);
230
+ });
231
+ });
232
+
233
+ describe('detectInterfaceCompliance', () => {
234
+ it('flags missing members', () => {
235
+ const profile = makeProfile('impl.ts', {
236
+ interfaceImpls: [
237
+ {
238
+ interfaceName: 'IService',
239
+ className: 'MyService',
240
+ classFile: 'impl.ts',
241
+ classLine: 15,
242
+ missingMembers: ['init', 'destroy'],
243
+ anycastMembers: [],
244
+ },
245
+ ],
246
+ });
247
+ const findings = detectInterfaceCompliance([profile]);
248
+ expect(findings.length).toBe(1);
249
+ expect(findings[0].category).toBe('interface-compliance');
250
+ expect(findings[0].severity).toBe('high');
251
+ expect(findings[0].reason).toContain('init');
252
+ });
253
+
254
+ it('flags any-cast members', () => {
255
+ const profile = makeProfile('impl.ts', {
256
+ interfaceImpls: [
257
+ {
258
+ interfaceName: 'IRepo',
259
+ className: 'MyRepo',
260
+ classFile: 'impl.ts',
261
+ classLine: 20,
262
+ missingMembers: [],
263
+ anycastMembers: ['save'],
264
+ },
265
+ ],
266
+ });
267
+ const findings = detectInterfaceCompliance([profile]);
268
+ expect(findings.length).toBe(1);
269
+ expect(findings[0].severity).toBe('medium');
270
+ expect(findings[0].reason).toContain('any-cast');
271
+ });
272
+
273
+ it('produces no findings for clean implementations', () => {
274
+ const profile = makeProfile('impl.ts', { interfaceImpls: [] });
275
+ expect(detectInterfaceCompliance([profile])).toHaveLength(0);
276
+ });
277
+ });
278
+
279
+ describe('detectUnusedImports', () => {
280
+ it('flags imports with zero usage', () => {
281
+ const profile = makeProfile('consumer.ts', {
282
+ unusedImports: [{ name: 'unusedHelper', lineStart: 3 }],
283
+ });
284
+ const findings = detectUnusedImports([profile]);
285
+ expect(findings.length).toBe(1);
286
+ expect(findings[0].category).toBe('unused-import');
287
+ expect(findings[0].severity).toBe('low');
288
+ expect(findings[0].title).toContain('unusedHelper');
289
+ });
290
+
291
+ it('produces no findings when all imports used', () => {
292
+ const profile = makeProfile('consumer.ts', { unusedImports: [] });
293
+ expect(detectUnusedImports([profile])).toHaveLength(0);
294
+ });
295
+ });
296
+
297
+ describe('detectCircularTypeDependency', () => {
298
+ it('returns empty for non-circular types', () => {
299
+ const dir = makeTempDir();
300
+ writeFiles(dir, {
301
+ 'a.ts': 'export interface A { x: number; }\n',
302
+ 'b.ts': 'import type { A } from "./a";\nexport interface B { a: A; }\n',
303
+ });
304
+ const ctx = createSemanticContext(
305
+ [path.join(dir, 'a.ts'), path.join(dir, 'b.ts')],
306
+ dir
307
+ );
308
+ const profileA = makeProfile('a.ts');
309
+ const profileB = makeProfile('b.ts');
310
+ const findings = detectCircularTypeDependency(ctx, [profileA, profileB]);
311
+ expect(findings.length).toBe(0);
312
+ fs.rmSync(dir, { recursive: true });
313
+ });
314
+ });
315
+
316
+ describe('runSemanticDetectors', () => {
317
+ it('aggregates findings from all detectors', () => {
318
+ const dir = makeTempDir();
319
+ writeFiles(dir, {
320
+ 'test.ts': 'export const x = 1;\n',
321
+ });
322
+ const ctx = createSemanticContext([path.join(dir, 'test.ts')], dir);
323
+ const profile = makeProfile('test.ts', {
324
+ referenceCountByExport: new Map([
325
+ ['deadExport', { count: 0, uniqueFiles: 0, lineStart: 5, lineEnd: 5 }],
326
+ ]),
327
+ unusedParams: [
328
+ { functionName: 'fn', paramName: 'p', lineStart: 1, lineEnd: 1 },
329
+ ],
330
+ unusedImports: [{ name: 'unused', lineStart: 1 }],
331
+ });
332
+ const findings = runSemanticDetectors(ctx, [profile]);
333
+ const categories = new Set(findings.map(f => f.category));
334
+ expect(categories.has('unused-parameter')).toBe(true);
335
+ expect(categories.has('unused-import')).toBe(true);
336
+ fs.rmSync(dir, { recursive: true });
337
+ });
338
+ });
339
+
340
+ describe('collectAllAbsoluteFiles', () => {
341
+ it('merges files from summaries and dependency state', () => {
342
+ const dir = makeTempDir();
343
+ writeFiles(dir, {
344
+ 'a.ts': 'export const a = 1;',
345
+ 'b.ts': 'export const b = 2;',
346
+ 'c.ts': 'export const c = 3;',
347
+ });
348
+ const entries: FileEntry[] = [makeFileEntry('a.ts'), makeFileEntry('b.ts')];
349
+ const state: DependencyState = {
350
+ files: new Set(['b.ts', 'c.ts']),
351
+ outgoing: new Map(),
352
+ incoming: new Map(),
353
+ incomingFromTests: new Map(),
354
+ incomingFromProduction: new Map(),
355
+ externalCounts: new Map(),
356
+ unresolvedCounts: new Map(),
357
+ declaredExportsByFile: new Map(),
358
+ importedSymbolsByFile: new Map(),
359
+ reExportsByFile: new Map(),
360
+ };
361
+ const result = collectAllAbsoluteFiles(entries, state, dir);
362
+ expect(result.length).toBe(3);
363
+ expect(result).toContain(path.join(dir, 'a.ts'));
364
+ expect(result).toContain(path.join(dir, 'b.ts'));
365
+ expect(result).toContain(path.join(dir, 'c.ts'));
366
+ fs.rmSync(dir, { recursive: true });
367
+ });
368
+ });
369
+
370
+ describe('SEMANTIC_CATEGORIES constant', () => {
371
+ it('has exactly 12 semantic categories', async () => {
372
+ const { SEMANTIC_CATEGORIES } = await import('../types/index.js');
373
+ expect(SEMANTIC_CATEGORIES.size).toBe(12);
374
+ });
375
+ });
376
+
377
+ function setupCtx(dir: string, files: Record<string, string>) {
378
+ writeFiles(dir, files);
379
+ const absPaths = Object.keys(files)
380
+ .filter(f => f.endsWith('.ts'))
381
+ .map(f => path.join(dir, f));
382
+ return createSemanticContext(absPaths, dir);
383
+ }
384
+
385
+ describe('integration: semantic-dead-export', () => {
386
+ it('detects an export that no other file imports', () => {
387
+ const dir = makeTempDir();
388
+ const ctx = setupCtx(dir, {
389
+ 'lib.ts': [
390
+ 'export function usedFn() { return 1; }',
391
+ 'export function deadFn() { return 2; }',
392
+ ].join('\n'),
393
+ 'consumer.ts': [
394
+ 'import { usedFn } from "./lib";',
395
+ 'console.log(usedFn());',
396
+ ].join('\n'),
397
+ });
398
+ const entry = makeFileEntry('lib.ts', {
399
+ dependencyProfile: {
400
+ internalDependencies: [],
401
+ externalDependencies: [],
402
+ unresolvedDependencies: [],
403
+ declaredExports: [
404
+ { name: 'usedFn', kind: 'value', lineStart: 1, lineEnd: 1 },
405
+ { name: 'deadFn', kind: 'value', lineStart: 2, lineEnd: 2 },
406
+ ],
407
+ importedSymbols: [],
408
+ reExports: [],
409
+ },
410
+ });
411
+ const profile = analyzeSemanticProfile(
412
+ ctx,
413
+ path.join(dir, 'lib.ts'),
414
+ entry
415
+ );
416
+ const deadInfo = profile.referenceCountByExport.get('deadFn');
417
+ const usedInfo = profile.referenceCountByExport.get('usedFn');
418
+ expect(deadInfo?.count).toBe(0);
419
+ expect(usedInfo!.count).toBeGreaterThan(0);
420
+
421
+ const findings = detectSemanticDeadExports([profile]);
422
+ expect(findings.length).toBe(1);
423
+ expect(findings[0].title).toContain('deadFn');
424
+ expect(findings[0].lineStart).toBe(2);
425
+ expect(findings[0].lspHints![0].lineHint).toBe(2);
426
+ fs.rmSync(dir, { recursive: true });
427
+ });
428
+
429
+ it('does not flag exports that are referenced', () => {
430
+ const dir = makeTempDir();
431
+ const ctx = setupCtx(dir, {
432
+ 'util.ts': 'export const FOO = 42;\n',
433
+ 'main.ts': 'import { FOO } from "./util";\nconsole.log(FOO);\n',
434
+ });
435
+ const entry = makeFileEntry('util.ts', {
436
+ dependencyProfile: {
437
+ internalDependencies: [],
438
+ externalDependencies: [],
439
+ unresolvedDependencies: [],
440
+ declaredExports: [
441
+ { name: 'FOO', kind: 'value', lineStart: 1, lineEnd: 1 },
442
+ ],
443
+ importedSymbols: [],
444
+ reExports: [],
445
+ },
446
+ });
447
+ const profile = analyzeSemanticProfile(
448
+ ctx,
449
+ path.join(dir, 'util.ts'),
450
+ entry
451
+ );
452
+ expect(detectSemanticDeadExports([profile])).toHaveLength(0);
453
+ fs.rmSync(dir, { recursive: true });
454
+ });
455
+ });
456
+
457
+ describe('integration: unused-parameter', () => {
458
+ it('detects function parameter never used in body', () => {
459
+ const dir = makeTempDir();
460
+ const ctx = setupCtx(dir, {
461
+ 'handler.ts': [
462
+ 'export function process(data: string, unused: number): string {',
463
+ ' return data.trim();',
464
+ '}',
465
+ ].join('\n'),
466
+ });
467
+ const entry = makeFileEntry('handler.ts', {
468
+ functions: [
469
+ {
470
+ kind: 'function',
471
+ name: 'process',
472
+ nameHint: 'process',
473
+ file: 'handler.ts',
474
+ lineStart: 1,
475
+ lineEnd: 3,
476
+ columnStart: 0,
477
+ columnEnd: 0,
478
+ statementCount: 1,
479
+ complexity: 1,
480
+ maxBranchDepth: 0,
481
+ maxLoopDepth: 0,
482
+ returns: 1,
483
+ awaits: 0,
484
+ calls: 1,
485
+ loops: 0,
486
+ lengthLines: 3,
487
+ cognitiveComplexity: 0,
488
+ params: 2,
489
+ },
490
+ ],
491
+ });
492
+ const profile = analyzeSemanticProfile(
493
+ ctx,
494
+ path.join(dir, 'handler.ts'),
495
+ entry
496
+ );
497
+ expect(profile.unusedParams.length).toBe(1);
498
+ expect(profile.unusedParams[0].paramName).toBe('unused');
499
+ expect(profile.unusedParams[0].functionName).toBe('process');
500
+
501
+ const findings = detectUnusedParameters([profile]);
502
+ expect(findings.length).toBe(1);
503
+ expect(findings[0].category).toBe('unused-parameter');
504
+ expect(findings[0].title).toContain('unused');
505
+ fs.rmSync(dir, { recursive: true });
506
+ });
507
+
508
+ it('does not flag parameters that are used', () => {
509
+ const dir = makeTempDir();
510
+ const ctx = setupCtx(dir, {
511
+ 'fn.ts': [
512
+ 'export function add(a: number, b: number): number {',
513
+ ' return a + b;',
514
+ '}',
515
+ ].join('\n'),
516
+ });
517
+ const entry = makeFileEntry('fn.ts', {
518
+ functions: [
519
+ {
520
+ kind: 'function',
521
+ name: 'add',
522
+ nameHint: 'add',
523
+ file: 'fn.ts',
524
+ lineStart: 1,
525
+ lineEnd: 3,
526
+ columnStart: 0,
527
+ columnEnd: 0,
528
+ statementCount: 1,
529
+ complexity: 1,
530
+ maxBranchDepth: 0,
531
+ maxLoopDepth: 0,
532
+ returns: 1,
533
+ awaits: 0,
534
+ calls: 0,
535
+ loops: 0,
536
+ lengthLines: 3,
537
+ cognitiveComplexity: 0,
538
+ params: 2,
539
+ },
540
+ ],
541
+ });
542
+ const profile = analyzeSemanticProfile(ctx, path.join(dir, 'fn.ts'), entry);
543
+ expect(profile.unusedParams).toHaveLength(0);
544
+ fs.rmSync(dir, { recursive: true });
545
+ });
546
+
547
+ it('ignores underscore-prefixed params', () => {
548
+ const dir = makeTempDir();
549
+ const ctx = setupCtx(dir, {
550
+ 'skip.ts': [
551
+ 'export function handle(_event: string, data: number): number {',
552
+ ' return data;',
553
+ '}',
554
+ ].join('\n'),
555
+ });
556
+ const entry = makeFileEntry('skip.ts', {
557
+ functions: [
558
+ {
559
+ kind: 'function',
560
+ name: 'handle',
561
+ nameHint: 'handle',
562
+ file: 'skip.ts',
563
+ lineStart: 1,
564
+ lineEnd: 3,
565
+ columnStart: 0,
566
+ columnEnd: 0,
567
+ statementCount: 1,
568
+ complexity: 1,
569
+ maxBranchDepth: 0,
570
+ maxLoopDepth: 0,
571
+ returns: 1,
572
+ awaits: 0,
573
+ calls: 0,
574
+ loops: 0,
575
+ lengthLines: 3,
576
+ cognitiveComplexity: 0,
577
+ params: 2,
578
+ },
579
+ ],
580
+ });
581
+ const profile = analyzeSemanticProfile(
582
+ ctx,
583
+ path.join(dir, 'skip.ts'),
584
+ entry
585
+ );
586
+ expect(profile.unusedParams).toHaveLength(0);
587
+ fs.rmSync(dir, { recursive: true });
588
+ });
589
+ });
590
+
591
+ describe('integration: deep-override-chain', () => {
592
+ it('detects method overridden deep in hierarchy', () => {
593
+ const dir = makeTempDir();
594
+ const ctx = setupCtx(dir, {
595
+ 'override.ts': [
596
+ 'class Base { render(): string { return "base"; } }',
597
+ 'class Mid1 extends Base { render(): string { return "mid1"; } }',
598
+ 'class Mid2 extends Mid1 { render(): string { return "mid2"; } }',
599
+ 'class Mid3 extends Mid2 { render(): string { return "mid3"; } }',
600
+ 'export class Leaf extends Mid3 { render(): string { return "leaf"; } }',
601
+ ].join('\n'),
602
+ });
603
+ const entry = makeFileEntry('override.ts');
604
+ const profile = analyzeSemanticProfile(
605
+ ctx,
606
+ path.join(dir, 'override.ts'),
607
+ entry
608
+ );
609
+ const leafOverrides = profile.overrideChains.filter(
610
+ c => c.className === 'Leaf'
611
+ );
612
+ expect(leafOverrides.length).toBeGreaterThanOrEqual(1);
613
+ expect(leafOverrides[0].methodName).toBe('render');
614
+ expect(leafOverrides[0].depth).toBeGreaterThanOrEqual(4);
615
+
616
+ const findings = detectDeepOverrideChain([profile], 3);
617
+ expect(findings.length).toBeGreaterThanOrEqual(1);
618
+ expect(
619
+ findings.some(f => f.title.includes('render') && f.title.includes('Leaf'))
620
+ ).toBe(true);
621
+ fs.rmSync(dir, { recursive: true });
622
+ });
623
+ });
624
+
625
+ describe('integration: over-abstraction', () => {
626
+ it('detects interface with exactly one implementor', () => {
627
+ const dir = makeTempDir();
628
+ const ctx = setupCtx(dir, {
629
+ 'svc.ts': [
630
+ 'export interface IService { run(): void; }',
631
+ 'export class MyService implements IService { run() {} }',
632
+ ].join('\n'),
633
+ });
634
+ const profiles = [makeProfile('svc.ts')];
635
+ const findings = detectOverAbstraction(ctx, profiles);
636
+ expect(findings.length).toBe(1);
637
+ expect(findings[0].category).toBe('over-abstraction');
638
+ expect(findings[0].title).toContain('IService');
639
+ expect(findings[0].lspHints![0].symbolName).toBe('IService');
640
+ fs.rmSync(dir, { recursive: true });
641
+ });
642
+ });
643
+
644
+ describe('integration: concrete-dependency', () => {
645
+ it('detects import of concrete class via analyzeSemanticProfile', () => {
646
+ const dir = makeTempDir();
647
+ const ctx = setupCtx(dir, {
648
+ 'service.ts': 'export class DbService { save() {} }\n',
649
+ 'consumer.ts': [
650
+ 'import { DbService } from "./service";',
651
+ 'export function run(db: DbService) { db.save(); }',
652
+ ].join('\n'),
653
+ });
654
+ const entry = makeFileEntry('consumer.ts', {
655
+ dependencyProfile: {
656
+ internalDependencies: ['service.ts'],
657
+ externalDependencies: [],
658
+ unresolvedDependencies: [],
659
+ declaredExports: [],
660
+ importedSymbols: [
661
+ {
662
+ sourceModule: './service',
663
+ resolvedModule: 'service.ts',
664
+ importedName: 'DbService',
665
+ localName: 'DbService',
666
+ isTypeOnly: false,
667
+ lineStart: 1,
668
+ lineEnd: 1,
669
+ },
670
+ ],
671
+ reExports: [],
672
+ },
673
+ });
674
+ const profile = analyzeSemanticProfile(
675
+ ctx,
676
+ path.join(dir, 'consumer.ts'),
677
+ entry
678
+ );
679
+ expect(profile.concreteImports.length).toBe(1);
680
+ expect(profile.concreteImports[0].name).toBe('DbService');
681
+
682
+ const findings = detectConcreteDependency([profile]);
683
+ expect(findings.length).toBe(1);
684
+ expect(findings[0].category).toBe('concrete-dependency');
685
+ expect(findings[0].title).toContain('DbService');
686
+ fs.rmSync(dir, { recursive: true });
687
+ });
688
+
689
+ it('does not flag import of abstract class', () => {
690
+ const dir = makeTempDir();
691
+ const ctx = setupCtx(dir, {
692
+ 'base.ts':
693
+ 'export abstract class BaseService { abstract save(): void; }\n',
694
+ 'consumer2.ts': [
695
+ 'import { BaseService } from "./base";',
696
+ 'export function run(svc: BaseService) { svc.save(); }',
697
+ ].join('\n'),
698
+ });
699
+ const entry = makeFileEntry('consumer2.ts', {
700
+ dependencyProfile: {
701
+ internalDependencies: ['base.ts'],
702
+ externalDependencies: [],
703
+ unresolvedDependencies: [],
704
+ declaredExports: [],
705
+ importedSymbols: [
706
+ {
707
+ sourceModule: './base',
708
+ resolvedModule: 'base.ts',
709
+ importedName: 'BaseService',
710
+ localName: 'BaseService',
711
+ isTypeOnly: false,
712
+ lineStart: 1,
713
+ lineEnd: 1,
714
+ },
715
+ ],
716
+ reExports: [],
717
+ },
718
+ });
719
+ const profile = analyzeSemanticProfile(
720
+ ctx,
721
+ path.join(dir, 'consumer2.ts'),
722
+ entry
723
+ );
724
+ expect(profile.concreteImports).toHaveLength(0);
725
+ fs.rmSync(dir, { recursive: true });
726
+ });
727
+ });
728
+
729
+ describe('integration: unused-import', () => {
730
+ it('detects import that is never used in the file', () => {
731
+ const dir = makeTempDir();
732
+ const ctx = setupCtx(dir, {
733
+ 'utils.ts':
734
+ 'export function helper() { return 1; }\nexport function unused() { return 2; }\n',
735
+ 'main.ts': [
736
+ 'import { helper, unused } from "./utils";',
737
+ 'console.log(helper());',
738
+ ].join('\n'),
739
+ });
740
+ const entry = makeFileEntry('main.ts', {
741
+ dependencyProfile: {
742
+ internalDependencies: ['utils.ts'],
743
+ externalDependencies: [],
744
+ unresolvedDependencies: [],
745
+ declaredExports: [],
746
+ importedSymbols: [
747
+ {
748
+ sourceModule: './utils',
749
+ importedName: 'helper',
750
+ localName: 'helper',
751
+ isTypeOnly: false,
752
+ lineStart: 1,
753
+ lineEnd: 1,
754
+ },
755
+ {
756
+ sourceModule: './utils',
757
+ importedName: 'unused',
758
+ localName: 'unused',
759
+ isTypeOnly: false,
760
+ lineStart: 1,
761
+ lineEnd: 1,
762
+ },
763
+ ],
764
+ reExports: [],
765
+ },
766
+ });
767
+ const profile = analyzeSemanticProfile(
768
+ ctx,
769
+ path.join(dir, 'main.ts'),
770
+ entry
771
+ );
772
+ expect(profile.unusedImports.length).toBe(1);
773
+ expect(profile.unusedImports[0].name).toBe('unused');
774
+
775
+ const findings = detectUnusedImports([profile]);
776
+ expect(findings.length).toBe(1);
777
+ expect(findings[0].category).toBe('unused-import');
778
+ expect(findings[0].title).toContain('unused');
779
+ fs.rmSync(dir, { recursive: true });
780
+ });
781
+ });
782
+
783
+ describe('integration: orphan-implementation', () => {
784
+ it('detects exported class with no external references and no interface', () => {
785
+ const dir = makeTempDir();
786
+ const ctx = setupCtx(dir, {
787
+ 'orphan.ts': [
788
+ 'export class OrphanClass {',
789
+ ' doStuff() { return 42; }',
790
+ '}',
791
+ ].join('\n'),
792
+ });
793
+ const profiles = [makeProfile('orphan.ts')];
794
+ const findings = detectOrphanImplementation(ctx, profiles);
795
+ expect(findings.length).toBe(1);
796
+ expect(findings[0].category).toBe('orphan-implementation');
797
+ expect(findings[0].title).toContain('OrphanClass');
798
+ expect(findings[0].lspHints![0].symbolName).toBe('OrphanClass');
799
+ fs.rmSync(dir, { recursive: true });
800
+ });
801
+
802
+ it('does not flag class that is imported by another file', () => {
803
+ const dir = makeTempDir();
804
+ const ctx = setupCtx(dir, {
805
+ 'used.ts': 'export class UsedClass { run() {} }\n',
806
+ 'consumer.ts':
807
+ 'import { UsedClass } from "./used";\nnew UsedClass().run();\n',
808
+ });
809
+ const profiles = [makeProfile('used.ts')];
810
+ const findings = detectOrphanImplementation(ctx, profiles);
811
+ expect(findings).toHaveLength(0);
812
+ fs.rmSync(dir, { recursive: true });
813
+ });
814
+
815
+ it('does not flag class that implements an interface', () => {
816
+ const dir = makeTempDir();
817
+ const ctx = setupCtx(dir, {
818
+ 'impl.ts': [
819
+ 'interface IWorker { work(): void; }',
820
+ 'export class Worker implements IWorker { work() {} }',
821
+ ].join('\n'),
822
+ });
823
+ const profiles = [makeProfile('impl.ts')];
824
+ const findings = detectOrphanImplementation(ctx, profiles);
825
+ expect(findings).toHaveLength(0);
826
+ fs.rmSync(dir, { recursive: true });
827
+ });
828
+ });
829
+
830
+ describe('integration: circular-type-dependency', () => {
831
+ it('detects circular type references between two types', () => {
832
+ const dir = makeTempDir();
833
+ const ctx = setupCtx(dir, {
834
+ 'types.ts': [
835
+ 'export interface NodeA { child: NodeB; }',
836
+ 'export interface NodeB { parent: NodeA; }',
837
+ ].join('\n'),
838
+ });
839
+ const profiles = [makeProfile('types.ts')];
840
+ const findings = detectCircularTypeDependency(ctx, profiles);
841
+ expect(findings.length).toBeGreaterThanOrEqual(1);
842
+ expect(findings[0].category).toBe('circular-type-dependency');
843
+ fs.rmSync(dir, { recursive: true });
844
+ });
845
+
846
+ it('does not flag non-circular types', () => {
847
+ const dir = makeTempDir();
848
+ const ctx = setupCtx(dir, {
849
+ 'linear.ts': [
850
+ 'export interface Req { url: string; }',
851
+ 'export interface Res { body: string; }',
852
+ ].join('\n'),
853
+ });
854
+ const profiles = [makeProfile('linear.ts')];
855
+ const findings = detectCircularTypeDependency(ctx, profiles);
856
+ expect(findings).toHaveLength(0);
857
+ fs.rmSync(dir, { recursive: true });
858
+ });
859
+ });
860
+
861
+ describe('integration: interface-compliance', () => {
862
+ it('detects class with any-cast member implementing interface', () => {
863
+ const dir = makeTempDir();
864
+ const ctx = setupCtx(dir, {
865
+ 'compliance.ts': [
866
+ 'interface ILogger { log(msg: string): void; level: string; }',
867
+ 'export class BadLogger implements ILogger {',
868
+ ' log(msg: any): void { console.log(msg); }',
869
+ ' level: any = "info";',
870
+ '}',
871
+ ].join('\n'),
872
+ });
873
+ const entry = makeFileEntry('compliance.ts');
874
+ const profile = analyzeSemanticProfile(
875
+ ctx,
876
+ path.join(dir, 'compliance.ts'),
877
+ entry
878
+ );
879
+ expect(profile.interfaceImpls.length).toBeGreaterThanOrEqual(1);
880
+ const impl = profile.interfaceImpls.find(i => i.className === 'BadLogger');
881
+ expect(impl).toBeDefined();
882
+ expect(impl!.anycastMembers.length).toBeGreaterThanOrEqual(1);
883
+
884
+ const findings = detectInterfaceCompliance([profile]);
885
+ expect(findings.length).toBeGreaterThanOrEqual(1);
886
+ expect(findings[0].category).toBe('interface-compliance');
887
+ fs.rmSync(dir, { recursive: true });
888
+ });
889
+ });
890
+
891
+ describe('integration: includeTests filtering', () => {
892
+ it('excludes test-file refs by default (includeTests=false)', () => {
893
+ const dir = makeTempDir();
894
+ const ctx = setupCtx(dir, {
895
+ 'lib.ts': 'export function internalOnly() { return 1; }\n',
896
+ 'lib.test.ts': 'import { internalOnly } from "./lib";\ninternalOnly();\n',
897
+ });
898
+ const entry = makeFileEntry('lib.ts', {
899
+ dependencyProfile: {
900
+ internalDependencies: [],
901
+ externalDependencies: [],
902
+ unresolvedDependencies: [],
903
+ declaredExports: [
904
+ { name: 'internalOnly', kind: 'value', lineStart: 1, lineEnd: 1 },
905
+ ],
906
+ importedSymbols: [],
907
+ reExports: [],
908
+ },
909
+ });
910
+ const profile = analyzeSemanticProfile(
911
+ ctx,
912
+ path.join(dir, 'lib.ts'),
913
+ entry,
914
+ false
915
+ );
916
+ const info = profile.referenceCountByExport.get('internalOnly');
917
+ expect(info?.count).toBe(0);
918
+
919
+ const findings = detectSemanticDeadExports([profile]);
920
+ expect(findings.length).toBe(1);
921
+ expect(findings[0].title).toContain('internalOnly');
922
+ fs.rmSync(dir, { recursive: true });
923
+ });
924
+
925
+ it('includes test-file refs when includeTests=true', () => {
926
+ const dir = makeTempDir();
927
+ const ctx = setupCtx(dir, {
928
+ 'lib.ts': 'export function internalOnly() { return 1; }\n',
929
+ 'lib.test.ts': 'import { internalOnly } from "./lib";\ninternalOnly();\n',
930
+ });
931
+ const entry = makeFileEntry('lib.ts', {
932
+ dependencyProfile: {
933
+ internalDependencies: [],
934
+ externalDependencies: [],
935
+ unresolvedDependencies: [],
936
+ declaredExports: [
937
+ { name: 'internalOnly', kind: 'value', lineStart: 1, lineEnd: 1 },
938
+ ],
939
+ importedSymbols: [],
940
+ reExports: [],
941
+ },
942
+ });
943
+ const profile = analyzeSemanticProfile(
944
+ ctx,
945
+ path.join(dir, 'lib.ts'),
946
+ entry,
947
+ true
948
+ );
949
+ const info = profile.referenceCountByExport.get('internalOnly');
950
+ expect(info!.count).toBeGreaterThan(0);
951
+
952
+ const findings = detectSemanticDeadExports([profile]);
953
+ expect(findings).toHaveLength(0);
954
+ fs.rmSync(dir, { recursive: true });
955
+ });
956
+ });
957
+
958
+ describe('integration: full pipeline runSemanticDetectors', () => {
959
+ it('finds multiple issues in a multi-file project', () => {
960
+ const dir = makeTempDir();
961
+ const ctx = setupCtx(dir, {
962
+ 'types.ts': [
963
+ 'export interface NodeA { child: NodeB; }',
964
+ 'export interface NodeB { parent: NodeA; }',
965
+ ].join('\n'),
966
+ 'lib.ts': [
967
+ 'export function usedFn() { return 1; }',
968
+ 'export function deadFn() { return 2; }',
969
+ 'export class Orphan { x = 1; }',
970
+ ].join('\n'),
971
+ 'app.ts': ['import { usedFn } from "./lib";', 'usedFn();'].join('\n'),
972
+ });
973
+
974
+ const libEntry = makeFileEntry('lib.ts', {
975
+ dependencyProfile: {
976
+ internalDependencies: [],
977
+ externalDependencies: [],
978
+ unresolvedDependencies: [],
979
+ declaredExports: [
980
+ { name: 'usedFn', kind: 'value', lineStart: 1, lineEnd: 1 },
981
+ { name: 'deadFn', kind: 'value', lineStart: 2, lineEnd: 2 },
982
+ { name: 'Orphan', kind: 'value', lineStart: 3, lineEnd: 3 },
983
+ ],
984
+ importedSymbols: [],
985
+ reExports: [],
986
+ },
987
+ });
988
+
989
+ const profiles: SemanticProfile[] = [];
990
+ profiles.push(
991
+ analyzeSemanticProfile(ctx, path.join(dir, 'lib.ts'), libEntry)
992
+ );
993
+ profiles.push(makeProfile('types.ts'));
994
+
995
+ const findings = runSemanticDetectors(ctx, profiles);
996
+ const categories = new Set(findings.map(f => f.category));
997
+
998
+ expect(categories.has('circular-type-dependency')).toBe(true);
999
+ expect(categories.has('orphan-implementation')).toBe(true);
1000
+
1001
+ expect(findings.every(f => f.file)).toBe(true);
1002
+ expect(findings.every(f => f.category)).toBe(true);
1003
+ expect(findings.every(f => f.severity)).toBe(true);
1004
+ expect(findings.every(f => f.suggestedFix)).toBe(true);
1005
+
1006
+ const withHints = findings.filter(f => f.lspHints && f.lspHints.length > 0);
1007
+ expect(withHints.length).toBeGreaterThan(0);
1008
+ for (const f of withHints) {
1009
+ for (const h of f.lspHints!) {
1010
+ expect(h.tool).toBeTruthy();
1011
+ expect(h.symbolName).toBeTruthy();
1012
+ expect(h.file).toBeTruthy();
1013
+ expect(h.expectedResult).toBeTruthy();
1014
+ expect(typeof h.lineHint).toBe('number');
1015
+ }
1016
+ }
1017
+
1018
+ fs.rmSync(dir, { recursive: true });
1019
+ });
1020
+ });
1021
+
1022
+ describe('integration: shotgun-surgery', () => {
1023
+ it('detects export used by many files', () => {
1024
+ const profile = makeProfile('util.ts', {
1025
+ referenceCountByExport: new Map([
1026
+ [
1027
+ 'widelyUsed',
1028
+ { count: 25, uniqueFiles: 10, lineStart: 5, lineEnd: 5 },
1029
+ ],
1030
+ [
1031
+ 'rarelyUsed',
1032
+ { count: 2, uniqueFiles: 1, lineStart: 10, lineEnd: 10 },
1033
+ ],
1034
+ ]),
1035
+ });
1036
+ const findings = detectShotgunSurgery([profile], 8);
1037
+ expect(findings.length).toBe(1);
1038
+ expect(findings[0].category).toBe('shotgun-surgery');
1039
+ expect(findings[0].title).toContain('widelyUsed');
1040
+ expect(findings[0].title).toContain('10 files');
1041
+ expect(findings[0].lspHints![0].tool).toBe('lspFindReferences');
1042
+ });
1043
+
1044
+ it('does not flag exports below threshold', () => {
1045
+ const profile = makeProfile('util.ts', {
1046
+ referenceCountByExport: new Map([
1047
+ ['ok', { count: 10, uniqueFiles: 5, lineStart: 1, lineEnd: 1 }],
1048
+ ]),
1049
+ });
1050
+ expect(detectShotgunSurgery([profile], 8)).toHaveLength(0);
1051
+ });
1052
+ });
1053
+
1054
+ describe('integration: move-to-caller', () => {
1055
+ it('detects export with exactly 1 consumer file', () => {
1056
+ const profile = makeProfile('helper.ts', {
1057
+ referenceCountByExport: new Map([
1058
+ ['singleUse', { count: 3, uniqueFiles: 1, lineStart: 5, lineEnd: 10 }],
1059
+ ]),
1060
+ });
1061
+ const findings = detectMoveToCaller([profile]);
1062
+ expect(findings.length).toBe(1);
1063
+ expect(findings[0].category).toBe('move-to-caller');
1064
+ expect(findings[0].title).toContain('singleUse');
1065
+ });
1066
+
1067
+ it('does not flag unused or multi-consumer exports', () => {
1068
+ const profile = makeProfile('helper.ts', {
1069
+ referenceCountByExport: new Map([
1070
+ ['unused', { count: 0, uniqueFiles: 0, lineStart: 1, lineEnd: 1 }],
1071
+ ['multi', { count: 5, uniqueFiles: 3, lineStart: 10, lineEnd: 10 }],
1072
+ ]),
1073
+ });
1074
+ expect(detectMoveToCaller([profile])).toHaveLength(0);
1075
+ });
1076
+ });
1077
+
1078
+ describe('integration: narrowable-type', () => {
1079
+ it('detects param narrowable from mock profile', () => {
1080
+ const profile = makeProfile('handler.ts', {
1081
+ narrowableParams: [
1082
+ {
1083
+ functionName: 'process',
1084
+ paramName: 'data',
1085
+ declaredType: 'string | number',
1086
+ actualTypes: ['string'],
1087
+ narrowedType: 'string',
1088
+ lineStart: 5,
1089
+ lineEnd: 10,
1090
+ },
1091
+ ],
1092
+ });
1093
+ const findings = detectNarrowableType([profile]);
1094
+ expect(findings.length).toBe(1);
1095
+ expect(findings[0].category).toBe('narrowable-type');
1096
+ expect(findings[0].title).toContain('string | number');
1097
+ expect(findings[0].title).toContain('string');
1098
+ expect(findings[0].lspHints![0].tool).toBe('lspCallHierarchy');
1099
+ });
1100
+
1101
+ it('returns empty when no narrowable params', () => {
1102
+ expect(detectNarrowableType([makeProfile('a.ts')])).toHaveLength(0);
1103
+ });
1104
+ });
1105
+
1106
+ describe('integration: type-hierarchy-and-override-chains', () => {
1107
+ it('detects typeHierarchyDepth and overrideChains in multi-file inheritance', () => {
1108
+ const dir = makeTempDir();
1109
+ const ctx = setupCtx(dir, {
1110
+ 'base.ts': [
1111
+ 'export class Base {',
1112
+ ' run(): string { return "base"; }',
1113
+ '}',
1114
+ ].join('\n'),
1115
+ 'child.ts': [
1116
+ 'import { Base } from "./base";',
1117
+ 'export class Child extends Base {',
1118
+ ' run(): string { return "child"; }',
1119
+ '}',
1120
+ ].join('\n'),
1121
+ 'grandchild.ts': [
1122
+ 'import { Child } from "./child";',
1123
+ 'export class Grandchild extends Child {',
1124
+ ' run(): string { return "grandchild"; }',
1125
+ '}',
1126
+ ].join('\n'),
1127
+ });
1128
+ const entry = makeFileEntry('grandchild.ts');
1129
+ const profile = analyzeSemanticProfile(
1130
+ ctx,
1131
+ path.join(dir, 'grandchild.ts'),
1132
+ entry
1133
+ );
1134
+
1135
+ expect(profile.typeHierarchyDepth).toBeGreaterThan(0);
1136
+ expect(profile.typeHierarchies.length).toBeGreaterThanOrEqual(1);
1137
+ const hierarchy = profile.typeHierarchies.find(
1138
+ h => h.name === 'Grandchild'
1139
+ );
1140
+ expect(hierarchy).toBeDefined();
1141
+ expect(hierarchy!.depth).toBeGreaterThanOrEqual(2);
1142
+
1143
+ expect(profile.overrideChains.length).toBeGreaterThanOrEqual(1);
1144
+ const runChain = profile.overrideChains.find(
1145
+ c => c.methodName === 'run' && c.className === 'Grandchild'
1146
+ );
1147
+ expect(runChain).toBeDefined();
1148
+ expect(runChain!.depth).toBeGreaterThanOrEqual(2);
1149
+
1150
+ const findings = detectDeepOverrideChain([profile], 0);
1151
+ expect(findings.length).toBeGreaterThanOrEqual(1);
1152
+ expect(
1153
+ findings.some(
1154
+ f => f.title.includes('run') && f.title.includes('Grandchild')
1155
+ )
1156
+ ).toBe(true);
1157
+
1158
+ fs.rmSync(dir, { recursive: true });
1159
+ });
1160
+ });
1161
+
1162
+ describe('integration: leaky-returns', () => {
1163
+ it('detects exported function returning type from another file', () => {
1164
+ const dir = makeTempDir();
1165
+ const ctx = setupCtx(dir, {
1166
+ 'types.ts': [
1167
+ 'export interface ApiResponse {',
1168
+ ' data: string;',
1169
+ '}',
1170
+ ].join('\n'),
1171
+ 'api.ts': [
1172
+ 'import type { ApiResponse } from "./types";',
1173
+ 'export function fetchData(): ApiResponse {',
1174
+ ' return { data: "ok" };',
1175
+ '}',
1176
+ ].join('\n'),
1177
+ });
1178
+ const entry = makeFileEntry('api.ts', {
1179
+ dependencyProfile: {
1180
+ internalDependencies: ['types.ts'],
1181
+ externalDependencies: [],
1182
+ unresolvedDependencies: [],
1183
+ declaredExports: [
1184
+ { name: 'fetchData', kind: 'value', lineStart: 2, lineEnd: 4 },
1185
+ ],
1186
+ importedSymbols: [
1187
+ {
1188
+ sourceModule: './types',
1189
+ resolvedModule: 'types.ts',
1190
+ importedName: 'ApiResponse',
1191
+ localName: 'ApiResponse',
1192
+ isTypeOnly: true,
1193
+ lineStart: 1,
1194
+ lineEnd: 1,
1195
+ },
1196
+ ],
1197
+ reExports: [],
1198
+ },
1199
+ functions: [
1200
+ {
1201
+ kind: 'function',
1202
+ name: 'fetchData',
1203
+ nameHint: 'fetchData',
1204
+ file: 'api.ts',
1205
+ lineStart: 2,
1206
+ lineEnd: 4,
1207
+ columnStart: 0,
1208
+ columnEnd: 0,
1209
+ statementCount: 1,
1210
+ complexity: 1,
1211
+ maxBranchDepth: 0,
1212
+ maxLoopDepth: 0,
1213
+ returns: 1,
1214
+ awaits: 0,
1215
+ calls: 0,
1216
+ loops: 0,
1217
+ lengthLines: 3,
1218
+ cognitiveComplexity: 0,
1219
+ params: 0,
1220
+ },
1221
+ ],
1222
+ });
1223
+ const profile = analyzeSemanticProfile(
1224
+ ctx,
1225
+ path.join(dir, 'api.ts'),
1226
+ entry
1227
+ );
1228
+
1229
+ expect(profile.leakyReturns.length).toBeGreaterThanOrEqual(1);
1230
+ const leaky = profile.leakyReturns.find(
1231
+ l => l.functionName === 'fetchData'
1232
+ );
1233
+ expect(leaky).toBeDefined();
1234
+ expect(leaky!.returnType).toContain('ApiResponse');
1235
+ expect(leaky!.sourceFile).toMatch(/types\.ts$/);
1236
+
1237
+ fs.rmSync(dir, { recursive: true });
1238
+ });
1239
+ });
1240
+
1241
+ describe('integration: abstractness-ratio', () => {
1242
+ it('computes abstractnessRatio from interfaces and classes', () => {
1243
+ const dir = makeTempDir();
1244
+ const ctx = setupCtx(dir, {
1245
+ 'shapes.ts': [
1246
+ 'export interface IShape { area(): number; }',
1247
+ 'export interface IDrawable { draw(): void; }',
1248
+ 'export class Circle implements IShape, IDrawable {',
1249
+ ' area() { return 0; }',
1250
+ ' draw() {}',
1251
+ '}',
1252
+ ].join('\n'),
1253
+ });
1254
+ const entry = makeFileEntry('shapes.ts', {
1255
+ dependencyProfile: {
1256
+ internalDependencies: [],
1257
+ externalDependencies: [],
1258
+ unresolvedDependencies: [],
1259
+ declaredExports: [
1260
+ { name: 'IShape', kind: 'type', lineStart: 1, lineEnd: 1 },
1261
+ { name: 'IDrawable', kind: 'type', lineStart: 2, lineEnd: 2 },
1262
+ { name: 'Circle', kind: 'value', lineStart: 3, lineEnd: 6 },
1263
+ ],
1264
+ importedSymbols: [],
1265
+ reExports: [],
1266
+ },
1267
+ });
1268
+ const profile = analyzeSemanticProfile(
1269
+ ctx,
1270
+ path.join(dir, 'shapes.ts'),
1271
+ entry
1272
+ );
1273
+
1274
+ expect(profile.abstractnessRatio).toBeGreaterThan(0);
1275
+ expect(profile.abstractnessRatio).toBeLessThanOrEqual(1);
1276
+ expect(Number.isFinite(profile.abstractnessRatio)).toBe(true);
1277
+
1278
+ fs.rmSync(dir, { recursive: true });
1279
+ });
1280
+
1281
+ it('abstractnessRatio is high when file has only interfaces (abstract types)', () => {
1282
+ const dir = makeTempDir();
1283
+ const ctx = setupCtx(dir, {
1284
+ 'types-only.ts': [
1285
+ 'export interface A { x: number; }',
1286
+ 'export interface B { y: string; }',
1287
+ ].join('\n'),
1288
+ });
1289
+ const entry = makeFileEntry('types-only.ts', {
1290
+ dependencyProfile: {
1291
+ internalDependencies: [],
1292
+ externalDependencies: [],
1293
+ unresolvedDependencies: [],
1294
+ declaredExports: [
1295
+ { name: 'A', kind: 'type', lineStart: 1, lineEnd: 1 },
1296
+ { name: 'B', kind: 'type', lineStart: 2, lineEnd: 2 },
1297
+ ],
1298
+ importedSymbols: [],
1299
+ reExports: [],
1300
+ },
1301
+ });
1302
+ const profile = analyzeSemanticProfile(
1303
+ ctx,
1304
+ path.join(dir, 'types-only.ts'),
1305
+ entry
1306
+ );
1307
+
1308
+ expect(profile.abstractnessRatio).toBeGreaterThan(0);
1309
+ expect(profile.abstractnessRatio).toBeLessThanOrEqual(1);
1310
+ fs.rmSync(dir, { recursive: true });
1311
+ });
1312
+ });
1313
+
1314
+ describe('integration: narrowable-params-comprehensive', () => {
1315
+ it('detects union param narrowable when all callers pass narrower type', () => {
1316
+ const dir = makeTempDir();
1317
+ const ctx = setupCtx(dir, {
1318
+ 'lib.ts': [
1319
+ 'export function process(data: string | number): string {',
1320
+ ' return String(data);',
1321
+ '}',
1322
+ ].join('\n'),
1323
+ 'caller-b.ts': [
1324
+ 'import { process } from "./lib";',
1325
+ 'process("hello");',
1326
+ ].join('\n'),
1327
+ 'caller-c.ts': [
1328
+ 'import { process } from "./lib";',
1329
+ 'process("world");',
1330
+ ].join('\n'),
1331
+ });
1332
+ const entry = makeFileEntry('lib.ts', {
1333
+ dependencyProfile: {
1334
+ internalDependencies: [],
1335
+ externalDependencies: [],
1336
+ unresolvedDependencies: [],
1337
+ declaredExports: [
1338
+ { name: 'process', kind: 'value', lineStart: 1, lineEnd: 3 },
1339
+ ],
1340
+ importedSymbols: [],
1341
+ reExports: [],
1342
+ },
1343
+ functions: [
1344
+ {
1345
+ kind: 'function',
1346
+ name: 'process',
1347
+ nameHint: 'process',
1348
+ file: 'lib.ts',
1349
+ lineStart: 1,
1350
+ lineEnd: 3,
1351
+ columnStart: 0,
1352
+ columnEnd: 0,
1353
+ statementCount: 1,
1354
+ complexity: 1,
1355
+ maxBranchDepth: 0,
1356
+ maxLoopDepth: 0,
1357
+ returns: 1,
1358
+ awaits: 0,
1359
+ calls: 1,
1360
+ loops: 0,
1361
+ lengthLines: 3,
1362
+ cognitiveComplexity: 0,
1363
+ params: 1,
1364
+ },
1365
+ ],
1366
+ });
1367
+ const profile = analyzeSemanticProfile(
1368
+ ctx,
1369
+ path.join(dir, 'lib.ts'),
1370
+ entry
1371
+ );
1372
+
1373
+ if (profile.narrowableParams.length > 0) {
1374
+ const np = profile.narrowableParams[0];
1375
+ expect(np.functionName).toBe('process');
1376
+ expect(np.paramName).toBe('data');
1377
+ expect(np.declaredType).toContain('string');
1378
+ expect(np.declaredType).toContain('number');
1379
+ expect(np.actualTypes).toBeDefined();
1380
+ expect(np.actualTypes.length).toBeGreaterThan(0);
1381
+ }
1382
+
1383
+ fs.rmSync(dir, { recursive: true });
1384
+ });
1385
+ });
1386
+
1387
+ describe('collectAllAbsoluteFiles (extended)', () => {
1388
+ it('filters out non-existent files', () => {
1389
+ const dir = makeTempDir();
1390
+ writeFiles(dir, {
1391
+ 'a.ts': 'export const a = 1;',
1392
+ });
1393
+ const entries: FileEntry[] = [
1394
+ makeFileEntry('a.ts'),
1395
+ makeFileEntry('nonexistent.ts'),
1396
+ ];
1397
+ const state: DependencyState = {
1398
+ files: new Set(['ghost.ts']),
1399
+ outgoing: new Map(),
1400
+ incoming: new Map(),
1401
+ incomingFromTests: new Map(),
1402
+ incomingFromProduction: new Map(),
1403
+ externalCounts: new Map(),
1404
+ unresolvedCounts: new Map(),
1405
+ declaredExportsByFile: new Map(),
1406
+ importedSymbolsByFile: new Map(),
1407
+ reExportsByFile: new Map(),
1408
+ };
1409
+ const result = collectAllAbsoluteFiles(entries, state, dir);
1410
+ expect(result).toContain(path.join(dir, 'a.ts'));
1411
+ expect(result).not.toContain(path.join(dir, 'nonexistent.ts'));
1412
+ expect(result).not.toContain(path.join(dir, 'ghost.ts'));
1413
+ expect(result.length).toBe(1);
1414
+ fs.rmSync(dir, { recursive: true });
1415
+ });
1416
+
1417
+ it('returns empty array when no files exist', () => {
1418
+ const dir = makeTempDir();
1419
+ const entries: FileEntry[] = [makeFileEntry('missing.ts')];
1420
+ const state: DependencyState = {
1421
+ files: new Set(),
1422
+ outgoing: new Map(),
1423
+ incoming: new Map(),
1424
+ incomingFromTests: new Map(),
1425
+ incomingFromProduction: new Map(),
1426
+ externalCounts: new Map(),
1427
+ unresolvedCounts: new Map(),
1428
+ declaredExportsByFile: new Map(),
1429
+ importedSymbolsByFile: new Map(),
1430
+ reExportsByFile: new Map(),
1431
+ };
1432
+ const result = collectAllAbsoluteFiles(entries, state, dir);
1433
+ expect(result).toEqual([]);
1434
+ fs.rmSync(dir, { recursive: true });
1435
+ });
1436
+ });
1437
+
1438
+ describe('integration: edge-cases', () => {
1439
+ it('file with no exports produces empty profile for reference counts', () => {
1440
+ const dir = makeTempDir();
1441
+ const ctx = setupCtx(dir, {
1442
+ 'no-exports.ts': ['const x = 1;', 'function foo() { return x; }'].join(
1443
+ '\n'
1444
+ ),
1445
+ });
1446
+ const entry = makeFileEntry('no-exports.ts', {
1447
+ dependencyProfile: {
1448
+ internalDependencies: [],
1449
+ externalDependencies: [],
1450
+ unresolvedDependencies: [],
1451
+ declaredExports: [],
1452
+ importedSymbols: [],
1453
+ reExports: [],
1454
+ },
1455
+ });
1456
+ const profile = analyzeSemanticProfile(
1457
+ ctx,
1458
+ path.join(dir, 'no-exports.ts'),
1459
+ entry
1460
+ );
1461
+
1462
+ expect(profile.referenceCountByExport.size).toBe(0);
1463
+ expect(profile.leakyReturns).toEqual([]);
1464
+ expect(profile.narrowableParams).toEqual([]);
1465
+ fs.rmSync(dir, { recursive: true });
1466
+ });
1467
+
1468
+ it('file with only type exports has no leakyReturns or narrowableParams', () => {
1469
+ const dir = makeTempDir();
1470
+ const ctx = setupCtx(dir, {
1471
+ 'types.ts': [
1472
+ 'export type Foo = { x: number };',
1473
+ 'export interface Bar { y: string; }',
1474
+ ].join('\n'),
1475
+ });
1476
+ const entry = makeFileEntry('types.ts', {
1477
+ dependencyProfile: {
1478
+ internalDependencies: [],
1479
+ externalDependencies: [],
1480
+ unresolvedDependencies: [],
1481
+ declaredExports: [
1482
+ { name: 'Foo', kind: 'type', lineStart: 1, lineEnd: 1 },
1483
+ { name: 'Bar', kind: 'type', lineStart: 2, lineEnd: 2 },
1484
+ ],
1485
+ importedSymbols: [],
1486
+ reExports: [],
1487
+ },
1488
+ });
1489
+ const profile = analyzeSemanticProfile(
1490
+ ctx,
1491
+ path.join(dir, 'types.ts'),
1492
+ entry
1493
+ );
1494
+
1495
+ expect(profile.abstractnessRatio).toBeGreaterThan(0);
1496
+ expect(profile.leakyReturns).toEqual([]);
1497
+ expect(profile.narrowableParams).toEqual([]);
1498
+ fs.rmSync(dir, { recursive: true });
1499
+ });
1500
+
1501
+ it('file with no functions has empty leakyReturns and narrowableParams', () => {
1502
+ const dir = makeTempDir();
1503
+ const ctx = setupCtx(dir, {
1504
+ 'constants.ts': [
1505
+ 'export const PI = 3.14;',
1506
+ 'export const E = 2.71;',
1507
+ ].join('\n'),
1508
+ });
1509
+ const entry = makeFileEntry('constants.ts', {
1510
+ dependencyProfile: {
1511
+ internalDependencies: [],
1512
+ externalDependencies: [],
1513
+ unresolvedDependencies: [],
1514
+ declaredExports: [
1515
+ { name: 'PI', kind: 'value', lineStart: 1, lineEnd: 1 },
1516
+ { name: 'E', kind: 'value', lineStart: 2, lineEnd: 2 },
1517
+ ],
1518
+ importedSymbols: [],
1519
+ reExports: [],
1520
+ },
1521
+ functions: [],
1522
+ });
1523
+ const profile = analyzeSemanticProfile(
1524
+ ctx,
1525
+ path.join(dir, 'constants.ts'),
1526
+ entry
1527
+ );
1528
+
1529
+ expect(profile.leakyReturns).toEqual([]);
1530
+ expect(profile.narrowableParams).toEqual([]);
1531
+ fs.rmSync(dir, { recursive: true });
1532
+ });
1533
+ });