@polymorphism-tech/morph-spec 9.8.0 → 10.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 (185) hide show
  1. package/CLAUDE.md +10 -10
  2. package/README.md +4 -4
  3. package/bin/morph-spec.js +40 -43
  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 +24 -30
  8. package/framework/MORPH.md +47 -123
  9. package/framework/agents.json +6 -374
  10. package/framework/commands/morph-apply.md +1 -1
  11. package/framework/commands/morph-archive.md +4 -3
  12. package/framework/commands/morph-hotfix.md +8 -30
  13. package/framework/commands/morph-infra.md +2 -2
  14. package/framework/commands/morph-media.md +10 -9
  15. package/framework/commands/morph-review.md +15 -0
  16. package/framework/commands/morph-status.md +1 -1
  17. package/framework/docs/CLI.md +83 -94
  18. package/framework/evals/review-checklist.md +45 -53
  19. package/framework/hooks/README.md +4 -4
  20. package/framework/hooks/claude-code/core/README.md +1 -1
  21. package/framework/hooks/claude-code/core/denylist.json +4 -4
  22. package/framework/hooks/claude-code/core/gate-guard.js +9 -0
  23. package/framework/hooks/claude-code/core/masked-exit.js +49 -0
  24. package/framework/hooks/claude-code/core/state-sync.js +4 -1
  25. package/framework/hooks/claude-code/core/telemetry-log.js +1 -2
  26. package/framework/hooks/claude-code/core/test-budget.js +10 -70
  27. package/framework/hooks/claude-code/core/validate-completion.js +11 -24
  28. package/framework/hooks/claude-code/core/worktree-context.js +5 -22
  29. package/framework/hooks/claude-code/core/worktree-create.js +6 -45
  30. package/framework/hooks/claude-code/core/worktree-env-inject.js +2 -10
  31. package/framework/hooks/shared/e2e-check.js +0 -0
  32. package/framework/hooks/shared/gate-event.js +2 -2
  33. package/framework/hooks/shared/graph-refresh.js +20 -3
  34. package/framework/hooks/shared/heavy-command.js +32 -1
  35. package/framework/hooks/shared/heavy-config.js +1 -1
  36. package/framework/hooks/shared/next-step.js +63 -24
  37. package/framework/hooks/shared/phase-utils.js +6 -9
  38. package/framework/hooks/shared/report-progress.js +86 -0
  39. package/framework/hooks/shared/state-reader.js +3 -2
  40. package/framework/hooks/statusline.js +332 -0
  41. package/framework/phases.json +32 -10
  42. package/framework/rules/ai-agents-standards.md +9 -1
  43. package/framework/rules/csharp-standards.md +4 -0
  44. package/framework/rules/ef-standards.md +20 -0
  45. package/framework/rules/frontend-standards.md +1 -1
  46. package/framework/rules/implement-report.md +25 -0
  47. package/framework/rules/infrastructure-standards.md +2 -0
  48. package/framework/rules/nextjs-standards.md +7 -0
  49. package/framework/rules/plan-grammar.md +53 -0
  50. package/framework/rules/ui-design.md +27 -0
  51. package/framework/schemas/config.schema.json +11 -18
  52. package/framework/schemas/feature.schema.json +7 -1
  53. package/framework/skills/core/create-umbraco-lp/SKILL.md +1 -1
  54. package/framework/skills/core/create-umbraco-lp/references/umbraco-lp-patterns.md +1 -1
  55. package/framework/skills/core/migrate-lp-to-umbraco/SKILL.md +2 -2
  56. package/framework/skills/core/morph-apply/SKILL.md +38 -166
  57. package/framework/skills/core/morph-apply/references/debugging.md +8 -0
  58. package/framework/skills/core/morph-apply/references/hotfix.md +42 -0
  59. package/framework/skills/core/morph-apply/references/machine.md +29 -0
  60. package/framework/skills/core/{morph-media/SKILL.md → morph-apply/references/media.md} +11 -74
  61. package/framework/skills/core/morph-apply/references/review.md +65 -0
  62. package/framework/skills/core/morph-plan/SKILL.md +42 -108
  63. package/framework/skills/core/morph-plan/references/critique.md +40 -0
  64. package/framework/skills/core/morph-plan/references/interview.md +25 -0
  65. package/framework/skills/core/morph-uiux/SKILL.md +47 -64
  66. package/framework/skills/core/morph-uiux/surfaces.json +32 -0
  67. package/framework/standards/STANDARDS.json +32 -5
  68. package/framework/standards/STANDARDS.meta.json +5 -1
  69. package/framework/standards/ai-agents/agent-session.md +2 -3
  70. package/framework/standards/ai-agents/context-providers.md +2 -2
  71. package/framework/standards/ai-agents/durable-workflows-hitl.md +2 -2
  72. package/framework/standards/ai-agents/providers/model-registry.md +2 -2
  73. package/framework/standards/ai-agents/rag-custom-pgvector.md +6 -6
  74. package/framework/standards/ai-agents/setup.md +1 -1
  75. package/framework/standards/ai-agents/tool-adapters.md +2 -2
  76. package/framework/standards/architecture/stack-baseline.md +47 -0
  77. package/framework/standards/backend/integrations/neon-auth/neon-auth.md +2 -4
  78. package/framework/standards/frontend/design-system/ai-image-generation.md +51 -12
  79. package/framework/standards/frontend/design-system/ai-video-generation.md +50 -4
  80. package/framework/standards/frontend/design-system/art-direction.md +2 -2
  81. package/framework/standards/frontend/scroll-driven/frame-scrub.md +12 -2
  82. package/framework/standards/frontend/scroll-driven/scroll-components.md +76 -4
  83. package/framework/templates/REGISTRY.json +1 -2
  84. package/framework/templates/context/ARCHITECTURE.md +1 -1
  85. package/framework/templates/docs/plan.md +12 -12
  86. package/framework/templates/docs/ui-mockups.md +17 -105
  87. package/package.json +1 -1
  88. package/src/commands/e2e/e2e.js +88 -23
  89. package/src/commands/graph/diff.js +74 -0
  90. package/src/commands/graph/status.js +1 -1
  91. package/src/commands/observability/cost.js +2 -4
  92. package/src/commands/observability/telemetry.js +1 -1
  93. package/src/commands/project/doctor.js +23 -39
  94. package/src/commands/project/fleet.js +5 -6
  95. package/src/commands/project/init.js +63 -36
  96. package/src/commands/project/stack-scan.js +3 -42
  97. package/src/commands/project/stack-set.js +26 -0
  98. package/src/commands/project/status.js +12 -13
  99. package/src/commands/project/update.js +39 -3
  100. package/src/commands/project/worktree.js +3 -14
  101. package/src/commands/state/advance.js +17 -17
  102. package/src/commands/state/approve.js +0 -6
  103. package/src/commands/state/archive.js +27 -1
  104. package/src/commands/state/auto-approval.js +1 -1
  105. package/src/commands/state/create.js +0 -5
  106. package/src/commands/state/gate-check.js +37 -30
  107. package/src/commands/state/gate-decision.js +3 -2
  108. package/src/commands/state/next.js +1 -0
  109. package/src/commands/state/recap.js +67 -0
  110. package/src/commands/state/report-check.js +36 -178
  111. package/src/commands/validation/plan-check.js +16 -11
  112. package/src/commands/validation/validate-feature.js +3 -3
  113. package/src/commands/validation/verify.js +3 -6
  114. package/src/core/paths/output-schema.js +3 -3
  115. package/src/core/state/state-manager.js +2 -4
  116. package/src/dashboard/app/components/CommandPalette.jsx +2 -5
  117. package/src/dashboard/app/components/GatePauses.jsx +116 -0
  118. package/src/dashboard/app/components/SessionMap.jsx +77 -0
  119. package/src/dashboard/app/pages/Activity.jsx +4 -0
  120. package/src/dashboard/app/pages/AgentMap.jsx +1 -2
  121. package/src/dashboard/app/pages/FeatureDetail.jsx +129 -186
  122. package/src/dashboard/app/pages/Features.jsx +7 -7
  123. package/src/dashboard/app/pages/PhasePipeline.jsx +1 -1
  124. package/src/dashboard/app/pages/Portfolio.jsx +40 -5
  125. package/src/dashboard/scanner/activity-scanner.js +29 -0
  126. package/src/dashboard/scanner/agents-scanner.js +11 -14
  127. package/src/dashboard/scanner/features-scanner.js +45 -24
  128. package/src/dashboard/scanner/project-api.js +11 -2
  129. package/src/dashboard/scanner/skills-scanner.js +11 -1
  130. package/src/lib/e2e/applicability.js +3 -1
  131. package/src/lib/git/tree-state.js +1 -16
  132. package/src/lib/graph/diff-tree.js +127 -0
  133. package/src/lib/graph/graphify-runtime.js +12 -5
  134. package/src/lib/installers/memory-installer.js +2 -2
  135. package/src/lib/installers/rules-installer.js +57 -0
  136. package/src/lib/installers/stack-assets.js +17 -0
  137. package/src/lib/installers/stack-sync.js +219 -0
  138. package/src/lib/plan/checks.js +55 -110
  139. package/src/lib/plan/critique.js +28 -0
  140. package/src/lib/plan/media.js +42 -0
  141. package/src/lib/plan/plan-html.js +125 -0
  142. package/src/lib/plan/plan-parser.js +34 -45
  143. package/src/lib/recap/piper.js +36 -0
  144. package/src/lib/recap/recap.js +232 -0
  145. package/src/lib/review-reports.js +28 -4
  146. package/src/lib/risk-detector.js +90 -11
  147. package/src/lib/specs/screen-checklist.js +33 -48
  148. package/src/lib/stack/dotnet-build-ignores.js +8 -9
  149. package/src/lib/telemetry/metrics.js +27 -29
  150. package/src/lib/validators/reuse/reuse-validator.js +1 -9
  151. package/src/lib/validators/verify-runner.js +0 -7
  152. package/src/lib/worktree/fleet-scanner.js +7 -20
  153. package/src/lib/worktree/worktree-audit.js +0 -37
  154. package/src/scripts/setup-infra.js +15 -50
  155. package/src/utils/agents-installer.js +27 -201
  156. package/src/utils/hooks-installer.js +11 -95
  157. package/framework/hooks/claude-code/core/warn-bash-source-write.js +0 -23
  158. package/framework/hooks/claude-code/statusline.py +0 -699
  159. package/framework/hooks/claude-code/statusline.sh +0 -11
  160. package/framework/hooks/shared/agent-reap.js +0 -74
  161. package/framework/hooks/shared/slice-progress.js +0 -110
  162. package/framework/skills/core/morph-hotfix/SKILL.md +0 -133
  163. package/framework/skills/core/morph-machine/SKILL.md +0 -46
  164. package/framework/skills/core/morph-review/SKILL.md +0 -129
  165. package/framework/skills/core/morph-standards/SKILL.md +0 -52
  166. package/framework/standards/PROVENANCE.json +0 -119
  167. package/src/commands/project/worktree-reap.js +0 -34
  168. package/src/commands/state/handoff.js +0 -65
  169. package/src/commands/validation/mutate.js +0 -263
  170. package/src/commands/validation/scout-check.js +0 -28
  171. package/src/lib/generators/handoff-generator.js +0 -172
  172. package/src/lib/mutation/adapters.js +0 -79
  173. package/src/lib/mutation/commands.js +0 -61
  174. package/src/lib/mutation/dotnet-introspection.js +0 -281
  175. package/src/lib/mutation/manifest.js +0 -144
  176. package/src/lib/mutation/plan.js +0 -44
  177. package/src/lib/mutation/report.js +0 -68
  178. package/src/lib/mutation/runner.js +0 -301
  179. package/src/lib/mutation/schemata.js +0 -60
  180. package/src/lib/mutation/snapshot.js +0 -65
  181. package/src/lib/mutation/subset.js +0 -109
  182. package/src/lib/mutation/trx.js +0 -50
  183. package/src/lib/scout/scout-check.js +0 -293
  184. package/src/lib/validators/traceability-check.js +0 -103
  185. package/src/lib/worktree/agent-reaper.js +0 -247
