@webpieces/dev-config 0.0.0-dev

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 (31) hide show
  1. package/README.md +306 -0
  2. package/bin/set-version.sh +86 -0
  3. package/bin/setup-claude-patterns.sh +51 -0
  4. package/bin/start.sh +107 -0
  5. package/bin/stop.sh +65 -0
  6. package/bin/use-local-webpieces.sh +89 -0
  7. package/bin/use-published-webpieces.sh +33 -0
  8. package/config/eslint/base.mjs +91 -0
  9. package/config/typescript/tsconfig.base.json +25 -0
  10. package/eslint-plugin/__tests__/catch-error-pattern.test.ts +360 -0
  11. package/eslint-plugin/__tests__/max-file-lines.test.ts +195 -0
  12. package/eslint-plugin/__tests__/max-method-lines.test.ts +246 -0
  13. package/eslint-plugin/index.d.ts +14 -0
  14. package/eslint-plugin/index.js +19 -0
  15. package/eslint-plugin/index.js.map +1 -0
  16. package/eslint-plugin/index.ts +18 -0
  17. package/eslint-plugin/rules/catch-error-pattern.d.ts +11 -0
  18. package/eslint-plugin/rules/catch-error-pattern.js +196 -0
  19. package/eslint-plugin/rules/catch-error-pattern.js.map +1 -0
  20. package/eslint-plugin/rules/catch-error-pattern.ts +281 -0
  21. package/eslint-plugin/rules/max-file-lines.d.ts +12 -0
  22. package/eslint-plugin/rules/max-file-lines.js +257 -0
  23. package/eslint-plugin/rules/max-file-lines.js.map +1 -0
  24. package/eslint-plugin/rules/max-file-lines.ts +272 -0
  25. package/eslint-plugin/rules/max-method-lines.d.ts +12 -0
  26. package/eslint-plugin/rules/max-method-lines.js +257 -0
  27. package/eslint-plugin/rules/max-method-lines.js.map +1 -0
  28. package/eslint-plugin/rules/max-method-lines.ts +304 -0
  29. package/package.json +54 -0
  30. package/patterns/CLAUDE.md +293 -0
  31. package/patterns/claude.patterns.md +798 -0
