Skip to content

Commit 0d03e59

Browse files
committed
fix: correctly provide review in status updates
1 parent 90741cf commit 0d03e59

5 files changed

Lines changed: 63 additions & 17 deletions

File tree

api/src/agent/agent.model.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
export const AGENT_PHASES = ['PLAN', 'IMPLEMENT', 'REVIEW', 'REVISE'] as const;
1+
export const AGENT_PHASES = ['PLAN', 'IMPLEMENT', 'REVIEW', 'REVISE', 'PR_BODY'] as const;
22
export type AgentPhase = (typeof AGENT_PHASES)[number];
33

44
export type AgentRunStatus = 'RUNNING' | 'SUCCEEDED' | 'FAILED' | 'TIMED_OUT';
@@ -70,3 +70,12 @@ export interface RevisePromptContext {
7070
plan: string;
7171
issuesText: string;
7272
}
73+
74+
export interface PrBodyPromptContext {
75+
repoFullName: string;
76+
issueNumber: number;
77+
issueTitle: string;
78+
issueBody: string;
79+
baseBranch: string;
80+
branchName: string;
81+
}

api/src/agent/agent.prompts.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import {
22
type ImplementPromptContext,
33
type PlanPromptContext,
4+
type PrBodyPromptContext,
45
type RevisePromptContext,
56
} from './agent.model.js';
67

@@ -64,3 +65,13 @@ export function buildRevisePrompt(ctx: RevisePromptContext): string {
6465
`When finished, end your reply with a short summary of the fixes. The orchestrator will commit your changes — do not run git yourself.`,
6566
].join('\n\n');
6667
}
68+
69+
export function buildPrBodyPrompt(ctx: PrBodyPromptContext): string {
70+
return [
71+
`You are Hermes, an autonomous engineer working in a clone of \`${ctx.repoFullName}\`. You have just finished implementing changes for the following GitHub issue.`,
72+
`--- ISSUE #${ctx.issueNumber}: ${ctx.issueTitle} ---\n${ctx.issueBody}\n--- END ISSUE ---`,
73+
`Your implementation is on branch \`${ctx.branchName}\`. Use git to inspect the diff against \`${ctx.baseBranch}\` to understand exactly what was changed.`,
74+
`Write the body for the GitHub pull request. Write it as an experienced developer would — first person, concise, describing what was done and any key decisions. Do not reproduce the implementation plan verbatim. Do not add a \`Closes #N\` line or any AI-generated footer; those are added automatically.`,
75+
`Output ONLY the PR body as GitHub-flavored Markdown. No preamble, no explanation — just the content.`,
76+
].join('\n\n');
77+
}

