@coralai/sps-cli 0.42.0 → 0.44.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 (147) hide show
  1. package/README.md +59 -4
  2. package/dist/commands/consoleCommand.d.ts +2 -0
  3. package/dist/commands/consoleCommand.d.ts.map +1 -0
  4. package/dist/commands/consoleCommand.js +129 -0
  5. package/dist/commands/consoleCommand.js.map +1 -0
  6. package/dist/commands/projectInit.d.ts.map +1 -1
  7. package/dist/commands/projectInit.js +40 -53
  8. package/dist/commands/projectInit.js.map +1 -1
  9. package/dist/commands/setup.d.ts.map +1 -1
  10. package/dist/commands/setup.js +14 -2
  11. package/dist/commands/setup.js.map +1 -1
  12. package/dist/commands/skillCommand.d.ts +2 -0
  13. package/dist/commands/skillCommand.d.ts.map +1 -0
  14. package/dist/commands/skillCommand.js +235 -0
  15. package/dist/commands/skillCommand.js.map +1 -0
  16. package/dist/console-assets/assets/index-Bhd2f9AP.js +125 -0
  17. package/dist/console-assets/assets/index-bsAN2a12.css +1 -0
  18. package/dist/console-assets/index.html +16 -0
  19. package/dist/console-server/index.d.ts +29 -0
  20. package/dist/console-server/index.d.ts.map +1 -0
  21. package/dist/console-server/index.js +145 -0
  22. package/dist/console-server/index.js.map +1 -0
  23. package/dist/console-server/lib/lockFile.d.ts +17 -0
  24. package/dist/console-server/lib/lockFile.d.ts.map +1 -0
  25. package/dist/console-server/lib/lockFile.js +61 -0
  26. package/dist/console-server/lib/lockFile.js.map +1 -0
  27. package/dist/console-server/lib/portPicker.d.ts +3 -0
  28. package/dist/console-server/lib/portPicker.d.ts.map +1 -0
  29. package/dist/console-server/lib/portPicker.js +25 -0
  30. package/dist/console-server/lib/portPicker.js.map +1 -0
  31. package/dist/console-server/routes/projects.d.ts +11 -0
  32. package/dist/console-server/routes/projects.d.ts.map +1 -0
  33. package/dist/console-server/routes/projects.js +149 -0
  34. package/dist/console-server/routes/projects.js.map +1 -0
  35. package/dist/console-server/routes/system.d.ts +7 -0
  36. package/dist/console-server/routes/system.d.ts.map +1 -0
  37. package/dist/console-server/routes/system.js +19 -0
  38. package/dist/console-server/routes/system.js.map +1 -0
  39. package/dist/console-server/sse/eventBus.d.ts +25 -0
  40. package/dist/console-server/sse/eventBus.d.ts.map +1 -0
  41. package/dist/console-server/sse/eventBus.js +32 -0
  42. package/dist/console-server/sse/eventBus.js.map +1 -0
  43. package/dist/console-server/watchers/cardWatcher.d.ts +9 -0
  44. package/dist/console-server/watchers/cardWatcher.d.ts.map +1 -0
  45. package/dist/console-server/watchers/cardWatcher.js +42 -0
  46. package/dist/console-server/watchers/cardWatcher.js.map +1 -0
  47. package/dist/core/skillStore.d.ts +46 -0
  48. package/dist/core/skillStore.d.ts.map +1 -0
  49. package/dist/core/skillStore.js +210 -0
  50. package/dist/core/skillStore.js.map +1 -0
  51. package/dist/core/skillStore.test.d.ts +2 -0
  52. package/dist/core/skillStore.test.d.ts.map +1 -0
  53. package/dist/core/skillStore.test.js +203 -0
  54. package/dist/core/skillStore.test.js.map +1 -0
  55. package/dist/main.js +27 -17
  56. package/dist/main.js.map +1 -1
  57. package/package.json +8 -2
  58. package/skills/architecture-decision-records/SKILL.md +207 -0
  59. package/skills/backend/SKILL.md +62 -0
  60. package/skills/backend/references/api-design.md +168 -0
  61. package/skills/backend/references/caching.md +181 -0
  62. package/skills/backend/references/data-access.md +173 -0
  63. package/skills/backend/references/layering.md +181 -0
  64. package/skills/backend/references/observability.md +190 -0
  65. package/skills/backend/references/resilience.md +201 -0
  66. package/skills/backend/references/security.md +186 -0
  67. package/skills/backend-architect/SKILL.md +119 -0
  68. package/skills/code-reviewer/SKILL.md +143 -0
  69. package/skills/coding-standards/SKILL.md +60 -0
  70. package/skills/coding-standards/references/clean-code.md +258 -0
  71. package/skills/coding-standards/references/code-review.md +192 -0
  72. package/skills/coding-standards/references/commits-and-prs.md +226 -0
  73. package/skills/coding-standards/references/error-strategy.md +193 -0
  74. package/skills/coding-standards/references/naming.md +185 -0
  75. package/skills/coding-standards/references/tdd.md +171 -0
  76. package/skills/database/SKILL.md +53 -0
  77. package/skills/database/references/indexing.md +190 -0
  78. package/skills/database/references/migrations.md +199 -0
  79. package/skills/database/references/nosql.md +185 -0
  80. package/skills/database/references/queries.md +295 -0
  81. package/skills/database/references/scaling.md +203 -0
  82. package/skills/database/references/schema.md +191 -0
  83. package/skills/database-optimizer/SKILL.md +168 -0
  84. package/skills/debugging-workflow/SKILL.md +244 -0
  85. package/skills/devops/SKILL.md +55 -0
  86. package/skills/devops/references/ci-cd.md +204 -0
  87. package/skills/devops/references/containers.md +272 -0
  88. package/skills/devops/references/deploy.md +201 -0
  89. package/skills/devops/references/iac.md +252 -0
  90. package/skills/devops/references/observability.md +228 -0
  91. package/skills/devops/references/secrets.md +178 -0
  92. package/skills/devops-automator/SKILL.md +164 -0
  93. package/skills/frontend/SKILL.md +52 -0
  94. package/skills/frontend/references/accessibility.md +222 -0
  95. package/skills/frontend/references/components.md +206 -0
  96. package/skills/frontend/references/performance.md +219 -0
  97. package/skills/frontend/references/routing.md +209 -0
  98. package/skills/frontend/references/state.md +190 -0
  99. package/skills/frontend/references/testing.md +216 -0
  100. package/skills/frontend-developer/SKILL.md +115 -0
  101. package/skills/git-workflow/SKILL.md +355 -0
  102. package/skills/golang/SKILL.md +49 -0
  103. package/skills/golang/references/concurrency.md +284 -0
  104. package/skills/golang/references/errors.md +241 -0
  105. package/skills/golang/references/idioms.md +285 -0
  106. package/skills/golang/references/testing.md +238 -0
  107. package/skills/java/SKILL.md +50 -0
  108. package/skills/java/references/concurrency.md +194 -0
  109. package/skills/java/references/idioms.md +283 -0
  110. package/skills/java/references/testing.md +228 -0
  111. package/skills/kotlin/SKILL.md +47 -0
  112. package/skills/kotlin/references/coroutines.md +240 -0
  113. package/skills/kotlin/references/idioms.md +268 -0
  114. package/skills/kotlin/references/testing.md +219 -0
  115. package/skills/mobile/SKILL.md +50 -0
  116. package/skills/mobile/references/architecture.md +204 -0
  117. package/skills/mobile/references/navigation.md +158 -0
  118. package/skills/mobile/references/performance.md +152 -0
  119. package/skills/mobile/references/platform.md +166 -0
  120. package/skills/mobile/references/state-and-data.md +174 -0
  121. package/skills/python/SKILL.md +51 -0
  122. package/skills/python/THIRD_PARTY.md +14 -0
  123. package/skills/python/references/async.md +218 -0
  124. package/skills/python/references/error-handling.md +254 -0
  125. package/skills/python/references/idioms.md +279 -0
  126. package/skills/python/references/packaging.md +233 -0
  127. package/skills/python/references/testing.md +269 -0
  128. package/skills/python/references/typing.md +292 -0
  129. package/skills/qa-tester/SKILL.md +186 -0
  130. package/skills/rust/SKILL.md +50 -0
  131. package/skills/rust/references/async.md +224 -0
  132. package/skills/rust/references/errors.md +240 -0
  133. package/skills/rust/references/ownership.md +263 -0
  134. package/skills/rust/references/testing.md +274 -0
  135. package/skills/rust/references/traits.md +250 -0
  136. package/skills/security-engineer/SKILL.md +157 -0
  137. package/skills/swift/SKILL.md +48 -0
  138. package/skills/swift/references/concurrency.md +280 -0
  139. package/skills/swift/references/idioms.md +334 -0
  140. package/skills/swift/references/testing.md +229 -0
  141. package/skills/typescript/SKILL.md +51 -0
  142. package/skills/typescript/references/async.md +241 -0
  143. package/skills/typescript/references/errors.md +208 -0
  144. package/skills/typescript/references/idioms.md +246 -0
  145. package/skills/typescript/references/testing.md +225 -0
  146. package/skills/typescript/references/tooling.md +208 -0
  147. package/skills/typescript/references/types.md +259 -0
