@nklisch/pi-agile-workflow 0.15.3

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 (203) hide show
  1. package/.claude-plugin/plugin.json +10 -0
  2. package/.codex-plugin/plugin.json +28 -0
  3. package/CHANGELOG.md +591 -0
  4. package/README.md +107 -0
  5. package/docs/ARCHITECTURE.md +716 -0
  6. package/docs/MIGRATION.md +419 -0
  7. package/docs/PRINCIPLES.md +464 -0
  8. package/docs/ROADMAP.md +95 -0
  9. package/docs/SPEC.md +855 -0
  10. package/docs/VISION.md +106 -0
  11. package/extensions/agile-workflow.test.ts +419 -0
  12. package/extensions/agile-workflow.ts +467 -0
  13. package/hooks/hooks.json +52 -0
  14. package/hooks/scripts/prompt-context.py +628 -0
  15. package/hooks/scripts/substrate-maintainer.py +359 -0
  16. package/hooks/scripts/test_prompt_context.py +757 -0
  17. package/package.json +30 -0
  18. package/scripts/install-work-view.sh +165 -0
  19. package/scripts/tests/agent-metadata.test.sh +75 -0
  20. package/scripts/tests/bump-version.test.sh +552 -0
  21. package/scripts/tests/channel-parity.test.sh +117 -0
  22. package/scripts/tests/convert-content-integrity.test.sh +246 -0
  23. package/scripts/tests/convert-install-routing.test.sh +159 -0
  24. package/scripts/tests/install-work-view.test.sh +411 -0
  25. package/scripts/tests/pi-package-metadata.test.sh +119 -0
  26. package/scripts/tests/work-board-shim.test.sh +239 -0
  27. package/scripts/tests/work-view-dist-version.test.sh +105 -0
  28. package/scripts/work-board.sh +64 -0
  29. package/scripts/work-view.sh +423 -0
  30. package/skills/autopilot/SKILL.md +330 -0
  31. package/skills/autopilot/agents/openai.yaml +6 -0
  32. package/skills/board/SKILL.md +62 -0
  33. package/skills/board/agents/openai.yaml +6 -0
  34. package/skills/bold-refactor/SKILL.md +244 -0
  35. package/skills/bold-refactor/agents/openai.yaml +6 -0
  36. package/skills/bug-scan/SKILL.md +378 -0
  37. package/skills/bug-scan/agents/openai.yaml +6 -0
  38. package/skills/bug-scan/references/async-promises.md +119 -0
  39. package/skills/bug-scan/references/concurrency-races.md +148 -0
  40. package/skills/bug-scan/references/data-layer.md +167 -0
  41. package/skills/bug-scan/references/error-handling.md +197 -0
  42. package/skills/bug-scan/references/gate-item-template.md +94 -0
  43. package/skills/bug-scan/references/language-footguns.md +219 -0
  44. package/skills/bug-scan/references/parked-item-template.md +103 -0
  45. package/skills/bug-scan/references/report-template.md +115 -0
  46. package/skills/bug-scan/references/resource-leaks.md +199 -0
  47. package/skills/bug-scan/references/state-closures.md +148 -0
  48. package/skills/bug-scan/references/time-numbers.md +158 -0
  49. package/skills/convert/SKILL.md +1463 -0
  50. package/skills/convert/agents/openai.yaml +6 -0
  51. package/skills/convert/references/legacy-overlap-migration.md +179 -0
  52. package/skills/deep-code-scan/SKILL.md +378 -0
  53. package/skills/deep-code-scan/agents/openai.yaml +6 -0
  54. package/skills/deep-code-scan/references/artifact-ledger.md +148 -0
  55. package/skills/deep-code-scan/references/consolidation.md +91 -0
  56. package/skills/deep-code-scan/references/decomposition.md +127 -0
  57. package/skills/deep-code-scan/references/item-templates.md +263 -0
  58. package/skills/deep-code-scan/references/lane-catalog.md +129 -0
  59. package/skills/deep-code-scan/references/review-gauntlet.md +108 -0
  60. package/skills/deep-code-scan/references/scanner-brief.md +152 -0
  61. package/skills/e2e-test-design/SKILL.md +517 -0
  62. package/skills/e2e-test-design/references/anti-tautology.md +261 -0
  63. package/skills/e2e-test-design/references/service-mocks.md +234 -0
  64. package/skills/e2e-test-design/references/test-taxonomy.md +211 -0
  65. package/skills/epic-design/SKILL.md +476 -0
  66. package/skills/epicize/SKILL.md +210 -0
  67. package/skills/epicize/agents/openai.yaml +6 -0
  68. package/skills/feature-design/SKILL.md +472 -0
  69. package/skills/fix/SKILL.md +173 -0
  70. package/skills/gate-cruft/SKILL.md +258 -0
  71. package/skills/gate-docs/SKILL.md +278 -0
  72. package/skills/gate-patterns/SKILL.md +389 -0
  73. package/skills/gate-refactor/SKILL.md +408 -0
  74. package/skills/gate-security/SKILL.md +275 -0
  75. package/skills/gate-tests/SKILL.md +367 -0
  76. package/skills/groom/SKILL.md +162 -0
  77. package/skills/groom/agents/openai.yaml +6 -0
  78. package/skills/ideate/SKILL.md +185 -0
  79. package/skills/ideate/agents/openai.yaml +6 -0
  80. package/skills/implement/SKILL.md +288 -0
  81. package/skills/implement-orchestrator/SKILL.md +604 -0
  82. package/skills/park/SKILL.md +111 -0
  83. package/skills/perf-design/SKILL.md +427 -0
  84. package/skills/perf-scout/SKILL.md +404 -0
  85. package/skills/perf-scout/agents/openai.yaml +6 -0
  86. package/skills/perf-scout/references/algorithmic-and-data-structures.md +126 -0
  87. package/skills/perf-scout/references/approximation-and-precomputation.md +136 -0
  88. package/skills/perf-scout/references/caching-and-memoization.md +123 -0
  89. package/skills/perf-scout/references/compiler-runtime-and-language.md +136 -0
  90. package/skills/perf-scout/references/database-and-storage-internals.md +144 -0
  91. package/skills/perf-scout/references/distributed-systems.md +150 -0
  92. package/skills/perf-scout/references/game-engine-and-realtime.md +136 -0
  93. package/skills/perf-scout/references/gpu-and-accelerators.md +130 -0
  94. package/skills/perf-scout/references/idea-ranking.md +75 -0
  95. package/skills/perf-scout/references/io-and-batching.md +135 -0
  96. package/skills/perf-scout/references/memory-and-data-locality.md +137 -0
  97. package/skills/perf-scout/references/parallelism-and-vectorization.md +126 -0
  98. package/skills/perf-scout/references/parked-item-template.md +117 -0
  99. package/skills/perf-scout/references/peer-review-pass.md +150 -0
  100. package/skills/perf-scout/references/report-template.md +148 -0
  101. package/skills/principles/SKILL.md +678 -0
  102. package/skills/principles/references/models.md +191 -0
  103. package/skills/principles/references/subagents.md +158 -0
  104. package/skills/prose-author/SKILL.md +185 -0
  105. package/skills/refactor-conventions-creator/SKILL.md +359 -0
  106. package/skills/refactor-conventions-creator/agents/openai.yaml +6 -0
  107. package/skills/refactor-conventions-creator/references/common-structures.md +91 -0
  108. package/skills/refactor-conventions-creator/references/common-styles.md +90 -0
  109. package/skills/refactor-design/SKILL.md +384 -0
  110. package/skills/release-deploy/SKILL.md +667 -0
  111. package/skills/release-deploy/agents/openai.yaml +6 -0
  112. package/skills/research/SKILL.md +166 -0
  113. package/skills/research/agents/openai.yaml +6 -0
  114. package/skills/review/SKILL.md +267 -0
  115. package/skills/review/references/deep-review.md +98 -0
  116. package/skills/review/references/review-lenses.md +68 -0
  117. package/skills/review/references/substrate-side-effects.md +161 -0
  118. package/skills/review/references/target-resolution.md +52 -0
  119. package/skills/scope/SKILL.md +486 -0
  120. package/work-view/Cargo.lock +486 -0
  121. package/work-view/Cargo.toml +9 -0
  122. package/work-view/crates/cli/.work-view-version +1 -0
  123. package/work-view/crates/cli/Cargo.toml +18 -0
  124. package/work-view/crates/cli/src/actionable.rs +500 -0
  125. package/work-view/crates/cli/src/args.rs +792 -0
  126. package/work-view/crates/cli/src/board/assets/board.css +855 -0
  127. package/work-view/crates/cli/src/board/assets/board.js +259 -0
  128. package/work-view/crates/cli/src/board/assets/card.js +139 -0
  129. package/work-view/crates/cli/src/board/assets/components.css +290 -0
  130. package/work-view/crates/cli/src/board/assets/dependency.js +1193 -0
  131. package/work-view/crates/cli/src/board/assets/detail.js +256 -0
  132. package/work-view/crates/cli/src/board/assets/filters.js +389 -0
  133. package/work-view/crates/cli/src/board/assets/index.html +77 -0
  134. package/work-view/crates/cli/src/board/assets/kanban.js +231 -0
  135. package/work-view/crates/cli/src/board/assets/markdown.js +260 -0
  136. package/work-view/crates/cli/src/board/assets/motion.css +131 -0
  137. package/work-view/crates/cli/src/board/assets/state.js +245 -0
  138. package/work-view/crates/cli/src/board/assets/table.js +304 -0
  139. package/work-view/crates/cli/src/board/assets/tokens.css +602 -0
  140. package/work-view/crates/cli/src/board/assets/views.js +41 -0
  141. package/work-view/crates/cli/src/board/assets.rs +162 -0
  142. package/work-view/crates/cli/src/board/feed.rs +204 -0
  143. package/work-view/crates/cli/src/board/mod.rs +246 -0
  144. package/work-view/crates/cli/src/board/open.rs +144 -0
  145. package/work-view/crates/cli/src/board/server.rs +407 -0
  146. package/work-view/crates/cli/src/main.rs +195 -0
  147. package/work-view/crates/cli/src/render.rs +354 -0
  148. package/work-view/crates/cli/src/scope.rs +157 -0
  149. package/work-view/crates/cli/src/stale.rs +581 -0
  150. package/work-view/crates/cli/tests/board-js/dependency-table.test.mjs +64 -0
  151. package/work-view/crates/cli/tests/board-js/expanded-browsing.test.mjs +79 -0
  152. package/work-view/crates/cli/tests/board-js/harness-smoke.test.mjs +11 -0
  153. package/work-view/crates/cli/tests/board-js/harness.mjs +437 -0
  154. package/work-view/crates/cli/tests/board-js/kanban-detail.test.mjs +75 -0
  155. package/work-view/crates/cli/tests/board-js/markdown-filter.test.mjs +60 -0
  156. package/work-view/crates/cli/tests/fixtures/golden/.work/CONVENTIONS.md +3 -0
  157. package/work-view/crates/cli/tests/fixtures/golden/.work/active/epics/epic-alpha.md +16 -0
  158. package/work-view/crates/cli/tests/fixtures/golden/.work/active/features/feat-a.md +16 -0
  159. package/work-view/crates/cli/tests/fixtures/golden/.work/active/features/feat-b.md +16 -0
  160. package/work-view/crates/cli/tests/fixtures/golden/.work/active/stories/story-alpha-1.md +16 -0
  161. package/work-view/crates/cli/tests/fixtures/golden/.work/active/stories/story-research-1.md +19 -0
  162. package/work-view/crates/cli/tests/fixtures/golden/.work/archive/feat-done.md +16 -0
  163. package/work-view/crates/cli/tests/fixtures/golden/.work/archive/feat-shipped.md +18 -0
  164. package/work-view/crates/cli/tests/fixtures/golden/.work/backlog/idea-backlog.md +7 -0
  165. package/work-view/crates/cli/tests/fixtures/golden/.work/releases/v1.0/v1.0.md +16 -0
  166. package/work-view/crates/cli/tests/fixtures/ready-drafting/.work/CONVENTIONS.md +6 -0
  167. package/work-view/crates/cli/tests/fixtures/ready-drafting/.work/active/features/feat-dep-done.md +17 -0
  168. package/work-view/crates/cli/tests/fixtures/ready-drafting/.work/active/features/feat-design-ready.md +17 -0
  169. package/work-view/crates/cli/tests/fixtures/ready-drafting/.work/active/features/feat-impl-ready.md +17 -0
  170. package/work-view/crates/cli/tests/fixtures/ready-drafting/.work/active/stories/story-review-ready.md +17 -0
  171. package/work-view/crates/cli/tests/fixtures/with-malformed/.work/CONVENTIONS.md +4 -0
  172. package/work-view/crates/cli/tests/fixtures/with-malformed/.work/active/epics/malformed-no-closing-fence.md +8 -0
  173. package/work-view/crates/cli/tests/fixtures/with-malformed/.work/active/features/good-item.md +16 -0
  174. package/work-view/crates/cli/tests/integration.rs +2833 -0
  175. package/work-view/crates/core/Cargo.toml +13 -0
  176. package/work-view/crates/core/src/error.rs +61 -0
  177. package/work-view/crates/core/src/filter.rs +804 -0
  178. package/work-view/crates/core/src/graph.rs +324 -0
  179. package/work-view/crates/core/src/index.rs +590 -0
  180. package/work-view/crates/core/src/lib.rs +54 -0
  181. package/work-view/crates/core/src/model.rs +202 -0
  182. package/work-view/crates/core/src/parse.rs +494 -0
  183. package/work-view/crates/core/tests/fixtures/golden/.work/CONVENTIONS.md +15 -0
  184. package/work-view/crates/core/tests/fixtures/golden/.work/active/epics/epic-core.md +16 -0
  185. package/work-view/crates/core/tests/fixtures/golden/.work/active/features/feat-parser.md +16 -0
  186. package/work-view/crates/core/tests/fixtures/golden/.work/active/features/feat-query.md +16 -0
  187. package/work-view/crates/core/tests/fixtures/golden/.work/active/stories/malformed-no-id.md +15 -0
  188. package/work-view/crates/core/tests/fixtures/golden/.work/active/stories/story-parse-unit.md +16 -0
  189. package/work-view/crates/core/tests/fixtures/golden/.work/active/stories/story-security-gate.md +16 -0
  190. package/work-view/crates/core/tests/fixtures/golden/.work/archive/feat-old.md +16 -0
  191. package/work-view/crates/core/tests/fixtures/golden/.work/archive/feat-parser.md +17 -0
  192. package/work-view/crates/core/tests/fixtures/golden/.work/backlog/idea-future-board.md +7 -0
  193. package/work-view/crates/core/tests/fixtures/golden/.work/releases/v1.0/v1.0.md +16 -0
  194. package/work-view/crates/core/tests/fixtures/precedence/.work/CONVENTIONS.md +10 -0
  195. package/work-view/crates/core/tests/fixtures/precedence/.work/archive/dup-item.md +18 -0
  196. package/work-view/crates/core/tests/fixtures/precedence/.work/releases/v2.0/dup-item.md +18 -0
  197. package/work-view/crates/core/tests/integration.rs +541 -0
  198. package/work-view/dist/.gitattributes +1 -0
  199. package/work-view/dist/README.md +42 -0
  200. package/work-view/dist/aarch64-apple-darwin/work-view +0 -0
  201. package/work-view/dist/aarch64-unknown-linux-musl/work-view +0 -0
  202. package/work-view/dist/x86_64-apple-darwin/work-view +0 -0
  203. package/work-view/dist/x86_64-unknown-linux-musl/work-view +0 -0
