pi-crew 0.1.44 → 0.1.46

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 (103) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/README.md +5 -5
  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 +11 -11
  12. package/agents/writer.md +11 -11
  13. package/docs/next-upgrade-roadmap.md +733 -0
  14. package/docs/research-awesome-agent-skills-distillation.md +100 -0
  15. package/docs/research-oh-my-pi-distillation.md +322 -0
  16. package/docs/source-runtime-refactor-map.md +24 -0
  17. package/docs/usage.md +3 -3
  18. package/install.mjs +52 -8
  19. package/package.json +1 -1
  20. package/schema.json +2 -1
  21. package/skills/async-worker-recovery/SKILL.md +42 -0
  22. package/skills/context-artifact-hygiene/SKILL.md +52 -0
  23. package/skills/delegation-patterns/SKILL.md +54 -0
  24. package/skills/mailbox-interactive/SKILL.md +40 -0
  25. package/skills/model-routing-context/SKILL.md +39 -0
  26. package/skills/multi-perspective-review/SKILL.md +58 -0
  27. package/skills/observability-reliability/SKILL.md +41 -0
  28. package/skills/ownership-session-security/SKILL.md +41 -0
  29. package/skills/pi-extension-lifecycle/SKILL.md +39 -0
  30. package/skills/requirements-to-task-packet/SKILL.md +63 -0
  31. package/skills/resource-discovery-config/SKILL.md +41 -0
  32. package/skills/runtime-state-reader/SKILL.md +44 -0
  33. package/skills/secure-agent-orchestration-review/SKILL.md +45 -0
  34. package/skills/state-mutation-locking/SKILL.md +42 -0
  35. package/skills/systematic-debugging/SKILL.md +67 -0
  36. package/skills/ui-render-performance/SKILL.md +39 -0
  37. package/skills/verification-before-done/SKILL.md +57 -0
  38. package/skills/worktree-isolation/SKILL.md +39 -0
  39. package/src/agents/discover-agents.ts +12 -11
  40. package/src/config/config.ts +48 -24
  41. package/src/config/defaults.ts +14 -0
  42. package/src/extension/project-init.ts +62 -2
  43. package/src/extension/register.ts +19 -10
  44. package/src/extension/registration/commands.ts +49 -26
  45. package/src/extension/registration/subagent-helpers.ts +8 -0
  46. package/src/extension/registration/subagent-tools.ts +2 -1
  47. package/src/extension/registration/team-tool.ts +28 -8
  48. package/src/extension/run-index.ts +13 -5
  49. package/src/extension/run-maintenance.ts +22 -3
  50. package/src/extension/team-tool/api.ts +25 -8
  51. package/src/extension/team-tool/cancel.ts +134 -102
  52. package/src/extension/team-tool/context.ts +6 -0
  53. package/src/extension/team-tool/lifecycle-actions.ts +17 -5
  54. package/src/extension/team-tool/respond.ts +103 -66
  55. package/src/extension/team-tool/run.ts +53 -10
  56. package/src/extension/team-tool/status.ts +12 -1
  57. package/src/extension/team-tool-types.ts +2 -0
  58. package/src/extension/team-tool.ts +32 -11
  59. package/src/observability/event-to-metric.ts +8 -1
  60. package/src/runtime/background-runner.ts +10 -4
  61. package/src/runtime/cancellation.ts +51 -0
  62. package/src/runtime/child-pi.ts +17 -4
  63. package/src/runtime/crash-recovery.ts +1 -0
  64. package/src/runtime/crew-agent-records.ts +41 -1
  65. package/src/runtime/deadletter.ts +1 -0
  66. package/src/runtime/delivery-coordinator.ts +174 -142
  67. package/src/runtime/effectiveness.ts +76 -0
  68. package/src/runtime/live-agent-control.ts +2 -1
  69. package/src/runtime/live-agent-manager.ts +20 -2
  70. package/src/runtime/live-control-realtime.ts +1 -1
  71. package/src/runtime/live-session-runtime.ts +5 -1
  72. package/src/runtime/manifest-cache.ts +17 -2
  73. package/src/runtime/model-fallback.ts +6 -4
  74. package/src/runtime/overflow-recovery.ts +175 -156
  75. package/src/runtime/pi-args.ts +18 -3
  76. package/src/runtime/process-status.ts +5 -1
  77. package/src/runtime/retry-executor.ts +26 -9
  78. package/src/runtime/runtime-resolver.ts +22 -6
  79. package/src/runtime/skill-instructions.ts +222 -0
  80. package/src/runtime/stale-reconciler.ts +189 -179
  81. package/src/runtime/subagent-manager.ts +3 -0
  82. package/src/runtime/task-runner/capabilities.ts +78 -0
  83. package/src/runtime/task-runner/live-executor.ts +4 -0
  84. package/src/runtime/task-runner/prompt-builder.ts +3 -1
  85. package/src/runtime/task-runner/prompt-pipeline.ts +64 -0
  86. package/src/runtime/task-runner.ts +44 -5
  87. package/src/runtime/team-runner.ts +91 -19
  88. package/src/schema/config-schema.ts +1 -0
  89. package/src/schema/team-tool-schema.ts +3 -3
  90. package/src/state/active-run-registry.ts +165 -0
  91. package/src/state/contracts.ts +1 -1
  92. package/src/state/mailbox.ts +44 -4
  93. package/src/state/state-store.ts +51 -1
  94. package/src/state/types.ts +46 -2
  95. package/src/teams/team-config.ts +1 -0
  96. package/src/ui/crew-widget.ts +9 -4
  97. package/src/ui/dashboard-panes/mailbox-pane.ts +2 -1
  98. package/src/ui/dashboard-panes/progress-pane.ts +2 -0
  99. package/src/ui/powerbar-publisher.ts +1 -1
  100. package/src/ui/run-snapshot-cache.ts +66 -39
  101. package/src/ui/snapshot-types.ts +7 -0
  102. package/src/utils/paths.ts +4 -2
  103. package/src/workflows/workflow-config.ts +1 -0
