@cluesmith/codev 2.0.0-rc.6 → 2.0.0-rc.61

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 (377) hide show
  1. package/bin/af.js +2 -2
  2. package/bin/consult.js +1 -1
  3. package/bin/porch.js +6 -35
  4. package/dashboard/dist/assets/index-CXloFYpB.css +32 -0
  5. package/dashboard/dist/assets/index-Ca2fjOJf.js +131 -0
  6. package/dashboard/dist/assets/index-Ca2fjOJf.js.map +1 -0
  7. package/dashboard/dist/index.html +14 -0
  8. package/dist/agent-farm/cli.d.ts.map +1 -1
  9. package/dist/agent-farm/cli.js +94 -65
  10. package/dist/agent-farm/cli.js.map +1 -1
  11. package/dist/agent-farm/commands/architect.d.ts.map +1 -1
  12. package/dist/agent-farm/commands/architect.js +13 -6
  13. package/dist/agent-farm/commands/architect.js.map +1 -1
  14. package/dist/agent-farm/commands/attach.d.ts +13 -0
  15. package/dist/agent-farm/commands/attach.d.ts.map +1 -0
  16. package/dist/agent-farm/commands/attach.js +202 -0
  17. package/dist/agent-farm/commands/attach.js.map +1 -0
  18. package/dist/agent-farm/commands/cleanup.d.ts.map +1 -1
  19. package/dist/agent-farm/commands/cleanup.js +30 -3
  20. package/dist/agent-farm/commands/cleanup.js.map +1 -1
  21. package/dist/agent-farm/commands/consult.js +1 -1
  22. package/dist/agent-farm/commands/consult.js.map +1 -1
  23. package/dist/agent-farm/commands/index.d.ts +2 -2
  24. package/dist/agent-farm/commands/index.d.ts.map +1 -1
  25. package/dist/agent-farm/commands/index.js +2 -2
  26. package/dist/agent-farm/commands/index.js.map +1 -1
  27. package/dist/agent-farm/commands/open.d.ts +4 -2
  28. package/dist/agent-farm/commands/open.d.ts.map +1 -1
  29. package/dist/agent-farm/commands/open.js +34 -70
  30. package/dist/agent-farm/commands/open.js.map +1 -1
  31. package/dist/agent-farm/commands/send.d.ts.map +1 -1
  32. package/dist/agent-farm/commands/send.js +55 -17
  33. package/dist/agent-farm/commands/send.js.map +1 -1
  34. package/dist/agent-farm/commands/shell.d.ts +15 -0
  35. package/dist/agent-farm/commands/shell.d.ts.map +1 -0
  36. package/dist/agent-farm/commands/shell.js +61 -0
  37. package/dist/agent-farm/commands/shell.js.map +1 -0
  38. package/dist/agent-farm/commands/spawn.d.ts.map +1 -1
  39. package/dist/agent-farm/commands/spawn.js +503 -226
  40. package/dist/agent-farm/commands/spawn.js.map +1 -1
  41. package/dist/agent-farm/commands/start.d.ts +3 -0
  42. package/dist/agent-farm/commands/start.d.ts.map +1 -1
  43. package/dist/agent-farm/commands/start.js +58 -265
  44. package/dist/agent-farm/commands/start.js.map +1 -1
  45. package/dist/agent-farm/commands/status.d.ts +2 -0
  46. package/dist/agent-farm/commands/status.d.ts.map +1 -1
  47. package/dist/agent-farm/commands/status.js +61 -3
  48. package/dist/agent-farm/commands/status.js.map +1 -1
  49. package/dist/agent-farm/commands/stop.d.ts +6 -0
  50. package/dist/agent-farm/commands/stop.d.ts.map +1 -1
  51. package/dist/agent-farm/commands/stop.js +116 -12
  52. package/dist/agent-farm/commands/stop.js.map +1 -1
  53. package/dist/agent-farm/commands/tower.d.ts +9 -0
  54. package/dist/agent-farm/commands/tower.d.ts.map +1 -1
  55. package/dist/agent-farm/commands/tower.js +59 -19
  56. package/dist/agent-farm/commands/tower.js.map +1 -1
  57. package/dist/agent-farm/db/index.d.ts.map +1 -1
  58. package/dist/agent-farm/db/index.js +124 -0
  59. package/dist/agent-farm/db/index.js.map +1 -1
  60. package/dist/agent-farm/db/schema.d.ts +2 -2
  61. package/dist/agent-farm/db/schema.d.ts.map +1 -1
  62. package/dist/agent-farm/db/schema.js +26 -5
  63. package/dist/agent-farm/db/schema.js.map +1 -1
  64. package/dist/agent-farm/db/types.d.ts +3 -0
  65. package/dist/agent-farm/db/types.d.ts.map +1 -1
  66. package/dist/agent-farm/db/types.js +3 -0
  67. package/dist/agent-farm/db/types.js.map +1 -1
  68. package/dist/agent-farm/hq-connector.d.ts +2 -6
  69. package/dist/agent-farm/hq-connector.d.ts.map +1 -1
  70. package/dist/agent-farm/hq-connector.js +2 -17
  71. package/dist/agent-farm/hq-connector.js.map +1 -1
  72. package/dist/agent-farm/lib/tower-client.d.ts +157 -0
  73. package/dist/agent-farm/lib/tower-client.d.ts.map +1 -0
  74. package/dist/agent-farm/lib/tower-client.js +223 -0
  75. package/dist/agent-farm/lib/tower-client.js.map +1 -0
  76. package/dist/agent-farm/servers/tower-server.js +2340 -109
  77. package/dist/agent-farm/servers/tower-server.js.map +1 -1
  78. package/dist/agent-farm/state.d.ts +4 -10
  79. package/dist/agent-farm/state.d.ts.map +1 -1
  80. package/dist/agent-farm/state.js +30 -31
  81. package/dist/agent-farm/state.js.map +1 -1
  82. package/dist/agent-farm/types.d.ts +48 -1
  83. package/dist/agent-farm/types.d.ts.map +1 -1
  84. package/dist/agent-farm/utils/config.d.ts.map +1 -1
  85. package/dist/agent-farm/utils/config.js +13 -14
  86. package/dist/agent-farm/utils/config.js.map +1 -1
  87. package/dist/agent-farm/utils/deps.d.ts.map +1 -1
  88. package/dist/agent-farm/utils/deps.js +0 -16
  89. package/dist/agent-farm/utils/deps.js.map +1 -1
  90. package/dist/agent-farm/utils/notifications.d.ts +30 -0
  91. package/dist/agent-farm/utils/notifications.d.ts.map +1 -0
  92. package/dist/agent-farm/utils/notifications.js +121 -0
  93. package/dist/agent-farm/utils/notifications.js.map +1 -0
  94. package/dist/agent-farm/utils/port-registry.d.ts +0 -1
  95. package/dist/agent-farm/utils/port-registry.d.ts.map +1 -1
  96. package/dist/agent-farm/utils/port-registry.js +1 -1
  97. package/dist/agent-farm/utils/port-registry.js.map +1 -1
  98. package/dist/agent-farm/utils/server-utils.d.ts +4 -4
  99. package/dist/agent-farm/utils/server-utils.d.ts.map +1 -1
  100. package/dist/agent-farm/utils/server-utils.js +4 -15
  101. package/dist/agent-farm/utils/server-utils.js.map +1 -1
  102. package/dist/agent-farm/utils/shell.d.ts +9 -22
  103. package/dist/agent-farm/utils/shell.d.ts.map +1 -1
  104. package/dist/agent-farm/utils/shell.js +34 -34
  105. package/dist/agent-farm/utils/shell.js.map +1 -1
  106. package/dist/agent-farm/utils/terminal-ports.d.ts +1 -1
  107. package/dist/agent-farm/utils/terminal-ports.js +1 -1
  108. package/dist/cli.d.ts.map +1 -1
  109. package/dist/cli.js +9 -54
  110. package/dist/cli.js.map +1 -1
  111. package/dist/commands/adopt.d.ts.map +1 -1
  112. package/dist/commands/adopt.js +49 -4
  113. package/dist/commands/adopt.js.map +1 -1
  114. package/dist/commands/consult/index.d.ts +2 -0
  115. package/dist/commands/consult/index.d.ts.map +1 -1
  116. package/dist/commands/consult/index.js +103 -6
  117. package/dist/commands/consult/index.js.map +1 -1
  118. package/dist/commands/doctor.d.ts.map +1 -1
  119. package/dist/commands/doctor.js +0 -15
  120. package/dist/commands/doctor.js.map +1 -1
  121. package/dist/commands/init.d.ts.map +1 -1
  122. package/dist/commands/init.js +41 -2
  123. package/dist/commands/init.js.map +1 -1
  124. package/dist/commands/porch/build-counter.d.ts +5 -0
  125. package/dist/commands/porch/build-counter.d.ts.map +1 -0
  126. package/dist/commands/porch/build-counter.js +5 -0
  127. package/dist/commands/porch/build-counter.js.map +1 -0
  128. package/dist/commands/porch/checks.d.ts +17 -29
  129. package/dist/commands/porch/checks.d.ts.map +1 -1
  130. package/dist/commands/porch/checks.js +96 -144
  131. package/dist/commands/porch/checks.js.map +1 -1
  132. package/dist/commands/porch/index.d.ts +21 -43
  133. package/dist/commands/porch/index.d.ts.map +1 -1
  134. package/dist/commands/porch/index.js +418 -1123
  135. package/dist/commands/porch/index.js.map +1 -1
  136. package/dist/commands/porch/next.d.ts +22 -0
  137. package/dist/commands/porch/next.d.ts.map +1 -0
  138. package/dist/commands/porch/next.js +481 -0
  139. package/dist/commands/porch/next.js.map +1 -0
  140. package/dist/commands/porch/plan.d.ts +70 -0
  141. package/dist/commands/porch/plan.d.ts.map +1 -0
  142. package/dist/commands/porch/plan.js +190 -0
  143. package/dist/commands/porch/plan.js.map +1 -0
  144. package/dist/commands/porch/prompts.d.ts +19 -0
  145. package/dist/commands/porch/prompts.d.ts.map +1 -0
  146. package/dist/commands/porch/prompts.js +255 -0
  147. package/dist/commands/porch/prompts.js.map +1 -0
  148. package/dist/commands/porch/protocol.d.ts +59 -0
  149. package/dist/commands/porch/protocol.d.ts.map +1 -0
  150. package/dist/commands/porch/protocol.js +294 -0
  151. package/dist/commands/porch/protocol.js.map +1 -0
  152. package/dist/commands/porch/state.d.ts +23 -112
  153. package/dist/commands/porch/state.d.ts.map +1 -1
  154. package/dist/commands/porch/state.js +81 -699
  155. package/dist/commands/porch/state.js.map +1 -1
  156. package/dist/commands/porch/types.d.ts +99 -164
  157. package/dist/commands/porch/types.d.ts.map +1 -1
  158. package/dist/commands/porch/types.js +2 -1
  159. package/dist/commands/porch/types.js.map +1 -1
  160. package/dist/commands/porch/verdict.d.ts +31 -0
  161. package/dist/commands/porch/verdict.d.ts.map +1 -0
  162. package/dist/commands/porch/verdict.js +59 -0
  163. package/dist/commands/porch/verdict.js.map +1 -0
  164. package/dist/commands/update.d.ts.map +1 -1
  165. package/dist/commands/update.js +31 -0
  166. package/dist/commands/update.js.map +1 -1
  167. package/dist/lib/scaffold.d.ts +37 -0
  168. package/dist/lib/scaffold.d.ts.map +1 -1
  169. package/dist/lib/scaffold.js +114 -0
  170. package/dist/lib/scaffold.js.map +1 -1
  171. package/dist/terminal/index.d.ts +8 -0
  172. package/dist/terminal/index.d.ts.map +1 -0
  173. package/dist/terminal/index.js +5 -0
  174. package/dist/terminal/index.js.map +1 -0
  175. package/dist/terminal/pty-manager.d.ts +60 -0
  176. package/dist/terminal/pty-manager.d.ts.map +1 -0
  177. package/dist/terminal/pty-manager.js +334 -0
  178. package/dist/terminal/pty-manager.js.map +1 -0
  179. package/dist/terminal/pty-session.d.ts +79 -0
  180. package/dist/terminal/pty-session.d.ts.map +1 -0
  181. package/dist/terminal/pty-session.js +215 -0
  182. package/dist/terminal/pty-session.js.map +1 -0
  183. package/dist/terminal/ring-buffer.d.ts +27 -0
  184. package/dist/terminal/ring-buffer.d.ts.map +1 -0
  185. package/dist/terminal/ring-buffer.js +74 -0
  186. package/dist/terminal/ring-buffer.js.map +1 -0
  187. package/dist/terminal/ws-protocol.d.ts +27 -0
  188. package/dist/terminal/ws-protocol.d.ts.map +1 -0
  189. package/dist/terminal/ws-protocol.js +44 -0
  190. package/dist/terminal/ws-protocol.js.map +1 -0
  191. package/package.json +18 -5
  192. package/skeleton/.claude/skills/af/SKILL.md +74 -0
  193. package/skeleton/.claude/skills/codev/SKILL.md +41 -0
  194. package/skeleton/.claude/skills/consult/SKILL.md +81 -0
  195. package/skeleton/.claude/skills/generate-image/SKILL.md +56 -0
  196. package/skeleton/DEPENDENCIES.md +3 -29
  197. package/skeleton/builders.md +1 -1
  198. package/skeleton/consult-types/impl-review.md +9 -0
  199. package/skeleton/porch/prompts/defend.md +1 -1
  200. package/skeleton/porch/prompts/evaluate.md +2 -2
  201. package/skeleton/porch/prompts/implement.md +1 -1
  202. package/skeleton/porch/prompts/plan.md +1 -1
  203. package/skeleton/porch/prompts/review.md +4 -4
  204. package/skeleton/porch/prompts/specify.md +1 -1
  205. package/skeleton/porch/prompts/understand.md +2 -2
  206. package/skeleton/protocol-schema.json +282 -0
  207. package/skeleton/protocols/bugfix/builder-prompt.md +54 -0
  208. package/skeleton/protocols/bugfix/prompts/fix.md +77 -0
  209. package/skeleton/protocols/bugfix/prompts/investigate.md +77 -0
  210. package/skeleton/protocols/bugfix/prompts/pr.md +61 -0
  211. package/skeleton/protocols/bugfix/protocol.json +19 -2
  212. package/skeleton/protocols/experiment/builder-prompt.md +52 -0
  213. package/skeleton/protocols/experiment/protocol.json +101 -0
  214. package/skeleton/protocols/experiment/protocol.md +3 -3
  215. package/skeleton/protocols/experiment/templates/notes.md +1 -1
  216. package/skeleton/protocols/maintain/builder-prompt.md +46 -0
  217. package/skeleton/protocols/maintain/prompts/audit.md +111 -0
  218. package/skeleton/protocols/maintain/prompts/clean.md +91 -0
  219. package/skeleton/protocols/maintain/prompts/sync.md +113 -0
  220. package/skeleton/protocols/maintain/prompts/verify.md +110 -0
  221. package/skeleton/protocols/maintain/protocol.json +141 -0
  222. package/skeleton/protocols/maintain/protocol.md +14 -8
  223. package/skeleton/protocols/protocol-schema.json +54 -1
  224. package/skeleton/protocols/spir/builder-prompt.md +59 -0
  225. package/skeleton/protocols/spir/prompts/implement.md +208 -0
  226. package/skeleton/protocols/{spider → spir}/prompts/plan.md +6 -70
  227. package/skeleton/protocols/{spider → spir}/prompts/review.md +7 -25
  228. package/skeleton/protocols/{spider → spir}/prompts/specify.md +33 -61
  229. package/skeleton/protocols/spir/protocol.json +152 -0
  230. package/skeleton/protocols/{spider → spir}/protocol.md +35 -21
  231. package/skeleton/protocols/{spider → spir}/templates/plan.md +14 -0
  232. package/skeleton/protocols/{spider → spir}/templates/review.md +1 -1
  233. package/skeleton/protocols/tick/builder-prompt.md +56 -0
  234. package/skeleton/protocols/tick/protocol.json +7 -2
  235. package/skeleton/protocols/tick/protocol.md +18 -18
  236. package/skeleton/protocols/tick/templates/review.md +1 -1
  237. package/skeleton/resources/commands/agent-farm.md +25 -43
  238. package/skeleton/resources/commands/overview.md +7 -17
  239. package/skeleton/resources/workflow-reference.md +4 -4
  240. package/skeleton/roles/architect.md +152 -315
  241. package/skeleton/roles/builder.md +109 -218
  242. package/skeleton/templates/AGENTS.md +2 -2
  243. package/skeleton/templates/CLAUDE.md +2 -2
  244. package/skeleton/templates/cheatsheet.md +7 -5
  245. package/skeleton/templates/projectlist.md +1 -1
  246. package/templates/dashboard/index.html +17 -43
  247. package/templates/dashboard/js/dialogs.js +7 -7
  248. package/templates/dashboard/js/files.js +2 -2
  249. package/templates/dashboard/js/main.js +4 -4
  250. package/templates/dashboard/js/projects.js +3 -3
  251. package/templates/dashboard/js/tabs.js +1 -1
  252. package/templates/dashboard/js/utils.js +22 -87
  253. package/templates/open.html +26 -0
  254. package/templates/tower.html +542 -27
  255. package/dist/agent-farm/commands/kickoff.d.ts +0 -19
  256. package/dist/agent-farm/commands/kickoff.d.ts.map +0 -1
  257. package/dist/agent-farm/commands/kickoff.js +0 -331
  258. package/dist/agent-farm/commands/kickoff.js.map +0 -1
  259. package/dist/agent-farm/commands/rename.d.ts +0 -13
  260. package/dist/agent-farm/commands/rename.d.ts.map +0 -1
  261. package/dist/agent-farm/commands/rename.js +0 -33
  262. package/dist/agent-farm/commands/rename.js.map +0 -1
  263. package/dist/agent-farm/commands/tutorial.d.ts +0 -10
  264. package/dist/agent-farm/commands/tutorial.d.ts.map +0 -1
  265. package/dist/agent-farm/commands/tutorial.js +0 -49
  266. package/dist/agent-farm/commands/tutorial.js.map +0 -1
  267. package/dist/agent-farm/commands/util.d.ts +0 -15
  268. package/dist/agent-farm/commands/util.d.ts.map +0 -1
  269. package/dist/agent-farm/commands/util.js +0 -108
  270. package/dist/agent-farm/commands/util.js.map +0 -1
  271. package/dist/agent-farm/servers/dashboard-server.d.ts +0 -7
  272. package/dist/agent-farm/servers/dashboard-server.d.ts.map +0 -1
  273. package/dist/agent-farm/servers/dashboard-server.js +0 -1872
  274. package/dist/agent-farm/servers/dashboard-server.js.map +0 -1
  275. package/dist/agent-farm/servers/open-server.d.ts +0 -7
  276. package/dist/agent-farm/servers/open-server.d.ts.map +0 -1
  277. package/dist/agent-farm/servers/open-server.js +0 -315
  278. package/dist/agent-farm/servers/open-server.js.map +0 -1
  279. package/dist/agent-farm/tutorial/index.d.ts +0 -8
  280. package/dist/agent-farm/tutorial/index.d.ts.map +0 -1
  281. package/dist/agent-farm/tutorial/index.js +0 -8
  282. package/dist/agent-farm/tutorial/index.js.map +0 -1
  283. package/dist/agent-farm/tutorial/prompts.d.ts +0 -57
  284. package/dist/agent-farm/tutorial/prompts.d.ts.map +0 -1
  285. package/dist/agent-farm/tutorial/prompts.js +0 -147
  286. package/dist/agent-farm/tutorial/prompts.js.map +0 -1
  287. package/dist/agent-farm/tutorial/runner.d.ts +0 -52
  288. package/dist/agent-farm/tutorial/runner.d.ts.map +0 -1
  289. package/dist/agent-farm/tutorial/runner.js +0 -204
  290. package/dist/agent-farm/tutorial/runner.js.map +0 -1
  291. package/dist/agent-farm/tutorial/state.d.ts +0 -26
  292. package/dist/agent-farm/tutorial/state.d.ts.map +0 -1
  293. package/dist/agent-farm/tutorial/state.js +0 -89
  294. package/dist/agent-farm/tutorial/state.js.map +0 -1
  295. package/dist/agent-farm/tutorial/steps/first-spec.d.ts +0 -7
  296. package/dist/agent-farm/tutorial/steps/first-spec.d.ts.map +0 -1
  297. package/dist/agent-farm/tutorial/steps/first-spec.js +0 -136
  298. package/dist/agent-farm/tutorial/steps/first-spec.js.map +0 -1
  299. package/dist/agent-farm/tutorial/steps/implementation.d.ts +0 -7
  300. package/dist/agent-farm/tutorial/steps/implementation.d.ts.map +0 -1
  301. package/dist/agent-farm/tutorial/steps/implementation.js +0 -76
  302. package/dist/agent-farm/tutorial/steps/implementation.js.map +0 -1
  303. package/dist/agent-farm/tutorial/steps/index.d.ts +0 -10
  304. package/dist/agent-farm/tutorial/steps/index.d.ts.map +0 -1
  305. package/dist/agent-farm/tutorial/steps/index.js +0 -10
  306. package/dist/agent-farm/tutorial/steps/index.js.map +0 -1
  307. package/dist/agent-farm/tutorial/steps/planning.d.ts +0 -7
  308. package/dist/agent-farm/tutorial/steps/planning.d.ts.map +0 -1
  309. package/dist/agent-farm/tutorial/steps/planning.js +0 -143
  310. package/dist/agent-farm/tutorial/steps/planning.js.map +0 -1
  311. package/dist/agent-farm/tutorial/steps/review.d.ts +0 -7
  312. package/dist/agent-farm/tutorial/steps/review.d.ts.map +0 -1
  313. package/dist/agent-farm/tutorial/steps/review.js +0 -78
  314. package/dist/agent-farm/tutorial/steps/review.js.map +0 -1
  315. package/dist/agent-farm/tutorial/steps/setup.d.ts +0 -7
  316. package/dist/agent-farm/tutorial/steps/setup.d.ts.map +0 -1
  317. package/dist/agent-farm/tutorial/steps/setup.js +0 -126
  318. package/dist/agent-farm/tutorial/steps/setup.js.map +0 -1
  319. package/dist/agent-farm/tutorial/steps/welcome.d.ts +0 -7
  320. package/dist/agent-farm/tutorial/steps/welcome.d.ts.map +0 -1
  321. package/dist/agent-farm/tutorial/steps/welcome.js +0 -50
  322. package/dist/agent-farm/tutorial/steps/welcome.js.map +0 -1
  323. package/dist/commands/pcheck/cache.d.ts +0 -48
  324. package/dist/commands/pcheck/cache.d.ts.map +0 -1
  325. package/dist/commands/pcheck/cache.js +0 -170
  326. package/dist/commands/pcheck/cache.js.map +0 -1
  327. package/dist/commands/pcheck/evaluator.d.ts +0 -15
  328. package/dist/commands/pcheck/evaluator.d.ts.map +0 -1
  329. package/dist/commands/pcheck/evaluator.js +0 -246
  330. package/dist/commands/pcheck/evaluator.js.map +0 -1
  331. package/dist/commands/pcheck/index.d.ts +0 -12
  332. package/dist/commands/pcheck/index.d.ts.map +0 -1
  333. package/dist/commands/pcheck/index.js +0 -249
  334. package/dist/commands/pcheck/index.js.map +0 -1
  335. package/dist/commands/pcheck/parser.d.ts +0 -39
  336. package/dist/commands/pcheck/parser.d.ts.map +0 -1
  337. package/dist/commands/pcheck/parser.js +0 -155
  338. package/dist/commands/pcheck/parser.js.map +0 -1
  339. package/dist/commands/pcheck/types.d.ts +0 -82
  340. package/dist/commands/pcheck/types.d.ts.map +0 -1
  341. package/dist/commands/pcheck/types.js +0 -5
  342. package/dist/commands/pcheck/types.js.map +0 -1
  343. package/dist/commands/porch/consultation.d.ts +0 -56
  344. package/dist/commands/porch/consultation.d.ts.map +0 -1
  345. package/dist/commands/porch/consultation.js +0 -330
  346. package/dist/commands/porch/consultation.js.map +0 -1
  347. package/dist/commands/porch/notifications.d.ts +0 -99
  348. package/dist/commands/porch/notifications.d.ts.map +0 -1
  349. package/dist/commands/porch/notifications.js +0 -223
  350. package/dist/commands/porch/notifications.js.map +0 -1
  351. package/dist/commands/porch/plan-parser.d.ts +0 -38
  352. package/dist/commands/porch/plan-parser.d.ts.map +0 -1
  353. package/dist/commands/porch/plan-parser.js +0 -166
  354. package/dist/commands/porch/plan-parser.js.map +0 -1
  355. package/dist/commands/porch/protocol-loader.d.ts +0 -46
  356. package/dist/commands/porch/protocol-loader.d.ts.map +0 -1
  357. package/dist/commands/porch/protocol-loader.js +0 -253
  358. package/dist/commands/porch/protocol-loader.js.map +0 -1
  359. package/dist/commands/porch/signal-parser.d.ts +0 -88
  360. package/dist/commands/porch/signal-parser.d.ts.map +0 -1
  361. package/dist/commands/porch/signal-parser.js +0 -148
  362. package/dist/commands/porch/signal-parser.js.map +0 -1
  363. package/dist/commands/tower.d.ts +0 -16
  364. package/dist/commands/tower.d.ts.map +0 -1
  365. package/dist/commands/tower.js +0 -21
  366. package/dist/commands/tower.js.map +0 -1
  367. package/skeleton/config.json +0 -7
  368. package/skeleton/porch/protocols/bugfix.json +0 -85
  369. package/skeleton/porch/protocols/spider.json +0 -135
  370. package/skeleton/porch/protocols/tick.json +0 -76
  371. package/skeleton/protocols/spider/prompts/defend.md +0 -215
  372. package/skeleton/protocols/spider/prompts/evaluate.md +0 -241
  373. package/skeleton/protocols/spider/prompts/implement.md +0 -149
  374. package/skeleton/protocols/spider/protocol.json +0 -210
  375. package/templates/dashboard/css/activity.css +0 -151
  376. package/templates/dashboard/js/activity.js +0 -112
  377. /package/skeleton/protocols/{spider → spir}/templates/spec.md +0 -0
