sortie-dogs 0.12.18 → 0.12.19

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.
@@ -3,5 +3,5 @@
3
3
  * installed project marker without importing every asset body.
4
4
  */
5
5
  export declare const RUNTIME_ASSET_VERSION = "0.3.89-completion-proof-v1";
6
- export declare const V010_RUNTIME_ASSET_VERSION = "0.12.18-review-references-retry-v1";
6
+ export declare const V010_RUNTIME_ASSET_VERSION = "0.12.19-focused-behavior-review-v1";
7
7
  export type RuntimeAssetVersion = typeof RUNTIME_ASSET_VERSION | typeof V010_RUNTIME_ASSET_VERSION;
@@ -3,4 +3,4 @@
3
3
  * installed project marker without importing every asset body.
4
4
  */
5
5
  export const RUNTIME_ASSET_VERSION = "0.3.89-completion-proof-v1";
6
- export const V010_RUNTIME_ASSET_VERSION = "0.12.18-review-references-retry-v1";
6
+ export const V010_RUNTIME_ASSET_VERSION = "0.12.19-focused-behavior-review-v1";
@@ -3,6 +3,6 @@ export declare const CONTRACT_TEXT_LIMITS: Readonly<{
3
3
  title: 160;
4
4
  objective: 2000;
5
5
  statement: 1000;
6
- command: 1000;
6
+ command: 8192;
7
7
  path: 512;
8
8
  }>;
@@ -1,2 +1,2 @@
1
1
  /** Common text bounds shared by handoff, manifest and goal evidence validation. */
2
- export const CONTRACT_TEXT_LIMITS = Object.freeze({ title: 160, objective: 2000, statement: 1000, command: 1000, path: 512 });
2
+ export const CONTRACT_TEXT_LIMITS = Object.freeze({ title: 160, objective: 2000, statement: 1000, command: 8192, path: 512 });
@@ -130,6 +130,7 @@ export declare class OperatorMissionRuntime {
130
130
  start(root: string, requirements: unknown, replaceRequirements?: boolean, options?: {
131
131
  kind?: OperatorMission["kind"];
132
132
  context?: MissionContext[];
133
+ cancelledRunID?: string;
133
134
  }): Promise<OperatorMission>;
134
135
  update(root: string, change: (state: OperatorMission) => void): Promise<OperatorMission>;
135
136
  /** Keep host-accepted criteria ahead of new text, including an exactly linked settled predecessor. */
@@ -240,16 +240,19 @@ export class OperatorMissionRuntime {
240
240
  }
241
241
  if (previous)
242
242
  await this.save(this.file(root, `.${previous.id}`), previous);
243
+ // A cancelled standalone/legacy run has no mission-owned runID. Only an explicit
244
+ // replacement may link it; normal continuation must not silently inherit its goal.
245
+ const predecessor = previous?.runID ?? previous?.supersededRunID ??
246
+ (replaceRequirements ? options.cancelledRunID : undefined);
243
247
  const state = { version: "0.12", id: `mission-${randomUUID()}`, root, requests: [request],
244
248
  kind: options.kind ?? "implementation",
245
249
  context: (options.context ?? []).filter(item => item.id !== request.id),
246
250
  requirements: requirements.map((text, index) => ({ id: `R${index + 1}`, text })), phase: "open",
247
251
  ...(replaceRequirements ? { requirementsReplaced: true } : {}),
248
252
  coordinator: null, callID: null, dispatchOpen: false, runID: null, plans: 0, progress: [], submission: null,
249
- ...(previous?.phase === "cancelled" &&
250
- (previous.runID === null || request.id !== previous.requests[0]?.id) &&
251
- (previous.runID ?? previous.supersededRunID)
252
- ? { supersededRunID: (previous.runID ?? previous.supersededRunID) } : {}) };
253
+ ...((previous === undefined || previous.phase === "cancelled") &&
254
+ (previous?.runID === null || previous === undefined || request.id !== previous.requests[0]?.id) && predecessor
255
+ ? { supersededRunID: predecessor } : {}) };
253
256
  await this.save(this.file(root), state);
254
257
  return state;
255
258
  });
@@ -356,17 +359,20 @@ export function missionPlan(mission, raw) {
356
359
  throw new Error(`mission-unit-${index + 1}: ${field} must be paths`);
357
360
  return [...new Set(entries.map(item => normalizeExecutionScope(item)))];
358
361
  };
359
- // Scheduling coverage is not semantic proof. Only a single requirement is unambiguous.
360
- const ids = value.requirement_ids ?? (mission.requirements.length === 1 ? [mission.requirements[0].id] : []);
362
+ // A sole unit owns the whole request. This schedules work; it does not prove acceptance.
363
+ const ids = value.requirement_ids ?? (raw.length === 1 || mission.requirements.length === 1 ? mission.requirements.map(item => item.id) : []);
361
364
  if (!Array.isArray(ids) || ids.length === 0 || !ids.every(id => mission.requirements.some(item => item.id === id))) {
362
- throw new Error(`mission-unit-${index + 1}: requirement_ids must name the related R IDs; multi-requirement work needs explicit coverage`);
365
+ throw new Error(`mission-unit-${index + 1}: requirement_ids must name the related R IDs; splitting multiple requirements across units needs explicit coverage`);
363
366
  }
