@melihmucuk/pi-crew 1.0.16 → 1.0.17

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 (35) hide show
  1. package/README.md +8 -8
  2. package/agents/code-reviewer.md +2 -2
  3. package/agents/oracle.md +1 -1
  4. package/agents/planner.md +5 -1
  5. package/agents/quality-reviewer.md +2 -2
  6. package/agents/scout.md +2 -2
  7. package/agents/worker.md +3 -3
  8. package/extension/agent-catalog.ts +369 -0
  9. package/extension/agent-config-fields.ts +359 -0
  10. package/extension/agent-discovery.ts +49 -717
  11. package/extension/index.ts +4 -2
  12. package/extension/integration/crew-tool-actions.ts +306 -0
  13. package/extension/integration/crew-tool-executor.ts +109 -0
  14. package/extension/integration/register-tools.ts +10 -2
  15. package/extension/integration/tool-presentation.ts +0 -20
  16. package/extension/integration/tools/crew-abort.ts +14 -84
  17. package/extension/integration/tools/crew-done.ts +7 -26
  18. package/extension/integration/tools/crew-list.ts +4 -60
  19. package/extension/integration/tools/crew-respond.ts +8 -29
  20. package/extension/integration/tools/crew-spawn.ts +15 -56
  21. package/extension/message-delivery-policy.ts +22 -0
  22. package/extension/runtime/crew-runtime.ts +60 -223
  23. package/extension/runtime/{delivery-coordinator.ts → owner-session-coordinator.ts} +44 -37
  24. package/extension/runtime/subagent-lifecycle.ts +203 -0
  25. package/extension/runtime/subagent-registry.ts +50 -6
  26. package/extension/runtime/subagent-transitions.ts +100 -0
  27. package/extension/subagent-messages.ts +9 -17
  28. package/package.json +8 -6
  29. package/prompts/pi-crew-plan.md +14 -13
  30. package/prompts/pi-crew-review.md +20 -16
  31. package/skills/pi-crew/REFERENCE.md +32 -20
  32. package/skills/pi-crew/SKILL.md +13 -10
  33. package/extension/integration/tools/tool-deps.ts +0 -16
  34. package/extension/integration.ts +0 -13
  35. package/extension/runtime/subagent-state.ts +0 -59
package/README.md CHANGED
@@ -86,7 +86,7 @@ Note: This prompt requires the `scout` and `planner` subagent definitions. These
86
86
  #### `/pi-crew-review`
87
87
 
88
88
  Expands a bundled prompt template that orchestrates parallel code and quality reviews.
89
- Use it to review recent commits, staged changes, unstaged changes, and untracked files with `code-reviewer` and `quality-reviewer`, then merge both results into one report.
89
+ Use it to review provided or default changed-code scope with `code-reviewer` and `quality-reviewer`, using compact task-specific briefs focused on intent, expected behavior, and relevant references, then merge both results into one report.
90
90
 
91
91
  Note: This prompt requires the `code-reviewer` and `quality-reviewer` subagent definitions. These are included as bundled subagents and work out of the box.
92
92
 
@@ -94,7 +94,7 @@ Note: This prompt requires the `code-reviewer` and `quality-reviewer` subagent d
94
94
 
95
95
  #### `pi-crew`
96
96
 
97
- A bundled orchestration skill that provides best practices for delegating work to subagents, handling asynchronous results, and managing interactive subagent lifecycle. It loads automatically when you coordinate work with pi-crew tools.
97
+ A bundled orchestration skill that provides best practices for delegating work to subagents, handling asynchronous results, and managing interactive subagent lifecycle. It also guides compact, task-specific subagent briefs that avoid repeating role boilerplate or mechanical repo/Git state. It loads automatically when you coordinate work with pi-crew tools.
98
98
 
99
99
  ## Bundled Subagents
100
100
 
@@ -102,12 +102,12 @@ pi-crew ships with six subagent definitions that cover common workflows:
102
102
 
103
103
  | Subagent | Purpose | Tools | Model |
104
104
  | -------------------- | ------------------------------------------------------------------------------------------------------------------------ | -------------------------- | --------------------------- |
