techbro 0.0.0-stage → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (122) hide show
  1. package/README.md +453 -2
  2. package/assets/baseline-rules.yml +60 -0
  3. package/assets/caller-workflow.yml +26 -0
  4. package/assets/review-action.yml +27 -0
  5. package/assets/review-config.json +4 -0
  6. package/assets/review-workflow.yml +443 -0
  7. package/assets/review.mjs +721 -0
  8. package/bin/techbro.js +9 -0
  9. package/package.json +23 -4
  10. package/src/cli.js +690 -0
  11. package/src/cluster.js +134 -0
  12. package/src/config.js +35 -0
  13. package/src/doctor.js +125 -0
  14. package/src/github.js +152 -0
  15. package/src/init.js +167 -0
  16. package/src/kube-project.js +468 -0
  17. package/src/kube.js +317 -0
  18. package/src/managed.js +63 -0
  19. package/src/project.js +202 -0
  20. package/src/review.js +165 -0
  21. package/src/secret.js +80 -0
  22. package/src/setup.js +91 -0
  23. package/src/team-workflow.js +106 -0
  24. package/src/team.js +145 -0
  25. package/src/templates.js +44 -0
  26. package/src/upgrade.js +94 -0
  27. package/templates/_default/.agents/skills/README.md +36 -0
  28. package/templates/_default/.agents/skills/database-migrations/SKILL.md +72 -0
  29. package/templates/_default/.agents/skills/frontend-feature/SKILL.md +65 -0
  30. package/templates/_default/.agents/skills/release-safe-change/SKILL.md +75 -0
  31. package/templates/_default/.agents/skills/rust-backend-feature/SKILL.md +68 -0
  32. package/templates/_default/.editorconfig +15 -0
  33. package/templates/_default/.env.example +9 -0
  34. package/templates/_default/.github/workflows/ci.yml +60 -0
  35. package/templates/_default/.nvmrc +1 -0
  36. package/templates/_default/Cargo.lock +1834 -0
  37. package/templates/_default/Cargo.toml +3 -0
  38. package/templates/_default/README.md +257 -0
  39. package/templates/_default/TEMPLATE.md +29 -0
  40. package/templates/_default/_dockerignore +8 -0
  41. package/templates/_default/_gitignore +8 -0
  42. package/templates/_default/apps/backend/Cargo.toml +22 -0
  43. package/templates/_default/apps/backend/Dockerfile +34 -0
  44. package/templates/_default/apps/backend/README.md +13 -0
  45. package/templates/_default/apps/backend/src/config.rs +111 -0
  46. package/templates/_default/apps/backend/src/controllers/health.rs +36 -0
  47. package/templates/_default/apps/backend/src/controllers/items.rs +79 -0
  48. package/templates/_default/apps/backend/src/controllers/mod.rs +2 -0
  49. package/templates/_default/apps/backend/src/error.rs +115 -0
  50. package/templates/_default/apps/backend/src/lib.rs +7 -0
  51. package/templates/_default/apps/backend/src/main.rs +71 -0
  52. package/templates/_default/apps/backend/src/models/item.rs +94 -0
  53. package/templates/_default/apps/backend/src/models/mod.rs +1 -0
  54. package/templates/_default/apps/backend/src/routes.rs +94 -0
  55. package/templates/_default/apps/backend/src/state.rs +12 -0
  56. package/templates/_default/apps/backend/src/views/items.rs +18 -0
  57. package/templates/_default/apps/backend/src/views/mod.rs +1 -0
  58. package/templates/_default/apps/backend/tests/api.rs +234 -0
  59. package/templates/_default/apps/frontend/.env.example +2 -0
  60. package/templates/_default/apps/frontend/.prettierignore +3 -0
  61. package/templates/_default/apps/frontend/.prettierrc +1 -0
  62. package/templates/_default/apps/frontend/Dockerfile +15 -0
  63. package/templates/_default/apps/frontend/README.md +39 -0
  64. package/templates/_default/apps/frontend/_dockerignore +3 -0
  65. package/templates/_default/apps/frontend/_gitignore +24 -0
  66. package/templates/_default/apps/frontend/components.json +25 -0
  67. package/templates/_default/apps/frontend/eslint.config.js +37 -0
  68. package/templates/_default/apps/frontend/index.html +13 -0
  69. package/templates/_default/apps/frontend/jsconfig.json +7 -0
  70. package/templates/_default/apps/frontend/nginx.conf +30 -0
  71. package/templates/_default/apps/frontend/package-lock.json +4338 -0
  72. package/templates/_default/apps/frontend/package.json +49 -0
  73. package/templates/_default/apps/frontend/public/favicon.svg +1 -0
  74. package/templates/_default/apps/frontend/src/App.jsx +11 -0
  75. package/templates/_default/apps/frontend/src/App.test.jsx +40 -0
  76. package/templates/_default/apps/frontend/src/api/client.js +50 -0
  77. package/templates/_default/apps/frontend/src/api/client.test.js +70 -0
  78. package/templates/_default/apps/frontend/src/api/items.js +26 -0
  79. package/templates/_default/apps/frontend/src/api/system.js +11 -0
  80. package/templates/_default/apps/frontend/src/components/ItemList.jsx +20 -0
  81. package/templates/_default/apps/frontend/src/components/ItemList.test.jsx +18 -0
  82. package/templates/_default/apps/frontend/src/components/Layout.jsx +35 -0
  83. package/templates/_default/apps/frontend/src/components/StatusBadge.jsx +22 -0
  84. package/templates/_default/apps/frontend/src/components/StepList.jsx +19 -0
  85. package/templates/_default/apps/frontend/src/components/ui/button.jsx +52 -0
  86. package/templates/_default/apps/frontend/src/components/ui/card.jsx +83 -0
  87. package/templates/_default/apps/frontend/src/components/ui/input.jsx +19 -0
  88. package/templates/_default/apps/frontend/src/hooks/useBackendStatus.js +33 -0
  89. package/templates/_default/apps/frontend/src/hooks/useItems.js +47 -0
  90. package/templates/_default/apps/frontend/src/index.css +130 -0
  91. package/templates/_default/apps/frontend/src/lib/config.js +2 -0
  92. package/templates/_default/apps/frontend/src/lib/utils.js +6 -0
  93. package/templates/_default/apps/frontend/src/main.jsx +11 -0
  94. package/templates/_default/apps/frontend/src/models/items.js +24 -0
  95. package/templates/_default/apps/frontend/src/models/items.test.js +24 -0
  96. package/templates/_default/apps/frontend/src/pages/ItemsPage.jsx +41 -0
  97. package/templates/_default/apps/frontend/src/pages/WelcomePage.jsx +156 -0
  98. package/templates/_default/apps/frontend/src/pages/WelcomePage.test.jsx +55 -0
  99. package/templates/_default/apps/frontend/src/routes.jsx +17 -0
  100. package/templates/_default/apps/frontend/src/shadcn.css +655 -0
  101. package/templates/_default/apps/frontend/src/test/setup.js +1 -0
  102. package/templates/_default/apps/frontend/vite.config.js +25 -0
  103. package/templates/_default/database/Dockerfile +9 -0
  104. package/templates/_default/database/README.md +15 -0
  105. package/templates/_default/database/migrations/postgres/20261008120000_create_items.sql +5 -0
  106. package/templates/_default/database/migrations/sqlite/20261008120000_create_items.sql +5 -0
  107. package/templates/_default/docker-compose.prod.yml +53 -0
  108. package/templates/_default/docker-compose.yml +69 -0
  109. package/templates/_default/docs/api/README.md +41 -0
  110. package/templates/_default/docs/api/openapi.yaml +152 -0
  111. package/templates/_default/docs/decisions/0001-adopt-engineering-convention.md +13 -0
  112. package/templates/_default/docs/decisions/README.md +6 -0
  113. package/templates/_default/docs/deployment.md +46 -0
  114. package/templates/_default/docs/docker.md +63 -0
  115. package/templates/_default/docs/getting-started.md +214 -0
  116. package/templates/_default/package.json +15 -0
  117. package/templates/_default/rust-toolchain.toml +3 -0
  118. package/templates/_default/scripts/check +10 -0
  119. package/templates/_default/scripts/db-migrate +25 -0
  120. package/templates/_default/scripts/install-hooks +5 -0
  121. package/templates/_default/scripts/pre-push +3 -0
  122. package/templates/_default/scripts/setup +26 -0
