@cluesmith/codev 3.0.0 → 3.0.1

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 (286) 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-BWYZtvwm.css} +1 -1
  7. package/dashboard-dist/assets/index-Dn9zmcKp.js +195 -0
  8. package/dashboard-dist/assets/index-Dn9zmcKp.js.map +1 -0
  9. package/{dashboard/dist → dashboard-dist}/index.html +2 -2
  10. package/dist/agent-farm/cli.d.ts.map +1 -1
  11. package/dist/agent-farm/cli.js +13 -14
  12. package/dist/agent-farm/cli.js.map +1 -1
  13. package/dist/agent-farm/commands/architect.d.ts +4 -4
  14. package/dist/agent-farm/commands/architect.d.ts.map +1 -1
  15. package/dist/agent-farm/commands/architect.js +23 -12
  16. package/dist/agent-farm/commands/architect.js.map +1 -1
  17. package/dist/agent-farm/commands/attach.js +8 -8
  18. package/dist/agent-farm/commands/attach.js.map +1 -1
  19. package/dist/agent-farm/commands/bench.d.ts +1 -1
  20. package/dist/agent-farm/commands/bench.js +1 -1
  21. package/dist/agent-farm/commands/cron.js +1 -1
  22. package/dist/agent-farm/commands/cron.js.map +1 -1
  23. package/dist/agent-farm/commands/db.d.ts +3 -3
  24. package/dist/agent-farm/commands/db.js +4 -4
  25. package/dist/agent-farm/commands/db.js.map +1 -1
  26. package/dist/agent-farm/commands/open.js +2 -2
  27. package/dist/agent-farm/commands/open.js.map +1 -1
  28. package/dist/agent-farm/commands/rename.js +1 -1
  29. package/dist/agent-farm/commands/rename.js.map +1 -1
  30. package/dist/agent-farm/commands/send.js +3 -3
  31. package/dist/agent-farm/commands/send.js.map +1 -1
  32. package/dist/agent-farm/commands/shell.js +3 -3
  33. package/dist/agent-farm/commands/shell.js.map +1 -1
  34. package/dist/agent-farm/commands/spawn-roles.d.ts +8 -4
  35. package/dist/agent-farm/commands/spawn-roles.d.ts.map +1 -1
  36. package/dist/agent-farm/commands/spawn-roles.js +48 -28
  37. package/dist/agent-farm/commands/spawn-roles.js.map +1 -1
  38. package/dist/agent-farm/commands/spawn-worktree.d.ts +1 -1
  39. package/dist/agent-farm/commands/spawn-worktree.d.ts.map +1 -1
  40. package/dist/agent-farm/commands/spawn-worktree.js +80 -13
  41. package/dist/agent-farm/commands/spawn-worktree.js.map +1 -1
  42. package/dist/agent-farm/commands/spawn.d.ts +6 -6
  43. package/dist/agent-farm/commands/spawn.d.ts.map +1 -1
  44. package/dist/agent-farm/commands/spawn.js +90 -53
  45. package/dist/agent-farm/commands/spawn.js.map +1 -1
  46. package/dist/agent-farm/commands/status.d.ts.map +1 -1
  47. package/dist/agent-farm/commands/status.js +48 -3
  48. package/dist/agent-farm/commands/status.js.map +1 -1
  49. package/dist/agent-farm/commands/team-update.d.ts +1 -1
  50. package/dist/agent-farm/commands/team-update.js +1 -1
  51. package/dist/agent-farm/commands/team.d.ts +1 -1
  52. package/dist/agent-farm/commands/team.js +1 -1
  53. package/dist/agent-farm/commands/tower-cloud.d.ts +2 -2
  54. package/dist/agent-farm/commands/tower-cloud.js +3 -3
  55. package/dist/agent-farm/commands/tower-cloud.js.map +1 -1
  56. package/dist/agent-farm/commands/tower.js +2 -2
  57. package/dist/agent-farm/commands/tower.js.map +1 -1
  58. package/dist/agent-farm/lib/cloud-config.js +2 -2
  59. package/dist/agent-farm/lib/cloud-config.js.map +1 -1
  60. package/dist/agent-farm/lib/tower-client.d.ts +6 -230
  61. package/dist/agent-farm/lib/tower-client.d.ts.map +1 -1
  62. package/dist/agent-farm/lib/tower-client.js +6 -293
  63. package/dist/agent-farm/lib/tower-client.js.map +1 -1
  64. package/dist/agent-farm/lib/tunnel-client.js +1 -1
  65. package/dist/agent-farm/lib/tunnel-client.js.map +1 -1
  66. package/dist/agent-farm/servers/message-write.d.ts +24 -0
  67. package/dist/agent-farm/servers/message-write.d.ts.map +1 -0
  68. package/dist/agent-farm/servers/message-write.js +63 -0
  69. package/dist/agent-farm/servers/message-write.js.map +1 -0
  70. package/dist/agent-farm/servers/overview.d.ts +4 -1
  71. package/dist/agent-farm/servers/overview.d.ts.map +1 -1
  72. package/dist/agent-farm/servers/overview.js +38 -5
  73. package/dist/agent-farm/servers/overview.js.map +1 -1
  74. package/dist/agent-farm/servers/send-buffer.d.ts +4 -3
  75. package/dist/agent-farm/servers/send-buffer.d.ts.map +1 -1
  76. package/dist/agent-farm/servers/send-buffer.js +7 -4
  77. package/dist/agent-farm/servers/send-buffer.js.map +1 -1
  78. package/dist/agent-farm/servers/tower-cron-parser.js +1 -1
  79. package/dist/agent-farm/servers/tower-cron-parser.js.map +1 -1
  80. package/dist/agent-farm/servers/tower-cron.d.ts.map +1 -1
  81. package/dist/agent-farm/servers/tower-cron.js +4 -5
  82. package/dist/agent-farm/servers/tower-cron.js.map +1 -1
  83. package/dist/agent-farm/servers/tower-instances.d.ts.map +1 -1
  84. package/dist/agent-farm/servers/tower-instances.js +6 -5
  85. package/dist/agent-farm/servers/tower-instances.js.map +1 -1
  86. package/dist/agent-farm/servers/tower-routes.d.ts.map +1 -1
  87. package/dist/agent-farm/servers/tower-routes.js +19 -16
  88. package/dist/agent-farm/servers/tower-routes.js.map +1 -1
  89. package/dist/agent-farm/servers/tower-server.js +1 -1
  90. package/dist/agent-farm/servers/tower-terminals.d.ts +2 -2
  91. package/dist/agent-farm/servers/tower-terminals.d.ts.map +1 -1
  92. package/dist/agent-farm/servers/tower-terminals.js +47 -18
  93. package/dist/agent-farm/servers/tower-terminals.js.map +1 -1
  94. package/dist/agent-farm/servers/tower-tunnel.js +2 -2
  95. package/dist/agent-farm/servers/tower-tunnel.js.map +1 -1
  96. package/dist/agent-farm/servers/tower-utils.d.ts +9 -6
  97. package/dist/agent-farm/servers/tower-utils.d.ts.map +1 -1
  98. package/dist/agent-farm/servers/tower-utils.js +12 -6
  99. package/dist/agent-farm/servers/tower-utils.js.map +1 -1
  100. package/dist/agent-farm/types.d.ts +9 -0
  101. package/dist/agent-farm/types.d.ts.map +1 -1
  102. package/dist/agent-farm/utils/config.d.ts +11 -0
  103. package/dist/agent-farm/utils/config.d.ts.map +1 -1
  104. package/dist/agent-farm/utils/config.js +21 -0
  105. package/dist/agent-farm/utils/config.js.map +1 -1
  106. package/dist/agent-farm/utils/harness.d.ts +85 -0
  107. package/dist/agent-farm/utils/harness.d.ts.map +1 -0
  108. package/dist/agent-farm/utils/harness.js +212 -0
  109. package/dist/agent-farm/utils/harness.js.map +1 -0
  110. package/dist/cli.d.ts +1 -1
  111. package/dist/cli.d.ts.map +1 -1
  112. package/dist/cli.js +19 -5
  113. package/dist/cli.js.map +1 -1
  114. package/dist/commands/adopt.js +2 -2
  115. package/dist/commands/adopt.js.map +1 -1
  116. package/dist/commands/consult/index.d.ts +37 -3
  117. package/dist/commands/consult/index.d.ts.map +1 -1
  118. package/dist/commands/consult/index.js +220 -150
  119. package/dist/commands/consult/index.js.map +1 -1
  120. package/dist/commands/doctor.d.ts.map +1 -1
  121. package/dist/commands/doctor.js +54 -8
  122. package/dist/commands/doctor.js.map +1 -1
  123. package/dist/commands/init.js +1 -1
  124. package/dist/commands/init.js.map +1 -1
  125. package/dist/commands/porch/artifacts.d.ts +69 -0
  126. package/dist/commands/porch/artifacts.d.ts.map +1 -0
  127. package/dist/commands/porch/artifacts.js +368 -0
  128. package/dist/commands/porch/artifacts.js.map +1 -0
  129. package/dist/commands/porch/checks.d.ts +8 -1
  130. package/dist/commands/porch/checks.d.ts.map +1 -1
  131. package/dist/commands/porch/checks.js +98 -1
  132. package/dist/commands/porch/checks.js.map +1 -1
  133. package/dist/commands/porch/index.d.ts +12 -7
  134. package/dist/commands/porch/index.d.ts.map +1 -1
  135. package/dist/commands/porch/index.js +216 -61
  136. package/dist/commands/porch/index.js.map +1 -1
  137. package/dist/commands/porch/next.d.ts.map +1 -1
  138. package/dist/commands/porch/next.js +72 -74
  139. package/dist/commands/porch/next.js.map +1 -1
  140. package/dist/commands/porch/notify.d.ts +2 -2
  141. package/dist/commands/porch/notify.js +5 -5
  142. package/dist/commands/porch/notify.js.map +1 -1
  143. package/dist/commands/porch/prompts.d.ts +2 -1
  144. package/dist/commands/porch/prompts.d.ts.map +1 -1
  145. package/dist/commands/porch/prompts.js +19 -7
  146. package/dist/commands/porch/prompts.js.map +1 -1
  147. package/dist/commands/porch/state.d.ts +27 -2
  148. package/dist/commands/porch/state.d.ts.map +1 -1
  149. package/dist/commands/porch/state.js +96 -9
  150. package/dist/commands/porch/state.js.map +1 -1
  151. package/dist/commands/porch/types.d.ts +8 -0
  152. package/dist/commands/porch/types.d.ts.map +1 -1
  153. package/dist/lib/config.d.ts +14 -0
  154. package/dist/lib/config.d.ts.map +1 -1
  155. package/dist/lib/config.js +7 -0
  156. package/dist/lib/config.js.map +1 -1
  157. package/dist/lib/forge-contracts.d.ts +9 -0
  158. package/dist/lib/forge-contracts.d.ts.map +1 -1
  159. package/dist/lib/forge-contracts.js.map +1 -1
  160. package/dist/lib/forge.js +1 -1
  161. package/dist/lib/forge.js.map +1 -1
  162. package/dist/lib/team-github.d.ts +49 -0
  163. package/dist/lib/team-github.d.ts.map +1 -1
  164. package/dist/lib/team-github.js +115 -6
  165. package/dist/lib/team-github.js.map +1 -1
  166. package/dist/terminal/pty-manager.d.ts.map +1 -1
  167. package/dist/terminal/pty-manager.js +2 -0
  168. package/dist/terminal/pty-manager.js.map +1 -1
  169. package/dist/terminal/ws-protocol.d.ts +10 -8
  170. package/dist/terminal/ws-protocol.d.ts.map +1 -1
  171. package/dist/terminal/ws-protocol.js +3 -0
  172. package/dist/terminal/ws-protocol.js.map +1 -1
  173. package/package.json +28 -25
  174. package/scripts/forge/gitea/auth-status.sh +0 -0
  175. package/scripts/forge/gitea/issue-comment.sh +0 -0
  176. package/scripts/forge/gitea/issue-list.sh +0 -0
  177. package/scripts/forge/gitea/issue-view.sh +0 -0
  178. package/scripts/forge/gitea/pr-exists.sh +4 -1
  179. package/scripts/forge/gitea/pr-list.sh +0 -0
  180. package/scripts/forge/gitea/pr-merge.sh +0 -0
  181. package/scripts/forge/gitea/pr-view.sh +0 -0
  182. package/scripts/forge/gitea/recently-closed.sh +0 -0
  183. package/scripts/forge/gitea/recently-merged.sh +0 -0
  184. package/scripts/forge/gitea/repo-archive.sh +0 -0
  185. package/scripts/forge/gitea/user-identity.sh +0 -0
  186. package/scripts/forge/github/auth-status.sh +0 -0
  187. package/scripts/forge/github/issue-comment.sh +0 -0
  188. package/scripts/forge/github/issue-list.sh +2 -2
  189. package/scripts/forge/github/issue-view.sh +0 -0
  190. package/scripts/forge/github/on-it-timestamps.sh +0 -0
  191. package/scripts/forge/github/pr-diff.sh +0 -0
  192. package/scripts/forge/github/pr-exists.sh +3 -1
  193. package/scripts/forge/github/pr-list.sh +2 -2
  194. package/scripts/forge/github/pr-merge.sh +0 -0
  195. package/scripts/forge/github/pr-search.sh +0 -0
  196. package/scripts/forge/github/pr-view.sh +0 -0
  197. package/scripts/forge/github/recently-closed.sh +0 -0
  198. package/scripts/forge/github/recently-merged.sh +0 -0
  199. package/scripts/forge/github/repo-archive.sh +0 -0
  200. package/scripts/forge/github/team-activity.sh +0 -0
  201. package/scripts/forge/github/user-identity.sh +0 -0
  202. package/scripts/forge/gitlab/auth-status.sh +0 -0
  203. package/scripts/forge/gitlab/issue-comment.sh +0 -0
  204. package/scripts/forge/gitlab/issue-list.sh +0 -0
  205. package/scripts/forge/gitlab/issue-view.sh +0 -0
  206. package/scripts/forge/gitlab/pr-diff.sh +0 -0
  207. package/scripts/forge/gitlab/pr-exists.sh +3 -1
  208. package/scripts/forge/gitlab/pr-list.sh +0 -0
  209. package/scripts/forge/gitlab/pr-merge.sh +0 -0
  210. package/scripts/forge/gitlab/pr-search.sh +0 -0
  211. package/scripts/forge/gitlab/pr-view.sh +0 -0
  212. package/scripts/forge/gitlab/recently-closed.sh +0 -0
  213. package/scripts/forge/gitlab/recently-merged.sh +0 -0
  214. package/scripts/forge/gitlab/repo-archive.sh +0 -0
  215. package/scripts/forge/gitlab/user-identity.sh +0 -0
  216. package/scripts/postinstall.mjs +51 -0
  217. package/skeleton/.claude/skills/afx/SKILL.md +144 -0
  218. package/skeleton/.claude/skills/codev/SKILL.md +3 -3
  219. package/skeleton/.claude/skills/consult/SKILL.md +1 -1
  220. package/skeleton/.claude/skills/porch/SKILL.md +2 -2
  221. package/skeleton/DEPENDENCIES.md +1 -1
  222. package/skeleton/builders.md +4 -4
  223. package/skeleton/protocol-schema.json +2 -2
  224. package/skeleton/protocols/air/builder-prompt.md +6 -6
  225. package/skeleton/protocols/air/prompts/pr.md +17 -5
  226. package/skeleton/protocols/air/protocol.md +1 -2
  227. package/skeleton/protocols/aspir/builder-prompt.md +28 -6
  228. package/skeleton/protocols/aspir/prompts/review.md +11 -0
  229. package/skeleton/protocols/aspir/protocol.json +9 -1
  230. package/skeleton/protocols/aspir/protocol.md +1 -1
  231. package/skeleton/protocols/aspir/templates/plan.md +0 -20
  232. package/skeleton/protocols/aspir/templates/spec.md +0 -28
  233. package/skeleton/protocols/bugfix/builder-prompt.md +6 -6
  234. package/skeleton/protocols/bugfix/prompts/pr.md +16 -4
  235. package/skeleton/protocols/experiment/builder-prompt.md +15 -0
  236. package/skeleton/protocols/experiment/protocol.md +1 -6
  237. package/skeleton/protocols/maintain/builder-prompt.md +12 -11
  238. package/skeleton/protocols/maintain/prompts/maintain.md +88 -0
  239. package/skeleton/protocols/maintain/prompts/review.md +84 -0
  240. package/skeleton/protocols/maintain/protocol.json +14 -68
  241. package/skeleton/protocols/maintain/protocol.md +111 -409
  242. package/skeleton/protocols/maintain/templates/maintenance-run.md +18 -49
  243. package/skeleton/protocols/protocol-schema.json +1 -1
  244. package/skeleton/protocols/research/builder-prompt.md +85 -0
  245. package/skeleton/protocols/research/protocol.json +118 -0
  246. package/skeleton/protocols/research/protocol.md +169 -0
  247. package/skeleton/protocols/spike/builder-prompt.md +1 -1
  248. package/skeleton/protocols/spike/protocol.md +4 -4
  249. package/skeleton/protocols/spir/builder-prompt.md +28 -5
  250. package/skeleton/protocols/spir/prompts/review.md +11 -0
  251. package/skeleton/protocols/spir/protocol.json +9 -1
  252. package/skeleton/protocols/spir/protocol.md +2 -2
  253. package/skeleton/protocols/spir/templates/plan.md +0 -20
  254. package/skeleton/protocols/spir/templates/spec.md +0 -28
  255. package/skeleton/resources/commands/agent-farm.md +84 -84
  256. package/skeleton/resources/commands/codev.md +1 -1
  257. package/skeleton/resources/commands/consult.md +16 -7
  258. package/skeleton/resources/commands/overview.md +14 -15
  259. package/skeleton/resources/risk-triage.md +1 -1
  260. package/skeleton/resources/workflow-reference.md +14 -14
  261. package/skeleton/roles/architect.md +39 -39
  262. package/skeleton/roles/builder.md +22 -14
  263. package/skeleton/templates/AGENTS.md +5 -3
  264. package/skeleton/templates/CLAUDE.md +5 -3
  265. package/skeleton/templates/cheatsheet.md +12 -12
  266. package/templates/3d-viewer.html +16 -18
  267. package/templates/tower.html +1 -1
  268. package/dashboard/dist/assets/index-CTrlY11L.js +0 -194
  269. package/dashboard/dist/assets/index-CTrlY11L.js.map +0 -1
  270. package/skeleton/.claude/skills/af/SKILL.md +0 -146
  271. package/skeleton/porch/prompts/understand.md +0 -61
  272. package/skeleton/porch/prompts/verify.md +0 -58
  273. package/skeleton/protocols/maintain/prompts/audit.md +0 -111
  274. package/skeleton/protocols/maintain/prompts/clean.md +0 -91
  275. package/skeleton/protocols/maintain/prompts/sync.md +0 -113
  276. package/skeleton/protocols/maintain/prompts/verify.md +0 -110
  277. package/skeleton/protocols/tick/builder-prompt.md +0 -65
  278. package/skeleton/protocols/tick/consult-types/impl-review.md +0 -72
  279. package/skeleton/protocols/tick/consult-types/plan-review.md +0 -59
  280. package/skeleton/protocols/tick/consult-types/pr-review.md +0 -72
  281. package/skeleton/protocols/tick/consult-types/spec-review.md +0 -55
  282. package/skeleton/protocols/tick/protocol.json +0 -151
  283. package/skeleton/protocols/tick/protocol.md +0 -277
  284. package/skeleton/protocols/tick/templates/plan.md +0 -67
  285. package/skeleton/protocols/tick/templates/review.md +0 -89
  286. package/skeleton/protocols/tick/templates/spec.md +0 -61
