@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.
- package/CLAUDE.md +10 -10
- package/README.md +4 -4
- package/bin/morph-spec.js +40 -43
- package/claude-plugin.json +1 -1
- package/docs/CHEATSHEET.md +1 -1
- package/docs/QUICKSTART.md +1 -1
- package/framework/CLAUDE.md +24 -30
- package/framework/MORPH.md +47 -123
- package/framework/agents.json +6 -374
- package/framework/commands/morph-apply.md +1 -1
- package/framework/commands/morph-archive.md +4 -3
- package/framework/commands/morph-hotfix.md +8 -30
- package/framework/commands/morph-infra.md +2 -2
- package/framework/commands/morph-media.md +10 -9
- package/framework/commands/morph-review.md +15 -0
- package/framework/commands/morph-status.md +1 -1
- package/framework/docs/CLI.md +83 -94
- package/framework/evals/review-checklist.md +45 -53
- package/framework/hooks/README.md +4 -4
- package/framework/hooks/claude-code/core/README.md +1 -1
- package/framework/hooks/claude-code/core/denylist.json +4 -4
- package/framework/hooks/claude-code/core/gate-guard.js +9 -0
- package/framework/hooks/claude-code/core/masked-exit.js +49 -0
- package/framework/hooks/claude-code/core/state-sync.js +4 -1
- package/framework/hooks/claude-code/core/telemetry-log.js +1 -2
- package/framework/hooks/claude-code/core/test-budget.js +10 -70
- package/framework/hooks/claude-code/core/validate-completion.js +11 -24
- package/framework/hooks/claude-code/core/worktree-context.js +5 -22
- package/framework/hooks/claude-code/core/worktree-create.js +6 -45
- package/framework/hooks/claude-code/core/worktree-env-inject.js +2 -10
- package/framework/hooks/shared/e2e-check.js +0 -0
- package/framework/hooks/shared/gate-event.js +2 -2
- package/framework/hooks/shared/graph-refresh.js +20 -3
- package/framework/hooks/shared/heavy-command.js +32 -1
- package/framework/hooks/shared/heavy-config.js +1 -1
- package/framework/hooks/shared/next-step.js +63 -24
- package/framework/hooks/shared/phase-utils.js +6 -9
- package/framework/hooks/shared/report-progress.js +86 -0
- package/framework/hooks/shared/state-reader.js +3 -2
- package/framework/hooks/statusline.js +332 -0
- package/framework/phases.json +32 -10
- package/framework/rules/ai-agents-standards.md +9 -1
- package/framework/rules/csharp-standards.md +4 -0
- package/framework/rules/ef-standards.md +20 -0
- package/framework/rules/frontend-standards.md +1 -1
- package/framework/rules/implement-report.md +25 -0
- package/framework/rules/infrastructure-standards.md +2 -0
- package/framework/rules/nextjs-standards.md +7 -0
- package/framework/rules/plan-grammar.md +53 -0
- package/framework/rules/ui-design.md +27 -0
- package/framework/schemas/config.schema.json +11 -18
- package/framework/schemas/feature.schema.json +7 -1
- package/framework/skills/core/create-umbraco-lp/SKILL.md +1 -1
- package/framework/skills/core/create-umbraco-lp/references/umbraco-lp-patterns.md +1 -1
- package/framework/skills/core/migrate-lp-to-umbraco/SKILL.md +2 -2
- package/framework/skills/core/morph-apply/SKILL.md +38 -166
- package/framework/skills/core/morph-apply/references/debugging.md +8 -0
- package/framework/skills/core/morph-apply/references/hotfix.md +42 -0
- package/framework/skills/core/morph-apply/references/machine.md +29 -0
- package/framework/skills/core/{morph-media/SKILL.md → morph-apply/references/media.md} +11 -74
- package/framework/skills/core/morph-apply/references/review.md +65 -0
- package/framework/skills/core/morph-plan/SKILL.md +42 -108
- package/framework/skills/core/morph-plan/references/critique.md +40 -0
- package/framework/skills/core/morph-plan/references/interview.md +25 -0
- package/framework/skills/core/morph-uiux/SKILL.md +47 -64
- package/framework/skills/core/morph-uiux/surfaces.json +32 -0
- package/framework/standards/STANDARDS.json +32 -5
- package/framework/standards/STANDARDS.meta.json +5 -1
- package/framework/standards/ai-agents/agent-session.md +2 -3
- package/framework/standards/ai-agents/context-providers.md +2 -2
- package/framework/standards/ai-agents/durable-workflows-hitl.md +2 -2
- package/framework/standards/ai-agents/providers/model-registry.md +2 -2
- package/framework/standards/ai-agents/rag-custom-pgvector.md +6 -6
- package/framework/standards/ai-agents/setup.md +1 -1
- package/framework/standards/ai-agents/tool-adapters.md +2 -2
- package/framework/standards/architecture/stack-baseline.md +47 -0
- package/framework/standards/backend/integrations/neon-auth/neon-auth.md +2 -4
- package/framework/standards/frontend/design-system/ai-image-generation.md +51 -12
- package/framework/standards/frontend/design-system/ai-video-generation.md +50 -4
- package/framework/standards/frontend/design-system/art-direction.md +2 -2
- package/framework/standards/frontend/scroll-driven/frame-scrub.md +12 -2
- package/framework/standards/frontend/scroll-driven/scroll-components.md +76 -4
- package/framework/templates/REGISTRY.json +1 -2
- package/framework/templates/context/ARCHITECTURE.md +1 -1
- package/framework/templates/docs/plan.md +12 -12
- package/framework/templates/docs/ui-mockups.md +17 -105
- package/package.json +1 -1
- package/src/commands/e2e/e2e.js +88 -23
- package/src/commands/graph/diff.js +74 -0
- package/src/commands/graph/status.js +1 -1
- package/src/commands/observability/cost.js +2 -4
- package/src/commands/observability/telemetry.js +1 -1
- package/src/commands/project/doctor.js +23 -39
- package/src/commands/project/fleet.js +5 -6
- package/src/commands/project/init.js +63 -36
- package/src/commands/project/stack-scan.js +3 -42
- package/src/commands/project/stack-set.js +26 -0
- package/src/commands/project/status.js +12 -13
- package/src/commands/project/update.js +39 -3
- package/src/commands/project/worktree.js +3 -14
- package/src/commands/state/advance.js +17 -17
- package/src/commands/state/approve.js +0 -6
- package/src/commands/state/archive.js +27 -1
- package/src/commands/state/auto-approval.js +1 -1
- package/src/commands/state/create.js +0 -5
- package/src/commands/state/gate-check.js +37 -30
- package/src/commands/state/gate-decision.js +3 -2
- package/src/commands/state/next.js +1 -0
- package/src/commands/state/recap.js +67 -0
- package/src/commands/state/report-check.js +36 -178
- package/src/commands/validation/plan-check.js +16 -11
- package/src/commands/validation/validate-feature.js +3 -3
- package/src/commands/validation/verify.js +3 -6
- package/src/core/paths/output-schema.js +3 -3
- package/src/core/state/state-manager.js +2 -4
- package/src/dashboard/app/components/CommandPalette.jsx +2 -5
- package/src/dashboard/app/components/GatePauses.jsx +116 -0
- package/src/dashboard/app/components/SessionMap.jsx +77 -0
- package/src/dashboard/app/pages/Activity.jsx +4 -0
- package/src/dashboard/app/pages/AgentMap.jsx +1 -2
- package/src/dashboard/app/pages/FeatureDetail.jsx +129 -186
- package/src/dashboard/app/pages/Features.jsx +7 -7
- package/src/dashboard/app/pages/PhasePipeline.jsx +1 -1
- package/src/dashboard/app/pages/Portfolio.jsx +40 -5
- package/src/dashboard/scanner/activity-scanner.js +29 -0
- package/src/dashboard/scanner/agents-scanner.js +11 -14
- package/src/dashboard/scanner/features-scanner.js +45 -24
- package/src/dashboard/scanner/project-api.js +11 -2
- package/src/dashboard/scanner/skills-scanner.js +11 -1
- package/src/lib/e2e/applicability.js +3 -1
- package/src/lib/git/tree-state.js +1 -16
- package/src/lib/graph/diff-tree.js +127 -0
- package/src/lib/graph/graphify-runtime.js +12 -5
- package/src/lib/installers/memory-installer.js +2 -2
- package/src/lib/installers/rules-installer.js +57 -0
- package/src/lib/installers/stack-assets.js +17 -0
- package/src/lib/installers/stack-sync.js +219 -0
- package/src/lib/plan/checks.js +55 -110
- package/src/lib/plan/critique.js +28 -0
- package/src/lib/plan/media.js +42 -0
- package/src/lib/plan/plan-html.js +125 -0
- package/src/lib/plan/plan-parser.js +34 -45
- package/src/lib/recap/piper.js +36 -0
- package/src/lib/recap/recap.js +232 -0
- package/src/lib/review-reports.js +28 -4
- package/src/lib/risk-detector.js +90 -11
- package/src/lib/specs/screen-checklist.js +33 -48
- package/src/lib/stack/dotnet-build-ignores.js +8 -9
- package/src/lib/telemetry/metrics.js +27 -29
- package/src/lib/validators/reuse/reuse-validator.js +1 -9
- package/src/lib/validators/verify-runner.js +0 -7
- package/src/lib/worktree/fleet-scanner.js +7 -20
- package/src/lib/worktree/worktree-audit.js +0 -37
- package/src/scripts/setup-infra.js +15 -50
- package/src/utils/agents-installer.js +27 -201
- package/src/utils/hooks-installer.js +11 -95
- package/framework/hooks/claude-code/core/warn-bash-source-write.js +0 -23
- package/framework/hooks/claude-code/statusline.py +0 -699
- package/framework/hooks/claude-code/statusline.sh +0 -11
- package/framework/hooks/shared/agent-reap.js +0 -74
- package/framework/hooks/shared/slice-progress.js +0 -110
- package/framework/skills/core/morph-hotfix/SKILL.md +0 -133
- package/framework/skills/core/morph-machine/SKILL.md +0 -46
- package/framework/skills/core/morph-review/SKILL.md +0 -129
- package/framework/skills/core/morph-standards/SKILL.md +0 -52
- package/framework/standards/PROVENANCE.json +0 -119
- package/src/commands/project/worktree-reap.js +0 -34
- package/src/commands/state/handoff.js +0 -65
- package/src/commands/validation/mutate.js +0 -263
- package/src/commands/validation/scout-check.js +0 -28
- package/src/lib/generators/handoff-generator.js +0 -172
- package/src/lib/mutation/adapters.js +0 -79
- package/src/lib/mutation/commands.js +0 -61
- package/src/lib/mutation/dotnet-introspection.js +0 -281
- package/src/lib/mutation/manifest.js +0 -144
- package/src/lib/mutation/plan.js +0 -44
- package/src/lib/mutation/report.js +0 -68
- package/src/lib/mutation/runner.js +0 -301
- package/src/lib/mutation/schemata.js +0 -60
- package/src/lib/mutation/snapshot.js +0 -65
- package/src/lib/mutation/subset.js +0 -109
- package/src/lib/mutation/trx.js +0 -50
- package/src/lib/scout/scout-check.js +0 -293
- package/src/lib/validators/traceability-check.js +0 -103
- package/src/lib/worktree/agent-reaper.js +0 -247
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
> **Keywords:** pinned gallery, horizontal scroll, modal, lightbox, parallax, count-up, reveal, scroll progress, accordion
|
|
6
6
|
> **Load When:** ui-designer active, or building a scroll-driven marketing page
|
|
7
7
|
|
|
8
|
-
**Verified against:** GSAP 3.13 (ScrollTrigger, matchMedia, context) + React 18 + Lenis 1.x, shipped to production. Last-verified: 2026-
|
|
8
|
+
**Verified against:** GSAP 3.13 (ScrollTrigger, matchMedia, context) + React 18 + Lenis 1.x, shipped to production. Last-verified: 2026-10-27.
|
|
9
9
|
|
|
10
10
|
---
|
|
11
11
|
|
|
@@ -114,6 +114,19 @@ useEffect(() => {
|
|
|
114
114
|
|
|
115
115
|
**Render overlays outside any pinned section.** A pin applies a `transform` to its container, which becomes the containing block for `position: fixed` and breaks the overlay. Put the modal in a fragment as a sibling of the section.
|
|
116
116
|
|
|
117
|
+
The markup: the backdrop closes on click, the panel stops propagation so a click inside does not close it, and the dialog carries its role.
|
|
118
|
+
|
|
119
|
+
```jsx
|
|
120
|
+
{active !== null && (
|
|
121
|
+
<div className={`lmodal${shown ? ' in' : ''}`} onClick={close} role="dialog" aria-modal="true">
|
|
122
|
+
<div className="lmodal__panel" data-lenis-prevent onClick={(e) => e.stopPropagation()}>
|
|
123
|
+
<button className="lmodal__close" onClick={close} aria-label="Close">×</button>
|
|
124
|
+
{/* content */}
|
|
125
|
+
</div>
|
|
126
|
+
</div>
|
|
127
|
+
)}
|
|
128
|
+
```
|
|
129
|
+
|
|
117
130
|
**Mark the scrollable panel `data-lenis-prevent`.** Stopping the smooth scroller is not enough: while stopped it calls `preventDefault()` on any `touchmove` that reaches it, so a panel with `overflow-y: auto` scrolls with the wheel and is frozen under a finger. Whatever sits at the bottom of the panel, usually the CTA, becomes unreachable on a phone. See `frontend/scroll-driven/smooth-scroll.md`, Section 3.
|
|
118
131
|
|
|
119
132
|
```jsx
|
|
@@ -199,6 +212,8 @@ Format for the locale at write time (the decimal comma above is pt-BR). Never an
|
|
|
199
212
|
|
|
200
213
|
## Section 7: Persistent Elements
|
|
201
214
|
|
|
215
|
+
These live at the app root, outside every section, and repeat on almost every marketing page.
|
|
216
|
+
|
|
202
217
|
**Slim translucent nav** that gains a background once scrolled:
|
|
203
218
|
|
|
204
219
|
```jsx
|
|
@@ -208,6 +223,19 @@ useEffect(() => {
|
|
|
208
223
|
window.addEventListener('scroll', onScroll, { passive: true })
|
|
209
224
|
return () => window.removeEventListener('scroll', onScroll)
|
|
210
225
|
}, [])
|
|
226
|
+
return (
|
|
227
|
+
<nav className={`nav${solid ? ' nav--solid' : ''}`}>
|
|
228
|
+
<a className="nav__logo" href="#top"><span>{BRAND}</span></a>
|
|
229
|
+
<div className="nav__links">{/* anchors */}</div>
|
|
230
|
+
<a className="btn btn-accent" href="#contact">{CTA}</a>
|
|
231
|
+
</nav>
|
|
232
|
+
)
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
```css
|
|
236
|
+
.nav { position: fixed; top: 0; inset-inline: 0; z-index: 100; display: flex; align-items: center;
|
|
237
|
+
justify-content: space-between; padding: 16px clamp(16px,5vw,48px); transition: background .3s, backdrop-filter .3s; }
|
|
238
|
+
.nav--solid { background: rgba(14,11,12,.7); backdrop-filter: blur(10px); border-bottom: 1px solid var(--hair); }
|
|
211
239
|
```
|
|
212
240
|
|
|
213
241
|
**Scroll progress bar** that prefers the smooth scroller when present:
|
|
@@ -225,20 +253,64 @@ useEffect(() => {
|
|
|
225
253
|
}, [])
|
|
226
254
|
```
|
|
227
255
|
|
|
256
|
+
```jsx
|
|
257
|
+
return <div className="scroll-progress" ref={ref} />
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
```css
|
|
261
|
+
.scroll-progress { position: fixed; top: 0; left: 0; z-index: 101; height: 3px; width: 100%;
|
|
262
|
+
transform: scaleX(0); transform-origin: 0 50%; background: linear-gradient(90deg, var(--accent), var(--accent-soft)); }
|
|
263
|
+
```
|
|
264
|
+
|
|
228
265
|
Transform-based progress (`scaleX` with `transform-origin: 0 50%`) stays on the compositor. Animating `width` does not.
|
|
229
266
|
|
|
267
|
+
**Floating chat button.** A fixed exit channel to the business's messaging app, always within reach. It is also the fallback when the lead form fails. For WhatsApp, a `wa.me` deep link with a prefilled message:
|
|
268
|
+
|
|
269
|
+
```jsx
|
|
270
|
+
<a className="chat-float" href={`https://wa.me/${PHONE}?text=${encodeURIComponent(MESSAGE)}`}
|
|
271
|
+
target="_blank" rel="noopener noreferrer" aria-label="Chat on WhatsApp">
|
|
272
|
+
<svg viewBox="0 0 24 24" width="26" height="26" aria-hidden="true">{/* icon path */}</svg>
|
|
273
|
+
</a>
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
```css
|
|
277
|
+
.chat-float { position: fixed; right: 20px; bottom: 20px; z-index: 90; width: 56px; height: 56px;
|
|
278
|
+
display: grid; place-items: center; border-radius: 50%; background: #25d366; color: #fff;
|
|
279
|
+
box-shadow: 0 8px 24px rgba(0,0,0,.35); transition: transform .25s; }
|
|
280
|
+
.chat-float:hover { transform: translateY(-3px) scale(1.05); }
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
Keep its z-index below the nav and overlays, and check that it does not cover a CTA on mobile.
|
|
284
|
+
|
|
230
285
|
---
|
|
231
286
|
|
|
232
287
|
## Section 8: Accordion
|
|
233
288
|
|
|
234
|
-
Use native `<details>`. It provides toggle, keyboard support and accessibility for free.
|
|
289
|
+
Use native `<details>`. It provides toggle, keyboard support and accessibility for free. On a landing page the FAQ sits right before the form, to clear objections.
|
|
290
|
+
|
|
291
|
+
```jsx
|
|
292
|
+
const FAQ = [{ q: '...', a: '...' }]
|
|
293
|
+
return (
|
|
294
|
+
<section className="section faq" id="faq">
|
|
295
|
+
<h2>{TITLE}</h2>
|
|
296
|
+
{FAQ.map((f) => (
|
|
297
|
+
<details className="faq__item" key={f.q}>
|
|
298
|
+
<summary>{f.q}<span className="faq__icon" aria-hidden="true">+</span></summary>
|
|
299
|
+
<p>{f.a}</p>
|
|
300
|
+
</details>
|
|
301
|
+
))}
|
|
302
|
+
</section>
|
|
303
|
+
)
|
|
304
|
+
```
|
|
235
305
|
|
|
236
306
|
```css
|
|
307
|
+
.faq__item { border-bottom: 1px solid var(--hair); padding: 18px 0; }
|
|
237
308
|
.faq__item summary { cursor: pointer; list-style: none; display: flex;
|
|
238
|
-
justify-content: space-between; align-items: center; }
|
|
309
|
+
justify-content: space-between; align-items: center; font-family: var(--serif); font-size: 1.15rem; }
|
|
239
310
|
.faq__item summary::-webkit-details-marker { display: none; }
|
|
240
|
-
.faq__item .faq__icon { transition: transform .3s; }
|
|
311
|
+
.faq__item .faq__icon { transition: transform .3s; color: var(--accent); }
|
|
241
312
|
.faq__item[open] .faq__icon { transform: rotate(45deg); }
|
|
313
|
+
.faq__item p { margin: 14px 0 0; color: var(--fg-dim); max-width: 70ch; }
|
|
242
314
|
```
|
|
243
315
|
|
|
244
316
|
Native `<details>` cannot animate its own height. If height animation is required, drive the inner content with GSAP rather than replacing the element with a custom widget.
|
|
@@ -332,10 +332,9 @@
|
|
|
332
332
|
"path": "docs/ui-mockups.md",
|
|
333
333
|
"category": "documentation",
|
|
334
334
|
"phase": "uiux",
|
|
335
|
-
"description": "
|
|
335
|
+
"description": "Por tela: protótipo HTML, direção escolhida, checklist de tela de 5 itens, componentes e fluxo",
|
|
336
336
|
"outputName": "mockups.md",
|
|
337
337
|
"placeholders": [
|
|
338
|
-
"DATE",
|
|
339
338
|
"FEATURE_NAME"
|
|
340
339
|
],
|
|
341
340
|
"deprecated": false,
|
|
@@ -88,7 +88,7 @@
|
|
|
88
88
|
## Centralidade (grafo de codebase)
|
|
89
89
|
|
|
90
90
|
Símbolos com mais conexões depois do saneamento — o que quebra mais se mudar.
|
|
91
|
-
Detalhe de um símbolo: `morph-spec graph explain <nome
|
|
91
|
+
Detalhe de um símbolo: `morph-spec graph explain <nome>` (sem grafo, símbolo ausente ou ambíguo: `grep -rn <nome>`).
|
|
92
92
|
|
|
93
93
|
| Símbolo | Arquivo | Grau |
|
|
94
94
|
|---------|---------|------|
|
|
@@ -15,10 +15,21 @@
|
|
|
15
15
|
|
|
16
16
|
- <o que foi assumido sem prova; "nenhuma" vale>
|
|
17
17
|
|
|
18
|
+
## Padrões assumidos
|
|
19
|
+
|
|
20
|
+
<!-- Valor de baixo risco que você decidiu sem perguntar, numerado: o usuário veta por número ("veta 2").
|
|
21
|
+
Auth, credencial, permissão, pagamento e apagar dado nunca entram aqui: viram pergunta. "nenhum" vale. -->
|
|
22
|
+
|
|
23
|
+
1. <padrão e o valor escolhido>
|
|
24
|
+
|
|
25
|
+
<!-- Opcional, só se algo muda de estado: uma seção "Estados" (h2) com uma transição por linha,
|
|
26
|
+
"- origem → destino: gatilho". O plan-check desenha a máquina em 2-plan/plan.html. -->
|
|
27
|
+
|
|
18
28
|
## Cenários
|
|
19
29
|
|
|
20
30
|
<!-- Um cenário por comportamento observável. `prova:` é teste | e2e | eval | manual | leitura.
|
|
21
31
|
`prova: teste` exige `risco:` regra | dinheiro | contrato | concorrência | dado | regressão; sem risco, `leitura`.
|
|
32
|
+
`degrau:` migrar | irreversível quando um revert não desfaz (toda migration é migrar); sem a linha, refazer.
|
|
22
33
|
Cenário que fala de arquivo, grep ou texto exige a linha "> Por que artefato:" logo abaixo. -->
|
|
23
34
|
|
|
24
35
|
- **C1** — Dado <estado>, quando <ação>, então <resultado observável>.
|
|
@@ -56,19 +67,8 @@ nenhum
|
|
|
56
67
|
|---|---|---|---|
|
|
57
68
|
| <conceito> | REUSAR | <arquivo ou símbolo existente> | |
|
|
58
69
|
|
|
59
|
-
## Fatias
|
|
60
|
-
|
|
61
|
-
<!-- Uma fatia = uma unidade de entrega (com ou sem dispatch), relatório em 3-implement/reports/Fn.md.
|
|
62
|
-
arquivos ≤ 8; superfície e depende são opcionais; toda Cn é coberta por alguma fatia. -->
|
|
63
|
-
|
|
64
|
-
### F1 — <título>
|
|
65
|
-
- persona: dotnet-senior
|
|
66
|
-
- standards: <id>
|
|
67
|
-
- arquivos: <caminho/a.cs>, <caminho/b.cs>
|
|
68
|
-
- cobre: C1
|
|
69
|
-
|
|
70
70
|
## Contrato de implementação
|
|
71
71
|
|
|
72
|
-
<Assinaturas, records, enums e rotas que
|
|
72
|
+
<Assinaturas, records, enums e rotas que a implementação honra; runtime do e2e; baseline de teste como comando.>
|
|
73
73
|
|
|
74
74
|
## Errata
|
|
@@ -1,119 +1,31 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Mockups: {{titleCase FEATURE_NAME}}
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
<!-- Uma `## Tela:` por tela nova ou alterada. As opções navegáveis ficam em 1-design/opcoes/{tela}.html;
|
|
4
|
+
a escolhida vira 1-design/prototipos/{tela}.html, com os prints {tela}-1280.png e {tela}-390.png ao lado,
|
|
5
|
+
e um seletor de estado (data-estado="vazio|carregando|erro", ?estado=) com {tela}-{estado}-390.png de cada
|
|
6
|
+
estado que o checklist diz que se aplica. Faltando um, o Gate uiux pausa. -->
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
## Screen Inventory
|
|
11
|
-
|
|
12
|
-
| Screen ID | Name | Route | Priority |
|
|
13
|
-
|-----------|------|-------|----------|
|
|
14
|
-
| **S001** | {Screen Name} | /path | High |
|
|
15
|
-
| **S002** | {Screen Name} | /path | Medium |
|
|
16
|
-
|
|
17
|
-
---
|
|
18
|
-
|
|
19
|
-
## Tela: {Primary Screen}
|
|
8
|
+
## Tela: {Nome da tela}
|
|
20
9
|
|
|
21
10
|
**Protótipo:** 1-design/prototipos/{tela}.html
|
|
22
|
-
|
|
23
|
-
O protótipo renderizado é a fonte visual da tela; os prints `{tela}-1280.png` e `{tela}-390.png` ficam ao lado dele, e sem os três arquivos o Gate uiux pausa. O layout em texto abaixo é opcional.
|
|
24
|
-
|
|
25
|
-
### Desktop Layout
|
|
26
|
-
|
|
27
|
-
```
|
|
28
|
-
┌─────────────────────────────────────┐
|
|
29
|
-
│ Header [User ▼] │
|
|
30
|
-
├─────────────────────────────────────┤
|
|
31
|
-
│ │
|
|
32
|
-
│ [Title] │
|
|
33
|
-
│ │
|
|
34
|
-
│ ┌──────────────────────────────┐ │
|
|
35
|
-
│ │ Main Content Area │ │
|
|
36
|
-
│ │ │ │
|
|
37
|
-
│ │ [Card 1] [Card 2] │ │
|
|
38
|
-
│ │ │ │
|
|
39
|
-
│ │ ┌────────────────────────┐ │ │
|
|
40
|
-
│ │ │ Data Table │ │ │
|
|
41
|
-
│ │ │ ... │ │ │
|
|
42
|
-
│ │ └────────────────────────┘ │ │
|
|
43
|
-
│ └──────────────────────────────┘ │
|
|
44
|
-
│ │
|
|
45
|
-
└─────────────────────────────────────┘
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
-
### Mobile Layout
|
|
49
|
-
|
|
50
|
-
```
|
|
51
|
-
┌──────────────────────┐
|
|
52
|
-
│ ☰ Title [User ▼]│
|
|
53
|
-
├──────────────────────┤
|
|
54
|
-
│ │
|
|
55
|
-
│ [Card 1] │
|
|
56
|
-
│ │
|
|
57
|
-
│ [Card 2] │
|
|
58
|
-
│ │
|
|
59
|
-
│ [View All Items] │
|
|
60
|
-
│ │
|
|
61
|
-
└──────────────────────┘
|
|
62
|
-
```
|
|
11
|
+
**Direção escolhida:** <qual opção de opcoes/{tela}.html e por quê, numa frase>
|
|
63
12
|
|
|
64
13
|
### Checklist de tela
|
|
65
14
|
|
|
66
|
-
Troque cada `…` pela decisão
|
|
15
|
+
Troque cada `…` pela decisão. "Não se aplica" vale se escrito; `…`, vazio ou "a definir" reprovam o Gate uiux.
|
|
67
16
|
|
|
68
17
|
| Item | Decisão |
|
|
69
18
|
|---|---|
|
|
70
|
-
|
|
|
71
|
-
|
|
|
72
|
-
|
|
|
73
|
-
|
|
|
74
|
-
|
|
|
75
|
-
| Estado vazio | … |
|
|
76
|
-
| Estado carregando | … |
|
|
77
|
-
| Estado de erro | … |
|
|
78
|
-
| Mobile | … |
|
|
79
|
-
| Abas | … |
|
|
80
|
-
|
|
81
|
-
---
|
|
82
|
-
|
|
83
|
-
## States
|
|
84
|
-
|
|
85
|
-
### Loading
|
|
86
|
-
- Show skeleton placeholders
|
|
87
|
-
- Spinner for primary content
|
|
88
|
-
|
|
89
|
-
### Empty
|
|
90
|
-
- Icon + message
|
|
91
|
-
- Clear call-to-action button
|
|
92
|
-
|
|
93
|
-
### Error
|
|
94
|
-
- Warning icon
|
|
95
|
-
- User-friendly message
|
|
96
|
-
- Retry button
|
|
97
|
-
|
|
98
|
-
### Success (After Action)
|
|
99
|
-
- Toast notification
|
|
100
|
-
- Auto-dismiss after 3s
|
|
101
|
-
|
|
102
|
-
---
|
|
103
|
-
|
|
104
|
-
## Interactions
|
|
105
|
-
|
|
106
|
-
- **Hover**: Subtle elevation increase
|
|
107
|
-
- **Click**: Scale down (0.98)
|
|
108
|
-
- **Transitions**: Fade-in 200ms
|
|
19
|
+
| Lista no servidor | … |
|
|
20
|
+
| Estados vazio e carregando | … |
|
|
21
|
+
| Erro com próxima ação | … |
|
|
22
|
+
| Uma ação primária | … |
|
|
23
|
+
| Celular | … |
|
|
109
24
|
|
|
110
|
-
|
|
25
|
+
### Componentes
|
|
111
26
|
|
|
112
|
-
|
|
113
|
-
- [ ] 44x44px minimum touch targets
|
|
114
|
-
- [ ] Focus indicators visible
|
|
115
|
-
- [ ] Proper heading hierarchy (H1 → H2 → H3)
|
|
27
|
+
- <componente do design system reusado, ou novo e por quê>
|
|
116
28
|
|
|
117
|
-
|
|
29
|
+
### Fluxo
|
|
118
30
|
|
|
119
|
-
|
|
31
|
+
1. <passo do usuário até o resultado>
|
package/package.json
CHANGED
package/src/commands/e2e/e2e.js
CHANGED
|
@@ -16,7 +16,9 @@ import {
|
|
|
16
16
|
deleteStamp,
|
|
17
17
|
getStackStatus,
|
|
18
18
|
isDockerAvailable,
|
|
19
|
+
isHostOnlyStack,
|
|
19
20
|
lintComposeIsolation,
|
|
21
|
+
resetE2eData,
|
|
20
22
|
readStamp,
|
|
21
23
|
resolveComposeFile,
|
|
22
24
|
resolveSpecCommand,
|
|
@@ -34,8 +36,9 @@ import { readHeavyConfig } from '../../../framework/hooks/shared/heavy-config.js
|
|
|
34
36
|
const EXIT_USAGE = 2;
|
|
35
37
|
const EXIT_BUSY = 3;
|
|
36
38
|
|
|
37
|
-
// No compose file is a usage error here, unlike in the verify node where
|
|
38
|
-
|
|
39
|
+
// No compose file and no e2e.hostServices is a usage error here, unlike in the verify node where
|
|
40
|
+
// it is a graded skip. `composeFile` is null for a host-only stack.
|
|
41
|
+
function resolveContext(featureName, { json = false, onMissing } = {}) {
|
|
39
42
|
const root = resolvePrimaryRoot(process.cwd());
|
|
40
43
|
const found = findWorktreeForFeature(featureName, root);
|
|
41
44
|
const cwd = found?.path ?? root;
|
|
@@ -43,11 +46,13 @@ function resolveContext(featureName, { json = false } = {}) {
|
|
|
43
46
|
const config = readConfig(cwd) ?? readConfig(root);
|
|
44
47
|
const composeFile = resolveComposeFile(cwd, config);
|
|
45
48
|
|
|
46
|
-
if (!composeFile) {
|
|
49
|
+
if (!composeFile && !isHostOnlyStack(config, composeFile)) {
|
|
47
50
|
const message =
|
|
48
|
-
`No compose file found in ${cwd} (compose.yaml, docker-compose.yml, …). ` +
|
|
49
|
-
'Set
|
|
50
|
-
|
|
51
|
+
`No compose file found in ${cwd} (compose.yaml, docker-compose.yml, …) and no e2e.hostServices. ` +
|
|
52
|
+
'Set e2e.composeFile in .morph/config/config.json if it lives elsewhere, ' +
|
|
53
|
+
'or declare the app in e2e.hostServices when it has no dependency to containerize.';
|
|
54
|
+
if (onMissing) onMissing(message, cwd);
|
|
55
|
+
else if (json) console.log(JSON.stringify({ error: message, tree: cwd }, null, 2));
|
|
51
56
|
else console.error(chalk.red(`\n❌ ${message}\n`));
|
|
52
57
|
return null;
|
|
53
58
|
}
|
|
@@ -86,7 +91,7 @@ export async function e2eUpCommand(featureName, options = {}) {
|
|
|
86
91
|
|
|
87
92
|
// Isolation BEFORE the docker check: a compose that hardcodes host ports or container names
|
|
88
93
|
// collides with a sibling worktree whatever we inject.
|
|
89
|
-
const lint = lintComposeIsolation(readComposeText(ctx.cwd, ctx.composeFile));
|
|
94
|
+
const lint = ctx.composeFile ? lintComposeIsolation(readComposeText(ctx.cwd, ctx.composeFile)) : { findings: [] };
|
|
90
95
|
const isolation = decideIsolation({ findings: lint.findings, insideWorktree: ctx.insideWorktree });
|
|
91
96
|
if (isolation.status === 'fail') {
|
|
92
97
|
if (options.json) {
|
|
@@ -106,7 +111,7 @@ export async function e2eUpCommand(featureName, options = {}) {
|
|
|
106
111
|
}
|
|
107
112
|
|
|
108
113
|
const exec = options._exec ?? defaultExec;
|
|
109
|
-
if (!isDockerAvailable(ctx.cwd, exec)) {
|
|
114
|
+
if (ctx.composeFile && !isDockerAvailable(ctx.cwd, exec)) {
|
|
110
115
|
console.error(chalk.red('\n❌ Docker is not available in this environment.\n'));
|
|
111
116
|
process.exit(EXIT_USAGE);
|
|
112
117
|
}
|
|
@@ -149,7 +154,7 @@ export async function e2eUpCommand(featureName, options = {}) {
|
|
|
149
154
|
tree: ctx.tree,
|
|
150
155
|
composeFile: ctx.composeFile,
|
|
151
156
|
status: failed ? 'fail' : 'pass',
|
|
152
|
-
reused: result.stack.reused,
|
|
157
|
+
reused: ctx.composeFile ? result.stack.reused : hostServices.reused,
|
|
153
158
|
reason: result.reason,
|
|
154
159
|
auth: { status: auth.status, reason: auth.reason },
|
|
155
160
|
...(hostServices.status === 'skip' ? {} : { hostServices: hostServices.services }),
|
|
@@ -164,7 +169,9 @@ export async function e2eUpCommand(featureName, options = {}) {
|
|
|
164
169
|
if (result.output) console.error(result.output);
|
|
165
170
|
for (const w of result.warnings) console.error(chalk.yellow(`⚠️ ${w}`));
|
|
166
171
|
} else {
|
|
167
|
-
const how =
|
|
172
|
+
const how = !ctx.composeFile
|
|
173
|
+
? chalk.gray(`(${result.stack.reason})`)
|
|
174
|
+
: result.stack.reused ? chalk.gray(`(${result.stack.reason})`) : chalk.gray('(built)');
|
|
168
175
|
console.log(chalk.green(`\n✓ Stack up — ${ctx.projectName} ${how}`));
|
|
169
176
|
console.log(chalk.gray(` tree: ${ctx.tree}`));
|
|
170
177
|
for (const s of hostServices.services) {
|
|
@@ -190,18 +197,24 @@ export async function e2eStatusCommand(featureName, options = {}) {
|
|
|
190
197
|
if (!ctx) process.exit(EXIT_USAGE);
|
|
191
198
|
|
|
192
199
|
const exec = options._exec ?? defaultExec;
|
|
193
|
-
const
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
200
|
+
const hostServices = ctx.gitCommonDir ? hostServicesStatus(ctx.gitCommonDir, ctx.projectName) : [];
|
|
201
|
+
const status = ctx.composeFile
|
|
202
|
+
? getStackStatus(ctx.cwd, {
|
|
203
|
+
composeFile: ctx.composeFile,
|
|
204
|
+
projectName: ctx.projectName,
|
|
205
|
+
env: ctx.env,
|
|
206
|
+
}, exec)
|
|
207
|
+
: {
|
|
208
|
+
up: hostServices.length > 0 && hostServices.every((h) => h.alive),
|
|
209
|
+
healthy: hostServices.length > 0 && hostServices.every((h) => h.alive && h.health !== false),
|
|
210
|
+
services: [],
|
|
211
|
+
};
|
|
198
212
|
|
|
199
213
|
const stamp = computeSourceStamp(ctx.cwd, exec);
|
|
200
214
|
const previous = ctx.gitCommonDir ? readStamp(ctx.gitCommonDir, ctx.projectName) : null;
|
|
201
215
|
const fresh = Boolean(stamp && previous?.stamp === stamp);
|
|
202
216
|
|
|
203
217
|
const spec = resolveSpecCommand(ctx.cwd, ctx.config, ctx.config?.project?.frontendPath);
|
|
204
|
-
const hostServices = ctx.gitCommonDir ? hostServicesStatus(ctx.gitCommonDir, ctx.projectName) : [];
|
|
205
218
|
|
|
206
219
|
const payload = {
|
|
207
220
|
feature: featureName,
|
|
@@ -228,7 +241,7 @@ export async function e2eStatusCommand(featureName, options = {}) {
|
|
|
228
241
|
console.log('');
|
|
229
242
|
console.log(chalk.bold(`E2E stack — ${ctx.projectName}`));
|
|
230
243
|
console.log(chalk.gray(`tree: ${ctx.tree}`));
|
|
231
|
-
console.log(chalk.gray(`compose: ${ctx.composeFile}`));
|
|
244
|
+
console.log(chalk.gray(`compose: ${ctx.composeFile ?? 'none (host services only)'}`));
|
|
232
245
|
console.log(chalk.gray(`url: ${payload.baseUrl}`));
|
|
233
246
|
console.log('');
|
|
234
247
|
console.log(` up ${status.up ? chalk.green('yes') : chalk.gray('no')}`);
|
|
@@ -257,12 +270,14 @@ export async function e2eDownCommand(featureName, options = {}) {
|
|
|
257
270
|
if (!ctx) process.exit(EXIT_USAGE);
|
|
258
271
|
|
|
259
272
|
const hostServices = stopHostServices(ctx.gitCommonDir, ctx.projectName);
|
|
260
|
-
const result =
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
273
|
+
const result = ctx.composeFile
|
|
274
|
+
? tearDownStack(ctx.cwd, {
|
|
275
|
+
composeFile: ctx.composeFile,
|
|
276
|
+
projectName: ctx.projectName,
|
|
277
|
+
env: ctx.env,
|
|
278
|
+
volumes: Boolean(options.volumes),
|
|
279
|
+
}, options._exec ?? defaultExec)
|
|
280
|
+
: { status: 'pass', output: '' };
|
|
266
281
|
const stampDeleted = deleteStamp(ctx.gitCommonDir, ctx.projectName);
|
|
267
282
|
const cleanup = afterHeavyRun({ projectPath: ctx.cwd, config: readHeavyConfig(ctx.cwd) });
|
|
268
283
|
|
|
@@ -288,6 +303,56 @@ export async function e2eDownCommand(featureName, options = {}) {
|
|
|
288
303
|
process.exit(0);
|
|
289
304
|
}
|
|
290
305
|
|
|
306
|
+
// Between the verifier's flows: the next flow starts from the seeded state, not from the rows the
|
|
307
|
+
// previous one left. Exit 1 only on fail; skip (nothing configured to reset) is exit 0.
|
|
308
|
+
export async function e2eResetCommand(featureName, options = {}) {
|
|
309
|
+
const emit = (payload) => {
|
|
310
|
+
if (options.json) {
|
|
311
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
312
|
+
} else if (payload.status === 'fail') {
|
|
313
|
+
console.error(chalk.red(`\n✗ ${payload.reason}\n`));
|
|
314
|
+
if (payload.output) console.error(payload.output);
|
|
315
|
+
} else if (payload.status === 'skip') {
|
|
316
|
+
console.log(chalk.gray(`\n○ e2e reset: ${payload.reason}\n`));
|
|
317
|
+
} else {
|
|
318
|
+
const what = payload.reset.mode === 'command'
|
|
319
|
+
? `e2e.resetCommand (${payload.reset.command})`
|
|
320
|
+
: payload.reset.mode === 'volumes'
|
|
321
|
+
? `recreated ${payload.reset.services.join(', ')} (volumes ${payload.reset.volumes.join(', ')})`
|
|
322
|
+
: 'no data reset';
|
|
323
|
+
console.log(chalk.green(`\n✓ e2e reset — ${what}`));
|
|
324
|
+
if (payload.auth.reason) console.log(chalk.gray(` auth: ${payload.auth.reason}`));
|
|
325
|
+
console.log('');
|
|
326
|
+
}
|
|
327
|
+
process.exit(payload.status === 'fail' ? 1 : 0);
|
|
328
|
+
};
|
|
329
|
+
|
|
330
|
+
const ctx = resolveContext(featureName, {
|
|
331
|
+
...options,
|
|
332
|
+
onMissing: (message, tree) => emit({ feature: featureName, tree, status: 'fail', reason: message }),
|
|
333
|
+
});
|
|
334
|
+
if (!ctx) return;
|
|
335
|
+
|
|
336
|
+
const result = resetE2eData(ctx.cwd, {
|
|
337
|
+
config: ctx.config,
|
|
338
|
+
composeFile: ctx.composeFile,
|
|
339
|
+
projectName: ctx.projectName,
|
|
340
|
+
gitCommonDir: ctx.gitCommonDir,
|
|
341
|
+
env: ctx.env,
|
|
342
|
+
}, options._exec ?? defaultExec);
|
|
343
|
+
|
|
344
|
+
emit({
|
|
345
|
+
feature: featureName,
|
|
346
|
+
project: ctx.projectName,
|
|
347
|
+
tree: ctx.tree,
|
|
348
|
+
status: result.status,
|
|
349
|
+
reason: result.reason,
|
|
350
|
+
reset: result.reset,
|
|
351
|
+
auth: result.auth,
|
|
352
|
+
...(result.status === 'fail' && result.output ? { output: result.output } : {}),
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
|
|
291
356
|
function stoppedAnything({ killed, skipped, warnings }) {
|
|
292
357
|
return killed.length + skipped.length + warnings.length > 0;
|
|
293
358
|
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { execFileSync } from 'child_process';
|
|
3
|
+
import { loadGraph, getFreshness, staleCause } from '../../lib/graph/graph-loader.js';
|
|
4
|
+
import { parseDiff, buildDiffTree, renderDiffTree, declarationDrift } from '../../lib/graph/diff-tree.js';
|
|
5
|
+
import { detectDefaultBranch } from '../../lib/git/default-branch.js';
|
|
6
|
+
|
|
7
|
+
const git = (cwd, ...args) => execFileSync('git', args, { cwd, stdio: 'pipe' }).toString();
|
|
8
|
+
|
|
9
|
+
function fail(options, payload, lines) {
|
|
10
|
+
if (options.json) console.log(JSON.stringify({ ok: false, ...payload }, null, 2));
|
|
11
|
+
else for (const [tone, text] of lines) console.error(chalk[tone](text));
|
|
12
|
+
process.exit(1);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// Only the committed range: the graph is a snapshot of HEAD, so a diff of the working tree would put
|
|
16
|
+
// lines the graph never saw against symbols at the wrong place.
|
|
17
|
+
export async function graphDiffCommand(options = {}) {
|
|
18
|
+
const projectPath = options.path || process.cwd();
|
|
19
|
+
const loaded = loadGraph(projectPath);
|
|
20
|
+
if (!loaded.ok) {
|
|
21
|
+
fail(options, { reason: 'no-graph', detail: loaded.reason }, [
|
|
22
|
+
['yellow', `\n○ Sem grafo (${loaded.reason}): leia o diff cru (git diff).\n`],
|
|
23
|
+
]);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const freshness = getFreshness(projectPath, loaded.graph);
|
|
27
|
+
if (freshness.state !== 'fresh') {
|
|
28
|
+
fail(options, { reason: 'stale-graph', freshness }, [
|
|
29
|
+
['yellow', `\n○ O grafo não é deste commit (${staleCause(freshness)}): a árvore sairia com símbolos e linhas errados.`],
|
|
30
|
+
['gray', ' Rode `morph-spec graph refresh` e repita; ou leia o diff cru (git diff).\n'],
|
|
31
|
+
]);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
let base = options.base;
|
|
35
|
+
try {
|
|
36
|
+
base ??= git(projectPath, 'merge-base', detectDefaultBranch(projectPath) || 'main', 'HEAD').trim();
|
|
37
|
+
} catch {
|
|
38
|
+
fail(options, { reason: 'no-base' }, [['yellow', '\n○ Sem base para o diff: passe --base <ref>.\n']]);
|
|
39
|
+
}
|
|
40
|
+
const changes = parseDiff(git(projectPath, 'diff', '-U0', '--no-renames', '--no-color', base, 'HEAD'));
|
|
41
|
+
let uncommitted = [];
|
|
42
|
+
try {
|
|
43
|
+
const dirty = new Set(git(projectPath, 'diff', '--name-only', 'HEAD').split('\n').filter(Boolean));
|
|
44
|
+
uncommitted = [...changes.keys()].filter((f) => dirty.has(f));
|
|
45
|
+
} catch { uncommitted = []; }
|
|
46
|
+
|
|
47
|
+
const tree = buildDiffTree(loaded.graph, changes);
|
|
48
|
+
const drift = declarationDrift(tree, (file) => {
|
|
49
|
+
try { return git(projectPath, 'show', `HEAD:${file}`).split(/\r?\n/); } catch { return []; }
|
|
50
|
+
});
|
|
51
|
+
if (drift.length) {
|
|
52
|
+
fail(options, { reason: 'graph-drift', drift }, [
|
|
53
|
+
['yellow', `\n○ O grafo diz ser deste commit, mas ${drift.length} símbolo(s) não estão na linha que ele aponta (ex.: ${drift[0].label} em ${drift[0].file}:${drift[0].line}).`],
|
|
54
|
+
['gray', ' A extração leu arquivos diferentes do commit. Rode `morph-spec graph build` e repita; ou leia o diff cru (git diff).\n'],
|
|
55
|
+
]);
|
|
56
|
+
}
|
|
57
|
+
const payload = { ok: true, base, head: freshness.headCommit, ...tree, uncommitted };
|
|
58
|
+
if (options.json) {
|
|
59
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const { added, modified, entries, files } = tree.counts;
|
|
64
|
+
console.log(chalk.green(`\n✅ ${added} novo(s) +, ${modified} alterado(s) ~, ${entries} entrada(s), em ${files} arquivo(s) — ${String(base).slice(0, 8)}..${String(freshness.headCommit).slice(0, 8)}`));
|
|
65
|
+
for (const line of renderDiffTree(tree)) console.log(` ${line}`);
|
|
66
|
+
if (tree.unmapped.length) {
|
|
67
|
+
console.log(chalk.gray(`\n Sem símbolo tocado (só import/topo do arquivo, config, doc, ou fora do .graphifyignore) — leia no diff:`));
|
|
68
|
+
for (const f of tree.unmapped) console.log(chalk.gray(` ${f}`));
|
|
69
|
+
}
|
|
70
|
+
if (uncommitted.length) {
|
|
71
|
+
console.log(chalk.yellow(`\n ⚠ Mudança não commitada em ${uncommitted.length} arquivo(s) do diff: ela não está nesta árvore. Commit e repita.`));
|
|
72
|
+
}
|
|
73
|
+
console.log('');
|
|
74
|
+
}
|
|
@@ -37,7 +37,7 @@ export async function graphStatusCommand(options = {}) {
|
|
|
37
37
|
console.log(chalk.yellow(`\n○ Sem grafo (${loaded.reason})`));
|
|
38
38
|
console.log(chalk.gray(` ${loaded.path}`));
|
|
39
39
|
console.log(chalk.gray(' Gere com: morph-spec graph build'));
|
|
40
|
-
console.log(chalk.gray(' Sem grafo,
|
|
40
|
+
console.log(chalk.gray(' Sem grafo, as skills seguem por grep normalmente.\n'));
|
|
41
41
|
return;
|
|
42
42
|
}
|
|
43
43
|
|
|
@@ -111,11 +111,9 @@ function printMetricsBody(m) {
|
|
|
111
111
|
const tov = m.testOutsideVerify;
|
|
112
112
|
const timed = (t) => `${String(t.runs).padStart(4)}× ${fmtDuration(t.measuredMs / 1000).padStart(8)}`
|
|
113
113
|
+ (t.unmeasured ? chalk.gray(` (${t.unmeasured} sem duração)`) : '');
|
|
114
|
-
console.log('\n' + chalk.white.bold('Teste fora do verify
|
|
114
|
+
console.log('\n' + chalk.white.bold('Teste fora do verify')
|
|
115
115
|
+ chalk.gray(` · de ${tov.total.runs} testes+verifies, ${fmtDuration(tov.total.measuredMs / 1000)} cronometrados`
|
|
116
116
|
+ (tov.total.unmeasured ? `, ${tov.total.unmeasured} sem duração` : '')));
|
|
117
|
-
console.log(` sub-agente rodando verify cheio ${timed(tov.subagentVerify)}`);
|
|
118
|
-
console.log(` implementador, 3ª rodada em diante ${timed(tov.implementerRound3Plus)}`);
|
|
119
117
|
console.log(` agente de review rodando teste ${timed(tov.reviewTests)}`);
|
|
120
118
|
console.log(chalk.gray(` ${tov.reviewTests.byPersona.runs}× pela persona morph-* (o que o hook test-budget reconhece);`
|
|
121
119
|
+ ' o resto veio de um nome de dispatch (ver-, rev-, gate3-…)'));
|
|
@@ -163,7 +161,7 @@ function printMetricsBody(m) {
|
|
|
163
161
|
return;
|
|
164
162
|
}
|
|
165
163
|
for (const [gate, g] of gates) {
|
|
166
|
-
console.log(` gate ${gate}: ${g.decisions} decisões, ${g.paused} pausas → ${g.approvedAfterPause} aprovadas como estavam, `
|
|
164
|
+
console.log(` gate ${gate}: ${g.decisions} decisões, ${g.paused} pausas (${g.pausedForHuman} para o humano) → ${g.approvedAfterPause} aprovadas como estavam, `
|
|
167
165
|
+ `${g.reworkedAfterPause} retrabalhadas${g.unapproved ? `, ${g.unapproved} desaprovações` : ''}`);
|
|
168
166
|
for (const [signal, s] of Object.entries(g.bySignal)) {
|
|
169
167
|
console.log(chalk.gray(` ${signal}: ${s.paused} pausas, ${s.approvedAfterPause} aprovadas como estavam, ${s.reworkedAfterPause} retrabalhadas`));
|
|
@@ -35,7 +35,7 @@ export function telemetryCommand(options = {}) {
|
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
section('Skills', summary.skills, s => `${chalk.green(s.name.padEnd(24))} ${s.count}`);
|
|
38
|
-
section('
|
|
38
|
+
section('Sub-agents despachados', summary.personas, p => `${chalk.magenta(p.name.padEnd(24))} ${p.count}`);
|
|
39
39
|
section('Base de conhecimento (standards lidos explicitamente)', summary.standards,
|
|
40
40
|
s => `${chalk.yellow(String(s.count).padStart(3))}× ${s.path}`);
|
|
41
41
|
if (summary.standards.length || summary.tools.some(t => t.name === 'Read')) {
|