@cluesmith/codev 3.0.0-rc.1 → 3.0.0-rc.11

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 (328) hide show
  1. package/LICENSE +201 -0
  2. package/bin/af.js +2 -2
  3. package/bin/afx.js +8 -0
  4. package/bin/consult.js +2 -1
  5. package/bin/team.js +2 -1
  6. package/{dashboard/dist/assets/index-nr0cXxtf.css → dashboard-dist/assets/index-Bs8I7N2s.css} +1 -1
  7. package/dashboard-dist/assets/index-CkNo2z6t.js +195 -0
  8. package/dashboard-dist/assets/index-CkNo2z6t.js.map +1 -0
  9. package/dashboard-dist/dist/assets/index-Bs8I7N2s.css +32 -0
  10. package/dashboard-dist/dist/assets/index-CkNo2z6t.js +195 -0
  11. package/dashboard-dist/dist/assets/index-CkNo2z6t.js.map +1 -0
  12. package/{dashboard → dashboard-dist}/dist/index.html +2 -2
  13. package/dashboard-dist/index.html +14 -0
  14. package/dist/agent-farm/cli.d.ts.map +1 -1
  15. package/dist/agent-farm/cli.js +17 -14
  16. package/dist/agent-farm/cli.js.map +1 -1
  17. package/dist/agent-farm/commands/architect.d.ts +4 -4
  18. package/dist/agent-farm/commands/architect.d.ts.map +1 -1
  19. package/dist/agent-farm/commands/architect.js +23 -12
  20. package/dist/agent-farm/commands/architect.js.map +1 -1
  21. package/dist/agent-farm/commands/attach.js +8 -8
  22. package/dist/agent-farm/commands/attach.js.map +1 -1
  23. package/dist/agent-farm/commands/bench.d.ts +1 -1
  24. package/dist/agent-farm/commands/bench.js +1 -1
  25. package/dist/agent-farm/commands/cleanup.d.ts.map +1 -1
  26. package/dist/agent-farm/commands/cleanup.js +10 -5
  27. package/dist/agent-farm/commands/cleanup.js.map +1 -1
  28. package/dist/agent-farm/commands/cron.js +1 -1
  29. package/dist/agent-farm/commands/cron.js.map +1 -1
  30. package/dist/agent-farm/commands/db.d.ts +3 -3
  31. package/dist/agent-farm/commands/db.js +4 -4
  32. package/dist/agent-farm/commands/db.js.map +1 -1
  33. package/dist/agent-farm/commands/open.js +2 -2
  34. package/dist/agent-farm/commands/open.js.map +1 -1
  35. package/dist/agent-farm/commands/rename.js +1 -1
  36. package/dist/agent-farm/commands/rename.js.map +1 -1
  37. package/dist/agent-farm/commands/send.d.ts +4 -1
  38. package/dist/agent-farm/commands/send.d.ts.map +1 -1
  39. package/dist/agent-farm/commands/send.js +8 -5
  40. package/dist/agent-farm/commands/send.js.map +1 -1
  41. package/dist/agent-farm/commands/shell.js +3 -3
  42. package/dist/agent-farm/commands/shell.js.map +1 -1
  43. package/dist/agent-farm/commands/spawn-roles.d.ts +2 -1
  44. package/dist/agent-farm/commands/spawn-roles.d.ts.map +1 -1
  45. package/dist/agent-farm/commands/spawn-roles.js +1 -1
  46. package/dist/agent-farm/commands/spawn-roles.js.map +1 -1
  47. package/dist/agent-farm/commands/spawn-worktree.d.ts +49 -10
  48. package/dist/agent-farm/commands/spawn-worktree.d.ts.map +1 -1
  49. package/dist/agent-farm/commands/spawn-worktree.js +356 -51
  50. package/dist/agent-farm/commands/spawn-worktree.js.map +1 -1
  51. package/dist/agent-farm/commands/spawn.d.ts +6 -6
  52. package/dist/agent-farm/commands/spawn.d.ts.map +1 -1
  53. package/dist/agent-farm/commands/spawn.js +182 -74
  54. package/dist/agent-farm/commands/spawn.js.map +1 -1
  55. package/dist/agent-farm/commands/status.d.ts.map +1 -1
  56. package/dist/agent-farm/commands/status.js +48 -3
  57. package/dist/agent-farm/commands/status.js.map +1 -1
  58. package/dist/agent-farm/commands/team-update.d.ts +1 -1
  59. package/dist/agent-farm/commands/team-update.d.ts.map +1 -1
  60. package/dist/agent-farm/commands/team-update.js +8 -5
  61. package/dist/agent-farm/commands/team-update.js.map +1 -1
  62. package/dist/agent-farm/commands/team.d.ts +1 -1
  63. package/dist/agent-farm/commands/team.d.ts.map +1 -1
  64. package/dist/agent-farm/commands/team.js +7 -3
  65. package/dist/agent-farm/commands/team.js.map +1 -1
  66. package/dist/agent-farm/commands/tower-cloud.d.ts +2 -2
  67. package/dist/agent-farm/commands/tower-cloud.js +3 -3
  68. package/dist/agent-farm/commands/tower-cloud.js.map +1 -1
  69. package/dist/agent-farm/commands/tower.js +2 -2
  70. package/dist/agent-farm/commands/tower.js.map +1 -1
  71. package/dist/agent-farm/lib/cloud-config.js +2 -2
  72. package/dist/agent-farm/lib/cloud-config.js.map +1 -1
  73. package/dist/agent-farm/lib/tower-client.d.ts +6 -230
  74. package/dist/agent-farm/lib/tower-client.d.ts.map +1 -1
  75. package/dist/agent-farm/lib/tower-client.js +6 -293
  76. package/dist/agent-farm/lib/tower-client.js.map +1 -1
  77. package/dist/agent-farm/lib/tunnel-client.js +1 -1
  78. package/dist/agent-farm/lib/tunnel-client.js.map +1 -1
  79. package/dist/agent-farm/servers/message-write.d.ts +24 -0
  80. package/dist/agent-farm/servers/message-write.d.ts.map +1 -0
  81. package/dist/agent-farm/servers/message-write.js +63 -0
  82. package/dist/agent-farm/servers/message-write.js.map +1 -0
  83. package/dist/agent-farm/servers/overview.d.ts +10 -8
  84. package/dist/agent-farm/servers/overview.d.ts.map +1 -1
  85. package/dist/agent-farm/servers/overview.js +78 -38
  86. package/dist/agent-farm/servers/overview.js.map +1 -1
  87. package/dist/agent-farm/servers/send-buffer.d.ts +4 -3
  88. package/dist/agent-farm/servers/send-buffer.d.ts.map +1 -1
  89. package/dist/agent-farm/servers/send-buffer.js +7 -4
  90. package/dist/agent-farm/servers/send-buffer.js.map +1 -1
  91. package/dist/agent-farm/servers/tower-cron-parser.js +1 -1
  92. package/dist/agent-farm/servers/tower-cron-parser.js.map +1 -1
  93. package/dist/agent-farm/servers/tower-cron.d.ts.map +1 -1
  94. package/dist/agent-farm/servers/tower-cron.js +4 -5
  95. package/dist/agent-farm/servers/tower-cron.js.map +1 -1
  96. package/dist/agent-farm/servers/tower-instances.d.ts.map +1 -1
  97. package/dist/agent-farm/servers/tower-instances.js +15 -13
  98. package/dist/agent-farm/servers/tower-instances.js.map +1 -1
  99. package/dist/agent-farm/servers/tower-routes.d.ts.map +1 -1
  100. package/dist/agent-farm/servers/tower-routes.js +9 -16
  101. package/dist/agent-farm/servers/tower-routes.js.map +1 -1
  102. package/dist/agent-farm/servers/tower-server.js +1 -1
  103. package/dist/agent-farm/servers/tower-terminals.d.ts +2 -2
  104. package/dist/agent-farm/servers/tower-terminals.d.ts.map +1 -1
  105. package/dist/agent-farm/servers/tower-terminals.js +64 -36
  106. package/dist/agent-farm/servers/tower-terminals.js.map +1 -1
  107. package/dist/agent-farm/servers/tower-tunnel.js +2 -2
  108. package/dist/agent-farm/servers/tower-tunnel.js.map +1 -1
  109. package/dist/agent-farm/servers/tower-utils.d.ts +9 -6
  110. package/dist/agent-farm/servers/tower-utils.d.ts.map +1 -1
  111. package/dist/agent-farm/servers/tower-utils.js +13 -7
  112. package/dist/agent-farm/servers/tower-utils.js.map +1 -1
  113. package/dist/agent-farm/types.d.ts +13 -0
  114. package/dist/agent-farm/types.d.ts.map +1 -1
  115. package/dist/agent-farm/utils/config.d.ts +11 -0
  116. package/dist/agent-farm/utils/config.d.ts.map +1 -1
  117. package/dist/agent-farm/utils/config.js +39 -17
  118. package/dist/agent-farm/utils/config.js.map +1 -1
  119. package/dist/agent-farm/utils/harness.d.ts +85 -0
  120. package/dist/agent-farm/utils/harness.d.ts.map +1 -0
  121. package/dist/agent-farm/utils/harness.js +212 -0
  122. package/dist/agent-farm/utils/harness.js.map +1 -0
  123. package/dist/agent-farm/utils/roles.d.ts +11 -4
  124. package/dist/agent-farm/utils/roles.d.ts.map +1 -1
  125. package/dist/agent-farm/utils/roles.js +21 -6
  126. package/dist/agent-farm/utils/roles.js.map +1 -1
  127. package/dist/cli.d.ts +1 -1
  128. package/dist/cli.d.ts.map +1 -1
  129. package/dist/cli.js +40 -5
  130. package/dist/cli.js.map +1 -1
  131. package/dist/commands/adopt.d.ts.map +1 -1
  132. package/dist/commands/adopt.js +16 -45
  133. package/dist/commands/adopt.js.map +1 -1
  134. package/dist/commands/consult/index.d.ts +37 -3
  135. package/dist/commands/consult/index.d.ts.map +1 -1
  136. package/dist/commands/consult/index.js +281 -194
  137. package/dist/commands/consult/index.js.map +1 -1
  138. package/dist/commands/doctor.d.ts.map +1 -1
  139. package/dist/commands/doctor.js +121 -31
  140. package/dist/commands/doctor.js.map +1 -1
  141. package/dist/commands/init.d.ts.map +1 -1
  142. package/dist/commands/init.js +13 -43
  143. package/dist/commands/init.js.map +1 -1
  144. package/dist/commands/porch/artifacts.d.ts +69 -0
  145. package/dist/commands/porch/artifacts.d.ts.map +1 -0
  146. package/dist/commands/porch/artifacts.js +368 -0
  147. package/dist/commands/porch/artifacts.js.map +1 -0
  148. package/dist/commands/porch/checks.d.ts +14 -3
  149. package/dist/commands/porch/checks.d.ts.map +1 -1
  150. package/dist/commands/porch/checks.js +150 -4
  151. package/dist/commands/porch/checks.js.map +1 -1
  152. package/dist/commands/porch/config.d.ts +6 -9
  153. package/dist/commands/porch/config.d.ts.map +1 -1
  154. package/dist/commands/porch/config.js +12 -37
  155. package/dist/commands/porch/config.js.map +1 -1
  156. package/dist/commands/porch/index.d.ts +12 -7
  157. package/dist/commands/porch/index.d.ts.map +1 -1
  158. package/dist/commands/porch/index.js +273 -82
  159. package/dist/commands/porch/index.js.map +1 -1
  160. package/dist/commands/porch/next.d.ts.map +1 -1
  161. package/dist/commands/porch/next.js +142 -75
  162. package/dist/commands/porch/next.js.map +1 -1
  163. package/dist/commands/porch/notify.d.ts +2 -2
  164. package/dist/commands/porch/notify.js +5 -5
  165. package/dist/commands/porch/notify.js.map +1 -1
  166. package/dist/commands/porch/prompts.d.ts +2 -1
  167. package/dist/commands/porch/prompts.d.ts.map +1 -1
  168. package/dist/commands/porch/prompts.js +34 -38
  169. package/dist/commands/porch/prompts.js.map +1 -1
  170. package/dist/commands/porch/protocol.d.ts +4 -3
  171. package/dist/commands/porch/protocol.d.ts.map +1 -1
  172. package/dist/commands/porch/protocol.js +21 -21
  173. package/dist/commands/porch/protocol.js.map +1 -1
  174. package/dist/commands/porch/state.d.ts +27 -2
  175. package/dist/commands/porch/state.d.ts.map +1 -1
  176. package/dist/commands/porch/state.js +100 -9
  177. package/dist/commands/porch/state.js.map +1 -1
  178. package/dist/commands/porch/types.d.ts +10 -2
  179. package/dist/commands/porch/types.d.ts.map +1 -1
  180. package/dist/commands/sync.d.ts +26 -0
  181. package/dist/commands/sync.d.ts.map +1 -0
  182. package/dist/commands/sync.js +207 -0
  183. package/dist/commands/sync.js.map +1 -0
  184. package/dist/commands/update.d.ts +15 -2
  185. package/dist/commands/update.d.ts.map +1 -1
  186. package/dist/commands/update.js +159 -202
  187. package/dist/commands/update.js.map +1 -1
  188. package/dist/lib/config.d.ts +96 -0
  189. package/dist/lib/config.d.ts.map +1 -0
  190. package/dist/lib/config.js +165 -0
  191. package/dist/lib/config.js.map +1 -0
  192. package/dist/lib/forge-contracts.d.ts +100 -0
  193. package/dist/lib/forge-contracts.d.ts.map +1 -0
  194. package/dist/lib/forge-contracts.js +35 -0
  195. package/dist/lib/forge-contracts.js.map +1 -0
  196. package/dist/lib/forge.d.ts +102 -0
  197. package/dist/lib/forge.d.ts.map +1 -0
  198. package/dist/lib/forge.js +376 -0
  199. package/dist/lib/forge.js.map +1 -0
  200. package/dist/lib/github.d.ts +68 -60
  201. package/dist/lib/github.d.ts.map +1 -1
  202. package/dist/lib/github.js +160 -169
  203. package/dist/lib/github.js.map +1 -1
  204. package/dist/lib/skeleton.d.ts +15 -5
  205. package/dist/lib/skeleton.d.ts.map +1 -1
  206. package/dist/lib/skeleton.js +82 -7
  207. package/dist/lib/skeleton.js.map +1 -1
  208. package/dist/lib/team-github.d.ts +7 -3
  209. package/dist/lib/team-github.d.ts.map +1 -1
  210. package/dist/lib/team-github.js +29 -19
  211. package/dist/lib/team-github.js.map +1 -1
  212. package/dist/terminal/pty-manager.d.ts.map +1 -1
  213. package/dist/terminal/pty-manager.js +2 -0
  214. package/dist/terminal/pty-manager.js.map +1 -1
  215. package/dist/terminal/ws-protocol.d.ts +10 -8
  216. package/dist/terminal/ws-protocol.d.ts.map +1 -1
  217. package/dist/terminal/ws-protocol.js +3 -0
  218. package/dist/terminal/ws-protocol.js.map +1 -1
  219. package/package.json +27 -24
  220. package/scripts/forge/gitea/auth-status.sh +3 -0
  221. package/scripts/forge/gitea/issue-comment.sh +3 -0
  222. package/scripts/forge/gitea/issue-list.sh +3 -0
  223. package/scripts/forge/gitea/issue-view.sh +3 -0
  224. package/scripts/forge/gitea/pr-exists.sh +6 -0
  225. package/scripts/forge/gitea/pr-list.sh +3 -0
  226. package/scripts/forge/gitea/pr-merge.sh +3 -0
  227. package/scripts/forge/gitea/pr-view.sh +3 -0
  228. package/scripts/forge/gitea/recently-closed.sh +3 -0
  229. package/scripts/forge/gitea/recently-merged.sh +3 -0
  230. package/scripts/forge/gitea/repo-archive.sh +11 -0
  231. package/scripts/forge/gitea/user-identity.sh +3 -0
  232. package/scripts/forge/github/auth-status.sh +4 -0
  233. package/scripts/forge/github/issue-comment.sh +5 -0
  234. package/scripts/forge/github/issue-list.sh +4 -0
  235. package/scripts/forge/github/issue-view.sh +5 -0
  236. package/scripts/forge/github/on-it-timestamps.sh +8 -0
  237. package/scripts/forge/github/pr-diff.sh +10 -0
  238. package/scripts/forge/github/pr-exists.sh +7 -0
  239. package/scripts/forge/github/pr-list.sh +4 -0
  240. package/scripts/forge/github/pr-merge.sh +5 -0
  241. package/scripts/forge/github/pr-search.sh +5 -0
  242. package/scripts/forge/github/pr-view.sh +11 -0
  243. package/scripts/forge/github/recently-closed.sh +15 -0
  244. package/scripts/forge/github/recently-merged.sh +15 -0
  245. package/scripts/forge/github/repo-archive.sh +10 -0
  246. package/scripts/forge/github/team-activity.sh +5 -0
  247. package/scripts/forge/github/user-identity.sh +4 -0
  248. package/scripts/forge/gitlab/auth-status.sh +3 -0
  249. package/scripts/forge/gitlab/issue-comment.sh +3 -0
  250. package/scripts/forge/gitlab/issue-list.sh +3 -0
  251. package/scripts/forge/gitlab/issue-view.sh +3 -0
  252. package/scripts/forge/gitlab/pr-diff.sh +3 -0
  253. package/scripts/forge/gitlab/pr-exists.sh +5 -0
  254. package/scripts/forge/gitlab/pr-list.sh +3 -0
  255. package/scripts/forge/gitlab/pr-merge.sh +3 -0
  256. package/scripts/forge/gitlab/pr-search.sh +3 -0
  257. package/scripts/forge/gitlab/pr-view.sh +3 -0
  258. package/scripts/forge/gitlab/recently-closed.sh +3 -0
  259. package/scripts/forge/gitlab/recently-merged.sh +3 -0
  260. package/scripts/forge/gitlab/repo-archive.sh +12 -0
  261. package/scripts/forge/gitlab/user-identity.sh +3 -0
  262. package/skeleton/.claude/skills/afx/SKILL.md +144 -0
  263. package/skeleton/.claude/skills/codev/SKILL.md +3 -3
  264. package/skeleton/.claude/skills/consult/SKILL.md +1 -1
  265. package/skeleton/.claude/skills/porch/SKILL.md +2 -2
  266. package/skeleton/DEPENDENCIES.md +1 -1
  267. package/skeleton/builders.md +4 -4
  268. package/skeleton/protocol-schema.json +2 -2
  269. package/skeleton/protocols/air/builder-prompt.md +6 -6
  270. package/skeleton/protocols/air/prompts/pr.md +17 -5
  271. package/skeleton/protocols/air/protocol.md +1 -2
  272. package/skeleton/protocols/aspir/builder-prompt.md +28 -6
  273. package/skeleton/protocols/aspir/prompts/review.md +11 -0
  274. package/skeleton/protocols/aspir/protocol.json +9 -1
  275. package/skeleton/protocols/aspir/protocol.md +1 -1
  276. package/skeleton/protocols/aspir/templates/plan.md +0 -20
  277. package/skeleton/protocols/aspir/templates/spec.md +0 -28
  278. package/skeleton/protocols/bugfix/builder-prompt.md +6 -6
  279. package/skeleton/protocols/bugfix/prompts/pr.md +16 -4
  280. package/skeleton/protocols/experiment/builder-prompt.md +15 -0
  281. package/skeleton/protocols/experiment/protocol.md +1 -6
  282. package/skeleton/protocols/maintain/builder-prompt.md +12 -11
  283. package/skeleton/protocols/maintain/prompts/maintain.md +88 -0
  284. package/skeleton/protocols/maintain/prompts/review.md +84 -0
  285. package/skeleton/protocols/maintain/protocol.json +14 -68
  286. package/skeleton/protocols/maintain/protocol.md +111 -409
  287. package/skeleton/protocols/maintain/templates/maintenance-run.md +18 -49
  288. package/skeleton/protocols/protocol-schema.json +1 -1
  289. package/skeleton/protocols/spike/builder-prompt.md +1 -1
  290. package/skeleton/protocols/spike/protocol.md +4 -4
  291. package/skeleton/protocols/spir/builder-prompt.md +28 -5
  292. package/skeleton/protocols/spir/prompts/review.md +11 -0
  293. package/skeleton/protocols/spir/protocol.json +9 -1
  294. package/skeleton/protocols/spir/protocol.md +2 -2
  295. package/skeleton/protocols/spir/templates/plan.md +0 -20
  296. package/skeleton/protocols/spir/templates/spec.md +0 -28
  297. package/skeleton/resources/commands/agent-farm.md +84 -84
  298. package/skeleton/resources/commands/codev.md +1 -1
  299. package/skeleton/resources/commands/consult.md +16 -7
  300. package/skeleton/resources/commands/overview.md +14 -15
  301. package/skeleton/resources/risk-triage.md +1 -1
  302. package/skeleton/resources/workflow-reference.md +14 -14
  303. package/skeleton/roles/architect.md +55 -43
  304. package/skeleton/roles/builder.md +22 -14
  305. package/skeleton/templates/AGENTS.md +4 -3
  306. package/skeleton/templates/CLAUDE.md +4 -3
  307. package/skeleton/templates/cheatsheet.md +11 -12
  308. package/templates/3d-viewer.html +16 -18
  309. package/templates/tower.html +1 -1
  310. package/dashboard/dist/assets/index-CiIBsixF.js +0 -194
  311. package/dashboard/dist/assets/index-CiIBsixF.js.map +0 -1
  312. package/skeleton/.claude/skills/af/SKILL.md +0 -146
  313. package/skeleton/porch/prompts/understand.md +0 -61
  314. package/skeleton/porch/prompts/verify.md +0 -58
  315. package/skeleton/protocols/maintain/prompts/audit.md +0 -111
  316. package/skeleton/protocols/maintain/prompts/clean.md +0 -91
  317. package/skeleton/protocols/maintain/prompts/sync.md +0 -113
  318. package/skeleton/protocols/maintain/prompts/verify.md +0 -110
  319. package/skeleton/protocols/tick/builder-prompt.md +0 -65
  320. package/skeleton/protocols/tick/consult-types/impl-review.md +0 -72
  321. package/skeleton/protocols/tick/consult-types/plan-review.md +0 -59
  322. package/skeleton/protocols/tick/consult-types/pr-review.md +0 -72
  323. package/skeleton/protocols/tick/consult-types/spec-review.md +0 -55
  324. package/skeleton/protocols/tick/protocol.json +0 -151
  325. package/skeleton/protocols/tick/protocol.md +0 -277
  326. package/skeleton/protocols/tick/templates/plan.md +0 -67
  327. package/skeleton/protocols/tick/templates/review.md +0 -89
  328. package/skeleton/protocols/tick/templates/spec.md +0 -61
