@martinffx/atelier 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.
Files changed (130) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/LICENSE +21 -0
  3. package/README.md +285 -0
  4. package/agents/architect.md +38 -0
  5. package/agents/oracle.md +40 -0
  6. package/agents/recon.md +39 -0
  7. package/dist/atelier.js +33082 -0
  8. package/package.json +44 -0
  9. package/skills/code-conventional-commit/SKILL.md +134 -0
  10. package/skills/code-conventional-commit/evals/evals.json +23 -0
  11. package/skills/code-conventional-commit/workspace/iteration-1/eval-0/with_skill/outputs/commit-message.txt +4 -0
  12. package/skills/code-conventional-commit/workspace/iteration-1/eval-0/with_skill/outputs/eval_metadata.json +6 -0
  13. package/skills/code-conventional-commit/workspace/iteration-1/eval-1/with_skill/outputs/eval_metadata.json +6 -0
  14. package/skills/code-conventional-commit/workspace/iteration-1/eval-1/with_skill/outputs/validation-result.txt +16 -0
  15. package/skills/code-conventional-commit/workspace/iteration-1/eval-2/with_skill/outputs/commit-message.txt +8 -0
  16. package/skills/code-conventional-commit/workspace/iteration-1/eval-2/with_skill/outputs/eval_metadata.json +6 -0
  17. package/skills/code-conventional-commit/workspace/iteration-1/report.html +1325 -0
  18. package/skills/code-debug/SKILL.md +271 -0
  19. package/skills/code-debug/references/language-tools.md +139 -0
  20. package/skills/code-debug/references/techniques.md +117 -0
  21. package/skills/code-docs/SKILL.md +171 -0
  22. package/skills/code-docs/references/api-docs.md +116 -0
  23. package/skills/code-docs/references/changelog.md +64 -0
  24. package/skills/code-docs/references/readme-structure.md +94 -0
  25. package/skills/code-git-worktrees/SKILL.md +218 -0
  26. package/skills/code-perf/SKILL.md +163 -0
  27. package/skills/code-perf/references/common-bottlenecks.md +124 -0
  28. package/skills/code-perf/references/optimization-patterns.md +167 -0
  29. package/skills/code-perf/references/profiling-tools.md +126 -0
  30. package/skills/code-review/SKILL.md +78 -0
  31. package/skills/code-review/references/output.md +200 -0
  32. package/skills/code-review/references/reviewers.md +719 -0
  33. package/skills/code-review/references/rq.md +357 -0
  34. package/skills/code-review/references/rs.md +30 -0
  35. package/skills/code-security/SKILL.md +183 -0
  36. package/skills/code-security/references/owasp-top-10.md +129 -0
  37. package/skills/code-security/references/security-tools.md +102 -0
  38. package/skills/code-security/references/vulnerability-patterns.md +129 -0
  39. package/skills/code-stacked-commit/SKILL.md +155 -0
  40. package/skills/code-subagents/SKILL.md +184 -0
  41. package/skills/code-subagents/references/code-quality-reviewer-prompt.md +48 -0
  42. package/skills/code-subagents/references/implementor-prompt.md +79 -0
  43. package/skills/code-subagents/references/spec-reviewer-prompt.md +62 -0
  44. package/skills/oracle-architect/SKILL.md +246 -0
  45. package/skills/oracle-architect/references/api-design.md +345 -0
  46. package/skills/oracle-architect/references/data-modeling.md +318 -0
  47. package/skills/oracle-architect/references/ddd-patterns.md +247 -0
  48. package/skills/oracle-challenge/SKILL.md +126 -0
  49. package/skills/oracle-testing/SKILL.md +237 -0
  50. package/skills/oracle-testing/references/anti-patterns.md +618 -0
  51. package/skills/oracle-testing/references/boundaries.md +579 -0
  52. package/skills/oracle-testing/references/core-testing.md +691 -0
  53. package/skills/oracle-testing/references/edge-testing.md +794 -0
  54. package/skills/oracle-testing/references/stub-driven-tdd.md +322 -0
  55. package/skills/oracle-thinkdeep/SKILL.md +164 -0
  56. package/skills/python-architecture/SKILL.md +148 -0
  57. package/skills/python-architecture/references/data-modeling.md +558 -0
  58. package/skills/python-architecture/references/ddd.md +569 -0
  59. package/skills/python-architecture/references/functional-core.md +337 -0
  60. package/skills/python-build-tools/SKILL.md +153 -0
  61. package/skills/python-build-tools/references/basedpyright.md +321 -0
  62. package/skills/python-build-tools/references/ruff.md +225 -0
  63. package/skills/python-build-tools/references/uv.md +257 -0
  64. package/skills/python-fastapi/SKILL.md +184 -0
  65. package/skills/python-fastapi/references/api-design.md +563 -0
  66. package/skills/python-fastapi/references/dependencies.md +479 -0
  67. package/skills/python-fastapi/references/middleware.md +475 -0
  68. package/skills/python-fastapi/references/validation.md +497 -0
  69. package/skills/python-modern-python/SKILL.md +201 -0
  70. package/skills/python-modern-python/references/async.md +76 -0
  71. package/skills/python-modern-python/references/pattern-matching.md +89 -0
  72. package/skills/python-modern-python/references/typing.md +96 -0
  73. package/skills/python-monorepo/SKILL.md +146 -0
  74. package/skills/python-monorepo/references/docker.md +515 -0
  75. package/skills/python-monorepo/references/namespace-packages.md +426 -0
  76. package/skills/python-monorepo/references/workspace-config.md +341 -0
  77. package/skills/python-sqlalchemy/SKILL.md +119 -0
  78. package/skills/python-sqlalchemy/references/async.md +51 -0
  79. package/skills/python-sqlalchemy/references/models.md +67 -0
  80. package/skills/python-sqlalchemy/references/queries.md +57 -0
  81. package/skills/python-temporal/SKILL.md +128 -0
  82. package/skills/python-temporal/references/patterns.md +87 -0
  83. package/skills/python-temporal/references/testing.md +54 -0
  84. package/skills/python-testing/SKILL.md +161 -0
  85. package/skills/python-testing/references/boundaries.md +449 -0
  86. package/skills/python-testing/references/mocking.md +449 -0
  87. package/skills/python-testing/references/pytest.md +524 -0
  88. package/skills/spec-brainstorm/SKILL.md +325 -0
  89. package/skills/spec-finish/SKILL.md +159 -0
  90. package/skills/spec-implement/SKILL.md +296 -0
  91. package/skills/spec-orchestrator/SKILL.md +156 -0
  92. package/skills/spec-plan/SKILL.md +248 -0
  93. package/skills/typescript-api-design/SKILL.md +396 -0
  94. package/skills/typescript-api-design/references/error-responses.md +469 -0
  95. package/skills/typescript-build-tools/SKILL.md +447 -0
  96. package/skills/typescript-build-tools/references/biome.md +436 -0
  97. package/skills/typescript-build-tools/references/bun.md +432 -0
  98. package/skills/typescript-build-tools/references/turborepo.md +426 -0
  99. package/skills/typescript-build-tools/references/vitest.md +518 -0
  100. package/skills/typescript-drizzle-orm/SKILL.md +398 -0
  101. package/skills/typescript-drizzle-orm/references/cloudflare.md +392 -0
  102. package/skills/typescript-drizzle-orm/references/entity-pattern.md +412 -0
  103. package/skills/typescript-drizzle-orm/references/postgresql.md +191 -0
  104. package/skills/typescript-drizzle-orm/references/repository-pattern.md +479 -0
  105. package/skills/typescript-drizzle-orm/references/sqlite.md +291 -0
  106. package/skills/typescript-dynamodb-toolbox/SKILL.md +370 -0
  107. package/skills/typescript-dynamodb-toolbox/references/entity-layer.md +574 -0
  108. package/skills/typescript-dynamodb-toolbox/references/error-handling.md +495 -0
  109. package/skills/typescript-dynamodb-toolbox/references/modeling.md +332 -0
  110. package/skills/typescript-dynamodb-toolbox/references/repository-layer.md +631 -0
  111. package/skills/typescript-dynamodb-toolbox/references/testing.md +718 -0
  112. package/skills/typescript-dynamodb-toolbox/references/transactions.md +423 -0
  113. package/skills/typescript-effect-ts/SKILL.md +341 -0
  114. package/skills/typescript-effect-ts/references/core.md +412 -0
  115. package/skills/typescript-effect-ts/references/error-handling.md +468 -0
  116. package/skills/typescript-effect-ts/references/resources.md +476 -0
  117. package/skills/typescript-effect-ts/references/schema.md +536 -0
  118. package/skills/typescript-effect-ts/references/services.md +461 -0
  119. package/skills/typescript-fastify/SKILL.md +405 -0
  120. package/skills/typescript-fastify/references/plugins.md +329 -0
  121. package/skills/typescript-fastify/references/typeid.md +315 -0
  122. package/skills/typescript-functional-patterns/SKILL.md +402 -0
  123. package/skills/typescript-functional-patterns/references/adts.md +497 -0
  124. package/skills/typescript-functional-patterns/references/branded-types.md +542 -0
  125. package/skills/typescript-functional-patterns/references/migration-guide.md +695 -0
  126. package/skills/typescript-functional-patterns/references/option-result.md +585 -0
  127. package/skills/typescript-testing/SKILL.md +353 -0
  128. package/skills/typescript-testing/references/mocking.md +675 -0
  129. package/skills/typescript-testing/references/msw.md +852 -0
  130. package/skills/typescript-testing/references/snapshot-testing.md +774 -0
