diff options
| author | Omni Worker <bot@omni.agent> | 2025-11-22 06:51:40 -0500 |
|---|---|---|
| committer | Omni Worker <bot@omni.agent> | 2025-11-22 06:51:40 -0500 |
| commit | fcc58e142eef98febdd385f65d4a28a869818110 (patch) | |
| tree | 1883d4805ddb0ff1b575e856236d0ed9e46b17d0 /Omni/Bild | |
| parent | efbb7cf298585cfff5336d048e7d1af2dd94d560 (diff) | |
fix: rename to .py and fix lint errors
Amp-Thread-ID:
https://ampcode.com/threads/T-ec034efc-0ef1-43df-8abe-b0cda0f9115b
Co-authored-by: Amp <amp@ampcode.com>
Diffstat (limited to 'Omni/Bild')
| -rwxr-xr-x | Omni/Bild/Audit.py | 176 | ||||
| -rwxr-xr-x | Omni/Bild/audit.sh | 143 |
2 files changed, 176 insertions, 143 deletions
diff --git a/Omni/Bild/Audit.py b/Omni/Bild/Audit.py new file mode 100755 index 0000000..4df6c0b --- /dev/null +++ b/Omni/Bild/Audit.py @@ -0,0 +1,176 @@ +#!/usr/bin/env python3 +""" +Audit codebase builds. + +Iterates through every namespace in the project and runs 'bild'. +For every build failure encountered, it automatically creates a new task. +""" + +# : out bild-audit + +import argparse +import re +import shutil +import subprocess +import sys +from pathlib import Path + +# Extensions supported by bild (from Omni/Bild.hs and Omni/Namespace.hs) +EXTENSIONS = {".c", ".hs", ".lisp", ".nix", ".py", ".scm", ".rs", ".toml"} +MAX_TITLE_LENGTH = 50 + + +def strip_ansi(text: str) -> str: + """Strip ANSI escape codes from text.""" + ansi_escape = re.compile(r"\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])") + return ansi_escape.sub("", text) + + +def is_ignored(path: Path) -> bool: + """Check if a file is ignored by git.""" + res = subprocess.run( + ["git", "check-ignore", str(path)], + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + check=False, + ) + return res.returncode == 0 + + +def get_buildable_files(root_dir: str = ".") -> list[str]: + """Find all files that bild can build.""" + targets: list[str] = [] + + root = Path(root_dir) + if not root.exists(): + return [] + + for path in root.rglob("*"): + # Skip directories + if path.is_dir(): + continue + + # Skip hidden files/dirs and '_' dirs + parts = path.parts + if any(p.startswith(".") or p == "_" for p in parts): + continue + + if path.suffix in EXTENSIONS: + # Clean up path: keep it relative to cwd if possible + try: + # We want the path as a string, relative to current directory + # if possible + p_str = ( + str(path.relative_to(Path.cwd())) + if path.is_absolute() + else str(path) + ) + except ValueError: + p_str = str(path) + + if not is_ignored(Path(p_str)): + targets.append(p_str) + return targets + + +def run_bild(target: str) -> subprocess.CompletedProcess[str]: + """Run bild on the target.""" + # --time 0 disables timeout + # --loud enables output (which we capture) + cmd = ["bild", "--time", "0", "--loud", target] + return subprocess.run(cmd, capture_output=True, text=True, check=False) + + +def create_task( + target: str, + result: subprocess.CompletedProcess[str], + parent_id: str | None = None, +) -> None: + """Create a task for a build failure.""" + # Construct a descriptive title + # Try to get the last meaningful line of error output + lines = (result.stdout + result.stderr).strip().split("\n") + last_line = lines[-1] if lines else "Unknown error" + last_line = strip_ansi(last_line).strip() + + if len(last_line) > MAX_TITLE_LENGTH: + last_line = last_line[: MAX_TITLE_LENGTH - 3] + "..." + + title = f"Build failed: {target} - {last_line}" + + cmd = ["task", "create", title, "--priority", "2", "--json"] + + if parent_id: + cmd.append(f"--discovered-from={parent_id}") + + # Try to infer namespace + # e.g. Omni/Bild.hs -> Omni/Bild + ns = Path(target).parent + if str(ns) != ".": + cmd.append(f"--namespace={ns}") + + print(f"Creating task for {target}...") # noqa: T201 + proc = subprocess.run(cmd, capture_output=True, text=True, check=False) + + if proc.returncode != 0: + print(f"Error creating task: {proc.stderr}", file=sys.stderr) # noqa: T201 + else: + # task create --json returns the created task json + print(f"Task created: {proc.stdout.strip()}") # noqa: T201 + + +def main() -> None: + """Run the build audit.""" + parser = argparse.ArgumentParser(description="Audit codebase builds.") + parser.add_argument( + "--parent", + help="Parent task ID to link discovered tasks to", + ) + parser.add_argument( + "paths", + nargs="*", + default=["."], + help="Paths to search for targets", + ) + args = parser.parse_args() + + # Check if bild is available + if not shutil.which("bild"): + print( # noqa: T201 + "Warning: 'bild' command not found. Ensure it is in PATH.", + file=sys.stderr, + ) + + print(f"Scanning for targets in {args.paths}...") # noqa: T201 + targets: list[str] = [] + for path_str in args.paths: + path = Path(path_str) + if path.is_file(): + targets.append(str(path)) + else: + targets.extend(get_buildable_files(path_str)) + + # Remove duplicates + targets = sorted(set(targets)) + print(f"Found {len(targets)} targets.") # noqa: T201 + + failures = 0 + for target in targets: + res = run_bild(target) + + if res.returncode == 0: + print("OK") # noqa: T201 + else: + print("FAIL") # noqa: T201 + failures += 1 + create_task(target, res, args.parent) + + print(f"\nAudit complete. {failures} failures found.") # noqa: T201 + if failures > 0: + sys.exit(1) + else: + sys.exit(0) + + +if __name__ == "__main__": + main() diff --git a/Omni/Bild/audit.sh b/Omni/Bild/audit.sh deleted file mode 100755 index 1a0acd5..0000000 --- a/Omni/Bild/audit.sh +++ /dev/null @@ -1,143 +0,0 @@ -#!/usr/bin/env python3 -""" -Audit codebase builds. - -Iterates through every namespace in the project and runs 'bild'. -For every build failure encountered, it automatically creates a new task. -""" - -# : out bild-audit - -import os -import sys -import subprocess -import argparse -import shutil -import re - -# Extensions supported by bild (from Omni/Bild.hs and Omni/Namespace.hs) -EXTENSIONS = {'.c', '.hs', '.lisp', '.nix', '.py', '.scm', '.rs', '.toml'} - -def strip_ansi(text): - """Strip ANSI escape codes from text.""" - ansi_escape = re.compile(r'\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])') - return ansi_escape.sub('', text) - -def is_ignored(path): - """Check if a file is ignored by git.""" - res = subprocess.run( - ['git', 'check-ignore', path], - stdout=subprocess.DEVNULL, - stderr=subprocess.DEVNULL - ) - return res.returncode == 0 - -def get_buildable_files(root_dir='.'): - """Find all files that bild can build.""" - targets = [] - for root, dirs, files in os.walk(root_dir): - # Skip hidden directories, build artifacts (_), and task db (.tasks) - dirs[:] = [d for d in dirs if not d.startswith('.') and d != '_'] - - for f in files: - ext = os.path.splitext(f)[1] - if ext in EXTENSIONS: - path = os.path.join(root, f) - # Clean up path - if path.startswith('./'): - path = path[2:] - - if not is_ignored(path): - targets.append(path) - return targets - -def run_bild(target): - """Run bild on the target.""" - # --time 0 disables timeout - # --loud enables output (which we capture) - cmd = ['bild', '--time', '0', '--loud', target] - result = subprocess.run(cmd, capture_output=True, text=True) - return result - -def create_task(target, result, parent_id=None): - """Create a task for a build failure.""" - # Construct a descriptive title - # Try to get the last meaningful line of error output - lines = (result.stdout + result.stderr).strip().split('\n') - last_line = lines[-1] if lines else "Unknown error" - last_line = strip_ansi(last_line).strip() - - if len(last_line) > 50: - last_line = last_line[:47] + "..." - - title = f"Build failed: {target} - {last_line}" - - cmd = [ - 'task', 'create', title, - '--priority', '2', - '--json' - ] - - if parent_id: - cmd.append(f'--discovered-from={parent_id}') - - # Try to infer namespace - parts = target.split('/') - if len(parts) > 1: - # e.g. Omni/Bild.hs -> Omni/Bild - # Omni/Bild/Audit.py -> Omni/Bild - ns = os.path.dirname(target) - if ns: - cmd.append(f'--namespace={ns}') - - print(f"Creating task for {target}...") - proc = subprocess.run(cmd, capture_output=True, text=True) - - if proc.returncode != 0: - print(f"Error creating task: {proc.stderr}", file=sys.stderr) - else: - # task create --json returns the created task json - print(f"Task created: {proc.stdout.strip()}") - -def main(): - parser = argparse.ArgumentParser(description='Audit codebase builds.') - parser.add_argument('--parent', help='Parent task ID to link discovered tasks to') - parser.add_argument('paths', nargs='*', default=['.'], help='Paths to search for targets') - args = parser.parse_args() - - # Check if bild is available - if not shutil.which('bild'): - print("Warning: 'bild' command not found. Ensure it is in PATH.", file=sys.stderr) - - print(f"Scanning for targets in {args.paths}...") - targets = [] - for path in args.paths: - if os.path.isfile(path): - targets.append(path) - else: - targets.extend(get_buildable_files(path)) - - # Remove duplicates - targets = sorted(list(set(targets))) - print(f"Found {len(targets)} targets.") - - failures = 0 - for i, target in enumerate(targets): - print(f"[{i+1}/{len(targets)}] Building {target} ... ", end='', flush=True) - res = run_bild(target) - - if res.returncode == 0: - print("OK") - else: - print("FAIL") - failures += 1 - create_task(target, res, args.parent) - - print(f"\nAudit complete. {failures} failures found.") - if failures > 0: - sys.exit(1) - else: - sys.exit(0) - -if __name__ == '__main__': - main() |
