pi-crew 0.6.1 → 0.6.4

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 (119) hide show
  1. package/CHANGELOG.md +194 -0
  2. package/README.md +81 -33
  3. package/docs/issue-29-analysis.md +189 -0
  4. package/docs/superpowers/plans/2026-06-09-fallow-patterns-adoption.md +1268 -0
  5. package/docs/ui-optimization-plan.md +447 -0
  6. package/package.json +2 -2
  7. package/src/config/config.ts +106 -15
  8. package/src/errors.ts +107 -0
  9. package/src/extension/async-notifier.ts +6 -2
  10. package/src/extension/crew-cleanup.ts +8 -5
  11. package/src/extension/management.ts +464 -109
  12. package/src/extension/register.ts +213 -35
  13. package/src/extension/registration/brief-tool-overrides.ts +400 -0
  14. package/src/extension/registration/commands.ts +27 -2
  15. package/src/extension/registration/subagent-helpers.ts +2 -2
  16. package/src/extension/registration/subagent-tools.ts +9 -4
  17. package/src/extension/registration/team-tool.ts +17 -10
  18. package/src/extension/registration/viewers.ts +2 -2
  19. package/src/extension/team-tool/api.ts +3 -3
  20. package/src/extension/team-tool/cancel.ts +3 -3
  21. package/src/extension/team-tool/explain.ts +1 -1
  22. package/src/extension/team-tool/handle-schedule.ts +40 -0
  23. package/src/extension/team-tool/inspect.ts +3 -3
  24. package/src/extension/team-tool/lifecycle-actions.ts +4 -4
  25. package/src/extension/team-tool/respond.ts +2 -2
  26. package/src/extension/team-tool/run.ts +64 -14
  27. package/src/extension/team-tool/status.ts +1 -1
  28. package/src/extension/team-tool-types.ts +2 -0
  29. package/src/extension/team-tool.ts +173 -46
  30. package/src/hooks/registry.ts +77 -13
  31. package/src/hooks/types.ts +9 -0
  32. package/src/plugins/plugin-define.ts +6 -0
  33. package/src/plugins/plugin-registry.ts +32 -0
  34. package/src/plugins/plugins/index.ts +3 -0
  35. package/src/plugins/plugins/nextjs.ts +19 -0
  36. package/src/plugins/plugins/vite.ts +14 -0
  37. package/src/plugins/plugins/vitest.ts +14 -0
  38. package/src/runtime/async-runner.ts +35 -7
  39. package/src/runtime/background-runner.ts +529 -144
  40. package/src/runtime/chain-parser.ts +5 -1
  41. package/src/runtime/chain-runner.ts +9 -3
  42. package/src/runtime/checkpoint.ts +262 -180
  43. package/src/runtime/child-pi.ts +164 -37
  44. package/src/runtime/crash-recovery.ts +25 -14
  45. package/src/runtime/crew-agent-records.ts +2 -0
  46. package/src/runtime/diagnostic-export.ts +1 -1
  47. package/src/runtime/dynamic-script-runner.ts +10 -7
  48. package/src/runtime/foreground-watchdog.ts +1 -1
  49. package/src/runtime/heartbeat-watcher.ts +19 -2
  50. package/src/runtime/intercom-bridge.ts +7 -0
  51. package/src/runtime/iteration-hooks.ts +1 -1
  52. package/src/runtime/manifest-cache.ts +4 -0
  53. package/src/runtime/orphan-worker-registry.ts +444 -0
  54. package/src/runtime/parent-guard.ts +70 -16
  55. package/src/runtime/pi-args.ts +437 -14
  56. package/src/runtime/pi-spawn.ts +1 -0
  57. package/src/runtime/post-checks.ts +1 -1
  58. package/src/runtime/retry-runner.ts +9 -3
  59. package/src/runtime/run-tracker.ts +38 -10
  60. package/src/runtime/sandbox.ts +17 -2
  61. package/src/runtime/scheduler.ts +25 -2
  62. package/src/runtime/skill-effectiveness.ts +105 -62
  63. package/src/runtime/skill-instructions.ts +110 -32
  64. package/src/runtime/stale-reconciler.ts +310 -69
  65. package/src/runtime/subagent-manager.ts +251 -57
  66. package/src/runtime/task-health.ts +76 -0
  67. package/src/runtime/task-id.ts +20 -0
  68. package/src/runtime/task-runner/live-executor.ts +1 -1
  69. package/src/runtime/task-runner/progress.ts +1 -1
  70. package/src/runtime/task-runner/state-helpers.ts +110 -6
  71. package/src/runtime/task-runner.ts +92 -70
  72. package/src/runtime/team-runner.ts +186 -20
  73. package/src/schema/team-tool-schema.ts +27 -9
  74. package/src/skills/discover-skills.ts +9 -3
  75. package/src/state/active-run-registry.ts +170 -38
  76. package/src/state/artifact-store.ts +25 -13
  77. package/src/state/atomic-write-v2.ts +86 -0
  78. package/src/state/atomic-write.ts +346 -55
  79. package/src/state/blob-store.ts +178 -10
  80. package/src/state/crew-init.ts +161 -28
  81. package/src/state/decision-ledger.ts +172 -111
  82. package/src/state/event-log-rotation.ts +82 -52
  83. package/src/state/event-log.ts +254 -70
  84. package/src/state/health-store.ts +71 -0
  85. package/src/state/locks.ts +102 -20
  86. package/src/state/mailbox.ts +45 -7
  87. package/src/state/observation-store.ts +4 -1
  88. package/src/state/run-graph.ts +141 -130
  89. package/src/state/run-metrics.ts +24 -8
  90. package/src/state/session-state-map.ts +51 -0
  91. package/src/state/state-store.ts +330 -43
  92. package/src/ui/live-run-sidebar.ts +1 -1
  93. package/src/ui/loaders.ts +4 -0
  94. package/src/ui/overlays/agent-picker-overlay.ts +1 -1
  95. package/src/ui/overlays/mailbox-detail-overlay.ts +1 -1
  96. package/src/ui/powerbar-publisher.ts +1 -1
  97. package/src/ui/run-action-dispatcher.ts +2 -2
  98. package/src/ui/run-snapshot-cache.ts +1 -1
  99. package/src/ui/status-colors.ts +5 -1
  100. package/src/ui/theme-adapter.ts +80 -1
  101. package/src/ui/tool-progress-formatter.ts +9 -5
  102. package/src/ui/tool-render.ts +4 -0
  103. package/src/ui/tool-renderers/brief-mode.ts +207 -0
  104. package/src/ui/tool-renderers/index.ts +627 -0
  105. package/src/ui/widget/index.ts +224 -0
  106. package/src/ui/widget/widget-formatters.ts +148 -0
  107. package/src/ui/widget/widget-model.ts +90 -0
  108. package/src/ui/widget/widget-renderer.ts +130 -0
  109. package/src/ui/widget/widget-types.ts +37 -0
  110. package/src/utils/env-filter.ts +66 -0
  111. package/src/utils/file-coalescer.ts +9 -1
  112. package/src/utils/guards.ts +110 -0
  113. package/src/utils/paths.ts +80 -5
  114. package/src/utils/redaction.ts +6 -1
  115. package/src/utils/safe-paths.ts +281 -10
  116. package/src/worktree/cleanup.ts +112 -24
  117. package/src/worktree/worktree-manager.ts +128 -15
  118. package/test-bugs-all.mjs +10 -6
  119. package/test-integration-check.ts +4 -0