@@ -0,0 +1,57 @@
1
+ ---
2
+ name: verification-before-done
3
+ description: Use when about to claim work is complete, fixed, passing, reviewed, committed, or ready to hand off.
4
+ ---
5
+
6
+ # verification-before-done
7
+
8
+ Core principle: evidence before claims. A worker report, green-looking log, or previous run is not fresh verification.
9
+
10
+ Distilled from detailed reads of agent-skill patterns for verification-before-completion, TDD, review reception, and QA workflows.
11
+
12
+ ## Gate Function
13
+
14
+ Before any completion claim:
15
+
16
+ 1. Identify the command or inspection that proves the claim.
17
+ 2. Run the full command fresh, or explicitly state why a command cannot be run.
18
+ 3. Read the output, including exit code and failure counts.
19
+ 4. Compare the output to the claim.
20
+ 5. Report the claim only with the evidence.
21
+
22
+ ## Claim-to-Evidence Table
23
+
24
+ | Claim | Requires | Not sufficient |
25
+ |---|---|---|
26
+ | Tests pass | Fresh test output with zero failures | Prior run, “should pass” |
27
+ | Typecheck passes | Typecheck command exit 0 | Lint or targeted tests only |
28
+ | Bug fixed | Original symptom/regression test passes | Code changed |
29
+ | Requirements met | Checklist against request/plan | Generic test success |
30
+ | Agent completed | Worker output plus artifact/diff/state inspection | Worker says DONE |
31
+ | Safe to commit | Relevant checks pass and status reviewed | Partial local confidence |
32
+
33
+ ## Verification Ladder
34
+
35
+ Choose the smallest reliable gate, then escalate when risk requires it:
36
+
37
+ 1. Read-only inspection for plans/reviews.
38
+ 2. Targeted unit test for touched behavior.
39
+ 3. Typecheck for TypeScript/schema/API changes.
40
+ 4. Integration test for runtime, subprocess, state, filesystem, UI, config, or session behavior.
41
+ 5. Full suite before commit/release or broad changes.
42
+ 6. Real Pi smoke only when safe and needed.
43
+
44
+ ## Done Report
45
+
46
+ Include:
47
+
48
+ - changed files or read-only status;
49
+ - commands run and pass/fail result;
50
+ - artifacts, run IDs, logs, or state paths inspected;
51
+ - behavior actually verified;
52
+ - skipped checks and why;
53
+ - risks and rollback notes.
54
+
55
+ ## Red Flags
56
+
57
+ Stop before saying done if you are using words like “should”, “probably”, “looks”, “seems”, “I think”, or if you are trusting an agent report without checking evidence.
@@ -0,0 +1,39 @@
1
+ ---
2
+ name: worktree-isolation
3
+ description: Conflict-safe git worktree workflow. Use when running parallel implementation workers, isolating risky edits, or cleaning up task worktrees.
4
+ ---
5
+
6
+ # worktree-isolation
7
+
8
+ Use this skill for worktree-based execution or cleanup.
9
+
10
+ ## Source patterns distilled
11
+
12
+ - pi-subagents worktree runner and cleanup patterns
13
+ - pi-crew worktrees: `src/worktree/worktree-manager.ts`, `src/worktree/cleanup.ts`, `src/worktree/branch-freshness.ts`
14
+ - Team runner workspace mode: `src/runtime/team-runner.ts`, workflow/team resource fields
15
+
16
+ ## Rules
17
+
18
+ - Use worktree mode for parallel or risky code-changing tasks when the repository is clean enough and merge ownership is clear.
19
+ - Assign one owner per file/symbol/migration path to avoid conflict-heavy merges.
20
+ - Name branches/worktrees deterministically from run/task IDs; avoid user-controlled path fragments without sanitization.
21
+ - Before cleanup, check dirty state. Preserve dirty worktrees unless `force` is explicitly set.
22
+ - Record worktree paths and branch metadata in artifacts/events so the operator can inspect or recover.
23
+ - Do not run destructive git operations without explicit confirmation and evidence of target path containment.
24
+
25
+ ## Anti-patterns
26
+
27
+ - Parallel editing the same file in multiple worktrees without a merge plan.
28
+ - Force-removing dirty worktrees by default.
29
+ - Reusing stale worktrees after the base branch has moved without freshness checks.
30
+ - Storing worktrees outside the intended contained workspace root.
31
+
32
+ ## Verification
33
+
34
+ ```bash
35
+ cd pi-crew
36
+ npx tsc --noEmit
37
+ node --experimental-strip-types --test test/integration/worktree-mode.test.ts test/unit/run-index.test.ts
38
+ npm test
39
+ ```
@@ -1,7 +1,7 @@
1
1
  import * as fs from "node:fs";
