@polymorphism-tech/morph-spec 4.22.0 → 6.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (211) hide show
  1. package/CLAUDE.md +57 -71
  2. package/README.md +2 -2
  3. package/bin/morph-spec.js +224 -491
  4. package/claude-plugin.json +1 -1
  5. package/docs/CHEATSHEET.md +1 -1
  6. package/docs/QUICKSTART.md +1 -1
  7. package/framework/CLAUDE.md +55 -38
  8. package/framework/MORPH.md +244 -0
  9. package/framework/agents.json +127 -1198
  10. package/framework/commands/morph-apply.md +56 -240
  11. package/framework/commands/morph-archive.md +48 -85
  12. package/framework/commands/morph-preflight.md +7 -7
  13. package/framework/commands/morph-proposal.md +35 -191
  14. package/framework/commands/morph-status.md +19 -50
  15. package/framework/commands/morph-troubleshoot.md +2 -2
  16. package/framework/evals/rubrics/README.md +44 -0
  17. package/framework/evals/rubrics/architecture.md +46 -0
  18. package/framework/evals/rubrics/code-quality.md +50 -0
  19. package/framework/evals/rubrics/contracts.md +47 -0
  20. package/framework/evals/rubrics/test-coverage.md +50 -0
  21. package/framework/evals/scenarios/dotnet-crud.yaml +42 -0
  22. package/framework/evals/scenarios/nextjs-auth.yaml +38 -0
  23. package/framework/evals/suites/standard.yaml +5 -0
  24. package/framework/evals/trace-schema.md +67 -0
  25. package/framework/hooks/claude-code/core/README.md +3 -0
  26. package/framework/hooks/claude-code/core/denylist.json +14 -0
  27. package/framework/hooks/claude-code/{pre-tool-use → core}/enforce-phase-writes.js +5 -5
  28. package/framework/hooks/claude-code/core/post-edit-typecheck.js +69 -0
  29. package/framework/hooks/claude-code/core/pre-bash-denylist.js +29 -0
  30. package/framework/hooks/claude-code/{pre-tool-use → core}/protect-spec-files.js +4 -6
  31. package/framework/hooks/claude-code/{stop → core}/validate-completion.js +13 -82
  32. package/framework/hooks/shared/phase-utils.js +20 -31
  33. package/framework/hooks/shared/state-reader.js +4 -5
  34. package/framework/{skills/level-0-meta/mcp-registry.json → mcp/registry.json} +254 -243
  35. package/framework/phases.json +41 -115
  36. package/framework/rubrics/eval-rubric.json +66 -0
  37. package/framework/skills/README.md +39 -64
  38. package/framework/skills/core/README.md +3 -0
  39. package/framework/skills/core/morph-brainstorming/SKILL.md +102 -0
  40. package/framework/skills/core/morph-eval/SKILL.md +57 -0
  41. package/framework/skills/core/morph-implement/SKILL.md +83 -0
  42. package/framework/skills/core/morph-plan/SKILL.md +64 -0
  43. package/framework/skills/core/morph-review/SKILL.md +62 -0
  44. package/framework/skills/core/morph-stack-scan/SKILL.md +109 -0
  45. package/framework/skills/core/morph-standards/SKILL.md +49 -0
  46. package/framework/templates/CLAUDE.md.core.guide.md +29 -0
  47. package/framework/templates/CLAUDE.md.core.template +22 -0
  48. package/framework/templates/REGISTRY.json +4 -4
  49. package/framework/workflows/configs/fast-track.json +5 -10
  50. package/framework/workflows/configs/full.json +4 -8
  51. package/framework/workflows/configs/quick.json +32 -0
  52. package/framework/workflows/configs/standard.json +57 -64
  53. package/package.json +7 -6
  54. package/src/commands/mcp/mcp-setup.js +58 -31
  55. package/src/commands/project/doctor.js +69 -70
  56. package/src/commands/project/init.js +26 -5
  57. package/src/commands/project/status.js +18 -19
  58. package/src/commands/state/approve.js +1 -1
  59. package/src/commands/validation/validate-feature.js +18 -112
  60. package/src/core/paths/output-schema.js +133 -152
  61. package/src/core/state/phase-state-machine.js +10 -24
  62. package/src/core/state/state-manager.js +17 -17
  63. package/src/core/workflows/workflow-detector.js +214 -31
  64. package/src/core/workflows/workflow-router.js +204 -0
  65. package/src/dashboard/app/pages/AgentMap.jsx +32 -33
  66. package/src/dashboard/app/pages/LiveState.jsx +1 -1
  67. package/src/dashboard/app/pages/PhasePipeline.jsx +2 -4
  68. package/src/dashboard/app/pages/SkillsLibrary.jsx +2 -3
  69. package/src/dashboard/app/pages/Workflows.jsx +2 -2
  70. package/src/dashboard/scanner/agents-scanner.js +30 -35
  71. package/src/lib/agents/metrics-aggregator.js +1 -1
  72. package/src/lib/agents/output-collector.js +7 -9
  73. package/src/lib/context/context-builder.js +289 -0
  74. package/src/lib/detectors/claude-config-detector.js +19 -3
  75. package/src/lib/generators/context-generator.js +20 -42
  76. package/src/lib/generators/index.js +1 -0
  77. package/src/lib/generators/mandate-generator.js +512 -0
  78. package/src/lib/generators/metadata-extractor.js +1 -1
  79. package/src/lib/generators/recap-generator.js +3 -4
  80. package/src/lib/installers/mcp-installer.js +300 -92
  81. package/src/lib/phase-chain/phase-validator.js +36 -40
  82. package/src/lib/stack/stack-profile.js +74 -91
  83. package/src/lib/tasks/task-parser.js +1 -1
  84. package/src/lib/tasks/wave-resolver.js +353 -0
  85. package/src/lib/validators/checkpoint-persistence.js +2 -2
  86. package/src/lib/validators/content/cross-phase-validator.js +281 -0
  87. package/src/lib/validators/content/index.js +1 -0
  88. package/src/lib/validators/shared/emit-validator-dispatch.js +6 -56
  89. package/src/scripts/global-install.js +2 -27
  90. package/src/utils/agents-installer.js +224 -447
  91. package/src/utils/claude-settings-manager.js +57 -15
  92. package/src/utils/file-copier.js +1 -0
  93. package/src/utils/hooks-installer.js +15 -117
  94. package/src/utils/skills-installer.js +12 -18
  95. package/bin/detect-agents.js +0 -226
  96. package/bin/task-manager.js +0 -1036
  97. package/framework/agents/README.md +0 -13
  98. package/framework/agents/ai-agents/ai-system-architect.md +0 -194
  99. package/framework/agents/ai-agents/vector-search-expert.md +0 -247
  100. package/framework/agents/architecture/architecture-expert.md +0 -98
  101. package/framework/agents/architecture/morph-spec-architect.md +0 -66
  102. package/framework/agents/architecture/packages-validator.md +0 -89
  103. package/framework/agents/architecture/security-expert.md +0 -79
  104. package/framework/agents/architecture/seo-growth-hacker.md +0 -168
  105. package/framework/agents/architecture/standards-architect.md +0 -153
  106. package/framework/agents/backend/dotnet-senior.md +0 -163
  107. package/framework/agents/backend/ef-modeler.md +0 -119
  108. package/framework/agents/backend/hangfire-orchestrator.md +0 -133
  109. package/framework/agents/backend/ms-agent-expert.md +0 -55
  110. package/framework/agents/backend/neon-expert.md +0 -379
  111. package/framework/agents/frontend/css-specialist.md +0 -89
  112. package/framework/agents/frontend/design-system-validator.md +0 -100
  113. package/framework/agents/frontend/gsap-animator.md +0 -131
  114. package/framework/agents/frontend/nextjs-component-validator.md +0 -97
  115. package/framework/agents/frontend/nextjs-expert.md +0 -201
  116. package/framework/agents/frontend/ui-designer.md +0 -259
  117. package/framework/agents/infrastructure/infra-architect.md +0 -215
  118. package/framework/agents/integrations/asaas-financial.md +0 -138
  119. package/framework/agents/integrations/neon-auth.md +0 -151
  120. package/framework/agents/integrations/resend-email.md +0 -127
  121. package/framework/hooks/claude-code/post-tool-use/agent-complete.js +0 -122
  122. package/framework/hooks/claude-code/post-tool-use/dispatch.js +0 -363
  123. package/framework/hooks/claude-code/post-tool-use/handle-tool-failure.js +0 -78
  124. package/framework/hooks/claude-code/post-tool-use/validator-feedback.js +0 -194
  125. package/framework/hooks/claude-code/pre-compact/save-morph-context.js +0 -199
  126. package/framework/hooks/claude-code/pre-tool-use/task-tracking-guard.js +0 -55
  127. package/framework/hooks/claude-code/session-start/inject-morph-context.js +0 -323
  128. package/framework/hooks/claude-code/session-start/post-compact-restore.js +0 -41
  129. package/framework/hooks/claude-code/session-start/resume-context.js +0 -77
  130. package/framework/hooks/claude-code/teammate-idle/teammate-idle.js +0 -90
  131. package/framework/hooks/claude-code/user-prompt/enrich-prompt.js +0 -114
  132. package/framework/hooks/shared/compact-restore.js +0 -123
  133. package/framework/hooks/shared/dispatch-helpers.js +0 -147
  134. package/framework/hooks/shared/skill-reminder-helpers.js +0 -79
  135. package/framework/hooks/shared/stale-task-reset.js +0 -57
  136. package/framework/hooks/shared/worktree-helpers.js +0 -75
  137. package/framework/skills/level-0-meta/README.md +0 -7
  138. package/framework/skills/level-0-meta/morph-agent-teams/SKILL.md +0 -472
  139. package/framework/skills/level-0-meta/morph-brainstorming/SKILL.md +0 -174
  140. package/framework/skills/level-0-meta/morph-brainstorming/references/proposal-example.md +0 -138
  141. package/framework/skills/level-0-meta/morph-checklist/SKILL.md +0 -145
  142. package/framework/skills/level-0-meta/morph-code-review/SKILL.md +0 -250
  143. package/framework/skills/level-0-meta/morph-code-review/references/review-example.md +0 -164
  144. package/framework/skills/level-0-meta/morph-code-review/references/review-guidelines.md +0 -100
  145. package/framework/skills/level-0-meta/morph-code-review/scripts/scan-csharp.mjs +0 -151
  146. package/framework/skills/level-0-meta/morph-code-review-nextjs/SKILL.md +0 -165
  147. package/framework/skills/level-0-meta/morph-code-review-nextjs/references/review-example-nextjs.md +0 -254
  148. package/framework/skills/level-0-meta/morph-code-review-nextjs/scripts/scan-nextjs.mjs +0 -189
  149. package/framework/skills/level-0-meta/morph-frontend-review/SKILL.md +0 -344
  150. package/framework/skills/level-0-meta/morph-frontend-review/scripts/scan-accessibility.mjs +0 -376
  151. package/framework/skills/level-0-meta/morph-gemini-image/SKILL.md +0 -290
  152. package/framework/skills/level-0-meta/morph-init/SKILL.md +0 -537
  153. package/framework/skills/level-0-meta/morph-parallel-dispatch/SKILL.md +0 -225
  154. package/framework/skills/level-0-meta/morph-post-implementation/SKILL.md +0 -401
  155. package/framework/skills/level-0-meta/morph-post-implementation/scripts/detect-dev-server.mjs +0 -153
  156. package/framework/skills/level-0-meta/morph-post-implementation/scripts/detect-stack.mjs +0 -234
  157. package/framework/skills/level-0-meta/morph-simulation-checklist/SKILL.md +0 -106
  158. package/framework/skills/level-0-meta/morph-tool-usage-guide/SKILL.md +0 -243
  159. package/framework/skills/level-0-meta/morph-tool-usage-guide/references/tools-per-phase.md +0 -169
  160. package/framework/skills/level-0-meta/morph-verification-before-completion/SKILL.md +0 -148
  161. package/framework/skills/level-0-meta/morph-verification-before-completion/scripts/check-phase-outputs.mjs +0 -110
  162. package/framework/skills/level-1-workflows/README.md +0 -7
  163. package/framework/skills/level-1-workflows/morph-phase-codebase-analysis/SKILL.md +0 -269
  164. package/framework/skills/level-1-workflows/morph-phase-design/SKILL.md +0 -453
  165. package/framework/skills/level-1-workflows/morph-phase-design/references/architecture-analysis-guide.md +0 -44
  166. package/framework/skills/level-1-workflows/morph-phase-design/references/spec-authoring-guide.md +0 -55
  167. package/framework/skills/level-1-workflows/morph-phase-design/references/spec-example.md +0 -253
  168. package/framework/skills/level-1-workflows/morph-phase-implement/.morph/logs/activity.json +0 -38
  169. package/framework/skills/level-1-workflows/morph-phase-implement/SKILL.md +0 -634
  170. package/framework/skills/level-1-workflows/morph-phase-implement/prompts/code-quality-reviewer-prompt.md +0 -50
  171. package/framework/skills/level-1-workflows/morph-phase-implement/prompts/implementer-prompt.md +0 -54
  172. package/framework/skills/level-1-workflows/morph-phase-implement/prompts/spec-reviewer-prompt.md +0 -47
  173. package/framework/skills/level-1-workflows/morph-phase-implement/references/recap-example.md +0 -132
  174. package/framework/skills/level-1-workflows/morph-phase-implement/references/vsa-implementation-guide.md +0 -92
  175. package/framework/skills/level-1-workflows/morph-phase-plan/SKILL.md +0 -257
  176. package/framework/skills/level-1-workflows/morph-phase-tasks/.morph/logs/activity.json +0 -14
  177. package/framework/skills/level-1-workflows/morph-phase-tasks/SKILL.md +0 -291
  178. package/framework/skills/level-1-workflows/morph-phase-tasks/references/task-planning-patterns.md +0 -143
  179. package/framework/skills/level-1-workflows/morph-phase-tasks/references/tasks-example.md +0 -404
  180. package/framework/skills/level-1-workflows/morph-phase-tasks/scripts/validate-tasks.mjs +0 -112
  181. package/framework/skills/level-1-workflows/morph-phase-uiux/SKILL.md +0 -995
  182. package/framework/skills/level-1-workflows/morph-scope-escalation/SKILL.md +0 -147
  183. package/framework/squad-templates/backend-only.json +0 -47
  184. package/framework/squad-templates/frontend-only.json +0 -56
  185. package/framework/squad-templates/full-stack.json +0 -74
  186. package/src/commands/agents/agent-metrics.js +0 -174
  187. package/src/commands/agents/audit-dispatch.js +0 -82
  188. package/src/commands/agents/detect-agents.js +0 -275
  189. package/src/commands/agents/dispatch-agents.js +0 -461
  190. package/src/commands/agents/index.js +0 -13
  191. package/src/commands/agents/manage-agents.js +0 -352
  192. package/src/commands/agents/project-metrics.js +0 -74
  193. package/src/commands/phase/phase-reset.js +0 -74
  194. package/src/commands/scope/escalate.js +0 -217
  195. package/src/commands/state/advance-phase.js +0 -600
  196. package/src/commands/state/phase-runner.js +0 -215
  197. package/src/commands/state/state.js +0 -444
  198. package/src/commands/task/expand.js +0 -100
  199. package/src/commands/tasks/index.js +0 -4
  200. package/src/commands/tasks/task.js +0 -105
  201. package/src/commands/templates/index.js +0 -5
  202. package/src/commands/templates/template-render.js +0 -93
  203. package/src/commands/trust/trust.js +0 -118
  204. package/src/lib/agents/agents-manager.js +0 -349
  205. package/src/lib/build/build-runner.js +0 -99
  206. package/src/lib/checkpoints/checkpoint-hooks.js +0 -111
  207. package/src/lib/contracts/sprint-contract-generator.js +0 -137
  208. package/src/lib/scope/impact-analyzer.js +0 -106
  209. package/src/lib/trust/trust-manager.js +0 -157
  210. package/src/utils/tier-helpers.js +0 -24
  211. /package/framework/hooks/claude-code/{post-tool-use → core}/loop-detect.js +0 -0
