@mediadatafusion/pi-workflow-suite 0.0.9 → 0.0.11
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 +38 -0
- package/CONTRIBUTING.md +14 -4
- package/README.md +153 -129
- package/SECURITY.md +6 -2
- package/SUPPORT.md +3 -5
- package/VERSION +1 -1
- package/config/prompts/mission-final-validation.md +3 -2
- package/config/prompts/mission-review-prompt.md +42 -0
- package/config/prompts/validate-approved-plan.md +4 -3
- package/config/prompts/workflow-reviewer-prompt.md +44 -0
- package/extensions/subagent/index.ts +69 -3
- package/extensions/subagent/repolock-guard.ts +111 -0
- package/extensions/subagent/runner.ts +51 -3
- package/extensions/workflow-model-router.ts +28 -14
- package/extensions/workflow-modes.ts +1256 -337
- package/extensions/workflow-parsers.ts +2 -1
- package/extensions/workflow-state.ts +64 -6
- package/extensions/workflow-tool-guard.ts +172 -43
- package/extensions/workflow-validation-classifier.ts +5 -2
- package/package.json +5 -3
- package/scripts/install-to-live.sh +2 -1
|
@@ -204,7 +204,8 @@ export function formatAnswersForPlanner(questions: ClarificationQuestion[], answ
|
|
|
204
204
|
|
|
205
205
|
export function planValidationStatusForVerdict(verdict: WorkflowState["validationVerdict"]): PlanValidationStatus {
|
|
206
206
|
if (verdict === "PASS") return "pass";
|
|
207
|
-
if (verdict === "
|
|
207
|
+
if (verdict === "PARTIAL PASS") return "partial pass";
|
|
208
|
+
if (verdict === "UNKNOWN") return "unknown";
|
|
208
209
|
return "fail";
|
|
209
210
|
}
|
|
210
211
|
|
|
@@ -93,7 +93,7 @@ export interface StandardRuntimeState {
|
|
|
93
93
|
|
|
94
94
|
export type PlanLifecycleStatus = "planning" | "awaiting_clarification" | "plan_ready" | "approved" | "reviewing" | "executing" | "validating" | "repairing" | "revalidating" | "completed" | "blocked";
|
|
95
95
|
export type PlanStepStatus = "pending" | "active" | "completed" | "failed" | "blocked" | "skipped";
|
|
96
|
-
export type PlanValidationStatus = "pending" | "running" | "pass" | "fail" | "unknown";
|
|
96
|
+
export type PlanValidationStatus = "pending" | "running" | "pass" | "partial pass" | "fail" | "unknown";
|
|
97
97
|
|
|
98
98
|
export interface PlanProgressStep {
|
|
99
99
|
id: string;
|
|
@@ -163,12 +163,32 @@ export interface CompletedPlanSummary {
|
|
|
163
163
|
finalReport?: string;
|
|
164
164
|
}
|
|
165
165
|
|
|
166
|
+
export interface BlockedPlanResumeSnapshot {
|
|
167
|
+
task?: string;
|
|
168
|
+
originalTask?: string;
|
|
169
|
+
approvedPlan?: string;
|
|
170
|
+
planHistoryId?: string;
|
|
171
|
+
approvedPlanHistoryId?: string;
|
|
172
|
+
executionSummary?: string;
|
|
173
|
+
validationReport?: string;
|
|
174
|
+
validationVerdict?: "PASS" | "PARTIAL PASS" | "FAIL" | "UNKNOWN";
|
|
175
|
+
lastValidationFailure?: string;
|
|
176
|
+
lastRepairAttempt?: string;
|
|
177
|
+
repairHistory?: WorkflowRepairHistoryEntry[];
|
|
178
|
+
lastRepairStatus?: "none" | "running" | "completed" | "failed" | "blocked";
|
|
179
|
+
currentValidationRetry?: number;
|
|
180
|
+
workflowValidationRetryCount?: number;
|
|
181
|
+
planRuntime?: PlanRuntimeState;
|
|
182
|
+
planProgress?: PlanProgressState;
|
|
183
|
+
}
|
|
184
|
+
|
|
166
185
|
export interface WorkflowFinalStopSummary {
|
|
167
186
|
stoppedAt: string;
|
|
168
187
|
kind: "plan" | "mission";
|
|
169
188
|
status: "completed" | "blocked";
|
|
170
189
|
title: string;
|
|
171
190
|
summary: string;
|
|
191
|
+
blockedPlanSnapshot?: BlockedPlanResumeSnapshot;
|
|
172
192
|
}
|
|
173
193
|
|
|
174
194
|
export interface CompletedMissionSummary {
|
|
@@ -234,9 +254,20 @@ export interface WorkflowState {
|
|
|
234
254
|
lastRepairAttempt?: string;
|
|
235
255
|
repairHistory?: WorkflowRepairHistoryEntry[];
|
|
236
256
|
lastRepairStatus?: "none" | "running" | "completed" | "failed" | "blocked";
|
|
257
|
+
concreteRepairableIssue?: boolean;
|
|
258
|
+
manualVerificationRequired?: boolean;
|
|
259
|
+
evidenceGap?: boolean;
|
|
260
|
+
lastValidationCompletedAt?: string;
|
|
237
261
|
planStepValidationIndex?: number;
|
|
262
|
+
planExecutionStepIndex?: number;
|
|
238
263
|
planRuntime?: PlanRuntimeState;
|
|
239
264
|
planProgress?: PlanProgressState;
|
|
265
|
+
planProgressLastToolStep?: number;
|
|
266
|
+
planProgressLastToolStatus?: PlanStepStatus;
|
|
267
|
+
planProgressLastToolAt?: string;
|
|
268
|
+
planTokensUsed?: number;
|
|
269
|
+
missionTokensUsed?: number;
|
|
270
|
+
standardTokensUsed?: number;
|
|
240
271
|
standardRuntime?: StandardRuntimeState;
|
|
241
272
|
standardTodo?: StandardTodoState;
|
|
242
273
|
standardLastAutoCheckAt?: string;
|
|
@@ -290,6 +321,15 @@ export interface SavedWorkflowPlan {
|
|
|
290
321
|
finalReport?: string;
|
|
291
322
|
modelsUsed?: WorkflowState["modelsUsed"];
|
|
292
323
|
subagents?: Record<string, unknown>;
|
|
324
|
+
planProgress?: WorkflowState["planProgress"];
|
|
325
|
+
planRuntime?: WorkflowState["planRuntime"];
|
|
326
|
+
planExecutionStepIndex?: number;
|
|
327
|
+
planStepValidationIndex?: number;
|
|
328
|
+
currentValidationRetry?: number;
|
|
329
|
+
workflowValidationRetryCount?: number;
|
|
330
|
+
repairRetryState?: WorkflowState["repairRetryState"];
|
|
331
|
+
repairHistory?: WorkflowState["repairHistory"];
|
|
332
|
+
reviewHistory?: WorkflowState["reviewHistory"];
|
|
293
333
|
}
|
|
294
334
|
|
|
295
335
|
export interface PlanSavingOptions {
|
|
@@ -384,6 +424,9 @@ export interface MissionState {
|
|
|
384
424
|
reviewHistory?: WorkflowReviewHistoryEntry[];
|
|
385
425
|
reviewRepairInProgress?: boolean;
|
|
386
426
|
lastValidationResult?: string;
|
|
427
|
+
concreteRepairableIssue?: boolean;
|
|
428
|
+
manualVerificationRequired?: boolean;
|
|
429
|
+
evidenceGap?: boolean;
|
|
387
430
|
modelsUsed: Record<string, string>;
|
|
388
431
|
subagentsUsed: string[];
|
|
389
432
|
approvalRequired: boolean;
|
|
@@ -534,6 +577,15 @@ export function saveWorkflowPlan(state: WorkflowState, options: PlanSavingOption
|
|
|
534
577
|
finalReport: options.finalReport?.trim() ? (redactSecrets(compact(options.finalReport, 5000)) ?? compact(options.finalReport, 5000)) : undefined,
|
|
535
578
|
modelsUsed: state.modelsUsed,
|
|
536
579
|
subagents: options.subagents,
|
|
580
|
+
planProgress: state.planProgress,
|
|
581
|
+
planRuntime: state.planRuntime,
|
|
582
|
+
planExecutionStepIndex: state.planExecutionStepIndex,
|
|
583
|
+
planStepValidationIndex: state.planStepValidationIndex,
|
|
584
|
+
currentValidationRetry: state.currentValidationRetry,
|
|
585
|
+
workflowValidationRetryCount: state.workflowValidationRetryCount,
|
|
586
|
+
repairRetryState: state.repairRetryState,
|
|
587
|
+
repairHistory: state.repairHistory,
|
|
588
|
+
reviewHistory: state.reviewHistory,
|
|
537
589
|
};
|
|
538
590
|
|
|
539
591
|
writeFileSync(LATEST_PLAN_FILE, JSON.stringify(record, null, 2) + "\n", { encoding: "utf8", mode: 0o600 });
|
|
@@ -713,8 +765,10 @@ function activeElapsedMs(startedAt: string | null | undefined, nowMs: number, la
|
|
|
713
765
|
const parsed = Date.parse(startedAt ?? "");
|
|
714
766
|
if (!Number.isFinite(parsed)) return 0;
|
|
715
767
|
const updated = Date.parse(lastUpdatedAt ?? "");
|
|
716
|
-
const end = parsed < RUNTIME_SESSION_STARTED_AT_MS
|
|
717
|
-
?
|
|
768
|
+
const end = parsed < RUNTIME_SESSION_STARTED_AT_MS
|
|
769
|
+
? (Number.isFinite(updated) && updated < RUNTIME_SESSION_STARTED_AT_MS
|
|
770
|
+
? Math.max(parsed, updated)
|
|
771
|
+
: RUNTIME_SESSION_STARTED_AT_MS)
|
|
718
772
|
: nowMs;
|
|
719
773
|
return Math.max(0, end - parsed);
|
|
720
774
|
}
|
|
@@ -769,7 +823,9 @@ export function planActiveRuntimeMs(state: WorkflowState, now = new Date()): num
|
|
|
769
823
|
export function planWallClockAgeMs(state: WorkflowState, now = new Date()): number {
|
|
770
824
|
const start = Date.parse(state.planRuntime?.createdAt ?? "");
|
|
771
825
|
if (!Number.isFinite(start)) return 0;
|
|
772
|
-
|
|
826
|
+
const terminalTimestamp = planRuntimeCounterState(state) === "stopped" ? state.updatedAt : undefined;
|
|
827
|
+
const end = terminalTimestamp ? Date.parse(terminalTimestamp) : now.getTime();
|
|
828
|
+
return Math.max(0, (Number.isFinite(end) ? end : now.getTime()) - start);
|
|
773
829
|
}
|
|
774
830
|
|
|
775
831
|
export function applyStandardRuntimeAccounting(previous: WorkflowState | undefined, state: WorkflowState, now = new Date()): WorkflowState {
|
|
@@ -825,7 +881,9 @@ export function standardActiveRuntimeMs(state: WorkflowState, now = new Date()):
|
|
|
825
881
|
export function standardWallClockAgeMs(state: WorkflowState, now = new Date()): number {
|
|
826
882
|
const start = Date.parse(state.standardRuntime?.createdAt ?? "");
|
|
827
883
|
if (!Number.isFinite(start)) return 0;
|
|
828
|
-
|
|
884
|
+
const terminalTimestamp = standardRuntimeCounterState(state) === "stopped" ? state.updatedAt : undefined;
|
|
885
|
+
const end = terminalTimestamp ? Date.parse(terminalTimestamp) : now.getTime();
|
|
886
|
+
return Math.max(0, (Number.isFinite(end) ? end : now.getTime()) - start);
|
|
829
887
|
}
|
|
830
888
|
|
|
831
889
|
export function applyMissionRuntimeAccounting(previous: MissionState | undefined, mission: MissionState, now = new Date()): MissionState {
|
|
@@ -858,7 +916,7 @@ export function applyMissionRuntimeAccounting(previous: MissionState | undefined
|
|
|
858
916
|
lastResumedAt: mission.lastResumedAt ?? nowIso,
|
|
859
917
|
};
|
|
860
918
|
} else if (nextActive && previousStartedAt) {
|
|
861
|
-
next = { ...next, activeRunStartedAt: previousStartedAt };
|
|
919
|
+
next = { ...next, activeRunStartedAt: Date.parse(previousStartedAt) < RUNTIME_SESSION_STARTED_AT_MS ? nowIso : previousStartedAt };
|
|
862
920
|
} else if (!nextActive) {
|
|
863
921
|
next = { ...next, activeRunStartedAt: null };
|
|
864
922
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { existsSync, realpathSync } from "node:fs";
|
|
2
2
|
import { execFileSync } from "node:child_process";
|
|
3
|
-
import { isAbsolute, resolve } from "node:path";
|
|
3
|
+
import { isAbsolute, resolve, join, dirname } from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
4
5
|
import { getAgentDir, type ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
5
6
|
import { loadWorkflowSettings } from "./workflow-model-router.js";
|
|
6
7
|
import type { WorkflowState } from "./workflow-state.js";
|
|
@@ -22,9 +23,9 @@ export const EXECUTION_RESULT_TOOLS = [WORKFLOW_EXECUTION_RESULT_TOOL, MISSION_M
|
|
|
22
23
|
export const VALIDATION_RESULT_TOOLS = [WORKFLOW_VALIDATION_RESULT_TOOL];
|
|
23
24
|
export const REPAIR_RESULT_TOOLS = [WORKFLOW_REPAIR_RESULT_TOOL];
|
|
24
25
|
export const STANDARD_RESULT_TOOLS = [STANDARD_HANDOFF_RESULT_TOOL];
|
|
25
|
-
export const BASE_EXECUTE_TOOLS = ["read", "grep", "find", "ls", "edit", "write", "bash",
|
|
26
|
-
export const EXECUTE_TOOLS = [...BASE_EXECUTE_TOOLS, ...EXECUTION_RESULT_TOOLS, ...REPAIR_RESULT_TOOLS];
|
|
27
|
-
export const VALIDATOR_TOOLS = ["read", "grep", "find", "ls", "bash", WORKFLOW_DIAGRAM_TOOL, ...REVIEW_RESULT_TOOLS, ...VALIDATION_RESULT_TOOLS];
|
|
26
|
+
export const BASE_EXECUTE_TOOLS = ["read", "grep", "find", "ls", "edit", "write", "bash", WORKFLOW_DIAGRAM_TOOL];
|
|
27
|
+
export const EXECUTE_TOOLS = [...BASE_EXECUTE_TOOLS, WORKFLOW_PROGRESS_TOOL, ...EXECUTION_RESULT_TOOLS, ...REPAIR_RESULT_TOOLS];
|
|
28
|
+
export const VALIDATOR_TOOLS = ["read", "grep", "find", "ls", "bash", "write", WORKFLOW_DIAGRAM_TOOL, ...REVIEW_RESULT_TOOLS, ...VALIDATION_RESULT_TOOLS];
|
|
28
29
|
|
|
29
30
|
|
|
30
31
|
const PATH_SCOPED_TOOLS = new Set(["read", "grep", "find", "ls", "edit", "write"]);
|
|
@@ -58,33 +59,87 @@ function pathInsideRoot(candidate: string, root: string): boolean {
|
|
|
58
59
|
return candidate === root || candidate.startsWith(`${root}/`);
|
|
59
60
|
}
|
|
60
61
|
|
|
61
|
-
function
|
|
62
|
-
return
|
|
62
|
+
function repoLockRoot(cwd: string): string {
|
|
63
|
+
return process.env.PI_WORKFLOW_REPO_LOCK_ENABLED === "1" && process.env.PI_WORKFLOW_REPO_LOCK_ROOT
|
|
64
|
+
? safeRealpath(process.env.PI_WORKFLOW_REPO_LOCK_ROOT)
|
|
65
|
+
: repoRootForCwd(cwd);
|
|
63
66
|
}
|
|
64
67
|
|
|
65
|
-
function
|
|
66
|
-
const
|
|
67
|
-
return
|
|
68
|
+
function protectedRepoPath(candidate: string, root: string): boolean {
|
|
69
|
+
const rel = candidate === root ? "" : candidate.slice(root.length + 1);
|
|
70
|
+
return rel === ".pi" || rel.startsWith(".pi/");
|
|
68
71
|
}
|
|
69
72
|
|
|
70
|
-
function
|
|
71
|
-
const root =
|
|
73
|
+
function piRuntimeInstructionPath(candidate: string): boolean {
|
|
74
|
+
const root = safeRealpath(getAgentDir());
|
|
75
|
+
if (!pathInsideRoot(candidate, root)) return false;
|
|
76
|
+
const rel = candidate === root ? "" : candidate.slice(root.length + 1);
|
|
77
|
+
return rel === "skills" || rel.startsWith("skills/")
|
|
78
|
+
|| rel === "agents" || rel.startsWith("agents/")
|
|
79
|
+
|| rel === "config/prompts" || rel.startsWith("config/prompts/")
|
|
80
|
+
|| rel === "prompts" || rel.startsWith("prompts/")
|
|
81
|
+
|| rel === "themes" || rel.startsWith("themes/");
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function packageInstructionPath(candidate: string): boolean {
|
|
85
|
+
const root = safeRealpath(join(dirname(fileURLToPath(import.meta.url)), ".."));
|
|
86
|
+
if (!pathInsideRoot(candidate, root)) return false;
|
|
87
|
+
const rel = candidate === root ? "" : candidate.slice(root.length + 1);
|
|
88
|
+
return rel === "skills" || rel.startsWith("skills/")
|
|
89
|
+
|| rel === "agents" || rel.startsWith("agents/")
|
|
90
|
+
|| rel === "config/prompts" || rel.startsWith("config/prompts/")
|
|
91
|
+
|| rel === "prompts" || rel.startsWith("prompts/")
|
|
92
|
+
|| rel === "themes" || rel.startsWith("themes/");
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function repoLockPathBlock(pathValue: unknown, cwd: string, tool: string): string | undefined {
|
|
96
|
+
const root = repoLockRoot(cwd);
|
|
72
97
|
const candidate = resolveCandidatePath(typeof pathValue === "string" && pathValue.trim() ? pathValue.trim() : ".", cwd);
|
|
73
|
-
if (!
|
|
98
|
+
if (!pathInsideRoot(candidate, root)) {
|
|
99
|
+
if ((tool === "read" || tool === "grep" || tool === "find" || tool === "ls") && (piRuntimeInstructionPath(candidate) || packageInstructionPath(candidate))) return undefined;
|
|
100
|
+
if (candidate.startsWith("/private/tmp/") || candidate.startsWith("/tmp/") || candidate.startsWith("/var/tmp/")) return undefined;
|
|
101
|
+
return `Repo Lock blocked path outside current repository: ${candidate} (repo root: ${root})`;
|
|
102
|
+
}
|
|
103
|
+
if ((tool === "edit" || tool === "write") && protectedRepoPath(candidate, root)) return `Repo Lock blocked ${tool} for protected project control path: ${candidate}`;
|
|
74
104
|
return undefined;
|
|
75
105
|
}
|
|
76
106
|
|
|
107
|
+
function stripHereDocBodies(command: string): string {
|
|
108
|
+
const lines = command.split("\n");
|
|
109
|
+
const kept: string[] = [];
|
|
110
|
+
for (let i = 0; i < lines.length; i++) {
|
|
111
|
+
const line = lines[i];
|
|
112
|
+
kept.push(line);
|
|
113
|
+
const match = line.match(/<<[-]?\s*['\"]?([A-Za-z_][A-Za-z0-9_]*)['\"]?/);
|
|
114
|
+
if (!match) continue;
|
|
115
|
+
const marker = match[1];
|
|
116
|
+
i++;
|
|
117
|
+
while (i < lines.length && lines[i].trim() !== marker) i++;
|
|
118
|
+
}
|
|
119
|
+
return kept.join("\n");
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function stripUriTokens(command: string): string {
|
|
123
|
+
return command.replace(/\b[A-Za-z][A-Za-z0-9+.-]*:\/\/[^\s'"`;&|)]*/g, " ");
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function bashPathCandidates(command: string): string[] {
|
|
127
|
+
const trimmed = stripUriTokens(stripHereDocBodies(command)).trim();
|
|
128
|
+
if (!trimmed) return [];
|
|
129
|
+
return Array.from(trimmed.matchAll(/(?:^|[\s=:'"`])((?:\.{1,2}|~|\/)[^\s'"`;&|)]*)/g)).map((match) => match[1]).filter(Boolean);
|
|
130
|
+
}
|
|
131
|
+
|
|
77
132
|
function repoLockBashBlock(command: string, cwd: string): string | undefined {
|
|
78
|
-
const
|
|
79
|
-
|
|
80
|
-
const root = repoRootForCwd(cwd);
|
|
81
|
-
const pathCandidates = Array.from(trimmed.matchAll(/(?:^|[\s=:'"`])((?:\.{1,2}|~|\/)[^\s'"`;&|)]*)/g)).map((match) => match[1]).filter(Boolean);
|
|
133
|
+
const root = repoLockRoot(cwd);
|
|
134
|
+
const pathCandidates = bashPathCandidates(command);
|
|
82
135
|
for (const raw of pathCandidates) {
|
|
83
|
-
if (raw === "." || raw === "./") continue;
|
|
136
|
+
if (raw === "." || raw === "./" || raw === "/") continue;
|
|
84
137
|
const cleaned = raw.replace(/[),]+$/, "");
|
|
85
138
|
if (!cleaned || cleaned.startsWith("./node_modules/.bin")) continue;
|
|
139
|
+
if (cleaned.startsWith("/dev/")) continue;
|
|
140
|
+
if (cleaned.startsWith("/tmp/") || cleaned.startsWith("/private/tmp/") || cleaned.startsWith("/var/tmp/")) continue;
|
|
86
141
|
const candidate = resolveCandidatePath(cleaned, cwd);
|
|
87
|
-
if (!
|
|
142
|
+
if (!pathInsideRoot(candidate, root)) return `Repo Lock blocked bash path outside current repository: ${cleaned} -> ${candidate} (repo root: ${root})`;
|
|
88
143
|
}
|
|
89
144
|
return undefined;
|
|
90
145
|
}
|
|
@@ -107,6 +162,26 @@ const BLOCKED_EXECUTE_BASH: RegExp[] = [
|
|
|
107
162
|
/\bpnpm\s+add\b/i,
|
|
108
163
|
/\byarn\s+add\b/i,
|
|
109
164
|
/\bpip\s+install\b/i,
|
|
165
|
+
/\bpip3?\s+install\b/i,
|
|
166
|
+
/\bbundle\s+install\b/i,
|
|
167
|
+
/\bgem\s+install\b/i,
|
|
168
|
+
/\bcargo\s+install\b/i,
|
|
169
|
+
/\bgo\s+(?:get|install)\b/i,
|
|
170
|
+
/\bdeno\s+(?:install|add|cache)\b/i,
|
|
171
|
+
/\bcomposer\s+(?:install|require|update)\b/i,
|
|
172
|
+
/\bmix\s+(?:deps\.get|deps\.compile)\b/i,
|
|
173
|
+
/\bbrew\s+install\b/i,
|
|
174
|
+
/\bapt\s+(?:install|get\s+install)\b/i,
|
|
175
|
+
/\byum\s+install\b/i,
|
|
176
|
+
/\bdnf\s+install\b/i,
|
|
177
|
+
/\bapk\s+add\b/i,
|
|
178
|
+
/\bnuget\s+install\b/i,
|
|
179
|
+
/\bdotnet\s+(?:add\s+package|tool\s+install|restore)\b/i,
|
|
180
|
+
/\bcabal\s+(?:install|update)\b/i,
|
|
181
|
+
/\bstack\s+(?:install|update)\b/i,
|
|
182
|
+
/\bconan\s+install\b/i,
|
|
183
|
+
/\bvcpkg\s+install\b/i,
|
|
184
|
+
/\bcoursier\s+(?:install|fetch)\b/i,
|
|
110
185
|
/\bcurl\b[^\n]*\|\s*sh\b/i,
|
|
111
186
|
/\bwget\b[^\n]*\|\s*sh\b/i,
|
|
112
187
|
/\bvercel\s+deploy\b/i,
|
|
@@ -125,7 +200,7 @@ function isPlanMode(mode: WorkflowState["mode"]): boolean {
|
|
|
125
200
|
}
|
|
126
201
|
|
|
127
202
|
function isValidatorMode(mode: WorkflowState["mode"]): boolean {
|
|
128
|
-
return mode === "reviewing" || mode === "reviewed" || mode === "validating" || mode === "revalidating" || mode === "
|
|
203
|
+
return mode === "reviewing" || mode === "reviewed" || mode === "validating" || mode === "revalidating" || mode === "mission_validating" || mode === "mission_revalidating" || mode === "mission_final_validating";
|
|
129
204
|
}
|
|
130
205
|
|
|
131
206
|
function isValidationResultMode(mode: WorkflowState["mode"]): boolean {
|
|
@@ -140,9 +215,17 @@ function isSubagentWorker(): boolean {
|
|
|
140
215
|
return process.env.PI_SUBAGENT_WORKER === "1";
|
|
141
216
|
}
|
|
142
217
|
|
|
218
|
+
const PACKAGE_INSTALL_RE = /\b(?:npm\s+install|pnpm\s+add|yarn\s+add|pip3?\s+install|bundle\s+install|gem\s+install|cargo\s+install|go\s+(?:get|install)|deno\s+(?:install|add|cache)|composer\s+(?:install|require|update)|mix\s+deps\.(?:get|compile)|brew\s+install|apt(?:-get)?\s+install|yum\s+install|dnf\s+install|apk\s+add|nuget\s+install|dotnet\s+(?:add\s+package|tool\s+install|restore)|cabal\s+(?:install|update)|stack\s+(?:install|update)|conan\s+install|vcpkg\s+install|coursier\s+(?:install|fetch))\b/i;
|
|
219
|
+
|
|
220
|
+
function isPackageInstallCommand(command: string): boolean {
|
|
221
|
+
return PACKAGE_INSTALL_RE.test(command);
|
|
222
|
+
}
|
|
223
|
+
|
|
143
224
|
function commandBlocked(command: string, cwd?: string): boolean {
|
|
144
225
|
const settings = loadWorkflowSettings(cwd);
|
|
145
|
-
|
|
226
|
+
if (settings.safety.blockDestructiveCommands === false) return false;
|
|
227
|
+
if (isPackageInstallCommand(command) && settings.safety.allowPackageInstallInExecution !== false) return false;
|
|
228
|
+
return isBlockedExecuteCommand(command);
|
|
146
229
|
}
|
|
147
230
|
|
|
148
231
|
function standardTodoMode(settings: ReturnType<typeof loadWorkflowSettings>): "off" | "manual" | "auto" | "required" {
|
|
@@ -162,17 +245,32 @@ function standardTaskLooksSubstantive(task: string | undefined): boolean {
|
|
|
162
245
|
return text.length >= 8 || text.split(/\s+/).filter(Boolean).length >= 2;
|
|
163
246
|
}
|
|
164
247
|
|
|
165
|
-
function
|
|
248
|
+
function stripTimeoutPrefix(command: string): string {
|
|
249
|
+
return command.replace(/^timeout\s+\d+[smhd]?\s+/, "").trim() || command;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
const DESTRUCTIVE_WORD_RE = /\b(?:install|add|update|upgrade|publish|deploy|push|checkout|switch|commit|merge|rebase|stash|tag|apply|am|restore|sed\s+-i|perl\s+-pi|chmod|chown|curl\s.*\|\s*(?:sh|bash)|wget\s.*\|\s*(?:sh|bash))\b/i;
|
|
253
|
+
|
|
254
|
+
const SAFE_READ_ONLY_COMMANDS_RE = /^(?:git\s+(?:status|log|diff|show|branch|rev-parse|ls-files|describe|remote|tag|shortlog|count-objects|blame|name-rev)\b|cat\b|head\b|tail\b|less\b|more\b|wc\b|file\b|stat\b|which\b|where\b|command\s+-v\b|type\b|echo\b|printf\b|printenv\b|env\b|uname\b|date\b|id\b|whoami\b|hostname\b|pwd\b|ls\b|du\b|df\b|diff\b|comm\b|sort\b|uniq\b|cut\b|tr\b|awk\b|jq\b|yq\b|xq\b|(?:npm|pnpm|yarn|bun)\s+(?:run\s+)?(?:build|test|lint|typecheck|type-check|check[\s:]?\w*|dev|start|preview|serve|watch|format|analyze|compile|ci|validate|verify|coverage|bench|benchmark|bundle|pack|dist|static|docs|doc|stylelint|e2e|integration|unit)\b|(?:npm|pnpm|yarn|bun)\s+(?:exec|info|ls|list|query|outdated|why|view|pack\s+--dry-run)\b|npx\s+(?:serve|http-server|lite-server|tsc|vite|eslint|prettier|vitest|jest|mocha|cypress|playwright|webpack|rollup|parcel|turbo|nx|ts-node|tsx|esbuild|swc|babel|stylelint|biome|rome|knip|typedoc|compodoc|angular-cli|react-scripts|next|nuxt|remix|astro|svelte-kit)\b|pnpm\s+(?:exec|dlx)\s+\w+\b|bun\s+(?:test|check|build|run)\b|deno\s+(?:check|test|build|lint|task|info|doc|compile|fmt|eval|cache)\b|cargo\s+(?:build|test|check|clippy|doc|bench|run|metadata|locate-project|tree|version)\b|(?:rustc|rustup)\s+(?:--version|--print|which)\b|go\s+(?:build|test|vet|run|doc|list|mod\s+(?:verify|tidy|graph|download|why))\b|python3?\s+(?:--version|-V|-c\b|-m\s+(?:pytest|unittest|mypy|pylint|flake8|black|isort|ruff|json\.tool|compileall|bandit|pyright|http\.server|html\.parser|html))\b|pip3?\s+(?:list|show|check|debug|index\s+versions)\b|tsc\b|node\s+(?:--version|-v|--check|-c|-e|--eval)\b|make\s+(?:build|test|check|lint|all|verify|docs|format|static|analyze)\b|cmake\s+(?:--build|--version)\b|(?:dotnet|msbuild)\s+(?:build|test|restore|check|format|lint|pack)\b|(?:gradle|\.\/gradlew|gradlew\.bat)\s+(?:build|test|check|compile|lint|dependencies|projects|tasks)\b|mvn\s+(?:compile|test|verify|checkstyle|pmd|versions:display|dependency:tree|dependency:list)\b|(?:swift|swiftc)\s+(?:build|test|package\s+(?:describe|dump-package))\b|(?:bundle|gem)\s+(?:exec|list|check|info|query)\b|rake\s+(?:test|spec|lint|check|notes|stats|about)\b|php\s+(?:--version|-v|-l)\b|(?:php\s+)?artisan\s+(?:--version|route:list|config:show|env)\b|composer\s+(?:validate|check|show|outdated|info|diagnose)\b|mix\s+(?:test|compile|lint|format|docs)\b|bazel\s+(?:build|test|query|cquery|info|version)\b|buck\s+(?:build|test|query|audit)\b|curl\s+(?:-[^\s]*[sSfIv][^\s]*\s+)+(?:https?:\/\/|localhost|\$)|kill\s+\$!\b|kill\s+-0\s+\$\w+\b|wait\s+\$!\b|wait\s+\$\w+\b|sleep\s+[0-9.]+[smhd]?\b|ps\s+(?:aux?|-[a-z]*[eE][a-z]*|-[a-z]*[pP][a-z]*)\b|pgrep\s+-\w+\s+\w+|true\b|false\b|\.\s*\/node_modules\/\.bin\/\S+\b)/i;
|
|
255
|
+
|
|
256
|
+
export function standardSafeReadOnlyBash(command: string): boolean {
|
|
166
257
|
const trimmed = command.trim();
|
|
167
258
|
if (!trimmed || isBlockedExecuteCommand(trimmed)) return false;
|
|
168
|
-
|
|
259
|
+
const cmd = stripTimeoutPrefix(trimmed);
|
|
260
|
+
return SAFE_READ_ONLY_COMMANDS_RE.test(cmd);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function stripSafePreamble(command: string): string {
|
|
264
|
+
return command.replace(/^(?:set\s+[-+][euxo]+(?:\s+[^\n]*)?|export\s+\w+=["']?[^\n"']*["']?|\w+=\S+)\s*\n+/gm, "").trim() || command;
|
|
169
265
|
}
|
|
170
266
|
|
|
171
267
|
function validatorSafeEvidenceBash(command: string): boolean {
|
|
172
268
|
const trimmed = command.trim();
|
|
173
|
-
if (!trimmed
|
|
174
|
-
|
|
175
|
-
|
|
269
|
+
if (!trimmed) return false;
|
|
270
|
+
const cmd = stripSafePreamble(stripTimeoutPrefix(trimmed));
|
|
271
|
+
if (isBlockedExecuteCommand(cmd)) return false;
|
|
272
|
+
if (DESTRUCTIVE_WORD_RE.test(cmd)) return false;
|
|
273
|
+
return true;
|
|
176
274
|
}
|
|
177
275
|
|
|
178
276
|
function standardTodoTitleLooksGeneric(title: string): boolean {
|
|
@@ -198,26 +296,42 @@ function standardRequiredTodoMissing(state: WorkflowState, settings: ReturnType<
|
|
|
198
296
|
&& standardTaskLooksSubstantive(task);
|
|
199
297
|
}
|
|
200
298
|
|
|
299
|
+
function planProgressRelevantWorkTool(tool: string, input: unknown): boolean {
|
|
300
|
+
if (tool === "edit" || tool === "write") return true;
|
|
301
|
+
if (tool !== "bash") return false;
|
|
302
|
+
const command = String((input as { command?: unknown } | undefined)?.command ?? "");
|
|
303
|
+
return Boolean(command.trim()) && !standardSafeReadOnlyBash(command);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
function currentPlanProgressStepNumber(state: WorkflowState): number | undefined {
|
|
307
|
+
const steps = state.planProgress?.steps ?? [];
|
|
308
|
+
if (!steps.length) return undefined;
|
|
309
|
+
if (steps.every((step) => step.status === "completed" || step.status === "skipped")) return undefined;
|
|
310
|
+
const activeIndex = steps.findIndex((step) => step.status === "active");
|
|
311
|
+
const fallbackIndex = Math.max(0, Math.min(steps.length - 1, Math.floor(state.planProgress?.currentStepIndex ?? 0)));
|
|
312
|
+
return (activeIndex >= 0 ? activeIndex : fallbackIndex) + 1;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
function planProgressToolRequiredBlock(state: WorkflowState, tool: string, input: unknown): string | undefined {
|
|
316
|
+
if (state.mode !== "executing" && state.mode !== "repairing") return undefined;
|
|
317
|
+
if (!planProgressRelevantWorkTool(tool, input)) return undefined;
|
|
318
|
+
const stepNumber = currentPlanProgressStepNumber(state);
|
|
319
|
+
if (!stepNumber) return undefined;
|
|
320
|
+
if (state.planProgressLastToolStatus === "active" && state.planProgressLastToolStep === stepNumber) return undefined;
|
|
321
|
+
return `Plan execution ${tool} is blocked until workflow_progress({ step: ${stepNumber}, status: "active" }) is called for the current approved Plan step.`;
|
|
322
|
+
}
|
|
323
|
+
|
|
201
324
|
export function registerToolGuard(pi: ExtensionAPI, getState: () => WorkflowState): void {
|
|
202
325
|
pi.on("tool_call", async (event, ctx) => {
|
|
203
326
|
const state = getState();
|
|
204
327
|
const tool = event.toolName;
|
|
205
328
|
const settings = loadWorkflowSettings(ctx.cwd);
|
|
206
329
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
// Destructive bash remains blocked when global safety requires it.
|
|
210
|
-
if (isSubagentWorker()) {
|
|
211
|
-
if (tool === "bash") {
|
|
212
|
-
const command = String((event.input as { command?: unknown }).command ?? "");
|
|
213
|
-
if (commandBlocked(command, ctx.cwd)) return { block: true, reason: `Workflow safety blocked destructive sub-agent bash command: ${command}` };
|
|
214
|
-
}
|
|
215
|
-
return;
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
if (repoLockEnabled(settings)) {
|
|
330
|
+
const effectiveRepoLockEnabled = repoLockEnabled(settings) || process.env.PI_WORKFLOW_REPO_LOCK_ENABLED === "1";
|
|
331
|
+
if (effectiveRepoLockEnabled) {
|
|
219
332
|
if (PATH_SCOPED_TOOLS.has(tool)) {
|
|
220
|
-
const
|
|
333
|
+
const input = event.input as { path?: unknown; file_path?: unknown };
|
|
334
|
+
const reason = repoLockPathBlock(input.path ?? input.file_path, ctx.cwd, tool);
|
|
221
335
|
if (reason) return { block: true, reason };
|
|
222
336
|
}
|
|
223
337
|
if (tool === "bash") {
|
|
@@ -226,20 +340,31 @@ export function registerToolGuard(pi: ExtensionAPI, getState: () => WorkflowStat
|
|
|
226
340
|
if (reason) return { block: true, reason };
|
|
227
341
|
}
|
|
228
342
|
if (tool === "subagent") {
|
|
229
|
-
const reason = repoLockPathBlock(".", ctx.cwd);
|
|
343
|
+
const reason = repoLockPathBlock(".", ctx.cwd, tool);
|
|
230
344
|
if (reason) return { block: true, reason };
|
|
231
345
|
}
|
|
232
346
|
}
|
|
233
347
|
|
|
348
|
+
if (isSubagentWorker()) {
|
|
349
|
+
if (tool === "bash") {
|
|
350
|
+
const command = String((event.input as { command?: unknown }).command ?? "");
|
|
351
|
+
if (commandBlocked(command, ctx.cwd)) return { block: true, reason: `Workflow safety blocked destructive sub-agent bash command: ${command}` };
|
|
352
|
+
}
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
|
|
234
356
|
if (tool === STANDARD_HANDOFF_RESULT_TOOL && state.mode !== "standard") return { block: true, reason: "Standard handoff result is only available while Standard Mode is active." };
|
|
235
357
|
|
|
236
|
-
if (
|
|
358
|
+
if (tool === WORKFLOW_PLAN_RESULT_TOOL && state.mode !== "planning" && state.mode !== "executing" && state.mode !== "repairing") return { block: true, reason: `${tool} is only available during its planning phase.` };
|
|
359
|
+
if (tool === MISSION_PLAN_RESULT_TOOL && state.mode !== "mission_planning") return { block: true, reason: `${tool} is only available during its planning phase.` };
|
|
237
360
|
if (tool === WORKFLOW_REVIEW_RESULT_TOOL && state.mode !== "reviewing" && state.mode !== "mission_plan_ready") return { block: true, reason: "workflow_review_result is only available during review phases." };
|
|
238
361
|
if (tool === WORKFLOW_EXECUTION_RESULT_TOOL && state.mode !== "executing") return { block: true, reason: "workflow_execution_result is only available during Plan execution." };
|
|
239
362
|
if (tool === MISSION_MILESTONE_RESULT_TOOL && state.mode !== "mission_running") return { block: true, reason: "mission_milestone_result is only available during Mission execution." };
|
|
240
363
|
if (tool === WORKFLOW_VALIDATION_RESULT_TOOL && !isValidationResultMode(state.mode)) return { block: true, reason: "workflow_validation_result is only available during validation phases." };
|
|
241
364
|
if (tool === WORKFLOW_REPAIR_RESULT_TOOL && state.mode !== "repairing" && state.mode !== "mission_repairing") return { block: true, reason: "workflow_repair_result is only available during repair phases." };
|
|
242
365
|
|
|
366
|
+
if (tool === WORKFLOW_PROGRESS_TOOL && state.mode !== "executing" && state.mode !== "repairing") return { block: true, reason: "Plan step progress tracking is only available during Plan execution." };
|
|
367
|
+
|
|
243
368
|
if (tool === "standard_todo") {
|
|
244
369
|
if (state.mode !== "standard") return { block: true, reason: "Standard Mode To Do is only available while Standard Mode is active." };
|
|
245
370
|
if (state.standardClarificationPending || state.standardClarificationStage === "drafting" || state.standardClarificationStage === "awaiting_answer") return { block: true, reason: "Standard Mode To Do is blocked until the pending Standard clarification is answered." };
|
|
@@ -253,14 +378,18 @@ export function registerToolGuard(pi: ExtensionAPI, getState: () => WorkflowStat
|
|
|
253
378
|
}
|
|
254
379
|
}
|
|
255
380
|
|
|
381
|
+
const planProgressBlock = planProgressToolRequiredBlock(state, tool, event.input);
|
|
382
|
+
if (planProgressBlock) return { block: true, reason: planProgressBlock };
|
|
383
|
+
|
|
256
384
|
if (isPlanMode(state.mode)) {
|
|
385
|
+
if (state.mode === "plan_approved" && state.approvedPlan) return;
|
|
257
386
|
if (tool === "edit" || tool === "write") return { block: true, reason: `Workflow Plan Mode blocks ${tool}. Allowed tools: ${PLAN_TOOLS.join(", ")}${settings.safety.disableBashInPlanMode === false ? ", bash (safe commands)" : ""}` };
|
|
258
387
|
if (tool === "bash" && settings.safety.disableBashInPlanMode !== false) return { block: true, reason: `Workflow Plan Mode blocks bash. Allowed tools: ${PLAN_TOOLS.join(", ")}` };
|
|
259
388
|
}
|
|
260
389
|
|
|
261
390
|
if (isValidatorMode(state.mode)) {
|
|
262
|
-
if (tool === "edit"
|
|
263
|
-
if (tool === "bash") {
|
|
391
|
+
if (tool === "edit") return { block: true, reason: `Workflow Review/Validator Mode blocks ${tool}. Allowed tools: ${VALIDATOR_TOOLS.join(", ")}` };
|
|
392
|
+
if (tool === "bash" && settings.safety.disableBashInValidatorMode !== false) {
|
|
264
393
|
const command = String((event.input as { command?: unknown }).command ?? "");
|
|
265
394
|
if (!validatorSafeEvidenceBash(command)) return { block: true, reason: `Workflow Review/Validator Mode blocks unsafe bash. Allowed bash is limited to safe read-only evidence commands.` };
|
|
266
395
|
}
|
|
@@ -281,7 +410,7 @@ export function registerToolGuard(pi: ExtensionAPI, getState: () => WorkflowStat
|
|
|
281
410
|
return;
|
|
282
411
|
}
|
|
283
412
|
|
|
284
|
-
if (repoLockEnabled(settings)) {
|
|
413
|
+
if (repoLockEnabled(settings) || process.env.PI_WORKFLOW_REPO_LOCK_ENABLED === "1") {
|
|
285
414
|
const reason = repoLockBashBlock(event.command, ctx.cwd);
|
|
286
415
|
if (reason) return { result: { output: reason, exitCode: 1, cancelled: false, truncated: false } };
|
|
287
416
|
}
|
|
@@ -40,10 +40,12 @@ export function validationReportHasRepairableIssue(text?: string): boolean {
|
|
|
40
40
|
if (!normalized.trim()) return false;
|
|
41
41
|
const actionable = normalized
|
|
42
42
|
.replace(/\bno (actual |concrete )?(code |repairable )?(failure|failures|issue|issues|defect|defects)\b/g, " ")
|
|
43
|
+
.replace(/\bno (blocking|remaining|required) (issue|issues|action|actions|fix|fixes|gap|gaps)\b/g, " ")
|
|
44
|
+
.replace(/\brequired action (?:is )?(?:manual|visual|browser) (?:verification|qa|inspection|confirmation)\b/g, " ")
|
|
43
45
|
.replace(/\bno automated repair is needed\b/g, " ")
|
|
44
46
|
.replace(/\bno specific missing requirements? (?:is |are )?identified\b/g, " ")
|
|
45
47
|
.replace(/\bmanual[-\s]only\b/g, " ");
|
|
46
|
-
return /\b(needs? repair|needs? revision|repair pass|repairable (issue|failure|defect)|concrete (issue|failure|defect|regression)|critical issues?|must fix|required fixes|fixes required|missing requirements?|not fully meet|does not fully meet|not (a )?full final artifact|acceptable as (a )?checkpoint baseline but not (a )?(full )?final artifact|unexpected changes?|regression introduced|build (failed|error)|type error|tests? failed|new lint error|incomplete (file|artifact|implementation|coverage)|persistent artifact|structured artifact|risk register artifact|artifact required|(?:produce|create|add|write) (a )?(structured |persistent )?(risk register )?artifact|missing (file|config|import|export|declaration|function|module|dependency))\b/.test(actionable);
|
|
48
|
+
return /\b(needs? repair|needs? revision|repair pass|repairable (issue|failure|defect)|concrete (issue|failure|defect|regression)|blocking issues?|critical issues?|must fix|required (fixes?|actions?)|fixes required|remaining (fixes?|issues?|gaps?)|should be fixed before advancing|apply (the )?(two |[0-9]+ )?remaining fixes?|needs? to be (replaced|updated|expanded|corrected)|missing requirements?|not fully meet|does not fully meet|not (a )?full final artifact|acceptable as (a )?checkpoint baseline but not (a )?(full )?final artifact|unexpected changes?|regression introduced|build (failed|error)|type error|tests? failed|new lint error|incomplete (file|artifact|implementation|coverage)|persistent artifact|structured artifact|risk register artifact|artifact required|(?:produce|create|add|write) (a )?(structured |persistent )?(risk register )?artifact|missing (file|config|import|export|declaration|function|module|dependency))\b/.test(actionable);
|
|
47
49
|
}
|
|
48
50
|
|
|
49
51
|
export function validationReportIsEvidenceGap(text?: string): boolean {
|
|
@@ -94,7 +96,8 @@ export function normalizeValidationVerdict(verdict: WorkflowState["validationVer
|
|
|
94
96
|
// Re-export the verdict-to-status helper so consumers do not need workflow-parsers.
|
|
95
97
|
export function planValidationStatusForVerdict(verdict: WorkflowState["validationVerdict"]): PlanValidationStatus {
|
|
96
98
|
if (verdict === "PASS") return "pass";
|
|
97
|
-
if (verdict === "
|
|
99
|
+
if (verdict === "PARTIAL PASS") return "partial pass";
|
|
100
|
+
if (verdict === "UNKNOWN") return "unknown";
|
|
98
101
|
return "fail";
|
|
99
102
|
}
|
|
100
103
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mediadatafusion/pi-workflow-suite",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.11",
|
|
4
4
|
"description": "Structured workflow orchestration suite for Pi with Standard, Plan, Mission, compaction, diagrams, web access, repo lock, and safety gates.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -36,6 +36,7 @@
|
|
|
36
36
|
"type": "module",
|
|
37
37
|
"files": [
|
|
38
38
|
"extensions/",
|
|
39
|
+
"!extensions/*.bak",
|
|
39
40
|
"skills/",
|
|
40
41
|
"agents/",
|
|
41
42
|
"config/",
|
|
@@ -71,7 +72,8 @@
|
|
|
71
72
|
"./skills"
|
|
72
73
|
],
|
|
73
74
|
"prompts": [
|
|
74
|
-
"./config/prompts"
|
|
75
|
+
"./config/prompts",
|
|
76
|
+
"!*.md"
|
|
75
77
|
],
|
|
76
78
|
"themes": [
|
|
77
79
|
"./themes"
|
|
@@ -104,7 +106,7 @@
|
|
|
104
106
|
"scripts": {
|
|
105
107
|
"check:ts": "tsc --noEmit --noCheck",
|
|
106
108
|
"typecheck": "tsc --noEmit",
|
|
107
|
-
"validate": "npm run check:ts && ./scripts/
|
|
109
|
+
"validate": "npm run check:ts && ./scripts/test-workflow-forced-subagent-regression.sh && ./scripts/test-agent-skill-boundary-regression.sh && ./scripts/test-startup-visual-mode-entry-regression.sh && ./scripts/test-settings-health-regression.sh && ./scripts/test-handoff-visibility-regression.sh && ./scripts/test-mission-milestone-handoff-regression.sh && ./scripts/test-plan-handoff-chain-regression.sh && ./scripts/test-plan-step-progress-regression.sh && ./scripts/test-standard-mode-regression.sh && ./scripts/test-final-handoff-summary-regression.sh && ./scripts/test-clarification-answer-handoff-regression.sh && ./scripts/test-validation-evidence-contract-regression.sh && ./scripts/test-mermaid-guidance-regression.sh && ./scripts/test-runtime-web-tools-regression.sh && ./scripts/test-repolock-scope-regression.sh && ./scripts/test-repo-lock-version-regression.sh && ./scripts/test-package-menu-surface.sh && npm run check:package-size && git diff --check",
|
|
108
110
|
"check:package-size": "node scripts/check-package-size.mjs",
|
|
109
111
|
"prepack": "node scripts/prepare-package-readme.mjs apply",
|
|
110
112
|
"postpack": "node scripts/prepare-package-readme.mjs restore --pack",
|
|
@@ -14,7 +14,7 @@ printf 'A live backup will be created before installing files.\n'
|
|
|
14
14
|
is_forbidden_path() {
|
|
15
15
|
local rel="$1"
|
|
16
16
|
case "$rel" in
|
|
17
|
-
auth.json|settings.json|workflow-settings.json|active.json|workflows/*|missions/*|plans/*|sessions/*|logs/*|*.log|*.backup.*|*.broken.*|.env|.env.*|.factory/*|.cursor/*|*.DS_Store|*.tmp)
|
|
17
|
+
auth.json|settings.json|workflow-settings.json|active.json|workflows/*|missions/*|plans/*|sessions/*|logs/*|*.log|*.backup.*|*.broken.*|.env|.env.*|.factory/*|.cursor/*|.kilo/*|node_modules/*|*.DS_Store|*.tmp)
|
|
18
18
|
return 0
|
|
19
19
|
;;
|
|
20
20
|
esac
|
|
@@ -83,5 +83,6 @@ install_dir "extensions"
|
|
|
83
83
|
install_dir "agents"
|
|
84
84
|
install_dir "skills"
|
|
85
85
|
install_dir "config"
|
|
86
|
+
install_dir "themes"
|
|
86
87
|
|
|
87
88
|
printf 'install complete; auth, settings, and workflow state were not touched\n'
|