codeforge-dev 1.4.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 (131) hide show
  1. package/.devcontainer/.env +22 -0
  2. package/.devcontainer/CHANGELOG.md +197 -0
  3. package/.devcontainer/CLAUDE.md +117 -0
  4. package/.devcontainer/README.md +222 -0
  5. package/.devcontainer/config/main-system-prompt.md +502 -0
  6. package/.devcontainer/config/settings.json +47 -0
  7. package/.devcontainer/devcontainer.json +94 -0
  8. package/.devcontainer/features/README.md +113 -0
  9. package/.devcontainer/features/agent-browser/README.md +65 -0
  10. package/.devcontainer/features/agent-browser/devcontainer-feature.json +23 -0
  11. package/.devcontainer/features/agent-browser/install.sh +79 -0
  12. package/.devcontainer/features/ast-grep/README.md +24 -0
  13. package/.devcontainer/features/ast-grep/devcontainer-feature.json +24 -0
  14. package/.devcontainer/features/ast-grep/install.sh +51 -0
  15. package/.devcontainer/features/ccstatusline/README.md +296 -0
  16. package/.devcontainer/features/ccstatusline/devcontainer-feature.json +19 -0
  17. package/.devcontainer/features/ccstatusline/install.sh +290 -0
  18. package/.devcontainer/features/ccusage/README.md +205 -0
  19. package/.devcontainer/features/ccusage/devcontainer-feature.json +38 -0
  20. package/.devcontainer/features/ccusage/install.sh +132 -0
  21. package/.devcontainer/features/claude-code/README.md +498 -0
  22. package/.devcontainer/features/claude-code/config/settings.json +36 -0
  23. package/.devcontainer/features/claude-code/config/system-prompt.md +118 -0
  24. package/.devcontainer/features/claude-code/config/world-building-sp.md +1432 -0
  25. package/.devcontainer/features/claude-code/devcontainer-feature.json +42 -0
  26. package/.devcontainer/features/claude-code/install.sh +466 -0
  27. package/.devcontainer/features/claude-monitor/README.md +74 -0
  28. package/.devcontainer/features/claude-monitor/devcontainer-feature.json +38 -0
  29. package/.devcontainer/features/claude-monitor/install.sh +99 -0
  30. package/.devcontainer/features/lsp-servers/README.md +85 -0
  31. package/.devcontainer/features/lsp-servers/devcontainer-feature.json +40 -0
  32. package/.devcontainer/features/lsp-servers/install.sh +116 -0
  33. package/.devcontainer/features/mcp-qdrant/CHANGES.md +399 -0
  34. package/.devcontainer/features/mcp-qdrant/README.md +474 -0
  35. package/.devcontainer/features/mcp-qdrant/devcontainer-feature.json +57 -0
  36. package/.devcontainer/features/mcp-qdrant/install.sh +295 -0
  37. package/.devcontainer/features/mcp-qdrant/poststart-hook.sh +129 -0
  38. package/.devcontainer/features/mcp-reasoner/README.md +177 -0
  39. package/.devcontainer/features/mcp-reasoner/devcontainer-feature.json +20 -0
  40. package/.devcontainer/features/mcp-reasoner/install.sh +177 -0
  41. package/.devcontainer/features/mcp-reasoner/poststart-hook.sh +67 -0
  42. package/.devcontainer/features/notify-hook/README.md +86 -0
  43. package/.devcontainer/features/notify-hook/devcontainer-feature.json +23 -0
  44. package/.devcontainer/features/notify-hook/install.sh +38 -0
  45. package/.devcontainer/features/splitrail/README.md +140 -0
  46. package/.devcontainer/features/splitrail/devcontainer-feature.json +34 -0
  47. package/.devcontainer/features/splitrail/install.sh +129 -0
  48. package/.devcontainer/features/tree-sitter/README.md +138 -0
  49. package/.devcontainer/features/tree-sitter/devcontainer-feature.json +52 -0
  50. package/.devcontainer/features/tree-sitter/install.sh +173 -0
  51. package/.devcontainer/plugins/devs-marketplace/.claude-plugin/marketplace.json +106 -0
  52. package/.devcontainer/plugins/devs-marketplace/plugins/auto-formatter/.claude-plugin/plugin.json +7 -0
  53. package/.devcontainer/plugins/devs-marketplace/plugins/auto-formatter/hooks/hooks.json +17 -0
  54. package/.devcontainer/plugins/devs-marketplace/plugins/auto-formatter/scripts/format-file.py +101 -0
  55. package/.devcontainer/plugins/devs-marketplace/plugins/auto-linter/.claude-plugin/plugin.json +7 -0
  56. package/.devcontainer/plugins/devs-marketplace/plugins/auto-linter/hooks/hooks.json +17 -0
  57. package/.devcontainer/plugins/devs-marketplace/plugins/auto-linter/scripts/lint-file.py +137 -0
  58. package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/.claude-plugin/plugin.json +8 -0
  59. package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/claude-code-headless/SKILL.md +387 -0
  60. package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/claude-code-headless/references/cli-flags-and-output.md +312 -0
  61. package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/claude-code-headless/references/sdk-and-mcp.md +569 -0
  62. package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/docker/SKILL.md +309 -0
  63. package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/docker/references/compose-services.md +438 -0
  64. package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/docker/references/dockerfile-patterns.md +340 -0
  65. package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/docker-py/SKILL.md +412 -0
  66. package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/docker-py/references/container-lifecycle.md +388 -0
  67. package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/docker-py/references/resources-and-security.md +444 -0
  68. package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/fastapi/SKILL.md +344 -0
  69. package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/fastapi/references/middleware-and-lifespan.md +254 -0
  70. package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/fastapi/references/pydantic-models.md +245 -0
  71. package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/fastapi/references/routing-and-dependencies.md +255 -0
  72. package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/fastapi/references/sse-and-streaming.md +318 -0
  73. package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/pydantic-ai/SKILL.md +345 -0
  74. package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/pydantic-ai/references/agents-and-tools.md +271 -0
  75. package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/pydantic-ai/references/models-and-streaming.md +422 -0
  76. package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/skill-building/SKILL.md +220 -0
  77. package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/skill-building/references/cross-vendor-principles.md +139 -0
  78. package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/skill-building/references/patterns-and-antipatterns.md +376 -0
  79. package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/skill-building/references/skill-authoring-patterns.md +356 -0
  80. package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/sqlite/SKILL.md +329 -0
  81. package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/sqlite/references/advanced-queries.md +314 -0
  82. package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/sqlite/references/javascript-patterns.md +323 -0
  83. package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/sqlite/references/python-patterns.md +354 -0
  84. package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/sqlite/references/schema-and-pragmas.md +326 -0
  85. package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/svelte5/SKILL.md +356 -0
  86. package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/svelte5/references/ai-sdk-svelte.md +128 -0
  87. package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/svelte5/references/component-patterns.md +332 -0
  88. package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/svelte5/references/layercake.md +203 -0
  89. package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/svelte5/references/migration-guide.md +350 -0
  90. package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/svelte5/references/runes-and-reactivity.md +328 -0
  91. package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/svelte5/references/spa-and-routing.md +262 -0
  92. package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/svelte5/references/svelte-dnd-action.md +181 -0
  93. package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/testing/SKILL.md +414 -0
  94. package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/testing/references/fastapi-testing.md +411 -0
  95. package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/testing/references/svelte-testing.md +538 -0
  96. package/.devcontainer/plugins/devs-marketplace/plugins/codeforge-lsp/.claude-plugin/plugin.json +7 -0
  97. package/.devcontainer/plugins/devs-marketplace/plugins/dangerous-command-blocker/.claude-plugin/plugin.json +7 -0
  98. package/.devcontainer/plugins/devs-marketplace/plugins/dangerous-command-blocker/hooks/hooks.json +17 -0
  99. package/.devcontainer/plugins/devs-marketplace/plugins/dangerous-command-blocker/scripts/block-dangerous.py +110 -0
  100. package/.devcontainer/plugins/devs-marketplace/plugins/notify-hook/.claude-plugin/plugin.json +7 -0
  101. package/.devcontainer/plugins/devs-marketplace/plugins/notify-hook/hooks/hooks.json +17 -0
  102. package/.devcontainer/plugins/devs-marketplace/plugins/planning-reminder/.claude-plugin/plugin.json +7 -0
  103. package/.devcontainer/plugins/devs-marketplace/plugins/planning-reminder/hooks/hooks.json +17 -0
  104. package/.devcontainer/plugins/devs-marketplace/plugins/protected-files-guard/.claude-plugin/plugin.json +7 -0
  105. package/.devcontainer/plugins/devs-marketplace/plugins/protected-files-guard/hooks/hooks.json +17 -0
  106. package/.devcontainer/plugins/devs-marketplace/plugins/protected-files-guard/scripts/guard-protected.py +108 -0
  107. package/.devcontainer/plugins/devs-marketplace/plugins/ticket-workflow/.claude-plugin/commands/ticket/357/200/272create-pr.md +337 -0
  108. package/.devcontainer/plugins/devs-marketplace/plugins/ticket-workflow/.claude-plugin/commands/ticket/357/200/272new.md +166 -0
  109. package/.devcontainer/plugins/devs-marketplace/plugins/ticket-workflow/.claude-plugin/commands/ticket/357/200/272review-commit.md +290 -0
  110. package/.devcontainer/plugins/devs-marketplace/plugins/ticket-workflow/.claude-plugin/commands/ticket/357/200/272work.md +257 -0
  111. package/.devcontainer/plugins/devs-marketplace/plugins/ticket-workflow/.claude-plugin/plugin.json +8 -0
  112. package/.devcontainer/plugins/devs-marketplace/plugins/ticket-workflow/.claude-plugin/system-prompt.md +184 -0
  113. package/.devcontainer/plugins/devs-marketplace/plugins/workflow-enhancer/.claude-plugin/plugin.json +6 -0
  114. package/.devcontainer/plugins/devs-marketplace/plugins/workflow-enhancer/config/planning-instructions.md +14 -0
  115. package/.devcontainer/plugins/devs-marketplace/plugins/workflow-enhancer/functional-conjuring-map.md +989 -0
  116. package/.devcontainer/plugins/devs-marketplace/plugins/workflow-enhancer/hooks/hooks.json +33 -0
  117. package/.devcontainer/plugins/devs-marketplace/plugins/workflow-enhancer/scripts/__pycache__/post-enhance-task.cpython-314.pyc +0 -0
  118. package/.devcontainer/plugins/devs-marketplace/plugins/workflow-enhancer/scripts/enhance-planning.py +71 -0
  119. package/.devcontainer/plugins/devs-marketplace/plugins/workflow-enhancer/scripts/enhancers/enhance-plan.sh +68 -0
  120. package/.devcontainer/plugins/devs-marketplace/plugins/workflow-enhancer/scripts/enhancers/enhance-task.sh +120 -0
  121. package/.devcontainer/plugins/devs-marketplace/plugins/workflow-enhancer/scripts/post-enhance-plan.py +133 -0
  122. package/.devcontainer/plugins/devs-marketplace/plugins/workflow-enhancer/scripts/post-enhance-task.py +253 -0
  123. package/.devcontainer/scripts/setup-aliases.sh +80 -0
  124. package/.devcontainer/scripts/setup-config.sh +28 -0
  125. package/.devcontainer/scripts/setup-irie-claude.sh +32 -0
  126. package/.devcontainer/scripts/setup-plugins.sh +80 -0
  127. package/.devcontainer/scripts/setup.sh +58 -0
  128. package/LICENSE.txt +674 -0
  129. package/README.md +267 -0
  130. package/package.json +44 -0
  131. package/setup.js +83 -0
