@webpieces/dev-config 0.2.63 → 0.2.64

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.
@@ -13,9 +13,10 @@
13
13
  * The disable expires after 1 month from the date specified.
14
14
  */
15
15
  import type { ExecutorContext } from '@nx/devkit';
16
+ export type ValidationMode = 'STRICT' | 'NORMAL' | 'OFF';
16
17
  export interface ValidateModifiedFilesOptions {
17
18
  max?: number;
18
- forceLimit?: boolean;
19
+ mode?: ValidationMode;
19
20
  }
20
21
  export interface ExecutorResult {
21
22
  success: boolean;
@@ -287,7 +287,7 @@ function checkDisableComment(content) {
287
287
  * Count lines in a file and check for violations
288
288
  */
289
289
  // webpieces-disable max-lines-new-methods -- File iteration with disable checking logic
290
- function findViolations(workspaceRoot, changedFiles, maxLines, forceLimit) {
290
+ function findViolations(workspaceRoot, changedFiles, maxLines, mode) {
291
291
  const violations = [];
292
292
  for (const file of changedFiles) {
293
293
  const fullPath = path.join(workspaceRoot, file);
@@ -298,8 +298,8 @@ function findViolations(workspaceRoot, changedFiles, maxLines, forceLimit) {
298
298
  // Skip files under the limit
299
299
  if (lineCount <= maxLines)
300
300
  continue;
301
- // When forceLimit is true, ignore all disable comments
302
- if (forceLimit) {
301
+ // When mode is STRICT, ignore all disable comments
302
+ if (mode === 'STRICT') {
303
303
  violations.push({ file, lines: lineCount });
304
304
  continue;
305
305
  }
@@ -374,7 +374,7 @@ function getTodayDateString() {
374
374
  * Report violations to console
375
375
  */
376
376
  // webpieces-disable max-lines-new-methods -- Error output formatting with multiple message sections
377
- function reportViolations(violations, maxLines, forceLimit) {
377
+ function reportViolations(violations, maxLines, mode) {
378
378
  console.error('');
379
379
  console.error('❌ YOU MUST FIX THIS AND NOT be more than ' + maxLines + ' lines of code per file');
380
380
  console.error(' as it slows down IDEs AND is VERY VERY EASY to refactor.');
@@ -398,8 +398,8 @@ function reportViolations(violations, maxLines, forceLimit) {
398
398
  }
399
399
  }
400
400
  console.error('');
401
- // Only show escape hatch instructions when forceLimit is not enabled
402
- if (!forceLimit) {
401
+ // Only show escape hatch instructions when mode is not STRICT
402
+ if (mode !== 'STRICT') {
403
403
  console.error(' You can disable this error, but you will be forced to fix again in 1 month');
404
404
  console.error(' since 99% of files can be less than ' + maxLines + ' lines of code.');
405
405
  console.error('');
@@ -408,7 +408,7 @@ function reportViolations(violations, maxLines, forceLimit) {
408
408
  console.error('');
409
409
  }
410
410
  else {
411
- console.error(' ⚠️ forceModifiedFilesLimit is enabled - disable comments are NOT allowed.');
411
+ console.error(' ⚠️ validationMode is STRICT - disable comments are NOT allowed.');
412
412
  console.error(' You MUST refactor to reduce file size.');
413
413
  console.error('');
414
414
  }
@@ -416,7 +416,13 @@ function reportViolations(violations, maxLines, forceLimit) {
416
416
  async function runExecutor(options, context) {
417
417
  const workspaceRoot = context.root;
418
418
  const maxLines = options.max ?? 900;
419
- const forceLimit = options.forceLimit ?? false;
419
+ const mode = options.mode ?? 'NORMAL';
420
+ // Skip validation entirely if mode is OFF
421
+ if (mode === 'OFF') {
422
+ console.log('\n⏭️ Skipping modified files validation (validationMode: OFF)');
423
+ console.log('');
424
+ return { success: true };
425
+ }
420
426
  let base = process.env['NX_BASE'];
421
427
  if (!base) {
422
428
  base = detectBase(workspaceRoot) ?? undefined;
@@ -434,9 +440,7 @@ async function runExecutor(options, context) {
434
440
  console.log(` Base: ${base}`);
435
441
  console.log(' Comparing to: working tree (includes uncommitted changes)');
436
442
  console.log(` Max lines for modified files: ${maxLines}`);
437
- if (forceLimit) {
438
- console.log(' Force limit: ENABLED (disable comments will be ignored)');
439
- }
443
+ console.log(` Validation mode: ${mode}${mode === 'STRICT' ? ' (disable comments ignored)' : ''}`);
440
444
  console.log('');
441
445
  try {
442
446
  const changedFiles = getChangedTypeScriptFiles(workspaceRoot, base);
@@ -445,13 +449,13 @@ async function runExecutor(options, context) {
445
449
  return { success: true };
446
450
  }
447
451
  console.log(`📂 Checking ${changedFiles.length} changed file(s)...`);
448
- const violations = findViolations(workspaceRoot, changedFiles, maxLines, forceLimit);
452
+ const violations = findViolations(workspaceRoot, changedFiles, maxLines, mode);
449
453
  if (violations.length === 0) {
450
454
  console.log('✅ All modified files are under ' + maxLines + ' lines');
451
455
  return { success: true };
452
456
  }
453
457
  writeTmpInstructions(workspaceRoot);
454
- reportViolations(violations, maxLines, forceLimit);
458
+ reportViolations(violations, maxLines, mode);
455
459
  return { success: false };
456
460
  }
457
461
  catch (err) {
@@ -1 +1 @@
1
- {"version":3,"file":"executor.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/dev-config/architecture/executors/validate-modified-files/executor.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Validate Modified Files Executor\n *\n * Validates that modified files don't exceed a maximum line count (default 900).\n * This encourages keeping files small and focused - when you touch a file,\n * you must bring it under the limit.\n *\n * Usage:\n * nx affected --target=validate-modified-files --base=origin/main\n *\n * Escape hatch: Add webpieces-disable max-lines-modified-files comment with date and justification\n * Format: // webpieces-disable max-lines-modified-files 2025/01/15 -- [reason]\n * The disable expires after 1 month from the date specified.\n */\n\nimport type { ExecutorContext } from '@nx/devkit';\nimport { execSync } from 'child_process';\nimport * as fs from 'fs';\nimport * as path from 'path';\n\nexport interface ValidateModifiedFilesOptions {\n max?: number;\n forceLimit?: boolean;\n}\n\nexport interface ExecutorResult {\n success: boolean;\n}\n\ninterface FileViolation {\n file: string;\n lines: number;\n expiredDisable?: boolean;\n expiredDate?: string;\n}\n\nconst TMP_DIR = 'tmp/webpieces';\nconst TMP_MD_FILE = 'webpieces.filesize.md';\n\nconst FILESIZE_DOC_CONTENT = `# AI Agent Instructions: File Too Long\n\n**READ THIS FILE to fix files that are too long**\n\n## Core Principle\n\nWith **stateless systems + dependency injection, refactor is trivial**.\nPick a method or a few and move to new class XXXXX, then inject XXXXX\ninto all users of those methods via the constructor.\nDelete those methods from original class.\n\n**99% of files can be less than the configured max lines of code.**\n\nFiles should contain a SINGLE COHESIVE UNIT.\n- One class per file (Java convention)\n- If class is too large, extract child responsibilities\n- Use dependency injection to compose functionality\n\n## Command: Reduce File Size\n\n### Step 1: Check for Multiple Classes\nIf the file contains multiple classes, **SEPARATE each class into its own file**.\n\n\\`\\`\\`typescript\n// BAD: UserController.ts (multiple classes)\nexport class UserController { /* ... */ }\nexport class UserValidator { /* ... */ }\nexport class UserNotifier { /* ... */ }\n\n// GOOD: Three separate files\n// UserController.ts\nexport class UserController { /* ... */ }\n\n// UserValidator.ts\nexport class UserValidator { /* ... */ }\n\n// UserNotifier.ts\nexport class UserNotifier { /* ... */ }\n\\`\\`\\`\n\n### Step 2: Extract Child Responsibilities (if single class is too large)\n\n#### Pattern: Create New Service Class with Dependency Injection\n\n\\`\\`\\`typescript\n// BAD: UserController.ts (800 lines, single class)\n@provideSingleton()\n@Controller()\nexport class UserController {\n // 200 lines: CRUD operations\n // 300 lines: validation logic\n // 200 lines: notification logic\n // 100 lines: analytics logic\n}\n\n// GOOD: Extract validation service\n// 1. Create UserValidationService.ts\n@provideSingleton()\nexport class UserValidationService {\n validateUserData(data: UserData): ValidationResult {\n // 300 lines of validation logic moved here\n }\n\n validateEmail(email: string): boolean { /* ... */ }\n validatePassword(password: string): boolean { /* ... */ }\n}\n\n// 2. Inject into UserController.ts\n@provideSingleton()\n@Controller()\nexport class UserController {\n constructor(\n @inject(TYPES.UserValidationService)\n private validator: UserValidationService\n ) {}\n\n async createUser(data: UserData): Promise<User> {\n const validation = this.validator.validateUserData(data);\n if (!validation.isValid) {\n throw new ValidationError(validation.errors);\n }\n // ... rest of logic\n }\n}\n\\`\\`\\`\n\n## AI Agent Action Steps\n\n1. **ANALYZE** the file structure:\n - Count classes (if >1, separate immediately)\n - Identify logical responsibilities within single class\n\n2. **IDENTIFY** \"child code\" to extract:\n - Validation logic -> ValidationService\n - Notification logic -> NotificationService\n - Data transformation -> TransformerService\n - External API calls -> ApiService\n - Business rules -> RulesEngine\n\n3. **CREATE** new service file(s):\n - Start with temporary name: \\`XXXX.ts\\` or \\`ChildService.ts\\`\n - Add \\`@provideSingleton()\\` decorator\n - Move child methods to new class\n\n4. **UPDATE** dependency injection:\n - Add to \\`TYPES\\` constants (if using symbol-based DI)\n - Inject new service into original class constructor\n - Replace direct method calls with \\`this.serviceName.method()\\`\n\n5. **RENAME** extracted file:\n - Read the extracted code to understand its purpose\n - Rename \\`XXXX.ts\\` to logical name (e.g., \\`UserValidationService.ts\\`)\n\n6. **VERIFY** file sizes:\n - Original file should now be under the limit\n - Each extracted file should be under the limit\n - If still too large, extract more services\n\n## Examples of Child Responsibilities to Extract\n\n| If File Contains | Extract To | Pattern |\n|-----------------|------------|---------|\n| Validation logic (200+ lines) | \\`XValidator.ts\\` or \\`XValidationService.ts\\` | Singleton service |\n| Notification logic (150+ lines) | \\`XNotifier.ts\\` or \\`XNotificationService.ts\\` | Singleton service |\n| Data transformation (200+ lines) | \\`XTransformer.ts\\` | Singleton service |\n| External API calls (200+ lines) | \\`XApiClient.ts\\` | Singleton service |\n| Complex business rules (300+ lines) | \\`XRulesEngine.ts\\` | Singleton service |\n| Database queries (200+ lines) | \\`XRepository.ts\\` | Singleton service |\n\n## WebPieces Dependency Injection Pattern\n\n\\`\\`\\`typescript\n// 1. Define service with @provideSingleton\nimport { provideSingleton } from '@webpieces/http-routing';\n\n@provideSingleton()\nexport class MyService {\n doSomething(): void { /* ... */ }\n}\n\n// 2. Inject into consumer\nimport { inject } from 'inversify';\nimport { TYPES } from './types';\n\n@provideSingleton()\n@Controller()\nexport class MyController {\n constructor(\n @inject(TYPES.MyService) private service: MyService\n ) {}\n}\n\\`\\`\\`\n\n## Escape Hatch\n\nIf refactoring is genuinely not feasible (generated files, complex algorithms, etc.),\nadd a disable comment at the TOP of the file (within first 5 lines) with a DATE:\n\n\\`\\`\\`typescript\n// webpieces-disable max-lines-modified-files 2025/01/15 -- Complex generated file, refactoring would break generation\n\\`\\`\\`\n\n**IMPORTANT**: The date format is yyyy/mm/dd. The disable will EXPIRE after 1 month from this date.\nAfter expiration, you must either fix the file or update the date to get another month.\nThis ensures that disable comments are reviewed periodically.\n\nRemember: Find the \"child code\" and pull it down into a new class. Once moved, the code's purpose becomes clear, making it easy to rename to a logical name.\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, FILESIZE_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 * Parse a date string in yyyy/mm/dd format and return a Date object.\n * Returns null if the format is invalid.\n */\nfunction parseDisableDate(dateStr: string): Date | null {\n // Match yyyy/mm/dd format\n const match = dateStr.match(/^(\\d{4})\\/(\\d{2})\\/(\\d{2})$/);\n if (!match) return null;\n\n const year = parseInt(match[1], 10);\n const month = parseInt(match[2], 10) - 1; // JS months are 0-indexed\n const day = parseInt(match[3], 10);\n\n const date = new Date(year, month, day);\n\n // Validate the date is valid (e.g., not Feb 30)\n if (date.getFullYear() !== year || date.getMonth() !== month || date.getDate() !== day) {\n return null;\n }\n\n return date;\n}\n\n/**\n * Check if a date is within the last month (not expired).\n */\nfunction isDateWithinMonth(date: Date): boolean {\n const now = new Date();\n const oneMonthAgo = new Date(now.getFullYear(), now.getMonth() - 1, now.getDate());\n return date >= oneMonthAgo;\n}\n\ninterface DisableStatus {\n hasDisable: boolean;\n isValid: boolean;\n isExpired: boolean;\n date?: string;\n}\n\n/**\n * Check if a file has a valid, non-expired disable comment at the top (within first 5 lines).\n * Returns status object with details about the disable comment.\n */\n// webpieces-disable max-lines-new-methods -- Date validation logic requires checking multiple conditions\nfunction checkDisableComment(content: string): DisableStatus {\n const lines = content.split('\\n').slice(0, 5);\n\n for (const line of lines) {\n if (line.includes('webpieces-disable') && line.includes('max-lines-modified-files')) {\n // Found disable comment, now check for date\n // Format: // webpieces-disable max-lines-modified-files yyyy/mm/dd -- reason\n const dateMatch = line.match(/max-lines-modified-files\\s+(\\d{4}\\/\\d{2}\\/\\d{2}|XXXX\\/XX\\/XX)/);\n\n if (!dateMatch) {\n // No date found - invalid disable comment\n return { hasDisable: true, isValid: false, isExpired: false };\n }\n\n const dateStr = dateMatch[1];\n\n // Secret permanent disable\n if (dateStr === 'XXXX/XX/XX') {\n return { hasDisable: true, isValid: true, isExpired: false, date: dateStr };\n }\n\n const date = parseDisableDate(dateStr);\n if (!date) {\n // Invalid date format\n return { hasDisable: true, isValid: false, isExpired: false, date: dateStr };\n }\n\n if (!isDateWithinMonth(date)) {\n // Date is expired (older than 1 month)\n return { hasDisable: true, isValid: true, isExpired: true, date: dateStr };\n }\n\n // Valid and not expired\n return { hasDisable: true, isValid: true, isExpired: false, date: dateStr };\n }\n }\n\n return { hasDisable: false, isValid: false, isExpired: false };\n}\n\n/**\n * Count lines in a file and check for violations\n */\n// webpieces-disable max-lines-new-methods -- File iteration with disable checking logic\nfunction findViolations(workspaceRoot: string, changedFiles: string[], maxLines: number, forceLimit: boolean): FileViolation[] {\n const violations: FileViolation[] = [];\n\n for (const file of changedFiles) {\n const fullPath = path.join(workspaceRoot, file);\n\n if (!fs.existsSync(fullPath)) continue;\n\n const content = fs.readFileSync(fullPath, 'utf-8');\n const lineCount = content.split('\\n').length;\n\n // Skip files under the limit\n if (lineCount <= maxLines) continue;\n\n // When forceLimit is true, ignore all disable comments\n if (forceLimit) {\n violations.push({ file, lines: lineCount });\n continue;\n }\n\n // Check for disable comment\n const disableStatus = checkDisableComment(content);\n\n if (disableStatus.hasDisable) {\n if (disableStatus.isValid && !disableStatus.isExpired) {\n // Valid, non-expired disable - skip this file\n continue;\n }\n\n if (disableStatus.isExpired) {\n // Expired disable - report as violation with expired info\n violations.push({\n file,\n lines: lineCount,\n expiredDisable: true,\n expiredDate: disableStatus.date,\n });\n continue;\n }\n\n // Invalid disable (missing/bad date) - fall through to report as violation\n }\n\n violations.push({\n file,\n lines: lineCount,\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\n/**\n * Get today's date in yyyy/mm/dd format for error messages\n */\nfunction getTodayDateString(): string {\n const now = new Date();\n const year = now.getFullYear();\n const month = String(now.getMonth() + 1).padStart(2, '0');\n const day = String(now.getDate()).padStart(2, '0');\n return `${year}/${month}/${day}`;\n}\n\n/**\n * Report violations to console\n */\n// webpieces-disable max-lines-new-methods -- Error output formatting with multiple message sections\nfunction reportViolations(violations: FileViolation[], maxLines: number, forceLimit: boolean): void {\n console.error('');\n console.error('❌ YOU MUST FIX THIS AND NOT be more than ' + maxLines + ' lines of code per file');\n console.error(' as it slows down IDEs AND is VERY VERY EASY to refactor.');\n console.error('');\n console.error('📚 With stateless systems + dependency injection, refactor is trivial:');\n console.error(' Pick a method or a few and move to new class XXXXX, then inject XXXXX');\n console.error(' into all users of those methods via the constructor.');\n console.error(' Delete those methods from original class.');\n console.error(' 99% of files can be less than ' + maxLines + ' lines of code.');\n console.error('');\n console.error('⚠️ *** READ tmp/webpieces/webpieces.filesize.md for detailed guidance on how to fix this easily *** ⚠️');\n console.error('');\n\n for (const v of violations) {\n if (v.expiredDisable) {\n console.error(` ❌ ${v.file} (${v.lines} lines, max: ${maxLines})`);\n console.error(` ⏰ EXPIRED DISABLE: Your disable comment dated ${v.expiredDate} has expired (>1 month old).`);\n console.error(` You must either FIX the file or UPDATE the date to get another month.`);\n } else {\n console.error(` ❌ ${v.file} (${v.lines} lines, max: ${maxLines})`);\n }\n }\n console.error('');\n\n // Only show escape hatch instructions when forceLimit is not enabled\n if (!forceLimit) {\n console.error(' You can disable this error, but you will be forced to fix again in 1 month');\n console.error(' since 99% of files can be less than ' + maxLines + ' lines of code.');\n console.error('');\n console.error(' Use escape with DATE (expires in 1 month):');\n console.error(` // webpieces-disable max-lines-modified-files ${getTodayDateString()} -- [your reason]`);\n console.error('');\n } else {\n console.error(' ⚠️ forceModifiedFilesLimit is enabled - disable comments are NOT allowed.');\n console.error(' You MUST refactor to reduce file size.');\n console.error('');\n }\n}\n\nexport default async function runExecutor(\n options: ValidateModifiedFilesOptions,\n context: ExecutorContext\n): Promise<ExecutorResult> {\n const workspaceRoot = context.root;\n const maxLines = options.max ?? 900;\n const forceLimit = options.forceLimit ?? false;\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 files validation (could not detect base branch)');\n console.log(' To run explicitly: nx affected --target=validate-modified-files --base=origin/main');\n console.log('');\n return { success: true };\n }\n\n console.log('\\n📏 Validating Modified File Sizes (auto-detected base)\\n');\n } else {\n console.log('\\n📏 Validating Modified File 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 files: ${maxLines}`);\n if (forceLimit) {\n console.log(' Force limit: ENABLED (disable comments will be ignored)');\n }\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, maxLines, forceLimit);\n\n if (violations.length === 0) {\n console.log('✅ All modified files are under ' + maxLines + ' lines');\n return { success: true };\n }\n\n writeTmpInstructions(workspaceRoot);\n reportViolations(violations, maxLines, forceLimit);\n return { success: false };\n } catch (err: unknown) {\n const error = err instanceof Error ? err : new Error(String(err));\n console.error('❌ Modified files validation failed:', error.message);\n return { success: false };\n }\n}\n"]}
1
+ {"version":3,"file":"executor.js","sourceRoot":"","sources":["../../../../../../../packages/tooling/dev-config/architecture/executors/validate-modified-files/executor.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Validate Modified Files Executor\n *\n * Validates that modified files don't exceed a maximum line count (default 900).\n * This encourages keeping files small and focused - when you touch a file,\n * you must bring it under the limit.\n *\n * Usage:\n * nx affected --target=validate-modified-files --base=origin/main\n *\n * Escape hatch: Add webpieces-disable max-lines-modified-files comment with date and justification\n * Format: // webpieces-disable max-lines-modified-files 2025/01/15 -- [reason]\n * The disable expires after 1 month from the date specified.\n */\n\nimport type { ExecutorContext } from '@nx/devkit';\nimport { execSync } from 'child_process';\nimport * as fs from 'fs';\nimport * as path from 'path';\n\nexport type ValidationMode = 'STRICT' | 'NORMAL' | 'OFF';\n\nexport interface ValidateModifiedFilesOptions {\n max?: number;\n mode?: ValidationMode;\n}\n\nexport interface ExecutorResult {\n success: boolean;\n}\n\ninterface FileViolation {\n file: string;\n lines: number;\n expiredDisable?: boolean;\n expiredDate?: string;\n}\n\nconst TMP_DIR = 'tmp/webpieces';\nconst TMP_MD_FILE = 'webpieces.filesize.md';\n\nconst FILESIZE_DOC_CONTENT = `# AI Agent Instructions: File Too Long\n\n**READ THIS FILE to fix files that are too long**\n\n## Core Principle\n\nWith **stateless systems + dependency injection, refactor is trivial**.\nPick a method or a few and move to new class XXXXX, then inject XXXXX\ninto all users of those methods via the constructor.\nDelete those methods from original class.\n\n**99% of files can be less than the configured max lines of code.**\n\nFiles should contain a SINGLE COHESIVE UNIT.\n- One class per file (Java convention)\n- If class is too large, extract child responsibilities\n- Use dependency injection to compose functionality\n\n## Command: Reduce File Size\n\n### Step 1: Check for Multiple Classes\nIf the file contains multiple classes, **SEPARATE each class into its own file**.\n\n\\`\\`\\`typescript\n// BAD: UserController.ts (multiple classes)\nexport class UserController { /* ... */ }\nexport class UserValidator { /* ... */ }\nexport class UserNotifier { /* ... */ }\n\n// GOOD: Three separate files\n// UserController.ts\nexport class UserController { /* ... */ }\n\n// UserValidator.ts\nexport class UserValidator { /* ... */ }\n\n// UserNotifier.ts\nexport class UserNotifier { /* ... */ }\n\\`\\`\\`\n\n### Step 2: Extract Child Responsibilities (if single class is too large)\n\n#### Pattern: Create New Service Class with Dependency Injection\n\n\\`\\`\\`typescript\n// BAD: UserController.ts (800 lines, single class)\n@provideSingleton()\n@Controller()\nexport class UserController {\n // 200 lines: CRUD operations\n // 300 lines: validation logic\n // 200 lines: notification logic\n // 100 lines: analytics logic\n}\n\n// GOOD: Extract validation service\n// 1. Create UserValidationService.ts\n@provideSingleton()\nexport class UserValidationService {\n validateUserData(data: UserData): ValidationResult {\n // 300 lines of validation logic moved here\n }\n\n validateEmail(email: string): boolean { /* ... */ }\n validatePassword(password: string): boolean { /* ... */ }\n}\n\n// 2. Inject into UserController.ts\n@provideSingleton()\n@Controller()\nexport class UserController {\n constructor(\n @inject(TYPES.UserValidationService)\n private validator: UserValidationService\n ) {}\n\n async createUser(data: UserData): Promise<User> {\n const validation = this.validator.validateUserData(data);\n if (!validation.isValid) {\n throw new ValidationError(validation.errors);\n }\n // ... rest of logic\n }\n}\n\\`\\`\\`\n\n## AI Agent Action Steps\n\n1. **ANALYZE** the file structure:\n - Count classes (if >1, separate immediately)\n - Identify logical responsibilities within single class\n\n2. **IDENTIFY** \"child code\" to extract:\n - Validation logic -> ValidationService\n - Notification logic -> NotificationService\n - Data transformation -> TransformerService\n - External API calls -> ApiService\n - Business rules -> RulesEngine\n\n3. **CREATE** new service file(s):\n - Start with temporary name: \\`XXXX.ts\\` or \\`ChildService.ts\\`\n - Add \\`@provideSingleton()\\` decorator\n - Move child methods to new class\n\n4. **UPDATE** dependency injection:\n - Add to \\`TYPES\\` constants (if using symbol-based DI)\n - Inject new service into original class constructor\n - Replace direct method calls with \\`this.serviceName.method()\\`\n\n5. **RENAME** extracted file:\n - Read the extracted code to understand its purpose\n - Rename \\`XXXX.ts\\` to logical name (e.g., \\`UserValidationService.ts\\`)\n\n6. **VERIFY** file sizes:\n - Original file should now be under the limit\n - Each extracted file should be under the limit\n - If still too large, extract more services\n\n## Examples of Child Responsibilities to Extract\n\n| If File Contains | Extract To | Pattern |\n|-----------------|------------|---------|\n| Validation logic (200+ lines) | \\`XValidator.ts\\` or \\`XValidationService.ts\\` | Singleton service |\n| Notification logic (150+ lines) | \\`XNotifier.ts\\` or \\`XNotificationService.ts\\` | Singleton service |\n| Data transformation (200+ lines) | \\`XTransformer.ts\\` | Singleton service |\n| External API calls (200+ lines) | \\`XApiClient.ts\\` | Singleton service |\n| Complex business rules (300+ lines) | \\`XRulesEngine.ts\\` | Singleton service |\n| Database queries (200+ lines) | \\`XRepository.ts\\` | Singleton service |\n\n## WebPieces Dependency Injection Pattern\n\n\\`\\`\\`typescript\n// 1. Define service with @provideSingleton\nimport { provideSingleton } from '@webpieces/http-routing';\n\n@provideSingleton()\nexport class MyService {\n doSomething(): void { /* ... */ }\n}\n\n// 2. Inject into consumer\nimport { inject } from 'inversify';\nimport { TYPES } from './types';\n\n@provideSingleton()\n@Controller()\nexport class MyController {\n constructor(\n @inject(TYPES.MyService) private service: MyService\n ) {}\n}\n\\`\\`\\`\n\n## Escape Hatch\n\nIf refactoring is genuinely not feasible (generated files, complex algorithms, etc.),\nadd a disable comment at the TOP of the file (within first 5 lines) with a DATE:\n\n\\`\\`\\`typescript\n// webpieces-disable max-lines-modified-files 2025/01/15 -- Complex generated file, refactoring would break generation\n\\`\\`\\`\n\n**IMPORTANT**: The date format is yyyy/mm/dd. The disable will EXPIRE after 1 month from this date.\nAfter expiration, you must either fix the file or update the date to get another month.\nThis ensures that disable comments are reviewed periodically.\n\nRemember: Find the \"child code\" and pull it down into a new class. Once moved, the code's purpose becomes clear, making it easy to rename to a logical name.\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, FILESIZE_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 * Parse a date string in yyyy/mm/dd format and return a Date object.\n * Returns null if the format is invalid.\n */\nfunction parseDisableDate(dateStr: string): Date | null {\n // Match yyyy/mm/dd format\n const match = dateStr.match(/^(\\d{4})\\/(\\d{2})\\/(\\d{2})$/);\n if (!match) return null;\n\n const year = parseInt(match[1], 10);\n const month = parseInt(match[2], 10) - 1; // JS months are 0-indexed\n const day = parseInt(match[3], 10);\n\n const date = new Date(year, month, day);\n\n // Validate the date is valid (e.g., not Feb 30)\n if (date.getFullYear() !== year || date.getMonth() !== month || date.getDate() !== day) {\n return null;\n }\n\n return date;\n}\n\n/**\n * Check if a date is within the last month (not expired).\n */\nfunction isDateWithinMonth(date: Date): boolean {\n const now = new Date();\n const oneMonthAgo = new Date(now.getFullYear(), now.getMonth() - 1, now.getDate());\n return date >= oneMonthAgo;\n}\n\ninterface DisableStatus {\n hasDisable: boolean;\n isValid: boolean;\n isExpired: boolean;\n date?: string;\n}\n\n/**\n * Check if a file has a valid, non-expired disable comment at the top (within first 5 lines).\n * Returns status object with details about the disable comment.\n */\n// webpieces-disable max-lines-new-methods -- Date validation logic requires checking multiple conditions\nfunction checkDisableComment(content: string): DisableStatus {\n const lines = content.split('\\n').slice(0, 5);\n\n for (const line of lines) {\n if (line.includes('webpieces-disable') && line.includes('max-lines-modified-files')) {\n // Found disable comment, now check for date\n // Format: // webpieces-disable max-lines-modified-files yyyy/mm/dd -- reason\n const dateMatch = line.match(/max-lines-modified-files\\s+(\\d{4}\\/\\d{2}\\/\\d{2}|XXXX\\/XX\\/XX)/);\n\n if (!dateMatch) {\n // No date found - invalid disable comment\n return { hasDisable: true, isValid: false, isExpired: false };\n }\n\n const dateStr = dateMatch[1];\n\n // Secret permanent disable\n if (dateStr === 'XXXX/XX/XX') {\n return { hasDisable: true, isValid: true, isExpired: false, date: dateStr };\n }\n\n const date = parseDisableDate(dateStr);\n if (!date) {\n // Invalid date format\n return { hasDisable: true, isValid: false, isExpired: false, date: dateStr };\n }\n\n if (!isDateWithinMonth(date)) {\n // Date is expired (older than 1 month)\n return { hasDisable: true, isValid: true, isExpired: true, date: dateStr };\n }\n\n // Valid and not expired\n return { hasDisable: true, isValid: true, isExpired: false, date: dateStr };\n }\n }\n\n return { hasDisable: false, isValid: false, isExpired: false };\n}\n\n/**\n * Count lines in a file and check for violations\n */\n// webpieces-disable max-lines-new-methods -- File iteration with disable checking logic\nfunction findViolations(workspaceRoot: string, changedFiles: string[], maxLines: number, mode: ValidationMode): FileViolation[] {\n const violations: FileViolation[] = [];\n\n for (const file of changedFiles) {\n const fullPath = path.join(workspaceRoot, file);\n\n if (!fs.existsSync(fullPath)) continue;\n\n const content = fs.readFileSync(fullPath, 'utf-8');\n const lineCount = content.split('\\n').length;\n\n // Skip files under the limit\n if (lineCount <= maxLines) continue;\n\n // When mode is STRICT, ignore all disable comments\n if (mode === 'STRICT') {\n violations.push({ file, lines: lineCount });\n continue;\n }\n\n // Check for disable comment\n const disableStatus = checkDisableComment(content);\n\n if (disableStatus.hasDisable) {\n if (disableStatus.isValid && !disableStatus.isExpired) {\n // Valid, non-expired disable - skip this file\n continue;\n }\n\n if (disableStatus.isExpired) {\n // Expired disable - report as violation with expired info\n violations.push({\n file,\n lines: lineCount,\n expiredDisable: true,\n expiredDate: disableStatus.date,\n });\n continue;\n }\n\n // Invalid disable (missing/bad date) - fall through to report as violation\n }\n\n violations.push({\n file,\n lines: lineCount,\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\n/**\n * Get today's date in yyyy/mm/dd format for error messages\n */\nfunction getTodayDateString(): string {\n const now = new Date();\n const year = now.getFullYear();\n const month = String(now.getMonth() + 1).padStart(2, '0');\n const day = String(now.getDate()).padStart(2, '0');\n return `${year}/${month}/${day}`;\n}\n\n/**\n * Report violations to console\n */\n// webpieces-disable max-lines-new-methods -- Error output formatting with multiple message sections\nfunction reportViolations(violations: FileViolation[], maxLines: number, mode: ValidationMode): void {\n console.error('');\n console.error('❌ YOU MUST FIX THIS AND NOT be more than ' + maxLines + ' lines of code per file');\n console.error(' as it slows down IDEs AND is VERY VERY EASY to refactor.');\n console.error('');\n console.error('📚 With stateless systems + dependency injection, refactor is trivial:');\n console.error(' Pick a method or a few and move to new class XXXXX, then inject XXXXX');\n console.error(' into all users of those methods via the constructor.');\n console.error(' Delete those methods from original class.');\n console.error(' 99% of files can be less than ' + maxLines + ' lines of code.');\n console.error('');\n console.error('⚠️ *** READ tmp/webpieces/webpieces.filesize.md for detailed guidance on how to fix this easily *** ⚠️');\n console.error('');\n\n for (const v of violations) {\n if (v.expiredDisable) {\n console.error(` ❌ ${v.file} (${v.lines} lines, max: ${maxLines})`);\n console.error(` ⏰ EXPIRED DISABLE: Your disable comment dated ${v.expiredDate} has expired (>1 month old).`);\n console.error(` You must either FIX the file or UPDATE the date to get another month.`);\n } else {\n console.error(` ❌ ${v.file} (${v.lines} lines, max: ${maxLines})`);\n }\n }\n console.error('');\n\n // Only show escape hatch instructions when mode is not STRICT\n if (mode !== 'STRICT') {\n console.error(' You can disable this error, but you will be forced to fix again in 1 month');\n console.error(' since 99% of files can be less than ' + maxLines + ' lines of code.');\n console.error('');\n console.error(' Use escape with DATE (expires in 1 month):');\n console.error(` // webpieces-disable max-lines-modified-files ${getTodayDateString()} -- [your reason]`);\n console.error('');\n } else {\n console.error(' ⚠️ validationMode is STRICT - disable comments are NOT allowed.');\n console.error(' You MUST refactor to reduce file size.');\n console.error('');\n }\n}\n\nexport default async function runExecutor(\n options: ValidateModifiedFilesOptions,\n context: ExecutorContext\n): Promise<ExecutorResult> {\n const workspaceRoot = context.root;\n const maxLines = options.max ?? 900;\n const mode: ValidationMode = options.mode ?? 'NORMAL';\n\n // Skip validation entirely if mode is OFF\n if (mode === 'OFF') {\n console.log('\\n⏭️ Skipping modified files validation (validationMode: OFF)');\n console.log('');\n return { success: true };\n }\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 files validation (could not detect base branch)');\n console.log(' To run explicitly: nx affected --target=validate-modified-files --base=origin/main');\n console.log('');\n return { success: true };\n }\n\n console.log('\\n📏 Validating Modified File Sizes (auto-detected base)\\n');\n } else {\n console.log('\\n📏 Validating Modified File 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 files: ${maxLines}`);\n console.log(` Validation mode: ${mode}${mode === 'STRICT' ? ' (disable comments ignored)' : ''}`);\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, maxLines, mode);\n\n if (violations.length === 0) {\n console.log('✅ All modified files are under ' + maxLines + ' lines');\n return { success: true };\n }\n\n writeTmpInstructions(workspaceRoot);\n reportViolations(violations, maxLines, mode);\n return { success: false };\n } catch (err: unknown) {\n const error = err instanceof Error ? err : new Error(String(err));\n console.error('❌ Modified files validation failed:', error.message);\n return { success: false };\n }\n}\n"]}
@@ -18,9 +18,11 @@ import { execSync } from 'child_process';
18
18
  import * as fs from 'fs';
19
19
  import * as path from 'path';
20
20
 
21
+ export type ValidationMode = 'STRICT' | 'NORMAL' | 'OFF';
22
+
21
23
  export interface ValidateModifiedFilesOptions {
22
24
  max?: number;
23
- forceLimit?: boolean;
25
+ mode?: ValidationMode;
24
26
  }
25
27
 
26
28
  export interface ExecutorResult {
@@ -328,7 +330,7 @@ function checkDisableComment(content: string): DisableStatus {
328
330
  * Count lines in a file and check for violations
329
331
  */
330
332
  // webpieces-disable max-lines-new-methods -- File iteration with disable checking logic
331
- function findViolations(workspaceRoot: string, changedFiles: string[], maxLines: number, forceLimit: boolean): FileViolation[] {
333
+ function findViolations(workspaceRoot: string, changedFiles: string[], maxLines: number, mode: ValidationMode): FileViolation[] {
332
334
  const violations: FileViolation[] = [];
333
335
 
334
336
  for (const file of changedFiles) {
@@ -342,8 +344,8 @@ function findViolations(workspaceRoot: string, changedFiles: string[], maxLines:
342
344
  // Skip files under the limit
343
345
  if (lineCount <= maxLines) continue;
344
346
 
345
- // When forceLimit is true, ignore all disable comments
346
- if (forceLimit) {
347
+ // When mode is STRICT, ignore all disable comments
348
+ if (mode === 'STRICT') {
347
349
  violations.push({ file, lines: lineCount });
348
350
  continue;
349
351
  }
@@ -427,7 +429,7 @@ function getTodayDateString(): string {
427
429
  * Report violations to console
428
430
  */
429
431
  // webpieces-disable max-lines-new-methods -- Error output formatting with multiple message sections
430
- function reportViolations(violations: FileViolation[], maxLines: number, forceLimit: boolean): void {
432
+ function reportViolations(violations: FileViolation[], maxLines: number, mode: ValidationMode): void {
431
433
  console.error('');
432
434
  console.error('❌ YOU MUST FIX THIS AND NOT be more than ' + maxLines + ' lines of code per file');
433
435
  console.error(' as it slows down IDEs AND is VERY VERY EASY to refactor.');
@@ -452,8 +454,8 @@ function reportViolations(violations: FileViolation[], maxLines: number, forceLi
452
454
  }
453
455
  console.error('');
454
456
 
455
- // Only show escape hatch instructions when forceLimit is not enabled
456
- if (!forceLimit) {
457
+ // Only show escape hatch instructions when mode is not STRICT
458
+ if (mode !== 'STRICT') {
457
459
  console.error(' You can disable this error, but you will be forced to fix again in 1 month');
458
460
  console.error(' since 99% of files can be less than ' + maxLines + ' lines of code.');
459
461
  console.error('');
@@ -461,7 +463,7 @@ function reportViolations(violations: FileViolation[], maxLines: number, forceLi
461
463
  console.error(` // webpieces-disable max-lines-modified-files ${getTodayDateString()} -- [your reason]`);
462
464
  console.error('');
463
465
  } else {
464
- console.error(' ⚠️ forceModifiedFilesLimit is enabled - disable comments are NOT allowed.');
466
+ console.error(' ⚠️ validationMode is STRICT - disable comments are NOT allowed.');
465
467
  console.error(' You MUST refactor to reduce file size.');
466
468
  console.error('');
467
469
  }
@@ -473,7 +475,14 @@ export default async function runExecutor(
473
475
  ): Promise<ExecutorResult> {
474
476
  const workspaceRoot = context.root;
475
477
  const maxLines = options.max ?? 900;
476
- const forceLimit = options.forceLimit ?? false;
478
+ const mode: ValidationMode = options.mode ?? 'NORMAL';
479
+
480
+ // Skip validation entirely if mode is OFF
481
+ if (mode === 'OFF') {
482
+ console.log('\n⏭️ Skipping modified files validation (validationMode: OFF)');
483
+ console.log('');
484
+ return { success: true };
485
+ }
477
486
 
478
487
  let base = process.env['NX_BASE'];
479
488
 
@@ -495,9 +504,7 @@ export default async function runExecutor(
495
504
  console.log(` Base: ${base}`);
496
505
  console.log(' Comparing to: working tree (includes uncommitted changes)');
497
506
  console.log(` Max lines for modified files: ${maxLines}`);
498
- if (forceLimit) {
499
- console.log(' Force limit: ENABLED (disable comments will be ignored)');
500
- }
507
+ console.log(` Validation mode: ${mode}${mode === 'STRICT' ? ' (disable comments ignored)' : ''}`);
501
508
  console.log('');
502
509
 
503
510
  try {
@@ -510,7 +517,7 @@ export default async function runExecutor(
510
517
 
511
518
  console.log(`📂 Checking ${changedFiles.length} changed file(s)...`);
512
519
 
513
- const violations = findViolations(workspaceRoot, changedFiles, maxLines, forceLimit);
520
+ const violations = findViolations(workspaceRoot, changedFiles, maxLines, mode);
514
521
 
515
522
  if (violations.length === 0) {
516
523
  console.log('✅ All modified files are under ' + maxLines + ' lines');
@@ -518,7 +525,7 @@ export default async function runExecutor(
518
525
  }
519
526
 
520
527
  writeTmpInstructions(workspaceRoot);
521
- reportViolations(violations, maxLines, forceLimit);
528
+ reportViolations(violations, maxLines, mode);
522
529
  return { success: false };
523
530
  } catch (err: unknown) {
524
531
  const error = err instanceof Error ? err : new Error(String(err));
@@ -9,10 +9,11 @@
9
9
  "description": "Maximum number of lines allowed for modified files",
10
10
  "default": 900
11
11
  },
12
- "forceLimit": {
13
- "type": "boolean",
14
- "description": "When true, disable comments are ignored and the limit is strictly enforced",
15
- "default": false
12
+ "mode": {
13
+ "type": "string",
14
+ "enum": ["STRICT", "NORMAL", "OFF"],
15
+ "description": "STRICT: limits enforced, no disable comments. NORMAL: disable comments work. OFF: skip validation.",
16
+ "default": "NORMAL"
16
17
  }
17
18
  },
18
19
  "required": []
@@ -19,8 +19,10 @@
19
19
  * The disable expires after 1 month from the date specified.
20
20
  */
21
21
  import type { ExecutorContext } from '@nx/devkit';
22
+ export type ValidationMode = 'STRICT' | 'NORMAL' | 'OFF';
22
23
  export interface ValidateModifiedMethodsOptions {
23
24
  max?: number;
25
+ mode?: ValidationMode;
24
26
  }
25
27
  export interface ExecutorResult {
26
28
  success: boolean;
@@ -428,111 +428,80 @@ function findMethodsInFile(filePath, workspaceRoot) {
428
428
  visit(sourceFile);
429
429
  return methods;
430
430
  }
431
+ /**
432
+ * Check if a method has any changes within its line range
433
+ */
434
+ function methodHasChanges(method, changedLineNumbers) {
435
+ for (let line = method.line; line <= method.endLine; line++) {
436
+ if (changedLineNumbers.has(line))
437
+ return true;
438
+ }
439
+ return false;
440
+ }
441
+ /**
442
+ * Check a NEW method and return violation if applicable
443
+ */
444
+ function checkNewMethodViolation(file, method, mode) {
445
+ const { type: disableType, isExpired, date: disableDate } = method.disableInfo;
446
+ if (mode === 'STRICT') {
447
+ // When mode is STRICT, skip NEW methods without escape (let validate-new-methods handle)
448
+ if (disableType === 'none')
449
+ return null;
450
+ return { file, methodName: method.name, line: method.line, lines: method.lines };
451
+ }
452
+ if (disableType === 'full' && isExpired) {
453
+ return { file, methodName: method.name, line: method.line, lines: method.lines, expiredDisable: true, expiredDate: disableDate };
454
+ }
455
+ if (disableType !== 'new-only')
456
+ return null;
457
+ if (isExpired) {
458
+ return { file, methodName: method.name, line: method.line, lines: method.lines, expiredDisable: true, expiredDate: disableDate };
459
+ }
460
+ return { file, methodName: method.name, line: method.line, lines: method.lines };
461
+ }
462
+ /**
463
+ * Check a MODIFIED method and return violation if applicable
464
+ */
465
+ function checkModifiedMethodViolation(file, method, mode) {
466
+ const { type: disableType, isExpired, date: disableDate } = method.disableInfo;
467
+ if (mode === 'STRICT') {
468
+ return { file, methodName: method.name, line: method.line, lines: method.lines };
469
+ }
470
+ if (disableType === 'full' && isExpired) {
471
+ return { file, methodName: method.name, line: method.line, lines: method.lines, expiredDisable: true, expiredDate: disableDate };
472
+ }
473
+ return { file, methodName: method.name, line: method.line, lines: method.lines };
474
+ }
431
475
  /**
432
476
  * Find methods that exceed the 80-line limit.
433
- *
434
- * This validator checks:
435
- * 1. NEW methods that have `max-lines-new-methods` escape (they passed 30-line check, now need 80-line check)
436
- * 2. MODIFIED methods (existing methods with changes)
437
- *
438
- * Skips:
439
- * - NEW methods without any escape (let validate-new-methods handle them first)
440
- * - Methods with valid, non-expired `max-lines-modified` escape (ultimate escape hatch)
477
+ * Checks NEW methods with escape hatches and MODIFIED methods.
441
478
  */
442
- // webpieces-disable max-lines-modified 2025/12/20 -- Core validation logic with multiple file operations
443
- function findViolations(workspaceRoot, changedFiles, base, maxLines) {
479
+ function findViolations(workspaceRoot, changedFiles, base, maxLines, mode) {
444
480
  const violations = [];
445
481
  for (const file of changedFiles) {
446
482
  const diff = getFileDiff(workspaceRoot, file, base);
447
483
  if (!diff)
448
484
  continue;
449
- // Find NEW methods from the diff
450
485
  const newMethodNames = findNewMethodSignaturesInDiff(diff);
451
- // Find which lines have changes
452
486
  const changedLineNumbers = getChangedLineNumbers(diff);
453
487
  if (changedLineNumbers.size === 0)
454
488
  continue;
455
- // Parse the current file to get all methods
456
489
  const methods = findMethodsInFile(file, workspaceRoot);
457
490
  for (const method of methods) {
458
- const isNewMethod = newMethodNames.has(method.name);
459
- const { type: disableType, isExpired, date: disableDate } = method.disableInfo;
460
- // Skip methods with valid, non-expired full escape (max-lines-modified)
461
- if (disableType === 'full' && !isExpired)
491
+ const { type: disableType, isExpired } = method.disableInfo;
492
+ // Skip methods with valid, non-expired full escape - unless mode is STRICT
493
+ if (mode !== 'STRICT' && disableType === 'full' && !isExpired)
462
494
  continue;
463
- // Skip methods under the limit
464
495
  if (method.lines <= maxLines)
465
496
  continue;
497
+ const isNewMethod = newMethodNames.has(method.name);
466
498
  if (isNewMethod) {
467
- // For NEW methods:
468
- // - If has 'new-only' escape (non-expired) → check (they escaped 30-line, now need 80-line check)
469
- // - If has 'none' → skip (let validate-new-methods handle first)
470
- // - If has expired disable → report as violation
471
- if (disableType === 'full' && isExpired) {
472
- // Expired full disable - report with expired info
473
- violations.push({
474
- file,
475
- methodName: method.name,
476
- line: method.line,
477
- lines: method.lines,
478
- expiredDisable: true,
479
- expiredDate: disableDate,
480
- });
481
- continue;
482
- }
483
- if (disableType !== 'new-only')
484
- continue;
485
- if (isExpired) {
486
- // Expired new-only disable - report with expired info
487
- violations.push({
488
- file,
489
- methodName: method.name,
490
- line: method.line,
491
- lines: method.lines,
492
- expiredDisable: true,
493
- expiredDate: disableDate,
494
- });
495
- }
496
- else {
497
- // New method with valid max-lines-new-methods escape - check against 80-line limit
498
- violations.push({
499
- file,
500
- methodName: method.name,
501
- line: method.line,
502
- lines: method.lines,
503
- });
504
- }
499
+ const violation = checkNewMethodViolation(file, method, mode);
500
+ if (violation)
501
+ violations.push(violation);
505
502
  }
506
- else {
507
- // For MODIFIED methods: check if any changed line falls within method's range
508
- let hasChanges = false;
509
- for (let line = method.line; line <= method.endLine; line++) {
510
- if (changedLineNumbers.has(line)) {
511
- hasChanges = true;
512
- break;
513
- }
514
- }
515
- if (hasChanges) {
516
- if (disableType === 'full' && isExpired) {
517
- // Expired full disable - report with expired info
518
- violations.push({
519
- file,
520
- methodName: method.name,
521
- line: method.line,
522
- lines: method.lines,
523
- expiredDisable: true,
524
- expiredDate: disableDate,
525
- });
526
- }
527
- else {
528
- violations.push({
529
- file,
530
- methodName: method.name,
531
- line: method.line,
532
- lines: method.lines,
533
- });
534
- }
535
- }
503
+ else if (methodHasChanges(method, changedLineNumbers)) {
504
+ violations.push(checkModifiedMethodViolation(file, method, mode));
536
505
  }
537
506
  }
538
507
  }
@@ -573,7 +542,7 @@ function detectBase(workspaceRoot) {
573
542
  * Report violations to console
574
543
  */
575
544
  // webpieces-disable max-lines-new-methods -- Error output formatting with multiple message sections
576
- function reportViolations(violations, maxLines) {
545
+ function reportViolations(violations, maxLines, mode) {
577
546
  console.error('');
578
547
  console.error('❌ Modified methods exceed ' + maxLines + ' lines!');
579
548
  console.error('');
@@ -598,16 +567,31 @@ function reportViolations(violations, maxLines) {
598
567
  }
599
568
  }
600
569
  console.error('');
601
- console.error(' You can disable this error, but you will be forced to fix again in 1 month');
602
- console.error(' since 99% of methods can be less than ' + maxLines + ' lines of code.');
603
- console.error('');
604
- console.error(' Use escape with DATE (expires in 1 month):');
605
- console.error(` // webpieces-disable max-lines-modified ${getTodayDateString()} -- [your reason]`);
606
- console.error('');
570
+ // Only show escape hatch instructions when mode is not STRICT
571
+ if (mode !== 'STRICT') {
572
+ console.error(' You can disable this error, but you will be forced to fix again in 1 month');
573
+ console.error(' since 99% of methods can be less than ' + maxLines + ' lines of code.');
574
+ console.error('');
575
+ console.error(' Use escape with DATE (expires in 1 month):');
576
+ console.error(` // webpieces-disable max-lines-modified ${getTodayDateString()} -- [your reason]`);
577
+ console.error('');
578
+ }
579
+ else {
580
+ console.error(' ⚠️ validationMode is STRICT - disable comments are NOT allowed.');
581
+ console.error(' You MUST refactor to reduce method size.');
582
+ console.error('');
583
+ }
607
584
  }
608
585
  async function runExecutor(options, context) {
609
586
  const workspaceRoot = context.root;
610
587
  const maxLines = options.max ?? 80;
588
+ const mode = options.mode ?? 'NORMAL';
589
+ // Skip validation entirely if mode is OFF
590
+ if (mode === 'OFF') {
591
+ console.log('\n⏭️ Skipping modified method validation (validationMode: OFF)');
592
+ console.log('');
593
+ return { success: true };
594
+ }
611
595
  let base = process.env['NX_BASE'];
612
596
  if (!base) {
613
597
  base = detectBase(workspaceRoot) ?? undefined;
@@ -625,6 +609,7 @@ async function runExecutor(options, context) {
625
609
  console.log(` Base: ${base}`);
626
610
  console.log(' Comparing to: working tree (includes uncommitted changes)');
627
611
  console.log(` Max lines for modified methods: ${maxLines}`);
612
+ console.log(` Validation mode: ${mode}${mode === 'STRICT' ? ' (disable comments ignored)' : ''}`);
628
613
  console.log('');
629
614
  try {
630
615
  const changedFiles = getChangedTypeScriptFiles(workspaceRoot, base);
@@ -633,13 +618,13 @@ async function runExecutor(options, context) {
633
618
  return { success: true };
634
619
  }
635
620
  console.log(`📂 Checking ${changedFiles.length} changed file(s)...`);
636
- const violations = findViolations(workspaceRoot, changedFiles, base, maxLines);
621
+ const violations = findViolations(workspaceRoot, changedFiles, base, maxLines, mode);
637
622
  if (violations.length === 0) {
638
623
  console.log('✅ All modified methods are under ' + maxLines + ' lines');
639
624
  return { success: true };
640
625
  }
641
626
  writeTmpInstructions(workspaceRoot);
642
- reportViolations(violations, maxLines);
627
+ reportViolations(violations, maxLines, mode);
643
628
  return { success: false };
644
629
  }
645
630
  catch (err) {
@@ -1 +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-modified comment with date and justification\n * Format: // webpieces-disable max-lines-modified 2025/01/15 -- [reason]\n * The disable expires after 1 month from the date specified.\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 expiredDisable?: boolean;\n expiredDate?: string;\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 DATE and justification:\n\n\\`\\`\\`typescript\n// webpieces-disable max-lines-modified 2025/01/15 -- Complex state machine, splitting reduces clarity\nasync complexStateMachine(): Promise<void> {\n // ... longer method with justification\n}\n\\`\\`\\`\n\n**IMPORTANT**: The date format is yyyy/mm/dd. The disable will EXPIRE after 1 month from this date.\nAfter expiration, you must either fix the method or update the date to get another month.\nThis ensures that disable comments are reviewed periodically.\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-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 * Parse a date string in yyyy/mm/dd format and return a Date object.\n * Returns null if the format is invalid.\n */\nfunction parseDisableDate(dateStr: string): Date | null {\n // Match yyyy/mm/dd format\n const match = dateStr.match(/^(\\d{4})\\/(\\d{2})\\/(\\d{2})$/);\n if (!match) return null;\n\n const year = parseInt(match[1], 10);\n const month = parseInt(match[2], 10) - 1; // JS months are 0-indexed\n const day = parseInt(match[3], 10);\n\n const date = new Date(year, month, day);\n\n // Validate the date is valid (e.g., not Feb 30)\n if (date.getFullYear() !== year || date.getMonth() !== month || date.getDate() !== day) {\n return null;\n }\n\n return date;\n}\n\n/**\n * Check if a date is within the last month (not expired).\n */\nfunction isDateWithinMonth(date: Date): boolean {\n const now = new Date();\n const oneMonthAgo = new Date(now.getFullYear(), now.getMonth() - 1, now.getDate());\n return date >= oneMonthAgo;\n}\n\n/**\n * Get today's date in yyyy/mm/dd format for error messages\n */\nfunction getTodayDateString(): string {\n const now = new Date();\n const year = now.getFullYear();\n const month = String(now.getMonth() + 1).padStart(2, '0');\n const day = String(now.getDate()).padStart(2, '0');\n return `${year}/${month}/${day}`;\n}\n\ninterface DisableInfo {\n type: 'full' | 'new-only' | 'none';\n isExpired: boolean;\n date?: string;\n}\n\n/**\n * Check what kind of webpieces-disable comment is present for a method.\n * Returns: DisableInfo with type, expiration status, and date\n * - 'full': max-lines-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 */\n// webpieces-disable max-lines-new-methods -- Complex validation logic with multiple escape hatch types\nfunction getDisableInfo(lines: string[], lineNumber: number): DisableInfo {\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-modified')) {\n // Check for date in format: max-lines-modified yyyy/mm/dd\n const dateMatch = line.match(/max-lines-modified\\s+(\\d{4}\\/\\d{2}\\/\\d{2}|XXXX\\/XX\\/XX)/);\n\n if (!dateMatch) {\n // No date found - treat as expired (invalid)\n return { type: 'full', isExpired: true, date: undefined };\n }\n\n const dateStr = dateMatch[1];\n\n // Secret permanent disable\n if (dateStr === 'XXXX/XX/XX') {\n return { type: 'full', isExpired: false, date: dateStr };\n }\n\n const date = parseDisableDate(dateStr);\n if (!date) {\n // Invalid date format - treat as expired\n return { type: 'full', isExpired: true, date: dateStr };\n }\n\n if (!isDateWithinMonth(date)) {\n // Date is expired (older than 1 month)\n return { type: 'full', isExpired: true, date: dateStr };\n }\n\n // Valid and not expired\n return { type: 'full', isExpired: false, date: dateStr };\n }\n if (line.includes('max-lines-new-methods')) {\n // Check for date in format: max-lines-new-methods yyyy/mm/dd\n const dateMatch = line.match(/max-lines-new-methods\\s+(\\d{4}\\/\\d{2}\\/\\d{2}|XXXX\\/XX\\/XX)/);\n\n if (!dateMatch) {\n // No date found - treat as expired (invalid)\n return { type: 'new-only', isExpired: true, date: undefined };\n }\n\n const dateStr = dateMatch[1];\n\n // Secret permanent disable\n if (dateStr === 'XXXX/XX/XX') {\n return { type: 'new-only', isExpired: false, date: dateStr };\n }\n\n const date = parseDisableDate(dateStr);\n if (!date) {\n // Invalid date format - treat as expired\n return { type: 'new-only', isExpired: true, date: dateStr };\n }\n\n if (!isDateWithinMonth(date)) {\n // Date is expired (older than 1 month)\n return { type: 'new-only', isExpired: true, date: dateStr };\n }\n\n // Valid and not expired\n return { type: 'new-only', isExpired: false, date: dateStr };\n }\n }\n }\n return { type: 'none', isExpired: false };\n}\n\ninterface MethodInfo {\n name: string;\n line: number;\n endLine: number;\n lines: number;\n disableInfo: DisableInfo;\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(filePath: string, workspaceRoot: string): MethodInfo[] {\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: MethodInfo[] = [];\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 disableInfo: getDisableInfo(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 valid, non-expired `max-lines-modified` escape (ultimate escape hatch)\n */\n// webpieces-disable max-lines-modified 2025/12/20 -- 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 const { type: disableType, isExpired, date: disableDate } = method.disableInfo;\n\n // Skip methods with valid, non-expired full escape (max-lines-modified)\n if (disableType === 'full' && !isExpired) 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 (non-expired) → check (they escaped 30-line, now need 80-line check)\n // - If has 'none' → skip (let validate-new-methods handle first)\n // - If has expired disable → report as violation\n if (disableType === 'full' && isExpired) {\n // Expired full disable - report with expired info\n violations.push({\n file,\n methodName: method.name,\n line: method.line,\n lines: method.lines,\n expiredDisable: true,\n expiredDate: disableDate,\n });\n continue;\n }\n if (disableType !== 'new-only') continue;\n\n if (isExpired) {\n // Expired new-only disable - report with expired info\n violations.push({\n file,\n methodName: method.name,\n line: method.line,\n lines: method.lines,\n expiredDisable: true,\n expiredDate: disableDate,\n });\n } else {\n // New method with valid 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 }\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 if (disableType === 'full' && isExpired) {\n // Expired full disable - report with expired info\n violations.push({\n file,\n methodName: method.name,\n line: method.line,\n lines: method.lines,\n expiredDisable: true,\n expiredDate: disableDate,\n });\n } else {\n violations.push({\n file,\n methodName: method.name,\n line: method.line,\n lines: method.lines,\n });\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\n/**\n * Report violations to console\n */\n// webpieces-disable max-lines-new-methods -- Error output formatting with multiple message sections\nfunction reportViolations(violations: MethodViolation[], maxLines: number): void {\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 rule encourages GRADUAL cleanup so even though you did not cause it,');\n console.error(' you touched it, so you should fix now as part of your PR');\n console.error(' (this is for vibe coding and AI to fix as it touches things).');\n console.error(' You can refactor to stay under the limit 50% of the time. 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 if (v.expiredDisable) {\n console.error(` ❌ ${v.file}:${v.line}`);\n console.error(` Method: ${v.methodName} (${v.lines} lines, max: ${maxLines})`);\n console.error(` ⏰ EXPIRED DISABLE: Your disable comment dated ${v.expiredDate ?? 'unknown'} has expired (>1 month old).`);\n console.error(` You must either FIX the method or UPDATE the date to get another month.`);\n } else {\n console.error(` ❌ ${v.file}:${v.line}`);\n console.error(` Method: ${v.methodName} (${v.lines} lines, max: ${maxLines})`);\n }\n }\n console.error('');\n\n console.error(' You can disable this error, but you will be forced to fix again in 1 month');\n console.error(' since 99% of methods can be less than ' + maxLines + ' lines of code.');\n console.error('');\n console.error(' Use escape with DATE (expires in 1 month):');\n console.error(` // webpieces-disable max-lines-modified ${getTodayDateString()} -- [your reason]`);\n console.error('');\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 writeTmpInstructions(workspaceRoot);\n reportViolations(violations, maxLines);\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"]}
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-modified comment with date and justification\n * Format: // webpieces-disable max-lines-modified 2025/01/15 -- [reason]\n * The disable expires after 1 month from the date specified.\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 type ValidationMode = 'STRICT' | 'NORMAL' | 'OFF';\n\nexport interface ValidateModifiedMethodsOptions {\n max?: number;\n mode?: ValidationMode;\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 expiredDisable?: boolean;\n expiredDate?: string;\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 DATE and justification:\n\n\\`\\`\\`typescript\n// webpieces-disable max-lines-modified 2025/01/15 -- Complex state machine, splitting reduces clarity\nasync complexStateMachine(): Promise<void> {\n // ... longer method with justification\n}\n\\`\\`\\`\n\n**IMPORTANT**: The date format is yyyy/mm/dd. The disable will EXPIRE after 1 month from this date.\nAfter expiration, you must either fix the method or update the date to get another month.\nThis ensures that disable comments are reviewed periodically.\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-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 * Parse a date string in yyyy/mm/dd format and return a Date object.\n * Returns null if the format is invalid.\n */\nfunction parseDisableDate(dateStr: string): Date | null {\n // Match yyyy/mm/dd format\n const match = dateStr.match(/^(\\d{4})\\/(\\d{2})\\/(\\d{2})$/);\n if (!match) return null;\n\n const year = parseInt(match[1], 10);\n const month = parseInt(match[2], 10) - 1; // JS months are 0-indexed\n const day = parseInt(match[3], 10);\n\n const date = new Date(year, month, day);\n\n // Validate the date is valid (e.g., not Feb 30)\n if (date.getFullYear() !== year || date.getMonth() !== month || date.getDate() !== day) {\n return null;\n }\n\n return date;\n}\n\n/**\n * Check if a date is within the last month (not expired).\n */\nfunction isDateWithinMonth(date: Date): boolean {\n const now = new Date();\n const oneMonthAgo = new Date(now.getFullYear(), now.getMonth() - 1, now.getDate());\n return date >= oneMonthAgo;\n}\n\n/**\n * Get today's date in yyyy/mm/dd format for error messages\n */\nfunction getTodayDateString(): string {\n const now = new Date();\n const year = now.getFullYear();\n const month = String(now.getMonth() + 1).padStart(2, '0');\n const day = String(now.getDate()).padStart(2, '0');\n return `${year}/${month}/${day}`;\n}\n\ninterface DisableInfo {\n type: 'full' | 'new-only' | 'none';\n isExpired: boolean;\n date?: string;\n}\n\n/**\n * Check what kind of webpieces-disable comment is present for a method.\n * Returns: DisableInfo with type, expiration status, and date\n * - 'full': max-lines-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 */\n// webpieces-disable max-lines-new-methods -- Complex validation logic with multiple escape hatch types\nfunction getDisableInfo(lines: string[], lineNumber: number): DisableInfo {\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-modified')) {\n // Check for date in format: max-lines-modified yyyy/mm/dd\n const dateMatch = line.match(/max-lines-modified\\s+(\\d{4}\\/\\d{2}\\/\\d{2}|XXXX\\/XX\\/XX)/);\n\n if (!dateMatch) {\n // No date found - treat as expired (invalid)\n return { type: 'full', isExpired: true, date: undefined };\n }\n\n const dateStr = dateMatch[1];\n\n // Secret permanent disable\n if (dateStr === 'XXXX/XX/XX') {\n return { type: 'full', isExpired: false, date: dateStr };\n }\n\n const date = parseDisableDate(dateStr);\n if (!date) {\n // Invalid date format - treat as expired\n return { type: 'full', isExpired: true, date: dateStr };\n }\n\n if (!isDateWithinMonth(date)) {\n // Date is expired (older than 1 month)\n return { type: 'full', isExpired: true, date: dateStr };\n }\n\n // Valid and not expired\n return { type: 'full', isExpired: false, date: dateStr };\n }\n if (line.includes('max-lines-new-methods')) {\n // Check for date in format: max-lines-new-methods yyyy/mm/dd\n const dateMatch = line.match(/max-lines-new-methods\\s+(\\d{4}\\/\\d{2}\\/\\d{2}|XXXX\\/XX\\/XX)/);\n\n if (!dateMatch) {\n // No date found - treat as expired (invalid)\n return { type: 'new-only', isExpired: true, date: undefined };\n }\n\n const dateStr = dateMatch[1];\n\n // Secret permanent disable\n if (dateStr === 'XXXX/XX/XX') {\n return { type: 'new-only', isExpired: false, date: dateStr };\n }\n\n const date = parseDisableDate(dateStr);\n if (!date) {\n // Invalid date format - treat as expired\n return { type: 'new-only', isExpired: true, date: dateStr };\n }\n\n if (!isDateWithinMonth(date)) {\n // Date is expired (older than 1 month)\n return { type: 'new-only', isExpired: true, date: dateStr };\n }\n\n // Valid and not expired\n return { type: 'new-only', isExpired: false, date: dateStr };\n }\n }\n }\n return { type: 'none', isExpired: false };\n}\n\ninterface MethodInfo {\n name: string;\n line: number;\n endLine: number;\n lines: number;\n disableInfo: DisableInfo;\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(filePath: string, workspaceRoot: string): MethodInfo[] {\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: MethodInfo[] = [];\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 disableInfo: getDisableInfo(fileLines, startLine),\n });\n }\n\n ts.forEachChild(node, visit);\n }\n\n visit(sourceFile);\n return methods;\n}\n\n/**\n * Check if a method has any changes within its line range\n */\nfunction methodHasChanges(method: MethodInfo, changedLineNumbers: Set<number>): boolean {\n for (let line = method.line; line <= method.endLine; line++) {\n if (changedLineNumbers.has(line)) return true;\n }\n return false;\n}\n\n/**\n * Check a NEW method and return violation if applicable\n */\nfunction checkNewMethodViolation(file: string, method: MethodInfo, mode: ValidationMode): MethodViolation | null {\n const { type: disableType, isExpired, date: disableDate } = method.disableInfo;\n\n if (mode === 'STRICT') {\n // When mode is STRICT, skip NEW methods without escape (let validate-new-methods handle)\n if (disableType === 'none') return null;\n return { file, methodName: method.name, line: method.line, lines: method.lines };\n }\n\n if (disableType === 'full' && isExpired) {\n return { file, methodName: method.name, line: method.line, lines: method.lines, expiredDisable: true, expiredDate: disableDate };\n }\n if (disableType !== 'new-only') return null;\n\n if (isExpired) {\n return { file, methodName: method.name, line: method.line, lines: method.lines, expiredDisable: true, expiredDate: disableDate };\n }\n return { file, methodName: method.name, line: method.line, lines: method.lines };\n}\n\n/**\n * Check a MODIFIED method and return violation if applicable\n */\nfunction checkModifiedMethodViolation(file: string, method: MethodInfo, mode: ValidationMode): MethodViolation {\n const { type: disableType, isExpired, date: disableDate } = method.disableInfo;\n\n if (mode === 'STRICT') {\n return { file, methodName: method.name, line: method.line, lines: method.lines };\n }\n if (disableType === 'full' && isExpired) {\n return { file, methodName: method.name, line: method.line, lines: method.lines, expiredDisable: true, expiredDate: disableDate };\n }\n return { file, methodName: method.name, line: method.line, lines: method.lines };\n}\n\n/**\n * Find methods that exceed the 80-line limit.\n * Checks NEW methods with escape hatches and MODIFIED methods.\n */\nfunction findViolations(\n workspaceRoot: string,\n changedFiles: string[],\n base: string,\n maxLines: number,\n mode: ValidationMode\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 const newMethodNames = findNewMethodSignaturesInDiff(diff);\n const changedLineNumbers = getChangedLineNumbers(diff);\n if (changedLineNumbers.size === 0) continue;\n\n const methods = findMethodsInFile(file, workspaceRoot);\n\n for (const method of methods) {\n const { type: disableType, isExpired } = method.disableInfo;\n\n // Skip methods with valid, non-expired full escape - unless mode is STRICT\n if (mode !== 'STRICT' && disableType === 'full' && !isExpired) continue;\n if (method.lines <= maxLines) continue;\n\n const isNewMethod = newMethodNames.has(method.name);\n\n if (isNewMethod) {\n const violation = checkNewMethodViolation(file, method, mode);\n if (violation) violations.push(violation);\n } else if (methodHasChanges(method, changedLineNumbers)) {\n violations.push(checkModifiedMethodViolation(file, method, mode));\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\n/**\n * Report violations to console\n */\n// webpieces-disable max-lines-new-methods -- Error output formatting with multiple message sections\nfunction reportViolations(violations: MethodViolation[], maxLines: number, mode: ValidationMode): void {\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 rule encourages GRADUAL cleanup so even though you did not cause it,');\n console.error(' you touched it, so you should fix now as part of your PR');\n console.error(' (this is for vibe coding and AI to fix as it touches things).');\n console.error(' You can refactor to stay under the limit 50% of the time. 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 if (v.expiredDisable) {\n console.error(` ❌ ${v.file}:${v.line}`);\n console.error(` Method: ${v.methodName} (${v.lines} lines, max: ${maxLines})`);\n console.error(` ⏰ EXPIRED DISABLE: Your disable comment dated ${v.expiredDate ?? 'unknown'} has expired (>1 month old).`);\n console.error(` You must either FIX the method or UPDATE the date to get another month.`);\n } else {\n console.error(` ❌ ${v.file}:${v.line}`);\n console.error(` Method: ${v.methodName} (${v.lines} lines, max: ${maxLines})`);\n }\n }\n console.error('');\n\n // Only show escape hatch instructions when mode is not STRICT\n if (mode !== 'STRICT') {\n console.error(' You can disable this error, but you will be forced to fix again in 1 month');\n console.error(' since 99% of methods can be less than ' + maxLines + ' lines of code.');\n console.error('');\n console.error(' Use escape with DATE (expires in 1 month):');\n console.error(` // webpieces-disable max-lines-modified ${getTodayDateString()} -- [your reason]`);\n console.error('');\n } else {\n console.error(' ⚠️ validationMode is STRICT - disable comments are NOT allowed.');\n console.error(' You MUST refactor to reduce method size.');\n console.error('');\n }\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 const mode: ValidationMode = options.mode ?? 'NORMAL';\n\n // Skip validation entirely if mode is OFF\n if (mode === 'OFF') {\n console.log('\\n⏭️ Skipping modified method validation (validationMode: OFF)');\n console.log('');\n return { success: true };\n }\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(` Validation mode: ${mode}${mode === 'STRICT' ? ' (disable comments ignored)' : ''}`);\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, mode);\n\n if (violations.length === 0) {\n console.log('✅ All modified methods are under ' + maxLines + ' lines');\n return { success: true };\n }\n\n writeTmpInstructions(workspaceRoot);\n reportViolations(violations, maxLines, mode);\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"]}
@@ -25,8 +25,11 @@ import * as fs from 'fs';
25
25
  import * as path from 'path';
26
26
  import * as ts from 'typescript';
27
27
 
28
+ export type ValidationMode = 'STRICT' | 'NORMAL' | 'OFF';
29
+
28
30
  export interface ValidateModifiedMethodsOptions {
29
31
  max?: number;
32
+ mode?: ValidationMode;
30
33
  }
31
34
 
32
35
  export interface ExecutorResult {
@@ -494,23 +497,64 @@ function findMethodsInFile(filePath: string, workspaceRoot: string): MethodInfo[
494
497
  return methods;
495
498
  }
496
499
 
500
+ /**
501
+ * Check if a method has any changes within its line range
502
+ */
503
+ function methodHasChanges(method: MethodInfo, changedLineNumbers: Set<number>): boolean {
504
+ for (let line = method.line; line <= method.endLine; line++) {
505
+ if (changedLineNumbers.has(line)) return true;
506
+ }
507
+ return false;
508
+ }
509
+
510
+ /**
511
+ * Check a NEW method and return violation if applicable
512
+ */
513
+ function checkNewMethodViolation(file: string, method: MethodInfo, mode: ValidationMode): MethodViolation | null {
514
+ const { type: disableType, isExpired, date: disableDate } = method.disableInfo;
515
+
516
+ if (mode === 'STRICT') {
517
+ // When mode is STRICT, skip NEW methods without escape (let validate-new-methods handle)
518
+ if (disableType === 'none') return null;
519
+ return { file, methodName: method.name, line: method.line, lines: method.lines };
520
+ }
521
+
522
+ if (disableType === 'full' && isExpired) {
523
+ return { file, methodName: method.name, line: method.line, lines: method.lines, expiredDisable: true, expiredDate: disableDate };
524
+ }
525
+ if (disableType !== 'new-only') return null;
526
+
527
+ if (isExpired) {
528
+ return { file, methodName: method.name, line: method.line, lines: method.lines, expiredDisable: true, expiredDate: disableDate };
529
+ }
530
+ return { file, methodName: method.name, line: method.line, lines: method.lines };
531
+ }
532
+
533
+ /**
534
+ * Check a MODIFIED method and return violation if applicable
535
+ */
536
+ function checkModifiedMethodViolation(file: string, method: MethodInfo, mode: ValidationMode): MethodViolation {
537
+ const { type: disableType, isExpired, date: disableDate } = method.disableInfo;
538
+
539
+ if (mode === 'STRICT') {
540
+ return { file, methodName: method.name, line: method.line, lines: method.lines };
541
+ }
542
+ if (disableType === 'full' && isExpired) {
543
+ return { file, methodName: method.name, line: method.line, lines: method.lines, expiredDisable: true, expiredDate: disableDate };
544
+ }
545
+ return { file, methodName: method.name, line: method.line, lines: method.lines };
546
+ }
547
+
497
548
  /**
498
549
  * Find methods that exceed the 80-line limit.
499
- *
500
- * This validator checks:
501
- * 1. NEW methods that have `max-lines-new-methods` escape (they passed 30-line check, now need 80-line check)
502
- * 2. MODIFIED methods (existing methods with changes)
503
- *
504
- * Skips:
505
- * - NEW methods without any escape (let validate-new-methods handle them first)
506
- * - Methods with valid, non-expired `max-lines-modified` escape (ultimate escape hatch)
550
+ * Checks NEW methods with escape hatches and MODIFIED methods.
507
551
  */
508
- // webpieces-disable max-lines-modified 2025/12/20 -- Core validation logic with multiple file operations
509
552
  function findViolations(
510
553
  workspaceRoot: string,
511
554
  changedFiles: string[],
512
555
  base: string,
513
- maxLines: number
556
+ maxLines: number,
557
+ mode: ValidationMode
514
558
  ): MethodViolation[] {
515
559
  const violations: MethodViolation[] = [];
516
560
 
@@ -518,94 +562,26 @@ function findViolations(
518
562
  const diff = getFileDiff(workspaceRoot, file, base);
519
563
  if (!diff) continue;
520
564
 
521
- // Find NEW methods from the diff
522
565
  const newMethodNames = findNewMethodSignaturesInDiff(diff);
523
-
524
- // Find which lines have changes
525
566
  const changedLineNumbers = getChangedLineNumbers(diff);
526
567
  if (changedLineNumbers.size === 0) continue;
527
568
 
528
- // Parse the current file to get all methods
529
569
  const methods = findMethodsInFile(file, workspaceRoot);
530
570
 
531
571
  for (const method of methods) {
532
- const isNewMethod = newMethodNames.has(method.name);
533
- const { type: disableType, isExpired, date: disableDate } = method.disableInfo;
534
-
535
- // Skip methods with valid, non-expired full escape (max-lines-modified)
536
- if (disableType === 'full' && !isExpired) continue;
572
+ const { type: disableType, isExpired } = method.disableInfo;
537
573
 
538
- // Skip methods under the limit
574
+ // Skip methods with valid, non-expired full escape - unless mode is STRICT
575
+ if (mode !== 'STRICT' && disableType === 'full' && !isExpired) continue;
539
576
  if (method.lines <= maxLines) continue;
540
577
 
541
- if (isNewMethod) {
542
- // For NEW methods:
543
- // - If has 'new-only' escape (non-expired) → check (they escaped 30-line, now need 80-line check)
544
- // - If has 'none' → skip (let validate-new-methods handle first)
545
- // - If has expired disable → report as violation
546
- if (disableType === 'full' && isExpired) {
547
- // Expired full disable - report with expired info
548
- violations.push({
549
- file,
550
- methodName: method.name,
551
- line: method.line,
552
- lines: method.lines,
553
- expiredDisable: true,
554
- expiredDate: disableDate,
555
- });
556
- continue;
557
- }
558
- if (disableType !== 'new-only') continue;
559
-
560
- if (isExpired) {
561
- // Expired new-only disable - report with expired info
562
- violations.push({
563
- file,
564
- methodName: method.name,
565
- line: method.line,
566
- lines: method.lines,
567
- expiredDisable: true,
568
- expiredDate: disableDate,
569
- });
570
- } else {
571
- // New method with valid max-lines-new-methods escape - check against 80-line limit
572
- violations.push({
573
- file,
574
- methodName: method.name,
575
- line: method.line,
576
- lines: method.lines,
577
- });
578
- }
579
- } else {
580
- // For MODIFIED methods: check if any changed line falls within method's range
581
- let hasChanges = false;
582
- for (let line = method.line; line <= method.endLine; line++) {
583
- if (changedLineNumbers.has(line)) {
584
- hasChanges = true;
585
- break;
586
- }
587
- }
578
+ const isNewMethod = newMethodNames.has(method.name);
588
579
 
589
- if (hasChanges) {
590
- if (disableType === 'full' && isExpired) {
591
- // Expired full disable - report with expired info
592
- violations.push({
593
- file,
594
- methodName: method.name,
595
- line: method.line,
596
- lines: method.lines,
597
- expiredDisable: true,
598
- expiredDate: disableDate,
599
- });
600
- } else {
601
- violations.push({
602
- file,
603
- methodName: method.name,
604
- line: method.line,
605
- lines: method.lines,
606
- });
607
- }
608
- }
580
+ if (isNewMethod) {
581
+ const violation = checkNewMethodViolation(file, method, mode);
582
+ if (violation) violations.push(violation);
583
+ } else if (methodHasChanges(method, changedLineNumbers)) {
584
+ violations.push(checkModifiedMethodViolation(file, method, mode));
609
585
  }
610
586
  }
611
587
  }
@@ -649,7 +625,7 @@ function detectBase(workspaceRoot: string): string | null {
649
625
  * Report violations to console
650
626
  */
651
627
  // webpieces-disable max-lines-new-methods -- Error output formatting with multiple message sections
652
- function reportViolations(violations: MethodViolation[], maxLines: number): void {
628
+ function reportViolations(violations: MethodViolation[], maxLines: number, mode: ValidationMode): void {
653
629
  console.error('');
654
630
  console.error('❌ Modified methods exceed ' + maxLines + ' lines!');
655
631
  console.error('');
@@ -677,12 +653,19 @@ function reportViolations(violations: MethodViolation[], maxLines: number): void
677
653
  }
678
654
  console.error('');
679
655
 
680
- console.error(' You can disable this error, but you will be forced to fix again in 1 month');
681
- console.error(' since 99% of methods can be less than ' + maxLines + ' lines of code.');
682
- console.error('');
683
- console.error(' Use escape with DATE (expires in 1 month):');
684
- console.error(` // webpieces-disable max-lines-modified ${getTodayDateString()} -- [your reason]`);
685
- console.error('');
656
+ // Only show escape hatch instructions when mode is not STRICT
657
+ if (mode !== 'STRICT') {
658
+ console.error(' You can disable this error, but you will be forced to fix again in 1 month');
659
+ console.error(' since 99% of methods can be less than ' + maxLines + ' lines of code.');
660
+ console.error('');
661
+ console.error(' Use escape with DATE (expires in 1 month):');
662
+ console.error(` // webpieces-disable max-lines-modified ${getTodayDateString()} -- [your reason]`);
663
+ console.error('');
664
+ } else {
665
+ console.error(' ⚠️ validationMode is STRICT - disable comments are NOT allowed.');
666
+ console.error(' You MUST refactor to reduce method size.');
667
+ console.error('');
668
+ }
686
669
  }
687
670
 
688
671
  export default async function runExecutor(
@@ -691,6 +674,14 @@ export default async function runExecutor(
691
674
  ): Promise<ExecutorResult> {
692
675
  const workspaceRoot = context.root;
693
676
  const maxLines = options.max ?? 80;
677
+ const mode: ValidationMode = options.mode ?? 'NORMAL';
678
+
679
+ // Skip validation entirely if mode is OFF
680
+ if (mode === 'OFF') {
681
+ console.log('\n⏭️ Skipping modified method validation (validationMode: OFF)');
682
+ console.log('');
683
+ return { success: true };
684
+ }
694
685
 
695
686
  let base = process.env['NX_BASE'];
696
687
 
@@ -712,6 +703,7 @@ export default async function runExecutor(
712
703
  console.log(` Base: ${base}`);
713
704
  console.log(' Comparing to: working tree (includes uncommitted changes)');
714
705
  console.log(` Max lines for modified methods: ${maxLines}`);
706
+ console.log(` Validation mode: ${mode}${mode === 'STRICT' ? ' (disable comments ignored)' : ''}`);
715
707
  console.log('');
716
708
 
717
709
  try {
@@ -724,7 +716,7 @@ export default async function runExecutor(
724
716
 
725
717
  console.log(`📂 Checking ${changedFiles.length} changed file(s)...`);
726
718
 
727
- const violations = findViolations(workspaceRoot, changedFiles, base, maxLines);
719
+ const violations = findViolations(workspaceRoot, changedFiles, base, maxLines, mode);
728
720
 
729
721
  if (violations.length === 0) {
730
722
  console.log('✅ All modified methods are under ' + maxLines + ' lines');
@@ -732,7 +724,7 @@ export default async function runExecutor(
732
724
  }
733
725
 
734
726
  writeTmpInstructions(workspaceRoot);
735
- reportViolations(violations, maxLines);
727
+ reportViolations(violations, maxLines, mode);
736
728
  return { success: false };
737
729
  } catch (err: unknown) {
738
730
  const error = err instanceof Error ? err : new Error(String(err));
@@ -8,6 +8,12 @@
8
8
  "type": "number",
9
9
  "description": "Maximum number of lines allowed for modified methods",
10
10
  "default": 80
11
+ },
12
+ "mode": {
13
+ "type": "string",
14
+ "enum": ["STRICT", "NORMAL", "OFF"],
15
+ "description": "STRICT: limits enforced, no disable comments. NORMAL: disable comments work. OFF: skip validation.",
16
+ "default": "NORMAL"
11
17
  }
12
18
  },
13
19
  "required": []
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webpieces/dev-config",
3
- "version": "0.2.63",
3
+ "version": "0.2.64",
4
4
  "description": "Development configuration, scripts, and patterns for WebPieces projects",
5
5
  "type": "commonjs",
6
6
  "bin": {
package/plugin.js CHANGED
@@ -42,7 +42,7 @@ const DEFAULT_OPTIONS = {
42
42
  newMethodsMaxLines: 30,
43
43
  modifiedAndNewMethodsMaxLines: 80,
44
44
  modifiedFilesMaxLines: 900,
45
- forceModifiedFilesLimit: false,
45
+ validationMode: 'NORMAL',
46
46
  },
47
47
  features: {
48
48
  generate: true,
@@ -198,10 +198,10 @@ function createWorkspaceTargetsWithoutPrefix(opts) {
198
198
  targets['validate-new-methods'] = createValidateNewMethodsTarget(validations.newMethodsMaxLines);
199
199
  }
200
200
  if (validations.validateModifiedMethods) {
201
- targets['validate-modified-methods'] = createValidateModifiedMethodsTarget(validations.modifiedAndNewMethodsMaxLines);
201
+ targets['validate-modified-methods'] = createValidateModifiedMethodsTarget(validations.modifiedAndNewMethodsMaxLines, validations.validationMode);
202
202
  }
203
203
  if (validations.validateModifiedFiles) {
204
- targets['validate-modified-files'] = createValidateModifiedFilesTarget(validations.modifiedFilesMaxLines, validations.forceModifiedFilesLimit);
204
+ targets['validate-modified-files'] = createValidateModifiedFilesTarget(validations.modifiedFilesMaxLines, validations.validationMode);
205
205
  }
206
206
  if (validations.validateVersionsLocked) {
207
207
  targets['validate-versions-locked'] = createValidateVersionsLockedTarget();
@@ -245,10 +245,10 @@ function createWorkspaceTargets(opts) {
245
245
  targets[`${prefix}validate-new-methods`] = createValidateNewMethodsTarget(opts.workspace.validations.newMethodsMaxLines);
246
246
  }
247
247
  if (opts.workspace.validations.validateModifiedMethods) {
248
- targets[`${prefix}validate-modified-methods`] = createValidateModifiedMethodsTarget(opts.workspace.validations.modifiedAndNewMethodsMaxLines);
248
+ targets[`${prefix}validate-modified-methods`] = createValidateModifiedMethodsTarget(opts.workspace.validations.modifiedAndNewMethodsMaxLines, opts.workspace.validations.validationMode);
249
249
  }
250
250
  if (opts.workspace.validations.validateModifiedFiles) {
251
- targets[`${prefix}validate-modified-files`] = createValidateModifiedFilesTarget(opts.workspace.validations.modifiedFilesMaxLines, opts.workspace.validations.forceModifiedFilesLimit);
251
+ targets[`${prefix}validate-modified-files`] = createValidateModifiedFilesTarget(opts.workspace.validations.modifiedFilesMaxLines, opts.workspace.validations.validationMode);
252
252
  }
253
253
  return targets;
254
254
  }
@@ -344,24 +344,24 @@ function createValidateNewMethodsTarget(maxLines) {
344
344
  },
345
345
  };
346
346
  }
347
- function createValidateModifiedMethodsTarget(maxLines) {
347
+ function createValidateModifiedMethodsTarget(maxLines, mode) {
348
348
  return {
349
349
  executor: '@webpieces/dev-config:validate-modified-methods',
350
350
  cache: false, // Don't cache - depends on git state
351
351
  inputs: ['default'],
352
- options: { max: maxLines },
352
+ options: { max: maxLines, mode },
353
353
  metadata: {
354
354
  technologies: ['nx'],
355
355
  description: `Validate new and modified methods do not exceed ${maxLines} lines (encourages gradual cleanup)`,
356
356
  },
357
357
  };
358
358
  }
359
- function createValidateModifiedFilesTarget(maxLines, forceLimit) {
359
+ function createValidateModifiedFilesTarget(maxLines, mode) {
360
360
  return {
361
361
  executor: '@webpieces/dev-config:validate-modified-files',
362
362
  cache: false, // Don't cache - depends on git state
363
363
  inputs: ['default'],
364
- options: { max: maxLines, forceLimit },
364
+ options: { max: maxLines, mode },
365
365
  metadata: {
366
366
  technologies: ['nx'],
367
367
  description: `Validate modified files do not exceed ${maxLines} lines (encourages keeping files small)`,