@webpieces/code-rules 0.3.196 → 0.3.198

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/package.json +2 -2
  2. package/src/diff-utils.d.ts +3 -22
  3. package/src/diff-utils.js +9 -116
  4. package/src/diff-utils.js.map +1 -1
  5. package/src/validate-catch-error-pattern.d.ts +1 -1
  6. package/src/validate-catch-error-pattern.js +7 -152
  7. package/src/validate-catch-error-pattern.js.map +1 -1
  8. package/src/validate-dtos.d.ts +1 -1
  9. package/src/validate-dtos.js +6 -141
  10. package/src/validate-dtos.js.map +1 -1
  11. package/src/validate-modified-files.js +3 -87
  12. package/src/validate-modified-files.js.map +1 -1
  13. package/src/validate-modified-methods.d.ts +1 -1
  14. package/src/validate-modified-methods.js +7 -197
  15. package/src/validate-modified-methods.js.map +1 -1
  16. package/src/validate-new-methods.js +4 -164
  17. package/src/validate-new-methods.js.map +1 -1
  18. package/src/validate-no-any-unknown.d.ts +1 -1
  19. package/src/validate-no-any-unknown.js +7 -143
  20. package/src/validate-no-any-unknown.js.map +1 -1
  21. package/src/validate-no-destructure.d.ts +1 -1
  22. package/src/validate-no-destructure.js +7 -143
  23. package/src/validate-no-destructure.js.map +1 -1
  24. package/src/validate-no-direct-api-resolver.d.ts +1 -1
  25. package/src/validate-no-direct-api-resolver.js +10 -89
  26. package/src/validate-no-direct-api-resolver.js.map +1 -1
  27. package/src/validate-no-implicit-any.d.ts +1 -1
  28. package/src/validate-no-implicit-any.js +6 -94
  29. package/src/validate-no-implicit-any.js.map +1 -1
  30. package/src/validate-no-inline-types.d.ts +1 -1
  31. package/src/validate-no-inline-types.js +10 -174
  32. package/src/validate-no-inline-types.js.map +1 -1
  33. package/src/validate-no-symbol-di-tokens.d.ts +1 -1
  34. package/src/validate-no-symbol-di-tokens.js +6 -130
  35. package/src/validate-no-symbol-di-tokens.js.map +1 -1
  36. package/src/validate-no-unmanaged-exceptions.d.ts +1 -1
  37. package/src/validate-no-unmanaged-exceptions.js +7 -152
  38. package/src/validate-no-unmanaged-exceptions.js.map +1 -1
  39. package/src/validate-prisma-converters.d.ts +1 -1
  40. package/src/validate-prisma-converters.js +10 -89
  41. package/src/validate-prisma-converters.js.map +1 -1
  42. package/src/validate-return-types.d.ts +1 -1
  43. package/src/validate-return-types.js +10 -174
  44. package/src/validate-return-types.js.map +1 -1
@@ -19,7 +19,6 @@
19
19
  Object.defineProperty(exports, "__esModule", { value: true });
20
20
  exports.runNewMethods = runNewMethods;
21
21
  const tslib_1 = require("tslib");
22
- const child_process_1 = require("child_process");
23
22
  const fs = tslib_1.__importStar(require("fs"));
24
23
  const path = tslib_1.__importStar(require("path"));
25
24
  const ts = tslib_1.__importStar(require("typescript"));
@@ -33,127 +32,6 @@ const TMP_MD_FILE = 'webpieces.methodsize.md';
33
32
  function writeTmpInstructions(workspaceRoot) {
34
33
  return (0, rules_config_1.writeTemplate)(workspaceRoot, TMP_MD_FILE);
35
34
  }
