balladeer 1.0.19 → 1.0.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/README.md +18 -0
  2. package/dist/anchoring.js +2 -2
  3. package/dist/anchors-schema.d.ts +3 -1
  4. package/dist/anchors-schema.js +5 -3
  5. package/dist/commands/judge.d.ts +50 -1
  6. package/dist/commands/judge.js +768 -110
  7. package/dist/commands/mcp.d.ts +4 -0
  8. package/dist/commands/mcp.js +24 -1
  9. package/dist/currency.js +9 -3
  10. package/dist/guidance-hook.mjs +13 -3
  11. package/dist/headless-agent.d.ts +2 -0
  12. package/dist/headless-agent.js +5 -1
  13. package/dist/judge-brief.d.ts +11 -5
  14. package/dist/judge-brief.js +26 -10
  15. package/dist/judge-evidence.d.ts +87 -0
  16. package/dist/judge-evidence.js +393 -0
  17. package/dist/judge-hook.d.ts +14 -1
  18. package/dist/judge-hook.js +34 -5
  19. package/dist/judge-review.d.ts +10 -0
  20. package/dist/judge-review.js +36 -0
  21. package/dist/judge-said.d.ts +1 -0
  22. package/dist/judge-said.js +3 -0
  23. package/dist/promise-meaning.d.ts +4 -0
  24. package/dist/promise-review-client.d.ts +84 -0
  25. package/dist/promise-review-client.js +357 -0
  26. package/dist/push-operation.d.ts +61 -0
  27. package/dist/push-operation.js +472 -0
  28. package/dist/read-only-review.d.ts +2 -0
  29. package/dist/read-only-review.js +2 -0
  30. package/dist/review-backstop.d.ts +20 -0
  31. package/dist/review-backstop.js +54 -0
  32. package/dist/review-reporting.d.ts +32 -0
  33. package/dist/review-reporting.js +319 -0
  34. package/dist/review-selection.d.ts +33 -0
  35. package/dist/review-selection.js +64 -0
  36. package/dist/review-settings.d.ts +6 -0
  37. package/dist/review-settings.js +9 -0
  38. package/dist/self-update.d.ts +9 -0
  39. package/dist/self-update.js +35 -1
  40. package/dist/user-scope.d.ts +1 -10
  41. package/dist/user-scope.js +12 -7
  42. package/dist/wire.d.ts +3 -3
  43. package/dist/wire.js +1 -1
  44. package/package.json +1 -1
@@ -1,3 +1,4 @@
1
+ import { type SelfUpdateDeps } from "../self-update.js";
1
2
  import { type StoredAgent } from "../store.js";
2
3
  export { forwarderHeaders, selectAgent, type AgentSelection } from "../agent.js";
