ultimate-pi 0.24.0 → 0.25.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 (48) hide show
  1. package/.pi/extensions/agt-prompt-guard.ts +20 -6
  2. package/.pi/extensions/harness-auto-compact.ts +94 -0
  3. package/.pi/extensions/harness-debate-tools.ts +26 -2
  4. package/.pi/extensions/harness-live-widget.ts +19 -2
  5. package/.pi/extensions/harness-plan-approval.ts +62 -19
  6. package/.pi/extensions/harness-plan-orchestration.ts +140 -0
  7. package/.pi/extensions/harness-run-context.ts +457 -48
  8. package/.pi/extensions/harness-web-tools.ts +1 -0
  9. package/.pi/extensions/policy-gate.ts +9 -0
  10. package/.pi/harness/agents.manifest.json +1 -1
  11. package/.pi/harness/docs/adrs/0056-agent-native-speed-wiring.md +26 -0
  12. package/.pi/harness/env.harness.template +7 -1
  13. package/.pi/lib/harness-auto-approve.ts +140 -0
  14. package/.pi/lib/harness-auto-compact-policy.ts +85 -0
  15. package/.pi/lib/harness-phase-telemetry.ts +7 -0
  16. package/.pi/lib/harness-phase-worker.ts +23 -0
  17. package/.pi/lib/harness-plan-fsm.ts +162 -0
  18. package/.pi/lib/harness-plan-route.ts +134 -0
  19. package/.pi/lib/harness-posthog.ts +4 -1
  20. package/.pi/lib/harness-remediation.ts +79 -0
  21. package/.pi/lib/harness-repair-brief.ts +2 -2
  22. package/.pi/lib/harness-review-parallel.ts +18 -0
  23. package/.pi/lib/harness-run-context.ts +119 -72
  24. package/.pi/lib/harness-spawn-budget.ts +32 -4
  25. package/.pi/lib/harness-spawn-topology.ts +36 -1
  26. package/.pi/lib/harness-subagent-precheck.ts +3 -2
  27. package/.pi/lib/harness-subagent-progress.ts +8 -5
  28. package/.pi/lib/harness-subagents-bridge.ts +14 -12
  29. package/.pi/lib/harness-vcc-settings.ts +36 -0
  30. package/.pi/lib/plan-approval-readiness.ts +9 -5
  31. package/.pi/lib/plan-debate-eligibility-snapshot.ts +90 -0
  32. package/.pi/lib/plan-debate-eligibility.ts +12 -7
  33. package/.pi/lib/plan-debate-focus.ts +23 -11
  34. package/.pi/lib/plan-debate-gate.ts +71 -29
  35. package/.pi/lib/plan-debate-round-status.ts +23 -8
  36. package/.pi/lib/plan-headless-ux.ts +598 -0
  37. package/.pi/lib/plan-human-gates.ts +24 -85
  38. package/.pi/lib/plan-messenger.ts +3 -3
  39. package/.pi/lib/plan-review-gate.ts +56 -0
  40. package/.pi/prompts/harness-abort.md +1 -0
  41. package/.pi/prompts/harness-auto.md +1 -1
  42. package/.pi/prompts/harness-clear.md +6 -6
  43. package/.pi/prompts/harness-plan.md +15 -2
  44. package/.pi/prompts/harness-review.md +2 -2
  45. package/.pi/scripts/harness-project-toggle.mjs +1 -1
  46. package/CHANGELOG.md +10 -0
  47. package/README.md +2 -2
  48. package/package.json +1 -1
@@ -6,6 +6,7 @@ import { constants } from "node:fs";
6
6
  import { access, readFile } from "node:fs/promises";
7
7
  import { join } from "node:path";
8
8
  import { parse as parseYaml } from "yaml";
9
+ import { synthesizerArtifactsComplete } from "./harness-plan-route.js";
9
10
  import {
10
11
  isTaskClarificationReady,
11
12
  TASK_CLARIFICATION_ARTIFACT,
@@ -213,11 +214,14 @@ export async function validatePlanApprovalReadiness(
213
214
  }
214
215
  }
