Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 33 additions & 2 deletions src/workflow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,7 @@ async function executeIssue(
await linear.markStage(state.issue.id, "implementing");
await linear.comment(
state.issue.id,
buildPlanComment(state.issue.key, state.planSummary),
buildPlanComment(state.issue.key, state.planSummary, result.usage),
);
logger.info(buildIssueJobLogFields(state, "planning"), "Plan completed");
}
Expand Down Expand Up @@ -335,7 +335,10 @@ async function executeIssue(
await linear.applyStageLabel(state.issue.id, "pr_created");
await linear.comment(
state.issue.id,
`Implementation completed. Draft PR: ${state.pullRequest.url ?? "(created)"}`,
[
`Implementation completed. Draft PR: ${state.pullRequest.url ?? "(created)"}`,
formatCodexUsageLine(result.usage),
].join("\n"),
);
logger.info(
buildIssueJobLogFields(state, "implementing"),
Expand Down Expand Up @@ -376,6 +379,8 @@ async function executeIssue(
"",
`Result: ${outcome.passed ? "PASS" : "FAIL"}`,
"",
formatCodexUsageLine(review.usage),
"",
outcome.summary,
"",
outcome.bugs.length > 0
Expand Down Expand Up @@ -457,6 +462,11 @@ export interface ReviewOutcome {
export function buildPlanComment(
issueKey: string,
planSummary: string,
usage?: {
inputTokens?: number;
outputTokens?: number;
totalTokens?: number;
},
): string {
const maxSummaryLength = 6000;
const normalized = planSummary.trim();
Expand All @@ -470,11 +480,32 @@ export function buildPlanComment(
"",
"Planning completed; implementation started.",
"",
formatCodexUsageLine(usage),
"",
"Plan:",
truncated || "(No plan summary returned by planning agent.)",
].join("\n");
}

export function formatCodexUsageLine(usage?: {
inputTokens?: number;
outputTokens?: number;
totalTokens?: number;
}): string {
if (!usage) {
return "Token usage: unknown";
}
const input = usage.inputTokens ?? "unknown";
const output = usage.outputTokens ?? "unknown";
const total =
usage.totalTokens ??
(typeof usage.inputTokens === "number" &&
typeof usage.outputTokens === "number"
? usage.inputTokens + usage.outputTokens
: "unknown");
return `Token usage: input ${input}, output ${output}, total ${total}`;
}

export function parseReviewOutcome(text: string): ReviewOutcome {
const upper = text.toUpperCase();
const passed =
Expand Down
36 changes: 35 additions & 1 deletion tests/workflow.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
appendCodexUsage,
buildIssueJobLogFields,
buildPlanComment,
formatCodexUsageLine,
parseReviewOutcome,
resolvePollingSettings,
shouldStopPolling,
Expand Down Expand Up @@ -38,15 +39,48 @@ BUGS_JSON:

describe("buildPlanComment", () => {
it("includes header and plan summary", () => {
const comment = buildPlanComment("ENG-1", "1. Do A\n2. Do B");
const comment = buildPlanComment("ENG-1", "1. Do A\n2. Do B", {
inputTokens: 12,
outputTokens: 8,
});
expect(comment).toContain("PIV loop plan for ENG-1");
expect(comment).toContain("Planning completed; implementation started.");
expect(comment).toContain("Token usage: input 12, output 8, total 20");
expect(comment).toContain("1. Do A");
});

it("uses fallback when no summary is returned", () => {
const comment = buildPlanComment("ENG-1", " ");
expect(comment).toContain("(No plan summary returned by planning agent.)");
expect(comment).toContain("Token usage: unknown");
});
});

describe("formatCodexUsageLine", () => {
it("formats full usage values", () => {
expect(
formatCodexUsageLine({
inputTokens: 3,
outputTokens: 7,
totalTokens: 10,
}),
).toBe("Token usage: input 3, output 7, total 10");
});

it("derives total when missing", () => {
expect(
formatCodexUsageLine({
inputTokens: 9,
outputTokens: 4,
}),
).toBe("Token usage: input 9, output 4, total 13");
});

it("handles missing fields", () => {
expect(formatCodexUsageLine({ inputTokens: 5 })).toBe(
"Token usage: input 5, output unknown, total unknown",
);
expect(formatCodexUsageLine()).toBe("Token usage: unknown");
});
});

Expand Down
Loading