@mediadatafusion/pi-workflow-suite 0.0.11 → 0.0.13

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 (45) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/README.md +26 -17
  3. package/VERSION +1 -1
  4. package/agents/codebase-research.md +7 -5
  5. package/agents/general-worker.md +9 -7
  6. package/agents/implementation-planning.md +5 -3
  7. package/agents/quality-validation.md +9 -8
  8. package/agents/workflow-orchestrator.md +9 -7
  9. package/config/prompts/execute-approved-plan.md +12 -2
  10. package/config/prompts/mission-final-validation.md +38 -5
  11. package/config/prompts/mission-plan.md +17 -1
  12. package/config/prompts/mission-repair.md +16 -2
  13. package/config/prompts/mission-review-prompt.md +19 -6
  14. package/config/prompts/mission-run.md +18 -5
  15. package/config/prompts/validate-approved-plan.md +57 -3
  16. package/config/prompts/workflow-plan-prompt.md +11 -1
  17. package/config/prompts/workflow-repair.md +18 -2
  18. package/config/prompts/workflow-reviewer-prompt.md +25 -9
  19. package/config/prompts/workflow-summary.md +1 -4
  20. package/config/workflow-settings.example.json +13 -11
  21. package/extensions/subagent/index.ts +41 -18
  22. package/extensions/subagent/repolock-guard.ts +224 -4
  23. package/extensions/subagent/runner.ts +136 -12
  24. package/extensions/workflow-model-router.ts +124 -41
  25. package/extensions/workflow-modes.ts +3791 -967
  26. package/extensions/workflow-settings-capabilities.ts +10 -0
  27. package/extensions/workflow-state.ts +77 -10
  28. package/extensions/workflow-subagent-policy.ts +13 -1
  29. package/extensions/workflow-summary.ts +8 -19
  30. package/extensions/workflow-tool-guard.ts +326 -35
  31. package/extensions/workflow-validation-classifier.ts +46 -4
  32. package/extensions/workflow-web-tools.ts +361 -1
  33. package/package.json +8 -5
  34. package/scripts/audit-live.sh +1 -1
  35. package/scripts/build-package-export.mjs +8 -13
  36. package/scripts/check-clean-release-tree.sh +3 -2
  37. package/scripts/check-package-media.mjs +78 -0
  38. package/scripts/install-to-live.sh +2 -0
  39. package/scripts/package-media-config.mjs +28 -0
  40. package/scripts/prepare-package-readme.mjs +19 -18
  41. package/scripts/quarantine-live-junk.sh +1 -1
  42. package/scripts/verify-live.sh +9 -1
  43. package/skills/implementation-planning/SKILL.md +1 -1
  44. package/skills/safe-execution/SKILL.md +1 -1
  45. package/skills/validation-review/SKILL.md +1 -1
@@ -240,6 +240,16 @@ const STANDARD_MISSION_CONTEXT_CAPABILITIES: CapabilityFactory[] = [
240
240
  risk: "Can look fully enforced when current design keeps recovery supervised.",
241
241
  action: "Preserve; label as planned/partial until user-supervised recovery actions are implemented.",
242
242
  }),
