@polymorphism-tech/morph-spec 4.10.9 → 4.11.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.
Files changed (145) hide show
  1. package/CLAUDE.md +3 -3
  2. package/LICENSE +71 -71
  3. package/README.md +5 -11
  4. package/bin/morph-spec.js +23 -1
  5. package/bin/task-manager.js +5 -5
  6. package/claude-plugin.json +1 -1
  7. package/docs/CHEATSHEET.md +1 -1
  8. package/docs/QUICKSTART.md +2 -8
  9. package/framework/agents/README.md +14 -14
  10. package/framework/agents/architecture/standards-architect.md +159 -159
  11. package/framework/agents/frontend/css-specialist.md +1 -1
  12. package/framework/agents/infrastructure/azure-architect.md +149 -149
  13. package/framework/agents/infrastructure/azure-deploy-specialist.md +4 -4
  14. package/framework/agents/infrastructure/infra-architect.md +48 -48
  15. package/framework/commands/morph-apply.md +18 -18
  16. package/framework/commands/morph-preflight.md +3 -3
  17. package/framework/commands/morph-proposal.md +15 -15
  18. package/framework/commands/morph-status.md +3 -3
  19. package/framework/commands/morph-troubleshoot.md +1 -1
  20. package/framework/hooks/README.md +204 -204
  21. package/framework/hooks/claude-code/pre-tool-use/task-tracking-guard.js +2 -2
  22. package/framework/hooks/claude-code/session-start/inject-morph-context.js +1 -1
  23. package/framework/hooks/claude-code/teammate-idle/teammate-idle.js +3 -3
  24. package/framework/hooks/claude-code/user-prompt/enrich-prompt.js +2 -2
  25. package/framework/hooks/git/pre-commit/agents.sh +1 -1
  26. package/framework/hooks/shared/phase-utils.js +139 -139
  27. package/framework/rules/csharp-standards.md +13 -13
  28. package/framework/rules/frontend-standards.md +13 -13
  29. package/framework/rules/infrastructure-standards.md +16 -16
  30. package/framework/rules/testing-standards.md +14 -14
  31. package/framework/skills/level-0-meta/morph-brainstorming/SKILL.md +8 -8
  32. package/framework/skills/level-0-meta/morph-checklist/SKILL.md +3 -3
  33. package/framework/skills/level-0-meta/morph-code-review-nextjs/references/review-example-nextjs.md +254 -254
  34. package/framework/skills/level-0-meta/morph-frontend-review/SKILL.md +1 -1
  35. package/framework/skills/level-0-meta/morph-init/SKILL.md +11 -11
  36. package/framework/skills/level-0-meta/morph-post-implementation/SKILL.md +3 -3
  37. package/framework/skills/level-0-meta/morph-replicate/SKILL.md +1 -1
  38. package/framework/skills/level-0-meta/morph-simulation-checklist/SKILL.md +4 -4
  39. package/framework/skills/level-0-meta/morph-tool-usage-guide/references/tools-per-phase.md +25 -25
  40. package/framework/skills/level-0-meta/morph-verification-before-completion/SKILL.md +8 -8
  41. package/framework/skills/level-1-workflows/morph-phase-clarify/SKILL.md +7 -7
  42. package/framework/skills/level-1-workflows/morph-phase-codebase-analysis/SKILL.md +4 -4
  43. package/framework/skills/level-1-workflows/morph-phase-design/SKILL.md +23 -23
  44. package/framework/skills/level-1-workflows/morph-phase-design/references/architecture-analysis-guide.md +1 -1
  45. package/framework/skills/level-1-workflows/morph-phase-implement/.morph/logs/activity.json +38 -0
  46. package/framework/skills/level-1-workflows/morph-phase-implement/SKILL.md +29 -29
  47. package/framework/skills/level-1-workflows/morph-phase-implement/prompts/implementer-prompt.md +4 -4
  48. package/framework/skills/level-1-workflows/morph-phase-plan/SKILL.md +9 -9
  49. package/framework/skills/level-1-workflows/morph-phase-setup/SKILL.md +17 -17
  50. package/framework/skills/level-1-workflows/morph-phase-tasks/.morph/logs/activity.json +14 -0
  51. package/framework/skills/level-1-workflows/morph-phase-tasks/SKILL.md +12 -12
  52. package/framework/skills/level-1-workflows/morph-phase-uiux/SKILL.md +16 -16
  53. package/framework/skills/level-1-workflows/morph-scope-escalation/SKILL.md +5 -5
  54. package/framework/standards/STANDARDS.json +2338 -2041
  55. package/framework/standards/frontend/nextjs/nextjs-patterns.md +17 -17
  56. package/framework/standards/infrastructure/supabase/supabase-custom-jwt.md +206 -0
  57. package/framework/templates/.idea/morph-templates.xml +92 -0
  58. package/framework/templates/.vscode/morph-templates.code-snippets +186 -0
  59. package/framework/templates/README.md +814 -814
  60. package/framework/templates/code/dotnet/contracts/Commands.cs +74 -74
  61. package/framework/templates/code/dotnet/contracts/Entities.cs +25 -25
  62. package/framework/templates/code/dotnet/contracts/Queries.cs +74 -74
  63. package/framework/templates/code/dotnet/contracts/README.md +74 -74
  64. package/framework/templates/code/dotnet/contracts/api-contracts.cs +173 -173
  65. package/framework/templates/code/dotnet/frontend/component.razor +239 -239
  66. package/framework/templates/code/dotnet/jobs/agent.cs +163 -163
  67. package/framework/templates/code/dotnet/jobs/job.cs +171 -171
  68. package/framework/templates/code/dotnet/test.cs +239 -239
  69. package/framework/templates/code/sql/rls-policy.sql +57 -57
  70. package/framework/templates/code/sql/supabase-migration.sql +100 -100
  71. package/framework/templates/code/sql/supabase-migration.template.sql +113 -113
  72. package/framework/templates/code/typescript/contracts.ts +168 -168
  73. package/framework/templates/context/CONTEXT.md +181 -181
  74. package/framework/templates/docs/proposal.md +182 -182
  75. package/framework/templates/docs/user-stories.md +34 -34
  76. package/framework/templates/examples/design-system-examples.md +357 -357
  77. package/framework/templates/examples/spec-examples.md +90 -90
  78. package/framework/templates/feature/decisions.md +187 -187
  79. package/framework/templates/feature/recap.md +146 -146
  80. package/framework/templates/feature/tasks.md +199 -199
  81. package/framework/templates/infrastructure/azure/README.md +286 -286
  82. package/framework/templates/infrastructure/azure/deploy-checklist.md +426 -426
  83. package/framework/templates/infrastructure/azure/parameters.dev.json +29 -29
  84. package/framework/templates/infrastructure/azure/parameters.prod.json +29 -29
  85. package/framework/templates/infrastructure/azure/parameters.staging.json +29 -29
  86. package/framework/templates/infrastructure/docker/docker-compose.template.yml +67 -67
  87. package/framework/templates/infrastructure/docker/dockerfile-api.dockerfile +38 -38
  88. package/framework/templates/infrastructure/docker/dockerfile-web.dockerfile +48 -48
  89. package/framework/templates/infrastructure/docker/easypanel.template.json +54 -54
  90. package/framework/workflows/README.md +18 -18
  91. package/framework/workflows/docs/enforcement-pipeline.md +1 -1
  92. package/framework/workflows/docs/full-morph.md +2 -2
  93. package/package.json +1 -1
  94. package/src/commands/agents/dispatch-agents.js +17 -0
  95. package/src/commands/agents/index.js +13 -13
  96. package/src/commands/agents/project-metrics.js +74 -0
  97. package/src/commands/project/doctor.js +3 -4
  98. package/src/commands/project/install-plugin-cmd.js +28 -28
  99. package/src/commands/project/setup-infra-cmd.js +12 -12
  100. package/src/commands/project/status.js +8 -1
  101. package/src/commands/scope/escalate.js +5 -3
  102. package/src/commands/standards/context-budget.js +89 -0
  103. package/src/commands/tasks/index.js +4 -4
  104. package/src/commands/templates/index.js +5 -5
  105. package/src/commands/utils/index.js +3 -3
  106. package/src/commands/validation/index.js +4 -4
  107. package/src/commands/validation/validate-feature.js +32 -3
  108. package/src/core/state/phase-state-machine.js +217 -217
  109. package/src/lib/agents/metrics-aggregator.js +56 -0
  110. package/src/lib/generators/design-system-generator.js +298 -298
  111. package/src/lib/generators/index.js +10 -10
  112. package/src/lib/generators/metadata-extractor.js +387 -387
  113. package/src/lib/stack-filter.js +65 -65
  114. package/src/lib/standards/digest-builder.js +57 -3
  115. package/src/lib/standards/index.js +7 -7
  116. package/src/lib/standards/standards-context-injector.js +302 -302
  117. package/src/lib/validators/architecture/architecture-validator.js +387 -387
  118. package/src/lib/validators/architecture/index.js +7 -7
  119. package/src/lib/validators/blazor/blazor-concurrency-analyzer.js +316 -316
  120. package/src/lib/validators/blazor/blazor-state-validator.js +279 -279
  121. package/src/lib/validators/blazor/blazor-validator.js +369 -369
  122. package/src/lib/validators/blazor/index.js +9 -9
  123. package/src/lib/validators/content/content-validator.js +287 -287
  124. package/src/lib/validators/content/index.js +7 -7
  125. package/src/lib/validators/contracts/index.js +7 -7
  126. package/src/lib/validators/css/css-validator.js +352 -352
  127. package/src/lib/validators/css/index.js +7 -7
  128. package/src/lib/validators/design-system/design-system-validator.js +231 -231
  129. package/src/lib/validators/design-system/index.js +7 -7
  130. package/src/lib/validators/nextjs/index.js +6 -6
  131. package/src/lib/validators/nextjs/next-component-validator.js +183 -183
  132. package/src/lib/validators/packages/index.js +7 -7
  133. package/src/lib/validators/packages/package-validator.js +360 -360
  134. package/src/lib/validators/shared/index.js +12 -12
  135. package/src/lib/validators/shared/issue-counter.js +18 -18
  136. package/src/lib/validators/shared/result-formatter.js +124 -124
  137. package/src/lib/validators/ui/index.js +7 -7
  138. package/src/lib/validators/ui/ui-contrast-validator.js +422 -422
  139. package/src/scripts/global-install.js +112 -58
  140. package/src/scripts/install-plugin.js +126 -126
  141. package/src/utils/claude-md-injector.js +90 -90
  142. package/src/utils/file-copier.js +200 -200
  143. package/src/utils/logger.js +32 -32
  144. package/src/utils/skills-installer.js +178 -178
  145. package/src/utils/version-checker.js +169 -175