@@ -0,0 +1,332 @@
1
+ #!/usr/bin/env node
2
+ // The payload is the source of truth: what Claude Code already computes (context, rate limits, worktree, PR) is
3
+ // read, never recomputed. The feature line comes from the project's own installed rules, imported at runtime,
4
+ // so this file carries no copy of them. Stateless: it never writes to disk.
5
+
6
+ import { execFileSync } from 'node:child_process';
7
+ import { existsSync, readFileSync, readdirSync, realpathSync } from 'node:fs';
8
+ import { homedir } from 'node:os';
9
+ import { join } from 'node:path';
10
+ import { pathToFileURL } from 'node:url';
11
+
12
+ const R = '\x1b[0m';
13
+ const BOLD = '\x1b[1m';
14
+ const CYAN = '\x1b[36m';
15
+ const GREEN = '\x1b[32m';
16
+ const YELLOW = '\x1b[33m';
17
+ const RED = '\x1b[31m';
18
+ const BLUE = '\x1b[34m';
19
+ const GRAY = '\x1b[90m';
20
+ const WHITE = '\x1b[97m';
21
+ const SEP = ` ${GRAY}│${R} `;
22
+
23
+ const PHASE_INDEX = { plan: 0, uiux: 1, implement: 2, review: 3 };
24
+ const PHASE_LABELS = { plan: 'planning', uiux: 'UI/UX', implement: 'implement', review: 'review' };
25
+ const GATE_PHASES = new Set(['plan', 'uiux', 'review']);
26
+
27
+ // Mirrors claude.exe: auto-compact fires at min(modelWindow, autoCompactWindow) − 20000 output reserve − 13000.
28
+ // Reproducible only when `autoCompactWindow` is set in settings; any other source is server-driven.
29
+ const COMPACT_OUTPUT_RESERVE = 20000;
30
+ const COMPACT_HEADROOM = 13000;
31
+
32
+ const int = (v, d = 0) => (Number.isFinite(Number(v)) && v !== null && v !== '' ? Math.trunc(Number(v)) : d);
33
+ const num = (v, d = 0) => (Number.isFinite(Number(v)) && v !== null && v !== '' ? Number(v) : d);
34
+
35
+ export function usageColor(pct) {
36
+ if (pct < 70) return GREEN;
37
+ return pct < 90 ? YELLOW : RED;
38
+ }
39
+
40
+ const EIGHTHS = '▏▎▍▌▋▊▉';
41
+
42
+ // Eighth blocks keep a small nonzero value visible on a 5-wide gauge.
43
+ export function progressBar(pct, width = 8) {
44
+ const exact = (Math.max(0, Math.min(100, pct)) / 100) * width;
45
+ const filled = Math.floor(exact);
46
+ let bar = '█'.repeat(filled);
47
+ if (filled < width) {
48
+ const remainder = Math.floor((exact - filled) * 8);
49
+ if (remainder) bar += EIGHTHS[remainder - 1];
50
+ }
51
+ return bar + '░'.repeat(width - [...bar].length);
52
+ }
53
+
54
+ export function formatTokens(n) {
55
+ if (n >= 1_000_000) return `${(n / 1_000_000).toFixed(1).replace(/\.0$/, '')}M`;
56
+ if (n >= 1000) return `${Math.floor(n / 1000)}k`;
57
+ return String(n);
58
+ }
59
+
60
+ export function formatCountdown(epoch, now = Date.now() / 1000) {
61
+ const remaining = epoch - now;
62
+ if (remaining <= 0) return null;
63
+ const days = Math.floor(remaining / 86400);
64
+ const rest = Math.floor(remaining % 86400);
65
+ const hours = Math.floor(rest / 3600);
66
+ const minutes = Math.floor((rest % 3600) / 60);
67
+ if (days) return `${days}d${String(hours).padStart(2, '0')}`;
68
+ if (hours) return `${hours}h${String(minutes).padStart(2, '0')}`;
69
+ return `${minutes}m`;
70
+ }
71
+
72
+ // OSC included: an OSC 8 hyperlink carries its URL as an invisible payload.
73
+ const ANSI_RE = /\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)|\x1b\[[0-9;]*m/g;
74
+ // ⏳ renders double (Emoji_Presentation) while the gauge glyphs render single.
75
+ const WIDE_SINGLES = new Set([...'⚡⛔⬛⬜✅❌⏳']);
76
+
77
+ export function visibleWidth(text) {
78
+ let width = 0;
79
+ for (const ch of text.replace(ANSI_RE, '')) {
80
+ const code = ch.codePointAt(0);
81
+ width += (code >= 0x1f300 && code <= 0x1faff) || WIDE_SINGLES.has(ch) ? 2 : 1;
82
+ }
83
+ return width;
84
+ }
85
+
86
+ /** Joins `[priority, text]` parts with SEP, dropping the highest priority number until it fits `columns`. */
87
+ export function joinToWidth(parts, columns) {
88
+ const kept = parts.filter(([, text]) => text);
89
+ if (!kept.length) return '';
90
+ const sep = visibleWidth(SEP);
91
+ const total = () => kept.reduce((sum, [, text]) => sum + visibleWidth(text), 0) + sep * Math.max(0, kept.length - 1);
92
+ if (columns) {
93
+ while (kept.length > 1 && total() > columns) {
94
+ let worst = 0;
95
+ kept.forEach(([p], i) => { if (p > kept[worst][0]) worst = i; });
96
+ kept.splice(worst, 1);
97
+ }
98
+ }
99
+ return kept.map(([, text]) => text).join(SEP);
100
+ }
101
+
102
+ function readUserSettings(env = process.env) {
103
+ try {
104
+ const base = env.CLAUDE_CONFIG_DIR || join(homedir(), '.claude');
105
+ const data = JSON.parse(readFileSync(join(base, 'settings.json'), 'utf8'));
106
+ return data && typeof data === 'object' ? data : {};
107
+ } catch {
108
+ return {};
109
+ }
110
+ }
111
+
112
+ function git(args, cwd) {
113
+ return execFileSync('git', args, { cwd, stdio: ['ignore', 'pipe', 'ignore'], timeout: 3000, windowsHide: true }).toString('utf8').trim();
114
+ }
115
+
116
+ // `-uno`: porcelain v2 lists untracked entries by default, which would change what the file count means.
117
+ function gitInfo(cwd) {
118
+ let out;
119
+ try { out = git(['status', '--porcelain=v2', '--branch', '-uno'], cwd); } catch { return ''; }
120
+ let branch = '';
121
+ let ahead = 0;
122
+ let behind = 0;
123
+ let files = 0;
124
+ for (const line of out.split(/\r?\n/)) {
125
+ if (line.startsWith('# branch.head ')) branch = line.slice(14).trim();
126
+ else if (line.startsWith('# branch.ab ')) {
127
+ const m = /# branch\.ab \+(\d+) -(\d+)/.exec(line);
128
+ if (m) { ahead = Number(m[1]); behind = Number(m[2]); }
129
+ } else if ('12u'.includes(line[0] ?? 'x')) files += 1;
130
+ }
131
+ if (!branch) return '';
132
+ let ins = 0;
133
+ let dels = 0;
134
+ if (files) {
135
+ try {
136
+ const stat = git(['diff', 'HEAD', '--shortstat'], cwd);
137
+ ins = Number(/(\d+) insertion/.exec(stat)?.[1] ?? 0);
138
+ dels = Number(/(\d+) deletion/.exec(stat)?.[1] ?? 0);
139
+ } catch { /* no diff stats */ }
140
+ }
141
+ const parts = [`${BLUE} ${branch}${R}`];
142
+ if (ahead || behind) parts.push(`${ahead ? `${GREEN}↑${ahead}${R}` : ''}${behind ? `${RED}↓${behind}${R}` : ''}`);
143
+ if (ins || dels) parts.push(`${GREEN}+${ins}${RED}-${dels}${R}`);
144
+ return parts.join(' ');
145
+ }
146
+
147
+ const samePath = (a, b) => {
148
+ try { return realpathSync(a).toLowerCase() === realpathSync(b).toLowerCase(); } catch { return false; }
149
+ };
150
+
151
+ /** Worktree name of this session, or null in the primary tree. */
152
+ export function worktreeLabel(payload, cwd) {
153
+ if (payload.worktree?.name) return (payload.worktree.branch || '').replace('refs/heads/', '') || payload.worktree.name;
154
+ if (payload.workspace?.git_worktree) return payload.workspace.git_worktree;
155
+ if (!existsSync(join(cwd, '.morph', 'features'))) return null;
156
+ let out;
157
+ try { out = git(['worktree', 'list', '--porcelain'], cwd); } catch { return null; }
158
+ const entries = [];
159
+ let current = null;
160
+ for (const line of out.split(/\r?\n/)) {
161
+ if (line.startsWith('worktree ')) { current = { path: line.slice(9) }; entries.push(current); }
162
+ else if (line.startsWith('branch ') && current) current.branch = line.slice(7);
163
+ }
164
+ const mine = entries.slice(1).find((e) => samePath(e.path, cwd));
165
+ return mine ? (mine.branch || '').replace('refs/heads/', '') || null : null;
166
+ }
167
+
168
+ async function importShared(projectDir, name) {
169
+ const path = join(projectDir, '.morph', 'framework', 'hooks', 'shared', name);
170
+ return existsSync(path) ? import(pathToFileURL(path).href) : null;
171
+ }
172
+
173
+ /** In-progress features with phase, scenario progress and the open gate, read through the project's own rules. */
174
+ export async function activeFeatures(projectDir) {
175
+ const base = join(projectDir, '.morph', 'features');
176
+ if (!existsSync(base)) return [];
177
+ let stateReader;
178
+ let progressRules;
179
+ try {
180
+ [stateReader, progressRules] = await Promise.all([importShared(projectDir, 'state-reader.js'), importShared(projectDir, 'report-progress.js')]);
181
+ } catch {
182
+ return [];
183
+ }
184
+ if (!stateReader?.derivePhaseForFeature) return [];
185
+ const out = [];
186
+ for (const name of readdirSync(base).sort()) {
187
+ let feature;
188
+ try { feature = JSON.parse(readFileSync(join(base, name, 'feature.json'), 'utf8').replace(/^/, '')); } catch { continue; }
189
+ if (feature?.status !== 'in_progress') continue;
190
+ let phase = 'plan';
191
+ try { phase = stateReader.derivePhaseForFeature(name, projectDir) || 'plan'; } catch { /* plan */ }
192
+ const progress = progressRules?.implementProgress?.(join(base, name)) ?? null;
193
+ const gates = feature.approvalGates || {};
194
+ out.push({
195
+ name,
196
+ phase,
197
+ done: progress?.done ?? 0,
198
+ total: progress?.total ?? 0,
199
+ pending: GATE_PHASES.has(phase) && gates[phase]?.approved !== true ? phase : null,
200
+ });
201
+ }
202
+ return out;
203
+ }
204
+
205
+ export function renderFeatureLine(feature, columns) {
206
+ const parts = [[0, `${CYAN}${BOLD}► ${feature.name}${R}`]];
207
+ const position = PHASE_INDEX[feature.phase];
208
+ const label = PHASE_LABELS[feature.phase] ?? feature.phase;
209
+ parts.push([1, position === undefined ? `${CYAN}${label}${R}` : `${CYAN}${label} ${position + 1}/4${R}`]);
210
+ if (feature.total > 0) {
211
+ parts.push([3, `${GREEN}${progressBar((feature.done / feature.total) * 100, 6)} ${feature.done}/${feature.total} cenários${R}`]);
212
+ }
213
+ if (feature.pending) parts.push([2, `${RED}⛔ gate: ${feature.pending}${R}`]);
214
+ return joinToWidth(parts, columns);
215
+ }
216
+
217
+ /** The window /context divides by — not the model window — or null when not reproducible. */
218
+ export function effectiveWindow(payload, settings) {
219
+ const modelWindow = int(payload.context_window?.context_window_size);
220
+ if (modelWindow <= 0) return null;
221
+ if (settings.autoCompactEnabled === false) return modelWindow;
222
+ const configured = int(settings.autoCompactWindow);
223
+ return configured > 0 ? Math.min(modelWindow, configured) : null;
224
+ }
225
+
226
+ export function compactThreshold(payload, settings) {
227
+ if (settings.autoCompactEnabled === false || int(settings.autoCompactWindow) <= 0) return null;
228
+ const window = effectiveWindow(payload, settings);
229
+ if (!window) return null;
230
+ const threshold = window - COMPACT_OUTPUT_RESERVE - COMPACT_HEADROOM;
231
+ return threshold > 0 ? threshold : null;
232
+ }
233
+
234
+ export function renderStatusLine(payload, cwd, settings, columns, { gitLine = gitInfo, now } = {}) {
235
+ const parts = [];
236
+ const model = payload.model;
237
+ const modelName = model && typeof model === 'object' ? model.display_name || model.id || '' : String(model || '');
238
+ if (modelName) {
239
+ const short = modelName.replace('Claude ', '').replace('claude-', '').replace(' (1M context)', ' 1M').replace(' (claude.ai)', '');
240
+ const state = [];
241
+ if (payload.effort?.level) state.push(payload.effort.level);
242
+ if (payload.thinking?.enabled === false) state.push('no-think');
243
+ if (payload.fast_mode) state.push('fast');
244
+ parts.push([3, `${WHITE}${short}${R}${state.length ? `${GRAY}·${state.join('·')}${R}` : ''}`]);
245
+ }
246
+
247
+ const context = payload.context_window;
248
+ if (context && typeof context === 'object' && Object.keys(context).length) {
249
+ const modelWindow = int(context.context_window_size);
250
+ let tokens = int(context.total_input_tokens);
251
+ // Null right after /compact: the app's own input-only sum; output tokens are not part of it.
252
+ if (!tokens && context.current_usage && typeof context.current_usage === 'object') {
253
+ const u = context.current_usage;
254
+ tokens = int(u.input_tokens) + int(u.cache_creation_input_tokens) + int(u.cache_read_input_tokens);
255
+ }
256
+ const window = effectiveWindow(payload, settings);
257
+ let pct = null;
258
+ if (window && tokens) pct = (tokens / window) * 100;
259
+ else if (context.used_percentage !== undefined && context.used_percentage !== null) pct = num(context.used_percentage);
260
+ else if (modelWindow && tokens) pct = (tokens / modelWindow) * 100;
261
+ if (pct !== null) {
262
+ let chunk = `${usageColor(pct)}${progressBar(pct, 8)} ${pct.toFixed(0)}%${R}`;
263
+ const threshold = compactThreshold(payload, settings);
264
+ if (threshold && tokens) {
265
+ const left = threshold - tokens;
266
+ if (left <= 0) chunk += ` ${RED}⚠ compactando${R}`;
267
+ else {
268
+ const leftPct = (left / threshold) * 100;
269
+ chunk += ` ${leftPct > 25 ? GRAY : leftPct > 10 ? YELLOW : RED}↻ ${formatTokens(left)}${R}`;
270
+ }
271
+ }
272
+ parts.push([0, chunk]);
273
+ }
274
+ }
275
+
276
+ const limits = payload.rate_limits || {};
277
+ for (const [key, label, priority, countdown] of [['five_hour', '⏳', 2, true], ['seven_day', '📅', 5, false]]) {
278
+ const raw = limits[key]?.used_percentage;
279
+ if (raw === undefined || raw === null) continue;
280
+ const pct = num(raw);
281
+ let chunk = `${GRAY}${label}${R} ${usageColor(pct)}${pct.toFixed(0)}%${R}`;
282
+ const until = countdown && limits[key]?.resets_at ? formatCountdown(num(limits[key].resets_at), now) : null;
283
+ if (until) chunk += ` ${GRAY}↻ ${until}${R}`;
284
+ parts.push([priority, chunk]);
285
+ }
286
+
287
+ const branch = gitLine(cwd);
288
+ if (branch) parts.push([1, branch]);
289
+
290
+ const pr = payload.pr || {};
291
+ if (pr.number) {
292
+ const mark = { approved: '✓', changes_requested: '✗', draft: '·', pending: '…' }[pr.review_state] ?? '';
293
+ const color = { approved: GREEN, changes_requested: RED, draft: GRAY }[pr.review_state] ?? YELLOW;
294
+ let label = `PR #${pr.number} ${mark}`.trim();
295
+ if (pr.url) label = `\x1b]8;;${pr.url}\x1b\\${label}\x1b]8;;\x1b\\`;
296
+ parts.push([4, `${color}${label}${R}`]);
297
+ }
298
+ return joinToWidth(parts, columns);
299
+ }
300
+
301
+ async function readStdin() {
302
+ let raw = '';
303
+ for await (const chunk of process.stdin) raw += chunk;
304
+ return raw;
305
+ }
306
+
307
+ export async function main(raw, env = process.env, write = (s) => process.stdout.write(`${s}\n`)) {
308
+ let payload;
309
+ try {
310
+ payload = JSON.parse(raw);
311
+ } catch {
312
+ return;
313
+ }
314
+ if (!payload || typeof payload !== 'object') return;
315
+ try {
316
+ const cwd = payload.workspace?.current_dir || payload.cwd || process.cwd();
317
+ const projectDir = payload.workspace?.project_dir || cwd;
318
+ const columns = int(env.COLUMNS);
319
+ // Only in a secondary worktree: in the primary tree the line resurrects stale feature names.
320
+ if (worktreeLabel(payload, cwd)) {
321
+ for (const feature of await activeFeatures(projectDir)) write(renderFeatureLine(feature, columns));
322
+ }
323
+ const line = renderStatusLine(payload, cwd, readUserSettings(env), columns);
324
+ if (line) write(line);
325
+ } catch (err) {
326
+ write(`${RED}ERRO: ${err?.message ?? err}${R}`);
327
+ }
328
+ }
329
+
330
+ if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
331
+ main(await readStdin());
332
+ }
@@ -11,10 +11,16 @@
11
11
  "Write plan.md — scenarios with proof, decisions, reuse and slices",
