@mmerterden/multi-agent-pipeline 20.36.0 → 20.37.1

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 (222) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/docs/facts.json +2 -2
  3. package/docs/features.md +1 -1
  4. package/docs/recovery-guide.md +1 -1
  5. package/install/catalog-history.json +1 -1
  6. package/manifest.json +225 -148
  7. package/package.json +1 -1
  8. package/pipeline/commands/multi-agent/SKILL.md +33 -154
  9. package/pipeline/commands/multi-agent/analysis/SKILL.md +14 -68
  10. package/pipeline/commands/multi-agent/analysis-jira/SKILL.md +5 -8
  11. package/pipeline/commands/multi-agent/autopilot-on/SKILL.md +38 -109
  12. package/pipeline/commands/multi-agent/channels/SKILL.md +38 -167
  13. package/pipeline/commands/multi-agent/complaint-analysis/SKILL.md +37 -144
  14. package/pipeline/commands/multi-agent/design-check/SKILL.md +27 -131
  15. package/pipeline/commands/multi-agent/garbage-collect/SKILL.md +22 -48
  16. package/pipeline/commands/multi-agent/ios-coding-standard/SKILL.md +37 -180
  17. package/pipeline/commands/multi-agent/jira/SKILL.md +16 -46
  18. package/pipeline/commands/multi-agent/manual-test/SKILL.md +12 -7
  19. package/pipeline/commands/multi-agent/refactor/SKILL.md +23 -184
  20. package/pipeline/commands/multi-agent/resume/SKILL.md +30 -136
  21. package/pipeline/commands/multi-agent/review/SKILL.md +24 -199
  22. package/pipeline/commands/multi-agent/setup/SKILL.md +25 -181
  23. package/pipeline/commands/multi-agent/stack/SKILL.md +9 -53
  24. package/pipeline/commands/multi-agent/status/SKILL.md +13 -2
  25. package/pipeline/commands/multi-agent/store-ready/SKILL.md +27 -226
  26. package/pipeline/commands/multi-agent/sync/SKILL.md +35 -164
  27. package/pipeline/commands/multi-agent/test-gen/SKILL.md +2 -4
  28. package/pipeline/commands/multi-agent/update/SKILL.md +18 -60
  29. package/pipeline/contract/CHANGELOG.md +9 -0
  30. package/pipeline/contract/fixtures/error-invalid-request.json +1 -1
  31. package/pipeline/contract/fixtures/error-unauthorized.json +1 -1
  32. package/pipeline/contract/fixtures/error-unsigned.json +1 -1
  33. package/pipeline/contract/fixtures/runs-awaiting-question.json +1 -1
  34. package/pipeline/contract/fixtures/runs-empty.json +1 -1
  35. package/pipeline/contract/fixtures/runs-failed.json +1 -1
  36. package/pipeline/contract/fixtures/runs-pr-opened-redacted.json +1 -1
  37. package/pipeline/contract/fixtures/runs-pr-opened.json +1 -1
  38. package/pipeline/contract/fixtures/runs-running.json +1 -1
  39. package/pipeline/contract/manifest.json +5 -5
  40. package/pipeline/contract/types/index.d.ts +3 -2
  41. package/pipeline/multi-agent-refs/audit-guide.md +20 -0
  42. package/pipeline/multi-agent-refs/autopilot-on/details.md +148 -0
  43. package/pipeline/multi-agent-refs/channels/generator.md +14 -0
  44. package/pipeline/multi-agent-refs/channels/preview.md +13 -0
  45. package/pipeline/multi-agent-refs/channels/reference.md +46 -0
  46. package/pipeline/multi-agent-refs/complaint-analysis/phases.md +159 -0
  47. package/pipeline/multi-agent-refs/design-check/export.md +1 -0
  48. package/pipeline/multi-agent-refs/design-check/init-inventory.md +1 -1
  49. package/pipeline/multi-agent-refs/design-check/scope-and-ledger.md +64 -0
  50. package/pipeline/multi-agent-refs/features/analysis-command-notes.md +53 -0
  51. package/pipeline/multi-agent-refs/features/commit-multi-repo.md +63 -0
  52. package/pipeline/multi-agent-refs/features/dev-multi-repo.md +51 -0
  53. package/pipeline/multi-agent-refs/features/test-gap.md +40 -0
  54. package/pipeline/multi-agent-refs/features/worktree-finalize.md +1 -1
  55. package/pipeline/multi-agent-refs/garbage-collect/phases.md +60 -0
  56. package/pipeline/multi-agent-refs/ios-coding-standard/plan.md +71 -0
  57. package/pipeline/multi-agent-refs/ios-coding-standard/resolve-and-scan.md +135 -0
  58. package/pipeline/multi-agent-refs/jira/picker-detail.md +71 -0
  59. package/pipeline/multi-agent-refs/orchestrator/copilot-pipeline.md +88 -0
  60. package/pipeline/multi-agent-refs/orchestrator/dispatcher.md +96 -1
  61. package/pipeline/multi-agent-refs/orchestrator/phase-3-user-test.md +12 -9
  62. package/pipeline/multi-agent-refs/phases/phase-1-plan.md +4 -4
  63. package/pipeline/multi-agent-refs/phases/phase-2-dev.md +8 -47
  64. package/pipeline/multi-agent-refs/phases/phase-3-review.md +21 -65
  65. package/pipeline/multi-agent-refs/phases/phase-4-commit.md +9 -59
  66. package/pipeline/multi-agent-refs/phases/phase-5-report.md +0 -31
  67. package/pipeline/multi-agent-refs/refactor/sweep.md +190 -0
  68. package/pipeline/multi-agent-refs/resume/details.md +115 -2
  69. package/pipeline/multi-agent-refs/review/inputs.md +33 -1
  70. package/pipeline/multi-agent-refs/review/module-guides.md +9 -1
  71. package/pipeline/multi-agent-refs/review/panel.md +75 -0
  72. package/pipeline/multi-agent-refs/review/valven.md +5 -1
  73. package/pipeline/multi-agent-refs/rules.md +1 -1
  74. package/pipeline/multi-agent-refs/setup/hooks-and-plugins.md +4 -0
  75. package/pipeline/multi-agent-refs/setup/token-save-flow.md +13 -0
  76. package/pipeline/multi-agent-refs/stack/background.md +72 -0
  77. package/pipeline/multi-agent-refs/store-ready/gates.md +139 -0
  78. package/pipeline/multi-agent-refs/store-ready/report.md +40 -2
  79. package/pipeline/multi-agent-refs/sync/office.md +10 -0
  80. package/pipeline/multi-agent-refs/sync/overview.md +61 -0
  81. package/pipeline/multi-agent-refs/sync/repo.md +91 -0
  82. package/pipeline/multi-agent-refs/update/details.md +83 -0
  83. package/pipeline/office/index.html +6 -3
  84. package/pipeline/office/toolkit.json +1 -1
  85. package/pipeline/rules/figma-pipeline.md +6 -3
  86. package/pipeline/schemas/prefs.schema.json +1 -1
  87. package/pipeline/schemas/runs-index.schema.json +2 -1
  88. package/pipeline/scripts/_memory-policy.mjs +27 -5
  89. package/pipeline/scripts/checkpoint-fold.sh +59 -0
  90. package/pipeline/scripts/contract-server.mjs +1 -1
  91. package/pipeline/scripts/gate-ledger.mjs +1 -1
  92. package/pipeline/scripts/office-control.mjs +41 -15
  93. package/pipeline/scripts/offload-ref.sh +1 -1
  94. package/pipeline/scripts/rework-rulings.mjs +5 -3
  95. package/pipeline/scripts/runs-index.mjs +34 -7
  96. package/pipeline/scripts/scope-check-gate.mjs +15 -2
  97. package/pipeline/scripts/status-overview.mjs +401 -0
  98. package/pipeline/scripts/user-test-handoff.sh +203 -0
  99. package/pipeline/scripts/validate-planning.mjs +5 -3
  100. package/pipeline/scripts/validate-triage.mjs +2 -1
  101. package/pipeline/scripts/write-state.mjs +13 -4
  102. package/pipeline/skills/.skill-manifest.json +64 -64
  103. package/pipeline/skills/shared/core/apple-archive-compliance/SKILL.md +7 -70
  104. package/pipeline/skills/shared/core/apple-archive-compliance/references/examples.md +67 -0
  105. package/pipeline/skills/shared/core/apple-archive-compliance/references/version-history.md +8 -0
  106. package/pipeline/skills/shared/core/google-play-compliance/SKILL.md +10 -72
  107. package/pipeline/skills/shared/core/google-play-compliance/references/examples.md +69 -0
  108. package/pipeline/skills/shared/core/google-play-compliance/references/notes.md +7 -0
  109. package/pipeline/skills/shared/core/google-play-compliance/references/version-history.md +5 -0
  110. package/pipeline/skills/shared/core/multi-agent/SKILL.md +39 -262
  111. package/pipeline/skills/shared/core/multi-agent-autopilot-on/SKILL.md +35 -73
  112. package/pipeline/skills/shared/core/multi-agent-channels/SKILL.md +37 -337
  113. package/pipeline/skills/shared/core/multi-agent-design-check/SKILL.md +27 -122
  114. package/pipeline/skills/shared/core/multi-agent-help/SKILL.md +8 -340
  115. package/pipeline/skills/shared/core/multi-agent-ios-coding-standard/SKILL.md +37 -180
  116. package/pipeline/skills/shared/core/multi-agent-manual-test/SKILL.md +12 -7
  117. package/pipeline/skills/shared/core/multi-agent-refactor/SKILL.md +23 -184
  118. package/pipeline/skills/shared/core/multi-agent-review/SKILL.md +14 -82
  119. package/pipeline/skills/shared/core/multi-agent-setup/SKILL.md +25 -277
  120. package/pipeline/skills/shared/core/multi-agent-status/SKILL.md +13 -2
  121. package/pipeline/skills/shared/core/multi-agent-sync/SKILL.md +73 -217
  122. package/pipeline/skills/shared/external/alarmkit/SKILL.md +11 -162
  123. package/pipeline/skills/shared/external/alarmkit/references/alarmkit-code.md +257 -0
  124. package/pipeline/skills/shared/external/android-architecture/SKILL.md +7 -58
  125. package/pipeline/skills/shared/external/android-architecture/references/code-examples.md +68 -0
  126. package/pipeline/skills/shared/external/app-intents/SKILL.md +3 -45
  127. package/pipeline/skills/shared/external/app-intents/references/code-examples.md +54 -0
  128. package/pipeline/skills/shared/external/app-store-optimization/SKILL.md +5 -81
  129. package/pipeline/skills/shared/external/app-store-optimization/references/engagement-and-localization.md +96 -0
  130. package/pipeline/skills/shared/external/app-store-review/SKILL.md +6 -112
  131. package/pipeline/skills/shared/external/app-store-review/references/monetization-and-submission.md +138 -0
  132. package/pipeline/skills/shared/external/apple-on-device-ai/SKILL.md +10 -117
  133. package/pipeline/skills/shared/external/apple-on-device-ai/references/foundation-models-code.md +186 -0
  134. package/pipeline/skills/shared/external/authentication/SKILL.md +3 -50
  135. package/pipeline/skills/shared/external/authentication/references/code-examples.md +59 -0
  136. package/pipeline/skills/shared/external/callkit-voip/SKILL.md +3 -84
  137. package/pipeline/skills/shared/external/callkit-voip/references/code-examples.md +93 -0
  138. package/pipeline/skills/shared/external/ci-cd-pipelines/SKILL.md +7 -263
  139. package/pipeline/skills/shared/external/ci-cd-pipelines/references/pipeline-examples.md +281 -0
  140. package/pipeline/skills/shared/external/clean-code/SKILL.md +8 -41
  141. package/pipeline/skills/shared/external/clean-code/references/code-examples.md +56 -0
  142. package/pipeline/skills/shared/external/cloudkit-sync/SKILL.md +8 -146
  143. package/pipeline/skills/shared/external/cloudkit-sync/references/cloudkit-code.md +214 -0
  144. package/pipeline/skills/shared/external/compose-components/SKILL.md +6 -80
  145. package/pipeline/skills/shared/external/compose-components/references/code-examples.md +89 -0
  146. package/pipeline/skills/shared/external/compose-navigation/SKILL.md +5 -64
  147. package/pipeline/skills/shared/external/compose-navigation/references/code-examples.md +70 -0
  148. package/pipeline/skills/shared/external/core-data/SKILL.md +9 -157
  149. package/pipeline/skills/shared/external/core-data/references/core-data-code.md +221 -0
  150. package/pipeline/skills/shared/external/core-nfc/SKILL.md +3 -89
  151. package/pipeline/skills/shared/external/core-nfc/references/code-examples.md +98 -0
  152. package/pipeline/skills/shared/external/coreml/SKILL.md +17 -133
  153. package/pipeline/skills/shared/external/coreml/references/coreml-code.md +208 -0
  154. package/pipeline/skills/shared/external/css-modern/SKILL.md +8 -131
  155. package/pipeline/skills/shared/external/css-modern/references/code-examples.md +142 -0
  156. package/pipeline/skills/shared/external/database-patterns/SKILL.md +6 -103
  157. package/pipeline/skills/shared/external/database-patterns/references/orm-examples.md +112 -0
  158. package/pipeline/skills/shared/external/html-semantic/SKILL.md +7 -48
  159. package/pipeline/skills/shared/external/html-semantic/references/code-examples.md +54 -0
  160. package/pipeline/skills/shared/external/humanizer/SKILL.md +6 -19
  161. package/pipeline/skills/shared/external/humanizer/references/voice.md +24 -0
  162. package/pipeline/skills/shared/external/ios-accessibility/SKILL.md +13 -170
  163. package/pipeline/skills/shared/external/ios-accessibility/references/assistive-tech-review.md +224 -0
  164. package/pipeline/skills/shared/external/ios-localization/SKILL.md +2 -24
  165. package/pipeline/skills/shared/external/ios-localization/references/code-examples.md +30 -0
  166. package/pipeline/skills/shared/external/ios-networking/SKILL.md +3 -58
  167. package/pipeline/skills/shared/external/ios-networking/references/code-examples.md +67 -0
  168. package/pipeline/skills/shared/external/ios-security/SKILL.md +4 -64
  169. package/pipeline/skills/shared/external/ios-security/references/code-examples.md +74 -0
  170. package/pipeline/skills/shared/external/live-activities/SKILL.md +4 -90
  171. package/pipeline/skills/shared/external/live-activities/references/code-examples.md +108 -0
  172. package/pipeline/skills/shared/external/localization-reuse-map/SKILL.md +9 -23
  173. package/pipeline/skills/shared/external/localization-reuse-map/references/project-settings.md +22 -0
  174. package/pipeline/skills/shared/external/mapkit-location/SKILL.md +5 -95
  175. package/pipeline/skills/shared/external/mapkit-location/references/code-examples.md +117 -0
  176. package/pipeline/skills/shared/external/nextjs-app-router/SKILL.md +7 -106
  177. package/pipeline/skills/shared/external/nextjs-app-router/references/app-router-examples.md +124 -0
  178. package/pipeline/skills/shared/external/permissionkit/SKILL.md +2 -31
  179. package/pipeline/skills/shared/external/permissionkit/references/code-examples.md +37 -0
  180. package/pipeline/skills/shared/external/play-store-review/SKILL.md +6 -74
  181. package/pipeline/skills/shared/external/play-store-review/references/signing-and-policies.md +83 -0
  182. package/pipeline/skills/shared/external/push-notifications/SKILL.md +8 -125
  183. package/pipeline/skills/shared/external/push-notifications/references/notification-code.md +176 -0
  184. package/pipeline/skills/shared/external/python-patterns/SKILL.md +7 -158
  185. package/pipeline/skills/shared/external/python-patterns/references/python-examples.md +176 -0
  186. package/pipeline/skills/shared/external/rest-api-design/SKILL.md +7 -157
  187. package/pipeline/skills/shared/external/rest-api-design/references/api-examples.md +175 -0
  188. package/pipeline/skills/shared/external/swift-architecture/SKILL.md +11 -230
  189. package/pipeline/skills/shared/external/swift-architecture/references/pattern-examples.md +295 -0
  190. package/pipeline/skills/shared/external/swift-codable/SKILL.md +17 -149
  191. package/pipeline/skills/shared/external/swift-codable/references/codable-recipes.md +207 -0
  192. package/pipeline/skills/shared/external/swift-concurrency/SKILL.md +12 -99
  193. package/pipeline/skills/shared/external/swift-concurrency/references/code-examples.md +100 -0
  194. package/pipeline/skills/shared/external/swift-concurrency/references/swift-6-2-additions.md +18 -0
  195. package/pipeline/skills/shared/external/swift-formatstyle/SKILL.md +9 -78
  196. package/pipeline/skills/shared/external/swift-formatstyle/references/formatstyle-examples.md +113 -0
  197. package/pipeline/skills/shared/external/swift-language/SKILL.md +3 -56
  198. package/pipeline/skills/shared/external/swift-language/references/code-examples.md +65 -0
  199. package/pipeline/skills/shared/external/swift-testing-pro/SKILL.md +1 -0
  200. package/pipeline/skills/shared/external/swiftdata/SKILL.md +6 -122
  201. package/pipeline/skills/shared/external/swiftdata/references/code-examples.md +140 -0
  202. package/pipeline/skills/shared/external/swiftui-uikit-interop/SKILL.md +12 -139
  203. package/pipeline/skills/shared/external/swiftui-uikit-interop/references/interop-code.md +198 -0
  204. package/pipeline/skills/shared/external/tailwind-css/SKILL.md +9 -81
  205. package/pipeline/skills/shared/external/tailwind-css/references/code-examples.md +91 -0
  206. package/pipeline/skills/shared/external/testing-backend/SKILL.md +7 -207
  207. package/pipeline/skills/shared/external/testing-backend/references/testing-examples.md +225 -0
  208. package/pipeline/skills/shared/external/typescript-patterns/SKILL.md +8 -89
  209. package/pipeline/skills/shared/external/typescript-patterns/references/code-examples.md +100 -0
  210. package/pipeline/skills/shared/external/vision-framework/SKILL.md +2 -20
  211. package/pipeline/skills/shared/external/vision-framework/references/code-examples.md +26 -0
  212. package/pipeline/skills/shared/external/vue-composition/SKILL.md +7 -169
  213. package/pipeline/skills/shared/external/vue-composition/references/vue-examples.md +187 -0
  214. package/pipeline/skills/shared/external/web-accessibility/SKILL.md +7 -174
  215. package/pipeline/skills/shared/external/web-accessibility/references/a11y-examples.md +192 -0
  216. package/pipeline/skills/shared/external/web-performance/SKILL.md +8 -66
  217. package/pipeline/skills/shared/external/web-performance/references/code-examples.md +77 -0
  218. package/pipeline/skills/shared/external/web-testing/SKILL.md +11 -186
  219. package/pipeline/skills/shared/external/web-testing/references/testing-examples.md +213 -0
  220. /package/pipeline/skills/shared/external/localization-reuse-map/{reference → references}/format-and-output.md +0 -0
  221. /package/pipeline/skills/shared/external/localization-reuse-map/{reference → references}/publish-and-snapshot.md +0 -0
  222. /package/pipeline/skills/shared/external/localization-reuse-map/{reference → references}/sources-and-recipes.md +0 -0
