@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.
- package/README.md +60 -23
- package/bin/cairn.js +13 -5
- package/lib/doctor/index.js +6 -1
- package/lib/init.js +15 -1
- package/lib/manifest.js +33 -0
- package/lib/render/cli.js +116 -0
- package/lib/render/engine.js +148 -0
- package/lib/render/index.js +220 -0
- package/lib/sync/cli.js +197 -0
- package/lib/sync/index.js +249 -0
- package/package.json +2 -1
- package/presets/EXTRACTION.md +210 -0
- package/presets/README.md +86 -0
- package/presets/angular/code-guidelines.md +197 -0
- package/presets/angular/slots/architect-discussion-topics.md +10 -0
- package/presets/angular/slots/architect-mandatory-docs.md +5 -0
- package/presets/angular/slots/architect-references.md +4 -0
- package/presets/angular/slots/implementation-reference.md +34 -0
- package/presets/angular/slots/key-patterns.md +11 -0
- package/presets/angular/slots/plan-step-ordering.md +11 -0
- package/presets/angular/slots/review-checklist.md +16 -0
- package/presets/angular/variants/i18n-external-service.md +11 -0
- package/presets/angular/variants/i18n-glossr.md +63 -0
- package/presets/core/AGENTS.md +49 -0
- package/presets/core/README.md +35 -0
- package/presets/core/WORKFLOW.md +56 -0
- package/presets/core/agents/architect.md +269 -0
- package/presets/core/agents/developer.md +145 -0
- package/presets/core/agents/reviewer.md +167 -0
- package/presets/core/commands/_stub.md +7 -0
- package/presets/core/workflow.json +45 -0
- package/presets/drizzle/code-guidelines.md +33 -0
- package/presets/drizzle/slots/architect-discussion-topics.md +4 -0
- package/presets/drizzle/slots/architect-mandatory-docs.md +4 -0
- package/presets/drizzle/slots/implementation-reference.md +17 -0
- package/presets/drizzle/slots/key-patterns.md +7 -0
- package/presets/drizzle/slots/review-checklist.md +10 -0
- package/presets/nestjs/code-guidelines.md +273 -0
- package/presets/nestjs/slots/architect-discussion-topics.md +4 -0
- package/presets/nestjs/slots/architect-mandatory-docs.md +5 -0
- package/presets/nestjs/slots/architect-references.md +5 -0
- package/presets/nestjs/slots/implementation-reference.md +45 -0
- package/presets/nestjs/slots/key-patterns.md +11 -0
- package/presets/nestjs/slots/plan-step-ordering.md +12 -0
- package/presets/nestjs/slots/review-checklist.md +12 -0
- package/presets/nestjs/variants/validation-class-validator.md +120 -0
- package/presets/nestjs/variants/validation-zod.md +194 -0
- package/presets/nextjs/code-guidelines.md +45 -0
- package/presets/nextjs/slots/architect-discussion-topics.md +5 -0
- package/presets/nextjs/slots/architect-mandatory-docs.md +3 -0
- package/presets/nextjs/slots/architect-references.md +6 -0
- package/presets/nextjs/slots/implementation-reference.md +24 -0
- package/presets/nextjs/slots/key-patterns.md +8 -0
- package/presets/nextjs/slots/plan-step-ordering.md +11 -0
- package/presets/nextjs/slots/review-checklist.md +11 -0
- package/presets/react/code-guidelines.md +46 -0
- package/presets/react/slots/architect-discussion-topics.md +5 -0
- package/presets/react/slots/architect-references.md +5 -0
- package/presets/react/slots/implementation-reference.md +26 -0
- package/presets/react/slots/key-patterns.md +8 -0
- package/presets/react/slots/plan-step-ordering.md +9 -0
- package/presets/react/slots/review-checklist.md +10 -0
- package/presets/tailwind/code-guidelines.md +28 -0
- package/presets/tailwind/slots/implementation-reference.md +8 -0
- package/presets/tailwind/slots/key-patterns.md +5 -0
- package/presets/tailwind/slots/review-checklist.md +8 -0
- package/presets/typeorm/code-guidelines.md +329 -0
- package/presets/typeorm/slots/architect-discussion-topics.md +4 -0
- package/presets/typeorm/slots/architect-mandatory-docs.md +3 -0
- package/presets/typeorm/slots/implementation-reference.md +19 -0
- package/presets/typeorm/slots/key-patterns.md +8 -0
- 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,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
|