@polymorphism-tech/morph-spec 8.0.0 → 8.1.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.
Files changed (168) hide show
  1. package/README.md +2 -2
  2. package/bin/validate.js +379 -379
  3. package/claude-plugin.json +1 -1
  4. package/docs/CHEATSHEET.md +1 -1
  5. package/docs/QUICKSTART.md +1 -1
  6. package/framework/CLAUDE.md +1 -1
  7. package/framework/MORPH.md +5 -5
  8. package/framework/agents.json +1 -1
  9. package/framework/commands/commit.md +166 -166
  10. package/framework/commands/morph-infra.md +215 -215
  11. package/framework/commands/morph-preflight.md +205 -205
  12. package/framework/commands/morph-troubleshoot.md +124 -124
  13. package/framework/hooks/README.md +0 -1
  14. package/framework/hooks/claude-code/statusline.py +679 -679
  15. package/framework/hooks/claude-code/statusline.sh +11 -11
  16. package/framework/hooks/git/commit-msg/conventional-commits.sh +33 -33
  17. package/framework/hooks/git/pre-commit/agents.sh +25 -25
  18. package/framework/hooks/git/pre-commit/orchestrator.sh +64 -64
  19. package/framework/hooks/git/pre-commit/specs.sh +50 -50
  20. package/framework/hooks/git/pre-push/run-tests.sh +44 -44
  21. package/framework/hooks/shared/activity-logger.js +160 -160
  22. package/framework/hooks/shared/stdin-reader.js +26 -26
  23. package/framework/rubrics/backend.json +41 -41
  24. package/framework/rules/ai-agents-standards.md +45 -45
  25. package/framework/rules/dotnet-templates.md +86 -86
  26. package/framework/rules/frontend-standards.md +13 -13
  27. package/framework/rules/infrastructure-standards.md +27 -27
  28. package/framework/rules/neon-standards.md +25 -25
  29. package/framework/rules/nextjs-standards.md +20 -20
  30. package/framework/rules/nextjs-templates.md +40 -40
  31. package/framework/skills/core/create-umbraco-lp/SKILL.md +1 -1
  32. package/framework/skills/core/migrate-lp-to-umbraco/SKILL.md +1 -1
  33. package/framework/skills/core/morph-apply/SKILL.md +12 -21
  34. package/framework/skills/core/morph-brainstorming/SKILL.md +1 -1
  35. package/framework/skills/core/morph-eval/SKILL.md +1 -1
  36. package/framework/skills/core/morph-hotfix/SKILL.md +2 -4
  37. package/framework/skills/core/morph-implement/SKILL.md +10 -19
  38. package/framework/skills/core/morph-plan/SKILL.md +1 -1
  39. package/framework/skills/core/morph-proposal/SKILL.md +1 -1
  40. package/framework/skills/core/morph-review/SKILL.md +11 -15
  41. package/framework/skills/core/morph-stack-scan/SKILL.md +1 -1
  42. package/framework/skills/core/morph-standards/SKILL.md +1 -1
  43. package/framework/standards/ai-agents/agent-session.md +160 -160
  44. package/framework/standards/ai-agents/batching.md +193 -193
  45. package/framework/standards/ai-agents/context-providers.md +188 -188
  46. package/framework/standards/ai-agents/mcp-server.md +250 -250
  47. package/framework/standards/ai-agents/mcp-tools.md +197 -197
  48. package/framework/standards/ai-agents/middleware-patterns.md +207 -207
  49. package/framework/standards/ai-agents/modalities-image-gen.md +173 -173
  50. package/framework/standards/ai-agents/modalities-speech-to-text.md +171 -171
  51. package/framework/standards/ai-agents/modalities-text-to-speech.md +181 -181
  52. package/framework/standards/ai-agents/modalities-vision.md +201 -201
  53. package/framework/standards/ai-agents/multi-agent-patterns.md +155 -155
  54. package/framework/standards/ai-agents/production.md +275 -275
  55. package/framework/standards/ai-agents/prompts-in-db.md +214 -214
  56. package/framework/standards/ai-agents/providers/model-registry.md +158 -158
  57. package/framework/standards/ai-agents/service-tier-flex.md +135 -135
  58. package/framework/standards/ai-agents/structured-output.md +249 -249
  59. package/framework/standards/ai-agents/sweet-spot.md +109 -109
  60. package/framework/templates/IDE-SNIPPETS.md +262 -262
  61. package/framework/templates/code/dotnet/abstractions/api-endpoint.cs +7 -7
  62. package/framework/templates/code/dotnet/ai-agents/AgentBootstrap.cs.template +39 -39
  63. package/framework/templates/code/dotnet/ai-agents/EmbeddingService.cs.template +68 -68
  64. package/framework/templates/code/dotnet/ai-agents/ModelRegistry.cs.template +117 -117
  65. package/framework/templates/code/dotnet/ai-agents/PromptRepository.cs.template +95 -95
  66. package/framework/templates/code/dotnet/ai-agents/ResilientToolMiddleware.cs.template +61 -61
  67. package/framework/templates/code/dotnet/ai-agents/VectorSearchTool.cs.template +107 -107
  68. package/framework/templates/code/dotnet/ai-agents/model-registry.json.template +58 -58
  69. package/framework/templates/code/dotnet/contracts/contracts-vsa.cs +281 -281
  70. package/framework/templates/code/dotnet/database/migration.cs +83 -83
  71. package/framework/templates/code/dotnet/feature/endpoint.cs.hbs +21 -21
  72. package/framework/templates/code/dotnet/feature/request.cs.hbs +4 -4
  73. package/framework/templates/code/dotnet/feature/response.cs.hbs +4 -4
  74. package/framework/templates/code/dotnet/feature/validator.cs.hbs +15 -15
  75. package/framework/templates/code/sql/ai-agents/agent-prompts-migration.sql.template +58 -58
  76. package/framework/templates/code/sql/ai-agents/pgvector-embeddings-migration.sql.template +91 -91
  77. package/framework/templates/code/typescript/contracts.ts +167 -167
  78. package/framework/templates/docs/clarifications.md +253 -253
  79. package/framework/templates/docs/schema-analysis.md +119 -119
  80. package/framework/templates/docs/ui-components.md +52 -52
  81. package/framework/templates/docs/ui-design-system.md +76 -76
  82. package/framework/templates/docs/ui-flows.md +167 -167
  83. package/framework/templates/docs/ui-mockups.md +98 -98
  84. package/framework/templates/feature/decisions.md +122 -122
  85. package/framework/templates/frontend/nextjs/Dockerfile.nextjs.hbs +43 -43
  86. package/framework/templates/frontend/nextjs/client-component.tsx.hbs +26 -26
  87. package/framework/templates/frontend/nextjs/env.mjs.hbs +32 -32
  88. package/framework/templates/frontend/nextjs/feature-form.tsx.hbs +56 -56
  89. package/framework/templates/frontend/nextjs/page.tsx.hbs +22 -22
  90. package/framework/templates/frontend/nextjs/tsconfig.json.hbs +26 -26
  91. package/framework/templates/frontend/nextjs/use-feature.ts.hbs +54 -54
  92. package/framework/templates/infrastructure/docker/Dockerfile.template +58 -58
  93. package/framework/templates/infrastructure/docker/docker-compose.template.yml +50 -50
  94. package/framework/templates/infrastructure/docker/dockerfile-api.dockerfile +38 -38
  95. package/framework/templates/infrastructure/docker/dockerfile-web.dockerfile +48 -48
  96. package/framework/templates/infrastructure/github/README.md +56 -56
  97. package/framework/templates/infrastructure/github/actions/docker-build-push/action.yml.hbs +45 -45
  98. package/framework/templates/infrastructure/github/actions/health-check/action.yml.hbs +27 -27
  99. package/framework/templates/infrastructure/github/workflows/docker-build-push.yml.hbs +59 -59
  100. package/framework/templates/infrastructure/github/workflows/dotnet-build.yml.hbs +39 -39
  101. package/framework/templates/integrations/asaas-client.cs +387 -387
  102. package/framework/templates/integrations/asaas-webhook.cs +351 -351
  103. package/framework/templates/meta-prompts/fusion/fusion-agent.md +76 -76
  104. package/framework/templates/meta-prompts/fusion/fusion-aggregator.md +100 -100
  105. package/framework/templates/meta-prompts/validators/pre-commit-validator.md +95 -95
  106. package/framework/templates/project/validate.js +120 -120
  107. package/framework/templates/state.template.json +17 -17
  108. package/framework/templates/ui/design-system.css +226 -226
  109. package/package.json +1 -1
  110. package/src/commands/dashboard/dashboard.js +53 -53
  111. package/src/commands/index.js +13 -13
  112. package/src/commands/mcp/mcp-setup.js +519 -519
  113. package/src/commands/project/checkpoint.js +209 -209
  114. package/src/commands/project/worktree.js +83 -1
  115. package/src/core/state/phase-state-machine.js +197 -197
  116. package/src/core/templates/template-renderer.js +477 -477
  117. package/src/dashboard/app/components/DetailPanel.jsx +11 -11
  118. package/src/dashboard/app/components/FlowGraph.jsx +17 -17
  119. package/src/dashboard/app/components/SearchFilter.jsx +22 -22
  120. package/src/dashboard/app/hooks/useDetailPanel.js +15 -15
  121. package/src/dashboard/app/main.jsx +13 -13
  122. package/src/dashboard/app/pages/AgentMap.jsx +370 -370
  123. package/src/dashboard/app/pages/HookInspector.jsx +418 -418
  124. package/src/dashboard/app/pages/PhasePipeline.jsx +408 -408
  125. package/src/dashboard/app/pages/RulesMap.jsx +273 -273
  126. package/src/dashboard/app/pages/SkillsLibrary.jsx +366 -366
  127. package/src/dashboard/app/pages/Standards.jsx +332 -332
  128. package/src/dashboard/app/styles/index.css +47 -47
  129. package/src/dashboard/index.html +12 -12
  130. package/src/dashboard/package.json +24 -24
  131. package/src/dashboard/scanner/hooks-scanner.js +41 -41
  132. package/src/dashboard/scanner/phases-scanner.js +15 -15
  133. package/src/dashboard/scanner/rubrics-scanner.js +12 -12
  134. package/src/dashboard/scanner/rules-scanner.js +37 -37
  135. package/src/dashboard/scanner/skills-scanner.js +43 -43
  136. package/src/dashboard/scanner/squads-scanner.js +13 -13
  137. package/src/dashboard/scanner/standards-scanner.js +26 -26
  138. package/src/dashboard/vite-plugin-morph-data.js +17 -17
  139. package/src/dashboard/vite.config.js +24 -24
  140. package/src/lib/agents/metrics-aggregator.js +56 -56
  141. package/src/lib/agents/metrics-calculator.js +125 -125
  142. package/src/lib/agents/output-collector.js +73 -73
  143. package/src/lib/detectors/claude-config-detector.js +152 -152
  144. package/src/lib/detectors/design-system-detector.js +231 -231
  145. package/src/lib/detectors/index.js +217 -217
  146. package/src/lib/generators/architecture-scanner.js +639 -639
  147. package/src/lib/generators/design-system-generator.js +508 -508
  148. package/src/lib/generators/index.js +13 -13
  149. package/src/lib/generators/settings-generator.js +209 -209
  150. package/src/lib/hooks/hook-executor.js +167 -167
  151. package/src/lib/installers/mcp-installer.js +719 -719
  152. package/src/lib/tasks/task-classifier.js +30 -30
  153. package/src/lib/tasks/task-parser.js +209 -209
  154. package/src/lib/validators/architecture/architecture-validator.js +259 -259
  155. package/src/lib/validators/checkpoint-persistence.js +39 -39
  156. package/src/lib/validators/contracts/contract-compliance-validator.js +274 -274
  157. package/src/lib/validators/css/css-validator.js +340 -340
  158. package/src/lib/validators/packages/package-validator.js +347 -347
  159. package/src/lib/validators/shared/emit-validator-dispatch.js +14 -14
  160. package/src/lib/validators/shared/issue-counter.js +14 -14
  161. package/src/lib/validators/validation-runner.js +349 -349
  162. package/src/utils/agent-id-mapper.js +11 -11
  163. package/src/utils/claude-settings-manager.js +300 -300
  164. package/src/utils/hooks-installer.js +3 -24
  165. package/src/utils/skills-installer.js +174 -174
  166. package/framework/hooks/claude-code/core/subagent-report-guard.js +0 -95
  167. package/framework/templates/.idea/morph-templates.xml +0 -92
  168. package/framework/templates/.vscode/morph-templates.code-snippets +0 -186
