@cluesmith/codev 3.0.0-rc.0 → 3.0.0-rc.10

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 (331) 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 +8 -0
  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 +21 -13
  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 +357 -49
  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 +7 -1
  63. package/dist/agent-farm/commands/team.d.ts.map +1 -1
  64. package/dist/agent-farm/commands/team.js +36 -4
  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.d.ts +1 -0
  70. package/dist/agent-farm/commands/tower.d.ts.map +1 -1
  71. package/dist/agent-farm/commands/tower.js +54 -3
  72. package/dist/agent-farm/commands/tower.js.map +1 -1
  73. package/dist/agent-farm/lib/cloud-config.js +2 -2
  74. package/dist/agent-farm/lib/cloud-config.js.map +1 -1
  75. package/dist/agent-farm/lib/tower-client.d.ts +6 -230
  76. package/dist/agent-farm/lib/tower-client.d.ts.map +1 -1
  77. package/dist/agent-farm/lib/tower-client.js +6 -293
  78. package/dist/agent-farm/lib/tower-client.js.map +1 -1
  79. package/dist/agent-farm/lib/tunnel-client.js +1 -1
  80. package/dist/agent-farm/lib/tunnel-client.js.map +1 -1
  81. package/dist/agent-farm/servers/message-write.d.ts +24 -0
  82. package/dist/agent-farm/servers/message-write.d.ts.map +1 -0
  83. package/dist/agent-farm/servers/message-write.js +63 -0
  84. package/dist/agent-farm/servers/message-write.js.map +1 -0
  85. package/dist/agent-farm/servers/overview.d.ts +10 -8
  86. package/dist/agent-farm/servers/overview.d.ts.map +1 -1
  87. package/dist/agent-farm/servers/overview.js +78 -38
  88. package/dist/agent-farm/servers/overview.js.map +1 -1
  89. package/dist/agent-farm/servers/send-buffer.d.ts +4 -3
  90. package/dist/agent-farm/servers/send-buffer.d.ts.map +1 -1
  91. package/dist/agent-farm/servers/send-buffer.js +7 -4
  92. package/dist/agent-farm/servers/send-buffer.js.map +1 -1
  93. package/dist/agent-farm/servers/tower-cron-parser.js +1 -1
  94. package/dist/agent-farm/servers/tower-cron-parser.js.map +1 -1
  95. package/dist/agent-farm/servers/tower-cron.d.ts.map +1 -1
  96. package/dist/agent-farm/servers/tower-cron.js +4 -5
  97. package/dist/agent-farm/servers/tower-cron.js.map +1 -1
  98. package/dist/agent-farm/servers/tower-instances.d.ts.map +1 -1
  99. package/dist/agent-farm/servers/tower-instances.js +24 -15
  100. package/dist/agent-farm/servers/tower-instances.js.map +1 -1
  101. package/dist/agent-farm/servers/tower-routes.d.ts.map +1 -1
  102. package/dist/agent-farm/servers/tower-routes.js +9 -16
  103. package/dist/agent-farm/servers/tower-routes.js.map +1 -1
  104. package/dist/agent-farm/servers/tower-server.js +15 -1
  105. package/dist/agent-farm/servers/tower-server.js.map +1 -1
  106. package/dist/agent-farm/servers/tower-terminals.d.ts +2 -2
  107. package/dist/agent-farm/servers/tower-terminals.d.ts.map +1 -1
  108. package/dist/agent-farm/servers/tower-terminals.js +64 -36
  109. package/dist/agent-farm/servers/tower-terminals.js.map +1 -1
  110. package/dist/agent-farm/servers/tower-tunnel.js +2 -2
  111. package/dist/agent-farm/servers/tower-tunnel.js.map +1 -1
  112. package/dist/agent-farm/servers/tower-utils.d.ts +9 -6
  113. package/dist/agent-farm/servers/tower-utils.d.ts.map +1 -1
  114. package/dist/agent-farm/servers/tower-utils.js +13 -7
  115. package/dist/agent-farm/servers/tower-utils.js.map +1 -1
  116. package/dist/agent-farm/types.d.ts +13 -0
  117. package/dist/agent-farm/types.d.ts.map +1 -1
  118. package/dist/agent-farm/utils/config.d.ts +11 -0
  119. package/dist/agent-farm/utils/config.d.ts.map +1 -1
  120. package/dist/agent-farm/utils/config.js +39 -17
  121. package/dist/agent-farm/utils/config.js.map +1 -1
  122. package/dist/agent-farm/utils/harness.d.ts +85 -0
  123. package/dist/agent-farm/utils/harness.d.ts.map +1 -0
  124. package/dist/agent-farm/utils/harness.js +212 -0
  125. package/dist/agent-farm/utils/harness.js.map +1 -0
  126. package/dist/agent-farm/utils/roles.d.ts +11 -4
  127. package/dist/agent-farm/utils/roles.d.ts.map +1 -1
  128. package/dist/agent-farm/utils/roles.js +21 -6
  129. package/dist/agent-farm/utils/roles.js.map +1 -1
  130. package/dist/cli.d.ts +1 -1
  131. package/dist/cli.d.ts.map +1 -1
  132. package/dist/cli.js +118 -5
  133. package/dist/cli.js.map +1 -1
  134. package/dist/commands/adopt.d.ts.map +1 -1
  135. package/dist/commands/adopt.js +16 -45
  136. package/dist/commands/adopt.js.map +1 -1
  137. package/dist/commands/consult/index.d.ts +6 -2
  138. package/dist/commands/consult/index.d.ts.map +1 -1
  139. package/dist/commands/consult/index.js +231 -183
  140. package/dist/commands/consult/index.js.map +1 -1
  141. package/dist/commands/doctor.d.ts.map +1 -1
  142. package/dist/commands/doctor.js +121 -31
  143. package/dist/commands/doctor.js.map +1 -1
  144. package/dist/commands/init.d.ts.map +1 -1
  145. package/dist/commands/init.js +13 -43
  146. package/dist/commands/init.js.map +1 -1
  147. package/dist/commands/porch/artifacts.d.ts +69 -0
  148. package/dist/commands/porch/artifacts.d.ts.map +1 -0
  149. package/dist/commands/porch/artifacts.js +368 -0
  150. package/dist/commands/porch/artifacts.js.map +1 -0
  151. package/dist/commands/porch/checks.d.ts +14 -3
  152. package/dist/commands/porch/checks.d.ts.map +1 -1
  153. package/dist/commands/porch/checks.js +150 -4
  154. package/dist/commands/porch/checks.js.map +1 -1
  155. package/dist/commands/porch/config.d.ts +6 -9
  156. package/dist/commands/porch/config.d.ts.map +1 -1
  157. package/dist/commands/porch/config.js +12 -37
  158. package/dist/commands/porch/config.js.map +1 -1
  159. package/dist/commands/porch/index.d.ts +12 -7
  160. package/dist/commands/porch/index.d.ts.map +1 -1
  161. package/dist/commands/porch/index.js +273 -82
  162. package/dist/commands/porch/index.js.map +1 -1
  163. package/dist/commands/porch/next.d.ts.map +1 -1
  164. package/dist/commands/porch/next.js +142 -75
  165. package/dist/commands/porch/next.js.map +1 -1
  166. package/dist/commands/porch/notify.d.ts +2 -2
  167. package/dist/commands/porch/notify.js +5 -5
  168. package/dist/commands/porch/notify.js.map +1 -1
  169. package/dist/commands/porch/prompts.d.ts +2 -1
  170. package/dist/commands/porch/prompts.d.ts.map +1 -1
  171. package/dist/commands/porch/prompts.js +34 -38
  172. package/dist/commands/porch/prompts.js.map +1 -1
  173. package/dist/commands/porch/protocol.d.ts +4 -3
  174. package/dist/commands/porch/protocol.d.ts.map +1 -1
  175. package/dist/commands/porch/protocol.js +21 -21
  176. package/dist/commands/porch/protocol.js.map +1 -1
  177. package/dist/commands/porch/state.d.ts +27 -2
  178. package/dist/commands/porch/state.d.ts.map +1 -1
  179. package/dist/commands/porch/state.js +100 -9
  180. package/dist/commands/porch/state.js.map +1 -1
  181. package/dist/commands/porch/types.d.ts +10 -2
  182. package/dist/commands/porch/types.d.ts.map +1 -1
  183. package/dist/commands/sync.d.ts +26 -0
  184. package/dist/commands/sync.d.ts.map +1 -0
  185. package/dist/commands/sync.js +207 -0
  186. package/dist/commands/sync.js.map +1 -0
  187. package/dist/commands/update.d.ts +15 -2
  188. package/dist/commands/update.d.ts.map +1 -1
  189. package/dist/commands/update.js +159 -202
  190. package/dist/commands/update.js.map +1 -1
  191. package/dist/lib/config.d.ts +96 -0
  192. package/dist/lib/config.d.ts.map +1 -0
  193. package/dist/lib/config.js +165 -0
  194. package/dist/lib/config.js.map +1 -0
  195. package/dist/lib/forge-contracts.d.ts +100 -0
  196. package/dist/lib/forge-contracts.d.ts.map +1 -0
  197. package/dist/lib/forge-contracts.js +35 -0
  198. package/dist/lib/forge-contracts.js.map +1 -0
  199. package/dist/lib/forge.d.ts +102 -0
  200. package/dist/lib/forge.d.ts.map +1 -0
  201. package/dist/lib/forge.js +376 -0
  202. package/dist/lib/forge.js.map +1 -0
  203. package/dist/lib/github.d.ts +68 -60
  204. package/dist/lib/github.d.ts.map +1 -1
  205. package/dist/lib/github.js +160 -169
  206. package/dist/lib/github.js.map +1 -1
  207. package/dist/lib/skeleton.d.ts +15 -5
  208. package/dist/lib/skeleton.d.ts.map +1 -1
  209. package/dist/lib/skeleton.js +82 -7
  210. package/dist/lib/skeleton.js.map +1 -1
  211. package/dist/lib/team-github.d.ts +7 -3
  212. package/dist/lib/team-github.d.ts.map +1 -1
  213. package/dist/lib/team-github.js +29 -19
  214. package/dist/lib/team-github.js.map +1 -1
  215. package/dist/lib/team.d.ts.map +1 -1
  216. package/dist/lib/team.js +4 -1
  217. package/dist/lib/team.js.map +1 -1
  218. package/dist/terminal/pty-manager.d.ts.map +1 -1
  219. package/dist/terminal/pty-manager.js +2 -0
  220. package/dist/terminal/pty-manager.js.map +1 -1
  221. package/dist/terminal/ws-protocol.d.ts +10 -8
  222. package/dist/terminal/ws-protocol.d.ts.map +1 -1
  223. package/dist/terminal/ws-protocol.js +3 -0
  224. package/dist/terminal/ws-protocol.js.map +1 -1
  225. package/package.json +28 -24
  226. package/scripts/forge/gitea/auth-status.sh +3 -0
  227. package/scripts/forge/gitea/issue-comment.sh +3 -0
  228. package/scripts/forge/gitea/issue-list.sh +3 -0
  229. package/scripts/forge/gitea/issue-view.sh +3 -0
  230. package/scripts/forge/gitea/pr-exists.sh +6 -0
  231. package/scripts/forge/gitea/pr-list.sh +3 -0
  232. package/scripts/forge/gitea/pr-merge.sh +3 -0
  233. package/scripts/forge/gitea/pr-view.sh +3 -0
  234. package/scripts/forge/gitea/recently-closed.sh +3 -0
  235. package/scripts/forge/gitea/recently-merged.sh +3 -0
  236. package/scripts/forge/gitea/repo-archive.sh +11 -0
  237. package/scripts/forge/gitea/user-identity.sh +3 -0
  238. package/scripts/forge/github/auth-status.sh +4 -0
  239. package/scripts/forge/github/issue-comment.sh +5 -0
  240. package/scripts/forge/github/issue-list.sh +4 -0
  241. package/scripts/forge/github/issue-view.sh +5 -0
  242. package/scripts/forge/github/on-it-timestamps.sh +8 -0
  243. package/scripts/forge/github/pr-diff.sh +10 -0
  244. package/scripts/forge/github/pr-exists.sh +7 -0
  245. package/scripts/forge/github/pr-list.sh +4 -0
  246. package/scripts/forge/github/pr-merge.sh +5 -0
  247. package/scripts/forge/github/pr-search.sh +5 -0
  248. package/scripts/forge/github/pr-view.sh +11 -0
  249. package/scripts/forge/github/recently-closed.sh +15 -0
  250. package/scripts/forge/github/recently-merged.sh +15 -0
  251. package/scripts/forge/github/repo-archive.sh +10 -0
  252. package/scripts/forge/github/team-activity.sh +5 -0
  253. package/scripts/forge/github/user-identity.sh +4 -0
  254. package/scripts/forge/gitlab/auth-status.sh +3 -0
  255. package/scripts/forge/gitlab/issue-comment.sh +3 -0
  256. package/scripts/forge/gitlab/issue-list.sh +3 -0
  257. package/scripts/forge/gitlab/issue-view.sh +3 -0
  258. package/scripts/forge/gitlab/pr-diff.sh +3 -0
  259. package/scripts/forge/gitlab/pr-exists.sh +5 -0
  260. package/scripts/forge/gitlab/pr-list.sh +3 -0
  261. package/scripts/forge/gitlab/pr-merge.sh +3 -0
  262. package/scripts/forge/gitlab/pr-search.sh +3 -0
  263. package/scripts/forge/gitlab/pr-view.sh +3 -0
  264. package/scripts/forge/gitlab/recently-closed.sh +3 -0
  265. package/scripts/forge/gitlab/recently-merged.sh +3 -0
  266. package/scripts/forge/gitlab/repo-archive.sh +12 -0
  267. package/scripts/forge/gitlab/user-identity.sh +3 -0
  268. package/skeleton/.claude/skills/afx/SKILL.md +144 -0
  269. package/skeleton/.claude/skills/codev/SKILL.md +3 -3
  270. package/skeleton/.claude/skills/consult/SKILL.md +1 -1
  271. package/skeleton/.claude/skills/porch/SKILL.md +2 -2
  272. package/skeleton/DEPENDENCIES.md +1 -1
  273. package/skeleton/builders.md +4 -4
  274. package/skeleton/protocol-schema.json +2 -2
  275. package/skeleton/protocols/air/builder-prompt.md +6 -6
  276. package/skeleton/protocols/air/prompts/pr.md +2 -2
  277. package/skeleton/protocols/air/protocol.md +1 -2
  278. package/skeleton/protocols/aspir/builder-prompt.md +28 -6
  279. package/skeleton/protocols/aspir/protocol.json +9 -1
  280. package/skeleton/protocols/aspir/protocol.md +1 -1
  281. package/skeleton/protocols/aspir/templates/plan.md +0 -20
  282. package/skeleton/protocols/aspir/templates/spec.md +0 -28
  283. package/skeleton/protocols/bugfix/builder-prompt.md +6 -6
  284. package/skeleton/protocols/bugfix/prompts/pr.md +1 -1
  285. package/skeleton/protocols/experiment/protocol.md +1 -6
  286. package/skeleton/protocols/maintain/builder-prompt.md +12 -11
  287. package/skeleton/protocols/maintain/prompts/maintain.md +88 -0
  288. package/skeleton/protocols/maintain/prompts/review.md +72 -0
  289. package/skeleton/protocols/maintain/protocol.json +14 -68
  290. package/skeleton/protocols/maintain/protocol.md +111 -409
  291. package/skeleton/protocols/maintain/templates/maintenance-run.md +18 -49
  292. package/skeleton/protocols/protocol-schema.json +1 -1
  293. package/skeleton/protocols/spike/builder-prompt.md +1 -1
  294. package/skeleton/protocols/spike/protocol.md +4 -4
  295. package/skeleton/protocols/spir/builder-prompt.md +28 -5
  296. package/skeleton/protocols/spir/protocol.json +9 -1
  297. package/skeleton/protocols/spir/protocol.md +2 -2
  298. package/skeleton/protocols/spir/templates/plan.md +0 -20
  299. package/skeleton/protocols/spir/templates/spec.md +0 -28
  300. package/skeleton/resources/commands/agent-farm.md +84 -84
  301. package/skeleton/resources/commands/codev.md +1 -1
  302. package/skeleton/resources/commands/consult.md +16 -7
  303. package/skeleton/resources/commands/overview.md +14 -15
  304. package/skeleton/resources/risk-triage.md +1 -1
  305. package/skeleton/resources/workflow-reference.md +14 -14
  306. package/skeleton/roles/architect.md +55 -43
  307. package/skeleton/roles/builder.md +22 -14
  308. package/skeleton/templates/AGENTS.md +4 -3
  309. package/skeleton/templates/CLAUDE.md +4 -3
  310. package/skeleton/templates/cheatsheet.md +11 -12
  311. package/templates/3d-viewer.html +16 -18
  312. package/templates/tower.html +1 -1
  313. package/dashboard/dist/assets/index-CiIBsixF.js +0 -194
  314. package/dashboard/dist/assets/index-CiIBsixF.js.map +0 -1
  315. package/skeleton/.claude/skills/af/SKILL.md +0 -146
  316. package/skeleton/porch/prompts/understand.md +0 -61
  317. package/skeleton/porch/prompts/verify.md +0 -58
  318. package/skeleton/protocols/maintain/prompts/audit.md +0 -111
  319. package/skeleton/protocols/maintain/prompts/clean.md +0 -91
  320. package/skeleton/protocols/maintain/prompts/sync.md +0 -113
  321. package/skeleton/protocols/maintain/prompts/verify.md +0 -110
  322. package/skeleton/protocols/tick/builder-prompt.md +0 -65
  323. package/skeleton/protocols/tick/consult-types/impl-review.md +0 -72
  324. package/skeleton/protocols/tick/consult-types/plan-review.md +0 -59
  325. package/skeleton/protocols/tick/consult-types/pr-review.md +0 -72
  326. package/skeleton/protocols/tick/consult-types/spec-review.md +0 -55
  327. package/skeleton/protocols/tick/protocol.json +0 -151
  328. package/skeleton/protocols/tick/protocol.md +0 -277
  329. package/skeleton/protocols/tick/templates/plan.md +0 -67
  330. package/skeleton/protocols/tick/templates/review.md +0 -89
  331. 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,25 +677,28 @@ 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
