Buckets:
ktongue/docker_container / .vscode-server /extensions /ms-python.vscode-python-envs-1.20.1-linux-arm64 /.github /hooks /scripts /stop_hook.py
| # Copyright (c) Microsoft Corporation. All rights reserved. | |
| # Licensed under the MIT License. | |
| """Stop hook - Ensures pre-commit checks were run before session ends. | |
| For maintainer sessions that modified files: | |
| - Checks if there are uncommitted changes | |
| - Verifies lint/type-check/tests passed or reminds to run them | |
| """ | |
| import json | |
| import os | |
| import subprocess | |
| import sys | |
| from pathlib import Path | |
| from typing import List, Optional, Tuple | |
| def run_command(cmd: List[str], cwd: Optional[Path] = None) -> Tuple[int, str]: | |
| """Run a command and return (exit_code, output).""" | |
| try: | |
| result = subprocess.run( | |
| cmd, | |
| cwd=cwd, | |
| capture_output=True, | |
| text=True, | |
| timeout=10, | |
| ) | |
| return result.returncode, result.stdout.strip() | |
| except (subprocess.TimeoutExpired, FileNotFoundError) as e: | |
| return 1, str(e) | |
| def has_uncommitted_changes(repo_root: Path) -> bool: | |
| """Check if there are uncommitted changes (tracked files only).""" | |
| code, output = run_command(["git", "status", "--porcelain"], repo_root) | |
| if code == 0 and output: | |
| # Filter to only tracked files (staged/modified, not untracked with ??) | |
| lines = [ | |
| line | |
| for line in output.split("\n") | |
| if line.strip() and not line.strip().startswith("??") | |
| ] | |
| return len(lines) > 0 | |
| return False | |
| def has_untracked_ts_files(repo_root: Path) -> bool: | |
| """Check if there are untracked TypeScript files.""" | |
| code, output = run_command(["git", "status", "--porcelain"], repo_root) | |
| if code == 0 and output: | |
| for line in output.split("\n"): | |
| if line.strip().startswith("??"): | |
| # Extract filename from untracked entry (format: "?? path/to/file") | |
| filename = line.strip()[3:].strip() | |
| if filename.endswith((".ts", ".tsx")): | |
| return True | |
| return False | |
| def has_staged_changes(repo_root: Path) -> bool: | |
| """Check if there are staged but uncommitted changes.""" | |
| code, output = run_command(["git", "diff", "--cached", "--name-only"], repo_root) | |
| return code == 0 and bool(output.strip()) | |
| def check_ts_files_changed(repo_root: Path) -> bool: | |
| """Check if any TypeScript files were changed.""" | |
| code, output = run_command( | |
| ["git", "diff", "--name-only", "HEAD"], | |
| repo_root, | |
| ) | |
| if code == 0 and output: | |
| return any(f.endswith((".ts", ".tsx")) for f in output.split("\n")) | |
| # Also check staged files | |
| code, output = run_command( | |
| ["git", "diff", "--cached", "--name-only"], | |
| repo_root, | |
| ) | |
| if code == 0 and output: | |
| return any(f.endswith((".ts", ".tsx")) for f in output.split("\n")) | |
| return False | |
| def main() -> int: | |
| """Main entry point.""" | |
| # Read input from stdin | |
| try: | |
| input_data = json.load(sys.stdin) | |
| except json.JSONDecodeError: | |
| input_data = {} | |
| repo_root = Path(input_data.get("cwd", os.getcwd())) | |
| # Check if this is already a continuation from a previous stop hook | |
| stop_hook_active = input_data.get("stop_hook_active", False) | |
| if stop_hook_active: | |
| # Don't block again to prevent infinite loop | |
| print(json.dumps({})) | |
| return 0 | |
| # Check for uncommitted TypeScript changes (including new untracked TS files) | |
| ts_work_present = ( | |
| has_uncommitted_changes(repo_root) and check_ts_files_changed(repo_root) | |
| ) or has_untracked_ts_files(repo_root) | |
| if ts_work_present: | |
| # There are uncommitted TS changes - remind about pre-commit checks | |
| response = { | |
| "hookSpecificOutput": { | |
| "hookEventName": "Stop", | |
| "decision": "block", | |
| "reason": ( | |
| "You have uncommitted TypeScript changes. " | |
| "Before finishing, use the run-pre-commit-checks skill " | |
| "or manually run: npm run lint && npm run compile-tests && npm run unittest. " | |
| "If checks pass and changes are ready, commit them. " | |
| "If this session is just research/exploration, you can proceed without committing." | |
| ), | |
| } | |
| } | |
| print(json.dumps(response)) | |
| return 0 | |
| # Check for staged but uncommitted changes | |
| if has_staged_changes(repo_root): | |
| response = { | |
| "hookSpecificOutput": { | |
| "hookEventName": "Stop", | |
| "decision": "block", | |
| "reason": ( | |
| "You have staged changes that haven't been committed. " | |
| "Either commit them with a proper message or unstage them before finishing." | |
| ), | |
| } | |
| } | |
| print(json.dumps(response)) | |
| return 0 | |
| # All good | |
| print(json.dumps({})) | |
| return 0 | |
| if __name__ == "__main__": | |
| sys.exit(main()) | |
Xet Storage Details
- Size:
- 4.91 kB
- Xet hash:
- b04dcad15d3b5bbd191a3926abae7047b82ac9ef3c0d5c66d070316f3ed1fb13
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.