@cluesmith/codev 3.3.2 → 3.3.4

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 (257) hide show
  1. package/dashboard-dist/assets/{index-D3XaKpZW.js → index-DZf6pNPE.js} +41 -41
  2. package/dashboard-dist/assets/index-DZf6pNPE.js.map +1 -0
  3. package/dashboard-dist/assets/index-DnYLy5C_.css +32 -0
  4. package/dashboard-dist/index.html +2 -2
  5. package/dist/agent-farm/cli.d.ts.map +1 -1
  6. package/dist/agent-farm/cli.js +48 -0
  7. package/dist/agent-farm/cli.js.map +1 -1
  8. package/dist/agent-farm/commands/ide.d.ts +20 -0
  9. package/dist/agent-farm/commands/ide.d.ts.map +1 -0
  10. package/dist/agent-farm/commands/ide.js +138 -0
  11. package/dist/agent-farm/commands/ide.js.map +1 -0
  12. package/dist/agent-farm/commands/inbox.d.ts.map +1 -1
  13. package/dist/agent-farm/commands/inbox.js +79 -10
  14. package/dist/agent-farm/commands/inbox.js.map +1 -1
  15. package/dist/agent-farm/commands/interrupt.d.ts +4 -1
  16. package/dist/agent-farm/commands/interrupt.d.ts.map +1 -1
  17. package/dist/agent-farm/commands/interrupt.js +6 -3
  18. package/dist/agent-farm/commands/interrupt.js.map +1 -1
  19. package/dist/agent-farm/commands/reset.d.ts +4 -1
  20. package/dist/agent-farm/commands/reset.d.ts.map +1 -1
  21. package/dist/agent-farm/commands/reset.js +6 -3
  22. package/dist/agent-farm/commands/reset.js.map +1 -1
  23. package/dist/agent-farm/commands/send.d.ts +28 -0
  24. package/dist/agent-farm/commands/send.d.ts.map +1 -1
  25. package/dist/agent-farm/commands/send.js +79 -6
  26. package/dist/agent-farm/commands/send.js.map +1 -1
  27. package/dist/agent-farm/commands/spawn-worktree.d.ts.map +1 -1
  28. package/dist/agent-farm/commands/spawn-worktree.js +16 -7
  29. package/dist/agent-farm/commands/spawn-worktree.js.map +1 -1
  30. package/dist/agent-farm/commands/tower.d.ts +21 -9
  31. package/dist/agent-farm/commands/tower.d.ts.map +1 -1
  32. package/dist/agent-farm/commands/tower.js +90 -30
  33. package/dist/agent-farm/commands/tower.js.map +1 -1
  34. package/dist/agent-farm/db/index.d.ts +3 -1
  35. package/dist/agent-farm/db/index.d.ts.map +1 -1
  36. package/dist/agent-farm/db/index.js +8 -443
  37. package/dist/agent-farm/db/index.js.map +1 -1
  38. package/dist/agent-farm/db/mailbox.d.ts +47 -7
  39. package/dist/agent-farm/db/mailbox.d.ts.map +1 -1
  40. package/dist/agent-farm/db/mailbox.js +56 -12
  41. package/dist/agent-farm/db/mailbox.js.map +1 -1
  42. package/dist/agent-farm/db/migrations.d.ts +57 -0
  43. package/dist/agent-farm/db/migrations.d.ts.map +1 -0
  44. package/dist/agent-farm/db/migrations.js +530 -0
  45. package/dist/agent-farm/db/migrations.js.map +1 -0
  46. package/dist/agent-farm/db/schema.d.ts +1 -1
  47. package/dist/agent-farm/db/schema.d.ts.map +1 -1
  48. package/dist/agent-farm/db/schema.js +1 -0
  49. package/dist/agent-farm/db/schema.js.map +1 -1
  50. package/dist/agent-farm/db/tower-owner.d.ts +168 -0
  51. package/dist/agent-farm/db/tower-owner.d.ts.map +1 -0
  52. package/dist/agent-farm/db/tower-owner.js +369 -0
  53. package/dist/agent-farm/db/tower-owner.js.map +1 -0
  54. package/dist/agent-farm/db/types.d.ts +47 -0
  55. package/dist/agent-farm/db/types.d.ts.map +1 -1
  56. package/dist/agent-farm/db/types.js.map +1 -1
  57. package/dist/agent-farm/lib/ide-record.d.ts +65 -0
  58. package/dist/agent-farm/lib/ide-record.d.ts.map +1 -0
  59. package/dist/agent-farm/lib/ide-record.js +118 -0
  60. package/dist/agent-farm/lib/ide-record.js.map +1 -0
  61. package/dist/agent-farm/lib/tunnel-client.d.ts +22 -0
  62. package/dist/agent-farm/lib/tunnel-client.d.ts.map +1 -1
  63. package/dist/agent-farm/lib/tunnel-client.js +96 -19
  64. package/dist/agent-farm/lib/tunnel-client.js.map +1 -1
  65. package/dist/agent-farm/servers/cron-delivery.d.ts +7 -1
  66. package/dist/agent-farm/servers/cron-delivery.d.ts.map +1 -1
  67. package/dist/agent-farm/servers/cron-delivery.js +2 -1
  68. package/dist/agent-farm/servers/cron-delivery.js.map +1 -1
  69. package/dist/agent-farm/servers/gate-profiles.d.ts +44 -2
  70. package/dist/agent-farm/servers/gate-profiles.d.ts.map +1 -1
  71. package/dist/agent-farm/servers/gate-profiles.js +79 -6
  72. package/dist/agent-farm/servers/gate-profiles.js.map +1 -1
  73. package/dist/agent-farm/servers/ide-forward.d.ts +62 -0
  74. package/dist/agent-farm/servers/ide-forward.d.ts.map +1 -0
  75. package/dist/agent-farm/servers/ide-forward.js +274 -0
  76. package/dist/agent-farm/servers/ide-forward.js.map +1 -0
  77. package/dist/agent-farm/servers/ide-server.d.ts +68 -0
  78. package/dist/agent-farm/servers/ide-server.d.ts.map +1 -0
  79. package/dist/agent-farm/servers/ide-server.js +281 -0
  80. package/dist/agent-farm/servers/ide-server.js.map +1 -0
  81. package/dist/agent-farm/servers/mailbox-delivery.d.ts +296 -12
  82. package/dist/agent-farm/servers/mailbox-delivery.d.ts.map +1 -1
  83. package/dist/agent-farm/servers/mailbox-delivery.js +566 -69
  84. package/dist/agent-farm/servers/mailbox-delivery.js.map +1 -1
  85. package/dist/agent-farm/servers/mailbox-wiring.d.ts +66 -19
  86. package/dist/agent-farm/servers/mailbox-wiring.d.ts.map +1 -1
  87. package/dist/agent-farm/servers/mailbox-wiring.js +204 -37
  88. package/dist/agent-farm/servers/mailbox-wiring.js.map +1 -1
  89. package/dist/agent-farm/servers/message-write.d.ts +138 -11
  90. package/dist/agent-farm/servers/message-write.d.ts.map +1 -1
  91. package/dist/agent-farm/servers/message-write.js +144 -35
  92. package/dist/agent-farm/servers/message-write.js.map +1 -1
  93. package/dist/agent-farm/servers/overview.d.ts +28 -12
  94. package/dist/agent-farm/servers/overview.d.ts.map +1 -1
  95. package/dist/agent-farm/servers/overview.js +130 -79
  96. package/dist/agent-farm/servers/overview.js.map +1 -1
  97. package/dist/agent-farm/servers/render-gate.d.ts +77 -1
  98. package/dist/agent-farm/servers/render-gate.d.ts.map +1 -1
  99. package/dist/agent-farm/servers/render-gate.js +174 -25
  100. package/dist/agent-farm/servers/render-gate.js.map +1 -1
  101. package/dist/agent-farm/servers/session-submit.d.ts +7 -4
  102. package/dist/agent-farm/servers/session-submit.d.ts.map +1 -1
  103. package/dist/agent-farm/servers/session-submit.js +7 -4
  104. package/dist/agent-farm/servers/session-submit.js.map +1 -1
  105. package/dist/agent-farm/servers/tower-cron.d.ts.map +1 -1
  106. package/dist/agent-farm/servers/tower-cron.js +7 -3
  107. package/dist/agent-farm/servers/tower-cron.js.map +1 -1
  108. package/dist/agent-farm/servers/tower-instances.d.ts.map +1 -1
  109. package/dist/agent-farm/servers/tower-instances.js +17 -10
  110. package/dist/agent-farm/servers/tower-instances.js.map +1 -1
  111. package/dist/agent-farm/servers/tower-routes.d.ts +32 -0
  112. package/dist/agent-farm/servers/tower-routes.d.ts.map +1 -1
  113. package/dist/agent-farm/servers/tower-routes.js +186 -26
  114. package/dist/agent-farm/servers/tower-routes.js.map +1 -1
  115. package/dist/agent-farm/servers/tower-server.js +79 -9
  116. package/dist/agent-farm/servers/tower-server.js.map +1 -1
  117. package/dist/agent-farm/servers/tower-terminals.d.ts +21 -0
  118. package/dist/agent-farm/servers/tower-terminals.d.ts.map +1 -1
  119. package/dist/agent-farm/servers/tower-terminals.js +106 -13
  120. package/dist/agent-farm/servers/tower-terminals.js.map +1 -1
  121. package/dist/agent-farm/servers/tower-utils.d.ts +35 -0
  122. package/dist/agent-farm/servers/tower-utils.d.ts.map +1 -1
  123. package/dist/agent-farm/servers/tower-utils.js +39 -0
  124. package/dist/agent-farm/servers/tower-utils.js.map +1 -1
  125. package/dist/agent-farm/servers/tower-websocket.d.ts.map +1 -1
  126. package/dist/agent-farm/servers/tower-websocket.js +26 -2
  127. package/dist/agent-farm/servers/tower-websocket.js.map +1 -1
  128. package/dist/agent-farm/utils/message-format.d.ts +78 -2
  129. package/dist/agent-farm/utils/message-format.d.ts.map +1 -1
  130. package/dist/agent-farm/utils/message-format.js +107 -11
  131. package/dist/agent-farm/utils/message-format.js.map +1 -1
  132. package/dist/agent-farm/utils/port.d.ts +22 -0
  133. package/dist/agent-farm/utils/port.d.ts.map +1 -0
  134. package/dist/agent-farm/utils/port.js +35 -0
  135. package/dist/agent-farm/utils/port.js.map +1 -0
  136. package/dist/agent-farm/utils/server-utils.d.ts +26 -0
  137. package/dist/agent-farm/utils/server-utils.d.ts.map +1 -1
  138. package/dist/agent-farm/utils/server-utils.js +36 -0
  139. package/dist/agent-farm/utils/server-utils.js.map +1 -1
  140. package/dist/cli.d.ts.map +1 -1
  141. package/dist/cli.js +3 -1
  142. package/dist/cli.js.map +1 -1
  143. package/dist/commands/consult/index.d.ts.map +1 -1
  144. package/dist/commands/consult/index.js +61 -1
  145. package/dist/commands/consult/index.js.map +1 -1
  146. package/dist/commands/consult/tree-tripwire.d.ts +177 -0
  147. package/dist/commands/consult/tree-tripwire.d.ts.map +1 -0
  148. package/dist/commands/consult/tree-tripwire.js +356 -0
  149. package/dist/commands/consult/tree-tripwire.js.map +1 -0
  150. package/dist/commands/doctor.d.ts +69 -0
  151. package/dist/commands/doctor.d.ts.map +1 -1
  152. package/dist/commands/doctor.js +165 -2
  153. package/dist/commands/doctor.js.map +1 -1
  154. package/dist/commands/generate-image.d.ts +25 -1
  155. package/dist/commands/generate-image.d.ts.map +1 -1
  156. package/dist/commands/generate-image.js +410 -2
  157. package/dist/commands/generate-image.js.map +1 -1
  158. package/dist/commands/porch/prompts.d.ts.map +1 -1
  159. package/dist/commands/porch/prompts.js +18 -2
  160. package/dist/commands/porch/prompts.js.map +1 -1
  161. package/dist/lib/agent-env.d.ts +86 -0
  162. package/dist/lib/agent-env.d.ts.map +1 -0
  163. package/dist/lib/agent-env.js +126 -0
  164. package/dist/lib/agent-env.js.map +1 -0
  165. package/dist/lib/forge-contracts.d.ts +76 -5
  166. package/dist/lib/forge-contracts.d.ts.map +1 -1
  167. package/dist/lib/forge-contracts.js.map +1 -1
  168. package/dist/lib/forge-rate-limit.d.ts +45 -0
  169. package/dist/lib/forge-rate-limit.d.ts.map +1 -0
  170. package/dist/lib/forge-rate-limit.js +89 -0
  171. package/dist/lib/forge-rate-limit.js.map +1 -0
  172. package/dist/lib/forge.d.ts +29 -1
  173. package/dist/lib/forge.d.ts.map +1 -1
  174. package/dist/lib/forge.js +96 -21
  175. package/dist/lib/forge.js.map +1 -1
  176. package/dist/lib/github.d.ts +13 -1
  177. package/dist/lib/github.d.ts.map +1 -1
  178. package/dist/lib/github.js +58 -15
  179. package/dist/lib/github.js.map +1 -1
  180. package/dist/lib/test-env.d.ts +17 -0
  181. package/dist/lib/test-env.d.ts.map +1 -1
  182. package/dist/lib/test-env.js +20 -0
  183. package/dist/lib/test-env.js.map +1 -1
  184. package/dist/terminal/pty-manager.d.ts +10 -1
  185. package/dist/terminal/pty-manager.d.ts.map +1 -1
  186. package/dist/terminal/pty-manager.js +38 -9
  187. package/dist/terminal/pty-manager.js.map +1 -1
  188. package/dist/terminal/pty-session.d.ts +174 -8
  189. package/dist/terminal/pty-session.d.ts.map +1 -1
  190. package/dist/terminal/pty-session.js +299 -25
  191. package/dist/terminal/pty-session.js.map +1 -1
  192. package/dist/terminal/session-manager.d.ts +23 -2
  193. package/dist/terminal/session-manager.d.ts.map +1 -1
  194. package/dist/terminal/session-manager.js +115 -36
  195. package/dist/terminal/session-manager.js.map +1 -1
  196. package/dist/terminal/session-screen.d.ts +34 -5
  197. package/dist/terminal/session-screen.d.ts.map +1 -1
  198. package/dist/terminal/session-screen.js +52 -6
  199. package/dist/terminal/session-screen.js.map +1 -1
  200. package/dist/terminal/shellper-client.d.ts +73 -0
  201. package/dist/terminal/shellper-client.d.ts.map +1 -1
  202. package/dist/terminal/shellper-client.js +151 -0
  203. package/dist/terminal/shellper-client.js.map +1 -1
  204. package/dist/terminal/shellper-main.js +20 -3
  205. package/dist/terminal/shellper-main.js.map +1 -1
  206. package/dist/terminal/shellper-process.d.ts +2 -0
  207. package/dist/terminal/shellper-process.d.ts.map +1 -1
  208. package/dist/terminal/shellper-process.js +17 -1
  209. package/dist/terminal/shellper-process.js.map +1 -1
  210. package/dist/terminal/shellper-protocol.d.ts +28 -0
  211. package/dist/terminal/shellper-protocol.d.ts.map +1 -1
  212. package/dist/terminal/shellper-protocol.js.map +1 -1
  213. package/dist/terminal/terminal-replies.d.ts +102 -0
  214. package/dist/terminal/terminal-replies.d.ts.map +1 -0
  215. package/dist/terminal/terminal-replies.js +181 -0
  216. package/dist/terminal/terminal-replies.js.map +1 -0
  217. package/package.json +4 -4
  218. package/scripts/forge/gitea/_lib.sh +214 -0
  219. package/scripts/forge/gitea/issue-comment.sh +8 -1
  220. package/scripts/forge/gitea/issue-view.sh +95 -4
  221. package/scripts/forge/gitea/pr-create.sh +155 -0
  222. package/scripts/forge/gitea/pr-exists.sh +32 -4
  223. package/scripts/forge/gitea/pr-list.sh +40 -31
  224. package/scripts/forge/gitea/pr-view.sh +65 -4
  225. package/scripts/forge/gitea/recently-merged.sh +91 -21
  226. package/scripts/forge/gitea/user-identity.sh +30 -1
  227. package/scripts/forge/github/issue-view.sh +5 -2
  228. package/scripts/forge/github/pr-create.sh +47 -0
  229. package/scripts/forge/github/pr-list.sh +6 -2
  230. package/scripts/forge/github/pr-search.sh +6 -2
  231. package/scripts/forge/gitlab/issue-search.sh +1 -0
  232. package/scripts/forge/gitlab/issue-view.sh +24 -4
  233. package/scripts/forge/gitlab/pr-create.sh +53 -0
  234. package/scripts/forge/gitlab/pr-search.sh +37 -2
  235. package/scripts/forge/linear/auth-status.sh +1 -0
  236. package/scripts/forge/linear/issue-comment.sh +1 -0
  237. package/scripts/forge/linear/issue-list.sh +1 -0
  238. package/scripts/forge/linear/issue-search.sh +1 -0
  239. package/scripts/forge/linear/issue-view.sh +14 -2
  240. package/scripts/forge/linear/recently-closed.sh +1 -0
  241. package/scripts/forge/linear/user-identity.sh +1 -0
  242. package/skeleton/.claude/skills/generate-image/SKILL.md +39 -1
  243. package/skeleton/.codex/skills/generate-image/SKILL.md +39 -1
  244. package/skeleton/protocols/air/prompts/pr.md +6 -1
  245. package/skeleton/protocols/aspir/prompts/review.md +6 -1
  246. package/skeleton/protocols/bugfix/prompts/pr.md +6 -1
  247. package/skeleton/protocols/bugfix/protocol.md +2 -1
  248. package/skeleton/protocols/maintain/prompts/review.md +6 -1
  249. package/skeleton/protocols/pir/prompts/review.md +10 -7
  250. package/skeleton/protocols/spir/prompts/review.md +6 -1
  251. package/skeleton/resources/commands/agent-farm.md +28 -3
  252. package/skeleton/roles/consultant.md +32 -0
  253. package/skeleton/templates/AGENTS.md +1 -1
  254. package/skeleton/templates/CLAUDE.md +1 -1
  255. package/templates/tower.html +16 -1
  256. package/dashboard-dist/assets/index-BZRTEMtH.css +0 -32
  257. package/dashboard-dist/assets/index-D3XaKpZW.js.map +0 -1