2
2
  import * as path from "node:path";
3
3
  import type { AgentConfig, ResourceSource } from "./agent-config.ts";
4
- import { loadConfig } from "../config/config.ts";
4
+ import { loadConfig, type LoadedPiTeamsConfig } from "../config/config.ts";
5
5
  import { parseCsv, parseFrontmatter } from "../utils/frontmatter.ts";
6
6
  import { packageRoot, projectCrewRoot, userPiRoot } from "../utils/paths.ts";
7
7
 
@@ -36,9 +36,9 @@ function parseAgentFile(filePath: string, source: ResourceSource): AgentConfig |
36
36
  source,
37
37
  filePath,
38
38
  systemPrompt: body.trim(),
39
- model: frontmatter.model || undefined,
39
+ model: frontmatter.model === "false" ? undefined : frontmatter.model || undefined,
40
40
  fallbackModels: parseCsv(frontmatter.fallbackModels),
41
- thinking: frontmatter.thinking || undefined,
41
+ thinking: frontmatter.thinking === "false" ? undefined : frontmatter.thinking || undefined,
42
42
  tools: parseCsv(frontmatter.tools),
43
43
  extensions: frontmatter.extensions === "" ? [] : parseCsv(frontmatter.extensions),
44
44
  skills: parseCsv(frontmatter.skills ?? frontmatter.skill),
@@ -63,12 +63,12 @@ function readAgentDir(dir: string, source: ResourceSource): AgentConfig[] {
63
63
  .sort((a, b) => a.name.localeCompare(b.name));
64
64
  }
65
65
 