api/src/orchestrator/orchestrator.service.ts

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { HermesAgentService } from '../agent/agent.service.js';
1111
import {
1212
buildImplementPrompt,
1313
buildPlanPrompt,
14+
buildPrBodyPrompt,
1415
buildRevisePrompt,
1516
} from '../agent/agent.prompts.js';
1617
import { WorkspaceService } from '../workspace/workspace.service.js';
@@ -501,7 +502,12 @@ export class OrchestratorService {
501502
const maxPasses = this.review.maxPasses;
502503
let result: ReviewResult | null = null;
503504

504-
for (let pass = 1; pass <= maxPasses; pass++) {
505+
// Pass numbers are globally incrementing per job across task retries so the paper trail is
506+
// preserved and ordering by passNumber desc always returns the most recent pass.
507+
const priorPasses = await this.prisma.reviewPass.count({ where: { jobId } });
508+
509+
for (let iter = 1; iter <= maxPasses; iter++) {
510+
const pass = priorPasses + iter;
505511
// Idempotent: a no-op when already SELF_REVIEWING, REVISING -> SELF_REVIEWING otherwise.
506512
await this.jobs.transition(jobId, 'SELF_REVIEWING', {
507513
reason: `review pass ${pass}`,
@@ -546,7 +552,7 @@ export class OrchestratorService {
546552
// Only revise when the review produced parseable, actionable issues. If the output
547553
// couldn't be parsed there is nothing for the revise agent to act on — it would spin
548554
// and time out. Re-reviewing the unchanged code on the next pass is the right move.
549-
if (pass < maxPasses && parsed !== null) {
555+
if (iter < maxPasses && parsed !== null) {
550556
await this.jobs.transition(jobId, 'REVISING', {
551557
reason: `addressing review pass ${pass}`,
552558
actor: 'AGENT',
@@ -609,12 +615,29 @@ export class OrchestratorService {
609615
lastReview.verdict === 'PASS' &&
610616
lastReview.confidence >= this.review.threshold;
611617
const unresolved = lastReview ? formatIssues(JSON.parse(lastReview.issues)) : undefined;
612-
const plan = await this.approvedPlan(jobId);
613618

614619
if (!job.prNumber) {
620+
const prBodyRes = await this.agent.run({
621+
jobId,
622+
phase: 'PR_BODY',
623+
cwd: this.workspace.dir(jobId),
624+
prompt: buildPrBodyPrompt({
625+
repoFullName: job.repoFullName,
626+
issueNumber: job.issueNumber,
627+
issueTitle: job.issueTitle,
628+
issueBody: job.issueBody,
629+
baseBranch: base,
630+
branchName,
631+
}),
632+
});
633+
const agentSummary =
634+
prBodyRes.status === 'SUCCEEDED' && prBodyRes.stdout.trim().length > 0
635+
? prBodyRes.stdout.trim().slice(0, 8_000)
636+
: `Resolves #${job.issueNumber}: ${job.issueTitle}`;
637+
615638
const body = buildPrBody({
616639
issueNumber: job.issueNumber,
617-
plan,
640+
agentSummary,
618641
confidence: lastReview?.confidence ?? null,
619642
threshold: this.review.threshold,
620643
meetsThreshold: meets,

api/src/orchestrator/orchestrator.utility.ts

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ export function reviseCommitMessage(pass: number): string {
6060

6161
export interface PrBodyInput {
6262
issueNumber: number;
63-
plan: string;
63+
agentSummary: string;
6464
confidence: number | null;
6565
threshold: number;
6666
meetsThreshold: boolean;
@@ -69,26 +69,24 @@ export interface PrBodyInput {
6969

7070
export function buildPrBody(input: PrBodyInput): string {
7171
const lines = [
72-
`This pull request was generated by **Hermes Agent** to resolve #${input.issueNumber}.`,
72+
input.agentSummary.trim(),
7373
'',
7474
`Closes #${input.issueNumber}`,
7575
'',
76-
'## Plan',
77-
input.plan.trim(),
78-
'',
79-
'## Self-review',
80-
`Confidence: ${input.confidence ?? 'n/a'}/100 (threshold ${input.threshold}).`,
76+
'---',
8177
];
8278
if (input.meetsThreshold) {
83-
lines.push('All automated review checks passed.');
79+
lines.push(
80+
`🤖 Automated self-review: confidence ${input.confidence}/100 — all checks passed.`,
81+
);
8482
} else {
8583
lines.push(
86-
'Opened below the confidence threshold for human attention. Unresolved findings:',
87-
'',
88-
input.unresolvedIssues ?? '(none recorded)',
84+
`🤖 Automated self-review: confidence ${input.confidence ?? 'n/a'}/100 (threshold ${input.threshold}) — opened below threshold for human attention.`,
8985
);
86+
if (input.unresolvedIssues) {
87+
lines.push('', '**Unresolved findings:**', '', input.unresolvedIssues);
88+
}
9089
}
91-
lines.push('', '---', '🤖 Draft PR generated by Hermes Agent — review before merging.');
9290
return lines.join('\n');
9391
}
9492

api/src/workspace/workspace.service.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,11 @@ export class WorkspaceService {
151151
await rm(dir, { recursive: true, force: true });
152152
}
153153

154+
/** Returns the absolute path of the job's workspace directory. */
155+
dir(jobId: string): string {
156+
return workspaceDir(this.config.get('WORKSPACE_ROOT'), jobId);
157+
}
158+
154159
/**
155160
* Downloads GitHub-hosted attachments into a `.attachments/` sub-directory of the
156161
* workspace. Skips files already present (idempotent across phases). Returns the set of

0 commit comments

Comments
 (0)