pi-crew 0.1.32 → 0.1.33

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-crew",
3
- "version": "0.1.32",
3
+ "version": "0.1.33",
4
4
  "description": "Pi extension for coordinated AI teams, workflows, worktrees, and async task orchestration",
5
5
  "author": "baphuongna",
6
6
  "license": "MIT",
@@ -210,6 +210,18 @@ function mergeConfig(base: PiTeamsConfig, override: PiTeamsConfig): PiTeamsConfi
210
210
  },
211
211
  };
212
212
  }
213
+ if (base.tools || override.tools) {
214
+ merged.tools = {
215
+ ...(base.tools ?? {}),
216
+ ...withoutUndefined((override.tools ?? {}) as Record<string, unknown>),
217
+ };
218
+ }
219
+ if (base.telemetry || override.telemetry) {
220
+ merged.telemetry = {
221
+ ...(base.telemetry ?? {}),
222
+ ...withoutUndefined((override.telemetry ?? {}) as Record<string, unknown>),
223
+ };
224
+ }
213
225
  if (merged.agents?.overrides && Object.keys(merged.agents.overrides).length === 0) delete merged.agents.overrides;
214
226
  return merged;
215
227
  }
@@ -409,6 +421,26 @@ function parseAgentsConfig(value: unknown): CrewAgentsConfig | undefined {
409
421
  return Object.values(agents).some((entry) => entry !== undefined) ? agents : undefined;
410
422
  }
411
423
 
424
+ function parseToolsConfig(value: unknown): CrewToolsConfig | undefined {
425
+ const obj = asRecord(value);
426
+ if (!obj) return undefined;
427
+ const tools: CrewToolsConfig = {
428
+ enableClaudeStyleAliases: parseWithSchema(Type.Boolean(), obj.enableClaudeStyleAliases),
429
+ enableSteer: parseWithSchema(Type.Boolean(), obj.enableSteer),
430
+ terminateOnForeground: parseWithSchema(Type.Boolean(), obj.terminateOnForeground),
431
+ };
432
+ return Object.values(tools).some((entry) => entry !== undefined) ? tools : undefined;
433
+ }
434
+
435
+ function parseTelemetryConfig(value: unknown): CrewTelemetryConfig | undefined {
436
+ const obj = asRecord(value);
437
+ if (!obj) return undefined;
438
+ const telemetry: CrewTelemetryConfig = {
439
+ enabled: parseWithSchema(Type.Boolean(), obj.enabled),
440
+ };
441
+ return Object.values(telemetry).some((entry) => entry !== undefined) ? telemetry : undefined;
442
+ }
443
+
412
444
  export function parseConfig(raw: unknown): PiTeamsConfig {
413
445
  const obj = asRecord(raw);
414
446
  if (!obj) return {};
@@ -423,6 +455,8 @@ export function parseConfig(raw: unknown): PiTeamsConfig {
423
455
  control: parseControlConfig(obj.control),
424
456
  worktree: parseWorktreeConfig(obj.worktree),
425
457
  agents: parseAgentsConfig(obj.agents),
458
+ tools: parseToolsConfig(obj.tools),
459
+ telemetry: parseTelemetryConfig(obj.telemetry),
426
460
  ui: parseUiConfig(obj.ui),
427
461
  };
428
462
  }
@@ -5,22 +5,61 @@ import { allTeams, discoverTeams } from "../teams/discover-teams.ts";
5
5
  import { allWorkflows, discoverWorkflows } from "../workflows/discover-workflows.ts";
6
6
 
7
7
  const DEFAULT_MAGIC_KEYWORDS: Record<string, string[]> = {
8
- implementation: ["autoteam", "team:", "implementation-team"],
8
+ implementation: ["autoteam", "team:", "implementation-team", "pi-crew", "dùng team", "use team"],
9
9
  review: ["review-team", "security review", "code review"],
10
10
  fastFix: ["fast-fix", "quick fix"],
11
11
  research: ["research-team", "deep research"],
12
12
  };
13
13
 
