ultimate-pi 0.14.0 → 0.16.0

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 (92) hide show
  1. package/.agents/skills/harness-debate-plan/SKILL.md +41 -61
  2. package/.agents/skills/harness-governor/SKILL.md +11 -0
  3. package/.agents/skills/harness-orchestration/SKILL.md +5 -3
  4. package/.agents/skills/harness-plan/SKILL.md +11 -9
  5. package/.pi/agents/harness/adversary.md +1 -1
  6. package/.pi/agents/harness/evaluator.md +1 -1
  7. package/.pi/agents/harness/executor.md +1 -1
  8. package/.pi/agents/harness/incident-recorder.md +1 -1
  9. package/.pi/agents/harness/meta-optimizer.md +1 -1
  10. package/.pi/agents/harness/planning/decompose.md +8 -35
  11. package/.pi/agents/harness/planning/execution-plan-author.md +27 -15
  12. package/.pi/agents/harness/planning/hypothesis-validator.md +23 -6
  13. package/.pi/agents/harness/planning/hypothesis.md +4 -27
  14. package/.pi/agents/harness/planning/implementation-researcher.md +43 -0
  15. package/.pi/agents/harness/planning/plan-adversary.md +20 -5
  16. package/.pi/agents/harness/planning/plan-evaluator.md +28 -6
  17. package/.pi/agents/harness/planning/review-integrator.md +23 -10
  18. package/.pi/agents/harness/planning/scout-graphify.md +4 -23
  19. package/.pi/agents/harness/planning/scout-semantic.md +3 -18
  20. package/.pi/agents/harness/planning/scout-structure.md +3 -18
  21. package/.pi/agents/harness/planning/sprint-contract-auditor.md +22 -6
  22. package/.pi/agents/harness/planning/stack-researcher.md +21 -11
  23. package/.pi/agents/harness/tie-breaker.md +1 -1
  24. package/.pi/agents/harness/trace-librarian.md +1 -1
  25. package/.pi/extensions/budget-guard.ts +33 -19
  26. package/.pi/extensions/harness-debate-tools.ts +280 -19
  27. package/.pi/extensions/harness-live-widget.ts +39 -159
  28. package/.pi/extensions/harness-plan-approval.ts +47 -5
  29. package/.pi/extensions/harness-run-context.ts +96 -2
  30. package/.pi/extensions/harness-subagent-submit.ts +195 -0
  31. package/.pi/extensions/lib/debate-bus-core.ts +108 -17
  32. package/.pi/extensions/lib/debate-bus-state.ts +6 -0
  33. package/.pi/extensions/lib/harness-subagent-policy.ts +45 -0
  34. package/.pi/extensions/lib/harness-subagent-submit-pipeline.ts +82 -0
  35. package/.pi/extensions/lib/harness-subagent-submit-registry.ts +172 -0
  36. package/.pi/extensions/lib/harness-subagents-bridge.ts +42 -0
  37. package/.pi/extensions/lib/plan-approval/plan-review.ts +56 -0
  38. package/.pi/extensions/lib/plan-approval/types.ts +1 -0
  39. package/.pi/extensions/lib/plan-debate-eligibility.ts +214 -0
  40. package/.pi/extensions/lib/plan-debate-focus.ts +151 -0
  41. package/.pi/extensions/lib/plan-debate-gate.ts +88 -34
  42. package/.pi/extensions/lib/plan-debate-lane.ts +15 -0
  43. package/.pi/extensions/lib/plan-debate-lanes.ts +44 -0
  44. package/.pi/extensions/lib/plan-debate-round-status.ts +63 -20
  45. package/.pi/extensions/lib/plan-messenger.ts +93 -17
  46. package/.pi/extensions/policy-gate.ts +1 -1
  47. package/.pi/harness/README.md +1 -1
  48. package/.pi/harness/agents.manifest.json +25 -21
  49. package/.pi/harness/docs/adrs/0034-darwin-plan-research-pipeline.md +1 -3
  50. package/.pi/harness/docs/adrs/0035-plan-phase-review-gate.md +13 -5
  51. package/.pi/harness/docs/adrs/0036-implementation-research-and-selective-debate.md +51 -0
  52. package/.pi/harness/docs/adrs/0037-subagent-submit-tools.md +31 -0
  53. package/.pi/harness/docs/adrs/0038-budget-telemetry-only.md +23 -0
  54. package/.pi/harness/docs/adrs/README.md +4 -0
  55. package/.pi/harness/evals/smoke/fixtures/plan-phase/minimal-low-light/artifacts/implementation-research.yaml +28 -0
  56. package/.pi/harness/evals/smoke/fixtures/plan-phase/minimal-low-light/artifacts/review-round-r1.yaml +24 -0
  57. package/.pi/harness/evals/smoke/fixtures/plan-phase/minimal-low-light/artifacts/review-round-r2.yaml +25 -0
  58. package/.pi/harness/evals/smoke/fixtures/plan-phase/minimal-low-light/plan-packet.yaml +196 -0
  59. package/.pi/harness/evals/smoke/fixtures/plan-phase/minimal-low-light/plan-review.md +14 -0
  60. package/.pi/harness/evals/smoke/fixtures/plan-phase/minimal-low-light/research-brief.yaml +62 -0
  61. package/.pi/harness/evals/smoke/fixtures/plan-phase/minimal-med/artifacts/implementation-research.yaml +28 -0
  62. package/.pi/harness/evals/smoke/fixtures/plan-phase/minimal-med/artifacts/review-round-r2.yaml +24 -0
  63. package/.pi/harness/evals/smoke/fixtures/plan-phase/minimal-med/artifacts/review-round-r3.yaml +24 -0
  64. package/.pi/harness/evals/smoke/fixtures/plan-phase/minimal-med/research-brief.yaml +29 -0
  65. package/.pi/harness/evals/smoke/smoke-harness-plan.mjs +97 -16
  66. package/.pi/harness/specs/harness-executor-handoff.schema.json +19 -0
  67. package/.pi/harness/specs/harness-human-required.schema.json +16 -0
  68. package/.pi/harness/specs/plan-implementation-research-brief.schema.json +128 -0
  69. package/.pi/harness/specs/plan-review-round-draft.schema.json +1 -1
  70. package/.pi/harness/specs/plan-scout-findings.schema.json +19 -0
  71. package/.pi/harness/specs/round-result.schema.json +15 -2
  72. package/.pi/lib/harness-agent-output.ts +45 -0
  73. package/.pi/lib/harness-budget-enforce.ts +18 -0
  74. package/.pi/lib/harness-schema-validate.ts +89 -0
  75. package/.pi/lib/harness-spawn-parse.ts +86 -0
  76. package/.pi/lib/harness-subagent-submit-path.ts +41 -0
  77. package/.pi/lib/harness-ui-state.ts +107 -2
  78. package/.pi/prompts/harness-auto.md +2 -2
  79. package/.pi/prompts/harness-plan.md +94 -42
  80. package/.pi/prompts/harness-run.md +2 -2
  81. package/.pi/prompts/planning-rubrics.md +31 -0
  82. package/.pi/scripts/harness-verify.mjs +2 -0
  83. package/.pi/scripts/harness_web/__pycache__/__init__.cpython-314.pyc +0 -0
  84. package/.pi/scripts/harness_web/__pycache__/config.cpython-314.pyc +0 -0
  85. package/.pi/scripts/harness_web/__pycache__/output.cpython-314.pyc +0 -0
  86. package/.pi/scripts/harness_web/__pycache__/scrape.cpython-314.pyc +0 -0
  87. package/.pi/scripts/harness_web/__pycache__/search.cpython-314.pyc +0 -0
  88. package/.pi/scripts/harness_web/__pycache__/search_ddg.cpython-314.pyc +0 -0
  89. package/.pi/scripts/harness_web/__pycache__/search_searxng.cpython-314.pyc +0 -0
  90. package/CHANGELOG.md +21 -0
  91. package/package.json +4 -2
  92. package/vendor/pi-subagents/src/subagents.ts +29 -3
