@horka/app-forge 0.1.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/LICENSE +32 -0
- package/README.md +99 -0
- package/bin/cli.js +371 -0
- package/bin/cli.test.js +91 -0
- package/package.json +43 -0
- package/templates/core/CLAUDE.md +36 -0
- package/templates/core/claude/memory/ARCHITECTURE.md +20 -0
- package/templates/core/claude/memory/COMMANDS.md +13 -0
- package/templates/core/claude/memory/DECISIONS.md +5 -0
- package/templates/core/claude/memory/NEXT_STEPS.md +11 -0
- package/templates/core/claude/memory/PROJECT_STATE.md +24 -0
- package/templates/core/claude/skills/kickoff/SKILL.md +84 -0
- package/templates/core/claude/skills/product-owner/SKILL.md +58 -0
- package/templates/core/claude/skills/restore-context/SKILL.md +29 -0
- package/templates/core/claude/skills/save-context/SKILL.md +35 -0
- package/templates/core/docs-architecture/ANTI_PATTERNS.md +180 -0
- package/templates/core/docs-architecture/ARCHITECTURE_PRINCIPLES.md +134 -0
- package/templates/core/docs-architecture/DELIVERY.md +68 -0
- package/templates/core/docs-architecture/DOCS_PLACEMENT.md +151 -0
- package/templates/core/docs-architecture/MULTI_REPO_CONTRACT.md +158 -0
- package/templates/core/docs-architecture/SDK_CONTRACT.md +214 -0
- package/templates/core/docs-architecture/SECURITY_USER_URLS.md +152 -0
- package/templates/core/gitignore +15 -0
- package/templates/core/mcp.json +8 -0
- package/templates/packs/nuxt-web/CLAUDE.md +74 -0
- package/templates/packs/nuxt-web/app/app.vue +5 -0
- package/templates/packs/nuxt-web/app/assets/css/main.css +18 -0
- package/templates/packs/nuxt-web/app/assets/css/tokens.css +41 -0
- package/templates/packs/nuxt-web/app/designSystem/DSButton/components/DSButton.vue +70 -0
- package/templates/packs/nuxt-web/app/designSystem/DSButton/index.ts +4 -0
- package/templates/packs/nuxt-web/app/designSystem/DSButton/tests/DSButton.spec.ts +34 -0
- package/templates/packs/nuxt-web/app/designSystem/DSButton/types/dsButton.ts +5 -0
- package/templates/packs/nuxt-web/app/domain/.gitkeep +0 -0
- package/templates/packs/nuxt-web/app/features/.gitkeep +0 -0
- package/templates/packs/nuxt-web/app/pages/index.vue +36 -0
- package/templates/packs/nuxt-web/app/utils/.gitkeep +0 -0
- package/templates/packs/nuxt-web/claude/memory/COMMANDS.md +21 -0
- package/templates/packs/nuxt-web/docs-architecture/ARCHITECTURE.md +169 -0
- package/templates/packs/nuxt-web/docs-architecture/CONVENTIONS.md +140 -0
- package/templates/packs/nuxt-web/docs-architecture/I18N.md +102 -0
- package/templates/packs/nuxt-web/docs-architecture/OPS_WEB.md +176 -0
- package/templates/packs/nuxt-web/docs-architecture/SEO_AND_ROUTING.md +118 -0
- package/templates/packs/nuxt-web/gitignore +18 -0
- package/templates/packs/nuxt-web/nuxt.config.ts +49 -0
- package/templates/packs/nuxt-web/pack.json +11 -0
- package/templates/packs/nuxt-web/package.json +31 -0
- package/templates/packs/nuxt-web/playwright.config.ts +39 -0
- package/templates/packs/nuxt-web/server/api/health.get.ts +7 -0
- package/templates/packs/nuxt-web/tests/e2e/home.spec.ts +19 -0
- package/templates/packs/nuxt-web/tsconfig.json +4 -0
- package/templates/packs/nuxt-web/vitest.config.ts +23 -0
- package/templates/packs/swift-ios/CLAUDE.md +64 -0
- package/templates/packs/swift-ios/Packages/DataLayer/Package.swift +21 -0
- package/templates/packs/swift-ios/Packages/DataLayer/Sources/DataLayer/DataLayer.swift +11 -0
- package/templates/packs/swift-ios/Packages/{{PROJECT_NAME}}Core/Package.swift +20 -0
- package/templates/packs/swift-ios/Packages/{{PROJECT_NAME}}Core/Sources/{{PROJECT_NAME}}Core/Domain/SampleItem.swift +15 -0
- package/templates/packs/swift-ios/Packages/{{PROJECT_NAME}}Core/Sources/{{PROJECT_NAME}}Core/Engine/SampleEngine.swift +14 -0
- package/templates/packs/swift-ios/Packages/{{PROJECT_NAME}}Core/Sources/{{PROJECT_NAME}}Core/Repository/SampleItemRepository.swift +27 -0
- package/templates/packs/swift-ios/Packages/{{PROJECT_NAME}}Core/Tests/{{PROJECT_NAME}}CoreTests/SampleEngineTests.swift +32 -0
- package/templates/packs/swift-ios/Packages/{{PROJECT_NAME}}DS/Package.swift +17 -0
- package/templates/packs/swift-ios/Packages/{{PROJECT_NAME}}DS/Sources/{{PROJECT_NAME}}DS/Color+DS.swift +18 -0
- package/templates/packs/swift-ios/Packages/{{PROJECT_NAME}}DS/Sources/{{PROJECT_NAME}}DS/Components/DSCard.swift +22 -0
- package/templates/packs/swift-ios/Packages/{{PROJECT_NAME}}DS/Sources/{{PROJECT_NAME}}DS/DS.swift +36 -0
- package/templates/packs/swift-ios/Packages/{{PROJECT_NAME}}DS/Sources/{{PROJECT_NAME}}DS/DSFont.swift +26 -0
- package/templates/packs/swift-ios/claude/memory/COMMANDS.md +18 -0
- package/templates/packs/swift-ios/docs-architecture/ARCHITECTURE.md +246 -0
- package/templates/packs/swift-ios/docs-architecture/CLOUDKIT_GUIDE.md +224 -0
- package/templates/packs/swift-ios/docs-architecture/CONVENTIONS.md +246 -0
- package/templates/packs/swift-ios/docs-architecture/DESIGN_SYSTEM.md +272 -0
- package/templates/packs/swift-ios/docs-architecture/NAVIGATION.md +241 -0
- package/templates/packs/swift-ios/docs-architecture/TESTING.md +176 -0
- package/templates/packs/swift-ios/docs-architecture/WORKFLOW.md +165 -0
- package/templates/packs/swift-ios/github/workflows/ci.yml +48 -0
- package/templates/packs/swift-ios/gitignore +5 -0
- package/templates/packs/swift-ios/mcp.json +8 -0
- package/templates/packs/swift-ios/pack.json +11 -0
- package/templates/packs/swift-ios/project.yml +33 -0
- package/templates/packs/swift-ios/{{PROJECT_NAME}}/App/App.swift +32 -0
- package/templates/packs/swift-ios/{{PROJECT_NAME}}/App/AppNamespace.swift +4 -0
- package/templates/packs/swift-ios/{{PROJECT_NAME}}/Module/.gitkeep +0 -0
- package/templates/packs/swift-ios/{{PROJECT_NAME}}/Store/.gitkeep +0 -0
- package/templates/packs/swift-ios/{{PROJECT_NAME}}/Tools/.gitkeep +0 -0
- package/templates/packs/ts-sdk/CHANGELOG.md +9 -0
- package/templates/packs/ts-sdk/CLAUDE.md +72 -0
- package/templates/packs/ts-sdk/MIGRATION.md +28 -0
- package/templates/packs/ts-sdk/claude/memory/COMMANDS.md +21 -0
- package/templates/packs/ts-sdk/docs-architecture/ARCHITECTURE.md +132 -0
- package/templates/packs/ts-sdk/docs-architecture/CONVENTIONS_TS.md +152 -0
- package/templates/packs/ts-sdk/gitignore +6 -0
- package/templates/packs/ts-sdk/pack.json +11 -0
- package/templates/packs/ts-sdk/package.json +55 -0
- package/templates/packs/ts-sdk/scripts/verify-dist.mjs +67 -0
- package/templates/packs/ts-sdk/src/clients/AuthClient.ts +168 -0
- package/templates/packs/ts-sdk/src/core/HttpClient.ts +85 -0
- package/templates/packs/ts-sdk/src/core/Logger.ts +27 -0
- package/templates/packs/ts-sdk/src/core/SDKContext.ts +40 -0
- package/templates/packs/ts-sdk/src/core/withTimeout.ts +19 -0
- package/templates/packs/ts-sdk/src/errors/ApiError.ts +93 -0
- package/templates/packs/ts-sdk/src/index.ts +62 -0
- package/templates/packs/ts-sdk/src/types/index.ts +33 -0
- package/templates/packs/ts-sdk/tests/apiError.test.ts +58 -0
- package/templates/packs/ts-sdk/tests/httpClient.test.ts +60 -0
- package/templates/packs/ts-sdk/tests/singleFlight.test.ts +191 -0
- package/templates/packs/ts-sdk/tsconfig.json +15 -0
- package/templates/packs/ts-sdk/tsup.config.ts +22 -0
- package/templates/packs/ts-sdk/vitest.config.ts +8 -0
- package/templates/packs/vapor-api/CLAUDE.md +73 -0
- package/templates/packs/vapor-api/Dockerfile +80 -0
- package/templates/packs/vapor-api/Package.swift +68 -0
- package/templates/packs/vapor-api/Sources/App/App.swift +5 -0
- package/templates/packs/vapor-api/Sources/App/Configure/AppConfig.swift +108 -0
- package/templates/packs/vapor-api/Sources/App/Configure/configure.swift +74 -0
- package/templates/packs/vapor-api/Sources/App/Configure/entrypoint.swift +47 -0
- package/templates/packs/vapor-api/Sources/App/Configure/routes.swift +21 -0
- package/templates/packs/vapor-api/Sources/App/Error/Failed.swift +73 -0
- package/templates/packs/vapor-api/Sources/App/Error/FailedMiddleware.swift +56 -0
- package/templates/packs/vapor-api/Sources/App/Features/Item/AppItem.swift +38 -0
- package/templates/packs/vapor-api/Sources/App/Features/Item/Controllers/ItemControllersCrud.swift +41 -0
- package/templates/packs/vapor-api/Sources/App/Features/Item/DTO/ItemDTO.swift +22 -0
- package/templates/packs/vapor-api/Sources/App/Features/Item/Entities/ItemEntity.swift +30 -0
- package/templates/packs/vapor-api/Sources/App/Features/Item/Migrations/ItemMigrationCreate.swift +25 -0
- package/templates/packs/vapor-api/Sources/App/Features/Item/Repositories/ItemRepository.swift +32 -0
- package/templates/packs/vapor-api/Sources/App/Features/Item/Services/ItemService.swift +57 -0
- package/templates/packs/vapor-api/Sources/App/Registry/ControllersRegister.swift +17 -0
- package/templates/packs/vapor-api/Sources/App/Registry/MiddlewaresRegister.swift +15 -0
- package/templates/packs/vapor-api/Sources/App/Registry/MigrationsRegister.swift +18 -0
- package/templates/packs/vapor-api/Sources/Monitoring/Logging/JSONLogHandler.swift +59 -0
- package/templates/packs/vapor-api/Sources/Monitoring/Middleware/HTTPLoggingMiddleware.swift +50 -0
- package/templates/packs/vapor-api/Sources/Monitoring/Monitoring.swift +110 -0
- package/templates/packs/vapor-api/Sources/{{PROJECT_NAME}}Foundation/String+Trimmed.swift +15 -0
- package/templates/packs/vapor-api/Tests/AppTests/AppTests.swift +155 -0
- package/templates/packs/vapor-api/claude/memory/COMMANDS.md +30 -0
- package/templates/packs/vapor-api/docs-architecture/ARCHITECTURE.md +144 -0
- package/templates/packs/vapor-api/docs-architecture/CONVENTIONS.md +121 -0
- package/templates/packs/vapor-api/docs-architecture/GOTCHAS_LINUX_SWIFT.md +109 -0
- package/templates/packs/vapor-api/docs-architecture/OPS.md +102 -0
- package/templates/packs/vapor-api/env_dist +29 -0
- package/templates/packs/vapor-api/gitignore +7 -0
- package/templates/packs/vapor-api/pack.json +11 -0
- package/templates/packs/vapor-api/scripts/generate-error-codes.sh +73 -0
- package/templates/packs/vapor-api/scripts/validate-env-vars.sh +72 -0
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# {{PROJECT_NAME}} — Claude Code Operating Manual
|
|
2
|
+
|
|
3
|
+
Scaffolded by **AppForge**: a Claude-Code-first project factory. You (Claude) are the team
|
|
4
|
+
lead AND the primary developer. This manual encodes hard-won lessons — follow it exactly.
|
|
5
|
+
|
|
6
|
+
## Identity
|
|
7
|
+
- Project: **{{PROJECT_NAME}}** · Identifier: `{{BUNDLE_ID}}`
|
|
8
|
+
- Platform pack: {{PACK_LABEL}}
|
|
9
|
+
|
|
10
|
+
## Session protocol (MANDATORY)
|
|
11
|
+
1. **Session start**: run the `restore-context` skill — read `.claude/memory/*.md` first. Never invent project facts.
|
|
12
|
+
2. **Empty project / new idea**: run the `kickoff` skill — interview → PRD → slices → autonomous build.
|
|
13
|
+
3. **After significant work**: run the `save-context` skill (PROJECT_STATE.md at minimum).
|
|
14
|
+
|
|
15
|
+
## Knowledge base — read before coding
|
|
16
|
+
`docs-architecture/` is law. Read the relevant doc BEFORE touching that area:
|
|
17
|
+
- `ARCHITECTURE_PRINCIPLES.md` — the 6-layer model (any stack): L0 Foundation → L1 Ops → L2 Data → L3 Core Logic+UI → L4 Shared Features → L5 Complete Features. Imports point downward only.
|
|
18
|
+
- `DELIVERY.md` — vertical slices, build loop, validation etiquette, memory protocol.
|
|
19
|
+
- `MULTI_REPO_CONTRACT.md` — product spans repos (API + SDK + clients)? Implementation order + gates.
|
|
20
|
+
- `SDK_CONTRACT.md` — shipping or consuming a typed SDK between backend and clients.
|
|
21
|
+
- `SECURITY_USER_URLS.md` — accepting user-supplied URLs (webhooks, callbacks, imports) — SSRF defense.
|
|
22
|
+
- `DOCS_PLACEMENT.md` — where knowledge lives (two-tier rule); read before writing any doc.
|
|
23
|
+
- `ANTI_PATTERNS.md` — verified production anti-patterns; read before adding caching/flags/auth shortcuts.
|
|
24
|
+
- Platform docs (from the pack) — conventions, build commands, platform gotchas. They refine,
|
|
25
|
+
never contradict, the files above.
|
|
26
|
+
|
|
27
|
+
## Non-negotiable rules
|
|
28
|
+
- **Dependency direction**: a layer imports only layers below it (sole exception: L2 implements L3 contracts). L3 logic stays pure (no UI/IO imports). Bricks communicate by callbacks, never by importing app state.
|
|
29
|
+
- **Design tokens only** — no hardcoded colors/fonts/spacing in presentation code.
|
|
30
|
+
- **Proof over claims**: layer tests green → app build green → eyes-on proof (screenshot/response) before anything is "done". Failures reported with output.
|
|
31
|
+
- **Every domain rule ships with its test** in the same change.
|
|
32
|
+
- **Memory is law**: contradictions between memory and code → code wins, then fix the memory file.
|
|
33
|
+
|
|
34
|
+
## Git
|
|
35
|
+
- Never push without explicit user approval. Feature branches; commit format `add/update/fix(scope) - description`.
|
|
36
|
+
- No AI attribution in commits or file headers.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# {{PROJECT_NAME}} — Architecture Memory
|
|
2
|
+
|
|
3
|
+
> Project-specific structure decisions. The generic 6-layer model lives in
|
|
4
|
+
> `docs-architecture/ARCHITECTURE_PRINCIPLES.md` — this file records how THIS app instantiates
|
|
5
|
+
> them in THIS stack's module system (packages / workspaces / modules — whatever the pack uses).
|
|
6
|
+
|
|
7
|
+
## Layers (instantiated)
|
|
8
|
+
> Filled at kickoff once the stack is known. Map each layer (L0–L5) to a concrete location in
|
|
9
|
+
> this project's module system. Example shape — replace with the real paths:
|
|
10
|
+
- **L0 Foundation** — design tokens, base utilities: _(location TBD at kickoff)_.
|
|
11
|
+
- **L3 Core Logic** — domain entities + engines + repository contracts: _(location + entities TBD)_.
|
|
12
|
+
- **L2 Data** — repository implementations, clients, mapping: _(location TBD)_.
|
|
13
|
+
- **L4 Shared Features** — reusable feature bricks: _(none yet)_.
|
|
14
|
+
- **L5 Complete Features** — screens/pages/endpoints + wiring: _(none yet)_.
|
|
15
|
+
|
|
16
|
+
## Domain map
|
|
17
|
+
_(filled at kickoff: entity → layer location/file)_
|
|
18
|
+
|
|
19
|
+
## Deviations from the boilerplate
|
|
20
|
+
_(record any conscious deviation + why)_
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# {{PROJECT_NAME}} — Commands
|
|
2
|
+
|
|
3
|
+
> Only commands proven to work in THIS project, with exact flags.
|
|
4
|
+
> Filled at kickoff once the stack's build/test/run loop is established.
|
|
5
|
+
|
|
6
|
+
## Layer loop (fast — always first)
|
|
7
|
+
_(per-layer build + test commands)_
|
|
8
|
+
|
|
9
|
+
## App target
|
|
10
|
+
_(full build / run commands)_
|
|
11
|
+
|
|
12
|
+
## Validation
|
|
13
|
+
_(how proof is captured: screenshot, curl, recording…)_
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# {{PROJECT_NAME}} — Next Steps
|
|
2
|
+
|
|
3
|
+
## Now
|
|
4
|
+
1. Run `/kickoff`.
|
|
5
|
+
|
|
6
|
+
## Next
|
|
7
|
+
_(slice backlog appears here after kickoff)_
|
|
8
|
+
|
|
9
|
+
## Blockers
|
|
10
|
+
_(waiting-on-user items: accounts/credentials, backend provisioning, store/registry setup,
|
|
11
|
+
real-environment tests — whatever only the user can unblock for this stack)_
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# {{PROJECT_NAME}} — Project State
|
|
2
|
+
|
|
3
|
+
> Single source of truth for "where are we". Updated after every significant change.
|
|
4
|
+
> Keep ≤ 300 lines: prune completed/stale entries into git history.
|
|
5
|
+
|
|
6
|
+
## Status
|
|
7
|
+
- Phase: ⏳ not started — run `/kickoff` to begin.
|
|
8
|
+
- Current slice: —
|
|
9
|
+
- Last session: —
|
|
10
|
+
|
|
11
|
+
## Done ✅
|
|
12
|
+
_(nothing yet)_
|
|
13
|
+
|
|
14
|
+
## In progress 🚧
|
|
15
|
+
_(nothing yet)_
|
|
16
|
+
|
|
17
|
+
## Gotchas log
|
|
18
|
+
> Format: **Symptom** → cause → fix. These save future sessions hours.
|
|
19
|
+
|
|
20
|
+
_(none yet)_
|
|
21
|
+
|
|
22
|
+
## Launch blockers
|
|
23
|
+
_(filled during kickoff: things only the user can unblock — accounts/credentials, backend
|
|
24
|
+
provisioning, store/registry setup, privacy policy, etc. The pack/kickoff names the specifics.)_
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: kickoff
|
|
3
|
+
description: Turn a product idea into a built, running app — you are the team lead. Orchestrate the product-owner skill for the PRD, plan vertical slices, then build autonomously slice by slice with full validation. Use when the project is empty, when the user describes an app idea, or says "kickoff", "start the project", "build my idea". Do NOT use for adding features or fixes to an established codebase — that's the normal delivery loop (DELIVERY.md).
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Kickoff — idea → running app (you are the team lead)
|
|
7
|
+
|
|
8
|
+
You own the whole delivery: product, architecture, code, validation. Earn that trust:
|
|
9
|
+
follow every gate below. Do not skip phases. Do not start coding before Phase 3 is approved.
|
|
10
|
+
|
|
11
|
+
## Phase 0 — Context
|
|
12
|
+
1. Read `CLAUDE.md`, `docs-architecture/ARCHITECTURE_PRINCIPLES.md` and `docs-architecture/DELIVERY.md`.
|
|
13
|
+
2. Read the **platform pack docs** in `docs-architecture/` (conventions, build commands, platform gotchas).
|
|
14
|
+
- **No platform pack installed?** Establish the stack's build/test/run loop yourself (verify the
|
|
15
|
+
commands actually work), write it to `.claude/memory/COMMANDS.md`, and tell the user best-practice
|
|
16
|
+
coverage is reduced for this stack.
|
|
17
|
+
3. Read `.claude/memory/*.md` — if a PRD/slice plan already exists, resume instead of restarting.
|
|
18
|
+
4. **Tooling gate** — split by severity:
|
|
19
|
+
- **Build/run tooling (hard STOP).** Verify the pack's **documented** requirements (read
|
|
20
|
+
them from its `CLAUDE.md` / `COMMANDS.md` — there is no `pack.json` in a generated project)
|
|
21
|
+
by RUNNING their commands (version checks). A missing build tool → STOP: give the one-line
|
|
22
|
+
install and ask the user to run it (or approve a reduced-proof plan — if the pack provides a
|
|
23
|
+
`WORKFLOW.md` / proof ladder, follow it; otherwise agree a plan and record it in
|
|
24
|
+
`.claude/memory/DECISIONS.md`). Building a whole project that ends with "zero pixels ever
|
|
25
|
+
seen" is not a default anyone chose.
|
|
26
|
+
- **Docs/MCP servers (WARNING, not STOP).** Check that MCP servers from `.mcp.json` respond.
|
|
27
|
+
A missing or non-responding **docs** MCP (e.g. context7) is a WARNING: note it, tell the
|
|
28
|
+
user docs lookups are degraded, and continue. (Caveat: a bare `npx` command can fail to
|
|
29
|
+
start on native Windows because npm installs a `.cmd` shim — if the docs MCP won't launch
|
|
30
|
+
there, that's the likely cause; it's not a blocker.) Only build tooling earns a hard STOP.
|
|
31
|
+
|
|
32
|
+
## Phase 1–2 — Product (delegate to the PO)
|
|
33
|
+
Run the **`product-owner` skill**: one focused interview → `docs/PRD.md` (lean, ≤150 lines, with
|
|
34
|
+
domain glossary + epics/stories) → explicit user OK. If a full BMAD install is present, you may use
|
|
35
|
+
its PM/PO workflows instead. You stay team lead: challenge the PO output if the domain glossary
|
|
36
|
+
doesn't map cleanly onto the layer model.
|
|
37
|
+
|
|
38
|
+
## Phase 3 — Slice plan
|
|
39
|
+
Map the PRD onto the layers (`ARCHITECTURE_PRINCIPLES.md`) and write `docs/SLICES.md`:
|
|
40
|
+
- **Slice 0 — skeleton runs**: app boots empty on the target (simulator/emulator/browser/server). Proof required.
|
|
41
|
+
- **Slice 1 — domain heart**: L3 entities + main engine fully tested + the ONE main screen on InMemory data.
|
|
42
|
+
- **Slice 2+**: one vertical feature each (persistence/cloud, screens, gamification, sharing…), always end-to-end, always shippable.
|
|
43
|
+
Each slice lists: goal, files per layer, test plan, demo criterion ("what the user sees").
|
|
44
|
+
Demo numbers/strings in the plan are ESTIMATES until executed — label them so, and correct the
|
|
45
|
+
plan from real output when the slice lands (the executed-output rule applies to plans too).
|
|
46
|
+
Show the plan; get explicit OK. **This is the last blocking approval.**
|
|
47
|
+
|
|
48
|
+
**Gate template** (both blocking gates use this shape — short, decision-ready):
|
|
49
|
+
```markdown
|
|
50
|
+
## Gate: <PRD | Slice plan> ready for your OK
|
|
51
|
+
- <3–6 bullets: the essence — core loop, the 5 features / the slices and their demo criteria>
|
|
52
|
+
- Trade-offs made: <what was cut/parked and why>
|
|
53
|
+
Reply "OK" to proceed, or tell me what to change. I will NOT build before your OK.
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Phase 4 — Autonomous build loop (per slice)
|
|
57
|
+
Follow `DELIVERY.md` §"The build loop" exactly, with the platform pack's commands:
|
|
58
|
+
1. L3 Core Logic: models/engines/contracts + tests → layer tests green.
|
|
59
|
+
2. L2 Data: InMemory impl of the contracts (real backend only when the slice demands it — follow
|
|
60
|
+
the platform pack's backend guide and its gotchas religiously).
|
|
61
|
+
3. L3 Core UI / L4 bricks: reusable components (L0 tokens only, callbacks as boundaries).
|
|
62
|
+
4. L5 Feature: screen assembly, store wiring, navigation per the pack's navigation doc.
|
|
63
|
+
5. Full app build; fix until green.
|
|
64
|
+
6. **Eyes-on proof**: run it (simulator MCP / browser / curl), navigate to the feature, capture and
|
|
65
|
+
actually inspect the proof (layout, dark mode, empty states, error states). Full eyes-on proof
|
|
66
|
+
not possible (no simulator/emulator/headless runner) → if the pack provides a degraded/reduced-proof
|
|
67
|
+
ladder, climb down it; otherwise fall back to the strongest proof the stack allows (integration
|
|
68
|
+
test, logs, response capture) and SAY explicitly which rung you reached.
|
|
69
|
+
Any value you attribute to the code (counts, durations, demo strings) must come from executed
|
|
70
|
+
output — never fabricate a demo number.
|
|
71
|
+
7. Memory update: `PROJECT_STATE.md` (done/todo/gotchas) + `DECISIONS.md` for any choice a future
|
|
72
|
+
session must not re-litigate. **Then commit** (`add/update/fix(scope) - description`) — every
|
|
73
|
+
gate and every slice leaves a commit; untracked delivered work is an audit failure.
|
|
74
|
+
8. Report the slice with its proof — for a non-technical owner, translate it into product terms
|
|
75
|
+
("your two requirements are now automated tests named X and Y", screenshot when possible),
|
|
76
|
+
not raw test logs. Then continue to the next slice.
|
|
77
|
+
|
|
78
|
+
Stop and ask ONLY when: a slice's scope is genuinely ambiguous, a paid/external dependency appears,
|
|
79
|
+
or the user must act (store consoles, certificates, cloud dashboard schema deploys, real-device tests).
|
|
80
|
+
|
|
81
|
+
## Quality bars (every slice)
|
|
82
|
+
- Layer tests green BEFORE app build. New domain rule ⇒ new test, same change.
|
|
83
|
+
- Zero hardcoded visual values; zero UI/IO framework imports in Core; dependency arrows point one way.
|
|
84
|
+
- Honest reporting: failures shown with output, not narrated away.
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: product-owner
|
|
3
|
+
description: Act as the Product Owner — turn a raw idea into a validated PRD with epics and stories, through one focused interview. Use when the user wants a SPEC ONLY: says "PRD", "product brief", "spec my idea", "write the requirements", or when /kickoff reaches its product phase. The interview ALWAYS precedes any spec artifact. For an EMPTY project or "build my app / start the project" (idea → built running app, not just a doc), DEFER to the kickoff skill — kickoff owns the full delivery and calls this skill for its product phase.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Product Owner — idea → validated PRD
|
|
7
|
+
|
|
8
|
+
You are the PO on this project. Your output feeds an autonomous build, so vagueness here
|
|
9
|
+
becomes wasted slices later. Be opinionated: challenge scope, cut ruthlessly, name trade-offs.
|
|
10
|
+
If a full BMAD installation is present (`_bmad/` or `bmad-*` commands), you may use its
|
|
11
|
+
PM/PO workflows instead — this skill is the lean built-in equivalent.
|
|
12
|
+
|
|
13
|
+
## Step 1 — Interview (ONE round)
|
|
14
|
+
Ask everything in a single message (AskUserQuestion when available). Cover only what you
|
|
15
|
+
cannot infer:
|
|
16
|
+
1. **Core loop** — what does the user do, and what do they get back? One sentence.
|
|
17
|
+
2. **Persona** — who is this for; what's the single moment of delight?
|
|
18
|
+
3. **v1 must-haves** — cap at 5 features. Everything else is v2 (say so explicitly).
|
|
19
|
+
4. **Data reality** — local-only, cloud sync, shared/collaborative, external APIs?
|
|
20
|
+
**Always attach a recommended default** ("if unsure: private cloud sync — free, no server,
|
|
21
|
+
survives reinstalls"). Non-technical users can't answer this raw — a question without a
|
|
22
|
+
default stalls them; experts just override the default.
|
|
23
|
+
5. **Differentiators** — gamification (ranks/streaks/achievements), social (groups/sharing),
|
|
24
|
+
monetization? (Proven patterns exist for these — flag which apply.)
|
|
25
|
+
6. **Fixed constraints** — name, brand/visual vibe, deadlines, platforms.
|
|
26
|
+
|
|
27
|
+
Challenge weak answers once ("feature 4 and 5 both serve power users — which one earns v1?").
|
|
28
|
+
If the user signals they are non-technical ("je ne code pas", product-only vocabulary): phrase
|
|
29
|
+
every technical question as a recommendation to accept or reject, never an open choice.
|
|
30
|
+
|
|
31
|
+
**Example** — user says *"an app where friends share book recommendations"* → you ask (one message):
|
|
32
|
+
core loop ("what does someone do daily — browse friends' shelves? log a book?"), persona, the 5
|
|
33
|
+
v1 features, data reality (private groups → cloud sync?), differentiators (reading streaks?
|
|
34
|
+
clubs?), constraints. You do NOT write a PRD yet — the answers shape it.
|
|
35
|
+
|
|
36
|
+
## Step 2 — PRD (`docs/PRD.md`, ≤ 150 lines)
|
|
37
|
+
```markdown
|
|
38
|
+
# <App> — PRD
|
|
39
|
+
## Problem & Persona (3 lines max each)
|
|
40
|
+
## Core Loop (1 sentence + the delight moment)
|
|
41
|
+
## V1 Features (numbered, each: 1-line description + acceptance criterion)
|
|
42
|
+
## V2 Parking Lot (everything cut, so it stays cut)
|
|
43
|
+
## Domain Glossary (entities, fields, relationships — this seeds the Core layer)
|
|
44
|
+
## Non-functional (offline? privacy? performance budgets? age rating?)
|
|
45
|
+
## Success Criteria (3 measurable statements)
|
|
46
|
+
```
|
|
47
|
+
The **Domain Glossary is the contract** with the architecture: every entity named here maps
|
|
48
|
+
to a Core model; every relationship hints at a repository.
|
|
49
|
+
|
|
50
|
+
## Step 3 — Epics & stories (inside PRD or `docs/STORIES.md` if large)
|
|
51
|
+
Break v1 features into stories of one-slice size: "As <persona>, I <action>, so <value>" +
|
|
52
|
+
acceptance criteria (Given/When/Then, 2–4 each). Order by dependency, not excitement —
|
|
53
|
+
domain heart first, polish last.
|
|
54
|
+
|
|
55
|
+
## Step 4 — Validation gate
|
|
56
|
+
Present a ≤ 10-line summary: loop, 5 features, key trade-offs made. Get an explicit OK.
|
|
57
|
+
Record scope decisions in `.claude/memory/DECISIONS.md` (dated one-liners).
|
|
58
|
+
**Do not let implementation start before the OK.**
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: restore-context
|
|
3
|
+
description: Restore project context from memory files at session start or after losing the thread. Use on the first message of any session, or on "where were we", "resume", "catch up", "refresh context".
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Restore context (anti-hallucination)
|
|
7
|
+
|
|
8
|
+
1. Read ALL 5 files: `.claude/memory/PROJECT_STATE.md`, `ARCHITECTURE.md`, `DECISIONS.md`,
|
|
9
|
+
`NEXT_STEPS.md`, `COMMANDS.md` — and **name each one in your summary's first line** (a missing
|
|
10
|
+
or empty file is said out loud, not skipped silently). COMMANDS.md counts: it's where the
|
|
11
|
+
build loop lives.
|
|
12
|
+
2. Cross-check the 2–3 most load-bearing claims against the code (file exists? API matches?).
|
|
13
|
+
**Code wins** over memory: fix the stale memory entries FIRST, so your summary describes the
|
|
14
|
+
corrected state and mentions what was fixed.
|
|
15
|
+
3. Summarize in ≤ 10 lines using this shape:
|
|
16
|
+
```markdown
|
|
17
|
+
Context restored (read: PROJECT_STATE, ARCHITECTURE, DECISIONS, NEXT_STEPS, COMMANDS).
|
|
18
|
+
- Current slice: <n — title, status>
|
|
19
|
+
- Last done: <most recent completed work>
|
|
20
|
+
- Memory vs code: <"verified ✓" | "drift found + fixed: …">
|
|
21
|
+
- Open gotchas/blockers: <the ones that constrain what we do next>
|
|
22
|
+
- Next step: <the single next action>
|
|
23
|
+
```
|
|
24
|
+
4. If memory files are missing or empty: say so, offer to initialize them from a project scan —
|
|
25
|
+
never invent history.
|
|
26
|
+
|
|
27
|
+
Rules:
|
|
28
|
+
- NEVER assert a project fact that is not in memory or verifiable in code.
|
|
29
|
+
- A memory file naming a function/flag that no longer exists = update the file, then proceed.
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: save-context
|
|
3
|
+
description: Persist session progress into the project memory files. Use after completing a slice or significant work, before ending a session, or on "save progress", "checkpoint", "update memory".
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Save context
|
|
7
|
+
|
|
8
|
+
## Workflow
|
|
9
|
+
1. **Inventory the session**: list what was built, what was decided (and why), what broke and how
|
|
10
|
+
it was fixed, what's now blocked. If the session contradicts an existing memory entry, that's a
|
|
11
|
+
decision change → record the new choice in `DECISIONS.md` (dated), don't silently overwrite.
|
|
12
|
+
2. **Map each item to its file** using the table below. One fact, one home — no duplication.
|
|
13
|
+
3. **Apply surgical edits, then verify integrity**: re-read your diff — no pre-existing fact, date
|
|
14
|
+
or gotcha may disappear. Updating an entry in place is fine; losing its date is not.
|
|
15
|
+
|
|
16
|
+
| File | Write here |
|
|
17
|
+
|---|---|
|
|
18
|
+
| `PROJECT_STATE.md` | What changed this session (done ✅ / in-progress 🚧 / todo), new gotchas with symptom→cause→fix, current slice status. Most important file — keep it ≤ 300 lines, prune stale entries. |
|
|
19
|
+
| `DECISIONS.md` | Choices a future session must not re-litigate ("we chose X over Y because Z"). One line each, dated. |
|
|
20
|
+
| `NEXT_STEPS.md` | Reordered priorities, new blockers, launch checklist deltas. |
|
|
21
|
+
| `ARCHITECTURE.md` | Only if the structure itself changed (new package, new layer rule). |
|
|
22
|
+
| `COMMANDS.md` | Only when a new command proved useful (with the exact working flags). |
|
|
23
|
+
|
|
24
|
+
**Example — a gotcha entry** (PROJECT_STATE.md, gotchas log):
|
|
25
|
+
```markdown
|
|
26
|
+
- **Records failed to save: "cannot use an empty list to initialize a new field (tags)"**
|
|
27
|
+
→ backend rejects empty arrays on list fields → omit the field when the array is empty. Fixed 2026-06-09.
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Rules
|
|
31
|
+
- Facts only — no narration, no praise. Convert relative dates to absolute (2026-06-09, not "today").
|
|
32
|
+
- Gotchas are the most valuable entries: always symptom → cause → fix.
|
|
33
|
+
- **Integrity**: never delete or rewrite pre-existing facts/dates; append or update in place.
|
|
34
|
+
- Never log secrets, tokens, or personal data.
|
|
35
|
+
- Finish with a one-line confirmation listing the files you touched.
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
# Anti-Patterns Register — what NOT to do (each one paid for)
|
|
2
|
+
|
|
3
|
+
Every entry below was found in production code and verified — none is hypothetical.
|
|
4
|
+
Treat this as a pre-flight checklist: before shipping a cache layer, an auth middleware,
|
|
5
|
+
a config provider or a doc, scan the matching section.
|
|
6
|
+
|
|
7
|
+
> **This register is template-owned and curated.** It ships read-only and is refreshed when
|
|
8
|
+
> you run `update --apply` — **edits made here are overwritten on the next update.** Do NOT add
|
|
9
|
+
> your project's own incidents to this file. When you hit a real anti-pattern in THIS project,
|
|
10
|
+
> record it in the **gotchas log in `.claude/memory/PROJECT_STATE.md`** (symptom → cause → fix) —
|
|
11
|
+
> that file is yours and `update` never touches it. Worth-generalizing lessons can be proposed
|
|
12
|
+
> upstream to this register; they don't live in your local copy.
|
|
13
|
+
|
|
14
|
+
Layer references (L0–L5) follow ARCHITECTURE_PRINCIPLES.md. Proof discipline follows
|
|
15
|
+
DELIVERY.md. Format per entry: **Symptom → Why it bites → Fix.**
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Caching & HTTP
|
|
20
|
+
|
|
21
|
+
### 1. Process-seeded hash as ETag
|
|
22
|
+
- **Symptom:** ETags computed from the language's built-in `hashValue`/`hash()` of the
|
|
23
|
+
response body. Cache hit rate near zero behind a load balancer; clients re-download
|
|
24
|
+
identical bodies.
|
|
25
|
+
- **Why it bites:** Swift's `hashValue` (and Python's `hash()`) are seeded **per process**.
|
|
26
|
+
The same bytes produce a different ETag on every replica and every restart — conditional
|
|
27
|
+
requests can never match. Bonus damage: the middleware decoded the full JSON body on
|
|
28
|
+
every request just to decide cacheability.
|
|
29
|
+
- **Fix:** ETags must be stable across processes: SHA-256 (truncated) of the body bytes,
|
|
30
|
+
or a version/updatedAt field from the resource. Decide cacheability from route + model
|
|
31
|
+
metadata, never by re-parsing the response you just serialized.
|
|
32
|
+
|
|
33
|
+
> 📖 **War story:** a production team shipped ETag support and saw zero `304`s in the logs.
|
|
34
|
+
> Cause: per-process hash seed — three replicas, three ETags for the same body. Fix: content
|
|
35
|
+
> digest; conditional requests started matching the same day.
|
|
36
|
+
|
|
37
|
+
## Auth & Routing (L5)
|
|
38
|
+
|
|
39
|
+
### 2. Name-allowlist auth middleware
|
|
40
|
+
- **Symptom:** a global route middleware holding a hand-maintained array of page names
|
|
41
|
+
that skip auth (`['index', 'pricing', 'features', …]`); everything else redirects to login.
|
|
42
|
+
- **Why it bites:** every new public page must be *remembered* into the list. Forget one
|
|
43
|
+
and it silently vanishes behind auth — a public changelog page 302'd anonymous visitors
|
|
44
|
+
to the homepage for weeks before anyone noticed. The list also drifts from the actual
|
|
45
|
+
route table; nothing fails when they diverge.
|
|
46
|
+
- **Fix:** derive auth from **route meta** declared next to the page
|
|
47
|
+
(`definePageMeta({ auth: false })` or equivalent): the middleware reads `to.meta`,
|
|
48
|
+
default-denies, and new pages carry their own policy. One source of truth, at L5 where
|
|
49
|
+
the route lives.
|
|
50
|
+
|
|
51
|
+
## Contracts & Dependencies
|
|
52
|
+
|
|
53
|
+
### 3. Deep imports into a dependency's internals
|
|
54
|
+
- **Symptom:** app code importing `your-sdk/src/types/*` (internal paths) instead of the
|
|
55
|
+
package's public entry point — found in 22 files of one consumer.
|
|
56
|
+
- **Why it bites:** it bypasses the public contract; any internal refactor of the SDK
|
|
57
|
+
(moving a file, renaming a folder) breaks 22 call sites in a *different repo*. The import
|
|
58
|
+
graph **is** the architecture — deep imports make it a lie.
|
|
59
|
+
- **Fix:** import only from the package root / declared `exports`. Enforce: `exports` field
|
|
60
|
+
in the SDK's package.json (makes deep paths unresolvable) + `no-restricted-imports` lint
|
|
61
|
+
rule in consumers.
|
|
62
|
+
|
|
63
|
+
### 4. `console.log` in SDK hot paths
|
|
64
|
+
- **Symptom:** a shared SDK logging request/response details straight to `console.log`
|
|
65
|
+
(9 occurrences) on every call.
|
|
66
|
+
- **Why it bites:** every consumer's console fills with another library's traffic — request
|
|
67
|
+
payloads included. No way to silence it, filter it, or route it. The log sink is an L1 Ops
|
|
68
|
+
decision owned by the app; an L2 library hardcoding it seizes a choice the consumer can
|
|
69
|
+
never override (and in pure L3 code, logging IO has no place at all).
|
|
70
|
+
- **Fix:** inject a logger interface (default: no-op) and gate verbosity behind an explicit
|
|
71
|
+
`debug` flag the consumer sets. The SDK never decides where logs go.
|
|
72
|
+
|
|
73
|
+
### 5. Unpinned git dependencies
|
|
74
|
+
- **Symptom:** a package consumed via `git+ssh://…#main` — branch HEAD, no tag, no lockfile
|
|
75
|
+
protection across fresh installs.
|
|
76
|
+
- **Why it bites:** every clean install pulls whatever the branch points to *today*. A
|
|
77
|
+
breaking SDK change lands silently in consumers' next CI run; "works on my machine"
|
|
78
|
+
becomes literally true and useless.
|
|
79
|
+
- **Fix:** pin to a tag or commit SHA (`#v1.4.2`), or publish proper semver releases.
|
|
80
|
+
Upgrades become deliberate diffs, not surprises.
|
|
81
|
+
|
|
82
|
+
## Configuration & Ops (L1)
|
|
83
|
+
|
|
84
|
+
### 6. Test-detection heuristics in production config
|
|
85
|
+
- **Symptom:** the global config provider sniffing for the test framework at runtime —
|
|
86
|
+
checking `XCTestConfigurationFilePath`, `xctest` in process arguments, `PackageTests`
|
|
87
|
+
in the binary name, plus a `CI` + database-hostname combo — and swapping the entire
|
|
88
|
+
config to mocks when any heuristic matches.
|
|
89
|
+
- **Why it bites:** production behavior forks on *test-framework presence*, not on declared
|
|
90
|
+
intent. The heuristics rot as runners evolve, and a matching env-var combination in any
|
|
91
|
+
non-test context silently flips real config to mock values. Debugging this is archaeology.
|
|
92
|
+
- **Fix:** one explicit variable (`APP_ENV=test`) set by the test harness; the provider
|
|
93
|
+
reads it and nothing else. Better: inject config into the composition root so tests pass
|
|
94
|
+
their own — no global singleton to fork.
|
|
95
|
+
|
|
96
|
+
### 7. Hardcoded log level overriding the env var
|
|
97
|
+
- **Symptom:** `app.logger.logLevel = .debug` hardcoded in `configure()`, executed *after*
|
|
98
|
+
logging bootstrap — the `LOG_LEVEL` env var exists, is documented, and does nothing.
|
|
99
|
+
- **Why it bites:** debug logs in production: noise, log-storage cost, and request data in
|
|
100
|
+
plaintext. Worse, the config **lies about being configurable** — operators set the env
|
|
101
|
+
var, see no effect, and lose trust in every other knob.
|
|
102
|
+
- **Fix:** read the level from the environment with a quiet production default
|
|
103
|
+
(`.info`/`.notice`). If a hardcode is ever needed for local work, gate it behind
|
|
104
|
+
`#if DEBUG` and a comment saying so.
|
|
105
|
+
|
|
106
|
+
### 8. Feature flags that default OPEN
|
|
107
|
+
- **Symptom:** flag parsing like `enabled = value.lowercased() != "false"` — anything that
|
|
108
|
+
isn't the exact string `false` (a typo, `"0"`, `"no"`, an empty default) turns the
|
|
109
|
+
feature **on**.
|
|
110
|
+
- **Why it bites:** misconfiguration becomes silent activation. The dangerous failure mode
|
|
111
|
+
(feature unexpectedly live) is the *default* failure mode.
|
|
112
|
+
- **Fix:** fail closed. Parse with an explicit truthy allowlist (`"true"`, `"1"`); missing
|
|
113
|
+
or unrecognized values mean **off**, and log a warning naming the variable.
|
|
114
|
+
|
|
115
|
+
## Concurrency & Lifecycle (L2)
|
|
116
|
+
|
|
117
|
+
### 9. Detached background task on a request-scoped resource
|
|
118
|
+
- **Symptom:** a fire-and-forget `Task { … }` spawned inside a request handler, capturing
|
|
119
|
+
the request's database connection to do slow work after the response is sent.
|
|
120
|
+
- **Why it bites:** the request-scoped connection returns to the pool when the response
|
|
121
|
+
completes; the background task then races a closed — or worse, *reused* — connection.
|
|
122
|
+
Intermittent failures, occasionally someone else's transaction.
|
|
123
|
+
- **Fix:** background work uses **application-scoped** resources (the app's database
|
|
124
|
+
handle, an injected context) or a proper job queue. Rule of thumb: anything outliving
|
|
125
|
+
the response must not hold anything scoped to the request.
|
|
126
|
+
|
|
127
|
+
## Testing
|
|
128
|
+
|
|
129
|
+
### 10. Testing theater
|
|
130
|
+
- **Symptom:** `package.json` scripts invoking Playwright with **no Playwright dependency
|
|
131
|
+
installed** — the scripts cannot run from a clean checkout. A stale `playwright-report/`
|
|
132
|
+
committed to the repo as "proof" the tests once passed.
|
|
133
|
+
- **Why it bites:** test claims that can't be executed are worse than no tests: they grant
|
|
134
|
+
false confidence and rot invisibly. DELIVERY.md's contract is *proof over claims* — a
|
|
135
|
+
committed report is a claim, a green run is proof.
|
|
136
|
+
- **Fix:** every test script must run from a clean checkout in CI, or be deleted. Generated
|
|
137
|
+
reports go in `.gitignore`, never in version control.
|
|
138
|
+
|
|
139
|
+
## Documentation
|
|
140
|
+
|
|
141
|
+
### 11. Docs that restate code
|
|
142
|
+
- **Symptom:** static mapping tables, per-folder `AGENTS.md` inventories listing what each
|
|
143
|
+
directory contains — every instance checked was stale.
|
|
144
|
+
- **Why it bites:** a doc that mirrors structure drifts the moment the structure moves, and
|
|
145
|
+
agents/newcomers trust the stale copy over the code. On conflict, code wins — so the doc
|
|
146
|
+
was only ever a liability.
|
|
147
|
+
- **Fix:** document *intent and invariants*, never inventory. Placement rules in
|
|
148
|
+
**DOCS_PLACEMENT.md** decide what deserves prose at all.
|
|
149
|
+
|
|
150
|
+
### 12. Two documentation roots
|
|
151
|
+
- **Symptom:** both `docs/` and `documentations/` at the repo root, with overlapping,
|
|
152
|
+
diverging content.
|
|
153
|
+
- **Why it bites:** nobody knows where truth lives, so contributors update one (or neither)
|
|
154
|
+
and readers trust whichever they find first. Two roots = zero canonical sources.
|
|
155
|
+
- **Fix:** one root (`docs/`), one redirect commit to merge the other, done. See
|
|
156
|
+
DOCS_PLACEMENT.md for what goes where inside it.
|
|
157
|
+
|
|
158
|
+
### 13. Hand-written docs that look generated
|
|
159
|
+
- **Symptom:** an `ERROR_CODES.md` formatted like tool output, maintained by hand — and
|
|
160
|
+
drifted from the error enum it claimed to mirror.
|
|
161
|
+
- **Why it bites:** readers assume generated docs are exact, so drift here is actively
|
|
162
|
+
misleading — worse than no doc. The enum was the truth all along.
|
|
163
|
+
- **Fix:** if a doc mirrors code, **generate it from code** (build step or CI check that
|
|
164
|
+
fails on drift). If generating isn't worth it, delete the doc and link to the source file.
|
|
165
|
+
|
|
166
|
+
---
|
|
167
|
+
|
|
168
|
+
## Recording an anti-pattern you hit
|
|
169
|
+
|
|
170
|
+
Your incidents go in **`.claude/memory/PROJECT_STATE.md`** (gotchas log), never in this
|
|
171
|
+
template-owned file — `update --apply` would erase an edit here.
|
|
172
|
+
|
|
173
|
+
1. It happened **here**, in real code — link the commit or file.
|
|
174
|
+
2. Write it as symptom → cause → fix, 3–6 lines. Generic names, no blame.
|
|
175
|
+
3. If the fix changed a layer rule or convention, record it in `.claude/memory/DECISIONS.md` too.
|
|
176
|
+
4. If the lesson is broadly reusable across projects, propose it upstream so a future `update`
|
|
177
|
+
ships it to this curated register for everyone.
|
|
178
|
+
|
|
179
|
+
> ⚠️ **Gotcha:** an entry only works if it stays falsifiable. "Avoid bad caching" teaches
|
|
180
|
+
> nothing; "per-process hash seed broke ETags across replicas" prevents a repeat.
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
# Architecture Principles — the 6-Layer Lego Model (every stack, every language)
|
|
2
|
+
|
|
3
|
+
This is the universal contract. iOS, Android, web, API — the platform pack instantiates
|
|
4
|
+
these layers for your language, but the layers themselves never change.
|
|
5
|
+
|
|
6
|
+
## The layers
|
|
7
|
+
|
|
8
|
+
```
|
|
9
|
+
L5 COMPLETE FEATURES final user-facing features: screens/pages/endpoints, app state,
|
|
10
|
+
composition root. Assembles everything below. Throwaway by design.
|
|
11
|
+
L4 SHARED FEATURES feature bricks reused by several complete features
|
|
12
|
+
(auth flow, paywall, media picker, share sheet, comment thread…)
|
|
13
|
+
L3 CORE LOGIC + CORE UI the heart, two siblings at the same level:
|
|
14
|
+
· Core Logic — domain models, engines, services, repository CONTRACTS.
|
|
15
|
+
Pure: no UI framework, no IO framework. Injected clocks/calendars.
|
|
16
|
+
· Core UI — reusable, domain-blind components (buttons, cards, lists,
|
|
17
|
+
inputs) built on the design tokens.
|
|
18
|
+
L2 DATA IO: repository implementations, network/DB/cloud clients,
|
|
19
|
+
DTO ↔ domain mapping, caching, sync.
|
|
20
|
+
L1 OPS operational plumbing: remote config, feature flags, analytics,
|
|
21
|
+
logging, crash reporting, monitoring, push registration.
|
|
22
|
+
L0 FOUNDATION primitives: design tokens (colors/spacing/typography), base
|
|
23
|
+
extensions, formatters, tiny utilities. Zero dependencies.
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## The dependency rule
|
|
27
|
+
|
|
28
|
+
**A layer may only import layers strictly below it.** Never sideways (a feature never
|
|
29
|
+
imports a sibling feature), never upward.
|
|
30
|
+
|
|
31
|
+
```
|
|
32
|
+
L5 → L4, L3, L2, L1, L0
|
|
33
|
+
L4 → L3, L2, L1, L0
|
|
34
|
+
L3 → L1, L0 (L3 never imports L2 — it stays IO-free)
|
|
35
|
+
L2 → L3 contracts*, L1, L0 (* the one sanctioned upward arrow — see below)
|
|
36
|
+
L1 → L0
|
|
37
|
+
L0 → nothing
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
> **The one sanctioned upward arrow — ports & adapters.** The only arrow that points up is
|
|
41
|
+
> **L2 → L3**, and even that is narrow. Core Logic (L3) declares the repository **contracts**
|
|
42
|
+
> (interfaces) and the domain models it needs; Data (L2) *implements* those contracts, so L2
|
|
43
|
+
> imports them. **L3 itself never imports L2** — it knows nothing of the IO layer; it depends
|
|
44
|
+
> only on its own contracts, which L2 satisfies. That implementation import (L2 → L3 contracts)
|
|
45
|
+
> is the only upward arrow allowed, and it may touch **interfaces and models only — never
|
|
46
|
+
> services or engines**.
|
|
47
|
+
> Why: tests and previews run against an InMemory implementation of the contract without
|
|
48
|
+
> dragging in CloudKit/HTTP/ORM; the real backend stays swappable.
|
|
49
|
+
|
|
50
|
+
Everything else is absolute:
|
|
51
|
+
- **L3 Core Logic is pure.** No UI imports, no IO imports. If it needs the network, it
|
|
52
|
+
declares a contract; L2 implements it. Injected `Calendar`/clock/randomness — never read
|
|
53
|
+
the system clock inside an engine.
|
|
54
|
+
- **L3 Core UI is domain-blind.** A button doesn't know what an "Order" is. Domain-aware
|
|
55
|
+
composites belong to L4.
|
|
56
|
+
- **L4/L5 communicate by callbacks/props/events** (`onSave`, `onDelete`) — a brick never
|
|
57
|
+
reaches into app state on its own.
|
|
58
|
+
- **L0 tokens are the only source of visual values.** A hardcoded color in L4/L5 is a bug.
|
|
59
|
+
|
|
60
|
+
## Placing a new brick — the algorithm
|
|
61
|
+
|
|
62
|
+
The named bands above are the canonical shape, but a brick's level is not picked from a
|
|
63
|
+
category — it is **computed from its dependencies**:
|
|
64
|
+
|
|
65
|
+
> **Start at L0. Climb only when a dependency forces you up.**
|
|
66
|
+
> A brick's level = (highest level among the bricks it imports) + 1. Place it as LOW as it
|
|
67
|
+
> can possibly live.
|
|
68
|
+
|
|
69
|
+
Worked example — adding a `RateLimiter` brick:
|
|
70
|
+
1. Assume **L0**… but it needs base formatters from Foundation → it must sit above L0 → **L1**.
|
|
71
|
+
2. At L1… but it reads its quotas from remote config, which lives in Ops (L1) → above L1 → **L2**.
|
|
72
|
+
3. At L2 nothing else pushes it up → **it lands at L2.** Done — even though "rate limiting"
|
|
73
|
+
*sounds* like Ops, its dependencies decide, not its name.
|
|
74
|
+
|
|
75
|
+
Corollaries:
|
|
76
|
+
- A brick that keeps climbing is a smell: split it (the pure part stays low, the consuming
|
|
77
|
+
part moves up).
|
|
78
|
+
- **~6 levels is the maximum.** If you need a 7th, you are over-slicing — merge bands.
|
|
79
|
+
- Re-run the algorithm when a brick gains a dependency; it may need to move up (or the
|
|
80
|
+
dependency may belong lower).
|
|
81
|
+
|
|
82
|
+
## This applies to EVERY stack — including APIs
|
|
83
|
+
|
|
84
|
+
The layer discipline is not a UI concern. A backend has the exact same lattice:
|
|
85
|
+
|
|
86
|
+
```
|
|
87
|
+
L5 routes/controllers, app bootstrap, DI wiring
|
|
88
|
+
L4 shared feature modules (auth flow, billing, webhooks)
|
|
89
|
+
L3 use cases / domain services / entities / repository contracts
|
|
90
|
+
L2 repository impls, DB access, external API clients (Stripe, auth provider…)
|
|
91
|
+
L1 ops: remote config, feature flags, metrics, structured logging
|
|
92
|
+
L0 primitives: shared types, formatters, base extensions
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Same absolute rule downward-only: a repository (L2) never calls a use case (L3); a use case
|
|
96
|
+
never imports a controller; **and no brick ever calls an SDK/client that lives above its own
|
|
97
|
+
level** — if you need it, you are at the wrong level, or the SDK is.
|
|
98
|
+
|
|
99
|
+
## Where does this file go? (decision table)
|
|
100
|
+
|
|
101
|
+
Typical band per kind of file — **the placement algorithm above has the final word**:
|
|
102
|
+
|
|
103
|
+
| You are adding… | Layer |
|
|
104
|
+
|---|---|
|
|
105
|
+
| A color, spacing, font, date formatter, small extension | **L0** Foundation |
|
|
106
|
+
| Remote config, feature flag, analytics event, logger setup | **L1** Ops |
|
|
107
|
+
| A repository implementation, API client, DB access, DTO mapping | **L2** Data |
|
|
108
|
+
| A business rule, domain model, engine, service, repository contract | **L3** Core Logic |
|
|
109
|
+
| A reusable domain-blind component (button, card, badge, empty state) | **L3** Core UI |
|
|
110
|
+
| A feature brick used by ≥ 2 features (auth, paywall, picker) | **L4** Shared Features |
|
|
111
|
+
| A screen/page/endpoint, navigation, app state, service wiring | **L5** Complete Features |
|
|
112
|
+
|
|
113
|
+
When in doubt between L4 and L5: start in L5, promote to L4 on second use.
|
|
114
|
+
|
|
115
|
+
## Physical mapping
|
|
116
|
+
|
|
117
|
+
Each platform pack maps layers to its module system (packages, gradle modules, workspaces…).
|
|
118
|
+
Two rules survive any mapping:
|
|
119
|
+
1. **Lower layers must build & test standalone** — fast feedback without booting the app.
|
|
120
|
+
2. **The import graph is the architecture.** If the module system can't enforce an arrow,
|
|
121
|
+
a grep must be able to detect the violation.
|
|
122
|
+
|
|
123
|
+
## Why AI agents thrive in this model
|
|
124
|
+
|
|
125
|
+
1. **Fast ground truth** — L0–L3 build/test in seconds; agents iterate there before paying
|
|
126
|
+
for full app builds.
|
|
127
|
+
2. **Grep-visible violations** — a UI import in L3 logic or a hex color in L5 is one search away.
|
|
128
|
+
3. **Small blast radius** — layers bound the context an agent must hold per change.
|
|
129
|
+
4. **Tests are the spec** — every L3 rule ships with its test; intent survives sessions.
|
|
130
|
+
|
|
131
|
+
## Naming
|
|
132
|
+
- Names express intent, not technology (`ItemRepository`, not `ItemAPIManager`).
|
|
133
|
+
- One type per file; the file is named after the type.
|
|
134
|
+
- Feature namespacing: scoped types (`Item.DetailCard`, `App.Feed`) over prefix soup.
|