364
367
  if (!Array.isArray(value.validation) || value.validation.length === 0 ||
365
368
  !value.validation.every(command => typeof command === "string" && command.trim())) {
366
369
  throw new Error(`mission-unit-${index + 1}: validation must contain exact commands; final command proves the unit`);
367
370
  }
371
+ const validation = value.validation.map(missionValidationCommand);
372
+ // Retain last occurrences so removing a redundant check preserves the final proof command.
368
373
  return { id: `unit-${index + 1}`, title: line("title"), objective: line("objective"), read: paths("read"), write: paths("write"),
369
- validation: value.validation.map(missionValidationCommand), acceptance_indices: [...new Set(ids.map(id => mission.requirements.findIndex(item => item.id === id)))] };
374
+ validation: validation.filter((command, i) => validation.lastIndexOf(command) === i),
375
+ acceptance_indices: [...new Set(ids.map(id => mission.requirements.findIndex(item => item.id === id)))] };
370
376
  });
371
377
  // The serial engine counts new proof milestones. Units sharing the same final suite are one
372
378
  // milestone, so coalesce them instead of making the model repair a bookkeeping rejection.
@@ -462,7 +468,8 @@ export function missionReviewTraces(mission, raw) {
462
468
  throw new Error("mission-review-traces: supply concise implementation/test traces");
463
469
  }
464
470
  const grouped = raw.map(text => ({ text,
465
- ids: /^\s*((?:R\d+\s*[/,]?\s*)+):/u.exec(text)?.[1]?.match(/R\d+/gu) ?? [] }));
471
+ ids: [...text.matchAll(/(?:^|[.;]\s+|[\r\n])\s*((?:R\d+\s*[/,]?\s*)+):/gu)]
472
+ .flatMap(match => match[1].match(/R\d+/gu) ?? []) }));
466
473
  if (grouped.every(item => item.ids.length === 0) && raw.length === mission.requirements.length)
467
474
  return raw;
468
475
  const unknown = grouped.flatMap(item => item.ids).filter(id => !mission.requirements.some(requirement => requirement.id === id));
@@ -13,6 +13,40 @@ import { normalizeManifestScope } from "../core/path.js";
13
13
  import { declaredArtifacts } from "./declared-artifacts.js";
14
14
  const exec = promisify(execFile);
15
15
  const record = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