694
  * Build query for PR review.
646
695
  * Includes full PR diff + file list; model reads surrounding context from disk.
647
696
  */
648
- function buildPRQuery(prNumber) {
649
- const prData = fetchPRData(prNumber);
650
- const diff = fetchPRDiff(prNumber);
697
+ function buildPRQuery(prId) {
698
+ const prData = fetchPRData(prId);
699
+ const diff = fetchPRDiff(prId);
651
700
  const fileList = prData.changedFiles.map(f => `- ${f}`).join('\n');
652
- return `Review Pull Request #${prNumber}
701
+ return `Review Pull Request #${prId}
653
702
 
654
703
  ## PR Info
655
704
  \`\`\`json
@@ -693,22 +742,22 @@ KEY_ISSUES: [List of critical issues if any, or "None"]`;
693
742
  /**
694
743
  * Build query for spec review
695
744
  */
696
- function buildSpecQuery(specPath, planPath) {
697
- let query = `Review Specification: ${path.basename(specPath)}
745
+ function buildSpecQuery(spec, plan) {
746
+ let query = `Review Specification: ${spec.label}
747
+
748
+ ## Specification
749
+
750
+ ${spec.content}
698
751
 
699
- Please read and review this specification:
700
- - Spec file: ${specPath}
701
752
  `;
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.
753
+ if (plan) {
754
+ query += `## Plan
710
755
 
711
- Please review:
756
+ ${plan.content}
757
+
758
+ `;
759
+ }
760
+ query += `Please review:
712
761
  1. Clarity and completeness of requirements
713
762
  2. Technical feasibility
714
763
  3. Edge cases and error scenarios
@@ -731,7 +780,7 @@ KEY_ISSUES: [List of critical issues if any, or "None"]`;
731
780
  * Build query for implementation review.
732
781
  * Accepts spec/plan paths and optional diff reference override.
733
782
  */
734
- function buildImplQuery(workspaceRoot, specPath, planPath, planPhase, diffRef) {
783
+ function buildImplQuery(workspaceRoot, spec, plan, planPhase, diffRef) {
735
784
  // Get compact diff summary
736
785
  let diffStat = '';
737
786
  let changedFiles = [];
@@ -748,15 +797,15 @@ function buildImplQuery(workspaceRoot, specPath, planPath, planPhase, diffRef) {
748
797
  if (planPhase) {
749
798
  query += ` — Phase: ${planPhase}`;
750
799
  }
751
- query += `\n\n## Context Files\n`;
752
- if (specPath) {
753
- query += `- Spec: ${specPath}\n`;
800
+ query += '\n\n';
801
+ if (spec) {
802
+ query += `## Specification\n\n${spec.content}\n\n`;
754
803
  }
755
- if (planPath) {
756
- query += `- Plan: ${planPath}\n`;
804
+ if (plan) {
805
+ query += `## Plan\n\n${plan.content}\n\n`;
757
806
  }
758
807
  if (planPhase) {
759
- query += `\n## REVIEW SCOPE — CURRENT PLAN PHASE ONLY\n`;
808
+ query += `## REVIEW SCOPE — CURRENT PLAN PHASE ONLY\n`;
760
809
  query += `You are reviewing **plan phase "${planPhase}" ONLY**.\n`;
761
810
  query += `Read the plan, find the section for "${planPhase}", and scope your review to ONLY the work described in that phase.\n\n`;
762
811
  query += `**DO NOT** request changes for work that belongs to other plan phases.\n`;
@@ -774,7 +823,7 @@ function buildImplQuery(workspaceRoot, specPath, planPath, planPhase, diffRef) {
774
823
  query += `Do NOT rely on git diffs to determine the current state of code — diffs miss uncommitted changes in worktrees.\n`;
775
824
  }
776
825
  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`;
826
+ query += `\n## Instructions\n\nExplore the filesystem to find and review the implementation changes.\n`;
778
827
  }
779
828
  query += `
780
829
  Please review:
@@ -798,22 +847,22 @@ KEY_ISSUES: [List of critical issues if any, or "None"]`;
798
847
  /**
799
848
  * Build query for plan review
800
849
  */
801
- function buildPlanQuery(planPath, specPath) {
802
- let query = `Review Implementation Plan: ${path.basename(planPath)}
850
+ function buildPlanQuery(plan, spec) {
851
+ let query = `Review Implementation Plan: ${plan.label}
852
+
853
+ ## Plan
854
+
855
+ ${plan.content}
803
856
 
804
- Please read and review this implementation plan:
805
- - Plan file: ${planPath}
806
857
  `;
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.
858
+ if (spec) {
859
+ query += `## Specification (for context)
815
860
 
816
- Please review:
861
+ ${spec.content}
862
+
863
+ `;
864
+ }
865
+ query += `Please review:
817
866
  1. Alignment with specification requirements
818
867
  2. Implementation approach and architecture
819
868
  3. Task breakdown and ordering
@@ -836,7 +885,7 @@ KEY_ISSUES: [List of critical issues if any, or "None"]`;
836
885
  * Build query for phase-scoped review.
837
886
  * Uses git show HEAD for the phase's atomic commit diff.
838
887
  */
839
- function buildPhaseQuery(workspaceRoot, planPhase, specPath, planPath) {
888
+ function buildPhaseQuery(workspaceRoot, planPhase, spec, plan) {
840
889
  let phaseDiff = '';
841
890
  try {
842
891
  phaseDiff = execSync('git show HEAD', { cwd: workspaceRoot, encoding: 'utf-8', maxBuffer: 10 * 1024 * 1024 });
@@ -844,11 +893,11 @@ function buildPhaseQuery(workspaceRoot, planPhase, specPath, planPath) {
844
893
  catch {
845
894
  // If git show fails, reviewer explores filesystem
846
895
  }
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`;
896
+ let query = `Review Phase Implementation: "${planPhase}"\n\n`;
897
+ if (spec)
898
+ query += `## Specification\n\n${spec.content}\n\n`;
899
+ if (plan)
900
+ query += `## Plan\n\n${plan.content}\n\n`;
852
901
  query += `
853
902
  ## REVIEW SCOPE — CURRENT PLAN PHASE ONLY
854
903
  You are reviewing **plan phase "${planPhase}" ONLY**.
@@ -885,92 +934,94 @@ KEY_ISSUES: [List of critical issues if any, or "None"]`;
885
934
  return query;
886
935
  }
887
936
  /**
888
- * Find PR number for the current branch
937
+ * Find PR number for the current branch via pr-search forge concept.
889
938
  */
890
939
  function findPRForCurrentBranch(workspaceRoot) {
891
940
  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) {
941
+ const result = executeForgeCommandSync('pr-search', {
942
+ CODEV_SEARCH_QUERY: `head:${branchName}`,
943
+ }, { cwd: workspaceRoot });
944
+ const prs = Array.isArray(result) ? result : [];
945
+ if (prs.length === 0 || !prs[0]?.number) {
894
946
  throw new Error(`No PR found for branch: ${branchName}`);
895
947
  }
896
- const prNumber = parseInt(prJson, 10);
897
- if (isNaN(prNumber)) {
898
- throw new Error(`No PR found for branch: ${branchName}`);
899
- }
900
- return prNumber;
948
+ return String(prs[0].number);
901
949
  }
902
950
  /**
903
- * Find PR number for a given issue number (architect mode)
951
+ * Find PR number for a given issue number (architect mode) via pr-search forge concept.
904
952
  */
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);
953
+ function findPRForIssue(workspaceRoot, issueId) {
954
+ const result = executeForgeCommandSync('pr-search', {
955
+ CODEV_SEARCH_QUERY: issueId,
956
+ }, { cwd: workspaceRoot });
957
+ const prs = Array.isArray(result) ? result : [];
958
+ if (prs.length === 0 || !prs[0]?.number) {
959
+ throw new Error(`No PR found for issue #${issueId}`);
960
+ }
961
+ return prs[0];
911
962
  }
912
963
  /**
913
964
  * Resolve query for builder context (auto-detected from porch state)
914
965
  */
915
966
  function resolveBuilderQuery(workspaceRoot, type, options) {
916
967
  const projectState = getBuilderProjectState(workspaceRoot, options.projectId);
917
- const projectNumber = parseInt(projectState.id, 10);
968
+ const projectId = projectState.id;
918
969
  switch (type) {
919
970
  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);
971
+ const spec = findSpecContent(workspaceRoot, projectId);
972
+ if (!spec)
973
+ throw new Error(`Spec ${projectId} not found`);
974
+ const plan = findPlanContent(workspaceRoot, projectId);
975
+ console.error(`Spec: ${spec.label}`);
976
+ if (plan)
977
+ console.error(`Plan: ${plan.label}`);
978
+ return buildSpecQuery(spec, plan);
928
979
  }
929
980
  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);
981
+ const plan = findPlanContent(workspaceRoot, projectId);
982
+ if (!plan)
983
+ throw new Error(`Plan ${projectId} not found`);
984
+ const spec = findSpecContent(workspaceRoot, projectId);
985
+ console.error(`Plan: ${plan.label}`);
986
+ if (spec)
987
+ console.error(`Spec: ${spec.label}`);
988
+ return buildPlanQuery(plan, spec);
938
989
  }
939
990
  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}`);
991
+ const spec = findSpecContent(workspaceRoot, projectId);
992
+ const plan = findPlanContent(workspaceRoot, projectId);
993
+ console.error(`Project: ${projectId}`);
994
+ if (spec)
995
+ console.error(`Spec: ${spec.label}`);
996
+ if (plan)
997
+ console.error(`Plan: ${plan.label}`);
947
998
  if (options.planPhase)
948
999
  console.error(`Plan phase: ${options.planPhase}`);
949
- return buildImplQuery(workspaceRoot, specPath, planPath, options.planPhase);
1000
+ return buildImplQuery(workspaceRoot, spec, plan, options.planPhase);
950
1001
  }
951
1002
  case 'pr': {
952
- const prNumber = findPRForCurrentBranch(workspaceRoot);
953
- console.error(`PR: #${prNumber}`);
954
- return buildPRQuery(prNumber);
1003
+ const prId = findPRForCurrentBranch(workspaceRoot);
1004
+ console.error(`PR: #${prId}`);
1005
+ return buildPRQuery(prId);
955
1006
  }
