mirror of
https://github.com/wassname/pi-goals.git
synced 2026-07-24 13:10:41 +08:00
Co-Authored-By: Claudypoo <288921227+claudypoo@users.noreply.github.com>
105 lines
5.1 KiB
Diff
105 lines
5.1 KiB
Diff
diff --git a/README.md b/README.md
|
|
index ce4056a..5485002 100644
|
|
--- a/README.md
|
|
+++ b/README.md
|
|
@@ -145,9 +145,9 @@ else is the agent editing the file. It reads the goal's `evidence:` block from `
|
|
reasoning comes back in the result.
|
|
|
|
The judge defaults to the current session model and streams partial output while it runs. If the
|
|
-current model is not visible to the extension, `CompleteGoal` does not fall back to Pi's implicit
|
|
-default; it signs off as `judge inconclusive` and tells you to set `/goals judge <provider/model>`.
|
|
-Point it at another model for an independent cross-family check.
|
|
+session model is not visible to the extension, the `--model` flag is omitted and pi uses its own
|
|
+configured default, so the judge always runs. `/goals judge <provider/model>` is an optional override
|
|
+for an independent cross-family check; never required.
|
|
|
|
## Prompts
|
|
|
|
diff --git a/src/index.ts b/src/index.ts
|
|
index 9eb2a16..14784f7 100644
|
|
--- a/src/index.ts
|
|
+++ b/src/index.ts
|
|
@@ -326,7 +326,7 @@ export default function piGoalsExtension(pi: ExtensionAPI): void {
|
|
durationMs,
|
|
verifyCommand: goal.verify ?? undefined,
|
|
verifyExitCode: outcome.kind === "verify_failed" ? outcome.exitCode : undefined,
|
|
- judgeModel: judgeModel ?? "no explicit judge model",
|
|
+ judgeModel: judgeModel ?? "pi default",
|
|
reasoning,
|
|
isError: res.isError,
|
|
};
|
|
@@ -522,14 +522,6 @@ async function decideSignOff(
|
|
};
|
|
}
|
|
}
|
|
- if (!judgeModel) {
|
|
- const reason = "no explicit judge model available; set /goals judge <provider/model>";
|
|
- return {
|
|
- outcome: { kind: "accepted_inconclusive", reason },
|
|
- reasoning: `VERDICT: inconclusive\nreason: ${reason}`,
|
|
- durationMs: Date.now() - startedAt,
|
|
- };
|
|
- }
|
|
const verdict = await runJudge(goal, evidence, paths, verifyResult, judgeModel, cwd, signal, onUpdate);
|
|
const outcome: SignOff =
|
|
verdict.kind === "accepted"
|
|
@@ -573,13 +565,25 @@ type JudgeResult =
|
|
| { kind: "rejected"; missing: string; reasoning: string; durationMs: number }
|
|
| { kind: "inconclusive"; reason: string; reasoning: string; durationMs: number };
|
|
|
|
+/** Stage 2: a read-only pi subprocess inspects the evidence against the repo and returns a verdict. */
|
|
+/** Build the pi argv for the read-only judge. `--model` is omitted when no explicit/session model is
|
|
+ * set, so pi falls back to its configured default -- the judge always runs, never pre-emptively
|
|
+ * fails as "no model". Exported for a unit test that locks this invariant (an empty `--model ""`
|
|
+ * would make every sign-off silently inconclusive). */
|
|
+export function buildJudgeArgs(judgeModel: string | null): string[] {
|
|
+ const args = ["--mode", "json", "-p", "--no-session"];
|
|
+ if (judgeModel) args.push("--model", judgeModel);
|
|
+ args.push("--tools", JUDGE_TOOLS.join(","), "--exclude-tools", JUDGE_BLOCKED_TOOLS.join(","), "--append-system-prompt", evidenceJudgeSystem);
|
|
+ return args;
|
|
+}
|
|
+
|
|
/** Stage 2: a read-only pi subprocess inspects the evidence against the repo and returns a verdict. */
|
|
async function runJudge(
|
|
goal: Goal,
|
|
evidence: string,
|
|
paths: string[],
|
|
verifyResult: { command: string; exitCode: number; outputTail: string } | null,
|
|
- judgeModel: string,
|
|
+ judgeModel: string | null,
|
|
cwd: string,
|
|
signal: AbortSignal | undefined,
|
|
onUpdate?: (partial: { content: Array<{ type: "text"; text: string }>; details: SignOffDetails }) => void,
|
|
@@ -600,14 +604,14 @@ async function runJudge(
|
|
evidence,
|
|
paths,
|
|
});
|
|
- const args = ["--mode", "json", "-p", "--no-session", "--model", judgeModel, "--tools", JUDGE_TOOLS.join(","), "--exclude-tools", JUDGE_BLOCKED_TOOLS.join(","), "--append-system-prompt", evidenceJudgeSystem];
|
|
+ const args = buildJudgeArgs(judgeModel);
|
|
args.push(task);
|
|
|
|
emit("spawning", `Spawning read-only judge for: ${goal.subject}`);
|
|
const inv = getPiInvocation(args);
|
|
- // FIXME(side-effect): pi -p --no-session clones the repo into the PARENT of cwd (so alongside
|
|
- // the working dir), leaving a stale directory. The judge should run in a temp dir or inside the
|
|
- // existing repo checkout so it doesn't pollute the user's workspace.
|
|
+ // The judge runs in-place against this checkout (cwd is passed to spawn and the read-only tools
|
|
+ // read from it); pi --no-session does not clone into the parent. Proven and re-checked by
|
|
+ // scripts/check-judge-footprint.sh, which reproduces this invocation and asserts no parent clone.
|
|
const judge = await new Promise<{ output: string; error?: string; aborted?: boolean }>((resolve) => {
|
|
let settled = false;
|
|
let stdoutBuffer = "";
|
|
diff --git a/src/prompts.ts b/src/prompts.ts
|
|
index 03faea8..3b8d270 100644
|
|
--- a/src/prompts.ts
|
|
+++ b/src/prompts.ts
|
|
@@ -117,8 +117,6 @@ export function planInjection(p: {
|
|
counts: { done: number; open: number };
|
|
}): string {
|
|
if (!p.activeGoal) {
|
|
- // FIXME(heading): user wants the heading to show ".pi/goals.md: <title>" so the filename is explicit
|
|
- // even in the injection. Currently says "Goals (goals.md):" which is close but not the same.
|
|
return `.pi/goals.md: ${p.title}\nNo active goal. ${p.counts.open} open, ${p.counts.done} done. Pick the next goal (set its checkbox to [/]) or run /goals.`;
|
|
}
|
|
const subtasks = p.activeGoal.openSubtasks.length
|