12
12
  "Run plan-check to produce checks.json and reach the plan gate"
13
13
  ],
14
- "requiredOutputs": ["plan", "checks"],
14
+ "requiredOutputs": [
15
+ "plan",
16
+ "checks"
17
+ ],
15
18
  "optionalOutputs": [],
16
19
  "pausePoints": [
17
- { "id": "plan", "label": "Gate do plano — Approve plan.md + checks.json" }
20
+ {
21
+ "id": "plan",
22
+ "label": "Gate do plano — Approve plan.md + checks.json"
23
+ }
18
24
  ],
19
25
  "skill": "morph-plan"
20
26
  },
@@ -28,10 +34,15 @@
28
34
  "Define design system tokens and components",
29
35
  "Produce mockups and user flows"
30
36
  ],
31
- "requiredOutputs": ["uiDesignSystem"],
32
- "optionalOutputs": ["uiMockups", "uiComponents", "uiFlows"],
37
+ "requiredOutputs": [],
38
+ "optionalOutputs": [
39
+ "uiMockups"
40
+ ],
33
41
  "pausePoints": [
34
- { "id": "uiux", "label": "Approve UI/UX" }
42
+ {
43
+ "id": "uiux",
44
+ "label": "Approve UI/UX"
45
+ }
35
46
  ],