956
1007
  case 'phase': {
957
1008
  const currentPhase = options.planPhase ?? projectState.currentPlanPhase;
958
1009
  if (!currentPhase) {
959
1010
  throw new Error('No current plan phase detected. Use --plan-phase to specify.');
960
1011
  }
961
- const specPath = findSpec(workspaceRoot, projectNumber);
962
- const planPath = findPlan(workspaceRoot, projectNumber);
1012
+ const spec = findSpecContent(workspaceRoot, projectId);
1013
+ const plan = findPlanContent(workspaceRoot, projectId);
963
1014
  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);
1015
+ if (spec)
1016
+ console.error(`Spec: ${spec.label}`);
1017
+ if (plan)
1018
+ console.error(`Plan: ${plan.label}`);
1019
+ return buildPhaseQuery(workspaceRoot, currentPhase, spec, plan);
969
1020
  }
970
1021
  case 'integration': {
971
- const prNumber = findPRForCurrentBranch(workspaceRoot);
972
- console.error(`PR: #${prNumber} (integration review)`);
973
- return buildPRQuery(prNumber);
1022
+ const prId = findPRForCurrentBranch(workspaceRoot);
1023
+ console.error(`PR: #${prId} (integration review)`);
1024
+ return buildPRQuery(prId);
974
1025
  }
