ultimate-pi 0.22.1 → 0.23.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 (44) hide show
  1. package/.pi/extensions/agt-kill-switch.ts +7 -1
  2. package/.pi/extensions/harness-plan-approval.ts +9 -1
  3. package/.pi/extensions/harness-run-context.ts +587 -86
  4. package/.pi/extensions/policy-gate.ts +15 -2
  5. package/.pi/harness/agents.manifest.json +3 -3
  6. package/.pi/harness/agents.policy.yaml +82 -3
  7. package/.pi/harness/specs/plan-task-clarification.schema.json +10 -1
  8. package/.pi/lib/agents-policy.mjs +42 -1
  9. package/.pi/lib/agt/build-evaluation-context.ts +3 -1
  10. package/.pi/lib/agt/kill-switch-state.ts +14 -0
  11. package/.pi/lib/agt/legacy-evaluate.ts +3 -1
  12. package/.pi/lib/ask-user/index.ts +2 -0
  13. package/.pi/lib/ask-user/merge-task-clarification.ts +5 -0
  14. package/.pi/lib/ask-user/policy.ts +23 -0
  15. package/.pi/lib/ask-user/presenters/glimpse.ts +8 -1
  16. package/.pi/lib/ask-user/presenters/headless.ts +15 -0
  17. package/.pi/lib/ask-user/presenters/select.ts +11 -2
  18. package/.pi/lib/ask-user/validate-core.mjs +16 -0
  19. package/.pi/lib/harness-artifact-gate.ts +75 -5
  20. package/.pi/lib/harness-repair-brief.ts +30 -4
  21. package/.pi/lib/harness-run-context.ts +842 -17
  22. package/.pi/lib/harness-schema-validate.ts +147 -38
  23. package/.pi/lib/harness-spawn-policy.ts +9 -0
  24. package/.pi/lib/harness-spawn-topology.ts +109 -7
  25. package/.pi/lib/harness-subagent-precheck.ts +21 -0
  26. package/.pi/lib/harness-subagent-submit-pipeline.ts +95 -21
  27. package/.pi/lib/harness-subagent-submit-register.ts +6 -1
  28. package/.pi/lib/harness-subagents-bridge.ts +3 -0
  29. package/.pi/lib/harness-yaml.ts +11 -3
  30. package/.pi/lib/plan-approval/create-plan.ts +2 -6
  31. package/.pi/lib/plan-debate-gate.ts +87 -0
  32. package/.pi/lib/plan-debate-lane.ts +8 -2
  33. package/.pi/lib/plan-human-gates.ts +404 -0
  34. package/.pi/prompts/harness-clear.md +25 -0
  35. package/.pi/prompts/harness-plan.md +6 -0
  36. package/.pi/prompts/harness-review.md +2 -0
  37. package/.pi/prompts/harness-run.md +4 -3
  38. package/.pi/scripts/generate-agents-policy-yaml.mjs +73 -7
  39. package/.pi/scripts/harness-reconcile-run-context.mjs +62 -0
  40. package/.pi/scripts/harness-schema-compile-verify.mjs +29 -0
  41. package/.pi/scripts/harness-verify.mjs +27 -0
  42. package/CHANGELOG.md +13 -0
  43. package/README.md +4 -0
  44. package/package.json +1 -1
@@ -10,6 +10,7 @@ import {
10
10
  readdir,
11
11
  readFile,
12
12
  rename,
13
+ rm,
13
14
  stat,
14
15
  writeFile,
15
16
  } from "node:fs/promises";
@@ -17,14 +18,25 @@ import { basename, dirname, join } from "node:path";
17
18
  import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
18
19
  import { Type } from "@sinclair/typebox";
19
20
  import { allowsAgentTool } from "../lib/agents-policy.mjs";
21
+ import {
22
+ disarmHarnessKillSwitch,
23
+ resetHarnessPolicyDenyCount,
24
+ } from "../lib/agt/kill-switch-state.js";
25
+ import { runAskUser } from "../lib/ask-user/index.js";
20
26
  import { claimHarnessGovernanceLoad } from "../lib/extension-load-guard.js";
21
27
  import { getHarnessPackageRoot } from "../lib/harness-paths.js";
