@polymorphism-tech/morph-spec 4.22.0 → 6.0.0
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,352 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* manage-agents.js — CLI command handlers for agent management
|
|
3
|
-
*
|
|
4
|
-
* Thin wrappers around agents-manager.js core functions.
|
|
5
|
-
* Each handler: parses args -> calls core -> formats output via logger.
|
|
6
|
-
*
|
|
7
|
-
* @module manage-agents
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import { join, dirname } from 'node:path';
|
|
11
|
-
import { fileURLToPath } from 'node:url';
|
|
12
|
-
import { createInterface } from 'node:readline';
|
|
13
|
-
import { existsSync, readFileSync } from 'node:fs';
|
|
14
|
-
import chalk from 'chalk';
|
|
15
|
-
import { logger } from '../../utils/logger.js';
|
|
16
|
-
import {
|
|
17
|
-
loadAgentsFile,
|
|
18
|
-
saveAgentsFile,
|
|
19
|
-
listAgents,
|
|
20
|
-
showAgent,
|
|
21
|
-
addAgent,
|
|
22
|
-
editAgent,
|
|
23
|
-
removeAgent,
|
|
24
|
-
TIER_LABELS,
|
|
25
|
-
KNOWN_PHASES,
|
|
26
|
-
} from '../../lib/agents/agents-manager.js';
|
|
27
|
-
|
|
28
|
-
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
29
|
-
const AGENTS_PATH = join(__dirname, '../../../framework/agents.json');
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Resolve the agents.json path.
|
|
33
|
-
* Uses the canonical framework/agents.json (v4 schema).
|
|
34
|
-
* If running inside a client project (not the framework repo), falls back
|
|
35
|
-
* to .morph/framework/agents.json.
|
|
36
|
-
*/
|
|
37
|
-
function getAgentsPath() {
|
|
38
|
-
// Prefer the framework source file (always v4 schema)
|
|
39
|
-
if (existsSync(AGENTS_PATH)) return AGENTS_PATH;
|
|
40
|
-
// Fallback for client projects
|
|
41
|
-
const clientPath = join(process.cwd(), '.morph', 'framework', 'agents.json');
|
|
42
|
-
if (existsSync(clientPath)) return clientPath;
|
|
43
|
-
return AGENTS_PATH;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
// ── List ────────────────────────────────────────────────────────────────────────
|
|
47
|
-
|
|
48
|
-
export async function agentListCommand(options) {
|
|
49
|
-
const agentsData = loadAgentsFile(getAgentsPath());
|
|
50
|
-
const filters = {};
|
|
51
|
-
if (options.group) filters.group = options.group;
|
|
52
|
-
if (options.tier) filters.tier = parseInt(options.tier, 10);
|
|
53
|
-
|
|
54
|
-
const result = listAgents(agentsData, filters);
|
|
55
|
-
|
|
56
|
-
if (options.json) {
|
|
57
|
-
console.log(JSON.stringify(result.agents, null, 2));
|
|
58
|
-
return;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
if (result.total === 0) {
|
|
62
|
-
logger.info('No agents found matching filters.');
|
|
63
|
-
return;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
logger.header(`Agents (${result.total} total)`);
|
|
67
|
-
|
|
68
|
-
// Table header
|
|
69
|
-
const header = ` ${'ID'.padEnd(28)} ${'Tier'.padEnd(6)} ${'Group'.padEnd(18)} ${'Phases'.padEnd(16)} Keywords`;
|
|
70
|
-
console.log(chalk.dim(header));
|
|
71
|
-
console.log(chalk.dim(' ' + '\u2500'.repeat(90)));
|
|
72
|
-
|
|
73
|
-
for (const a of result.agents) {
|
|
74
|
-
const tierLabel = `T${a.tier}`;
|
|
75
|
-
const group = (a.group || '\u2014').padEnd(18);
|
|
76
|
-
const phases = (a.phases || '*').padEnd(16);
|
|
77
|
-
console.log(` ${a.id.padEnd(28)} ${tierLabel.padEnd(6)} ${group} ${phases} ${a.keywords || '\u2014'}`);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
logger.blank();
|
|
81
|
-
const groups = Object.entries(result.groupCounts).map(([g, c]) => `${g}(${c})`).join(' ');
|
|
82
|
-
logger.dim(`Groups: ${groups}`);
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// ── Show ────────────────────────────────────────────────────────────────────────
|
|
86
|
-
|
|
87
|
-
export async function agentShowCommand(id, options) {
|
|
88
|
-
const agentsData = loadAgentsFile(getAgentsPath());
|
|
89
|
-
const agent = showAgent(agentsData, id);
|
|
90
|
-
|
|
91
|
-
if (!agent) {
|
|
92
|
-
const ids = Object.keys(agentsData.agents || {});
|
|
93
|
-
const similar = ids.filter(aid => levenshtein(aid, id) <= 3);
|
|
94
|
-
logger.error(`Agent '${id}' not found.`);
|
|
95
|
-
if (similar.length > 0) {
|
|
96
|
-
logger.dim(`Did you mean: ${similar.join(', ')}?`);
|
|
97
|
-
}
|
|
98
|
-
process.exitCode = 1;
|
|
99
|
-
return;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
if (options.json) {
|
|
103
|
-
console.log(JSON.stringify({ [id]: agent }, null, 2));
|
|
104
|
-
return;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
logger.header(`Agent: ${id}`);
|
|
108
|
-
logger.info(`Title: ${agent.title}`);
|
|
109
|
-
logger.info(`Tier: ${agent.tier} (${TIER_LABELS[agent.tier] || '?'})`);
|
|
110
|
-
logger.info(`Group: ${agent.group}`);
|
|
111
|
-
logger.info(`Phases: ${(agent.active_phases || []).join(', ')}`);
|
|
112
|
-
logger.info(`Reports to: ${agent.reports_to}`);
|
|
113
|
-
logger.info(`Domains: ${(agent.domains || []).join(', ') || '\u2014'}`);
|
|
114
|
-
logger.info(`Keywords: ${(agent.keywords || []).join(', ') || '\u2014'}`);
|
|
115
|
-
logger.info(`Validators: ${(agent.validators || []).join(', ') || '\u2014'}`);
|
|
116
|
-
|
|
117
|
-
if (agent.stacks?.length > 0) {
|
|
118
|
-
logger.info(`Stacks: ${agent.stacks.join(', ')}`);
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
if (agent.standards?.length > 0) {
|
|
122
|
-
logger.blank();
|
|
123
|
-
logger.info('Standards:');
|
|
124
|
-
for (const s of agent.standards) {
|
|
125
|
-
logger.dim(` ${s.id} (scope: ${s.scope}, priority: ${s.priority})`);
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
if (agent.teammate) {
|
|
130
|
-
logger.blank();
|
|
131
|
-
logger.info('Teammate:');
|
|
132
|
-
logger.dim(` Role: ${agent.teammate.role}`);
|
|
133
|
-
logger.dim(` Icon: ${agent.teammate.icon}`);
|
|
134
|
-
const prompt = agent.teammate.spawn_prompt || '';
|
|
135
|
-
const truncated = options.full ? prompt : (prompt.length > 200 ? prompt.slice(0, 200) + '...' : prompt);
|
|
136
|
-
logger.dim(` Prompt: ${truncated}`);
|
|
137
|
-
} else {
|
|
138
|
-
logger.blank();
|
|
139
|
-
logger.dim('No teammate configured.');
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
// ── Add ─────────────────────────────────────────────────────────────────────────
|
|
144
|
-
|
|
145
|
-
export async function agentAddCommand(id, options) {
|
|
146
|
-
const agentsData = loadAgentsFile(getAgentsPath());
|
|
147
|
-
|
|
148
|
-
let fields;
|
|
149
|
-
if (options.nonInteractive) {
|
|
150
|
-
fields = parseNonInteractiveFlags(options);
|
|
151
|
-
} else {
|
|
152
|
-
fields = await runAddWizard(id, agentsData);
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
const result = addAgent(agentsData, id, fields);
|
|
156
|
-
|
|
157
|
-
if (!result.success) {
|
|
158
|
-
for (const e of result.errors) logger.error(e);
|
|
159
|
-
process.exitCode = 1;
|
|
160
|
-
return;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
if (result.warnings?.length > 0) {
|
|
164
|
-
for (const w of result.warnings) logger.warn(w);
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
saveAgentsFile(getAgentsPath(), result.data);
|
|
168
|
-
logger.success(`Created agent '${id}' in agents.json`);
|
|
169
|
-
logger.dim(`Run 'morph-spec setup-infra' to install the .claude/agents/ file`);
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
// ── Edit ────────────────────────────────────────────────────────────────────────
|
|
173
|
-
|
|
174
|
-
export async function agentEditCommand(id, options) {
|
|
175
|
-
const agentsData = loadAgentsFile(getAgentsPath());
|
|
176
|
-
const changes = {};
|
|
177
|
-
|
|
178
|
-
if (options.tier) changes.tier = parseInt(options.tier, 10);
|
|
179
|
-
if (options.title) changes.title = options.title;
|
|
180
|
-
if (options.group) changes.group = options.group;
|
|
181
|
-
if (options.activePhases) changes.active_phases = options.activePhases.split(',').map(s => s.trim());
|
|
182
|
-
if (options.reportsTo) changes.reports_to = options.reportsTo;
|
|
183
|
-
if (options.keywords) changes.keywords = options.keywords.split(',').map(s => s.trim());
|
|
184
|
-
if (options.domains) changes.domains = options.domains.split(',').map(s => s.trim());
|
|
185
|
-
if (options.spawnPrompt) changes.spawn_prompt = options.spawnPrompt;
|
|
186
|
-
|
|
187
|
-
if (Object.keys(changes).length === 0) {
|
|
188
|
-
logger.error('No changes specified. Use flags like --title, --tier, --keywords, etc.');
|
|
189
|
-
process.exitCode = 1;
|
|
190
|
-
return;
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
const result = editAgent(agentsData, id, changes);
|
|
194
|
-
|
|
195
|
-
if (!result.success) {
|
|
196
|
-
for (const e of result.errors) logger.error(e);
|
|
197
|
-
process.exitCode = 1;
|
|
198
|
-
return;
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
saveAgentsFile(getAgentsPath(), result.data);
|
|
202
|
-
logger.success(`Updated agent '${id}'`);
|
|
203
|
-
for (const c of result.changes) logger.dim(` ${c}`);
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
// ── Remove ──────────────────────────────────────────────────────────────────────
|
|
207
|
-
|
|
208
|
-
export async function agentRemoveCommand(id, options) {
|
|
209
|
-
const agentsData = loadAgentsFile(getAgentsPath());
|
|
210
|
-
const agent = showAgent(agentsData, id);
|
|
211
|
-
|
|
212
|
-
if (!agent) {
|
|
213
|
-
logger.error(`Agent '${id}' not found.`);
|
|
214
|
-
process.exitCode = 1;
|
|
215
|
-
return;
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
// Show summary
|
|
219
|
-
logger.info(`Agent: ${id} (T${agent.tier} ${agent.group})`);
|
|
220
|
-
|
|
221
|
-
// Check for dependents
|
|
222
|
-
const dependents = Object.entries(agentsData.agents)
|
|
223
|
-
.filter(([aid, a]) => aid !== id && a.reports_to === id)
|
|
224
|
-
.map(([aid]) => aid);
|
|
225
|
-
|
|
226
|
-
if (dependents.length > 0) {
|
|
227
|
-
logger.warn(`These agents report to '${id}': ${dependents.join(', ')}`);
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
// Check state.json for active references (advisory)
|
|
231
|
-
checkActiveFeatures(id);
|
|
232
|
-
|
|
233
|
-
if (!options.force) {
|
|
234
|
-
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
235
|
-
const answer = await new Promise(resolve => {
|
|
236
|
-
rl.question(chalk.yellow(' Remove this agent? (y/N) '), resolve);
|
|
237
|
-
});
|
|
238
|
-
rl.close();
|
|
239
|
-
if (answer.toLowerCase() !== 'y') {
|
|
240
|
-
logger.info('Cancelled.');
|
|
241
|
-
return;
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
const result = removeAgent(agentsData, id);
|
|
246
|
-
saveAgentsFile(getAgentsPath(), result.data);
|
|
247
|
-
logger.success(`Removed agent '${id}' from agents.json`);
|
|
248
|
-
logger.dim(`Run 'morph-spec setup-infra' to clean up .claude/agents/ files`);
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
// ── Helpers ─────────────────────────────────────────────────────────────────────
|
|
252
|
-
|
|
253
|
-
/**
|
|
254
|
-
* Levenshtein distance between two strings.
|
|
255
|
-
* Used for "did you mean?" suggestions on typos.
|
|
256
|
-
*/
|
|
257
|
-
export function levenshtein(a, b) {
|
|
258
|
-
const m = a.length;
|
|
259
|
-
const n = b.length;
|
|
260
|
-
const dp = Array.from({ length: m + 1 }, (_, i) => Array(n + 1).fill(0));
|
|
261
|
-
for (let i = 0; i <= m; i++) dp[i][0] = i;
|
|
262
|
-
for (let j = 0; j <= n; j++) dp[0][j] = j;
|
|
263
|
-
for (let i = 1; i <= m; i++) {
|
|
264
|
-
for (let j = 1; j <= n; j++) {
|
|
265
|
-
dp[i][j] = a[i - 1] === b[j - 1]
|
|
266
|
-
? dp[i - 1][j - 1]
|
|
267
|
-
: 1 + Math.min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]);
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
return dp[m][n];
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
/**
|
|
274
|
-
* Interactive wizard for adding an agent.
|
|
275
|
-
*/
|
|
276
|
-
async function runAddWizard(id, agentsData) {
|
|
277
|
-
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
278
|
-
const ask = (q) => new Promise(resolve => rl.question(q, resolve));
|
|
279
|
-
|
|
280
|
-
console.log(chalk.bold(`\n Adding agent: ${id}\n`));
|
|
281
|
-
|
|
282
|
-
const knownGroups = [...new Set(Object.values(agentsData.agents || {}).map(a => a.group))];
|
|
283
|
-
|
|
284
|
-
const tier = parseInt(await ask(` ? Tier (1=${TIER_LABELS[1]}, 2=${TIER_LABELS[2]}, 3=${TIER_LABELS[3]}, 4=${TIER_LABELS[4]}): `), 10);
|
|
285
|
-
const title = await ask(' ? Title: ');
|
|
286
|
-
console.log(chalk.dim(` Known groups: ${knownGroups.join(', ')}`));
|
|
287
|
-
const group = await ask(' ? Group: ');
|
|
288
|
-
const phasesRaw = await ask(' ? Active phases (comma-sep, or * for all): ');
|
|
289
|
-
const active_phases = phasesRaw.trim() === '*' ? ['*'] : phasesRaw.split(',').map(s => s.trim());
|
|
290
|
-
|
|
291
|
-
const existingIds = Object.keys(agentsData.agents || {});
|
|
292
|
-
console.log(chalk.dim(` Valid: ${existingIds.slice(0, 5).join(', ')}... or "user"`));
|
|
293
|
-
const reports_to = await ask(' ? Reports to: ');
|
|
294
|
-
|
|
295
|
-
const domainsRaw = await ask(' ? Domains (comma-sep): ');
|
|
296
|
-
const domains = domainsRaw.split(',').map(s => s.trim()).filter(Boolean);
|
|
297
|
-
const keywordsRaw = await ask(' ? Keywords (comma-sep): ');
|
|
298
|
-
const keywords = keywordsRaw.split(',').map(s => s.trim()).filter(Boolean);
|
|
299
|
-
|
|
300
|
-
let teammate = null;
|
|
301
|
-
if (tier >= 3) {
|
|
302
|
-
const role = await ask(' ? Teammate role: ');
|
|
303
|
-
const icon = await ask(' ? Teammate icon (emoji): ');
|
|
304
|
-
const spawn_prompt = await ask(' ? Spawn prompt: ');
|
|
305
|
-
teammate = { role, icon, spawn_prompt };
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
rl.close();
|
|
309
|
-
|
|
310
|
-
return { tier, title, group, active_phases, reports_to, domains, keywords, teammate };
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
/**
|
|
314
|
-
* Parse --non-interactive flags into fields object.
|
|
315
|
-
*/
|
|
316
|
-
function parseNonInteractiveFlags(options) {
|
|
317
|
-
return {
|
|
318
|
-
tier: options.tier ? parseInt(options.tier, 10) : undefined,
|
|
319
|
-
title: options.title,
|
|
320
|
-
group: options.group,
|
|
321
|
-
active_phases: options.activePhases ? options.activePhases.split(',').map(s => s.trim()) : ['implement'],
|
|
322
|
-
reports_to: options.reportsTo || 'user',
|
|
323
|
-
domains: options.domains ? options.domains.split(',').map(s => s.trim()) : [],
|
|
324
|
-
keywords: options.keywords ? options.keywords.split(',').map(s => s.trim()) : [],
|
|
325
|
-
teammate: options.spawnPrompt ? {
|
|
326
|
-
role: options.role || 'specialist',
|
|
327
|
-
icon: options.icon || '\u{1F527}',
|
|
328
|
-
spawn_prompt: options.spawnPrompt,
|
|
329
|
-
} : null,
|
|
330
|
-
};
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
/**
|
|
334
|
-
* Advisory check: is this agent referenced in active features?
|
|
335
|
-
* Fail-open — any errors are silently ignored.
|
|
336
|
-
*/
|
|
337
|
-
function checkActiveFeatures(agentId) {
|
|
338
|
-
try {
|
|
339
|
-
const statePath = join(process.cwd(), '.morph', 'state.json');
|
|
340
|
-
if (!existsSync(statePath)) return;
|
|
341
|
-
const state = JSON.parse(readFileSync(statePath, 'utf8'));
|
|
342
|
-
const activeIn = [];
|
|
343
|
-
for (const [fname, fdata] of Object.entries(state.features || {})) {
|
|
344
|
-
if (fdata.activeAgents?.includes(agentId)) activeIn.push(fname);
|
|
345
|
-
}
|
|
346
|
-
if (activeIn.length > 0) {
|
|
347
|
-
logger.warn(`Agent '${agentId}' is active in features: ${activeIn.join(', ')}. Removing from agents.json does not affect state.`);
|
|
348
|
-
}
|
|
349
|
-
} catch {
|
|
350
|
-
/* ignore — state check is advisory */
|
|
351
|
-
}
|
|
352
|
-
}
|
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* project-metrics — Project-level metrics aggregation CLI
|
|
3
|
-
*
|
|
4
|
-
* Aggregates feature data from state.json into a project-wide summary.
|
|
5
|
-
*
|
|
6
|
-
* Usage:
|
|
7
|
-
* morph-spec project-metrics [--json] [--save]
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import { writeFileSync, existsSync, mkdirSync } from 'fs';
|
|
11
|
-
import { join } from 'path';
|
|
12
|
-
import { loadState, stateExists } from '../../core/state/state-manager.js';
|
|
13
|
-
import { aggregateProjectMetrics } from '../../lib/agents/metrics-aggregator.js';
|
|
14
|
-
import chalk from 'chalk';
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* CLI command: morph-spec project-metrics [--json] [--save]
|
|
18
|
-
*/
|
|
19
|
-
export async function projectMetricsCommand(options = {}) {
|
|
20
|
-
if (!stateExists()) {
|
|
21
|
-
console.error(chalk.red('No state.json found. Run morph-spec state init first.'));
|
|
22
|
-
process.exit(1);
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
try {
|
|
26
|
-
const state = loadState(false);
|
|
27
|
-
if (!state) {
|
|
28
|
-
console.error(chalk.red('Failed to load state.json.'));
|
|
29
|
-
process.exit(1);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
const metrics = aggregateProjectMetrics(state);
|
|
33
|
-
|
|
34
|
-
if (options.save) {
|
|
35
|
-
const morphDir = join(process.cwd(), '.morph');
|
|
36
|
-
if (!existsSync(morphDir)) {
|
|
37
|
-
mkdirSync(morphDir, { recursive: true });
|
|
38
|
-
}
|
|
39
|
-
const outPath = join(morphDir, 'metrics.json');
|
|
40
|
-
writeFileSync(outPath, JSON.stringify(metrics, null, 2) + '\n', 'utf8');
|
|
41
|
-
console.log(chalk.green(`Metrics saved to ${outPath}`));
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
if (options.json) {
|
|
45
|
-
console.log(JSON.stringify(metrics, null, 2));
|
|
46
|
-
return;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
// Human-readable table
|
|
50
|
-
const s = metrics.summary;
|
|
51
|
-
console.log(chalk.bold('\n Project Metrics'));
|
|
52
|
-
console.log(' ' + '─'.repeat(50));
|
|
53
|
-
console.log(` Features: ${s.totalFeatures} (${s.completedFeatures} completed)`);
|
|
54
|
-
console.log(` Tasks: ${s.completedTasks}/${s.totalTasks} (${s.completionRate}%)`);
|
|
55
|
-
console.log(` Avg tasks/feat: ${s.avgTasksPerFeature}`);
|
|
56
|
-
console.log(` Dispatches: ${s.totalDispatches}`);
|
|
57
|
-
|
|
58
|
-
if (metrics.features.length > 0) {
|
|
59
|
-
console.log(chalk.bold('\n Features:'));
|
|
60
|
-
console.log(` ${'Name'.padEnd(24)} ${'Phase'.padEnd(14)} ${'Tasks'.padEnd(10)} Dispatches`);
|
|
61
|
-
console.log(' ' + '─'.repeat(60));
|
|
62
|
-
for (const f of metrics.features) {
|
|
63
|
-
const taskStr = `${f.completedTasks}/${f.taskCount}`;
|
|
64
|
-
console.log(
|
|
65
|
-
` ${f.name.padEnd(24)} ${chalk.cyan(f.phase.padEnd(14))} ${taskStr.padEnd(10)} ${f.dispatchCount}`
|
|
66
|
-
);
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
console.log();
|
|
70
|
-
} catch (err) {
|
|
71
|
-
console.error(chalk.red(`Error: ${err.message}`));
|
|
72
|
-
process.exit(1);
|
|
73
|
-
}
|
|
74
|
-
}
|
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* MORPH-SPEC Phase Reset Command
|
|
3
|
-
*
|
|
4
|
-
* Recovery tool for corrupted phase state.
|
|
5
|
-
* Sets the phase directly without approval gate checks.
|
|
6
|
-
*
|
|
7
|
-
* Usage:
|
|
8
|
-
* morph-spec phase reset <feature> <phase>
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
import chalk from 'chalk';
|
|
12
|
-
import { loadState, saveState, derivePhase } from '../../core/state/state-manager.js';
|
|
13
|
-
import { join } from 'path';
|
|
14
|
-
|
|
15
|
-
const VALID_PHASES = ['proposal', 'uiux', 'design', 'plan', 'tasks', 'implement', 'sync'];
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Core reset logic — exported for testing.
|
|
19
|
-
* @param {string} featureName
|
|
20
|
-
* @param {string} targetPhase
|
|
21
|
-
* @returns {{ success: boolean, error?: string, derivedPhase?: string }}
|
|
22
|
-
*/
|
|
23
|
-
export function resetPhase(featureName, targetPhase) {
|
|
24
|
-
if (!VALID_PHASES.includes(targetPhase)) {
|
|
25
|
-
return {
|
|
26
|
-
success: false,
|
|
27
|
-
error: `Unknown phase: "${targetPhase}". Valid phases: ${VALID_PHASES.join(', ')}`
|
|
28
|
-
};
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
const state = loadState();
|
|
32
|
-
if (!state?.features?.[featureName]) {
|
|
33
|
-
return {
|
|
34
|
-
success: false,
|
|
35
|
-
error: `Feature "${featureName}" not found in state.json`
|
|
36
|
-
};
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// Get filesystem-derived phase for comparison
|
|
40
|
-
const featureFolderPath = join(process.cwd(), '.morph', 'features', featureName);
|
|
41
|
-
const derivedPhase = derivePhase(featureFolderPath);
|
|
42
|
-
|
|
43
|
-
// Set phase directly — no approval gate checks
|
|
44
|
-
state.features[featureName].phase = targetPhase;
|
|
45
|
-
state.features[featureName].updatedAt = new Date().toISOString();
|
|
46
|
-
saveState(state);
|
|
47
|
-
|
|
48
|
-
return { success: true, derivedPhase };
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* CLI command handler
|
|
53
|
-
*/
|
|
54
|
-
export async function phaseResetCommand(feature, phase) {
|
|
55
|
-
console.log(chalk.cyan('\n╔════════════════════════════════════════════════╗'));
|
|
56
|
-
console.log(chalk.cyan('║ MORPH-SPEC PHASE RESET ║'));
|
|
57
|
-
console.log(chalk.cyan('╚════════════════════════════════════════════════╝\n'));
|
|
58
|
-
|
|
59
|
-
const result = resetPhase(feature, phase);
|
|
60
|
-
|
|
61
|
-
if (!result.success) {
|
|
62
|
-
console.log(chalk.red(`\n✗ ${result.error}`));
|
|
63
|
-
process.exit(1);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
console.log(chalk.green(`✓ Phase reset to "${phase}" for feature "${feature}"`));
|
|
67
|
-
|
|
68
|
-
if (result.derivedPhase !== phase) {
|
|
69
|
-
console.log(chalk.yellow(`\n⚠ Filesystem-derived phase is "${result.derivedPhase}" (folders on disk differ from state)`));
|
|
70
|
-
console.log(chalk.gray(` This is expected during recovery. The state.json value takes priority.`));
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
console.log('');
|
|
74
|
-
}
|