forked from microsoft/vscode-python-environments
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathstop_hook.py
More file actions
146 lines (122 loc) · 4.7 KB
/
stop_hook.py
File metadata and controls
146 lines (122 loc) · 4.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
# 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": "warn",
"reason": (
"You have uncommitted TypeScript changes. "
"Before finishing, consider using the run-pre-commit-checks skill "
"or manually run: npm run lint && npm run compile-tests && npm run unittest. "
"Ask the user whether to commit or leave changes uncommitted."
),
}
}
print(json.dumps(response))
return 0
# Check for staged but uncommitted changes
if has_staged_changes(repo_root):
response = {
"hookSpecificOutput": {
"hookEventName": "Stop",
"decision": "warn",
"reason": (
"You have staged changes that haven't been committed. "
"Ask the user whether to commit them or leave them staged."
),
}
}
print(json.dumps(response))
return 0
# All good
print(json.dumps({}))
return 0
if __name__ == "__main__":
sys.exit(main())