@@ -0,0 +1,423 @@
1
+ # Transactions and Atomic Operations
2
+
3
+ Patterns for multi-entity consistency and atomic counters in DynamoDB.
4
+
5
+ ## Transaction Patterns
6
+
7
+ ### Basic: Create with Parent Validation
8
+
9
+ Ensure parent entity exists before creating child entity.
10
+
11
+ **Use Case:** Create an issue only if the repository exists
12
+
13
+ ```typescript
14
+ import { PutTransaction } from 'dynamodb-toolbox/entity/actions/transactPut'
15
+ import { ConditionCheck } from 'dynamodb-toolbox'
16
+ import { execute } from 'dynamodb-toolbox/entity/actions/transactWrite'
17
+
18
+ async createIssue(issue: IssueEntity): Promise<IssueEntity> {
19
+ try {
20
+ // 1. Build transaction to put issue with duplicate check
21
+ const putIssueTransaction = this.issueRecord
22
+ .build(PutTransaction)
23
+ .item(issue.toRecord())
24
+ .options({
25
+ condition: { attr: "PK", exists: false } // Prevent duplicates
26
+ })
27
+
28
+ // 2. Build condition check to verify repository exists
29
+ const repoCheckTransaction = this.repoRecord
30
+ .build(ConditionCheck)
31
+ .key({
32
+ owner: issue.owner,
33
+ repo_name: issue.repoName,
34
+ })
35
+ .condition({ attr: "PK", exists: true }) // Repo must exist
36
+
37
+ // 3. Execute both operations in transaction
38
+ await execute(putIssueTransaction, repoCheckTransaction)
39
+
40
+ // 4. Fetch the created item (transaction doesn't return it)
41
+ const created = await this.get(issue.owner, issue.repoName, issue.issueNumber)
42
+ if (!created) {
43
+ throw new Error("Failed to retrieve created issue")
44
+ }
45
+
46
+ return created
47
+ } catch (error: unknown) {
48
+ // See error-handling.md for error mapping
49
+ handleTransactionError(error, {
50
+ entityType: "IssueEntity",
51
+ entityKey: issue.getEntityKey(),
52
+ parentEntityType: "RepositoryEntity",
53
+ parentEntityKey: issue.getParentEntityKey(),
54
+ })
55
+ }
56
+ }
57
+ ```
58
+
59
+ **Key Points:**
60
+ - `PutTransaction` creates the new entity
61
+ - `ConditionCheck` validates the parent exists
62
+ - Both succeed or both fail (atomic)
63
+ - Must fetch created item separately
64
+
65
+ ### Advanced: Three-Way Validation
66
+
67
+ Validate multiple parent entities before creating relationship.
68
+
69
+ **Use Case:** Create a star only if both user and repository exist
70
+
71
+ ```typescript
72
+ async createStar(star: StarEntity): Promise<StarEntity> {
73
+ try {
74
+ // 1. Build transaction to put star
75
+ const putStarTransaction = this.starRecord
76
+ .build(PutTransaction)
77
+ .item(star.toRecord())
78
+ .options({
79
+ condition: { attr: "PK", exists: false }
80
+ })
81
+
82
+ // 2. Build condition check to verify user exists
83
+ const userCheck = this.userRecord
84
+ .build(ConditionCheck)
85
+ .key({ username: star.username })
86
+ .condition({ attr: "PK", exists: true })
87
+
88
+ // 3. Build condition check to verify repo exists
89
+ const repoCheck = this.repoRecord
90
+ .build(ConditionCheck)
91
+ .key({
92
+ owner: star.repoOwner,
93
+ repo_name: star.repoName,
94
+ })
95
+ .condition({ attr: "PK", exists: true })
96
+
97
+ // 4. Execute all three operations
98
+ await execute(putStarTransaction, userCheck, repoCheck)
99
+
100
+ // Fetch and return created star
101
+ const created = await this.get(star.username, star.repoOwner, star.repoName)
102
+ if (!created) {
103
+ throw new Error("Failed to retrieve created star")
104
+ }
105
+
106
+ return created
107
+ } catch (error: unknown) {
108
+ handleThreeWayTransactionError(error, star)
109
+ }
110
+ }
111
+ ```
112
+
113
+ **DynamoDB Transaction Limit:** Maximum 100 operations per transaction.
114
+
115
+ ## Atomic Counter Pattern
116
+
117
+ ### Use Case: Sequential Issue/PR Numbering
118
+
119
+ Each repository needs its own issue counter that increments atomically.
120
+
121
+ ### Counter Schema
122
+
123
+ ```typescript
124
+ import { Entity } from 'dynamodb-toolbox/entity'
125
+ import { item } from 'dynamodb-toolbox/schema/item'
126
+ import { string, number } from 'dynamodb-toolbox/schema'
127
+
128
+ const CounterRecord = new Entity({
129
+ name: "Counter",
130
+ table: AppTable,
131
+ schema: item({
132
+ org_id: string().required().key(),
133
+ repo_id: string().required().key(),
134
+ current_value: number().required().default(0),
135
+ }).and(_schema => ({
136
+ PK: string().key().link<typeof _schema>(
137
+ ({ org_id, repo_id }) => `COUNTER#${org_id}#${repo_id}`
138
+ ),
139
+ SK: string().key().default("METADATA"),
140
+ })),
141
+ })
142
+ ```
143
+
144
+ **Key Design:**
145
+ - PK: `COUNTER#{org}#{repo}` - One counter per repository
146
+ - SK: `METADATA` - Static sort key
147
+ - `current_value`: Defaults to 0, increments atomically
148
+
149
+ ### Atomic Increment Implementation
150
+
151
+ ```typescript
152
+ import { UpdateItemCommand, $add } from 'dynamodb-toolbox'
153
+
154
+ class CounterRepository {
155
+ constructor(private entity: CounterRecord) {}
156
+
157
+ async incrementAndGet(orgId: string, repoId: string): Promise<number> {
158
+ const result = await this.entity
159
+ .build(UpdateItemCommand)
160
+ .item({
161
+ org_id: orgId,
162
+ repo_id: repoId,
163
+ current_value: $add(1), // Atomic increment
164
+ })
165
+ .options({
166
+ returnValues: "ALL_NEW" // Return updated value
167
+ })
168
+ .send()
169
+
170
+ if (!result.Attributes?.current_value) {
171
+ throw new Error(
172
+ "Failed to increment counter: invalid response from DynamoDB"
173
+ )
174
+ }
175
+
176
+ return result.Attributes.current_value
177
+ }
178
+ }
179
+ ```
180
+
181
+ **How `$add(1)` Works:**
182
+ - If counter doesn't exist: Creates with `current_value = 1`
183
+ - If counter exists: Increments `current_value` by 1
184
+ - Operation is atomic - no race conditions
185
+ - Returns new value in single round-trip
186
+
187
+ ### Usage in Issue Creation
188
+
189
+ ```typescript
190
+ async createIssue(issue: IssueEntity): Promise<IssueEntity> {
191
+ // 1. Get next issue number atomically
192
+ const issueNumber = await this.counterRepo.incrementAndGet(
193
+ issue.owner,
194
+ issue.repoName
195
+ )
196
+
197
+ // 2. Create issue with assigned number
198
+ const issueWithNumber = new IssueEntity({
199
+ ...issue,
200
+ issueNumber, // Use atomically generated number
201
+ })
202
+
203
+ // 3. Use transaction to ensure repo exists
204
+ try {
205
+ const putIssueTransaction = this.issueRecord
206
+ .build(PutTransaction)
207
+ .item(issueWithNumber.toRecord())
208
+ .options({
209
+ condition: { attr: "PK", exists: false }
210
+ })
211
+
212
+ const repoCheckTransaction = this.repoRecord
213
+ .build(ConditionCheck)
214
+ .key({ owner: issue.owner, repo_name: issue.repoName })
215
+ .condition({ attr: "PK", exists: true })
216
+
217
+ await execute(putIssueTransaction, repoCheckTransaction)
218
+
219
+ return issueWithNumber
220
+ } catch (error: unknown) {
221
+ // Note: Counter was incremented but issue creation failed
222
+ // This creates a gap in issue numbers, which is acceptable
223
+ // (GitHub also has gaps in issue numbers)
224
+ handleTransactionError(error, /* ... */)
225
+ }
226
+ }
227
+ ```
228
+
229
+ **Gap Handling:**
230
+ If transaction fails after counter increment, you get a gap in issue numbers. This is acceptable:
231
+ - GitHub has gaps in issue/PR numbers
232
+ - Alternative: Pre-allocate batches (more complex)
233
+ - Alternative: Use UUIDs instead (loses sequential ordering)
234
+
235
+ ## Concurrent Issue Creation
236
+
237
+ The atomic counter pattern handles concurrent creation correctly:
238
+
239
+ ```typescript
240
+ // Five concurrent requests to create issues in same repo
241
+ const issues = Array.from({ length: 5 }, (_, i) =>
242
+ IssueEntity.fromRequest({
243
+ owner: "alice",
244
+ repo_name: "my-repo",
245
+ title: `Concurrent Issue ${i}`,
246
+ author: "testuser",
247
+ })
248
+ )
249
+
250
+ // All five execute in parallel
251
+ const created = await Promise.all(
252
+ issues.map(issue => issueRepo.create(issue))
253
+ )
254
+
255
+ // Result: Five unique sequential numbers
256
+ // e.g., [1, 2, 3, 4, 5] (order may vary)
257
+ const numbers = created.map(i => i.issueNumber)
258
+ console.log(new Set(numbers).size === 5) // true - all unique
259
+ ```
260
+
261
+ **DynamoDB Guarantees:**
262
+ - Each `$add(1)` operation is atomic
263
+ - Concurrent requests get unique sequential numbers
264
+ - No duplicates or race conditions
265
+
266
+ ## Other Atomic Operations
267
+
268
+ ### $subtract
269
+
270
+ ```typescript
271
+ current_value: $subtract(1) // Decrement by 1
272
+ ```
273
+
274
+ ### $append
275
+
276
+ ```typescript
277
+ import { $append } from 'dynamodb-toolbox'
278
+
279
+ history: $append(['2024-01-01: Status changed to closed'])
280
+ ```
281
+
282
+ Appends to list attribute. Creates list with value if attribute doesn't exist.
283
+
284
+ ### $remove
285
+
286
+ ```typescript
287
+ import { $remove } from 'dynamodb-toolbox'
288
+
289
+ deprecated_field: $remove() // Remove attribute entirely
290
+ ```
291
+
292
+ ## Transaction Error Handling
293
+
294
+ See [error-handling.md](error-handling.md) for detailed error handling patterns, including:
295
+
296
+ - Parsing `TransactionCanceledException` CancellationReasons
297
+ - Mapping to domain errors (DuplicateEntityError, EntityNotFoundError)
298
+ - Distinguishing which operation failed in multi-entity transactions
299
+
300
+ ## Transaction Best Practices
301
+
302
+ ### ✓ Do
303
+
304
+ - Use transactions for multi-entity consistency
305
+ - Use atomic counters for sequential numbering
306
+ - Keep transactions small (< 10 operations ideal)
307
+ - Accept gaps in sequential numbers
308
+ - Use `returnValues: "ALL_NEW"` to get updated values
309
+
310
+ ### ✗ Don't
311
+
312
+ - Don't use transactions when single PutItem with condition suffices
313
+ - Don't create transactions with 50+ operations (performance cost)
314
+ - Don't assume transaction returns created item (must fetch separately)
315
+ - Don't retry blindly on transaction failure (may have partial state)
316
+
317
+ ## Performance Considerations
318
+
319
+ **Transactions:**
320
+ - Cost: 2x write capacity units (prepares + commits)
321
+ - Latency: Slightly higher than single operations (~10-20ms overhead)
322
+ - Limit: 100 operations per transaction, 4MB total size
323
+
324
+ **Atomic Counters:**
325
+ - Cost: 1 WCU per increment (standard write)
326
+ - Latency: Sub-10ms typical
327
+ - Throughput: 1000 increments/second per counter partition
328
+
329
+ **Hot Counter Mitigation:**
330
+ If a single counter exceeds 1000 writes/second:
331
+ ```typescript
332
+ // Shard the counter across multiple partitions
333
+ const shard = hashCode(orgId + repoId) % 10 // 10 shards
334
+ const counterId = `COUNTER#${orgId}#${repoId}#${shard}`
335
+
336
+ // Query all shards and sum for total count
337
+ ```
338
+
339
+ ## Example: Complete Issue Creation Flow
340
+
341
+ ```typescript
342
+ class IssueRepository {
343
+ constructor(
344
+ private table: Table,
345
+ private issueRecord: IssueRecord,
346
+ private counterRecord: CounterRecord,
347
+ private repoRecord: RepoRecord
348
+ ) {}
349
+
350
+ async create(issue: IssueEntity): Promise<IssueEntity> {
351
+ // Step 1: Get next issue number (atomic)
352
+ const issueNumber = await this.incrementCounter(
353
+ issue.owner,
354
+ issue.repoName
355
+ )
356
+
357
+ // Step 2: Create issue with number
358
+ const issueWithNumber = new IssueEntity({
359
+ ...issue,
360
+ issueNumber,
361
+ })
362
+
363
+ try {
364
+ // Step 3: Transaction - create issue + verify repo exists
365
+ const putIssue = this.issueRecord
366
+ .build(PutTransaction)
367
+ .item(issueWithNumber.toRecord())
368
+ .options({ condition: { attr: "PK", exists: false } })
369
+
370
+ const checkRepo = this.repoRecord
371
+ .build(ConditionCheck)
372
+ .key({ owner: issue.owner, repo_name: issue.repoName })
373
+ .condition({ attr: "PK", exists: true })
374
+
375
+ await execute(putIssue, checkRepo)
376
+
377
+ // Step 4: Fetch created issue
378
+ const created = await this.get(
379
+ issue.owner,
380
+ issue.repoName,
381
+ issueNumber
382
+ )
383
+
384
+ if (!created) {
385
+ throw new Error("Failed to retrieve created issue")
386
+ }
387
+
388
+ return created
389
+ } catch (error: unknown) {
390
+ handleTransactionError(error, {
391
+ entityType: "IssueEntity",
392
+ entityKey: issueWithNumber.getEntityKey(),
393
+ parentEntityType: "RepositoryEntity",
394
+ parentEntityKey: issue.getParentEntityKey(),
395
+ operationName: "issue",
396
+ })
397
+ }
398
+ }
399
+
400
+ private async incrementCounter(
401
+ owner: string,
402
+ repoName: string
403
+ ): Promise<number> {
404
+ const result = await this.counterRecord
405
+ .build(UpdateItemCommand)
406
+ .item({
407
+ org_id: owner,
408
+ repo_id: repoName,
409
+ current_value: $add(1),
410
+ })
411
+ .options({ returnValues: "ALL_NEW" })
412
+ .send()
413
+
414
+ return result.Attributes!.current_value
415
+ }
416
+ }
417
+ ```
418
+
419
+ This pattern ensures:
420
+ - ✓ Unique sequential issue numbers (atomic counter)
421
+ - ✓ No orphaned issues (repo existence check)
422
+ - ✓ No duplicate issues (PK exists check)
423
+ - ✓ Consistent state (transaction)