@@ -0,0 +1,91 @@
1
+ # Tailwind CSS Code Examples
2
+
3
+ Full code samples for the SKILL.md sections that link here. The rules and the decision guidance stay in SKILL.md; each heading below matches the SKILL.md section the code belongs to.
4
+
5
+ ## Form
6
+
7
+ ```html
8
+ <form class="space-y-6 max-w-md mx-auto">
9
+ <div>
10
+ <label for="email" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
11
+ Email
12
+ </label>
13
+ <input
14
+ type="email"
15
+ id="email"
16
+ class="
17
+ mt-1 block w-full rounded-lg border border-gray-300
18
+ px-4 py-2.5 text-gray-900
19
+ shadow-sm
20
+ placeholder:text-gray-400
21
+ focus:border-brand focus:ring-2 focus:ring-brand/20 focus:outline-none
22
+ dark:border-gray-600 dark:bg-gray-800 dark:text-gray-100
23
+ dark:focus:border-brand-light
24
+ "
25
+ placeholder="you@example.com"
26
+ />
27
+ </div>
28
+
29
+ <div>
30
+ <label for="password" class="block text-sm font-medium text-gray-700 dark:text-gray-300">
31
+ Password
32
+ </label>
33
+ <input
34
+ type="password"
35
+ id="password"
36
+ class="
37
+ mt-1 block w-full rounded-lg border border-gray-300
38
+ px-4 py-2.5 text-gray-900
39
+ shadow-sm
40
+ focus:border-brand focus:ring-2 focus:ring-brand/20 focus:outline-none
41
+ dark:border-gray-600 dark:bg-gray-800 dark:text-gray-100
42
+ "
43
+ />
44
+ <p class="mt-1 text-xs text-gray-500">Must be at least 8 characters.</p>
45
+ </div>
46
+
47
+ <button
48
+ type="submit"
49
+ class="
50
+ w-full rounded-lg bg-brand px-4 py-2.5
51
+ text-sm font-semibold text-white
52
+ shadow-sm
53
+ hover:bg-brand-dark focus:ring-2 focus:ring-brand/50 focus:outline-none
54
+ disabled:opacity-50 disabled:cursor-not-allowed
55
+ transition-colors
56
+ "
57
+ >
58
+ Sign in
59
+ </button>
60
+ </form>
61
+ ```
62
+
63
+ ## Navigation
64
+
65
+ ```html
66
+ <nav class="bg-white dark:bg-gray-900 border-b border-gray-200 dark:border-gray-700">
67
+ <div class="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
68
+ <div class="flex h-16 items-center justify-between">
69
+ <div class="flex items-center gap-8">
70
+ <a href="/" class="text-xl font-bold text-brand">Logo</a>
71
+ <div class="hidden md:flex gap-6">
72
+ <a href="#" class="text-sm font-medium text-gray-900 dark:text-gray-100 hover:text-brand">
73
+ Home
74
+ </a>
75
+ <a href="#" class="text-sm font-medium text-gray-500 dark:text-gray-400 hover:text-brand">
76
+ Products
77
+ </a>
78
+ <a href="#" class="text-sm font-medium text-gray-500 dark:text-gray-400 hover:text-brand">
79
+ About
80
+ </a>
81
+ </div>
82
+ </div>
83
+ <button class="md:hidden p-2 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-800">
84
+ <svg class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
85
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16" />
86
+ </svg>
87
+ </button>
88
+ </div>
89
+ </div>
90
+ </nav>
91
+ ```
@@ -38,70 +38,7 @@ tests/
38
38
 