@@ -1,376 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * scan-accessibility.mjs
4
- *
5
- * Scans .tsx/.ts (Next.js) and .razor (Blazor) files for static accessibility violations.
6
- * Executed locally by Claude via Bash — output returned to Claude.
7
- *
8
- * Usage:
9
- * node scan-accessibility.mjs [path] # scan directory or file (default: '.')
10
- * node scan-accessibility.mjs src/ # scan src/ recursively
11
- * node scan-accessibility.mjs --summary # counts only, no details
12
- *
13
- * Checks (7 total):
14
- * IMG_MISSING_ALT CRITICAL .tsx/.razor <img without alt= attribute
15
- * INPUT_MISSING_LABEL HIGH .tsx/.razor <input without associated label or aria-label
16
- * <InputText/<FluentTextField without Label=
17
- * HEADING_HIERARCHY HIGH .tsx/.razor Heading level skip (H1→H3 skipping H2)
18
- * LINK_NO_TEXT HIGH .tsx/.razor <a> empty or icon-only without aria-label
19
- * BUTTON_NO_TEXT HIGH .tsx/.razor <button>/<FluentButton> without accessible text
20
- * AUTOPLAY_MEDIA MEDIUM .tsx/.razor <video>/<audio> with autoPlay without muted
21
- * FLUENT_CHECKBOX_NO_LABEL MEDIUM .razor <FluentCheckbox without Label=
22
- *
23
- * Output JSON: { summary: { scanned, findings, bySeverity, clean }, findings: [...] }
24
- * Exit code: 1 if CRITICAL findings, 0 otherwise
25
- */
26
-
27
- import { readdirSync, readFileSync, statSync, existsSync } from 'fs';
28
- import { join, extname, relative } from 'path';
29
-
30
- const targetPath = process.argv[2] ?? '.';
31
- const summaryOnly = process.argv.includes('--summary');
32
-
33
- // ─── File collectors ────────────────────────────────────────────────────────
34
-
35
- const IGNORED_DIRS = ['node_modules', '.git', '.next', 'dist', 'build', '.turbo', 'bin', 'obj'];
36
-
37
- function collectFrontendFiles(dir) {
38
- if (!existsSync(dir)) return [];
39
- const stat = statSync(dir);
40
- if (stat.isFile()) {
41
- const ext = extname(dir);
42
- return ['.tsx', '.ts', '.razor'].includes(ext) ? [dir] : [];
43
- }
44
- const results = [];
45
- for (const entry of readdirSync(dir, { withFileTypes: true })) {
46
- if (entry.isDirectory() && !IGNORED_DIRS.includes(entry.name)) {
47
- results.push(...collectFrontendFiles(join(dir, entry.name)));
48
- } else if (entry.isFile()) {
49
- const ext = extname(entry.name);
50
- if (['.tsx', '.ts', '.razor'].includes(ext)) {
51
- results.push(join(dir, entry.name));
52
- }
53
- }
54
- }
55
- return results;
56
- }
57
-
58
- // ─── Checks ─────────────────────────────────────────────────────────────────
59
-
60
- /**
61
- * Check: IMG_MISSING_ALT — CRITICAL
62
- * Detect <img without alt= in .tsx and .razor files.
63
- * Matches both JSX <img ... /> and HTML <img ...> forms.
64
- */
65
- function checkImgMissingAlt(content, lines, relFile, findings) {
66
- // Match <img tags that do NOT contain alt=
67
- const imgRegex = /<img\b([^>]*?)(?:\/>|>)/g;
68
- let match;
69
- while ((match = imgRegex.exec(content)) !== null) {
70
- const attrs = match[1];
71
- if (!/\balt\s*=/i.test(attrs)) {
72
- const lineNum = content.slice(0, match.index).split('\n').length;
73
- findings.push({
74
- severity: 'CRITICAL',
75
- id: 'IMG_MISSING_ALT',
76
- file: relFile,
77
- line: lineNum,
78
- message: '<img> element missing alt attribute — required for screen readers',
79
- snippet: lines[lineNum - 1]?.trim().slice(0, 100),
80
- });
81
- }
82
- }
83
- }
84
-
85
- /**
86
- * Check: INPUT_MISSING_LABEL — HIGH
87
- * Detect <input>, <InputText>, <FluentTextField without accessible label.
88
- * Accepts: aria-label=, aria-labelledby=, or associated <label for=
89
- * For Blazor: Label= attribute on <InputText> and <FluentTextField>
90
- */
91
- function checkInputMissingLabel(content, lines, relFile, findings) {
92
- const isRazor = relFile.endsWith('.razor');
93
-
94
- // --- Standard <input> (both tsx and razor HTML sections) ---
95
- const inputRegex = /<input\b([^>]*?)(?:\/>|>)/g;
96
- let match;
97
- while ((match = inputRegex.exec(content)) !== null) {
98
- const attrs = match[1];
99
- // Skip hidden inputs — they don't need labels
100
- if (/type\s*=\s*['"]hidden['"]/i.test(attrs)) continue;
101
- const hasLabel =
102
- /\baria-label\s*=/i.test(attrs) ||
103
- /\baria-labelledby\s*=/i.test(attrs) ||
104
- /\bid\s*=\s*['"]([^'"]+)['"]/i.test(attrs); // id present → could be associated by <label for>
105
- if (!hasLabel) {
106
- const lineNum = content.slice(0, match.index).split('\n').length;
107
- findings.push({
108
- severity: 'HIGH',
109
- id: 'INPUT_MISSING_LABEL',
110
- file: relFile,
111
- line: lineNum,
112
- message: '<input> element missing accessible label (aria-label, aria-labelledby, or <label for=)',
113
- snippet: lines[lineNum - 1]?.trim().slice(0, 100),
114
- });
115
- }
116
- }
117
-
118
- if (isRazor) {
119
- // --- Blazor <InputText> without Label= ---
120
- const inputTextRegex = /<InputText\b([^>]*?)(?:\/>|>)/g;
121
- while ((match = inputTextRegex.exec(content)) !== null) {
122
- const attrs = match[1];
123
- if (!/\bLabel\s*=/i.test(attrs) && !/\baria-label\s*=/i.test(attrs)) {
124
- const lineNum = content.slice(0, match.index).split('\n').length;
125
- findings.push({
126
- severity: 'HIGH',
127
- id: 'INPUT_MISSING_LABEL',
128
- file: relFile,
129
- line: lineNum,
130
- message: '<InputText> component missing Label= attribute (or aria-label)',
131
- snippet: lines[lineNum - 1]?.trim().slice(0, 100),
132
- });
133
- }
134
- }
135
-
136
- // --- Fluent UI <FluentTextField> without Label= ---
137
- const fluentTextRegex = /<FluentTextField\b([^>]*?)(?:\/>|>)/g;
138
- while ((match = fluentTextRegex.exec(content)) !== null) {
139
- const attrs = match[1];
140
- if (!/\bLabel\s*=/i.test(attrs) && !/\baria-label\s*=/i.test(attrs)) {
141
- const lineNum = content.slice(0, match.index).split('\n').length;
142
- findings.push({
143
- severity: 'HIGH',
144
- id: 'INPUT_MISSING_LABEL',
145
- file: relFile,
146
- line: lineNum,
147
- message: '<FluentTextField> component missing Label= attribute (or aria-label)',
148
- snippet: lines[lineNum - 1]?.trim().slice(0, 100),
149
- });
150
- }
151
- }
152
- }
153
- }
154
-
155
- /**
156
- * Check: HEADING_HIERARCHY — HIGH
157
- * Detect heading level skips (e.g. H1 → H3 without H2) within a file.
158
- * Scans for h1–h6 / H1–H6 tags in order and reports any skip > 1 level.
159
- */
160
- function checkHeadingHierarchy(content, lines, relFile, findings) {
161
- // Collect all headings in document order
162
- const headingRegex = /<[Hh]([1-6])\b/g;
163
- const headings = [];
164
- let match;
165
- while ((match = headingRegex.exec(content)) !== null) {
166
- headings.push({
167
- level: parseInt(match[1], 10),
168
- lineNum: content.slice(0, match.index).split('\n').length,
169
- });
170
- }
171
-
172
- // Check for level skips
173
- for (let i = 1; i < headings.length; i++) {
174
- const prev = headings[i - 1];
175
- const curr = headings[i];
176
- // Only flag increases that skip a level (e.g. h1 → h3)
177
- if (curr.level > prev.level + 1) {
178
- findings.push({
179
- severity: 'HIGH',
180
- id: 'HEADING_HIERARCHY',
181
- file: relFile,
182
- line: curr.lineNum,
183
- message: `Heading level skip: H${prev.level} (line ${prev.lineNum}) → H${curr.level} (line ${curr.lineNum}) — missing H${prev.level + 1}`,
184
- snippet: lines[curr.lineNum - 1]?.trim().slice(0, 100),
185
- });
186
- }
187
- }
188
- }
189
-
190
- /**
191
- * Check: LINK_NO_TEXT — HIGH
192
- * Detect <a> elements that are empty or contain only an icon without aria-label.
193
- * Empty: <a href="..."></a> or <a .../>
194
- * Icon-only: <a href="..."><SomeIcon /></a> or <a href="..."><i class="..."></i></a>
195
- */
196
- function checkLinkNoText(content, lines, relFile, findings) {
197
- // Match opening <a ...> tag and capture attributes, then check for aria-label
198
- // We use a simplified approach: find <a> tags and look for aria-label in attrs
199
- const linkRegex = /<a\b([^>]*?)>([\s\S]*?)<\/a>/g;
200
- let match;
201
- while ((match = linkRegex.exec(content)) !== null) {
202
- const attrs = match[1];
203
- const innerContent = match[2].trim();
204
-
205
- // Has aria-label → OK
206
- if (/\baria-label\s*=/i.test(attrs) || /\baria-labelledby\s*=/i.test(attrs)) continue;
207
-
208
- // Check if inner content has visible text (not just whitespace, tags, or expressions)
209
- // Strip JSX expressions {}, HTML tags, and whitespace
210
- const textOnly = innerContent
211
- .replace(/\{[^}]*\}/g, '') // strip {expressions}
212
- .replace(/<[^>]+>/g, '') // strip HTML/JSX tags
213
- .trim();
214
-
215
- if (!textOnly) {
216
- const lineNum = content.slice(0, match.index).split('\n').length;
217
- findings.push({
218
- severity: 'HIGH',
219
- id: 'LINK_NO_TEXT',
220
- file: relFile,
221
- line: lineNum,
222
- message: '<a> element has no accessible text — add aria-label or visible text content',
223
- snippet: lines[lineNum - 1]?.trim().slice(0, 100),
224
- });
225
- }
226
- }
227
- }
228
-
229
- /**
230
- * Check: BUTTON_NO_TEXT — HIGH
231
- * Detect <button> and <FluentButton> elements without accessible text or aria-label.
232
- */
233
- function checkButtonNoText(content, lines, relFile, findings) {
234
- // <button> elements
235
- const buttonRegex = /<button\b([^>]*?)>([\s\S]*?)<\/button>/g;
236
- let match;
237
- while ((match = buttonRegex.exec(content)) !== null) {
238
- const attrs = match[1];
239
- const innerContent = match[2].trim();
240
-
241
- if (/\baria-label\s*=/i.test(attrs) || /\baria-labelledby\s*=/i.test(attrs)) continue;
242
-
243
- const textOnly = innerContent
244
- .replace(/\{[^}]*\}/g, '')
245
- .replace(/<[^>]+>/g, '')
246
- .trim();
247
-
248
- if (!textOnly) {
249
- const lineNum = content.slice(0, match.index).split('\n').length;
250
- findings.push({
251
- severity: 'HIGH',
252
- id: 'BUTTON_NO_TEXT',
253
- file: relFile,
254
- line: lineNum,
255
- message: '<button> element has no accessible text — add aria-label or visible text content',
256
- snippet: lines[lineNum - 1]?.trim().slice(0, 100),
257
- });
258
- }
259
- }
260
-
261
- // <FluentButton> (Blazor Fluent UI)
262
- const fluentBtnRegex = /<FluentButton\b([^>]*?)>([\s\S]*?)<\/FluentButton>/g;
263
- while ((match = fluentBtnRegex.exec(content)) !== null) {
264
- const attrs = match[1];
265
- const innerContent = match[2].trim();
266
-
267
- if (/\baria-label\s*=/i.test(attrs) || /\bTitle\s*=/i.test(attrs)) continue;
268
-
269
- const textOnly = innerContent
270
- .replace(/\{[^}]*\}/g, '')
271
- .replace(/<[^>]+>/g, '')
272
- .trim();
273
-
274
- if (!textOnly) {
275
- const lineNum = content.slice(0, match.index).split('\n').length;
276
- findings.push({
277
- severity: 'HIGH',
278
- id: 'BUTTON_NO_TEXT',
279
- file: relFile,
280
- line: lineNum,
281
- message: '<FluentButton> has no accessible text — add aria-label, Title=, or visible text',
282
- snippet: lines[lineNum - 1]?.trim().slice(0, 100),
283
- });
284
- }
285
- }
286
- }
287
-
288
- /**
289
- * Check: AUTOPLAY_MEDIA — MEDIUM
290
- * Detect <video> or <audio> with autoPlay attribute but without muted.
291
- * Autoplay without muted violates WCAG 1.4.2 (Audio Control).
292
- */
293
- function checkAutoplayMedia(content, lines, relFile, findings) {
294
- const mediaRegex = /<(video|audio)\b([^>]*?)(?:\/>|>)/gi;
295
- let match;
296
- while ((match = mediaRegex.exec(content)) !== null) {
297
- const attrs = match[2];
298
- // JSX: autoPlay (camelCase), HTML: autoplay
299
- if (/\bautoPlay\b|\bautoplay\b/i.test(attrs) && !/\bmuted\b/i.test(attrs)) {
300
- const lineNum = content.slice(0, match.index).split('\n').length;
301
- findings.push({
302
- severity: 'MEDIUM',
303
- id: 'AUTOPLAY_MEDIA',
304
- file: relFile,
305
- line: lineNum,
306
- message: `<${match[1]}> has autoPlay without muted — violates WCAG 1.4.2 (Audio Control)`,
307
- snippet: lines[lineNum - 1]?.trim().slice(0, 100),
308
- });
309
- }
310
- }
311
- }
312
-
313
- /**
314
- * Check: FLUENT_CHECKBOX_NO_LABEL — MEDIUM
315
- * Detect <FluentCheckbox without Label= in .razor files.
316
- */
317
- function checkFluentCheckboxNoLabel(content, lines, relFile, findings) {
318
- if (!relFile.endsWith('.razor')) return;
319
-
320
- const checkboxRegex = /<FluentCheckbox\b([^>]*?)(?:\/>|>)/g;
321
- let match;
322
- while ((match = checkboxRegex.exec(content)) !== null) {
323
- const attrs = match[1];
324
- if (!/\bLabel\s*=/i.test(attrs) && !/\baria-label\s*=/i.test(attrs)) {
325
- const lineNum = content.slice(0, match.index).split('\n').length;
326
- findings.push({
327
- severity: 'MEDIUM',
328
- id: 'FLUENT_CHECKBOX_NO_LABEL',
329
- file: relFile,
330
- line: lineNum,
331
- message: '<FluentCheckbox> missing Label= attribute — screen readers cannot identify this checkbox',
332
- snippet: lines[lineNum - 1]?.trim().slice(0, 100),
333
- });
334
- }
335
- }
336
- }
337
-
338
- // ─── Main ────────────────────────────────────────────────────────────────────
339
-
340
- const files = collectFrontendFiles(targetPath);
341
- const findings = [];
342
-
343
- for (const file of files) {
344
- const content = readFileSync(file, 'utf-8');
345
- const lines = content.split('\n');
346
- const relFile = relative(process.cwd(), file).replace(/\\/g, '/');
347
-
348
- checkImgMissingAlt(content, lines, relFile, findings);
349
- checkInputMissingLabel(content, lines, relFile, findings);
350
- checkHeadingHierarchy(content, lines, relFile, findings);
351
- checkLinkNoText(content, lines, relFile, findings);
352
- checkButtonNoText(content, lines, relFile, findings);
353
- checkAutoplayMedia(content, lines, relFile, findings);
354
- checkFluentCheckboxNoLabel(content, lines, relFile, findings);
355
- }
356
-
357
- const bySeverity = { CRITICAL: 0, HIGH: 0, MEDIUM: 0, LOW: 0 };
358
- for (const f of findings) {
359
- bySeverity[f.severity] = (bySeverity[f.severity] ?? 0) + 1;
360
- }
361
-
362
- const summary = {
363
- scanned: files.length,
364
- findings: findings.length,
365
- bySeverity,
366
- clean: findings.length === 0,
367
- };
368
-
369
- if (summaryOnly) {
370
- console.log(JSON.stringify(summary, null, 2));
371
- } else {
372
- console.log(JSON.stringify({ summary, findings }, null, 2));
373
- }
374
-
375
- // Exit 1 if any CRITICAL findings
376
- process.exit(findings.some(f => f.severity === 'CRITICAL') ? 1 : 0);
@@ -1,290 +0,0 @@
1
- ---
2
- name: morph-gemini-image
3
- description: |
4
- Expert at generating and editing images via the Gemini Image MCP (gemini-image-mcp).
5
- Constructs high-quality prompts for Gemini image models using narrative natural language.
6
- Use when implementing UI components that need generated images, or when assets.md
7
- specifies image requirements. Also use for standalone image generation requests.
8
- Triggers: /morph-gemini-image, "generate image", "create image", "edit image",
9
- "make a logo", "generate assets", image generation for components.
10
- argument-hint: "[optional: image description or assets.md IMG-ID]"
11
- user-invocable: true
12
- allowed-tools: Read, Write, Glob, Grep, AskUserQuestion
13
- cliVersion: "4.22.0"
14
- ---
15
-
16
- # Gemini Image — Prompt Specialist
17
-
18
- You are an expert at communicating with the Gemini 2.5 Flash Image model (Nano Banana) through the `gemini-image-mcp` MCP server. Your job is to transform the user's intent into the best possible prompt and then execute it.
19
-
20
- ## The Golden Rules
21
-
22
- **1. Describe scenes narratively, never list keywords.** The Gemini image model has deep language comprehension — coherent paragraphs consistently outperform disconnected keyword lists. Think of it as briefing a photographer or illustrator, not filling in search tags.
23
-
24
- **2. Generation demands depth; editing demands restraint.** These are opposite strategies:
25
- - **Generation**: Go deep. Multi-layered compositions, rich textures, dramatic lighting contrasts, visual storytelling. Simple prompts produce flat, generic images. Push for complexity — foreground interest, mid-ground subject, background atmosphere. Think in layers.
26
- - **Editing**: Go minimal. Short, direct instructions targeting exactly what changes. The more you describe in an edit prompt, the more the model "re-imagines" the image and the more artificial it looks. 1-2 sentences max for edits.
27
-
28
- ## Important: JSON Does NOT Work
29
-
30
- The Gemini image models do NOT support JSON/structured output mode for image generation. Sending JSON-formatted prompts provides no benefit and can confuse the model. Always use natural language.
31
-
32
- ## Available MCP Tools
33
-
34
- | Tool | When to use |
35
- |------|-------------|
36
- | `generate_image` | Create new images from text description |
37
- | `edit_image` | Modify an existing image (needs `imagePath`) |
38
- | `continue_editing` | Iterate on the last generated/edited image |
39
- | `list_images` | Browse image history |
40
- | `get_image_info` | Get metadata about a specific image |
41
- | `get_configuration_status` | Check API key and server health |
42
-
43
- ## How to Build Prompts
44
-
45
- ### Step 1: Identify the Category
46
-
47
- Determine what kind of image the user wants. Each category has a different prompt template that produces optimal results:
48
-
49
- **Photorealistic** — Real-world scenes, portraits, product photos, landscapes
50
- **Illustration** — Stickers, icons, cartoon, kawaii, branded designs
51
- **Text Rendering** — Logos, infographics, menus, diagrams with legible text
52
- **Product Mockup** — E-commerce, studio product shots
53
- **Minimalist / Negative Space** — Clean compositions for overlays or branding
54
- **Sequential Art** — Comics, storyboards, panels
55
-
56
- ### Step 2: Construct the Prompt
57
-
58
- Build the prompt by layering these elements (order matters — put the most important things first).
59
-
60
- The key to images that feel deep and impactful rather than flat and generic is **visual layering** — describe what's happening in the foreground, mid-ground, and background separately. A flat description ("neon city at night") produces a flat image. A layered description creates depth that pulls the viewer in.
61
-
62
- 1. **Subject** — Who/what is the main focus? Be hyper-specific.
63
- - Bad: "a warrior in armor"
64
- - Good: "a tall elven warrior in ornate silver plate armor with falcon-wing pauldrons and a high collar etched with leaf patterns"
65
-
66
- 2. **Action / Pose** — What is the subject doing? Expression? Gesture?
67
-
68
- 3. **Environment / Setting** — Where is this happening? Describe in **layers**:
69
- - **Foreground**: What's closest to camera? (objects, textures, particles, rain drops)
70
- - **Mid-ground**: Where is the subject? What surrounds them?
71
- - **Background**: What's far away? (sky, distant buildings, horizon, atmospheric haze)
72
- - This layered approach creates depth of field and visual richness that flat descriptions miss.
73
-
74
- 4. **Lighting** — This dramatically affects mood and is the single biggest quality lever. Use photography terms and describe **multiple light sources** for complexity:
75
- - "golden hour key light from the left, with cool blue fill from ambient sky on the right, and warm rim light from behind catching hair edges"
76
- - "harsh overhead spotlight casting dramatic shadows, with neon signs reflecting colored light off wet surfaces"
77
- - "soft diffused window light as primary, with practical warm bulbs adding depth in the background"
78
- - Single-source flat lighting = flat image. Multiple sources with different colors/directions = cinematic depth.
79
-
80
- 5. **Style / Medium** — How should it look?
81
- - Photography: mention camera, lens, film stock ("85mm portrait lens, shallow depth of field, Kodak Portra 400 film grain")
82
- - Illustration: name the style ("kawaii", "cel-shaded", "watercolor", "ink wash", "art nouveau")
83
- - Reference artists when useful: "in the style of Studio Ghibli", "Van Gogh's Starry Night brushwork"
84
-
85
- 6. **Color Palette** — Specify dominant colors and **contrast relationships**
86
- - "warm amber highlights against deep teal shadows"
87
- - "desaturated cool tones with a single pop of crimson red on the subject"
88
- - Color contrast draws the eye and creates visual hierarchy.
89
-
90
- 7. **Composition** — Camera angle, framing, negative space
91
- - "low angle looking up", "bird's eye view", "close-up macro", "rule of thirds with subject on left"
92
- - Describe **depth cues**: objects overlapping, foreground blur, atmospheric perspective (distant objects fading into haze)
93
-
94
- 8. **Texture and Detail** — What makes it tactile and real?
95
- - "rain droplets on glass", "worn leather with visible stitching", "dust particles floating in light beams", "condensation on cold metal"
96
- - These micro-details transform flat images into immersive scenes.
97
-
98
- 9. **Aspect Ratio** — Choose based on purpose:
99
- - `1:1` — Social media posts, avatars, icons
100
- - `16:9` — Presentations, YouTube thumbnails, desktop wallpapers
101
- - `9:16` — Stories, reels, mobile wallpapers
102
- - `3:2` — Landscape photography, blog headers
103
- - `2:3` — Portrait photography, book covers
104
- - `4:3` — Standard display, documents
105
- - `3:4` — Pinterest, portrait posters
106
-
107
- ### Step 3: Apply Category-Specific Templates
108
-
109
- #### Photorealistic
110
- ```
111
- A [shot type] of [subject with specific details], [action/pose],
112
- set in [environment with atmosphere], illuminated by [lighting description],
113
- captured with [camera/lens], [mood/feeling], [aspect ratio orientation].
114
- ```
115
- Example: "A close-up portrait of an elderly ceramicist inspecting a hand-glazed tea bowl in his sun-drenched workshop, golden hour light streaming through dusty windows, 85mm portrait lens with soft bokeh, serene and contemplative atmosphere, vertical orientation."
116
-
117
- #### Illustration / Sticker
118
- ```
119
- [Art style] [subject with personality traits], [key visual details],
120
- [line style] outlines, [shading approach], [color palette],
121
- [background specification].
122
- ```
123
- Example: "Kawaii-style red panda sticker wearing a tiny bamboo hat and holding a matcha cup, bold clean outlines, cel-shading with soft highlights, vibrant warm colors with pink accents, white background."
124
-
125
- #### Text Rendering (Logos, Infographics)
126
- ```
127
- [Image type] for [brand/concept context], featuring the text "[exact text]"
128
- in [font style description], [visual elements around text],
129
- [overall color scheme and style], [format].
130
- ```
131
- Example: "Minimalist coffee shop logo for a premium artisan brand, featuring the text 'The Daily Grind' in clean geometric sans-serif, with a stylized coffee bean icon integrated into the letter 'D', black and white with a single copper accent, square format."
132
-
133
- **Important: Text rendering often contains spelling errors.** The model frequently misspells words, swaps letters, or drops characters — especially in longer words. After generating any image with text, always verify the spelling by viewing the result. If text is wrong, use `continue_editing` with a prompt like: "Fix the text spelling — it should read '[correct text]' exactly. Keep everything else the same." You may need 2-3 iterations to get text perfect.
134
-
135
- #### Product Mockup
136
- ```
137
- High-resolution studio photograph of [product with materials],
138
- on [surface/background], [lighting setup], [camera angle] for [purpose],
139
- sharp focus on [detail], [realism level], [aspect ratio].
140
- ```
141
- Example: "High-resolution studio photograph of a minimalist black ceramic mug with a matte finish on a polished concrete surface, three-point softbox lighting with a subtle warm key light, 45-degree elevated angle for e-commerce listing, sharp focus on the steam rising from dark coffee, photorealistic, square format."
142
-
143
- #### Minimalist / Negative Space
144
- ```
145
- [Single subject] positioned [placement], [vast background description],
146
- generous negative space [purpose], [subtle lighting direction], [aspect ratio].
147
- ```
148
- Example: "A single red maple leaf positioned in the bottom-right third, vast off-white linen-textured background, generous negative space ideal for text overlay, soft diffused light from top-left casting a gentle shadow, square format."
149
-
150
- ## How to Handle Image Editing
151
-
152
- **The cardinal rule of editing: LESS IS MORE.** Verbose edit prompts cause the model to "re-imagine" the entire image, producing an artificial, over-processed look. Keep edit prompts short, direct, and focused on exactly what changes. Let the model figure out how to integrate the change naturally — it's very good at this when you don't over-constrain it.
153
-
154
- ### Adding / Removing Elements
155
- One sentence describing the change. Don't describe how to integrate it — the model handles that.
156
- ```
157
- "Add a knitted wizard hat to the cat."
158
- ```
159
-
160
- ### Background Replacement
161
- State the new background simply. Don't over-describe — the model matches lighting automatically.
162
- ```
163
- "Replace the background with a tropical beach at sunset."
164
- ```
165
- Avoid: "Replace the background with a beautiful tropical beach with golden sand, palm trees swaying, turquoise waves, warm orange and pink sunset colors reflecting on the water, ensuring the subject's lighting matches the warm sunset glow..." — this level of detail makes the result look MORE artificial, not less.
166
-
167
- ### Inpainting (Targeted Changes)
168
- Name what changes and state what stays in one line.
169
- ```
170
- "Change the blue sofa to a brown leather chesterfield. Keep everything else."
171
- ```
172
-
173
- ### Style Transfer
174
- Name the target style. Keep it simple.
175
- ```
176
- "Transform this into Van Gogh's Starry Night style."
177
- ```
178
-
179
- ### Multi-Image Composition
180
- Use `referenceImages` parameter to supply source images.
181
- ```
182
- "Put the woman from the second image in the blue dress from the first image."
183
- ```
184
-
185
- ### Iterative Refinement
186
- Use `continue_editing` for small adjustments. One change at a time:
187
- - "Make the lighting warmer"
188
- - "More contrast in the shadows"
189
- - "Zoom out a bit"
190
- - "More serious expression"
191
-
192
- ## Critical Best Practices
193
-
194
- 1. **Be hyper-specific** — Generic descriptions produce generic images. Replace "fantasy armor" with "ornate elven plate armor, etched with silver leaf patterns, high collar, falcon-wing pauldrons."
195
-
196
- 2. **Frame positively** — The model handles positive instructions better than negations. Say "empty deserted street with no traffic, completely still" instead of "street without cars." Describe what IS there, not what isn't.
197
-
198
- 3. **Use photography language** — Camera terms give precise control: "85mm portrait lens", "wide-angle", "macro", "Dutch angle", "low-angle looking up", "bird's eye view."
199
-
200
- 4. **Specify lighting explicitly** — Lighting is the single biggest quality lever. Always include it. "Golden hour", "overcast diffused", "dramatic Rembrandt", "neon rim-lit", "three-point studio."
201
-
202
- 5. **Include context and intent** — "Logo for a high-end minimalist skincare brand" produces better results than just "make a logo." The model uses context to make style decisions.
203
-
204
- 6. **One concept per generation** — Complex multi-scene compositions work less reliably. Break them into separate generations and compose with editing tools.
205
-
206
- 7. **Character consistency** — If features drift after multiple edits, restart the conversation with a detailed character description upfront.
207
-
208
- 8. **Aspect ratio matters** — Choose deliberately based on final use. The model preserves input image ratio during editing. Be explicit if you need a specific ratio.
209
-
210
- ## Workflow
211
-
212
- When the user asks for an image:
213
-
214
- 1. **Understand intent** — What is this image for? What style/mood? What will it be used for?
215
- 2. **Ask clarifying questions if needed** — Use AskUserQuestion for ambiguous requests. Ask about style, mood, aspect ratio, specific elements.
216
- 3. **Build the prompt** — Apply the templates above. Be detailed and narrative.
217
- 4. **Choose the right tool** — `generate_image` for new, `edit_image` for modifying existing, `continue_editing` for iterating.
218
- 5. **Set `outputPath`** — If the user specifies where to save, use absolute paths. Otherwise let the MCP use its default directory.
219
- 6. **Execute and review** — After generation, read the image file to show the user the result.
220
- 7. **Iterate** — If the user wants changes, use `continue_editing` with targeted adjustment prompts.
221
-
222
- ## Design System Integration
223
-
224
- When generating images for a morph-spec project:
225
-
226
- 1. **Read design tokens first:**
227
- ```
228
- Read: .morph/context/design-system.md
229
- ```
230
- Extract: background color (`--color-background`), primary (`--color-primary`), accent (`--color-accent`), font pair, aesthetic direction.
231
-
232
- 2. **Inject tokens into prompts:**
233
- Replace generic color references with actual project values:
234
- - "dark background" → "dark charcoal background ({--color-background value})"
235
- - "accent colors" → "{--color-primary value} and {--color-accent value} accents"
236
- - "premium aesthetic" → "{aesthetic direction from design-system.md}"
237
-
238
- 3. **If `globals.css` exists**, read it for additional token values:
239
- ```
240
- Grep: "--color-" in src/app/globals.css (or similar)
241
- ```
242
-
243
- ## Assets.md Integration
244
-
245
- When invoked from the implement phase with an assets.md reference:
246
-
247
- 1. **Read the assets spec:**
248
- ```
249
- Read: .morph/features/{feature}/1-ui/assets.md
250
- ```
251
-
252
- 2. **For each IMG-NNN entry assigned to the current task:**
253
- - Use the `Prompt` field as the base prompt
254
- - Use the `Aspect Ratio` field for the aspectRatio parameter
255
- - Use the `Output Path` field for the outputPath parameter
256
- - Apply design system token injection (section above)
257
-
258
- 3. **Generate and verify:**
259
- ```
260
- mcp__gemini-image-mcp__generate_image({
261
- prompt: "{enhanced prompt with design tokens}",
262
- aspectRatio: "{from assets.md}",
263
- outputPath: "{from assets.md}"
264
- })
265
- ```
266
- Read the generated image to verify quality. If it needs refinement, use `continue_editing`.
267
-
268
- ## Output Path Conventions
269
-
270
- - **morph-spec projects:** `public/images/{feature}/{purpose}.png`
271
- - **Custom path:** Use `outputPath` parameter with absolute path
272
- - **Default:** saves to `generated-images/` in the MCP project root
273
- - Supported formats: PNG (most common), JPG, WEBP, GIF, BMP
274
-
275
- ## Model Options
276
-
277
- The default model is `gemini-2.5-flash-image`. Alternative models can be specified via the `model` parameter:
278
- - `gemini-2.5-flash-image` — Default, fastest, great quality
279
- - `gemini-3.1-flash-image-preview` — Newer, supports up to 14 reference images
280
- - `gemini-3-pro-image-preview` — Higher quality, slower
281
-
282
- ## Common Pitfalls to Avoid
283
-
284
- - **Keyword spam** — "beautiful, stunning, amazing, 4k, ultra HD, masterpiece" adds noise, not quality
285
- - **JSON prompts** — The model does not support structured output; natural language only
286
- - **Negative prompts with "no/don't"** — Frame what you want, not what you don't want
287
- - **Vague lighting** — "good lighting" means nothing; specify the type
288
- - **Missing context** — Always explain what the image is for (social media, website, print, etc.)
289
- - **Too many subjects** — Keep compositions focused; split complex scenes into steps
290
- - **Forgetting aspect ratio** — Always set it deliberately based on the intended use