@polymorphism-tech/morph-spec 9.1.0 → 9.3.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.
- package/CLAUDE.md +14 -78
- package/README.md +2 -2
- package/bin/morph-spec.js +14 -1
- package/claude-plugin.json +1 -1
- package/docs/CHEATSHEET.md +1 -1
- package/docs/QUICKSTART.md +1 -1
- package/framework/CLAUDE.md +4 -3
- package/framework/MORPH.md +7 -10
- package/framework/agents.json +15 -12
- package/framework/commands/morph-status.md +2 -2
- package/framework/docs/CLI.md +24 -16
- package/framework/evals/review-checklist.md +15 -2
- package/framework/hooks/README.md +8 -44
- package/framework/hooks/claude-code/__pycache__/statusline.cpython-313.pyc +0 -0
- package/framework/hooks/claude-code/core/state-sync.js +1 -1
- package/framework/hooks/claude-code/core/worktree-context.js +23 -0
- package/framework/hooks/claude-code/statusline.py +3 -2
- package/framework/hooks/claude-code/statusline.sh +11 -11
- package/framework/hooks/shared/gate-event.js +18 -0
- package/framework/hooks/shared/next-step.js +107 -0
- package/framework/hooks/shared/slice-progress.js +18 -5
- package/framework/hooks/shared/state-writer.js +6 -1
- package/framework/mcp/registry.json +31 -1
- package/framework/rules/nextjs-standards.md +1 -1
- package/framework/schemas/feature.schema.json +1 -0
- package/framework/skills/core/create-umbraco-lp/SKILL.md +1 -1
- package/framework/skills/core/migrate-lp-to-umbraco/SKILL.md +1 -1
- package/framework/skills/core/morph-apply/SKILL.md +6 -4
- package/framework/skills/core/morph-hotfix/SKILL.md +3 -3
- package/framework/skills/core/morph-machine/SKILL.md +1 -1
- package/framework/skills/core/morph-media/SKILL.md +1 -1
- package/framework/skills/core/morph-plan/SKILL.md +8 -6
- package/framework/skills/core/morph-review/SKILL.md +9 -6
- package/framework/skills/core/morph-standards/SKILL.md +1 -1
- package/framework/skills/core/morph-uiux/SKILL.md +2 -1
- package/framework/standards/STANDARDS.json +2 -2
- package/framework/standards/STANDARDS.meta.json +1 -1
- package/framework/standards/ai-agents/setup.md +2 -0
- package/framework/standards/backend/dotnet/testing.md +10 -4
- package/framework/standards/frontend/nextjs/testing.md +10 -6
- package/framework/standards/integration/mcp/mcp-tools.md +3 -1
- package/framework/templates/.idea/morph-templates.xml +92 -0
- package/framework/templates/.vscode/morph-templates.code-snippets +186 -0
- package/framework/templates/IDE-SNIPPETS.md +262 -262
- package/framework/templates/code/dotnet/abstractions/api-endpoint.cs +7 -7
- package/framework/templates/code/dotnet/feature/request.cs.hbs +4 -4
- package/framework/templates/code/dotnet/feature/response.cs.hbs +4 -4
- package/framework/templates/code/sql/ai-agents/pgvector-embeddings-migration.sql.template +91 -91
- package/framework/templates/docs/plan.md +3 -1
- package/framework/templates/frontend/nextjs/client-component.tsx.hbs +26 -26
- package/framework/templates/frontend/nextjs/feature-form.tsx.hbs +56 -56
- package/framework/templates/frontend/nextjs/tsconfig.json.hbs +26 -26
- package/framework/templates/infrastructure/docker/Dockerfile.template +58 -58
- package/framework/templates/infrastructure/docker/dockerfile-api.dockerfile +38 -38
- package/framework/templates/infrastructure/docker/dockerfile-web.dockerfile +48 -48
- package/framework/templates/infrastructure/github/actions/docker-build-push/action.yml.hbs +45 -45
- package/framework/templates/infrastructure/github/actions/health-check/action.yml.hbs +27 -27
- package/framework/templates/infrastructure/github/workflows/docker-build-push.yml.hbs +59 -59
- package/framework/templates/infrastructure/github/workflows/dotnet-build.yml.hbs +39 -39
- package/framework/templates/meta-prompts/fusion/fusion-agent.md +76 -76
- package/framework/templates/meta-prompts/fusion/fusion-aggregator.md +100 -100
- package/framework/templates/state.template.json +17 -17
- package/framework/templates/ui/design-system.css +226 -226
- package/package.json +1 -1
- package/src/commands/observability/cost.js +12 -0
- package/src/commands/project/status.js +6 -68
- package/src/commands/state/advance.js +1 -1
- package/src/commands/state/approve.js +2 -2
- package/src/commands/state/gate-check.js +2 -2
- package/src/commands/state/gate-decision.js +4 -1
- package/src/commands/state/next.js +24 -0
- package/src/commands/state/report-check.js +3 -1
- package/src/commands/validation/mutate.js +9 -6
- package/src/commands/validation/plan-check.js +4 -2
- package/src/commands/validation/tests-audit.js +7 -0
- package/src/commands/validation/validate-feature.js +4 -2
- package/src/commands/validation/verify.js +21 -16
- package/src/core/state/state-manager.js +4 -0
- package/src/dashboard/app/main.jsx +13 -13
- package/src/dashboard/app/pages/FeatureDetail.jsx +6 -2
- package/src/dashboard/scanner/features-scanner.js +5 -2
- package/src/dashboard/vite.config.js +24 -24
- package/src/lib/ci/workflow-scaffold.js +12 -0
- package/src/lib/e2e/applicability.js +37 -0
- package/src/lib/generators/handoff-generator.js +11 -25
- package/src/lib/mutation/adapters.js +6 -17
- package/src/lib/mutation/runner.js +23 -75
- package/src/lib/mutation/trx.js +0 -1
- package/src/lib/next-step.js +10 -0
- package/src/lib/plan/checks.js +29 -2
- package/src/lib/plan/plan-parser.js +6 -2
- package/src/lib/review-reports.js +6 -3
- package/src/lib/risk-detector.js +20 -7
- package/src/lib/stack/stack-scan.js +13 -1
- package/src/lib/telemetry/metrics.js +46 -0
- package/src/lib/tests-audit/audit.js +96 -0
- package/src/lib/validators/code-budget/code-budget-validator.js +1 -1
- package/src/lib/validators/validation-runner.js +6 -38
- package/src/lib/validators/verify-runner.js +37 -22
- package/src/utils/agents-installer.js +24 -1
- package/framework/hooks/git/commit-msg/conventional-commits.sh +0 -33
- package/framework/hooks/git/pre-commit/agents.sh +0 -25
- package/framework/hooks/git/pre-commit/orchestrator.sh +0 -64
- package/framework/hooks/git/pre-push/run-tests.sh +0 -44
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { existsSync } from 'fs';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import { appendTelemetryEvent } from './telemetry-logger.js';
|
|
4
|
+
|
|
5
|
+
export const GATE_EVENT = 'Gate';
|
|
6
|
+
export const GATE_STEPS = ['decision', 'approve', 'unapprove'];
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* The trail `cost --metrics` turns into "what did the human do with each pause":
|
|
10
|
+
* `gateDecisions` keeps only the latest decision, so the history lives here.
|
|
11
|
+
*/
|
|
12
|
+
export function recordGateEvent(projectPath, { feature, gate, step, pause, signal, source, approvedBy }) {
|
|
13
|
+
if (!existsSync(join(projectPath || process.cwd(), '.morph'))) return;
|
|
14
|
+
const event = { event: GATE_EVENT, feature, gate, step };
|
|
15
|
+
if (step === 'decision') Object.assign(event, { pause: pause === true, signal: signal ?? null, source: source ?? null });
|
|
16
|
+
else event.approvedBy = approvedBy ?? null;
|
|
17
|
+
appendTelemetryEvent(event, projectPath);
|
|
18
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
// The single rule of "what the session does next". `decideNext` is pure: collectors feed facts.
|
|
2
|
+
// Hooks and the CLI share it so the SessionStart pre-fetch and `morph-spec next` never disagree.
|
|
3
|
+
|
|
4
|
+
import { existsSync, readFileSync } from 'fs';
|
|
5
|
+
import { join } from 'path';
|
|
6
|
+
import { derivePhaseForFeature } from './state-reader.js';
|
|
7
|
+
import { sliceProgress, readySlices } from './slice-progress.js';
|
|
8
|
+
|
|
9
|
+
export const FIX_ATTEMPTS = 3;
|
|
10
|
+
|
|
11
|
+
const step = (action, command, reason, extra = {}) => ({ action, command, reason, ...extra });
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @param {{ feature: string, phase: string, slices: object|null, gates: object,
|
|
15
|
+
* verification: { status?: string }|null, verificationStale?: boolean, failStreak?: number }} s
|
|
16
|
+
* `verificationStale` undefined = unknown (the hook has no git): read as fresh.
|
|
17
|
+
*/
|
|
18
|
+
export function decideNext(s) {
|
|
19
|
+
const f = s.feature;
|
|
20
|
+
const approved = (g) => s.gates?.[g]?.approved === true;
|
|
21
|
+
// The hotfix pipeline (scout, human approval, ship) lives in its own skill, end to end.
|
|
22
|
+
const skill = (name) => (s.workType === 'hotfix' ? `/morph-hotfix ${f}` : `/${name} ${f}`);
|
|
23
|
+
|
|
24
|
+
switch (s.phase) {
|
|
25
|
+
case 'plan':
|
|
26
|
+
return s.slices
|
|
27
|
+
? step('gate', `morph-spec gate-check ${f} plan`, 'plano extraído: o gate do plano decide a pausa')
|
|
28
|
+
: step('plan', `/morph-plan ${f}`, 'sem checks.json: escreva o plan.md e rode plan-check até zero erro');
|
|
29
|
+
|
|
30
|
+
case 'uiux':
|
|
31
|
+
return step('design', `morph-spec gate-check ${f} uiux`, 'complete 1-design/ (morph-uiux) e passe o gate de uiux');
|
|
32
|
+
|
|
33
|
+
case 'implement': {
|
|
34
|
+
if (!s.slices) return step('plan', skill('morph-plan'), 'sem checks.json: plan.md curto (uma fatia) e plan-check até zero erro');
|
|
35
|
+
if (s.slices.done < s.slices.total) {
|
|
36
|
+
const ready = readySlices(s.slices);
|
|
37
|
+
if (!ready.length) {
|
|
38
|
+
const waiting = s.slices.slices.filter((sl) => sl.status === 'bloqueado').map((sl) => sl.id);
|
|
39
|
+
if (waiting.length) {
|
|
40
|
+
return step('decide', 'AskUserQuestion (decisão da fatia)',
|
|
41
|
+
'relatório BLOQUEADO: decida pelo plano/ADR ou pergunte, e retome o MESMO agente com a resposta — nunca re-despache',
|
|
42
|
+
{ slices: waiting });
|
|
43
|
+
}
|
|
44
|
+
return step('blocked', `morph-spec report-check ${f}`, 'fatias pendentes sem dependência satisfeita');
|
|
45
|
+
}
|
|
46
|
+
const partial = ready.filter((sl) => sl.status === 'parcial').map((sl) => sl.id);
|
|
47
|
+
return step('dispatch', skill('morph-apply'),
|
|
48
|
+
partial.length ? `retome ${partial.join(', ')} (PARCIAL) e despache as prontas` : 'despache as fatias prontas',
|
|
49
|
+
{ slices: ready.map((sl) => sl.id) });
|
|
50
|
+
}
|
|
51
|
+
const v = s.verification;
|
|
52
|
+
if (!v?.status || s.verificationStale === true) {
|
|
53
|
+
return step('verify', `morph-spec verify ${f}`, v?.status ? 'a árvore andou desde o último verify' : 'todas as fatias relatadas, sem verify');
|
|
54
|
+
}
|
|
55
|
+
if (v.status === 'fail') {
|
|
56
|
+
const streak = s.failStreak ?? 0;
|
|
57
|
+
if (streak > 0 && streak % FIX_ATTEMPTS === 0) {
|
|
58
|
+
return step('escalate', 'AskUserQuestion (aceitar/continuar/parar)',
|
|
59
|
+
`${streak} verifies vermelhos seguidos, nunca uma tentativa a mais por conta própria. `
|
|
60
|
+
+ `aceitar → ${skill('morph-review')} (o verify vermelho pausa o gate: o humano assina lá); `
|
|
61
|
+
+ 'continuar → corrija e rode o verify de novo; parar → encerre aqui. Segue `escalate` até a árvore andar');
|
|
62
|
+
}
|
|
63
|
+
return step('fix', `morph-spec verify ${f}`,
|
|
64
|
+
`corrija pelo feedback do verify e rode de novo (${FIX_ATTEMPTS - (streak % FIX_ATTEMPTS)} tentativa(s) antes de perguntar)`);
|
|
65
|
+
}
|
|
66
|
+
return step('review', skill('morph-review'), 'verify verde: Gate 3');
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
case 'review':
|
|
70
|
+
return approved('review')
|
|
71
|
+
? step('finish', `morph-spec finish ${f} --pr|--merge`, 'Gate 3 aprovado: arquiva e integra num passo')
|
|
72
|
+
: step('review', skill('morph-review'), 'relatórios do Gate 3 e gate-check review');
|
|
73
|
+
|
|
74
|
+
default:
|
|
75
|
+
return step('status', `morph-spec status ${f}`, `fase desconhecida: ${s.phase ?? '—'}`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function formatNext(n) {
|
|
80
|
+
const slices = n.slices?.length ? ` [${n.slices.join(', ')}]` : '';
|
|
81
|
+
return `próximo: ${n.action}${slices} → ${n.command} — ${n.reason}`;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** Facts readable from `hooks/shared`; the CLI adds `verificationStale` (git) on top. Null without feature.json. */
|
|
85
|
+
export function collectNextSignals(featureName, cwd) {
|
|
86
|
+
const featureDir = join(cwd, '.morph', 'features', featureName);
|
|
87
|
+
let feature;
|
|
88
|
+
try {
|
|
89
|
+
const path = join(featureDir, 'feature.json');
|
|
90
|
+
if (!existsSync(path)) return null;
|
|
91
|
+
feature = JSON.parse(readFileSync(path, 'utf8').replace(/^/, ''));
|
|
92
|
+
} catch {
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
let phase = 'plan';
|
|
96
|
+
try { phase = derivePhaseForFeature(featureName, cwd) || 'plan'; } catch {}
|
|
97
|
+
return {
|
|
98
|
+
feature: featureName,
|
|
99
|
+
workType: feature.workType ?? 'feature',
|
|
100
|
+
worktreePath: feature.worktree?.path ?? null,
|
|
101
|
+
phase,
|
|
102
|
+
slices: sliceProgress(featureDir),
|
|
103
|
+
gates: feature.approvals || feature.approvalGates || {},
|
|
104
|
+
verification: feature.verification ?? null,
|
|
105
|
+
failStreak: feature.verifications?.feature?.failStreak ?? 0,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
@@ -4,7 +4,10 @@ import { join } from 'path';
|
|
|
4
4
|
/** `PARCIAL` on the report's first non-blank line, tolerating heading/bold/quote typography. */
|
|
5
5
|
export const PARTIAL_MARKER = /^[\s#>*_]*PARCIAL\b/;
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
/** `BLOQUEADO`: the slice waits on a decision only the human can make; re-dispatching it repeats the question. */
|
|
8
|
+
export const BLOCKED_MARKER = /^[\s#>*_]*BLOQUEADO\b/;
|
|
9
|
+
|
|
10
|
+
export const SLICE_STATUSES = ['pendente', 'parcial', 'bloqueado', 'feito'];
|
|
8
11
|
|
|
9
12
|
export function checksPath(featureDir) {
|
|
10
13
|
return join(featureDir, '2-plan', 'checks.json');
|
|
@@ -17,10 +20,11 @@ export function sliceReportPath(featureDir, sliceId) {
|
|
|
17
20
|
export function sliceStatusFromReport(text) {
|
|
18
21
|
if (text == null) return 'pendente';
|
|
19
22
|
const first = String(text).split(/\r?\n/).find((l) => l.trim() !== '') ?? '';
|
|
23
|
+
if (BLOCKED_MARKER.test(first)) return 'bloqueado';
|
|
20
24
|
return PARTIAL_MARKER.test(first) ? 'parcial' : 'feito';
|
|
21
25
|
}
|
|
22
26
|
|
|
23
|
-
/** @returns {{ total, done, partial, pending, slices } | null} null when the checks text is unreadable */
|
|
27
|
+
/** @returns {{ total, done, partial, blocked, pending, slices } | null} null when the checks text is unreadable */
|
|
24
28
|
export function sliceProgressFrom(checksText, readReport) {
|
|
25
29
|
let slices;
|
|
26
30
|
try {
|
|
@@ -39,7 +43,14 @@ export function sliceProgressFrom(checksText, readReport) {
|
|
|
39
43
|
status: sliceStatusFromReport(readReport(s.id)),
|
|
40
44
|
}));
|
|
41
45
|
const count = (status) => out.filter((s) => s.status === status).length;
|
|
42
|
-
return {
|
|
46
|
+
return {
|
|
47
|
+
total: out.length,
|
|
48
|
+
done: count('feito'),
|
|
49
|
+
partial: count('parcial'),
|
|
50
|
+
blocked: count('bloqueado'),
|
|
51
|
+
pending: count('pendente'),
|
|
52
|
+
slices: out,
|
|
53
|
+
};
|
|
43
54
|
}
|
|
44
55
|
|
|
45
56
|
function readOrNull(path) {
|
|
@@ -57,7 +68,7 @@ export function sliceProgress(featureDir) {
|
|
|
57
68
|
export function readySlices(progress) {
|
|
58
69
|
if (!progress) return [];
|
|
59
70
|
const done = new Set(progress.slices.filter((s) => s.status === 'feito').map((s) => s.id));
|
|
60
|
-
return progress.slices.filter((s) => s.status !== 'feito' && s.depends.every((d) => done.has(d)));
|
|
71
|
+
return progress.slices.filter((s) => s.status !== 'feito' && s.status !== 'bloqueado' && s.depends.every((d) => done.has(d)));
|
|
61
72
|
}
|
|
62
73
|
|
|
63
74
|
// Loose on purpose: a false "complete" costs a read, a false "incomplete"
|
|
@@ -94,6 +105,8 @@ export function inspectReport(path, { covers = [] } = {}) {
|
|
|
94
105
|
const firstLine = body.split('\n').find((l) => l.trim() !== '') ?? '';
|
|
95
106
|
const partial = PARTIAL_MARKER.test(firstLine);
|
|
96
107
|
if (partial) missing.push('partial');
|
|
108
|
+
const blocked = BLOCKED_MARKER.test(firstLine);
|
|
109
|
+
if (blocked) missing.push('blocked');
|
|
97
110
|
|
|
98
|
-
return { exists: true, complete: missing.length === 0, missing, partial };
|
|
111
|
+
return { exists: true, complete: missing.length === 0, missing, partial, blocked };
|
|
99
112
|
}
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import { readFileSync, writeFileSync, renameSync, unlinkSync, existsSync, mkdirSync } from 'fs';
|
|
5
5
|
import { join, dirname } from 'path';
|
|
6
6
|
import { clearStateCache } from './state-reader.js';
|
|
7
|
+
import { recordGateEvent } from './gate-event.js';
|
|
7
8
|
|
|
8
9
|
const STATE_FILE = '.morph/state.json';
|
|
9
10
|
const STATE_VERSION = '5.0.0';
|
|
@@ -81,10 +82,14 @@ export function setGateApproved(featureName, gate, meta = {}, projectPath) {
|
|
|
81
82
|
...(meta.selfAssessed ? { selfAssessed: true } : {}),
|
|
82
83
|
};
|
|
83
84
|
feature.updatedAt = now;
|
|
84
|
-
|
|
85
|
+
if (!persistFeature(featureName, feature, projectPath)) return false;
|
|
85
86
|
} catch {
|
|
86
87
|
return false;
|
|
87
88
|
}
|
|
89
|
+
try {
|
|
90
|
+
recordGateEvent(projectPath, { feature: featureName, gate, step: 'approve', approvedBy: meta.approvedBy || 'manual' });
|
|
91
|
+
} catch {}
|
|
92
|
+
return true;
|
|
88
93
|
}
|
|
89
94
|
|
|
90
95
|
// Stamps identity so a feature born implicitly by a hook is never an anonymous
|
|
@@ -277,9 +277,39 @@
|
|
|
277
277
|
"warnings": ["OAuth against your Neon account on first use. The server can delete branches and projects: keep destructive calls behind a human."],
|
|
278
278
|
"setupGuide": "Run: claude mcp add --transport http neon https://mcp.neon.tech/mcp"
|
|
279
279
|
}
|
|
280
|
+
},
|
|
281
|
+
"microsoft-learn": {
|
|
282
|
+
"phases": ["plan", "implement"],
|
|
283
|
+
"tools": ["microsoft_docs_search", "microsoft_docs_fetch", "microsoft_code_sample_search"],
|
|
284
|
+
"serverNames": ["microsoft-learn", "plugin_microsoft-docs_microsoft-learn"],
|
|
285
|
+
"detection": "mcpServers includes microsoft-learn or enabledPlugins includes microsoft-docs",
|
|
286
|
+
"fallback": "context7, then WebFetch on learn.microsoft.com",
|
|
287
|
+
"usage": "Official, current .NET 10 / ASP.NET Core / EF Core / Microsoft Agent Framework docs and code samples: search first, fetch the page when the excerpt is cut. Use it for any API the model may know from an older version",
|
|
288
|
+
"healthCheck": {
|
|
289
|
+
"toolPattern": "microsoft-learn",
|
|
290
|
+
"testCall": "Call microsoft_docs_search with query 'EF Core 10 what is new'",
|
|
291
|
+
"testParams": {
|
|
292
|
+
"query": "EF Core 10 what is new"
|
|
293
|
+
},
|
|
294
|
+
"successIndicator": "Returns Microsoft Learn excerpts with contentUrl",
|
|
295
|
+
"failureHint": "Microsoft Learn MCP not responding. Re-add it: claude mcp add --transport http microsoft-learn https://learn.microsoft.com/api/mcp"
|
|
296
|
+
},
|
|
297
|
+
"install": {
|
|
298
|
+
"autoInstall": false,
|
|
299
|
+
"transport": "http",
|
|
300
|
+
"config": {
|
|
301
|
+
"type": "http",
|
|
302
|
+
"url": "https://learn.microsoft.com/api/mcp"
|
|
303
|
+
},
|
|
304
|
+
"credentials": [],
|
|
305
|
+
"prerequisites": [],
|
|
306
|
+
"stackRelevance": ["dotnet"],
|
|
307
|
+
"warnings": [],
|
|
308
|
+
"setupGuide": "Run: claude mcp add --transport http microsoft-learn https://learn.microsoft.com/api/mcp"
|
|
309
|
+
}
|
|
280
310
|
}
|
|
281
311
|
},
|
|
282
|
-
"_phaseMatrixNote": "Keys must be the v5 phases from framework/phases.json. The v4 keys 'design' and 'tasks' both collapsed into 'plan'; 'review' was absent entirely, so a project in review resolved to undefined. Stack-scoped MCPs (asaas, neon) stay out of this matrix on purpose — only universal ones are listed.",
|
|
312
|
+
"_phaseMatrixNote": "Keys must be the v5 phases from framework/phases.json. The v4 keys 'design' and 'tasks' both collapsed into 'plan'; 'review' was absent entirely, so a project in review resolved to undefined. Stack-scoped MCPs (asaas, neon, microsoft-learn) stay out of this matrix on purpose — only universal ones are listed.",
|
|
283
313
|
"phaseMatrix": {
|
|
284
314
|
"plan": ["context7", "graphify"],
|
|
285
315
|
"uiux": ["playwright", "context7", "stitch", "morph-assets"],
|
|
@@ -15,7 +15,7 @@ stacks:
|
|
|
15
15
|
- **frontend-nextjs-data-fetching** (`frontend/nextjs/data-fetching.md`) — Server Component com `BACKEND_API_PREFIX`; Client Component só pelo proxy same-origin com `PROXY_API_PREFIX`. TanStack Query 5 com key factory por feature e `invalidateQueries({ queryKey })`; toda resposta passa por Zod. PROIBIDO: `useEffect(() => fetch…)` e paginar/filtrar a coleção inteira no browser.
|
|
16
16
|
- **frontend-nextjs-forms** (`frontend/nextjs/forms.md`) — react-hook-form + Zod (`zodResolver`) + Field do shadcn via `Controller`; tipo por `z.infer`; nunca `useState` por campo. Submit por `useMutation`, erro em `root`. Edição reusa o schema de criação (`.partial()`). Zod 4: `z.email()`.
|
|
17
17
|
- **frontend-nextjs-state-management** (`frontend/nextjs/state-management.md`) — Estado de servidor no TanStack Query, nunca copiado para `useState`. Página, ordenação, filtros e aba ativa na URL (`searchParams`). Context só para tema, usuário ou locale. NUNCA Zustand/Redux antes de esgotar Server Components + TanStack Query.
|
|
18
|
-
- **frontend-nextjs-testing** (`frontend/nextjs/testing.md`) — Vitest + Testing Library; MSW para API; Playwright para e2e. Teste comportamento, não implementação. Orçamento: um teste por
|
|
18
|
+
- **frontend-nextjs-testing** (`frontend/nextjs/testing.md`) — Vitest + Testing Library; MSW para API; Playwright para e2e. Teste comportamento, não implementação. Orçamento por risco: um teste por cenário com `risco:` + o de regressão do bug, componente sem risco é `leitura`/`e2e`; cenários do mesmo comportamento em `it.each`; nada de teste que só confere string de copy.
|
|
19
19
|
- **frontend-nextjs-nextjs-patterns** (`frontend/nextjs/nextjs-patterns.md`) — Route Handler é BFF: `auth.getSession()` (401 sem sessão) e delega ao .NET; banco NUNCA em route handler. Zod no client e no server (`safeParse`, 400). `strict: true` + `noUncheckedIndexedAccess`; sem `any` em resposta de API. shadcn via `npx shadcn@latest add`.
|
|
20
20
|
- **frontend-nextjs-motion-patterns** (`frontend/nextjs/motion-patterns.md`) — Motion via `motion/react` (nunca `framer-motion` junto). Variants em `motion/variants.ts` com durations/easings de `@/lib/design-tokens`. `useReducedMotion()` obrigatório; o `layout.tsx` raiz segue Server Component. 3–5 momentos de motion por página; cena guiada por scroll é do GSAP, nunca no mesmo elemento.
|
|
21
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.
|
|
@@ -95,6 +95,7 @@
|
|
|
95
95
|
"additionalProperties": false
|
|
96
96
|
},
|
|
97
97
|
"attempts": { "type": "integer", "minimum": 1 },
|
|
98
|
+
"failStreak": { "type": "integer", "minimum": 0, "description": "Persisted red verifies in a row, reset to 0 by a green one. Read by `morph-spec next` (decideNext in framework/hooks/shared/next-step.js) to turn the fix loop into an escalation every FIX_ATTEMPTS reds. Absent on entries written before the field existed, which is why it is NOT in required." },
|
|
98
99
|
"updatedAt": { "type": "string", "format": "date-time" }
|
|
99
100
|
},
|
|
100
101
|
"additionalProperties": false
|
|
@@ -4,7 +4,7 @@ description: Guia a criação de uma landing page nova ponta-a-ponta a partir do
|
|
|
4
4
|
version: core
|
|
5
5
|
when_to_use: Use quando for iniciar um novo projeto de LP com o template Umbraco genérico
|
|
6
6
|
user-invocable: true
|
|
7
|
-
cliVersion: "9.
|
|
7
|
+
cliVersion: "9.3.0"
|
|
8
8
|
stacks:
|
|
9
9
|
- umbraco
|
|
10
10
|
---
|
|
@@ -4,7 +4,7 @@ description: Migra uma landing page existente (React/Vite, Next.js estático ou
|
|
|
4
4
|
version: core
|
|
5
5
|
when_to_use: Use quando o usuário pedir "use /migrate-lp-to-umbraco {projeto}" ou descrever a necessidade de migrar uma LP para Umbraco
|
|
6
6
|
user-invocable: true
|
|
7
|
-
cliVersion: "9.
|
|
7
|
+
cliVersion: "9.3.0"
|
|
8
8
|
stacks:
|
|
9
9
|
- umbraco
|
|
10
10
|
---
|
|
@@ -4,7 +4,7 @@ description: Executa as fatias do plano de uma feature morph-spec — dispatch p
|
|
|
4
4
|
version: core
|
|
5
5
|
when_to_use: Use com o gate do plano aprovado, em "use /morph-apply {feature}" ou "implementar feature"
|
|
6
6
|
user-invocable: true
|
|
7
|
-
cliVersion: "9.
|
|
7
|
+
cliVersion: "9.3.0"
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# morph-apply
|
|
@@ -78,7 +78,7 @@ relatório) está no corpo da persona gerada e chega a todo dispatch sem ser col
|
|
|
78
78
|
```
|
|
79
79
|
dispatch Agent (subagent_type: "morph-{id}") with prompt:
|
|
80
80
|
Fatia F{n} — <título>
|
|
81
|
-
cobre: <cada Cn com o texto literal do cenário e
|
|
81
|
+
cobre: <cada Cn com o texto literal do cenário, a `prova:` e o `risco:`>
|
|
82
82
|
arquivos: <os `arquivos:` da fatia>
|
|
83
83
|
standards: <por id: o digest de `morph-spec standards --find` + as 3-8 regras que ESTA fatia aplica>
|
|
84
84
|
reuso: <as linhas de `## Reuso` que tocam a fatia — conceito, veredito, alvo>
|
|
@@ -108,6 +108,7 @@ relatório, não a mensagem** — a mensagem final pode não chegar ou vir resum
|
|
|
108
108
|
| `ok` | siga |
|
|
109
109
|
| `report-stale` | **não re-despache**: o agente escreveu depois de relatar; leia `observed` e atualize o relatório |
|
|
110
110
|
| `report-incomplete` + `resume` | `PARCIAL` na 1ª linha = a fatia não está pronta — o `report-check` o acusa como `report-incomplete`. Retome o MESMO agente por `SendMessage`, colando `tree.dirty`, `tree.commits` e o que falta |
|
|
111
|
+
| `report-blocked` + `ask-human` | `BLOQUEADO` na 1ª linha = decisão que não é do agente; a pergunta e as opções estão no relatório. **Não re-despache.** Plano ou ADR já decide → decida e registre; senão `AskUserQuestion`. Retome o MESMO agente com a resposta |
|
|
111
112
|
| `report-incomplete` + `complete-degraded` | complete a partir da mensagem, carimbe DEGRADADO e registre a falha no relatório |
|
|
112
113
|
| `work-landed-report-missing` | não re-despache; grave você o relatório, DEGRADADO |
|
|
113
114
|
| `no-work-no-report` / `report-missing-evidence-unknown` | re-despache uma vez; segunda ausência sem trabalho = escale |
|
|
@@ -128,10 +129,11 @@ config **antes** do verify: o stamp grava o HEAD, e código ou `config.json` com
|
|
|
128
129
|
|
|
129
130
|
Depois `morph-spec verify {feature} --json`, **sem filtro** — nunca recorte o
|
|
130
131
|
verify da feature. `--filter` é do loop do próprio sub-agente; o Gate 3 e o CI rodam a suíte
|
|
131
|
-
inteira. Nó de teste sem `expectedCount
|
|
132
|
+
inteira. Nó de teste sem `expectedCount`, ou fatia que apagou teste de propósito (limpeza, `morph-spec tests-audit`): acrescente `--record-baseline` a ESTA rodada — ela mede e
|
|
132
133
|
prova de uma vez; nunca um segundo verify só para gravar a baseline. Vermelho: corrija com `verify.feedback` (direto no código, ou re-despachando a fatia dona)
|
|
133
134
|
e rode de novo; **3 tentativas** sem verde → `AskUserQuestion` ("Verify de {feature} vermelho após 3
|
|
134
|
-
tentativas — aceitar com ressalva, continuar tentando, ou parar?").
|
|
135
|
+
tentativas — aceitar com ressalva, continuar tentando, ou parar?"). Quem conta é o `feature.json`:
|
|
136
|
+
`morph-spec next {feature}` devolve `fix` enquanto há tentativa e `escalate` na 3ª vermelha seguida.
|
|
135
137
|
|
|
136
138
|
**Cenário que julga saída de LLM** (`prova: eval`): a evidência é rodar, não ler código. Com nó
|
|
137
139
|
`runner: "evals"`: `morph-spec verify {feature} --filter {id-do-nó-evals} --skip-e2e --no-persist`
|
|
@@ -4,7 +4,7 @@ description: ADW cirúrgico de hotfix — resolve uma queda/bug crítico de prod
|
|
|
4
4
|
version: core
|
|
5
5
|
when_to_use: Use quando chegar "/morph-hotfix {feature}", quando a morph-plan rotear o pedido como workType hotfix, ou quando o usuário disser "produção caiu / hotfix / urgente" a respeito de um incidente
|
|
6
6
|
user-invocable: true
|
|
7
|
-
cliVersion: "9.
|
|
7
|
+
cliVersion: "9.3.0"
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# morph-hotfix
|
|
@@ -53,8 +53,8 @@ Escreva o `plan.md` mínimo (~25 linhas) numa única escrita: o hotfix nasce com
|
|
|
53
53
|
|
|
54
54
|
- `## Problema` — o sintoma em produção, o root cause que o scout localizou e o blast radius.
|
|
55
55
|
- `## Cenários` — dois, sempre:
|
|
56
|
-
- **C1** — o sintoma ausente: dado o estado que quebrava, quando a ação que falhava, então o resultado certo. `prova: teste` (ou `e2e`).
|
|
57
|
-
- **C2** —
|
|
56
|
+
- **C1** — o sintoma ausente: dado o estado que quebrava, quando a ação que falhava, então o resultado certo. `prova: teste` (ou `e2e`) e `risco: regressão` — o teste do bug real, o único que o hotfix escreve; o `plan-check` recusa o plano sem ele.
|
|
57
|
+
- **C2** — o blast radius: o que o fix poderia quebrar continua funcionando. `prova: leitura` (o caminho tocado, citado por `arquivo:linha`) ou um teste existente que já o cobre — nunca um teste novo.
|
|
58
58
|
- `## Decisões` com `### Conflitos com o acervo` (`nenhum` vale).
|
|
59
59
|
- `## Fatias` — uma, `### F1 — fix`, com `persona:`, `arquivos:` e `cobre: C1, C2`.
|
|
60
60
|
|
|
@@ -4,7 +4,7 @@ description: Cuida da RAM e da CPU da máquina enquanto o morph-spec trabalha
|
|
|
4
4
|
version: core
|
|
5
5
|
when_to_use: Use quando a máquina estiver lenta ou sem RAM, um comando pesado esperou ou foi recusado pela fila, sobrou worktree de sub-agent, ou vários sub-agents pesados vão rodar em paralelo
|
|
6
6
|
user-invocable: false
|
|
7
|
-
cliVersion: "9.
|
|
7
|
+
cliVersion: "9.3.0"
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# morph-machine
|
|
@@ -4,7 +4,7 @@ description: Conduz a AUTORIA de mídia gerada por IA — escolher o modelo pelo
|
|
|
4
4
|
version: core
|
|
5
5
|
when_to_use: Use quando a tarefa for PRODUZIR imagem ou vídeo por IA (catálogo, marketing, asset de LP) e alguém precisar decidir modelo, prompt e aprovação
|
|
6
6
|
user-invocable: true
|
|
7
|
-
cliVersion: "9.
|
|
7
|
+
cliVersion: "9.3.0"
|
|
8
8
|
stacks:
|
|
9
9
|
- dotnet
|
|
10
10
|
- nextjs
|
|
@@ -4,7 +4,7 @@ description: Leva uma feature do pedido ao plano aprovado — roteia o tipo de t
|
|
|
4
4
|
version: core
|
|
5
5
|
when_to_use: Use quando uma feature, bug ou chore começar ("/morph-plan {feature}", "vamos planejar X"), ou ao retomar uma feature cujo gate plan ainda não foi aprovado
|
|
6
6
|
user-invocable: true
|
|
7
|
-
cliVersion: "9.
|
|
7
|
+
cliVersion: "9.3.0"
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# morph-plan
|
|
@@ -66,19 +66,21 @@ Exit ≠ 0 → descarte o mapa e re-despache **uma** vez com as `failures` colad
|
|
|
66
66
|
|
|
67
67
|
O `create` já escreveu o esqueleto em `2-plan/plan.md` (leia e preencha; `chore` e `hotfix` nascem com o gate carimbado e criam o arquivo numa única escrita); gramática completa: `morph-spec plan-check --help`. `plan-check` é o único leitor; o que ele não reconhece, ninguém lê.
|
|
68
68
|
|
|
69
|
-
**Cenários** (`## Cenários`): `- **Cn** — Dado …, quando …, então …`, com `prova:` (`teste` | `e2e` | `eval` | `manual`) **na linha seguinte, sem linha em branco** — linha vazia encerra o cenário.
|
|
70
|
-
- **
|
|
69
|
+
**Cenários** (`## Cenários`): `- **Cn** — Dado …, quando …, então …`, com `prova:` (`teste` | `e2e` | `eval` | `manual` | `leitura`) **na linha seguinte, sem linha em branco** — linha vazia encerra o cenário.
|
|
70
|
+
- **Teste é por risco, não por código novo.** `prova: teste` exige `risco:` (`regra` | `dinheiro` | `contrato` | `concorrência` | `dado` | `regressão`) na linha abaixo: 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 citando `arquivo:linha` — ou `e2e` quando o fluxo é o que importa. O `plan-check` recusa `teste` sem `risco:`.
|
|
71
|
+
- **Observável**: que asserção (ou que linha de código) prova? Sem resposta, reescreva ou descarte.
|
|
71
72
|
- **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]`).
|
|
72
73
|
- **Saída de LLM** (o que o modelo responde) prova-se por `eval` de runtime, nunca pelo texto do prompt: evidência em `3-implement/evidencias/{Cn}.md`, path listado em `arquivos:` da fatia; com nó `runner: "evals"` (standard `ai-agents-evals-with-cache`) o cenário mora no dataset. No máximo 1 teste de estrutura da fonte do prompt.
|
|
73
74
|
- **"O comando de teste da raiz roda de ponta a ponta"** prova-se pelo `verify {feature}` (o nó `e2e` e o nó de teste `e2e: true` já percorrem a cadeia): nomeie essa rodada como a prova, nunca uma execução avulsa. Precisando mesmo rodar fora dele, numa linha só (o Bash não guarda o env entre chamadas): `eval "$(morph-spec env {feature})" && morph-spec heavy run -- <comando>` — o `COMPOSE_PROJECT_NAME` da feature é o que o `finish` derruba com os volumes. `prova: manual` registra passo e resultado em `3-implement/evidencias/{Cn}.md`.
|
|
74
|
-
- **Orçamento de teste**: um teste por cenário
|
|
75
|
+
- **Orçamento de teste**: um teste por cenário de risco — o cenário é a unidade, nunca a pós-condição nem o componente; variantes são linhas de tabela (`[Theory]`/`it.each`); acima disso, o porquê na linha do cenário. Nenhum teste que só fixa texto, prompt ou constante, nenhum de DTO/mapping, e o mesmo comportamento numa camada só (handler **ou** endpoint). Teste além do orçamento é escopo para o revisor. O `verify` avisa quando o teste passa da produção.
|
|
75
76
|
|
|
76
77
|
```markdown
|
|
77
78
|
## Cenários
|
|
78
79
|
- **C1** — Dado um tenant com `criterion_overrides`, quando ele é publicado, então a publicação é recusada nomeando a chave.
|
|
79
80
|
prova: teste
|
|
81
|
+
risco: contrato
|
|
80
82
|
- **C2** — Nenhum arquivo em `tenants/**` contém a chave `criterion_overrides`.
|
|
81
|
-
prova:
|
|
83
|
+
prova: leitura
|
|
82
84
|
> Por que artefato: a chave saiu do contrato; nenhum leitor de runtime a consulta.
|
|
83
85
|
|
|
84
86
|
## Fatias
|
|
@@ -136,7 +138,7 @@ A pergunta **nomeia a feature**: é por ela que o hook `state-sync` grava a apro
|
|
|
136
138
|
|
|
137
139
|
## 8. Deltas por tipo
|
|
138
140
|
|
|
139
|
-
- **`bug` (bug-lean):** a varredura roda inteira; a árvore é de repro, diagnóstico e comportamento do sistema, não de negócio; no modo automático, no máximo 1 pergunta de repro, e só se o pedido não deixar claro. `## Problema` traz repro, diagnóstico e causa provável;
|
|
141
|
+
- **`bug` (bug-lean):** a varredura roda inteira; a árvore é de repro, diagnóstico e comportamento do sistema, não de negócio; no modo automático, no máximo 1 pergunta de repro, e só se o pedido não deixar claro. `## Problema` traz repro, diagnóstico e causa provável; um cenário é o sintoma ausente com `prova: teste` e `risco: regressão` (o `plan-check` recusa bug sem ele) — esse é o teste que fica; `### Conflitos com o acervo` continua presente.
|
|
140
142
|
- **`chore`:** nasce com o gate `plan` carimbado por política. Escreva o plan.md curto numa única escrita (o gate já aprovado congela o arquivo depois de criado): `## Problema`, um ou dois cenários, `## Decisões` só com `### Conflitos com o acervo`, uma fatia. `plan-check` e `Skill(morph-apply)`.
|
|
141
143
|
- **`hotfix`:** é da `morph-hotfix`.
|
|
142
144
|
|
|
@@ -4,7 +4,7 @@ description: Conduz o Gate 3 — despacha verificador ∥ revisor (∥ seguranç
|
|
|
4
4
|
version: core
|
|
5
5
|
when_to_use: Use quando a fase review começar, com todas as fatias com relatório final e o verify da feature verde
|
|
6
6
|
user-invocable: true
|
|
7
|
-
cliVersion: "9.
|
|
7
|
+
cliVersion: "9.3.0"
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# morph-review
|
|
@@ -32,14 +32,15 @@ Commite o trabalho da feature (`git add <paths>` + `git commit`) e rode
|
|
|
32
32
|
`morph-spec verify {feature} --ci --json` com `run_in_background`. Ele empurra a branch, espera o run
|
|
33
33
|
e grava o stamp do artefato que o run publicou; sem CI, roda local e diz por quê.
|
|
34
34
|
**Sem `--filter`, sempre**: o Gate 3 roda a suíte inteira. Esta é a única execução de testes do Gate 3 — nenhum agente
|
|
35
|
-
roda a suíte de novo. `repeatOf` no JSON = nada mudou, use aquela rodada; `reason: "stamp-reused"` =
|
|
36
|
-
|
|
35
|
+
roda a suíte de novo. `repeatOf` no JSON = nada mudou, use aquela rodada; `reason: "stamp-reused"` = nada
|
|
36
|
+
fora de `.morph/` andou desde o último verify verde (local ou CI): ele vale e o CI não roda de novo.
|
|
37
37
|
- **Quando a saída mostrar `CI rodando`, despache (passo 3) sem esperar o fim**: o push e o
|
|
38
38
|
workflow já estão commitados e a foto da árvore não os acusa. Saiu `verify local`: espere o fim
|
|
39
39
|
antes do passo 3 — suíte local e `mutate` disputam a máquina. Exit 2 = trabalho fora de commit.
|
|
40
40
|
- Nó `e2e`: `pass` local → `morph-spec e2e status {feature} --json` → `baseUrl` vai ao verificador.
|
|
41
41
|
`fail` → correção (CI: `gh run view <id> --log-failed`). `skip` → quem classifica é o `gate-check`
|
|
42
|
-
(`classifyE2eSkip`): dispensa de projeto
|
|
42
|
+
(`classifyE2eSkip`): dispensa de projeto e `not applicable` (feature sem UI/spec/container no diff e sem
|
|
43
|
+
`prova: e2e`) passam; ambiente, `--skip-e2e` ou razão desconhecida pausam.
|
|
43
44
|
- `traceability.warnings` do verify vão ao revisor como sinal.
|
|
44
45
|
|
|
45
46
|
## 3. Dispatch — verificador ∥ revisor (∥ segurança)
|
|
@@ -49,7 +50,8 @@ Anote `base` (merge-base com a default) e `sha` (`git rev-parse HEAD`). Blast ra
|
|
|
49
50
|
símbolo público do diff, `morph-spec graph affected <símbolo> --prod --json`; sem grafo
|
|
50
51
|
(`no-graph`/`not-found`/`ambiguous`), siga sem o blast radius — o revisor cai para `git grep` — e
|
|
51
52
|
registre a ausência no recado a ele. Cada agente nasce com contexto fresco e **nunca recebe relatório anterior** (ancoragem).
|
|
52
|
-
|
|
53
|
+
`chore` tem review leve: só o revisor (∥ segurança, quando exigida) — sem verificador, sem cobertura de
|
|
54
|
+
`Cn`, sem mutação. Despache todos na mesma resposta:
|
|
53
55
|
```
|
|
54
56
|
dispatch Agent (subagent_type: "morph-verifier", model: {reviewModels.verifier.model}) with prompt:
|
|
55
57
|
feature, base, sha, JSON do verify (nós, e2e, URL) se já terminou, 1-design/mockups.md e
|
|
@@ -92,7 +94,8 @@ retomar, despache um novo.
|
|
|
92
94
|
## 5. Bloqueadores
|
|
93
95
|
O avaliador também erra: **antes de editar, reverifique cada bloqueador no código** — evidência que
|
|
94
96
|
não se reproduz vira pergunta ao mesmo agente, não correção. Confirmado: corrija (loop da
|
|
95
|
-
`morph-apply`), commite, rode
|
|
97
|
+
`morph-apply`), commite, rode `verify {feature}` local (o stamp verde dele é a prova: um `--ci` depois
|
|
98
|
+
reusa, não roda de novo) e uma foto `--before-eval` nova.
|
|
96
99
|
Retome por `SendMessage` TODO agente exigido, com o `sha` novo e `tree.dirty`/`tree.commits`: quem
|
|
97
100
|
abriu bloqueadores **confirme só os bloqueadores** (retomando o MESMO agente que os abriu); os outros
|
|
98
101
|
julgam só o delta `{sha anterior}..{sha novo}` — o que a correção abriu. `reviewModels.security.required`
|
|
@@ -4,7 +4,7 @@ description: Localiza um standard específico via busca server-side (morph-spec
|
|
|
4
4
|
version: core
|
|
5
5
|
when_to_use: Use quando precisar consultar um standard específico durante a implementação
|
|
6
6
|
user-invocable: true
|
|
7
|
-
cliVersion: "9.
|
|
7
|
+
cliVersion: "9.3.0"
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# morph-standards
|
|
@@ -4,7 +4,7 @@ description: Conduz a fase opcional de UI/UX de uma feature com plano aprovado
|
|
|
4
4
|
version: core
|
|
5
5
|
when_to_use: Use quando o ramo de design for tomado (morph-spec advance {feature} --design, depois do gate do plano) e antes de /morph-apply
|
|
6
6
|
user-invocable: true
|
|
7
|
-
cliVersion: "9.
|
|
7
|
+
cliVersion: "9.3.0"
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# morph-uiux
|
|
@@ -22,6 +22,7 @@ Entra depois do gate do plano, quando a `morph-plan` §6 decidiu que a feature c
|
|
|
22
22
|
|
|
23
23
|
- WCAG AA e todos os estados de componente interativo (default/hover/focus/disabled/loading/error), não só o caminho feliz.
|
|
24
24
|
- Reusa token e componente existentes antes de criar; token novo precisa de justificativa. Sistemas, não telas isoladas.
|
|
25
|
+
- Tarefa principal da tela em até 3 interações, uma ação primária por tela, voltar/cancelar sempre à vista. Erro diz o que fazer a seguir; conteúdo gerado por IA aparece marcado como tal.
|
|
25
26
|
- **Toda tela sai com o checklist de tela preenchido.** O `advance --design` já escreveu `1-design/mockups.md` a partir do template do pacote, a fonte única dos dez itens (tabela `### Checklist de tela` dentro de cada `## Tela:`). "Não se aplica" vale se escrito; `…`, vazio ou "a definir" reprovam. Design sem tela nova ou alterada (só componente ou estilo): apague o `1-design/mockups.md` semeado. É o checklist que faz a lista de 2 000 linhas paginar: o verificador do Gate 3 cobra cada item decidido em `## Checklist de tela`. Tela de dados segue os standards de `admin-app` (lista/tabela, formulário, feedback, estrutura de página); login, onboarding, primeira tela, estado vazio de destaque e sucesso são `brand-moment`.
|
|
26
27
|
- **Conformidade não basta: a régua estética vale junto.** Direção escrita antes de componente (`1-design/direcao.md`: conceito, metáfora, o que torna a tela memorável, o que ela não pode parecer; 2 ou 3 direções para tela nova ou momento de marca), celular desenhado e não derivado, movimento com versão estática digna. A régua é o standard `frontend-design-system-art-direction`.
|
|
27
28
|
- **Toda tela nova ou alterada tem protótipo renderizado e conferido por print.** `1-design/prototipos/{tela}.html` autossuficiente, com os tokens reais do projeto (lidos do CSS de tokens), desktop e celular lado a lado, controles animado/estático/pausar; prints `{tela}-1280.png` e `{tela}-390.png` ao lado, saídos do laço de autocrítica. A `## Tela:` aponta o protótipo na linha `**Protótipo:**`.
|
|
@@ -966,7 +966,7 @@
|
|
|
966
966
|
"errorbody": "#rule-2--error-path-tests-assert-the-response-body-not-just-the-status-code",
|
|
967
967
|
"test-budget": "#test-budget"
|
|
968
968
|
},
|
|
969
|
-
"digest": "Inject TimeProvider (never DateTime.UtcNow / DateOnly.FromDateTime(DateTime.UtcNow) directly in a handler or validator) and fake it with FakeTimeProvider (Microsoft.Extensions.TimeProvider.Testing) in integration tests — deterministic, no midnight-UTC flake, boundary dates (today vs. yesterday) become facts about the fake clock instead of when the suite happens to run. xUnit + WebApplicationFactory error-path tests (400/404/409) must assert the ProblemDetails response BODY (title/detail/violated field or error code), never just the HTTP status code — a status-only assertion lets the wrong validation rule or the wrong error branch pass silently as long as the status matches.
|
|
969
|
+
"digest": "Inject TimeProvider (never DateTime.UtcNow / DateOnly.FromDateTime(DateTime.UtcNow) directly in a handler or validator) and fake it with FakeTimeProvider (Microsoft.Extensions.TimeProvider.Testing) in integration tests — deterministic, no midnight-UTC flake, boundary dates (today vs. yesterday) become facts about the fake clock instead of when the suite happens to run. xUnit + WebApplicationFactory error-path tests (400/404/409) must assert the ProblemDetails response BODY (title/detail/violated field or error code), never just the HTTP status code — a status-only assertion lets the wrong validation rule or the wrong error branch pass silently as long as the status matches. Tests are paid for by risk, not new code: one test per plan scenario with risco: (rule, money, contract, concurrency, data, regression) plus the regression test of the bug fixed, more only with a one-line justification on the scenario; a scenario without risk (CRUD, DTO, mapping, wiring, constants) is prova: leitura and gets no test; one layer per behavior (handler or endpoint, not both); scenarios of the same behavior (edge cases included) are rows of one [Theory], never copied [Fact]s; no test whose only assertion is a copy, config or prose string (that is an analyzer or a publish-time validation, outside the suite count); shared fixtures before new helpers; bugs and risky scenarios get tests in any order — the implementer runs its own filter once, never red-green cycles nor production mutation (the Gate 3 mutation proof shows the test bites); a surviving mutant is killed by tightening an assertion or adding a row, not by a new test."
|
|
970
970
|
},
|
|
971
971
|
{
|
|
972
972
|
"id": "backend-dotnet-vsa-handler-patterns",
|
|
@@ -1516,7 +1516,7 @@
|
|
|
1516
1516
|
"e2e": "#test-file-co-location",
|
|
1517
1517
|
"test-budget": "#test-budget"
|
|
1518
1518
|
},
|
|
1519
|
-
"digest": "Vitest for unit tests, React Testing Library for component tests. Mock next/navigation with vi.mock. Test Server Actions with direct function calls. Use MSW for API mocking in component tests. Playwright for E2E tests against running app. Never test implementation details — test user behavior and output.
|
|
1519
|
+
"digest": "Vitest for unit tests, React Testing Library for component tests. Mock next/navigation with vi.mock. Test Server Actions with direct function calls. Use MSW for API mocking in component tests. Playwright for E2E tests against running app. Never test implementation details — test user behavior and output. Tests are paid for by risk, not new code: one test per plan scenario with risco: plus the regression test of the bug fixed, more only with a one-line justification on the scenario; a component is not a budget unit — rendering, layout and copy are prova: leitura or e2e; scenarios of the same behavior are rows of one it.each, never copied it() blocks; no test whose only assertion is a copy string (lint or publish-time validation instead); shared wrappers, MSW handlers and factories before new helpers; bugs and risky scenarios get tests in any order — the implementer runs its own filter once, never red-green cycles nor production mutation."
|
|
1520
1520
|
},
|
|
1521
1521
|
{
|
|
1522
1522
|
"id": "frontend-scroll-driven-frame-scrub",
|
|
@@ -1123,7 +1123,7 @@
|
|
|
1123
1123
|
"MCP server",
|
|
1124
1124
|
"MCP client"
|
|
1125
1125
|
],
|
|
1126
|
-
"verifiedAgainst": "Claude Code 2.1.282 MCP tooling — context7, Playwright (as plugin and as a project MCP server); GitHub via `gh` CLI; Neon via its MCP or `neonctl
|
|
1126
|
+
"verifiedAgainst": "Claude Code 2.1.282 MCP tooling — context7, Playwright (as plugin and as a project MCP server); GitHub via `gh` CLI; Neon via its MCP or `neonctl`; Microsoft Learn MCP (`https://learn.microsoft.com/api/mcp`, tools listed by `tools/list` on 2026-10-17).",
|
|
1127
1127
|
"provedBy": []
|
|
1128
1128
|
},
|
|
1129
1129
|
"frontend-admin-app-data-table": {
|
|
@@ -331,6 +331,8 @@ AIAgent instrumented = agent.AsBuilder()
|
|
|
331
331
|
|
|
332
332
|
## 7. What NOT to do (anti-patterns)
|
|
333
333
|
|
|
334
|
+
`morph-spec stack-scan` cobra três linhas desta tabela no código: Semantic Kernel, hosted agent service e versão flutuante.
|
|
335
|
+
|
|
334
336
|
| Wrong | Right |
|
|
335
337
|
|-------|-------|
|
|
336
338
|
| `using Microsoft.SemanticKernel` | `using Microsoft.Agents.AI` |
|
|
@@ -115,8 +115,12 @@ respectively and the test suite stays green.
|
|
|
115
115
|
|
|
116
116
|
## Test budget
|
|
117
117
|
|
|
118
|
-
The plan (`morph-plan`)
|
|
119
|
-
|
|
118
|
+
Tests are paid for by **risk, not by new code**. The plan (`morph-plan`) marks each scenario
|
|
119
|
+
with `prova:` and, for a test, a `risco:` (business rule, money, external contract, concurrency,
|
|
120
|
+
data integrity, regression). The budget is **one test per risky scenario, plus the regression test of
|
|
121
|
+
the bug being fixed**; going above it takes a one-line justification on the scenario. A scenario
|
|
122
|
+
without risk — CRUD, DTO, mapping, wiring, constants, layout — is `prova: leitura`: the Gate 3
|
|
123
|
+
verifier proves it by reading the code, and it gets no test.
|
|
120
124
|
|
|
121
125
|
- **Table before copy.** Scenarios of the same behavior — including its edge cases (null, empty,
|
|
122
126
|
boundary) — are rows of one `[Theory]` (`[InlineData]`/`[MemberData]`), never N `[Fact]` methods
|
|
@@ -125,11 +129,13 @@ regression test per bug fixed**. Going above it takes a one-line justification i
|
|
|
125
129
|
UI copy, tenant config or prose proves the file's version, not behavior. Enforce it with an
|
|
126
130
|
analyzer, a lint rule or a validation at publish time, outside the suite count. The versioned
|
|
127
131
|
prompt case (at most one structural test) is `ai-agents-testing-ai`.
|
|
132
|
+
- **One layer per behavior.** A rule tested in the handler is not tested again through the
|
|
133
|
+
endpoint or the SQL integration: pick the cheapest layer that bites (wiring is the verifier's).
|
|
128
134
|
- **Shared fixture by default.** Reuse the project's `WebApplicationFactory` fixture, fakes and
|
|
129
135
|
builders. Before writing a new helper, look for the existing one (`morph-spec graph explain
|
|
130
136
|
<Name>`; fallback without a graph: `grep -rn`).
|
|
131
|
-
- **Order is free, proof is not the implementer's:** a bug gets a regression test, and
|
|
132
|
-
gets its
|
|
137
|
+
- **Order is free, proof is not the implementer's:** a bug gets a regression test, and a risky
|
|
138
|
+
scenario gets its test, in whatever order the code comes out. The implementer does not run red→green
|
|
133
139
|
cycles nor mutate production code to watch a test fail; it runs its own filter once before the
|
|
134
140
|
report, and the Gate 3 mutation proof is what shows the test bites.
|
|
135
141
|
- **A surviving mutant** of the Gate 3 mutation proof is killed by tightening an existing assertion
|