39
39
  ### Fixtures and Factories
40
40
 
41
- ```python
42
- # tests/conftest.py
43
- import pytest
44
- from httpx import AsyncClient, ASGITransport
45
- from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
46
- from app.main import app
47
- from app.database import Base, get_db
48
-
49
- TEST_DB_URL = "postgresql+asyncpg://test:test@localhost:5432/test_db"
50
-
51
- @pytest.fixture(scope="session")
52
- def anyio_backend():
53
- return "asyncio"
54
-
55
- @pytest.fixture(scope="session")
56
- async def engine():
57
- engine = create_async_engine(TEST_DB_URL)
58
- async with engine.begin() as conn:
59
- await conn.run_sync(Base.metadata.create_all)
60
- yield engine
61
- async with engine.begin() as conn:
62
- await conn.run_sync(Base.metadata.drop_all)
63
- await engine.dispose()
64
-
65
- @pytest.fixture
66
- async def db_session(engine):
67
- async with AsyncSession(engine) as session:
68
- async with session.begin():
69
- yield session
70
- await session.rollback() # Rollback after each test
71
-
72
- @pytest.fixture
73
- async def client(db_session):
74
- async def override_get_db():
75
- yield db_session
76
-
77
- app.dependency_overrides[get_db] = override_get_db
78
- transport = ASGITransport(app=app)
79
- async with AsyncClient(transport=transport, base_url="http://test") as client:
80
- yield client
81
- app.dependency_overrides.clear()
82
- ```
83
-
84
- ```python
85
- # tests/factories.py
86
- from dataclasses import dataclass, field
87
- import uuid
88
-
89
- @dataclass
90
- class UserFactory:
91
- id: str = field(default_factory=lambda: str(uuid.uuid4()))
92
- name: str = "Test User"
93
- email: str = field(default_factory=lambda: f"user-{uuid.uuid4().hex[:8]}@test.com")
94
- role: str = "user"
95
-
96
- def build(self) -> dict:
97
- return {"id": self.id, "name": self.name, "email": self.email, "role": self.role}
98
-
99
- async def create(self, db: AsyncSession) -> User:
100
- user = User(**self.build())
101
- db.add(user)
102
- await db.flush()
103
- return user
104
- ```
41
+ Code for this section: [Backend testing examples](references/testing-examples.md).
105
42
 