975
1026
  default:
976
1027
  throw new Error(`Unknown review type: ${type}\nValid types: spec, plan, impl, pr, phase, integration`);
@@ -987,33 +1038,30 @@ function resolveArchitectQuery(workspaceRoot, type, options) {
987
1038
  throw new Error(`--issue is required from architect context for --type ${type}.\n` +
988
1039
  `Example: consult -m gemini --protocol spir --type ${type} --issue 42`);
989
1040
  }
990
- const issueNumber = parseInt(options.issue, 10);
991
- if (isNaN(issueNumber)) {
992
- throw new Error(`Invalid issue number: ${options.issue}`);
993
- }
1041
+ const issueId = options.issue;
994
1042
  switch (type) {
995
1043
  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);
1044
+ const spec = findSpecContent(workspaceRoot, issueId);
1045
+ if (!spec)
1046
+ throw new Error(`Spec ${issueId} not found`);
1047
+ const plan = findPlanContent(workspaceRoot, issueId);
1048
+ console.error(`Spec: ${spec.label}`);
1049
+ if (plan)
1050
+ console.error(`Plan: ${plan.label}`);
1051
+ return buildSpecQuery(spec, plan);
1004
1052
  }
1005
1053
  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);
1054
+ const plan = findPlanContent(workspaceRoot, issueId);
1055
+ if (!plan)
1056
+ throw new Error(`Plan ${issueId} not found`);
1057
+ const spec = findSpecContent(workspaceRoot, issueId);
1058
+ console.error(`Plan: ${plan.label}`);
1059
+ if (spec)
1060
+ console.error(`Spec: ${spec.label}`);
1061
+ return buildPlanQuery(plan, spec);
1014
1062
  }