3
4
  export type McpOptions = Readonly<{
@@ -15,6 +16,9 @@ export type McpOptions = Readonly<{
15
16
  stdin: NodeJS.ReadableStream;
16
17
  write: (text: string) => void;
17
18
  error: (text: string) => void;
19
+ now?: () => number;
20
+ /** Runs the background update; injected so tests never reach npm. */
21
+ startUpdate?: SelfUpdateDeps["run"];
18
22
  }>;
19
23
  export type ProbeResult = Readonly<{
20
24
  kind: "answered";
@@ -1,10 +1,11 @@
1
1
  import { createInterface } from "node:readline";
2
2
  import { callAgentTool, forwarderHeaders, noAgentCredentialSentence, safeJson, selectAgent, structuredString, updateLine, } from "../agent.js";
3
- import { noteServerVersion, updateNotice } from "../currency.js";
3
+ import { newerVersionPublished, noteServerVersion, updateNotice } from "../currency.js";
4
4
  import { repositoryHint, repositoryHints } from "../repository.js";
5
5
  import { CLI_VERSION } from "../wire.js";
6
6
  import { untrackedLine } from "./guidance.js";
7
7
  import { hooksAwaitingTrust, hooksAwaitingTrustNotice, trustingHost } from "../hook-trust.js";
8
+ import { startSelfUpdateIfDue } from "../self-update.js";
8
9
  import { StoreError, readCredentials } from "../store.js";
9
10
  // The three commands that use an agent connection select and address it through
10
11
  // one module. These are re-exported because this is where the forwarder's
@@ -12,6 +13,8 @@ import { StoreError, readCredentials } from "../store.js";
12
13
  export { forwarderHeaders, selectAgent } from "../agent.js";
13
14
  const MAX_REQUEST_BYTES = 1024 * 1024;
14
15
  const MAX_RESPONSE_BYTES = 4 * 1024 * 1024;
16
+ /** How often one long-running forwarder looks at whether an update is due. */
17
+ const UPDATE_CHECK_MS = 60 * 60 * 1000;
15
18
  /**
16
19
  * One real call over the connection just issued, before anything claims it
17
20
  * works.
@@ -195,6 +198,8 @@ export async function runMcp(options) {
195
198
  // user-scope hook had just switched off. It installs nothing now.
196
199
  const { agent } = selection;
197
200
  let saidUpdate = false;
201
+ let updateCheckedAt = Number.NEGATIVE_INFINITY;
202
+ const clock = options.now ?? Date.now;
198
203
  // Which host this session belongs to is known from its hello; whether its
199
204
  // hooks are waiting for the person's trust is asked at the first tool call,
200
205
  // by which time a trusted session-start hook has certainly run.
@@ -230,6 +235,24 @@ export async function runMcp(options) {
230
235
  saidUpdate = true;
231
236
  options.error(`${notice}\n`);
232
237
  }
238
+ // The guidance hook is not the only way in. Codex skips a hook nobody has
239
+ // trusted, and a Codex-only laptop (Luke's, at Didero) then never started
240
+ // an update at all, while this forwarder ran every session. So the
241
+ // forwarder starts the same background install, under the same stamp,
242
+ // looking at most once an hour however many frames pass.
243
+ if (newerVersionPublished() !== undefined && clock() - updateCheckedAt >= UPDATE_CHECK_MS) {
244
+ updateCheckedAt = clock();
245
+ try {
246
+ startSelfUpdateIfDue(newerVersionPublished(), {
247
+ environment: options.environment,
248
+ now: clock,
249
+ ...(options.startUpdate ? { run: options.startUpdate } : {}),
250
+ });
251
+ }
252
+ catch {
253
+ /* An update that cannot even be considered never costs the session its tools. */
254
+ }
255
+ }
233
256
  if (response.status === 401) {
234
257
  options.write(`${revokedConnectionFrame(frameId(line), options.controlPlane)}\n`);
235
258
  options.error("Balladeer refused this agent connection. Rotate it in Balladeer.\n");
package/dist/currency.js CHANGED
@@ -60,10 +60,16 @@ export function noteServerVersion(headers) {
60
60
  newestSeen = newest;
61
61
  const advice = headers.get(CLI_ADVICE_HEADER);
62
62
  const line = advice === null ? "" : bounded(advice);
63
+ // On this copy's own major the background update installs it (it lands
64
+ // from 1.0.21), so there is nothing to tell anyone to run. Another major
65
+ // is a choice a person makes.
66
+ const sameMajor = newest.split(".")[0] === CLI_VERSION.split(".")[0];
63
67
  notice =
64
- line === ""
65
- ? `A newer Balladeer is available (${newest}; this is ${CLI_VERSION}). Run npx -y balladeer@latest setup --refresh to update the command and instructions, then restart this session.`
66
- : line;
68
+ line !== ""
69
+ ? line
70
+ : sameMajor
71
+ ? `A newer Balladeer is available (${newest}; this is ${CLI_VERSION}). It installs in the background; nothing needs to be run.`
72
+ : `A newer Balladeer is available (${newest}; this is ${CLI_VERSION}). Run npx -y balladeer@latest setup --refresh to update the command and instructions, then restart this session.`;
67
73
  }
68
74
  /** The one line this run prints about itself, or nothing when it is current. */
69
75
  export function updateNotice() {
@@ -2152,7 +2152,7 @@ function relayHook(input, directory) {
2152
2152
  }
2153
2153
 
2154
2154
  // src/wire.ts
2155
- var CLI_VERSION = "1.0.19";
2155
+ var CLI_VERSION = "1.0.21";
2156
2156
  var CLIENT_HEADER = "x-balladeer-client";
2157
2157
  var UPDATE_STATE_HEADER = "x-balladeer-update-state";
2158
2158
  var HOOK_HOST_HEADER = "x-balladeer-hook";
@@ -2289,7 +2289,8 @@ function noteServerVersion(headers) {
2289
2289
  newestSeen = newest;
2290
2290
  const advice = headers.get(CLI_ADVICE_HEADER);
2291
2291
  const line = advice === null ? "" : bounded(advice);
2292
- notice = line === "" ? `A newer Balladeer is available (${newest}; this is ${CLI_VERSION}). Run npx -y balladeer@latest setup --refresh to update the command and instructions, then restart this session.` : line;
2292
+ const sameMajor = newest.split(".")[0] === CLI_VERSION.split(".")[0];
2293
+ notice = line !== "" ? line : sameMajor ? `A newer Balladeer is available (${newest}; this is ${CLI_VERSION}). It installs in the background; nothing needs to be run.` : `A newer Balladeer is available (${newest}; this is ${CLI_VERSION}). Run npx -y balladeer@latest setup --refresh to update the command and instructions, then restart this session.`;
2293
2294
  }
2294
2295
  function newerVersionPublished() {
2295
2296
  return newestSeen;
@@ -2334,6 +2335,13 @@ function due(newer, last, now) {
2334
2335
  const newerTarget = last.toward === void 0 || older(last.toward, newer);
2335
2336
  return newerTarget && since >= HOUR_MS;
2336
2337
  }
2338
+ function updateSpecifier(newer) {
2339
+ if (!VERSION2.test(newer)) return void 0;
2340
+ const [name, major] = HOOK_SPECIFIER.split("@");
2341
+ if (newer.split(".")[0] !== major) return void 0;
2342
+ if (!older(CLI_VERSION, newer)) return void 0;
2343
+ return `${name}@${newer}`;
2344
+ }
2337
2345
  function writePrivate(path, value, environment) {
2338
2346
  mkdirSync3(configHome(environment), { recursive: true, mode: 448 });
2339
2347
  writeFileSync2(path, JSON.stringify(value) + "\n", { mode: 384 });
@@ -2375,9 +2383,11 @@ function detached(command, args, environment) {
2375
2383
  }
2376
2384
  function startSelfUpdateIfDue(newer, deps) {
2377
2385
  if (newer === void 0) return false;
2386
+ const specifier = updateSpecifier(newer);
2387
+ if (specifier === void 0) return false;
2378
2388
  const now = deps.now ?? Date.now;
2379
2389
  if (!due(newer, lastAttempt(deps.environment), now())) return false;
2380
- const launch = resolveLaunch(HOOK_SPECIFIER, deps.environment, deps.execPath, deps.exists);
2390
+ const launch = resolveLaunch(specifier, deps.environment, deps.execPath, deps.exists);
2381
2391
  let started;
2382
2392
  try {
2383
2393
  started = (deps.run ?? detached)(launch.command, launch.args, launch.environment) !== false;
@@ -133,6 +133,7 @@ export declare function verifyLogin(client: HeadlessClient, options: Readonly<{
133
133
  cwd: string;
134
134
  spawn?: SpawnLike;
135
135
  killGroup?: KillGroup;
136
+ deadline?: number;
136
137
  }>): Promise<LoginCheck>;
137
138
  /**
138
139
  * The client to run, or none. `auto` tries the hook's own client first, then
@@ -144,6 +145,7 @@ export declare function resolveClient(requested: HeadlessClient | "auto", option
144
145
  prefer?: HeadlessClient;
145
146
  spawn?: SpawnLike;
146
147
  killGroup?: KillGroup;
148
+ deadline?: number;
147
149
  }>): Promise<LoginCheck | undefined>;
148
150
  /**
149
151
  * The last JSON object in a piece of text.
@@ -99,13 +99,15 @@ export function codexArguments(input) {
99
99
  * dropped, the same way the compatibility canary treats them.
100
100
  */
101
101
  export async function verifyLogin(client, options) {
102
+ if (options.deadline !== undefined && Date.now() >= options.deadline)
103
+ return { client, verified: false };
102
104
  const env = agentEnvironment(options.env);
103
105
  const run = await runOwnedProcess({
104
106
  command: clientBinary(client, options.env),
105
107
  args: client === "claude" ? ["auth", "status", "--json"] : ["login", "status"],
106
108
  cwd: options.cwd,
107
109
  env,
108
- timeoutMs: 15_000,
110
+ timeoutMs: Math.max(1, Math.min(15_000, (options.deadline ?? Number.POSITIVE_INFINITY) - Date.now())),
109
111
  maxOutputBytes: 64 * 1024,
110
112
  ...(options.spawn === undefined ? {} : { spawn: options.spawn }),
111
113
  ...(options.killGroup === undefined ? {} : { killGroup: options.killGroup }),
@@ -136,6 +138,8 @@ export async function resolveClient(requested, options) {
136
138
  ? ["codex", "claude"]
137
139
  : ["claude", "codex"];
138
140
  for (const client of order) {
141
+ if (options.deadline !== undefined && Date.now() >= options.deadline)
142
+ return undefined;
139
143
  const check = await verifyLogin(client, options);
140
144
  if (check.verified)
141
145
  return check;
@@ -2,10 +2,10 @@ import type { PromiseAnchors } from "./anchors-schema.js";
2
2
  import type { BehaviorMap } from "./behavior-map-schema.js";
3
3
  import type { PromiseDocument } from "./promise-meaning.js";
4
4
  /**
5
- * The fixed instructions a judge works from, `judge-brief/v5`.
5
+ * The fixed instructions a judge works from, `judge-brief/v7`.
6
6
  *
7
7
  * One promise, one change, one disposable checkout at the change's head. The
8
- * rules are the contract's: cases first, linked test first, broken only with a
8
+ * rules are the contract's: cases first, source-grounded review, broken only with a
9
9
  * command that fails with the change and passes without it, kept only after
10
10
  * each passing case was exercised, and could not tell for everything else,
11
11
  * with what stopped the judge. The runner checks the reproduction itself by
@@ -15,7 +15,13 @@ import type { PromiseDocument } from "./promise-meaning.js";
15
15
  * The text is a constant so a verdict can name the exact brief it was given.
16
16
  * Changing a word here is a new revision.
17
17
  *
18
- * v5 is v3's rules word for word; what it added is a section of the prompt
18
+ * v7 requires a substantive code review before settling a verdict: trace affected
19
+ * behavior, assess test blind spots, and preserve risks and uncertainty locally.
20
+ * Tests support the review; writing new tests is optional. v6 requires explicit
21
+ * commands for passing-case evidence and names every new
22
+ * evidence file. The runner executes frozen artifacts in fresh checkouts, then
23
+ * a separate local reviewer assesses their semantic fidelity. v5 used v3's
24
+ * rules; what it added is a section of the prompt
19
25
  * (1 October 2026, with promise anchors): a promise with anchors and no
20
26
  * behavior map is handed its anchors, the linked tests the graph gives at
21
27
  * head, and the files one import step from its anchor files, where v3 said
@@ -24,7 +30,7 @@ import type { PromiseDocument } from "./promise-meaning.js";
24
30
  * adopted on 30 September, so it is never reused. The judge with anchors in
25
31
  * place of a map has not been measured yet.
26
32
  */
27
- export declare const JUDGE_BRIEF_REVISION = "judge-brief/v5";
33
+ export declare const JUDGE_BRIEF_REVISION = "judge-brief/v7";
28
34
  /**
29
35
  * What `judge-brief/v3` added to v1, and all it added (Robert, 30 September
30
36
  * 2026): a way forward when the tests need something the machine lacks.
@@ -37,7 +43,7 @@ export declare const JUDGE_BRIEF_REVISION = "judge-brief/v5";
37
43
  * thing in the answer is `nextStep`, the one step that would let the tests run.
38
44
  */
39
45
  export declare const JUDGE_BRIEF_SETUP_RULE = "9. Only when you would otherwise answer \"could_not_tell\" because the tests need something this machine does not have, such as a test database, a running service, a browser or an environment variable, and only after you have tried to show a break or a keep as the rules above say: look for how the repository documents running its tests, in its README, its agent instruction files (AGENTS.md, CLAUDE.md and the like) and its package scripts, and try the steps it documents. Take no step it does not document, and even among those never install system software, and never ask for or read credentials. If the tests then run, carry on under the rules above. If they still cannot run, or trying would leave no time to answer, the answer stays \"could_not_tell\", never \"kept\", and you add \"nextStep\" to your JSON answer: the one step that would let the tests run, written so the person's agent can act on it, such as the exact documented command that starts the test database. Otherwise leave nextStep out.";
40
- export declare const JUDGE_BRIEF = "You are judging one change to this repository against one promise about the software's behavior. A named person agreed the promise's meaning. Your working directory is a disposable copy of the repository checked out at the change's head commit; anything you write here is thrown away afterwards.\n\nDecide whether this change keeps the promise, breaks it, or whether you could not tell, and back what you say with evidence you produced yourself.\n\nRules:\n\n1. Read the promise's cases first. The passing cases (passing-1, passing-2, and so on, in the order listed) say what must keep happening. The failing cases (failing-1, and so on) say what must never happen. The non-goals say what the promise does not cover; a change there is not a break.\n2. If linked tests are listed, run them first, before anything else. If the runner already reports a linked test's outcome below, use it and rerun only when you need its output.\n3. Read the code, not descriptions of it. Commit messages, comments, documentation and pull request text say what someone intended, not what the code does.\n4. Report \"broken\" only with a command that fails (exits non-zero) with this change and passes (exits zero) without it, and only after you have run it both ways yourself. To run it without the change, check out the base commit in this copy with `git checkout --detach <base>`, run the command, then return with `git checkout --detach <head>`. If the command needs a test you wrote, put that test in a new file rather than editing an existing one, so it exists on both commits. The command must run from the repository root or from the directory you name as its cwd, and must not depend on anything outside this copy.\n5. Report \"kept\" only after you have exercised each passing case against the changed code, by running a test or a command that shows it, and list every case you exercised.\n6. Anything else is \"could_not_tell\". Say in notes what stopped you: a missing dependency, a service you could not reach, a case you could not exercise, time running out.\n7. Name the promise, never the person. Do not put anyone's name, email or account in your answer.\n8. Do not push, open or merge pull requests, publish anything, or change anything outside this copy. Do not read or print credentials.\n9. Only when you would otherwise answer \"could_not_tell\" because the tests need something this machine does not have, such as a test database, a running service, a browser or an environment variable, and only after you have tried to show a break or a keep as the rules above say: look for how the repository documents running its tests, in its README, its agent instruction files (AGENTS.md, CLAUDE.md and the like) and its package scripts, and try the steps it documents. Take no step it does not document, and even among those never install system software, and never ask for or read credentials. If the tests then run, carry on under the rules above. If they still cannot run, or trying would leave no time to answer, the answer stays \"could_not_tell\", never \"kept\", and you add \"nextStep\" to your JSON answer: the one step that would let the tests run, written so the person's agent can act on it, such as the exact documented command that starts the test database. Otherwise leave nextStep out.\n\nYour final message must be exactly one JSON object and nothing after it:\n\n{\"promiseId\": \"<the promise id below>\", \"verdict\": \"kept\" | \"broken\" | \"could_not_tell\", \"reproduction\": {\"command\": \"<shell command>\", \"cwd\": \".\"} or null, \"exercisedCases\": [\"passing-1\", ...], \"notes\": \"<at most a few sentences>\"}\n\nA reproduction is required for \"broken\" and optional otherwise.";
46
+ export declare const JUDGE_BRIEF = "You are judging one change to this repository against one promise about the software's behavior. The supplied meaning may be recorded or agreed; use its supplied standing and never infer human agreement or protection. Your working directory is a disposable copy of the repository checked out at the change's head commit; anything you write here is thrown away afterwards.\n\nYour primary job is a substantive code review against this promise. Understand the affected behavior and investigate plausible failures, then decide whether this change keeps the promise, breaks it, or whether you could not tell. Tests and experiments support your review; generating tests is not the goal.\n\nReview approach:\n- Read the actual diff and the relevant implementation at head and base. Trace the affected behavior from its entry points through changed functions, unchanged callers, dependencies and observable effects. Follow a path beyond the supplied anchors when the behavior requires it; shared words or nearby files are not a reason to expand scope.\n- Explain what changed behaviorally and why it matters to this promise. Investigate realistic failure conditions, alternate branches and boundary cases grounded in this code. Consider state, retries, ordering, error handling and permissions where they affect the promised behavior; do not manufacture concerns that do not fit its scope.\n- Inspect the assertions and inputs of relevant existing tests. A passing suite does not settle your review: identify which affected paths it covers and what it misses. Prefer the repository's own entry points and existing checks. Use a focused command, experiment or new test when it resolves a concrete question. Do not mock away the behavior under review or assert implementation spelling instead of observable behavior.\n- Spend the available time on the most consequential uncertainties. Do not stop after a superficial glance or a trivially passing test, and do not pad the review with extra files or tests. Return a concise assessment, not private deliberation: the causal path you established, relevant files you actually inspected, concrete remaining risks and limits of your investigation. State no unresolved risks when you found none; distinguish a plausible concern from an observed failure.\n\nRules:\n\n1. Read the promise's cases first. The passing cases (passing-1, passing-2, and so on, in the order listed) say what must keep happening. The failing cases (failing-1, and so on) say what must never happen. The non-goals say what the promise does not cover; a change there is not a break.\n2. If linked tests are listed, inspect how they exercise this promise and run the relevant checks as part of your investigation. If the runner already reports a linked test's outcome below, use it and rerun only when you need its output. Neither a passing linked test nor its failure substitutes for reviewing the affected code.\n3. Read the code, not descriptions of it. Commit messages, comments, documentation and pull request text say what someone intended, not what the code does.\n4. Report \"broken\" only with a command that fails (exits non-zero) with this change and passes (exits zero) without it, and only after you have run it both ways yourself. To run it without the change, check out the base commit in this copy with `git checkout --detach <base>`, run the command, then return with `git checkout --detach <head>`. If the command needs a test you wrote, put that test in a new file rather than editing an existing one, so it exists on both commits. The command must run from the repository root or from the directory you name as its cwd, and must not depend on anything outside this copy.\n5. Report \"kept\" only after reviewing the affected behavior and exercising each passing case against the changed code, by running a test or a command that shows it. Return \"caseEvidence\" entries containing caseIds, command and cwd for every passing case. One command may cover several cases only when its inputs and assertions exercise each one. The runner will execute these commands itself in a fresh head checkout; listing case IDs or inspecting code alone cannot establish kept. If a concrete concern about this promise remains unresolved, answer \"could_not_tell\" and preserve that concern in your review.\n6. Anything else is \"could_not_tell\". Say in notes what stopped you: a missing dependency, a service you could not reach, a case you could not exercise, time running out.\n7. Name the promise, never the person. Do not put anyone's name, email or account in your answer.\n8. Do not push, open or merge pull requests, publish anything, or change anything outside this copy. Do not read or print credentials.\n9. Only when you would otherwise answer \"could_not_tell\" because the tests need something this machine does not have, such as a test database, a running service, a browser or an environment variable, and only after you have tried to show a break or a keep as the rules above say: look for how the repository documents running its tests, in its README, its agent instruction files (AGENTS.md, CLAUDE.md and the like) and its package scripts, and try the steps it documents. Take no step it does not document, and even among those never install system software, and never ask for or read credentials. If the tests then run, carry on under the rules above. If they still cannot run, or trying would leave no time to answer, the answer stays \"could_not_tell\", never \"kept\", and you add \"nextStep\" to your JSON answer: the one step that would let the tests run, written so the person's agent can act on it, such as the exact documented command that starts the test database. Otherwise leave nextStep out.\n\nList every new file your evidence commands need in \"evidenceFiles\", as repository-relative paths. Include only new regular files, never edits to tracked files or symlinks. Use [] when the commands need no new files. The runner carries only these frozen files into fresh execution checkouts, with the laptop's existing local dependencies. Ignored files or state from your exploration do not travel unless explicitly named. Evidence commands must not change tracked code, their own evidence artifacts or the checked-out commit. A separate local reader reviews whether the commands and assertions actually exercise the promise. Neither independent execution nor that review establishes protection.\n\nYour final message must be exactly one JSON object and nothing after it:\n\n{\"promiseId\": \"<the promise id below>\", \"verdict\": \"kept\" | \"broken\" | \"could_not_tell\", \"reproduction\": {\"command\": \"<shell command>\", \"cwd\": \".\"} or null, \"exercisedCases\": [\"passing-1\", ...], \"caseEvidence\": [{\"caseIds\": [\"passing-1\"], \"command\": \"<shell command>\", \"cwd\": \".\"}], \"evidenceFiles\": [\"<new evidence file>\"], \"review\": {\"summary\": \"<concise behavioral assessment with the causal path and test blind spots>\", \"inspectedFiles\": [\"<repository-relative files actually inspected>\"], \"risks\": [\"<path/function, triggering condition and promised consequence of an unresolved concern>\"], \"limitations\": [\"<what you could not investigate or exercise>\"]}, \"notes\": \"<at most a few sentences>\"}\n\nA reproduction is required for \"broken\" and optional otherwise.";
41
47
  export type JudgeBriefInput = Readonly<{
42
48
  promise: PromiseDocument;
43
49
  map?: BehaviorMap;
@@ -1,8 +1,8 @@
1
1
  /**
2
- * The fixed instructions a judge works from, `judge-brief/v5`.
2
+ * The fixed instructions a judge works from, `judge-brief/v7`.
3
3
  *
4
4
  * One promise, one change, one disposable checkout at the change's head. The
5
- * rules are the contract's: cases first, linked test first, broken only with a
5
+ * rules are the contract's: cases first, source-grounded review, broken only with a
6
6
  * command that fails with the change and passes without it, kept only after
7
7
  * each passing case was exercised, and could not tell for everything else,
8
8
  * with what stopped the judge. The runner checks the reproduction itself by
@@ -12,7 +12,13 @@
12
12
  * The text is a constant so a verdict can name the exact brief it was given.
13
13
  * Changing a word here is a new revision.
14
14
  *
15
- * v5 is v3's rules word for word; what it added is a section of the prompt
15
+ * v7 requires a substantive code review before settling a verdict: trace affected
16
+ * behavior, assess test blind spots, and preserve risks and uncertainty locally.
17
+ * Tests support the review; writing new tests is optional. v6 requires explicit
18
+ * commands for passing-case evidence and names every new
19
+ * evidence file. The runner executes frozen artifacts in fresh checkouts, then
20
+ * a separate local reviewer assesses their semantic fidelity. v5 used v3's
21
+ * rules; what it added is a section of the prompt
16
22
  * (1 October 2026, with promise anchors): a promise with anchors and no
17
23
  * behavior map is handed its anchors, the linked tests the graph gives at
18
24
  * head, and the files one import step from its anchor files, where v3 said
@@ -21,7 +27,7 @@
21
27
  * adopted on 30 September, so it is never reused. The judge with anchors in
22
28
  * place of a map has not been measured yet.
23
29
  */
24
- export const JUDGE_BRIEF_REVISION = "judge-brief/v5";
30
+ export const JUDGE_BRIEF_REVISION = "judge-brief/v7";
25
31
  /**
26
32
  * What `judge-brief/v3` added to v1, and all it added (Robert, 30 September
27
33
  * 2026): a way forward when the tests need something the machine lacks.
@@ -34,25 +40,33 @@ export const JUDGE_BRIEF_REVISION = "judge-brief/v5";
34
40
  * thing in the answer is `nextStep`, the one step that would let the tests run.
35
41
  */
36
42
  export const JUDGE_BRIEF_SETUP_RULE = `9. Only when you would otherwise answer "could_not_tell" because the tests need something this machine does not have, such as a test database, a running service, a browser or an environment variable, and only after you have tried to show a break or a keep as the rules above say: look for how the repository documents running its tests, in its README, its agent instruction files (AGENTS.md, CLAUDE.md and the like) and its package scripts, and try the steps it documents. Take no step it does not document, and even among those never install system software, and never ask for or read credentials. If the tests then run, carry on under the rules above. If they still cannot run, or trying would leave no time to answer, the answer stays "could_not_tell", never "kept", and you add "nextStep" to your JSON answer: the one step that would let the tests run, written so the person's agent can act on it, such as the exact documented command that starts the test database. Otherwise leave nextStep out.`;
37
- export const JUDGE_BRIEF = `You are judging one change to this repository against one promise about the software's behavior. A named person agreed the promise's meaning. Your working directory is a disposable copy of the repository checked out at the change's head commit; anything you write here is thrown away afterwards.
43
+ export const JUDGE_BRIEF = `You are judging one change to this repository against one promise about the software's behavior. The supplied meaning may be recorded or agreed; use its supplied standing and never infer human agreement or protection. Your working directory is a disposable copy of the repository checked out at the change's head commit; anything you write here is thrown away afterwards.
38
44
 
39
- Decide whether this change keeps the promise, breaks it, or whether you could not tell, and back what you say with evidence you produced yourself.
45
+ Your primary job is a substantive code review against this promise. Understand the affected behavior and investigate plausible failures, then decide whether this change keeps the promise, breaks it, or whether you could not tell. Tests and experiments support your review; generating tests is not the goal.
46
+
47
+ Review approach:
48
+ - Read the actual diff and the relevant implementation at head and base. Trace the affected behavior from its entry points through changed functions, unchanged callers, dependencies and observable effects. Follow a path beyond the supplied anchors when the behavior requires it; shared words or nearby files are not a reason to expand scope.
49
+ - Explain what changed behaviorally and why it matters to this promise. Investigate realistic failure conditions, alternate branches and boundary cases grounded in this code. Consider state, retries, ordering, error handling and permissions where they affect the promised behavior; do not manufacture concerns that do not fit its scope.
50
+ - Inspect the assertions and inputs of relevant existing tests. A passing suite does not settle your review: identify which affected paths it covers and what it misses. Prefer the repository's own entry points and existing checks. Use a focused command, experiment or new test when it resolves a concrete question. Do not mock away the behavior under review or assert implementation spelling instead of observable behavior.
51
+ - Spend the available time on the most consequential uncertainties. Do not stop after a superficial glance or a trivially passing test, and do not pad the review with extra files or tests. Return a concise assessment, not private deliberation: the causal path you established, relevant files you actually inspected, concrete remaining risks and limits of your investigation. State no unresolved risks when you found none; distinguish a plausible concern from an observed failure.
40
52
 
41
53
  Rules:
42
54
 
43
55
  1. Read the promise's cases first. The passing cases (passing-1, passing-2, and so on, in the order listed) say what must keep happening. The failing cases (failing-1, and so on) say what must never happen. The non-goals say what the promise does not cover; a change there is not a break.
44
- 2. If linked tests are listed, run them first, before anything else. If the runner already reports a linked test's outcome below, use it and rerun only when you need its output.
56
+ 2. If linked tests are listed, inspect how they exercise this promise and run the relevant checks as part of your investigation. If the runner already reports a linked test's outcome below, use it and rerun only when you need its output. Neither a passing linked test nor its failure substitutes for reviewing the affected code.
45
57
  3. Read the code, not descriptions of it. Commit messages, comments, documentation and pull request text say what someone intended, not what the code does.
46
58
  4. Report "broken" only with a command that fails (exits non-zero) with this change and passes (exits zero) without it, and only after you have run it both ways yourself. To run it without the change, check out the base commit in this copy with \`git checkout --detach <base>\`, run the command, then return with \`git checkout --detach <head>\`. If the command needs a test you wrote, put that test in a new file rather than editing an existing one, so it exists on both commits. The command must run from the repository root or from the directory you name as its cwd, and must not depend on anything outside this copy.
47
- 5. Report "kept" only after you have exercised each passing case against the changed code, by running a test or a command that shows it, and list every case you exercised.
59
+ 5. Report "kept" only after reviewing the affected behavior and exercising each passing case against the changed code, by running a test or a command that shows it. Return "caseEvidence" entries containing caseIds, command and cwd for every passing case. One command may cover several cases only when its inputs and assertions exercise each one. The runner will execute these commands itself in a fresh head checkout; listing case IDs or inspecting code alone cannot establish kept. If a concrete concern about this promise remains unresolved, answer "could_not_tell" and preserve that concern in your review.
48
60
  6. Anything else is "could_not_tell". Say in notes what stopped you: a missing dependency, a service you could not reach, a case you could not exercise, time running out.
49
61
  7. Name the promise, never the person. Do not put anyone's name, email or account in your answer.
50
62
  8. Do not push, open or merge pull requests, publish anything, or change anything outside this copy. Do not read or print credentials.
51
63
  ${JUDGE_BRIEF_SETUP_RULE}
52
64
 
65
+ List every new file your evidence commands need in "evidenceFiles", as repository-relative paths. Include only new regular files, never edits to tracked files or symlinks. Use [] when the commands need no new files. The runner carries only these frozen files into fresh execution checkouts, with the laptop's existing local dependencies. Ignored files or state from your exploration do not travel unless explicitly named. Evidence commands must not change tracked code, their own evidence artifacts or the checked-out commit. A separate local reader reviews whether the commands and assertions actually exercise the promise. Neither independent execution nor that review establishes protection.
66
+
53
67
  Your final message must be exactly one JSON object and nothing after it:
54
68
 
55
- {"promiseId": "<the promise id below>", "verdict": "kept" | "broken" | "could_not_tell", "reproduction": {"command": "<shell command>", "cwd": "."} or null, "exercisedCases": ["passing-1", ...], "notes": "<at most a few sentences>"}
69
+ {"promiseId": "<the promise id below>", "verdict": "kept" | "broken" | "could_not_tell", "reproduction": {"command": "<shell command>", "cwd": "."} or null, "exercisedCases": ["passing-1", ...], "caseEvidence": [{"caseIds": ["passing-1"], "command": "<shell command>", "cwd": "."}], "evidenceFiles": ["<new evidence file>"], "review": {"summary": "<concise behavioral assessment with the causal path and test blind spots>", "inspectedFiles": ["<repository-relative files actually inspected>"], "risks": ["<path/function, triggering condition and promised consequence of an unresolved concern>"], "limitations": ["<what you could not investigate or exercise>"]}, "notes": "<at most a few sentences>"}
56
70
 
57
71
  A reproduction is required for "broken" and optional otherwise.`;
58
72
  const CHANGED_FILES_SHOWN = 300;
@@ -69,10 +83,12 @@ export function judgePrompt(input) {
69
83
  promiseId: input.promise.promiseId,
70
84
  title: input.promise.title,
71
85
  semanticDigest: input.promise.semanticDigest,
86
+ standing: input.promise.standing,
87
+ subject: input.promise.subject,
72
88
  meaning: input.promise.meaning,
73
89
  }, null, 2));
74
90
  if (input.promise.meaning.passingExamples.length === 0)
75
- sections.push("Its passing cases were not available here, so no case can be exercised; answer kept only if you can say from the code that none of the failing cases can happen.");
91
+ sections.push("Its passing cases were not available here, so kept cannot be established by executable case evidence; answer broken with a reproduction or could_not_tell.");
76
92
  sections.push("");
77
93
  if (input.map !== undefined) {
78
94
  // The excerpt repeats the promise printed above; the judge reads it once.
@@ -0,0 +1,87 @@
1
+ import { type HeadlessAgentOptions, type HeadlessAgentResult, type LoginCheck } from "./headless-agent.js";
2
+ import { type PromiseDocument } from "./promise-meaning.js";
3
+ import { type CarriedFile } from "./scratch-worktree.js";
4
+ /** Local execution provenance, not proof that an assertion expresses the promise. */
5
+ export declare const EVIDENCE_PROTOCOL_REVISION = "judge-evidence/v1";
6
+ export type CaseEvidence = Readonly<{
7
+ caseIds: readonly string[];
8
+ command: string;
9
+ cwd: string;
10
+ }>;
11
+ export type FrozenEvidenceFile = CarriedFile & Readonly<{
12
+ bytes: Buffer;
13
+ }>;
14
+ export type FrozenEvidenceManifest = Readonly<{
15
+ files: readonly FrozenEvidenceFile[];
16
+ digest: string;
17
+ }>;
18
+ export type EvidenceReceipt = Readonly<{
19
+ caseIds: readonly string[];
20
+ command: string;
21
+ cwd: string;
22
+ commit: string;
23
+ exitCode: number | null;
24
+ timedOut: boolean;
25
+ aborted: boolean;
26
+ durationMs: number;
27
+ artifactDigest: string;
28
+ dependencyMode: "shared-local";
29
+ /** Local lockfile fingerprints describe declared dependencies, not installed-byte integrity. */
30
+ localDependencyLocks: readonly CarriedFile[];
31
+ nodeVersion: string;
32
+ platform: NodeJS.Platform;
33
+ output: string;
34
+ }>;
35
+ export type EvidenceExecution = Readonly<{
36
+ receipts: readonly EvidenceReceipt[];
37
+ rejected: string | null;
38
+ stopped: boolean;
39
+ }>;
40
+ type LocalInput = Readonly<{
41
+ root: string;
42
+ sourceTree: string;
43
+ head: string;
44
+ manifest: FrozenEvidenceManifest;
45
+ env: NodeJS.ProcessEnv;
46
+ timeoutMs?: number;
47
+ signal?: AbortSignal;
48
+ scratch?: string;
49
+ }>;
50
+ export declare function freezeEvidenceFiles(sourceTree: string, paths: readonly string[]): Promise<{
51
+ ok: true;
52
+ manifest: FrozenEvidenceManifest;
53
+ } | {
54
+ ok: false;
55
+ reason: string;
56
+ }>;
57
+ export declare function executeKeptEvidence(input: LocalInput & Readonly<{
58
+ caseEvidence: readonly CaseEvidence[];
59
+ passingCaseIds: readonly string[];
60
+ }>): Promise<EvidenceExecution>;
61
+ export declare function executeFreshReproduction(input: LocalInput & Readonly<{
62
+ base: string;
63
+ command: string;
64
+ cwd: string;
65
+ }>): Promise<EvidenceExecution & Readonly<{
66
+ failsOnHead: boolean;
67
+ passesOnBase: boolean;
68
+ digest: string;
69
+ files: readonly CarriedFile[];
70
+ }>>;
71
+ export type EvidenceReview = Readonly<{
72
+ accepted: boolean;
73
+ reason: string;
74
+ stopped: boolean;
75
+ agent?: HeadlessAgentResult;
76
+ }>;
77
+ /** A fresh local reader assesses semantics; execution receipts alone cannot establish fidelity. */
78
+ export declare function reviewExecutedEvidence(input: LocalInput & Readonly<{
79
+ promise: PromiseDocument;
80
+ base: string;
81
+ claim: "kept" | "broken";
82
+ review?: import("./judge-review.js").CodeReview;
83
+ receipts: readonly EvidenceReceipt[];
84
+ login: LoginCheck;
85
+ runAgent?: (options: HeadlessAgentOptions) => Promise<HeadlessAgentResult>;
86
+ }>): Promise<EvidenceReview>;
87
+ export {};