@mediadatafusion/pi-workflow-suite 0.0.11 → 0.0.12
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.
- package/CHANGELOG.md +36 -0
- package/README.md +26 -17
- package/VERSION +1 -1
- package/agents/codebase-research.md +7 -5
- package/agents/general-worker.md +9 -7
- package/agents/implementation-planning.md +5 -3
- package/agents/quality-validation.md +9 -8
- package/agents/workflow-orchestrator.md +9 -7
- package/config/prompts/execute-approved-plan.md +12 -2
- package/config/prompts/mission-final-validation.md +38 -5
- package/config/prompts/mission-plan.md +17 -1
- package/config/prompts/mission-repair.md +16 -2
- package/config/prompts/mission-review-prompt.md +19 -6
- package/config/prompts/mission-run.md +18 -5
- package/config/prompts/validate-approved-plan.md +57 -3
- package/config/prompts/workflow-plan-prompt.md +11 -1
- package/config/prompts/workflow-repair.md +18 -2
- package/config/prompts/workflow-reviewer-prompt.md +25 -9
- package/config/prompts/workflow-summary.md +1 -4
- package/config/workflow-settings.example.json +13 -11
- package/docs/assets/mediadatafusion-logo.png +0 -0
- package/docs/assets/pi-workflow-suite-demo.gif +0 -0
- package/docs/assets/pi-workflow-suite-demo.mp4 +0 -0
- package/docs/assets/pi-workflow-suite-header.png +0 -0
- package/docs/assets/pi-workflow-suite-video-thumb.png +0 -0
- package/docs/assets/readme-link-commands.svg +10 -0
- package/docs/assets/readme-link-install.svg +10 -0
- package/docs/assets/readme-link-quick-start.svg +10 -0
- package/docs/assets/readme-link-settings.svg +10 -0
- package/docs/assets/screenshots/.gitkeep +1 -0
- package/docs/assets/screenshots/00-mission-home.png +0 -0
- package/docs/assets/screenshots/01-startup-Logo.png +0 -0
- package/docs/assets/screenshots/02-theme-settings.png +0 -0
- package/docs/assets/screenshots/03-GlobalSafetySettings.png +0 -0
- package/docs/assets/screenshots/04-SharedSubAgentsSettings.png +0 -0
- package/docs/assets/screenshots/05-mission-mode.png +0 -0
- package/docs/assets/screenshots/06-diagram-mermaid.png +0 -0
- package/extensions/subagent/index.ts +41 -18
- package/extensions/subagent/repolock-guard.ts +224 -4
- package/extensions/subagent/runner.ts +136 -12
- package/extensions/workflow-model-router.ts +124 -41
- package/extensions/workflow-modes.ts +3791 -967
- package/extensions/workflow-settings-capabilities.ts +10 -0
- package/extensions/workflow-state.ts +77 -10
- package/extensions/workflow-subagent-policy.ts +13 -1
- package/extensions/workflow-summary.ts +8 -19
- package/extensions/workflow-tool-guard.ts +326 -35
- package/extensions/workflow-validation-classifier.ts +46 -4
- package/extensions/workflow-web-tools.ts +361 -1
- package/package.json +9 -5
- package/scripts/audit-live.sh +1 -1
- package/scripts/build-package-export.mjs +8 -13
- package/scripts/check-clean-release-tree.sh +3 -2
- package/scripts/check-package-media.mjs +78 -0
- package/scripts/install-to-live.sh +2 -0
- package/scripts/package-media-config.mjs +28 -0
- package/scripts/prepare-package-readme.mjs +19 -18
- package/scripts/quarantine-live-junk.sh +1 -1
- package/scripts/verify-live.sh +9 -1
- package/skills/implementation-planning/SKILL.md +1 -1
- package/skills/safe-execution/SKILL.md +1 -1
- 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 (
|
|
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 =
|
|
788
|
-
const nextActive =
|
|
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 || !
|
|
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 =
|
|
893
|
-
const nextActive =
|
|
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 (!
|
|
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 (
|
|
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"
|
|
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
|
|
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.
|