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
@@ -0,0 +1,180 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+
3
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
4
+ import express from 'express';
5
+ import request from 'supertest';
6
+ import { errorHandler } from '../../middleware/errorHandler.js';
7
+
8
+ vi.mock('../../mcpCache.js', () => ({
9
+ getMcpContent: vi.fn().mockReturnValue({
10
+ tools: {},
11
+ prompts: {
12
+ research: {
13
+ name: 'Research',
14
+ description: 'Start a code research session',
15
+ args: [
16
+ { name: 'goal', description: 'The research goal', required: true },
17
+ { name: 'context', description: 'Additional context', required: false },
18
+ ],
19
+ content: 'You are a code research agent...',
20
+ },
21
+ research_local: {
22
+ name: 'Research Local',
23
+ description: 'Research local codebase',
24
+ args: [{ name: 'goal', description: 'What to investigate', required: true }],
25
+ content: 'Investigate the local codebase...',
26
+ },
27
+ plan: {
28
+ name: 'Plan',
29
+ description: 'Plan an implementation',
30
+ args: [],
31
+ content: 'Plan the implementation steps...',
32
+ },
33
+ },
34
+ instructions: 'Test',
35
+ baseHints: [],
36
+ genericErrorHints: [],
37
+ }),
38
+ initializeMcpContent: vi.fn().mockResolvedValue({}),
39
+ isMcpInitialized: vi.fn().mockReturnValue(true),
40
+ }));
41
+
42
+ vi.mock('../../index.js', () => ({
43
+ logPromptCall: vi.fn().mockResolvedValue(undefined),
44
+ }));
45
+
46
+ vi.mock('../../utils/asyncTimeout.js', () => ({
47
+ fireAndForgetWithTimeout: vi.fn(),
48
+ }));
49
+
50
+ import { promptsRoutes } from '../../routes/prompts.js';
51
+
52
+ function createApp(): any {
53
+ const app = express();
54
+ app.use(express.json());
55
+ app.use('/prompts', promptsRoutes);
56
+ app.use(errorHandler);
57
+ return app;
58
+ }
59
+
60
+ describe('Prompts Routes', () => {
61
+ let app: any;
62
+
63
+ beforeEach(() => {
64
+ app = createApp();
65
+ vi.clearAllMocks();
66
+ });
67
+
68
+ describe('GET /prompts/list', () => {
69
+ it('returns 200 with all prompts', async () => {
70
+ const res = await request(app).get('/prompts/list');
71
+ expect(res.status).toBe(200);
72
+ expect(res.body.success).toBe(true);
73
+ });
74
+
75
+ it('returns correct number of prompts', async () => {
76
+ const res = await request(app).get('/prompts/list');
77
+ expect(res.body.data.prompts).toHaveLength(3);
78
+ expect(res.body.data.totalCount).toBe(3);
79
+ });
80
+
81
+ it('includes version', async () => {
82
+ const res = await request(app).get('/prompts/list');
83
+ expect(res.body.data).toHaveProperty('version');
84
+ });
85
+
86
+ it('each prompt has name and description', async () => {
87
+ const res = await request(app).get('/prompts/list');
88
+ for (const prompt of res.body.data.prompts) {
89
+ expect(prompt).toHaveProperty('name');
90
+ expect(prompt).toHaveProperty('description');
91
+ }
92
+ });
93
+
94
+ it('uses key name not display name', async () => {
95
+ const res = await request(app).get('/prompts/list');
96
+ const names = res.body.data.prompts.map((p: any) => p.name);
97
+ expect(names).toContain('research');
98
+ expect(names).toContain('research_local');
99
+ expect(names).toContain('plan');
100
+ });
101
+
102
+ it('includes arguments when present', async () => {
103
+ const res = await request(app).get('/prompts/list');
104
+ const research = res.body.data.prompts.find((p: any) => p.name === 'research');
105
+ expect(research.arguments).toHaveLength(2);
106
+ expect(research.arguments[0]).toEqual({
107
+ name: 'goal',
108
+ description: 'The research goal',
109
+ required: true,
110
+ });
111
+ });
112
+
113
+ it('includes hints', async () => {
114
+ const res = await request(app).get('/prompts/list');
115
+ expect(res.body.hints).toBeDefined();
116
+ expect(res.body.hints[0]).toContain('/prompts/info/');
117
+ });
118
+ });
119
+
120
+ describe('GET /prompts/info/:promptName', () => {
121
+ it('returns prompt details for valid prompt', async () => {
122
+ const res = await request(app).get('/prompts/info/research');
123
+ expect(res.status).toBe(200);
124
+ expect(res.body.success).toBe(true);
125
+ expect(res.body.data.name).toBe('Research');
126
+ expect(res.body.data.description).toBe('Start a code research session');
127
+ });
128
+
129
+ it('includes prompt content', async () => {
130
+ const res = await request(app).get('/prompts/info/research');
131
+ expect(res.body.data.content).toBe('You are a code research agent...');
132
+ });
133
+
134
+ it('includes arguments with required field', async () => {
135
+ const res = await request(app).get('/prompts/info/research');
136
+ expect(res.body.data.arguments).toHaveLength(2);
137
+ expect(res.body.data.arguments[0].required).toBe(true);
138
+ expect(res.body.data.arguments[1].required).toBe(false);
139
+ });
140
+
141
+ it('returns prompt with no arguments', async () => {
142
+ const res = await request(app).get('/prompts/info/plan');
143
+ expect(res.status).toBe(200);
144
+ expect(res.body.data.name).toBe('Plan');
145
+ });
146
+
147
+ it('returns 404 for unknown prompt', async () => {
148
+ const res = await request(app).get('/prompts/info/nonExistent');
149
+ expect(res.status).toBe(404);
150
+ expect(res.body.success).toBe(false);
151
+ });
152
+
153
+ it('provides helpful hints for unknown prompt', async () => {
154
+ const res = await request(app).get('/prompts/info/badPrompt');
155
+ expect(res.body.hints).toBeDefined();
156
+ expect(res.body.hints.some((h: string) => h.includes('badPrompt'))).toBe(true);
157
+ expect(res.body.hints.some((h: string) => h.includes('Available prompts'))).toBe(true);
158
+ expect(res.body.hints.some((h: string) => h.includes('/prompts/list'))).toBe(true);
159
+ });
160
+
161
+ it('logs prompt call for telemetry', async () => {
162
+ await request(app).get('/prompts/info/research');
163
+ const { fireAndForgetWithTimeout } = await import('../../utils/asyncTimeout.js');
164
+ expect(fireAndForgetWithTimeout).toHaveBeenCalled();
165
+ });
166
+ });
167
+
168
+ describe('Readiness gate', () => {
169
+ it('returns 503 when not initialized', async () => {
170
+ const { isMcpInitialized } = await import('../../mcpCache.js');
171
+ vi.mocked(isMcpInitialized).mockReturnValue(false);
172
+
173
+ const res = await request(app).get('/prompts/list');
174
+ expect(res.status).toBe(503);
175
+ expect(res.body.error.code).toBe('SERVER_INITIALIZING');
176
+
177
+ vi.mocked(isMcpInitialized).mockReturnValue(true);
178
+ });
179
+ });
180
+ });
@@ -0,0 +1,221 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+
3
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
4
+ import request from 'supertest';
5
+
6
+ vi.mock('../../mcpCache.js', () => ({
7
+ getMcpContent: vi.fn().mockReturnValue({
8
+ tools: { localSearchCode: { name: 'localSearchCode', description: 'Search', schema: {}, hints: { hasResults: [], empty: [] } } },
9
+ prompts: { research: { name: 'research', description: 'Research', args: [], content: 'test' } },
10
+ instructions: 'Test instructions',
11
+ baseHints: [],
12
+ genericErrorHints: [],
13
+ baseSchema: {},
14
+ }),
15
+ initializeMcpContent: vi.fn().mockResolvedValue({}),
16
+ isMcpInitialized: vi.fn().mockReturnValue(true),
17
+ }));
18
+
19
+ vi.mock('../../index.js', () => {
20
+ const r = { content: [{ type: 'text', text: 'results:\n - status: hasResults\n data:\n files: []\n totalMatches: 0' }] };
21
+ return {
22
+ initializeProviders: vi.fn().mockResolvedValue(undefined),
23
+ initializeSession: vi.fn(),
24
+ logSessionInit: vi.fn().mockResolvedValue(undefined),
25
+ logToolCall: vi.fn().mockResolvedValue(undefined),
26
+ logPromptCall: vi.fn().mockResolvedValue(undefined),
27
+ localSearchCode: vi.fn().mockResolvedValue(r),
28
+ localGetFileContent: vi.fn().mockResolvedValue(r),
29
+ localFindFiles: vi.fn().mockResolvedValue(r),
30
+ localViewStructure: vi.fn().mockResolvedValue(r),
31
+ githubSearchCode: vi.fn().mockResolvedValue(r),
32
+ githubGetFileContent: vi.fn().mockResolvedValue(r),
33
+ githubViewRepoStructure: vi.fn().mockResolvedValue(r),
34
+ githubSearchRepositories: vi.fn().mockResolvedValue(r),
35
+ githubSearchPullRequests: vi.fn().mockResolvedValue(r),
36
+ lspGotoDefinition: vi.fn().mockResolvedValue(r),
37
+ lspFindReferences: vi.fn().mockResolvedValue(r),
38
+ lspCallHierarchy: vi.fn().mockResolvedValue(r),
39
+ packageSearch: vi.fn().mockResolvedValue(r),
40
+ };
41
+ });
42
+
43
+ vi.mock('../../utils/logger.js', () => ({
44
+ initializeLogger: vi.fn(),
45
+ getLogsPath: vi.fn().mockReturnValue('/tmp/logs'),
46
+ logToolCall: vi.fn(),
47
+ logError: vi.fn(),
48
+ logWarn: vi.fn(),
49
+ sanitizeQueryParams: vi.fn().mockReturnValue({}),
50
+ }));
51
+
52
+ vi.mock('../../utils/circuitBreaker.js', () => ({
53
+ getAllCircuitStates: vi.fn().mockReturnValue({}),
54
+ clearAllCircuits: vi.fn(),
55
+ stopCircuitCleanup: vi.fn(),
56
+ configureCircuit: vi.fn(),
57
+ withCircuitBreaker: vi.fn((_name: string, fn: () => any) => fn()),
58
+ }));
59
+
60
+ vi.mock('../../utils/resilience.js', () => ({
61
+ withGitHubResilience: vi.fn(async (fn: () => any) => fn()),
62
+ withLocalResilience: vi.fn(async (fn: () => any) => fn()),
63
+ withLspResilience: vi.fn(async (fn: () => any) => fn()),
64
+ withPackageResilience: vi.fn(async (fn: () => any) => fn()),
65
+ }));
66
+
67
+ vi.mock('../../utils/asyncTimeout.js', () => ({
68
+ fireAndForgetWithTimeout: vi.fn(),
69
+ withTimeout: vi.fn(async (fn: () => any) => fn()),
70
+ }));
71
+
72
+ vi.mock('../../utils/errorQueue.js', () => ({
73
+ errorQueue: {
74
+ getRecent: vi.fn().mockReturnValue([]),
75
+ push: vi.fn(),
76
+ size: 0,
77
+ },
78
+ }));
79
+
80
+ import { createServer } from '../../server.js';
81
+
82
+ describe('Server HTTP Flows', () => {
83
+ let app: any;
84
+
85
+ beforeEach(async () => {
86
+ app = await createServer();
87
+ });
88
+
89
+ describe('GET /health', () => {
90
+ it('returns 200 with health data when initialized', async () => {
91
+ const res = await request(app).get('/health');
92
+ expect(res.status).toBe(200);
93
+ expect(res.body.status).toBe('ok');
94
+ });
95
+
96
+ it('includes all required health fields', async () => {
97
+ const res = await request(app).get('/health');
98
+ const body = res.body;
99
+ expect(body).toHaveProperty('host');
100
+ expect(body).toHaveProperty('port');
101
+ expect(body).toHaveProperty('uptime');
102
+ expect(body).toHaveProperty('processManager');
103
+ expect(body).toHaveProperty('pid');
104
+ expect(body).toHaveProperty('idle');
105
+ expect(body).toHaveProperty('memory');
106
+ expect(body).toHaveProperty('circuits');
107
+ expect(body).toHaveProperty('errors');
108
+ });
109
+
110
+ it('reports detached daemon as process manager', async () => {
111
+ const res = await request(app).get('/health');
112
+ expect(res.body.processManager).toContain('detached');
113
+ });
114
+
115
+ it('returns numeric pid', async () => {
116
+ const res = await request(app).get('/health');
117
+ expect(typeof res.body.pid).toBe('number');
118
+ expect(res.body.pid).toBeGreaterThan(0);
119
+ });
120
+
121
+ it('includes idle tracking info', async () => {
122
+ const res = await request(app).get('/health');
123
+ const { idle } = res.body;
124
+ expect(idle).toHaveProperty('currentMs');
125
+ expect(idle).toHaveProperty('thresholdMs');
126
+ expect(idle).toHaveProperty('checkIntervalMs');
127
+ expect(idle).toHaveProperty('percentToRestart');
128
+ expect(typeof idle.currentMs).toBe('number');
129
+ });
130
+
131
+ it('includes memory stats in MB', async () => {
132
+ const res = await request(app).get('/health');
133
+ const { memory } = res.body;
134
+ expect(memory).toHaveProperty('heapUsed');
135
+ expect(memory).toHaveProperty('heapTotal');
136
+ expect(memory).toHaveProperty('rss');
137
+ expect(memory.heapUsed).toBeGreaterThan(0);
138
+ });
139
+
140
+ it('returns initializing when MCP not ready', async () => {
141
+ const { isMcpInitialized } = await import('../../mcpCache.js');
142
+ vi.mocked(isMcpInitialized).mockReturnValueOnce(false);
143
+ const res = await request(app).get('/health');
144
+ expect(res.body.status).toBe('initializing');
145
+ });
146
+
147
+ it('includes version string', async () => {
148
+ const res = await request(app).get('/health');
149
+ expect(res.body).toHaveProperty('version');
150
+ expect(typeof res.body.version).toBe('string');
151
+ });
152
+ });
153
+
154
+ describe('404 handler', () => {
155
+ it('returns 404 for unknown routes', async () => {
156
+ const res = await request(app).get('/nonexistent');
157
+ expect(res.status).toBe(404);
158
+ expect(res.body.success).toBe(false);
159
+ });
160
+
161
+ it('includes error code NOT_FOUND', async () => {
162
+ const res = await request(app).get('/does-not-exist');
163
+ expect(res.body.error.code).toBe('NOT_FOUND');
164
+ });
165
+
166
+ it('lists available routes in response', async () => {
167
+ const res = await request(app).get('/unknown');
168
+ expect(res.body.error.availableRoutes).toBeDefined();
169
+ expect(Array.isArray(res.body.error.availableRoutes)).toBe(true);
170
+ expect(res.body.error.availableRoutes.length).toBeGreaterThan(5);
171
+ });
172
+
173
+ it('includes hint about POST tool calls', async () => {
174
+ const res = await request(app).get('/wrong-path');
175
+ expect(res.body.error.hint).toContain('POST');
176
+ });
177
+
178
+ it('returns 404 for POST to unknown routes', async () => {
179
+ const res = await request(app).post('/unknown').send({});
180
+ expect(res.status).toBe(404);
181
+ });
182
+ });
183
+
184
+ describe('Middleware', () => {
185
+ it('parses JSON request bodies', async () => {
186
+ const res = await request(app)
187
+ .post('/tools/call/localSearchCode')
188
+ .send({
189
+ queries: [{
190
+ id: 'test-1',
191
+ researchGoal: 'test',
192
+ reasoning: 'test',
193
+ pattern: 'foo',
194
+ path: '/test',
195
+ }],
196
+ });
197
+ expect(res.status).not.toBe(415);
198
+ });
199
+
200
+ it('updates idle timer on requests', async () => {
201
+ const res1 = await request(app).get('/health');
202
+ const idle1 = res1.body.idle.currentMs;
203
+ await new Promise((r) => setTimeout(r, 50));
204
+ const res2 = await request(app).get('/health');
205
+ const idle2 = res2.body.idle.currentMs;
206
+ expect(idle2).toBeLessThanOrEqual(idle1 + 200);
207
+ });
208
+ });
209
+
210
+ describe('Route mounting', () => {
211
+ it('tools routes are mounted at /tools', async () => {
212
+ const res = await request(app).get('/tools/list');
213
+ expect(res.status).toBe(200);
214
+ });
215
+
216
+ it('prompts routes are mounted at /prompts', async () => {
217
+ const res = await request(app).get('/prompts/list');
218
+ expect(res.status).toBe(200);
219
+ });
220
+ });
221
+ });
@@ -0,0 +1,194 @@
1
+ /**
2
+ * Integration test: detached daemon lifecycle.
3
+ *
4
+ * Spawns server-init.js on a random test port, verifies that:
5
+ * 1. init exits after server is healthy (detached daemon behavior)
6
+ * 2. server survives init exit
7
+ * 3. second init detects running server and exits immediately
8
+ * 4. PID file is created on startup and removed on shutdown
9
+ */
10
+
11
+ import { describe, it, expect, afterEach } from 'vitest';
12
+ import { spawn, type ChildProcess } from 'child_process';
13
+ import { join } from 'path';
14
+ import { existsSync, readFileSync } from 'fs';
15
+ import { homedir } from 'os';
16
+
17
+ const SCRIPTS_DIR = join(import.meta.dirname, '..', '..', '..', 'scripts');
18
+ const SERVER_INIT_SCRIPT = join(SCRIPTS_DIR, 'server-init.js');
19
+
20
+ const TEST_PORT = 19871;
21
+ const HEALTH_URL = `http://localhost:${TEST_PORT}/health`;
22
+ const OCTOCODE_DIR = process.env.OCTOCODE_HOME || join(homedir(), '.octocode');
23
+ const PID_FILE = join(OCTOCODE_DIR, `research-server-${TEST_PORT}.pid`);
24
+
25
+ async function pollHealth(timeoutMs: number): Promise<boolean> {
26
+ const deadline = Date.now() + timeoutMs;
27
+ while (Date.now() < deadline) {
28
+ try {
29
+ const res = await fetch(HEALTH_URL, { signal: AbortSignal.timeout(2000) });
30
+ if (res.ok) {
31
+ const body = (await res.json()) as { status: string };
32
+ if (body.status === 'ok') return true;
33
+ }
34
+ } catch {
35
+ // not ready yet
36
+ }
37
+ await new Promise((r) => setTimeout(r, 500));
38
+ }
39
+ return false;
40
+ }
41
+
42
+ async function isServerDown(timeoutMs: number): Promise<boolean> {
43
+ const deadline = Date.now() + timeoutMs;
44
+ while (Date.now() < deadline) {
45
+ try {
46
+ await fetch(HEALTH_URL, { signal: AbortSignal.timeout(1000) });
47
+ } catch {
48
+ return true;
49
+ }
50
+ await new Promise((r) => setTimeout(r, 300));
51
+ }
52
+ return false;
53
+ }
54
+
55
+ function spawnInitScript(): ChildProcess {
56
+ return spawn('node', [SERVER_INIT_SCRIPT], {
57
+ env: {
58
+ ...process.env,
59
+ OCTOCODE_PORT: String(TEST_PORT),
60
+ OCTOCODE_RESEARCH_PORT: String(TEST_PORT),
61
+ },
62
+ stdio: 'pipe',
63
+ });
64
+ }
65
+
66
+ function killListenersOnPort(port: number): Promise<void> {
67
+ return new Promise((resolve) => {
68
+ const proc = spawn('sh', ['-c', `lsof -sTCP:LISTEN -ti :${port} | xargs kill -9 2>/dev/null`], {
69
+ stdio: 'ignore',
70
+ });
71
+ proc.on('close', () => resolve());
72
+ setTimeout(() => resolve(), 2000);
73
+ });
74
+ }
75
+
76
+ function waitForExit(proc: ChildProcess, timeoutMs: number): Promise<number | null> {
77
+ return new Promise((resolve) => {
78
+ const timeout = setTimeout(() => {
79
+ proc.kill('SIGKILL');
80
+ resolve(null);
81
+ }, timeoutMs);
82
+ proc.on('exit', (code) => {
83
+ clearTimeout(timeout);
84
+ resolve(code);
85
+ });
86
+ });
87
+ }
88
+
89
+ describe('Server Lifecycle (Detached Daemon)', () => {
90
+ let initProc: ChildProcess | null = null;
91
+
92
+ afterEach(async () => {
93
+ if (initProc && !initProc.killed) {
94
+ initProc.kill('SIGTERM');
95
+ await new Promise<void>((resolve) => {
96
+ const timeout = setTimeout(() => {
97
+ try { initProc?.kill('SIGKILL'); } catch { /* already dead */ }
98
+ resolve();
99
+ }, 5000);
100
+ initProc!.on('exit', () => { clearTimeout(timeout); resolve(); });
101
+ });
102
+ }
103
+ initProc = null;
104
+ await killListenersOnPort(TEST_PORT);
105
+ await new Promise((r) => setTimeout(r, 500));
106
+ });
107
+
108
+ it('init exits after server is healthy and server survives', async () => {
109
+ initProc = spawnInitScript();
110
+
111
+ let initOutput = '';
112
+ initProc.stdout?.on('data', (chunk: Buffer) => { initOutput += chunk.toString(); });
113
+ initProc.stderr?.on('data', (chunk: Buffer) => { initOutput += chunk.toString(); });
114
+
115
+ // Init should exit on its own after server is ready
116
+ const exitCode = await waitForExit(initProc, 45_000);
117
+ expect(exitCode, `Init should exit 0. Output:\n${initOutput}`).toBe(0);
118
+ expect(initOutput).toContain('ok');
119
+
120
+ // Server should still be running after init exits
121
+ const serverAlive = await pollHealth(5_000);
122
+ expect(serverAlive, 'Server should survive init exit (detached daemon)').toBe(true);
123
+
124
+ // Verify health response reflects detached mode
125
+ const healthRes = await fetch(HEALTH_URL, { signal: AbortSignal.timeout(3000) });
126
+ const healthBody = (await healthRes.json()) as { status: string; port: number; processManager: string; pid: number };
127
+ expect(healthBody.status).toBe('ok');
128
+ expect(healthBody.port).toBe(TEST_PORT);
129
+ expect(healthBody.processManager).toContain('detached');
130
+ expect(healthBody.pid).toBeGreaterThan(0);
131
+ }, 60_000);
132
+
133
+ it('init exits immediately when server is already running', async () => {
134
+ // Start first init → starts server
135
+ initProc = spawnInitScript();
136
+
137
+ let output1 = '';
138
+ initProc.stdout?.on('data', (chunk: Buffer) => { output1 += chunk.toString(); });
139
+
140
+ const exitCode1 = await waitForExit(initProc, 45_000);
141
+ expect(exitCode1, `First init should exit 0. Output:\n${output1}`).toBe(0);
142
+
143
+ // Server should be alive
144
+ const ready = await pollHealth(5_000);
145
+ expect(ready, 'Server should be running').toBe(true);
146
+
147
+ // Start a SECOND init — should detect running server and exit fast
148
+ const secondInit = spawnInitScript();
149
+
150
+ let output2 = '';
151
+ secondInit.stdout?.on('data', (chunk: Buffer) => { output2 += chunk.toString(); });
152
+
153
+ const exitCode2 = await waitForExit(secondInit, 15_000);
154
+ expect(exitCode2, `Second init should exit 0 (fast path). Output:\n${output2}`).toBe(0);
155
+ expect(output2).toContain('ok');
156
+
157
+ // Server should still be alive
158
+ const stillAlive = await pollHealth(3_000);
159
+ expect(stillAlive).toBe(true);
160
+ }, 60_000);
161
+
162
+ it('PID file is created on startup and cleaned up on shutdown', async () => {
163
+ initProc = spawnInitScript();
164
+
165
+ let initOutput = '';
166
+ initProc.stdout?.on('data', (chunk: Buffer) => { initOutput += chunk.toString(); });
167
+
168
+ const exitCode = await waitForExit(initProc, 45_000);
169
+ expect(exitCode, `Init should exit 0. Output:\n${initOutput}`).toBe(0);
170
+
171
+ // PID file should exist
172
+ expect(existsSync(PID_FILE), 'PID file should exist after server starts').toBe(true);
173
+
174
+ const pidContent = readFileSync(PID_FILE, 'utf-8').trim();
175
+ const pid = parseInt(pidContent, 10);
176
+ expect(pid).toBeGreaterThan(0);
177
+
178
+ // Verify the PID matches the running server
179
+ const healthRes = await fetch(HEALTH_URL, { signal: AbortSignal.timeout(3000) });
180
+ const healthBody = (await healthRes.json()) as { pid: number };
181
+ expect(pid).toBe(healthBody.pid);
182
+
183
+ // Kill the server via its PID
184
+ process.kill(pid, 'SIGTERM');
185
+
186
+ // Server should stop
187
+ const serverDead = await isServerDown(10_000);
188
+ expect(serverDead, 'Server should stop after SIGTERM').toBe(true);
189
+
190
+ // PID file should be cleaned up
191
+ await new Promise((r) => setTimeout(r, 1000));
192
+ expect(existsSync(PID_FILE), 'PID file should be removed on shutdown').toBe(false);
193
+ }, 60_000);
194
+ });