16
+ /** Use the space left by short references for longer requested branches, without starving later references. */
17
+ function focusedAllowances(sizes, budget) {
18
+ const allowances = sizes.map(() => 0);
19
+ let remaining = budget;
20
+ let pending = sizes.map((_, index) => index);
21
+ while (pending.length && remaining > 0) {
22
+ const share = Math.floor(remaining / pending.length);
23
+ const complete = pending.filter(index => sizes[index] <= share);
24
+ if (!complete.length) {
25
+ for (const index of pending)
26
+ allowances[index] = share;
27
+ for (const index of pending.slice(0, remaining - share * pending.length))
28
+ allowances[index]++;
29
+ break;
30
+ }
31
+ for (const index of complete) {
32
+ allowances[index] = sizes[index];
33
+ remaining -= sizes[index];
34
+ }
35
+ pending = pending.filter(index => !complete.includes(index));
36
+ }
37
+ return allowances;
38
+ }
39
+ function visibleLineCount(lines, allowance) {
40
+ let bytes = 0, count = 0;
41
+ for (const line of lines) {
42
+ const size = Buffer.byteLength(line);
43
+ if (bytes + size > allowance)
44
+ break;
45
+ bytes += size;
46
+ count++;
47
+ }
48
+ return count;
49
+ }
16
50
  export async function missionReviewBaseline(directory) {
17
51
  try {
18
52
  const { stdout } = await exec("git", ["rev-parse", "--verify", "HEAD^{commit}"], { cwd: directory });
@@ -99,7 +133,8 @@ export async function completedMissionReviewPrompts(mission, profile, root, requ
99
133
  export async function missionReviewSource(directory, run, evidence = [], baseline, priorScope) {
100
134
  const scope = missionReviewScope(priorScope, run);
101
135
  const hash = createHash("sha256").update(JSON.stringify({ baseline, scope, units: run.units.map(unit => ({ unit: unit.unit, hashes: unit.hashes })) }));
102
- let selected = "";
136
+ const writes = [...new Set(scope.write.map(path => path === "." ? path : normalizeManifestScope(path).path))];
137
+ const focused = [];
103
138
  for (const entry of evidence) {
104
139
  const absolute = resolve(directory, entry.path);
105
140
  const local = relative(resolve(directory), absolute);
@@ -125,21 +160,15 @@ export async function missionReviewSource(directory, run, evidence = [], baselin
125
160
  stream.on("data", chunk => hash.update(chunk));
126
161
  const lines = createInterface({ input: stream, crlfDelay: Infinity });
127
162
  stream.on("error", error => lines.emit("error", error));
128
- selected += `\n--- evidence: ${entry.path}:${entry.offset} ---\n`;
129
- // Share the focused budget rather than silently starving later requested error branches.
130
- const allowance = Math.floor(11_000 / evidence.length);
131
- let number = 0, bytes = 0, truncated = false;
163
+ const excerpt = [];
164
+ let number = 0, bytes = 0;
132
165
  try {
133
166
  for await (const line of lines) {
134
167
  number++;
135
168
  if (number >= entry.offset && number < entry.offset + entry.limit) {
136
- const text = `${number}: ${line.slice(0, 2_000)}\n`, size = Buffer.byteLength(text);
137
- if (!truncated && bytes + size <= allowance) {
138
- selected += text;
139
- bytes += size;
140
- }
141
- else
142
- truncated = true;
169
+ const text = `${number}: ${line.slice(0, 2_000)}\n`;
170
+ excerpt.push(text);
171
+ bytes += Buffer.byteLength(text);
143
172
  }
144
173
  }
145
174
  }
@@ -149,14 +178,42 @@ export async function missionReviewSource(directory, run, evidence = [], baselin
149
178
  }
150
179
  if (entry.offset > number)
151
180
  throw new Error(`offset ${entry.offset} exceeds ${number} lines; select existing lines`);
152
- if (truncated)
153
- selected += `[FOCUSED EXCERPT TRUNCATED: ${entry.path}:${entry.offset}; request a smaller range]\n`;
181
+ focused.push({ entry, lines: excerpt, bytes });
154
182
  }
155
183
  catch (error) {
156
184
  throw fail(error instanceof Error ? error.message : String(error));
157
185
  }
158
186
  }
159
- const writes = [...new Set(scope.write.map(path => path === "." ? path : normalizeManifestScope(path).path))];
187
+ // Keep room for the baseline diff. Reserve notices only after the allocated whole lines show
188
+ // truncation; pre-reserving every possible notice can hide all six otherwise fitting references.
189
+ const heading = (entry) => `\n--- evidence: ${entry.path}:${entry.offset} ---\n`;
190
+ const truncated = (entry) => `[FOCUSED EXCERPT TRUNCATED: ${entry.path}:${entry.offset}; request a smaller range]\n`;
191
+ // Read-only reviews retain their smaller existing envelope; code reviews may use the space
192
+ // previously reserved for automatic diff prefixes, while leaving room for changed-file context.
193
+ const limit = writes.length ? 18_000 : 11_000;
194
+ const headings = focused.reduce((size, { entry }) => size + Buffer.byteLength(heading(entry)), 0);
195
+ const needsNotice = focused.map(() => false);
196
+ let allowances;
197
+ while (true) {
198
+ const notices = focused.reduce((size, { entry }, index) => size + (needsNotice[index] ? Buffer.byteLength(truncated(entry)) : 0), 0);
199
+ allowances = focusedAllowances(focused.map(item => item.bytes), Math.max(0, limit - headings - notices));
200
+ let changed = false;
201
+ for (const [index, item] of focused.entries()) {
202
+ if (!needsNotice[index] && visibleLineCount(item.lines, allowances[index]) < item.lines.length) {
203
+ needsNotice[index] = true;
204
+ changed = true;
205
+ }
206
+ }
207
+ if (!changed)
208
+ break;
209
+ }
210
+ let selected = "";
211
+ for (const [index, item] of focused.entries()) {
212
+ selected += heading(item.entry);
213
+ selected += item.lines.slice(0, visibleLineCount(item.lines, allowances[index])).join("");
214
+ if (needsNotice[index])
215
+ selected += truncated(item.entry);
216
+ }
160
217
  if (writes.length === 0)
161
218
  return { fingerprint: `sha256:${hash.digest("hex")}`,
162
219
  excerpt: selected + "[Read-only units: no declared output files. Review the supplied observations, traces and validation evidence.]" };
@@ -1,4 +1,5 @@
1
1
  import { V010_RUNTIME_ASSET_VERSION } from "../asset-version.js";
2
+ import { MISSION_BEHAVIOR_REVIEW } from "../runtime-mission-assets.js";
2
3
  import { cancelledMissionRetainsAcceptance, OperatorContractError, OperatorRuntime } from "../core/operator-runtime.js";
3
4
  import { DEFAULT_OPERATOR_PROPOSAL_BUDGET, OPERATOR_APPROVAL_CONTRACT, OPERATOR_PROPOSAL_BUDGET_CAPS, OPERATOR_PROPOSAL_REVISION_CONTRACT, OperatorProposalBudgetError, OperatorProposalRuntime } from "../core/operator-proposal.js";
4
5
  import { CANONICAL_AGENT_ROLES, canonicalAgent, profileAgent, profileTool, V010_RUNTIME_PROFILE } from "../core/runtime-profile.js";
@@ -561,7 +562,9 @@ export function createProfiledPlugin(profile, assetVersion) {
561
562
  // Only the same source can retain a cancelled run's acceptance as a prefix.
562
563
  if (previous.sourceRefs[0] !== `user:${mission.requests[0]?.id}` ||
563
564
  !previous.sourceRefs.every(ref => mission.requests.some(request => ref === `user:${request.id}`))) {
564
- throw new Error("mission-cancelled-source-unproven");
565
+ throw new Error("mission-cancelled-source-unproven: the cancelled run's source_refs do not prove this is the same request. " +
566
+ "If the user changed or narrowed the goal, start_mission with intent=replace and the complete current requirements; " +
567
+ "otherwise preserve the earlier accepted criteria and establish the original source before continuing.");
565
568
  }
566
569
  return previous.acceptance.every((text, index) => mission.requirements[index]?.text === text)
567
570
  ? mission : missions.carryForward(root, mission.id, previous.acceptance);
@@ -1355,9 +1358,11 @@ export function createProfiledPlugin(profile, assetVersion) {
1355
1358
  if (relocated)
1356
1359
  return JSON.stringify({ ...relocated, budget: await control.currentBudget(context.sessionID) });
1357
1360
  }
1361
+ const prior = await operators.read(context.sessionID);
1358
1362
  let mission = await retainCancelledMissionAcceptance(context.sessionID, await missions.start(context.sessionID, args.requirements, args.intent === "replace" || args.intent === "new", {
1359
1363
  kind: args.kind === "operation" ? "operation" : "implementation",
1360
1364
  context: missionConversationContext(await messages(context.sessionID)),
1365
+ ...(args.intent === "replace" && prior?.phase === "cancelled" ? { cancelledRunID: prior.runID } : {}),
1361
1366
  }));
1362
1367
  if (!mission.reviewBaseline) {
1363
1368
  const baseline = await missionReviewBaseline(input.directory);
@@ -1454,13 +1459,21 @@ export function createProfiledPlugin(profile, assetVersion) {
1454
1459
  args: { units: { type: "array", minItems: 1, maxItems: 32, items: { type: "object", additionalProperties: false,
1455
1460
  properties: { title: { type: "string" }, objective: { type: "string" },
1456
1461
  read: { ...stringList, description: "Inputs that affect validation, not an allowlist for observation. Do not include whole live session/database/log trees just to inspect them." }, write: stringList,
1457
- validation: stringList, requirement_ids: stringList }, required: ["title", "objective", "write", "validation"] } },
1462
+ validation: stringList, requirement_ids: { ...stringList, description: "Related requirement IDs. A single unit inherits all requirements when omitted; specify coverage when splitting work across units." } }, required: ["title", "objective", "write", "validation"] } },
1458
1463
  execution: { type: "object", properties: { commands: stringList, directory: { type: "string" } },
1459
1464
  required: ["commands", "directory"], additionalProperties: false,
1460
1465
  description: "For an operation: the actual run/grade commands, not a preflight or NO_START check. Host observes their native shell completion; no handwritten proof file is needed.", "x-sortie-optional": true },
1461
1466
  reason: { type: "string", "x-sortie-optional": true } }, execute: async (args, context) => {
1462
1467
  const { root, mission } = await missionAuthority(context.sessionID);
1463
- return serializeDispatchTransition(root, () => declareMissionUnits(root, context.sessionID, mission, args.units, args.reason, args.execution));
1468
+ try {
1469
+ return await serializeDispatchTransition(root, () => declareMissionUnits(root, context.sessionID, mission, args.units, args.reason, args.execution));
1470
+ }
1471
+ catch (error) {
1472
+ if (!(error instanceof OperatorContractError))
1473
+ throw error;
1474
+ return JSON.stringify({ status: "invalid-plan", diagnostics: error.diagnostics, diagnostics_truncated: error.diagnostics_truncated,
1475
+ next_action: "Correct the reported field or control-storage problem and retry plan_units directly. Keep the original requirements and existing run; do not cancel or repeat passed work to repair the plan." });
1476
+ }
1464
1477
  } };
1465
1478
  tools[expandUnit] = { description: "Coordinator: extend the stopped unit's write scope immediately within the original request. Keeps original requirements and cumulative budget; generates a replacement Worker contract. No Operator approval or handwritten manifest repair is needed. Use only after the Worker has returned.",
1466
1479
  args: { unit_id: stringSchema, paths: stringList, reason: stringSchema }, execute: async (args, context) => {
@@ -1517,9 +1530,10 @@ export function createProfiledPlugin(profile, assetVersion) {
1517
1530
  description: `🔎 ${run.units[0].unit.title}`, prompt: [
1518
1531
  `candidate_id: ${mission.id}`, `review_phase: ${phase}`, "canonical_validation_exit: 0", `risk_tags: [${risk.join(", ")}]`,
1519
1532
  "Review this candidate independently. Use the language of the requirements/traces. Invoke no tools. First line: exactly PASS, FINDINGS or EVIDENCE_GAPS.",
1520
- "Use EVIDENCE_GAPS only when no concrete source/test defect is established and a specific acceptance-relevant behavior or required validation cannot be settled by the supplied artifact. Name the affected path and why the missing evidence matters; do not request a generic route inventory or raw history for incidental workflow constraints already covered by a concise Worker trace. Any concrete defect uses FINDINGS.",
1533
+ "Use EVIDENCE_GAPS only when no concrete source/test defect is established and a specific acceptance-relevant behavior or required validation cannot be settled by the supplied artifact. Name the affected path and why the missing evidence matters; do not request a generic route inventory. Any concrete defect uses FINDINGS.",
1521
1534
  "This Reviewer's native outcome and final acceptance can only be observed after this review. List those as deferred Operator checks, not as a reason to request another review. Still assess all available source, validation and historical evidence independently.",
1522
1535
  "For changed failure paths, assess the public return value, error and post-failure state together against existing API behavior; matching error text alone does not establish compatibility.",
1536
+ MISSION_BEHAVIOR_REVIEW,
1523
1537
  `acceptance: ${JSON.stringify(run.acceptance)}`, `changedLogicSummary: ${JSON.stringify(traces)}`,
1524
1538
  ...run.acceptance.map((_, i) => `acceptance[${i}] -> changedLogicSummary[${i}]`),
1525
1539
  `manifest: ${JSON.stringify(run.units.map(unit => unit.unit))}`, `sourceFingerprint: ${source.fingerprint}`,
@@ -4,7 +4,7 @@ import { V010_RUNTIME_PROFILE as profile, profileAgent, renderProfileInstruction
4
4
  import { GOAL_DECLARATION_FORMAT } from "./core/goal-declaration-format.js";
5
5
  import { STRATEGY_TRIGGERS, SOURCE_REVIEW_PHASES, SOURCE_REVIEW_RISK_TAGS } from "./core/consultation.js";
6
6
  import { SCOUT_EVIDENCE_CODES } from "./core/scout-contract.js";
7
- import { missionOperatorContent, missionCoordinatorContent, missionWorkerContent } from "./runtime-mission-assets.js";
7
+ import { MISSION_BEHAVIOR_REVIEW, missionOperatorContent, missionCoordinatorContent, missionWorkerContent } from "./runtime-mission-assets.js";
8
8
  const coordinator = profileAgent(profile, "dog-coordinator");
9
9
  const operator = profileAgent(profile, "dog-operator");
10
10
  const worker = profileAgent(profile, "dog-worker");
@@ -420,10 +420,9 @@ For a multi-target change, verify each affected target when the source shows ind
420
420
 
421
421
  Do not demand a generic creation/binding/mutation inventory, every possible value representation, or a
422
422
  cross-product of independent dimensions just because the construct already exists. Absence of that
423
- inventory alone is not an evidence gap. Do not demand raw logs or historical process traces for
424
- incidental workflow constraints when a concise Worker trace reports them and the supplied record does
425
- not contradict it. The Operator owns final comparison to the original request; still flag a missing
426
- required behavioral result or canonical validation evidence.
423
+ inventory alone is not an evidence gap.
424
+
425
+ ${MISSION_BEHAVIOR_REVIEW}
427
426
 
428
427
  ## Mission review verdict
429
428
 
@@ -1514,7 +1514,7 @@ END_INTERNAL_TERMINAL_PROOF_FIXTURE
1514
1514
  readonly name: "dog-reviewer";
1515
1515
  readonly version: "0.3.89-completion-proof-v1";
1516
1516
  readonly installPath: "agent/dog-reviewer.md";
1517
- readonly content: "---\ndescription: Independent source reviewer for dog-coordinator\nmode: subagent\npermission:\n bash: deny\n webfetch: deny\n task: deny\n question: deny\n glob: deny\n grep: deny\n edit: deny\n list: deny\n write: deny\n patch: deny\n read: deny\ntools:\n bash: false\n webfetch: false\n task: false\n question: false\n glob: false\n grep: false\n edit: false\n list: false\n write: false\n patch: false\n read: false\n---\n# dog-reviewer\n\nAccept only one bounded SourceReview request from dog-coordinator after canonical\nvalidation for one high-risk candidate. Review only the supplied acceptance criteria, exact\nmanifest, changedLogicSummary, supplied changed-code excerpts, and validation evidence. Confirm every acceptance item explicitly\nmaps to at least one changedLogicSummary entry and assess that changed logic against the mapped\nacceptance item. Missing or incomplete coverage is a concrete finding, never PASS.\nRequire one indexed acceptance[i] -> changedLogicSummary[j] mapping line per acceptance item and\nreject a missing index or unequal mapping count before assessing the changed logic.\nAlso require each acceptance item to map to a concrete exercising test/input/branch and result. A broad\nsuite PASS without criterion-level exercise evidence is insufficient. When one item contains materially\ndifferent syntax forms, value shapes, scopes, or error paths, reject PASS unless representative traces cover\neach path or the artifact proves they share one implementation path.\nCheck contract-derived expectations only for accepted behavior. For an applicable failure or composite-value\ncriterion, error-only or helper-only assertions can leave public result/state behavior unproved. Accept justified\nN/A dimensions; do not demand new behavior, new review rounds, or redundant checks outside accepted scope.\nWhen changed files include generator inputs or checked-in generated outputs, require the canonical generator\ncommand, a stable post-generation diff, and validation executed after generation. Reject PASS if helper logic\nexists only in a generated output, regeneration removes behavior, or validation predates the generated candidate.\nDo not request raw logs or full source files, review low-risk candidates, expand scope, or dispatch\nanother agent.\nTreat those supplied fields as the complete bounded SourceReview artifact; use only that artifact and invoke no tools.\nDo not infer that a branch or exemption is absent from source because a prose summary omits it.\nIf the supplied excerpts do not establish a claim, report an evidence gap and request the exact\nbranch/helper excerpt in the next artifact; do not prescribe a source fix for an unproven defect.\n\nReturn one concise PASS or concrete-finding response only to dog-coordinator before the\ncoordinator commit. Write every finding, evidence, and required-fix sentence in the language the\nsupplied artifact uses for its own prose, one statement per line, and keep verdict values,\nidentifiers, paths, and commands verbatim. Do not implement, remediate, resolve blockers, edit,\nstage, commit, or become user-facing. Remain host-routed: do not require or identify a provider, vendor, model, variant,\nor transport.\n";
1517
+ readonly content: "---\ndescription: Independent source reviewer for dog-coordinator\nmode: subagent\npermission:\n bash: deny\n webfetch: deny\n task: deny\n question: deny\n glob: deny\n grep: deny\n edit: deny\n list: deny\n write: deny\n patch: deny\n read: deny\ntools:\n bash: false\n webfetch: false\n task: false\n question: false\n glob: false\n grep: false\n edit: false\n list: false\n write: false\n patch: false\n read: false\n---\n# dog-reviewer\n\nAccept only one bounded SourceReview request from dog-coordinator after canonical\nvalidation for one high-risk candidate. Review only the supplied acceptance criteria, exact\nmanifest, changedLogicSummary, supplied changed-code excerpts, and validation evidence. Confirm every acceptance item explicitly\nmaps to at least one changedLogicSummary entry and assess that changed logic against the mapped\nacceptance item. Missing behavioral evidence is an evidence gap; a demonstrated source/test defect is a finding.\nRequire one indexed acceptance[i] -> changedLogicSummary[j] mapping line per acceptance item and\nreject a missing index or unequal mapping count before assessing the changed logic.\nFor behavioral acceptance items, require a concrete exercising test/input/branch and result. A broad\nsuite PASS without criterion-level exercise evidence is insufficient. When one item contains materially\ndifferent syntax forms, value shapes, scopes, or error paths, reject PASS unless representative traces cover\neach path or the artifact proves they share one implementation path.\nCheck contract-derived expectations only for accepted behavior. For an applicable failure or composite-value\ncriterion, error-only or helper-only assertions can leave public result/state behavior unproved. Accept justified\nN/A dimensions; do not demand new behavior, new review rounds, or redundant checks outside accepted scope.\nWhen changed files include generator inputs or checked-in generated outputs, require the canonical generator\ncommand, a stable post-generation diff, and validation executed after generation. Reject PASS if helper logic\nexists only in a generated output, regeneration removes behavior, or validation predates the generated candidate.\nDo not request raw logs or full source files, review low-risk candidates, expand scope, or dispatch\nanother agent.\nTreat those supplied fields as the complete bounded SourceReview artifact; use only that artifact and invoke no tools.\nDo not infer that a branch or exemption is absent from source because a prose summary omits it.\nIf the supplied excerpts do not establish a claim, report an evidence gap and request the exact\nbranch/helper excerpt in the next artifact; do not prescribe a source fix for an unproven defect.\n\nReturn one concise PASS or concrete-finding response only to dog-coordinator before the\ncoordinator commit. Write every finding, evidence, and required-fix sentence in the language the\nsupplied artifact uses for its own prose, one statement per line, and keep verdict values,\nidentifiers, paths, and commands verbatim. Do not implement, remediate, resolve blockers, edit,\nstage, commit, or become user-facing. Remain host-routed: do not require or identify a provider, vendor, model, variant,\nor transport.\n";
1518
1518
  }, {
1519
1519
  readonly name: "dog-advisor";
1520
1520
  readonly version: "0.3.89-completion-proof-v1";
@@ -1824,10 +1824,10 @@ Accept only one bounded SourceReview request from dog-coordinator after canonica
1824
1824
  validation for one high-risk candidate. Review only the supplied acceptance criteria, exact
1825
1825
  manifest, changedLogicSummary, supplied changed-code excerpts, and validation evidence. Confirm every acceptance item explicitly
1826
1826
  maps to at least one changedLogicSummary entry and assess that changed logic against the mapped
1827
- acceptance item. Missing or incomplete coverage is a concrete finding, never PASS.
1827
+ acceptance item. Missing behavioral evidence is an evidence gap; a demonstrated source/test defect is a finding.
1828
1828
  Require one indexed acceptance[i] -> changedLogicSummary[j] mapping line per acceptance item and
1829
1829
  reject a missing index or unequal mapping count before assessing the changed logic.
1830
- Also require each acceptance item to map to a concrete exercising test/input/branch and result. A broad
1830
+ For behavioral acceptance items, require a concrete exercising test/input/branch and result. A broad
1831
1831
  suite PASS without criterion-level exercise evidence is insufficient. When one item contains materially
1832
1832
  different syntax forms, value shapes, scopes, or error paths, reject PASS unless representative traces cover
1833
1833
  each path or the artifact proves they share one implementation path.
@@ -2,5 +2,7 @@ import { type RuntimeProfile } from "./core/runtime-profile.ts";
2
2
  /** Repository-local dependency environment shared by all units; excluded from reviewed and captured source. */
3
3
  export declare const TOOL_ENVIRONMENT = ".sortie-env";
4
4
  export declare function missionOperatorContent(profile: RuntimeProfile, version: string): string;
5
+ /** Shared by the installed Reviewer and its host-generated mission prompt. */
6
+ export declare const MISSION_BEHAVIOR_REVIEW = "For a changed failure handler, inspect the operation it calls and the public inputs reaching it,\nincluding failures not listed in the new handler. Use the supplied source/tests and established API behavior\nto identify a concrete input that could still violate the requested contract. A passing normal input does\nnot settle a different failure outcome of that same operation. A demonstrable defect is FINDINGS; ask for\nan excerpt or result only when a specific material outcome cannot be settled. Do not invent new behavior,\nrequire an exhaustive exception inventory, or recommend catching every exception.\n\nBehavioral requirements need concrete input/result evidence. For incidental workflow constraints such as\ncache settings or command-path spelling, use the existing host observations and concise compliance trace;\nabsence of a separate settings dump or historical log is not itself an evidence gap. Flag observed\ncontradictions. The Operator owns final comparison with the original request. If a missing check genuinely\naffects correctness or a requested deliverable, name that consequence and the smallest useful next check.";
5
7
  export declare function missionCoordinatorContent(profile: RuntimeProfile, version: string): string;
6
8
  export declare function missionWorkerContent(profile: RuntimeProfile): string;
@@ -81,7 +81,8 @@ results to guide the following units. Do not invent preparation units or plan-ap
81
81
  For operations, plan_units.execution names the actual run/grade commands and working directory. Keep
82
82
  setup, execution and result collection in the same Worker. The host records native execution; NO_START
83
83
  or setup success cannot complete the operation. Reward/score zero is a result, not failure to execute.
84
- Use requirement_ids for each unit when there are multiple requirements; these are related requirements,
84
+ Use requirement_ids when splitting multiple requirements across units; a single unit inherits all requirements
85
+ when they are omitted. These are related requirements,
85
86
  not claims that a command proves every semantic obligation. Compare the final result yourself.
86
87
 
87
88
  Copy returned task fields exactly (V2: subagent_type -> agent, task_id -> sessionID). Do not append to a
@@ -101,6 +102,19 @@ once. Do not invent scores, medals, costs, savings, models or successful checks.
101
102
  existing user authorization and project gates; npm publication remains manual.
102
103
  `;
103
104
  }
105
+ /** Shared by the installed Reviewer and its host-generated mission prompt. */
106
+ export const MISSION_BEHAVIOR_REVIEW = `For a changed failure handler, inspect the operation it calls and the public inputs reaching it,
107
+ including failures not listed in the new handler. Use the supplied source/tests and established API behavior
108
+ to identify a concrete input that could still violate the requested contract. A passing normal input does
109
+ not settle a different failure outcome of that same operation. A demonstrable defect is FINDINGS; ask for
110
+ an excerpt or result only when a specific material outcome cannot be settled. Do not invent new behavior,
111
+ require an exhaustive exception inventory, or recommend catching every exception.
112
+
113
+ Behavioral requirements need concrete input/result evidence. For incidental workflow constraints such as
114
+ cache settings or command-path spelling, use the existing host observations and concise compliance trace;
115
+ absence of a separate settings dump or historical log is not itself an evidence gap. Flag observed
116
+ contradictions. The Operator owns final comparison with the original request. If a missing check genuinely
117
+ affects correctness or a requested deliverable, name that consequence and the smallest useful next check.`;
104
118
  export function missionCoordinatorContent(profile, version) {
105
119
  return `---
106
120
  description: Sortie-dogs ${version} Coordinator — investigation, unit dispatch, scope extension and correction loop.
@@ -144,7 +158,8 @@ select the matching runner/profile before starting rather than changing the pinn
144
158
  Investigate only enough to start the first useful Worker. Prefer a targeted read/reproduction over a broad
145
159
  inventory or speculative full design. Call ${profile.toolPrefix}plan_units with concise units:
146
160
  title, objective, read/write file or directory scopes, validation commands, and related requirement_ids
147
- when there is more than one requirement. For operation missions, include execution with the actual
161
+ when splitting multiple requirements across multiple units; a single unit inherits all requirements when
162
+ requirement_ids is omitted. For operation missions, include execution with the actual
148
163
  run/grade commands and working directory. Setup, launch and result collection normally stay in one Worker;
149
164
  do not forbid execution while assigning that Worker the requirement to execute.
150
165
  When the issue includes a concrete public reproduction, pass its entrypoint, relevant input and observed
@@ -153,7 +168,9 @@ example depends on a working directory or package layout, preserve that context.
153
168
  existing analogous source/tests for the expected contract when available. Avoid separate investigation
154
169
  units just to restate the issue. A test of a neighboring name is not an adjacent check unless it runs
155
170
  the changed branch on a relevant different input; keep validation focused and do not require an extra
156
- test when the existing checks already exercise that boundary.
171
+ test when the existing checks already exercise that boundary. For an exception fix, identify the failing
172
+ operation and ask the Worker to consider its other source/API-backed failure inputs, including ones the
173
+ new handler does not catch. A normal input alone does not check a different failure outcome.
157
174
  For read-only verification, use write: []; do not invent an output file or request write access to inputs.
158
175
  For ordinary diagnostics, use native read/search/shell directly, including while an old run is being
159
176
  reconciled. Do not create a dummy validation/console.log unit just to inspect status. The read list is
@@ -164,7 +181,7 @@ Use absolute native paths for requested global installations or other external o
164
181
  a directory including a not-yet-created tree. These are execution/evidence scopes, not an additional
165
182
  permission grant: the host's native permissions still apply. Include the actual external input/output
166
183
  paths in read/write so validation and review observe them; do not substitute a repository symlink.
167
- Keep all original requirements covered; only a single requirement can be inferred. Last validation command checks
184
+ Keep all original requirements covered. Last validation command checks
168
185
  that unit. If your quick check shows repository-declared dependencies or the test runner are missing, keep
169
186
  setup inside the first unit: declare its checks through the repository-local tool environment ${TOOL_ENVIRONMENT}/
170
187
  (for Python, ${TOOL_ENVIRONMENT}/bin/python -m pytest ...) and let that Worker create it. Never plan a separate setup
@@ -198,7 +215,8 @@ and ask a bounded question in the user's language; do not send generic explorato
198
215
  If the user explicitly requested Advisor input before a decision, do not treat it as optional.
199
216
 
200
217
  After formal validation, call review_mission with risk_tags and one concise implementation/test trace per
201
- requirement. Include the observed result for required behavioral checks, not a speculative route inventory
218
+ requirement. For changed failure behavior, connect the operation and concrete input to the contract-derived
219
+ expected result and observed result in those existing traces. Include required behavioral checks, not a speculative route inventory
202
220
  or raw history to prove incidental process constraints. Recognized tags: ${SOURCE_REVIEW_RISK_TAGS.join(", ")}.
203
221
  High-risk changes require the generated independent ${profileAgent(profile, "dog-reviewer")} task.
204
222
  Low risk uses [] and the host records the skip. The host supplies source excerpts, manifest, requirement
@@ -253,11 +271,13 @@ formal acceptance evidence. Do not repeat a failed command without a concrete so
253
271
  repeat passed checks on unchanged source. Add meaningful tests only when needed by the change/request.
254
272
  For a reported bug, keep the public reproduction's entrypoint, input and layout intact during diagnosis;
255
273
  after a fix, run it again when the available environment permits. If it cannot run, report exactly
256
- what remains unverified instead of substituting a different passing check. When a changed condition
257
- or exception handler also covers an adjacent input demonstrated by the public source/tests, check that
258
- input on the same branch (e.g. a neighboring exception type), rather than checking only a nearby test
259
- name. Derive expected behavior from public code and tests, not hidden evaluator details; skip redundant
260
- checks already covered by formal validation. Return a concrete failed reproduction to Coordinator for
274
+ what remains unverified instead of substituting a different passing check. For a changed condition or
275
+ exception handler, inspect the underlying operation and inputs reaching it, including failures the new
276
+ handler does not catch. Check a materially different failure input when public source/tests or established
277
+ API behavior support the same requested contract; a normal input alone does not check that failure outcome.
278
+ Choose the smallest complete fix, not a catch-all or an exhaustive exception matrix. Derive expected behavior
279
+ from public code and tests, not hidden evaluator details; skip redundant checks already covered by formal validation.
280
+ Return a concrete failed reproduction to Coordinator for
261
281
  the same-goal correction loop rather than declaring the whole task complete.
262
282
  When changing a failure path, check its public return value, error and post-failure state together against
263
283
  the existing API contract; do not stop assertions after matching error text.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sortie-dogs",
3
- "version": "0.12.18",
3
+ "version": "0.12.19",
4
4
  "description": "Bounded agent harness and validated orchestration loop plugin for OpenCode",
5
5
  "keywords": [
6
6
  "opencode",