@ai-support-agent/cli 0.1.32-beta.1 → 0.1.33-beta.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 (62) hide show
  1. package/dist/commands/claude-code-args.d.ts +1 -0
  2. package/dist/commands/claude-code-args.d.ts.map +1 -1
  3. package/dist/commands/claude-code-args.js +3 -0
  4. package/dist/commands/claude-code-args.js.map +1 -1
  5. package/dist/commands/claude-code-runner.d.ts.map +1 -1
  6. package/dist/commands/claude-code-runner.js +2 -1
  7. package/dist/commands/claude-code-runner.js.map +1 -1
  8. package/dist/commands/plugin-dir.d.ts +20 -0
  9. package/dist/commands/plugin-dir.d.ts.map +1 -0
  10. package/dist/commands/plugin-dir.js +71 -0
  11. package/dist/commands/plugin-dir.js.map +1 -0
  12. package/dist/plugin/.claude-plugin/plugin.json +9 -0
  13. package/dist/plugin/LICENSE +26 -0
  14. package/dist/plugin/README.md +86 -0
  15. package/dist/plugin/SYNC.md +113 -0
  16. package/dist/plugin/agents/build-error-resolver.md +159 -0
  17. package/dist/plugin/agents/code-reviewer.md +277 -0
  18. package/dist/plugin/agents/django-reviewer.md +159 -0
  19. package/dist/plugin/agents/infra-reviewer.md +172 -0
  20. package/dist/plugin/agents/investigator.md +75 -0
  21. package/dist/plugin/agents/nextjs-reviewer.md +191 -0
  22. package/dist/plugin/agents/php-reviewer.md +167 -0
  23. package/dist/plugin/agents/planner.md +184 -0
  24. package/dist/plugin/agents/python-reviewer.md +161 -0
  25. package/dist/plugin/agents/react-reviewer.md +150 -0
  26. package/dist/plugin/agents/silent-failure-hunter.md +158 -0
  27. package/dist/plugin/agents/typescript-reviewer.md +179 -0
  28. package/dist/plugin/agents/ui-reviewer.md +203 -0
  29. package/dist/plugin/commands/add-feature.md +301 -0
  30. package/dist/plugin/commands/build-fix.md +47 -0
  31. package/dist/plugin/commands/code-review.md +228 -0
  32. package/dist/plugin/commands/fix-defect.md +393 -0
  33. package/dist/plugin/commands/learn-eval.md +94 -0
  34. package/dist/plugin/commands/learn.md +84 -0
  35. package/dist/plugin/commands/plan.md +211 -0
  36. package/dist/plugin/commands/test-coverage.md +64 -0
  37. package/dist/plugin/commands/update-docs.md +98 -0
  38. package/dist/plugin/hooks/hooks.json +59 -0
  39. package/dist/plugin/hooks/scripts/auto-format.sh +63 -0
  40. package/dist/plugin/hooks/scripts/check-secrets-before-commit.sh +50 -0
  41. package/dist/plugin/hooks/scripts/guard-dangerous-commands.sh +55 -0
  42. package/dist/plugin/hooks/scripts/on-command-resume.sh +112 -0
  43. package/dist/plugin/hooks/scripts/on-command-stop.sh +200 -0
  44. package/dist/plugin/hooks/scripts/protect-sensitive-files.sh +58 -0
  45. package/dist/plugin/rules/common/coding-guidelines.md +73 -0
  46. package/dist/plugin/rules/documentation/api-docs.md +46 -0
  47. package/dist/plugin/rules/documentation/docs-site.md +60 -0
  48. package/dist/plugin/rules/documentation/source-docs.md +89 -0
  49. package/dist/plugin/rules/documentation/test-docs.md +39 -0
  50. package/dist/plugin/rules/logging/logging-rules.md +83 -0
  51. package/dist/plugin/rules/php/coding-rules.md +40 -0
  52. package/dist/plugin/rules/python/coding-rules.md +40 -0
  53. package/dist/plugin/rules/typescript/coding-rules.md +45 -0
  54. package/dist/plugin/skills/api-design/SKILL.md +269 -0
  55. package/dist/plugin/skills/backend-patterns/SKILL.md +312 -0
  56. package/dist/plugin/skills/database-migrations/SKILL.md +323 -0
  57. package/dist/plugin/skills/docker-patterns/SKILL.md +308 -0
  58. package/dist/plugin/skills/docs-site/SKILL.md +341 -0
  59. package/dist/plugin/skills/e2e-testing/SKILL.md +334 -0
  60. package/dist/plugin/skills/frontend-patterns/SKILL.md +318 -0
  61. package/dist/plugin/skills/integration-testing/SKILL.md +273 -0
  62. package/package.json +2 -2
