@polymorphism-tech/morph-spec 4.21.0 → 5.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 +57 -71
- package/README.md +2 -2
- package/bin/morph-spec.js +224 -491
- package/claude-plugin.json +1 -1
- package/docs/CHEATSHEET.md +1 -1
- package/docs/QUICKSTART.md +1 -1
- package/framework/CLAUDE.md +55 -38
- package/framework/MORPH.md +244 -0
- package/framework/agents.json +127 -1183
- package/framework/commands/morph-apply.md +56 -240
- package/framework/commands/morph-archive.md +48 -85
- package/framework/commands/morph-preflight.md +7 -7
- package/framework/commands/morph-proposal.md +35 -191
- package/framework/commands/morph-status.md +19 -50
- package/framework/commands/morph-troubleshoot.md +2 -2
- package/framework/evals/rubrics/README.md +44 -0
- package/framework/evals/rubrics/architecture.md +46 -0
- package/framework/evals/rubrics/code-quality.md +50 -0
- package/framework/evals/rubrics/contracts.md +47 -0
- package/framework/evals/rubrics/test-coverage.md +50 -0
- package/framework/evals/scenarios/dotnet-crud.yaml +42 -0
- package/framework/evals/scenarios/nextjs-auth.yaml +38 -0
- package/framework/evals/suites/standard.yaml +5 -0
- package/framework/evals/trace-schema.md +67 -0
- package/framework/hooks/claude-code/core/README.md +3 -0
- package/framework/hooks/claude-code/core/denylist.json +14 -0
- package/framework/hooks/claude-code/{pre-tool-use → core}/enforce-phase-writes.js +5 -5
- package/framework/hooks/claude-code/core/post-edit-typecheck.js +69 -0
- package/framework/hooks/claude-code/core/pre-bash-denylist.js +29 -0
- package/framework/hooks/claude-code/{pre-tool-use → core}/protect-spec-files.js +4 -6
- package/framework/hooks/claude-code/{stop → core}/validate-completion.js +13 -82
- package/framework/hooks/shared/phase-utils.js +20 -28
- package/framework/hooks/shared/state-reader.js +4 -5
- package/framework/{skills/level-0-meta/mcp-registry.json → mcp/registry.json} +254 -210
- package/framework/phases.json +40 -114
- package/framework/rubrics/eval-rubric.json +66 -0
- package/framework/skills/README.md +39 -64
- package/framework/skills/core/README.md +3 -0
- package/framework/skills/core/morph-brainstorming/SKILL.md +102 -0
- package/framework/skills/core/morph-eval/SKILL.md +57 -0
- package/framework/skills/core/morph-implement/SKILL.md +83 -0
- package/framework/skills/core/morph-plan/SKILL.md +64 -0
- package/framework/skills/core/morph-review/SKILL.md +62 -0
- package/framework/skills/core/morph-stack-scan/SKILL.md +109 -0
- package/framework/skills/core/morph-standards/SKILL.md +49 -0
- package/framework/standards/STANDARDS.json +32 -0
- package/framework/standards/frontend/design-system/ai-image-generation.md +244 -0
- package/framework/standards/integration/mcp/mcp-tools.md +48 -0
- package/framework/templates/CLAUDE.md.core.guide.md +29 -0
- package/framework/templates/CLAUDE.md.core.template +22 -0
- package/framework/templates/REGISTRY.json +4 -4
- package/framework/workflows/configs/fast-track.json +5 -10
- package/framework/workflows/configs/full.json +4 -8
- package/framework/workflows/configs/quick.json +32 -0
- package/framework/workflows/configs/standard.json +57 -64
- package/package.json +7 -6
- package/src/commands/mcp/mcp-setup.js +58 -31
- package/src/commands/project/doctor.js +69 -70
- package/src/commands/project/init.js +26 -5
- package/src/commands/project/status.js +18 -19
- package/src/commands/state/approve.js +1 -1
- package/src/commands/validation/validate-feature.js +18 -112
- package/src/core/paths/output-schema.js +133 -151
- package/src/core/state/phase-state-machine.js +10 -24
- package/src/core/state/state-manager.js +17 -17
- package/src/core/workflows/workflow-detector.js +214 -31
- package/src/core/workflows/workflow-router.js +204 -0
- package/src/dashboard/app/pages/AgentMap.jsx +32 -33
- package/src/dashboard/app/pages/LiveState.jsx +1 -1
- package/src/dashboard/app/pages/PhasePipeline.jsx +2 -4
- package/src/dashboard/app/pages/SkillsLibrary.jsx +2 -3
- package/src/dashboard/app/pages/Workflows.jsx +2 -2
- package/src/dashboard/scanner/agents-scanner.js +30 -35
- package/src/lib/agents/metrics-aggregator.js +1 -1
- package/src/lib/agents/output-collector.js +7 -8
- package/src/lib/context/context-builder.js +289 -0
- package/src/lib/detectors/claude-config-detector.js +19 -3
- package/src/lib/generators/context-generator.js +20 -42
- package/src/lib/generators/index.js +1 -0
- package/src/lib/generators/mandate-generator.js +512 -0
- package/src/lib/generators/metadata-extractor.js +1 -1
- package/src/lib/generators/recap-generator.js +3 -4
- package/src/lib/installers/mcp-installer.js +300 -92
- package/src/lib/phase-chain/phase-validator.js +36 -40
- package/src/lib/stack/stack-profile.js +74 -91
- package/src/lib/tasks/task-parser.js +1 -1
- package/src/lib/tasks/wave-resolver.js +353 -0
- package/src/lib/validators/checkpoint-persistence.js +2 -2
- package/src/lib/validators/content/cross-phase-validator.js +281 -0
- package/src/lib/validators/content/index.js +1 -0
- package/src/lib/validators/shared/emit-validator-dispatch.js +6 -56
- package/src/scripts/global-install.js +2 -27
- package/src/utils/agents-installer.js +224 -447
- package/src/utils/claude-settings-manager.js +57 -15
- package/src/utils/file-copier.js +1 -0
- package/src/utils/hooks-installer.js +15 -117
- package/src/utils/skills-installer.js +12 -18
- package/bin/detect-agents.js +0 -226
- package/bin/task-manager.js +0 -1036
- package/framework/agents/README.md +0 -13
- package/framework/agents/ai-agents/ai-system-architect.md +0 -194
- package/framework/agents/ai-agents/vector-search-expert.md +0 -247
- package/framework/agents/architecture/architecture-expert.md +0 -98
- package/framework/agents/architecture/morph-spec-architect.md +0 -66
- package/framework/agents/architecture/packages-validator.md +0 -89
- package/framework/agents/architecture/security-expert.md +0 -79
- package/framework/agents/architecture/seo-growth-hacker.md +0 -168
- package/framework/agents/architecture/standards-architect.md +0 -153
- package/framework/agents/backend/dotnet-senior.md +0 -163
- package/framework/agents/backend/ef-modeler.md +0 -119
- package/framework/agents/backend/hangfire-orchestrator.md +0 -133
- package/framework/agents/backend/ms-agent-expert.md +0 -55
- package/framework/agents/backend/neon-expert.md +0 -379
- package/framework/agents/frontend/css-specialist.md +0 -89
- package/framework/agents/frontend/design-system-validator.md +0 -100
- package/framework/agents/frontend/gsap-animator.md +0 -131
- package/framework/agents/frontend/nextjs-component-validator.md +0 -97
- package/framework/agents/frontend/nextjs-expert.md +0 -201
- package/framework/agents/frontend/ui-designer.md +0 -259
- package/framework/agents/infrastructure/infra-architect.md +0 -215
- package/framework/agents/integrations/asaas-financial.md +0 -138
- package/framework/agents/integrations/neon-auth.md +0 -151
- package/framework/agents/integrations/resend-email.md +0 -127
- package/framework/hooks/claude-code/post-tool-use/agent-complete.js +0 -122
- package/framework/hooks/claude-code/post-tool-use/dispatch.js +0 -363
- package/framework/hooks/claude-code/post-tool-use/handle-tool-failure.js +0 -78
- package/framework/hooks/claude-code/post-tool-use/validator-feedback.js +0 -194
- package/framework/hooks/claude-code/pre-compact/save-morph-context.js +0 -199
- package/framework/hooks/claude-code/pre-tool-use/task-tracking-guard.js +0 -55
- package/framework/hooks/claude-code/session-start/inject-morph-context.js +0 -323
- package/framework/hooks/claude-code/session-start/post-compact-restore.js +0 -41
- package/framework/hooks/claude-code/session-start/resume-context.js +0 -77
- package/framework/hooks/claude-code/teammate-idle/teammate-idle.js +0 -90
- package/framework/hooks/claude-code/user-prompt/enrich-prompt.js +0 -114
- package/framework/hooks/shared/compact-restore.js +0 -123
- package/framework/hooks/shared/dispatch-helpers.js +0 -147
- package/framework/hooks/shared/skill-reminder-helpers.js +0 -79
- package/framework/hooks/shared/stale-task-reset.js +0 -57
- package/framework/hooks/shared/worktree-helpers.js +0 -75
- package/framework/skills/level-0-meta/README.md +0 -7
- package/framework/skills/level-0-meta/morph-agent-teams/SKILL.md +0 -472
- package/framework/skills/level-0-meta/morph-brainstorming/SKILL.md +0 -174
- package/framework/skills/level-0-meta/morph-brainstorming/references/proposal-example.md +0 -138
- package/framework/skills/level-0-meta/morph-checklist/SKILL.md +0 -145
- package/framework/skills/level-0-meta/morph-code-review/SKILL.md +0 -250
- package/framework/skills/level-0-meta/morph-code-review/references/review-example.md +0 -164
- package/framework/skills/level-0-meta/morph-code-review/references/review-guidelines.md +0 -100
- package/framework/skills/level-0-meta/morph-code-review/scripts/scan-csharp.mjs +0 -151
- package/framework/skills/level-0-meta/morph-code-review-nextjs/SKILL.md +0 -165
- package/framework/skills/level-0-meta/morph-code-review-nextjs/references/review-example-nextjs.md +0 -254
- package/framework/skills/level-0-meta/morph-code-review-nextjs/scripts/scan-nextjs.mjs +0 -189
- package/framework/skills/level-0-meta/morph-frontend-review/SKILL.md +0 -344
- package/framework/skills/level-0-meta/morph-frontend-review/scripts/scan-accessibility.mjs +0 -376
- package/framework/skills/level-0-meta/morph-init/SKILL.md +0 -537
- package/framework/skills/level-0-meta/morph-parallel-dispatch/SKILL.md +0 -225
- package/framework/skills/level-0-meta/morph-post-implementation/SKILL.md +0 -382
- package/framework/skills/level-0-meta/morph-post-implementation/scripts/detect-dev-server.mjs +0 -153
- package/framework/skills/level-0-meta/morph-post-implementation/scripts/detect-stack.mjs +0 -234
- package/framework/skills/level-0-meta/morph-simulation-checklist/SKILL.md +0 -106
- package/framework/skills/level-0-meta/morph-tool-usage-guide/SKILL.md +0 -243
- package/framework/skills/level-0-meta/morph-tool-usage-guide/references/tools-per-phase.md +0 -169
- package/framework/skills/level-0-meta/morph-verification-before-completion/SKILL.md +0 -148
- package/framework/skills/level-0-meta/morph-verification-before-completion/scripts/check-phase-outputs.mjs +0 -110
- package/framework/skills/level-1-workflows/README.md +0 -7
- package/framework/skills/level-1-workflows/morph-phase-codebase-analysis/SKILL.md +0 -269
- package/framework/skills/level-1-workflows/morph-phase-design/SKILL.md +0 -453
- package/framework/skills/level-1-workflows/morph-phase-design/references/architecture-analysis-guide.md +0 -44
- package/framework/skills/level-1-workflows/morph-phase-design/references/spec-authoring-guide.md +0 -55
- package/framework/skills/level-1-workflows/morph-phase-design/references/spec-example.md +0 -253
- package/framework/skills/level-1-workflows/morph-phase-implement/.morph/logs/activity.json +0 -38
- package/framework/skills/level-1-workflows/morph-phase-implement/SKILL.md +0 -493
- package/framework/skills/level-1-workflows/morph-phase-implement/prompts/code-quality-reviewer-prompt.md +0 -50
- package/framework/skills/level-1-workflows/morph-phase-implement/prompts/implementer-prompt.md +0 -46
- package/framework/skills/level-1-workflows/morph-phase-implement/prompts/spec-reviewer-prompt.md +0 -47
- package/framework/skills/level-1-workflows/morph-phase-implement/references/recap-example.md +0 -132
- package/framework/skills/level-1-workflows/morph-phase-implement/references/vsa-implementation-guide.md +0 -92
- package/framework/skills/level-1-workflows/morph-phase-plan/SKILL.md +0 -257
- package/framework/skills/level-1-workflows/morph-phase-tasks/.morph/logs/activity.json +0 -14
- package/framework/skills/level-1-workflows/morph-phase-tasks/SKILL.md +0 -291
- package/framework/skills/level-1-workflows/morph-phase-tasks/references/task-planning-patterns.md +0 -143
- package/framework/skills/level-1-workflows/morph-phase-tasks/references/tasks-example.md +0 -404
- package/framework/skills/level-1-workflows/morph-phase-tasks/scripts/validate-tasks.mjs +0 -112
- package/framework/skills/level-1-workflows/morph-phase-uiux/SKILL.md +0 -788
- package/framework/skills/level-1-workflows/morph-scope-escalation/SKILL.md +0 -147
- package/framework/squad-templates/backend-only.json +0 -47
- package/framework/squad-templates/frontend-only.json +0 -56
- package/framework/squad-templates/full-stack.json +0 -74
- package/src/commands/agents/agent-metrics.js +0 -174
- package/src/commands/agents/audit-dispatch.js +0 -82
- package/src/commands/agents/detect-agents.js +0 -275
- package/src/commands/agents/dispatch-agents.js +0 -461
- package/src/commands/agents/index.js +0 -13
- package/src/commands/agents/manage-agents.js +0 -352
- package/src/commands/agents/project-metrics.js +0 -74
- package/src/commands/phase/phase-reset.js +0 -74
- package/src/commands/scope/escalate.js +0 -217
- package/src/commands/state/advance-phase.js +0 -600
- package/src/commands/state/phase-runner.js +0 -215
- package/src/commands/state/state.js +0 -444
- package/src/commands/task/expand.js +0 -100
- package/src/commands/tasks/index.js +0 -4
- package/src/commands/tasks/task.js +0 -105
- package/src/commands/templates/index.js +0 -5
- package/src/commands/templates/template-render.js +0 -93
- package/src/commands/trust/trust.js +0 -118
- package/src/lib/agents/agents-manager.js +0 -349
- package/src/lib/build/build-runner.js +0 -99
- package/src/lib/checkpoints/checkpoint-hooks.js +0 -111
- package/src/lib/contracts/sprint-contract-generator.js +0 -137
- package/src/lib/scope/impact-analyzer.js +0 -106
- package/src/lib/trust/trust-manager.js +0 -157
- package/src/utils/tier-helpers.js +0 -24
- /package/framework/hooks/claude-code/{post-tool-use → core}/loop-detect.js +0 -0
|
@@ -68,30 +68,23 @@ function getNpmGlobalPrefix() {
|
|
|
68
68
|
}
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
-
// lib files
|
|
71
|
+
// lib files (v5 — minimal surface)
|
|
72
72
|
const REQUIRED_LIB_FILES = [
|
|
73
|
-
'src/lib/
|
|
73
|
+
'src/lib/stack-filter.js'
|
|
74
74
|
];
|
|
75
75
|
|
|
76
|
-
// command files (
|
|
76
|
+
// command files (v5 CLI surface — installer-only)
|
|
77
77
|
const REQUIRED_COMMAND_FILES = [
|
|
78
78
|
'src/commands/project/init.js',
|
|
79
79
|
'src/commands/project/update.js',
|
|
80
80
|
'src/commands/project/doctor.js',
|
|
81
81
|
'src/commands/project/status.js',
|
|
82
|
-
'src/commands/
|
|
83
|
-
'src/commands/state/advance-phase.js',
|
|
82
|
+
'src/commands/project/worktree.js',
|
|
84
83
|
'src/commands/state/approve.js',
|
|
85
|
-
'src/commands/tasks/task.js',
|
|
86
|
-
'src/commands/trust/trust.js',
|
|
87
84
|
'src/commands/validation/validate-feature.js',
|
|
88
|
-
'src/commands/
|
|
89
|
-
'src/commands/
|
|
90
|
-
'src/commands/
|
|
91
|
-
'src/commands/phase/phase-reset.js',
|
|
92
|
-
'src/commands/scope/escalate.js',
|
|
93
|
-
'src/commands/task/expand.js',
|
|
94
|
-
'src/lib/scope/impact-analyzer.js'
|
|
85
|
+
'src/commands/scaffold/scaffold.js',
|
|
86
|
+
'src/commands/mcp/mcp-setup.js',
|
|
87
|
+
'src/commands/context/context-refresh.js',
|
|
95
88
|
];
|
|
96
89
|
|
|
97
90
|
// framework standards (stack-relevant only)
|
|
@@ -105,9 +98,9 @@ const FRAMEWORK_STANDARDS = [
|
|
|
105
98
|
'framework/standards/workflows/parallel-execution.md'
|
|
106
99
|
];
|
|
107
100
|
|
|
108
|
-
// required
|
|
101
|
+
// required personas expected in agents.json (v5 — at least the universal evaluator)
|
|
109
102
|
const REQUIRED_AGENTS = [
|
|
110
|
-
'
|
|
103
|
+
'evaluator'
|
|
111
104
|
];
|
|
112
105
|
|
|
113
106
|
/**
|
|
@@ -211,7 +204,7 @@ async function doctorFullCommand(frameworkRoot) {
|
|
|
211
204
|
check('state.json parse', false, false, 'invalid JSON');
|
|
212
205
|
}
|
|
213
206
|
} else {
|
|
214
|
-
check('state.json', false, true, 'not found (run: morph-spec
|
|
207
|
+
check('state.json', false, true, 'not found (run: morph-spec init)');
|
|
215
208
|
}
|
|
216
209
|
|
|
217
210
|
// ── Display ──────────────────────────────────────────────────────────────
|
|
@@ -242,8 +235,8 @@ async function doctorFullCommand(frameworkRoot) {
|
|
|
242
235
|
}
|
|
243
236
|
|
|
244
237
|
/**
|
|
245
|
-
* MCP health check: reads settings.json
|
|
246
|
-
* each configured MCP server's command binary and env var requirements.
|
|
238
|
+
* MCP health check: reads .mcp.json, settings.json, and settings.local.json
|
|
239
|
+
* and validates each configured MCP server's command binary and env var requirements.
|
|
247
240
|
*
|
|
248
241
|
* Strategy:
|
|
249
242
|
* - "ok" : command binary exists (or is a known runtime like npx/node) AND
|
|
@@ -258,23 +251,43 @@ async function doctorMcpCommand(targetPath) {
|
|
|
258
251
|
logger.header('MCP Server Health Check');
|
|
259
252
|
logger.blank();
|
|
260
253
|
|
|
254
|
+
const allMcpServers = {};
|
|
255
|
+
const sourcesFound = [];
|
|
256
|
+
|
|
257
|
+
// Primary: .mcp.json (Claude Code reads MCPs from here)
|
|
258
|
+
const mcpJsonPath = join(targetPath, '.mcp.json');
|
|
259
|
+
if (await pathExists(mcpJsonPath)) {
|
|
260
|
+
try {
|
|
261
|
+
const mcpJson = JSON.parse(await fs.readFile(mcpJsonPath, 'utf8'));
|
|
262
|
+
const mcps = mcpJson.mcpServers || {};
|
|
263
|
+
const count = Object.keys(mcps).length;
|
|
264
|
+
if (count > 0) {
|
|
265
|
+
Object.assign(allMcpServers, mcps);
|
|
266
|
+
sourcesFound.push(`.mcp.json (${count})`);
|
|
267
|
+
}
|
|
268
|
+
} catch { /* invalid JSON — ignore */ }
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// Legacy: settings files (for backward compat)
|
|
261
272
|
const settingsPaths = [
|
|
262
273
|
join(targetPath, '.claude', 'settings.json'),
|
|
263
274
|
join(targetPath, '.claude', 'settings.local.json'),
|
|
264
275
|
];
|
|
265
276
|
|
|
266
|
-
const allMcpServers = {};
|
|
267
|
-
const sourcesFound = [];
|
|
268
|
-
|
|
269
277
|
for (const sp of settingsPaths) {
|
|
270
278
|
if (await pathExists(sp)) {
|
|
271
279
|
try {
|
|
272
280
|
const settings = JSON.parse(await fs.readFile(sp, 'utf8'));
|
|
273
281
|
const mcps = settings.mcpServers || {};
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
282
|
+
let addedCount = 0;
|
|
283
|
+
for (const [name, config] of Object.entries(mcps)) {
|
|
284
|
+
if (!allMcpServers[name]) {
|
|
285
|
+
allMcpServers[name] = config;
|
|
286
|
+
addedCount++;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
if (addedCount > 0) {
|
|
290
|
+
sourcesFound.push(`${relative(targetPath, sp)} (${addedCount})`);
|
|
278
291
|
}
|
|
279
292
|
} catch { /* invalid JSON — ignore */ }
|
|
280
293
|
}
|
|
@@ -283,7 +296,7 @@ async function doctorMcpCommand(targetPath) {
|
|
|
283
296
|
if (Object.keys(allMcpServers).length === 0) {
|
|
284
297
|
logger.warn('No MCP servers configured');
|
|
285
298
|
logger.blank();
|
|
286
|
-
logger.dim('Configure MCPs in: .
|
|
299
|
+
logger.dim('Configure MCPs in: .mcp.json → mcpServers: { ... }');
|
|
287
300
|
logger.dim('Or use: morph-spec mcp setup');
|
|
288
301
|
return;
|
|
289
302
|
}
|
|
@@ -564,77 +577,63 @@ export async function doctorCommand(options = {}) {
|
|
|
564
577
|
checks.push({ name: 'agents.json count', status: 'ok', msg: `${actualCount} agents` });
|
|
565
578
|
}
|
|
566
579
|
|
|
567
|
-
// Check 2: required
|
|
568
|
-
const
|
|
569
|
-
const
|
|
570
|
-
if (
|
|
580
|
+
// Check 2: required v5 personas present
|
|
581
|
+
const requiredPersonas = REQUIRED_AGENTS;
|
|
582
|
+
const missingPersonas = requiredPersonas.filter(id => !agentsData.agents[id]);
|
|
583
|
+
if (missingPersonas.length > 0) {
|
|
571
584
|
checks.push({
|
|
572
|
-
name: 'agents.json required
|
|
585
|
+
name: 'agents.json required personas',
|
|
573
586
|
status: 'error',
|
|
574
|
-
msg: `Missing required
|
|
587
|
+
msg: `Missing required personas: ${missingPersonas.join(', ')}`
|
|
575
588
|
});
|
|
576
589
|
hasErrors = true;
|
|
577
590
|
} else {
|
|
578
|
-
checks.push({ name: 'agents.json required
|
|
579
|
-
}
|
|
580
|
-
|
|
581
|
-
// Check 3: tier counts sum matches total
|
|
582
|
-
const tierCounts = [1, 2, 3, 4].map(t =>
|
|
583
|
-
agentEntries.filter(([, v]) => v.tier === t).length
|
|
584
|
-
);
|
|
585
|
-
const tierSum = tierCounts.reduce((a, b) => a + b, 0);
|
|
586
|
-
if (tierSum !== actualCount) {
|
|
587
|
-
checks.push({
|
|
588
|
-
name: 'agents.json tier counts',
|
|
589
|
-
status: 'warn',
|
|
590
|
-
msg: `Tier counts sum to ${tierSum} but total is ${actualCount}`
|
|
591
|
-
});
|
|
592
|
-
hasWarnings = true;
|
|
593
|
-
} else {
|
|
594
|
-
checks.push({ name: 'agents.json tier counts', status: 'ok' });
|
|
591
|
+
checks.push({ name: 'agents.json required personas', status: 'ok' });
|
|
595
592
|
}
|
|
596
593
|
|
|
597
|
-
// Check
|
|
594
|
+
// Check 3: v5 schema validation (flat personas, no tiers)
|
|
598
595
|
const version = agentsData.version || 'unknown';
|
|
599
|
-
const
|
|
600
|
-
const
|
|
601
|
-
const
|
|
596
|
+
const isV5 = version.startsWith('5.');
|
|
597
|
+
const v4Fields = [];
|
|
598
|
+
const missingV5Fields = [];
|
|
602
599
|
|
|
603
600
|
for (const [id, data] of agentEntries) {
|
|
604
|
-
if (data.
|
|
605
|
-
if (data.
|
|
606
|
-
if (data.
|
|
607
|
-
if (
|
|
608
|
-
if (!
|
|
601
|
+
if (data.tier !== undefined) v4Fields.push(`${id}.tier`);
|
|
602
|
+
if (data.active_phases !== undefined) v4Fields.push(`${id}.active_phases`);
|
|
603
|
+
if (data.reports_to !== undefined) v4Fields.push(`${id}.reports_to`);
|
|
604
|
+
if (data.teammate !== undefined) v4Fields.push(`${id}.teammate`);
|
|
605
|
+
if (!data.title) missingV5Fields.push(`${id}.title`);
|
|
606
|
+
if (!Array.isArray(data.domains)) missingV5Fields.push(`${id}.domains`);
|
|
607
|
+
if (typeof data.persona !== 'string' || !data.persona) missingV5Fields.push(`${id}.persona`);
|
|
609
608
|
}
|
|
610
609
|
|
|
611
|
-
if (
|
|
610
|
+
if (isV5) {
|
|
612
611
|
checks.push({ name: `agents.json schema (v${version})`, status: 'ok' });
|
|
613
612
|
} else {
|
|
614
613
|
checks.push({
|
|
615
614
|
name: `agents.json schema (v${version})`,
|
|
616
615
|
status: 'warn',
|
|
617
|
-
msg: 'expected
|
|
616
|
+
msg: 'expected v5.x — run agents migration'
|
|
618
617
|
});
|
|
619
618
|
hasWarnings = true;
|
|
620
619
|
}
|
|
621
620
|
|
|
622
|
-
if (
|
|
623
|
-
const sample =
|
|
621
|
+
if (v4Fields.length > 0) {
|
|
622
|
+
const sample = v4Fields.slice(0, 3).join(', ');
|
|
624
623
|
checks.push({
|
|
625
|
-
name: 'agents.json deprecated fields',
|
|
624
|
+
name: 'agents.json deprecated v4 fields',
|
|
626
625
|
status: 'warn',
|
|
627
|
-
msg: `${
|
|
626
|
+
msg: `${v4Fields.length} v4 field(s) found: ${sample}${v4Fields.length > 3 ? '...' : ''}`
|
|
628
627
|
});
|
|
629
628
|
hasWarnings = true;
|
|
630
629
|
}
|
|
631
630
|
|
|
632
|
-
if (
|
|
633
|
-
const sample =
|
|
631
|
+
if (missingV5Fields.length > 0 && isV5) {
|
|
632
|
+
const sample = missingV5Fields.slice(0, 3).join(', ');
|
|
634
633
|
checks.push({
|
|
635
|
-
name: 'agents.json
|
|
634
|
+
name: 'agents.json v5 required fields',
|
|
636
635
|
status: 'warn',
|
|
637
|
-
msg: `${
|
|
636
|
+
msg: `${missingV5Fields.length} missing: ${sample}${missingV5Fields.length > 3 ? '...' : ''}`
|
|
638
637
|
});
|
|
639
638
|
hasWarnings = true;
|
|
640
639
|
}
|
|
@@ -853,7 +852,7 @@ export async function doctorCommand(options = {}) {
|
|
|
853
852
|
hasErrors = true;
|
|
854
853
|
}
|
|
855
854
|
} else {
|
|
856
|
-
checks.push({ name: 'state.json', status: 'warn', msg: 'not initialized (run: morph-spec
|
|
855
|
+
checks.push({ name: 'state.json', status: 'warn', msg: 'not initialized (run: morph-spec init)' });
|
|
857
856
|
hasWarnings = true;
|
|
858
857
|
}
|
|
859
858
|
|
|
@@ -24,7 +24,8 @@ import {
|
|
|
24
24
|
installMcpWithCredentials,
|
|
25
25
|
generateSetupInstructions,
|
|
26
26
|
formatMcpStatusTable,
|
|
27
|
-
isRemoteMcp
|
|
27
|
+
isRemoteMcp,
|
|
28
|
+
setupStitch
|
|
28
29
|
} from '../../lib/installers/mcp-installer.js';
|
|
29
30
|
import { setupInfra, installRulesForStack } from '../../scripts/setup-infra.js';
|
|
30
31
|
import { installSkills } from '../../utils/skills-installer.js';
|
|
@@ -209,7 +210,7 @@ export async function initCommand(options) {
|
|
|
209
210
|
// Skills
|
|
210
211
|
await installSkills(targetPath, { projectTags: stackTags });
|
|
211
212
|
|
|
212
|
-
// Agents (
|
|
213
|
+
// Agents (domain personas from agents.json)
|
|
213
214
|
await installAgents(targetPath, fwSrcDir, { projectTags: stackTags });
|
|
214
215
|
await installDomainAgents(targetPath, fwSrcDir, { projectTags: stackTags });
|
|
215
216
|
|
|
@@ -290,6 +291,26 @@ export async function initCommand(options) {
|
|
|
290
291
|
}]);
|
|
291
292
|
|
|
292
293
|
if (setupNow) {
|
|
294
|
+
// Stitch — automated setup via setupStitch()
|
|
295
|
+
if (name === 'stitch' && !entry.install.autoInstall) {
|
|
296
|
+
spinner.start('Setting up Stitch MCP...');
|
|
297
|
+
const stitchResult = await setupStitch(targetPath);
|
|
298
|
+
if (stitchResult.success) {
|
|
299
|
+
spinner.succeed('Stitch MCP setup complete');
|
|
300
|
+
manualInstalled[name] = entry;
|
|
301
|
+
} else {
|
|
302
|
+
const failedStep = stitchResult.steps.find(s => !s.success && s.step !== 'enable-api' && s.step !== 'health-check');
|
|
303
|
+
spinner.fail(`Stitch setup failed: ${failedStep?.error || 'unknown'}`);
|
|
304
|
+
}
|
|
305
|
+
if (entry.companionSkills?.installCommand) {
|
|
306
|
+
const skillResults = installCompanionSkills({ stitch: entry });
|
|
307
|
+
for (const r of skillResults) {
|
|
308
|
+
if (r.success) logger.success(` Companion skills installed: ${r.repo}`);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
continue;
|
|
312
|
+
}
|
|
313
|
+
|
|
293
314
|
// Remote MCPs — launch OAuth via claude mcp add
|
|
294
315
|
if (isRemoteMcp(entry.install.config)) {
|
|
295
316
|
const url = entry.install.config.url;
|
|
@@ -327,7 +348,7 @@ export async function initCommand(options) {
|
|
|
327
348
|
// Show setup instructions
|
|
328
349
|
const instructions = generateSetupInstructions(name, entry);
|
|
329
350
|
logger.blank();
|
|
330
|
-
logger.dim(' Add to .
|
|
351
|
+
logger.dim(' Add to .mcp.json → mcpServers:');
|
|
331
352
|
logger.dim(' ' + '─'.repeat(55));
|
|
332
353
|
for (const line of instructions.configSnippet.split('\n')) {
|
|
333
354
|
logger.dim(` ${line}`);
|
|
@@ -434,8 +455,8 @@ export async function initCommand(options) {
|
|
|
434
455
|
// Show agent team breakdown from setup-infra result
|
|
435
456
|
const agents = infraResult?.agents;
|
|
436
457
|
if (agents) {
|
|
437
|
-
const total = agents.tier1 + agents.tier2 + agents.specialists;
|
|
438
|
-
logger.dim(` ✓ .claude/agents/ (${total}
|
|
458
|
+
const total = (agents.tier1 || 0) + (agents.tier2 || 0) + (agents.specialists || 0) + (agents.personas || 0);
|
|
459
|
+
logger.dim(` ✓ .claude/agents/ (${total} domain personas installed)`);
|
|
439
460
|
} else {
|
|
440
461
|
logger.dim(' ✓ .claude/agents/ (native subagents installed)');
|
|
441
462
|
}
|
|
@@ -32,11 +32,9 @@ const PHASE_SYMBOLS = {
|
|
|
32
32
|
const PHASE_ORDER = [
|
|
33
33
|
{ key: 'proposal', name: 'FASE 0: PROPOSAL', order: 0 },
|
|
34
34
|
{ key: 'uiux', name: 'FASE 1: UI/UX', order: 1, optional: true },
|
|
35
|
-
{ key: '
|
|
36
|
-
{ key: '
|
|
37
|
-
{ key: '
|
|
38
|
-
{ key: 'implement', name: 'FASE 5: IMPLEMENT', order: 5 },
|
|
39
|
-
{ key: 'sync', name: 'FASE 6: SYNC', order: 6, optional: true }
|
|
35
|
+
{ key: 'plan', name: 'FASE 2: PLAN', order: 2 },
|
|
36
|
+
{ key: 'implement', name: 'FASE 3: IMPLEMENT', order: 3 },
|
|
37
|
+
{ key: 'review', name: 'FASE 4: REVIEW', order: 4 }
|
|
40
38
|
];
|
|
41
39
|
|
|
42
40
|
/**
|
|
@@ -65,10 +63,11 @@ function getPhaseStatus(phaseKey, currentPhase, feature) {
|
|
|
65
63
|
*/
|
|
66
64
|
function checkPhaseOutputs(phaseKey, feature) {
|
|
67
65
|
const phaseOutputMap = {
|
|
68
|
-
proposal: ['proposal'],
|
|
66
|
+
proposal: ['proposal', 'stackScan', 'migrationPlan'],
|
|
69
67
|
uiux: ['uiDesignSystem', 'uiMockups', 'uiComponents', 'uiFlows'],
|
|
70
|
-
|
|
71
|
-
|
|
68
|
+
plan: ['spec', 'mandate', 'tasks', 'decisions'],
|
|
69
|
+
implement: ['recap'],
|
|
70
|
+
review: ['reviewReport']
|
|
72
71
|
};
|
|
73
72
|
|
|
74
73
|
const outputs = phaseOutputMap[phaseKey] || [];
|
|
@@ -102,11 +101,11 @@ function formatPhase(phaseKey, currentPhase, feature, options = {}) {
|
|
|
102
101
|
// Add output files if verbose
|
|
103
102
|
if (options.verbose && status !== 'pending' && status !== 'skipped') {
|
|
104
103
|
const phaseOutputMap = {
|
|
105
|
-
proposal: ['proposal'],
|
|
104
|
+
proposal: ['proposal', 'stackScan', 'migrationPlan'],
|
|
106
105
|
uiux: ['uiDesignSystem', 'uiMockups', 'uiComponents', 'uiFlows'],
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
106
|
+
plan: ['spec', 'mandate', 'tasks', 'decisions'],
|
|
107
|
+
implement: ['recap'],
|
|
108
|
+
review: ['reviewReport']
|
|
110
109
|
};
|
|
111
110
|
|
|
112
111
|
const outputs = phaseOutputMap[phaseKey] || [];
|
|
@@ -143,9 +142,9 @@ function getNextSteps(feature) {
|
|
|
143
142
|
const phaseApprovalMap = {
|
|
144
143
|
proposal: 'proposal',
|
|
145
144
|
uiux: 'uiux',
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
145
|
+
plan: 'plan',
|
|
146
|
+
implement: 'plan',
|
|
147
|
+
review: 'review'
|
|
149
148
|
};
|
|
150
149
|
|
|
151
150
|
const relevantGate = phaseApprovalMap[feature.phase];
|
|
@@ -162,9 +161,9 @@ function getNextSteps(feature) {
|
|
|
162
161
|
// Check for missing outputs in current phase
|
|
163
162
|
const phaseOutputMap = {
|
|
164
163
|
proposal: ['proposal'],
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
164
|
+
plan: ['spec', 'mandate', 'tasks'],
|
|
165
|
+
implement: ['recap'],
|
|
166
|
+
review: ['reviewReport']
|
|
168
167
|
};
|
|
169
168
|
|
|
170
169
|
const requiredOutputs = phaseOutputMap[feature.phase] || [];
|
|
@@ -240,7 +239,7 @@ export async function statusCommand(featureName, options = {}) {
|
|
|
240
239
|
}
|
|
241
240
|
if (!feature) {
|
|
242
241
|
console.error(chalk.red(`✗ Error: Feature '${featureName}' not found`));
|
|
243
|
-
console.error(chalk.gray(`Run 'morph-spec
|
|
242
|
+
console.error(chalk.gray(`Run 'morph-spec status' to list known features`));
|
|
244
243
|
process.exit(1);
|
|
245
244
|
}
|
|
246
245
|
|
|
@@ -54,7 +54,7 @@ const program = new Command();
|
|
|
54
54
|
/**
|
|
55
55
|
* Valid approval gate names
|
|
56
56
|
*/
|
|
57
|
-
const VALID_GATES = ['proposal', 'uiux', '
|
|
57
|
+
const VALID_GATES = ['proposal', 'uiux', 'plan', 'review'];
|
|
58
58
|
|
|
59
59
|
/**
|
|
60
60
|
* Approve action function - Mark a gate as approved
|
|
@@ -35,13 +35,9 @@ const OUTPUT_SECTIONS = {
|
|
|
35
35
|
'Performance', 'Edge Cases', 'Clarifications'
|
|
36
36
|
]
|
|
37
37
|
},
|
|
38
|
-
'
|
|
39
|
-
required: ['
|
|
40
|
-
|
|
41
|
-
},
|
|
42
|
-
'schema-analysis.md': {
|
|
43
|
-
required: ['Tables Analyzed', 'Columns', 'Relationships'],
|
|
44
|
-
optional: ['CRITICAL FINDINGS', 'Recommendations', 'Indexes']
|
|
38
|
+
'mandate.md': {
|
|
39
|
+
required: ['Stack', 'Standards', 'Done'],
|
|
40
|
+
optional: ['Patterns proibidos', 'Riscos']
|
|
45
41
|
},
|
|
46
42
|
'decisions.md': {
|
|
47
43
|
required: ['ADR', 'Status', 'Context', 'Decision'],
|
|
@@ -51,29 +47,32 @@ const OUTPUT_SECTIONS = {
|
|
|
51
47
|
required: ['T001', 'category', 'dependencies'],
|
|
52
48
|
optional: ['CHECKPOINT', 'estimatedMinutes']
|
|
53
49
|
},
|
|
50
|
+
'review-report.md': {
|
|
51
|
+
required: ['Scores', 'Recommendation'],
|
|
52
|
+
optional: ['Blockers', 'Improvements']
|
|
53
|
+
},
|
|
54
54
|
};
|
|
55
55
|
|
|
56
56
|
/**
|
|
57
|
-
* Phase-to-output mapping
|
|
57
|
+
* Phase-to-output mapping (v5 NLH)
|
|
58
58
|
*/
|
|
59
59
|
const PHASE_OUTPUTS = {
|
|
60
60
|
proposal: ['proposal.md'],
|
|
61
61
|
uiux: ['design-system.md', 'mockups.md', 'components.md', 'flows.md'],
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
62
|
+
plan: ['spec.md', 'mandate.md', 'tasks.md'],
|
|
63
|
+
implement: ['recap.md'],
|
|
64
|
+
review: ['review-report.md'],
|
|
65
65
|
};
|
|
66
66
|
|
|
67
67
|
/**
|
|
68
|
-
* Phase-to-folder mapping (filesystem convention)
|
|
68
|
+
* Phase-to-folder mapping (v5 filesystem convention)
|
|
69
69
|
*/
|
|
70
70
|
const PHASE_FOLDERS = {
|
|
71
71
|
proposal: '0-proposal',
|
|
72
72
|
uiux: '1-ui',
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
implement: '5-implement',
|
|
73
|
+
plan: '2-plan',
|
|
74
|
+
implement: '3-implement',
|
|
75
|
+
review: '4-review',
|
|
77
76
|
};
|
|
78
77
|
|
|
79
78
|
/**
|
|
@@ -105,13 +104,8 @@ function validateOutput(featurePath, outputFile) {
|
|
|
105
104
|
return result;
|
|
106
105
|
}
|
|
107
106
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
validateCSharpContent(content, result);
|
|
111
|
-
} else {
|
|
112
|
-
// Markdown section validation
|
|
113
|
-
validateMarkdownSections(content, sections, result);
|
|
114
|
-
}
|
|
107
|
+
// v5: only markdown outputs — contracts moved into spec.md
|
|
108
|
+
validateMarkdownSections(content, sections, result);
|
|
115
109
|
|
|
116
110
|
// Size warnings
|
|
117
111
|
if (result.lineCount < 10) {
|
|
@@ -143,94 +137,6 @@ function validateMarkdownSections(content, sections, result) {
|
|
|
143
137
|
}
|
|
144
138
|
}
|
|
145
139
|
|
|
146
|
-
/**
|
|
147
|
-
* Validate C# contracts content
|
|
148
|
-
*/
|
|
149
|
-
function validateCSharpContent(content, result) {
|
|
150
|
-
// Check for namespace
|
|
151
|
-
if (/namespace\s+\S+/.test(content)) {
|
|
152
|
-
result.sections.found.push('namespace');
|
|
153
|
-
} else {
|
|
154
|
-
result.sections.missing.push('namespace');
|
|
155
|
-
result.errors.push('Missing namespace declaration');
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
// Check for records (DTOs)
|
|
159
|
-
const records = content.match(/public\s+record\s+\w+/g) || [];
|
|
160
|
-
if (records.length > 0) {
|
|
161
|
-
result.sections.found.push(`${records.length} record(s)`);
|
|
162
|
-
} else {
|
|
163
|
-
result.warnings.push('No record types found — DTOs expected');
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
// Check for interfaces
|
|
167
|
-
const interfaces = content.match(/public\s+interface\s+I\w+/g) || [];
|
|
168
|
-
if (interfaces.length > 0) {
|
|
169
|
-
result.sections.found.push(`${interfaces.length} interface(s)`);
|
|
170
|
-
} else {
|
|
171
|
-
result.warnings.push('No interfaces found — service contracts expected');
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
// Check for CancellationToken in async methods
|
|
175
|
-
const asyncMethods = content.match(/Task<.*>\s+\w+\(/g) || [];
|
|
176
|
-
const methodsWithCt = content.match(/CancellationToken\s+ct/g) || [];
|
|
177
|
-
if (asyncMethods.length > 0 && methodsWithCt.length < asyncMethods.length) {
|
|
178
|
-
result.warnings.push(
|
|
179
|
-
`${asyncMethods.length - methodsWithCt.length} async method(s) may be missing CancellationToken`
|
|
180
|
-
);
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
// Check for schema-analysis reference
|
|
184
|
-
if (content.includes('schema-analysis')) {
|
|
185
|
-
result.sections.found.push('schema reference');
|
|
186
|
-
} else {
|
|
187
|
-
result.warnings.push('No reference to schema-analysis.md — fields may not match database');
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
/**
|
|
192
|
-
* Cross-validate contracts against schema-analysis
|
|
193
|
-
*/
|
|
194
|
-
function crossValidateSchema(featurePath) {
|
|
195
|
-
const schemaPath = path.join(featurePath, 'schema-analysis.md');
|
|
196
|
-
const contractsPath = path.join(featurePath, 'contracts.cs');
|
|
197
|
-
const warnings = [];
|
|
198
|
-
|
|
199
|
-
if (!fs.existsSync(schemaPath) || !fs.existsSync(contractsPath)) {
|
|
200
|
-
return warnings;
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
const schema = fs.readFileSync(schemaPath, 'utf8');
|
|
204
|
-
const contracts = fs.readFileSync(contractsPath, 'utf8');
|
|
205
|
-
|
|
206
|
-
// Extract field names from schema (look for column names in tables)
|
|
207
|
-
const schemaFields = new Set();
|
|
208
|
-
const columnRegex = /\|\s*(\w+)\s*\|/g;
|
|
209
|
-
let match;
|
|
210
|
-
while ((match = columnRegex.exec(schema)) !== null) {
|
|
211
|
-
const field = match[1].toLowerCase();
|
|
212
|
-
if (!['column', 'name', 'type', 'nullable', 'default', 'notes', 'table', '---'].includes(field)) {
|
|
213
|
-
schemaFields.add(field);
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
// Extract field names from contracts
|
|
218
|
-
const contractFields = new Set();
|
|
219
|
-
const fieldRegex = /(?:string|int|Guid|DateTime|bool|decimal|JsonObject)\???\s+(\w+)/g;
|
|
220
|
-
while ((match = fieldRegex.exec(contracts)) !== null) {
|
|
221
|
-
contractFields.add(match[1].toLowerCase());
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
// Check for contract fields not in schema
|
|
225
|
-
for (const field of contractFields) {
|
|
226
|
-
if (schemaFields.size > 0 && !schemaFields.has(field) && field !== 'ct') {
|
|
227
|
-
warnings.push(`Contract field "${field}" not found in schema-analysis.md — verify correctness`);
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
return warnings;
|
|
232
|
-
}
|
|
233
|
-
|
|
234
140
|
/**
|
|
235
141
|
* Main validate command
|
|
236
142
|
*/
|
|
@@ -282,7 +188,7 @@ export async function validateFeatureCommand(featureName, options = {}) {
|
|
|
282
188
|
const results = phase
|
|
283
189
|
? outputFiles.map(file => validateOutput(searchPath, file))
|
|
284
190
|
: outputFiles.map(({ file, dir }) => validateOutput(dir, file));
|
|
285
|
-
const schemaWarnings =
|
|
191
|
+
const schemaWarnings = []; // v5: cross-validation removed (contracts in spec.md)
|
|
286
192
|
|
|
287
193
|
// Aggregate results
|
|
288
194
|
const totalErrors = results.reduce((sum, r) => sum + r.errors.length, 0);
|