@@ -0,0 +1,47 @@
1
+ #!/bin/sh
2
+ # Forge concept: pr-create (GitHub via gh CLI)
3
+ # forge-executable: gh
4
+ #
5
+ # Input: CODEV_PR_TITLE (required)
6
+ # CODEV_PR_BODY (required, may be empty)
7
+ # CODEV_PR_BASE, CODEV_PR_HEAD, CODEV_PR_REPO, CODEV_PR_DRAFT (optional)
8
+ # Output: {"number": <int>, "url": "<web url>"}
9
+ #
10
+ # `gh pr create` prints the new PR's URL on stdout; the number is its last path
11
+ # segment. Anything gh writes that isn't a URL (hints, prompts) is ignored.
12
+ set -e
13
+
14
+ if [ -z "$CODEV_PR_TITLE" ]; then
15
+ echo "pr-create: CODEV_PR_TITLE is required" >&2
16
+ exit 2
17
+ fi
18
+
19
+ # An empty body is allowed; an *absent* one is not. Without this, forgetting the
20
+ # variable opens a PR with no body at exit 0 — the silent failure #1455 is about.
21
+ if [ -z "${CODEV_PR_BODY+x}" ]; then
22
+ echo "pr-create: CODEV_PR_BODY is required (set it to \"\" for an empty body)" >&2
23
+ exit 2
24
+ fi
25
+
26
+ set -- --title "$CODEV_PR_TITLE" --body "$CODEV_PR_BODY"
27
+ if [ -n "$CODEV_PR_BASE" ]; then set -- "$@" --base "$CODEV_PR_BASE"; fi
28
+ if [ -n "$CODEV_PR_HEAD" ]; then set -- "$@" --head "$CODEV_PR_HEAD"; fi
29
+ if [ -n "$CODEV_PR_REPO" ]; then set -- "$@" --repo "$CODEV_PR_REPO"; fi
30
+ if [ "$CODEV_PR_DRAFT" = "1" ]; then set -- "$@" --draft; fi
31
+
32
+ url=$(gh pr create "$@" | grep -E '^https?://' | tail -n 1)
33
+
34
+ if [ -z "$url" ]; then
35
+ echo "pr-create: gh pr create returned no PR URL" >&2
36
+ exit 1
37
+ fi
38
+
39
+ number=${url##*/}
40
+ case "$number" in
41
+ '' | *[!0-9]*)
42
+ echo "pr-create: could not parse a PR number from '$url'" >&2
43
+ exit 1
44
+ ;;
45
+ esac
46
+
47
+ printf '{"number":%s,"url":"%s"}\n' "$number" "$url"
@@ -7,5 +7,9 @@
7
7
  # as objects (users carry `login`; teams carry `slug`/`name` and no `login`), so
