@sparkleideas/shared 3.0.0-alpha.7
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.
- package/README.md +323 -0
- package/__tests__/hooks/bash-safety.test.ts +289 -0
- package/__tests__/hooks/file-organization.test.ts +335 -0
- package/__tests__/hooks/git-commit.test.ts +336 -0
- package/__tests__/hooks/index.ts +23 -0
- package/__tests__/hooks/session-hooks.test.ts +357 -0
- package/__tests__/hooks/task-hooks.test.ts +193 -0
- package/docs/EVENTS_IMPLEMENTATION_SUMMARY.md +388 -0
- package/docs/EVENTS_QUICK_REFERENCE.md +470 -0
- package/docs/EVENTS_README.md +352 -0
- package/package.json +39 -0
- package/src/core/config/defaults.ts +207 -0
- package/src/core/config/index.ts +15 -0
- package/src/core/config/loader.ts +271 -0
- package/src/core/config/schema.ts +188 -0
- package/src/core/config/validator.ts +209 -0
- package/src/core/event-bus.ts +236 -0
- package/src/core/index.ts +22 -0
- package/src/core/interfaces/agent.interface.ts +251 -0
- package/src/core/interfaces/coordinator.interface.ts +363 -0
- package/src/core/interfaces/event.interface.ts +267 -0
- package/src/core/interfaces/index.ts +19 -0
- package/src/core/interfaces/memory.interface.ts +332 -0
- package/src/core/interfaces/task.interface.ts +223 -0
- package/src/core/orchestrator/event-coordinator.ts +122 -0
- package/src/core/orchestrator/health-monitor.ts +214 -0
- package/src/core/orchestrator/index.ts +89 -0
- package/src/core/orchestrator/lifecycle-manager.ts +263 -0
- package/src/core/orchestrator/session-manager.ts +279 -0
- package/src/core/orchestrator/task-manager.ts +317 -0
- package/src/events/domain-events.ts +584 -0
- package/src/events/event-store.test.ts +387 -0
- package/src/events/event-store.ts +588 -0
- package/src/events/example-usage.ts +293 -0
- package/src/events/index.ts +90 -0
- package/src/events/projections.ts +561 -0
- package/src/events/state-reconstructor.ts +349 -0
- package/src/events.ts +367 -0
- package/src/hooks/INTEGRATION.md +658 -0
- package/src/hooks/README.md +532 -0
- package/src/hooks/example-usage.ts +499 -0
- package/src/hooks/executor.ts +379 -0
- package/src/hooks/hooks.test.ts +421 -0
- package/src/hooks/index.ts +131 -0
- package/src/hooks/registry.ts +333 -0
- package/src/hooks/safety/bash-safety.ts +604 -0
- package/src/hooks/safety/file-organization.ts +473 -0
- package/src/hooks/safety/git-commit.ts +623 -0
- package/src/hooks/safety/index.ts +46 -0
- package/src/hooks/session-hooks.ts +559 -0
- package/src/hooks/task-hooks.ts +513 -0
- package/src/hooks/types.ts +357 -0
- package/src/hooks/verify-exports.test.ts +125 -0
- package/src/index.ts +195 -0
- package/src/mcp/connection-pool.ts +438 -0
- package/src/mcp/index.ts +183 -0
- package/src/mcp/server.ts +774 -0
- package/src/mcp/session-manager.ts +428 -0
- package/src/mcp/tool-registry.ts +566 -0
- package/src/mcp/transport/http.ts +557 -0
- package/src/mcp/transport/index.ts +294 -0
- package/src/mcp/transport/stdio.ts +324 -0
- package/src/mcp/transport/websocket.ts +484 -0
- package/src/mcp/types.ts +565 -0
- package/src/plugin-interface.ts +663 -0
- package/src/plugin-loader.ts +638 -0
- package/src/plugin-registry.ts +604 -0
- package/src/plugins/index.ts +34 -0
- package/src/plugins/official/hive-mind-plugin.ts +330 -0
- package/src/plugins/official/index.ts +24 -0
- package/src/plugins/official/maestro-plugin.ts +508 -0
- package/src/plugins/types.ts +108 -0
- package/src/resilience/bulkhead.ts +277 -0
- package/src/resilience/circuit-breaker.ts +326 -0
- package/src/resilience/index.ts +26 -0
- package/src/resilience/rate-limiter.ts +420 -0
- package/src/resilience/retry.ts +224 -0
- package/src/security/index.ts +39 -0
- package/src/security/input-validation.ts +265 -0
- package/src/security/secure-random.ts +159 -0
- package/src/services/index.ts +16 -0
- package/src/services/v3-progress.service.ts +505 -0
- package/src/types/agent.types.ts +144 -0
- package/src/types/index.ts +22 -0
- package/src/types/mcp.types.ts +300 -0
- package/src/types/memory.types.ts +263 -0
- package/src/types/swarm.types.ts +255 -0
- package/src/types/task.types.ts +205 -0
- package/src/types.ts +367 -0
- package/src/utils/secure-logger.d.ts +69 -0
- package/src/utils/secure-logger.d.ts.map +1 -0
- package/src/utils/secure-logger.js +208 -0
- package/src/utils/secure-logger.js.map +1 -0
- package/src/utils/secure-logger.ts +257 -0
- package/tmp.json +0 -0
- package/tsconfig.json +9 -0
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* V3 File Organization Hook Tests
|
|
3
|
+
*
|
|
4
|
+
* Tests for file organization enforcement and formatter recommendations.
|
|
5
|
+
*
|
|
6
|
+
* @module v3/shared/hooks/__tests__/file-organization.test
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { describe, it, expect, beforeEach } from 'vitest';
|
|
10
|
+
import {
|
|
11
|
+
createHookRegistry,
|
|
12
|
+
createFileOrganizationHook,
|
|
13
|
+
FileOrganizationHook,
|
|
14
|
+
HookRegistry,
|
|
15
|
+
} from '../../src/hooks/index.js';
|
|
16
|
+
|
|
17
|
+
describe('FileOrganizationHook', () => {
|
|
18
|
+
let registry: HookRegistry;
|
|
19
|
+
let fileOrg: FileOrganizationHook;
|
|
20
|
+
|
|
21
|
+
beforeEach(() => {
|
|
22
|
+
registry = createHookRegistry();
|
|
23
|
+
fileOrg = createFileOrganizationHook(registry);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
describe('root folder blocking', () => {
|
|
27
|
+
it('should block TypeScript source files in root', async () => {
|
|
28
|
+
const result = await fileOrg.analyze('utils.ts');
|
|
29
|
+
|
|
30
|
+
expect(result.blocked).toBe(true);
|
|
31
|
+
expect(result.blockReason).toBeDefined();
|
|
32
|
+
expect(result.suggestedDirectory).toBe('src/');
|
|
33
|
+
expect(result.suggestedPath).toBe('src/utils.ts');
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('should block JavaScript source files in root', async () => {
|
|
37
|
+
const result = await fileOrg.analyze('index.js');
|
|
38
|
+
|
|
39
|
+
expect(result.blocked).toBe(true);
|
|
40
|
+
expect(result.suggestedDirectory).toBe('src/');
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('should block TypeScript test files in root', async () => {
|
|
44
|
+
const result = await fileOrg.analyze('utils.test.ts');
|
|
45
|
+
|
|
46
|
+
expect(result.blocked).toBe(true);
|
|
47
|
+
expect(result.suggestedDirectory).toBe('tests/');
|
|
48
|
+
expect(result.suggestedPath).toBe('tests/utils.test.ts');
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('should block spec files in root', async () => {
|
|
52
|
+
const result = await fileOrg.analyze('api.spec.ts');
|
|
53
|
+
|
|
54
|
+
expect(result.blocked).toBe(true);
|
|
55
|
+
expect(result.suggestedDirectory).toBe('tests/');
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('should block Python files in root', async () => {
|
|
59
|
+
const result = await fileOrg.analyze('main.py');
|
|
60
|
+
|
|
61
|
+
expect(result.blocked).toBe(true);
|
|
62
|
+
expect(result.suggestedDirectory).toBe('src/');
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('should block Go files in root', async () => {
|
|
66
|
+
const result = await fileOrg.analyze('main.go');
|
|
67
|
+
|
|
68
|
+
expect(result.blocked).toBe(true);
|
|
69
|
+
expect(result.suggestedDirectory).toBe('cmd/');
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('should block shell scripts in root', async () => {
|
|
73
|
+
const result = await fileOrg.analyze('deploy.sh');
|
|
74
|
+
|
|
75
|
+
expect(result.blocked).toBe(true);
|
|
76
|
+
expect(result.suggestedDirectory).toBe('scripts/');
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('should block CSS files in root', async () => {
|
|
80
|
+
const result = await fileOrg.analyze('styles.css');
|
|
81
|
+
|
|
82
|
+
expect(result.blocked).toBe(true);
|
|
83
|
+
expect(result.suggestedDirectory).toBe('styles/');
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
describe('allowed root files', () => {
|
|
88
|
+
it('should allow JSON config files in root', async () => {
|
|
89
|
+
const result = await fileOrg.analyze('package.json');
|
|
90
|
+
|
|
91
|
+
expect(result.blocked).toBe(false);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('should allow YAML config files in root', async () => {
|
|
95
|
+
const result = await fileOrg.analyze('config.yaml');
|
|
96
|
+
|
|
97
|
+
expect(result.blocked).toBe(false);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('should allow Markdown files in root', async () => {
|
|
101
|
+
const result = await fileOrg.analyze('README.md');
|
|
102
|
+
|
|
103
|
+
expect(result.blocked).toBe(false);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('should allow environment files in root', async () => {
|
|
107
|
+
const result = await fileOrg.analyze('.env');
|
|
108
|
+
|
|
109
|
+
expect(result.blocked).toBe(false);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it('should allow .env.local files in root', async () => {
|
|
113
|
+
const result = await fileOrg.analyze('.env.local');
|
|
114
|
+
|
|
115
|
+
expect(result.blocked).toBe(false);
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
describe('files in correct directories', () => {
|
|
120
|
+
it('should allow TypeScript files in src/', async () => {
|
|
121
|
+
const result = await fileOrg.analyze('src/utils.ts');
|
|
122
|
+
|
|
123
|
+
expect(result.blocked).toBe(false);
|
|
124
|
+
expect(result.issues?.some(i => i.type === 'wrong-directory')).toBeFalsy();
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it('should allow test files in tests/', async () => {
|
|
128
|
+
const result = await fileOrg.analyze('tests/utils.test.ts');
|
|
129
|
+
|
|
130
|
+
expect(result.blocked).toBe(false);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it('should allow test files in __tests__/', async () => {
|
|
134
|
+
const result = await fileOrg.analyze('__tests__/utils.test.ts');
|
|
135
|
+
|
|
136
|
+
expect(result.blocked).toBe(false);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it('should allow scripts in scripts/', async () => {
|
|
140
|
+
const result = await fileOrg.analyze('scripts/deploy.sh');
|
|
141
|
+
|
|
142
|
+
expect(result.blocked).toBe(false);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it('should allow Go files in cmd/', async () => {
|
|
146
|
+
const result = await fileOrg.analyze('cmd/main.go');
|
|
147
|
+
|
|
148
|
+
expect(result.blocked).toBe(false);
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
describe('files in wrong directories', () => {
|
|
153
|
+
it('should warn about test files in src/', async () => {
|
|
154
|
+
const result = await fileOrg.analyze('src/utils.test.ts');
|
|
155
|
+
|
|
156
|
+
expect(result.blocked).toBe(false);
|
|
157
|
+
expect(result.issues?.some(i => i.type === 'wrong-directory')).toBe(true);
|
|
158
|
+
expect(result.warnings?.length).toBeGreaterThan(0);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it('should warn about source files in tests/', async () => {
|
|
162
|
+
const result = await fileOrg.analyze('tests/utils.ts');
|
|
163
|
+
|
|
164
|
+
expect(result.blocked).toBe(false);
|
|
165
|
+
expect(result.issues?.some(i => i.type === 'wrong-directory')).toBe(true);
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
describe('formatter recommendations', () => {
|
|
170
|
+
it('should recommend Prettier for TypeScript', async () => {
|
|
171
|
+
const result = await fileOrg.analyze('src/utils.ts');
|
|
172
|
+
|
|
173
|
+
expect(result.formatter).toBeDefined();
|
|
174
|
+
expect(result.formatter!.name).toBe('Prettier');
|
|
175
|
+
expect(result.formatter!.command).toBe('prettier --write');
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
it('should recommend Black for Python', async () => {
|
|
179
|
+
const result = await fileOrg.analyze('src/main.py');
|
|
180
|
+
|
|
181
|
+
expect(result.formatter).toBeDefined();
|
|
182
|
+
expect(result.formatter!.name).toBe('Black');
|
|
183
|
+
expect(result.formatter!.command).toBe('black');
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
it('should recommend gofmt for Go', async () => {
|
|
187
|
+
const result = await fileOrg.analyze('cmd/main.go');
|
|
188
|
+
|
|
189
|
+
expect(result.formatter).toBeDefined();
|
|
190
|
+
expect(result.formatter!.name).toBe('gofmt');
|
|
191
|
+
expect(result.formatter!.command).toBe('gofmt -w');
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it('should recommend rustfmt for Rust', async () => {
|
|
195
|
+
const result = await fileOrg.analyze('src/main.rs');
|
|
196
|
+
|
|
197
|
+
expect(result.formatter).toBeDefined();
|
|
198
|
+
expect(result.formatter!.name).toBe('rustfmt');
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
it('should recommend Prettier for CSS', async () => {
|
|
202
|
+
const result = await fileOrg.analyze('styles/app.css');
|
|
203
|
+
|
|
204
|
+
expect(result.formatter).toBeDefined();
|
|
205
|
+
expect(result.formatter!.name).toBe('Prettier');
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
it('should recommend Prettier for JSON', async () => {
|
|
209
|
+
const result = await fileOrg.analyze('config.json');
|
|
210
|
+
|
|
211
|
+
expect(result.formatter).toBeDefined();
|
|
212
|
+
expect(result.formatter!.name).toBe('Prettier');
|
|
213
|
+
});
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
describe('linter recommendations', () => {
|
|
217
|
+
it('should recommend ESLint for TypeScript', async () => {
|
|
218
|
+
const result = await fileOrg.analyze('src/utils.ts');
|
|
219
|
+
|
|
220
|
+
expect(result.linter).toBeDefined();
|
|
221
|
+
expect(result.linter!.name).toBe('ESLint');
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
it('should recommend Pylint for Python', async () => {
|
|
225
|
+
const result = await fileOrg.analyze('src/main.py');
|
|
226
|
+
|
|
227
|
+
expect(result.linter).toBeDefined();
|
|
228
|
+
expect(result.linter!.name).toBe('Pylint');
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
it('should recommend golangci-lint for Go', async () => {
|
|
232
|
+
const result = await fileOrg.analyze('cmd/main.go');
|
|
233
|
+
|
|
234
|
+
expect(result.linter).toBeDefined();
|
|
235
|
+
expect(result.linter!.name).toBe('golangci-lint');
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
it('should recommend Clippy for Rust', async () => {
|
|
239
|
+
const result = await fileOrg.analyze('src/main.rs');
|
|
240
|
+
|
|
241
|
+
expect(result.linter).toBeDefined();
|
|
242
|
+
expect(result.linter!.name).toBe('Clippy');
|
|
243
|
+
});
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
describe('organization issues', () => {
|
|
247
|
+
it('should detect root write issues', async () => {
|
|
248
|
+
const result = await fileOrg.analyze('utils.ts');
|
|
249
|
+
|
|
250
|
+
expect(result.issues).toBeDefined();
|
|
251
|
+
expect(result.issues!.some(i => i.type === 'root-write')).toBe(true);
|
|
252
|
+
expect(result.issues![0].severity).toBe('error');
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
it('should provide suggested fixes', async () => {
|
|
256
|
+
const result = await fileOrg.analyze('utils.ts');
|
|
257
|
+
|
|
258
|
+
expect(result.issues).toBeDefined();
|
|
259
|
+
expect(result.issues![0].suggestedFix).toBeDefined();
|
|
260
|
+
});
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
describe('helper methods', () => {
|
|
264
|
+
it('should get suggested directory for file', () => {
|
|
265
|
+
expect(fileOrg.getSuggestedDirectory('app.ts')).toBe('src/');
|
|
266
|
+
expect(fileOrg.getSuggestedDirectory('test.spec.ts')).toBe('tests/');
|
|
267
|
+
expect(fileOrg.getSuggestedDirectory('deploy.sh')).toBe('scripts/');
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
it('should check if file would be blocked', () => {
|
|
271
|
+
expect(fileOrg.wouldBlock('utils.ts')).toBe(true);
|
|
272
|
+
expect(fileOrg.wouldBlock('package.json')).toBe(false);
|
|
273
|
+
expect(fileOrg.wouldBlock('src/utils.ts')).toBe(false);
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
it('should allow setting project root', () => {
|
|
277
|
+
fileOrg.setProjectRoot('/custom/project');
|
|
278
|
+
// No error should be thrown
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
it('should get all formatters', () => {
|
|
282
|
+
const formatters = fileOrg.getAllFormatters();
|
|
283
|
+
|
|
284
|
+
expect(formatters['.ts']).toBeDefined();
|
|
285
|
+
expect(formatters['.py']).toBeDefined();
|
|
286
|
+
expect(formatters['.go']).toBeDefined();
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
it('should get all linters', () => {
|
|
290
|
+
const linters = fileOrg.getAllLinters();
|
|
291
|
+
|
|
292
|
+
expect(linters['.ts']).toBeDefined();
|
|
293
|
+
expect(linters['.py']).toBeDefined();
|
|
294
|
+
expect(linters['.go']).toBeDefined();
|
|
295
|
+
});
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
describe('file type detection', () => {
|
|
299
|
+
it('should detect TypeScript source type', async () => {
|
|
300
|
+
const result = await fileOrg.analyze('src/app.ts');
|
|
301
|
+
|
|
302
|
+
expect(result.fileType).toBe('TypeScript source');
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
it('should detect test file type', async () => {
|
|
306
|
+
const result = await fileOrg.analyze('tests/app.test.ts');
|
|
307
|
+
|
|
308
|
+
expect(result.fileType).toBe('test file');
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
it('should detect spec file type', async () => {
|
|
312
|
+
const result = await fileOrg.analyze('spec/app.spec.ts');
|
|
313
|
+
|
|
314
|
+
expect(result.fileType).toBe('spec file');
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
it('should detect shell script type', async () => {
|
|
318
|
+
const result = await fileOrg.analyze('scripts/build.sh');
|
|
319
|
+
|
|
320
|
+
expect(result.fileType).toBe('shell script');
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
it('should detect SQL file type', async () => {
|
|
324
|
+
const result = await fileOrg.analyze('migrations/001_init.sql');
|
|
325
|
+
|
|
326
|
+
expect(result.fileType).toBe('SQL file');
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
it('should detect image file type', async () => {
|
|
330
|
+
const result = await fileOrg.analyze('assets/logo.png');
|
|
331
|
+
|
|
332
|
+
expect(result.fileType).toBe('image');
|
|
333
|
+
});
|
|
334
|
+
});
|
|
335
|
+
});
|
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* V3 Git Commit Hook Tests
|
|
3
|
+
*
|
|
4
|
+
* Tests for git commit message formatting and validation.
|
|
5
|
+
*
|
|
6
|
+
* @module v3/shared/hooks/__tests__/git-commit.test
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { describe, it, expect, beforeEach } from 'vitest';
|
|
10
|
+
import {
|
|
11
|
+
createHookRegistry,
|
|
12
|
+
createGitCommitHook,
|
|
13
|
+
GitCommitHook,
|
|
14
|
+
HookRegistry,
|
|
15
|
+
} from '../../src/hooks/index.js';
|
|
16
|
+
|
|
17
|
+
describe('GitCommitHook', () => {
|
|
18
|
+
let registry: HookRegistry;
|
|
19
|
+
let gitCommit: GitCommitHook;
|
|
20
|
+
|
|
21
|
+
beforeEach(() => {
|
|
22
|
+
registry = createHookRegistry();
|
|
23
|
+
gitCommit = createGitCommitHook(registry);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
describe('commit type detection', () => {
|
|
27
|
+
it('should detect feat type from message', async () => {
|
|
28
|
+
const result = await gitCommit.process('Add user authentication');
|
|
29
|
+
|
|
30
|
+
expect(result.commitType).toBe('feat');
|
|
31
|
+
expect(result.modifiedMessage).toMatch(/^feat:/);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('should detect fix type from message', async () => {
|
|
35
|
+
const result = await gitCommit.process('Fix login validation bug');
|
|
36
|
+
|
|
37
|
+
expect(result.commitType).toBe('fix');
|
|
38
|
+
expect(result.modifiedMessage).toMatch(/^fix:/);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('should detect docs type from message', async () => {
|
|
42
|
+
const result = await gitCommit.process('Update README documentation');
|
|
43
|
+
|
|
44
|
+
expect(result.commitType).toBe('docs');
|
|
45
|
+
expect(result.modifiedMessage).toMatch(/^docs:/);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('should detect refactor type from message', async () => {
|
|
49
|
+
const result = await gitCommit.process('Refactor authentication module');
|
|
50
|
+
|
|
51
|
+
expect(result.commitType).toBe('refactor');
|
|
52
|
+
expect(result.modifiedMessage).toMatch(/^refactor:/);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('should detect test type from message', async () => {
|
|
56
|
+
const result = await gitCommit.process('Add unit tests for user service');
|
|
57
|
+
|
|
58
|
+
expect(result.commitType).toBe('test');
|
|
59
|
+
expect(result.modifiedMessage).toMatch(/^test:/);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('should detect perf type from message', async () => {
|
|
63
|
+
const result = await gitCommit.process('Optimize database queries');
|
|
64
|
+
|
|
65
|
+
expect(result.commitType).toBe('perf');
|
|
66
|
+
expect(result.modifiedMessage).toMatch(/^perf:/);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('should detect build type from message', async () => {
|
|
70
|
+
const result = await gitCommit.process('Update webpack configuration');
|
|
71
|
+
|
|
72
|
+
expect(result.commitType).toBe('build');
|
|
73
|
+
expect(result.modifiedMessage).toMatch(/^build:/);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('should detect ci type from message', async () => {
|
|
77
|
+
const result = await gitCommit.process('Update GitHub Actions workflow');
|
|
78
|
+
|
|
79
|
+
expect(result.commitType).toBe('ci');
|
|
80
|
+
expect(result.modifiedMessage).toMatch(/^ci:/);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('should detect chore type from message', async () => {
|
|
84
|
+
const result = await gitCommit.process('Update dependencies');
|
|
85
|
+
|
|
86
|
+
expect(result.commitType).toBe('chore');
|
|
87
|
+
expect(result.modifiedMessage).toMatch(/^chore:/);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('should detect revert type from message', async () => {
|
|
91
|
+
const result = await gitCommit.process('Revert previous commit');
|
|
92
|
+
|
|
93
|
+
expect(result.commitType).toBe('revert');
|
|
94
|
+
expect(result.modifiedMessage).toMatch(/^revert:/);
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
describe('preserving existing prefixes', () => {
|
|
99
|
+
it('should not add duplicate prefix if already present', async () => {
|
|
100
|
+
const result = await gitCommit.process('feat: add user authentication');
|
|
101
|
+
|
|
102
|
+
expect(result.commitType).toBe('feat');
|
|
103
|
+
// Should not have double prefix
|
|
104
|
+
expect(result.modifiedMessage).not.toMatch(/^feat:.*feat:/);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it('should detect type from existing prefix', async () => {
|
|
108
|
+
const result = await gitCommit.process('fix(auth): resolve login issue');
|
|
109
|
+
|
|
110
|
+
expect(result.commitType).toBe('fix');
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it('should handle scoped commits', async () => {
|
|
114
|
+
const result = await gitCommit.process('feat(api): add new endpoint');
|
|
115
|
+
|
|
116
|
+
expect(result.commitType).toBe('feat');
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
describe('ticket extraction', () => {
|
|
121
|
+
it('should extract JIRA ticket from branch name', async () => {
|
|
122
|
+
const result = await gitCommit.process('Add feature', 'feature/ABC-123-new-feature');
|
|
123
|
+
|
|
124
|
+
expect(result.ticketReference).toBe('ABC-123');
|
|
125
|
+
expect(result.modifiedMessage).toContain('Refs: ABC-123');
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it('should extract GitHub issue from branch name', async () => {
|
|
129
|
+
const result = await gitCommit.process('Fix bug', 'fix/#456-login-bug');
|
|
130
|
+
|
|
131
|
+
expect(result.ticketReference).toBe('#456');
|
|
132
|
+
expect(result.modifiedMessage).toContain('Refs: #456');
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it('should not duplicate ticket if already in message', async () => {
|
|
136
|
+
const result = await gitCommit.process('Fix ABC-123 bug', 'feature/ABC-123-test');
|
|
137
|
+
|
|
138
|
+
// Should only appear once
|
|
139
|
+
const matches = result.modifiedMessage.match(/ABC-123/g);
|
|
140
|
+
expect(matches).toBeDefined();
|
|
141
|
+
expect(matches!.length).toBeLessThanOrEqual(2);
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
describe('co-author addition', () => {
|
|
146
|
+
it('should add co-author by default', async () => {
|
|
147
|
+
const result = await gitCommit.process('Add feature');
|
|
148
|
+
|
|
149
|
+
expect(result.coAuthorAdded).toBe(true);
|
|
150
|
+
expect(result.modifiedMessage).toContain('Co-Authored-By:');
|
|
151
|
+
expect(result.modifiedMessage).toContain('Claude');
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it('should add Claude Code reference', async () => {
|
|
155
|
+
const result = await gitCommit.process('Add feature');
|
|
156
|
+
|
|
157
|
+
expect(result.modifiedMessage).toContain('Claude Code');
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it('should not duplicate co-author if already present', async () => {
|
|
161
|
+
const result = await gitCommit.process('Add feature\n\nCo-Authored-By: Someone <some@email.com>');
|
|
162
|
+
|
|
163
|
+
// Should still add Claude co-author
|
|
164
|
+
expect(result.modifiedMessage).toContain('Claude');
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
describe('validation', () => {
|
|
169
|
+
it('should warn about missing conventional prefix', async () => {
|
|
170
|
+
const hook = createGitCommitHook(registry, { requireConventional: true });
|
|
171
|
+
const result = await hook.process('Some random message');
|
|
172
|
+
|
|
173
|
+
// Message should be modified to include prefix
|
|
174
|
+
expect(result.suggestions).toBeDefined();
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
it('should warn about long subject line', async () => {
|
|
178
|
+
const longMessage = 'This is a very long commit message that exceeds the recommended length for commit subject lines which should be concise';
|
|
179
|
+
const result = await gitCommit.process(longMessage);
|
|
180
|
+
|
|
181
|
+
expect(result.validationIssues).toBeDefined();
|
|
182
|
+
expect(result.validationIssues!.some(i => i.type === 'length')).toBe(true);
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it('should warn about trailing period in subject', async () => {
|
|
186
|
+
const result = await gitCommit.process('Add new feature.');
|
|
187
|
+
|
|
188
|
+
expect(result.validationIssues).toBeDefined();
|
|
189
|
+
expect(result.validationIssues!.some(i => i.description.includes('period'))).toBe(true);
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
it('should detect breaking change indicator', async () => {
|
|
193
|
+
const result = await gitCommit.process('feat!: major API change');
|
|
194
|
+
|
|
195
|
+
expect(result.validationIssues).toBeDefined();
|
|
196
|
+
expect(result.validationIssues!.some(i => i.type === 'breaking')).toBe(true);
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
it('should detect BREAKING CHANGE footer', async () => {
|
|
200
|
+
const result = await gitCommit.process('feat: add feature\n\nBREAKING CHANGE: API changed');
|
|
201
|
+
|
|
202
|
+
expect(result.validationIssues).toBeDefined();
|
|
203
|
+
expect(result.validationIssues!.some(i => i.type === 'breaking')).toBe(true);
|
|
204
|
+
});
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
describe('configuration', () => {
|
|
208
|
+
it('should respect maxSubjectLength config', async () => {
|
|
209
|
+
const hook = createGitCommitHook(registry, { maxSubjectLength: 50 });
|
|
210
|
+
const result = await hook.process('This is a message that is definitely longer than fifty characters');
|
|
211
|
+
|
|
212
|
+
expect(result.validationIssues).toBeDefined();
|
|
213
|
+
expect(result.validationIssues!.some(i => i.type === 'length')).toBe(true);
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
it('should allow disabling co-author', async () => {
|
|
217
|
+
const hook = createGitCommitHook(registry, { addCoAuthor: false });
|
|
218
|
+
const result = await hook.process('Add feature');
|
|
219
|
+
|
|
220
|
+
expect(result.coAuthorAdded).toBe(false);
|
|
221
|
+
expect(result.modifiedMessage).not.toContain('Co-Authored-By');
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
it('should allow disabling Claude reference', async () => {
|
|
225
|
+
const hook = createGitCommitHook(registry, { addClaudeReference: false });
|
|
226
|
+
const result = await hook.process('Add feature');
|
|
227
|
+
|
|
228
|
+
expect(result.modifiedMessage).not.toContain('Claude Code');
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
it('should allow custom co-author', async () => {
|
|
232
|
+
const hook = createGitCommitHook(registry, {
|
|
233
|
+
coAuthor: { name: 'Custom AI', email: 'ai@example.com' },
|
|
234
|
+
});
|
|
235
|
+
const result = await hook.process('Add feature');
|
|
236
|
+
|
|
237
|
+
expect(result.modifiedMessage).toContain('Custom AI');
|
|
238
|
+
expect(result.modifiedMessage).toContain('ai@example.com');
|
|
239
|
+
});
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
describe('helper methods', () => {
|
|
243
|
+
it('should format message for git heredoc', () => {
|
|
244
|
+
const formatted = gitCommit.formatForGit('Test message');
|
|
245
|
+
|
|
246
|
+
expect(formatted).toContain('$(cat <<');
|
|
247
|
+
expect(formatted).toContain('Test message');
|
|
248
|
+
expect(formatted).toContain('EOF');
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
it('should generate full commit command', () => {
|
|
252
|
+
const command = gitCommit.generateCommitCommand('Test message');
|
|
253
|
+
|
|
254
|
+
expect(command).toContain('git commit -m');
|
|
255
|
+
expect(command).toContain('Test message');
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
it('should get commit type description', () => {
|
|
259
|
+
expect(gitCommit.getCommitTypeDescription('feat')).toContain('feature');
|
|
260
|
+
expect(gitCommit.getCommitTypeDescription('fix')).toContain('bug fix');
|
|
261
|
+
expect(gitCommit.getCommitTypeDescription('docs')).toContain('Documentation');
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
it('should get all commit types', () => {
|
|
265
|
+
const types = gitCommit.getAllCommitTypes();
|
|
266
|
+
|
|
267
|
+
expect(types.length).toBeGreaterThan(0);
|
|
268
|
+
expect(types.some(t => t.type === 'feat')).toBe(true);
|
|
269
|
+
expect(types.some(t => t.type === 'fix')).toBe(true);
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
it('should get current config', () => {
|
|
273
|
+
const config = gitCommit.getConfig();
|
|
274
|
+
|
|
275
|
+
expect(config.maxSubjectLength).toBeDefined();
|
|
276
|
+
expect(config.addCoAuthor).toBe(true);
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
it('should update config', () => {
|
|
280
|
+
gitCommit.setConfig({ addCoAuthor: false });
|
|
281
|
+
const config = gitCommit.getConfig();
|
|
282
|
+
|
|
283
|
+
expect(config.addCoAuthor).toBe(false);
|
|
284
|
+
});
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
describe('message case handling', () => {
|
|
288
|
+
it('should lowercase first letter after prefix', async () => {
|
|
289
|
+
const result = await gitCommit.process('Add new feature');
|
|
290
|
+
|
|
291
|
+
expect(result.modifiedMessage).toMatch(/^feat: add/);
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
it('should preserve acronyms', async () => {
|
|
295
|
+
const result = await gitCommit.process('Add API endpoint');
|
|
296
|
+
|
|
297
|
+
// Should not lowercase API
|
|
298
|
+
expect(result.modifiedMessage).toMatch(/API/);
|
|
299
|
+
});
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
describe('full message processing', () => {
|
|
303
|
+
it('should process complete message with all modifications', async () => {
|
|
304
|
+
const result = await gitCommit.process(
|
|
305
|
+
'Implement user authentication',
|
|
306
|
+
'feature/AUTH-123-login'
|
|
307
|
+
);
|
|
308
|
+
|
|
309
|
+
// Should have commit type prefix
|
|
310
|
+
expect(result.modifiedMessage).toMatch(/^feat:/);
|
|
311
|
+
|
|
312
|
+
// Should have ticket reference
|
|
313
|
+
expect(result.modifiedMessage).toContain('AUTH-123');
|
|
314
|
+
|
|
315
|
+
// Should have Claude reference
|
|
316
|
+
expect(result.modifiedMessage).toContain('Claude Code');
|
|
317
|
+
|
|
318
|
+
// Should have co-author
|
|
319
|
+
expect(result.modifiedMessage).toContain('Co-Authored-By');
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
it('should return original message unchanged in result', async () => {
|
|
323
|
+
const original = 'Original message';
|
|
324
|
+
const result = await gitCommit.process(original);
|
|
325
|
+
|
|
326
|
+
expect(result.originalMessage).toBe(original);
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
it('should track suggestions for modifications', async () => {
|
|
330
|
+
const result = await gitCommit.process('Add feature', 'feature/JIRA-123');
|
|
331
|
+
|
|
332
|
+
expect(result.suggestions).toBeDefined();
|
|
333
|
+
expect(result.suggestions!.length).toBeGreaterThan(0);
|
|
334
|
+
});
|
|
335
|
+
});
|
|
336
|
+
});
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* V3 Hooks Tests Index
|
|
3
|
+
*
|
|
4
|
+
* Exports all hook tests for the V3 hooks system.
|
|
5
|
+
*
|
|
6
|
+
* @module v3/shared/hooks/__tests__
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
// Test files are automatically discovered by vitest
|
|
10
|
+
// This file serves as documentation of available tests
|
|
11
|
+
|
|
12
|
+
export const testFiles = [
|
|
13
|
+
'./task-hooks.test.ts',
|
|
14
|
+
'./session-hooks.test.ts',
|
|
15
|
+
'./bash-safety.test.ts',
|
|
16
|
+
'./file-organization.test.ts',
|
|
17
|
+
'./git-commit.test.ts',
|
|
18
|
+
];
|
|
19
|
+
|
|
20
|
+
export const testCategories = {
|
|
21
|
+
'Core Hooks': ['task-hooks.test.ts', 'session-hooks.test.ts'],
|
|
22
|
+
'Safety Hooks': ['bash-safety.test.ts', 'file-organization.test.ts', 'git-commit.test.ts'],
|
|
23
|
+
};
|