octocode-cli 1.2.7 → 1.2.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (282) hide show
  1. package/README.md +42 -35
  2. package/out/octocode-cli.js +36 -11719
  3. package/package.json +36 -36
  4. package/skills/README.md +42 -114
  5. package/skills/{octocode-code-engineer → octocode-engineer}/.claude/settings.local.json +2 -1
  6. package/skills/octocode-engineer/README.md +99 -0
  7. package/skills/octocode-engineer/SKILL.md +499 -0
  8. package/skills/octocode-engineer/build.mjs +29 -0
  9. package/skills/{octocode-code-engineer → octocode-engineer}/eslint.config.mjs +3 -13
  10. package/skills/{octocode-code-engineer → octocode-engineer}/package.json +28 -27
  11. package/skills/octocode-engineer/references/ast-reference.md +166 -0
  12. package/skills/{octocode-code-engineer → octocode-engineer}/references/cli-reference.md +80 -6
  13. package/skills/octocode-engineer/references/externals.md +86 -0
  14. package/skills/{octocode-code-engineer → octocode-engineer}/references/output-files.md +46 -6
  15. package/skills/octocode-engineer/references/quality-indicators.md +202 -0
  16. package/skills/octocode-engineer/references/tool-workflows.md +298 -0
  17. package/skills/octocode-engineer/references/validation-playbooks.md +99 -0
  18. package/skills/octocode-engineer/scripts/ast/search.js +45 -0
  19. package/skills/octocode-engineer/scripts/ast/tree-search.js +27 -0
  20. package/skills/octocode-engineer/scripts/index.js +173 -0
  21. package/skills/octocode-engineer/scripts/run.js +179 -0
  22. package/skills/octocode-engineer/src/analysis/dependencies.ts +378 -0
  23. package/skills/{octocode-code-engineer → octocode-engineer}/src/analysis/discovery.test.ts +57 -0
  24. package/skills/{octocode-code-engineer → octocode-engineer}/src/analysis/discovery.ts +43 -0
  25. package/skills/{octocode-code-engineer → octocode-engineer}/src/ast/search.test.ts +113 -0
  26. package/skills/{octocode-code-engineer → octocode-engineer}/src/ast/search.ts +64 -1
  27. package/skills/{octocode-code-engineer → octocode-engineer}/src/ast/tree-sitter.test.ts +118 -2
  28. package/skills/{octocode-code-engineer → octocode-engineer}/src/ast/tree-sitter.ts +65 -3
  29. package/skills/{octocode-code-engineer → octocode-engineer}/src/ast/ts-analyzer.test.ts +281 -1
  30. package/skills/{octocode-code-engineer → octocode-engineer}/src/ast/ts-analyzer.ts +173 -3
  31. package/skills/{octocode-code-engineer → octocode-engineer}/src/collectors/security.test.ts +73 -0
  32. package/skills/{octocode-code-engineer → octocode-engineer}/src/collectors/security.ts +62 -4
  33. package/skills/octocode-engineer/src/detector-gating.test.ts +59 -0
  34. package/skills/{octocode-code-engineer → octocode-engineer}/src/detectors/code-quality.ts +342 -0
  35. package/skills/{octocode-code-engineer → octocode-engineer}/src/detectors/index.ts +8 -0
  36. package/skills/{octocode-code-engineer → octocode-engineer}/src/index.test.ts +565 -11
  37. package/skills/octocode-engineer/src/index.ts +468 -0
  38. package/skills/octocode-engineer/src/pipeline/affected.test.ts +147 -0
  39. package/skills/octocode-engineer/src/pipeline/affected.ts +68 -0
  40. package/skills/octocode-engineer/src/pipeline/baseline.test.ts +276 -0
  41. package/skills/octocode-engineer/src/pipeline/baseline.ts +76 -0
  42. package/skills/{octocode-code-engineer → octocode-engineer}/src/pipeline/cli.test.ts +300 -53
  43. package/skills/{octocode-code-engineer → octocode-engineer}/src/pipeline/cli.ts +180 -36
  44. package/skills/octocode-engineer/src/pipeline/config-loader.test.ts +264 -0
  45. package/skills/octocode-engineer/src/pipeline/config-loader.ts +109 -0
  46. package/skills/octocode-engineer/src/pipeline/create-options.ts +55 -0
  47. package/skills/octocode-engineer/src/pipeline/health-score.test.ts +65 -0
  48. package/skills/{octocode-code-engineer → octocode-engineer}/src/pipeline/main.ts +130 -17
  49. package/skills/octocode-engineer/src/pipeline/progress.ts +51 -0
  50. package/skills/octocode-engineer/src/pipeline/reporters.test.ts +155 -0
  51. package/skills/octocode-engineer/src/pipeline/reporters.ts +64 -0
  52. package/skills/octocode-engineer/src/reporting/graph-features.test.ts +279 -0
  53. package/skills/{octocode-code-engineer → octocode-engineer}/src/reporting/output-contract.test.ts +6 -0
  54. package/skills/octocode-engineer/src/reporting/summary-md.test.ts +1066 -0
  55. package/skills/octocode-engineer/src/reporting/summary-md.ts +1604 -0
  56. package/skills/{octocode-code-engineer → octocode-engineer}/src/reporting/writer.ts +136 -13
  57. package/skills/octocode-engineer/src/run.ts +78 -0
  58. package/skills/{octocode-code-engineer → octocode-engineer}/src/sanity.test.ts +1 -1
  59. package/skills/octocode-engineer/src/types/analysis.ts +25 -0
  60. package/skills/octocode-engineer/src/types/collectors.ts +134 -0
  61. package/skills/{octocode-code-engineer → octocode-engineer}/src/types/constants.ts +75 -41
  62. package/skills/octocode-engineer/src/types/core.ts +203 -0
  63. package/skills/octocode-engineer/src/types/dependency.ts +215 -0
  64. package/skills/octocode-engineer/src/types/file-entry.ts +108 -0
  65. package/skills/octocode-engineer/src/types/findings.ts +105 -0
  66. package/skills/{octocode-code-engineer → octocode-engineer}/src/types/index.ts +60 -30
  67. package/skills/octocode-engineer/src/types/tree-sitter.ts +38 -0
  68. package/skills/{octocode-code-engineer → octocode-engineer}/tsconfig.json +1 -0
  69. package/skills/octocode-research/.octocode/scan/.cache/analysis-cache.json +1 -0
  70. package/skills/octocode-research/.octocode/scan/2026-03-22T10-32-27-073Z/architecture.json +1 -0
  71. package/skills/octocode-research/.octocode/scan/2026-03-22T10-32-27-073Z/ast-trees.txt +5566 -0
  72. package/skills/octocode-research/.octocode/scan/2026-03-22T10-32-27-073Z/code-quality.json +1 -0
  73. package/skills/octocode-research/.octocode/scan/2026-03-22T10-32-27-073Z/dead-code.json +1 -0
  74. package/skills/octocode-research/.octocode/scan/2026-03-22T10-32-27-073Z/file-inventory.json +1 -0
  75. package/skills/octocode-research/.octocode/scan/2026-03-22T10-32-27-073Z/findings.json +1 -0
  76. package/skills/octocode-research/.octocode/scan/2026-03-22T10-32-27-073Z/graph.md +189 -0
  77. package/skills/octocode-research/.octocode/scan/2026-03-22T10-32-27-073Z/security.json +1 -0
  78. package/skills/octocode-research/.octocode/scan/2026-03-22T10-32-27-073Z/summary.json +1 -0
  79. package/skills/octocode-research/.octocode/scan/2026-03-22T10-32-27-073Z/summary.md +265 -0
  80. package/skills/octocode-research/.octocode/scan/2026-03-22T10-40-10-469Z/architecture.json +1 -0
  81. package/skills/octocode-research/.octocode/scan/2026-03-22T10-40-10-469Z/ast-trees.txt +5555 -0
  82. package/skills/octocode-research/.octocode/scan/2026-03-22T10-40-10-469Z/code-quality.json +1 -0
  83. package/skills/octocode-research/.octocode/scan/2026-03-22T10-40-10-469Z/dead-code.json +1 -0
  84. package/skills/octocode-research/.octocode/scan/2026-03-22T10-40-10-469Z/file-inventory.json +1 -0
  85. package/skills/octocode-research/.octocode/scan/2026-03-22T10-40-10-469Z/findings.json +1 -0
  86. package/skills/octocode-research/.octocode/scan/2026-03-22T10-40-10-469Z/graph.md +190 -0
  87. package/skills/octocode-research/.octocode/scan/2026-03-22T10-40-10-469Z/security.json +1 -0
  88. package/skills/octocode-research/.octocode/scan/2026-03-22T10-40-10-469Z/summary.json +1 -0
  89. package/skills/octocode-research/.octocode/scan/2026-03-22T10-40-10-469Z/summary.md +265 -0
  90. package/skills/octocode-research/CHANGELOG.md +60 -0
  91. package/skills/octocode-research/README.md +102 -388
  92. package/skills/octocode-research/SKILL.md +169 -498
  93. package/skills/octocode-research/package.json +19 -31
  94. package/skills/octocode-research/references/PARALLEL_AGENT_PROTOCOL.md +19 -0
  95. package/skills/octocode-research/references/SESSION_MANAGEMENT.md +38 -0
  96. package/skills/octocode-research/scripts/server-init.js +1 -1
  97. package/skills/octocode-research/scripts/server.d.ts +2 -1
  98. package/skills/octocode-research/scripts/server.js +329 -233
  99. package/skills/octocode-research/src/__tests__/integration/promptsRoutes.test.ts +180 -0
  100. package/skills/octocode-research/src/__tests__/integration/serverHttp.test.ts +221 -0
  101. package/skills/octocode-research/src/__tests__/integration/serverLifecycle.test.ts +194 -0
  102. package/skills/octocode-research/src/__tests__/integration/toolsRoutes.test.ts +501 -0
  103. package/skills/octocode-research/src/__tests__/unit/readiness.test.ts +61 -0
  104. package/skills/octocode-research/src/__tests__/unit/resilience.test.ts +192 -0
  105. package/skills/octocode-research/src/__tests__/unit/responseFactory.test.ts +172 -0
  106. package/skills/octocode-research/src/__tests__/unit/responseParser.test.ts +288 -0
  107. package/skills/octocode-research/src/__tests__/unit/schemas.test.ts +509 -0
  108. package/skills/octocode-research/src/index.ts +4 -124
  109. package/skills/octocode-research/src/middleware/queryParser.ts +0 -26
  110. package/skills/octocode-research/src/routes/lsp.ts +58 -59
  111. package/skills/octocode-research/src/routes/package.ts +35 -65
  112. package/skills/octocode-research/src/routes/prompts.ts +3 -3
  113. package/skills/octocode-research/src/routes/tools.ts +8 -20
  114. package/skills/octocode-research/src/server-init.ts +30 -237
  115. package/skills/octocode-research/src/server.ts +50 -23
  116. package/skills/octocode-research/src/types/errorGuards.ts +9 -80
  117. package/skills/octocode-research/src/types/guards.ts +0 -28
  118. package/skills/octocode-research/src/types/mcp.ts +11 -66
  119. package/skills/octocode-research/src/types/responses.ts +11 -129
  120. package/skills/octocode-research/src/utils/circuitBreaker.ts +0 -21
  121. package/skills/octocode-research/src/utils/logger.ts +1 -97
  122. package/skills/octocode-research/src/utils/resilience.ts +2 -12
  123. package/skills/octocode-research/src/utils/responseFactory.ts +0 -42
  124. package/skills/octocode-research/src/utils/responseParser.ts +3 -25
  125. package/skills/octocode-research/src/utils/retry.ts +0 -63
  126. package/skills/octocode-research/src/utils/routeFactory.ts +1 -1
  127. package/skills/octocode-research/src/validation/httpPreprocess.ts +0 -3
  128. package/skills/octocode-research/src/validation/index.ts +0 -1
  129. package/skills/octocode-research/src/validation/schemas.ts +0 -63
  130. package/skills/octocode-research/src/validation/toolCallSchema.ts +3 -3
  131. package/skills/octocode-research/tsdown.config.ts +4 -0
  132. package/skills/octocode-research/vitest.config.ts +3 -0
  133. package/skills/octocode-code-engineer/.plan/VALIDATED_PLAN.md +0 -223
  134. package/skills/octocode-code-engineer/README.md +0 -178
  135. package/skills/octocode-code-engineer/SKILL.md +0 -418
  136. package/skills/octocode-code-engineer/minify-scripts.mjs +0 -32
  137. package/skills/octocode-code-engineer/references/agent-ast-reading-rfc.md +0 -95
  138. package/skills/octocode-code-engineer/references/architecture-techniques.md +0 -121
  139. package/skills/octocode-code-engineer/references/ast-search.md +0 -210
  140. package/skills/octocode-code-engineer/references/ast-tree-search.md +0 -151
  141. package/skills/octocode-code-engineer/references/concepts.md +0 -107
  142. package/skills/octocode-code-engineer/references/finding-categories.md +0 -128
  143. package/skills/octocode-code-engineer/references/improvement-roadmap.md +0 -304
  144. package/skills/octocode-code-engineer/references/playbooks.md +0 -204
  145. package/skills/octocode-code-engineer/references/present-results.md +0 -136
  146. package/skills/octocode-code-engineer/references/tool-workflows.md +0 -566
  147. package/skills/octocode-code-engineer/references/validate-investigate.md +0 -225
  148. package/skills/octocode-code-engineer/scripts/analysis/dependencies.js +0 -1
  149. package/skills/octocode-code-engineer/scripts/analysis/dependency-summary.js +0 -1
  150. package/skills/octocode-code-engineer/scripts/analysis/discovery.js +0 -1
  151. package/skills/octocode-code-engineer/scripts/analysis/graph-analytics.js +0 -1
  152. package/skills/octocode-code-engineer/scripts/analysis/semantic.js +0 -1
  153. package/skills/octocode-code-engineer/scripts/ast/helpers.js +0 -1
  154. package/skills/octocode-code-engineer/scripts/ast/metrics.js +0 -1
  155. package/skills/octocode-code-engineer/scripts/ast/search.js +0 -2
  156. package/skills/octocode-code-engineer/scripts/ast/tree-search.js +0 -2
  157. package/skills/octocode-code-engineer/scripts/ast/tree-sitter.js +0 -1
  158. package/skills/octocode-code-engineer/scripts/ast/ts-analyzer.js +0 -1
  159. package/skills/octocode-code-engineer/scripts/collectors/chains.js +0 -1
  160. package/skills/octocode-code-engineer/scripts/collectors/effects.js +0 -1
  161. package/skills/octocode-code-engineer/scripts/collectors/input-sources.js +0 -1
  162. package/skills/octocode-code-engineer/scripts/collectors/performance.js +0 -1
  163. package/skills/octocode-code-engineer/scripts/collectors/prototype-pollution.js +0 -1
  164. package/skills/octocode-code-engineer/scripts/collectors/security.js +0 -1
  165. package/skills/octocode-code-engineer/scripts/collectors/test-profile.js +0 -1
  166. package/skills/octocode-code-engineer/scripts/common/is-direct-run.js +0 -1
  167. package/skills/octocode-code-engineer/scripts/common/utils.js +0 -1
  168. package/skills/octocode-code-engineer/scripts/detectors/code-quality.js +0 -1
  169. package/skills/octocode-code-engineer/scripts/detectors/cohesion.js +0 -1
  170. package/skills/octocode-code-engineer/scripts/detectors/coupling.js +0 -1
  171. package/skills/octocode-code-engineer/scripts/detectors/cycle.js +0 -1
  172. package/skills/octocode-code-engineer/scripts/detectors/dead-code.js +0 -1
  173. package/skills/octocode-code-engineer/scripts/detectors/import-style.js +0 -1
  174. package/skills/octocode-code-engineer/scripts/detectors/index.js +0 -1
  175. package/skills/octocode-code-engineer/scripts/detectors/security.js +0 -1
  176. package/skills/octocode-code-engineer/scripts/detectors/semantic.js +0 -1
  177. package/skills/octocode-code-engineer/scripts/detectors/shared.js +0 -1
  178. package/skills/octocode-code-engineer/scripts/detectors/test-quality.js +0 -1
  179. package/skills/octocode-code-engineer/scripts/index.js +0 -1
  180. package/skills/octocode-code-engineer/scripts/pipeline/cache.js +0 -1
  181. package/skills/octocode-code-engineer/scripts/pipeline/cli.js +0 -1
  182. package/skills/octocode-code-engineer/scripts/pipeline/main.js +0 -2
  183. package/skills/octocode-code-engineer/scripts/reporting/analysis.js +0 -1
  184. package/skills/octocode-code-engineer/scripts/reporting/summary-md.js +0 -1
  185. package/skills/octocode-code-engineer/scripts/reporting/writer.js +0 -1
  186. package/skills/octocode-code-engineer/scripts/types/constants.js +0 -1
  187. package/skills/octocode-code-engineer/scripts/types/index.js +0 -1
  188. package/skills/octocode-code-engineer/scripts/types/interfaces.js +0 -1
  189. package/skills/octocode-code-engineer/src/analysis/dependencies.ts +0 -406
  190. package/skills/octocode-code-engineer/src/index.ts +0 -403
  191. package/skills/octocode-code-engineer/src/reporting/summary-md.test.ts +0 -421
  192. package/skills/octocode-code-engineer/src/reporting/summary-md.ts +0 -714
  193. package/skills/octocode-code-engineer/src/types/interfaces.ts +0 -682
  194. package/skills/octocode-research/src/types/toolTypes.ts +0 -33
  195. package/skills/octocode-research/src/utils/logEmoji.ts +0 -103
  196. /package/skills/{octocode-code-engineer → octocode-engineer}/.octocode/rfc/RFC-code-engineer-weakness-fixes.md +0 -0
  197. /package/skills/{octocode-code-engineer → octocode-engineer}/coverage/architecture.ts.html +0 -0
  198. /package/skills/{octocode-code-engineer → octocode-engineer}/coverage/ast-helpers.ts.html +0 -0
  199. /package/skills/{octocode-code-engineer → octocode-engineer}/coverage/ast-search.ts.html +0 -0
  200. /package/skills/{octocode-code-engineer → octocode-engineer}/coverage/base.css +0 -0
  201. /package/skills/{octocode-code-engineer → octocode-engineer}/coverage/block-navigation.js +0 -0
  202. /package/skills/{octocode-code-engineer → octocode-engineer}/coverage/cache.ts.html +0 -0
  203. /package/skills/{octocode-code-engineer → octocode-engineer}/coverage/cli.ts.html +0 -0
  204. /package/skills/{octocode-code-engineer → octocode-engineer}/coverage/clover.xml +0 -0
  205. /package/skills/{octocode-code-engineer → octocode-engineer}/coverage/collect-effects.ts.html +0 -0
  206. /package/skills/{octocode-code-engineer → octocode-engineer}/coverage/collect-input-sources.ts.html +0 -0
  207. /package/skills/{octocode-code-engineer → octocode-engineer}/coverage/collect-performance.ts.html +0 -0
  208. /package/skills/{octocode-code-engineer → octocode-engineer}/coverage/collect-prototype-pollution.ts.html +0 -0
  209. /package/skills/{octocode-code-engineer → octocode-engineer}/coverage/collect-security.ts.html +0 -0
  210. /package/skills/{octocode-code-engineer → octocode-engineer}/coverage/collect-test-profile.ts.html +0 -0
  211. /package/skills/{octocode-code-engineer → octocode-engineer}/coverage/coverage-final.json +0 -0
  212. /package/skills/{octocode-code-engineer → octocode-engineer}/coverage/dependencies.ts.html +0 -0
  213. /package/skills/{octocode-code-engineer → octocode-engineer}/coverage/dependency-summary.ts.html +0 -0
  214. /package/skills/{octocode-code-engineer → octocode-engineer}/coverage/discovery.ts.html +0 -0
  215. /package/skills/{octocode-code-engineer → octocode-engineer}/coverage/favicon.png +0 -0
  216. /package/skills/{octocode-code-engineer → octocode-engineer}/coverage/graph-analytics.ts.html +0 -0
  217. /package/skills/{octocode-code-engineer → octocode-engineer}/coverage/index.html +0 -0
  218. /package/skills/{octocode-code-engineer → octocode-engineer}/coverage/index.ts.html +0 -0
  219. /package/skills/{octocode-code-engineer → octocode-engineer}/coverage/metrics.ts.html +0 -0
  220. /package/skills/{octocode-code-engineer → octocode-engineer}/coverage/pipeline.ts.html +0 -0
  221. /package/skills/{octocode-code-engineer → octocode-engineer}/coverage/prettify.css +0 -0
  222. /package/skills/{octocode-code-engineer → octocode-engineer}/coverage/prettify.js +0 -0
  223. /package/skills/{octocode-code-engineer → octocode-engineer}/coverage/report-analysis.ts.html +0 -0
  224. /package/skills/{octocode-code-engineer → octocode-engineer}/coverage/report-writer.ts.html +0 -0
  225. /package/skills/{octocode-code-engineer → octocode-engineer}/coverage/security-detectors.ts.html +0 -0
  226. /package/skills/{octocode-code-engineer → octocode-engineer}/coverage/semantic-detectors.ts.html +0 -0
  227. /package/skills/{octocode-code-engineer → octocode-engineer}/coverage/semantic.ts.html +0 -0
  228. /package/skills/{octocode-code-engineer → octocode-engineer}/coverage/sort-arrow-sprite.png +0 -0
  229. /package/skills/{octocode-code-engineer → octocode-engineer}/coverage/sorter.js +0 -0
  230. /package/skills/{octocode-code-engineer → octocode-engineer}/coverage/summary-md.ts.html +0 -0
  231. /package/skills/{octocode-code-engineer → octocode-engineer}/coverage/test-quality-detectors.ts.html +0 -0
  232. /package/skills/{octocode-code-engineer → octocode-engineer}/coverage/tree-sitter-analyzer.ts.html +0 -0
  233. /package/skills/{octocode-code-engineer → octocode-engineer}/coverage/ts-analyzer.ts.html +0 -0
  234. /package/skills/{octocode-code-engineer → octocode-engineer}/coverage/types.ts.html +0 -0
  235. /package/skills/{octocode-code-engineer → octocode-engineer}/coverage/utils.ts.html +0 -0
  236. /package/skills/{octocode-code-engineer → octocode-engineer}/src/analysis/dependencies.test.ts +0 -0
  237. /package/skills/{octocode-code-engineer → octocode-engineer}/src/analysis/dependency-summary.test.ts +0 -0
  238. /package/skills/{octocode-code-engineer → octocode-engineer}/src/analysis/dependency-summary.ts +0 -0
  239. /package/skills/{octocode-code-engineer → octocode-engineer}/src/analysis/graph-analytics.test.ts +0 -0
  240. /package/skills/{octocode-code-engineer → octocode-engineer}/src/analysis/graph-analytics.ts +0 -0
  241. /package/skills/{octocode-code-engineer → octocode-engineer}/src/analysis/semantic.test.ts +0 -0
  242. /package/skills/{octocode-code-engineer → octocode-engineer}/src/analysis/semantic.ts +0 -0
  243. /package/skills/{octocode-code-engineer → octocode-engineer}/src/ast/helpers.test.ts +0 -0
  244. /package/skills/{octocode-code-engineer → octocode-engineer}/src/ast/helpers.ts +0 -0
  245. /package/skills/{octocode-code-engineer → octocode-engineer}/src/ast/metrics.test.ts +0 -0
  246. /package/skills/{octocode-code-engineer → octocode-engineer}/src/ast/metrics.ts +0 -0
  247. /package/skills/{octocode-code-engineer → octocode-engineer}/src/ast/tree-search.test.ts +0 -0
  248. /package/skills/{octocode-code-engineer → octocode-engineer}/src/ast/tree-search.ts +0 -0
  249. /package/skills/{octocode-code-engineer → octocode-engineer}/src/collectors/chains.ts +0 -0
  250. /package/skills/{octocode-code-engineer → octocode-engineer}/src/collectors/effects.test.ts +0 -0
  251. /package/skills/{octocode-code-engineer → octocode-engineer}/src/collectors/effects.ts +0 -0
  252. /package/skills/{octocode-code-engineer → octocode-engineer}/src/collectors/input-sources.test.ts +0 -0
  253. /package/skills/{octocode-code-engineer → octocode-engineer}/src/collectors/input-sources.ts +0 -0
  254. /package/skills/{octocode-code-engineer → octocode-engineer}/src/collectors/performance.test.ts +0 -0
  255. /package/skills/{octocode-code-engineer → octocode-engineer}/src/collectors/performance.ts +0 -0
  256. /package/skills/{octocode-code-engineer → octocode-engineer}/src/collectors/prototype-pollution.test.ts +0 -0
  257. /package/skills/{octocode-code-engineer → octocode-engineer}/src/collectors/prototype-pollution.ts +0 -0
  258. /package/skills/{octocode-code-engineer → octocode-engineer}/src/collectors/test-profile.test.ts +0 -0
  259. /package/skills/{octocode-code-engineer → octocode-engineer}/src/collectors/test-profile.ts +0 -0
  260. /package/skills/{octocode-code-engineer → octocode-engineer}/src/common/is-direct-run.test.ts +0 -0
  261. /package/skills/{octocode-code-engineer → octocode-engineer}/src/common/is-direct-run.ts +0 -0
  262. /package/skills/{octocode-code-engineer → octocode-engineer}/src/common/utils.test.ts +0 -0
  263. /package/skills/{octocode-code-engineer → octocode-engineer}/src/common/utils.ts +0 -0
  264. /package/skills/{octocode-code-engineer → octocode-engineer}/src/detectors/cohesion.ts +0 -0
  265. /package/skills/{octocode-code-engineer → octocode-engineer}/src/detectors/coupling.ts +0 -0
  266. /package/skills/{octocode-code-engineer → octocode-engineer}/src/detectors/cycle.ts +0 -0
  267. /package/skills/{octocode-code-engineer → octocode-engineer}/src/detectors/dead-code.ts +0 -0
  268. /package/skills/{octocode-code-engineer → octocode-engineer}/src/detectors/import-style.ts +0 -0
  269. /package/skills/{octocode-code-engineer → octocode-engineer}/src/detectors/index.test.ts +0 -0
  270. /package/skills/{octocode-code-engineer → octocode-engineer}/src/detectors/security.test.ts +0 -0
  271. /package/skills/{octocode-code-engineer → octocode-engineer}/src/detectors/security.ts +0 -0
  272. /package/skills/{octocode-code-engineer → octocode-engineer}/src/detectors/semantic.ts +0 -0
  273. /package/skills/{octocode-code-engineer → octocode-engineer}/src/detectors/shared.ts +0 -0
  274. /package/skills/{octocode-code-engineer → octocode-engineer}/src/detectors/test-quality.test.ts +0 -0
  275. /package/skills/{octocode-code-engineer → octocode-engineer}/src/detectors/test-quality.ts +0 -0
  276. /package/skills/{octocode-code-engineer → octocode-engineer}/src/pipeline/cache.test.ts +0 -0
  277. /package/skills/{octocode-code-engineer → octocode-engineer}/src/pipeline/cache.ts +0 -0
  278. /package/skills/{octocode-code-engineer → octocode-engineer}/src/pipeline/main.test.ts +0 -0
  279. /package/skills/{octocode-code-engineer → octocode-engineer}/src/pipeline.test.ts +0 -0
  280. /package/skills/{octocode-code-engineer → octocode-engineer}/src/reporting/analysis.test.ts +0 -0
  281. /package/skills/{octocode-code-engineer → octocode-engineer}/src/reporting/analysis.ts +0 -0
  282. /package/skills/{octocode-code-engineer → octocode-engineer}/vitest.config.ts +0 -0