@@ -8,9 +8,10 @@ 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';
16
17
  import { loadConfig } from '../../lib/config.js';
@@ -24,6 +25,26 @@ function header(text) {
24
25
  function section(title, content) {
25
26
  return `\n${chalk.bold(title)}:\n${content}`;
26
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
+ }
27
48
  /**
28
49
  * Log override/skip notices before running checks.
29
50
  * Only emits output when overrides are actually in use.
@@ -58,7 +79,7 @@ function logCheckOverrides(phaseCheckNames, resolvedChecks, overrides) {
58
79
  * porch status <id>
59
80
  * Shows current state and prescriptive next steps.
60
81
  */
61
- export async function status(workspaceRoot, projectId) {
82
+ export async function status(workspaceRoot, projectId, resolver) {
62
83
  const statusPath = findStatusPath(workspaceRoot, projectId);
63
84
  if (!statusPath) {
64
85
  throw new Error(`Project ${projectId} not found.\nRun 'porch init' to create a new project.`);
@@ -79,7 +100,8 @@ export async function status(workspaceRoot, projectId) {
79
100
  // Status icons
80
101
  const icon = (status) => {
81
102
  switch (status) {
82
- case 'complete': return chalk.green('✓');
103
+ case 'verified': return chalk.green('✓');
104
+ case 'complete': return chalk.green('✓'); // backward compat
83
105
  case 'in_progress': return chalk.yellow('►');
84
106
  default: return chalk.gray('○');
85
107
  }
@@ -95,11 +117,11 @@ export async function status(workspaceRoot, projectId) {
95
117
  if (currentPlanPhase) {
96
118
  console.log('');
97
119
  console.log(chalk.bold(`CURRENT: ${currentPlanPhase.id} - ${currentPlanPhase.title}`));
98
- // Show phase content from plan
99
- const planPath = findPlanFile(workspaceRoot, state.id, state.title);
100
- if (planPath) {
101
- const content = fs.readFileSync(planPath, 'utf-8');
102
- 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);
103
125
  if (phaseContent) {
104
126
  console.log(section('FROM THE PLAN', phaseContent.slice(0, 500)));
105
127
  }
@@ -146,11 +168,16 @@ export async function status(workspaceRoot, projectId) {
146
168
  * porch check <id>
147
169
  * Runs the phase checks and reports results.
148
170
  */
149
- export async function check(workspaceRoot, projectId) {
171
+ export async function check(workspaceRoot, projectId, resolver) {
150
172
  const statusPath = findStatusPath(workspaceRoot, projectId);
151
173
  if (!statusPath) {
152
174
  throw new Error(`Project ${projectId} not found.`);
153
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);
154
181
  const state = readState(statusPath);
155
182
  const protocol = loadProtocol(workspaceRoot, state.protocol);
156
183
  const overrides = loadCheckOverrides(workspaceRoot);
@@ -161,7 +188,7 @@ export async function check(workspaceRoot, projectId) {
161
188
  console.log(chalk.dim('No checks defined for this phase.'));
162
189
  return;
163
190
  }
164
- 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) };
165
192
  console.log('');
166
193
  console.log(chalk.bold('RUNNING CHECKS...'));
167
194
  logCheckOverrides(phaseCheckNames, checks, overrides);
@@ -174,7 +201,7 @@ export async function check(workspaceRoot, projectId) {
174
201
  console.log('');
175
202
  return;
176
203
  }
177
- const results = await runPhaseChecks(checks, workspaceRoot, checkEnv);
204
+ const results = await runPhaseChecks(checks, artifactRoot, checkEnv, undefined, scopedResolver);
178
205
  console.log(formatCheckResults(results));
179
206
  console.log('');
180
207
  if (allChecksPassed(results)) {
@@ -191,17 +218,50 @@ export async function check(workspaceRoot, projectId) {
191
218
  * porch done <id>
192
219
  * Advances to next phase if checks pass. Refuses if checks fail.
193
220
  */
194
- export async function done(workspaceRoot, projectId) {
221
+ export async function done(workspaceRoot, projectId, resolver, options) {
195
222
  const statusPath = findStatusPath(workspaceRoot, projectId);
196
223
  if (!statusPath) {
197
224
  throw new Error(`Project ${projectId} not found.`);
198
225
  }
199
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
+ }
200
256
  const protocol = loadProtocol(workspaceRoot, state.protocol);
201
257
  const overrides = loadCheckOverrides(workspaceRoot);
202
258
  const phaseConfig = getPhaseConfig(protocol, state.phase);
203
259
  const phaseCheckNames = phaseConfig?.checks ?? [];
204
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);
205
265
  // Run checks first — but skip if the gate was just approved (approve already ran them)
206
266
  if (phaseCheckNames.length > 0) {
207
267
  const gate = getPhaseGate(protocol, state.phase);
@@ -213,12 +273,12 @@ export async function done(workspaceRoot, projectId) {
213
273
  console.log(chalk.dim('Checks skipped (gate approved <60s ago).'));
214
274
  }
215
275
  else {
216
- 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) };
217
277
  console.log('');
218
278
  console.log(chalk.bold('RUNNING CHECKS...'));
219
279
  logCheckOverrides(phaseCheckNames, checks, overrides);
220
280
  if (Object.keys(checks).length > 0) {
221
- const results = await runPhaseChecks(checks, workspaceRoot, checkEnv);
281
+ const results = await runPhaseChecks(checks, artifactRoot, checkEnv, undefined, scopedResolver);
222
282
  console.log(formatCheckResults(results));
223
283
  if (!allChecksPassed(results)) {
224
284
  console.log('');
@@ -235,7 +295,7 @@ export async function done(workspaceRoot, projectId) {
235
295
  // For build_verify phases: mark build as complete for verification
236
296
  if (isBuildVerify(protocol, state.phase) && !state.build_complete) {
237
297
  state.build_complete = true;
238
- writeState(statusPath, state);
298
+ await writeStateAndCommit(statusPath, state, `chore(porch): ${state.id} ${state.phase} build-complete`);
239
299
  console.log('');
240
300
  console.log(chalk.green('BUILD COMPLETE. Ready for verification.'));
241
301
  console.log(`\n Run: porch next ${state.id} (to get verification tasks)`);
@@ -298,9 +358,17 @@ export async function done(workspaceRoot, projectId) {
298
358
  }
299
359
  }
300
360
  }
301
- // Check for gate
361
+ // Check for gate — auto-request if not yet requested
302
362
  const gate = getPhaseGate(protocol, state.phase);
303
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
+ }
304
372
  console.log('');
305
373
  console.log(chalk.yellow(`GATE REQUIRED: ${gate}`));
306
374
  console.log(`\n Run: porch gate ${state.id}`);
@@ -320,13 +388,13 @@ export async function done(workspaceRoot, projectId) {
320
388
  }
321
389
  }
322
390
  // Advance to next protocol phase
323
- advanceProtocolPhase(workspaceRoot, state, protocol, statusPath);
391
+ await advanceProtocolPhase(workspaceRoot, state, protocol, statusPath, scopedResolver);
324
392
  }
325
- function advanceProtocolPhase(workspaceRoot, state, protocol, statusPath) {
393
+ async function advanceProtocolPhase(workspaceRoot, state, protocol, statusPath, resolver) {
326
394
  const nextPhase = getNextPhase(protocol, state.phase);
327
395
  if (!nextPhase) {
328
- state.phase = 'complete';
329
- writeState(statusPath, state);
396
+ state.phase = 'verified';
397
+ await writeStateAndCommit(statusPath, state, `chore(porch): ${state.id} protocol complete`);
330
398
  console.log('');
331
399
  console.log(chalk.green.bold('🎉 PROTOCOL COMPLETE'));
332
400
  console.log(`\n Project ${state.id} has completed the ${state.protocol} protocol.`);
@@ -337,16 +405,17 @@ function advanceProtocolPhase(workspaceRoot, state, protocol, statusPath) {
337
405
  state.iteration = 1;
338
406
  // If entering a phased phase (implement), extract plan phases
339
407
  if (isPhased(protocol, nextPhase.id)) {
340
- const planPath = findPlanFile(workspaceRoot, state.id, state.title);
341
- if (planPath) {
342
- state.plan_phases = extractPhasesFromFile(planPath);
343
- // 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
344
413
  if (state.plan_phases.length > 0) {
345
414
  state.current_plan_phase = state.plan_phases[0].id;
346
415
  }
347
416
  }
348
417
  }
349
- writeState(statusPath, state);
418
+ await writeStateAndCommit(statusPath, state, `chore(porch): ${state.id} ${nextPhase.id} phase-transition`);
350
419
  console.log('');
351
420
  console.log(chalk.green(`ADVANCING TO: ${nextPhase.id} - ${nextPhase.name}`));
352
421
  // If we just entered implement phase, show phase 1 info and the critical warning
@@ -355,11 +424,11 @@ function advanceProtocolPhase(workspaceRoot, state, protocol, statusPath) {
355
424
  const nextPlanPhase = state.plan_phases[1];
356
425
  console.log('');
357
426
  console.log(chalk.bold(`YOUR TASK: ${firstPhase.id} - "${firstPhase.title}"`));
358
- // Show phase content from plan
359
- const planPath = findPlanFile(workspaceRoot, state.id, state.title);
360
- if (planPath) {
361
- const content = fs.readFileSync(planPath, 'utf-8');
362
- 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);
363
432
  if (phaseContent) {
364
433
  console.log(section('FROM THE PLAN', phaseContent.slice(0, 800)));
365
434
  }
@@ -383,7 +452,7 @@ function advanceProtocolPhase(workspaceRoot, state, protocol, statusPath) {
383
452
  * porch gate <id>
384
453
  * Requests human approval for current gate.
385
454
  */
386
- export async function gate(workspaceRoot, projectId) {
455
+ export async function gate(workspaceRoot, projectId, resolver) {
387
456
  const statusPath = findStatusPath(workspaceRoot, projectId);
388
457
  if (!statusPath) {
389
458
  throw new Error(`Project ${projectId} not found.`);
@@ -402,22 +471,22 @@ export async function gate(workspaceRoot, projectId) {
402
471
  }
403
472
  if (!state.gates[gateName].requested_at) {
404
473
  state.gates[gateName].requested_at = new Date().toISOString();
405
- writeState(statusPath, state);
474
+ await writeStateAndCommit(statusPath, state, `chore(porch): ${state.id} ${gateName} gate-requested`);
406
475
  }
407
476
  console.log('');
408
477
  console.log(chalk.bold(`GATE: ${gateName}`));
409
478
  console.log('');
410
479
  // Show relevant artifact and open it for review
411
- const artifact = getArtifactForPhase(workspaceRoot, state);
480
+ const artifact = getArtifactForPhase(workspaceRoot, state, resolver);
412
481
  if (artifact) {
413
482
  const fullPath = path.join(workspaceRoot, artifact);
414
483
  if (fs.existsSync(fullPath)) {
415
484
  console.log(` Artifact: ${artifact}`);
416
485
  console.log('');
417
486
  console.log(chalk.cyan(' Opening artifact for human review...'));
418
- // Use af open to display in annotation viewer
487
+ // Use afx open to display in annotation viewer
419
488
  const { spawn } = await import('node:child_process');
420
- spawn('af', ['open', fullPath], {
489
+ spawn('afx', ['open', fullPath], {
421
490
  stdio: 'inherit',
422
491
  detached: true
423
492
  }).unref();
@@ -436,15 +505,34 @@ export async function gate(workspaceRoot, projectId) {
436
505
  * porch approve <id> <gate> --a-human-explicitly-approved-this
437
506
  * Human approves a gate. Requires explicit flag to prevent automated approvals.
438
507
  */
439
- export async function approve(workspaceRoot, projectId, gateName, hasHumanFlag) {
508
+ export async function approve(workspaceRoot, projectId, gateName, hasHumanFlag, resolver) {
440
509
  const statusPath = findStatusPath(workspaceRoot, projectId);
441
510
  if (!statusPath) {
442
511
  throw new Error(`Project ${projectId} not found.`);
443
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);
444
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)
445
524
  if (!state.gates[gateName]) {
446
- const knownGates = Object.keys(state.gates).join(', ');
447
- 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
+ }
448
536
  }
449
537
  if (state.gates[gateName].status === 'approved') {
450
538
  console.log(chalk.yellow(`Gate ${gateName} is already approved.`));
@@ -468,12 +556,12 @@ export async function approve(workspaceRoot, projectId, gateName, hasHumanFlag)
468
556
  const phaseCheckNames = phaseConfig?.checks ?? [];
469
557
  const checks = getPhaseChecks(protocol, state.phase, overrides ?? undefined);
470
558
  if (phaseCheckNames.length > 0) {
471
- 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) };
472
560
  console.log('');
473
561
  console.log(chalk.bold('RUNNING CHECKS...'));
474
562
  logCheckOverrides(phaseCheckNames, checks, overrides);
475
563
  if (Object.keys(checks).length > 0) {
476
- const results = await runPhaseChecks(checks, workspaceRoot, checkEnv);
564
+ const results = await runPhaseChecks(checks, artifactRoot, checkEnv, undefined, scopedResolver);
477
565
  console.log(formatCheckResults(results));
478
566
  if (!allChecksPassed(results)) {
479
567
  console.log('');
@@ -488,17 +576,27 @@ export async function approve(workspaceRoot, projectId, gateName, hasHumanFlag)
488
576
  }
489
577
  state.gates[gateName].status = 'approved';
490
578
  state.gates[gateName].approved_at = new Date().toISOString();
491
- writeState(statusPath, state);
579
+ await writeStateAndCommit(statusPath, state, `chore(porch): ${state.id} ${gateName} gate-approved`);
492
580
  console.log('');
493
581
  console.log(chalk.green(`Gate ${gateName} approved.`));
494
- 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
+ }
495
593
  console.log('');
496
594
  }
497
595
  /**
498
596
  * porch rollback <id> <phase>
499
597
  * Rewinds project to an earlier phase, clearing downstream gates and resetting build state.
500
598
  */
501
- export async function rollback(workspaceRoot, projectId, targetPhase) {
599
+ export async function rollback(workspaceRoot, projectId, targetPhase, resolver) {
502
600
  const statusPath = findStatusPath(workspaceRoot, projectId);
503
601
  if (!statusPath) {
504
602
  throw new Error(`Project ${projectId} not found.`);
@@ -515,7 +613,7 @@ export async function rollback(workspaceRoot, projectId, targetPhase) {
515
613
  const currentIndex = protocol.phases.findIndex(p => p.id === state.phase);
516
614
  const targetIndex = protocol.phases.findIndex(p => p.id === targetPhase);
517
615
  // Handle completed projects (phase not in protocol phases array)
518
- if (state.phase === 'complete') {
616
+ if (state.phase === 'verified' || state.phase === 'complete') {
519
617
  // Allow rollback from complete state to any valid phase
520
618
  }
521
619
  else if (currentIndex === -1) {
@@ -538,11 +636,12 @@ export async function rollback(workspaceRoot, projectId, targetPhase) {
538
636
  state.iteration = 1;
539
637
  state.build_complete = false;
540
638
  state.history = [];
541
- // 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
542
640
  if (isPhased(protocol, targetPhase)) {
543
- const planPath = findPlanFile(workspaceRoot, state.id, state.title);
544
- if (planPath) {
545
- 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);
546
645
  if (state.plan_phases.length > 0) {
547
646
  state.current_plan_phase = state.plan_phases[0].id;
548
647
  }
@@ -559,7 +658,7 @@ export async function rollback(workspaceRoot, projectId, targetPhase) {
559
658
  state.plan_phases = [];
560
659
  state.current_plan_phase = null;
561
660
  }
562
- writeState(statusPath, state);
661
+ await writeStateAndCommit(statusPath, state, `chore(porch): ${state.id} rollback ${previousPhase} → ${targetPhase}`);
563
662
  console.log('');
564
663
  console.log(chalk.green(`ROLLED BACK: ${previousPhase} → ${targetPhase}`));
565
664
  console.log(` Project: ${state.id}`);
@@ -572,7 +671,7 @@ export async function rollback(workspaceRoot, projectId, targetPhase) {
572
671
  * Initialize a new project.
573
672
  *
574
673
  * Idempotent: if status.yaml already exists, preserves it and reports
575
- * current state. This supports `af spawn --resume` where the builder
674
+ * current state. This supports `afx spawn --resume` where the builder
576
675
  * may re-run `porch init` after a session restart.
577
676
  */
578
677
  export async function init(workspaceRoot, protocolName, projectId, projectName) {
@@ -608,7 +707,7 @@ export async function init(workspaceRoot, protocolName, projectId, projectName)
608
707
  return;
609
708
  }
610
709
  const state = createInitialState(protocol, projectId, projectName, workspaceRoot);
611
- writeState(statusPath, state);
710
+ await writeStateAndCommit(statusPath, state, `chore(porch): ${state.id} init ${protocolName}`);
612
711
  console.log('');
613
712
  console.log(chalk.green(`Project initialized: ${projectId}-${projectName}`));
614
713
  console.log(` Protocol: ${protocolName}`);
@@ -647,8 +746,8 @@ function getNextAction(state, protocol) {
647
746
  }
648
747
  return `Complete the phase work, then run: porch done ${state.id}`;
649
748
  }
650
- function getArtifactForPhase(workspaceRoot, state) {
651
- const baseName = resolveArtifactBaseName(workspaceRoot, state.id, state.title);
749
+ function getArtifactForPhase(workspaceRoot, state, resolver) {
750
+ const baseName = resolveArtifactBaseName(workspaceRoot, state.id, state.title, resolver);
652
751
  switch (state.phase) {
653
752
  case 'specify':
654
753
  return `codev/specs/${baseName}.md`;
@@ -666,6 +765,7 @@ function getArtifactForPhase(workspaceRoot, state) {
666
765
  export async function cli(args) {
667
766
  const [command, ...rest] = args;
668
767
  const workspaceRoot = process.cwd();
768
+ const resolver = getResolver(workspaceRoot);
669
769
  // Auto-detect project ID for commands that need it
670
770
  function getProjectId(provided) {
671
771
  const { id, source } = resolveProjectId(provided, process.cwd(), workspaceRoot);
@@ -691,28 +791,80 @@ export async function cli(args) {
691
791
  process.exit(1);
692
792
  break;
693
793
  case 'status':
694
- await status(workspaceRoot, getProjectId(rest[0]));
794
+ await status(workspaceRoot, getProjectId(rest[0]), resolver);
695
795
  break;
696
796
  case 'check':
697
- await check(workspaceRoot, getProjectId(rest[0]));
797
+ await check(workspaceRoot, getProjectId(rest[0]), resolver);
698
798
  break;
699
- case 'done':
700
- 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);
701
828
  break;
829
+ }
702
830
  case 'gate':
703
- await gate(workspaceRoot, getProjectId(rest[0]));
831
+ await gate(workspaceRoot, getProjectId(rest[0]), resolver);
704
832
  break;
705
833
  case 'approve':
706
834
  if (!rest[0] || !rest[1])
707
835
  throw new Error('Usage: porch approve <id> <gate> --a-human-explicitly-approved-this');
708
836
  const hasHumanFlag = rest.includes('--a-human-explicitly-approved-this');
709
- await approve(workspaceRoot, rest[0], rest[1], hasHumanFlag);
837
+ await approve(workspaceRoot, rest[0], rest[1], hasHumanFlag, resolver);
710
838
  break;
711
839
  case 'rollback':
712
840
  if (!rest[0] || !rest[1])
713
841
  throw new Error('Usage: porch rollback <id> <phase>');
714
- await rollback(workspaceRoot, rest[0], rest[1]);
842
+ await rollback(workspaceRoot, rest[0], rest[1], resolver);
715
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
+ }
716
868
  case 'init':
717
869
  if (!rest[0] || !rest[1] || !rest[2]) {
718
870
  throw new Error('Usage: porch init <protocol> <id> <name>');
@@ -727,8 +879,11 @@ export async function cli(args) {
727
879
  console.log(' status [id] Show current state and instructions');
728
880
  console.log(' check [id] Run checks for current phase');
729
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)');
730
884
  console.log(' gate [id] Request human approval');
731
885
  console.log(' approve <id> <gate> --a-human-explicitly-approved-this');
886
+ console.log(' verify <id> --skip "reason" Skip verification and mark as verified');
732
887
  console.log(' rollback <id> <phase> Rewind project to an earlier phase');
733
888
  console.log(' init <protocol> <id> <name> Initialize a new project');
734
889
  console.log('');