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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (328) hide show
  1. package/LICENSE +201 -0
  2. package/bin/af.js +2 -2
  3. package/bin/afx.js +8 -0
  4. package/bin/consult.js +2 -1
  5. package/bin/team.js +2 -1
  6. package/{dashboard/dist/assets/index-nr0cXxtf.css → dashboard-dist/assets/index-Bs8I7N2s.css} +1 -1
  7. package/dashboard-dist/assets/index-CkNo2z6t.js +195 -0
  8. package/dashboard-dist/assets/index-CkNo2z6t.js.map +1 -0
  9. package/dashboard-dist/dist/assets/index-Bs8I7N2s.css +32 -0
  10. package/dashboard-dist/dist/assets/index-CkNo2z6t.js +195 -0
  11. package/dashboard-dist/dist/assets/index-CkNo2z6t.js.map +1 -0
  12. package/{dashboard → dashboard-dist}/dist/index.html +2 -2
  13. package/dashboard-dist/index.html +14 -0
  14. package/dist/agent-farm/cli.d.ts.map +1 -1
  15. package/dist/agent-farm/cli.js +17 -14
  16. package/dist/agent-farm/cli.js.map +1 -1
  17. package/dist/agent-farm/commands/architect.d.ts +4 -4
  18. package/dist/agent-farm/commands/architect.d.ts.map +1 -1
  19. package/dist/agent-farm/commands/architect.js +23 -12
  20. package/dist/agent-farm/commands/architect.js.map +1 -1
  21. package/dist/agent-farm/commands/attach.js +8 -8
  22. package/dist/agent-farm/commands/attach.js.map +1 -1
  23. package/dist/agent-farm/commands/bench.d.ts +1 -1
  24. package/dist/agent-farm/commands/bench.js +1 -1
  25. package/dist/agent-farm/commands/cleanup.d.ts.map +1 -1
  26. package/dist/agent-farm/commands/cleanup.js +10 -5
  27. package/dist/agent-farm/commands/cleanup.js.map +1 -1
  28. package/dist/agent-farm/commands/cron.js +1 -1
  29. package/dist/agent-farm/commands/cron.js.map +1 -1
  30. package/dist/agent-farm/commands/db.d.ts +3 -3
  31. package/dist/agent-farm/commands/db.js +4 -4
  32. package/dist/agent-farm/commands/db.js.map +1 -1
  33. package/dist/agent-farm/commands/open.js +2 -2
  34. package/dist/agent-farm/commands/open.js.map +1 -1
  35. package/dist/agent-farm/commands/rename.js +1 -1
  36. package/dist/agent-farm/commands/rename.js.map +1 -1
  37. package/dist/agent-farm/commands/send.d.ts +4 -1
  38. package/dist/agent-farm/commands/send.d.ts.map +1 -1
  39. package/dist/agent-farm/commands/send.js +8 -5
  40. package/dist/agent-farm/commands/send.js.map +1 -1
  41. package/dist/agent-farm/commands/shell.js +3 -3
  42. package/dist/agent-farm/commands/shell.js.map +1 -1
  43. package/dist/agent-farm/commands/spawn-roles.d.ts +2 -1
  44. package/dist/agent-farm/commands/spawn-roles.d.ts.map +1 -1
  45. package/dist/agent-farm/commands/spawn-roles.js +1 -1
  46. package/dist/agent-farm/commands/spawn-roles.js.map +1 -1
  47. package/dist/agent-farm/commands/spawn-worktree.d.ts +49 -10
  48. package/dist/agent-farm/commands/spawn-worktree.d.ts.map +1 -1
  49. package/dist/agent-farm/commands/spawn-worktree.js +356 -51
  50. package/dist/agent-farm/commands/spawn-worktree.js.map +1 -1
  51. package/dist/agent-farm/commands/spawn.d.ts +6 -6
  52. package/dist/agent-farm/commands/spawn.d.ts.map +1 -1
  53. package/dist/agent-farm/commands/spawn.js +182 -74
  54. package/dist/agent-farm/commands/spawn.js.map +1 -1
  55. package/dist/agent-farm/commands/status.d.ts.map +1 -1
  56. package/dist/agent-farm/commands/status.js +48 -3
  57. package/dist/agent-farm/commands/status.js.map +1 -1
  58. package/dist/agent-farm/commands/team-update.d.ts +1 -1
  59. package/dist/agent-farm/commands/team-update.d.ts.map +1 -1
  60. package/dist/agent-farm/commands/team-update.js +8 -5
  61. package/dist/agent-farm/commands/team-update.js.map +1 -1
  62. package/dist/agent-farm/commands/team.d.ts +1 -1
  63. package/dist/agent-farm/commands/team.d.ts.map +1 -1
  64. package/dist/agent-farm/commands/team.js +7 -3
  65. package/dist/agent-farm/commands/team.js.map +1 -1
  66. package/dist/agent-farm/commands/tower-cloud.d.ts +2 -2
  67. package/dist/agent-farm/commands/tower-cloud.js +3 -3
  68. package/dist/agent-farm/commands/tower-cloud.js.map +1 -1
  69. package/dist/agent-farm/commands/tower.js +2 -2
  70. package/dist/agent-farm/commands/tower.js.map +1 -1
  71. package/dist/agent-farm/lib/cloud-config.js +2 -2
  72. package/dist/agent-farm/lib/cloud-config.js.map +1 -1
  73. package/dist/agent-farm/lib/tower-client.d.ts +6 -230
  74. package/dist/agent-farm/lib/tower-client.d.ts.map +1 -1
  75. package/dist/agent-farm/lib/tower-client.js +6 -293
  76. package/dist/agent-farm/lib/tower-client.js.map +1 -1
  77. package/dist/agent-farm/lib/tunnel-client.js +1 -1
  78. package/dist/agent-farm/lib/tunnel-client.js.map +1 -1
  79. package/dist/agent-farm/servers/message-write.d.ts +24 -0
  80. package/dist/agent-farm/servers/message-write.d.ts.map +1 -0
  81. package/dist/agent-farm/servers/message-write.js +63 -0
  82. package/dist/agent-farm/servers/message-write.js.map +1 -0
  83. package/dist/agent-farm/servers/overview.d.ts +10 -8
  84. package/dist/agent-farm/servers/overview.d.ts.map +1 -1
  85. package/dist/agent-farm/servers/overview.js +78 -38
  86. package/dist/agent-farm/servers/overview.js.map +1 -1
  87. package/dist/agent-farm/servers/send-buffer.d.ts +4 -3
  88. package/dist/agent-farm/servers/send-buffer.d.ts.map +1 -1
  89. package/dist/agent-farm/servers/send-buffer.js +7 -4
  90. package/dist/agent-farm/servers/send-buffer.js.map +1 -1
  91. package/dist/agent-farm/servers/tower-cron-parser.js +1 -1
  92. package/dist/agent-farm/servers/tower-cron-parser.js.map +1 -1
  93. package/dist/agent-farm/servers/tower-cron.d.ts.map +1 -1
  94. package/dist/agent-farm/servers/tower-cron.js +4 -5
  95. package/dist/agent-farm/servers/tower-cron.js.map +1 -1
  96. package/dist/agent-farm/servers/tower-instances.d.ts.map +1 -1
  97. package/dist/agent-farm/servers/tower-instances.js +15 -13
  98. package/dist/agent-farm/servers/tower-instances.js.map +1 -1
  99. package/dist/agent-farm/servers/tower-routes.d.ts.map +1 -1
  100. package/dist/agent-farm/servers/tower-routes.js +9 -16
  101. package/dist/agent-farm/servers/tower-routes.js.map +1 -1
  102. package/dist/agent-farm/servers/tower-server.js +1 -1
  103. package/dist/agent-farm/servers/tower-terminals.d.ts +2 -2
  104. package/dist/agent-farm/servers/tower-terminals.d.ts.map +1 -1
  105. package/dist/agent-farm/servers/tower-terminals.js +64 -36
  106. package/dist/agent-farm/servers/tower-terminals.js.map +1 -1
  107. package/dist/agent-farm/servers/tower-tunnel.js +2 -2
  108. package/dist/agent-farm/servers/tower-tunnel.js.map +1 -1
  109. package/dist/agent-farm/servers/tower-utils.d.ts +9 -6
  110. package/dist/agent-farm/servers/tower-utils.d.ts.map +1 -1
  111. package/dist/agent-farm/servers/tower-utils.js +13 -7
  112. package/dist/agent-farm/servers/tower-utils.js.map +1 -1
  113. package/dist/agent-farm/types.d.ts +13 -0
  114. package/dist/agent-farm/types.d.ts.map +1 -1
  115. package/dist/agent-farm/utils/config.d.ts +11 -0
  116. package/dist/agent-farm/utils/config.d.ts.map +1 -1
  117. package/dist/agent-farm/utils/config.js +39 -17
  118. package/dist/agent-farm/utils/config.js.map +1 -1
  119. package/dist/agent-farm/utils/harness.d.ts +85 -0
  120. package/dist/agent-farm/utils/harness.d.ts.map +1 -0
  121. package/dist/agent-farm/utils/harness.js +212 -0
  122. package/dist/agent-farm/utils/harness.js.map +1 -0
  123. package/dist/agent-farm/utils/roles.d.ts +11 -4
  124. package/dist/agent-farm/utils/roles.d.ts.map +1 -1
  125. package/dist/agent-farm/utils/roles.js +21 -6
  126. package/dist/agent-farm/utils/roles.js.map +1 -1
  127. package/dist/cli.d.ts +1 -1
  128. package/dist/cli.d.ts.map +1 -1
  129. package/dist/cli.js +40 -5
  130. package/dist/cli.js.map +1 -1
  131. package/dist/commands/adopt.d.ts.map +1 -1
  132. package/dist/commands/adopt.js +16 -45
  133. package/dist/commands/adopt.js.map +1 -1
  134. package/dist/commands/consult/index.d.ts +37 -3
  135. package/dist/commands/consult/index.d.ts.map +1 -1
  136. package/dist/commands/consult/index.js +281 -194
  137. package/dist/commands/consult/index.js.map +1 -1
  138. package/dist/commands/doctor.d.ts.map +1 -1
  139. package/dist/commands/doctor.js +121 -31
  140. package/dist/commands/doctor.js.map +1 -1
  141. package/dist/commands/init.d.ts.map +1 -1
  142. package/dist/commands/init.js +13 -43
  143. package/dist/commands/init.js.map +1 -1
  144. package/dist/commands/porch/artifacts.d.ts +69 -0
  145. package/dist/commands/porch/artifacts.d.ts.map +1 -0
  146. package/dist/commands/porch/artifacts.js +368 -0
  147. package/dist/commands/porch/artifacts.js.map +1 -0
  148. package/dist/commands/porch/checks.d.ts +14 -3
  149. package/dist/commands/porch/checks.d.ts.map +1 -1
  150. package/dist/commands/porch/checks.js +150 -4
  151. package/dist/commands/porch/checks.js.map +1 -1
  152. package/dist/commands/porch/config.d.ts +6 -9
  153. package/dist/commands/porch/config.d.ts.map +1 -1
  154. package/dist/commands/porch/config.js +12 -37
  155. package/dist/commands/porch/config.js.map +1 -1
  156. package/dist/commands/porch/index.d.ts +12 -7
  157. package/dist/commands/porch/index.d.ts.map +1 -1
  158. package/dist/commands/porch/index.js +273 -82
  159. package/dist/commands/porch/index.js.map +1 -1
  160. package/dist/commands/porch/next.d.ts.map +1 -1
  161. package/dist/commands/porch/next.js +142 -75
  162. package/dist/commands/porch/next.js.map +1 -1
  163. package/dist/commands/porch/notify.d.ts +2 -2
  164. package/dist/commands/porch/notify.js +5 -5
  165. package/dist/commands/porch/notify.js.map +1 -1
  166. package/dist/commands/porch/prompts.d.ts +2 -1
  167. package/dist/commands/porch/prompts.d.ts.map +1 -1
  168. package/dist/commands/porch/prompts.js +34 -38
  169. package/dist/commands/porch/prompts.js.map +1 -1
  170. package/dist/commands/porch/protocol.d.ts +4 -3
  171. package/dist/commands/porch/protocol.d.ts.map +1 -1
  172. package/dist/commands/porch/protocol.js +21 -21
  173. package/dist/commands/porch/protocol.js.map +1 -1
  174. package/dist/commands/porch/state.d.ts +27 -2
  175. package/dist/commands/porch/state.d.ts.map +1 -1
  176. package/dist/commands/porch/state.js +100 -9
  177. package/dist/commands/porch/state.js.map +1 -1
  178. package/dist/commands/porch/types.d.ts +10 -2
  179. package/dist/commands/porch/types.d.ts.map +1 -1
  180. package/dist/commands/sync.d.ts +26 -0
  181. package/dist/commands/sync.d.ts.map +1 -0
  182. package/dist/commands/sync.js +207 -0
  183. package/dist/commands/sync.js.map +1 -0
  184. package/dist/commands/update.d.ts +15 -2
  185. package/dist/commands/update.d.ts.map +1 -1
  186. package/dist/commands/update.js +159 -202
  187. package/dist/commands/update.js.map +1 -1
  188. package/dist/lib/config.d.ts +96 -0
  189. package/dist/lib/config.d.ts.map +1 -0
  190. package/dist/lib/config.js +165 -0
  191. package/dist/lib/config.js.map +1 -0
  192. package/dist/lib/forge-contracts.d.ts +100 -0
  193. package/dist/lib/forge-contracts.d.ts.map +1 -0
  194. package/dist/lib/forge-contracts.js +35 -0
  195. package/dist/lib/forge-contracts.js.map +1 -0
  196. package/dist/lib/forge.d.ts +102 -0
  197. package/dist/lib/forge.d.ts.map +1 -0
  198. package/dist/lib/forge.js +376 -0
  199. package/dist/lib/forge.js.map +1 -0
  200. package/dist/lib/github.d.ts +68 -60
  201. package/dist/lib/github.d.ts.map +1 -1
  202. package/dist/lib/github.js +160 -169
  203. package/dist/lib/github.js.map +1 -1
  204. package/dist/lib/skeleton.d.ts +15 -5
  205. package/dist/lib/skeleton.d.ts.map +1 -1
  206. package/dist/lib/skeleton.js +82 -7
  207. package/dist/lib/skeleton.js.map +1 -1
  208. package/dist/lib/team-github.d.ts +7 -3
  209. package/dist/lib/team-github.d.ts.map +1 -1
  210. package/dist/lib/team-github.js +29 -19
  211. package/dist/lib/team-github.js.map +1 -1
  212. package/dist/terminal/pty-manager.d.ts.map +1 -1
  213. package/dist/terminal/pty-manager.js +2 -0
  214. package/dist/terminal/pty-manager.js.map +1 -1
  215. package/dist/terminal/ws-protocol.d.ts +10 -8
  216. package/dist/terminal/ws-protocol.d.ts.map +1 -1
  217. package/dist/terminal/ws-protocol.js +3 -0
  218. package/dist/terminal/ws-protocol.js.map +1 -1
  219. package/package.json +27 -24
  220. package/scripts/forge/gitea/auth-status.sh +3 -0
  221. package/scripts/forge/gitea/issue-comment.sh +3 -0
  222. package/scripts/forge/gitea/issue-list.sh +3 -0
  223. package/scripts/forge/gitea/issue-view.sh +3 -0
  224. package/scripts/forge/gitea/pr-exists.sh +6 -0
  225. package/scripts/forge/gitea/pr-list.sh +3 -0
  226. package/scripts/forge/gitea/pr-merge.sh +3 -0
  227. package/scripts/forge/gitea/pr-view.sh +3 -0
  228. package/scripts/forge/gitea/recently-closed.sh +3 -0
  229. package/scripts/forge/gitea/recently-merged.sh +3 -0
  230. package/scripts/forge/gitea/repo-archive.sh +11 -0
  231. package/scripts/forge/gitea/user-identity.sh +3 -0
  232. package/scripts/forge/github/auth-status.sh +4 -0
  233. package/scripts/forge/github/issue-comment.sh +5 -0
  234. package/scripts/forge/github/issue-list.sh +4 -0
  235. package/scripts/forge/github/issue-view.sh +5 -0
  236. package/scripts/forge/github/on-it-timestamps.sh +8 -0
  237. package/scripts/forge/github/pr-diff.sh +10 -0
  238. package/scripts/forge/github/pr-exists.sh +7 -0
  239. package/scripts/forge/github/pr-list.sh +4 -0
  240. package/scripts/forge/github/pr-merge.sh +5 -0
  241. package/scripts/forge/github/pr-search.sh +5 -0
  242. package/scripts/forge/github/pr-view.sh +11 -0
  243. package/scripts/forge/github/recently-closed.sh +15 -0
  244. package/scripts/forge/github/recently-merged.sh +15 -0
  245. package/scripts/forge/github/repo-archive.sh +10 -0
  246. package/scripts/forge/github/team-activity.sh +5 -0
  247. package/scripts/forge/github/user-identity.sh +4 -0
  248. package/scripts/forge/gitlab/auth-status.sh +3 -0
  249. package/scripts/forge/gitlab/issue-comment.sh +3 -0
  250. package/scripts/forge/gitlab/issue-list.sh +3 -0
  251. package/scripts/forge/gitlab/issue-view.sh +3 -0
  252. package/scripts/forge/gitlab/pr-diff.sh +3 -0
  253. package/scripts/forge/gitlab/pr-exists.sh +5 -0
  254. package/scripts/forge/gitlab/pr-list.sh +3 -0
  255. package/scripts/forge/gitlab/pr-merge.sh +3 -0
  256. package/scripts/forge/gitlab/pr-search.sh +3 -0
  257. package/scripts/forge/gitlab/pr-view.sh +3 -0
  258. package/scripts/forge/gitlab/recently-closed.sh +3 -0
  259. package/scripts/forge/gitlab/recently-merged.sh +3 -0
  260. package/scripts/forge/gitlab/repo-archive.sh +12 -0
  261. package/scripts/forge/gitlab/user-identity.sh +3 -0
  262. package/skeleton/.claude/skills/afx/SKILL.md +144 -0
  263. package/skeleton/.claude/skills/codev/SKILL.md +3 -3
  264. package/skeleton/.claude/skills/consult/SKILL.md +1 -1
  265. package/skeleton/.claude/skills/porch/SKILL.md +2 -2
  266. package/skeleton/DEPENDENCIES.md +1 -1
  267. package/skeleton/builders.md +4 -4
  268. package/skeleton/protocol-schema.json +2 -2
  269. package/skeleton/protocols/air/builder-prompt.md +6 -6
  270. package/skeleton/protocols/air/prompts/pr.md +17 -5
  271. package/skeleton/protocols/air/protocol.md +1 -2
  272. package/skeleton/protocols/aspir/builder-prompt.md +28 -6
  273. package/skeleton/protocols/aspir/prompts/review.md +11 -0
  274. package/skeleton/protocols/aspir/protocol.json +9 -1
  275. package/skeleton/protocols/aspir/protocol.md +1 -1
  276. package/skeleton/protocols/aspir/templates/plan.md +0 -20
  277. package/skeleton/protocols/aspir/templates/spec.md +0 -28
  278. package/skeleton/protocols/bugfix/builder-prompt.md +6 -6
  279. package/skeleton/protocols/bugfix/prompts/pr.md +16 -4
  280. package/skeleton/protocols/experiment/builder-prompt.md +15 -0
  281. package/skeleton/protocols/experiment/protocol.md +1 -6
  282. package/skeleton/protocols/maintain/builder-prompt.md +12 -11
  283. package/skeleton/protocols/maintain/prompts/maintain.md +88 -0
  284. package/skeleton/protocols/maintain/prompts/review.md +84 -0
  285. package/skeleton/protocols/maintain/protocol.json +14 -68
  286. package/skeleton/protocols/maintain/protocol.md +111 -409
  287. package/skeleton/protocols/maintain/templates/maintenance-run.md +18 -49
  288. package/skeleton/protocols/protocol-schema.json +1 -1
  289. package/skeleton/protocols/spike/builder-prompt.md +1 -1
  290. package/skeleton/protocols/spike/protocol.md +4 -4
  291. package/skeleton/protocols/spir/builder-prompt.md +28 -5
  292. package/skeleton/protocols/spir/prompts/review.md +11 -0
  293. package/skeleton/protocols/spir/protocol.json +9 -1
  294. package/skeleton/protocols/spir/protocol.md +2 -2
  295. package/skeleton/protocols/spir/templates/plan.md +0 -20
  296. package/skeleton/protocols/spir/templates/spec.md +0 -28
  297. package/skeleton/resources/commands/agent-farm.md +84 -84
  298. package/skeleton/resources/commands/codev.md +1 -1
  299. package/skeleton/resources/commands/consult.md +16 -7
  300. package/skeleton/resources/commands/overview.md +14 -15
  301. package/skeleton/resources/risk-triage.md +1 -1
  302. package/skeleton/resources/workflow-reference.md +14 -14
  303. package/skeleton/roles/architect.md +55 -43
  304. package/skeleton/roles/builder.md +22 -14
  305. package/skeleton/templates/AGENTS.md +4 -3
  306. package/skeleton/templates/CLAUDE.md +4 -3
  307. package/skeleton/templates/cheatsheet.md +11 -12
  308. package/templates/3d-viewer.html +16 -18
  309. package/templates/tower.html +1 -1
  310. package/dashboard/dist/assets/index-CiIBsixF.js +0 -194
  311. package/dashboard/dist/assets/index-CiIBsixF.js.map +0 -1
  312. package/skeleton/.claude/skills/af/SKILL.md +0 -146
  313. package/skeleton/porch/prompts/understand.md +0 -61
  314. package/skeleton/porch/prompts/verify.md +0 -58
  315. package/skeleton/protocols/maintain/prompts/audit.md +0 -111
  316. package/skeleton/protocols/maintain/prompts/clean.md +0 -91
  317. package/skeleton/protocols/maintain/prompts/sync.md +0 -113
  318. package/skeleton/protocols/maintain/prompts/verify.md +0 -110
  319. package/skeleton/protocols/tick/builder-prompt.md +0 -65
  320. package/skeleton/protocols/tick/consult-types/impl-review.md +0 -72
  321. package/skeleton/protocols/tick/consult-types/plan-review.md +0 -59
  322. package/skeleton/protocols/tick/consult-types/pr-review.md +0 -72
  323. package/skeleton/protocols/tick/consult-types/spec-review.md +0 -55
  324. package/skeleton/protocols/tick/protocol.json +0 -151
  325. package/skeleton/protocols/tick/protocol.md +0 -277
  326. package/skeleton/protocols/tick/templates/plan.md +0 -67
  327. package/skeleton/protocols/tick/templates/review.md +0 -89
  328. package/skeleton/protocols/tick/templates/spec.md +0 -61