36
47
  "skill": "morph-uiux"
37
48
  },
@@ -46,7 +57,10 @@
46
57
  "One report per slice (3-implement/reports/F{n}.md, with **Arquivos:** and **Standards:**) — the implementation record"
47
58
  ],
48
59
  "requiredOutputs": [],
49
- "optionalOutputs": ["evidencias"],
60
+ "optionalOutputs": [
61
+ "implementReport",
62
+ "evidencias"
63
+ ],
50
64
  "pausePoints": [],
51
65
  "skill": "morph-apply"
52
66
  },
@@ -59,12 +73,20 @@
59
73
  "Verifier proves every scenario; reviewer (and security, when required) judge the diff at a fixed SHA",
60
74
  "verifier.md + reviewer.md (+ security.md) with SHIP/FIX and evidence-typed blockers, then Gate 3"
61
75
  ],
62
- "requiredOutputs": ["verifierReport", "reviewerReport"],
63
- "optionalOutputs": ["securityReport"],
76
+ "requiredOutputs": [
77
+ "verifierReport",
78
+ "reviewerReport"
79
+ ],
80
+ "optionalOutputs": [
81
+ "securityReport"
82
+ ],
64
83
  "pausePoints": [
65
- { "id": "review", "label": "Gate 3 — Approve the review reports" }
84
+ {
85
+ "id": "review",
86
+ "label": "Gate 3 — Approve the review reports"
87
+ }
66
88
  ],