105
- | **scout** | Investigates codebase and returns structured findings. Read-only. Use before planning or implementing to gather context. | read, grep, find, ls, bash | anthropic/claude-haiku-4-5 |
106
- | **planner** | Analyzes requirements and produces a step-by-step implementation plan. Read-only. Does not write code. Interactive. | read, grep, find, ls, bash | openai-codex/gpt-5.4 |
107
- | **oracle** | Evaluates critical decisions, surfaces blind spots, and challenges assumptions. Read-only. Does not implement. Interactive. | read, grep, find, ls, bash | openai-codex/gpt-5.4 |
108
- | **code-reviewer** | Reviews code changes for bugs, security issues, and correctness. Read-only. Does not fix issues. | read, grep, find, ls, bash | openai-codex/gpt-5.4 |
109
- | **quality-reviewer** | Reviews code structure for maintainability, duplication, and complexity. Read-only. Does not look for bugs. | read, grep, find, ls, bash | openai-codex/gpt-5.4 |
110
- | **worker** | Implements code changes, fixes, and refactors autonomously. Has full read-write access to the codebase. | all | anthropic/claude-sonnet-4-6 |
105
+ | **scout** | Investigates codebase and returns structured findings. Read-only. Use before planning or implementing to gather context. | read, grep, find, ls, bash | openai-codex/gpt-5.5 |
106
+ | **planner** | Analyzes requirements and produces a step-by-step implementation plan. Read-only. Does not write code. Interactive. | read, grep, find, ls, bash | openai-codex/gpt-5.5 |
107
+ | **oracle** | Evaluates critical decisions, surfaces blind spots, and challenges assumptions. Read-only. Does not implement. Interactive. | read, grep, find, ls, bash | openai-codex/gpt-5.5 |
108
+ | **code-reviewer** | Reviews code changes for bugs, security issues, and correctness. Read-only. Does not fix issues. | read, grep, find, ls, bash | openai-codex/gpt-5.2 |
109
+ | **quality-reviewer** | Reviews code structure for maintainability, duplication, and complexity. Read-only. Does not look for bugs. | read, grep, find, ls, bash | openai-codex/gpt-5.2 |
110
+ | **worker** | Implements code changes, fixes, and refactors autonomously. Has full read-write access to the codebase. | all | openai-codex/gpt-5.5 |
111
111
 
112
112
  Read-only bundled subagents still keep `bash` for inspection workflows like `git` and `ast-grep`. This is an instruction-level contract, not a sandbox boundary.
113
113
 
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: code-reviewer
3
3
  description: Reviews changed code for actionable bugs. Read-only.
4
- model: openai-codex/gpt-5.4
4
+ model: openai-codex/gpt-5.2
5
5
  thinking: high
6
6
  tools: read, grep, find, ls, bash
7
7
  ---
@@ -14,7 +14,7 @@ Do not modify files. Use bash only for read-only inspection. Do not run builds,
14
14
 
15
15
  Review the provided scope. If none is provided, review uncommitted changes. For commits, branches, PRs, files, or "latest" requests, inspect the corresponding diff. If "latest" is requested, review the last 5 commits unless a count is given.
16
16
 
17
- If the diff exceeds 500 lines, list changed files with one-line risk notes, then deeply review only the highest-risk files: business logic, auth, data mutation, error handling, public APIs.
17
+ For large or broad diffs, summarize coverage by area with brief risk notes, then deeply review only the highest-risk changed files: business logic, auth, data mutation, error handling, and public APIs. Avoid exhaustive file inventories.
18
18
 
19
19
  Review changed-code issues only. Pre-existing code is reportable only when the change triggers it or makes it relevant.
20
20
 
package/agents/oracle.md CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: oracle
3
3
  description: Evaluates critical decisions, surfaces blind spots, and challenges assumptions. Read-only.
4
- model: openai-codex/gpt-5.4
4
+ model: openai-codex/gpt-5.5
5
5
  thinking: xhigh
6
6
  tools: read, grep, find, ls, bash
7
7
  interactive: true
package/agents/planner.md CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: planner
3
3
  description: Produces deterministic implementation plans. Read-only. Does not write code.
4
- model: openai-codex/gpt-5.4
4
+ model: openai-codex/gpt-5.5
5
5
  thinking: high
6
6
  tools: read, grep, find, ls, bash
7
7
  interactive: true
@@ -50,9 +50,11 @@ Use exactly these sections:
50
50
  1. `# Plan – <Short Title>`
51
51
 
52
52
  2. `## What`