8
8
  # `.login // empty` keeps user reviewers and drops team reviewers — matching the
9
9
  # `reviewRequests: string[]` contract in PrListItem (forge-contracts.ts).
10
- exec gh pr list --json number,title,url,reviewDecision,body,createdAt,author,reviewRequests,isDraft \
11
- | jq '[.[] | .reviewRequests = [.reviewRequests[].login // empty]]'
10
+ #
11
+ # Not `gh … | jq`: POSIX sh has no pipefail, so the caller would see jq's exit
12
+ # status and a rate-limited gh would look like a successful empty list (#1645).
13
+ # forge-executable: gh
14
+ out="$(gh pr list --json number,title,url,reviewDecision,body,createdAt,author,reviewRequests,isDraft)" || exit 1
15
+ printf '%s' "$out" | jq '[.[] | .reviewRequests = [.reviewRequests[].login // empty]]'
@@ -1,5 +1,9 @@
1
1
  #!/bin/sh
2
2
  # Forge concept: pr-search (GitHub via gh CLI)
3
3
  # Input: CODEV_SEARCH_QUERY
4
- # Output: JSON [{number, headRefName, baseRefName}]
5
- exec gh pr list --search "$CODEV_SEARCH_QUERY" --json number,headRefName,baseRefName
4
+ # Output: JSON [{number, headRefName, baseRefName, state}]
5
+ # --state all is required so the search includes merged/closed PRs; without it
6
+ # `gh pr list` defaults to --state open and post-merge lookups return nothing (#759).
7
+ # `state` (OPEN/MERGED/CLOSED) lets consumers that only want open PRs filter
8
+ # client-side — the spawn collision guard must ignore merged/closed ones (#1637).
9
+ exec gh pr list --state all --search "$CODEV_SEARCH_QUERY" --json number,headRefName,baseRefName,state
@@ -1,5 +1,6 @@
1
1
  #!/bin/sh