@@ -1,1213 +1,508 @@
1
1
  /**
2
2
  * Porch - Protocol Orchestrator
3
3
  *
4
- * Generic loop orchestrator that reads protocol definitions from JSON
5
- * and executes them with Claude. Implements the Ralph pattern: fresh
6
- * context per iteration with state persisted to files.
4
+ * Claude calls porch as a tool; porch returns prescriptive instructions.
5
+ * All commands produce clear, actionable output.
7
6
  */
8
7
  import * as fs from 'node:fs';
9
8
  import * as path from 'node:path';
10
- import * as readline from 'node:readline';
11
- import { spawn } from 'node:child_process';
12
9
  import chalk from 'chalk';
13
- import { findProjectRoot, getSkeletonDir } from '../../lib/skeleton.js';
14
- import { getProjectDir, readState, writeState, createInitialState, updateState, approveGate, requestGateApproval, updatePhaseStatus, setPlanPhases, findProjects, findExecutions, findStatusFile, findPendingGates, getConsultationAttempts, incrementConsultationAttempts, resetConsultationAttempts, } from './state.js';
15
- import { extractPhasesFromPlanFile, findPlanFile, } from './plan-parser.js';
16
- import { extractSignal } from './signal-parser.js';
17
- import { runPhaseChecks, formatCheckResults } from './checks.js';
18
- import { runConsultationLoop, formatConsultationResults, hasConsultation, } from './consultation.js';
19
- import { loadProtocol as loadProtocolFromLoader, listProtocols as listProtocolsFromLoader, } from './protocol-loader.js';
20
- import { createNotifier, } from './notifications.js';
10
+ import { readState, writeState, createInitialState, findStatusPath, getStatusPath, detectProjectId, } from './state.js';
11
+ import { loadProtocol, getPhaseConfig, getNextPhase, getPhaseChecks, getPhaseGate, isPhased, isBuildVerify, } from './protocol.js';
12
+ import { findPlanFile, extractPhasesFromFile, getCurrentPlanPhase, getPhaseContent, allPlanPhasesComplete, } from './plan.js';
13
+ import { runPhaseChecks, formatCheckResults, allChecksPassed, } from './checks.js';
21
14
  // ============================================================================