53
+
53
54
  - Brief technical restatement of the change.
54
55
 
55
56
  3. `## How`
57
+
56
58
  - High-level approach.
57
59
  - **Scope**: in scope, out of scope, and scope assumptions.
58
60
  - **Assumptions**: list assumptions or `None`.
@@ -60,6 +62,7 @@ Use exactly these sections:
60
62
  - Key constraints/trade-offs, only if relevant.
61
63
 
62
64
  4. `## TODO`
65
+
63
66
  - File-oriented steps in dependency order.
64
67
  - Each step starts with `Create`, `Add`, `Update`, `Remove`, `Refactor`, or `Move`.
65
68
  - Name the file path and concrete identifiers.
@@ -68,6 +71,7 @@ Use exactly these sections:
68
71
  - If TODO exceeds 20 steps, split into phases, mark the first implementation phase, and re-check for scope creep.
69
72
 
70
73
  5. `## Outcome`
74
+
71
75
  - Expected end state.
72
76
  - Functional criteria.
73
77
  - Relevant non-functional criteria.
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: quality-reviewer
3
3
  description: Reviews changed code for maintainability, duplication, and complexity. Read-only.
4
- model: openai-codex/gpt-5.4
4
+ model: openai-codex/gpt-5.2
5
5
  thinking: high
6
6
  tools: read, grep, find, ls, bash
7
7
  ---
@@ -18,7 +18,7 @@ Review the provided scope. If none is provided, review uncommitted changes. For
18
18
 
19
19
  If "full" or "codebase" is requested, first produce a structural risk map, then deeply review only the highest-risk areas.
20
20
 
21
- If the scope exceeds 15 files, summarize files with one-line structural notes, then deeply review the highest-risk files: large files, dependency-heavy files, widely imported files, or files crossing module boundaries. State skipped files briefly.
21
+ For large or broad scopes, summarize coverage by area with brief structural notes, then deeply review the highest-risk areas/files: large files, dependency-heavy files, widely imported files, or files crossing module boundaries. Avoid exhaustive file inventories; state skipped areas briefly.
22
22
 
23
23
  ## Method
24
24
 
package/agents/scout.md CHANGED
@@ -1,8 +1,8 @@
1
1
  ---
2
2
  name: scout
3
3
  description: Investigates codebase and returns structured findings. Read-only.
4
- model: anthropic/claude-haiku-4-5
5
- thinking: minimal
4
+ model: openai-codex/gpt-5.5
5
+ thinking: off
6
6
  tools: read, grep, find, ls, bash
7
7
  ---
8
8
 
package/agents/worker.md CHANGED
@@ -1,8 +1,8 @@
1
1
  ---
2
2
  name: worker
3
3
  description: Implements scoped code changes safely and verifies them.
4
- model: anthropic/claude-sonnet-4-6
5
- thinking: medium
4
+ model: openai-codex/gpt-5.5
5
+ thinking: low
6
6
  ---
7
7
 
8
8
  You are a worker agent. Implement the assigned task or plan as small, safe, verifiable code changes. Reply in the user's language.
@@ -25,7 +25,7 @@ Before changing code, gather enough context to act safely: project conventions,
25
25
 
26
26
  ## Verification
27
27
 
28
- Run relevant verification: lint, typecheck, tests, and build as applicable. If a relevant check cannot be run, state why.
28
+ Run the smallest meaningful verification for the change; use broader lint, typecheck, tests, or build only when relevant. If a relevant check cannot be run, state why.
29
29
 
30
30
  Fix only failures caused by your changes. Do not fix pre-existing failures; report them with evidence. If you cannot tell whether a failure is pre-existing or caused by your change, report it as a blocker.
31
31
 