@@ -1,174 +1,174 @@
1
- /**
2
- * Skills Installer
3
- *
4
- * Copies framework skills to .claude/skills/ during morph-spec init.
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/.
9
- *
10
- * Source skills are directory-based: framework/skills/core/{skill-name}/SKILL.md
11
- * (plus optional scripts/, references/, assets/ subdirectories).
12
- *
13
- * Directory-based skills have their entire directory copied so Claude Code can
14
- * execute scripts and load references on demand.
15
- */
16
-
17
- import { mkdirSync, copyFileSync, existsSync, readdirSync, statSync, readFileSync, rmSync } from 'fs';
18
- import { join } from 'path';
19
- import { fileURLToPath } from 'url';
20
- import { parseStacks, shouldInstall } from '../lib/stack-filter.js';
21
-
22
- const __dirname = fileURLToPath(new URL('.', import.meta.url));
23
- const FRAMEWORK_SKILLS_DIR = join(__dirname, '..', '..', 'framework', 'skills');
24
-
25
- /**
26
- * Skill levels to install in .claude/skills/.
27
- * Only `core/` survives — single source of truth aligned with MORPH.md.
28
- */
29
- const SKILL_LEVELS_TO_INSTALL = ['core'];
30
-
31
- /**
32
- * Check if a skill should be installed based on its stacks: frontmatter.
33
- * @param {string} skillContent - Raw SKILL.md content
34
- * @param {string[]} projectTags - Stack tags from config
35
- * @returns {boolean}
36
- */
37
- export function shouldInstallSkill(skillContent, projectTags) {
38
- return shouldInstall(parseStacks(skillContent), projectTags);
39
- }
40
-
41
- /**
42
- * Recursively copy a directory tree from src to dest.
43
- * Creates dest if it doesn't exist. Skips README.md files.
44
- *
45
- * @param {string} src - Source directory path
46
- * @param {string} dest - Destination directory path
47
- */
48
- function copyDirectory(src, dest) {
49
- mkdirSync(dest, { recursive: true });
50
- for (const entry of readdirSync(src)) {
51
- if (entry === 'README.md') continue;
52
- const srcPath = join(src, entry);
53
- const destPath = join(dest, entry);
54
- if (statSync(srcPath).isDirectory()) {
55
- copyDirectory(srcPath, destPath);
56
- } else {
57
- copyFileSync(srcPath, destPath);
58
- }
59
- }
60
- }
61
-
62
- /**
63
- * Walk srcDir and install skills into destDir.
64
- *
65
- * Handles two source formats:
66
- * 1. Directory-based skill: srcDir/{skill-name}/SKILL.md
67
- * → copies entire directory (SKILL.md + scripts/ + references/ + assets/)
68
- * → destDir/{skill-name}/
69
- *
70
- * 2. Flat .md skill (legacy): srcDir/{skill-name}.md
71
- * → destDir/{skill-name}/SKILL.md
72
- *
73
- * The `core/` level directory is traversed recursively. A directory is
74
- * recognized as a skill directory when it contains a SKILL.md file.
75
- *
76
- * @param {string} srcDir - Source directory to walk
77
- * @param {string} destDir - Destination base directory (.claude/skills/)
78
- */
79
- function installSkillsFromDir(srcDir, destDir, projectTags) {
80
- const entries = readdirSync(srcDir);
81
- for (const entry of entries) {
82
- const srcPath = join(srcDir, entry);
83
- const stat = statSync(srcPath);
84
-
85
- if (stat.isDirectory()) {
86
- const skillMdPath = join(srcPath, 'SKILL.md');
87
- if (existsSync(skillMdPath)) {
88
- // Directory-based skill — check stack filter before copying
89
- const content = readFileSync(skillMdPath, 'utf-8');
90
- if (!shouldInstallSkill(content, projectTags)) continue;
91
- const skillDestDir = join(destDir, entry);
92
- copyDirectory(srcPath, skillDestDir);
93
- } else {
94
- // Level/category directory — recurse
95
- installSkillsFromDir(srcPath, destDir, projectTags);
96
- }
97
- } else if (entry.endsWith('.md') && entry !== 'README.md') {
98
- // Legacy flat .md skill — check stack filter before copying
99
- const content = readFileSync(srcPath, 'utf-8');
100
- if (!shouldInstallSkill(content, projectTags)) continue;
101
- const skillName = entry.slice(0, -3);
102
- const skillDir = join(destDir, skillName);
103
- mkdirSync(skillDir, { recursive: true });
104
- copyFileSync(srcPath, join(skillDir, 'SKILL.md'));
105
- }
106
- }
107
- }
108
-
109
- function collectSourceSkillNames() {
110
- const names = new Set();
111
- for (const level of SKILL_LEVELS_TO_INSTALL) {
112
- const levelDir = join(FRAMEWORK_SKILLS_DIR, level);
113
- if (!existsSync(levelDir)) continue;
114
- collectNamesFromDir(levelDir, names);
115
- }
116
- return names;
117
- }
118
-
119
- function collectNamesFromDir(dir, names) {
120
- for (const entry of readdirSync(dir)) {
121
- const fullPath = join(dir, entry);
122
- if (!statSync(fullPath).isDirectory()) continue;
123
- if (existsSync(join(fullPath, 'SKILL.md'))) {
124
- names.add(entry);
125
- } else {
126
- collectNamesFromDir(fullPath, names);
127
- }
128
- }
129
- }
130
-
131
- /**
132
- * Install morph framework skills to .claude/skills/ in the target project.
133
- * Skills are copied into subdirectories as SKILL.md files so Claude Code can
134
- * discover them natively via /<skill-name> syntax (e.g., /phase-design,
135
- * /nextjs-expert). Directory-based skills also get their scripts/,
136
- * references/, and assets/ subdirectories copied for on-demand loading.
137
- *
138
- * Stack filtering: if projectTags is provided, only skills whose `stacks:`
139
- * frontmatter matches the project tags are installed. Skills without a
140
- * `stacks:` field are always installed (universal).
141
- *
142
- * @param {string} projectDir - Target project root directory
143
- * @param {Object} [options]
144
- * @param {string[]} [options.projectTags=[]] - Stack tags from config
145
- */
146
- export async function installSkills(projectDir, { projectTags = [] } = {}) {
147
- const claudeSkillsDir = join(projectDir, '.claude', 'skills');
148
- mkdirSync(claudeSkillsDir, { recursive: true });
149
-
150
- for (const level of SKILL_LEVELS_TO_INSTALL) {
151
- const levelDir = join(FRAMEWORK_SKILLS_DIR, level);
152
- if (!existsSync(levelDir)) continue;
153
- installSkillsFromDir(levelDir, claudeSkillsDir, projectTags);
154
- }
155
-
156
- // Collect installed morph-* skill names
157
- const installed = new Set();
158
- for (const entry of readdirSync(claudeSkillsDir)) {
159
- if (existsSync(join(claudeSkillsDir, entry, 'SKILL.md'))) {
160
- installed.add(entry);
161
- }
162
- }
163
-
164
- // Cleanup orphan morph-* skill dirs that exist in source but were filtered out
165
- const allSourceSkills = collectSourceSkillNames();
166
- for (const entry of readdirSync(claudeSkillsDir)) {
167
- if (!entry.startsWith('morph-')) continue;
168
- if (!installed.has(entry) && allSourceSkills.has(entry)) {
169
- rmSync(join(claudeSkillsDir, entry), { recursive: true, force: true });
170
- }
171
- }
172
-
173
- return { installed: installed.size };
174
- }
1
+ /**
2
+ * Skills Installer
3
+ *
4
+ * Copies framework skills to .claude/skills/ during morph-spec init.
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/.
9
+ *
10
+ * Source skills are directory-based: framework/skills/core/{skill-name}/SKILL.md
11
+ * (plus optional scripts/, references/, assets/ subdirectories).
12
+ *
13
+ * Directory-based skills have their entire directory copied so Claude Code can
14
+ * execute scripts and load references on demand.
15
+ */
16
+
17
+ import { mkdirSync, copyFileSync, existsSync, readdirSync, statSync, readFileSync, rmSync } from 'fs';
18
+ import { join } from 'path';
19
+ import { fileURLToPath } from 'url';
20
+ import { parseStacks, shouldInstall } from '../lib/stack-filter.js';
21
+
22
+ const __dirname = fileURLToPath(new URL('.', import.meta.url));
23
+ const FRAMEWORK_SKILLS_DIR = join(__dirname, '..', '..', 'framework', 'skills');
24
+
25
+ /**
26
+ * Skill levels to install in .claude/skills/.
27
+ * Only `core/` survives — single source of truth aligned with MORPH.md.
28
+ */
29
+ const SKILL_LEVELS_TO_INSTALL = ['core'];
30
+
31
+ /**
32
+ * Check if a skill should be installed based on its stacks: frontmatter.
33
+ * @param {string} skillContent - Raw SKILL.md content
34
+ * @param {string[]} projectTags - Stack tags from config
35
+ * @returns {boolean}
36
+ */
37
+ export function shouldInstallSkill(skillContent, projectTags) {
38
+ return shouldInstall(parseStacks(skillContent), projectTags);
39
+ }
40
+
41
+ /**
42
+ * Recursively copy a directory tree from src to dest.
43
+ * Creates dest if it doesn't exist. Skips README.md files.
44
+ *
45
+ * @param {string} src - Source directory path
46
+ * @param {string} dest - Destination directory path
47
+ */
48
+ function copyDirectory(src, dest) {
49
+ mkdirSync(dest, { recursive: true });
50
+ for (const entry of readdirSync(src)) {
51
+ if (entry === 'README.md') continue;
52
+ const srcPath = join(src, entry);
53
+ const destPath = join(dest, entry);
54
+ if (statSync(srcPath).isDirectory()) {
55
+ copyDirectory(srcPath, destPath);
56
+ } else {
57
+ copyFileSync(srcPath, destPath);
58
+ }
59
+ }
60
+ }
61
+
62
+ /**
63
+ * Walk srcDir and install skills into destDir.
64
+ *
65
+ * Handles two source formats:
66
+ * 1. Directory-based skill: srcDir/{skill-name}/SKILL.md
67
+ * → copies entire directory (SKILL.md + scripts/ + references/ + assets/)
68
+ * → destDir/{skill-name}/
69
+ *
70
+ * 2. Flat .md skill (legacy): srcDir/{skill-name}.md
71
+ * → destDir/{skill-name}/SKILL.md
72
+ *
73
+ * The `core/` level directory is traversed recursively. A directory is
74
+ * recognized as a skill directory when it contains a SKILL.md file.
75
+ *
76
+ * @param {string} srcDir - Source directory to walk
77
+ * @param {string} destDir - Destination base directory (.claude/skills/)
78
+ */
79
+ function installSkillsFromDir(srcDir, destDir, projectTags) {
80
+ const entries = readdirSync(srcDir);
81
+ for (const entry of entries) {
82
+ const srcPath = join(srcDir, entry);
83
+ const stat = statSync(srcPath);
84
+
85
+ if (stat.isDirectory()) {
86
+ const skillMdPath = join(srcPath, 'SKILL.md');
87
+ if (existsSync(skillMdPath)) {
88
+ // Directory-based skill — check stack filter before copying
89
+ const content = readFileSync(skillMdPath, 'utf-8');
90
+ if (!shouldInstallSkill(content, projectTags)) continue;
91
+ const skillDestDir = join(destDir, entry);
92
+ copyDirectory(srcPath, skillDestDir);
93
+ } else {
94
+ // Level/category directory — recurse
95
+ installSkillsFromDir(srcPath, destDir, projectTags);
96
+ }
97
+ } else if (entry.endsWith('.md') && entry !== 'README.md') {
98
+ // Legacy flat .md skill — check stack filter before copying
99
+ const content = readFileSync(srcPath, 'utf-8');
100
+ if (!shouldInstallSkill(content, projectTags)) continue;
101
+ const skillName = entry.slice(0, -3);
102
+ const skillDir = join(destDir, skillName);
103
+ mkdirSync(skillDir, { recursive: true });
104
+ copyFileSync(srcPath, join(skillDir, 'SKILL.md'));
105
+ }
106
+ }
107
+ }
108
+
109
+ function collectSourceSkillNames() {
110
+ const names = new Set();
111
+ for (const level of SKILL_LEVELS_TO_INSTALL) {
112
+ const levelDir = join(FRAMEWORK_SKILLS_DIR, level);
113
+ if (!existsSync(levelDir)) continue;
114
+ collectNamesFromDir(levelDir, names);
115
+ }
116
+ return names;
117
+ }
118
+
119
+ function collectNamesFromDir(dir, names) {
120
+ for (const entry of readdirSync(dir)) {
121
+ const fullPath = join(dir, entry);
122
+ if (!statSync(fullPath).isDirectory()) continue;
123
+ if (existsSync(join(fullPath, 'SKILL.md'))) {
124
+ names.add(entry);
125
+ } else {
126
+ collectNamesFromDir(fullPath, names);
127
+ }
128
+ }
129
+ }
130
+
131
+ /**
132
+ * Install morph framework skills to .claude/skills/ in the target project.
133
+ * Skills are copied into subdirectories as SKILL.md files so Claude Code can
134
+ * discover them natively via /<skill-name> syntax (e.g., /phase-design,
135
+ * /nextjs-expert). Directory-based skills also get their scripts/,
136
+ * references/, and assets/ subdirectories copied for on-demand loading.
137
+ *
138
+ * Stack filtering: if projectTags is provided, only skills whose `stacks:`
139
+ * frontmatter matches the project tags are installed. Skills without a
140
+ * `stacks:` field are always installed (universal).
141
+ *
142
+ * @param {string} projectDir - Target project root directory
143
+ * @param {Object} [options]
144
+ * @param {string[]} [options.projectTags=[]] - Stack tags from config
145
+ */
146
+ export async function installSkills(projectDir, { projectTags = [] } = {}) {
147
+ const claudeSkillsDir = join(projectDir, '.claude', 'skills');
148
+ mkdirSync(claudeSkillsDir, { recursive: true });
149
+
150
+ for (const level of SKILL_LEVELS_TO_INSTALL) {
151
+ const levelDir = join(FRAMEWORK_SKILLS_DIR, level);
152
+ if (!existsSync(levelDir)) continue;
153
+ installSkillsFromDir(levelDir, claudeSkillsDir, projectTags);
154
+ }
155
+
156
+ // Collect installed morph-* skill names
157
+ const installed = new Set();
158
+ for (const entry of readdirSync(claudeSkillsDir)) {
159
+ if (existsSync(join(claudeSkillsDir, entry, 'SKILL.md'))) {
160
+ installed.add(entry);
161
+ }
162
+ }
163
+
164
+ // Cleanup orphan morph-* skill dirs that exist in source but were filtered out
165
+ const allSourceSkills = collectSourceSkillNames();
166
+ for (const entry of readdirSync(claudeSkillsDir)) {
167
+ if (!entry.startsWith('morph-')) continue;
168
+ if (!installed.has(entry) && allSourceSkills.has(entry)) {
169
+ rmSync(join(claudeSkillsDir, entry), { recursive: true, force: true });
170
+ }
171
+ }
172
+
173
+ return { installed: installed.size };
174
+ }
@@ -1,95 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * subagent-report-guard.js — Stop / SubagentStop hook
4
- *
5
- * Enforces clause 1 of the morph-spec dispatch protocol: a dispatched
6
- * sub-agent's FINAL act is SendMessage to the orchestrator (main). Plain text
7
- * in the last message never reaches the orchestrator, and an idle agent forces
8
- * the costly nag-and-wait cycle (observed 2/2 in real apply sessions —
9
- * MORPH_GHLBrain 2026-07-11).
10
- *
11
- * Detection: the session transcript (JSONL) contains the dispatch-protocol
12
- * marker ("Protocolo obrigatório") inside a USER message — that is how the
13
- * dispatch prompt arrives in the sub-agent's own transcript. In the
14
- * ORCHESTRATOR's transcript the marker only appears inside assistant tool_use
15
- * input (the Agent call that did the dispatch), so the orchestrator is never
16
- * blocked by this guard. A SendMessage tool_use anywhere in an assistant
17
- * message counts as the report.
18
- *
19
- * Action: block the stop ONCE ({"decision":"block"}) with the exact
20
- * instruction to call SendMessage — the agent continues and reports.
21
- * stop_hook_active guards against blocking loops. Fail-open on any error.
22
- */
23
-
24
- import { readFileSync, existsSync } from 'fs';
25
- import { stateExists } from '../../shared/state-reader.js';
26
- import { block, pass } from '../../shared/hook-response.js';
27
- import { logHookActivity } from '../../shared/activity-logger.js';
28
-
29
- const PROTOCOL_MARKER = 'Protocolo obrigatório';
30
-
31
- function readStdin() {
32
- try {
33
- return JSON.parse(readFileSync(0, 'utf8') || '{}');
34
- } catch {
35
- return {};
36
- }
37
- }
38
-
39
- function projectDir() {
40
- return process.env.CLAUDE_PROJECT_DIR || process.cwd();
41
- }
42
-
43
- /**
44
- * Scan a transcript for the dispatch-protocol marker (user messages only) and
45
- * for a SendMessage tool call (assistant messages).
46
- *
47
- * @param {string} path - JSONL transcript path
48
- * @returns {{ dispatched: boolean, reported: boolean }}
49
- */
50
- function scanTranscript(path) {
51
- const raw = readFileSync(path, 'utf8');
52
- let dispatched = false;
53
- let reported = false;
54
- for (const line of raw.split('\n')) {
55
- if (!line.trim()) continue;
56
- let role = null;
57
- try {
58
- const entry = JSON.parse(line);
59
- role = entry?.message?.role ?? entry?.type ?? null;
60
- } catch {
61
- continue;
62
- }
63
- if (!dispatched && role === 'user' && line.includes(PROTOCOL_MARKER)) dispatched = true;
64
- if (!reported && role === 'assistant' && line.includes('"name":"SendMessage"')) reported = true;
65
- }
66
- return { dispatched, reported };
67
- }
68
-
69
- try {
70
- if (!stateExists(projectDir())) pass();
71
-
72
- const input = readStdin();
73
-
74
- // Never loop: a previous block already forced one continuation — let this
75
- // stop through even if the agent still failed to report.
76
- if (input.stop_hook_active) pass();
77
-
78
- const transcriptPath = input.transcript_path;
79
- if (!transcriptPath || !existsSync(transcriptPath)) pass();
80
-
81
- const { dispatched, reported } = scanTranscript(transcriptPath);
82
- if (!dispatched || reported) pass();
83
-
84
- logHookActivity('subagent-report-guard', input.hook_event_name || 'Stop', 'blocked', projectDir());
85
- block(
86
- '[morph-spec] PROTOCOLO DE RETORNO — você foi despachado com o protocolo obrigatório ' +
87
- 'mas ainda não chamou SendMessage. Sua última mensagem em texto NÃO chega ao orquestrador. ' +
88
- 'Antes de encerrar, chame a tool SendMessage com to:"main" e o relatório completo no content: ' +
89
- '(1) o que foi feito em 2-3 frases, (2) arquivos modificados, (3) problemas encontrados e ' +
90
- 'como foram resolvidos, (4) auto-score 0-10. O SendMessage é o seu ato final.'
91
- );
92
- } catch {
93
- // Fail-open
94
- process.exit(0);
95
- }
@@ -1,92 +0,0 @@
1
- <templateSet group="MORPH Templates">
2
- <template name="morph-pascal" value="{{pascalCase $FEATURE_NAME$}}" description="Convert to PascalCase (UserAuth)" toReformat="false" toShortenFQNames="true">
3
- <variable name="FEATURE_NAME" expression="" defaultValue="&quot;FEATURE_NAME&quot;" alwaysStopAt="true" />
4
- <context>
5
- <option name="OTHER" value="true" />
6
- </context>
7
- </template>
8
-
9
- <template name="morph-camel" value="{{camelCase $FEATURE_NAME$}}" description="Convert to camelCase (userAuth)" toReformat="false" toShortenFQNames="true">
10
- <variable name="FEATURE_NAME" expression="" defaultValue="&quot;FEATURE_NAME&quot;" alwaysStopAt="true" />
11
- <context>
12
- <option name="OTHER" value="true" />
13
- </context>
14
- </template>
15
-
16
- <template name="morph-snake" value="{{snakeCase $FEATURE_NAME$}}" description="Convert to snake_case (user_auth)" toReformat="false" toShortenFQNames="true">
17
- <variable name="FEATURE_NAME" expression="" defaultValue="&quot;FEATURE_NAME&quot;" alwaysStopAt="true" />
18
- <context>
19
- <option name="OTHER" value="true" />
20
- </context>
21
- </template>
22
-
23
- <template name="morph-title" value="{{titleCase $FEATURE_NAME$}}" description="Convert to Title Case (User Auth)" toReformat="false" toShortenFQNames="true">
24
- <variable name="FEATURE_NAME" expression="" defaultValue="&quot;FEATURE_NAME&quot;" alwaysStopAt="true" />
25
- <context>
26
- <option name="OTHER" value="true" />
27
- </context>
28
- </template>
29
-
30
- <template name="morph-kebab" value="{{kebabCase $FEATURE_NAME$}}" description="Convert to kebab-case (user-auth)" toReformat="false" toShortenFQNames="true">
31
- <variable name="FEATURE_NAME" expression="" defaultValue="&quot;FEATURE_NAME&quot;" alwaysStopAt="true" />
32
- <context>
33
- <option name="OTHER" value="true" />
34
- </context>
35
- </template>
36
-
37
- <template name="morph-if" value="{{#if $CONDITION$}}&#10; $END$&#10;{{/if}}" description="Handlebars if block" toReformat="false" toShortenFQNames="true">
38
- <variable name="CONDITION" expression="" defaultValue="&quot;condition&quot;" alwaysStopAt="true" />
39
- <context>
40
- <option name="OTHER" value="true" />
41
- </context>
42
- </template>
43
-
44
- <template name="morph-if-else" value="{{#if $CONDITION$}}&#10; $TRUE_BRANCH$&#10;{{else}}&#10; $FALSE_BRANCH$&#10;{{/if}}" description="Handlebars if-else block" toReformat="false" toShortenFQNames="true">
45
- <variable name="CONDITION" expression="" defaultValue="&quot;condition&quot;" alwaysStopAt="true" />
46
- <variable name="TRUE_BRANCH" expression="" defaultValue="&quot;&quot;" alwaysStopAt="true" />
47
- <variable name="FALSE_BRANCH" expression="" defaultValue="&quot;&quot;" alwaysStopAt="true" />
48
- <context>
49
- <option name="OTHER" value="true" />
50
- </context>
51
- </template>
52
-
53
- <template name="morph-each" value="{{#each $ITEMS$}}&#10; {{this.$PROPERTY$}}&#10;{{/each}}" description="Handlebars each loop" toReformat="false" toShortenFQNames="true">
54
- <variable name="ITEMS" expression="" defaultValue="&quot;items&quot;" alwaysStopAt="true" />
55
- <variable name="PROPERTY" expression="" defaultValue="&quot;name&quot;" alwaysStopAt="true" />
56
- <context>
57
- <option name="OTHER" value="true" />
58
- </context>
59
- </template>
60
-
61
- <template name="morph-service" value="namespace {{NAMESPACE}}.Application.Features.{{pascalCase FEATURE_NAME}}.Services;&#10;&#10;public class {{pascalCase FEATURE_NAME}}Service(&#10; I{{pascalCase FEATURE_NAME}}Repository repository,&#10; ILogger&lt;{{pascalCase FEATURE_NAME}}Service&gt; logger) : I{{pascalCase FEATURE_NAME}}Service&#10;{&#10; public async Task&lt;{{pascalCase FEATURE_NAME}}Dto?&gt; GetByIdAsync(int id, CancellationToken cancellationToken = default)&#10; {&#10; $END$&#10; }&#10;}" description="MORPH .NET Service class template" toReformat="false" toShortenFQNames="true">
62
- <context>
63
- <option name="C_SHARP" value="true" />
64
- </context>
65
- </template>
66
-
67
- <template name="morph-repository" value="public class {{pascalCase FEATURE_NAME}}Repository(AppDbContext context) : I{{pascalCase FEATURE_NAME}}Repository&#10;{&#10; private readonly DbSet&lt;{{pascalCase FEATURE_NAME}}&gt; _dbSet = context.Set&lt;{{pascalCase FEATURE_NAME}}&gt;();&#10;&#10; public async Task&lt;{{pascalCase FEATURE_NAME}}?&gt; GetByIdAsync(int id, CancellationToken cancellationToken = default)&#10; {&#10; $END$&#10; }&#10;}" description="MORPH .NET Repository class template" toReformat="false" toShortenFQNames="true">
68
- <context>
69
- <option name="C_SHARP" value="true" />
70
- </context>
71
- </template>
72
-
73
- <template name="morph-component" value="@page &quot;/{{kebabCase FEATURE_NAME}}&quot;&#10;@inject I{{pascalCase FEATURE_NAME}}Service {{pascalCase FEATURE_NAME}}Service&#10;@inject ILogger&lt;{{pascalCase FEATURE_NAME}}Page&gt; Logger&#10;&#10;&lt;PageTitle&gt;{{pascalCase FEATURE_NAME}}&lt;/PageTitle&gt;&#10;&#10;&lt;div class=&quot;container-fluid&quot;&gt;&#10; $END$&#10;&lt;/div&gt;&#10;&#10;@code {&#10;&#10;}" description="MORPH Blazor component template" toReformat="false" toShortenFQNames="true">
74
- <context>
75
- <option name="HTML" value="true" />
76
- </context>
77
- </template>
78
-
79
- <template name="morph-migration" value="public partial class Add{{pascalCase FEATURE_NAME}} : Migration&#10;{&#10; protected override void Up(MigrationBuilder migrationBuilder)&#10; {&#10; migrationBuilder.CreateTable(&#10; name: &quot;{{pascalCase FEATURE_NAME}}s&quot;,&#10; columns: table =&gt; new&#10; {&#10; Id = table.Column&lt;int&gt;(type: &quot;int&quot;, nullable: false)&#10; .Annotation(&quot;SqlServer:Identity&quot;, &quot;1, 1&quot;),&#10; $END$&#10; },&#10; constraints: table =&gt;&#10; {&#10; table.PrimaryKey(&quot;PK_{{pascalCase FEATURE_NAME}}s&quot;, x =&gt; x.Id);&#10; });&#10; }&#10;&#10; protected override void Down(MigrationBuilder migrationBuilder)&#10; {&#10; migrationBuilder.DropTable(name: &quot;{{pascalCase FEATURE_NAME}}s&quot;);&#10; }&#10;}" description="MORPH EF Core migration template" toReformat="false" toShortenFQNames="true">
80
- <context>
81
- <option name="C_SHARP" value="true" />
82
- </context>
83
- </template>
84
-
85
- <template name="morph-ts-contract" value="export interface {{pascalCase FEATURE_NAME}} {&#10; id: number;&#10; $FIELD1$&#10;}&#10;&#10;export interface Create{{pascalCase FEATURE_NAME}}Request {&#10; $FIELD2$&#10;}&#10;&#10;export interface Update{{pascalCase FEATURE_NAME}}Request {&#10; $END$&#10;}" description="MORPH TypeScript contract template" toReformat="false" toShortenFQNames="true">
86
- <variable name="FIELD1" expression="" defaultValue="&quot;&quot;" alwaysStopAt="true" />
87
- <variable name="FIELD2" expression="" defaultValue="&quot;&quot;" alwaysStopAt="true" />
88
- <context>
89
- <option name="TypeScript" value="true" />
90
- </context>
91
- </template>
92
- </templateSet>