@polymorphism-tech/morph-spec 9.0.0 → 9.2.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 +8 -9
- package/README.md +38 -36
- package/bin/morph-spec.js +11 -63
- package/bin/validate.js +1 -39
- package/claude-plugin.json +1 -1
- package/docs/CHEATSHEET.md +1 -1
- package/docs/QUICKSTART.md +1 -1
- package/framework/CLAUDE.md +2 -1
- package/framework/MORPH.md +3 -3
- package/framework/agents.json +19 -11
- package/framework/commands/morph-apply.md +1 -1
- package/framework/commands/morph-status.md +1 -1
- package/framework/docs/CLI.md +22 -31
- package/framework/evals/review-checklist.md +12 -2
- package/framework/hooks/README.md +8 -44
- package/framework/hooks/claude-code/core/block-destructive-worktree-removal.js +6 -49
- package/framework/hooks/claude-code/core/block-raw-worktree-add.js +2 -35
- package/framework/hooks/claude-code/core/chain.js +4 -65
- package/framework/hooks/claude-code/core/comment-guard.js +2 -16
- package/framework/hooks/claude-code/core/enforce-phase-writes.js +6 -50
- package/framework/hooks/claude-code/core/gate-guard.js +7 -67
- package/framework/hooks/claude-code/core/graph-refresh-kick.js +3 -24
- package/framework/hooks/claude-code/core/heavy-gate.js +3 -21
- package/framework/hooks/claude-code/core/heavy-release.js +2 -13
- package/framework/hooks/claude-code/core/loop-detect.js +2 -31
- package/framework/hooks/claude-code/core/pre-bash-denylist.js +0 -4
- package/framework/hooks/claude-code/core/protect-spec-files.js +7 -67
- package/framework/hooks/claude-code/core/state-sync.js +12 -68
- package/framework/hooks/claude-code/core/telemetry-log.js +12 -93
- package/framework/hooks/claude-code/core/validate-completion.js +11 -57
- package/framework/hooks/claude-code/core/warn-bash-source-write.js +1 -9
- package/framework/hooks/claude-code/core/worktree-context.js +10 -110
- package/framework/hooks/claude-code/core/worktree-create.js +6 -70
- package/framework/hooks/claude-code/core/worktree-env-inject.js +5 -56
- package/framework/hooks/claude-code/core/worktree-remove.js +5 -31
- package/framework/hooks/claude-code/statusline.sh +11 -11
- package/framework/hooks/shared/activity-logger.js +9 -89
- package/framework/hooks/shared/agent-reap.js +2 -17
- package/framework/hooks/shared/build-check.js +6 -75
- package/framework/hooks/shared/claim-registry.js +9 -119
- package/framework/hooks/shared/command-tokens.js +6 -51
- package/framework/hooks/shared/comment-rules.js +5 -20
- package/framework/hooks/shared/comment-scanner.js +6 -25
- package/framework/hooks/shared/comment-stripper.js +11 -85
- package/framework/hooks/shared/e2e-check.js +0 -0
- package/framework/hooks/shared/errata.js +2 -15
- package/framework/hooks/shared/feature-locator.js +11 -95
- package/framework/hooks/shared/frontend-locations.js +1 -3
- package/framework/hooks/shared/gate-question.js +1 -7
- package/framework/hooks/shared/git-exclude.js +3 -20
- package/framework/hooks/shared/git-paths.js +8 -104
- package/framework/hooks/shared/graph-freshness.js +22 -166
- package/framework/hooks/shared/graph-refresh.js +21 -149
- package/framework/hooks/shared/heavy-command.js +4 -12
- package/framework/hooks/shared/hook-response.js +8 -83
- package/framework/hooks/shared/morph-cli.js +8 -52
- package/framework/hooks/shared/payload-utils.js +10 -132
- package/framework/hooks/shared/phase-utils.js +1 -42
- package/framework/hooks/shared/redact-secrets.js +4 -15
- package/framework/hooks/shared/reparse-scan.js +6 -66
- package/framework/hooks/shared/slice-progress.js +4 -23
- package/framework/hooks/shared/source-files.js +4 -17
- package/framework/hooks/shared/state-reader.js +13 -183
- package/framework/hooks/shared/state-writer.js +8 -52
- package/framework/hooks/shared/stdin-reader.js +1 -11
- package/framework/hooks/shared/telemetry-logger.js +7 -67
- package/framework/hooks/shared/transcript-usage.js +7 -29
- package/framework/hooks/shared/worktree-root.js +7 -29
- package/framework/rules/csharp-standards.md +1 -1
- package/framework/rules/frontend-standards.md +1 -0
- package/framework/rules/hangfire-standards.md +1 -1
- package/framework/rules/neon-standards.md +1 -1
- package/framework/rules/nextjs-standards.md +1 -1
- 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 +4 -4
- package/framework/skills/core/morph-hotfix/SKILL.md +3 -3
- package/framework/skills/core/morph-machine/SKILL.md +1 -1
- package/framework/skills/core/morph-media/SKILL.md +1 -1
- package/framework/skills/core/morph-plan/SKILL.md +9 -7
- package/framework/skills/core/morph-review/SKILL.md +9 -6
- package/framework/skills/core/morph-standards/SKILL.md +1 -1
- package/framework/skills/core/morph-uiux/SKILL.md +29 -12
- package/framework/standards/STANDARDS.json +36 -14
- package/framework/standards/STANDARDS.meta.json +16 -2
- package/framework/standards/ai-agents/agent-archetypes.md +2 -2
- package/framework/standards/ai-agents/agent-session.md +1 -1
- package/framework/standards/ai-agents/conversational-agent-with-phases.md +4 -4
- package/framework/standards/ai-agents/durable-workflows-hitl.md +3 -3
- package/framework/standards/ai-agents/guardrails.md +1 -1
- package/framework/standards/ai-agents/multi-agent-patterns.md +2 -2
- package/framework/standards/ai-agents/observability-patterns.md +1 -1
- package/framework/standards/ai-agents/production.md +1 -1
- package/framework/standards/ai-agents/prompt-sources.md +2 -2
- package/framework/standards/ai-agents/providers/model-registry.md +1 -1
- package/framework/standards/ai-agents/rag-custom-pgvector.md +2 -2
- package/framework/standards/ai-agents/setup.md +2 -2
- package/framework/standards/ai-agents/sweet-spot.md +2 -2
- package/framework/standards/ai-agents/tool-adapters.md +0 -2
- package/framework/standards/ai-agents/vector-data-extensions.md +4 -4
- package/framework/standards/ai-agents/workflows.md +1 -1
- package/framework/standards/architecture/code-comments.md +1 -1
- package/framework/standards/architecture/vertical-slice/vertical-slice.md +1 -1
- package/framework/standards/backend/api/validation.md +1 -1
- package/framework/standards/backend/dotnet/testing.md +10 -4
- package/framework/standards/backend/dotnet/vsa-handler-patterns.md +5 -5
- package/framework/standards/backend/integrations/hangfire/hangfire-jobs.md +1 -1
- package/framework/standards/backend/integrations/neon-auth/neon-auth.md +2 -2
- package/framework/standards/data/nosql/cache/redis.md +1 -1
- package/framework/standards/data/nosql/cosmos-db.md +3 -3
- package/framework/standards/frontend/admin-app/forms.md +1 -1
- package/framework/standards/frontend/design-system/art-direction.md +88 -0
- package/framework/standards/frontend/design-system/premium-finish.md +77 -2
- package/framework/standards/frontend/nextjs/testing.md +10 -6
- package/framework/standards/infrastructure/neon/neon-pgvector.md +5 -5
- package/framework/templates/.idea/morph-templates.xml +92 -0
- package/framework/templates/.vscode/morph-templates.code-snippets +186 -0
- package/framework/templates/CLAUDE.md.core.template +3 -3
- package/framework/templates/IDE-SNIPPETS.md +262 -262
- package/framework/templates/REGISTRY.json +9 -9
- package/framework/templates/code/dotnet/abstractions/api-endpoint.cs +7 -7
- package/framework/templates/code/dotnet/feature/request.cs.hbs +4 -4
- package/framework/templates/code/dotnet/feature/response.cs.hbs +4 -4
- package/framework/templates/code/sql/ai-agents/pgvector-embeddings-migration.sql.template +91 -91
- package/framework/templates/docs/plan.md +3 -1
- package/framework/templates/docs/ui-mockups.md +4 -0
- package/framework/templates/frontend/nextjs/client-component.tsx.hbs +26 -26
- package/framework/templates/frontend/nextjs/feature-form.tsx.hbs +56 -56
- package/framework/templates/frontend/nextjs/tsconfig.json.hbs +26 -26
- package/framework/templates/infrastructure/docker/Dockerfile.template +58 -58
- package/framework/templates/infrastructure/docker/dockerfile-api.dockerfile +38 -38
- package/framework/templates/infrastructure/docker/dockerfile-web.dockerfile +48 -48
- package/framework/templates/infrastructure/github/actions/docker-build-push/action.yml.hbs +45 -45
- package/framework/templates/infrastructure/github/actions/health-check/action.yml.hbs +27 -27
- package/framework/templates/infrastructure/github/workflows/docker-build-push.yml.hbs +59 -59
- package/framework/templates/infrastructure/github/workflows/dotnet-build.yml.hbs +39 -39
- package/framework/templates/meta-prompts/fusion/fusion-agent.md +76 -76
- package/framework/templates/meta-prompts/fusion/fusion-aggregator.md +100 -100
- package/framework/templates/state.template.json +17 -17
- package/framework/templates/ui/design-system.css +226 -226
- package/package.json +1 -1
- package/src/commands/context/context-refresh.js +0 -16
- package/src/commands/dashboard/dashboard.js +2 -8
- package/src/commands/e2e/e2e.js +17 -104
- package/src/commands/graph/affected.js +0 -14
- package/src/commands/graph/build.js +11 -50
- package/src/commands/graph/explain.js +0 -16
- package/src/commands/graph/status.js +1 -12
- package/src/commands/mcp/mcp-setup.js +4 -62
- package/src/commands/observability/cost.js +5 -57
- package/src/commands/observability/telemetry.js +1 -14
- package/src/commands/project/branch.js +4 -45
- package/src/commands/project/claim.js +4 -48
- package/src/commands/project/doctor.js +48 -302
- package/src/commands/project/env.js +1 -27
- package/src/commands/project/fleet.js +3 -32
- package/src/commands/project/init.js +4 -68
- package/src/commands/project/install-plugin-cmd.js +0 -1
- package/src/commands/project/standards.js +1 -62
- package/src/commands/project/status.js +0 -53
- package/src/commands/project/update.js +31 -239
- package/src/commands/project/worktree.js +108 -882
- package/src/commands/scaffold/scaffold.js +0 -25
- package/src/commands/state/advance.js +14 -85
- package/src/commands/state/approve.js +15 -97
- package/src/commands/state/archive.js +12 -64
- package/src/commands/state/auto-approval.js +2 -5
- package/src/commands/state/create.js +22 -109
- package/src/commands/state/delete.js +11 -63
- package/src/commands/state/finish.js +98 -475
- package/src/commands/state/gate-check.js +22 -132
- package/src/commands/state/gate-decision.js +3 -39
- package/src/commands/state/handoff.js +3 -30
- package/src/commands/state/list.js +14 -84
- package/src/commands/state/report-check.js +8 -58
- package/src/commands/state/retype.js +4 -26
- package/src/commands/validation/index.js +0 -3
- package/src/commands/validation/mutate.js +11 -11
- package/src/commands/validation/plan-check.js +4 -2
- package/src/commands/validation/tests-audit.js +7 -0
- package/src/commands/validation/validate-feature.js +10 -97
- package/src/commands/validation/verify.js +30 -113
- package/src/core/paths/output-schema.js +2 -101
- package/src/core/state/feature-identity.js +1 -25
- package/src/core/state/gate-order.js +0 -27
- package/src/core/state/lifecycle-audit.js +8 -100
- package/src/core/state/state-manager.js +22 -304
- package/src/core/templates/template-registry.js +2 -97
- package/src/core/templates/template-renderer.js +13 -93
- package/src/core/templates/template-seed.js +0 -1
- package/src/core/work-types.js +1 -36
- package/src/dashboard/app/App.jsx +1 -8
- package/src/dashboard/app/components/ActivityFeed.jsx +0 -5
- package/src/dashboard/app/components/CommandPalette.jsx +0 -7
- package/src/dashboard/app/components/DetailPanel.jsx +1 -5
- package/src/dashboard/app/components/EntityLink.jsx +0 -12
- package/src/dashboard/app/components/ErrorBoundary.jsx +1 -8
- package/src/dashboard/app/components/KnowledgeMarkdown.jsx +0 -11
- package/src/dashboard/app/components/Markdown.jsx +0 -9
- package/src/dashboard/app/components/PhaseRail.jsx +0 -11
- package/src/dashboard/app/components/SearchFilter.jsx +0 -4
- package/src/dashboard/app/components/Sidebar.jsx +0 -5
- package/src/dashboard/app/hooks/useMorphQuery.js +1 -11
- package/src/dashboard/app/hooks/usePref.js +1 -5
- package/src/dashboard/app/lib/format.js +0 -2
- package/src/dashboard/app/lib/phases.js +1 -5
- package/src/dashboard/app/lib/project.js +0 -12
- package/src/dashboard/app/main.jsx +13 -13
- package/src/dashboard/app/pages/AgentMap.jsx +1 -8
- package/src/dashboard/app/pages/FeatureDetail.jsx +1 -5
- package/src/dashboard/app/pages/Features.jsx +1 -8
- package/src/dashboard/app/pages/Guide.jsx +0 -1
- package/src/dashboard/app/pages/HookInspector.jsx +2 -23
- package/src/dashboard/app/pages/PhasePipeline.jsx +0 -3
- package/src/dashboard/app/pages/Portfolio.jsx +0 -1
- package/src/dashboard/app/pages/RulesMap.jsx +1 -16
- package/src/dashboard/app/pages/SkillsLibrary.jsx +1 -16
- package/src/dashboard/app/pages/Standards.jsx +2 -17
- package/src/dashboard/scanner/activity-scanner.js +10 -69
- package/src/dashboard/scanner/agents-scanner.js +1 -23
- package/src/dashboard/scanner/docs-scanner.js +0 -6
- package/src/dashboard/scanner/feature-status.js +2 -5
- package/src/dashboard/scanner/features-scanner.js +10 -66
- package/src/dashboard/scanner/hooks-scanner.js +5 -28
- package/src/dashboard/scanner/index.js +0 -4
- package/src/dashboard/scanner/portfolio.js +5 -29
- package/src/dashboard/scanner/project-api.js +8 -54
- package/src/dashboard/scanner/rules-scanner.js +1 -2
- package/src/dashboard/scanner/skills-scanner.js +2 -12
- package/src/dashboard/scanner/standards-scanner.js +2 -6
- package/src/dashboard/vite-plugin-morph-data.js +5 -19
- package/src/dashboard/vite.config.js +24 -24
- package/src/lib/ai/ai-checks.js +7 -52
- package/src/lib/ai/ai-project-scan.js +14 -108
- package/src/lib/ai/code-audit.js +7 -63
- package/src/lib/ai/pin-drift.js +8 -51
- package/src/lib/ai/registry-audit.js +11 -90
- package/src/lib/ci/ci-verify.js +4 -19
- package/src/lib/ci/github-ci.js +9 -37
- package/src/lib/ci/workflow-scaffold.js +14 -10
- package/src/lib/detectors/claude-config-detector.js +13 -74
- package/src/lib/detectors/index.js +4 -50
- package/src/lib/e2e/applicability.js +37 -0
- package/src/lib/e2e/compose-env.js +5 -66
- package/src/lib/e2e/stack-teardown.js +6 -40
- package/src/lib/generators/architecture-scanner.js +18 -156
- package/src/lib/generators/handoff-generator.js +9 -53
- package/src/lib/generators/scaffold-generator.js +5 -101
- package/src/lib/generators/settings-generator.js +4 -37
- package/src/lib/git/branch-classifier.js +11 -84
- package/src/lib/git/default-branch.js +14 -169
- package/src/lib/git/evidence-staleness.js +1 -0
- package/src/lib/git/gh.js +2 -17
- package/src/lib/git/pending-prs.js +6 -37
- package/src/lib/git/pr-files.js +3 -28
- package/src/lib/git/pr-index.js +3 -34
- package/src/lib/git/tree-fingerprint.js +3 -15
- package/src/lib/git/tree-state.js +6 -33
- package/src/lib/graph/god-nodes.js +2 -22
- package/src/lib/graph/graph-health.js +3 -24
- package/src/lib/graph/graph-loader.js +5 -56
- package/src/lib/graph/graphify-runtime.js +7 -37
- package/src/lib/graph/graphifyignore.js +8 -74
- package/src/lib/graph/sanitize.js +16 -136
- package/src/lib/heavy/cleanup.js +1 -1
- package/src/lib/installers/effects.js +8 -93
- package/src/lib/installers/mcp-installer.js +9 -169
- package/src/lib/installers/memory-installer.js +4 -27
- package/src/lib/installers/templates-reconcile.js +5 -60
- package/src/lib/mutation/adapters.js +6 -17
- package/src/lib/mutation/dotnet-introspection.js +20 -106
- package/src/lib/mutation/runner.js +23 -75
- package/src/lib/mutation/subset.js +1 -4
- package/src/lib/mutation/trx.js +0 -1
- package/src/lib/plan/artifact-vocabulary.js +5 -25
- package/src/lib/plan/checks.js +34 -33
- package/src/lib/plan/plan-parser.js +7 -8
- package/src/lib/projects/registry.js +7 -44
- package/src/lib/review-reports.js +11 -26
- package/src/lib/risk-detector.js +41 -173
- package/src/lib/specs/adr-index.js +6 -34
- package/src/lib/specs/frontmatter.js +2 -15
- package/src/lib/specs/gfm-table.js +1 -7
- package/src/lib/specs/revocations.js +1 -14
- package/src/lib/specs/screen-checklist.js +47 -124
- package/src/lib/stack/dotnet-build-ignores.js +2 -6
- package/src/lib/stack/stack-profile.js +1 -28
- package/src/lib/stack/stack-scan.js +1 -8
- package/src/lib/stack-filter.js +4 -27
- package/src/lib/standards/registry.js +1 -7
- package/src/lib/standards/sections.js +2 -13
- package/src/lib/standards/verified-against.js +19 -191
- package/src/lib/tasks/eval-node.js +21 -169
- package/src/lib/tasks/linux-runner.js +18 -142
- package/src/lib/tasks/test-nodes.js +43 -240
- package/src/lib/tasks/test-runner.js +43 -341
- package/src/lib/telemetry/metrics.js +7 -36
- package/src/lib/telemetry/telemetry-reader.js +13 -125
- package/src/lib/telemetry/verify-event.js +2 -30
- package/src/lib/tests-audit/audit.js +96 -0
- package/src/lib/validators/code-budget/code-budget-validator.js +4 -44
- package/src/lib/validators/code-budget/index.js +0 -7
- package/src/lib/validators/contracts/contract-compliance-validator.js +2 -24
- package/src/lib/validators/contracts/index.js +0 -6
- package/src/lib/validators/nextjs/index.js +0 -5
- package/src/lib/validators/nextjs/next-component-validator.js +4 -81
- package/src/lib/validators/packages/index.js +0 -6
- package/src/lib/validators/packages/package-validator.js +6 -56
- package/src/lib/validators/reuse/index.js +0 -8
- package/src/lib/validators/reuse/reuse-validator.js +11 -101
- package/src/lib/validators/reuse/symbol-scanner.js +6 -62
- package/src/lib/validators/traceability-check.js +5 -63
- package/src/lib/validators/validation-runner.js +21 -158
- package/src/lib/validators/verify-runner.js +85 -376
- package/src/lib/work-type-detector.js +5 -67
- package/src/lib/worktree/docker-audit.js +17 -136
- package/src/lib/worktree/env-allocator.js +10 -98
- package/src/lib/worktree/feature-truth.js +12 -115
- package/src/lib/worktree/finish-backup.js +32 -242
- package/src/lib/worktree/fleet-scanner.js +13 -95
- package/src/lib/worktree/ignored-triage.js +15 -91
- package/src/lib/worktree/vscode-settings.js +2 -3
- package/src/lib/worktree/worktree-audit.js +21 -213
- package/src/lib/worktree/worktree-include.js +3 -5
- package/src/scripts/global-install.js +3 -9
- package/src/scripts/install-plugin.js +1 -13
- package/src/scripts/setup-infra.js +17 -147
- package/src/utils/agents-installer.js +29 -276
- package/src/utils/banner.js +4 -25
- package/src/utils/claude-md-injector.js +1 -37
- package/src/utils/claude-settings-manager.js +1 -52
- package/src/utils/file-copier.js +11 -95
- package/src/utils/hooks-installer.js +21 -298
- package/src/utils/skills-installer.js +4 -101
- package/src/utils/version-checker.js +4 -83
- package/framework/commands/morph-proposal.md +0 -9
- package/framework/hooks/git/commit-msg/conventional-commits.sh +0 -33
- package/framework/hooks/git/pre-commit/agents.sh +0 -25
- package/framework/hooks/git/pre-commit/orchestrator.sh +0 -64
- package/framework/hooks/git/pre-push/run-tests.sh +0 -44
- package/src/commands/utils/index.js +0 -3
- package/src/lib/detectors/design-system-detector.js +0 -188
- package/src/lib/generators/design-system-generator.js +0 -508
- package/src/lib/specs/old-format.js +0 -68
- package/src/utils/agent-id-mapper.js +0 -11
|
@@ -115,8 +115,12 @@ respectively and the test suite stays green.
|
|
|
115
115
|
|
|
116
116
|
## Test budget
|
|
117
117
|
|
|
118
|
-
The plan (`morph-plan`)
|
|
119
|
-
|
|
118
|
+
Tests are paid for by **risk, not by new code**. The plan (`morph-plan`) marks each scenario
|
|
119
|
+
with `prova:` and, for a test, a `risco:` (business rule, money, external contract, concurrency,
|
|
120
|
+
data integrity, regression). The budget is **one test per risky scenario, plus the regression test of
|
|
121
|
+
the bug being fixed**; going above it takes a one-line justification on the scenario. A scenario
|
|
122
|
+
without risk — CRUD, DTO, mapping, wiring, constants, layout — is `prova: leitura`: the Gate 3
|
|
123
|
+
verifier proves it by reading the code, and it gets no test.
|
|
120
124
|
|
|
121
125
|
- **Table before copy.** Scenarios of the same behavior — including its edge cases (null, empty,
|
|
122
126
|
boundary) — are rows of one `[Theory]` (`[InlineData]`/`[MemberData]`), never N `[Fact]` methods
|
|
@@ -125,11 +129,13 @@ regression test per bug fixed**. Going above it takes a one-line justification i
|
|
|
125
129
|
UI copy, tenant config or prose proves the file's version, not behavior. Enforce it with an
|
|
126
130
|
analyzer, a lint rule or a validation at publish time, outside the suite count. The versioned
|
|
127
131
|
prompt case (at most one structural test) is `ai-agents-testing-ai`.
|
|
132
|
+
- **One layer per behavior.** A rule tested in the handler is not tested again through the
|
|
133
|
+
endpoint or the SQL integration: pick the cheapest layer that bites (wiring is the verifier's).
|
|
128
134
|
- **Shared fixture by default.** Reuse the project's `WebApplicationFactory` fixture, fakes and
|
|
129
135
|
builders. Before writing a new helper, look for the existing one (`morph-spec graph explain
|
|
130
136
|
<Name>`; fallback without a graph: `grep -rn`).
|
|
131
|
-
- **Order is free, proof is not the implementer's:** a bug gets a regression test, and
|
|
132
|
-
gets its
|
|
137
|
+
- **Order is free, proof is not the implementer's:** a bug gets a regression test, and a risky
|
|
138
|
+
scenario gets its test, in whatever order the code comes out. The implementer does not run red→green
|
|
133
139
|
cycles nor mutate production code to watch a test fail; it runs its own filter once before the
|
|
134
140
|
report, and the Gate 3 mutation proof is what shows the test bites.
|
|
135
141
|
- **A surviving mutant** of the Gate 3 mutation proof is killed by tightening an existing assertion
|
|
@@ -225,13 +225,13 @@ public sealed class SearchBooksHandler(
|
|
|
225
225
|
When a slice needs concurrency control (`SELECT ... FOR UPDATE`, lease/outbox semantics,
|
|
226
226
|
`DbUpdateConcurrencyException` retry loops) or SQL that LINQ cannot express, write a
|
|
227
227
|
**hand-written, purpose-built store** with intention-named methods (e.g. `OutboxStore.ClaimBatchAsync`,
|
|
228
|
-
`LeaseStore.TryAcquireAsync`) — and document the justification in `
|
|
228
|
+
`LeaseStore.TryAcquireAsync`) — and document the justification in `plan.md` (`## Decisões`).
|
|
229
229
|
Never a generic-per-entity abstraction.
|
|
230
230
|
|
|
231
231
|
**Rule of thumb:** everything → `ApplicationDbContext` in the slice. Concurrency or
|
|
232
|
-
specific SQL → dedicated hand-written store + `
|
|
232
|
+
specific SQL → dedicated hand-written store + `plan.md` (`## Decisões`) entry. Never reach for raw SQL
|
|
233
233
|
(`FromSqlRaw`) unless a query genuinely cannot be expressed in LINQ — and document that
|
|
234
|
-
in `
|
|
234
|
+
in `plan.md` (`## Decisões`).
|
|
235
235
|
|
|
236
236
|
---
|
|
237
237
|
|
|
@@ -307,8 +307,8 @@ builder.Services.AddExceptionHandler<CustomExceptionHandler>()
|
|
|
307
307
|
| Share handlers across features | Each feature has its own handler, even if similar |
|
|
308
308
|
| Use constructor + field for DI | Use primary constructors: `class Handler(IDep dep)` |
|
|
309
309
|
| Group by technical concern (Controllers/, Services/) | Group by feature: `Features/BookFeature/CreateBook/` |
|
|
310
|
-
| Route data access through any indirection layer | Inject `DbContext` and write LINQ in the slice; dedicated store only with `
|
|
311
|
-
| Use raw SQL (`FromSqlRaw` / `ExecuteQuery(string)`) by default | Use LINQ; raw SQL only with a `
|
|
310
|
+
| Route data access through any indirection layer | Inject `DbContext` and write LINQ in the slice; dedicated store only with `plan.md` (`## Decisões`) justification |
|
|
311
|
+
| Use raw SQL (`FromSqlRaw` / `ExecuteQuery(string)`) by default | Use LINQ; raw SQL only with a `plan.md` (`## Decisões`) justification |
|
|
312
312
|
|
|
313
313
|
---
|
|
314
314
|
|
|
@@ -15,7 +15,7 @@ Background job processing for .NET 10 VSA applications with Hangfire on Neon Pos
|
|
|
15
15
|
|
|
16
16
|
## When to use Hangfire
|
|
17
17
|
|
|
18
|
-
Hangfire is in the stack **only with a real reason**: work that must survive a process restart, run on a schedule, or be retried later (daily reindex, delayed reminders, retrying a flaky third party). Work that fits in the request (send one email after a signup, index one uploaded document) runs in the slice handler. Record the reason in `
|
|
18
|
+
Hangfire is in the stack **only with a real reason**: work that must survive a process restart, run on a schedule, or be retried later (daily reindex, delayed reminders, retrying a flaky third party). Work that fits in the request (send one email after a signup, index one uploaded document) runs in the slice handler. Record the reason in `plan.md` (`## Decisões`).
|
|
19
19
|
|
|
20
20
|
Hangfire provides fire-and-forget, delayed and recurring (cron) jobs, continuations, a dashboard and automatic retries.
|
|
21
21
|
|
|
@@ -64,7 +64,7 @@ place to audit.
|
|
|
64
64
|
**The exception process:** a feature that genuinely needs the frontend to reach Neon directly for
|
|
65
65
|
business data (for example, via the Neon MCP `provision_neon_data_api` tool) is an exception, not a
|
|
66
66
|
default — treat it exactly like vertical-slice.md's "Stores dedicados (exceção justificada)" clause:
|
|
67
|
-
document the justification in the feature's `
|
|
67
|
+
document the justification in the feature's `plan.md` (`## Decisões`) before writing the code. A tool being
|
|
68
68
|
available is not, by itself, justification.
|
|
69
69
|
|
|
70
70
|
See [Row-Level Security Integration](#row-level-security-integration) for how to constrain a
|
|
@@ -727,7 +727,7 @@ Each Neon branch gets its own isolated auth data. This means:
|
|
|
727
727
|
fetch and cache the JWKS yourself and verify EdDSA signatures manually (BouncyCastle)
|
|
728
728
|
11. **Route business data through the .NET backend by default** — the frontend's only inherent
|
|
729
729
|
reason to talk to Neon directly is its own auth/session SDK; a direct Data API call needs a
|
|
730
|
-
`
|
|
730
|
+
`plan.md` (`## Decisões`) ADR (see [Data Access Boundary](#data-access-boundary--backend-first-by-default))
|
|
731
731
|
|
|
732
732
|
---
|
|
733
733
|
|
|
@@ -179,4 +179,4 @@ The minute comes from `TimeProvider` (testable with `FakeTimeProvider`), never `
|
|
|
179
179
|
- Never omit TTL -- unbounded keys fill memory
|
|
180
180
|
- Never wrap the cache in a `XService`/repository class -- cache-aside is three lines in the handler
|
|
181
181
|
- Never return `null` for "not found" -- the handler returns `Result` with `NotFound`
|
|
182
|
-
- Never swallow `RedisConnectionException` in an empty `catch`: a cache outage must be visible (health check + logs). Falling back to the database while Redis is down is a product decision: record it in `
|
|
182
|
+
- Never swallow `RedisConnectionException` in an empty `catch`: a cache outage must be visible (health check + logs). Falling back to the database while Redis is down is a product decision: record it in `plan.md` (`## Decisões`) and implement it once, at the cache boundary, not per call site
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
> **Scope:** data-nosql
|
|
4
4
|
> **Layer:** 3 (opcional — nenhuma rule nem persona o carrega; só sob demanda via `morph-spec standards --find`, depois de a decisão existir)
|
|
5
5
|
> **Keywords:** cosmos-db, nosql, document-database, event-store, partition-key, azure-cosmos
|
|
6
|
-
> **Load When:** `
|
|
6
|
+
> **Load When:** `plan.md` (`## Decisões`) already records the choice of Cosmos DB over Neon for a specific store
|
|
7
7
|
|
|
8
8
|
**Verified against:** .NET 10 (SDK 10.0.400-preview) + Microsoft.Azure.Cosmos 3.63.2 + Newtonsoft.Json 13.0.4 — every `csharp` block below compiles with `TreatWarningsAsErrors` against the `Result`/`Error` types of `backend-dotnet-result-pattern`. Last-verified: 2026-10-03.
|
|
9
9
|
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
|
|
12
12
|
## Overview
|
|
13
13
|
|
|
14
|
-
**Cosmos DB is not part of the stack.** The default store is Neon (Postgres) through EF Core, with the `ApplicationDbContext` used directly in the slice. Reach for Cosmos only when a requirement Postgres cannot meet is written down in `
|
|
14
|
+
**Cosmos DB is not part of the stack.** The default store is Neon (Postgres) through EF Core, with the `ApplicationDbContext` used directly in the slice. Reach for Cosmos only when a requirement Postgres cannot meet is written down in `plan.md` (`## Decisões`) (the "why not Neon" included). Everything below applies the stack conventions to that exception: the client is injected straight into the slice handler, ids are `Guid.CreateVersion7()`, expected failures are `Result` values, and classes are `sealed`.
|
|
15
15
|
|
|
16
16
|
## When It Can Be Justified
|
|
17
17
|
|
|
@@ -155,7 +155,7 @@ using var feed = container.GetItemQueryIterator<OrderEvent>(
|
|
|
155
155
|
|
|
156
156
|
| Do NOT | Do Instead |
|
|
157
157
|
|--------|------------|
|
|
158
|
-
| Pick Cosmos without a `
|
|
158
|
+
| Pick Cosmos without a `plan.md` (`## Decisões`) entry | Neon (Postgres) via EF Core is the default store |
|
|
159
159
|
| Wrap `Container` in a generic `CosmosRepository<T>` | Inject the keyed `Container` into the slice handler |
|
|
160
160
|
| `try/catch (CosmosException)` for 404/409 | Stream APIs + `StatusCode` → `Result` |
|
|
161
161
|
| `Guid.NewGuid()` for document ids | `Guid.CreateVersion7()` |
|
|
@@ -16,7 +16,7 @@ An admin app has **one** form pattern. It is the base pattern of `nextjs/forms.m
|
|
|
16
16
|
- **The Zod schema is shared.** The same schema types the form, validates it, and parses the API response (`admin-app/data-table.md` parses the list query with Zod too). One source of truth per entity.
|
|
17
17
|
- **react-hook-form keeps inputs uncontrolled** — a 30-field form does not re-render on every keystroke, which is what `useState` per field does.
|
|
18
18
|
- **shadcn's current form docs are written around it** (`Field`, `FieldLabel`, `FieldError` + `Controller`).
|
|
19
|
-
- **Coexisting styles are the defect this rule exists for.** Measured in a real admin app: three form styles side by side (react-hook-form in some screens, hand-rolled `useState` in others) — three validation behaviours for one product, and a redesign that changed none of them. A screen that needs something the pattern cannot do registers the exception in `
|
|
19
|
+
- **Coexisting styles are the defect this rule exists for.** Measured in a real admin app: three form styles side by side (react-hook-form in some screens, hand-rolled `useState` in others) — three validation behaviours for one product, and a redesign that changed none of them. A screen that needs something the pattern cannot do registers the exception in `plan.md` (`## Decisões`); it does not start a fourth style.
|
|
20
20
|
|
|
21
21
|
## Core Rules
|
|
22
22
|
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# Art Direction — Decide What the Screen Is Before Drawing It
|
|
2
|
+
|
|
3
|
+
> **Scope:** frontend-design-system
|
|
4
|
+
> **Layer:** 3
|
|
5
|
+
> **Keywords:** art direction, concept, visual metaphor, design direction, template look, generic ui, memorable, mobile-first, brand moment
|
|
6
|
+
> **Load When:** ui-designer active, before any component of a new or changed screen
|
|
7
|
+
|
|
8
|
+
**Verified against:** the `login-ciclo-do-produto` design round (2026-10-06): a correct, plain design was rejected; a second round with a written direction, an HTML prototype and a screenshot self-review was approved on first sight. Last-verified: 2026-10-06.
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
A design that follows the tokens, has every state and passes WCAG can still be forgettable. What made the difference in practice was not talent but order: a direction written before any component, a real prototype, and a critical look at the rendered result. This standard is the first of the three.
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## Section 1: Write the Direction First
|
|
17
|
+
|
|
18
|
+
Before the first component, answer four questions in `1-design/direcao.md`:
|
|
19
|
+
|
|
20
|
+
1. **Concept in one sentence.** What the screen says, not what it contains. *"The login tells the product cycle in scenes while you sign in."*
|
|
21
|
+
2. **Visual metaphor.** The image that organizes the layout. *Stories; a control room; a ticket being stamped; a garden that grows.*
|
|
22
|
+
3. **What makes it memorable.** The one thing someone would describe to a colleague. If nothing, the direction is not done.
|
|
23
|
+
4. **What it must NOT look like.** Name the generic version you are avoiding (Section 4).
|
|
24
|
+
|
|
25
|
+
Only then pick components. A component chosen before the direction is chosen by habit.
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## Section 2: Explore Two or Three Directions
|
|
30
|
+
|
|
31
|
+
For a new screen or a brand moment (login, onboarding, first screen, empty state, success, showcase), write two or three directions that are **distinct**, not three color variations of one layout. For each: concept, metaphor, the mobile idea, the motion idea, and the risk. Recommend one and say why, in terms of the product and the user, not taste.
|
|
32
|
+
|
|
33
|
+
A changed screen inside an established system may need only one direction, written the same way, with what changes and why.
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## Section 3: Good and Bad Directions
|
|
38
|
+
|
|
39
|
+
| Bad direction | Why it fails | Good direction |
|
|
40
|
+
|---|---|---|
|
|
41
|
+
| "Clean, modern login with the logo and a form." | Describes every login. Nothing to remember. | "The panel tells the product cycle in three scenes, stories-style; the form is a sheet the thumb reaches." |
|
|
42
|
+
| "Dashboard with cards for each metric." | Content, not concept. Layout by habit. | "A control room: one hero number the operator acts on, the rest as quiet instruments around it." |
|
|
43
|
+
| "Empty state with an icon and a button." | The default of every kit. | "The empty list is the first page of a notebook: an illustrated first entry shows what a good one looks like." |
|
|
44
|
+
| "Mobile: same layout, stacked." | Mobile derived, not designed. | "Mobile is an app: bottom sheet, thumb-zone actions, the scene compressed to a strip above." |
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## Section 4: Template Tells to Avoid
|
|
49
|
+
|
|
50
|
+
Each one is fine in isolation; together they read as "generated". If the screen has three or more, raise it.
|
|
51
|
+
|
|
52
|
+
- Raw shadcn look: default radius, default gray border, no brand in any surface.
|
|
53
|
+
- A white card centered on a gray background, logo on top, form inside.
|
|
54
|
+
- A generic icon list (three features, three Lucide icons, three lines of copy).
|
|
55
|
+
- Stack-default or AI-tell fonts with no tracking or scale decision (see `premium-finish` Section 1).
|
|
56
|
+
- Flat solid backgrounds with no light, layer or texture.
|
|
57
|
+
- Mobile as the desktop stacked: centered card, tiny targets, actions out of thumb reach.
|
|
58
|
+
- Motion as decoration: everything fades in, nothing tells a sequence.
|
|
59
|
+
- Made-up numbers on illustrative cards (`R$ 12.430`, `98% satisfaction`).
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## Section 5: Mobile Is Designed, Not Derived
|
|
64
|
+
|
|
65
|
+
Draw the phone at 390 px as its own composition: bottom sheet for the primary task, actions in the thumb zone, targets of at least 44 px, the brand scene compressed rather than removed. Then check the desktop is not the phone blown up either.
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## Section 6: Motion With Intent
|
|
70
|
+
|
|
71
|
+
Motion should tell something (a sequence, a cause, a state change). Pick the lightest tool that delivers it: CSS transition and `@keyframes`, then CSS scroll-driven animation, then Framer Motion, then GSAP with ScrollTrigger and timelines. Budget: 60 fps, only `transform` and `opacity`, no layout thrash, lazy below the fold. `prefers-reduced-motion` gets a composed static version, designed as carefully as the animated one.
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
## Section 7: Prototype and Self-Review
|
|
76
|
+
|
|
77
|
+
The direction is proved in a rendered prototype, not in prose: `1-design/prototipos/{screen}.html`, self-contained, using the project's REAL tokens read from its token CSS (the design-system doc can be stale), desktop and phone side by side in a device frame, with animated / static / pause controls. Serve it over local HTTP, screenshot at 1280 and 390, judge against this standard and the system bar (hierarchy, contrast, rhythm, mobile, "does it look like a template?"), fix, repeat. The final screenshots sit next to the HTML.
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## Checklist
|
|
82
|
+
|
|
83
|
+
- [ ] `direcao.md` answers concept, metaphor, memorable, must-not-look-like
|
|
84
|
+
- [ ] Two or three distinct directions for a new screen or brand moment, one recommended with a reason
|
|
85
|
+
- [ ] Fewer than three template tells on the final screen
|
|
86
|
+
- [ ] Mobile composed at 390 px on its own terms
|
|
87
|
+
- [ ] Motion tells something, with a composed reduced-motion version
|
|
88
|
+
- [ ] Prototype rendered, screenshotted at 1280 and 390, and revised until it passes
|
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
> **Scope:** frontend-design-system
|
|
4
4
|
> **Layer:** 3
|
|
5
5
|
> **Keywords:** premium, polish, typography, film grain, atmosphere, load-in, scrim, legibility over video, accent
|
|
6
|
-
> **Load When:** ui-designer active, or the brief asks for a premium or high-end feel
|
|
6
|
+
> **Load When:** ui-designer active (every surface), or the brief asks for a premium or high-end feel
|
|
7
7
|
|
|
8
|
-
**Verified against:** Production landing pages, CSS-only techniques, no runtime dependency. Last-verified: 2026-
|
|
8
|
+
**Verified against:** Production landing pages, CSS-only techniques, no runtime dependency; app patterns from an approved login prototype (2026-10-06). Last-verified: 2026-10-06.
|
|
9
9
|
|
|
10
10
|
---
|
|
11
11
|
|
|
@@ -148,6 +148,78 @@ Finish with fresh eyes: open the page as if you were the client. Whatever looks
|
|
|
148
148
|
|
|
149
149
|
---
|
|
150
150
|
|
|
151
|
+
## Section 8: Patterns For App Screens
|
|
152
|
+
|
|
153
|
+
The finish is not only for marketing pages. Login, onboarding, the first screen after sign-up, an empty state and a success moment are brand moments inside the product, and a data screen also has to look designed: elegant density, illustrated empty states, skeletons with shimmer, transitions between states. These patterns come from an app login that was approved on first sight after a plain one was rejected.
|
|
154
|
+
|
|
155
|
+
### Bottom sheet on mobile
|
|
156
|
+
|
|
157
|
+
On a phone the form lives in a sheet anchored to the bottom, inside the thumb zone, with the brand scene above it. Never the desktop card centered and shrunk.
|
|
158
|
+
|
|
159
|
+
```css
|
|
160
|
+
.sheet {
|
|
161
|
+
position: fixed; inset: auto 0 0 0;
|
|
162
|
+
padding: 12px 20px calc(20px + env(safe-area-inset-bottom));
|
|
163
|
+
border-radius: 24px 24px 0 0;
|
|
164
|
+
background: var(--surface);
|
|
165
|
+
box-shadow: 0 -12px 40px color-mix(in oklab, var(--brand) 18%, transparent);
|
|
166
|
+
}
|
|
167
|
+
.sheet::before {
|
|
168
|
+
content: ''; display: block; width: 36px; height: 4px; margin: 0 auto 16px;
|
|
169
|
+
border-radius: 2px; background: var(--border);
|
|
170
|
+
}
|
|
171
|
+
.sheet button, .sheet input { min-height: 44px; }
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### Stories-style progress
|
|
175
|
+
|
|
176
|
+
A sequence of scenes reads like stories: thin segmented bars at the top, each filling while its scene is on. One keyframe, the delay per segment, and the scene swap keyed to the same delays.
|
|
177
|
+
|
|
178
|
+
```css
|
|
179
|
+
.stories { display: flex; gap: 4px; }
|
|
180
|
+
.stories span { flex: 1; height: 3px; border-radius: 2px; background: color-mix(in oklab, var(--fg) 20%, transparent); overflow: hidden; }
|
|
181
|
+
.stories span::after {
|
|
182
|
+
content: ''; display: block; height: 100%; background: var(--fg);
|
|
183
|
+
transform: scaleX(0); transform-origin: left;
|
|
184
|
+
animation: fill var(--beat) linear forwards;
|
|
185
|
+
animation-delay: calc(var(--i) * var(--beat));
|
|
186
|
+
}
|
|
187
|
+
@keyframes fill { to { transform: scaleX(1); } }
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
### Synchronized highlight by `animation-delay`
|
|
191
|
+
|
|
192
|
+
When a scene card lights up while its story bar runs, both share `--i` and `--beat`. No JavaScript timer: the browser keeps them in step, and pausing is one `animation-play-state: paused` on the parent.
|
|
193
|
+
|
|
194
|
+
### Scene cards without real data
|
|
195
|
+
|
|
196
|
+
A card that illustrates the product cycle shows shapes, icons and labels, never a metric. A made-up number on a real screen reads as a real number ("R$ 12.430 recovered") and becomes a claim nobody can back. Use bars without values, avatars without names, a check without a count.
|
|
197
|
+
|
|
198
|
+
### Dot grid faded with `mask-image`
|
|
199
|
+
|
|
200
|
+
Texture for a hero or a sheet backdrop, cheaper than an image and on brand:
|
|
201
|
+
|
|
202
|
+
```css
|
|
203
|
+
.dots {
|
|
204
|
+
background-image: radial-gradient(color-mix(in oklab, var(--fg) 22%, transparent) 1px, transparent 1px);
|
|
205
|
+
background-size: 16px 16px;
|
|
206
|
+
mask-image: radial-gradient(ellipse at 50% 30%, #000 30%, transparent 75%);
|
|
207
|
+
}
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
### Static version for reduced motion
|
|
211
|
+
|
|
212
|
+
`prefers-reduced-motion: reduce` gets a composed still frame (the best scene, every bar full, the highlight on), not the first frame of a stopped animation.
|
|
213
|
+
|
|
214
|
+
```css
|
|
215
|
+
@media (prefers-reduced-motion: reduce) {
|
|
216
|
+
.stories span::after { animation: none; transform: scaleX(1); }
|
|
217
|
+
.scene { animation: none; opacity: 1; }
|
|
218
|
+
}
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
---
|
|
222
|
+
|
|
151
223
|
## Checklist
|
|
152
224
|
|
|
153
225
|
- [ ] Display font chosen from the brand, not from stack defaults or the AI-tell list
|
|
@@ -157,4 +229,7 @@ Finish with fresh eyes: open the page as if you were the client. Whatever looks
|
|
|
157
229
|
- [ ] Directional scrim plus text-shadow on every text-over-media block
|
|
158
230
|
- [ ] Accent used in three places or fewer
|
|
159
231
|
- [ ] Details swept: scrollbar, selection, hairlines, hover micro-transitions
|
|
232
|
+
- [ ] App screen: mobile designed on its own (bottom sheet, thumb zone, 44px targets), never the desktop stacked
|
|
233
|
+
- [ ] No invented number or data on a real screen
|
|
234
|
+
- [ ] Reduced motion gets a composed static frame
|
|
160
235
|
- [ ] Fresh-eyes pass done, and it does not read as a template
|
|
@@ -17,7 +17,7 @@ Vitest + React Testing Library for component tests. MSW for API mocking. Co-loca
|
|
|
17
17
|
- ALWAYS use `@testing-library/user-event` for interactions — not `fireEvent`
|
|
18
18
|
- ALWAYS mock the API layer with MSW — never mock `fetch` directly
|
|
19
19
|
- NEVER test implementation details — test what the user sees
|
|
20
|
-
-
|
|
20
|
+
- ONLY test a component when a plan scenario with `risco:` lives in it — a component is not a unit of the budget
|
|
21
21
|
|
|
22
22
|
## Test File Co-location
|
|
23
23
|
|
|
@@ -103,9 +103,13 @@ export function QueryClientWrapper({ children }: { children: React.ReactNode })
|
|
|
103
103
|
|
|
104
104
|
## Test Budget
|
|
105
105
|
|
|
106
|
-
The plan (`morph-plan`)
|
|
107
|
-
|
|
108
|
-
|
|
106
|
+
Tests are paid for by **risk, not by new code**. The plan (`morph-plan`) marks each scenario
|
|
107
|
+
with `prova:` and, for a test, a `risco:` (business rule, money, external contract, concurrency,
|
|
108
|
+
data integrity, regression). The budget is **one test per risky scenario, plus the regression test of
|
|
109
|
+
the bug being fixed**; going above it takes a one-line justification on the scenario. A scenario
|
|
110
|
+
without risk — CRUD, DTO, mapping, wiring, constants, layout — is `prova: leitura`: the Gate 3
|
|
111
|
+
verifier proves it by reading the code, and it gets no test. Rendering, layout and copy are
|
|
112
|
+
`leitura` or `e2e`, never a unit test per component.
|
|
109
113
|
|
|
110
114
|
- **Table before copy.** Scenarios of the same behavior — edge cases included — are rows of one
|
|
111
115
|
`it.each`/`test.each`, never N `it()` blocks that differ by one value.
|
|
@@ -116,8 +120,8 @@ happy path + one error path per component above is that budget applied to a comp
|
|
|
116
120
|
- **Shared helpers by default.** Reuse the existing wrappers (`test/helpers.tsx`), MSW handlers and
|
|
117
121
|
factories. Before writing a new one, look for it (`morph-spec graph explain <Name>`; fallback
|
|
118
122
|
without a graph: `grep -rn`).
|
|
119
|
-
- **Order is free, proof is not the implementer's:** a bug gets a regression test, and
|
|
120
|
-
(
|
|
123
|
+
- **Order is free, proof is not the implementer's:** a bug gets a regression test, and a risky
|
|
124
|
+
scenario (a formatter of money, a reducer with rules, a schema) gets its test, in whatever order the code comes out. The
|
|
121
125
|
implementer does not run red→green cycles nor mutate production code to watch a test fail; it
|
|
122
126
|
runs its own filter once before the report, and the Gate 3 mutation proof shows the test bites.
|
|
123
127
|
|
|
@@ -19,7 +19,7 @@ Stack: .NET VSA + Neon Serverless Postgres. For the end-to-end RAG pipeline (ing
|
|
|
19
19
|
- ALWAYS call `UseVector()` on the `UseNpgsql(...)` options builder (EF Core); `NpgsqlDataSourceBuilder.UseVector()` is only for raw Npgsql
|
|
20
20
|
- ALWAYS register the `vector` extension in `OnModelCreating` (`HasPostgresExtension("vector")`)
|
|
21
21
|
- Map vector properties as `Pgvector.Vector` -- EF Core does not map `float[]` or `ReadOnlyMemory<float>` to the `vector` column
|
|
22
|
-
- Query through the `ApplicationDbContext` in the slice handler with the `CosineDistance`/`L2Distance`/`MaxInnerProduct` LINQ translations -- no repository class, no raw SQL from C# without a `
|
|
22
|
+
- Query through the `ApplicationDbContext` in the slice handler with the `CosineDistance`/`L2Distance`/`MaxInnerProduct` LINQ translations -- no repository class, no raw SQL from C# without a `plan.md` (`## Decisões`) justification
|
|
23
23
|
- IDs come from the app (`Guid.CreateVersion7()`), never from a `gen_random_uuid()` column default
|
|
24
24
|
|
|
25
25
|
## Enable pgvector
|
|
@@ -39,7 +39,7 @@ CREATE EXTENSION IF NOT EXISTS vector;
|
|
|
39
39
|
<PackageReference Include="Pgvector.EntityFrameworkCore" Version="0.3.0" />
|
|
40
40
|
```
|
|
41
41
|
|
|
42
|
-
`Pgvector.EntityFrameworkCore` 0.3.0 requires `Npgsql.EntityFrameworkCore.PostgreSQL` >= 9.0.1 and brings `Pgvector` (the `Vector` type and the raw-Npgsql plugin) transitively. The configuration below maps each column to the snake_case names of `backend/database/postgresql/database.md` (`embedding`, `created_at`) with `HasColumnName`; without it EF creates `"Embedding"`, `"CreatedAt"` and every SQL snippet below would have to quote PascalCase names. (A project that wants this for every entity can add `EFCore.NamingConventions` and `UseSnakeCaseNamingConvention()` once, instead of per column -- a project-wide decision for `
|
|
42
|
+
`Pgvector.EntityFrameworkCore` 0.3.0 requires `Npgsql.EntityFrameworkCore.PostgreSQL` >= 9.0.1 and brings `Pgvector` (the `Vector` type and the raw-Npgsql plugin) transitively. The configuration below maps each column to the snake_case names of `backend/database/postgresql/database.md` (`embedding`, `created_at`) with `HasColumnName`; without it EF creates `"Embedding"`, `"CreatedAt"` and every SQL snippet below would have to quote PascalCase names. (A project that wants this for every entity can add `EFCore.NamingConventions` and `UseSnakeCaseNamingConvention()` once, instead of per column -- a project-wide decision for `plan.md` (`## Decisões`).)
|
|
43
43
|
|
|
44
44
|
### Entity Definition
|
|
45
45
|
|
|
@@ -196,9 +196,9 @@ public sealed class SearchDocumentsHandler(
|
|
|
196
196
|
|
|
197
197
|
Storing an embedding is an ordinary EF write in the handler that owns the document: `document.Embedding = new Vector(await embeddings.GenerateVectorAsync(text, cancellationToken: ct));` followed by `SaveChangesAsync`. No `UPDATE ... SET embedding` from C#.
|
|
198
198
|
|
|
199
|
-
### SQL Functions (only with a `
|
|
199
|
+
### SQL Functions (only with a `plan.md` (`## Decisões`) justification)
|
|
200
200
|
|
|
201
|
-
When the query must live in the database (called from several apps, or a hybrid score the LINQ translation cannot express), a SQL function is acceptable -- record why in `
|
|
201
|
+
When the query must live in the database (called from several apps, or a hybrid score the LINQ translation cannot express), a SQL function is acceptable -- record why in `plan.md` (`## Decisões`), call it through EF Core with interpolated parameters, and keep the same distance rules:
|
|
202
202
|
|
|
203
203
|
```sql
|
|
204
204
|
CREATE OR REPLACE FUNCTION match_documents(
|
|
@@ -218,7 +218,7 @@ $$;
|
|
|
218
218
|
|
|
219
219
|
## Hybrid Search (Vector + Full-Text)
|
|
220
220
|
|
|
221
|
-
Blends the vector score with `ts_rank`. Same rule as above: a SQL function, justified in `
|
|
221
|
+
Blends the vector score with `ts_rank`. Same rule as above: a SQL function, justified in `plan.md` (`## Decisões`).
|
|
222
222
|
|
|
223
223
|
```sql
|
|
224
224
|
CREATE OR REPLACE FUNCTION hybrid_search(
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
<templateSet group="MORPH Templates">
|
|
2
|
+
<template name="morph-pascal" value="{{pascalCase $FEATURE_NAME$}}" description="Convert to PascalCase (UserAuth)" toReformat="false" toShortenFQNames="true">
|
|
3
|
+
<variable name="FEATURE_NAME" expression="" defaultValue=""FEATURE_NAME"" alwaysStopAt="true" />
|
|
4
|
+
<context>
|
|
5
|
+
<option name="OTHER" value="true" />
|
|
6
|
+
</context>
|
|
7
|
+
</template>
|
|
8
|
+
|
|
9
|
+
<template name="morph-camel" value="{{camelCase $FEATURE_NAME$}}" description="Convert to camelCase (userAuth)" toReformat="false" toShortenFQNames="true">
|
|
10
|
+
<variable name="FEATURE_NAME" expression="" defaultValue=""FEATURE_NAME"" alwaysStopAt="true" />
|
|
11
|
+
<context>
|
|
12
|
+
<option name="OTHER" value="true" />
|
|
13
|
+
</context>
|
|
14
|
+
</template>
|
|
15
|
+
|
|
16
|
+
<template name="morph-snake" value="{{snakeCase $FEATURE_NAME$}}" description="Convert to snake_case (user_auth)" toReformat="false" toShortenFQNames="true">
|
|
17
|
+
<variable name="FEATURE_NAME" expression="" defaultValue=""FEATURE_NAME"" alwaysStopAt="true" />
|
|
18
|
+
<context>
|
|
19
|
+
<option name="OTHER" value="true" />
|
|
20
|
+
</context>
|
|
21
|
+
</template>
|
|
22
|
+
|
|
23
|
+
<template name="morph-title" value="{{titleCase $FEATURE_NAME$}}" description="Convert to Title Case (User Auth)" toReformat="false" toShortenFQNames="true">
|
|
24
|
+
<variable name="FEATURE_NAME" expression="" defaultValue=""FEATURE_NAME"" alwaysStopAt="true" />
|
|
25
|
+
<context>
|
|
26
|
+
<option name="OTHER" value="true" />
|
|
27
|
+
</context>
|
|
28
|
+
</template>
|
|
29
|
+
|
|
30
|
+
<template name="morph-kebab" value="{{kebabCase $FEATURE_NAME$}}" description="Convert to kebab-case (user-auth)" toReformat="false" toShortenFQNames="true">
|
|
31
|
+
<variable name="FEATURE_NAME" expression="" defaultValue=""FEATURE_NAME"" alwaysStopAt="true" />
|
|
32
|
+
<context>
|
|
33
|
+
<option name="OTHER" value="true" />
|
|
34
|
+
</context>
|
|
35
|
+
</template>
|
|
36
|
+
|
|
37
|
+
<template name="morph-if" value="{{#if $CONDITION$}} $END$ {{/if}}" description="Handlebars if block" toReformat="false" toShortenFQNames="true">
|
|
38
|
+
<variable name="CONDITION" expression="" defaultValue=""condition"" alwaysStopAt="true" />
|
|
39
|
+
<context>
|
|
40
|
+
<option name="OTHER" value="true" />
|
|
41
|
+
</context>
|
|
42
|
+
</template>
|
|
43
|
+
|
|
44
|
+
<template name="morph-if-else" value="{{#if $CONDITION$}} $TRUE_BRANCH$ {{else}} $FALSE_BRANCH$ {{/if}}" description="Handlebars if-else block" toReformat="false" toShortenFQNames="true">
|
|
45
|
+
<variable name="CONDITION" expression="" defaultValue=""condition"" alwaysStopAt="true" />
|
|
46
|
+
<variable name="TRUE_BRANCH" expression="" defaultValue="""" alwaysStopAt="true" />
|
|
47
|
+
<variable name="FALSE_BRANCH" expression="" defaultValue="""" alwaysStopAt="true" />
|
|
48
|
+
<context>
|
|
49
|
+
<option name="OTHER" value="true" />
|
|
50
|
+
</context>
|
|
51
|
+
</template>
|
|
52
|
+
|
|
53
|
+
<template name="morph-each" value="{{#each $ITEMS$}} {{this.$PROPERTY$}} {{/each}}" description="Handlebars each loop" toReformat="false" toShortenFQNames="true">
|
|
54
|
+
<variable name="ITEMS" expression="" defaultValue=""items"" alwaysStopAt="true" />
|
|
55
|
+
<variable name="PROPERTY" expression="" defaultValue=""name"" alwaysStopAt="true" />
|
|
56
|
+
<context>
|
|
57
|
+
<option name="OTHER" value="true" />
|
|
58
|
+
</context>
|
|
59
|
+
</template>
|
|
60
|
+
|
|
61
|
+
<template name="morph-service" value="namespace {{NAMESPACE}}.Application.Features.{{pascalCase FEATURE_NAME}}.Services; public class {{pascalCase FEATURE_NAME}}Service( I{{pascalCase FEATURE_NAME}}Repository repository, ILogger<{{pascalCase FEATURE_NAME}}Service> logger) : I{{pascalCase FEATURE_NAME}}Service { public async Task<{{pascalCase FEATURE_NAME}}Dto?> GetByIdAsync(int id, CancellationToken cancellationToken = default) { $END$ } }" description="MORPH .NET Service class template" toReformat="false" toShortenFQNames="true">
|
|
62
|
+
<context>
|
|
63
|
+
<option name="C_SHARP" value="true" />
|
|
64
|
+
</context>
|
|
65
|
+
</template>
|
|
66
|
+
|
|
67
|
+
<template name="morph-repository" value="public class {{pascalCase FEATURE_NAME}}Repository(AppDbContext context) : I{{pascalCase FEATURE_NAME}}Repository { private readonly DbSet<{{pascalCase FEATURE_NAME}}> _dbSet = context.Set<{{pascalCase FEATURE_NAME}}>(); public async Task<{{pascalCase FEATURE_NAME}}?> GetByIdAsync(int id, CancellationToken cancellationToken = default) { $END$ } }" description="MORPH .NET Repository class template" toReformat="false" toShortenFQNames="true">
|
|
68
|
+
<context>
|
|
69
|
+
<option name="C_SHARP" value="true" />
|
|
70
|
+
</context>
|
|
71
|
+
</template>
|
|
72
|
+
|
|
73
|
+
<template name="morph-component" value="@page "/{{kebabCase FEATURE_NAME}}" @inject I{{pascalCase FEATURE_NAME}}Service {{pascalCase FEATURE_NAME}}Service @inject ILogger<{{pascalCase FEATURE_NAME}}Page> Logger <PageTitle>{{pascalCase FEATURE_NAME}}</PageTitle> <div class="container-fluid"> $END$ </div> @code { }" description="MORPH Blazor component template" toReformat="false" toShortenFQNames="true">
|
|
74
|
+
<context>
|
|
75
|
+
<option name="HTML" value="true" />
|
|
76
|
+
</context>
|
|
77
|
+
</template>
|
|
78
|
+
|
|
79
|
+
<template name="morph-migration" value="public partial class Add{{pascalCase FEATURE_NAME}} : Migration { protected override void Up(MigrationBuilder migrationBuilder) { migrationBuilder.CreateTable( name: "{{pascalCase FEATURE_NAME}}s", columns: table => new { Id = table.Column<int>(type: "int", nullable: false) .Annotation("SqlServer:Identity", "1, 1"), $END$ }, constraints: table => { table.PrimaryKey("PK_{{pascalCase FEATURE_NAME}}s", x => x.Id); }); } protected override void Down(MigrationBuilder migrationBuilder) { migrationBuilder.DropTable(name: "{{pascalCase FEATURE_NAME}}s"); } }" description="MORPH EF Core migration template" toReformat="false" toShortenFQNames="true">
|
|
80
|
+
<context>
|
|
81
|
+
<option name="C_SHARP" value="true" />
|
|
82
|
+
</context>
|
|
83
|
+
</template>
|
|
84
|
+
|
|
85
|
+
<template name="morph-ts-contract" value="export interface {{pascalCase FEATURE_NAME}} { id: number; $FIELD1$ } export interface Create{{pascalCase FEATURE_NAME}}Request { $FIELD2$ } export interface Update{{pascalCase FEATURE_NAME}}Request { $END$ }" description="MORPH TypeScript contract template" toReformat="false" toShortenFQNames="true">
|
|
86
|
+
<variable name="FIELD1" expression="" defaultValue="""" alwaysStopAt="true" />
|
|
87
|
+
<variable name="FIELD2" expression="" defaultValue="""" alwaysStopAt="true" />
|
|
88
|
+
<context>
|
|
89
|
+
<option name="TypeScript" value="true" />
|
|
90
|
+
</context>
|
|
91
|
+
</template>
|
|
92
|
+
</templateSet>
|