@polymorphism-tech/morph-spec 2.4.0 → 3.0.1
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/CLAUDE.md +75 -239
- package/bin/morph-spec.js +8 -0
- package/content/.claude/commands/morph-deploy.md +529 -0
- package/content/.claude/skills/level-0-meta/README.md +7 -0
- package/content/.claude/skills/{checklists → level-0-meta}/morph-checklist.md +117 -117
- package/content/.claude/skills/level-1-workflows/README.md +7 -0
- package/content/.claude/skills/{workflows → level-1-workflows}/morph-replicate.md +213 -213
- package/content/.claude/skills/{workflows → level-1-workflows}/phase-clarify.md +131 -131
- package/content/.claude/skills/{workflows → level-1-workflows}/phase-design.md +213 -205
- package/content/.claude/skills/{workflows → level-1-workflows}/phase-setup.md +106 -92
- package/content/.claude/skills/{workflows → level-1-workflows}/phase-tasks.md +164 -164
- package/content/.claude/skills/{workflows → level-1-workflows}/phase-uiux.md +169 -138
- package/content/.claude/skills/level-2-domains/README.md +14 -0
- package/content/.claude/skills/level-2-domains/architecture/prompt-engineer.md +189 -0
- package/content/.claude/skills/level-2-domains/architecture/seo-growth-hacker.md +320 -0
- package/content/.claude/skills/level-2-domains/infrastructure/azure-deploy-specialist.md +699 -0
- package/content/.claude/skills/{specialists → level-2-domains/quality}/testing-specialist.md +126 -126
- package/content/.claude/skills/level-3-technologies/README.md +7 -0
- package/content/.claude/skills/level-4-patterns/README.md +7 -0
- package/content/.morph/config/agents.json +744 -358
- package/content/.morph/config/config.template.json +33 -0
- package/content/.morph/docs/workflows/enforcement-pipeline.md +668 -0
- package/content/.morph/examples/scheduled-reports/decisions.md +158 -158
- package/content/.morph/examples/scheduled-reports/proposal.md +95 -95
- package/content/.morph/examples/scheduled-reports/spec.md +267 -267
- package/content/.morph/hooks/README.md +158 -0
- package/content/.morph/hooks/task-completed.js +73 -0
- package/content/.morph/hooks/teammate-idle.js +68 -0
- package/content/.morph/schemas/tasks.schema.json +220 -220
- package/content/.morph/standards/agent-teams-workflow.md +474 -0
- package/content/.morph/templates/CONTEXT-FEATURE.md +276 -0
- package/content/.morph/templates/CONTEXT.md +170 -0
- package/content/.morph/templates/clarify-questions.md +159 -159
- package/content/.morph/templates/contracts/Commands.cs +74 -74
- package/content/.morph/templates/contracts/Entities.cs +25 -25
- package/content/.morph/templates/contracts/Queries.cs +74 -74
- package/content/.morph/templates/contracts/README.md +74 -74
- package/content/.morph/templates/infra/azure-pipelines-deploy.yml +480 -0
- package/package.json +1 -2
- package/scripts/reorganize-skills.cjs +175 -0
- package/scripts/validate-agents-structure.cjs +52 -0
- package/scripts/validate-skills.cjs +180 -0
- package/src/commands/advance-phase.js +83 -0
- package/src/commands/deploy.js +780 -0
- package/src/commands/detect-agents.js +43 -6
- package/src/commands/detect.js +1 -1
- package/src/commands/generate-context.js +40 -0
- package/src/commands/state.js +2 -1
- package/src/commands/sync.js +1 -1
- package/src/commands/update.js +13 -1
- package/src/lib/context-generator.js +513 -0
- package/src/lib/design-system-detector.js +187 -0
- package/src/lib/design-system-scaffolder.js +299 -0
- package/src/lib/hook-executor.js +256 -0
- package/src/lib/spec-validator.js +258 -0
- package/src/lib/standards-context-injector.js +287 -0
- package/src/lib/state-manager.js +21 -4
- package/src/lib/team-orchestrator.js +322 -0
- package/src/lib/validation-runner.js +65 -13
- package/src/lib/validators/design-system-validator.js +231 -0
- package/src/utils/file-copier.js +9 -1
- /package/content/.claude/skills/{checklists → level-0-meta}/code-review.md +0 -0
- /package/content/.claude/skills/{checklists → level-0-meta}/simulation-checklist.md +0 -0
- /package/content/.claude/skills/{specialists → level-2-domains/ai-agents}/ai-system-architect.md +0 -0
- /package/content/.claude/skills/{specialists → level-2-domains/architecture}/po-pm-advisor.md +0 -0
- /package/content/.claude/skills/{specialists → level-2-domains/architecture}/standards-architect.md +0 -0
- /package/content/.claude/skills/{specialists → level-2-domains/backend}/dotnet-senior.md +0 -0
- /package/content/.claude/skills/{specialists → level-2-domains/backend}/ef-modeler.md +0 -0
- /package/content/.claude/skills/{specialists → level-2-domains/backend}/hangfire-orchestrator.md +0 -0
- /package/content/.claude/skills/{specialists → level-2-domains/backend}/ms-agent-expert.md +0 -0
- /package/content/.claude/skills/{stacks/dotnet-blazor.md → level-2-domains/frontend/blazor-builder.md} +0 -0
- /package/content/.claude/skills/{stacks/dotnet-nextjs.md → level-2-domains/frontend/nextjs-expert.md} +0 -0
- /package/content/.claude/skills/{specialists → level-2-domains/frontend}/ui-ux-designer.md +0 -0
- /package/content/.claude/skills/{specialists → level-2-domains/infrastructure}/azure-architect.md +0 -0
- /package/content/.claude/skills/{infra → level-2-domains/infrastructure}/bicep-architect.md +0 -0
- /package/content/.claude/skills/{infra → level-2-domains/infrastructure}/container-specialist.md +0 -0
- /package/content/.claude/skills/{infra → level-2-domains/infrastructure}/devops-engineer.md +0 -0
- /package/content/.claude/skills/{integrations → level-2-domains/integrations}/asaas-financial.md +0 -0
- /package/content/.claude/skills/{integrations → level-2-domains/integrations}/azure-identity.md +0 -0
- /package/content/.claude/skills/{integrations → level-2-domains/integrations}/clerk-auth.md +0 -0
- /package/content/.claude/skills/{integrations → level-2-domains/integrations}/resend-email.md +0 -0
- /package/content/.claude/skills/{specialists → level-2-domains/quality}/code-analyzer.md +0 -0
- /package/{detectors → src/lib/detectors}/config-detector.js +0 -0
- /package/{detectors → src/lib/detectors}/conversation-analyzer.js +0 -0
- /package/{detectors → src/lib/detectors}/index.js +0 -0
- /package/{detectors → src/lib/detectors}/standards-generator.js +0 -0
- /package/{detectors → src/lib/detectors}/structure-detector.js +0 -0
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Standards Context Injector
|
|
3
|
+
*
|
|
4
|
+
* Automatically loads and surfaces relevant standards content for agents.
|
|
5
|
+
* Resolution order: project overrides → framework standards → content standards
|
|
6
|
+
*
|
|
7
|
+
* @module standards-context-injector
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { readFileSync, existsSync } from 'fs';
|
|
11
|
+
import { join } from 'path';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Agent → Standards mapping
|
|
15
|
+
* Maps each agent ID to relevant standard files (without .md extension)
|
|
16
|
+
*/
|
|
17
|
+
const AGENT_STANDARDS_MAP = {
|
|
18
|
+
// Tier 1: Orchestrators
|
|
19
|
+
'standards-architect': [
|
|
20
|
+
'architecture',
|
|
21
|
+
'coding',
|
|
22
|
+
'azure',
|
|
23
|
+
'dotnet10-compatibility',
|
|
24
|
+
'program-cs-checklist'
|
|
25
|
+
],
|
|
26
|
+
'ai-system-architect': [
|
|
27
|
+
'agent-framework-setup',
|
|
28
|
+
'agent-framework-workflows',
|
|
29
|
+
'agent-framework-production',
|
|
30
|
+
'agent-framework-blazor-ui',
|
|
31
|
+
'architecture',
|
|
32
|
+
'coding'
|
|
33
|
+
],
|
|
34
|
+
'popm-advisor': [], // No technical standards
|
|
35
|
+
|
|
36
|
+
// Tier 2: Domain Leaders
|
|
37
|
+
'dotnet-senior': [
|
|
38
|
+
'coding',
|
|
39
|
+
'architecture',
|
|
40
|
+
'dotnet10-compatibility',
|
|
41
|
+
'program-cs-checklist'
|
|
42
|
+
],
|
|
43
|
+
'azure-architect': [
|
|
44
|
+
'azure',
|
|
45
|
+
'architecture',
|
|
46
|
+
'dotnet10-compatibility'
|
|
47
|
+
],
|
|
48
|
+
'ui-designer': [
|
|
49
|
+
'css-naming',
|
|
50
|
+
'css-animations',
|
|
51
|
+
'fluent-ui-setup',
|
|
52
|
+
'fluent-ui-blazor'
|
|
53
|
+
],
|
|
54
|
+
|
|
55
|
+
// Tier 3: Specialists - Backend Squad
|
|
56
|
+
'ef-modeler': [
|
|
57
|
+
'blazor-efcore',
|
|
58
|
+
'architecture',
|
|
59
|
+
'coding',
|
|
60
|
+
'dotnet10-compatibility'
|
|
61
|
+
],
|
|
62
|
+
'event-architect': [
|
|
63
|
+
'architecture',
|
|
64
|
+
'coding',
|
|
65
|
+
'dotnet10-compatibility'
|
|
66
|
+
],
|
|
67
|
+
'api-designer': [
|
|
68
|
+
'architecture',
|
|
69
|
+
'coding',
|
|
70
|
+
'dotnet10-compatibility'
|
|
71
|
+
],
|
|
72
|
+
'nosql-cache-expert': [
|
|
73
|
+
'architecture',
|
|
74
|
+
'coding'
|
|
75
|
+
],
|
|
76
|
+
'ddd-expert': [
|
|
77
|
+
'architecture',
|
|
78
|
+
'coding'
|
|
79
|
+
],
|
|
80
|
+
'hangfire-orchestrator': [
|
|
81
|
+
'architecture',
|
|
82
|
+
'coding',
|
|
83
|
+
'blazor-lifecycle', // Background jobs + Blazor lifecycle
|
|
84
|
+
'dotnet10-compatibility'
|
|
85
|
+
],
|
|
86
|
+
'ms-agent-expert': [
|
|
87
|
+
'agent-framework-setup',
|
|
88
|
+
'agent-framework-workflows',
|
|
89
|
+
'agent-framework-production',
|
|
90
|
+
'agent-framework-blazor-ui',
|
|
91
|
+
'coding',
|
|
92
|
+
'architecture'
|
|
93
|
+
],
|
|
94
|
+
'asaas-financial': [
|
|
95
|
+
'coding',
|
|
96
|
+
'architecture'
|
|
97
|
+
],
|
|
98
|
+
'clerk-auth': [
|
|
99
|
+
'coding',
|
|
100
|
+
'architecture',
|
|
101
|
+
'passkeys-auth'
|
|
102
|
+
],
|
|
103
|
+
'resend-email': [
|
|
104
|
+
'coding',
|
|
105
|
+
'architecture'
|
|
106
|
+
],
|
|
107
|
+
|
|
108
|
+
// Tier 3: Specialists - Frontend Squad
|
|
109
|
+
'blazor-builder': [
|
|
110
|
+
'blazor-lifecycle',
|
|
111
|
+
'blazor-state',
|
|
112
|
+
'blazor-efcore',
|
|
113
|
+
'blazor-pitfalls',
|
|
114
|
+
'html-to-blazor',
|
|
115
|
+
'fluent-ui-blazor',
|
|
116
|
+
'css-naming',
|
|
117
|
+
'css-animations',
|
|
118
|
+
'coding',
|
|
119
|
+
'architecture'
|
|
120
|
+
],
|
|
121
|
+
'nextjs-expert': [
|
|
122
|
+
'coding'
|
|
123
|
+
],
|
|
124
|
+
'css-specialist': [
|
|
125
|
+
'css-naming',
|
|
126
|
+
'css-animations',
|
|
127
|
+
'fluent-ui-blazor'
|
|
128
|
+
],
|
|
129
|
+
|
|
130
|
+
// Tier 3: Specialists - Infrastructure Squad
|
|
131
|
+
'bicep-architect': [
|
|
132
|
+
'azure',
|
|
133
|
+
'architecture'
|
|
134
|
+
],
|
|
135
|
+
'devops-engineer': [
|
|
136
|
+
'azure',
|
|
137
|
+
'dotnet10-compatibility'
|
|
138
|
+
],
|
|
139
|
+
'container-specialist': [
|
|
140
|
+
'azure',
|
|
141
|
+
'dotnet10-compatibility'
|
|
142
|
+
],
|
|
143
|
+
'observability-expert': [
|
|
144
|
+
'azure',
|
|
145
|
+
'architecture'
|
|
146
|
+
],
|
|
147
|
+
'azure-identity': [
|
|
148
|
+
'azure',
|
|
149
|
+
'architecture',
|
|
150
|
+
'passkeys-auth'
|
|
151
|
+
],
|
|
152
|
+
|
|
153
|
+
// Tier 3: Specialists - Quality Squad
|
|
154
|
+
'testing-specialist': [
|
|
155
|
+
'coding',
|
|
156
|
+
'architecture',
|
|
157
|
+
'dotnet10-compatibility'
|
|
158
|
+
],
|
|
159
|
+
'code-analyzer': [
|
|
160
|
+
'coding',
|
|
161
|
+
'architecture',
|
|
162
|
+
'dotnet10-compatibility',
|
|
163
|
+
'program-cs-checklist'
|
|
164
|
+
],
|
|
165
|
+
'troubleshooting-expert': [
|
|
166
|
+
'architecture',
|
|
167
|
+
'blazor-pitfalls',
|
|
168
|
+
'dotnet10-compatibility'
|
|
169
|
+
],
|
|
170
|
+
'load-testing-expert': [
|
|
171
|
+
'architecture',
|
|
172
|
+
'azure'
|
|
173
|
+
],
|
|
174
|
+
'documentation-specialist': [], // No technical standards
|
|
175
|
+
|
|
176
|
+
// Tier 4: Validators
|
|
177
|
+
'security-expert': [
|
|
178
|
+
'coding',
|
|
179
|
+
'architecture',
|
|
180
|
+
'passkeys-auth'
|
|
181
|
+
]
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Load a single standard file with project override support
|
|
186
|
+
* @param {string} standardName - Standard filename without .md extension
|
|
187
|
+
* @param {string} projectPath - Root path of the project
|
|
188
|
+
* @returns {string|null} Standard content or null if not found
|
|
189
|
+
*/
|
|
190
|
+
function loadStandard(standardName, projectPath) {
|
|
191
|
+
const locations = [
|
|
192
|
+
// 1. Project override
|
|
193
|
+
join(projectPath, '.morph/project/standards', `${standardName}.md`),
|
|
194
|
+
// 2. Content standards (AI/Agent Framework, Azure, etc.)
|
|
195
|
+
join(projectPath, 'content/.morph/standards', `${standardName}.md`),
|
|
196
|
+
// 3. Framework standards (Blazor, CSS, .NET)
|
|
197
|
+
join(projectPath, 'framework/standards', `${standardName}.md`)
|
|
198
|
+
];
|
|
199
|
+
|
|
200
|
+
for (const path of locations) {
|
|
201
|
+
if (existsSync(path)) {
|
|
202
|
+
try {
|
|
203
|
+
return readFileSync(path, 'utf8');
|
|
204
|
+
} catch (err) {
|
|
205
|
+
// Continue to next location
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
return null;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Load all relevant standards for a given agent
|
|
215
|
+
* @param {string} agentId - Agent ID (e.g., 'blazor-builder')
|
|
216
|
+
* @param {string} projectPath - Root path of the project
|
|
217
|
+
* @returns {Object} { sections: [{standard, content}], fullContent: string }
|
|
218
|
+
*/
|
|
219
|
+
export function loadStandardsForAgent(agentId, projectPath = '.') {
|
|
220
|
+
const standardNames = AGENT_STANDARDS_MAP[agentId] || [];
|
|
221
|
+
const sections = [];
|
|
222
|
+
const contentParts = [];
|
|
223
|
+
|
|
224
|
+
// Load inferred standards if exists (project-specific learnings)
|
|
225
|
+
const inferredPath = join(projectPath, '.morph/project/standards/inferred.md');
|
|
226
|
+
if (existsSync(inferredPath)) {
|
|
227
|
+
try {
|
|
228
|
+
const inferredContent = readFileSync(inferredPath, 'utf8');
|
|
229
|
+
sections.push({
|
|
230
|
+
standard: 'inferred',
|
|
231
|
+
content: inferredContent
|
|
232
|
+
});
|
|
233
|
+
contentParts.push(`# Project-Specific Standards (Inferred)\n\n${inferredContent}`);
|
|
234
|
+
} catch (err) {
|
|
235
|
+
// Ignore if can't read
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Load each standard
|
|
240
|
+
for (const standardName of standardNames) {
|
|
241
|
+
const content = loadStandard(standardName, projectPath);
|
|
242
|
+
if (content) {
|
|
243
|
+
sections.push({
|
|
244
|
+
standard: standardName,
|
|
245
|
+
content
|
|
246
|
+
});
|
|
247
|
+
contentParts.push(`# ${standardName}\n\n${content}`);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
return {
|
|
252
|
+
sections,
|
|
253
|
+
fullContent: contentParts.join('\n\n---\n\n'),
|
|
254
|
+
standardNames // For debugging/introspection
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Get list of standards for an agent (without loading content)
|
|
260
|
+
* @param {string} agentId - Agent ID
|
|
261
|
+
* @returns {string[]} Array of standard names
|
|
262
|
+
*/
|
|
263
|
+
export function getStandardsListForAgent(agentId) {
|
|
264
|
+
return AGENT_STANDARDS_MAP[agentId] || [];
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Check if a standard exists in any location
|
|
269
|
+
* @param {string} standardName - Standard filename without .md extension
|
|
270
|
+
* @param {string} projectPath - Root path of the project
|
|
271
|
+
* @returns {Object} { exists: boolean, location: string|null }
|
|
272
|
+
*/
|
|
273
|
+
export function checkStandardExists(standardName, projectPath = '.') {
|
|
274
|
+
const locations = [
|
|
275
|
+
{ type: 'project', path: join(projectPath, '.morph/project/standards', `${standardName}.md`) },
|
|
276
|
+
{ type: 'content', path: join(projectPath, 'content/.morph/standards', `${standardName}.md`) },
|
|
277
|
+
{ type: 'framework', path: join(projectPath, 'framework/standards', `${standardName}.md`) }
|
|
278
|
+
];
|
|
279
|
+
|
|
280
|
+
for (const loc of locations) {
|
|
281
|
+
if (existsSync(loc.path)) {
|
|
282
|
+
return { exists: true, location: loc.type, path: loc.path };
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
return { exists: false, location: null, path: null };
|
|
287
|
+
}
|
package/src/lib/state-manager.js
CHANGED
|
@@ -291,6 +291,21 @@ export function removeAgent(featureName, agentId) {
|
|
|
291
291
|
return false;
|
|
292
292
|
}
|
|
293
293
|
|
|
294
|
+
/**
|
|
295
|
+
* Normalize output type from kebab-case to camelCase for UI types (BUG #12 fix)
|
|
296
|
+
* @param {string} type - Output type (e.g., 'ui-design-system' or 'uiDesignSystem')
|
|
297
|
+
* @returns {string} Normalized type in camelCase
|
|
298
|
+
*/
|
|
299
|
+
function normalizeOutputType(type) {
|
|
300
|
+
const kebabMap = {
|
|
301
|
+
'ui-design-system': 'uiDesignSystem',
|
|
302
|
+
'ui-mockups': 'uiMockups',
|
|
303
|
+
'ui-components': 'uiComponents',
|
|
304
|
+
'ui-flows': 'uiFlows'
|
|
305
|
+
};
|
|
306
|
+
return kebabMap[type] || type;
|
|
307
|
+
}
|
|
308
|
+
|
|
294
309
|
/**
|
|
295
310
|
* Mark output as created
|
|
296
311
|
* @param {string} featureName - Feature name
|
|
@@ -300,15 +315,17 @@ export function markOutput(featureName, outputType) {
|
|
|
300
315
|
ensureFeature(featureName);
|
|
301
316
|
const state = loadState();
|
|
302
317
|
|
|
303
|
-
|
|
304
|
-
|
|
318
|
+
const normalized = normalizeOutputType(outputType);
|
|
319
|
+
|
|
320
|
+
if (!state.features[featureName].outputs[normalized]) {
|
|
321
|
+
throw new Error(`Output type '${outputType}' not valid. Valid types: proposal, spec, contracts, tasks, uiDesignSystem (or ui-design-system), uiMockups (or ui-mockups), uiComponents (or ui-components), uiFlows (or ui-flows), decisions, recap`);
|
|
305
322
|
}
|
|
306
323
|
|
|
307
|
-
state.features[featureName].outputs[
|
|
324
|
+
state.features[featureName].outputs[normalized].created = true;
|
|
308
325
|
state.features[featureName].updatedAt = new Date().toISOString();
|
|
309
326
|
|
|
310
327
|
// If marking tasks output, try to sync task count from state tasks array
|
|
311
|
-
if (
|
|
328
|
+
if (normalized === 'tasks') {
|
|
312
329
|
syncTasksCount(state.features[featureName]);
|
|
313
330
|
}
|
|
314
331
|
|
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Hierarchical Agent Teams Orchestrator
|
|
3
|
+
*
|
|
4
|
+
* Builds Agent Teams using 4-tier hierarchy:
|
|
5
|
+
* - Tier 1: Orchestrators (Team Lead in delegate mode)
|
|
6
|
+
* - Tier 2: Domain Leaders (Squad Leaders)
|
|
7
|
+
* - Tier 3: Specialists (Domain experts)
|
|
8
|
+
* - Tier 4: Validators (Run in hooks, not teammates)
|
|
9
|
+
*
|
|
10
|
+
* @module team-orchestrator
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import fs from 'fs/promises';
|
|
14
|
+
import path from 'path';
|
|
15
|
+
import { fileURLToPath } from 'url';
|
|
16
|
+
|
|
17
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
18
|
+
const __dirname = path.dirname(__filename);
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Load agents.json with hierarchical structure
|
|
22
|
+
* @param {string} projectPath - Root path of the project
|
|
23
|
+
* @returns {Promise<Object>} Parsed agents configuration
|
|
24
|
+
*/
|
|
25
|
+
async function loadAgentsConfig(projectPath) {
|
|
26
|
+
const agentsPath = path.join(projectPath, 'content/.morph/config/agents.json');
|
|
27
|
+
const content = await fs.readFile(agentsPath, 'utf8');
|
|
28
|
+
return JSON.parse(content);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Detect if Agent Teams should be spawned based on complexity
|
|
33
|
+
* @param {Object} options
|
|
34
|
+
* @param {string[]} options.activeAgents - Agent IDs detected for feature
|
|
35
|
+
* @param {string} options.complexity - Complexity level (trivial|low|medium|high|critical)
|
|
36
|
+
* @param {number} options.estimatedFiles - Estimated files to modify
|
|
37
|
+
* @param {boolean} options.multiDomain - Does feature span multiple domains?
|
|
38
|
+
* @returns {Object} { shouldSpawn: boolean, reason: string, recommendedMode: string }
|
|
39
|
+
*/
|
|
40
|
+
export function shouldSpawnAgentTeam(options) {
|
|
41
|
+
const { activeAgents = [], complexity, estimatedFiles = 0, multiDomain = false } = options;
|
|
42
|
+
|
|
43
|
+
// Never spawn for trivial/low complexity
|
|
44
|
+
if (complexity === 'trivial' || complexity === 'low') {
|
|
45
|
+
return {
|
|
46
|
+
shouldSpawn: false,
|
|
47
|
+
reason: 'Complexity too low - single session sufficient',
|
|
48
|
+
recommendedMode: 'single'
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Always spawn for critical complexity
|
|
53
|
+
if (complexity === 'critical') {
|
|
54
|
+
return {
|
|
55
|
+
shouldSpawn: true,
|
|
56
|
+
reason: 'Critical complexity - parallel coordination essential',
|
|
57
|
+
recommendedMode: 'tmux'
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Spawn if multi-domain (backend + frontend + infra)
|
|
62
|
+
if (multiDomain) {
|
|
63
|
+
return {
|
|
64
|
+
shouldSpawn: true,
|
|
65
|
+
reason: 'Multi-domain feature - benefits from parallel squads',
|
|
66
|
+
recommendedMode: 'auto'
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Spawn if 3+ independent modules (high file count)
|
|
71
|
+
if (estimatedFiles >= 15) {
|
|
72
|
+
return {
|
|
73
|
+
shouldSpawn: true,
|
|
74
|
+
reason: 'High file count - parallel implementation recommended',
|
|
75
|
+
recommendedMode: 'auto'
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Spawn if 5+ specialists active (indicates complex coordination)
|
|
80
|
+
if (activeAgents.length >= 5) {
|
|
81
|
+
return {
|
|
82
|
+
shouldSpawn: true,
|
|
83
|
+
reason: '5+ specialists active - team coordination beneficial',
|
|
84
|
+
recommendedMode: 'auto'
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Default: single session for medium complexity
|
|
89
|
+
return {
|
|
90
|
+
shouldSpawn: false,
|
|
91
|
+
reason: 'Medium complexity - single session with subagents OK',
|
|
92
|
+
recommendedMode: 'single'
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Build hierarchical team structure from active agents
|
|
98
|
+
* @param {string[]} activeAgentIds - Agent IDs to include in team
|
|
99
|
+
* @param {Object} agentsConfig - Loaded agents.json
|
|
100
|
+
* @returns {Object} Team structure with lead, domain leaders, specialists
|
|
101
|
+
*/
|
|
102
|
+
export function buildTeamHierarchy(activeAgentIds, agentsConfig) {
|
|
103
|
+
const agents = agentsConfig.agents;
|
|
104
|
+
const activeAgents = activeAgentIds
|
|
105
|
+
.filter(id => !id.startsWith('_comment') && agents[id])
|
|
106
|
+
.map(id => ({ id, ...agents[id] }));
|
|
107
|
+
|
|
108
|
+
// Tier 1: Orchestrator (Team Lead)
|
|
109
|
+
const orchestrators = activeAgents.filter(a => a.tier === 1 && a.role === 'orchestrator');
|
|
110
|
+
const teamLead = orchestrators.find(a => a.id === 'standards-architect') || orchestrators[0];
|
|
111
|
+
|
|
112
|
+
// Tier 2: Domain Leaders
|
|
113
|
+
const domainLeaders = activeAgents.filter(a => a.tier === 2 && a.role === 'domain-leader');
|
|
114
|
+
|
|
115
|
+
// Tier 3: Specialists (group by domain)
|
|
116
|
+
const specialists = activeAgents.filter(a => a.tier === 3 && a.role === 'specialist');
|
|
117
|
+
|
|
118
|
+
// Tier 4: Validators (run in hooks, NOT teammates)
|
|
119
|
+
const validators = activeAgents.filter(a => a.tier === 4 && a.role === 'validator');
|
|
120
|
+
|
|
121
|
+
// Group specialists under their Domain Leaders
|
|
122
|
+
const squads = {};
|
|
123
|
+
domainLeaders.forEach(leader => {
|
|
124
|
+
const squadMembers = specialists.filter(specialist => {
|
|
125
|
+
// Check if specialist reports to this leader
|
|
126
|
+
return specialist.relationships?.reports_to === leader.id;
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
if (squadMembers.length > 0) {
|
|
130
|
+
squads[leader.id] = {
|
|
131
|
+
leader,
|
|
132
|
+
members: squadMembers
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
return {
|
|
138
|
+
teamLead,
|
|
139
|
+
squads,
|
|
140
|
+
validators, // Not in teammates, run as hooks
|
|
141
|
+
totalTeammates: 1 + domainLeaders.length + specialists.length // Lead + Leaders + Specialists
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Generate teammate spawn instructions for Agent Teams
|
|
147
|
+
* @param {Object} teamHierarchy - Output from buildTeamHierarchy
|
|
148
|
+
* @param {string} featureName - Feature name
|
|
149
|
+
* @param {string} phase - Current MORPH phase
|
|
150
|
+
* @returns {Object[]} Array of teammate configs for Agent Teams
|
|
151
|
+
*/
|
|
152
|
+
export function generateTeammateConfigs(teamHierarchy, featureName, phase) {
|
|
153
|
+
const teammates = [];
|
|
154
|
+
const { teamLead, squads } = teamHierarchy;
|
|
155
|
+
|
|
156
|
+
// Add Team Lead (standards-architect in delegate mode)
|
|
157
|
+
if (teamLead && teamLead.teammate) {
|
|
158
|
+
teammates.push({
|
|
159
|
+
id: teamLead.id,
|
|
160
|
+
tier: teamLead.tier,
|
|
161
|
+
role: teamLead.teammate.role,
|
|
162
|
+
icon: teamLead.teammate.icon,
|
|
163
|
+
spawnPrompt: `${teamLead.teammate.spawn_prompt}
|
|
164
|
+
|
|
165
|
+
Feature: ${featureName}
|
|
166
|
+
Phase: ${phase}
|
|
167
|
+
|
|
168
|
+
Your Domain Leaders:
|
|
169
|
+
${Object.values(squads).map(s => `- ${s.leader.title} (${s.leader.id})`).join('\n')}
|
|
170
|
+
|
|
171
|
+
DO NOT implement code yourself. Coordinate Domain Leaders, resolve conflicts, synthesize results.`
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Add Domain Leaders + their squad members
|
|
176
|
+
Object.entries(squads).forEach(([leaderId, squad]) => {
|
|
177
|
+
const leader = squad.leader;
|
|
178
|
+
|
|
179
|
+
// Add Domain Leader
|
|
180
|
+
if (leader.teammate) {
|
|
181
|
+
teammates.push({
|
|
182
|
+
id: leader.id,
|
|
183
|
+
tier: leader.tier,
|
|
184
|
+
role: leader.teammate.role,
|
|
185
|
+
icon: leader.teammate.icon,
|
|
186
|
+
spawnPrompt: `${leader.teammate.spawn_prompt}
|
|
187
|
+
|
|
188
|
+
Feature: ${featureName}
|
|
189
|
+
Phase: ${phase}
|
|
190
|
+
|
|
191
|
+
Your Squad (${squad.members.length} specialists):
|
|
192
|
+
${squad.members.map(m => `- ${m.title} (${m.id})`).join('\n')}
|
|
193
|
+
|
|
194
|
+
Coordinate your specialists. Escalate conflicts to ${leader.relationships.escalates_to}.`
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Add Specialists in this squad
|
|
199
|
+
squad.members.forEach(specialist => {
|
|
200
|
+
if (specialist.teammate) {
|
|
201
|
+
teammates.push({
|
|
202
|
+
id: specialist.id,
|
|
203
|
+
tier: specialist.tier,
|
|
204
|
+
role: specialist.teammate.role,
|
|
205
|
+
icon: specialist.teammate.icon,
|
|
206
|
+
spawnPrompt: `${specialist.teammate.spawn_prompt}
|
|
207
|
+
|
|
208
|
+
Feature: ${featureName}
|
|
209
|
+
Phase: ${phase}
|
|
210
|
+
|
|
211
|
+
Reports to: ${specialist.relationships.reports_to}
|
|
212
|
+
Escalate to: ${specialist.relationships.escalates_to}
|
|
213
|
+
|
|
214
|
+
${specialist.relationships.collaborates_with ?
|
|
215
|
+
`Collaborate with: ${specialist.relationships.collaborates_with.join(', ')}` : ''}`
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
});
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
return teammates;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Generate Agent Teams command for spawning
|
|
226
|
+
* @param {Object[]} teammates - Output from generateTeammateConfigs
|
|
227
|
+
* @param {string} displayMode - 'auto' | 'in-process' | 'tmux'
|
|
228
|
+
* @returns {string} Command to spawn Agent Teams
|
|
229
|
+
*/
|
|
230
|
+
export function generateSpawnCommand(teammates, displayMode = 'auto') {
|
|
231
|
+
// Agent Teams are spawned via Claude Code's Task tool with subagent_type="general-purpose"
|
|
232
|
+
// and special teammate configs. This is a conceptual representation.
|
|
233
|
+
|
|
234
|
+
const command = {
|
|
235
|
+
tool: 'Task',
|
|
236
|
+
subagent_type: 'general-purpose',
|
|
237
|
+
agent_teams_enabled: true,
|
|
238
|
+
display_mode: displayMode,
|
|
239
|
+
teammates: teammates.map(t => ({
|
|
240
|
+
id: t.id,
|
|
241
|
+
role: t.role,
|
|
242
|
+
icon: t.icon,
|
|
243
|
+
prompt: t.spawnPrompt
|
|
244
|
+
}))
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
return JSON.stringify(command, null, 2);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Main orchestration function - decides if/when to spawn Agent Teams
|
|
252
|
+
* @param {Object} options
|
|
253
|
+
* @param {string} options.projectPath - Project root path
|
|
254
|
+
* @param {string} options.featureName - Feature name
|
|
255
|
+
* @param {string} options.phase - MORPH phase
|
|
256
|
+
* @param {string[]} options.activeAgents - Active agent IDs
|
|
257
|
+
* @param {string} options.complexity - Complexity level
|
|
258
|
+
* @param {number} options.estimatedFiles - Estimated file count
|
|
259
|
+
* @param {boolean} options.multiDomain - Multi-domain feature?
|
|
260
|
+
* @returns {Promise<Object>} Team orchestration result
|
|
261
|
+
*/
|
|
262
|
+
export async function orchestrateTeam(options) {
|
|
263
|
+
const {
|
|
264
|
+
projectPath,
|
|
265
|
+
featureName,
|
|
266
|
+
phase,
|
|
267
|
+
activeAgents = [],
|
|
268
|
+
complexity,
|
|
269
|
+
estimatedFiles = 0,
|
|
270
|
+
multiDomain = false
|
|
271
|
+
} = options;
|
|
272
|
+
|
|
273
|
+
// Step 1: Decide if Agent Teams should spawn
|
|
274
|
+
const spawnDecision = shouldSpawnAgentTeam({
|
|
275
|
+
activeAgents,
|
|
276
|
+
complexity,
|
|
277
|
+
estimatedFiles,
|
|
278
|
+
multiDomain
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
if (!spawnDecision.shouldSpawn) {
|
|
282
|
+
return {
|
|
283
|
+
useAgentTeams: false,
|
|
284
|
+
reason: spawnDecision.reason,
|
|
285
|
+
recommendation: 'Use single session with Task tool for subagents'
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// Step 2: Load agents config and build hierarchy
|
|
290
|
+
const agentsConfig = await loadAgentsConfig(projectPath);
|
|
291
|
+
const teamHierarchy = buildTeamHierarchy(activeAgents, agentsConfig);
|
|
292
|
+
|
|
293
|
+
// Step 3: Generate teammate configs
|
|
294
|
+
const teammates = generateTeammateConfigs(teamHierarchy, featureName, phase);
|
|
295
|
+
|
|
296
|
+
// Step 4: Generate spawn command
|
|
297
|
+
const spawnCommand = generateSpawnCommand(teammates, spawnDecision.recommendedMode);
|
|
298
|
+
|
|
299
|
+
return {
|
|
300
|
+
useAgentTeams: true,
|
|
301
|
+
reason: spawnDecision.reason,
|
|
302
|
+
displayMode: spawnDecision.recommendedMode,
|
|
303
|
+
teamHierarchy: {
|
|
304
|
+
teamLead: teamHierarchy.teamLead?.id,
|
|
305
|
+
squads: Object.keys(teamHierarchy.squads),
|
|
306
|
+
totalTeammates: teamHierarchy.totalTeammates,
|
|
307
|
+
validators: teamHierarchy.validators.map(v => v.id)
|
|
308
|
+
},
|
|
309
|
+
teammates,
|
|
310
|
+
spawnCommand,
|
|
311
|
+
recommendation: `Spawn ${teamHierarchy.totalTeammates} teammates in ${spawnDecision.recommendedMode} mode`
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Get agent squad assignments (for informational purposes)
|
|
317
|
+
* @param {Object} agentsConfig - Loaded agents.json
|
|
318
|
+
* @returns {Object} Squad information
|
|
319
|
+
*/
|
|
320
|
+
export function getSquadInfo(agentsConfig) {
|
|
321
|
+
return agentsConfig.squads || {};
|
|
322
|
+
}
|