67
- "skill": "morph-review"
89
+ "skill": "morph-apply"
68
90
  }
69
91
  }
70
92
  }
@@ -9,6 +9,14 @@ stacks:
9
9
 
10
10
  # Microsoft Agent Framework Standards
11
11
 
12
+ **Ponto de vista:** especialista no Microsoft Agent Framework (MAF 1.0 GA).
13
+ - Sweet-spot primeiro: single-agent + structured output + tools + RAG custom resolvem 95%. Semantic Kernel e Workflows só com fan-in/out genuíno.
14
+ - Alias do Model Registry, nunca modelo cravado; providers diretos (OpenAI/Google/Anthropic/Ollama), nunca hosted agent service. Para OpenAI o padrão é a Responses API; alias sem `api` significa `responses`, Chat Completions é opt-in declarado.
15
+ - Telemetria por `UseOpenTelemetry(sourceName, configure)` no builder, nos DOIS níveis (agente e chat client), `EnableSensitiveData` desligado fora de dev. `AgentOpenTelemetryConsts` e `agent.WithOpenTelemetry()` NÃO existem no pin; nomes de ActivitySource, span e métrica vêm de `ai-agents-observability-patterns`, nunca deduzidos.
16
+ - Histórico de conversa: tabela/jsonb do projeto exposta por um `ChatHistoryProvider` seu (`store: false`), nunca o `AgentSession` — ele só serve quando o histórico é SÓ a lista de mensagens, sem campo de negócio nem leitura fora do agente.
17
+ - Instruções e tools por fase: compositor determinístico em C# (um record Instructions + Tools escolhido por switch antes de montar `ChatOptions`), nem context provider nem tool — `AIContext.Tools` só ACRESCENTA tools.
18
+ - A lista abaixo é o ponto de partida: antes de concluir que um assunto não tem standard, `morph-spec standards --find "<tema>" --category ai-agents`.
19
+
12
20
  > **Layer 0/1 — resumo injetado pela rule.** São os standards core: aplicam-se a praticamente todo
13
21
  > agente MAF. O resumo de cada um entra no contexto quando você toca código de agentes.
14
22
 
@@ -30,7 +38,7 @@ stacks:
30
38
 
31
39
  > **Layer 2 — sob demanda (sem resumo aqui).** São standards de modalidade, otimização e
32
40
  > MCP: relevantes apenas quando a feature usa especificamente esse recurso. Busque via
33
- > `morph-spec standards --find` (skill `morph-standards`) quando uma keyword corresponder — manter fora do resumo preserva o
41
+ > `morph-spec standards --find` quando uma keyword corresponder — manter fora do resumo preserva o
34
42
  > context window para o trabalho que importa.
35
43
 
36
44
  | Standard | Carregue quando a feature envolve |
@@ -8,6 +8,8 @@ stacks:
8
8
 
9
9
  # C# and .NET VSA Standards
10
10
 
11
+ **Ponto de vista:** engenheiro .NET sênior em VSA. Handler direto com `ApplicationDbContext` injetado no slice (`DbSet<T>` já é repository, o `DbContext` já é unit of work); concorrência ou SQL específico → store dedicado escrito à mão, com ADR no `plan.md`. Nunca camada Service/Manager, nunca `IRepository<T>`/`IUnitOfWork`, nunca exceção como fluxo. Abra o standard antes de escrever cada slice.
12
+
11
13
  - **architecture-vertical-slice-vertical-slice** (`architecture/vertical-slice/vertical-slice.md`) — Uma slice por operação em `Features/{Entity}Feature/{Verb}{Entity}/`: Request/Response `record`, Handler, Validator e Endpoint `IApiEndpoint`. Decorators Scrutor: Logging → Validation → Handler. PROIBIDO: MediatR/`IRequest`, AggregateRoot, DomainEvent, CQRS formal, camada Application Service, DTO compartilhado entre features, `IRepository<T>`/`IUnitOfWork`.
12
14
  - **backend-dotnet-vsa-handler-patterns** (`backend/dotnet/vsa-handler-patterns.md`) — Handler implementando `IHandler<TRequest, TResponse>` (não MediatR): injeta `ApplicationDbContext` direto e devolve `Result<T>`. Erros em `{Feature}Errors.cs`. SQL cru ou store dedicado só com justificativa em `plan.md` (`## Decisões`).
