@promptwheel/cli 0.6.0

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 (530) hide show
  1. package/README.md +215 -0
  2. package/dist/bin/promptwheel.d.ts +17 -0
  3. package/dist/bin/promptwheel.d.ts.map +1 -0
  4. package/dist/bin/promptwheel.js +90 -0
  5. package/dist/bin/promptwheel.js.map +1 -0
  6. package/dist/commands/auto-auth.d.ts +26 -0
  7. package/dist/commands/auto-auth.d.ts.map +1 -0
  8. package/dist/commands/auto-auth.js +216 -0
  9. package/dist/commands/auto-auth.js.map +1 -0
  10. package/dist/commands/auto-ci-mode.d.ts +9 -0
  11. package/dist/commands/auto-ci-mode.d.ts.map +1 -0
  12. package/dist/commands/auto-ci-mode.js +176 -0
  13. package/dist/commands/auto-ci-mode.js.map +1 -0
  14. package/dist/commands/solo-analytics.d.ts +6 -0
  15. package/dist/commands/solo-analytics.d.ts.map +1 -0
  16. package/dist/commands/solo-analytics.js +267 -0
  17. package/dist/commands/solo-analytics.js.map +1 -0
  18. package/dist/commands/solo-auto.d.ts +6 -0
  19. package/dist/commands/solo-auto.d.ts.map +1 -0
  20. package/dist/commands/solo-auto.js +135 -0
  21. package/dist/commands/solo-auto.js.map +1 -0
  22. package/dist/commands/solo-daemon.d.ts +12 -0
  23. package/dist/commands/solo-daemon.d.ts.map +1 -0
  24. package/dist/commands/solo-daemon.js +177 -0
  25. package/dist/commands/solo-daemon.js.map +1 -0
  26. package/dist/commands/solo-exec.d.ts +6 -0
  27. package/dist/commands/solo-exec.d.ts.map +1 -0
  28. package/dist/commands/solo-exec.js +656 -0
  29. package/dist/commands/solo-exec.js.map +1 -0
  30. package/dist/commands/solo-inspect.d.ts +6 -0
  31. package/dist/commands/solo-inspect.d.ts.map +1 -0
  32. package/dist/commands/solo-inspect.js +765 -0
  33. package/dist/commands/solo-inspect.js.map +1 -0
  34. package/dist/commands/solo-lifecycle.d.ts +6 -0
  35. package/dist/commands/solo-lifecycle.d.ts.map +1 -0
  36. package/dist/commands/solo-lifecycle.js +226 -0
  37. package/dist/commands/solo-lifecycle.js.map +1 -0
  38. package/dist/commands/solo-nudge.d.ts +6 -0
  39. package/dist/commands/solo-nudge.d.ts.map +1 -0
  40. package/dist/commands/solo-nudge.js +49 -0
  41. package/dist/commands/solo-nudge.js.map +1 -0
  42. package/dist/commands/solo-qa.d.ts +6 -0
  43. package/dist/commands/solo-qa.d.ts.map +1 -0
  44. package/dist/commands/solo-qa.js +253 -0
  45. package/dist/commands/solo-qa.js.map +1 -0
  46. package/dist/commands/solo-trajectory.d.ts +6 -0
  47. package/dist/commands/solo-trajectory.d.ts.map +1 -0
  48. package/dist/commands/solo-trajectory.js +268 -0
  49. package/dist/commands/solo-trajectory.js.map +1 -0
  50. package/dist/commands/solo.d.ts +11 -0
  51. package/dist/commands/solo.d.ts.map +1 -0
  52. package/dist/commands/solo.js +52 -0
  53. package/dist/commands/solo.js.map +1 -0
  54. package/dist/index.d.ts +18 -0
  55. package/dist/index.d.ts.map +1 -0
  56. package/dist/index.js +18 -0
  57. package/dist/index.js.map +1 -0
  58. package/dist/lib/artifacts.d.ts +136 -0
  59. package/dist/lib/artifacts.d.ts.map +1 -0
  60. package/dist/lib/artifacts.js +147 -0
  61. package/dist/lib/artifacts.js.map +1 -0
  62. package/dist/lib/codebase-index.d.ts +5 -0
  63. package/dist/lib/codebase-index.d.ts.map +1 -0
  64. package/dist/lib/codebase-index.js +5 -0
  65. package/dist/lib/codebase-index.js.map +1 -0
  66. package/dist/lib/cycle-context.d.ts +62 -0
  67. package/dist/lib/cycle-context.d.ts.map +1 -0
  68. package/dist/lib/cycle-context.js +141 -0
  69. package/dist/lib/cycle-context.js.map +1 -0
  70. package/dist/lib/daemon-fork.d.ts +39 -0
  71. package/dist/lib/daemon-fork.d.ts.map +1 -0
  72. package/dist/lib/daemon-fork.js +164 -0
  73. package/dist/lib/daemon-fork.js.map +1 -0
  74. package/dist/lib/daemon-notifier.d.ts +29 -0
  75. package/dist/lib/daemon-notifier.d.ts.map +1 -0
  76. package/dist/lib/daemon-notifier.js +147 -0
  77. package/dist/lib/daemon-notifier.js.map +1 -0
  78. package/dist/lib/daemon.d.ts +75 -0
  79. package/dist/lib/daemon.d.ts.map +1 -0
  80. package/dist/lib/daemon.js +265 -0
  81. package/dist/lib/daemon.js.map +1 -0
  82. package/dist/lib/dedup-memory.d.ts +46 -0
  83. package/dist/lib/dedup-memory.d.ts.map +1 -0
  84. package/dist/lib/dedup-memory.js +190 -0
  85. package/dist/lib/dedup-memory.js.map +1 -0
  86. package/dist/lib/dedup.d.ts +37 -0
  87. package/dist/lib/dedup.d.ts.map +1 -0
  88. package/dist/lib/dedup.js +85 -0
  89. package/dist/lib/dedup.js.map +1 -0
  90. package/dist/lib/display-adapter-log.d.ts +26 -0
  91. package/dist/lib/display-adapter-log.d.ts.map +1 -0
  92. package/dist/lib/display-adapter-log.js +63 -0
  93. package/dist/lib/display-adapter-log.js.map +1 -0
  94. package/dist/lib/display-adapter-spinner.d.ts +28 -0
  95. package/dist/lib/display-adapter-spinner.d.ts.map +1 -0
  96. package/dist/lib/display-adapter-spinner.js +93 -0
  97. package/dist/lib/display-adapter-spinner.js.map +1 -0
  98. package/dist/lib/display-adapter-tui.d.ts +32 -0
  99. package/dist/lib/display-adapter-tui.d.ts.map +1 -0
  100. package/dist/lib/display-adapter-tui.js +77 -0
  101. package/dist/lib/display-adapter-tui.js.map +1 -0
  102. package/dist/lib/display-adapter.d.ts +40 -0
  103. package/dist/lib/display-adapter.d.ts.map +1 -0
  104. package/dist/lib/display-adapter.js +9 -0
  105. package/dist/lib/display-adapter.js.map +1 -0
  106. package/dist/lib/doctor.d.ts +45 -0
  107. package/dist/lib/doctor.d.ts.map +1 -0
  108. package/dist/lib/doctor.js +382 -0
  109. package/dist/lib/doctor.js.map +1 -0
  110. package/dist/lib/exclusion-index.d.ts +50 -0
  111. package/dist/lib/exclusion-index.d.ts.map +1 -0
  112. package/dist/lib/exclusion-index.js +249 -0
  113. package/dist/lib/exclusion-index.js.map +1 -0
  114. package/dist/lib/exec.d.ts +24 -0
  115. package/dist/lib/exec.d.ts.map +1 -0
  116. package/dist/lib/exec.js +295 -0
  117. package/dist/lib/exec.js.map +1 -0
  118. package/dist/lib/execution-backends/claude.d.ts +27 -0
  119. package/dist/lib/execution-backends/claude.d.ts.map +1 -0
  120. package/dist/lib/execution-backends/claude.js +161 -0
  121. package/dist/lib/execution-backends/claude.js.map +1 -0
  122. package/dist/lib/execution-backends/codex.d.ts +28 -0
  123. package/dist/lib/execution-backends/codex.d.ts.map +1 -0
  124. package/dist/lib/execution-backends/codex.js +216 -0
  125. package/dist/lib/execution-backends/codex.js.map +1 -0
  126. package/dist/lib/execution-backends/index.d.ts +10 -0
  127. package/dist/lib/execution-backends/index.d.ts.map +1 -0
  128. package/dist/lib/execution-backends/index.js +9 -0
  129. package/dist/lib/execution-backends/index.js.map +1 -0
  130. package/dist/lib/execution-backends/kimi.d.ts +25 -0
  131. package/dist/lib/execution-backends/kimi.d.ts.map +1 -0
  132. package/dist/lib/execution-backends/kimi.js +76 -0
  133. package/dist/lib/execution-backends/kimi.js.map +1 -0
  134. package/dist/lib/execution-backends/types.d.ts +38 -0
  135. package/dist/lib/execution-backends/types.d.ts.map +1 -0
  136. package/dist/lib/execution-backends/types.js +5 -0
  137. package/dist/lib/execution-backends/types.js.map +1 -0
  138. package/dist/lib/failure-classifier.d.ts +11 -0
  139. package/dist/lib/failure-classifier.d.ts.map +1 -0
  140. package/dist/lib/failure-classifier.js +24 -0
  141. package/dist/lib/failure-classifier.js.map +1 -0
  142. package/dist/lib/file-cooldown.d.ts +9 -0
  143. package/dist/lib/file-cooldown.d.ts.map +1 -0
  144. package/dist/lib/file-cooldown.js +75 -0
  145. package/dist/lib/file-cooldown.js.map +1 -0
  146. package/dist/lib/formulas.d.ts +52 -0
  147. package/dist/lib/formulas.d.ts.map +1 -0
  148. package/dist/lib/formulas.js +123 -0
  149. package/dist/lib/formulas.js.map +1 -0
  150. package/dist/lib/git.d.ts +9 -0
  151. package/dist/lib/git.d.ts.map +1 -0
  152. package/dist/lib/git.js +60 -0
  153. package/dist/lib/git.js.map +1 -0
  154. package/dist/lib/goals.d.ts +59 -0
  155. package/dist/lib/goals.d.ts.map +1 -0
  156. package/dist/lib/goals.js +216 -0
  157. package/dist/lib/goals.js.map +1 -0
  158. package/dist/lib/guidelines.d.ts +32 -0
  159. package/dist/lib/guidelines.d.ts.map +1 -0
  160. package/dist/lib/guidelines.js +102 -0
  161. package/dist/lib/guidelines.js.map +1 -0
  162. package/dist/lib/learnings.d.ts +65 -0
  163. package/dist/lib/learnings.d.ts.map +1 -0
  164. package/dist/lib/learnings.js +193 -0
  165. package/dist/lib/learnings.js.map +1 -0
  166. package/dist/lib/logger.d.ts +17 -0
  167. package/dist/lib/logger.d.ts.map +1 -0
  168. package/dist/lib/logger.js +42 -0
  169. package/dist/lib/logger.js.map +1 -0
  170. package/dist/lib/meta-learnings.d.ts +27 -0
  171. package/dist/lib/meta-learnings.d.ts.map +1 -0
  172. package/dist/lib/meta-learnings.js +270 -0
  173. package/dist/lib/meta-learnings.js.map +1 -0
  174. package/dist/lib/metrics.d.ts +48 -0
  175. package/dist/lib/metrics.d.ts.map +1 -0
  176. package/dist/lib/metrics.js +107 -0
  177. package/dist/lib/metrics.js.map +1 -0
  178. package/dist/lib/openai-local-execution.d.ts +29 -0
  179. package/dist/lib/openai-local-execution.d.ts.map +1 -0
  180. package/dist/lib/openai-local-execution.js +282 -0
  181. package/dist/lib/openai-local-execution.js.map +1 -0
  182. package/dist/lib/project-metadata/csharp.d.ts +6 -0
  183. package/dist/lib/project-metadata/csharp.d.ts.map +1 -0
  184. package/dist/lib/project-metadata/csharp.js +19 -0
  185. package/dist/lib/project-metadata/csharp.js.map +1 -0
  186. package/dist/lib/project-metadata/elixir.d.ts +6 -0
  187. package/dist/lib/project-metadata/elixir.d.ts.map +1 -0
  188. package/dist/lib/project-metadata/elixir.js +23 -0
  189. package/dist/lib/project-metadata/elixir.js.map +1 -0
  190. package/dist/lib/project-metadata/go.d.ts +6 -0
  191. package/dist/lib/project-metadata/go.d.ts.map +1 -0
  192. package/dist/lib/project-metadata/go.js +30 -0
  193. package/dist/lib/project-metadata/go.js.map +1 -0
  194. package/dist/lib/project-metadata/index.d.ts +13 -0
  195. package/dist/lib/project-metadata/index.d.ts.map +1 -0
  196. package/dist/lib/project-metadata/index.js +111 -0
  197. package/dist/lib/project-metadata/index.js.map +1 -0
  198. package/dist/lib/project-metadata/java.d.ts +6 -0
  199. package/dist/lib/project-metadata/java.d.ts.map +1 -0
  200. package/dist/lib/project-metadata/java.js +37 -0
  201. package/dist/lib/project-metadata/java.js.map +1 -0
  202. package/dist/lib/project-metadata/node.d.ts +6 -0
  203. package/dist/lib/project-metadata/node.d.ts.map +1 -0
  204. package/dist/lib/project-metadata/node.js +135 -0
  205. package/dist/lib/project-metadata/node.js.map +1 -0
  206. package/dist/lib/project-metadata/php.d.ts +6 -0
  207. package/dist/lib/project-metadata/php.d.ts.map +1 -0
  208. package/dist/lib/project-metadata/php.js +29 -0
  209. package/dist/lib/project-metadata/php.js.map +1 -0
  210. package/dist/lib/project-metadata/python.d.ts +6 -0
  211. package/dist/lib/project-metadata/python.d.ts.map +1 -0
  212. package/dist/lib/project-metadata/python.js +79 -0
  213. package/dist/lib/project-metadata/python.js.map +1 -0
  214. package/dist/lib/project-metadata/ruby.d.ts +6 -0
  215. package/dist/lib/project-metadata/ruby.d.ts.map +1 -0
  216. package/dist/lib/project-metadata/ruby.js +38 -0
  217. package/dist/lib/project-metadata/ruby.js.map +1 -0
  218. package/dist/lib/project-metadata/rust.d.ts +6 -0
  219. package/dist/lib/project-metadata/rust.d.ts.map +1 -0
  220. package/dist/lib/project-metadata/rust.js +33 -0
  221. package/dist/lib/project-metadata/rust.js.map +1 -0
  222. package/dist/lib/project-metadata/swift.d.ts +6 -0
  223. package/dist/lib/project-metadata/swift.d.ts.map +1 -0
  224. package/dist/lib/project-metadata/swift.js +19 -0
  225. package/dist/lib/project-metadata/swift.js.map +1 -0
  226. package/dist/lib/project-metadata/types.d.ts +37 -0
  227. package/dist/lib/project-metadata/types.d.ts.map +1 -0
  228. package/dist/lib/project-metadata/types.js +5 -0
  229. package/dist/lib/project-metadata/types.js.map +1 -0
  230. package/dist/lib/proposal-review.d.ts +9 -0
  231. package/dist/lib/proposal-review.d.ts.map +1 -0
  232. package/dist/lib/proposal-review.js +9 -0
  233. package/dist/lib/proposal-review.js.map +1 -0
  234. package/dist/lib/providers/claude.d.ts +6 -0
  235. package/dist/lib/providers/claude.d.ts.map +1 -0
  236. package/dist/lib/providers/claude.js +21 -0
  237. package/dist/lib/providers/claude.js.map +1 -0
  238. package/dist/lib/providers/codex.d.ts +6 -0
  239. package/dist/lib/providers/codex.d.ts.map +1 -0
  240. package/dist/lib/providers/codex.js +26 -0
  241. package/dist/lib/providers/codex.js.map +1 -0
  242. package/dist/lib/providers/index.d.ts +22 -0
  243. package/dist/lib/providers/index.d.ts.map +1 -0
  244. package/dist/lib/providers/index.js +39 -0
  245. package/dist/lib/providers/index.js.map +1 -0
  246. package/dist/lib/providers/kimi.d.ts +6 -0
  247. package/dist/lib/providers/kimi.d.ts.map +1 -0
  248. package/dist/lib/providers/kimi.js +22 -0
  249. package/dist/lib/providers/kimi.js.map +1 -0
  250. package/dist/lib/providers/openai-local.d.ts +8 -0
  251. package/dist/lib/providers/openai-local.d.ts.map +1 -0
  252. package/dist/lib/providers/openai-local.js +30 -0
  253. package/dist/lib/providers/openai-local.js.map +1 -0
  254. package/dist/lib/providers/types.d.ts +37 -0
  255. package/dist/lib/providers/types.d.ts.map +1 -0
  256. package/dist/lib/providers/types.js +5 -0
  257. package/dist/lib/providers/types.js.map +1 -0
  258. package/dist/lib/qa-stats.d.ts +113 -0
  259. package/dist/lib/qa-stats.d.ts.map +1 -0
  260. package/dist/lib/qa-stats.js +311 -0
  261. package/dist/lib/qa-stats.js.map +1 -0
  262. package/dist/lib/retention.d.ts +125 -0
  263. package/dist/lib/retention.d.ts.map +1 -0
  264. package/dist/lib/retention.js +767 -0
  265. package/dist/lib/retention.js.map +1 -0
  266. package/dist/lib/run-history.d.ts +52 -0
  267. package/dist/lib/run-history.d.ts.map +1 -0
  268. package/dist/lib/run-history.js +116 -0
  269. package/dist/lib/run-history.js.map +1 -0
  270. package/dist/lib/run-state.d.ts +129 -0
  271. package/dist/lib/run-state.d.ts.map +1 -0
  272. package/dist/lib/run-state.js +312 -0
  273. package/dist/lib/run-state.js.map +1 -0
  274. package/dist/lib/scope.d.ts +8 -0
  275. package/dist/lib/scope.d.ts.map +1 -0
  276. package/dist/lib/scope.js +8 -0
  277. package/dist/lib/scope.js.map +1 -0
  278. package/dist/lib/scout-prompt-builder.d.ts +22 -0
  279. package/dist/lib/scout-prompt-builder.d.ts.map +1 -0
  280. package/dist/lib/scout-prompt-builder.js +97 -0
  281. package/dist/lib/scout-prompt-builder.js.map +1 -0
  282. package/dist/lib/sectors.d.ts +21 -0
  283. package/dist/lib/sectors.d.ts.map +1 -0
  284. package/dist/lib/sectors.js +96 -0
  285. package/dist/lib/sectors.js.map +1 -0
  286. package/dist/lib/selection.d.ts +35 -0
  287. package/dist/lib/selection.d.ts.map +1 -0
  288. package/dist/lib/selection.js +110 -0
  289. package/dist/lib/selection.js.map +1 -0
  290. package/dist/lib/session-report.d.ts +26 -0
  291. package/dist/lib/session-report.d.ts.map +1 -0
  292. package/dist/lib/session-report.js +143 -0
  293. package/dist/lib/session-report.js.map +1 -0
  294. package/dist/lib/solo-auto-between-cycles.d.ts +10 -0
  295. package/dist/lib/solo-auto-between-cycles.d.ts.map +1 -0
  296. package/dist/lib/solo-auto-between-cycles.js +577 -0
  297. package/dist/lib/solo-auto-between-cycles.js.map +1 -0
  298. package/dist/lib/solo-auto-execute.d.ts +22 -0
  299. package/dist/lib/solo-auto-execute.d.ts.map +1 -0
  300. package/dist/lib/solo-auto-execute.js +530 -0
  301. package/dist/lib/solo-auto-execute.js.map +1 -0
  302. package/dist/lib/solo-auto-filter.d.ts +14 -0
  303. package/dist/lib/solo-auto-filter.d.ts.map +1 -0
  304. package/dist/lib/solo-auto-filter.js +311 -0
  305. package/dist/lib/solo-auto-filter.js.map +1 -0
  306. package/dist/lib/solo-auto-finalize.d.ts +6 -0
  307. package/dist/lib/solo-auto-finalize.d.ts.map +1 -0
  308. package/dist/lib/solo-auto-finalize.js +242 -0
  309. package/dist/lib/solo-auto-finalize.js.map +1 -0
  310. package/dist/lib/solo-auto-init-qa.d.ts +23 -0
  311. package/dist/lib/solo-auto-init-qa.d.ts.map +1 -0
  312. package/dist/lib/solo-auto-init-qa.js +183 -0
  313. package/dist/lib/solo-auto-init-qa.js.map +1 -0
  314. package/dist/lib/solo-auto-planning.d.ts +40 -0
  315. package/dist/lib/solo-auto-planning.d.ts.map +1 -0
  316. package/dist/lib/solo-auto-planning.js +171 -0
  317. package/dist/lib/solo-auto-planning.js.map +1 -0
  318. package/dist/lib/solo-auto-scout.d.ts +18 -0
  319. package/dist/lib/solo-auto-scout.d.ts.map +1 -0
  320. package/dist/lib/solo-auto-scout.js +269 -0
  321. package/dist/lib/solo-auto-scout.js.map +1 -0
  322. package/dist/lib/solo-auto-state.d.ts +20 -0
  323. package/dist/lib/solo-auto-state.d.ts.map +1 -0
  324. package/dist/lib/solo-auto-state.js +859 -0
  325. package/dist/lib/solo-auto-state.js.map +1 -0
  326. package/dist/lib/solo-auto-types.d.ts +197 -0
  327. package/dist/lib/solo-auto-types.d.ts.map +1 -0
  328. package/dist/lib/solo-auto-types.js +11 -0
  329. package/dist/lib/solo-auto-types.js.map +1 -0
  330. package/dist/lib/solo-auto-utils.d.ts +23 -0
  331. package/dist/lib/solo-auto-utils.d.ts.map +1 -0
  332. package/dist/lib/solo-auto-utils.js +53 -0
  333. package/dist/lib/solo-auto-utils.js.map +1 -0
  334. package/dist/lib/solo-auto.d.ts +25 -0
  335. package/dist/lib/solo-auto.d.ts.map +1 -0
  336. package/dist/lib/solo-auto.js +345 -0
  337. package/dist/lib/solo-auto.js.map +1 -0
  338. package/dist/lib/solo-ci.d.ts +84 -0
  339. package/dist/lib/solo-ci.d.ts.map +1 -0
  340. package/dist/lib/solo-ci.js +306 -0
  341. package/dist/lib/solo-ci.js.map +1 -0
  342. package/dist/lib/solo-config.d.ts +242 -0
  343. package/dist/lib/solo-config.d.ts.map +1 -0
  344. package/dist/lib/solo-config.js +354 -0
  345. package/dist/lib/solo-config.js.map +1 -0
  346. package/dist/lib/solo-cycle-formula.d.ts +36 -0
  347. package/dist/lib/solo-cycle-formula.d.ts.map +1 -0
  348. package/dist/lib/solo-cycle-formula.js +90 -0
  349. package/dist/lib/solo-cycle-formula.js.map +1 -0
  350. package/dist/lib/solo-git.d.ts +87 -0
  351. package/dist/lib/solo-git.d.ts.map +1 -0
  352. package/dist/lib/solo-git.js +365 -0
  353. package/dist/lib/solo-git.js.map +1 -0
  354. package/dist/lib/solo-hints.d.ts +32 -0
  355. package/dist/lib/solo-hints.d.ts.map +1 -0
  356. package/dist/lib/solo-hints.js +98 -0
  357. package/dist/lib/solo-hints.js.map +1 -0
  358. package/dist/lib/solo-prompt-builder.d.ts +12 -0
  359. package/dist/lib/solo-prompt-builder.d.ts.map +1 -0
  360. package/dist/lib/solo-prompt-builder.js +53 -0
  361. package/dist/lib/solo-prompt-builder.js.map +1 -0
  362. package/dist/lib/solo-qa-retry.d.ts +43 -0
  363. package/dist/lib/solo-qa-retry.d.ts.map +1 -0
  364. package/dist/lib/solo-qa-retry.js +125 -0
  365. package/dist/lib/solo-qa-retry.js.map +1 -0
  366. package/dist/lib/solo-remote.d.ts +14 -0
  367. package/dist/lib/solo-remote.d.ts.map +1 -0
  368. package/dist/lib/solo-remote.js +48 -0
  369. package/dist/lib/solo-remote.js.map +1 -0
  370. package/dist/lib/solo-session-summary.d.ts +52 -0
  371. package/dist/lib/solo-session-summary.d.ts.map +1 -0
  372. package/dist/lib/solo-session-summary.js +188 -0
  373. package/dist/lib/solo-session-summary.js.map +1 -0
  374. package/dist/lib/solo-stdin.d.ts +33 -0
  375. package/dist/lib/solo-stdin.d.ts.map +1 -0
  376. package/dist/lib/solo-stdin.js +299 -0
  377. package/dist/lib/solo-stdin.js.map +1 -0
  378. package/dist/lib/solo-ticket-qa.d.ts +19 -0
  379. package/dist/lib/solo-ticket-qa.d.ts.map +1 -0
  380. package/dist/lib/solo-ticket-qa.js +68 -0
  381. package/dist/lib/solo-ticket-qa.js.map +1 -0
  382. package/dist/lib/solo-ticket-types.d.ts +146 -0
  383. package/dist/lib/solo-ticket-types.d.ts.map +1 -0
  384. package/dist/lib/solo-ticket-types.js +26 -0
  385. package/dist/lib/solo-ticket-types.js.map +1 -0
  386. package/dist/lib/solo-ticket.d.ts +12 -0
  387. package/dist/lib/solo-ticket.d.ts.map +1 -0
  388. package/dist/lib/solo-ticket.js +192 -0
  389. package/dist/lib/solo-ticket.js.map +1 -0
  390. package/dist/lib/solo-utils.d.ts +130 -0
  391. package/dist/lib/solo-utils.d.ts.map +1 -0
  392. package/dist/lib/solo-utils.js +277 -0
  393. package/dist/lib/solo-utils.js.map +1 -0
  394. package/dist/lib/spindle/failure-patterns.d.ts +15 -0
  395. package/dist/lib/spindle/failure-patterns.d.ts.map +1 -0
  396. package/dist/lib/spindle/failure-patterns.js +22 -0
  397. package/dist/lib/spindle/failure-patterns.js.map +1 -0
  398. package/dist/lib/spindle/format.d.ts +9 -0
  399. package/dist/lib/spindle/format.d.ts.map +1 -0
  400. package/dist/lib/spindle/format.js +37 -0
  401. package/dist/lib/spindle/format.js.map +1 -0
  402. package/dist/lib/spindle/index.d.ts +33 -0
  403. package/dist/lib/spindle/index.d.ts.map +1 -0
  404. package/dist/lib/spindle/index.js +231 -0
  405. package/dist/lib/spindle/index.js.map +1 -0
  406. package/dist/lib/spindle/oscillation.d.ts +17 -0
  407. package/dist/lib/spindle/oscillation.d.ts.map +1 -0
  408. package/dist/lib/spindle/oscillation.js +96 -0
  409. package/dist/lib/spindle/oscillation.js.map +1 -0
  410. package/dist/lib/spindle/repetition.d.ts +16 -0
  411. package/dist/lib/spindle/repetition.d.ts.map +1 -0
  412. package/dist/lib/spindle/repetition.js +52 -0
  413. package/dist/lib/spindle/repetition.js.map +1 -0
  414. package/dist/lib/spindle/similarity.d.ts +14 -0
  415. package/dist/lib/spindle/similarity.d.ts.map +1 -0
  416. package/dist/lib/spindle/similarity.js +58 -0
  417. package/dist/lib/spindle/similarity.js.map +1 -0
  418. package/dist/lib/spindle/types.d.ts +97 -0
  419. package/dist/lib/spindle/types.d.ts.map +1 -0
  420. package/dist/lib/spindle/types.js +46 -0
  421. package/dist/lib/spindle/types.js.map +1 -0
  422. package/dist/lib/spinner.d.ts +41 -0
  423. package/dist/lib/spinner.d.ts.map +1 -0
  424. package/dist/lib/spinner.js +186 -0
  425. package/dist/lib/spinner.js.map +1 -0
  426. package/dist/lib/taste-profile.d.ts +26 -0
  427. package/dist/lib/taste-profile.d.ts.map +1 -0
  428. package/dist/lib/taste-profile.js +121 -0
  429. package/dist/lib/taste-profile.js.map +1 -0
  430. package/dist/lib/ticket-steps/index.d.ts +14 -0
  431. package/dist/lib/ticket-steps/index.d.ts.map +1 -0
  432. package/dist/lib/ticket-steps/index.js +13 -0
  433. package/dist/lib/ticket-steps/index.js.map +1 -0
  434. package/dist/lib/ticket-steps/step-agent.d.ts +6 -0
  435. package/dist/lib/ticket-steps/step-agent.d.ts.map +1 -0
  436. package/dist/lib/ticket-steps/step-agent.js +122 -0
  437. package/dist/lib/ticket-steps/step-agent.js.map +1 -0
  438. package/dist/lib/ticket-steps/step-cleanup.d.ts +6 -0
  439. package/dist/lib/ticket-steps/step-cleanup.d.ts.map +1 -0
  440. package/dist/lib/ticket-steps/step-cleanup.js +11 -0
  441. package/dist/lib/ticket-steps/step-cleanup.js.map +1 -0
  442. package/dist/lib/ticket-steps/step-commit.d.ts +6 -0
  443. package/dist/lib/ticket-steps/step-commit.d.ts.map +1 -0
  444. package/dist/lib/ticket-steps/step-commit.js +31 -0
  445. package/dist/lib/ticket-steps/step-commit.js.map +1 -0
  446. package/dist/lib/ticket-steps/step-pr.d.ts +6 -0
  447. package/dist/lib/ticket-steps/step-pr.d.ts.map +1 -0
  448. package/dist/lib/ticket-steps/step-pr.js +38 -0
  449. package/dist/lib/ticket-steps/step-pr.js.map +1 -0
  450. package/dist/lib/ticket-steps/step-push.d.ts +6 -0
  451. package/dist/lib/ticket-steps/step-push.d.ts.map +1 -0
  452. package/dist/lib/ticket-steps/step-push.js +35 -0
  453. package/dist/lib/ticket-steps/step-push.js.map +1 -0
  454. package/dist/lib/ticket-steps/step-qa.d.ts +6 -0
  455. package/dist/lib/ticket-steps/step-qa.d.ts.map +1 -0
  456. package/dist/lib/ticket-steps/step-qa.js +300 -0
  457. package/dist/lib/ticket-steps/step-qa.js.map +1 -0
  458. package/dist/lib/ticket-steps/step-scope.d.ts +6 -0
  459. package/dist/lib/ticket-steps/step-scope.d.ts.map +1 -0
  460. package/dist/lib/ticket-steps/step-scope.js +121 -0
  461. package/dist/lib/ticket-steps/step-scope.js.map +1 -0
  462. package/dist/lib/ticket-steps/step-spindle.d.ts +9 -0
  463. package/dist/lib/ticket-steps/step-spindle.d.ts.map +1 -0
  464. package/dist/lib/ticket-steps/step-spindle.js +160 -0
  465. package/dist/lib/ticket-steps/step-spindle.js.map +1 -0
  466. package/dist/lib/ticket-steps/step-worktree.d.ts +6 -0
  467. package/dist/lib/ticket-steps/step-worktree.d.ts.map +1 -0
  468. package/dist/lib/ticket-steps/step-worktree.js +157 -0
  469. package/dist/lib/ticket-steps/step-worktree.js.map +1 -0
  470. package/dist/lib/ticket-steps/types.d.ts +70 -0
  471. package/dist/lib/ticket-steps/types.d.ts.map +1 -0
  472. package/dist/lib/ticket-steps/types.js +8 -0
  473. package/dist/lib/ticket-steps/types.js.map +1 -0
  474. package/dist/lib/tool-command-map.d.ts +11 -0
  475. package/dist/lib/tool-command-map.d.ts.map +1 -0
  476. package/dist/lib/tool-command-map.js +24 -0
  477. package/dist/lib/tool-command-map.js.map +1 -0
  478. package/dist/lib/trajectory-generate.d.ts +24 -0
  479. package/dist/lib/trajectory-generate.d.ts.map +1 -0
  480. package/dist/lib/trajectory-generate.js +163 -0
  481. package/dist/lib/trajectory-generate.js.map +1 -0
  482. package/dist/lib/trajectory.d.ts +20 -0
  483. package/dist/lib/trajectory.d.ts.map +1 -0
  484. package/dist/lib/trajectory.js +104 -0
  485. package/dist/lib/trajectory.js.map +1 -0
  486. package/dist/lib/trigger-config.d.ts +12 -0
  487. package/dist/lib/trigger-config.d.ts.map +1 -0
  488. package/dist/lib/trigger-config.js +37 -0
  489. package/dist/lib/trigger-config.js.map +1 -0
  490. package/dist/lib/update-check.d.ts +28 -0
  491. package/dist/lib/update-check.d.ts.map +1 -0
  492. package/dist/lib/update-check.js +178 -0
  493. package/dist/lib/update-check.js.map +1 -0
  494. package/dist/lib/wave-scheduling.d.ts +18 -0
  495. package/dist/lib/wave-scheduling.d.ts.map +1 -0
  496. package/dist/lib/wave-scheduling.js +29 -0
  497. package/dist/lib/wave-scheduling.js.map +1 -0
  498. package/dist/tui/app.d.ts +17 -0
  499. package/dist/tui/app.d.ts.map +1 -0
  500. package/dist/tui/app.js +139 -0
  501. package/dist/tui/app.js.map +1 -0
  502. package/dist/tui/index.d.ts +10 -0
  503. package/dist/tui/index.d.ts.map +1 -0
  504. package/dist/tui/index.js +9 -0
  505. package/dist/tui/index.js.map +1 -0
  506. package/dist/tui/poller.d.ts +42 -0
  507. package/dist/tui/poller.d.ts.map +1 -0
  508. package/dist/tui/poller.js +62 -0
  509. package/dist/tui/poller.js.map +1 -0
  510. package/dist/tui/screens/auto.d.ts +85 -0
  511. package/dist/tui/screens/auto.d.ts.map +1 -0
  512. package/dist/tui/screens/auto.js +440 -0
  513. package/dist/tui/screens/auto.js.map +1 -0
  514. package/dist/tui/screens/overview.d.ts +9 -0
  515. package/dist/tui/screens/overview.d.ts.map +1 -0
  516. package/dist/tui/screens/overview.js +189 -0
  517. package/dist/tui/screens/overview.js.map +1 -0
  518. package/dist/tui/state.d.ts +93 -0
  519. package/dist/tui/state.d.ts.map +1 -0
  520. package/dist/tui/state.js +169 -0
  521. package/dist/tui/state.js.map +1 -0
  522. package/dist/tui/ticket-output-buffer.d.ts +23 -0
  523. package/dist/tui/ticket-output-buffer.d.ts.map +1 -0
  524. package/dist/tui/ticket-output-buffer.js +60 -0
  525. package/dist/tui/ticket-output-buffer.js.map +1 -0
  526. package/dist/tui/types.d.ts +18 -0
  527. package/dist/tui/types.d.ts.map +1 -0
  528. package/dist/tui/types.js +5 -0
  529. package/dist/tui/types.js.map +1 -0
  530. package/package.json +70 -0