@@ -0,0 +1,538 @@
1
+ # Svelte Testing -- Deep Dive
2
+
3
+ ## 1. Vitest Configuration for SvelteKit
4
+
5
+ ### Full Setup
6
+
7
+ ```bash
8
+ npm install -D vitest jsdom @testing-library/svelte @testing-library/jest-dom @testing-library/user-event
9
+ ```
10
+
11
+ ```javascript
12
+ // vite.config.js
13
+ import { defineConfig } from 'vitest/config'
14
+ import { sveltekit } from '@sveltejs/kit/vite'
15
+ import { svelteTesting } from '@testing-library/svelte/vite'
16
+
17
+ export default defineConfig({
18
+ plugins: [sveltekit(), svelteTesting()],
19
+ test: {
20
+ environment: 'jsdom',
21
+ setupFiles: ['./vitest-setup.js'],
22
+ include: ['tests/**/*.test.ts', 'tests/**/*.svelte.test.ts'],
23
+ },
24
+ })
25
+ ```
26
+
27
+ ```javascript
28
+ // vitest-setup.js
29
+ import '@testing-library/jest-dom/vitest'
30
+ ```
31
+
32
+ ### TypeScript Support
33
+
34
+ Add to `tsconfig.json`:
35
+
36
+ ```json
37
+ {
38
+ "compilerOptions": {
39
+ "types": ["@testing-library/jest-dom"]
40
+ }
41
+ }
42
+ ```
43
+
44
+ ### Runes in Test Files
45
+
46
+ Svelte 5 runes (`$state`, `$derived`, `$effect`) are only available inside `.svelte` files. To use them directly in tests, name the file with `.svelte.test.ts`:
47
+
48
+ ```
49
+ tests/
50
+ counter.svelte.test.ts # Can use $state, $derived
51
+ api.test.ts # Standard test file (no runes)
52
+ ```
53
+
54
+ ---
55
+
56
+ ## 2. @testing-library/svelte Render API
57
+
58
+ ### render() Signature
59
+
60
+ ```javascript
61
+ import { render, screen } from '@testing-library/svelte'
62
+
63
+ // Short form: props directly
64
+ const result = render(MyComponent, { name: 'World', count: 0 })
65
+
66
+ // Long form: with additional options
67
+ const result = render(MyComponent, {
68
+ props: { name: 'World', count: 0 },
69
+ context: new Map([['theme', 'dark']]),
70
+ target: document.getElementById('custom-root'),
71
+ })
72
+ ```
73
+
74
+ ### Render Result
75
+
76
+ ```javascript
77
+ const {
78
+ container, // wrapping DOM element
79
+ baseElement, // base element for queries (document.body)
80
+ component, // Svelte component instance
81
+ debug, // pretty-print DOM: debug() or debug(element)
82
+ rerender, // update props: await rerender({ newProp: 'value' })
83
+ unmount, // destroy component
84
+
85
+ // All query functions bound to baseElement:
86
+ getByRole, getByText, getByLabelText,
87
+ queryByRole, queryByText,
88
+ findByRole, findByText,
89
+ // ... and all other query variants
90
+ } = render(MyComponent, { name: 'World' })
91
+ ```
92
+
93
+ ### Updating Props
94
+
95
+ ```javascript
96
+ const { rerender } = render(Counter, { count: 0 })
97
+ expect(screen.getByText('0')).toBeInTheDocument()
98
+
99
+ await rerender({ count: 5 })
100
+ expect(screen.getByText('5')).toBeInTheDocument()
101
+ ```
102
+
103
+ ### Debug Output
104
+
105
+ ```javascript
106
+ screen.debug() // logs entire document.body
107
+ screen.debug(screen.getByRole('button')) // logs specific element
108
+ ```
109
+
110
+ ---
111
+
112
+ ## 3. User Event Simulation
113
+
114
+ ### Setup
115
+
116
+ Always call `.setup()` before using userEvent:
117
+
118
+ ```javascript
119
+ import userEvent from '@testing-library/user-event'
120
+
121
+ const user = userEvent.setup()
122
+ ```
123
+
124
+ ### Click Events
125
+
126
+ ```javascript
127
+ test('handles click', async () => {
128
+ const user = userEvent.setup()
129
+ render(Button, { label: 'Submit' })
130
+
131
+ await user.click(screen.getByRole('button', { name: 'Submit' }))
132
+ expect(screen.getByText('Submitted')).toBeInTheDocument()
133
+ })
134
+ ```
135
+
136
+ ### Typing
137
+
138
+ ```javascript
139
+ test('handles text input', async () => {
140
+ const user = userEvent.setup()
141
+ render(SearchForm)
142
+
143
+ const input = screen.getByRole('textbox')
144
+ await user.type(input, 'hello world')
145
+ expect(input).toHaveValue('hello world')
146
+
147
+ await user.clear(input)
148
+ expect(input).toHaveValue('')
149
+ })
150
+ ```
151
+
152
+ ### Special Keys
153
+
154
+ ```javascript
155
+ await user.type(input, '{Enter}')
156
+ await user.type(input, '{Backspace}')
157
+ await user.keyboard('{Shift>}A{/Shift}') // hold Shift, press A
158
+ await user.tab()
159
+ await user.tab({ shift: true })
160
+ ```
161
+
162
+ ### Select and Hover
163
+
164
+ ```javascript
165
+ await user.selectOptions(screen.getByRole('combobox'), ['option1'])
166
+ await user.hover(screen.getByRole('button'))
167
+ await user.unhover(screen.getByRole('button'))
168
+ ```
169
+
170
+ ### userEvent vs fireEvent
171
+
172
+ `userEvent` simulates full browser interaction sequences (focus, keydown, keypress, input, keyup). `fireEvent` dispatches a single event. Prefer `userEvent` -- it catches bugs that `fireEvent` misses (e.g., event handlers that depend on focus state).
173
+
174
+ ```javascript
175
+ import { fireEvent } from '@testing-library/svelte'
176
+
177
+ // fireEvent is async in svelte-testing-library
178
+ await fireEvent.click(button)
179
+ ```
180
+
181
+ ---
182
+
183
+ ## 4. Async State Updates
184
+
185
+ ### findBy Queries (Wait for Elements)
186
+
187
+ ```javascript
188
+ test('loads data asynchronously', async () => {
189
+ render(AsyncList)
190
+
191
+ // findByText polls until the element appears (default timeout: 1000ms)
192
+ const item = await screen.findByText('Loaded Item', {}, { timeout: 3000 })
193
+ expect(item).toBeInTheDocument()
194
+ })
195
+ ```
196
+
197
+ ### waitFor (Wait for Assertions)
198
+
199
+ ```javascript
200
+ import { waitFor } from '@testing-library/svelte'
201
+
202
+ test('counter updates after delay', async () => {
203
+ render(DelayedCounter)
204
+
205
+ await waitFor(() => {
206
+ expect(screen.getByText('Updated: 42')).toBeInTheDocument()
207
+ }, { timeout: 2000 })
208
+ })
209
+ ```
210
+
211
+ ### act() -- Flush Pending Updates
212
+
213
+ ```javascript
214
+ import { act } from '@testing-library/svelte'
215
+
216
+ test('programmatic state change', async () => {
217
+ const { component } = render(Counter)
218
+
219
+ await act(() => {
220
+ component.increment()
221
+ })
222
+
223
+ expect(screen.getByText('1')).toBeInTheDocument()
224
+ })
225
+ ```
226
+
227
+ ### flushSync -- Synchronous Flush (Svelte Native)
228
+
229
+ ```javascript
230
+ import { flushSync, mount, unmount } from 'svelte'
231
+
232
+ test('counter with flushSync', () => {
233
+ const component = mount(Counter, {
234
+ target: document.body,
235
+ props: { initial: 0 },
236
+ })
237
+
238
+ document.body.querySelector('button').click()
239
+ flushSync()
240
+
241
+ expect(document.body.innerHTML).toContain('1')
242
+ unmount(component)
243
+ })
244
+ ```
245
+
246
+ ### Testing $effect with $effect.root
247
+
248
+ In `.svelte.test.ts` files:
249
+
250
+ ```javascript
251
+ import { flushSync } from 'svelte'
252
+
253
+ test('derived state tracks changes', () => {
254
+ const cleanup = $effect.root(() => {
255
+ let count = $state(0)
256
+ let doubled = $derived(count * 2)
257
+
258
+ flushSync()
259
+ expect(doubled).toBe(0)
260
+
261
+ count = 5
262
+ flushSync()
263
+ expect(doubled).toBe(10)
264
+ })
265
+ cleanup()
266
+ })
267
+ ```
268
+
269
+ ---
270
+
271
+ ## 5. Mocking Fetch and SSE
272
+
273
+ ### Simple Fetch Mock with vi.fn()
274
+
275
+ ```javascript
276
+ import { vi, beforeEach, afterEach } from 'vitest'
277
+
278
+ beforeEach(() => {
279
+ globalThis.fetch = vi.fn()
280
+ })
281
+
282
+ afterEach(() => {
283
+ vi.restoreAllMocks()
284
+ })
285
+
286
+ test('fetches and displays data', async () => {
287
+ globalThis.fetch.mockResolvedValueOnce(
288
+ new Response(JSON.stringify({ items: ['Apple', 'Banana'] }), {
289
+ headers: { 'Content-Type': 'application/json' },
290
+ })
291
+ )
292
+
293
+ render(ItemList)
294
+
295
+ expect(await screen.findByText('Apple')).toBeInTheDocument()
296
+ expect(screen.getByText('Banana')).toBeInTheDocument()
297
+ expect(fetch).toHaveBeenCalledWith('/api/items', expect.any(Object))
298
+ })
299
+ ```
300
+
301
+ ### MSW (Mock Service Worker) -- Recommended
302
+
303
+ MSW intercepts requests at the network level, keeping test code decoupled from implementation:
304
+
305
+ ```bash
306
+ npm install -D msw
307
+ ```
308
+
309
+ ```javascript
310
+ // tests/mocks/handlers.js
311
+ import { http, HttpResponse } from 'msw'
312
+
313
+ export const handlers = [
314
+ http.get('/api/items', () => {
315
+ return HttpResponse.json({ items: ['Apple', 'Banana'] })
316
+ }),
317
+
318
+ http.post('/api/items', async ({ request }) => {
319
+ const body = await request.json()
320
+ return HttpResponse.json({ id: 1, ...body }, { status: 201 })
321
+ }),
322
+ ]
323
+ ```
324
+
325
+ ```javascript
326
+ // tests/mocks/server.js
327
+ import { setupServer } from 'msw/node'
328
+ import { handlers } from './handlers'
329
+
330
+ export const server = setupServer(...handlers)
331
+ ```
332
+
333
+ ```javascript
334
+ // vitest-setup.js
335
+ import '@testing-library/jest-dom/vitest'
336
+ import { server } from './tests/mocks/server'
337
+ import { beforeAll, afterAll, afterEach } from 'vitest'
338
+
339
+ beforeAll(() => server.listen())
340
+ afterEach(() => server.resetHandlers())
341
+ afterAll(() => server.close())
342
+ ```
343
+
344
+ ```javascript
345
+ test('displays fetched items', async () => {
346
+ render(ItemList)
347
+ expect(await screen.findByText('Apple')).toBeInTheDocument()
348
+ })
349
+
350
+ test('handles server error', async () => {
351
+ server.use(
352
+ http.get('/api/items', () => {
353
+ return new HttpResponse(null, { status: 500 })
354
+ })
355
+ )
356
+ render(ItemList)
357
+ expect(await screen.findByText('Error loading items')).toBeInTheDocument()
358
+ })
359
+ ```
360
+
361
+ ### MSW for SSE Streams
362
+
363
+ ```javascript
364
+ import { http, HttpResponse } from 'msw'
365
+
366
+ const handlers = [
367
+ http.get('/api/stream', () => {
368
+ const encoder = new TextEncoder()
369
+ const stream = new ReadableStream({
370
+ start(controller) {
371
+ controller.enqueue(encoder.encode('data: {"token": "Hello"}\n\n'))
372
+ controller.enqueue(encoder.encode('data: {"token": " World"}\n\n'))
373
+ controller.enqueue(encoder.encode('event: done\ndata: \n\n'))
374
+ controller.close()
375
+ },
376
+ })
377
+ return new HttpResponse(stream, {
378
+ headers: { 'Content-Type': 'text/event-stream' },
379
+ })
380
+ }),
381
+ ]
382
+ ```
383
+
384
+ ### Mocking EventSource Directly
385
+
386
+ ```javascript
387
+ class MockEventSource {
388
+ constructor(url) {
389
+ this.url = url
390
+ this.readyState = 0
391
+ MockEventSource.instances.push(this)
392
+ setTimeout(() => {
393
+ this.readyState = 1
394
+ this.onopen?.()
395
+ }, 0)
396
+ }
397
+ close() { this.readyState = 2 }
398
+ emitMessage(data) {
399
+ this.onmessage?.({ data: JSON.stringify(data) })
400
+ }
401
+ static instances = []
402
+ }
403
+
404
+ vi.stubGlobal('EventSource', MockEventSource)
405
+
406
+ test('receives SSE messages', async () => {
407
+ render(StreamComponent)
408
+
409
+ // Wait for connection
410
+ await vi.waitFor(() => expect(MockEventSource.instances).toHaveLength(1))
411
+
412
+ const es = MockEventSource.instances[0]
413
+ es.emitMessage({ token: 'Hello' })
414
+
415
+ expect(await screen.findByText('Hello')).toBeInTheDocument()
416
+ })
417
+ ```
418
+
419
+ ---
420
+
421
+ ## 6. Mocking SvelteKit Modules
422
+
423
+ ### $app/navigation
424
+
425
+ ```javascript
426
+ import { vi } from 'vitest'
427
+
428
+ vi.mock('$app/navigation', () => ({
429
+ goto: vi.fn(),
430
+ invalidate: vi.fn(),
431
+ invalidateAll: vi.fn(),
432
+ beforeNavigate: vi.fn(),
433
+ afterNavigate: vi.fn(),
434
+ onNavigate: vi.fn(),
435
+ }))
436
+ ```
437
+
438
+ ### $app/environment
439
+
440
+ ```javascript
441
+ vi.mock('$app/environment', () => ({
442
+ browser: true,
443
+ dev: true,
444
+ building: false,
445
+ version: 'test',
446
+ }))
447
+ ```
448
+
449
+ ### $app/stores
450
+
451
+ ```javascript
452
+ import { readable, writable } from 'svelte/store'
453
+
454
+ vi.mock('$app/stores', () => ({
455
+ page: readable({
456
+ url: new URL('http://localhost/items'),
457
+ params: { id: '1' },
458
+ route: { id: '/items/[id]' },
459
+ status: 200,
460
+ error: null,
461
+ data: {},
462
+ form: null,
463
+ }),
464
+ navigating: readable(null),
465
+ updated: {
466
+ check: vi.fn(),
467
+ subscribe: readable(false).subscribe,
468
+ },
469
+ }))
470
+ ```
471
+
472
+ ### Assert Navigation
473
+
474
+ ```javascript
475
+ import { goto } from '$app/navigation'
476
+
477
+ test('navigates on button click', async () => {
478
+ const user = userEvent.setup()
479
+ render(NavButton, { href: '/dashboard' })
480
+
481
+ await user.click(screen.getByRole('button'))
482
+ expect(goto).toHaveBeenCalledWith('/dashboard')
483
+ })
484
+ ```
485
+
486
+ ---
487
+
488
+ ## 7. Vitest Mocking API Reference
489
+
490
+ | Function | Purpose |
491
+ |----------|---------|
492
+ | `vi.fn()` | Create a mock function |
493
+ | `vi.fn(() => impl)` | Mock with implementation |
494
+ | `vi.mock('./module')` | Auto-mock entire module (hoisted) |
495
+ | `vi.mock('./module', () => ({...}))` | Mock with factory (hoisted) |
496
+ | `vi.spyOn(obj, 'method')` | Spy on existing method |
497
+ | `vi.stubGlobal('name', value)` | Stub a global variable |
498
+ | `vi.stubEnv('VAR', 'value')` | Stub an environment variable |
499
+ | `vi.useFakeTimers()` | Mock Date/setTimeout/setInterval |
500
+ | `vi.setSystemTime(date)` | Set mocked system time |
501
+ | `vi.advanceTimersByTime(ms)` | Advance fake timers |
502
+ | `vi.restoreAllMocks()` | Restore all mocks/spies |
503
+ | `vi.resetAllMocks()` | Reset mock call/result state |
504
+ | `vi.clearAllMocks()` | Clear mock call history |
505
+ | `vi.waitFor(callback)` | Wait for callback to not throw |
506
+
507
+ ---
508
+
509
+ ## 8. Snapshot Testing
510
+
511
+ ### Component Snapshots
512
+
513
+ ```javascript
514
+ test('matches snapshot', () => {
515
+ const { container } = render(Badge, { label: 'New', variant: 'primary' })
516
+ expect(container.innerHTML).toMatchSnapshot()
517
+ })
518
+ ```
519
+
520
+ ### Inline Snapshots
521
+
522
+ ```javascript
523
+ test('renders badge HTML', () => {
524
+ const { container } = render(Badge, { label: 'New' })
525
+ expect(container.innerHTML).toMatchInlineSnapshot(`
526
+ "<span class="badge badge-default">New</span>"
527
+ `)
528
+ })
529
+ ```
530
+
531
+ ### When to Snapshot
532
+
533
+ Snapshots are useful for detecting unintended DOM changes in presentational components. Avoid snapshots for:
534
+ - Components with dynamic data (timestamps, random IDs).
535
+ - Complex interactive components (assertions on specific behavior are more maintainable).
536
+ - Components that change frequently (snapshots become noise).
537
+
538
+ Prefer explicit assertions (`expect(element).toHaveTextContent(...)`) over snapshots for behavioral tests.
@@ -0,0 +1,7 @@
1
+ {
2
+ "name": "codeforge-lsp",
3
+ "description": "LSP servers for CodeForge (Python, TypeScript, Go)",
4
+ "author": {
5
+ "name": "AnExiledDev"
6
+ }
7
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "name": "dangerous-command-blocker",
3
+ "description": "Blocks dangerous bash commands (rm -rf, sudo rm, chmod 777, force push)",
4
+ "author": {
5
+ "name": "AnExiledDev"
6
+ }
7
+ }
@@ -0,0 +1,17 @@
1
+ {
2
+ "description": "Block dangerous bash commands before execution",
3
+ "hooks": {
4
+ "PreToolUse": [
5
+ {
6
+ "matcher": "Bash",
7
+ "hooks": [
8
+ {
9
+ "type": "command",
10
+ "command": "python3 ${CLAUDE_PLUGIN_ROOT}/scripts/block-dangerous.py",
11
+ "timeout": 5
12
+ }
13
+ ]
14
+ }
15
+ ]
16
+ }
17
+ }
@@ -0,0 +1,110 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Block dangerous bash commands before execution.
4
+
5
+ Reads tool input from stdin, checks against dangerous patterns.
6
+ Exit code 2 blocks the command with error message.
7
+ Exit code 0 allows the command to proceed.
8
+ """
9
+
10
+ import json
11
+ import re
12
+ import sys
13
+
14
+ DANGEROUS_PATTERNS = [
15
+ # Destructive filesystem deletion
16
+ (r'\brm\s+.*-[^\s]*r[^\s]*f[^\s]*\s+[/~](?:\s|$)',
17
+ "Blocked: rm -rf on root or home directory"),
18
+ (r'\brm\s+.*-[^\s]*f[^\s]*r[^\s]*\s+[/~](?:\s|$)',
19
+ "Blocked: rm -rf on root or home directory"),
20
+ (r'\brm\s+-rf\s+/(?:\s|$)',
21
+ "Blocked: rm -rf /"),
22
+ (r'\brm\s+-rf\s+~(?:\s|$)',
23
+ "Blocked: rm -rf ~"),
24
+
25
+ # Root-level file removal
26
+ (r'\bsudo\s+rm\b',
27
+ "Blocked: sudo rm - use caution with privileged deletion"),
28
+
29
+ # World-writable permissions
30
+ (r'\bchmod\s+777\b',
31
+ "Blocked: chmod 777 creates security vulnerability"),
32
+ (r'\bchmod\s+-R\s+777\b',
33
+ "Blocked: recursive chmod 777 creates security vulnerability"),
34
+
35
+ # Force push to main/master
36
+ (r'\bgit\s+push\s+.*--force.*\s+(origin\s+)?(main|master)\b',
37
+ "Blocked: force push to main/master destroys history"),
38
+ (r'\bgit\s+push\s+.*-f\s+.*\s+(origin\s+)?(main|master)\b',
39
+ "Blocked: force push to main/master destroys history"),
40
+ (r'\bgit\s+push\s+-f\s+(origin\s+)?(main|master)\b',
41
+ "Blocked: force push to main/master destroys history"),
42
+ (r'\bgit\s+push\s+--force\s+(origin\s+)?(main|master)\b',
43
+ "Blocked: force push to main/master destroys history"),
44
+
45
+ # System directory modification
46
+ (r'>\s*/usr/',
47
+ "Blocked: writing to /usr system directory"),
48
+ (r'>\s*/etc/',
49
+ "Blocked: writing to /etc system directory"),
50
+ (r'>\s*/bin/',
51
+ "Blocked: writing to /bin system directory"),
52
+ (r'>\s*/sbin/',
53
+ "Blocked: writing to /sbin system directory"),
54
+
55
+ # Disk formatting
56
+ (r'\bmkfs\.\w+',
57
+ "Blocked: disk formatting command"),
58
+ (r'\bdd\s+.*of=/dev/',
59
+ "Blocked: dd writing to device"),
60
+
61
+ # History manipulation
62
+ (r'\bgit\s+reset\s+--hard\s+origin/(main|master)\b',
63
+ "Blocked: hard reset to remote main/master - destructive operation"),
64
+ ]
65
+
66
+
67
+ def check_command(command: str) -> tuple[bool, str]:
68
+ """Check if command matches any dangerous pattern.
69
+
70
+ Returns:
71
+ (is_dangerous, message)
72
+ """
73
+ for pattern, message in DANGEROUS_PATTERNS:
74
+ if re.search(pattern, command, re.IGNORECASE):
75
+ return True, message
76
+ return False, ""
77
+
78
+
79
+ def main():
80
+ try:
81
+ input_data = json.load(sys.stdin)
82
+ tool_input = input_data.get("tool_input", {})
83
+ command = tool_input.get("command", "")
84
+
85
+ if not command:
86
+ sys.exit(0)
87
+
88
+ is_dangerous, message = check_command(command)
89
+
90
+ if is_dangerous:
91
+ # Output error message and exit 2 to block
92
+ print(json.dumps({
93
+ "error": message
94
+ }))
95
+ sys.exit(2)
96
+
97
+ # Allow command to proceed
98
+ sys.exit(0)
99
+
100
+ except json.JSONDecodeError:
101
+ # If we can't parse input, allow by default
102
+ sys.exit(0)
103
+ except Exception as e:
104
+ # Log error but don't block on hook failure
105
+ print(f"Hook error: {e}", file=sys.stderr)
106
+ sys.exit(0)
107
+
108
+
109
+ if __name__ == "__main__":
110
+ main()
@@ -0,0 +1,7 @@
1
+ {
2
+ "name": "notify-hook",
3
+ "description": "Desktop notifications and audio chime when Claude finishes responding",
4
+ "author": {
5
+ "name": "AnExiledDev"
6
+ }
7
+ }
@@ -0,0 +1,17 @@
1
+ {
2
+ "description": "Notification when Claude finishes responding",
3
+ "hooks": {
4
+ "Stop": [
5
+ {
6
+ "matcher": "",
7
+ "hooks": [
8
+ {
9
+ "type": "command",
10
+ "command": "/usr/local/bin/claude-notify",
11
+ "timeout": 5
12
+ }
13
+ ]
14
+ }
15
+ ]
16
+ }
17
+ }