13
15
  - **backend-dotnet-result-pattern** (`backend/dotnet/result-pattern.md`) — Falha esperada é `Result`/`Result<T>`, nunca exceção. Endpoint: `result.Match(value => Results.Ok(value), error => error.ToHttpResult())`; nunca leia `.Value` fora do `Match`. Status pelo `ErrorType` (Validation 400, NotFound 404, Conflict 409, Unauthorized 401, Forbidden 403, Unexpected 500), nunca BadRequest para tudo. PROIBIDO: try/catch no handler, retornar null, erro string sem código.
@@ -19,4 +21,6 @@ stacks:
19
21
  - **architecture-code-comments** (`architecture/code-comments.md`) — Padrão é nenhum comentário: renomeie, extraia, tipe ou teste antes. Só o porquê que o código não mostra (invariante, ordem, quirk externo com link), até ~3 linhas. PROIBIDO: história de processo (task, UC, ADR, Gate), repetir o código, XML doc/JSDoc que repete a assinatura, banner, `#region`, código comentado, TODO solto. O hook `comment-guard` bloqueia.
20
22
  - **architecture-engineering-lens** (`architecture/engineering-lens.md`) — Escreva para quem herda amanhã: convenção do projeto, o mais simples, causa raiz. PROIBIDO no que você adiciona: `catch` que engole, flag booleana que bifurca o método (são dois métodos), efeito colateral escondido, símbolo/flag/config sem consumidor, abstração ou camada sem segundo uso, literal repetido sem constante. Nomes de domínio buscáveis, funções pequenas, ≤ 3 parâmetros, condicional positiva. Commit em Conventional Commits (hook `conventional-commit`).
21
23
 
24
+ - **backend-dotnet-testing** (`backend/dotnet/testing.md`) — Injete `TimeProvider` (nunca `DateTime.UtcNow` direto em handler ou validator) e use `FakeTimeProvider` no teste de integração. Teste por risco: um por cenário com `risco:`, a pós-condição no nome do teste.
25
+
22
26
  > Resumo injetado quando você lê ou edita um arquivo casado por `paths:`. Texto completo: `morph-spec standards --find "<id>"` e leia só a seção necessária de `.morph/framework/standards/<path>`.
@@ -0,0 +1,20 @@
1
+ ---
2
+ paths:
3
+ - "**/Migrations/**"
4
+ - "**/*DbContext*.cs"
5
+ - "**/*Configuration.cs"
6
+ - "**/Entities/**"
7
+ stacks:
8
+ - dotnet
9
+ ---
10
+
11
+ # EF Core + Neon Standards
12
+
13
+ **Ponto de vista:** especialista em EF Core sobre Neon (Postgres). Migrations explícitas, configuração fluente, navigation properties bem nomeadas, índice intencional. Nunca vaze o `DbContext` para fora do handler. Entidade duplicada vira migration divergente: confira a tabela `## Reuso` do plano antes de criar uma.
14
+
15
+ - **backend-database-postgresql-database** (`backend/database/postgresql/database.md`) — Tabelas snake_case no plural, colunas snake_case, PK `id uuid primary key` sem default na coluna: a app gera com `Guid.CreateVersion7()` (linha inserida fora da app passa o id explícito).
16
+ - **infrastructure-neon-setup** (`infrastructure/neon/neon-setup.md`) — Duas connection strings por `IConfiguration`: `DefaultConnection` no endpoint `-pooler` para o tráfego da app e `MigrationConnection` direta para as migrations.
17
+ - **infrastructure-neon-pgvector** (`infrastructure/neon/neon-pgvector.md`) — Npgsql.EntityFrameworkCore.PostgreSQL + Pgvector.EntityFrameworkCore, colunas mapeadas por `HasColumnName`, `HasPostgresExtension("vector")` no `OnModelCreating`.
18
+ - **backend-dotnet-testing** (`backend/dotnet/testing.md`) — `TimeProvider` injetado e `FakeTimeProvider` no teste; teste de integração contra o banco real do stack, um por cenário com `risco:`.
19
+
20
+ > Resumo injetado quando você lê ou edita um arquivo casado por `paths:`. Texto completo: `morph-spec standards --find "<id>"` e leia só a seção necessária de `.morph/framework/standards/<path>`.
@@ -10,6 +10,6 @@ stacks:
10
10
  # Frontend Standards
11
11
 
12
12
  - **frontend-design-system-animations** (`frontend/design-system/animations.md`) — Lista escalonada exige `animation-delay` E `animation-fill-mode: backwards` (senão pisca antes do atraso). Sempre o bloco `prefers-reduced-motion: reduce`. Além do CSS: Motion (`motion/react`) para enter/exit/layout; GSAP só para ScrollTrigger, SVG morph e timelines.
13
- - **frontend-design-system-art-direction** (`frontend/design-system/art-direction.md`) — Tela nova ou alterada segue a direção escrita em `1-design/direcao.md` e o protótipo de `1-design/prototipos/`: celular composto (folha inferior, alvos de 44px), movimento só em `transform`/`opacity` com versão estática em `prefers-reduced-motion`, nenhum número inventado em cena ilustrativa.
13
+ - **frontend-design-system-art-direction** (`frontend/design-system/art-direction.md`) — Tela nova ou alterada segue a `**Direção escolhida:**` do `1-design/mockups.md` e o protótipo de `1-design/prototipos/`: celular composto (folha inferior, alvos de 44px), movimento só em `transform`/`opacity` com versão estática em `prefers-reduced-motion`, nenhum número inventado em cena ilustrativa.
14
14
 
15
15
  > Resumo injetado quando você lê ou edita um arquivo casado por `paths:`. Texto completo: `morph-spec standards --find "<id>"` e leia só a seção necessária de `.morph/framework/standards/<path>`.
@@ -0,0 +1,25 @@
1
+ ---
2
+ paths:
3
+ - ".morph/features/*/3-implement/report.md"
4
+ ---
5
+
6
+ # Formato do report.md
7
+
8
+ A memória da implementação depois de um `/compact` e o que o `next` lê para decidir o passo.
9
+
10
+ ```markdown
11
+ | Cn | status | evidência |
12
+ |----|--------|-----------|
13
+ | C1 | feito | `OrdersTests.RecusaSemItens` |
14
+ | C2 | feito | `src/Orders/List.cs:31` |
15
+ | C3 | bloqueado | |
16
+
17
+ ## Notas
18
+ - 2026-10-27 — C3: o plano não diz se pedido cancelado volta ao estoque. Pergunta: devolve?
19
+ ```
20
+
21
+ - Status: `pendente` | `feito` | `bloqueado`. Uma linha por `Cn` do `checks.json`, sem inventar nem pular.
22
+ - `feito` exige evidência: o teste que prova, o `arquivo:linha` (`prova: leitura`) ou `evidencias/{Cn}.md` (`eval`/`manual`). `feito` sem evidência é o que o hook de Stop aponta.
23
+ - `bloqueado` só com a pergunta em `## Notas`: o `next` devolve `decide` e ela vira `AskUserQuestion`. Respondida, a resposta vai em `## Notas` e o `Cn` volta a `pendente`.
24
+ - `## Notas` é append-only, uma linha datada por decisão ou fato novo. Fato errado do plano vai na `## Errata` do `plan.md`, não aqui.
25
+ - Atualize ao fechar cada `Cn` e commite junto (`wip(<f>): Cn …`).
@@ -10,6 +10,8 @@ stacks:
10
10
 
