@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.
Files changed (96) hide show
  1. package/README.md +323 -0
  2. package/__tests__/hooks/bash-safety.test.ts +289 -0
  3. package/__tests__/hooks/file-organization.test.ts +335 -0
  4. package/__tests__/hooks/git-commit.test.ts +336 -0
  5. package/__tests__/hooks/index.ts +23 -0
  6. package/__tests__/hooks/session-hooks.test.ts +357 -0
  7. package/__tests__/hooks/task-hooks.test.ts +193 -0
  8. package/docs/EVENTS_IMPLEMENTATION_SUMMARY.md +388 -0
  9. package/docs/EVENTS_QUICK_REFERENCE.md +470 -0
  10. package/docs/EVENTS_README.md +352 -0
  11. package/package.json +39 -0
  12. package/src/core/config/defaults.ts +207 -0
  13. package/src/core/config/index.ts +15 -0
  14. package/src/core/config/loader.ts +271 -0
  15. package/src/core/config/schema.ts +188 -0
  16. package/src/core/config/validator.ts +209 -0
  17. package/src/core/event-bus.ts +236 -0
  18. package/src/core/index.ts +22 -0
  19. package/src/core/interfaces/agent.interface.ts +251 -0
  20. package/src/core/interfaces/coordinator.interface.ts +363 -0
  21. package/src/core/interfaces/event.interface.ts +267 -0
  22. package/src/core/interfaces/index.ts +19 -0
  23. package/src/core/interfaces/memory.interface.ts +332 -0
  24. package/src/core/interfaces/task.interface.ts +223 -0
  25. package/src/core/orchestrator/event-coordinator.ts +122 -0
  26. package/src/core/orchestrator/health-monitor.ts +214 -0
  27. package/src/core/orchestrator/index.ts +89 -0
  28. package/src/core/orchestrator/lifecycle-manager.ts +263 -0
  29. package/src/core/orchestrator/session-manager.ts +279 -0
  30. package/src/core/orchestrator/task-manager.ts +317 -0
  31. package/src/events/domain-events.ts +584 -0
  32. package/src/events/event-store.test.ts +387 -0
  33. package/src/events/event-store.ts +588 -0
  34. package/src/events/example-usage.ts +293 -0
  35. package/src/events/index.ts +90 -0
  36. package/src/events/projections.ts +561 -0
  37. package/src/events/state-reconstructor.ts +349 -0
  38. package/src/events.ts +367 -0
  39. package/src/hooks/INTEGRATION.md +658 -0
  40. package/src/hooks/README.md +532 -0
  41. package/src/hooks/example-usage.ts +499 -0
  42. package/src/hooks/executor.ts +379 -0
  43. package/src/hooks/hooks.test.ts +421 -0
  44. package/src/hooks/index.ts +131 -0
  45. package/src/hooks/registry.ts +333 -0
  46. package/src/hooks/safety/bash-safety.ts +604 -0
  47. package/src/hooks/safety/file-organization.ts +473 -0
  48. package/src/hooks/safety/git-commit.ts +623 -0
  49. package/src/hooks/safety/index.ts +46 -0
  50. package/src/hooks/session-hooks.ts +559 -0
  51. package/src/hooks/task-hooks.ts +513 -0
  52. package/src/hooks/types.ts +357 -0
  53. package/src/hooks/verify-exports.test.ts +125 -0
  54. package/src/index.ts +195 -0
  55. package/src/mcp/connection-pool.ts +438 -0
  56. package/src/mcp/index.ts +183 -0
  57. package/src/mcp/server.ts +774 -0
  58. package/src/mcp/session-manager.ts +428 -0
  59. package/src/mcp/tool-registry.ts +566 -0
  60. package/src/mcp/transport/http.ts +557 -0
  61. package/src/mcp/transport/index.ts +294 -0
  62. package/src/mcp/transport/stdio.ts +324 -0
  63. package/src/mcp/transport/websocket.ts +484 -0
  64. package/src/mcp/types.ts +565 -0
  65. package/src/plugin-interface.ts +663 -0
  66. package/src/plugin-loader.ts +638 -0
  67. package/src/plugin-registry.ts +604 -0
  68. package/src/plugins/index.ts +34 -0
  69. package/src/plugins/official/hive-mind-plugin.ts +330 -0
  70. package/src/plugins/official/index.ts +24 -0
  71. package/src/plugins/official/maestro-plugin.ts +508 -0
  72. package/src/plugins/types.ts +108 -0
  73. package/src/resilience/bulkhead.ts +277 -0
  74. package/src/resilience/circuit-breaker.ts +326 -0
  75. package/src/resilience/index.ts +26 -0
  76. package/src/resilience/rate-limiter.ts +420 -0
  77. package/src/resilience/retry.ts +224 -0
  78. package/src/security/index.ts +39 -0
  79. package/src/security/input-validation.ts +265 -0
  80. package/src/security/secure-random.ts +159 -0
  81. package/src/services/index.ts +16 -0
  82. package/src/services/v3-progress.service.ts +505 -0
  83. package/src/types/agent.types.ts +144 -0
  84. package/src/types/index.ts +22 -0
  85. package/src/types/mcp.types.ts +300 -0
  86. package/src/types/memory.types.ts +263 -0
  87. package/src/types/swarm.types.ts +255 -0
  88. package/src/types/task.types.ts +205 -0
  89. package/src/types.ts +367 -0
  90. package/src/utils/secure-logger.d.ts +69 -0
  91. package/src/utils/secure-logger.d.ts.map +1 -0
  92. package/src/utils/secure-logger.js +208 -0
  93. package/src/utils/secure-logger.js.map +1 -0
  94. package/src/utils/secure-logger.ts +257 -0
  95. package/tmp.json +0 -0
  96. package/tsconfig.json +9 -0
