From 8fb0d45fac997a3b3fd4a80789260c5bb0734484 Mon Sep 17 00:00:00 2001 From: quotentiroler Date: Sun, 8 Feb 2026 04:21:20 -0800 Subject: [PATCH] update --- scripts/analyze_code_files.py | 219 ++++++++++++++++++++++++++++++++++ src/entry.ts | 2 +- src/infra/warnings.ts | 1 - src/memory/sqlite.ts | 2 +- test/setup.ts | 2 +- 5 files changed, 222 insertions(+), 4 deletions(-) create mode 100644 scripts/analyze_code_files.py delete mode 100644 src/infra/warnings.ts diff --git a/scripts/analyze_code_files.py b/scripts/analyze_code_files.py new file mode 100644 index 0000000000..638f1f498c --- /dev/null +++ b/scripts/analyze_code_files.py @@ -0,0 +1,219 @@ +#!/usr/bin/env python3 +""" +Lists the longest and shortest code files in the project. +Threshold can be set to warn about files longer or shorter than a certain number of lines. +""" + +import os +import argparse +from pathlib import Path +from typing import List, Tuple + +# File extensions to consider as code files +CODE_EXTENSIONS = { + '.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs', # TypeScript/JavaScript + '.swift', # macOS/iOS + '.kt', '.java', # Android + '.py', '.sh', # Scripts +} + +# Directories to skip +SKIP_DIRS = { + 'node_modules', '.git', 'dist', 'build', 'coverage', + '__pycache__', '.turbo', 'out', '.worktrees', 'vendor', + 'Pods', 'DerivedData', '.gradle', '.idea' +} + +# Filename patterns to skip in short-file warnings (barrel exports, stubs) +SKIP_SHORT_PATTERNS = { + 'index.js', 'index.ts', 'postinstall.js', +} +SKIP_SHORT_SUFFIXES = ('-cli.ts',) + +# Known packages in the monorepo +PACKAGES = { + 'src', 'apps', 'extensions', 'packages', 'scripts', 'ui', 'test', 'docs' +} + + +def get_package(file_path: Path, root_dir: Path) -> str: + """Get the package name for a file, or 'root' if at top level.""" + try: + relative = file_path.relative_to(root_dir) + parts = relative.parts + if len(parts) > 0 and parts[0] in PACKAGES: + return parts[0] + return 'root' + except ValueError: + return 'root' + + +def count_lines(file_path: Path) -> int: + """Count the number of lines in a file.""" + try: + with open(file_path, 'r', encoding='utf-8', errors='ignore') as f: + return sum(1 for _ in f) + except Exception: + return 0 + + +def find_code_files(root_dir: Path) -> List[Tuple[Path, int]]: + """Find all code files and their line counts.""" + files_with_counts = [] + + for dirpath, dirnames, filenames in os.walk(root_dir): + # Remove skip directories from dirnames to prevent walking into them + dirnames[:] = [d for d in dirnames if d not in SKIP_DIRS] + + for filename in filenames: + file_path = Path(dirpath) / filename + if file_path.suffix.lower() in CODE_EXTENSIONS: + line_count = count_lines(file_path) + files_with_counts.append((file_path, line_count)) + + return files_with_counts + + +def main(): + parser = argparse.ArgumentParser( + description='List the longest and shortest code files in a project' + ) + parser.add_argument( + '-t', '--threshold', + type=int, + default=1000, + help='Warn about files longer than this many lines (default: 1000)' + ) + parser.add_argument( + '--min-threshold', + type=int, + default=10, + help='Warn about files shorter than this many lines (default: 10)' + ) + parser.add_argument( + '-n', '--top', + type=int, + default=20, + help='Show top N longest files (default: 20)' + ) + parser.add_argument( + '-b', '--bottom', + type=int, + default=10, + help='Show bottom N shortest files (default: 10)' + ) + parser.add_argument( + '-d', '--directory', + type=str, + default='.', + help='Directory to scan (default: current directory)' + ) + + args = parser.parse_args() + + root_dir = Path(args.directory).resolve() + print(f"\n📂 Scanning: {root_dir}\n") + + # Find and sort files by line count + files = find_code_files(root_dir) + files_desc = sorted(files, key=lambda x: x[1], reverse=True) + files_asc = sorted(files, key=lambda x: x[1]) + + # Show top N longest files + top_files = files_desc[:args.top] + + print(f"📊 Top {min(args.top, len(top_files))} longest code files:\n") + print(f"{'Lines':>8} {'File'}") + print("-" * 60) + + long_warnings = [] + + for file_path, line_count in top_files: + relative_path = file_path.relative_to(root_dir) + + # Check if over threshold + if line_count >= args.threshold: + marker = " ⚠️" + long_warnings.append((relative_path, line_count)) + else: + marker = "" + + print(f"{line_count:>8} {relative_path}{marker}") + + # Show bottom N shortest files + bottom_files = files_asc[:args.bottom] + + print(f"\n📉 Bottom {min(args.bottom, len(bottom_files))} shortest code files:\n") + print(f"{'Lines':>8} {'File'}") + print("-" * 60) + + short_warnings = [] + + for file_path, line_count in bottom_files: + relative_path = file_path.relative_to(root_dir) + filename = file_path.name + + # Skip known barrel exports and stubs + is_expected_short = ( + filename in SKIP_SHORT_PATTERNS or + any(filename.endswith(suffix) for suffix in SKIP_SHORT_SUFFIXES) + ) + + # Check if under threshold + if line_count <= args.min_threshold and not is_expected_short: + marker = " ⚠️" + short_warnings.append((relative_path, line_count)) + else: + marker = "" + + print(f"{line_count:>8} {relative_path}{marker}") + + # Summary + total_files = len(files) + total_lines = sum(count for _, count in files) + + print("-" * 60) + print(f"\n📈 Summary:") + print(f" Total code files: {total_files:,}") + print(f" Total lines: {total_lines:,}") + print(f" Average lines/file: {total_lines // total_files if total_files else 0:,}") + + # Per-package breakdown + package_stats: dict[str, dict] = {} + for file_path, line_count in files: + pkg = get_package(file_path, root_dir) + if pkg not in package_stats: + package_stats[pkg] = {'files': 0, 'lines': 0} + package_stats[pkg]['files'] += 1 + package_stats[pkg]['lines'] += line_count + + print(f"\n📦 Per-package breakdown:\n") + print(f"{'Package':<15} {'Files':>8} {'Lines':>10} {'Avg':>8}") + print("-" * 45) + + for pkg in sorted(package_stats.keys(), key=lambda p: package_stats[p]['lines'], reverse=True): + stats = package_stats[pkg] + avg = stats['lines'] // stats['files'] if stats['files'] else 0 + print(f"{pkg:<15} {stats['files']:>8,} {stats['lines']:>10,} {avg:>8,}") + + # Long file warnings + if long_warnings: + print(f"\n⚠️ Warning: {len(long_warnings)} file(s) exceed {args.threshold} lines (consider refactoring):") + for path, count in long_warnings: + print(f" - {path} ({count:,} lines)") + else: + print(f"\n✅ No files exceed {args.threshold} lines") + + # Short file warnings + if short_warnings: + print(f"\n⚠️ Warning: {len(short_warnings)} file(s) are {args.min_threshold} lines or less (check if needed):") + for path, count in short_warnings: + print(f" - {path} ({count} lines)") + else: + print(f"\n✅ No files are {args.min_threshold} lines or less") + + print() + + +if __name__ == '__main__': + main() diff --git a/src/entry.ts b/src/entry.ts index bbf2173a36..81ce2c3e60 100644 --- a/src/entry.ts +++ b/src/entry.ts @@ -4,7 +4,7 @@ import path from "node:path"; import process from "node:process"; import { applyCliProfileEnv, parseCliProfileArgs } from "./cli/profile.js"; import { isTruthyEnvValue, normalizeEnv } from "./infra/env.js"; -import { installProcessWarningFilter } from "./infra/warnings.js"; +import { installProcessWarningFilter } from "./infra/warning-filter.js"; import { attachChildProcessBridge } from "./process/child-process-bridge.js"; process.title = "openclaw"; diff --git a/src/infra/warnings.ts b/src/infra/warnings.ts deleted file mode 100644 index e8b048ddb9..0000000000 --- a/src/infra/warnings.ts +++ /dev/null @@ -1 +0,0 @@ -export { installProcessWarningFilter } from "./warning-filter.js"; diff --git a/src/memory/sqlite.ts b/src/memory/sqlite.ts index bbda48dd20..00308fb607 100644 --- a/src/memory/sqlite.ts +++ b/src/memory/sqlite.ts @@ -1,5 +1,5 @@ import { createRequire } from "node:module"; -import { installProcessWarningFilter } from "../infra/warnings.js"; +import { installProcessWarningFilter } from "../infra/warning-filter.js"; const require = createRequire(import.meta.url); diff --git a/test/setup.ts b/test/setup.ts index 215935b930..725554b7f3 100644 --- a/test/setup.ts +++ b/test/setup.ts @@ -10,7 +10,7 @@ import type { } from "../src/channels/plugins/types.js"; import type { OpenClawConfig } from "../src/config/config.js"; import type { OutboundSendDeps } from "../src/infra/outbound/deliver.js"; -import { installProcessWarningFilter } from "../src/infra/warnings.js"; +import { installProcessWarningFilter } from "../src/infra/warning-filter.js"; import { setActivePluginRegistry } from "../src/plugins/runtime.js"; import { createTestRegistry } from "../src/test-utils/channel-plugins.js"; import { withIsolatedTestHome } from "./test-env";