@webpieces/dev-config 0.2.45 → 0.2.47

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,26 @@
1
+ /**
2
+ * Validate Modified Methods Executor
3
+ *
4
+ * Validates that modified methods don't exceed a maximum line count (default 80).
5
+ * This encourages gradual cleanup of legacy long methods - when you touch a method,
6
+ * you must bring it under the limit.
7
+ *
8
+ * Combined with validate-new-methods (30 line limit), this creates a gradual
9
+ * transition to cleaner code:
10
+ * - New methods: strict 30 line limit
11
+ * - Modified methods: lenient 80 line limit (cleanup when touched)
12
+ * - Untouched methods: no limit (legacy allowed)
13
+ *
14
+ * Usage:
15
+ * nx affected --target=validate-modified-methods --base=origin/main
16
+ *
17
+ * Escape hatch: Add webpieces-disable max-lines-new-and-modified comment with justification
18
+ */
19
+ import type { ExecutorContext } from '@nx/devkit';
20
+ export interface ValidateModifiedMethodsOptions {
21
+ max?: number;
22
+ }
23
+ export interface ExecutorResult {
24
+ success: boolean;
25
+ }
26
+ export default function runExecutor(options: ValidateModifiedMethodsOptions, context: ExecutorContext): Promise<ExecutorResult>;
@@ -0,0 +1,505 @@
1
+ "use strict";
2
+ /**
3
+ * Validate Modified Methods Executor
4
+ *
5
+ * Validates that modified methods don't exceed a maximum line count (default 80).
6
+ * This encourages gradual cleanup of legacy long methods - when you touch a method,
7
+ * you must bring it under the limit.
8
+ *
9
+ * Combined with validate-new-methods (30 line limit), this creates a gradual
10
+ * transition to cleaner code:
11
+ * - New methods: strict 30 line limit
12
+ * - Modified methods: lenient 80 line limit (cleanup when touched)
13
+ * - Untouched methods: no limit (legacy allowed)
14
+ *
15
+ * Usage:
16
+ * nx affected --target=validate-modified-methods --base=origin/main
17
+ *
18
+ * Escape hatch: Add webpieces-disable max-lines-new-and-modified comment with justification
19
+ */
20
+ Object.defineProperty(exports, "__esModule", { value: true });
21
+ exports.default = runExecutor;
22
+ const tslib_1 = require("tslib");
23
+ const child_process_1 = require("child_process");
24
+ const fs = tslib_1.__importStar(require("fs"));
25
+ const path = tslib_1.__importStar(require("path"));
26
+ const ts = tslib_1.__importStar(require("typescript"));
27
+ const TMP_DIR = 'tmp/webpieces';
28
+ const TMP_MD_FILE = 'webpieces.methodsize.md';
29
+ const METHODSIZE_DOC_CONTENT = `# Instructions: Method Too Long
30
+
31
+ ## Requirement
32
+
33
+ **~50% of the time**, you can stay under the \`newMethodsMaxLines\` limit from nx.json
34
+ by extracting logical units into well-named methods.
35
+
36
+ **~99% of the time**, you can stay under the \`modifiedAndNewMethodsMaxLines\` limit from nx.json.
37
+ Nearly all software can be written with methods under this size.
38
+ Take the extra time to refactor - it's worth it for long-term maintainability.
39
+
40
+ ## The "Table of Contents" Principle
41
+
42
+ Good code reads like a book's table of contents:
43
+ - Chapter titles (method names) tell you WHAT happens
44
+ - Reading chapter titles gives you the full story
45
+ - You can dive into chapters (implementations) for details
46
+
47
+ ## Why Limit Method Sizes?
48
+
49
+ Methods under reasonable limits are:
50
+ - Easy to review in a single screen
51
+ - Simple to understand without scrolling
52
+ - Quick for AI to analyze and suggest improvements
53
+ - More testable in isolation
54
+ - Self-documenting through well-named extracted methods
55
+
56
+ ## Gradual Cleanup Strategy
57
+
58
+ This codebase uses a gradual cleanup approach:
59
+ - **New methods**: Must be under \`newMethodsMaxLines\` from nx.json
60
+ - **Modified methods**: Must be under \`modifiedAndNewMethodsMaxLines\` from nx.json
61
+ - **Untouched methods**: No limit (legacy code is allowed until touched)
62
+
63
+ ## How to Refactor
64
+
65
+ Instead of:
66
+ \`\`\`typescript
67
+ async processOrder(order: Order): Promise<Result> {
68
+ // 100 lines of validation, transformation, saving, notifications...
69
+ }
70
+ \`\`\`
71
+
72
+ Write:
73
+ \`\`\`typescript
74
+ async processOrder(order: Order): Promise<Result> {
75
+ const validated = this.validateOrder(order);
76
+ const transformed = this.applyBusinessRules(validated);
77
+ const saved = await this.saveToDatabase(transformed);
78
+ await this.notifyStakeholders(saved);
79
+ return this.buildResult(saved);
80
+ }
81
+ \`\`\`
82
+
83
+ Now the main method is a "table of contents" - each line tells part of the story!
84
+
85
+ ## Patterns for Extraction
86
+
87
+ ### Pattern 1: Extract Loop Bodies
88
+ \`\`\`typescript
89
+ // BEFORE
90
+ for (const item of items) {
91
+ // 20 lines of processing
92
+ }
93
+
94
+ // AFTER
95
+ for (const item of items) {
96
+ this.processItem(item);
97
+ }
98
+ \`\`\`
99
+
100
+ ### Pattern 2: Extract Conditional Blocks
101
+ \`\`\`typescript
102
+ // BEFORE
103
+ if (isAdmin(user)) {
104
+ // 15 lines of admin logic
105
+ }
106
+
107
+ // AFTER
108
+ if (isAdmin(user)) {
109
+ this.handleAdminUser(user);
110
+ }
111
+ \`\`\`
112
+
113
+ ### Pattern 3: Extract Data Transformations
114
+ \`\`\`typescript
115
+ // BEFORE
116
+ const result = {
117
+ // 10+ lines of object construction
118
+ };
119
+
120
+ // AFTER
121
+ const result = this.buildResultObject(data);
122
+ \`\`\`
123
+
124
+ ## If Refactoring Is Not Feasible
125
+
126
+ Sometimes methods genuinely need to be longer (complex algorithms, state machines, etc.).
127
+
128
+ **Escape hatch**: Add a webpieces-disable comment with justification:
129
+
130
+ \`\`\`typescript
131
+ // webpieces-disable max-lines-new-and-modified -- Complex state machine, splitting reduces clarity
132
+ async complexStateMachine(): Promise<void> {
133
+ // ... longer method with justification
134
+ }
135
+ \`\`\`
136
+
137
+ ## AI Agent Action Steps
138
+
139
+ 1. **READ** the method to understand its logical sections
140
+ 2. **IDENTIFY** logical units that can be extracted
141
+ 3. **EXTRACT** into well-named private methods
142
+ 4. **VERIFY** the main method now reads like a table of contents
143
+ 5. **IF NOT FEASIBLE**: Add webpieces-disable max-lines-new-and-modified comment with clear justification
144
+
145
+ ## Remember
146
+
147
+ - Every method you write today will be read many times tomorrow
148
+ - The best code explains itself through structure
149
+ - When in doubt, extract and name it
150
+ `;
151
+ /**
152
+ * Write the instructions documentation to tmp directory
153
+ */
154
+ function writeTmpInstructions(workspaceRoot) {
155
+ const tmpDir = path.join(workspaceRoot, TMP_DIR);
156
+ const mdPath = path.join(tmpDir, TMP_MD_FILE);
157
+ fs.mkdirSync(tmpDir, { recursive: true });
158
+ fs.writeFileSync(mdPath, METHODSIZE_DOC_CONTENT);
159
+ return mdPath;
160
+ }
161
+ /**
162
+ * Get changed TypeScript files between base and working tree.
163
+ * Uses `git diff base` (no three-dots) to match what `nx affected` does -
164
+ * this includes both committed and uncommitted changes in one diff.
165
+ */
166
+ function getChangedTypeScriptFiles(workspaceRoot, base) {
167
+ try {
168
+ // Use two-dot diff (base to working tree) - same as nx affected
169
+ const output = (0, child_process_1.execSync)(`git diff --name-only ${base} -- '*.ts' '*.tsx'`, {
170
+ cwd: workspaceRoot,
171
+ encoding: 'utf-8',
172
+ });
173
+ return output
174
+ .trim()
175
+ .split('\n')
176
+ .filter((f) => f && !f.includes('.spec.ts') && !f.includes('.test.ts'));
177
+ }
178
+ catch {
179
+ return [];
180
+ }
181
+ }
182
+ /**
183
+ * Get the diff content for a specific file between base and working tree.
184
+ * Uses `git diff base` (no three-dots) to match what `nx affected` does -
185
+ * this includes both committed and uncommitted changes in one diff.
186
+ */
187
+ function getFileDiff(workspaceRoot, file, base) {
188
+ try {
189
+ // Use two-dot diff (base to working tree) - same as nx affected
190
+ return (0, child_process_1.execSync)(`git diff ${base} -- "${file}"`, {
191
+ cwd: workspaceRoot,
192
+ encoding: 'utf-8',
193
+ });
194
+ }
195
+ catch {
196
+ return '';
197
+ }
198
+ }
199
+ /**
200
+ * Parse diff to find NEW method signatures.
201
+ * Must handle: export function, async function, const/let arrow functions, class methods
202
+ */
203
+ // webpieces-disable max-lines-new-methods -- Regex patterns require inline documentation
204
+ function findNewMethodSignaturesInDiff(diffContent) {
205
+ const newMethods = new Set();
206
+ const lines = diffContent.split('\n');
207
+ // Patterns to match method definitions (same as validate-new-methods)
208
+ const patterns = [
209
+ // [export] [async] function methodName( - most explicit, check first
210
+ /^\+\s*(?:export\s+)?(?:async\s+)?function\s+(\w+)\s*\(/,
211
+ // [export] const/let methodName = [async] (
212
+ /^\+\s*(?:export\s+)?(?:const|let)\s+(\w+)\s*=\s*(?:async\s*)?\(/,
213
+ // [export] const/let methodName = [async] function
214
+ /^\+\s*(?:export\s+)?(?:const|let)\s+(\w+)\s*=\s*(?:async\s+)?function/,
215
+ // class method: [async] methodName( - but NOT constructor, if, for, while, etc.
216
+ /^\+\s*(?:async\s+)?(\w+)\s*\(/,
217
+ ];
218
+ for (const line of lines) {
219
+ if (line.startsWith('+') && !line.startsWith('+++')) {
220
+ for (const pattern of patterns) {
221
+ const match = line.match(pattern);
222
+ if (match) {
223
+ // Extract method name - now always in capture group 1
224
+ const methodName = match[1];
225
+ if (methodName && !['if', 'for', 'while', 'switch', 'catch', 'constructor'].includes(methodName)) {
226
+ newMethods.add(methodName);
227
+ }
228
+ break;
229
+ }
230
+ }
231
+ }
232
+ }
233
+ return newMethods;
234
+ }
235
+ /**
236
+ * Parse diff to find line numbers that have changes in the new file
237
+ */
238
+ function getChangedLineNumbers(diffContent) {
239
+ const changedLines = new Set();
240
+ const lines = diffContent.split('\n');
241
+ let currentNewLine = 0;
242
+ for (const line of lines) {
243
+ // Parse hunk header: @@ -oldStart,oldCount +newStart,newCount @@
244
+ const hunkMatch = line.match(/^@@ -\d+(?:,\d+)? \+(\d+)(?:,\d+)? @@/);
245
+ if (hunkMatch) {
246
+ currentNewLine = parseInt(hunkMatch[1], 10);
247
+ continue;
248
+ }
249
+ if (currentNewLine === 0)
250
+ continue;
251
+ if (line.startsWith('+') && !line.startsWith('+++')) {
252
+ // Added line
253
+ changedLines.add(currentNewLine);
254
+ currentNewLine++;
255
+ }
256
+ else if (line.startsWith('-') && !line.startsWith('---')) {
257
+ // Removed line - doesn't increment new line counter
258
+ }
259
+ else if (!line.startsWith('\\')) {
260
+ // Context line (unchanged)
261
+ currentNewLine++;
262
+ }
263
+ }
264
+ return changedLines;
265
+ }
266
+ /**
267
+ * Check what kind of webpieces-disable comment is present for a method.
268
+ * Returns: 'full' | 'new-only' | 'none'
269
+ * - 'full': max-lines-new-and-modified (ultimate escape, skips both validators)
270
+ * - 'new-only': max-lines-new-methods (escaped 30-line check, still needs 80-line check)
271
+ * - 'none': no escape hatch
272
+ */
273
+ function getDisableType(lines, lineNumber) {
274
+ const startCheck = Math.max(0, lineNumber - 5);
275
+ for (let i = lineNumber - 2; i >= startCheck; i--) {
276
+ const line = lines[i]?.trim() ?? '';
277
+ if (line.startsWith('function ') || line.startsWith('class ') || line.endsWith('}')) {
278
+ break;
279
+ }
280
+ if (line.includes('webpieces-disable')) {
281
+ if (line.includes('max-lines-new-and-modified')) {
282
+ return 'full';
283
+ }
284
+ if (line.includes('max-lines-new-methods')) {
285
+ return 'new-only';
286
+ }
287
+ }
288
+ }
289
+ return 'none';
290
+ }
291
+ /**
292
+ * Parse a TypeScript file and find methods with their line counts
293
+ */
294
+ // webpieces-disable max-lines-new-methods -- AST traversal requires inline visitor function
295
+ function findMethodsInFile(filePath, workspaceRoot) {
296
+ const fullPath = path.join(workspaceRoot, filePath);
297
+ if (!fs.existsSync(fullPath))
298
+ return [];
299
+ const content = fs.readFileSync(fullPath, 'utf-8');
300
+ const fileLines = content.split('\n');
301
+ const sourceFile = ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, true);
302
+ const methods = [];
303
+ // webpieces-disable max-lines-new-methods -- AST visitor pattern requires handling multiple node types
304
+ function visit(node) {
305
+ let methodName;
306
+ let startLine;
307
+ let endLine;
308
+ if (ts.isMethodDeclaration(node) && node.name) {
309
+ methodName = node.name.getText(sourceFile);
310
+ const start = sourceFile.getLineAndCharacterOfPosition(node.getStart());
311
+ const end = sourceFile.getLineAndCharacterOfPosition(node.getEnd());
312
+ startLine = start.line + 1;
313
+ endLine = end.line + 1;
314
+ }
315
+ else if (ts.isFunctionDeclaration(node) && node.name) {
316
+ methodName = node.name.getText(sourceFile);
317
+ const start = sourceFile.getLineAndCharacterOfPosition(node.getStart());
318
+ const end = sourceFile.getLineAndCharacterOfPosition(node.getEnd());
319
+ startLine = start.line + 1;
320
+ endLine = end.line + 1;
321
+ }
322
+ else if (ts.isArrowFunction(node)) {
323
+ if (ts.isVariableDeclaration(node.parent) && ts.isIdentifier(node.parent.name)) {
324
+ methodName = node.parent.name.getText(sourceFile);
325
+ const start = sourceFile.getLineAndCharacterOfPosition(node.getStart());
326
+ const end = sourceFile.getLineAndCharacterOfPosition(node.getEnd());
327
+ startLine = start.line + 1;
328
+ endLine = end.line + 1;
329
+ }
330
+ }
331
+ if (methodName && startLine !== undefined && endLine !== undefined) {
332
+ methods.push({
333
+ name: methodName,
334
+ line: startLine,
335
+ endLine: endLine,
336
+ lines: endLine - startLine + 1,
337
+ disableType: getDisableType(fileLines, startLine),
338
+ });
339
+ }
340
+ ts.forEachChild(node, visit);
341
+ }
342
+ visit(sourceFile);
343
+ return methods;
344
+ }
345
+ /**
346
+ * Find methods that exceed the 80-line limit.
347
+ *
348
+ * This validator checks:
349
+ * 1. NEW methods that have `max-lines-new-methods` escape (they passed 30-line check, now need 80-line check)
350
+ * 2. MODIFIED methods (existing methods with changes)
351
+ *
352
+ * Skips:
353
+ * - NEW methods without any escape (let validate-new-methods handle them first)
354
+ * - Methods with `max-lines-new-and-modified` escape (ultimate escape hatch)
355
+ */
356
+ // webpieces-disable max-lines-new-methods -- Core validation logic with multiple file operations
357
+ function findViolations(workspaceRoot, changedFiles, base, maxLines) {
358
+ const violations = [];
359
+ for (const file of changedFiles) {
360
+ const diff = getFileDiff(workspaceRoot, file, base);
361
+ if (!diff)
362
+ continue;
363
+ // Find NEW methods from the diff
364
+ const newMethodNames = findNewMethodSignaturesInDiff(diff);
365
+ // Find which lines have changes
366
+ const changedLineNumbers = getChangedLineNumbers(diff);
367
+ if (changedLineNumbers.size === 0)
368
+ continue;
369
+ // Parse the current file to get all methods
370
+ const methods = findMethodsInFile(file, workspaceRoot);
371
+ for (const method of methods) {
372
+ const isNewMethod = newMethodNames.has(method.name);
373
+ // Skip methods with full escape (max-lines-new-and-modified)
374
+ if (method.disableType === 'full')
375
+ continue;
376
+ // Skip methods under the limit
377
+ if (method.lines <= maxLines)
378
+ continue;
379
+ if (isNewMethod) {
380
+ // For NEW methods:
381
+ // - If has 'new-only' escape → check (they escaped 30-line, now need 80-line check)
382
+ // - If has 'none' → skip (let validate-new-methods handle first)
383
+ if (method.disableType !== 'new-only')
384
+ continue;
385
+ // New method with max-lines-new-methods escape - check against 80-line limit
386
+ violations.push({
387
+ file,
388
+ methodName: method.name,
389
+ line: method.line,
390
+ lines: method.lines,
391
+ });
392
+ }
393
+ else {
394
+ // For MODIFIED methods: check if any changed line falls within method's range
395
+ let hasChanges = false;
396
+ for (let line = method.line; line <= method.endLine; line++) {
397
+ if (changedLineNumbers.has(line)) {
398
+ hasChanges = true;
399
+ break;
400
+ }
401
+ }
402
+ if (hasChanges) {
403
+ violations.push({
404
+ file,
405
+ methodName: method.name,
406
+ line: method.line,
407
+ lines: method.lines,
408
+ });
409
+ }
410
+ }
411
+ }
412
+ }
413
+ return violations;
414
+ }
415
+ /**
416
+ * Auto-detect the base branch by finding the merge-base with origin/main.
417
+ */
418
+ function detectBase(workspaceRoot) {
419
+ try {
420
+ const mergeBase = (0, child_process_1.execSync)('git merge-base HEAD origin/main', {
421
+ cwd: workspaceRoot,
422
+ encoding: 'utf-8',
423
+ stdio: ['pipe', 'pipe', 'pipe'],
424
+ }).trim();
425
+ if (mergeBase) {
426
+ return mergeBase;
427
+ }
428
+ }
429
+ catch {
430
+ try {
431
+ const mergeBase = (0, child_process_1.execSync)('git merge-base HEAD main', {
432
+ cwd: workspaceRoot,
433
+ encoding: 'utf-8',
434
+ stdio: ['pipe', 'pipe', 'pipe'],
435
+ }).trim();
436
+ if (mergeBase) {
437
+ return mergeBase;
438
+ }
439
+ }
440
+ catch {
441
+ // Ignore
442
+ }
443
+ }
444
+ return null;
445
+ }
446
+ async function runExecutor(options, context) {
447
+ const workspaceRoot = context.root;
448
+ const maxLines = options.max ?? 80;
449
+ let base = process.env['NX_BASE'];
450
+ if (!base) {
451
+ base = detectBase(workspaceRoot) ?? undefined;
452
+ if (!base) {
453
+ console.log('\n⏭️ Skipping modified method validation (could not detect base branch)');
454
+ console.log(' To run explicitly: nx affected --target=validate-modified-methods --base=origin/main');
455
+ console.log('');
456
+ return { success: true };
457
+ }
458
+ console.log('\n📏 Validating Modified Method Sizes (auto-detected base)\n');
459
+ }
460
+ else {
461
+ console.log('\n📏 Validating Modified Method Sizes\n');
462
+ }
463
+ console.log(` Base: ${base}`);
464
+ console.log(' Comparing to: working tree (includes uncommitted changes)');
465
+ console.log(` Max lines for modified methods: ${maxLines}`);
466
+ console.log('');
467
+ try {
468
+ const changedFiles = getChangedTypeScriptFiles(workspaceRoot, base);
469
+ if (changedFiles.length === 0) {
470
+ console.log('✅ No TypeScript files changed');
471
+ return { success: true };
472
+ }
473
+ console.log(`📂 Checking ${changedFiles.length} changed file(s)...`);
474
+ const violations = findViolations(workspaceRoot, changedFiles, base, maxLines);
475
+ if (violations.length === 0) {
476
+ console.log('✅ All modified methods are under ' + maxLines + ' lines');
477
+ return { success: true };
478
+ }
479
+ // Write instructions file
480
+ writeTmpInstructions(workspaceRoot);
481
+ // Report violations
482
+ console.error('');
483
+ console.error('❌ Modified methods exceed ' + maxLines + ' lines!');
484
+ console.error('');
485
+ console.error('📚 When you modify a method, you must bring it under ' + maxLines + ' lines.');
486
+ console.error(' This encourages gradual cleanup of legacy code.');
487
+ console.error(' You can refactor to stay under the limit 50% of the time.');
488
+ console.error(' If not feasible, use the escape hatch.');
489
+ console.error('');
490
+ console.error('⚠️ *** READ tmp/webpieces/webpieces.methodsize.md for detailed guidance on how to fix this easily *** ⚠️');
491
+ console.error('');
492
+ for (const v of violations) {
493
+ console.error(` ❌ ${v.file}:${v.line}`);
494
+ console.error(` Method: ${v.methodName} (${v.lines} lines, max: ${maxLines})`);
495
+ }
496
+ console.error('');
497
+ return { success: false };
498
+ }
499
+ catch (err) {
500
+ const error = err instanceof Error ? err : new Error(String(err));
501
+ console.error('❌ Modified method validation failed:', error.message);
502
+ return { success: false };
503
+ }
504
+ }
505
+ //# sourceMappingURL=executor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"executor.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/dev-config/architecture/executors/validate-modified-methods/executor.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Validate Modified Methods Executor\n *\n * Validates that modified methods don't exceed a maximum line count (default 80).\n * This encourages gradual cleanup of legacy long methods - when you touch a method,\n * you must bring it under the limit.\n *\n * Combined with validate-new-methods (30 line limit), this creates a gradual\n * transition to cleaner code:\n * - New methods: strict 30 line limit\n * - Modified methods: lenient 80 line limit (cleanup when touched)\n * - Untouched methods: no limit (legacy allowed)\n *\n * Usage:\n * nx affected --target=validate-modified-methods --base=origin/main\n *\n * Escape hatch: Add webpieces-disable max-lines-new-and-modified comment with justification\n */\n\nimport type { ExecutorContext } from '@nx/devkit';\nimport { execSync } from 'child_process';\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport * as ts from 'typescript';\n\nexport interface ValidateModifiedMethodsOptions {\n max?: number;\n}\n\nexport interface ExecutorResult {\n success: boolean;\n}\n\ninterface MethodViolation {\n file: string;\n methodName: string;\n line: number;\n lines: number;\n}\n\nconst TMP_DIR = 'tmp/webpieces';\nconst TMP_MD_FILE = 'webpieces.methodsize.md';\n\nconst METHODSIZE_DOC_CONTENT = `# Instructions: Method Too Long\n\n## Requirement\n\n**~50% of the time**, you can stay under the \\`newMethodsMaxLines\\` limit from nx.json\nby extracting logical units into well-named methods.\n\n**~99% of the time**, you can stay under the \\`modifiedAndNewMethodsMaxLines\\` limit from nx.json.\nNearly all software can be written with methods under this size.\nTake the extra time to refactor - it's worth it for long-term maintainability.\n\n## The \"Table of Contents\" Principle\n\nGood code reads like a book's table of contents:\n- Chapter titles (method names) tell you WHAT happens\n- Reading chapter titles gives you the full story\n- You can dive into chapters (implementations) for details\n\n## Why Limit Method Sizes?\n\nMethods under reasonable limits are:\n- Easy to review in a single screen\n- Simple to understand without scrolling\n- Quick for AI to analyze and suggest improvements\n- More testable in isolation\n- Self-documenting through well-named extracted methods\n\n## Gradual Cleanup Strategy\n\nThis codebase uses a gradual cleanup approach:\n- **New methods**: Must be under \\`newMethodsMaxLines\\` from nx.json\n- **Modified methods**: Must be under \\`modifiedAndNewMethodsMaxLines\\` from nx.json\n- **Untouched methods**: No limit (legacy code is allowed until touched)\n\n## How to Refactor\n\nInstead of:\n\\`\\`\\`typescript\nasync processOrder(order: Order): Promise<Result> {\n // 100 lines of validation, transformation, saving, notifications...\n}\n\\`\\`\\`\n\nWrite:\n\\`\\`\\`typescript\nasync processOrder(order: Order): Promise<Result> {\n const validated = this.validateOrder(order);\n const transformed = this.applyBusinessRules(validated);\n const saved = await this.saveToDatabase(transformed);\n await this.notifyStakeholders(saved);\n return this.buildResult(saved);\n}\n\\`\\`\\`\n\nNow the main method is a \"table of contents\" - each line tells part of the story!\n\n## Patterns for Extraction\n\n### Pattern 1: Extract Loop Bodies\n\\`\\`\\`typescript\n// BEFORE\nfor (const item of items) {\n // 20 lines of processing\n}\n\n// AFTER\nfor (const item of items) {\n this.processItem(item);\n}\n\\`\\`\\`\n\n### Pattern 2: Extract Conditional Blocks\n\\`\\`\\`typescript\n// BEFORE\nif (isAdmin(user)) {\n // 15 lines of admin logic\n}\n\n// AFTER\nif (isAdmin(user)) {\n this.handleAdminUser(user);\n}\n\\`\\`\\`\n\n### Pattern 3: Extract Data Transformations\n\\`\\`\\`typescript\n// BEFORE\nconst result = {\n // 10+ lines of object construction\n};\n\n// AFTER\nconst result = this.buildResultObject(data);\n\\`\\`\\`\n\n## If Refactoring Is Not Feasible\n\nSometimes methods genuinely need to be longer (complex algorithms, state machines, etc.).\n\n**Escape hatch**: Add a webpieces-disable comment with justification:\n\n\\`\\`\\`typescript\n// webpieces-disable max-lines-new-and-modified -- Complex state machine, splitting reduces clarity\nasync complexStateMachine(): Promise<void> {\n // ... longer method with justification\n}\n\\`\\`\\`\n\n## AI Agent Action Steps\n\n1. **READ** the method to understand its logical sections\n2. **IDENTIFY** logical units that can be extracted\n3. **EXTRACT** into well-named private methods\n4. **VERIFY** the main method now reads like a table of contents\n5. **IF NOT FEASIBLE**: Add webpieces-disable max-lines-new-and-modified comment with clear justification\n\n## Remember\n\n- Every method you write today will be read many times tomorrow\n- The best code explains itself through structure\n- When in doubt, extract and name it\n`;\n\n/**\n * Write the instructions documentation to tmp directory\n */\nfunction writeTmpInstructions(workspaceRoot: string): string {\n const tmpDir = path.join(workspaceRoot, TMP_DIR);\n const mdPath = path.join(tmpDir, TMP_MD_FILE);\n\n fs.mkdirSync(tmpDir, { recursive: true });\n fs.writeFileSync(mdPath, METHODSIZE_DOC_CONTENT);\n\n return mdPath;\n}\n\n/**\n * Get changed TypeScript files between base and working tree.\n * Uses `git diff base` (no three-dots) to match what `nx affected` does -\n * this includes both committed and uncommitted changes in one diff.\n */\nfunction getChangedTypeScriptFiles(workspaceRoot: string, base: string): string[] {\n try {\n // Use two-dot diff (base to working tree) - same as nx affected\n const output = execSync(`git diff --name-only ${base} -- '*.ts' '*.tsx'`, {\n cwd: workspaceRoot,\n encoding: 'utf-8',\n });\n return output\n .trim()\n .split('\\n')\n .filter((f) => f && !f.includes('.spec.ts') && !f.includes('.test.ts'));\n } catch {\n return [];\n }\n}\n\n/**\n * Get the diff content for a specific file between base and working tree.\n * Uses `git diff base` (no three-dots) to match what `nx affected` does -\n * this includes both committed and uncommitted changes in one diff.\n */\nfunction getFileDiff(workspaceRoot: string, file: string, base: string): string {\n try {\n // Use two-dot diff (base to working tree) - same as nx affected\n return execSync(`git diff ${base} -- \"${file}\"`, {\n cwd: workspaceRoot,\n encoding: 'utf-8',\n });\n } catch {\n return '';\n }\n}\n\n/**\n * Parse diff to find NEW method signatures.\n * Must handle: export function, async function, const/let arrow functions, class methods\n */\n// webpieces-disable max-lines-new-methods -- Regex patterns require inline documentation\nfunction findNewMethodSignaturesInDiff(diffContent: string): Set<string> {\n const newMethods = new Set<string>();\n const lines = diffContent.split('\\n');\n\n // Patterns to match method definitions (same as validate-new-methods)\n const patterns = [\n // [export] [async] function methodName( - most explicit, check first\n /^\\+\\s*(?:export\\s+)?(?:async\\s+)?function\\s+(\\w+)\\s*\\(/,\n // [export] const/let methodName = [async] (\n /^\\+\\s*(?:export\\s+)?(?:const|let)\\s+(\\w+)\\s*=\\s*(?:async\\s*)?\\(/,\n // [export] const/let methodName = [async] function\n /^\\+\\s*(?:export\\s+)?(?:const|let)\\s+(\\w+)\\s*=\\s*(?:async\\s+)?function/,\n // class method: [async] methodName( - but NOT constructor, if, for, while, etc.\n /^\\+\\s*(?:async\\s+)?(\\w+)\\s*\\(/,\n ];\n\n for (const line of lines) {\n if (line.startsWith('+') && !line.startsWith('+++')) {\n for (const pattern of patterns) {\n const match = line.match(pattern);\n if (match) {\n // Extract method name - now always in capture group 1\n const methodName = match[1];\n if (methodName && !['if', 'for', 'while', 'switch', 'catch', 'constructor'].includes(methodName)) {\n newMethods.add(methodName);\n }\n break;\n }\n }\n }\n }\n\n return newMethods;\n}\n\n/**\n * Parse diff to find line numbers that have changes in the new file\n */\nfunction getChangedLineNumbers(diffContent: string): Set<number> {\n const changedLines = new Set<number>();\n const lines = diffContent.split('\\n');\n\n let currentNewLine = 0;\n\n for (const line of lines) {\n // Parse hunk header: @@ -oldStart,oldCount +newStart,newCount @@\n const hunkMatch = line.match(/^@@ -\\d+(?:,\\d+)? \\+(\\d+)(?:,\\d+)? @@/);\n if (hunkMatch) {\n currentNewLine = parseInt(hunkMatch[1], 10);\n continue;\n }\n\n if (currentNewLine === 0) continue;\n\n if (line.startsWith('+') && !line.startsWith('+++')) {\n // Added line\n changedLines.add(currentNewLine);\n currentNewLine++;\n } else if (line.startsWith('-') && !line.startsWith('---')) {\n // Removed line - doesn't increment new line counter\n } else if (!line.startsWith('\\\\')) {\n // Context line (unchanged)\n currentNewLine++;\n }\n }\n\n return changedLines;\n}\n\n/**\n * Check what kind of webpieces-disable comment is present for a method.\n * Returns: 'full' | 'new-only' | 'none'\n * - 'full': max-lines-new-and-modified (ultimate escape, skips both validators)\n * - 'new-only': max-lines-new-methods (escaped 30-line check, still needs 80-line check)\n * - 'none': no escape hatch\n */\nfunction getDisableType(lines: string[], lineNumber: number): 'full' | 'new-only' | 'none' {\n const startCheck = Math.max(0, lineNumber - 5);\n for (let i = lineNumber - 2; i >= startCheck; i--) {\n const line = lines[i]?.trim() ?? '';\n if (line.startsWith('function ') || line.startsWith('class ') || line.endsWith('}')) {\n break;\n }\n if (line.includes('webpieces-disable')) {\n if (line.includes('max-lines-new-and-modified')) {\n return 'full';\n }\n if (line.includes('max-lines-new-methods')) {\n return 'new-only';\n }\n }\n }\n return 'none';\n}\n\n/**\n * Parse a TypeScript file and find methods with their line counts\n */\n// webpieces-disable max-lines-new-methods -- AST traversal requires inline visitor function\nfunction findMethodsInFile(\n filePath: string,\n workspaceRoot: string\n): Array<{ name: string; line: number; endLine: number; lines: number; disableType: 'full' | 'new-only' | 'none' }> {\n const fullPath = path.join(workspaceRoot, filePath);\n if (!fs.existsSync(fullPath)) return [];\n\n const content = fs.readFileSync(fullPath, 'utf-8');\n const fileLines = content.split('\\n');\n const sourceFile = ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, true);\n\n const methods: Array<{ name: string; line: number; endLine: number; lines: number; disableType: 'full' | 'new-only' | 'none' }> =\n [];\n\n // webpieces-disable max-lines-new-methods -- AST visitor pattern requires handling multiple node types\n function visit(node: ts.Node): void {\n let methodName: string | undefined;\n let startLine: number | undefined;\n let endLine: number | undefined;\n\n if (ts.isMethodDeclaration(node) && node.name) {\n methodName = node.name.getText(sourceFile);\n const start = sourceFile.getLineAndCharacterOfPosition(node.getStart());\n const end = sourceFile.getLineAndCharacterOfPosition(node.getEnd());\n startLine = start.line + 1;\n endLine = end.line + 1;\n } else if (ts.isFunctionDeclaration(node) && node.name) {\n methodName = node.name.getText(sourceFile);\n const start = sourceFile.getLineAndCharacterOfPosition(node.getStart());\n const end = sourceFile.getLineAndCharacterOfPosition(node.getEnd());\n startLine = start.line + 1;\n endLine = end.line + 1;\n } else if (ts.isArrowFunction(node)) {\n if (ts.isVariableDeclaration(node.parent) && ts.isIdentifier(node.parent.name)) {\n methodName = node.parent.name.getText(sourceFile);\n const start = sourceFile.getLineAndCharacterOfPosition(node.getStart());\n const end = sourceFile.getLineAndCharacterOfPosition(node.getEnd());\n startLine = start.line + 1;\n endLine = end.line + 1;\n }\n }\n\n if (methodName && startLine !== undefined && endLine !== undefined) {\n methods.push({\n name: methodName,\n line: startLine,\n endLine: endLine,\n lines: endLine - startLine + 1,\n disableType: getDisableType(fileLines, startLine),\n });\n }\n\n ts.forEachChild(node, visit);\n }\n\n visit(sourceFile);\n return methods;\n}\n\n/**\n * Find methods that exceed the 80-line limit.\n *\n * This validator checks:\n * 1. NEW methods that have `max-lines-new-methods` escape (they passed 30-line check, now need 80-line check)\n * 2. MODIFIED methods (existing methods with changes)\n *\n * Skips:\n * - NEW methods without any escape (let validate-new-methods handle them first)\n * - Methods with `max-lines-new-and-modified` escape (ultimate escape hatch)\n */\n// webpieces-disable max-lines-new-methods -- Core validation logic with multiple file operations\nfunction findViolations(\n workspaceRoot: string,\n changedFiles: string[],\n base: string,\n maxLines: number\n): MethodViolation[] {\n const violations: MethodViolation[] = [];\n\n for (const file of changedFiles) {\n const diff = getFileDiff(workspaceRoot, file, base);\n if (!diff) continue;\n\n // Find NEW methods from the diff\n const newMethodNames = findNewMethodSignaturesInDiff(diff);\n\n // Find which lines have changes\n const changedLineNumbers = getChangedLineNumbers(diff);\n if (changedLineNumbers.size === 0) continue;\n\n // Parse the current file to get all methods\n const methods = findMethodsInFile(file, workspaceRoot);\n\n for (const method of methods) {\n const isNewMethod = newMethodNames.has(method.name);\n\n // Skip methods with full escape (max-lines-new-and-modified)\n if (method.disableType === 'full') continue;\n\n // Skip methods under the limit\n if (method.lines <= maxLines) continue;\n\n if (isNewMethod) {\n // For NEW methods:\n // - If has 'new-only' escape → check (they escaped 30-line, now need 80-line check)\n // - If has 'none' → skip (let validate-new-methods handle first)\n if (method.disableType !== 'new-only') continue;\n\n // New method with max-lines-new-methods escape - check against 80-line limit\n violations.push({\n file,\n methodName: method.name,\n line: method.line,\n lines: method.lines,\n });\n } else {\n // For MODIFIED methods: check if any changed line falls within method's range\n let hasChanges = false;\n for (let line = method.line; line <= method.endLine; line++) {\n if (changedLineNumbers.has(line)) {\n hasChanges = true;\n break;\n }\n }\n\n if (hasChanges) {\n violations.push({\n file,\n methodName: method.name,\n line: method.line,\n lines: method.lines,\n });\n }\n }\n }\n }\n\n return violations;\n}\n\n/**\n * Auto-detect the base branch by finding the merge-base with origin/main.\n */\nfunction detectBase(workspaceRoot: string): string | null {\n try {\n const mergeBase = execSync('git merge-base HEAD origin/main', {\n cwd: workspaceRoot,\n encoding: 'utf-8',\n stdio: ['pipe', 'pipe', 'pipe'],\n }).trim();\n\n if (mergeBase) {\n return mergeBase;\n }\n } catch {\n try {\n const mergeBase = execSync('git merge-base HEAD main', {\n cwd: workspaceRoot,\n encoding: 'utf-8',\n stdio: ['pipe', 'pipe', 'pipe'],\n }).trim();\n\n if (mergeBase) {\n return mergeBase;\n }\n } catch {\n // Ignore\n }\n }\n return null;\n}\n\nexport default async function runExecutor(\n options: ValidateModifiedMethodsOptions,\n context: ExecutorContext\n): Promise<ExecutorResult> {\n const workspaceRoot = context.root;\n const maxLines = options.max ?? 80;\n\n let base = process.env['NX_BASE'];\n\n if (!base) {\n base = detectBase(workspaceRoot) ?? undefined;\n\n if (!base) {\n console.log('\\n⏭️ Skipping modified method validation (could not detect base branch)');\n console.log(' To run explicitly: nx affected --target=validate-modified-methods --base=origin/main');\n console.log('');\n return { success: true };\n }\n\n console.log('\\n📏 Validating Modified Method Sizes (auto-detected base)\\n');\n } else {\n console.log('\\n📏 Validating Modified Method Sizes\\n');\n }\n\n console.log(` Base: ${base}`);\n console.log(' Comparing to: working tree (includes uncommitted changes)');\n console.log(` Max lines for modified methods: ${maxLines}`);\n console.log('');\n\n try {\n const changedFiles = getChangedTypeScriptFiles(workspaceRoot, base);\n\n if (changedFiles.length === 0) {\n console.log('✅ No TypeScript files changed');\n return { success: true };\n }\n\n console.log(`📂 Checking ${changedFiles.length} changed file(s)...`);\n\n const violations = findViolations(workspaceRoot, changedFiles, base, maxLines);\n\n if (violations.length === 0) {\n console.log('✅ All modified methods are under ' + maxLines + ' lines');\n return { success: true };\n }\n\n // Write instructions file\n writeTmpInstructions(workspaceRoot);\n\n // Report violations\n console.error('');\n console.error('❌ Modified methods exceed ' + maxLines + ' lines!');\n console.error('');\n console.error('📚 When you modify a method, you must bring it under ' + maxLines + ' lines.');\n console.error(' This encourages gradual cleanup of legacy code.');\n console.error(' You can refactor to stay under the limit 50% of the time.');\n console.error(' If not feasible, use the escape hatch.');\n console.error('');\n console.error(\n '⚠️ *** READ tmp/webpieces/webpieces.methodsize.md for detailed guidance on how to fix this easily *** ⚠️'\n );\n console.error('');\n\n for (const v of violations) {\n console.error(` ❌ ${v.file}:${v.line}`);\n console.error(` Method: ${v.methodName} (${v.lines} lines, max: ${maxLines})`);\n }\n console.error('');\n\n return { success: false };\n } catch (err: unknown) {\n const error = err instanceof Error ? err : new Error(String(err));\n console.error('❌ Modified method validation failed:', error.message);\n return { success: false };\n }\n}\n"]}