@@ -0,0 +1,318 @@
1
+ ---
2
+ name: frontend-patterns
3
+ description: A pattern collection for component design, state management, performance, forms, error handling, and accessibility in React (Next.js App Router) and Vue.js (Composition API). Use when creating or refactoring frontend components, deciding on a state management or data-fetching approach, or checking what to look for when reviewing frontend code.
4
+ ---
5
+
6
+ # Frontend Development Patterns
7
+
8
+ This guide centers on React (Next.js App Router), with the equivalent Vue.js (Composition API)
9
+ pattern noted alongside each topic. When in doubt, follow the principle of "start simple, add
10
+ complexity only when you actually need it."
11
+
12
+ ## 1. Component Design
13
+
14
+ ### Splitting responsibilities
15
+
16
+ Aim for one responsibility per component. If you can't describe what a component does in a
17
+ single sentence, consider splitting it.
18
+
19
+ - Separate presentation (display) from data fetching and state management (container).
20
+ - In the Next.js App Router, fetch data in Server Components and carve out only the
21
+ interactive parts into small `"use client"` Client Components.
22
+ - In Vue, once the logic inside `setup` starts spanning multiple areas of the screen, split it
23
+ into child components or composables.
24
+
25
+ ### Signs a component has grown too large
26
+
27
+ If several of these apply, it's a signal to split the component:
28
+
29
+ - The file is over 300 lines, or the JSX/template is over 100 lines.
30
+ - Seven or more `useState`/`ref` calls are lined up.
31
+ - Boolean props (`isCompact`, `showHeader`, etc.) keep growing and their conditional branches
32
+ start interacting with each other.
33
+ - The component performs multiple unrelated data fetches.
34
+ - Writing a test for it requires mocking a pile of unrelated functionality.
35
+
36
+ ### Composing with children / slots
37
+
38
+ Express variants through composition rather than piling on flag props.
39
+
40
+ ```tsx
41
+ // Bad: variants expressed as a growing set of flag props
42
+ <Card title="Sales" showIcon iconType="chart" footerText="Details" footerLink="/sales" />
43
+
44
+ // Good: composition via children lets the caller decide the structure
45
+ <Card>
46
+ <Card.Header icon={<ChartIcon />}>Sales</Card.Header>
47
+ <Card.Body>{children}</Card.Body>
48
+ <Card.Footer><Link href="/sales">Details</Link></Card.Footer>
49
+ </Card>
50
+ ```
51
+
52
+ ```vue
53
+ <!-- In Vue, named slots express the same structure -->
54
+ <Card>
55
+ <template #header>Sales</template>
56
+ <template #default>...</template>
57
+ <template #footer><RouterLink to="/sales">Details</RouterLink></template>
58
+ </Card>
59
+ ```
60
+
61
+ ## 2. Reusing Logic (Custom Hooks / Composables)
62
+
63
+ ### When to extract
64
+
65
+ - The same combination of state and effects shows up in two or more places.
66
+ - There's logic you want to unit test independently of the component.
67
+ - You can give it a name that describes what it does (`useDebounce`, `usePagination`, etc.).
68
+
69
+ Conversely, don't force an extraction for logic that's used in only one place and is hard to
70
+ name meaningfully.
71
+
72
+ ```tsx
73
+ // Good: extract the state + effect boilerplate into a hook (React)
74
+ function useDebouncedValue<T>(value: T, delayMs = 300): T {
75
+ const [debounced, setDebounced] = useState(value);
76
+ useEffect(() => {
77
+ const id = setTimeout(() => setDebounced(value), delayMs);
78
+ return () => clearTimeout(id);
79
+ }, [value, delayMs]);
80
+ return debounced;
81
+ }
82
+ ```
83
+
84
+ ```ts
85
+ // Good: the same logic implemented as a Vue composable
86
+ export function useDebouncedValue<T>(source: Ref<T>, delayMs = 300) {
87
+ const debounced = ref(source.value) as Ref<T>;
88
+ let id: ReturnType<typeof setTimeout>;
89
+ watch(source, (v) => {
90
+ clearTimeout(id);
91
+ id = setTimeout(() => (debounced.value = v), delayMs);
92
+ });
93
+ onUnmounted(() => clearTimeout(id));
94
+ return debounced;
95
+ }
96
+ ```
97
+
98
+ - Hooks/composables should not return UI. If the reusable unit involves UI, make it a
99
+ component instead.
100
+ - Return an object so callers can destructure only what they need.
101
+
102
+ ## 3. State Management
103
+
104
+ ### Where state should live: priority order
105
+
106
+ 1. Local state (`useState` / `ref`). Always start here.
107
+ 2. Lift state up to a parent, once it needs to be shared between siblings.
108
+ 3. Context (`createContext` / `provide` and `inject`), once passing props down deeply becomes
109
+ painful.
110
+ 4. A global store (Zustand / Pinia), once it meets the criteria below.
111
+
112
+ ### When a global store is warranted
113
+
114
+ - State that outlives a single screen (authenticated user, theme, cart, etc.).
115
+ - State that must survive route changes without being discarded.
116
+ - State that's read and written by many far-apart components.
117
+
118
+ If the only issue is "props get passed through 2-3 levels," component composition or context
119
+ can usually solve it — that alone isn't a reason to introduce a store.
120
+
121
+ ### Let a data-fetching library own server state
122
+
123
+ Don't hand-roll caching, refetching, and loading-state management for API responses. Delegate
124
+ to SWR or TanStack Query (React Query / Vue Query).
125
+
126
+ ```tsx
127
+ // Bad: manually managing server data with useEffect + a global store
128
+ useEffect(() => {
129
+ fetch("/api/users").then((r) => r.json()).then((d) => store.setUsers(d));
130
+ }, []);
131
+
132
+ // Good: delegate to a data-fetching library (caching, revalidation, and dedup included)
133
+ const { data, error, isLoading } = useQuery({
134
+ queryKey: ["users"],
135
+ queryFn: fetchUsers,
136
+ });
137
+ ```
138
+
139
+ - Reserve the global store for genuinely client-only state.
140
+ - Copying server data into a store means you end up reimplementing freshness tracking and
141
+ invalidation yourself.
142
+
143
+ ## 4. Derived State
144
+
145
+ Values that can be computed from existing state should be derived during render (React) or via
146
+ `computed` (Vue) — don't duplicate them into a separate piece of state via an effect or watcher.
147
+
148
+ ```tsx
149
+ // Bad: duplicating a derived value into separate state via useEffect
150
+ // (lags a render behind and is a common source of bugs)
151
+ const [filtered, setFiltered] = useState<Item[]>([]);
152
+ useEffect(() => {
153
+ setFiltered(items.filter((i) => i.name.includes(query)));
154
+ }, [items, query]);
155
+
156
+ // Good: compute it during render; add useMemo only if profiling shows it's expensive
157
+ const filtered = items.filter((i) => i.name.includes(query));
158
+ ```
159
+
160
+ ```ts
161
+ // Bad: duplicating into a separate ref via watch (Vue)
162
+ watch([items, query], () => {
163
+ filtered.value = items.value.filter((i) => i.name.includes(query.value));
164
+ });
165
+
166
+ // Good: derive it declaratively with computed
167
+ const filtered = computed(() =>
168
+ items.value.filter((i) => i.name.includes(query.value))
169
+ );
170
+ ```
171
+
172
+ - Whenever you see "an effect that updates state B when state A changes," ask first whether B
173
+ can just be a derived value instead.
174
+ - Effects/watchers are legitimately for syncing with external systems (DOM manipulation,
175
+ subscriptions, sending logs, etc.) — not for mirroring state.
176
+
177
+ ## 5. Performance
178
+
179
+ ### Memoization discipline: measure first
180
+
181
+ - Check actual re-render cost with React DevTools Profiler / Vue DevTools before optimizing.
182
+ - Don't reflexively slap `useMemo` / `useCallback` / `memo` on everything — it costs
183
+ readability, and a wrong dependency array turns into a bug.
184
+ - It's worth adding when a computation is measurably expensive, or when referential stability
185
+ matters for a `memo`-ized child or a dependency array.
186
+ - In environments where the React Compiler or Vue's built-in optimizations are effective,
187
+ manual memoization can often be reduced further.
188
+
189
+ ### Code splitting and lazy loading
190
+
191
+ - Route-level splitting is handled automatically by Next.js / Vue Router — rely on that first.
192
+ - Lazy-load heavy components that aren't needed on initial render (modals, charts, editors)
193
+ with `next/dynamic` or `defineAsyncComponent`.
194
+
195
+ ```tsx
196
+ // Good: lazily load a heavy component that's only needed once it's opened
197
+ const ChartPanel = dynamic(() => import("./ChartPanel"), { ssr: false });
198
+ ```
199
+
200
+ ### Virtualizing long lists
201
+
202
+ Don't render every row of a list with hundreds of items — render only the visible range with
203
+ `@tanstack/react-virtual` / `@tanstack/vue-virtual` or similar. Before reaching for
204
+ virtualization, first consider whether pagination or a "load more" pattern can simply reduce
205
+ the item count.
206
+
207
+ ## 6. Forms
208
+
209
+ ### Schema-based validation
210
+
211
+ Define validation rules as a schema (Zod, Valibot, etc.) and wire it into your form library
212
+ (React Hook Form / VeeValidate). Re-validate with the same schema on the server.
213
+
214
+ ```tsx
215
+ // Good: the schema is the single source of truth for validation rules
216
+ const schema = z.object({
217
+ email: z.string().email("Please enter a valid email address"),
218
+ age: z.coerce.number().int().min(18, "Must be 18 or older"),
219
+ });
220
+
221
+ const { register, handleSubmit, formState } = useForm({
222
+ resolver: zodResolver(schema),
223
+ });
224
+ ```
225
+
226
+ - Display error messages near the relevant field and associate them with the input via
227
+ `aria-describedby`.
228
+ - Don't hardcode validation logic inline in JSX or templates.
229
+
230
+ ### Submission state and preventing double submits
231
+
232
+ ```tsx
233
+ // Bad: no submission-state guard, so rapid clicks trigger duplicate POSTs
234
+ <button onClick={() => submit(values)}>Submit</button>
235
+
236
+ // Good: disable the button while submitting and show the state to the user
237
+ const { isSubmitting } = formState;
238
+ <button type="submit" disabled={isSubmitting}>
239
+ {isSubmitting ? "Submitting..." : "Submit"}
240
+ </button>
241
+ ```
242
+
243
+ - Using `useMutation` from React Query / Vue Query for mutations standardizes handling of
244
+ `isPending`, errors, and retries.
245
+ - After a successful submission, reset the form or navigate away so the user isn't left in a
246
+ state where they could resubmit the same data.
247
+
248
+ ## 7. Error Handling
249
+
250
+ ### Error boundaries / onErrorCaptured
251
+
252
+ - React: place an error boundary around each layout region so one failure doesn't blank out
253
+ the whole screen. In the Next.js App Router, add an `error.tsx` per segment.
254
+ - Vue: catch descendant rendering errors in a parent's `onErrorCaptured` and switch to a
255
+ fallback UI.
256
+ - Never swallow the error silently in the boundary — always report it to your error monitoring
257
+ service.
258
+
259
+ ### The three states of a data-fetching UI
260
+
261
+ Always explicitly handle the three states: loading, error, and empty (zero results).
262
+
263
+ ```tsx
264
+ // Good: handle all three states before rendering the real content
265
+ if (isLoading) return <Spinner />;
266
+ if (error) return <ErrorMessage error={error} onRetry={refetch} />;
267
+ if (data.length === 0) return <EmptyState message="No data available" />;
268
+ return <UserList users={data} />;
269
+ ```
270
+
271
+ - Give the error state a retry action (a retry button).
272
+ - Word the empty state so it clearly reads as "not an error," and point toward a next action
273
+ (e.g., create new).
274
+ - To avoid flicker, consider a skeleton UI or `placeholderData` (keeping the previous data
275
+ visible while refetching).
276
+
277
+ ## 8. Accessibility
278
+
279
+ ### Semantic elements
280
+
281
+ - Use `button` / `a` for anything clickable. Don't attach `onClick` to a `div`.
282
+ - Use `a` (`Link`) for navigation/URL changes and `button` for in-place actions.
283
+ - Keep heading levels in order starting from `h1`; don't pick a heading level for its visual
284
+ size.
285
+ - Use landmarks (`main`, `nav`, `header`, `footer`) to convey document structure.
286
+
287
+ ```tsx
288
+ // Bad: onClick on a div (unreachable via keyboard or assistive technology)
289
+ <div onClick={save}>Save</div>
290
+
291
+ // Good: use a native button (focus and Enter/Space work out of the box)
292
+ <button type="button" onClick={save}>Save</button>
293
+ ```
294
+
295
+ ### Labels
296
+
297
+ - Associate every form input with a `label` (`htmlFor` / `for`).
298
+ - Add `aria-label` to icon-only buttons.
299
+ - Image `alt` text should describe the content; use `alt=""` for purely decorative images.
300
+
301
+ ### Keyboard operation basics
302
+
303
+ - Everything should be reachable via Tab, operable with Enter/Space, and modals should close
304
+ with Esc.
305
+ - Don't remove the focus ring via CSS. If you do, provide an alternative visible style.
306
+ - When a modal opens, move focus into it, and restore focus to the triggering element when it
307
+ closes.
308
+ - Before building custom UI, consider whether a native element or a proven headless UI library
309
+ already covers the need.
310
+
311
+ ## Related
312
+
313
+ - For reviews, use the react-reviewer / typescript-reviewer subagents, or `/code-review`.
314
+ - For Next.js-specific concerns (Server/Client boundaries, Server Actions, caching, etc.), use
315
+ the nextjs-reviewer.
316
+ - For usability and consistency (UI/UX) concerns, use the ui-reviewer.
317
+ - Treat this skill as the decision criteria to apply during implementation and refactoring;
318
+ delegate the actual application of review criteria to the subagents listed above.
@@ -0,0 +1,273 @@
1
+ ---
2
+ name: integration-testing
3
+ description: A collection of integration-testing patterns for verifying actual database reads and writes. Covers the limits of mocking, isolating the test database, transaction rollback, and how to verify constraint violations, optimistic locking, and N+1 queries, for NestJS (TypeORM/Prisma), Django, Flask, Laravel, and CakePHP. Use this when writing or reviewing tests for DB-touching features, when you want to avoid a "green only because it's mocked" false sense of safety, or when designing how DB tests run in CI.
4
+ ---
5
+
6
+ # integration-testing: Integration Testing (DB Read/Write Verification) Patterns
7
+
8
+ ## 1. Why Integration Tests Are Necessary (The Limits of Mocking)
9
+
10
+ Unit tests that mock the repository or ORM never verify any of the following:
11
+
12
+ - Correctness of SQL or query-builder output (typo'd column names, wrong JOIN conditions)
13
+ - Consistency with the schema (missing migrations, type mismatches)
14
+ - DB constraint behavior (unique constraints, foreign keys, NOT NULL, CHECK)
15
+ - Transaction boundaries (commit/rollback timing)
16
+ - Optimistic locking and concurrency conflicts
17
+
18
+ ### The Classic Trap: Tests Are Green, but the DB Breaks
19
+
20
+ ```typescript
21
+ // Bad: the repository is mocked, so a unique-constraint violation is never caught
22
+ it('can register a user', async () => {
23
+ const repo = { save: jest.fn().mockResolvedValue({ id: 1 }) };
24
+ const service = new UserService(repo as any);
25
+ await service.register('taro@example.com');
26
+ await service.register('taro@example.com'); // the mock silently succeeds
27
+ expect(repo.save).toHaveBeenCalledTimes(2); // green, but in production the
28
+ }); // second call hits a unique-constraint 500 error
29
+ ```
30
+
31
+ ```typescript
32
+ // Good: register against a real DB and verify the behavior on duplicates
33
+ it('throws ConflictException when the email address is duplicated', async () => {
34
+ await service.register('taro@example.com');
35
+ await expect(service.register('taro@example.com'))
36
+ .rejects.toThrow(ConflictException); // confirms constraint-violation handling against a real DB
37
+ });
38
+ ```
39
+
40
+ As long as the mocked `save` keeps returning success, the test stays green forever — and a unit test will never catch "the second registration fails on the DB's unique constraint" or "the service layer never converts that exception into the right HTTP error." Those are exactly the failures that show up in production.
41
+
42
+ ## 2. Dividing Responsibilities Across Test Layers
43
+
44
+ | Layer | Target | DB | Responsibility |
45
+ |---|---|---|---|
46
+ | Unit | Pure logic (calculations, transforms, validation) | Not used | Branch coverage, edge cases |
47
+ | Integration | Repository/service layer, queries | Real DB | Correctness of DB-touching code |
48
+ | E2E | Business flows through the UI/API | Real DB (production-like) | User-facing scenarios |
49
+
50
+ - Verifying DB-touching code is the responsibility of integration tests. Pushing that burden onto E2E tests makes them slow and flaky.
51
+ - For E2E test structure and waiting strategies, see the e2e-testing skill.
52
+
53
+ ## 3. Core Principles for DB Testing
54
+
55
+ ### 3.1 Isolate the Test Database
56
+
57
+ Never share the development database. Test runs will destroy data if you do, so use a dedicated database (e.g. `myapp_test`) or a disposable container. Switch the connection target via environment variables, and verify "this is really the test connection" at test startup to prevent accidents.
58
+
59
+ ### 3.2 Keep Tests Independent of Each Other
60
+
61
+ No test may depend on data left behind by a previous test. There are two cleanup strategies:
62
+
63
+ - **Transaction rollback**: wrap each test in a transaction and roll it back at the end. Fast — but it can't verify code paths where the code under test explicitly commits, uses a separate connection, or relies on nested transactions.
64
+ - **Truncate/recreate**: truncate all tables between tests. Slower, but it also works for logic that involves an actual commit (i.e. testing the transaction boundary itself).
65
+
66
+ Rule of thumb: default to the rollback strategy, and switch to truncation only for the specific tests that verify transaction behavior or post-commit hooks.
67
+
68
+ ### 3.3 Use the Same DB Engine as Production (the SQLite-Substitute Trap)
69
+
70
+ "Let's just use SQLite for speed" is a reliable source of both false positives and false negatives.
71
+
72
+ - **Loose typing**: SQLite will happily store 100 characters in a `VARCHAR(10)`. Length-overflow bugs go undetected.
73
+ - **Constraints**: foreign keys are disabled by default, and CHECK constraints and some unique-constraint behaviors differ from production engines.
74
+ - **Functions and SQL dialect**: differences in `ILIKE`, `ON CONFLICT`, window functions, and date functions mean things break only in production, or only in tests.
75
+ - **Locking behavior**: SQLite locks the whole database rather than individual rows, so concurrency and deadlock tests can't meaningfully run against it.
76
+
77
+ Use Testcontainers or Docker Compose to spin up the same PostgreSQL/MySQL you run in production. The few seconds of startup cost is easily absorbed by reusing one container per test suite.
78
+
79
+ ### 3.4 Mock External APIs, but Never Mock the Database
80
+
81
+ The boundary for an integration test is: "things we own are real, things we don't own can be mocked." Payment gateways, email delivery, and external SaaS can reasonably be mocked (or stubbed). The database, however, is the very thing whose schema and constraints you're trying to verify — so it must never be mocked.
82
+
83
+ ## 4. What You Must Always Test
84
+
85
+ 1. **Read-back after write**: read the saved value back through a separate path (a repository `find`, or raw SQL) and confirm what was actually persisted. Checking only the return value of `save` will miss values that never made it to the DB — lost conversions, missing column mappings, and the like.
86
+ 2. **Behavior on constraint violations**: deliberately trigger unique-constraint, foreign-key, and NOT NULL violations, and confirm the application layer converts them into the right exception or error response.
87
+ 3. **Transaction atomicity**: force a failure partway through a multi-table update (e.g. the second of two writes violates a constraint), then read back the first write to confirm it was rolled back too.
88
+ 4. **Concurrency and optimistic locking**: fetch a versioned entity in two separate contexts, update one first, then update the other, and confirm a conflict exception is raised.
89
+ 5. **Complex queries**: for JOINs, aggregations, and pagination, insert real data that includes boundary conditions and verify the results (zero rows, exact page boundaries, stable sort ordering on duplicate keys).
90
+ 6. **N+1 detection**: assert on the query count, confirming that listing endpoints don't issue a number of queries proportional to the row count.
91
+
92
+ ## 5. Stack-Specific Implementation Patterns
93
+
94
+ ### 5.1 NestJS + TypeORM (Testcontainers)
95
+
96
+ ```typescript
97
+ // Good: spin up a real PostgreSQL via Testcontainers and verify against the real repository
98
+ import { PostgreSqlContainer, StartedPostgreSqlContainer } from '@testcontainers/postgresql';
99
+
100
+ let container: StartedPostgreSqlContainer;
101
+ let module: TestingModule;
102
+
103
+ beforeAll(async () => {
104
+ container = await new PostgreSqlContainer('postgres:16').start();
105
+ module = await Test.createTestingModule({
106
+ imports: [
107
+ TypeOrmModule.forRoot({
108
+ type: 'postgres',
109
+ url: container.getConnectionUri(),
110
+ entities: [User, Order],
111
+ migrations: ['migrations/*.ts'],
112
+ migrationsRun: true, // apply real migrations instead of `synchronize`
113
+ }),
114
+ UserModule, // use the real repository, not a mock
115
+ ],
116
+ }).compile();
117
+ }, 60_000);
118
+
119
+ afterEach(async () => {
120
+ // truncate strategy: needed because this suite tests transaction boundaries
121
+ await module.get(DataSource).query('TRUNCATE "user", "order" CASCADE');
122
+ });
123
+
124
+ it('rolls back the stock decrement when order confirmation fails partway through', async () => {
125
+ const service = module.get(OrderService);
126
+ await expect(service.confirm(orderWithInvalidItem)).rejects.toThrow();
127
+ const stock = await module.get(DataSource).getRepository(Stock).findOneBy({ sku: 'A' });
128
+ expect(stock.quantity).toBe(10); // the first decrement was rolled back too
129
+ });
130
+ ```
131
+
132
+ ### 5.2 NestJS + Prisma
133
+
134
+ ```typescript
135
+ // Good: point DATABASE_URL at the container and run `prisma migrate deploy`
136
+ beforeAll(async () => {
137
+ container = await new PostgreSqlContainer('postgres:16').start();
138
+ process.env.DATABASE_URL = container.getConnectionUri();
139
+ execSync('npx prisma migrate deploy'); // build the schema from real migrations
140
+ prisma = new PrismaClient();
141
+ });
142
+
143
+ it('reads back a saved value', async () => {
144
+ await service.createUser({ email: 'taro@example.com', name: 'Taro' });
145
+ // read back directly from the DB instead of trusting the return value of save
146
+ const row = await prisma.user.findUnique({ where: { email: 'taro@example.com' } });
147
+ expect(row?.name).toBe('Taro');
148
+ });
149
+
150
+ it('catches a unique-constraint violation as P2002 and handles it', async () => {
151
+ await service.createUser({ email: 'a@example.com', name: 'A' });
152
+ await expect(service.createUser({ email: 'a@example.com', name: 'B' }))
153
+ .rejects.toThrow(ConflictException);
154
+ });
155
+ ```
156
+
157
+ ### 5.3 Django + pytest-django
158
+
159
+ ```python
160
+ # Good: the django_db mark uses a real DB; by default each test is rolled back afterward
161
+ import pytest
162
+
163
+ @pytest.mark.django_db
164
+ def test_read_back_after_save():
165
+ Order.objects.create(code="A-001", total=1000)
166
+ row = Order.objects.get(code="A-001")
167
+ assert row.total == 1000
168
+
169
+ # Use transaction=True (equivalent to TransactionTestCase) when testing the
170
+ # transaction boundary itself — select_for_update and on_commit hooks only work here
171
+ @pytest.mark.django_db(transaction=True)
172
+ def test_full_rollback_on_partial_failure():
173
+ with pytest.raises(IntegrityError):
174
+ place_order_with_invalid_item()
175
+ assert Stock.objects.get(sku="A").quantity == 10
176
+
177
+ # N+1 detection: assert on the query count
178
+ def test_listing_uses_2_queries_regardless_of_row_count(django_assert_num_queries):
179
+ OrderFactory.create_batch(20)
180
+ with django_assert_num_queries(2): # pins down the effect of select_related / prefetch_related
181
+ list_orders_with_items()
182
+ ```
183
+
184
+ Rule of thumb: default to `@pytest.mark.django_db` (the fast rollback strategy). `transaction=True` actually commits, so it's slower — reserve it for the specific tests that need it.
185
+
186
+ ### 5.4 Flask + pytest (SQLAlchemy)
187
+
188
+ ```python
189
+ # Good: share one PostgreSQL container for the whole session, and roll back after each test
190
+ @pytest.fixture(scope="session")
191
+ def engine():
192
+ with PostgresContainer("postgres:16") as pg:
193
+ engine = create_engine(pg.get_connection_url())
194
+ run_migrations(engine) # apply the real schema via Alembic
195
+ yield engine
196
+
197
+ @pytest.fixture
198
+ def db_session(engine):
199
+ conn = engine.connect()
200
+ trans = conn.begin()
201
+ session = Session(bind=conn, join_transaction_mode="create_savepoint")
202
+ yield session # even if the code under test commits, it stays inside the savepoint
203
+ session.close()
204
+ trans.rollback() # the independence between tests is guaranteed by the outer transaction
205
+ conn.close()
206
+
207
+ def test_foreign_key_violation_becomes_400(client, db_session):
208
+ res = client.post("/orders", json={"user_id": 9999}) # a user that doesn't exist
209
+ assert res.status_code == 400 # confirms IntegrityError is converted, not swallowed
210
+ ```
211
+
212
+ ### 5.5 Laravel + PHPUnit
213
+
214
+ ```php
215
+ // Good: RefreshDatabase wraps each test in a transaction and rolls it back quickly
216
+ use Illuminate\Foundation\Testing\RefreshDatabase;
217
+
218
+ class OrderTest extends TestCase
219
+ {
220
+ use RefreshDatabase;
221
+
222
+ public function test_saved_value_is_persisted_to_the_database(): void
223
+ {
224
+ $user = User::factory()->create();
225
+ $this->postJson('/api/orders', ['user_id' => $user->id, 'total' => 1000])
226
+ ->assertCreated();
227
+ // Good: use assertDatabaseHas to verify the actual data in the DB
228
+ $this->assertDatabaseHas('orders', ['user_id' => $user->id, 'total' => 1000]);
229
+ }
230
+
231
+ public function test_duplicate_registration_returns_409(): void
232
+ {
233
+ User::factory()->create(['email' => 'taro@example.com']);
234
+ $this->postJson('/api/users', ['email' => 'taro@example.com'])
235
+ ->assertStatus(409);
236
+ $this->assertDatabaseCount('users', 1); // also confirm no extra row was created
237
+ }
238
+ }
239
+ ```
240
+
241
+ Note: pin the connection in `phpunit.xml` to the test database. Don't swap in SQLite's `:memory:` — that reintroduces the trap described in 3.3. If production runs MySQL, test against a MySQL container.
242
+
243
+ ### 5.6 CakePHP + PHPUnit
244
+
245
+ ```php
246
+ // Good: manage tables and data via fixtures against the `test` connection
247
+ class OrdersTableTest extends TestCase
248
+ {
249
+ protected array $fixtures = ['app.Orders', 'app.Users']; // loaded into the test connection
250
+
251
+ public function test_total_aggregation_query_is_correct(): void
252
+ {
253
+ $orders = $this->getTableLocator()->get('Orders');
254
+ $total = $orders->find()->where(['user_id' => 1])->sumOf('total');
255
+ $this->assertSame(3000, $total); // expected value is derived from the fixture data
256
+ }
257
+ }
258
+ ```
259
+
260
+ Define a dedicated database under `Datasources.test` in `config/app_local.php`. It's worth verifying in your test bootstrap that this isn't accidentally pointing at the same database as the default connection.
261
+
262
+ ## 6. Running in CI
263
+
264
+ - **Starting the DB container**: on GitHub Actions, either define it under `services:` (PostgreSQL/MySQL) or run Testcontainers directly (requires a runner with Docker available). Wait for a health check (e.g. `pg_isready`) before starting the tests.
265
+ - **Applying migrations**: always run real migrations before the test suite. Relying on `synchronize` or schema auto-generation means you can't detect missing migrations.
266
+ - **DB isolation under parallel execution**: assign each worker its own database. For Jest, suffix the DB name with `JEST_WORKER_ID`; for pytest-xdist, split by worker ID such as `gw0` (pytest-django appends a suffix automatically). If you can't isolate databases, fall back to serial execution (e.g. `--runInBand`).
267
+ - **Debugging CI-only failures**: if a test only fails in CI, suspect insufficient container-startup waiting, database sharing under parallel execution, or timezone/locale differences.
268
+
269
+ ## 7. Related
270
+
271
+ - Overall test-layer picture and how to write E2E tests: the e2e-testing skill
272
+ - Measuring coverage and finding gaps: `/test-coverage`
273
+ - Reviewing test code: the code-reviewer subagent
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-support-agent/cli",
3
- "version": "0.1.32-beta.1",
3
+ "version": "0.1.33-beta.0",
4
4
  "description": "AI Support Agent CLI client",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -30,7 +30,7 @@
30
30
  ],
31
31
  "scripts": {
32
32
  "dev": "node --env-file-if-exists=.env --require ts-node/register src/index.ts",
33
- "build": "tsc && cp -r src/locales dist/locales",
33
+ "build": "tsc && cp -r src/locales dist/locales && cp -r src/plugin dist/plugin && chmod +x dist/plugin/hooks/scripts/*.sh",
34
34
  "prepublishOnly": "npm run build && npm test",
35
35
  "test": "jest",
36
36
  "test:cov": "jest --coverage",