@@ -8,11 +8,13 @@ import * as fs from 'node:fs';
8
8
  import * as path from 'node:path';
9
9
  import chalk from 'chalk';
10
10
  import { globSync } from 'glob';
11
- import { readState, writeState, createInitialState, findStatusPath, getProjectDir, getStatusPath, resolveProjectId, resolveArtifactBaseName, } from './state.js';
11
+ import { readState, writeStateAndCommit, createInitialState, findStatusPath, getArtifactRoot, getProjectDir, getStatusPath, resolveProjectId, resolveArtifactBaseName, } from './state.js';
12
12
  import { loadProtocol, getPhaseConfig, getNextPhase, getPhaseChecks, getPhaseGate, isPhased, isBuildVerify, getVerifyConfig, } from './protocol.js';
13
- import { findPlanFile, extractPhasesFromFile, getCurrentPlanPhase, getPhaseContent, allPlanPhasesComplete, } from './plan.js';
13
+ import { findPlanFile, extractPlanPhases, getCurrentPlanPhase, getPhaseContent, allPlanPhasesComplete, } from './plan.js';
14
+ import { getResolver, LocalResolver } from './artifacts.js';
14
15
  import { runPhaseChecks, formatCheckResults, allChecksPassed, } from './checks.js';
15
16
  import { loadCheckOverrides } from './config.js';