@@ -0,0 +1,226 @@
1
+ # Commits and Pull Requests
2
+
3
+ Small commits, small PRs, clear messages. The boring hygiene that pays off during incidents.
4
+
5
+ ## One logical change per commit
6
+
7
+ A commit should be a coherent unit — reverting it should be safe, and the message should explain *why*.
8
+
9
+ ```
10
+ # ✅ good commit progression
11
+ 1. refactor: extract UserValidator from UserService (no behavior change)
12
+ 2. test: add failing test for empty-email case
13
+ 3. fix(user): reject empty email in validator
14
+ 4. docs: note the new error response in the API doc
15
+
16
+ # ❌ "WIP", "fix stuff", "address review", "final changes final v2"
17
+ ```
18
+
19
+ If you can't describe the commit in one clear sentence, split it.
20
+
21
+ ## Commit messages — Conventional Commits
22
+
23
+ ```
24
+ <type>(<scope>): <summary>
25
+
26
+ <body: why the change, not what>
27
+
28
+ <footer: tickets, breaking changes, co-authors>
29
+ ```
30
+
31
+ ### Types
32
+
33
+ | Type | Use for |
34
+ |---|---|
35
+ | `feat` | New user-visible feature |
36
+ | `fix` | Bug fix |
37
+ | `perf` | Performance improvement (behavior unchanged) |
38
+ | `refactor` | Code change with no behavior change |
39
+ | `docs` | Docs only |
40
+ | `test` | Tests only |
41
+ | `build` | Build system, deps, tooling |
42
+ | `ci` | CI config |
43
+ | `chore` | Housekeeping, version bumps |
44
+ | `style` | Formatting only |
45
+ | `revert` | Reverts a previous commit |
46
+
47
+ ### Example
48
+
49
+ ```
50
+ fix(auth): reject tokens with `alg: none`
51
+
52
+ A crafted JWT with `alg: none` bypassed signature verification on the
53
+ /admin endpoints because we used a default-permissive parser. This locks
54
+ the accepted algorithm set to HS256 and RS256 and rejects everything else
55
+ at parse time.
56
+
57
+ Refs: SEC-412
58
+ Co-Authored-By: Alice <alice@example.com>
59
+ ```
60
+
61
+ Rules:
62
+ - **Summary line ≤ 72 chars**, imperative mood ("add", not "added").
63
+ - **Body wraps at 72**, separated from summary by blank line.
64
+ - Explain *why*; the diff already shows *what*.
65
+ - Reference tickets / incidents.
66
+
67
+ ## Subject line quick rules
68
+
69
+ ```
70
+ ✅ feat(orders): support partial shipments
71
+ ✅ fix(api): return 422 instead of 500 on malformed JSON
72
+ ✅ perf(search): precompute embedding lookup table
73
+
74
+ ❌ Updated file
75
+ ❌ More changes
76
+ ❌ Fix bug
77
+ ❌ Small tweaks
78
+ ❌ WIP
79
+ ```
80
+
81
+ If you find yourself writing "and" in a commit message, you're committing two things. Split.
82
+
83
+ ## Atomic commits = safe rollbacks
84
+
85
+ ```
86
+ git revert <commit> # cleanly reverses one logical change
87
+ ```
88
+
89
+ If one commit touches migrations, feature code, and unrelated formatting, you can't revert safely. Splitting pays off the first time something goes wrong.
90
+
91
+ ## PR size — keep it reviewable
92
+
93
+ | Diff size | What's reasonable |
94
+ |---|---|
95
+ | < 100 lines | Normal feature increment |
96
+ | 100–400 | Upper bound for typical work |
97
+ | 400+ | Only for: rename, generated code, new directory from a template |
98
+ | 1000+ | You're hiding a mistake |
99
+
100
+ Split by:
101
+ - Behavior change vs. refactor — separate PRs
102
+ - Independent feature pieces — stacked PRs
103
+ - Rename / formatting — always its own PR
104
+
105
+ Reviewers have finite attention. A 2000-line PR gets a rubber stamp, not a review.
106
+
107
+ ## Branching
108
+
109
+ Pick one and stick to it.
110
+
111
+ - **Trunk-based** — short-lived branches, merge to main daily. Best for most teams.
112
+ - **GitFlow** — `develop` + `release/*` + `hotfix/*`. Heavier; makes sense for shipped products with long support branches.
113
+ - **Feature branches + PR to main** — pragmatic middle ground.
114
+
115
+ Whichever you pick, the rule holds: short-lived branches. Long-lived branches drift and merge pain compounds.
116
+
117
+ ## Branch naming
118
+
119
+ ```
120
+ <type>/<short-slug>
121
+
122
+ feat/partial-shipments
123
+ fix/auth-alg-none
124
+ chore/bump-node-20
125
+ refactor/extract-validator
126
+ ```
127
+
128
+ Include a ticket id if your team tracks them: `fix/SEC-412-alg-none`.
129
+
130
+ ## The PR description
131
+
132
+ A good PR description saves the reviewer 10 minutes.
133
+
134
+ ```markdown
135
+ ## What
136
+ Support partial shipments on orders.
137
+
138
+ ## Why
139
+ Warehouse can't always fulfill the full order in one go; today they either
140
+ ship late or cancel. Closes ORDERS-187.
141
+
142
+ ## How
143
+ - New `Shipment` aggregate, `Order.shipments: []Shipment`
144
+ - `POST /orders/:id/shipments` creates one
145
+ - Order status derives from shipment state (`partial` if any unshipped items)
146
+
147
+ ## Tests
148
+ - Unit: shipment state transitions
149
+ - Integration: POST /shipments, GET /orders/:id reflects partial status
150
+ - Manual: verified on staging with 2-item order, shipped one item, refunded another
151
+
152
+ ## Rollout
153
+ - Feature flag: `partial_shipments_enabled` (default off)
154
+ - Migration: adds `shipments` table; safe on empty + large tables (indexed)
155
+ - Rollback: disable flag; table can stay
156
+ ```
157
+
158
+ If the team has a PR template, use it — even for "obvious" PRs. Reviewer context is never obvious to the reviewer.
159
+
160
+ ## Rebase vs. merge
161
+
162
+ - **Rebase your feature branch on main** before opening a PR to get a clean linear history.
163
+ - **Squash on merge** to main if the PR has noisy intermediate commits; keep it unsquashed if the history is clean and meaningful.
164
+ - **Never rebase a branch other people are working on.** Force-push on a shared branch is how you make enemies.
165
+
166
+ ## `.gitignore` — keep secrets and junk out
167
+
168
+ Default entries for any repo:
169
+
170
+ ```
171
+ # dependencies
172
+ node_modules/
173
+ .venv/
174
+ target/
175
+
176
+ # env / secrets
177
+ .env
178
+ .env.local
179
+ *.pem
180
+ *.key
181
+
182
+ # IDE
183
+ .idea/
184
+ .vscode/
185
+ *.swp
186
+
187
+ # OS
188
+ .DS_Store
189
+ Thumbs.db
190
+
191
+ # build
192
+ dist/
193
+ build/
194
+ *.pyc
195
+ ```
196
+
197
+ Check before the first commit. Once a secret lands in git history, you must rotate it — `git rm` doesn't remove history.
198
+
199
+ ## Commit discipline during review
200
+
201
+ If review asks for changes, commit the change, don't amend silently. The reviewer needs to see the delta.
202
+
203
+ ```
204
+ # ✅
205
+ git commit -m "fix(auth): handle empty role list per review"
206
+ git push
207
+
208
+ # ❌
209
+ git commit --amend --no-edit
210
+ git push --force-with-lease # reviewer's already-read diff is now lost
211
+ ```
212
+
213
+ Squash at merge time if you want a clean final history.
214
+
215
+ ## Anti-patterns
216
+
217
+ | Anti-pattern | Fix |
218
+ |---|---|
219
+ | `git add -A` with no check | Review with `git status` / `git diff --staged` first |
220
+ | Committing with `--no-verify` to skip hooks | Fix the hook failure; don't bypass |
221
+ | Force-push to a shared branch | Rebase your own branch only |
222
+ | 5000-line PR "final cleanup" | Split into: rename / format / real change |
223
+ | Empty PR description | Write what / why / how, even for small changes |
224
+ | "WIP" / "fix" commit messages that ship to main | Squash on merge if they're noise |
225
+ | Mixing a dependency bump with a feature | Separate PRs |
226
+ | Merging your own PR without review | Team agrees on when this is OK (e.g., docs only) |
@@ -0,0 +1,193 @@
1
+ # Error Strategy
2
+
3
+ When to raise, when to return a result, when to log, when to retry. Language-neutral decisions. For syntax (`try/except`, chaining, exception groups), see the language skill.
4
+
5
+ ## The question tree
6
+
7
+ ```
8
+ Is this an expected outcome?
9
+ yes → return a result (Option, Result, ValidationResult, nullable)
10
+ no ↓
11
+ Can this layer recover meaningfully?
12
+ yes → catch specifically, handle, don't re-raise
13
+ no ↓
14
+ Propagate. Let the edge (HTTP handler, CLI, worker) decide.
15
+ ```
16
+
17
+ One sentence: **expected = data, unexpected = exception, recoverable = catch narrowly, else propagate.**
18
+
19
+ ## Expected vs. exceptional
20
+
21
+ | Case | Treatment | Why |
22
+ |---|---|---|
23
+ | User input invalid | Return errors to the caller | User mistakes are expected; not exceptional |
24
+ | Item not found on lookup | Return `None` / `Option::None` | Missing is a normal outcome |
25
+ | DB connection dropped | Raise | Infrastructure failure; unexpected |
26
+ | Optimistic-lock version conflict | Raise a domain `ConflictError` | Expected under concurrency; caller decides retry |
27
+ | Programmer bug (null where not-null) | Raise (let it crash) | Hiding this creates a zombie system |
28
+
29
+ The dividing line: if it's part of the normal business flow, return data; if it breaks the flow, raise.
30
+
31
+ ## Catch only what you can handle
32
+
33
+ ```
34
+ # ❌ swallow-all
35
+ try:
36
+ do_thing()
37
+ except Exception:
38
+ pass # now it looks fine; tomorrow something real is lost
39
+
40
+ # ❌ swallow-and-log (better, still bad if you can't recover)
41
+ try:
42
+ do_thing()
43
+ except Exception as e:
44
+ log.error("oops: %s", e)
45
+
46
+ # ✅ catch the specific type you know how to handle
47
+ try:
48
+ return cache.get(key)
49
+ except CacheUnavailable:
50
+ return source.load(key) # real fallback
51
+
52
+ # ✅ the rest propagates
53
+ ```
54
+
55
+ Rule: if catching doesn't let you recover, re-raise. The higher layer knows what to do.
56
+
57
+ ## Exception hierarchy
58
+
59
+ Per service / module, one base, several subclasses for kinds of failure.
60
+
61
+ ```
62
+ AppError (base)
63
+ ├── ValidationError # 4xx-ish
64
+ ├── NotFoundError # 404
65
+ ├── ConflictError # 409
66
+ ├── AuthError # 401/403
67
+ └── ExternalServiceError # 502/503
68
+ ```
69
+
70
+ - The edge (HTTP handler) maps `NotFoundError` → 404, `AuthError` → 401, etc. One translation layer, not scattered throughout the app.
71
+ - Never leak framework exceptions (`SQLAlchemyIntegrityError`, `httpx.TimeoutException`) above the adapter. Translate at the adapter boundary into domain errors.
72
+
73
+ ## Preserve the cause
74
+
75
+ When re-raising as a different type, chain the original so the traceback still makes sense. Every language has a form of `raise new from original`. Use it.
76
+
77
+ ```
78
+ try:
79
+ response = http.get(url)
80
+ except NetworkTimeout as e:
81
+ raise ExternalServiceError("pricing down") from e
82
+ ```
83
+
84
+ Losing the cause makes incidents take twice as long to diagnose.
85
+
86
+ ## Result / Option types
87
+
88
+ Some languages (Rust, Kotlin, Swift, Haskell) prefer typed results over exceptions. The calculus is the same, syntax differs:
89
+
90
+ ```
91
+ # Result-style
92
+ fn find_user(id: UserId) -> Result<User, FindError>
93
+
94
+ match find_user(id):
95
+ Ok(user) -> ...
96
+ Err(FindError::NotFound) -> ...
97
+ Err(FindError::DbDown) -> ...
98
+ ```
99
+
100
+ In languages without sum types, the idiomatic equivalent is `Optional` for missing + exceptions for failure.
101
+
102
+ Don't fight the language. Use its native style.
103
+
104
+ ## Validation — return data, don't raise
105
+
106
+ User-facing validation almost always returns data, not an exception. You want to collect *all* errors, not stop at the first.
107
+
108
+ ```
109
+ result = validate(input) # returns { ok: false, errors: [...] }
110
+ if not result.ok:
111
+ return 422, result.errors
112
+ ```
113
+
114
+ Reserve exceptions for truly exceptional validation — "the request doesn't even parse as JSON", for example.
115
+
116
+ ## Retries — only at the right layer
117
+
118
+ Retries belong at the boundary that knows the call is retry-safe. Not inside a domain method, not sprinkled at every layer.
119
+
120
+ ```
121
+ # ✅
122
+ edge → retrier(timeout=5s, attempts=3) → adapter → network
123
+
124
+ # ❌
125
+ adapter retries, service retries, handler retries
126
+ # → user waits 5× the timeout; retry storms on the dependency
127
+ ```
128
+
129
+ See `backend/references/resilience.md` for retry patterns; this skill just says *where* the retry sits.
130
+
131
+ ## Logging exceptions
132
+
133
+ Log at the layer that decided not to re-raise. Logging at every level produces log storms and duplicate reports.
134
+
135
+ ```
136
+ # Bad — logged three times, same error
137
+ handler: log.error(e); raise
138
+ service: log.error(e); raise
139
+ adapter: log.error(e); raise
140
+
141
+ # Good — log once, at the boundary
142
+ handler: log.exception(...); return 500
143
+ service: (no log, propagate)
144
+ adapter: (no log, propagate)
145
+ ```
146
+
147
+ Include context in the log (request id, user id, inputs), never the secret values.
148
+
149
+ ## `finally` — for cleanup only
150
+
151
+ Use `finally` / equivalent for release-the-resource logic (close file, release lock, restore state). Never put business logic in `finally`; it runs even on errors.
152
+
153
+ ```
154
+ # ✅
155
+ try:
156
+ lock.acquire()
157
+ do_critical_section()
158
+ finally:
159
+ lock.release()
160
+
161
+ # ❌
162
+ try:
163
+ submit_order()
164
+ finally:
165
+ send_confirmation_email() # sends even if submit failed
166
+ ```
167
+
168
+ ## Crash early, crash clearly
169
+
170
+ When an invariant breaks (null where not-null, state impossibility, config missing at boot), fail loudly at the earliest point. Don't try to "keep going".
171
+
172
+ ```
173
+ # ✅ at boot
174
+ assert config.db_url, "DB_URL is required"
175
+
176
+ # ❌
177
+ db_url = config.db_url or "localhost" # silently masked; prod points at localhost
178
+ ```
179
+
180
+ A service that refuses to start is easier to fix than one that pretends to work.
181
+
182
+ ## Anti-patterns
183
+
184
+ | Anti-pattern | Fix |
185
+ |---|---|
186
+ | `try: ... except: pass` | Catch specific, handle or re-raise |
187
+ | Converting exception into `return None` | Caller loses the "why" — expose the error |
188
+ | Re-raising with a new message but no `from` | Traceback becomes useless |
189
+ | Logging and re-raising at every layer | Log once, at the decider |
190
+ | Catching `Exception` at a tight scope | You don't actually know what can happen there |
191
+ | Control flow via exceptions (`raise StopIteration` to break a loop) | Use explicit flow |
192
+ | Different error shapes for similar failures | Pick one base + specific subclasses |
193
+ | Leaking adapter exceptions to the domain | Translate at the boundary |
@@ -0,0 +1,185 @@
1
+ # Naming
2
+
3
+ Names are the cheapest form of documentation and the most-read part of a codebase.
4
+
5
+ ## The test
6
+
7
+ If you can name something clearly, you understand it. If every name you try is bad, the concept is bad.
8
+
9
+ ## Principles
10
+
11
+ 1. **Intention over implementation.** `active_users`, not `filtered_list`. `is_expired`, not `flag`.
12
+ 2. **Domain over mechanics.** `price_in_cents` over `value_int`. Names should read like the business, not the code.
13
+ 3. **Consistent vocabulary.** Pick one word per concept. `fetch / retrieve / load / get` → choose one, stick to it. Same for `user / account / customer` — they are different things; don't conflate.
14
+ 4. **Searchable.** A two-letter name is unsearchable. `i` in a three-line loop is fine; `i` as a module-level variable is not.
15
+ 5. **No encodings.** `strName`, `iCount`, `bFlag` (Hungarian notation) adds noise without signal — languages have types.
16
+ 6. **Avoid disinformation.** `user_list` that isn't actually a list is a trap. `account_map` that's a list of pairs is a trap.
17
+
18
+ ## Functions — verbs
19
+
20
+ Describe what they do, not how.
21
+
22
+ ```
23
+ # ✅
24
+ calculate_total(order)
25
+ send_welcome_email(user)
26
+ mark_as_read(notification)
27
+
28
+ # ❌
29
+ process(x) # process what?
30
+ handle_thing(data) # what thing? what handling?
31
+ do_work() # cool
32
+ ```
33
+
34
+ ## Booleans — predicates
35
+
36
+ `is_`, `has_`, `can_`, `should_` prefixes read naturally in conditionals.
37
+
38
+ ```
39
+ if is_active and has_permission(READ) and not is_expired: ...
40
+ ```
41
+
42
+ Avoid double negatives: `is_not_disabled` is a trap. Flip to `is_enabled`.
43
+
44
+ ## Collections — plural nouns
45
+
46
+ ```
47
+ users : list of User
48
+ user : single User
49
+ users_by_id : map from id to User
50
+ ```
51
+
52
+ Don't name the container in the name: `user_list` is redundant when the type is `list[User]`. Reserve `_list` / `_map` suffixes for when you have multiple collections of the same thing and need to disambiguate.
53
+
54
+ ## Numbers — include units
55
+
56
+ Units in the name prevent entire categories of bugs.
57
+
58
+ ```
59
+ # ✅
60
+ timeout_seconds = 30
61
+ price_cents = 2599
62
+ file_size_bytes = 1024
63
+ latency_ms = 87
64
+
65
+ # ❌
66
+ timeout = 30 # seconds? ms?
67
+ price = 25.99 # USD? EUR?
68
+ size = 1024 # bytes? KB? items?
69
+ ```
70
+
71
+ Mixed-unit bugs (feet vs meters) have crashed spacecraft. They crash billing systems too.
72
+
73
+ ## Variables — scope dictates length
74
+
75
+ Short scope → short name. Long scope → longer, more descriptive name.
76
+
77
+ ```
78
+ # OK in a three-line loop
79
+ for i, x in enumerate(items):
80
+ xs[i] = f(x)
81
+
82
+ # Not OK at module scope
83
+ x = load_all_records() # x? x of what?
84
+ ```
85
+
86
+ ## Functions returning booleans
87
+
88
+ Read as a predicate in an `if`.
89
+
90
+ ```
91
+ # ✅
92
+ if user.can_edit(document): ...
93
+
94
+ # ❌
95
+ if user.edit_permission(document): ... # reads weird
96
+ ```
97
+
98
+ ## Async / concurrent
99
+
100
+ Mark functions that do I/O or schedule work.
101
+
102
+ ```
103
+ async def fetch_user(id): ...
104
+ def fetch_user_async(id): ... # in langs without `async` marker
105
+ ```
106
+
107
+ Callers need to know they're awaiting something.
108
+
109
+ ## Types — nouns, in the singular
110
+
111
+ ```
112
+ class User: ... # a user
113
+ class UserRepository: ... # manages users
114
+ class UserNotFoundError: ... # specific, actionable
115
+ ```
116
+
117
+ ## Avoiding "magic"
118
+
119
+ Replace literal numbers and strings with named constants when the meaning is non-obvious.
120
+
121
+ ```
122
+ # ❌
123
+ if user.age >= 18: ...
124
+ sleep(86400)
125
+
126
+ # ✅
127
+ LEGAL_ADULT_AGE = 18
128
+ ONE_DAY_SECONDS = 86400
129
+
130
+ if user.age >= LEGAL_ADULT_AGE: ...
131
+ sleep(ONE_DAY_SECONDS)
132
+ ```
133
+
134
+ Exception: one-off small numbers that read naturally — `x * 2`, `str[0:10]` — don't need constants.
135
+
136
+ ## Events / notifications — past tense, subject-first
137
+
138
+ ```
139
+ OrderPlaced, OrderShipped, UserRegistered, PaymentFailed
140
+ ```
141
+
142
+ Not `PlaceOrder` (that's a command), not `OrderIsShipped` (noise). Events describe things that happened; commands describe things requested.
143
+
144
+ ## Files, modules, packages
145
+
146
+ - **Lowercase, hyphen- or underscore-separated**, depending on the language.
147
+ - Name by what's inside, not by mechanics. `user_service.py` > `helpers.py`.
148
+ - Avoid kitchen-sink files: `utils`, `common`, `misc`, `helpers` — they become dumping grounds.
149
+
150
+ ## Endpoints, routes
151
+
152
+ - Plural resource nouns: `/users`, `/orders/{id}/items`.
153
+ - Kebab-case, not snake_case or camelCase in paths: `/user-sessions`, not `/userSessions`.
154
+ - No verbs when CRUD fits: `POST /orders`, not `POST /createOrder`.
155
+
156
+ ## Feature flags
157
+
158
+ Describe the capability, not the date, not the ticket.
159
+
160
+ ```
161
+ # ✅
162
+ checkout_v2_enabled
163
+ redis_session_store_enabled
164
+
165
+ # ❌
166
+ temp_flag_jan_2026
167
+ jira_1234_rollout
168
+ ```
169
+
170
+ Flags outlive the ticket and the engineer who wrote it. Name for what the flag *does*.
171
+
172
+ ## Renaming is cheap; bad names compound
173
+
174
+ IDEs rename safely across projects. If a name is wrong, fix it now. Six months of new code built on top of a confused name will be six months of confusion.
175
+
176
+ ## Anti-patterns
177
+
178
+ | Anti-pattern | Fix |
179
+ |---|---|
180
+ | `data`, `info`, `item`, `manager`, `handler` | Say what it actually represents |
181
+ | `foo_list` when it's a set | Use `foo_set` or just `foo` + typed container |
182
+ | Ambiguous pairs: `user` / `account` / `customer` interchangeably | Define the distinction; use the right one |
183
+ | Mixed naming conventions in one file | Follow the language's convention; don't invent your own |
184
+ | Abbreviations that aren't standard | `usr`, `mngr`, `proc` — spell them out |
185
+ | Numbered copies: `process1`, `process2`, `processFinal`, `processFinalV2` | Delete the old ones; one canonical name |