11
11
  # Infrastructure Standards
12
12
 
13
+ **Ponto de vista:** jobs assíncronos (Hangfire só com razão real), deploy e observabilidade — retry, idempotência, dead-letter, métricas. Não invente infra: use o que o stack já oferece. GitHub (issues, PRs, releases, Actions) sempre pelo `gh` no Bash, nunca MCP. Coolify sempre por SSH + `curl` na API local — não há MCP nem CLI; o token é lido do lado do servidor e nunca impresso. Recurso pago acima de US$ 10/mês → ADR em `## Decisões` antes de criar.
14
+
13
15
  - **infrastructure-docker-coolify-deploy** (`infrastructure/docker/coolify-deploy.md`) — Acesso só por SSH na VPS, API do Coolify em `localhost:8000/api/v1` com token server-side (nunca impresso). Env vars no Coolify, nunca no Dockerfile. Endpoint `/health`. Redeploy por `POST /api/v1/deploy?uuid=` (GET não deploya); sem `deployment_uuid` na resposta, nada foi deployado.
14
16
  - **infrastructure-docker-local-compose-isolation** (`infrastructure/docker/local-compose-isolation.md`) — Cada worktree roda o próprio stack compose; portas do bloco da worktree (`PORT`, `API_PORT`, `POSTGRES_PORT`, `REDIS_PORT`) publicadas como `${VAR:-default}:container`. PROIBIDO: porta fixa no host, `container_name`. Healthcheck em todo serviço. Compose que lê a porta por outro nome: declare em `e2e.portEnvNames`.
15
17
 
@@ -8,6 +8,8 @@ stacks:
8
8
 
9
9
  # Next.js Standards
10
10
 
11
+ **Ponto de vista:** Next.js App Router idiomático — Server Component por padrão, Client Component só com interação real; Tailwind + shadcn + Motion; tipos por Zod. Tipo local redeclarado em cada arquivo é o defeito mais comum aqui: confira a tabela `## Reuso` do plano antes de criar um tipo.
12
+
11
13
  - **frontend-nextjs-lint** (`frontend/nextjs/lint.md`) — O lint cobra o mecânico: `framer-motion`, Zustand/Redux, driver de banco, Form legado, deep import de feature, fronteiras `components`/`features`/`app`/`hooks`, fetch em `useEffect`, `'use client'` no layout raiz, `export default` fora de rota, `any`. O `verify` roda o eslint nos arquivos da feature quando há `eslint.config.*`; sem `eslint.config.mjs`: `morph-spec standards --show frontend-nextjs-lint#files`. Regra mecânica nova vira linha nele, nunca prosa.
12
14
  - **frontend-nextjs-naming-conventions** (`frontend/nextjs/naming-conventions.md`) — Arquivo SEMPRE kebab-case (`user-card.tsx`); componente PascalCase. Zod em `.schemas.ts`, tipos em `.types.ts` via `z.infer`. Nunca misture casos na mesma categoria.
13
15
  - **frontend-nextjs-project-structure** (`frontend/nextjs/project-structure.md`) — `src/app/` só roteia; a lógica da tela mora em `src/features/{f}/` (`components/`, `hooks/`, `types/`, `index.ts` como API pública). NUNCA uma feature importa outra (extraia para `components/` ou `lib/`); `components/ui/` é shadcn e não se edita.
@@ -22,4 +24,9 @@ stacks:
22
24
  - **architecture-code-comments** (`architecture/code-comments.md`) — Padrão é nenhum comentário: renomeie, extraia, tipe ou teste antes. Só o porquê que o código não mostra (invariante, ordem, quirk externo com link), até ~3 linhas. PROIBIDO: história de processo (task, UC, ADR, Gate), repetir o código, XML doc/JSDoc que repete a assinatura, banner, `#region`, código comentado, TODO solto. O hook `comment-guard` bloqueia.
23
25
  - **architecture-engineering-lens** (`architecture/engineering-lens.md`) — Escreva para quem herda amanhã: convenção do projeto, o mais simples, causa raiz. PROIBIDO no que você adiciona: `catch` que engole, flag booleana que bifurca o método (são dois métodos), efeito colateral escondido, símbolo/flag/config sem consumidor, abstração ou camada sem segundo uso, literal repetido sem constante. Nomes de domínio buscáveis, funções pequenas, ≤ 3 parâmetros, condicional positiva. Commit em Conventional Commits (hook `conventional-commit`).
24
26
 
27
+ - **frontend-admin-app-data-table** (`frontend/admin-app/data-table.md`) — Toda lista do admin pagina, ordena e filtra no servidor, com `page/pageSize/sort/dir/filters` na URL (Zod com `.catch`) e a API devolvendo `{ items, totalCount }` sobre um `OrderBy` com desempate único.
28
+ - **frontend-admin-app-forms** (`frontend/admin-app/forms.md`) — O padrão de `nextjs/forms.md` mais os deltas de CRUD: `ValidationProblemDetails` do .NET mapeado para cada campo por `setError`.
29
+ - **frontend-admin-app-feedback** (`frontend/admin-app/feedback.md`) — Um `ConfirmDialog` (AlertDialog controlado) só para ação destrutiva irreversível, em lote ou que descarta mudança; ação reversível usa toast com desfazer.
30
+ - **frontend-admin-app-page-structure** (`frontend/admin-app/page-structure.md`) — Decida por tela e registre no checklist do mockup: abas para 3+ seções independentes, página longa com navegação fixa quando lidas juntas, wizard quando um campo depende do anterior.
31
+
25
32
  > Resumo injetado quando você lê ou edita um arquivo casado por `paths:`. Texto completo: `morph-spec standards --find "<id>"` e leia só a seção necessária de `.morph/framework/standards/<path>`.
