@polymorphism-tech/morph-spec 7.7.3 → 7.8.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/validate.js +379 -379
- package/claude-plugin.json +1 -1
- package/docs/CHEATSHEET.md +1 -1
- package/docs/QUICKSTART.md +1 -1
- package/framework/CLAUDE.md +1 -1
- package/framework/MORPH.md +3 -2
- package/framework/agents.json +1 -1
- package/framework/commands/commit.md +166 -166
- package/framework/commands/morph-infra.md +215 -215
- package/framework/commands/morph-preflight.md +205 -205
- package/framework/commands/morph-troubleshoot.md +124 -124
- package/framework/evals/scenarios/dotnet-crud.yaml +1 -1
- package/framework/hooks/claude-code/statusline.py +679 -679
- 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/activity-logger.js +160 -160
- package/framework/hooks/shared/hook-response.js +45 -45
- package/framework/hooks/shared/payload-utils.js +78 -78
- package/framework/hooks/shared/stdin-reader.js +26 -26
- package/framework/rubrics/backend.json +41 -41
- package/framework/rules/ai-agents-standards.md +45 -45
- package/framework/rules/dotnet-templates.md +86 -87
- package/framework/rules/frontend-standards.md +13 -13
- package/framework/rules/infrastructure-standards.md +27 -27
- package/framework/rules/neon-standards.md +25 -25
- package/framework/rules/nextjs-standards.md +20 -20
- package/framework/rules/nextjs-templates.md +40 -40
- 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 +1 -1
- package/framework/skills/core/morph-brainstorming/SKILL.md +9 -5
- package/framework/skills/core/morph-eval/SKILL.md +1 -1
- package/framework/skills/core/morph-implement/SKILL.md +1 -1
- package/framework/skills/core/morph-plan/SKILL.md +11 -6
- package/framework/skills/core/morph-proposal/SKILL.md +1 -1
- package/framework/skills/core/morph-review/SKILL.md +13 -7
- package/framework/skills/core/morph-stack-scan/SKILL.md +5 -5
- package/framework/skills/core/morph-standards/SKILL.md +1 -1
- package/framework/standards/STANDARDS.json +1 -1
- package/framework/standards/ai-agents/agent-session.md +160 -160
- package/framework/standards/ai-agents/batching.md +193 -193
- package/framework/standards/ai-agents/context-providers.md +188 -188
- package/framework/standards/ai-agents/mcp-server.md +250 -250
- package/framework/standards/ai-agents/mcp-tools.md +197 -197
- package/framework/standards/ai-agents/middleware-patterns.md +207 -207
- package/framework/standards/ai-agents/modalities-image-gen.md +173 -173
- package/framework/standards/ai-agents/modalities-speech-to-text.md +171 -171
- package/framework/standards/ai-agents/modalities-text-to-speech.md +181 -181
- package/framework/standards/ai-agents/modalities-vision.md +201 -201
- package/framework/standards/ai-agents/multi-agent-patterns.md +155 -155
- package/framework/standards/ai-agents/production.md +275 -275
- package/framework/standards/ai-agents/prompts-in-db.md +214 -214
- package/framework/standards/ai-agents/providers/model-registry.md +158 -158
- package/framework/standards/ai-agents/service-tier-flex.md +135 -135
- package/framework/standards/ai-agents/structured-output.md +249 -249
- package/framework/standards/ai-agents/sweet-spot.md +109 -109
- package/framework/standards/architecture/vertical-slice/vertical-slice.md +34 -35
- package/framework/standards/backend/api/validation.md +2 -2
- package/framework/standards/backend/dotnet/scrutor.md +1 -3
- package/framework/standards/backend/dotnet/vsa-handler-patterns.md +42 -37
- package/framework/standards/data/nosql/cache/redis.md +2 -2
- package/framework/templates/CLAUDE.md.core.template +1 -1
- package/framework/templates/IDE-SNIPPETS.md +262 -262
- package/framework/templates/README.md +0 -1
- package/framework/templates/REGISTRY.json +0 -21
- package/framework/templates/code/dotnet/abstractions/api-endpoint.cs +7 -7
- package/framework/templates/code/dotnet/abstractions/handler.cs +0 -3
- package/framework/templates/code/dotnet/ai-agents/AgentBootstrap.cs.template +39 -39
- package/framework/templates/code/dotnet/ai-agents/EmbeddingService.cs.template +68 -68
- package/framework/templates/code/dotnet/ai-agents/ModelRegistry.cs.template +117 -117
- package/framework/templates/code/dotnet/ai-agents/PromptRepository.cs.template +95 -95
- package/framework/templates/code/dotnet/ai-agents/ResilientToolMiddleware.cs.template +61 -61
- package/framework/templates/code/dotnet/ai-agents/VectorSearchTool.cs.template +107 -107
- package/framework/templates/code/dotnet/ai-agents/model-registry.json.template +58 -58
- package/framework/templates/code/dotnet/constants/api-tags.cs +1 -1
- package/framework/templates/code/dotnet/contracts/contracts-vsa.cs +281 -282
- package/framework/templates/code/dotnet/database/migration.cs +83 -83
- package/framework/templates/code/dotnet/feature/endpoint.cs.hbs +21 -21
- package/framework/templates/code/dotnet/feature/handler.cs.hbs +23 -24
- 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/dotnet/feature/validator.cs.hbs +15 -15
- package/framework/templates/code/dotnet/jobs/job.cs +15 -15
- package/framework/templates/code/dotnet/test.cs +72 -101
- package/framework/templates/code/sql/ai-agents/agent-prompts-migration.sql.template +58 -58
- package/framework/templates/code/sql/ai-agents/pgvector-embeddings-migration.sql.template +91 -91
- package/framework/templates/code/typescript/contracts.ts +167 -167
- package/framework/templates/docs/clarifications.md +253 -253
- package/framework/templates/docs/schema-analysis.md +119 -119
- 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/docs/ui-mockups.md +98 -98
- package/framework/templates/feature/decisions.md +122 -122
- package/framework/templates/frontend/nextjs/Dockerfile.nextjs.hbs +43 -43
- package/framework/templates/frontend/nextjs/client-component.tsx.hbs +26 -26
- package/framework/templates/frontend/nextjs/env.mjs.hbs +32 -32
- package/framework/templates/frontend/nextjs/feature-form.tsx.hbs +56 -56
- package/framework/templates/frontend/nextjs/page.tsx.hbs +22 -22
- package/framework/templates/frontend/nextjs/tsconfig.json.hbs +26 -26
- package/framework/templates/frontend/nextjs/use-feature.ts.hbs +54 -54
- package/framework/templates/infrastructure/docker/Dockerfile.template +58 -58
- package/framework/templates/infrastructure/docker/docker-compose.template.yml +50 -50
- 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/integrations/asaas-client.cs +387 -387
- package/framework/templates/integrations/asaas-webhook.cs +351 -351
- 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/parallel-workers/parallel-coordinator.md +113 -113
- package/framework/templates/meta-prompts/validators/pre-commit-validator.md +95 -95
- package/framework/templates/project/validate.js +120 -120
- package/framework/templates/state.template.json +17 -17
- package/framework/templates/ui/design-system.css +226 -226
- package/package.json +1 -1
- package/src/commands/dashboard/dashboard.js +53 -53
- package/src/commands/index.js +13 -13
- package/src/commands/mcp/mcp-setup.js +519 -519
- package/src/commands/project/checkpoint.js +209 -209
- package/src/commands/project/update.js +7 -4
- package/src/core/state/phase-state-machine.js +197 -197
- package/src/core/templates/template-renderer.js +477 -477
- package/src/dashboard/app/components/DetailPanel.jsx +11 -11
- package/src/dashboard/app/components/FlowGraph.jsx +17 -17
- package/src/dashboard/app/components/SearchFilter.jsx +22 -22
- package/src/dashboard/app/hooks/useDetailPanel.js +15 -15
- package/src/dashboard/app/main.jsx +13 -13
- package/src/dashboard/app/pages/AgentMap.jsx +370 -370
- package/src/dashboard/app/pages/HookInspector.jsx +418 -418
- package/src/dashboard/app/pages/LiveState.jsx +239 -239
- package/src/dashboard/app/pages/PhasePipeline.jsx +408 -408
- package/src/dashboard/app/pages/RulesMap.jsx +273 -273
- package/src/dashboard/app/pages/SkillsLibrary.jsx +366 -366
- package/src/dashboard/app/pages/Standards.jsx +332 -332
- package/src/dashboard/app/styles/index.css +47 -47
- package/src/dashboard/index.html +12 -12
- package/src/dashboard/package.json +24 -24
- package/src/dashboard/scanner/hooks-scanner.js +41 -41
- package/src/dashboard/scanner/phases-scanner.js +15 -15
- package/src/dashboard/scanner/rubrics-scanner.js +12 -12
- package/src/dashboard/scanner/rules-scanner.js +37 -37
- package/src/dashboard/scanner/skills-scanner.js +43 -43
- package/src/dashboard/scanner/squads-scanner.js +13 -13
- package/src/dashboard/scanner/standards-scanner.js +26 -26
- package/src/dashboard/vite-plugin-morph-data.js +17 -17
- package/src/dashboard/vite.config.js +24 -24
- package/src/lib/agents/metrics-aggregator.js +56 -56
- package/src/lib/agents/metrics-calculator.js +125 -125
- package/src/lib/agents/output-collector.js +73 -73
- package/src/lib/detectors/claude-config-detector.js +152 -152
- package/src/lib/detectors/design-system-detector.js +231 -231
- package/src/lib/detectors/index.js +217 -217
- package/src/lib/generators/architecture-scanner.js +639 -639
- package/src/lib/generators/design-system-generator.js +508 -508
- package/src/lib/generators/index.js +13 -13
- package/src/lib/generators/settings-generator.js +209 -209
- package/src/lib/hooks/hook-executor.js +167 -167
- package/src/lib/installers/mcp-installer.js +719 -719
- package/src/lib/tasks/task-classifier.js +30 -30
- package/src/lib/tasks/task-parser.js +209 -209
- package/src/lib/tasks/test-runner.js +81 -81
- package/src/lib/validators/architecture/architecture-validator.js +259 -259
- package/src/lib/validators/checkpoint-persistence.js +39 -39
- package/src/lib/validators/contracts/contract-compliance-validator.js +274 -274
- package/src/lib/validators/css/css-validator.js +340 -340
- package/src/lib/validators/packages/package-validator.js +347 -347
- package/src/lib/validators/shared/emit-validator-dispatch.js +14 -14
- package/src/lib/validators/shared/issue-counter.js +14 -14
- package/src/lib/validators/validation-runner.js +349 -349
- package/src/scripts/setup-infra.js +383 -383
- package/src/utils/agent-id-mapper.js +11 -11
- package/src/utils/banner.js +52 -43
- package/src/utils/claude-settings-manager.js +300 -300
- package/src/utils/skills-installer.js +172 -172
- package/framework/templates/.idea/morph-templates.xml +0 -92
- package/framework/templates/.vscode/morph-templates.code-snippets +0 -186
- package/framework/templates/code/dotnet/backend/repository.cs +0 -141
|
@@ -1,109 +1,109 @@
|
|
|
1
|
-
# Sweet Spot — When to use what in MAF
|
|
2
|
-
|
|
3
|
-
> **Scope:** stacks=["dotnet"]
|
|
4
|
-
> **Layer:** 0 (always-load via persona maf-expert)
|
|
5
|
-
> **Keywords:** sweet spot, when to use agent, agent vs workflow, when not to use workflow, MAF decision, simple agent
|
|
6
|
-
> **Read by Claude in:** plan (decide arquitetura da feature antes de quebrar em tasks) + implement (validar que a arquitetura ainda é a mais simples possível)
|
|
7
|
-
|
|
8
|
-
**Verified against:** MAF Tier List research (2026-05-18) + Microsoft Learn docs. Last-verified: 2026-05-19.
|
|
9
|
-
|
|
10
|
-
---
|
|
11
|
-
|
|
12
|
-
## The 4-feature rule
|
|
13
|
-
|
|
14
|
-
```
|
|
15
|
-
95% of production AI agents need ONLY:
|
|
16
|
-
|
|
17
|
-
1. Chat (the LLM call)
|
|
18
|
-
2. Structured Output (typed records, not free text)
|
|
19
|
-
3. Tools (typed functions the agent can call)
|
|
20
|
-
4. RAG Custom (pgvector tool, not hosted RAG)
|
|
21
|
-
|
|
22
|
-
If your feature needs ONLY these 4 → use a single agent. Don't reach for workflows.
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
Apply this rule before deciding multi-agent or workflow. Most of the time the answer is "single agent".
|
|
26
|
-
|
|
27
|
-
---
|
|
28
|
-
|
|
29
|
-
## Decision tree (Claude reads this in plan phase)
|
|
30
|
-
|
|
31
|
-
```
|
|
32
|
-
Step 1 — Does the task even need an LLM?
|
|
33
|
-
├─ "Can a deterministic function solve this?" → YES → write the function, no agent
|
|
34
|
-
└─ NO → continue
|
|
35
|
-
|
|
36
|
-
Step 2 — Single agent vs multi-agent?
|
|
37
|
-
├─ One topic + one decision → single agent (DEFAULT)
|
|
38
|
-
├─ Multiple specialized topics with clear handoff → multi-agent
|
|
39
|
-
│ └─ Prefer Agent-as-Tool pattern (one agent uses another as a tool)
|
|
40
|
-
│ └─ See ai-agents-multi-agent-patterns
|
|
41
|
-
└─ "I'm not sure" → start with single agent, escalate if needed
|
|
42
|
-
|
|
43
|
-
Step 3 — Need a Workflow?
|
|
44
|
-
├─ The feature has fan-in/out (many inputs aggregated) → maybe workflow
|
|
45
|
-
│ └─ Confirm: can you write it as a foreach + LINQ aggregate?
|
|
46
|
-
│ └─ YES → write the foreach, NO workflow
|
|
47
|
-
│ └─ NO → workflow may help
|
|
48
|
-
├─ Long-running with human-in-the-loop checkpoints → maybe workflow
|
|
49
|
-
│ └─ See ai-agents-workflows (Onda 2)
|
|
50
|
-
└─ Otherwise → NO workflow
|
|
51
|
-
|
|
52
|
-
Step 4 — Which provider?
|
|
53
|
-
├─ ALWAYS direct models — never a hosted agent service
|
|
54
|
-
├─ Provider choice (OpenAI / Google / Anthropic / Ollama) lives in the
|
|
55
|
-
│ project's Model Registry, not hardcoded → see ai-agents-providers-model-registry
|
|
56
|
-
└─ Hosted services (Foundry Agent Service, Vertex AI hosted) → NOT used
|
|
57
|
-
|
|
58
|
-
Step 5 — RAG approach?
|
|
59
|
-
├─ Need control over chunking/embedding/storage? → RAG custom (pgvector)
|
|
60
|
-
│ └─ See ai-agents-rag-custom-pgvector (Onda 2)
|
|
61
|
-
└─ Don't reach for hosted RAG — loses control
|
|
62
|
-
```
|
|
63
|
-
|
|
64
|
-
---
|
|
65
|
-
|
|
66
|
-
## Anti-patterns (what Claude should NOT do)
|
|
67
|
-
|
|
68
|
-
| Anti-pattern | Why it's wrong | What to do instead |
|
|
69
|
-
|--------------|----------------|---------------------|
|
|
70
|
-
| Reach for Workflow when a function does the job | "Tudo que Workflows fazem, código C# normal faz com menos complexidade" — pesquisa | Write the function. Wrap in a tool if an agent needs to call it. |
|
|
71
|
-
| Use a hosted agent service | Hosted is "always 6-9 months behind" on models + removes control | Direct models always (OpenAI / Google / Anthropic / Ollama) |
|
|
72
|
-
| Upload PDFs to a hosted RAG | Loses control of chunking + embedding + storage | Custom RAG with pgvector. See ai-agents-rag-custom-pgvector |
|
|
73
|
-
| Add Memory/Session because "agents need state" | If not building a chatbot, don't add chat state | Skip. Add when a real chat UI exists. |
|
|
74
|
-
| Use Semantic Kernel for new features | SK is superseded by MAF | Use Microsoft.Agents.AI. See ai-agents-setup |
|
|
75
|
-
| Return free text and parse it downstream | LLM output is non-deterministic | Structured Output. See ai-agents-structured-output (Onda 2) |
|
|
76
|
-
|
|
77
|
-
---
|
|
78
|
-
|
|
79
|
-
## When the 4-feature rule does NOT apply
|
|
80
|
-
|
|
81
|
-
You may need to step outside the sweet spot when:
|
|
82
|
-
- The feature genuinely fans-out (e.g., monthly report aggregating N items) → workflow with fan-in
|
|
83
|
-
- The feature needs vision/audio/image-gen → modality-specific standards (see ai-agents-modalities-* — Onda 3)
|
|
84
|
-
|
|
85
|
-
**Even in these cases:** the core of the agent still follows the 4-feature rule. Only the orchestration layer changes.
|
|
86
|
-
|
|
87
|
-
---
|
|
88
|
-
|
|
89
|
-
## Checklist (verifiable by morph-eval)
|
|
90
|
-
|
|
91
|
-
- [ ] Was each agent designed as single-agent BEFORE reaching for multi-agent? (Check `decisions.md`.)
|
|
92
|
-
- [ ] If a Workflow is used, is fan-in/out or HITL checkpointing genuinely present? Or could `foreach + LINQ` replace it?
|
|
93
|
-
- [ ] Are the 4 features (Chat, Structured Output, Tools, RAG Custom) all present where applicable?
|
|
94
|
-
- [ ] Are hosted agent services avoided?
|
|
95
|
-
|
|
96
|
-
---
|
|
97
|
-
|
|
98
|
-
## References
|
|
99
|
-
|
|
100
|
-
- `ai-agents-setup` — packages and DI
|
|
101
|
-
- `ai-agents-providers-model-registry` — provider-agnostic config
|
|
102
|
-
- `ai-agents-structured-output` (Onda 2)
|
|
103
|
-
- `ai-agents-multi-agent-patterns` (Onda 2)
|
|
104
|
-
- `ai-agents-workflows` (Onda 2, reframed)
|
|
105
|
-
- `ai-agents-rag-custom-pgvector` (Onda 2)
|
|
106
|
-
|
|
107
|
-
---
|
|
108
|
-
|
|
109
|
-
*MORPH-SPEC by Polymorphism Tech — ai-agents/sweet-spot.md v1.0 (2026-05-19)*
|
|
1
|
+
# Sweet Spot — When to use what in MAF
|
|
2
|
+
|
|
3
|
+
> **Scope:** stacks=["dotnet"]
|
|
4
|
+
> **Layer:** 0 (always-load via persona maf-expert)
|
|
5
|
+
> **Keywords:** sweet spot, when to use agent, agent vs workflow, when not to use workflow, MAF decision, simple agent
|
|
6
|
+
> **Read by Claude in:** plan (decide arquitetura da feature antes de quebrar em tasks) + implement (validar que a arquitetura ainda é a mais simples possível)
|
|
7
|
+
|
|
8
|
+
**Verified against:** MAF Tier List research (2026-05-18) + Microsoft Learn docs. Last-verified: 2026-05-19.
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## The 4-feature rule
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
95% of production AI agents need ONLY:
|
|
16
|
+
|
|
17
|
+
1. Chat (the LLM call)
|
|
18
|
+
2. Structured Output (typed records, not free text)
|
|
19
|
+
3. Tools (typed functions the agent can call)
|
|
20
|
+
4. RAG Custom (pgvector tool, not hosted RAG)
|
|
21
|
+
|
|
22
|
+
If your feature needs ONLY these 4 → use a single agent. Don't reach for workflows.
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Apply this rule before deciding multi-agent or workflow. Most of the time the answer is "single agent".
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## Decision tree (Claude reads this in plan phase)
|
|
30
|
+
|
|
31
|
+
```
|
|
32
|
+
Step 1 — Does the task even need an LLM?
|
|
33
|
+
├─ "Can a deterministic function solve this?" → YES → write the function, no agent
|
|
34
|
+
└─ NO → continue
|
|
35
|
+
|
|
36
|
+
Step 2 — Single agent vs multi-agent?
|
|
37
|
+
├─ One topic + one decision → single agent (DEFAULT)
|
|
38
|
+
├─ Multiple specialized topics with clear handoff → multi-agent
|
|
39
|
+
│ └─ Prefer Agent-as-Tool pattern (one agent uses another as a tool)
|
|
40
|
+
│ └─ See ai-agents-multi-agent-patterns
|
|
41
|
+
└─ "I'm not sure" → start with single agent, escalate if needed
|
|
42
|
+
|
|
43
|
+
Step 3 — Need a Workflow?
|
|
44
|
+
├─ The feature has fan-in/out (many inputs aggregated) → maybe workflow
|
|
45
|
+
│ └─ Confirm: can you write it as a foreach + LINQ aggregate?
|
|
46
|
+
│ └─ YES → write the foreach, NO workflow
|
|
47
|
+
│ └─ NO → workflow may help
|
|
48
|
+
├─ Long-running with human-in-the-loop checkpoints → maybe workflow
|
|
49
|
+
│ └─ See ai-agents-workflows (Onda 2)
|
|
50
|
+
└─ Otherwise → NO workflow
|
|
51
|
+
|
|
52
|
+
Step 4 — Which provider?
|
|
53
|
+
├─ ALWAYS direct models — never a hosted agent service
|
|
54
|
+
├─ Provider choice (OpenAI / Google / Anthropic / Ollama) lives in the
|
|
55
|
+
│ project's Model Registry, not hardcoded → see ai-agents-providers-model-registry
|
|
56
|
+
└─ Hosted services (Foundry Agent Service, Vertex AI hosted) → NOT used
|
|
57
|
+
|
|
58
|
+
Step 5 — RAG approach?
|
|
59
|
+
├─ Need control over chunking/embedding/storage? → RAG custom (pgvector)
|
|
60
|
+
│ └─ See ai-agents-rag-custom-pgvector (Onda 2)
|
|
61
|
+
└─ Don't reach for hosted RAG — loses control
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
## Anti-patterns (what Claude should NOT do)
|
|
67
|
+
|
|
68
|
+
| Anti-pattern | Why it's wrong | What to do instead |
|
|
69
|
+
|--------------|----------------|---------------------|
|
|
70
|
+
| Reach for Workflow when a function does the job | "Tudo que Workflows fazem, código C# normal faz com menos complexidade" — pesquisa | Write the function. Wrap in a tool if an agent needs to call it. |
|
|
71
|
+
| Use a hosted agent service | Hosted is "always 6-9 months behind" on models + removes control | Direct models always (OpenAI / Google / Anthropic / Ollama) |
|
|
72
|
+
| Upload PDFs to a hosted RAG | Loses control of chunking + embedding + storage | Custom RAG with pgvector. See ai-agents-rag-custom-pgvector |
|
|
73
|
+
| Add Memory/Session because "agents need state" | If not building a chatbot, don't add chat state | Skip. Add when a real chat UI exists. |
|
|
74
|
+
| Use Semantic Kernel for new features | SK is superseded by MAF | Use Microsoft.Agents.AI. See ai-agents-setup |
|
|
75
|
+
| Return free text and parse it downstream | LLM output is non-deterministic | Structured Output. See ai-agents-structured-output (Onda 2) |
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
## When the 4-feature rule does NOT apply
|
|
80
|
+
|
|
81
|
+
You may need to step outside the sweet spot when:
|
|
82
|
+
- The feature genuinely fans-out (e.g., monthly report aggregating N items) → workflow with fan-in
|
|
83
|
+
- The feature needs vision/audio/image-gen → modality-specific standards (see ai-agents-modalities-* — Onda 3)
|
|
84
|
+
|
|
85
|
+
**Even in these cases:** the core of the agent still follows the 4-feature rule. Only the orchestration layer changes.
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
## Checklist (verifiable by morph-eval)
|
|
90
|
+
|
|
91
|
+
- [ ] Was each agent designed as single-agent BEFORE reaching for multi-agent? (Check `decisions.md`.)
|
|
92
|
+
- [ ] If a Workflow is used, is fan-in/out or HITL checkpointing genuinely present? Or could `foreach + LINQ` replace it?
|
|
93
|
+
- [ ] Are the 4 features (Chat, Structured Output, Tools, RAG Custom) all present where applicable?
|
|
94
|
+
- [ ] Are hosted agent services avoided?
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
## References
|
|
99
|
+
|
|
100
|
+
- `ai-agents-setup` — packages and DI
|
|
101
|
+
- `ai-agents-providers-model-registry` — provider-agnostic config
|
|
102
|
+
- `ai-agents-structured-output` (Onda 2)
|
|
103
|
+
- `ai-agents-multi-agent-patterns` (Onda 2)
|
|
104
|
+
- `ai-agents-workflows` (Onda 2, reframed)
|
|
105
|
+
- `ai-agents-rag-custom-pgvector` (Onda 2)
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
*MORPH-SPEC by Polymorphism Tech — ai-agents/sweet-spot.md v1.0 (2026-05-19)*
|
|
@@ -10,7 +10,8 @@
|
|
|
10
10
|
|
|
11
11
|
Padrão arquitetural baseado em **KanaiyaKatarmal/VerticalSliceArchitectureTemplate** (.NET 10, Minimal APIs, EF Core 10).
|
|
12
12
|
Cada feature é uma fatia vertical completa: endpoint → handler → validator → entity → DB.
|
|
13
|
-
Sem MediatR, sem AggregateRoot, sem Domain Events, sem CQRS formal
|
|
13
|
+
Sem MediatR, sem AggregateRoot, sem Domain Events, sem CQRS formal, sem repository genérico
|
|
14
|
+
(divergência intencional do template base: o handler usa `ApplicationDbContext` direto).
|
|
14
15
|
|
|
15
16
|
---
|
|
16
17
|
|
|
@@ -54,11 +55,6 @@ Sem MediatR, sem AggregateRoot, sem Domain Events, sem CQRS formal.
|
|
|
54
55
|
├── Pipelines/
|
|
55
56
|
│ ├── ValidationDecorator.cs # Decorator que executa validators
|
|
56
57
|
│ └── LoggingDecorator.cs # Decorator que loga request/failure
|
|
57
|
-
├── Repository/
|
|
58
|
-
│ ├── IRepository.cs # IRepository<T> genérico
|
|
59
|
-
│ ├── Repository.cs # Repository<T> : IRepository<T>
|
|
60
|
-
│ ├── IUnitOfWork.cs # IUnitOfWork
|
|
61
|
-
│ └── UnitOfWork.cs # UnitOfWork
|
|
62
58
|
└── Program.cs
|
|
63
59
|
```
|
|
64
60
|
|
|
@@ -190,8 +186,7 @@ public sealed record CreateBookRequest(string Title, string Author, string ISBN,
|
|
|
190
186
|
public sealed record CreateBookResponse(Guid Id, string Title, string Author, string ISBN, decimal Price, int PublishedYear);
|
|
191
187
|
|
|
192
188
|
public sealed class CreateBookHandler(
|
|
193
|
-
|
|
194
|
-
IUnitOfWork _unitOfWork) : IHandler<CreateBookRequest, Result<CreateBookResponse>>
|
|
189
|
+
ApplicationDbContext _db) : IHandler<CreateBookRequest, Result<CreateBookResponse>>
|
|
195
190
|
{
|
|
196
191
|
public async Task<Result<CreateBookResponse>> HandleAsync(
|
|
197
192
|
CreateBookRequest command, CancellationToken cancellationToken)
|
|
@@ -205,8 +200,8 @@ public sealed class CreateBookHandler(
|
|
|
205
200
|
Price = command.Price,
|
|
206
201
|
PublishedYear = command.PublishedYear
|
|
207
202
|
};
|
|
208
|
-
|
|
209
|
-
await
|
|
203
|
+
_db.Books.Add(book);
|
|
204
|
+
await _db.SaveChangesAsync(cancellationToken);
|
|
210
205
|
return Result.Success(new CreateBookResponse(
|
|
211
206
|
book.Id, book.Title, book.Author, book.ISBN, book.Price, book.PublishedYear));
|
|
212
207
|
}
|
|
@@ -306,38 +301,41 @@ Ordem de execução (innermost-first): `LoggingDecorator → ValidationDecorator
|
|
|
306
301
|
|
|
307
302
|
---
|
|
308
303
|
|
|
309
|
-
##
|
|
304
|
+
## Acesso a Dados — DbContext direto no slice
|
|
305
|
+
|
|
306
|
+
O handler injeta `ApplicationDbContext` direto. Sobre EF Core um repository genérico é
|
|
307
|
+
redundante: `DbSet<T>` já é repository, `DbContext` já é unit of work. **Nunca crie
|
|
308
|
+
`IRepository<T>`/`IUnitOfWork`** — a auditoria de projetos reais mostrou que o par nasce
|
|
309
|
+
registrado no DI e morre órfão, sem consumidores.
|
|
310
310
|
|
|
311
311
|
```csharp
|
|
312
|
-
//
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
312
|
+
// CRUD por chave — direto no DbSet
|
|
313
|
+
var book = await _db.Books.FindAsync([command.Id], cancellationToken);
|
|
314
|
+
_db.Books.Add(book);
|
|
315
|
+
await _db.SaveChangesAsync(cancellationToken);
|
|
316
|
+
|
|
317
|
+
// Queries específicas — LINQ no slice (joins, projeção, paginação, filtros compostos)
|
|
318
|
+
var page = await _db.Books
|
|
319
|
+
.Where(b => b.Author == command.Author)
|
|
320
|
+
.OrderBy(b => b.Title)
|
|
321
|
+
.Skip(command.Skip).Take(command.Take)
|
|
322
|
+
.Select(b => new BookDto(b.Id, b.Title))
|
|
323
|
+
.ToListAsync(cancellationToken);
|
|
324
324
|
```
|
|
325
325
|
|
|
326
|
-
**
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
326
|
+
**Stores dedicados (exceção justificada):** quando um slice exige controle de concorrência
|
|
327
|
+
(ex.: `SELECT ... FOR UPDATE`, retry de `DbUpdateConcurrencyException`) ou SQL específico
|
|
328
|
+
que o LINQ não expressa, escreva um store hand-written e purpose-built (ex.: `OutboxStore`,
|
|
329
|
+
`LeaseStore`) com métodos nomeados pela intenção — e registre a justificativa em
|
|
330
|
+
`decisions.md`. Nunca um genérico por entidade.
|
|
331
|
+
|
|
332
|
+
Veja `backend/dotnet/vsa-handler-patterns.md` → "Data Access: DbContext in the Slice".
|
|
331
333
|
|
|
332
334
|
---
|
|
333
335
|
|
|
334
336
|
## DI Registration (Program.cs)
|
|
335
337
|
|
|
336
338
|
```csharp
|
|
337
|
-
// Repositories + Unit of Work
|
|
338
|
-
builder.Services.AddScoped(typeof(IRepository<>), typeof(Repository<>));
|
|
339
|
-
builder.Services.AddScoped(typeof(IUnitOfWork), typeof(UnitOfWork));
|
|
340
|
-
|
|
341
339
|
// EF Core (SQLite/SqlServer/PostgreSQL)
|
|
342
340
|
builder.Services.AddSQLDatabaseConfiguration(builder.Configuration);
|
|
343
341
|
|
|
@@ -385,8 +383,8 @@ app.MapApiEndpoints();
|
|
|
385
383
|
4. **`result.Match()`** nos endpoints — nunca acessar `result.Value` sem checar `IsSuccess`.
|
|
386
384
|
5. **IApiEndpoint `internal sealed`** — endpoints não são públicos.
|
|
387
385
|
6. **`{Entity}Errors` compartilhado** por todas as operações da feature (1 arquivo por feature, não por slice).
|
|
388
|
-
7.
|
|
389
|
-
8. **`
|
|
386
|
+
7. **`ApplicationDbContext` direto no slice**: CRUD por chave via `DbSet<T>`; queries específicas (joins, projeção, paginação) via LINQ no handler. Nunca uma Application Service layer entre handler e dados; nunca repository genérico (`IRepository<T>`/`IUnitOfWork`).
|
|
387
|
+
8. **`SaveChangesAsync()`** explícito em operações de escrita (Create, Update, Delete).
|
|
390
388
|
9. **Sem validator em GetAll** — GetAllBooksHandler não tem validator (sem parâmetros para validar).
|
|
391
389
|
|
|
392
390
|
---
|
|
@@ -399,7 +397,8 @@ app.MapApiEndpoints();
|
|
|
399
397
|
| `DomainEvent` | VSA não tem Domain Events — reações são handlers separados |
|
|
400
398
|
| `MediatR` / `IRequest` / `IRequestHandler` | Substituído por `IHandler<TRequest, TResponse>` próprio |
|
|
401
399
|
| DTOs compartilhados entre features | Cada slice define seus próprios records |
|
|
402
|
-
| Application Service layer | Handler acessa
|
|
400
|
+
| Application Service layer | Handler acessa o `ApplicationDbContext` diretamente |
|
|
401
|
+
| Repository genérico (`IRepository<T>`/`IUnitOfWork`) | `DbSet<T>` já é repository, `DbContext` já é unit of work — vira código morto |
|
|
403
402
|
| CQRS formal (Commands/Queries) | Handler simples, sem distinção Command/Query |
|
|
404
403
|
| Shared Validators em Application layer | Validator fica no mesmo namespace do slice |
|
|
405
404
|
|
|
@@ -62,7 +62,7 @@ public enum OrderStatus
|
|
|
62
62
|
```csharp
|
|
63
63
|
public async Task<Result> ProcessOrderAsync(Guid orderId, CancellationToken ct)
|
|
64
64
|
{
|
|
65
|
-
var order = await
|
|
65
|
+
var order = await _db.Orders.FindAsync([orderId], ct);
|
|
66
66
|
if (order == null) return Result.Failure("Order not found");
|
|
67
67
|
|
|
68
68
|
// State validation (invalid states)
|
|
@@ -74,7 +74,7 @@ public async Task<Result> ProcessOrderAsync(Guid orderId, CancellationToken ct)
|
|
|
74
74
|
if (order.RequiresPayment && !order.IsPaid) return Result.Failure("Payment required");
|
|
75
75
|
|
|
76
76
|
order.MarkAsProcessing();
|
|
77
|
-
await
|
|
77
|
+
await _db.SaveChangesAsync(ct);
|
|
78
78
|
return Result.Success();
|
|
79
79
|
}
|
|
80
80
|
```
|
|
@@ -126,9 +126,7 @@ builder.Services.AddHandlersFromAssembly(typeof(Program).Assembly);
|
|
|
126
126
|
The full DI registration sequence in VSA:
|
|
127
127
|
|
|
128
128
|
```csharp
|
|
129
|
-
// 1. Infrastructure
|
|
130
|
-
builder.Services.AddScoped(typeof(IRepository<>), typeof(Repository<>));
|
|
131
|
-
builder.Services.AddScoped(typeof(IUnitOfWork), typeof(UnitOfWork));
|
|
129
|
+
// 1. Infrastructure (DbContext)
|
|
132
130
|
builder.Services.AddSQLDatabaseConfiguration(builder.Configuration);
|
|
133
131
|
|
|
134
132
|
// 2. Endpoints (auto-discovery)
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
|
|
12
12
|
## Overview
|
|
13
13
|
|
|
14
|
-
Each feature in VSA is a self-contained slice with 5 files in a feature folder. Handlers implement `IHandler<TRequest, TResponse>` (not MediatR). Dependencies use primary constructors. The data path is `Handler ->
|
|
14
|
+
Each feature in VSA is a self-contained slice with 5 files in a feature folder. Handlers implement `IHandler<TRequest, TResponse>` (not MediatR). Dependencies use primary constructors. The data path is `Handler -> ApplicationDbContext` — no service layer, no generic repository in between.
|
|
15
15
|
|
|
16
16
|
---
|
|
17
17
|
|
|
@@ -71,8 +71,8 @@ public sealed record CreateBookResponse(
|
|
|
71
71
|
namespace ProjectName.Features.BookFeature.CreateBook;
|
|
72
72
|
|
|
73
73
|
using ProjectName.Abstractions;
|
|
74
|
+
using ProjectName.Database;
|
|
74
75
|
using ProjectName.Entities;
|
|
75
|
-
using ProjectName.Repository;
|
|
76
76
|
|
|
77
77
|
public sealed record CreateBookRequest(
|
|
78
78
|
string Title, string Author, string ISBN, decimal Price, int PublishedYear);
|
|
@@ -82,8 +82,7 @@ public sealed record CreateBookResponse(
|
|
|
82
82
|
|
|
83
83
|
// Primary constructor for DI — no field declarations needed
|
|
84
84
|
public sealed class CreateBookHandler(
|
|
85
|
-
|
|
86
|
-
IUnitOfWork unitOfWork) : IHandler<CreateBookRequest, Result<CreateBookResponse>>
|
|
85
|
+
ApplicationDbContext db) : IHandler<CreateBookRequest, Result<CreateBookResponse>>
|
|
87
86
|
{
|
|
88
87
|
public async Task<Result<CreateBookResponse>> HandleAsync(
|
|
89
88
|
CreateBookRequest command, CancellationToken cancellationToken)
|
|
@@ -98,8 +97,8 @@ public sealed class CreateBookHandler(
|
|
|
98
97
|
PublishedYear = command.PublishedYear
|
|
99
98
|
};
|
|
100
99
|
|
|
101
|
-
|
|
102
|
-
await
|
|
100
|
+
db.Books.Add(book);
|
|
101
|
+
await db.SaveChangesAsync(cancellationToken);
|
|
103
102
|
|
|
104
103
|
return Result.Success(new CreateBookResponse(
|
|
105
104
|
book.Id, book.Title, book.Author, book.ISBN, book.Price, book.PublishedYear));
|
|
@@ -111,7 +110,7 @@ public sealed class CreateBookHandler(
|
|
|
111
110
|
|
|
112
111
|
- **`sealed class`** — handlers are never inherited
|
|
113
112
|
- **`sealed record`** — DTOs are immutable value types
|
|
114
|
-
- **Primary constructors** — `(
|
|
113
|
+
- **Primary constructors** — `(ApplicationDbContext db)` replaces field + constructor boilerplate
|
|
115
114
|
- **`Guid.CreateVersion7()`** — time-sortable, index-friendly UUID (not `Guid.NewGuid()`)
|
|
116
115
|
- **`CancellationToken`** — passed through every async call
|
|
117
116
|
- **`Result<T>` return** — never throw for expected failures
|
|
@@ -172,36 +171,35 @@ All endpoint classes implementing this interface are auto-discovered and mapped
|
|
|
172
171
|
|
|
173
172
|
---
|
|
174
173
|
|
|
175
|
-
##
|
|
174
|
+
## Data Access: DbContext in the Slice (No Service Layer, No Generic Repository)
|
|
176
175
|
|
|
177
|
-
Handlers
|
|
176
|
+
Handlers inject `ApplicationDbContext` directly. Over EF Core a generic repository is
|
|
177
|
+
redundant — `DbSet<T>` **is** the repository, `DbContext` **is** the unit of work. A
|
|
178
|
+
generic `IRepository<T>`/`IUnitOfWork` pair adds indirection with zero value and, in
|
|
179
|
+
audited real projects, ends up registered in DI with zero consumers (dead code).
|
|
180
|
+
|
|
181
|
+
CRUD by key goes straight to the `DbSet`:
|
|
178
182
|
|
|
179
183
|
```csharp
|
|
180
|
-
public
|
|
184
|
+
public sealed class DeleteBookHandler(
|
|
185
|
+
ApplicationDbContext db) : IHandler<DeleteBookRequest, Result>
|
|
181
186
|
{
|
|
182
|
-
Task<
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
void RemoveRange(IEnumerable<T> entities);
|
|
189
|
-
}
|
|
187
|
+
public async Task<Result> HandleAsync(
|
|
188
|
+
DeleteBookRequest command, CancellationToken cancellationToken)
|
|
189
|
+
{
|
|
190
|
+
var book = await db.Books.FindAsync([command.Id], cancellationToken);
|
|
191
|
+
if (book is null)
|
|
192
|
+
return BookErrors.NotFound(command.Id);
|
|
190
193
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
194
|
+
db.Books.Remove(book);
|
|
195
|
+
await db.SaveChangesAsync(cancellationToken);
|
|
196
|
+
return Result.Success();
|
|
197
|
+
}
|
|
195
198
|
}
|
|
196
199
|
```
|
|
197
200
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
### When to Use DbContext Directly Instead
|
|
201
|
-
|
|
202
|
-
`IRepository<T>` is the right tool for CRUD by key. It is **not** the right tool for
|
|
203
|
-
slice-specific queries — joins, projections, pagination, composite filters. For those,
|
|
204
|
-
inject `ApplicationDbContext` directly into the handler and write the LINQ in the slice:
|
|
201
|
+
Slice-specific queries — joins, projections, pagination, composite filters — are LINQ
|
|
202
|
+
written in the slice:
|
|
205
203
|
|
|
206
204
|
```csharp
|
|
207
205
|
public sealed class SearchBooksHandler(
|
|
@@ -222,8 +220,16 @@ public sealed class SearchBooksHandler(
|
|
|
222
220
|
}
|
|
223
221
|
```
|
|
224
222
|
|
|
225
|
-
|
|
226
|
-
|
|
223
|
+
### Dedicated Stores (the justified exception)
|
|
224
|
+
|
|
225
|
+
When a slice needs concurrency control (`SELECT ... FOR UPDATE`, lease/outbox semantics,
|
|
226
|
+
`DbUpdateConcurrencyException` retry loops) or SQL that LINQ cannot express, write a
|
|
227
|
+
**hand-written, purpose-built store** with intention-named methods (e.g. `OutboxStore.ClaimBatchAsync`,
|
|
228
|
+
`LeaseStore.TryAcquireAsync`) — and document the justification in `decisions.md`.
|
|
229
|
+
Never a generic-per-entity abstraction.
|
|
230
|
+
|
|
231
|
+
**Rule of thumb:** everything → `ApplicationDbContext` in the slice. Concurrency or
|
|
232
|
+
specific SQL → dedicated hand-written store + `decisions.md` entry. Never reach for raw SQL
|
|
227
233
|
(`FromSqlRaw`) unless a query genuinely cannot be expressed in LINQ — and document that
|
|
228
234
|
in `decisions.md`.
|
|
229
235
|
|
|
@@ -251,7 +257,7 @@ public static class BookErrors
|
|
|
251
257
|
Usage in handler:
|
|
252
258
|
|
|
253
259
|
```csharp
|
|
254
|
-
var existing = await
|
|
260
|
+
var existing = await db.Books.FindAsync([command.BookId], cancellationToken);
|
|
255
261
|
if (existing is null)
|
|
256
262
|
return BookErrors.NotFound(command.BookId); // implicit conversion to Result<T>
|
|
257
263
|
```
|
|
@@ -264,9 +270,7 @@ if (existing is null)
|
|
|
264
270
|
// 1. OpenAPI
|
|
265
271
|
builder.Services.AddOpenApi();
|
|
266
272
|
|
|
267
|
-
// 2. Infrastructure (
|
|
268
|
-
builder.Services.AddScoped(typeof(IRepository<>), typeof(Repository<>));
|
|
269
|
-
builder.Services.AddScoped(typeof(IUnitOfWork), typeof(UnitOfWork));
|
|
273
|
+
// 2. Infrastructure (DbContext)
|
|
270
274
|
builder.Services.AddSQLDatabaseConfiguration(builder.Configuration);
|
|
271
275
|
|
|
272
276
|
// 3. Endpoint auto-discovery
|
|
@@ -293,7 +297,8 @@ builder.Services.AddExceptionHandler<CustomExceptionHandler>()
|
|
|
293
297
|
| Do NOT | Do Instead |
|
|
294
298
|
|--------|------------|
|
|
295
299
|
| Use MediatR `IRequest`/`Send()` | Use `IHandler<TReq, TRes>` resolved directly from DI |
|
|
296
|
-
| Create a service/manager layer | Handler
|
|
300
|
+
| Create a service/manager layer | Handler uses `ApplicationDbContext` directly |
|
|
301
|
+
| Add a generic repository (`IRepository<T>`/`IUnitOfWork`) | `DbSet<T>` is the repository; `DbContext` is the unit of work |
|
|
297
302
|
| Use `Guid.NewGuid()` | Use `Guid.CreateVersion7()` for time-sortable IDs |
|
|
298
303
|
| Make handler classes non-sealed | Always `sealed class` |
|
|
299
304
|
| Use mutable DTOs (class with setters) | Always `sealed record` with positional parameters |
|
|
@@ -302,7 +307,7 @@ builder.Services.AddExceptionHandler<CustomExceptionHandler>()
|
|
|
302
307
|
| Share handlers across features | Each feature has its own handler, even if similar |
|
|
303
308
|
| Use constructor + field for DI | Use primary constructors: `class Handler(IDep dep)` |
|
|
304
309
|
| Group by technical concern (Controllers/, Services/) | Group by feature: `Features/BookFeature/CreateBook/` |
|
|
305
|
-
|
|
|
310
|
+
| Route data access through any indirection layer | Inject `DbContext` and write LINQ in the slice; dedicated store only with `decisions.md` justification |
|
|
306
311
|
| Use raw SQL (`FromSqlRaw` / `ExecuteQuery(string)`) by default | Use LINQ; raw SQL only with a `decisions.md` justification |
|
|
307
312
|
|
|
308
313
|
---
|
|
@@ -32,7 +32,7 @@ builder.Services.AddStackExchangeRedisCache(opts =>
|
|
|
32
32
|
public class CachedProductService : IProductService
|
|
33
33
|
{
|
|
34
34
|
private readonly IDistributedCache _cache;
|
|
35
|
-
private readonly
|
|
35
|
+
private readonly ApplicationDbContext _db;
|
|
36
36
|
|
|
37
37
|
public async Task<Product?> GetAsync(Guid id, CancellationToken ct = default)
|
|
38
38
|
{
|
|
@@ -44,7 +44,7 @@ public class CachedProductService : IProductService
|
|
|
44
44
|
return JsonSerializer.Deserialize<Product>(cached);
|
|
45
45
|
|
|
46
46
|
// Cache miss → read from DB
|
|
47
|
-
var product = await
|
|
47
|
+
var product = await _db.Products.FindAsync([id], ct);
|
|
48
48
|
if (product == null) return null;
|
|
49
49
|
|
|
50
50
|
// Cache with TTL
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
VSA + .NET 10 + EF Core + Neon (Postgres) + Microsoft Agent Framework + Next.js (App Router) + Tailwind + Framer Motion + Shadcn + Hangfire (quando necessário).
|
|
8
8
|
|
|
9
|
-
Padrões proibidos por padrão: `Guid.NewGuid()` (use `Guid.CreateVersion7()`), classes não-sealed sem justificativa, exceptions como fluxo de controle, camada de Service/Manager entre handler e dados, SQL cru sem justificativa. Acesso a dados
|
|
9
|
+
Padrões proibidos por padrão: `Guid.NewGuid()` (use `Guid.CreateVersion7()`), classes não-sealed sem justificativa, exceptions como fluxo de controle, camada de Service/Manager entre handler e dados, repository genérico (`IRepository<T>`/`IUnitOfWork`), SQL cru sem justificativa. Acesso a dados: `ApplicationDbContext` direto no slice (`DbSet<T>` já é repository, `DbContext` já é unit of work); para concorrência ou SQL específico, store dedicado hand-written com justificativa em `decisions.md`.
|
|
10
10
|
|
|
11
11
|
## Harness
|
|
12
12
|
|