@stonepandastudio/cairn 0.4.2 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/README.md +60 -23
  2. package/bin/cairn.js +13 -5
  3. package/lib/doctor/index.js +6 -1
  4. package/lib/init.js +15 -1
  5. package/lib/manifest.js +33 -0
  6. package/lib/render/cli.js +116 -0
  7. package/lib/render/engine.js +148 -0
  8. package/lib/render/index.js +220 -0
  9. package/lib/sync/cli.js +197 -0
  10. package/lib/sync/index.js +249 -0
  11. package/package.json +2 -1
  12. package/presets/EXTRACTION.md +210 -0
  13. package/presets/README.md +86 -0
  14. package/presets/angular/code-guidelines.md +197 -0
  15. package/presets/angular/slots/architect-discussion-topics.md +10 -0
  16. package/presets/angular/slots/architect-mandatory-docs.md +5 -0
  17. package/presets/angular/slots/architect-references.md +4 -0
  18. package/presets/angular/slots/implementation-reference.md +34 -0
  19. package/presets/angular/slots/key-patterns.md +11 -0
  20. package/presets/angular/slots/plan-step-ordering.md +11 -0
  21. package/presets/angular/slots/review-checklist.md +16 -0
  22. package/presets/angular/variants/i18n-external-service.md +11 -0
  23. package/presets/angular/variants/i18n-glossr.md +63 -0
  24. package/presets/core/AGENTS.md +49 -0
  25. package/presets/core/README.md +35 -0
  26. package/presets/core/WORKFLOW.md +56 -0
  27. package/presets/core/agents/architect.md +269 -0
  28. package/presets/core/agents/developer.md +145 -0
  29. package/presets/core/agents/reviewer.md +167 -0
  30. package/presets/core/commands/_stub.md +7 -0
  31. package/presets/core/workflow.json +45 -0
  32. package/presets/drizzle/code-guidelines.md +33 -0
  33. package/presets/drizzle/slots/architect-discussion-topics.md +4 -0
  34. package/presets/drizzle/slots/architect-mandatory-docs.md +4 -0
  35. package/presets/drizzle/slots/implementation-reference.md +17 -0
  36. package/presets/drizzle/slots/key-patterns.md +7 -0
  37. package/presets/drizzle/slots/review-checklist.md +10 -0
  38. package/presets/nestjs/code-guidelines.md +273 -0
  39. package/presets/nestjs/slots/architect-discussion-topics.md +4 -0
  40. package/presets/nestjs/slots/architect-mandatory-docs.md +5 -0
  41. package/presets/nestjs/slots/architect-references.md +5 -0
  42. package/presets/nestjs/slots/implementation-reference.md +45 -0
  43. package/presets/nestjs/slots/key-patterns.md +11 -0
  44. package/presets/nestjs/slots/plan-step-ordering.md +12 -0
  45. package/presets/nestjs/slots/review-checklist.md +12 -0
  46. package/presets/nestjs/variants/validation-class-validator.md +120 -0
  47. package/presets/nestjs/variants/validation-zod.md +194 -0
  48. package/presets/nextjs/code-guidelines.md +45 -0
  49. package/presets/nextjs/slots/architect-discussion-topics.md +5 -0
  50. package/presets/nextjs/slots/architect-mandatory-docs.md +3 -0
  51. package/presets/nextjs/slots/architect-references.md +6 -0
  52. package/presets/nextjs/slots/implementation-reference.md +24 -0
  53. package/presets/nextjs/slots/key-patterns.md +8 -0
  54. package/presets/nextjs/slots/plan-step-ordering.md +11 -0
  55. package/presets/nextjs/slots/review-checklist.md +11 -0
  56. package/presets/react/code-guidelines.md +46 -0
  57. package/presets/react/slots/architect-discussion-topics.md +5 -0
  58. package/presets/react/slots/architect-references.md +5 -0
  59. package/presets/react/slots/implementation-reference.md +26 -0
  60. package/presets/react/slots/key-patterns.md +8 -0
  61. package/presets/react/slots/plan-step-ordering.md +9 -0
  62. package/presets/react/slots/review-checklist.md +10 -0
  63. package/presets/tailwind/code-guidelines.md +28 -0
  64. package/presets/tailwind/slots/implementation-reference.md +8 -0
  65. package/presets/tailwind/slots/key-patterns.md +5 -0
  66. package/presets/tailwind/slots/review-checklist.md +8 -0
  67. package/presets/typeorm/code-guidelines.md +329 -0
  68. package/presets/typeorm/slots/architect-discussion-topics.md +4 -0
  69. package/presets/typeorm/slots/architect-mandatory-docs.md +3 -0
  70. package/presets/typeorm/slots/implementation-reference.md +19 -0
  71. package/presets/typeorm/slots/key-patterns.md +8 -0
  72. package/presets/typeorm/slots/review-checklist.md +8 -0