106
43
  ### Unit Tests
107
44
 
@@ -154,152 +91,11 @@ class TestUserService:
154
91
 
155
92
  ### Integration Tests
156
93
 
157
- ```python
158
- # tests/integration/test_api.py
159
- import pytest
160
- from httpx import AsyncClient
161
-
162
- class TestUserAPI:
163
- async def test_create_user(self, client: AsyncClient):
164
- response = await client.post("/api/v1/users", json={
165
- "name": "Alice",
166
- "email": "alice@example.com",
167
- "role": "user",
168
- })
169
-
170
- assert response.status_code == 201
171
- data = response.json()["data"]
172
- assert data["name"] == "Alice"
173
- assert data["email"] == "alice@example.com"
174
- assert "id" in data
175
-
176
- async def test_create_user_duplicate_email(self, client: AsyncClient, db_session):
177
- # Arrange: create existing user
178
- from tests.factories import UserFactory
179
- await UserFactory(email="taken@example.com").create(db_session)
180
-
181
- # Act
182
- response = await client.post("/api/v1/users", json={
183
- "name": "Bob",
184
- "email": "taken@example.com",
185
- })
186
-
187
- # Assert
188
- assert response.status_code == 409
189
- assert response.json()["error"]["code"] == "DUPLICATE_EMAIL"
190
-
191
- async def test_list_users_pagination(self, client: AsyncClient, db_session):
192
- from tests.factories import UserFactory
193
- for i in range(25):
194
- await UserFactory(name=f"User {i}").create(db_session)
195
-
196
- response = await client.get("/api/v1/users?per_page=10&page=1")
197
-
198
- assert response.status_code == 200
199
- body = response.json()
200
- assert len(body["data"]) == 10
201
- assert body["meta"]["total"] == 25
202
- assert body["meta"]["has_next"] is True
203
-
204
- async def test_get_user_not_found(self, client: AsyncClient):
205
- response = await client.get("/api/v1/users/nonexistent-id")
206
- assert response.status_code == 404
207
- ```
94
+ Code for this section: [Backend testing examples](references/testing-examples.md).
208
95
 