@@ -0,0 +1,261 @@
1
+ # Anti-Tautology
2
+
3
+ Patterns that make a test useless, and grep-able heuristics the `--audit` mode
4
+ uses to find them. Referenced from SKILL.md Phase 5c (design-time
5
+ guardrails) and Phase A2 (audit dispatch brief).
6
+
7
+ ## The core failure mode
8
+
9
+ A tautological test verifies that the code does what the code does — it
10
+ restates the implementation as an assertion. When the implementation changes,
11
+ the test changes with it; when the implementation has a bug, the test has
12
+ the same bug. It passes when the system is broken in the same way the test
13
+ is broken. It catches nothing.
14
+
15
+ The fix: tests must assert on **what the system promises a user**, not on
16
+ **how the system computes**. The promise is the invariant. If you can't state
17
+ the invariant in one English sentence without referring to function names or
18
+ internal modules, you're describing implementation, not contract.
19
+
20
+ ## The five tautology patterns
21
+
22
+ ### 1. Mock-and-assert-the-mock
23
+
24
+ The test sets up a mock, calls the system, then asserts the mock was called
25
+ in a specific way. There is no real I/O, no real outcome, no real user. The
26
+ test verifies that the wiring of mocks matches what the test author wrote
27
+ into the mocks.
28
+
29
+ ```python
30
+ # Tautological
31
+ mock_db.save.return_value = {"id": 1}
32
+ result = service.create_user("alice")
33
+ assert mock_db.save.called_with(name="alice") # asserting on the mock
34
+ ```
35
+
36
+ ```python
37
+ # Non-tautological — real DB, real outcome
38
+ result = service.create_user("alice") # writes to a real Postgres container
39
+ row = db.execute("SELECT name FROM users WHERE id = ?", result.id).fetchone()
40
+ assert row["name"] == "alice" # asserting on user-visible state
41
+ ```
42
+
43
+ **Grep signal:** `assert.*called_with`, `expect.*toHaveBeenCalled`,
44
+ `verify(mock`, `mock.assert_called`. Every match is a candidate for audit.
45
+
46
+ ### 2. Mirror-the-implementation
47
+
48
+ The test reads as a step-by-step re-implementation of the function being
49
+ tested. The assertions check that each internal step produced an expected
50
+ intermediate value.
51
+
52
+ ```python
53
+ # Tautological — testing the algorithm by re-running the algorithm
54
+ def test_calculate_total():
55
+ items = [{"price": 10, "qty": 2}, {"price": 5, "qty": 3}]
56
+ expected = items[0]["price"] * items[0]["qty"] + items[1]["price"] * items[1]["qty"]
57
+ assert calculate_total(items) == expected
58
+ ```
59
+
60
+ The `expected` value is computed by re-implementing `calculate_total`. If
61
+ the function is wrong, the test is wrong the same way.
62
+
63
+ ```python
64
+ # Non-tautological — concrete expected value
65
+ def test_calculate_total():
66
+ items = [{"price": 10, "qty": 2}, {"price": 5, "qty": 3}]
67
+ assert calculate_total(items) == 35 # the contract: this input → this output
68
+ ```
69
+
70
+ **Grep signal:** Tests where `expected` is a computed expression involving
71
+ the same operations as the function under test. Harder to grep mechanically;
72
+ audit sub-agent reads test bodies looking for this shape.
73
+
74
+ ### 3. Assertion-free tests
75
+
76
+ The test runs the system but never asserts anything substantive. It "passes"
77
+ as long as nothing throws. Most common in golden-path tests written under
78
+ deadline pressure.
79
+
80
+ ```javascript
81
+ // Tautological — runs the code, asserts nothing
82
+ test('checkout works', async () => {
83
+ await page.goto('/checkout');
84
+ await page.click('#submit');
85
+ });
86
+ ```
87
+
88
+ The test fails only if `goto` or `click` themselves throw. It passes when
89
+ the checkout silently submits a corrupt order.
90
+
91
+ ```javascript
92
+ // Non-tautological
93
+ test('checkout creates an order and redirects to confirmation', async () => {
94
+ await page.goto('/checkout');
95
+ await page.click('#submit');
96
+ await expect(page).toHaveURL(/\/confirmation\?order=\d+/);
97
+ const orderId = (await page.url()).match(/order=(\d+)/)[1];
98
+ const order = await db.query('SELECT * FROM orders WHERE id = ?', orderId);
99
+ expect(order.status).toBe('placed');
100
+ });
101
+ ```
102
+
103
+ **Grep signal:** test bodies with no `assert` / `expect` / `should` / `must`
104
+ calls. Also tests whose only assertion is a `toBeTruthy()` / `toBeDefined()`
105
+ on the call's return value with no value check.
106
+
107
+ ### 4. No-op golden path
108
+
109
+ The test sets up a happy path but the system under test does nothing
110
+ meaningful before the assertion fires. Common when test data accidentally
111
+ matches the empty / default response.
112
+
113
+ ```python
114
+ # Tautological — empty list assertion when list was never populated
115
+ def test_list_users_returns_users():
116
+ response = client.get("/users")
117
+ assert isinstance(response.json(), list) # passes for []
118
+ ```
119
+
120
+ If the database was never seeded, `[]` satisfies this test. The system could
121
+ be returning the empty list because it crashed, because it has a bug, or
122
+ because everything works — the test can't distinguish.
123
+
124
+ ```python
125
+ # Non-tautological — seeded data with specific assertion
126
+ def test_list_users_returns_seeded_users():
127
+ seed_user(name="alice")
128
+ seed_user(name="bob")
129
+ response = client.get("/users")
130
+ names = [u["name"] for u in response.json()]
131
+ assert set(names) == {"alice", "bob"}
132
+ ```
133
+
134
+ **Grep signal:** Hard to detect mechanically. Audit sub-agent reads tests
135
+ where the assertion target could be satisfied by an empty/default response.
136
+
137
+ ### 5. Snapshot-only
138
+
139
+ The test captures a snapshot and asserts the system matches it. When the
140
+ implementation changes, the snapshot gets regenerated. No semantic invariant
141
+ is ever stated.
142
+
143
+ ```javascript
144
+ // Tautological — the snapshot becomes whatever the code produces
145
+ test('user serialization', () => {
146
+ const user = serialize(buildUser());
147
+ expect(user).toMatchSnapshot();
148
+ });
149
+ ```
150
+
151
+ When the serialization changes to drop a critical field, the snapshot updates
152
+ and the test still passes.
153
+
154
+ ```javascript
155
+ // Non-tautological — semantic assertions
156
+ test('user serialization includes id, name, email and never includes password', () => {
157
+ const user = serialize(buildUser({ password: 'secret' }));
158
+ expect(user).toHaveProperty('id');
159
+ expect(user).toHaveProperty('name');
160
+ expect(user).toHaveProperty('email');
161
+ expect(user).not.toHaveProperty('password');
162
+ });
163
+ ```
164
+
165
+ **Grep signal:** `toMatchSnapshot`, `assertSnapshot`, `.snap` files. Audit
166
+ flags every snapshot test for human review.
167
+
168
+ ## Mock-boundary violation patterns
169
+
170
+ Distinct from tautology but in the same audit pass. These flag tests that
171
+ mock at the wrong boundary.
172
+
173
+ ### In-process mocks where a service-level mock exists
174
+
175
+ ```python
176
+ # Violation — mocking the DB driver
177
+ @patch("psycopg2.connect")
178
+ def test_create_user(mock_connect):
179
+ ...
180
+
181
+ # Fix — Testcontainers, real Postgres
182
+ def test_create_user(postgres_container):
183
+ ...
184
+ ```
185
+
186
+ **Grep signal:** `@patch.*psycopg`, `@patch.*boto3`, `@patch.*requests`,
187
+ `jest.mock("aws-sdk")`, `sinon.stub.*http`. Every match is a candidate for
188
+ promotion to service-level.
189
+
190
+ ### In-process mocks of code the system-under-test owns
191
+
192
+ The system mocks its own internal modules, then asserts the integration. The
193
+ "integration" never integrates.
194
+
195
+ ```python
196
+ # Violation — mocking your own service
197
+ @patch("app.services.payment.charge")
198
+ def test_checkout(mock_charge):
199
+ mock_charge.return_value = {"status": "ok"}
200
+ ...
201
+ ```
202
+
203
+ **Grep signal:** `@patch("app.` / `@patch("src.` / `jest.mock("./` /
204
+ `jest.mock("@/`. Mocking internal modules in e2e tests is almost always
205
+ wrong — the test should let the internal modules run.
206
+
207
+ ### Time-based mocks where libfaketime would work
208
+
209
+ In-process time mocking (freezegun, sinon.useFakeTimers) is OK for unit tests
210
+ but disqualifies an e2e test. Use libfaketime via `LD_PRELOAD` to skew the
211
+ clock for the entire container.
212
+
213
+ **Grep signal:** `freeze_time`, `freezegun`, `useFakeTimers`, `sinon.clock`.
214
+
215
+ ## Audit-mode heuristics
216
+
217
+ The `--audit` sub-agent runs these ripgrep passes over the test directory
218
+ and reports findings:
219
+
220
+ ```bash
221
+ # 1. Mock-and-assert-the-mock
222
+ rg -n 'assert.*called_with|expect.*toHaveBeenCalled|verify\(.*mock' tests/
223
+
224
+ # 2. Internal-module mocking
225
+ rg -n '@patch\("(app|src)\.|jest\.mock\("(\.|@/)' tests/
226
+
227
+ # 3. AWS / DB driver mocks (should be service-level)
228
+ rg -n '@patch.*\b(boto3|psycopg|pymongo|redis)|jest\.mock\("(aws-sdk|pg|mongodb|ioredis)"' tests/
229
+
230
+ # 4. Time mocking (should be libfaketime in e2e)
231
+ rg -n 'freeze_time|freezegun|useFakeTimers|sinon\.useFakeTimers' tests/
232
+
233
+ # 5. Snapshot tests
234
+ rg -n 'toMatchSnapshot|assertSnapshot' tests/ ; find tests/ -name '*.snap'
235
+
236
+ # 6. Assertion-free tests (heuristic — fewer assertions than expected)
237
+ # Sub-agent inspects per-test body; no clean grep available
238
+
239
+ # 7. HTTP request mocks (should be WireMock / service-level)
240
+ rg -n 'requests_mock|nock\(|httpretty|jest\.mock\("axios"' tests/
241
+ ```
242
+
243
+ Each match is a **candidate**, not a confirmed finding. The sub-agent reads
244
+ the matched test to confirm before filing an item.
245
+
246
+ ## Writing the invariant
247
+
248
+ Every test in a design must have a one-line invariant stated in user-visible
249
+ terms. Examples:
250
+
251
+ - "After registration → login → POST /projects, the new project ID appears
252
+ in GET /dashboard"
253
+ - "When the DB is unreachable, POST /projects returns 503 with Retry-After
254
+ set"
255
+ - "parse(serialize(req)) returns a request semantically equal to req for all
256
+ valid requests"
257
+ - "Checkout succeeds within 3 retries when the payment gateway has 500ms
258
+ latency; exactly one order row is created"
259
+
260
+ If you can't write the invariant, the test is tautological. Stop and rethink
261
+ what user-visible promise the test is supposed to verify.
@@ -0,0 +1,234 @@
1
+ # Service-Level Mocks Catalog
2
+
3
+ Reference for `e2e-test-design` SKILL.md Phase 5a. Picks come from this
4
+ catalog. If a dep type isn't here, the design must either find a service-level
5
+ substitute, build a **custom mock container** (per the policy at the bottom),
6
+ or carry a strong written justification for an in-process mock.
7
+
8
+ ## Mock-ladder principle
9
+
10
+ 1. **Off-the-shelf service mock** (preferred) — listed below by dep type
11
+ 2. **Custom mock container** (fallback) — language-agnostic patterns at the bottom
12
+ 3. **In-process mock** (last resort) — requires strong justification documented in the feature body
13
+
14
+ Audit mode actively promotes in-process mocks to custom containers wherever
15
+ feasible. Don't accept an in-process mock just because it's already there.
16
+
17
+ ## Catalog by dependency type
18
+
19
+ ### Cloud (AWS)
20
+
21
+ **LocalStack** (`localstack/localstack`) — emulates ~30 AWS services
22
+ (S3, DynamoDB, SQS, SNS, Lambda, IAM, Cognito, Secrets Manager, KMS, STS,
23
+ API Gateway, EventBridge, Step Functions, CloudWatch, Kinesis, SES, SSM, etc.).
24
+
25
+ ```yaml
26
+ services:
27
+ localstack:
28
+ image: localstack/localstack:latest
29
+ environment:
30
+ SERVICES: s3,sqs,dynamodb
31
+ AWS_DEFAULT_REGION: us-east-1
32
+ ports: ["4566:4566"]
33
+ healthcheck:
34
+ test: ["CMD", "curl", "-f", "http://localhost:4566/_localstack/health"]
35
+ ```
36
+
37
+ Use the `awslocal` CLI or point AWS SDK clients at the endpoint URL.
38
+
39
+ ### Cloud (GCP)
40
+
41
+ No single-image equivalent. Per-service options:
42
+ - **Cloud Storage** → fake-gcs-server (`fsouza/fake-gcs-server`)
43
+ - **Pub/Sub** → official emulator image (`gcr.io/google.com/cloudsdktool/cloud-sdk`)
44
+ - **Firestore / Datastore** → official emulator
45
+ - **BigQuery** → BigQuery emulator (community)
46
+
47
+ ### Cloud (Azure)
48
+
49
+ - **Storage (blob/queue/table)** → Azurite (`mcr.microsoft.com/azure-storage/azurite`)
50
+ - **Cosmos DB** → Cosmos DB Linux emulator (Microsoft official)
51
+ - **Event Hubs / Service Bus** → no first-party emulator; consider a custom mock container
52
+
53
+ ### Relational databases
54
+
55
+ **Testcontainers** (`testcontainers.com`) — most languages have bindings;
56
+ spins up real database containers per test session.
57
+
58
+ | Database | Image |
59
+ |---|---|
60
+ | Postgres | `postgres:<version>` |
61
+ | MySQL | `mysql:<version>` |
62
+ | MariaDB | `mariadb:<version>` |
63
+ | MS SQL Server | `mcr.microsoft.com/mssql/server` |
64
+ | Oracle Free | `gvenzl/oracle-free` |
65
+ | CockroachDB | `cockroachdb/cockroach` |
66
+
67
+ Use real schema migrations; seed with deterministic fixtures.
68
+
69
+ ### NoSQL / KV / cache
70
+
71
+ | Service | Image |
72
+ |---|---|
73
+ | MongoDB | `mongo:<version>` |
74
+ | Redis | `redis:<version>` |
75
+ | DynamoDB local | `amazon/dynamodb-local` (or via LocalStack) |
76
+ | Cassandra | `cassandra:<version>` |
77
+ | ScyllaDB | `scylladb/scylla` |
78
+ | Elasticsearch | `elasticsearch:<version>` |
79
+ | OpenSearch | `opensearchproject/opensearch` |
80
+
81
+ ### HTTP APIs (third-party services)
82
+
83
+ | Tool | When to use |
84
+ |---|---|
85
+ | **WireMock** (`wiremock/wiremock`) | OpenAPI-driven mocks; record/replay; configurable response delays and failure injection |
86
+ | **Mockoon** (`mockoon/cli`) | GUI-built mock spec; export to docker |
87
+ | **Prism** (`stoplight/prism`) | Mocks directly from an OpenAPI/Swagger document |
88
+ | **MockServer** (`mockserver/mockserver`) | Programmatic mock expectations via REST or Java client |
89
+
90
+ WireMock is the default workhorse — it handles record-and-replay, stateful
91
+ scenarios, and is fully configurable as a docker service.
92
+
93
+ ### Email (SMTP)
94
+
95
+ | Service | Image | Notes |
96
+ |---|---|---|
97
+ | **MailHog** | `mailhog/mailhog` | SMTP sink + HTTP UI for inspecting sent mail |
98
+ | **smtp4dev** | `rnwood/smtp4dev` | Similar, with .NET-friendly features |
99
+ | **MailCatcher** | `schickling/mailcatcher` | Lightweight |
100
+
101
+ ### Object storage (S3-compatible)
102
+
103
+ | Service | Image |
104
+ |---|---|
105
+ | **MinIO** | `minio/minio` |
106
+ | LocalStack S3 | `localstack/localstack` (S3 service enabled) |
107
+
108
+ ### Message queues / streaming
109
+
110
+ | Service | Image |
111
+ |---|---|
112
+ | Kafka (single-node, fast) | `redpandadata/redpanda` (Kafka-compatible, faster startup) |
113
+ | Kafka (official) | `apache/kafka` |
114
+ | RabbitMQ | `rabbitmq:<version>-management` |
115
+ | NATS | `nats:<version>` |
116
+ | MQTT | `eclipse-mosquitto` |
117
+
118
+ ### Auth / identity
119
+
120
+ | Service | Image | When |
121
+ |---|---|---|
122
+ | **Keycloak** | `quay.io/keycloak/keycloak` | OAuth2/OIDC/SAML — full IdP |
123
+ | **dex** | `ghcr.io/dexidp/dex` | Lighter OIDC provider |
124
+ | **mock-oauth2-server** | `ghcr.io/navikt/mock-oauth2-server` | Minimal OIDC for tests |
125
+
126
+ ### Network failure injection
127
+
128
+ | Tool | Image / Binary | Capabilities |
129
+ |---|---|---|
130
+ | **Toxiproxy** | `ghcr.io/shopify/toxiproxy` | latency, bandwidth, jitter, slow-close, down, limit-data, slicer toxics |
131
+ | **Pumba** | `gaiaadm/pumba` | container kill, pause, stop, rm; netem-based netem injection |
132
+ | **tc / netem** | host kernel | direct Linux traffic-control rules |
133
+
134
+ ### Time / clock
135
+
136
+ | Tool | Approach |
137
+ |---|---|
138
+ | **libfaketime** | `LD_PRELOAD` shim; overrides syscalls; works inside containers |
139
+ | Frozen-time test helpers | language-level (freezegun in Python, sinon in JS) — these are in-process mocks; prefer libfaketime for true e2e |
140
+
141
+ ### Browsers / E2E UI
142
+
143
+ Not "service mocks" exactly, but commonly part of the same stack:
144
+
145
+ | Tool | Notes |
146
+ |---|---|
147
+ | **Playwright** | Multi-browser; can run headless in CI; built-in trace viewer |
148
+ | **Selenium Grid** | Multi-node; mature; heavier |
149
+ | Cypress (note: in-process) | Useful but its same-origin and in-process design means it's NOT an e2e tool by the strict definition here |
150
+
151
+ ### Search
152
+
153
+ | Service | Image |
154
+ |---|---|
155
+ | Elasticsearch | `elasticsearch:<version>` |
156
+ | OpenSearch | `opensearchproject/opensearch` |
157
+ | Meilisearch | `getmeili/meilisearch` |
158
+ | Typesense | `typesense/typesense` |
159
+
160
+ ### Background job systems
161
+
162
+ Most are layered on a queue/DB and don't need a separate mock — bring up the
163
+ underlying Redis/Postgres/RabbitMQ and the job system runs against it
164
+ naturally.
165
+
166
+ ## Building a custom mock container
167
+
168
+ When no off-the-shelf service-level mock exists for a dependency, design a
169
+ purpose-built container. **Do not default to a single framework or language**
170
+ — that's how mock containers become alien to the project they're embedded in.
171
+
172
+ ### Picking the framework
173
+
174
+ In order:
175
+
176
+ 1. **Match the upstream service's actual tech.** If you're mocking an internal
177
+ Rails API, a Rails skeleton is the most faithful substitute — the same
178
+ serialization quirks, the same routing semantics. If the upstream is a Go
179
+ gRPC service, mock it in Go with the same `.proto` files.
180
+
181
+ 2. **Match the project's primary stack.** If the project is a TypeScript
182
+ monorepo, a TypeScript mock is one less language to maintain. If the
183
+ project is Python, the mock is Python.
184
+
185
+ 3. **Ask the user.** When neither rule suggests an obvious choice (e.g., a
186
+ greenfield project mocking a third-party SaaS), surface the decision via
187
+ `structured question tool`.
188
+
189
+ ### Container shape
190
+
191
+ Regardless of language, a custom mock container should:
192
+
193
+ - Be **stateless** between test runs (state in memory or a wiped volume)
194
+ - Be **deterministic** — same input → same output, no randomness without a
195
+ seed knob
196
+ - Expose a **healthcheck endpoint** for docker-compose `depends_on:
197
+ condition: service_healthy`
198
+ - Expose a **reset endpoint** (`POST /__reset`) the test fixture calls between
199
+ tests to wipe state
200
+ - Expose **inspection endpoints** (`GET /__received`) so tests can assert what
201
+ the system-under-test sent
202
+ - Read **scenario config** from a file or env var so different tests can
203
+ configure different failure modes (slow, error, partial response, etc.)
204
+
205
+ ### Patterns
206
+
207
+ **Record-and-replay** — when you have access to the real upstream, capture
208
+ real traffic (WireMock's `record` mode, or a small proxy) and replay it in
209
+ tests. Best for stable, read-heavy upstreams.
210
+
211
+ **Configurable WireMock** — for HTTP-only deps, a WireMock container with
212
+ mounted mappings JSON often replaces a hand-written custom server. Less code
213
+ to maintain.
214
+
215
+ **Mini-server with deterministic logic** — when the upstream has nontrivial
216
+ behavior (state machines, calculations, side effects), a small server in the
217
+ matched language is right. Keep the surface tight: only the endpoints the
218
+ system-under-test actually calls.
219
+
220
+ **Hybrid (real service + injected behavior)** — sometimes the upstream IS
221
+ something you can run in docker (e.g., your own service) but you need to
222
+ inject failure. Toxiproxy in front of the real service is the right tool;
223
+ don't build a custom mock.
224
+
225
+ ### Anti-patterns
226
+
227
+ - Building a Python mock for a Java project just because Python is "easier" —
228
+ the project ends up maintaining a Python toolchain solely for tests
229
+ - Reimplementing the upstream's full surface — only mock what the
230
+ system-under-test actually calls
231
+ - Mock containers that drift from the real upstream's behavior without anyone
232
+ noticing — pin to a real-upstream contract test where possible
233
+ - Mock containers that share state between tests — every test should start
234
+ clean via `__reset`