pi-crew 0.1.49 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (249) hide show
  1. package/CHANGELOG.md +74 -1
  2. package/README.md +176 -781
  3. package/agents/analyst.md +11 -11
  4. package/agents/critic.md +11 -11
  5. package/agents/executor.md +11 -11
  6. package/agents/explorer.md +11 -11
  7. package/agents/planner.md +11 -11
  8. package/agents/reviewer.md +11 -11
  9. package/agents/security-reviewer.md +11 -11
  10. package/agents/test-engineer.md +11 -11
  11. package/agents/verifier.md +70 -11
  12. package/agents/writer.md +11 -11
  13. package/docs/actions-reference.md +595 -0
  14. package/docs/commands-reference.md +347 -0
  15. package/docs/runtime-flow.md +148 -148
  16. package/index.ts +6 -6
  17. package/package.json +99 -99
  18. package/skills/async-worker-recovery/SKILL.md +42 -42
  19. package/skills/context-artifact-hygiene/SKILL.md +52 -52
  20. package/skills/delegation-patterns/SKILL.md +54 -54
  21. package/skills/mailbox-interactive/SKILL.md +40 -40
  22. package/skills/model-routing-context/SKILL.md +39 -39
  23. package/skills/multi-perspective-review/SKILL.md +58 -58
  24. package/skills/observability-reliability/SKILL.md +41 -41
  25. package/skills/orchestration/SKILL.md +157 -157
  26. package/skills/ownership-session-security/SKILL.md +41 -41
  27. package/skills/pi-extension-lifecycle/SKILL.md +39 -39
  28. package/skills/requirements-to-task-packet/SKILL.md +63 -63
  29. package/skills/resource-discovery-config/SKILL.md +41 -41
  30. package/skills/runtime-state-reader/SKILL.md +44 -44
  31. package/skills/secure-agent-orchestration-review/SKILL.md +45 -45
  32. package/skills/state-mutation-locking/SKILL.md +42 -42
  33. package/skills/systematic-debugging/SKILL.md +67 -67
  34. package/skills/ui-render-performance/SKILL.md +39 -39
  35. package/skills/verification-before-done/SKILL.md +57 -57
  36. package/skills/worktree-isolation/SKILL.md +39 -39
  37. package/src/adapters/claude-adapter.ts +25 -0
  38. package/src/adapters/codex-adapter.ts +21 -0
  39. package/src/adapters/cursor-adapter.ts +17 -0
  40. package/src/adapters/export-util.ts +137 -0
  41. package/src/adapters/index.ts +15 -0
  42. package/src/adapters/registry.ts +18 -0
  43. package/src/adapters/types.ts +23 -0
  44. package/src/agents/agent-config.ts +2 -0
  45. package/src/agents/agent-search.ts +98 -98
  46. package/src/agents/discover-agents.ts +2 -1
  47. package/src/config/config.ts +14 -1
  48. package/src/config/defaults.ts +5 -5
  49. package/src/config/drift-detector.ts +211 -0
  50. package/src/config/markers.ts +327 -0
  51. package/src/config/resilient-parser.ts +108 -0
  52. package/src/config/suggestions.ts +74 -0
  53. package/src/extension/cross-extension-rpc.ts +103 -82
  54. package/src/extension/project-init.ts +36 -4
  55. package/src/extension/register.ts +67 -22
  56. package/src/extension/registration/commands.ts +77 -8
  57. package/src/extension/registration/subagent-tools.ts +10 -1
  58. package/src/extension/registration/team-tool.ts +10 -1
  59. package/src/extension/registration/viewers.ts +48 -34
  60. package/src/extension/run-bundle-schema.ts +89 -89
  61. package/src/extension/run-export.ts +26 -12
  62. package/src/extension/run-import.ts +25 -1
  63. package/src/extension/run-index.ts +5 -1
  64. package/src/extension/run-maintenance.ts +142 -68
  65. package/src/extension/team-manager-command.ts +10 -1
  66. package/src/extension/team-tool/context.ts +1 -1
  67. package/src/extension/team-tool/doctor.ts +28 -3
  68. package/src/extension/team-tool/handle-settings.ts +195 -188
  69. package/src/extension/team-tool/inspect.ts +41 -41
  70. package/src/extension/team-tool/intent-policy.ts +42 -42
  71. package/src/extension/team-tool/lifecycle-actions.ts +27 -8
  72. package/src/extension/team-tool/plan.ts +19 -19
  73. package/src/extension/team-tool/run.ts +12 -1
  74. package/src/extension/team-tool.ts +14 -3
  75. package/src/i18n.ts +184 -184
  76. package/src/observability/exporters/otlp-exporter.ts +92 -77
  77. package/src/prompt/prompt-runtime.ts +72 -72
  78. package/src/runtime/agent-memory.ts +72 -72
  79. package/src/runtime/agent-observability.ts +114 -114
  80. package/src/runtime/async-marker.ts +26 -26
  81. package/src/runtime/attention-events.ts +28 -28
  82. package/src/runtime/auto-resume.ts +100 -0
  83. package/src/runtime/background-runner.ts +11 -1
  84. package/src/runtime/cancellation-token.ts +89 -89
  85. package/src/runtime/cancellation.ts +61 -61
  86. package/src/runtime/capability-inventory.ts +116 -116
  87. package/src/runtime/child-pi.ts +7 -2
  88. package/src/runtime/compaction-summary.ts +271 -0
  89. package/src/runtime/completion-guard.ts +190 -190
  90. package/src/runtime/concurrency.ts +3 -1
  91. package/src/runtime/crash-recovery.ts +33 -0
  92. package/src/runtime/delta-conflict.ts +360 -0
  93. package/src/runtime/diagnostic-export.ts +3 -1
  94. package/src/runtime/direct-run.ts +35 -35
  95. package/src/runtime/event-stream-bridge.ts +3 -1
  96. package/src/runtime/foreground-control.ts +82 -82
  97. package/src/runtime/green-contract.ts +46 -46
  98. package/src/runtime/group-join.ts +106 -106
  99. package/src/runtime/heartbeat-gradient.ts +28 -28
  100. package/src/runtime/heartbeat-watcher.ts +124 -124
  101. package/src/runtime/iteration-hooks.ts +262 -0
  102. package/src/runtime/live-agent-control.ts +88 -88
  103. package/src/runtime/live-control-realtime.ts +36 -36
  104. package/src/runtime/live-extension-bridge.ts +150 -150
  105. package/src/runtime/live-irc.ts +92 -92
  106. package/src/runtime/live-session-health.ts +100 -100
  107. package/src/runtime/loop-gates.ts +129 -0
  108. package/src/runtime/metric-parser.ts +40 -0
  109. package/src/runtime/notebook-helpers.ts +90 -90
  110. package/src/runtime/orphan-sentinel.ts +7 -7
  111. package/src/runtime/parallel-research.ts +44 -44
  112. package/src/runtime/phase-progress.ts +217 -0
  113. package/src/runtime/pi-args.ts +38 -2
  114. package/src/runtime/pi-json-output.ts +111 -111
  115. package/src/runtime/pi-spawn.ts +74 -6
  116. package/src/runtime/policy-engine.ts +79 -79
  117. package/src/runtime/post-checks.ts +122 -0
  118. package/src/runtime/process-status.ts +14 -1
  119. package/src/runtime/progress-event-coalescer.ts +43 -43
  120. package/src/runtime/prose-compressor.ts +164 -164
  121. package/src/runtime/recovery-recipes.ts +74 -74
  122. package/src/runtime/result-extractor.ts +121 -121
  123. package/src/runtime/role-permission.ts +39 -39
  124. package/src/runtime/sensitive-paths.ts +3 -3
  125. package/src/runtime/session-resources.ts +25 -25
  126. package/src/runtime/session-snapshot.ts +59 -59
  127. package/src/runtime/session-usage.ts +79 -79
  128. package/src/runtime/sidechain-output.ts +29 -29
  129. package/src/runtime/stream-preview.ts +177 -177
  130. package/src/runtime/supervisor-contact.ts +59 -59
  131. package/src/runtime/task-display.ts +38 -38
  132. package/src/runtime/task-graph.ts +207 -0
  133. package/src/runtime/task-quality.ts +207 -0
  134. package/src/runtime/task-runner/capabilities.ts +78 -78
  135. package/src/runtime/task-runner/live-executor.ts +7 -1
  136. package/src/runtime/task-runner/progress.ts +119 -119
  137. package/src/runtime/task-runner/prompt-builder.ts +1 -1
  138. package/src/runtime/task-runner/prompt-pipeline.ts +64 -64
  139. package/src/runtime/task-runner/result-utils.ts +14 -14
  140. package/src/runtime/task-runner/run-projection.ts +103 -103
  141. package/src/runtime/task-runner/state-helpers.ts +22 -22
  142. package/src/runtime/team-runner.ts +126 -7
  143. package/src/runtime/worker-heartbeat.ts +21 -21
  144. package/src/runtime/worker-startup.ts +57 -57
  145. package/src/runtime/workflow-state.ts +187 -0
  146. package/src/runtime/workspace-tree.ts +298 -298
  147. package/src/schema/config-schema.ts +12 -0
  148. package/src/schema/validation-types.ts +148 -0
  149. package/src/skills/skill-templates.ts +374 -0
  150. package/src/state/active-run-registry.ts +35 -11
  151. package/src/state/atomic-write.ts +33 -26
  152. package/src/state/contracts.ts +1 -0
  153. package/src/state/event-reconstructor.ts +217 -0
  154. package/src/state/locks.ts +2 -11
  155. package/src/state/mailbox.ts +4 -3
  156. package/src/state/state-store.ts +32 -14
  157. package/src/state/task-claims.ts +44 -44
  158. package/src/state/types.ts +9 -0
  159. package/src/state/usage.ts +29 -29
  160. package/src/subagents/async-entry.ts +1 -1
  161. package/src/subagents/index.ts +3 -3
  162. package/src/subagents/live/control.ts +1 -1
  163. package/src/subagents/live/manager.ts +1 -1
  164. package/src/subagents/live/realtime.ts +1 -1
  165. package/src/subagents/live/session-runtime.ts +1 -1
  166. package/src/subagents/manager.ts +1 -1
  167. package/src/subagents/spawn.ts +1 -1
  168. package/src/teams/team-serializer.ts +38 -38
  169. package/src/types/diff.d.ts +18 -18
  170. package/src/ui/crew-footer.ts +101 -101
  171. package/src/ui/crew-select-list.ts +111 -111
  172. package/src/ui/crew-widget.ts +9 -4
  173. package/src/ui/dashboard-panes/cancellation-pane.ts +42 -42
  174. package/src/ui/dashboard-panes/capability-pane.ts +59 -59
  175. package/src/ui/dashboard-panes/mailbox-pane.ts +35 -35
  176. package/src/ui/dashboard-panes/metrics-pane.ts +34 -34
  177. package/src/ui/dashboard-panes/progress-pane.ts +11 -0
  178. package/src/ui/dynamic-border.ts +25 -25
  179. package/src/ui/layout-primitives.ts +106 -106
  180. package/src/ui/loaders.ts +158 -158
  181. package/src/ui/powerbar-publisher.ts +6 -0
  182. package/src/ui/render-coalescer.ts +51 -51
  183. package/src/ui/render-diff.ts +119 -119
  184. package/src/ui/render-scheduler.ts +143 -143
  185. package/src/ui/run-action-dispatcher.ts +10 -1
  186. package/src/ui/spinner.ts +17 -17
  187. package/src/ui/status-colors.ts +58 -58
  188. package/src/ui/syntax-highlight.ts +116 -116
  189. package/src/ui/transcript-entries.ts +258 -258
  190. package/src/utils/completion-dedupe.ts +63 -63
  191. package/src/utils/frontmatter.ts +68 -68
  192. package/src/utils/git.ts +262 -262
  193. package/src/utils/ids.ts +17 -17
  194. package/src/utils/incremental-reader.ts +104 -104
  195. package/src/utils/names.ts +27 -27
  196. package/src/utils/redaction.ts +44 -44
  197. package/src/utils/safe-paths.ts +47 -47
  198. package/src/utils/scan-cache.ts +136 -136
  199. package/src/utils/sleep.ts +40 -26
  200. package/src/utils/task-name-generator.ts +337 -337
  201. package/src/workflows/validate-workflow.ts +40 -40
  202. package/src/worktree/branch-freshness.ts +45 -45
  203. package/src/worktree/worktree-manager.ts +11 -3
  204. package/teams/default.team.md +12 -12
  205. package/teams/fast-fix.team.md +11 -11
  206. package/teams/implementation.team.md +18 -18
  207. package/teams/parallel-research.team.md +14 -14
  208. package/teams/research.team.md +11 -11
  209. package/teams/review.team.md +12 -12
  210. package/workflows/default.workflow.md +30 -29
  211. package/workflows/fast-fix.workflow.md +23 -22
  212. package/workflows/implementation.workflow.md +43 -38
  213. package/workflows/parallel-research.workflow.md +46 -46
  214. package/workflows/research.workflow.md +22 -22
  215. package/workflows/review.workflow.md +30 -30
  216. package/docs/refactor-tasks-phase3.md +0 -394
  217. package/docs/refactor-tasks-phase4.md +0 -564
  218. package/docs/refactor-tasks-phase5.md +0 -402
  219. package/docs/refactor-tasks-phase6.md +0 -662
  220. package/docs/refactor-tasks.md +0 -1484
  221. package/docs/research/AGENT-EXECUTION-ARCHITECTURE.md +0 -261
  222. package/docs/research/AGENT-LIFECYCLE-COMPARISON.md +0 -111
  223. package/docs/research/AUDIT_OH_MY_PI.md +0 -261
  224. package/docs/research/AUDIT_PI_CREW.md +0 -457
  225. package/docs/research/CAVEMAN-DEEP-RESEARCH.md +0 -281
  226. package/docs/research/COMPARISON_OH_MY_PI_VS_PI_CREW.md +0 -264
  227. package/docs/research/DEEP-RESEARCH-PI-POWERBAR.md +0 -343
  228. package/docs/research/DEEP_RESEARCH_SUBAGENT_ARCHITECTURE.md +0 -480
  229. package/docs/research/GAP_CLOSURE_IMPLEMENTATION_PLAN.md +0 -354
  230. package/docs/research/IMPLEMENTATION_PLAN.md +0 -385
  231. package/docs/research/LIVE-SESSION-PRODUCTION-READY-PLAN.md +0 -502
  232. package/docs/research/OH-MY-PI-DEEP-RESEARCH-v14.7.6.md +0 -266
  233. package/docs/research/REMAINING-GAPS-PLAN.md +0 -363
  234. package/docs/research/SESSION-SUMMARY-2026-05-08.md +0 -146
  235. package/docs/research/UI-RESPONSIVENESS-AUDIT.md +0 -173
  236. package/docs/research-awesome-agent-skills-distillation.md +0 -100
  237. package/docs/research-extension-examples.md +0 -297
  238. package/docs/research-extension-system.md +0 -324
  239. package/docs/research-oh-my-pi-distillation.md +0 -369
  240. package/docs/research-optimization-plan.md +0 -548
  241. package/docs/research-phase10-distillation.md +0 -199
  242. package/docs/research-phase11-distillation.md +0 -201
  243. package/docs/research-phase8-operator-experience-plan.md +0 -819
  244. package/docs/research-phase9-observability-reliability-plan.md +0 -1190
  245. package/docs/research-pi-coding-agent.md +0 -357
  246. package/docs/research-source-pi-crew-reference.md +0 -174
  247. package/docs/research-ui-optimization-plan.md +0 -480
  248. package/docs/source-runtime-refactor-map.md +0 -107
  249. package/src/utils/atomic-write.ts +0 -33
