@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.
- package/LICENSE +201 -0
- package/bin/af.js +2 -2
- package/bin/afx.js +8 -0
- package/bin/consult.js +2 -1
- package/bin/team.js +2 -1
- package/{dashboard/dist/assets/index-nr0cXxtf.css → dashboard-dist/assets/index-Bs8I7N2s.css} +1 -1
- package/dashboard-dist/assets/index-CkNo2z6t.js +195 -0
- package/dashboard-dist/assets/index-CkNo2z6t.js.map +1 -0
- package/dashboard-dist/dist/assets/index-Bs8I7N2s.css +32 -0
- package/dashboard-dist/dist/assets/index-CkNo2z6t.js +195 -0
- package/dashboard-dist/dist/assets/index-CkNo2z6t.js.map +1 -0
- package/{dashboard → dashboard-dist}/dist/index.html +2 -2
- package/dashboard-dist/index.html +14 -0
- package/dist/agent-farm/cli.d.ts.map +1 -1
- package/dist/agent-farm/cli.js +17 -14
- package/dist/agent-farm/cli.js.map +1 -1
- package/dist/agent-farm/commands/architect.d.ts +4 -4
- package/dist/agent-farm/commands/architect.d.ts.map +1 -1
- package/dist/agent-farm/commands/architect.js +23 -12
- package/dist/agent-farm/commands/architect.js.map +1 -1
- package/dist/agent-farm/commands/attach.js +8 -8
- package/dist/agent-farm/commands/attach.js.map +1 -1
- package/dist/agent-farm/commands/bench.d.ts +1 -1
- package/dist/agent-farm/commands/bench.js +1 -1
- package/dist/agent-farm/commands/cleanup.d.ts.map +1 -1
- package/dist/agent-farm/commands/cleanup.js +10 -5
- package/dist/agent-farm/commands/cleanup.js.map +1 -1
- package/dist/agent-farm/commands/cron.js +1 -1
- package/dist/agent-farm/commands/cron.js.map +1 -1
- package/dist/agent-farm/commands/db.d.ts +3 -3
- package/dist/agent-farm/commands/db.js +4 -4
- package/dist/agent-farm/commands/db.js.map +1 -1
- package/dist/agent-farm/commands/open.js +2 -2
- package/dist/agent-farm/commands/open.js.map +1 -1
- package/dist/agent-farm/commands/rename.js +1 -1
- package/dist/agent-farm/commands/rename.js.map +1 -1
- package/dist/agent-farm/commands/send.d.ts +4 -1
- package/dist/agent-farm/commands/send.d.ts.map +1 -1
- package/dist/agent-farm/commands/send.js +8 -5
- package/dist/agent-farm/commands/send.js.map +1 -1
- package/dist/agent-farm/commands/shell.js +3 -3
- package/dist/agent-farm/commands/shell.js.map +1 -1
- package/dist/agent-farm/commands/spawn-roles.d.ts +2 -1
- package/dist/agent-farm/commands/spawn-roles.d.ts.map +1 -1
- package/dist/agent-farm/commands/spawn-roles.js +1 -1
- package/dist/agent-farm/commands/spawn-roles.js.map +1 -1
- package/dist/agent-farm/commands/spawn-worktree.d.ts +49 -10
- package/dist/agent-farm/commands/spawn-worktree.d.ts.map +1 -1
- package/dist/agent-farm/commands/spawn-worktree.js +356 -51
- package/dist/agent-farm/commands/spawn-worktree.js.map +1 -1
- package/dist/agent-farm/commands/spawn.d.ts +6 -6
- package/dist/agent-farm/commands/spawn.d.ts.map +1 -1
- package/dist/agent-farm/commands/spawn.js +182 -74
- package/dist/agent-farm/commands/spawn.js.map +1 -1
- package/dist/agent-farm/commands/status.d.ts.map +1 -1
- package/dist/agent-farm/commands/status.js +48 -3
- package/dist/agent-farm/commands/status.js.map +1 -1
- package/dist/agent-farm/commands/team-update.d.ts +1 -1
- package/dist/agent-farm/commands/team-update.d.ts.map +1 -1
- package/dist/agent-farm/commands/team-update.js +8 -5
- package/dist/agent-farm/commands/team-update.js.map +1 -1
- package/dist/agent-farm/commands/team.d.ts +1 -1
- package/dist/agent-farm/commands/team.d.ts.map +1 -1
- package/dist/agent-farm/commands/team.js +7 -3
- package/dist/agent-farm/commands/team.js.map +1 -1
- package/dist/agent-farm/commands/tower-cloud.d.ts +2 -2
- package/dist/agent-farm/commands/tower-cloud.js +3 -3
- package/dist/agent-farm/commands/tower-cloud.js.map +1 -1
- package/dist/agent-farm/commands/tower.js +2 -2
- package/dist/agent-farm/commands/tower.js.map +1 -1
- package/dist/agent-farm/lib/cloud-config.js +2 -2
- package/dist/agent-farm/lib/cloud-config.js.map +1 -1
- package/dist/agent-farm/lib/tower-client.d.ts +6 -230
- package/dist/agent-farm/lib/tower-client.d.ts.map +1 -1
- package/dist/agent-farm/lib/tower-client.js +6 -293
- package/dist/agent-farm/lib/tower-client.js.map +1 -1
- package/dist/agent-farm/lib/tunnel-client.js +1 -1
- package/dist/agent-farm/lib/tunnel-client.js.map +1 -1
- package/dist/agent-farm/servers/message-write.d.ts +24 -0
- package/dist/agent-farm/servers/message-write.d.ts.map +1 -0
- package/dist/agent-farm/servers/message-write.js +63 -0
- package/dist/agent-farm/servers/message-write.js.map +1 -0
- package/dist/agent-farm/servers/overview.d.ts +10 -8
- package/dist/agent-farm/servers/overview.d.ts.map +1 -1
- package/dist/agent-farm/servers/overview.js +78 -38
- package/dist/agent-farm/servers/overview.js.map +1 -1
- package/dist/agent-farm/servers/send-buffer.d.ts +4 -3
- package/dist/agent-farm/servers/send-buffer.d.ts.map +1 -1
- package/dist/agent-farm/servers/send-buffer.js +7 -4
- package/dist/agent-farm/servers/send-buffer.js.map +1 -1
- package/dist/agent-farm/servers/tower-cron-parser.js +1 -1
- package/dist/agent-farm/servers/tower-cron-parser.js.map +1 -1
- package/dist/agent-farm/servers/tower-cron.d.ts.map +1 -1
- package/dist/agent-farm/servers/tower-cron.js +4 -5
- package/dist/agent-farm/servers/tower-cron.js.map +1 -1
- package/dist/agent-farm/servers/tower-instances.d.ts.map +1 -1
- package/dist/agent-farm/servers/tower-instances.js +15 -13
- package/dist/agent-farm/servers/tower-instances.js.map +1 -1
- package/dist/agent-farm/servers/tower-routes.d.ts.map +1 -1
- package/dist/agent-farm/servers/tower-routes.js +9 -16
- package/dist/agent-farm/servers/tower-routes.js.map +1 -1
- package/dist/agent-farm/servers/tower-server.js +1 -1
- package/dist/agent-farm/servers/tower-terminals.d.ts +2 -2
- package/dist/agent-farm/servers/tower-terminals.d.ts.map +1 -1
- package/dist/agent-farm/servers/tower-terminals.js +64 -36
- package/dist/agent-farm/servers/tower-terminals.js.map +1 -1
- package/dist/agent-farm/servers/tower-tunnel.js +2 -2
- package/dist/agent-farm/servers/tower-tunnel.js.map +1 -1
- package/dist/agent-farm/servers/tower-utils.d.ts +9 -6
- package/dist/agent-farm/servers/tower-utils.d.ts.map +1 -1
- package/dist/agent-farm/servers/tower-utils.js +13 -7
- package/dist/agent-farm/servers/tower-utils.js.map +1 -1
- package/dist/agent-farm/types.d.ts +13 -0
- package/dist/agent-farm/types.d.ts.map +1 -1
- package/dist/agent-farm/utils/config.d.ts +11 -0
- package/dist/agent-farm/utils/config.d.ts.map +1 -1
- package/dist/agent-farm/utils/config.js +39 -17
- package/dist/agent-farm/utils/config.js.map +1 -1
- package/dist/agent-farm/utils/harness.d.ts +85 -0
- package/dist/agent-farm/utils/harness.d.ts.map +1 -0
- package/dist/agent-farm/utils/harness.js +212 -0
- package/dist/agent-farm/utils/harness.js.map +1 -0
- package/dist/agent-farm/utils/roles.d.ts +11 -4
- package/dist/agent-farm/utils/roles.d.ts.map +1 -1
- package/dist/agent-farm/utils/roles.js +21 -6
- package/dist/agent-farm/utils/roles.js.map +1 -1
- package/dist/cli.d.ts +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +40 -5
- package/dist/cli.js.map +1 -1
- package/dist/commands/adopt.d.ts.map +1 -1
- package/dist/commands/adopt.js +16 -45
- package/dist/commands/adopt.js.map +1 -1
- package/dist/commands/consult/index.d.ts +37 -3
- package/dist/commands/consult/index.d.ts.map +1 -1
- package/dist/commands/consult/index.js +281 -194
- package/dist/commands/consult/index.js.map +1 -1
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +121 -31
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +13 -43
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/porch/artifacts.d.ts +69 -0
- package/dist/commands/porch/artifacts.d.ts.map +1 -0
- package/dist/commands/porch/artifacts.js +368 -0
- package/dist/commands/porch/artifacts.js.map +1 -0
- package/dist/commands/porch/checks.d.ts +14 -3
- package/dist/commands/porch/checks.d.ts.map +1 -1
- package/dist/commands/porch/checks.js +150 -4
- package/dist/commands/porch/checks.js.map +1 -1
- package/dist/commands/porch/config.d.ts +6 -9
- package/dist/commands/porch/config.d.ts.map +1 -1
- package/dist/commands/porch/config.js +12 -37
- package/dist/commands/porch/config.js.map +1 -1
- package/dist/commands/porch/index.d.ts +12 -7
- package/dist/commands/porch/index.d.ts.map +1 -1
- package/dist/commands/porch/index.js +273 -82
- package/dist/commands/porch/index.js.map +1 -1
- package/dist/commands/porch/next.d.ts.map +1 -1
- package/dist/commands/porch/next.js +142 -75
- package/dist/commands/porch/next.js.map +1 -1
- package/dist/commands/porch/notify.d.ts +2 -2
- package/dist/commands/porch/notify.js +5 -5
- package/dist/commands/porch/notify.js.map +1 -1
- package/dist/commands/porch/prompts.d.ts +2 -1
- package/dist/commands/porch/prompts.d.ts.map +1 -1
- package/dist/commands/porch/prompts.js +34 -38
- package/dist/commands/porch/prompts.js.map +1 -1
- package/dist/commands/porch/protocol.d.ts +4 -3
- package/dist/commands/porch/protocol.d.ts.map +1 -1
- package/dist/commands/porch/protocol.js +21 -21
- package/dist/commands/porch/protocol.js.map +1 -1
- package/dist/commands/porch/state.d.ts +27 -2
- package/dist/commands/porch/state.d.ts.map +1 -1
- package/dist/commands/porch/state.js +100 -9
- package/dist/commands/porch/state.js.map +1 -1
- package/dist/commands/porch/types.d.ts +10 -2
- package/dist/commands/porch/types.d.ts.map +1 -1
- package/dist/commands/sync.d.ts +26 -0
- package/dist/commands/sync.d.ts.map +1 -0
- package/dist/commands/sync.js +207 -0
- package/dist/commands/sync.js.map +1 -0
- package/dist/commands/update.d.ts +15 -2
- package/dist/commands/update.d.ts.map +1 -1
- package/dist/commands/update.js +159 -202
- package/dist/commands/update.js.map +1 -1
- package/dist/lib/config.d.ts +96 -0
- package/dist/lib/config.d.ts.map +1 -0
- package/dist/lib/config.js +165 -0
- package/dist/lib/config.js.map +1 -0
- package/dist/lib/forge-contracts.d.ts +100 -0
- package/dist/lib/forge-contracts.d.ts.map +1 -0
- package/dist/lib/forge-contracts.js +35 -0
- package/dist/lib/forge-contracts.js.map +1 -0
- package/dist/lib/forge.d.ts +102 -0
- package/dist/lib/forge.d.ts.map +1 -0
- package/dist/lib/forge.js +376 -0
- package/dist/lib/forge.js.map +1 -0
- package/dist/lib/github.d.ts +68 -60
- package/dist/lib/github.d.ts.map +1 -1
- package/dist/lib/github.js +160 -169
- package/dist/lib/github.js.map +1 -1
- package/dist/lib/skeleton.d.ts +15 -5
- package/dist/lib/skeleton.d.ts.map +1 -1
- package/dist/lib/skeleton.js +82 -7
- package/dist/lib/skeleton.js.map +1 -1
- package/dist/lib/team-github.d.ts +7 -3
- package/dist/lib/team-github.d.ts.map +1 -1
- package/dist/lib/team-github.js +29 -19
- package/dist/lib/team-github.js.map +1 -1
- package/dist/terminal/pty-manager.d.ts.map +1 -1
- package/dist/terminal/pty-manager.js +2 -0
- package/dist/terminal/pty-manager.js.map +1 -1
- package/dist/terminal/ws-protocol.d.ts +10 -8
- package/dist/terminal/ws-protocol.d.ts.map +1 -1
- package/dist/terminal/ws-protocol.js +3 -0
- package/dist/terminal/ws-protocol.js.map +1 -1
- package/package.json +27 -24
- package/scripts/forge/gitea/auth-status.sh +3 -0
- package/scripts/forge/gitea/issue-comment.sh +3 -0
- package/scripts/forge/gitea/issue-list.sh +3 -0
- package/scripts/forge/gitea/issue-view.sh +3 -0
- package/scripts/forge/gitea/pr-exists.sh +6 -0
- package/scripts/forge/gitea/pr-list.sh +3 -0
- package/scripts/forge/gitea/pr-merge.sh +3 -0
- package/scripts/forge/gitea/pr-view.sh +3 -0
- package/scripts/forge/gitea/recently-closed.sh +3 -0
- package/scripts/forge/gitea/recently-merged.sh +3 -0
- package/scripts/forge/gitea/repo-archive.sh +11 -0
- package/scripts/forge/gitea/user-identity.sh +3 -0
- package/scripts/forge/github/auth-status.sh +4 -0
- package/scripts/forge/github/issue-comment.sh +5 -0
- package/scripts/forge/github/issue-list.sh +4 -0
- package/scripts/forge/github/issue-view.sh +5 -0
- package/scripts/forge/github/on-it-timestamps.sh +8 -0
- package/scripts/forge/github/pr-diff.sh +10 -0
- package/scripts/forge/github/pr-exists.sh +7 -0
- package/scripts/forge/github/pr-list.sh +4 -0
- package/scripts/forge/github/pr-merge.sh +5 -0
- package/scripts/forge/github/pr-search.sh +5 -0
- package/scripts/forge/github/pr-view.sh +11 -0
- package/scripts/forge/github/recently-closed.sh +15 -0
- package/scripts/forge/github/recently-merged.sh +15 -0
- package/scripts/forge/github/repo-archive.sh +10 -0
- package/scripts/forge/github/team-activity.sh +5 -0
- package/scripts/forge/github/user-identity.sh +4 -0
- package/scripts/forge/gitlab/auth-status.sh +3 -0
- package/scripts/forge/gitlab/issue-comment.sh +3 -0
- package/scripts/forge/gitlab/issue-list.sh +3 -0
- package/scripts/forge/gitlab/issue-view.sh +3 -0
- package/scripts/forge/gitlab/pr-diff.sh +3 -0
- package/scripts/forge/gitlab/pr-exists.sh +5 -0
- package/scripts/forge/gitlab/pr-list.sh +3 -0
- package/scripts/forge/gitlab/pr-merge.sh +3 -0
- package/scripts/forge/gitlab/pr-search.sh +3 -0
- package/scripts/forge/gitlab/pr-view.sh +3 -0
- package/scripts/forge/gitlab/recently-closed.sh +3 -0
- package/scripts/forge/gitlab/recently-merged.sh +3 -0
- package/scripts/forge/gitlab/repo-archive.sh +12 -0
- package/scripts/forge/gitlab/user-identity.sh +3 -0
- package/skeleton/.claude/skills/afx/SKILL.md +144 -0
- package/skeleton/.claude/skills/codev/SKILL.md +3 -3
- package/skeleton/.claude/skills/consult/SKILL.md +1 -1
- package/skeleton/.claude/skills/porch/SKILL.md +2 -2
- package/skeleton/DEPENDENCIES.md +1 -1
- package/skeleton/builders.md +4 -4
- package/skeleton/protocol-schema.json +2 -2
- package/skeleton/protocols/air/builder-prompt.md +6 -6
- package/skeleton/protocols/air/prompts/pr.md +17 -5
- package/skeleton/protocols/air/protocol.md +1 -2
- package/skeleton/protocols/aspir/builder-prompt.md +28 -6
- package/skeleton/protocols/aspir/prompts/review.md +11 -0
- package/skeleton/protocols/aspir/protocol.json +9 -1
- package/skeleton/protocols/aspir/protocol.md +1 -1
- package/skeleton/protocols/aspir/templates/plan.md +0 -20
- package/skeleton/protocols/aspir/templates/spec.md +0 -28
- package/skeleton/protocols/bugfix/builder-prompt.md +6 -6
- package/skeleton/protocols/bugfix/prompts/pr.md +16 -4
- package/skeleton/protocols/experiment/builder-prompt.md +15 -0
- package/skeleton/protocols/experiment/protocol.md +1 -6
- package/skeleton/protocols/maintain/builder-prompt.md +12 -11
- package/skeleton/protocols/maintain/prompts/maintain.md +88 -0
- package/skeleton/protocols/maintain/prompts/review.md +84 -0
- package/skeleton/protocols/maintain/protocol.json +14 -68
- package/skeleton/protocols/maintain/protocol.md +111 -409
- package/skeleton/protocols/maintain/templates/maintenance-run.md +18 -49
- package/skeleton/protocols/protocol-schema.json +1 -1
- package/skeleton/protocols/spike/builder-prompt.md +1 -1
- package/skeleton/protocols/spike/protocol.md +4 -4
- package/skeleton/protocols/spir/builder-prompt.md +28 -5
- package/skeleton/protocols/spir/prompts/review.md +11 -0
- package/skeleton/protocols/spir/protocol.json +9 -1
- package/skeleton/protocols/spir/protocol.md +2 -2
- package/skeleton/protocols/spir/templates/plan.md +0 -20
- package/skeleton/protocols/spir/templates/spec.md +0 -28
- package/skeleton/resources/commands/agent-farm.md +84 -84
- package/skeleton/resources/commands/codev.md +1 -1
- package/skeleton/resources/commands/consult.md +16 -7
- package/skeleton/resources/commands/overview.md +14 -15
- package/skeleton/resources/risk-triage.md +1 -1
- package/skeleton/resources/workflow-reference.md +14 -14
- package/skeleton/roles/architect.md +55 -43
- package/skeleton/roles/builder.md +22 -14
- package/skeleton/templates/AGENTS.md +4 -3
- package/skeleton/templates/CLAUDE.md +4 -3
- package/skeleton/templates/cheatsheet.md +11 -12
- package/templates/3d-viewer.html +16 -18
- package/templates/tower.html +1 -1
- package/dashboard/dist/assets/index-CiIBsixF.js +0 -194
- package/dashboard/dist/assets/index-CiIBsixF.js.map +0 -1
- package/skeleton/.claude/skills/af/SKILL.md +0 -146
- package/skeleton/porch/prompts/understand.md +0 -61
- package/skeleton/porch/prompts/verify.md +0 -58
- package/skeleton/protocols/maintain/prompts/audit.md +0 -111
- package/skeleton/protocols/maintain/prompts/clean.md +0 -91
- package/skeleton/protocols/maintain/prompts/sync.md +0 -113
- package/skeleton/protocols/maintain/prompts/verify.md +0 -110
- package/skeleton/protocols/tick/builder-prompt.md +0 -65
- package/skeleton/protocols/tick/consult-types/impl-review.md +0 -72
- package/skeleton/protocols/tick/consult-types/plan-review.md +0 -59
- package/skeleton/protocols/tick/consult-types/pr-review.md +0 -72
- package/skeleton/protocols/tick/consult-types/spec-review.md +0 -55
- package/skeleton/protocols/tick/protocol.json +0 -151
- package/skeleton/protocols/tick/protocol.md +0 -277
- package/skeleton/protocols/tick/templates/plan.md +0 -67
- package/skeleton/protocols/tick/templates/review.md +0 -89
- 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
|
|
133
|
-
*
|
|
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
|
|
136
|
-
const
|
|
137
|
-
const
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
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
|
|
154
|
-
*
|
|
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
|
|
157
|
-
const
|
|
158
|
-
const
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
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
|
-
|
|
300
|
+
model_instructions_file: tempFile,
|
|
313
301
|
},
|
|
314
302
|
});
|
|
315
303
|
const thread = codex.startThread({
|
|
316
|
-
model: 'gpt-5.
|
|
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
|
-
|
|
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
|
|
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: [
|
|
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(
|
|
615
|
-
console.error(`Fetching PR #${
|
|
646
|
+
function fetchPRData(prId) {
|
|
647
|
+
console.error(`Fetching PR #${prId} data...`);
|
|
616
648
|
try {
|
|
617
|
-
const
|
|
618
|
-
|
|
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
|
|
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
|
|
680
|
+
* Fetch the full PR diff via the pr-diff forge concept command.
|
|
635
681
|
*/
|
|
636
|
-
function fetchPRDiff(
|
|
682
|
+
function fetchPRDiff(prId) {
|
|
637
683
|
try {
|
|
638
|
-
|
|
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 #${
|
|
690
|
+
throw new Error(`Failed to fetch PR diff for #${prId}: ${err}`);
|
|
642
691
|
}
|
|
643
692
|
}
|
|
644
693
|
/**
|
|
645
|
-
*
|
|
646
|
-
*
|
|
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
|
|
649
|
-
const
|
|
650
|
-
const
|
|
651
|
-
const
|
|
652
|
-
return `Review Pull Request #${
|
|
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
|
-
${
|
|
709
|
+
${info}
|
|
657
710
|
\`\`\`
|
|
658
711
|
|
|
659
|
-
## Changed Files
|
|
712
|
+
## Changed Files (${changedFiles.length})
|
|
660
713
|
${fileList}
|
|
661
714
|
|
|
662
715
|
## PR Diff
|
|
663
|
-
|
|
664
|
-
|
|
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
|
-
|
|
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
|
-
${
|
|
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(
|
|
697
|
-
let query = `Review Specification: ${
|
|
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 (
|
|
703
|
-
query +=
|
|
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
|
-
|
|
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,
|
|
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 +=
|
|
752
|
-
if (
|
|
753
|
-
query +=
|
|
839
|
+
query += '\n\n';
|
|
840
|
+
if (spec) {
|
|
841
|
+
query += `## Specification\n\n${spec.content}\n\n`;
|
|
754
842
|
}
|
|
755
|
-
if (
|
|
756
|
-
query +=
|
|
843
|
+
if (plan) {
|
|
844
|
+
query += `## Plan\n\n${plan.content}\n\n`;
|
|
757
845
|
}
|
|
758
846
|
if (planPhase) {
|
|
759
|
-
query +=
|
|
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\
|
|
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(
|
|
802
|
-
let query = `Review Implementation Plan: ${
|
|
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 (
|
|
808
|
-
query +=
|
|
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
|
-
|
|
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,
|
|
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
|
|
848
|
-
if (
|
|
849
|
-
query +=
|
|
850
|
-
if (
|
|
851
|
-
query +=
|
|
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
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
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
|
|
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,
|
|
906
|
-
const
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
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
|
|
1007
|
+
const projectId = projectState.id;
|
|
918
1008
|
switch (type) {
|
|
919
1009
|
case 'spec': {
|
|
920
|
-
const
|
|
921
|
-
if (!
|
|
922
|
-
throw new Error(`Spec ${
|
|
923
|
-
const
|
|
924
|
-
console.error(`Spec: ${
|
|
925
|
-
if (
|
|
926
|
-
console.error(`Plan: ${
|
|
927
|
-
return buildSpecQuery(
|
|
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
|
|
931
|
-
if (!
|
|
932
|
-
throw new Error(`Plan ${
|
|
933
|
-
const
|
|
934
|
-
console.error(`Plan: ${
|
|
935
|
-
if (
|
|
936
|
-
console.error(`Spec: ${
|
|
937
|
-
return buildPlanQuery(
|
|
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
|
|
941
|
-
const
|
|
942
|
-
console.error(`Project: ${
|
|
943
|
-
if (
|
|
944
|
-
console.error(`Spec: ${
|
|
945
|
-
if (
|
|
946
|
-
console.error(`Plan: ${
|
|
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,
|
|
1039
|
+
return buildImplQuery(workspaceRoot, spec, plan, options.planPhase);
|
|
950
1040
|
}
|
|
951
1041
|
case 'pr': {
|
|
952
|
-
const
|
|
953
|
-
console.error(`PR: #${
|
|
954
|
-
return buildPRQuery(
|
|
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
|
|
962
|
-
const
|
|
1051
|
+
const spec = findSpecContent(workspaceRoot, projectId);
|
|
1052
|
+
const plan = findPlanContent(workspaceRoot, projectId);
|
|
963
1053
|
console.error(`Phase: ${currentPhase}`);
|
|
964
|
-
if (
|
|
965
|
-
console.error(`Spec: ${
|
|
966
|
-
if (
|
|
967
|
-
console.error(`Plan: ${
|
|
968
|
-
return buildPhaseQuery(workspaceRoot, currentPhase,
|
|
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
|
|
972
|
-
console.error(`PR: #${
|
|
973
|
-
return buildPRQuery(
|
|
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
|
|
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
|
|
997
|
-
if (!
|
|
998
|
-
throw new Error(`Spec ${
|
|
999
|
-
const
|
|
1000
|
-
console.error(`Spec: ${
|
|
1001
|
-
if (
|
|
1002
|
-
console.error(`Plan: ${
|
|
1003
|
-
return buildSpecQuery(
|
|
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
|
|
1007
|
-
if (!
|
|
1008
|
-
throw new Error(`Plan ${
|
|
1009
|
-
const
|
|
1010
|
-
console.error(`Plan: ${
|
|
1011
|
-
if (
|
|
1012
|
-
console.error(`Spec: ${
|
|
1013
|
-
return buildPlanQuery(
|
|
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,
|
|
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
|
|
1026
|
-
const
|
|
1027
|
-
console.error(`Project: ${
|
|
1028
|
-
if (
|
|
1029
|
-
console.error(`Spec: ${
|
|
1030
|
-
if (
|
|
1031
|
-
console.error(`Plan: ${
|
|
1032
|
-
return buildImplQuery(workspaceRoot,
|
|
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,
|
|
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,
|
|
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
|