@@ -1,22 +1,37 @@
1
1
  import * as crypto from "node:crypto";
2
2
  import * as fs from "node:fs";
3
3
  import * as path from "node:path";
4
- import type { AgentConfig, ResourceSource, RoutingMetadata } from "../agents/agent-config.ts";
4
+ import type {
5
+ AgentConfig,
6
+ ResourceSource,
7
+ RoutingMetadata,
8
+ } from "../agents/agent-config.ts";
5
9
  import { serializeAgent } from "../agents/agent-serializer.ts";
6
10
  import { discoverAgents } from "../agents/discover-agents.ts";
7
- import type { TeamToolDetails } from "./team-tool-types.ts";
8
- import { toolResult, type PiTeamsToolResult } from "./tool-result.ts";
9
- import type { TeamToolParamsValue } from "../schema/team-tool-schema.ts";
10
11
  import type { PiTeamsConfig } from "../config/config.ts";
11
- import { enforceDestructiveIntent } from "./team-tool/intent-policy.ts";
12
+ import type { TeamToolParamsValue } from "../schema/team-tool-schema.ts";
13
+ import { allTeams, discoverTeams } from "../teams/discover-teams.ts";
12
14
  import type { TeamConfig, TeamRole } from "../teams/team-config.ts";
13
15
  import { serializeTeam } from "../teams/team-serializer.ts";
14
- import { allTeams, discoverTeams } from "../teams/discover-teams.ts";
15
- import type { WorkflowConfig, WorkflowStep } from "../workflows/workflow-config.ts";
16
- import { serializeWorkflow } from "../workflows/workflow-serializer.ts";
17
- import { allWorkflows, discoverWorkflows } from "../workflows/discover-workflows.ts";
16
+ import {
17
+ hasOwn,
18
+ parseConfigObject,
19
+ requireString,
20
+ sanitizeName,
21
+ } from "../utils/names.ts";
18
22
  import { projectCrewRoot, userPiRoot } from "../utils/paths.ts";
19
- import { hasOwn, parseConfigObject, requireString, sanitizeName } from "../utils/names.ts";
23
+ import {
24
+ allWorkflows,
25
+ discoverWorkflows,
26
+ } from "../workflows/discover-workflows.ts";
27
+ import type {
28
+ WorkflowConfig,
29
+ WorkflowStep,
30
+ } from "../workflows/workflow-config.ts";
31
+ import { serializeWorkflow } from "../workflows/workflow-serializer.ts";
32
+ import { enforceDestructiveIntent } from "./team-tool/intent-policy.ts";
33
+ import type { TeamToolDetails } from "./team-tool-types.ts";
34
+ import { type PiTeamsToolResult, toolResult } from "./tool-result.ts";
20
35
 