215
216
 
216
- if (!(await fileExists(join(runDir, "artifacts/decomposition.yaml")))) {
217
- errors.push("missing artifacts/decomposition.yaml");
218
- }
219
- if (!(await fileExists(join(runDir, "artifacts/hypothesis.yaml")))) {
220
- errors.push("missing artifacts/hypothesis.yaml");
217
+ const synthComplete = await synthesizerArtifactsComplete(runDir);
218
+ if (!synthComplete) {
219
+ if (!(await fileExists(join(runDir, "artifacts/decomposition.yaml")))) {
220
+ errors.push("missing artifacts/decomposition.yaml");
221
+ }
222
+ if (!(await fileExists(join(runDir, "artifacts/hypothesis.yaml")))) {
223
+ errors.push("missing artifacts/hypothesis.yaml");
224
+ }
221
225
  }
222
226
 
223
227
  return { ok: errors.length === 0, errors, warnings };
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Persisted plan-debate eligibility snapshot for gate pass-through.
3
+ */
4
+
5
+ import { constants } from "node:fs";
6
+ import { access, mkdir, readFile, writeFile } from "node:fs/promises";
7
+ import { dirname, join } from "node:path";
8
+ import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
9
+ import type { DebateEligibilityResult } from "./plan-debate-eligibility.js";
10
+
11
+ export const PLAN_DEBATE_ELIGIBILITY_ARTIFACT =
12
+ "artifacts/plan-debate-eligibility.yaml";
13
+
14
+ async function fileExists(path: string): Promise<boolean> {
15
+ try {
16
+ await access(path, constants.R_OK);
17
+ return true;
18
+ } catch {
19
+ return false;
20
+ }
21
+ }
22
+
23
+ export async function writePlanDebateEligibilitySnapshot(
24
+ runDir: string,
25
+ result: DebateEligibilityResult,
26
+ ): Promise<string> {
27
+ const rel = PLAN_DEBATE_ELIGIBILITY_ARTIFACT;
28
+ const abs = join(runDir, rel);
29
+ await mkdir(dirname(abs), { recursive: true });
30
+ const doc = {
31
+ schema_version: "1.0.0",
32
+ captured_at: new Date().toISOString(),
33
+ profile: result.profile,
34
+ required_focuses: result.required_focuses,
35
+ min_focus_rounds: result.review_gate_strategy.min_focus_rounds,
36
+ max_rounds: result.max_rounds,
37
+ max_exchanges_per_round: result.max_exchanges_per_round,
38
+ round_token_cap: result.round_token_cap,
39
+ debate_global_cap: result.debate_global_cap,
40
+ human_required: result.human_required,
41
+ rationale: result.rationale,
42
+ review_gate_strategy: result.review_gate_strategy,
43
+ };
44
+ await writeFile(abs, stringifyYaml(doc), "utf-8");
45
+ return rel;
46
+ }
47
+
48
+ export async function loadPlanDebateEligibilitySnapshot(
49
+ runDir: string,
50
+ ): Promise<DebateEligibilityResult | null> {
51
+ const abs = join(runDir, PLAN_DEBATE_ELIGIBILITY_ARTIFACT);
52
+ if (!(await fileExists(abs))) return null;
53
+ try {
54
+ const raw = await readFile(abs, "utf-8");
55
+ const doc = parseYaml(raw) as Record<string, unknown>;
56
+ if (!doc || typeof doc !== "object") return null;
57
+ const strategy = doc.review_gate_strategy as
58
+ | DebateEligibilityResult["review_gate_strategy"]
59
+ | undefined;
60
+ if (!strategy?.mode) return null;
61
+ return {
62
+ profile: String(
63
+ doc.profile ?? strategy.profile ?? "standard",
64
+ ) as DebateEligibilityResult["profile"],
65
+ required_focuses: (doc.required_focuses ??
66
+ strategy.required_focuses ??
67
+ []) as DebateEligibilityResult["required_focuses"],
68
+ min_focus_rounds: Number(
69
+ doc.min_focus_rounds ?? strategy.min_focus_rounds ?? 1,
70
+ ),
71
+ max_rounds: Number(doc.max_rounds ?? strategy.max_rounds ?? 12),
72
+ max_exchanges_per_round: Number(
73
+ doc.max_exchanges_per_round ?? strategy.max_exchanges_per_round ?? 3,
74
+ ),
75
+ round_token_cap: Number(
76
+ doc.round_token_cap ?? strategy.round_token_cap ?? 8000,
77
+ ),
78
+ debate_global_cap: Number(
79
+ doc.debate_global_cap ?? strategy.debate_global_cap ?? 80000,
80
+ ),
81
+ human_required: doc.human_required === true,
82
+ rationale: Array.isArray(doc.rationale)
83
+ ? doc.rationale.map((r) => String(r))
84
+ : [],
85
+ review_gate_strategy: strategy,
86
+ };
87
+ } catch {
88
+ return null;
89
+ }
90
+ }
@@ -249,6 +249,16 @@ export function harnessPlanDebateEligibility(
249
249
  : [...PLAN_FOCUS_AREAS];
250
250
 
251
251
  const caps = capsForProfile(profile);
252
+ const reviewMode =
253
+ profile === "fast"
254
+ ? ("consolidated" as const)
255
+ : profile === "standard"
256
+ ? ("parallel_probes" as const)
257
+ : ("threaded" as const);
258
+ const minFocusForStrategy =
259
+ reviewMode === "parallel_probes" || reviewMode === "consolidated"
260
+ ? 1
261
+ : caps.min_focus_rounds;
252
262
 
253
263
  return {
254
264
  profile,
@@ -257,15 +267,10 @@ export function harnessPlanDebateEligibility(
257
267
  human_required,
258
268
  rationale,
259
269
  review_gate_strategy: {
260
- mode:
261
- profile === "fast"
262
- ? "consolidated"
263
- : profile === "standard"
264
- ? "parallel_probes"
265
- : "threaded",
270
+ mode: reviewMode,
266
271
  profile,
267
272
  required_focuses: [...required_focuses],
268
- min_focus_rounds: caps.min_focus_rounds,
273
+ min_focus_rounds: minFocusForStrategy,
269
274
  max_rounds: caps.max_rounds,
270
275
  max_exchanges_per_round: caps.max_exchanges_per_round,
271
276
  round_token_cap: caps.round_token_cap,
@@ -65,7 +65,7 @@ export async function getPlanFocusCoverage(
65
65
  let files: string[] = [];
66
66
  try {
67
67
  files = (await readdir(artifactsDir)).filter((f) =>
68
- /^review-round(?:-r\d+|-consolidated)\.yaml$/i.test(f),
68
+ /^review-round(?:-r\d+|-consolidated|-parallel-probes)\.yaml$/i.test(f),
69
69
  );
70
70
  } catch {
71
71
  return {
@@ -80,11 +80,14 @@ export async function getPlanFocusCoverage(
80
80
 
81
81
  for (const name of files.sort()) {
82
82
  const consolidated = /^review-round-consolidated\.yaml$/i.test(name);
83
+ const parallelProbes = /^review-round-parallel-probes\.yaml$/i.test(name);
83
84
  const m = consolidated
84
85
  ? ["review-round-consolidated.yaml", "1"]
85
- : /^review-round-r(\d+)\.yaml$/i.exec(name);
86
+ : parallelProbes
87
+ ? ["review-round-parallel-probes.yaml", "1"]
88
+ : /^review-round-r(\d+)\.yaml$/i.exec(name);
86
89
  if (!m) continue;
87
- const roundIndex = consolidated ? 1 : Number(m[1]);
90
+ const roundIndex = consolidated || parallelProbes ? 1 : Number(m[1]);
88
91
  if (roundIndex > last_round_index) last_round_index = roundIndex;
89
92
  const raw = await readFile(join(artifactsDir, name), "utf-8");
90
93
  let draft: Record<string, unknown>;
@@ -151,13 +154,22 @@ export async function readDebateRoundFocus(
151
154
  runDir: string,
152
155
  roundIndex: number,
153
156
  ): Promise<PlanDebateRoundFocus | null> {
154
- const path = join(runDir, "artifacts", `review-round-r${roundIndex}.yaml`);
155
- if (!(await fileExists(path))) return null;
156
- try {
157
- const raw = await readFile(path, "utf-8");
158
- const draft = parseYaml(raw) as Record<string, unknown>;
159
- return focusFromDraft(draft);
160
- } catch {
161
- return null;
157
+ const candidates =
158
+ roundIndex === 1
159
+ ? [
160
+ "review-round-parallel-probes.yaml",
161
+ "review-round-consolidated.yaml",
162
+ `review-round-r${roundIndex}.yaml`,
163
+ ]
164
+ : [`review-round-r${roundIndex}.yaml`];
165
+ for (const name of candidates) {
166
+ const path = join(runDir, "artifacts", name);
167
+ if (!(await fileExists(path))) continue;
168
+ try {
169
+ const raw = await readFile(path, "utf-8");
170
+ const draft = parseYaml(raw) as Record<string, unknown>;
171
+ return focusFromDraft(draft);
172
+ } catch {}
162
173
  }
174
+ return null;
163
175
  }
@@ -5,9 +5,14 @@
5
5
  import { constants } from "node:fs";
6
6
  import { access, readFile } from "node:fs/promises";
7
7
  import { join } from "node:path";
8
+ import { isHarnessNonInteractive } from "./ask-user/policy.js";
8
9
  import { capsForDebate } from "./debate-bus-core.js";
9
10
  import { isHarnessBudgetEnforceOn } from "./harness-budget-enforce.js";
10
- import type { DebateEligibilityResult } from "./plan-debate-eligibility.js";
11
+ import type {
12
+ DebateEligibilityResult,
13
+ DebateProfile,
14
+ } from "./plan-debate-eligibility.js";
15
+ import { loadPlanDebateEligibilitySnapshot } from "./plan-debate-eligibility-snapshot.js";
11
16
  import {
12
17
  getPlanFocusCoverage,
13
18
  type PlanDebateFocus,
@@ -31,9 +36,12 @@ import {
31
36
  } from "./plan-messenger.js";
32
37
  import {
33
38
  CONSOLIDATED_REVIEW_ARTIFACT,
39
+ effectiveMinFocusRounds,
34
40
  isConsolidatedReviewStrategy,
35
41
  isParallelProbesReviewStrategy,
42
+ PARALLEL_PROBES_REVIEW_ARTIFACT,
36
43
  planReviewGateStrategyFromEligibility,
44
+ reviewStrategyFromMessenger,
37
45
  } from "./plan-review-gate.js";
38
46
 
39
47
  async function fileExists(path: string): Promise<boolean> {
@@ -226,37 +234,70 @@ export async function validatePlanDebateGate(
226
234
  const debatesDir = join(projectRoot, ".pi", "harness", "debates");
227
235
  const messenger = await loadMessengerState(runDir);
228
236
  const debateProfile = messenger?.debate_profile ?? "standard";
237
+
238
+ if (process.env.HARNESS_QA_SMOKE === "1" && isHarnessNonInteractive()) {
239
+ const consensusPath = join(debatesDir, `${debateId}.consensus.json`);
240
+ if (await fileExists(consensusPath)) {
241
+ try {
242
+ const packet = JSON.parse(await readFile(consensusPath, "utf-8")) as {
243
+ headless_bypass?: boolean;
244
+ policy_decision?: string;
245
+ };
246
+ if (
247
+ packet.headless_bypass === true &&
248
+ packet.policy_decision !== "block"
249
+ ) {
250
+ const coverage = await getPlanFocusCoverage(runDir);
251
+ return {
252
+ ok: true,
253
+ errors: [],
254
+ warnings: ["QA smoke: headless debate bypass consensus accepted"],
255
+ debateId,
256
+ focus_coverage: {
257
+ covered: coverage.covered,
258
+ missing: coverage.missing,
259
+ last_review_gate_ready: coverage.last_review_gate_ready,
260
+ },
261
+ debate_profile: debateProfile,
262
+ };
263
+ }
264
+ } catch {
265
+ // fall through to full gate
266
+ }
267
+ }
268
+ }
269
+
229
270
  const requiredFocuses: readonly PlanDebateFocus[] =
230
271
  messenger?.required_focuses && messenger.required_focuses.length > 0
231
272
  ? messenger.required_focuses
232
273
  : (["spec", "wbs", "schedule", "quality"] as const);
233
274
  const caps = capsForDebate(debateId, debateProfile);
234
- const reviewStrategy =
235
- eligibility != null
236
- ? planReviewGateStrategyFromEligibility(eligibility)
237
- : messenger?.review_gate_mode === "consolidated"
238
- ? {
239
- mode: "consolidated" as const,
240
- profile: debateProfile as DebateEligibilityResult["profile"],
241
- required_focuses: [...requiredFocuses],
242
- min_focus_rounds: caps.min_focus_rounds,
243
- max_rounds: caps.max_rounds,
244
- max_exchanges_per_round: caps.max_exchanges_per_round,
245
- round_token_cap: caps.round_token_cap,
246
- debate_global_cap: caps.debate_global_cap,
247
- rationale: ["messenger review_gate_mode=consolidated"],
248
- }
249
- : {
250
- mode: "threaded" as const,
251
- profile: debateProfile as DebateEligibilityResult["profile"],
252
- required_focuses: [...requiredFocuses],
253
- min_focus_rounds: caps.min_focus_rounds,
254
- max_rounds: caps.max_rounds,
255
- max_exchanges_per_round: caps.max_exchanges_per_round,
256
- round_token_cap: caps.round_token_cap,
257
- debate_global_cap: caps.debate_global_cap,
258
- rationale: [],
259
- };
275
+ const eligibilitySnapshot =
276
+ eligibility ?? (await loadPlanDebateEligibilitySnapshot(runDir));
277
+ const reviewStrategy = eligibilitySnapshot
278
+ ? planReviewGateStrategyFromEligibility(eligibilitySnapshot)
279
+ : messenger
280
+ ? reviewStrategyFromMessenger(
281
+ messenger,
282
+ debateProfile as DebateProfile,
283
+ requiredFocuses,
284
+ caps,
285
+ )
286
+ : {
287
+ mode: "threaded" as const,
288
+ profile: debateProfile as DebateProfile,
289
+ required_focuses: [...requiredFocuses],
290
+ min_focus_rounds: caps.min_focus_rounds,
291
+ max_rounds: caps.max_rounds,
292
+ max_exchanges_per_round: caps.max_exchanges_per_round,
293
+ round_token_cap: caps.round_token_cap,
294
+ debate_global_cap: caps.debate_global_cap,
295
+ rationale: [],
296
+ };
297
+ const effectiveCaps = {
298
+ ...caps,
299
+ min_focus_rounds: effectiveMinFocusRounds(reviewStrategy),
300
+ };
260
301
  const _consolidated = isConsolidatedReviewStrategy(reviewStrategy);
261
302
  const _parallelProbes = isParallelProbesReviewStrategy(reviewStrategy);
262
303
  const coverage = await getPlanFocusCoverage(runDir, { requiredFocuses });
@@ -324,7 +365,7 @@ export async function validatePlanDebateGate(
324
365
  const busChecks = await collectBusAndConsensusIssues({
325
366
  debateId,
326
367
  debatesDir,
327
- caps,
368
+ caps: effectiveCaps,
328
369
  requiredFocuses,
329
370
  coverage,
330
371
  debateProfile,
@@ -350,7 +391,8 @@ export function isReviewRoundArtifactPath(relPath: string): boolean {
350
391
  const norm = relPath.replace(/\\/g, "/");
351
392
  return (
352
393
  /^artifacts\/review-round-r\d+\.yaml$/i.test(norm) ||
353
- norm === CONSOLIDATED_REVIEW_ARTIFACT
394
+ norm === CONSOLIDATED_REVIEW_ARTIFACT ||
395
+ norm === PARALLEL_PROBES_REVIEW_ARTIFACT
354
396
  );
355
397
  }
356
398
 
@@ -14,7 +14,9 @@ import { planDebateIdForRun } from "./plan-debate-id.js";
14
14
  import { laneArtifactPath } from "./plan-debate-lane.js";
15
15
  import {
16
16
  lanesForConsolidatedRound,
17
+ lanesForParallelProbesRound,
17
18
  lanesForRound,
19
+ PARALLEL_PROBES_REVIEW_ARTIFACT,
18
20
  } from "./plan-debate-lanes.js";
19
21
  import {
20
22
  getMessengerRoundState,
@@ -53,16 +55,20 @@ export async function getPlanDebateRoundStatus(
53
55
  opts?: { debate_round_focus?: PlanDebateRoundFocus },
54
56
  ): Promise<RoundStatusResult> {
55
57
  const messengerState = await loadMessengerState(runDir);
58
+ const parallelProbes =
59
+ messengerState?.review_gate_mode === "parallel_probes" && roundIndex === 1;
56
60
  const consolidated =
57
61
  messengerState?.review_gate_mode === "consolidated" && roundIndex === 1;
58
62
  const focus =
59
63
  opts?.debate_round_focus ??
60
- (consolidated ? ("all" as PlanDebateRoundFocus) : null) ??
64
+ (consolidated || parallelProbes ? ("all" as PlanDebateRoundFocus) : null) ??
61
65
  (await readDebateRoundFocus(runDir, roundIndex));
62
66
  const missing: string[] = [];
63
- const laneList = consolidated
64
- ? lanesForConsolidatedRound()
65
- : lanesForRound(roundIndex, focus);
67
+ const laneList = parallelProbes
68
+ ? lanesForParallelProbesRound()
69
+ : consolidated
70
+ ? lanesForConsolidatedRound()
71
+ : lanesForRound(roundIndex, focus);
66
72
  for (const lane of laneList) {
67
73
  const rel = laneArtifactPath(lane, roundIndex);
68
74
  if (!(await exists(join(runDir, rel)))) {
@@ -82,13 +88,22 @@ export async function getPlanDebateRoundStatus(
82
88
  if (!dialogue.ok) {
83
89
  missing.push(...dialogue.errors.map((e) => `messenger: ${e}`));
84
90
  }
85
- const reviewRound = consolidated
86
- ? "artifacts/review-round-consolidated.yaml"
87
- : `artifacts/review-round-r${roundIndex}.yaml`;
91
+ const reviewRound = parallelProbes
92
+ ? PARALLEL_PROBES_REVIEW_ARTIFACT
93
+ : consolidated
94
+ ? "artifacts/review-round-consolidated.yaml"
95
+ : `artifacts/review-round-r${roundIndex}.yaml`;
88
96
  const reviewRoundOnDisk = await exists(join(runDir, reviewRound));
89
97
 
90
98
  let next_tool: string | undefined;
91
- if (missing.some((m) => m.includes("hypothesis-validation"))) {
99
+ if (
100
+ parallelProbes &&
101
+ missing.some((m) => m.includes("validation-turn")) &&
102
+ missing.some((m) => m.includes("adversary-brief"))
103
+ ) {
104
+ next_tool =
105
+ "subagent parallel batch: harness/planning/plan-evaluator ∥ harness/planning/plan-adversary (parallel_probes)";
106
+ } else if (missing.some((m) => m.includes("hypothesis-validation"))) {
92
107
  next_tool = "subagent harness/planning/hypothesis-validator";
93
108
  } else if (missing.some((m) => m.includes("validation-turn"))) {
94
109
  next_tool = "subagent harness/planning/plan-evaluator";