@polymorphism-tech/morph-spec 4.22.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 -1198
- 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 -31
- package/framework/hooks/shared/state-reader.js +4 -5
- package/framework/{skills/level-0-meta/mcp-registry.json → mcp/registry.json} +254 -243
- package/framework/phases.json +41 -115
- 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/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 -152
- 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 -9
- 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-gemini-image/SKILL.md +0 -290
- 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 -401
- 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 -634
- 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 -54
- 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 -995
- 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
|
@@ -1,461 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* dispatch-agents — Advisor for native Claude Code agent dispatch
|
|
3
|
-
*
|
|
4
|
-
* Reads activeAgents from state.json + agents.json and outputs a
|
|
5
|
-
* recommendation of which agents to use and which tasks they handle.
|
|
6
|
-
* Agent .md files in .claude/agents/ are self-contained — dispatch
|
|
7
|
-
* does NOT compose prompts (except for tier-4 validators).
|
|
8
|
-
*
|
|
9
|
-
* Usage:
|
|
10
|
-
* morph-spec dispatch-agents <feature> <phase>
|
|
11
|
-
* morph-spec dispatch-agents <feature> <phase> --table
|
|
12
|
-
*
|
|
13
|
-
* Output (JSON):
|
|
14
|
-
* {
|
|
15
|
-
* feature, phase, shouldDispatch, reason,
|
|
16
|
-
* recommended: { [group]: subagentType[] },
|
|
17
|
-
* tasks: { [group]: taskId[] },
|
|
18
|
-
* agents: [{ id, subagentType, title, tier, icon, group }],
|
|
19
|
-
* warnings: []
|
|
20
|
-
* }
|
|
21
|
-
*/
|
|
22
|
-
|
|
23
|
-
import { readFileSync, existsSync } from 'fs';
|
|
24
|
-
import { join, dirname } from 'path';
|
|
25
|
-
import { fileURLToPath } from 'url';
|
|
26
|
-
import { loadState, stateExists, logDispatch } from '../../core/state/state-manager.js';
|
|
27
|
-
import { buildValidatorPrompt } from '../../lib/standards/digest-builder.js';
|
|
28
|
-
import { agentIdToSubagentType } from '../../utils/agent-id-mapper.js';
|
|
29
|
-
import { shouldInstall, getProjectTags } from '../../lib/stack-filter.js';
|
|
30
|
-
import { parseAndGroupTasks } from '../../lib/tasks/task-parser.js';
|
|
31
|
-
export { parseAndGroupTasks } from '../../lib/tasks/task-parser.js';
|
|
32
|
-
import chalk from 'chalk';
|
|
33
|
-
|
|
34
|
-
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
35
|
-
|
|
36
|
-
/** Path to agents.json in the installed package */
|
|
37
|
-
const AGENTS_JSON_PATH = join(__dirname, '../../../framework/agents.json');
|
|
38
|
-
|
|
39
|
-
/** Path to phases.json in the installed package */
|
|
40
|
-
const PHASES_JSON_PATH = join(__dirname, '../../../framework/phases.json');
|
|
41
|
-
|
|
42
|
-
/** Phases where agent dispatch is meaningful */
|
|
43
|
-
const DISPATCHABLE_PHASES = ['design', 'plan', 'tasks', 'implement'];
|
|
44
|
-
|
|
45
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
46
|
-
// Required Skills Injection
|
|
47
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Build a mandatory skills block for the given phase.
|
|
51
|
-
* Reads requiredSkills from phases.json data and formats them as instructions for subagents.
|
|
52
|
-
* Returns null if no required skills for this phase.
|
|
53
|
-
*
|
|
54
|
-
* @param {string} phase - Phase id (e.g. 'implement')
|
|
55
|
-
* @param {Object|null} phasesData - Parsed phases.json content
|
|
56
|
-
* @returns {string|null}
|
|
57
|
-
*/
|
|
58
|
-
export function buildSkillsBlock(phase, phasesData) {
|
|
59
|
-
try {
|
|
60
|
-
const skills = phasesData?.phases?.[phase]?.requiredSkills;
|
|
61
|
-
if (!skills || skills.length === 0) return null;
|
|
62
|
-
|
|
63
|
-
const triggerLabel = trigger =>
|
|
64
|
-
trigger.replace(/([A-Z])/g, ' $1').toUpperCase().trim();
|
|
65
|
-
|
|
66
|
-
const lines = skills.map(s => `- ${triggerLabel(s.trigger)}: Invoke Skill(${s.skill})`);
|
|
67
|
-
|
|
68
|
-
return [
|
|
69
|
-
'Mandatory Skills (non-negotiable — invoke at the specified trigger points):',
|
|
70
|
-
...lines,
|
|
71
|
-
'',
|
|
72
|
-
'Do NOT skip these skills. They are required by MORPH-SPEC for this phase.',
|
|
73
|
-
].join('\n');
|
|
74
|
-
} catch {
|
|
75
|
-
return null;
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
80
|
-
// MCP Awareness Injection
|
|
81
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* Build an MCP availability block for the given phase.
|
|
85
|
-
* Reads recommendedMCPs from phases.json and MCP usage info from mcp-registry.json.
|
|
86
|
-
* Returns null if no MCPs recommended for this phase.
|
|
87
|
-
*
|
|
88
|
-
* @param {string} phase - Phase id (e.g. 'implement')
|
|
89
|
-
* @param {Object|null} phasesData - Parsed phases.json content
|
|
90
|
-
* @param {Object} [opts] - Options
|
|
91
|
-
* @param {string} [opts.registryPath] - Override path to mcp-registry.json
|
|
92
|
-
* @returns {string|null}
|
|
93
|
-
*/
|
|
94
|
-
export function buildMcpBlock(phase, phasesData, opts = {}) {
|
|
95
|
-
try {
|
|
96
|
-
const mcps = phasesData?.phases?.[phase]?.recommendedMCPs;
|
|
97
|
-
if (!mcps || mcps.length === 0) return null;
|
|
98
|
-
|
|
99
|
-
// Try to load registry for usage descriptions
|
|
100
|
-
let registry = null;
|
|
101
|
-
const registryPath = opts.registryPath || join(__dirname, '../../../framework/skills/level-0-meta/mcp-registry.json');
|
|
102
|
-
try {
|
|
103
|
-
if (existsSync(registryPath)) {
|
|
104
|
-
registry = JSON.parse(readFileSync(registryPath, 'utf8'));
|
|
105
|
-
}
|
|
106
|
-
} catch { /* non-blocking */ }
|
|
107
|
-
|
|
108
|
-
const lines = mcps.map(name => {
|
|
109
|
-
const usage = registry?.mcps?.[name]?.usage || '';
|
|
110
|
-
return usage ? `- ${name}: ${usage}` : `- ${name}`;
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
return [
|
|
114
|
-
`Available MCPs for ${phase} phase:`,
|
|
115
|
-
...lines,
|
|
116
|
-
'',
|
|
117
|
-
'Use MCP tools when available; fall back to native tools (Bash, Read, Grep) when not.',
|
|
118
|
-
].join('\n');
|
|
119
|
-
} catch {
|
|
120
|
-
return null;
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
125
|
-
// Helpers
|
|
126
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
127
|
-
|
|
128
|
-
/**
|
|
129
|
-
* Determine the parallel execution group for an agent.
|
|
130
|
-
* Reads from the `group` field in agents.json v4; falls back to 'backend'.
|
|
131
|
-
*
|
|
132
|
-
* @param {string} agentId
|
|
133
|
-
* @param {Object} agentData
|
|
134
|
-
* @returns {string} group name
|
|
135
|
-
*/
|
|
136
|
-
function getAgentGroup(agentId, agentData) {
|
|
137
|
-
return agentData.group || 'backend';
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
/**
|
|
141
|
-
* Check whether an agent should be dispatched for the given phase.
|
|
142
|
-
*
|
|
143
|
-
* Rules:
|
|
144
|
-
* 1. Tier-4 validators → never dispatch (validate mode bypasses this)
|
|
145
|
-
* 2. `active_phases` includes '*' → all dispatchable phases
|
|
146
|
-
* 3. `active_phases` includes the phase → dispatch
|
|
147
|
-
* 4. Default (no active_phases): implement only
|
|
148
|
-
*
|
|
149
|
-
* @param {string} agentId
|
|
150
|
-
* @param {Object} agentData
|
|
151
|
-
* @param {string} phase
|
|
152
|
-
* @returns {boolean}
|
|
153
|
-
*/
|
|
154
|
-
function isRelevantForPhase(agentId, agentData, phase) {
|
|
155
|
-
if (agentData.tier === 4) return false;
|
|
156
|
-
|
|
157
|
-
const phases = agentData.active_phases;
|
|
158
|
-
if (Array.isArray(phases)) {
|
|
159
|
-
return phases.includes('*') || phases.includes(phase);
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
return phase === 'implement';
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
166
|
-
// Core Logic
|
|
167
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
168
|
-
|
|
169
|
-
/**
|
|
170
|
-
* Build dispatch config for a feature and phase.
|
|
171
|
-
*
|
|
172
|
-
* @param {string} projectPath - Absolute path to project root (cwd)
|
|
173
|
-
* @param {string} featureName - Feature name
|
|
174
|
-
* @param {string} phase - Phase: 'design' | 'tasks' | 'implement'
|
|
175
|
-
* @param {Object} [opts] - Optional overrides (for testing)
|
|
176
|
-
* @param {string} [opts.agentsJsonPath] - Override path to agents.json
|
|
177
|
-
* @returns {Object} Dispatch config
|
|
178
|
-
*/
|
|
179
|
-
export async function buildDispatchConfig(projectPath, featureName, phase, opts = {}) {
|
|
180
|
-
// Load state
|
|
181
|
-
const state = loadState(false);
|
|
182
|
-
if (!state) {
|
|
183
|
-
return {
|
|
184
|
-
shouldDispatch: false,
|
|
185
|
-
reason: 'state.json not found — run morph-spec state init',
|
|
186
|
-
recommended: {},
|
|
187
|
-
tasks: {},
|
|
188
|
-
agents: [],
|
|
189
|
-
warnings: [],
|
|
190
|
-
};
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
const feature = state.features?.[featureName];
|
|
194
|
-
if (!feature) {
|
|
195
|
-
return {
|
|
196
|
-
feature: featureName,
|
|
197
|
-
phase,
|
|
198
|
-
shouldDispatch: false,
|
|
199
|
-
reason: `Feature '${featureName}' not found in state.json`,
|
|
200
|
-
recommended: {},
|
|
201
|
-
tasks: {},
|
|
202
|
-
agents: [],
|
|
203
|
-
warnings: [],
|
|
204
|
-
};
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
// Load agents.json (opts.agentsJsonPath allows override for testing)
|
|
208
|
-
const agentsPath = opts.agentsJsonPath || AGENTS_JSON_PATH;
|
|
209
|
-
if (!existsSync(agentsPath)) {
|
|
210
|
-
return {
|
|
211
|
-
feature: featureName,
|
|
212
|
-
phase,
|
|
213
|
-
shouldDispatch: false,
|
|
214
|
-
reason: 'framework/agents.json not found',
|
|
215
|
-
recommended: {},
|
|
216
|
-
tasks: {},
|
|
217
|
-
agents: [],
|
|
218
|
-
warnings: [],
|
|
219
|
-
};
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
const agentsData = JSON.parse(readFileSync(agentsPath, 'utf8'));
|
|
223
|
-
const allAgents = agentsData.agents || {};
|
|
224
|
-
|
|
225
|
-
// Load project config for stack filtering
|
|
226
|
-
const configPath = join(projectPath, '.morph', 'config.json');
|
|
227
|
-
let projectTags = [];
|
|
228
|
-
try {
|
|
229
|
-
if (existsSync(configPath)) {
|
|
230
|
-
const config = JSON.parse(readFileSync(configPath, 'utf8'));
|
|
231
|
-
projectTags = getProjectTags(config);
|
|
232
|
-
}
|
|
233
|
-
} catch { /* non-blocking */ }
|
|
234
|
-
|
|
235
|
-
// Active agents from state (keyword-detected during setup phase)
|
|
236
|
-
const activeAgentIds = new Set(feature.activeAgents || []);
|
|
237
|
-
|
|
238
|
-
// Collect dispatchable agents
|
|
239
|
-
const isValidateMode = opts.mode === 'validate';
|
|
240
|
-
const processed = new Set();
|
|
241
|
-
let dispatchableAgents = [];
|
|
242
|
-
|
|
243
|
-
const candidateIds = [
|
|
244
|
-
...activeAgentIds,
|
|
245
|
-
// Add always-active agents (active_phases includes '*')
|
|
246
|
-
...Object.entries(allAgents)
|
|
247
|
-
.filter(([id, data]) => Array.isArray(data.active_phases) && data.active_phases.includes('*'))
|
|
248
|
-
.map(([id]) => id),
|
|
249
|
-
// In validate mode, also include tier-4 validators
|
|
250
|
-
...(isValidateMode
|
|
251
|
-
? Object.entries(allAgents)
|
|
252
|
-
.filter(([id, data]) => !id.startsWith('_comment') && data.tier === 4)
|
|
253
|
-
.map(([id]) => id)
|
|
254
|
-
: []),
|
|
255
|
-
];
|
|
256
|
-
|
|
257
|
-
for (const agentId of candidateIds) {
|
|
258
|
-
if (processed.has(agentId)) continue;
|
|
259
|
-
processed.add(agentId);
|
|
260
|
-
|
|
261
|
-
const agentData = allAgents[agentId];
|
|
262
|
-
if (!agentData || agentId.startsWith('_comment')) continue;
|
|
263
|
-
|
|
264
|
-
const isTier4 = agentData.tier === 4;
|
|
265
|
-
|
|
266
|
-
// Must have a spawn_prompt (or be a tier-4 with hook_behavior in validate mode)
|
|
267
|
-
if (!agentData.teammate?.spawn_prompt && !(isValidateMode && isTier4 && agentData.hook_behavior)) continue;
|
|
268
|
-
|
|
269
|
-
// Phase relevance check (tier-4 bypass in validate mode)
|
|
270
|
-
if (!isRelevantForPhase(agentId, agentData, phase) && !(isValidateMode && isTier4)) continue;
|
|
271
|
-
|
|
272
|
-
// Stack filtering (Tier 1/2 exempt — always dispatched)
|
|
273
|
-
if (agentData.tier >= 3 && projectTags.length > 0) {
|
|
274
|
-
const agentStacks = agentData.stacks || [];
|
|
275
|
-
if (!shouldInstall(agentStacks, projectTags)) continue;
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
const group = getAgentGroup(agentId, agentData);
|
|
279
|
-
|
|
280
|
-
// Tier-4 validators in validate mode still get taskPrompt (dynamically generated)
|
|
281
|
-
const agentEntry = {
|
|
282
|
-
id: agentId,
|
|
283
|
-
subagentType: agentIdToSubagentType(agentId, agentData.tier),
|
|
284
|
-
title: agentData.title,
|
|
285
|
-
tier: agentData.tier,
|
|
286
|
-
icon: agentData.teammate?.icon || '🤖',
|
|
287
|
-
group,
|
|
288
|
-
};
|
|
289
|
-
|
|
290
|
-
if (isValidateMode && isTier4) {
|
|
291
|
-
agentEntry.taskPrompt = buildValidatorPrompt(agentId) || agentData.teammate?.spawn_prompt || '';
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
dispatchableAgents.push(agentEntry);
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
// Sort: domain-analysis first, then by tier (ascending), then alphabetically
|
|
298
|
-
const groupOrder = ['domain-analysis', 'backend', 'frontend', 'infra', 'tests', 'docs', 'support'];
|
|
299
|
-
dispatchableAgents.sort((a, b) => {
|
|
300
|
-
const gi = groupOrder.indexOf(a.group);
|
|
301
|
-
const gj = groupOrder.indexOf(b.group);
|
|
302
|
-
if (gi !== gj) return gi - gj;
|
|
303
|
-
if (a.tier !== b.tier) return a.tier - b.tier;
|
|
304
|
-
return a.id.localeCompare(b.id);
|
|
305
|
-
});
|
|
306
|
-
|
|
307
|
-
// Pre-flight: check agent files exist, skip missing (BUG-001)
|
|
308
|
-
const warnings = [];
|
|
309
|
-
const verifiedAgents = [];
|
|
310
|
-
for (const agent of dispatchableAgents) {
|
|
311
|
-
const agentFile = join(projectPath, '.claude', 'agents', `${agent.subagentType}.md`);
|
|
312
|
-
if (!existsSync(agentFile)) {
|
|
313
|
-
warnings.push({
|
|
314
|
-
agentId: agent.id,
|
|
315
|
-
subagentType: agent.subagentType,
|
|
316
|
-
message: `File .claude/agents/${agent.subagentType}.md not found — agent skipped from dispatch`,
|
|
317
|
-
});
|
|
318
|
-
} else {
|
|
319
|
-
verifiedAgents.push(agent);
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
dispatchableAgents = verifiedAgents;
|
|
323
|
-
|
|
324
|
-
const shouldDispatch = dispatchableAgents.length >= 2;
|
|
325
|
-
|
|
326
|
-
// ── Recommended agents by group ───────────────────────────────────────────
|
|
327
|
-
const recommended = {};
|
|
328
|
-
for (const agent of dispatchableAgents) {
|
|
329
|
-
if (!recommended[agent.group]) recommended[agent.group] = [];
|
|
330
|
-
recommended[agent.group].push(agent.subagentType);
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
// ── Tasks by group (implement phase only) ─────────────────────────────────
|
|
334
|
-
let tasks = {};
|
|
335
|
-
if (phase === 'implement') {
|
|
336
|
-
const tasksPath = join(projectPath, `.morph/features/${featureName}/4-tasks/tasks.md`);
|
|
337
|
-
tasks = parseAndGroupTasks(tasksPath);
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
return {
|
|
341
|
-
feature: featureName,
|
|
342
|
-
phase,
|
|
343
|
-
shouldDispatch,
|
|
344
|
-
reason: shouldDispatch
|
|
345
|
-
? `${dispatchableAgents.length} dispatchable agents for phase '${phase}'`
|
|
346
|
-
: dispatchableAgents.length === 1
|
|
347
|
-
? `Only 1 dispatchable agent — sequential execution preferred`
|
|
348
|
-
: `No dispatchable agents found for phase '${phase}'`,
|
|
349
|
-
recommended,
|
|
350
|
-
tasks,
|
|
351
|
-
agents: dispatchableAgents,
|
|
352
|
-
warnings,
|
|
353
|
-
};
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
357
|
-
// CLI Handler
|
|
358
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
359
|
-
|
|
360
|
-
/**
|
|
361
|
-
* CLI command: morph-spec dispatch-agents <feature> <phase>
|
|
362
|
-
*/
|
|
363
|
-
export async function dispatchAgentsCommand(featureName, phase, options = {}) {
|
|
364
|
-
if (!featureName || !phase) {
|
|
365
|
-
console.error(chalk.red('Usage: morph-spec dispatch-agents <feature> <phase>'));
|
|
366
|
-
console.error(chalk.gray('Phases: design | tasks | implement'));
|
|
367
|
-
process.exit(1);
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
const normalizedPhase = phase.toLowerCase();
|
|
371
|
-
if (!DISPATCHABLE_PHASES.includes(normalizedPhase)) {
|
|
372
|
-
console.error(chalk.red(`Invalid phase: '${phase}'. Must be one of: ${DISPATCHABLE_PHASES.join(', ')}`));
|
|
373
|
-
process.exit(1);
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
if (!stateExists()) {
|
|
377
|
-
console.error(chalk.red('No state.json found. Run morph-spec state init first.'));
|
|
378
|
-
process.exit(1);
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
try {
|
|
382
|
-
const dispatchOpts = {};
|
|
383
|
-
if (options.mode) dispatchOpts.mode = options.mode;
|
|
384
|
-
const config = await buildDispatchConfig(process.cwd(), featureName, normalizedPhase, dispatchOpts);
|
|
385
|
-
|
|
386
|
-
// Log dispatch to state.json so enforcement gates can verify it happened
|
|
387
|
-
if (config.agents?.length > 0) {
|
|
388
|
-
try {
|
|
389
|
-
logDispatch(featureName, {
|
|
390
|
-
timestamp: new Date().toISOString(),
|
|
391
|
-
phase: normalizedPhase,
|
|
392
|
-
agents: config.agents.map(a => ({
|
|
393
|
-
id: a.id,
|
|
394
|
-
subagentType: a.subagentType,
|
|
395
|
-
group: a.group,
|
|
396
|
-
tasks: config.tasks?.[a.group] || [],
|
|
397
|
-
})),
|
|
398
|
-
});
|
|
399
|
-
} catch {
|
|
400
|
-
// Non-blocking — dispatch output still succeeds even if log fails
|
|
401
|
-
}
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
if (options.table) {
|
|
405
|
-
// Human-readable table format
|
|
406
|
-
console.log(chalk.bold(`\n Dispatch Config — ${featureName} (${normalizedPhase})`));
|
|
407
|
-
console.log(' ' + '─'.repeat(62));
|
|
408
|
-
|
|
409
|
-
const dispatchLabel = config.shouldDispatch ? chalk.green('YES') : chalk.yellow('NO');
|
|
410
|
-
console.log(` Should dispatch: ${dispatchLabel}`);
|
|
411
|
-
console.log(` Reason: ${config.reason}`);
|
|
412
|
-
|
|
413
|
-
if (config.agents?.length > 0) {
|
|
414
|
-
console.log(`\n Agents (${config.agents.length}):`);
|
|
415
|
-
console.log(` ${'ID'.padEnd(28)} ${'Group'.padEnd(16)} Tier`);
|
|
416
|
-
console.log(' ' + '─'.repeat(50));
|
|
417
|
-
for (const agent of config.agents) {
|
|
418
|
-
console.log(
|
|
419
|
-
` ${(agent.icon + ' ' + agent.id).padEnd(28)} ` +
|
|
420
|
-
`${chalk.cyan(agent.group.padEnd(16))} ` +
|
|
421
|
-
`${agent.tier}`
|
|
422
|
-
);
|
|
423
|
-
}
|
|
424
|
-
} else {
|
|
425
|
-
console.log(chalk.gray('\n No dispatchable agents.'));
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
const recEntries = Object.entries(config.recommended || {});
|
|
429
|
-
if (recEntries.length > 0) {
|
|
430
|
-
console.log(`\n Recommended:`);
|
|
431
|
-
console.log(` ${'Group'.padEnd(20)} ${'Tasks'.padEnd(8)} Agents`);
|
|
432
|
-
console.log(' ' + '─'.repeat(50));
|
|
433
|
-
for (const [group, agentTypes] of recEntries) {
|
|
434
|
-
const taskCount = config.tasks?.[group]?.length || 0;
|
|
435
|
-
console.log(
|
|
436
|
-
` ${group.padEnd(20)} ` +
|
|
437
|
-
`${String(taskCount).padEnd(8)} ` +
|
|
438
|
-
`${agentTypes.join(', ')}`
|
|
439
|
-
);
|
|
440
|
-
}
|
|
441
|
-
}
|
|
442
|
-
console.log();
|
|
443
|
-
} else {
|
|
444
|
-
// JSON (default — consumed by phase skills / LLM)
|
|
445
|
-
console.log(JSON.stringify(config, null, 2));
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
// Pre-flight mode: exit with code 1 if warnings exist
|
|
449
|
-
if (options.preflight && config.warnings?.length > 0) {
|
|
450
|
-
console.error(chalk.yellow(`\n ⚠ Pre-flight: ${config.warnings.length} missing agent file(s):`));
|
|
451
|
-
for (const w of config.warnings) {
|
|
452
|
-
console.error(chalk.yellow(` - ${w.agentId}: ${w.message}`));
|
|
453
|
-
}
|
|
454
|
-
process.exit(1);
|
|
455
|
-
}
|
|
456
|
-
} catch (err) {
|
|
457
|
-
console.error(chalk.red(`Error: ${err.message}`));
|
|
458
|
-
if (options.verbose) console.error(err.stack);
|
|
459
|
-
process.exit(1);
|
|
460
|
-
}
|
|
461
|
-
}
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Agent Commands
|
|
3
|
-
*/
|
|
4
|
-
export { dispatchAgentsCommand, buildDispatchConfig } from './dispatch-agents.js';
|
|
5
|
-
export { auditDispatchCommand } from './audit-dispatch.js';
|
|
6
|
-
export { detectAgentsCommand, detectAgents } from './detect-agents.js';
|
|
7
|
-
export {
|
|
8
|
-
agentListCommand,
|
|
9
|
-
agentShowCommand,
|
|
10
|
-
agentAddCommand,
|
|
11
|
-
agentEditCommand,
|
|
12
|
-
agentRemoveCommand,
|
|
13
|
-
} from './manage-agents.js';
|