@polymorphism-tech/morph-spec 4.9.0 → 4.10.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/README.md +2 -2
- package/bin/morph-spec.js +30 -0
- package/bin/task-manager.js +34 -22
- package/claude-plugin.json +1 -1
- package/docs/CHEATSHEET.md +1 -1
- package/docs/QUICKSTART.md +1 -1
- package/framework/CLAUDE.md +35 -98
- package/framework/agents/backend/api-designer.md +3 -0
- package/framework/agents/backend/dotnet-senior.md +3 -0
- package/framework/agents/backend/ef-modeler.md +2 -0
- package/framework/agents/backend/hangfire-orchestrator.md +2 -0
- package/framework/agents/backend/ms-agent-expert.md +2 -0
- package/framework/agents/frontend/blazor-builder.md +2 -0
- package/framework/agents/frontend/nextjs-expert.md +2 -0
- package/framework/agents/infrastructure/azure-architect.md +2 -0
- package/framework/agents/infrastructure/azure-deploy-specialist.md +2 -0
- package/framework/agents/infrastructure/bicep-architect.md +2 -0
- package/framework/agents/infrastructure/container-specialist.md +2 -0
- package/framework/agents/infrastructure/devops-engineer.md +3 -0
- package/framework/agents/infrastructure/infra-architect.md +3 -0
- package/framework/agents/integrations/asaas-financial.md +2 -0
- package/framework/agents/integrations/azure-identity.md +2 -0
- package/framework/agents/integrations/clerk-auth.md +3 -0
- package/framework/agents/integrations/hangfire-integration.md +2 -0
- package/framework/agents/integrations/resend-email.md +2 -0
- package/framework/agents.json +37 -7
- package/framework/commands/commit.md +166 -0
- package/framework/commands/morph-apply.md +156 -155
- package/framework/commands/morph-archive.md +33 -27
- package/framework/commands/morph-infra.md +83 -77
- package/framework/commands/morph-preflight.md +97 -55
- package/framework/commands/morph-proposal.md +131 -58
- package/framework/commands/morph-status.md +36 -30
- package/framework/commands/morph-troubleshoot.md +68 -59
- package/framework/hooks/claude-code/notification/approval-reminder.js +3 -2
- package/framework/hooks/claude-code/post-tool-use/dispatch.js +154 -31
- package/framework/hooks/claude-code/post-tool-use/skill-reminder.js +7 -84
- package/framework/hooks/claude-code/post-tool-use/validator-feedback.js +8 -17
- package/framework/hooks/claude-code/pre-compact/save-morph-context.js +16 -3
- package/framework/hooks/claude-code/pre-tool-use/enforce-phase-writes.js +4 -3
- package/framework/hooks/claude-code/pre-tool-use/protect-spec-files.js +3 -2
- package/framework/hooks/claude-code/pre-tool-use/task-tracking-guard.js +60 -0
- package/framework/hooks/claude-code/session-start/inject-morph-context.js +55 -2
- package/framework/hooks/claude-code/session-start/post-compact-restore.js +41 -0
- package/framework/hooks/claude-code/stop/validate-completion.js +2 -15
- package/framework/hooks/claude-code/user-prompt/enrich-prompt.js +23 -5
- package/framework/hooks/shared/compact-restore.js +100 -0
- package/framework/hooks/shared/dispatch-helpers.js +116 -0
- package/framework/hooks/shared/phase-utils.js +9 -5
- package/framework/hooks/shared/state-reader.js +27 -3
- package/framework/phases.json +30 -7
- package/framework/rules/csharp-standards.md +3 -0
- package/framework/rules/frontend-standards.md +2 -0
- package/framework/rules/infrastructure-standards.md +3 -0
- package/framework/rules/morph-workflow.md +143 -86
- package/framework/rules/nextjs-standards.md +2 -0
- package/framework/rules/testing-standards.md +3 -0
- package/framework/skills/level-0-meta/mcp-registry.json +86 -51
- package/framework/skills/level-0-meta/morph-brainstorming/SKILL.md +139 -0
- package/framework/skills/level-0-meta/morph-checklist/SKILL.md +42 -19
- package/framework/skills/level-0-meta/{code-review → morph-code-review}/SKILL.md +8 -5
- package/framework/skills/level-0-meta/{code-review-nextjs → morph-code-review-nextjs}/SKILL.md +8 -6
- package/framework/skills/level-0-meta/morph-frontend-review/SKILL.md +362 -0
- package/framework/skills/level-0-meta/morph-init/SKILL.md +114 -20
- package/framework/skills/level-0-meta/morph-post-implementation/SKILL.md +362 -0
- package/framework/skills/level-0-meta/morph-replicate/SKILL.md +95 -87
- package/framework/skills/level-0-meta/{simulation-checklist → morph-simulation-checklist}/SKILL.md +24 -0
- package/framework/skills/level-0-meta/{tool-usage-guide → morph-tool-usage-guide}/SKILL.md +43 -43
- package/framework/skills/level-0-meta/{tool-usage-guide → morph-tool-usage-guide}/references/tools-per-phase.md +1 -2
- package/framework/skills/level-0-meta/{verification-before-completion → morph-verification-before-completion}/SKILL.md +23 -12
- package/framework/skills/level-0-meta/{verification-before-completion → morph-verification-before-completion}/scripts/check-phase-outputs.mjs +2 -2
- package/framework/skills/level-1-workflows/morph-phase-clarify/SKILL.md +247 -0
- package/framework/skills/level-1-workflows/morph-phase-codebase-analysis/SKILL.md +270 -0
- package/framework/skills/level-1-workflows/morph-phase-design/SKILL.md +499 -0
- package/framework/skills/level-1-workflows/morph-phase-implement/.morph/logs/activity.json +38 -0
- package/framework/skills/level-1-workflows/morph-phase-implement/SKILL.md +472 -0
- package/framework/skills/level-1-workflows/morph-phase-implement/prompts/code-quality-reviewer-prompt.md +50 -0
- package/framework/skills/level-1-workflows/morph-phase-implement/prompts/implementer-prompt.md +45 -0
- package/framework/skills/level-1-workflows/morph-phase-implement/prompts/spec-reviewer-prompt.md +47 -0
- package/framework/skills/level-1-workflows/morph-phase-plan/SKILL.md +246 -0
- package/framework/skills/level-1-workflows/morph-phase-setup/SKILL.md +238 -0
- package/framework/skills/level-1-workflows/morph-phase-tasks/.morph/logs/activity.json +14 -0
- package/framework/skills/level-1-workflows/morph-phase-tasks/SKILL.md +312 -0
- package/framework/skills/level-1-workflows/{phase-tasks → morph-phase-tasks}/scripts/validate-tasks.mjs +3 -3
- package/framework/skills/level-1-workflows/morph-phase-uiux/SKILL.md +324 -0
- package/framework/skills/level-1-workflows/morph-scope-escalation/SKILL.md +146 -0
- package/framework/standards/integration/mcp/mcp-tools.md +25 -7
- package/framework/templates/docs/onboarding.md +2 -2
- package/package.json +3 -4
- package/src/commands/agents/dispatch-agents.js +50 -3
- package/src/commands/mcp/mcp-setup.js +39 -2
- package/src/commands/phase/phase-reset.js +74 -0
- package/src/commands/project/doctor.js +26 -7
- package/src/commands/project/update.js +4 -4
- package/src/commands/scope/escalate.js +215 -0
- package/src/commands/state/advance-phase.js +27 -53
- package/src/commands/state/state.js +1 -1
- package/src/commands/task/expand.js +100 -0
- package/src/core/paths/output-schema.js +4 -3
- package/src/core/state/phase-state-machine.js +7 -4
- package/src/core/state/state-manager.js +4 -3
- package/src/lib/detectors/claude-config-detector.js +93 -347
- package/src/lib/detectors/design-system-detector.js +189 -189
- package/src/lib/detectors/index.js +155 -57
- package/src/lib/generators/context-generator.js +2 -2
- package/src/lib/installers/mcp-installer.js +37 -5
- package/src/lib/phase-chain/phase-validator.js +22 -16
- package/src/lib/scope/impact-analyzer.js +106 -0
- package/src/lib/stack-filter.js +58 -0
- package/src/lib/tasks/task-parser.js +1 -1
- package/src/lib/validators/shared/emit-validator-dispatch.js +64 -0
- package/src/scripts/setup-infra.js +68 -18
- package/src/utils/agents-installer.js +51 -17
- package/src/utils/claude-md-injector.js +90 -0
- package/src/utils/file-copier.js +0 -1
- package/src/utils/hooks-installer.js +16 -5
- package/src/utils/skills-installer.js +67 -7
- package/CLAUDE.md +0 -98
- package/framework/memory/patterns-learned.md +0 -766
- package/framework/skills/level-0-meta/brainstorming/SKILL.md +0 -137
- package/framework/skills/level-0-meta/frontend-review/SKILL.md +0 -359
- package/framework/skills/level-0-meta/post-implementation/SKILL.md +0 -362
- package/framework/skills/level-0-meta/terminal-title/SKILL.md +0 -61
- package/framework/skills/level-0-meta/terminal-title/scripts/set_title.sh +0 -65
- package/framework/skills/level-1-workflows/phase-clarify/SKILL.md +0 -216
- package/framework/skills/level-1-workflows/phase-codebase-analysis/SKILL.md +0 -252
- package/framework/skills/level-1-workflows/phase-design/SKILL.md +0 -383
- package/framework/skills/level-1-workflows/phase-implement/SKILL.md +0 -492
- package/framework/skills/level-1-workflows/phase-setup/SKILL.md +0 -195
- package/framework/skills/level-1-workflows/phase-tasks/SKILL.md +0 -271
- package/framework/skills/level-1-workflows/phase-uiux/SKILL.md +0 -286
- package/src/commands/project/index.js +0 -8
- package/src/core/index.js +0 -10
- package/src/core/state/index.js +0 -8
- package/src/core/templates/index.js +0 -9
- package/src/core/templates/template-data-sources.js +0 -325
- package/src/core/workflows/index.js +0 -7
- package/src/lib/detectors/config-detector.js +0 -223
- package/src/lib/detectors/standards-generator.js +0 -335
- package/src/lib/detectors/structure-detector.js +0 -275
- package/src/lib/monitor/agent-resolver.js +0 -144
- package/src/lib/monitor/renderer.js +0 -230
- package/src/lib/orchestration/index.js +0 -7
- package/src/lib/orchestration/team-orchestrator.js +0 -404
- package/src/sanitizer/context-sanitizer.js +0 -221
- package/src/sanitizer/patterns.js +0 -163
- package/src/writer/file-writer.js +0 -86
- /package/framework/skills/level-0-meta/{brainstorming → morph-brainstorming}/references/proposal-example.md +0 -0
- /package/framework/skills/level-0-meta/{code-review → morph-code-review}/references/review-example.md +0 -0
- /package/framework/skills/level-0-meta/{code-review → morph-code-review}/references/review-guidelines.md +0 -0
- /package/framework/skills/level-0-meta/{code-review → morph-code-review}/scripts/scan-csharp.mjs +0 -0
- /package/framework/skills/level-0-meta/{code-review-nextjs → morph-code-review-nextjs}/references/review-example-nextjs.md +0 -0
- /package/framework/skills/level-0-meta/{code-review-nextjs → morph-code-review-nextjs}/scripts/scan-nextjs.mjs +0 -0
- /package/framework/skills/level-0-meta/{frontend-review → morph-frontend-review}/scripts/scan-accessibility.mjs +0 -0
- /package/framework/skills/level-0-meta/{post-implementation → morph-post-implementation}/scripts/detect-dev-server.mjs +0 -0
- /package/framework/skills/level-0-meta/{post-implementation → morph-post-implementation}/scripts/detect-stack.mjs +0 -0
- /package/framework/skills/level-1-workflows/{phase-clarify → morph-phase-clarify}/references/clarifications-example.md +0 -0
- /package/framework/skills/level-1-workflows/{phase-design → morph-phase-design}/references/architecture-analysis-guide.md +0 -0
- /package/framework/skills/level-1-workflows/{phase-design → morph-phase-design}/references/spec-authoring-guide.md +0 -0
- /package/framework/skills/level-1-workflows/{phase-design → morph-phase-design}/references/spec-example.md +0 -0
- /package/framework/skills/level-1-workflows/{phase-implement → morph-phase-implement}/references/recap-example.md +0 -0
- /package/framework/skills/level-1-workflows/{phase-implement → morph-phase-implement}/references/vsa-implementation-guide.md +0 -0
- /package/framework/skills/level-1-workflows/{phase-tasks → morph-phase-tasks}/references/task-planning-patterns.md +0 -0
- /package/framework/skills/level-1-workflows/{phase-tasks → morph-phase-tasks}/references/tasks-example.md +0 -0
|
@@ -8,8 +8,9 @@
|
|
|
8
8
|
* maxTurns, skills, memory) followed by the agent's spawn prompt as the body.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import { readFileSync, writeFileSync, mkdirSync, existsSync, readdirSync, statSync } from 'node:fs';
|
|
11
|
+
import { readFileSync, writeFileSync, mkdirSync, existsSync, readdirSync, statSync, unlinkSync } from 'node:fs';
|
|
12
12
|
import { join } from 'path';
|
|
13
|
+
import { parseStacks, shouldInstall } from '../lib/stack-filter.js';
|
|
13
14
|
|
|
14
15
|
/**
|
|
15
16
|
* Installs tier-1 and tier-2 morph agents as native Claude Code subagents
|
|
@@ -39,7 +40,9 @@ export async function installAgents(projectDir, frameworkDir = 'framework', opti
|
|
|
39
40
|
|
|
40
41
|
const DOTNET_STACKS = ['dotnet', 'blazor', 'dotnet-api', 'fullstack'];
|
|
41
42
|
const eligible = agents.filter(a => {
|
|
42
|
-
if (a.tier !== 1 && a.tier !== 2) return false;
|
|
43
|
+
if (a.tier !== 1 && a.tier !== 2 && a.tier !== 4) return false;
|
|
44
|
+
// Tier 4 validators must have a teammate with spawn_prompt
|
|
45
|
+
if (a.tier === 4 && !a.teammate?.spawn_prompt) return false;
|
|
43
46
|
// Skip dotnet-senior for non-.NET projects
|
|
44
47
|
if (a.id === 'dotnet-senior' && projectStack && !DOTNET_STACKS.includes(projectStack)) {
|
|
45
48
|
return false;
|
|
@@ -49,9 +52,11 @@ export async function installAgents(projectDir, frameworkDir = 'framework', opti
|
|
|
49
52
|
|
|
50
53
|
let tier1 = 0;
|
|
51
54
|
let tier2 = 0;
|
|
55
|
+
let tier4 = 0;
|
|
52
56
|
for (const agent of eligible) {
|
|
53
57
|
const slug = agent.id ?? agent.name?.toLowerCase().replace(/\s+/g, '-');
|
|
54
|
-
const
|
|
58
|
+
const prefix = agent.tier === 4 ? 'morph-validator-' : 'morph-';
|
|
59
|
+
const filename = `${prefix}${slug}.md`;
|
|
55
60
|
const targetPath = join(targetDir, filename);
|
|
56
61
|
|
|
57
62
|
const description = buildDescription(agent);
|
|
@@ -63,8 +68,9 @@ export async function installAgents(projectDir, frameworkDir = 'framework', opti
|
|
|
63
68
|
|
|
64
69
|
if (agent.tier === 1) tier1++;
|
|
65
70
|
else if (agent.tier === 2) tier2++;
|
|
71
|
+
else if (agent.tier === 4) tier4++;
|
|
66
72
|
}
|
|
67
|
-
return { tier1, tier2 };
|
|
73
|
+
return { tier1, tier2, tier4 };
|
|
68
74
|
}
|
|
69
75
|
|
|
70
76
|
/**
|
|
@@ -88,6 +94,13 @@ const TIER_DEFAULTS = {
|
|
|
88
94
|
skills: ['morph:checklist'],
|
|
89
95
|
memory: 'local',
|
|
90
96
|
},
|
|
97
|
+
4: {
|
|
98
|
+
model: 'haiku',
|
|
99
|
+
tools: 'Read, Glob, Grep',
|
|
100
|
+
maxTurns: 10,
|
|
101
|
+
skills: [],
|
|
102
|
+
memory: 'project',
|
|
103
|
+
},
|
|
91
104
|
};
|
|
92
105
|
|
|
93
106
|
/**
|
|
@@ -101,19 +114,27 @@ const TIER_DEFAULTS = {
|
|
|
101
114
|
function buildFrontmatter(agent, description) {
|
|
102
115
|
const defaults = TIER_DEFAULTS[agent.tier] ?? TIER_DEFAULTS[2];
|
|
103
116
|
const name = agent.title ?? agent.name;
|
|
104
|
-
const skillsList = defaults.skills.map(s => ` - ${s}`).join('\n');
|
|
105
117
|
|
|
106
|
-
|
|
118
|
+
// Allow per-agent tool overrides (e.g., morph-spec-validator needs Bash for npm test)
|
|
119
|
+
const tools = agent.teammate?.tools ?? defaults.tools;
|
|
120
|
+
// Allow per-agent maxTurns override
|
|
121
|
+
const maxTurns = agent.teammate?.maxTurns ?? defaults.maxTurns;
|
|
122
|
+
|
|
123
|
+
const lines = [
|
|
107
124
|
`name: ${name}`,
|
|
108
125
|
`description: ${description}`,
|
|
109
126
|
`model: ${defaults.model}`,
|
|
110
|
-
`tools: ${
|
|
111
|
-
`maxTurns: ${
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
''
|
|
116
|
-
|
|
127
|
+
`tools: ${tools}`,
|
|
128
|
+
`maxTurns: ${maxTurns}`,
|
|
129
|
+
];
|
|
130
|
+
|
|
131
|
+
if (defaults.skills.length > 0) {
|
|
132
|
+
const skillsList = defaults.skills.map(s => ` - ${s}`).join('\n');
|
|
133
|
+
lines.push(`skills:`, skillsList);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
lines.push(`memory: ${defaults.memory}`, '');
|
|
137
|
+
return lines.join('\n');
|
|
117
138
|
}
|
|
118
139
|
|
|
119
140
|
function buildDescription(agent) {
|
|
@@ -166,7 +187,8 @@ export function parseSkillFile(rawContent) {
|
|
|
166
187
|
if (inlineMatch) description = inlineMatch[1].trim();
|
|
167
188
|
}
|
|
168
189
|
|
|
169
|
-
|
|
190
|
+
const stacks = parseStacks(rawContent);
|
|
191
|
+
return { name, description, allowedTools, stacks, body };
|
|
170
192
|
}
|
|
171
193
|
|
|
172
194
|
/**
|
|
@@ -195,7 +217,7 @@ export function collectSkillFiles(dir) {
|
|
|
195
217
|
* @param {string} projectDir - Target project directory
|
|
196
218
|
* @param {string} frameworkDir - Path to morph-spec framework/ directory
|
|
197
219
|
*/
|
|
198
|
-
export async function installDomainAgents(projectDir, frameworkDir = 'framework') {
|
|
220
|
+
export async function installDomainAgents(projectDir, frameworkDir = 'framework', { projectTags = [] } = {}) {
|
|
199
221
|
const domainsDir = join(frameworkDir, 'agents');
|
|
200
222
|
if (!existsSync(domainsDir)) return { specialists: 0 };
|
|
201
223
|
|
|
@@ -203,10 +225,12 @@ export async function installDomainAgents(projectDir, frameworkDir = 'framework'
|
|
|
203
225
|
mkdirSync(targetDir, { recursive: true });
|
|
204
226
|
|
|
205
227
|
let specialists = 0;
|
|
228
|
+
const installed = new Set();
|
|
206
229
|
for (const filePath of collectSkillFiles(domainsDir)) {
|
|
207
230
|
const raw = readFileSync(filePath, 'utf-8');
|
|
208
|
-
const { name, description, allowedTools, body } = parseSkillFile(raw);
|
|
231
|
+
const { name, description, allowedTools, stacks, body } = parseSkillFile(raw);
|
|
209
232
|
if (!name) continue;
|
|
233
|
+
if (!shouldInstall(stacks, projectTags)) continue;
|
|
210
234
|
|
|
211
235
|
const desc = description ?? `MORPH-SPEC domain agent: ${name}`;
|
|
212
236
|
const frontmatter = [
|
|
@@ -219,9 +243,19 @@ export async function installDomainAgents(projectDir, frameworkDir = 'framework'
|
|
|
219
243
|
'',
|
|
220
244
|
].join('\n');
|
|
221
245
|
|
|
222
|
-
const
|
|
246
|
+
const filename = `morph-domain-${name}.md`;
|
|
247
|
+
const targetPath = join(targetDir, filename);
|
|
223
248
|
writeFileSync(targetPath, `---\n${frontmatter}---\n\n${body.trimStart()}`, 'utf-8');
|
|
249
|
+
installed.add(filename);
|
|
224
250
|
specialists++;
|
|
225
251
|
}
|
|
252
|
+
|
|
253
|
+
// Cleanup orphan morph-domain-* files
|
|
254
|
+
for (const file of readdirSync(targetDir)) {
|
|
255
|
+
if (file.startsWith('morph-domain-') && !installed.has(file)) {
|
|
256
|
+
unlinkSync(join(targetDir, file));
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
226
260
|
return { specialists };
|
|
227
261
|
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* claude-md-injector.js
|
|
3
|
+
*
|
|
4
|
+
* Manages the root CLAUDE.md file in client projects.
|
|
5
|
+
* Instead of overwriting the user's CLAUDE.md, injects an @import line
|
|
6
|
+
* that references the morph-spec managed .claude/CLAUDE.md.
|
|
7
|
+
*
|
|
8
|
+
* The root CLAUDE.md belongs to the USER — morph-spec only adds its import.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { pathExists, readFile, writeFile } from './file-copier.js';
|
|
12
|
+
|
|
13
|
+
const MORPH_IMPORT = '@.claude/CLAUDE.md';
|
|
14
|
+
const MORPH_MARKER = 'MORPH-SPEC';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Injects the morph-spec @import into the root CLAUDE.md.
|
|
18
|
+
*
|
|
19
|
+
* Three scenarios:
|
|
20
|
+
* 1. No CLAUDE.md exists → create minimal file with the import
|
|
21
|
+
* 2. Existing morph-only CLAUDE.md (old overwrite) → replace with minimal + import
|
|
22
|
+
* 3. Existing user CLAUDE.md → append import section at the end
|
|
23
|
+
*
|
|
24
|
+
* Idempotent — does nothing if the import already exists.
|
|
25
|
+
*
|
|
26
|
+
* @param {string} claudeMdPath - Absolute path to root CLAUDE.md
|
|
27
|
+
* @returns {Promise<'created'|'migrated'|'injected'|'already-present'>}
|
|
28
|
+
*/
|
|
29
|
+
export async function injectMorphImport(claudeMdPath) {
|
|
30
|
+
if (!await pathExists(claudeMdPath)) {
|
|
31
|
+
await writeFile(claudeMdPath, `# Project\n\n${MORPH_IMPORT}\n`);
|
|
32
|
+
return 'created';
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const content = await readFile(claudeMdPath);
|
|
36
|
+
|
|
37
|
+
if (content.includes(MORPH_IMPORT)) {
|
|
38
|
+
return 'already-present';
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (isMorphOnly(content)) {
|
|
42
|
+
await writeFile(claudeMdPath, `# Project\n\n${MORPH_IMPORT}\n`);
|
|
43
|
+
return 'migrated';
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// User has their own content — append the import section
|
|
47
|
+
const separator = content.endsWith('\n') ? '\n' : '\n\n';
|
|
48
|
+
await writeFile(claudeMdPath, `${content}${separator}## MORPH-SPEC\n\n${MORPH_IMPORT}\n`);
|
|
49
|
+
return 'injected';
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Checks if CLAUDE.md contains ONLY morph-spec content (old overwrite pattern).
|
|
54
|
+
* Heuristic: contains MORPH-SPEC marker AND no substantial user content.
|
|
55
|
+
*/
|
|
56
|
+
function isMorphOnly(content) {
|
|
57
|
+
if (!content.includes(MORPH_MARKER)) return false;
|
|
58
|
+
|
|
59
|
+
// Strip morph-spec boilerplate markers and check if anything meaningful remains
|
|
60
|
+
const stripped = content
|
|
61
|
+
.replace(/# MORPH-SPEC Runtime Instructions/g, '')
|
|
62
|
+
.replace(/by Polymorphism Tech[^\n]*/g, '')
|
|
63
|
+
.replace(/\*MORPH-SPEC by Polymorphism Tech\*/g, '')
|
|
64
|
+
.replace(/@\.morph\/context\/README\.md/g, '')
|
|
65
|
+
.replace(/@\.claude\/CLAUDE\.md/g, '')
|
|
66
|
+
.replace(/## Critical Rules[\s\S]*?(?=##|$)/g, '')
|
|
67
|
+
.replace(/## Quick Reference[\s\S]*?(?=##|$)/g, '')
|
|
68
|
+
.replace(/## State & Outputs[\s\S]*?(?=##|$)/g, '')
|
|
69
|
+
.replace(/## Phase Sequence[\s\S]*?(?=##|$)/g, '')
|
|
70
|
+
.replace(/## Agents[\s\S]*?(?=##|$)/g, '')
|
|
71
|
+
.replace(/## Context Window Tip[\s\S]*?(?=##|$)/g, '')
|
|
72
|
+
.replace(/## Project Context[\s\S]*?(?=##|$)/g, '')
|
|
73
|
+
.replace(/[#\-|>*`\s]/g, '');
|
|
74
|
+
|
|
75
|
+
return stripped.length < 50;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Checks if the root CLAUDE.md has the morph-spec import.
|
|
80
|
+
* Used by doctor.js for health checks.
|
|
81
|
+
*
|
|
82
|
+
* @param {string} claudeMdPath - Absolute path to root CLAUDE.md
|
|
83
|
+
* @returns {Promise<'ok'|'missing'|'missing-import'>}
|
|
84
|
+
*/
|
|
85
|
+
export async function checkClaudeMdImport(claudeMdPath) {
|
|
86
|
+
if (!await pathExists(claudeMdPath)) return 'missing';
|
|
87
|
+
const content = await readFile(claudeMdPath);
|
|
88
|
+
if (content.includes(MORPH_IMPORT)) return 'ok';
|
|
89
|
+
return 'missing-import';
|
|
90
|
+
}
|
package/src/utils/file-copier.js
CHANGED
|
@@ -15,7 +15,7 @@ import { homedir } from 'os';
|
|
|
15
15
|
import { execSync } from 'child_process';
|
|
16
16
|
|
|
17
17
|
/** Current hooks schema version — bump when hook definitions change */
|
|
18
|
-
const HOOKS_VERSION = '2.
|
|
18
|
+
const HOOKS_VERSION = '2.12.0';
|
|
19
19
|
|
|
20
20
|
/** Marker for old dispatch.js (v1) */
|
|
21
21
|
const OLD_DISPATCH_COMMAND = 'node framework/hooks/agent-teams/dispatch.js';
|
|
@@ -31,10 +31,7 @@ const MORPH_PERMISSIONS = [
|
|
|
31
31
|
'Edit(.morph/state.json)',
|
|
32
32
|
'Write(.morph/framework/**)',
|
|
33
33
|
'Edit(.morph/framework/**)',
|
|
34
|
-
//
|
|
35
|
-
'Write(CLAUDE.md)',
|
|
36
|
-
'Edit(CLAUDE.md)',
|
|
37
|
-
// .claude/CLAUDE.md (managed copy — source is framework/CLAUDE_runtime.md)
|
|
34
|
+
// .claude/CLAUDE.md (managed copy — source is framework/CLAUDE.md)
|
|
38
35
|
'Write(.claude/CLAUDE.md)',
|
|
39
36
|
'Edit(.claude/CLAUDE.md)',
|
|
40
37
|
// .claude/rules/ (copied from framework/rules/)
|
|
@@ -63,6 +60,16 @@ const MORPH_HOOKS = [
|
|
|
63
60
|
}]
|
|
64
61
|
},
|
|
65
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
|
+
|
|
66
73
|
// === UserPromptSubmit ===
|
|
67
74
|
{
|
|
68
75
|
event: 'UserPromptSubmit',
|
|
@@ -93,6 +100,10 @@ const MORPH_HOOKS = [
|
|
|
93
100
|
{
|
|
94
101
|
type: 'command',
|
|
95
102
|
command: 'node framework/hooks/claude-code/pre-tool-use/enforce-phase-writes.js'
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
type: 'command',
|
|
106
|
+
command: 'node framework/hooks/claude-code/pre-tool-use/task-tracking-guard.js'
|
|
96
107
|
}
|
|
97
108
|
]
|
|
98
109
|
},
|
|
@@ -16,9 +16,10 @@
|
|
|
16
16
|
* assets/) so Claude Code can execute scripts and load references on demand.
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
|
-
import { mkdirSync, copyFileSync, existsSync, readdirSync, statSync } from 'fs';
|
|
19
|
+
import { mkdirSync, copyFileSync, existsSync, readdirSync, statSync, readFileSync, rmSync } from 'fs';
|
|
20
20
|
import { join } from 'path';
|
|
21
21
|
import { fileURLToPath } from 'url';
|
|
22
|
+
import { parseStacks, shouldInstall } from '../lib/stack-filter.js';
|
|
22
23
|
|
|
23
24
|
const __dirname = fileURLToPath(new URL('.', import.meta.url));
|
|
24
25
|
const FRAMEWORK_SKILLS_DIR = join(__dirname, '..', '..', 'framework', 'skills');
|
|
@@ -32,6 +33,16 @@ const FRAMEWORK_SKILLS_DIR = join(__dirname, '..', '..', 'framework', 'skills');
|
|
|
32
33
|
*/
|
|
33
34
|
const SKILL_LEVELS_TO_INSTALL = ['level-0-meta', 'level-1-workflows'];
|
|
34
35
|
|
|
36
|
+
/**
|
|
37
|
+
* Check if a skill should be installed based on its stacks: frontmatter.
|
|
38
|
+
* @param {string} skillContent - Raw SKILL.md content
|
|
39
|
+
* @param {string[]} projectTags - Stack tags from config
|
|
40
|
+
* @returns {boolean}
|
|
41
|
+
*/
|
|
42
|
+
export function shouldInstallSkill(skillContent, projectTags) {
|
|
43
|
+
return shouldInstall(parseStacks(skillContent), projectTags);
|
|
44
|
+
}
|
|
45
|
+
|
|
35
46
|
/**
|
|
36
47
|
* Recursively copy a directory tree from src to dest.
|
|
37
48
|
* Creates dest if it doesn't exist. Skips README.md files.
|
|
@@ -71,7 +82,7 @@ function copyDirectory(src, dest) {
|
|
|
71
82
|
* @param {string} srcDir - Source directory to walk
|
|
72
83
|
* @param {string} destDir - Destination base directory (.claude/skills/)
|
|
73
84
|
*/
|
|
74
|
-
function installSkillsFromDir(srcDir, destDir) {
|
|
85
|
+
function installSkillsFromDir(srcDir, destDir, projectTags) {
|
|
75
86
|
const entries = readdirSync(srcDir);
|
|
76
87
|
for (const entry of entries) {
|
|
77
88
|
const srcPath = join(srcDir, entry);
|
|
@@ -80,15 +91,19 @@ function installSkillsFromDir(srcDir, destDir) {
|
|
|
80
91
|
if (stat.isDirectory()) {
|
|
81
92
|
const skillMdPath = join(srcPath, 'SKILL.md');
|
|
82
93
|
if (existsSync(skillMdPath)) {
|
|
83
|
-
// Directory-based skill —
|
|
94
|
+
// Directory-based skill — check stack filter before copying
|
|
95
|
+
const content = readFileSync(skillMdPath, 'utf-8');
|
|
96
|
+
if (!shouldInstallSkill(content, projectTags)) continue;
|
|
84
97
|
const skillDestDir = join(destDir, entry);
|
|
85
98
|
copyDirectory(srcPath, skillDestDir);
|
|
86
99
|
} else {
|
|
87
100
|
// Level/category directory — recurse
|
|
88
|
-
installSkillsFromDir(srcPath, destDir);
|
|
101
|
+
installSkillsFromDir(srcPath, destDir, projectTags);
|
|
89
102
|
}
|
|
90
103
|
} else if (entry.endsWith('.md') && entry !== 'README.md') {
|
|
91
|
-
// Legacy flat .md skill —
|
|
104
|
+
// Legacy flat .md skill — check stack filter before copying
|
|
105
|
+
const content = readFileSync(srcPath, 'utf-8');
|
|
106
|
+
if (!shouldInstallSkill(content, projectTags)) continue;
|
|
92
107
|
const skillName = entry.slice(0, -3);
|
|
93
108
|
const skillDir = join(destDir, skillName);
|
|
94
109
|
mkdirSync(skillDir, { recursive: true });
|
|
@@ -97,6 +112,28 @@ function installSkillsFromDir(srcDir, destDir) {
|
|
|
97
112
|
}
|
|
98
113
|
}
|
|
99
114
|
|
|
115
|
+
function collectSourceSkillNames() {
|
|
116
|
+
const names = new Set();
|
|
117
|
+
for (const level of SKILL_LEVELS_TO_INSTALL) {
|
|
118
|
+
const levelDir = join(FRAMEWORK_SKILLS_DIR, level);
|
|
119
|
+
if (!existsSync(levelDir)) continue;
|
|
120
|
+
collectNamesFromDir(levelDir, names);
|
|
121
|
+
}
|
|
122
|
+
return names;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function collectNamesFromDir(dir, names) {
|
|
126
|
+
for (const entry of readdirSync(dir)) {
|
|
127
|
+
const fullPath = join(dir, entry);
|
|
128
|
+
if (!statSync(fullPath).isDirectory()) continue;
|
|
129
|
+
if (existsSync(join(fullPath, 'SKILL.md'))) {
|
|
130
|
+
names.add(entry);
|
|
131
|
+
} else {
|
|
132
|
+
collectNamesFromDir(fullPath, names);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
100
137
|
/**
|
|
101
138
|
* Install morph framework skills to .claude/skills/ in the target project.
|
|
102
139
|
* Skills are copied into subdirectories as SKILL.md files so Claude Code can
|
|
@@ -104,15 +141,38 @@ function installSkillsFromDir(srcDir, destDir) {
|
|
|
104
141
|
* /blazor-builder). Directory-based skills also get their scripts/,
|
|
105
142
|
* references/, and assets/ subdirectories copied for on-demand loading.
|
|
106
143
|
*
|
|
144
|
+
* Stack filtering: if projectTags is provided, only skills whose `stacks:`
|
|
145
|
+
* frontmatter matches the project tags are installed. Skills without a
|
|
146
|
+
* `stacks:` field are always installed (universal).
|
|
147
|
+
*
|
|
107
148
|
* @param {string} projectDir - Target project root directory
|
|
149
|
+
* @param {Object} [options]
|
|
150
|
+
* @param {string[]} [options.projectTags=[]] - Stack tags from config
|
|
108
151
|
*/
|
|
109
|
-
export async function installSkills(projectDir) {
|
|
152
|
+
export async function installSkills(projectDir, { projectTags = [] } = {}) {
|
|
110
153
|
const claudeSkillsDir = join(projectDir, '.claude', 'skills');
|
|
111
154
|
mkdirSync(claudeSkillsDir, { recursive: true });
|
|
112
155
|
|
|
113
156
|
for (const level of SKILL_LEVELS_TO_INSTALL) {
|
|
114
157
|
const levelDir = join(FRAMEWORK_SKILLS_DIR, level);
|
|
115
158
|
if (!existsSync(levelDir)) continue;
|
|
116
|
-
installSkillsFromDir(levelDir, claudeSkillsDir);
|
|
159
|
+
installSkillsFromDir(levelDir, claudeSkillsDir, projectTags);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Collect installed morph-* skill names
|
|
163
|
+
const installed = new Set();
|
|
164
|
+
for (const entry of readdirSync(claudeSkillsDir)) {
|
|
165
|
+
if (existsSync(join(claudeSkillsDir, entry, 'SKILL.md'))) {
|
|
166
|
+
installed.add(entry);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Cleanup orphan morph-* skill dirs that exist in source but were filtered out
|
|
171
|
+
const allSourceSkills = collectSourceSkillNames();
|
|
172
|
+
for (const entry of readdirSync(claudeSkillsDir)) {
|
|
173
|
+
if (!entry.startsWith('morph-')) continue;
|
|
174
|
+
if (!installed.has(entry) && allSourceSkills.has(entry)) {
|
|
175
|
+
rmSync(join(claudeSkillsDir, entry), { recursive: true, force: true });
|
|
176
|
+
}
|
|
117
177
|
}
|
|
118
178
|
}
|
package/CLAUDE.md
DELETED
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
# MORPH-SPEC Runtime Instructions
|
|
2
|
-
|
|
3
|
-
> by Polymorphism Tech — Spec-driven development for .NET/Blazor/Next.js/Azure
|
|
4
|
-
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
## Project Context
|
|
8
|
-
|
|
9
|
-
@.morph/context/README.md
|
|
10
|
-
|
|
11
|
-
---
|
|
12
|
-
|
|
13
|
-
## Critical Rules
|
|
14
|
-
|
|
15
|
-
**NEVER:**
|
|
16
|
-
- Skip to code without a specification
|
|
17
|
-
- Implement without design approval
|
|
18
|
-
- Ignore standards in `.morph/framework/standards/`
|
|
19
|
-
- Create infrastructure manually
|
|
20
|
-
- Generate code without defined contracts
|
|
21
|
-
- List questions as plain text — always use the `AskUserQuestion` tool
|
|
22
|
-
|
|
23
|
-
**ALWAYS:**
|
|
24
|
-
- Follow the mandatory phases
|
|
25
|
-
- Generate outputs in `.morph/features/{feature}/`
|
|
26
|
-
- Document decisions in `decisions.md`
|
|
27
|
-
- Checkpoint every 3 implemented tasks
|
|
28
|
-
- Use Infrastructure as Code
|
|
29
|
-
- Use `AskUserQuestion` tool whenever asking the user anything (1–4 questions per call; split into sequential calls if more)
|
|
30
|
-
|
|
31
|
-
---
|
|
32
|
-
|
|
33
|
-
## Quick Reference
|
|
34
|
-
|
|
35
|
-
| Command | Purpose |
|
|
36
|
-
|---------|---------|
|
|
37
|
-
| `/morph-proposal {feature}` | Full spec pipeline (phases 1–4, pauses for approval) |
|
|
38
|
-
| `/morph-apply {feature}` | Implement feature (phase 5) |
|
|
39
|
-
| `/morph-status` | Feature status dashboard |
|
|
40
|
-
| `/morph-preflight` | Pre-implementation validation |
|
|
41
|
-
|
|
42
|
-
---
|
|
43
|
-
|
|
44
|
-
## State & Outputs
|
|
45
|
-
|
|
46
|
-
| Path | Notes |
|
|
47
|
-
|------|-------|
|
|
48
|
-
| `.morph/state.json` | **READ-ONLY** — use `morph-spec` CLI to update |
|
|
49
|
-
| `.morph/features/{feature}/{phase}/` | Feature outputs organized by phase |
|
|
50
|
-
| `.morph/framework/` | **READ-ONLY** — framework files managed by morph-spec |
|
|
51
|
-
| `.morph/config/config.json` | Project configuration (editable) |
|
|
52
|
-
|
|
53
|
-
### mark-output types
|
|
54
|
-
|
|
55
|
-
Use `morph-spec state mark-output <feature> <type>` with one of these exact type names:
|
|
56
|
-
|
|
57
|
-
| Type | Phase | kebab alias |
|
|
58
|
-
|------|-------|-------------|
|
|
59
|
-
| `proposal` | proposal | — |
|
|
60
|
-
| `schemaAnalysis` | design | `schema-analysis` |
|
|
61
|
-
| `spec` | design | — |
|
|
62
|
-
| `contracts` | design | — |
|
|
63
|
-
| `contractsVsa` | design | `contracts-vsa` |
|
|
64
|
-
| `decisions` | design | — |
|
|
65
|
-
| `clarifications` | clarify | — |
|
|
66
|
-
| `tasks` | tasks | — |
|
|
67
|
-
| `uiDesignSystem` | uiux | `ui-design-system` |
|
|
68
|
-
| `uiMockups` | uiux | `ui-mockups` |
|
|
69
|
-
| `uiComponents` | uiux | `ui-components` |
|
|
70
|
-
| `uiFlows` | uiux | `ui-flows` |
|
|
71
|
-
| `recap` | implement | — |
|
|
72
|
-
---
|
|
73
|
-
|
|
74
|
-
## Phase Sequence
|
|
75
|
-
|
|
76
|
-
```
|
|
77
|
-
proposal → setup → [uiux] → design → clarify → tasks → implement → [sync]
|
|
78
|
-
```
|
|
79
|
-
|
|
80
|
-
Use `morph-spec status {feature}` to see current phase and pending approval gates.
|
|
81
|
-
|
|
82
|
-
---
|
|
83
|
-
|
|
84
|
-
## Agents
|
|
85
|
-
|
|
86
|
-
Tier-1 and tier-2 MORPH agents are available as native subagents in `.claude/agents/`.
|
|
87
|
-
They can be invoked directly by Claude Code during multi-agent workflows.
|
|
88
|
-
|
|
89
|
-
---
|
|
90
|
-
|
|
91
|
-
## Context Window Tip
|
|
92
|
-
|
|
93
|
-
When using 3+ MCPs, add `"experimental": { "mcpCliMode": true }` to `.claude/settings.json`.
|
|
94
|
-
MCP tools load on-demand instead of all at startup — keeps context clean for actual work.
|
|
95
|
-
|
|
96
|
-
---
|
|
97
|
-
|
|
98
|
-
*MORPH-SPEC by Polymorphism Tech*
|