sign-off ticks the goal [x] itself + document that the judge reads the working tree, not HEAD

Dogfood exit interview: agent bookkeeping is the drift point (tick after accept was the
step most likely forgotten), and 'committed artifact' language implied the judge sees HEAD.
Tick is exact-subject match via the existing GOAL_LINE regex; on drift the result explicitly
asks the agent to tick, so neither path is silent.

Co-Authored-By: Claudypoo <288921227+claudypoo@users.noreply.github.com>
This commit is contained in:
wassname
2026-07-03 11:49:31 +08:00
co-authored by Claudypoo
parent 03e88d34ad
commit 92d3f6d725
4 changed files with 74 additions and 12 deletions
+4 -2
View File
@@ -90,9 +90,11 @@ Latency target came from the SLO review; keep the existing client API.
--no-session --no-extensions`, tools `read,bash,grep,find,ls`, edit/write excluded) with the whole
plan file and the claimed goal. The judge finds the goal, re-derives from the cited artifacts rather
than trusting claims, runs `verify` if the goal names one, and returns `VERDICT: accept | reject`
plus what's missing.
plus what's missing. It inspects the live working tree, not HEAD: uncommitted work counts, and
committing before sign-off is for durable evidence, not for the judge's visibility.
- accept: a sign-off line is appended to `## Log` and the agent ticks the goal `[x]` itself. The
- accept: a sign-off line is appended to `## Log` and the goal is ticked `[x]` in the same write
(exact goal-line match only; on wording drift the result asks the agent to tick it). The
tool-written log line is the audit trail; a hand-tick without one shows in the diff.
- reject: the goal stays open and the agent gets the missing list.
- judge ran but failed/errored/timed out, or returned no VERDICT line: accepted inconclusive,
+33 -7
View File
@@ -15,8 +15,9 @@
*
* The judge subsumes what v1 did in code: goal matching (no findGoal), evidence validation (a
* placeholder gets rejected in words), verify execution, and format reading. The extension's only
* write is appending a sign-off line to ## Log the audit trail. The agent ticks [x] itself; a
* hand-tick without a matching tool-written log line is visible in the diff.
* writes are the sign-off: append a log line to ## Log (the audit trail) and tick the goal [x] when
* an exact goal line matches (on drift the agent ticks, and the result says so). A hand-tick
* without a matching tool-written log line is visible in the diff either way.
*
* Judge ran but failed/errored/timed out, or returned no VERDICT line => accepted_inconclusive: the
* working agent is never blocked on judge infra; the log line says the judge ran but failed. There
@@ -233,8 +234,21 @@ export default function piGoalsExtension(pi: ExtensionAPI): void {
(task) => runJudge(task, judgeModel, ctx.cwd, signal),
);
if (outcome.logEntry) {
writePlan(ctx, appendLog(readPlan(ctx), `${stamp()} ${outcome.logEntry}`));
// Sign-off write: tick the goal [x] (exact-subject match; dogfood showed agent bookkeeping
// is the drift point) and append the audit log line, one write. On wording drift the tick
// falls to the agent and the result says so -- both paths are explicit, never silent.
let updated = readPlan(ctx);
let tickNote = "";
if (outcome.logEntry.startsWith("signed off")) {
const ticked = tickGoal(updated, params.goal);
updated = ticked ?? updated;
tickNote = ticked
? `\n\nGoal ticked [x] in ${PLAN_REL}.`
: `\n\nNo exact goal line matched your wording -- tick it [x] in ${PLAN_REL} yourself.`;
}
writePlan(ctx, appendLog(updated, `${stamp()} ${outcome.logEntry}`));
updateWidget(ctx);
return result(outcome.resultText + tickNote, outcome.isError);
}
return result(outcome.resultText, outcome.isError);
},
@@ -300,7 +314,7 @@ export async function decideSignOff(
if (judge.error) {
const partial = judge.output ? `\n\npartial judge output:\n${judge.output}` : "";
return {
resultText: `Judge ran but failed (${judge.error}). Accepted inconclusive — logged. Tick the goal [x] in ${PLAN_REL} yourself.${partial}`,
resultText: `Judge ran but failed (${judge.error}). Accepted inconclusive — logged.${partial}`,
isError: false,
logEntry: `signed off "${input.goal}" (judge inconclusive: ran but failed: ${oneLine(judge.error)})`,
};
@@ -312,7 +326,7 @@ export async function decideSignOff(
if (verdict === "accept") {
return {
resultText: `Sign-off ACCEPTED. Tick the goal [x] in ${PLAN_REL} (the log line is already appended).\n\n--- judge ---\n${reasoning}`,
resultText: `Sign-off ACCEPTED (log line appended).\n\n--- judge ---\n${reasoning}`,
isError: false,
logEntry: `signed off "${input.goal}" (judge accept)`,
};
@@ -327,13 +341,25 @@ export async function decideSignOff(
}
// No VERDICT line: same fail-forward as a judge error -- the judge ran but didn't answer.
return {
resultText: `Judge returned no VERDICT line. Accepted inconclusive — logged. Tick the goal [x] in ${PLAN_REL} yourself.\n\n--- judge ---\n${reasoning || "(no output)"}`,
resultText: `Judge returned no VERDICT line. Accepted inconclusive — logged.\n\n--- judge ---\n${reasoning || "(no output)"}`,
isError: false,
logEntry: `signed off "${input.goal}" (judge inconclusive: no VERDICT line)`,
};
}
/** Append one line under ## Log (creating the section at EOF if absent). The extension's only write. */
/** Tick the goal line whose subject exactly matches `goal` (trimmed, case-insensitive) to [x].
* Null when there is no unique exact match (wording drift / duplicates) -- the caller then asks the
* agent to tick it itself. Reuses GOAL_LINE; deliberately NOT fuzzy, that's the judge's job. */
export function tickGoal(plan: string, goal: string): string | null {
const lines = plan.split("\n");
const want = goal.trim().toLowerCase();
const hits = lines.flatMap((l, i) => (GOAL_LINE.exec(l)?.[2].trim().toLowerCase() === want ? [i] : []));
if (hits.length !== 1) return null;
lines[hits[0]] = lines[hits[0]].replace(/\[[ xX/-]\]/, "[x]");
return lines.join("\n");
}
/** Append one line under ## Log (creating the section at EOF if absent). */
export function appendLog(text: string, entry: string): string {
const lines = text.split("\n");
const line = `- ${entry}`;
+5 -3
View File
@@ -96,9 +96,11 @@ export const completeGoalDescription =
"table plus how to read it, a metric plus what it shows -- not a bare claim). The read must show " +
"success POSITIVELY happened, not just that failures were avoided. Then call this with the goal's " +
"text (the line after 'goal:'; small wording drift is fine). A fresh read-only judge inspects the " +
"repo, runs the goal's verify command if it names one, and returns accept or reject with what's " +
"missing. On accept (or if the judge itself is unavailable), a sign-off line is appended to ## Log " +
"and YOU tick the goal [x]. On reject the goal stays open.";
"LIVE WORKING TREE (uncommitted changes included; committing first is for durability, not " +
"visibility), runs the goal's verify command if it names one, and returns accept or reject with " +
"what's missing. On accept (or if the judge itself failed), a sign-off line is appended to ## Log " +
"and the goal is ticked [x] for you; the result says if you must tick it yourself. On reject the " +
"goal stays open.";
export const completeGoalParamDescription = "The goal's text: the line after 'goal:' in the plan file.";
+32
View File
@@ -0,0 +1,32 @@
import { describe, expect, it } from "vitest";
import { tickGoal } from "../src/index.js";
const plan = `# Plan
## Goals
1. [/] goal: Implement the cache layer
- tasks:
1. [x] wire client
2. [ ] goal: Ship the docs
## Log
`;
describe("tickGoal (sign-off ticks the goal; agent only ticks on wording drift)", () => {
it("ticks the exact-matching goal line, case-insensitive, leaving subtasks alone", () => {
const out = tickGoal(plan, "implement the CACHE layer");
expect(out).toContain("1. [x] goal: Implement the cache layer");
expect(out).toContain("1. [x] wire client"); // subtask untouched (was already x)
expect(out).toContain("2. [ ] goal: Ship the docs"); // other goal untouched
});
it("returns null on wording drift (fuzzy matching is the judge's job, not TypeScript's)", () => {
expect(tickGoal(plan, "Implement caching")).toBeNull();
});
it("returns null when the subject matches more than one goal line", () => {
const dup = `${plan}3. [ ] goal: Ship the docs\n`;
expect(tickGoal(dup, "Ship the docs")).toBeNull();
});
});