14
+ const BULLET_OR_NUMBERED_TASK_RE = /^\s*(?:[-*•]|\d+[.)])\s+\S+/;
15
+ const ACTIONABLE_TASK_TERMS: readonly string[] = Array.from(new Set([
16
+ "implement",
17
+ "refactor",
18
+ "migrate",
19
+ "fix",
20
+ "add",
21
+ "update",
22
+ "test",
23
+ "review",
24
+ "research",
25
+ "analyze",
26
+ "document",
27
+ "docs",
28
+ "sửa",
29
+ "thêm",
30
+ "cập nhật",
31
+ "kiểm thử",
32
+ "nghiên cứu",
33
+ "phân tích",
34
+ "viết docs",
35
+ ]));
36
+
14
37
  function mergeMagicKeywords(configured: Record<string, string[]> | undefined): Record<string, string[]> {
15
38
  return { ...DEFAULT_MAGIC_KEYWORDS, ...(configured ?? {}) };
16
39
  }
17
40
 
41
+ function actionableLineCount(prompt: string): number {
42
+ return prompt
43
+ .split(/\r?\n/)
44
+ .map((line) => line.trim())
45
+ .filter((line) => BULLET_OR_NUMBERED_TASK_RE.test(line) && ACTIONABLE_TASK_TERMS.some((term) => line.toLowerCase().includes(term)))
46
+ .length;
47
+ }
48
+
49
+ function hasTaskListSignal(prompt: string): boolean {
50
+ const lower = prompt.toLowerCase();
51
+ const bulletCount = prompt.split(/\r?\n/).filter((line) => BULLET_OR_NUMBERED_TASK_RE.test(line)).length;
52
+ const explicitList = ["các task", "danh sách task", "todo", "tasks sau", "task list", "làm lần lượt"].some((term) => lower.includes(term));
53
+ return bulletCount >= 3 || actionableLineCount(prompt) >= 2 || (explicitList && bulletCount >= 2);
54
+ }
55
+
18
56
  export function detectTeamIntent(prompt: string, config: PiTeamsAutonomousConfig = {}): string[] {
19
57
  const lower = prompt.toLowerCase();
20
58
  const matches: string[] = [];
21
59
  for (const [intent, keywords] of Object.entries(mergeMagicKeywords(config.magicKeywords))) {
22
60
  if (keywords.some((keyword) => lower.includes(keyword.toLowerCase()))) matches.push(intent);
23
61
  }
62
+ if (hasTaskListSignal(prompt) && !matches.includes("taskList")) matches.push("taskList");
24
63
  return matches;
25
64
  }
26
65
 