@@ -0,0 +1,473 @@
1
+ /**
2
+ * V3 File Organization Hook
3
+ *
4
+ * TypeScript conversion of V2 file-hook.sh.
5
+ * Enforces file organization, blocks writes to root folder,
6
+ * suggests proper directories, and recommends formatters.
7
+ *
8
+ * @module v3/shared/hooks/safety/file-organization
9
+ */
10
+
11
+ import * as path from 'path';
12
+ import {
13
+ HookEvent,
14
+ HookContext,
15
+ HookResult,
16
+ HookPriority,
17
+ FileOperationInfo,
18
+ } from '../types.js';
19
+ import { HookRegistry } from '../registry.js';
20
+
21
+ /**
22
+ * File organization hook result
23
+ */
24
+ export interface FileOrganizationResult extends HookResult {
25
+ /** Whether the file operation should be blocked */
26
+ blocked: boolean;
27
+ /** Reason for blocking */
28
+ blockReason?: string;
29
+ /** Suggested new path */
30
+ suggestedPath?: string;
31
+ /** Suggested directory */
32
+ suggestedDirectory?: string;
33
+ /** Formatter recommendation */
34
+ formatter?: FormatterRecommendation;
35
+ /** Linter recommendation */
36
+ linter?: LinterRecommendation;
37
+ /** File type detected */
38
+ fileType?: string;
39
+ /** Warnings */
40
+ warnings?: string[];
41
+ /** Organization issues detected */
42
+ issues?: OrganizationIssue[];
43
+ }
44
+
45
+ /**
46
+ * Formatter recommendation
47
+ */
48
+ export interface FormatterRecommendation {
49
+ /** Formatter name */
50
+ name: string;
51
+ /** Command to run */
52
+ command: string;
53
+ /** Config file to check for */
54
+ configFile?: string;
55
+ /** Whether config exists */
56
+ configExists?: boolean;
57
+ }
58
+
59
+ /**
60
+ * Linter recommendation
61
+ */
62
+ export interface LinterRecommendation {
63
+ /** Linter name */
64
+ name: string;
65
+ /** Command to run */
66
+ command: string;
67
+ /** Config file to check for */
68
+ configFile?: string;
69
+ }
70
+
71
+ /**
72
+ * Organization issue
73
+ */
74
+ export interface OrganizationIssue {
75
+ /** Issue type */
76
+ type: 'wrong-directory' | 'naming-convention' | 'missing-config' | 'root-write';
77
+ /** Issue severity */
78
+ severity: 'info' | 'warning' | 'error';
79
+ /** Issue description */
80
+ description: string;
81
+ /** Suggested fix */
82
+ suggestedFix?: string;
83
+ }
84
+
85
+ /**
86
+ * File type mapping to directories
87
+ */
88
+ const FILE_TYPE_DIRECTORIES: Array<{
89
+ /** File extension or pattern */
90
+ pattern: RegExp;
91
+ /** Suggested directories */
92
+ directories: string[];
93
+ /** File type name */
94
+ type: string;
95
+ /** Whether to block root writes */
96
+ blockRoot: boolean;
97
+ }> = [
98
+ // Test files (MUST come before source files - more specific patterns first)
99
+ { pattern: /\.test\.(ts|tsx|js|jsx)$/, directories: ['tests/', '__tests__/', 'test/'], type: 'test file', blockRoot: true },
100
+ { pattern: /\.spec\.(ts|tsx|js|jsx)$/, directories: ['tests/', '__tests__/', 'test/', 'spec/'], type: 'spec file', blockRoot: true },
101
+ { pattern: /_test\.go$/, directories: ['tests/', 'test/'], type: 'Go test file', blockRoot: true },
102
+ { pattern: /test_.*\.py$/, directories: ['tests/', 'test/'], type: 'Python test file', blockRoot: true },
103
+ { pattern: /.*_test\.py$/, directories: ['tests/', 'test/'], type: 'Python test file', blockRoot: true },
104
+
105
+ // Source files
106
+ { pattern: /\.(ts|tsx)$/, directories: ['src/', 'lib/'], type: 'TypeScript source', blockRoot: true },
107
+ { pattern: /\.(js|jsx|mjs|cjs)$/, directories: ['src/', 'lib/', 'dist/'], type: 'JavaScript source', blockRoot: true },
108
+ { pattern: /\.py$/, directories: ['src/', 'lib/', 'app/'], type: 'Python source', blockRoot: true },
109
+ { pattern: /\.go$/, directories: ['cmd/', 'pkg/', 'internal/'], type: 'Go source', blockRoot: true },
110
+ { pattern: /\.rs$/, directories: ['src/'], type: 'Rust source', blockRoot: true },
111
+ { pattern: /\.java$/, directories: ['src/main/java/', 'src/'], type: 'Java source', blockRoot: true },
112
+ { pattern: /\.rb$/, directories: ['lib/', 'app/'], type: 'Ruby source', blockRoot: true },
113
+ { pattern: /\.php$/, directories: ['src/', 'app/'], type: 'PHP source', blockRoot: true },
114
+ { pattern: /\.cs$/, directories: ['src/'], type: 'C# source', blockRoot: true },
115
+ { pattern: /\.cpp?$/, directories: ['src/'], type: 'C/C++ source', blockRoot: true },
116
+ { pattern: /\.swift$/, directories: ['Sources/'], type: 'Swift source', blockRoot: true },
117
+ { pattern: /\.kt$/, directories: ['src/main/kotlin/', 'src/'], type: 'Kotlin source', blockRoot: true },
118
+
119
+ // Config files (usually allowed at root)
120
+ { pattern: /\.(json|yaml|yml|toml)$/, directories: ['config/', './', 'configs/'], type: 'config file', blockRoot: false },
121
+ { pattern: /\.(env|env\.[a-z]+)$/, directories: ['./'], type: 'environment file', blockRoot: false },
122
+
123
+ // Documentation
124
+ { pattern: /\.md$/, directories: ['docs/', './'], type: 'Markdown documentation', blockRoot: false },
125
+ { pattern: /\.rst$/, directories: ['docs/'], type: 'reStructuredText documentation', blockRoot: true },
126
+ { pattern: /\.adoc$/, directories: ['docs/'], type: 'AsciiDoc documentation', blockRoot: true },
127
+
128
+ // Assets
129
+ { pattern: /\.(css|scss|sass|less)$/, directories: ['styles/', 'src/styles/', 'assets/css/'], type: 'stylesheet', blockRoot: true },
130
+ { pattern: /\.(png|jpg|jpeg|gif|svg|ico)$/, directories: ['assets/', 'public/', 'images/', 'static/'], type: 'image', blockRoot: true },
131
+ { pattern: /\.(woff2?|ttf|otf|eot)$/, directories: ['assets/fonts/', 'fonts/', 'public/fonts/'], type: 'font', blockRoot: true },
132
+
133
+ // Scripts
134
+ { pattern: /\.sh$/, directories: ['scripts/', 'bin/'], type: 'shell script', blockRoot: true },
135
+ { pattern: /\.ps1$/, directories: ['scripts/', 'bin/'], type: 'PowerShell script', blockRoot: true },
136
+
137
+ // Data
138
+ { pattern: /\.sql$/, directories: ['migrations/', 'db/', 'database/'], type: 'SQL file', blockRoot: true },
139
+ { pattern: /\.csv$/, directories: ['data/', 'fixtures/', 'test/fixtures/'], type: 'CSV data', blockRoot: true },
140
+ ];
141
+
142
+ /**
143
+ * Formatter recommendations by file extension
144
+ */
145
+ const FORMATTERS: Record<string, FormatterRecommendation> = {
146
+ '.ts': { name: 'Prettier', command: 'prettier --write', configFile: '.prettierrc' },
147
+ '.tsx': { name: 'Prettier', command: 'prettier --write', configFile: '.prettierrc' },
148
+ '.js': { name: 'Prettier', command: 'prettier --write', configFile: '.prettierrc' },
149
+ '.jsx': { name: 'Prettier', command: 'prettier --write', configFile: '.prettierrc' },
150
+ '.json': { name: 'Prettier', command: 'prettier --write', configFile: '.prettierrc' },
151
+ '.md': { name: 'Prettier', command: 'prettier --write', configFile: '.prettierrc' },
152
+ '.yaml': { name: 'Prettier', command: 'prettier --write', configFile: '.prettierrc' },
153
+ '.yml': { name: 'Prettier', command: 'prettier --write', configFile: '.prettierrc' },
154
+ '.py': { name: 'Black', command: 'black', configFile: 'pyproject.toml' },
155
+ '.go': { name: 'gofmt', command: 'gofmt -w', configFile: undefined },
156
+ '.rs': { name: 'rustfmt', command: 'rustfmt', configFile: 'rustfmt.toml' },
157
+ '.java': { name: 'google-java-format', command: 'google-java-format -i', configFile: undefined },
158
+ '.rb': { name: 'RuboCop', command: 'rubocop -a', configFile: '.rubocop.yml' },
159
+ '.php': { name: 'PHP-CS-Fixer', command: 'php-cs-fixer fix', configFile: '.php-cs-fixer.php' },
160
+ '.cs': { name: 'dotnet-format', command: 'dotnet format', configFile: '.editorconfig' },
161
+ '.swift': { name: 'swift-format', command: 'swift-format -i', configFile: '.swift-format' },
162
+ '.kt': { name: 'ktlint', command: 'ktlint -F', configFile: '.editorconfig' },
163
+ '.css': { name: 'Prettier', command: 'prettier --write', configFile: '.prettierrc' },
164
+ '.scss': { name: 'Prettier', command: 'prettier --write', configFile: '.prettierrc' },
165
+ '.html': { name: 'Prettier', command: 'prettier --write', configFile: '.prettierrc' },
166
+ };
167
+
168
+ /**
169
+ * Linter recommendations by file extension
170
+ */
171
+ const LINTERS: Record<string, LinterRecommendation> = {
172
+ '.ts': { name: 'ESLint', command: 'eslint --fix', configFile: '.eslintrc' },
173
+ '.tsx': { name: 'ESLint', command: 'eslint --fix', configFile: '.eslintrc' },
174
+ '.js': { name: 'ESLint', command: 'eslint --fix', configFile: '.eslintrc' },
175
+ '.jsx': { name: 'ESLint', command: 'eslint --fix', configFile: '.eslintrc' },
176
+ '.py': { name: 'Pylint', command: 'pylint', configFile: 'pylintrc' },
177
+ '.go': { name: 'golangci-lint', command: 'golangci-lint run', configFile: '.golangci.yml' },
178
+ '.rs': { name: 'Clippy', command: 'cargo clippy', configFile: 'Cargo.toml' },
179
+ '.rb': { name: 'RuboCop', command: 'rubocop', configFile: '.rubocop.yml' },
180
+ '.php': { name: 'PHPStan', command: 'phpstan analyse', configFile: 'phpstan.neon' },
181
+ };
182
+
183
+ /**
184
+ * Naming convention checks
185
+ */
186
+ const NAMING_CONVENTIONS: Array<{
187
+ /** Pattern to match file names */
188
+ pattern: RegExp;
189
+ /** Expected naming convention */
190
+ convention: 'kebab-case' | 'camelCase' | 'PascalCase' | 'snake_case';
191
+ /** File types this applies to */
192
+ fileTypes: RegExp;
193
+ }> = [
194
+ { pattern: /^[a-z][a-z0-9-]*\.[a-z]+$/, convention: 'kebab-case', fileTypes: /\.(tsx?|jsx?|css|scss)$/ },
195
+ { pattern: /^[a-z][a-z0-9_]*\.[a-z]+$/, convention: 'snake_case', fileTypes: /\.py$/ },
196
+ { pattern: /^[a-z][a-z0-9_]*\.[a-z]+$/, convention: 'snake_case', fileTypes: /\.go$/ },
197
+ { pattern: /^[A-Z][a-zA-Z0-9]*\.[a-z]+$/, convention: 'PascalCase', fileTypes: /\.(java|kt|cs)$/ },
198
+ ];
199
+
200
+ /**
201
+ * File Organization Hook Manager
202
+ */
203
+ export class FileOrganizationHook {
204
+ private registry: HookRegistry;
205
+ private projectRoot: string = process.cwd();
206
+
207
+ constructor(registry: HookRegistry) {
208
+ this.registry = registry;
209
+ this.registerHooks();
210
+ }
211
+
212
+ /**
213
+ * Register file organization hooks
214
+ */
215
+ private registerHooks(): void {
216
+ // Pre-edit hook
217
+ this.registry.register(
218
+ HookEvent.PreEdit,
219
+ this.analyzeFileOperation.bind(this),
220
+ HookPriority.High,
221
+ { name: 'file-organization:pre-edit' }
222
+ );
223
+
224
+ // Pre-write hook
225
+ this.registry.register(
226
+ HookEvent.PreWrite,
227
+ this.analyzeFileOperation.bind(this),
228
+ HookPriority.High,
229
+ { name: 'file-organization:pre-write' }
230
+ );
231
+ }
232
+
233
+ /**
234
+ * Analyze file operation for organization issues
235
+ */
236
+ async analyzeFileOperation(context: HookContext): Promise<FileOrganizationResult> {
237
+ const fileInfo = context.file;
238
+ if (!fileInfo) {
239
+ return this.createResult(false, []);
240
+ }
241
+
242
+ const filePath = fileInfo.path;
243
+ const fileName = path.basename(filePath);
244
+ const dirName = path.dirname(filePath);
245
+ const ext = path.extname(filePath);
246
+
247
+ const issues: OrganizationIssue[] = [];
248
+ const warnings: string[] = [];
249
+ let blocked = false;
250
+ let blockReason: string | undefined;
251
+ let suggestedPath: string | undefined;
252
+ let suggestedDirectory: string | undefined;
253
+
254
+ // Check if writing to root folder
255
+ const isRootWrite = this.isRootDirectory(dirName);
256
+ const fileTypeInfo = this.getFileTypeInfo(fileName);
257
+
258
+ if (isRootWrite && fileTypeInfo?.blockRoot) {
259
+ blocked = true;
260
+ blockReason = `Source files should not be written to root folder. Suggested: ${fileTypeInfo.directories[0]}`;
261
+ suggestedDirectory = fileTypeInfo.directories[0];
262
+ suggestedPath = path.join(suggestedDirectory, fileName);
263
+
264
+ issues.push({
265
+ type: 'root-write',
266
+ severity: 'error',
267
+ description: `${fileTypeInfo.type} files should not be in the root directory`,
268
+ suggestedFix: `Move to ${suggestedDirectory}`,
269
+ });
270
+ }
271
+
272
+ // Check if file is in wrong directory
273
+ if (!isRootWrite && fileTypeInfo) {
274
+ const isInCorrectDir = fileTypeInfo.directories.some(dir =>
275
+ this.normalizePath(dirName).includes(this.normalizePath(dir))
276
+ );
277
+
278
+ if (!isInCorrectDir) {
279
+ issues.push({
280
+ type: 'wrong-directory',
281
+ severity: 'warning',
282
+ description: `${fileTypeInfo.type} typically goes in: ${fileTypeInfo.directories.join(' or ')}`,
283
+ suggestedFix: `Consider moving to ${fileTypeInfo.directories[0]}`,
284
+ });
285
+ warnings.push(`File may be in wrong directory. Expected: ${fileTypeInfo.directories.join(' or ')}`);
286
+ }
287
+ }
288
+
289
+ // Check naming convention
290
+ const namingIssue = this.checkNamingConvention(fileName, ext);
291
+ if (namingIssue) {
292
+ issues.push(namingIssue);
293
+ warnings.push(namingIssue.description);
294
+ }
295
+
296
+ // Get formatter recommendation
297
+ const formatter = this.getFormatterRecommendation(ext);
298
+
299
+ // Get linter recommendation
300
+ const linter = this.getLinterRecommendation(ext);
301
+
302
+ return {
303
+ success: true,
304
+ blocked,
305
+ blockReason,
306
+ suggestedPath,
307
+ suggestedDirectory,
308
+ formatter,
309
+ linter,
310
+ fileType: fileTypeInfo?.type,
311
+ warnings: warnings.length > 0 ? warnings : undefined,
312
+ issues: issues.length > 0 ? issues : undefined,
313
+ abort: blocked,
314
+ data: blocked ? undefined : {
315
+ file: {
316
+ ...fileInfo,
317
+ path: suggestedPath || filePath,
318
+ },
319
+ metadata: {
320
+ formatter: formatter?.command,
321
+ linter: linter?.command,
322
+ },
323
+ },
324
+ };
325
+ }
326
+
327
+ /**
328
+ * Check if directory is root
329
+ */
330
+ private isRootDirectory(dirName: string): boolean {
331
+ const normalized = this.normalizePath(dirName);
332
+ return normalized === '.' ||
333
+ normalized === './' ||
334
+ normalized === '' ||
335
+ normalized === this.normalizePath(this.projectRoot);
336
+ }
337
+
338
+ /**
339
+ * Normalize path for comparison
340
+ */
341
+ private normalizePath(p: string): string {
342
+ return p.replace(/\\/g, '/').replace(/^\.\//, '').replace(/\/$/, '');
343
+ }
344
+
345
+ /**
346
+ * Get file type information
347
+ */
348
+ private getFileTypeInfo(fileName: string): {
349
+ directories: string[];
350
+ type: string;
351
+ blockRoot: boolean;
352
+ } | null {
353
+ for (const info of FILE_TYPE_DIRECTORIES) {
354
+ if (info.pattern.test(fileName)) {
355
+ return {
356
+ directories: info.directories,
357
+ type: info.type,
358
+ blockRoot: info.blockRoot,
359
+ };
360
+ }
361
+ }
362
+ return null;
363
+ }
364
+
365
+ /**
366
+ * Check naming convention
367
+ */
368
+ private checkNamingConvention(fileName: string, ext: string): OrganizationIssue | null {
369
+ for (const rule of NAMING_CONVENTIONS) {
370
+ if (rule.fileTypes.test(ext)) {
371
+ const baseName = fileName.replace(ext, '');
372
+ if (!rule.pattern.test(fileName)) {
373
+ return {
374
+ type: 'naming-convention',
375
+ severity: 'info',
376
+ description: `File name may not follow ${rule.convention} convention`,
377
+ suggestedFix: `Consider renaming to follow ${rule.convention}`,
378
+ };
379
+ }
380
+ }
381
+ }
382
+ return null;
383
+ }
384
+
385
+ /**
386
+ * Get formatter recommendation
387
+ */
388
+ private getFormatterRecommendation(ext: string): FormatterRecommendation | undefined {
389
+ return FORMATTERS[ext];
390
+ }
391
+
392
+ /**
393
+ * Get linter recommendation
394
+ */
395
+ private getLinterRecommendation(ext: string): LinterRecommendation | undefined {
396
+ return LINTERS[ext];
397
+ }
398
+
399
+ /**
400
+ * Create result object
401
+ */
402
+ private createResult(blocked: boolean, issues: OrganizationIssue[]): FileOrganizationResult {
403
+ return {
404
+ success: true,
405
+ blocked,
406
+ issues: issues.length > 0 ? issues : undefined,
407
+ };
408
+ }
409
+
410
+ /**
411
+ * Manually analyze a file path
412
+ */
413
+ async analyze(filePath: string): Promise<FileOrganizationResult> {
414
+ const context: HookContext = {
415
+ event: HookEvent.PreEdit,
416
+ timestamp: new Date(),
417
+ file: {
418
+ path: filePath,
419
+ operation: 'write',
420
+ },
421
+ };
422
+
423
+ return this.analyzeFileOperation(context);
424
+ }
425
+
426
+ /**
427
+ * Get suggested directory for a file
428
+ */
429
+ getSuggestedDirectory(fileName: string): string | null {
430
+ const info = this.getFileTypeInfo(fileName);
431
+ return info?.directories[0] || null;
432
+ }
433
+
434
+ /**
435
+ * Check if a file path would be blocked
436
+ */
437
+ wouldBlock(filePath: string): boolean {
438
+ const fileName = path.basename(filePath);
439
+ const dirName = path.dirname(filePath);
440
+ const isRoot = this.isRootDirectory(dirName);
441
+ const info = this.getFileTypeInfo(fileName);
442
+
443
+ return isRoot && (info?.blockRoot ?? false);
444
+ }
445
+
446
+ /**
447
+ * Set project root directory
448
+ */
449
+ setProjectRoot(root: string): void {
450
+ this.projectRoot = root;
451
+ }
452
+
453
+ /**
454
+ * Get all formatter recommendations
455
+ */
456
+ getAllFormatters(): Record<string, FormatterRecommendation> {
457
+ return { ...FORMATTERS };
458
+ }
459
+
460
+ /**
461
+ * Get all linter recommendations
462
+ */
463
+ getAllLinters(): Record<string, LinterRecommendation> {
464
+ return { ...LINTERS };
465
+ }
466
+ }
467
+
468
+ /**
469
+ * Create file organization hook
470
+ */
471
+ export function createFileOrganizationHook(registry: HookRegistry): FileOrganizationHook {
472
+ return new FileOrganizationHook(registry);
473
+ }