@@ -0,0 +1,369 @@
1
+ import { parseFrontmatter } from "@earendil-works/pi-coding-agent";
2
+ import {
3
+ type AgentConfigFields,
4
+ parseDefinitionFields,
5
+ parseOverrideFields as parseConfigOverrideFields,
6
+ } from "./agent-config-fields.js";
7
+
8
+ export interface AgentConfig extends AgentConfigFields {
9
+ name: string;
10
+ description: string;
11
+ systemPrompt: string;
12
+ filePath: string;
13
+ }
14
+
15
+ type AgentConfigOverride = AgentConfigFields;
16
+
17
+ export interface AgentDiscoveryWarning {
18
+ filePath: string;
19
+ message: string;
20
+ }
21
+
22
+ export interface AgentDiscoveryResult {
23
+ agents: AgentConfig[];
24
+ warnings: AgentDiscoveryWarning[];
25
+ }
26
+
27
+ interface ParseResult {
28
+ agent: AgentConfig | null;
29
+ warnings: AgentDiscoveryWarning[];
30
+ }
31
+
32
+ export interface AgentDefinitionFile {
33
+ filePath: string;
34
+ content: string | null;
35
+ warnings?: AgentDiscoveryWarning[];
36
+ }
37
+
38
+ export interface AgentDefinitionSourceGroup {
39
+ agentsDir: string;
40
+ files: AgentDefinitionFile[];
41
+ warnings?: AgentDiscoveryWarning[];
42
+ }
43
+
44
+ export interface AgentConfigFile {
45
+ filePath: string;
46
+ content: string | null;
47
+ warnings?: AgentDiscoveryWarning[];
48
+ }
49
+
50
+ export interface AgentCatalogSource {
51
+ loadAgentDefinitionGroups(cwd: string): AgentDefinitionSourceGroup[];
52
+ loadConfigFiles(cwd: string): AgentConfigFile[];
53
+ }
54
+
55
+ interface ConfigParseResult {
56
+ overrides: Record<string, AgentConfigOverride>;
57
+ overrideSources: Record<string, string>;
58
+ warnings: AgentDiscoveryWarning[];
59
+ }
60
+
61
+ function createDiscoveryWarning(filePath: string, message: string): AgentDiscoveryWarning {
62
+ return { filePath, message };
63
+ }
64
+
65
+ function parseAgentDefinition(content: string, filePath: string): ParseResult {
66
+ const warnings: AgentDiscoveryWarning[] = [];
67
+
68
+ let frontmatter: Record<string, unknown>;
69
+ let body: string;
70
+ try {
71
+ const parsed = parseFrontmatter<Record<string, unknown>>(content);
72
+ frontmatter = parsed.frontmatter;
73
+ body = parsed.body;
74
+ } catch (error) {
75
+ const reason = error instanceof Error ? error.message : String(error);
76
+ return {
77
+ agent: null,
78
+ warnings: [
79
+ createDiscoveryWarning(
80
+ filePath,
81
+ `Ignored invalid subagent definition. Frontmatter could not be parsed: ${reason}`,
82
+ ),
83
+ ],
84
+ };
85
+ }
86
+
87
+ const name = typeof frontmatter.name === "string" ? frontmatter.name.trim() : undefined;
88
+ const description = typeof frontmatter.description === "string" ? frontmatter.description.trim() : undefined;
89
+
90
+ if (!name || !description) {
91
+ return {
92
+ agent: null,
93
+ warnings: [
94
+ createDiscoveryWarning(
95
+ filePath,
96
+ 'Ignored invalid subagent definition. Required frontmatter fields "name" and "description" must be non-empty strings.',
97
+ ),
98
+ ],
99
+ };
100
+ }
101
+
102
+ if (/\s/.test(name)) {
103
+ return {
104
+ agent: null,
105
+ warnings: [
106
+ createDiscoveryWarning(
107
+ filePath,
108
+ `Ignored subagent definition "${name}". Subagent names cannot contain whitespace. Use "-" instead.`,
109
+ ),
110
+ ],
111
+ };
112
+ }
113
+
114
+ const parsedFields = parseDefinitionFields(frontmatter, filePath, name);
115
+ warnings.push(...parsedFields.warnings);
116
+
117
+ return {
118
+ agent: {
119
+ name,
120
+ description,
121
+ ...parsedFields.fields,
122
+ systemPrompt: body,
123
+ filePath,
124
+ },
125
+ warnings,
126
+ };
127
+ }
128
+
129
+ function parseAgentOverride(
130
+ agentName: string,
131
+ value: unknown,
132
+ filePath: string,
133
+ ): { override: AgentConfigOverride | null; warnings: AgentDiscoveryWarning[] } {
134
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
135
+ return {
136
+ override: null,
137
+ warnings: [
138
+ createDiscoveryWarning(
139
+ filePath,
140
+ `Subagent override "${agentName}" must be a JSON object, ignoring`,
141
+ ),
142
+ ],
143
+ };
144
+ }
145
+
146
+ const parsedFields = parseConfigOverrideFields(value as Record<string, unknown>, filePath, agentName);
147
+ return { override: parsedFields.fields, warnings: parsedFields.warnings };
148
+ }
149
+
150
+ function parseConfigFile(content: string, filePath: string): ConfigParseResult {
151
+ let parsed: unknown;
152
+ try {
153
+ parsed = JSON.parse(content);
154
+ } catch (error) {
155
+ const reason = error instanceof Error ? error.message : String(error);
156
+ return {
157
+ overrides: {},
158
+ overrideSources: {},
159
+ warnings: [
160
+ createDiscoveryWarning(
161
+ filePath,
162
+ `Ignored pi-crew config. JSON could not be parsed: ${reason}`,
163
+ ),
164
+ ],
165
+ };
166
+ }
167
+
168
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
169
+ return {
170
+ overrides: {},
171
+ overrideSources: {},
172
+ warnings: [
173
+ createDiscoveryWarning(
174
+ filePath,
175
+ "Ignored pi-crew config. Root value must be a JSON object.",
176
+ ),
177
+ ],
178
+ };
179
+ }
180
+
181
+ const root = parsed as Record<string, unknown>;
182
+ if (root.agents === undefined) {
183
+ return { overrides: {}, overrideSources: {}, warnings: [] };
184
+ }
185
+
186
+ if (!root.agents || typeof root.agents !== "object" || Array.isArray(root.agents)) {
187
+ return {
188
+ overrides: {},
189
+ overrideSources: {},
190
+ warnings: [
191
+ createDiscoveryWarning(
192
+ filePath,
193
+ 'Ignored pi-crew config. Field "agents" must be a JSON object.',
194
+ ),
195
+ ],
196
+ };
197
+ }
198
+
199
+ const overrides: Record<string, AgentConfigOverride> = {};
200
+ const overrideSources: Record<string, string> = {};
201
+ const warnings: AgentDiscoveryWarning[] = [];
202
+
203
+ for (const [agentName, value] of Object.entries(root.agents)) {
204
+ if (!agentName.trim()) {
205
+ warnings.push(
206
+ createDiscoveryWarning(
207
+ filePath,
208
+ "Ignored pi-crew config entry with empty subagent name.",
209
+ ),
210
+ );
211
+ continue;
212
+ }
213
+
214
+ const parsedOverride = parseAgentOverride(agentName, value, filePath);
215
+ warnings.push(...parsedOverride.warnings);
216
+ if (parsedOverride.override) {
217
+ overrides[agentName] = parsedOverride.override;
218
+ overrideSources[agentName] = filePath;
219
+ }
220
+ }
221
+
222
+ return { overrides, overrideSources, warnings };
223
+ }
224
+
225
+ function mergeConfigOverrides(
226
+ base: Record<string, AgentConfigOverride>,
227
+ override: Record<string, AgentConfigOverride>,
228
+ ): Record<string, AgentConfigOverride> {
229
+ const merged: Record<string, AgentConfigOverride> = { ...base };
230
+
231
+ for (const [agentName, agentOverride] of Object.entries(override)) {
232
+ merged[agentName] = {
233
+ ...(merged[agentName] ?? {}),
234
+ ...agentOverride,
235
+ };
236
+ }
237
+
238
+ return merged;
239
+ }
240
+
241
+ function mergeOverrideSources(
242
+ base: Record<string, string>,
243
+ override: Record<string, string>,
244
+ ): Record<string, string> {
245
+ return {
246
+ ...base,
247
+ ...override,
248
+ };
249
+ }
250
+
251
+ function applyAgentOverride(agent: AgentConfig, override: AgentConfigOverride): AgentConfig {
252
+ return {
253
+ ...agent,
254
+ ...(override.model !== undefined ? { model: override.model, parsedModel: override.parsedModel } : {}),
255
+ ...(override.thinking !== undefined ? { thinking: override.thinking } : {}),
256
+ ...(override.tools !== undefined ? { tools: override.tools } : {}),
257
+ ...(override.skills !== undefined ? { skills: override.skills } : {}),
258
+ ...(override.compaction !== undefined ? { compaction: override.compaction } : {}),
259
+ ...(override.interactive !== undefined ? { interactive: override.interactive } : {}),
260
+ };
261
+ }
262
+
263
+ function loadAgentDefinitionFromFile(file: AgentDefinitionFile): ParseResult {
264
+ if (!file.content) {
265
+ return { agent: null, warnings: file.warnings ?? [] };
266
+ }
267
+
268
+ const parsed = parseAgentDefinition(file.content, file.filePath);
269
+ return {
270
+ agent: parsed.agent,
271
+ warnings: [...(file.warnings ?? []), ...parsed.warnings],
272
+ };
273
+ }
274
+
275
+ function mergeConfigFiles(configFiles: AgentConfigFile[]): ConfigParseResult {
276
+ let overrides: Record<string, AgentConfigOverride> = {};
277
+ let overrideSources: Record<string, string> = {};
278
+ const warnings: AgentDiscoveryWarning[] = [];
279
+
280
+ for (const configFile of configFiles) {
281
+ warnings.push(...(configFile.warnings ?? []));
282
+ if (!configFile.content) continue;
283
+
284
+ const parsed = parseConfigFile(configFile.content, configFile.filePath);
285
+ overrides = mergeConfigOverrides(overrides, parsed.overrides);
286
+ overrideSources = mergeOverrideSources(overrideSources, parsed.overrideSources);
287
+ warnings.push(...parsed.warnings);
288
+ }
289
+
290
+ return { overrides, overrideSources, warnings };
291
+ }
292
+
293
+ export class AgentCatalog {
294
+ constructor(private readonly source: AgentCatalogSource) {}
295
+
296
+ discover(cwd: string = process.cwd()): AgentDiscoveryResult {
297
+ const agents: AgentConfig[] = [];
298
+ const warnings: AgentDiscoveryWarning[] = [];
299
+ const seenNames = new Map<string, string>();
300
+
301
+ for (const group of this.source.loadAgentDefinitionGroups(cwd)) {
302
+ this.loadAgentsFromGroup(group, seenNames, agents, warnings);
303
+ }
304
+
305
+ const configOverrides = mergeConfigFiles(this.source.loadConfigFiles(cwd));
306
+ warnings.push(...configOverrides.warnings);
307
+
308
+ const finalAgents = agents.map((agent) => {
309
+ const override = configOverrides.overrides[agent.name];
310
+ return override ? applyAgentOverride(agent, override) : agent;
311
+ });
312
+
313
+ for (const agentName of Object.keys(configOverrides.overrides)) {
314
+ if (!seenNames.has(agentName)) {
315
+ warnings.push(
316
+ createDiscoveryWarning(
317
+ configOverrides.overrideSources[agentName] ?? "pi-crew.json",
318
+ `Subagent override "${agentName}" does not match any discovered subagent, ignoring`,
319
+ ),
320
+ );
321
+ }
322
+ }
323
+
324
+ return { agents: finalAgents, warnings };
325
+ }
326
+
327
+ /**
328
+ * Loads agents from a single source group into the agents list.
329
+ * Skips agents whose name already exists in seenNames (higher-priority source wins).
330
+ * Within the same source group, duplicate names produce a warning.
331
+ */
332
+ private loadAgentsFromGroup(
333
+ group: AgentDefinitionSourceGroup,
334
+ seenNames: Map<string, string>,
335
+ agents: AgentConfig[],
336
+ warnings: AgentDiscoveryWarning[],
337
+ ): void {
338
+ warnings.push(...(group.warnings ?? []));
339
+
340
+ const groupNames = new Set<string>();
341
+
342
+ for (const file of group.files) {
343
+ const loaded = loadAgentDefinitionFromFile(file);
344
+ warnings.push(...loaded.warnings);
345
+ if (!loaded.agent) continue;
346
+
347
+ const { name } = loaded.agent;
348
+
349
+ // Duplicate within the same source group
350
+ if (groupNames.has(name)) {
351
+ warnings.push(
352
+ createDiscoveryWarning(
353
+ file.filePath,
354
+ `Duplicate subagent name "${name}" in ${group.agentsDir}, skipping`,
355
+ ),
356
+ );
357
+ continue;
358
+ }
359
+
360
+ groupNames.add(name);
361
+
362
+ // Higher-priority source already registered this name
363
+ if (seenNames.has(name)) continue;
364
+
365
+ seenNames.set(name, file.filePath);
366
+ agents.push(loaded.agent);
367
+ }
368
+ }
369
+ }