21
36
  interface ManagementContext {
22
37
  cwd: string;
@@ -27,11 +42,19 @@ type MutableSource = "user" | "project";
27
42
 
28
43
  type MutableResource = AgentConfig | TeamConfig | WorkflowConfig;
29
44
 
30
- function result(text: string, status: TeamToolDetails["status"] = "ok", isError = false): PiTeamsToolResult {
45
+ function result(
46
+ text: string,
47
+ status: TeamToolDetails["status"] = "ok",
48
+ isError = false,
49
+ ): PiTeamsToolResult {
31
50
  return toolResult(text, { action: "management", status }, isError);
32
51
  }
33
52
 
34
- function scopeDir(ctx: ManagementContext, resource: "agent" | "team" | "workflow", scope: MutableSource): string {
53
+ function scopeDir(
54
+ ctx: ManagementContext,
55
+ resource: "agent" | "team" | "workflow",
56
+ scope: MutableSource,
57
+ ): string {
35
58
  const base = scope === "user" ? userPiRoot() : projectCrewRoot(ctx.cwd);
36
59
  if (resource === "agent") return path.join(base, "agents");
37
60
  if (resource === "team") return path.join(base, "teams");
@@ -54,33 +77,77 @@ function backupFile(filePath: string): string {
54
77
  return backupPath;
55
78
  }
56
79
 
57
- function targetPath(ctx: ManagementContext, resource: "agent" | "team" | "workflow", scope: MutableSource, name: string): string {
58
- return path.join(scopeDir(ctx, resource, scope), `${name}${extensionFor(resource)}`);
80
+ function targetPath(
81
+ ctx: ManagementContext,
82
+ resource: "agent" | "team" | "workflow",
83
+ scope: MutableSource,
84
+ name: string,
85
+ ): string {
86
+ return path.join(
87
+ scopeDir(ctx, resource, scope),
88
+ `${name}${extensionFor(resource)}`,
89
+ );
59
90
  }
60
91
 
61
92
  function parseStringArray(value: unknown): string[] | undefined {
62
- if (typeof value === "string") return value.split(",").map((entry) => entry.trim()).filter(Boolean);
63
- if (Array.isArray(value)) return value.filter((entry): entry is string => typeof entry === "string" && entry.trim().length > 0).map((entry) => entry.trim());
93
+ if (typeof value === "string")
94
+ return value
95
+ .split(",")
96
+ .map((entry) => entry.trim())
97
+ .filter(Boolean);
98
+ if (Array.isArray(value))
99
+ return value
100
+ .filter(
101
+ (entry): entry is string =>
102
+ typeof entry === "string" && entry.trim().length > 0,
103
+ )
104
+ .map((entry) => entry.trim());
64
105
  return undefined;
65
106
  }
66
107
 
67
- function parseRouting(value: Record<string, unknown>, fallback?: RoutingMetadata): RoutingMetadata | undefined {
108
+ function parseRouting(
109
+ value: Record<string, unknown>,
110
+ fallback?: RoutingMetadata,
111
+ ): RoutingMetadata | undefined {
68
112
  const routing = {
69
- triggers: hasOwn(value, "triggers") ? parseStringArray(value.triggers) : fallback?.triggers,
70
- useWhen: hasOwn(value, "useWhen") ? parseStringArray(value.useWhen) : fallback?.useWhen,
71
- avoidWhen: hasOwn(value, "avoidWhen") ? parseStringArray(value.avoidWhen) : fallback?.avoidWhen,
72
- cost: value.cost === "free" || value.cost === "cheap" || value.cost === "expensive" ? value.cost : fallback?.cost,
73
- category: hasOwn(value, "category") ? (typeof value.category === "string" && value.category.trim() ? value.category.trim() : undefined) : fallback?.category,
113
+ triggers: hasOwn(value, "triggers")
114
+ ? parseStringArray(value.triggers)
115
+ : fallback?.triggers,
116
+ useWhen: hasOwn(value, "useWhen")
117
+ ? parseStringArray(value.useWhen)
118
+ : fallback?.useWhen,
119
+ avoidWhen: hasOwn(value, "avoidWhen")
120
+ ? parseStringArray(value.avoidWhen)
121
+ : fallback?.avoidWhen,
122
+ cost:
123
+ value.cost === "free" ||
124
+ value.cost === "cheap" ||
125
+ value.cost === "expensive"
126
+ ? value.cost
127
+ : fallback?.cost,
128
+ category: hasOwn(value, "category")
129
+ ? typeof value.category === "string" && value.category.trim()
130
+ ? value.category.trim()
131
+ : undefined
132
+ : fallback?.category,
74
133
  };
75
- return routing.triggers || routing.useWhen || routing.avoidWhen || routing.cost || routing.category ? routing : undefined;
134
+ return routing.triggers ||
135
+ routing.useWhen ||
136
+ routing.avoidWhen ||
137
+ routing.cost ||
138
+ routing.category
139
+ ? routing
140
+ : undefined;
76
141
  }
77
142
 
78
143
  function parseRoles(value: unknown): { roles?: TeamRole[]; error?: string } {
79
- if (!Array.isArray(value) || value.length === 0) return { error: "config.roles must be a non-empty array." };
144
+ if (!Array.isArray(value) || value.length === 0)
145
+ return { error: "config.roles must be a non-empty array." };
80
146
  const roles: TeamRole[] = [];
81
147
  for (let i = 0; i < value.length; i++) {
82
148
  const item = value[i];
83
- if (!item || typeof item !== "object" || Array.isArray(item)) return { error: `config.roles[${i}] must be an object.` };
149
+ if (!item || typeof item !== "object" || Array.isArray(item))
150
+ return { error: `config.roles[${i}] must be an object.` };
84
151
  const obj = item as Record<string, unknown>;
85
152
  const name = requireString(obj.name, `config.roles[${i}].name`);
86
153
  if (name.error) return { error: name.error };
@@ -89,20 +156,33 @@ function parseRoles(value: unknown): { roles?: TeamRole[]; error?: string } {
89
156
  roles.push({
90
157
  name: sanitizeName(name.value!),
91
158
  agent: sanitizeName(agent.value!),
92
- description: typeof obj.description === "string" ? obj.description.trim() : undefined,
159
+ description:
160
+ typeof obj.description === "string"
161
+ ? obj.description.trim()
162
+ : undefined,
93
163
  model: typeof obj.model === "string" ? obj.model.trim() : undefined,
94
- maxConcurrency: typeof obj.maxConcurrency === "number" && Number.isInteger(obj.maxConcurrency) && obj.maxConcurrency > 0 ? obj.maxConcurrency : undefined,
164
+ maxConcurrency:
165
+ typeof obj.maxConcurrency === "number" &&
166
+ Number.isInteger(obj.maxConcurrency) &&
167
+ obj.maxConcurrency > 0
168
+ ? obj.maxConcurrency
169
+ : undefined,
95
170
  });
96
171
  }
97
172
  return { roles };
98
173
  }
99
174
 
100
- function parseSteps(value: unknown): { steps?: WorkflowStep[]; error?: string } {
101
- if (!Array.isArray(value) || value.length === 0) return { error: "config.steps must be a non-empty array." };
175
+ function parseSteps(value: unknown): {
176
+ steps?: WorkflowStep[];
177
+ error?: string;
178
+ } {
179
+ if (!Array.isArray(value) || value.length === 0)
180
+ return { error: "config.steps must be a non-empty array." };
102
181
  const steps: WorkflowStep[] = [];
103
182
  for (let i = 0; i < value.length; i++) {
104
183
  const item = value[i];
105
- if (!item || typeof item !== "object" || Array.isArray(item)) return { error: `config.steps[${i}] must be an object.` };
184
+ if (!item || typeof item !== "object" || Array.isArray(item))
185
+ return { error: `config.steps[${i}] must be an object.` };
106
186
  const obj = item as Record<string, unknown>;
107
187
  const id = requireString(obj.id, `config.steps[${i}].id`);
108
188
  if (id.error) return { error: id.error };
@@ -112,21 +192,36 @@ function parseSteps(value: unknown): { steps?: WorkflowStep[]; error?: string }
112
192
  // Strip characters that could create headings, code blocks, or links.
113
193
  // Defense-in-depth — the task field is stored in YAML frontmatter, not executed.
114
194
  const rawTask = obj.task;
115
- const task = typeof rawTask === "string"
116
- ? rawTask.replace(/^#{1,6}\s+/gm, "").replace(/```/g, "\`\`").replace(/\n{3,}/g, "\n\n").slice(0, 4000)
117
- : "{goal}";
195
+ const task =
196
+ typeof rawTask === "string"
197
+ ? rawTask
198
+ .replace(/^#{1,6}\s+/gm, "")
199
+ .replace(/```/g, "``")
200
+ .replace(/\n{3,}/g, "\n\n")
201
+ .slice(0, 4000)
202
+ : "{goal}";
118
203
  steps.push({
119
204
  id: sanitizeName(id.value!),
120
205
  role: sanitizeName(role.value!),
121
206
  task,
122
207
  dependsOn: parseStringArray(obj.dependsOn),
123
- parallelGroup: typeof obj.parallelGroup === "string" ? obj.parallelGroup.trim() : undefined,
124
- output: obj.output === false ? false : typeof obj.output === "string" ? obj.output.trim() : undefined,
208
+ parallelGroup:
209
+ typeof obj.parallelGroup === "string"
210
+ ? obj.parallelGroup.trim()
211
+ : undefined,
212
+ output:
213
+ obj.output === false
214
+ ? false
215
+ : typeof obj.output === "string"
216
+ ? obj.output.trim()
217
+ : undefined,
125
218
  reads: obj.reads === false ? false : parseStringArray(obj.reads),
126
219
  model: typeof obj.model === "string" ? obj.model.trim() : undefined,
127
220
  skills: obj.skills === false ? false : parseStringArray(obj.skills),
128
- progress: typeof obj.progress === "boolean" ? obj.progress : undefined,
129
- worktree: typeof obj.worktree === "boolean" ? obj.worktree : undefined,
221
+ progress:
222
+ typeof obj.progress === "boolean" ? obj.progress : undefined,
223
+ worktree:
224
+ typeof obj.worktree === "boolean" ? obj.worktree : undefined,
130
225
  verify: typeof obj.verify === "boolean" ? obj.verify : undefined,
131
226
  });
132
227
  }
@@ -134,45 +229,75 @@ function parseSteps(value: unknown): { steps?: WorkflowStep[]; error?: string }
134
229
  }
135
230
 
136
231
  function parseWorkflowMaxConcurrency(value: unknown): number | undefined {
137
- if (typeof value !== "number" || !Number.isInteger(value) || value < 1) return undefined;
232
+ if (typeof value !== "number" || !Number.isInteger(value) || value < 1)
233
+ return undefined;
138
234
  return value;
139
235
  }
140
236
 
141
- function findResource(ctx: ManagementContext, resource: "agent" | "team" | "workflow", name: string, scope?: string): MutableResource[] {
237
+ function findResource(
238
+ ctx: ManagementContext,
239
+ resource: "agent" | "team" | "workflow",
240
+ name: string,
241
+ scope?: string,
242
+ ): MutableResource[] {
142
243
  const normalized = sanitizeName(name);
143
- const sourceMatches = (item: { name: string; source: ResourceSource }) => (scope === "user" || scope === "project" ? item.source === scope : item.source !== "builtin") && item.name === normalized;
244
+ const sourceMatches = (item: { name: string; source: ResourceSource }) =>
245
+ (scope === "user" || scope === "project"
246
+ ? item.source === scope
247
+ : item.source !== "builtin") && item.name === normalized;
144
248
  // Search in the correct scope array directly to avoid allAgents shadowing issue.
145
249
  if (resource === "agent") {
146
250
  const discovery = discoverAgents(ctx.cwd);
147
- const pool = scope === "user" ? discovery.user : scope === "project" ? discovery.project : [...discovery.builtin, ...discovery.user];
251
+ const pool =
252
+ scope === "user"
253
+ ? discovery.user
254
+ : scope === "project"
255
+ ? discovery.project
256
+ : [...discovery.builtin, ...discovery.user];
148
257
  return pool.filter(sourceMatches);
149
258
  }
150
259
  if (resource === "team") {
151
260
  const discovery = discoverTeams(ctx.cwd);
152
- const pool = scope === "user" ? discovery.user : scope === "project" ? discovery.project : [...discovery.builtin, ...discovery.user];
261
+ const pool =
262
+ scope === "user"
263
+ ? discovery.user
264
+ : scope === "project"
265
+ ? discovery.project
266
+ : [...discovery.builtin, ...discovery.user];
153
267
  return pool.filter(sourceMatches);
154
268
  }
155
269
  {
156
270
  const discovery = discoverWorkflows(ctx.cwd);
157
- const pool = scope === "user" ? discovery.user : scope === "project" ? discovery.project : [...discovery.builtin, ...discovery.user];
271
+ const pool =
272
+ scope === "user"
273
+ ? discovery.user
274
+ : scope === "project"
275
+ ? discovery.project
276
+ : [...discovery.builtin, ...discovery.user];
158
277
  return pool.filter(sourceMatches);
159
278
  }
160
279
  }
161
280
 
162
281
  // Note: only checks agent→team references and defaultWorkflow. Does not detect
163
282
  // workflow-step→agent/team references or team name in workflow metadata.
164
- function findReferences(ctx: ManagementContext, resource: "agent" | "team" | "workflow", name: string): string[] {
283
+ function findReferences(
284
+ ctx: ManagementContext,
285
+ resource: "agent" | "team" | "workflow",
286
+ name: string,
287
+ ): string[] {
165
288
  const refs: string[] = [];
166
289
  if (resource === "agent") {
167
290
  for (const team of allTeams(discoverTeams(ctx.cwd))) {
168
291
  for (const role of team.roles) {
169
- if (role.agent === name) refs.push(`team '${team.name}' role '${role.name}'`);
292
+ if (role.agent === name)
293
+ refs.push(`team '${team.name}' role '${role.name}'`);
170
294
  }
171
295
  }
172
296
  }
173
297
  if (resource === "workflow") {
174
298
  for (const team of allTeams(discoverTeams(ctx.cwd))) {
175
- if (team.defaultWorkflow === name) refs.push(`team '${team.name}' defaultWorkflow`);
299
+ if (team.defaultWorkflow === name)
300
+ refs.push(`team '${team.name}' defaultWorkflow`);
176
301
  }
177
302
  }
178
303
  return refs;
@@ -196,16 +321,29 @@ function walkTsFiles(dir: string): string[] {
196
321
  return results;
197
322
  }
198
323
 
199
- function updateReferencesForRename(ctx: ManagementContext, resource: "agent" | "team" | "workflow", oldName: string, newName: string, scope: MutableSource, dryRun: boolean): string[] {
324
+ function updateReferencesForRename(
325
+ ctx: ManagementContext,
326
+ resource: "agent" | "team" | "workflow",
327
+ oldName: string,
328
+ newName: string,
329
+ scope: MutableSource,
330
+ dryRun: boolean,
331
+ ): string[] {
200
332
  if (oldName === newName) return [];
201
333
  if (resource !== "agent" && resource !== "workflow") return [];
202
334
  const changed: string[] = [];
203
- for (const team of allTeams(discoverTeams(ctx.cwd)).filter((candidate) => candidate.source === scope)) {
335
+ for (const team of allTeams(discoverTeams(ctx.cwd)).filter(
336
+ (candidate) => candidate.source === scope,
337
+ )) {
204
338
  let updated = false;
205
339
  let nextTeam = team;
206
340
  if (resource === "agent") {
207
- const roles = team.roles.map((role) => role.agent === oldName ? { ...role, agent: newName } : role);
208
- updated = roles.some((role, index) => role.agent !== team.roles[index]!.agent);
341
+ const roles = team.roles.map((role) =>
342
+ role.agent === oldName ? { ...role, agent: newName } : role,
343
+ );
344
+ updated = roles.some(
345
+ (role, index) => role.agent !== team.roles[index]!.agent,
346
+ );
209
347
  nextTeam = { ...team, roles };
210
348
  }
211
349
  if (resource === "workflow" && team.defaultWorkflow === oldName) {
@@ -221,7 +359,9 @@ function updateReferencesForRename(ctx: ManagementContext, resource: "agent" | "
221
359
  }
222
360
  // L12 fix: also update workflow step role references when renaming agents.
223
361
  // Workflow files use `role:` to reference agent roles, not agent names.
224
- for (const workflow of allWorkflows(discoverWorkflows(ctx.cwd)).filter((w) => w.source === scope)) {
362
+ for (const workflow of allWorkflows(discoverWorkflows(ctx.cwd)).filter(
363
+ (w) => w.source === scope,
364
+ )) {
225
365
  let updated = false;
226
366
  const newSteps = workflow.steps.map((step) => {
227
367
  if (step.role === oldName) {
@@ -234,16 +374,28 @@ function updateReferencesForRename(ctx: ManagementContext, resource: "agent" | "
234
374
  changed.push(workflow.filePath);
235
375
  if (!dryRun) {
236
376
  backupFile(workflow.filePath);
237
- fs.writeFileSync(workflow.filePath, serializeWorkflow({ ...workflow, steps: newSteps }), "utf-8");
377
+ fs.writeFileSync(
378
+ workflow.filePath,
379
+ serializeWorkflow({ ...workflow, steps: newSteps }),
380
+ "utf-8",
381
+ );
238
382
  }
239
383
  }
240
384
  // L12 fix: update agent references in test fixtures.
241
- const testDir = scope === "user" ? path.join(ctx.cwd, ".crew", "test") : path.join(ctx.cwd, "test", "fixtures");
385
+ const testDir =
386
+ scope === "user"
387
+ ? path.join(projectCrewRoot(ctx.cwd), "test")
388
+ : path.join(ctx.cwd, "test", "fixtures");
242
389
  if (fs.existsSync(testDir)) {
243
390
  for (const fixture of walkTsFiles(testDir)) {
244
391
  const content = fs.readFileSync(fixture, "utf-8");
245
392
  if (!content.includes(oldName)) continue;
246
- const agentPattern = new RegExp('(["\'\\`]agent[="\':\\s]*)' + escapeRegex(oldName) + '(["\'\\`]|\\s)', 'g');
393
+ const agentPattern = new RegExp(
394
+ "([\"'\\`]agent[=\"':\\s]*)" +
395
+ escapeRegex(oldName) +
396
+ "([\"'\\`]|\\s)",
397
+ "g",
398
+ );
247
399
  const newContent = content.replace(agentPattern, `$1${newName}$2`);
248
400
  if (newContent !== content) {
249
401
  changed.push(fixture);
@@ -256,30 +408,80 @@ function updateReferencesForRename(ctx: ManagementContext, resource: "agent" | "
256
408
  return changed;
257
409
  }
258
410
 
259
- function resolveMutable(ctx: ManagementContext, params: TeamToolParamsValue): { resource?: MutableResource; error?: PiTeamsToolResult } {
260
- if (!params.resource) return { error: result("resource is required for update/delete.", "error", true) };
261
- const name = params.resource === "agent" ? params.agent : params.resource === "team" ? params.team : params.workflow;
262
- if (!name) return { error: result(`${params.resource} name is required.`, "error", true) };
411
+ function resolveMutable(
412
+ ctx: ManagementContext,
413
+ params: TeamToolParamsValue,
414
+ ): { resource?: MutableResource; error?: PiTeamsToolResult } {
415
+ if (!params.resource)
416
+ return {
417
+ error: result(
418
+ "resource is required for update/delete.",
419
+ "error",
420
+ true,
421
+ ),
422
+ };
423
+ const name =
424
+ params.resource === "agent"
425
+ ? params.agent
426
+ : params.resource === "team"
427
+ ? params.team
428
+ : params.workflow;
429
+ if (!name)
430
+ return {
431
+ error: result(
432
+ `${params.resource} name is required.`,
433
+ "error",
434
+ true,
435
+ ),
436
+ };
263
437
  const matches = findResource(ctx, params.resource, name, params.scope);
264
- if (matches.length === 0) return { error: result(`${params.resource} '${name}' not found in mutable user/project scopes.`, "error", true) };
265
- if (matches.length > 1) return { error: result(`${params.resource} '${name}' exists in multiple scopes. Specify scope: 'user' or 'project'.`, "error", true) };
438
+ if (matches.length === 0)
439
+ return {
440
+ error: result(
441
+ `${params.resource} '${name}' not found in mutable user/project scopes.`,
442
+ "error",
443
+ true,
444
+ ),
445
+ };
446
+ if (matches.length > 1)
447
+ return {
448
+ error: result(
449
+ `${params.resource} '${name}' exists in multiple scopes. Specify scope: 'user' or 'project'.`,
450
+ "error",
451
+ true,
452
+ ),
453
+ };
266
454
  return { resource: matches[0] };
267
455
  }
268
456
 
269
- export function handleCreate(params: TeamToolParamsValue, ctx: ManagementContext): PiTeamsToolResult {
270
- if (!params.resource) return result("resource is required for create.", "error", true);
457
+ export function handleCreate(
458
+ params: TeamToolParamsValue,
459
+ ctx: ManagementContext,
460
+ ): PiTeamsToolResult {
461
+ if (!params.resource)
462
+ return result("resource is required for create.", "error", true);
271
463
  const parsed = parseConfigObject(params.config);
272
464
  if (parsed.error) return result(parsed.error, "error", true);
273
465
  const cfg = parsed.value!;
274
466
  const nameValue = requireString(cfg.name, "config.name");
275
467
  if (nameValue.error) return result(nameValue.error, "error", true);
276
- const descriptionValue = requireString(cfg.description, "config.description");
277
- if (descriptionValue.error) return result(descriptionValue.error, "error", true);
468
+ const descriptionValue = requireString(
469
+ cfg.description,
470
+ "config.description",
471
+ );
472
+ if (descriptionValue.error)
473
+ return result(descriptionValue.error, "error", true);
278
474
  const name = sanitizeName(nameValue.value!);
279
- if (!name) return result("config.name is invalid after sanitization.", "error", true);
475
+ if (!name)
476
+ return result(
477
+ "config.name is invalid after sanitization.",
478
+ "error",
479
+ true,
480
+ );
280
481
  const scope = cfg.scope === "project" ? "project" : "user";
281
482
  const filePath = targetPath(ctx, params.resource, scope, name);
282
- if (fs.existsSync(filePath)) return result(`File already exists: ${filePath}`, "error", true);
483
+ if (fs.existsSync(filePath))
484
+ return result(`File already exists: ${filePath}`, "error", true);
283
485
  fs.mkdirSync(path.dirname(filePath), { recursive: true });
284
486
 
285
487
  let content: string;
@@ -289,14 +491,19 @@ export function handleCreate(params: TeamToolParamsValue, ctx: ManagementContext
289
491
  description: descriptionValue.value!,
290
492
  source: scope,
291
493
  filePath,
292
- systemPrompt: typeof cfg.systemPrompt === "string" ? cfg.systemPrompt : "",
494
+ systemPrompt:
495
+ typeof cfg.systemPrompt === "string" ? cfg.systemPrompt : "",
293
496
  model: typeof cfg.model === "string" ? cfg.model : undefined,
294
497
  fallbackModels: parseStringArray(cfg.fallbackModels),
295
- thinking: typeof cfg.thinking === "string" ? cfg.thinking : undefined,
498
+ thinking:
499
+ typeof cfg.thinking === "string" ? cfg.thinking : undefined,
296
500
  tools: parseStringArray(cfg.tools),
297
- extensions: hasOwn(cfg, "extensions") ? parseStringArray(cfg.extensions) ?? [] : undefined,
501
+ extensions: hasOwn(cfg, "extensions")
502
+ ? (parseStringArray(cfg.extensions) ?? [])
503
+ : undefined,
298
504
  skills: parseStringArray(cfg.skills),
299
- systemPromptMode: cfg.systemPromptMode === "append" ? "append" : "replace",
505
+ systemPromptMode:
506
+ cfg.systemPromptMode === "append" ? "append" : "replace",
300
507
  inheritProjectContext: cfg.inheritProjectContext === true,
301
508
  inheritSkills: cfg.inheritSkills === true,
302
509
  routing: parseRouting(cfg),
@@ -311,9 +518,18 @@ export function handleCreate(params: TeamToolParamsValue, ctx: ManagementContext
311
518
  source: scope,
312
519
  filePath,
313
520
  roles: parsedRoles.roles!,
314
- defaultWorkflow: typeof cfg.defaultWorkflow === "string" ? sanitizeName(cfg.defaultWorkflow) : undefined,
315
- workspaceMode: cfg.workspaceMode === "worktree" ? "worktree" : "single",
316
- maxConcurrency: typeof cfg.maxConcurrency === "number" && Number.isInteger(cfg.maxConcurrency) && cfg.maxConcurrency > 0 ? cfg.maxConcurrency : undefined,
521
+ defaultWorkflow:
522
+ typeof cfg.defaultWorkflow === "string"
523
+ ? sanitizeName(cfg.defaultWorkflow)
524
+ : undefined,
525
+ workspaceMode:
526
+ cfg.workspaceMode === "worktree" ? "worktree" : "single",
527
+ maxConcurrency:
528
+ typeof cfg.maxConcurrency === "number" &&
529
+ Number.isInteger(cfg.maxConcurrency) &&
530
+ cfg.maxConcurrency > 0
531
+ ? cfg.maxConcurrency
532
+ : undefined,
317
533
  routing: parseRouting(cfg),
318
534
  });
319
535
  } else {
@@ -329,27 +545,45 @@ export function handleCreate(params: TeamToolParamsValue, ctx: ManagementContext
329
545
  });
330
546
  }
331
547
 
332
- if (params.dryRun) return result(`[dry-run] Would create ${params.resource} '${name}' at ${filePath}:\n\n${content}`);
548
+ if (params.dryRun)
549
+ return result(
550
+ `[dry-run] Would create ${params.resource} '${name}' at ${filePath}:\n\n${content}`,
551
+ );
333
552
  try {
334
553
  fs.writeFileSync(filePath, content, "utf-8");
335
554
  } catch (writeError) {
336
- return result(`Failed to create ${params.resource}: ${writeError instanceof Error ? writeError.message : String(writeError)}`, "error", true);
555
+ return result(
556
+ `Failed to create ${params.resource}: ${writeError instanceof Error ? writeError.message : String(writeError)}`,
557
+ "error",
558
+ true,
559
+ );
337
560
  }
338
561
  return result(`Created ${params.resource} '${name}' at ${filePath}.`);
339
562
  }
340
563
 
341
- export function handleUpdate(params: TeamToolParamsValue, ctx: ManagementContext): PiTeamsToolResult {
564
+ export function handleUpdate(
565
+ params: TeamToolParamsValue,
566
+ ctx: ManagementContext,
567
+ ): PiTeamsToolResult {
342
568
  const resolved = resolveMutable(ctx, params);
343
569
  if (resolved.error) return resolved.error;
344
570
  const parsed = parseConfigObject(params.config);
345
571
  if (parsed.error) return result(parsed.error, "error", true);
346
572
  const cfg = parsed.value!;
347
573
  const current = resolved.resource!;
348
- const nextName = hasOwn(cfg, "name") ? sanitizeName(String(cfg.name ?? "")) : current.name;
349
- if (!nextName) return result("config.name is invalid after sanitization.", "error", true);
574
+ const nextName = hasOwn(cfg, "name")
575
+ ? sanitizeName(String(cfg.name ?? ""))
576
+ : current.name;
577
+ if (!nextName)
578
+ return result(
579
+ "config.name is invalid after sanitization.",
580
+ "error",
581
+ true,
582
+ );
350
583
  const source = current.source === "project" ? "project" : "user";
351
584
  const nextPath = targetPath(ctx, params.resource!, source, nextName);
352
- if (nextPath !== current.filePath && fs.existsSync(nextPath)) return result(`Target file already exists: ${nextPath}`, "error", true);
585
+ if (nextPath !== current.filePath && fs.existsSync(nextPath))
586
+ return result(`Target file already exists: ${nextPath}`, "error", true);
353
587
 
354
588
  let content: string;
355
589
  if (params.resource === "agent") {
@@ -358,17 +592,50 @@ export function handleUpdate(params: TeamToolParamsValue, ctx: ManagementContext
358
592
  ...agent,
359
593
  name: nextName,
360
594
  filePath: nextPath,
361
- description: typeof cfg.description === "string" && cfg.description.trim() ? cfg.description.trim() : agent.description,
362
- systemPrompt: typeof cfg.systemPrompt === "string" ? cfg.systemPrompt : agent.systemPrompt,
363
- model: hasOwn(cfg, "model") ? (typeof cfg.model === "string" && cfg.model.trim() ? cfg.model.trim() : undefined) : agent.model,
364
- fallbackModels: hasOwn(cfg, "fallbackModels") ? parseStringArray(cfg.fallbackModels) : agent.fallbackModels,
365
- thinking: hasOwn(cfg, "thinking") ? (typeof cfg.thinking === "string" && cfg.thinking.trim() ? cfg.thinking.trim() : undefined) : agent.thinking,
366
- tools: hasOwn(cfg, "tools") ? parseStringArray(cfg.tools) : agent.tools,
367
- extensions: hasOwn(cfg, "extensions") ? parseStringArray(cfg.extensions) ?? [] : agent.extensions,
368
- skills: hasOwn(cfg, "skills") ? parseStringArray(cfg.skills) : agent.skills,
369
- systemPromptMode: cfg.systemPromptMode === "append" ? "append" : cfg.systemPromptMode === "replace" ? "replace" : agent.systemPromptMode,
370
- inheritProjectContext: typeof cfg.inheritProjectContext === "boolean" ? cfg.inheritProjectContext : agent.inheritProjectContext,
371
- inheritSkills: typeof cfg.inheritSkills === "boolean" ? cfg.inheritSkills : agent.inheritSkills,
595
+ description:
596
+ typeof cfg.description === "string" && cfg.description.trim()
597
+ ? cfg.description.trim()
598
+ : agent.description,
599
+ systemPrompt:
600
+ typeof cfg.systemPrompt === "string"
601
+ ? cfg.systemPrompt
602
+ : agent.systemPrompt,
603
+ model: hasOwn(cfg, "model")
604
+ ? typeof cfg.model === "string" && cfg.model.trim()
605
+ ? cfg.model.trim()
606
+ : undefined
607
+ : agent.model,
608
+ fallbackModels: hasOwn(cfg, "fallbackModels")
609
+ ? parseStringArray(cfg.fallbackModels)
610
+ : agent.fallbackModels,
611
+ thinking: hasOwn(cfg, "thinking")
612
+ ? typeof cfg.thinking === "string" && cfg.thinking.trim()
613
+ ? cfg.thinking.trim()
614
+ : undefined
615
+ : agent.thinking,
616
+ tools: hasOwn(cfg, "tools")
617
+ ? parseStringArray(cfg.tools)
618
+ : agent.tools,
619
+ extensions: hasOwn(cfg, "extensions")
620
+ ? (parseStringArray(cfg.extensions) ?? [])
621
+ : agent.extensions,
622
+ skills: hasOwn(cfg, "skills")
623
+ ? parseStringArray(cfg.skills)
624
+ : agent.skills,
625
+ systemPromptMode:
626
+ cfg.systemPromptMode === "append"
627
+ ? "append"
628
+ : cfg.systemPromptMode === "replace"
629
+ ? "replace"
630
+ : agent.systemPromptMode,
631
+ inheritProjectContext:
632
+ typeof cfg.inheritProjectContext === "boolean"
633
+ ? cfg.inheritProjectContext
634
+ : agent.inheritProjectContext,
635
+ inheritSkills:
636
+ typeof cfg.inheritSkills === "boolean"
637
+ ? cfg.inheritSkills
638
+ : agent.inheritSkills,
372
639
  routing: parseRouting(cfg, agent.routing),
373
640
  });
374
641
  } else if (params.resource === "team") {
@@ -376,18 +643,36 @@ export function handleUpdate(params: TeamToolParamsValue, ctx: ManagementContext
376
643
  let roles = team.roles;
377
644
  if (hasOwn(cfg, "roles")) {
378
645
  const parsedRoles = parseRoles(cfg.roles);
379
- if (parsedRoles.error) return result(parsedRoles.error, "error", true);
646
+ if (parsedRoles.error)
647
+ return result(parsedRoles.error, "error", true);
380
648
  roles = parsedRoles.roles!;
381
649
  }
382
650
  content = serializeTeam({
383
651
  ...team,
384
652
  name: nextName,
385
653
  filePath: nextPath,
386
- description: typeof cfg.description === "string" && cfg.description.trim() ? cfg.description.trim() : team.description,
654
+ description:
655
+ typeof cfg.description === "string" && cfg.description.trim()
656
+ ? cfg.description.trim()
657
+ : team.description,
387
658
  roles,
388
- defaultWorkflow: hasOwn(cfg, "defaultWorkflow") ? (typeof cfg.defaultWorkflow === "string" ? sanitizeName(cfg.defaultWorkflow) : undefined) : team.defaultWorkflow,
389
- workspaceMode: cfg.workspaceMode === "worktree" ? "worktree" : cfg.workspaceMode === "single" ? "single" : team.workspaceMode,
390
- maxConcurrency: typeof cfg.maxConcurrency === "number" && Number.isInteger(cfg.maxConcurrency) && cfg.maxConcurrency > 0 ? cfg.maxConcurrency : team.maxConcurrency,
659
+ defaultWorkflow: hasOwn(cfg, "defaultWorkflow")
660
+ ? typeof cfg.defaultWorkflow === "string"
661
+ ? sanitizeName(cfg.defaultWorkflow)
662
+ : undefined
663
+ : team.defaultWorkflow,
664
+ workspaceMode:
665
+ cfg.workspaceMode === "worktree"
666
+ ? "worktree"
667
+ : cfg.workspaceMode === "single"
668
+ ? "single"
669
+ : team.workspaceMode,
670
+ maxConcurrency:
671
+ typeof cfg.maxConcurrency === "number" &&
672
+ Number.isInteger(cfg.maxConcurrency) &&
673
+ cfg.maxConcurrency > 0
674
+ ? cfg.maxConcurrency
675
+ : team.maxConcurrency,
391
676
  routing: parseRouting(cfg, team.routing),
392
677
  });
393
678
  } else {
@@ -395,22 +680,52 @@ export function handleUpdate(params: TeamToolParamsValue, ctx: ManagementContext
395
680
  let steps = workflow.steps;
396
681
  if (hasOwn(cfg, "steps")) {
397
682
  const parsedSteps = parseSteps(cfg.steps);
398
- if (parsedSteps.error) return result(parsedSteps.error, "error", true);
683
+ if (parsedSteps.error)
684
+ return result(parsedSteps.error, "error", true);
399
685
  steps = parsedSteps.steps!;
400
686
  }
401
687
  content = serializeWorkflow({
402
688
  ...workflow,
403
689
  name: nextName,
404
690
  filePath: nextPath,
405
- description: typeof cfg.description === "string" && cfg.description.trim() ? cfg.description.trim() : workflow.description,
406
- maxConcurrency: hasOwn(cfg, "maxConcurrency") ? parseWorkflowMaxConcurrency(cfg.maxConcurrency) : workflow.maxConcurrency,
691
+ description:
692
+ typeof cfg.description === "string" && cfg.description.trim()
693
+ ? cfg.description.trim()
694
+ : workflow.description,
695
+ maxConcurrency: hasOwn(cfg, "maxConcurrency")
696
+ ? parseWorkflowMaxConcurrency(cfg.maxConcurrency)
697
+ : workflow.maxConcurrency,
407
698
  steps,
408
699
  });
409
700
  }
410
701
 
411
- const referenceUpdates = params.updateReferences ? updateReferencesForRename(ctx, params.resource!, current.name, nextName, source, true) : [];
702
+ const referenceUpdates = params.updateReferences
703
+ ? updateReferencesForRename(
704
+ ctx,
705
+ params.resource!,
706
+ current.name,
707
+ nextName,
708
+ source,
709
+ true,
710
+ )
711
+ : [];
412
712
  if (params.dryRun) {
413
- return result([`[dry-run] Would update ${params.resource} at ${current.filePath}:`, "", content, ...(referenceUpdates.length ? ["", "Would update references in:", ...referenceUpdates.map((filePath) => `- ${filePath}`)] : [])].join("\n"));
713
+ return result(
714
+ [
715
+ `[dry-run] Would update ${params.resource} at ${current.filePath}:`,
716
+ "",
717
+ content,
718
+ ...(referenceUpdates.length
719
+ ? [
720
+ "",
721
+ "Would update references in:",
722
+ ...referenceUpdates.map(
723
+ (filePath) => `- ${filePath}`,
724
+ ),
725
+ ]
726
+ : []),
727
+ ].join("\n"),
728
+ );
414
729
  }
415
730
  const backupPath = backupFile(current.filePath);
416
731
  try {
@@ -428,28 +743,68 @@ export function handleUpdate(params: TeamToolParamsValue, ctx: ManagementContext
428
743
  }
429
744
  fs.writeFileSync(nextPath, content, "utf-8");
430
745
  } catch (updateError) {
431
- return result(`Failed to update ${params.resource}: ${updateError instanceof Error ? updateError.message : String(updateError)}`, "error", true);
746
+ return result(
747
+ `Failed to update ${params.resource}: ${updateError instanceof Error ? updateError.message : String(updateError)}`,
748
+ "error",
749
+ true,
750
+ );
432
751
  }
433
- const updatedRefs = params.updateReferences ? updateReferencesForRename(ctx, params.resource!, current.name, nextName, source, false) : [];
434
- return result([`Updated ${params.resource} at ${nextPath}. Backup: ${backupPath}.`, ...(updatedRefs.length ? ["Updated references:", ...updatedRefs.map((filePath) => `- ${filePath}`)] : [])].join("\n"));
752
+ const updatedRefs = params.updateReferences
753
+ ? updateReferencesForRename(
754
+ ctx,
755
+ params.resource!,
756
+ current.name,
757
+ nextName,
758
+ source,
759
+ false,
760
+ )
761
+ : [];
762
+ return result(
763
+ [
764
+ `Updated ${params.resource} at ${nextPath}. Backup: ${backupPath}.`,
765
+ ...(updatedRefs.length
766
+ ? [
767
+ "Updated references:",
768
+ ...updatedRefs.map((filePath) => `- ${filePath}`),
769
+ ]
770
+ : []),
771
+ ].join("\n"),
772
+ );
435
773
  }
436
774
 
437
- export function handleDelete(params: TeamToolParamsValue, ctx: ManagementContext): PiTeamsToolResult {
775
+ export function handleDelete(
776
+ params: TeamToolParamsValue,
777
+ ctx: ManagementContext,
778
+ ): PiTeamsToolResult {
438
779
  const intentError = enforceDestructiveIntent("delete", params, ctx.config);
439
780
  if (intentError) return intentError;
440
- if (!params.confirm) return result("delete requires confirm: true.", "error", true);
781
+ if (!params.confirm)
782
+ return result("delete requires confirm: true.", "error", true);
441
783
  const resolved = resolveMutable(ctx, params);
442
784
  if (resolved.error) return resolved.error;
443
785
  const refs = findReferences(ctx, params.resource!, resolved.resource!.name);
444
786
  if (refs.length > 0 && !params.force) {
445
- return result(`${params.resource} '${resolved.resource!.name}' is still referenced. Use force: true to delete anyway.\n${refs.map((ref) => `- ${ref}`).join("\n")}`, "error", true);
787
+ return result(
788
+ `${params.resource} '${resolved.resource!.name}' is still referenced. Use force: true to delete anyway.\n${refs.map((ref) => `- ${ref}`).join("\n")}`,
789
+ "error",
790
+ true,
791
+ );
446
792
  }
447
- if (params.dryRun) return result(`[dry-run] Would delete ${params.resource} at ${resolved.resource!.filePath}.${refs.length ? `\nReferences:\n${refs.map((ref) => `- ${ref}`).join("\n")}` : ""}`);
793
+ if (params.dryRun)
794
+ return result(
795
+ `[dry-run] Would delete ${params.resource} at ${resolved.resource!.filePath}.${refs.length ? `\nReferences:\n${refs.map((ref) => `- ${ref}`).join("\n")}` : ""}`,
796
+ );
448
797
  const backupPath = backupFile(resolved.resource!.filePath);
449
798
  try {
450
799
  fs.unlinkSync(resolved.resource!.filePath);
451
800
  } catch (deleteError) {
452
- return result(`Failed to delete ${params.resource}: ${deleteError instanceof Error ? deleteError.message : String(deleteError)}`, "error", true);
801
+ return result(
802
+ `Failed to delete ${params.resource}: ${deleteError instanceof Error ? deleteError.message : String(deleteError)}`,
803
+ "error",
804
+ true,
805
+ );
453
806
  }
454
- return result(`Deleted ${params.resource} at ${resolved.resource!.filePath}. Backup: ${backupPath}.`);
807
+ return result(
808
+ `Deleted ${params.resource} at ${resolved.resource!.filePath}. Backup: ${backupPath}.`,
809
+ );
455
810
  }