@@ -14,13 +14,19 @@ import chalk from 'chalk';
14
14
  import { query as claudeQuery } from '@anthropic-ai/claude-agent-sdk';
15
15
  import { Codex } from '@openai/codex-sdk';
16
16
  import { readCodevFile, findWorkspaceRoot } from '../../lib/skeleton.js';
17
+ import { getResolver } from '../porch/artifacts.js';
17
18
  import { MetricsDB } from './metrics.js';
18
19
  import { extractUsage, extractReviewText } from './usage-extractor.js';
20
+ import { executeForgeCommandSync } from '../../lib/forge.js';
19
21
  const MODEL_CONFIGS = {
20
22
  gemini: { cli: 'gemini', args: ['--model', 'gemini-3-pro-preview'], envVar: 'GEMINI_SYSTEM_MD' },
23
+ hermes: { cli: 'hermes', args: ['chat', '-q'], envVar: null },
21
24
  };
22
25
  // Models that use an Agent SDK instead of CLI subprocess
23
26
  const SDK_MODELS = ['claude', 'codex'];
27
+ // Prevent E2BIG when passing very large prompts to CLI backends.
28
+ // Large payloads are written to a temp file and referenced in the query.
29
+ const CLI_PROMPT_INLINE_MAX_CHARS = 100_000;
24
30
  // Claude Agent SDK turn limit. Claude explores the codebase with Read/Glob/Grep