@@ -0,0 +1,468 @@
1
+ import {
2
+ buildConsumedFromModule,
3
+ computeHotFiles,
4
+ detectAwaitInLoop,
5
+ detectBarrelExplosion,
6
+ detectBooleanParameterCluster,
7
+ detectBoundaryViolations,
8
+ detectCatchRethrow,
9
+ detectChangeRisk,
10
+ detectCognitiveComplexity,
11
+ detectCommonJsInEsm,
12
+ detectCriticalPaths,
13
+ detectDeadExports,
14
+ detectDeadFiles,
15
+ detectDeadReExports,
16
+ detectDeepNesting,
17
+ detectDependencyCycles,
18
+ detectDistanceFromMainSequence,
19
+ detectDuplicateFlowStructures,
20
+ detectDuplicateFunctionBodies,
21
+ detectEmptyCatchBlocks,
22
+ detectExcessiveParameters,
23
+ detectExportStarLeak,
24
+ detectExportSurfaceDensity,
25
+ detectFeatureEnvy,
26
+ detectFunctionOptimization,
27
+ detectGodFunctions,
28
+ detectGodModuleCoupling,
29
+ detectGodModules,
30
+ detectHighCoupling,
31
+ detectHighHalsteadEffort,
32
+ detectImportSideEffectRisk,
33
+ detectLayerViolations,
34
+ detectListenerLeakRisk,
35
+ detectLowCohesion,
36
+ detectLowMaintainability,
37
+ detectMagicStrings,
38
+ detectMegaFolders,
39
+ detectMessageChains,
40
+ detectMissingErrorBoundary,
41
+ detectMultipleReturnPaths,
42
+ detectNamespaceImport,
43
+ detectOrphanModules,
44
+ detectPromiseAllUnhandled,
45
+ detectPromiseMisuse,
46
+ detectSdpViolations,
47
+ detectSimilarFunctionBodies,
48
+ detectSwitchNoDefault,
49
+ detectSyncIo,
50
+ detectTestOnlyModules,
51
+ detectTypeAssertionEscape,
52
+ detectUnboundedCollection,
53
+ detectUnclearedTimers,
54
+ detectUnreachableModules,
55
+ detectUnsafeAny,
56
+ detectUntestedCriticalCode,
57
+ detectUnusedNpmDeps,
58
+ } from './detectors/index.js';
59
+ import {
60
+ detectCommandInjectionRisk,
61
+ detectDebugLogLeakage,
62
+ detectEvalUsage,
63
+ detectHardcodedSecrets,
64
+ detectInputPassthroughRisk,
65
+ detectPathTraversalRisk,
66
+ detectPrototypePollutionRisk,
67
+ detectSensitiveDataLogging,
68
+ detectSqlInjectionRisk,
69
+ detectUnsafeHtml,
70
+ detectUnsafeRegex,
71
+ detectUnvalidatedInputSink,
72
+ } from './detectors/security.js';
73
+ import {
74
+ detectExcessiveMocking,
75
+ detectFakeTimersWithoutRestore,
76
+ detectFocusedTests,
77
+ detectLowAssertionDensity,
78
+ detectMissingMockRestoration,
79
+ detectMissingTestCleanup,
80
+ detectSharedMutableState,
81
+ detectTestNoAssertion,
82
+ } from './detectors/test-quality.js';
83
+ import { diversifyFindings } from './reporting/summary-md.js';
84
+ import { PILLAR_CATEGORIES, SEVERITY_ORDER } from './types/index.js';
85
+
86
+ import type {
87
+ AnalysisOptions,
88
+ DependencyState,
89
+ DependencySummary,
90
+ DuplicateGroup,
91
+ FileCriticality,
92
+ FileEntry,
93
+ Finding,
94
+ FlowMapEntry,
95
+ RedundantFlowGroup,
96
+ } from './types/index.js';
97
+
98
+ export { bus } from './pipeline/progress.js';
99
+ export type { ProgressPhase, ProgressEvent } from './pipeline/progress.js';
100
+ export { createOptions, OptionsError } from './pipeline/create-options.js';
101
+ export { HELP_TEXT } from './pipeline/cli.js';
102
+ export { EXIT_SUCCESS, EXIT_FINDINGS, EXIT_ERROR, computeGateScore } from './pipeline/main.js';
103
+ export { resolveAffectedFiles } from './pipeline/affected.js';
104
+ export { saveBaseline, filterKnownFindings } from './pipeline/baseline.js';
105
+ export { formatFindings } from './pipeline/reporters.js';
106
+ export { loadConfigFile, mergeConfigIntoDefaults } from './pipeline/config-loader.js';
107
+
108
+ type DependencyStateArg = DependencyState | undefined;
109
+
110
+ export {
111
+ buildDependencySummary,
112
+ computeDependencyCycles,
113
+ computeDependencyCriticalPaths,
114
+ } from './analysis/dependency-summary.js';
115
+ export {
116
+ REPORT_SCHEMA_VERSION,
117
+ ARCHITECTURE_CATEGORIES,
118
+ CODE_QUALITY_CATEGORIES,
119
+ DEAD_CODE_CATEGORIES,
120
+ SECURITY_CATEGORIES,
121
+ TEST_QUALITY_CATEGORIES,
122
+ writeMultiFileReport,
123
+ generateMermaidGraph,
124
+ } from './reporting/writer.js';
125
+ export type { FullReport } from './reporting/writer.js';
126
+ export {
127
+ severityBreakdown,
128
+ categoryBreakdown,
129
+ computeHealthScore,
130
+ computeFeatureScores,
131
+ computeQualityAspectRatings,
132
+ collectTagCloud,
133
+ formatFileSize,
134
+ diversifyFindings,
135
+ diverseTopRecommendations,
136
+ generateSummaryMd,
137
+ } from './reporting/summary-md.js';
138
+ export type {
139
+ SummaryMdOptions,
140
+ QualityAspectRating,
141
+ QualityRatingSummary,
142
+ } from './reporting/summary-md.js';
143
+
144
+ type FindingDraft = Omit<Finding, 'id'>;
145
+ type DetectorFn = () => Iterable<FindingDraft>;
146
+
147
+ interface EnabledPillars {
148
+ architecture: boolean;
149
+ codeQuality: boolean;
150
+ deadCode: boolean;
151
+ security: boolean;
152
+ testQuality: boolean;
153
+ }
154
+
155
+ function hasEnabledCategory(
156
+ features: Set<string>,
157
+ categories: string[]
158
+ ): boolean {
159
+ return categories.some(category => features.has(category));
160
+ }
161
+
162
+ export function resolveEnabledPillars(
163
+ features: Set<string> | null
164
+ ): EnabledPillars {
165
+ if (!features) {
166
+ return {
167
+ architecture: true,
168
+ codeQuality: true,
169
+ deadCode: true,
170
+ security: true,
171
+ testQuality: true,
172
+ };
173
+ }
174
+ return {
175
+ architecture: hasEnabledCategory(features, PILLAR_CATEGORIES['architecture']),
176
+ codeQuality: hasEnabledCategory(features, PILLAR_CATEGORIES['code-quality']),
177
+ deadCode: hasEnabledCategory(features, PILLAR_CATEGORIES['dead-code']),
178
+ security: hasEnabledCategory(features, PILLAR_CATEGORIES['security']),
179
+ testQuality: hasEnabledCategory(features, PILLAR_CATEGORIES['test-quality']),
180
+ };
181
+ }
182
+
183
+ function collectArchitectureFindings(
184
+ dependencySummary: DependencySummary,
185
+ dependencyState: DependencyState,
186
+ fileSummaries: FileEntry[],
187
+ options: AnalysisOptions,
188
+ fileCriticalityByPath: Map<string, FileCriticality>,
189
+ consumedFromModule: Map<string, Set<string>>,
190
+ testConsumedFromModule: Map<string, Set<string>>,
191
+ pkgJsonDeps: Record<string, string>,
192
+ pkgJsonDevDeps: Record<string, string>
193
+ ): DetectorFn[] {
194
+ const hotFiles = computeHotFiles(dependencyState, dependencySummary, fileCriticalityByPath);
195
+ const detectors: DetectorFn[] = [
196
+ () => detectTestOnlyModules(dependencySummary),
197
+ () => detectDependencyCycles(dependencySummary, dependencyState),
198
+ () => detectCriticalPaths(dependencySummary, dependencyState, options.thresholds.criticalComplexityThreshold),
199
+ () => detectDeadFiles(dependencySummary, dependencyState),
200
+ () => detectDeadExports(dependencyState, consumedFromModule, testConsumedFromModule),
201
+ () => detectDeadReExports(dependencyState, consumedFromModule),
202
+ () => detectSdpViolations(dependencyState, options.thresholds.sdpMinDelta, options.thresholds.sdpMaxSourceInstability),
203
+ () => detectHighCoupling(dependencyState, options.thresholds.couplingThreshold),
204
+ () => detectGodModuleCoupling(dependencyState, options.thresholds.fanInThreshold, options.thresholds.fanOutThreshold),
205
+ () => detectOrphanModules(dependencyState),
206
+ () => detectUnreachableModules(dependencyState),
207
+ () => detectUnusedNpmDeps(dependencyState.externalCounts, pkgJsonDeps, pkgJsonDevDeps),
208
+ () => detectBoundaryViolations(dependencyState),
209
+ () => detectBarrelExplosion(dependencyState, options.thresholds.barrelSymbolThreshold),
210
+ () => detectMegaFolders(fileSummaries),
211
+ () => detectLowCohesion(dependencyState),
212
+ () => detectDistanceFromMainSequence(dependencyState),
213
+ () => detectFeatureEnvy(dependencyState),
214
+ () => detectUntestedCriticalCode(dependencyState, hotFiles, fileCriticalityByPath),
215
+ () => detectImportSideEffectRisk(fileSummaries, dependencyState, dependencySummary, hotFiles),
216
+ () => detectNamespaceImport(dependencyState),
217
+ () => detectCommonJsInEsm(dependencyState),
218
+ () => detectExportStarLeak(dependencyState),
219
+ ];
220
+ if (options.thresholds.layerOrder.length >= 2) {
221
+ detectors.push(() => detectLayerViolations(dependencyState, options.thresholds.layerOrder));
222
+ }
223
+ return detectors;
224
+ }
225
+
226
+ function collectCodeQualityFindings(
227
+ duplicates: DuplicateGroup[],
228
+ controlDuplicates: RedundantFlowGroup[],
229
+ fileSummaries: FileEntry[],
230
+ options: AnalysisOptions,
231
+ flowMap: Map<string, FlowMapEntry[]>,
232
+ dependencyState?: DependencyStateArg
233
+ ): DetectorFn[] {
234
+ return [
235
+ () => detectDuplicateFunctionBodies(duplicates),
236
+ () => detectDuplicateFlowStructures(controlDuplicates, options.thresholds.flowDupThreshold),
237
+ () => detectFunctionOptimization(fileSummaries, options.thresholds.criticalComplexityThreshold),
238
+ () => detectGodFunctions(fileSummaries, options.thresholds.godFunctionStatements, options.thresholds.godFunctionMiThreshold),
239
+ () => detectCognitiveComplexity(fileSummaries, options.thresholds.cognitiveComplexityThreshold),
240
+ () => detectExcessiveParameters(fileSummaries, options.thresholds.parameterThreshold),
241
+ () => detectEmptyCatchBlocks(fileSummaries),
242
+ () => detectSwitchNoDefault(fileSummaries),
243
+ () => detectUnsafeAny(fileSummaries, options.thresholds.anyThreshold),
244
+ () => detectHighHalsteadEffort(fileSummaries, options.thresholds.halsteadEffortThreshold),
245
+ () => detectLowMaintainability(fileSummaries, options.thresholds.maintainabilityIndexThreshold),
246
+ () => detectTypeAssertionEscape(fileSummaries),
247
+ () => detectMissingErrorBoundary(fileSummaries),
248
+ () => detectPromiseMisuse(fileSummaries),
249
+ () => detectAwaitInLoop(fileSummaries),
250
+ () => detectSyncIo(fileSummaries),
251
+ () => detectUnclearedTimers(fileSummaries),
252
+ () => detectListenerLeakRisk(fileSummaries),
253
+ () => detectUnboundedCollection(fileSummaries),
254
+ () => detectMessageChains(fileSummaries),
255
+ () => detectSimilarFunctionBodies(flowMap, options.thresholds.similarityThreshold),
256
+ () => detectDeepNesting(fileSummaries, options.thresholds.deepNestingThreshold),
257
+ () => detectMultipleReturnPaths(fileSummaries, options.thresholds.multipleReturnThreshold),
258
+ () => detectCatchRethrow(fileSummaries),
259
+ () => detectMagicStrings(fileSummaries, options.thresholds.magicStringMinOccurrences),
260
+ () => detectBooleanParameterCluster(fileSummaries, options.thresholds.booleanParamThreshold),
261
+ () => detectPromiseAllUnhandled(fileSummaries),
262
+ () => detectExportSurfaceDensity(fileSummaries, dependencyState),
263
+ () => detectChangeRisk(fileSummaries, flowMap, dependencyState),
264
+ ...(dependencyState
265
+ ? [() => detectGodModules(fileSummaries, dependencyState, options.thresholds.godModuleStatements, options.thresholds.godModuleExports)]
266
+ : []),
267
+ ];
268
+ }
269
+
270
+ function collectSecurityFindings(fileSummaries: FileEntry[]): DetectorFn[] {
271
+ return [
272
+ () => detectHardcodedSecrets(fileSummaries),
273
+ () => detectEvalUsage(fileSummaries),
274
+ () => detectUnsafeHtml(fileSummaries),
275
+ () => detectSqlInjectionRisk(fileSummaries),
276
+ () => detectUnsafeRegex(fileSummaries),
277
+ () => detectUnvalidatedInputSink(fileSummaries),
278
+ () => detectInputPassthroughRisk(fileSummaries),
279
+ () => detectPrototypePollutionRisk(fileSummaries),
280
+ () => detectPathTraversalRisk(fileSummaries),
281
+ () => detectCommandInjectionRisk(fileSummaries),
282
+ () => detectDebugLogLeakage(fileSummaries),
283
+ () => detectSensitiveDataLogging(fileSummaries),
284
+ ];
285
+ }
286
+
287
+ function collectTestQualityFindings(
288
+ fileSummaries: FileEntry[],
289
+ options: AnalysisOptions
290
+ ): DetectorFn[] {
291
+ return [
292
+ () => detectLowAssertionDensity(fileSummaries),
293
+ () => detectTestNoAssertion(fileSummaries),
294
+ () => detectExcessiveMocking(fileSummaries, options.thresholds.mockThreshold),
295
+ () => detectSharedMutableState(fileSummaries),
296
+ () => detectMissingTestCleanup(fileSummaries),
297
+ () => detectFocusedTests(fileSummaries),
298
+ () => detectFakeTimersWithoutRestore(fileSummaries),
299
+ () => detectMissingMockRestoration(fileSummaries),
300
+ ];
301
+ }
302
+
303
+ export function buildIssueCatalog(
304
+ duplicates: DuplicateGroup[],
305
+ controlDuplicates: RedundantFlowGroup[],
306
+ fileSummaries: FileEntry[],
307
+ dependencySummary: DependencySummary,
308
+ dependencyState: DependencyState,
309
+ options: AnalysisOptions,
310
+ pkgJsonDeps: Record<string, string> = {},
311
+ pkgJsonDevDeps: Record<string, string> = {},
312
+ fileCriticalityByPath: Map<string, FileCriticality> = new Map(),
313
+ semanticFindings: Array<FindingDraft> = [],
314
+ flowMap: Map<string, FlowMapEntry[]> = new Map(),
315
+ additionalFindings: Array<FindingDraft> = []
316
+ ): {
317
+ allFindings: Array<FindingDraft>;
318
+ findings: Finding[];
319
+ byFile: Map<string, string[]>;
320
+ totalBeforeTruncation: number;
321
+ droppedCategories: string[];
322
+ } {
323
+ const rawFindings: Array<FindingDraft> = [];
324
+
325
+ const addFinding = (finding: FindingDraft): void => {
326
+ if (options.features && !options.features.has(finding.category)) return;
327
+ rawFindings.push(finding);
328
+ };
329
+
330
+ const { production: consumedFromModule, test: testConsumedFromModule } =
331
+ buildConsumedFromModule(dependencyState);
332
+ const enabledPillars = resolveEnabledPillars(options.features);
333
+
334
+ const detectors: DetectorFn[] = [
335
+ ...(enabledPillars.architecture || enabledPillars.deadCode
336
+ ? collectArchitectureFindings(
337
+ dependencySummary, dependencyState, fileSummaries, options,
338
+ fileCriticalityByPath, consumedFromModule, testConsumedFromModule,
339
+ pkgJsonDeps, pkgJsonDevDeps
340
+ )
341
+ : []),
342
+ ...(enabledPillars.codeQuality
343
+ ? collectCodeQualityFindings(
344
+ duplicates,
345
+ controlDuplicates,
346
+ fileSummaries,
347
+ options,
348
+ flowMap,
349
+ dependencyState
350
+ )
351
+ : []),
352
+ ...(enabledPillars.security ? collectSecurityFindings(fileSummaries) : []),
353
+ ...(enabledPillars.testQuality
354
+ ? collectTestQualityFindings(fileSummaries, options)
355
+ : []),
356
+ ];
357
+
358
+ for (const detect of detectors) {
359
+ for (const f of detect()) addFinding(f);
360
+ }
361
+ for (const f of semanticFindings) addFinding(f);
362
+ for (const f of additionalFindings) addFinding(f);
363
+
364
+ const sorted = rawFindings.sort((a, b) => {
365
+ const bySeverity = SEVERITY_ORDER[b.severity] - SEVERITY_ORDER[a.severity];
366
+ if (bySeverity !== 0) return bySeverity;
367
+ if (a.category < b.category) return -1;
368
+ if (a.category > b.category) return 1;
369
+ return 0;
370
+ });
371
+
372
+ const { findings: truncated, totalBeforeTruncation, droppedCategories } =
373
+ applyFindingsLimit(sorted, options);
374
+ const { findings, byFile } = assignFindingIds(truncated);
375
+
376
+ return {
377
+ allFindings: sorted,
378
+ findings,
379
+ byFile,
380
+ totalBeforeTruncation,
381
+ droppedCategories,
382
+ };
383
+ }
384
+
385
+ export function applyFindingsLimit<T extends Omit<Finding, 'id'>>(
386
+ sorted: T[],
387
+ options: Pick<AnalysisOptions, 'findingsLimit' | 'noDiversify'>
388
+ ): {
389
+ findings: T[];
390
+ totalBeforeTruncation: number;
391
+ droppedCategories: string[];
392
+ } {
393
+ const totalBeforeTruncation = sorted.length;
394
+ const allCategoriesBefore = new Set(sorted.map(f => f.category));
395
+ const limit = options.findingsLimit;
396
+ const truncated =
397
+ !Number.isFinite(limit) || limit == null
398
+ ? sorted
399
+ : options.noDiversify
400
+ ? sorted.slice(0, limit)
401
+ : diversifyFindings(sorted, limit);
402
+ const categoriesAfter = new Set(truncated.map(f => f.category));
403
+ const droppedCategories = [...allCategoriesBefore].filter(
404
+ c => !categoriesAfter.has(c)
405
+ );
406
+
407
+ return {
408
+ findings: truncated,
409
+ totalBeforeTruncation,
410
+ droppedCategories,
411
+ };
412
+ }
413
+
414
+ export function assignFindingIds(
415
+ rawFindings: Array<Omit<Finding, 'id'>>
416
+ ): {
417
+ findings: Finding[];
418
+ byFile: Map<string, string[]>;
419
+ } {
420
+ const findings: Finding[] = [];
421
+ const byFile = new Map<string, string[]>();
422
+
423
+ for (const [i, raw] of rawFindings.entries()) {
424
+ const id = `AST-ISSUE-${String(i + 1).padStart(4, '0')}`;
425
+ const full: Finding = { id, ...raw };
426
+ findings.push(full);
427
+ if (full.file) {
428
+ if (!byFile.has(full.file)) byFile.set(full.file, []);
429
+ byFile.get(full.file)!.push(id);
430
+ }
431
+ }
432
+
433
+ return { findings, byFile };
434
+ }
435
+
436
+ /**
437
+ * Programmatic scan API — the equivalent of dependency-cruiser's `cruise()`
438
+ * or madge's constructor. Runs the full analysis pipeline without CLI I/O.
439
+ *
440
+ * Usage:
441
+ * ```ts
442
+ * import { scan, DEFAULT_OPTS } from './index.js';
443
+ * const result = await scan({ root: '/path/to/project', graph: true });
444
+ * console.log(result.exitCode); // 0=clean, 1=findings, 2=error
445
+ * ```
446
+ */
447
+ export async function scan(
448
+ overrides: Partial<AnalysisOptions> = {}
449
+ ): Promise<ScanResult> {
450
+ const { DEFAULT_OPTS } = await import('./types/constants.js');
451
+ const opts: AnalysisOptions = {
452
+ ...DEFAULT_OPTS,
453
+ ...overrides,
454
+ thresholds: { ...DEFAULT_OPTS.thresholds, ...overrides.thresholds },
455
+ };
456
+
457
+ const { createOptions } = await import('./pipeline/create-options.js');
458
+ const finalOpts = createOptions({ args: opts });
459
+
460
+ const { main } = await import('./pipeline/main.js');
461
+ const exitCode = await main(finalOpts);
462
+
463
+ return { exitCode };
464
+ }
465
+
466
+ export interface ScanResult {
467
+ exitCode: number;
468
+ }
@@ -0,0 +1,147 @@
1
+ import { describe, expect, it, vi, beforeEach } from 'vitest';
2
+ import { execSync } from 'node:child_process';
3
+ import path from 'node:path';
4
+ import { resolveAffectedFiles } from './affected.js';
5
+ import type { DependencyState } from '../types/index.js';
6
+
7
+ vi.mock('node:child_process', () => ({
8
+ execSync: vi.fn(),
9
+ }));
10
+
11
+ function makeDependencyState(
12
+ outgoing: Record<string, string[]>,
13
+ incoming: Record<string, string[]>
14
+ ): DependencyState {
15
+ return {
16
+ outgoing: new Map(Object.entries(outgoing).map(([k, v]) => [k, new Set(v)])),
17
+ incoming: new Map(Object.entries(incoming).map(([k, v]) => [k, new Set(v)])),
18
+ files: new Set([...Object.keys(outgoing), ...Object.keys(incoming)]),
19
+ externalImports: new Map(),
20
+ packageJsonDeps: new Map(),
21
+ packageJsonDevDeps: new Map(),
22
+ } as unknown as DependencyState;
23
+ }
24
+
25
+ describe('resolveAffectedFiles', () => {
26
+ beforeEach(() => {
27
+ vi.mocked(execSync).mockReset();
28
+ });
29
+
30
+ it('returns empty when git returns no changed files', () => {
31
+ vi.mocked(execSync).mockReturnValue('');
32
+ const state = makeDependencyState({}, {});
33
+ expect(resolveAffectedFiles('/repo', 'HEAD', state)).toEqual([]);
34
+ });
35
+
36
+ it('returns changed files and their transitive dependents as relative paths', () => {
37
+ vi.mocked(execSync).mockReturnValue('src/a.ts\nsrc/b.ts\n');
38
+ const state = makeDependencyState(
39
+ { 'src/a.ts': ['src/c.ts'], 'src/c.ts': [] },
40
+ {
41
+ 'src/a.ts': ['src/d.ts'],
42
+ 'src/d.ts': ['src/e.ts'],
43
+ }
44
+ );
45
+
46
+ const result = resolveAffectedFiles('/repo', 'HEAD', state);
47
+ expect(result).toContain('src/a.ts');
48
+ expect(result).toContain('src/b.ts');
49
+ expect(result).toContain('src/d.ts');
50
+ expect(result).toContain('src/e.ts');
51
+ for (const p of result) {
52
+ expect(path.isAbsolute(p)).toBe(false);
53
+ }
54
+ });
55
+
56
+ it('returns empty when git command fails', () => {
57
+ vi.mocked(execSync).mockImplementation(() => {
58
+ throw new Error('git failed');
59
+ });
60
+ const state = makeDependencyState({}, {});
61
+ expect(resolveAffectedFiles('/repo', 'main', state)).toEqual([]);
62
+ });
63
+
64
+ it('filters non-ts/js files from git output', () => {
65
+ vi.mocked(execSync).mockReturnValue('README.md\nsrc/a.ts\npackage.json\n');
66
+ const state = makeDependencyState({}, {});
67
+
68
+ const result = resolveAffectedFiles('/repo', 'HEAD', state);
69
+ expect(result).toHaveLength(1);
70
+ expect(result[0]).toBe('src/a.ts');
71
+ });
72
+
73
+ it('passes the revision argument to git diff', () => {
74
+ vi.mocked(execSync).mockReturnValue('');
75
+ const state = makeDependencyState({}, {});
76
+
77
+ resolveAffectedFiles('/repo', 'main~5', state);
78
+
79
+ expect(execSync).toHaveBeenCalledWith(
80
+ 'git diff --name-only --diff-filter=ACMRT main~5',
81
+ expect.objectContaining({ cwd: '/repo' })
82
+ );
83
+ });
84
+
85
+ it('returns only changed file when it has no dependents', () => {
86
+ vi.mocked(execSync).mockReturnValue('src/isolated.ts\n');
87
+ const state = makeDependencyState(
88
+ { 'src/other.ts': ['src/isolated.ts'] },
89
+ {}
90
+ );
91
+
92
+ const result = resolveAffectedFiles('/repo', 'HEAD', state);
93
+ expect(result).toEqual(['src/isolated.ts']);
94
+ });
95
+
96
+ it('handles cycles in dependency graph without infinite loop', () => {
97
+ vi.mocked(execSync).mockReturnValue('src/a.ts\n');
98
+ const state = makeDependencyState(
99
+ {
100
+ 'src/a.ts': ['src/b.ts'],
101
+ 'src/b.ts': ['src/a.ts'],
102
+ },
103
+ {
104
+ 'src/a.ts': ['src/b.ts'],
105
+ 'src/b.ts': ['src/a.ts'],
106
+ }
107
+ );
108
+
109
+ const result = resolveAffectedFiles('/repo', 'HEAD', state);
110
+ expect(result).toContain('src/a.ts');
111
+ expect(result).toContain('src/b.ts');
112
+ expect(result.length).toBe(2);
113
+ });
114
+
115
+ it('follows deep transitive chains (A→B→C→D)', () => {
116
+ vi.mocked(execSync).mockReturnValue('src/a.ts\n');
117
+ const state = makeDependencyState(
118
+ {},
119
+ {
120
+ 'src/a.ts': ['src/b.ts'],
121
+ 'src/b.ts': ['src/c.ts'],
122
+ 'src/c.ts': ['src/d.ts'],
123
+ }
124
+ );
125
+
126
+ const result = resolveAffectedFiles('/repo', 'HEAD', state);
127
+ expect(result).toHaveLength(4);
128
+ expect(result).toContain('src/a.ts');
129
+ expect(result).toContain('src/b.ts');
130
+ expect(result).toContain('src/c.ts');
131
+ expect(result).toContain('src/d.ts');
132
+ });
133
+
134
+ it('accepts .jsx, .tsx, .js, .mjs, .cjs extensions', () => {
135
+ vi.mocked(execSync).mockReturnValue(
136
+ 'src/a.ts\nsrc/b.tsx\nsrc/c.js\nsrc/d.jsx\nsrc/e.mjs\nsrc/f.cjs\ndata.csv\n'
137
+ );
138
+ const state = makeDependencyState({}, {});
139
+
140
+ const result = resolveAffectedFiles('/repo', 'HEAD', state);
141
+ expect(result.length).toBeGreaterThanOrEqual(4);
142
+ expect(result.some(r => r.endsWith('.ts'))).toBe(true);
143
+ expect(result.some(r => r.endsWith('.tsx'))).toBe(true);
144
+ expect(result.some(r => r.endsWith('.js'))).toBe(true);
145
+ expect(result.every(r => !r.endsWith('.csv'))).toBe(true);
146
+ });
147
+ });
@@ -0,0 +1,68 @@
1
+ import { execSync } from 'node:child_process';
2
+ import path from 'node:path';
3
+
4
+ import { ALLOWED_EXTS } from '../types/index.js';
5
+
6
+
7
+ import type { DependencyState } from '../types/index.js';
8
+
9
+ /**
10
+ * Resolves --affected: git changed files + transitive dependents.
11
+ * Inspired by dependency-cruiser's --affected (uses watskeburt internally).
12
+ */
13
+ export function resolveAffectedFiles(
14
+ root: string,
15
+ revision: string,
16
+ dependencyState: DependencyState
17
+ ): string[] {
18
+ const changedFiles = getGitChangedFiles(root, revision);
19
+ if (changedFiles.length === 0) return [];
20
+
21
+ const changedRelPaths = new Set(changedFiles);
22
+ const affected = new Set(changedRelPaths);
23
+ collectTransitiveDependents(changedRelPaths, dependencyState, affected);
24
+
25
+ return [...affected];
26
+ }
27
+
28
+ function getGitChangedFiles(root: string, revision: string): string[] {
29
+ try {
30
+ const stdout = execSync(
31
+ `git diff --name-only --diff-filter=ACMRT ${revision}`,
32
+ { cwd: root, encoding: 'utf8', timeout: 10000 }
33
+ ).trim();
34
+
35
+ if (!stdout) return [];
36
+
37
+ return stdout
38
+ .split('\n')
39
+ .filter(f => {
40
+ const ext = path.extname(f);
41
+ return ALLOWED_EXTS.has(ext);
42
+ });
43
+ } catch {
44
+ return [];
45
+ }
46
+ }
47
+
48
+ /**
49
+ * BFS: walk incoming edges to collect all transitive dependents of changed files.
50
+ */
51
+ function collectTransitiveDependents(
52
+ seeds: Set<string>,
53
+ state: DependencyState,
54
+ result: Set<string>
55
+ ): void {
56
+ const queue = [...seeds];
57
+ while (queue.length > 0) {
58
+ const current = queue.pop()!;
59
+ const dependents = state.incoming.get(current);
60
+ if (!dependents) continue;
61
+ for (const dep of dependents) {
62
+ if (!result.has(dep)) {
63
+ result.add(dep);
64
+ queue.push(dep);
65
+ }
66
+ }
67
+ }
68
+ }