@@ -0,0 +1,167 @@
1
+ # {{ reviewerAgent }}
2
+
3
+ <!-- cairn preset: core. Role contract shared by node-reviewer.md and
4
+ angular-reviewer.md (85% identical, measured 2026-09-09). The Dimension 2
5
+ rule table is the one stack-specific part — it comes from the preset slot. -->
6
+
7
+ > **Model Check**: This agent requires **{{ model }}**. Before doing anything, verify
8
+ > the active model. If the current model is not {{ model }}, stop immediately and
9
+ > notify the user: "Wrong model active. {{ reviewerAgent }} requires {{ model }}.
10
+ > Please run `/model sonnet` and retry."
11
+
12
+ ## Role
13
+
14
+ Code quality gatekeeper. Reviews the output of `{{ developerAgent }}` against the
15
+ original plan, the enriched description, and the project's code guidelines. Produces
16
+ a structured report of blocking issues and suggestions — it does **not** apply fixes.
17
+
18
+ ---
19
+
20
+ ## Core principles
21
+
22
+ 1. **Review against the plan, not your own preferences.** The plan is the contract between the architect and the developer. Deviations from the plan are findings; personal style preferences that don't conflict with `code-guidelines.md` are not.
23
+ 2. **Be precise, not comprehensive.** Every finding must name the exact file and line (or code block). Vague observations ("this could be cleaner") are not findings.
24
+ 3. **Distinguish blocking from suggestions.** Blocking issues must be resolved before the step is considered done. Suggestions are improvements the developer can choose to apply.
25
+ 4. **Report only. Do not edit files.** If fixes are needed, the user re-runs `/execute-plan` or addresses them manually.
26
+
27
+ ---
28
+
29
+ ## Workflow (`{{ step.fn }}(task, step)`)
30
+
31
+ ### R.1 — Read the plan and description
32
+
33
+ Read both files in full before examining any code:
34
+
35
+ - `ai/tasks/[task]/plan-step-[step].md` — authoritative scope, file list, implementation steps, context doc requirements
36
+ - `ai/tasks/[task]/description-step-[step].md` — the enriched brief that motivated the plan; clarifies intent where the plan is ambiguous
37
+
38
+ `task` is the full folder key as it appears under `ai/tasks/` — either a tracker key
39
+ (e.g. `{{ tracker.exampleKey }}-207`) or a `LOCAL-<n>-<slug>` folder for local-only
40
+ tasks (see `ai/WORKFLOW.md`). Don't assume a `{{ tracker.exampleKey }}-` prefix.
41
+
42
+ ### R.2 — Read mandatory guidelines
43
+
44
+ - `ai/infrastructure/code-guidelines.md` — the complete rule set; every blocking finding must cite a specific section
45
+ - `ai/infrastructure/project-structure.md` — module layout and layer rules
46
+ - `ai/infrastructure/mappers.md` — mapper conventions
47
+ - Any `ai/contexts/**` files referenced in the plan
48
+
49
+ ### R.3 — Collect the diff
50
+
51
+ Run `git diff HEAD` scoped to the files the plan names. Focus exclusively on lines that were added or modified. Do not flag pre-existing issues in unchanged lines.
52
+
53
+ ```bash
54
+ git diff HEAD -- <file1> <file2> ...
55
+ ```
56
+
57
+ If the plan does not name individual files, use `git status` to find all modified/added files, then diff them.
58
+
59
+ **New (untracked) files do not appear in `git diff HEAD`.** For every file in the plan's "Files to Create" list, use the `Read` tool to read it directly and review its content — do not skip it because the diff is empty.
60
+
61
+ ### R.4 — Review each dimension
62
+
63
+ Work through all four dimensions in order. For each finding, record:
64
+ - **File + location** (file path, line number or function name)
65
+ - **What was found** (quote the relevant code)
66
+ - **Why it's a problem** (cite the guideline section or plan step)
67
+ - **How to fix it** (concrete, actionable instruction — one sentence)
68
+
69
+ #### Dimension 1 — Plan adherence
70
+
71
+ - Does every file in the plan's **"Files to Create"** list exist?
72
+ - Does every file in the plan's **"Files to Modify"** list show a diff?
73
+ - Does the implementation match each **Implementation Step** described in the plan? (Structure, not line-by-line verbatim — intent counts.)
74
+ - Are there files changed that are **not** in either list? (Flag as scope creep unless trivially justified.)
75
+
76
+ #### Dimension 2 — Code guidelines compliance
77
+
78
+ Check every changed file against `code-guidelines.md`, `project-structure.md`, and
79
+ `mappers.md`. High-priority rules to verify:
80
+
81
+ | Rule | Where to look |
82
+ |---|---|
83
+ {{> stack/review-checklist }}
84
+
85
+ #### Dimension 3 — Context doc coverage
86
+
87
+ Look at the plan's **"Context Docs to Update"** table. For each item:
88
+
89
+ - **Create** action: does the file exist? Does it cover the sections the plan described?
90
+ - **Update** action: does the diff show the file was modified in the way the plan described?
91
+
92
+ If a context doc item is missing or incomplete, that is a blocking finding.
93
+
94
+ #### Dimension 4 — Scope creep
95
+
96
+ List any files modified that appear in the diff but are **not** in the plan's file lists. For each:
97
+ - If the change is a necessary side-effect, mark it **expected** and move on.
98
+ - If the change is not explained by the plan, flag it as **unexpected scope** — blocking if it introduces new behaviour, suggestion if it's a trivial cleanup.
99
+
100
+ ---
101
+
102
+ ## Report format
103
+
104
+ Output the review as a structured markdown report. Use exactly this format:
105
+
106
+ ```
107
+ ## Code Review — [task] Step [step]
108
+
109
+ ### Summary
110
+ [One paragraph: overall assessment. "No blocking issues found." or "X blocking issues, Y suggestions."]
111
+
112
+ ---
113
+
114
+ ### 🔴 Blocking Issues
115
+
116
+ #### B1 — [Short title]
117
+ **File:** `path/to/file.ts` (line N or function name)
118
+ **Found:** [Quoted or paraphrased code]
119
+ **Problem:** [Why it violates the guideline or plan — cite the source]
120
+ **Fix:** [One-sentence instruction]
121
+
122
+ #### B2 — ...
123
+
124
+ ---
125
+
126
+ ### 🟡 Suggestions
127
+
128
+ #### S1 — [Short title]
129
+ **File:** `path/to/file.ts`
130
+ **Found:** [Quoted or paraphrased code]
131
+ **Suggestion:** [What could be improved and why]
132
+
133
+ ---
134
+
135
+ ### ✅ Context Docs
136
+ [One line per item from the plan's "Context Docs to Update" table: ✅ covered / ❌ missing / ⚠️ incomplete]
137
+
138
+ ### ✅ Plan Coverage
139
+ [One line per "Files to Create/Modify": ✅ present / ❌ missing / ⚠️ differs from plan intent]
140
+
141
+ ### ⚠️ Scope Creep
142
+ [List any unplanned file changes, or "None."]
143
+ ```
144
+
145
+ If there are no blocking issues, say so clearly in the Summary. If there are no suggestions, omit that section entirely.
146
+
147
+ ---
148
+
149
+ ## When to stop and ask
150
+
151
+ Stop and ask the user before producing the review if:
152
+
153
+ - `plan-step-[step].md` does not exist — the developer may have run against the wrong step.
154
+ - `description-step-[step].md` does not exist — the plan cannot be evaluated without understanding the original intent.
155
+ - The git diff is empty for all plan-named files — the developer may not have committed or the task ID is wrong.
156
+
157
+ ---
158
+
159
+ ## What this agent does NOT do
160
+
161
+ - Does not edit source files.
162
+ - Does not re-run the developer agent.
163
+ - Does not evaluate test coverage (out of scope for this project's current workflow).
164
+ - Does not review files outside the diff (pre-existing issues in unchanged code are invisible to this agent).
165
+ {{#if tracker.remote}}
166
+ - **Does not sync to the tracker**, even on a clean review — moving to a done state is manual. See `ai/WORKFLOW.md` § Sync points.
167
+ {{/if}}
@@ -0,0 +1,7 @@
1
+ {{! cairn preset: core. The renderer emits one file per workflow.steps[] entry,
2
+ named `<step.command>.md`, into `.claude/commands/`. Every stub in
3
+ snap-proof/{backend,frontend} is byte-identical to this modulo the two
4
+ variables below (plus a stray BOM on the frontend copies). }}
5
+ You are acting as **{{ step.agentName }}**. Read `{{ step.agentDoc }}` for your full role, workflow, and instructions.
6
+
7
+ Run `{{ step.fn }}($ARGUMENTS)` — treat the first token as the task ID (e.g. {{ tracker.exampleKey }}-210) and the second as the step number (e.g. 1).
@@ -0,0 +1,45 @@
1
+ {
2
+ "$comment": "cairn preset: core. The default workflow the renderer uses when a repo's cairn.config.json has no `workflow` block. Each step names its slash command, the underlying fn the agent docs refer to, and the role that runs it. `oneLine` is the AGENTS.md summary; `trackerAction` (optional) is the WORKFLOW.md sync-points cell for a remote tracker.",
3
+ "steps": [
4
+ {
5
+ "id": "brief",
6
+ "fn": "create_brief",
7
+ "command": "create-brief",
8
+ "role": "architect",
9
+ "oneLine": "creates the task brief scaffold for you to fill in",
10
+ "trackerAction": "create the parent issue if new; always create the child issue for this step; write its key into the brief frontmatter"
11
+ },
12
+ {
13
+ "id": "describe",
14
+ "fn": "enrich_description",
15
+ "command": "enrich-description",
16
+ "role": "architect",
17
+ "oneLine": "iterative discussion, then writes the enriched description",
18
+ "trackerAction": "child issue description ← the enriched description; a 1–2 sentence summary appended to the parent"
19
+ },
20
+ {
21
+ "id": "plan",
22
+ "fn": "plan_task",
23
+ "command": "plan-task",
24
+ "role": "architect",
25
+ "oneLine": "produces the implementation plan and any context-doc updates",
26
+ "trackerAction": "parent and child issue → in-progress stage"
27
+ },
28
+ {
29
+ "id": "execute",
30
+ "fn": "execute_plan",
31
+ "command": "execute-plan",
32
+ "role": "developer",
33
+ "oneLine": "implements the plan (switch to Haiku first)",
34
+ "trackerAction": "no sync"
35
+ },
36
+ {
37
+ "id": "review",
38
+ "fn": "code_review",
39
+ "command": "code-review",
40
+ "role": "reviewer",
41
+ "oneLine": "checks the result against the plan and the guidelines",
42
+ "trackerAction": "no sync — later stage moves are manual"
43
+ }
44
+ ]
45
+ }
@@ -0,0 +1,33 @@
1
+ # Drizzle Guidelines
2
+
3
+ <!-- cairn preset: drizzle. The ORM add-on for a Postgres/Supabase stack, parallel
4
+ to presets/typeorm. Seeded 2026-09-09 from charityiq-app (Sprint 0 — mostly
5
+ CLAUDE.md hard rules + ADR 0001, no battle-tested code yet). Single consumer:
6
+ refine when a second Drizzle repo appears. Composes onto a framework preset
7
+ (nestjs or react/nextjs) via `--stack <framework>,drizzle`. -->
8
+
9
+ ## Schema
10
+
11
+ * One schema module — `lib/db/schema.ts`. Every table, column, and relation is declared there in Drizzle syntax.
12
+ * Never write raw SQL in application code. The only place hand-written SQL is allowed is a migration file under `supabase/migrations/`.
13
+ * Column names snake_case, TypeScript field names camelCase (Drizzle maps them).
14
+ * Row-Level Security policies live in the migration files, not in the schema module — the schema describes shape, the migration describes access.
15
+
16
+ ## Migrations
17
+
18
+ * Generate with `pnpm exec drizzle-kit generate` whenever the diff is mechanical; hand-write the `.sql` only for RLS policies, functions, triggers, and data backfills Drizzle Kit cannot express.
19
+ * Review every generated migration before committing — check for accidental drops or type changes.
20
+ * A schema change and its migration land in the same commit. The database is the source of truth; the schema module must always match what the migrations have applied.
21
+ * Keep `ai/infrastructure/DATABASE_SCHEMA.md` current with each migration.
22
+
23
+ ## Clients
24
+
25
+ * Server queries go through the `postgres-js` + Drizzle client in `lib/db/client.ts`. It uses the service-role key and must **never** be imported from a `"use client"` module.
26
+ * The browser talks to the database only through the Supabase client, constrained by RLS — never the Drizzle client.
27
+
28
+ ## Queries
29
+
30
+ * Prefer the typed query builder; reach for `sql` template literals only for expressions Drizzle does not model.
31
+ * Select only the columns a caller needs.
32
+ * Multi-step writes run inside `db.transaction(...)`.
33
+ * Watch for N+1 — batch with `inArray` / a join rather than a loop of point reads.
@@ -0,0 +1,4 @@
1
+ <!-- cairn preset: drizzle — appended to the framework preset's
2
+ architect-discussion-topics slot on `--stack <framework>,drizzle`. -->
3
+ - **Schema shape** — new tables/columns, nullability, relations, indexes, RLS policy needed
4
+ - **Migration scope** — mechanical `drizzle-kit generate` vs. a hand-written policy/backfill; is any change destructive
@@ -0,0 +1,4 @@
1
+ <!-- cairn preset: drizzle — appended to the framework preset's
2
+ architect-mandatory-docs slot on `--stack <framework>,drizzle`. -->
3
+ - `ai/infrastructure/DATABASE_SCHEMA.md`
4
+ - `lib/db/schema.ts` — the current Drizzle schema
@@ -0,0 +1,17 @@
1
+ <!-- cairn preset: drizzle — appended to the framework preset's
2
+ implementation-reference slot on `--stack <framework>,drizzle`. -->
3
+
4
+ ### Schema
5
+ - All tables in `lib/db/schema.ts`, Drizzle syntax; snake_case columns, camelCase fields
6
+ - Relations declared with `relations(...)` so typed joins work
7
+ - No RLS in the schema module — that goes in the migration
8
+
9
+ ### Migrations
10
+ - `pnpm exec drizzle-kit generate` for mechanical diffs
11
+ - Hand-write the `.sql` only for RLS policies, functions, triggers, backfills
12
+ - One commit = schema edit + its migration
13
+
14
+ ### Access
15
+ - Server: the Drizzle client in `lib/db/client.ts` (service-role, never in `"use client"`)
16
+ - Browser: the Supabase client only, bounded by RLS
17
+ - Reads select named columns; multi-step writes go in `db.transaction(...)`
@@ -0,0 +1,7 @@
1
+ <!-- cairn preset: drizzle — appended to the framework preset's key-patterns slot
2
+ on `--stack <framework>,drizzle`. -->
3
+
4
+ Database schema: **`lib/db/schema.ts`** (single Drizzle module) and
5
+ **`ai/infrastructure/DATABASE_SCHEMA.md`** — keep both current with every migration.
6
+
7
+ Migration and RLS conventions: **`ai/infrastructure/code-guidelines.md`** § Drizzle.
@@ -0,0 +1,10 @@
1
+ <!-- cairn preset: drizzle — appended to the framework preset's review-checklist
2
+ slot on `--stack <framework>,drizzle`. -->
3
+
4
+ | No raw SQL in application code — only the Drizzle query builder, or `sql` for expressions it can't model | any `.ts` outside `supabase/migrations/` |
5
+ | Schema change committed together with its `drizzle-kit` migration | `lib/db/schema.ts` + `supabase/migrations/` |
6
+ | Generated migration reviewed — no accidental drop or type change | the migration `.sql` |
7
+ | RLS policy declared in a migration, not assumed | `supabase/migrations/` |
8
+ | Server Drizzle client not imported from a `"use client"` module | client components |
9
+ | Multi-step write wrapped in `db.transaction(...)` | service / action files |
10
+ | `ai/infrastructure/DATABASE_SCHEMA.md` updated to match | that file |
@@ -0,0 +1,273 @@
1
+ # Code Guidelines
2
+
3
+ <!-- cairn preset: nestjs. Composes with presets/typeorm for TypeORM-backed services. -->
4
+
5
+ ## General TypeScript Guidelines
6
+
7
+ * Use TypeScript strict mode
8
+ * Prefer `const` over `let`, never use `var`
9
+ * Avoid using `any` type - use proper TypeScript types
10
+ * Use async/await instead of raw promises
11
+ * Handle all error cases explicitly
12
+ * Use proper error types from NestJS (`NotFoundException`, `BadRequestException`, etc.)
13
+
14
+ ## NestJS Module Structure
15
+
16
+ * Separate business logic (feature modules in `src/feature/`) from API layer (API modules in `src/api/modules/`)
17
+ * Use proper dependency injection with `@Injectable()`, `@Module()` decorators
18
+ * Register providers, imports, and exports correctly in module files
19
+ * Keep controllers thin - delegate business logic to services
20
+
21
+ ## File Naming Conventions
22
+
23
+ * Use kebab-case (dash-case) for all file names
24
+ * Modules: `feature-name.module.ts`
25
+ * Services: `feature-name.service.ts`
26
+ * Controllers: `feature-name.controller.ts`
27
+ * Entities: `feature-name.entity.ts`
28
+ * Interfaces: `interface-name.interface.ts` (stored in `interfaces/` folder)
29
+ * Tests: `feature-name.service.spec.ts`, `feature-name.controller.spec.ts`
30
+
31
+ DTO file naming follows the validation stack:
32
+
33
+ {{> slot: dto-naming }}
34
+
35
+ ## File Organization
36
+
37
+ Every utility function, model, constant, DTO, or mapping function should be in a separated file.
38
+
39
+ Files should be placed in appropriate folders within modules (see `ai/infrastructure/project-structure.md` for module details):
40
+
41
+ * **constants/** - Constants specific to the module
42
+ * **enums/** - Enums specific to the module
43
+ * **interfaces/** - TypeScript interfaces (one per file)
44
+ * **dtos/** - Data Transfer Objects for request/response
45
+ * **entities/** - Database entities
46
+ * **services/** - Business logic services
47
+ * **controllers/** - HTTP controllers
48
+ * **mappers/** - Mapping functions for transforming data
49
+
50
+ ## Interfaces
51
+
52
+ **IMPORTANT**: Store interfaces separately from code in dedicated interface files.
53
+
54
+ **Rules**:
55
+ * Each interface gets its own file in `src/feature/[feature-name]/interfaces/` folder
56
+ * File naming: convert interface name to kebab-case + `.interface.ts`
57
+ - E.g., `CreateUserData` → `create-user-data.interface.ts`
58
+ - E.g., `RegisterOwnerResult` → `register-owner-result.interface.ts`
59
+ * One interface per file — no mixing multiple interfaces
60
+ * Interfaces are NOT exported from barrel files unless necessary
61
+
62
+ **Examples**:
63
+ ```typescript
64
+ // src/feature/user/interfaces/create-user-data.interface.ts
65
+ export interface CreateUserData {
66
+ companyId: string;
67
+ email: string;
68
+ name: string;
69
+ passwordHash: string;
70
+ role?: TenantRole;
71
+ }
72
+
73
+ // src/feature/user/interfaces/register-owner-result.interface.ts
74
+ export interface RegisterOwnerResult {
75
+ user: User;
76
+ company: Company;
77
+ }
78
+ ```
79
+
80
+ ## Controllers
81
+
82
+ * Keep controllers thin - only handle HTTP concerns
83
+ * Use proper HTTP method decorators: `@Get()`, `@Post()`, `@Put()`, `@Delete()`, `@Patch()`
84
+ * Use parameter decorators: `@Body()`, `@Param()`, `@Query()`, `@Headers()`
85
+ * Use `@UseGuards()` for authentication and authorization
86
+ * Return consistent response structures
87
+ * Let NestJS exception filters handle errors
88
+
89
+ Response typing and serialization are stack-specific:
90
+
91
+ {{> slot: controller-response }}
92
+
93
+ ## Services
94
+
95
+ * Implement all business logic in services
96
+ * Use dependency injection for all dependencies
97
+ * Mark services with `@Injectable()` decorator
98
+ * Handle database transactions properly
99
+ * Throw appropriate NestJS exceptions
100
+ * Keep methods focused and single-purpose
101
+ * Use async/await for all asynchronous operations
102
+
103
+ ## DTOs (Data Transfer Objects)
104
+
105
+ * Create separate DTOs for different operations:
106
+ - `create-*.dto.ts` for POST requests
107
+ - `update-*.dto.ts` for PUT/PATCH requests
108
+ - `*-response.dto.ts` for responses
109
+ - `*-query.dto.ts` for query parameters
110
+
111
+ {{> slot: dto-definition }}
112
+
113
+ ## Validation
114
+
115
+ * Validate at API boundaries (in controllers via DTOs)
116
+ * Provide clear, user-friendly validation error messages
117
+
118
+ {{> slot: validation-stack }}
119
+
120
+ ## Error Handling
121
+
122
+ * Use NestJS built-in exceptions:
123
+ - `NotFoundException` - Resource not found (404)
124
+ - `BadRequestException` - Invalid input (400)
125
+ - `UnauthorizedException` - Not authenticated (401)
126
+ - `ForbiddenException` - Not authorized (403)
127
+ - `ConflictException` - Resource conflict (409)
128
+ * Create custom exception classes for domain-specific errors
129
+ * Use exception filters for global error handling
130
+ * Log errors appropriately (use NestJS Logger)
131
+ * Never expose sensitive information in error messages
132
+ * Never expose stack traces in production
133
+
134
+ ## Error Codes
135
+
136
+ **MANDATORY**: Every thrown exception **must** include a machine-readable `code` from `ERROR_CODES`.
137
+
138
+ **Why**: Frontend uses `code` to display localized messages. String-matching on `message` is forbidden.
139
+
140
+ **Constants file**: `src/common/constants/error-codes.constant.ts` — single source of truth for all codes.
141
+ Add new codes here before using them. Never use inline strings.
142
+
143
+ **Throw pattern**:
144
+ ```typescript
145
+ // Correct
146
+ import { ERROR_CODES } from '@common/constants/error-codes.constant';
147
+ throw new NotFoundException({ message: 'User not found', code: ERROR_CODES.USER_NOT_FOUND });
148
+
149
+ // Incorrect — missing code
150
+ throw new NotFoundException('User not found');
151
+ ```
152
+
153
+ **Naming convention**: `SCREAMING_SNAKE_CASE`, format `[DOMAIN]_[REASON]`
154
+ - `USER_NOT_FOUND`, `USER_DEACTIVATED`
155
+ - `INVITATION_NOT_FOUND`, `USER_LIMIT_REACHED`
156
+ - `INVALID_CREDENTIALS`, `CONFIRMATION_TOKEN_EXPIRED`
157
+
158
+ **Exceptions to this rule** (do NOT add codes):
159
+ - Validation pipe errors — these carry a per-field `code` in the `errors` array instead
160
+ - Bare `UnauthorizedException()` in the JWT strategy — no info leak on invalid token by design
161
+
162
+ ## Response Transformation
163
+
164
+ {{> slot: response-transformation }}
165
+
166
+ ## Pagination
167
+
168
+ Use a shared `paginatedResponseSchema` helper for all list endpoints that return paginated data.
169
+
170
+ **Rules**:
171
+ * Query params always use `page`/`pageSize` — never expose `skip`/`take` to the client
172
+ * Response always uses the `{ items, total, skip, take }` shape — never `page`/`pageSize`
173
+ * `pageSize` max is 100; default is 50
174
+ * Always use `getManyAndCount()` so `total` is accurate
175
+
176
+ **Service** — convert `page`/`pageSize` to `skip`/`take` and return the envelope:
177
+
178
+ ```typescript
179
+ async list(params: { page: number; pageSize: number }): Promise<{ items: Foo[]; total: number; skip: number; take: number }> {
180
+ const skip = (params.page - 1) * params.pageSize;
181
+ const take = params.pageSize;
182
+ const [items, total] = await qb.skip(skip).take(take).getManyAndCount();
183
+ return { items, total, skip, take };
184
+ }
185
+ ```
186
+
187
+ Query-param coercion and response-DTO declaration are stack-specific:
188
+
189
+ {{> slot: pagination-dtos }}
190
+
191
+ ## Security
192
+
193
+ * Sanitize all user input
194
+ * Use parameterized queries (the ORM handles this automatically)
195
+ * Implement authentication with Guards (`@UseGuards(AuthGuard)`)
196
+ * Implement authorization checks for protected resources
197
+ * Validate file uploads (type, size, content)
198
+ * Never commit secrets, API keys, or credentials to version control
199
+ * Use environment variables for configuration
200
+ * Hash passwords with bcrypt
201
+ * Use HTTPS in production
202
+
203
+ ## Constants
204
+
205
+ File name template: `xx-yy.constant.ts` for `XxYy` constant
206
+
207
+ Files with constants should be placed in the `constants/` folder within the corresponding module (see
208
+ `ai/infrastructure/project-structure.md` for module details)
209
+
210
+ ## Enums
211
+
212
+ File name template: `xx-yy.enum.ts` for `XxYy` enum
213
+
214
+ Files with enums should be placed in the `enums/` folder within the corresponding module (see `ai/infrastructure/project-structure.md` for
215
+ module details)
216
+
217
+ ## No Magic Strings or Numbers
218
+
219
+ **MANDATORY**: Never use raw string or number literals where a named constant or enum value can be used instead. Magic values scatter meaning across the codebase, make refactoring error-prone, and produce silent bugs when a value changes in one place but not another.
220
+
221
+ **Rules**:
222
+ * Any string or number that carries domain meaning and appears in more than one place **must** be a constant or enum
223
+ * Any string or number that carries domain meaning and appears in only one place **should still** be a constant or enum if it is likely to be referenced elsewhere in the future (e.g. audit action names, entity type labels, JWT token types)
224
+ * Raw string comparisons against domain values are always wrong — use the enum
225
+
226
+ **Enums** for closed sets of values (token types, roles, statuses, entity types, action names):
227
+ ```typescript
228
+ // ✅ Correct
229
+ export enum JwtType {
230
+ USER = 'user',
231
+ ADMIN = 'admin',
232
+ IMPERSONATION = 'impersonation',
233
+ }
234
+
235
+ if (payload.type === JwtType.IMPERSONATION) { ... }
236
+
237
+ // ❌ Incorrect
238
+ if (payload.type === 'impersonation') { ... }
239
+ ```
240
+
241
+ **Constants** for single values with no closed set (limits, expiry defaults, config keys):
242
+ ```typescript
243
+ // ✅ Correct — src/feature/user/constants/max-invitation-attempts.constant.ts
244
+ export const MAX_INVITATION_ATTEMPTS = 5;
245
+
246
+ // ❌ Incorrect
247
+ if (attempts > 5) { ... }
248
+ ```
249
+
250
+ **Where to place them**:
251
+ * Enums belong in `enums/` within the owning module — see **Enums** section above
252
+ * Constants belong in `constants/` within the owning module — see **Constants** section above
253
+ * Cross-cutting enums/constants (e.g. `JwtType`, `ERROR_CODES`) belong in `src/common/`
254
+
255
+ **Common categories that always require an enum or constant**:
256
+ * JWT `type` field values
257
+ * Audit action names and entity type labels
258
+ * Platform and tenant role values
259
+ * Any status value with a closed set
260
+ * Any HTTP header name, cookie name, or metadata key used in multiple places
261
+
262
+ ## Code Quality
263
+
264
+ * Follow SOLID principles
265
+ * Keep functions small and focused (single responsibility)
266
+ * Use meaningful variable and function names
267
+ * Add comments only where logic is complex or non-obvious
268
+ * Remove commented-out code before committing
269
+ * Use TypeScript features: interfaces, types, generics, decorators
270
+ * Prefer composition over inheritance
271
+ * Use dependency injection for loose coupling
272
+ * Write self-documenting code
273
+ * Keep code DRY (Don't Repeat Yourself)
@@ -0,0 +1,4 @@
1
+ <!-- cairn preset: nestjs — fills {{> stack/architect-discussion-topics }} (A.3).
2
+ `--stack nestjs,typeorm` also appends the typeorm slot (schema, migrations). -->
3
+ - **API design questions** — endpoint naming, request/response shape, pagination or filtering needs
4
+ - **Layering** — what belongs in the feature module vs. the API module
@@ -0,0 +1,5 @@
1
+ <!-- cairn preset: nestjs — fills {{> stack/architect-mandatory-docs }} (A.2).
2
+ `--stack nestjs,typeorm` also appends the typeorm slot (DATABASE_SCHEMA.md). -->
3
+ - `ai/infrastructure/code-guidelines.md`
4
+ - `ai/infrastructure/project-structure.md`
5
+ - `ai/infrastructure/mappers.md`
@@ -0,0 +1,5 @@
1
+ <!-- cairn preset: nestjs — fills {{> stack/architect-references }} (A.1).
2
+ Primary-stack-only, not concatenated. -->
3
+ - Backend source files (modules, services, entities, DTOs, controllers)
4
+ - Context docs in `ai/contexts/**` — entity definitions, business logic, feature docs
5
+ - The frontend repo's context docs, if the task touches an API shape the frontend consumes
@@ -0,0 +1,45 @@
1
+ <!-- cairn preset: nestjs — fills {{> stack/implementation-reference }} in the core
2
+ developer. Framework areas only. `--stack nestjs,typeorm` also appends
3
+ presets/typeorm/slots/implementation-reference.md (entities, migrations,
4
+ repositories). From snap-proof/backend/ai/agents/node-developer.md. -->
5
+
6
+ Quick reminders for the most common implementation areas. The authoritative source
7
+ is always `ai/infrastructure/code-guidelines.md`.
8
+
9
+ ### Module structure
10
+ - Feature modules in `src/feature/<name>/` — business logic and services
11
+ - API modules in `src/api/modules/<name>/` — HTTP layer only (controllers, API-layer services, request/response DTOs)
12
+ - Register providers, imports, and exports explicitly in every module file
13
+
14
+ ### Controllers
15
+ - Thin controllers: delegate all business logic to services
16
+ - `@Get()` / `@Post()` / `@Put()` / `@Patch()` / `@Delete()` with explicit route paths
17
+ - Bind inputs with `@Body()`, `@Param()`, `@Query()`
18
+ - Apply `@UsePipes(ValidationPipe)` and `@UseGuards()` where required
19
+ - Return consistent response shapes; use response DTOs and mappers
20
+
21
+ ### Services
22
+ - All business logic in feature services; API-layer services handle HTTP concerns only
23
+ - Throw NestJS built-in exceptions: `NotFoundException`, `BadRequestException`, `UnauthorizedException`, …
24
+ - async/await throughout
25
+
26
+ ### DTOs
27
+ - One file per DTO, `.dto.ts` suffix; validation decorators for every property (per the validation-stack variant)
28
+ - `@ApiProperty()` from `@nestjs/swagger` on every property
29
+ - Request DTOs in `src/api/dtos/`; shared/internal DTOs in `src/feature/dtos/`
30
+
31
+ ### Error handling
32
+ - Use NestJS exceptions — never expose raw DB errors or stack traces
33
+ - Custom exceptions extend `HttpException` only when no built-in fits
34
+ - Log errors at service level; never log sensitive data
35
+
36
+ ### Testing
37
+ - Unit tests for services: `feature-name.service.spec.ts`
38
+ - `Test.createTestingModule()` for NestJS context; mock dependencies
39
+ - Cover happy paths, not-found cases, and validation failures
40
+
41
+ ### File naming
42
+ - `feature-name.module.ts` / `.service.ts` / `.controller.ts`
43
+ - DTOs: `create-feature.dto.ts`, `update-feature.dto.ts`, `feature-response.dto.ts`
44
+ - Tests: `feature-name.service.spec.ts`
45
+ - All kebab-case
@@ -0,0 +1,11 @@
1
+ <!-- cairn preset: nestjs — fills {{> stack/key-patterns }} in the core architect
2
+ and developer. `--stack nestjs,typeorm` also appends the typeorm slot. -->
3
+
4
+ Module and layer conventions: **`ai/infrastructure/project-structure.md`** — feature
5
+ modules (`src/feature/`) vs. API modules (`src/api/modules/`), service/repository
6
+ placement. Plans should follow the same shape.
7
+
8
+ Code conventions (naming, DTOs, validation, decorators, error handling):
9
+ **`ai/infrastructure/code-guidelines.md`**.
10
+
11
+ Mapper conventions: **`ai/infrastructure/mappers.md`**.
@@ -0,0 +1,12 @@
1
+ <!-- cairn preset: nestjs — fills {{> stack/plan-step-ordering }} in the core
2
+ architect's Plan format. This slot is *not* concatenated across stack entries
3
+ — it is one coherent sequence, taken from the primary stack (stack[0]). It
4
+ assumes an ORM in step 1; a bare `--stack nestjs` repo trims that step.
5
+ From node-architect.md. -->
6
+ 1. Entity / migration changes
7
+ 2. Feature module — service and repository setup
8
+ 3. DTOs and validation decorators
9
+ 4. API module — controller and endpoint implementation
10
+ 5. Error handling and edge cases
11
+ 6. Unit and integration test hooks
12
+ 7. Context doc updates