66
- function applyAgentOverrides(agents: AgentConfig[], cwd: string): AgentConfig[] {
67
- const loaded = loadConfig(cwd);
68
- const config = loaded.config.agents;
69
- const overrides = config?.overrides ?? {};
66
+ function applyAgentOverrides(agents: AgentConfig[], cwd: string, loadedConfig?: LoadedPiTeamsConfig): AgentConfig[] {
67
+ const loaded = loadedConfig ?? loadConfig(cwd);
68
+ const agentsConfig = loaded.config.agents;
69
+ const overrides = agentsConfig?.overrides ?? {};
70
70
  return agents
71
- .filter((agent) => !(config?.disableBuiltins && agent.source === "builtin"))
71
+ .filter((agent) => !(agentsConfig?.disableBuiltins && agent.source === "builtin"))
72
72
  .map((agent) => {
73
73
  const overrideEntry = Object.entries(overrides).find(([name]) => name.toLowerCase() === agent.name.toLowerCase());
74
74
  if (!overrideEntry) return agent;
@@ -87,10 +87,11 @@ function applyAgentOverrides(agents: AgentConfig[], cwd: string): AgentConfig[]
87
87
  }
88
88
 
89
89
  export function discoverAgents(cwd: string): AgentDiscoveryResult {
90
+ const loaded = loadConfig(cwd);
90
91
  return {
91
- builtin: applyAgentOverrides(readAgentDir(path.join(packageRoot(), "agents"), "builtin"), cwd),
92
- user: applyAgentOverrides(readAgentDir(path.join(userPiRoot(), "agents"), "user"), cwd),
93
- project: applyAgentOverrides(readAgentDir(path.join(projectCrewRoot(cwd), "agents"), "project"), cwd),
92
+ builtin: applyAgentOverrides(readAgentDir(path.join(packageRoot(), "agents"), "builtin"), cwd, loaded),
93
+ user: applyAgentOverrides(readAgentDir(path.join(userPiRoot(), "agents"), "user"), cwd, loaded),
94
+ project: applyAgentOverrides(readAgentDir(path.join(projectCrewRoot(cwd), "agents"), "project"), cwd, loaded),
94
95
  };
95
96
  }
96
97
 
@@ -4,7 +4,7 @@ import * as fs from "node:fs";
4
4
  import * as os from "node:os";
5
5
  import * as path from "node:path";
6
6
  import { PiTeamsAutonomyProfileSchema, PiTeamsConfigSchema } from "../schema/config-schema.ts";
7
- import { projectCrewRoot } from "../utils/paths.ts";
7
+ import { projectCrewRoot, projectPiRoot } from "../utils/paths.ts";
8
8
 
9
9
  export type PiTeamsAutonomyProfile = "manual" | "suggested" | "assisted" | "aggressive";
10
10
 
@@ -31,6 +31,7 @@ export interface CrewLimitsConfig {
31
31
  export type CrewRuntimeMode = "auto" | "scaffold" | "child-process" | "live-session";
32
32
 
33
33
  export type CompletionMutationGuardMode = "off" | "warn" | "fail";
34
+ export type EffectivenessGuardMode = "off" | "warn" | "block" | "fail";
34
35
 
35
36
  export interface CrewRuntimeConfig {
36
37
  mode?: CrewRuntimeMode;
@@ -44,6 +45,7 @@ export interface CrewRuntimeConfig {
44
45
  groupJoinAckTimeoutMs?: number;
45
46
  requirePlanApproval?: boolean;
46
47
  completionMutationGuard?: CompletionMutationGuardMode;
48
+ effectivenessGuard?: EffectivenessGuardMode;
47
49
  }
48
50
 
49
51
  export interface CrewControlConfig {
@@ -182,6 +184,11 @@ export interface UpdateConfigOptions {
182
184
  }
183
185
 
184
186
  export function configPath(): string {
187
+ const home = process.env.PI_TEAMS_HOME?.trim() || os.homedir();
188
+ return path.join(home, ".pi", "agent", "pi-crew.json");
189
+ }
190
+
191
+ export function legacyConfigPath(): string {
185
192
  const home = process.env.PI_TEAMS_HOME?.trim() || os.homedir();
186
193
  return path.join(home, ".pi", "agent", "extensions", "pi-crew", "config.json");
187
194
  }
@@ -195,7 +202,7 @@ export function projectConfigPath(cwd: string): string {
195
202
  * This is a convenience path alongside the standard `config.json` in crewRoot.
196
203
  */
197
204
  export function projectPiCrewJsonPath(cwd: string): string {
198
- return path.join(cwd, ".pi", "pi-crew.json");
205
+ return path.join(projectPiRoot(cwd), "pi-crew.json");
199
206
  }
200
207
 
201
208
  function withoutUndefined<T extends Record<string, unknown>>(value: T): Partial<T> {
@@ -509,6 +516,7 @@ function parseRuntimeConfig(value: unknown): CrewRuntimeConfig | undefined {
509
516
  groupJoinAckTimeoutMs: parsePositiveInteger(obj.groupJoinAckTimeoutMs, 86_400_000),
510
517
  requirePlanApproval: parseWithSchema(Type.Boolean(), obj.requirePlanApproval),
511
518
  completionMutationGuard: parseWithSchema(Type.Union([Type.Literal("off"), Type.Literal("warn"), Type.Literal("fail")]), obj.completionMutationGuard),
519
+ effectivenessGuard: parseWithSchema(Type.Union([Type.Literal("off"), Type.Literal("warn"), Type.Literal("block"), Type.Literal("fail")]), obj.effectivenessGuard),
512
520
  };
513
521
  return Object.values(runtime).some((entry) => entry !== undefined) ? runtime : undefined;
514
522
  }
@@ -727,35 +735,51 @@ function readConfigRecord(filePath: string): Record<string, unknown> {
727
735
  return raw as Record<string, unknown>;
728
736
  }
729
737
 
738
+ function readOptionalConfig(filePath: string): { exists: boolean; config: PiTeamsConfig; warnings: string[] } {
739
+ if (!fs.existsSync(filePath)) return { exists: false, config: {}, warnings: [] };
740
+ try {
741
+ const raw = readConfigRecord(filePath);
742
+ const parsed = parseConfigWithWarnings(raw);
743
+ return { exists: true, config: parsed.config, warnings: parsed.warnings.map((warning) => `${filePath}: ${warning}`) };
744
+ } catch (error) {
745
+ const message = error instanceof Error ? error.message : String(error);
746
+ return { exists: true, config: {}, warnings: [`${filePath}: invalid config ignored: ${message}`] };
747
+ }
748
+ }
749
+
730
750
  export function loadConfig(cwd?: string): LoadedPiTeamsConfig {
731
751
  const filePath = configPath();
752
+ const legacyPath = legacyConfigPath();
732
753
  const paths = cwd ? [filePath, projectConfigPath(cwd)] : [filePath];
733
- try {
734
- const userRaw = readConfigRecord(filePath);
735
- const userConfig = parseConfigWithWarnings(userRaw);
736
- let config = userConfig.config;
737
- const warnings: string[] = userConfig.warnings.map((warning) => `${filePath}: ${warning}`);
738
- if (cwd) {
739
- const projectPath = projectConfigPath(cwd);
740
- const projectConfig = parseConfigWithWarnings(readConfigRecord(projectPath));
754
+ const warnings: string[] = [];
755
+ const legacyConfig = readOptionalConfig(legacyPath);
756
+ if (legacyConfig.exists && legacyPath !== filePath) {
757
+ warnings.push(...legacyConfig.warnings);
758
+ paths.unshift(legacyPath);
759
+ }
760
+ const userConfig = readOptionalConfig(filePath);
761
+ warnings.push(...userConfig.warnings);
762
+ let config = mergeConfig(legacyConfig.exists && legacyPath !== filePath ? legacyConfig.config : {}, userConfig.config);
763
+ if (cwd) {
764
+ const projectPath = projectConfigPath(cwd);
765
+ const projectConfig = readOptionalConfig(projectPath);
766
+ if (projectConfig.exists) {
741
767
  const projectSafeConfig = sanitizeProjectConfig(projectPath, config, projectConfig.config);
742
- warnings.push(...projectConfig.warnings.map((warning) => `${projectPath}: ${warning}`), ...projectSafeConfig.warnings);
768
+ warnings.push(...projectConfig.warnings, ...projectSafeConfig.warnings);
743
769
  config = mergeConfig(config, projectSafeConfig.config);
744
- // Also load .pi/pi-crew.json from project root if it exists
745
- const piCrewJsonPath = projectPiCrewJsonPath(cwd);
746
- if (fs.existsSync(piCrewJsonPath)) {
747
- const piCrewJsonConfig = parseConfigWithWarnings(readConfigRecord(piCrewJsonPath));
748
- const piCrewJsonSafeConfig = sanitizeProjectConfig(piCrewJsonPath, config, piCrewJsonConfig.config);
749
- warnings.push(...piCrewJsonConfig.warnings.map((warning) => `${piCrewJsonPath}: ${warning}`), ...piCrewJsonSafeConfig.warnings);
750
- config = mergeConfig(config, piCrewJsonSafeConfig.config);
751
- paths.push(piCrewJsonPath);
752
- }
753
770
  }
754
- return { path: filePath, paths, config, warnings: warnings.length > 0 ? warnings : undefined };
755
- } catch (error) {
756
- const message = error instanceof Error ? error.message : String(error);
757
- return { path: filePath, paths, config: {}, error: message };
771
+ // `.pi/pi-crew.json` is the project-owned override file. If present and valid,
772
+ // it may override all pi-crew config fields, including agents.overrides.
773
+ // If missing or invalid, it is ignored and defaults/user config remain effective.
774
+ const piCrewJsonPath = projectPiCrewJsonPath(cwd);
775
+ const piCrewJsonConfig = readOptionalConfig(piCrewJsonPath);
776
+ if (piCrewJsonConfig.exists) {
777
+ warnings.push(...piCrewJsonConfig.warnings);
778
+ config = mergeConfig(config, piCrewJsonConfig.config);
779
+ paths.push(piCrewJsonPath);
780
+ }
758
781
  }
782
+ return { path: filePath, paths, config, warnings: warnings.length > 0 ? warnings : undefined };
759
783
  }
760
784
 
761
785
  export function updateConfig(patch: PiTeamsConfig, options: UpdateConfigOptions = {}): SavedPiTeamsConfig {
@@ -53,6 +53,20 @@ export const DEFAULT_UI = {
53
53
  refreshMs: 1000,
54
54
  notifierIntervalMs: 5000,
55
55
  widgetDefaultFrameMs: 1000,
56
+ widgetPlacement: "aboveEditor" as const,
57
+ widgetMaxLines: 8,
58
+ powerbar: true,
59
+ dashboardPlacement: "center" as const,
60
+ dashboardWidth: 72,
61
+ dashboardLiveRefreshMs: 1000,
62
+ autoOpenDashboard: false,
63
+ autoOpenDashboardForForegroundRuns: false,
64
+ showModel: true,
65
+ showTokens: true,
66
+ showTools: true,
67
+ transcriptTailBytes: 1024 * 1024,
68
+ mascotStyle: "cat" as const,
69
+ mascotEffect: "random" as const,
56
70
  };
57
71
 
58
72
  export const DEFAULT_NOTIFICATIONS = {
@@ -1,10 +1,13 @@
1
1
  import * as fs from "node:fs";
2
2
  import * as path from "node:path";
3
- import { packageRoot, projectCrewRoot } from "../utils/paths.ts";
3
+ import { configPath as globalConfigPath } from "../config/config.ts";
4
+ import { DEFAULT_UI } from "../config/defaults.ts";
5
+ import { packageRoot, projectCrewRoot, projectPiRoot } from "../utils/paths.ts";
4
6
 
5
7
  export interface ProjectInitOptions {
6
8
  copyBuiltins?: boolean;
7
9
  overwrite?: boolean;
10
+ configScope?: "global" | "project" | "none";
8
11
  }
9
12
 
10
13
  export interface ProjectInitResult {
@@ -13,6 +16,10 @@ export interface ProjectInitResult {
13
16
  skippedFiles: string[];
14
17
  gitignorePath: string;
15
18
  gitignoreUpdated: boolean;
19
+ configPath: string;
20
+ configScope: "global" | "project" | "none";
21
+ configCreated: boolean;
22
+ configSkipped: boolean;
16
23
  }
17
24
 
18
25
  function ensureDir(dir: string, createdDirs: string[]): void {
@@ -24,6 +31,44 @@ function ensureDir(dir: string, createdDirs: string[]): void {
24
31
  }
25
32
  }
26
33
 
34
+ const DEFAULT_PI_CREW_CONFIG = {
35
+ // Keep generated config non-invasive: do not set runtime/limits defaults here.
36
+ // Those are provided by pi-crew internals and should not make a normal workflow block.
37
+ autonomous: {
38
+ enabled: true,
39
+ injectPolicy: true,
40
+ preferAsyncForLongTasks: false,
41
+ allowWorktreeSuggestion: true,
42
+ },
43
+ agents: {
44
+ overrides: {
45
+ explorer: { model: false, thinking: "off" },
46
+ writer: { model: false, thinking: "off" },
47
+ planner: { model: false, thinking: "medium" },
48
+ analyst: { model: false, thinking: "off" },
49
+ critic: { model: false, thinking: "low" },
50
+ executor: { model: false, thinking: "medium" },
51
+ reviewer: { model: false, thinking: "off" },
52
+ "security-reviewer": { model: false, thinking: "medium" },
53
+ "test-engineer": { model: false, thinking: "low" },
54
+ verifier: { model: false, thinking: "off" },
55
+ },
56
+ },
57
+ ui: {
58
+ widgetPlacement: DEFAULT_UI.widgetPlacement,
59
+ widgetMaxLines: DEFAULT_UI.widgetMaxLines,
60
+ powerbar: DEFAULT_UI.powerbar,
61
+ dashboardPlacement: DEFAULT_UI.dashboardPlacement,
62
+ dashboardWidth: DEFAULT_UI.dashboardWidth,
63
+ dashboardLiveRefreshMs: DEFAULT_UI.dashboardLiveRefreshMs,
64
+ autoOpenDashboard: DEFAULT_UI.autoOpenDashboard,
65
+ autoOpenDashboardForForegroundRuns: DEFAULT_UI.autoOpenDashboardForForegroundRuns,
66
+ showModel: DEFAULT_UI.showModel,
67
+ showTokens: DEFAULT_UI.showTokens,
68
+ showTools: DEFAULT_UI.showTools,
69
+ },
70
+ };
71
+
27
72
  function copyBuiltinDir(kind: "agents" | "teams" | "workflows", targetDir: string, overwrite: boolean, copiedFiles: string[], skippedFiles: string[]): void {
28
73
  const sourceDir = path.join(packageRoot(), kind);
29
74
  if (!fs.existsSync(sourceDir)) return;
@@ -50,11 +95,26 @@ export function initializeProject(cwd: string, options: ProjectInitOptions = {})
50
95
  const agentsDir = path.join(crewRoot, "agents");
51
96
  const teamsDir = path.join(crewRoot, "teams");
52
97
  const workflowsDir = path.join(crewRoot, "workflows");
98
+ const configScope = options.configScope ?? "global";
99
+ const configPath = configScope === "project" ? path.join(projectPiRoot(cwd), "pi-crew.json") : configScope === "global" ? globalConfigPath() : "";
53
100
  ensureDir(agentsDir, createdDirs);
54
101
  ensureDir(teamsDir, createdDirs);
55
102
  ensureDir(workflowsDir, createdDirs);
56
103
  ensureDir(path.join(crewRoot, "imports"), createdDirs);
57
104
 
105
+ let configCreated = false;
106
+ let configSkipped = false;
107
+ if (configPath) {
108
+ if (configScope === "project") ensureDir(path.dirname(configPath), createdDirs);
109
+ else fs.mkdirSync(path.dirname(configPath), { recursive: true });
110
+ if (!fs.existsSync(configPath) || options.overwrite === true) {
111
+ fs.writeFileSync(configPath, `${JSON.stringify(DEFAULT_PI_CREW_CONFIG, null, 2)}\n`, "utf-8");
112
+ configCreated = true;
113
+ } else {
114
+ configSkipped = true;
115
+ }
116
+ }
117
+
58
118
  if (options.copyBuiltins) {
59
119
  copyBuiltinDir("agents", agentsDir, options.overwrite === true, copiedFiles, skippedFiles);
60
120
  copyBuiltinDir("teams", teamsDir, options.overwrite === true, copiedFiles, skippedFiles);
@@ -72,5 +132,5 @@ export function initializeProject(cwd: string, options: ProjectInitOptions = {})
72
132
  gitignoreUpdated = true;
73
133
  }
74
134
 
75
- return { createdDirs, copiedFiles, skippedFiles, gitignorePath, gitignoreUpdated };
135
+ return { createdDirs, copiedFiles, skippedFiles, gitignorePath, gitignoreUpdated, configPath, configScope, configCreated, configSkipped };
76
136
  }
@@ -1,6 +1,7 @@
1
1
  import type { ExtensionAPI, ExtensionContext } from "@mariozechner/pi-coding-agent";
2
2
  import * as fs from "node:fs";
3
3
  import * as path from "node:path";
4
+ import { fileURLToPath } from "node:url";
4
5
  import { loadConfig } from "../config/config.ts";
5
6
  import { registerAutonomousPolicy } from "./autonomous-policy.ts";
6
7
  import { startAsyncRunNotifier, stopAsyncRunNotifier, type AsyncNotifierState } from "./async-notifier.ts";
@@ -40,6 +41,7 @@ import { detectInterruptedRuns } from "../runtime/crash-recovery.ts";
40
41
  import { DeliveryCoordinator } from "../runtime/delivery-coordinator.ts";
41
42
  import { OverflowRecoveryTracker } from "../runtime/overflow-recovery.ts";
42
43
  import { tryRegisterSessionCleanup } from "../runtime/session-resources.ts";
44
+ import { createSessionSnapshot } from "../runtime/session-snapshot.ts";
43
45
  import { initI18n } from "../i18n.ts";
44
46
 
45
47
  export { __test__subagentSpawnParams };
@@ -252,18 +254,18 @@ export function registerPiTeams(pi: ExtensionAPI): void {
252
254
  const openLiveSidebar = (ctx: ExtensionContext, runId: string): void => {
253
255
  const uiConfig = loadConfig(ctx.cwd).config.ui;
254
256
  const autoOpen = uiConfig?.autoOpenDashboard === true;
255
- const foregroundAutoOpen = uiConfig?.autoOpenDashboardForForegroundRuns !== false;
256
- if (!ctx.hasUI || !autoOpen || !foregroundAutoOpen || (uiConfig?.dashboardPlacement ?? "right") !== "right") return;
257
+ const foregroundAutoOpen = uiConfig?.autoOpenDashboardForForegroundRuns ?? DEFAULT_UI.autoOpenDashboardForForegroundRuns;
258
+ if (!ctx.hasUI || !autoOpen || !foregroundAutoOpen || (uiConfig?.dashboardPlacement ?? DEFAULT_UI.dashboardPlacement) !== "right") return;
257
259
  if (liveSidebarRunId === runId) return;
258
260
  liveSidebarRunId = runId;
259
- const widgetPlacement = uiConfig?.widgetPlacement ?? "aboveEditor";
261
+ const widgetPlacement = uiConfig?.widgetPlacement ?? DEFAULT_UI.widgetPlacement;
260
262
  setExtensionWidget(ctx, "pi-crew", undefined, { placement: widgetPlacement });
261
263
  setExtensionWidget(ctx, "pi-crew-active", undefined, { placement: widgetPlacement });
262
264
  widgetState.lastVisibility = "hidden";
263
265
  widgetState.lastPlacement = widgetPlacement;
264
266
  widgetState.lastKey = "pi-crew-active";
265
267
  widgetState.model = undefined;
266
- const width = Math.min(90, Math.max(40, uiConfig?.dashboardWidth ?? 56));
268
+ const width = Math.min(90, Math.max(40, uiConfig?.dashboardWidth ?? DEFAULT_UI.dashboardWidth));
267
269
  void showCustom<undefined>(ctx, (_tui, theme, _keybindings, done) => new LiveRunSidebar({ cwd: ctx.cwd, runId, done, theme, config: uiConfig, snapshotCache: getRunSnapshotCache(ctx.cwd) }), {
268
270
  overlay: true,
269
271
  overlayOptions: { width, minWidth: 40, maxHeight: "100%", anchor: "top-right", offsetX: 0, offsetY: 0, margin: { top: 0, right: 0, bottom: 0, left: 0 }, visible: (termWidth: number) => termWidth >= 100 },
@@ -397,7 +399,7 @@ export function registerPiTeams(pi: ExtensionAPI): void {
397
399
  configureNotifications(ctx);
398
400
  configureObservability(ctx);
399
401
  configureDeliveryCoordinator();
400
- const sessionId = (ctx as unknown as Record<string, unknown>).sessionId;
402
+ const sessionId = ctx.sessionManager?.getSessionId?.() ?? (ctx as unknown as Record<string, unknown>).sessionId;
401
403
  if (typeof sessionId === "string" && sessionId) deliveryCoordinator?.activate(sessionId);
402
404
  tryRegisterSessionCleanup(pi, () => { terminateActiveChildPiProcesses(); cleanupRuntime(); });
403
405
  registerPiCrewPowerbarSegments(pi.events, loadedConfig.config.ui);
@@ -459,7 +461,7 @@ export function registerPiTeams(pi: ExtensionAPI): void {
459
461
  const snapshotCache = lastFrameSnapshotCache ?? getRunSnapshotCache(currentCtx.cwd);
460
462
  const manifests = lastPreloadedManifests.length > 0 ? lastPreloadedManifests : activeCache.list(20);
461
463
  if (liveSidebarRunId) {
462
- const placement = config?.widgetPlacement ?? "aboveEditor";
464
+ const placement = config?.widgetPlacement ?? DEFAULT_UI.widgetPlacement;
463
465
  if (widgetState.lastVisibility !== "hidden" || widgetState.lastPlacement !== placement) {
464
466
  setExtensionWidget(currentCtx, "pi-crew", undefined, { placement });
465
467
  setExtensionWidget(currentCtx, "pi-crew-active", undefined, { placement });
@@ -499,7 +501,7 @@ export function registerPiTeams(pi: ExtensionAPI): void {
499
501
  }
500
502
  };
501
503
 
502
- const fallbackMs = loadedConfig.config.ui?.dashboardLiveRefreshMs ?? 250;
504
+ const fallbackMs = loadedConfig.config.ui?.dashboardLiveRefreshMs ?? DEFAULT_UI.refreshMs;
503
505
  renderScheduler = new RenderScheduler(pi.events, renderTick, {
504
506
  fallbackMs,
505
507
  onInvalidate: () => getRunSnapshotCache(ctx.cwd).invalidate(),
@@ -509,8 +511,14 @@ export function registerPiTeams(pi: ExtensionAPI): void {
509
511
  });
510
512
  pi.on("session_before_switch", () => {
511
513
  sessionGeneration++;
512
- // Phase 11b: Capture state before session switch
513
514
  const pendingCount = deliveryCoordinator?.getPendingCount() ?? 0;
515
+ try {
516
+ const activeRuns = currentCtx ? getManifestCache(currentCtx.cwd).list(50).filter((run) => run.status === "running" || run.status === "queued" || run.status === "blocked") : [];
517
+ const snapshot = createSessionSnapshot(activeRuns, pendingCount, sessionGeneration);
518
+ if (pendingCount > 0 || snapshot.activeRunIds.length > 0) logInternalError("register.session-before-switch", undefined, JSON.stringify(snapshot));
519
+ } catch (error) {
520
+ logInternalError("register.session-before-switch.snapshot", error);
521
+ }
514
522
  if (pendingCount > 0) {
515
523
  logInternalError("register.session-before-switch", `Switching session with ${pendingCount} pending deliveries`);
516
524
  }
@@ -523,8 +531,9 @@ export function registerPiTeams(pi: ExtensionAPI): void {
523
531
  // Phase 11a: Dynamic resource discovery — inject pi-crew skill paths.
524
532
  try {
525
533
  pi.on("resources_discover", () => {
526
- const skillDir = path.resolve(process.cwd(), "skills");
527
- const extSkillDir = path.resolve(__dirname, "..", "..", "skills");
534
+ const sessionCwd = currentCtx?.cwd ?? process.cwd();
535
+ const skillDir = path.resolve(sessionCwd, "skills");
536
+ const extSkillDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", "..", "skills");
528
537
  const paths: string[] = [];
529
538
  if (fs.existsSync(extSkillDir)) paths.push(extSkillDir);
530
539
  if (skillDir !== extSkillDir && fs.existsSync(skillDir)) paths.push(skillDir);