25
31
  // tools before producing its verdict, so it needs a generous turn budget.
26
32
  const CLAUDE_MAX_TURNS = 200;
@@ -129,46 +135,28 @@ function loadDotenv(workspaceRoot) {
129
135
  }
130
136
  }
131
137
  /**
132
- * Find a spec file by number. Returns null if not found.
133
- * Errors if multiple matches found.
138
+ * Find spec content by project ID using the artifact resolver.
139
+ * Returns a ContentRef with content and label, or null if not found.
134
140
  */
135
- function findSpec(workspaceRoot, number) {
136
- const specsDir = path.join(workspaceRoot, 'codev', 'specs');
137
- const unpadded = String(number);
138
- const padded = unpadded.padStart(4, '0');
139
- if (fs.existsSync(specsDir)) {
140
- const files = fs.readdirSync(specsDir).filter(f => f.endsWith('.md'));
141
- const matches = files.filter(f => f.startsWith(`${unpadded}-`) || f.startsWith(`${padded}-`));
142
- if (matches.length > 1) {
143
- const list = matches.map(f => ` - codev/specs/${f}`).join('\n');
144
- throw new Error(`Multiple spec files match '${unpadded}' or '${padded}':\n${list}`);
145
- }
146
- if (matches.length === 1) {
147
- return path.join(specsDir, matches[0]);
148
- }
149
- }
150
- return null;
141
+ function findSpecContent(workspaceRoot, id) {
142
+ const resolver = getResolver(workspaceRoot);
143
+ const content = resolver.getSpecContent(id, '');
144
+ if (!content)
145
+ return null;
146
+ const label = resolver.findSpecBaseName(id, '') ?? id;
147
+ return { content, label };
151
148
  }
152
149
  /**
153
- * Find a plan file by number. Returns null if not found.
154
- * Errors if multiple matches found.
150
+ * Find plan content by project ID using the artifact resolver.
151
+ * Returns a ContentRef with content and label, or null if not found.
155
152
  */
156
- function findPlan(workspaceRoot, number) {
157
- const plansDir = path.join(workspaceRoot, 'codev', 'plans');
158
- const unpadded = String(number);
159
- const padded = unpadded.padStart(4, '0');
160
- if (fs.existsSync(plansDir)) {
161
- const files = fs.readdirSync(plansDir).filter(f => f.endsWith('.md'));
162
- const matches = files.filter(f => f.startsWith(`${unpadded}-`) || f.startsWith(`${padded}-`));
163
- if (matches.length > 1) {
164
- const list = matches.map(f => ` - codev/plans/${f}`).join('\n');
165
- throw new Error(`Multiple plan files match '${unpadded}' or '${padded}':\n${list}`);
166
- }
167
- if (matches.length === 1) {
168
- return path.join(plansDir, matches[0]);
169
- }
170
- }
171
- return null;
153
+ function findPlanContent(workspaceRoot, id) {
154
+ const resolver = getResolver(workspaceRoot);
155
+ const content = resolver.getPlanContent(id, '');
156
+ if (!content)
157
+ return null;
158
+ const baseName = resolver.findSpecBaseName(id, '') ?? id;
159
+ return { content, label: baseName };
172
160
  }
173
161
  /**
174
162
  * Check if running in a builder worktree
@@ -309,11 +297,11 @@ export async function runCodexConsultation(queryText, role, workspaceRoot, outpu
309
297
  try {
310
298
  const codex = new Codex({
311
299
  config: {
312
- experimental_instructions_file: tempFile,
300
+ model_instructions_file: tempFile,
313
301
  },
314
302
  });
315
303
  const thread = codex.startThread({
316
- model: 'gpt-5.2-codex',
304
+ model: 'gpt-5.4',
317
305
  sandboxMode: 'read-only',
318
306
  modelReasoningEffort: 'medium',
319
307
  workingDirectory: workspaceRoot,
@@ -508,28 +496,72 @@ async function runConsultation(model, query, workspaceRoot, role, outputPath, me
508
496
  let tempFile = null;
509
497
  const env = {};
510
498
  let cmd;
499
+ // When true, the query is written to the child's stdin instead of argv.
500
+ // Used for gemini to avoid V8 heap exhaustion on large prompts (#680).
501
+ let stdinPayload = null;
511
502
  if (model === 'gemini') {
512
503
  // Gemini uses GEMINI_SYSTEM_MD env var for role
513
504
  tempFile = path.join(tmpdir(), `codev-role-${Date.now()}.md`);
514
505
  fs.writeFileSync(tempFile, role);
515
506
  env['GEMINI_SYSTEM_MD'] = tempFile;
507
+ // Bugfix #680: gemini-cli v0.37.x crashes on large PR diffs (>500KB) due to
508
+ // V8 old-space exhaustion in the spawned subprocess. Mitigations:
509
+ // 1. Bump heap via NODE_OPTIONS (survives gemini-cli's internal relaunch).
510
+ // 2. Pipe the prompt via stdin instead of argv — avoids ARG_MAX and keeps
511
+ // V8 from holding the full prompt buffer twice.
512
+ env['NODE_OPTIONS'] = [process.env.NODE_OPTIONS ?? '', '--max-old-space-size=8192']
513
+ .join(' ')
514
+ .trim();
515
+ stdinPayload = query;
516
516
  // Use --output-format json to capture token usage/cost in structured output.
517
517
  // Never use --yolo — it allows Gemini to write files (#370).
518
- cmd = [config.cli, '--output-format', 'json', ...config.args, query];
518
+ // No positional query arg: prompt arrives on stdin (triggers non-interactive mode).
519
+ cmd = [config.cli, '--output-format', 'json', ...config.args];
520
+ }
521
+ else if (model === 'hermes') {
522
+ // Hermes does not have a dedicated system prompt flag for single-shot mode.
523
+ // Include role context at the top of the prompt.
524
+ const hermesPrompt = `${role}\n\n---\n\n${query}`;
525
+ // Large inline CLI args can exceed OS ARG_MAX and fail with E2BIG.
526
+ // For very large prompts, write the full prompt to a temp file and pass
527
+ // an instruction that points Hermes at that file.
528
+ if (hermesPrompt.length > CLI_PROMPT_INLINE_MAX_CHARS) {
529
+ tempFile = path.join(tmpdir(), `codev-consult-prompt-${Date.now()}.md`);
530
+ fs.writeFileSync(tempFile, hermesPrompt);
531
+ const instruction = [
532
+ `Read the full consultation prompt from this file: ${tempFile}`,
533
+ 'You have file access. Read files directly from disk to review code.',
534
+ ].join('\n\n');
535
+ cmd = [config.cli, ...config.args, instruction];
536
+ }
537
+ else {
538
+ cmd = [config.cli, ...config.args, hermesPrompt];
539
+ }
519
540
  }
520
541
  else {
521
542
  throw new Error(`Unknown model: ${model}`);
522
543
  }
523
- // Execute with passthrough stdio
524
- // Use 'ignore' for stdin to prevent blocking when spawned as subprocess
544
+ // Execute with passthrough stdio.
545
+ // Use 'ignore' for stdin when no payload — prevents blocking when spawned as subprocess.
546
+ // Use 'pipe' when we need to stream the prompt in (e.g. gemini, see #680).
525
547
  const fullEnv = { ...process.env, ...env };
526
548
  const startTime = Date.now();
549
+ const stdinMode = stdinPayload !== null ? 'pipe' : 'ignore';
527
550
  return new Promise((resolve, reject) => {
528
551
  const proc = spawn(cmd[0], cmd.slice(1), {
529
552
  cwd: workspaceRoot,
530
553
  env: fullEnv,
531
- stdio: ['ignore', 'pipe', 'inherit'],
554
+ stdio: [stdinMode, 'pipe', 'inherit'],
532
555
  });
556
+ if (stdinPayload !== null && proc.stdin) {
557
+ proc.stdin.on('error', (err) => {
558
+ // EPIPE can happen if the child exits before reading all input — not fatal.
559
+ if (err.code !== 'EPIPE') {
560
+ reject(err);
561
+ }
562
+ });
563
+ proc.stdin.end(stdinPayload, 'utf-8');
564
+ }
533
565
  const chunks = [];
534
566
  if (proc.stdout) {
535
567
  proc.stdout.on('data', (chunk) => {
@@ -609,17 +641,31 @@ function getDiffStat(workspaceRoot, ref) {
609
641
  return { stat, files };
610
642
  }
611
643
  /**
612
- * Fetch PR metadata (no diff — that's fetched separately)
644
+ * Fetch PR metadata via forge concept commands (no diff — that's fetched separately).
613
645
  */
614
- function fetchPRData(prNumber) {
615
- console.error(`Fetching PR #${prNumber} data...`);
646
+ function fetchPRData(prId) {
647
+ console.error(`Fetching PR #${prId} data...`);
616
648
  try {
617
- const info = execSync(`gh pr view ${prNumber} --json title,body,state,author,baseRefName,headRefName,additions,deletions`, { encoding: 'utf-8' });
618
- const nameOnly = execSync(`gh pr diff ${prNumber} --name-only`, { encoding: 'utf-8' });
649
+ const prView = executeForgeCommandSync('pr-view', {
650
+ CODEV_PR_NUMBER: prId,
651
+ });
652
+ const info = typeof prView === 'string' ? prView : JSON.stringify(prView);
653
+ const diffResult = executeForgeCommandSync('pr-diff', {
654
+ CODEV_PR_NUMBER: prId,
655
+ CODEV_DIFF_NAME_ONLY: '1',
656
+ }, { raw: true });
657
+ const nameOnly = typeof diffResult === 'string' ? diffResult : '';
619
658
  const changedFiles = nameOnly.trim().split('\n').filter(Boolean);
620
659
  let comments = '(No comments)';
621
660
  try {
622
- comments = execSync(`gh pr view ${prNumber} --comments`, { encoding: 'utf-8' });
661
+ // Fetch PR comments via pr-view concept with CODEV_INCLUDE_COMMENTS flag
662
+ const commentsResult = executeForgeCommandSync('pr-view', {
663
+ CODEV_PR_NUMBER: prId,
664
+ CODEV_INCLUDE_COMMENTS: '1',
665
+ }, { raw: true });
666
+ if (commentsResult && typeof commentsResult === 'string' && commentsResult.trim()) {
667
+ comments = commentsResult;
668
+ }
623
669
  }
624
670
  catch {
625
671
  // No comments or error fetching
@@ -631,44 +677,53 @@ function fetchPRData(prNumber) {
631
677
  }
632
678
  }
633
679
  /**
634
- * Fetch the full PR diff via gh pr diff
680
+ * Fetch the full PR diff via the pr-diff forge concept command.
635
681
  */
636
- function fetchPRDiff(prNumber) {
682
+ function fetchPRDiff(prId) {
637
683
  try {
638
- return execSync(`gh pr diff ${prNumber}`, { encoding: 'utf-8', maxBuffer: 10 * 1024 * 1024 });
684
+ const result = executeForgeCommandSync('pr-diff', {
685
+ CODEV_PR_NUMBER: prId,
686
+ }, { raw: true });
687
+ return typeof result === 'string' ? result : '';
639
688
  }
640
689
  catch (err) {
641
- throw new Error(`Failed to fetch PR diff for #${prNumber}: ${err}`);
690
+ throw new Error(`Failed to fetch PR diff for #${prId}: ${err}`);
642
691
  }
643
692
  }
644
693
  /**
645
- * Build query for PR review.
646
- * Includes full PR diff + file list; model reads surrounding context from disk.
694
+ * Compose the PR review prompt text from already-fetched pieces.
695
+ *
696
+ * Split from the I/O wrapper so it can be tested without mocking the forge
697
+ * layer. Points the model at a temp-file path rather than inlining the diff:
698
+ * large (~800KB+) inlined diffs blow past the gemini-cli JSON path and bloat
699
+ * prompts for all models (#684).
647
700
  */
648
- function buildPRQuery(prNumber) {
649
- const prData = fetchPRData(prNumber);
650
- const diff = fetchPRDiff(prNumber);
651
- const fileList = prData.changedFiles.map(f => `- ${f}`).join('\n');
652
- return `Review Pull Request #${prNumber}
701
+ function composePRQueryText(params) {
702
+ const { prId, info, changedFiles, comments, diffPath, diffBytes, diffLines } = params;
703
+ const fileList = changedFiles.map(f => `- ${f}`).join('\n');
704
+ const diffSizeKb = (diffBytes / 1024).toFixed(1);
705
+ return `Review Pull Request #${prId}
653
706
 
654
707
  ## PR Info
655
708
  \`\`\`json
656
- ${prData.info}
709
+ ${info}
657
710
  \`\`\`
658
711
 
659
- ## Changed Files
712
+ ## Changed Files (${changedFiles.length})
660
713
  ${fileList}
661
714
 
662
715
  ## PR Diff
663
- \`\`\`diff
664
- ${diff}
665
- \`\`\`
716
+ The full PR diff is **not inlined** in this prompt to keep the payload small. It has been written to this path on disk:
717
+
718
+ **Diff file**: \`${diffPath}\`
719
+ **Size**: ${diffSizeKb} KB (${diffBytes} bytes, ${diffLines} lines)
666
720
 
667
721
  ## How to Review
668
- Review the PR diff above for the changes. You also have **full filesystem access** — read files from disk for surrounding context beyond what the diff shows.
722
+ 1. **Read the diff file** from \`${diffPath}\` to see the exact changes (use the Read tool or \`cat\`).
723
+ 2. You have **full filesystem access** — read any project files from disk for surrounding context beyond what the diff shows.
669
724
 
670
725
  ## Comments
671
- ${prData.comments}
726
+ ${comments}
672
727
 
673
728
  ---
674
729
 
@@ -690,25 +745,58 @@ CONFIDENCE: [HIGH | MEDIUM | LOW]
690
745
 
691
746
  KEY_ISSUES: [List of critical issues if any, or "None"]`;
692
747
  }
748
+ /**
749
+ * Build query for PR review.
750
+ *
751
+ * Writes the full PR diff to a temp file and points the model at the path
752
+ * instead of inlining it. Inlining large diffs (~800KB+) caused gemini-cli's
753
+ * JSON output path to fail with "Unexpected end of JSON input" in 0.3s
754
+ * (#684); it also bloats prompts for Claude/Codex. This mirrors the pattern
755
+ * used by buildImplQuery.
756
+ *
757
+ * The temp file is left in place for the model to read during consultation.
758
+ * OS /tmp rotation handles cleanup.
759
+ */
760
+ function buildPRQuery(prId) {
761
+ const prData = fetchPRData(prId);
762
+ const diff = fetchPRDiff(prId);
763
+ // Private-per-user dir to avoid world-readable /tmp diffs + symlink/clobber
764
+ // races: mkdtempSync creates a fresh dir owned by us; writeFileSync with
765
+ // flag 'wx' refuses to follow a symlink or overwrite an existing file.
766
+ const diffDir = fs.mkdtempSync(path.join(tmpdir(), 'codev-pr-'));
767
+ const diffPath = path.join(diffDir, `pr-${prId}.diff`);
768
+ fs.writeFileSync(diffPath, diff, { encoding: 'utf-8', mode: 0o600, flag: 'wx' });
769
+ const diffBytes = Buffer.byteLength(diff, 'utf-8');
770
+ const diffLines = diff ? diff.split('\n').length : 0;
771
+ return composePRQueryText({
772
+ prId,
773
+ info: prData.info,
774
+ changedFiles: prData.changedFiles,
775
+ comments: prData.comments,
776
+ diffPath,
777
+ diffBytes,
778
+ diffLines,
779
+ });
780
+ }
693
781
  /**
694
782
  * Build query for spec review
695
783
  */
696
- function buildSpecQuery(specPath, planPath) {
697
- let query = `Review Specification: ${path.basename(specPath)}
784
+ function buildSpecQuery(spec, plan) {
785
+ let query = `Review Specification: ${spec.label}
786
+
787
+ ## Specification
788
+
789
+ ${spec.content}
698
790
 
699
- Please read and review this specification:
700
- - Spec file: ${specPath}
701
791
  `;
702
- if (planPath) {
703
- query += `- Plan file: ${planPath}\n`;
704
- }
705
- query += `
706
- ## How to Review
707
- **Read the files listed above directly from disk.** You have full filesystem access.
708
- Do NOT rely on \`git diff\` or \`git log\` to review content — diffs may be truncated or miss uncommitted work.
709
- Open the spec file, read it in full, and evaluate it directly.
792
+ if (plan) {
793
+ query += `## Plan
710
794
 
711
- Please review:
795
+ ${plan.content}
796
+
797
+ `;
798
+ }
799
+ query += `Please review:
712
800
  1. Clarity and completeness of requirements
713
801
  2. Technical feasibility
714
802
  3. Edge cases and error scenarios
@@ -731,7 +819,7 @@ KEY_ISSUES: [List of critical issues if any, or "None"]`;
731
819
  * Build query for implementation review.
732
820
  * Accepts spec/plan paths and optional diff reference override.
733
821
  */
734
- function buildImplQuery(workspaceRoot, specPath, planPath, planPhase, diffRef) {
822
+ function buildImplQuery(workspaceRoot, spec, plan, planPhase, diffRef) {
735
823
  // Get compact diff summary
736
824
  let diffStat = '';
737
825
  let changedFiles = [];
@@ -748,15 +836,15 @@ function buildImplQuery(workspaceRoot, specPath, planPath, planPhase, diffRef) {
748
836
  if (planPhase) {
749
837
  query += ` — Phase: ${planPhase}`;
750
838
  }
751
- query += `\n\n## Context Files\n`;
752
- if (specPath) {
753
- query += `- Spec: ${specPath}\n`;
839
+ query += '\n\n';
840
+ if (spec) {
841
+ query += `## Specification\n\n${spec.content}\n\n`;
754
842
  }
755
- if (planPath) {
756
- query += `- Plan: ${planPath}\n`;
843
+ if (plan) {
844
+ query += `## Plan\n\n${plan.content}\n\n`;
757
845
  }
758
846
  if (planPhase) {
759
- query += `\n## REVIEW SCOPE — CURRENT PLAN PHASE ONLY\n`;
847
+ query += `## REVIEW SCOPE — CURRENT PLAN PHASE ONLY\n`;
760
848
  query += `You are reviewing **plan phase "${planPhase}" ONLY**.\n`;
761
849
  query += `Read the plan, find the section for "${planPhase}", and scope your review to ONLY the work described in that phase.\n\n`;
762
850
  query += `**DO NOT** request changes for work that belongs to other plan phases.\n`;
@@ -774,7 +862,7 @@ function buildImplQuery(workspaceRoot, specPath, planPath, planPhase, diffRef) {
774
862
  query += `Do NOT rely on git diffs to determine the current state of code — diffs miss uncommitted changes in worktrees.\n`;
775
863
  }
776
864
  else {
777
- query += `\n## Instructions\n\nRead the spec and plan files above, then explore the filesystem to find and review the implementation changes.\n`;
865
+ query += `\n## Instructions\n\nExplore the filesystem to find and review the implementation changes.\n`;
778
866
  }
779
867
  query += `
780
868
  Please review:
@@ -798,22 +886,22 @@ KEY_ISSUES: [List of critical issues if any, or "None"]`;
798
886
  /**
799
887
  * Build query for plan review
800
888
  */
801
- function buildPlanQuery(planPath, specPath) {
802
- let query = `Review Implementation Plan: ${path.basename(planPath)}
889
+ function buildPlanQuery(plan, spec) {
890
+ let query = `Review Implementation Plan: ${plan.label}
891
+
892
+ ## Plan
893
+
894
+ ${plan.content}
803
895
 
804
- Please read and review this implementation plan:
805
- - Plan file: ${planPath}
806
896
  `;
807
- if (specPath) {
808
- query += `- Spec file: ${specPath} (for context)\n`;
809
- }
810
- query += `
811
- ## How to Review
812
- **Read the files listed above directly from disk.** You have full filesystem access.
813
- Do NOT rely on \`git diff\` or \`git log\` to review content — diffs may be truncated or miss uncommitted work.
814
- Open the plan file (and spec if provided), read them in full, and evaluate the plan directly.
897
+ if (spec) {
898
+ query += `## Specification (for context)
815
899
 
816
- Please review:
900
+ ${spec.content}
901
+
902
+ `;
903
+ }
904
+ query += `Please review:
817
905
  1. Alignment with specification requirements
818
906
  2. Implementation approach and architecture
819
907
  3. Task breakdown and ordering
@@ -836,7 +924,7 @@ KEY_ISSUES: [List of critical issues if any, or "None"]`;
836
924
  * Build query for phase-scoped review.
837
925
  * Uses git show HEAD for the phase's atomic commit diff.
838
926
  */
839
- function buildPhaseQuery(workspaceRoot, planPhase, specPath, planPath) {
927
+ function buildPhaseQuery(workspaceRoot, planPhase, spec, plan) {
840
928
  let phaseDiff = '';
841
929
  try {
842
930
  phaseDiff = execSync('git show HEAD', { cwd: workspaceRoot, encoding: 'utf-8', maxBuffer: 10 * 1024 * 1024 });
@@ -844,11 +932,11 @@ function buildPhaseQuery(workspaceRoot, planPhase, specPath, planPath) {
844
932
  catch {
845
933
  // If git show fails, reviewer explores filesystem
846
934
  }
847
- let query = `Review Phase Implementation: "${planPhase}"\n\n## Context Files\n`;
848
- if (specPath)
849
- query += `- Spec: ${specPath}\n`;
850
- if (planPath)
851
- query += `- Plan: ${planPath}\n`;
935
+ let query = `Review Phase Implementation: "${planPhase}"\n\n`;
936
+ if (spec)
937
+ query += `## Specification\n\n${spec.content}\n\n`;
938
+ if (plan)
939
+ query += `## Plan\n\n${plan.content}\n\n`;
852
940
  query += `
853
941
  ## REVIEW SCOPE — CURRENT PLAN PHASE ONLY
854
942
  You are reviewing **plan phase "${planPhase}" ONLY**.
@@ -885,92 +973,94 @@ KEY_ISSUES: [List of critical issues if any, or "None"]`;
885
973
  return query;
886
974
  }
887
975
  /**
888
- * Find PR number for the current branch
976
+ * Find PR number for the current branch via pr-search forge concept.
889
977
  */
890
978
  function findPRForCurrentBranch(workspaceRoot) {
891
979
  const branchName = execSync('git branch --show-current', { cwd: workspaceRoot, encoding: 'utf-8' }).trim();
892
- const prJson = execSync(`gh pr list --head "${branchName}" --json number --jq '.[0].number'`, { cwd: workspaceRoot, encoding: 'utf-8' }).trim();
893
- if (!prJson) {
894
- throw new Error(`No PR found for branch: ${branchName}`);
895
- }
896
- const prNumber = parseInt(prJson, 10);
897
- if (isNaN(prNumber)) {
980
+ const result = executeForgeCommandSync('pr-search', {
981
+ CODEV_SEARCH_QUERY: `head:${branchName}`,
982
+ }, { cwd: workspaceRoot });
983
+ const prs = Array.isArray(result) ? result : [];
984
+ if (prs.length === 0 || !prs[0]?.number) {
898
985
  throw new Error(`No PR found for branch: ${branchName}`);
899
986
  }
900
- return prNumber;
987
+ return String(prs[0].number);
901
988
  }
902
989
  /**
903
- * Find PR number for a given issue number (architect mode)
990
+ * Find PR number for a given issue number (architect mode) via pr-search forge concept.
904
991
  */
905
- function findPRForIssue(workspaceRoot, issueNumber) {
906
- const prJson = execSync(`gh pr list --search "${issueNumber}" --json number,headRefName --jq '.[0]'`, { cwd: workspaceRoot, encoding: 'utf-8' }).trim();
907
- if (!prJson || prJson === 'null') {
908
- throw new Error(`No PR found for issue #${issueNumber}`);
909
- }
910
- return JSON.parse(prJson);
992
+ function findPRForIssue(workspaceRoot, issueId) {
993
+ const result = executeForgeCommandSync('pr-search', {
994
+ CODEV_SEARCH_QUERY: issueId,
995
+ }, { cwd: workspaceRoot });
996
+ const prs = Array.isArray(result) ? result : [];
997
+ if (prs.length === 0 || !prs[0]?.number) {
998
+ throw new Error(`No PR found for issue #${issueId}`);
999
+ }
1000
+ return prs[0];
911
1001
  }
912
1002
  /**
913
1003
  * Resolve query for builder context (auto-detected from porch state)
914
1004
  */
915
1005
  function resolveBuilderQuery(workspaceRoot, type, options) {
916
1006
  const projectState = getBuilderProjectState(workspaceRoot, options.projectId);
917
- const projectNumber = parseInt(projectState.id, 10);
1007
+ const projectId = projectState.id;
918
1008
  switch (type) {
919
1009
  case 'spec': {
920
- const specPath = findSpec(workspaceRoot, projectNumber);
921
- if (!specPath)
922
- throw new Error(`Spec ${projectState.id} not found in codev/specs/`);
923
- const planPath = findPlan(workspaceRoot, projectNumber);
924
- console.error(`Spec: ${specPath}`);
925
- if (planPath)
926
- console.error(`Plan: ${planPath}`);
927
- return buildSpecQuery(specPath, planPath);
1010
+ const spec = findSpecContent(workspaceRoot, projectId);
1011
+ if (!spec)
1012
+ throw new Error(`Spec ${projectId} not found`);
1013
+ const plan = findPlanContent(workspaceRoot, projectId);
1014
+ console.error(`Spec: ${spec.label}`);
1015
+ if (plan)
1016
+ console.error(`Plan: ${plan.label}`);
1017
+ return buildSpecQuery(spec, plan);
928
1018
  }
929
1019
  case 'plan': {
930
- const planPath = findPlan(workspaceRoot, projectNumber);
931
- if (!planPath)
932
- throw new Error(`Plan ${projectState.id} not found in codev/plans/`);
933
- const specPath = findSpec(workspaceRoot, projectNumber);
934
- console.error(`Plan: ${planPath}`);
935
- if (specPath)
936
- console.error(`Spec: ${specPath}`);
937
- return buildPlanQuery(planPath, specPath);
1020
+ const plan = findPlanContent(workspaceRoot, projectId);
1021
+ if (!plan)
1022
+ throw new Error(`Plan ${projectId} not found`);
1023
+ const spec = findSpecContent(workspaceRoot, projectId);
1024
+ console.error(`Plan: ${plan.label}`);
1025
+ if (spec)
1026
+ console.error(`Spec: ${spec.label}`);
1027
+ return buildPlanQuery(plan, spec);
938
1028
  }
939
1029
  case 'impl': {
940
- const specPath = findSpec(workspaceRoot, projectNumber);
941
- const planPath = findPlan(workspaceRoot, projectNumber);
942
- console.error(`Project: ${projectState.id}`);
943
- if (specPath)
944
- console.error(`Spec: ${specPath}`);
945
- if (planPath)
946
- console.error(`Plan: ${planPath}`);
1030
+ const spec = findSpecContent(workspaceRoot, projectId);
1031
+ const plan = findPlanContent(workspaceRoot, projectId);
1032
+ console.error(`Project: ${projectId}`);
1033
+ if (spec)
1034
+ console.error(`Spec: ${spec.label}`);
1035
+ if (plan)
1036
+ console.error(`Plan: ${plan.label}`);
947
1037
  if (options.planPhase)
948
1038
  console.error(`Plan phase: ${options.planPhase}`);
949
- return buildImplQuery(workspaceRoot, specPath, planPath, options.planPhase);
1039
+ return buildImplQuery(workspaceRoot, spec, plan, options.planPhase);
950
1040
  }
951
1041
  case 'pr': {
952
- const prNumber = findPRForCurrentBranch(workspaceRoot);
953
- console.error(`PR: #${prNumber}`);
954
- return buildPRQuery(prNumber);
1042
+ const prId = findPRForCurrentBranch(workspaceRoot);
1043
+ console.error(`PR: #${prId}`);
1044
+ return buildPRQuery(prId);
955
1045
  }
956
1046
  case 'phase': {
957
1047
  const currentPhase = options.planPhase ?? projectState.currentPlanPhase;
958
1048
  if (!currentPhase) {
959
1049
  throw new Error('No current plan phase detected. Use --plan-phase to specify.');
960
1050
  }
961
- const specPath = findSpec(workspaceRoot, projectNumber);
962
- const planPath = findPlan(workspaceRoot, projectNumber);
1051
+ const spec = findSpecContent(workspaceRoot, projectId);
1052
+ const plan = findPlanContent(workspaceRoot, projectId);
963
1053
  console.error(`Phase: ${currentPhase}`);
964
- if (specPath)
965
- console.error(`Spec: ${specPath}`);
966
- if (planPath)
967
- console.error(`Plan: ${planPath}`);
968
- return buildPhaseQuery(workspaceRoot, currentPhase, specPath, planPath);
1054
+ if (spec)
1055
+ console.error(`Spec: ${spec.label}`);
1056
+ if (plan)
1057
+ console.error(`Plan: ${plan.label}`);
1058
+ return buildPhaseQuery(workspaceRoot, currentPhase, spec, plan);
969
1059
  }
970
1060
  case 'integration': {
971
- const prNumber = findPRForCurrentBranch(workspaceRoot);
972
- console.error(`PR: #${prNumber} (integration review)`);
973
- return buildPRQuery(prNumber);
1061
+ const prId = findPRForCurrentBranch(workspaceRoot);
1062
+ console.error(`PR: #${prId} (integration review)`);
1063
+ return buildPRQuery(prId);
974
1064
  }
975
1065
  default:
976
1066
  throw new Error(`Unknown review type: ${type}\nValid types: spec, plan, impl, pr, phase, integration`);
@@ -987,33 +1077,30 @@ function resolveArchitectQuery(workspaceRoot, type, options) {
987
1077
  throw new Error(`--issue is required from architect context for --type ${type}.\n` +
988
1078
  `Example: consult -m gemini --protocol spir --type ${type} --issue 42`);
989
1079
  }
990
- const issueNumber = parseInt(options.issue, 10);
991
- if (isNaN(issueNumber)) {
992
- throw new Error(`Invalid issue number: ${options.issue}`);
993
- }
1080
+ const issueId = options.issue;
994
1081
  switch (type) {
995
1082
  case 'spec': {
996
- const specPath = findSpec(workspaceRoot, issueNumber);
997
- if (!specPath)
998
- throw new Error(`Spec ${issueNumber} not found in codev/specs/`);
999
- const planPath = findPlan(workspaceRoot, issueNumber);
1000
- console.error(`Spec: ${specPath}`);
1001
- if (planPath)
1002
- console.error(`Plan: ${planPath}`);
1003
- return buildSpecQuery(specPath, planPath);
1083
+ const spec = findSpecContent(workspaceRoot, issueId);
1084
+ if (!spec)
1085
+ throw new Error(`Spec ${issueId} not found`);
1086
+ const plan = findPlanContent(workspaceRoot, issueId);
1087
+ console.error(`Spec: ${spec.label}`);
1088
+ if (plan)
1089
+ console.error(`Plan: ${plan.label}`);
1090
+ return buildSpecQuery(spec, plan);
1004
1091
  }
1005
1092
  case 'plan': {
1006
- const planPath = findPlan(workspaceRoot, issueNumber);
1007
- if (!planPath)
1008
- throw new Error(`Plan ${issueNumber} not found in codev/plans/`);
1009
- const specPath = findSpec(workspaceRoot, issueNumber);
1010
- console.error(`Plan: ${planPath}`);
1011
- if (specPath)
1012
- console.error(`Spec: ${specPath}`);
1013
- return buildPlanQuery(planPath, specPath);
1093
+ const plan = findPlanContent(workspaceRoot, issueId);
1094
+ if (!plan)
1095
+ throw new Error(`Plan ${issueId} not found`);
1096
+ const spec = findSpecContent(workspaceRoot, issueId);
1097
+ console.error(`Plan: ${plan.label}`);
1098
+ if (spec)
1099
+ console.error(`Spec: ${spec.label}`);
1100
+ return buildPlanQuery(plan, spec);
1014
1101
  }
1015
1102
  case 'impl': {
1016
- const pr = findPRForIssue(workspaceRoot, issueNumber);
1103
+ const pr = findPRForIssue(workspaceRoot, issueId);
1017
1104
  // Fetch the branch and diff from merge-base
1018
1105
  try {
1019
1106
  execSync(`git fetch origin ${pr.headRefName}`, { cwd: workspaceRoot, stdio: 'pipe' });
@@ -1022,24 +1109,24 @@ function resolveArchitectQuery(workspaceRoot, type, options) {
1022
1109
  // May already be fetched
1023
1110
  }
1024
1111
  const mergeBase = execSync(`git merge-base main origin/${pr.headRefName}`, { cwd: workspaceRoot, encoding: 'utf-8' }).trim();
1025
- const specPath = findSpec(workspaceRoot, issueNumber);
1026
- const planPath = findPlan(workspaceRoot, issueNumber);
1027
- console.error(`Project: ${issueNumber} (PR #${pr.number}, branch: ${pr.headRefName})`);
1028
- if (specPath)
1029
- console.error(`Spec: ${specPath}`);
1030
- if (planPath)
1031
- console.error(`Plan: ${planPath}`);
1032
- return buildImplQuery(workspaceRoot, specPath, planPath, options.planPhase, `${mergeBase}..origin/${pr.headRefName}`);
1112
+ const spec = findSpecContent(workspaceRoot, issueId);
1113
+ const plan = findPlanContent(workspaceRoot, issueId);
1114
+ console.error(`Project: ${issueId} (PR #${pr.number}, branch: ${pr.headRefName})`);
1115
+ if (spec)
1116
+ console.error(`Spec: ${spec.label}`);
1117
+ if (plan)
1118
+ console.error(`Plan: ${plan.label}`);
1119
+ return buildImplQuery(workspaceRoot, spec, plan, options.planPhase, `${mergeBase}..origin/${pr.headRefName}`);
1033
1120
  }
1034
1121
  case 'pr': {
1035
- const pr = findPRForIssue(workspaceRoot, issueNumber);
1122
+ const pr = findPRForIssue(workspaceRoot, issueId);
1036
1123
  console.error(`PR: #${pr.number}`);
1037
- return buildPRQuery(pr.number);
1124
+ return buildPRQuery(String(pr.number));
1038
1125
  }
1039
1126
  case 'integration': {
1040
- const pr = findPRForIssue(workspaceRoot, issueNumber);
1127
+ const pr = findPRForIssue(workspaceRoot, issueId);
1041
1128
  console.error(`PR: #${pr.number} (integration review)`);
1042
- return buildPRQuery(pr.number);
1129
+ return buildPRQuery(String(pr.number));
1043
1130
  }
1044
1131
  default:
1045
1132
  throw new Error(`Unknown review type: ${type}\nValid types: spec, plan, impl, pr, phase, integration`);
@@ -1144,7 +1231,7 @@ export async function consult(options) {
1144
1231
  }
1145
1232
  }
1146
1233
  // Add file access instruction for Gemini
1147
- if (model === 'gemini') {
1234
+ if (model === 'gemini' || model === 'hermes') {
1148
1235
  query += '\n\nYou have file access. Read files directly from disk to review code.';
1149
1236
  }
1150
1237
  // Show the query/prompt being sent
@@ -1182,5 +1269,5 @@ export async function consult(options) {
1182
1269
  await runConsultation(model, query, workspaceRoot, role, outputPath, metricsCtx, isGeneralMode);
1183
1270
  }
1184
1271
  // Exported for testing
1185
- export { getDiffStat as _getDiffStat, buildSpecQuery as _buildSpecQuery, buildPlanQuery as _buildPlanQuery, computePersistentOutputPath as _computePersistentOutputPath, };
1272
+ export { getDiffStat as _getDiffStat, buildSpecQuery as _buildSpecQuery, buildPlanQuery as _buildPlanQuery, buildPRQuery as _buildPRQuery, composePRQueryText as _composePRQueryText, computePersistentOutputPath as _computePersistentOutputPath, };
1186
1273
  //# sourceMappingURL=index.js.map