From cd70f23b5cf0d0b8fd347f1c99c5ed2eb0e9e426 Mon Sep 17 00:00:00 2001 From: Brezn Date: Tue, 21 Apr 2026 10:51:33 -0600 Subject: [PATCH] Translate Wrap-Up values into explicit LLM instructions --- src/index.ts | 58 ++++++++++++++++++++++++++++++++++++++++++++-- test/index.test.ts | 20 ++++++++++++++++ 2 files changed, 76 insertions(+), 2 deletions(-) create mode 100644 test/index.test.ts diff --git a/src/index.ts b/src/index.ts index 1599dbc..ffd6a41 100644 --- a/src/index.ts +++ b/src/index.ts @@ -15,6 +15,51 @@ async function getAuthenticatedClient(): Promise { } } +function buildWrapUpInstruction( + guidance: + | { + active?: boolean; + selectedMode?: "auto" | "wrap_up" | "full_depth"; + remainingMinutes?: number | null; + reason?: string; + hints?: string[]; + } + | null + | undefined, +): string | null { + if (!guidance || !guidance.active) { + return null; + } + + let instruction = ""; + if (guidance.selectedMode === "wrap_up") { + instruction = + "Execution policy for this task: keep scope minimal, avoid starting new refactors, finish the current slice cleanly, and include clear handoff notes for deferred work."; + } else if (guidance.selectedMode === "full_depth") { + instruction = + "Execution policy for this task: proceed with full implementation depth, include robust validation and tests, and do not shrink scope only because a deadline is near."; + } else { + instruction = + "Execution policy for this task: follow the provided context to balance scope and depth, stay focused on the requested outcome, and avoid unnecessary expansion."; + } + + const context: string[] = []; + + if (typeof guidance.remainingMinutes === "number") { + context.push(`About ${guidance.remainingMinutes} minutes remain before the attention deadline.`); + } + + if (guidance.reason) { + context.push(`Reason: ${guidance.reason}`); + } + + if (guidance.hints && guidance.hints.length > 0) { + context.push(`Hints: ${guidance.hints.join("; ")}`); + } + + return [instruction, ...context].join(" "); +} + function formatAvailabilitySummary(contract: Contract | null, availability: ScheduleResolution): string { const parts: string[] = []; @@ -53,6 +98,11 @@ function formatAvailabilitySummary(contract: Contract | null, availability: Sche parts.push(`Wrap-Up guidance: ${timing} (${availability.wrapUpGuidance.selectedMode})`); } + const wrapUpInstruction = buildWrapUpInstruction(availability.wrapUpGuidance); + if (wrapUpInstruction) { + parts.push(`Wrap-Up instruction: ${wrapUpInstruction}`); + } + if (availability.nextWindow) { parts.push(`Next availability window: ${availability.nextWindow.label} (${availability.nextWindow.mode})`); } @@ -76,12 +126,14 @@ export const HeadsDownOpenCodePlugin: Plugin = async () => { async execute() { const client = await getAuthenticatedClient(); const { contract, schedule: availability } = await client.getAvailability(); + const wrapUpInstruction = buildWrapUpInstruction(availability.wrapUpGuidance); return JSON.stringify( { authenticated: true, contract, availability, - summary: formatAvailabilitySummary(contract, availability) + summary: formatAvailabilitySummary(contract, availability), + wrapUpInstruction }, null, 2 @@ -135,13 +187,15 @@ export const HeadsDownOpenCodePlugin: Plugin = async () => { } } + const wrapUpInstruction = buildWrapUpInstruction(verdict.wrapUpGuidance); return JSON.stringify( { decision: verdict.decision, reason: verdict.reason, proposalId: verdict.proposalId, evaluatedAt: verdict.evaluatedAt, - wrapUpGuidance: verdict.wrapUpGuidance + wrapUpGuidance: verdict.wrapUpGuidance, + wrapUpInstruction }, null, 2 diff --git a/test/index.test.ts b/test/index.test.ts new file mode 100644 index 0000000..9581e1b --- /dev/null +++ b/test/index.test.ts @@ -0,0 +1,20 @@ +import { describe, it, expect } from "vitest"; +import { readFile } from "node:fs/promises"; +import { join } from "node:path"; + +describe("OpenCode HeadsDown plugin source", () => { + it("includes cold-start Wrap-Up instruction mapping", async () => { + const source = await readFile(join(import.meta.dirname, "..", "src", "index.ts"), "utf8"); + + expect(source).toContain("Execution policy for this task"); + expect(source).toContain("buildWrapUpInstruction"); + expect(source).toContain("wrapUpInstruction"); + }); + + it("passes delivery_mode through to SDK proposal input", async () => { + const source = await readFile(join(import.meta.dirname, "..", "src", "index.ts"), "utf8"); + + expect(source).toContain("delivery_mode"); + expect(source).toContain("deliveryMode: args.delivery_mode"); + }); +});