@@ -1,42 +1,42 @@
1
- import type { PiTeamsConfig } from "../../config/config.ts";
2
- import type { TeamToolParamsValue } from "../../schema/team-tool-schema.ts";
3
- import type { PiTeamsToolResult } from "../tool-result.ts";
4
- import { configRecord, result } from "./context.ts";
5
-
6
- export type DestructiveIntentAction = "cancel" | "cleanup" | "delete" | "forget" | "prune";
7
-
8
- const DESTRUCTIVE_ACTION_LABELS: Record<DestructiveIntentAction, string> = {
9
- cancel: "cancel",
10
- cleanup: "forced cleanup",
11
- delete: "delete",
12
- forget: "forget",
13
- prune: "prune",
14
- };
15
-
16
- export function intentFromConfig(config: unknown): string | undefined {
17
- const cfg = configRecord(config);
18
- const rawIntent = cfg.intent ?? cfg._intent;
19
- if (typeof rawIntent !== "string") return undefined;
20
- const intent = rawIntent.replace(/\s+/g, " ").trim();
21
- return intent ? intent.slice(0, 500) : undefined;
22
- }
23
-
24
- export function shouldRequireIntent(config: PiTeamsConfig | undefined): boolean {
25
- return config?.policy?.requireIntentForDestructiveActions === true;
26
- }
27
-
28
- export function enforceDestructiveIntent(
29
- action: DestructiveIntentAction,
30
- params: TeamToolParamsValue,
31
- config: PiTeamsConfig | undefined,
32
- ): PiTeamsToolResult | undefined {
33
- if (!shouldRequireIntent(config)) return undefined;
34
- if (action === "cleanup" && params.force !== true) return undefined;
35
- if (intentFromConfig(params.config)) return undefined;
36
- const label = DESTRUCTIVE_ACTION_LABELS[action];
37
- return result(
38
- `Destructive action '${label}' requires config.intent when policy.requireIntentForDestructiveActions is enabled.`,
39
- { action: action === "delete" ? "management" : action, status: "error" },
40
- true,
41
- );
42
- }
1
+ import type { PiTeamsConfig } from "../../config/config.ts";
2
+ import type { TeamToolParamsValue } from "../../schema/team-tool-schema.ts";
3
+ import type { PiTeamsToolResult } from "../tool-result.ts";
4
+ import { configRecord, result } from "./context.ts";
5
+
6
+ export type DestructiveIntentAction = "cancel" | "cleanup" | "delete" | "forget" | "prune";
7
+
8
+ const DESTRUCTIVE_ACTION_LABELS: Record<DestructiveIntentAction, string> = {
9
+ cancel: "cancel",
10
+ cleanup: "forced cleanup",
11
+ delete: "delete",
12
+ forget: "forget",
13
+ prune: "prune",
14
+ };
15
+
16
+ export function intentFromConfig(config: unknown): string | undefined {
17
+ const cfg = configRecord(config);
18
+ const rawIntent = cfg.intent ?? cfg._intent;
19
+ if (typeof rawIntent !== "string") return undefined;
20
+ const intent = rawIntent.replace(/\s+/g, " ").trim();
21
+ return intent ? intent.slice(0, 500) : undefined;
22
+ }
23
+
24
+ export function shouldRequireIntent(config: PiTeamsConfig | undefined): boolean {
25
+ return config?.policy?.requireIntentForDestructiveActions === true;
26
+ }
27
+
28
+ export function enforceDestructiveIntent(
29
+ action: DestructiveIntentAction,
30
+ params: TeamToolParamsValue,
31
+ config: PiTeamsConfig | undefined,
32
+ ): PiTeamsToolResult | undefined {
33
+ if (!shouldRequireIntent(config)) return undefined;
34
+ if (action === "cleanup" && params.force !== true) return undefined;
35
+ if (intentFromConfig(params.config)) return undefined;
36
+ const label = DESTRUCTIVE_ACTION_LABELS[action];
37
+ return result(
38
+ `Destructive action '${label}' requires config.intent when policy.requireIntentForDestructiveActions is enabled.`,
39
+ { action: action === "delete" ? "management" : action, status: "error" },
40
+ true,
41
+ );
42
+ }
@@ -11,6 +11,9 @@ import type { PiTeamsToolResult } from "../tool-result.ts";
11
11
  import { configRecord, result, type TeamContext } from "./context.ts";