36
- /**
37
- * Get changed TypeScript files between base and head (or working tree if head not specified).
38
- * Uses `git diff base [head]` to match what `nx affected` does.
39
- * When head is NOT specified, also includes untracked files (matching nx affected behavior).
40
- */
41
- function getChangedTypeScriptFiles(workspaceRoot, base, head) {
42
- // eslint-disable-next-line @webpieces/no-unmanaged-exceptions
43
- try {
44
- // If head is specified, diff base to head; otherwise diff base to working tree
45
- const diffTarget = head ? `${base} ${head}` : base;
46
- const output = (0, child_process_1.execSync)(`git diff --name-only ${diffTarget} -- '*.ts' '*.tsx'`, {
47
- cwd: workspaceRoot,
48
- encoding: 'utf-8',
49
- });
50
- const changedFiles = output
51
- .trim()
52
- .split('\n')
53
- .filter((f) => f && !f.includes('.spec.ts') && !f.includes('.test.ts'));
54
- // When comparing to working tree (no head specified), also include untracked files
55
- // This matches what nx affected does: "All modified files not yet committed or tracked will also be added"
56
- if (!head) {
57
- // eslint-disable-next-line @webpieces/no-unmanaged-exceptions
58
- try {
59
- const untrackedOutput = (0, child_process_1.execSync)(`git ls-files --others --exclude-standard '*.ts' '*.tsx'`, {
60
- cwd: workspaceRoot,
61
- encoding: 'utf-8',
62
- });
63
- const untrackedFiles = untrackedOutput
64
- .trim()
65
- .split('\n')
66
- .filter((f) => f && !f.includes('.spec.ts') && !f.includes('.test.ts'));
67
- // Merge and dedupe
68
- const allFiles = new Set([...changedFiles, ...untrackedFiles]);
69
- return Array.from(allFiles);
70
- }
71
- catch (err) {
72
- //const error = toError(err);
73
- // If ls-files fails, just return the changed files
74
- return changedFiles;
75
- }
76
- }
77
- return changedFiles;
78
- }
79
- catch (err) {
80
- //const error = toError(err);
81
- return [];
82
- }
83
- }
84
- /**
85
- * Get the diff content for a specific file between base and head (or working tree if head not specified).
86
- * Uses `git diff base [head]` to match what `nx affected` does.
87
- * For untracked files, returns the entire file content as additions.
88
- */
89
- function getFileDiff(workspaceRoot, file, base, head) {
90
- // eslint-disable-next-line @webpieces/no-unmanaged-exceptions
91
- try {
92
- // If head is specified, diff base to head; otherwise diff base to working tree
93
- const diffTarget = head ? `${base} ${head}` : base;
94
- const diff = (0, child_process_1.execSync)(`git diff ${diffTarget} -- "${file}"`, {
95
- cwd: workspaceRoot,
96
- encoding: 'utf-8',
97
- });
98
- // If diff is empty and we're comparing to working tree, check if it's an untracked file
99
- if (!diff && !head) {
100
- const fullPath = path.join(workspaceRoot, file);
101
- if (fs.existsSync(fullPath)) {
102
- // Check if file is untracked
103
- const isUntracked = (0, child_process_1.execSync)(`git ls-files --others --exclude-standard "${file}"`, {
104
- cwd: workspaceRoot,
105
- encoding: 'utf-8',
106
- }).trim();
107
- if (isUntracked) {
108
- // For untracked files, treat entire content as additions
109
- const content = fs.readFileSync(fullPath, 'utf-8');
110
- const lines = content.split('\n');
111
- // Create a pseudo-diff where all lines are additions
112
- return lines.map((line) => `+${line}`).join('\n');
113
- }
114
- }
115
- }
116
- return diff;
117
- }
118
- catch (err) {
119
- //const error = toError(err);
120
- return '';
121
- }
122
- }
123
- /**
124
- * Parse diff to find newly added method signatures
125
- */
126
- function findNewMethodSignaturesInDiff(diffContent) {
127
- const newMethods = new Set();
128
- const lines = diffContent.split('\n');
129
- // Patterns to match method definitions
130
- const patterns = [
131
- // [export] [async] function methodName( - most explicit, check first
132
- /^\+\s*(?:export\s+)?(?:async\s+)?function\s+(\w+)\s*\(/,
133
- // [export] const/let methodName = [async] (
134
- /^\+\s*(?:export\s+)?(?:const|let)\s+(\w+)\s*=\s*(?:async\s*)?\(/,
135
- // [export] const/let methodName = [async] function
136
- /^\+\s*(?:export\s+)?(?:const|let)\s+(\w+)\s*=\s*(?:async\s+)?function/,
137
- // class method: [public/private/protected] [static] [async] methodName( - but NOT constructor, if, for, while, etc.
138
- /^\+\s*(?:(?:public|private|protected)\s+)?(?:static\s+)?(?:async\s+)?(\w+)\s*\(/,
139
- ];
140
- for (const line of lines) {
141
- if (line.startsWith('+') && !line.startsWith('+++')) {
142
- for (const pattern of patterns) {
143
- const match = line.match(pattern);
144
- if (match) {
145
- // Extract method name - now always in capture group 1
146
- const methodName = match[1];
147
- if (methodName && !['if', 'for', 'while', 'switch', 'catch', 'constructor'].includes(methodName)) {
148
- newMethods.add(methodName);
149
- }
150
- break;
151
- }
152
- }
153
- }
154
- }
155
- return newMethods;
156
- }
157
35
  /**
158
36
  * Check if a line contains a webpieces-disable comment that exempts from new method validation.
159
37
  * Both max-lines-new-methods AND max-lines-modified are accepted here.
@@ -237,8 +115,8 @@ function findViolations(workspaceRoot, changedFiles, base, limit, disableAllowed
237
115
  const violations = [];
238
116
  for (const file of changedFiles) {
239
117
  // Get the diff to find which methods are NEW (not just modified)
240
- const diff = getFileDiff(workspaceRoot, file, base, head);
241
- const newMethodNames = findNewMethodSignaturesInDiff(diff);
118
+ const diff = (0, rules_config_1.getFileDiff)(workspaceRoot, file, base, head);
119
+ const newMethodNames = (0, rules_config_1.findNewMethodSignaturesInDiff)(diff);
242
120
  if (newMethodNames.size === 0)
243
121
  continue;
244
122
  // Parse the current file to get method line counts
@@ -274,44 +152,6 @@ function findViolations(workspaceRoot, changedFiles, base, limit, disableAllowed
274
152
  }
275
153
  return violations;
276
154
  }
277
- /**
278
- * Auto-detect the base branch by finding the merge-base with origin/main.
279
- * This allows the executor to run even when NX_BASE isn't set (e.g., via dependsOn).
280
- */
281
- function detectBase(workspaceRoot) {
282
- // eslint-disable-next-line @webpieces/no-unmanaged-exceptions
283
- try {
284
- // First, try to get merge-base with origin/main
285
- const mergeBase = (0, child_process_1.execSync)('git merge-base HEAD origin/main', {
286
- cwd: workspaceRoot,
287
- encoding: 'utf-8',
288
- stdio: ['pipe', 'pipe', 'pipe'],
289
- }).trim();
290
- if (mergeBase) {
291
- return mergeBase;
292
- }
293
- }
294
- catch (err) {
295
- //const error = toError(err);
296
- // origin/main might not exist, try main
297
- // eslint-disable-next-line @webpieces/no-unmanaged-exceptions
298
- try {
299
- const mergeBase = (0, child_process_1.execSync)('git merge-base HEAD main', {
300
- cwd: workspaceRoot,
301
- encoding: 'utf-8',
302
- stdio: ['pipe', 'pipe', 'pipe'],
303
- }).trim();
304
- if (mergeBase) {
305
- return mergeBase;
306
- }
307
- }
308
- catch (err2) {
309
- //const error2 = toError(err2);
310
- // Ignore - will return null
311
- }
312
- }
313
- return null;
314
- }
315
155
  /**
316
156
  * Report violations to the user with helpful instructions
317
157
  */
@@ -363,7 +203,7 @@ async function runNewMethods(options, workspaceRoot) {
363
203
  const head = process.env['NX_HEAD'];
364
204
  if (!base) {
365
205
  // Try to auto-detect base from git merge-base
366
- base = detectBase(workspaceRoot) ?? undefined;
206
+ base = (0, rules_config_1.detectBase)(workspaceRoot) ?? undefined;
367
207
  if (!base) {
368
208
  console.log('\n\u23ed\ufe0f Skipping new method validation (could not detect base branch)');
369
209
  console.log(' To run explicitly: nx affected --target=validate-new-methods --base=origin/main');
@@ -383,7 +223,7 @@ async function runNewMethods(options, workspaceRoot) {
383
223
  // eslint-disable-next-line @webpieces/no-unmanaged-exceptions
384
224
  try {
385
225
  // Get changed TypeScript files (base to head, or working tree if head not set)
386
- const changedFiles = getChangedTypeScriptFiles(workspaceRoot, base, head);
226
+ const changedFiles = (0, rules_config_1.getChangedFiles)(workspaceRoot, base, head);
387
227
  if (changedFiles.length === 0) {
388
228
  console.log('\u2705 No TypeScript files changed');
389
229
  return { success: true };
@@ -1 +1 @@
1
- {"version":3,"file":"validate-new-methods.js","sourceRoot":"","sources":["../../../../../packages/tooling/code-rules/src/validate-new-methods.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Validate New Methods Executor\n *\n * Validates that newly added methods don't exceed a maximum line count.\n * Runs in affected mode when:\n * 1. NX_BASE environment variable is set (via nx affected), OR\n * 2. Auto-detects base by finding merge-base with origin/main\n *\n * This validator encourages writing methods that read like a \"table of contents\"\n * where each method call describes a larger piece of work.\n *\n * Usage:\n * nx affected --target=validate-new-methods --base=origin/main\n * OR: runs automatically via build's architecture:validate-complete dependency\n *\n * Escape hatch: Add webpieces-disable max-lines-new-methods comment with justification\n */\n\nimport { execSync } from 'child_process';\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport * as ts from 'typescript';\nimport { writeTemplate, RULE_NAMES, WEBPIECES_DISABLE, MaxMethodLinesConfig, MethodLimitMode } from '@webpieces/rules-config';\nimport { ExecutorResult } from './code-validator';\nimport { shouldSkipRule } from './resolve-mode';\n\ninterface MethodViolation {\n file: string;\n methodName: string;\n line: number;\n lines: number;\n isNew: boolean;\n limit: number;\n}\n\ninterface MethodInfo {\n name: string;\n line: number;\n lines: number;\n hasDisableComment: boolean;\n}\n\nconst TMP_MD_FILE = 'webpieces.methodsize.md';\n\n/**\n * Write the instructions documentation to .webpieces/instruct-ai/.\n * Sourced from @webpieces/rules-config.\n */\nfunction writeTmpInstructions(workspaceRoot: string): string {\n return writeTemplate(workspaceRoot, TMP_MD_FILE);\n}\n\n/**\n * Get changed TypeScript files between base and head (or working tree if head not specified).\n * Uses `git diff base [head]` to match what `nx affected` does.\n * When head is NOT specified, also includes untracked files (matching nx affected behavior).\n */\nfunction getChangedTypeScriptFiles(workspaceRoot: string, base: string, head?: string): string[] {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n // If head is specified, diff base to head; otherwise diff base to working tree\n const diffTarget = head ? `${base} ${head}` : base;\n const output = execSync(`git diff --name-only ${diffTarget} -- '*.ts' '*.tsx'`, {\n cwd: workspaceRoot,\n encoding: 'utf-8',\n });\n const changedFiles = output\n .trim()\n .split('\\n')\n .filter((f) => f && !f.includes('.spec.ts') && !f.includes('.test.ts'));\n\n // When comparing to working tree (no head specified), also include untracked files\n // This matches what nx affected does: \"All modified files not yet committed or tracked will also be added\"\n if (!head) {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const untrackedOutput = execSync(`git ls-files --others --exclude-standard '*.ts' '*.tsx'`, {\n cwd: workspaceRoot,\n encoding: 'utf-8',\n });\n const untrackedFiles = untrackedOutput\n .trim()\n .split('\\n')\n .filter((f) => f && !f.includes('.spec.ts') && !f.includes('.test.ts'));\n // Merge and dedupe\n const allFiles = new Set([...changedFiles, ...untrackedFiles]);\n return Array.from(allFiles);\n } catch (err: unknown) {\n //const error = toError(err);\n // If ls-files fails, just return the changed files\n return changedFiles;\n }\n }\n\n return changedFiles;\n } catch (err: unknown) {\n //const error = toError(err);\n return [];\n }\n}\n\n/**\n * Get the diff content for a specific file between base and head (or working tree if head not specified).\n * Uses `git diff base [head]` to match what `nx affected` does.\n * For untracked files, returns the entire file content as additions.\n */\nfunction getFileDiff(workspaceRoot: string, file: string, base: string, head?: string): string {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n // If head is specified, diff base to head; otherwise diff base to working tree\n const diffTarget = head ? `${base} ${head}` : base;\n const diff = execSync(`git diff ${diffTarget} -- \"${file}\"`, {\n cwd: workspaceRoot,\n encoding: 'utf-8',\n });\n\n // If diff is empty and we're comparing to working tree, check if it's an untracked file\n if (!diff && !head) {\n const fullPath = path.join(workspaceRoot, file);\n if (fs.existsSync(fullPath)) {\n // Check if file is untracked\n const isUntracked = execSync(`git ls-files --others --exclude-standard \"${file}\"`, {\n cwd: workspaceRoot,\n encoding: 'utf-8',\n }).trim();\n\n if (isUntracked) {\n // For untracked files, treat entire content as additions\n const content = fs.readFileSync(fullPath, 'utf-8');\n const lines = content.split('\\n');\n // Create a pseudo-diff where all lines are additions\n return lines.map((line) => `+${line}`).join('\\n');\n }\n }\n }\n\n return diff;\n } catch (err: unknown) {\n //const error = toError(err);\n return '';\n }\n}\n\n/**\n * Parse diff to find newly added method signatures\n */\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\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: [public/private/protected] [static] [async] methodName( - but NOT constructor, if, for, while, etc.\n /^\\+\\s*(?:(?:public|private|protected)\\s+)?(?:static\\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 * Check if a line contains a webpieces-disable comment that exempts from new method validation.\n * Both max-lines-new-methods AND max-lines-modified are accepted here.\n */\nfunction hasDisableComment(lines: string[], lineNumber: number): boolean {\n // Check the line before the method (lineNumber is 1-indexed, array is 0-indexed)\n // We need to check a few lines before in case there's JSDoc or decorators\n const startCheck = Math.max(0, lineNumber - 5);\n for (let i = lineNumber - 2; i >= startCheck; i--) {\n const line = lines[i]?.trim() ?? '';\n // Stop if we hit another function/class/etc\n if (line.startsWith('function ') || line.startsWith('class ') || line.endsWith('}')) {\n break;\n }\n if (line.includes(WEBPIECES_DISABLE)) {\n // Either escape hatch exempts from the lowLimit new method check\n if (line.includes(RULE_NAMES.MAX_LINES_NEW_METHODS) || line.includes(RULE_NAMES.MAX_LINES_MODIFIED)) {\n return true;\n }\n }\n }\n return false;\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 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 // Check if it's assigned to a variable\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 lines: endLine - startLine + 1,\n hasDisableComment: hasDisableComment(fileLines, startLine),\n });\n }\n\n ts.forEachChild(node, visit);\n }\n\n visit(sourceFile);\n return methods;\n}\n\n/**\n * Find new methods that exceed the line limit\n */\nfunction findViolations(\n workspaceRoot: string,\n changedFiles: string[],\n base: string,\n limit: number,\n disableAllowed: boolean,\n head?: string\n): MethodViolation[] {\n const violations: MethodViolation[] = [];\n\n for (const file of changedFiles) {\n // Get the diff to find which methods are NEW (not just modified)\n const diff = getFileDiff(workspaceRoot, file, base, head);\n const newMethodNames = findNewMethodSignaturesInDiff(diff);\n\n if (newMethodNames.size === 0) continue;\n\n // Parse the current file to get method line counts\n const methods = findMethodsInFile(file, workspaceRoot);\n\n for (const method of methods) {\n if (!newMethodNames.has(method.name)) continue;\n\n if (method.lines > limit) {\n if (!disableAllowed) {\n // No escape possible\n violations.push({\n file,\n methodName: method.name,\n line: method.line,\n lines: method.lines,\n isNew: true,\n limit,\n });\n } else if (!method.hasDisableComment) {\n // Escape allowed but not present\n violations.push({\n file,\n methodName: method.name,\n line: method.line,\n lines: method.lines,\n isNew: true,\n limit,\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 * This allows the executor to run even when NX_BASE isn't set (e.g., via dependsOn).\n */\nfunction detectBase(workspaceRoot: string): string | null {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n // First, try to get merge-base with origin/main\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 (err: unknown) {\n //const error = toError(err);\n // origin/main might not exist, try main\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\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 (err2: unknown) {\n //const error2 = toError(err2);\n // Ignore - will return null\n }\n }\n return null;\n}\n\n/**\n * Report violations to the user with helpful instructions\n */\nfunction reportViolations(violations: MethodViolation[], limit: number, disableAllowed: boolean): void {\n console.error('');\n console.error('\\u274c New methods exceed ' + limit + ' line limit!');\n console.error('');\n console.error('\\ud83d\\udcda Methods should read like a \"table of contents\" - each method call');\n console.error(' describes a larger piece of work.');\n console.error('');\n console.error('\\u26a0\\ufe0f *** READ .webpieces/instruct-ai/webpieces.methodsize.md for detailed guidance on how to fix this easily *** \\u26a0\\ufe0f');\n console.error('');\n\n if (disableAllowed) {\n console.error('\\u26a0\\ufe0f VIOLATIONS (can use escape hatch):');\n } else {\n console.error('\\ud83d\\udeab VIOLATIONS (cannot be bypassed with disable comment):');\n }\n console.error('');\n for (const v of violations) {\n console.error(` \\u274c ${v.file}:${v.line}`);\n console.error(` Method: ${v.methodName} (${v.lines} lines, limit: ${limit})`);\n }\n console.error('');\n if (disableAllowed) {\n console.error(' Use escape: // webpieces-disable max-lines-new-methods -- [your reason]');\n } else {\n console.error(' These methods MUST be refactored - no escape hatch available (disableAllowed=false).');\n }\n console.error('');\n}\n\nexport async function runNewMethods(\n options: MaxMethodLinesConfig,\n workspaceRoot: string\n): Promise<ExecutorResult> {\n const limit = options.limit ?? 80;\n const disableAllowed = options.disableAllowed ?? true;\n\n const rawMode: MethodLimitMode = options.mode ?? 'NEW_AND_MODIFIED_METHODS';\n const skip = rawMode !== 'OFF' ? shouldSkipRule(options.ignoreModifiedUntilEpoch, options.ignoreRuleWhileOnBranch) : { skip: false };\n const mode: MethodLimitMode = skip.skip ? 'OFF' : rawMode;\n\n // Skip validation entirely if mode is OFF\n if (mode === 'OFF') {\n const reason = skip.skip ? skip.reason : 'mode: OFF';\n console.log(`\\n\\u23ed\\ufe0f Skipping new method validation (${reason})`);\n console.log('');\n return { success: true };\n }\n\n // Check if running in affected mode via NX_BASE, or auto-detect\n // If NX_HEAD is set (via nx affected --head=X), use it; otherwise compare to working tree\n let base = process.env['NX_BASE'];\n const head = process.env['NX_HEAD'];\n\n if (!base) {\n // Try to auto-detect base from git merge-base\n base = detectBase(workspaceRoot) ?? undefined;\n\n if (!base) {\n console.log('\\n\\u23ed\\ufe0f Skipping new method validation (could not detect base branch)');\n console.log(' To run explicitly: nx affected --target=validate-new-methods --base=origin/main');\n console.log('');\n return { success: true };\n }\n\n console.log('\\n\\ud83d\\udccf Validating New Method Sizes (auto-detected base)\\n');\n } else {\n console.log('\\n\\ud83d\\udccf Validating New Method Sizes\\n');\n }\n\n console.log(` Base: ${base}`);\n console.log(` Head: ${head ?? 'working tree (includes uncommitted changes)'}`);\n console.log(` Mode: ${mode}`);\n console.log(` Limit for new methods: ${limit} lines (${disableAllowed ? 'can escape' : 'NO escape possible'})`);\n console.log('');\n\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n // Get changed TypeScript files (base to head, or working tree if head not set)\n const changedFiles = getChangedTypeScriptFiles(workspaceRoot, base, head);\n\n if (changedFiles.length === 0) {\n console.log('\\u2705 No TypeScript files changed');\n return { success: true };\n }\n\n console.log(`\\ud83d\\udcc2 Checking ${changedFiles.length} changed file(s)...`);\n\n // Find violations\n const violations = findViolations(workspaceRoot, changedFiles, base, limit, disableAllowed, head);\n\n if (violations.length === 0) {\n console.log('\\u2705 All new methods are within ' + limit + ' lines');\n return { success: true };\n }\n\n // Write instructions file and report violations\n writeTmpInstructions(workspaceRoot);\n reportViolations(violations, limit, disableAllowed);\n\n return { success: false };\n } catch (err: unknown) {\n //const error = toError(err);\n const error = err instanceof Error ? err : new Error(String(err));\n console.error('\\u274c New method validation failed:', error.message);\n return { success: false };\n }\n}\n"]}
1
+ {"version":3,"file":"validate-new-methods.js","sourceRoot":"","sources":["../../../../../packages/tooling/code-rules/src/validate-new-methods.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Validate New Methods Executor\n *\n * Validates that newly added methods don't exceed a maximum line count.\n * Runs in affected mode when:\n * 1. NX_BASE environment variable is set (via nx affected), OR\n * 2. Auto-detects base by finding merge-base with origin/main\n *\n * This validator encourages writing methods that read like a \"table of contents\"\n * where each method call describes a larger piece of work.\n *\n * Usage:\n * nx affected --target=validate-new-methods --base=origin/main\n * OR: runs automatically via build's architecture:validate-complete dependency\n *\n * Escape hatch: Add webpieces-disable max-lines-new-methods comment with justification\n */\n\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport * as ts from 'typescript';\nimport {\n writeTemplate,\n RULE_NAMES,\n WEBPIECES_DISABLE,\n MaxMethodLinesConfig,\n MethodLimitMode,\n detectBase,\n getChangedFiles,\n getFileDiff,\n findNewMethodSignaturesInDiff,\n} from '@webpieces/rules-config';\nimport { ExecutorResult } from './code-validator';\nimport { shouldSkipRule } from './resolve-mode';\n\ninterface MethodViolation {\n file: string;\n methodName: string;\n line: number;\n lines: number;\n isNew: boolean;\n limit: number;\n}\n\ninterface MethodInfo {\n name: string;\n line: number;\n lines: number;\n hasDisableComment: boolean;\n}\n\nconst TMP_MD_FILE = 'webpieces.methodsize.md';\n\n/**\n * Write the instructions documentation to .webpieces/instruct-ai/.\n * Sourced from @webpieces/rules-config.\n */\nfunction writeTmpInstructions(workspaceRoot: string): string {\n return writeTemplate(workspaceRoot, TMP_MD_FILE);\n}\n\n/**\n * Check if a line contains a webpieces-disable comment that exempts from new method validation.\n * Both max-lines-new-methods AND max-lines-modified are accepted here.\n */\nfunction hasDisableComment(lines: string[], lineNumber: number): boolean {\n // Check the line before the method (lineNumber is 1-indexed, array is 0-indexed)\n // We need to check a few lines before in case there's JSDoc or decorators\n const startCheck = Math.max(0, lineNumber - 5);\n for (let i = lineNumber - 2; i >= startCheck; i--) {\n const line = lines[i]?.trim() ?? '';\n // Stop if we hit another function/class/etc\n if (line.startsWith('function ') || line.startsWith('class ') || line.endsWith('}')) {\n break;\n }\n if (line.includes(WEBPIECES_DISABLE)) {\n // Either escape hatch exempts from the lowLimit new method check\n if (line.includes(RULE_NAMES.MAX_LINES_NEW_METHODS) || line.includes(RULE_NAMES.MAX_LINES_MODIFIED)) {\n return true;\n }\n }\n }\n return false;\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 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 // Check if it's assigned to a variable\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 lines: endLine - startLine + 1,\n hasDisableComment: hasDisableComment(fileLines, startLine),\n });\n }\n\n ts.forEachChild(node, visit);\n }\n\n visit(sourceFile);\n return methods;\n}\n\n/**\n * Find new methods that exceed the line limit\n */\nfunction findViolations(\n workspaceRoot: string,\n changedFiles: string[],\n base: string,\n limit: number,\n disableAllowed: boolean,\n head?: string\n): MethodViolation[] {\n const violations: MethodViolation[] = [];\n\n for (const file of changedFiles) {\n // Get the diff to find which methods are NEW (not just modified)\n const diff = getFileDiff(workspaceRoot, file, base, head);\n const newMethodNames = findNewMethodSignaturesInDiff(diff);\n\n if (newMethodNames.size === 0) continue;\n\n // Parse the current file to get method line counts\n const methods = findMethodsInFile(file, workspaceRoot);\n\n for (const method of methods) {\n if (!newMethodNames.has(method.name)) continue;\n\n if (method.lines > limit) {\n if (!disableAllowed) {\n // No escape possible\n violations.push({\n file,\n methodName: method.name,\n line: method.line,\n lines: method.lines,\n isNew: true,\n limit,\n });\n } else if (!method.hasDisableComment) {\n // Escape allowed but not present\n violations.push({\n file,\n methodName: method.name,\n line: method.line,\n lines: method.lines,\n isNew: true,\n limit,\n });\n }\n }\n }\n }\n\n return violations;\n}\n\n/**\n * Report violations to the user with helpful instructions\n */\nfunction reportViolations(violations: MethodViolation[], limit: number, disableAllowed: boolean): void {\n console.error('');\n console.error('\\u274c New methods exceed ' + limit + ' line limit!');\n console.error('');\n console.error('\\ud83d\\udcda Methods should read like a \"table of contents\" - each method call');\n console.error(' describes a larger piece of work.');\n console.error('');\n console.error('\\u26a0\\ufe0f *** READ .webpieces/instruct-ai/webpieces.methodsize.md for detailed guidance on how to fix this easily *** \\u26a0\\ufe0f');\n console.error('');\n\n if (disableAllowed) {\n console.error('\\u26a0\\ufe0f VIOLATIONS (can use escape hatch):');\n } else {\n console.error('\\ud83d\\udeab VIOLATIONS (cannot be bypassed with disable comment):');\n }\n console.error('');\n for (const v of violations) {\n console.error(` \\u274c ${v.file}:${v.line}`);\n console.error(` Method: ${v.methodName} (${v.lines} lines, limit: ${limit})`);\n }\n console.error('');\n if (disableAllowed) {\n console.error(' Use escape: // webpieces-disable max-lines-new-methods -- [your reason]');\n } else {\n console.error(' These methods MUST be refactored - no escape hatch available (disableAllowed=false).');\n }\n console.error('');\n}\n\nexport async function runNewMethods(\n options: MaxMethodLinesConfig,\n workspaceRoot: string\n): Promise<ExecutorResult> {\n const limit = options.limit ?? 80;\n const disableAllowed = options.disableAllowed ?? true;\n\n const rawMode: MethodLimitMode = options.mode ?? 'NEW_AND_MODIFIED_METHODS';\n const skip = rawMode !== 'OFF' ? shouldSkipRule(options.ignoreModifiedUntilEpoch, options.ignoreRuleWhileOnBranch) : { skip: false };\n const mode: MethodLimitMode = skip.skip ? 'OFF' : rawMode;\n\n // Skip validation entirely if mode is OFF\n if (mode === 'OFF') {\n const reason = skip.skip ? skip.reason : 'mode: OFF';\n console.log(`\\n\\u23ed\\ufe0f Skipping new method validation (${reason})`);\n console.log('');\n return { success: true };\n }\n\n // Check if running in affected mode via NX_BASE, or auto-detect\n // If NX_HEAD is set (via nx affected --head=X), use it; otherwise compare to working tree\n let base = process.env['NX_BASE'];\n const head = process.env['NX_HEAD'];\n\n if (!base) {\n // Try to auto-detect base from git merge-base\n base = detectBase(workspaceRoot) ?? undefined;\n\n if (!base) {\n console.log('\\n\\u23ed\\ufe0f Skipping new method validation (could not detect base branch)');\n console.log(' To run explicitly: nx affected --target=validate-new-methods --base=origin/main');\n console.log('');\n return { success: true };\n }\n\n console.log('\\n\\ud83d\\udccf Validating New Method Sizes (auto-detected base)\\n');\n } else {\n console.log('\\n\\ud83d\\udccf Validating New Method Sizes\\n');\n }\n\n console.log(` Base: ${base}`);\n console.log(` Head: ${head ?? 'working tree (includes uncommitted changes)'}`);\n console.log(` Mode: ${mode}`);\n console.log(` Limit for new methods: ${limit} lines (${disableAllowed ? 'can escape' : 'NO escape possible'})`);\n console.log('');\n\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n // Get changed TypeScript files (base to head, or working tree if head not set)\n const changedFiles = getChangedFiles(workspaceRoot, base, head);\n\n if (changedFiles.length === 0) {\n console.log('\\u2705 No TypeScript files changed');\n return { success: true };\n }\n\n console.log(`\\ud83d\\udcc2 Checking ${changedFiles.length} changed file(s)...`);\n\n // Find violations\n const violations = findViolations(workspaceRoot, changedFiles, base, limit, disableAllowed, head);\n\n if (violations.length === 0) {\n console.log('\\u2705 All new methods are within ' + limit + ' lines');\n return { success: true };\n }\n\n // Write instructions file and report violations\n writeTmpInstructions(workspaceRoot);\n reportViolations(violations, limit, disableAllowed);\n\n return { success: false };\n } catch (err: unknown) {\n //const error = toError(err);\n const error = err instanceof Error ? err : new Error(String(err));\n console.error('\\u274c New method validation failed:', error.message);\n return { success: false };\n }\n}\n"]}
@@ -20,7 +20,7 @@
20
20
  * ============================================================================
21
21
  * - OFF: Skip validation entirely
22
22
  * - MODIFIED_CODE: Flag any/unknown on changed lines (lines in diff hunks)
23
- * - MODIFIED_FILES: Flag ALL any/unknown in files that were modified
23
+ * - NEW_AND_MODIFIED_FILES: Flag ALL any/unknown in files that were modified
24
24
  *
25
25
  * ============================================================================
26
26
  * ESCAPE HATCH
@@ -21,7 +21,7 @@
21
21
  * ============================================================================
22
22
  * - OFF: Skip validation entirely
23
23
  * - MODIFIED_CODE: Flag any/unknown on changed lines (lines in diff hunks)
24
- * - MODIFIED_FILES: Flag ALL any/unknown in files that were modified
24
+ * - NEW_AND_MODIFIED_FILES: Flag ALL any/unknown in files that were modified
25
25
  *
26
26
  * ============================================================================
27
27
  * ESCAPE HATCH
@@ -33,113 +33,12 @@
33
33
  Object.defineProperty(exports, "__esModule", { value: true });
34
34
  exports.NoAnyUnknownValidator = void 0;
35
35
  const tslib_1 = require("tslib");
36
- const child_process_1 = require("child_process");
37
36
  const fs = tslib_1.__importStar(require("fs"));
38
37
  const path = tslib_1.__importStar(require("path"));
39
38
  const ts = tslib_1.__importStar(require("typescript"));
40
39
  const rules_config_1 = require("@webpieces/rules-config");
41
40
  const code_validator_1 = require("./code-validator");
42
41
  const resolve_mode_1 = require("./resolve-mode");
43
- /**
44
- * Get changed TypeScript files between base and head (or working tree if head not specified).
45
- */
46
- // webpieces-disable max-lines-new-methods -- Git command handling with untracked files requires multiple code paths
47
- function getChangedTypeScriptFiles(workspaceRoot, base, head) {
48
- // eslint-disable-next-line @webpieces/no-unmanaged-exceptions
49
- try {
50
- const diffTarget = head ? `${base} ${head}` : base;
51
- const output = (0, child_process_1.execSync)(`git diff --name-only ${diffTarget} -- '*.ts' '*.tsx'`, {
52
- cwd: workspaceRoot,
53
- encoding: 'utf-8',
54
- });
55
- const changedFiles = output
56
- .trim()
57
- .split('\n')
58
- .filter((f) => f && !f.includes('.spec.ts') && !f.includes('.test.ts'));
59
- if (!head) {
60
- // eslint-disable-next-line @webpieces/no-unmanaged-exceptions
61
- try {
62
- const untrackedOutput = (0, child_process_1.execSync)(`git ls-files --others --exclude-standard '*.ts' '*.tsx'`, {
63
- cwd: workspaceRoot,
64
- encoding: 'utf-8',
65
- });
66
- const untrackedFiles = untrackedOutput
67
- .trim()
68
- .split('\n')
69
- .filter((f) => f && !f.includes('.spec.ts') && !f.includes('.test.ts'));
70
- const allFiles = new Set([...changedFiles, ...untrackedFiles]);
71
- return Array.from(allFiles);
72
- }
73
- catch (err) {
74
- //const error = toError(err);
75
- return changedFiles;
76
- }
77
- }
78
- return changedFiles;
79
- }
80
- catch (err) {
81
- //const error = toError(err);
82
- return [];
83
- }
84
- }
85
- /**
86
- * Get the diff content for a specific file.
87
- */
88
- function getFileDiff(workspaceRoot, file, base, head) {
89
- // eslint-disable-next-line @webpieces/no-unmanaged-exceptions
90
- try {
91
- const diffTarget = head ? `${base} ${head}` : base;
92
- const diff = (0, child_process_1.execSync)(`git diff ${diffTarget} -- "${file}"`, {
93
- cwd: workspaceRoot,
94
- encoding: 'utf-8',
95
- });
96
- if (!diff && !head) {
97
- const fullPath = path.join(workspaceRoot, file);
98
- if (fs.existsSync(fullPath)) {
99
- const isUntracked = (0, child_process_1.execSync)(`git ls-files --others --exclude-standard "${file}"`, {
100
- cwd: workspaceRoot,
101
- encoding: 'utf-8',
102
- }).trim();
103
- if (isUntracked) {
104
- const content = fs.readFileSync(fullPath, 'utf-8');
105
- const lines = content.split('\n');
106
- return lines.map((line) => `+${line}`).join('\n');
107
- }
108
- }
109
- }
110
- return diff;
111
- }
112
- catch (err) {
113
- //const error = toError(err);
114
- return '';
115
- }
116
- }
117
- /**
118
- * Parse diff to extract changed line numbers (additions only - lines starting with +).
119
- */
120
- function getChangedLineNumbers(diffContent) {
121
- const changedLines = new Set();
122
- const lines = diffContent.split('\n');
123
- let currentLine = 0;
124
- for (const line of lines) {
125
- const hunkMatch = line.match(/^@@ -\d+(?:,\d+)? \+(\d+)(?:,\d+)? @@/);
126
- if (hunkMatch) {
127
- currentLine = parseInt(hunkMatch[1], 10);
128
- continue;
129
- }
130
- if (line.startsWith('+') && !line.startsWith('+++')) {
131
- changedLines.add(currentLine);
132
- currentLine++;
133
- }
134
- else if (line.startsWith('-') && !line.startsWith('---')) {
135
- // Deletions don't increment line number
136
- }
137
- else {
138
- currentLine++;
139
- }
140
- }
141
- return changedLines;
142
- }
143
42
  /**
144
43
  * Check if a line contains a webpieces-disable comment for no-any-unknown.
145
44
  */
@@ -309,8 +208,8 @@ function findAnyUnknownInFile(filePath, workspaceRoot) {
309
208
  function findViolationsForModifiedCode(workspaceRoot, changedFiles, base, head, disableAllowed) {
310
209
  const violations = [];
311
210
  for (const file of changedFiles) {
312
- const diff = getFileDiff(workspaceRoot, file, base, head);
313
- const changedLines = getChangedLineNumbers(diff);
211
+ const diff = (0, rules_config_1.getFileDiff)(workspaceRoot, file, base, head);
212
+ const changedLines = (0, rules_config_1.getChangedLineNumbers)(diff);
314
213
  if (changedLines.size === 0)
315
214
  continue;
316
215
  const allViolations = findAnyUnknownInFile(file, workspaceRoot);
@@ -332,7 +231,7 @@ function findViolationsForModifiedCode(workspaceRoot, changedFiles, base, head,
332
231
  return violations;
333
232
  }
334
233
  /**
335
- * MODIFIED_FILES mode: Flag ALL violations in files that were modified.
234
+ * NEW_AND_MODIFIED_FILES mode: Flag ALL violations in files that were modified.
336
235
  */
337
236
  function findViolationsForModifiedFiles(workspaceRoot, changedFiles, disableAllowed) {
338
237
  const violations = [];
@@ -352,41 +251,6 @@ function findViolationsForModifiedFiles(workspaceRoot, changedFiles, disableAllo
352
251
  }
353
252
  return violations;
354
253
  }
355
- /**
356
- * Auto-detect the base branch by finding the merge-base with origin/main.
357
- */
358
- function detectBase(workspaceRoot) {
359
- // eslint-disable-next-line @webpieces/no-unmanaged-exceptions
360
- try {
361
- const mergeBase = (0, child_process_1.execSync)('git merge-base HEAD origin/main', {
362
- cwd: workspaceRoot,
363
- encoding: 'utf-8',
364
- stdio: ['pipe', 'pipe', 'pipe'],
365
- }).trim();
366
- if (mergeBase) {
367
- return mergeBase;
368
- }
369
- }
370
- catch (err) {
371
- //const error = toError(err);
372
- // eslint-disable-next-line @webpieces/no-unmanaged-exceptions
373
- try {
374
- const mergeBase = (0, child_process_1.execSync)('git merge-base HEAD main', {
375
- cwd: workspaceRoot,
376
- encoding: 'utf-8',
377
- stdio: ['pipe', 'pipe', 'pipe'],
378
- }).trim();
379
- if (mergeBase) {
380
- return mergeBase;
381
- }
382
- }
383
- catch (err2) {
384
- //const error2 = toError(err2);
385
- // Ignore
386
- }
387
- }
388
- return null;
389
- }
390
254
  /**
391
255
  * Report violations to console.
392
256
  */
@@ -444,7 +308,7 @@ async function runValidatorImpl(options, workspaceRoot) {
444
308
  let base = process.env['NX_BASE'];
445
309
  const head = process.env['NX_HEAD'];
446
310
  if (!base) {
447
- base = detectBase(workspaceRoot) ?? undefined;
311
+ base = (0, rules_config_1.detectBase)(workspaceRoot) ?? undefined;
448
312
  if (!base) {
449
313
  console.log('\n⏭️ Skipping no-any-unknown validation (could not detect base branch)');
450
314
  console.log('');
@@ -454,7 +318,7 @@ async function runValidatorImpl(options, workspaceRoot) {
454
318
  console.log(` Base: ${base}`);
455
319
  console.log(` Head: ${head ?? 'working tree (includes uncommitted changes)'}`);
456
320
  console.log('');
457
- const changedFiles = getChangedTypeScriptFiles(workspaceRoot, base, head);
321
+ const changedFiles = (0, rules_config_1.getChangedFiles)(workspaceRoot, base, head);
458
322
  if (changedFiles.length === 0) {
459
323
  console.log('✅ No TypeScript files changed');
460
324
  return { success: true };
@@ -464,7 +328,7 @@ async function runValidatorImpl(options, workspaceRoot) {
464
328
  if (mode === 'MODIFIED_CODE') {
465
329
  violations = findViolationsForModifiedCode(workspaceRoot, changedFiles, base, head, disableAllowed);
466
330
  }
467
- else if (mode === 'MODIFIED_FILES') {
331
+ else if (mode === 'NEW_AND_MODIFIED_FILES') {
468
332
  violations = findViolationsForModifiedFiles(workspaceRoot, changedFiles, disableAllowed);
469
333
  }
470
334
  if (violations.length === 0) {