@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,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
|
+
}
|