209
96
  ## Jest Patterns (Node.js)
210
97
 
211
- ```typescript
212
- // tests/setup.ts
213
- import { PrismaClient } from '@prisma/client';
214
-
215
- const prisma = new PrismaClient();
216
-
217
- beforeEach(async () => {
218
- // Clean tables in correct order (respect foreign keys)
219
- await prisma.$transaction([
220
- prisma.postTag.deleteMany(),
221
- prisma.post.deleteMany(),
222
- prisma.user.deleteMany(),
223
- ]);
224
- });
225
-
226
- afterAll(async () => {
227
- await prisma.$disconnect();
228
- });
229
-
230
- export { prisma };
231
- ```
232
-
233
- ```typescript
234
- // tests/unit/user-service.test.ts
235
- import { UserService } from '@/services/user-service';
236
- import { UserRepository } from '@/repositories/user-repository';
237
-
238
- jest.mock('@/repositories/user-repository');
239
-
240
- describe('UserService', () => {
241
- let service: UserService;
242
- let mockRepo: jest.Mocked<UserRepository>;
243
-
244
- beforeEach(() => {
245
- mockRepo = new UserRepository() as jest.Mocked<UserRepository>;
246
- service = new UserService(mockRepo);
247
- });
248
-
249
- describe('getUserById', () => {
250
- it('returns user when found', async () => {
251
- const expected = { id: '1', name: 'Alice', email: 'alice@test.com' };
252
- mockRepo.findById.mockResolvedValue(expected);
253
-
254
- const result = await service.getUserById('1');
255
-
256
- expect(result).toEqual(expected);
257
- expect(mockRepo.findById).toHaveBeenCalledWith('1');
258
- });
259
-
260
- it('throws NotFoundError when user does not exist', async () => {
261
- mockRepo.findById.mockResolvedValue(null);
262
-
263
- await expect(service.getUserById('999')).rejects.toThrow('User not found');
264
- });
265
- });
266
- });
267
- ```
268
-
269
- ```typescript
270
- // tests/integration/user-api.test.ts
271
- import request from 'supertest';
272
- import { app } from '@/app';
273
- import { prisma } from '../setup';
274
-
275
- describe('POST /api/v1/users', () => {
276
- it('creates a user and returns 201', async () => {
277
- const response = await request(app)
278
- .post('/api/v1/users')
279
- .send({ name: 'Alice', email: 'alice@example.com' })
280
- .expect(201);
281
-
282
- expect(response.body.data).toMatchObject({
283
- name: 'Alice',
284
- email: 'alice@example.com',
285
- });
286
- expect(response.body.data.id).toBeDefined();
287
-
288
- // Verify in database
289
- const user = await prisma.user.findUnique({ where: { email: 'alice@example.com' } });
290
- expect(user).not.toBeNull();
291
- });
292
-
293
- it('returns 422 for invalid email', async () => {
294
- const response = await request(app)
295
- .post('/api/v1/users')
296
- .send({ name: 'Bob', email: 'invalid' })
297
- .expect(422);
298
-
299
- expect(response.body.error.details[0].field).toBe('email');
300
- });
301
- });
302
- ```
98
+ Code for this section: [Backend testing examples](references/testing-examples.md).
303
99
 
