@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
|
@@ -165,13 +165,36 @@ export async function resetMorphSettings(targetPath) {
|
|
|
165
165
|
}
|
|
166
166
|
}
|
|
167
167
|
|
|
168
|
-
// Remove managed MCP servers
|
|
169
|
-
if (managed.mcps_installed
|
|
170
|
-
|
|
171
|
-
|
|
168
|
+
// Remove managed MCP servers from .mcp.json (primary location)
|
|
169
|
+
if (managed.mcps_installed) {
|
|
170
|
+
const mcpJson = await loadMcpJson(targetPath);
|
|
171
|
+
if (mcpJson.mcpServers) {
|
|
172
|
+
for (const mcpName of managed.mcps_installed) {
|
|
173
|
+
delete mcpJson.mcpServers[mcpName];
|
|
174
|
+
}
|
|
175
|
+
if (Object.keys(mcpJson.mcpServers).length === 0) {
|
|
176
|
+
delete mcpJson.mcpServers;
|
|
177
|
+
}
|
|
178
|
+
// Write back (or remove file if empty)
|
|
179
|
+
if (Object.keys(mcpJson).length === 0) {
|
|
180
|
+
const mcpJsonPath = join(targetPath, '.mcp.json');
|
|
181
|
+
if (existsSync(mcpJsonPath)) {
|
|
182
|
+
const { unlink } = await import('fs/promises');
|
|
183
|
+
await unlink(mcpJsonPath);
|
|
184
|
+
}
|
|
185
|
+
} else {
|
|
186
|
+
await saveMcpJson(targetPath, mcpJson);
|
|
187
|
+
}
|
|
172
188
|
}
|
|
173
|
-
|
|
174
|
-
|
|
189
|
+
|
|
190
|
+
// Also clean legacy entries from settings.local.json
|
|
191
|
+
if (settings.mcpServers) {
|
|
192
|
+
for (const mcpName of managed.mcps_installed) {
|
|
193
|
+
delete settings.mcpServers[mcpName];
|
|
194
|
+
}
|
|
195
|
+
if (Object.keys(settings.mcpServers).length === 0) {
|
|
196
|
+
delete settings.mcpServers;
|
|
197
|
+
}
|
|
175
198
|
}
|
|
176
199
|
}
|
|
177
200
|
|
|
@@ -192,31 +215,35 @@ export async function resetMorphSettings(targetPath) {
|
|
|
192
215
|
}
|
|
193
216
|
|
|
194
217
|
/**
|
|
195
|
-
* Install MCP server entries into .
|
|
196
|
-
*
|
|
218
|
+
* Install MCP server entries into .mcp.json at the project root.
|
|
219
|
+
* Claude Code reads MCP servers from .mcp.json (not settings.local.json).
|
|
220
|
+
* Tracks installed names in settings.local.json _morph_managed.mcps_installed for safe reset.
|
|
197
221
|
*
|
|
198
222
|
* @param {string} targetPath - Project root directory
|
|
199
|
-
* @param {Object} servers - Map of server name → config { command, args, env? }
|
|
223
|
+
* @param {Object} servers - Map of server name → config { type?, command, args, env? }
|
|
200
224
|
* @returns {Promise<Object>} Summary of changes { added, skipped, total }
|
|
201
225
|
*/
|
|
202
226
|
export async function installMcpServers(targetPath, servers) {
|
|
203
|
-
const
|
|
227
|
+
const mcpJson = await loadMcpJson(targetPath);
|
|
204
228
|
|
|
205
|
-
|
|
229
|
+
mcpJson.mcpServers = mcpJson.mcpServers || {};
|
|
206
230
|
|
|
207
231
|
const added = [];
|
|
208
232
|
const skipped = [];
|
|
209
233
|
|
|
210
234
|
for (const [name, config] of Object.entries(servers)) {
|
|
211
|
-
if (
|
|
235
|
+
if (mcpJson.mcpServers[name]) {
|
|
212
236
|
skipped.push(name);
|
|
213
237
|
} else {
|
|
214
|
-
|
|
238
|
+
mcpJson.mcpServers[name] = config;
|
|
215
239
|
added.push(name);
|
|
216
240
|
}
|
|
217
241
|
}
|
|
218
242
|
|
|
219
|
-
|
|
243
|
+
await saveMcpJson(targetPath, mcpJson);
|
|
244
|
+
|
|
245
|
+
// Track in settings.local.json _morph_managed
|
|
246
|
+
const settings = await loadSettings(targetPath);
|
|
220
247
|
settings[MORPH_MANAGED_KEY] = settings[MORPH_MANAGED_KEY] || {};
|
|
221
248
|
settings[MORPH_MANAGED_KEY].mcps_installed =
|
|
222
249
|
settings[MORPH_MANAGED_KEY].mcps_installed || [];
|
|
@@ -229,13 +256,28 @@ export async function installMcpServers(targetPath, servers) {
|
|
|
229
256
|
|
|
230
257
|
await saveSettings(targetPath, settings);
|
|
231
258
|
|
|
232
|
-
return { added, skipped, total: Object.keys(
|
|
259
|
+
return { added, skipped, total: Object.keys(mcpJson.mcpServers).length };
|
|
233
260
|
}
|
|
234
261
|
|
|
235
262
|
// ============================================================================
|
|
236
263
|
// Helpers
|
|
237
264
|
// ============================================================================
|
|
238
265
|
|
|
266
|
+
async function loadMcpJson(targetPath) {
|
|
267
|
+
const mcpJsonPath = join(targetPath, '.mcp.json');
|
|
268
|
+
try {
|
|
269
|
+
if (!existsSync(mcpJsonPath)) return {};
|
|
270
|
+
return JSON.parse(await readFile(mcpJsonPath, 'utf-8'));
|
|
271
|
+
} catch {
|
|
272
|
+
return {};
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
async function saveMcpJson(targetPath, data) {
|
|
277
|
+
const mcpJsonPath = join(targetPath, '.mcp.json');
|
|
278
|
+
await writeFile(mcpJsonPath, JSON.stringify(data, null, 2) + '\n', 'utf-8');
|
|
279
|
+
}
|
|
280
|
+
|
|
239
281
|
async function loadSettings(targetPath) {
|
|
240
282
|
const settingsPath = join(targetPath, '.claude', 'settings.local.json');
|
|
241
283
|
try {
|
package/src/utils/file-copier.js
CHANGED
|
@@ -50,164 +50,62 @@ const MORPH_PERMISSIONS = [
|
|
|
50
50
|
* Each entry maps to a Claude Code hook event.
|
|
51
51
|
*/
|
|
52
52
|
const MORPH_HOOKS = [
|
|
53
|
-
// ===
|
|
54
|
-
{
|
|
55
|
-
event: 'SessionStart',
|
|
56
|
-
matcher: 'startup|resume|compact',
|
|
57
|
-
hooks: [{
|
|
58
|
-
type: 'command',
|
|
59
|
-
command: 'node framework/hooks/claude-code/session-start/inject-morph-context.js'
|
|
60
|
-
}]
|
|
61
|
-
},
|
|
62
|
-
|
|
63
|
-
// === SessionStart: compact only — post-compact context restore ===
|
|
64
|
-
{
|
|
65
|
-
event: 'SessionStart',
|
|
66
|
-
matcher: 'compact',
|
|
67
|
-
hooks: [{
|
|
68
|
-
type: 'command',
|
|
69
|
-
command: 'node framework/hooks/claude-code/session-start/post-compact-restore.js'
|
|
70
|
-
}]
|
|
71
|
-
},
|
|
72
|
-
|
|
73
|
-
// === SessionStart: resume only — orientation context ===
|
|
74
|
-
{
|
|
75
|
-
event: 'SessionStart',
|
|
76
|
-
matcher: 'resume',
|
|
77
|
-
hooks: [{
|
|
78
|
-
type: 'command',
|
|
79
|
-
command: 'node framework/hooks/claude-code/session-start/resume-context.js'
|
|
80
|
-
}]
|
|
81
|
-
},
|
|
82
|
-
|
|
83
|
-
// === UserPromptSubmit ===
|
|
84
|
-
{
|
|
85
|
-
event: 'UserPromptSubmit',
|
|
86
|
-
matcher: null,
|
|
87
|
-
hooks: [
|
|
88
|
-
{
|
|
89
|
-
type: 'command',
|
|
90
|
-
command: 'node framework/hooks/claude-code/user-prompt/enrich-prompt.js'
|
|
91
|
-
},
|
|
92
|
-
]
|
|
93
|
-
},
|
|
94
|
-
|
|
95
|
-
// === PreToolUse: Write|Edit ===
|
|
53
|
+
// === PreToolUse: Write|Edit — phase + spec protection (silent on success) ===
|
|
96
54
|
{
|
|
97
55
|
event: 'PreToolUse',
|
|
98
56
|
matcher: 'Write|Edit',
|
|
99
57
|
hooks: [
|
|
100
|
-
// Note: protect-readonly-files.js replaced by native permissions.deny (see MORPH_PERMISSIONS)
|
|
101
|
-
// Order matters: cheapest check first
|
|
102
58
|
{
|
|
103
59
|
type: 'command',
|
|
104
|
-
command: 'node framework/hooks/claude-code/
|
|
60
|
+
command: 'node framework/hooks/claude-code/core/protect-spec-files.js'
|
|
105
61
|
},
|
|
106
62
|
{
|
|
107
63
|
type: 'command',
|
|
108
|
-
command: 'node framework/hooks/claude-code/
|
|
109
|
-
},
|
|
110
|
-
{
|
|
111
|
-
type: 'command',
|
|
112
|
-
command: 'node framework/hooks/claude-code/pre-tool-use/task-tracking-guard.js'
|
|
64
|
+
command: 'node framework/hooks/claude-code/core/enforce-phase-writes.js'
|
|
113
65
|
}
|
|
114
66
|
]
|
|
115
67
|
},
|
|
116
68
|
|
|
117
|
-
// === PreToolUse: Bash ===
|
|
69
|
+
// === PreToolUse: Bash — denylist (irreversible commands only) ===
|
|
118
70
|
{
|
|
119
71
|
event: 'PreToolUse',
|
|
120
72
|
matcher: 'Bash',
|
|
121
|
-
hooks: [{
|
|
122
|
-
type: 'prompt',
|
|
123
|
-
prompt: `You are a guard for a morph-spec project. Read the bash command from the hook input and check EXACTLY these 3 conditions. IMPORTANT: reading files (cat, head, tail, grep, less, bat) is NEVER blocked regardless of path.
|
|
124
|
-
1. Does the command DELETE .morph/ or .morph/state.json or .morph/framework/ using rm -r, rm -rf, or rmdir? BLOCK only these exact paths. NOTE: deleting .morph/features/ subdirectories is NOT blocked.
|
|
125
|
-
2. Does the command OVERWRITE .morph/state.json using a shell redirect (>) combined with sed, jq, awk, echo, cat, or printf? If yes, BLOCK.
|
|
126
|
-
3. Does the command WRITE TO .morph/framework/ via a shell redirect (>) or Node.js fs.writeFileSync/appendFileSync on a path under .morph/framework/? READING .morph/framework/ is always allowed. Writing to .morph/features/, .morph/config/, .morph/context/ is allowed.
|
|
127
|
-
If ANY of these 3 specific conditions matches, respond: {"ok": false, "reason": "MORPH-SPEC: [explain what was blocked]. To read agents/state use: morph-spec state get <feature> | morph-spec dispatch-agents <feature> <phase> --table | cat .morph/framework/agents.json (reading is always allowed). To update state use: morph-spec state set <feature> <key> <value>."}
|
|
128
|
-
Otherwise respond: {"ok": true}`
|
|
129
|
-
}]
|
|
130
|
-
},
|
|
131
|
-
|
|
132
|
-
// === PostToolUse: Agent ===
|
|
133
|
-
{
|
|
134
|
-
event: 'PostToolUse',
|
|
135
|
-
matcher: 'Agent',
|
|
136
|
-
hooks: [{
|
|
137
|
-
type: 'command',
|
|
138
|
-
command: 'node framework/hooks/claude-code/post-tool-use/agent-complete.js'
|
|
139
|
-
}]
|
|
140
|
-
},
|
|
141
|
-
|
|
142
|
-
// === PostToolUse: Bash ===
|
|
143
|
-
{
|
|
144
|
-
event: 'PostToolUse',
|
|
145
|
-
matcher: 'Bash',
|
|
146
73
|
hooks: [
|
|
147
74
|
{
|
|
148
75
|
type: 'command',
|
|
149
|
-
command: 'node framework/hooks/claude-code/
|
|
150
|
-
},
|
|
151
|
-
{
|
|
152
|
-
type: 'command',
|
|
153
|
-
command: 'node framework/hooks/claude-code/post-tool-use/validator-feedback.js'
|
|
76
|
+
command: 'node framework/hooks/claude-code/core/pre-bash-denylist.js'
|
|
154
77
|
}
|
|
155
78
|
]
|
|
156
79
|
},
|
|
157
80
|
|
|
158
|
-
// === PostToolUse:
|
|
81
|
+
// === PostToolUse: Write|Edit — typecheck (debounced, silent on success) ===
|
|
159
82
|
{
|
|
160
83
|
event: 'PostToolUse',
|
|
161
|
-
matcher:
|
|
162
|
-
hooks: [{
|
|
163
|
-
type: 'command',
|
|
164
|
-
command: 'node framework/hooks/claude-code/post-tool-use/loop-detect.js',
|
|
165
|
-
timeout: 5000
|
|
166
|
-
}]
|
|
167
|
-
},
|
|
168
|
-
|
|
169
|
-
// === Stop ===
|
|
170
|
-
{
|
|
171
|
-
event: 'Stop',
|
|
172
|
-
matcher: null,
|
|
173
|
-
hooks: [{
|
|
174
|
-
type: 'command',
|
|
175
|
-
command: 'node framework/hooks/claude-code/stop/validate-completion.js'
|
|
176
|
-
}]
|
|
177
|
-
},
|
|
178
|
-
|
|
179
|
-
// === PreCompact ===
|
|
180
|
-
{
|
|
181
|
-
event: 'PreCompact',
|
|
182
|
-
matcher: null,
|
|
84
|
+
matcher: 'Write|Edit',
|
|
183
85
|
hooks: [{
|
|
184
86
|
type: 'command',
|
|
185
|
-
command: 'node framework/hooks/claude-code/
|
|
87
|
+
command: 'node framework/hooks/claude-code/core/post-edit-typecheck.js'
|
|
186
88
|
}]
|
|
187
89
|
},
|
|
188
90
|
|
|
189
|
-
// ===
|
|
91
|
+
// === PostToolUse: All — loop detection ===
|
|
190
92
|
{
|
|
191
|
-
event: '
|
|
93
|
+
event: 'PostToolUse',
|
|
192
94
|
matcher: null,
|
|
193
95
|
hooks: [{
|
|
194
96
|
type: 'command',
|
|
195
|
-
command: 'node framework/hooks/claude-code/
|
|
196
|
-
timeout:
|
|
97
|
+
command: 'node framework/hooks/claude-code/core/loop-detect.js',
|
|
98
|
+
timeout: 5000
|
|
197
99
|
}]
|
|
198
100
|
},
|
|
199
101
|
|
|
200
|
-
// ===
|
|
201
|
-
// Fires when an Agent Team teammate finishes and is about to go idle.
|
|
202
|
-
// Exit 2 = send feedback to teammate to continue working (validation failed).
|
|
203
|
-
// Exit 0 = teammate may become idle (validation passed).
|
|
204
|
-
// Requires CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1 (set in env above).
|
|
102
|
+
// === Stop — build check + session snapshot ===
|
|
205
103
|
{
|
|
206
|
-
event: '
|
|
104
|
+
event: 'Stop',
|
|
207
105
|
matcher: null,
|
|
208
106
|
hooks: [{
|
|
209
107
|
type: 'command',
|
|
210
|
-
command: 'node framework/hooks/claude-code/
|
|
108
|
+
command: 'node framework/hooks/claude-code/core/validate-completion.js'
|
|
211
109
|
}]
|
|
212
110
|
}
|
|
213
111
|
];
|
|
@@ -2,18 +2,16 @@
|
|
|
2
2
|
* Skills Installer
|
|
3
3
|
*
|
|
4
4
|
* Copies framework skills to .claude/skills/ during morph-spec init.
|
|
5
|
-
* Installs
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* Level-3 and level-4 only contain README.md placeholder files and are skipped.
|
|
5
|
+
* Installs only the `core/` skill set — the canonical NLH skills referenced by
|
|
6
|
+
* MORPH.md (morph-brainstorming, morph-stack-scan, morph-plan, morph-implement,
|
|
7
|
+
* morph-eval, morph-review, morph-standards). The 3-tier system was collapsed
|
|
8
|
+
* into a single source of truth in framework/skills/core/.
|
|
10
9
|
*
|
|
11
|
-
* Source skills
|
|
12
|
-
*
|
|
13
|
-
* - Flat file: framework/skills/{level}/{skill-name}.md (legacy, still supported)
|
|
10
|
+
* Source skills are directory-based: framework/skills/core/{skill-name}/SKILL.md
|
|
11
|
+
* (plus optional scripts/, references/, assets/ subdirectories).
|
|
14
12
|
*
|
|
15
|
-
* Directory-based skills have their entire directory copied
|
|
16
|
-
*
|
|
13
|
+
* Directory-based skills have their entire directory copied so Claude Code can
|
|
14
|
+
* execute scripts and load references on demand.
|
|
17
15
|
*/
|
|
18
16
|
|
|
19
17
|
import { mkdirSync, copyFileSync, existsSync, readdirSync, statSync, readFileSync, rmSync } from 'fs';
|
|
@@ -26,12 +24,9 @@ const FRAMEWORK_SKILLS_DIR = join(__dirname, '..', '..', 'framework', 'skills');
|
|
|
26
24
|
|
|
27
25
|
/**
|
|
28
26
|
* Skill levels to install in .claude/skills/.
|
|
29
|
-
*
|
|
30
|
-
* (phase-design, phase-implement, etc.) are installed. Level-2 domain
|
|
31
|
-
* specialists are installed as native subagents via installDomainAgents()
|
|
32
|
-
* instead. Level-3 and level-4 are skipped (no content yet).
|
|
27
|
+
* Only `core/` survives — single source of truth aligned with MORPH.md.
|
|
33
28
|
*/
|
|
34
|
-
const SKILL_LEVELS_TO_INSTALL = ['
|
|
29
|
+
const SKILL_LEVELS_TO_INSTALL = ['core'];
|
|
35
30
|
|
|
36
31
|
/**
|
|
37
32
|
* Check if a skill should be installed based on its stacks: frontmatter.
|
|
@@ -75,9 +70,8 @@ function copyDirectory(src, dest) {
|
|
|
75
70
|
* 2. Flat .md skill (legacy): srcDir/{skill-name}.md
|
|
76
71
|
* → destDir/{skill-name}/SKILL.md
|
|
77
72
|
*
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
* it contains a SKILL.md file.
|
|
73
|
+
* The `core/` level directory is traversed recursively. A directory is
|
|
74
|
+
* recognized as a skill directory when it contains a SKILL.md file.
|
|
81
75
|
*
|
|
82
76
|
* @param {string} srcDir - Source directory to walk
|
|
83
77
|
* @param {string} destDir - Destination base directory (.claude/skills/)
|
package/bin/detect-agents.js
DELETED
|
@@ -1,226 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* MORPH-SPEC Agent Detection CLI
|
|
5
|
-
*
|
|
6
|
-
* Detecta automaticamente quais agentes devem ser ativados baseado
|
|
7
|
-
* em keywords no input do usuário.
|
|
8
|
-
*
|
|
9
|
-
* Uso:
|
|
10
|
-
* node bin/detect-agents.js "implementar jobs agendados"
|
|
11
|
-
* node bin/detect-agents.js --verbose "criar dashboard com charts"
|
|
12
|
-
* node bin/detect-agents.js --json "adicionar background tasks"
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
import { readFileSync } from 'fs';
|
|
16
|
-
import { join, dirname } from 'path';
|
|
17
|
-
import { fileURLToPath } from 'url';
|
|
18
|
-
|
|
19
|
-
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
20
|
-
const frameworkRoot = join(__dirname, '..');
|
|
21
|
-
const AGENTS_CONFIG_PATH = join(frameworkRoot, '.morph/framework/agents.json');
|
|
22
|
-
|
|
23
|
-
// Cores para output no terminal
|
|
24
|
-
const colors = {
|
|
25
|
-
reset: '\x1b[0m',
|
|
26
|
-
bright: '\x1b[1m',
|
|
27
|
-
green: '\x1b[32m',
|
|
28
|
-
yellow: '\x1b[33m',
|
|
29
|
-
blue: '\x1b[34m',
|
|
30
|
-
cyan: '\x1b[36m',
|
|
31
|
-
gray: '\x1b[90m'
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
function log(message, color = 'reset') {
|
|
35
|
-
console.log(`${colors[color]}${message}${colors.reset}`);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Carrega configuração de agentes
|
|
40
|
-
* @returns {Object} Configuração de agentes
|
|
41
|
-
*/
|
|
42
|
-
function loadAgentsConfig() {
|
|
43
|
-
try {
|
|
44
|
-
const content = readFileSync(AGENTS_CONFIG_PATH, 'utf8');
|
|
45
|
-
return JSON.parse(content);
|
|
46
|
-
} catch (err) {
|
|
47
|
-
console.error(`ERROR: Failed to load agents config: ${err.message}`);
|
|
48
|
-
process.exit(1);
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Detecta agentes baseado em user input
|
|
54
|
-
* @param {string} userInput - Input do usuário
|
|
55
|
-
* @param {Object} config - Configuração de agentes
|
|
56
|
-
* @returns {Object} Resultado da detecção { core, specialists, all, matches }
|
|
57
|
-
*/
|
|
58
|
-
function detectAgents(userInput, config) {
|
|
59
|
-
const input = userInput.toLowerCase();
|
|
60
|
-
const coreAgents = config.agents.core || [];
|
|
61
|
-
const specialists = config.agents.specialists || [];
|
|
62
|
-
|
|
63
|
-
// Core agents sempre ativos
|
|
64
|
-
const coreIds = coreAgents.map(a => a.id);
|
|
65
|
-
|
|
66
|
-
// Detectar specialists por keyword matching
|
|
67
|
-
const specialistMatches = [];
|
|
68
|
-
|
|
69
|
-
for (const agent of specialists) {
|
|
70
|
-
const keywords = agent.autoActivation?.keywords || [];
|
|
71
|
-
const matchedKeywords = [];
|
|
72
|
-
|
|
73
|
-
for (const keyword of keywords) {
|
|
74
|
-
if (input.includes(keyword.toLowerCase())) {
|
|
75
|
-
matchedKeywords.push(keyword);
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
if (matchedKeywords.length > 0) {
|
|
80
|
-
specialistMatches.push({
|
|
81
|
-
id: agent.id,
|
|
82
|
-
name: agent.name,
|
|
83
|
-
emoji: agent.emoji,
|
|
84
|
-
matchedKeywords: matchedKeywords,
|
|
85
|
-
matchCount: matchedKeywords.length
|
|
86
|
-
});
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
// Ordenar por número de matches (mais relevante primeiro)
|
|
91
|
-
specialistMatches.sort((a, b) => b.matchCount - a.matchCount);
|
|
92
|
-
|
|
93
|
-
const specialistIds = specialistMatches.map(m => m.id);
|
|
94
|
-
const allIds = [...new Set([...coreIds, ...specialistIds])];
|
|
95
|
-
|
|
96
|
-
return {
|
|
97
|
-
core: coreIds,
|
|
98
|
-
specialists: specialistIds,
|
|
99
|
-
all: allIds,
|
|
100
|
-
matches: specialistMatches,
|
|
101
|
-
input: userInput
|
|
102
|
-
};
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* Formata output em modo verbose
|
|
107
|
-
* @param {Object} result - Resultado da detecção
|
|
108
|
-
*/
|
|
109
|
-
function formatVerbose(result) {
|
|
110
|
-
log('\n╔════════════════════════════════════════════════════════════════╗', 'cyan');
|
|
111
|
-
log('║ AGENT DETECTION RESULT ║', 'cyan');
|
|
112
|
-
log('╠════════════════════════════════════════════════════════════════╣', 'cyan');
|
|
113
|
-
log(`║ Input: ${result.input.substring(0, 54).padEnd(54)} ║`, 'bright');
|
|
114
|
-
log('╠════════════════════════════════════════════════════════════════╣', 'cyan');
|
|
115
|
-
log('║ CORE AGENTS (always active) ║', 'cyan');
|
|
116
|
-
log('╠════════════════════════════════════════════════════════════════╣', 'cyan');
|
|
117
|
-
|
|
118
|
-
result.core.forEach(id => {
|
|
119
|
-
log(`║ ✓ ${id.padEnd(58)}║`, 'green');
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
if (result.specialists.length > 0) {
|
|
123
|
-
log('╠════════════════════════════════════════════════════════════════╣', 'cyan');
|
|
124
|
-
log('║ SPECIALIST AGENTS (keyword matched) ║', 'cyan');
|
|
125
|
-
log('╠════════════════════════════════════════════════════════════════╣', 'cyan');
|
|
126
|
-
|
|
127
|
-
result.matches.forEach(match => {
|
|
128
|
-
const line = `${match.emoji} ${match.name} (${match.matchCount} matches)`;
|
|
129
|
-
log(`║ ${line.padEnd(61)}║`, 'yellow');
|
|
130
|
-
const keywords = match.matchedKeywords.join(', ');
|
|
131
|
-
const keywordLine = `Keywords: ${keywords}`;
|
|
132
|
-
// Quebrar em múltiplas linhas se necessário
|
|
133
|
-
if (keywordLine.length > 58) {
|
|
134
|
-
const chunks = keywordLine.match(/.{1,58}/g) || [];
|
|
135
|
-
chunks.forEach((chunk, i) => {
|
|
136
|
-
log(`║ ${chunk.padEnd(59)}║`, 'gray');
|
|
137
|
-
});
|
|
138
|
-
} else {
|
|
139
|
-
log(`║ ${keywordLine.padEnd(59)}║`, 'gray');
|
|
140
|
-
}
|
|
141
|
-
});
|
|
142
|
-
} else {
|
|
143
|
-
log('╠════════════════════════════════════════════════════════════════╣', 'cyan');
|
|
144
|
-
log('║ No specialist agents matched ║', 'gray');
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
log('╠════════════════════════════════════════════════════════════════╣', 'cyan');
|
|
148
|
-
log(`║ TOTAL: ${result.all.length} agents activated ║`, 'bright');
|
|
149
|
-
log('╚════════════════════════════════════════════════════════════════╝\n', 'cyan');
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
/**
|
|
153
|
-
* Formata output em modo simples (apenas IDs)
|
|
154
|
-
* @param {Object} result - Resultado da detecção
|
|
155
|
-
*/
|
|
156
|
-
function formatSimple(result) {
|
|
157
|
-
result.all.forEach(id => console.log(id));
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
/**
|
|
161
|
-
* Formata output em modo JSON
|
|
162
|
-
* @param {Object} result - Resultado da detecção
|
|
163
|
-
*/
|
|
164
|
-
function formatJSON(result) {
|
|
165
|
-
console.log(JSON.stringify(result, null, 2));
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
// ============================================================================
|
|
169
|
-
// MAIN
|
|
170
|
-
// ============================================================================
|
|
171
|
-
|
|
172
|
-
function main() {
|
|
173
|
-
const args = process.argv.slice(2);
|
|
174
|
-
|
|
175
|
-
// Parse flags
|
|
176
|
-
const hasVerbose = args.includes('--verbose') || args.includes('-v');
|
|
177
|
-
const hasJSON = args.includes('--json') || args.includes('-j');
|
|
178
|
-
const hasHelp = args.includes('--help') || args.includes('-h');
|
|
179
|
-
const hasIdsOnly = args.includes('--ids-only') || args.includes('-i');
|
|
180
|
-
|
|
181
|
-
// Remove flags para pegar user input
|
|
182
|
-
const userInput = args
|
|
183
|
-
.filter(arg => !arg.startsWith('--') && !arg.startsWith('-'))
|
|
184
|
-
.join(' ');
|
|
185
|
-
|
|
186
|
-
if (hasHelp || args.length === 0) {
|
|
187
|
-
log('\nMORPH-SPEC Agent Detection CLI', 'bright');
|
|
188
|
-
log('\nUsage:', 'cyan');
|
|
189
|
-
log(' node bin/detect-agents.js [options] "<user input>"', 'gray');
|
|
190
|
-
log('\nOptions:', 'cyan');
|
|
191
|
-
log(' -v, --verbose Show detailed detection results', 'gray');
|
|
192
|
-
log(' -j, --json Output full result as JSON', 'gray');
|
|
193
|
-
log(' -i, --ids-only Output only agent IDs (one per line)', 'gray');
|
|
194
|
-
log(' -h, --help Show this help', 'gray');
|
|
195
|
-
log('\nExamples:', 'cyan');
|
|
196
|
-
log(' node bin/detect-agents.js "criar dashboard com charts"', 'gray');
|
|
197
|
-
log(' node bin/detect-agents.js --verbose "implementar jobs agendados"', 'gray');
|
|
198
|
-
log(' node bin/detect-agents.js --json "adicionar RAG pipeline"', 'gray');
|
|
199
|
-
log(' node bin/detect-agents.js --ids-only "background tasks"\n', 'gray');
|
|
200
|
-
process.exit(0);
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
if (!userInput) {
|
|
204
|
-
console.error('ERROR: User input is required');
|
|
205
|
-
console.error('Run with --help for usage information');
|
|
206
|
-
process.exit(1);
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
// Carregar config e detectar agentes
|
|
210
|
-
const config = loadAgentsConfig();
|
|
211
|
-
const result = detectAgents(userInput, config);
|
|
212
|
-
|
|
213
|
-
// Formatar output
|
|
214
|
-
if (hasJSON) {
|
|
215
|
-
formatJSON(result);
|
|
216
|
-
} else if (hasVerbose) {
|
|
217
|
-
formatVerbose(result);
|
|
218
|
-
} else if (hasIdsOnly) {
|
|
219
|
-
formatSimple(result);
|
|
220
|
-
} else {
|
|
221
|
-
// Default: JSON compact (só all array)
|
|
222
|
-
console.log(JSON.stringify(result.all));
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
main();
|