octocode-cli 1.2.5 → 1.2.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (303) hide show
  1. package/LICENSE +21 -63
  2. package/README.md +86 -109
  3. package/out/octocode-cli.js +7027 -7014
  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,966 @@
1
+ import * as ts from 'typescript';
2
+
3
+ import { canAddFinding } from './shared.js';
4
+ import { isTestFile } from '../common/utils.js';
5
+
6
+ import type { FindingDraft } from './shared.js';
7
+ import type {
8
+ DuplicateGroup,
9
+ FileEntry,
10
+ Finding,
11
+ RedundantFlowGroup,
12
+ } from '../types/index.js';
13
+
14
+ export function detectDuplicateFunctionBodies(
15
+ duplicates: DuplicateGroup[]
16
+ ): FindingDraft[] {
17
+ const findings: FindingDraft[] = [];
18
+ for (const group of duplicates) {
19
+ const sample = group.locations[0];
20
+ const reason =
21
+ `Same ${group.kind} body shape appears in ${group.occurrences} places (` +
22
+ `${group.filesCount} file${group.filesCount > 1 ? 's' : ''}).`;
23
+ const severity: Finding['severity'] =
24
+ group.occurrences >= 6
25
+ ? 'high'
26
+ : group.occurrences >= 3
27
+ ? 'medium'
28
+ : 'low';
29
+ if (!canAddFinding(findings)) break;
30
+ findings.push({
31
+ ...sample,
32
+ severity,
33
+ category: 'duplicate-function-body',
34
+ title: `Deduplicate function body: ${group.signature}`,
35
+ reason,
36
+ files: group.locations.map(
37
+ loc => `${loc.file}:${loc.lineStart}-${loc.lineEnd}`
38
+ ),
39
+ suggestedFix: {
40
+ strategy:
41
+ 'Create a shared helper function once and replace duplicate call sites.',
42
+ steps: [
43
+ 'Extract one function to a dedicated utility module.',
44
+ 'Keep behavior unchanged by passing function-specific differences as params.',
45
+ 'Replace duplicated blocks with calls to the shared helper.',
46
+ 'Add/extend tests around each entry point that previously used duplicates.',
47
+ ],
48
+ },
49
+ impact: `Lower maintenance cost and reduce regression risk when behavior changes.`,
50
+ tags: ['duplication', 'maintainability', 'dryness'],
51
+ lspHints: [
52
+ {
53
+ tool: 'lspGotoDefinition',
54
+ symbolName: group.signature,
55
+ lineHint: sample.lineStart,
56
+ file: sample.file,
57
+ expectedResult: `navigate to one instance to compare implementations side-by-side`,
58
+ },
59
+ ],
60
+ });
61
+ }
62
+ return findings;
63
+ }
64
+
65
+ export function detectDuplicateFlowStructures(
66
+ controlDuplicates: RedundantFlowGroup[],
67
+ flowDupThreshold: number
68
+ ): FindingDraft[] {
69
+ const findings: FindingDraft[] = [];
70
+ for (const group of controlDuplicates) {
71
+ if (group.occurrences < flowDupThreshold) continue;
72
+ const sample = group.locations[0];
73
+ const reason = `${group.kind} structure appears ${group.occurrences} times across ${group.filesCount} file(s).`;
74
+ const severity: Finding['severity'] =
75
+ group.occurrences >= 10 ? 'high' : 'medium';
76
+ if (!canAddFinding(findings)) break;
77
+ findings.push({
78
+ ...sample,
79
+ severity,
80
+ category: 'duplicate-flow-structure',
81
+ title: `Extract repeated flow structure: ${group.kind}`,
82
+ reason,
83
+ files: group.locations.map(
84
+ loc => `${loc.file}:${loc.lineStart}-${loc.lineEnd}`
85
+ ),
86
+ suggestedFix: {
87
+ strategy:
88
+ 'Extract a reusable flow helper around the repeated structure.',
89
+ steps: [
90
+ 'Create one clear helper that accepts varying inputs as parameters.',
91
+ 'Call helper from each repeated site.',
92
+ 'Keep variable names aligned and add local adapter logic where needed.',
93
+ 'Document expected invariants for the shared flow.',
94
+ ],
95
+ },
96
+ impact: `Reduces duplicate control branches and normalizes edge-case handling.`,
97
+ tags: ['duplication', 'control-flow', 'dryness'],
98
+ });
99
+ }
100
+ return findings;
101
+ }
102
+
103
+ export function detectFunctionOptimization(
104
+ fileSummaries: FileEntry[],
105
+ criticalComplexityThreshold: number
106
+ ): FindingDraft[] {
107
+ const findings: FindingDraft[] = [];
108
+ for (const fileEntry of fileSummaries) {
109
+ for (const fn of fileEntry.functions) {
110
+ const alerts: string[] = [];
111
+ if (fn.complexity >= criticalComplexityThreshold)
112
+ alerts.push(
113
+ `Cyclomatic-like complexity is high (>=${criticalComplexityThreshold}).`
114
+ );
115
+ if (fn.maxBranchDepth >= 7)
116
+ alerts.push('Branch depth is very deep and hard to reason about.');
117
+ if (fn.maxLoopDepth >= 4)
118
+ alerts.push('Nested loops are high and likely expensive.');
119
+ if (fn.statementCount >= 24)
120
+ alerts.push(
121
+ 'Function body is large and may be doing multiple responsibilities.'
122
+ );
123
+
124
+ if (alerts.length === 0) continue;
125
+
126
+ const isHigh =
127
+ fn.complexity >= criticalComplexityThreshold ||
128
+ fn.maxBranchDepth >= 7 ||
129
+ fn.maxLoopDepth >= 4;
130
+ findings.push({
131
+ ...fn,
132
+ severity: isHigh ? 'high' : 'medium',
133
+ category: 'function-optimization',
134
+ title: `Potential function refactor: ${fn.name}`,
135
+ reason: alerts.join(' '),
136
+ files: [`${fn.file}:${fn.lineStart}-${fn.lineEnd}`],
137
+ suggestedFix: {
138
+ strategy: 'Refactor for readability and testability.',
139
+ steps: [
140
+ 'Split into smaller subroutines with single responsibilities.',
141
+ 'Convert deeply nested branches into guard clauses when safe.',
142
+ 'Replace loops with intent-specific helpers if one loop owns most lines.',
143
+ 'Add unit coverage for each extracted piece before deleting old logic.',
144
+ ],
145
+ },
146
+ impact: 'Cleaner flow, easier review and safer refactors.',
147
+ tags: ['complexity', 'readability', 'refactor'],
148
+ lspHints: [
149
+ {
150
+ tool: 'lspCallHierarchy',
151
+ symbolName: fn.name,
152
+ lineHint: fn.lineStart,
153
+ file: fn.file,
154
+ expectedResult: `inspect callers and callees to plan safe decomposition of ${fn.name}`,
155
+ },
156
+ ],
157
+ });
158
+ }
159
+ }
160
+ return findings;
161
+ }
162
+
163
+ export function computeCognitiveComplexity(node: ts.Node): number {
164
+ let total = 0;
165
+
166
+ const visit = (current: ts.Node, nesting: number): void => {
167
+ let increment = 0;
168
+ let nestable = false;
169
+
170
+ switch (current.kind) {
171
+ case ts.SyntaxKind.IfStatement:
172
+ case ts.SyntaxKind.ForStatement:
173
+ case ts.SyntaxKind.ForInStatement:
174
+ case ts.SyntaxKind.ForOfStatement:
175
+ case ts.SyntaxKind.WhileStatement:
176
+ case ts.SyntaxKind.DoStatement:
177
+ case ts.SyntaxKind.CatchClause:
178
+ case ts.SyntaxKind.ConditionalExpression:
179
+ case ts.SyntaxKind.SwitchStatement:
180
+ increment = 1;
181
+ nestable = true;
182
+ break;
183
+ default:
184
+ break;
185
+ }
186
+
187
+ if (
188
+ current.kind === ts.SyntaxKind.BinaryExpression &&
189
+ ((current as ts.BinaryExpression).operatorToken.kind ===
190
+ ts.SyntaxKind.AmpersandAmpersandToken ||
191
+ (current as ts.BinaryExpression).operatorToken.kind ===
192
+ ts.SyntaxKind.BarBarToken ||
193
+ (current as ts.BinaryExpression).operatorToken.kind ===
194
+ ts.SyntaxKind.QuestionQuestionToken)
195
+ ) {
196
+ increment = 1;
197
+ }
198
+
199
+ if (
200
+ current.kind === ts.SyntaxKind.IfStatement &&
201
+ current.parent &&
202
+ ts.isIfStatement(current.parent) &&
203
+ current.parent.elseStatement === current
204
+ ) {
205
+ increment = 1;
206
+ nestable = false;
207
+ }
208
+
209
+ if (nestable) {
210
+ total += increment + nesting;
211
+ ts.forEachChild(current, child => visit(child, nesting + 1));
212
+ return;
213
+ }
214
+
215
+ total += increment;
216
+ ts.forEachChild(current, child => visit(child, nesting));
217
+ };
218
+
219
+ visit(node, 0);
220
+ return total;
221
+ }
222
+
223
+ export function detectCognitiveComplexity(
224
+ fileSummaries: FileEntry[],
225
+ threshold: number = 15
226
+ ): FindingDraft[] {
227
+ const findings: FindingDraft[] = [];
228
+
229
+ for (const entry of fileSummaries) {
230
+ if (isTestFile(entry.file)) continue;
231
+ for (const fn of entry.functions) {
232
+ if (fn.cognitiveComplexity > threshold) {
233
+ findings.push({
234
+ severity: fn.cognitiveComplexity > 25 ? 'high' : 'medium',
235
+ category: 'cognitive-complexity',
236
+ file: entry.file,
237
+ lineStart: fn.lineStart,
238
+ lineEnd: fn.lineEnd,
239
+ title: `High cognitive complexity: ${fn.name} (${fn.cognitiveComplexity})`,
240
+ reason: `Function cognitive complexity is ${fn.cognitiveComplexity} (threshold: ${threshold}). Nested branches compound reading difficulty.`,
241
+ files: [`${entry.file}:${fn.lineStart}-${fn.lineEnd}`],
242
+ suggestedFix: {
243
+ strategy: 'Reduce nesting and simplify control flow.',
244
+ steps: [
245
+ 'Convert nested branches into early returns / guard clauses.',
246
+ 'Extract deeply nested blocks into named helper functions.',
247
+ 'Replace complex boolean chains with named predicates.',
248
+ ],
249
+ },
250
+ impact:
251
+ 'Lower cognitive complexity directly correlates with fewer bugs and faster code reviews.',
252
+ tags: ['complexity', 'readability', 'nesting'],
253
+ lspHints: [
254
+ {
255
+ tool: 'lspCallHierarchy',
256
+ symbolName: fn.name,
257
+ lineHint: fn.lineStart,
258
+ file: entry.file,
259
+ expectedResult: `understand call graph before simplifying ${fn.name}`,
260
+ },
261
+ ],
262
+ });
263
+ }
264
+ }
265
+ }
266
+
267
+ return findings;
268
+ }
269
+
270
+ export function detectExcessiveParameters(
271
+ fileSummaries: FileEntry[],
272
+ threshold: number = 5
273
+ ): FindingDraft[] {
274
+ const findings: FindingDraft[] = [];
275
+ for (const entry of fileSummaries) {
276
+ if (isTestFile(entry.file)) continue;
277
+ for (const fn of entry.functions) {
278
+ if (fn.params == null || fn.params <= threshold) continue;
279
+ findings.push({
280
+ severity: fn.params > 7 ? 'high' : 'medium',
281
+ category: 'excessive-parameters',
282
+ file: entry.file,
283
+ lineStart: fn.lineStart,
284
+ lineEnd: fn.lineEnd,
285
+ title: `Excessive parameters: ${fn.name} (${fn.params} params)`,
286
+ reason: `Function has ${fn.params} parameters (threshold: ${threshold}). High parameter counts make call sites hard to read and signal the function may be doing too much.`,
287
+ files: [`${entry.file}:${fn.lineStart}-${fn.lineEnd}`],
288
+ suggestedFix: {
289
+ strategy: 'Introduce a parameter object or split the function.',
290
+ steps: [
291
+ 'Group related parameters into an options/config object.',
292
+ 'Use destructuring at the function signature for clarity.',
293
+ 'Consider splitting into smaller, focused functions if params serve different concerns.',
294
+ ],
295
+ },
296
+ impact:
297
+ 'Improves call-site readability and makes the API easier to evolve.',
298
+ tags: ['api-design', 'readability', 'refactor'],
299
+ });
300
+ }
301
+ }
302
+ return findings;
303
+ }
304
+
305
+ export function detectEmptyCatchBlocks(
306
+ fileSummaries: FileEntry[]
307
+ ): FindingDraft[] {
308
+ const findings: FindingDraft[] = [];
309
+ for (const entry of fileSummaries) {
310
+ if (isTestFile(entry.file)) continue;
311
+ if (!entry.emptyCatches || entry.emptyCatches.length === 0) continue;
312
+ for (const loc of entry.emptyCatches) {
313
+ findings.push({
314
+ severity: 'medium',
315
+ category: 'empty-catch',
316
+ file: entry.file,
317
+ lineStart: loc.lineStart,
318
+ lineEnd: loc.lineEnd,
319
+ title: `Empty catch block silently swallows errors`,
320
+ reason: `Catch block at line ${loc.lineStart} has no statements — errors are silently ignored.`,
321
+ files: [`${entry.file}:${loc.lineStart}-${loc.lineEnd}`],
322
+ suggestedFix: {
323
+ strategy: 'Log, re-throw, or handle the error explicitly.',
324
+ steps: [
325
+ 'Add error logging (console.error or a logger) at minimum.',
326
+ 'Re-throw if the caller should handle the error.',
327
+ 'Add a comment explaining why swallowing is intentional, if it truly is.',
328
+ ],
329
+ },
330
+ impact:
331
+ 'Prevents silent failures that are extremely hard to debug in production.',
332
+ tags: ['error-handling', 'reliability', 'silent-failure'],
333
+ });
334
+ }
335
+ }
336
+ return findings;
337
+ }
338
+
339
+ export function detectSwitchNoDefault(
340
+ fileSummaries: FileEntry[]
341
+ ): FindingDraft[] {
342
+ const findings: FindingDraft[] = [];
343
+ for (const entry of fileSummaries) {
344
+ if (isTestFile(entry.file)) continue;
345
+ if (
346
+ !entry.switchesWithoutDefault ||
347
+ entry.switchesWithoutDefault.length === 0
348
+ )
349
+ continue;
350
+ for (const loc of entry.switchesWithoutDefault) {
351
+ findings.push({
352
+ severity: 'low',
353
+ category: 'switch-no-default',
354
+ file: entry.file,
355
+ lineStart: loc.lineStart,
356
+ lineEnd: loc.lineEnd,
357
+ title: `Switch statement missing default case`,
358
+ reason: `Switch at line ${loc.lineStart} has no default clause — unexpected values fall through silently.`,
359
+ files: [`${entry.file}:${loc.lineStart}-${loc.lineEnd}`],
360
+ suggestedFix: {
361
+ strategy:
362
+ 'Add a default case with error handling or exhaustive check.',
363
+ steps: [
364
+ 'Add a default clause that throws an unreachable error for exhaustiveness.',
365
+ 'Or log a warning for unexpected values.',
366
+ 'In TypeScript, use `never` type assertion for compile-time exhaustive checks.',
367
+ ],
368
+ },
369
+ impact:
370
+ 'Catches unexpected values early and prevents silent logic bugs.',
371
+ tags: ['control-flow', 'exhaustiveness', 'safety'],
372
+ });
373
+ }
374
+ }
375
+ return findings;
376
+ }
377
+
378
+ export function detectUnsafeAny(
379
+ fileSummaries: FileEntry[],
380
+ threshold: number = 5
381
+ ): FindingDraft[] {
382
+ const findings: FindingDraft[] = [];
383
+ for (const entry of fileSummaries) {
384
+ if (isTestFile(entry.file)) continue;
385
+ if (entry.anyCount == null || entry.anyCount <= threshold) continue;
386
+ if (!canAddFinding(findings)) break;
387
+ findings.push({
388
+ severity: entry.anyCount > 10 ? 'high' : 'medium',
389
+ category: 'unsafe-any',
390
+ file: entry.file,
391
+ lineStart: 1,
392
+ lineEnd: 1,
393
+ title: `Excessive \`any\` usage: ${entry.file} (${entry.anyCount} occurrences)`,
394
+ reason: `File uses \`any\` type ${entry.anyCount} times (threshold: ${threshold}). Each \`any\` disables type checking and allows silent runtime errors.`,
395
+ files: [entry.file],
396
+ suggestedFix: {
397
+ strategy: 'Replace `any` with specific types, `unknown`, or generics.',
398
+ steps: [
399
+ 'Replace `any` with `unknown` and add type guards where needed.',
400
+ 'Use generics for functions that operate on multiple types.',
401
+ 'Define proper interfaces for complex data shapes.',
402
+ 'Use `as const` assertions instead of `as any` where possible.',
403
+ ],
404
+ },
405
+ impact:
406
+ 'Restores TypeScript safety and catches bugs at compile time instead of runtime.',
407
+ tags: ['type-safety', 'reliability', 'typescript'],
408
+ });
409
+ }
410
+ return findings;
411
+ }
412
+
413
+ export function detectHighHalsteadEffort(
414
+ fileSummaries: FileEntry[],
415
+ effortThreshold: number = 500_000,
416
+ bugThreshold: number = 2.0
417
+ ): FindingDraft[] {
418
+ const findings: FindingDraft[] = [];
419
+ for (const entry of fileSummaries) {
420
+ if (isTestFile(entry.file)) continue;
421
+ for (const fn of entry.functions) {
422
+ if (!fn.halstead) continue;
423
+ const { effort, estimatedBugs, volume } = fn.halstead;
424
+ if (effort <= effortThreshold && estimatedBugs <= bugThreshold) continue;
425
+ const reasons: string[] = [];
426
+ if (effort > effortThreshold)
427
+ reasons.push(
428
+ `effort=${Math.round(effort)} (threshold: ${effortThreshold})`
429
+ );
430
+ if (estimatedBugs > bugThreshold)
431
+ reasons.push(
432
+ `estimatedBugs=${estimatedBugs.toFixed(2)} (threshold: ${bugThreshold})`
433
+ );
434
+ findings.push({
435
+ severity:
436
+ effort > effortThreshold * 2 || estimatedBugs > 5 ? 'high' : 'medium',
437
+ category: 'halstead-effort',
438
+ file: entry.file,
439
+ lineStart: fn.lineStart,
440
+ lineEnd: fn.lineEnd,
441
+ title: `High Halstead complexity: ${fn.name}`,
442
+ reason: `Function has high implementation complexity: ${reasons.join('; ')}. Volume=${Math.round(volume)}.`,
443
+ files: [`${entry.file}:${fn.lineStart}-${fn.lineEnd}`],
444
+ suggestedFix: {
445
+ strategy:
446
+ 'Reduce operator/operand count by extracting helpers and simplifying expressions.',
447
+ steps: [
448
+ 'Extract complex sub-expressions into named intermediate variables.',
449
+ 'Split into smaller functions with fewer unique operators/operands.',
450
+ 'Replace imperative loops with declarative array methods where clearer.',
451
+ ],
452
+ },
453
+ impact:
454
+ 'Lower Halstead effort correlates with fewer bugs and faster comprehension.',
455
+ tags: ['complexity', 'maintainability', 'effort'],
456
+ });
457
+ }
458
+ }
459
+ return findings;
460
+ }
461
+
462
+ export function detectLowMaintainability(
463
+ fileSummaries: FileEntry[],
464
+ threshold: number = 20
465
+ ): FindingDraft[] {
466
+ const findings: FindingDraft[] = [];
467
+ for (const entry of fileSummaries) {
468
+ if (isTestFile(entry.file)) continue;
469
+ for (const fn of entry.functions) {
470
+ if (
471
+ fn.maintainabilityIndex == null ||
472
+ fn.maintainabilityIndex >= threshold
473
+ )
474
+ continue;
475
+ findings.push({
476
+ severity: fn.maintainabilityIndex < 10 ? 'critical' : 'high',
477
+ category: 'low-maintainability',
478
+ file: entry.file,
479
+ lineStart: fn.lineStart,
480
+ lineEnd: fn.lineEnd,
481
+ title: `Low maintainability: ${fn.name} (MI=${fn.maintainabilityIndex.toFixed(1)})`,
482
+ reason: `Maintainability Index is ${fn.maintainabilityIndex.toFixed(1)} (threshold: ${threshold}, scale 0-100). Combines Halstead volume, cyclomatic complexity, and lines of code.`,
483
+ files: [`${entry.file}:${fn.lineStart}-${fn.lineEnd}`],
484
+ suggestedFix: {
485
+ strategy:
486
+ 'Reduce complexity, shorten the function, and simplify expressions.',
487
+ steps: [
488
+ 'Split into smaller functions to reduce LOC and cyclomatic complexity.',
489
+ 'Extract complex expressions to reduce Halstead volume.',
490
+ 'Convert nested logic to early returns and guard clauses.',
491
+ 'Consider if parts of the function belong in separate modules.',
492
+ ],
493
+ },
494
+ impact:
495
+ 'Higher MI directly predicts lower maintenance cost and defect rate.',
496
+ tags: ['maintainability', 'complexity', 'technical-debt'],
497
+ });
498
+ }
499
+ }
500
+ return findings;
501
+ }
502
+
503
+ export function detectTypeAssertionEscape(
504
+ fileSummaries: FileEntry[]
505
+ ): FindingDraft[] {
506
+ const findings: FindingDraft[] = [];
507
+
508
+ for (const entry of fileSummaries) {
509
+ if (isTestFile(entry.file)) continue;
510
+ const esc = entry.typeAssertionEscapes;
511
+ if (!esc) continue;
512
+
513
+ const total =
514
+ esc.asAny.length + esc.doubleAssertion.length + esc.nonNull.length;
515
+ if (total === 0) continue;
516
+
517
+ const parts: string[] = [];
518
+ if (esc.asAny.length > 0) parts.push(`${esc.asAny.length} \`as any\``);
519
+ if (esc.doubleAssertion.length > 0)
520
+ parts.push(`${esc.doubleAssertion.length} double-assertion`);
521
+ if (esc.nonNull.length > 0)
522
+ parts.push(`${esc.nonNull.length} non-null \`!\``);
523
+ const allLines = [...esc.asAny, ...esc.doubleAssertion, ...esc.nonNull].map(
524
+ l => l.lineStart
525
+ );
526
+ const firstLine = Math.min(...allLines);
527
+
528
+ if (!canAddFinding(findings)) break;
529
+ findings.push({
530
+ severity:
531
+ esc.asAny.length + esc.doubleAssertion.length > 3 ? 'high' : 'medium',
532
+ category: 'type-assertion-escape',
533
+ file: entry.file,
534
+ lineStart: firstLine,
535
+ lineEnd: firstLine,
536
+ title: `Type-safety escapes in ${entry.file} (${total})`,
537
+ reason: `Found ${parts.join(', ')}. Each assertion bypasses TypeScript's type checker.`,
538
+ files: [entry.file],
539
+ suggestedFix: {
540
+ strategy:
541
+ 'Replace type assertions with proper type guards or narrow types.',
542
+ steps: [
543
+ 'Replace `as any` with `unknown` and add runtime type checks.',
544
+ 'Replace `as unknown as T` with proper generic constraints.',
545
+ 'Replace `!` assertions with explicit null checks.',
546
+ ],
547
+ },
548
+ impact:
549
+ 'Type assertions silence the compiler — runtime errors go undetected.',
550
+ tags: ['type-safety', 'assertions', 'code-quality'],
551
+ });
552
+ }
553
+
554
+ return findings;
555
+ }
556
+
557
+ export function detectMissingErrorBoundary(
558
+ fileSummaries: FileEntry[]
559
+ ): FindingDraft[] {
560
+ const findings: FindingDraft[] = [];
561
+
562
+ for (const entry of fileSummaries) {
563
+ if (isTestFile(entry.file)) continue;
564
+ if (!entry.unprotectedAsync) continue;
565
+
566
+ for (const fn of entry.unprotectedAsync) {
567
+ const severity =
568
+ fn.awaitCount >= 4 ? 'high' : fn.awaitCount >= 2 ? 'medium' : 'low';
569
+ findings.push({
570
+ severity,
571
+ category: 'missing-error-boundary',
572
+ file: entry.file,
573
+ lineStart: fn.lineStart,
574
+ lineEnd: fn.lineEnd,
575
+ title: `Missing error boundary: ${fn.name} (${fn.awaitCount} awaits, no try-catch)`,
576
+ reason: `Async function "${fn.name}" has ${fn.awaitCount} await(s) but no try-catch. Rejected promises propagate as unhandled rejections.`,
577
+ files: [entry.file],
578
+ suggestedFix: {
579
+ strategy: 'Wrap await calls in try-catch or add a .catch() handler.',
580
+ steps: [
581
+ 'Add a try-catch block around the await expressions.',
582
+ 'Handle errors appropriately (log, return default, re-throw with context).',
583
+ 'If the caller handles errors, document it with a comment.',
584
+ ],
585
+ },
586
+ impact:
587
+ 'Unhandled promise rejections crash Node.js processes and cause silent failures in browsers.',
588
+ tags: ['error-handling', 'async', 'reliability'],
589
+ lspHints: [
590
+ {
591
+ tool: 'lspCallHierarchy',
592
+ symbolName: fn.name,
593
+ lineHint: fn.lineStart,
594
+ file: entry.file,
595
+ expectedResult: `check if callers wrap this in try-catch or .catch() — if so, the boundary may exist upstream`,
596
+ },
597
+ ],
598
+ });
599
+ }
600
+ }
601
+
602
+ return findings;
603
+ }
604
+
605
+ export function detectPromiseMisuse(
606
+ fileSummaries: FileEntry[]
607
+ ): FindingDraft[] {
608
+ const findings: FindingDraft[] = [];
609
+
610
+ for (const entry of fileSummaries) {
611
+ if (isTestFile(entry.file)) continue;
612
+ if (!entry.asyncWithoutAwait) continue;
613
+
614
+ for (const fn of entry.asyncWithoutAwait) {
615
+ findings.push({
616
+ severity: 'medium',
617
+ category: 'promise-misuse',
618
+ file: entry.file,
619
+ lineStart: fn.lineStart,
620
+ lineEnd: fn.lineEnd,
621
+ title: `Unnecessary async: ${fn.name} has no await`,
622
+ reason: `Function "${fn.name}" is declared \`async\` but never uses \`await\`. The \`async\` keyword adds unnecessary Promise wrapping.`,
623
+ files: [entry.file],
624
+ suggestedFix: {
625
+ strategy: 'Remove the async keyword or add the missing await.',
626
+ steps: [
627
+ 'If the function does not need to be async, remove the `async` keyword.',
628
+ 'If an `await` was forgotten, add it to the appropriate call.',
629
+ 'Verify callers handle the return value correctly after the change.',
630
+ ],
631
+ },
632
+ impact:
633
+ 'Unnecessary async wrapping adds microtask overhead and misleads readers.',
634
+ tags: ['async', 'performance', 'clarity'],
635
+ });
636
+ }
637
+ }
638
+
639
+ return findings;
640
+ }
641
+
642
+ export function detectAwaitInLoop(fileSummaries: FileEntry[]): FindingDraft[] {
643
+ const findings: FindingDraft[] = [];
644
+ for (const entry of fileSummaries) {
645
+ if (isTestFile(entry.file)) continue;
646
+ for (const loc of entry.awaitInLoopLocations || []) {
647
+ findings.push({
648
+ severity: 'high',
649
+ category: 'await-in-loop',
650
+ file: entry.file,
651
+ lineStart: loc.lineStart,
652
+ lineEnd: loc.lineEnd,
653
+ title: 'await inside loop — sequential async execution',
654
+ reason:
655
+ 'Each await runs serially. For N iterations this takes N * latency instead of max(latency). Use Promise.all() or Promise.allSettled() for parallel execution.',
656
+ files: [entry.file],
657
+ suggestedFix: {
658
+ strategy:
659
+ 'Collect promises and await them in parallel with Promise.all().',
660
+ steps: [
661
+ 'Collect all async operations into an array of promises.',
662
+ 'Use await Promise.all(promises) or Promise.allSettled(promises).',
663
+ 'If order matters or rate limiting is needed, use a batching utility.',
664
+ ],
665
+ },
666
+ impact:
667
+ 'Sequential awaits multiply latency by N iterations — parallelizing can reduce total time to max(single-latency).',
668
+ tags: ['performance', 'async', 'n-plus-one'],
669
+ lspHints: [
670
+ {
671
+ tool: 'lspGotoDefinition',
672
+ symbolName: 'await',
673
+ lineHint: loc.lineStart,
674
+ file: entry.file,
675
+ expectedResult: `navigate to the awaited call to check if parallelization is safe`,
676
+ },
677
+ ],
678
+ });
679
+ }
680
+ }
681
+ return findings;
682
+ }
683
+
684
+ export function detectSyncIo(fileSummaries: FileEntry[]): FindingDraft[] {
685
+ const findings: FindingDraft[] = [];
686
+ for (const entry of fileSummaries) {
687
+ if (isTestFile(entry.file)) continue;
688
+ for (const call of entry.syncIoCalls || []) {
689
+ findings.push({
690
+ severity: 'medium',
691
+ category: 'sync-io',
692
+ file: entry.file,
693
+ lineStart: call.lineStart,
694
+ lineEnd: call.lineEnd,
695
+ title: `Synchronous I/O: ${call.name}`,
696
+ reason: `${call.name} blocks the event loop. In server or UI code this degrades responsiveness for all concurrent operations.`,
697
+ files: [entry.file],
698
+ suggestedFix: {
699
+ strategy: 'Replace with async equivalent.',
700
+ steps: [
701
+ `Replace ${call.name} with its async counterpart (e.g. fs.promises.readFile).`,
702
+ 'Sync I/O is acceptable in CLI scripts, build tools, or one-time init code.',
703
+ ],
704
+ },
705
+ impact:
706
+ 'Synchronous I/O blocks the event loop, stalling all concurrent requests until the operation completes.',
707
+ tags: ['performance', 'blocking', 'io'],
708
+ lspHints: [
709
+ {
710
+ tool: 'lspCallHierarchy',
711
+ symbolName: call.name,
712
+ lineHint: call.lineStart,
713
+ file: entry.file,
714
+ expectedResult: `find callers to assess if this sync I/O is in a hot path`,
715
+ },
716
+ ],
717
+ });
718
+ }
719
+ }
720
+ return findings;
721
+ }
722
+
723
+ export function detectUnclearedTimers(
724
+ fileSummaries: FileEntry[]
725
+ ): FindingDraft[] {
726
+ const findings: FindingDraft[] = [];
727
+ for (const entry of fileSummaries) {
728
+ if (isTestFile(entry.file)) continue;
729
+ for (const timer of entry.timerCalls || []) {
730
+ if (timer.kind === 'setInterval' && !timer.hasCleanup) {
731
+ findings.push({
732
+ severity: 'medium',
733
+ category: 'uncleared-timer',
734
+ file: entry.file,
735
+ lineStart: timer.lineStart,
736
+ lineEnd: timer.lineEnd,
737
+ title: 'setInterval without clearInterval in scope',
738
+ reason:
739
+ 'setInterval without cleanup runs indefinitely, causing memory leaks and unexpected behavior after component unmount or scope exit.',
740
+ files: [entry.file],
741
+ suggestedFix: {
742
+ strategy: 'Store the timer ID and call clearInterval in cleanup.',
743
+ steps: [
744
+ 'Assign the return value: const id = setInterval(...).',
745
+ 'Call clearInterval(id) in cleanup (useEffect return, componentWillUnmount, or scope exit).',
746
+ ],
747
+ },
748
+ impact:
749
+ 'Uncleared intervals run indefinitely, leaking memory and CPU cycles after their scope is no longer relevant.',
750
+ tags: ['performance', 'memory-leak', 'timer'],
751
+ });
752
+ }
753
+ }
754
+ }
755
+ return findings;
756
+ }
757
+
758
+ export function detectListenerLeakRisk(
759
+ fileSummaries: FileEntry[]
760
+ ): FindingDraft[] {
761
+ const findings: FindingDraft[] = [];
762
+ for (const entry of fileSummaries) {
763
+ if (isTestFile(entry.file)) continue;
764
+ const regs = entry.listenerRegistrations || [];
765
+ const removals = entry.listenerRemovals || [];
766
+ if (regs.length > 0 && removals.length === 0) {
767
+ findings.push({
768
+ severity: 'medium',
769
+ category: 'listener-leak-risk',
770
+ file: entry.file,
771
+ lineStart: regs[0].lineStart,
772
+ lineEnd: regs[regs.length - 1].lineEnd,
773
+ title: `${regs.length} event listener(s) added without any removal`,
774
+ reason:
775
+ 'addEventListener/on without corresponding removeEventListener/off risks memory leaks if the target outlives the subscriber.',
776
+ files: [entry.file],
777
+ suggestedFix: {
778
+ strategy: 'Add corresponding listener removal in cleanup.',
779
+ steps: [
780
+ 'Store the handler reference in a variable.',
781
+ 'Call removeEventListener/off in cleanup (unmount, dispose, close).',
782
+ 'Or use AbortController signal for automatic cleanup.',
783
+ ],
784
+ },
785
+ impact:
786
+ 'Listener references prevent garbage collection of the subscriber, causing memory growth proportional to event-target lifetime.',
787
+ tags: ['performance', 'memory-leak', 'events'],
788
+ });
789
+ }
790
+ }
791
+ return findings;
792
+ }
793
+
794
+ export function detectUnboundedCollection(
795
+ fileSummaries: FileEntry[]
796
+ ): FindingDraft[] {
797
+ const findings: FindingDraft[] = [];
798
+ for (const entry of fileSummaries) {
799
+ if (isTestFile(entry.file)) continue;
800
+ for (const fn of entry.functions) {
801
+ if (fn.loops >= 2 && fn.calls >= 5 && fn.maxLoopDepth >= 2) {
802
+ findings.push({
803
+ severity: 'low',
804
+ category: 'unbounded-collection',
805
+ file: entry.file,
806
+ lineStart: fn.lineStart,
807
+ lineEnd: fn.lineEnd,
808
+ title: `Potential unbounded collection growth in ${fn.name}`,
809
+ reason: `Function "${fn.name}" has ${fn.loops} loops nested ${fn.maxLoopDepth} levels deep with ${fn.calls} calls — structural signal for unbounded growth. Validate with tools: read the function body and check for collection mutations (.push, .add, .set) inside loops.`,
810
+ files: [entry.file],
811
+ suggestedFix: {
812
+ strategy: 'Add size limits, pagination, or streaming.',
813
+ steps: [
814
+ 'Add a maximum size check before adding to collections.',
815
+ 'Use pagination or streaming for large datasets.',
816
+ 'Consider using generators for lazy evaluation.',
817
+ ],
818
+ },
819
+ impact:
820
+ 'Unbounded collection growth inside nested loops can cause out-of-memory crashes under large input.',
821
+ tags: ['performance', 'memory', 'collection'],
822
+ });
823
+ }
824
+ }
825
+ }
826
+ return findings;
827
+ }
828
+
829
+ export function detectSimilarFunctionBodies(
830
+ flowMap: Map<string, import('../types/index.js').FlowMapEntry[]>,
831
+ similarityThreshold: number = 0.85
832
+ ): FindingDraft[] {
833
+ const findings: FindingDraft[] = [];
834
+
835
+ const allEntries: import('../types/index.js').FlowMapEntry[] = [];
836
+ for (const entries of flowMap.values()) {
837
+ for (const e of entries) {
838
+ if (!isTestFile(e.file)) allEntries.push(e);
839
+ }
840
+ }
841
+
842
+ const buckets = new Map<string, import('../types/index.js').FlowMapEntry[]>();
843
+ for (const entry of allEntries) {
844
+ const key = `${entry.kind}|${Math.round(entry.statementCount / 3)}`;
845
+ if (!buckets.has(key)) buckets.set(key, []);
846
+ buckets.get(key)!.push(entry);
847
+ }
848
+
849
+ for (const [, bucket] of buckets) {
850
+ if (bucket.length < 2 || bucket.length > 50) continue;
851
+
852
+ for (let i = 0; i < bucket.length; i++) {
853
+ for (let j = i + 1; j < bucket.length; j++) {
854
+ const a = bucket[i];
855
+ const b = bucket[j];
856
+ if (a.hash === b.hash) continue;
857
+ if (a.file === b.file && a.lineStart === b.lineStart) continue;
858
+
859
+ const stmtRatio =
860
+ Math.min(a.statementCount, b.statementCount) /
861
+ Math.max(a.statementCount, b.statementCount);
862
+ if (stmtRatio < 0.8) continue;
863
+
864
+ const similarity = computeMetricSimilarity(a, b);
865
+ if (similarity >= similarityThreshold) {
866
+ findings.push({
867
+ severity: similarity >= 0.95 ? 'high' : 'medium',
868
+ category: 'similar-function-body',
869
+ file: a.file,
870
+ lineStart: a.lineStart,
871
+ lineEnd: a.lineEnd,
872
+ title: `Similar function: ${a.name} (${(similarity * 100).toFixed(0)}% similar to ${b.name} in ${b.file})`,
873
+ reason: `"${a.name}" and "${b.name}" have ${(similarity * 100).toFixed(0)}% structural similarity. Near-duplicates diverge over time and should be consolidated.`,
874
+ files: [a.file, b.file],
875
+ suggestedFix: {
876
+ strategy: 'Extract shared logic into a parameterized helper.',
877
+ steps: [
878
+ `Compare ${a.file}:${a.lineStart} with ${b.file}:${b.lineStart}.`,
879
+ 'Identify the varying parts and extract them as parameters.',
880
+ 'Create a shared function and call it from both locations.',
881
+ ],
882
+ },
883
+ impact:
884
+ 'Near-clone functions diverge over time, causing inconsistent behavior and multiplied maintenance cost.',
885
+ tags: ['duplication', 'maintainability', 'near-clone'],
886
+ });
887
+ }
888
+ }
889
+ }
890
+ }
891
+
892
+ return findings;
893
+ }
894
+
895
+ function computeMetricSimilarity(
896
+ a: import('../types/index.js').FlowMapEntry,
897
+ b: import('../types/index.js').FlowMapEntry
898
+ ): number {
899
+ const features = [
900
+ [a.metrics.complexity, b.metrics.complexity],
901
+ [a.metrics.maxBranchDepth, b.metrics.maxBranchDepth],
902
+ [a.metrics.maxLoopDepth, b.metrics.maxLoopDepth],
903
+ [a.metrics.returns, b.metrics.returns],
904
+ [a.metrics.awaits, b.metrics.awaits],
905
+ [a.metrics.calls, b.metrics.calls],
906
+ [a.metrics.loops, b.metrics.loops],
907
+ [a.statementCount, b.statementCount],
908
+ ];
909
+
910
+ let totalSimilarity = 0;
911
+ for (const [va, vb] of features) {
912
+ const max = Math.max(va, vb, 1);
913
+ totalSimilarity += 1 - Math.abs(va - vb) / max;
914
+ }
915
+ return totalSimilarity / features.length;
916
+ }
917
+
918
+ export function detectMessageChains(fileSummaries: FileEntry[]): FindingDraft[] {
919
+ const findings: FindingDraft[] = [];
920
+ for (const entry of fileSummaries) {
921
+ if (!entry.messageChains || entry.messageChains.length === 0) continue;
922
+ // Group by line start — take the deepest chain at each location
923
+ const byLine = new Map<number, typeof entry.messageChains[0]>();
924
+ for (const chain of entry.messageChains) {
925
+ const existing = byLine.get(chain.lineStart);
926
+ if (!existing || chain.depth > existing.depth) {
927
+ byLine.set(chain.lineStart, chain);
928
+ }
929
+ }
930
+ for (const chain of byLine.values()) {
931
+ const severity = chain.depth >= 6 ? 'high' : 'medium';
932
+ findings.push({
933
+ severity,
934
+ category: 'message-chain',
935
+ file: entry.file,
936
+ lineStart: chain.lineStart,
937
+ lineEnd: chain.lineEnd,
938
+ title: `Message chain of depth ${chain.depth}: ${chain.chain.slice(0, 50)}`,
939
+ reason: `A property-access chain of ${chain.depth} steps violates the Law of Demeter — the caller navigates through ${chain.depth - 1} intermediate objects to reach its target. Deep chains tightly couple the caller to internal object structure, making refactoring brittle.`,
940
+ files: [entry.file],
941
+ suggestedFix: {
942
+ strategy: 'Apply the Law of Demeter — talk only to immediate friends.',
943
+ steps: [
944
+ 'Identify the root object and the final method/property being used.',
945
+ 'Add a delegating method to the root object (Tell, Don\'t Ask).',
946
+ 'Replace the chain with a single call on the immediate object.',
947
+ 'If the chain crosses module boundaries, consider whether the intermediate objects should be passed directly.',
948
+ ],
949
+ },
950
+ impact:
951
+ 'Deep property chains tightly couple code to internal object structure. When intermediate objects change, every chain accessing them must be updated.',
952
+ tags: ['coupling', 'law-of-demeter', 'maintainability'],
953
+ lspHints: [
954
+ {
955
+ tool: 'lspGotoDefinition',
956
+ symbolName: chain.chain.split('.')[0],
957
+ lineHint: chain.lineStart,
958
+ file: entry.file,
959
+ expectedResult: `find the type of the root object to understand what intermediate types the chain traverses`,
960
+ },
961
+ ],
962
+ });
963
+ }
964
+ }
965
+ return findings;
966
+ }