@polymorphism-tech/morph-spec 8.43.0 → 8.45.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 +16 -13
- package/README.md +10 -10
- package/bin/morph-spec.js +10 -13
- package/bin/validate.js +379 -379
- package/claude-plugin.json +1 -1
- package/docs/CHEATSHEET.md +2 -4
- package/docs/QUICKSTART.md +6 -6
- package/framework/CLAUDE.md +6 -8
- package/framework/MORPH.md +39 -45
- package/framework/agents.json +8 -9
- package/framework/commands/morph-apply.md +1 -3
- package/framework/commands/morph-hotfix.md +1 -1
- package/framework/commands/morph-proposal.md +2 -2
- package/framework/docs/CLI.md +22 -17
- package/framework/evals/review-checklist.md +90 -0
- package/framework/evals/trace-schema.md +2 -12
- package/framework/hooks/README.md +2 -3
- package/framework/hooks/claude-code/core/heavy-gate.js +1 -1
- package/framework/hooks/claude-code/core/protect-spec-files.js +2 -2
- package/framework/hooks/claude-code/core/telemetry-log.js +2 -2
- package/framework/hooks/claude-code/core/trace-autogen.js +1 -22
- package/framework/hooks/claude-code/core/validate-completion.js +3 -56
- package/framework/hooks/claude-code/core/worktree-env-inject.js +1 -4
- package/framework/hooks/claude-code/statusline.sh +11 -11
- package/framework/hooks/git/commit-msg/conventional-commits.sh +33 -33
- package/framework/hooks/git/pre-commit/agents.sh +25 -25
- package/framework/hooks/git/pre-commit/orchestrator.sh +64 -64
- package/framework/hooks/git/pre-commit/specs.sh +50 -50
- package/framework/hooks/git/pre-push/run-tests.sh +44 -44
- package/framework/hooks/shared/build-check.js +1 -1
- package/framework/hooks/shared/comment-scanner.js +1 -1
- package/framework/hooks/shared/phase-utils.js +0 -5
- package/framework/hooks/shared/stdin-reader.js +26 -26
- package/framework/phases.json +4 -6
- package/framework/rubrics/backend.json +1 -1
- package/framework/rubrics/default.json +1 -1
- package/framework/rubrics/frontend.json +1 -1
- package/framework/rules/ai-agents-standards.md +1 -0
- package/framework/rules/morph-workflow.md +6 -6
- package/framework/schemas/feature.schema.json +2 -28
- package/framework/schemas/tasks.schema.json +6 -2
- package/framework/skills/README.md +1 -2
- 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 +145 -246
- package/framework/skills/core/morph-brainstorming/SKILL.md +1 -1
- package/framework/skills/core/morph-hotfix/SKILL.md +5 -3
- package/framework/skills/core/morph-implement/SKILL.md +101 -201
- package/framework/skills/core/morph-machine/SKILL.md +3 -3
- package/framework/skills/core/morph-media/SKILL.md +1 -1
- package/framework/skills/core/morph-plan/SKILL.md +171 -287
- package/framework/skills/core/morph-proposal/SKILL.md +4 -5
- package/framework/skills/core/morph-review/SKILL.md +109 -430
- package/framework/skills/core/morph-stack-scan/SKILL.md +1 -1
- package/framework/skills/core/morph-standards/SKILL.md +2 -2
- package/framework/skills/core/morph-uiux/SKILL.md +2 -3
- package/framework/standards/STANDARDS.json +54 -10
- package/framework/standards/ai-agents/agent-archetypes.md +1 -1
- package/framework/standards/ai-agents/agent-session.md +1 -1
- package/framework/standards/ai-agents/agent-spec.md +1 -1
- package/framework/standards/ai-agents/batching.md +1 -1
- package/framework/standards/ai-agents/context-providers.md +1 -1
- package/framework/standards/ai-agents/conversational-agent-with-phases.md +1 -1
- package/framework/standards/ai-agents/cost-and-budget.md +1 -1
- package/framework/standards/ai-agents/durable-workflows-hitl.md +1 -1
- package/framework/standards/ai-agents/guardrails.md +1 -1
- package/framework/standards/ai-agents/llm-runtime-defaults.md +1 -1
- package/framework/standards/ai-agents/mcp-server.md +1 -1
- package/framework/standards/ai-agents/mcp-tools.md +1 -1
- package/framework/standards/ai-agents/media-pipeline.md +1 -1
- package/framework/standards/ai-agents/media-video.md +1 -1
- package/framework/standards/ai-agents/middleware-patterns.md +1 -1
- package/framework/standards/ai-agents/modalities-image-gen.md +1 -1
- package/framework/standards/ai-agents/modalities-speech-to-text.md +1 -1
- package/framework/standards/ai-agents/modalities-text-to-speech.md +1 -1
- package/framework/standards/ai-agents/modalities-vision.md +1 -1
- package/framework/standards/ai-agents/multi-agent-patterns.md +1 -1
- package/framework/standards/ai-agents/observability-patterns.md +3 -3
- package/framework/standards/ai-agents/production.md +2 -2
- package/framework/standards/ai-agents/prompt-sources.md +1 -1
- package/framework/standards/ai-agents/providers/model-registry.md +1 -1
- package/framework/standards/ai-agents/rag-custom-pgvector.md +1 -1
- package/framework/standards/ai-agents/service-tier-flex.md +1 -1
- package/framework/standards/ai-agents/setup.md +1 -1
- package/framework/standards/ai-agents/structured-output.md +1 -1
- package/framework/standards/ai-agents/sweet-spot.md +1 -1
- package/framework/standards/ai-agents/testing-ai.md +1 -1
- package/framework/standards/ai-agents/tool-adapters.md +226 -0
- package/framework/standards/ai-agents/vector-data-extensions.md +1 -1
- package/framework/standards/ai-agents/workflows.md +1 -1
- package/framework/standards/infrastructure/docker/local-compose-isolation.md +11 -1
- package/framework/templates/CLAUDE.md.core.template +1 -1
- package/framework/templates/IDE-SNIPPETS.md +262 -262
- package/framework/templates/README.md +1 -1
- package/framework/templates/REGISTRY.json +0 -81
- package/framework/templates/code/dotnet/abstractions/api-endpoint.cs +7 -7
- package/framework/templates/code/dotnet/feature/endpoint.cs.hbs +21 -21
- 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/spec.md +19 -205
- package/framework/templates/docs/ui-components.md +52 -52
- package/framework/templates/docs/ui-design-system.md +76 -76
- package/framework/templates/docs/ui-flows.md +167 -167
- 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/README.md +56 -56
- 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/meta-prompts/validators/pre-commit-validator.md +95 -95
- package/framework/templates/state.template.json +17 -17
- package/framework/templates/ui/design-system.css +226 -226
- package/package.json +2 -2
- package/src/commands/e2e/e2e.js +1 -1
- package/src/commands/project/heavy.js +1 -1
- package/src/commands/project/status.js +3 -3
- package/src/commands/project/worktree.js +34 -16
- package/src/commands/state/approve.js +9 -50
- package/src/commands/state/finish.js +4 -0
- package/src/commands/state/gate-check.js +18 -107
- package/src/commands/state/gate-decision.js +1 -1
- package/src/commands/state/report-check.js +42 -17
- package/src/commands/validation/mutate.js +1 -1
- package/src/commands/validation/scout-check.js +28 -0
- package/src/commands/validation/validate-feature.js +5 -32
- package/src/commands/validation/verify.js +16 -25
- package/src/core/paths/output-schema.js +0 -2
- package/src/core/state/state-manager.js +1 -2
- package/src/core/templates/template-renderer.js +477 -477
- package/src/dashboard/app/lib/format.js +5 -0
- package/src/dashboard/app/main.jsx +13 -13
- package/src/dashboard/app/pages/FeatureDetail.jsx +16 -22
- package/src/dashboard/app/pages/Features.jsx +7 -7
- package/src/dashboard/app/pages/PhasePipeline.jsx +1 -1
- package/src/dashboard/app/pages/Rubrics.jsx +7 -11
- package/src/dashboard/scanner/docs-scanner.js +1 -2
- package/src/dashboard/scanner/features-scanner.js +3 -4
- package/src/dashboard/scanner/rubrics-scanner.js +12 -12
- package/src/dashboard/vite.config.js +24 -24
- package/src/lib/agents/output-collector.js +0 -1
- package/src/lib/detectors/design-system-detector.js +188 -188
- package/src/lib/e2e/compose-env.js +5 -2
- package/src/lib/evaluator-report.js +52 -334
- package/src/lib/generators/design-system-generator.js +508 -508
- package/src/lib/generators/handoff-generator.js +4 -5
- package/src/lib/git/evidence-staleness.js +39 -0
- package/src/lib/installers/memory-installer.js +2 -2
- package/src/lib/mutation/manifest.js +8 -2
- package/src/lib/mutation/report.js +5 -3
- package/src/lib/mutation/runner.js +9 -7
- package/src/lib/risk-detector.js +63 -125
- package/src/lib/scout/scout-check.js +293 -0
- package/src/lib/tasks/eval-node.js +4 -34
- package/src/lib/tasks/task-classifier.js +30 -30
- package/src/lib/tasks/task-parser.js +209 -209
- package/src/lib/tasks/tasks-json-parser.js +7 -5
- package/src/lib/tasks/test-nodes.js +1 -1
- package/src/lib/tasks/test-runner.js +1 -1
- package/src/lib/validators/contracts/contract-compliance-validator.js +4 -42
- package/src/lib/validators/css/css-validator.js +340 -340
- package/src/lib/validators/reuse/reuse-validator.js +6 -8
- package/src/lib/validators/verify-runner.js +1 -1
- package/src/lib/worktree/env-allocator.js +35 -5
- package/src/scripts/setup-infra.js +1 -1
- package/src/utils/agent-id-mapper.js +11 -11
- package/src/utils/agents-installer.js +5 -9
- package/src/utils/claude-settings-manager.js +300 -300
- package/src/utils/hooks-installer.js +3 -10
- package/src/utils/skills-installer.js +1 -1
- package/framework/evals/proof-rubrics/README.md +0 -76
- package/framework/evals/proof-rubrics/eval-de-modelo.md +0 -80
- package/framework/evals/proof-rubrics/fiacao.md +0 -54
- package/framework/evals/proof-rubrics/guard-cego.md +0 -52
- package/framework/evals/proof-rubrics/media-claims.md +0 -109
- package/framework/evals/proof-rubrics/mutation-proof.md +0 -107
- package/framework/evals/proof-rubrics/observabilidade-genai.md +0 -119
- package/framework/evals/proof-rubrics/verde-nao-e-coberto.md +0 -48
- package/framework/evals/rubrics/README.md +0 -45
- package/framework/evals/rubrics/architecture.md +0 -103
- package/framework/evals/rubrics/code-quality.md +0 -98
- package/framework/evals/rubrics/condensed.md +0 -57
- package/framework/evals/rubrics/contracts.md +0 -67
- package/framework/evals/rubrics/test-coverage.md +0 -99
- package/framework/evals/scenarios/dotnet-crud.yaml +0 -42
- package/framework/evals/scenarios/nextjs-auth.yaml +0 -38
- package/framework/evals/suites/standard.yaml +0 -5
- package/framework/hooks/claude-code/core/post-edit-typecheck.js +0 -127
- package/framework/skills/core/morph-eval/SKILL.md +0 -64
- package/framework/templates/.idea/morph-templates.xml +0 -92
- package/framework/templates/.vscode/morph-templates.code-snippets +0 -186
- package/framework/templates/docs/clarifications.md +0 -253
- package/framework/templates/docs/onboarding.md +0 -122
- package/framework/templates/docs/schema-analysis.md +0 -119
- package/framework/templates/docs/user-stories.md +0 -34
- package/src/commands/state/score.js +0 -147
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
# Tool Adapters — a regra em `*Ops`, o texto do modelo no adaptador
|
|
2
|
+
|
|
3
|
+
> **Scope:** stacks=["dotnet"]
|
|
4
|
+
> **Layer:** 2 (on-keyword)
|
|
5
|
+
> **Keywords:** tool, AIFunctionFactory, adaptador de tool, tool fina, Ops, Desfecho, Render, regra de negócio em tool, descrição da tool, golden de tool, refactor neutro, migração de teste
|
|
6
|
+
> **Read by Claude in:** implement (ao escrever ou migrar uma tool de agente que carrega regra de negócio)
|
|
7
|
+
|
|
8
|
+
**Verified against:** Microsoft.Extensions.AI 10.9.0 + Microsoft.Agents.AI 1.20.0 (ai-pin 2026-09-08); os dois blocos C# abaixo foram extraídos com `scripts/extract-doc-csharp.mjs` e compilados contra o pin com `TreatWarningsAsErrors`, e os três testes do segundo bloco foram executados e passam. **A separação em si e a migração de teste são verificação documental:** vêm da feature `tools-finas` do MORPH_GHLBrain e o ai-kit não as traz. Last-verified: 2026-09-08.
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## Fronteira com os outros standards
|
|
13
|
+
|
|
14
|
+
`ai-agents-mcp-tools` cobre **consumir** tools de um MCP server. `ai-agents-testing-ai` cobre **testar** um agente sem rede. `backend-dotnet-testing` é dono de xUnit, relógio falso e orçamento de teste. **Nada disso se repete aqui.**
|
|
15
|
+
|
|
16
|
+
Este standard cobre uma coisa só: a tool que o agente chama **e que carrega regra**. Onde mora a regra, onde mora a frase que o modelo lê, e como se testa cada uma sem que uma mascare a outra.
|
|
17
|
+
|
|
18
|
+
## O problema
|
|
19
|
+
|
|
20
|
+
Uma tool nasce como uma lambda dentro do `AIFunctionFactory.Create`. Enquanto é só "chame a API e devolva", serve. Quando ganha regra, a lambda passa a misturar três coisas que mudam por motivos diferentes:
|
|
21
|
+
|
|
22
|
+
| O que é | Muda quando | Quem lê |
|
|
23
|
+
|---|---|---|
|
|
24
|
+
| a **regra** (validar, decidir, gravar) | o negócio muda | o código e o teste |
|
|
25
|
+
| a **frase para o modelo** (descrição, recusa, instrução de correção) | o prompt é ajustado | o modelo |
|
|
26
|
+
| o **efeito** (task, nota, mensagem entregue por código) | o processo muda | um humano |
|
|
27
|
+
|
|
28
|
+
Misturadas, o único teste possível é por string: qualquer ajuste de redação quebra o teste da regra, e qualquer refactor da regra arrisca mudar, sem ninguém ver, o que o modelo lê.
|
|
29
|
+
|
|
30
|
+
## O padrão
|
|
31
|
+
|
|
32
|
+
```
|
|
33
|
+
o modelo chama o modelo lê
|
|
34
|
+
┌──────────────┐ pedido tipado ┌───────────┐ desfecho ┌────────────────┐
|
|
35
|
+
│ Adaptador │ ──────────────────► │ *Ops │ ─────────► │ Render(desfecho)│
|
|
36
|
+
│ (tool fina) │ │ (a regra) │ tipado │ (frase p/ modelo)│
|
|
37
|
+
└──────────────┘ ◄───────────────────────────────────────────└────────────────┘
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
1. **`Ops`, não `Service`.** Regra determinística com dependências injetadas vira `sealed class XOps(...)`, com construtor primário e um `AddScoped` explícito. `*Service` colide com a proibição de camada de Service/Manager entre handler e dados; `*Ops` é outra coisa: a regra de uma tool, não um intermediário de persistência.
|
|
41
|
+
2. **Desfecho tipado, e a fronteira é quem lê o texto.** Cada método de `Ops` devolve um `abstract record XDesfecho` com um `sealed record` por caso. A frase para o **modelo** (descrição, recusa, instrução de autocorreção, sucesso, a decisão de "tool indisponível") mora no adaptador, em `public static object Render(XDesfecho)`. O texto para **humano** entregue por código (corpo de task, nota, ack) mora na `Ops`, porque é efeito. É verificável por `grep`: a `Ops` não cita o nome da tool nem dá instrução ao modelo.
|
|
42
|
+
3. **Adaptador fino.** `AIFunctionFactory.Create` com a lambda montando o pedido tipado, chamando a `Ops` e devolvendo `Render(desfecho)`. A régua é **zero regra no adaptador**, não um número de linhas: uma descrição dinâmica pode ser longa e tem de continuar byte a byte.
|
|
43
|
+
4. **Testes em duas camadas.** O teste da `Ops` asserta desfecho, chamadas e ordem no fake, **nunca texto**. O teste de contrato asserta o `Render` com o literal exato e a `.Description`. Um golden de nome, descrição e schema, por tenant real, protege a superfície que o modelo lê.
|
|
44
|
+
5. **Refactor neutro se prova antes de migrar teste.** Primeiro um commit de prova, com a suíte antiga intocada e verde (inclusive os evals offline); só depois a migração dos testes, com mutação rodando **só** contra os testes novos.
|
|
45
|
+
|
|
46
|
+
## Exemplo C# completo
|
|
47
|
+
|
|
48
|
+
A regra, o desfecho, o adaptador e o registro. O pedido é tipado, então a lambda não decide nada: monta, chama, renderiza.
|
|
49
|
+
|
|
50
|
+
```csharp
|
|
51
|
+
using System.ComponentModel;
|
|
52
|
+
using Microsoft.Extensions.AI;
|
|
53
|
+
using Microsoft.Extensions.DependencyInjection;
|
|
54
|
+
|
|
55
|
+
namespace Exemplo.Tools;
|
|
56
|
+
|
|
57
|
+
public interface IAgendaExterna
|
|
58
|
+
{
|
|
59
|
+
Task<int> CapacidadeAsync(string salaId, CancellationToken ct);
|
|
60
|
+
Task<string> GravarAsync(ReservaPedido pedido, CancellationToken ct);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
public sealed record ReservaPedido(string SalaId, DateOnly Dia, int Pessoas);
|
|
64
|
+
|
|
65
|
+
public abstract record ReservaDesfecho
|
|
66
|
+
{
|
|
67
|
+
public sealed record Reservada(string ConfirmacaoId) : ReservaDesfecho;
|
|
68
|
+
public sealed record SalaCheia(int Capacidade) : ReservaDesfecho;
|
|
69
|
+
public sealed record DiaSemExpediente(DateOnly Dia) : ReservaDesfecho;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
public sealed class ReservaOps(IAgendaExterna agenda)
|
|
73
|
+
{
|
|
74
|
+
public async Task<ReservaDesfecho> ReservarAsync(ReservaPedido pedido, CancellationToken ct)
|
|
75
|
+
{
|
|
76
|
+
if (pedido.Dia.DayOfWeek is DayOfWeek.Saturday or DayOfWeek.Sunday)
|
|
77
|
+
return new ReservaDesfecho.DiaSemExpediente(pedido.Dia);
|
|
78
|
+
|
|
79
|
+
var capacidade = await agenda.CapacidadeAsync(pedido.SalaId, ct);
|
|
80
|
+
if (pedido.Pessoas > capacidade)
|
|
81
|
+
return new ReservaDesfecho.SalaCheia(capacidade);
|
|
82
|
+
|
|
83
|
+
return new ReservaDesfecho.Reservada(await agenda.GravarAsync(pedido, ct));
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
public static class ReservarSalaTool
|
|
88
|
+
{
|
|
89
|
+
public const string Name = "reservar_sala";
|
|
90
|
+
|
|
91
|
+
public static AIFunction Create(ReservaOps ops) =>
|
|
92
|
+
AIFunctionFactory.Create(
|
|
93
|
+
async (
|
|
94
|
+
[Description("Identificador da sala, como listado por consultar_salas")] string salaId,
|
|
95
|
+
[Description("Dia da reserva, AAAA-MM-DD")] DateOnly dia,
|
|
96
|
+
[Description("Quantidade de pessoas")] int pessoas,
|
|
97
|
+
CancellationToken ct) =>
|
|
98
|
+
Render(await ops.ReservarAsync(new ReservaPedido(salaId, dia, pessoas), ct)),
|
|
99
|
+
Name,
|
|
100
|
+
"Reserva uma sala para um dia. Só chame depois de confirmar sala, dia e número de pessoas com o cliente.");
|
|
101
|
+
|
|
102
|
+
public static object Render(ReservaDesfecho desfecho) => desfecho switch
|
|
103
|
+
{
|
|
104
|
+
ReservaDesfecho.Reservada r => new { ok = true, confirmacao = r.ConfirmacaoId },
|
|
105
|
+
ReservaDesfecho.SalaCheia c => new { ok = false, erro = $"A sala comporta no máximo {c.Capacidade} pessoas. Ofereça outra sala ou reduza o grupo." },
|
|
106
|
+
ReservaDesfecho.DiaSemExpediente d => new { ok = false, erro = $"{d.Dia:yyyy-MM-dd} não tem expediente. Peça outro dia." },
|
|
107
|
+
_ => throw new ArgumentOutOfRangeException(nameof(desfecho)),
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
public static class ReservaToolsRegistration
|
|
112
|
+
{
|
|
113
|
+
public static IServiceCollection AddReservaTools(this IServiceCollection services) =>
|
|
114
|
+
services.AddScoped<ReservaOps>();
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
A checagem de fronteira é um `grep`, e tem de sair vazio: a `Ops` não conhece a tool nem fala com o modelo.
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
grep -rnE "reservar_sala|Ofereça|Peça|AIFunction" Features/Reservas/Ops/
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## Os testes das duas camadas
|
|
125
|
+
|
|
126
|
+
O teste da `Ops` compara **tipos e chamadas**; o de contrato compara **o texto que o modelo lê**. Trocar a redação de uma recusa quebra só o segundo.
|
|
127
|
+
|
|
128
|
+
```csharp
|
|
129
|
+
using System.Text.Encodings.Web;
|
|
130
|
+
using System.Text.Json;
|
|
131
|
+
using Exemplo.Tools;
|
|
132
|
+
using Xunit;
|
|
133
|
+
|
|
134
|
+
namespace Exemplo.Tools.Tests;
|
|
135
|
+
|
|
136
|
+
internal sealed class AgendaFake(int capacidade) : IAgendaExterna
|
|
137
|
+
{
|
|
138
|
+
public List<string> Chamadas { get; } = [];
|
|
139
|
+
|
|
140
|
+
public Task<int> CapacidadeAsync(string salaId, CancellationToken ct)
|
|
141
|
+
{
|
|
142
|
+
Chamadas.Add($"capacidade:{salaId}");
|
|
143
|
+
return Task.FromResult(capacidade);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
public Task<string> GravarAsync(ReservaPedido pedido, CancellationToken ct)
|
|
147
|
+
{
|
|
148
|
+
Chamadas.Add($"gravar:{pedido.SalaId}");
|
|
149
|
+
return Task.FromResult("R-1");
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
public sealed class ReservaOpsTests
|
|
154
|
+
{
|
|
155
|
+
[Fact]
|
|
156
|
+
public async Task Sala_cheia_nao_grava_e_devolve_a_capacidade()
|
|
157
|
+
{
|
|
158
|
+
var agenda = new AgendaFake(capacidade: 4);
|
|
159
|
+
var ops = new ReservaOps(agenda);
|
|
160
|
+
|
|
161
|
+
var desfecho = await ops.ReservarAsync(new ReservaPedido("s1", new DateOnly(2026, 10, 5), 6), CancellationToken.None);
|
|
162
|
+
|
|
163
|
+
Assert.Equal(new ReservaDesfecho.SalaCheia(4), desfecho);
|
|
164
|
+
Assert.Equal(["capacidade:s1"], agenda.Chamadas);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
public sealed class ReservarSalaToolTests
|
|
169
|
+
{
|
|
170
|
+
private static readonly JsonSerializerOptions Literal = new() { Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping };
|
|
171
|
+
|
|
172
|
+
[Fact]
|
|
173
|
+
public void Render_da_sala_cheia_diz_ao_modelo_o_que_fazer()
|
|
174
|
+
{
|
|
175
|
+
var json = JsonSerializer.Serialize(ReservarSalaTool.Render(new ReservaDesfecho.SalaCheia(4)), Literal);
|
|
176
|
+
|
|
177
|
+
Assert.Equal("{\"ok\":false,\"erro\":\"A sala comporta no máximo 4 pessoas. Ofereça outra sala ou reduza o grupo.\"}", json);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
[Fact]
|
|
181
|
+
public void A_superficie_que_o_modelo_le_nao_mudou()
|
|
182
|
+
{
|
|
183
|
+
var tool = ReservarSalaTool.Create(new ReservaOps(new AgendaFake(capacidade: 10)));
|
|
184
|
+
|
|
185
|
+
Assert.Equal("reservar_sala", tool.Name);
|
|
186
|
+
Assert.StartsWith("Reserva uma sala para um dia.", tool.Description);
|
|
187
|
+
Assert.Contains("\"salaId\"", tool.JsonSchema.GetRawText());
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
## Migrar uma tool que já tem a regra no adaptador
|
|
193
|
+
|
|
194
|
+
A migração é um refactor que **não pode mudar o que o modelo lê**. Por isso ela tem ordem:
|
|
195
|
+
|
|
196
|
+
1. **Commit de prova.** Extraia a `Ops` e o `Render` sem tocar nenhum teste. A suíte antiga tem de continuar verde, inclusive os evals offline. Esse commit é a âncora: prova que o refactor é neutro.
|
|
197
|
+
2. **Migre os testes**, e só depois de o commit de prova estar verde.
|
|
198
|
+
3. **Matriz de migração.** Liste cada asserção antiga e a asserção nova que a cobre. "Já está coberto pelo teste X" precisa de prova **asserção por asserção**: na migração que originou este standard, 11 de 22 alegações desse tipo tinham perdido alguma asserção do teste antigo.
|
|
199
|
+
4. **Os testes que ficam na origem têm de ser idênticos à base.** A migração só apaga. Uma checagem byte a byte dos métodos restantes contra o commit de prova pegou (e provou corrigidos) `Edit`s que casaram no método errado.
|
|
200
|
+
5. **Mutação só contra os testes novos.** Ela acha lacunas que a suíte antiga nunca protegeu: em duas das três `Ops` migradas apareceram regras sem teste algum (valor digitado pelo comercial sobrescrito pelo catálogo, campos reafirmados sumindo da falha do `PUT`, corpo de update reenviando valor igual).
|
|
201
|
+
|
|
202
|
+
## Exemplos reais
|
|
203
|
+
|
|
204
|
+
MORPH_GHLBrain, `src/Morph.GHLBrain/Features/Conversation/Operator/`:
|
|
205
|
+
|
|
206
|
+
| Tool | Adaptador | `Ops` | Desfecho |
|
|
207
|
+
|---|---|---|---|
|
|
208
|
+
| `finalizar_handoff` | `Tools/FinalizarHandoffTool.cs` | `Ops/HandoffOps.cs` | `HandoffDesfecho` |
|
|
209
|
+
| `atualizar_dossie` | `Tools/AtualizarDossieTool.cs` | `Ops/DossieOps.cs` | `DossieDesfecho` |
|
|
210
|
+
| `set_lead_fields` | `Tools/SetLeadFieldsTool.cs` | `Ops/DossieOps.cs` | `CamposDesfecho` |
|
|
211
|
+
| `route_lead` | `Tools/RouteLeadTool.cs` | `Ops/TriagemOps.cs` | `TriagemDesfecho` |
|
|
212
|
+
|
|
213
|
+
As decisões estão em `.morph/features/tools-finas/2-plan/decisions.md` (ADR-1 e ADR-2) daquele repositório.
|
|
214
|
+
|
|
215
|
+
## Regras
|
|
216
|
+
|
|
217
|
+
- **ALWAYS** a regra de uma tool vai numa `sealed class XOps(...)` com construtor primário e `AddScoped` explícito
|
|
218
|
+
- **NEVER** chame essa classe de `*Service` ou `*Manager`: o MORPH.md proíbe essa camada entre handler e dados, e `*Ops` é a forma da casa
|
|
219
|
+
- **ALWAYS** cada método de `Ops` devolve um `abstract record XDesfecho`, com um `sealed record` por caso que a regra distingue
|
|
220
|
+
- **NEVER** ponha na `Ops` uma frase para o modelo, o nome da tool ou uma instrução de autocorreção. Verifique por `grep`
|
|
221
|
+
- **ALWAYS** o texto para humano entregue por código (task, nota, ack) fica na `Ops`: é efeito
|
|
222
|
+
- **ALWAYS** o adaptador só monta o pedido tipado, chama a `Ops` e devolve `Render(desfecho)`. Zero regra ali; descrição dinâmica longa é permitida e segue byte a byte
|
|
223
|
+
- **ALWAYS** o teste da `Ops` asserta desfecho, chamadas e ordem no fake, nunca texto
|
|
224
|
+
- **ALWAYS** o teste de contrato asserta o `Render` com o literal exato e a `.Description`, e há um golden de nome, descrição e schema por tenant real
|
|
225
|
+
- **NEVER** declare "já coberto" sem a asserção nova que cobre cada asserção antiga
|
|
226
|
+
- **NEVER** migre o teste antes do commit de prova verde, nem deixe na origem um teste diferente da base
|
|
@@ -171,7 +171,7 @@ This maps conceptually onto the existing custom pattern: `IngestionDocumentReade
|
|
|
171
171
|
|
|
172
172
|
---
|
|
173
173
|
|
|
174
|
-
## Checklist (verifiable
|
|
174
|
+
## Checklist (verifiable in review)
|
|
175
175
|
|
|
176
176
|
- [ ] If `Microsoft.SemanticKernel.Connectors.*` appears in a `.csproj`, it is used only for `VectorStoreCollection`/`VectorStore` types — no `Kernel`, `KernelFunction`, or `ChatHistory` usage anywhere in the project.
|
|
177
177
|
- [ ] No plain `Microsoft.SemanticKernel` (core) package reference.
|
|
@@ -183,7 +183,7 @@ Sempre use `await using` no `StreamingRun` / `Run` — é `IAsyncDisposable`.
|
|
|
183
183
|
|
|
184
184
|
---
|
|
185
185
|
|
|
186
|
-
## Checklist (verifiable
|
|
186
|
+
## Checklist (verifiable in review)
|
|
187
187
|
|
|
188
188
|
- [ ] Foi verificado que um `foreach` ou single agent não resolve o problema?
|
|
189
189
|
- [ ] O critério de fan-in/out genuíno ou HITL está documentado em `decisions.md`?
|
|
@@ -27,6 +27,16 @@ The harness gives each worktree a **block of 10 ports** (`.morph/worktree.env`,
|
|
|
27
27
|
|
|
28
28
|
Also exported: `COMPOSE_PROJECT_NAME=morph-{feature}` (isolates container, network and volume names), `MORPH_PORT_BASE`, `MORPH_PORT_RANGE`, `PLAYWRIGHT_BASE_URL=http://localhost:<+0>`.
|
|
29
29
|
|
|
30
|
+
## Project-named port variables
|
|
31
|
+
|
|
32
|
+
The harness exports only the four names above. A compose file that reads a port by another name (`"${PG_PORT:-5433}:5432"`) is **not** fed by the block: it publishes its own default, or whatever a `.env.e2e` copied from a sibling worktree carries, while `worktree.env` announces a different port. Hand-run specs then hit a closed port, or worse, seed one postgres and read another. Declare the name the project reads:
|
|
33
|
+
|
|
34
|
+
```json
|
|
35
|
+
{ "e2e": { "portEnvNames": { "postgres": "PG_PORT" } } }
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Keys are `web`, `api`, `postgres` and `redis`. Each declared name carries the **same value** as the standard variable, everywhere the layout goes: `worktree.env`, the compose env of `verify`/`e2e up`/`morph-spec env`, and the `PreToolUse:Bash` hook. Being layout, it is ignored (and named on stderr) if the credentials file carries it. An entry the harness cannot honour — unknown port, invalid or reserved name, a name used twice — is dropped and named on stderr.
|
|
39
|
+
|
|
30
40
|
## Core Rules
|
|
31
41
|
|
|
32
42
|
- NEVER set `container_name` — Compose already names containers `<project>-<service>-1`, and an explicit name is shared by every worktree
|
|
@@ -36,7 +46,7 @@ Also exported: `COMPOSE_PROJECT_NAME=morph-{feature}` (isolates container, netwo
|
|
|
36
46
|
- ALWAYS give `DATABASE_URL` an in-network default so the stack comes up with no `.env`: `postgresql://postgres:postgres@postgres:5432/${POSTGRES_DB:-app}`
|
|
37
47
|
- ALWAYS read the base URL from the environment in `playwright.config.ts`: `process.env.PLAYWRIGHT_BASE_URL ?? \`http://localhost:${process.env.PORT ?? 3000}\``
|
|
38
48
|
- NEVER put a cloud connection string (Neon branch etc.) in `.morph/worktree.env` — it is regenerated; credentials go in the gitignored file named by `config.json → e2e.auth.envFile`
|
|
39
|
-
- NEVER put a variable from the port layout table above (nor `PLAYWRIGHT_BASE_URL`) in that credentials file — the harness **ignores** it and says which key it ignored on stderr. Ports come from the allocated block; the file carries credentials. A `.env.e2e` copied from a sibling worktree carries THAT worktree's block, and before the filter existed it silently repointed this stack at the neighbour's postgres
|
|
49
|
+
- NEVER put a variable from the port layout table above (nor `PLAYWRIGHT_BASE_URL`, nor a name declared in `e2e.portEnvNames`) in that credentials file — the harness **ignores** it and says which key it ignored on stderr. Ports come from the allocated block; the file carries credentials. A `.env.e2e` copied from a sibling worktree carries THAT worktree's block, and before the filter existed it silently repointed this stack at the neighbour's postgres
|
|
40
50
|
|
|
41
51
|
## What the harness enforces
|
|
42
52
|
|
|
@@ -18,5 +18,5 @@ morph-spec NLH — leia `.morph/framework/MORPH.md` ao iniciar cada sessão. Em
|
|
|
18
18
|
|
|
19
19
|
## Slash commands
|
|
20
20
|
|
|
21
|
-
- `/morph-proposal {feature}` — entendimento de negócio + spec
|
|
21
|
+
- `/morph-proposal {feature}` — entendimento de negócio + use cases, spec e tasks (Gates 1 e 2)
|
|
22
22
|
- `/morph-apply {feature}` — execução autônoma das tarefas + review (Gate 3)
|