Buckets:
ktongue/docker_container / .vscode-server /extensions /ms-python.vscode-python-envs-1.20.1-linux-arm64 /.github /hooks /scripts /post_tool_use.py
| # Copyright (c) Microsoft Corporation. All rights reserved. | |
| # Licensed under the MIT License. | |
| """PostToolUse hook - Runs validation after file edits. | |
| After editFiles tool: | |
| - Runs ESLint on changed TypeScript files | |
| - Reports lint errors back to the model as additional context | |
| """ | |
| import json | |
| import os | |
| import subprocess | |
| import sys | |
| from pathlib import Path | |
| from typing import Dict, List, Optional | |
| # Tools that modify files and should trigger validation | |
| FILE_EDIT_TOOLS = {"editFiles", "createFile", "create_file", "replace_string_in_file"} | |
| # File patterns to validate | |
| TYPESCRIPT_EXTENSIONS = {".ts", ".tsx"} | |
| def run_eslint(files: List[str], cwd: Path) -> Optional[str]: | |
| """Run ESLint on specified files and return errors.""" | |
| ts_files = [f for f in files if Path(f).suffix in TYPESCRIPT_EXTENSIONS] | |
| if not ts_files: | |
| return None | |
| try: | |
| result = subprocess.run( | |
| ["npx", "eslint", "--format", "compact", *ts_files], | |
| cwd=cwd, | |
| capture_output=True, | |
| text=True, | |
| timeout=30, | |
| ) | |
| if result.returncode != 0 and result.stdout: | |
| # Parse compact format and summarize | |
| lines = result.stdout.strip().split("\n") | |
| error_count = sum( | |
| 1 for line in lines if "Error" in line or "error" in line.lower() | |
| ) | |
| warning_count = sum( | |
| 1 for line in lines if "Warning" in line or "warning" in line.lower() | |
| ) | |
| if error_count > 0 or warning_count > 0: | |
| summary = [] | |
| if error_count > 0: | |
| summary.append(f"{error_count} error(s)") | |
| if warning_count > 0: | |
| summary.append(f"{warning_count} warning(s)") | |
| # Include first few actual errors | |
| sample_errors = [ | |
| line | |
| for line in lines[:5] | |
| if "Error" in line or "error" in line.lower() | |
| ] | |
| return f"ESLint: {', '.join(summary)}. " + " | ".join(sample_errors[:3]) | |
| except (subprocess.TimeoutExpired, FileNotFoundError): | |
| # ESLint failures (timeout or missing binary) should not block the hook; | |
| # treat them as "no lint results" and continue without reporting lint output. | |
| pass | |
| return None | |
| def extract_files_from_tool_input(tool_name: str, tool_input: Dict) -> List[str]: | |
| """Extract file paths from tool input based on tool type.""" | |
| files = [] | |
| if tool_name in {"editFiles", "edit_files"}: | |
| # editFiles has a 'files' array | |
| if isinstance(tool_input, dict): | |
| files.extend(tool_input.get("files", [])) | |
| elif tool_name in {"createFile", "create_file"}: | |
| # createFile has 'filePath' | |
| if isinstance(tool_input, dict) and "filePath" in tool_input: | |
| files.append(tool_input["filePath"]) | |
| elif tool_name == "replace_string_in_file": | |
| # replace_string_in_file has 'filePath' | |
| if isinstance(tool_input, dict) and "filePath" in tool_input: | |
| files.append(tool_input["filePath"]) | |
| return files | |
| def main() -> int: | |
| """Main entry point.""" | |
| # Read input from stdin | |
| try: | |
| input_data = json.load(sys.stdin) | |
| except json.JSONDecodeError: | |
| input_data = {} | |
| tool_name = input_data.get("tool_name", "") | |
| tool_input = input_data.get("tool_input", {}) | |
| repo_root = Path(input_data.get("cwd", os.getcwd())) | |
| # Only process file edit tools | |
| if tool_name not in FILE_EDIT_TOOLS: | |
| print(json.dumps({})) | |
| return 0 | |
| # Extract files that were edited | |
| files = extract_files_from_tool_input(tool_name, tool_input) | |
| if not files: | |
| print(json.dumps({})) | |
| return 0 | |
| # Run ESLint on TypeScript files | |
| lint_result = run_eslint(files, repo_root) | |
| # Build response | |
| if lint_result: | |
| response = { | |
| "hookSpecificOutput": { | |
| "hookEventName": "PostToolUse", | |
| "additionalContext": lint_result, | |
| } | |
| } | |
| else: | |
| response = {} | |
| print(json.dumps(response)) | |
| return 0 | |
| if __name__ == "__main__": | |
| sys.exit(main()) | |
Xet Storage Details
- Size:
- 4.21 kB
- Xet hash:
- cde49ddd4f6f99ba449c71845a7dc9f49a6d2255b5dacbba2da76bb5c48d2fab
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.