@polymorphism-tech/morph-spec 4.2.0 → 4.3.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 +108 -946
- package/bin/morph-spec.js +284 -9
- package/bin/task-manager.cjs +102 -14
- package/bin/validate.js +4 -4
- package/docs/{v3.0 → next-generation}/AGENTS.md +1 -1
- package/docs/next-generation/CONTEXT-OPTIMIZATION.md +267 -0
- package/docs/next-generation/EXECUTION-FLOW.md +274 -0
- package/docs/next-generation/META-PROMPTS.md +235 -0
- package/docs/next-generation/MIGRATION-GUIDE.md +253 -0
- package/docs/next-generation/THREAD-MANAGEMENT.md +240 -0
- package/package.json +5 -5
- package/src/commands/agents/agents-fuse.js +97 -0
- package/src/commands/agents/micro-agent.js +112 -0
- package/src/commands/agents/spawn-team.js +69 -4
- package/src/commands/agents/squad-template.js +146 -0
- package/src/commands/analytics/analytics.js +176 -0
- package/src/commands/context/context-prime.js +63 -0
- package/src/commands/context/core-four.js +54 -0
- package/src/commands/mcp/mcp.js +102 -0
- package/src/commands/project/detect-agents.js +32 -2
- package/src/commands/project/detect.js +11 -1
- package/src/commands/project/doctor.js +573 -356
- package/src/commands/project/init.js +9 -2
- package/src/commands/project/update.js +13 -3
- package/src/commands/state/advance-phase.js +448 -416
- package/src/commands/state/state.js +14 -12
- package/src/commands/tasks/task.js +1 -1
- package/src/commands/templates/template-render.js +80 -1
- package/src/commands/threads/thread-template.js +103 -0
- package/src/commands/threads/threads.js +261 -0
- package/src/commands/trust/trust.js +205 -0
- package/src/{orchestrator.js → core/orchestrator.js} +8 -8
- package/src/core/state/state-manager.js +37 -17
- package/src/core/workflows/workflow-detector.js +114 -3
- package/src/lib/agents/micro-agent-factory.js +161 -0
- package/src/lib/analytics/analytics-engine.js +345 -0
- package/src/lib/checkpoints/checkpoint-hooks.js +298 -258
- package/src/lib/context/context-bundler.js +240 -0
- package/src/lib/context/context-optimizer.js +212 -0
- package/src/lib/context/context-tracker.js +273 -0
- package/src/lib/context/core-four-tracker.js +201 -0
- package/src/lib/context/mcp-optimizer.js +200 -0
- package/src/lib/detectors/index.js +1 -1
- package/src/lib/detectors/standards-generator.js +77 -17
- package/src/lib/detectors/structure-detector.js +67 -39
- package/src/lib/execution/fusion-executor.js +304 -0
- package/src/lib/execution/parallel-executor.js +270 -0
- package/src/lib/generators/context-generator.js +3 -3
- package/src/lib/generators/recap-generator.js +32 -12
- package/src/lib/hooks/hook-executor.js +169 -0
- package/src/lib/hooks/stop-hook-executor.js +286 -0
- package/src/lib/hops/hop-composer.js +221 -0
- package/src/lib/threads/thread-coordinator.js +238 -0
- package/src/lib/threads/thread-manager.js +317 -0
- package/src/lib/tracking/artifact-trail.js +202 -0
- package/src/lib/trust/trust-manager.js +269 -0
- package/src/lib/validators/design-system/design-system-validator.js +2 -2
- package/src/lib/validators/validation-runner.js +14 -30
- package/src/utils/hooks-installer.js +69 -0
- package/stacks/blazor-azure/.morph/config/agents.json +72 -3
- package/stacks/nextjs-supabase/.morph/config/agents.json +3 -3
- package/docs/llm-interaction-config.md +0 -735
- package/docs/v3.0/EXECUTION-FLOW.md +0 -1304
- package/src/commands/utils/migrate-state.js +0 -158
- package/src/commands/utils/upgrade.js +0 -346
- package/src/lib/validators/architecture-validator.js +0 -60
- package/src/lib/validators/content-validator.js +0 -164
- package/src/lib/validators/package-validator.js +0 -61
- package/src/lib/validators/ui-contrast-validator.js +0 -44
- package/stacks/blazor-azure/.claude/commands/morph-apply.md +0 -221
- package/stacks/blazor-azure/.claude/commands/morph-archive.md +0 -79
- package/stacks/blazor-azure/.claude/commands/morph-deploy.md +0 -529
- package/stacks/blazor-azure/.claude/commands/morph-infra.md +0 -209
- package/stacks/blazor-azure/.claude/commands/morph-preflight.md +0 -227
- package/stacks/blazor-azure/.claude/commands/morph-proposal.md +0 -122
- package/stacks/blazor-azure/.claude/commands/morph-status.md +0 -86
- package/stacks/blazor-azure/.claude/commands/morph-troubleshoot.md +0 -122
- package/stacks/blazor-azure/.claude/skills/level-0-meta/README.md +0 -7
- package/stacks/blazor-azure/.claude/skills/level-0-meta/code-review.md +0 -226
- package/stacks/blazor-azure/.claude/skills/level-0-meta/morph-checklist.md +0 -117
- package/stacks/blazor-azure/.claude/skills/level-0-meta/simulation-checklist.md +0 -77
- package/stacks/blazor-azure/.claude/skills/level-1-workflows/README.md +0 -7
- package/stacks/blazor-azure/.claude/skills/level-1-workflows/morph-replicate.md +0 -213
- package/stacks/blazor-azure/.claude/skills/level-1-workflows/phase-clarify.md +0 -131
- package/stacks/blazor-azure/.claude/skills/level-1-workflows/phase-design.md +0 -213
- package/stacks/blazor-azure/.claude/skills/level-1-workflows/phase-setup.md +0 -106
- package/stacks/blazor-azure/.claude/skills/level-1-workflows/phase-tasks.md +0 -164
- package/stacks/blazor-azure/.claude/skills/level-1-workflows/phase-uiux.md +0 -169
- package/stacks/blazor-azure/.claude/skills/level-2-domains/README.md +0 -14
- package/stacks/blazor-azure/.claude/skills/level-2-domains/ai-agents/ai-system-architect.md +0 -192
- package/stacks/blazor-azure/.claude/skills/level-2-domains/architecture/po-pm-advisor.md +0 -197
- package/stacks/blazor-azure/.claude/skills/level-2-domains/architecture/prompt-engineer.md +0 -189
- package/stacks/blazor-azure/.claude/skills/level-2-domains/architecture/seo-growth-hacker.md +0 -320
- package/stacks/blazor-azure/.claude/skills/level-2-domains/architecture/standards-architect.md +0 -156
- package/stacks/blazor-azure/.claude/skills/level-2-domains/backend/api-designer.md +0 -59
- package/stacks/blazor-azure/.claude/skills/level-2-domains/backend/dotnet-senior.md +0 -77
- package/stacks/blazor-azure/.claude/skills/level-2-domains/backend/ef-modeler.md +0 -58
- package/stacks/blazor-azure/.claude/skills/level-2-domains/backend/hangfire-orchestrator.md +0 -126
- package/stacks/blazor-azure/.claude/skills/level-2-domains/backend/ms-agent-expert.md +0 -45
- package/stacks/blazor-azure/.claude/skills/level-2-domains/frontend/blazor-builder.md +0 -210
- package/stacks/blazor-azure/.claude/skills/level-2-domains/frontend/nextjs-expert.md +0 -154
- package/stacks/blazor-azure/.claude/skills/level-2-domains/frontend/ui-ux-designer.md +0 -191
- package/stacks/blazor-azure/.claude/skills/level-2-domains/infrastructure/azure-architect.md +0 -142
- package/stacks/blazor-azure/.claude/skills/level-2-domains/infrastructure/azure-deploy-specialist.md +0 -699
- package/stacks/blazor-azure/.claude/skills/level-2-domains/infrastructure/bicep-architect.md +0 -126
- package/stacks/blazor-azure/.claude/skills/level-2-domains/infrastructure/container-specialist.md +0 -131
- package/stacks/blazor-azure/.claude/skills/level-2-domains/infrastructure/devops-engineer.md +0 -119
- package/stacks/blazor-azure/.claude/skills/level-2-domains/integrations/asaas-financial.md +0 -130
- package/stacks/blazor-azure/.claude/skills/level-2-domains/integrations/azure-identity.md +0 -142
- package/stacks/blazor-azure/.claude/skills/level-2-domains/integrations/clerk-auth.md +0 -108
- package/stacks/blazor-azure/.claude/skills/level-2-domains/integrations/hangfire-orchestrator.md +0 -64
- package/stacks/blazor-azure/.claude/skills/level-2-domains/integrations/resend-email.md +0 -119
- package/stacks/blazor-azure/.claude/skills/level-2-domains/quality/code-analyzer.md +0 -235
- package/stacks/blazor-azure/.claude/skills/level-2-domains/quality/testing-specialist.md +0 -126
- package/stacks/blazor-azure/.claude/skills/level-3-technologies/README.md +0 -7
- package/stacks/blazor-azure/.claude/skills/level-4-patterns/README.md +0 -7
- package/stacks/blazor-azure/.morph/archive/.gitkeep +0 -25
- package/stacks/blazor-azure/.morph/features/.gitkeep +0 -25
- package/stacks/blazor-azure/.morph/schemas/agent.schema.json +0 -296
- package/stacks/blazor-azure/.morph/schemas/tasks.schema.json +0 -220
- package/stacks/blazor-azure/.morph/specs/.gitkeep +0 -20
- package/stacks/blazor-azure/.morph/test-infra/example.bicep +0 -59
- package/stacks/nextjs-supabase/.claude/commands/morph-apply.md +0 -221
- package/stacks/nextjs-supabase/.claude/commands/morph-archive.md +0 -79
- package/stacks/nextjs-supabase/.claude/commands/morph-deploy.md +0 -529
- package/stacks/nextjs-supabase/.claude/commands/morph-infra.md +0 -209
- package/stacks/nextjs-supabase/.claude/commands/morph-preflight.md +0 -227
- package/stacks/nextjs-supabase/.claude/commands/morph-proposal.md +0 -122
- package/stacks/nextjs-supabase/.claude/commands/morph-status.md +0 -86
- package/stacks/nextjs-supabase/.claude/commands/morph-troubleshoot.md +0 -122
- package/stacks/nextjs-supabase/.claude/settings.local.json +0 -6
- package/stacks/nextjs-supabase/.claude/skills/level-2-domains/backend/dotnet-supabase.md +0 -244
- package/stacks/nextjs-supabase/.claude/skills/level-2-domains/frontend/nextjs-supabase.md +0 -335
- package/stacks/nextjs-supabase/.claude/skills/level-2-domains/infrastructure/easypanel-deployer.md +0 -189
- package/stacks/nextjs-supabase/.claude/skills/level-2-domains/integrations/supabase-expert.md +0 -50
- /package/docs/{v3.0 → next-generation}/ANALYSIS.md +0 -0
- /package/docs/{v3.0 → next-generation}/ARCHITECTURE.md +0 -0
- /package/docs/{v3.0 → next-generation}/FEATURES.md +0 -0
- /package/docs/{v3.0 → next-generation}/README.md +0 -0
- /package/docs/{v3.0 → next-generation}/ROADMAP.md +0 -0
|
@@ -129,12 +129,15 @@ Based on detected patterns in your codebase:
|
|
|
129
129
|
}
|
|
130
130
|
|
|
131
131
|
if (config?.language === 'javascript') {
|
|
132
|
+
const techLine = config.technologies?.length > 0
|
|
133
|
+
? `\n- **Framework**: ${config.technologies.join(', ')}`
|
|
134
|
+
: '';
|
|
135
|
+
|
|
132
136
|
return `### JavaScript / TypeScript Conventions
|
|
133
137
|
|
|
134
138
|
Based on detected patterns in your codebase:
|
|
135
139
|
|
|
136
|
-
- **Package Manager**: ${config.packageManager}
|
|
137
|
-
- **Framework**: ${config.technologies.join(', ')}
|
|
140
|
+
- **Package Manager**: ${config.packageManager}${techLine}
|
|
138
141
|
|
|
139
142
|
**Recommendation**: Refer to framework standards for JavaScript best practices.`;
|
|
140
143
|
}
|
|
@@ -149,6 +152,37 @@ function generateArchitectureSection(structure) {
|
|
|
149
152
|
const { architecture } = structure;
|
|
150
153
|
|
|
151
154
|
const descriptions = {
|
|
155
|
+
'cli-library': `### CLI / Library Architecture
|
|
156
|
+
|
|
157
|
+
Your project follows a CLI/Library pattern:
|
|
158
|
+
|
|
159
|
+
- ✅ **bin/** entry points detected
|
|
160
|
+
- ✅ **src/** source directory detected
|
|
161
|
+
- ✅ **package.json** present
|
|
162
|
+
|
|
163
|
+
**Key principles**:
|
|
164
|
+
- Commands exposed via bin/
|
|
165
|
+
- Core logic in src/commands/ and src/lib/
|
|
166
|
+
- Public API exported from src/index.js`,
|
|
167
|
+
|
|
168
|
+
'nextjs-app-router': `### Next.js App Router Architecture
|
|
169
|
+
|
|
170
|
+
Your project uses the Next.js App Router pattern:
|
|
171
|
+
|
|
172
|
+
- ✅ **app/** directory detected (React Server Components)
|
|
173
|
+
|
|
174
|
+
**Key principles**:
|
|
175
|
+
- Server Components by default
|
|
176
|
+
- Client Components with 'use client' directive
|
|
177
|
+
- Route handlers in app/api/`,
|
|
178
|
+
|
|
179
|
+
'express-mvc': `### Express MVC Architecture
|
|
180
|
+
|
|
181
|
+
Your project uses Express with MVC pattern:
|
|
182
|
+
|
|
183
|
+
- ✅ **routes/** detected
|
|
184
|
+
- ✅ **controllers/** detected`,
|
|
185
|
+
|
|
152
186
|
'clean-architecture': `### Clean Architecture
|
|
153
187
|
|
|
154
188
|
Your project follows Clean Architecture pattern:
|
|
@@ -203,19 +237,29 @@ function generateRecommendations(structure, config, conversation) {
|
|
|
203
237
|
}
|
|
204
238
|
}
|
|
205
239
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
240
|
+
if (structure?.stack === 'nodejs' || config?.language === 'javascript') {
|
|
241
|
+
if (!structure?.patterns?.includes('Unit Tests')) {
|
|
242
|
+
recommendations.push('No unit tests detected - consider adding test coverage with Jest or Vitest');
|
|
243
|
+
}
|
|
210
244
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
}
|
|
245
|
+
if (structure?.architecture === 'unknown') {
|
|
246
|
+
recommendations.push('No clear architecture detected - consider organizing code into src/commands/, src/lib/, src/core/');
|
|
247
|
+
}
|
|
248
|
+
} else {
|
|
249
|
+
// Recommend tests (generic / .NET)
|
|
250
|
+
if (!structure?.patterns?.includes('Unit Tests')) {
|
|
251
|
+
recommendations.push('No unit tests detected - consider adding test project');
|
|
252
|
+
}
|
|
215
253
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
254
|
+
// Recommend DI (only meaningful for C# / .NET)
|
|
255
|
+
if (config?.language === 'csharp' && !structure?.patterns?.includes('Dependency Injection')) {
|
|
256
|
+
recommendations.push('Consider implementing Dependency Injection pattern');
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Architecture recommendations (.NET)
|
|
260
|
+
if (structure?.architecture === 'unknown' && structure?.folders?.hasServices) {
|
|
261
|
+
recommendations.push('Service layer detected but no clear architecture - consider Clean Architecture or CQRS');
|
|
262
|
+
}
|
|
219
263
|
}
|
|
220
264
|
|
|
221
265
|
return recommendations;
|
|
@@ -250,14 +294,30 @@ function identifyGaps(structure, config) {
|
|
|
250
294
|
*/
|
|
251
295
|
function filterImportantDependencies(deps) {
|
|
252
296
|
const important = deps.filter(dep => {
|
|
253
|
-
// Framework packages
|
|
297
|
+
// .NET Framework packages
|
|
254
298
|
if (dep.includes('Microsoft.') || dep.includes('System.')) return true;
|
|
255
|
-
// UI libraries
|
|
299
|
+
// Blazor UI libraries
|
|
256
300
|
if (dep.includes('Blazor') || dep.includes('FluentUI') || dep.includes('Mud')) return true;
|
|
257
|
-
//
|
|
301
|
+
// .NET common tools
|
|
258
302
|
if (dep.includes('Hangfire') || dep.includes('Serilog') || dep.includes('AutoMapper')) return true;
|
|
259
|
-
// AI/ML
|
|
303
|
+
// AI/ML (.NET)
|
|
260
304
|
if (dep.includes('Agents') || dep.includes('AI') || dep.includes('OpenAI')) return true;
|
|
305
|
+
// JS: frontend frameworks
|
|
306
|
+
if (['next', 'react', 'react-dom', 'vue', 'nuxt', 'svelte', 'astro'].includes(dep)) return true;
|
|
307
|
+
// JS: backend frameworks
|
|
308
|
+
if (['express', 'fastify', 'hono', 'koa', 'nestjs', '@nestjs/core'].includes(dep)) return true;
|
|
309
|
+
// JS: databases / ORMs
|
|
310
|
+
if (['prisma', '@prisma/client', 'supabase', '@supabase/supabase-js', 'drizzle-orm', 'mongoose', 'typeorm'].includes(dep)) return true;
|
|
311
|
+
// JS: auth
|
|
312
|
+
if (['next-auth', 'clerk', '@clerk/nextjs', 'lucia', 'better-auth'].includes(dep)) return true;
|
|
313
|
+
// JS: validation / schema
|
|
314
|
+
if (['zod', 'yup', 'joi', 'valibot'].includes(dep)) return true;
|
|
315
|
+
// JS: testing
|
|
316
|
+
if (['vitest', 'jest', '@jest/core', 'mocha', 'cypress', 'playwright'].includes(dep)) return true;
|
|
317
|
+
// JS: UI / styling
|
|
318
|
+
if (['tailwindcss', '@mui/material', 'shadcn-ui', 'radix-ui', 'chakra-ui'].includes(dep)) return true;
|
|
319
|
+
// JS: AI
|
|
320
|
+
if (dep.includes('openai') || dep.includes('anthropic') || dep.includes('langchain') || dep.includes('ai')) return true;
|
|
261
321
|
return false;
|
|
262
322
|
});
|
|
263
323
|
|
|
@@ -97,9 +97,11 @@ async function detectStack(projectPath) {
|
|
|
97
97
|
]
|
|
98
98
|
};
|
|
99
99
|
|
|
100
|
+
const globIgnore = ['stacks/**', 'framework/**', 'test/**', 'node_modules/**', '.morph/**'];
|
|
101
|
+
|
|
100
102
|
for (const [stack, globs] of Object.entries(patterns)) {
|
|
101
103
|
for (const pattern of globs) {
|
|
102
|
-
const files = await glob(pattern, { cwd: projectPath, nodir: true });
|
|
104
|
+
const files = await glob(pattern, { cwd: projectPath, nodir: true, ignore: globIgnore });
|
|
103
105
|
if (files.length > 0) {
|
|
104
106
|
return stack;
|
|
105
107
|
}
|
|
@@ -113,7 +115,10 @@ async function detectStack(projectPath) {
|
|
|
113
115
|
* Detect architecture pattern
|
|
114
116
|
*/
|
|
115
117
|
async function detectArchitecture(projectPath) {
|
|
118
|
+
const globIgnore = ['stacks/**', 'framework/**', 'test/**', 'node_modules/**', '.morph/**'];
|
|
119
|
+
|
|
116
120
|
const checks = [
|
|
121
|
+
// .NET: Clean Architecture
|
|
117
122
|
{
|
|
118
123
|
pattern: 'clean-architecture',
|
|
119
124
|
indicators: [
|
|
@@ -122,13 +127,15 @@ async function detectArchitecture(projectPath) {
|
|
|
122
127
|
existsSync(join(projectPath, 'src', 'Infrastructure'))
|
|
123
128
|
]
|
|
124
129
|
},
|
|
130
|
+
// .NET: CQRS
|
|
125
131
|
{
|
|
126
132
|
pattern: 'cqrs',
|
|
127
133
|
indicators: [
|
|
128
|
-
await glob('**/Commands/**/*.cs', { cwd: projectPath }).then(f => f.length > 0),
|
|
129
|
-
await glob('**/Queries/**/*.cs', { cwd: projectPath }).then(f => f.length > 0)
|
|
134
|
+
await glob('**/Commands/**/*.cs', { cwd: projectPath, ignore: globIgnore }).then(f => f.length > 0),
|
|
135
|
+
await glob('**/Queries/**/*.cs', { cwd: projectPath, ignore: globIgnore }).then(f => f.length > 0)
|
|
130
136
|
]
|
|
131
137
|
},
|
|
138
|
+
// .NET: MVC
|
|
132
139
|
{
|
|
133
140
|
pattern: 'mvc',
|
|
134
141
|
indicators: [
|
|
@@ -136,6 +143,31 @@ async function detectArchitecture(projectPath) {
|
|
|
136
143
|
existsSync(join(projectPath, 'Models')),
|
|
137
144
|
existsSync(join(projectPath, 'Views'))
|
|
138
145
|
]
|
|
146
|
+
},
|
|
147
|
+
// JS: CLI / Library (bin + src + lib)
|
|
148
|
+
{
|
|
149
|
+
pattern: 'cli-library',
|
|
150
|
+
indicators: [
|
|
151
|
+
existsSync(join(projectPath, 'bin')),
|
|
152
|
+
existsSync(join(projectPath, 'src')),
|
|
153
|
+
existsSync(join(projectPath, 'package.json'))
|
|
154
|
+
]
|
|
155
|
+
},
|
|
156
|
+
// JS: Next.js App Router
|
|
157
|
+
{
|
|
158
|
+
pattern: 'nextjs-app-router',
|
|
159
|
+
indicators: [
|
|
160
|
+
existsSync(join(projectPath, 'app')),
|
|
161
|
+
await glob('app/**/*.{js,ts,jsx,tsx}', { cwd: projectPath, ignore: globIgnore }).then(f => f.length > 0)
|
|
162
|
+
]
|
|
163
|
+
},
|
|
164
|
+
// JS: Express MVC
|
|
165
|
+
{
|
|
166
|
+
pattern: 'express-mvc',
|
|
167
|
+
indicators: [
|
|
168
|
+
existsSync(join(projectPath, 'src', 'routes')) || existsSync(join(projectPath, 'routes')),
|
|
169
|
+
existsSync(join(projectPath, 'src', 'controllers')) || existsSync(join(projectPath, 'controllers'))
|
|
170
|
+
]
|
|
139
171
|
}
|
|
140
172
|
];
|
|
141
173
|
|
|
@@ -176,47 +208,43 @@ async function detectUILibrary(projectPath) {
|
|
|
176
208
|
*/
|
|
177
209
|
async function detectPatterns(projectPath) {
|
|
178
210
|
const patterns = [];
|
|
211
|
+
// Ignore template/vendor directories; keep test/ accessible for JS test detection
|
|
212
|
+
const globIgnore = ['stacks/**', 'framework/**', 'test/fixtures/**', 'node_modules/**', '.morph/**'];
|
|
179
213
|
|
|
180
|
-
//
|
|
181
|
-
const
|
|
182
|
-
{
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
},
|
|
186
|
-
{
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
{
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
},
|
|
198
|
-
{
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
},
|
|
202
|
-
{
|
|
203
|
-
name: 'Hangfire Jobs',
|
|
204
|
-
glob: '**/*Job.cs'
|
|
205
|
-
},
|
|
206
|
-
{
|
|
207
|
-
name: 'AI Agents',
|
|
208
|
-
glob: '**/*Agent.cs'
|
|
209
|
-
},
|
|
210
|
-
{
|
|
211
|
-
name: 'Unit Tests',
|
|
212
|
-
glob: '**/*.Tests/**/*.cs'
|
|
213
|
-
}
|
|
214
|
+
// C# / .NET patterns
|
|
215
|
+
const dotnetChecks = [
|
|
216
|
+
{ name: 'Repository Pattern', glob: '**/*Repository.cs' },
|
|
217
|
+
{ name: 'Service Layer', glob: '**/*Service.cs' },
|
|
218
|
+
{ name: 'DTOs', glob: '**/*Dto.cs' },
|
|
219
|
+
{ name: 'Entity Framework', glob: '**/Migrations/**/*.cs' },
|
|
220
|
+
{ name: 'Dependency Injection', glob: '**/DependencyInjection.cs' },
|
|
221
|
+
{ name: 'Hangfire Jobs', glob: '**/*Job.cs' },
|
|
222
|
+
{ name: 'AI Agents', glob: '**/*Agent.cs' },
|
|
223
|
+
{ name: 'Unit Tests', glob: '**/*.Tests/**/*.cs' }
|
|
224
|
+
];
|
|
225
|
+
|
|
226
|
+
// JavaScript / TypeScript patterns
|
|
227
|
+
const jsChecks = [
|
|
228
|
+
{ name: 'Service Layer', glob: 'src/**/*Service.js' },
|
|
229
|
+
{ name: 'Service Layer', glob: 'src/**/*Service.ts' },
|
|
230
|
+
{ name: 'Repository Pattern', glob: 'src/**/*Repository.js' },
|
|
231
|
+
{ name: 'Repository Pattern', glob: 'src/**/*Repository.ts' },
|
|
232
|
+
{ name: 'API Routes', glob: 'src/**/routes/**/*.{js,ts}' },
|
|
233
|
+
{ name: 'API Routes', glob: 'src/commands/**/*.{js,ts}' },
|
|
234
|
+
{ name: 'Unit Tests', glob: 'test/**/*.test.{js,ts}' },
|
|
235
|
+
{ name: 'Unit Tests', glob: 'src/**/*.test.{js,ts}' },
|
|
236
|
+
{ name: 'Unit Tests', glob: 'src/**/*.spec.{js,ts}' }
|
|
214
237
|
];
|
|
215
238
|
|
|
216
|
-
|
|
217
|
-
|
|
239
|
+
const allChecks = [...dotnetChecks, ...jsChecks];
|
|
240
|
+
const seen = new Set();
|
|
241
|
+
|
|
242
|
+
for (const { name, glob: pattern } of allChecks) {
|
|
243
|
+
if (seen.has(name)) continue;
|
|
244
|
+
const files = await glob(pattern, { cwd: projectPath, nodir: true, ignore: globIgnore });
|
|
218
245
|
if (files.length > 0) {
|
|
219
246
|
patterns.push(name);
|
|
247
|
+
seen.add(name);
|
|
220
248
|
}
|
|
221
249
|
}
|
|
222
250
|
|
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fusion Executor — F-Thread best-of-N aggregation
|
|
3
|
+
*
|
|
4
|
+
* Runs N parallel agents on the same prompt, collects results,
|
|
5
|
+
* then aggregates using one of three strategies:
|
|
6
|
+
* - best-of-n: score each result, pick highest (automated)
|
|
7
|
+
* - consensus: merge common elements from all results
|
|
8
|
+
* - manual-select: present all N to user for selection
|
|
9
|
+
*
|
|
10
|
+
* Returns: { winner, allResults, scores }
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { createThread, startThread, completeThread, failThread, THREAD_TYPES } from '../threads/thread-manager.js';
|
|
14
|
+
import { recordEvent, generateAsciiChart } from '../analytics/analytics-engine.js';
|
|
15
|
+
|
|
16
|
+
// ============================================================================
|
|
17
|
+
// Scoring
|
|
18
|
+
// ============================================================================
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Score a fusion result based on completeness, compliance, and quality
|
|
22
|
+
* @param {Object} result - Agent result object
|
|
23
|
+
* @param {Object} [rubric] - Scoring rubric
|
|
24
|
+
* @returns {number} Score 0-100
|
|
25
|
+
*/
|
|
26
|
+
export function scoreResult(result, rubric = {}) {
|
|
27
|
+
let score = 0;
|
|
28
|
+
const weights = {
|
|
29
|
+
completeness: rubric.completenessWeight || 40,
|
|
30
|
+
checkpointCompliance: rubric.complianceWeight || 30,
|
|
31
|
+
codeQuality: rubric.qualityWeight || 30
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
// Completeness: does it have all required deliverables?
|
|
35
|
+
if (result.deliverables) {
|
|
36
|
+
const required = rubric.requiredDeliverables?.length || 1;
|
|
37
|
+
const provided = Object.keys(result.deliverables).length;
|
|
38
|
+
score += weights.completeness * Math.min(provided / required, 1.0);
|
|
39
|
+
} else if (result.content) {
|
|
40
|
+
// Estimate completeness from content length
|
|
41
|
+
const targetLength = rubric.targetLength || 1000;
|
|
42
|
+
score += weights.completeness * Math.min(result.content.length / targetLength, 1.0);
|
|
43
|
+
} else {
|
|
44
|
+
score += weights.completeness * 0.5; // Partial credit
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Checkpoint compliance: did it follow patterns?
|
|
48
|
+
if (result.checkpointsPassed !== undefined) {
|
|
49
|
+
const total = (result.checkpointsPassed || 0) + (result.checkpointsFailed || 0);
|
|
50
|
+
const passRate = total > 0 ? result.checkpointsPassed / total : 0.8; // Assume 80% if unknown
|
|
51
|
+
score += weights.checkpointCompliance * passRate;
|
|
52
|
+
} else {
|
|
53
|
+
score += weights.checkpointCompliance * 0.8; // Default: assume compliant
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Code quality: based on error count and warnings
|
|
57
|
+
const errors = result.errors || 0;
|
|
58
|
+
const warnings = result.warnings || 0;
|
|
59
|
+
const qualityScore = Math.max(0, 1.0 - (errors * 0.2) - (warnings * 0.05));
|
|
60
|
+
score += weights.codeQuality * qualityScore;
|
|
61
|
+
|
|
62
|
+
return Math.round(score);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// ============================================================================
|
|
66
|
+
// Aggregation Strategies
|
|
67
|
+
// ============================================================================
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Best-of-N aggregation: score each result, return highest scorer
|
|
71
|
+
* @param {Array} results - Array of agent results
|
|
72
|
+
* @param {Object} rubric - Scoring rubric
|
|
73
|
+
* @returns {Object} { winner, scores }
|
|
74
|
+
*/
|
|
75
|
+
export function aggregateBestOfN(results, rubric = {}) {
|
|
76
|
+
const scored = results.map((result, idx) => ({
|
|
77
|
+
index: idx,
|
|
78
|
+
agentId: result.agentId || `agent-${idx}`,
|
|
79
|
+
result,
|
|
80
|
+
score: scoreResult(result, rubric)
|
|
81
|
+
}));
|
|
82
|
+
|
|
83
|
+
scored.sort((a, b) => b.score - a.score);
|
|
84
|
+
const winner = scored[0];
|
|
85
|
+
|
|
86
|
+
return {
|
|
87
|
+
winner: winner.result,
|
|
88
|
+
winnerId: winner.agentId,
|
|
89
|
+
winnerScore: winner.score,
|
|
90
|
+
scores: scored.map(s => ({
|
|
91
|
+
agentId: s.agentId,
|
|
92
|
+
score: s.score,
|
|
93
|
+
rank: scored.indexOf(s) + 1
|
|
94
|
+
}))
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Consensus aggregation: merge common elements from all results
|
|
100
|
+
* @param {Array} results - Array of agent results
|
|
101
|
+
* @returns {Object} { merged, commonElements }
|
|
102
|
+
*/
|
|
103
|
+
export function aggregateConsensus(results) {
|
|
104
|
+
if (results.length === 0) return { merged: {}, commonElements: [] };
|
|
105
|
+
if (results.length === 1) return { merged: results[0], commonElements: [] };
|
|
106
|
+
|
|
107
|
+
// Find common keys across all results
|
|
108
|
+
const allKeys = results.map(r => Object.keys(r));
|
|
109
|
+
const commonKeys = allKeys[0].filter(key =>
|
|
110
|
+
allKeys.every(keys => keys.includes(key))
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
const merged = {};
|
|
114
|
+
const commonElements = [];
|
|
115
|
+
|
|
116
|
+
for (const key of commonKeys) {
|
|
117
|
+
const values = results.map(r => r[key]);
|
|
118
|
+
|
|
119
|
+
// For strings: use longest (most complete) version
|
|
120
|
+
if (values.every(v => typeof v === 'string')) {
|
|
121
|
+
merged[key] = values.reduce((longest, v) =>
|
|
122
|
+
v.length > longest.length ? v : longest, ''
|
|
123
|
+
);
|
|
124
|
+
commonElements.push(key);
|
|
125
|
+
}
|
|
126
|
+
// For arrays: union of all arrays
|
|
127
|
+
else if (values.every(v => Array.isArray(v))) {
|
|
128
|
+
merged[key] = [...new Set(values.flat())];
|
|
129
|
+
commonElements.push(key);
|
|
130
|
+
}
|
|
131
|
+
// For objects: merge recursively (simple one level)
|
|
132
|
+
else if (values.every(v => v && typeof v === 'object')) {
|
|
133
|
+
merged[key] = Object.assign({}, ...values);
|
|
134
|
+
commonElements.push(key);
|
|
135
|
+
}
|
|
136
|
+
// For primitives: use majority value
|
|
137
|
+
else {
|
|
138
|
+
const valueCounts = values.reduce((acc, v) => {
|
|
139
|
+
const key2 = String(v);
|
|
140
|
+
acc[key2] = (acc[key2] || 0) + 1;
|
|
141
|
+
return acc;
|
|
142
|
+
}, {});
|
|
143
|
+
const [majority] = Object.entries(valueCounts).sort(([, a], [, b]) => b - a);
|
|
144
|
+
merged[key] = majority ? JSON.parse(majority[0]) : values[0];
|
|
145
|
+
commonElements.push(key);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return { merged, commonElements };
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// ============================================================================
|
|
153
|
+
// Main Fusion Runner
|
|
154
|
+
// ============================================================================
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Run fusion execution: spawn N agents in parallel, collect and aggregate results
|
|
158
|
+
* @param {Object} opts
|
|
159
|
+
* @param {string} opts.feature - Feature name
|
|
160
|
+
* @param {string} opts.prompt - Prompt/mission for all agents
|
|
161
|
+
* @param {number} [opts.count=3] - Number of agents to run
|
|
162
|
+
* @param {string} [opts.strategy='best-of-n'] - Aggregation strategy
|
|
163
|
+
* @param {string[]} [opts.agents] - Agent IDs to use (will use thread types if not specified)
|
|
164
|
+
* @param {Object} [opts.rubric] - Scoring rubric for best-of-n
|
|
165
|
+
* @returns {Promise<Object>} { winner, allResults, scores, strategy, threads }
|
|
166
|
+
*/
|
|
167
|
+
export async function runFusion(prompt, {
|
|
168
|
+
feature,
|
|
169
|
+
count = 3,
|
|
170
|
+
strategy = 'best-of-n',
|
|
171
|
+
agents = null,
|
|
172
|
+
rubric = {}
|
|
173
|
+
} = {}) {
|
|
174
|
+
const { randomUUID } = await import('crypto');
|
|
175
|
+
const sessionId = randomUUID();
|
|
176
|
+
const agentList = agents || Array.from({ length: count }, (_, i) => `fusion-agent-${i + 1}`);
|
|
177
|
+
const threads = [];
|
|
178
|
+
const results = [];
|
|
179
|
+
|
|
180
|
+
recordEvent({
|
|
181
|
+
type: 'fusion_started',
|
|
182
|
+
feature,
|
|
183
|
+
data: { count, strategy, agents: agentList }
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
// Create and start all fusion threads simultaneously
|
|
187
|
+
for (let i = 0; i < agentList.length; i++) {
|
|
188
|
+
const thread = createThread({
|
|
189
|
+
feature,
|
|
190
|
+
type: THREAD_TYPES.FUSION,
|
|
191
|
+
agent: agentList[i],
|
|
192
|
+
mission: prompt,
|
|
193
|
+
meta: { fusionIndex: i, strategy }
|
|
194
|
+
});
|
|
195
|
+
startThread(thread.id);
|
|
196
|
+
threads.push(thread);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
console.log(` [fusion] ${threads.length} agents spawned. Collecting results...`);
|
|
200
|
+
|
|
201
|
+
// In a real system, results would come from actual sub-agent execution.
|
|
202
|
+
// Here we return thread configs for the calling code to use with Task tool.
|
|
203
|
+
// The aggregation is designed to be called after results are collected.
|
|
204
|
+
|
|
205
|
+
return {
|
|
206
|
+
id: sessionId,
|
|
207
|
+
threads,
|
|
208
|
+
strategy,
|
|
209
|
+
feature,
|
|
210
|
+
prompt,
|
|
211
|
+
agentConfigs: agentList.map((agentId, i) => ({
|
|
212
|
+
agentId,
|
|
213
|
+
threadId: threads[i].id,
|
|
214
|
+
mission: prompt
|
|
215
|
+
})),
|
|
216
|
+
aggregate: (collectedResults) => aggregateResults(collectedResults, strategy, rubric)
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Aggregate collected results from N agents
|
|
222
|
+
* @param {Array} results - Array of result objects from agents
|
|
223
|
+
* @param {string} strategy - Aggregation strategy
|
|
224
|
+
* @param {Object} [rubric] - Scoring rubric for best-of-n
|
|
225
|
+
* @returns {Object} Aggregated result
|
|
226
|
+
*/
|
|
227
|
+
export function aggregateResults(results, strategy = 'best-of-n', rubric = {}) {
|
|
228
|
+
if (!results || results.length === 0) {
|
|
229
|
+
throw new Error('No results to aggregate');
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
let aggregated;
|
|
233
|
+
|
|
234
|
+
switch (strategy) {
|
|
235
|
+
case 'best-of-n':
|
|
236
|
+
aggregated = aggregateBestOfN(results, rubric);
|
|
237
|
+
break;
|
|
238
|
+
|
|
239
|
+
case 'consensus':
|
|
240
|
+
const { merged, commonElements } = aggregateConsensus(results);
|
|
241
|
+
aggregated = {
|
|
242
|
+
winner: merged,
|
|
243
|
+
winnerId: 'consensus',
|
|
244
|
+
scores: results.map((_, i) => ({
|
|
245
|
+
agentId: `agent-${i}`,
|
|
246
|
+
score: 0,
|
|
247
|
+
rank: i + 1
|
|
248
|
+
})),
|
|
249
|
+
commonElements
|
|
250
|
+
};
|
|
251
|
+
break;
|
|
252
|
+
|
|
253
|
+
case 'manual-select':
|
|
254
|
+
aggregated = {
|
|
255
|
+
winner: null,
|
|
256
|
+
winnerId: null,
|
|
257
|
+
allResults: results,
|
|
258
|
+
requiresUserSelection: true,
|
|
259
|
+
scores: results.map((r, i) => ({
|
|
260
|
+
agentId: r.agentId || `agent-${i}`,
|
|
261
|
+
score: scoreResult(r, rubric),
|
|
262
|
+
rank: i + 1
|
|
263
|
+
}))
|
|
264
|
+
};
|
|
265
|
+
break;
|
|
266
|
+
|
|
267
|
+
default:
|
|
268
|
+
throw new Error(`Unknown aggregation strategy: ${strategy}`);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
return {
|
|
272
|
+
...aggregated,
|
|
273
|
+
strategy,
|
|
274
|
+
totalAgents: results.length,
|
|
275
|
+
allResults: results
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Display fusion results summary
|
|
281
|
+
* @param {Object} fusionResult - Result from aggregateResults
|
|
282
|
+
*/
|
|
283
|
+
export function displayFusionSummary(fusionResult) {
|
|
284
|
+
const { strategy, totalAgents, scores, winner, requiresUserSelection } = fusionResult;
|
|
285
|
+
|
|
286
|
+
console.log(`\n Fusion Results — ${strategy} (${totalAgents} agents)\n`);
|
|
287
|
+
console.log(' ' + '─'.repeat(50));
|
|
288
|
+
|
|
289
|
+
if (scores) {
|
|
290
|
+
const scoreData = {};
|
|
291
|
+
scores.forEach(s => { scoreData[s.agentId] = s.score; });
|
|
292
|
+
console.log(generateAsciiChart(scoreData, { title: 'Agent Scores' }));
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
if (requiresUserSelection) {
|
|
296
|
+
console.log('\n Manual selection required — review results above');
|
|
297
|
+
} else if (winner) {
|
|
298
|
+
console.log(`\n Winner: ${fusionResult.winnerId || 'consensus'}`);
|
|
299
|
+
if (fusionResult.winnerScore !== undefined) {
|
|
300
|
+
console.log(` Score: ${fusionResult.winnerScore}/100`);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
console.log('');
|
|
304
|
+
}
|