17
+ import { loadConfig } from '../../lib/config.js';
16
18
  // ============================================================================
17
19
  // Output Helpers
18
20
  // ============================================================================
@@ -23,12 +25,32 @@ function header(text) {
23
25
  function section(title, content) {
24
26
  return `\n${chalk.bold(title)}:\n${content}`;
25
27
  }
28
+ /**
29
+ * Return a resolver scoped to `artifactRoot` when it differs from the caller's
30
+ * cwd-rooted resolver. The incoming `resolver` is typically built from
31
+ * `process.cwd()` (the main workspace), but `findStatusPath` may resolve a
32
+ * project to a builder worktree under `.builders/`. Checks like `plan_exists`
33
+ * must read from that worktree, not from the main tree (bugfix #676).
34
+ *
35
+ * For the LOCAL backend we rebuild the resolver against `artifactRoot` so file
36
+ * lookups point at `<artifactRoot>/codev/...`. For other backends (e.g. CLI)
37
+ * artifact location is already independent of the filesystem, so we keep the
38
+ * caller's resolver.
39
+ */
40
+ function scopeResolver(workspaceRoot, artifactRoot, resolver) {
41
+ if (artifactRoot === workspaceRoot)
42
+ return resolver;
43
+ // Only rebuild if the existing resolver is a LocalResolver (path-dependent).
44
+ if (resolver && !(resolver instanceof LocalResolver))
45
+ return resolver;
46
+ return getResolver(workspaceRoot, artifactRoot);
47
+ }
26
48
  /**
27
49
  * Log override/skip notices before running checks.
28
50
  * Only emits output when overrides are actually in use.
29
51
  * @param phaseCheckNames - original check names from the protocol phase
30
52
  * @param resolvedChecks - checks after applying overrides (skipped ones absent)
31
- * @param overrides - raw override map from af-config.json (null if not configured)
53
+ * @param overrides - raw override map from .codev/config.json (null if not configured)
32
54
  */
33
55
  function logCheckOverrides(phaseCheckNames, resolvedChecks, overrides) {
34
56
  if (!overrides)
@@ -38,7 +60,7 @@ function logCheckOverrides(phaseCheckNames, resolvedChecks, overrides) {
38
60
  if (!override)
39
61
  continue;
40
62
  if (override.skip) {
41
- console.log(chalk.yellow(` ⚠ Check "${name}" skipped (af-config.json)`));
63
+ console.log(chalk.yellow(` ⚠ Check "${name}" skipped (.codev/config.json)`));
42
64
  }
43
65
  else if (override.command || override.cwd) {
44
66
  const parts = [];
@@ -57,7 +79,7 @@ function logCheckOverrides(phaseCheckNames, resolvedChecks, overrides) {
57
79
  * porch status <id>
58
80
  * Shows current state and prescriptive next steps.
59
81
  */
60
- export async function status(workspaceRoot, projectId) {
82
+ export async function status(workspaceRoot, projectId, resolver) {
61
83
  const statusPath = findStatusPath(workspaceRoot, projectId);
62
84
  if (!statusPath) {
63
85
  throw new Error(`Project ${projectId} not found.\nRun 'porch init' to create a new project.`);
@@ -78,7 +100,8 @@ export async function status(workspaceRoot, projectId) {
78
100
  // Status icons
79
101
  const icon = (status) => {
80
102
  switch (status) {
81
- case 'complete': return chalk.green('✓');
103
+ case 'verified': return chalk.green('✓');
104
+ case 'complete': return chalk.green('✓'); // backward compat
82
105
  case 'in_progress': return chalk.yellow('►');
83
106
  default: return chalk.gray('○');
84
107
  }
@@ -94,11 +117,11 @@ export async function status(workspaceRoot, projectId) {
94
117
  if (currentPlanPhase) {
95
118
  console.log('');
96
119
  console.log(chalk.bold(`CURRENT: ${currentPlanPhase.id} - ${currentPlanPhase.title}`));
97
- // Show phase content from plan
98
- const planPath = findPlanFile(workspaceRoot, state.id, state.title);
99
- if (planPath) {
100
- const content = fs.readFileSync(planPath, 'utf-8');
101
- const phaseContent = getPhaseContent(content, currentPlanPhase.id);
120
+ // Show phase content from plan (via resolver if available)
121
+ const planContent = resolver?.getPlanContent(state.id, state.title)
122
+ ?? (() => { const p = findPlanFile(workspaceRoot, state.id, state.title); return p ? fs.readFileSync(p, 'utf-8') : null; })();
123
+ if (planContent) {
124
+ const phaseContent = getPhaseContent(planContent, currentPlanPhase.id);
102
125
  if (phaseContent) {
103
126
  console.log(section('FROM THE PLAN', phaseContent.slice(0, 500)));
104
127
  }
@@ -145,11 +168,16 @@ export async function status(workspaceRoot, projectId) {
145
168
  * porch check <id>
146
169
  * Runs the phase checks and reports results.
147
170
  */
148
- export async function check(workspaceRoot, projectId) {
171
+ export async function check(workspaceRoot, projectId, resolver) {
149
172
  const statusPath = findStatusPath(workspaceRoot, projectId);
150
173
  if (!statusPath) {
151
174
  throw new Error(`Project ${projectId} not found.`);
152
175
  }
176
+ // Scope artifact reads + check cwd to the worktree that owns this status.yaml.
177
+ // `findStatusPath` searches `.builders/*` first; resolver/cwd must match so
178
+ // checks like `plan_exists` see files in the same worktree (bugfix #676).
179
+ const artifactRoot = getArtifactRoot(statusPath);
180
+ const scopedResolver = scopeResolver(workspaceRoot, artifactRoot, resolver);
153
181
  const state = readState(statusPath);
154
182
  const protocol = loadProtocol(workspaceRoot, state.protocol);
155
183
  const overrides = loadCheckOverrides(workspaceRoot);
@@ -160,20 +188,20 @@ export async function check(workspaceRoot, projectId) {
160
188
  console.log(chalk.dim('No checks defined for this phase.'));
161
189
  return;
162
190
  }
163
- const checkEnv = { PROJECT_ID: state.id, PROJECT_TITLE: resolveArtifactBaseName(workspaceRoot, state.id, state.title) };
191
+ const checkEnv = { PROJECT_ID: state.id, PROJECT_TITLE: resolveArtifactBaseName(artifactRoot, state.id, state.title, scopedResolver) };
164
192
  console.log('');
165
193
  console.log(chalk.bold('RUNNING CHECKS...'));
166
194
  logCheckOverrides(phaseCheckNames, checks, overrides);
167
195
  console.log('');
168
196
  if (Object.keys(checks).length === 0) {
169
- console.log(chalk.dim(' (all checks skipped via af-config.json)'));
197
+ console.log(chalk.dim(' (all checks skipped via .codev/config.json)'));
170
198
  console.log('');
171
199
  console.log(chalk.green('RESULT: ALL CHECKS PASSED'));
172
200
  console.log(`\n Run: porch done ${state.id} (to advance)`);
173
201
  console.log('');
174
202
  return;
175
203
  }
176
- const results = await runPhaseChecks(checks, workspaceRoot, checkEnv);
204
+ const results = await runPhaseChecks(checks, artifactRoot, checkEnv, undefined, scopedResolver);
177
205
  console.log(formatCheckResults(results));
178
206
  console.log('');
179
207
  if (allChecksPassed(results)) {
@@ -190,17 +218,50 @@ export async function check(workspaceRoot, projectId) {
190
218
  * porch done <id>
191
219
  * Advances to next phase if checks pass. Refuses if checks fail.
192
220
  */
193
- export async function done(workspaceRoot, projectId) {
221
+ export async function done(workspaceRoot, projectId, resolver, options) {
194
222
  const statusPath = findStatusPath(workspaceRoot, projectId);
195
223
  if (!statusPath) {
196
224
  throw new Error(`Project ${projectId} not found.`);
197
225
  }
198
226
  let state = readState(statusPath);
227
+ // Record-only mode: --pr or --merged writes PR metadata and exits immediately.
228
+ // Does NOT run checks, does NOT advance the phase, does NOT mark build_complete.
229
+ if (options?.pr !== undefined) {
230
+ if (!options.branch)
231
+ throw new Error('--pr requires --branch <name>');
232
+ if (!state.pr_history)
233
+ state.pr_history = [];
234
+ state.pr_history.push({
235
+ phase: state.phase,
236
+ pr_number: options.pr,
237
+ branch: options.branch,
238
+ created_at: new Date().toISOString(),
239
+ });
240
+ await writeStateAndCommit(statusPath, state, `chore(porch): ${state.id} record PR #${options.pr}`);
241
+ console.log(chalk.green(`Recorded PR #${options.pr} (branch: ${options.branch}) in pr_history.`));
242
+ return;
243
+ }
244
+ if (options?.merged !== undefined) {
245
+ if (!state.pr_history)
246
+ throw new Error(`No PR history found for project ${projectId}`);
247
+ const entry = state.pr_history.find(e => e.pr_number === options.merged);
248
+ if (!entry)
249
+ throw new Error(`PR #${options.merged} not found in pr_history`);
250
+ entry.merged = true;
251
+ entry.merged_at = new Date().toISOString();
252
+ await writeStateAndCommit(statusPath, state, `chore(porch): ${state.id} PR #${options.merged} merged`);
253
+ console.log(chalk.green(`Marked PR #${options.merged} as merged.`));
254
+ return;
255
+ }
199
256
  const protocol = loadProtocol(workspaceRoot, state.protocol);
200
257
  const overrides = loadCheckOverrides(workspaceRoot);
201
258
  const phaseConfig = getPhaseConfig(protocol, state.phase);
202
259
  const phaseCheckNames = phaseConfig?.checks ?? [];
203
260
  const checks = getPhaseChecks(protocol, state.phase, overrides ?? undefined);
261
+ // Scope artifact reads + check cwd to the worktree that owns this status.yaml
262
+ // (bugfix #676 — see check() for rationale).
263
+ const artifactRoot = getArtifactRoot(statusPath);
264
+ const scopedResolver = scopeResolver(workspaceRoot, artifactRoot, resolver);
204
265
  // Run checks first — but skip if the gate was just approved (approve already ran them)
205
266
  if (phaseCheckNames.length > 0) {
206
267
  const gate = getPhaseGate(protocol, state.phase);
@@ -212,12 +273,12 @@ export async function done(workspaceRoot, projectId) {
212
273
  console.log(chalk.dim('Checks skipped (gate approved <60s ago).'));
213
274
  }
214
275
  else {
215
- const checkEnv = { PROJECT_ID: state.id, PROJECT_TITLE: resolveArtifactBaseName(workspaceRoot, state.id, state.title) };
276
+ const checkEnv = { PROJECT_ID: state.id, PROJECT_TITLE: resolveArtifactBaseName(artifactRoot, state.id, state.title, scopedResolver) };
216
277
  console.log('');
217
278
  console.log(chalk.bold('RUNNING CHECKS...'));
218
279
  logCheckOverrides(phaseCheckNames, checks, overrides);
219
280
  if (Object.keys(checks).length > 0) {
220
- const results = await runPhaseChecks(checks, workspaceRoot, checkEnv);
281
+ const results = await runPhaseChecks(checks, artifactRoot, checkEnv, undefined, scopedResolver);
221
282
  console.log(formatCheckResults(results));
222
283
  if (!allChecksPassed(results)) {
223
284
  console.log('');
@@ -227,44 +288,87 @@ export async function done(workspaceRoot, projectId) {
227
288
  }
228
289
  }
229
290
  else {
230
- console.log(chalk.dim(' (all checks skipped via af-config.json)'));
291
+ console.log(chalk.dim(' (all checks skipped via .codev/config.json)'));
231
292
  }
232
293
  }
233
294
  }
234
295
  // For build_verify phases: mark build as complete for verification
235
296
  if (isBuildVerify(protocol, state.phase) && !state.build_complete) {
236
297
  state.build_complete = true;
237
- writeState(statusPath, state);
298
+ await writeStateAndCommit(statusPath, state, `chore(porch): ${state.id} ${state.phase} build-complete`);
238
299
  console.log('');
239
300
  console.log(chalk.green('BUILD COMPLETE. Ready for verification.'));
240
301
  console.log(`\n Run: porch next ${state.id} (to get verification tasks)`);
241
302
  return;
242
303
  }
243
- // Enforce 3-way verification for build_verify phases
304
+ // Enforce verification for build_verify phases (config-aware)
244
305
  const verifyConfig = getVerifyConfig(protocol, state.phase);
245
306
  if (verifyConfig) {
246
- const projectDir = getProjectDir(workspaceRoot, state.id, state.title);
247
- const phase = state.current_plan_phase || state.phase;
248
- const missingModels = [];
249
- for (const model of verifyConfig.models) {
250
- // Look for any review file for this model+phase (any iteration)
251
- const pattern = path.join(projectDir, `${state.id}-${phase}-iter*-${model}.txt`);
252
- const matches = globSync(pattern);
253
- if (matches.length === 0) {
254
- missingModels.push(model);
307
+ // Resolve effective models from config (overrides protocol defaults)
308
+ let effectiveModels = verifyConfig.models;
309
+ let consultMode = 'normal';
310
+ try {
311
+ const config = loadConfig(workspaceRoot);
312
+ const configModels = config.porch?.consultation?.models;
313
+ if (configModels !== undefined) {
314
+ if (configModels === 'none') {
315
+ consultMode = 'none';
316
+ }
317
+ else if (configModels === 'parent') {
318
+ consultMode = 'parent';
319
+ }
320
+ else if (Array.isArray(configModels)) {
321
+ effectiveModels = configModels;
322
+ }
323
+ else if (typeof configModels === 'string') {
324
+ effectiveModels = [configModels];
325
+ }
255
326
  }
256
327
  }
257
- if (missingModels.length > 0) {
258
- console.log('');
259
- console.log(chalk.red('VERIFICATION REQUIRED'));
260
- console.log(`\n 3-way review not completed. Missing: ${missingModels.join(', ')}`);
261
- console.log(`\n Run: porch next ${state.id} (to trigger verification)`);
262
- process.exit(1);
328
+ catch {
329
+ // Config load failed — use protocol defaults
330
+ }
331
+ // "none" mode: skip verification
332
+ if (consultMode === 'none') {
333
+ console.log(chalk.dim(' (consultation skipped — configured: none)'));
334
+ }
335
+ else if (consultMode === 'parent') {
336
+ // "parent" mode: verification is handled by architect gate, not review files
337
+ console.log(chalk.dim(' (consultation delegated to architect — configured: parent)'));
338
+ }
339
+ else {
340
+ // Normal mode: check for review files from effective models
341
+ const projectDir = getProjectDir(workspaceRoot, state.id, state.title);
342
+ const phase = state.current_plan_phase || state.phase;
343
+ const missingModels = [];
344
+ for (const model of effectiveModels) {
345
+ // Look for any review file for this model+phase (any iteration)
346
+ const pattern = path.join(projectDir, `${state.id}-${phase}-iter*-${model}.txt`);
347
+ const matches = globSync(pattern);
348
+ if (matches.length === 0) {
349
+ missingModels.push(model);
350
+ }
351
+ }
352
+ if (missingModels.length > 0) {
353
+ console.log('');
354
+ console.log(chalk.red('VERIFICATION REQUIRED'));
355
+ console.log(`\n ${effectiveModels.length}-way review not completed. Missing: ${missingModels.join(', ')}`);
356
+ console.log(`\n Run: porch next ${state.id} (to trigger verification)`);
357
+ process.exit(1);
358
+ }
263
359
  }
264
360
  }
265
- // Check for gate
361
+ // Check for gate — auto-request if not yet requested
266
362
  const gate = getPhaseGate(protocol, state.phase);
267
363
  if (gate && state.gates[gate]?.status !== 'approved') {
364
+ // Auto-request the gate if it hasn't been requested yet
365
+ if (!state.gates[gate]) {
366
+ state.gates[gate] = { status: 'pending' };
367
+ }
368
+ if (!state.gates[gate].requested_at) {
369
+ state.gates[gate].requested_at = new Date().toISOString();
370
+ await writeStateAndCommit(statusPath, state, `chore(porch): ${state.id} ${gate} gate-requested`);
371
+ }
268
372
  console.log('');
269
373
  console.log(chalk.yellow(`GATE REQUIRED: ${gate}`));
270
374
  console.log(`\n Run: porch gate ${state.id}`);
@@ -284,13 +388,13 @@ export async function done(workspaceRoot, projectId) {
284
388
  }
285
389
  }
286
390
  // Advance to next protocol phase
287
- advanceProtocolPhase(workspaceRoot, state, protocol, statusPath);
391
+ await advanceProtocolPhase(workspaceRoot, state, protocol, statusPath, scopedResolver);
288
392
  }
289
- function advanceProtocolPhase(workspaceRoot, state, protocol, statusPath) {
393
+ async function advanceProtocolPhase(workspaceRoot, state, protocol, statusPath, resolver) {
290
394
  const nextPhase = getNextPhase(protocol, state.phase);
291
395
  if (!nextPhase) {
292
- state.phase = 'complete';
293
- writeState(statusPath, state);
396
+ state.phase = 'verified';
397
+ await writeStateAndCommit(statusPath, state, `chore(porch): ${state.id} protocol complete`);
294
398
  console.log('');
295
399
  console.log(chalk.green.bold('🎉 PROTOCOL COMPLETE'));
296
400
  console.log(`\n Project ${state.id} has completed the ${state.protocol} protocol.`);
@@ -301,16 +405,17 @@ function advanceProtocolPhase(workspaceRoot, state, protocol, statusPath) {
301
405
  state.iteration = 1;
302
406
  // If entering a phased phase (implement), extract plan phases
303
407
  if (isPhased(protocol, nextPhase.id)) {
304
- const planPath = findPlanFile(workspaceRoot, state.id, state.title);
305
- if (planPath) {
306
- state.plan_phases = extractPhasesFromFile(planPath);
307
- // extractPhasesFromFile already marks first phase as in_progress
408
+ const planContent = resolver?.getPlanContent(state.id, state.title)
409
+ ?? (() => { const p = findPlanFile(workspaceRoot, state.id, state.title); return p ? fs.readFileSync(p, 'utf-8') : null; })();
410
+ if (planContent) {
411
+ state.plan_phases = extractPlanPhases(planContent);
412
+ // extractPlanPhases already marks first phase as in_progress
308
413
  if (state.plan_phases.length > 0) {
309
414
  state.current_plan_phase = state.plan_phases[0].id;
310
415
  }
311
416
  }
312
417
  }
313
- writeState(statusPath, state);
418
+ await writeStateAndCommit(statusPath, state, `chore(porch): ${state.id} ${nextPhase.id} phase-transition`);
314
419
  console.log('');
315
420
  console.log(chalk.green(`ADVANCING TO: ${nextPhase.id} - ${nextPhase.name}`));
316
421
  // If we just entered implement phase, show phase 1 info and the critical warning
@@ -319,11 +424,11 @@ function advanceProtocolPhase(workspaceRoot, state, protocol, statusPath) {
319
424
  const nextPlanPhase = state.plan_phases[1];
320
425
  console.log('');
321
426
  console.log(chalk.bold(`YOUR TASK: ${firstPhase.id} - "${firstPhase.title}"`));
322
- // Show phase content from plan
323
- const planPath = findPlanFile(workspaceRoot, state.id, state.title);
324
- if (planPath) {
325
- const content = fs.readFileSync(planPath, 'utf-8');
326
- const phaseContent = getPhaseContent(content, firstPhase.id);
427
+ // Show phase content from plan (via resolver if available)
428
+ const planContentForDisplay = resolver?.getPlanContent(state.id, state.title)
429
+ ?? (() => { const p = findPlanFile(workspaceRoot, state.id, state.title); return p ? fs.readFileSync(p, 'utf-8') : null; })();
430
+ if (planContentForDisplay) {
431
+ const phaseContent = getPhaseContent(planContentForDisplay, firstPhase.id);
327
432
  if (phaseContent) {
328
433
  console.log(section('FROM THE PLAN', phaseContent.slice(0, 800)));
329
434
  }
@@ -347,7 +452,7 @@ function advanceProtocolPhase(workspaceRoot, state, protocol, statusPath) {
347
452
  * porch gate <id>
348
453
  * Requests human approval for current gate.
349
454
  */
350
- export async function gate(workspaceRoot, projectId) {
455
+ export async function gate(workspaceRoot, projectId, resolver) {
351
456
  const statusPath = findStatusPath(workspaceRoot, projectId);
352
457
  if (!statusPath) {
353
458
  throw new Error(`Project ${projectId} not found.`);
@@ -366,22 +471,22 @@ export async function gate(workspaceRoot, projectId) {
366
471
  }
367
472
  if (!state.gates[gateName].requested_at) {
368
473
  state.gates[gateName].requested_at = new Date().toISOString();
369
- writeState(statusPath, state);
474
+ await writeStateAndCommit(statusPath, state, `chore(porch): ${state.id} ${gateName} gate-requested`);
370
475
  }
371
476
  console.log('');
372
477
  console.log(chalk.bold(`GATE: ${gateName}`));
373
478
  console.log('');
374
479
  // Show relevant artifact and open it for review
375
- const artifact = getArtifactForPhase(workspaceRoot, state);
480
+ const artifact = getArtifactForPhase(workspaceRoot, state, resolver);
376
481
  if (artifact) {
377
482
  const fullPath = path.join(workspaceRoot, artifact);
378
483
  if (fs.existsSync(fullPath)) {
379
484
  console.log(` Artifact: ${artifact}`);
380
485
  console.log('');
381
486
  console.log(chalk.cyan(' Opening artifact for human review...'));
382
- // Use af open to display in annotation viewer
487
+ // Use afx open to display in annotation viewer
383
488
  const { spawn } = await import('node:child_process');
384
- spawn('af', ['open', fullPath], {
489
+ spawn('afx', ['open', fullPath], {
385
490
  stdio: 'inherit',
386
491
  detached: true
387
492
  }).unref();
@@ -400,15 +505,34 @@ export async function gate(workspaceRoot, projectId) {
400
505
  * porch approve <id> <gate> --a-human-explicitly-approved-this
401
506
  * Human approves a gate. Requires explicit flag to prevent automated approvals.
402
507
  */
403
- export async function approve(workspaceRoot, projectId, gateName, hasHumanFlag) {
508
+ export async function approve(workspaceRoot, projectId, gateName, hasHumanFlag, resolver) {
404
509
  const statusPath = findStatusPath(workspaceRoot, projectId);
405
510
  if (!statusPath) {
406
511
  throw new Error(`Project ${projectId} not found.`);
407
512
  }
513
+ // Scope artifact reads + check cwd to the worktree that owns this status.yaml
514
+ // (bugfix #676 — see check() for rationale).
515
+ const artifactRoot = getArtifactRoot(statusPath);
516
+ const scopedResolver = scopeResolver(workspaceRoot, artifactRoot, resolver);
408
517
  const state = readState(statusPath);
518
+ // Convenience: for verify-approval, auto-complete porch done if build_complete is false
519
+ if (gateName === 'verify-approval' && state.phase === 'verify' && !state.build_complete) {
520
+ state.build_complete = true;
521
+ await writeStateAndCommit(statusPath, state, `chore(porch): ${state.id} verify build-complete (auto)`);
522
+ }
523
+ // Auto-create gate entry for upgraded projects (e.g., verify-approval missing after upgrade)
409
524
  if (!state.gates[gateName]) {
410
- const knownGates = Object.keys(state.gates).join(', ');
411
- throw new Error(`Unknown gate: ${gateName}\nKnown gates: ${knownGates || 'none'}`);
525
+ const protocol = loadProtocol(workspaceRoot, state.protocol);
526
+ const phaseGate = getPhaseGate(protocol, state.phase);
527
+ if (phaseGate === gateName) {
528
+ // Gate belongs to the current phase — initialize it
529
+ state.gates[gateName] = { status: 'pending', requested_at: new Date().toISOString() };
530
+ await writeStateAndCommit(statusPath, state, `chore(porch): ${state.id} ${gateName} gate-created (upgrade)`);
531
+ }
532
+ else {
533
+ const knownGates = Object.keys(state.gates).join(', ');
534
+ throw new Error(`Unknown gate: ${gateName}\nKnown gates: ${knownGates || 'none'}`);
535
+ }
412
536
  }
413
537
  if (state.gates[gateName].status === 'approved') {
414
538
  console.log(chalk.yellow(`Gate ${gateName} is already approved.`));
@@ -432,12 +556,12 @@ export async function approve(workspaceRoot, projectId, gateName, hasHumanFlag)
432
556
  const phaseCheckNames = phaseConfig?.checks ?? [];
433
557
  const checks = getPhaseChecks(protocol, state.phase, overrides ?? undefined);
434
558
  if (phaseCheckNames.length > 0) {
435
- const checkEnv = { PROJECT_ID: state.id, PROJECT_TITLE: resolveArtifactBaseName(workspaceRoot, state.id, state.title) };
559
+ const checkEnv = { PROJECT_ID: state.id, PROJECT_TITLE: resolveArtifactBaseName(artifactRoot, state.id, state.title, scopedResolver) };
436
560
  console.log('');
437
561
  console.log(chalk.bold('RUNNING CHECKS...'));
438
562
  logCheckOverrides(phaseCheckNames, checks, overrides);
439
563
  if (Object.keys(checks).length > 0) {
440
- const results = await runPhaseChecks(checks, workspaceRoot, checkEnv);
564
+ const results = await runPhaseChecks(checks, artifactRoot, checkEnv, undefined, scopedResolver);
441
565
  console.log(formatCheckResults(results));
442
566
  if (!allChecksPassed(results)) {
443
567
  console.log('');
@@ -447,22 +571,32 @@ export async function approve(workspaceRoot, projectId, gateName, hasHumanFlag)
447
571
  }
448
572
  }
449
573
  else {
450
- console.log(chalk.dim(' (all checks skipped via af-config.json)'));
574
+ console.log(chalk.dim(' (all checks skipped via .codev/config.json)'));
451
575
  }
452
576
  }
453
577
  state.gates[gateName].status = 'approved';
454
578
  state.gates[gateName].approved_at = new Date().toISOString();
455
- writeState(statusPath, state);
579
+ await writeStateAndCommit(statusPath, state, `chore(porch): ${state.id} ${gateName} gate-approved`);
456
580
  console.log('');
457
581
  console.log(chalk.green(`Gate ${gateName} approved.`));
458
- console.log(`\n Run: porch done ${state.id} (to advance)`);
582
+ // For verify-approval: auto-advance to terminal state (convenience — one command)
583
+ // NOTE: The 'verified' state is committed to the builder branch, which may not
584
+ // be merged back to main. The closed GitHub Issue serves as the canonical "done"
585
+ // signal on main. State alignment (making status.yaml on main authoritative) is
586
+ // tracked as future work per spec 653.
587
+ if (gateName === 'verify-approval') {
588
+ await advanceProtocolPhase(workspaceRoot, state, protocol, statusPath, resolver);
589
+ }
590
+ else {
591
+ console.log(`\n Run: porch done ${state.id} (to advance)`);
592
+ }
459
593
  console.log('');
460
594
  }
461
595
  /**
462
596
  * porch rollback <id> <phase>
463
597
  * Rewinds project to an earlier phase, clearing downstream gates and resetting build state.
464
598
  */
465
- export async function rollback(workspaceRoot, projectId, targetPhase) {
599
+ export async function rollback(workspaceRoot, projectId, targetPhase, resolver) {
466
600
  const statusPath = findStatusPath(workspaceRoot, projectId);
467
601
  if (!statusPath) {
468
602
  throw new Error(`Project ${projectId} not found.`);
@@ -479,7 +613,7 @@ export async function rollback(workspaceRoot, projectId, targetPhase) {
479
613
  const currentIndex = protocol.phases.findIndex(p => p.id === state.phase);
480
614
  const targetIndex = protocol.phases.findIndex(p => p.id === targetPhase);
481
615
  // Handle completed projects (phase not in protocol phases array)
482
- if (state.phase === 'complete') {
616
+ if (state.phase === 'verified' || state.phase === 'complete') {
483
617
  // Allow rollback from complete state to any valid phase
484
618
  }
485
619
  else if (currentIndex === -1) {
@@ -502,11 +636,12 @@ export async function rollback(workspaceRoot, projectId, targetPhase) {
502
636
  state.iteration = 1;
503
637
  state.build_complete = false;
504
638
  state.history = [];
505
- // If rolling back to a phased phase, re-extract plan phases from plan file
639
+ // If rolling back to a phased phase, re-extract plan phases
506
640
  if (isPhased(protocol, targetPhase)) {
507
- const planPath = findPlanFile(workspaceRoot, state.id, state.title);
508
- if (planPath) {
509
- state.plan_phases = extractPhasesFromFile(planPath);
641
+ const planContent = resolver?.getPlanContent(state.id, state.title)
642
+ ?? (() => { const p = findPlanFile(workspaceRoot, state.id, state.title); return p ? fs.readFileSync(p, 'utf-8') : null; })();
643
+ if (planContent) {
644
+ state.plan_phases = extractPlanPhases(planContent);
510
645
  if (state.plan_phases.length > 0) {
511
646
  state.current_plan_phase = state.plan_phases[0].id;
512
647
  }
@@ -523,7 +658,7 @@ export async function rollback(workspaceRoot, projectId, targetPhase) {
523
658
  state.plan_phases = [];
524
659
  state.current_plan_phase = null;
525
660
  }
526
- writeState(statusPath, state);
661
+ await writeStateAndCommit(statusPath, state, `chore(porch): ${state.id} rollback ${previousPhase} → ${targetPhase}`);
527
662
  console.log('');
528
663
  console.log(chalk.green(`ROLLED BACK: ${previousPhase} → ${targetPhase}`));
529
664
  console.log(` Project: ${state.id}`);
@@ -536,7 +671,7 @@ export async function rollback(workspaceRoot, projectId, targetPhase) {
536
671
  * Initialize a new project.
537
672
  *
538
673
  * Idempotent: if status.yaml already exists, preserves it and reports
539
- * current state. This supports `af spawn --resume` where the builder
674
+ * current state. This supports `afx spawn --resume` where the builder
540
675
  * may re-run `porch init` after a session restart.
541
676
  */
542
677
  export async function init(workspaceRoot, protocolName, projectId, projectName) {
@@ -572,7 +707,7 @@ export async function init(workspaceRoot, protocolName, projectId, projectName)
572
707
  return;
573
708
  }
574
709
  const state = createInitialState(protocol, projectId, projectName, workspaceRoot);
575
- writeState(statusPath, state);
710
+ await writeStateAndCommit(statusPath, state, `chore(porch): ${state.id} init ${protocolName}`);
576
711
  console.log('');
577
712
  console.log(chalk.green(`Project initialized: ${projectId}-${projectName}`));
578
713
  console.log(` Protocol: ${protocolName}`);
@@ -611,8 +746,8 @@ function getNextAction(state, protocol) {
611
746
  }
612
747
  return `Complete the phase work, then run: porch done ${state.id}`;
613
748
  }
614
- function getArtifactForPhase(workspaceRoot, state) {
615
- const baseName = resolveArtifactBaseName(workspaceRoot, state.id, state.title);
749
+ function getArtifactForPhase(workspaceRoot, state, resolver) {
750
+ const baseName = resolveArtifactBaseName(workspaceRoot, state.id, state.title, resolver);
616
751
  switch (state.phase) {
617
752
  case 'specify':
618
753
  return `codev/specs/${baseName}.md`;
@@ -630,6 +765,7 @@ function getArtifactForPhase(workspaceRoot, state) {
630
765
  export async function cli(args) {
631
766
  const [command, ...rest] = args;
632
767
  const workspaceRoot = process.cwd();
768
+ const resolver = getResolver(workspaceRoot);
633
769
  // Auto-detect project ID for commands that need it
634
770
  function getProjectId(provided) {
635
771
  const { id, source } = resolveProjectId(provided, process.cwd(), workspaceRoot);
@@ -655,28 +791,80 @@ export async function cli(args) {
655
791
  process.exit(1);
656
792
  break;
657
793
  case 'status':
658
- await status(workspaceRoot, getProjectId(rest[0]));
794
+ await status(workspaceRoot, getProjectId(rest[0]), resolver);
659
795
  break;
660
796
  case 'check':
661
- await check(workspaceRoot, getProjectId(rest[0]));
797
+ await check(workspaceRoot, getProjectId(rest[0]), resolver);
662
798
  break;
663
- case 'done':
664
- await done(workspaceRoot, getProjectId(rest[0]));
799
+ case 'done': {
800
+ const doneOpts = {};
801
+ const prIdx = rest.indexOf('--pr');
802
+ const brIdx = rest.indexOf('--branch');
803
+ const mergedIdx = rest.indexOf('--merged');
804
+ if (prIdx !== -1) {
805
+ const val = parseInt(rest[prIdx + 1], 10);
806
+ if (!Number.isInteger(val) || val <= 0)
807
+ throw new Error('--pr requires a positive integer PR number');
808
+ doneOpts.pr = val;
809
+ }
810
+ if (brIdx !== -1) {
811
+ if (!rest[brIdx + 1] || rest[brIdx + 1].startsWith('--'))
812
+ throw new Error('--branch requires a branch name');
813
+ doneOpts.branch = rest[brIdx + 1];
814
+ }
815
+ if (mergedIdx !== -1) {
816
+ const val = parseInt(rest[mergedIdx + 1], 10);
817
+ if (!Number.isInteger(val) || val <= 0)
818
+ throw new Error('--merged requires a positive integer PR number');
819
+ doneOpts.merged = val;
820
+ }
821
+ if (doneOpts.pr !== undefined && doneOpts.merged !== undefined) {
822
+ throw new Error('--pr and --merged are mutually exclusive');
823
+ }
824
+ const hasRecordFlags = doneOpts.pr !== undefined || doneOpts.merged !== undefined;
825
+ // For project ID: use first positional arg, or fall back to auto-detection
826
+ const projectIdArg = rest[0] && !rest[0].startsWith('--') ? rest[0] : undefined;
827
+ await done(workspaceRoot, getProjectId(projectIdArg), resolver, hasRecordFlags ? doneOpts : undefined);
665
828
  break;
829
+ }
666
830
  case 'gate':
667
- await gate(workspaceRoot, getProjectId(rest[0]));
831
+ await gate(workspaceRoot, getProjectId(rest[0]), resolver);
668
832
  break;
669
833
  case 'approve':
670
834
  if (!rest[0] || !rest[1])
671
835
  throw new Error('Usage: porch approve <id> <gate> --a-human-explicitly-approved-this');
672
836
  const hasHumanFlag = rest.includes('--a-human-explicitly-approved-this');
673
- await approve(workspaceRoot, rest[0], rest[1], hasHumanFlag);
837
+ await approve(workspaceRoot, rest[0], rest[1], hasHumanFlag, resolver);
674
838
  break;
675
839
  case 'rollback':
676
840
  if (!rest[0] || !rest[1])
677
841
  throw new Error('Usage: porch rollback <id> <phase>');
678
- await rollback(workspaceRoot, rest[0], rest[1]);
842
+ await rollback(workspaceRoot, rest[0], rest[1], resolver);
679
843
  break;
844
+ case 'verify': {
845
+ const verifyProjectId = rest[0] && !rest[0].startsWith('--') ? rest[0] : undefined;
846
+ const skipIdx = rest.indexOf('--skip');
847
+ if (skipIdx === -1)
848
+ throw new Error('Usage: porch verify <id> --skip "reason"');
849
+ const skipReason = rest[skipIdx + 1];
850
+ if (!skipReason || skipReason.startsWith('--'))
851
+ throw new Error('--skip requires a reason');
852
+ const pid = getProjectId(verifyProjectId);
853
+ const sp = findStatusPath(workspaceRoot, pid);
854
+ if (!sp)
855
+ throw new Error(`Project ${pid} not found.`);
856
+ const st = readState(sp);
857
+ if (st.phase !== 'verify') {
858
+ throw new Error(`porch verify --skip can only be used in the verify phase (current: ${st.phase}). The PR must be merged first.`);
859
+ }
860
+ st.phase = 'verified';
861
+ st.context = { ...st.context, verify_skip_reason: skipReason };
862
+ await writeStateAndCommit(sp, st, `chore(porch): ${st.id} verify skipped: ${skipReason}`);
863
+ console.log('');
864
+ console.log(chalk.green(`VERIFIED (skipped): ${st.id}`));
865
+ console.log(` Reason: ${skipReason}`);
866
+ break;
867
+ }
680
868
  case 'init':
681
869
  if (!rest[0] || !rest[1] || !rest[2]) {
682
870
  throw new Error('Usage: porch init <protocol> <id> <name>');
@@ -691,8 +879,11 @@ export async function cli(args) {
691
879
  console.log(' status [id] Show current state and instructions');
692
880
  console.log(' check [id] Run checks for current phase');
693
881
  console.log(' done [id] Signal build complete (validates checks, advances)');
882
+ console.log(' done [id] --pr N --branch NAME Record PR creation (no phase advancement)');
883
+ console.log(' done [id] --merged N Mark PR as merged (no phase advancement)');
694
884
  console.log(' gate [id] Request human approval');
695
885
  console.log(' approve <id> <gate> --a-human-explicitly-approved-this');
886
+ console.log(' verify <id> --skip "reason" Skip verification and mark as verified');
696
887
  console.log(' rollback <id> <phase> Rewind project to an earlier phase');
697
888
  console.log(' init <protocol> <id> <name> Initialize a new project');
698
889
  console.log('');