@polymorphism-tech/morph-spec 4.3.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 +155 -0
- package/bin/morph-spec.js +2 -2
- package/bin/task-manager.cjs +102 -14
- package/package.json +1 -1
- package/src/commands/agents/agents-fuse.js +2 -1
- package/src/commands/project/detect-agents.js +31 -1
- package/src/commands/project/detect.js +11 -1
- package/src/commands/project/doctor.js +52 -52
- package/src/commands/project/init.js +8 -1
- package/src/commands/project/update.js +12 -2
- package/src/commands/state/advance-phase.js +19 -4
- package/src/commands/state/state.js +14 -12
- package/src/commands/tasks/task.js +1 -1
- package/src/commands/threads/thread-template.js +1 -1
- package/src/core/state/state-manager.js +19 -15
- package/src/core/workflows/workflow-detector.js +14 -1
- package/src/lib/checkpoints/checkpoint-hooks.js +8 -3
- 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/generators/recap-generator.js +30 -10
- package/src/lib/validators/validation-runner.js +8 -24
- package/src/utils/hooks-installer.js +69 -0
package/CLAUDE.md
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
# MORPH-SPEC - Claude Code Instructions
|
|
2
|
+
|
|
3
|
+
> by Polymorphism Tech
|
|
4
|
+
|
|
5
|
+
Spec-driven development system. Specialized agent hub for multi-stack projects with Infrastructure as Code.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## CRITICAL RULES
|
|
10
|
+
|
|
11
|
+
### NEVER:
|
|
12
|
+
- Skip to code without a specification
|
|
13
|
+
- Implement without design approval
|
|
14
|
+
- Ignore standards in `.morph/standards/`
|
|
15
|
+
- Create infrastructure manually
|
|
16
|
+
- Generate code without defined contracts
|
|
17
|
+
|
|
18
|
+
### ALWAYS:
|
|
19
|
+
- Follow the 5 mandatory phases
|
|
20
|
+
- Generate outputs in `.morph/project/outputs/{feature}/`
|
|
21
|
+
- Document decisions in `decisions.md`
|
|
22
|
+
- Checkpoint every 3 implemented tasks
|
|
23
|
+
- Use Infrastructure as Code
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## WORKFLOW (5 Phases)
|
|
28
|
+
|
|
29
|
+
| Phase | Trigger | Actions | Output | Pause? |
|
|
30
|
+
|-------|---------|---------|--------|--------|
|
|
31
|
+
| **1. SETUP** | Feature request | Read project context, identify stack, activate agents, create folder | proposal.md | No |
|
|
32
|
+
| **2. DESIGN** | Setup done | Spec + contracts + decisions + costs | spec.md, contracts, decisions.md | **Yes** |
|
|
33
|
+
| **3. CLARIFY** | Design approved | Ambiguities, questions, edge cases | Updated spec | No |
|
|
34
|
+
| **4. TASKS** | Clarifications resolved | Break into tasks, ordering, checkpoints, dependencies | tasks.md | **Yes** |
|
|
35
|
+
| **5. IMPLEMENT** | Tasks approved | Task by task, checkpoint every 3, recap | Code + recap.md | No |
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## PROJECT STRUCTURE
|
|
40
|
+
|
|
41
|
+
```
|
|
42
|
+
project/
|
|
43
|
+
├── CLAUDE.md
|
|
44
|
+
├── .morph/
|
|
45
|
+
│ ├── config/ # config.json, agents.json
|
|
46
|
+
│ ├── standards/ # coding, architecture, stack-specific
|
|
47
|
+
│ ├── templates/ # Output templates
|
|
48
|
+
│ ├── project/
|
|
49
|
+
│ │ ├── context/ # detection-log.md, project.md
|
|
50
|
+
│ │ ├── standards/ # inferred.md, overrides.md
|
|
51
|
+
│ │ └── outputs/{feature}/ # Features in development
|
|
52
|
+
│ │ ├── proposal.md, spec.md, tasks.md
|
|
53
|
+
│ │ ├── contracts, decisions.md, recap.md
|
|
54
|
+
└── .claude/commands/ # Slash commands
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## AGENTS
|
|
60
|
+
|
|
61
|
+
### Tier 1 — Orchestrators (Always Active)
|
|
62
|
+
|
|
63
|
+
| Agent | Responsibilities |
|
|
64
|
+
|-------|-----------------|
|
|
65
|
+
| **Standards Architect** | Standards, naming, review, cross-cutting concerns |
|
|
66
|
+
| **AI System Architect** | Agent systems, AI integrations, LLM orchestration |
|
|
67
|
+
| **Thread Orchestrator** | Parallel execution, thread management, context optimization |
|
|
68
|
+
|
|
69
|
+
### Tier 2 — Domain Leaders
|
|
70
|
+
|
|
71
|
+
| Agent | Responsibilities |
|
|
72
|
+
|-------|-----------------|
|
|
73
|
+
| **Tech Lead** | Architecture decisions, code quality, task breakdown |
|
|
74
|
+
| **DevOps Engineer** | CI/CD, deployment pipelines, infrastructure |
|
|
75
|
+
| **QA Lead** | Test strategies, validation, quality gates |
|
|
76
|
+
|
|
77
|
+
### Tier 3 — Specialists (Activated by keywords)
|
|
78
|
+
|
|
79
|
+
| Agent | Keywords |
|
|
80
|
+
|-------|---------|
|
|
81
|
+
| **Next.js Expert** | nextjs, react, frontend, app router, pages |
|
|
82
|
+
| **Blazor Builder** | blazor, razor, mudblazor, fluent ui |
|
|
83
|
+
| **API Designer** | api, endpoint, rest, openapi, swagger |
|
|
84
|
+
| **.NET Senior** | dotnet, csharp, minimal api, ef core |
|
|
85
|
+
| **EF Modeler** | entity framework, migrations, dbcontext |
|
|
86
|
+
| **Azure Architect** | azure, container apps, bicep, app service |
|
|
87
|
+
| **Bicep Architect** | bicep, iac, arm template, azure resources |
|
|
88
|
+
| **Azure Deploy Specialist** | deploy, ci/cd, github actions, azure devops |
|
|
89
|
+
| **Container Specialist** | docker, dockerfile, compose, kubernetes |
|
|
90
|
+
| **Supabase Expert** | supabase, postgresql, rls, realtime, pgvector |
|
|
91
|
+
| **Azure Identity** | auth, authentication, entra id, jwt, oauth |
|
|
92
|
+
| **Clerk Auth** | clerk, auth0, authentication, social login |
|
|
93
|
+
| **Asaas Financial** | payment, billing, subscription, asaas |
|
|
94
|
+
| **Resend Email** | email, resend, smtp, notifications |
|
|
95
|
+
| **Hangfire Orchestrator** | background jobs, queue, scheduling, hangfire |
|
|
96
|
+
| **MS Agent Expert** | semantic kernel, microsoft agents, ai pipeline |
|
|
97
|
+
| **UI/UX Designer** | design, ux, wireframe, mockup, figma |
|
|
98
|
+
| **SEO Growth Hacker** | seo, performance, analytics, lighthouse |
|
|
99
|
+
| **PO/PM Advisor** | product, roadmap, requirements, user stories |
|
|
100
|
+
| **Prompt Engineer** | prompts, system prompts, llm, few-shot |
|
|
101
|
+
| **Context Optimizer** | context window, token optimization, chunking |
|
|
102
|
+
| **Vector Search Expert** | vector db, embeddings, semantic search, rag |
|
|
103
|
+
| **Observability Expert** | logging, tracing, metrics, opentelemetry |
|
|
104
|
+
| **Code Analyzer** | refactor, analysis, code review, complexity |
|
|
105
|
+
| **Testing Specialist** | tests, coverage, tdd, bdd, vitest, jest, xunit |
|
|
106
|
+
|
|
107
|
+
### Tier 4 — Validators
|
|
108
|
+
|
|
109
|
+
| Agent | Responsibilities |
|
|
110
|
+
|-------|-----------------|
|
|
111
|
+
| **Contract Compliance Validator** | Ensures implementation matches spec contracts |
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## TEMPLATES
|
|
116
|
+
|
|
117
|
+
> ALWAYS use `morph-spec template render` to render. NEVER fill placeholders manually.
|
|
118
|
+
|
|
119
|
+
### Placeholders: `{{PLACEHOLDER}}`
|
|
120
|
+
|
|
121
|
+
| Placeholder | Example |
|
|
122
|
+
|-------------|---------|
|
|
123
|
+
| `{{FEATURE_NAME}}` | `scheduled-reports` |
|
|
124
|
+
| `{{FEATURE_NAME_PASCAL}}` | `ScheduledReports` |
|
|
125
|
+
| `{{FEATURE_NAME_CAMEL}}` | `scheduledReports` |
|
|
126
|
+
| `{{STACK}}` | `blazor-azure` or `nextjs-supabase` |
|
|
127
|
+
| `{{DATE}}`, `{{AUTHOR}}`, `{{NAMESPACE}}` | Auto from config.json |
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
## COMMANDS
|
|
132
|
+
|
|
133
|
+
| Command | Action |
|
|
134
|
+
|---------|--------|
|
|
135
|
+
| `/morph-proposal {feature}` | Full spec pipeline (phases 1-4, auto-continuation with pauses) |
|
|
136
|
+
| `/morph-apply {feature}` | Implement feature (phase 5) |
|
|
137
|
+
| `/morph-status` | Dashboard |
|
|
138
|
+
| `/morph-archive {feature}` | Archive completed feature |
|
|
139
|
+
| `/morph-infra {action}` | Manage infrastructure |
|
|
140
|
+
| `/morph-preflight` | Pre-deploy validation |
|
|
141
|
+
| `/morph-troubleshoot {error}` | Error troubleshooting |
|
|
142
|
+
|
|
143
|
+
---
|
|
144
|
+
|
|
145
|
+
## REFERENCES
|
|
146
|
+
|
|
147
|
+
- `.morph/config/config.json` — Project configuration
|
|
148
|
+
- `.morph/config/agents.json` — Full agent definitions
|
|
149
|
+
- `.morph/standards/` — Coding, architecture, stack-specific standards
|
|
150
|
+
- `.morph/project/context/` — Detection log, project context
|
|
151
|
+
- `.morph/project/standards/inferred.md` — Auto-detected project standards
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
155
|
+
*MORPH-SPEC v4.3.0 by Polymorphism Tech*
|
package/bin/morph-spec.js
CHANGED
|
@@ -141,7 +141,7 @@ program
|
|
|
141
141
|
program
|
|
142
142
|
.command('doctor')
|
|
143
143
|
.description('Check MORPH installation health')
|
|
144
|
-
.option('--
|
|
144
|
+
.option('--full', 'Run full health check (lib files, commands, HOPs, standards, agents, state)')
|
|
145
145
|
.action(doctorCommand);
|
|
146
146
|
|
|
147
147
|
program
|
|
@@ -256,7 +256,7 @@ generateCommand
|
|
|
256
256
|
.description('Auto-generate recap.md from project data (state, validation, contracts)')
|
|
257
257
|
.option('--quiet', 'Suppress output')
|
|
258
258
|
.action(async (feature, options) => {
|
|
259
|
-
const { generateRecap } = await import('../src/lib/recap-generator.js');
|
|
259
|
+
const { generateRecap } = await import('../src/lib/generators/recap-generator.js');
|
|
260
260
|
await generateRecap('.', feature, options);
|
|
261
261
|
});
|
|
262
262
|
|
package/bin/task-manager.cjs
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
const fs = require('fs').promises;
|
|
11
|
+
const fsSync = require('fs');
|
|
11
12
|
const path = require('path');
|
|
12
13
|
|
|
13
14
|
// Simple ANSI color helpers (chalk v5 is ESM-only, can't require in CJS)
|
|
@@ -22,6 +23,68 @@ const chalk = {
|
|
|
22
23
|
bold: (s) => `\x1b[1m${s}\x1b[0m`,
|
|
23
24
|
};
|
|
24
25
|
|
|
26
|
+
// ============================================================================
|
|
27
|
+
// v3 Schema Helpers
|
|
28
|
+
// ============================================================================
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Parse tasks.md to extract task stubs for v3 state format.
|
|
32
|
+
* Looks for headings like: ### T001 — Task title
|
|
33
|
+
*/
|
|
34
|
+
async function parseTasksMd(featureName) {
|
|
35
|
+
const tasksPath = path.join(process.cwd(), `.morph/project/outputs/${featureName}/tasks.md`);
|
|
36
|
+
let content = '';
|
|
37
|
+
try {
|
|
38
|
+
content = await fs.readFile(tasksPath, 'utf-8');
|
|
39
|
+
} catch {
|
|
40
|
+
return [];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const tasks = [];
|
|
44
|
+
const headingRe = /^###\s+(T\d+)\s+[—–-]\s+(.+)$/gm;
|
|
45
|
+
let match;
|
|
46
|
+
while ((match = headingRe.exec(content)) !== null) {
|
|
47
|
+
tasks.push({
|
|
48
|
+
id: match[1],
|
|
49
|
+
title: match[2].trim(),
|
|
50
|
+
status: 'pending',
|
|
51
|
+
dependencies: [],
|
|
52
|
+
files: [],
|
|
53
|
+
checkpoint: null
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
return tasks;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Ensure feature.taskList exists (array of individual task objects).
|
|
61
|
+
* In v3 state, feature.tasks is a counter object {total, completed, ...}.
|
|
62
|
+
* Individual task objects live in feature.taskList.
|
|
63
|
+
*/
|
|
64
|
+
async function ensureTaskList(feature, featureName) {
|
|
65
|
+
if (Array.isArray(feature.tasks)) {
|
|
66
|
+
// v2 format: tasks IS the array
|
|
67
|
+
return feature.tasks;
|
|
68
|
+
}
|
|
69
|
+
// v3 format: use taskList or build from tasks.md
|
|
70
|
+
if (!feature.taskList || feature.taskList.length === 0) {
|
|
71
|
+
feature.taskList = await parseTasksMd(featureName);
|
|
72
|
+
}
|
|
73
|
+
return feature.taskList;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* After modifying taskList, sync counts back to feature.tasks counter.
|
|
78
|
+
*/
|
|
79
|
+
function syncCounters(feature) {
|
|
80
|
+
if (Array.isArray(feature.tasks)) return; // v2, nothing to sync
|
|
81
|
+
const list = feature.taskList || [];
|
|
82
|
+
feature.tasks.completed = list.filter(t => t.status === 'completed').length;
|
|
83
|
+
feature.tasks.inProgress = list.filter(t => t.status === 'in_progress').length;
|
|
84
|
+
feature.tasks.pending = list.filter(t => t.status === 'pending').length;
|
|
85
|
+
// Don't touch feature.tasks.total — it is the authoritative count
|
|
86
|
+
}
|
|
87
|
+
|
|
25
88
|
class TaskManager {
|
|
26
89
|
constructor(statePath = '.morph/state.json') {
|
|
27
90
|
this.statePath = statePath;
|
|
@@ -46,7 +109,12 @@ class TaskManager {
|
|
|
46
109
|
* Save state.json
|
|
47
110
|
*/
|
|
48
111
|
async saveState(state) {
|
|
49
|
-
state.
|
|
112
|
+
// v3 state uses metadata.lastUpdated, v2 used project.updatedAt
|
|
113
|
+
if (state.metadata) {
|
|
114
|
+
state.metadata.lastUpdated = new Date().toISOString();
|
|
115
|
+
} else if (state.project) {
|
|
116
|
+
state.project.updatedAt = new Date().toISOString();
|
|
117
|
+
}
|
|
50
118
|
await fs.writeFile(this.statePath, JSON.stringify(state, null, 2), 'utf-8');
|
|
51
119
|
}
|
|
52
120
|
|
|
@@ -65,11 +133,12 @@ class TaskManager {
|
|
|
65
133
|
throw new Error(`Feature '${featureName}' not found in state.json`);
|
|
66
134
|
}
|
|
67
135
|
|
|
136
|
+
const taskList = await ensureTaskList(feature, featureName);
|
|
68
137
|
const results = [];
|
|
69
138
|
const tasksToComplete = [];
|
|
70
139
|
|
|
71
140
|
for (const taskId of taskIds) {
|
|
72
|
-
const task =
|
|
141
|
+
const task = taskList.find(t => t.id === taskId);
|
|
73
142
|
|
|
74
143
|
if (!task) {
|
|
75
144
|
console.error(chalk.red(`❌ Task ${taskId} not found`));
|
|
@@ -82,7 +151,7 @@ class TaskManager {
|
|
|
82
151
|
}
|
|
83
152
|
|
|
84
153
|
// Validate dependencies
|
|
85
|
-
const missingDeps = this.checkDependencies(task,
|
|
154
|
+
const missingDeps = this.checkDependencies(task, taskList);
|
|
86
155
|
if (missingDeps.length > 0) {
|
|
87
156
|
console.error(chalk.red(`❌ Cannot complete ${taskId}: missing dependencies: ${missingDeps.join(', ')}`));
|
|
88
157
|
continue;
|
|
@@ -118,11 +187,12 @@ class TaskManager {
|
|
|
118
187
|
}
|
|
119
188
|
}
|
|
120
189
|
|
|
121
|
-
//
|
|
122
|
-
|
|
190
|
+
// Sync v3 counters then compute progress
|
|
191
|
+
syncCounters(feature);
|
|
192
|
+
feature.progress = this.calculateProgress(taskList);
|
|
123
193
|
|
|
124
194
|
// Auto-checkpoint every 3 tasks
|
|
125
|
-
const recentCompleted = this.getRecentCompleted(
|
|
195
|
+
const recentCompleted = this.getRecentCompleted(taskList, 3);
|
|
126
196
|
if (recentCompleted.length === 3) {
|
|
127
197
|
const lastCheckpoint = feature.checkpoints[feature.checkpoints.length - 1];
|
|
128
198
|
const shouldAutoCheckpoint = !lastCheckpoint ||
|
|
@@ -138,6 +208,21 @@ class TaskManager {
|
|
|
138
208
|
// Save state
|
|
139
209
|
await this.saveState(state);
|
|
140
210
|
|
|
211
|
+
// Run TaskCompleted agent-teams hook for each completed task (non-blocking)
|
|
212
|
+
for (const task of results) {
|
|
213
|
+
try {
|
|
214
|
+
const { executeHook, formatHookResults } = await import('../src/lib/hooks/hook-executor.js');
|
|
215
|
+
const hookResult = await executeHook(process.cwd(), featureName, 'TaskCompleted', {
|
|
216
|
+
taskId: task.id
|
|
217
|
+
});
|
|
218
|
+
if (!hookResult.passed && hookResult.errors.length > 0) {
|
|
219
|
+
console.log(formatHookResults(hookResult, 'TaskCompleted'));
|
|
220
|
+
}
|
|
221
|
+
} catch {
|
|
222
|
+
// Hook executor unavailable — non-blocking
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
141
226
|
// Auto-generate metadata.json (for quick LLM access)
|
|
142
227
|
await this.generateMetadata(featureName, feature);
|
|
143
228
|
|
|
@@ -145,7 +230,7 @@ class TaskManager {
|
|
|
145
230
|
this.displayProgress(feature);
|
|
146
231
|
|
|
147
232
|
// Suggest next task
|
|
148
|
-
const nextTask = this.getNextTask(
|
|
233
|
+
const nextTask = this.getNextTask(taskList);
|
|
149
234
|
if (nextTask) {
|
|
150
235
|
console.log(chalk.cyan(`\n⏭️ Next: ${nextTask.id} - ${nextTask.title}`));
|
|
151
236
|
if (nextTask.dependencies && nextTask.dependencies.length > 0) {
|
|
@@ -167,7 +252,7 @@ class TaskManager {
|
|
|
167
252
|
try {
|
|
168
253
|
console.log(chalk.cyan('\n🔍 Running code validation...'));
|
|
169
254
|
|
|
170
|
-
const { runValidation, formatValidationResults } = await import('../src/lib/validation-runner.js');
|
|
255
|
+
const { runValidation, formatValidationResults } = await import('../src/lib/validators/validation-runner.js');
|
|
171
256
|
const result = await runValidation('.', featureName, { verbose: true });
|
|
172
257
|
|
|
173
258
|
formatValidationResults(result);
|
|
@@ -243,7 +328,7 @@ class TaskManager {
|
|
|
243
328
|
|
|
244
329
|
// Run checkpoint hooks (new enhanced validation system)
|
|
245
330
|
try {
|
|
246
|
-
const { runCheckpointHooks, shouldRunCheckpoint } = await import('../src/lib/checkpoint-hooks.js');
|
|
331
|
+
const { runCheckpointHooks, shouldRunCheckpoint } = await import('../src/lib/checkpoints/checkpoint-hooks.js');
|
|
247
332
|
|
|
248
333
|
if (shouldRunCheckpoint(tasksCompleted, 3)) {
|
|
249
334
|
checkpointResult = await runCheckpointHooks(featureName, checkpointNum);
|
|
@@ -268,7 +353,7 @@ class TaskManager {
|
|
|
268
353
|
// Fallback to old validation if checkpoint-hooks not available
|
|
269
354
|
console.log(chalk.yellow('⚠️ Checkpoint hooks not available, using legacy validation'));
|
|
270
355
|
try {
|
|
271
|
-
const { runValidation } = await import('../src/lib/validation-runner.js');
|
|
356
|
+
const { runValidation } = await import('../src/lib/validators/validation-runner.js');
|
|
272
357
|
const result = await runValidation('.', featureName, { verbose: false });
|
|
273
358
|
validationNote = result.passed
|
|
274
359
|
? ' | Validation: PASSED'
|
|
@@ -333,7 +418,7 @@ class TaskManager {
|
|
|
333
418
|
return;
|
|
334
419
|
}
|
|
335
420
|
|
|
336
|
-
const { extractFeatureMetadata } = await import('../src/lib/metadata-extractor.js');
|
|
421
|
+
const { extractFeatureMetadata } = await import('../src/lib/generators/metadata-extractor.js');
|
|
337
422
|
const metadata = extractFeatureMetadata(feature);
|
|
338
423
|
|
|
339
424
|
const outputPath = path.join(
|
|
@@ -410,7 +495,8 @@ class TaskManager {
|
|
|
410
495
|
throw new Error(`Feature '${featureName}' not found`);
|
|
411
496
|
}
|
|
412
497
|
|
|
413
|
-
const
|
|
498
|
+
const taskList = await ensureTaskList(feature, featureName);
|
|
499
|
+
const task = taskList.find(t => t.id === taskId);
|
|
414
500
|
|
|
415
501
|
if (!task) {
|
|
416
502
|
throw new Error(`Task ${taskId} not found`);
|
|
@@ -422,13 +508,14 @@ class TaskManager {
|
|
|
422
508
|
}
|
|
423
509
|
|
|
424
510
|
// Validate dependencies
|
|
425
|
-
const missingDeps = this.checkDependencies(task,
|
|
511
|
+
const missingDeps = this.checkDependencies(task, taskList);
|
|
426
512
|
if (missingDeps.length > 0) {
|
|
427
513
|
throw new Error(`Cannot start ${taskId}: missing dependencies: ${missingDeps.join(', ')}`);
|
|
428
514
|
}
|
|
429
515
|
|
|
430
516
|
task.status = 'in_progress';
|
|
431
517
|
task.startedAt = new Date().toISOString();
|
|
518
|
+
syncCounters(feature);
|
|
432
519
|
|
|
433
520
|
await this.saveState(state);
|
|
434
521
|
|
|
@@ -446,7 +533,8 @@ class TaskManager {
|
|
|
446
533
|
throw new Error(`Feature '${featureName}' not found`);
|
|
447
534
|
}
|
|
448
535
|
|
|
449
|
-
const
|
|
536
|
+
const taskList = await ensureTaskList(feature, featureName);
|
|
537
|
+
const nextTask = this.getNextTask(taskList);
|
|
450
538
|
|
|
451
539
|
if (nextTask) {
|
|
452
540
|
console.log(chalk.cyan(`\n⏭️ Next task: ${nextTask.id} - ${nextTask.title}`));
|
package/package.json
CHANGED
|
@@ -71,7 +71,8 @@ export async function agentsReviewAggregateCommand(feature, options) {
|
|
|
71
71
|
const agentList = options.agents.split(',').map(a => a.trim());
|
|
72
72
|
const strategy = options.strategy || 'best-of-n';
|
|
73
73
|
|
|
74
|
-
console.log(chalk.
|
|
74
|
+
console.log(chalk.yellow('\n ⚠ [STUB] This command returns mock data. Real aggregation not yet implemented.\n'));
|
|
75
|
+
console.log(chalk.cyan(` Aggregate Reviews — ${feature}\n`));
|
|
75
76
|
console.log(` Agents: ${agentList.join(', ')}`);
|
|
76
77
|
console.log(` Strategy: ${strategy}`);
|
|
77
78
|
console.log('');
|
|
@@ -123,6 +123,34 @@ export function detectAgentsCommand(input, options) {
|
|
|
123
123
|
const config = loadAgentsConfig();
|
|
124
124
|
const result = detectAgents(userInput, config);
|
|
125
125
|
|
|
126
|
+
// Stack-aware filtering: penalize stack-mismatched agents
|
|
127
|
+
try {
|
|
128
|
+
const projectConfigPath = join(process.cwd(), '.morph/config/config.json');
|
|
129
|
+
const projectConfig = JSON.parse(readFileSync(projectConfigPath, 'utf8'));
|
|
130
|
+
const stack = projectConfig?.project?.stack || projectConfig?.project?.language || '';
|
|
131
|
+
const isJsStack = stack === 'nodejs' || stack === 'javascript' || stack === 'nextjs';
|
|
132
|
+
|
|
133
|
+
if (isJsStack) {
|
|
134
|
+
const dotnetKeywords = ['dotnet', '.net', 'csharp', 'c#', 'blazor', 'razor', 'azure', 'bicep',
|
|
135
|
+
'ef core', 'entity framework', 'hangfire', 'mudblazor', 'fluent ui', 'asp.net'];
|
|
136
|
+
const hasExplicitDotnetRequest = dotnetKeywords.some(k => userInput.toLowerCase().includes(k));
|
|
137
|
+
|
|
138
|
+
if (!hasExplicitDotnetRequest) {
|
|
139
|
+
const dotnetAgents = new Set([
|
|
140
|
+
'dotnet-senior', 'azure-architect', 'ef-modeler', 'bicep-architect',
|
|
141
|
+
'blazor-builder', 'blazor-concurrency-validator', 'hangfire-orchestrator',
|
|
142
|
+
'azure-deploy-specialist'
|
|
143
|
+
]);
|
|
144
|
+
result.core = result.core.filter(id => !dotnetAgents.has(id));
|
|
145
|
+
result.specialists = result.specialists.filter(id => !dotnetAgents.has(id));
|
|
146
|
+
result.all = result.all.filter(id => !dotnetAgents.has(id));
|
|
147
|
+
result.matches = result.matches.filter(m => !dotnetAgents.has(m.id));
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
} catch {
|
|
151
|
+
// Config not found or unreadable — skip stack filtering
|
|
152
|
+
}
|
|
153
|
+
|
|
126
154
|
// Enrich with complexity analysis
|
|
127
155
|
try {
|
|
128
156
|
result.complexity = analyzeRequestComplexity(userInput);
|
|
@@ -141,7 +169,9 @@ export function detectAgentsCommand(input, options) {
|
|
|
141
169
|
}
|
|
142
170
|
|
|
143
171
|
// Enrich with skill paths for each detected agent
|
|
144
|
-
const allAgents =
|
|
172
|
+
const allAgents = Object.entries(config.agents || {})
|
|
173
|
+
.filter(([id]) => !id.startsWith('_'))
|
|
174
|
+
.map(([id, agent]) => ({ id, ...agent }));
|
|
145
175
|
result.skillPaths = result.all
|
|
146
176
|
.map(id => {
|
|
147
177
|
const agent = allAgents.find(a => a.id === id);
|
|
@@ -3,7 +3,7 @@ import ora from 'ora';
|
|
|
3
3
|
import chalk from 'chalk';
|
|
4
4
|
import { logger } from '../../utils/logger.js';
|
|
5
5
|
import { detectProject, getDetectionSummary } from '../../lib/detectors/index.js';
|
|
6
|
-
import { ensureDir, writeFile } from '../../utils/file-copier.js';
|
|
6
|
+
import { ensureDir, writeFile, readJson, writeJson, pathExists } from '../../utils/file-copier.js';
|
|
7
7
|
|
|
8
8
|
export async function detectCommand(options) {
|
|
9
9
|
const targetPath = options.path || process.cwd();
|
|
@@ -84,6 +84,16 @@ export async function detectCommand(options) {
|
|
|
84
84
|
const standardsPath = join(standardsDir, 'inferred.md');
|
|
85
85
|
await writeFile(standardsPath, results.inferred.markdown);
|
|
86
86
|
|
|
87
|
+
// Update config.json with detected stack and architecture
|
|
88
|
+
const configPath = join(targetPath, '.morph', 'config', 'config.json');
|
|
89
|
+
if (await pathExists(configPath)) {
|
|
90
|
+
const projectConfig = await readJson(configPath);
|
|
91
|
+
projectConfig.project = projectConfig.project || {};
|
|
92
|
+
projectConfig.project.stack = results.structure.stack;
|
|
93
|
+
projectConfig.project.architecture = results.structure.architecture;
|
|
94
|
+
await writeJson(configPath, projectConfig);
|
|
95
|
+
}
|
|
96
|
+
|
|
87
97
|
spinner.succeed('Results saved!');
|
|
88
98
|
logger.dim(` - ${logPath}`);
|
|
89
99
|
logger.dim(` - ${standardsPath}`);
|