1015
1063
  case 'impl': {
1016
- const pr = findPRForIssue(workspaceRoot, issueNumber);
1064
+ const pr = findPRForIssue(workspaceRoot, issueId);
1017
1065
  // Fetch the branch and diff from merge-base
1018
1066
  try {
1019
1067
  execSync(`git fetch origin ${pr.headRefName}`, { cwd: workspaceRoot, stdio: 'pipe' });
@@ -1022,24 +1070,24 @@ function resolveArchitectQuery(workspaceRoot, type, options) {
1022
1070
  // May already be fetched
1023
1071
  }
1024
1072
  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}`);
1073
+ const spec = findSpecContent(workspaceRoot, issueId);
1074
+ const plan = findPlanContent(workspaceRoot, issueId);
1075
+ console.error(`Project: ${issueId} (PR #${pr.number}, branch: ${pr.headRefName})`);
1076
+ if (spec)
1077
+ console.error(`Spec: ${spec.label}`);
1078
+ if (plan)
1079
+ console.error(`Plan: ${plan.label}`);
1080
+ return buildImplQuery(workspaceRoot, spec, plan, options.planPhase, `${mergeBase}..origin/${pr.headRefName}`);
1033
1081
  }
1034
1082
  case 'pr': {
1035
- const pr = findPRForIssue(workspaceRoot, issueNumber);
1083
+ const pr = findPRForIssue(workspaceRoot, issueId);
1036
1084
  console.error(`PR: #${pr.number}`);
1037
- return buildPRQuery(pr.number);
1085
+ return buildPRQuery(String(pr.number));
1038
1086
  }
1039
1087
  case 'integration': {
1040
- const pr = findPRForIssue(workspaceRoot, issueNumber);
1088
+ const pr = findPRForIssue(workspaceRoot, issueId);
1041
1089
  console.error(`PR: #${pr.number} (integration review)`);
1042
- return buildPRQuery(pr.number);
1090
+ return buildPRQuery(String(pr.number));
1043
1091
  }
1044
1092
  default:
1045
1093
  throw new Error(`Unknown review type: ${type}\nValid types: spec, plan, impl, pr, phase, integration`);
@@ -1144,7 +1192,7 @@ export async function consult(options) {
1144
1192
  }
1145
1193
  }
1146
1194
  // Add file access instruction for Gemini
1147
- if (model === 'gemini') {
1195
+ if (model === 'gemini' || model === 'hermes') {
1148
1196
  query += '\n\nYou have file access. Read files directly from disk to review code.';
1149
1197
  }
1150
1198
  // Show the query/prompt being sent