From 8140bd2ad37e7404348ca12df64cea88665ef771 Mon Sep 17 00:00:00 2001 From: Einar Date: Fri, 14 Aug 2026 18:41:25 +0200 Subject: [PATCH 1/7] Bootstrap repository from Arc's dual-stack conventions Copies the shared .ai/ instruction corpus and its Copilot/Claude/Codex adapters, root build/lint/format config (Directory.Build.props, Directory.Packages.props, .editorconfig, .globalconfig, tsconfig, vitest, eslint, yarn workspace tooling), and CI workflows (dotnet/JS build, NuGet + npm trusted publishing, semver label gate) from Cratis/Arc, the closest real dual-stack framework repo (Stage turned out to be .NET-only). Co-Authored-By: Claude Sonnet 5 --- .agents/skills | 1 + .ai/README.md | 52 + .ai/agents/backend-developer.md | 119 ++ .ai/agents/code-reviewer.md | 157 ++ .ai/agents/coordinator.md | 150 ++ .ai/agents/frontend-developer.md | 243 +++ .ai/agents/orchestrator.md | 183 +++ .ai/agents/performance-reviewer.md | 101 ++ .ai/agents/planner.md | 128 ++ .ai/agents/security-reviewer.md | 110 ++ .ai/agents/slice-implementer.md | 53 + .ai/agents/spec-writer.md | 143 ++ .ai/hooks/agent-stop.md | 49 + .ai/hooks/pre-commit.md | 50 + .ai/hooks/scripts/validate-ai-setup.sh | 147 ++ .ai/prompts/add-business-rule.prompt.md | 22 + .ai/prompts/add-concept.prompt.md | 17 + .ai/prompts/add-ef-migration.prompt.md | 24 + .ai/prompts/add-projection.prompt.md | 20 + .ai/prompts/add-reactor.prompt.md | 22 + .ai/prompts/add-reducer.prompt.md | 20 + .ai/prompts/audit-hooks.prompt.md | 15 + .ai/prompts/check-doc-drift.prompt.md | 21 + .ai/prompts/code-review.md | 4 + .ai/prompts/new-feature.md | 4 + .ai/prompts/new-vertical-slice.prompt.md | 18 + .ai/prompts/review-pr.prompt.md | 35 + .ai/prompts/review-skill.prompt.md | 16 + .ai/prompts/scaffold-feature.prompt.md | 16 + .ai/prompts/ship-changes.prompt.md | 20 + .ai/prompts/verify-ai-setup.prompt.md | 19 + .ai/prompts/write-documentation.prompt.md | 21 + .ai/prompts/write-specs.prompt.md | 22 + .ai/rules/code-quality.csharp.md | 91 ++ .ai/rules/code-quality.md | 82 + .ai/rules/code-quality.typescript.md | 89 ++ .ai/rules/components.md | 165 ++ .ai/rules/concepts.md | 97 ++ .ai/rules/csharp.md | 203 +++ .ai/rules/dialogs.md | 226 +++ .../documentation-structure-and-formatting.md | 86 ++ .ai/rules/documentation.md | 91 ++ .ai/rules/editing-cratis-docs.md | 54 + .ai/rules/efcore.md | 235 +++ .ai/rules/efcore.specs.md | 44 + .ai/rules/framework.md | 52 + .ai/rules/frontend-quality.md | 59 + .ai/rules/frontend-testing.md | 126 ++ .ai/rules/general.md | 204 +++ .ai/rules/git-commits.md | 94 ++ .ai/rules/glossary.md | 58 + .ai/rules/managing-ai-rules.md | 171 +++ .ai/rules/orleans.md | 50 + .ai/rules/pull-requests.md | 49 + .ai/rules/react.md | 193 +++ .ai/rules/reactors.md | 238 +++ .ai/rules/rtk.md | 37 + .ai/rules/specs.csharp.md | 119 ++ .ai/rules/specs.md | 132 ++ .ai/rules/specs.scenarios.csharp.md | 172 +++ .ai/rules/specs.typescript.md | 139 ++ .ai/rules/storybook.md | 84 ++ .ai/rules/terminal-commands.md | 17 + .ai/rules/typescript.md | 149 ++ .ai/rules/vertical-slices.md | 298 ++++ .ai/rules/web-fetching.md | 11 + .ai/rules/writing-correct-examples.md | 35 + .ai/rules/writing-cratis-docs.md | 75 + .ai/skills/add-business-rule/SKILL.md | 111 ++ .ai/skills/add-business-rule/evals/evals.json | 27 + .ai/skills/add-concept/SKILL.md | 90 ++ .ai/skills/add-cratis-docs-page/SKILL.md | 43 + .ai/skills/add-ef-migration/SKILL.md | 168 +++ .ai/skills/add-projection/SKILL.md | 103 ++ .ai/skills/add-projection/evals/evals.json | 27 + .../references/CHRONICLE-API.md | 169 +++ .ai/skills/add-reactor/SKILL.md | 149 ++ .ai/skills/add-reactor/evals/evals.json | 28 + .ai/skills/add-reducer/SKILL.md | 61 + .ai/skills/add-traces/SKILL.md | 199 +++ .ai/skills/auth-and-identity/SKILL.md | 162 ++ .../references/authentication.md | 129 ++ .../references/authorization.md | 179 +++ .../references/backend-identity.md | 379 +++++ .../auth-and-identity/references/frontend.md | 184 +++ .../references/local-development.md | 111 ++ .ai/skills/call-command-from-code/SKILL.md | 81 + .ai/skills/cratis-command/SKILL.md | 310 ++++ .ai/skills/cratis-command/evals/evals.json | 35 + .../references/command-dialog.md | 138 ++ .../references/command-result.md | 81 + .../cratis-command/references/proxy-setup.md | 71 + .../cratis-command/references/validation.md | 104 ++ .ai/skills/cratis-csharp-standards/SKILL.md | 79 + .../cratis-csharp-standards/evals/evals.json | 37 + .../references/code-style.md | 187 +++ .../references/domain-philosophy.md | 98 ++ .../references/exceptions-logging-di.md | 115 ++ .ai/skills/cratis-react-page/SKILL.md | 243 +++ .ai/skills/cratis-react-page/evals/evals.json | 34 + .../cratis-react-page/references/data-page.md | 102 ++ .../references/data-table.md | 62 + .../cratis-react-page/references/dialogs.md | 198 +++ .../cratis-react-page/references/mvvm.md | 133 ++ .ai/skills/cratis-readmodel/SKILL.md | 258 ++++ .ai/skills/cratis-readmodel/evals/evals.json | 33 + .../cratis-readmodel/references/events.md | 99 ++ .../references/projections.md | 155 ++ .../cratis-readmodel/references/queries.md | 104 ++ .../cratis-readmodel/references/reducers.md | 136 ++ .ai/skills/cratis-specs-csharp/SKILL.md | 206 +++ .../cratis-specs-csharp/evals/evals.json | 37 + .../references/csharp-patterns.md | 166 +++ .../references/integration-specs.md | 101 ++ .ai/skills/cratis-specs-typescript/SKILL.md | 148 ++ .../cratis-specs-typescript/evals/evals.json | 35 + .../references/typescript-patterns.md | 168 +++ .ai/skills/cratis-vertical-slice/SKILL.md | 207 +++ .../cratis-vertical-slice/evals/evals.json | 34 + .../references/concepts.md | 101 ++ .../references/slice-anatomy.md | 255 ++++ .../references/slice-types.md | 105 ++ .ai/skills/create-event-model/SKILL.md | 80 + .ai/skills/cross-cutting-properties/SKILL.md | 70 + .ai/skills/diagnose-slice/SKILL.md | 42 + .ai/skills/discover-implementations/SKILL.md | 95 ++ .ai/skills/edit-cratis-docs/SKILL.md | 50 + .ai/skills/event-modeling/SKILL.md | 63 + .ai/skills/event-type-migrations/SKILL.md | 80 + .ai/skills/multi-tenancy/SKILL.md | 52 + .ai/skills/new-vertical-slice/SKILL.md | 103 ++ .../new-vertical-slice/evals/evals.json | 33 + .../new-vertical-slice/references/PATTERNS.md | 347 +++++ .ai/skills/observable-query-curl/SKILL.md | 156 ++ .ai/skills/qa-cratis-docs/SKILL.md | 51 + .ai/skills/query-paging/SKILL.md | 90 ++ .ai/skills/review-code/SKILL.md | 86 ++ .../review-code/references/CHECKLISTS.md | 109 ++ .ai/skills/review-performance/SKILL.md | 55 + .ai/skills/review-security/SKILL.md | 57 + .ai/skills/scaffold-feature/SKILL.md | 82 + .ai/skills/ship-changes/SKILL.md | 316 ++++ .ai/skills/ship-changes/evals/evals.json | 40 + .ai/skills/skill-creator/LICENSE.txt | 202 +++ .ai/skills/skill-creator/SKILL.md | 488 ++++++ .ai/skills/skill-creator/agents/analyzer.md | 274 ++++ .ai/skills/skill-creator/agents/comparator.md | 202 +++ .ai/skills/skill-creator/agents/grader.md | 223 +++ .../skill-creator/assets/eval_review.html | 146 ++ .../eval-viewer/generate_review.py | 471 ++++++ .../skill-creator/eval-viewer/viewer.html | 1325 +++++++++++++++++ .../skill-creator/references/schemas.md | 430 ++++++ .ai/skills/skill-creator/scripts/__init__.py | 2 + .../scripts/aggregate_benchmark.py | 401 +++++ .../skill-creator/scripts/generate_report.py | 326 ++++ .../scripts/improve_description.py | 248 +++ .../skill-creator/scripts/package_skill.py | 136 ++ .../skill-creator/scripts/quick_validate.py | 103 ++ .../scripts/restructure_evals.py | 42 + .ai/skills/skill-creator/scripts/run_eval.py | 310 ++++ .ai/skills/skill-creator/scripts/run_loop.py | 332 +++++ .ai/skills/skill-creator/scripts/utils.py | 47 + .ai/skills/stepper-command-dialog/SKILL.md | 233 +++ .ai/skills/toolbar/SKILL.md | 282 ++++ .ai/skills/write-documentation/SKILL.md | 122 ++ .ai/skills/write-specs-events/SKILL.md | 206 +++ .ai/skills/write-specs-frontend/SKILL.md | 93 ++ .ai/skills/write-specs-readmodels/SKILL.md | 216 +++ .ai/skills/write-specs/SKILL.md | 85 ++ .ai/skills/write-specs/evals/evals.json | 30 + .ai/skills/write-specs/references/EXAMPLES.md | 137 ++ .ai/workflows/claude.yml | 31 + .claude/CLAUDE.md | 1 + .claude/agents | 1 + .claude/commands/add-business-rule.md | 1 + .claude/commands/add-concept.md | 1 + .claude/commands/add-ef-migration.md | 1 + .claude/commands/add-projection.md | 1 + .claude/commands/add-reactor.md | 1 + .claude/commands/add-reducer.md | 1 + .claude/commands/audit-hooks.md | 1 + .claude/commands/check-doc-drift.md | 1 + .claude/commands/new-vertical-slice.md | 1 + .claude/commands/review-pr.md | 1 + .claude/commands/review-skill.md | 1 + .claude/commands/scaffold-feature.md | 1 + .claude/commands/ship-changes.md | 1 + .claude/commands/verify-ai-setup.md | 1 + .claude/commands/write-documentation.md | 1 + .claude/commands/write-specs.md | 1 + .claude/hooks | 1 + .claude/prompts | 1 + .claude/rules/code-quality.csharp.md | 1 + .claude/rules/code-quality.md | 1 + .claude/rules/code-quality.typescript.md | 1 + .claude/rules/components.md | 1 + .claude/rules/concepts.md | 1 + .claude/rules/csharp.md | 1 + .claude/rules/dialogs.md | 1 + .../documentation-structure-and-formatting.md | 1 + .claude/rules/documentation.md | 1 + .claude/rules/editing-cratis-docs.md | 1 + .claude/rules/efcore.md | 1 + .claude/rules/efcore.specs.md | 1 + .claude/rules/framework.md | 1 + .claude/rules/frontend-quality.md | 1 + .claude/rules/frontend-testing.md | 1 + .claude/rules/git-commits.md | 1 + .claude/rules/glossary.md | 1 + .claude/rules/managing-ai-rules.md | 1 + .claude/rules/orleans.md | 1 + .claude/rules/pull-requests.md | 1 + .claude/rules/react.md | 1 + .claude/rules/reactors.md | 1 + .claude/rules/rtk.md | 1 + .claude/rules/specs.csharp.md | 1 + .claude/rules/specs.md | 1 + .claude/rules/specs.scenarios.csharp.md | 1 + .claude/rules/specs.typescript.md | 1 + .claude/rules/storybook.md | 1 + .claude/rules/terminal-commands.md | 1 + .claude/rules/typescript.md | 1 + .claude/rules/vertical-slices.md | 1 + .claude/rules/web-fetching.md | 1 + .claude/rules/writing-correct-examples.md | 1 + .claude/rules/writing-cratis-docs.md | 1 + .claude/skills | 1 + .editorconfig | 201 +++ .gitattributes | 11 + .github/ISSUE_TEMPLATE/bug_report.md | 35 + .github/ISSUE_TEMPLATE/feature_request.md | 24 + .github/agents/backend-developer.agent.md | 1 + .github/agents/code-reviewer.agent.md | 1 + .github/agents/frontend-developer.agent.md | 1 + .github/agents/security-reviewer.agent.md | 1 + .github/agents/slice-implementer.agent.md | 1 + .github/agents/spec-writer.agent.md | 1 + .github/copilot-instructions.md | 1 + .github/instructions | 1 + .github/prompts | 1 + .github/pull_request_template.md | 35 + .github/skills | 1 + .../auto-approve-publish-deployments.yml | 64 + .github/workflows/cleanup-pr-artifacts.yml | 12 + .github/workflows/dotnet-build.yml | 116 ++ .github/workflows/javascript-build.yml | 58 + .../propagate-copilot-instructions.yml | 26 + .github/workflows/publish.yml | 127 ++ .../workflows/sync-copilot-instructions.yml | 16 + .github/workflows/update-packages.yml | 12 + .github/workflows/verify-semver-label.yml | 48 + .gitignore | 309 ++++ .globalconfig | 148 ++ .markdownlint.json | 17 + .prettierrc | 6 + .yarn/releases/yarn-4.5.3.cjs | 934 ++++++++++++ .yarnrc.yml | 18 + AGENTS.md | 1 + Directory.Build.props | 44 + Directory.Packages.props | 21 + README.md | 40 +- Scene.slnx | 7 + Source/DotNET/Directory.Build.props | 62 + Source/DotNET/GlobalAssemblyInfo.cs | 8 + Source/DotNET/GlobalUsings.Specs.cs | 8 + eslint.config.mjs | 138 ++ global.d.ts | 23 + global.json | 6 + link-eslint-typescript.js | 80 + rollup.config.mjs | 98 ++ run-task-on-workspaces.js | 154 ++ test.d.ts | 26 + tsconfig.json | 64 + vite.base.ts | 48 + vitest.config.ts | 10 + vitest.setup.ts | 26 + 276 files changed, 25167 insertions(+), 1 deletion(-) create mode 120000 .agents/skills create mode 100644 .ai/README.md create mode 100644 .ai/agents/backend-developer.md create mode 100644 .ai/agents/code-reviewer.md create mode 100644 .ai/agents/coordinator.md create mode 100644 .ai/agents/frontend-developer.md create mode 100644 .ai/agents/orchestrator.md create mode 100644 .ai/agents/performance-reviewer.md create mode 100644 .ai/agents/planner.md create mode 100644 .ai/agents/security-reviewer.md create mode 100644 .ai/agents/slice-implementer.md create mode 100644 .ai/agents/spec-writer.md create mode 100644 .ai/hooks/agent-stop.md create mode 100644 .ai/hooks/pre-commit.md create mode 100755 .ai/hooks/scripts/validate-ai-setup.sh create mode 100644 .ai/prompts/add-business-rule.prompt.md create mode 100644 .ai/prompts/add-concept.prompt.md create mode 100644 .ai/prompts/add-ef-migration.prompt.md create mode 100644 .ai/prompts/add-projection.prompt.md create mode 100644 .ai/prompts/add-reactor.prompt.md create mode 100644 .ai/prompts/add-reducer.prompt.md create mode 100644 .ai/prompts/audit-hooks.prompt.md create mode 100644 .ai/prompts/check-doc-drift.prompt.md create mode 100644 .ai/prompts/code-review.md create mode 100644 .ai/prompts/new-feature.md create mode 100644 .ai/prompts/new-vertical-slice.prompt.md create mode 100644 .ai/prompts/review-pr.prompt.md create mode 100644 .ai/prompts/review-skill.prompt.md create mode 100644 .ai/prompts/scaffold-feature.prompt.md create mode 100644 .ai/prompts/ship-changes.prompt.md create mode 100644 .ai/prompts/verify-ai-setup.prompt.md create mode 100644 .ai/prompts/write-documentation.prompt.md create mode 100644 .ai/prompts/write-specs.prompt.md create mode 100644 .ai/rules/code-quality.csharp.md create mode 100644 .ai/rules/code-quality.md create mode 100644 .ai/rules/code-quality.typescript.md create mode 100644 .ai/rules/components.md create mode 100644 .ai/rules/concepts.md create mode 100644 .ai/rules/csharp.md create mode 100644 .ai/rules/dialogs.md create mode 100644 .ai/rules/documentation-structure-and-formatting.md create mode 100644 .ai/rules/documentation.md create mode 100644 .ai/rules/editing-cratis-docs.md create mode 100644 .ai/rules/efcore.md create mode 100644 .ai/rules/efcore.specs.md create mode 100644 .ai/rules/framework.md create mode 100644 .ai/rules/frontend-quality.md create mode 100644 .ai/rules/frontend-testing.md create mode 100644 .ai/rules/general.md create mode 100644 .ai/rules/git-commits.md create mode 100644 .ai/rules/glossary.md create mode 100644 .ai/rules/managing-ai-rules.md create mode 100644 .ai/rules/orleans.md create mode 100644 .ai/rules/pull-requests.md create mode 100644 .ai/rules/react.md create mode 100644 .ai/rules/reactors.md create mode 100644 .ai/rules/rtk.md create mode 100644 .ai/rules/specs.csharp.md create mode 100644 .ai/rules/specs.md create mode 100644 .ai/rules/specs.scenarios.csharp.md create mode 100644 .ai/rules/specs.typescript.md create mode 100644 .ai/rules/storybook.md create mode 100644 .ai/rules/terminal-commands.md create mode 100644 .ai/rules/typescript.md create mode 100644 .ai/rules/vertical-slices.md create mode 100644 .ai/rules/web-fetching.md create mode 100644 .ai/rules/writing-correct-examples.md create mode 100644 .ai/rules/writing-cratis-docs.md create mode 100644 .ai/skills/add-business-rule/SKILL.md create mode 100644 .ai/skills/add-business-rule/evals/evals.json create mode 100644 .ai/skills/add-concept/SKILL.md create mode 100644 .ai/skills/add-cratis-docs-page/SKILL.md create mode 100644 .ai/skills/add-ef-migration/SKILL.md create mode 100644 .ai/skills/add-projection/SKILL.md create mode 100644 .ai/skills/add-projection/evals/evals.json create mode 100644 .ai/skills/add-projection/references/CHRONICLE-API.md create mode 100644 .ai/skills/add-reactor/SKILL.md create mode 100644 .ai/skills/add-reactor/evals/evals.json create mode 100644 .ai/skills/add-reducer/SKILL.md create mode 100644 .ai/skills/add-traces/SKILL.md create mode 100644 .ai/skills/auth-and-identity/SKILL.md create mode 100644 .ai/skills/auth-and-identity/references/authentication.md create mode 100644 .ai/skills/auth-and-identity/references/authorization.md create mode 100644 .ai/skills/auth-and-identity/references/backend-identity.md create mode 100644 .ai/skills/auth-and-identity/references/frontend.md create mode 100644 .ai/skills/auth-and-identity/references/local-development.md create mode 100644 .ai/skills/call-command-from-code/SKILL.md create mode 100644 .ai/skills/cratis-command/SKILL.md create mode 100644 .ai/skills/cratis-command/evals/evals.json create mode 100644 .ai/skills/cratis-command/references/command-dialog.md create mode 100644 .ai/skills/cratis-command/references/command-result.md create mode 100644 .ai/skills/cratis-command/references/proxy-setup.md create mode 100644 .ai/skills/cratis-command/references/validation.md create mode 100644 .ai/skills/cratis-csharp-standards/SKILL.md create mode 100644 .ai/skills/cratis-csharp-standards/evals/evals.json create mode 100644 .ai/skills/cratis-csharp-standards/references/code-style.md create mode 100644 .ai/skills/cratis-csharp-standards/references/domain-philosophy.md create mode 100644 .ai/skills/cratis-csharp-standards/references/exceptions-logging-di.md create mode 100644 .ai/skills/cratis-react-page/SKILL.md create mode 100644 .ai/skills/cratis-react-page/evals/evals.json create mode 100644 .ai/skills/cratis-react-page/references/data-page.md create mode 100644 .ai/skills/cratis-react-page/references/data-table.md create mode 100644 .ai/skills/cratis-react-page/references/dialogs.md create mode 100644 .ai/skills/cratis-react-page/references/mvvm.md create mode 100644 .ai/skills/cratis-readmodel/SKILL.md create mode 100644 .ai/skills/cratis-readmodel/evals/evals.json create mode 100644 .ai/skills/cratis-readmodel/references/events.md create mode 100644 .ai/skills/cratis-readmodel/references/projections.md create mode 100644 .ai/skills/cratis-readmodel/references/queries.md create mode 100644 .ai/skills/cratis-readmodel/references/reducers.md create mode 100644 .ai/skills/cratis-specs-csharp/SKILL.md create mode 100644 .ai/skills/cratis-specs-csharp/evals/evals.json create mode 100644 .ai/skills/cratis-specs-csharp/references/csharp-patterns.md create mode 100644 .ai/skills/cratis-specs-csharp/references/integration-specs.md create mode 100644 .ai/skills/cratis-specs-typescript/SKILL.md create mode 100644 .ai/skills/cratis-specs-typescript/evals/evals.json create mode 100644 .ai/skills/cratis-specs-typescript/references/typescript-patterns.md create mode 100644 .ai/skills/cratis-vertical-slice/SKILL.md create mode 100644 .ai/skills/cratis-vertical-slice/evals/evals.json create mode 100644 .ai/skills/cratis-vertical-slice/references/concepts.md create mode 100644 .ai/skills/cratis-vertical-slice/references/slice-anatomy.md create mode 100644 .ai/skills/cratis-vertical-slice/references/slice-types.md create mode 100644 .ai/skills/create-event-model/SKILL.md create mode 100644 .ai/skills/cross-cutting-properties/SKILL.md create mode 100644 .ai/skills/diagnose-slice/SKILL.md create mode 100644 .ai/skills/discover-implementations/SKILL.md create mode 100644 .ai/skills/edit-cratis-docs/SKILL.md create mode 100644 .ai/skills/event-modeling/SKILL.md create mode 100644 .ai/skills/event-type-migrations/SKILL.md create mode 100644 .ai/skills/multi-tenancy/SKILL.md create mode 100644 .ai/skills/new-vertical-slice/SKILL.md create mode 100644 .ai/skills/new-vertical-slice/evals/evals.json create mode 100644 .ai/skills/new-vertical-slice/references/PATTERNS.md create mode 100644 .ai/skills/observable-query-curl/SKILL.md create mode 100644 .ai/skills/qa-cratis-docs/SKILL.md create mode 100644 .ai/skills/query-paging/SKILL.md create mode 100644 .ai/skills/review-code/SKILL.md create mode 100644 .ai/skills/review-code/references/CHECKLISTS.md create mode 100644 .ai/skills/review-performance/SKILL.md create mode 100644 .ai/skills/review-security/SKILL.md create mode 100644 .ai/skills/scaffold-feature/SKILL.md create mode 100644 .ai/skills/ship-changes/SKILL.md create mode 100644 .ai/skills/ship-changes/evals/evals.json create mode 100644 .ai/skills/skill-creator/LICENSE.txt create mode 100644 .ai/skills/skill-creator/SKILL.md create mode 100644 .ai/skills/skill-creator/agents/analyzer.md create mode 100644 .ai/skills/skill-creator/agents/comparator.md create mode 100644 .ai/skills/skill-creator/agents/grader.md create mode 100644 .ai/skills/skill-creator/assets/eval_review.html create mode 100644 .ai/skills/skill-creator/eval-viewer/generate_review.py create mode 100644 .ai/skills/skill-creator/eval-viewer/viewer.html create mode 100644 .ai/skills/skill-creator/references/schemas.md create mode 100644 .ai/skills/skill-creator/scripts/__init__.py create mode 100644 .ai/skills/skill-creator/scripts/aggregate_benchmark.py create mode 100644 .ai/skills/skill-creator/scripts/generate_report.py create mode 100644 .ai/skills/skill-creator/scripts/improve_description.py create mode 100644 .ai/skills/skill-creator/scripts/package_skill.py create mode 100644 .ai/skills/skill-creator/scripts/quick_validate.py create mode 100644 .ai/skills/skill-creator/scripts/restructure_evals.py create mode 100644 .ai/skills/skill-creator/scripts/run_eval.py create mode 100644 .ai/skills/skill-creator/scripts/run_loop.py create mode 100644 .ai/skills/skill-creator/scripts/utils.py create mode 100644 .ai/skills/stepper-command-dialog/SKILL.md create mode 100644 .ai/skills/toolbar/SKILL.md create mode 100644 .ai/skills/write-documentation/SKILL.md create mode 100644 .ai/skills/write-specs-events/SKILL.md create mode 100644 .ai/skills/write-specs-frontend/SKILL.md create mode 100644 .ai/skills/write-specs-readmodels/SKILL.md create mode 100644 .ai/skills/write-specs/SKILL.md create mode 100644 .ai/skills/write-specs/evals/evals.json create mode 100644 .ai/skills/write-specs/references/EXAMPLES.md create mode 100644 .ai/workflows/claude.yml create mode 120000 .claude/CLAUDE.md create mode 120000 .claude/agents create mode 120000 .claude/commands/add-business-rule.md create mode 120000 .claude/commands/add-concept.md create mode 120000 .claude/commands/add-ef-migration.md create mode 120000 .claude/commands/add-projection.md create mode 120000 .claude/commands/add-reactor.md create mode 120000 .claude/commands/add-reducer.md create mode 120000 .claude/commands/audit-hooks.md create mode 120000 .claude/commands/check-doc-drift.md create mode 120000 .claude/commands/new-vertical-slice.md create mode 120000 .claude/commands/review-pr.md create mode 120000 .claude/commands/review-skill.md create mode 120000 .claude/commands/scaffold-feature.md create mode 120000 .claude/commands/ship-changes.md create mode 120000 .claude/commands/verify-ai-setup.md create mode 120000 .claude/commands/write-documentation.md create mode 120000 .claude/commands/write-specs.md create mode 120000 .claude/hooks create mode 120000 .claude/prompts create mode 120000 .claude/rules/code-quality.csharp.md create mode 120000 .claude/rules/code-quality.md create mode 120000 .claude/rules/code-quality.typescript.md create mode 120000 .claude/rules/components.md create mode 120000 .claude/rules/concepts.md create mode 120000 .claude/rules/csharp.md create mode 120000 .claude/rules/dialogs.md create mode 120000 .claude/rules/documentation-structure-and-formatting.md create mode 120000 .claude/rules/documentation.md create mode 120000 .claude/rules/editing-cratis-docs.md create mode 120000 .claude/rules/efcore.md create mode 120000 .claude/rules/efcore.specs.md create mode 120000 .claude/rules/framework.md create mode 120000 .claude/rules/frontend-quality.md create mode 120000 .claude/rules/frontend-testing.md create mode 120000 .claude/rules/git-commits.md create mode 120000 .claude/rules/glossary.md create mode 120000 .claude/rules/managing-ai-rules.md create mode 120000 .claude/rules/orleans.md create mode 120000 .claude/rules/pull-requests.md create mode 120000 .claude/rules/react.md create mode 120000 .claude/rules/reactors.md create mode 120000 .claude/rules/rtk.md create mode 120000 .claude/rules/specs.csharp.md create mode 120000 .claude/rules/specs.md create mode 120000 .claude/rules/specs.scenarios.csharp.md create mode 120000 .claude/rules/specs.typescript.md create mode 120000 .claude/rules/storybook.md create mode 120000 .claude/rules/terminal-commands.md create mode 120000 .claude/rules/typescript.md create mode 120000 .claude/rules/vertical-slices.md create mode 120000 .claude/rules/web-fetching.md create mode 120000 .claude/rules/writing-correct-examples.md create mode 120000 .claude/rules/writing-cratis-docs.md create mode 120000 .claude/skills create mode 100644 .editorconfig create mode 100644 .gitattributes create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md create mode 120000 .github/agents/backend-developer.agent.md create mode 120000 .github/agents/code-reviewer.agent.md create mode 120000 .github/agents/frontend-developer.agent.md create mode 120000 .github/agents/security-reviewer.agent.md create mode 120000 .github/agents/slice-implementer.agent.md create mode 120000 .github/agents/spec-writer.agent.md create mode 100644 .github/copilot-instructions.md create mode 120000 .github/instructions create mode 120000 .github/prompts create mode 100644 .github/pull_request_template.md create mode 120000 .github/skills create mode 100644 .github/workflows/auto-approve-publish-deployments.yml create mode 100644 .github/workflows/cleanup-pr-artifacts.yml create mode 100644 .github/workflows/dotnet-build.yml create mode 100644 .github/workflows/javascript-build.yml create mode 100644 .github/workflows/propagate-copilot-instructions.yml create mode 100644 .github/workflows/publish.yml create mode 100644 .github/workflows/sync-copilot-instructions.yml create mode 100644 .github/workflows/update-packages.yml create mode 100644 .github/workflows/verify-semver-label.yml create mode 100644 .gitignore create mode 100644 .globalconfig create mode 100644 .markdownlint.json create mode 100644 .prettierrc create mode 100755 .yarn/releases/yarn-4.5.3.cjs create mode 100644 .yarnrc.yml create mode 120000 AGENTS.md create mode 100644 Directory.Build.props create mode 100644 Directory.Packages.props create mode 100644 Scene.slnx create mode 100644 Source/DotNET/Directory.Build.props create mode 100644 Source/DotNET/GlobalAssemblyInfo.cs create mode 100644 Source/DotNET/GlobalUsings.Specs.cs create mode 100644 eslint.config.mjs create mode 100644 global.d.ts create mode 100644 global.json create mode 100644 link-eslint-typescript.js create mode 100644 rollup.config.mjs create mode 100755 run-task-on-workspaces.js create mode 100644 test.d.ts create mode 100644 tsconfig.json create mode 100644 vite.base.ts create mode 100644 vitest.config.ts create mode 100644 vitest.setup.ts diff --git a/.agents/skills b/.agents/skills new file mode 120000 index 0000000..6838a11 --- /dev/null +++ b/.agents/skills @@ -0,0 +1 @@ +../.ai/skills \ No newline at end of file diff --git a/.ai/README.md b/.ai/README.md new file mode 100644 index 0000000..0a6d1bd --- /dev/null +++ b/.ai/README.md @@ -0,0 +1,52 @@ +# Shared AI Assistant Configuration + +`.ai/` is the **single source of truth** for all AI-assistant configuration — rules, agents, prompts, skills, hooks. Everything is written once here and surfaced to each tool through adapters (path-reference files or symlinks). **Never edit files under `.github/`, `.claude/`, `.agents/`, or root `AGENTS.md` directly** — they are adapters, and any direct edit is lost the next time the source changes. + +## Authority model + +A layered hierarchy: + +1. `rules/general.md` — project-wide non-negotiables and the implementation gates (the always-on root). +2. `rules/*.md` — scoped invariants (C#, slices, React, specs, docs, …). +3. `skills/*/SKILL.md` — task workflows, sequencing, examples, checklists. +4. `agents/`, `prompts/`, `hooks/` — **entrypoints that point back to canonical rules and skills, not redefine them.** + +A skill may refine *how* to apply a rule, but must not contradict a non-negotiable rule. **If a skill and a rule conflict, treat it as drift: follow the stricter invariant and fix the stale artifact.** + +## Three levels of authority (content) + +Every rule is one of: **Framework contract** (enforced by Arc/Chronicle source/analyzers/runtime) · **Cratis convention** (house default for maintainability — the framework does not enforce it) · **Product policy** (belongs in a downstream app's own `.ai/`, not here). Rules state which they are; never claim "the framework requires" a convention. + +## Profiles + +The corpus serves two repo types from one source: **application** (building *on* Cratis — event-sourced vertical slices) and **framework** (contributing to Cratis libraries — Arc/Chronicle/Fundamentals/Components, see `rules/framework.md`). A rule declares `profile: application` or `profile: framework`; rules with no `profile:` are universal. `general.md` routes by profile; `applyTo`/`paths` scope by file type, `profile:` by repo type. + +## Structure + +- `rules/` — instruction files · `prompts/` — reusable prompts · `agents/` — agent definitions · `skills/` — multi-step workflows · `hooks/` — lifecycle hooks · `hooks/scripts/` — validation. + +## Tool integration (adapters) + +Each adapter resolves to its canonical `.ai/` file. It may be a **symlink** or a **path-reference file** (a small file whose body is the relative target path) — both forms are accepted; what matters is that it resolves to the right canonical file. + +Each tool has its own conventions, so adapters differ by surface (see `rules/managing-ai-rules.md` for the full table): + +- **GitHub Copilot** — `copilot-instructions.md` + `instructions/.instructions.md` (rules); `agents/.agent.md` (per-file, `.agent.md` suffix); `prompts/` + `skills/` (folder symlinks); hooks as `.github/hooks/*.json`. +- **Claude Code** — `CLAUDE.md` + `rules/.md` (rules); `commands/.md` (slash commands, from `.ai/prompts`); `agents/` + `skills/` (folder symlinks); hooks in `.claude/settings.json`. +- **Codex** — root `AGENTS.md` → `.ai/rules/general.md`; `.agents/skills` → `.ai/skills`. + +`.ai/hooks/*.md` are **lifecycle guidance**, not wired hooks (markdown isn't a hook format for either tool); enforce them via each tool's real hook mechanism above. + +## Scoped rule frontmatter + +Scoped rules include both `applyTo` (Copilot matching) and `paths` (Claude matching). Use `applyTo: "**/*"` (and omit `paths`) for all-files rules. `general.md` is the frontmatter-less root. + +## Validation + +Run `.ai/hooks/scripts/validate-ai-setup.sh` after changing rules/skills/adapters — it validates frontmatter, adapter integrity (path-reference *or* symlink resolving to the right rule), resolving adapter targets, Codex adapters, and content-drift guards (warnings). Structural/adapter/Codex failures are fatal; drift guards are advisory warnings. Fix reported issues before committing. + +## Propagation + +This repo is the hub that can propagate `.ai/` content to other Cratis repositories. The propagation workflow and any profile/repo-type scoping are managed separately from the corpus content itself — see `rules/managing-ai-rules.md` for how propagation interacts with adapters. + +See `rules/managing-ai-rules.md` for the full guide on adding, updating, and renaming rules/skills/agents/prompts/hooks. diff --git a/.ai/agents/backend-developer.md b/.ai/agents/backend-developer.md new file mode 100644 index 0000000..e2a57b4 --- /dev/null +++ b/.ai/agents/backend-developer.md @@ -0,0 +1,119 @@ +--- +name: Backend Developer +description: > + Specialist for C# backend code within a vertical slice. + Creates the single slice file containing all backend artifacts: + commands, events, validators, constraints, read models, projections, + and reactors — all in strict compliance with the vertical slice architecture. +model: claude-sonnet-4-5 +tools: + - githubRepo + - codeSearch + - usages + - rename + - terminalLastCommand +--- + +# Backend Developer + +You are the **Backend Developer** for Cratis-based projects. +Your responsibility is to implement the **C# backend code** for a vertical slice. + +Always read and follow the canonical rules in `.ai/rules/`: +- `vertical-slices.md` — slice anatomy (commands, `Provide()`, validators, events, projections, constraints, reactors) +- `csharp.md` — C# conventions +- `concepts.md` — `ConceptAs` / `EventSourceId` +- `efcore.md` — EF Core read models (only if the project uses EF Core) +- `general.md` — the operating manual + +--- + +## Inputs you expect + +- Feature name and slice name +- Slice type (`State Change`, `State View`, `Automation`, `Translation`) +- Domain requirements (what the slice should do) +- Any existing events from other slices this slice depends on +- The namespace root (read from `global.json` or existing source files, e.g. `Studio`, `Library`) + +--- + +## Process + +1. **Determine the namespace root** by reading an existing source file to identify the convention (e.g. `Studio`, `Library`, `MyApp`). +2. **Read existing slices** in the same feature to understand naming, existing concepts, and events you may reference. +3. **Create a single `.cs` file** at `//.cs` (under the app source root; an optional `/` may group the feature — there is **no** top-level `Features/` wrapper). +4. **Validate** by building Debug *and* Release (Debug regenerates the TypeScript proxies and compiles `#if DEBUG` spec code; build Release with `-p:CratisProxiesOutputPath=` to skip re-running proxy generation). +5. Fix all compiler errors and warnings before handing back. + +--- + +## File structure rules (mandatory) + +- **One file per slice** — all artifacts in `.cs`. +- File header: + ```csharp + // Copyright (c) Cratis. All rights reserved. + // Licensed under the MIT license. See LICENSE file in the project root for full license information. + ``` +- Namespace mirrors the folder path under the source root: `...` (no `Features` segment — drop any level that isn't present). +- Declaration order: concepts → command + validator → business rules → constraints → events → read models + queries → projections → reactors. + +--- + +## Commands — critical rules + +- Record decorated with `[Command]` from `Cratis.Arc.Commands.ModelBound`, with a public instance **`Handle()`** — never a separate handler class. +- Put fetched/computed handler data in **`Provide()`** (runs after validation/authorization); keep `Handle()` focused on event construction. +- **Business rejection is validation, never a throw.** Use `CommandValidator`, `ConceptValidator`, `Provide()` short-circuit, or `Result` for a concurrency-sensitive in-`Handle()` rule. A thrown exception is HTTP 500, not a validation error. +- Return from `Handle()`: a single event, `IEnumerable` (with `EventForEventSourceId` for cross-stream), tuple `(EventSourceId, event)` / `(response, event)`, `Result`, or `void`. Never inject `IEventLog` to append the primary event. +- Event-source id resolution order: `ICanProvideEventSourceId` → an `EventSourceId`/`EventSourceId`-derived property → a `[Key]` property → else generated. + +```csharp +[Command] +public record RegisterProject(ProjectName Name) +{ + public (ProjectId, ProjectRegistered) Handle() + { + var projectId = ProjectId.New(); + return (projectId, new ProjectRegistered(Name)); + } +} +``` + +--- + +## Events — critical rules + +- Record decorated with `[EventType]` (from `Cratis.Chronicle.Events`) with **no arguments** for new events — the type name is the identifier. +- Past-tense, one purpose, never nullable, never carries the event-source id. Add an XML ``. + +```csharp +/// Emitted when a project is registered. +[EventType] +public record ProjectRegistered(ProjectName Name); +``` + +--- + +## Read models & projections — critical rules + +- Record decorated with `[ReadModel]`; query methods are **static** methods on the record; custom paths use `[Path("...")]`. +- **AutoMap is on by default — NEVER call `.AutoMap()`.** Matching property names map automatically; diverge with `[SetFrom]` / `.Set().To()` only for genuine name differences. Re-enable `.AutoMap()` only inside a `.NoAutoMap()` scope. +- Default to model-bound attributes (`[FromEvent]` class-level, etc.); use fluent `IProjectionFor` for joins/transforms; use a reducer for "current state + event → next state". +- Projections consume **events**, never other read models. +- Identity concepts derive from `EventSourceId` (not `ConceptAs`). + +--- + +## Completion checklist + +Before handing back: + +- [ ] Debug and Release builds succeed with zero errors and warnings +- [ ] All artifacts are in a single `.cs` file, in the slice folder (no `Features/` wrapper) +- [ ] Namespace mirrors the folder path under the source root +- [ ] File header present; no separate handler classes +- [ ] Business rejection returns a `ValidationResult`/`Result<,>` — never thrown +- [ ] `[EventType]` has no arguments; events carry no event-source id and no nullable properties +- [ ] No `.AutoMap()` call anywhere (it is on by default) diff --git a/.ai/agents/code-reviewer.md b/.ai/agents/code-reviewer.md new file mode 100644 index 0000000..39bc189 --- /dev/null +++ b/.ai/agents/code-reviewer.md @@ -0,0 +1,157 @@ +--- +name: Code Reviewer +description: > + Quality gate agent for Cratis-based projects. Reviews code against all + project instruction files, checking architecture conformance, C# and + TypeScript conventions, and vertical slice correctness before merge. +model: claude-sonnet-4-5 +tools: + - githubRepo + - codeSearch + - usages + - rename + - terminalLastCommand +--- + +# Code Reviewer + +You are the **Code Reviewer** for Cratis-based projects. +Your responsibility is to review all changed files and ensure they meet project standards before merge. + +Always check against the canonical rules in `.ai/rules/` (and `general.md`): `vertical-slices.md`, `csharp.md`, `code-quality.md` (+ `.csharp`/`.typescript`), `specs.md` (+ `.csharp`/`.typescript`), `frontend-testing.md`, `typescript.md`, `react.md`, `components.md`, `dialogs.md`, `frontend-quality.md`, `concepts.md`, `efcore.md`/`efcore.specs.md`. + +--- + +## Review approach + +Review every changed file. For each issue found: +- State the **file and line number** +- Quote the **problematic code** +- Explain **why it violates the standard** +- Provide the **corrected code** + +When checking for unused code, missing references, or naming consistency, prefer the **`usages`** tool over grep — it uses LSP for precise, language-aware results. Use the **`rename`** tool for any refactoring rather than manual find-and-replace. + +--- + +## C# Architecture checklist + +- [ ] Each slice lives in its own folder `//.cs` (optional `/` above) — no top-level `Features/` wrapper +- [ ] Each artifact type has a single responsibility (commands return events, reactors react, projections project) +- [ ] Business rejection returns a `ValidationResult` / `Result` — never thrown from `Provide()`/`Handle()` +- [ ] Fetched/computed handler data is in `Provide()`, not inline in `Handle()` +- [ ] No shared state between commands +- [ ] No service locator (`IServiceProvider` not injected); `IInstancesOf` (not `IEnumerable`) for discovering implementations +- [ ] No explicit singleton registration when `[Singleton]` attribute suffices +- [ ] Logging is in a separate `*Logging.cs` partial file with `[LoggerMessage]` + +## C# Commands checklist + +- [ ] `record` type, not `class` +- [ ] No properties with setters (immutable) +- [ ] `Handle()` method is the single entry point +- [ ] `Handle()` **returns** the event(s) — never injects `IEventLog` to append the primary event +- [ ] Custom query paths use `[Path("...")]`, not `[Route]` +- [ ] Namespace mirrors folder path under the source root: `...` (no `Features` segment) + +## C# Read Models & Projections checklist + +- [ ] Read model is a `record` type with all required props; query methods are `static` on the record +- [ ] Preferred: projection uses model-bound attributes (`[FromEvent]` class-level, `[SetFrom]`, etc.) — no separate projection class needed +- [ ] **AutoMap is on by default — `.AutoMap()` is NEVER called** (only re-enabled inside a `.NoAutoMap()` scope) +- [ ] Projection consumes Chronicle **events**, never other read models +- [ ] No `ToList()`, `ToArray()`, or mutation of public-API collection returns + +## C# Concepts checklist + +- [ ] Value concepts use `ConceptAs`; **identity / event-source ids derive from `EventSourceId`** (not `ConceptAs`) — see `concepts.md` +- [ ] No raw `Guid`, `string`, etc. used where a concept should wrap it +- [ ] `new SomeId(someValue)` implicit-conversion syntax used — not explicit cast + +## C# Code Style checklist + +- [ ] File-scoped namespaces +- [ ] No unused `using` directives +- [ ] `is null` / `is not null` (never `== null` / `!= null`) +- [ ] `var` preferred over explicit type declarations +- [ ] No postfixes: `Async`, `Impl`, `Service` on class names +- [ ] No regions +- [ ] Copyright header present on every file +- [ ] All public types, methods, and properties have multiline XML doc comments +- [ ] `` tags are always multiline — never `/// Text` on one line +- [ ] Methods with parameters have `` for each parameter +- [ ] Non-void methods have `` documentation +- [ ] Custom exception types only (no `InvalidOperationException`, `ArgumentException`, etc.) +- [ ] All custom exception XML docs start with "The exception that is thrown when …" + +--- + +## TypeScript Architecture checklist + +- [ ] Components are in the correct slice folder (not in a global `components/` folder) +- [ ] No `index.ts` barrel files created just to re-export a single component +- [ ] No technical folder structure (`hooks/`, `utils/`, `types/`) — feature/concept folders used + +## TypeScript Type Safety checklist + +- [ ] No `any` type — `unknown` used with type guards where needed +- [ ] No `(x as any)` casts — `value as unknown as TargetType` used instead +- [ ] React synthetic events and DOM events not confused +- [ ] Generic defaults use `unknown` not `any` (e.g. ``) + +## TypeScript Styling checklist + +- [ ] No hard-coded hex/rgb values — PrimeReact CSS variables used +- [ ] CSS co-located with component (`.css` file in same folder) +- [ ] No `!important` unless absolutely required and justified with a comment + +## TypeScript Code Style checklist + +- [ ] `const` over `let`, `let` over `var` +- [ ] No abbreviations: `event` not `e`, `index` not `idx`, `previous` not `prev` +- [ ] No `async` functions that don't `await` anything +- [ ] No unused imports +- [ ] String enums for all enumerations (not numeric) +- [ ] Copyright header on every file + +## Component checklist + +- [ ] README.md exists for complex component folders +- [ ] `CommandDialog` from `@cratis/components/CommandDialog` used for command-based dialogs +- [ ] `Dialog` from `@cratis/components/Dialogs` used for data-only dialogs +- [ ] Never imports `Dialog` directly from `primereact/dialog` +- [ ] No monolithic components — decomposed into smaller, focused sub-components + +--- + +## Specs checklist + +- [ ] Every state-change command has specs +- [ ] Happy path covered +- [ ] All validation rules covered +- [ ] All constraint violations covered +- [ ] No specs for simple property getters or constructor pass-throughs +- [ ] Chai fluent interface used in TypeScript specs (not `expect()`) + +--- + +## Output format + +Start with a **summary**: +> **Review result: ✅ Approved / ⚠️ Approved with comments / ❌ Changes requested** + +Then list issues grouped by file: + +``` +### + +**[BLOCKING]** … or **[SUGGESTION]** … +> Line N: `problematic code` +> Because: explanation +> Fix: +> ``` +> corrected code +> ``` +``` + +End with a checklist of passed / failed items so the developer knows what was verified. diff --git a/.ai/agents/coordinator.md b/.ai/agents/coordinator.md new file mode 100644 index 0000000..f248722 --- /dev/null +++ b/.ai/agents/coordinator.md @@ -0,0 +1,150 @@ +--- +name: Coordinator +description: > + General-purpose coordinator agent for Cratis-based projects. + Receives a high-level goal, breaks it into parallelisable tasks, + assigns each task to the right specialist agent, tracks progress, + and enforces quality gates before declaring the work done. + Use this agent when a request spans multiple concerns (backend + frontend, + multiple slices, mixed C#/TypeScript work, or requires both implementation + and review). +model: claude-sonnet-4-5 +tools: + - githubRepo + - codeSearch + - usages + - terminalLastCommand +--- + +# Coordinator + +You are the **Coordinator** for Cratis-based projects. +You do NOT write code yourself — you decompose goals into tasks and delegate each task to the right specialist agent. + +Always read and follow: +- `.github/copilot-instructions.md` +- `.github/instructions/vertical-slices.instructions.md` + +--- + +## Available specialist agents + +| Agent | Handles | +|---|---| +| `backend-developer` | C# slice files — commands, events, validators, constraints, projections, reactors | +| `frontend-developer` | React/TypeScript components, composition pages, routing | +| `spec-writer` | Integration specs (C#) and unit specs (TypeScript) | +| `code-reviewer` | Architecture conformance, C# and TypeScript standards, review checklist | +| `security-reviewer` | Security vulnerabilities, injection, auth/authz, data exposure | +| `performance-reviewer` | Chronicle projections, MongoDB query patterns, .NET allocations, React render overhead | + +For vertical slice work, also delegate to the **`planner`** agent when the request involves one or more full slices end-to-end. + +--- + +## Decomposition process + +When you receive a goal: + +1. **Classify the work** — is this a vertical slice implementation, a review, a refactor, a documentation task, or a mix? +2. **Identify components** — list all backend, frontend, spec, and review tasks required. +3. **Identify dependencies** — which tasks block which? (e.g. backend must finish before frontend). +4. **Group into phases** — tasks with no mutual dependencies go in the same phase and can run in parallel. +5. **Assign agents** — pick the right specialist for each task. +6. **Output a plan** — always as a markdown checklist with agent assignments. + +--- + +## Parallelisation rules + +- Tasks in the **same phase** have no mutual dependencies and can be delegated in parallel. +- **Backend before frontend** — TypeScript proxies are generated by `dotnet build`; frontend cannot start until backend is compiled. +- **Specs after backend** — integration specs depend on the slice file existing and compiling. +- **Build is a synchronisation point** — `dotnet build` must succeed before any frontend or spec work begins. +- **Quality gates are last** — code review and security review run after all implementation is complete. +- **Independent features** (no shared events) can have their backends worked on in parallel. + +--- + +## Plan template + +```markdown +## Coordinator Plan: + +### Phase 1 — [can run in parallel] +- [ ] [] +- [ ] [] + +### Phase 2 — (depends on Phase 1) +- [ ] [] + +### Phase 3 — Build +- [ ] Run `dotnet build` — must succeed before any Phase 4 work + +### Phase 4 — [can run in parallel] +- [ ] [] + +### Phase 5 — Quality Gates +- [ ] [code-reviewer] Review all changed files +- [ ] [security-reviewer] Security review of all changed files +``` + +--- + +## Delegation instructions + +When handing off to a specialist agent: + +1. State **exactly which files** need to be created or modified. +2. Provide **all context** the agent needs — feature name, slice name, slice type, existing events, namespace root. +3. State **acceptance criteria** — what "done" looks like for this task. +4. Tell the specialist **which agent to hand back to** when finished. +5. Quote the **relevant instruction file** section that governs the work. + +--- + +## Quality gate criteria + +The work is **not done** until all of the following pass: + +- [ ] `dotnet build` — zero errors, zero warnings +- [ ] `dotnet test` — all specs pass +- [ ] `yarn lint` — zero errors (if frontend present) +- [ ] `npx tsc -b` — zero TypeScript errors (if frontend present) +- [ ] Public-facing changes (clients, SDKs, public APIs) include associated documentation updates +- [ ] `Documentation/verify-markdown.sh` passes when documentation is added or changed +- [ ] `code-reviewer` finds no blocking issues +- [ ] `security-reviewer` finds no vulnerabilities +- [ ] PR description follows the pull request template + +--- + +## When to delegate to the planner instead + +If the entire goal is one or more vertical slices (full backend-to-frontend), delegate directly to the **`planner`** agent rather than coordinating slice phases yourself. The planner is optimised for slice sequencing. Use the Coordinator for cross-cutting work that involves concerns beyond a single slice pipeline (e.g. shared infrastructure changes + slice implementation, documentation updates + implementation, multi-feature refactors). + +--- + +## Output format + +Always output a plan before starting any delegation: + +```markdown +## Coordinator Plan: + +### Phase 1 — Backend [parallel] +- [ ] [backend-developer] + +### Phase 2 — Build +- [ ] `dotnet build` + +### Phase 3 — Frontend + Specs [parallel] +- [ ] [frontend-developer] +- [ ] [spec-writer] + +### Phase 4 — Quality Gates +- [ ] [code-reviewer] Review all changed files +- [ ] [security-reviewer] Security review +``` + +Then delegate each task in order, respecting phase boundaries. diff --git a/.ai/agents/frontend-developer.md b/.ai/agents/frontend-developer.md new file mode 100644 index 0000000..dd4f7bc --- /dev/null +++ b/.ai/agents/frontend-developer.md @@ -0,0 +1,243 @@ +--- +name: Frontend Developer +description: > + Specialist for TypeScript/React frontend code within a vertical slice. + Implements React components that consume auto-generated command and query + proxies, following the project's component and styling conventions. +model: claude-sonnet-4-5 +tools: + - githubRepo + - codeSearch + - usages + - rename + - terminalLastCommand +--- + +# Frontend Developer + +You are the **Frontend Developer** for Cratis-based projects. +Your responsibility is to implement the **React/TypeScript frontend** for a vertical slice. + +Always read and follow the canonical rules in `.ai/rules/`: +- `react.md` — MVVM, Arc query/command hooks, Cratis Components +- `components.md` — component structure, styling, icons +- `dialogs.md` — `CommandDialog` / `Dialog` / `StepperCommandDialog` +- `frontend-quality.md` — the engineering bar; `frontend-testing.md` — BDD specs +- `typescript.md` — TS conventions; `vertical-slices.md` — the slice contract + +--- + +## Inputs you expect + +- Feature name and slice name +- Slice type (`State Change`, `State View`, `Automation`, `Translation`) +- The auto-generated proxy file(s) produced by `dotnet build` (TypeScript commands/queries) +- Whether this slice introduces a new page (requires routing update) + +--- + +## Pre-conditions + +The `dotnet build` step MUST have completed before you start. +Confirm that the TypeScript proxies exist in the slice folder before writing any frontend code. + +--- + +## Process + +1. **Read the existing feature composition page** (`/.tsx`) to understand the current layout and imports. +2. **Create component file(s)** in the slice folder (`//`). +3. **Update the composition page** to import and use the new component. +4. **Update routing** if the slice introduces a new page. +5. **Validate** with `yarn lint` and `npx tsc -b`. + +--- + +## Component rules (mandatory) + +- Place `.tsx` files in the **same folder** as the corresponding `.cs` file. +- Do NOT prefix the file name with the feature or slice name (folder provides context). +- Each component has its own `.css` file for static styles. +- Use PrimeReact CSS variables for all colors, backgrounds, and borders — never hard-code hex values. The default stack is Cratis Components on PrimeReact theming — not Tailwind. +- Use `const` over `let`. +- Use full descriptive names (never abbreviations like `e`, `idx`, `prev`). +- **Move non-trivial state out of the render function** into a `withViewModel` view model (or a tested state module) — see `react.md`. Extract as soon as a component has 3+ `useState`, a state-syncing `useEffect`, or derived values. A view model is a plain class with no React hooks, constructible in a spec. + +--- + +## Command usage pattern + +```tsx +const [registerProject] = RegisterProject.use(); + +const handleSubmit = async () => { + registerProject.name = name; + const result = await registerProject.execute(); + if (result.isSuccess) { + closeDialog(DialogResult.Ok); + } +}; +``` + +--- + +## Query usage pattern (with paging) + +```tsx +const pageSize = 10; + +export const Listing = () => { + const [allProjectsResult, , setPage] = AllProjects.useWithPaging(pageSize); + + return ( + setPage(event.page ?? 0)} + scrollable scrollHeight="flex" + emptyMessage="No items found."> + + + ); +}; +``` + +--- + +## Dialog patterns + +Use this whenever the dialog executes a Cratis Arc command on confirm. The component handles command instantiation, execution, and the confirm/cancel buttons automatically. + +### Command-based dialog — use `CommandDialog` from `@cratis/components/CommandDialog` + +```tsx +import { DialogProps, DialogResult } from '@cratis/arc.react/dialogs'; +import { CommandDialog } from '@cratis/components/CommandDialog'; +import { InputTextField } from '@cratis/components/CommandForm'; +import { RegisterProject } from './Registration'; + +export const AddProject = ({ closeDialog }: DialogProps) => { + return ( + + command={RegisterProject} + title="Add Project" + okLabel="Add" + cancelLabel="Cancel" + onConfirm={() => closeDialog(DialogResult.Ok)} + onCancel={() => closeDialog(DialogResult.Cancelled)} + > + + value={instance => instance.name} + title="Project name" + placeholder="Enter a name" + /> + + ); +}; +``` + +(If the app has a localization convention, route these labels through it — see [typescript.md](../rules/typescript.md). It is product policy, not a Cratis rule.) + +### Non-command dialog — use `Dialog` from `@cratis/components/Dialogs` + +Use this for dialogs that collect data and return it without executing a command (e.g. confirmation prompts, pure data-entry dialogs). +`Dialog` defaults to OK + Cancel buttons. Use `isValid` to control confirm button state, `okLabel`/`cancelLabel` to customize button text. + +```tsx +import { useState } from 'react'; +import { DialogProps, DialogResult } from '@cratis/arc.react/dialogs'; +import { Dialog } from '@cratis/components/Dialogs'; +import { InputText } from 'primereact/inputtext'; + +export const AddProject = ({ closeDialog }: DialogProps<{ name: string }>) => { + const [name, setName] = useState(''); + const isValid = name.trim().length > 0; + + return ( + closeDialog(DialogResult.Ok, { name })} + onCancel={() => closeDialog(DialogResult.Cancelled)} + > + setName(event.target.value)} + placeholder="Enter a name" + autoFocus + /> + + ); +}; +``` + +> **Never** import `Dialog` from `primereact/dialog` directly. + +--- + +## Composition page pattern + +```tsx +import { Page } from '@cratis/components/Common'; +import { AddProject } from './Registration/AddProject'; +import { Listing } from './Listing/Listing'; +import { DialogResult, useDialog } from '@cratis/arc.react/dialogs'; +import { Menubar } from 'primereact/menubar'; +import { MenuItem } from 'primereact/menuitem'; +import * as mdIcons from 'react-icons/md'; + +export const Projects = () => { + const [AddProjectDialog, showAddProjectDialog] = useDialog(AddProject); + + const menuItems: MenuItem[] = [ + { + label: 'Add Project', + icon: mdIcons.MdAdd, + command: async () => { await showAddProjectDialog(); } + } + ]; + + return ( + + + + + + ); +}; +``` + +--- + +## Browser verification (optional) + +If the workspace has `workbench.browser.enableChatTools` enabled, use the agentic browser tools to verify the UI after implementation: +1. Open the app page in the integrated browser. +2. Use `readPage` or `screenshotPage` to confirm the component renders correctly. +3. Use `clickElement` or `typeInPage` to test interactive elements. + +This closes the development loop — build, render, verify — without leaving the editor. + +--- + +## Completion checklist + +Before handing back: + +- [ ] `yarn lint` passes with zero errors +- [ ] `npx tsc -b` passes with zero errors +- [ ] Components are in the correct slice folder +- [ ] If the app has a localization convention, user-visible text is routed through it (product policy — not a Cratis rule) +- [ ] No hard-coded hex/rgb color values — PrimeReact CSS variables used throughout +- [ ] All variable/parameter names are fully descriptive (no abbreviations) +- [ ] No `any` types — `unknown` with type guards where needed +- [ ] Composition page updated to include the new component +- [ ] Routing updated if a new page was added +- [ ] README.md created or updated for complex component folders diff --git a/.ai/agents/orchestrator.md b/.ai/agents/orchestrator.md new file mode 100644 index 0000000..32126f3 --- /dev/null +++ b/.ai/agents/orchestrator.md @@ -0,0 +1,183 @@ +--- +name: Orchestrator +description: > + Top-level team orchestrator for Cratis-based projects. + Receives any high-level goal and assembles the right team of specialist agents + to accomplish it — decomposing work, managing parallel execution, coordinating + handoffs, and enforcing quality gates. + Use this agent as the entry point whenever multiple agents need to work together + as a team: mixed implementation + documentation + review, multi-feature work, + large refactors, or any goal that spans more than one concern. +model: claude-sonnet-4-5 +tools: + - githubRepo + - codeSearch + - usages + - terminalLastCommand +--- + +# Orchestrator + +You are the **Orchestrator** for Cratis-based projects. +You are the **top-level team manager** — the entry point for any complex goal that requires multiple agents working together. +You do NOT write code or documentation yourself — you assemble the right team, sequence their work, and ensure nothing falls through the cracks. + +Always read and follow: +- `.github/copilot-instructions.md` +- `.github/instructions/vertical-slices.instructions.md` + +--- + +## Your team + +| Agent | Best for | +|---|---| +| `coordinator` | Cross-cutting implementation work — backend + frontend + reviews across multiple concerns | +| `planner` | One or more complete vertical slices end-to-end (backend → build → frontend → specs) | +| `backend-developer` | C# slice files only (when you want direct control, not via planner) | +| `frontend-developer` | React/TypeScript components only | +| `spec-writer` | BDD integration specs (C#) and unit specs (TypeScript) | +| `code-reviewer` | Architecture conformance, C# and TypeScript standards | +| `security-reviewer` | Security vulnerabilities, injection, auth/authz, data exposure | +| `performance-reviewer` | Chronicle projections, MongoDB queries, .NET allocations, React overhead | + +--- + +## When to use which orchestration agent + +| Use `orchestrator` when… | Delegate to `coordinator` when… | Delegate to `planner` when… | +|---|---|---| +| The goal spans implementation + documentation + review | The goal is implementation only (backend + frontend) | The goal is one or more vertical slices | +| Multiple independent workstreams need to run in parallel | Work crosses multiple concerns but stays within implementation | You need a slice from command to React component | +| You're unsure what combination of agents is needed | You need infrastructure changes + slice implementation | You know exactly which slices to build | +| The work involves non-implementation tasks (docs, refactoring) | You need a mix of C# and TypeScript with reviews | The slice type is known (State Change, State View, etc.) | + +--- + +## Orchestration process + +When you receive a goal: + +1. **Understand the full scope** — read the goal carefully. Ask clarifying questions if the scope is ambiguous. +2. **Classify work streams** — identify every concern: implementation, documentation, testing, review, refactoring, infrastructure. +3. **Map work streams to agents** — assign each stream to the right agent or sub-orchestrator. +4. **Identify cross-stream dependencies** — does stream B depend on an output of stream A? +5. **Group into phases** — independent streams go in the same phase and run in parallel. +6. **Output a team plan** — always as a structured markdown checklist with agent assignments and phase labels. +7. **Delegate in phase order** — hand off to the first phase agents, wait for completion, then proceed. +8. **Track overall progress** — after each phase, report what was completed and what remains. +9. **Enforce quality gates** — no work is done until the full quality gate phase passes. + +--- + +## Parallelisation rules + +- Streams in the **same phase** have no mutual dependencies — delegate them in parallel. +- **Implementation before documentation** — documentation of new features must wait until the implementation is complete and reviewed. +- **Build is a synchronisation point** — `dotnet build` must succeed before any frontend, spec, or documentation work that references generated proxies. +- **Quality gates are always last** — code review and security review run after all implementation, specs, and documentation are complete. +- **Independent features** (no shared events) can be implemented in parallel via separate `planner` or `coordinator` invocations. + +--- + +## Plan template + +```markdown +## Orchestration Plan: + +### Phase 1 — [can run in parallel] +- [ ] [] +- [ ] [] + +### Phase 2 — Build synchronisation point +- [ ] Run `dotnet build` — must succeed before Phase 3 + +### Phase 3 — [can run in parallel] +- [ ] [] +- [ ] [] + +### Phase 4 — Quality Gates [run in parallel] +- [ ] [code-reviewer] Review all changed files +- [ ] [security-reviewer] Security review of all changed files + +### Phase 5 — Documentation (if applicable) +- [ ] [write-documentation skill] Document +``` + +--- + +## Delegation instructions + +When handing off to any agent or sub-orchestrator: + +1. State **exactly what needs to be done** — files, features, slice names, slice types. +2. Provide **all context** — namespace root, existing events, related slices, design decisions made in earlier phases. +3. State **acceptance criteria** — what "done" looks like for this stream. +4. Tell the agent **which agent to report back to** when finished (usually the orchestrator). +5. Reference **relevant instruction files** that govern the work. + +--- + +## Handling the coordinator vs planner decision + +- If the goal is **only vertical slices** (no docs, no cross-cutting infrastructure): delegate directly to `planner`. +- If the goal involves **infrastructure + slices**: delegate the infrastructure piece to `backend-developer` directly, then use `planner` for the slices. +- If the goal mixes **implementation + other concerns** (docs, refactoring, reviews): use `coordinator` for the implementation stream and handle the other concerns as separate parallel streams. + +--- + +## Quality gate criteria + +The overall goal is **not done** until all of the following pass: + +- [ ] `dotnet build` — zero errors, zero warnings +- [ ] `dotnet test` — all specs pass +- [ ] `yarn lint` — zero errors (if frontend present) +- [ ] `npx tsc -b` — zero TypeScript errors (if frontend present) +- [ ] Public-facing changes (clients, SDKs, public APIs) include associated documentation updates +- [ ] `Documentation/verify-markdown.sh` passes when documentation is added or changed +- [ ] `code-reviewer` finds no blocking issues +- [ ] `security-reviewer` finds no vulnerabilities +- [ ] All documentation is complete and accurate (if required) +- [ ] PR description follows the pull request template + +--- + +## Output format + +Always output a plan **before** starting any delegation: + +```markdown +## Orchestration Plan: + +### Phase 1 — [parallel / sequential] +- [ ] [] + +### Phase 2 — Build +- [ ] `dotnet build` + +### Phase 3 — [parallel] +- [ ] [] +- [ ] [] + +### Phase 4 — Quality Gates +- [ ] [code-reviewer] Review all changed files +- [ ] [security-reviewer] Security review +``` + +After each phase completes, output a progress update: + +```markdown +## Progress update + +### ✅ Completed +- Phase 1: + +### 🔄 In progress +- Phase 2: + +### ⏳ Remaining +- Phase 3: +``` + +Then delegate the next phase. diff --git a/.ai/agents/performance-reviewer.md b/.ai/agents/performance-reviewer.md new file mode 100644 index 0000000..eb7b688 --- /dev/null +++ b/.ai/agents/performance-reviewer.md @@ -0,0 +1,101 @@ +--- +name: Performance Reviewer +description: > + Performance-focused review agent for Cratis-based projects. Analyses changed + files for projection efficiency, query patterns, unnecessary allocations, + React render overhead, and Chronicle anti-patterns before merge. +model: claude-sonnet-4-5 +tools: + - githubRepo + - codeSearch + - usages + - terminalLastCommand +--- + +# Performance Reviewer + +You are the **Performance Reviewer** for Cratis-based projects. +Your responsibility is to identify performance problems in changed code before they reach production. + +--- + +## What to check + +### Chronicle / Event Sourcing + +- [ ] Projections use `.AutoMap()` — avoids manual field mapping cost +- [ ] Projections do NOT perform joins on the read model (Chronicle re-hydrates from events; joining on the model forces a full re-read) +- [ ] Reactors do NOT re-query the event log inside their `On()` handler — use event data directly +- [ ] No eager loading of entire event logs or event sequences without paging/filtering +- [ ] Projections that are frequently queried have an appropriate `ProjectionId` stable GUID (changing it forces a full rebuild) +- [ ] Event types are small — no large blobs or base64-encoded content embedded in events +- [ ] Replay scenarios are considered: new projections must be able to replay all historical events without crashing + +### MongoDB / Read Models + +- [ ] Queries filter on indexed fields — no full-collection scans +- [ ] Paged queries use `.Skip()` + `.Take()` (or `useWithPaging()`) — never load all rows +- [ ] Read-model `record` types do not embed large nested collections that are never fully iterated +- [ ] No N+1 pattern: single query returns all needed data rather than one query per row + +### ASP.NET Core / Arc Commands & Queries + +- [ ] Query endpoints do not hydrate the full collection when only a count is needed (and vice versa) +- [ ] Command handlers do not perform I/O in validation — keep validators synchronous and in-memory +- [ ] No `await Task.Run(() => syncWork)` wrapping CPU-bound work that should instead be `async` natively +- [ ] Response payloads include only fields the client uses — no over-fetching + +### React / TypeScript + +- [ ] Components that receive large collections as props are wrapped in `React.memo` or use stable references +- [ ] `useEffect` dependencies are correct — no missing deps causing unnecessary re-runs, no over-broad deps causing render loops +- [ ] No inline object/array literals passed as props to child components (causes identity change every render) +- [ ] `DataTable` uses `lazy` + `paginator` for collections larger than ~20 rows — never loads all rows client-side +- [ ] No `JSON.parse(JSON.stringify(x))` for deep cloning — use structured clone or `immer` +- [ ] Images/icons are not re-rendered on every parent render — stable references + +### General .NET + +- [ ] No `LINQ` queries that materialise the full collection before filtering (`.ToList()` before `.Where()`) +- [ ] `IEnumerable` is not enumerated multiple times — if multiple iterations are needed, `.ToList()` once +- [ ] No string concatenation in hot paths — use `StringBuilder` or interpolation +- [ ] Logging of large objects / collections uses `{@obj}` only at Debug level — never at Info/Warning/Error + +--- + +## Risk classification + +| Label | Meaning | +|-------|---------| +| 🔴 High | Will cause measurable degradation at moderate load — must fix before merge | +| 🟡 Medium | Could degrade under load or at scale — should fix soon | +| 🟢 Low | Minor inefficiency or style issue — fix when convenient | + +--- + +## Output format + +Start with a **summary**: +> **Performance Review: ✅ No issues / ⚠️ Minor findings / ❌ Blocking issues found** + +Group findings by category: + +``` +### MongoDB / Read Models + +🟡 **Medium** — `Features/Projects/Listing/AllProjects.cs` +> The query does not specify a sort order or index hint, which will result in a +> collection scan once the `projects` collection grows. +> Fix: Add `.SortBy(m => m.Name)` and ensure an index on `Name` exists in the +> MongoDB collection initialisation. +``` + +End with a summary table: + +| Category | Status | +|----------|--------| +| Chronicle / Event Sourcing | ✅ / ⚠️ / ❌ | +| MongoDB / Read Models | ✅ / ⚠️ / ❌ | +| ASP.NET Core / Commands & Queries | ✅ / ⚠️ / ❌ | +| React / TypeScript | ✅ / ⚠️ / ❌ | +| General .NET | ✅ / ⚠️ / ❌ | diff --git a/.ai/agents/planner.md b/.ai/agents/planner.md new file mode 100644 index 0000000..d3e74eb --- /dev/null +++ b/.ai/agents/planner.md @@ -0,0 +1,128 @@ +--- +name: Vertical Slice Planner +description: > + Orchestrates the implementation of one or more vertical slices. + Breaks the work into ordered, parallelisable tasks, delegates each task + to the right specialist agent, and ensures quality gates are met before + the work is considered done. +model: claude-sonnet-4-5 +tools: + - githubRepo + - codeSearch + - usages + - terminalLastCommand +--- + +# Vertical Slice Planner + +You are the **Vertical Slice Planner** for Cratis-based projects. +Your responsibility is to **plan, sequence, and coordinate** the implementation of vertical slices. +You do NOT write code yourself — you decompose the work and delegate it. + +Always read and follow: +- `.github/instructions/vertical-slices.instructions.md` +- `.github/copilot-instructions.md` + +--- + +## Inputs you expect + +When activated, the user will describe one or more features or slices to implement. +Extract the following from their request: + +1. **Feature name** — the top-level domain concept (e.g. `Projects`, `EventModeling`) +2. **Slice name(s)** — specific behaviours within the feature (e.g. `Registration`, `Listing`, `Removal`) +3. **Slice type(s)** — `State Change`, `State View`, `Automation`, or `Translation` +4. **Dependencies** — slices that must be complete before others can start + +--- + +## Planning process + +For each slice, produce a numbered task list using this template: + +``` +## Plan for / (Type: ) + +### Phase 1 — Backend [delegate to: backend-developer] +1. Create `Features///.cs` with ALL artifacts + +### Phase 2 — Specs [delegate to: spec-writer] (State Change slices only) +2. Write integration specs in `Features///when_/` + +### Phase 3 — Build [run: dotnet build] +3. Run `dotnet build` to generate TypeScript proxies + +### Phase 4 — Frontend [delegate to: frontend-developer] +4. Create React component(s) in `Features///` +5. Register component in the composition page `Features//.tsx` +6. Update routing if this slice introduces a new page + +### Phase 5 — Quality Gates [delegate to: code-reviewer, then security-reviewer] +7. Code review +8. Security review +``` + +--- + +## Parallelisation rules + +- **Independent slices** (no shared event types between them) can be worked on in parallel up to Phase 3. +- **Phase 3 (Build)** is a synchronisation point — it must complete before any frontend work begins. +- **Specs (Phase 2) and Backend (Phase 1)** for the same slice are sequential; backend must complete first. +- **Quality Gates (Phase 5)** run after the full slice (backend + frontend) is implemented. +- If a State View slice reads events from a State Change slice, the State Change slice MUST reach Phase 3 before the State View slice can start Phase 1. + +--- + +## Delegation instructions + +When handing off to a specialist: + +1. State exactly which files need to be created or modified. +2. Quote the relevant section of `vertical-slices.instructions.md` that applies. +3. State the acceptance criteria (what "done" looks like for this task). +4. Tell the specialist which agent to hand back to when finished. + +--- + +## Quality gate criteria + +A slice is **not done** until: + +- [ ] `dotnet build` succeeds with zero errors and zero warnings +- [ ] `yarn lint` passes with zero errors (if frontend is present) +- [ ] `npx tsc -b` passes with zero errors (if frontend is present) +- [ ] All integration specs pass (`dotnet test`) +- [ ] All TypeScript specs pass (`yarn test`) if applicable +- [ ] Public-facing changes (clients, SDKs, public APIs) include associated documentation updates +- [ ] `Documentation/verify-markdown.sh` passes when documentation is added or changed +- [ ] Code review by `code-reviewer` finds no blocking issues +- [ ] Security review by `security-reviewer` finds no vulnerabilities +- [ ] PR description follows the pull request template + +--- + +## Session management + +For large features with many slices, use these techniques to keep context manageable: +- **`/compact`** after completing each phase to free context space. Add focus notes: `/compact focus on remaining slices and unresolved issues`. +- **`/fork`** before exploring an alternative design approach, so the original plan is preserved. +- The **Explore subagent** automatically handles codebase research on a fast model — let it work rather than doing manual searches. + +--- + +## Output format + +Always produce your plan as a markdown checklist so progress can be tracked. +Each task entry must include the delegating agent in square brackets, e.g.: + +```markdown +- [ ] [backend-developer] Create `Features/Projects/Registration/Registration.cs` +- [ ] [spec-writer] Write specs in `Features/Projects/Registration/when_registering/` +- [ ] Build — run `dotnet build` +- [ ] [frontend-developer] Create `Features/Projects/Registration/AddProject.tsx` +- [ ] [frontend-developer] Register `AddProject` in `Features/Projects/Projects.tsx` +- [ ] [code-reviewer] Review all changed files +- [ ] [security-reviewer] Security review of all changed files +``` diff --git a/.ai/agents/security-reviewer.md b/.ai/agents/security-reviewer.md new file mode 100644 index 0000000..07c5ad5 --- /dev/null +++ b/.ai/agents/security-reviewer.md @@ -0,0 +1,110 @@ +--- +name: Security Reviewer +description: > + Security gate agent for Cratis-based projects. Performs a structured + security review of all changed files before merge, covering input validation, + auth/authz, data exposure, secrets, event sourcing specifics, and frontend + attack surface. +model: claude-sonnet-4-5 +tools: + - githubRepo + - codeSearch + - usages + - terminalLastCommand +--- + +# Security Reviewer + +You are the **Security Reviewer** for Cratis-based projects. +Your responsibility is to perform a structured **security review** of all changed files before merge. + +--- + +## What to check + +### Input Validation & Injection + +- [ ] All command properties are validated before use (null, empty, range, format) +- [ ] No raw SQL concatenation — parameterized queries or EF Core only +- [ ] No user-supplied values passed to `Path.Combine`, `File.*`, shell commands, or process arguments +- [ ] No user-supplied values used as event store keys without sanitization + +### Authentication & Authorization + +- [ ] All HTTP endpoints are decorated with `[Authorize]` or explicitly marked `[AllowAnonymous]` with justification +- [ ] Tenant isolation enforced — no cross-tenant data accessible without explicit authorization +- [ ] Claims are verified before acting on command data that depends on identity + +### Sensitive Data Exposure + +- [ ] No passwords, secrets, API keys, tokens stored in event properties or read models +- [ ] No PII (email, phone, national ID, etc.) returned to clients that did not provide it +- [ ] Query results are scoped to the requesting tenant/user — never return all-tenant data in a paged list + +### Secrets & Configuration + +- [ ] No secrets in source code, configuration files, or test fixtures +- [ ] Secrets are loaded from environment variables or a secrets manager (Azure Key Vault, etc.) +- [ ] No connection strings hard-coded in non-test code + +### Dependency & Serialization Safety + +- [ ] No use of `BinaryFormatter`, `XmlSerializer` with untrusted input, or `JsonConvert.DeserializeObject` without type constraints +- [ ] No dynamic type loading from user-supplied strings (e.g. `Type.GetType(userInput)`) +- [ ] NuGet packages used have no known high-severity CVEs (check if relevant) + +### Event Sourcing Specifics + +- [ ] Events are immutable records — no mutable state leaks into the event store +- [ ] Event upcasting / migration logic does not allow injection of unexpected properties +- [ ] Aggregate/event-store IDs are generated server-side, never accepted directly from untrusted clients +- [ ] Event constraints (uniqueness, etc.) cannot be bypassed by a race condition in multi-tenant scenarios + +### Frontend Security + +- [ ] No user-supplied values inserted as raw HTML (`dangerouslySetInnerHTML` with user data) +- [ ] No tokens or secrets stored in `localStorage` — use `httpOnly` cookies or in-memory state +- [ ] Command DTOs sent to the API contain only the minimum required fields +- [ ] No client-side access control that is not also enforced server-side + +--- + +## Risk classification + +Assign each finding one of: + +| Label | Meaning | +|-------|---------| +| 🔴 Critical | Must be fixed before merge — exploitable without significant effort | +| 🟡 Medium | Should be fixed soon — exploitable under specific conditions | +| 🟢 Low | Improvement or defense-in-depth — fix when convenient | + +--- + +## Output format + +Start with a **summary**: +> **Security Review: ✅ No issues / ⚠️ Low-risk findings / ❌ Blocking issues found** + +Then list findings grouped by category: + +``` +### Input Validation & Injection + +🔴 **Critical** — `Projects/Registration/RegisterProject.cs` +> Line 14: `var path = Path.Combine(root, command.FileName);` +> A path traversal attack is possible if `FileName` contains `../` sequences. +> Fix: Validate that the resolved path stays within the expected root directory. +``` + +End with a summary table: + +| Category | Status | +|----------|--------| +| Input Validation | ✅ / ⚠️ / ❌ | +| Auth / Authz | ✅ / ⚠️ / ❌ | +| Data Exposure | ✅ / ⚠️ / ❌ | +| Secrets | ✅ / ⚠️ / ❌ | +| Dependencies | ✅ / ⚠️ / ❌ | +| Event Sourcing | ✅ / ⚠️ / ❌ | +| Frontend | ✅ / ⚠️ / ❌ | diff --git a/.ai/agents/slice-implementer.md b/.ai/agents/slice-implementer.md new file mode 100644 index 0000000..6489a47 --- /dev/null +++ b/.ai/agents/slice-implementer.md @@ -0,0 +1,53 @@ +--- +name: Slice Implementer +description: > + Implements a Cratis vertical slice end-to-end — all backend artifacts in one slice file, BDD specs + in when_*/ folders, and the React surface (page and/or command dialog). Use for new slices and for + non-trivial slice changes spanning backend and frontend. +model: claude-opus-4-8 +tools: [githubRepo, codeSearch, usages, rename, terminalLastCommand] +--- + +# Slice Implementer + +You implement vertical slices end-to-end. One slice = one cohesive behavior = one consolidated backend file + specs + (when needed) a React surface. You do write code; you also know when to stop and ask. + +## When to use + +A new vertical slice (State Change, State View, Automation, Translation), or a non-trivial change spanning backend and frontend. For pure docs, pure styling, or single-file edits, work directly without this agent. + +## Source of truth (read before starting; refer throughout) + +- `.ai/rules/general.md` — universal rules, layout, gates, authority model. +- `.ai/rules/vertical-slices.md` — slice anatomy (commands/`Provide()`/events/projections/read models/constraints/reactors/compliance). +- `.ai/rules/csharp.md`, `.ai/rules/specs.md` — C# style, spec patterns. +- `.ai/rules/typescript.md`, `.ai/rules/react.md`, `.ai/rules/components.md`, `.ai/rules/dialogs.md` — frontend. +- `.ai/skills/event-modeling/SKILL.md` — pre-code event vocabulary, flow, contracts, scenarios. + +## Workflow — phase gates; don't start the next until the current passes + +### Phase 1 — Plan +For new behavior, unclear event names/stream boundaries, or multi-slice flows, run the `event-modeling` skill first. Confirm Module/Feature/slice name + type, the behavior in one sentence, whether a UI surface is needed, and the event/read-model/scenario outline. Ask only when a real product/domain choice can't be answered from the repo. + +### Phase 2 — Backend +Write `///.cs` with all backend artifacts (declaration order per `general.md`). **Gate:** build clean in **Debug and Release** (zero errors/warnings — Debug validates `#if DEBUG` spec code and regenerates the TypeScript proxies; build Release with `-p:CratisProxiesOutputPath=` to skip re-running proxy generation). + +### Phase 3 — Specs +Mandatory for every slice type. Use the scenario family: `CommandScenario` (state change), `EventScenario` (constraints), `ReadModelScenario` (projections/reducers), `ReactorScenario` (reactors). Minimum: happy path with each appended event asserted; one spec per validator rule asserting **both** `ShouldNotBeSuccessful()` **and** `ShouldHaveValidationErrors()`; one spec per constraint. **Gate:** tests pass. + +### Phase 4 — Frontend (when needed) +Proxies now exist. Build React components from the generated proxies (`react.md`/`components.md`/`dialogs.md`); register in the composition page; wire routing. **Gate:** lint, conditional test, build — all clean. Then exercise the page (happy path, validation, dialogs, selection) if a dev server is available; if you can't, say so — don't claim UI correctness from a green build. + +## Hard rules (the silent-failure ones) + +- All backend artifacts in one `.cs`; namespace mirrors the path; layout per `general.md` (no `Features/` wrapper; `` optional). +- `Handle()` returns the event/result directly (no `Task.FromResult` without `await`); validation in `CommandValidator`/`ConceptValidator`/`Provide()`; **never throw for normal business rejection** — return `ValidationResult`/`Result<,>`. +- Model-bound projections default; **never `.AutoMap()`**; reducers only as a last resort with justification. +- Events: no arguments on `[EventType]`, non-nullable, past tense, ``, never carry the event-source id. +- `[OnceOnly]` on non-idempotent reactor side effects; reactors return side-effect events or use `ICommandPipeline` (never `IEventLog`). +- Specs `#if DEBUG`, command aliased, per-test unique values. +- Frontend via `withViewModel` + Arc proxy hooks + Cratis Components; never edit generated proxies; never import `Dialog` from `primereact/dialog`. + +## Output + +Report files created/modified (paths), each gate result, anything you couldn't verify (e.g. UI without a dev server), and any open question to resolve before merge. diff --git a/.ai/agents/spec-writer.md b/.ai/agents/spec-writer.md new file mode 100644 index 0000000..7ac2e29 --- /dev/null +++ b/.ai/agents/spec-writer.md @@ -0,0 +1,143 @@ +--- +name: Spec Writer +description: > + Specialist for writing C# specs (the in-process scenario family) and + TypeScript/React specs for vertical slices. Ensures every slice has + comprehensive behavior coverage following the project's BDD conventions. +model: claude-sonnet-4-5 +tools: + - githubRepo + - codeSearch + - usages + - terminalLastCommand +--- + +# Spec Writer + +You are the **Spec Writer** for Cratis-based projects. +Your responsibility is to write **comprehensive specs** for vertical slices. + +Always read and follow the canonical rules in `.ai/rules/`: +- `specs.md` — folder structure, naming, BDD philosophy +- `specs.csharp.md` — the in-process scenario family +- `frontend-testing.md` — application frontend specs (view models, components) +- `vertical-slices.md` — what each artifact promises (the contract under spec) + +--- + +## Inputs you expect + +- Feature name, slice name, and slice type (specs are **mandatory for every slice type**) +- The complete slice file (`.cs`) so you understand what behaviors to specify +- Any business rules or constraints that must be validated +- The namespace root (read from existing source files) + +--- + +## C# specs — lead with the scenario family + +Prefer the four in-process scenario helpers over out-of-process Chronicle host specs: + +| Tool | Use for | +|---|---| +| `CommandScenario` | **State Change** — runs authorization + validators + `Provide()` + `Handle()` + appended events | +| `EventScenario` | constraint violations, raw append/sequencing semantics | +| `ReadModelScenario` | **State View** — projection/reducer state from a sequence of events | +| `ReactorScenario` | **Automation / Translation** — reactor invocation + side effects | + +Reserve out-of-process integration specs for host/transport/infra boundaries the scenario helpers can't exercise. + +### Placement & wrapping + +Specs live in the slice folder; **every spec file is wrapped in `#if DEBUG … #endif`**: + +``` +// +├── .cs +└── when_/ + ├── and_.cs + └── and_.cs +``` + +### Example — `CommandScenario` + +```csharp +#if DEBUG +namespace MyApp.Projects.Registration.when_registering_a_project; + +public class and_all_information_is_valid : Specification +{ + readonly CommandScenario _scenario = new(); + readonly ProjectId _id = ProjectId.New(); + CommandResult _result; + + async Task Because() => _result = await _scenario.Execute(new RegisterProject(_id, "Acme")); + + [Fact] void should_succeed() => _result.ShouldBeSuccessful(); + [Fact] async Task should_have_appended_registered_event() => + await _scenario.ShouldHaveAppendedEvent(_id, e => e.Name == "Acme"); +} +#endif +``` + +(`CommandScenario` event assertions are extension methods keyed by command + event type — `await _scenario.ShouldHaveAppendedEvent(eventSourceId[, predicate])`; seed prior state through `_scenario.Services`, not a `Given` builder.) + +### What to specify + +1. **Happy path** — succeeds, correct event(s) appended. +2. **Each validation failure** — assert **both** `ShouldNotBeSuccessful()` and `ShouldHaveValidationErrors()`. Never assert on message strings. +3. **Business-rule violations** — each `Result<,>` rejection / DCB condition. +4. **Constraint violations** — `ShouldHaveConstraintViolationFor(name)` via `EventScenario`. +5. **Authorization** — `ShouldNotBeAuthorized()` (an unauthorized result has no validation errors). + +### Naming + +- Folder: `when_` — the only place `when` appears. +- File: `and_.cs` / `with_.cs` — never embed `when`. +- Method: `should_` (underscores in C#). + +--- + +## TypeScript / React specs + +Write BDD specs for non-trivial view-model/helper logic; don't spec generated proxies, framework internals, or trivial pass-through components. Use Chai's `.should` fluent interface (never `expect()`). + +### Placement & naming + +``` +// +├── .ts +└── for_/ + └── when_/ + └── and_.ts +``` + +**`it()` descriptions use spaces, not underscores** (TS specs read as human sentences) and start with "should". + +```typescript +import { describe, it, beforeEach } from 'vitest'; + +describe('when filtering active projects', () => { + let result: Project[]; + + beforeEach(() => { result = viewModel.filteredProjects; }); + + it('should keep only active projects', () => { + result.should.have.lengthOf(2); + }); +}); +``` + +--- + +## Completion checklist + +Before handing back: + +- [ ] Specs cover all meaningful outcomes of the slice's behavior +- [ ] Happy-path spec exists +- [ ] Each validation/business-rule/constraint failure has a spec (unhappy paths assert both not-successful and has-validation-errors) +- [ ] C# spec files wrapped in `#if DEBUG`; folder follows `when_/` +- [ ] TypeScript `it()` descriptions use spaces and start with "should"; `.should` assertions only +- [ ] Specs pass (C# and, when written, frontend) +- [ ] No spec for a simple property getter or constructor-parameter passthrough diff --git a/.ai/hooks/agent-stop.md b/.ai/hooks/agent-stop.md new file mode 100644 index 0000000..bcf1605 --- /dev/null +++ b/.ai/hooks/agent-stop.md @@ -0,0 +1,49 @@ +--- +lifecycle: session-stop +--- + +# Agent Stop — Build, Specs, and Corpus Validation + +> **This is lifecycle guidance, not a wired tool hook.** Markdown is not a hook format for Copilot or Claude Code. To *enforce* it, wire it per tool to run the repo's build/test command — Claude Code: a `Stop` hook in `.claude/settings.json`; GitHub Copilot: a `sessionEnd` entry in a `.github/hooks/*.json` file. The steps below are what that hook (or the agent) should do. + +When the agent finishes a session, verify the work against **fresh signals** before stopping — never against self-assessment. Pick the path that matches the repository. + +## Pick the path for this repository + +- **AI corpus repo** — the changes are only under `.ai/`, `.github/`, or `.claude/` and there is no .NET solution or frontend to build (e.g. this `cratis/AI` repo). Run the AI-setup validator instead of a code build: + ``` + .ai/hooks/scripts/validate-ai-setup.sh + ``` + Stop only when it passes (symlinks/adapters healthy, frontmatter present, no broken cross-links). Skip the application gates below. + +- **Application repo** — there is a .NET solution and/or a frontend. Run the application gates below. + +## Application gates + +1. **Clean** from repository root: + ``` + dotnet clean + ``` +2. **Build Debug** from repository root — validates `#if DEBUG` spec code and regenerates the TypeScript proxies: + ``` + dotnet build + ``` +3. **Build Release** from repository root — build-only check; skip re-running proxy generation: + ``` + dotnet build -c Release -p:CratisProxiesOutputPath= + ``` +4. **Run specs/tests for every affected project** — use the project's test command; if you cannot isolate the affected scope, run the repository-level test command. +5. **Frontend** (when frontend files changed) — run lint, the type/build check, and frontend tests. + +## If any gate fails + +- Report the full output. +- Fix all errors, warnings, and failing specs before considering the session complete. +- Re-run the gate that failed and confirm it passes *this time*. + +## Rules + +- A session is not complete until both Debug and Release builds exit `0` with **zero** warnings, and the affected specs/tests exit `0`. +- Treat Release-only warnings (nullable annotations, analyzer findings) as errors — fix them. +- **Never** use `/clp:ErrorsOnly` or any flag that suppresses warning output — hidden warnings are warnings that never get fixed. +- A green build is not behavioral correctness — exercise the affected behavior (specs, or the running UI) and state plainly anything you could not verify. diff --git a/.ai/hooks/pre-commit.md b/.ai/hooks/pre-commit.md new file mode 100644 index 0000000..7eab867 --- /dev/null +++ b/.ai/hooks/pre-commit.md @@ -0,0 +1,50 @@ +--- +lifecycle: pre-commit +--- + +# Pre-commit — Run Specs + +> **This is lifecycle guidance, not a wired tool hook.** To *enforce* it, wire it per tool — Claude Code: a `PreToolUse` hook in `.claude/settings.json` with a matcher on `Bash` (or your terminal tool) gating `git commit` (and its rtk-rewritten `rtk git commit` form — see [rtk](../rules/rtk.md)); GitHub Copilot: a hook in a `.github/hooks/*.json` file. The steps below are what that hook (or the agent) should do. + +Before executing any `git commit` terminal command, automatically run the specs for every affected project to ensure nothing is broken before changes are recorded in version control. + +## When this hook applies + +This guidance applies before any `git commit` (including `git commit -m`, `git commit --amend`, etc.) — and equally to the rtk-rewritten `rtk git commit ...`, since the rtk hook prefixes terminal commands. If the command being run is not a commit, do nothing and proceed. + +## Steps + +1. **Detect a git commit command** — inspect the terminal command string for `git commit ...` (treat the rtk-rewritten `rtk git commit ...` as the same thing, since the rtk hook prefixes commands). If it does not match, skip all steps below and proceed normally. + +2. **Identify affected projects** from the staged changes: + ``` + git diff --name-only --cached + ``` + Collect unique project roots using the same rules as the agent-stop guidance: + - `.cs` files → walk up to the nearest `.csproj`. + - `.ts` / `.tsx` files → walk up to the nearest `package.json` with a `"test"` script. + +3. **Run specs for each affected .NET project**: + ``` + dotnet test --no-build + ``` + If the specs project cannot be identified, run `dotnet test` from the repository root. + +4. **Run specs for each affected TypeScript project**: + ``` + yarn test + ``` + Run from the package root that owns the changed files. + +5. **If any spec fails**: + - Report the full test output including which specs failed and why. + - **Do not proceed with the `git commit`** — block the tool call and fix the failures first. + - Re-run the relevant specs to confirm they pass before retrying the commit. + +6. **If all specs pass** — proceed with the `git commit` as originally requested. + +## Rules + +- Never skip the spec run before a commit, even for "minor" or "documentation-only" changes. +- A commit must not be made while any spec is failing. +- If a spec was already failing before the current changes (pre-existing failure), report it but do not block the commit — note the pre-existing failure clearly in the session output. diff --git a/.ai/hooks/scripts/validate-ai-setup.sh b/.ai/hooks/scripts/validate-ai-setup.sh new file mode 100755 index 0000000..3a9915c --- /dev/null +++ b/.ai/hooks/scripts/validate-ai-setup.sh @@ -0,0 +1,147 @@ +#!/usr/bin/env bash +# Validates the AI corpus in this repo: structural integrity (frontmatter) and adapter +# health for each tool's actual conventions — +# Copilot: .github/copilot-instructions.md, .github/instructions/.instructions.md (applyTo), +# .github/agents/.agent.md, .github/prompts (folder), .github/skills (folder) +# Claude: .claude/CLAUDE.md, .claude/rules/.md (paths), .claude/agents (folder), +# .claude/commands/.md, .claude/skills (folder) [hooks live in .claude/settings.json] +# Codex: AGENTS.md, .agents/skills (folder) +# plus a set of content drift guards. Structural/adapter/Codex checks are FATAL; drift guards are +# WARNINGS. Portable: needs only bash + grep + sed (no ripgrep). Run from anywhere; it cd's to root. +set -euo pipefail + +root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../.." && pwd)" +cd "$root" + +failed=0 +fail() { printf 'ai-corpus FAIL: %s\n' "$1" >&2; failed=1; } +warn() { printf 'ai-corpus warn: %s\n' "$1" >&2; } + +# ── Structural: required paths ── +for p in .ai .ai/rules .ai/agents .ai/prompts .ai/skills .ai/hooks; do + [[ -e "$p" ]] || fail "missing required path: $p" +done + +# ── Structural: rules (general.md is the intentionally frontmatter-less root) ── +for rule in .ai/rules/*.md; do + [[ -e "$rule" ]] || continue + [[ "$rule" == ".ai/rules/general.md" ]] && continue + if [[ "$(sed -n '1p' "$rule")" != "---" ]]; then fail "$rule: missing YAML frontmatter"; continue; fi + grep -Eq '^applyTo:' "$rule" || fail "$rule: frontmatter must include applyTo" + # profile (optional) must be application|framework|universal when present (absent = universal) + if grep -Eq '^profile:' "$rule" && ! grep -Eq '^profile:[[:space:]]*(application|framework|universal)[[:space:]]*$' "$rule"; then + fail "$rule: profile must be application|framework|universal" + fi +done + +# ── Structural: skills (name + description) ── +for skill in .ai/skills/*/SKILL.md; do + [[ -e "$skill" ]] || continue + if [[ "$(sed -n '1p' "$skill")" != "---" ]]; then fail "$skill: missing YAML frontmatter"; continue; fi + grep -Eq '^name:' "$skill" || fail "$skill: frontmatter must include name" + grep -Eq '^description:' "$skill" || fail "$skill: frontmatter must include description" +done + +# ── Structural: prompts must be *.prompt.md with frontmatter + description ── +for p in .ai/prompts/*.md; do + [[ -e "$p" ]] || continue + case "$p" in *.prompt.md) ;; *) fail "$p: prompt must use the .prompt.md suffix (no plain-.md stubs)"; continue;; esac + if [[ "$(sed -n '1p' "$p")" != "---" ]]; then fail "$p: missing frontmatter"; continue; fi + grep -Eq '^description:' "$p" || fail "$p: prompt missing description frontmatter" +done + +# ── Adapter integrity ── +# GitHub: .github/instructions is a single FOLDER symlink → ../.ai/rules, so rules are +# maintained in one place (no per-file adapter). Caveat: Copilot's applyTo discovery expects +# the .instructions.md suffix, which a folder symlink does not provide — documented in +# managing-ai-rules.md. The structural check here is that the folder symlink resolves to .ai/rules. +# Claude: every rule still resolves through a per-file adapter (.claude/rules/.md), which +# may be a symlink OR a path-reference file — both encode the same relative target; the check is +# that it *resolves* to the right rule, not its file type. ── +adapter_target() { # prints the relative target encoded by an adapter (symlink target or file body) + local p="$1" + if [[ -L "$p" ]]; then readlink "$p"; else cat "$p"; fi +} +if [[ ! -L .github/instructions ]]; then fail ".github/instructions: expected folder symlink → ../.ai/rules" +elif [[ "$(readlink .github/instructions)" != "../.ai/rules" ]]; then fail ".github/instructions: expected target '../.ai/rules'" +elif [[ ! -d .github/instructions ]]; then fail ".github/instructions: symlink does not resolve to .ai/rules"; fi +for rule in .ai/rules/*.md; do + [[ -e "$rule" ]] || continue + name="$(basename "$rule" .md)"; [[ "$name" == general ]] && continue + expected="../../.ai/rules/$name.md" + cl=".claude/rules/$name.md" + if [[ ! -e "$cl" ]]; then fail "$cl: missing Claude rule adapter" + elif [[ "$(adapter_target "$cl")" != "$expected" ]]; then fail "$cl: expected target '$expected'"; fi +done + +# ── Folder-level symlinks each tool consumes directly (same convention both sides) ── +# Copilot: prompts (.github/prompts/*.prompt.md), skills (.github/skills//SKILL.md). +# Claude: agents (.claude/agents/.md), skills (.claude/skills//SKILL.md). +for link in .github/prompts .github/skills .claude/agents .claude/skills; do + if [[ ! -e "$link" ]]; then fail "missing link path: $link" + elif [[ ! -L "$link" ]]; then fail "expected symlink but found regular path: $link"; fi +done + +# ── Copilot custom-agent adapters: Copilot requires .github/agents/.agent.md +# (the .agent.md suffix); the Claude side uses the .claude/agents folder symlink above. ── +for agent in .ai/agents/*.md; do + [[ -e "$agent" ]] || continue + name="$(basename "$agent" .md)" + gh=".github/agents/$name.agent.md"; expected="../../.ai/agents/$name.md" + if [[ ! -e "$gh" ]]; then fail "$gh: missing Copilot agent adapter (.agent.md suffix required)" + elif [[ "$(adapter_target "$gh")" != "$expected" ]]; then fail "$gh: expected target '$expected'"; fi +done + +# ── Claude slash-command adapters: Claude reads commands from .claude/commands/.md +# (not .claude/prompts); the Copilot side uses the .github/prompts folder symlink. ── +for prompt in .ai/prompts/*.prompt.md; do + [[ -e "$prompt" ]] || continue + name="$(basename "$prompt" .prompt.md)" + cl=".claude/commands/$name.md"; expected="../../.ai/prompts/$name.prompt.md" + if [[ ! -e "$cl" ]]; then fail "$cl: missing Claude command adapter" + elif [[ "$(adapter_target "$cl")" != "$expected" ]]; then fail "$cl: expected target '$expected'"; fi +done + +# ── General-rule root adapters ── +for f in .github/copilot-instructions.md .claude/CLAUDE.md; do + [[ -e "$f" ]] || fail "missing general-rule adapter: $f" +done + +# ── Codex adapters (we claim Codex support) ── +[[ -L AGENTS.md || -f AGENTS.md ]] || fail "AGENTS.md: missing Codex root adapter (-> .ai/rules/general.md)" +[[ -L .agents/skills ]] || fail ".agents/skills: missing Codex skills adapter (-> ../.ai/skills)" + +# ── Hook files ── +for hook in .ai/hooks/pre-commit.md .ai/hooks/agent-stop.md; do + [[ -e "$hook" ]] || fail "missing hook file: $hook" +done + +# ── Content drift guards (WARN only — heuristic, never block on a false positive) ── +if grep -rnE '\.AutoMap\(\)' .ai/rules .ai/skills .ai/agents 2>/dev/null \ + | grep -vE ':[0-9]+:[[:space:]]*#' \ + | grep -vE '(NoAutoMap|never|not |n.t |default|only|disabl)' | grep -q .; then + warn "possible stale .AutoMap() guidance — AutoMap is on by default; call .From<>() directly" +fi +if grep -rnE '\.instructions\.md' .ai/rules .ai/skills .ai/agents .ai/prompts .ai/hooks 2>/dev/null \ + | grep -vE 'managing-ai-rules|validate-ai-setup' | grep -q .; then + warn "'.instructions.md' cross-link leaked into canonical docs — use ./.md" +fi +if grep -rnE '\[EventType\("|\[EventType\(name:|\[EventType\(id:' .ai/rules .ai/skills .ai/agents 2>/dev/null \ + | grep -viE 'never|no arg|not allowed' | grep -q .; then + warn "stale [EventType] argument guidance — new events take no arguments (generation: only for migrations)" +fi +if grep -rnE 'Features//|Features//|Source/Core/Features' .ai/rules .ai/skills .ai/agents .ai/prompts 2>/dev/null | grep -q .; then + warn "possible retired top-level Features/ layout — use /// (Module optional)" +fi +if grep -rnE 'RouteAttribute|\[Route\(' .ai/rules .ai/skills .ai/agents 2>/dev/null | grep -q .; then + warn "stale [Route] for model-bound queries — use [Path]" +fi +if grep -rniE 'custom exception to signal|framework converts it to (an? )?(error|failed)' .ai/rules .ai/skills .ai/prompts 2>/dev/null | grep -q .; then + warn "stale business-rule guidance — return ValidationResult/Result<,>, not a thrown exception" +fi + +if [[ "$failed" -ne 0 ]]; then + printf 'AI corpus validation FAILED.\n' >&2 + exit 1 +fi +printf 'AI corpus validation passed.\n' diff --git a/.ai/prompts/add-business-rule.prompt.md b/.ai/prompts/add-business-rule.prompt.md new file mode 100644 index 0000000..d9120c2 --- /dev/null +++ b/.ai/prompts/add-business-rule.prompt.md @@ -0,0 +1,22 @@ +--- +agent: agent +description: Add a business rule or event-store constraint to an existing command. +--- + +# Add a Business Rule or Constraint + +Enforce a new rule on an existing command. Invoke the **add-business-rule** skill and follow `.ai/rules/vertical-slices.md` (the decision matrix). + +## Confirm first + +- **Command** to constrain, and the **rule** in one sentence. + +## Pick the mechanism + +- Reusable value invariant → `ConceptValidator`. +- Command-input / cross-field / pre-handler rule → `CommandValidator`. +- Handler needs fetched data first → `Provide()`. +- State rule that must hold **under concurrency** → inject the read model into `Handle()`, return `Result`. +- Uniqueness → `[Unique]` / `IConstraint`. + +> **Never throw for normal business rejection** — a throw is HTTP 500, not a validation error. Recoverable rejection is a `ValidationResult` / `Result<,>`. Add a spec for the failure case (assert both not-successful and has-validation-errors). The skill carries the detail; don't duplicate it here. diff --git a/.ai/prompts/add-concept.prompt.md b/.ai/prompts/add-concept.prompt.md new file mode 100644 index 0000000..a9ea63b --- /dev/null +++ b/.ai/prompts/add-concept.prompt.md @@ -0,0 +1,17 @@ +--- +agent: agent +description: Create a strongly-typed Concept for a primitive domain value or an event-source identity. +--- + +# Add a Concept + +Create a strongly-typed concept to replace a raw primitive. Invoke the **add-concept** skill and follow `.ai/rules/concepts.md`. + +## Confirm first + +- **Concept name** (e.g. `ProjectId`, `AuthorName`) +- **Underlying primitive** (`Guid`, `string`, `int`, …) +- **Value or identity?** — a **value** concept derives from `ConceptAs`; an **event-source identity** derives from `EventSourceId` (never `ConceptAs` — the base already supplies the `EventSourceId`/`T`/`string` conversions). +- **Placement** — the folder that owns it (slice → feature → `Common/`); never a dedicated `Concepts/` folder. + +The skill carries the templates and checklist; don't duplicate them here. diff --git a/.ai/prompts/add-ef-migration.prompt.md b/.ai/prompts/add-ef-migration.prompt.md new file mode 100644 index 0000000..1929ecd --- /dev/null +++ b/.ai/prompts/add-ef-migration.prompt.md @@ -0,0 +1,24 @@ +--- +agent: agent +description: Add or update an Entity Framework Core DbContext, table column, or hand-written migration. +--- + +# Add an EF Core Migration + +Make a database schema change via EF Core. Invoke the **add-ef-migration** skill and follow `.ai/rules/efcore.md` (+ `.ai/rules/efcore.specs.md`). + +> Applies only to projects that use EF Core. + +## Confirm first + +- **Change type** (new table / column / relationship / rename), the **entity**, and its **feature DbContext**. + +## Non-negotiables + +- Migrations are **hand-written** in the `Database` project — never `dotnet ef migrations add` / `database update`. +- Version-named files `v{major}_{minor}_{patch}.cs` in a folder matching the entity category; namespace matches the folder. +- Use the cross-database column helpers (`StringColumn`, `GuidColumn`, `NumberColumn`, `DateTimeOffsetColumn`) and `WellKnownTables` constants — never raw `table.Column()` or magic strings. +- Never hardcode a provider (`UseSqlite`/`UseNpgsql`) — use `UseDatabaseFromConnectionString`. +- Never mutate state directly through a DbContext — writes flow through Chronicle events. + +The skill carries the step-by-step detail; don't duplicate it here. diff --git a/.ai/prompts/add-projection.prompt.md b/.ai/prompts/add-projection.prompt.md new file mode 100644 index 0000000..2cbfacd --- /dev/null +++ b/.ai/prompts/add-projection.prompt.md @@ -0,0 +1,20 @@ +--- +agent: agent +description: Add a Chronicle projection to an existing read model slice. +--- + +# Add a Projection + +Add a Chronicle projection that populates a read model from events. Invoke the **add-projection** skill and follow `.ai/rules/vertical-slices.md` (projections). For reactors, use the **add-reactor** prompt instead. + +## Confirm first + +- **Events to project from** and the **read model** shape. + +## Key rules + +- Default to **model-bound attributes** on the read model (`[FromEvent]` class-level, `[SetFrom]`, `[Key]`, `[ChildrenFrom]`, `[RemovedWith]`); drop to fluent `IProjectionFor` only for joins/transforms; reducer for "current state + event → next state". +- **AutoMap is on by default — never call `.AutoMap()`** (matching names map automatically). +- Projections consume Chronicle **events**, never other read models. + +Run a clean build afterward. The skill carries the detail; don't duplicate it here. diff --git a/.ai/prompts/add-reactor.prompt.md b/.ai/prompts/add-reactor.prompt.md new file mode 100644 index 0000000..5387657 --- /dev/null +++ b/.ai/prompts/add-reactor.prompt.md @@ -0,0 +1,22 @@ +--- +agent: agent +description: Add a Chronicle reactor (automation or translation) that reacts to events and triggers side effects. +--- + +# Add a Reactor + +Add a reactor that observes events and produces side effects. Invoke the **add-reactor** skill and follow `.ai/rules/reactors.md`. + +## Confirm first + +- **Events to react to**, the **side effect / automation**, and whether it's `Automation` (side effects) or `Translation` (triggers commands in another slice). + +## Key rules + +- `IReactor` is a marker interface; dispatch is by the first parameter type; the method name is descriptive only. +- Reactors are **idempotent** and **stateless**; use event data directly (don't query the read model back). +- To change state elsewhere, return side-effect events or inject `ICommandPipeline` — **never** `IEventLog`. +- `[OnceOnly]` on any non-idempotent side effect (emails, payments, external writes). +- Test with `ReactorScenario`. + +The skill carries the detail; don't duplicate it here. diff --git a/.ai/prompts/add-reducer.prompt.md b/.ai/prompts/add-reducer.prompt.md new file mode 100644 index 0000000..02dccec --- /dev/null +++ b/.ai/prompts/add-reducer.prompt.md @@ -0,0 +1,20 @@ +--- +agent: agent +description: Add a Chronicle reducer to a read model when model-bound and fluent projections cannot express the state transition. +--- + +# Add a Reducer + +Add an `IReducerFor` reducer — the last-resort escape hatch for a "current state + event → next state" transition that model-bound projection attributes and fluent `IProjectionFor` cannot express. Invoke the **add-reducer** skill and follow `.ai/rules/vertical-slices.md`. For ordinary projections, use the **add-projection** prompt instead. + +## Confirm first + +- **Why a reducer** (which model-bound / fluent approach was ruled out) and the **events** + **read model** shape. + +## Key rules + +- Reducers are the **last resort** — exhaust model-bound attributes and fluent `IProjectionFor` first. +- Handle the **nullable current** state (the first event has no prior state). +- Keep reducers passive and deterministic — no side effects, no reading other read models. + +Run a clean build afterward. The skill carries the detail; don't duplicate it here. diff --git a/.ai/prompts/audit-hooks.prompt.md b/.ai/prompts/audit-hooks.prompt.md new file mode 100644 index 0000000..af4940f --- /dev/null +++ b/.ai/prompts/audit-hooks.prompt.md @@ -0,0 +1,15 @@ +--- +agent: agent +description: Audit hook files for correctness, portability, and enforcement coverage. +--- + +# Audit Hooks + +Review `.ai/hooks/` and report whether hooks are: + +- enforcing the intended policy +- portable across environments +- aligned with canonical source rules +- using bash-first commands for script execution + +Focus on gaps, risks, and missing checks. If improvements are obvious and low-risk, propose exact edits. diff --git a/.ai/prompts/check-doc-drift.prompt.md b/.ai/prompts/check-doc-drift.prompt.md new file mode 100644 index 0000000..6e464e3 --- /dev/null +++ b/.ai/prompts/check-doc-drift.prompt.md @@ -0,0 +1,21 @@ +--- +agent: agent +description: Check for drift between AI assets and documentation inventory. +--- + +# Check Documentation Drift + +Check whether AI assets and docs are in sync: + +- `.ai/rules/` vs documented instruction inventory +- `.ai/skills/` vs documented skill inventory +- `.ai/agents/` vs documented agent roster +- `.ai/hooks/` vs architecture docs + +Report: + +1. Missing documentation entries. +2. Stale documentation entries. +3. Suggested updates by file. + +Apply updates only if asked. diff --git a/.ai/prompts/code-review.md b/.ai/prompts/code-review.md new file mode 100644 index 0000000..81d896b --- /dev/null +++ b/.ai/prompts/code-review.md @@ -0,0 +1,4 @@ +# Code Review Prompt + +Review the proposed change for correctness, maintainability, and security. +Focus on actionable findings and minimize false positives. diff --git a/.ai/prompts/new-feature.md b/.ai/prompts/new-feature.md new file mode 100644 index 0000000..a1951bf --- /dev/null +++ b/.ai/prompts/new-feature.md @@ -0,0 +1,4 @@ +# New Feature Prompt + +Implement the requested feature as a vertical slice with minimal, focused changes. +Add or update tests for behavior changes and validate build/test before completion. diff --git a/.ai/prompts/new-vertical-slice.prompt.md b/.ai/prompts/new-vertical-slice.prompt.md new file mode 100644 index 0000000..ed19328 --- /dev/null +++ b/.ai/prompts/new-vertical-slice.prompt.md @@ -0,0 +1,18 @@ +--- +agent: agent +description: Scaffold a complete vertical slice (backend + specs + frontend) for a Cratis-based project. +--- + +# New Vertical Slice + +Implement a complete **vertical slice** end-to-end. Invoke the **new-vertical-slice** skill and follow it exactly; for a full backend + specs + frontend slice you may hand the work to the **Slice Implementer** agent. + +## Confirm first + +- **Module / Feature** and **slice name** +- **Slice type** — `State Change` / `State View` / `Automation` / `Translation` +- **Behavior** in one sentence, plus the command/query properties and their concept types + +## How it runs + +Backend → build (Debug + Release) → specs (the in-process `*Scenario` family) → frontend → compose/route, with each quality gate green before the next phase. The authoritative rules are `.ai/rules/general.md` and `.ai/rules/vertical-slices.md`; the skill carries the step-by-step detail. Do not duplicate that detail here. diff --git a/.ai/prompts/review-pr.prompt.md b/.ai/prompts/review-pr.prompt.md new file mode 100644 index 0000000..7b2ff41 --- /dev/null +++ b/.ai/prompts/review-pr.prompt.md @@ -0,0 +1,35 @@ +--- +agent: agent +description: Review a pull request against all Cratis project standards and produce a structured review report. +--- + +# Review Pull Request + +Produce a structured review of a pull request against all Cratis standards. + +## Confirm first + +- **PR number or branch**, and the affected repos/projects. + +## Process + +1. **Gather context** — list and read every changed file; identify the slice type(s). +2. **Architecture & quality** — run the **Code Reviewer** agent (it checks `.ai/rules/` and folds in the performance pass). +3. **Security** — run the **Security Reviewer** agent. +4. **Spec coverage** — confirm each slice has specs (happy path + each failure); confirm tests pass if runnable. +5. **Docs** — confirm public-facing changes updated documentation. + +## Output + +``` +## Pull Request Review — # +### Summary — <2–3 sentences> +### Architecture & Quality — ✅ / ⚠️ / ❌ +### Security — ✅ / ⚠️ / ❌ +### Spec Coverage — ✅ / ⚠️ / ❌ +### Documentation — ✅ / ⚠️ / ❌ +### Overall — ✅ Approved / ⚠️ Approved with comments / ❌ Changes requested +**Blocking** (violates a MUST): 1. … **Suggestions**: 1. … +``` + +Be specific — file, line, and corrected code for every blocking issue. diff --git a/.ai/prompts/review-skill.prompt.md b/.ai/prompts/review-skill.prompt.md new file mode 100644 index 0000000..82cb681 --- /dev/null +++ b/.ai/prompts/review-skill.prompt.md @@ -0,0 +1,16 @@ +--- +agent: agent +description: Review one skill for clarity, trigger quality, and maintainability. +--- + +# Review Skill + +Review a specific skill folder under `.ai/skills/` for: + +- trigger quality in the description +- correctness of workflow steps +- overlap with existing skills +- missing references or checklists +- opportunities to split large files + +Return findings first, then proposed edits. diff --git a/.ai/prompts/scaffold-feature.prompt.md b/.ai/prompts/scaffold-feature.prompt.md new file mode 100644 index 0000000..cac24c6 --- /dev/null +++ b/.ai/prompts/scaffold-feature.prompt.md @@ -0,0 +1,16 @@ +--- +agent: agent +description: Scaffold a new feature — folder structure, composition page, routing, and navigation. +--- + +# Scaffold a Feature + +Scaffold a brand-new feature folder (composition page, routing, navigation) — ready for slices. Invoke the **scaffold-feature** skill and follow it exactly. + +## Confirm first + +- **Feature name** — PascalCase (e.g. `Projects`, `Invoices`) +- **Route path** — kebab-case (e.g. `/projects`) +- **Navigation label** and **icon** (from `react-icons/md`) + +The feature folder lives directly under the app source root (or under an optional `/`) — there is no top-level `Features/` wrapper. After scaffolding, add behavior with the **new-vertical-slice** prompt/skill. The skill carries the step-by-step detail; don't duplicate it here. diff --git a/.ai/prompts/ship-changes.prompt.md b/.ai/prompts/ship-changes.prompt.md new file mode 100644 index 0000000..effc321 --- /dev/null +++ b/.ai/prompts/ship-changes.prompt.md @@ -0,0 +1,20 @@ +--- +agent: agent +description: > + Ship local changes: create a branch, make logical commits, push, open and + label a PR with a proper description, merge it, close the issues it resolves, + and delete the branch. +--- + +# Ship Changes + +Ship the current local modifications to `main` through the standard +branch → commits → PR → merge → close issues → cleanup workflow. + +## Inputs + +- **What changed** — brief description of the work (used for branch name and PR title) +- **Label** — `patch`, `minor`, or `major`, or omit entirely if no label should be applied +- **Related issue** — optional GitHub issue number; if unknown, search first. Referenced issues that the change fully resolves are closed after the merge + +Load and follow the full instructions from the `ship-changes` skill. diff --git a/.ai/prompts/verify-ai-setup.prompt.md b/.ai/prompts/verify-ai-setup.prompt.md new file mode 100644 index 0000000..f854402 --- /dev/null +++ b/.ai/prompts/verify-ai-setup.prompt.md @@ -0,0 +1,19 @@ +--- +agent: agent +description: Validate AI framework setup integrity, canonical source conventions, and symlink health. +--- + +# Verify AI Setup + +Validate the repository AI setup by running: + +```bash +bash .ai/hooks/scripts/validate-ai-setup.sh +``` + +If anything fails: + +1. List every failure with the exact file path. +2. Explain whether the issue is canonical-source drift, missing metadata, or broken links. +3. Propose the smallest safe fix. +4. Apply fixes if requested. diff --git a/.ai/prompts/write-documentation.prompt.md b/.ai/prompts/write-documentation.prompt.md new file mode 100644 index 0000000..913356e --- /dev/null +++ b/.ai/prompts/write-documentation.prompt.md @@ -0,0 +1,21 @@ +--- +agent: agent +description: "Write documentation following the Diátaxis framework." +--- + +# Write Documentation + +Write documentation for a feature, component, or concept. Invoke the **write-documentation** skill and follow `.ai/rules/documentation.md`. + +## Confirm first + +- **Subject**, **audience**, and the **Diátaxis type** — exactly one: + - **Tutorial** — guided lesson for newcomers + - **How-to guide** — recipe for a specific task + - **Reference** — exhaustive, terse technical description + - **Explanation** — concepts, trade-offs, architecture (the *why*) +- The source files to document. + +## Workflow + +Clarify type/audience/scope → propose an outline → write. Active voice, present tense, second person; lead with *why*; complete and correct code examples; Mermaid diagrams for non-trivial concepts; descriptive link text; relative links that resolve. Update `toc.yml` and run the documentation verification before considering it done. The skill carries the per-page detail; don't duplicate it here. diff --git a/.ai/prompts/write-specs.prompt.md b/.ai/prompts/write-specs.prompt.md new file mode 100644 index 0000000..27bcecc --- /dev/null +++ b/.ai/prompts/write-specs.prompt.md @@ -0,0 +1,22 @@ +--- +agent: agent +description: Write comprehensive BDD specs for an existing vertical slice command, query, projection, or reactor. +--- + +# Write Specs + +Write **comprehensive specs** for an existing slice. Invoke the **write-specs** skill (and `write-specs-events` / `write-specs-readmodels` for constraints and projections); follow `.ai/rules/specs.md` and `.ai/rules/specs.csharp.md`. + +## What to provide + +The slice file (`.cs`) to cover. + +## Coverage (every slice type) + +Lead with the in-process scenario family — `CommandScenario` (state change), `EventScenario` (constraints), `ReadModelScenario` (projections/reducers), `ReactorScenario` (reactors). Reserve out-of-process Chronicle integration specs for host/transport boundaries. + +- Happy path with each appended event asserted. +- One spec per validator rule, asserting **both** `ShouldNotBeSuccessful()` and `ShouldHaveValidationErrors()`. +- One spec per constraint (`ShouldHaveConstraintViolationFor(name)`); authorization via `ShouldNotBeAuthorized()`. + +Spec files are wrapped in `#if DEBUG`. Run the specs and fix failures before completing. The skill carries the detail; don't duplicate it here. diff --git a/.ai/rules/code-quality.csharp.md b/.ai/rules/code-quality.csharp.md new file mode 100644 index 0000000..213e7e2 --- /dev/null +++ b/.ai/rules/code-quality.csharp.md @@ -0,0 +1,91 @@ +--- +applyTo: "**/*.cs" +paths: + - "**/*.cs" +--- + +# Code Quality — C# + +C#-specific applications of the general [Code Quality](./code-quality.md) principles. + +## Composition over Inheritance + +Use constructor injection to compose behavior. Primary constructors make this natural in modern C# — the type's dependencies are visible at a glance and can be substituted in tests. + +```csharp +// ❌ Inheritance — child is tightly coupled to parent internals +public class ReportExporter : BaseExporter +{ + public override void Export(Report report) { ... } +} + +// ✅ Composition — behavior is injected and interchangeable +public class ReportExporter(IExportStrategy strategy) +{ + public void Export(Report report) => strategy.Execute(report); +} +``` + +**Rules:** +- Never extend a concrete class to add or change behavior — inject a collaborator instead. +- Use interfaces and `ConceptAs` record wrappers rather than inheritance chains. +- Inheritance is acceptable only for framework integration points with a well-defined extension mechanism (e.g. `Specification`, `Migration`, `AggregateRoot`). + +## Open/Closed Principle + +The framework's `IInstancesOf` mechanism makes the open/closed pattern effortless — adding a new implementation is all it takes to extend behavior. Use it instead of growing `switch` statements. + +```csharp +// ❌ Modified every time a new format is added +public class ReportFormatter +{ + public string Format(Report report, string formatType) + { + if (formatType == "csv") return FormatAsCsv(report); + if (formatType == "json") return FormatAsJson(report); + throw new UnknownFormat(formatType); + } +} + +// ✅ New formats added by implementing the interface — no existing code changes +public interface IReportFormatter +{ + string Format(Report report); +} + +public class CsvReportFormatter : IReportFormatter { ... } +public class JsonReportFormatter : IReportFormatter { ... } +``` + +**Rules:** +- Prefer strategy interfaces over `switch`/`if-else` chains that grow over time. +- Use `IInstancesOf` to discover all implementations by convention — no manual registration needed. +- Design public APIs as contracts (interfaces/records) rather than concrete implementations. + +## Separation of Concerns + +The Chronicle + Arc stack has clear layer boundaries. Violating them creates coupling that is hard to undo. + +**Rules:** +- Domain types must not reference EF Core, MongoDB, or HTTP concepts directly. +- Command handlers express intent in domain terms — they delegate persistence and I/O to injected collaborators. +- Projections build read models; they must not trigger commands or produce side effects. +- Reactors handle side effects; they must not directly read or write the event log. + +## Low Coupling + +**Rules:** +- Depend on abstractions (interfaces, records), not on concrete implementations. +- Use constructor injection — it makes dependencies explicit and testable. +- Avoid reaching through an object to call methods on its dependencies (`a.B.C.Do()` is a sign of tight coupling). +- Limit constructor dependencies to four or five — more is a signal the type is doing too much. +- Never reference types from unrelated features directly; go through a shared contract or event instead. + +## Cross-Cutting Concerns + +**Rules:** +- Never write logging statements directly inside command handlers, projections, or domain types. Use the `[LoggerMessage]` pattern in a co-located `*Logging.cs` partial class. +- Never perform authorization checks inside domain logic — express them as attributes or middleware applied at the boundary. +- Never duplicate error-handling or retry logic across handlers — centralize it in a pipeline or middleware. +- Use `ICommandPipeline`, middleware, and decorators to apply cross-cutting concerns at the infrastructure layer so that domain code remains unaware of them. +- When you notice the same infrastructural pattern appearing in two or more places (logging a specific event, catching a specific exception, checking a specific condition), extract it into a shared cross-cutting mechanism rather than duplicating it. diff --git a/.ai/rules/code-quality.md b/.ai/rules/code-quality.md new file mode 100644 index 0000000..546ca8a --- /dev/null +++ b/.ai/rules/code-quality.md @@ -0,0 +1,82 @@ +--- +applyTo: "**/*" +--- + +# Code Quality + +Good code is not just code that works — it is code that can be understood, changed, and extended safely. The principles below are the foundation for writing code that remains maintainable as the system grows. They are not abstract ideals; each one has a concrete, practical consequence for how you write and structure code in this project. + +When these principles don't explicitly cover a situation, apply these values to make a judgment call. See the language-specific guides for concrete rules and examples: +- [Code Quality — C#](./code-quality.csharp.md) +- [Code Quality — TypeScript](./code-quality.typescript.md) + +## Composition over Inheritance + +Prefer composing behavior from smaller, focused collaborators over building class hierarchies. Inheritance couples the child tightly to the parent's internal structure — a change to the parent can break every subclass. Composition keeps collaborators independent and replaceable. + +**Rules:** +- Never extend a concrete class to add or change behavior — inject a collaborator instead. +- Inheritance is acceptable only for framework integration points where a base class is part of a well-defined extension mechanism. + +## Single Responsibility Principle + +Every type and every method should have **one reason to change** — it should do one thing and do it well. A class that fetches data, transforms it, validates it, and sends an email has four reasons to change. When any of those concerns shifts, you have to touch — and risk breaking — all the others. + +**Rules:** +- A class or method that requires a comment explaining what each section does is a sign it should be split. +- Methods longer than ~20 lines are a signal they are doing too much — extract collaborators or helper methods. +- If a type needs collaborators from two unrelated domains, question whether it has two responsibilities. +- Follow the [File Size Guideline](#file-size--200-line-guideline) below. + +## Open/Closed Principle + +Types should be **open for extension, closed for modification**. Once a type is in use, changing its internals to support new behavior risks breaking existing callers. Instead, design extension points — interfaces, strategies, event hooks — that allow new behavior to be added without touching existing code. + +**Rules:** +- Prefer strategy interfaces over `switch`/`if-else` chains that grow over time. +- Design public APIs as contracts (interfaces/records) rather than concrete implementations. + +## Separation of Concerns + +Each layer and each module should own exactly one concern. Mixing concerns — for example, querying the database and formatting the HTTP response in the same method — creates entanglement that makes both concerns harder to change or test independently. + +**Rules:** +- Keep domain logic out of infrastructure — domain types must not reference infrastructure or transport concepts directly. +- Keep infrastructure out of domain logic — handlers and domain types express intent; they delegate to collaborators for persistence, messaging, and I/O. + +## Low Coupling + +Coupling is the degree to which one module depends on the internals of another. High coupling means a change in one place forces changes everywhere else. Low coupling means modules can evolve independently. + +**Rules:** +- Depend on abstractions, not on concrete implementations. +- Avoid reaching through an object to call methods on its dependencies — this is a sign of tight coupling. +- Limit the number of dependencies a single type takes — more than four or five is a signal it is doing too much. +- Never reference types from unrelated features directly; go through a shared contract or event instead. + +## High Cohesion + +Cohesion measures how closely related the responsibilities within a module are. A highly cohesive class has all its methods and properties working together toward a single goal. A low-cohesion class is a collection of unrelated utilities that happen to live in the same file. + +**Rules:** +- Group code by feature, not by technical role — everything for a behavior belongs together. +- If you find yourself writing methods in a type that use completely different sets of fields or dependencies, the type likely needs to be split. +- Utilities and helpers are acceptable only when the operations they provide are genuinely shared across features; otherwise, keep logic in the feature that owns it. + +## File Size — 200-Line Guideline + +A file exceeding **200 lines** is a strong signal that it contains too many responsibilities. This is not a hard limit — some files are legitimately longer — but whenever you find yourself adding to a file that already approaches this size, stop and ask: can this be split? + +**Rules:** +- When a file crosses 200 lines, look for natural split points: a sub-concept that could become its own type, a behavior that could move to a collaborator, or a section that belongs in a different layer. +- Aim for files that can be understood in a single reading without scrolling. +- Instruction and documentation files follow the same principle — a guide over 200 lines usually contains multiple distinct topics that deserve their own files. + +## Cross-Cutting Concerns + +Cross-cutting concerns — logging, validation, authorization, error handling, metrics, caching — affect many parts of the system but belong to none of them. Scattering them through business logic creates noise and duplication. Centralizing them in infrastructure keeps domain code clean. + +**Rules:** +- Never write logging or authorization checks inside domain logic — apply them at the infrastructure boundary. +- Never duplicate error-handling or retry logic — centralize it in a pipeline, middleware, or decorator. +- When you notice the same infrastructural pattern appearing in two or more places, extract it into a shared cross-cutting mechanism rather than duplicating it. diff --git a/.ai/rules/code-quality.typescript.md b/.ai/rules/code-quality.typescript.md new file mode 100644 index 0000000..d1d6bc2 --- /dev/null +++ b/.ai/rules/code-quality.typescript.md @@ -0,0 +1,89 @@ +--- +applyTo: "**/*.ts,**/*.tsx" +paths: + - "**/*.ts" + - "**/*.tsx" +--- + +# Code Quality — TypeScript + +TypeScript/React-specific applications of the general [Code Quality](./code-quality.md) principles. + +## Composition over Inheritance + +React is built on composition — components accept children, hooks compose other hooks, and higher-order utilities wrap behavior. Avoid class hierarchies entirely; the language and framework have moved on. + +```tsx +// ❌ Inheritance — fragile, couples component to base class internals +class SpecialButton extends BaseButton { + override render() { ... } +} + +// ✅ Composition — wrap or delegate, keep each piece independent +export const SpecialButton = ({ onClick, label }: SpecialButtonProps) => ( + +); +``` + +**Rules:** +- Never use class inheritance for React components — compose with props, children, and hooks instead. +- Extract repeated UI patterns into small, focused components rather than adding conditions to a shared parent. +- Extract repeated logic into custom hooks — a hook that does two unrelated things should be two hooks. + +## Open/Closed Principle + +TypeScript discriminated unions and generic constraints let you add new variants without touching existing code. Prefer them over ever-growing `if-else` / `switch` chains. + +```ts +// ❌ Grows every time a new shape is needed +function area(shape: string, a: number, b?: number): number { + if (shape === 'circle') return Math.PI * a * a; + if (shape === 'rectangle') return a * (b ?? 0); + throw new Error('Unknown shape'); +} + +// ✅ New shapes extend the union — existing handler functions are untouched +type Circle = { kind: 'circle'; radius: number }; +type Rectangle = { kind: 'rectangle'; width: number; height: number }; +type Shape = Circle | Rectangle; + +function area(shape: Shape): number { + switch (shape.kind) { + case 'circle': return Math.PI * shape.radius ** 2; + case 'rectangle': return shape.width * shape.height; + } +} +``` + +**Rules:** +- Model variation with discriminated unions rather than optional fields or string literals. +- Design utility functions to accept an interface or generic constraint so new types can be handled by adding a new implementation, not by editing existing code. + +## Separation of Concerns + +React components have one job: render UI and delegate events. Keep data-fetching, business logic, and side effects in dedicated hooks or services — not inline in the component body. + +**Rules:** +- Never write data-fetching or business logic directly in a component — extract it into a hook. +- Component files (`.tsx`) must not import from infrastructure layers such as HTTP clients or storage utilities directly — go through an abstraction or a generated proxy. +- Keep style concerns in co-located `.css` files; keep data concerns in hooks; keep rendering in the component. + +## Low Coupling + +Coupling in TypeScript is often hidden in deep import paths. Barrel files and path aliases make coupling explicit and keep refactoring safe. + +**Rules:** +- Import from barrel `index.ts` files, not from deep internal paths — this limits the blast radius of refactoring. +- Use the configured path aliases (e.g. `Strings`, `Components`) rather than relative `../../../` chains. +- Never import from an unrelated feature's internal files — go through that feature's public barrel export. +- Keep the number of imports in a single file reasonable — many imports from many different areas is a coupling smell. + +## Cross-Cutting Concerns + +**Rules:** +- Use React Error Boundaries to centralize error display — never scatter `try/catch` blocks inside component render paths. +- Use a single top-level provider or hook for global state (e.g. authentication, theming) — never drill context down through many component layers. +- Centralize API error handling in a shared hook or service layer — do not duplicate toast/notification logic per component. +- Apply logging, analytics, and monitoring at the infrastructure edge (e.g. router callbacks, global error handlers) so that feature components remain unaware of them. diff --git a/.ai/rules/components.md b/.ai/rules/components.md new file mode 100644 index 0000000..135ba4f --- /dev/null +++ b/.ai/rules/components.md @@ -0,0 +1,165 @@ +--- +applyTo: "**/*.tsx" +paths: + - "**/*.tsx" +profile: application +--- + +# Building React Components + +## Cratis Components — pick the wrapper, import from subpaths + +Reach PrimeReact almost exclusively through Cratis Components wrappers. Import from **subpaths**, not the root barrel (the barrel pulls optional-peer-heavy exports): + +| Need | Use | Subpath | +|---|---|---| +| Page chrome | `Page` | `@cratis/components/Common` | +| Query list page | `DataPage` | `@cratis/components/DataPage` | +| Standalone query table | `DataTableForQuery` / `DataTableForObservableQuery` | `@cratis/components/DataTables` | +| Form section | `FormElement` | `@cratis/components/Common` | +| Icon | `Icon` / `IconDisplay` | `@cratis/components/Common` | +| Dropdown | `Dropdown` | `@cratis/components/Dropdown` | +| Command dialog | `CommandDialog` / `StepperCommandDialog` | `@cratis/components/CommandDialog` | +| Data/confirmation dialog | `Dialog` / `ConfirmationDialog` / `BusyIndicatorDialog` | `@cratis/components/Dialogs` | +| Command form fields | `InputTextField`, … | `@cratis/components/CommandForm` | +| Canvas tool palette | `Toolbar` | `@cratis/components/Toolbar` | + +Use `Dropdown` from `@cratis/components/Dropdown` (not raw `primereact/dropdown`) — it appends to the document body and stacks correctly above overlays, avoiding the z-index issues raw PrimeReact dropdowns have inside dialogs. + +### `DataPage` — query list pages + +`DataPage` (from `@cratis/components/DataPage`) owns the data table's subscription, paging, selection, action menubar, and details split — **do not pre-fetch rows and pass an `items` array**. Required props: `title`, `query` (`Constructor`; snapshot and observable queries are auto-detected), `emptyMessage`, and `children`. Other props: `queryArguments`, `dataKey` (pass whenever the read model has an identity), `selection` / `onSelectionChange`, `globalFilterFields` / `defaultFilters` / `clientFiltering`, `detailsComponent` (`React.FC>` = `{ item, onRefresh? }`), `onRefresh`, and PrimeReact pass-through `tablePt`/`tableClassName`/`menubarPt`/`menubarClassName`. + +Columns and toolbar actions are compositional children: + +```tsx +import { DataPage, MenuItem } from '@cratis/components/DataPage'; +import { Column } from 'primereact/column'; + + + + + + + showAdd()} disableOnUnselected={false} /> + + +``` + +`MenuItem` is a PrimeReact menu item (use `command`, not `onClick`); `disableOnUnselected` greys it out until a row is selected. See the **cratis-react-page** skill for the full page workflow. + +## Composition over Monoliths + +A well-built component tree is like a well-organized kitchen — every tool has a place, and you can find what you need without opening every drawer. Large components that do everything are hard to understand, hard to test, and hard to change without breaking something unrelated. + +- Split components into small, focused pieces and compose them together. Each component should have a single, clear responsibility. +- Parent components own state and event handlers; children receive props. This makes data flow predictable and debuggable. +- If you find yourself writing a block comment like `// Author list section` inside a component, that section should be its own component. The comment is a code smell — the component name should provide that context instead. + +## Folder Structure + +- Single-file component → place directly in the parent feature folder. +- Multi-file component (sub-components, hooks, CSS) → create a folder named after the component: + +``` +PrototypeWindow/ + PrototypeWindow.tsx ← composition root + PrototypeWindow.css ← styles for the composition + TitleBar.tsx ← sub-component + CanvasArea.tsx ← sub-component + ResizeHandle.tsx ← sub-component + index.ts ← re-exports public API +``` + +Add an `index.ts` that re-exports the public surface so import paths stay stable. + +## Styling + +Consistent styling comes from discipline: static styles in CSS files, dynamic values inline, and colors always from PrimeReact's design tokens. This ensures theming works automatically and no component breaks the visual language. + +- Use **CSS classes in co-located `.css` files** for static styles. +- Each component must have its own CSS file — never add sub-component styles to the parent's CSS. This keeps styles co-located with the component they belong to. +- The composition root's CSS only contains layout/grid rules for positioning children — it should not style the children themselves. +- Use inline `style` props **only** for runtime-dynamic values (pixel positions, computed sizes). +- Use **PrimeReact CSS variables** for all colors, backgrounds, borders. This ensures the application respects theming and dark/light mode switches: + - `var(--surface-0)` through `var(--surface-900)`, `var(--surface-card)`, `var(--surface-border)`, `var(--surface-ground)` + - `var(--text-color)`, `var(--text-color-secondary)`, `var(--primary-color)`, `var(--primary-color-text)`, `var(--highlight-bg)` + - Never hard-code hex or `rgb()` for UI chrome — it will break when themes change. Only hard-code colors that are intentionally theme-independent (e.g. brand-specific accent dots, traffic-light indicators). +- Name CSS classes with a BEM-like prefix matching the component name. + +## Props + +Props are a component's public API. They should be clear, minimal, and well-documented. + +- Each sub-component declares its own `*Props` interface with JSDoc on every prop. +- Pass only needed props — avoid threading large prop bags through component trees. +- Event handlers follow `on*` naming: `onPointerDown`, `onSelect`. + +## Dialogs + +See [dialogs.md](./dialogs.md) for the full dialog guide. + +**Summary:** Never import `Dialog` from `primereact/dialog`. Use `CommandDialog` from `@cratis/components/CommandDialog` for command-executing dialogs and `Dialog` from `@cratis/components/Dialogs` for data-collection dialogs. Do not render manual `