@@ -2,11 +2,16 @@
2
2
  * P0–P3 plan debate tools — bus + pi-messenger transport.
3
3
  */
4
4
 
5
- import { mkdir } from "node:fs/promises";
5
+ import { mkdir, readFile } from "node:fs/promises";
6
6
  import { dirname, join } from "node:path";
7
7
  import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
8
8
  import { Type } from "@sinclair/typebox";
9
+ import { parse as parseYaml } from "yaml";
9
10
  import type { DebateParticipant } from "../lib/debate-orchestrator-types.js";
11
+ import {
12
+ extractLastSubmitCall,
13
+ type MessageLike,
14
+ } from "../lib/harness-agent-output.js";
10
15
  import {
11
16
  getLatestRunContext,
12
17
  getRunIdFromSession,
@@ -14,22 +19,33 @@ import {
14
19
  import { writeYamlFile } from "../lib/harness-yaml.js";
15
20
  import {
16
21
  acceptDebateRound,
22
+ capsForDebate,
17
23
  finalizeDebateConsensus,
18
24
  openDebateBus,
19
25
  } from "./lib/debate-bus-core.js";
20
26
  import { getDebateState } from "./lib/debate-bus-state.js";
21
27
  import { claimExtensionLoad } from "./lib/extension-load-guard.js";
22
28
  import { captureHarnessEvent } from "./lib/harness-posthog.js";
29
+ import { DEBATE_AGENT_SUBMIT_TOOL } from "./lib/harness-subagent-submit-registry.js";
30
+ import {
31
+ type DebateEligibilityInput,
32
+ harnessPlanDebateEligibility,
33
+ } from "./lib/plan-debate-eligibility.js";
23
34
  import {
24
35
  buildPlanReviewRoundEnvelope,
25
36
  type PlanReviewRoundDraft,
26
37
  } from "./lib/plan-debate-envelope.js";
38
+ import {
39
+ getPlanFocusCoverage,
40
+ planDebateOutcomeComplete,
41
+ } from "./lib/plan-debate-focus.js";
27
42
  import {
28
43
  normalizePlanDebateId,
29
44
  planDebateIdForRun,
30
45
  } from "./lib/plan-debate-id.js";
31
46
  import {
32
47
  applyDebateLane,
48
+ applyDebateLaneFromDoc,
33
49
  type DebateLaneKind,
34
50
  debateLaneForAgent,
35
51
  formatApplyLaneMessage,
@@ -40,6 +56,7 @@ import {
40
56
  formatTranscriptForSpawn,
41
57
  getMessengerRoundState,
42
58
  initPlanMessenger,
59
+ loadMessengerState,
43
60
  messengerRoundDebateReady,
44
61
  postMessengerMessage,
45
62
  readRoundTranscript,
@@ -84,13 +101,19 @@ function telemetryRound(
84
101
 
85
102
  function subagentResults(
86
103
  details: unknown,
87
- ): Array<{ agent: string; finalOutput?: string }> {
104
+ ): Array<{ agent: string; finalOutput?: string; messages?: MessageLike[] }> {
88
105
  const d = details as {
89
- results?: Array<{ agent: string; finalOutput?: string }>;
106
+ results?: Array<{
107
+ agent: string;
108
+ finalOutput?: string;
109
+ messages?: MessageLike[];
110
+ }>;
90
111
  };
91
112
  return d?.results ?? [];
92
113
  }
93
114
 
115
+ const USE_SUBMIT_TOOLS = process.env.HARNESS_SUBMIT_TOOLS !== "0";
116
+
94
117
  export default function harnessDebateTools(pi: ExtensionAPI) {
95
118
  if (!claimExtensionLoad("harness-debate-tools", MODULE_URL)) return;
96
119
 
@@ -107,7 +130,34 @@ export default function harnessDebateTools(pi: ExtensionAPI) {
107
130
  let lastRound = 1;
108
131
  for (const result of subagentResults(event.details)) {
109
132
  const lane = debateLaneForAgent(result.agent ?? "");
110
- if (!lane || !result.finalOutput?.trim()) continue;
133
+ if (!lane) continue;
134
+
135
+ const submitTool = DEBATE_AGENT_SUBMIT_TOOL[result.agent ?? ""];
136
+ const submitCall =
137
+ USE_SUBMIT_TOOLS && submitTool && result.messages
138
+ ? extractLastSubmitCall(result.messages, submitTool)
139
+ : null;
140
+
141
+ if (submitCall) {
142
+ const out = await applyDebateLaneFromDoc({
143
+ runDir: rd,
144
+ lane,
145
+ doc: submitCall.document,
146
+ });
147
+ if (out.round_index) lastRound = out.round_index;
148
+ pi.appendEntry("harness-debate-lane-applied", {
149
+ agent: result.agent,
150
+ source: "submit_tool",
151
+ tool: submitCall.toolName,
152
+ ...out,
153
+ });
154
+ applied.push(formatApplyLaneMessage(out));
155
+ continue;
156
+ }
157
+
158
+ if (!result.finalOutput?.trim()) continue;
159
+ if (USE_SUBMIT_TOOLS && submitTool) continue;
160
+
111
161
  const out = await applyDebateLane({
112
162
  runDir: rd,
113
163
  lane,
@@ -122,7 +172,7 @@ export default function harnessDebateTools(pi: ExtensionAPI) {
122
172
  }
123
173
  if (applied.length === 0) return;
124
174
 
125
- const status = await getPlanDebateRoundStatus(rd, lastRound);
175
+ const status = await getPlanDebateRoundStatus(rd, lastRound, runId);
126
176
  pi.sendMessage({
127
177
  customType: "harness-debate-next-step",
128
178
  content: [
@@ -138,28 +188,131 @@ export default function harnessDebateTools(pi: ExtensionAPI) {
138
188
  });
139
189
  });
140
190
 
191
+ pi.registerTool({
192
+ name: "harness_plan_debate_eligibility",
193
+ label: "Plan Debate Eligibility",
194
+ description:
195
+ "Pre-debate profile selection (full|standard|light). Call after DAG pass, before harness_debate_open. Uses risk, fork, implementation/stack briefs — not R1 hypothesis output.",
196
+ parameters: Type.Object({
197
+ risk_level: Type.Optional(
198
+ Type.String({ description: "low | med | high" }),
199
+ ),
200
+ material_fork: Type.Optional(Type.Boolean()),
201
+ dag_pass: Type.Optional(Type.Boolean()),
202
+ dag_manually_patched: Type.Optional(Type.Boolean()),
203
+ implementation_brief_path: Type.Optional(
204
+ Type.String({
205
+ description:
206
+ "Default: artifacts/implementation-research.yaml under run dir",
207
+ }),
208
+ ),
209
+ stack_brief_path: Type.Optional(Type.String()),
210
+ decomposition_path: Type.Optional(Type.String()),
211
+ }),
212
+ async execute(_id, params, _signal, _onUpdate, ctx) {
213
+ const runId = getRunId(ctx);
214
+ const rd = runDir(process.cwd(), runId);
215
+ const p = params as {
216
+ risk_level?: string;
217
+ material_fork?: boolean;
218
+ dag_pass?: boolean;
219
+ dag_manually_patched?: boolean;
220
+ implementation_brief_path?: string;
221
+ stack_brief_path?: string;
222
+ decomposition_path?: string;
223
+ };
224
+ async function loadYaml(
225
+ rel: string,
226
+ ): Promise<Record<string, unknown> | null> {
227
+ try {
228
+ const raw = await readFile(join(rd, rel), "utf-8");
229
+ return parseYaml(raw) as Record<string, unknown>;
230
+ } catch {
231
+ return null;
232
+ }
233
+ }
234
+ const input: DebateEligibilityInput = {
235
+ risk_level: p.risk_level,
236
+ material_fork: p.material_fork,
237
+ dag_pass: p.dag_pass,
238
+ dag_manually_patched: p.dag_manually_patched,
239
+ implementation_brief: await loadYaml(
240
+ p.implementation_brief_path ??
241
+ "artifacts/implementation-research.yaml",
242
+ ),
243
+ stack_brief: await loadYaml(
244
+ p.stack_brief_path ?? "artifacts/stack.yaml",
245
+ ),
246
+ decomposition: await loadYaml(
247
+ p.decomposition_path ?? "artifacts/decomposition.yaml",
248
+ ),
249
+ };
250
+ const result = harnessPlanDebateEligibility(input);
251
+ const lines = [
252
+ `profile: ${result.profile}`,
253
+ `required_focuses: ${result.required_focuses.join(", ")}`,
254
+ `min_focus_rounds: ${result.min_focus_rounds}`,
255
+ `debate_global_cap: ${result.debate_global_cap}`,
256
+ `human_required: ${result.human_required}`,
257
+ ...result.rationale.map((r) => `- ${r}`),
258
+ ];
259
+ return {
260
+ content: [{ type: "text", text: lines.join("\n") }],
261
+ details: result,
262
+ };
263
+ },
264
+ });
265
+
141
266
  pi.registerTool({
142
267
  name: "harness_debate_open",
143
268
  label: "Open Plan Debate",
144
269
  description:
145
- "Open plan-phase debate bus (plan-<run_id>) and initialize pi-messenger inboxes/threads. Call once before Review Gate rounds.",
270
+ "Open plan-phase debate bus (plan-<run_id>) and initialize pi-messenger inboxes/threads. Call once after harness_plan_debate_eligibility.",
146
271
  parameters: Type.Object({
147
272
  debate_id: Type.Optional(
148
273
  Type.String({ description: "Optional; normalized to plan-<run_id>" }),
149
274
  ),
275
+ debate_profile: Type.Optional(
276
+ Type.String({ description: "full | standard | light" }),
277
+ ),
278
+ required_focuses: Type.Optional(
279
+ Type.Array(
280
+ Type.String({ description: "spec | wbs | schedule | quality" }),
281
+ ),
282
+ ),
150
283
  }),
151
284
  async execute(_id, params, _signal, _onUpdate, ctx) {
152
285
  const runId = getRunId(ctx);
153
286
  const projectRoot = process.cwd();
154
- const raw = String((params as { debate_id?: string }).debate_id ?? "");
287
+ const p = params as {
288
+ debate_id?: string;
289
+ debate_profile?: string;
290
+ required_focuses?: string[];
291
+ };
292
+ const raw = String(p.debate_id ?? "");
155
293
  const { debateId, corrected, warning } = normalizePlanDebateId(
156
294
  raw,
157
295
  runId,
158
296
  );
159
- const opened = await openDebateBus(runId, debateId, debateHooks(pi));
297
+ const profile =
298
+ p.debate_profile === "full" ||
299
+ p.debate_profile === "standard" ||
300
+ p.debate_profile === "light"
301
+ ? p.debate_profile
302
+ : "standard";
303
+ const required_focuses = (p.required_focuses ?? []).filter((f) =>
304
+ ["spec", "wbs", "schedule", "quality"].includes(f),
305
+ ) as Array<"spec" | "wbs" | "schedule" | "quality">;
306
+ const opened = await openDebateBus(runId, debateId, debateHooks(pi), {
307
+ debate_profile: profile,
308
+ required_focuses:
309
+ required_focuses.length > 0 ? required_focuses : undefined,
310
+ });
160
311
  await initPlanMessenger(runDir(projectRoot, runId), {
161
312
  runId,
162
313
  debateId,
314
+ debate_profile: profile,
315
+ required_focuses: opened.required_focuses,
163
316
  });
164
317
  const sessionId = ctx.sessionManager.getSessionId();
165
318
  captureHarnessEvent(sessionId, "harness_debate_round", {
@@ -171,6 +324,12 @@ export default function harnessDebateTools(pi: ExtensionAPI) {
171
324
  });
172
325
  const lines = [
173
326
  `Plan debate opened: ${debateId}`,
327
+ `Profile: ${profile}`,
328
+ required_focuses.length
329
+ ? `Required focuses: ${required_focuses.join(", ")}`
330
+ : opened.required_focuses?.length
331
+ ? `Required focuses: ${opened.required_focuses.join(", ")}`
332
+ : "Required focuses: (default all four)",
174
333
  `Messenger: debate-messenger/ (inbox + threads/round-N/transcript.jsonl)`,
175
334
  ];
176
335
  if (warning) lines.push(`Note: ${warning}`);
@@ -187,13 +346,14 @@ export default function harnessDebateTools(pi: ExtensionAPI) {
187
346
  description:
188
347
  "Post a claim/rebuttal/integrate message to the round thread and agent inbox (pi-messenger style). Evaluator posts claims first; adversary rebuts with in_reply_to claim ids.",
189
348
  parameters: Type.Object({
190
- round_index: Type.Number({ description: "1–4" }),
349
+ round_index: Type.Number({ description: "1–12 (monotonic per run)" }),
191
350
  from: Type.String({
192
351
  description:
193
352
  "PlanEvaluatorAgent | PlanAdversaryAgent | ReviewIntegratorAgent | HypothesisValidatorAgent | SprintContractAuditorAgent",
194
353
  }),
195
354
  kind: Type.String({
196
- description: "claim | rebuttal | integrate | audit | system",
355
+ description:
356
+ "claim | rebuttal | clarification | counter | integrate | audit | system",
197
357
  }),
198
358
  body: Type.String(),
199
359
  to: Type.Optional(Type.Array(Type.String())),
@@ -207,7 +367,14 @@ export default function harnessDebateTools(pi: ExtensionAPI) {
207
367
  const p = params as {
208
368
  round_index: number;
209
369
  from: DebateParticipant;
210
- kind: "claim" | "rebuttal" | "integrate" | "audit" | "system";
370
+ kind:
371
+ | "claim"
372
+ | "rebuttal"
373
+ | "clarification"
374
+ | "counter"
375
+ | "integrate"
376
+ | "audit"
377
+ | "system";
211
378
  body: string;
212
379
  to?: Array<DebateParticipant | "broadcast">;
213
380
  in_reply_to?: string[];
@@ -269,7 +436,7 @@ export default function harnessDebateTools(pi: ExtensionAPI) {
269
436
  description:
270
437
  "Validate lane YAML + messenger thread, write review-round-rN.yaml, emit bus round envelope. Parent must not write review-round files directly.",
271
438
  parameters: Type.Object({
272
- round_index: Type.Number({ description: "1–4" }),
439
+ round_index: Type.Number({ description: "1–12 (monotonic per run)" }),
273
440
  integrator_draft: Type.Record(Type.String(), Type.Unknown(), {
274
441
  description: "ReviewIntegrator YAML object (review-round-rN fields)",
275
442
  }),
@@ -300,8 +467,11 @@ export default function harnessDebateTools(pi: ExtensionAPI) {
300
467
  evidence_refs: [`artifacts/review-round-r${roundIndex}.yaml`],
301
468
  });
302
469
 
470
+ const caps = capsForDebate(debateId);
303
471
  const roundState = await getMessengerRoundState(rd, roundIndex);
304
- const mCheck = messengerRoundDebateReady(roundState, roundIndex === 4);
472
+ const mCheck = messengerRoundDebateReady(roundState, roundIndex >= 4, {
473
+ max_exchanges_per_round: caps.max_exchanges_per_round,
474
+ });
305
475
  if (!mCheck.ok) {
306
476
  return {
307
477
  content: [
@@ -393,7 +563,7 @@ export default function harnessDebateTools(pi: ExtensionAPI) {
393
563
  name: "harness_debate_consensus",
394
564
  label: "Finalize Plan Debate Consensus",
395
565
  description:
396
- "After 4 bus rounds, emit consensus packet to .pi/harness/debates/plan-<run_id>.consensus.json",
566
+ "After all focus areas covered (spec|wbs|schedule|quality) and last review_gate_ready true, emit consensus packet to .pi/harness/debates/plan-<run_id>.consensus.json",
397
567
  parameters: Type.Object({
398
568
  rationale: Type.Optional(Type.String()),
399
569
  }),
@@ -401,7 +571,7 @@ export default function harnessDebateTools(pi: ExtensionAPI) {
401
571
  const runId = getRunId(ctx);
402
572
  const rationale =
403
573
  String((params as { rationale?: string }).rationale ?? "").trim() ||
404
- "Plan Review Gate consensus after 4 messenger-backed rounds.";
574
+ "Plan Review Gate consensus after focus coverage and messenger-backed rounds.";
405
575
  const decision = await finalizeDebateConsensus(
406
576
  rationale,
407
577
  debateHooks(pi),
@@ -468,16 +638,30 @@ export default function harnessDebateTools(pi: ExtensionAPI) {
468
638
  description:
469
639
  "List missing lane artifacts and messenger steps for a Review Gate round. Call when resuming after a stop.",
470
640
  parameters: Type.Object({
471
- round_index: Type.Number({ description: "1–4" }),
641
+ round_index: Type.Number({ description: "1–12 (monotonic per run)" }),
642
+ debate_round_focus: Type.Optional(
643
+ Type.String({ description: "spec | wbs | schedule | quality" }),
644
+ ),
472
645
  }),
473
646
  async execute(_id, params, _signal, _onUpdate, ctx) {
474
647
  const runId = getRunId(ctx);
475
- const roundIndex = Number(
476
- (params as { round_index: number }).round_index,
477
- );
648
+ const p = params as {
649
+ round_index: number;
650
+ debate_round_focus?: string;
651
+ };
652
+ const roundIndex = Number(p.round_index);
653
+ const focus =
654
+ p.debate_round_focus === "spec" ||
655
+ p.debate_round_focus === "wbs" ||
656
+ p.debate_round_focus === "schedule" ||
657
+ p.debate_round_focus === "quality"
658
+ ? p.debate_round_focus
659
+ : undefined;
478
660
  const status = await getPlanDebateRoundStatus(
479
661
  runDir(process.cwd(), runId),
480
662
  roundIndex,
663
+ runId,
664
+ focus ? { debate_round_focus: focus } : undefined,
481
665
  );
482
666
  const lines = [
483
667
  `Round ${roundIndex}: ready_for_integrator=${status.ready_for_integrator}`,
@@ -493,6 +677,83 @@ export default function harnessDebateTools(pi: ExtensionAPI) {
493
677
  },
494
678
  });
495
679
 
680
+ pi.registerTool({
681
+ name: "harness_debate_focus_coverage",
682
+ label: "Plan Debate Focus Coverage",
683
+ description:
684
+ "Return which Review Gate focuses (spec|wbs|schedule|quality) are covered by submitted review-round artifacts and whether debate outcome is complete.",
685
+ parameters: Type.Object({}),
686
+ async execute(_id, _params, _signal, _onUpdate, ctx) {
687
+ const runId = getRunId(ctx);
688
+ const rd = runDir(process.cwd(), runId);
689
+ const messenger = await loadMessengerState(rd);
690
+ const requiredFocuses = messenger?.required_focuses;
691
+ const coverage = await getPlanFocusCoverage(rd, { requiredFocuses });
692
+ const caps = capsForDebate(
693
+ planDebateIdForRun(runId),
694
+ messenger?.debate_profile,
695
+ );
696
+ const complete = planDebateOutcomeComplete(coverage, {
697
+ requiredFocuses,
698
+ minRoundIndex: caps.min_focus_rounds,
699
+ });
700
+ const lines = [
701
+ `Profile: ${messenger?.debate_profile ?? "standard"}`,
702
+ `Required: ${(requiredFocuses ?? ["spec", "wbs", "schedule", "quality"]).join(", ")}`,
703
+ `Covered: ${coverage.covered.join(", ") || "(none)"}`,
704
+ coverage.missing.length
705
+ ? `Missing: ${coverage.missing.join(", ")}`
706
+ : "All required focuses covered.",
707
+ `Last round: ${coverage.last_round_index}, review_gate_ready=${coverage.last_review_gate_ready}`,
708
+ `Outcome complete: ${complete}`,
709
+ `Budget: min_focus_rounds=${caps.min_focus_rounds}, max_rounds=${caps.max_rounds}, max_exchanges_per_round=${caps.max_exchanges_per_round}`,
710
+ ];
711
+ return {
712
+ content: [{ type: "text", text: lines.join("\n") }],
713
+ details: {
714
+ coverage,
715
+ caps,
716
+ complete,
717
+ profile: messenger?.debate_profile,
718
+ },
719
+ };
720
+ },
721
+ });
722
+
723
+ pi.registerTool({
724
+ name: "harness_debate_advance_thread",
725
+ label: "Advance Plan Debate Thread",
726
+ description:
727
+ "Ping-pong helper: read round transcript and return next spawn (evaluator clarification vs adversary counter) based on unresolved claim_ids and exchange_count.",
728
+ parameters: Type.Object({
729
+ round_index: Type.Number(),
730
+ }),
731
+ async execute(_id, params, _signal, _onUpdate, ctx) {
732
+ const runId = getRunId(ctx);
733
+ const roundIndex = Number(
734
+ (params as { round_index: number }).round_index,
735
+ );
736
+ const status = await getPlanDebateRoundStatus(
737
+ runDir(process.cwd(), runId),
738
+ roundIndex,
739
+ runId,
740
+ );
741
+ const text = [
742
+ `Round ${roundIndex}: exchange_count=${status.exchange_count}`,
743
+ status.unresolved_claim_ids.length
744
+ ? `Unresolved claims: ${status.unresolved_claim_ids.join(", ")}`
745
+ : "No unresolved claims.",
746
+ status.next_tool
747
+ ? `Next: ${status.next_tool}`
748
+ : "Dialogue complete — spawn review-integrator.",
749
+ ].join("\n");
750
+ return {
751
+ content: [{ type: "text", text }],
752
+ details: status,
753
+ };
754
+ },
755
+ });
756
+
496
757
  pi.registerTool({
497
758
  name: "harness_plan_scope_check",
498
759
  label: "Plan Scope Drift Check",