@@ -39,8 +78,15 @@ export function buildAutonomousPolicy(prompt: string, config: PiTeamsAutonomousC
39
78
  `Autonomy profile: ${effective.profile}.`,
40
79
  "You have access to the `team` tool for coordinated multi-agent work. Use it proactively when the task benefits from specialized roles, planning, review, verification, durable artifacts, async execution, or worktree isolation.",
41
80
  "",
81
+ "Decision framework (not keyword-only):",
82
+ "- Treat a user-supplied task list with 2+ actionable bullets/numbered items as a delegation candidate even when no pi-crew keyword appears.",
83
+ "- Prefer `team` when tasks span multiple files/subsystems, require sequencing, combine implementation + tests/docs/review, or need independent exploration before edits.",
84
+ "- If unsure whether subtasks conflict, call `team` with action='recommend' first instead of manually splitting work.",
85
+ "- For assisted/aggressive autonomy and non-trivial multi-task work, prefer a team run or plan over direct single-agent execution.",
86
+ "",
42
87
  "Use `team` automatically when:",
43
88
  "- The task spans multiple files, subsystems, or unclear code areas.",
89
+ "- The prompt contains a non-trivial task list, roadmap, checklist, migration plan, or ordered implementation plan.",
44
90
  "- The task requires planning before implementation.",
45
91
  "- The task asks for implementation plus tests, review, verification, migration, architecture, security review, or debugging.",
46
92
  "- The task would benefit from explorer/planner/executor/reviewer/verifier roles.",
@@ -48,6 +94,7 @@ export function buildAutonomousPolicy(prompt: string, config: PiTeamsAutonomousC
48
94
  "Do not use `team` when:",
49
95
  "- The user asks a simple factual question or tiny single-file edit.",
50
96
  "- The user explicitly asks you to work directly without delegation.",
97
+ "- The tasks clearly modify the same small file region and can be completed safer by one agent without parallel fanout.",
51
98
  "- The action is destructive (`delete`, `forget`, `prune`, forced cleanup) and the user has not explicitly confirmed it.",
52
99
  "",
53
100
  "Recommended mappings:",
@@ -60,9 +107,16 @@ export function buildAutonomousPolicy(prompt: string, config: PiTeamsAutonomousC
60
107
  "- Before claiming delegated work is complete, inspect the run with action='status' or action='summary'.",
61
108
  "- Unsure or risky work -> action='plan' first, then run the selected team.",
62
109
  "",
110
+ "Conflict-safe task splitting:",
111
+ "- Do not parallelize subtasks that may edit the same file, same symbol, same migration path, package manifest, lockfile, or generated schema unless a planner explicitly sequences them.",
112
+ "- For potential overlap, use plan/recommend first, assign one owner per file/symbol, and require workers to report intended changed files before editing.",
113
+ "- Prefer workspaceMode: 'worktree' for parallel implementation in clean git repositories, but still avoid merging overlapping edits without review.",
114
+ "- If workers discover overlap, blockers, missing requirements, or need leader decisions, they must use mailbox/status artifacts to ask the leader/orchestrator and pause risky edits.",
115
+ "- The leader should resolve conflicts by sequencing, narrowing scope, or reassigning ownership before continuing.",
116
+ "",
63
117
  asyncGuidance,
64
118
  worktreeGuidance,
65
- intents.length > 0 ? `Detected pi-crew routing keywords/intents in the user prompt: ${intents.join(", ")}. Consider the matching team workflow if appropriate.` : "No explicit pi-crew magic keyword was detected; decide based on task complexity and risk.",
119
+ intents.length > 0 ? `Detected pi-crew routing signals/intents in the user prompt: ${intents.join(", ")}. Consider the matching team workflow if appropriate.` : "No explicit pi-crew routing signal was detected; decide based on complexity, risk, task-list structure, and conflict potential.",
66
120
  ].join("\n");
67
121
  }
68
122
 
@@ -26,8 +26,8 @@ const REVIEW_TERMS = ["review", "audit", "security", "vulnerability", "diff", "p
26
26
  const RESEARCH_TERMS = ["research", "investigate", "compare", "analyze", "document", "docs", "explain", "architecture", "đọc sâu", "source", "projects"];
27
27
  const PARALLEL_RESEARCH_RE = /(?:đọc sâu|deep read|deep research|source audit|multiple projects|các project|pi-\*|source\/|@source)/i;
28
28
  const FAST_FIX_TERMS = ["quick fix", "fast-fix", "small bug", "typo", "one-line", "minor", "lint"];
29
- const IMPLEMENTATION_TERMS = ["implement", "refactor", "migrate", "feature", "tests", "test", "integration", "upgrade", "build", "create", "add"];
30
- const RISKY_TERMS = ["migration", "refactor", "large", "multiple", "parallel", "concurrent", "risky", "critical"];
29
+ const IMPLEMENTATION_TERMS = ["implement", "refactor", "migrate", "feature", "tests", "test", "integration", "upgrade", "build", "create", "add", "fix", "update", "sửa", "thêm", "cập nhật", "kiểm thử"];
30
+ const RISKY_TERMS = ["migration", "refactor", "large", "multiple", "parallel", "concurrent", "risky", "critical", "nhiều file", "nhiều task"];
31
31
  const NUMBERED_LINE_RE = /^\s*\d+[.)]\s+(.+)$/;
32
32
  const BULLETED_LINE_RE = /^\s*[-*•]\s+(.+)$/;
33
33
  const CONJUNCTION_SPLIT_RE = /\s+(?:and|,\s*and|,)\s+/i;
@@ -67,12 +67,14 @@ export function decomposeGoal(goal: string): { strategy: DecompositionStrategy;
67
67
  const subtask = makeSubtask(goal);
68
68
  return { strategy: "atomic", subtasks: [subtask], fanout: 1 };
69
69
  }
70
- if (lines.length >= 2 && lines.every((line) => NUMBERED_LINE_RE.test(line))) {
71
- const subtasks = lines.map((line) => makeSubtask(line.match(NUMBERED_LINE_RE)?.[1] ?? line));
70
+ const numberedLines = lines.map((line) => line.match(NUMBERED_LINE_RE)?.[1]).filter((line): line is string => line !== undefined);
71
+ if (numberedLines.length >= 2 && numberedLines.length >= lines.length - 1) {
72
+ const subtasks = numberedLines.map((line) => makeSubtask(line));
72
73
  return { strategy: "numbered", subtasks, fanout: subtasks.length };
73
74
  }
74
- if (lines.length >= 2 && lines.every((line) => BULLETED_LINE_RE.test(line))) {
75
- const subtasks = lines.map((line) => makeSubtask(line.match(BULLETED_LINE_RE)?.[1] ?? line));
75
+ const bulletedLines = lines.map((line) => line.match(BULLETED_LINE_RE)?.[1]).filter((line): line is string => line !== undefined);
76
+ if (bulletedLines.length >= 2 && bulletedLines.length >= lines.length - 1) {
77
+ const subtasks = bulletedLines.map((line) => makeSubtask(line));
76
78
  return { strategy: "bulleted", subtasks, fanout: subtasks.length };
77
79
  }
78
80
  if (lines.length === 1) {
@@ -94,6 +96,7 @@ function metadataMatches(goal: string, values: string[] | undefined): string[] {
94
96
  export function recommendTeam(goal: string, config: PiTeamsAutonomousConfig = {}, resources?: { teams?: TeamConfig[]; agents?: AgentConfig[] }): TeamRecommendation {
95
97
  const normalized = goal.toLowerCase();
96
98
  const intents = detectTeamIntent(goal, config);
99
+ const decomposition = decomposeGoal(goal);
97
100
  const reasons: string[] = [];
98
101
  let team: TeamRecommendation["team"] = "default";
99
102
  let workflow: TeamRecommendation["workflow"] = "default";
@@ -138,10 +141,15 @@ export function recommendTeam(goal: string, config: PiTeamsAutonomousConfig = {}
138
141
  workflow = "fast-fix";
139
142
  confidence = "high";
140
143
  reasons.push(`Small fix terms detected: ${fastFixMatches.join(", ") || "fast-fix intent"}.`);
144
+ } else if (intents.includes("taskList")) {
145
+ team = "implementation";
146
+ workflow = "implementation";
147
+ confidence = "high";
148
+ reasons.push(`Actionable multi-item task list detected (${decomposition.fanout} bullet${decomposition.fanout === 1 ? "" : "s"}); use coordinated implementation planning.`);
141
149
  } else if (intents.includes("implementation") || implementationMatches.length > 0) {
142
150
  team = "implementation";
143
151
  workflow = "implementation";
144
- confidence = implementationMatches.length >= 2 || riskyMatches.length > 0 ? "high" : "medium";
152
+ confidence = implementationMatches.length >= 2 || riskyMatches.length > 0 || decomposition.fanout >= 2 ? "high" : "medium";
145
153
  reasons.push(`Implementation terms detected: ${implementationMatches.join(", ") || "implementation intent"}.`);
146
154
  } else {
147
155
  action = "plan";
@@ -149,7 +157,7 @@ export function recommendTeam(goal: string, config: PiTeamsAutonomousConfig = {}
149
157
  reasons.push("No strong team-specific intent detected; start with planning/default discovery.");
150
158
  }
151
159
 
152
- const decomposition = decomposeGoal(goal);
160
+
153
161
  if (decomposition.strategy !== "atomic") reasons.push(`Goal decomposes into ${decomposition.subtasks.length} subtasks using ${decomposition.strategy} parsing.`);
154
162
  const async = config.preferAsyncForLongTasks === true && (wordCount(goal) > 24 || riskyMatches.length > 0 || implementationMatches.length >= 2 || decomposition.fanout >= 3);
155
163
  const workspaceMode = config.allowWorktreeSuggestion === false ? "single" : (riskyMatches.length > 0 && team === "implementation" ? "worktree" : "single");
@@ -43,12 +43,13 @@ export function buildTaskPacket(input: BuildTaskPacketInput): TaskPacket {
43
43
  branchPolicy: input.manifest.workspaceMode === "worktree" ? "Use the assigned task worktree and avoid modifying the leader checkout." : "Use the current checkout; do not create branches unless explicitly requested.",
44
44
  acceptanceTests: [],
45
45
  commitPolicy: "Do not commit unless explicitly requested by the user or workflow.",
46
- reportingContract: "Report changed files, verification evidence, blockers, and next recommended action.",
47
- escalationPolicy: "Stop and report if scope is ambiguous, destructive action is needed, permissions are missing, or verification cannot be completed.",
46
+ reportingContract: "Report intended/changed files, verification evidence, blockers, conflict risks, and next recommended action.",
47
+ escalationPolicy: "Stop and report if scope is ambiguous, destructive action is needed, permissions are missing, verification cannot be completed, or edits may overlap with another worker/task.",
48
48
  constraints: [
49
49
  "Stay within the assigned task scope.",
50
50
  "Do not claim completion without verification evidence.",
51
51
  "Use mailbox/API state for coordination when available.",
52
+ "Do not make overlapping edits to the same file/symbol without explicit leader sequencing or ownership guidance.",
52
53
  ],
53
54
  expectedArtifacts: ["prompt", "result", "verification"],
54
55
  verification: defaultVerificationContract(input.step),
@@ -66,6 +67,14 @@ export function validateTaskPacket(packet: TaskPacket): TaskPacketValidationResu
66
67
  if ((packet.scope === "module" || packet.scope === "single_file" || packet.scope === "custom") && !packet.scopePath?.trim()) {
67
68
  errors.push(`scopePath is required for scope '${packet.scope}'`);
68
69
  }
70
+ if (packet.constraints.length === 0) errors.push("constraints must contain at least one entry");
71
+ for (const [index, constraint] of packet.constraints.entries()) {
72
+ if (!constraint.trim()) errors.push(`constraints contains an empty value at index ${index}`);
73
+ }
74
+ if (packet.expectedArtifacts.length === 0) errors.push("expectedArtifacts must contain at least one entry");
75
+ for (const [index, artifact] of packet.expectedArtifacts.entries()) {
76
+ if (!artifact.trim()) errors.push(`expectedArtifacts contains an empty value at index ${index}`);
77
+ }
69
78
  for (const [index, test] of packet.acceptanceTests.entries()) {
70
79
  if (!test.trim()) errors.push(`acceptanceTests contains an empty value at index ${index}`);
71
80
  }
@@ -23,6 +23,9 @@ export function coordinationBridgeInstructions(task: TeamTaskState): string {
23
23
  `Mailbox target for this task: ${task.id}`,
24
24
  "Use the run mailbox contract for coordination with the leader/orchestrator:",
25
25
  "- If blocked or uncertain, report the blocker in your final result and, when mailbox tools/API are available, send an inbox/outbox message addressed to the leader.",
26
+ "- Ask the leader before editing when scope is ambiguous, requirements conflict, destructive action is needed, or you discover likely overlap with another task.",
27
+ "- Before making non-trivial edits, state intended changed files in your notes/result; if another worker may touch the same file/symbol, pause and request sequencing/ownership guidance.",
28
+ "- Do not resolve cross-worker conflicts silently. Escalate via mailbox/result with: file/symbol, conflicting task if known, proposed owner, and safest next step.",
26
29
  "- If nudged, answer with current status, blocker, or smallest next step.",
27
30
  "- Treat inherited/dependency context as reference-only; do not continue the parent conversation directly.",
28
31
  "- Completion handoff should include: DONE/FAILED, summary, changed/read files, verification evidence, and remaining risks.",