octocode-cli 1.2.8 → 1.2.10

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 +45 -38
  2. package/out/octocode-cli.js +73 -11763
  3. package/package.json +35 -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 +413 -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,1066 @@
1
+ import { describe, expect, it } from 'vitest';
2
+
3
+ import {
4
+ categoryBreakdown,
5
+ collectTagCloud,
6
+ computeFeatureScores,
7
+ computeHealthScore,
8
+ computeQualityAspectRatings,
9
+ diverseTopRecommendations,
10
+ diversifyFindings,
11
+ formatFileSize,
12
+ generateSummaryMd,
13
+ severityBreakdown,
14
+ } from './summary-md.js';
15
+ import {
16
+ ARCHITECTURE_CATEGORIES,
17
+ CODE_QUALITY_CATEGORIES,
18
+ DEAD_CODE_CATEGORIES,
19
+ REPORT_SCHEMA_VERSION,
20
+ SECURITY_CATEGORIES,
21
+ TEST_QUALITY_CATEGORIES,
22
+ } from './writer.js';
23
+
24
+ import type { FullReport } from './writer.js';
25
+ import type { FileEntry, Finding } from '../types/index.js';
26
+
27
+ function makeFinding(overrides: Partial<Finding> = {}): Finding {
28
+ return {
29
+ id: 'test-1',
30
+ severity: 'medium',
31
+ category: 'code-quality',
32
+ title: 'Test finding',
33
+ file: 'src/test.ts',
34
+ lineStart: 1,
35
+ lineEnd: 5,
36
+ reason: 'test reason',
37
+ files: ['src/test.ts'],
38
+ suggestedFix: { strategy: 'test', steps: [] },
39
+ ...overrides,
40
+ } as Finding;
41
+ }
42
+
43
+ describe('report-writer constants', () => {
44
+ it('REPORT_SCHEMA_VERSION is 1.1.0', () => {
45
+ expect(REPORT_SCHEMA_VERSION).toBe('1.1.0');
46
+ });
47
+
48
+ it('ARCHITECTURE_CATEGORIES is a Set', () => {
49
+ expect(ARCHITECTURE_CATEGORIES).toBeInstanceOf(Set);
50
+ });
51
+
52
+ it('CODE_QUALITY_CATEGORIES is a Set', () => {
53
+ expect(CODE_QUALITY_CATEGORIES).toBeInstanceOf(Set);
54
+ });
55
+
56
+ it('DEAD_CODE_CATEGORIES is a Set', () => {
57
+ expect(DEAD_CODE_CATEGORIES).toBeInstanceOf(Set);
58
+ });
59
+
60
+ it('SECURITY_CATEGORIES is a Set', () => {
61
+ expect(SECURITY_CATEGORIES).toBeInstanceOf(Set);
62
+ });
63
+
64
+ it('TEST_QUALITY_CATEGORIES is a Set', () => {
65
+ expect(TEST_QUALITY_CATEGORIES).toBeInstanceOf(Set);
66
+ });
67
+ });
68
+
69
+ describe('severityBreakdown', () => {
70
+ it('returns zero counts for empty findings', () => {
71
+ const result = severityBreakdown([]);
72
+ expect(result).toEqual({
73
+ critical: 0,
74
+ high: 0,
75
+ medium: 0,
76
+ low: 0,
77
+ info: 0,
78
+ });
79
+ });
80
+
81
+ it('counts single severity correctly', () => {
82
+ const findings = [makeFinding({ severity: 'critical' })];
83
+ const result = severityBreakdown(findings);
84
+ expect(result.critical).toBe(1);
85
+ expect(result.high).toBe(0);
86
+ });
87
+
88
+ it('counts multiple severities correctly', () => {
89
+ const findings = [
90
+ makeFinding({ id: '1', severity: 'high' }),
91
+ makeFinding({ id: '2', severity: 'high' }),
92
+ makeFinding({ id: '3', severity: 'medium' }),
93
+ makeFinding({ id: '4', severity: 'critical' }),
94
+ ];
95
+ const result = severityBreakdown(findings);
96
+ expect(result.critical).toBe(1);
97
+ expect(result.high).toBe(2);
98
+ expect(result.medium).toBe(1);
99
+ expect(result.low).toBe(0);
100
+ expect(result.info).toBe(0);
101
+ });
102
+
103
+ it('counts all five severities', () => {
104
+ const findings = [
105
+ makeFinding({ id: '1', severity: 'critical' }),
106
+ makeFinding({ id: '2', severity: 'high' }),
107
+ makeFinding({ id: '3', severity: 'medium' }),
108
+ makeFinding({ id: '4', severity: 'low' }),
109
+ makeFinding({ id: '5', severity: 'info' }),
110
+ ];
111
+ const result = severityBreakdown(findings);
112
+ expect(result).toEqual({
113
+ critical: 1,
114
+ high: 1,
115
+ medium: 1,
116
+ low: 1,
117
+ info: 1,
118
+ });
119
+ });
120
+ });
121
+
122
+ describe('categoryBreakdown', () => {
123
+ it('returns empty object for empty findings', () => {
124
+ expect(categoryBreakdown([])).toEqual({});
125
+ });
126
+
127
+ it('counts single category correctly', () => {
128
+ const findings = [makeFinding({ category: 'dead-export' })];
129
+ const result = categoryBreakdown(findings);
130
+ expect(result['dead-export']).toBe(1);
131
+ });
132
+
133
+ it('counts multiple categories correctly', () => {
134
+ const findings = [
135
+ makeFinding({ id: '1', category: 'dead-export' }),
136
+ makeFinding({ id: '2', category: 'dead-export' }),
137
+ makeFinding({ id: '3', category: 'dependency-cycle' }),
138
+ ];
139
+ const result = categoryBreakdown(findings);
140
+ expect(result['dead-export']).toBe(2);
141
+ expect(result['dependency-cycle']).toBe(1);
142
+ });
143
+
144
+ it('handles unknown categories', () => {
145
+ const findings = [makeFinding({ category: 'custom-category' })];
146
+ const result = categoryBreakdown(findings);
147
+ expect(result['custom-category']).toBe(1);
148
+ });
149
+ });
150
+
151
+ describe('computeHealthScore', () => {
152
+ it('returns 100 for 0 totalFiles', () => {
153
+ expect(computeHealthScore([], 0)).toBe(100);
154
+ expect(computeHealthScore([makeFinding()], 0)).toBe(100);
155
+ });
156
+
157
+ it('returns 100 for no findings', () => {
158
+ expect(computeHealthScore([], 50)).toBe(100);
159
+ });
160
+
161
+ it('penalizes critical findings (weight 25)', () => {
162
+ const findings = [makeFinding({ severity: 'critical' })];
163
+ const score = computeHealthScore(findings, 10);
164
+ expect(score).toBe(80);
165
+ });
166
+
167
+ it('penalizes high findings (weight 10)', () => {
168
+ const findings = [makeFinding({ severity: 'high' })];
169
+ const score = computeHealthScore(findings, 10);
170
+ expect(score).toBe(91);
171
+ });
172
+
173
+ it('penalizes medium findings (weight 3)', () => {
174
+ const findings = [makeFinding({ severity: 'medium' })];
175
+ const score = computeHealthScore(findings, 10);
176
+ expect(score).toBe(97); // 100 - (3/10)*10 = 97
177
+ });
178
+
179
+ it('penalizes low findings (weight 1)', () => {
180
+ const findings = [makeFinding({ severity: 'low' })];
181
+ const score = computeHealthScore(findings, 10);
182
+ expect(score).toBe(99); // 100 - (1/10)*10 = 99
183
+ });
184
+
185
+ it('info severity has weight 0', () => {
186
+ const findings = [makeFinding({ severity: 'info' })];
187
+ const score = computeHealthScore(findings, 10);
188
+ expect(score).toBe(100);
189
+ });
190
+
191
+ it('penalizes proportional to file count', () => {
192
+ const findings = [makeFinding({ severity: 'high' })];
193
+ const smallRepo = computeHealthScore(findings, 5);
194
+ const largeRepo = computeHealthScore(findings, 100);
195
+ expect(largeRepo).toBeGreaterThan(smallRepo);
196
+ });
197
+
198
+ it('floors at 0', () => {
199
+ const findings = Array.from({ length: 10 }, () =>
200
+ makeFinding({ id: `f-${Math.random()}`, severity: 'critical' })
201
+ );
202
+ expect(computeHealthScore(findings, 1)).toBe(4);
203
+ });
204
+
205
+ it('rounds score to integer', () => {
206
+ const findings = [makeFinding({ severity: 'medium' })];
207
+ const score = computeHealthScore(findings, 3);
208
+ expect(Number.isInteger(score)).toBe(true);
209
+ });
210
+
211
+ it('never returns a perfect score when non-info findings exist in huge repos', () => {
212
+ const high = computeHealthScore([makeFinding({ severity: 'high' })], 10_000);
213
+ const medium = computeHealthScore(
214
+ [makeFinding({ severity: 'medium' })],
215
+ 10_000
216
+ );
217
+ const low = computeHealthScore([makeFinding({ severity: 'low' })], 10_000);
218
+ expect(high).toBeLessThan(100);
219
+ expect(medium).toBeLessThan(100);
220
+ expect(low).toBeLessThan(100);
221
+ });
222
+ });
223
+
224
+ describe('collectTagCloud', () => {
225
+ it('returns empty for no findings', () => {
226
+ expect(collectTagCloud([])).toEqual([]);
227
+ });
228
+
229
+ it('returns empty when findings have no tags', () => {
230
+ expect(collectTagCloud([makeFinding()])).toEqual([]);
231
+ });
232
+
233
+ it('aggregates tags from single finding', () => {
234
+ const findings = [makeFinding({ tags: ['coupling', 'architecture'] })];
235
+ const cloud = collectTagCloud(findings);
236
+ expect(cloud).toHaveLength(2);
237
+ expect(cloud).toContainEqual({ tag: 'coupling', count: 1 });
238
+ expect(cloud).toContainEqual({ tag: 'architecture', count: 1 });
239
+ });
240
+
241
+ it('counts duplicate tags across findings', () => {
242
+ const findings = [
243
+ makeFinding({ id: '1', tags: ['coupling', 'architecture'] }),
244
+ makeFinding({ id: '2', tags: ['coupling', 'change-risk'] }),
245
+ makeFinding({ id: '3', tags: ['dead-code'] }),
246
+ ];
247
+ const cloud = collectTagCloud(findings);
248
+ expect(cloud[0]).toEqual({ tag: 'coupling', count: 2 });
249
+ expect(cloud.length).toBe(4);
250
+ });
251
+
252
+ it('sorts by count descending', () => {
253
+ const findings = [
254
+ makeFinding({ id: '1', tags: ['a', 'b', 'c'] }),
255
+ makeFinding({ id: '2', tags: ['a', 'b'] }),
256
+ makeFinding({ id: '3', tags: ['a'] }),
257
+ ];
258
+ const cloud = collectTagCloud(findings);
259
+ expect(cloud[0]).toEqual({ tag: 'a', count: 3 });
260
+ expect(cloud[1]).toEqual({ tag: 'b', count: 2 });
261
+ expect(cloud[2]).toEqual({ tag: 'c', count: 1 });
262
+ });
263
+
264
+ it('skips findings with undefined tags', () => {
265
+ const findings = [
266
+ makeFinding({ id: '1', tags: ['valid'] }),
267
+ makeFinding({ id: '2' }),
268
+ ];
269
+ const cloud = collectTagCloud(findings);
270
+ expect(cloud).toEqual([{ tag: 'valid', count: 1 }]);
271
+ });
272
+ });
273
+
274
+ describe('computeFeatureScores', () => {
275
+ it('scores active categories and maps them to pillars', () => {
276
+ const rows = computeFeatureScores(
277
+ [
278
+ makeFinding({
279
+ id: 'sec',
280
+ category: 'hardcoded-secret',
281
+ severity: 'critical',
282
+ file: 'src/security.ts',
283
+ }),
284
+ makeFinding({
285
+ id: 'dead',
286
+ category: 'dead-export',
287
+ severity: 'low',
288
+ file: 'src/dead.ts',
289
+ }),
290
+ ],
291
+ 100,
292
+ null
293
+ );
294
+ const security = rows.find(r => r.category === 'hardcoded-secret');
295
+ const dead = rows.find(r => r.category === 'dead-export');
296
+ expect(security).toBeDefined();
297
+ expect(security!.pillar).toBe('security');
298
+ expect(security!.score).toBeLessThan(100);
299
+ expect(dead).toBeDefined();
300
+ expect(dead!.pillar).toBe('dead-code');
301
+ });
302
+
303
+ it('respects active feature filters', () => {
304
+ const rows = computeFeatureScores(
305
+ [
306
+ makeFinding({
307
+ id: 'only',
308
+ category: 'dead-export',
309
+ severity: 'medium',
310
+ file: 'src/dead.ts',
311
+ }),
312
+ ],
313
+ 10,
314
+ new Set(['dead-export'])
315
+ );
316
+ expect(rows).toHaveLength(1);
317
+ expect(rows[0].category).toBe('dead-export');
318
+ });
319
+
320
+ it('applies hotspot context penalty for findings in high-risk files', () => {
321
+ const baseRows = computeFeatureScores(
322
+ [
323
+ makeFinding({
324
+ id: 'sec-hot',
325
+ category: 'hardcoded-secret',
326
+ severity: 'high',
327
+ file: 'src/hot.ts',
328
+ }),
329
+ ],
330
+ 100,
331
+ null
332
+ );
333
+ const contextRows = computeFeatureScores(
334
+ [
335
+ makeFinding({
336
+ id: 'sec-hot',
337
+ category: 'hardcoded-secret',
338
+ severity: 'high',
339
+ file: 'src/hot.ts',
340
+ }),
341
+ ],
342
+ 100,
343
+ null,
344
+ {
345
+ hotFiles: [
346
+ {
347
+ file: 'src/hot.ts',
348
+ riskScore: 90,
349
+ fanIn: 10,
350
+ fanOut: 8,
351
+ complexityScore: 30,
352
+ exportCount: 5,
353
+ inCycle: true,
354
+ onCriticalPath: true,
355
+ },
356
+ ],
357
+ }
358
+ );
359
+ expect(contextRows[0].score).toBeLessThan(baseRows[0].score);
360
+ expect(contextRows[0].contextPenalty).toBeGreaterThan(0);
361
+ expect(contextRows[0].hotspotHits).toBe(1);
362
+ });
363
+
364
+ it('does not penalize categories without hotspot overlap', () => {
365
+ const rows = computeFeatureScores(
366
+ [
367
+ makeFinding({
368
+ id: 'sec-cold',
369
+ category: 'hardcoded-secret',
370
+ severity: 'high',
371
+ file: 'src/cold.ts',
372
+ }),
373
+ ],
374
+ 100,
375
+ null,
376
+ {
377
+ hotFiles: [
378
+ {
379
+ file: 'src/hot.ts',
380
+ riskScore: 95,
381
+ fanIn: 11,
382
+ fanOut: 9,
383
+ complexityScore: 28,
384
+ exportCount: 6,
385
+ inCycle: true,
386
+ onCriticalPath: false,
387
+ },
388
+ ],
389
+ }
390
+ );
391
+ expect(rows[0].contextPenalty).toBe(0);
392
+ expect(rows[0].hotspotHits).toBe(0);
393
+ });
394
+ });
395
+
396
+ describe('computeQualityAspectRatings', () => {
397
+ function makeFileEntry(overrides: Partial<FileEntry> = {}): FileEntry {
398
+ return {
399
+ package: 'pkg',
400
+ file: 'src/service/user-service.ts',
401
+ parseEngine: 'typescript',
402
+ nodeCount: 10,
403
+ kindCounts: {},
404
+ functions: [
405
+ {
406
+ kind: 'function',
407
+ name: 'loadUserProfile',
408
+ nameHint: 'loadUserProfile',
409
+ file: 'src/service/user-service.ts',
410
+ lineStart: 1,
411
+ lineEnd: 10,
412
+ columnStart: 1,
413
+ columnEnd: 1,
414
+ statementCount: 8,
415
+ complexity: 5,
416
+ maxBranchDepth: 2,
417
+ maxLoopDepth: 1,
418
+ returns: 1,
419
+ awaits: 1,
420
+ calls: 3,
421
+ loops: 0,
422
+ lengthLines: 10,
423
+ cognitiveComplexity: 6,
424
+ },
425
+ ],
426
+ flows: [],
427
+ dependencyProfile: {
428
+ internalDependencies: [],
429
+ externalDependencies: [],
430
+ unresolvedDependencies: [],
431
+ declaredExports: [],
432
+ importedSymbols: [],
433
+ reExports: [],
434
+ },
435
+ ...overrides,
436
+ };
437
+ }
438
+
439
+ it('returns weighted hybrid ratings with expected aspects', () => {
440
+ const result = computeQualityAspectRatings(
441
+ [
442
+ makeFinding({
443
+ id: 'a1',
444
+ category: 'dependency-cycle',
445
+ severity: 'high',
446
+ file: 'src/service/user-service.ts',
447
+ }),
448
+ makeFinding({
449
+ id: 'a2',
450
+ category: 'dead-export',
451
+ severity: 'medium',
452
+ file: 'src/common/shared-utils.ts',
453
+ }),
454
+ ],
455
+ {
456
+ fileInventory: [
457
+ makeFileEntry(),
458
+ makeFileEntry({
459
+ file: 'src/common/shared-utils.ts',
460
+ functions: [],
461
+ symbolUsageSummary: {
462
+ declaredExportCount: 12,
463
+ importedSymbolCount: 2,
464
+ internalImportCount: 1,
465
+ externalImportCount: 0,
466
+ reExportCount: 0,
467
+ dominantInternalDependency: null,
468
+ },
469
+ }),
470
+ ],
471
+ hotFiles: [
472
+ {
473
+ file: 'src/service/user-service.ts',
474
+ riskScore: 88,
475
+ fanIn: 7,
476
+ fanOut: 4,
477
+ complexityScore: 20,
478
+ exportCount: 2,
479
+ inCycle: true,
480
+ onCriticalPath: true,
481
+ },
482
+ ],
483
+ reportAnalysis: {
484
+ graphSignals: [],
485
+ astSignals: [],
486
+ combinedSignals: [],
487
+ strongestGraphSignal: {
488
+ kind: 'dependency-hotspot',
489
+ lens: 'graph',
490
+ title: 'Hotspot',
491
+ summary: 'Hotspot found',
492
+ confidence: 'high',
493
+ score: 90,
494
+ files: ['src/service/user-service.ts'],
495
+ categories: ['dependency-cycle'],
496
+ evidence: {},
497
+ },
498
+ strongestAstSignal: null,
499
+ combinedInterpretation: null,
500
+ recommendedValidation: null,
501
+ investigationPrompts: [],
502
+ },
503
+ }
504
+ );
505
+ expect(result.model).toBe('hybrid-ai-structure-v1');
506
+ expect(result.overallScore).toBeGreaterThanOrEqual(0);
507
+ expect(result.overallScore).toBeLessThanOrEqual(100);
508
+ expect(result.aspects.map(a => a.aspect)).toEqual([
509
+ 'architecture-structure',
510
+ 'folder-topology',
511
+ 'naming-quality',
512
+ 'common-layer-health',
513
+ 'maintainability-evolvability',
514
+ 'codebase-consistency',
515
+ ]);
516
+ });
517
+
518
+ it('keeps common-layer score neutral-positive when no shared/common folders exist', () => {
519
+ const result = computeQualityAspectRatings(
520
+ [makeFinding({ id: 'n1', category: 'cognitive-complexity' })],
521
+ {
522
+ fileInventory: [
523
+ makeFileEntry({ file: 'src/domain/user/profile-service.ts' }),
524
+ ],
525
+ }
526
+ );
527
+ const commonLayer = result.aspects.find(
528
+ aspect => aspect.aspect === 'common-layer-health'
529
+ );
530
+ expect(commonLayer).toBeDefined();
531
+ expect(commonLayer!.score).toBeGreaterThanOrEqual(80);
532
+ });
533
+
534
+ it('ignores test-file findings when includeTests=false', () => {
535
+ const findings: Finding[] = [
536
+ makeFinding({
537
+ id: 'prod-arch',
538
+ category: 'dependency-cycle',
539
+ severity: 'high',
540
+ file: 'src/service/core.ts',
541
+ files: ['src/service/core.ts'],
542
+ }),
543
+ ...Array.from({ length: 12 }, (_, index) =>
544
+ makeFinding({
545
+ id: `test-only-${index}`,
546
+ category: 'dependency-critical-path',
547
+ severity: 'critical',
548
+ file: `tests/service/core-${index}.test.ts`,
549
+ files: [`tests/service/core-${index}.test.ts`],
550
+ })
551
+ ),
552
+ ];
553
+
554
+ const baseContext = {
555
+ fileInventory: [
556
+ ...Array.from({ length: 20 }, (_, index) =>
557
+ makeFileEntry({ file: `src/service/core-${index}.ts` })
558
+ ),
559
+ makeFileEntry({ file: 'tests/service/core.test.ts' }),
560
+ ],
561
+ };
562
+ const withoutTests = computeQualityAspectRatings(findings, {
563
+ ...baseContext,
564
+ includeTests: false,
565
+ });
566
+ const withTests = computeQualityAspectRatings(findings, {
567
+ ...baseContext,
568
+ includeTests: true,
569
+ });
570
+ const withoutTestsScore = withoutTests.aspects.find(
571
+ aspect => aspect.aspect === 'architecture-structure'
572
+ )!.score;
573
+ const withTestsScore = withTests.aspects.find(
574
+ aspect => aspect.aspect === 'architecture-structure'
575
+ )!.score;
576
+
577
+ expect(withoutTestsScore).toBeGreaterThan(withTestsScore);
578
+ });
579
+
580
+ it('downweights advisory dead-code categories in maintainability scoring', () => {
581
+ const advisoryFindings = Array.from({ length: 20 }, (_, index) =>
582
+ makeFinding({
583
+ id: `advisory-${index}`,
584
+ category: 'move-to-caller',
585
+ severity: 'low',
586
+ file: `src/tools/advisory-${index}.ts`,
587
+ files: [`src/tools/advisory-${index}.ts`],
588
+ })
589
+ );
590
+ const concreteFindings = Array.from({ length: 20 }, (_, index) =>
591
+ makeFinding({
592
+ id: `concrete-${index}`,
593
+ category: 'dead-file',
594
+ severity: 'low',
595
+ file: `src/tools/concrete-${index}.ts`,
596
+ files: [`src/tools/concrete-${index}.ts`],
597
+ })
598
+ );
599
+
600
+ const advisoryScore = computeQualityAspectRatings(advisoryFindings, {
601
+ fileInventory: advisoryFindings.map(f => makeFileEntry({ file: f.file })),
602
+ }).aspects.find(a => a.aspect === 'maintainability-evolvability')!.score;
603
+ const concreteScore = computeQualityAspectRatings(concreteFindings, {
604
+ fileInventory: concreteFindings.map(f => makeFileEntry({ file: f.file })),
605
+ }).aspects.find(a => a.aspect === 'maintainability-evolvability')!.score;
606
+
607
+ expect(advisoryScore).toBeGreaterThan(concreteScore);
608
+ });
609
+
610
+ it('normalizes folder topology for scans under a shared path prefix', () => {
611
+ const result = computeQualityAspectRatings([], {
612
+ fileInventory: [
613
+ makeFileEntry({
614
+ file: 'packages/octocode-mcp/src/tools/local_find_files/findFiles.ts',
615
+ }),
616
+ makeFileEntry({
617
+ file: 'packages/octocode-mcp/src/tools/local_ripgrep/ripgrepExecutor.ts',
618
+ }),
619
+ makeFileEntry({
620
+ file: 'packages/octocode-mcp/src/tools/local_view_structure/structureWalker.ts',
621
+ }),
622
+ ],
623
+ });
624
+
625
+ const folderAspect = result.aspects.find(
626
+ aspect => aspect.aspect === 'folder-topology'
627
+ );
628
+ expect(folderAspect).toBeDefined();
629
+ expect(folderAspect!.score).toBeGreaterThanOrEqual(85);
630
+ });
631
+
632
+ it('ignores generated and minified paths in hybrid quality rating by default', () => {
633
+ const result = computeQualityAspectRatings(
634
+ [
635
+ makeFinding({
636
+ id: 'gen-1',
637
+ category: 'dependency-cycle',
638
+ severity: 'high',
639
+ file: 'dist/vendor.min.js',
640
+ files: ['dist/vendor.min.js'],
641
+ }),
642
+ ],
643
+ {
644
+ fileInventory: [
645
+ makeFileEntry({ file: 'dist/vendor.min.js', functions: [] }),
646
+ makeFileEntry({ file: 'src/service/core.ts' }),
647
+ ],
648
+ }
649
+ );
650
+
651
+ const architectureScore = result.aspects.find(
652
+ aspect => aspect.aspect === 'architecture-structure'
653
+ )!.score;
654
+ expect(architectureScore).toBeGreaterThanOrEqual(90);
655
+ });
656
+ });
657
+
658
+ describe('formatFileSize', () => {
659
+ it('formats bytes as B when < 1024', () => {
660
+ expect(formatFileSize(0)).toBe('0 B');
661
+ expect(formatFileSize(512)).toBe('512 B');
662
+ expect(formatFileSize(1023)).toBe('1023 B');
663
+ });
664
+
665
+ it('formats as KB when >= 1024 and < 1024*1024', () => {
666
+ expect(formatFileSize(1024)).toBe('1.0 KB');
667
+ expect(formatFileSize(2048)).toBe('2.0 KB');
668
+ expect(formatFileSize(1536)).toBe('1.5 KB');
669
+ });
670
+
671
+ it('formats as MB when >= 1024*1024', () => {
672
+ expect(formatFileSize(1024 * 1024)).toBe('1.0 MB');
673
+ expect(formatFileSize(2.5 * 1024 * 1024)).toBe('2.5 MB');
674
+ });
675
+ });
676
+
677
+ describe('diversifyFindings', () => {
678
+ const makeDraft = (
679
+ severity: Finding['severity'],
680
+ category: string,
681
+ idx: number
682
+ ) =>
683
+ makeFinding({
684
+ id: `draft-${category}-${idx}`,
685
+ severity,
686
+ category,
687
+ file: `${category}-${idx}.ts`,
688
+ });
689
+
690
+ it('returns all when limit >= sorted.length', () => {
691
+ const input = [makeDraft('high', 'a', 1), makeDraft('high', 'b', 1)];
692
+ expect(diversifyFindings(input, 10)).toBe(input);
693
+ expect(diversifyFindings(input, 2)).toBe(input);
694
+ });
695
+
696
+ it('returns all when limit is Infinity', () => {
697
+ const input = [makeDraft('high', 'a', 1)];
698
+ expect(diversifyFindings(input, Infinity)).toBe(input);
699
+ });
700
+
701
+ it('returns diverse set across categories when limit < length', () => {
702
+ const input = [
703
+ ...Array.from({ length: 10 }, (_, i) =>
704
+ makeDraft('high', 'await-in-loop', i)
705
+ ),
706
+ makeDraft('high', 'dead-export', 1),
707
+ makeDraft('high', 'dead-export', 2),
708
+ ];
709
+ const result = diversifyFindings(input, 5);
710
+ expect(result).toHaveLength(5);
711
+ const categories = new Set(result.map(f => f.category));
712
+ expect(categories.size).toBe(2);
713
+ });
714
+
715
+ it('prioritizes categories by highest severity', () => {
716
+ const input = [
717
+ makeDraft('critical', 'security', 1),
718
+ makeDraft('high', 'quality', 1),
719
+ makeDraft('medium', 'dead-code', 1),
720
+ ];
721
+ const result = diversifyFindings(input, 3);
722
+ expect(result[0].category).toBe('security');
723
+ expect(result[1].category).toBe('quality');
724
+ expect(result[2].category).toBe('dead-code');
725
+ });
726
+
727
+ it('handles empty input', () => {
728
+ expect(diversifyFindings([], 5)).toEqual([]);
729
+ });
730
+
731
+ it('handles single category', () => {
732
+ const input = Array.from({ length: 10 }, (_, i) =>
733
+ makeDraft('high', 'only-cat', i)
734
+ );
735
+ const result = diversifyFindings(input, 3);
736
+ expect(result).toHaveLength(3);
737
+ expect(result.every(f => f.category === 'only-cat')).toBe(true);
738
+ });
739
+
740
+ it('handles limit of 1', () => {
741
+ const input = [makeDraft('critical', 'a', 1), makeDraft('high', 'b', 1)];
742
+ const result = diversifyFindings(input, 1);
743
+ expect(result).toHaveLength(1);
744
+ expect(result[0].severity).toBe('critical');
745
+ });
746
+
747
+ it('returns empty when limit is 0 and input has items', () => {
748
+ const input = [makeDraft('high', 'a', 1)];
749
+ const result = diversifyFindings(input, 0);
750
+ expect(result).toEqual([]);
751
+ });
752
+
753
+ it('handles non-finite limit by returning all', () => {
754
+ const input = [makeDraft('high', 'a', 1)];
755
+ expect(diversifyFindings(input, NaN)).toBe(input);
756
+ });
757
+ });
758
+
759
+ describe('diverseTopRecommendations', () => {
760
+ it('limits findings per category (default maxPerCategory=2)', () => {
761
+ const findings = [
762
+ makeFinding({ id: '1', category: 'dead-export' }),
763
+ makeFinding({ id: '2', category: 'dead-export' }),
764
+ makeFinding({ id: '3', category: 'dead-export' }),
765
+ makeFinding({ id: '4', category: 'cognitive-complexity' }),
766
+ makeFinding({ id: '5', category: 'cognitive-complexity' }),
767
+ ];
768
+ const result = diverseTopRecommendations(findings, 10);
769
+ expect(result).toHaveLength(4);
770
+ expect(result.filter(f => f.category === 'dead-export')).toHaveLength(2);
771
+ expect(
772
+ result.filter(f => f.category === 'cognitive-complexity')
773
+ ).toHaveLength(2);
774
+ });
775
+
776
+ it('respects total limit', () => {
777
+ const findings = Array.from({ length: 30 }, (_, i) =>
778
+ makeFinding({ id: `${i}`, category: `cat-${i % 10}` })
779
+ );
780
+ const result = diverseTopRecommendations(findings, 5, 2);
781
+ expect(result).toHaveLength(5);
782
+ });
783
+
784
+ it('returns empty for empty input', () => {
785
+ expect(diverseTopRecommendations([], 10)).toHaveLength(0);
786
+ });
787
+
788
+ it('uses maxPerCategory=1 for maximum diversity', () => {
789
+ const findings = [
790
+ makeFinding({ id: '1', category: 'a' }),
791
+ makeFinding({ id: '2', category: 'a' }),
792
+ makeFinding({ id: '3', category: 'b' }),
793
+ makeFinding({ id: '4', category: 'b' }),
794
+ makeFinding({ id: '5', category: 'c' }),
795
+ ];
796
+ const result = diverseTopRecommendations(findings, 10, 1);
797
+ expect(result).toHaveLength(3);
798
+ expect(new Set(result.map(f => f.category)).size).toBe(3);
799
+ });
800
+
801
+ it('returns all when limit exceeds available diverse findings', () => {
802
+ const findings = [
803
+ makeFinding({ id: '1', category: 'a' }),
804
+ makeFinding({ id: '2', category: 'b' }),
805
+ ];
806
+ const result = diverseTopRecommendations(findings, 10, 2);
807
+ expect(result).toHaveLength(2);
808
+ });
809
+
810
+ it('preserves input order, taking first maxPerCategory per category', () => {
811
+ const findings = [
812
+ makeFinding({ id: '1', category: 'a', severity: 'critical' }),
813
+ makeFinding({ id: '2', category: 'a', severity: 'high' }),
814
+ makeFinding({ id: '3', category: 'b', severity: 'medium' }),
815
+ ];
816
+ const result = diverseTopRecommendations(findings, 10, 2);
817
+ expect(result[0].id).toBe('1');
818
+ expect(result[1].id).toBe('2');
819
+ expect(result[2].id).toBe('3');
820
+ });
821
+ });
822
+
823
+ function makeMinimalReport(
824
+ overrides: Partial<FullReport> = {}
825
+ ): FullReport {
826
+ return {
827
+ generatedAt: '2026-01-01T00:00:00.000Z',
828
+ repoRoot: '/test',
829
+ options: {},
830
+ parser: {},
831
+ summary: {
832
+ totalFiles: 10,
833
+ totalFunctions: 50,
834
+ totalFlows: 20,
835
+ totalDependencyFiles: 10,
836
+ totalPackages: 1,
837
+ },
838
+ fileInventory: [],
839
+ duplicateFlows: {},
840
+ dependencyGraph: {
841
+ totalModules: 5,
842
+ totalEdges: 8,
843
+ cycles: [],
844
+ criticalPaths: [],
845
+ rootsCount: 2,
846
+ leavesCount: 1,
847
+ testOnlyModules: [],
848
+ unresolvedEdgeCount: 0,
849
+ } as FullReport['dependencyGraph'],
850
+ dependencyFindings: [],
851
+ agentOutput: {
852
+ totalFindings: 2,
853
+ findingStats: {
854
+ overall: {
855
+ totalFindings: 2,
856
+ severityBreakdown: { critical: 0, high: 1, medium: 1, low: 0, info: 0 },
857
+ },
858
+ },
859
+ topRecommendations: [
860
+ {
861
+ id: 'AST-0001',
862
+ file: 'src/a.ts',
863
+ severity: 'high',
864
+ category: 'cognitive-complexity',
865
+ title: 'High complexity in foo',
866
+ reason: 'too complex',
867
+ },
868
+ ],
869
+ },
870
+ optimizationOpportunities: [],
871
+ optimizationFindings: [
872
+ makeFinding({ severity: 'high', category: 'cognitive-complexity' }),
873
+ makeFinding({ severity: 'medium', category: 'dead-export' }),
874
+ ],
875
+ parseErrors: [],
876
+ ...overrides,
877
+ };
878
+ }
879
+
880
+ describe('generateSummaryMd', () => {
881
+ it('includes report header with generated date and root', () => {
882
+ const md = generateSummaryMd({
883
+ dir: '/tmp/scan',
884
+ report: makeMinimalReport(),
885
+ outputFiles: { summary: 'summary.json' },
886
+ architectureFindings: [],
887
+ codeQualityFindings: [],
888
+ deadCodeFindings: [],
889
+ });
890
+ expect(md).toContain('# Code Quality Scan Report');
891
+ expect(md).toContain('2026-01-01T00:00:00.000Z');
892
+ expect(md).toContain('`/test`');
893
+ });
894
+
895
+ it('includes scan scope metrics from summary', () => {
896
+ const md = generateSummaryMd({
897
+ dir: '/tmp/scan',
898
+ report: makeMinimalReport(),
899
+ outputFiles: { summary: 'summary.json' },
900
+ architectureFindings: [],
901
+ codeQualityFindings: [],
902
+ deadCodeFindings: [],
903
+ });
904
+ expect(md).toContain('Files analyzed | 10');
905
+ expect(md).toContain('Functions | 50');
906
+ expect(md).toContain('Flow nodes | 20');
907
+ });
908
+
909
+ it('includes findings overview severity table', () => {
910
+ const md = generateSummaryMd({
911
+ dir: '/tmp/scan',
912
+ report: makeMinimalReport(),
913
+ outputFiles: { summary: 'summary.json' },
914
+ architectureFindings: [],
915
+ codeQualityFindings: [],
916
+ deadCodeFindings: [],
917
+ });
918
+ expect(md).toContain('## Findings Overview');
919
+ expect(md).toContain('High | 1');
920
+ expect(md).toContain('**Total** | **2**');
921
+ });
922
+
923
+ it('includes health scores section', () => {
924
+ const md = generateSummaryMd({
925
+ dir: '/tmp/scan',
926
+ report: makeMinimalReport(),
927
+ outputFiles: { summary: 'summary.json' },
928
+ architectureFindings: [],
929
+ codeQualityFindings: [],
930
+ deadCodeFindings: [],
931
+ });
932
+ expect(md).toContain('## Health Scores');
933
+ expect(md).toContain('**Overall**');
934
+ });
935
+
936
+ it('includes feature scores section with category rows', () => {
937
+ const md = generateSummaryMd({
938
+ dir: '/tmp/scan',
939
+ report: makeMinimalReport(),
940
+ outputFiles: { summary: 'summary.json' },
941
+ architectureFindings: [],
942
+ codeQualityFindings: [],
943
+ deadCodeFindings: [],
944
+ });
945
+ expect(md).toContain('## Feature Scores');
946
+ expect(md).toContain('## AI + Structure Ratings');
947
+ expect(md).toContain('`cognitive-complexity`');
948
+ expect(md).toContain('`dead-export`');
949
+ });
950
+
951
+ it('includes features filter when activeFeatures is provided', () => {
952
+ const md = generateSummaryMd({
953
+ dir: '/tmp/scan',
954
+ report: makeMinimalReport(),
955
+ outputFiles: { summary: 'summary.json' },
956
+ architectureFindings: [],
957
+ codeQualityFindings: [],
958
+ deadCodeFindings: [],
959
+ activeFeatures: new Set(['cognitive-complexity', 'dead-export']),
960
+ });
961
+ expect(md).toContain('**Features filter**');
962
+ });
963
+
964
+ it('includes scope annotation when scope is provided', () => {
965
+ const md = generateSummaryMd({
966
+ dir: '/tmp/scan',
967
+ report: makeMinimalReport(),
968
+ outputFiles: { summary: 'summary.json' },
969
+ architectureFindings: [],
970
+ codeQualityFindings: [],
971
+ deadCodeFindings: [],
972
+ scope: ['/test/src/foo'],
973
+ root: '/test',
974
+ });
975
+ expect(md).toContain('**Scoped scan**');
976
+ expect(md).toContain('src/foo');
977
+ });
978
+
979
+ it('includes top recommendations from agentOutput', () => {
980
+ const md = generateSummaryMd({
981
+ dir: '/tmp/scan',
982
+ report: makeMinimalReport(),
983
+ outputFiles: { summary: 'summary.json' },
984
+ architectureFindings: [],
985
+ codeQualityFindings: [],
986
+ deadCodeFindings: [],
987
+ });
988
+ expect(md).toContain('## Top Recommendations');
989
+ expect(md).toContain('High complexity in foo');
990
+ });
991
+
992
+ it('includes output files table', () => {
993
+ const md = generateSummaryMd({
994
+ dir: '/tmp/scan',
995
+ report: makeMinimalReport(),
996
+ outputFiles: { summary: 'summary.json', findings: 'findings.json' },
997
+ architectureFindings: [],
998
+ codeQualityFindings: [],
999
+ deadCodeFindings: [],
1000
+ });
1001
+ expect(md).toContain('## Output Files');
1002
+ expect(md).toContain('summary.json');
1003
+ expect(md).toContain('findings.json');
1004
+ });
1005
+
1006
+ it('includes agent instructions section', () => {
1007
+ const md = generateSummaryMd({
1008
+ dir: '/tmp/scan',
1009
+ report: makeMinimalReport(),
1010
+ outputFiles: { summary: 'summary.json', findings: 'findings.json' },
1011
+ architectureFindings: [],
1012
+ codeQualityFindings: [],
1013
+ deadCodeFindings: [],
1014
+ });
1015
+ expect(md).toContain('## Agent Instructions');
1016
+ expect(md).toContain('Validate Before Presenting');
1017
+ expect(md).toContain('localSearchCode');
1018
+ expect(md).toContain('lspGotoDefinition');
1019
+ expect(md).toContain('lspFindReferences');
1020
+ expect(md).toContain('lspCallHierarchy');
1021
+ expect(md).toContain('False Positive Checklist');
1022
+ expect(md).toContain('ast/search.js');
1023
+ });
1024
+
1025
+ it('includes AST triage row when astTrees output exists', () => {
1026
+ const md = generateSummaryMd({
1027
+ dir: '/tmp/scan',
1028
+ report: makeMinimalReport(),
1029
+ outputFiles: { summary: 'summary.json', astTrees: 'ast-trees.txt' },
1030
+ architectureFindings: [],
1031
+ codeQualityFindings: [],
1032
+ deadCodeFindings: [],
1033
+ });
1034
+ expect(md).toContain('ast/tree-search.js');
1035
+ expect(md).toContain('AST triage');
1036
+ });
1037
+
1038
+ it('shows high/critical filter when high severity findings exist', () => {
1039
+ const report = makeMinimalReport();
1040
+ const md = generateSummaryMd({
1041
+ dir: '/tmp/scan',
1042
+ report,
1043
+ outputFiles: { summary: 'summary.json' },
1044
+ architectureFindings: [],
1045
+ codeQualityFindings: [],
1046
+ deadCodeFindings: [],
1047
+ });
1048
+ expect(md).toContain('High/critical findings');
1049
+ });
1050
+
1051
+ it('includes parse errors section when present', () => {
1052
+ const report = makeMinimalReport({
1053
+ parseErrors: [{ file: 'bad.ts', message: 'Unexpected token' }],
1054
+ });
1055
+ const md = generateSummaryMd({
1056
+ dir: '/tmp/scan',
1057
+ report,
1058
+ outputFiles: { summary: 'summary.json' },
1059
+ architectureFindings: [],
1060
+ codeQualityFindings: [],
1061
+ deadCodeFindings: [],
1062
+ });
1063
+ expect(md).toContain('## Parse Errors');
1064
+ expect(md).toContain('bad.ts');
1065
+ });
1066
+ });