2
2
  # Forge concept: issue-search (GitLab via glab CLI)
3
+ # forge-executable: glab
3
4
  #
4
5
  # ⚠️ UNVERIFIED — mirrors gitlab/issue-list.sh (raw `glab ... --output json`,
5
6
  # no field normalization) plus a state flag and a `body` field surfaced from
@@ -1,6 +1,26 @@
1
1
  #!/bin/sh
2
2
  # Forge concept: issue-view (GitLab via glab CLI)
3
- # Adds a `url` field mapped from GitLab's `web_url` (the issue's browser page);
4
- # all other fields glab emits are passed through unchanged. `. + {url: …}` is
5
- # non-destructive — if `web_url` is absent, `url` is null (optional by contract).
6
- glab issue view "$CODEV_ISSUE_ID" --output json | jq '. + {url: .web_url}'
3
+ # Maps GitLab's fields into the forge-neutral IssueView shape. `. + {…}` is
4
+ # non-destructive for keys we don't touch; the mapped keys are optional by
5
+ # contract, so a shape mismatch degrades to an omitted line rather than a crash
6
+ # (jq returns null for missing keys, and the guards below tolerate null):
7
+ # web_url -> url
8
+ # author.username -> author.login
9
+ # created_at -> createdAt
10
+ # assignees[].username -> assignees[].login
11
+ # labels[] -> labels[].name (newer glab emits label objects; older emits strings)
12
+ # milestone.title -> milestone.title (null when unset)
13
+ # Best-effort preset (Spec 589): fields GitLab doesn't expose stay absent. The
14
+ # contract's optional fields are added only when present (via `+ {…}` merges) so
15
+ # an absent field is OMITTED, not emitted as null — `author?`/`createdAt?` are not
16
+ # nullable (only `milestone` is). `exec` is dropped: with a pipe it would replace
17
+ # only the glab subshell, not the whole pipeline, so it's misleading here.
18
+ glab issue view "$CODEV_ISSUE_ID" --output json | jq '
19
+ .
20
+ + {url: .web_url}
21
+ + {assignees: [(.assignees // [])[] | {login: .username}]}
22
+ + {labels: [(.labels // [])[] | if type == "string" then {name: .} else {name: .name} end]}
23
+ + (if .author.username then {author: {login: .author.username}} else {} end)
24
+ + (if .created_at then {createdAt: .created_at} else {} end)
25
+ + (if .milestone then {milestone: {title: .milestone.title}} else {} end)
26
+ '
@@ -0,0 +1,53 @@
1
+ #!/bin/sh
2
+ # Forge concept: pr-create (GitLab via glab CLI — merge requests)
3
+ # forge-executable: glab
4
+ #
5
+ # Input: CODEV_PR_TITLE (required)
6
+ # CODEV_PR_BODY (required, may be empty)
7
+ # CODEV_PR_BASE, CODEV_PR_HEAD, CODEV_PR_REPO, CODEV_PR_DRAFT (optional)
8
+ # Output: {"number": <int>, "url": "<web url>"}
9
+ #
10
+ # ⚠️ UNVERIFIED — `glab` is not available in the authoring environment (same
11
+ # constraint as gitea/issue-search.sh, #920). Written against glab's
12
+ # documented `mr create` flags; smoke-test before relying on it. Without this
13
+ # file the gitlab preset would fall through to the GitHub default and shell
14
+ # out to `gh` — the bug this concept exists to close (#1455).
15
+ #
16
+ # `glab mr create --yes` skips the interactive prompts and prints the new MR's
17
+ # URL; the number (GitLab's `iid`) is its last path segment.
18
+ set -e
19
+
20
+ if [ -z "$CODEV_PR_TITLE" ]; then
21
+ echo "pr-create: CODEV_PR_TITLE is required" >&2
22
+ exit 2
23
+ fi
24
+
25
+ # An empty body is allowed; an *absent* one is not. Without this, forgetting the
26
+ # variable opens a PR with no body at exit 0 — the silent failure #1455 is about.
27
+ if [ -z "${CODEV_PR_BODY+x}" ]; then
28
+ echo "pr-create: CODEV_PR_BODY is required (set it to \"\" for an empty body)" >&2
29
+ exit 2
30
+ fi
31
+
32
+ set -- --title "$CODEV_PR_TITLE" --description "$CODEV_PR_BODY" --yes
33
+ if [ -n "$CODEV_PR_BASE" ]; then set -- "$@" --target-branch "$CODEV_PR_BASE"; fi
34
+ if [ -n "$CODEV_PR_HEAD" ]; then set -- "$@" --source-branch "$CODEV_PR_HEAD"; fi
35
+ if [ -n "$CODEV_PR_REPO" ]; then set -- "$@" --repo "$CODEV_PR_REPO"; fi
36
+ if [ "$CODEV_PR_DRAFT" = "1" ]; then set -- "$@" --draft; fi
37
+
38
+ url=$(glab mr create "$@" | grep -E '^https?://' | tail -n 1)
39
+
40
+ if [ -z "$url" ]; then
41
+ echo "pr-create: glab mr create returned no MR URL" >&2
42
+ exit 1
43
+ fi
44
+
45
+ number=${url##*/}
46
+ case "$number" in
47
+ '' | *[!0-9]*)
48
+ echo "pr-create: could not parse an MR number from '$url'" >&2
49
+ exit 1
50
+ ;;
51
+ esac
52
+
53
+ printf '{"number":%s,"url":"%s"}\n' "$number" "$url"
@@ -1,3 +1,38 @@
1
1
  #!/bin/sh
2
- # Forge concept: pr-search (GitLab via glab CLI)
3
- exec glab mr list --search "$CODEV_SEARCH_QUERY" --output json
2
+ # Forge concept: pr-search (GitLab via glab CLI — merge requests)
3
+ # Output: JSON [{number, headRefName, baseRefName, state, ...glab fields}]
4
+ #
5
+ # ⚠️ UNVERIFIED — `glab` is not available in the authoring environment (#920);
6
+ # written against glab's documented `mr list --output json` shape (confirmed
7
+ # against the field mappings already documented in gitlab/pr-list.sh and
8
+ # gitlab/pr-exists.sh). Smoke-test before relying on it.
9
+ #
10
+ # --all is required so the search includes merged/closed MRs; without it
11
+ # `glab mr list` defaults to opened only and post-merge lookups return nothing (#759).
12
+ #
13
+ # Maps glab's GitLab-shaped fields into the forge-neutral PrSearchItem contract
14
+ # (forge-contracts.ts); `. + {…}` is non-destructive for keys we don't touch:
15
+ # iid -> number (the MR number glab mr view/merge expects)
16
+ # source_branch -> headRefName
17
+ # target_branch -> baseRefName
18
+ # Without this the spawn guard's message reads `PR #undefined` and consult's
19
+ # findPRForIssue can't resolve the MR's base branch on GitLab.
20
+ #
21
+ # glab reports state lowercase (opened/merged/closed/locked); normalize to the
22
+ # GitHub convention (OPEN/MERGED/CLOSED) so consumers filter with one comparison.
23
+ # The spawn collision guard keys off state === "OPEN" to ignore merged MRs (#1637).
24
+ # `locked` (a transient merging state) maps to OPEN too: the guard should err
25
+ # toward the recoverable --force prompt, never silently skip a live collision.
26
+ #
27
+ # Not `glab … | jq`: POSIX sh has no pipefail, so the caller would see jq's exit
28
+ # status and a failed glab would look like a successful empty list (#1645).
29
+ # The capture-then-pipe shape hides `glab` from extractExecutable's first-line
30
+ # heuristic (it lands on the `printf` builtin), so declare the backend explicitly
31
+ # — same as github/pr-list.sh (#1645).
32
+ # forge-executable: glab
33
+ out="$(glab mr list --all --search "$CODEV_SEARCH_QUERY" --output json)" || exit 1
34
+ printf '%s' "$out" | jq 'map(.
35
+ + {number: .iid}
36
+ + {headRefName: .source_branch}
37
+ + {baseRefName: .target_branch}
38
+ + {state: ((.state // "") | ascii_downcase as $s | if $s == "opened" or $s == "locked" then "OPEN" else ($s | ascii_upcase) end)})'
@@ -1,5 +1,6 @@
1
1
  #!/bin/sh
2
2
  # Forge concept: auth-status (Linear via GraphQL API)
3
+ # forge-executable: curl
3
4
  # Output: exit code (0 = authenticated)
4
5
  set -e
5
6
 
@@ -1,5 +1,6 @@
1
1
  #!/bin/sh
2
2
  # Forge concept: issue-comment (Linear via GraphQL API)
3
+ # forge-executable: curl
3
4
  # Input: CODEV_ISSUE_ID, CODEV_COMMENT_BODY
4
5
  # Output: exit code only
5
6
  set -e
@@ -1,5 +1,6 @@
1
1
  #!/bin/sh
2
2
  # Forge concept: issue-list (Linear via GraphQL API)
3
+ # forge-executable: curl
3
4
  # Input: CODEV_LINEAR_TEAM (team key, e.g. "ENG")
4
5
  # Output: JSON [{number, title, url, labels, createdAt, author, assignees}]
5
6
  set -e
@@ -1,5 +1,6 @@
1
1
  #!/bin/sh
2
2
  # Forge concept: issue-search (Linear via GraphQL API)
3
+ # forge-executable: curl
3
4
  #
4
5
  # ⚠️ UNVERIFIED — mirrors linear/issue-list.sh with `description` added to the
5
6
  # query (mapped to `body`) and the state filter parameterized. No Linear
@@ -1,7 +1,14 @@
1
1
  #!/bin/sh
2
2
  # Forge concept: issue-view (Linear via GraphQL API)
3
+ # forge-executable: curl
3
4
  # Input: CODEV_ISSUE_ID (e.g. "ENG-123")
4
- # Output: JSON {title, body, state, url, comments[]}
5
+ # Output: JSON {title, body, state, url, author, createdAt, assignees, labels, milestone, comments[]}
6
+ #
7
+ # Maps Linear's fields into the forge-neutral IssueView shape. Linear has a
8
+ # single `assignee` (emitted as a one-element `assignees` array) and `project`
9
+ # rather than milestones (mapped into `milestone.title`). These fields are
10
+ # optional by contract, so anything Linear leaves null degrades to an omitted
11
+ # line. Best-effort preset (Spec 589).
5
12
  set -e
6
13
 
7
14
  if [ -z "$LINEAR_API_KEY" ]; then
@@ -18,7 +25,7 @@ curl -sf -X POST https://api.linear.app/graphql \
18
25
  -H "Content-Type: application/json" \
19
26
  -H "Authorization: $LINEAR_API_KEY" \
20
27
  -d "$(jq -n --arg id "$CODEV_ISSUE_ID" '{
21
- query: "query($id: String!) { issueVcsByFilter: issues(filter: { identifier: { eq: $id } }) { nodes { title url description state { name } comments { nodes { body createdAt user { displayName } } } } } }",
28
+ query: "query($id: String!) { issueVcsByFilter: issues(filter: { identifier: { eq: $id } }) { nodes { title url description createdAt state { name } creator { displayName } assignee { displayName } labels { nodes { name } } project { name } comments { nodes { body createdAt user { displayName } } } } } }",
22
29
  variables: { id: $id }
23
30
  }')" \
24
31
  | jq 'if (.data.issueVcsByFilter.nodes | length) == 0 then
@@ -29,6 +36,11 @@ curl -sf -X POST https://api.linear.app/graphql \
29
36
  url: .url,
30
37
  body: (.description // ""),
31
38
  state: .state.name,
39
+ author: (if .creator then { login: .creator.displayName } else null end),
40
+ createdAt: .createdAt,
41
+ assignees: (if .assignee then [{ login: .assignee.displayName }] else [] end),
42
+ labels: [(.labels.nodes // [])[] | { name: .name }],
43
+ milestone: (if .project then { title: .project.name } else null end),
32
44
  comments: [.comments.nodes[] | {
33
45
  body: .body,
34
46
  createdAt: .createdAt,
@@ -1,5 +1,6 @@
1
1
  #!/bin/sh
2
2
  # Forge concept: recently-closed (Linear via GraphQL API)
3
+ # forge-executable: curl
3
4
  # Input: CODEV_LINEAR_TEAM, CODEV_SINCE_DATE (optional, ISO date)
4
5
  # Output: JSON [{number, title, url, labels, createdAt, closedAt}]
5
6
  set -e
@@ -1,5 +1,6 @@
1
1
  #!/bin/sh
2
2
  # Forge concept: user-identity (Linear via GraphQL API)
3
+ # forge-executable: curl
3
4
  # Output: plain text display name
4
5
  set -e
5
6
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: generate-image
3
- description: AI image generation via Gemini. Use when the user wants to generate, create, or make an image, or when you need to create visual assets like logos, diagrams, or illustrations. Requires GEMINI_API_KEY or GOOGLE_API_KEY.
3
+ description: AI image generation via Gemini with optional async providers. Use when the user wants to generate, create, or make an image, or when you need to create visual assets like logos, diagrams, or illustrations. Requires the API key for the selected provider.
4
4
  ---
5
5
 
6
6
  # generate-image - AI Image Generation
@@ -22,6 +22,7 @@ Note: this is a `codev` subcommand, not standalone.
22
22
  -r, --resolution <res> Resolution: 1K, 2K, 4K (default: 1K)
23
23
  -a, --aspect <ratio> Aspect ratio (default: 1:1)
24
24
  --ref <image> Reference image (repeatable, max 14)
25
+ -p, --provider <name> Provider name (default: gemini)
25
26
  ```
26
27
 
27
28
  ## Aspect ratios
@@ -43,3 +44,40 @@ codev generate-image prompt.txt -o result.png # Prompt from .txt file
43
44
  - Prompt can be a `.txt` file path (auto-detected by extension)
44
45
  - Reference images must exist on disk
45
46
  - Requires `GEMINI_API_KEY` or `GOOGLE_API_KEY` environment variable
47
+
48
+ ## Alternative provider: Atlas Cloud
49
+
50
+ `-p atlas` generates through Atlas Cloud, which serves the same Nano Banana Pro
51
+ model over a submit-then-poll REST API. Useful when a Google AI Studio key is
52
+ not available. Requires `ATLASCLOUD_API_KEY`; the default provider is unchanged.
53
+
54
+ ```bash
55
+ codev generate-image "A sunset over mountains" -p atlas -a 16:9
56
+ ```
57
+
58
+ Measured differences on that path:
59
+
60
+ - `-a/--aspect` works (`1:1` returns 1024x1024, `16:9` returns 1376x768).
61
+ - `-r/--resolution` has no equivalent field; a note is printed and the model's
62
+ default resolution comes back.
63
+ - `--ref` is not supported (text-to-image only) and exits with an error rather
64
+ than silently ignoring the images.
65
+ - The model returns JPEG, so the output file is named after its actual bytes
66
+ instead of writing JPEG into the default `output.png`.
67
+
68
+ ## MuAPI provider
69
+
70
+ `-p muapi` uses MuAPI's Nano Banana Pro endpoint with the same aspect-ratio and
71
+ resolution controls. Set `MUAPI_API_KEY` (or `MU_API_KEY`) using the [MuAPI
72
+ access keys page](https://muapi.ai/access-keys). Reference images are uploaded
73
+ to MuAPI and sent to the edit endpoint; returned media is downloaded only over
74
+ HTTPS.
75
+
76
+ ```bash
77
+ codev generate-image "A sunset over mountains" -p muapi -a 16:9
78
+ codev generate-image "Keep the subject, change the lighting" -p muapi --ref subject.png
79
+ ```
80
+
81
+ The provider submits a job, polls the [MuAPI image API](https://muapi.ai/ai-image-api),
82
+ and writes the returned image locally. Polling and individual requests are
83
+ bounded, and the API key is never sent to the returned media host.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: generate-image
3
- description: AI image generation via Gemini. Use when the user wants to generate, create, or make an image, or when you need to create visual assets like logos, diagrams, or illustrations. Requires GEMINI_API_KEY or GOOGLE_API_KEY.
3
+ description: AI image generation via Gemini with optional async providers. Use when the user wants to generate, create, or make an image, or when you need to create visual assets like logos, diagrams, or illustrations. Requires the API key for the selected provider.
4
4
  ---
5
5
 
6
6
  # generate-image - AI Image Generation
@@ -22,6 +22,7 @@ Note: this is a `codev` subcommand, not standalone.
22
22
  -r, --resolution <res> Resolution: 1K, 2K, 4K (default: 1K)
23
23
  -a, --aspect <ratio> Aspect ratio (default: 1:1)
24
24
  --ref <image> Reference image (repeatable, max 14)
25
+ -p, --provider <name> Provider name (default: gemini)
25
26
  ```
26
27
 
27
28
  ## Aspect ratios
@@ -43,3 +44,40 @@ codev generate-image prompt.txt -o result.png # Prompt from .txt file
43
44
  - Prompt can be a `.txt` file path (auto-detected by extension)
44
45
  - Reference images must exist on disk
45
46
  - Requires `GEMINI_API_KEY` or `GOOGLE_API_KEY` environment variable
47
+
48
+ ## Alternative provider: Atlas Cloud
49
+
50
+ `-p atlas` generates through Atlas Cloud, which serves the same Nano Banana Pro
51
+ model over a submit-then-poll REST API. Useful when a Google AI Studio key is
52
+ not available. Requires `ATLASCLOUD_API_KEY`; the default provider is unchanged.
53
+
54
+ ```bash
55
+ codev generate-image "A sunset over mountains" -p atlas -a 16:9
56
+ ```
57
+
58
+ Measured differences on that path:
59
+
60
+ - `-a/--aspect` works (`1:1` returns 1024x1024, `16:9` returns 1376x768).
61
+ - `-r/--resolution` has no equivalent field; a note is printed and the model's
62
+ default resolution comes back.
63
+ - `--ref` is not supported (text-to-image only) and exits with an error rather
64
+ than silently ignoring the images.
65
+ - The model returns JPEG, so the output file is named after its actual bytes
66
+ instead of writing JPEG into the default `output.png`.
67
+
68
+ ## MuAPI provider
69
+
70
+ `-p muapi` uses MuAPI's Nano Banana Pro endpoint with the same aspect-ratio and
71
+ resolution controls. Set `MUAPI_API_KEY` (or `MU_API_KEY`) using the [MuAPI
72
+ access keys page](https://muapi.ai/access-keys). Reference images are uploaded
73
+ to MuAPI and sent to the edit endpoint; returned media is downloaded only over
74
+ HTTPS.
75
+
76
+ ```bash
77
+ codev generate-image "A sunset over mountains" -p muapi -a 16:9
78
+ codev generate-image "Keep the subject, change the lighting" -p muapi --ref subject.png
79
+ ```
80
+
81
+ The provider submits a job, polls the [MuAPI image API](https://muapi.ai/ai-image-api),
82
+ and writes the returned image locally. Polling and individual requests are
83
+ bounded, and the API key is never sent to the returned media host.
@@ -18,7 +18,8 @@ Open the PR with the review embedded in its body, optionally run CMAP, and notif
18
18
  The body must carry `Closes #<N>` for the driving issue — one per issue if several — so GitHub auto-closes it on merge. **Exception:** a partial fix uses `Refs #<N>` or `Part of #<N>` instead. Substitute the real number for `<N>`; leave no `{{...}}` tag or `<N>` placeholder in the committed body.
19
19
 
20
20
  ```bash
21
- gh pr create --title "[Air #<N>] feat: <brief description>" --body "$(cat <<'EOF'
21
+ export CODEV_PR_TITLE="[Air #<N>] feat: <brief description>"
22
+ export CODEV_PR_BODY="$(cat <<'EOF'
22
23
  ## Summary
23
24
 
24
25
  <1-2 sentence description of the feature>
@@ -44,8 +45,12 @@ Closes #<N> <!-- Substitute <N>; use "Refs #<N>" for a partial fix -->
44
45
  <anything the reviewer should focus on, or "Standard implementation — no special concerns">
45
46
  EOF
46
47
  )"
48
+
49
+ {{pr_create_command}}
47
50
  ```
48
51
 
52
+ The command above is your forge's `pr-create` concept, substituted by porch (`gh pr create` by default). It takes `CODEV_PR_TITLE` / `CODEV_PR_BODY` — optionally `CODEV_PR_BASE`, `CODEV_PR_HEAD`, `CODEV_PR_REPO` — from the environment, which is why they are exported rather than prefixed onto the command line: an inline override that spells `--title "$CODEV_PR_TITLE"` needs them set in the calling shell too. It prints `{"number": <int>, "url": "<url>"}`.
53
+
49
54
  ## Optional CMAP review
50
55
 
51
56
  CMAP is your judgement call for AIR. Skip it for simple changes (config, small UI); run it for features touching core logic or several modules:
@@ -44,7 +44,8 @@ The review's `## Architecture Updates` and `## Lessons Learned Updates` sections
44
44
  The PR body must carry `Closes #<N>` (feature) or `Fixes #<N>` (bug) for the driving issue — one keyword per issue if several — so GitHub auto-closes on merge. **Exception:** a PR that only partially addresses its issue uses `Refs #<N>` or `Part of #<N>` instead, leaving the issue open for the follow-up.
45
45
 
46
46
  ```bash
47
- gh pr create --title "[Spec {{project_id}}] {{title}}" --body "$(cat <<'EOF'
47
+ export CODEV_PR_TITLE="[Spec {{project_id}}] {{title}}"
48
+ export CODEV_PR_BODY="$(cat <<'EOF'
48
49
  ## Summary
49
50
  [what was implemented]
50
51
 
@@ -63,8 +64,12 @@ codev/specs/{{artifact_name}}.md
63
64
  codev/reviews/{{artifact_name}}.md
64
65
  EOF
65
66
  )"
67
+
68
+ {{pr_create_command}}
66
69
  ```
67
70
 
71
+ The command above is your forge's `pr-create` concept, substituted by porch (`gh pr create` by default). It takes `CODEV_PR_TITLE` / `CODEV_PR_BODY` — optionally `CODEV_PR_BASE`, `CODEV_PR_HEAD`, `CODEV_PR_REPO` — from the environment, which is why they are exported rather than prefixed onto the command line: an inline override that spells `--title "$CODEV_PR_TITLE"` needs them set in the calling shell too. It prints `{"number": <int>, "url": "<url>"}`.
72
+
68
73
  ## Signals
69
74
 
70
75
  - Review document complete:
@@ -16,7 +16,8 @@ Open the PR, run CMAP review on it, address feedback, and hand off to the archit
16
16
  The PR body must carry `Fixes #<N>` for the driving issue — one per issue if several — so GitHub auto-closes it on merge. **Exception:** a PR that only partially addresses the issue uses `Refs #<N>` or `Part of #<N>` instead, leaving it open for the follow-up. Substitute the real number for `<N>`; leave no `{{...}}` tag or `<N>` placeholder in the committed body.
17
17
 
18
18
  ```bash
19
- gh pr create --title "Fix #<N>: <brief description>" --body "$(cat <<'EOF'
19
+ export CODEV_PR_TITLE="Fix #<N>: <brief description>"
20
+ export CODEV_PR_BODY="$(cat <<'EOF'
20
21
  ## Summary
21
22
 
22
23
  <1-2 sentence description of the bug and fix>
@@ -38,8 +39,12 @@ Fixes #<N> <!-- Substitute <N>; use "Refs #<N>" for a partial fix -->
38
39
  - [ ] All tests pass
39
40
  EOF
40
41
  )"
42
+
43
+ {{pr_create_command}}
41
44
  ```
42
45
 
46
+ The command above is your forge's `pr-create` concept, substituted by porch (`gh pr create` by default). It takes `CODEV_PR_TITLE` / `CODEV_PR_BODY` — optionally `CODEV_PR_BASE`, `CODEV_PR_HEAD`, `CODEV_PR_REPO` — from the environment, which is why they are exported rather than prefixed onto the command line: an inline override that spells `--title "$CODEV_PR_TITLE"` needs them set in the calling shell too. It prints `{"number": <int>, "url": "<url>"}`.
47
+
43
48
  ## Run CMAP review
44
49
 
45
50
  BUGFIX runs its own 3-way consultation on the PR (porch does not do it for you). Dispatch all three in the background:
@@ -27,7 +27,8 @@ refactor surrounding code, fix unrelated bugs (file separate issues), or add fea
27
27
  [Bugfix #42] Test: regression for unencoded username
28
28
  ```
29
29
 
30
- **PR** — open with `gh pr create`, body carrying Summary, Root Cause, Fix and Test Plan plus
30
+ **PR** — open with the `pr-create` forge concept command the PR-phase prompt hands you (`gh
31
+ pr create` on GitHub), body carrying Summary, Root Cause, Fix and Test Plan plus
31
32
  `Fixes #<N>` so the issue closes on merge. Run one CMAP pass (Gemini, Codex, Claude), record
32
33
  each verdict, and address or rebut every `REQUEST_CHANGES`. Notify the architect with the
33
34
  verdicts, then `porch done <id>` and wait.
@@ -46,7 +46,8 @@ If the run was not tied to any issue, the `Closes` line can be omitted.
46
46
  ```bash
47
47
  git push origin HEAD
48
48
 
49
- gh pr create --title "[Maintain] Codebase maintenance run NNNN" --body "$(cat <<'PREOF'
49
+ export CODEV_PR_TITLE="[Maintain] Codebase maintenance run NNNN"
50
+ export CODEV_PR_BODY="$(cat <<'PREOF'
50
51
  ## Summary
51
52
 
52
53
  <2-3 bullet points of what was done>
@@ -67,8 +68,12 @@ Closes #<N> <!-- Only if this run was triggered by a GitHub issue. Use "Refs #<
67
68
  - [x] Documentation links resolve
68
69
  PREOF
69
70
  )"
71
+
72
+ {{pr_create_command}}
70
73
  ```
71
74
 
75
+ The command above is your forge's `pr-create` concept, substituted by porch (`gh pr create` by default). It takes `CODEV_PR_TITLE` / `CODEV_PR_BODY` — optionally `CODEV_PR_BASE`, `CODEV_PR_HEAD`, `CODEV_PR_REPO` — from the environment, which is why they are exported rather than prefixed onto the command line: an inline override that spells `--title "$CODEV_PR_TITLE"` needs them set in the calling shell too. It prints `{"number": <int>, "url": "<url>"}`.
76
+
72
77
  ## Signals
73
78
 
74
79
  When PR is created:
@@ -108,13 +108,16 @@ git push
108
108
  PR_TITLE="<concise description of the change>"
109
109
  BRANCH="$(git branch --show-current)"
110
110
 
111
- gh pr create \
112
- --base main \
113
- --head "$BRANCH" \
114
- --title "$PR_TITLE" \
115
- --body-file codev/reviews/{{artifact_name}}.md
111
+ export CODEV_PR_TITLE="$PR_TITLE"
112
+ export CODEV_PR_BODY="$(cat codev/reviews/{{artifact_name}}.md)"
113
+ export CODEV_PR_BASE=main
114
+ export CODEV_PR_HEAD="$BRANCH"
115
+
116
+ {{pr_create_command}}
116
117
  ```
117
118
 
119
+ The command above is your forge's `pr-create` concept, substituted by porch (`gh pr create` by default). It prints `{"number": <int>, "url": "<url>"}`. The inputs are **exported** so an inline override that spells `--title "$CODEV_PR_TITLE"` sees them too. The body goes in as an environment variable, not `--body-file` — read the created PR back and confirm the body arrived intact before moving on.
120
+
118
121
  **Verify the PR body contains `Fixes #{{issue.number}}`** (it should — the review file has it at the top). If somehow missing, edit and re-apply:
119
122
 
120
123
  ```bash
@@ -154,7 +157,7 @@ For any `REQUEST_CHANGES`:
154
157
 
155
158
  1. Read the finding in full (`codev/projects/{{project_id}}-*/{{project_id}}-<model>.txt`).
156
159
  2. **Assess it honestly:**
157
- - **Real defect** (correctness / cancellation / security / data-loss): fix it in code, add a regression test that fails without the fix, commit + push (the PR updates automatically — no new `gh pr create`). Then document the finding, your fix, and the pinning test in the review file's **"Things to Look At During PR Review"** section.
160
+ - **Real defect** (correctness / cancellation / security / data-loss): fix it in code, add a regression test that fails without the fix, commit + push (the PR updates automatically — no second PR). Then document the finding, your fix, and the pinning test in the review file's **"Things to Look At During PR Review"** section.
158
161
  - **False positive / out of scope**: write a brief rebuttal in that same section explaining why no change is warranted.
159
162
  3. Do **not** re-run `porch done` expecting another consultation pass — `max_iterations: 1` means it will not re-review. Proceed to step 7.
160
163
 
@@ -257,7 +260,7 @@ Together with the `--pr` record from step 4a and the `--merged` record from step
257
260
  ```
258
261
  - Resolve conflicts (do NOT use destructive shortcuts)
259
262
  - Force-push with lease: `git push --force-with-lease`
260
- - Re-run `gh pr create`
263
+ - Re-run the PR creation command from step 4
261
264
 
262
265
  **If porch's consultation fails (e.g., model unavailable):**
263
266
  - `porch done` will report the failure. Inspect `codev/projects/{{project_id}}-*/{{project_id}}-<model>.txt` for the failure details.
@@ -44,7 +44,8 @@ The review's `## Architecture Updates` and `## Lessons Learned Updates` sections
44
44
  The PR body must carry `Closes #<N>` (feature) or `Fixes #<N>` (bug) for the driving issue — one keyword per issue if several — so GitHub auto-closes on merge. **Exception:** a PR that only partially addresses its issue uses `Refs #<N>` or `Part of #<N>` instead, leaving the issue open for the follow-up.
45
45
 
46
46
  ```bash
47
- gh pr create --title "[Spec {{project_id}}] {{title}}" --body "$(cat <<'EOF'
47
+ export CODEV_PR_TITLE="[Spec {{project_id}}] {{title}}"
48
+ export CODEV_PR_BODY="$(cat <<'EOF'
48
49
  ## Summary
49
50
  [what was implemented]
50
51
 
@@ -63,8 +64,12 @@ codev/specs/{{artifact_name}}.md
63
64
  codev/reviews/{{artifact_name}}.md
64
65
  EOF
65
66
  )"
67
+
68
+ {{pr_create_command}}
66
69
  ```
67
70
 
71
+ The command above is your forge's `pr-create` concept, substituted by porch (`gh pr create` by default). It takes `CODEV_PR_TITLE` / `CODEV_PR_BODY` — optionally `CODEV_PR_BASE`, `CODEV_PR_HEAD`, `CODEV_PR_REPO` — from the environment, which is why they are exported rather than prefixed onto the command line: an inline override that spells `--title "$CODEV_PR_TITLE"` needs them set in the calling shell too. It prints `{"number": <int>, "url": "<url>"}`.
72
+
68
73
  ## Signals
69
74
 
70
75
  - Review document complete:
@@ -391,11 +391,36 @@ Sends text to a builder's terminal. Useful for:
391
391
 
392
392
  - **delivered** — the message was written to the recipient's prompt after a clean render-gate pass (an empty, render-verified prompt).
393
393
  - **held** — the prompt was not clear, so the message is persisted in Tower's durable mailbox and **delivers automatically** the moment the recipient's prompt is clean (after a submit, on output quiescence, or a poll backstop). The response carries a **why-held reason** and a mailbox id:
394
- - `busy` — a draft, menu, dialog, or wrapper screen occupies the prompt;
394
+ - `busy` — the prompt is occupied. `afx inbox` qualifies it with the gate's own detail:
395
+ `busy:user-text` (a draft, menu, dialog, or wrapper screen), `busy:recent-input` (the
396
+ terminal received input within the last fraction of a second — a keystroke, a click, or an
397
+ ungated `--interrupt`/`--escape` write — so the composer may be changing under the gate; it
398
+ clears by itself a moment after the input stops), `busy:composer-redraw` (the composer is
399
+ empty but its rendered region moved a moment ago — a turn-end redraw, a rotating
400
+ placeholder, a resize; also self-clearing, about a quarter second after it stops moving), or
401
+ `busy:no-region-end` / `busy:no-composer-marker` (the classifier could not verify the
402
+ composer at all — the one hold class that does NOT clear on its own);
395
403
  - `no-profile` — the target app has no render-gate classifier profile (only `claude`, `codex`, and `agy` are modeled);
396
404
  - `no-live-pty` — the recipient agent has no live terminal right now (it delivers when the agent respawns — rows address agents, not PTYs).
397
405
 
398
- A held message is **never force-injected** onto a busy line: a message body is only ever written to a verified-empty prompt, so it cannot fuse with a half-typed draft, and held rows survive Tower restart/shutdown (no shutdown force-flush). See held mail with `afx inbox`, read one (including its body) with `afx inbox show <id>`, and clear one with `afx inbox dismiss <id>`. `--interrupt` is the explicit, deliberate bypass: it interrupts the agent and writes without holding (unchanged semantics).
406
+ **A recipient that is simply TALKING does not hold mail** (Issue #1664). claude and codex accept
407
+ input during a turn and queue it for the next one, so a message written onto their empty composer
408
+ mid-turn is safe and lands as one message when the turn ends — the gate waits only on the
409
+ *composer*, never on the spinner or the streaming transcript above it. Before that, mail queued
410
+ behind whole turns: measured over 24 h, 227 deliveries waited more than 2 s, averaging 54 s and
411
+ peaking at 561 s.
412
+
413
+ A held message is **never force-injected** onto a busy line: a message body is only ever written to a verified-empty prompt, so it cannot fuse with a draft the gate can see, and held rows survive Tower restart/shutdown (no shutdown force-flush). Two signals make a draft visible (Issue #1473) — the composer classifier reads it off the rendered screen, and every write that reaches the PTY **through Tower** (browser and VS Code keystrokes, the terminal input API, an ungated `--interrupt`/`--escape`) is observed there — whatever survives the terminal-reply filter bumps an input counter the gate folds into its change token, so even a keystroke the app has not echoed yet holds the message, while a chunk that is nothing but a DA/DSR/CPR answer moves nothing. **`afx attach` is the one exception:** it pipes your keystrokes straight to the shellper socket, never through Tower's `PtySession.write()`, so they move no counter — a draft typed under `afx attach` is seen only once the app has echoed it and the classifier can read it off the screen. See held mail with `afx inbox`, read one (including its body) with `afx inbox show <id>`, and clear one with `afx inbox dismiss <id>`. `--interrupt` is the explicit, deliberate bypass: it interrupts the agent and writes without holding (unchanged semantics).
414
+
415
+ **How a body is typed (Issue #1567):** a frame under 4 lines and at most 256 bytes is one write plus
416
+ Enter, as always. Anything longer is written as **one explicit bracketed paste** (`ESC[200~ … ESC[201~`,
417
+ in ≤512-byte pieces 5 ms apart) with the Enter as a separate write after a short settle. This is the fix
418
+ for the delivery-side head loss where a >1 KB single write was split by the PTY input queue and the
419
+ recipient's paste heuristic discarded the first chunk (only the tail arrived, with `delivered` at the
420
+ sender). While it sits in the composer, claude shows the paste as `[Pasted text #N +M lines]` and codex
421
+ as `[Pasted Content N chars]`; the echo verification behind the `(unverified — …)` suffix accepts a new
422
+ placard as well as the header, so a correct long delivery is reported verified. agy keeps the older
423
+ line-by-line shape (also chunked) because its paste handling has not been measured.
399
424
 
400
425
  **Examples:**
401
426
 
@@ -432,7 +457,7 @@ afx inbox dismiss <id> [options]
432
457
  | `ID` | Mailbox row id (pass to `show` / `dismiss`) |
433
458
  | `AGE` | How long the message has been held (`5s`, `3m`, `2h`, `1d`) |
434
459
  | `REASON` | Why-held: `busy`, `no-profile`, or `no-live-pty`; a trailing `!` marks a row past the escalation age |
435
- | `FROM → TO` | Sender → recipient agent |
460
+ | `FROM → TO` | Sender → recipient agent. An architect sender carries its name (`architect:main`); the column is sized to its content, so long ids are never truncated |
436
461
  | `WORKSPACE` | Owning workspace |
437
462
 
438
463
  **Options:**