@@ -0,0 +1,767 @@
1
+ /**
2
+ * Retention & Cleanup
3
+ *
4
+ * Prune logic for all unbounded state accumulation points.
5
+ * Called automatically on session start and via `promptwheel prune`.
6
+ */
7
+ import * as fs from 'node:fs';
8
+ import * as os from 'node:os';
9
+ import * as path from 'node:path';
10
+ import { spawnSync } from 'node:child_process';
11
+ import { DEFAULT_RETENTION_CONFIG, getPromptwheelDir, } from './solo-config.js';
12
+ import { readRunState, writeRunState } from './run-state.js';
13
+ function emptyReport() {
14
+ return {
15
+ runFoldersRemoved: 0,
16
+ historyLinesRemoved: 0,
17
+ artifactsRemoved: 0,
18
+ spoolArchivesRemoved: 0,
19
+ deferredProposalsRemoved: 0,
20
+ completedTicketsRemoved: 0,
21
+ mergedBranchesRemoved: 0,
22
+ staleBranchesRemoved: 0,
23
+ staleWorktreesRemoved: 0,
24
+ logsRotated: 0,
25
+ metricsLinesRemoved: 0,
26
+ artifactsByAgeRemoved: 0,
27
+ totalPruned: 0,
28
+ };
29
+ }
30
+ // =============================================================================
31
+ // Config helper
32
+ // =============================================================================
33
+ export function getRetentionConfig(config) {
34
+ return {
35
+ ...DEFAULT_RETENTION_CONFIG,
36
+ ...(config?.retention ?? {}),
37
+ };
38
+ }
39
+ // =============================================================================
40
+ // Individual prune functions
41
+ // =============================================================================
42
+ /**
43
+ * Sort .promptwheel/runs/ by mtime, delete oldest beyond cap.
44
+ */
45
+ export function pruneRunFolders(repoRoot, maxRuns, dryRun = false) {
46
+ const runsDir = path.join(getPromptwheelDir(repoRoot), 'runs');
47
+ if (!fs.existsSync(runsDir))
48
+ return 0;
49
+ const entries = fs.readdirSync(runsDir, { withFileTypes: true })
50
+ .filter(e => e.isDirectory())
51
+ .map(e => ({
52
+ name: e.name,
53
+ path: path.join(runsDir, e.name),
54
+ mtime: fs.statSync(path.join(runsDir, e.name)).mtimeMs,
55
+ }))
56
+ .sort((a, b) => b.mtime - a.mtime); // newest first
57
+ if (entries.length <= maxRuns)
58
+ return 0;
59
+ const toRemove = entries.slice(maxRuns);
60
+ if (!dryRun) {
61
+ for (const entry of toRemove) {
62
+ fs.rmSync(entry.path, { recursive: true, force: true });
63
+ }
64
+ }
65
+ return toRemove.length;
66
+ }
67
+ /**
68
+ * Keep last N lines in history.ndjson, rewrite file.
69
+ */
70
+ export function pruneHistory(repoRoot, maxEntries, dryRun = false) {
71
+ const historyPath = path.join(getPromptwheelDir(repoRoot), 'history.ndjson');
72
+ if (!fs.existsSync(historyPath))
73
+ return 0;
74
+ const content = fs.readFileSync(historyPath, 'utf-8');
75
+ const lines = content.split('\n').filter(l => l.trim().length > 0);
76
+ if (lines.length <= maxEntries)
77
+ return 0;
78
+ const removed = lines.length - maxEntries;
79
+ if (!dryRun) {
80
+ const kept = lines.slice(-maxEntries);
81
+ fs.writeFileSync(historyPath, kept.join('\n') + '\n');
82
+ }
83
+ return removed;
84
+ }
85
+ /**
86
+ * Within each run folder, keep newest N artifact files.
87
+ */
88
+ export function pruneArtifacts(repoRoot, maxPerRun, dryRun = false) {
89
+ const runsDir = path.join(getPromptwheelDir(repoRoot), 'runs');
90
+ if (!fs.existsSync(runsDir))
91
+ return 0;
92
+ let totalRemoved = 0;
93
+ const runDirs = fs.readdirSync(runsDir, { withFileTypes: true })
94
+ .filter(e => e.isDirectory());
95
+ for (const runDir of runDirs) {
96
+ const runPath = path.join(runsDir, runDir.name);
97
+ const files = fs.readdirSync(runPath, { withFileTypes: true })
98
+ .filter(e => e.isFile())
99
+ .map(e => ({
100
+ name: e.name,
101
+ path: path.join(runPath, e.name),
102
+ mtime: fs.statSync(path.join(runPath, e.name)).mtimeMs,
103
+ }))
104
+ .sort((a, b) => b.mtime - a.mtime);
105
+ if (files.length <= maxPerRun)
106
+ continue;
107
+ const toRemove = files.slice(maxPerRun);
108
+ if (!dryRun) {
109
+ for (const file of toRemove) {
110
+ fs.unlinkSync(file.path);
111
+ }
112
+ }
113
+ totalRemoved += toRemove.length;
114
+ }
115
+ return totalRemoved;
116
+ }
117
+ /**
118
+ * Delete oldest *.archived.ndjson in spool/ beyond cap.
119
+ */
120
+ export function pruneSpoolArchives(repoRoot, maxArchives, dryRun = false) {
121
+ const spoolDir = path.join(getPromptwheelDir(repoRoot), 'spool');
122
+ if (!fs.existsSync(spoolDir))
123
+ return 0;
124
+ const archives = fs.readdirSync(spoolDir)
125
+ .filter(f => f.endsWith('.archived.ndjson'))
126
+ .map(f => ({
127
+ name: f,
128
+ path: path.join(spoolDir, f),
129
+ mtime: fs.statSync(path.join(spoolDir, f)).mtimeMs,
130
+ }))
131
+ .sort((a, b) => b.mtime - a.mtime);
132
+ if (archives.length <= maxArchives)
133
+ return 0;
134
+ const toRemove = archives.slice(maxArchives);
135
+ if (!dryRun) {
136
+ for (const archive of toRemove) {
137
+ fs.unlinkSync(archive.path);
138
+ }
139
+ }
140
+ return toRemove.length;
141
+ }
142
+ /**
143
+ * Trim oldest deferred proposals from run-state.json beyond cap.
144
+ */
145
+ export function pruneDeferredProposals(repoRoot, maxDeferred, dryRun = false) {
146
+ const state = readRunState(repoRoot);
147
+ if (state.deferredProposals.length <= maxDeferred)
148
+ return 0;
149
+ const removed = state.deferredProposals.length - maxDeferred;
150
+ if (!dryRun) {
151
+ // Keep the newest (last added)
152
+ state.deferredProposals = state.deferredProposals.slice(-maxDeferred);
153
+ writeRunState(repoRoot, state);
154
+ }
155
+ return removed;
156
+ }
157
+ /**
158
+ * Hard-delete oldest completed tickets beyond cap.
159
+ * Uses the DatabaseAdapter interface (Postgres $1 placeholders).
160
+ */
161
+ export async function pruneCompletedTickets(adapter, maxCompleted, dryRun = false) {
162
+ if (!adapter)
163
+ return 0;
164
+ try {
165
+ const countResult = await adapter.query(`SELECT COUNT(*) as cnt FROM tickets WHERE status = 'done'`);
166
+ const count = countResult.rows[0]?.cnt ?? 0;
167
+ if (count <= maxCompleted)
168
+ return 0;
169
+ const toRemove = count - maxCompleted;
170
+ if (!dryRun) {
171
+ // Subquery to find oldest N completed tickets, then delete them.
172
+ // Works on both SQLite and Postgres.
173
+ await adapter.query(`DELETE FROM tickets WHERE id IN (
174
+ SELECT id FROM tickets WHERE status = 'done'
175
+ ORDER BY updated_at ASC LIMIT $1
176
+ )`, [toRemove]);
177
+ }
178
+ return toRemove;
179
+ }
180
+ catch {
181
+ // Table might not exist or schema differs — non-fatal
182
+ return 0;
183
+ }
184
+ }
185
+ /**
186
+ * Delete local promptwheel/* branches that are fully merged, keeping the
187
+ * newest N merged branches. Only touches local branches — never deletes
188
+ * remote branches, and never touches unmerged branches.
189
+ *
190
+ * NOT called during auto-prune-on-start — only via `promptwheel prune`.
191
+ */
192
+ export function pruneMergedBranches(repoRoot, maxMergedBranches, dryRun = false) {
193
+ try {
194
+ // Get the list of promptwheel/* branches that are fully merged into HEAD
195
+ const mergedResult = spawnSync('git', ['branch', '--merged', 'HEAD', '--list', 'promptwheel/*', '--sort=-committerdate', '--format=%(refname:short)'], { cwd: repoRoot, encoding: 'utf-8' });
196
+ if (mergedResult.status !== 0 || !mergedResult.stdout)
197
+ return 0;
198
+ // Check current branch so we never delete it
199
+ const headResult = spawnSync('git', ['branch', '--show-current'], {
200
+ cwd: repoRoot,
201
+ encoding: 'utf-8',
202
+ });
203
+ const currentBranch = headResult.stdout?.trim() ?? '';
204
+ const mergedBranches = mergedResult.stdout.trim().split('\n')
205
+ .filter(b => b && b !== currentBranch);
206
+ if (mergedBranches.length <= maxMergedBranches)
207
+ return 0;
208
+ // Keep the newest N, delete the rest
209
+ const toDelete = mergedBranches.slice(maxMergedBranches);
210
+ let removed = 0;
211
+ if (!dryRun) {
212
+ for (const branch of toDelete) {
213
+ const delResult = spawnSync('git', ['branch', '-d', branch], {
214
+ cwd: repoRoot,
215
+ encoding: 'utf-8',
216
+ });
217
+ if (delResult.status === 0)
218
+ removed++;
219
+ }
220
+ }
221
+ else {
222
+ removed = toDelete.length;
223
+ }
224
+ return removed;
225
+ }
226
+ catch {
227
+ return 0;
228
+ }
229
+ }
230
+ // =============================================================================
231
+ // Stale unmerged branch cleanup
232
+ // =============================================================================
233
+ /**
234
+ * Delete local promptwheel/tkt_* and promptwheel/milestone-* branches that are
235
+ * NOT merged and whose last commit is older than `maxDays` days. Never touches
236
+ * promptwheel-direct or user branches. Never deletes the current branch.
237
+ */
238
+ export function pruneStaleBranches(repoRoot, maxDays, dryRun = false) {
239
+ if (maxDays <= 0)
240
+ return 0;
241
+ try {
242
+ // Current branch — never delete
243
+ const headResult = spawnSync('git', ['branch', '--show-current'], {
244
+ cwd: repoRoot,
245
+ encoding: 'utf-8',
246
+ });
247
+ const currentBranch = headResult.stdout?.trim() ?? '';
248
+ const cutoff = Date.now() - (maxDays * 24 * 60 * 60 * 1000);
249
+ let removed = 0;
250
+ // Prune both tkt_* and milestone-* branches
251
+ const refPatterns = ['refs/heads/promptwheel/tkt_*', 'refs/heads/promptwheel/milestone-*'];
252
+ for (const pattern of refPatterns) {
253
+ const listResult = spawnSync('git', ['for-each-ref', '--format=%(refname:short) %(committerdate:unix)', pattern], { cwd: repoRoot, encoding: 'utf-8' });
254
+ if (listResult.status !== 0 || !listResult.stdout?.trim())
255
+ continue;
256
+ for (const line of listResult.stdout.trim().split('\n')) {
257
+ const parts = line.trim().split(' ');
258
+ if (parts.length < 2)
259
+ continue;
260
+ const branch = parts[0];
261
+ const epochSec = parseInt(parts[1], 10);
262
+ if (!branch || isNaN(epochSec))
263
+ continue;
264
+ if (branch === currentBranch)
265
+ continue;
266
+ const commitMs = epochSec * 1000;
267
+ if (commitMs >= cutoff)
268
+ continue; // too recent
269
+ if (!dryRun) {
270
+ const delResult = spawnSync('git', ['branch', '-D', branch], {
271
+ cwd: repoRoot,
272
+ encoding: 'utf-8',
273
+ });
274
+ if (delResult.status === 0)
275
+ removed++;
276
+ }
277
+ else {
278
+ removed++;
279
+ }
280
+ }
281
+ }
282
+ return removed;
283
+ }
284
+ catch {
285
+ return 0;
286
+ }
287
+ }
288
+ // =============================================================================
289
+ // Stale worktree cleanup
290
+ // =============================================================================
291
+ /**
292
+ * Remove orphaned worktrees that have no associated running process.
293
+ * Uses `git worktree list` to find stale entries and `git worktree remove --force`.
294
+ */
295
+ export function pruneStaleWorktrees(repoRoot, dryRun = false) {
296
+ try {
297
+ const worktreesDir = path.join(repoRoot, '.promptwheel', 'worktrees');
298
+ if (!fs.existsSync(worktreesDir))
299
+ return 0;
300
+ const entries = fs.readdirSync(worktreesDir).filter(e => {
301
+ const fullPath = path.join(worktreesDir, e);
302
+ return fs.statSync(fullPath).isDirectory();
303
+ });
304
+ if (entries.length === 0)
305
+ return 0;
306
+ // Get list of worktrees git knows about
307
+ const listResult = spawnSync('git', ['worktree', 'list', '--porcelain'], {
308
+ cwd: repoRoot,
309
+ encoding: 'utf-8',
310
+ });
311
+ const gitWorktrees = new Set((listResult.stdout ?? '').split('\n')
312
+ .filter(l => l.startsWith('worktree '))
313
+ .map(l => l.replace('worktree ', '').trim()));
314
+ let removed = 0;
315
+ for (const entry of entries) {
316
+ // Skip _milestone — that's managed by solo-git
317
+ if (entry === '_milestone')
318
+ continue;
319
+ const worktreePath = path.join(worktreesDir, entry);
320
+ // Check if git still tracks this worktree with a lock file
321
+ // If git doesn't know about it, it's a leftover directory
322
+ const isTracked = gitWorktrees.has(path.resolve(worktreePath));
323
+ if (!dryRun) {
324
+ try {
325
+ if (isTracked) {
326
+ spawnSync('git', ['worktree', 'remove', '--force', worktreePath], {
327
+ cwd: repoRoot,
328
+ encoding: 'utf-8',
329
+ });
330
+ }
331
+ else {
332
+ // Not a git worktree anymore — just remove the directory
333
+ fs.rmSync(worktreePath, { recursive: true, force: true });
334
+ }
335
+ // Also delete the associated branch
336
+ if (entry.startsWith('tkt_')) {
337
+ spawnSync('git', ['branch', '-D', `promptwheel/${entry}`], {
338
+ cwd: repoRoot,
339
+ encoding: 'utf-8',
340
+ });
341
+ }
342
+ removed++;
343
+ }
344
+ catch {
345
+ // Individual removal failure is non-fatal
346
+ }
347
+ }
348
+ else {
349
+ removed++;
350
+ }
351
+ }
352
+ return removed;
353
+ }
354
+ catch {
355
+ return 0;
356
+ }
357
+ }
358
+ // =============================================================================
359
+ // Stale codex session cleanup
360
+ // =============================================================================
361
+ /**
362
+ * Remove codex session rollout files from incompatible versions and old sessions.
363
+ *
364
+ * Codex stores per-thread rollout files in ~/.codex/sessions/YYYY/MM/DD/.
365
+ * After a codex upgrade, old-version rollout files cause "state db missing
366
+ * rollout path" errors on every invocation because the new version can't
367
+ * index the old format. This prunes:
368
+ * 1. Rollout files from a different major.minor codex version
369
+ * 2. Rollout files older than `maxDays` days
370
+ * 3. Abandoned rollout files — same version, recent, but the owning
371
+ * process stopped writing (mtime > 5 min stale). These are left
372
+ * behind when a codex process is killed mid-execution.
373
+ */
374
+ export function pruneStaleCodexSessions(maxDays, dryRun = false) {
375
+ if (maxDays <= 0)
376
+ return 0;
377
+ try {
378
+ const codexDir = path.join(os.homedir(), '.codex', 'sessions');
379
+ if (!fs.existsSync(codexDir))
380
+ return 0;
381
+ // Detect current codex version
382
+ const verResult = spawnSync('codex', ['--version'], { encoding: 'utf-8', timeout: 5000 });
383
+ const verMatch = verResult.stdout?.match(/(\d+\.\d+)/);
384
+ const currentMajorMinor = verMatch?.[1] ?? null;
385
+ // Only prune abandoned threads if no codex process is running
386
+ const codexRunning = isCodexRunning();
387
+ const cutoff = Date.now() - (maxDays * 24 * 60 * 60 * 1000);
388
+ const staleCutoff = Date.now() - (5 * 60 * 1000); // 5 minutes
389
+ let removed = 0;
390
+ // Walk YYYY/MM/DD directory structure
391
+ for (const year of safeReaddir(codexDir)) {
392
+ const yearPath = path.join(codexDir, year);
393
+ if (!safeStat(yearPath)?.isDirectory())
394
+ continue;
395
+ for (const month of safeReaddir(yearPath)) {
396
+ const monthPath = path.join(yearPath, month);
397
+ if (!safeStat(monthPath)?.isDirectory())
398
+ continue;
399
+ for (const day of safeReaddir(monthPath)) {
400
+ const dayPath = path.join(monthPath, day);
401
+ if (!safeStat(dayPath)?.isDirectory())
402
+ continue;
403
+ for (const file of safeReaddir(dayPath)) {
404
+ const filePath = path.join(dayPath, file);
405
+ try {
406
+ const stat = fs.statSync(filePath);
407
+ let shouldRemove = stat.mtimeMs < cutoff;
408
+ // Check version incompatibility for rollout files
409
+ if (!shouldRemove && currentMajorMinor && file.endsWith('.jsonl')) {
410
+ const firstLine = readFirstLine(filePath);
411
+ if (firstLine) {
412
+ const verInFile = firstLine.match(/"cli_version":"(\d+\.\d+)/);
413
+ if (verInFile && verInFile[1] !== currentMajorMinor) {
414
+ shouldRemove = true;
415
+ }
416
+ }
417
+ }
418
+ // Check for abandoned rollout files — stale mtime, no codex running.
419
+ // These are left behind when a codex process is killed mid-execution.
420
+ // Only prune if no codex process is currently running (safe window).
421
+ if (!shouldRemove && !codexRunning && file.endsWith('.jsonl') && stat.mtimeMs < staleCutoff) {
422
+ shouldRemove = true;
423
+ }
424
+ if (shouldRemove) {
425
+ if (!dryRun)
426
+ fs.unlinkSync(filePath);
427
+ removed++;
428
+ }
429
+ }
430
+ catch { /* skip */ }
431
+ }
432
+ // Remove empty day directories
433
+ try {
434
+ if (!dryRun && safeReaddir(dayPath).length === 0)
435
+ fs.rmdirSync(dayPath);
436
+ }
437
+ catch { /* skip */ }
438
+ }
439
+ try {
440
+ if (!dryRun && safeReaddir(monthPath).length === 0)
441
+ fs.rmdirSync(monthPath);
442
+ }
443
+ catch { /* skip */ }
444
+ }
445
+ try {
446
+ if (!dryRun && safeReaddir(yearPath).length === 0)
447
+ fs.rmdirSync(yearPath);
448
+ }
449
+ catch { /* skip */ }
450
+ }
451
+ return removed;
452
+ }
453
+ catch {
454
+ return 0;
455
+ }
456
+ }
457
+ /**
458
+ * Check if any codex process is currently running.
459
+ * Used to gate abandoned-thread pruning — if codex is running,
460
+ * skip aggressive pruning to avoid deleting active rollout files.
461
+ */
462
+ function isCodexRunning() {
463
+ try {
464
+ const result = spawnSync('pgrep', ['-f', 'codex'], { encoding: 'utf-8', timeout: 5000 });
465
+ return result.status === 0 && !!result.stdout?.trim();
466
+ }
467
+ catch {
468
+ return true; // assume running if we can't check — safer to skip pruning
469
+ }
470
+ }
471
+ function safeReaddir(dir) {
472
+ try {
473
+ return fs.readdirSync(dir);
474
+ }
475
+ catch {
476
+ return [];
477
+ }
478
+ }
479
+ function safeStat(p) {
480
+ try {
481
+ return fs.statSync(p);
482
+ }
483
+ catch {
484
+ return null;
485
+ }
486
+ }
487
+ function readFirstLine(filePath) {
488
+ try {
489
+ const fd = fs.openSync(filePath, 'r');
490
+ const buf = Buffer.alloc(512);
491
+ const bytesRead = fs.readSync(fd, buf, 0, 512, 0);
492
+ fs.closeSync(fd);
493
+ const text = buf.toString('utf-8', 0, bytesRead);
494
+ const nl = text.indexOf('\n');
495
+ return nl >= 0 ? text.slice(0, nl) : text;
496
+ }
497
+ catch {
498
+ return null;
499
+ }
500
+ }
501
+ // =============================================================================
502
+ // Log rotation
503
+ // =============================================================================
504
+ /**
505
+ * Rotate tui.log when it exceeds maxBytes.
506
+ * Renames current to tui.log.1, starts fresh.
507
+ * Only keeps 1 backup — previous .1 is overwritten.
508
+ */
509
+ export function rotateLogs(repoRoot, maxBytes, dryRun = false) {
510
+ if (maxBytes <= 0)
511
+ return 0;
512
+ const bsDir = getPromptwheelDir(repoRoot);
513
+ const logPath = path.join(bsDir, 'tui.log');
514
+ if (!fs.existsSync(logPath))
515
+ return 0;
516
+ try {
517
+ const stat = fs.statSync(logPath);
518
+ if (stat.size <= maxBytes)
519
+ return 0;
520
+ if (!dryRun) {
521
+ const backupPath = path.join(bsDir, 'tui.log.1');
522
+ // Overwrite previous backup
523
+ try {
524
+ fs.unlinkSync(backupPath);
525
+ }
526
+ catch { /* may not exist */ }
527
+ fs.renameSync(logPath, backupPath);
528
+ }
529
+ return 1;
530
+ }
531
+ catch {
532
+ return 0;
533
+ }
534
+ }
535
+ /**
536
+ * Keep last N lines in metrics.ndjson, rewrite file.
537
+ */
538
+ export function pruneMetrics(repoRoot, maxEntries, dryRun = false) {
539
+ if (maxEntries <= 0)
540
+ return 0;
541
+ const metricsPath = path.join(getPromptwheelDir(repoRoot), 'metrics.ndjson');
542
+ if (!fs.existsSync(metricsPath))
543
+ return 0;
544
+ const content = fs.readFileSync(metricsPath, 'utf-8');
545
+ const lines = content.split('\n').filter(l => l.trim().length > 0);
546
+ if (lines.length <= maxEntries)
547
+ return 0;
548
+ const removed = lines.length - maxEntries;
549
+ if (!dryRun) {
550
+ const kept = lines.slice(-maxEntries);
551
+ fs.writeFileSync(metricsPath, kept.join('\n') + '\n');
552
+ }
553
+ return removed;
554
+ }
555
+ // =============================================================================
556
+ // Time-based artifact expiry
557
+ // =============================================================================
558
+ /**
559
+ * Delete artifact files older than maxDays from .promptwheel/artifacts/.
560
+ * Walks all subdirectories (executions/, diffs/, runs/, violations/).
561
+ * Removes empty subdirectories afterward.
562
+ */
563
+ export function pruneArtifactsByAge(repoRoot, maxDays, dryRun = false) {
564
+ if (maxDays <= 0)
565
+ return 0;
566
+ const artifactsDir = path.join(getPromptwheelDir(repoRoot), 'artifacts');
567
+ if (!fs.existsSync(artifactsDir))
568
+ return 0;
569
+ const cutoff = Date.now() - (maxDays * 24 * 60 * 60 * 1000);
570
+ let removed = 0;
571
+ for (const subdir of safeReaddir(artifactsDir)) {
572
+ const subdirPath = path.join(artifactsDir, subdir);
573
+ if (!safeStat(subdirPath)?.isDirectory())
574
+ continue;
575
+ for (const file of safeReaddir(subdirPath)) {
576
+ const filePath = path.join(subdirPath, file);
577
+ try {
578
+ const stat = fs.statSync(filePath);
579
+ if (!stat.isFile())
580
+ continue;
581
+ if (stat.mtimeMs < cutoff) {
582
+ if (!dryRun)
583
+ fs.unlinkSync(filePath);
584
+ removed++;
585
+ }
586
+ }
587
+ catch { /* skip */ }
588
+ }
589
+ // Remove empty subdirectory
590
+ try {
591
+ if (!dryRun && safeReaddir(subdirPath).length === 0) {
592
+ fs.rmdirSync(subdirPath);
593
+ }
594
+ }
595
+ catch { /* skip */ }
596
+ }
597
+ return removed;
598
+ }
599
+ // =============================================================================
600
+ // Git worktree prune
601
+ // =============================================================================
602
+ /**
603
+ * Run `git worktree prune` to clean dangling worktree refs that manual
604
+ * cleanup misses (e.g. worktree directories deleted without `git worktree remove`).
605
+ */
606
+ export function gitWorktreePrune(repoRoot) {
607
+ try {
608
+ spawnSync('git', ['worktree', 'prune'], {
609
+ cwd: repoRoot,
610
+ encoding: 'utf-8',
611
+ timeout: 10_000,
612
+ });
613
+ }
614
+ catch { /* non-fatal */ }
615
+ }
616
+ // =============================================================================
617
+ // Session lock file
618
+ // =============================================================================
619
+ /**
620
+ * Write a session lock file (.promptwheel/session.pid) with the current PID.
621
+ * Returns true if lock acquired, false if another live session holds it.
622
+ */
623
+ export function acquireSessionLock(repoRoot) {
624
+ const lockPath = path.join(getPromptwheelDir(repoRoot), 'session.pid');
625
+ // Check existing lock
626
+ if (fs.existsSync(lockPath)) {
627
+ try {
628
+ const content = fs.readFileSync(lockPath, 'utf-8').trim();
629
+ const pid = parseInt(content, 10);
630
+ if (!isNaN(pid) && pid > 0) {
631
+ // Check if PID is still alive
632
+ try {
633
+ process.kill(pid, 0); // signal 0 = existence check only
634
+ // Process is alive — another session is running
635
+ return { acquired: false };
636
+ }
637
+ catch {
638
+ // Process is dead — stale lock, clean it up
639
+ fs.unlinkSync(lockPath);
640
+ // Fall through to acquire
641
+ return acquireLock(lockPath, pid);
642
+ }
643
+ }
644
+ }
645
+ catch {
646
+ // Corrupt lock file — remove and proceed
647
+ try {
648
+ fs.unlinkSync(lockPath);
649
+ }
650
+ catch { /* skip */ }
651
+ }
652
+ }
653
+ return acquireLock(lockPath);
654
+ }
655
+ function acquireLock(lockPath, stalePid) {
656
+ try {
657
+ fs.writeFileSync(lockPath, String(process.pid));
658
+ return { acquired: true, stalePid };
659
+ }
660
+ catch {
661
+ return { acquired: false };
662
+ }
663
+ }
664
+ /**
665
+ * Release the session lock file on clean shutdown.
666
+ */
667
+ export function releaseSessionLock(repoRoot) {
668
+ const lockPath = path.join(getPromptwheelDir(repoRoot), 'session.pid');
669
+ try {
670
+ // Only remove if we own it
671
+ const content = fs.readFileSync(lockPath, 'utf-8').trim();
672
+ if (parseInt(content, 10) === process.pid) {
673
+ fs.unlinkSync(lockPath);
674
+ }
675
+ }
676
+ catch { /* non-fatal */ }
677
+ }
678
+ // =============================================================================
679
+ // Main prune (sync — file-system only)
680
+ // =============================================================================
681
+ export function pruneAll(repoRoot, config, dryRun = false) {
682
+ const report = emptyReport();
683
+ report.runFoldersRemoved = pruneRunFolders(repoRoot, config.maxRuns, dryRun);
684
+ report.historyLinesRemoved = pruneHistory(repoRoot, config.maxHistoryEntries, dryRun);
685
+ report.artifactsRemoved = pruneArtifacts(repoRoot, config.maxArtifactsPerRun, dryRun);
686
+ report.spoolArchivesRemoved = pruneSpoolArchives(repoRoot, config.maxSpoolArchives, dryRun);
687
+ report.deferredProposalsRemoved = pruneDeferredProposals(repoRoot, config.maxDeferredProposals, dryRun);
688
+ report.staleWorktreesRemoved = pruneStaleWorktrees(repoRoot, dryRun);
689
+ // Branch pruning is NOT run here — only via explicit `promptwheel prune`
690
+ // Log rotation & metrics pruning
691
+ report.logsRotated = rotateLogs(repoRoot, config.maxLogSizeBytes, dryRun);
692
+ report.metricsLinesRemoved = pruneMetrics(repoRoot, config.maxMetricsEntries, dryRun);
693
+ report.artifactsByAgeRemoved = pruneArtifactsByAge(repoRoot, config.maxArtifactAgeDays, dryRun);
694
+ // Git worktree prune (dangling refs)
695
+ if (!dryRun)
696
+ gitWorktreePrune(repoRoot);
697
+ report.totalPruned =
698
+ report.runFoldersRemoved +
699
+ report.historyLinesRemoved +
700
+ report.artifactsRemoved +
701
+ report.spoolArchivesRemoved +
702
+ report.deferredProposalsRemoved +
703
+ report.staleWorktreesRemoved +
704
+ report.logsRotated +
705
+ report.metricsLinesRemoved +
706
+ report.artifactsByAgeRemoved;
707
+ return report;
708
+ }
709
+ /**
710
+ * Full prune including async DB operations.
711
+ */
712
+ export async function pruneAllAsync(repoRoot, config, adapter = null, dryRun = false) {
713
+ const report = pruneAll(repoRoot, config, dryRun);
714
+ report.completedTicketsRemoved = await pruneCompletedTickets(adapter, config.maxCompletedTickets, dryRun);
715
+ report.mergedBranchesRemoved = pruneMergedBranches(repoRoot, config.maxMergedBranches, dryRun);
716
+ report.staleBranchesRemoved = pruneStaleBranches(repoRoot, config.maxStaleBranchDays, dryRun);
717
+ report.totalPruned += report.completedTicketsRemoved + report.mergedBranchesRemoved + report.staleBranchesRemoved;
718
+ return report;
719
+ }
720
+ /**
721
+ * Format a prune report for display.
722
+ */
723
+ export function formatPruneReport(report, dryRun = false) {
724
+ const prefix = dryRun ? 'Would remove' : 'Removed';
725
+ const lines = [];
726
+ if (report.runFoldersRemoved > 0) {
727
+ lines.push(` ${prefix} ${report.runFoldersRemoved} run folder(s)`);
728
+ }
729
+ if (report.historyLinesRemoved > 0) {
730
+ lines.push(` ${prefix} ${report.historyLinesRemoved} history line(s)`);
731
+ }
732
+ if (report.artifactsRemoved > 0) {
733
+ lines.push(` ${prefix} ${report.artifactsRemoved} artifact file(s)`);
734
+ }
735
+ if (report.spoolArchivesRemoved > 0) {
736
+ lines.push(` ${prefix} ${report.spoolArchivesRemoved} spool archive(s)`);
737
+ }
738
+ if (report.deferredProposalsRemoved > 0) {
739
+ lines.push(` ${prefix} ${report.deferredProposalsRemoved} deferred proposal(s)`);
740
+ }
741
+ if (report.completedTicketsRemoved > 0) {
742
+ lines.push(` ${prefix} ${report.completedTicketsRemoved} completed ticket(s)`);
743
+ }
744
+ if (report.mergedBranchesRemoved > 0) {
745
+ lines.push(` ${prefix} ${report.mergedBranchesRemoved} merged branch(es)`);
746
+ }
747
+ if (report.staleBranchesRemoved > 0) {
748
+ lines.push(` ${prefix} ${report.staleBranchesRemoved} stale ticket branch(es)`);
749
+ }
750
+ if (report.staleWorktreesRemoved > 0) {
751
+ lines.push(` ${prefix} ${report.staleWorktreesRemoved} stale worktree(s)`);
752
+ }
753
+ if (report.logsRotated > 0) {
754
+ lines.push(` Rotated tui.log`);
755
+ }
756
+ if (report.metricsLinesRemoved > 0) {
757
+ lines.push(` ${prefix} ${report.metricsLinesRemoved} metrics line(s)`);
758
+ }
759
+ if (report.artifactsByAgeRemoved > 0) {
760
+ lines.push(` ${prefix} ${report.artifactsByAgeRemoved} expired artifact(s)`);
761
+ }
762
+ if (lines.length === 0) {
763
+ return ' Nothing to prune.';
764
+ }
765
+ return lines.join('\n');
766
+ }
767
+ //# sourceMappingURL=retention.js.map