22
- // Protocol Loading (delegates to protocol-loader.ts)
15
+ // Output Helpers
23
16
  // ============================================================================
24
- /**
25
- * List available protocols
26
- * Delegates to protocol-loader.ts for proper conversion
27
- */
28
- export function listProtocols(projectRoot) {
29
- const root = projectRoot || findProjectRoot();
30
- return listProtocolsFromLoader(root);
31
- }
32
- /**
33
- * Load a protocol definition
34
- * Delegates to protocol-loader.ts which properly converts steps→substates
35
- */
36
- export function loadProtocol(name, projectRoot) {
37
- const root = projectRoot || findProjectRoot();
38
- const protocol = loadProtocolFromLoader(root, name);
39
- if (!protocol) {
40
- throw new Error(`Protocol not found: ${name}\nAvailable protocols: ${listProtocols(root).join(', ')}`);
41
- }
42
- return protocol;
43
- }
44
- /**
45
- * Load a prompt file for a phase
46
- */
47
- function loadPrompt(protocol, phaseId, projectRoot) {
48
- const phase = protocol.phases.find(p => p.id === phaseId);
49
- if (!phase?.prompt) {
50
- return null;
51
- }
52
- // New structure: protocols/<protocol>/prompts/<prompt>.md
53
- const promptPaths = [
54
- path.join(projectRoot, 'codev', 'protocols', protocol.name, 'prompts', phase.prompt),
55
- path.join(getSkeletonDir(), 'protocols', protocol.name, 'prompts', phase.prompt),
56
- // Legacy paths
57
- path.join(projectRoot, 'codev', 'porch', 'prompts', phase.prompt),
58
- path.join(getSkeletonDir(), 'porch', 'prompts', phase.prompt),
59
- ];
60
- for (const promptPath of promptPaths) {
61
- if (fs.existsSync(promptPath)) {
62
- return fs.readFileSync(promptPath, 'utf-8');
63
- }
64
- // Try with .md extension
65
- if (fs.existsSync(`${promptPath}.md`)) {
66
- return fs.readFileSync(`${promptPath}.md`, 'utf-8');
67
- }
68
- }
69
- return null;
70
- }
71
- // ============================================================================
72
- // Protocol Helpers
73
- // ============================================================================
74
- /**
75
- * Check if a phase is terminal
76
- */
77
- function isTerminalPhase(protocol, phaseId) {
78
- const phase = protocol.phases.find(p => p.id === phaseId);
79
- return phase?.terminal === true;
80
- }
81
- /**
82
- * Find the phase that has a gate blocking after the given state
83
- */
84
- function getGateForState(protocol, state) {
85
- const [phaseId, substate] = state.split(':');
86
- const phase = protocol.phases.find(p => p.id === phaseId);
87
- if (phase?.gate && phase.gate.after === substate) {
88
- const gateId = `${phaseId}_approval`;
89
- return { gateId, phase };
90
- }
91
- return null;
17
+ function header(text) {
18
+ const line = '═'.repeat(50);
19
+ return `${line}\n ${text}\n${line}`;
92
20
  }
93
- /**
94
- * Get next state after gate passes
95
- */
96
- function getGateNextState(protocol, phaseId) {
97
- const phase = protocol.phases.find(p => p.id === phaseId);
98
- return phase?.gate?.next || null;
99
- }
100
- /**
101
- * Get signal-based next state
102
- */
103
- function getSignalNextState(protocol, phaseId, signal) {
104
- const phase = protocol.phases.find(p => p.id === phaseId);
105
- return phase?.signals?.[signal] || null;
106
- }
107
- /**
108
- * Get the default next state for a phase (first substate or next phase)
109
- */
110
- function getDefaultNextState(protocol, state) {
111
- const [phaseId, substate] = state.split(':');
112
- const phase = protocol.phases.find(p => p.id === phaseId);
113
- if (!phase)
114
- return null;
115
- // If phase has substates, move to next substate
116
- if (phase.substates && substate) {
117
- const currentIdx = phase.substates.indexOf(substate);
118
- if (currentIdx >= 0 && currentIdx < phase.substates.length - 1) {
119
- return `${phaseId}:${phase.substates[currentIdx + 1]}`;
120
- }
121
- }
122
- // Move to next phase
123
- const phaseIdx = protocol.phases.findIndex(p => p.id === phaseId);
124
- if (phaseIdx >= 0 && phaseIdx < protocol.phases.length - 1) {
125
- const nextPhase = protocol.phases[phaseIdx + 1];
126
- if (nextPhase.substates && nextPhase.substates.length > 0) {
127
- return `${nextPhase.id}:${nextPhase.substates[0]}`;
128
- }
129
- return nextPhase.id;
130
- }
131
- return null;
21
+ function section(title, content) {
22
+ return `\n${chalk.bold(title)}:\n${content}`;
132
23
  }
133
24
  // ============================================================================
134
- // Claude Invocation
25
+ // Commands
135
26
  // ============================================================================
136
- // Note: extractSignal is now imported from signal-parser.js
137
- /**
138
- * Rolling buffer for last N lines of output
139
- */
140
- class OutputBuffer {
141
- lines = [];
142
- currentLine = '';
143
- maxLines;
144
- constructor(maxLines = 20) {
145
- this.maxLines = maxLines;
146
- }
147
- append(data) {
148
- const text = data.toString();
149
- for (const char of text) {
150
- if (char === '\n') {
151
- this.lines.push(this.currentLine);
152
- if (this.lines.length > this.maxLines) {
153
- this.lines.shift();
154
- }
155
- this.currentLine = '';
156
- }
157
- else {
158
- this.currentLine += char;
159
- }
160
- }
161
- }
162
- getContext() {
163
- return this.currentLine
164
- ? [...this.lines, this.currentLine]
165
- : [...this.lines];
166
- }
167
- }
168
27
  /**
169
- * Invoke Claude for a phase with escape-to-cancel and output buffering
28
+ * porch status <id>
29
+ * Shows current state and prescriptive next steps.
170
30
  */
171
- async function invokeClaude(protocol, phaseId, state, statusFilePath, projectRoot, options) {
172
- const promptContent = loadPrompt(protocol, phaseId, projectRoot);
173
- if (!promptContent) {
174
- console.log(chalk.yellow(`[porch] No prompt file for phase: ${phaseId}`));
175
- return '';
176
- }
177
- if (options.dryRun) {
178
- console.log(chalk.yellow(`[porch] [DRY RUN] Would invoke Claude for phase: ${phaseId}`));
179
- return '';
180
- }
181
- if (options.noClaude) {
182
- console.log(chalk.blue(`[porch] [NO_CLAUDE] Simulating phase: ${phaseId}`));
183
- await new Promise(r => setTimeout(r, 1000));
184
- console.log(chalk.green(`[porch] Simulated completion of phase: ${phaseId}`));
185
- return '';
186
- }
187
- console.log(chalk.cyan(`[phase] Invoking Claude for phase: ${phaseId}`));
188
- console.log(chalk.gray(`[porch] Press ESC to cancel`));
31
+ export async function status(projectRoot, projectId) {
32
+ const statusPath = findStatusPath(projectRoot, projectId);
33
+ if (!statusPath) {
34
+ throw new Error(`Project ${projectId} not found.\nRun 'porch init' to create a new project.`);
35
+ }
36
+ const state = readState(statusPath);
37
+ const protocol = loadProtocol(projectRoot, state.protocol);
38
+ const phaseConfig = getPhaseConfig(protocol, state.phase);
39
+ // Header
189
40
  console.log('');
190
- const timeout = protocol.config?.claude_timeout || 600000; // 10 minutes default
191
- const startTime = Date.now();
192
- const fullPrompt = `## Protocol: ${protocol.name}
193
- ## Phase: ${phaseId}
194
- ## Project ID: ${state.id}
195
-
196
- ## Current Status
197
- \`\`\`yaml
198
- state: "${state.current_state}"
199
- iteration: ${state.iteration}
200
- started_at: "${state.started_at}"
201
- \`\`\`
202
-
203
- ## Task
204
- Execute the ${phaseId} phase for project ${state.id} - ${state.title}
205
-
206
- ## Phase Instructions
207
- ${promptContent}
208
-
209
- ## Important
210
- - Project ID: ${state.id}
211
- - Protocol: ${protocol.name}
212
- - Follow the instructions above precisely
213
- - Output <signal>...</signal> tags when you reach completion points
214
- `;
215
- return new Promise((resolve, reject) => {
216
- const args = ['--print', '-p', fullPrompt, '--dangerously-skip-permissions'];
217
- const proc = spawn('claude', args, {
218
- stdio: ['ignore', 'pipe', 'pipe'],
219
- timeout,
220
- });
221
- let output = '';
222
- let stderr = '';
223
- let cancelled = false;
224
- const outputBuffer = new OutputBuffer(20);
225
- // Set up escape key handler
226
- const wasRawMode = process.stdin.isRaw;
227
- if (process.stdin.isTTY) {
228
- process.stdin.setRawMode(true);
229
- process.stdin.resume();
230
- }
231
- const escapeHandler = (key) => {
232
- // ESC key is ASCII 27, Ctrl+C is 3
233
- if (key[0] === 27 || key[0] === 3) {
234
- cancelled = true;
235
- console.log('');
236
- console.log(chalk.yellow('━'.repeat(50)));
237
- console.log(chalk.yellow('[porch] Cancelling Claude execution...'));
238
- console.log(chalk.yellow('━'.repeat(50)));
239
- // Show last 20 lines of context
240
- const context = outputBuffer.getContext();
241
- if (context.length > 0) {
242
- console.log(chalk.gray('\nLast output:'));
243
- console.log(chalk.gray('─'.repeat(40)));
244
- for (const line of context.slice(-10)) {
245
- console.log(chalk.gray(' ' + line.slice(0, 70)));
246
- }
247
- console.log(chalk.gray('─'.repeat(40)));
248
- }
249
- proc.kill('SIGTERM');
250
- // Give it a moment then force kill
251
- setTimeout(() => {
252
- if (!proc.killed) {
253
- proc.kill('SIGKILL');
254
- }
255
- }, 2000);
256
- }
257
- };
258
- if (process.stdin.isTTY) {
259
- process.stdin.on('data', escapeHandler);
260
- }
261
- // Periodic status update timer
262
- let lineCount = 0;
263
- const statusInterval = setInterval(() => {
264
- const elapsed = Math.floor((Date.now() - startTime) / 1000);
265
- const mins = Math.floor(elapsed / 60);
266
- const secs = elapsed % 60;
267
- const timeStr = mins > 0 ? `${mins}m ${secs}s` : `${secs}s`;
268
- // Use stderr so it doesn't interfere with output parsing
269
- process.stderr.write(chalk.gray(`\n[porch] Running... ${lineCount} lines | ${timeStr} elapsed | ESC to cancel\n`));
270
- }, 30000); // Every 30 seconds
271
- const cleanup = () => {
272
- clearInterval(statusInterval);
273
- if (process.stdin.isTTY) {
274
- process.stdin.removeListener('data', escapeHandler);
275
- process.stdin.setRawMode(wasRawMode ?? false);
276
- process.stdin.pause();
41
+ console.log(header(`PROJECT: ${state.id} - ${state.title}`));
42
+ console.log(` PROTOCOL: ${state.protocol}`);
43
+ console.log(` PHASE: ${state.phase} (${phaseConfig?.name || 'unknown'})`);
44
+ // For phased protocols, show plan phase status
45
+ if (isPhased(protocol, state.phase) && state.plan_phases.length > 0) {
46
+ console.log('');
47
+ console.log(chalk.bold('PLAN PHASES:'));
48
+ console.log('');
49
+ // Status icons
50
+ const icon = (status) => {
51
+ switch (status) {
52
+ case 'complete': return chalk.green('✓');
53
+ case 'in_progress': return chalk.yellow('►');
54
+ default: return chalk.gray('○');
277
55
  }
278
56
  };
279
- proc.stdout.on('data', (data) => {
280
- const text = data.toString();
281
- output += text;
282
- outputBuffer.append(text);
283
- // Count newlines for status
284
- lineCount += (text.match(/\n/g) || []).length;
285
- process.stdout.write(data);
286
- });
287
- proc.stderr.on('data', (data) => {
288
- stderr += data.toString();
289
- process.stderr.write(data);
290
- });
291
- proc.on('close', (code) => {
292
- cleanup();
293
- // Show completion summary
294
- const elapsed = Math.floor((Date.now() - startTime) / 1000);
295
- const mins = Math.floor(elapsed / 60);
296
- const secs = elapsed % 60;
297
- const timeStr = mins > 0 ? `${mins}m ${secs}s` : `${secs}s`;
298
- if (cancelled) {
299
- console.log(chalk.yellow(`[porch] Cancelled after ${timeStr}`));
300
- resolve(''); // Return empty on cancel, don't reject
57
+ // Show phases
58
+ for (const phase of state.plan_phases) {
59
+ const isCurrent = phase.status === 'in_progress';
60
+ const prefix = isCurrent ? chalk.cyan('→ ') : ' ';
61
+ const title = isCurrent ? chalk.bold(phase.title) : phase.title;
62
+ console.log(`${prefix}${icon(phase.status)} ${phase.id}: ${title}`);
63
+ }
64
+ const currentPlanPhase = getCurrentPlanPhase(state.plan_phases);
65
+ if (currentPlanPhase) {
66
+ console.log('');
67
+ console.log(chalk.bold(`CURRENT: ${currentPlanPhase.id} - ${currentPlanPhase.title}`));
68
+ // Show phase content from plan
69
+ const planPath = findPlanFile(projectRoot, state.id, state.title);
70
+ if (planPath) {
71
+ const content = fs.readFileSync(planPath, 'utf-8');
72
+ const phaseContent = getPhaseContent(content, currentPlanPhase.id);
73
+ if (phaseContent) {
74
+ console.log(section('FROM THE PLAN', phaseContent.slice(0, 500)));
75
+ }
301
76
  }
302
- else if (code !== 0) {
303
- console.log(chalk.red(`[porch] Failed after ${timeStr} (exit code ${code})`));
304
- reject(new Error(`Claude exited with code ${code}\n${stderr}`));
77
+ // Find the next phase name for the warning
78
+ const currentIdx = state.plan_phases.findIndex(p => p.id === currentPlanPhase.id);
79
+ const nextPlanPhase = state.plan_phases[currentIdx + 1];
80
+ console.log('');
81
+ console.log(chalk.red.bold('╔══════════════════════════════════════════════════════════════╗'));
82
+ console.log(chalk.red.bold('║ 🛑 CRITICAL RULES ║'));
83
+ if (nextPlanPhase) {
84
+ console.log(chalk.red.bold(`║ 1. DO NOT start ${nextPlanPhase.id} until you run porch again!`.padEnd(63) + '║'));
305
85
  }
306
86
  else {
307
- console.log('');
308
- console.log(chalk.green(`[porch] Phase complete: ${lineCount} lines in ${timeStr}`));
309
- resolve(output);
87
+ console.log(chalk.red.bold('║ 1. DO NOT start the next phase until you run porch again! ║'));
310
88
  }
311
- });
312
- proc.on('error', (err) => {
313
- cleanup();
314
- reject(err);
315
- });
316
- });
317
- }
318
- // ============================================================================
319
- // Commands
320
- // ============================================================================
321
- /**
322
- * Initialize a new project with a protocol
323
- */
324
- export async function init(protocolName, projectId, projectName, options = {}) {
325
- const projectRoot = findProjectRoot();
326
- const protocol = loadProtocol(protocolName, projectRoot);
327
- // Create project directory
328
- const projectDir = getProjectDir(projectRoot, projectId, projectName);
329
- fs.mkdirSync(projectDir, { recursive: true });
330
- // Create initial state
331
- const state = createInitialState(protocol, projectId, projectName, options.worktree);
332
- const statusPath = path.join(projectDir, 'status.yaml');
333
- await writeState(statusPath, state);
334
- console.log(chalk.green(`[porch] Initialized project ${projectId} with protocol ${protocolName}`));
335
- console.log(chalk.blue(`[porch] Project directory: ${projectDir}`));
336
- console.log(chalk.blue(`[porch] Initial state: ${state.current_state}`));
337
- }
338
- /**
339
- * Check if a protocol phase is a "phased" phase (runs per plan-phase)
340
- */
341
- function isPhasedPhase(protocol, phaseId) {
342
- const phase = protocol.phases.find(p => p.id === phaseId);
343
- return phase?.phased === true;
344
- }
345
- /**
346
- * Get the IDE phases (implement, defend, evaluate) that run per plan-phase
347
- */
348
- function getIDEPhases(protocol) {
349
- return protocol.phases
350
- .filter(p => p.phased === true)
351
- .map(p => p.id);
352
- }
353
- /**
354
- * Parse the current plan-phase from state like "implement:phase_1"
355
- */
356
- function parsePlanPhaseFromState(state) {
357
- const parts = state.split(':');
358
- const phaseId = parts[0];
359
- // Check if second part is a plan phase (phase_N) or a substate
360
- if (parts.length > 1) {
361
- if (parts[1].startsWith('phase_')) {
362
- return { phaseId, planPhaseId: parts[1], substate: parts[2] || null };
89
+ console.log(chalk.red.bold('║ 2. Run /compact before starting each new phase ║'));
90
+ console.log(chalk.red.bold('║ 3. After completing this phase, run: porch done ' + state.id.padEnd(12) + '║'));
91
+ console.log(chalk.red.bold('╚══════════════════════════════════════════════════════════════╝'));
363
92
  }
364
- return { phaseId, planPhaseId: null, substate: parts[1] };
365
93
  }
366
- return { phaseId, planPhaseId: null, substate: null };
367
- }
368
- /**
369
- * Get the next IDE state for a phased phase
370
- * implement:phase_1 → defend:phase_1 → evaluate:phase_1 → implement:phase_2 → ...
371
- */
372
- function getNextIDEState(protocol, currentState, planPhases, signal) {
373
- const { phaseId, planPhaseId } = parsePlanPhaseFromState(currentState);
374
- if (!planPhaseId)
375
- return null;
376
- const idePhases = getIDEPhases(protocol);
377
- const currentIdeIndex = idePhases.indexOf(phaseId);
378
- if (currentIdeIndex < 0)
379
- return null;
380
- // If not at the end of IDE phases, move to next IDE phase for same plan-phase
381
- if (currentIdeIndex < idePhases.length - 1) {
382
- return `${idePhases[currentIdeIndex + 1]}:${planPhaseId}`;
94
+ // Show checks status
95
+ const checks = getPhaseChecks(protocol, state.phase);
96
+ if (Object.keys(checks).length > 0) {
97
+ const checkLines = Object.keys(checks).map(name => ` ○ ${name} (not yet run)`);
98
+ console.log(section('CRITERIA', checkLines.join('\n')));
383
99
  }
384
- // At end of IDE phases (evaluate), move to next plan-phase
385
- const currentPlanIndex = planPhases.findIndex(p => p.id === planPhaseId);
386
- if (currentPlanIndex < 0)
387
- return null;
388
- // Check if there's a next plan phase
389
- if (currentPlanIndex < planPhases.length - 1) {
390
- const nextPlanPhase = planPhases[currentPlanIndex + 1];
391
- return `${idePhases[0]}:${nextPlanPhase.id}`; // Start implement for next phase
100
+ // Instructions
101
+ const gate = getPhaseGate(protocol, state.phase);
102
+ if (gate && state.gates[gate]?.status === 'pending' && state.gates[gate]?.requested_at) {
103
+ console.log(section('STATUS', chalk.yellow('WAITING FOR HUMAN APPROVAL')));
104
+ console.log(`\n Gate: ${gate}`);
105
+ console.log(' Do not proceed until gate is approved.');
106
+ console.log(`\n To approve: porch approve ${state.id} ${gate}`);
392
107
  }
393
- // All plan phases complete, move to review
394
- const reviewPhase = protocol.phases.find(p => p.id === 'review');
395
- if (reviewPhase) {
396
- return 'review';
108
+ else {
109
+ console.log(section('INSTRUCTIONS', getInstructions(state, protocol)));
397
110
  }
398
- return 'complete';
399
- }
400
- // ============================================================================
401
- // Interactive REPL
402
- // ============================================================================
403
- /**
404
- * Create a readline interface for interactive input
405
- */
406
- function createRepl() {
407
- return readline.createInterface({
408
- input: process.stdin,
409
- output: process.stdout,
410
- terminal: true,
411
- });
412
- }
413
- /**
414
- * Prompt user for input with a given message
415
- */
416
- async function prompt(rl, message) {
417
- return new Promise((resolve) => {
418
- rl.question(message, (answer) => {
419
- resolve(answer.trim().toLowerCase());
420
- });
421
- });
422
- }
423
- /**
424
- * Show REPL help
425
- */
426
- function showReplHelp() {
427
- console.log('');
428
- console.log(chalk.blue('Porch REPL Commands:'));
429
- console.log(' ' + chalk.green('approve') + ' [gate] - Approve pending gate (or current if omitted)');
430
- console.log(' ' + chalk.green('view') + ' - View the full artifact for current gate');
431
- console.log(' ' + chalk.green('status') + ' - Show current project status');
432
- console.log(' ' + chalk.green('continue') + ' - Continue to next iteration');
433
- console.log(' ' + chalk.green('skip') + ' - Skip current phase (use with caution)');
434
- console.log(' ' + chalk.green('help') + ' - Show this help');
435
- console.log(' ' + chalk.green('quit') + ' - Exit porch');
111
+ console.log(section('NEXT ACTION', getNextAction(state, protocol)));
436
112
  console.log('');
437
113
  }
438
114
  /**
439
- * Display current status summary
115
+ * porch check <id>
116
+ * Runs the phase checks and reports results.
440
117
  */
441
- function displayStatus(state, protocol) {
442
- console.log('');
443
- console.log(chalk.blue('─'.repeat(50)));
444
- console.log(chalk.blue(`Project: ${state.id} - ${state.title}`));
445
- console.log(chalk.blue(`Protocol: ${state.protocol}`));
446
- console.log(chalk.blue(`State: ${state.current_state}`));
447
- console.log(chalk.blue(`Iteration: ${state.iteration}`));
448
- // Show pending gates
449
- const pendingGates = Object.entries(state.gates)
450
- .filter(([, g]) => g.status === 'pending' && g.requested_at)
451
- .map(([id]) => id);
452
- if (pendingGates.length > 0) {
453
- console.log(chalk.yellow(`Pending gates: ${pendingGates.join(', ')}`));
118
+ export async function check(projectRoot, projectId) {
119
+ const statusPath = findStatusPath(projectRoot, projectId);
120
+ if (!statusPath) {
121
+ throw new Error(`Project ${projectId} not found.`);
122
+ }
123
+ const state = readState(statusPath);
124
+ const protocol = loadProtocol(projectRoot, state.protocol);
125
+ const checks = getPhaseChecks(protocol, state.phase);
126
+ if (Object.keys(checks).length === 0) {
127
+ console.log(chalk.dim('No checks defined for this phase.'));
128
+ return;
454
129
  }
455
- console.log(chalk.blue('─'.repeat(50)));
130
+ const checkEnv = { PROJECT_ID: state.id, PROJECT_TITLE: state.title };
456
131
  console.log('');
457
- }
458
- /**
459
- * Get the artifact file path for a gate
460
- */
461
- function getGateArtifact(gateId, state, projectRoot) {
462
- const projectDir = `${state.id}-${state.title}`;
463
- if (gateId === 'specify_approval') {
464
- // Look for spec file
465
- const specPath = path.join(projectRoot, 'codev', 'specs', `${projectDir}.md`);
466
- if (fs.existsSync(specPath))
467
- return specPath;
468
- // Try alternate naming
469
- const altPath = path.join(projectRoot, 'codev', 'specs', `${state.id}-${state.title}.md`);
470
- if (fs.existsSync(altPath))
471
- return altPath;
472
- }
473
- else if (gateId === 'plan_approval') {
474
- // Look for plan file
475
- const planPath = path.join(projectRoot, 'codev', 'plans', `${projectDir}.md`);
476
- if (fs.existsSync(planPath))
477
- return planPath;
478
- const altPath = path.join(projectRoot, 'codev', 'plans', `${state.id}-${state.title}.md`);
479
- if (fs.existsSync(altPath))
480
- return altPath;
132
+ console.log(chalk.bold('RUNNING CHECKS...'));
133
+ console.log('');
134
+ const results = await runPhaseChecks(checks, projectRoot, checkEnv);
135
+ console.log(formatCheckResults(results));
136
+ console.log('');
137
+ if (allChecksPassed(results)) {
138
+ console.log(chalk.green('RESULT: ALL CHECKS PASSED'));
139
+ console.log(`\n Run: porch done ${state.id} (to advance)`);
481
140
  }
482
- else if (gateId === 'review_approval') {
483
- // Look for review file
484
- const reviewPath = path.join(projectRoot, 'codev', 'reviews', `${projectDir}.md`);
485
- if (fs.existsSync(reviewPath))
486
- return reviewPath;
141
+ else {
142
+ console.log(chalk.red('RESULT: CHECKS FAILED'));
143
+ console.log(`\n Fix the failures and run: porch check ${state.id}`);
487
144
  }
488
- return null;
145
+ console.log('');
489
146
  }
490
147
  /**
491
- * Display gate context - show what artifact was created and key information
148
+ * porch done <id>
149
+ * Advances to next phase if checks pass. Refuses if checks fail.
492
150
  */
493
- function displayGateContext(gateId, state, projectRoot) {
494
- const artifactPath = getGateArtifact(gateId, state, projectRoot);
495
- console.log('');
496
- console.log(chalk.cyan('─'.repeat(50)));
497
- console.log(chalk.cyan(' CONTEXT FOR REVIEW'));
498
- console.log(chalk.cyan('─'.repeat(50)));
499
- if (artifactPath) {
500
- console.log(chalk.white(` Artifact: ${artifactPath}`));
151
+ export async function done(projectRoot, projectId) {
152
+ const statusPath = findStatusPath(projectRoot, projectId);
153
+ if (!statusPath) {
154
+ throw new Error(`Project ${projectId} not found.`);
155
+ }
156
+ let state = readState(statusPath);
157
+ const protocol = loadProtocol(projectRoot, state.protocol);
158
+ const checks = getPhaseChecks(protocol, state.phase);
159
+ // Run checks first
160
+ if (Object.keys(checks).length > 0) {
161
+ const checkEnv = { PROJECT_ID: state.id, PROJECT_TITLE: state.title };
501
162
  console.log('');
502
- // Read and show first ~30 lines of the artifact
503
- try {
504
- const content = fs.readFileSync(artifactPath, 'utf-8');
505
- const lines = content.split('\n');
506
- const preview = lines.slice(0, 30).join('\n');
507
- console.log(chalk.gray(' ┌' + '─'.repeat(46) + '┐'));
508
- for (const line of preview.split('\n')) {
509
- console.log(chalk.gray(' │ ') + line.slice(0, 44));
510
- }
511
- if (lines.length > 30) {
512
- console.log(chalk.gray(` │ ... (${lines.length - 30} more lines)`));
513
- }
514
- console.log(chalk.gray(' └' + '─'.repeat(46) + '┘'));
163
+ console.log(chalk.bold('RUNNING CHECKS...'));
164
+ const results = await runPhaseChecks(checks, projectRoot, checkEnv);
165
+ console.log(formatCheckResults(results));
166
+ if (!allChecksPassed(results)) {
515
167
  console.log('');
516
- console.log(chalk.white(` To view full document: cat ${artifactPath}`));
517
- }
518
- catch (e) {
519
- console.log(chalk.yellow(` Could not read artifact: ${e}`));
168
+ console.log(chalk.red('CHECKS FAILED. Cannot advance.'));
169
+ console.log(`\n Fix the failures and try again.`);
170
+ process.exit(1);
520
171
  }
521
172
  }
522
- else {
523
- console.log(chalk.yellow(' No artifact file found for this gate.'));
524
- console.log(chalk.yellow(' This may indicate Claude did not produce the expected output.'));
525
- // Show what we expected
526
- if (gateId === 'specify_approval') {
527
- console.log(chalk.gray(` Expected: codev/specs/${state.id}-${state.title}.md`));
528
- }
529
- else if (gateId === 'plan_approval') {
530
- console.log(chalk.gray(` Expected: codev/plans/${state.id}-${state.title}.md`));
173
+ // Check for gate
174
+ const gate = getPhaseGate(protocol, state.phase);
175
+ if (gate && state.gates[gate]?.status !== 'approved') {
176
+ console.log('');
177
+ console.log(chalk.yellow(`GATE REQUIRED: ${gate}`));
178
+ console.log(`\n Run: porch gate ${state.id}`);
179
+ console.log(' Wait for human approval before advancing.');
180
+ return;
181
+ }
182
+ // For build_verify phases: mark build as complete for verification
183
+ if (isBuildVerify(protocol, state.phase) && !state.build_complete) {
184
+ state.build_complete = true;
185
+ writeState(statusPath, state);
186
+ console.log('');
187
+ console.log(chalk.green('BUILD COMPLETE. Ready for verification.'));
188
+ console.log(`\n Run: porch next ${state.id} (to get verification tasks)`);
189
+ return;
190
+ }
191
+ // For phased protocols: plan phase advancement requires 3-way review.
192
+ // The isBuildVerify block above already marked build_complete=true.
193
+ // Redirect to porch next for verification (3-way review + unanimous verdict).
194
+ if (isPhased(protocol, state.phase) && state.plan_phases.length > 0) {
195
+ const currentPlanPhase = getCurrentPlanPhase(state.plan_phases);
196
+ if (currentPlanPhase && !allPlanPhasesComplete(state.plan_phases)) {
197
+ console.log('');
198
+ console.log(chalk.green('BUILD COMPLETE. Ready for 3-way review.'));
199
+ console.log(`\n Run: porch next ${state.id} (to trigger verification)`);
200
+ return;
531
201
  }
532
202
  }
533
- console.log('');
203
+ // Advance to next protocol phase
204
+ advanceProtocolPhase(state, protocol, statusPath);
534
205
  }
535
- /**
536
- * Run the protocol loop for a project (Interactive REPL mode)
537
- */
538
- export async function run(projectId, options = {}) {
539
- const projectRoot = findProjectRoot();
540
- // Find status file
541
- const statusFilePath = findStatusFile(projectRoot, projectId);
542
- if (!statusFilePath) {
543
- throw new Error(`Status file not found for project: ${projectId}\n` +
544
- `Run: porch init <protocol> ${projectId} <project-name>`);
545
- }
546
- // Read state and load protocol
547
- const state = readState(statusFilePath);
548
- if (!state) {
549
- throw new Error(`Could not read state from: ${statusFilePath}`);
206
+ function advanceProtocolPhase(state, protocol, statusPath) {
207
+ const nextPhase = getNextPhase(protocol, state.phase);
208
+ if (!nextPhase) {
209
+ state.phase = 'complete';
210
+ writeState(statusPath, state);
211
+ console.log('');
212
+ console.log(chalk.green.bold('🎉 PROTOCOL COMPLETE'));
213
+ console.log(`\n Project ${state.id} has completed the ${state.protocol} protocol.`);
214
+ return;
550
215
  }
551
- const protocol = loadProtocol(state.protocol, projectRoot);
552
- const maxIterations = protocol.config?.max_iterations || 100;
553
- // Create notifier for this project (desktop notifications for important events)
554
- const notifier = createNotifier(projectId, { desktop: true });
555
- // Create REPL interface
556
- const rl = createRepl();
557
- console.log('');
558
- console.log(chalk.green('═'.repeat(50)));
559
- console.log(chalk.green(` PORCH - Protocol Orchestrator`));
560
- console.log(chalk.green('═'.repeat(50)));
561
- console.log(chalk.blue(` Project: ${state.id} - ${state.title}`));
562
- console.log(chalk.blue(` Protocol: ${state.protocol}`));
563
- console.log(chalk.blue(` State: ${state.current_state}`));
564
- console.log(chalk.green('═'.repeat(50)));
565
- console.log('');
566
- console.log(chalk.gray('Type "help" for commands, "quit" to exit'));
567
- console.log('');
568
- let currentState = state;
569
- // Extract plan phases if not already done and we're past planning
570
- if (!currentState.plan_phases || currentState.plan_phases.length === 0) {
571
- const planFile = findPlanFile(projectRoot, projectId, currentState.title);
572
- if (planFile) {
573
- try {
574
- const planPhases = extractPhasesFromPlanFile(planFile);
575
- currentState = setPlanPhases(currentState, planPhases);
576
- await writeState(statusFilePath, currentState);
577
- console.log(chalk.blue(`[porch] Extracted ${planPhases.length} phases from plan`));
578
- for (const phase of planPhases) {
579
- console.log(chalk.blue(` - ${phase.id}: ${phase.title}`));
580
- }
581
- }
582
- catch (e) {
583
- console.log(chalk.yellow(`[porch] Could not extract plan phases: ${e}`));
216
+ state.phase = nextPhase.id;
217
+ state.build_complete = false;
218
+ state.iteration = 1;
219
+ // If entering a phased phase (implement), extract plan phases
220
+ if (isPhased(protocol, nextPhase.id)) {
221
+ const planPath = findPlanFile(process.cwd(), state.id, state.title);
222
+ if (planPath) {
223
+ state.plan_phases = extractPhasesFromFile(planPath);
224
+ // extractPhasesFromFile already marks first phase as in_progress
225
+ if (state.plan_phases.length > 0) {
226
+ state.current_plan_phase = state.plan_phases[0].id;
584
227
  }
585
228
  }
586
229
  }
587
- for (let iteration = currentState.iteration; iteration <= maxIterations; iteration++) {
588
- console.log(chalk.blue(''.repeat(40)));
589
- console.log(chalk.blue(`[porch] Iteration ${iteration}`));
590
- console.log(chalk.blue('━'.repeat(40)));
591
- // Fresh read of state each iteration (Ralph pattern)
592
- currentState = readState(statusFilePath) || currentState;
593
- console.log(chalk.blue(`[porch] Current state: ${currentState.current_state}`));
594
- // Parse state into phase and substate
595
- const { phaseId, planPhaseId, substate } = parsePlanPhaseFromState(currentState.current_state);
596
- // Re-attempt plan phase extraction if entering a phased phase without plan phases
597
- // This handles the case where porch started during Specify phase (no plan file yet)
598
- if (isPhasedPhase(protocol, phaseId) && (!currentState.plan_phases || currentState.plan_phases.length === 0)) {
599
- const planFile = findPlanFile(projectRoot, projectId, currentState.title);
600
- if (planFile) {
601
- try {
602
- const planPhases = extractPhasesFromPlanFile(planFile);
603
- currentState = setPlanPhases(currentState, planPhases);
604
- await writeState(statusFilePath, currentState);
605
- console.log(chalk.blue(`[porch] Late discovery: Extracted ${planPhases.length} phases from plan`));
606
- for (const phase of planPhases) {
607
- console.log(chalk.blue(` - ${phase.id}: ${phase.title}`));
608
- }
609
- }
610
- catch (e) {
611
- console.log(chalk.yellow(`[porch] Could not extract plan phases: ${e}`));
612
- }
613
- }
614
- else {
615
- console.log(chalk.yellow(`[porch] Warning: Entering phased phase '${phaseId}' but no plan file found`));
616
- }
617
- }
618
- // Check if terminal phase
619
- if (isTerminalPhase(protocol, phaseId)) {
620
- console.log(chalk.green('━'.repeat(40)));
621
- console.log(chalk.green(`[porch] ${state.protocol} loop COMPLETE`));
622
- console.log(chalk.green(`[porch] Project ${projectId} finished all phases`));
623
- console.log(chalk.green('━'.repeat(40)));
624
- rl.close();
625
- return;
626
- }
627
- // Check if there's a pending gate from a previous iteration (step already executed)
628
- const pendingGateInfo = getGateForState(protocol, currentState.current_state);
629
- if (pendingGateInfo) {
630
- const { gateId } = pendingGateInfo;
631
- // Check if gate is already approved
632
- if (currentState.gates[gateId]?.status === 'passed') {
633
- // Gate approved - proceed to next state
634
- const nextState = getGateNextState(protocol, phaseId);
635
- if (nextState) {
636
- console.log(chalk.green(`[porch] Gate ${gateId} passed! Proceeding to ${nextState}`));
637
- await notifier.gateApproved(gateId);
638
- // Reset any consultation attempts for the gated state
639
- currentState = resetConsultationAttempts(currentState, currentState.current_state);
640
- // If entering a phased phase, start with first plan phase
641
- if (isPhasedPhase(protocol, nextState.split(':')[0]) && currentState.plan_phases?.length) {
642
- const firstPlanPhase = currentState.plan_phases[0];
643
- currentState = updateState(currentState, `${nextState.split(':')[0]}:${firstPlanPhase.id}`);
644
- currentState = updatePhaseStatus(currentState, firstPlanPhase.id, 'in_progress');
645
- }
646
- else {
647
- currentState = updateState(currentState, nextState);
648
- }
649
- await writeState(statusFilePath, currentState);
650
- continue; // Start next iteration with new state
651
- }
652
- }
653
- else if (currentState.gates[gateId]?.requested_at) {
654
- // Gate requested but not approved - prompt user interactively
655
- console.log('');
656
- console.log(chalk.yellow('═'.repeat(50)));
657
- console.log(chalk.yellow(` GATE PENDING: ${gateId}`));
658
- console.log(chalk.yellow('═'.repeat(50)));
659
- console.log(chalk.cyan(` Phase: ${phaseId}`));
660
- console.log(chalk.cyan(` State: ${currentState.current_state}`));
661
- // Show context for the gate - what artifact was created
662
- displayGateContext(gateId, currentState, projectRoot);
663
- // Interactive gate approval loop
664
- let gateHandled = false;
665
- while (!gateHandled) {
666
- const answer = await prompt(rl, chalk.yellow(`Approve ${gateId}? [y/n/help]: `));
667
- switch (answer) {
668
- case 'y':
669
- case 'yes':
670
- case 'approve':
671
- currentState = approveGate(currentState, gateId);
672
- await writeState(statusFilePath, currentState);
673
- console.log(chalk.green(`✓ Gate ${gateId} approved`));
674
- gateHandled = true;
675
- break;
676
- case 'n':
677
- case 'no':
678
- console.log(chalk.yellow('Gate not approved. Staying in current state.'));
679
- console.log(chalk.gray('Type "quit" to exit or wait for changes.'));
680
- break;
681
- case 'status':
682
- displayStatus(currentState, protocol);
683
- break;
684
- case 'view':
685
- case 'cat':
686
- case 'show':
687
- // Show full artifact
688
- const artifactPath = getGateArtifact(gateId, currentState, projectRoot);
689
- if (artifactPath) {
690
- console.log(chalk.cyan(`\n─── ${artifactPath} ───\n`));
691
- try {
692
- const content = fs.readFileSync(artifactPath, 'utf-8');
693
- console.log(content);
694
- console.log(chalk.cyan(`\n─── End of ${artifactPath} ───\n`));
695
- }
696
- catch (e) {
697
- console.log(chalk.red(`Error reading file: ${e}`));
698
- }
699
- }
700
- else {
701
- console.log(chalk.yellow('No artifact file found for this gate.'));
702
- }
703
- break;
704
- case 'help':
705
- case '?':
706
- showReplHelp();
707
- break;
708
- case 'quit':
709
- case 'exit':
710
- console.log(chalk.blue('Exiting porch...'));
711
- rl.close();
712
- return;
713
- default:
714
- console.log(chalk.gray('Type "y" to approve, "n" to decline, or "help" for commands'));
715
- }
716
- }
717
- continue;
718
- }
719
- // If gate not yet requested, fall through to execute phase first
720
- }
721
- // Get the current phase definition
722
- const phase = protocol.phases.find(p => p.id === phaseId);
723
- // Show plan phase context if in a phased phase
724
- if (planPhaseId && currentState.plan_phases) {
725
- const planPhase = currentState.plan_phases.find(p => p.id === planPhaseId);
726
- if (planPhase) {
727
- console.log(chalk.cyan(`[phase] IDE Phase: ${phaseId} | Plan Phase: ${planPhase.title}`));
728
- }
729
- else {
730
- console.log(chalk.cyan(`[phase] Phase: ${phaseId}`));
731
- }
732
- }
733
- else {
734
- console.log(chalk.cyan(`[phase] Phase: ${phaseId}`));
735
- }
736
- // Notify phase start
737
- await notifier.phaseStart(phaseId);
738
- // Execute phase
739
- const output = await invokeClaude(protocol, phaseId, currentState, statusFilePath, projectRoot, options);
740
- const signal = extractSignal(output);
741
- // Run phase checks (build/test) if defined
742
- if (phase?.checks && !options.dryRun) {
743
- console.log(chalk.blue(`[porch] Running checks for phase ${phaseId}...`));
744
- const checkResult = await runPhaseChecks(phase, {
745
- cwd: projectRoot,
746
- dryRun: options.dryRun,
747
- });
748
- console.log(formatCheckResults(checkResult));
749
- if (!checkResult.success) {
750
- // Notify about check failure
751
- const failedCheck = checkResult.checks.find(c => !c.success);
752
- await notifier.checkFailed(phaseId, failedCheck?.name || 'build/test', failedCheck?.error || 'Check failed');
753
- // If checks fail, handle based on check configuration
754
- if (checkResult.returnTo) {
755
- console.log(chalk.yellow(`[porch] Checks failed, returning to ${checkResult.returnTo}`));
756
- if (planPhaseId) {
757
- currentState = updateState(currentState, `${checkResult.returnTo}:${planPhaseId}`);
758
- }
759
- else {
760
- currentState = updateState(currentState, checkResult.returnTo);
761
- }
762
- await writeState(statusFilePath, currentState);
763
- continue;
764
- }
765
- // No returnTo means we should retry (already handled in checks)
766
- }
767
- }
768
- // Run consultation if configured for this phase/substate
769
- if (phase?.consultation && hasConsultation(phase) && !options.dryRun && !options.noClaude) {
770
- const consultConfig = phase.consultation;
771
- const currentSubstate = substate || parsePlanPhaseFromState(currentState.current_state).substate;
772
- // Check if consultation is triggered by current substate
773
- if (consultConfig.on === currentSubstate || consultConfig.on === phaseId) {
774
- const maxRounds = consultConfig.max_rounds || 3;
775
- const stateKey = currentState.current_state;
776
- // Get attempt count from state (persisted across porch iterations)
777
- const attemptCount = getConsultationAttempts(currentState, stateKey) + 1;
778
- console.log(chalk.blue(`[porch] Consultation triggered for phase ${phaseId} (attempt ${attemptCount}/${maxRounds})`));
779
- await notifier.consultationStart(phaseId, consultConfig.models || ['gemini', 'codex', 'claude']);
780
- const consultResult = await runConsultationLoop(consultConfig, {
781
- subcommand: consultConfig.type.includes('pr') ? 'pr' : consultConfig.type.includes('spec') ? 'spec' : 'plan',
782
- identifier: projectId,
783
- cwd: projectRoot,
784
- timeout: protocol.config?.consultation_timeout,
785
- dryRun: options.dryRun,
786
- });
787
- console.log(formatConsultationResults(consultResult));
788
- await notifier.consultationComplete(phaseId, consultResult.feedback, consultResult.allApproved);
789
- // If not all approved, track attempt and check for escalation
790
- if (!consultResult.allApproved) {
791
- // Increment attempt count in state (persists across iterations)
792
- currentState = incrementConsultationAttempts(currentState, stateKey);
793
- await writeState(statusFilePath, currentState);
794
- // Check if we've reached max attempts
795
- if (attemptCount >= maxRounds) {
796
- // Create escalation gate - requires human intervention
797
- const escalationGateId = `${phaseId}_consultation_escalation`;
798
- // Check if escalation gate was already approved (human override)
799
- if (currentState.gates[escalationGateId]?.status === 'passed') {
800
- console.log(chalk.green(`[porch] Consultation escalation gate already approved, continuing`));
801
- // Reset attempts and fall through to next state handling
802
- currentState = resetConsultationAttempts(currentState, stateKey);
803
- await writeState(statusFilePath, currentState);
804
- }
805
- else {
806
- console.log(chalk.red(`[porch] Consultation failed after ${attemptCount} attempts - escalating to human`));
807
- console.log(chalk.yellow(`[porch] To override and continue: porch approve ${projectId} ${escalationGateId}`));
808
- // Request human gate if not already requested
809
- if (!currentState.gates[escalationGateId]?.requested_at) {
810
- currentState = requestGateApproval(currentState, escalationGateId);
811
- await writeState(statusFilePath, currentState);
812
- await notifier.gatePending(phaseId, escalationGateId);
813
- }
814
- // Prompt user to approve escalation gate
815
- console.log('');
816
- console.log(chalk.red('═'.repeat(50)));
817
- console.log(chalk.red(` ESCALATION: ${escalationGateId}`));
818
- console.log(chalk.red('═'.repeat(50)));
819
- console.log(chalk.yellow(` Consultation failed after ${attemptCount} attempts`));
820
- console.log('');
821
- let escalationHandled = false;
822
- while (!escalationHandled) {
823
- const answer = await prompt(rl, chalk.red(`Override and continue? [y/n/help]: `));
824
- switch (answer) {
825
- case 'y':
826
- case 'yes':
827
- case 'approve':
828
- case 'override':
829
- currentState = approveGate(currentState, escalationGateId);
830
- currentState = resetConsultationAttempts(currentState, stateKey);
831
- await writeState(statusFilePath, currentState);
832
- console.log(chalk.green(`✓ Escalation gate ${escalationGateId} approved`));
833
- escalationHandled = true;
834
- break;
835
- case 'n':
836
- case 'no':
837
- console.log(chalk.yellow('Escalation not approved. Consultation loop will continue.'));
838
- break;
839
- case 'status':
840
- displayStatus(currentState, protocol);
841
- break;
842
- case 'help':
843
- case '?':
844
- showReplHelp();
845
- break;
846
- case 'quit':
847
- case 'exit':
848
- console.log(chalk.blue('Exiting porch...'));
849
- rl.close();
850
- return;
851
- default:
852
- console.log(chalk.gray('Type "y" to override and continue, "n" to decline, or "help" for commands'));
853
- }
854
- }
855
- continue;
856
- }
857
- }
858
- else {
859
- console.log(chalk.yellow(`[porch] Consultation requested changes (attempt ${attemptCount}/${maxRounds}), continuing for revision`));
860
- // Stay in same state for Claude to revise on next iteration
861
- continue;
862
- }
863
- }
864
- else {
865
- // All approved - reset attempt counter
866
- currentState = resetConsultationAttempts(currentState, stateKey);
867
- await writeState(statusFilePath, currentState);
868
- }
869
- // All approved (or escalation gate passed) - use consultation's next state if defined
870
- if (consultConfig.next) {
871
- if (planPhaseId) {
872
- currentState = updateState(currentState, `${consultConfig.next}:${planPhaseId}`);
873
- }
874
- else {
875
- currentState = updateState(currentState, consultConfig.next);
876
- }
877
- await writeState(statusFilePath, currentState);
878
- continue;
879
- }
880
- }
881
- }
882
- // Check if current state has a gate that should trigger AFTER this step
883
- // This is the gate trigger point - step has executed, now block before transition
884
- const gateInfo = getGateForState(protocol, currentState.current_state);
885
- if (gateInfo) {
886
- const { gateId } = gateInfo;
887
- // Request gate approval if not already requested
888
- if (!currentState.gates[gateId]?.requested_at) {
889
- currentState = requestGateApproval(currentState, gateId);
890
- await writeState(statusFilePath, currentState);
891
- console.log(chalk.yellow(`[porch] Step complete. Gate approval requested: ${gateId}`));
892
- await notifier.gatePending(phaseId, gateId);
893
- }
894
- // Gate not yet approved - prompt user interactively
895
- if (currentState.gates[gateId]?.status !== 'passed') {
896
- console.log('');
897
- console.log(chalk.yellow('═'.repeat(50)));
898
- console.log(chalk.yellow(` GATE PENDING: ${gateId}`));
899
- console.log(chalk.yellow('═'.repeat(50)));
900
- let gateHandled = false;
901
- while (!gateHandled) {
902
- const answer = await prompt(rl, chalk.yellow(`Approve ${gateId}? [y/n/help]: `));
903
- switch (answer) {
904
- case 'y':
905
- case 'yes':
906
- case 'approve':
907
- currentState = approveGate(currentState, gateId);
908
- await writeState(statusFilePath, currentState);
909
- console.log(chalk.green(`✓ Gate ${gateId} approved`));
910
- gateHandled = true;
911
- break;
912
- case 'n':
913
- case 'no':
914
- console.log(chalk.yellow('Gate not approved. Staying in current state.'));
915
- break;
916
- case 'status':
917
- displayStatus(currentState, protocol);
918
- break;
919
- case 'help':
920
- case '?':
921
- showReplHelp();
922
- break;
923
- case 'quit':
924
- case 'exit':
925
- console.log(chalk.blue('Exiting porch...'));
926
- rl.close();
927
- return;
928
- default:
929
- console.log(chalk.gray('Type "y" to approve, "n" to decline, or "help" for commands'));
930
- }
931
- }
932
- continue;
933
- }
934
- }
935
- // Determine next state
936
- let nextState = null;
937
- if (signal) {
938
- console.log(chalk.green(`[porch] Signal received: ${signal}`));
939
- nextState = getSignalNextState(protocol, phaseId, signal);
940
- }
941
- if (!nextState) {
942
- // Check if this is a phased phase (IDE loop)
943
- if (isPhasedPhase(protocol, phaseId) && currentState.plan_phases?.length) {
944
- nextState = getNextIDEState(protocol, currentState.current_state, currentState.plan_phases, signal || undefined);
945
- // Mark current plan phase as complete if moving to next
946
- if (nextState && planPhaseId) {
947
- const { planPhaseId: nextPlanPhaseId } = parsePlanPhaseFromState(nextState);
948
- if (nextPlanPhaseId !== planPhaseId) {
949
- currentState = updatePhaseStatus(currentState, planPhaseId, 'complete');
950
- if (nextPlanPhaseId) {
951
- currentState = updatePhaseStatus(currentState, nextPlanPhaseId, 'in_progress');
952
- }
953
- }
954
- }
955
- }
956
- else {
957
- // Use default transition
958
- nextState = getDefaultNextState(protocol, currentState.current_state);
230
+ writeState(statusPath, state);
231
+ console.log('');
232
+ console.log(chalk.green(`ADVANCING TO: ${nextPhase.id} - ${nextPhase.name}`));
233
+ // If we just entered implement phase, show phase 1 info and the critical warning
234
+ if (isPhased(protocol, nextPhase.id) && state.plan_phases.length > 0) {
235
+ const firstPhase = state.plan_phases[0];
236
+ const nextPlanPhase = state.plan_phases[1];
237
+ console.log('');
238
+ console.log(chalk.bold(`YOUR TASK: ${firstPhase.id} - "${firstPhase.title}"`));
239
+ // Show phase content from plan
240
+ const planPath = findPlanFile(process.cwd(), state.id, state.title);
241
+ if (planPath) {
242
+ const content = fs.readFileSync(planPath, 'utf-8');
243
+ const phaseContent = getPhaseContent(content, firstPhase.id);
244
+ if (phaseContent) {
245
+ console.log(section('FROM THE PLAN', phaseContent.slice(0, 800)));
959
246
  }
960
247
  }
961
- if (nextState) {
962
- currentState = updateState(currentState, nextState, signal ? { signal } : undefined);
963
- await writeState(statusFilePath, currentState);
248
+ console.log('');
249
+ console.log(chalk.red.bold('╔══════════════════════════════════════════════════════════════╗'));
250
+ console.log(chalk.red.bold('║ 🛑 CRITICAL RULES ║'));
251
+ if (nextPlanPhase) {
252
+ console.log(chalk.red.bold(`║ 1. DO NOT start ${nextPlanPhase.id} until you run porch again!`.padEnd(63) + '║'));
964
253
  }
965
254
  else {
966
- console.log(chalk.yellow(`[porch] No transition defined, staying in current state`));
255
+ console.log(chalk.red.bold('║ 1. DO NOT start the next phase until you run porch again! ║'));
967
256
  }
257
+ console.log(chalk.red.bold('║ 2. Run /compact before starting each new phase ║'));
258
+ console.log(chalk.red.bold('║ 3. When phase complete, run: porch done ' + state.id.padEnd(20) + '║'));
259
+ console.log(chalk.red.bold('╚══════════════════════════════════════════════════════════════╝'));
968
260
  }
969
- rl.close();
970
- throw new Error(`Max iterations (${maxIterations}) reached!`);
261
+ console.log(`\n Run: porch status ${state.id}`);
971
262
  }
972
263
  /**
973
- * Approve a gate
264
+ * porch gate <id>
265
+ * Requests human approval for current gate.
974
266
  */
975
- export async function approve(projectId, gateId) {
976
- const projectRoot = findProjectRoot();
977
- const statusFilePath = findStatusFile(projectRoot, projectId);
978
- if (!statusFilePath) {
979
- throw new Error(`Status file not found for project: ${projectId}`);
267
+ export async function gate(projectRoot, projectId) {
268
+ const statusPath = findStatusPath(projectRoot, projectId);
269
+ if (!statusPath) {
270
+ throw new Error(`Project ${projectId} not found.`);
271
+ }
272
+ const state = readState(statusPath);
273
+ const protocol = loadProtocol(projectRoot, state.protocol);
274
+ const gateName = getPhaseGate(protocol, state.phase);
275
+ if (!gateName) {
276
+ console.log(chalk.dim('No gate required for this phase.'));
277
+ console.log(`\n Run: porch done ${state.id}`);
278
+ return;
980
279
  }
981
- const state = readState(statusFilePath);
982
- if (!state) {
983
- throw new Error(`Could not read state from: ${statusFilePath}`);
280
+ // Mark gate as requested
281
+ if (!state.gates[gateName]) {
282
+ state.gates[gateName] = { status: 'pending' };
984
283
  }
985
- const updatedState = approveGate(state, gateId);
986
- await writeState(statusFilePath, updatedState);
987
- console.log(chalk.green(`[porch] Approved: ${gateId}`));
988
- }
989
- /**
990
- * Show project status
991
- */
992
- export async function status(projectId) {
993
- const projectRoot = findProjectRoot();
994
- if (projectId) {
995
- // Show specific project
996
- const statusFilePath = findStatusFile(projectRoot, projectId);
997
- if (!statusFilePath) {
998
- throw new Error(`Status file not found for project: ${projectId}`);
999
- }
1000
- const state = readState(statusFilePath);
1001
- if (!state) {
1002
- throw new Error(`Could not read state from: ${statusFilePath}`);
1003
- }
1004
- console.log(chalk.blue(`[porch] Status for project ${projectId}:`));
1005
- console.log('');
1006
- console.log(` ID: ${state.id}`);
1007
- console.log(` Title: ${state.title}`);
1008
- console.log(` Protocol: ${state.protocol}`);
1009
- console.log(` State: ${state.current_state}`);
1010
- console.log(` Iteration: ${state.iteration}`);
1011
- console.log(` Started: ${state.started_at}`);
1012
- console.log(` Updated: ${state.last_updated}`);
1013
- console.log('');
1014
- if (Object.keys(state.gates).length > 0) {
1015
- console.log(' Gates:');
1016
- for (const [gateId, gateStatus] of Object.entries(state.gates)) {
1017
- const icon = gateStatus.status === 'passed' ? '✓' : gateStatus.status === 'failed' ? '✗' : '⏳';
1018
- console.log(` ${icon} ${gateId}: ${gateStatus.status}`);
1019
- }
1020
- console.log('');
1021
- }
1022
- if (state.plan_phases && state.plan_phases.length > 0) {
1023
- console.log(' Plan Phases:');
1024
- for (const phase of state.plan_phases) {
1025
- const phaseStatus = state.phases[phase.id]?.status || 'pending';
1026
- const icon = phaseStatus === 'complete' ? '✓' : phaseStatus === 'in_progress' ? '🔄' : '○';
1027
- console.log(` ${icon} ${phase.id}: ${phase.title}`);
1028
- }
1029
- }
284
+ if (!state.gates[gateName].requested_at) {
285
+ state.gates[gateName].requested_at = new Date().toISOString();
286
+ writeState(statusPath, state);
1030
287
  }
1031
- else {
1032
- // Show all projects
1033
- const projects = findProjects(projectRoot);
1034
- const executions = findExecutions(projectRoot);
1035
- if (projects.length === 0 && executions.length === 0) {
1036
- console.log(chalk.yellow('[porch] No projects found'));
1037
- return;
1038
- }
1039
- console.log(chalk.blue('[porch] Projects:'));
1040
- for (const { id, path: statusPath } of projects) {
1041
- const state = readState(statusPath);
1042
- if (state) {
1043
- const pendingGates = Object.entries(state.gates)
1044
- .filter(([, g]) => g.status === 'pending' && g.requested_at)
1045
- .map(([id]) => id);
1046
- const gateStr = pendingGates.length > 0 ? chalk.yellow(` [${pendingGates.join(', ')}]`) : '';
1047
- console.log(` ${id} ${state.title} - ${state.current_state}${gateStr}`);
1048
- }
1049
- }
1050
- if (executions.length > 0) {
288
+ console.log('');
289
+ console.log(chalk.bold(`GATE: ${gateName}`));
290
+ console.log('');
291
+ // Show relevant artifact and open it for review
292
+ const artifact = getArtifactForPhase(projectRoot, state);
293
+ if (artifact) {
294
+ const fullPath = path.join(projectRoot, artifact);
295
+ if (fs.existsSync(fullPath)) {
296
+ console.log(` Artifact: ${artifact}`);
1051
297
  console.log('');
1052
- console.log(chalk.blue('[porch] Executions:'));
1053
- for (const { protocol, id, path: statusPath } of executions) {
1054
- const state = readState(statusPath);
1055
- if (state) {
1056
- console.log(` ${protocol}/${id} - ${state.current_state}`);
1057
- }
1058
- }
298
+ console.log(chalk.cyan(' Opening artifact for human review...'));
299
+ // Use af open to display in annotation viewer
300
+ const { spawn } = await import('node:child_process');
301
+ spawn('af', ['open', fullPath], {
302
+ stdio: 'inherit',
303
+ detached: true
304
+ }).unref();
1059
305
  }
1060
306
  }
307
+ console.log('');
308
+ console.log(chalk.yellow(' Human approval required. STOP and wait.'));
309
+ console.log(' Do not proceed until gate is approved.');
310
+ console.log('');
311
+ console.log(chalk.bold('STATUS: WAITING FOR HUMAN APPROVAL'));
312
+ console.log('');
313
+ console.log(chalk.dim(` To approve: porch approve ${state.id} ${gateName}`));
314
+ console.log('');
1061
315
  }
1062
316
  /**
1063
- * List available protocols
317
+ * porch approve <id> <gate> --a-human-explicitly-approved-this
318
+ * Human approves a gate. Requires explicit flag to prevent automated approvals.
1064
319
  */
1065
- export async function list() {
1066
- const projectRoot = findProjectRoot();
1067
- const protocols = listProtocols(projectRoot);
1068
- if (protocols.length === 0) {
1069
- console.log(chalk.yellow('[porch] No protocols found'));
320
+ export async function approve(projectRoot, projectId, gateName, hasHumanFlag) {
321
+ const statusPath = findStatusPath(projectRoot, projectId);
322
+ if (!statusPath) {
323
+ throw new Error(`Project ${projectId} not found.`);
324
+ }
325
+ const state = readState(statusPath);
326
+ if (!state.gates[gateName]) {
327
+ const knownGates = Object.keys(state.gates).join(', ');
328
+ throw new Error(`Unknown gate: ${gateName}\nKnown gates: ${knownGates || 'none'}`);
329
+ }
330
+ if (state.gates[gateName].status === 'approved') {
331
+ console.log(chalk.yellow(`Gate ${gateName} is already approved.`));
1070
332
  return;
1071
333
  }
1072
- console.log(chalk.blue('[porch] Available protocols:'));
1073
- for (const name of protocols) {
1074
- try {
1075
- const protocol = loadProtocol(name, projectRoot);
1076
- console.log(` - ${name}: ${protocol.description}`);
1077
- }
1078
- catch {
1079
- console.log(` - ${name}: (error loading)`);
334
+ // Require explicit human flag
335
+ if (!hasHumanFlag) {
336
+ console.log('');
337
+ console.log(chalk.red('ERROR: Human approval required.'));
338
+ console.log('');
339
+ console.log(' To approve, please run:');
340
+ console.log('');
341
+ console.log(chalk.cyan(` porch approve ${projectId} ${gateName} --a-human-explicitly-approved-this`));
342
+ console.log('');
343
+ process.exit(1);
344
+ }
345
+ // Run phase checks before approving
346
+ const protocol = loadProtocol(projectRoot, state.protocol);
347
+ const checks = getPhaseChecks(protocol, state.phase);
348
+ if (Object.keys(checks).length > 0) {
349
+ const checkEnv = { PROJECT_ID: state.id, PROJECT_TITLE: state.title };
350
+ console.log('');
351
+ console.log(chalk.bold('RUNNING CHECKS...'));
352
+ const results = await runPhaseChecks(checks, projectRoot, checkEnv);
353
+ console.log(formatCheckResults(results));
354
+ if (!allChecksPassed(results)) {
355
+ console.log('');
356
+ console.log(chalk.red('CHECKS FAILED. Cannot approve gate.'));
357
+ console.log(`\n Fix the failures and try again.`);
358
+ process.exit(1);
1080
359
  }
1081
360
  }
1082
- }
1083
- /**
1084
- * Show protocol definition
1085
- */
1086
- export async function show(protocolName) {
1087
- const projectRoot = findProjectRoot();
1088
- const protocol = loadProtocol(protocolName, projectRoot);
1089
- console.log(chalk.blue(`[porch] Protocol: ${protocolName}`));
361
+ state.gates[gateName].status = 'approved';
362
+ state.gates[gateName].approved_at = new Date().toISOString();
363
+ writeState(statusPath, state);
364
+ console.log('');
365
+ console.log(chalk.green(`Gate ${gateName} approved.`));
366
+ console.log(`\n Run: porch done ${state.id} (to advance)`);
1090
367
  console.log('');
1091
- console.log(JSON.stringify(protocol, null, 2));
1092
368
  }
1093
369
  /**
1094
- * Show pending gates across all projects
370
+ * porch init <protocol> <id> <name>
371
+ * Initialize a new project.
1095
372
  */
1096
- export async function pending() {
1097
- const projectRoot = findProjectRoot();
1098
- const gates = findPendingGates(projectRoot);
1099
- if (gates.length === 0) {
1100
- console.log(chalk.green('[porch] No pending gates'));
1101
- return;
1102
- }
1103
- console.log(chalk.yellow('[porch] Pending gates:'));
1104
- for (const gate of gates) {
1105
- const requestedAt = gate.requestedAt ? ` (requested ${gate.requestedAt})` : '';
1106
- console.log(` ${gate.projectId}: ${gate.gateId}${requestedAt}`);
1107
- console.log(` porch approve ${gate.projectId} ${gate.gateId}`);
1108
- }
373
+ export async function init(projectRoot, protocolName, projectId, projectName) {
374
+ const protocol = loadProtocol(projectRoot, protocolName);
375
+ const statusPath = getStatusPath(projectRoot, projectId, projectName);
376
+ // Check if already exists
377
+ if (fs.existsSync(statusPath)) {
378
+ throw new Error(`Project ${projectId}-${projectName} already exists.`);
379
+ }
380
+ const state = createInitialState(protocol, projectId, projectName, projectRoot);
381
+ writeState(statusPath, state);
382
+ console.log('');
383
+ console.log(chalk.green(`Project initialized: ${projectId}-${projectName}`));
384
+ console.log(` Protocol: ${protocolName}`);
385
+ console.log(` Starting phase: ${state.phase}`);
386
+ console.log(`\n Run: porch status ${projectId}`);
387
+ console.log('');
1109
388
  }
1110
389
  // ============================================================================
1111
- // Auto-Detection
390
+ // Helpers
1112
391
  // ============================================================================
1113
- /**
1114
- * Auto-detect project ID from current directory
1115
- *
1116
- * Detection methods:
1117
- * 1. Check if cwd is a worktree matching pattern: .builders/<id> or worktrees/<protocol>_<id>_*
1118
- * 2. Check for a single project in codev/projects/
1119
- * 3. Check for .porch-project marker file
1120
- */
1121
- function autoDetectProject() {
1122
- const cwd = process.cwd();
1123
- // Method 1: Check path pattern for builder worktree
1124
- // Pattern: .builders/<id> or .builders/<id>-<name>
1125
- const buildersMatch = cwd.match(/[/\\]\.builders[/\\](\d+)(?:-[^/\\]*)?(?:[/\\]|$)/);
1126
- if (buildersMatch) {
1127
- return buildersMatch[1];
392
+ function getInstructions(state, protocol) {
393
+ const phase = state.phase;
394
+ if (isPhased(protocol, phase) && state.plan_phases.length > 0) {
395
+ const current = getCurrentPlanPhase(state.plan_phases);
396
+ if (current) {
397
+ return ` You are implementing ${current.id}: "${current.title}".\n\n Complete the work, then run: porch check ${state.id}`;
398
+ }
1128
399
  }
1129
- // Pattern: worktrees/<protocol>_<id>_<name>
1130
- const worktreeMatch = cwd.match(/[/\\]worktrees[/\\]\w+_(\d+)_[^/\\]*(?:[/\\]|$)/);
1131
- if (worktreeMatch) {
1132
- return worktreeMatch[1];
400
+ const phaseConfig = getPhaseConfig(protocol, phase);
401
+ return ` You are in the ${phaseConfig?.name || phase} phase.\n\n When complete, run: porch done ${state.id}`;
402
+ }
403
+ function getNextAction(state, protocol) {
404
+ const checks = getPhaseChecks(protocol, state.phase);
405
+ const gate = getPhaseGate(protocol, state.phase);
406
+ if (gate && state.gates[gate]?.status === 'pending' && state.gates[gate]?.requested_at) {
407
+ return chalk.yellow('Wait for human to approve the gate.');
1133
408
  }
1134
- // Method 2: Check for .porch-project marker file
1135
- const markerPath = path.join(cwd, '.porch-project');
1136
- if (fs.existsSync(markerPath)) {
1137
- const content = fs.readFileSync(markerPath, 'utf-8').trim();
1138
- if (content) {
1139
- return content;
409
+ if (isPhased(protocol, state.phase)) {
410
+ const current = getCurrentPlanPhase(state.plan_phases);
411
+ if (current) {
412
+ return `Implement ${current.title} as specified in the plan.`;
1140
413
  }
1141
414
  }
1142
- // Method 3: Check if there's exactly one project in codev/projects/
1143
- try {
1144
- const projectRoot = findProjectRoot();
1145
- const projects = findProjects(projectRoot);
1146
- if (projects.length === 1) {
1147
- return projects[0].id;
1148
- }
415
+ if (Object.keys(checks).length > 0) {
416
+ return `Complete the phase work, then run: porch check ${state.id}`;
1149
417
  }
1150
- catch {
1151
- // Not in a codev project
418
+ return `Complete the phase work, then run: porch done ${state.id}`;
419
+ }
420
+ function getArtifactForPhase(projectRoot, state) {
421
+ switch (state.phase) {
422
+ case 'specify':
423
+ return `codev/specs/${state.id}-${state.title}.md`;
424
+ case 'plan':
425
+ return `codev/plans/${state.id}-${state.title}.md`;
426
+ case 'review':
427
+ return `codev/reviews/${state.id}-${state.title}.md`;
428
+ default:
429
+ return null;
1152
430
  }
1153
- return null;
1154
431
  }
1155
- export async function porch(options) {
1156
- const { subcommand, args, dryRun, noClaude, pollInterval, description, worktree } = options;
1157
- switch (subcommand.toLowerCase()) {
1158
- case 'run': {
1159
- let projectId = args[0];
1160
- // Auto-detect project if not provided
1161
- if (!projectId) {
1162
- const detected = autoDetectProject();
1163
- if (!detected) {
1164
- throw new Error('Usage: porch run <project-id>\n' +
1165
- 'Or run from a project worktree to auto-detect.');
1166
- }
1167
- projectId = detected;
1168
- console.log(chalk.blue(`[porch] Auto-detected project: ${projectId}`));
1169
- }
1170
- await run(projectId, { dryRun, noClaude, pollInterval });
1171
- break;
1172
- }
1173
- case 'init': {
1174
- if (args.length < 3) {
1175
- throw new Error('Usage: porch init <protocol> <project-id> <project-name>');
1176
- }
1177
- await init(args[0], args[1], args[2], { description, worktree });
1178
- break;
1179
- }
1180
- case 'approve': {
1181
- if (args.length < 2) {
1182
- throw new Error('Usage: porch approve <project-id> <gate-id>');
1183
- }
1184
- await approve(args[0], args[1]);
1185
- break;
1186
- }
1187
- case 'status': {
1188
- await status(args[0]);
1189
- break;
1190
- }
1191
- case 'pending': {
1192
- await pending();
1193
- break;
1194
- }
1195
- case 'list':
1196
- case 'list-protocols': {
1197
- await list();
1198
- break;
1199
- }
1200
- case 'show':
1201
- case 'show-protocol': {
1202
- if (args.length < 1) {
1203
- throw new Error('Usage: porch show <protocol>');
432
+ // ============================================================================
433
+ // CLI
434
+ // ============================================================================
435
+ export async function cli(args) {
436
+ const [command, ...rest] = args;
437
+ const projectRoot = process.cwd();
438
+ // Auto-detect project ID for commands that need it
439
+ function getProjectId(provided) {
440
+ if (provided)
441
+ return provided;
442
+ const detected = detectProjectId(projectRoot);
443
+ if (detected) {
444
+ console.log(chalk.dim(`[auto-detected project: ${detected}]`));
445
+ return detected;
446
+ }
447
+ throw new Error('No project ID provided and could not auto-detect.\nProvide ID explicitly or ensure exactly one project exists in codev/projects/');
448
+ }
449
+ try {
450
+ switch (command) {
451
+ case 'next': {
452
+ const { next: porchNext } = await import('./next.js');
453
+ const result = await porchNext(projectRoot, getProjectId(rest[0]));
454
+ console.log(JSON.stringify(result, null, 2));
455
+ break;
1204
456
  }
1205
- await show(args[0]);
1206
- break;
457
+ case 'run':
458
+ console.error("Error: 'porch run' has been removed. Use 'porch next <id>' instead.");
459
+ console.error("See: porch --help");
460
+ process.exit(1);
461
+ break;
462
+ case 'status':
463
+ await status(projectRoot, getProjectId(rest[0]));
464
+ break;
465
+ case 'check':
466
+ await check(projectRoot, getProjectId(rest[0]));
467
+ break;
468
+ case 'done':
469
+ await done(projectRoot, getProjectId(rest[0]));
470
+ break;
471
+ case 'gate':
472
+ await gate(projectRoot, getProjectId(rest[0]));
473
+ break;
474
+ case 'approve':
475
+ if (!rest[0] || !rest[1])
476
+ throw new Error('Usage: porch approve <id> <gate> --a-human-explicitly-approved-this');
477
+ const hasHumanFlag = rest.includes('--a-human-explicitly-approved-this');
478
+ await approve(projectRoot, rest[0], rest[1], hasHumanFlag);
479
+ break;
480
+ case 'init':
481
+ if (!rest[0] || !rest[1] || !rest[2]) {
482
+ throw new Error('Usage: porch init <protocol> <id> <name>');
483
+ }
484
+ await init(projectRoot, rest[0], rest[1], rest[2]);
485
+ break;
486
+ default:
487
+ console.log('porch - Protocol Orchestrator');
488
+ console.log('');
489
+ console.log('Commands:');
490
+ console.log(' next [id] Emit next tasks as JSON (planner mode)');
491
+ console.log(' status [id] Show current state and instructions');
492
+ console.log(' check [id] Run checks for current phase');
493
+ console.log(' done [id] Signal build complete (validates checks, advances)');
494
+ console.log(' gate [id] Request human approval');
495
+ console.log(' approve <id> <gate> --a-human-explicitly-approved-this');
496
+ console.log(' init <protocol> <id> <name> Initialize a new project');
497
+ console.log('');
498
+ console.log('Project ID is auto-detected when exactly one project exists.');
499
+ console.log('');
500
+ process.exit(command ? 1 : 0);
1207
501
  }
1208
- default:
1209
- throw new Error(`Unknown subcommand: ${subcommand}\n` +
1210
- 'Valid subcommands: run, init, approve, status, pending, list, show');
502
+ }
503
+ catch (err) {
504
+ console.error(chalk.red(`Error: ${err.message}`));
505
+ process.exit(1);
1211
506
  }
1212
507
  }
1213
508
  //# sourceMappingURL=index.js.map