@@ -1,178 +1,178 @@
1
- /**
2
- * Skills Installer
3
- *
4
- * Copies framework skills to .claude/skills/ during morph-spec init.
5
- * Installs level-0-meta and level-1-workflows skills using the native Claude Code
6
- * subdirectory format: .claude/skills/{skill-name}/SKILL.md (plus optional
7
- * scripts/, references/, and assets/ subdirectories).
8
- * Level-2 domain specialists are installed as native subagents via installDomainAgents().
9
- * Level-3 and level-4 only contain README.md placeholder files and are skipped.
10
- *
11
- * Source skills can be either:
12
- * - Directory-based: framework/skills/{level}/{skill-name}/SKILL.md (preferred)
13
- * - Flat file: framework/skills/{level}/{skill-name}.md (legacy, still supported)
14
- *
15
- * Directory-based skills have their entire directory copied (scripts/, references/,
16
- * assets/) so Claude Code can execute scripts and load references on demand.
17
- */
18
-
19
- import { mkdirSync, copyFileSync, existsSync, readdirSync, statSync, readFileSync, rmSync } from 'fs';
20
- import { join } from 'path';
21
- import { fileURLToPath } from 'url';
22
- import { parseStacks, shouldInstall } from '../lib/stack-filter.js';
23
-
24
- const __dirname = fileURLToPath(new URL('.', import.meta.url));
25
- const FRAMEWORK_SKILLS_DIR = join(__dirname, '..', '..', 'framework', 'skills');
26
-
27
- /**
28
- * Skill levels to install in .claude/skills/.
29
- * Level-0 meta skills (planning, reviewing) and level-1 workflow skills
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).
33
- */
34
- const SKILL_LEVELS_TO_INSTALL = ['level-0-meta', 'level-1-workflows'];
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
-
46
- /**
47
- * Recursively copy a directory tree from src to dest.
48
- * Creates dest if it doesn't exist. Skips README.md files.
49
- *
50
- * @param {string} src - Source directory path
51
- * @param {string} dest - Destination directory path
52
- */
53
- function copyDirectory(src, dest) {
54
- mkdirSync(dest, { recursive: true });
55
- for (const entry of readdirSync(src)) {
56
- if (entry === 'README.md') continue;
57
- const srcPath = join(src, entry);
58
- const destPath = join(dest, entry);
59
- if (statSync(srcPath).isDirectory()) {
60
- copyDirectory(srcPath, destPath);
61
- } else {
62
- copyFileSync(srcPath, destPath);
63
- }
64
- }
65
- }
66
-
67
- /**
68
- * Walk srcDir and install skills into destDir.
69
- *
70
- * Handles two source formats:
71
- * 1. Directory-based skill: srcDir/{skill-name}/SKILL.md
72
- * → copies entire directory (SKILL.md + scripts/ + references/ + assets/)
73
- * → destDir/{skill-name}/
74
- *
75
- * 2. Flat .md skill (legacy): srcDir/{skill-name}.md
76
- * → destDir/{skill-name}/SKILL.md
77
- *
78
- * Level directories (level-0-meta, level-1-workflows, sub-categories) are
79
- * traversed recursively. A directory is recognized as a skill directory when
80
- * it contains a SKILL.md file.
81
- *
82
- * @param {string} srcDir - Source directory to walk
83
- * @param {string} destDir - Destination base directory (.claude/skills/)
84
- */
85
- function installSkillsFromDir(srcDir, destDir, projectTags) {
86
- const entries = readdirSync(srcDir);
87
- for (const entry of entries) {
88
- const srcPath = join(srcDir, entry);
89
- const stat = statSync(srcPath);
90
-
91
- if (stat.isDirectory()) {
92
- const skillMdPath = join(srcPath, 'SKILL.md');
93
- if (existsSync(skillMdPath)) {
94
- // Directory-based skill — check stack filter before copying
95
- const content = readFileSync(skillMdPath, 'utf-8');
96
- if (!shouldInstallSkill(content, projectTags)) continue;
97
- const skillDestDir = join(destDir, entry);
98
- copyDirectory(srcPath, skillDestDir);
99
- } else {
100
- // Level/category directory — recurse
101
- installSkillsFromDir(srcPath, destDir, projectTags);
102
- }
103
- } else if (entry.endsWith('.md') && entry !== 'README.md') {
104
- // Legacy flat .md skill — check stack filter before copying
105
- const content = readFileSync(srcPath, 'utf-8');
106
- if (!shouldInstallSkill(content, projectTags)) continue;
107
- const skillName = entry.slice(0, -3);
108
- const skillDir = join(destDir, skillName);
109
- mkdirSync(skillDir, { recursive: true });
110
- copyFileSync(srcPath, join(skillDir, 'SKILL.md'));
111
- }
112
- }
113
- }
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
-
137
- /**
138
- * Install morph framework skills to .claude/skills/ in the target project.
139
- * Skills are copied into subdirectories as SKILL.md files so Claude Code can
140
- * discover them natively via /<skill-name> syntax (e.g., /phase-design,
141
- * /blazor-builder). Directory-based skills also get their scripts/,
142
- * references/, and assets/ subdirectories copied for on-demand loading.
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
- *
148
- * @param {string} projectDir - Target project root directory
149
- * @param {Object} [options]
150
- * @param {string[]} [options.projectTags=[]] - Stack tags from config
151
- */
152
- export async function installSkills(projectDir, { projectTags = [] } = {}) {
153
- const claudeSkillsDir = join(projectDir, '.claude', 'skills');
154
- mkdirSync(claudeSkillsDir, { recursive: true });
155
-
156
- for (const level of SKILL_LEVELS_TO_INSTALL) {
157
- const levelDir = join(FRAMEWORK_SKILLS_DIR, level);
158
- if (!existsSync(levelDir)) continue;
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
- }
177
- }
178
- }
1
+ /**
2
+ * Skills Installer
3
+ *
4
+ * Copies framework skills to .claude/skills/ during morph-spec init.
5
+ * Installs level-0-meta and level-1-workflows skills using the native Claude Code
6
+ * subdirectory format: .claude/skills/{skill-name}/SKILL.md (plus optional
7
+ * scripts/, references/, and assets/ subdirectories).
8
+ * Level-2 domain specialists are installed as native subagents via installDomainAgents().
9
+ * Level-3 and level-4 only contain README.md placeholder files and are skipped.
10
+ *
11
+ * Source skills can be either:
12
+ * - Directory-based: framework/skills/{level}/{skill-name}/SKILL.md (preferred)
13
+ * - Flat file: framework/skills/{level}/{skill-name}.md (legacy, still supported)
14
+ *
15
+ * Directory-based skills have their entire directory copied (scripts/, references/,
16
+ * assets/) so Claude Code can execute scripts and load references on demand.
17
+ */
18
+
19
+ import { mkdirSync, copyFileSync, existsSync, readdirSync, statSync, readFileSync, rmSync } from 'fs';
20
+ import { join } from 'path';
21
+ import { fileURLToPath } from 'url';
22
+ import { parseStacks, shouldInstall } from '../lib/stack-filter.js';
23
+
24
+ const __dirname = fileURLToPath(new URL('.', import.meta.url));
25
+ const FRAMEWORK_SKILLS_DIR = join(__dirname, '..', '..', 'framework', 'skills');
26
+
27
+ /**
28
+ * Skill levels to install in .claude/skills/.
29
+ * Level-0 meta skills (planning, reviewing) and level-1 workflow skills
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).
33
+ */
34
+ const SKILL_LEVELS_TO_INSTALL = ['level-0-meta', 'level-1-workflows'];
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
+
46
+ /**
47
+ * Recursively copy a directory tree from src to dest.
48
+ * Creates dest if it doesn't exist. Skips README.md files.
49
+ *
50
+ * @param {string} src - Source directory path
51
+ * @param {string} dest - Destination directory path
52
+ */
53
+ function copyDirectory(src, dest) {
54
+ mkdirSync(dest, { recursive: true });
55
+ for (const entry of readdirSync(src)) {
56
+ if (entry === 'README.md') continue;
57
+ const srcPath = join(src, entry);
58
+ const destPath = join(dest, entry);
59
+ if (statSync(srcPath).isDirectory()) {
60
+ copyDirectory(srcPath, destPath);
61
+ } else {
62
+ copyFileSync(srcPath, destPath);
63
+ }
64
+ }
65
+ }
66
+
67
+ /**
68
+ * Walk srcDir and install skills into destDir.
69
+ *
70
+ * Handles two source formats:
71
+ * 1. Directory-based skill: srcDir/{skill-name}/SKILL.md
72
+ * → copies entire directory (SKILL.md + scripts/ + references/ + assets/)
73
+ * → destDir/{skill-name}/
74
+ *
75
+ * 2. Flat .md skill (legacy): srcDir/{skill-name}.md
76
+ * → destDir/{skill-name}/SKILL.md
77
+ *
78
+ * Level directories (level-0-meta, level-1-workflows, sub-categories) are
79
+ * traversed recursively. A directory is recognized as a skill directory when
80
+ * it contains a SKILL.md file.
81
+ *
82
+ * @param {string} srcDir - Source directory to walk
83
+ * @param {string} destDir - Destination base directory (.claude/skills/)
84
+ */
85
+ function installSkillsFromDir(srcDir, destDir, projectTags) {
86
+ const entries = readdirSync(srcDir);
87
+ for (const entry of entries) {
88
+ const srcPath = join(srcDir, entry);
89
+ const stat = statSync(srcPath);
90
+
91
+ if (stat.isDirectory()) {
92
+ const skillMdPath = join(srcPath, 'SKILL.md');
93
+ if (existsSync(skillMdPath)) {
94
+ // Directory-based skill — check stack filter before copying
95
+ const content = readFileSync(skillMdPath, 'utf-8');
96
+ if (!shouldInstallSkill(content, projectTags)) continue;
97
+ const skillDestDir = join(destDir, entry);
98
+ copyDirectory(srcPath, skillDestDir);
99
+ } else {
100
+ // Level/category directory — recurse
101
+ installSkillsFromDir(srcPath, destDir, projectTags);
102
+ }
103
+ } else if (entry.endsWith('.md') && entry !== 'README.md') {
104
+ // Legacy flat .md skill — check stack filter before copying
105
+ const content = readFileSync(srcPath, 'utf-8');
106
+ if (!shouldInstallSkill(content, projectTags)) continue;
107
+ const skillName = entry.slice(0, -3);
108
+ const skillDir = join(destDir, skillName);
109
+ mkdirSync(skillDir, { recursive: true });
110
+ copyFileSync(srcPath, join(skillDir, 'SKILL.md'));
111
+ }
112
+ }
113
+ }
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
+
137
+ /**
138
+ * Install morph framework skills to .claude/skills/ in the target project.
139
+ * Skills are copied into subdirectories as SKILL.md files so Claude Code can
140
+ * discover them natively via /<skill-name> syntax (e.g., /phase-design,
141
+ * /blazor-builder). Directory-based skills also get their scripts/,
142
+ * references/, and assets/ subdirectories copied for on-demand loading.
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
+ *
148
+ * @param {string} projectDir - Target project root directory
149
+ * @param {Object} [options]
150
+ * @param {string[]} [options.projectTags=[]] - Stack tags from config
151
+ */
152
+ export async function installSkills(projectDir, { projectTags = [] } = {}) {
153
+ const claudeSkillsDir = join(projectDir, '.claude', 'skills');
154
+ mkdirSync(claudeSkillsDir, { recursive: true });
155
+
156
+ for (const level of SKILL_LEVELS_TO_INSTALL) {
157
+ const levelDir = join(FRAMEWORK_SKILLS_DIR, level);
158
+ if (!existsSync(levelDir)) continue;
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
+ }
177
+ }
178
+ }