@@ -0,0 +1,53 @@
1
+ ---
2
+ paths:
3
+ - ".morph/features/*/2-plan/plan.md"
4
+ ---
5
+
6
+ # Gramática do plan.md
7
+
8
+ `plan-check` é o único leitor: o que ele não reconhece, ninguém lê. Referência completa: `morph-spec plan-check --help`.
9
+
10
+ ## Cenários
11
+
12
+ `- **Cn** — Dado …, quando …, então …`, com `prova:` **na linha seguinte, sem linha em branco** (linha vazia encerra o cenário).
13
+
14
+ - **Teste é por risco, não por código novo.** `prova: teste` exige `risco:` (`regra` | `dinheiro` | `contrato` | `concorrência` | `dado` | `regressão`): ramo de regra de negócio, valor, contrato externo, corrida, integridade de dado ou o bug real. Sem risco (CRUD, DTO, mapping, wiring, constante, texto, layout) a prova é `leitura` — o verificador fecha por `arquivo:linha` — ou `e2e` quando o fluxo é o que importa.
15
+ - **Degrau de retrabalho** (opcional, junto de `prova:`): `degrau:` `refazer` | `desfazer` | `migrar` | `irreversível`; sem a linha, `refazer`. `migrar` é toda migration, aditiva ou não (drop, rename, backfill, coluna nova); `irreversível` apaga dado que alguém lê ou quebra contrato externo. Os dois pausam o gate do plano para o usuário, e o Gate 3 pausa quando a feature toca `migrations/` sem nenhum dos dois, ou um arquivo de contrato (`openapi.yaml`, `swagger.json`, `.proto`) sem `irreversível`; o revisor do Gate 3 caça o resto (webhook, dado apagado fora de migration, envio a terceiro).
16
+ - **Observável**: que asserção, ou que passo no navegador, prova? Sem resposta, reescreva ou descarte.
17
+ - **Artefato não é comportamento.** Cenário que cita `grep`, "contém a string", "o binário", "nenhum arquivo", "arquivo/pasta/diretório em" ou glob `**/` descreve o repositório. Tente o efeito em runtime; se o artefato É o entregável, justifique em `> Por que artefato:` logo abaixo (nunca na mesma linha, nunca `> [!note]`, que o parser não lê).
18
+ - **Saída de LLM** prova-se por `eval` de runtime, nunca pelo texto do prompt: evidência em `3-implement/evidencias/{Cn}.md`; com nó `runner: "evals"` (standard `ai-agents-evals-with-cache`) o cenário mora no dataset.
19
+ - **"A suíte roda de ponta a ponta"** prova-se pelo `verify {feature}`; nunca uma execução avulsa.
20
+ - **Orçamento de teste:** um teste por cenário de risco; variantes são linhas de tabela (`[Theory]`/`it.each`); nenhum teste que só fixa texto, prompt, constante, DTO ou mapping; o mesmo comportamento numa camada só. Além disso é escopo para o revisor.
21
+
22
+ ```markdown
23
+ ## Cenários
24
+ - **C1** — Dado um tenant com `criterion_overrides`, quando ele é publicado, então a publicação é recusada nomeando a chave.
25
+ prova: teste
26
+ risco: contrato
27
+ - **C2** — Dado um admin logado, quando ele abre /tenants e filtra por "ativo", então a lista mostra só os ativos, paginada no servidor.
28
+ prova: e2e
29
+ - **C3** — Dado um lead com `sla_desde` preenchido, quando a migration roda, então a coluna some e o motor segue lendo o SLA do perfil.
30
+ prova: teste
31
+ risco: dado
32
+ degrau: migrar
33
+ ```
34
+
35
+ ## Padrões assumidos
36
+
37
+ Todo valor de baixo risco que você decidiu sem perguntar (limite, intervalo, retry, retenção, texto, ordenação), numerado `1.`…`N.`, um por linha, com o valor escolhido. O usuário veta por número ("veta 2") e o resto está aceito; "nenhum" vale. **Nunca padrão:** auth, credencial, permissão, pagamento ou apagar dado; isso é pergunta, e o `plan-check` avisa. Obrigatória em plano novo de `feature`/`bug`.
38
+
39
+ ## Estados (opcional)
40
+
41
+ Só quando a feature tem algo que muda de estado (pedido, aprovação, assinatura): uma transição por linha, `- origem → destino: gatilho` (`->` também vale). O `plan-check` desenha a máquina no `2-plan/plan.html`; linha que não é transição, ou seção vazia, é erro. Sem estado, não escreva a seção.
42
+
43
+ ## Reuso
44
+
45
+ `| conceito | veredito | alvo | why |`, veredito `REUSAR` | `ESTENDER` | `CRIAR`. **`CRIAR` exige `why`** (`graph explain X → not-found; grep -rn X → 0`). Abstração nova (interface, base, factory, camada) soma ao `why` o segundo uso que ela barateia; sem isso é excesso (`architecture-engineering-lens#excess`). Conceito de duas features vai a um módulo comum, nunca import cruzado. O validador `reuse` do `verify` cobra: símbolo novo homônimo de um existente reprova na mesma feature.
46
+
47
+ ## Decisões
48
+
49
+ `### ADR-001 — título` por decisão não trivial (alternativa descartada, motivo, quem decidiu) com `**Alcance:** feature` ou `projeto`. Greenfield: decisão que fixa stack, layout, infraestrutura ou test runner é `projeto`. `### Conflitos com o acervo` sempre (`nenhum` vale). Proibição além das do standard `architecture-stack-baseline` é ADR.
50
+
51
+ ## O que o plan-check recusa
52
+
53
+ Veredito de reuso fora do enum, id `C`/`ADR` duplicado, `## Errata` fora do fim, `## Cenários` vazio, `teste` sem `risco:`, bug sem cenário de regressão, `## Padrões assumidos` ausente ou fora de `1..N`, `## Estados` com linha que não é `origem → destino: gatilho`. Avisa: padrão que toca credencial, permissão, pagamento ou apagar dado, chave desconhecida (inclusive o `## Fatias` de planos antigos), ADR sem **Alcance**.
@@ -0,0 +1,27 @@
1
+ ---
2
+ paths:
3
+ - "**/*.tsx"
4
+ - "**/*.jsx"
5
+ - "**/*.vue"
6
+ - "**/*.svelte"
7
+ - "**/*.css"
8
+ - "**/*.scss"
9
+ - ".morph/features/*/1-design/**"
10
+ ---
11
+
12
+ # Régua de design
13
+
14
+ Vale para o desenho na fase uiux e para todo código de UI na implementação.
15
+
16
+ **Duas réguas ao mesmo tempo.** O sistema — tokens de cor, espaço e tipografia, componentes reutilizáveis, estados default/hover/focus/disabled/loading/error, WCAG AA — e a estética, que separa um design correto de um design aprovado. Régua estética: o standard `frontend-design-system-art-direction`.
17
+
18
+ - **Direção antes de componente.** Conceito em uma frase, metáfora visual, o que torna a tela memorável, o que ela não pode parecer (template shadcn cru, card branco centralizado, lista de ícones genérica). Escolhida, vai em `**Direção escolhida:**` no `1-design/mockups.md`.
19
+ - **Skills de design:** `frontend-design:frontend-design` sempre; `high-end-visual-design` em momento de marca; `redesign-existing-projects` em tela existente — cada uma só se a sessão a tiver; ausente, o standard de direção de arte é a régua.
20
+ - **Celular primeiro e nativo:** folha inferior, zona do polegar, alvos de 44px, navegação que o polegar alcança. O celular é desenhado, nunca o desktop empilhado.
21
+ - **Movimento com intenção:** a ferramenta mais leve que entrega o efeito (CSS transition/`@keyframes` → animação por scroll em CSS → Motion → GSAP com ScrollTrigger). 60 fps, só `transform` e `opacity`. `prefers-reduced-motion` tem versão estática digna, composta, não só a animação desligada.
22
+ - **Acabamento:** camadas, luz (gradiente radial, trama discreta), vidro com moderação, sombra com a cor do tema, ritmo tipográfico (escala modular, tracking negativo no display), microinteração em foco, hover e envio.
23
+ - **Tela de dados:** lista pagina, ordena e filtra no servidor e tem os três estados (vazio, carregando, erro). Uma ação primária por tela; erro diz o que fazer a seguir; conteúdo gerado por IA marcado como tal.
24
+ - **Reuso:** componente se busca antes de se escrever, também em `.morph/context/design-system.md`. Token novo precisa de justificativa.
25
+ - **Proibido:** número ou dado inventado em tela real, cópia de marca de terceiros, efeito que quebra contraste AA.
26
+
27
+ Standards: `frontend-design-system-art-direction`, `frontend-design-system-premium-finish`, `frontend-design-system-animations`, `frontend-nextjs-motion-patterns`, `frontend-nextjs-testing`. Por superfície (`marketing-site`, `admin-app`, `brand-moment`): `.claude/skills/morph-uiux/surfaces.json`. Texto inteiro: `morph-spec standards --show <id>`.