12
12
  import { enforceDestructiveIntent, intentFromConfig } from "./intent-policy.ts";
13
13
  import { executeHook, appendHookEvent } from "../../hooks/registry.ts";
14
+ import { resolveRealContainedPath } from "../../utils/safe-paths.ts";
15
+ import { projectCrewRoot, userCrewRoot } from "../../utils/paths.ts";
16
+ import * as path from "node:path";
14
17
 
15
18
  export function handleWorktrees(params: TeamToolParamsValue, ctx: TeamContext): PiTeamsToolResult {
16
19
  if (!params.runId) return result("Worktrees requires runId.", { action: "worktrees", status: "error" }, true);
@@ -42,6 +45,7 @@ export function handleImport(params: TeamToolParamsValue, ctx: TeamContext): PiT
42
45
  }
43
46
 
44
47
  export async function handleExport(params: TeamToolParamsValue, ctx: TeamContext): Promise<PiTeamsToolResult> {
48
+ // Note: no ownership check — export is intentionally cross-session (read-only, for sharing)
45
49
  if (!params.runId) return result("Export requires runId.", { action: "export", status: "error" }, true);
46
50
  const loaded = loadRunManifestById(ctx.cwd, params.runId);
47
51
  if (!loaded) return result(`Run '${params.runId}' not found.`, { action: "export", status: "error" }, true);
@@ -57,6 +61,9 @@ export async function handleExport(params: TeamToolParamsValue, ctx: TeamContext
57
61
  return result([`Exported run ${loaded.manifest.runId}.`, `JSON: ${exported.jsonPath}`, `Markdown: ${exported.markdownPath}`].join("\n"), { action: "export", status: "ok", runId: loaded.manifest.runId, artifactsRoot: loaded.manifest.artifactsRoot });
58
62
  }
59
63
 
64
+ // Note: handlePrune has no ownership check — intentionally cross-session.
65
+ // Prune is a maintenance-level operation that removes ALL finished runs
66
+ // regardless of which session created them. Requires confirm: true.
60
67
  export async function handlePrune(params: TeamToolParamsValue, ctx: TeamContext): Promise<PiTeamsToolResult> {
61
68
  const intentError = enforceDestructiveIntent("prune", params, ctx.config);
62
69
  if (intentError) return intentError;
@@ -65,12 +72,12 @@ export async function handlePrune(params: TeamToolParamsValue, ctx: TeamContext)
65
72
  if (keep < 0 || !Number.isInteger(keep)) return result("keep must be an integer >= 0.", { action: "prune", status: "error" }, true);
66
73
  const intent = intentFromConfig(params.config);
67
74
  const pruned = pruneFinishedRuns(ctx.cwd, keep, { intent, signal: ctx.signal });
68
- const firstRunId = pruned.kept[0] ?? pruned.removed[0];
69
- if (firstRunId) {
70
- const manifest = loadRunManifestById(ctx.cwd, firstRunId)?.manifest;
71
- if (manifest) {
72
- const hookReport = await executeHook("before_cleanup", { runId: manifest.runId, cwd: ctx.cwd });
73
- appendHookEvent(manifest, hookReport);
75
+ // Fire hook once with all removed run IDs for batch visibility
76
+ if (pruned.removed.length > 0) {
77
+ const sampleManifest = loadRunManifestById(ctx.cwd, pruned.removed[0])?.manifest;
78
+ if (sampleManifest) {
79
+ const hookReport = await executeHook("before_cleanup", { runId: sampleManifest.runId, cwd: ctx.cwd, data: { removedRunIds: pruned.removed, keptCount: pruned.kept.length } });
80
+ appendHookEvent(sampleManifest, hookReport);
74
81
  }
75
82
  }
76
83
  return result([`Pruned finished pi-crew runs.`, `Kept: ${pruned.kept.length}`, `Removed: ${pruned.removed.length}`, ...(pruned.auditPath ? [`Audit: ${pruned.auditPath}`] : []), ...(pruned.removed.length ? ["Removed runs:", ...pruned.removed.map((runId) => `- ${runId}`)] : [])].join("\n"), { action: "prune", status: "ok", intent });
@@ -84,6 +91,10 @@ export async function handleForget(params: TeamToolParamsValue, ctx: TeamContext
84
91
  const loaded = loadRunManifestById(ctx.cwd, params.runId);
85
92
  if (!loaded) return result(`Run '${params.runId}' not found.`, { action: "forget", status: "error" }, true);
86
93
 
94
+ // Ownership check — prevent cross-session deletion
95
+ const foreignRun = typeof loaded.manifest.ownerSessionId === "string" && loaded.manifest.ownerSessionId !== ctx.sessionId;
96
+ if (foreignRun) return result(`Run ${params.runId} belongs to another session; not forgotten.`, { action: "forget", status: "error", runId: loaded.manifest.runId }, true);
97
+
87
98
  const hookReport = await executeHook("before_forget", { runId: loaded.manifest.runId, cwd: ctx.cwd });
88
99
  appendHookEvent(loaded.manifest, hookReport);
89
100
  if (hookReport.outcome === "block") {
@@ -94,8 +105,12 @@ export async function handleForget(params: TeamToolParamsValue, ctx: TeamContext
94
105
  if (cleanup.preserved.length > 0 && !params.force) return result([`Run '${params.runId}' has preserved worktrees. Use force: true to forget anyway.`, ...cleanup.preserved.map((item) => `- ${item.path}: ${item.reason}`)].join("\n"), { action: "forget", status: "error", runId: loaded.manifest.runId, artifactsRoot: loaded.manifest.artifactsRoot }, true);
95
106
  const intent = intentFromConfig(params.config);
96
107
  appendEvent(loaded.manifest.eventsPath, { type: "run.forget_requested", runId: loaded.manifest.runId, message: "Run state and artifacts are being forgotten.", data: { force: params.force === true, removedWorktrees: cleanup.removed, preservedWorktrees: cleanup.preserved, intent } });
97
- fs.rmSync(loaded.manifest.stateRoot, { recursive: true, force: true });
98
- fs.rmSync(loaded.manifest.artifactsRoot, { recursive: true, force: true });
108
+ // Determine scope from manifest paths (project vs user-level runs)
109
+ const crewRoot = loaded.manifest.stateRoot.startsWith(userCrewRoot() + path.sep) ? userCrewRoot() : projectCrewRoot(loaded.manifest.cwd);
110
+ const resolvedStateRoot = resolveRealContainedPath(crewRoot, loaded.manifest.stateRoot);
111
+ const resolvedArtifactsRoot = resolveRealContainedPath(crewRoot, loaded.manifest.artifactsRoot);
112
+ fs.rmSync(resolvedStateRoot, { recursive: true, force: true });
113
+ fs.rmSync(resolvedArtifactsRoot, { recursive: true, force: true });
99
114
  return result([`Forgot run ${loaded.manifest.runId}.`, `Removed state: ${loaded.manifest.stateRoot}`, `Removed artifacts: ${loaded.manifest.artifactsRoot}`, ...(cleanup.removed.length ? ["Removed worktrees:", ...cleanup.removed.map((item) => `- ${item}`)] : [])].join("\n"), { action: "forget", status: "ok", runId: loaded.manifest.runId, intent });
100
115
  }
101
116
 
@@ -106,6 +121,10 @@ export async function handleCleanup(params: TeamToolParamsValue, ctx: TeamContex
106
121
  const loaded = loadRunManifestById(ctx.cwd, params.runId);
107
122
  if (!loaded) return result(`Run '${params.runId}' not found.`, { action: "cleanup", status: "error" }, true);
108
123
 
124
+ // Ownership check — prevent cross-session worktree cleanup
125
+ const foreignRun = typeof loaded.manifest.ownerSessionId === "string" && loaded.manifest.ownerSessionId !== ctx.sessionId;
126
+ if (foreignRun) return result(`Run ${params.runId} belongs to another session; not cleaned up.`, { action: "cleanup", status: "error", runId: loaded.manifest.runId }, true);
127
+
109
128
  const hookReport = await executeHook("before_cleanup", { runId: loaded.manifest.runId, cwd: ctx.cwd });
110
129
  appendHookEvent(loaded.manifest, hookReport);
111
130
  if (hookReport.outcome === "block") {
@@ -1,19 +1,19 @@
1
- import { allTeams, discoverTeams } from "../../teams/discover-teams.ts";
2
- import { allWorkflows, discoverWorkflows } from "../../workflows/discover-workflows.ts";
3
- import { validateWorkflowForTeam } from "../../workflows/validate-workflow.ts";
4
- import type { TeamToolParamsValue } from "../../schema/team-tool-schema.ts";
5
- import type { PiTeamsToolResult } from "../tool-result.ts";
6
- import { result, type TeamContext } from "./context.ts";
7
-
8
- export function handlePlan(params: TeamToolParamsValue, ctx: TeamContext): PiTeamsToolResult {
9
- const teamName = params.team ?? "default";
10
- const team = allTeams(discoverTeams(ctx.cwd)).find((item) => item.name === teamName);
11
- if (!team) return result(`Team '${teamName}' not found.`, { action: "plan", status: "error" }, true);
12
- const workflowName = params.workflow ?? team.defaultWorkflow ?? "default";
13
- const workflow = allWorkflows(discoverWorkflows(ctx.cwd)).find((item) => item.name === workflowName);
14
- if (!workflow) return result(`Workflow '${workflowName}' not found.`, { action: "plan", status: "error" }, true);
15
- const errors = validateWorkflowForTeam(workflow, team);
16
- if (errors.length > 0) return result([`Workflow '${workflow.name}' is not valid for team '${team.name}':`, ...errors.map((error) => `- ${error}`)].join("\n"), { action: "plan", status: "error" }, true);
17
- const lines = [`Team plan: ${team.name}`, `Workflow: ${workflow.name}`, `Goal: ${params.goal ?? params.task ?? "(not provided)"}`, "", "Steps:", ...workflow.steps.map((step, index) => `${index + 1}. ${step.id} [${step.role}]${step.dependsOn?.length ? ` after ${step.dependsOn.join(", ")}` : ""}`)];
18
- return result(lines.join("\n"), { action: "plan", status: "ok" });
19
- }
1
+ import { allTeams, discoverTeams } from "../../teams/discover-teams.ts";
2
+ import { allWorkflows, discoverWorkflows } from "../../workflows/discover-workflows.ts";
3
+ import { validateWorkflowForTeam } from "../../workflows/validate-workflow.ts";
4
+ import type { TeamToolParamsValue } from "../../schema/team-tool-schema.ts";
5
+ import type { PiTeamsToolResult } from "../tool-result.ts";
6
+ import { result, type TeamContext } from "./context.ts";
7
+
8
+ export function handlePlan(params: TeamToolParamsValue, ctx: TeamContext): PiTeamsToolResult {
9
+ const teamName = params.team ?? "default";
10
+ const team = allTeams(discoverTeams(ctx.cwd)).find((item) => item.name === teamName);
11
+ if (!team) return result(`Team '${teamName}' not found.`, { action: "plan", status: "error" }, true);
12
+ const workflowName = params.workflow ?? team.defaultWorkflow ?? "default";
13
+ const workflow = allWorkflows(discoverWorkflows(ctx.cwd)).find((item) => item.name === workflowName);
14
+ if (!workflow) return result(`Workflow '${workflowName}' not found.`, { action: "plan", status: "error" }, true);
15
+ const errors = validateWorkflowForTeam(workflow, team);
16
+ if (errors.length > 0) return result([`Workflow '${workflow.name}' is not valid for team '${team.name}':`, ...errors.map((error) => `- ${error}`)].join("\n"), { action: "plan", status: "error" }, true);
17
+ const lines = [`Team plan: ${team.name}`, `Workflow: ${workflow.name}`, `Goal: ${params.goal ?? params.task ?? "(not provided)"}`, "", "Steps:", ...workflow.steps.map((step, index) => `${index + 1}. ${step.id} [${step.role}]${step.dependsOn?.length ? ` after ${step.dependsOn.join(", ")}` : ""}`)];
18
+ return result(lines.join("\n"), { action: "plan", status: "ok" });
19
+ }
@@ -8,7 +8,18 @@ import { registerActiveRun, unregisterActiveRun } from "../../state/active-run-r
8
8
  import { createRunManifest, loadRunManifestById, updateRunStatus } from "../../state/state-store.ts";
9
9
  import { atomicWriteJson } from "../../state/atomic-write.ts";
10
10
  import { validateWorkflowForTeam } from "../../workflows/validate-workflow.ts";
11
- import { executeTeamRun } from "../../runtime/team-runner.ts";
11
+ // Heavy runtime lazy-loaded to avoid 1.4s import cost at extension registration.
12
+ import type { executeTeamRun as ExecuteTeamRunFn } from "../../runtime/team-runner.ts";
13
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars -- type-only import for TS inference
14
+ const _typeCheck: typeof ExecuteTeamRunFn = null as never as typeof ExecuteTeamRunFn;
15
+ let _cachedExecuteTeamRun: typeof ExecuteTeamRunFn | undefined;
16
+ async function executeTeamRun(...args: Parameters<typeof ExecuteTeamRunFn>): Promise<Awaited<ReturnType<typeof ExecuteTeamRunFn>>> {
17
+ if (!_cachedExecuteTeamRun) {
18
+ const mod = await import("../../runtime/team-runner.ts");
19
+ _cachedExecuteTeamRun = mod.executeTeamRun;
20
+ }
21
+ return _cachedExecuteTeamRun(...args);
22
+ }
12
23
  import { spawnBackgroundTeamRun } from "../../subagents/async-entry.ts";
13
24
  import { appendEvent, readEvents } from "../../state/event-log.ts";
14
25
  import { resolveCrewRuntime, runtimeResolutionState } from "../../runtime/runtime-resolver.ts";
@@ -26,7 +26,17 @@ import { formatValidationReport, validateResources } from "./validate-resources.
26
26
  import { formatRecommendation, recommendTeam } from "./team-recommendation.ts";
27
27
  import type { PiTeamsToolResult } from "./tool-result.ts";
28
28
  import type { ArtifactDescriptor, TeamRunManifest, TeamTaskState } from "../state/types.ts";
29
- import { executeTeamRun } from "../runtime/team-runner.ts";
29
+ // Heavy runtime lazy-loaded to avoid 1.4s import cost at extension registration.
30
+ // executeTeamRun is only called when a team run actually executes.
31
+ import type { executeTeamRun as ExecuteTeamRunFn } from "../runtime/team-runner.ts";
32
+ let _cachedExecuteTeamRun: typeof ExecuteTeamRunFn | undefined;
33
+ async function executeTeamRun(...args: Parameters<typeof ExecuteTeamRunFn>): Promise<Awaited<ReturnType<typeof ExecuteTeamRunFn>>> {
34
+ if (!_cachedExecuteTeamRun) {
35
+ const mod = await import("../runtime/team-runner.ts");
36
+ _cachedExecuteTeamRun = mod.executeTeamRun;
37
+ }
38
+ return _cachedExecuteTeamRun(...args);
39
+ }
30
40
  import { checkProcessLiveness, isActiveRunStatus } from "../runtime/process-status.ts";
31
41
  import { saveCrewAgents, readCrewAgents, recordFromTask } from "../runtime/crew-agent-records.ts";
32
42
  import { resolveCrewRuntime, runtimeResolutionState } from "../runtime/runtime-resolver.ts";
@@ -227,7 +237,8 @@ export async function handleTeamTool(params: TeamToolParamsValue, ctx: TeamConte
227
237
  case "get": return handleGet(params, ctx);
228
238
  case "init": {
229
239
  const cfg = configRecord(params.config);
230
- const initialized = initializeProject(ctx.cwd, { copyBuiltins: cfg.copyBuiltins === true, overwrite: cfg.overwrite === true, configScope: cfg.configScope === "project" || cfg.scope === "project" ? "project" : cfg.configScope === "none" || cfg.scope === "none" ? "none" : "global" });
240
+ const ignoreMethod = typeof cfg.ignoreMethod === "string" && (cfg.ignoreMethod === "gitignore" || cfg.ignoreMethod === "exclude") ? cfg.ignoreMethod : undefined;
241
+ const initialized = initializeProject(ctx.cwd, { copyBuiltins: cfg.copyBuiltins === true, overwrite: cfg.overwrite === true, configScope: cfg.configScope === "project" || cfg.scope === "project" ? "project" : cfg.configScope === "none" || cfg.scope === "none" ? "none" : "global", ignoreMethod });
231
242
  return result([
232
243
  "Initialized pi-crew project layout.",
233
244
  "Directories:",
@@ -236,7 +247,7 @@ export async function handleTeamTool(params: TeamToolParamsValue, ctx: TeamConte
236
247
  ...(initialized.copiedFiles.length ? initialized.copiedFiles.map((file) => `- ${file}`) : ["- (none)"]),
237
248
  ...(initialized.skippedFiles.length ? ["Skipped existing files:", ...initialized.skippedFiles.map((file) => `- ${file}`)] : []),
238
249
  `Config: ${initialized.configPath || "(none)"} (${initialized.configScope}${initialized.configCreated ? "; created" : initialized.configSkipped ? "; already existed" : "; unchanged"})`,
239
- `Gitignore: ${initialized.gitignorePath} (${initialized.gitignoreUpdated ? "updated" : "already configured"})`,
250
+ `Ignore: ${initialized.gitignorePath} (${initialized.gitignoreUpdated ? "updated" : "already configured"})`,
240
251
  ].join("\n"), { action: "init", status: "ok" });
241
252
  }
242
253
  case "help": return result(piTeamsHelp(), { action: "help", status: "ok" });