22
28
  import {
29
+ blockingHarnessAutoCommandReason,
30
+ blockingReviewCommandReason,
31
+ blockingRunCommandReason,
32
+ blockingSteerCommandReason,
33
+ buildHarnessClearManifest,
23
34
  canonicalPlanPath,
24
35
  claimRunOwnership,
25
36
  createFreshRunContext,
26
37
  criticalPathWorkItemIdsFromPlanPacket,
27
38
  driftGateActive,
39
+ ensureReviewOutcomeFromEval,
28
40
  evaluateCrossSessionResume,
29
41
  extractWritePathFromToolInput,
30
42
  formatActivePlanBlock,
@@ -36,6 +48,7 @@ import {
36
48
  getPolicyTransitionBlock,
37
49
  type HarnessRunContext,
38
50
  type HarnessTurnEntry,
51
+ harnessAutoTasksDiffer,
39
52
  hasHarnessAbortSignal,
40
53
  hasPlanUserApproval,
41
54
  inferHarnessPhase,
@@ -51,6 +64,7 @@ import {
51
64
  normalizeHarnessPath,
52
65
  nowIso,
53
66
  type PlanPacketSummary,
67
+ parseArgFlag,
54
68
  parseHarnessSlashInput,
55
69
  parseHarnessUseRunArgs,
56
70
  parsePlanApprovalFromMessage,
@@ -58,14 +72,24 @@ import {
58
72
  readExecutorHandoffFromRun,
59
73
  readPlanPacketFromPath,
60
74
  readReviewOutcomeFromRun,
75
+ reconcileReviewRouting,
76
+ reconcileStaleExecuteCompletion,
77
+ refreshRunContextProgress,
78
+ relPathUnderActiveRun,
79
+ resetRunContextForHarnessAuto,
61
80
  resolveArgsForCommand,
62
81
  resolveCompletionStatuses,
82
+ resolveHarnessRunPostAgentState,
83
+ resolveHarnessRunWriteTarget,
84
+ resolveRemediationClassForRun,
63
85
  saveProjectActiveRun,
64
86
  saveRunContextToDisk,
65
87
  sessionHasResumePromptForRun,
66
88
  shouldAutoClaimHarnessRun,
67
89
  shouldReuseHarnessRunId,
68
90
  steerMaxAttemptsFromEnv,
91
+ syncPlanLastOutcomeFromTaskClarification,
92
+ syncPlanReadyFromDisk,
69
93
  userVisiblePromptSlice,
70
94
  validatePlanOverridePath,
71
95
  validatePlanPacket,
@@ -80,6 +104,11 @@ import {
80
104
  } from "../lib/harness-yaml.js";
81
105
  import { isReviewRoundArtifactPath } from "../lib/plan-debate-gate.js";
82
106
  import { isReviewRoundYamlWriteAllowed } from "../lib/plan-debate-write-guard.js";
107
+ import {
108
+ formatPlanHumanGateBlock,
109
+ resolvePlanHumanGateStatus,
110
+ validateTaskClarificationHumanGate,
111
+ } from "../lib/plan-human-gates.js";
83
112
  import {
84
113
  assertTaskClarificationReadyForPlanWrite,
85
114
  readTaskClarificationDoc,
@@ -102,8 +131,20 @@ function getEntries(ctx: {
102
131
 
103
132
  function persistContext(pi: ExtensionAPI, ctx: HarnessRunContext): void {
104
133
  pi.appendEntry("harness-run-context", ctx);
105
- void saveRunContextToDisk(ctx);
106
- void saveProjectActiveRun(ctx);
134
+ void saveRunContextToDisk(ctx).catch((err) => {
135
+ pi.appendEntry("harness-run-context-disk-error", {
136
+ run_id: ctx.run_id,
137
+ error: err instanceof Error ? err.message : String(err),
138
+ recorded_at: nowIso(),
139
+ });
140
+ });
141
+ void saveProjectActiveRun(ctx).catch((err) => {
142
+ pi.appendEntry("harness-run-context-disk-error", {
143
+ run_id: ctx.run_id,
144
+ error: err instanceof Error ? err.message : String(err),
145
+ recorded_at: nowIso(),
146
+ });
147
+ });
107
148
  pi.events.emit("harness-run-context:updated", { run_id: ctx.run_id });
108
149
  }
109
150
 
@@ -215,7 +256,8 @@ export async function archivePlanRevisionArtifacts(input: {
215
256
  return { archiveDir, moved };
216
257
  }
217
258
 
218
- function shouldArchiveForPlanRevise(input: {
259
+ /** Exported for tests — avoid archiving on every /harness-plan continue. */
260
+ export function shouldArchiveForPlanRevise(input: {
219
261
  command: string;
220
262
  mode: "create" | "revise" | null;
221
263
  runCtx: HarnessRunContext;
@@ -226,15 +268,20 @@ function shouldArchiveForPlanRevise(input: {
226
268
  return false;
227
269
  }
228
270
  if (input.mode !== "revise") return false;
229
- const next = (input.runCtx.next_recommended_command ?? "").toLowerCase();
230
271
  const prompt = input.userPrompt.toLowerCase();
231
- return (
232
- input.reviewOutcome?.remediation_class === "plan_gap" ||
233
- next.includes("/harness-plan") ||
234
- next.includes("revise") ||
272
+ const explicitRevise =
235
273
  prompt.includes("--mode revise") ||
236
274
  prompt.includes("--mode=revise") ||
237
- prompt.includes("mode: revise")
275
+ prompt.includes("mode: revise") ||
276
+ /\b(revise\s+(the\s+)?plan|reset\s+plan|start\s+over\s+on\s+the\s+plan)\b/.test(
277
+ prompt,
278
+ );
279
+ if (explicitRevise) return true;
280
+ if (input.reviewOutcome?.remediation_class !== "plan_gap") return false;
281
+ return (
282
+ prompt.includes("plan_gap") ||
283
+ prompt.includes("remediation_class") ||
284
+ /\brevise\s+per\s+review\b/.test(prompt)
238
285
  );
239
286
  }
240
287
 
@@ -341,13 +388,22 @@ async function hydrateFromDisk(
341
388
  entries: unknown[],
342
389
  ): Promise<HarnessRunContext | null> {
343
390
  const fromSession = getLatestRunContext(entries);
344
- if (fromSession) return fromSession;
391
+ if (fromSession) {
392
+ return reconcileStaleExecuteCompletion(projectRoot, fromSession, entries);
393
+ }
345
394
 
346
395
  const pointer = await loadProjectActiveRun(projectRoot);
347
396
  if (!pointer || isStaleActiveRunPointer(pointer, projectRoot)) return null;
348
397
 
349
398
  const disk = await loadRunContextFromDisk(pointer.run_id, projectRoot);
350
- if (disk) return disk;
399
+ if (disk) {
400
+ const clar = await syncPlanLastOutcomeFromTaskClarification(
401
+ projectRoot,
402
+ disk,
403
+ );
404
+ const planSynced = await syncPlanReadyFromDisk(projectRoot, clar, entries);
405
+ return reconcileStaleExecuteCompletion(projectRoot, planSynced, entries);
406
+ }
351
407
 
352
408
  return {
353
409
  schema_version: "1.0.0",
@@ -476,10 +532,13 @@ function startFreshPlanAttempt(input: {
476
532
  activeCtx: HarnessRunContext;
477
533
  command: string;
478
534
  turn: HarnessTurnEntry | null;
535
+ sessionId: string;
479
536
  }): void {
480
537
  input.activeCtx.plan_ready = false;
481
538
  input.activeCtx.phase = "plan";
482
539
  input.activeCtx.status = "active";
540
+ disarmHarnessKillSwitch(input.sessionId);
541
+ resetHarnessPolicyDenyCount(input.sessionId);
483
542
  input.pi.appendEntry("harness-plan-attempt", {
484
543
  run_id: input.activeCtx.run_id,
485
544
  command: input.command,
@@ -584,6 +643,159 @@ type ActiveContextAccess = {
584
643
  set(ctx: HarnessRunContext | null): void;
585
644
  };
586
645
 
646
+ const HARNESS_CLEAR_CONFIRM_OPTION = "Delete historical runs";
647
+
648
+ function isHarnessClearConfirmed(response: unknown): boolean {
649
+ if (!response || typeof response !== "object") return false;
650
+ const payload = response as {
651
+ kind?: string;
652
+ selections?: unknown;
653
+ };
654
+ if (payload.kind !== "selection" || !Array.isArray(payload.selections)) {
655
+ return false;
656
+ }
657
+ return (
658
+ payload.selections.length === 1 &&
659
+ payload.selections[0] === HARNESS_CLEAR_CONFIRM_OPTION
660
+ );
661
+ }
662
+
663
+ function registerHarnessClearCommand(
664
+ pi: ExtensionAPI,
665
+ active: ActiveContextAccess,
666
+ ): void {
667
+ pi.registerCommand("harness-clear", {
668
+ description:
669
+ "Delete historical harness runs under .pi/harness/runs while preserving the active run",
670
+ handler: async (_args, ctx) => {
671
+ const entries = getEntries(ctx);
672
+ const projectRoot = process.cwd();
673
+ const latest = active.get() ?? getLatestRunContext(entries);
674
+ const pointer = await loadProjectActiveRun(projectRoot);
675
+ const protectedRunIds = new Set<string>();
676
+ if (latest?.run_id) protectedRunIds.add(latest.run_id);
677
+ if (pointer?.run_id) protectedRunIds.add(pointer.run_id);
678
+ const manifest = await buildHarnessClearManifest(
679
+ projectRoot,
680
+ protectedRunIds,
681
+ );
682
+ if (manifest.candidates.length === 0) {
683
+ const message = [
684
+ "/harness-clear: no historical run directories eligible for deletion.",
685
+ ` protected: ${manifest.protected_run_ids.join(", ") || "(none)"}`,
686
+ ` skipped: ${manifest.skipped.length}`,
687
+ ].join("\n");
688
+ if (ctx.hasUI) ctx.ui.notify(message, "info");
689
+ else
690
+ pi.sendMessage({
691
+ customType: "harness-clear-result",
692
+ content: message,
693
+ display: true,
694
+ });
695
+ pi.appendEntry("harness-clear-result", {
696
+ approved: false,
697
+ deleted: 0,
698
+ protected: manifest.protected_run_ids,
699
+ skipped: manifest.skipped,
700
+ recorded_at: nowIso(),
701
+ });
702
+ return;
703
+ }
704
+ const ask = await runAskUser(
705
+ {
706
+ question: `Delete ${manifest.candidates.length} historical harness run directories?`,
707
+ context: [
708
+ "Scope: .pi/harness/runs/<run_id> only (historical runs).",
709
+ `Preserved active run ids: ${manifest.protected_run_ids.join(", ") || "(none)"}`,
710
+ `Candidates: ${manifest.candidates.map((item) => item.run_id).join(", ")}`,
711
+ ].join("\n"),
712
+ options: [HARNESS_CLEAR_CONFIRM_OPTION, "Cancel"],
713
+ allowSkip: true,
714
+ },
715
+ { ui: ctx.ui, hasUI: ctx.hasUI },
716
+ );
717
+ if ("error" in ask) {
718
+ const message = [
719
+ "/harness-clear: confirmation unavailable; no files deleted (fail-closed).",
720
+ ` reason: ${ask.error}`,
721
+ ].join("\n");
722
+ if (ctx.hasUI) ctx.ui.notify(message, "warning");
723
+ else
724
+ pi.sendMessage({
725
+ customType: "harness-clear-result",
726
+ content: message,
727
+ display: true,
728
+ });
729
+ pi.appendEntry("harness-clear-result", {
730
+ approved: false,
731
+ deleted: 0,
732
+ protected: manifest.protected_run_ids,
733
+ skipped: manifest.skipped,
734
+ ask_error: ask.error,
735
+ recorded_at: nowIso(),
736
+ });
737
+ return;
738
+ }
739
+ const confirmed =
740
+ !ask.details.cancelled && isHarnessClearConfirmed(ask.details.response);
741
+ if (!confirmed) {
742
+ const message = [
743
+ "/harness-clear: cancelled; no files deleted.",
744
+ ` candidates: ${manifest.candidates.length}`,
745
+ ].join("\n");
746
+ if (ctx.hasUI) ctx.ui.notify(message, "info");
747
+ else
748
+ pi.sendMessage({
749
+ customType: "harness-clear-result",
750
+ content: message,
751
+ display: true,
752
+ });
753
+ pi.appendEntry("harness-clear-result", {
754
+ approved: false,
755
+ deleted: 0,
756
+ protected: manifest.protected_run_ids,
757
+ skipped: manifest.skipped,
758
+ recorded_at: nowIso(),
759
+ });
760
+ return;
761
+ }
762
+ let deleted = 0;
763
+ const failed: Array<{ run_id: string; reason: string }> = [];
764
+ for (const candidate of manifest.candidates) {
765
+ try {
766
+ await rm(candidate.canonical_path, { recursive: true, force: true });
767
+ deleted += 1;
768
+ } catch (err) {
769
+ failed.push({
770
+ run_id: candidate.run_id,
771
+ reason: err instanceof Error ? err.message : String(err),
772
+ });
773
+ }
774
+ }
775
+ const message = [
776
+ "/harness-clear complete.",
777
+ ` deleted: ${deleted}`,
778
+ ` protected: ${manifest.protected_run_ids.length}`,
779
+ ` skipped: ${manifest.skipped.length + failed.length}`,
780
+ ].join("\n");
781
+ if (ctx.hasUI) ctx.ui.notify(message, "info");
782
+ else
783
+ pi.sendMessage({
784
+ customType: "harness-clear-result",
785
+ content: message,
786
+ display: true,
787
+ });
788
+ pi.appendEntry("harness-clear-result", {
789
+ approved: true,
790
+ deleted,
791
+ protected: manifest.protected_run_ids,
792
+ skipped: [...manifest.skipped, ...failed],
793
+ recorded_at: nowIso(),
794
+ });
795
+ },
796
+ });
797
+ }
798
+
587
799
  function registerHarnessRunStatusCommand(
588
800
  pi: ExtensionAPI,
589
801
  active: ActiveContextAccess,
@@ -603,6 +815,13 @@ function registerHarnessRunStatusCommand(
603
815
  if (ctx.hasUI) ctx.ui.notify(msg, "warning");
604
816
  return;
605
817
  }
818
+ ctxState = await refreshRunContextProgress(
819
+ projectRoot,
820
+ ctxState,
821
+ entries,
822
+ );
823
+ active.set(ctxState);
824
+ persistContext(pi, ctxState);
606
825
  let summary: PlanPacketSummary | null = null;
607
826
  for (let i = entries.length - 1; i >= 0; i--) {
608
827
  const entry = entries[i] as SessionEntryLike;
@@ -926,6 +1145,13 @@ async function archivePlanRevisionIfNeeded(input: {
926
1145
  reason: "review_plan_gap_revise",
927
1146
  });
928
1147
  if (reset.moved.length === 0) return;
1148
+ input.activeCtx.plan_ready = false;
1149
+ const synced = await syncPlanLastOutcomeFromTaskClarification(
1150
+ input.projectRoot,
1151
+ input.activeCtx,
1152
+ );
1153
+ Object.assign(input.activeCtx, synced);
1154
+ persistContext(input.pi, input.activeCtx);
929
1155
  input.pi.appendEntry("harness-plan-revision-reset", {
930
1156
  run_id: input.activeCtx.run_id,
931
1157
  archive_dir: reset.archiveDir,
@@ -989,18 +1215,27 @@ async function updatePlanReadinessAfterAgent(input: {
989
1215
  )
990
1216
  return;
991
1217
  if (!input.activeCtx.plan_packet_path) return;
992
- const packet = await readPlanPacketFromPath(input.activeCtx.plan_packet_path);
993
- const validation = validatePlanPacket(packet);
994
- const approved = hasPlanUserApproval(input.entries, {
995
- sincePlanCommand: true,
996
- planId: packet?.plan_id ?? null,
997
- });
998
- input.activeCtx.plan_ready = validation.valid && approved;
999
- if (validation.valid && !approved) {
1000
- input.activeCtx.last_outcome = "needs_clarification";
1001
- input.activeCtx.last_completed_step = "plan";
1218
+ const beforeReady = input.activeCtx.plan_ready;
1219
+ const synced = await syncPlanReadyFromDisk(
1220
+ process.cwd(),
1221
+ input.activeCtx,
1222
+ input.entries,
1223
+ );
1224
+ Object.assign(input.activeCtx, synced);
1225
+ if (!beforeReady && synced.plan_ready && synced.plan_packet_path) {
1226
+ const packet = await readPlanPacketFromPath(synced.plan_packet_path);
1227
+ if (packet?.plan_id) {
1228
+ syncPolicyFromPlan(input.pi, input.entries, packet.plan_id, "plan", true);
1229
+ const summary = planPacketSummary(packet, synced.plan_packet_path);
1230
+ input.pi.appendEntry("harness-plan-packet", summary);
1231
+ }
1232
+ } else if (
1233
+ synced.plan_packet_path &&
1234
+ !synced.plan_ready &&
1235
+ synced.last_outcome === "pending_approval"
1236
+ ) {
1002
1237
  const msg =
1003
- "Plan file exists but user approval was not recorded. Planner must call approve_plan (or bridged ask_user Approve) before writing plan-packet.yaml.";
1238
+ "A draft plan-packet.yaml is on disk, but user approval was not recorded. Complete Review Gate (debate rounds + harness_debate_consensus), then call approve_plan; use create_plan only after Approve.";
1004
1239
  if (input.ctx.hasUI) input.ctx.ui.notify(msg, "warning");
1005
1240
  else
1006
1241
  input.pi.sendMessage({
@@ -1008,17 +1243,8 @@ async function updatePlanReadinessAfterAgent(input: {
1008
1243
  content: msg,
1009
1244
  display: true,
1010
1245
  });
1011
- } else if (input.activeCtx.plan_ready && packet?.plan_id) {
1012
- input.activeCtx.plan_id = packet.plan_id;
1013
- syncPolicyFromPlan(input.pi, input.entries, packet.plan_id, "plan", true);
1014
- const summary = planPacketSummary(packet, input.activeCtx.plan_packet_path);
1015
- input.pi.appendEntry("harness-plan-packet", summary);
1016
- input.activeCtx.last_completed_step = "plan";
1017
- input.activeCtx.last_outcome = summary.plan_status;
1018
- } else if (!validation.valid) {
1019
- input.activeCtx.last_outcome = "needs_clarification";
1020
- input.activeCtx.last_completed_step = "plan";
1021
1246
  }
1247
+ persistContext(input.pi, input.activeCtx);
1022
1248
  }
1023
1249
 
1024
1250
  function registerPlanApprovalCapture(
@@ -1029,15 +1255,63 @@ function registerPlanApprovalCapture(
1029
1255
  if (event.isError) return;
1030
1256
  if (event.toolName !== "ask_user" && event.toolName !== "approve_plan")
1031
1257
  return;
1258
+ const entries = getEntries(ctx);
1259
+ const runCtx = getLatestRunContext(entries) ?? active.get();
1260
+ if (!runCtx) return;
1261
+ if (event.toolName === "ask_user") {
1262
+ const details = event.details as { cancelled?: boolean; input?: unknown };
1263
+ if (details?.cancelled) {
1264
+ // Ignore cancels from later planning forks (e.g. debate profile choice):
1265
+ // only treat cancel as Phase-0 clarification failure when clarification
1266
+ // is not already locked ready.
1267
+ const runRoot = join(
1268
+ process.cwd(),
1269
+ ".pi",
1270
+ "harness",
1271
+ "runs",
1272
+ runCtx.run_id ?? "",
1273
+ );
1274
+ const clarDoc = runCtx.run_id
1275
+ ? await readTaskClarificationDoc(runRoot)
1276
+ : null;
1277
+ const clarReady =
1278
+ String(clarDoc?.status ?? "").toLowerCase() === "ready";
1279
+ if (!clarReady) {
1280
+ const synced = await syncPlanLastOutcomeFromTaskClarification(
1281
+ process.cwd(),
1282
+ runCtx,
1283
+ );
1284
+ Object.assign(runCtx, synced);
1285
+ persistContext(pi, runCtx);
1286
+ }
1287
+ } else if (
1288
+ !isPlanApprovalAskUser(
1289
+ (details?.input ?? {}) as {
1290
+ question?: string;
1291
+ options?: unknown[];
1292
+ questions?: unknown[];
1293
+ },
1294
+ )
1295
+ ) {
1296
+ pi.appendEntry("harness-task-clarification-engagement", {
1297
+ run_id: runCtx.run_id,
1298
+ recorded_at: nowIso(),
1299
+ source: "ask_user",
1300
+ });
1301
+ const synced = await syncPlanLastOutcomeFromTaskClarification(
1302
+ process.cwd(),
1303
+ runCtx,
1304
+ );
1305
+ Object.assign(runCtx, synced);
1306
+ persistContext(pi, runCtx);
1307
+ }
1308
+ }
1032
1309
  const approval = parsePlanApprovalFromMessage({
1033
1310
  toolName: event.toolName,
1034
1311
  details: event.details,
1035
1312
  content: event.content,
1036
1313
  });
1037
1314
  if (!approval) return;
1038
- const entries = getEntries(ctx);
1039
- const runCtx = getLatestRunContext(entries) ?? active.get();
1040
- if (!runCtx) return;
1041
1315
  pi.appendEntry("harness-plan-approval", {
1042
1316
  plan_id: approval.plan_id ?? runCtx.plan_id,
1043
1317
  approved_at: approval.approved_at,
@@ -1046,6 +1320,36 @@ function registerPlanApprovalCapture(
1046
1320
  });
1047
1321
  }
1048
1322
 
1323
+ function registerExecutorHandoffReconcile(
1324
+ pi: ExtensionAPI,
1325
+ active: ActiveContextAccess,
1326
+ ): void {
1327
+ pi.on("tool_result", async (event, ctx) => {
1328
+ if (event.isError || event.toolName !== "submit_executor_handoff") return;
1329
+ const entries = getEntries(ctx);
1330
+ const runCtx = getLatestRunContext(entries) ?? active.get();
1331
+ if (!runCtx?.run_id) return;
1332
+ const projectRoot = process.cwd();
1333
+ const refreshed = await refreshRunContextProgress(
1334
+ projectRoot,
1335
+ runCtx,
1336
+ entries,
1337
+ );
1338
+ Object.assign(runCtx, refreshed);
1339
+ active.set(runCtx);
1340
+ persistContext(pi, runCtx);
1341
+ if (refreshed.last_completed_step === "execute") {
1342
+ const notify = `Execute finished (${refreshed.last_outcome ?? "done"}). Next: ${refreshed.next_recommended_command ?? "/harness-review"}`;
1343
+ pi.appendEntry("harness-step-handoff", {
1344
+ next_command: refreshed.next_recommended_command,
1345
+ execution_status: refreshed.last_outcome,
1346
+ phase: refreshed.phase,
1347
+ });
1348
+ if (ctx.hasUI) ctx.ui.notify(notify, "info");
1349
+ }
1350
+ });
1351
+ }
1352
+
1049
1353
  async function guardToolCall(input: {
1050
1354
  event: { toolName: string; input: unknown };
1051
1355
  ctx: { sessionManager: { getEntries(): unknown[] } };
@@ -1165,18 +1469,41 @@ async function resolveCommandRunContext(input: {
1165
1469
  input.command === "harness-auto" ||
1166
1470
  (!activeCtx && input.command !== "harness-abort")
1167
1471
  ) {
1472
+ const task = extractTaskSummary(input.args, input.userPrompt);
1168
1473
  if (
1169
- !activeCtx ||
1170
- !shouldReuseHarnessRunId(input.userPrompt, activeCtx, input.command)
1474
+ input.command === "harness-auto" &&
1475
+ activeCtx &&
1476
+ task &&
1477
+ harnessAutoTasksDiffer(activeCtx, task)
1171
1478
  ) {
1479
+ activeCtx.status = "aborted";
1480
+ activeCtx.plan_ready = false;
1481
+ activeCtx.last_outcome = "abandoned";
1482
+ activeCtx.last_completed_step = "abort";
1483
+ persistContext(input.pi, activeCtx);
1484
+ activeCtx = null;
1485
+ }
1486
+ const reuseRun =
1487
+ activeCtx &&
1488
+ shouldReuseHarnessRunId(input.userPrompt, activeCtx, input.command);
1489
+ if (!activeCtx || !reuseRun) {
1490
+ if (activeCtx?.status === "active") {
1491
+ activeCtx.status = "aborted";
1492
+ activeCtx.plan_ready = false;
1493
+ activeCtx.last_outcome = "abandoned";
1494
+ activeCtx.last_completed_step = "abort";
1495
+ persistContext(input.pi, activeCtx);
1496
+ }
1172
1497
  activeCtx = createFreshRunContext(
1173
1498
  input.sessionId,
1174
1499
  input.projectRoot,
1175
- extractTaskSummary(input.args, input.userPrompt),
1500
+ task,
1176
1501
  );
1502
+ } else if (input.command === "harness-auto") {
1503
+ activeCtx = resetRunContextForHarnessAuto(activeCtx);
1504
+ if (task) activeCtx.task_summary = task;
1177
1505
  }
1178
1506
  if (input.command === "harness-plan") {
1179
- const task = extractTaskSummary(input.args, input.userPrompt);
1180
1507
  if (task) activeCtx.task_summary = task;
1181
1508
  }
1182
1509
  startFreshPlanAttempt({
@@ -1184,6 +1511,7 @@ async function resolveCommandRunContext(input: {
1184
1511
  activeCtx,
1185
1512
  command: input.command,
1186
1513
  turn: input.turn,
1514
+ sessionId: input.sessionId,
1187
1515
  });
1188
1516
  } else if (
1189
1517
  activeCtx &&
@@ -1297,7 +1625,7 @@ async function handlePreResolvedHarnessCommand(args: {
1297
1625
  handled: true,
1298
1626
  };
1299
1627
  }
1300
- if (command === "harness-run-status") {
1628
+ if (command === "harness-run-status" || command === "harness-clear") {
1301
1629
  return { activeCtx, response: undefined, handled: true };
1302
1630
  }
1303
1631
  if (
@@ -1317,21 +1645,6 @@ async function handlePreResolvedHarnessCommand(args: {
1317
1645
  return { activeCtx, response: null, handled: false };
1318
1646
  }
1319
1647
 
1320
- function blockingRunCommandReason(
1321
- command: string,
1322
- activeCtx: HarnessRunContext,
1323
- ): string | null {
1324
- if (command !== "harness-run") return null;
1325
- if (!activeCtx.plan_ready) return "Plan not ready. Run /harness-plan first.";
1326
- if (
1327
- activeCtx.last_completed_step === "execute" &&
1328
- activeCtx.last_outcome === "completed"
1329
- ) {
1330
- return "Execute already completed for this run. Next: /harness-review (same session), or /harness-abort to replan.";
1331
- }
1332
- return null;
1333
- }
1334
-
1335
1648
  async function handleBeforeAgentStart(input: {
1336
1649
  pi: ExtensionAPI;
1337
1650
  event: any;
@@ -1371,12 +1684,21 @@ async function handleBeforeAgentStart(input: {
1371
1684
  "plan";
1372
1685
  const driftActive = driftGateActive(entries);
1373
1686
  if (!parsed && needsClarificationFollowUp(activeCtx) && activeCtx) {
1374
- return maybeHandleClarificationFollowUp({
1375
- pi: input.pi,
1687
+ const synced = await syncPlanLastOutcomeFromTaskClarification(
1688
+ projectRoot,
1376
1689
  activeCtx,
1377
- entries,
1378
- systemPrompt: input.event.systemPrompt,
1379
- });
1690
+ );
1691
+ if (synced.last_outcome !== "needs_clarification") {
1692
+ input.active.set(synced);
1693
+ persistContext(input.pi, synced);
1694
+ } else {
1695
+ return maybeHandleClarificationFollowUp({
1696
+ pi: input.pi,
1697
+ activeCtx,
1698
+ entries,
1699
+ systemPrompt: input.event.systemPrompt,
1700
+ });
1701
+ }
1380
1702
  }
1381
1703
  if (!parsed) return undefined;
1382
1704
  const { command, args } = parsed;
@@ -1433,8 +1755,40 @@ async function handleBeforeAgentStart(input: {
1433
1755
  return blockRunContextMessage(check.reason ?? "Invalid --plan override");
1434
1756
  activeCtx.plan_packet_path = resolved.planPath;
1435
1757
  }
1436
- const runBlockReason = blockingRunCommandReason(command, activeCtx);
1758
+ let planSynced = await reconcileStaleExecuteCompletion(
1759
+ projectRoot,
1760
+ activeCtx,
1761
+ entries,
1762
+ );
1763
+ planSynced = await reconcileReviewRouting(projectRoot, planSynced);
1764
+ Object.assign(activeCtx, planSynced);
1765
+ persistContext(input.pi, activeCtx);
1766
+ const autoBlockReason = await blockingHarnessAutoCommandReason(
1767
+ command,
1768
+ activeCtx,
1769
+ args,
1770
+ userPrompt,
1771
+ );
1772
+ if (autoBlockReason) return blockRunContextMessage(autoBlockReason);
1773
+ const runBlockReason = await blockingRunCommandReason(
1774
+ command,
1775
+ activeCtx,
1776
+ projectRoot,
1777
+ entries,
1778
+ );
1437
1779
  if (runBlockReason) return blockRunContextMessage(runBlockReason);
1780
+ const reviewBlockReason = await blockingReviewCommandReason(
1781
+ command,
1782
+ activeCtx,
1783
+ projectRoot,
1784
+ );
1785
+ if (reviewBlockReason) return blockRunContextMessage(reviewBlockReason);
1786
+ const steerBlockReason = await blockingSteerCommandReason(
1787
+ command,
1788
+ activeCtx,
1789
+ projectRoot,
1790
+ );
1791
+ if (steerBlockReason) return blockRunContextMessage(steerBlockReason);
1438
1792
  const { planSummary, planPacketForSpawn } =
1439
1793
  await readPlanSpawnState(activeCtx);
1440
1794
  const { activePlanBlock, planMode, contextSpawnOpts } =
@@ -1452,10 +1806,34 @@ async function handleBeforeAgentStart(input: {
1452
1806
  projectRoot,
1453
1807
  userPrompt,
1454
1808
  });
1809
+ const syncedCtx = await syncPlanLastOutcomeFromTaskClarification(
1810
+ projectRoot,
1811
+ activeCtx,
1812
+ );
1813
+ Object.assign(activeCtx, syncedCtx);
1455
1814
  input.active.set(activeCtx);
1456
1815
  persistContext(input.pi, activeCtx);
1816
+ if (command === "harness-plan" || command === "harness-auto") {
1817
+ syncPolicyFromRunContext(input.pi, entries, activeCtx);
1818
+ }
1819
+ let gateBlock = "";
1820
+ if (command === "harness-plan" || command === "harness-auto") {
1821
+ const quick = parseArgFlag(args, "--quick") != null;
1822
+ const gateStatus = await resolvePlanHumanGateStatus(
1823
+ projectRoot,
1824
+ activeCtx.run_id,
1825
+ entries,
1826
+ {
1827
+ quick,
1828
+ taskSummary: activeCtx.task_summary ?? undefined,
1829
+ lastOutcome: activeCtx.last_outcome ?? undefined,
1830
+ },
1831
+ );
1832
+ gateBlock = formatPlanHumanGateBlock(gateStatus);
1833
+ }
1834
+ const gateSuffix = gateBlock ? `\n\n${gateBlock}` : "";
1457
1835
  return {
1458
- systemPrompt: `${input.event.systemPrompt}\n\n${formatPlanContextBlock(activeCtx, contextSpawnOpts)}${activePlanBlock ? `\n\n${activePlanBlock}` : ""}`,
1836
+ systemPrompt: `${input.event.systemPrompt}\n\n${formatPlanContextBlock(activeCtx, contextSpawnOpts)}${activePlanBlock ? `\n\n${activePlanBlock}` : ""}${gateSuffix}`,
1459
1837
  };
1460
1838
  }
1461
1839
 
@@ -1468,6 +1846,13 @@ async function handleAgentEnd(input: {
1468
1846
  const entries = getEntries(input.ctx);
1469
1847
  const activeCtx = input.active.get() ?? getLatestRunContext(entries);
1470
1848
  if (!activeCtx) return;
1849
+ let reconciledOnEnd = await reconcileStaleExecuteCompletion(
1850
+ projectRoot,
1851
+ activeCtx,
1852
+ entries,
1853
+ );
1854
+ reconciledOnEnd = await reconcileReviewRouting(projectRoot, reconciledOnEnd);
1855
+ Object.assign(activeCtx, reconciledOnEnd);
1471
1856
  input.active.set(activeCtx);
1472
1857
  const parsed = latestParsedHarnessCommand(entries);
1473
1858
  if (!parsed && !needsClarificationFollowUp(activeCtx)) return;
@@ -1482,13 +1867,23 @@ async function handleAgentEnd(input: {
1482
1867
  parsed,
1483
1868
  activeCtx,
1484
1869
  });
1870
+ if (
1871
+ parsed?.command === "harness-plan" ||
1872
+ parsed?.command === "harness-auto"
1873
+ ) {
1874
+ const synced = await syncPlanLastOutcomeFromTaskClarification(
1875
+ projectRoot,
1876
+ activeCtx,
1877
+ );
1878
+ Object.assign(activeCtx, synced);
1879
+ persistContext(input.pi, activeCtx);
1880
+ }
1485
1881
  const statuses = await resolveCompletionStatuses(
1486
1882
  entries,
1487
1883
  activeCtx.run_id,
1488
1884
  projectRoot,
1489
1885
  );
1490
- if (parsed?.command === "harness-run") {
1491
- activeCtx.last_completed_step = "execute";
1886
+ if (parsed?.command === "harness-run" || parsed?.command === "harness-auto") {
1492
1887
  let execStatus = statuses.executionStatus;
1493
1888
  if (!execStatus) {
1494
1889
  const handoff = await readExecutorHandoffFromRun(
@@ -1497,8 +1892,11 @@ async function handleAgentEnd(input: {
1497
1892
  );
1498
1893
  execStatus = handoff?.execution_status ?? null;
1499
1894
  }
1500
- activeCtx.last_outcome = execStatus ?? "completed";
1501
- activeCtx.phase = "evaluate";
1895
+ const runPost = resolveHarnessRunPostAgentState(
1896
+ execStatus,
1897
+ activeCtx.plan_ready,
1898
+ );
1899
+ Object.assign(activeCtx, runPost);
1502
1900
  }
1503
1901
  if (parsed?.command === "harness-steer") {
1504
1902
  activeCtx.last_completed_step = "steer";
@@ -1521,7 +1919,14 @@ async function handleAgentEnd(input: {
1521
1919
  activeCtx.last_completed_step = "adversary";
1522
1920
  } else if (statuses.evalStatus) activeCtx.phase = "evaluate";
1523
1921
  }
1524
- const reviewOutcome = await readReviewOutcomeFromRun(
1922
+ if (
1923
+ ["harness-eval", "harness-review", "harness-critic"].includes(
1924
+ parsed?.command ?? "",
1925
+ )
1926
+ ) {
1927
+ await ensureReviewOutcomeFromEval(activeCtx.run_id, projectRoot);
1928
+ }
1929
+ const remediationClass = await resolveRemediationClassForRun(
1525
1930
  activeCtx.run_id,
1526
1931
  projectRoot,
1527
1932
  );
@@ -1537,7 +1942,7 @@ async function handleAgentEnd(input: {
1537
1942
  evalStatus: statuses.evalStatus,
1538
1943
  adversaryComplete: statuses.adversaryComplete,
1539
1944
  aborted: activeCtx.status === "aborted",
1540
- remediationClass: reviewOutcome?.remediation_class ?? null,
1945
+ remediationClass,
1541
1946
  steerAttempt: activeCtx.steer_attempt ?? 0,
1542
1947
  steerMaxAttempts: activeCtx.steer_max_attempts ?? steerMaxAttemptsFromEnv(),
1543
1948
  reviewComplete,
@@ -1545,7 +1950,7 @@ async function handleAgentEnd(input: {
1545
1950
  activeCtx.next_recommended_command = next;
1546
1951
  activeCtx.updated_at = new Date().toISOString();
1547
1952
  if (
1548
- parsed?.command === "harness-run" &&
1953
+ (parsed?.command === "harness-run" || parsed?.command === "harness-auto") &&
1549
1954
  activeCtx.last_outcome === "completed"
1550
1955
  ) {
1551
1956
  syncPolicyFromRunContext(input.pi, entries, activeCtx);
@@ -1590,7 +1995,7 @@ function registerHarnessRunContextTool1(
1590
1995
  parameters: Type.Object({
1591
1996
  path: Type.String({
1592
1997
  description:
1593
- "Path under the active run, e.g. artifacts/decomposition.yaml or research-brief.yaml",
1998
+ "Run-relative path (preferred): artifacts/decomposition.yaml, research-brief.yaml, plan-packet.yaml. The active run id is applied automatically — do not prefix with .pi/harness/runs/.",
1594
1999
  }),
1595
2000
  content: Type.String({
1596
2001
  description:
@@ -1640,21 +2045,32 @@ function registerHarnessRunContextTool1(
1640
2045
  };
1641
2046
  }
1642
2047
  const projectRoot = process.cwd();
1643
- const absPath = normalizeHarnessPath(pathArg, projectRoot);
1644
- const scoped = await isPlanPhaseScopedWrite(absPath, runCtx, projectRoot);
2048
+ const resolved = resolveHarnessRunWriteTarget(
2049
+ pathArg,
2050
+ runCtx,
2051
+ projectRoot,
2052
+ );
2053
+ const absPath =
2054
+ resolved?.absPath ?? normalizeHarnessPath(pathArg, projectRoot);
2055
+ const scoped =
2056
+ resolved != null ||
2057
+ (await isPlanPhaseScopedWrite(absPath, runCtx, projectRoot));
1645
2058
  if (!scoped) {
1646
2059
  return {
1647
2060
  content: [
1648
2061
  {
1649
2062
  type: "text",
1650
- text: `Path not allowed: ${pathArg}. Must be under .pi/harness/runs/${runCtx.run_id}/ (artifacts/*.yaml, research-brief.yaml, etc.).`,
2063
+ text: `Path not allowed: ${pathArg}. Use a run-relative path like artifacts/decomposition.yaml or research-brief.yaml (active run ${runCtx.run_id} is applied automatically). Full paths under .pi/harness/runs/${runCtx.run_id}/ are also accepted.`,
1651
2064
  },
1652
2065
  ],
1653
- details: { path: pathArg },
2066
+ details: { path: pathArg, run_id: runCtx.run_id },
1654
2067
  isError: true,
1655
2068
  };
1656
2069
  }
1657
- const relForGate = pathArg.replace(/\\/g, "/");
2070
+ const relForGate =
2071
+ resolved?.relUnderRun ??
2072
+ (await relPathUnderActiveRun(absPath, runCtx, projectRoot)) ??
2073
+ pathArg.replace(/\\/g, "/");
1658
2074
  const subagentOnly = new Set([
1659
2075
  "artifacts/eval-verdict.yaml",
1660
2076
  "artifacts/adversary-report.yaml",
@@ -1721,12 +2137,67 @@ function registerHarnessRunContextTool1(
1721
2137
  doc = parseStructuredDocument(content, pathArg);
1722
2138
  } catch (err) {
1723
2139
  const msg = err instanceof Error ? err.message : String(err);
2140
+ const hint =
2141
+ msg.includes("not valid YAML") || msg.includes("JSON parse")
2142
+ ? " Pass a fenced ```yaml block, raw YAML object, or JSON object — not prose or a partial fragment."
2143
+ : "";
1724
2144
  return {
1725
- content: [{ type: "text", text: msg }],
1726
- details: { path: pathArg },
2145
+ content: [
2146
+ {
2147
+ type: "text",
2148
+ text: `${relForGate}: ${msg}${hint}`,
2149
+ },
2150
+ ],
2151
+ details: { path: relForGate, run_id: runCtx.run_id },
1727
2152
  isError: true,
1728
2153
  };
1729
2154
  }
2155
+ const docRecord = doc as Record<string, unknown>;
2156
+ if (relForGate === TASK_CLARIFICATION_ARTIFACT) {
2157
+ const humanGate = validateTaskClarificationHumanGate(
2158
+ entries,
2159
+ docRecord,
2160
+ {
2161
+ quick:
2162
+ parseArgFlag(
2163
+ getLatestHarnessTurn(entries)?.args ?? "",
2164
+ "--quick",
2165
+ ) != null,
2166
+ taskSummary: runCtx.task_summary ?? undefined,
2167
+ allowFollowUpMessage: runCtx.last_outcome === "needs_clarification",
2168
+ },
2169
+ );
2170
+ if (!humanGate.ok) {
2171
+ return {
2172
+ content: [
2173
+ {
2174
+ type: "text",
2175
+ text: humanGate.errors.join("\n"),
2176
+ },
2177
+ ],
2178
+ details: { path: pathArg },
2179
+ isError: true,
2180
+ };
2181
+ }
2182
+ }
2183
+ if (relForGate === "artifacts/plan-phase-status.yaml") {
2184
+ const planStatus = String(docRecord.plan_status ?? "").toLowerCase();
2185
+ if (
2186
+ planStatus === "ready" &&
2187
+ !hasPlanUserApproval(entries, { sincePlanCommand: true })
2188
+ ) {
2189
+ return {
2190
+ content: [
2191
+ {
2192
+ type: "text",
2193
+ text: "Blocked: plan_status ready requires approve_plan (then create_plan) before marking the plan phase complete.",
2194
+ },
2195
+ ],
2196
+ details: { path: pathArg },
2197
+ isError: true,
2198
+ };
2199
+ }
2200
+ }
1730
2201
  await mkdir(dirname(absPath), { recursive: true });
1731
2202
  await writeYamlFile(absPath, doc);
1732
2203
  if (relForGate === TASK_CLARIFICATION_ARTIFACT) {
@@ -1743,10 +2214,10 @@ function registerHarnessRunContextTool1(
1743
2214
  content: [
1744
2215
  {
1745
2216
  type: "text",
1746
- text: `Wrote ${pathArg} as canonical YAML.`,
2217
+ text: `Wrote ${relForGate} as canonical YAML.`,
1747
2218
  },
1748
2219
  ],
1749
- details: { path: absPath },
2220
+ details: { path: absPath, rel: relForGate, run_id: runCtx.run_id },
1750
2221
  };
1751
2222
  },
1752
2223
  });
@@ -1812,17 +2283,25 @@ function registerHarnessRunContextTool2(
1812
2283
  };
1813
2284
  }
1814
2285
  const projectRoot = process.cwd();
1815
- const absPath = normalizeHarnessPath(pathArg, projectRoot);
1816
- const scoped = await isPlanPhaseScopedWrite(absPath, runCtx, projectRoot);
2286
+ const resolved = resolveHarnessRunWriteTarget(
2287
+ pathArg,
2288
+ runCtx,
2289
+ projectRoot,
2290
+ );
2291
+ const absPath =
2292
+ resolved?.absPath ?? normalizeHarnessPath(pathArg, projectRoot);
2293
+ const scoped =
2294
+ resolved != null ||
2295
+ (await isPlanPhaseScopedWrite(absPath, runCtx, projectRoot));
1817
2296
  if (!scoped) {
1818
2297
  return {
1819
2298
  content: [
1820
2299
  {
1821
2300
  type: "text",
1822
- text: `Path not allowed: ${pathArg}.`,
2301
+ text: `Path not allowed: ${pathArg}. Use run-relative paths like artifacts/decomposition.yaml (active run ${runCtx.run_id}).`,
1823
2302
  },
1824
2303
  ],
1825
- details: { path: pathArg },
2304
+ details: { path: pathArg, run_id: runCtx.run_id },
1826
2305
  isError: true,
1827
2306
  };
1828
2307
  }
@@ -1833,7 +2312,10 @@ function registerHarnessRunContextTool2(
1833
2312
  "runs",
1834
2313
  runCtx.run_id,
1835
2314
  );
1836
- const relMerge = pathArg.replace(/\\/g, "/");
2315
+ const relMerge =
2316
+ resolved?.relUnderRun ??
2317
+ (await relPathUnderActiveRun(absPath, runCtx, projectRoot)) ??
2318
+ pathArg.replace(/\\/g, "/");
1837
2319
  const clarMerge = await assertTaskClarificationReadyForPlanWrite(
1838
2320
  runRoot,
1839
2321
  relMerge,
@@ -2044,7 +2526,18 @@ function registerHarnessRunContextTool4(
2044
2526
  const { validateHarnessArtifactPaths } = await import(
2045
2527
  "../lib/harness-artifact-gate.js"
2046
2528
  );
2047
- const gate = await validateHarnessArtifactPaths(runRoot, paths, specsDir);
2529
+ const turn = getLatestHarnessTurn(entries);
2530
+ const gate = await validateHarnessArtifactPaths(
2531
+ runRoot,
2532
+ paths,
2533
+ specsDir,
2534
+ {
2535
+ entries,
2536
+ quick: turn ? parseArgFlag(turn.args, "--quick") != null : false,
2537
+ taskSummary: runCtx.task_summary ?? undefined,
2538
+ lastOutcome: runCtx.last_outcome ?? undefined,
2539
+ },
2540
+ );
2048
2541
  if (
2049
2542
  gate.ok &&
2050
2543
  paths.some((p) => p.replace(/\\/g, "/") === TASK_CLARIFICATION_ARTIFACT)
@@ -2053,8 +2546,13 @@ function registerHarnessRunContextTool4(
2053
2546
  const clarified = String(clarDoc?.clarified_task ?? "").trim();
2054
2547
  if (clarified && runCtx.task_summary !== clarified) {
2055
2548
  runCtx.task_summary = clarified;
2056
- persistContext(pi, runCtx);
2057
2549
  }
2550
+ const synced = await syncPlanLastOutcomeFromTaskClarification(
2551
+ projectRoot,
2552
+ runCtx,
2553
+ );
2554
+ Object.assign(runCtx, synced);
2555
+ persistContext(pi, runCtx);
2058
2556
  }
2059
2557
  const text = gate.ok
2060
2558
  ? `All ${gate.present.length} artifact(s) present and valid.`
@@ -2136,8 +2634,11 @@ export default function harnessRunContext(pi: ExtensionAPI) {
2136
2634
  });
2137
2635
 
2138
2636
  registerPlanApprovalCapture(pi, activeAccess);
2637
+ registerExecutorHandoffReconcile(pi, activeAccess);
2139
2638
  registerHarnessToolCallGuards(pi, activeAccess);
2140
2639
  registerHarnessRunStatusCommand(pi, activeAccess);
2640
+
2641
+ registerHarnessClearCommand(pi, activeAccess);
2141
2642
  registerHarnessNewRunCommand(pi, activeAccess);
2142
2643
 
2143
2644
  registerHarnessPlanCommitCommand(pi, activeAccess);