@@ -0,0 +1,304 @@
1
+ /**
2
+ * ESLint rule to enforce maximum method length
3
+ *
4
+ * Enforces a configurable maximum line count for methods, functions, and arrow functions.
5
+ * Default: 70 lines
6
+ *
7
+ * Configuration:
8
+ * '@webpieces/max-method-lines': ['error', { max: 70 }]
9
+ */
10
+
11
+ import type { Rule } from 'eslint';
12
+ import * as fs from 'fs';
13
+ import * as path from 'path';
14
+
15
+ interface MethodLinesOptions {
16
+ max: number;
17
+ }
18
+
19
+ interface FunctionNode {
20
+ type:
21
+ | 'FunctionDeclaration'
22
+ | 'FunctionExpression'
23
+ | 'ArrowFunctionExpression'
24
+ | 'MethodDefinition';
25
+ body?: any;
26
+ loc?: {
27
+ start: { line: number };
28
+ end: { line: number };
29
+ };
30
+ key?: {
31
+ name?: string;
32
+ };
33
+ id?: {
34
+ name?: string;
35
+ };
36
+ [key: string]: any;
37
+ }
38
+
39
+ const METHOD_DOC_CONTENT = `# AI Agent Instructions: Method Too Long
40
+
41
+ **READ THIS FILE to fix methods that are too long**
42
+
43
+ ## Core Principle
44
+ Every method should read like a TABLE OF CONTENTS of a book.
45
+ - Each method call is a "chapter"
46
+ - When you dive into a method, you find another table of contents
47
+ - Keeping methods under 70 lines is achievable with proper extraction
48
+
49
+ ## Command: Extract Code into Named Methods
50
+
51
+ ### Pattern 1: Extract Loop Bodies
52
+ \`\`\`typescript
53
+ // ❌ BAD: 50 lines embedded in loop
54
+ for (const order of orders) {
55
+ // 20 lines of validation logic
56
+ // 15 lines of processing logic
57
+ // 10 lines of notification logic
58
+ }
59
+
60
+ // ✅ GOOD: Extracted to named methods
61
+ for (const order of orders) {
62
+ validateOrder(order);
63
+ processOrderItems(order);
64
+ sendNotifications(order);
65
+ }
66
+ \`\`\`
67
+
68
+ ### Pattern 2: Try-Catch Wrapper for Exception Handling
69
+ \`\`\`typescript
70
+ // ✅ GOOD: Separates success path from error handling
71
+ async function handleRequest(req: Request): Promise<Response> {
72
+ try {
73
+ return await executeRequest(req);
74
+ } catch (err: any) {
75
+ const error = toError(err);
76
+ return createErrorResponse(error);
77
+ }
78
+ }
79
+ \`\`\`
80
+
81
+ ### Pattern 3: Sequential Method Calls (Table of Contents)
82
+ \`\`\`typescript
83
+ // ✅ GOOD: Self-documenting steps
84
+ function processOrder(order: Order): void {
85
+ validateOrderData(order);
86
+ calculateTotals(order);
87
+ applyDiscounts(order);
88
+ processPayment(order);
89
+ updateInventory(order);
90
+ sendConfirmation(order);
91
+ }
92
+ \`\`\`
93
+
94
+ ### Pattern 4: Separate Data Object Creation
95
+ \`\`\`typescript
96
+ // ❌ BAD: 15 lines of inline object creation
97
+ doSomething({ field1: ..., field2: ..., field3: ..., /* 15 more fields */ });
98
+
99
+ // ✅ GOOD: Extract to factory method
100
+ const request = createRequestObject(data);
101
+ doSomething(request);
102
+ \`\`\`
103
+
104
+ ### Pattern 5: Extract Inline Logic to Named Functions
105
+ \`\`\`typescript
106
+ // ❌ BAD: Complex inline logic
107
+ if (user.role === 'admin' && user.permissions.includes('write') && !user.suspended) {
108
+ // 30 lines of admin logic
109
+ }
110
+
111
+ // ✅ GOOD: Extract to named methods
112
+ if (isAdminWithWriteAccess(user)) {
113
+ performAdminOperation(user);
114
+ }
115
+ \`\`\`
116
+
117
+ ## AI Agent Action Steps
118
+
119
+ 1. **IDENTIFY** the long method in the error message
120
+ 2. **READ** the method to understand its logical sections
121
+ 3. **EXTRACT** logical units into separate methods with descriptive names
122
+ 4. **REPLACE** inline code with method calls
123
+ 5. **VERIFY** each extracted method is <70 lines
124
+ 6. **TEST** that functionality remains unchanged
125
+
126
+ ## Examples of "Logical Units" to Extract
127
+ - Validation logic → \`validateX()\`
128
+ - Data transformation → \`transformXToY()\`
129
+ - API calls → \`fetchXFromApi()\`
130
+ - Object creation → \`createX()\`
131
+ - Loop bodies → \`processItem()\`
132
+ - Error handling → \`handleXError()\`
133
+
134
+ Remember: Methods should read like a table of contents. Each line should be a "chapter title" (method call) that describes what happens, not how it happens.
135
+ `;
136
+
137
+ // Module-level flag to prevent redundant file creation
138
+ let methodDocCreated = false;
139
+
140
+ function getWorkspaceRoot(context: Rule.RuleContext): string {
141
+ const filename = context.filename || context.getFilename();
142
+ let dir = path.dirname(filename);
143
+
144
+ // Walk up directory tree to find workspace root
145
+ while (dir !== path.dirname(dir)) {
146
+ const pkgPath = path.join(dir, 'package.json');
147
+ if (fs.existsSync(pkgPath)) {
148
+ try {
149
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
150
+ if (pkg.workspaces || pkg.name === 'webpieces-ts') {
151
+ return dir;
152
+ }
153
+ } catch (err: any) {
154
+ //const error = toError(err);
155
+ // Continue searching if JSON parse fails
156
+ }
157
+ }
158
+ dir = path.dirname(dir);
159
+ }
160
+ return process.cwd(); // Fallback
161
+ }
162
+
163
+ function ensureDocFile(docPath: string, content: string): boolean {
164
+ try {
165
+ fs.mkdirSync(path.dirname(docPath), { recursive: true });
166
+ fs.writeFileSync(docPath, content, 'utf-8');
167
+ return true;
168
+ } catch (err: any) {
169
+ //const error = toError(err);
170
+ // Graceful degradation: log warning but don't break lint
171
+ console.warn(`[webpieces] Could not create doc file: ${docPath}`, err);
172
+ return false;
173
+ }
174
+ }
175
+
176
+ function ensureMethodDoc(context: Rule.RuleContext): void {
177
+ if (methodDocCreated) return; // Performance: only create once per lint run
178
+
179
+ const workspaceRoot = getWorkspaceRoot(context);
180
+ const docPath = path.join(workspaceRoot, 'tmp', 'webpieces', 'webpieces.methods.md');
181
+
182
+ if (ensureDocFile(docPath, METHOD_DOC_CONTENT)) {
183
+ methodDocCreated = true;
184
+ }
185
+ }
186
+
187
+ const rule: Rule.RuleModule = {
188
+ meta: {
189
+ type: 'suggestion',
190
+ docs: {
191
+ description: 'Enforce maximum method length',
192
+ category: 'Best Practices',
193
+ recommended: false,
194
+ url: 'https://github.com/deanhiller/webpieces-ts',
195
+ },
196
+ messages: {
197
+ tooLong:
198
+ 'AI Agent: READ tmp/webpieces/webpieces.methods.md for fix instructions. Method "{{name}}" has {{actual}} lines (max: {{max}})',
199
+ },
200
+ fixable: undefined,
201
+ schema: [
202
+ {
203
+ type: 'object',
204
+ properties: {
205
+ max: {
206
+ type: 'integer',
207
+ minimum: 1,
208
+ },
209
+ },
210
+ additionalProperties: false,
211
+ },
212
+ ],
213
+ },
214
+
215
+ create(context: Rule.RuleContext): Rule.RuleListener {
216
+ const options = context.options[0] as MethodLinesOptions | undefined;
217
+ const maxLines = options?.max ?? 70;
218
+
219
+ function checkFunction(node: any): void {
220
+ ensureMethodDoc(context);
221
+
222
+ const funcNode = node as FunctionNode;
223
+
224
+ // Skip if this is a function expression that's part of a method definition
225
+ // (method definitions will be handled by checkMethod)
226
+ if (
227
+ funcNode.type === 'FunctionExpression' &&
228
+ funcNode['parent']?.type === 'MethodDefinition'
229
+ ) {
230
+ return;
231
+ }
232
+
233
+ // Skip if no location info or no body
234
+ if (!funcNode.loc || !funcNode.body) {
235
+ return;
236
+ }
237
+
238
+ // Get function name
239
+ let name = 'anonymous';
240
+ if (funcNode.type === 'FunctionDeclaration' && funcNode.id?.name) {
241
+ name = funcNode.id.name;
242
+ } else if (funcNode.type === 'FunctionExpression' && funcNode.id?.name) {
243
+ name = funcNode.id.name;
244
+ }
245
+
246
+ // Calculate line count
247
+ const startLine = funcNode.loc.start.line;
248
+ const endLine = funcNode.loc.end.line;
249
+ const lineCount = endLine - startLine + 1;
250
+
251
+ if (lineCount > maxLines) {
252
+ context.report({
253
+ node: funcNode as any,
254
+ messageId: 'tooLong',
255
+ data: {
256
+ name,
257
+ actual: String(lineCount),
258
+ max: String(maxLines),
259
+ },
260
+ });
261
+ }
262
+ }
263
+
264
+ function checkMethod(node: any): void {
265
+ ensureMethodDoc(context);
266
+
267
+ const methodNode = node;
268
+
269
+ // Skip if no location info
270
+ if (!methodNode.loc || !methodNode.value) {
271
+ return;
272
+ }
273
+
274
+ // Get method name from key
275
+ const name = methodNode.key?.name || 'anonymous';
276
+
277
+ // Calculate line count for the method (including the method definition)
278
+ const startLine = methodNode.loc.start.line;
279
+ const endLine = methodNode.loc.end.line;
280
+ const lineCount = endLine - startLine + 1;
281
+
282
+ if (lineCount > maxLines) {
283
+ context.report({
284
+ node: methodNode as any,
285
+ messageId: 'tooLong',
286
+ data: {
287
+ name,
288
+ actual: String(lineCount),
289
+ max: String(maxLines),
290
+ },
291
+ });
292
+ }
293
+ }
294
+
295
+ return {
296
+ FunctionDeclaration: checkFunction,
297
+ FunctionExpression: checkFunction,
298
+ ArrowFunctionExpression: checkFunction,
299
+ MethodDefinition: checkMethod,
300
+ };
301
+ },
302
+ };
303
+
304
+ export = rule;
package/package.json ADDED
@@ -0,0 +1,54 @@
1
+ {
2
+ "name": "@webpieces/dev-config",
3
+ "version": "0.0.0-dev",
4
+ "description": "Development configuration, scripts, and patterns for WebPieces projects",
5
+ "type": "commonjs",
6
+ "bin": {
7
+ "wp-start": "./bin/start.sh",
8
+ "wp-stop": "./bin/stop.sh",
9
+ "wp-set-version": "./bin/set-version.sh",
10
+ "wp-use-local": "./bin/use-local-webpieces.sh",
11
+ "wp-use-published": "./bin/use-published-webpieces.sh",
12
+ "wp-setup-patterns": "./bin/setup-claude-patterns.sh"
13
+ },
14
+ "exports": {
15
+ "./eslint": "./config/eslint/base.mjs",
16
+ "./eslint-plugin": "./eslint-plugin/index.js",
17
+ "./jest": "./config/jest/preset.js",
18
+ "./tsconfig": "./config/typescript/tsconfig.base.json"
19
+ },
20
+ "files": [
21
+ "bin/**/*",
22
+ "config/**/*",
23
+ "eslint-plugin/**/*",
24
+ "patterns/**/*",
25
+ "README.md"
26
+ ],
27
+ "peerDependencies": {
28
+ "eslint": ">=8.0.0"
29
+ },
30
+ "devDependencies": {
31
+ "@types/eslint": "^9.6.1",
32
+ "eslint": "^9.39.1"
33
+ },
34
+ "keywords": [
35
+ "webpieces",
36
+ "config",
37
+ "eslint",
38
+ "scripts",
39
+ "typescript",
40
+ "jest"
41
+ ],
42
+ "author": "Dean Hiller",
43
+ "license": "Apache-2.0",
44
+ "repository": {
45
+ "type": "git",
46
+ "url": "https://github.com/deanhiller/webpieces-ts.git",
47
+ "directory": "packages/tooling/dev-config"
48
+ },
49
+ "publishConfig": {
50
+ "access": "public"
51
+ },
52
+ "types": "./src/index.d.ts",
53
+ "main": "./src/index.js"
54
+ }
@@ -0,0 +1,293 @@
1
+ # Claude Code Guidelines for webpieces-ts
2
+
3
+ This document contains guidelines and patterns for Claude Code when working on the webpieces-ts codebase.
4
+
5
+ ## Core Principles
6
+
7
+ ### 1. Classes Over Interfaces for Data Structures
8
+
9
+ **RULE: All data-only structures MUST be classes, not interfaces.**
10
+
11
+ **What is a data-only structure?**
12
+ - Contains only fields/properties
13
+ - No methods with business logic
14
+ - Used purely for data transfer or configuration
15
+
16
+ **Examples of DATA ONLY (use classes):**
17
+ - `ClientConfig` - Configuration data
18
+ - `FilterDefinition` - Filter metadata
19
+ - `RouteDefinition` - Route metadata
20
+ - `RouteRequest` - Request data
21
+ - `RouteContext` - Context data
22
+ - `MethodMeta` - Method metadata
23
+ - `Action` - Response data
24
+ - `RouteMetadata` - Route decorator metadata
25
+ - `JsonFilterConfig` - Configuration data
26
+ - `RegisteredRoute` - Extended route data
27
+
28
+ **Examples of BUSINESS LOGIC (use interfaces):**
29
+ - `Filter` - Has `filter(meta, next)` method with logic
30
+ - `Routes` - Has `configure(routeBuilder)` method with logic
31
+ - `RouteBuilder` - Has `addRoute()`, `addFilter()` methods
32
+ - `WebAppMeta` - Has `getDIModules()`, `getRoutes()` methods
33
+ - `SaveApi` - Has `save(request)` method with logic
34
+ - `RemoteApi` - Has `fetchValue(request)` method with logic
35
+ - `Counter` - Has `inc()`, `get()` methods with logic
36
+
37
+ **Why classes for data?**
38
+ 1. No anonymous object literals - explicit construction
39
+ 2. Better type safety
40
+ 3. Clear instantiation points
41
+ 4. Easier to trace in debugger
42
+ 5. Can add validation/defaults in constructor
43
+
44
+ **Pattern:**
45
+ ```typescript
46
+ // BAD - Interface for data
47
+ export interface UserData {
48
+ name: string;
49
+ age: number;
50
+ }
51
+
52
+ const user = { name: 'John', age: 30 }; // Anonymous object literal
53
+
54
+ // GOOD - Class for data
55
+ export class UserData {
56
+ name: string;
57
+ age: number;
58
+
59
+ constructor(name: string, age: number) {
60
+ this.name = name;
61
+ this.age = age;
62
+ }
63
+ }
64
+
65
+ const user = new UserData('John', 30); // Explicit construction
66
+ ```
67
+
68
+ ### 2. Filter Chain Architecture
69
+
70
+ **Pattern inspired by Java webpieces:**
71
+
72
+ The filter system uses filepath-based matching:
73
+ - Filters are registered with glob patterns (e.g., `'src/controllers/admin/**/*.ts'`)
74
+ - `FilterMatcher` matches filters to routes based on controller filepath
75
+ - Filters without a pattern (or pattern `'*'`) apply globally
76
+ - Filter matching happens at startup (no runtime overhead)
77
+
78
+ **Key classes:**
79
+ - `FilterDefinition(priority, filterClass, filepathPattern)` - Filter registration
80
+ - `FilterMatcher.findMatchingFilters()` - Pattern matching logic
81
+ - `FilterChain` - Executes filters in priority order
82
+
83
+ **Example:**
84
+ ```typescript
85
+ export class FilterRoutes implements Routes {
86
+ configure(routeBuilder: RouteBuilder): void {
87
+ // Global filter (pattern '*' matches all)
88
+ routeBuilder.addFilter(
89
+ new FilterDefinition(140, ContextFilter, '*')
90
+ );
91
+
92
+ // Admin-only filter
93
+ routeBuilder.addFilter(
94
+ new FilterDefinition(100, AdminAuthFilter, 'src/controllers/admin/**/*.ts')
95
+ );
96
+ }
97
+ }
98
+ ```
99
+
100
+ ### 3. No Anonymous Object Literals
101
+
102
+ **RULE: Avoid anonymous object structures - use explicit class constructors.**
103
+
104
+ **BAD:**
105
+ ```typescript
106
+ routeBuilder.addRoute({
107
+ method: 'POST',
108
+ path: '/api/save',
109
+ handler: myHandler,
110
+ });
111
+ ```
112
+
113
+ **GOOD:**
114
+ ```typescript
115
+ routeBuilder.addRoute(
116
+ new RouteDefinition('POST', '/api/save', myHandler)
117
+ );
118
+ ```
119
+
120
+ ### 4. Type Safety
121
+
122
+ - Use `unknown` instead of `any` for better type safety
123
+ - Use generics for type-safe route handlers: `RouteHandler<TResult>`
124
+ - Prefer explicit types over inference when defining public APIs
125
+
126
+ ### 5. Dependency Injection
127
+
128
+ **Use Inversify for DI:**
129
+ - `@injectable()` - Mark classes as injectable
130
+ - `@inject(TYPES.Something)` - Inject dependencies
131
+ - `@provideSingleton()` - Register singleton in container
132
+ - `@unmanaged()` - Mark constructor params that aren't injected
133
+
134
+ **Pattern:**
135
+ ```typescript
136
+ @provideSingleton()
137
+ @Controller()
138
+ export class SaveController {
139
+ constructor(
140
+ @inject(TYPES.Counter) private counter: Counter,
141
+ @inject(TYPES.RemoteApi) private remoteService: RemoteApi
142
+ ) {}
143
+ }
144
+ ```
145
+
146
+ ### 6. Decorators
147
+
148
+ **API Decorators (shared between client and server):**
149
+ - `@ApiInterface()` - Mark API prototype class
150
+ - `@Post()`, `@Get()`, `@Put()`, `@Delete()`, `@Patch()` - HTTP methods
151
+ - `@Path('/path')` - Route path
152
+
153
+ **Server-only Decorators:**
154
+ - `@Controller()` - Mark controller class
155
+ - `@SourceFile('path/to/controller.ts')` - Explicit filepath for filter matching
156
+ - `@provideSingleton()` - Register as singleton
157
+
158
+ ### 7. Testing
159
+
160
+ **Unit tests:**
161
+ - Test filter matching logic in isolation
162
+ - Mock dependencies using classes
163
+ - Verify priority ordering
164
+
165
+ **Integration tests:**
166
+ - Use `WebpiecesServer.createApiClient()` for testing without HTTP
167
+ - Test full filter chain execution
168
+ - Verify end-to-end behavior
169
+
170
+ ### 8. Documentation
171
+
172
+ - Use JSDoc for all public APIs
173
+ - Explain WHY, not just WHAT
174
+ - Include usage examples
175
+ - Document differences from Java version when applicable
176
+
177
+ ## Common Patterns
178
+
179
+ ### Creating a New Filter
180
+
181
+ ```typescript
182
+ import { injectable } from 'inversify';
183
+ import { Filter, MethodMeta, Action, NextFilter } from '@webpieces/http-filters';
184
+
185
+ @injectable()
186
+ export class MyFilter implements Filter {
187
+ priority = 100;
188
+
189
+ async filter(meta: MethodMeta, next: NextFilter): Promise<Action> {
190
+ // Before logic
191
+ console.log(`Request: ${meta.httpMethod} ${meta.path}`);
192
+
193
+ // Execute next filter/controller
194
+ const action = await next.execute();
195
+
196
+ // After logic
197
+ console.log(`Response: ${action.statusCode}`);
198
+
199
+ return action;
200
+ }
201
+ }
202
+ ```
203
+
204
+ ### Creating a New Controller
205
+
206
+ ```typescript
207
+ import { provideSingleton, Controller } from '@webpieces/http-routing';
208
+
209
+ @provideSingleton()
210
+ @Controller()
211
+ export class MyController extends MyApiPrototype implements MyApi {
212
+ private readonly __validator!: ValidateImplementation<MyController, MyApi>;
213
+
214
+ async myMethod(request: MyRequest): Promise<MyResponse> {
215
+ // Implementation
216
+ }
217
+ }
218
+ ```
219
+
220
+ ### Registering Routes and Filters
221
+
222
+ ```typescript
223
+ export class MyRoutes implements Routes {
224
+ configure(routeBuilder: RouteBuilder): void {
225
+ // Register filters
226
+ routeBuilder.addFilter(
227
+ new FilterDefinition(140, ContextFilter, '*')
228
+ );
229
+
230
+ // Register API routes
231
+ // (handled automatically by RESTApiRoutes)
232
+ }
233
+ }
234
+ ```
235
+
236
+ ## Architecture Overview
237
+
238
+ ```
239
+ ┌─────────────────────────────────────────────────┐
240
+ │ WebAppMeta │
241
+ │ - getDIModules() - Returns DI modules │
242
+ │ - getRoutes() - Returns route configurations │
243
+ └─────────────────────────────────────────────────┘
244
+
245
+
246
+ ┌─────────────────────────────────────────────────┐
247
+ │ WebpiecesServer │
248
+ │ - Initializes DI containers │
249
+ │ - Registers routes using RouteBuilder │
250
+ │ - Matches filters to routes (FilterMatcher) │
251
+ │ - Creates filter chains per route │
252
+ └─────────────────────────────────────────────────┘
253
+
254
+
255
+ ┌─────────────────────────────────────────────────┐
256
+ │ FilterChain │
257
+ │ - Executes filters in priority order │
258
+ │ - Wraps controller invocation │
259
+ └─────────────────────────────────────────────────┘
260
+
261
+
262
+ ┌─────────────────────────────────────────────────┐
263
+ │ Controller │
264
+ │ - Implements API interface │
265
+ │ - Business logic │
266
+ │ - Returns response │
267
+ └─────────────────────────────────────────────────┘
268
+ ```
269
+
270
+ ## Key Differences from Java Version
271
+
272
+ 1. **Glob patterns instead of Regex**: TypeScript uses glob patterns for filepath matching
273
+ 2. **Class-based data structures**: All data structures are classes, not interfaces
274
+ 3. **Decorator-based metadata**: Uses TypeScript decorators instead of annotations
275
+ 4. **Inversify instead of Guice**: Different DI framework but similar patterns
276
+ 5. **Class name fallback**: Since TypeScript doesn't provide source paths at runtime, we use class name patterns like `**/SaveController.ts`
277
+
278
+ ## When Adding New Features
279
+
280
+ 1. **Check for data-only structures** - If it's just data, use a class
281
+ 2. **Add filter matching support** - Consider if filters need to scope to it
282
+ 3. **Write tests first** - Unit tests for logic, integration tests for behavior
283
+ 4. **Update documentation** - Keep this file and claude.patterns.md up to date
284
+ 5. **Follow existing patterns** - Look at similar features for consistency
285
+
286
+ ## Common Mistakes to Avoid
287
+
288
+ 1. ❌ Using interfaces for data structures
289
+ 2. ❌ Creating anonymous object literals for configs/definitions
290
+ 3. ❌ Forgetting to export classes from index.ts
291
+ 4. ❌ Using `any` instead of `unknown` for generic types
292
+ 5. ❌ Skipping tests for new features
293
+ 6. ❌ Not documenting differences from Java version