octocode-cli 1.2.8 → 1.2.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (282) hide show
  1. package/README.md +42 -35
  2. package/out/octocode-cli.js +36 -11767
  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
@@ -1,8 +1,9 @@
1
1
  import path from 'node:path';
2
2
 
3
3
  import { ALL_CATEGORIES, DEFAULT_OPTS, PILLAR_CATEGORIES } from '../types/index.js';
4
+ import { OptionsError, resolveExcludeToFeatures } from './create-options.js';
4
5
 
5
- import type { AnalysisOptions } from '../types/index.js';
6
+ import type { AnalysisOptions, Thresholds } from '../types/index.js';
6
7
 
7
8
  function parseNumeric(raw: string | undefined, fallback: number): number {
8
9
  const n = parseInt(raw ?? '', 10);
@@ -14,6 +15,24 @@ function parseDecimal(raw: string | undefined, fallback: number): number {
14
15
  return Number.isNaN(n) ? fallback : n;
15
16
  }
16
17
 
18
+ function setIntOpt(
19
+ target: Record<string, number>,
20
+ key: string,
21
+ raw: string,
22
+ defaults: Record<string, number>
23
+ ): void {
24
+ target[key] = parseNumeric(raw, defaults[key]);
25
+ }
26
+
27
+ function setFloatOpt(
28
+ target: Record<string, number>,
29
+ key: string,
30
+ raw: string,
31
+ defaults: Record<string, number>
32
+ ): void {
33
+ target[key] = parseDecimal(raw, defaults[key]);
34
+ }
35
+
17
36
  function resolveCategories(val: string, flagName: string): Set<string> {
18
37
  const tokens = val
19
38
  .split(',')
@@ -39,22 +58,35 @@ function parseScope(
39
58
  val: string,
40
59
  root: string
41
60
  ): { paths: string[]; symbols: Map<string, string[]> } {
61
+ const splitScopeToken = (
62
+ token: string
63
+ ): { filePath: string; symbolName: string | null } => {
64
+ const colonIdx = token.lastIndexOf(':');
65
+ if (colonIdx <= 0 || colonIdx === token.length - 1) {
66
+ return { filePath: token, symbolName: null };
67
+ }
68
+ const symbolName = token.substring(colonIdx + 1);
69
+ if (symbolName.includes('/') || symbolName.includes('\\')) {
70
+ return { filePath: token, symbolName: null };
71
+ }
72
+ return {
73
+ filePath: token.substring(0, colonIdx),
74
+ symbolName,
75
+ };
76
+ };
77
+
42
78
  const paths: string[] = [];
43
79
  const symbols = new Map<string, string[]>();
44
80
  for (const token of val
45
81
  .split(',')
46
82
  .map(s => s.trim())
47
83
  .filter(Boolean)) {
48
- const colonIdx = token.lastIndexOf(':');
49
- if (colonIdx > 0 && !token.substring(0, colonIdx).includes(':')) {
50
- const filePart = token.substring(0, colonIdx);
51
- const symbolPart = token.substring(colonIdx + 1);
52
- const absFile = path.resolve(root, filePart);
53
- paths.push(absFile);
84
+ const { filePath, symbolName } = splitScopeToken(token);
85
+ const absFile = path.resolve(root, filePath);
86
+ paths.push(absFile);
87
+ if (symbolName) {
54
88
  if (!symbols.has(absFile)) symbols.set(absFile, []);
55
- symbols.get(absFile)!.push(symbolPart);
56
- } else {
57
- paths.push(path.resolve(root, token));
89
+ symbols.get(absFile)!.push(symbolName);
58
90
  }
59
91
  }
60
92
  return { paths, symbols };
@@ -103,15 +135,23 @@ const BOOL_FLAGS: Record<
103
135
  o.includeTests = true;
104
136
  o.semantic = true;
105
137
  },
138
+ '--save-baseline': o => {
139
+ o.saveBaseline = true;
140
+ },
106
141
  };
107
142
 
108
- const INT_FLAGS: Record<string, keyof AnalysisOptions> = {
143
+ const CORE_INT_FLAGS: Record<string, keyof AnalysisOptions> = {
109
144
  '--findings-limit': 'findingsLimit',
145
+ '--deep-link-topn': 'deepLinkTopN',
146
+ '--tree-depth': 'treeDepth',
147
+ '--max-recs-per-category': 'maxRecsPerCategory',
148
+ '--focus-depth': 'focusDepth',
149
+ };
150
+
151
+ const THRESHOLD_INT_FLAGS: Record<string, keyof Thresholds> = {
110
152
  '--min-function-statements': 'minFunctionStatements',
111
153
  '--min-flow-statements': 'minFlowStatements',
112
154
  '--critical-complexity-threshold': 'criticalComplexityThreshold',
113
- '--deep-link-topn': 'deepLinkTopN',
114
- '--tree-depth': 'treeDepth',
115
155
  '--coupling-threshold': 'couplingThreshold',
116
156
  '--fan-in-threshold': 'fanInThreshold',
117
157
  '--fan-out-threshold': 'fanOutThreshold',
@@ -126,14 +166,17 @@ const INT_FLAGS: Record<string, keyof AnalysisOptions> = {
126
166
  '--maintainability-index-threshold': 'maintainabilityIndexThreshold',
127
167
  '--any-threshold': 'anyThreshold',
128
168
  '--flow-dup-threshold': 'flowDupThreshold',
129
- '--max-recs-per-category': 'maxRecsPerCategory',
130
169
  '--override-chain-threshold': 'overrideChainThreshold',
131
170
  '--shotgun-threshold': 'shotgunThreshold',
132
171
  '--secret-min-length': 'secretMinLength',
133
172
  '--mock-threshold': 'mockThreshold',
173
+ '--deep-nesting-threshold': 'deepNestingThreshold',
174
+ '--multiple-return-threshold': 'multipleReturnThreshold',
175
+ '--magic-string-min-occurrences': 'magicStringMinOccurrences',
176
+ '--boolean-param-threshold': 'booleanParamThreshold',
134
177
  };
135
178
 
136
- const FLOAT_FLAGS: Record<string, keyof AnalysisOptions> = {
179
+ const THRESHOLD_FLOAT_FLAGS: Record<string, keyof Thresholds> = {
137
180
  '--secret-entropy-threshold': 'secretEntropyThreshold',
138
181
  '--similarity-threshold': 'similarityThreshold',
139
182
  '--sdp-min-delta': 'sdpMinDelta',
@@ -161,7 +204,20 @@ const SPECIAL_FLAGS: Record<string, FlagHandler> = {
161
204
  return i + 1;
162
205
  },
163
206
  '--layer-order': (opts, argv, i) => {
164
- opts.layerOrder = argv[i + 1].split(',').map(s => s.trim());
207
+ opts.thresholds.layerOrder = argv[i + 1].split(',').map(s => s.trim());
208
+ return i + 1;
209
+ },
210
+ '--reporter': (opts, argv, i) => {
211
+ const next = argv[i + 1];
212
+ if (!['default', 'compact', 'github-actions'].includes(next)) {
213
+ console.error(`Unsupported reporter: ${next}. Use default|compact|github-actions`);
214
+ process.exit(1);
215
+ }
216
+ opts.reporter = next as AnalysisOptions['reporter'];
217
+ return i + 1;
218
+ },
219
+ '--config': (opts, argv, i) => {
220
+ opts.configFile = argv[i + 1];
165
221
  return i + 1;
166
222
  },
167
223
  '--help': () => {
@@ -175,7 +231,7 @@ const SPECIAL_FLAGS: Record<string, FlagHandler> = {
175
231
  };
176
232
 
177
233
  export function parseArgs(argv: string[]): AnalysisOptions {
178
- const opts: AnalysisOptions = { ...DEFAULT_OPTS };
234
+ const opts: AnalysisOptions = { ...DEFAULT_OPTS, thresholds: { ...DEFAULT_OPTS.thresholds } };
179
235
  let excludeSet: Set<string> | null = null;
180
236
 
181
237
  for (let i = 0; i < argv.length; i++) {
@@ -186,17 +242,33 @@ export function parseArgs(argv: string[]): AnalysisOptions {
186
242
  continue;
187
243
  }
188
244
 
189
- if (INT_FLAGS[arg]) {
190
- const key = INT_FLAGS[arg];
191
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
192
- (opts as any)[key] = parseNumeric(argv[++i], (DEFAULT_OPTS as any)[key]);
245
+ if (CORE_INT_FLAGS[arg]) {
246
+ const key = CORE_INT_FLAGS[arg];
247
+ setIntOpt(
248
+ opts as unknown as Record<string, number>,
249
+ key, argv[++i],
250
+ DEFAULT_OPTS as unknown as Record<string, number>
251
+ );
252
+ continue;
253
+ }
254
+
255
+ if (THRESHOLD_INT_FLAGS[arg]) {
256
+ const key = THRESHOLD_INT_FLAGS[arg];
257
+ setIntOpt(
258
+ opts.thresholds as unknown as Record<string, number>,
259
+ key, argv[++i],
260
+ DEFAULT_OPTS.thresholds as unknown as Record<string, number>
261
+ );
193
262
  continue;
194
263
  }
195
264
 
196
- if (FLOAT_FLAGS[arg]) {
197
- const key = FLOAT_FLAGS[arg];
198
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
199
- (opts as any)[key] = parseDecimal(argv[++i], (DEFAULT_OPTS as any)[key]);
265
+ if (THRESHOLD_FLOAT_FLAGS[arg]) {
266
+ const key = THRESHOLD_FLOAT_FLAGS[arg];
267
+ setFloatOpt(
268
+ opts.thresholds as unknown as Record<string, number>,
269
+ key, argv[++i],
270
+ DEFAULT_OPTS.thresholds as unknown as Record<string, number>
271
+ );
200
272
  continue;
201
273
  }
202
274
 
@@ -211,7 +283,9 @@ export function parseArgs(argv: string[]): AnalysisOptions {
211
283
  }
212
284
 
213
285
  if (arg === '--scope' || arg.startsWith('--scope=')) {
214
- const val = arg.includes('=') ? arg.split('=')[1] : argv[++i];
286
+ const val = arg.startsWith('--scope=')
287
+ ? arg.slice('--scope='.length)
288
+ : argv[++i];
215
289
  const { paths, symbols } = parseScope(val, opts.root);
216
290
  opts.scope = paths;
217
291
  if (symbols.size > 0) opts.scopeSymbols = symbols;
@@ -233,20 +307,58 @@ export function parseArgs(argv: string[]): AnalysisOptions {
233
307
  excludeSet = resolveCategories(val, 'exclude');
234
308
  continue;
235
309
  }
310
+
311
+ if (arg === '--affected' || arg.startsWith('--affected=')) {
312
+ opts.affected = arg.startsWith('--affected=')
313
+ ? arg.slice('--affected='.length)
314
+ : (argv[i + 1] && !argv[i + 1].startsWith('--') ? argv[++i] : 'HEAD');
315
+ continue;
316
+ }
317
+
318
+ if (arg === '--ignore-known' || arg.startsWith('--ignore-known=')) {
319
+ opts.ignoreKnown = arg.startsWith('--ignore-known=')
320
+ ? arg.slice('--ignore-known='.length)
321
+ : (argv[i + 1] && !argv[i + 1].startsWith('--') ? argv[++i] : '.octocode/baseline.json');
322
+ continue;
323
+ }
324
+
325
+ if (arg === '--focus' || arg.startsWith('--focus=')) {
326
+ opts.focus = arg.startsWith('--focus=')
327
+ ? arg.slice('--focus='.length)
328
+ : argv[++i];
329
+ continue;
330
+ }
331
+
332
+ if (arg === '--collapse' || arg.startsWith('--collapse=')) {
333
+ const val = arg.startsWith('--collapse=')
334
+ ? arg.slice('--collapse='.length)
335
+ : argv[++i];
336
+ opts.collapse = parseNumeric(val, 2);
337
+ continue;
338
+ }
339
+
340
+ if (arg === '--at-least' || arg.startsWith('--at-least=')) {
341
+ const val = arg.startsWith('--at-least=')
342
+ ? arg.slice('--at-least='.length)
343
+ : argv[++i];
344
+ opts.atLeast = parseNumeric(val, 0);
345
+ continue;
346
+ }
347
+
348
+ if (arg.startsWith('--')) {
349
+ console.warn(`Warning: unknown flag "${arg}" — ignored.`);
350
+ }
236
351
  }
237
352
 
238
353
  opts.packageRoot = path.join(opts.root, 'packages');
239
354
 
240
355
  if (opts.features !== null && excludeSet !== null) {
241
- console.error(
356
+ throw new OptionsError(
242
357
  '--features and --exclude are mutually exclusive. Use one or the other.'
243
358
  );
244
- process.exit(1);
245
359
  }
246
360
  if (excludeSet !== null) {
247
- opts.features = new Set(
248
- [...ALL_CATEGORIES].filter(c => !excludeSet!.has(c))
249
- );
361
+ opts.features = resolveExcludeToFeatures(excludeSet);
250
362
  }
251
363
 
252
364
  if (opts.features !== null) {
@@ -259,10 +371,9 @@ export function parseArgs(argv: string[]): AnalysisOptions {
259
371
  return opts;
260
372
  }
261
373
 
262
- export function printHelp(): void {
263
- console.log(`
374
+ export const HELP_TEXT = `
264
375
  Usage:
265
- node scripts/index.js [options]
376
+ node scripts/run.js [options]
266
377
 
267
378
  Options:
268
379
  --root <path> Analyze a different repo root (default: cwd)
@@ -320,7 +431,7 @@ Options:
320
431
  Examples: --exclude=architecture
321
432
  --exclude=dead-export,unsafe-any
322
433
  --semantic Enable semantic analysis phase (TypeChecker + LanguageService).
323
- Adds 14 categories: over-abstraction, concrete-dependency,
434
+ Adds 12 categories: over-abstraction, concrete-dependency,
324
435
  circular-type-dependency, unused-parameter,
325
436
  deep-override-chain, interface-compliance, unused-import,
326
437
  orphan-implementation, shotgun-surgery, move-to-caller,
@@ -332,6 +443,11 @@ Options:
332
443
  --secret-entropy-threshold N Shannon entropy threshold for secret detection (default 4.5)
333
444
  --secret-min-length N Min string length for entropy-based secret detection (default 20)
334
445
  --similarity-threshold N Jaccard similarity threshold for near-clone detection (default 0.85)
446
+ --deep-nesting-threshold N Max branch/loop nesting depth before flagging (default 5)
447
+ --multiple-return-threshold N Max return/throw paths per function before flagging (default 6)
448
+ --magic-string-min-occurrences N
449
+ Min repeated string comparisons to flag as magic string (default 3)
450
+ --boolean-param-threshold N Min boolean params per function to flag as cluster (default 3)
335
451
  --mock-threshold N Max mock/spy calls per test file (default 10)
336
452
  --no-diversify Disable category-aware diversification when truncating findings.
337
453
  By default, --findings-limit interleaves categories so the
@@ -339,6 +455,34 @@ Options:
339
455
  --no-cache Disable incremental cache; re-parse all files
340
456
  --clear-cache Delete the analysis cache and exit (no scan)
341
457
  --all Enable all features: --include-tests --semantic
458
+
459
+ --affected [revision] Scope to files changed since git revision (default: HEAD) plus
460
+ their transitive dependents. Like dep-cruiser's --affected flag.
461
+ Examples: --affected
462
+ --affected HEAD~3
463
+ --affected main
464
+ --save-baseline Save current findings to .octocode/baseline.json for future
465
+ comparison. Use with --ignore-known for progressive adoption.
466
+ --ignore-known [file] Suppress findings matching a baseline file (default:
467
+ .octocode/baseline.json). Findings are matched by (category, file).
468
+ --reporter <format> Output format: default|compact|github-actions (default: default)
469
+ compact: one-line per finding for terminal/CI logs
470
+ github-actions: ::warning annotations for GitHub Actions
471
+ --focus <module> Show only this module and its neighbors in the dependency graph.
472
+ Requires --graph. Use with --focus-depth to control neighbor hops.
473
+ Examples: --focus src/session.ts
474
+ --focus=src/session.ts
475
+ --focus packages/octocode-mcp/src/tools
476
+ --focus-depth N Neighbor depth for --focus (default 1). 2 = friends-of-friends.
477
+ --collapse N Collapse graph nodes to folder depth N. Reduces large graphs to
478
+ high-level architecture view. Example: --collapse 2
479
+ --at-least N Fail (exit 1) if health score drops below N (0-100). Use in CI
480
+ to enforce a quality floor. Example: --at-least 60
481
+ --config <file> Path to config file. Also auto-discovers .octocode-scan.json,
482
+ .octocode-scan.jsonc, or package.json#octocode in the project root.
342
483
  --help Show this message
343
- `);
484
+ `;
485
+
486
+ export function printHelp(): void {
487
+ console.log(HELP_TEXT);
344
488
  }
@@ -0,0 +1,264 @@
1
+ import fs from 'node:fs';
2
+ import os from 'node:os';
3
+ import path from 'node:path';
4
+
5
+ import { afterEach, describe, expect, it } from 'vitest';
6
+
7
+ import { loadConfigFile, mergeConfigIntoDefaults } from './config-loader.js';
8
+ import { DEFAULT_OPTS } from '../types/index.js';
9
+
10
+ describe('config-loader', () => {
11
+ const tmpDirs: string[] = [];
12
+
13
+ function makeTmpDir(): string {
14
+ const d = fs.mkdtempSync(path.join(os.tmpdir(), 'config-test-'));
15
+ tmpDirs.push(d);
16
+ return d;
17
+ }
18
+
19
+ afterEach(() => {
20
+ for (const d of tmpDirs) {
21
+ fs.rmSync(d, { recursive: true, force: true });
22
+ }
23
+ tmpDirs.length = 0;
24
+ });
25
+
26
+ describe('loadConfigFile', () => {
27
+ it('loads explicit config file', () => {
28
+ const root = makeTmpDir();
29
+ const cfgPath = path.join(root, 'my-config.json');
30
+ fs.writeFileSync(cfgPath, JSON.stringify({ graph: true, semantic: true }));
31
+
32
+ const config = loadConfigFile(root, cfgPath);
33
+ expect(config).toBeDefined();
34
+ expect(config!.graph).toBe(true);
35
+ expect(config!.semantic).toBe(true);
36
+ });
37
+
38
+ it('auto-discovers .octocode-scan.json', () => {
39
+ const root = makeTmpDir();
40
+ fs.writeFileSync(
41
+ path.join(root, '.octocode-scan.json'),
42
+ JSON.stringify({ flow: true })
43
+ );
44
+
45
+ const config = loadConfigFile(root, null);
46
+ expect(config).toBeDefined();
47
+ expect(config!.flow).toBe(true);
48
+ });
49
+
50
+ it('auto-discovers .octocode-scan.jsonc', () => {
51
+ const root = makeTmpDir();
52
+ fs.writeFileSync(
53
+ path.join(root, '.octocode-scan.jsonc'),
54
+ '// comment\n{ "graph": true }\n'
55
+ );
56
+
57
+ const config = loadConfigFile(root, null);
58
+ expect(config).toBeDefined();
59
+ expect(config!.graph).toBe(true);
60
+ });
61
+
62
+ it('reads from package.json#octocode', () => {
63
+ const root = makeTmpDir();
64
+ fs.writeFileSync(
65
+ path.join(root, 'package.json'),
66
+ JSON.stringify({ name: 'test', octocode: { includeTests: true } })
67
+ );
68
+
69
+ const config = loadConfigFile(root, null);
70
+ expect(config).toBeDefined();
71
+ expect(config!.includeTests).toBe(true);
72
+ });
73
+
74
+ it('returns null when no config found', () => {
75
+ const root = makeTmpDir();
76
+ expect(loadConfigFile(root, null)).toBeNull();
77
+ });
78
+
79
+ it('converts kebab-case keys to camelCase', () => {
80
+ const root = makeTmpDir();
81
+ fs.writeFileSync(
82
+ path.join(root, '.octocode-scan.json'),
83
+ JSON.stringify({ 'include-tests': true, 'graph-advanced': true })
84
+ );
85
+
86
+ const config = loadConfigFile(root, null);
87
+ expect(config!.includeTests).toBe(true);
88
+ expect(config!.graphAdvanced).toBe(true);
89
+ });
90
+
91
+ it('prefers .octocode-scan.json over package.json', () => {
92
+ const root = makeTmpDir();
93
+ fs.writeFileSync(
94
+ path.join(root, '.octocode-scan.json'),
95
+ JSON.stringify({ graph: true })
96
+ );
97
+ fs.writeFileSync(
98
+ path.join(root, 'package.json'),
99
+ JSON.stringify({ name: 'x', octocode: { graph: false, flow: true } })
100
+ );
101
+
102
+ const config = loadConfigFile(root, null);
103
+ expect(config!.graph).toBe(true);
104
+ expect(config!.flow).toBeUndefined();
105
+ });
106
+
107
+ it('converts features string to Set', () => {
108
+ const root = makeTmpDir();
109
+ fs.writeFileSync(
110
+ path.join(root, '.octocode-scan.json'),
111
+ JSON.stringify({ features: 'architecture, dead-code' })
112
+ );
113
+
114
+ const config = loadConfigFile(root, null);
115
+ expect(config!.features).toBeInstanceOf(Set);
116
+ expect((config!.features as Set<string>).has('architecture')).toBe(true);
117
+ expect((config!.features as Set<string>).has('dead-code')).toBe(true);
118
+ });
119
+
120
+ it('converts scope string to array', () => {
121
+ const root = makeTmpDir();
122
+ fs.writeFileSync(
123
+ path.join(root, '.octocode-scan.json'),
124
+ JSON.stringify({ scope: 'packages/foo,packages/bar' })
125
+ );
126
+
127
+ const config = loadConfigFile(root, null);
128
+ expect(config!.scope).toEqual(['packages/foo', 'packages/bar']);
129
+ });
130
+
131
+ it('converts ignoreDirs array to Set', () => {
132
+ const root = makeTmpDir();
133
+ fs.writeFileSync(
134
+ path.join(root, '.octocode-scan.json'),
135
+ JSON.stringify({ 'ignore-dirs': ['vendor', 'generated'] })
136
+ );
137
+
138
+ const config = loadConfigFile(root, null);
139
+ const dirs = config!.ignoreDirs as Set<string>;
140
+ expect(dirs).toBeInstanceOf(Set);
141
+ expect(dirs.has('vendor')).toBe(true);
142
+ expect(dirs.has('generated')).toBe(true);
143
+ });
144
+
145
+ it('returns null for invalid JSON', () => {
146
+ const root = makeTmpDir();
147
+ fs.writeFileSync(
148
+ path.join(root, '.octocode-scan.json'),
149
+ 'not { valid json !!!'
150
+ );
151
+ expect(loadConfigFile(root, null)).toBeNull();
152
+ });
153
+
154
+ it('resolves relative explicit path against root', () => {
155
+ const root = makeTmpDir();
156
+ const cfgPath = path.join(root, 'configs', 'scan.json');
157
+ fs.mkdirSync(path.dirname(cfgPath), { recursive: true });
158
+ fs.writeFileSync(cfgPath, JSON.stringify({ semantic: true }));
159
+
160
+ const config = loadConfigFile(root, 'configs/scan.json');
161
+ expect(config).toBeDefined();
162
+ expect(config!.semantic).toBe(true);
163
+ });
164
+
165
+ it('strips single-line comments in JSONC', () => {
166
+ const root = makeTmpDir();
167
+ fs.writeFileSync(
168
+ path.join(root, '.octocode-scan.json'),
169
+ `{
170
+ // Enable graph
171
+ "graph": true,
172
+ "flow": false // Not yet
173
+ }`
174
+ );
175
+ const config = loadConfigFile(root, null);
176
+ expect(config!.graph).toBe(true);
177
+ expect(config!.flow).toBe(false);
178
+ });
179
+
180
+ it('strips block comments in JSONC', () => {
181
+ const root = makeTmpDir();
182
+ fs.writeFileSync(
183
+ path.join(root, '.octocode-scan.json'),
184
+ `{
185
+ /* This enables the semantic phase */
186
+ "semantic": true
187
+ }`
188
+ );
189
+ const config = loadConfigFile(root, null);
190
+ expect(config!.semantic).toBe(true);
191
+ });
192
+
193
+ it('passes through threshold objects', () => {
194
+ const root = makeTmpDir();
195
+ fs.writeFileSync(
196
+ path.join(root, '.octocode-scan.json'),
197
+ JSON.stringify({ thresholds: { minFunctionStatements: 10 } })
198
+ );
199
+
200
+ const config = loadConfigFile(root, null);
201
+ const t = config!.thresholds as Record<string, number>;
202
+ expect(t.minFunctionStatements).toBe(10);
203
+ });
204
+
205
+ it('ignores package.json without octocode key', () => {
206
+ const root = makeTmpDir();
207
+ fs.writeFileSync(
208
+ path.join(root, 'package.json'),
209
+ JSON.stringify({ name: 'test', version: '1.0.0' })
210
+ );
211
+ expect(loadConfigFile(root, null)).toBeNull();
212
+ });
213
+ });
214
+
215
+ describe('mergeConfigIntoDefaults', () => {
216
+ it('config values override defaults', () => {
217
+ const config = { graph: true, semantic: true };
218
+ const cliArgs = { ...DEFAULT_OPTS };
219
+
220
+ const result = mergeConfigIntoDefaults(DEFAULT_OPTS, config, cliArgs);
221
+ expect(result.graph).toBe(true);
222
+ expect(result.semantic).toBe(true);
223
+ });
224
+
225
+ it('CLI args override config when they differ from defaults', () => {
226
+ const config = { graph: true, semantic: true };
227
+ const cliArgs = { ...DEFAULT_OPTS, findingsLimit: 50 };
228
+
229
+ const result = mergeConfigIntoDefaults(DEFAULT_OPTS, config, cliArgs);
230
+ expect(result.graph).toBe(true);
231
+ expect(result.semantic).toBe(true);
232
+ expect(result.findingsLimit).toBe(50);
233
+ });
234
+
235
+ it('merges threshold overrides', () => {
236
+ const config = { thresholds: { minFunctionStatements: 10 } };
237
+ const cliArgs = { ...DEFAULT_OPTS };
238
+
239
+ const result = mergeConfigIntoDefaults(DEFAULT_OPTS, config, cliArgs);
240
+ expect(result.thresholds.minFunctionStatements).toBe(10);
241
+ expect(result.thresholds.minFlowStatements).toBe(
242
+ DEFAULT_OPTS.thresholds.minFlowStatements
243
+ );
244
+ });
245
+
246
+ it('preserves defaults when config and CLI are empty', () => {
247
+ const result = mergeConfigIntoDefaults(
248
+ DEFAULT_OPTS,
249
+ {},
250
+ { ...DEFAULT_OPTS }
251
+ );
252
+ expect(result).toEqual(DEFAULT_OPTS);
253
+ });
254
+
255
+ it('config does not override root or packageRoot', () => {
256
+ const config = { json: true };
257
+ const cliArgs = { ...DEFAULT_OPTS };
258
+
259
+ const result = mergeConfigIntoDefaults(DEFAULT_OPTS, config, cliArgs);
260
+ expect(result.root).toBe(DEFAULT_OPTS.root);
261
+ expect(result.json).toBe(true);
262
+ });
263
+ });
264
+ });