@@ -0,0 +1,3 @@
1
+ [workspace]
2
+ members = ["apps/backend"]
3
+ resolver = "2"
@@ -0,0 +1,257 @@
1
+ # Engineering Convention
2
+
3
+ Version: 1.0
4
+ Updated: 2026-10-08
5
+
6
+ ## Purpose and scope
7
+
8
+ This convention defines how new software is built and maintained. It applies to developers, nontechnical contributors, and coding agents. Follow these defaults without requesting technical approval for routine implementation work.
9
+
10
+ **MUST** means required. **SHOULD** means the default, with a documented reason for a different choice. **MAY** means optional.
11
+
12
+ For an existing project, preserve its established stack and conventions. Do not rewrite or upgrade it simply to match this document. Explicit project instructions take precedence; conflicts affecting schemas, API contracts, or high-risk work require tech team review.
13
+
14
+ ## 1. Default stack
15
+
16
+ | Area | Default |
17
+ | --- | --- |
18
+ | Frontend language | Plain JavaScript with JSX; no TypeScript by default |
19
+ | Frontend framework | React |
20
+ | Frontend build tool | Vite |
21
+ | Frontend routing | React Router with path-based URLs (`/items`, `/items/:id`); no hash routing |
22
+ | Backend architecture | Modular monolith: one process, one database, modules inside the app; no microservices by default |
23
+ | Styling | Tailwind CSS |
24
+ | UI components | shadcn/ui configured for JavaScript |
25
+ | Backend language | Rust |
26
+ | Backend HTTP framework | Axum |
27
+ | Backend async runtime | Tokio |
28
+ | JSON serialization | Serde |
29
+ | Database access | SQLx with explicit SQL |
30
+ | Local development and lightweight database tests | SQLite |
31
+ | Shared environments and production | PostgreSQL |
32
+ | JavaScript package manager | npm |
33
+ | Rust package manager | Cargo |
34
+ | Frontend checks | ESLint, Prettier, Vitest; React Testing Library for component tests |
35
+ | Backend checks | rustfmt, Clippy, cargo test |
36
+ | Testing approach | Test-driven: failing test first, then the smallest passing change, then refactor |
37
+ | Deployment model | Blue/green, zero downtime: stateless process, `/ready` drain on `SIGTERM`, backward-compatible migrations and APIs |
38
+ | Backend structured logging | tracing with JSON output |
39
+
40
+ Plain JavaScript means JavaScript rather than TypeScript; React, JSX, and build tooling remain allowed. Do not introduce Next.js, an ORM, another language, or another architecture without a concrete need and an approved exception.
41
+
42
+ Axum, Tokio, and SQLx are the recommended backend defaults adopted by this convention. Keep their configuration minimal.
43
+
44
+ ## 2. Versions and reproducibility
45
+
46
+ - New projects MUST use the latest stable, mutually compatible releases available at setup time. Use the current Node.js LTS release and stable Rust toolchain.
47
+ - Record exact toolchain versions in project configuration, including `rust-toolchain.toml` and a Node version file or equivalent.
48
+ - Commit `package-lock.json` and `Cargo.lock` for applications. Use `npm ci` and locked Cargo operations in CI.
49
+ - Do not resolve floating `latest` versions on each build. Upgrade deliberately, run checks, and commit the resulting version changes.
50
+ - Avoid prerelease dependencies unless explicitly required.
51
+
52
+ ## 3. Architecture
53
+
54
+ - A new product MUST use a monorepo with a React frontend and one Rust backend application by default.
55
+ - Build a modular monolith, not microservices. One backend process, one database, one deployable. Separate concerns with modules inside the application (`models/`, `controllers/`, `views/`), not with separate services or network boundaries.
56
+ - Splitting a service out requires a concrete, measured reason (for example independent scaling, a hard isolation requirement, or a separate team owning a separate release cadence) and an approved exception recorded in `docs/decisions/`. "It might need to scale later" is not a reason.
57
+ - Use simple MVC responsibilities. A monorepo describes repository organization; it does not require microservices or multiple backend processes.
58
+ - Keep controllers thin, business rules in model modules, and presentation in views.
59
+ - Add small service modules only when they clarify business logic shared by multiple controllers. Do not create mandatory layers for every operation.
60
+ - Do not introduce microservices, event sourcing, CQRS, generic repository frameworks, or elaborate dependency injection unless explicitly requested.
61
+ - Prefer the smallest implementation that satisfies the current requirement and remains readable.
62
+
63
+ ### MVC responsibilities
64
+
65
+ | Responsibility | Frontend | Backend |
66
+ | --- | --- | --- |
67
+ | Model | Data transformations and client state | Business rules, domain types, and persistence functions |
68
+ | View | React components and pages | JSON response representations; templates only if needed |
69
+ | Controller | Hooks and event handlers coordinating user actions and API calls | Axum handlers coordinating input, models, and responses |
70
+
71
+ Frontend navigation uses React Router in browser-history mode. Register routes in one place (`src/routes.jsx`), use path-based URLs that mirror backend resource names, and serve the SPA with a history fallback so deep links load directly. Do not use hash-based URLs or hand-written `window.location` switching.
72
+
73
+ React is component based. Apply MVC separation without forcing every component into three files. The backend remains authoritative for business validation and permissions. Frontend code MUST NOT connect directly to the database.
74
+
75
+ ## 4. Repository organization
76
+
77
+ Use the following locations as a starting point. Create folders only when they contain meaningful code.
78
+
79
+ | Path | Purpose |
80
+ | --- | --- |
81
+ | `apps/frontend/src/components/` | Reusable React components, including `ui/` for shadcn/ui |
82
+ | `apps/frontend/src/pages/` | Page views, one component per route |
83
+ | `apps/frontend/src/routes.jsx` | Route registration (React Router) |
84
+ | `apps/frontend/src/hooks/` | Controller-style coordination and reusable hooks |
85
+ | `apps/frontend/src/models/` | Client state helpers and data transformations |
86
+ | `apps/frontend/src/api/` | HTTP client functions |
87
+ | `apps/backend/src/controllers/` | HTTP handlers |
88
+ | `apps/backend/src/models/` | Business rules and persistence modules |
89
+ | `apps/backend/src/views/` | Response representations when separate types help |
90
+ | `apps/backend/src/routes.rs` | Route registration |
91
+ | `apps/backend/src/config.rs` | Validated application configuration |
92
+ | `database/migrations/postgres/` | PostgreSQL SQL migrations |
93
+ | `database/migrations/sqlite/` | SQLite SQL migrations, when SQLite is supported |
94
+ | `scripts/` | Small development, checking, and migration commands |
95
+ | `docs/api/` | API contract and examples |
96
+ | `docs/decisions/` | Significant decisions and approved exceptions |
97
+
98
+ Tests MAY live beside source files or in dedicated test folders. Choose one consistent arrangement per application. Avoid unused scaffolding and vague dumping grounds such as large `utils` modules.
99
+
100
+ ## 5. Programming style
101
+
102
+ - Favor modular, functional code and composition. Keep functions focused and side effects explicit.
103
+ - Use React function components and hooks.
104
+ - Use classes in JavaScript only when they make stateful behavior clearer; avoid deep inheritance.
105
+ - In Rust, use structs, enums, methods, and traits where appropriate. Do not recreate class inheritance patterns.
106
+ - Prefer explicit data flow over global mutable state, hidden behavior, and clever abstractions.
107
+ - Keep SQL, HTTP handling, and UI rendering out of unrelated business functions.
108
+
109
+ ## 6. Reuse and dependencies
110
+
111
+ - Check existing project components, helpers, endpoints, and dependencies before adding replacements.
112
+ - Use Tailwind utilities and existing shadcn/ui components before building another UI system. Generate JavaScript components rather than introducing TypeScript files.
113
+ - Add third-party dependencies case by case. Document the purpose, alternatives considered, license suitability, maintenance status, and material security or operational impact in the pull request.
114
+ - A small, established dependency within the approved stack MAY be added through normal review. Stack changes and dependencies affecting authentication, sensitive data, infrastructure, or major ongoing cost require tech team review.
115
+ - Do not add a dependency for a trivial task that is clearer with a small amount of ordinary code.
116
+
117
+ ## 7. Database access
118
+
119
+ - Use SQLx and parameterized SQL. Never construct queries by concatenating untrusted input.
120
+ - Keep database access inside backend model/persistence modules, not controllers or frontend code.
121
+ - Use transactions for related writes that must succeed or fail together. Enforce integrity with appropriate constraints and indexes.
122
+ - Use SQLite for local workflows and lightweight tests; use PostgreSQL for shared environments and production.
123
+ - SQLite is not proof of PostgreSQL compatibility. PostgreSQL-bound changes MUST be verified against PostgreSQL when they affect queries, migrations, constraints, concurrency, or database-specific behavior.
124
+ - Keep business rules independent of database-specific SQL where practical. Do not build a generic database abstraction solely to conceal SQL differences.
125
+
126
+ ## 8. Independent database migrations
127
+
128
+ Migrations MUST be deployable independently of the application process.
129
+
130
+ - Store ordered, version-controlled SQL files under `database/migrations/`, outside application source directories.
131
+ - Use SQLx CLI as the default migration runner. Provide documented commands such as `db:migrate:postgres` and `db:migrate:sqlite`, each supplying the correct migrations directory and connection configuration.
132
+ - The migration command MUST run without starting or building the backend application. Do not automatically migrate on application startup.
133
+ - Use separate engine-specific migration directories where SQL differs. Keep the intended logical schema aligned and document differences.
134
+ - Use ordered names such as `20261008120000_create_users.sql`. Once applied to a shared environment, a migration MUST NOT be edited or deleted; add a corrective migration.
135
+ - Use the runner's migration history and locking support. Run one migration job per target database; do not rely solely on naming order to prevent concurrent execution.
136
+ - Validate migrations on a fresh database and on the previous supported schema. Test the actual target engine.
137
+ - Prefer backward-compatible changes: add new structures, migrate data, update the application, then remove obsolete structures in a later approved change.
138
+ - For destructive changes, document backup/recovery, compatibility, and data transformation before execution. Do not assume reversing a migration can restore deleted data.
139
+ - Schema changes require tech team approval before implementation. Executing a destructive migration requires explicit authorization for the target environment.
140
+
141
+ ## 9. Communication and REST APIs
142
+
143
+ - Use HTTP REST APIs with JSON first. Introduce events, queues, WebSockets, or background jobs only for a concrete requirement.
144
+ - Use plural resource nouns and predictable routes, for example `GET /api/v1/users` and `PATCH /api/v1/users/{id}`.
145
+ - Use HTTP methods and status codes consistently. Do not return `200` for failed operations.
146
+ - Validate request bodies, parameters, and query strings on the backend. Frontend validation improves usability but does not replace backend validation.
147
+ - Use consistent JSON naming: `camelCase`. Map Rust's internal `snake_case` fields through serialization configuration.
148
+ - Use UTC ISO 8601 timestamps. Choose and document identifier and pagination formats.
149
+ - Return resource objects directly by default. Collections SHOULD use `{ "items": [], "pagination": {} }` with documented pagination fields.
150
+ - Use a consistent error shape: `{ "error": { "code": "VALIDATION_ERROR", "message": "Invalid input", "details": [] }, "requestId": "..." }`. Omit optional details when unnecessary; never expose internals.
151
+ - Document endpoints, permissions, inputs, outputs, errors, and examples in a machine-readable OpenAPI contract where practical.
152
+ - New or changed API contracts require tech team approval before implementation. Keep compatible changes within the current version; explicitly version approved breaking changes.
153
+ - Calls to external services SHOULD use timeouts. Retry only when safe; do not automatically retry non-idempotent writes without a deliberate duplicate-prevention strategy.
154
+
155
+ ## 10. Security baseline
156
+
157
+ Use established security practices with safeguards proportionate to the application. Additional hardening is added when requested or justified by actual exposure; these baseline requirements always apply.
158
+
159
+ - Enforce authentication where required and authorization on the backend for every protected operation, including access to individual records.
160
+ - Reuse approved authentication solutions. Do not invent cryptography or custom token schemes.
161
+ - Never commit secrets or put private credentials in frontend bundles. Use injected configuration and keep local secret files out of version control.
162
+ - Validate input, parameterize SQL, and avoid unsafe HTML rendering.
163
+ - Use HTTPS for exposed applications and secure session/cookie settings where applicable. Apply CSRF protection when the authentication method requires it.
164
+ - Restrict cross-origin access to intended clients. Do not use unrestricted credentialed access.
165
+ - Never log passwords, tokens, database credentials, or unnecessary personal data.
166
+ - If passwords are stored, use an established password-hashing library with appropriate settings.
167
+ - Authentication, permission, sensitive-data, payment, and security-boundary changes are high-risk and require tech team involvement.
168
+
169
+ ## 11. Naming, formatting, and comments
170
+
171
+ - Frontend: `camelCase` for variables/functions, `PascalCase` for React components, and consistent filenames matching their primary export.
172
+ - Backend: standard Rust naming, rustfmt, and Clippy. Avoid unchecked `unwrap()`/`expect()` in request paths; handle failures explicitly.
173
+ - Run ESLint and Prettier for frontend code. Do not disable checks broadly to make a change pass.
174
+ - Use JSDoc for exported frontend functions, shared data shapes, and non-obvious interfaces. Document parameters, return values, and meaningful behavior. Do not comment every obvious line.
175
+ - Use Rust documentation comments for public interfaces when they need explanation.
176
+ - Comments SHOULD explain intent, constraints, or surprising behavior. Keep them accurate as code changes.
177
+
178
+ ## 12. Testing and mandatory checks
179
+
180
+ - Work test-first (TDD) for business behavior: write a failing test that states the expected outcome, make it pass with the smallest change, then refactor with the test green. Commit the test with the code it covers.
181
+ - Start from the outside in: an integration test on the HTTP contract (`tests/api.rs` or a Vitest page test) for the feature, then unit tests on model functions for validation rules and edge cases. Bug fixes begin with a test that reproduces the bug.
182
+ - Keep tests fast and isolated: SQLite in memory for backend tests, mocked `fetch` for frontend API tests, no shared mutable state between tests. A test that needs a running service or network is an integration check, not part of the unit baseline.
183
+ - Linting and unit tests MUST pass before every push. Provide a pre-push hook and equivalent CI checks; CI remains authoritative.
184
+ - Frontend baseline: formatting check, ESLint, and Vitest. Backend baseline: `cargo fmt --check`, `cargo clippy --locked --all-targets -- -D warnings`, and `cargo test --locked`.
185
+ - Verify application builds before merging a releasable change.
186
+ - New or changed business behavior SHOULD have meaningful unit tests; bug fixes SHOULD include a regression test where practical.
187
+ - Choose additional component, integration, API, and end-to-end tests case by case according to risk. Database and migration changes require target-engine integration checks.
188
+ - Test outcomes and failure cases rather than implementation details. Do not add low-value tests simply to increase coverage.
189
+ - No universal coverage percentage is imposed by this document. Existing project thresholds remain in force.
190
+ - Never bypass a failing check silently. Any emergency exception must be explicitly approved and recorded with follow-up work.
191
+
192
+ ## 13. GitFlow and review
193
+
194
+ - `main` contains released code; `develop` contains integrated work for the next release.
195
+ - Create `feature/<description>` branches from `develop` and merge through pull requests after checks pass.
196
+ - Create `release/<version>` branches from `develop` for release preparation; merge the completed release into `main` and back into `develop`, and tag it.
197
+ - Create `hotfix/<description>` branches from `main` for urgent released-code fixes; merge back into `main` and `develop`, including any active release branch as needed.
198
+ - Do not push directly to protected branches. Follow the repository's reviewer and approval requirements.
199
+ - Keep pull requests focused. Describe the resulting behavior, checks performed, and any schema/API/risk implications.
200
+ - Routine work follows normal contributor review. Specialist tech team approval is required only at the boundaries defined below.
201
+
202
+ ## 14. Documentation and observability
203
+
204
+ - Maintain a concise README covering setup, pinned tools, configuration variable names, running, checking, and independent migration commands.
205
+ - Maintain API documentation and significant decision/exception records when those change.
206
+ - Provide a lightweight `GET /health` endpoint for process liveness. It MUST NOT expose sensitive configuration or run migrations.
207
+ - Add a separate readiness endpoint when traffic acceptance depends on database or other critical dependency availability. Keep dependency checks bounded and inexpensive.
208
+ - Backend application logs MUST be JSON lines written to stdout, ready for later aggregation.
209
+ - Include timestamp, severity, application name, event/message, and request ID where relevant. Log useful failures and request outcomes without secrets or full payloads by default.
210
+ - Add advanced monitoring only when required. Hosting, deployment infrastructure, and rollback tooling are outside this convention's current scope.
211
+
212
+ ### Zero-downtime deployment readiness
213
+
214
+ Applications are deployed blue/green: a new instance starts beside the old one, traffic switches once it is ready, and the old one drains. The code MUST make that safe regardless of the hosting platform.
215
+
216
+ - The backend process is stateless. Sessions, uploads, caches, and queues live outside the process; two instances with the same configuration MUST be interchangeable.
217
+ - All configuration comes from environment variables. The same image runs in every environment; no environment-specific build.
218
+ - `GET /health` reports liveness plus the running `version` and `build` (git SHA injected at build time) so the serving instance can be identified. `GET /ready` returns `503` while dependencies are unavailable and while the process is draining.
219
+ - Handle `SIGTERM` gracefully: flip readiness to `503`, wait a short configurable drain period so load balancers stop routing, finish in-flight requests, close the database pool, then exit `0`. Never exit immediately on `SIGTERM`.
220
+ - Database migrations run before the new instance starts (section 8) and MUST be compatible with both the old and the new code while both are serving. Follow expand/contract: add, backfill, switch code, remove later.
221
+ - API changes within a version MUST be backward compatible for the same reason; the old and new instances may answer requests from the same client during a cut-over.
222
+ - Rollback is switching traffic back to the previous instance. Do not ship a change that cannot run beside its predecessor without an approved exception and a documented cut-over plan.
223
+ - Multi-instance deployments require PostgreSQL; SQLite is single-process and not supported for shared environments.
224
+
225
+ ## 15. Decision boundaries for contributors and agents
226
+
227
+ | Change | Authority |
228
+ | --- | --- |
229
+ | UI, internal refactoring, tests, documentation, or implementation using existing schemas and API contracts | Contributor/agent may proceed through normal review |
230
+ | Small dependency within the approved stack | Case-by-case normal review; escalate material risk |
231
+ | New/changed database schema or migration | Tech team approval before implementation |
232
+ | New/changed API contract | Tech team approval before implementation |
233
+ | Authentication, permissions, sensitive data, payments, destructive operations, or substantial availability/cost impact | Tech team approval before implementation or execution, as applicable |
234
+ | Different stack, architecture, or exception to a required rule | Explicit approval from the tech team or responsible technical owner |
235
+
236
+ Routine implementation does not need repeated permission. Existing approval for a specified schema, contract, or high-risk task covers implementation within that approved scope. Renew review when the scope materially changes.
237
+
238
+ Nontechnical contributors and agents MUST identify whether a request crosses these boundaries before starting dependent work. They MAY continue independent, low-risk work while a technical decision is pending. Record approved exceptions, their rationale, and their scope in the pull request or decision log.
239
+
240
+ ## Completion checklist
241
+
242
+ - The change follows the approved stack and simple MVC responsibilities.
243
+ - Existing components and behavior were checked before adding replacements.
244
+ - Required schema, API, and high-risk decisions have approval.
245
+ - Formatting, linting, and unit tests pass before push; applicable builds and integration checks pass before merge.
246
+ - API documentation, README instructions, and migration files reflect relevant changes.
247
+ - No secrets or unnecessary sensitive data enter source, responses, or logs.
248
+ - The pull request describes behavior and verification clearly.
249
+
250
+ ## Reference documentation
251
+
252
+ - [Axum](https://docs.rs/axum/latest/axum/)
253
+ - [SQLx](https://docs.rs/sqlx/latest/sqlx/)
254
+ - [SQLx CLI and migrations](https://github.com/transact-rs/sqlx/blob/main/sqlx-cli/README.md)
255
+ - [shadcn/ui JavaScript configuration](https://ui.shadcn.com/docs/javascript)
256
+
257
+ These links explain the selected tools. The defaults and approval rules above are team policy, not requirements imposed by those tools.
@@ -0,0 +1,29 @@
1
+ # Starting a project from this template
2
+
3
+ 1. Copy this folder to your new repository and `git init`.
4
+ 2. Rename the app: `name` in `package.json`, the `backend` package in `apps/backend/Cargo.toml`
5
+ (and `-p backend` mentions), and `APP_NAME` in `.env.example`.
6
+ 3. Run `scripts/setup` (creates `.env`, installs frontend deps, fetches crates; it prints how to
7
+ install the `sqlx` CLI if missing).
8
+ 4. Create the local database: `scripts/db-migrate sqlite` (or `postgres` with a Postgres `DATABASE_URL`).
9
+ 5. Run all checks: `scripts/check` (or `npm run check`).
10
+ 6. Optionally install the pre-push hook: `scripts/install-hooks` (runs `scripts/check` before push).
11
+
12
+ Commit `Cargo.lock` and `apps/frontend/package-lock.json`.
13
+
14
+ ## Layout
15
+
16
+ - `apps/frontend`, `apps/backend` - the two applications.
17
+ - `database/migrations/{postgres,sqlite}` - SQL migrations, run independently via `scripts/db-migrate`.
18
+ - `docs/getting-started.md` - walkthrough of the sample feature and how to add your own.
19
+ - `docs/deployment.md` - blue/green zero-downtime deployment guarantees and cut-over steps.
20
+ - `docs/docker.md` - Docker images and compose stacks (dev and prod example).
21
+ - `docs/api`, `docs/decisions` - API contract and decision records.
22
+ - [`.agents/skills/README.md`](.agents/skills/README.md) - portable agent skills and explicit loading instructions.
23
+ - `.github/workflows/ci.yml` - CI; pin actions to commit SHAs for your team.
24
+
25
+ ## README.md
26
+
27
+ `README.md` is the Engineering Convention. In a real project, replace it with a project README
28
+ (setup, pinned tools, config variable names, running, checking, migration commands) and keep a
29
+ link to the convention.
@@ -0,0 +1,8 @@
1
+ target
2
+ node_modules
3
+ apps/frontend/node_modules
4
+ dist
5
+ .git
6
+ .env
7
+ *.db
8
+ local.db
@@ -0,0 +1,8 @@
1
+ target/
2
+ node_modules/
3
+ dist/
4
+ .env
5
+ *.sqlite
6
+ *.db
7
+ .DS_Store
8
+ coverage/
@@ -0,0 +1,22 @@
1
+ [package]
2
+ name = "backend"
3
+ version = "0.1.0"
4
+ edition = "2024"
5
+ publish = false
6
+
7
+ [dependencies]
8
+ axum = "0.8.9"
9
+ dotenvy = "0.15.7"
10
+ serde = { version = "1.0.229", features = ["derive"] }
11
+ serde_json = "1.0.151"
12
+ sqlx = { version = "0.9.0", default-features = false, features = ["runtime-tokio", "sqlite", "postgres", "any", "migrate", "derive"] }
13
+ thiserror = "2.0.21"
14
+ tokio = { version = "1.53.2", features = ["full"] }
15
+ tower-http = { version = "0.7.1", features = ["cors", "trace"] }
16
+ tracing = "0.1.44"
17
+ tracing-subscriber = { version = "0.3.23", features = ["json", "env-filter"] }
18
+ uuid = { version = "1.27.0", features = ["v4"] }
19
+
20
+ [dev-dependencies]
21
+ http-body-util = "0.1.5"
22
+ tower = "0.5.3"
@@ -0,0 +1,34 @@
1
+ # Build from the template root: docker build -f apps/backend/Dockerfile -t backend .
2
+ # No TLS crates in Cargo.lock, so no openssl build dependencies are needed.
3
+ # rust-toolchain.toml is not copied: the base image tag pins the toolchain (keep it in sync).
4
+ FROM rust:1.95-slim-bookworm AS dev
5
+ RUN cargo install cargo-watch --locked
6
+ WORKDIR /app
7
+ CMD ["cargo", "watch", "-x", "run -p backend"]
8
+
9
+ FROM rust:1.95-slim-bookworm AS builder
10
+ WORKDIR /build
11
+
12
+ # Dependency layer: build with stub sources so it is cached until manifests change.
13
+ COPY Cargo.toml Cargo.lock ./
14
+ COPY apps/backend/Cargo.toml apps/backend/Cargo.toml
15
+ RUN mkdir -p apps/backend/src \
16
+ && echo 'fn main() {}' > apps/backend/src/main.rs \
17
+ && touch apps/backend/src/lib.rs \
18
+ && cargo build --release --locked -p backend
19
+
20
+ COPY apps/backend/src apps/backend/src
21
+ ARG BUILD_SHA=dev
22
+ ENV BUILD_SHA=$BUILD_SHA
23
+ RUN touch apps/backend/src/main.rs apps/backend/src/lib.rs \
24
+ && cargo build --release --locked -p backend
25
+
26
+ FROM debian:bookworm-slim
27
+ RUN apt-get update \
28
+ && apt-get install -y --no-install-recommends ca-certificates \
29
+ && rm -rf /var/lib/apt/lists/* \
30
+ && useradd --system --uid 10001 --no-create-home app
31
+ COPY --from=builder /build/target/release/backend /usr/local/bin/backend
32
+ USER app
33
+ EXPOSE 8080
34
+ CMD ["backend"]
@@ -0,0 +1,13 @@
1
+ # Backend
2
+
3
+ Rust, Axum, Tokio, SQLx (`AnyPool`: SQLite or PostgreSQL chosen by `DATABASE_URL`).
4
+
5
+ Run (from the template root, after `scripts/db-migrate sqlite`):
6
+
7
+ cargo run -p backend
8
+
9
+ Environment (see `.env.example`): `APP_NAME`, `APP_PORT`, `DATABASE_URL`, `RUST_LOG`,
10
+ `CORS_ALLOWED_ORIGINS` (comma-separated, no wildcard).
11
+
12
+ Checks: `cargo fmt --all --check`, `cargo clippy --locked --all-targets -- -D warnings`,
13
+ `cargo test --locked`. The app never runs migrations; use `scripts/db-migrate`.
@@ -0,0 +1,111 @@
1
+ use std::env;
2
+
3
+ use axum::http::HeaderValue;
4
+
5
+ #[derive(Debug, thiserror::Error)]
6
+ pub enum ConfigError {
7
+ #[error("missing environment variable {0}")]
8
+ Missing(&'static str),
9
+ #[error("invalid {name}: {reason}")]
10
+ Invalid { name: &'static str, reason: String },
11
+ }
12
+
13
+ #[derive(Debug, Clone)]
14
+ pub struct Config {
15
+ pub app_name: String,
16
+ pub port: u16,
17
+ pub database_url: String,
18
+ pub cors_allowed_origins: Vec<HeaderValue>,
19
+ pub shutdown_drain_seconds: u64,
20
+ }
21
+
22
+ fn required(name: &'static str) -> Result<String, ConfigError> {
23
+ match env::var(name) {
24
+ Ok(v) if !v.trim().is_empty() => Ok(v.trim().to_string()),
25
+ _ => Err(ConfigError::Missing(name)),
26
+ }
27
+ }
28
+
29
+ impl Config {
30
+ pub fn from_env() -> Result<Self, ConfigError> {
31
+ let app_name = required("APP_NAME")?;
32
+ let port = required("APP_PORT")?
33
+ .parse::<u16>()
34
+ .map_err(|e| ConfigError::Invalid {
35
+ name: "APP_PORT",
36
+ reason: e.to_string(),
37
+ })?;
38
+ let database_url = required("DATABASE_URL")?;
39
+ let supported = ["sqlite:", "postgres:", "postgresql:"];
40
+ if !supported.iter().any(|p| database_url.starts_with(p)) {
41
+ return Err(ConfigError::Invalid {
42
+ name: "DATABASE_URL",
43
+ reason: "must start with sqlite:, postgres: or postgresql:".into(),
44
+ });
45
+ }
46
+ let cors_allowed_origins = parse_origins(&required("CORS_ALLOWED_ORIGINS")?)?;
47
+ let shutdown_drain_seconds = parse_drain(env::var("SHUTDOWN_DRAIN_SECONDS").ok())?;
48
+ Ok(Self {
49
+ app_name,
50
+ port,
51
+ database_url,
52
+ cors_allowed_origins,
53
+ shutdown_drain_seconds,
54
+ })
55
+ }
56
+ }
57
+
58
+ pub const DEFAULT_DRAIN_SECONDS: u64 = 5;
59
+
60
+ pub fn parse_drain(raw: Option<String>) -> Result<u64, ConfigError> {
61
+ match raw.as_deref().map(str::trim).filter(|v| !v.is_empty()) {
62
+ None => Ok(DEFAULT_DRAIN_SECONDS),
63
+ Some(v) => v
64
+ .parse()
65
+ .map_err(|e: std::num::ParseIntError| ConfigError::Invalid {
66
+ name: "SHUTDOWN_DRAIN_SECONDS",
67
+ reason: e.to_string(),
68
+ }),
69
+ }
70
+ }
71
+
72
+ pub fn parse_origins(raw: &str) -> Result<Vec<HeaderValue>, ConfigError> {
73
+ raw.split(',')
74
+ .map(str::trim)
75
+ .filter(|o| !o.is_empty())
76
+ .map(|o| {
77
+ if o == "*" {
78
+ return Err(ConfigError::Invalid {
79
+ name: "CORS_ALLOWED_ORIGINS",
80
+ reason: "wildcard origin is not allowed".into(),
81
+ });
82
+ }
83
+ HeaderValue::from_str(o).map_err(|e| ConfigError::Invalid {
84
+ name: "CORS_ALLOWED_ORIGINS",
85
+ reason: e.to_string(),
86
+ })
87
+ })
88
+ .collect()
89
+ }
90
+
91
+ #[cfg(test)]
92
+ mod tests {
93
+ use super::{parse_drain, parse_origins};
94
+
95
+ #[test]
96
+ fn drain_defaults_to_five() {
97
+ assert_eq!(parse_drain(None).unwrap(), 5);
98
+ assert_eq!(parse_drain(Some("12".into())).unwrap(), 12);
99
+ assert!(parse_drain(Some("x".into())).is_err());
100
+ }
101
+
102
+ #[test]
103
+ fn rejects_wildcard() {
104
+ assert!(parse_origins("*").is_err());
105
+ }
106
+
107
+ #[test]
108
+ fn parses_list() {
109
+ assert_eq!(parse_origins("http://a, http://b").unwrap().len(), 2);
110
+ }
111
+ }
@@ -0,0 +1,36 @@
1
+ use std::{sync::atomic::Ordering, time::Duration};
2
+
3
+ use axum::{Json, extract::State};
4
+ use serde_json::{Value, json};
5
+
6
+ use crate::{error::AppError, state::AppState};
7
+
8
+ pub async fn health() -> Json<Value> {
9
+ Json(json!({
10
+ "status": "ok",
11
+ "version": env!("CARGO_PKG_VERSION"),
12
+ "build": option_env!("BUILD_SHA").unwrap_or("dev"),
13
+ }))
14
+ }
15
+
16
+ pub async fn ready(State(state): State<AppState>) -> Result<Json<Value>, AppError> {
17
+ if state.draining.load(Ordering::SeqCst) {
18
+ return Err(AppError::Draining);
19
+ }
20
+ let check = tokio::time::timeout(
21
+ Duration::from_secs(2),
22
+ sqlx::query("SELECT 1").execute(&state.pool),
23
+ )
24
+ .await;
25
+ match check {
26
+ Ok(Ok(_)) => Ok(Json(json!({ "status": "ready" }))),
27
+ Ok(Err(e)) => {
28
+ tracing::error!(error = %e, "readiness check failed");
29
+ Err(AppError::Unavailable)
30
+ }
31
+ Err(_) => {
32
+ tracing::error!("readiness check timed out");
33
+ Err(AppError::Unavailable)
34
+ }
35
+ }
36
+ }
@@ -0,0 +1,79 @@
1
+ use axum::{
2
+ Json,
3
+ extract::{
4
+ Path, Query, State,
5
+ rejection::{JsonRejection, PathRejection, QueryRejection},
6
+ },
7
+ http::StatusCode,
8
+ };
9
+ use serde::Deserialize;
10
+
11
+ use crate::{
12
+ error::AppError,
13
+ models::item::{self, Item},
14
+ state::AppState,
15
+ views::items::{ItemListResponse, Pagination},
16
+ };
17
+
18
+ const DEFAULT_PER_PAGE: i64 = 20;
19
+ const MAX_PER_PAGE: i64 = 100;
20
+
21
+ #[derive(Debug, Deserialize)]
22
+ #[serde(rename_all = "camelCase")]
23
+ pub struct ListQuery {
24
+ page: Option<i64>,
25
+ per_page: Option<i64>,
26
+ }
27
+
28
+ #[derive(Debug, Deserialize)]
29
+ pub struct CreateItem {
30
+ name: String,
31
+ }
32
+
33
+ pub async fn list(
34
+ State(state): State<AppState>,
35
+ query: Result<Query<ListQuery>, QueryRejection>,
36
+ ) -> Result<Json<ItemListResponse>, AppError> {
37
+ let Query(q) = query.map_err(|_| AppError::validation("query", "Invalid query string"))?;
38
+ let page = q.page.unwrap_or(1);
39
+ let per_page = q.per_page.unwrap_or(DEFAULT_PER_PAGE);
40
+ if page < 1 {
41
+ return Err(AppError::validation("page", "Must be at least 1"));
42
+ }
43
+ if !(1..=MAX_PER_PAGE).contains(&per_page) {
44
+ return Err(AppError::validation("perPage", "Must be between 1 and 100"));
45
+ }
46
+ let offset = (page - 1)
47
+ .checked_mul(per_page)
48
+ .ok_or_else(|| AppError::validation("page", "Too large"))?;
49
+ let (items, total) = item::list(&state.pool, per_page, offset).await?;
50
+ Ok(Json(ItemListResponse {
51
+ items,
52
+ pagination: Pagination {
53
+ page,
54
+ per_page,
55
+ total,
56
+ },
57
+ }))
58
+ }
59
+
60
+ pub async fn create(
61
+ State(state): State<AppState>,
62
+ body: Result<Json<CreateItem>, JsonRejection>,
63
+ ) -> Result<(StatusCode, Json<Item>), AppError> {
64
+ let Json(input) = body.map_err(|_| AppError::validation("body", "Invalid JSON body"))?;
65
+ let name = item::validate_name(&input.name)?;
66
+ let created = item::create(&state.pool, &name).await?;
67
+ Ok((StatusCode::CREATED, Json(created)))
68
+ }
69
+
70
+ pub async fn get(
71
+ State(state): State<AppState>,
72
+ id: Result<Path<i64>, PathRejection>,
73
+ ) -> Result<Json<Item>, AppError> {
74
+ let Path(id) = id.map_err(|_| AppError::validation("id", "Must be an integer"))?;
75
+ item::find(&state.pool, id)
76
+ .await?
77
+ .map(Json)
78
+ .ok_or(AppError::NotFound)
79
+ }
@@ -0,0 +1,2 @@
1
+ pub mod health;
2
+ pub mod items;