243
+ () => capability({
244
+ path: "missions.missionHistoryLimit",
245
+ domain: "missions",
246
+ intent: "Limit retained saved Mission history records.",
247
+ owner: "saveMissionState / clearOldMissionStates / Mission History settings menu",
248
+ status: "wired",
249
+ related: ["workflow.planHistoryLimit"],
250
+ risk: "If misfiled under runtime settings, users may confuse saved history retention with Mission execution timers.",
251
+ action: "Keep surfaced under Mission History, parallel to Plan History.",
252
+ }),
243
253
  () => capability({
244
254
  path: "context.compactionMode",
245
255
  domain: "context",
@@ -36,6 +36,13 @@ export interface WorkflowTypedHandoff {
36
36
  payload: Record<string, unknown>;
37
37
  }
38
38
 
39
+ export interface WorkflowReviewHandoffSuppression {
40
+ kind: "plan_typed_initial_to_approval" | "plan_typed_review_to_execution" | "mission_typed_review_to_approval";
41
+ createdAt: string;
42
+ activePlanId?: string;
43
+ activeMissionId?: string;
44
+ }
45
+
39
46
  export interface WorkflowRepairHistoryEntry {
40
47
  timestamp: string;
41
48
  retry: number;
@@ -91,7 +98,7 @@ export interface StandardRuntimeState {
91
98
  runtimeCounter: "running" | "paused" | "stopped";
92
99
  }
93
100
 
94
- export type PlanLifecycleStatus = "planning" | "awaiting_clarification" | "plan_ready" | "approved" | "reviewing" | "executing" | "validating" | "repairing" | "revalidating" | "completed" | "blocked";
101
+ export type PlanLifecycleStatus = "planning" | "awaiting_clarification" | "plan_ready" | "approved" | "reviewing" | "reviewed" | "executing" | "validating" | "repairing" | "revalidating" | "completed" | "blocked";
95
102
  export type PlanStepStatus = "pending" | "active" | "completed" | "failed" | "blocked" | "skipped";
96
103
  export type PlanValidationStatus = "pending" | "running" | "pass" | "partial pass" | "fail" | "unknown";
97
104
 
@@ -180,6 +187,21 @@ export interface BlockedPlanResumeSnapshot {
180
187
  workflowValidationRetryCount?: number;
181
188
  planRuntime?: PlanRuntimeState;
182
189
  planProgress?: PlanProgressState;
190
+ reviewerReport?: string;
191
+ reviewerVerdict?: "PASS" | "NOTES" | "NEEDS REPAIR" | "FAIL" | "BLOCKED" | "UNKNOWN";
192
+ reviewHistory?: WorkflowReviewHistoryEntry[];
193
+ currentReviewRetry?: number;
194
+ workflowReviewRetryCount?: number;
195
+ lastReviewFailure?: string;
196
+ lastReviewAttempt?: string;
197
+ lastReviewRepairStatus?: "none" | "running" | "completed" | "failed" | "blocked";
198
+ reviewRepairInProgress?: boolean;
199
+ repairRetryState?: Partial<Record<RepairRetryGateName, RepairRetryGateState>>;
200
+ concreteRepairableIssue?: boolean;
201
+ manualVerificationRequired?: boolean;
202
+ evidenceGap?: boolean;
203
+ planTokensUsed?: number;
204
+ modelsUsed?: { planner?: string; executor?: string; validator?: string; reviewer?: string };
183
205
  }
184
206
 
185
207
  export interface WorkflowFinalStopSummary {
@@ -219,6 +241,7 @@ export interface WorkflowState {
219
241
  clarifyingQuestions?: ClarificationQuestion[];
220
242
  clarifyingAnswers?: ClarificationAnswer[];
221
243
  lastWorkflowHandoff?: WorkflowTypedHandoff;
244
+ reviewHandoffSuppression?: WorkflowReviewHandoffSuppression;
222
245
  clarificationAlreadyAsked?: boolean;
223
246
  clarificationRequiredBeforePlan?: boolean;
224
247
  clarificationRequirementReason?: string;
@@ -246,6 +269,9 @@ export interface WorkflowState {
246
269
  executionSummary?: string;
247
270
  validationReport?: string;
248
271
  validationVerdict?: "PASS" | "PARTIAL PASS" | "FAIL" | "UNKNOWN";
272
+ currentValidationHandoffRetry?: number;
273
+ maxValidationHandoffRetries?: number;
274
+ lastValidationHandoffFailure?: string;
249
275
  currentValidationRetry?: number;
250
276
  workflowValidationRetryCount?: number;
251
277
  maxValidationRetriesPerPlan?: number;
@@ -261,6 +287,7 @@ export interface WorkflowState {
261
287
  planStepValidationIndex?: number;
262
288
  planExecutionStepIndex?: number;
263
289
  planRuntime?: PlanRuntimeState;
290
+ planRuntimeHoldActive?: boolean;
264
291
  planProgress?: PlanProgressState;
265
292
  planProgressLastToolStep?: number;
266
293
  planProgressLastToolStatus?: PlanStepStatus;
@@ -348,6 +375,10 @@ export interface PlanSavingOptions {
348
375
  planHistoryLimit?: number;
349
376
  }
350
377
 
378
+ export interface MissionSavingOptions {
379
+ missionHistoryLimit?: number;
380
+ }
381
+
351
382
  export type MissionStatus = "draft" | "planning" | "awaiting_clarification" | "planned" | "approved" | "running" | "paused" | "checkpointing" | "validating" | "repairing" | "revalidating" | "completed" | "failed" | "blocked" | "stopped";
352
383
  export type MissionAutonomy = "manual" | "approval_gated" | "supervised_auto" | "full_auto";
353
384
  export type MissionMilestoneStatus = "pending" | "active" | "completed" | "failed" | "skipped";
@@ -439,6 +470,7 @@ export interface MissionState {
439
470
  heartbeatCount?: number;
440
471
  activeRuntimeMs?: number;
441
472
  activeRunStartedAt?: string | null;
473
+ runtimeHoldActive?: boolean;
442
474
  lastPausedAt?: string;
443
475
  lastResumedAt?: string;
444
476
  lastStoppedAt?: string;
@@ -469,10 +501,13 @@ export function emptyState(): WorkflowState {
469
501
  return { version: 1, mode: "idle", updatedAt: new Date().toISOString() };
470
502
  }
471
503
 
504
+ const VALID_MODES = new Set<string>(["idle", "standard", "awaiting_plan_input", "awaiting_mission_input", "awaiting_clarification", "planning", "plan_draft", "plan_approved", "reviewing", "reviewed", "executing", "executed", "validating", "validated", "repairing", "revalidating", "mission_draft", "mission_awaiting_clarification", "mission_planning", "mission_plan_ready", "mission_approved", "mission_running", "mission_paused", "mission_checkpointing", "mission_validating", "mission_repairing", "mission_revalidating", "mission_final_validating", "mission_completed", "mission_failed", "mission_blocked", "mission_stopped", "cancelled"]);
505
+
472
506
  export function loadState(): WorkflowState {
473
507
  try {
474
508
  if (!existsSync(ACTIVE_STATE_FILE)) return emptyState();
475
509
  const parsed = JSON.parse(readFileSync(ACTIVE_STATE_FILE, "utf8")) as WorkflowState;
510
+ if (parsed.mode && !VALID_MODES.has(parsed.mode)) return emptyState();
476
511
  return { ...emptyState(), ...parsed, version: 1 };
477
512
  } catch {
478
513
  return emptyState();
@@ -710,6 +745,15 @@ export function isMissionRuntimeActiveStatus(status?: MissionStatus): boolean {
710
745
  return status === "planning" || status === "running" || status === "validating" || status === "repairing" || status === "revalidating" || status === "checkpointing";
711
746
  }
712
747
 
748
+ export function isMissionRuntimeActive(mission?: MissionState): boolean {
749
+ if (!mission || mission.status === "completed" || mission.status === "failed" || mission.status === "stopped") return false;
750
+ return isMissionRuntimeActiveStatus(mission.status) || mission.runtimeHoldActive === true;
751
+ }
752
+
753
+ export function isMissionRuntimeHeldActive(mission?: MissionState): boolean {
754
+ return Boolean(mission && !isMissionRuntimeActiveStatus(mission.status) && mission.runtimeHoldActive === true);
755
+ }
756
+
713
757
  function runtimeEndReason(status: MissionStatus): MissionRuntimeSegment["reasonEnded"] {
714
758
  if (status === "paused") return "paused";
715
759
  if (status === "blocked") return "blocked";
@@ -737,9 +781,18 @@ export function isPlanRuntimeActiveMode(mode?: WorkflowMode): boolean {
737
781
  return mode === "planning" || mode === "reviewing" || mode === "executing" || mode === "validating" || mode === "repairing" || mode === "revalidating";
738
782
  }
739
783
 
784
+ export function isPlanRuntimeActive(state?: WorkflowState): boolean {
785
+ if (!state || state.mode === "idle" || state.mode === "cancelled") return false;
786
+ return isPlanRuntimeActiveMode(state.mode) || state.planRuntimeHoldActive === true;
787
+ }
788
+
789
+ export function isPlanRuntimeHeldActive(state?: WorkflowState): boolean {
790
+ return Boolean(state && !isPlanRuntimeActiveMode(state.mode) && state.planRuntimeHoldActive === true);
791
+ }
792
+
740
793
  export function planRuntimeCounterState(state: WorkflowState): "running" | "paused" | "stopped" {
741
794
  if (state.mode === "idle" || state.mode === "cancelled") return "stopped";
742
- if (isPlanRuntimeActiveMode(state.mode)) return "running";
795
+ if (isPlanRuntimeActive(state)) return "running";
743
796
  return "paused";
744
797
  }
745
798
 
@@ -784,8 +837,8 @@ export function applyPlanRuntimeAccounting(previous: WorkflowState | undefined,
784
837
  const createdAt = currentRuntime?.createdAt ?? previousRuntime?.createdAt ?? nowIso;
785
838
  const baseRuntimeMs = safeRuntimeMs(currentRuntime?.activeRuntimeMs ?? previousRuntime?.activeRuntimeMs);
786
839
  const previousStartedAt = previousRuntime?.activeRunStartedAt ?? currentRuntime?.activeRunStartedAt ?? null;
787
- const previousActive = isPlanRuntimeActiveMode(previous?.mode);
788
- const nextActive = isPlanRuntimeActiveMode(state.mode);
840
+ const previousActive = isPlanRuntimeActive(previous);
841
+ const nextActive = isPlanRuntimeActive(state);
789
842
 
790
843
  let activeRuntimeMs = baseRuntimeMs;
791
844
  let activeRunStartedAt = currentRuntime?.activeRunStartedAt ?? previousStartedAt ?? null;
@@ -816,7 +869,7 @@ export function applyPlanRuntimeAccounting(previous: WorkflowState | undefined,
816
869
  export function planActiveRuntimeMs(state: WorkflowState, now = new Date()): number {
817
870
  const runtime = state.planRuntime;
818
871
  const base = safeRuntimeMs(runtime?.activeRuntimeMs);
819
- if (!runtime || !isPlanRuntimeActiveMode(state.mode)) return base;
872
+ if (!runtime || !isPlanRuntimeActive(state)) return base;
820
873
  return base + activeElapsedMs(runtime.activeRunStartedAt, now.getTime(), state.updatedAt);
821
874
  }
822
875
 
@@ -889,8 +942,8 @@ export function standardWallClockAgeMs(state: WorkflowState, now = new Date()):
889
942
  export function applyMissionRuntimeAccounting(previous: MissionState | undefined, mission: MissionState, now = new Date()): MissionState {
890
943
  const nowIso = now.toISOString();
891
944
  const nowMs = now.getTime();
892
- const previousActive = isMissionRuntimeActiveStatus(previous?.status);
893
- const nextActive = isMissionRuntimeActiveStatus(mission.status);
945
+ const previousActive = isMissionRuntimeActive(previous);
946
+ const nextActive = isMissionRuntimeActive(mission);
894
947
  const previousStartedAt = previous?.activeRunStartedAt ?? mission.activeRunStartedAt ?? null;
895
948
  const baseRuntimeMs = safeRuntimeMs(mission.activeRuntimeMs ?? previous?.activeRuntimeMs);
896
949
  const baseSegments = mission.runtimeSegments ?? previous?.runtimeSegments ?? [];
@@ -929,7 +982,7 @@ export function applyMissionRuntimeAccounting(previous: MissionState | undefined
929
982
 
930
983
  export function missionActiveRuntimeMs(mission: MissionState, now = new Date()): number {
931
984
  const base = safeRuntimeMs(mission.activeRuntimeMs);
932
- if (!isMissionRuntimeActiveStatus(mission.status)) return base;
985
+ if (!isMissionRuntimeActive(mission)) return base;
933
986
  return base + activeElapsedMs(mission.activeRunStartedAt, now.getTime(), mission.updatedAt);
934
987
  }
935
988
 
@@ -942,7 +995,7 @@ export function missionWallClockAgeMs(mission: MissionState, now = new Date()):
942
995
  }
943
996
 
944
997
  export function missionRuntimeCounterState(mission: MissionState): "running" | "paused" | "blocked" | "stopped" | "completed" | "failed" | "waiting" {
945
- if (isMissionRuntimeActiveStatus(mission.status)) return "running";
998
+ if (isMissionRuntimeActive(mission)) return "running";
946
999
  if (mission.status === "paused") return "paused";
947
1000
  if (mission.status === "blocked") return "blocked";
948
1001
  if (mission.status === "stopped") return "stopped";
@@ -951,7 +1004,7 @@ export function missionRuntimeCounterState(mission: MissionState): "running" | "
951
1004
  return "waiting";
952
1005
  }
953
1006
 
954
- export function saveMissionState(mission: MissionState): MissionState {
1007
+ export function saveMissionState(mission: MissionState, options: MissionSavingOptions = {}): MissionState {
955
1008
  mkdirSync(MISSION_HISTORY_DIR, { recursive: true });
956
1009
  const savedAt = new Date();
957
1010
  const accounted = applyMissionRuntimeAccounting(readExistingMissionState(mission.id), mission, savedAt);
@@ -959,6 +1012,7 @@ export function saveMissionState(mission: MissionState): MissionState {
959
1012
  const content = JSON.stringify(next, null, 2) + "\n";
960
1013
  writeFileSync(join(MISSION_HISTORY_DIR, `${next.id}.json`), content, { encoding: "utf8", mode: 0o600 });
961
1014
  writeFileSync(LATEST_MISSION_FILE, content, { encoding: "utf8", mode: 0o600 });
1015
+ clearOldMissionStates(options.missionHistoryLimit ?? 50);
962
1016
  return next;
963
1017
  }
964
1018
 
@@ -986,6 +1040,19 @@ export function listMissionStates(): MissionState[] {
986
1040
  return missions.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
987
1041
  }
988
1042
 
1043
+ export function clearOldMissionStates(limit = 50): number {
1044
+ const safeLimit = Math.max(1, Math.min(500, Math.floor(limit)));
1045
+ const missions = listMissionStates();
1046
+ let removed = 0;
1047
+ for (const mission of missions.slice(safeLimit)) {
1048
+ try {
1049
+ unlinkSync(join(MISSION_HISTORY_DIR, `${mission.id}.json`));
1050
+ removed++;
1051
+ } catch { /* ignore */ }
1052
+ }
1053
+ return removed;
1054
+ }
1055
+
989
1056
  export function addMissionCheckpoint(mission: MissionState, summary: string, nextAction: string, milestoneId?: string, details: { filesChanged?: string[]; validationResult?: string; errors?: string[] } = {}): MissionState {
990
1057
  const id = `C${String((mission.checkpoints?.length ?? 0) + 1).padStart(4, "0")}`;
991
1058
  const checkpoint: MissionCheckpoint = {
@@ -21,7 +21,7 @@ export interface SubagentToolProfile {
21
21
  source?: string;
22
22
  }
23
23
 
24
- const MUTATING_SUBAGENT_TOOLS = new Set(["edit", "write"]);
24
+ const MUTATING_SUBAGENT_TOOLS = new Set(["edit"]);
25
25
  const ORCHESTRATOR_AGENT_NAME = "workflow-orchestrator";
26
26
 
27
27
  export function subagentToolsAllowMutation(tools?: string[]): boolean {
@@ -176,5 +176,17 @@ export function planningNeedsOrchestrator(settings: WorkflowSettings, _mode: "pl
176
176
  return orchestrationPolicy === "orchestrator_first" || orchestrationPolicy === "forced_orchestrated";
177
177
  }
178
178
 
179
+ // ── Uniform error classification (#9) ──────────────────────────
180
+ export type SubagentErrorClass = "transient" | "permanent" | "policy";
181
+
182
+ export function classifySubagentError(result: { exitCode: number; stopReason?: string; errorMessage?: string; stderr?: string }): SubagentErrorClass {
183
+ const reason = (result.errorMessage ?? result.stderr ?? "").toLowerCase();
184
+ if (/timed out|stale watchdog|aborted/i.test(reason) || (result.stopReason === "aborted" && /time/i.test(reason))) return "transient";
185
+ if (/repo lock|outside current repository/i.test(reason)) return "policy";
186
+ if (/unknown agent|not installed|not found/i.test(reason)) return "permanent";
187
+ if (result.exitCode === 0) return "transient"; // success
188
+ return "permanent";
189
+ }
190
+
179
191
  // No-op default export so this helper module can be safely auto-discovered as a Pi extension.
180
192
  export default function workflowSuiteNoopExtension(): void {}
@@ -100,15 +100,6 @@ function gitChangedFilesLine(status: string | undefined): string {
100
100
  return `${files.length} changed/untracked file(s): ${preview}${files.length > 16 ? ", ..." : ""}`;
101
101
  }
102
102
 
103
- function workflowSuitePublicImpact(root: string, pkg: Record<string, unknown> | undefined, status: string | undefined): string {
104
- if (pkg?.name !== "@mediadatafusion/pi-workflow-suite") return "not applicable unless the target repo is the Pi Workflow Suite package";
105
- const files = (status ?? "").split("\n").map((line) => line.trim().slice(3).trim()).filter(Boolean);
106
- if (!files.length) return "Pi Workflow Suite package repo detected; no current git changes detected";
107
- const publicPrefixes = ["extensions/", "agents/", "skills/", "config/", "docs/", "scripts/", "README.md", "LICENSE.md", "package.json", "package-lock.json", "tsconfig.json", "AGENTS.md"];
108
- const publicFiles = files.filter((file) => publicPrefixes.some((prefix) => file === prefix || file.startsWith(prefix)));
109
- return publicFiles.length ? `yes — public/live package files touched: ${publicFiles.slice(0, 12).join(", ")}${publicFiles.length > 12 ? ", ..." : ""}` : "Pi Workflow Suite package repo detected; changed files are not in public package paths";
110
- }
111
-
112
103
  export function renderHandoffProjectContext(cwd?: string): string {
113
104
  const current = cwd ?? process.cwd();
114
105
  const repoRoot = safeGit(current, ["rev-parse", "--show-toplevel"]);
@@ -118,7 +109,6 @@ export function renderHandoffProjectContext(cwd?: string): string {
118
109
  const head = safeGit(root, ["rev-parse", "--short", "HEAD"]);
119
110
  const status = safeGit(root, ["status", "--short"]);
120
111
  const instructions = detectedInstructionFiles(root);
121
- const isSuite = pkg?.name === "@mediadatafusion/pi-workflow-suite";
122
112
  return `## Target Application Context
123
113
  - CWD: ${current}
124
114
  - Git root: ${repoRoot ?? "not detected"}
@@ -126,14 +116,7 @@ export function renderHandoffProjectContext(cwd?: string): string {
126
116
  - HEAD: ${head ?? "unknown"}
127
117
  - Application profile: ${detectProjectProfile(root, pkg)}
128
118
  - Project instructions detected: ${instructions.length ? instructions.join(", ") : "none"}
129
- - Changed files: ${gitChangedFilesLine(status)}
130
-
131
- ## Pi Workflow Suite Context
132
- - Target is Pi Workflow Suite package repo: ${isSuite ? "yes" : "no"}
133
- - Context boundary: keep the target application repo, the Workflow Suite DEV worktree, the live Pi runtime, and the public main package mirror distinct.
134
- - Public package impact: ${workflowSuitePublicImpact(root, pkg, status)}
135
- - Live runtime sync: only confirmed when scripts/install-to-live.sh has been run and reports auth/settings/sessions/workflow state were not touched.
136
- - Promotion expectation for suite package changes: validate on DEV, sync live when requested, promote the same public-safe files to main, validate main, push both branches, then verify origin/main..origin/DEV parity.`;
119
+ - Changed files: ${gitChangedFilesLine(status)}`;
137
120
  }
138
121
 
139
122
  function planNeedsClarification(text?: string): boolean {
@@ -145,11 +128,17 @@ function planNeedsClarification(text?: string): boolean {
145
128
  }
146
129
 
147
130
  function planStatus(state: WorkflowState): string {
131
+ if (state.planProgress?.lifecycleStatus === "blocked") return "Blocked";
132
+ if (planReviewRepairActive(state)) return "Repairing";
148
133
  if (state.approvedPlan) return "Approved";
149
134
  if (state.draftPlan) return "Draft";
150
135
  return "None";
151
136
  }
152
137
 
138
+ function planReviewRepairActive(state: WorkflowState): boolean {
139
+ return state.reviewRepairInProgress === true || state.lastReviewRepairStatus === "running" || state.repairRetryState?.review?.inProgress === true;
140
+ }
141
+
153
142
  function isMissionMode(mode: string): boolean {
154
143
  return mode === "awaiting_mission_input" || mode.startsWith("mission_");
155
144
  }
@@ -374,7 +363,7 @@ export function renderWorkflowSummary(state: WorkflowState, cwd?: string): strin
374
363
  if (finalStop && (state.mode === "awaiting_plan_input" || state.mode === "awaiting_mission_input" || state.mode === "validated" || state.mode === "mission_blocked" || state.mode === "mission_completed" || state.mode === "mission_failed" || state.mode === "mission_stopped")) {
375
364
  return `# Workflow Summary\n\n${finalStop}`;
376
365
  }
377
- return `# Workflow Summary\n\n${renderHandoffProjectContext(cwd)}\n\n## Original Task\n${state.task ?? "(none)"}\n\n## Models Used\n- Planner: ${state.modelsUsed?.planner ?? "(not recorded)"}\n- Executor: ${state.modelsUsed?.executor ?? "(not recorded)"}\n- Validator: ${state.modelsUsed?.validator ?? "(not run)"}\n- Reviewer: ${state.modelsUsed?.reviewer ?? "(not run)"}\n\n## Current Model Configuration\n${renderWorkflowModels(settings)}\n\n## Approved Plan\n${compact(state.approvedPlan, 2200)}\n\n## Execution Summary\n${compact(state.executionSummary, 1800)}\n\n## Validation Result\n${state.validationVerdict ?? "(not validated)"}\n\n${compact(state.validationReport, 1800)}\n\n## Remaining Risks\nReview validation notes, unrun tests, changed files, and public/internal package impact before committing or promoting.\n\n## Recommended Next Action\nRun project checks manually if they were not run, then review the target repo diff. For Pi Workflow Suite package work, complete DEV validation, live sync if requested, main promotion, main validation, and branch parity verification.\n\n## Exact Resume Instructions\n- Re-open the target repo shown above and confirm branch/status.\n- Run /workflow status before continuing.\n- Review this summary alongside the saved plan record when available.\n- Re-read detected project instruction files before any new edits.\n\n## Suggested Commit Message\nImplement approved workflow plan`;
366
+ return `# Workflow Summary\n\n${renderHandoffProjectContext(cwd)}\n\n## Original Task\n${state.task ?? "(none)"}\n\n## Models Used\n- Planner: ${state.modelsUsed?.planner ?? "(not recorded)"}\n- Executor: ${state.modelsUsed?.executor ?? "(not recorded)"}\n- Validator: ${state.modelsUsed?.validator ?? "(not run)"}\n- Reviewer: ${state.modelsUsed?.reviewer ?? "(not run)"}\n\n## Current Model Configuration\n${renderWorkflowModels(settings)}\n\n## Approved Plan\n${compact(state.approvedPlan, 2200)}\n\n## Execution Summary\n${compact(state.executionSummary, 1800)}\n\n## Validation Result\n${state.validationVerdict ?? "(not validated)"}\n\n${compact(state.validationReport, 1800)}\n\n## Remaining Risks\nReview validation notes, unrun tests, and changed files before committing or promoting.\n\n## Recommended Next Action\nRun project checks manually if they were not run, then review the target repo diff.\n\n## Exact Resume Instructions\n- Re-open the target repo shown above and confirm branch/status.\n- Run /workflow status before continuing.\n- Review this summary alongside the saved plan record when available.\n- Re-read detected project instruction files before any new edits.`;
378
367
  }
379
368
 
380
369
  // No-op default export so this helper module can be safely auto-discovered as a Pi extension.