@oxog/codeguardian 1.0.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.
@@ -0,0 +1,487 @@
1
+ import ts from 'typescript';
2
+
3
+ /** Severity level for findings. */
4
+ type Severity = 'critical' | 'error' | 'warning' | 'info';
5
+ /** Detected role of a source file based on path and content. */
6
+ type FileRole = 'controller' | 'service' | 'repository' | 'util' | 'type' | 'config' | 'test' | 'unknown';
7
+ /**
8
+ * Information about a single import statement.
9
+ *
10
+ * @example
11
+ * ```typescript
12
+ * const info: ImportInfo = {
13
+ * source: './user.service',
14
+ * specifiers: ['UserService'],
15
+ * isTypeOnly: false,
16
+ * };
17
+ * ```
18
+ */
19
+ interface ImportInfo {
20
+ /** Module specifier (e.g., './user.service') */
21
+ source: string;
22
+ /** Imported names */
23
+ specifiers: string[];
24
+ /** Whether this is a type-only import */
25
+ isTypeOnly: boolean;
26
+ }
27
+ /**
28
+ * An edge in the import dependency graph.
29
+ *
30
+ * @example
31
+ * ```typescript
32
+ * const edge: ImportEdge = {
33
+ * from: 'src/controllers/user.controller.ts',
34
+ * to: 'src/services/user.service.ts',
35
+ * specifiers: ['UserService'],
36
+ * isTypeOnly: false,
37
+ * };
38
+ * ```
39
+ */
40
+ interface ImportEdge {
41
+ /** Importing file path */
42
+ from: string;
43
+ /** Imported file path */
44
+ to: string;
45
+ /** Imported symbol names */
46
+ specifiers: string[];
47
+ /** Whether type-only import */
48
+ isTypeOnly: boolean;
49
+ }
50
+ /** Parameter information for a function. */
51
+ interface ParamInfo {
52
+ name: string;
53
+ type: string;
54
+ optional: boolean;
55
+ }
56
+ /** An issue detected in a function during graph building. */
57
+ interface Issue {
58
+ message: string;
59
+ line: number;
60
+ column: number;
61
+ }
62
+ /**
63
+ * A node representing a source file in the codebase graph.
64
+ *
65
+ * @example
66
+ * ```typescript
67
+ * const file: FileNode = {
68
+ * path: 'src/services/user.service.ts',
69
+ * role: 'service',
70
+ * layer: 'service',
71
+ * exports: ['UserService'],
72
+ * imports: [],
73
+ * complexity: 5,
74
+ * loc: 120,
75
+ * functions: [],
76
+ * };
77
+ * ```
78
+ */
79
+ interface FileNode {
80
+ /** Relative file path */
81
+ path: string;
82
+ /** Detected role */
83
+ role: FileRole;
84
+ /** Architectural layer */
85
+ layer: string;
86
+ /** Exported symbol names */
87
+ exports: string[];
88
+ /** Import information */
89
+ imports: ImportInfo[];
90
+ /** Cyclomatic complexity (sum of all functions) */
91
+ complexity: number;
92
+ /** Lines of code */
93
+ loc: number;
94
+ /** Functions defined in this file */
95
+ functions: FunctionNode[];
96
+ }
97
+ /**
98
+ * A node representing a function in the codebase graph.
99
+ *
100
+ * @example
101
+ * ```typescript
102
+ * const fn: FunctionNode = {
103
+ * name: 'getUser',
104
+ * file: 'src/services/user.service.ts',
105
+ * startLine: 10,
106
+ * endLine: 25,
107
+ * params: [{ name: 'id', type: 'string', optional: false }],
108
+ * returnType: 'Promise<User>',
109
+ * complexity: 3,
110
+ * isAsync: true,
111
+ * hasSideEffects: true,
112
+ * issues: [],
113
+ * };
114
+ * ```
115
+ */
116
+ interface FunctionNode {
117
+ name: string;
118
+ file: string;
119
+ startLine: number;
120
+ endLine: number;
121
+ params: ParamInfo[];
122
+ returnType: string;
123
+ complexity: number;
124
+ isAsync: boolean;
125
+ hasSideEffects: boolean;
126
+ issues: Issue[];
127
+ }
128
+ /**
129
+ * A node representing an exported symbol.
130
+ *
131
+ * @example
132
+ * ```typescript
133
+ * const sym: SymbolNode = {
134
+ * name: 'UserService',
135
+ * kind: 'class',
136
+ * file: 'src/services/user.service.ts',
137
+ * usedBy: ['src/controllers/user.controller.ts'],
138
+ * dependsOn: ['src/repositories/user.repository.ts'],
139
+ * isPublicAPI: true,
140
+ * };
141
+ * ```
142
+ */
143
+ interface SymbolNode {
144
+ name: string;
145
+ kind: 'function' | 'class' | 'interface' | 'type' | 'variable' | 'enum';
146
+ file: string;
147
+ usedBy: string[];
148
+ dependsOn: string[];
149
+ isPublicAPI: boolean;
150
+ }
151
+ /** Architectural layer definition. */
152
+ interface LayerDefinition {
153
+ name: string;
154
+ order: number;
155
+ patterns: string[];
156
+ }
157
+ /** A detected pattern in the codebase. */
158
+ interface DetectedPattern {
159
+ type: string;
160
+ description: string;
161
+ files: string[];
162
+ confidence: number;
163
+ }
164
+ /** Module dependency graph (adjacency list). */
165
+ interface DependencyGraph {
166
+ /** Map of file path → set of file paths it depends on */
167
+ adjacency: Map<string, Set<string>>;
168
+ }
169
+ /**
170
+ * The codebase knowledge graph — core data structure.
171
+ *
172
+ * @example
173
+ * ```typescript
174
+ * const graph: CodebaseGraph = {
175
+ * files: new Map(),
176
+ * symbols: new Map(),
177
+ * edges: [],
178
+ * layers: [],
179
+ * patterns: [],
180
+ * dependencies: { adjacency: new Map() },
181
+ * };
182
+ * ```
183
+ */
184
+ interface CodebaseGraph {
185
+ files: Map<string, FileNode>;
186
+ symbols: Map<string, SymbolNode>;
187
+ edges: ImportEdge[];
188
+ layers: LayerDefinition[];
189
+ patterns: DetectedPattern[];
190
+ dependencies: DependencyGraph;
191
+ }
192
+ /**
193
+ * A single finding (issue) detected by a rule.
194
+ *
195
+ * @example
196
+ * ```typescript
197
+ * const finding: Finding = {
198
+ * message: 'console.log should not be in production code',
199
+ * file: 'src/services/user.service.ts',
200
+ * line: 42,
201
+ * column: 5,
202
+ * rule: 'quality/no-console',
203
+ * severity: 'warning',
204
+ * fix: { suggestion: 'Use a logger instead' },
205
+ * };
206
+ * ```
207
+ */
208
+ interface Finding {
209
+ message: string;
210
+ file: string;
211
+ line: number;
212
+ column: number;
213
+ rule?: string;
214
+ severity?: Severity;
215
+ fix?: {
216
+ suggestion: string;
217
+ replacement?: string;
218
+ };
219
+ }
220
+ /** Category for grouping rules. */
221
+ type RuleCategory = 'architecture' | 'security' | 'performance' | 'quality';
222
+ /** AST visitor function map. */
223
+ type ASTVisitors = {
224
+ [K in string]?: (node: ts.Node) => void;
225
+ };
226
+ /**
227
+ * Context provided to rules during analysis.
228
+ *
229
+ * @example
230
+ * ```typescript
231
+ * const check = (context: RuleContext) => {
232
+ * const findings: Finding[] = [];
233
+ * context.walk(context.ast, {
234
+ * CallExpression(node) {
235
+ * if (context.isConsoleCall(node as ts.CallExpression)) {
236
+ * findings.push({ message: 'No console', file: context.file.path, line: 1, column: 1 });
237
+ * }
238
+ * },
239
+ * });
240
+ * return findings;
241
+ * };
242
+ * ```
243
+ */
244
+ interface RuleContext {
245
+ file: FileNode;
246
+ ast: ts.SourceFile;
247
+ graph: CodebaseGraph;
248
+ program: ts.Program;
249
+ checker: ts.TypeChecker;
250
+ walk: (node: ts.Node, visitors: ASTVisitors) => void;
251
+ isCallTo: (node: ts.CallExpression, name: string) => boolean;
252
+ isConsoleCall: (node: ts.CallExpression, method?: string) => boolean;
253
+ getTypeString: (node: ts.Node) => string;
254
+ hasStringConcat: (node: ts.Node) => boolean;
255
+ getImports: () => ImportInfo[];
256
+ isExternallyUsed: (symbolName: string) => boolean;
257
+ config: Record<string, unknown>;
258
+ }
259
+ /**
260
+ * A single analysis rule.
261
+ *
262
+ * @example
263
+ * ```typescript
264
+ * const rule: Rule = {
265
+ * name: 'quality/no-any',
266
+ * severity: 'warning',
267
+ * description: 'Disallow any type',
268
+ * category: 'quality',
269
+ * check: (ctx) => [],
270
+ * };
271
+ * ```
272
+ */
273
+ interface Rule {
274
+ name: string;
275
+ severity: Severity;
276
+ description: string;
277
+ category: RuleCategory;
278
+ check: (context: RuleContext) => Finding[] | Promise<Finding[]>;
279
+ }
280
+ /**
281
+ * The kernel interface exposed to plugins during installation.
282
+ *
283
+ * @example
284
+ * ```typescript
285
+ * const install = (kernel: GuardianKernel) => {
286
+ * kernel.registerRule(myRule);
287
+ * };
288
+ * ```
289
+ */
290
+ interface GuardianKernel<TConfig = unknown> {
291
+ registerRule: (rule: Rule) => void;
292
+ unregisterRule: (name: string) => void;
293
+ getRules: () => Rule[];
294
+ getConfig: () => TConfig;
295
+ }
296
+ /**
297
+ * Plugin interface for extending codeguardian.
298
+ *
299
+ * @typeParam TConfig - Plugin-specific configuration type
300
+ *
301
+ * @example
302
+ * ```typescript
303
+ * const myPlugin: GuardianPlugin = {
304
+ * name: 'my-plugin',
305
+ * version: '1.0.0',
306
+ * install: (kernel) => {
307
+ * kernel.registerRule(myRule);
308
+ * },
309
+ * };
310
+ * ```
311
+ */
312
+ interface GuardianPlugin<TConfig = unknown> {
313
+ name: string;
314
+ version: string;
315
+ dependencies?: string[];
316
+ install: (kernel: GuardianKernel<TConfig>) => void;
317
+ onInit?: (graph: CodebaseGraph) => void | Promise<void>;
318
+ onDestroy?: () => void | Promise<void>;
319
+ onError?: (error: Error) => void;
320
+ }
321
+ /** Severity configuration for commit blocking. */
322
+ interface SeverityConfig {
323
+ blockOn: Severity[];
324
+ warnOn: Severity[];
325
+ ignoreBelow?: Severity;
326
+ }
327
+ /** Architecture plugin config. */
328
+ interface ArchitecturePluginConfig {
329
+ enabled: boolean;
330
+ layers?: string[];
331
+ enforceDirection?: boolean;
332
+ maxFileLines?: number;
333
+ maxFunctionLines?: number;
334
+ maxFunctionComplexity?: number;
335
+ }
336
+ /** Security plugin config. */
337
+ interface SecurityPluginConfig {
338
+ enabled: boolean;
339
+ checkInjection?: boolean;
340
+ checkAuth?: boolean;
341
+ checkSecrets?: boolean;
342
+ checkXSS?: boolean;
343
+ checkCSRF?: boolean;
344
+ }
345
+ /** Performance plugin config. */
346
+ interface PerformancePluginConfig {
347
+ enabled: boolean;
348
+ checkN1Queries?: boolean;
349
+ checkMemoryLeaks?: boolean;
350
+ checkAsyncPatterns?: boolean;
351
+ checkBundleSize?: boolean;
352
+ }
353
+ /** Quality plugin config. */
354
+ interface QualityPluginConfig {
355
+ enabled: boolean;
356
+ checkDeadCode?: boolean;
357
+ checkNaming?: boolean;
358
+ checkComplexity?: boolean;
359
+ maxCyclomaticComplexity?: number;
360
+ }
361
+ /** Optional plugin configs. */
362
+ interface NamingPluginConfig {
363
+ enabled: boolean;
364
+ }
365
+ interface ApiPluginConfig {
366
+ enabled: boolean;
367
+ }
368
+ interface TestGuardPluginConfig {
369
+ enabled: boolean;
370
+ }
371
+ interface DepAuditPluginConfig {
372
+ enabled: boolean;
373
+ maxDepth?: number;
374
+ }
375
+ /** All plugin configurations. */
376
+ interface PluginConfigs {
377
+ architecture?: ArchitecturePluginConfig;
378
+ security?: SecurityPluginConfig;
379
+ performance?: PerformancePluginConfig;
380
+ quality?: QualityPluginConfig;
381
+ naming?: NamingPluginConfig;
382
+ api?: ApiPluginConfig;
383
+ testGuard?: TestGuardPluginConfig;
384
+ depAudit?: DepAuditPluginConfig;
385
+ }
386
+ /** Ignore configuration. */
387
+ interface IgnoreConfig {
388
+ rules?: string[];
389
+ files?: string[];
390
+ lines?: Record<string, number[]>;
391
+ }
392
+ /**
393
+ * Full project configuration (shape of .codeguardian.json).
394
+ *
395
+ * @example
396
+ * ```typescript
397
+ * const config: ProjectConfig = {
398
+ * rootDir: '.',
399
+ * tsconfig: './tsconfig.json',
400
+ * include: ['src/**\/*.ts'],
401
+ * exclude: ['**\/*.test.ts'],
402
+ * severity: { blockOn: ['critical', 'error'], warnOn: ['warning'] },
403
+ * plugins: { architecture: { enabled: true } },
404
+ * ignore: { rules: [], files: [] },
405
+ * };
406
+ * ```
407
+ */
408
+ interface ProjectConfig {
409
+ rootDir: string;
410
+ tsconfig: string;
411
+ include: string[];
412
+ exclude: string[];
413
+ severity: SeverityConfig;
414
+ plugins: PluginConfigs;
415
+ ignore: IgnoreConfig;
416
+ }
417
+ /**
418
+ * Inline configuration (same shape minus rootDir/tsconfig which come from GuardianConfig).
419
+ */
420
+ interface InlineConfig {
421
+ include?: string[];
422
+ exclude?: string[];
423
+ severity?: Partial<SeverityConfig>;
424
+ plugins?: Partial<PluginConfigs>;
425
+ ignore?: Partial<IgnoreConfig>;
426
+ }
427
+ /**
428
+ * Options passed to createGuardian().
429
+ *
430
+ * @example
431
+ * ```typescript
432
+ * const config: GuardianConfig = {
433
+ * rootDir: process.cwd(),
434
+ * tsconfig: './tsconfig.json',
435
+ * };
436
+ * ```
437
+ */
438
+ interface GuardianConfig {
439
+ rootDir: string;
440
+ tsconfig?: string;
441
+ config?: string | InlineConfig;
442
+ autoDiscover?: boolean;
443
+ }
444
+ /** Options for running analysis. */
445
+ interface RunOptions {
446
+ staged?: boolean;
447
+ verbose?: boolean;
448
+ plugins?: string[];
449
+ format?: 'terminal' | 'json' | 'sarif';
450
+ }
451
+ /** Statistics from a run. */
452
+ interface RunStats {
453
+ filesAnalyzed: number;
454
+ rulesExecuted: number;
455
+ duration: number;
456
+ parseTime: number;
457
+ analysisTime: number;
458
+ }
459
+ /**
460
+ * Result of running analysis.
461
+ *
462
+ * @example
463
+ * ```typescript
464
+ * const result: RunResult = {
465
+ * findings: [],
466
+ * stats: { filesAnalyzed: 10, rulesExecuted: 29, duration: 150, parseTime: 80, analysisTime: 70 },
467
+ * blocked: false,
468
+ * bySeverity: { critical: [], error: [], warning: [], info: [] },
469
+ * byFile: {},
470
+ * };
471
+ * ```
472
+ */
473
+ interface RunResult {
474
+ findings: Finding[];
475
+ stats: RunStats;
476
+ blocked: boolean;
477
+ bySeverity: Record<Severity, Finding[]>;
478
+ byFile: Record<string, Finding[]>;
479
+ }
480
+ /** Result of incremental scan. */
481
+ interface IncrementalResult {
482
+ changedFiles: string[];
483
+ affectedFiles: string[];
484
+ graph: CodebaseGraph;
485
+ }
486
+
487
+ export type { ASTVisitors as A, CodebaseGraph as C, DetectedPattern as D, FileNode as F, GuardianConfig as G, IncrementalResult as I, NamingPluginConfig as N, ProjectConfig as P, QualityPluginConfig as Q, RunOptions as R, SymbolNode as S, TestGuardPluginConfig as T, RunResult as a, GuardianPlugin as b, Rule as c, Finding as d, FunctionNode as e, GuardianKernel as f, ImportEdge as g, ImportInfo as h, RuleCategory as i, RuleContext as j, RunStats as k, Severity as l, SeverityConfig as m, ArchitecturePluginConfig as n, SecurityPluginConfig as o, PerformancePluginConfig as p, ApiPluginConfig as q, DepAuditPluginConfig as r };
package/package.json ADDED
@@ -0,0 +1,88 @@
1
+ {
2
+ "name": "@oxog/codeguardian",
3
+ "version": "1.0.0",
4
+ "description": "Zero-dependency TypeScript codebase guardian - pre-commit hook enforcing architecture, security, performance, and quality rules",
5
+ "type": "module",
6
+ "main": "./dist/index.cjs",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "bin": {
10
+ "codeguardian": "./dist/cli.js"
11
+ },
12
+ "exports": {
13
+ ".": {
14
+ "import": {
15
+ "types": "./dist/index.d.ts",
16
+ "default": "./dist/index.js"
17
+ },
18
+ "require": {
19
+ "types": "./dist/index.d.cts",
20
+ "default": "./dist/index.cjs"
21
+ }
22
+ },
23
+ "./plugins": {
24
+ "import": {
25
+ "types": "./dist/plugins/index.d.ts",
26
+ "default": "./dist/plugins/index.js"
27
+ },
28
+ "require": {
29
+ "types": "./dist/plugins/index.d.cts",
30
+ "default": "./dist/plugins/index.cjs"
31
+ }
32
+ }
33
+ },
34
+ "files": [
35
+ "dist"
36
+ ],
37
+ "sideEffects": false,
38
+ "scripts": {
39
+ "build": "tsup",
40
+ "test": "vitest run",
41
+ "test:watch": "vitest",
42
+ "test:coverage": "vitest run --coverage",
43
+ "lint": "eslint src/",
44
+ "format": "prettier --write .",
45
+ "typecheck": "tsc --noEmit",
46
+ "prepublishOnly": "npm run build && npm run test:coverage"
47
+ },
48
+ "keywords": [
49
+ "typescript",
50
+ "code-quality",
51
+ "architecture",
52
+ "security",
53
+ "pre-commit",
54
+ "guardian",
55
+ "static-analysis",
56
+ "linter",
57
+ "code-review",
58
+ "ast",
59
+ "codebase",
60
+ "oxog"
61
+ ],
62
+ "author": "Ersin Koç",
63
+ "license": "MIT",
64
+ "repository": {
65
+ "type": "git",
66
+ "url": "git+https://github.com/ersinkoc/codeguardian.git"
67
+ },
68
+ "bugs": {
69
+ "url": "https://github.com/ersinkoc/codeguardian/issues"
70
+ },
71
+ "homepage": "https://codeguardian.oxog.dev",
72
+ "engines": {
73
+ "node": ">=18"
74
+ },
75
+ "peerDependencies": {
76
+ "typescript": ">=5.0.0"
77
+ },
78
+ "devDependencies": {
79
+ "@types/node": "^20.0.0",
80
+ "@vitest/coverage-v8": "^2.0.0",
81
+ "eslint": "^9.0.0",
82
+ "prettier": "^3.0.0",
83
+ "tsup": "^8.0.0",
84
+ "typescript": "^5.0.0",
85
+ "typescript-eslint": "^8.54.0",
86
+ "vitest": "^2.0.0"
87
+ }
88
+ }