304
100
  ## Database Testing
305
101
 
@@ -394,3 +190,7 @@ describe('User API Contract', () => {
394
190
  | Factory creates duplicate data | Unique constraints violated | Use random values in factories (UUID, random email) |
395
191
  | Mock not applied | Wrong import path or scope | Verify mock target matches actual import path |
396
192
  | DB schema out of sync | Migrations not run in test DB | Run migrations before test suite in CI |
193
+
194
+ ## References
195
+
196
+ - [Backend testing examples](references/testing-examples.md): open when writing pytest fixtures, integration tests against a real database, or Jest tests for a Node.js service.
@@ -0,0 +1,225 @@
1
+ # Backend testing examples
2
+
3
+ <!-- toc -->
4
+ - [Fixtures and Factories](#fixtures-and-factories)
5
+ - [Integration Tests](#integration-tests)
6
+ - [Jest Patterns (Node.js)](#jest-patterns-nodejs)
7
+ <!-- /toc -->
8
+
9
+ Full code for pytest fixtures and factories, integration tests and Jest patterns. SKILL.md keeps the pyramid, the rules and troubleshooting.
10
+
11
+ ## Fixtures and Factories
12
+
13
+ ```python
14
+ # tests/conftest.py
15
+ import pytest
16
+ from httpx import AsyncClient, ASGITransport
17
+ from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
18
+ from app.main import app
19
+ from app.database import Base, get_db
20
+
21
+ TEST_DB_URL = "postgresql+asyncpg://test:test@localhost:5432/test_db"
22
+
23
+ @pytest.fixture(scope="session")
24
+ def anyio_backend():
25
+ return "asyncio"
26
+
27
+ @pytest.fixture(scope="session")
28
+ async def engine():
29
+ engine = create_async_engine(TEST_DB_URL)
30
+ async with engine.begin() as conn:
31
+ await conn.run_sync(Base.metadata.create_all)
32
+ yield engine
33
+ async with engine.begin() as conn:
34
+ await conn.run_sync(Base.metadata.drop_all)
35
+ await engine.dispose()
36
+
37
+ @pytest.fixture
38
+ async def db_session(engine):
39
+ async with AsyncSession(engine) as session:
40
+ async with session.begin():
41
+ yield session
42
+ await session.rollback() # Rollback after each test
43
+
44
+ @pytest.fixture
45
+ async def client(db_session):
46
+ async def override_get_db():
47
+ yield db_session
48
+
49
+ app.dependency_overrides[get_db] = override_get_db
50
+ transport = ASGITransport(app=app)
51
+ async with AsyncClient(transport=transport, base_url="http://test") as client:
52
+ yield client
53
+ app.dependency_overrides.clear()
54
+ ```
55
+
56
+ ```python
57
+ # tests/factories.py
58
+ from dataclasses import dataclass, field
59
+ import uuid
60
+
61
+ @dataclass
62
+ class UserFactory:
63
+ id: str = field(default_factory=lambda: str(uuid.uuid4()))
64
+ name: str = "Test User"
65
+ email: str = field(default_factory=lambda: f"user-{uuid.uuid4().hex[:8]}@test.com")
66
+ role: str = "user"
67
+
68
+ def build(self) -> dict:
69
+ return {"id": self.id, "name": self.name, "email": self.email, "role": self.role}
70
+
71
+ async def create(self, db: AsyncSession) -> User:
72
+ user = User(**self.build())
73
+ db.add(user)
74
+ await db.flush()
75
+ return user
76
+ ```
77
+
78
+ ## Integration Tests
79
+
80
+ ```python
81
+ # tests/integration/test_api.py
82
+ import pytest
83
+ from httpx import AsyncClient
84
+
85
+ class TestUserAPI:
86
+ async def test_create_user(self, client: AsyncClient):
87
+ response = await client.post("/api/v1/users", json={
88
+ "name": "Alice",
89
+ "email": "alice@example.com",
90
+ "role": "user",
91
+ })
92
+
93
+ assert response.status_code == 201
94
+ data = response.json()["data"]
95
+ assert data["name"] == "Alice"
96
+ assert data["email"] == "alice@example.com"
97
+ assert "id" in data
98
+
99
+ async def test_create_user_duplicate_email(self, client: AsyncClient, db_session):
100
+ # Arrange: create existing user
101
+ from tests.factories import UserFactory
102
+ await UserFactory(email="taken@example.com").create(db_session)
103
+
104
+ # Act
105
+ response = await client.post("/api/v1/users", json={
106
+ "name": "Bob",
107
+ "email": "taken@example.com",
108
+ })
109
+
110
+ # Assert
111
+ assert response.status_code == 409
112
+ assert response.json()["error"]["code"] == "DUPLICATE_EMAIL"
113
+
114
+ async def test_list_users_pagination(self, client: AsyncClient, db_session):
115
+ from tests.factories import UserFactory
116
+ for i in range(25):
117
+ await UserFactory(name=f"User {i}").create(db_session)
118
+
119
+ response = await client.get("/api/v1/users?per_page=10&page=1")
120
+
121
+ assert response.status_code == 200
122
+ body = response.json()
123
+ assert len(body["data"]) == 10
124
+ assert body["meta"]["total"] == 25
125
+ assert body["meta"]["has_next"] is True
126
+
127
+ async def test_get_user_not_found(self, client: AsyncClient):
128
+ response = await client.get("/api/v1/users/nonexistent-id")
129
+ assert response.status_code == 404
130
+ ```
131
+
132
+ ## Jest Patterns (Node.js)
133
+
134
+ ```typescript
135
+ // tests/setup.ts
136
+ import { PrismaClient } from '@prisma/client';
137
+
138
+ const prisma = new PrismaClient();
139
+
140
+ beforeEach(async () => {
141
+ // Clean tables in correct order (respect foreign keys)
142
+ await prisma.$transaction([
143
+ prisma.postTag.deleteMany(),
144
+ prisma.post.deleteMany(),
145
+ prisma.user.deleteMany(),
146
+ ]);
147
+ });
148
+
149
+ afterAll(async () => {
150
+ await prisma.$disconnect();
151
+ });
152
+
153
+ export { prisma };
154
+ ```
155
+
156
+ ```typescript
157
+ // tests/unit/user-service.test.ts
158
+ import { UserService } from '@/services/user-service';
159
+ import { UserRepository } from '@/repositories/user-repository';
160
+
161
+ jest.mock('@/repositories/user-repository');
162
+
163
+ describe('UserService', () => {
164
+ let service: UserService;
165
+ let mockRepo: jest.Mocked<UserRepository>;
166
+
167
+ beforeEach(() => {
168
+ mockRepo = new UserRepository() as jest.Mocked<UserRepository>;
169
+ service = new UserService(mockRepo);
170
+ });
171
+
172
+ describe('getUserById', () => {
173
+ it('returns user when found', async () => {
174
+ const expected = { id: '1', name: 'Alice', email: 'alice@test.com' };
175
+ mockRepo.findById.mockResolvedValue(expected);
176
+
177
+ const result = await service.getUserById('1');
178
+
179
+ expect(result).toEqual(expected);
180
+ expect(mockRepo.findById).toHaveBeenCalledWith('1');
181
+ });
182
+
183
+ it('throws NotFoundError when user does not exist', async () => {
184
+ mockRepo.findById.mockResolvedValue(null);
185
+
186
+ await expect(service.getUserById('999')).rejects.toThrow('User not found');
187
+ });
188
+ });
189
+ });
190
+ ```
191
+
192
+ ```typescript
193
+ // tests/integration/user-api.test.ts
194
+ import request from 'supertest';
195
+ import { app } from '@/app';
196
+ import { prisma } from '../setup';
197
+
198
+ describe('POST /api/v1/users', () => {
199
+ it('creates a user and returns 201', async () => {
200
+ const response = await request(app)
201
+ .post('/api/v1/users')
202
+ .send({ name: 'Alice', email: 'alice@example.com' })
203
+ .expect(201);
204
+
205
+ expect(response.body.data).toMatchObject({
206
+ name: 'Alice',
207
+ email: 'alice@example.com',
208
+ });
209
+ expect(response.body.data.id).toBeDefined();
210
+
211
+ // Verify in database
212
+ const user = await prisma.user.findUnique({ where: { email: 'alice@example.com' } });
213
+ expect(user).not.toBeNull();
214
+ });
215
+
216
+ it('returns 422 for invalid email', async () => {
217
+ const response = await request(app)
218
+ .post('/api/v1/users')
219
+ .send({ name: 'Bob', email: 'invalid' })
220
+ .expect(422);
221
+
222
+ expect(response.body.error.details[0].field).toBe('email');
223
+ });
224
+ });
225
+ ```
@@ -211,100 +211,15 @@ function handleInput(input: string | number | { value: string }) {
211
211
 
212
212
  ## Discriminated Unions
213
213
 
214
- ```typescript
215
- // API response pattern
216
- type ApiResult<T> =
217
- | { status: 'success'; data: T }
218
- | { status: 'error'; error: { code: string; message: string } }
219
- | { status: 'loading' };
220
-
221
- function handleResult<T>(result: ApiResult<T>): string {
222
- switch (result.status) {
223
- case 'success':
224
- return JSON.stringify(result.data);
225
- case 'error':
226
- return `Error ${result.error.code}: ${result.error.message}`;
227
- case 'loading':
228
- return 'Loading...';
229
- }
230
- }
214
+ Model API results and events as unions keyed on a literal field (`status`, `type`), switch on that field, and end the switch with an `exhaustiveCheck(value: never)` helper so a new variant fails to compile.
231
215
 
232
- // Event system
233
- type AppEvent =
234
- | { type: 'USER_LOGIN'; payload: { userId: string } }
235
- | { type: 'USER_LOGOUT' }
236
- | { type: 'ITEM_ADDED'; payload: { itemId: string; quantity: number } }
237
- | { type: 'ITEM_REMOVED'; payload: { itemId: string } };
238
-
239
- function handleEvent(event: AppEvent): void {
240
- switch (event.type) {
241
- case 'USER_LOGIN':
242
- console.log(`User ${event.payload.userId} logged in`);
243
- break;
244
- case 'USER_LOGOUT':
245
- console.log('User logged out');
246
- break;
247
- case 'ITEM_ADDED':
248
- console.log(`Added ${event.payload.quantity} of ${event.payload.itemId}`);
249
- break;
250
- case 'ITEM_REMOVED':
251
- console.log(`Removed ${event.payload.itemId}`);
252
- break;
253
- }
254
- }
255
-
256
- // Exhaustive check helper
257
- function exhaustiveCheck(value: never): never {
258
- throw new Error(`Unhandled value: ${JSON.stringify(value)}`);
259
- }
260
- ```
216
+ Code: [Discriminated Unions](references/code-examples.md#discriminated-unions).
261
217
 
262
218
  ## Zod for Runtime Validation
263
219
 
264
- ```typescript
265
- import { z } from 'zod';
266
-
267
- // Define schema
268
- const UserSchema = z.object({
269
- id: z.string().uuid(),
270
- name: z.string().min(1).max(100),
271
- email: z.string().email(),
272
- role: z.enum(['admin', 'user', 'viewer']),
273
- age: z.number().int().min(0).max(150).optional(),
274
- tags: z.array(z.string()).default([]),
275
- });
276
-
277
- // Infer TypeScript type from schema
278
- type User = z.infer<typeof UserSchema>;
279
-
280
- // Validate at runtime
281
- function createUser(input: unknown): User {
282
- return UserSchema.parse(input); // throws ZodError on failure
283
- }
284
-
285
- // Safe parse (no throw)
286
- function validateUser(input: unknown): ApiResult<User> {
287
- const result = UserSchema.safeParse(input);
288
- if (result.success) {
289
- return { status: 'success', data: result.data };
290
- }
291
- return {
292
- status: 'error',
293
- error: { code: 'VALIDATION', message: result.error.message },
294
- };
295
- }
220
+ Define a schema once, infer the type with `z.infer`, use `parse` where failure should throw and `safeParse` where it should return a result, and derive create/update schemas with `omit`, `partial` and `required`.
296
221
 
297
- // Schema composition
298
- const CreateUserSchema = UserSchema.omit({ id: true });
299
- const UpdateUserSchema = UserSchema.partial().required({ id: true });
300
-
301
- // API request validation
302
- const PaginationSchema = z.object({
303
- page: z.coerce.number().int().min(1).default(1),
304
- limit: z.coerce.number().int().min(1).max(100).default(20),
305
- sort: z.enum(['asc', 'desc']).default('desc'),
306
- });
307
- ```
222
+ Code: [Zod for Runtime Validation](references/code-examples.md#zod-for-runtime-validation).
308
223
 
309
224
  ## Do's
310
225
 
@@ -337,3 +252,7 @@ const PaginationSchema = z.object({
337
252
  | "Argument of type 'string' is not assignable to parameter of type 'X'" | Widened literal type | Use `as const` or `satisfies` to preserve literals |
338
253
  | Circular type reference | Self-referencing generic | Use `interface` instead of `type` for recursive structures |
339
254
  | Slow TypeScript compiler | Complex conditional types | Simplify type computations; avoid deeply nested generics |
255
+
256
+ ## References
257
+
258
+ - [Code examples](references/code-examples.md): open for the full code behind Discriminated Unions, Zod for Runtime Validation.