@polymorphism-tech/morph-spec 7.6.0 → 7.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/bin/morph-spec.js +18 -0
- package/claude-plugin.json +1 -1
- package/docs/CHEATSHEET.md +1 -1
- package/docs/QUICKSTART.md +1 -1
- package/framework/CLAUDE.md +12 -4
- package/framework/MORPH.md +18 -10
- package/framework/agents.json +12 -4
- package/framework/hooks/README.md +8 -10
- package/framework/hooks/claude-code/core/gate-guard.js +36 -16
- package/framework/hooks/claude-code/core/protect-spec-files.js +20 -5
- package/framework/hooks/claude-code/core/tasks-json-guard.js +81 -0
- package/framework/hooks/shared/tasks-validator.js +68 -0
- package/framework/mcp/registry.json +5 -48
- package/framework/rubrics/backend.json +3 -3
- package/framework/rubrics/default.json +3 -3
- package/framework/rubrics/frontend.json +3 -3
- 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 +53 -40
- package/framework/skills/core/morph-brainstorming/SKILL.md +7 -12
- package/framework/skills/core/morph-eval/SKILL.md +10 -8
- package/framework/skills/core/morph-implement/SKILL.md +27 -21
- package/framework/skills/core/morph-plan/SKILL.md +12 -21
- package/framework/skills/core/morph-proposal/SKILL.md +3 -8
- package/framework/skills/core/morph-review/SKILL.md +30 -14
- package/framework/skills/core/morph-stack-scan/SKILL.md +1 -1
- package/framework/skills/core/morph-standards/SKILL.md +1 -1
- package/framework/standards/integration/mcp/mcp-tools.md +38 -43
- package/package.json +1 -1
- package/src/commands/project/doctor.js +36 -1
- package/src/commands/project/update.js +26 -2
- package/src/commands/state/approve.js +29 -4
- package/src/commands/state/score.js +88 -0
- package/src/commands/tasks/dag.js +68 -0
- package/src/dashboard/scanner/rubrics-scanner.js +1 -1
- package/src/lib/generators/recap-generator.js +1 -1
- package/src/lib/tasks/dispatch-planner.js +201 -0
- package/src/lib/validators/score-aggregator.js +3 -3
- package/src/scripts/setup-infra.js +43 -0
- package/src/utils/agents-installer.js +142 -10
- package/src/utils/hooks-installer.js +13 -5
- package/framework/rubrics/eval-rubric.json +0 -66
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* tasks-validator.js — minimal hook-side schema validation for tasks.json.
|
|
3
|
+
*
|
|
4
|
+
* Mirrors VALID_STATUS / VALID_EFFORT from src/lib/tasks/tasks-json-parser.js.
|
|
5
|
+
* Hooks are installed standalone into target projects (.morph/framework/hooks/),
|
|
6
|
+
* so they cannot import from src/ — the enum lives here, duplicated on purpose
|
|
7
|
+
* and covered by tests on both sides.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
export const VALID_TASK_STATUS = ['pending', 'in_progress', 'done', 'skipped', 'blocked'];
|
|
11
|
+
export const VALID_TASK_EFFORT = ['S', 'M', 'L', 'XL'];
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Validate the parts of a tasks.json document that hooks care about:
|
|
15
|
+
* a tasks[] array whose entries carry a valid status (and effort, when set).
|
|
16
|
+
* The production failure this guards: a leaf agent writing status "completed"
|
|
17
|
+
* (not in the enum) without any validation.
|
|
18
|
+
*
|
|
19
|
+
* @param {object} doc - parsed tasks.json
|
|
20
|
+
* @returns {{ valid: boolean, errors: string[] }}
|
|
21
|
+
*/
|
|
22
|
+
export function validateTasksDoc(doc) {
|
|
23
|
+
if (!doc || typeof doc !== 'object' || !Array.isArray(doc.tasks)) {
|
|
24
|
+
return { valid: false, errors: ['tasks.json must be an object with a tasks[] array'] };
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const errors = [];
|
|
28
|
+
for (const task of doc.tasks) {
|
|
29
|
+
const id = task?.id ?? '?';
|
|
30
|
+
if (!VALID_TASK_STATUS.includes(task?.status)) {
|
|
31
|
+
errors.push(`task '${id}': invalid status '${task?.status}' (valid: ${VALID_TASK_STATUS.join(', ')})`);
|
|
32
|
+
}
|
|
33
|
+
if (task?.effort !== undefined && !VALID_TASK_EFFORT.includes(task.effort)) {
|
|
34
|
+
errors.push(`task '${id}': invalid effort '${task.effort}' (valid: ${VALID_TASK_EFFORT.join(', ')})`);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return { valid: errors.length === 0, errors };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Planned-vs-real outputs divergence: tasks whose `outputs` changed relative
|
|
42
|
+
* to the previous document but whose `notes` is still empty. The divergence
|
|
43
|
+
* itself is legitimate (outputs is a mutable progress field) — shipping it
|
|
44
|
+
* without an explanatory note is what gets flagged.
|
|
45
|
+
*
|
|
46
|
+
* @param {object} oldDoc - parsed previous tasks.json
|
|
47
|
+
* @param {object} newDoc - parsed proposed tasks.json
|
|
48
|
+
* @returns {string[]} human-readable violation messages (empty = OK)
|
|
49
|
+
*/
|
|
50
|
+
export function outputsDivergenceViolations(oldDoc, newDoc) {
|
|
51
|
+
const violations = [];
|
|
52
|
+
const oldById = new Map((oldDoc?.tasks ?? []).map(t => [t.id, t]));
|
|
53
|
+
|
|
54
|
+
for (const task of newDoc?.tasks ?? []) {
|
|
55
|
+
const old = oldById.get(task.id);
|
|
56
|
+
if (!old) continue;
|
|
57
|
+
const outputsChanged =
|
|
58
|
+
JSON.stringify(old.outputs ?? []) !== JSON.stringify(task.outputs ?? []);
|
|
59
|
+
const hasNote = typeof task.notes === 'string' && task.notes.trim().length > 0;
|
|
60
|
+
if (outputsChanged && !hasNote) {
|
|
61
|
+
violations.push(
|
|
62
|
+
`task '${task.id}': outputs diverged from the plan but 'notes' is empty — ` +
|
|
63
|
+
`record why in 'notes' in the same edit`
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return violations;
|
|
68
|
+
}
|
|
@@ -58,50 +58,6 @@
|
|
|
58
58
|
"setupGuide": null
|
|
59
59
|
}
|
|
60
60
|
},
|
|
61
|
-
"github": {
|
|
62
|
-
"phases": ["tasks", "implement"],
|
|
63
|
-
"tools": ["get_repo", "search_code", "create_issue", "create_pull_request", "search_issues"],
|
|
64
|
-
"detection": "mcpServers includes github",
|
|
65
|
-
"fallback": "Bash `gh` CLI commands",
|
|
66
|
-
"usage": "Repo metadata, code search, issue creation, PR management",
|
|
67
|
-
"healthCheck": {
|
|
68
|
-
"toolPattern": "github",
|
|
69
|
-
"testCall": "Call any read-only GitHub tool (e.g., search_repositories or get_repo) to verify connectivity",
|
|
70
|
-
"testParams": {},
|
|
71
|
-
"successIndicator": "Returns repository data or search results without auth errors",
|
|
72
|
-
"failureHint": "GitHub MCP not responding. If using Docker, ensure Docker Desktop is running. Verify GITHUB_PERSONAL_ACCESS_TOKEN env var is set. Ensure the token has not expired and has the required scopes (repo, read:org)."
|
|
73
|
-
},
|
|
74
|
-
"install": {
|
|
75
|
-
"autoInstall": false,
|
|
76
|
-
"transport": "stdio",
|
|
77
|
-
"config": {
|
|
78
|
-
"type": "stdio",
|
|
79
|
-
"command": "docker",
|
|
80
|
-
"args": ["run", "-i", "--rm", "-e", "GITHUB_PERSONAL_ACCESS_TOKEN", "ghcr.io/github/github-mcp-server"],
|
|
81
|
-
"env": {
|
|
82
|
-
"GITHUB_PERSONAL_ACCESS_TOKEN": ""
|
|
83
|
-
}
|
|
84
|
-
},
|
|
85
|
-
"credentials": [
|
|
86
|
-
{
|
|
87
|
-
"name": "GitHub Personal Access Token",
|
|
88
|
-
"envVar": "GITHUB_PERSONAL_ACCESS_TOKEN",
|
|
89
|
-
"secret": true,
|
|
90
|
-
"helpUrl": "https://github.com/settings/tokens"
|
|
91
|
-
}
|
|
92
|
-
],
|
|
93
|
-
"prerequisites": [
|
|
94
|
-
{
|
|
95
|
-
"name": "Docker",
|
|
96
|
-
"checkCommand": "docker --version",
|
|
97
|
-
"installUrl": "https://docs.docker.com/get-docker/"
|
|
98
|
-
}
|
|
99
|
-
],
|
|
100
|
-
"stackRelevance": ["*"],
|
|
101
|
-
"warnings": [],
|
|
102
|
-
"setupGuide": "Official GitHub MCP Server (github/github-mcp-server). Requires Docker."
|
|
103
|
-
}
|
|
104
|
-
},
|
|
105
61
|
"vercel": {
|
|
106
62
|
"phases": ["implement"],
|
|
107
63
|
"tools": ["search_docs", "list_projects", "get_deployments", "get_deployment_logs", "manage_env_vars"],
|
|
@@ -234,7 +190,8 @@
|
|
|
234
190
|
{
|
|
235
191
|
"name": "Gemini API Key",
|
|
236
192
|
"envVar": "GEMINI_API_KEY",
|
|
237
|
-
"
|
|
193
|
+
"secret": true,
|
|
194
|
+
"helpUrl": "https://aistudio.google.com/apikey"
|
|
238
195
|
}
|
|
239
196
|
],
|
|
240
197
|
"prerequisites": ["Google AI Studio API key"],
|
|
@@ -247,8 +204,8 @@
|
|
|
247
204
|
"phaseMatrix": {
|
|
248
205
|
"proposal": ["context7"],
|
|
249
206
|
"uiux": ["playwright", "context7", "stitch", "gemini-image"],
|
|
250
|
-
"design": ["context7"
|
|
251
|
-
"tasks": ["context7"
|
|
252
|
-
"implement": ["context7", "playwright", "
|
|
207
|
+
"design": ["context7"],
|
|
208
|
+
"tasks": ["context7"],
|
|
209
|
+
"implement": ["context7", "playwright", "vercel", "gemini-image"]
|
|
253
210
|
}
|
|
254
211
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "backend",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "Backend-focused rubric. Higher weight on architecture, security, and package health.",
|
|
3
|
+
"version": "1.1.0",
|
|
4
|
+
"description": "Backend-focused rubric. Higher weight on architecture, security, and package health. Deterministic-validator rubric — approve threshold aligned to the single bar 9 from MORPH.md §0.",
|
|
5
5
|
"categories": {
|
|
6
6
|
"functionality": {
|
|
7
7
|
"weight": 0.25,
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
}
|
|
36
36
|
},
|
|
37
37
|
"thresholds": {
|
|
38
|
-
"approve":
|
|
38
|
+
"approve": 9.0,
|
|
39
39
|
"retry": 6.0
|
|
40
40
|
}
|
|
41
41
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "default",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "Default rubric for general-purpose projects. Balanced weights across all categories.",
|
|
3
|
+
"version": "1.1.0",
|
|
4
|
+
"description": "Default rubric for general-purpose projects. Balanced weights across all categories. Deterministic-validator rubric — approve threshold aligned to the single bar 9 from MORPH.md §0 (the LLM eval rubrics live in framework/evals/rubrics/*.md).",
|
|
5
5
|
"categories": {
|
|
6
6
|
"functionality": {
|
|
7
7
|
"weight": 0.30,
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
}
|
|
31
31
|
},
|
|
32
32
|
"thresholds": {
|
|
33
|
-
"approve":
|
|
33
|
+
"approve": 9.0,
|
|
34
34
|
"retry": 6.0
|
|
35
35
|
}
|
|
36
36
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "frontend",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "Frontend-focused rubric. Higher weight on design system compliance and UI quality.",
|
|
3
|
+
"version": "1.1.0",
|
|
4
|
+
"description": "Frontend-focused rubric. Higher weight on design system compliance and UI quality. Deterministic-validator rubric — approve threshold aligned to the single bar 9 from MORPH.md §0.",
|
|
5
5
|
"categories": {
|
|
6
6
|
"functionality": {
|
|
7
7
|
"weight": 0.25,
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
}
|
|
31
31
|
},
|
|
32
32
|
"thresholds": {
|
|
33
|
-
"approve":
|
|
33
|
+
"approve": 9.0,
|
|
34
34
|
"retry": 6.0
|
|
35
35
|
}
|
|
36
36
|
}
|
|
@@ -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-invoke: ao iniciar um novo projeto de LP com o template Umbraco genérico
|
|
6
6
|
user-invokable: true
|
|
7
|
-
cliVersion: "7.
|
|
7
|
+
cliVersion: "7.7.1"
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# create-umbraco-lp
|
|
@@ -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-invoke: ao receber instrução "use /migrate-lp-to-umbraco {projeto}" do usuário, ou quando o usuário descrever necessidade de migrar uma LP para Umbraco
|
|
6
6
|
user-invokable: true
|
|
7
|
-
cliVersion: "7.
|
|
7
|
+
cliVersion: "7.7.1"
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# migrate-lp-to-umbraco
|
|
@@ -4,7 +4,7 @@ description: Orquestra a execução autônoma de tasks de uma feature morph-spec
|
|
|
4
4
|
version: core
|
|
5
5
|
when-to-invoke: após Gate 2 aprovado, ao receber instrução "use /morph-apply" ou "implementar feature"
|
|
6
6
|
user-invokable: true
|
|
7
|
-
cliVersion: "7.
|
|
7
|
+
cliVersion: "7.7.1"
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# morph-apply
|
|
@@ -33,34 +33,29 @@ Executar todas as tasks pendentes de uma feature até `done`, aplicando `morph-e
|
|
|
33
33
|
### 1. Carregar contexto
|
|
34
34
|
|
|
35
35
|
- Ler `.morph/config/config.json → trust`.
|
|
36
|
-
-
|
|
37
|
-
```js
|
|
38
|
-
import { parseTasksJson, getReadyTasks, getPendingTasks, updateTaskStatus, writeTasksJson, groupTasks } from '../../../src/lib/tasks/tasks-json-parser.js';
|
|
39
|
-
const data = await parseTasksJson('.morph/features/{feature}/2-plan/tasks.json');
|
|
40
|
-
```
|
|
36
|
+
- **Use a tool Read** em `.morph/features/{feature}/2-plan/tasks.json` para carregar as tasks (ids, `dependencies`, `effort`, `group`, `status`, `doneCriteria`). Não escreva scripts para isso — o parse e a validação de schema acontecem no comando CLI do passo 2.
|
|
41
37
|
|
|
42
38
|
### 2. Análise de DAG — decisão sequencial vs paralelo
|
|
43
39
|
|
|
44
|
-
|
|
40
|
+
Rode o comando CLI (ele parseia o tasks.json, valida o schema e pondera o esforço):
|
|
45
41
|
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
-
const independentRoots = data.tasks.filter(t => t.dependencies.length === 0);
|
|
42
|
+
```
|
|
43
|
+
morph-spec dag {feature}
|
|
49
44
|
```
|
|
50
45
|
|
|
51
|
-
|
|
46
|
+
O comando analisa `dependencies`, `group` e `effort` das tasks pendentes e imprime a decisão pronta com justificativa. As regras que ele aplica (e que você segue):
|
|
52
47
|
|
|
53
|
-
1. Se `
|
|
54
|
-
2. **Fluxos de domínio independentes (o caso comum — backend ‖ frontend).**
|
|
55
|
-
3. **Raízes independentes (fan-out na largura).**
|
|
48
|
+
1. Se `tasks.length <= 3`: **sequencial** (overhead de Agent dispatch não compensa).
|
|
49
|
+
2. **Fluxos de domínio independentes (o caso comum — backend ‖ frontend).** Grupos cujas tasks não dependem de tasks de outro grupo formam fluxos paralelos (as cadeias só convergem, se convergirem, numa task final de integração). **≥ 2 fluxos independentes com peso de esforço suficiente** → dispache **um sub-agent por fluxo, em paralelo**, cada um com a persona e o standard do seu domínio (ver passo 3b).
|
|
50
|
+
3. **Raízes independentes (fan-out na largura).** Tasks 100% independentes (`dependencies: []`). **≥ 3 raízes com effort M ou maior** → dispachar essas raízes em **paralelo**, depois cair em sequencial para o resto.
|
|
56
51
|
4. Caso contrário: **sequencial puro** com auto-advance.
|
|
57
52
|
|
|
58
|
-
> O cap de **4 agentes simultâneos** (anti-padrão abaixo) vale para ambas as formas. Se um fluxo de domínio é uma cadeia longa, despache o **fluxo inteiro** a um sub-agent (que executa a cadeia em ordem internamente) — um agente por fluxo, não um agente por task.
|
|
53
|
+
> **Peso de esforço:** a decisão é ponderada por `effort` (S=1, M=2, L=4, XL=8). **Raízes e fluxos compostos só de tasks S não ganham dispatch próprio** — overhead > trabalho. O comando os lista como `inline` (você os executa no modo sequencial) ou os agrupa num **único** agente quando são muitos. O cap de **4 agentes simultâneos** (anti-padrão abaixo) vale para ambas as formas. Se um fluxo de domínio é uma cadeia longa, despache o **fluxo inteiro** a um sub-agent (que executa a cadeia em ordem internamente) — um agente por fluxo, não um agente por task.
|
|
59
54
|
|
|
60
|
-
|
|
55
|
+
Registre a linha de decisão que o comando imprimiu, por exemplo:
|
|
61
56
|
```
|
|
62
|
-
DAG
|
|
63
|
-
DAG
|
|
57
|
+
DAG: 14 tasks, fluxos {backend:9, frontend:2} independentes (peso 18‖4) → 2 fluxos paralelos + T14 (integração) sequencial no fim
|
|
58
|
+
DAG: 12 tasks, 4 raízes independentes (3 com peso ≥ M) → parallel batch (T1, T3, T7) + T9 (S) inline + restante sequencial
|
|
64
59
|
```
|
|
65
60
|
|
|
66
61
|
### 3a. Modo Sequencial (auto-advance)
|
|
@@ -68,13 +63,12 @@ DAG analysis: 12 tasks, 4 raízes independentes → parallel batch (T1, T3, T7,
|
|
|
68
63
|
Loop:
|
|
69
64
|
```
|
|
70
65
|
while (true):
|
|
71
|
-
ready =
|
|
66
|
+
ready = tasks 'pending' cujas dependencies estão todas 'done' (releia tasks.json)
|
|
72
67
|
if ready.length === 0:
|
|
73
68
|
break
|
|
74
69
|
|
|
75
70
|
task = ready[0]
|
|
76
|
-
|
|
77
|
-
writeTasksJson(tasksPath, data)
|
|
71
|
+
marcar task.status = 'in_progress' em tasks.json (tool Edit — só campos mutáveis)
|
|
78
72
|
|
|
79
73
|
// Loop de correção autônomo — a barra de qualidade é >= 9 (ver MORPH.md §0).
|
|
80
74
|
// Entre tasks NÃO há pausa humana: score < 9 dispara re-implementação, não AskUserQuestion.
|
|
@@ -86,25 +80,24 @@ while (true):
|
|
|
86
80
|
(no attempt > 1: passar o feedback priorizado do morph-eval anterior como input)
|
|
87
81
|
result = invoke Skill(morph-eval) with implementation output
|
|
88
82
|
if result.score > best.score: best = result
|
|
89
|
-
// Persista o score no feature.json autoritativo
|
|
90
|
-
//
|
|
91
|
-
//
|
|
92
|
-
//
|
|
93
|
-
|
|
83
|
+
// Persista o score no feature.json autoritativo — torna o evalScore um signal
|
|
84
|
+
// de disco no MOMENTO da implementação (não no fim). O hook gate-guard.js e o
|
|
85
|
+
// Gate 3 leem dali. O timestamp é carimbado server-side pelo CLI — NUNCA edite
|
|
86
|
+
// feature.json à mão para isso:
|
|
87
|
+
run: morph-spec score {feature} {task.id} {result.score} --dims {arch},{contracts},{quality},{tests}
|
|
94
88
|
|
|
95
89
|
if result.score >= 9:
|
|
96
90
|
break
|
|
97
91
|
|
|
98
92
|
if best.score >= 9:
|
|
99
|
-
|
|
93
|
+
marcar task.status = 'done' em tasks.json (tool Edit — atualize também outputs
|
|
94
|
+
reais e, se divergirem do planejado, registre o porquê em notes)
|
|
100
95
|
else:
|
|
101
96
|
// 3 tentativas sem atingir 9 — AQUI sim escala ao humano (escape definido).
|
|
102
97
|
// Esta é a única pausa do loop de implement, e só após esgotar o loop de correção.
|
|
103
98
|
registrar em recap.md: as 3 tentativas e o melhor score (best.score) com o feedback
|
|
104
99
|
AskUserQuestion: "Task {id} não atingiu score 9 após 3 tentativas (melhor: {best.score}) — aceitar mesmo assim, continuar tentando, ou pular?"
|
|
105
100
|
options: [aceitar (marca done com ressalva no recap), continuar tentando (+3 tentativas), pular task (marca blocked)]
|
|
106
|
-
|
|
107
|
-
writeTasksJson(tasksPath, data)
|
|
108
101
|
```
|
|
109
102
|
|
|
110
103
|
> **Por que loop e não pausa:** MORPH.md §0 manda auto-avançar entre tasks. Score baixo numa task individual é um problema de qualidade que o modelo deve **corrigir sozinho**, não delegar ao humano no meio da implementação. A pausa só existe quando o loop de correção se esgota (3 tentativas) — o "escape" do modelo autônomo. O trust mode não muda esse loop: ele governa os **gates** (proposal/plan/review), não o ciclo de correção intra-task.
|
|
@@ -113,18 +106,30 @@ while (true):
|
|
|
113
106
|
|
|
114
107
|
**Antes de despachar, escope cada agente ao seu domínio.** Um agente genérico sem persona nem standard não "conhece" as convenções daquele domínio — produz código fora do padrão. Para cada fluxo/task a despachar, resolva:
|
|
115
108
|
|
|
116
|
-
- **
|
|
109
|
+
- **Subagent:** em `.morph/framework/agents.json`, encontre o agente cujo `domains` cobre o `group`/domínio da task (ex: `backend` → `dotnet-senior`, `frontend` → `nextjs-expert`/`ui-designer`, `database` → `ef-modeler`). O `subagent_type` do dispatch é **`morph-{id}`** (ex.: `morph-dotnet-senior`) — os subagents são gerados de `agents.json` na instalação e a persona já é o **system prompt** deles; não a cole no prompt.
|
|
117
110
|
- **Standard(s):** do array `standards` desse mesmo agente em `agents.json` (e/ou do `mandate.md`), selecione o(s) path(s) `.md` relevante(s) à task.
|
|
111
|
+
- **Fallback (tipo não registrado):** se `morph-{id}` não estiver na sua lista de subagent types (stack não instalado neste projeto), use `subagent_type: "general-purpose"` colando o campo `persona` no prompt — e **registre nas `notes` da task**: `dispatch-fallback: general-purpose ({id} não registrado)`. O recap deve refletir o mecanismo real.
|
|
118
112
|
|
|
119
113
|
```
|
|
120
|
-
dispatch Agent (subagent_type: "
|
|
121
|
-
persona: <texto do campo `persona` do agente de domínio em agents.json>
|
|
114
|
+
dispatch Agent (subagent_type: "morph-{id}") with prompt:
|
|
122
115
|
contexto: spec.md, mandate.md, task description completa, doneCriteria
|
|
123
116
|
standard: <path(s) .md do(s) standard(s) do domínio — leia-o(s) antes de implementar>
|
|
124
117
|
instrução: |
|
|
125
|
-
|
|
118
|
+
Leia o(s) standard(s) indicado(s) antes de escrever código.
|
|
126
119
|
Implemente esta task (ou cadeia de tasks do fluxo) isoladamente.
|
|
127
120
|
Não modifique spec.md, mandate.md.
|
|
121
|
+
|
|
122
|
+
Protocolo obrigatório:
|
|
123
|
+
1. Ao concluir, ENTREGUE o relatório final — sua última mensagem é o relatório;
|
|
124
|
+
se a sessão permanecer viva aguardando, envie-o via SendMessage para main.
|
|
125
|
+
Terminar idle sem reportar é falha de protocolo.
|
|
126
|
+
2. NÃO toque em tasks.json, feature.json, state.json nem no board de sessão
|
|
127
|
+
(TaskCreate/TaskUpdate) — todo estado (status de task, scores, gates) é
|
|
128
|
+
gravado exclusivamente pelo orquestrador.
|
|
129
|
+
3. Você roda em batch paralelo: NÃO rode build/test da suíte compartilhada
|
|
130
|
+
(dotnet test / npm test do projeto inteiro) — edições concorrentes quebram
|
|
131
|
+
o build umas das outras; a verificação é do orquestrador, pós-batch.
|
|
132
|
+
|
|
128
133
|
Retorne:
|
|
129
134
|
- sumário das mudanças
|
|
130
135
|
- arquivos modificados (paths)
|
|
@@ -132,12 +137,16 @@ dispatch Agent (subagent_type: "general-purpose") with prompt:
|
|
|
132
137
|
- auto-score 0-10 (rubric: completeness, correctness, mandate compliance)
|
|
133
138
|
```
|
|
134
139
|
|
|
135
|
-
> Este template é deliberadamente paritário com o template sequencial em `morph-implement` §4: ambos
|
|
140
|
+
> Este template é deliberadamente paritário com o template sequencial em `morph-implement` §4: ambos despacham o **subagent registrado do domínio (`morph-{id}`)** com standard escopado e as mesmas cláusulas de protocolo. A única diferença é que aqui múltiplos agentes rodam ao mesmo tempo, um por fluxo independente.
|
|
141
|
+
|
|
142
|
+
> **Alternativa quando os fluxos tocam os mesmos projetos/arquivos** (ex.: o mesmo csproj de testes compartilhado): despache com `isolation: "worktree"` — cada agente trabalha numa working tree própria e o orquestrador integra ao final. Nesse caso a cláusula 3 vira "rode build/test apenas dentro do seu worktree".
|
|
136
143
|
|
|
137
|
-
Aguardar todos os agentes terminarem
|
|
138
|
-
-
|
|
144
|
+
Aguardar todos os agentes terminarem. **Se um agente ficar idle sem entregar o relatório, cobre-o via SendMessage e registre a falha de protocolo no recap.** Depois, sequencialmente:
|
|
145
|
+
- **Rodar a verificação compartilhada uma única vez** (build + suíte de testes do projeto) — os agentes não a rodaram (cláusula 3 do protocolo).
|
|
146
|
+
- Para cada relatório retornado:
|
|
139
147
|
- Aplicar `Skill(morph-eval)`.
|
|
140
|
-
-
|
|
148
|
+
- Gravar o score: `morph-spec score {feature} {taskId} {score} --dims {a},{c},{q},{t}`.
|
|
149
|
+
- Se score >= 9: marcar `done` em tasks.json (tool Edit — só campos mutáveis).
|
|
141
150
|
- Se score < 9: marcar `pending` novamente com nota — volta ao modo sequencial, onde o loop de correção (até 3 tentativas) busca o score 9.
|
|
142
151
|
|
|
143
152
|
Depois do batch paralelo, executar o restante em modo sequencial (passo 3a).
|
|
@@ -157,7 +166,7 @@ Sinais de risco para Gate 3:
|
|
|
157
166
|
- Presença de erros não resolvidos.
|
|
158
167
|
- Mudanças incidentais em standards/.morph/framework/.
|
|
159
168
|
|
|
160
|
-
Mesma lógica do Gate 1/Gate 2
|
|
169
|
+
Mesma lógica do Gate 1/Gate 2 (regras da tabela do MORPH.md §5, aplicadas em linguagem natural — sem scripts): `trust: manual` → pausa sempre; `standardsViolations` não-vazio → pausa; `evalScore < 9` → pausa; caso contrário → auto-passa.
|
|
161
170
|
|
|
162
171
|
**Persista a decisão no `feature.json` autoritativo da feature ANTES de apresentar ou auto-aprovar** — grave
|
|
163
172
|
`gateDecisions.review = { pause, reason, signals, trust, decidedAt: <ISO> }` em `.morph/features/{feature}/feature.json`
|
|
@@ -178,6 +187,9 @@ Mesma lógica do Gate 1/Gate 2 via `shouldPauseGate(config, signals)`:
|
|
|
178
187
|
- Não pular `morph-eval` — uma task só avança com score >= 9; abaixo disso, o loop de correção roda (não pausa).
|
|
179
188
|
- Não reescrever spec.md/mandate.md durante implement (são read-only após Gate 2 — `protect-spec-files.js` hook bloqueia).
|
|
180
189
|
- Não dispachar mais que 4 agentes em paralelo (sobrecarga de contexto + custo).
|
|
190
|
+
- Não dispachar um agente inteiro para uma raiz de effort S — overhead > trabalho; o `morph-spec dag` já as marca como `inline`.
|
|
191
|
+
- Não deixar sub-agent tocar `tasks.json`, `feature.json` ou o board de sessão — estado é do orquestrador (cláusula 2 do protocolo).
|
|
192
|
+
- Não editar `feature.json` à mão para gravar scores — use `morph-spec score` (timestamp server-side).
|
|
181
193
|
|
|
182
194
|
## Output esperado
|
|
183
195
|
|
|
@@ -193,5 +205,6 @@ Mesma lógica do Gate 1/Gate 2 via `shouldPauseGate(config, signals)`:
|
|
|
193
205
|
- `framework/skills/core/morph-implement/SKILL.md`
|
|
194
206
|
- `framework/skills/core/morph-eval/SKILL.md`
|
|
195
207
|
- `framework/skills/core/morph-review/SKILL.md`
|
|
196
|
-
- `
|
|
197
|
-
- `
|
|
208
|
+
- `morph-spec dag {feature}` — análise de DAG e decisão sequencial vs paralelo (ponderada por effort)
|
|
209
|
+
- `morph-spec score {feature} {task} {score} [--dims a,c,q,t]` — grava taskScores no feature.json com timestamp server-side
|
|
210
|
+
- `.morph/framework/MORPH.md` §5 — regras de pausa de gate (trust mode)
|
|
@@ -4,7 +4,7 @@ description: Entende o negócio, os usuários e a arquitetura antes de qualquer
|
|
|
4
4
|
version: core
|
|
5
5
|
when-to-invoke: ao receber um novo request de feature, antes de morph-plan
|
|
6
6
|
user-invokable: true
|
|
7
|
-
cliVersion: "7.
|
|
7
|
+
cliVersion: "7.7.1"
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# morph-brainstorming
|
|
@@ -75,17 +75,12 @@ O harness conhece o stack por padrão. Não pergunte sobre tecnologia a menos qu
|
|
|
75
75
|
|
|
76
76
|
5. Apresente o Gate 1 segundo trust mode.
|
|
77
77
|
|
|
78
|
-
Ler `.morph/config/config.json → trust` (default `"auto"`). Calcular sinais de risco
|
|
78
|
+
Ler `.morph/config/config.json → trust` (default `"auto"`). Calcular os sinais de risco em linguagem natural — sem scripts:
|
|
79
79
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
standardsViolations: /* lista de violações detectadas */,
|
|
85
|
-
};
|
|
86
|
-
const { pause, reason } = shouldPauseGate(config, signals);
|
|
87
|
-
const riskList = getRiskSignals(signals);
|
|
88
|
-
```
|
|
80
|
+
- `designSystemDivergence` = detectado no passo 3 (fontes de design conflitantes).
|
|
81
|
+
- `standardsViolations` = lista de violações detectadas.
|
|
82
|
+
|
|
83
|
+
**Decisão de pausa** (regras da tabela do MORPH.md §5): `trust: manual` → pausa sempre; `designSystemDivergence` → pausa; `standardsViolations` não-vazio → pausa; caso contrário → auto-passa (`pause: false`). Monte `reason` com o sinal que disparou.
|
|
89
84
|
|
|
90
85
|
**Persista a decisão no `feature.json` autoritativo da feature ANTES de apresentar ou auto-aprovar** — grave
|
|
91
86
|
`gateDecisions.proposal = { pause, reason, signals, trust, decidedAt: <ISO> }` em `.morph/features/{feature}/feature.json`
|
|
@@ -127,4 +122,4 @@ O harness conhece o stack por padrão. Não pergunte sobre tecnologia a menos qu
|
|
|
127
122
|
- `.morph/framework/MORPH.md` — seções 1 (Papéis), 2 (Fases), 5 (Gates), Trust Mode
|
|
128
123
|
- `.morph/framework/standards/STANDARDS.json` — para referência de standards relevantes à stack
|
|
129
124
|
- `framework/skills/core/morph-stack-scan/SKILL.md` — invocada na Parte 2
|
|
130
|
-
-
|
|
125
|
+
- `.morph/framework/MORPH.md` §5 — regras de pausa do Gate 1 (trust mode)
|
|
@@ -4,7 +4,7 @@ description: Avalia a qualidade de uma tarefa ou fase com rubricas graduadas 0-1
|
|
|
4
4
|
version: core
|
|
5
5
|
when-to-invoke: ao completar uma tarefa ou ao finalizar uma fase de implementação
|
|
6
6
|
user-invokable: false
|
|
7
|
-
cliVersion: "7.
|
|
7
|
+
cliVersion: "7.7.1"
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# morph-eval
|
|
@@ -17,11 +17,13 @@ O objetivo não é punir — é identificar o que melhorar antes de seguir adian
|
|
|
17
17
|
Após concluir uma tarefa ou ao finalizar a fase de implementação.
|
|
18
18
|
Também invocado ao receber o retorno de um sub-agent com auto-score < 7.
|
|
19
19
|
|
|
20
|
+
> **Escopo de fase (Gate 3):** a avaliação da feature inteira NÃO é feita por quem implementou — a `morph-review` despacha a persona `evaluator` de `agents.json` (contexto fresco, sem autoria; recebe mandate + diff + saída real dos testes) usando esta skill como guia. O eval por task pode ser inline; o score composto do Gate 3 vem sempre do avaliador independente.
|
|
21
|
+
|
|
20
22
|
## O que fazer
|
|
21
23
|
|
|
22
24
|
1. Identifique o escopo da avaliação: tarefa única ou fase completa.
|
|
23
25
|
|
|
24
|
-
2. Para cada dimensão, leia a rubrica correspondente em
|
|
26
|
+
2. Para cada dimensão, leia a rubrica correspondente em `.morph/framework/evals/rubrics/`:
|
|
25
27
|
- `architecture.md` — peso 30% — vertical slice, separação de responsabilidades, infra
|
|
26
28
|
- `contracts.md` — peso 25% — tipos, validação, tratamento de erros, naming
|
|
27
29
|
- `code-quality.md` — peso 25% — naming, ausência de code smells, padrões do stack
|
|
@@ -49,9 +51,9 @@ Também invocado ao receber o retorno de um sub-agent com auto-score < 7.
|
|
|
49
51
|
- Trace atualizado com `eval_score` e `eval_dimensions`
|
|
50
52
|
|
|
51
53
|
## Referências
|
|
52
|
-
-
|
|
53
|
-
-
|
|
54
|
-
-
|
|
55
|
-
-
|
|
56
|
-
-
|
|
57
|
-
-
|
|
54
|
+
- `.morph/framework/evals/rubrics/architecture.md` — rubrica de arquitetura (30%)
|
|
55
|
+
- `.morph/framework/evals/rubrics/contracts.md` — rubrica de contratos (25%)
|
|
56
|
+
- `.morph/framework/evals/rubrics/code-quality.md` — rubrica de qualidade (25%)
|
|
57
|
+
- `.morph/framework/evals/rubrics/test-coverage.md` — rubrica de cobertura (20%)
|
|
58
|
+
- `.morph/framework/evals/trace-schema.md` — formato canônico do trace
|
|
59
|
+
- `.morph/framework/MORPH.md` — seção 1 (papel Evaluator)
|
|
@@ -4,7 +4,7 @@ description: Guia a execução de cada tarefa de implementação com standard e
|
|
|
4
4
|
version: core
|
|
5
5
|
when-to-invoke: ao iniciar cada task de implementação listada em tasks.json
|
|
6
6
|
user-invokable: false
|
|
7
|
-
cliVersion: "7.
|
|
7
|
+
cliVersion: "7.7.1"
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# morph-implement
|
|
@@ -35,7 +35,7 @@ Ao iniciar cada task de `tasks.json` — antes de escrever qualquer código. Nor
|
|
|
35
35
|
3. Identifique a persona de agente adequada para a tarefa:
|
|
36
36
|
- **Use a tool Read em `.morph/framework/agents.json`** — não recupere a persona de memória. O registro existe para ser consultado; pular o Read significa que a persona vira um rótulo inventado em vez de uma seleção auditável. Encontre o agente cujo `domains` cobre o domínio da tarefa.
|
|
37
37
|
- Leia o campo `persona` do agente e adote esse ponto de vista durante a implementação. O array `standards` desse agente é o ponto de partida para o passo 2.
|
|
38
|
-
- O `**Persona:**` que você grava no recap (passo 6) **deve**
|
|
38
|
+
- O `**Persona:**` que você grava no recap (passo 6) **deve** refletir o mecanismo REAL usado: `morph-{id}` (dispatch pelo subagent registrado), `general-purpose (fallback: {id})` (tipo não registrado — registre também nas `notes` da task), ou `inline ({id})` (você executou inline adotando a persona). É assim que o reviewer audita contra o que o runtime realmente registrou — uma auto-declaração que não casa com o dispatch é drift.
|
|
39
39
|
|
|
40
40
|
4. Decida se a task deve ser despachada para um sub-agent. Dispatch é o **default** para o caso pesado/multi-domínio (ver `.morph/framework/MORPH.md` seção 6) — despache quando a task dispara qualquer gatilho:
|
|
41
41
|
- **Domínio diferente do que você tem carregado agora** (a persona do passo 3 difere da que você vinha usando) — gatilho mais importante: o especialista carrega seus próprios standards sem inchar seu contexto.
|
|
@@ -47,7 +47,7 @@ Ao iniciar cada task de `tasks.json` — antes de escrever qualquer código. Nor
|
|
|
47
47
|
|
|
48
48
|
Inline é aceitável só para tasks S/M **dentro do domínio já carregado**. Se for inline numa task que dispara um gatilho, registre a justificativa em uma linha no `decisions.md` — inline silencioso numa task pesada/multi-domínio é o anti-padrão da seção 6.
|
|
49
49
|
|
|
50
|
-
**Se for despachar — use
|
|
50
|
+
**Se for despachar — use `subagent_type: "morph-{id}"` do agente identificado no passo 3.** Os subagents são gerados de `agents.json` na instalação (`.claude/agents/morph-{id}.md`) e a persona é o **system prompt** deles — não a cole no prompt. **Fallback:** se `morph-{id}` não estiver na sua lista de subagent types (stack não instalado), use `general-purpose` colando o campo `persona` no prompt e registre nas `notes` da task: `dispatch-fallback: general-purpose ({id} não registrado)`. Template do prompt:
|
|
51
51
|
```
|
|
52
52
|
Implementar [ID]: [title da task]
|
|
53
53
|
|
|
@@ -56,10 +56,20 @@ Ao iniciar cada task de `tasks.json` — antes de escrever qualquer código. Nor
|
|
|
56
56
|
Arquivos relevantes: [lista de paths]
|
|
57
57
|
Done quando: [doneCriteria do tasks.json]
|
|
58
58
|
|
|
59
|
+
Protocolo obrigatório:
|
|
60
|
+
1. Ao concluir, ENTREGUE o relatório final — sua última mensagem é o relatório;
|
|
61
|
+
se a sessão permanecer viva aguardando, envie-o via SendMessage para main.
|
|
62
|
+
Terminar idle sem reportar é falha de protocolo.
|
|
63
|
+
2. NÃO toque em tasks.json, feature.json, state.json nem no board de sessão
|
|
64
|
+
(TaskCreate/TaskUpdate) — todo estado é gravado exclusivamente pelo
|
|
65
|
+
orquestrador.
|
|
66
|
+
3. Se você faz parte de um batch paralelo, NÃO rode build/test da suíte
|
|
67
|
+
compartilhada — a verificação é do orquestrador, pós-batch.
|
|
68
|
+
|
|
59
69
|
Retorne APENAS: (1) o que foi feito em 2-3 frases, (2) arquivos modificados,
|
|
60
70
|
(3) problemas encontrados e como foram resolvidos, (4) auto-score 0-10.
|
|
61
71
|
```
|
|
62
|
-
Após receber o retorno:
|
|
72
|
+
Após receber o retorno: o auto-score é **triagem, não aprovação** — se < 7, re-despache com o feedback específico (ou corrija inline) antes de prosseguir. Em qualquer caso, a task só avança quando `morph-eval` der score ≥ 9 (barra única do MORPH.md §0). Integre o relatório ao recap.md e vá para o passo 7.
|
|
63
73
|
|
|
64
74
|
**Se não — siga para o passo 5.**
|
|
65
75
|
|
|
@@ -74,7 +84,7 @@ Ao iniciar cada task de `tasks.json` — antes de escrever qualquer código. Nor
|
|
|
74
84
|
## T{N} — {title}
|
|
75
85
|
|
|
76
86
|
**Standard:** {standard-id} (`.morph/framework/standards/{path}.md`)
|
|
77
|
-
**Persona:** {
|
|
87
|
+
**Persona:** {mecanismo real — `morph-{id}` | `general-purpose (fallback: {id})` | `inline ({id})`}
|
|
78
88
|
**Files:**
|
|
79
89
|
- `{path}`
|
|
80
90
|
- `{path}`
|
|
@@ -93,26 +103,22 @@ Ao iniciar cada task de `tasks.json` — antes de escrever qualquer código. Nor
|
|
|
93
103
|
e o reviewer não consegue auditar de onde veio a decisão técnica.
|
|
94
104
|
|
|
95
105
|
7. Registre a entrada no trace de execução em `.morph/traces/YYYY-MM-DD-{feature}-implement.json`:
|
|
96
|
-
- Crie o arquivo se não existir (use o schema em
|
|
106
|
+
- Crie o arquivo se não existir (use o schema em `.morph/framework/evals/trace-schema.md`)
|
|
97
107
|
- Adicione entrada em `tasks[]` com: `id`, `title`, `status`, `standards_consulted`, `agent_persona`, `files_modified`, `sub_agent_dispatched`
|
|
98
108
|
- Deixe `eval_score` e `eval_dimensions` como `null` — serão preenchidos pelo morph-eval
|
|
99
109
|
- Se esta for a última tarefa da fase, preencha `completed_at`
|
|
100
110
|
|
|
101
|
-
8. Atualizar `tasks.json` marcando a task como `done`
|
|
102
|
-
|
|
103
|
-
```js
|
|
104
|
-
import { parseTasksJson, updateTaskStatus, writeTasksJson, getReadyTasks } from '../../../src/lib/tasks/tasks-json-parser.js';
|
|
105
|
-
const data = await parseTasksJson(tasksPath);
|
|
106
|
-
updateTaskStatus(data, task.id, 'done');
|
|
107
|
-
await writeTasksJson(tasksPath, data);
|
|
108
|
-
```
|
|
111
|
+
8. Atualizar `tasks.json` marcando a task como `done` — **direto com a tool `Edit` (ou `Write`)**, sem scripts:
|
|
109
112
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
113
|
+
- Mude apenas os campos mutáveis da task: `status` (enum válido: `pending`,
|
|
114
|
+
`in_progress`, `done`, `skipped`, `blocked` — nunca invente valores como
|
|
115
|
+
`"completed"`), `outputs` (paths realmente produzidos) e `notes`.
|
|
116
|
+
- **Se os `outputs` reais divergirem dos planejados**, registre o porquê em
|
|
117
|
+
`notes` na mesma edição — o hook exige a note quando há divergência.
|
|
118
|
+
- `tasks.json` é protegido após o Gate 2: o hook `protect-spec-files` faz diff
|
|
119
|
+
semântico e libera Write ou Edit desde que nenhum campo de contrato do plano
|
|
120
|
+
(`title`, `description`, `dependencies`, `effort`, `doneCriteria`, `group`)
|
|
121
|
+
mude e o schema continue válido.
|
|
116
122
|
|
|
117
123
|
9. Quando invocada por `morph-apply`, retornar o controle à skill orquestradora — ela decide qual task vem em seguida via `getReadyTasks(data)`. Quando invocada manualmente, verificar se há próxima task ready e iniciá-la imediatamente aplicando esta skill desde o passo 1 (auto-advance). Pause apenas se encontrar ambiguidade bloqueante não coberta pelo mandate.
|
|
118
124
|
|
|
@@ -127,4 +133,4 @@ Ao iniciar cada task de `tasks.json` — antes de escrever qualquer código. Nor
|
|
|
127
133
|
- `.morph/framework/agents.json` — personas dos agentes por domínio
|
|
128
134
|
- `.morph/framework/MORPH.md` seção 6 — critérios de dispatch e contrato de retorno do sub-agent
|
|
129
135
|
- `.morph/features/{feature}/2-plan/mandate.md` — contrato da feature
|
|
130
|
-
-
|
|
136
|
+
- `.morph/framework/schemas/tasks.schema.json` — schema de tasks.json (enum de status e campos)
|