@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,210 @@
|
|
|
1
|
+
# Extraction record
|
|
2
|
+
|
|
3
|
+
What was pulled into `presets/`, from where, and — more importantly — what was
|
|
4
|
+
deliberately left in the repos. Measured 2026-08-01 by splitting each
|
|
5
|
+
`code-guidelines.md` on `## ` headings and running cairn's own line differ over
|
|
6
|
+
matching sections.
|
|
7
|
+
|
|
8
|
+
The file-level numbers in `DESIGN.md` (backend 43% same, frontend 76%) turned out
|
|
9
|
+
to be the wrong resolution. Per section the picture is much sharper.
|
|
10
|
+
|
|
11
|
+
## Backend — glossr-api (828 ln) vs snap-backend (404 ln)
|
|
12
|
+
|
|
13
|
+
| Section | Same | Disposition |
|
|
14
|
+
|---|---|---|
|
|
15
|
+
| General TypeScript Guidelines | 100% | → `nestjs` |
|
|
16
|
+
| NestJS Module Structure | 100% | → `nestjs` |
|
|
17
|
+
| Services | 100% | → `nestjs` |
|
|
18
|
+
| Error Handling | 100% | → `nestjs` |
|
|
19
|
+
| Security | 100% | → `nestjs` |
|
|
20
|
+
| Constants | 100% | → `nestjs` |
|
|
21
|
+
| Enums | 100% | → `nestjs` |
|
|
22
|
+
| Code Quality | 100% | → `nestjs` |
|
|
23
|
+
| File Organization | 93% | → `nestjs` (union; glossr's `interfaces/` kept) |
|
|
24
|
+
| File Naming Conventions | 80% | → `nestjs`, DTO lines to `dto-naming` slot |
|
|
25
|
+
| TypeORM Entities | 100% | → `typeorm` |
|
|
26
|
+
| Database Query Decomposition Pattern | 100% | → `typeorm` |
|
|
27
|
+
| Mappers | 100% | → `typeorm` — **but see finding 1** |
|
|
28
|
+
| Database Operations | 85% | → `typeorm` (glossr's stronger wording) |
|
|
29
|
+
| Query Configuration | 75% | → `typeorm` (glossr is a strict superset) |
|
|
30
|
+
| Validation | 43% | spine → `nestjs`, rest → `validation-stack` slot |
|
|
31
|
+
| DTOs | 28% | spine → `nestjs`, rest → `dto-definition` slot |
|
|
32
|
+
| Controllers | 16% | spine → `nestjs`, rest → `controller-response` slot |
|
|
33
|
+
| Response Transformation | disjoint | entirely `response-transformation` slot |
|
|
34
|
+
| **Testing** | **7%** | **stays project-owned** |
|
|
35
|
+
| Interfaces, Error Codes, No Magic Strings, Pagination | glossr only | → `nestjs` (promoted, generic) |
|
|
36
|
+
| QueryBuilder Field Selection, Migrations | glossr only | → `typeorm` (promoted, generic) |
|
|
37
|
+
| Redis Key Naming | glossr only | **stays project-owned** — names a specific app prefix |
|
|
38
|
+
|
|
39
|
+
### The backend divergence is one axis, not entropy
|
|
40
|
+
|
|
41
|
+
Controllers 16%, DTOs 28%, Validation 43% and the two mutually exclusive Response
|
|
42
|
+
Transformation sections are all the same disagreement:
|
|
43
|
+
|
|
44
|
+
```
|
|
45
|
+
glossr-api Zod nestjs-zod, createZodDto, @ZodResponse
|
|
46
|
+
snap-backend class-* class-validator, class-transformer, @ResponseMapper
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
That is a **choice with two known values**, not accumulated rot — so it is a slot
|
|
50
|
+
with two variants, not a reason to fork the file. The file-level 43% reading
|
|
51
|
+
suggested "leave it alone"; the section-level reading says most of the document is
|
|
52
|
+
shareable and one decision is not.
|
|
53
|
+
|
|
54
|
+
## Frontend — glossr-frontend (231 ln) vs snap-frontend (191 ln)
|
|
55
|
+
|
|
56
|
+
| Section | Same | Disposition |
|
|
57
|
+
|---|---|---|
|
|
58
|
+
| Preamble (Angular rules) | 100% | → `angular` |
|
|
59
|
+
| Theming and Colors (+ 4 subsections, 94 ln) | 100% | → `angular` |
|
|
60
|
+
| Component Selectors | 100% | → `angular` |
|
|
61
|
+
| Constants | 100% | → `angular` |
|
|
62
|
+
| Enums | 100% | → `angular` |
|
|
63
|
+
| Translation Key Naming / Examples | 100% | → `angular` |
|
|
64
|
+
| Translations — sourcing | 48% | → `translation-source` slot |
|
|
65
|
+
| Change detection, Comments | snap only | → `angular` (promoted, generic) |
|
|
66
|
+
|
|
67
|
+
Cleaner than the backend: after the sourcing slot is carved out, essentially the
|
|
68
|
+
whole document is shared.
|
|
69
|
+
|
|
70
|
+
## CLI — glossr-cli (203 ln)
|
|
71
|
+
|
|
72
|
+
Not extracted. It shares no section headings with either pair, and per
|
|
73
|
+
`DESIGN.md` it is a stack of one, so nothing can be measured against it. A
|
|
74
|
+
`presets/cli/` should wait for a second CLI repo or a deliberate hand-authored
|
|
75
|
+
decision — not be reverse-engineered from a sample of one.
|
|
76
|
+
|
|
77
|
+
## Deliberately not extracted
|
|
78
|
+
|
|
79
|
+
- **Testing (7% same).** Two genuinely different regimes: glossr keeps all tests
|
|
80
|
+
under `test/` with integration helpers and factories; snap colocates `.spec.ts`
|
|
81
|
+
and targets >80% coverage. Extracting this would force one project onto the
|
|
82
|
+
other's layout.
|
|
83
|
+
- **Redis Key Naming.** Correct guidance, but it hardcodes `REDIS_APP_PREFIX =
|
|
84
|
+
'glossr'`. Generalising it would leave a rule with no concrete value in it.
|
|
85
|
+
- **`code-guidelines.md` as a whole remains `fork` mode** per `DESIGN.md`. These
|
|
86
|
+
presets are seeds. A repo that takes one owns its copy; cairn does not plan to
|
|
87
|
+
rewrite these files on upgrade.
|
|
88
|
+
|
|
89
|
+
## Findings
|
|
90
|
+
|
|
91
|
+
Two things surfaced only because the content was read section by section.
|
|
92
|
+
|
|
93
|
+
### 1. glossr-api's Mappers section documents a decorator it does not have
|
|
94
|
+
|
|
95
|
+
The section is byte-identical to snap-backend's and says:
|
|
96
|
+
|
|
97
|
+
> Basic entity-to-DTO conversion (use `@ResponseMapper` decorator)
|
|
98
|
+
|
|
99
|
+
`grep -rl ResponseMapper glossr-api/src` returns **nothing**. glossr moved to Zod;
|
|
100
|
+
the Mappers section was copied and never updated. It has been instructing agents to
|
|
101
|
+
reach for a decorator that does not exist in that codebase.
|
|
102
|
+
|
|
103
|
+
**A 100%-identical section is not automatically a correct one.** Identical means
|
|
104
|
+
"nobody has diverged", which includes "nobody has noticed". The preset version now
|
|
105
|
+
points at the response-transformation slot instead of naming a decorator.
|
|
106
|
+
|
|
107
|
+
### 2. Stale line-number cross-references
|
|
108
|
+
|
|
109
|
+
`glossr-api` contains *"See **Query Configuration** section below (lines 232-325)"*
|
|
110
|
+
— the section actually starts at line 323. Both repos contain *"Create Maps for O(1)
|
|
111
|
+
lookup (per guideline line 122)"*. Line numbers in a document that gets edited are
|
|
112
|
+
wrong the moment it is edited. Replaced with section-name references.
|
|
113
|
+
|
|
114
|
+
Also fixed in passing, present identically in both frontend repos: the typos
|
|
115
|
+
`cosntants` and `inrastructure`. Identical typos across two repos are the clearest
|
|
116
|
+
possible proof of copy-paste, and are a decent cheap signal for finding more.
|
|
117
|
+
|
|
118
|
+
## Agents, workflow, commands — 2026-09-09
|
|
119
|
+
|
|
120
|
+
Second pass, `snap-proof/{backend,frontend}` again. The 2026-07-31 survey scored the
|
|
121
|
+
reviewer contract at 85–92% and treated that as a ceiling. Reading them in full, the
|
|
122
|
+
**structure** is closer to identical — the divergence is confined to stack-specific
|
|
123
|
+
rule tables and reference lists, which are exactly slot-shaped.
|
|
124
|
+
|
|
125
|
+
| artefact | disposition | measurement |
|
|
126
|
+
|---|---|---|
|
|
127
|
+
| `.claude/commands/*.md` | → `core/commands/_stub.md` | every stub byte-identical modulo `{{ step.agentName }}` + `{{ step.agentDoc }}` (frontend copies carry a stray BOM) |
|
|
128
|
+
| `ai/agents/{node,angular}-reviewer.md` | contract → `core/agents/reviewer.md`; Dimension-2 table → `{nestjs,angular}/slots/review-checklist.md` | `diff` shows ~15 changed lines out of 160, all in the rule table, the model-check string, and agent names |
|
|
129
|
+
| `ai/agents/{node,angular}-developer.md` | spine → `core/agents/developer.md`; "Implementation reference" → `{nestjs,angular}/slots/implementation-reference.md` | E.1–E.5 identical; frontend carried 3 safety notes the backend lacked (folded into core) |
|
|
130
|
+
| `ai/agents/{node-architect,architect-lead}.md` | Mode A/B + file-roles + Plan format → `core/agents/architect.md`; the rest → 5 small `{nestjs,angular}/slots/architect-*.md` | whole scaffold shared; slots are the mandatory-doc list, discussion topics, plan-step ordering, A.1 references |
|
|
131
|
+
| `ai/WORKFLOW.md` | → `core/WORKFLOW.md`, rendered from `workflow.steps[].tracker` + `tracker.*` | same document both sides; only the provider block, stage names, and sync-point actions differ — all config |
|
|
132
|
+
| `ai/AGENTS.md` | → `core/AGENTS.md` skeleton | same structure; rendering it removes live drift (frontend's still says "Jira" while its `.env` and commands are on YouTrack) |
|
|
133
|
+
|
|
134
|
+
### `react` + `drizzle` seeded from charityiq-app — 2026-09-09
|
|
135
|
+
|
|
136
|
+
`charityiq-app` is a separate product (single founder, UK charity SaaS), Next.js 16
|
|
137
|
+
App Router + Drizzle + Supabase + Inngest, at Sprint 0 — a scaffold with documented
|
|
138
|
+
intent (`CLAUDE.md` hard rules, `docs/adr/0001-stack.md`) and almost no code
|
|
139
|
+
(everything under `lib/` is a `TODO(Sprint N)` stub).
|
|
140
|
+
|
|
141
|
+
So the seed is **conventions and structure, not code patterns** — and there is no
|
|
142
|
+
second repo to diff against, so what is "React-generic" vs "charityiq-specific" is
|
|
143
|
+
hand-judged, not measured. Same limit as `preset-cli`.
|
|
144
|
+
|
|
145
|
+
- `presets/react/` — components (folder-by-role: `ui`/`feature`/`brand`), no
|
|
146
|
+
`localStorage`, Tailwind v4 `@theme` tokens (no `tailwind.config.ts`), Vitest +
|
|
147
|
+
Testing Library + MSW + Playwright, Biome (tabs, double quotes). **Not** in it:
|
|
148
|
+
RSC, Server Actions, route handlers, the App Router — those are Next.js-specific
|
|
149
|
+
and belong in a future `presets/nextjs` that composes on top.
|
|
150
|
+
- `presets/drizzle/` — one schema module (`lib/db/schema.ts`), `drizzle-kit
|
|
151
|
+
generate` for mechanical migrations, hand-SQL only in `supabase/migrations/`, RLS
|
|
152
|
+
policies in the migration not the schema, server client never imported from a
|
|
153
|
+
`"use client"` module. Parallel to `presets/typeorm`.
|
|
154
|
+
- `presets/nextjs/` — the App-Router framework layer, added on top of `react`
|
|
155
|
+
(`--stack nextjs,react,…`, nextjs first so its fullstack plan ordering wins). Owns
|
|
156
|
+
the server/client boundary (`"use client"` discipline, no server-only imports
|
|
157
|
+
across it), Server Actions (`'use server'`, validate args, revalidate after a
|
|
158
|
+
write), Route Handlers (webhooks, verify caller), the App-Router file conventions,
|
|
159
|
+
`next/font` / `next/image` / `Metadata`, and a "the framework moves fast, read the
|
|
160
|
+
installed docs" note. **Not** in it: `requireTier()` + `audit.log()` on every
|
|
161
|
+
mutation — that is charityiq's business rule, tier-3 fork, not Next.js-generic.
|
|
162
|
+
- `presets/tailwind/` — styling is a third composable axis, orthogonal to the
|
|
163
|
+
framework (proof: Angular repos use SCSS `theme.$variable`, charityiq uses
|
|
164
|
+
Tailwind `@theme`, either could use either). The generic rules: tokens in a
|
|
165
|
+
`@theme` block not a `tailwind.config.ts`, no raw hex in markup, dark mode by
|
|
166
|
+
re-mapping semantic tokens. The React preset was made styling-agnostic to match.
|
|
167
|
+
Angular's SCSS theming stays inline in `presets/angular` for now — one system
|
|
168
|
+
across its two repos, not worth its own preset until a second SCSS-theming repo
|
|
169
|
+
appears (then: `presets/scss-theme`).
|
|
170
|
+
|
|
171
|
+
Stays charityiq-owned (tier 3, `fork`): the hard business rules — `requireTier()` +
|
|
172
|
+
`audit.log()` on every mutation, RAG-grounded AI through `lib/ai/client.ts`, the
|
|
173
|
+
audit row in the same transaction as the state change — plus `docs/adr/`, the build
|
|
174
|
+
guide, and the versioned-prompt registry (`docs/prompts/<name>/<version>.md`). Those
|
|
175
|
+
are the project's constitution, not shareable scaffolding.
|
|
176
|
+
|
|
177
|
+
### The nestjs slots were split nestjs / typeorm — 2026-09-09
|
|
178
|
+
|
|
179
|
+
The first cut of `presets/nestjs/slots/*` (review-checklist, implementation-reference,
|
|
180
|
+
key-patterns, architect-mandatory-docs, architect-discussion-topics) mixed framework
|
|
181
|
+
rules with TypeORM rules. Split: the framework half stays in `nestjs/slots/`, the ORM
|
|
182
|
+
half moved to `presets/typeorm/slots/` under the same filenames. The renderer appends
|
|
183
|
+
one from each `stack[]` entry, so `--stack nestjs,typeorm` reconstitutes the original
|
|
184
|
+
and `--stack nestjs,drizzle` would pair the framework rules with a Drizzle slot set
|
|
185
|
+
instead. `plan-step-ordering` and `architect-references` are primary-stack-only (not
|
|
186
|
+
concatenated) — noted in their headers.
|
|
187
|
+
|
|
188
|
+
### QA agents — preset-level, not extracted
|
|
189
|
+
|
|
190
|
+
Backend runs three (`qa-architect` / `qa-developer` / `qa-orchestrator`, ~630 ln
|
|
191
|
+
of Jest + factories + `src/feature/` layout + `yarn test:db` scripts); frontend runs
|
|
192
|
+
one (`angular-qa`, Playwright + `data-testid` + page objects). 33% similar at best,
|
|
193
|
+
confirming the 2026-07-31 read. The bodies are one project's test setup, not
|
|
194
|
+
shareable content — vendoring them as a "preset" would over-claim. They belong under
|
|
195
|
+
`{nestjs,angular}/agents/` once a second repo on the same stack shows what is
|
|
196
|
+
actually shared, the same "no peer" limit `preset-cli` has.
|
|
197
|
+
|
|
198
|
+
### Rot the renderer removes
|
|
199
|
+
|
|
200
|
+
- backend `node-reviewer.md` still requires `claude-sonnet-4-6` — frontend already says `claude-sonnet-5`
|
|
201
|
+
- frontend `AGENTS.md` + `WORKFLOW.md` are entirely Jira; the repo is on YouTrack (`.env`, `.claude/commands/youtrack-*`)
|
|
202
|
+
- same review step is `code_review` in backend, `review_plan` in frontend — one verb, rendered from `workflow.steps[].command`
|
|
203
|
+
- backend reviewer/developer hard-code an `ai/tasks/PROOF-[task]/` path — breaks on `LOCAL-` folders; the core versions use `ai/tasks/[task]/`
|
|
204
|
+
|
|
205
|
+
## Not wired up yet
|
|
206
|
+
|
|
207
|
+
There is no renderer. Nothing consumes `{{> slot: … }}` or `{{> stack/… }}`, and no
|
|
208
|
+
repo reads these files. They are the input for the agent-preset roadmap step
|
|
209
|
+
(`DESIGN.md`, "### Sequencing" step 4), and are already useful as the canonical
|
|
210
|
+
version of scaffolding that currently exists as two drifting copies.
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# Presets
|
|
2
|
+
|
|
3
|
+
Extracted guideline content shared across the repos. Internal only — an in-repo
|
|
4
|
+
folder, deliberately package-shaped so promotion to `@stonepandastudio/cairn-preset-*` is
|
|
5
|
+
adding a `package.json` rather than a refactor.
|
|
6
|
+
|
|
7
|
+
Nothing here is wired into a renderer yet. These are the *inputs* the renderer will
|
|
8
|
+
consume once the projects layer and extension mechanisms land (see `DESIGN.md`,
|
|
9
|
+
"Multi-repo projects"). Until then they are seeds: readable, diffable, and already
|
|
10
|
+
useful as the canonical version of guidance that currently exists in two drifting
|
|
11
|
+
copies.
|
|
12
|
+
|
|
13
|
+
## Layout
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
presets/
|
|
17
|
+
core/ stack-agnostic — agent role contracts, command stub, WORKFLOW/AGENTS skeletons
|
|
18
|
+
agents/ architect.md, developer.md, reviewer.md (85–92% identical across stacks)
|
|
19
|
+
commands/ _stub.md (renders one file per workflow step)
|
|
20
|
+
nestjs/ NestJS + TypeScript framework spine, validation-stack + ORM agnostic
|
|
21
|
+
variants/ validation-zod.md, validation-class-validator.md
|
|
22
|
+
slots/ the framework side of each {{> stack/… }} point
|
|
23
|
+
typeorm/ ORM add-on — entities, queries, migrations, decomposition
|
|
24
|
+
slots/ the ORM side of the same {{> stack/… }} points (appended to nestjs)
|
|
25
|
+
drizzle/ ORM add-on — Drizzle schema, drizzle-kit migrations, RLS in migrations
|
|
26
|
+
slots/ the ORM side, for a Postgres/Supabase stack
|
|
27
|
+
angular/ Angular spine + SCSS theming + i18n (styling still inline here — one system, two repos)
|
|
28
|
+
variants/ i18n-glossr.md, i18n-external-service.md
|
|
29
|
+
slots/ the Angular side of the same {{> stack/… }} points
|
|
30
|
+
react/ React frontend spine — components, hooks, state, testing (styling-agnostic)
|
|
31
|
+
slots/ the React side of the same {{> stack/… }} points
|
|
32
|
+
nextjs/ App-Router layer over `react` — server/client boundary, actions, route handlers
|
|
33
|
+
slots/ the Next.js side of the same {{> stack/… }} points
|
|
34
|
+
tailwind/ styling add-on — @theme tokens, no tailwind.config.ts, dark mode via token re-map
|
|
35
|
+
slots/ the styling side of the same {{> stack/… }} points
|
|
36
|
+
EXTRACTION.md what was extracted from where, and what deliberately was not
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Compose the slots a repo needs. Rough axes — **framework** (`nestjs`, `react`,
|
|
40
|
+
`nextjs` on top of `react`), **ORM** (`typeorm` / `drizzle`), **styling** (`tailwind`):
|
|
41
|
+
|
|
42
|
+
```
|
|
43
|
+
--stack nestjs,typeorm snap-proof / glossr backend
|
|
44
|
+
--stack angular snap-proof / glossr frontend (SCSS theming inline for now)
|
|
45
|
+
--stack nextjs,react,tailwind,drizzle charityiq — nextjs primary (owns plan ordering + fullstack refs)
|
|
46
|
+
--stack react,tailwind a Vite SPA against a separate API
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
`typeorm` and `angular` are seeded from two repos; `drizzle`, `react`, `nextjs`,
|
|
50
|
+
`tailwind` from one (charityiq). A single-consumer preset is hand-judged, not
|
|
51
|
+
measured, and gets refined when a second repo on that axis appears — the
|
|
52
|
+
`preset-cli` "no peer" limit.
|
|
53
|
+
|
|
54
|
+
`nestjs` and `typeorm` are separate on purpose. Every backend repo composes both
|
|
55
|
+
today, but the split is what proves the composition model works — a future backend
|
|
56
|
+
on Prisma or Drizzle takes `nestjs` without `typeorm`, and a repo declares the
|
|
57
|
+
composition as `--stack nestjs,typeorm`.
|
|
58
|
+
|
|
59
|
+
## Slots
|
|
60
|
+
|
|
61
|
+
A slot marks a point where a shared template knows a decision exists but not which
|
|
62
|
+
way a repo (or a stack) went. Two flavours:
|
|
63
|
+
|
|
64
|
+
- **`{{> slot: validation-stack }}`** in a `code-guidelines.md` — the repo picks a
|
|
65
|
+
file from `variants/` by name. Where a repo needs something no variant covers, it
|
|
66
|
+
supplies its own partial — the signal that a third variant may be warranted.
|
|
67
|
+
- **`{{> stack/review-checklist }}`** in a `core/` agent contract — filled from each
|
|
68
|
+
stack entry's `slots/` folder. Two fill modes:
|
|
69
|
+
- **concatenated** — the renderer appends the slot from every entry in `stack[]`,
|
|
70
|
+
in order. `--stack nestjs,typeorm` → `review-checklist` is the nestjs framework
|
|
71
|
+
rows followed by the typeorm ORM rows. Used for checklists, doc lists, reference
|
|
72
|
+
lists.
|
|
73
|
+
- **primary-only** — the slot is taken from `stack[0]` alone, because it is one
|
|
74
|
+
coherent thing that cannot be concatenated. `plan-step-ordering` (a numbered
|
|
75
|
+
sequence) and `architect-references` are primary-only; their file header says so.
|
|
76
|
+
|
|
77
|
+
Slots exist to keep the merge cheap. A template update that does not touch a slot
|
|
78
|
+
never conflicts with a repo's choice, which is the whole reason the extension
|
|
79
|
+
mechanisms are three rather than one.
|
|
80
|
+
|
|
81
|
+
## What is deliberately absent
|
|
82
|
+
|
|
83
|
+
Sections that are genuinely per-project stay per-project — they are listed in
|
|
84
|
+
`EXTRACTION.md` with the measurement that justified leaving them out. Extracting
|
|
85
|
+
content that *should* differ is the failure mode this whole exercise is designed to
|
|
86
|
+
avoid: it would report every legitimate project difference as drift, forever.
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
# Code Guidelines
|
|
2
|
+
|
|
3
|
+
<!-- cairn preset: angular -->
|
|
4
|
+
|
|
5
|
+
* In angular templates use new template syntax instead of old `*ng` syntax.
|
|
6
|
+
* Unsubscribe from observables in `ngOnDestroy`
|
|
7
|
+
* Use standalone Angular components by default, refactor component to standalone if it's not standalone component
|
|
8
|
+
* Every utility function, model, constant or mapping function should stay in separated file.
|
|
9
|
+
* For file names use dash-case (Example: `my-long-module.module.ts`)
|
|
10
|
+
* Use Reactive Forms for any new forms.
|
|
11
|
+
* All data access is handled through dedicated services.
|
|
12
|
+
* Complex data manipulation is placed in utils files
|
|
13
|
+
|
|
14
|
+
## Theming and Colors
|
|
15
|
+
|
|
16
|
+
The application uses a CSS variables-based theming system with the following architecture:
|
|
17
|
+
|
|
18
|
+
**Chain**: Global CSS Variables → SCSS Palette Variables → Abstract SCSS Variables → Component `.scss` files
|
|
19
|
+
|
|
20
|
+
### Architecture Layers
|
|
21
|
+
|
|
22
|
+
1. **Global CSS Variables** (`src/scss/themes/default/colors.scss`)
|
|
23
|
+
- Defines actual color values as CSS custom properties
|
|
24
|
+
- Example: `--fotofixer-color-primary: #00aabc;`
|
|
25
|
+
- Changing these allows dynamic theme switching without page refresh
|
|
26
|
+
|
|
27
|
+
2. **SCSS Palette Variables** (`src/scss/themes/colors.scss`)
|
|
28
|
+
- Maps CSS variables to SCSS variables using `var()` function
|
|
29
|
+
- Example: `$fotofixer-color-primary: var(--fotofixer-color-primary);`
|
|
30
|
+
- **DO NOT import this file directly in component SCSS**
|
|
31
|
+
|
|
32
|
+
3. **Abstract SCSS Variables** (`src/scss/themes/theme.scss`)
|
|
33
|
+
- Provides semantic/abstract names for colors
|
|
34
|
+
- Example: `$font-color-secondary: colors.$main-color-main-10;`
|
|
35
|
+
- **This is the only file you should import in component SCSS files**
|
|
36
|
+
|
|
37
|
+
### Rules for Using Colors in SCSS Files
|
|
38
|
+
|
|
39
|
+
* **DO NOT** use hardcoded colors (e.g., `#ffffff`, `white`, `rgb(...)`)
|
|
40
|
+
* **DO NOT** use CSS variables directly (e.g., `var(--main-color-main-11)`)
|
|
41
|
+
* **DO NOT** import `colors.scss` directly
|
|
42
|
+
* **DO** import `theme.scss` using `@use` directive
|
|
43
|
+
* **DO** use abstract SCSS variables from `theme.scss`
|
|
44
|
+
* **DO** add new abstract variables to `theme.scss` if needed
|
|
45
|
+
|
|
46
|
+
### Example Usage
|
|
47
|
+
|
|
48
|
+
```scss
|
|
49
|
+
@use "themes/theme" as theme;
|
|
50
|
+
|
|
51
|
+
.my-component {
|
|
52
|
+
background-color: theme.$background-color-white;
|
|
53
|
+
border: 1px solid theme.$border-color-secondary;
|
|
54
|
+
color: theme.$font-color;
|
|
55
|
+
|
|
56
|
+
.secondary-text {
|
|
57
|
+
color: theme.$font-color-secondary;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.muted-text {
|
|
61
|
+
color: theme.$font-color-muted;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
.primary-action {
|
|
65
|
+
background-color: theme.$fotofixer-color-primary;
|
|
66
|
+
|
|
67
|
+
&:hover {
|
|
68
|
+
background-color: theme.$fotofixer-color-hover;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Available Abstract Variables (from `theme.scss`)
|
|
75
|
+
|
|
76
|
+
**Primary Colors:**
|
|
77
|
+
- `$fotofixer-color-primary` - Main brand color (cyan)
|
|
78
|
+
- `$fotofixer-color-secondary` - Light brand color
|
|
79
|
+
- `$fotofixer-color-tetriary` - Very light brand color
|
|
80
|
+
- `$fotofixer-color-hover` - Brand hover state
|
|
81
|
+
|
|
82
|
+
**Background Colors:**
|
|
83
|
+
- `$background-color-main` - Main background (light gray)
|
|
84
|
+
- `$background-color-secondary` - Secondary background
|
|
85
|
+
- `$background-color-secondary-hover` - Secondary hover state
|
|
86
|
+
- `$background-color-white` - White background
|
|
87
|
+
|
|
88
|
+
**Font Colors:**
|
|
89
|
+
- `$font-color` - Main text color (black)
|
|
90
|
+
- `$font-color-navbar` - Navbar text
|
|
91
|
+
- `$font-color-secondary` - Secondary text (dark gray)
|
|
92
|
+
- `$font-color-muted` - Muted/disabled text (medium gray)
|
|
93
|
+
- `$invert-font-color` - Inverted text (white)
|
|
94
|
+
|
|
95
|
+
**Border Colors:**
|
|
96
|
+
- `$border-color` - Main border color
|
|
97
|
+
- `$border-color-secondary` - Secondary border (light gray)
|
|
98
|
+
- `$divider-color` - Divider lines (very light gray)
|
|
99
|
+
|
|
100
|
+
**Message Colors:**
|
|
101
|
+
- `$error-message-color` - Error text
|
|
102
|
+
- `$error-message-background-color` - Error background
|
|
103
|
+
- `$success-message-color` - Success text
|
|
104
|
+
|
|
105
|
+
**Other:**
|
|
106
|
+
- `$disabled-color` - Disabled state
|
|
107
|
+
- `$link-hover-color` - Link hover state
|
|
108
|
+
|
|
109
|
+
## Component Selectors
|
|
110
|
+
|
|
111
|
+
* Do NOT use `app-` prefix for component selectors in new components
|
|
112
|
+
* Use descriptive, kebab-case selector names (Example: `popover`, `project-status-icon`, `connected-projects-popover`)
|
|
113
|
+
* Selector should match the component's purpose without generic prefixes
|
|
114
|
+
|
|
115
|
+
## Translations and Internationalization
|
|
116
|
+
|
|
117
|
+
### General Rules
|
|
118
|
+
|
|
119
|
+
* Always use translation keys for static text in templates.
|
|
120
|
+
* For dynamic text in templates use translation keys when possible. If not possible, add a comment explaining why.
|
|
121
|
+
* **Locale files are generated artifacts.** Never hand-edit them and never add a key to one to make a feature render — any manual change is destroyed by the next sync.
|
|
122
|
+
|
|
123
|
+
Where translations come from, and how new keys are submitted, is project-specific:
|
|
124
|
+
|
|
125
|
+
{{> slot: translation-source }}
|
|
126
|
+
|
|
127
|
+
### Translation Key Naming Conventions
|
|
128
|
+
|
|
129
|
+
* Use PascalCase for naming translation keys.
|
|
130
|
+
* Key structure depends on context:
|
|
131
|
+
* **Common text (no specific context)**: Use 2 parts - `"Common.SaveButton"`, `"Common.CancelButton"`
|
|
132
|
+
* **Context-specific text**: Use nested structure - `"[PageName].[Subcontext].[TextDescription]"`
|
|
133
|
+
|
|
134
|
+
### Examples
|
|
135
|
+
|
|
136
|
+
**Common keys:**
|
|
137
|
+
```json
|
|
138
|
+
"Common": {
|
|
139
|
+
"SaveButton": "Save",
|
|
140
|
+
"CancelButton": "Cancel",
|
|
141
|
+
"Loading": "Loading..."
|
|
142
|
+
}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
**Page-specific keys:**
|
|
146
|
+
```json
|
|
147
|
+
"DevicesTable": {
|
|
148
|
+
"Title": "Devices Table",
|
|
149
|
+
"Header": {
|
|
150
|
+
"CityColumn": "City",
|
|
151
|
+
"DeviceColumn": "Device"
|
|
152
|
+
},
|
|
153
|
+
"Button": {
|
|
154
|
+
"DownloadReport": "Download Report"
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
**Message keys:**
|
|
160
|
+
```json
|
|
161
|
+
"Messages": {
|
|
162
|
+
"Devices": {
|
|
163
|
+
"FailedLoadDevices": "Failed to load devices",
|
|
164
|
+
"FailedDownloadReport": "Failed to download report"
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### Checking Existing Keys
|
|
170
|
+
|
|
171
|
+
Before creating new translation keys, check the existing English locale file for examples and to avoid duplication. Read it only — see the generated-artifact rule above.
|
|
172
|
+
|
|
173
|
+
## Change detection
|
|
174
|
+
|
|
175
|
+
* **Match the strategy used by nearby/similar components.** The codebase mixes `Default` and `OnPush`; don't default to `OnPush` unless the surrounding components use it. Most page-level components currently use `ChangeDetectionStrategy.Default`.
|
|
176
|
+
* If you're adding a component to an area where everything is `Default`, use `Default`. If you're adding one next to `OnPush` components, use `OnPush`.
|
|
177
|
+
* Don't "upgrade" an existing component from `Default` to `OnPush` as a drive-by change — that's a focused refactor, not part of a feature plan.
|
|
178
|
+
|
|
179
|
+
## Comments
|
|
180
|
+
|
|
181
|
+
* **Default to no comments.** Good names carry meaning; small functions are self-documenting.
|
|
182
|
+
* Only write a comment when the *why* is non-obvious: a hidden constraint, a subtle invariant, a workaround for a specific bug, behavior that would surprise a reader.
|
|
183
|
+
* Don't explain *what* the code does — identifiers and structure already do that.
|
|
184
|
+
* Don't reference task IDs, PR flows, or "added for feature X" in code comments — that context belongs in commit messages and PR descriptions, and rots as the codebase evolves.
|
|
185
|
+
* No multi-paragraph docstrings or multi-line comment blocks — one short line is the ceiling.
|
|
186
|
+
|
|
187
|
+
## Constants
|
|
188
|
+
|
|
189
|
+
File name template: `xx-yy.constant.ts` for `XxYy` constant
|
|
190
|
+
|
|
191
|
+
Files with constants should be placed in the `constants/` folder within the corresponding module (see `ai/infrastructure/project-structure.md` for module details)
|
|
192
|
+
|
|
193
|
+
## Enums
|
|
194
|
+
|
|
195
|
+
File name template: `xx-yy.enum.ts` for `XxYy` enum
|
|
196
|
+
|
|
197
|
+
Files with enums should be placed in the `enums/` folder within the corresponding module (see `ai/infrastructure/project-structure.md` for module details)
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
<!-- cairn preset: angular — fills {{> stack/architect-discussion-topics }} (A.3). -->
|
|
2
|
+
- **Ambiguities** — anything in the description that admits multiple reasonable interpretations
|
|
3
|
+
- **UX / design tradeoffs** — popup vs. page, tabs vs. stacked sections, inline vs. separate component
|
|
4
|
+
- **Integration tensions** — e.g. global company state vs. route-scoped company id
|
|
5
|
+
|
|
6
|
+
**Optional aid — `frontend-design` skill.** When the discussion hinges on a
|
|
7
|
+
visual/UX tradeoff, `frontend-design` can explore mockups. **Treat its output as
|
|
8
|
+
inspiration only** — it does not know this project's conventions (Material UI,
|
|
9
|
+
`theme.scss` variables, base classes, new template syntax, no `app-` prefix,
|
|
10
|
+
translation keys). Never feed its generated code into the plan.
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
<!-- cairn preset: angular — fills {{> stack/architect-references }} (A.1). -->
|
|
2
|
+
- Frontend source files (components, services, base classes)
|
|
3
|
+
- The backend repo's context docs — entity definitions, business logic, controllers
|
|
4
|
+
- Backend source files, for API-shape confirmation
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
<!-- cairn preset: angular — fills {{> stack/implementation-reference }} in the core
|
|
2
|
+
developer. snap-proof/frontend/ai/agents/angular-developer.md has no block like
|
|
3
|
+
the nestjs one — it points at patterns.md + code-guidelines.md and stops.
|
|
4
|
+
This is a minimal synthesis from angular-developer + angular-reviewer; extend
|
|
5
|
+
it when a second Angular repo shows what is actually shared. -->
|
|
6
|
+
|
|
7
|
+
The authoritative source is `ai/infrastructure/code-guidelines.md`; the base-class
|
|
8
|
+
and header-flow catalogue is `ai/infrastructure/patterns.md`. Reminders:
|
|
9
|
+
|
|
10
|
+
### Components
|
|
11
|
+
- Standalone components — no `NgModule` declarations block
|
|
12
|
+
- No `app-` prefix in the selector
|
|
13
|
+
- Match the change-detection strategy of nearby components
|
|
14
|
+
- Subscriptions collected in a `Subscription`, unsubscribed in `ngOnDestroy`
|
|
15
|
+
- Complex data manipulation goes in a `utils/` file, not inlined
|
|
16
|
+
|
|
17
|
+
### Templates
|
|
18
|
+
- Control flow: `@if` / `@for` / `@switch` — never `*ngIf` / `*ngFor`
|
|
19
|
+
- Every `@for` carries a `track` expression
|
|
20
|
+
- Every static string is a translation key — no bare literals
|
|
21
|
+
|
|
22
|
+
### Styles
|
|
23
|
+
- Only `theme.$variable` from `@use "themes/theme" as theme` — no `#hex`, `rgb()`, or CSS custom properties
|
|
24
|
+
|
|
25
|
+
### Forms
|
|
26
|
+
- Reactive forms only — no `ngModel`
|
|
27
|
+
|
|
28
|
+
### Files
|
|
29
|
+
- Models, enums, constants, and mapper functions each in their own kebab-case file
|
|
30
|
+
- `enums/`, `constants/`, `mappers/` folders per module
|
|
31
|
+
- Mapper shapes: `EntityResponse → Entity`, `Entity ↔ EntityForm`, `Entity → EntityPayload`
|
|
32
|
+
|
|
33
|
+
### Build
|
|
34
|
+
- Run the project's production build before reporting completion; a failing build blocks the report
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
<!-- cairn preset: angular — fills {{> stack/key-patterns }} in the core architect
|
|
2
|
+
and developer. From architect-lead.md / angular-developer.md. -->
|
|
3
|
+
|
|
4
|
+
Canonical catalogue: **`ai/infrastructure/patterns.md`** — base classes, the
|
|
5
|
+
`x-admin-profile` header flow, routing. Plans should lean on these instead of
|
|
6
|
+
reinventing them.
|
|
7
|
+
|
|
8
|
+
Micro-level conventions (theming, translations, template syntax, change detection,
|
|
9
|
+
file naming): **`ai/infrastructure/code-guidelines.md`**.
|
|
10
|
+
|
|
11
|
+
Mapper conventions: **`ai/infrastructure/mappers.md`**.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
<!-- cairn preset: angular — fills {{> stack/plan-step-ordering }} in the core
|
|
2
|
+
architect's Plan format. architect-lead.md gives no explicit ordering beyond
|
|
3
|
+
"each step leaves the app working"; this is a synthesis, refine against a
|
|
4
|
+
second Angular repo. -->
|
|
5
|
+
1. Models, enums, constants, mapper functions
|
|
6
|
+
2. Service methods (API calls, state)
|
|
7
|
+
3. Component logic (`.ts`)
|
|
8
|
+
4. Template (`.html`) and styles (`.scss`)
|
|
9
|
+
5. Routing and module wiring
|
|
10
|
+
6. Translation keys
|
|
11
|
+
7. Context doc updates
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<!-- cairn preset: angular — fills {{> stack/review-checklist }} in the core reviewer.
|
|
2
|
+
Verbatim from snap-proof/frontend/ai/agents/angular-reviewer.md § Dimension 2. -->
|
|
3
|
+
|
|
4
|
+
| New template syntax (`@if`, `@for`, `@switch`) — no `*ngIf`/`*ngFor` | `.html` files |
|
|
5
|
+
| `@for` always includes a `track` expression — missing `track` is a compile error in Angular 17+ | `.html` files |
|
|
6
|
+
| Standalone components — no `NgModule` declarations block | Component `.ts` files |
|
|
7
|
+
| Observables subscribed inside components are collected in a `Subscription` and unsubscribed in `ngOnDestroy` | Component `.ts` files |
|
|
8
|
+
| No hardcoded colors (`#hex`, `rgb()`, CSS custom properties) — only `theme.$variable` from `@use "themes/theme" as theme` | `.scss` files |
|
|
9
|
+
| No `app-` prefix in component selectors | Component `.ts` files |
|
|
10
|
+
| All static text in templates uses translation keys — no bare string literals | `.html` files |
|
|
11
|
+
| `src/assets/i18n/*.json` never hand-edited — autogenerated, replaced during localization — **blocking** if the diff touches these files directly | `src/assets/i18n/*.json` |
|
|
12
|
+
| Reactive forms only — no template-driven forms (`ngModel`) | Component `.ts` files |
|
|
13
|
+
| Complex data manipulation goes in `utils/` files, not inlined in components | Component `.ts` files |
|
|
14
|
+
| Mapper pattern respected: `EntityResponse → Entity`, `Entity ↔ EntityForm`, `Entity → EntityPayload` | `mappers/` files |
|
|
15
|
+
| Models, enums, constants, and mapper functions each in their own kebab-case file | Module folder structure |
|
|
16
|
+
| Enums in `enums/` folder, constants in `constants/` folder, mappers in `mappers/` folder | Module structure |
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
<!-- cairn variant: translation-source = external-service. Fills the
|
|
2
|
+
translation-source slot in presets/angular/code-guidelines.md.
|
|
3
|
+
In use by: snap-frontend. A third-party localization vendor owns the values. -->
|
|
4
|
+
|
|
5
|
+
## slot: translation-source
|
|
6
|
+
|
|
7
|
+
Never add or update translation values for translation keys in `src/assets/i18n/*.json` files directly. These are autogenerated files that will be replaced by a third-party service during the localization process.
|
|
8
|
+
|
|
9
|
+
### Checking Existing Keys
|
|
10
|
+
|
|
11
|
+
Before creating new translation keys, check existing keys in `src/assets/i18n/en-us.json` for examples and to avoid duplication.
|